Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/mono/aspnetwebstack.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/test
diff options
context:
space:
mode:
authorbradwilson <bradwils@microsoft.com>2012-03-11 21:17:56 +0400
committerbradwilson <bradwils@microsoft.com>2012-03-11 21:17:56 +0400
commit0f8c45fe03e71446fd8287115a1774b549a72314 (patch)
treee26a39eb9ce385aa6993677f44a27446d089c2ff /test
Initial revision.
Diffstat (limited to 'test')
-rw-r--r--test/Microsoft.TestCommon/AppDomainUtils.cs71
-rw-r--r--test/Microsoft.TestCommon/AssertEx.cs31
-rw-r--r--test/Microsoft.TestCommon/CultureReplacer.cs34
-rw-r--r--test/Microsoft.TestCommon/DefaultTimeoutFactAttribute.cs17
-rw-r--r--test/Microsoft.TestCommon/DefaultTimeoutTheoryAttribute.cs14
-rw-r--r--test/Microsoft.TestCommon/DictionaryEqualityComparer.cs47
-rw-r--r--test/Microsoft.TestCommon/ExceptionAssertions.cs515
-rw-r--r--test/Microsoft.TestCommon/MemberHelper.cs381
-rw-r--r--test/Microsoft.TestCommon/Microsoft.TestCommon.csproj116
-rw-r--r--test/Microsoft.TestCommon/Microsoft/TestCommon/DataSets/CommonUnitTestDataSets.cs38
-rw-r--r--test/Microsoft.TestCommon/Microsoft/TestCommon/DataSets/RefTypeTestData.cs67
-rw-r--r--test/Microsoft.TestCommon/Microsoft/TestCommon/DataSets/TestData.cs444
-rw-r--r--test/Microsoft.TestCommon/Microsoft/TestCommon/DataSets/TestDataVariations.cs95
-rw-r--r--test/Microsoft.TestCommon/Microsoft/TestCommon/DataSets/ValueTypeTestData.cs31
-rw-r--r--test/Microsoft.TestCommon/Microsoft/TestCommon/GenericTypeAssert.cs489
-rw-r--r--test/Microsoft.TestCommon/Microsoft/TestCommon/HttpAssert.cs252
-rw-r--r--test/Microsoft.TestCommon/Microsoft/TestCommon/MediaTypeAssert.cs53
-rw-r--r--test/Microsoft.TestCommon/Microsoft/TestCommon/MediaTypeHeaderValueComparer.cs88
-rw-r--r--test/Microsoft.TestCommon/Microsoft/TestCommon/ParsedMediaTypeHeaderValue.cs109
-rw-r--r--test/Microsoft.TestCommon/Microsoft/TestCommon/RegexReplacement.cs38
-rw-r--r--test/Microsoft.TestCommon/Microsoft/TestCommon/RuntimeEnvironment.cs31
-rw-r--r--test/Microsoft.TestCommon/Microsoft/TestCommon/SerializerAssert.cs150
-rw-r--r--test/Microsoft.TestCommon/Microsoft/TestCommon/StreamAssert.cs94
-rw-r--r--test/Microsoft.TestCommon/Microsoft/TestCommon/TaskAssert.cs99
-rw-r--r--test/Microsoft.TestCommon/Microsoft/TestCommon/TestDataSetAttribute.cs193
-rw-r--r--test/Microsoft.TestCommon/Microsoft/TestCommon/TimeoutConstant.cs20
-rw-r--r--test/Microsoft.TestCommon/Microsoft/TestCommon/TypeAssert.cs162
-rw-r--r--test/Microsoft.TestCommon/Microsoft/TestCommon/Types/FlagsEnum.cs12
-rw-r--r--test/Microsoft.TestCommon/Microsoft/TestCommon/Types/INameAndIdContainer.cs12
-rw-r--r--test/Microsoft.TestCommon/Microsoft/TestCommon/Types/ISerializableType.cs73
-rw-r--r--test/Microsoft.TestCommon/Microsoft/TestCommon/Types/LongEnum.cs9
-rw-r--r--test/Microsoft.TestCommon/Microsoft/TestCommon/Types/SimpleEnum.cs9
-rw-r--r--test/Microsoft.TestCommon/Microsoft/TestCommon/XmlAssert.cs77
-rw-r--r--test/Microsoft.TestCommon/PreAppStartTestHelper.cs25
-rw-r--r--test/Microsoft.TestCommon/PreserveSyncContextAttribute.cs24
-rw-r--r--test/Microsoft.TestCommon/Properties/AssemblyInfo.cs35
-rw-r--r--test/Microsoft.TestCommon/ReflectionAssert.cs115
-rw-r--r--test/Microsoft.TestCommon/TaskExtensions.cs25
-rw-r--r--test/Microsoft.TestCommon/TestFile.cs73
-rw-r--r--test/Microsoft.TestCommon/TestHelper.cs29
-rw-r--r--test/Microsoft.TestCommon/TheoryDataSet.cs86
-rw-r--r--test/Microsoft.TestCommon/ThreadPoolSyncContext.cs31
-rw-r--r--test/Microsoft.TestCommon/WebUtils.cs87
-rw-r--r--test/Microsoft.TestCommon/packages.config6
-rw-r--r--test/Microsoft.Web.Helpers.Test/AnalyticsTest.cs133
-rw-r--r--test/Microsoft.Web.Helpers.Test/BingTest.cs252
-rw-r--r--test/Microsoft.Web.Helpers.Test/FacebookTest.cs270
-rw-r--r--test/Microsoft.Web.Helpers.Test/FileUploadTest.cs199
-rw-r--r--test/Microsoft.Web.Helpers.Test/GamerCardTest.cs59
-rw-r--r--test/Microsoft.Web.Helpers.Test/GravatarTest.cs142
-rw-r--r--test/Microsoft.Web.Helpers.Test/LinkShareTest.cs188
-rw-r--r--test/Microsoft.Web.Helpers.Test/MapsTest.cs55
-rw-r--r--test/Microsoft.Web.Helpers.Test/Microsoft.Web.Helpers.Test.csproj108
-rw-r--r--test/Microsoft.Web.Helpers.Test/PreAppStartCodeTest.cs31
-rw-r--r--test/Microsoft.Web.Helpers.Test/Properties/AssemblyInfo.cs34
-rw-r--r--test/Microsoft.Web.Helpers.Test/ReCaptchaTest.cs251
-rw-r--r--test/Microsoft.Web.Helpers.Test/ThemesTest.cs373
-rw-r--r--test/Microsoft.Web.Helpers.Test/TwitterTest.cs481
-rw-r--r--test/Microsoft.Web.Helpers.Test/UrlBuilderTest.cs413
-rw-r--r--test/Microsoft.Web.Helpers.Test/VideoTest.cs300
-rw-r--r--test/Microsoft.Web.Helpers.Test/packages.config6
-rw-r--r--test/Microsoft.Web.Http.Data.Test/ChangeSetTests.cs164
-rw-r--r--test/Microsoft.Web.Http.Data.Test/Controllers/CatalogController.cs71
-rw-r--r--test/Microsoft.Web.Http.Data.Test/Controllers/CitiesController.cs15
-rw-r--r--test/Microsoft.Web.Http.Data.Test/Controllers/NorthwindEFController.cs45
-rw-r--r--test/Microsoft.Web.Http.Data.Test/DataControllerDescriptionTest.cs192
-rw-r--r--test/Microsoft.Web.Http.Data.Test/DataControllerQueryTests.cs231
-rw-r--r--test/Microsoft.Web.Http.Data.Test/DataControllerSubmitTests.cs372
-rw-r--r--test/Microsoft.Web.Http.Data.Test/MetadataExtensionsTests.cs59
-rw-r--r--test/Microsoft.Web.Http.Data.Test/Microsoft.Web.Http.Data.Test.csproj143
-rw-r--r--test/Microsoft.Web.Http.Data.Test/Models/CatalogEntities.cs169
-rw-r--r--test/Microsoft.Web.Http.Data.Test/Models/Cities.cs302
-rw-r--r--test/Microsoft.Web.Http.Data.Test/Models/Northwind.Designer.cs3411
-rw-r--r--test/Microsoft.Web.Http.Data.Test/Models/Northwind.edmx933
-rw-r--r--test/Microsoft.Web.Http.Data.Test/Properties/AssemblyInfo.cs34
-rw-r--r--test/Microsoft.Web.Http.Data.Test/TestHelpers.cs114
-rw-r--r--test/Microsoft.Web.Http.Data.Test/packages.config9
-rw-r--r--test/Microsoft.Web.Mvc.Test/Controls/Test/DesignModeSite.cs34
-rw-r--r--test/Microsoft.Web.Mvc.Test/Controls/Test/DropDownListTest.cs128
-rw-r--r--test/Microsoft.Web.Mvc.Test/Controls/Test/MvcControlTest.cs80
-rw-r--r--test/Microsoft.Web.Mvc.Test/Controls/Test/MvcTestHelper.cs19
-rw-r--r--test/Microsoft.Web.Mvc.Test/Controls/Test/ViewDataContainer.cs10
-rw-r--r--test/Microsoft.Web.Mvc.Test/Microsoft.Web.Mvc.Test.csproj167
-rw-r--r--test/Microsoft.Web.Mvc.Test/ModelBinding/Test/ArrayModelBinderProviderTest.cs100
-rw-r--r--test/Microsoft.Web.Mvc.Test/ModelBinding/Test/ArrayModelBinderTest.cs48
-rw-r--r--test/Microsoft.Web.Mvc.Test/ModelBinding/Test/BinaryDataModelBinderProviderTest.cs159
-rw-r--r--test/Microsoft.Web.Mvc.Test/ModelBinding/Test/BindingBehaviorAttributeTest.cs51
-rw-r--r--test/Microsoft.Web.Mvc.Test/ModelBinding/Test/CollectionModelBinderProviderTest.cs76
-rw-r--r--test/Microsoft.Web.Mvc.Test/ModelBinding/Test/CollectionModelBinderTest.cs245
-rw-r--r--test/Microsoft.Web.Mvc.Test/ModelBinding/Test/CollectionModelBinderUtilTest.cs391
-rw-r--r--test/Microsoft.Web.Mvc.Test/ModelBinding/Test/ComplexModelDtoModelBinderProviderTest.cs45
-rw-r--r--test/Microsoft.Web.Mvc.Test/ModelBinding/Test/ComplexModelDtoModelBinderTest.cs106
-rw-r--r--test/Microsoft.Web.Mvc.Test/ModelBinding/Test/ComplexModelDtoResultTest.cs38
-rw-r--r--test/Microsoft.Web.Mvc.Test/ModelBinding/Test/ComplexModelDtoTest.cs50
-rw-r--r--test/Microsoft.Web.Mvc.Test/ModelBinding/Test/DictionaryModelBinderProviderTest.cs76
-rw-r--r--test/Microsoft.Web.Mvc.Test/ModelBinding/Test/DictionaryModelBinderTest.cs52
-rw-r--r--test/Microsoft.Web.Mvc.Test/ModelBinding/Test/ExtensibleModelBinderAdapterTest.cs210
-rw-r--r--test/Microsoft.Web.Mvc.Test/ModelBinding/Test/ExtensibleModelBindingContextTest.cs136
-rw-r--r--test/Microsoft.Web.Mvc.Test/ModelBinding/Test/GenericModelBinderProviderTest.cs245
-rw-r--r--test/Microsoft.Web.Mvc.Test/ModelBinding/Test/KeyValuePairModelBinderProviderTest.cs104
-rw-r--r--test/Microsoft.Web.Mvc.Test/ModelBinding/Test/KeyValuePairModelBinderTest.cs116
-rw-r--r--test/Microsoft.Web.Mvc.Test/ModelBinding/Test/KeyValuePairModelBinderUtilTest.cs108
-rw-r--r--test/Microsoft.Web.Mvc.Test/ModelBinding/Test/ModelBinderConfigTest.cs166
-rw-r--r--test/Microsoft.Web.Mvc.Test/ModelBinding/Test/ModelBinderProviderCollectionTest.cs528
-rw-r--r--test/Microsoft.Web.Mvc.Test/ModelBinding/Test/ModelBinderProvidersTest.cs33
-rw-r--r--test/Microsoft.Web.Mvc.Test/ModelBinding/Test/ModelBinderUtilTest.cs324
-rw-r--r--test/Microsoft.Web.Mvc.Test/ModelBinding/Test/ModelValidationNodeTest.cs389
-rw-r--r--test/Microsoft.Web.Mvc.Test/ModelBinding/Test/MutableObjectModelBinderProviderTest.cs76
-rw-r--r--test/Microsoft.Web.Mvc.Test/ModelBinding/Test/MutableObjectModelBinderTest.cs769
-rw-r--r--test/Microsoft.Web.Mvc.Test/ModelBinding/Test/SimpleModelBinderProviderTest.cs152
-rw-r--r--test/Microsoft.Web.Mvc.Test/ModelBinding/Test/TypeConverterModelBinderProviderTest.cs69
-rw-r--r--test/Microsoft.Web.Mvc.Test/ModelBinding/Test/TypeConverterModelBinderTest.cs171
-rw-r--r--test/Microsoft.Web.Mvc.Test/ModelBinding/Test/TypeMatchModelBinderProviderTest.cs73
-rw-r--r--test/Microsoft.Web.Mvc.Test/ModelBinding/Test/TypeMatchModelBinderTest.cs113
-rw-r--r--test/Microsoft.Web.Mvc.Test/Properties/AssemblyInfo.cs38
-rw-r--r--test/Microsoft.Web.Mvc.Test/Test/AjaxOnlyAttributeTest.cs66
-rw-r--r--test/Microsoft.Web.Mvc.Test/Test/AreaHelpersTest.cs100
-rw-r--r--test/Microsoft.Web.Mvc.Test/Test/AsyncManagerExtensionsTest.cs189
-rw-r--r--test/Microsoft.Web.Mvc.Test/Test/ButtonTest.cs66
-rw-r--r--test/Microsoft.Web.Mvc.Test/Test/ContentTypeAttributeTest.cs43
-rw-r--r--test/Microsoft.Web.Mvc.Test/Test/ControllerExtensionsTest.cs67
-rw-r--r--test/Microsoft.Web.Mvc.Test/Test/CookieTempDataProviderTest.cs171
-rw-r--r--test/Microsoft.Web.Mvc.Test/Test/CookieValueProviderFactoryTest.cs38
-rw-r--r--test/Microsoft.Web.Mvc.Test/Test/CopyAsyncParametersAttributeTest.cs78
-rw-r--r--test/Microsoft.Web.Mvc.Test/Test/CreditCardAttributeTest.cs42
-rw-r--r--test/Microsoft.Web.Mvc.Test/Test/CssExtensionsTests.cs138
-rw-r--r--test/Microsoft.Web.Mvc.Test/Test/DeserializeAttributeTest.cs105
-rw-r--r--test/Microsoft.Web.Mvc.Test/Test/DynamicReflectionObjectTest.cs54
-rw-r--r--test/Microsoft.Web.Mvc.Test/Test/DynamicViewDataDictionaryTest.cs115
-rw-r--r--test/Microsoft.Web.Mvc.Test/Test/DynamicViewPageTest.cs53
-rw-r--r--test/Microsoft.Web.Mvc.Test/Test/ElementalValueProviderTest.cs54
-rw-r--r--test/Microsoft.Web.Mvc.Test/Test/EmailAddressAttribueTest.cs42
-rw-r--r--test/Microsoft.Web.Mvc.Test/Test/ExpressionHelperTest.cs302
-rw-r--r--test/Microsoft.Web.Mvc.Test/Test/FileExtensionsAttributeTest.cs53
-rw-r--r--test/Microsoft.Web.Mvc.Test/Test/FormExtensionsTest.cs98
-rw-r--r--test/Microsoft.Web.Mvc.Test/Test/ImageExtensionsTest.cs130
-rw-r--r--test/Microsoft.Web.Mvc.Test/Test/MailToExtensionsTest.cs132
-rw-r--r--test/Microsoft.Web.Mvc.Test/Test/ModelCopierTest.cs223
-rw-r--r--test/Microsoft.Web.Mvc.Test/Test/MvcSerializerTest.cs122
-rw-r--r--test/Microsoft.Web.Mvc.Test/Test/RadioExtensionsTest.cs199
-rw-r--r--test/Microsoft.Web.Mvc.Test/Test/ReaderWriterCacheTest.cs77
-rw-r--r--test/Microsoft.Web.Mvc.Test/Test/RenderActionTest.cs115
-rw-r--r--test/Microsoft.Web.Mvc.Test/Test/ScriptExtensionsTest.cs123
-rw-r--r--test/Microsoft.Web.Mvc.Test/Test/SerializationExtensionsTest.cs78
-rw-r--r--test/Microsoft.Web.Mvc.Test/Test/ServerVariablesValueProviderFactoryTest.cs35
-rw-r--r--test/Microsoft.Web.Mvc.Test/Test/SessionValueProviderFactoryTest.cs60
-rw-r--r--test/Microsoft.Web.Mvc.Test/Test/SkipBindingAttributeTest.cs22
-rw-r--r--test/Microsoft.Web.Mvc.Test/Test/SubmitButtonExtensionsTest.cs82
-rw-r--r--test/Microsoft.Web.Mvc.Test/Test/SubmitImageExtensionsTest.cs66
-rw-r--r--test/Microsoft.Web.Mvc.Test/Test/TempDataValueProviderFactoryTest.cs86
-rw-r--r--test/Microsoft.Web.Mvc.Test/Test/TypeHelpersTest.cs121
-rw-r--r--test/Microsoft.Web.Mvc.Test/Test/UrlAttributeTest.cs44
-rw-r--r--test/Microsoft.Web.Mvc.Test/Test/ValueProviderUtilTest.cs46
-rw-r--r--test/Microsoft.Web.Mvc.Test/packages.config6
-rw-r--r--test/Microsoft.Web.WebPages.OAuth.Test/Microsoft.Web.WebPages.OAuth.Test.csproj104
-rw-r--r--test/Microsoft.Web.WebPages.OAuth.Test/OAuthWebSecurityTest.cs389
-rw-r--r--test/Microsoft.Web.WebPages.OAuth.Test/PreAppStartCodeTest.cs12
-rw-r--r--test/Microsoft.Web.WebPages.OAuth.Test/Properties/AssemblyInfo.cs7
-rw-r--r--test/Microsoft.Web.WebPages.OAuth.Test/packages.config11
-rw-r--r--test/SPA.Test/Index.html47
-rw-r--r--test/SPA.Test/Properties/AssemblyInfo.cs35
-rw-r--r--test/SPA.Test/SPA.Test.csproj133
-rw-r--r--test/SPA.Test/Scripts/IntellisenseFix.js32
-rw-r--r--test/SPA.Test/Scripts/References.js20
-rw-r--r--test/SPA.Test/Scripts/TestSetup.js320
-rw-r--r--test/SPA.Test/Web.Debug.config30
-rw-r--r--test/SPA.Test/Web.Release.config31
-rw-r--r--test/SPA.Test/Web.config13
-rw-r--r--test/SPA.Test/css/qunit.css226
-rw-r--r--test/SPA.Test/css/tests.css15
-rw-r--r--test/SPA.Test/upshot/ChangeTracking.tests.js348
-rw-r--r--test/SPA.Test/upshot/Consistency.tests.js158
-rw-r--r--test/SPA.Test/upshot/Core.tests.js293
-rw-r--r--test/SPA.Test/upshot/DataContext.tests.js7
-rw-r--r--test/SPA.Test/upshot/DataProvider.tests.js156
-rw-r--r--test/SPA.Test/upshot/DataSource.Common.js161
-rw-r--r--test/SPA.Test/upshot/DataSource.Tests.js349
-rw-r--r--test/SPA.Test/upshot/Datasets.js257
-rw-r--r--test/SPA.Test/upshot/Delete.Tests.js753
-rw-r--r--test/SPA.Test/upshot/EntitySet.tests.js1525
-rw-r--r--test/SPA.Test/upshot/Init.js41
-rw-r--r--test/SPA.Test/upshot/Mapping.tests.js441
-rw-r--r--test/SPA.Test/upshot/RecordSet.js67
-rw-r--r--test/SPA.Test/upshot/jQuery.DataView.Tests.js287
-rw-r--r--test/Settings.StyleCop145
-rw-r--r--test/System.Json.Test.Integration/Common/InstanceCreator.cs1585
-rw-r--r--test/System.Json.Test.Integration/Common/JsonValueCreatorSurrogate.cs149
-rw-r--r--test/System.Json.Test.Integration/Common/Log.cs10
-rw-r--r--test/System.Json.Test.Integration/Common/TypeLibrary.cs1943
-rw-r--r--test/System.Json.Test.Integration/Common/Util.cs201
-rw-r--r--test/System.Json.Test.Integration/JObjectFunctionalTest.cs990
-rw-r--r--test/System.Json.Test.Integration/JsonPrimitiveTests.cs1068
-rw-r--r--test/System.Json.Test.Integration/JsonStringRoundTripTests.cs583
-rw-r--r--test/System.Json.Test.Integration/JsonValueAndComplexTypesTests.cs329
-rw-r--r--test/System.Json.Test.Integration/JsonValueDynamicTests.cs1108
-rw-r--r--test/System.Json.Test.Integration/JsonValueEventsTests.cs518
-rw-r--r--test/System.Json.Test.Integration/JsonValueLinqExtensionsIntegrationTest.cs68
-rw-r--r--test/System.Json.Test.Integration/JsonValuePartialTrustTests.cs186
-rw-r--r--test/System.Json.Test.Integration/JsonValueTestHelper.cs609
-rw-r--r--test/System.Json.Test.Integration/JsonValueTests.cs380
-rw-r--r--test/System.Json.Test.Integration/JsonValueUsageTest.cs433
-rw-r--r--test/System.Json.Test.Integration/Properties/AssemblyInfo.cs34
-rw-r--r--test/System.Json.Test.Integration/System.Json.Test.Integration.csproj88
-rw-r--r--test/System.Json.Test.Integration/packages.config5
-rw-r--r--test/System.Json.Test.Unit/Common/AnyInstance.cs220
-rw-r--r--test/System.Json.Test.Unit/Common/ExceptionTestHelper.cs27
-rw-r--r--test/System.Json.Test.Unit/Extensions/JsonValueExtensionsTest.cs476
-rw-r--r--test/System.Json.Test.Unit/FormUrlEncodedJsonTests.cs74
-rw-r--r--test/System.Json.Test.Unit/JsonArrayTest.cs604
-rw-r--r--test/System.Json.Test.Unit/JsonDefaultTest.cs153
-rw-r--r--test/System.Json.Test.Unit/JsonObjectTest.cs787
-rw-r--r--test/System.Json.Test.Unit/JsonPrimitiveTest.cs410
-rw-r--r--test/System.Json.Test.Unit/JsonTypeTest.cs26
-rw-r--r--test/System.Json.Test.Unit/JsonValueDynamicMetaObjectTest.cs534
-rw-r--r--test/System.Json.Test.Unit/JsonValueDynamicTest.cs468
-rw-r--r--test/System.Json.Test.Unit/JsonValueLinqExtensionsTest.cs35
-rw-r--r--test/System.Json.Test.Unit/JsonValueTest.cs565
-rw-r--r--test/System.Json.Test.Unit/Properties/AssemblyInfo.cs37
-rw-r--r--test/System.Json.Test.Unit/System.Json.Test.Unit.csproj85
-rw-r--r--test/System.Json.Test.Unit/packages.config5
-rw-r--r--test/System.Net.Http.Formatting.Test.Integration/FormUrlEncodedFromContentTests.cs527
-rw-r--r--test/System.Net.Http.Formatting.Test.Integration/FormUrlEncodedFromUriQueryTests.cs503
-rw-r--r--test/System.Net.Http.Formatting.Test.Integration/JsonNetSerializationTest.cs536
-rw-r--r--test/System.Net.Http.Formatting.Test.Integration/JsonValueRoundTripComparer.cs83
-rw-r--r--test/System.Net.Http.Formatting.Test.Integration/Properties/AssemblyInfo.cs34
-rw-r--r--test/System.Net.Http.Formatting.Test.Integration/System.Net.Http.Formatting.Test.Integration.csproj96
-rw-r--r--test/System.Net.Http.Formatting.Test.Integration/packages.config7
-rw-r--r--test/System.Net.Http.Formatting.Test.Unit/ContentDispositionHeaderValueExtensionsTests.cs48
-rw-r--r--test/System.Net.Http.Formatting.Test.Unit/DataSets/HttpUnitTestDataSets.cs103
-rw-r--r--test/System.Net.Http.Formatting.Test.Unit/DataSets/Types/DataContractEnum.cs16
-rw-r--r--test/System.Net.Http.Formatting.Test.Unit/DataSets/Types/DataContractType.cs75
-rw-r--r--test/System.Net.Http.Formatting.Test.Unit/DataSets/Types/DerivedDataContractType.cs59
-rw-r--r--test/System.Net.Http.Formatting.Test.Unit/DataSets/Types/DerivedFormUrlEncodedMediaTypeFormatter.cs7
-rw-r--r--test/System.Net.Http.Formatting.Test.Unit/DataSets/Types/DerivedJsonMediaTypeFormatter.cs7
-rw-r--r--test/System.Net.Http.Formatting.Test.Unit/DataSets/Types/DerivedWcfPocoType.cs38
-rw-r--r--test/System.Net.Http.Formatting.Test.Unit/DataSets/Types/DerivedXmlMediaTypeFormatter.cs7
-rw-r--r--test/System.Net.Http.Formatting.Test.Unit/DataSets/Types/DerivedXmlSerializableType.cs56
-rw-r--r--test/System.Net.Http.Formatting.Test.Unit/DataSets/Types/HttpTestData.cs427
-rw-r--r--test/System.Net.Http.Formatting.Test.Unit/DataSets/Types/INotJsonSerializable.cs9
-rw-r--r--test/System.Net.Http.Formatting.Test.Unit/DataSets/Types/WcfPocoType.cs86
-rw-r--r--test/System.Net.Http.Formatting.Test.Unit/DataSets/Types/XmlSerializableType.cs96
-rw-r--r--test/System.Net.Http.Formatting.Test.Unit/Formatting/BufferedMediaTypeFormatterTests.cs95
-rw-r--r--test/System.Net.Http.Formatting.Test.Unit/Formatting/DefaultContentNegotiatorTests.cs220
-rw-r--r--test/System.Net.Http.Formatting.Test.Unit/Formatting/FormDataCollectionTests.cs107
-rw-r--r--test/System.Net.Http.Formatting.Test.Unit/Formatting/FormUrlEncodedMediaTypeFormatterTests.cs148
-rw-r--r--test/System.Net.Http.Formatting.Test.Unit/Formatting/JsonKeyValueModelTest.cs39
-rw-r--r--test/System.Net.Http.Formatting.Test.Unit/Formatting/JsonMediaTypeFormatterTests.cs244
-rw-r--r--test/System.Net.Http.Formatting.Test.Unit/Formatting/MediaRangeMappingTests.cs151
-rw-r--r--test/System.Net.Http.Formatting.Test.Unit/Formatting/MediaTypeConstantsTests.cs74
-rw-r--r--test/System.Net.Http.Formatting.Test.Unit/Formatting/MediaTypeFormatterCollectionTests.cs222
-rw-r--r--test/System.Net.Http.Formatting.Test.Unit/Formatting/MediaTypeFormatterExtensionsTests.cs120
-rw-r--r--test/System.Net.Http.Formatting.Test.Unit/Formatting/MediaTypeFormatterTests.cs401
-rw-r--r--test/System.Net.Http.Formatting.Test.Unit/Formatting/MediaTypeHeadeValueComparerTests.cs209
-rw-r--r--test/System.Net.Http.Formatting.Test.Unit/Formatting/MediaTypeHeadeValueExtensionsTests.cs176
-rw-r--r--test/System.Net.Http.Formatting.Test.Unit/Formatting/MediaTypeHeaderValueEqualityComparerTests.cs199
-rw-r--r--test/System.Net.Http.Formatting.Test.Unit/Formatting/ParsedMediaTypeHeaderValueTests.cs168
-rw-r--r--test/System.Net.Http.Formatting.Test.Unit/Formatting/Parsers/FormUrlEncodedParserTests.cs176
-rw-r--r--test/System.Net.Http.Formatting.Test.Unit/Formatting/Parsers/HttpRequestHeaderParserTests.cs273
-rw-r--r--test/System.Net.Http.Formatting.Test.Unit/Formatting/Parsers/HttpRequestLineParserTests.cs273
-rw-r--r--test/System.Net.Http.Formatting.Test.Unit/Formatting/Parsers/HttpResponseHeaderParserTests.cs272
-rw-r--r--test/System.Net.Http.Formatting.Test.Unit/Formatting/Parsers/HttpStatusLineParserTests.cs284
-rw-r--r--test/System.Net.Http.Formatting.Test.Unit/Formatting/Parsers/InternetMessageFormatHeaderParserTests.cs664
-rw-r--r--test/System.Net.Http.Formatting.Test.Unit/Formatting/Parsers/MimeMultipartParserTests.cs482
-rw-r--r--test/System.Net.Http.Formatting.Test.Unit/Formatting/QueryStringMappingTests.cs202
-rw-r--r--test/System.Net.Http.Formatting.Test.Unit/Formatting/RequestHeaderMappingTests.cs236
-rw-r--r--test/System.Net.Http.Formatting.Test.Unit/Formatting/ThresholdStreamTest.cs114
-rw-r--r--test/System.Net.Http.Formatting.Test.Unit/Formatting/UriPathExtensionMappingTests.cs168
-rw-r--r--test/System.Net.Http.Formatting.Test.Unit/Formatting/XmlMediaTypeFormatterTests.cs304
-rw-r--r--test/System.Net.Http.Formatting.Test.Unit/FormattingUtilitiesTests.cs95
-rw-r--r--test/System.Net.Http.Formatting.Test.Unit/HttpClientExtensionsTest.cs256
-rw-r--r--test/System.Net.Http.Formatting.Test.Unit/HttpContentCollectionExtensionsTests.cs336
-rw-r--r--test/System.Net.Http.Formatting.Test.Unit/HttpContentExtensionsTest.cs127
-rw-r--r--test/System.Net.Http.Formatting.Test.Unit/HttpContentMessageExtensionsTests.cs491
-rw-r--r--test/System.Net.Http.Formatting.Test.Unit/HttpContentMultipartExtensionsTests.cs512
-rw-r--r--test/System.Net.Http.Formatting.Test.Unit/HttpMessageContentTests.cs309
-rw-r--r--test/System.Net.Http.Formatting.Test.Unit/Mocks/MockHttpContent.cs90
-rw-r--r--test/System.Net.Http.Formatting.Test.Unit/Mocks/MockMediaTypeFormatter.cs30
-rw-r--r--test/System.Net.Http.Formatting.Test.Unit/Mocks/TestableHttpMessageHandler.cs18
-rw-r--r--test/System.Net.Http.Formatting.Test.Unit/MultipartFileStreamProviderTests.cs117
-rw-r--r--test/System.Net.Http.Formatting.Test.Unit/MultipartFormDataStreamProviderTests.cs120
-rw-r--r--test/System.Net.Http.Formatting.Test.Unit/MultipartMemoryStreamProviderTests.cs55
-rw-r--r--test/System.Net.Http.Formatting.Test.Unit/ObjectContentOfTTests.cs38
-rw-r--r--test/System.Net.Http.Formatting.Test.Unit/ObjectContentTests.cs168
-rw-r--r--test/System.Net.Http.Formatting.Test.Unit/ParserData.cs192
-rw-r--r--test/System.Net.Http.Formatting.Test.Unit/Properties/AssemblyInfo.cs21
-rw-r--r--test/System.Net.Http.Formatting.Test.Unit/System.Net.Http.Formatting.Test.Unit.csproj156
-rw-r--r--test/System.Net.Http.Formatting.Test.Unit/UriExtensionsTests.cs173
-rw-r--r--test/System.Net.Http.Formatting.Test.Unit/UriQueryDataSet.cs31
-rw-r--r--test/System.Net.Http.Formatting.Test.Unit/UriQueryUtilityTests.cs160
-rw-r--r--test/System.Net.Http.Formatting.Test.Unit/packages.config8
-rw-r--r--test/System.Web.Helpers.Test/ChartTest.cs653
-rw-r--r--test/System.Web.Helpers.Test/ConversionUtilTest.cs76
-rw-r--r--test/System.Web.Helpers.Test/CryptoTest.cs170
-rw-r--r--test/System.Web.Helpers.Test/DynamicDictionary.cs103
-rw-r--r--test/System.Web.Helpers.Test/DynamicHelperTest.cs38
-rw-r--r--test/System.Web.Helpers.Test/DynamicWrapper.cs80
-rw-r--r--test/System.Web.Helpers.Test/HelperResultTest.cs72
-rw-r--r--test/System.Web.Helpers.Test/JsonTest.cs369
-rw-r--r--test/System.Web.Helpers.Test/ObjectInfoTest.cs728
-rw-r--r--test/System.Web.Helpers.Test/PreComputedGridDataSourceTest.cs41
-rw-r--r--test/System.Web.Helpers.Test/Properties/AssemblyInfo.cs34
-rw-r--r--test/System.Web.Helpers.Test/ServerInfoTest.cs162
-rw-r--r--test/System.Web.Helpers.Test/System.Web.Helpers.Test.csproj106
-rw-r--r--test/System.Web.Helpers.Test/TestFiles/HiRes.jpgbin0 -> 21694 bytes
-rw-r--r--test/System.Web.Helpers.Test/TestFiles/LambdaFinal.jpgbin0 -> 119973 bytes
-rw-r--r--test/System.Web.Helpers.Test/TestFiles/NETLogo.pngbin0 -> 10337 bytes
-rw-r--r--test/System.Web.Helpers.Test/TestFiles/logo.bmpbin0 -> 14310 bytes
-rw-r--r--test/System.Web.Helpers.Test/TestFiles/xhtml11-flat.dtd4513
-rw-r--r--test/System.Web.Helpers.Test/WebCacheTest.cs117
-rw-r--r--test/System.Web.Helpers.Test/WebGridDataSourceTest.cs305
-rw-r--r--test/System.Web.Helpers.Test/WebGridTest.cs2321
-rw-r--r--test/System.Web.Helpers.Test/WebImageTest.cs1162
-rw-r--r--test/System.Web.Helpers.Test/WebMailTest.cs378
-rw-r--r--test/System.Web.Helpers.Test/XhtmlAssert.cs128
-rw-r--r--test/System.Web.Helpers.Test/packages.config6
-rw-r--r--test/System.Web.Http.Common.Test/ErrorTests.cs21
-rw-r--r--test/System.Web.Http.Common.Test/HttpRequestMessageCommonExtensionsTest.cs57
-rw-r--r--test/System.Web.Http.Common.Test/System.Web.Http.Common.Test.csproj82
-rw-r--r--test/System.Web.Http.Common.Test/TaskHelpersExtensionsTest.cs2169
-rw-r--r--test/System.Web.Http.Common.Test/TaskHelpersTest.cs574
-rw-r--r--test/System.Web.Http.Common.Test/packages.config7
-rw-r--r--test/System.Web.Http.Integration.Test/ApiExplorer/ApiExplorerSettingsTest.cs62
-rw-r--r--test/System.Web.Http.Integration.Test/ApiExplorer/Controllers/DocumentationController.cs31
-rw-r--r--test/System.Web.Http.Integration.Test/ApiExplorer/Controllers/HiddenActionController.cs29
-rw-r--r--test/System.Web.Http.Integration.Test/ApiExplorer/Controllers/HiddenController.cs28
-rw-r--r--test/System.Web.Http.Integration.Test/ApiExplorer/Controllers/ItemController.cs32
-rw-r--r--test/System.Web.Http.Integration.Test/ApiExplorer/Controllers/OverloadsController.cs28
-rw-r--r--test/System.Web.Http.Integration.Test/ApiExplorer/Controllers/ParameterSourceController.cs58
-rw-r--r--test/System.Web.Http.Integration.Test/ApiExplorer/DocumentationProviders/AttributeDocumentationProvider.cs56
-rw-r--r--test/System.Web.Http.Integration.Test/ApiExplorer/DocumentationTest.cs65
-rw-r--r--test/System.Web.Http.Integration.Test/ApiExplorer/Formatters/ItemFormatter.cs27
-rw-r--r--test/System.Web.Http.Integration.Test/ApiExplorer/FormattersTest.cs42
-rw-r--r--test/System.Web.Http.Integration.Test/ApiExplorer/ParameterSourceTest.cs57
-rw-r--r--test/System.Web.Http.Integration.Test/ApiExplorer/RouteConstraintsTest.cs116
-rw-r--r--test/System.Web.Http.Integration.Test/ApiExplorer/RoutesTest.cs203
-rw-r--r--test/System.Web.Http.Integration.Test/Authentication/BasicOverHttpTest.cs74
-rw-r--r--test/System.Web.Http.Integration.Test/Authentication/CustomMessageHandler.cs33
-rw-r--r--test/System.Web.Http.Integration.Test/Authentication/CustomUsernamePasswordValidator.cs19
-rw-r--r--test/System.Web.Http.Integration.Test/Authentication/RequireAdminAttribute.cs21
-rw-r--r--test/System.Web.Http.Integration.Test/Authentication/SampleController.cs14
-rw-r--r--test/System.Web.Http.Integration.Test/ContentNegotiation/AcceptHeaderTests.cs30
-rw-r--r--test/System.Web.Http.Integration.Test/ContentNegotiation/ConnegController.cs19
-rw-r--r--test/System.Web.Http.Integration.Test/ContentNegotiation/ConnegItem.cs8
-rw-r--r--test/System.Web.Http.Integration.Test/ContentNegotiation/ContentNegotiationTestBase.cs46
-rw-r--r--test/System.Web.Http.Integration.Test/ContentNegotiation/CustomFormatterTests.cs299
-rw-r--r--test/System.Web.Http.Integration.Test/ContentNegotiation/DefaultContentNegotiatorTests.cs37
-rw-r--r--test/System.Web.Http.Integration.Test/ContentNegotiation/HttpResponseReturnTests.cs132
-rw-r--r--test/System.Web.Http.Integration.Test/Controllers/ActionAttributesTest.cs138
-rw-r--r--test/System.Web.Http.Integration.Test/Controllers/ApiControllerActionSelectorTest.cs191
-rw-r--r--test/System.Web.Http.Integration.Test/Controllers/Apis/ActionAttributeTestController.cs29
-rw-r--r--test/System.Web.Http.Integration.Test/Controllers/Apis/TestController.cs28
-rw-r--r--test/System.Web.Http.Integration.Test/Controllers/Apis/User.cs9
-rw-r--r--test/System.Web.Http.Integration.Test/Controllers/Apis/UserAddress.cs9
-rw-r--r--test/System.Web.Http.Integration.Test/Controllers/CustomControllerFactoryTest.cs70
-rw-r--r--test/System.Web.Http.Integration.Test/Controllers/Helpers/ApiControllerHelper.cs54
-rw-r--r--test/System.Web.Http.Integration.Test/ExceptionHandling/DuplicateControllers.cs23
-rw-r--r--test/System.Web.Http.Integration.Test/ExceptionHandling/ExceptionController.cs102
-rw-r--r--test/System.Web.Http.Integration.Test/ExceptionHandling/ExceptionHandlingTest.cs225
-rw-r--r--test/System.Web.Http.Integration.Test/ExceptionHandling/HttpResponseExceptionTest.cs224
-rw-r--r--test/System.Web.Http.Integration.Test/ExceptionHandling/IncludeErrorDetailTest.cs76
-rw-r--r--test/System.Web.Http.Integration.Test/Filters/IQueryableFilterPipelineTest.cs99
-rw-r--r--test/System.Web.Http.Integration.Test/ModelBinding/BodyBindingTests.cs120
-rw-r--r--test/System.Web.Http.Integration.Test/ModelBinding/CustomBindingTests.cs32
-rw-r--r--test/System.Web.Http.Integration.Test/ModelBinding/DefaultActionValueBinderTest.cs990
-rw-r--r--test/System.Web.Http.Integration.Test/ModelBinding/HttpContentBindingTests.cs92
-rw-r--r--test/System.Web.Http.Integration.Test/ModelBinding/ModelBindingController.cs272
-rw-r--r--test/System.Web.Http.Integration.Test/ModelBinding/ModelBindingTests.cs48
-rw-r--r--test/System.Web.Http.Integration.Test/ModelBinding/QueryStringBindingTests.cs115
-rw-r--r--test/System.Web.Http.Integration.Test/ModelBinding/RouteBindingTests.cs29
-rw-r--r--test/System.Web.Http.Integration.Test/PartialTrust/BasicScenarioTest.cs62
-rw-r--r--test/System.Web.Http.Integration.Test/PartialTrust/PartialTrustRunner.cs132
-rw-r--r--test/System.Web.Http.Integration.Test/Properties/AssemblyInfo.cs3
-rw-r--r--test/System.Web.Http.Integration.Test/System.Web.Http.Integration.Test.csproj155
-rw-r--r--test/System.Web.Http.Integration.Test/Util/ApiExplorerHelper.cs54
-rw-r--r--test/System.Web.Http.Integration.Test/Util/ContextUtil.cs62
-rw-r--r--test/System.Web.Http.Integration.Test/Util/ScenarioHelper.cs41
-rw-r--r--test/System.Web.Http.Integration.Test/packages.config7
-rw-r--r--test/System.Web.Http.Test/AuthorizeAttributeTest.cs280
-rw-r--r--test/System.Web.Http.Test/Controllers/ApiControllerActionInvokerTest.cs49
-rw-r--r--test/System.Web.Http.Test/Controllers/ApiControllerActionSelectorTest.cs47
-rw-r--r--test/System.Web.Http.Test/Controllers/ApiControllerTest.cs702
-rw-r--r--test/System.Web.Http.Test/Controllers/Apis/User.cs9
-rw-r--r--test/System.Web.Http.Test/Controllers/Apis/UsersController.cs40
-rw-r--r--test/System.Web.Http.Test/Controllers/Apis/UsersRpcController.cs57
-rw-r--r--test/System.Web.Http.Test/Controllers/HttpActionContextTest.cs76
-rw-r--r--test/System.Web.Http.Test/Controllers/HttpConfigurationTest.cs48
-rw-r--r--test/System.Web.Http.Test/Controllers/HttpControllerContextTest.cs108
-rw-r--r--test/System.Web.Http.Test/Controllers/HttpControllerDescriptorTest.cs183
-rw-r--r--test/System.Web.Http.Test/Controllers/HttpParameterDescriptorTest.cs73
-rw-r--r--test/System.Web.Http.Test/Controllers/ReflectedHttpActionDescriptorTest.cs299
-rw-r--r--test/System.Web.Http.Test/Controllers/ReflectedHttpParameterDescriptorTest.cs92
-rw-r--r--test/System.Web.Http.Test/DictionaryExtensionsTest.cs127
-rw-r--r--test/System.Web.Http.Test/Dispatcher/DefaultBuildManagerTest.cs73
-rw-r--r--test/System.Web.Http.Test/Filters/ActionDescriptorFilterProviderTest.cs75
-rw-r--r--test/System.Web.Http.Test/Filters/ActionFilterAttributeTest.cs334
-rw-r--r--test/System.Web.Http.Test/Filters/AuthorizationFilterAttributeTest.cs189
-rw-r--r--test/System.Web.Http.Test/Filters/ConfigurationFilterProviderTest.cs34
-rw-r--r--test/System.Web.Http.Test/Filters/EnumerableEvaluatorFilterProviderTest.cs82
-rw-r--r--test/System.Web.Http.Test/Filters/EnumerableEvaluatorFilterTest.cs191
-rw-r--r--test/System.Web.Http.Test/Filters/ExceptionFilterAttributeTest.cs82
-rw-r--r--test/System.Web.Http.Test/Filters/FilterAttributeTest.cs34
-rw-r--r--test/System.Web.Http.Test/Filters/FilterInfoComparerTest.cs37
-rw-r--r--test/System.Web.Http.Test/Filters/FilterInfoTest.cs29
-rw-r--r--test/System.Web.Http.Test/Filters/HttpActionExecutedContextTest.cs88
-rw-r--r--test/System.Web.Http.Test/Filters/HttpFilterCollectionTest.cs94
-rw-r--r--test/System.Web.Http.Test/Filters/QueryCompositionFilterAttributeTest.cs88
-rw-r--r--test/System.Web.Http.Test/Filters/QueryCompositionFilterProviderTest.cs72
-rw-r--r--test/System.Web.Http.Test/Hosting/HttpRouteTest.cs38
-rw-r--r--test/System.Web.Http.Test/HttpBindingBehaviorAttributeTest.cs51
-rw-r--r--test/System.Web.Http.Test/HttpRequestMessageExtensionsTest.cs256
-rw-r--r--test/System.Web.Http.Test/HttpRouteCollectionExtensionsTest.cs67
-rw-r--r--test/System.Web.Http.Test/HttpServerTest.cs163
-rw-r--r--test/System.Web.Http.Test/Internal/CollectionModelBinderUtilTest.cs394
-rw-r--r--test/System.Web.Http.Test/Internal/TypeActivatorTest.cs158
-rw-r--r--test/System.Web.Http.Test/ModelBinding/Binders/ArrayModelBinderProviderTest.cs100
-rw-r--r--test/System.Web.Http.Test/ModelBinding/Binders/ArrayModelBinderTest.cs47
-rw-r--r--test/System.Web.Http.Test/ModelBinding/Binders/BinaryDataModelBinderProviderTest.cs159
-rw-r--r--test/System.Web.Http.Test/ModelBinding/Binders/CollectionModelBinderProviderTest.cs77
-rw-r--r--test/System.Web.Http.Test/ModelBinding/Binders/CollectionModelBinderTest.cs240
-rw-r--r--test/System.Web.Http.Test/ModelBinding/Binders/ComplexModelDtoModelBinderProviderTest.cs44
-rw-r--r--test/System.Web.Http.Test/ModelBinding/Binders/ComplexModelDtoModelBinderTest.cs91
-rw-r--r--test/System.Web.Http.Test/ModelBinding/Binders/ComplexModelDtoResultTest.cs41
-rw-r--r--test/System.Web.Http.Test/ModelBinding/Binders/ComplexModelDtoTest.cs53
-rw-r--r--test/System.Web.Http.Test/ModelBinding/Binders/DictionaryModelBinderProviderTest.cs76
-rw-r--r--test/System.Web.Http.Test/ModelBinding/Binders/DictionaryModelBinderTest.cs51
-rw-r--r--test/System.Web.Http.Test/ModelBinding/Binders/GenericModelBinderProviderTest.cs251
-rw-r--r--test/System.Web.Http.Test/ModelBinding/Binders/KeyValuePairModelBinderProviderTest.cs104
-rw-r--r--test/System.Web.Http.Test/ModelBinding/Binders/KeyValuePairModelBinderTest.cs110
-rw-r--r--test/System.Web.Http.Test/ModelBinding/Binders/KeyValuePairModelBinderUtilTest.cs105
-rw-r--r--test/System.Web.Http.Test/ModelBinding/Binders/MutableObjectModelBinderProviderTest.cs103
-rw-r--r--test/System.Web.Http.Test/ModelBinding/Binders/MutableObjectModelBinderTest.cs737
-rw-r--r--test/System.Web.Http.Test/ModelBinding/Binders/SimpleModelBinderProviderTest.cs155
-rw-r--r--test/System.Web.Http.Test/ModelBinding/Binders/TypeConverterModelBinderProviderTest.cs68
-rw-r--r--test/System.Web.Http.Test/ModelBinding/Binders/TypeConverterModelBinderTest.cs170
-rw-r--r--test/System.Web.Http.Test/ModelBinding/Binders/TypeMatchModelBinderProviderTest.cs61
-rw-r--r--test/System.Web.Http.Test/ModelBinding/Binders/TypeMatchModelBinderTest.cs113
-rw-r--r--test/System.Web.Http.Test/ModelBinding/CompositeModelBinderTest.cs312
-rw-r--r--test/System.Web.Http.Test/ModelBinding/DefaultActionValueBinderTest.cs490
-rw-r--r--test/System.Web.Http.Test/ModelBinding/FormDataCollectionExtensionsTest.cs229
-rw-r--r--test/System.Web.Http.Test/ModelBinding/ModelBinderAttributeTest.cs101
-rw-r--r--test/System.Web.Http.Test/ModelBinding/ModelBinderConfigTest.cs147
-rw-r--r--test/System.Web.Http.Test/ModelBinding/ModelBindingContextTest.cs126
-rw-r--r--test/System.Web.Http.Test/ModelBinding/ModelBindingUtilTest.cs388
-rw-r--r--test/System.Web.Http.Test/Properties/AssemblyInfo.cs3
-rw-r--r--test/System.Web.Http.Test/Query/DataModel.cs43
-rw-r--r--test/System.Web.Http.Test/Query/ODataQueryDeserializerTests.cs623
-rw-r--r--test/System.Web.Http.Test/Query/QueryValidatorTest.cs93
-rw-r--r--test/System.Web.Http.Test/Routing/HttpRouteTest.cs27
-rw-r--r--test/System.Web.Http.Test/Routing/UrlHelperTest.cs149
-rw-r--r--test/System.Web.Http.Test/Services/DependencyResolverTests.cs219
-rw-r--r--test/System.Web.Http.Test/System.Web.Http.Test.csproj247
-rw-r--r--test/System.Web.Http.Test/Tracing/FormattingUtilitiesTest.cs283
-rw-r--r--test/System.Web.Http.Test/Tracing/HttpRequestMessageExtensionsTest.cs24
-rw-r--r--test/System.Web.Http.Test/Tracing/ITraceWriterExtensionsTest.cs1004
-rw-r--r--test/System.Web.Http.Test/Tracing/TestTraceWriter.cs30
-rw-r--r--test/System.Web.Http.Test/Tracing/TraceManagerTest.cs137
-rw-r--r--test/System.Web.Http.Test/Tracing/TraceRecordComparer.cs39
-rw-r--r--test/System.Web.Http.Test/Tracing/Tracers/ActionFilterAttributeTracerTest.cs108
-rw-r--r--test/System.Web.Http.Test/Tracing/Tracers/ActionFilterTracerTest.cs82
-rw-r--r--test/System.Web.Http.Test/Tracing/Tracers/ActionValueBinderTracerTest.cs71
-rw-r--r--test/System.Web.Http.Test/Tracing/Tracers/AuthorizationFilterAttributeTracerTest.cs72
-rw-r--r--test/System.Web.Http.Test/Tracing/Tracers/AuthorizationFilterTracerTest.cs76
-rw-r--r--test/System.Web.Http.Test/Tracing/Tracers/ContentNegotiatorTracerTest.cs227
-rw-r--r--test/System.Web.Http.Test/Tracing/Tracers/ExceptionFilterAttributeTracerTest.cs74
-rw-r--r--test/System.Web.Http.Test/Tracing/Tracers/ExceptionFilterTracerTest.cs71
-rw-r--r--test/System.Web.Http.Test/Tracing/Tracers/FilterTracerTest.cs274
-rw-r--r--test/System.Web.Http.Test/Tracing/Tracers/FormatterParameterBindingTracerTest.cs128
-rw-r--r--test/System.Web.Http.Test/Tracing/Tracers/HttpActionBindingTracerTest.cs102
-rw-r--r--test/System.Web.Http.Test/Tracing/Tracers/HttpActionDescriptorTracerTest.cs136
-rw-r--r--test/System.Web.Http.Test/Tracing/Tracers/HttpActionInvokerTracerTest.cs224
-rw-r--r--test/System.Web.Http.Test/Tracing/Tracers/HttpActionSelectorTracerTest.cs80
-rw-r--r--test/System.Web.Http.Test/Tracing/Tracers/HttpControllerActivatorTracerTest.cs64
-rw-r--r--test/System.Web.Http.Test/Tracing/Tracers/HttpControllerFactoryTracerTest.cs63
-rw-r--r--test/System.Web.Http.Test/Tracing/Tracers/HttpControllerTracerTest.cs121
-rw-r--r--test/System.Web.Http.Test/Tracing/Tracers/HttpParameterBindingTracerTest.cs122
-rw-r--r--test/System.Web.Http.Test/Tracing/Tracers/MediaTypeFormatterTracerTest.cs248
-rw-r--r--test/System.Web.Http.Test/Tracing/Tracers/MessageHandlerTracerTest.cs156
-rw-r--r--test/System.Web.Http.Test/Tracing/Tracers/RequestMessageHandlerTracerTest.cs150
-rw-r--r--test/System.Web.Http.Test/Util/ContextUtil.cs48
-rw-r--r--test/System.Web.Http.Test/Util/SimpleHttpValueProvider.cs70
-rw-r--r--test/System.Web.Http.Test/Validation/DefaultBodyModelValidatorTest.cs160
-rw-r--r--test/System.Web.Http.Test/Validation/ModelValidationNodeTest.cs331
-rw-r--r--test/System.Web.Http.Test/Validation/ModelValidationRequiredMemberSelectorTest.cs43
-rw-r--r--test/System.Web.Http.Test/ValueProviders/Providers/KeyValueModelValueProviderTest.cs217
-rw-r--r--test/System.Web.Http.Test/ValueProviders/Providers/NameValueCollectionValueProviderTest.cs210
-rw-r--r--test/System.Web.Http.Test/ValueProviders/Providers/QueryStringValueProviderTest.cs152
-rw-r--r--test/System.Web.Http.Test/packages.config7
-rw-r--r--test/System.Web.Http.WebHost.Test/HttpControllerHandlerTest.cs85
-rw-r--r--test/System.Web.Http.WebHost.Test/Properties/AssemblyInfo.cs3
-rw-r--r--test/System.Web.Http.WebHost.Test/RouteCollectionExtensionsTest.cs67
-rw-r--r--test/System.Web.Http.WebHost.Test/Routing/HostedUrlHelperTest.cs137
-rw-r--r--test/System.Web.Http.WebHost.Test/System.Web.Http.WebHost.Test.csproj92
-rw-r--r--test/System.Web.Http.WebHost.Test/packages.config7
-rw-r--r--test/System.Web.Mvc.Test/Ajax/Test/AjaxExtensionsTest.cs1975
-rw-r--r--test/System.Web.Mvc.Test/Ajax/Test/AjaxOptionsTest.cs290
-rw-r--r--test/System.Web.Mvc.Test/Async/Test/AsyncActionDescriptorTest.cs51
-rw-r--r--test/System.Web.Mvc.Test/Async/Test/AsyncActionMethodSelectorTest.cs377
-rw-r--r--test/System.Web.Mvc.Test/Async/Test/AsyncControllerActionInvokerTest.cs916
-rw-r--r--test/System.Web.Mvc.Test/Async/Test/AsyncManagerTest.cs113
-rw-r--r--test/System.Web.Mvc.Test/Async/Test/AsyncResultWrapperTest.cs228
-rw-r--r--test/System.Web.Mvc.Test/Async/Test/AsyncUtilTest.cs98
-rw-r--r--test/System.Web.Mvc.Test/Async/Test/MockAsyncResult.cs45
-rw-r--r--test/System.Web.Mvc.Test/Async/Test/OperationCounterTest.cs110
-rw-r--r--test/System.Web.Mvc.Test/Async/Test/ReflectedAsyncActionDescriptorTest.cs314
-rw-r--r--test/System.Web.Mvc.Test/Async/Test/ReflectedAsyncControllerDescriptorTest.cs177
-rw-r--r--test/System.Web.Mvc.Test/Async/Test/SignalContainer.cs22
-rw-r--r--test/System.Web.Mvc.Test/Async/Test/SimpleAsyncResultTest.cs101
-rw-r--r--test/System.Web.Mvc.Test/Async/Test/SingleEntryGateTest.cs24
-rw-r--r--test/System.Web.Mvc.Test/Async/Test/SynchronizationContextUtilTest.cs89
-rw-r--r--test/System.Web.Mvc.Test/Async/Test/SynchronousOperationExceptionTest.cs62
-rw-r--r--test/System.Web.Mvc.Test/Async/Test/TaskAsyncActionDescriptorTest.cs558
-rw-r--r--test/System.Web.Mvc.Test/Async/Test/TaskWrapperAsyncResultTest.cs62
-rw-r--r--test/System.Web.Mvc.Test/Async/Test/TriggerListenerTest.cs30
-rw-r--r--test/System.Web.Mvc.Test/ExpressionUtil/Test/BinaryExpressionFingerprintTest.cs91
-rw-r--r--test/System.Web.Mvc.Test/ExpressionUtil/Test/CachedExpressionCompilerTest.cs141
-rw-r--r--test/System.Web.Mvc.Test/ExpressionUtil/Test/ConditionalExpressionFingerprintTest.cs69
-rw-r--r--test/System.Web.Mvc.Test/ExpressionUtil/Test/ConstantExpressionFingerprintTest.cs69
-rw-r--r--test/System.Web.Mvc.Test/ExpressionUtil/Test/DefaultExpressionFingerprintTest.cs69
-rw-r--r--test/System.Web.Mvc.Test/ExpressionUtil/Test/DummyExpressionFingerprint.cs13
-rw-r--r--test/System.Web.Mvc.Test/ExpressionUtil/Test/ExpressionFingerprintTest.cs42
-rw-r--r--test/System.Web.Mvc.Test/ExpressionUtil/Test/FingerprintingExpressionVisitorTest.cs301
-rw-r--r--test/System.Web.Mvc.Test/ExpressionUtil/Test/HoistingExpressionVisitorTest.cs45
-rw-r--r--test/System.Web.Mvc.Test/ExpressionUtil/Test/IndexExpressionFingerprintTest.cs91
-rw-r--r--test/System.Web.Mvc.Test/ExpressionUtil/Test/LambdaExpressionFingerprintTest.cs69
-rw-r--r--test/System.Web.Mvc.Test/ExpressionUtil/Test/MemberExpressionFingerprintTest.cs91
-rw-r--r--test/System.Web.Mvc.Test/ExpressionUtil/Test/MethodCallExpressionFingerprintTest.cs91
-rw-r--r--test/System.Web.Mvc.Test/ExpressionUtil/Test/ParameterExpressionFingerprintTest.cs90
-rw-r--r--test/System.Web.Mvc.Test/ExpressionUtil/Test/TypeBinaryExpressionFingerprintTest.cs90
-rw-r--r--test/System.Web.Mvc.Test/ExpressionUtil/Test/UnaryExpressionFingerprintTest.cs91
-rw-r--r--test/System.Web.Mvc.Test/Html/Test/ChildActionExtensionsTest.cs256
-rw-r--r--test/System.Web.Mvc.Test/Html/Test/DefaultDisplayTemplatesTest.cs560
-rw-r--r--test/System.Web.Mvc.Test/Html/Test/DefaultEditorTemplatesTest.cs595
-rw-r--r--test/System.Web.Mvc.Test/Html/Test/DisplayNameExtensionsTest.cs255
-rw-r--r--test/System.Web.Mvc.Test/Html/Test/FormExtensionsTest.cs423
-rw-r--r--test/System.Web.Mvc.Test/Html/Test/InputExtensionsTest.cs2376
-rw-r--r--test/System.Web.Mvc.Test/Html/Test/LabelExtensionsTest.cs516
-rw-r--r--test/System.Web.Mvc.Test/Html/Test/LinkExtensionsTest.cs562
-rw-r--r--test/System.Web.Mvc.Test/Html/Test/MvcFormTest.cs97
-rw-r--r--test/System.Web.Mvc.Test/Html/Test/NameExtensionsTest.cs83
-rw-r--r--test/System.Web.Mvc.Test/Html/Test/PartialExtensionsTest.cs84
-rw-r--r--test/System.Web.Mvc.Test/Html/Test/RenderPartialExtensionsTest.cs122
-rw-r--r--test/System.Web.Mvc.Test/Html/Test/SelectExtensionsTest.cs1857
-rw-r--r--test/System.Web.Mvc.Test/Html/Test/TemplateHelpersTest.cs1158
-rw-r--r--test/System.Web.Mvc.Test/Html/Test/TextAreaExtensionsTest.cs629
-rw-r--r--test/System.Web.Mvc.Test/Html/Test/ValidationExtensionsTest.cs1086
-rw-r--r--test/System.Web.Mvc.Test/Html/Test/ValueExtensionsTest.cs164
-rw-r--r--test/System.Web.Mvc.Test/Properties/AssemblyInfo.cs3
-rw-r--r--test/System.Web.Mvc.Test/Razor/Test/MvcCSharpRazorCodeGeneratorTest.cs69
-rw-r--r--test/System.Web.Mvc.Test/Razor/Test/MvcCSharpRazorCodeParserTest.cs284
-rw-r--r--test/System.Web.Mvc.Test/Razor/Test/MvcVBRazorCodeParserTest.cs301
-rw-r--r--test/System.Web.Mvc.Test/Razor/Test/MvcWebPageRazorHostTest.cs107
-rw-r--r--test/System.Web.Mvc.Test/System.Web.Mvc.Test.csproj356
-rw-r--r--test/System.Web.Mvc.Test/Test/AcceptVerbsAttributeTest.cs214
-rw-r--r--test/System.Web.Mvc.Test/Test/ActionDescriptorTest.cs280
-rw-r--r--test/System.Web.Mvc.Test/Test/ActionExecutedContextTest.cs52
-rw-r--r--test/System.Web.Mvc.Test/Test/ActionExecutingContextTest.cs52
-rw-r--r--test/System.Web.Mvc.Test/Test/ActionFilterAttributeTest.cs42
-rw-r--r--test/System.Web.Mvc.Test/Test/ActionMethodDispatcherCacheTest.cs24
-rw-r--r--test/System.Web.Mvc.Test/Test/ActionMethodDispatcherTest.cs126
-rw-r--r--test/System.Web.Mvc.Test/Test/ActionMethodSelectorTest.cs212
-rw-r--r--test/System.Web.Mvc.Test/Test/ActionNameAttributeTest.cs60
-rw-r--r--test/System.Web.Mvc.Test/Test/AdditionalMetadataAttributeTest.cs73
-rw-r--r--test/System.Web.Mvc.Test/Test/AjaxHelperTest.cs236
-rw-r--r--test/System.Web.Mvc.Test/Test/AjaxHelper`1Test.cs41
-rw-r--r--test/System.Web.Mvc.Test/Test/AjaxRequestExtensionsTest.cs70
-rw-r--r--test/System.Web.Mvc.Test/Test/AllowHtmlAttributeTest.cs37
-rw-r--r--test/System.Web.Mvc.Test/Test/AreaHelpersTest.cs97
-rw-r--r--test/System.Web.Mvc.Test/Test/AreaRegistrationContextTest.cs138
-rw-r--r--test/System.Web.Mvc.Test/Test/AreaRegistrationTest.cs99
-rw-r--r--test/System.Web.Mvc.Test/Test/AssociatedMetadataProviderTest.cs346
-rw-r--r--test/System.Web.Mvc.Test/Test/AssociatedValidatorProviderTest.cs124
-rw-r--r--test/System.Web.Mvc.Test/Test/AsyncControllerTest.cs263
-rw-r--r--test/System.Web.Mvc.Test/Test/AsyncTimeoutAttributeTest.cs87
-rw-r--r--test/System.Web.Mvc.Test/Test/AuthorizationContextTest.cs35
-rw-r--r--test/System.Web.Mvc.Test/Test/AuthorizeAttributeTest.cs362
-rw-r--r--test/System.Web.Mvc.Test/Test/BindAttributeTest.cs96
-rw-r--r--test/System.Web.Mvc.Test/Test/BuildManagerCompiledViewTest.cs145
-rw-r--r--test/System.Web.Mvc.Test/Test/BuildManagerViewEngineTest.cs182
-rw-r--r--test/System.Web.Mvc.Test/Test/ByteArrayModelBinderTest.cs121
-rw-r--r--test/System.Web.Mvc.Test/Test/CachedAssociatedMetadataProviderTest.cs275
-rw-r--r--test/System.Web.Mvc.Test/Test/CachedDataAnnotationsModelMetadataProviderTest.cs10
-rw-r--r--test/System.Web.Mvc.Test/Test/CancellationTokenModelBinderTest.cs21
-rw-r--r--test/System.Web.Mvc.Test/Test/ChildActionOnlyAttributeTest.cs52
-rw-r--r--test/System.Web.Mvc.Test/Test/ChildActionValueProviderFactoryTest.cs93
-rw-r--r--test/System.Web.Mvc.Test/Test/ClientDataTypeModelValidatorProviderTest.cs215
-rw-r--r--test/System.Web.Mvc.Test/Test/CompareAttributeTest.cs198
-rw-r--r--test/System.Web.Mvc.Test/Test/ContentResultTest.cs158
-rw-r--r--test/System.Web.Mvc.Test/Test/ControllerActionInvokerTest.cs2427
-rw-r--r--test/System.Web.Mvc.Test/Test/ControllerBaseTest.cs234
-rw-r--r--test/System.Web.Mvc.Test/Test/ControllerBuilderTest.cs274
-rw-r--r--test/System.Web.Mvc.Test/Test/ControllerContextTest.cs296
-rw-r--r--test/System.Web.Mvc.Test/Test/ControllerDescriptorCacheTest.cs23
-rw-r--r--test/System.Web.Mvc.Test/Test/ControllerDescriptorTest.cs129
-rw-r--r--test/System.Web.Mvc.Test/Test/ControllerInstanceFilterProviderTest.cs44
-rw-r--r--test/System.Web.Mvc.Test/Test/ControllerTest.cs2047
-rw-r--r--test/System.Web.Mvc.Test/Test/DataAnnotationsModelMetadataProviderTest.cs10
-rw-r--r--test/System.Web.Mvc.Test/Test/DataAnnotationsModelMetadataProviderTestBase.cs614
-rw-r--r--test/System.Web.Mvc.Test/Test/DataAnnotationsModelValidatorProviderTest.cs725
-rw-r--r--test/System.Web.Mvc.Test/Test/DataAnnotationsModelValidatorTest.cs158
-rw-r--r--test/System.Web.Mvc.Test/Test/DataErrorInfoModelValidatorProviderTest.cs287
-rw-r--r--test/System.Web.Mvc.Test/Test/DataTypeUtilTest.cs75
-rw-r--r--test/System.Web.Mvc.Test/Test/DefaultControllerFactoryTest.cs766
-rw-r--r--test/System.Web.Mvc.Test/Test/DefaultModelBinderTest.cs2904
-rw-r--r--test/System.Web.Mvc.Test/Test/DefaultViewLocationCacheTest.cs73
-rw-r--r--test/System.Web.Mvc.Test/Test/DependencyResolverTest.cs241
-rw-r--r--test/System.Web.Mvc.Test/Test/DescriptorUtilTest.cs55
-rw-r--r--test/System.Web.Mvc.Test/Test/DictionaryHelpersTest.cs89
-rw-r--r--test/System.Web.Mvc.Test/Test/DictionaryValueProviderTest.cs102
-rw-r--r--test/System.Web.Mvc.Test/Test/DynamicViewDataDictionaryTest.cs214
-rw-r--r--test/System.Web.Mvc.Test/Test/EmptyModelValidatorProviderTest.cs21
-rw-r--r--test/System.Web.Mvc.Test/Test/ExceptionContextTest.cs46
-rw-r--r--test/System.Web.Mvc.Test/Test/ExpressionHelperTest.cs120
-rw-r--r--test/System.Web.Mvc.Test/Test/FieldValidationMetadataTest.cs33
-rw-r--r--test/System.Web.Mvc.Test/Test/FileContentResultTest.cs65
-rw-r--r--test/System.Web.Mvc.Test/Test/FilePathResultTest.cs64
-rw-r--r--test/System.Web.Mvc.Test/Test/FileResultTest.cs160
-rw-r--r--test/System.Web.Mvc.Test/Test/FileStreamResultTest.cs77
-rw-r--r--test/System.Web.Mvc.Test/Test/FilterAttributeFilterProviderTest.cs155
-rw-r--r--test/System.Web.Mvc.Test/Test/FilterInfoTest.cs69
-rw-r--r--test/System.Web.Mvc.Test/Test/FilterProviderCollectionTest.cs202
-rw-r--r--test/System.Web.Mvc.Test/Test/FilterProvidersTest.cs17
-rw-r--r--test/System.Web.Mvc.Test/Test/FilterTest.cs66
-rw-r--r--test/System.Web.Mvc.Test/Test/FormCollectionTest.cs180
-rw-r--r--test/System.Web.Mvc.Test/Test/FormContextTest.cs172
-rw-r--r--test/System.Web.Mvc.Test/Test/FormValueProviderFactoryTest.cs77
-rw-r--r--test/System.Web.Mvc.Test/Test/GlobalFilterCollectionTest.cs92
-rw-r--r--test/System.Web.Mvc.Test/Test/HandleErrorAttributeTest.cs251
-rw-r--r--test/System.Web.Mvc.Test/Test/HandleErrorInfoTest.cs76
-rw-r--r--test/System.Web.Mvc.Test/Test/HtmlHelperTest.cs1172
-rw-r--r--test/System.Web.Mvc.Test/Test/HtmlHelper`1Test.cs40
-rw-r--r--test/System.Web.Mvc.Test/Test/HttpDeleteAttributeTest.cs25
-rw-r--r--test/System.Web.Mvc.Test/Test/HttpFileCollectionValueProviderFactoryTest.cs36
-rw-r--r--test/System.Web.Mvc.Test/Test/HttpFileCollectionValueProviderTest.cs147
-rw-r--r--test/System.Web.Mvc.Test/Test/HttpGetAttributeTest.cs25
-rw-r--r--test/System.Web.Mvc.Test/Test/HttpHandlerUtilTest.cs153
-rw-r--r--test/System.Web.Mvc.Test/Test/HttpNotFoundResultTest.cs31
-rw-r--r--test/System.Web.Mvc.Test/Test/HttpPostAttributeTest.cs25
-rw-r--r--test/System.Web.Mvc.Test/Test/HttpPostedFileBaseModelBinderTest.cs90
-rw-r--r--test/System.Web.Mvc.Test/Test/HttpPutAttributeTest.cs25
-rw-r--r--test/System.Web.Mvc.Test/Test/HttpRequestExtensionsTest.cs50
-rw-r--r--test/System.Web.Mvc.Test/Test/HttpStatusCodeResultTest.cs96
-rw-r--r--test/System.Web.Mvc.Test/Test/HttpUnauthorizedResultTest.cs31
-rw-r--r--test/System.Web.Mvc.Test/Test/HttpVerbAttributeHelper.cs46
-rw-r--r--test/System.Web.Mvc.Test/Test/JavaScriptResultTest.cs69
-rw-r--r--test/System.Web.Mvc.Test/Test/JsonResultTest.cs292
-rw-r--r--test/System.Web.Mvc.Test/Test/JsonValueProviderFactoryTest.cs152
-rw-r--r--test/System.Web.Mvc.Test/Test/LinqBinaryModelBinderTest.cs120
-rw-r--r--test/System.Web.Mvc.Test/Test/MockBuildManager.cs80
-rw-r--r--test/System.Web.Mvc.Test/Test/MockHelpers.cs13
-rw-r--r--test/System.Web.Mvc.Test/Test/MockableUnvalidatedRequestValues.cs11
-rw-r--r--test/System.Web.Mvc.Test/Test/ModelBinderAttributeTest.cs85
-rw-r--r--test/System.Web.Mvc.Test/Test/ModelBinderDictionaryTest.cs191
-rw-r--r--test/System.Web.Mvc.Test/Test/ModelBinderProviderCollectionTest.cs113
-rw-r--r--test/System.Web.Mvc.Test/Test/ModelBinderProvidersTest.cs18
-rw-r--r--test/System.Web.Mvc.Test/Test/ModelBindersTest.cs65
-rw-r--r--test/System.Web.Mvc.Test/Test/ModelBindingContextTest.cs122
-rw-r--r--test/System.Web.Mvc.Test/Test/ModelClientValidationRuleTest.cs33
-rw-r--r--test/System.Web.Mvc.Test/Test/ModelErrorCollectionTest.cs37
-rw-r--r--test/System.Web.Mvc.Test/Test/ModelErrorTest.cs65
-rw-r--r--test/System.Web.Mvc.Test/Test/ModelMetadataProvidersTest.cs68
-rw-r--r--test/System.Web.Mvc.Test/Test/ModelMetadataTest.cs892
-rw-r--r--test/System.Web.Mvc.Test/Test/ModelStateDictionaryTest.cs309
-rw-r--r--test/System.Web.Mvc.Test/Test/ModelStateTest.cs17
-rw-r--r--test/System.Web.Mvc.Test/Test/ModelValidationResultTest.cs28
-rw-r--r--test/System.Web.Mvc.Test/Test/ModelValidatorProviderCollectionTest.cs185
-rw-r--r--test/System.Web.Mvc.Test/Test/ModelValidatorProvidersTest.cs26
-rw-r--r--test/System.Web.Mvc.Test/Test/ModelValidatorTest.cs233
-rw-r--r--test/System.Web.Mvc.Test/Test/MultiSelectListTest.cs307
-rw-r--r--test/System.Web.Mvc.Test/Test/MultiServiceResolverTest.cs136
-rw-r--r--test/System.Web.Mvc.Test/Test/MvcHandlerTest.cs360
-rw-r--r--test/System.Web.Mvc.Test/Test/MvcHtmlStringTest.cs62
-rw-r--r--test/System.Web.Mvc.Test/Test/MvcHttpHandlerTest.cs63
-rw-r--r--test/System.Web.Mvc.Test/Test/MvcRouteHandlerTest.cs71
-rw-r--r--test/System.Web.Mvc.Test/Test/MvcTestHelper.cs102
-rw-r--r--test/System.Web.Mvc.Test/Test/MvcWebRazorHostFactoryTest.cs56
-rw-r--r--test/System.Web.Mvc.Test/Test/NameValueCollectionExtensionsTest.cs76
-rw-r--r--test/System.Web.Mvc.Test/Test/NameValueCollectionValueProviderTest.cs143
-rw-r--r--test/System.Web.Mvc.Test/Test/NoAsyncTimeoutAttributeTest.cs18
-rw-r--r--test/System.Web.Mvc.Test/Test/NonActionAttributeTest.cs17
-rw-r--r--test/System.Web.Mvc.Test/Test/OutputCacheAttributeTest.cs279
-rw-r--r--test/System.Web.Mvc.Test/Test/ParameterBindingInfoTest.cs60
-rw-r--r--test/System.Web.Mvc.Test/Test/ParameterDescriptorTest.cs114
-rw-r--r--test/System.Web.Mvc.Test/Test/ParameterInfoUtilTest.cs182
-rw-r--r--test/System.Web.Mvc.Test/Test/PartialViewResultTest.cs197
-rw-r--r--test/System.Web.Mvc.Test/Test/PathHelpersTest.cs247
-rw-r--r--test/System.Web.Mvc.Test/Test/PreApplicationStartCodeTest.cs26
-rw-r--r--test/System.Web.Mvc.Test/Test/QueryStringValueProviderFactoryTest.cs77
-rw-r--r--test/System.Web.Mvc.Test/Test/RangeAttributeAdapterTest.cs32
-rw-r--r--test/System.Web.Mvc.Test/Test/RazorViewEngineTest.cs253
-rw-r--r--test/System.Web.Mvc.Test/Test/RazorViewTest.cs248
-rw-r--r--test/System.Web.Mvc.Test/Test/ReaderWriterCacheTest.cs76
-rw-r--r--test/System.Web.Mvc.Test/Test/RedirectResultTest.cs125
-rw-r--r--test/System.Web.Mvc.Test/Test/RedirectToRouteResultTest.cs209
-rw-r--r--test/System.Web.Mvc.Test/Test/ReflectedActionDescriptorTest.cs491
-rw-r--r--test/System.Web.Mvc.Test/Test/ReflectedControllerDescriptorTest.cs198
-rw-r--r--test/System.Web.Mvc.Test/Test/ReflectedParameterBindingInfoTest.cs172
-rw-r--r--test/System.Web.Mvc.Test/Test/ReflectedParameterDescriptorTest.cs159
-rw-r--r--test/System.Web.Mvc.Test/Test/RegularExpressionAttributeAdapterTest.cs31
-rw-r--r--test/System.Web.Mvc.Test/Test/RemoteAttributeTest.cs172
-rw-r--r--test/System.Web.Mvc.Test/Test/RequireHttpsAttributeTest.cs106
-rw-r--r--test/System.Web.Mvc.Test/Test/RequiredAttributeAdapterTest.cs30
-rw-r--r--test/System.Web.Mvc.Test/Test/ResultExecutedContextTest.cs55
-rw-r--r--test/System.Web.Mvc.Test/Test/ResultExecutingContextTest.cs47
-rw-r--r--test/System.Web.Mvc.Test/Test/RouteCollectionExtensionsTest.cs388
-rw-r--r--test/System.Web.Mvc.Test/Test/RouteDataValueProviderFactoryTest.cs44
-rw-r--r--test/System.Web.Mvc.Test/Test/SelectListTest.cs84
-rw-r--r--test/System.Web.Mvc.Test/Test/SessionStateTempDataProviderTest.cs166
-rw-r--r--test/System.Web.Mvc.Test/Test/SingleServiceResolverTest.cs163
-rw-r--r--test/System.Web.Mvc.Test/Test/StringLengthAttributeAdapterTest.cs32
-rw-r--r--test/System.Web.Mvc.Test/Test/TempDataDictionaryTest.cs340
-rw-r--r--test/System.Web.Mvc.Test/Test/TypeCacheSerializerTest.cs165
-rw-r--r--test/System.Web.Mvc.Test/Test/TypeCacheUtilTest.cs149
-rw-r--r--test/System.Web.Mvc.Test/Test/TypeHelpersTest.cs242
-rw-r--r--test/System.Web.Mvc.Test/Test/UrlHelperTest.cs694
-rw-r--r--test/System.Web.Mvc.Test/Test/UrlParameterTest.cs14
-rw-r--r--test/System.Web.Mvc.Test/Test/UrlRewriterHelperTest.cs83
-rw-r--r--test/System.Web.Mvc.Test/Test/ValidatableObjectAdapterTest.cs172
-rw-r--r--test/System.Web.Mvc.Test/Test/ValidateAntiForgeryTokenAttributeTest.cs68
-rw-r--r--test/System.Web.Mvc.Test/Test/ValidateInputAttributeTest.cs73
-rw-r--r--test/System.Web.Mvc.Test/Test/ValueProviderCollectionTest.cs246
-rw-r--r--test/System.Web.Mvc.Test/Test/ValueProviderDictionaryTest.cs204
-rw-r--r--test/System.Web.Mvc.Test/Test/ValueProviderFactoriesTest.cs29
-rw-r--r--test/System.Web.Mvc.Test/Test/ValueProviderFactoryCollectionTest.cs143
-rw-r--r--test/System.Web.Mvc.Test/Test/ValueProviderResultTest.cs398
-rw-r--r--test/System.Web.Mvc.Test/Test/ValueProviderUtilTest.cs185
-rw-r--r--test/System.Web.Mvc.Test/Test/ViewContextTest.cs182
-rw-r--r--test/System.Web.Mvc.Test/Test/ViewDataDictionaryTest.cs536
-rw-r--r--test/System.Web.Mvc.Test/Test/ViewDataInfoTest.cs64
-rw-r--r--test/System.Web.Mvc.Test/Test/ViewEngineCollectionTest.cs631
-rw-r--r--test/System.Web.Mvc.Test/Test/ViewEngineResultTest.cs80
-rw-r--r--test/System.Web.Mvc.Test/Test/ViewEnginesTest.cs19
-rw-r--r--test/System.Web.Mvc.Test/Test/ViewMasterPageControlBuilderTest.cs39
-rw-r--r--test/System.Web.Mvc.Test/Test/ViewMasterPageTest.cs245
-rw-r--r--test/System.Web.Mvc.Test/Test/ViewPageControlBuilderTest.cs39
-rw-r--r--test/System.Web.Mvc.Test/Test/ViewPageTest.cs262
-rw-r--r--test/System.Web.Mvc.Test/Test/ViewResultBaseTest.cs72
-rw-r--r--test/System.Web.Mvc.Test/Test/ViewResultTest.cs222
-rw-r--r--test/System.Web.Mvc.Test/Test/ViewStartPageTest.cs110
-rw-r--r--test/System.Web.Mvc.Test/Test/ViewTypeParserFilterTest.cs208
-rw-r--r--test/System.Web.Mvc.Test/Test/ViewUserControlControlBuilderTest.cs39
-rw-r--r--test/System.Web.Mvc.Test/Test/ViewUserControlTest.cs538
-rw-r--r--test/System.Web.Mvc.Test/Test/VirtualPathProviderViewEngineTest.cs1412
-rw-r--r--test/System.Web.Mvc.Test/Test/WebFormViewEngineTest.cs244
-rw-r--r--test/System.Web.Mvc.Test/Test/WebFormViewTest.cs164
-rw-r--r--test/System.Web.Mvc.Test/Util/AnonymousObject.cs29
-rw-r--r--test/System.Web.Mvc.Test/Util/DictionaryHelper.cs517
-rw-r--r--test/System.Web.Mvc.Test/Util/HttpContextHelpers.cs15
-rw-r--r--test/System.Web.Mvc.Test/Util/MvcHelper.cs174
-rw-r--r--test/System.Web.Mvc.Test/Util/Resolver.cs7
-rw-r--r--test/System.Web.Mvc.Test/Util/SimpleValueProvider.cs73
-rw-r--r--test/System.Web.Mvc.Test/Util/SimpleViewDataContainer.cs14
-rw-r--r--test/System.Web.Mvc.Test/packages.config6
-rw-r--r--test/System.Web.Razor.Test/CSharpRazorCodeLanguageTest.cs53
-rw-r--r--test/System.Web.Razor.Test/CodeCompileUnitExtensions.cs22
-rw-r--r--test/System.Web.Razor.Test/Editor/RazorEditorParserTest.cs216
-rw-r--r--test/System.Web.Razor.Test/Framework/BlockExtensions.cs27
-rw-r--r--test/System.Web.Razor.Test/Framework/BlockTypes.cs233
-rw-r--r--test/System.Web.Razor.Test/Framework/CodeParserTestBase.cs74
-rw-r--r--test/System.Web.Razor.Test/Framework/CsHtmlCodeParserTestBase.cs28
-rw-r--r--test/System.Web.Razor.Test/Framework/CsHtmlMarkupParserTestBase.cs28
-rw-r--r--test/System.Web.Razor.Test/Framework/ErrorCollector.cs54
-rw-r--r--test/System.Web.Razor.Test/Framework/MarkupParserTestBase.cs59
-rw-r--r--test/System.Web.Razor.Test/Framework/ParserTestBase.cs381
-rw-r--r--test/System.Web.Razor.Test/Framework/RawTextSymbol.cs71
-rw-r--r--test/System.Web.Razor.Test/Framework/TestSpanBuilder.cs405
-rw-r--r--test/System.Web.Razor.Test/Framework/VBHtmlCodeParserTestBase.cs28
-rw-r--r--test/System.Web.Razor.Test/Framework/VBHtmlMarkupParserTestBase.cs28
-rw-r--r--test/System.Web.Razor.Test/Generator/CSharpRazorCodeGeneratorTest.cs290
-rw-r--r--test/System.Web.Razor.Test/Generator/GeneratedCodeMappingTest.cs63
-rw-r--r--test/System.Web.Razor.Test/Generator/RazorCodeGeneratorTest.cs146
-rw-r--r--test/System.Web.Razor.Test/Generator/VBRazorCodeGeneratorTest.cs279
-rw-r--r--test/System.Web.Razor.Test/Parser/BlockTest.cs122
-rw-r--r--test/System.Web.Razor.Test/Parser/CSharp/CSharpAutoCompleteTest.cs172
-rw-r--r--test/System.Web.Razor.Test/Parser/CSharp/CSharpBlockTest.cs731
-rw-r--r--test/System.Web.Razor.Test/Parser/CSharp/CSharpDirectivesTest.cs161
-rw-r--r--test/System.Web.Razor.Test/Parser/CSharp/CSharpErrorTest.cs604
-rw-r--r--test/System.Web.Razor.Test/Parser/CSharp/CSharpExplicitExpressionTest.cs139
-rw-r--r--test/System.Web.Razor.Test/Parser/CSharp/CSharpHelperTest.cs345
-rw-r--r--test/System.Web.Razor.Test/Parser/CSharp/CSharpImplicitExpressionTest.cs201
-rw-r--r--test/System.Web.Razor.Test/Parser/CSharp/CSharpLayoutDirectiveTest.cs84
-rw-r--r--test/System.Web.Razor.Test/Parser/CSharp/CSharpNestedStatementsTest.cs100
-rw-r--r--test/System.Web.Razor.Test/Parser/CSharp/CSharpRazorCommentsTest.cs172
-rw-r--r--test/System.Web.Razor.Test/Parser/CSharp/CSharpReservedWordsTest.cs41
-rw-r--r--test/System.Web.Razor.Test/Parser/CSharp/CSharpSectionTest.cs298
-rw-r--r--test/System.Web.Razor.Test/Parser/CSharp/CSharpSpecialBlockTest.cs195
-rw-r--r--test/System.Web.Razor.Test/Parser/CSharp/CSharpStatementTest.cs208
-rw-r--r--test/System.Web.Razor.Test/Parser/CSharp/CSharpTemplateTest.cs258
-rw-r--r--test/System.Web.Razor.Test/Parser/CSharp/CSharpToMarkupSwitchTest.cs491
-rw-r--r--test/System.Web.Razor.Test/Parser/CSharp/CSharpVerbatimBlockTest.cs118
-rw-r--r--test/System.Web.Razor.Test/Parser/CSharp/CSharpWhitespaceHandlingTest.cs30
-rw-r--r--test/System.Web.Razor.Test/Parser/CSharp/CsHtmlDocumentTest.cs286
-rw-r--r--test/System.Web.Razor.Test/Parser/CallbackParserListenerTest.cs162
-rw-r--r--test/System.Web.Razor.Test/Parser/Html/HtmlAttributeTest.cs268
-rw-r--r--test/System.Web.Razor.Test/Parser/Html/HtmlBlockTest.cs388
-rw-r--r--test/System.Web.Razor.Test/Parser/Html/HtmlDocumentTest.cs214
-rw-r--r--test/System.Web.Razor.Test/Parser/Html/HtmlErrorTest.cs87
-rw-r--r--test/System.Web.Razor.Test/Parser/Html/HtmlParserTestUtils.cs37
-rw-r--r--test/System.Web.Razor.Test/Parser/Html/HtmlTagsTest.cs150
-rw-r--r--test/System.Web.Razor.Test/Parser/Html/HtmlToCodeSwitchTest.cs222
-rw-r--r--test/System.Web.Razor.Test/Parser/Html/HtmlUrlAttributeTest.cs270
-rw-r--r--test/System.Web.Razor.Test/Parser/Old/CsHtmlDocumentTest.cs267
-rw-r--r--test/System.Web.Razor.Test/Parser/ParserContextTest.cs240
-rw-r--r--test/System.Web.Razor.Test/Parser/ParserVisitorExtensionsTest.cs82
-rw-r--r--test/System.Web.Razor.Test/Parser/PartialParsing/CSharpPartialParsingTest.cs400
-rw-r--r--test/System.Web.Razor.Test/Parser/PartialParsing/PartialParsingTestBase.cs111
-rw-r--r--test/System.Web.Razor.Test/Parser/PartialParsing/VBPartialParsingTest.cs372
-rw-r--r--test/System.Web.Razor.Test/Parser/RazorParserTest.cs134
-rw-r--r--test/System.Web.Razor.Test/Parser/VB/VBAutoCompleteTest.cs153
-rw-r--r--test/System.Web.Razor.Test/Parser/VB/VBBlockTest.cs372
-rw-r--r--test/System.Web.Razor.Test/Parser/VB/VBContinueStatementTest.cs56
-rw-r--r--test/System.Web.Razor.Test/Parser/VB/VBDirectiveTest.cs127
-rw-r--r--test/System.Web.Razor.Test/Parser/VB/VBErrorTest.cs172
-rw-r--r--test/System.Web.Razor.Test/Parser/VB/VBExitStatementTest.cs94
-rw-r--r--test/System.Web.Razor.Test/Parser/VB/VBExplicitExpressionTest.cs20
-rw-r--r--test/System.Web.Razor.Test/Parser/VB/VBExpressionTest.cs109
-rw-r--r--test/System.Web.Razor.Test/Parser/VB/VBExpressionsInCodeTest.cs119
-rw-r--r--test/System.Web.Razor.Test/Parser/VB/VBHelperTest.cs322
-rw-r--r--test/System.Web.Razor.Test/Parser/VB/VBHtmlDocumentTest.cs248
-rw-r--r--test/System.Web.Razor.Test/Parser/VB/VBImplicitExpressionTest.cs77
-rw-r--r--test/System.Web.Razor.Test/Parser/VB/VBLayoutDirectiveTest.cs86
-rw-r--r--test/System.Web.Razor.Test/Parser/VB/VBNestedStatementsTest.cs167
-rw-r--r--test/System.Web.Razor.Test/Parser/VB/VBRazorCommentsTest.cs172
-rw-r--r--test/System.Web.Razor.Test/Parser/VB/VBReservedWordsTest.cs28
-rw-r--r--test/System.Web.Razor.Test/Parser/VB/VBSectionTest.cs225
-rw-r--r--test/System.Web.Razor.Test/Parser/VB/VBSpecialKeywordsTest.cs169
-rw-r--r--test/System.Web.Razor.Test/Parser/VB/VBStatementTest.cs218
-rw-r--r--test/System.Web.Razor.Test/Parser/VB/VBTemplateTest.cs211
-rw-r--r--test/System.Web.Razor.Test/Parser/VB/VBToMarkupSwitchTest.cs103
-rw-r--r--test/System.Web.Razor.Test/Parser/WhitespaceRewriterTest.cs50
-rw-r--r--test/System.Web.Razor.Test/Properties/AssemblyInfo.cs34
-rw-r--r--test/System.Web.Razor.Test/RazorCodeLanguageTest.cs47
-rw-r--r--test/System.Web.Razor.Test/RazorDirectiveAttributeTest.cs79
-rw-r--r--test/System.Web.Razor.Test/RazorEngineHostTest.cs186
-rw-r--r--test/System.Web.Razor.Test/RazorTemplateEngineTest.cs197
-rw-r--r--test/System.Web.Razor.Test/StringTextBuffer.cs50
-rw-r--r--test/System.Web.Razor.Test/System.Web.Razor.Test.csproj464
-rw-r--r--test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Output/Blocks.cs194
-rw-r--r--test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Output/CodeBlock.cs31
-rw-r--r--test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Output/CodeBlockAtEOF.cs27
-rw-r--r--test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Output/Comments.cs38
-rw-r--r--test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Output/ConditionalAttributes.cs197
-rw-r--r--test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Output/DesignTime.cs108
-rw-r--r--test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Output/EmptyCodeBlock.cs27
-rw-r--r--test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Output/EmptyExplicitExpression.cs29
-rw-r--r--test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Output/EmptyImplicitExpression.cs29
-rw-r--r--test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Output/EmptyImplicitExpressionInCode.cs43
-rw-r--r--test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Output/ExplicitExpression.cs30
-rw-r--r--test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Output/ExplicitExpressionAtEOF.cs29
-rw-r--r--test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Output/ExpressionsInCode.cs89
-rw-r--r--test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Output/FunctionsBlock.DesignTime.cs46
-rw-r--r--test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Output/FunctionsBlock.cs49
-rw-r--r--test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Output/Helpers.Instance.cs96
-rw-r--r--test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Output/Helpers.cs96
-rw-r--r--test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Output/HelpersMissingCloseParen.cs61
-rw-r--r--test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Output/HelpersMissingName.cs21
-rw-r--r--test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Output/HelpersMissingOpenBrace.cs67
-rw-r--r--test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Output/HelpersMissingOpenParen.cs67
-rw-r--r--test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Output/HiddenSpansInCode.cs35
-rw-r--r--test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Output/ImplicitExpression.cs45
-rw-r--r--test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Output/ImplicitExpressionAtEOF.cs29
-rw-r--r--test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Output/Imports.DesignTime.cs53
-rw-r--r--test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Output/Imports.cs58
-rw-r--r--test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Output/Inherits.Designtime.cs40
-rw-r--r--test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Output/Inherits.Runtime.cs30
-rw-r--r--test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Output/InlineBlocks.cs72
-rw-r--r--test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Output/Instrumented.cs348
-rw-r--r--test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Output/LayoutDirective.cs22
-rw-r--r--test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Output/MarkupInCodeBlock.cs49
-rw-r--r--test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Output/NestedCodeBlocks.cs42
-rw-r--r--test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Output/NestedHelpers.cs100
-rw-r--r--test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Output/NoLinePragmas.cs102
-rw-r--r--test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Output/ParserError.cs31
-rw-r--r--test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Output/RazorComments.DesignTime.cs72
-rw-r--r--test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Output/RazorComments.cs83
-rw-r--r--test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Output/ResolveUrl.cs230
-rw-r--r--test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Output/Sections.cs45
-rw-r--r--test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Output/Templates.cs180
-rw-r--r--test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Output/UnfinishedExpressionInCode.cs43
-rw-r--r--test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Source/Blocks.cshtml37
-rw-r--r--test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Source/CodeBlock.cshtml5
-rw-r--r--test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Source/CodeBlockAtEOF.cshtml1
-rw-r--r--test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Source/ConditionalAttributes.cshtml15
-rw-r--r--test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Source/DesignTime.cshtml21
-rw-r--r--test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Source/EmptyCodeBlock.cshtml3
-rw-r--r--test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Source/EmptyExplicitExpression.cshtml3
-rw-r--r--test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Source/EmptyImplicitExpression.cshtml3
-rw-r--r--test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Source/EmptyImplicitExpressionInCode.cshtml3
-rw-r--r--test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Source/ExplicitExpression.cshtml1
-rw-r--r--test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Source/ExplicitExpressionAtEOF.cshtml3
-rw-r--r--test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Source/ExpressionsInCode.cshtml16
-rw-r--r--test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Source/FunctionsBlock.cshtml12
-rw-r--r--test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Source/Helpers.cshtml11
-rw-r--r--test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Source/HelpersMissingCloseParen.cshtml7
-rw-r--r--test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Source/HelpersMissingName.cshtml1
-rw-r--r--test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Source/HelpersMissingOpenBrace.cshtml7
-rw-r--r--test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Source/HelpersMissingOpenParen.cshtml7
-rw-r--r--test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Source/HiddenSpansInCode.cshtml3
-rw-r--r--test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Source/ImplicitExpression.cshtml3
-rw-r--r--test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Source/ImplicitExpressionAtEOF.cshtml3
-rw-r--r--test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Source/Imports.cshtml6
-rw-r--r--test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Source/Inherits.cshtml3
-rw-r--r--test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Source/InlineBlocks.cshtml3
-rw-r--r--test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Source/Instrumented.cshtml42
-rw-r--r--test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Source/LayoutDirective.cshtml1
-rw-r--r--test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Source/MarkupInCodeBlock.cshtml5
-rw-r--r--test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Source/NestedCodeBlocks.cshtml4
-rw-r--r--test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Source/NestedHelpers.cshtml10
-rw-r--r--test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Source/NoLinePragmas.cshtml38
-rw-r--r--test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Source/ParserError.cshtml5
-rw-r--r--test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Source/RazorComments.cshtml15
-rw-r--r--test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Source/ResolveUrl.cshtml20
-rw-r--r--test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Source/Sections.cshtml13
-rw-r--r--test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Source/Templates.cshtml35
-rw-r--r--test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Source/UnfinishedExpressionInCode.cshtml3
-rw-r--r--test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Output/Blocks.vb255
-rw-r--r--test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Output/CodeBlock.vb31
-rw-r--r--test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Output/CodeBlockAtEOF.vb29
-rw-r--r--test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Output/Comments.vb51
-rw-r--r--test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Output/ConditionalAttributes.vb146
-rw-r--r--test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Output/DesignTime.vb99
-rw-r--r--test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Output/EmptyExplicitExpression.vb31
-rw-r--r--test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Output/EmptyImplicitExpression.vb31
-rw-r--r--test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Output/EmptyImplicitExpressionInCode.vb43
-rw-r--r--test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Output/ExplicitExpression.vb30
-rw-r--r--test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Output/ExplicitExpressionAtEOF.vb31
-rw-r--r--test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Output/ExpressionsInCode.vb86
-rw-r--r--test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Output/FunctionsBlock.DesignTime.vb47
-rw-r--r--test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Output/FunctionsBlock.vb50
-rw-r--r--test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Output/Helpers.Instance.vb87
-rw-r--r--test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Output/Helpers.vb87
-rw-r--r--test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Output/HelpersMissingCloseParen.vb60
-rw-r--r--test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Output/HelpersMissingName.vb24
-rw-r--r--test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Output/HelpersMissingOpenParen.vb66
-rw-r--r--test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Output/ImplicitExpression.vb56
-rw-r--r--test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Output/ImplicitExpressionAtEOF.vb31
-rw-r--r--test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Output/Imports.DesignTime.vb41
-rw-r--r--test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Output/Imports.vb46
-rw-r--r--test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Output/Inherits.Designtime.vb35
-rw-r--r--test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Output/Inherits.Runtime.vb25
-rw-r--r--test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Output/Instrumented.vb497
-rw-r--r--test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Output/LayoutDirective.vb25
-rw-r--r--test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Output/MarkupInCodeBlock.vb51
-rw-r--r--test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Output/NestedCodeBlocks.vb42
-rw-r--r--test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Output/NestedHelpers.vb90
-rw-r--r--test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Output/NoLinePragmas.vb143
-rw-r--r--test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Output/Options.vb28
-rw-r--r--test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Output/ParserError.vb31
-rw-r--r--test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Output/RazorComments.DesignTime.vb59
-rw-r--r--test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Output/RazorComments.vb72
-rw-r--r--test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Output/ResolveUrl.vb183
-rw-r--r--test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Output/Sections.vb47
-rw-r--r--test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Output/Templates.vb169
-rw-r--r--test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Output/UnfinishedExpressionInCode.vb43
-rw-r--r--test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Source/Blocks.vbhtml46
-rw-r--r--test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Source/CodeBlock.vbhtml3
-rw-r--r--test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Source/CodeBlockAtEOF.vbhtml3
-rw-r--r--test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Source/ConditionalAttributes.vbhtml12
-rw-r--r--test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Source/DesignTime.vbhtml21
-rw-r--r--test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Source/EmptyExplicitExpression.vbhtml3
-rw-r--r--test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Source/EmptyImplicitExpression.vbhtml3
-rw-r--r--test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Source/EmptyImplicitExpressionInCode.vbhtml3
-rw-r--r--test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Source/ExplicitExpression.vbhtml1
-rw-r--r--test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Source/ExplicitExpressionAtEOF.vbhtml3
-rw-r--r--test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Source/ExpressionsInCode.vbhtml16
-rw-r--r--test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Source/FunctionsBlock.vbhtml12
-rw-r--r--test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Source/Helpers.vbhtml11
-rw-r--r--test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Source/HelpersMissingCloseParen.vbhtml8
-rw-r--r--test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Source/HelpersMissingName.vbhtml1
-rw-r--r--test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Source/HelpersMissingOpenParen.vbhtml8
-rw-r--r--test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Source/ImplicitExpression.vbhtml5
-rw-r--r--test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Source/ImplicitExpressionAtEOF.vbhtml3
-rw-r--r--test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Source/Imports.vbhtml6
-rw-r--r--test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Source/Inherits.vbhtml1
-rw-r--r--test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Source/Instrumented.vbhtml53
-rw-r--r--test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Source/LayoutDirective.vbhtml1
-rw-r--r--test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Source/MarkupInCodeBlock.vbhtml5
-rw-r--r--test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Source/NestedCodeBlocks.vbhtml4
-rw-r--r--test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Source/NestedHelpers.vbhtml10
-rw-r--r--test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Source/NoLinePragmas.vbhtml45
-rw-r--r--test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Source/Options.vbhtml4
-rw-r--r--test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Source/ParserError.vbhtml3
-rw-r--r--test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Source/RazorComments.vbhtml12
-rw-r--r--test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Source/ResolveUrl.vbhtml20
-rw-r--r--test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Source/Sections.vbhtml13
-rw-r--r--test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Source/Templates.vbhtml36
-rw-r--r--test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Source/UnfinishedExpressionInCode.vbhtml3
-rw-r--r--test/System.Web.Razor.Test/TestFiles/DesignTime/Simple.cshtml16
-rw-r--r--test/System.Web.Razor.Test/TestFiles/DesignTime/Simple.txt60
-rw-r--r--test/System.Web.Razor.Test/TestFiles/nested-1000.html2002
-rw-r--r--test/System.Web.Razor.Test/Text/BufferingTextReaderTest.cs265
-rw-r--r--test/System.Web.Razor.Test/Text/LineTrackingStringBufferTest.cs25
-rw-r--r--test/System.Web.Razor.Test/Text/LookaheadTextReaderTestBase.cs252
-rw-r--r--test/System.Web.Razor.Test/Text/SourceLocationTest.cs20
-rw-r--r--test/System.Web.Razor.Test/Text/SourceLocationTrackerTest.cs179
-rw-r--r--test/System.Web.Razor.Test/Text/TextBufferReaderTest.cs229
-rw-r--r--test/System.Web.Razor.Test/Text/TextChangeTest.cs249
-rw-r--r--test/System.Web.Razor.Test/Text/TextReaderExtensionsTest.cs184
-rw-r--r--test/System.Web.Razor.Test/Tokenizer/CSharpTokenizerCommentTest.cs87
-rw-r--r--test/System.Web.Razor.Test/Tokenizer/CSharpTokenizerIdentifierTest.cs167
-rw-r--r--test/System.Web.Razor.Test/Tokenizer/CSharpTokenizerLiteralTest.cs222
-rw-r--r--test/System.Web.Razor.Test/Tokenizer/CSharpTokenizerOperatorsTest.cs294
-rw-r--r--test/System.Web.Razor.Test/Tokenizer/CSharpTokenizerTest.cs102
-rw-r--r--test/System.Web.Razor.Test/Tokenizer/CSharpTokenizerTestBase.cs26
-rw-r--r--test/System.Web.Razor.Test/Tokenizer/HtmlTokenizerTest.cs166
-rw-r--r--test/System.Web.Razor.Test/Tokenizer/HtmlTokenizerTestBase.cs26
-rw-r--r--test/System.Web.Razor.Test/Tokenizer/TokenizerLookaheadTest.cs39
-rw-r--r--test/System.Web.Razor.Test/Tokenizer/TokenizerTestBase.cs74
-rw-r--r--test/System.Web.Razor.Test/Tokenizer/VBTokenizerCommentTest.cs91
-rw-r--r--test/System.Web.Razor.Test/Tokenizer/VBTokenizerIdentifierTest.cs265
-rw-r--r--test/System.Web.Razor.Test/Tokenizer/VBTokenizerLiteralTest.cs180
-rw-r--r--test/System.Web.Razor.Test/Tokenizer/VBTokenizerOperatorsTest.cs152
-rw-r--r--test/System.Web.Razor.Test/Tokenizer/VBTokenizerTest.cs94
-rw-r--r--test/System.Web.Razor.Test/Tokenizer/VBTokenizerTestBase.cs26
-rw-r--r--test/System.Web.Razor.Test/Utils/DisposableActionTest.cs29
-rw-r--r--test/System.Web.Razor.Test/Utils/EnumerableUtils.cs17
-rw-r--r--test/System.Web.Razor.Test/Utils/MiscAssert.cs31
-rw-r--r--test/System.Web.Razor.Test/Utils/MiscUtils.cs33
-rw-r--r--test/System.Web.Razor.Test/Utils/SpanAssert.cs56
-rw-r--r--test/System.Web.Razor.Test/VBRazorCodeLanguageTest.cs53
-rw-r--r--test/System.Web.Razor.Test/packages.config6
-rw-r--r--test/System.Web.WebPages.Administration.Test/AdminPackageTest.cs397
-rw-r--r--test/System.Web.WebPages.Administration.Test/PackageManagerModuleTest.cs150
-rw-r--r--test/System.Web.WebPages.Administration.Test/PackagesSourceFileTest.cs146
-rw-r--r--test/System.Web.WebPages.Administration.Test/PageUtilsTest.cs153
-rw-r--r--test/System.Web.WebPages.Administration.Test/PreApplicationStartCodeTest.cs33
-rw-r--r--test/System.Web.WebPages.Administration.Test/RemoteAssemblyTest.cs147
-rw-r--r--test/System.Web.WebPages.Administration.Test/System.Web.WebPages.Administration.Test.csproj102
-rw-r--r--test/System.Web.WebPages.Administration.Test/WebProjectManagerTest.cs160
-rw-r--r--test/System.Web.WebPages.Administration.Test/WebProjectSystemTest.cs253
-rw-r--r--test/System.Web.WebPages.Administration.Test/packages.config7
-rw-r--r--test/System.Web.WebPages.Deployment.Test/App.Config3
-rw-r--r--test/System.Web.WebPages.Deployment.Test/AssemblyUtilsTest.cs266
-rw-r--r--test/System.Web.WebPages.Deployment.Test/DeploymentUtil.cs13
-rw-r--r--test/System.Web.WebPages.Deployment.Test/PreApplicationStartCodeTest.cs510
-rw-r--r--test/System.Web.WebPages.Deployment.Test/Properties/AssemblyInfo.cs34
-rw-r--r--test/System.Web.WebPages.Deployment.Test/System.Web.WebPages.Deployment.Test.csproj97
-rw-r--r--test/System.Web.WebPages.Deployment.Test/TestFileSystem.cs50
-rw-r--r--test/System.Web.WebPages.Deployment.Test/TestFiles/ConfigTestAssemblies/V2_Signed/System.Web.WebPages.Deployment.dllbin0 -> 3584 bytes
-rw-r--r--test/System.Web.WebPages.Deployment.Test/TestFiles/ConfigTestAssemblies/V2_Unsigned/System.Web.WebPages.Deployment.dllbin0 -> 3072 bytes
-rw-r--r--test/System.Web.WebPages.Deployment.Test/TestFiles/ConfigTestSites/CshtmlFileConfigV1/Default.cshtml9
-rw-r--r--test/System.Web.WebPages.Deployment.Test/TestFiles/ConfigTestSites/CshtmlFileConfigV1/web.config6
-rw-r--r--test/System.Web.WebPages.Deployment.Test/TestFiles/ConfigTestSites/CshtmlFileNoVersion/Default.cshtml9
-rw-r--r--test/System.Web.WebPages.Deployment.Test/TestFiles/ConfigTestSites/NoCshtml/Default.htm9
-rw-r--r--test/System.Web.WebPages.Deployment.Test/TestFiles/ConfigTestSites/NoCshtmlConfigv1/Default.htm9
-rw-r--r--test/System.Web.WebPages.Deployment.Test/TestFiles/ConfigTestSites/NoCshtmlConfigv1/web.config6
-rw-r--r--test/System.Web.WebPages.Deployment.Test/TestFiles/ConfigTestSites/NoCshtmlNoConfigSetting/Default.htm9
-rw-r--r--test/System.Web.WebPages.Deployment.Test/TestFiles/ConfigTestSites/NoCshtmlNoConfigSetting/web.config3
-rw-r--r--test/System.Web.WebPages.Deployment.Test/TestFiles/ConfigTestSites/NoCshtmlWithEnabledSetting/Default.htm9
-rw-r--r--test/System.Web.WebPages.Deployment.Test/TestFiles/ConfigTestSites/NoCshtmlWithEnabledSetting/web.config6
-rw-r--r--test/System.Web.WebPages.Deployment.Test/TestFiles/ConfigTestSites/NoCshtmlWithEnabledSettingFalse/Default.htm9
-rw-r--r--test/System.Web.WebPages.Deployment.Test/TestFiles/ConfigTestSites/NoCshtmlWithEnabledSettingFalse/web.config6
-rw-r--r--test/System.Web.WebPages.Deployment.Test/WebPagesDeploymentTest.cs303
-rw-r--r--test/System.Web.WebPages.Deployment.Test/packages.config5
-rw-r--r--test/System.Web.WebPages.Razor.Test/PreApplicationStartCodeTest.cs28
-rw-r--r--test/System.Web.WebPages.Razor.Test/Properties/AssemblyInfo.cs34
-rw-r--r--test/System.Web.WebPages.Razor.Test/RazorBuildProviderTest.cs248
-rw-r--r--test/System.Web.WebPages.Razor.Test/System.Web.WebPages.Razor.Test.csproj92
-rw-r--r--test/System.Web.WebPages.Razor.Test/WebCodeRazorEngineHostTest.cs110
-rw-r--r--test/System.Web.WebPages.Razor.Test/WebPageRazorEngineHostTest.cs100
-rw-r--r--test/System.Web.WebPages.Razor.Test/WebRazorHostFactoryTest.cs387
-rw-r--r--test/System.Web.WebPages.Razor.Test/app.config21
-rw-r--r--test/System.Web.WebPages.Razor.Test/packages.config6
-rw-r--r--test/System.Web.WebPages.Test/App.config18
-rw-r--r--test/System.Web.WebPages.Test/ApplicationParts/ApplicationPartRegistryTest.cs150
-rw-r--r--test/System.Web.WebPages.Test/ApplicationParts/ApplicationPartTest.cs198
-rw-r--r--test/System.Web.WebPages.Test/ApplicationParts/MimeMappingTest.cs61
-rw-r--r--test/System.Web.WebPages.Test/ApplicationParts/ResourceHandlerTest.cs62
-rw-r--r--test/System.Web.WebPages.Test/ApplicationParts/TestResourceAssembly.cs17
-rw-r--r--test/System.Web.WebPages.Test/Extensions/HttpContextExtensionsTest.cs83
-rw-r--r--test/System.Web.WebPages.Test/Extensions/HttpRequestExtensionsTest.cs130
-rw-r--r--test/System.Web.WebPages.Test/Extensions/HttpResponseExtensionsTest.cs128
-rw-r--r--test/System.Web.WebPages.Test/Extensions/StringExtensionsTest.cs251
-rw-r--r--test/System.Web.WebPages.Test/Helpers/AntiForgeryDataSerializerTest.cs103
-rw-r--r--test/System.Web.WebPages.Test/Helpers/AntiForgeryDataTest.cs168
-rw-r--r--test/System.Web.WebPages.Test/Helpers/AntiForgeryTest.cs36
-rw-r--r--test/System.Web.WebPages.Test/Helpers/AntiForgeryWorkerTest.cs237
-rw-r--r--test/System.Web.WebPages.Test/Helpers/UnvalidatedRequestValuesTest.cs78
-rw-r--r--test/System.Web.WebPages.Test/Html/CheckBoxTest.cs272
-rw-r--r--test/System.Web.WebPages.Test/Html/HtmlHelperFactory.cs16
-rw-r--r--test/System.Web.WebPages.Test/Html/HtmlHelperTest.cs159
-rw-r--r--test/System.Web.WebPages.Test/Html/InputHelperTest.cs584
-rw-r--r--test/System.Web.WebPages.Test/Html/RadioButtonTest.cs197
-rw-r--r--test/System.Web.WebPages.Test/Html/SelectHelperTest.cs776
-rw-r--r--test/System.Web.WebPages.Test/Html/TextAreaHelperTest.cs209
-rw-r--r--test/System.Web.WebPages.Test/Html/ValidationHelperTest.cs342
-rw-r--r--test/System.Web.WebPages.Test/Instrumentation/InstrumentationServiceTest.cs97
-rw-r--r--test/System.Web.WebPages.Test/Mvc/HttpAntiForgeryExceptionTest.cs64
-rw-r--r--test/System.Web.WebPages.Test/Mvc/TagBuilderTest.cs416
-rw-r--r--test/System.Web.WebPages.Test/PreApplicationStartCodeTest.cs38
-rw-r--r--test/System.Web.WebPages.Test/Properties/AssemblyInfo.cs34
-rw-r--r--test/System.Web.WebPages.Test/ScopeStorage/AspNetRequestScopeStorageProviderTest.cs93
-rw-r--r--test/System.Web.WebPages.Test/ScopeStorage/ScopeStorageDictionaryTest.cs170
-rw-r--r--test/System.Web.WebPages.Test/ScopeStorage/ScopeStorageKeyComparerTest.cs48
-rw-r--r--test/System.Web.WebPages.Test/ScopeStorage/WebConfigScopeStorageTest.cs78
-rw-r--r--test/System.Web.WebPages.Test/System.Web.WebPages.Test.csproj162
-rw-r--r--test/System.Web.WebPages.Test/TestFiles/Deployed/Bar1
-rw-r--r--test/System.Web.WebPages.Test/TestFiles/Deployed/Bar.cshtml1
-rw-r--r--test/System.Web.WebPages.Test/TestFiles/Deployed/Bar.foohtml1
-rw-r--r--test/System.Web.WebPages.Test/Utils/CultureUtilTest.cs256
-rw-r--r--test/System.Web.WebPages.Test/Utils/PathUtilTest.cs177
-rw-r--r--test/System.Web.WebPages.Test/Utils/SessionStateUtilTest.cs183
-rw-r--r--test/System.Web.WebPages.Test/Utils/TestObjectFactory.cs30
-rw-r--r--test/System.Web.WebPages.Test/Utils/TypeHelperTest.cs111
-rw-r--r--test/System.Web.WebPages.Test/Utils/UrlUtilTest.cs207
-rw-r--r--test/System.Web.WebPages.Test/Validation/ValidationHelperTest.cs835
-rw-r--r--test/System.Web.WebPages.Test/Validation/ValidatorTest.cs882
-rw-r--r--test/System.Web.WebPages.Test/WebPage/ApplicationStartPageTest.cs166
-rw-r--r--test/System.Web.WebPages.Test/WebPage/BrowserHelpersTest.cs285
-rw-r--r--test/System.Web.WebPages.Test/WebPage/BrowserOverrideStoresTest.cs42
-rw-r--r--test/System.Web.WebPages.Test/WebPage/BuildManagerExceptionUtilTest.cs90
-rw-r--r--test/System.Web.WebPages.Test/WebPage/BuildManagerWrapperTest.cs277
-rw-r--r--test/System.Web.WebPages.Test/WebPage/CookieBrowserOverrideStoreTest.cs153
-rw-r--r--test/System.Web.WebPages.Test/WebPage/DefaultDisplayModeTest.cs139
-rw-r--r--test/System.Web.WebPages.Test/WebPage/DisplayInfoTest.cs37
-rw-r--r--test/System.Web.WebPages.Test/WebPage/DisplayModeProviderTest.cs229
-rw-r--r--test/System.Web.WebPages.Test/WebPage/DynamicHttpApplicationStateTest.cs75
-rw-r--r--test/System.Web.WebPages.Test/WebPage/DynamicPageDataDictionaryTest.cs243
-rw-r--r--test/System.Web.WebPages.Test/WebPage/FileExistenceCacheTest.cs95
-rw-r--r--test/System.Web.WebPages.Test/WebPage/LayoutTest.cs714
-rw-r--r--test/System.Web.WebPages.Test/WebPage/PageDataDictionaryTest.cs236
-rw-r--r--test/System.Web.WebPages.Test/WebPage/RenderPageTest.cs867
-rw-r--r--test/System.Web.WebPages.Test/WebPage/RequestBrowserOverrideStoreTest.cs35
-rw-r--r--test/System.Web.WebPages.Test/WebPage/RequestResourceTrackerTest.cs38
-rw-r--r--test/System.Web.WebPages.Test/WebPage/SectionControlBuilderTest.cs249
-rw-r--r--test/System.Web.WebPages.Test/WebPage/StartPageTest.cs568
-rw-r--r--test/System.Web.WebPages.Test/WebPage/TemplateStackTest.cs86
-rw-r--r--test/System.Web.WebPages.Test/WebPage/UrlDataTest.cs111
-rw-r--r--test/System.Web.WebPages.Test/WebPage/Utils.cs261
-rw-r--r--test/System.Web.WebPages.Test/WebPage/VirtualPathFactoryExtensionsTest.cs61
-rw-r--r--test/System.Web.WebPages.Test/WebPage/VirtualPathFactoryManagerTest.cs103
-rw-r--r--test/System.Web.WebPages.Test/WebPage/WebPageContextTest.cs57
-rw-r--r--test/System.Web.WebPages.Test/WebPage/WebPageExecutingBaseTest.cs189
-rw-r--r--test/System.Web.WebPages.Test/WebPage/WebPageHttpHandlerTest.cs249
-rw-r--r--test/System.Web.WebPages.Test/WebPage/WebPageHttpModuleTest.cs83
-rw-r--r--test/System.Web.WebPages.Test/WebPage/WebPageRenderingBaseTest.cs65
-rw-r--r--test/System.Web.WebPages.Test/WebPage/WebPageRouteTest.cs307
-rw-r--r--test/System.Web.WebPages.Test/WebPage/WebPageSurrogateControlBuilderTest.cs550
-rw-r--r--test/System.Web.WebPages.Test/WebPage/WebPageTest.cs309
-rw-r--r--test/System.Web.WebPages.Test/packages.config6
-rw-r--r--test/WebMatrix.Data.Test/App.config9
-rw-r--r--test/WebMatrix.Data.Test/ConfigurationManagerWrapperTest.cs90
-rw-r--r--test/WebMatrix.Data.Test/DatabaseTest.cs89
-rw-r--r--test/WebMatrix.Data.Test/DynamicRecordTest.cs150
-rw-r--r--test/WebMatrix.Data.Test/FileHandlerTest.cs52
-rw-r--r--test/WebMatrix.Data.Test/Mocks/MockConfigurationManager.cs28
-rw-r--r--test/WebMatrix.Data.Test/Mocks/MockConnectionConfiguration.cs22
-rw-r--r--test/WebMatrix.Data.Test/Mocks/MockDbFileHandler.cs12
-rw-r--r--test/WebMatrix.Data.Test/Mocks/MockDbProviderFactory.cs10
-rw-r--r--test/WebMatrix.Data.Test/Properties/AssemblyInfo.cs8
-rw-r--r--test/WebMatrix.Data.Test/WebMatrix.Data.Test.csproj90
-rw-r--r--test/WebMatrix.Data.Test/packages.config6
-rw-r--r--test/WebMatrix.WebData.Test/MockDatabase.cs20
-rw-r--r--test/WebMatrix.WebData.Test/PreApplicationStartCodeTest.cs102
-rw-r--r--test/WebMatrix.WebData.Test/Properties/AssemblyInfo.cs8
-rw-r--r--test/WebMatrix.WebData.Test/SimpleMembershipProviderTest.cs199
-rw-r--r--test/WebMatrix.WebData.Test/WebMatrix.WebData.Test.csproj94
-rw-r--r--test/WebMatrix.WebData.Test/WebSecurityTest.cs26
-rw-r--r--test/WebMatrix.WebData.Test/packages.config6
1158 files changed, 198432 insertions, 0 deletions
diff --git a/test/Microsoft.TestCommon/AppDomainUtils.cs b/test/Microsoft.TestCommon/AppDomainUtils.cs
new file mode 100644
index 00000000..addbcee1
--- /dev/null
+++ b/test/Microsoft.TestCommon/AppDomainUtils.cs
@@ -0,0 +1,71 @@
+using System.IO;
+using System.Reflection;
+using System.Web.Compilation;
+using System.Web.Hosting;
+
+namespace System.Web.WebPages.TestUtils
+{
+ public static class AppDomainUtils
+ {
+ // Allow a test to modify static fields in an independent appdomain so that
+ // other tests will not be affected.
+ public static void RunInSeparateAppDomain(Action action)
+ {
+ RunInSeparateAppDomain(new AppDomainSetup(), action);
+ }
+
+ public static void RunInSeparateAppDomain(AppDomainSetup setup, Action action)
+ {
+ var dir = Path.GetDirectoryName(typeof(AppDomainUtils).Assembly.CodeBase).Replace("file:\\", "");
+ setup.PrivateBinPath = dir;
+ setup.ApplicationBase = dir;
+ setup.ApplicationName = Guid.NewGuid().ToString();
+ setup.ShadowCopyFiles = "true";
+ setup.ShadowCopyDirectories = setup.ApplicationBase;
+ setup.CachePath = Path.Combine(Path.GetTempPath(), setup.ApplicationName);
+
+ AppDomain appDomain = null;
+ try
+ {
+ appDomain = AppDomain.CreateDomain(setup.ApplicationName, null, setup);
+ AppDomainHelper helper = appDomain.CreateInstanceAndUnwrap(typeof(AppDomainUtils).Assembly.FullName, typeof(AppDomainHelper).FullName) as AppDomainHelper;
+ helper.Run(action);
+ }
+ finally
+ {
+ if (appDomain != null)
+ {
+ AppDomain.Unload(appDomain);
+ }
+ }
+ }
+
+ public class AppDomainHelper : MarshalByRefObject
+ {
+ public void Run(Action action)
+ {
+ action();
+ }
+ }
+
+ public static void SetPreAppStartStage()
+ {
+ var stage = typeof(BuildManager).GetProperty("PreStartInitStage", BindingFlags.Static | BindingFlags.NonPublic);
+ var value = ((FieldInfo)typeof(BuildManager).Assembly.GetType("System.Web.Compilation.PreStartInitStage").GetMember("DuringPreStartInit")[0]).GetValue(null);
+ stage.SetValue(null, value, new object[] { });
+ SetAppData();
+ var env = new HostingEnvironment();
+ }
+
+ public static void SetAppData()
+ {
+ var appdomain = AppDomain.CurrentDomain;
+ // Set some dummy values to make the appdomain seem more like a ASP.NET hosted one
+ appdomain.SetData(".appDomain", "*");
+ appdomain.SetData(".appId", "appId");
+ appdomain.SetData(".appPath", "appPath");
+ appdomain.SetData(".appVPath", "/WebSite1");
+ appdomain.SetData(".domainId", "1");
+ }
+ }
+}
diff --git a/test/Microsoft.TestCommon/AssertEx.cs b/test/Microsoft.TestCommon/AssertEx.cs
new file mode 100644
index 00000000..6b1a227c
--- /dev/null
+++ b/test/Microsoft.TestCommon/AssertEx.cs
@@ -0,0 +1,31 @@
+using System;
+using Xunit;
+
+namespace Microsoft.TestCommon
+{
+ // This extends xUnit.net's Assert class, and makes it partial so that we can
+ // organize the extension points by logical functionality (rather than dumping them
+ // all into this single file).
+ //
+ // See files named XxxAssertions for root extensions to AssertEx.
+ public partial class AssertEx : Assert
+ {
+ public static readonly ReflectionAssert Reflection = new ReflectionAssert();
+
+ public static readonly TypeAssert Type = new TypeAssert();
+
+ public static readonly HttpAssert Http = new HttpAssert();
+
+ public static readonly MediaTypeAssert MediaType = new MediaTypeAssert();
+
+ public static readonly GenericTypeAssert GenericType = new GenericTypeAssert();
+
+ public static readonly SerializerAssert Serializer = new SerializerAssert();
+
+ public static readonly StreamAssert Stream = new StreamAssert();
+
+ public static readonly TaskAssert Task = new TaskAssert();
+
+ public static readonly XmlAssert Xml = new XmlAssert();
+ }
+}
diff --git a/test/Microsoft.TestCommon/CultureReplacer.cs b/test/Microsoft.TestCommon/CultureReplacer.cs
new file mode 100644
index 00000000..2ef7a1ed
--- /dev/null
+++ b/test/Microsoft.TestCommon/CultureReplacer.cs
@@ -0,0 +1,34 @@
+using System.Globalization;
+using System.Threading;
+using Xunit;
+
+namespace System.Web.TestUtil
+{
+ public class CultureReplacer : IDisposable
+ {
+ private readonly CultureInfo _originalCulture;
+ private readonly long _threadId;
+
+ public CultureReplacer(string culture = "en-us")
+ {
+ _originalCulture = Thread.CurrentThread.CurrentCulture;
+ _threadId = Thread.CurrentThread.ManagedThreadId;
+ Thread.CurrentThread.CurrentCulture = CultureInfo.GetCultureInfo(culture);
+ }
+
+ public void Dispose()
+ {
+ Dispose(true);
+ GC.SuppressFinalize(this);
+ }
+
+ private void Dispose(bool disposing)
+ {
+ if (disposing)
+ {
+ Assert.True(Thread.CurrentThread.ManagedThreadId == _threadId, "The current thread is not the same as the thread invoking the constructor. This should never happen.");
+ Thread.CurrentThread.CurrentCulture = _originalCulture;
+ }
+ }
+ }
+}
diff --git a/test/Microsoft.TestCommon/DefaultTimeoutFactAttribute.cs b/test/Microsoft.TestCommon/DefaultTimeoutFactAttribute.cs
new file mode 100644
index 00000000..d9708552
--- /dev/null
+++ b/test/Microsoft.TestCommon/DefaultTimeoutFactAttribute.cs
@@ -0,0 +1,17 @@
+using System;
+using Xunit;
+
+namespace Microsoft.TestCommon
+{
+ /// <summary>
+ /// An override of <see cref="FactAttribute"/> that provides a default timeout.
+ /// </summary>
+ [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
+ public class DefaultTimeoutFactAttribute : FactAttribute
+ {
+ public DefaultTimeoutFactAttribute()
+ {
+ Timeout = TimeoutConstant.DefaultTimeout;
+ }
+ }
+}
diff --git a/test/Microsoft.TestCommon/DefaultTimeoutTheoryAttribute.cs b/test/Microsoft.TestCommon/DefaultTimeoutTheoryAttribute.cs
new file mode 100644
index 00000000..dbfa12b3
--- /dev/null
+++ b/test/Microsoft.TestCommon/DefaultTimeoutTheoryAttribute.cs
@@ -0,0 +1,14 @@
+using System;
+using Xunit.Extensions;
+
+namespace Microsoft.TestCommon
+{
+ [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
+ public class DefaultTimeoutTheoryAttribute : TheoryAttribute
+ {
+ public DefaultTimeoutTheoryAttribute()
+ {
+ Timeout = TimeoutConstant.DefaultTimeout;
+ }
+ }
+}
diff --git a/test/Microsoft.TestCommon/DictionaryEqualityComparer.cs b/test/Microsoft.TestCommon/DictionaryEqualityComparer.cs
new file mode 100644
index 00000000..34955d58
--- /dev/null
+++ b/test/Microsoft.TestCommon/DictionaryEqualityComparer.cs
@@ -0,0 +1,47 @@
+using System.Collections.Generic;
+
+namespace Microsoft.TestCommon
+{
+ public class DictionaryEqualityComparer : IEqualityComparer<IDictionary<string, object>>
+ {
+ public bool Equals(IDictionary<string, object> x, IDictionary<string, object> y)
+ {
+ if (x.Count != y.Count)
+ {
+ return false;
+ }
+
+ foreach (string key in x.Keys)
+ {
+ object xVal = x[key];
+ object yVal;
+ if (!y.TryGetValue(key, out yVal))
+ {
+ return false;
+ }
+
+ if (xVal == null)
+ {
+ if (yVal == null)
+ {
+ continue;
+ }
+
+ return false;
+ }
+
+ if (!xVal.Equals(yVal))
+ {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ public int GetHashCode(IDictionary<string, object> obj)
+ {
+ return 1;
+ }
+ }
+}
diff --git a/test/Microsoft.TestCommon/ExceptionAssertions.cs b/test/Microsoft.TestCommon/ExceptionAssertions.cs
new file mode 100644
index 00000000..b16e84d6
--- /dev/null
+++ b/test/Microsoft.TestCommon/ExceptionAssertions.cs
@@ -0,0 +1,515 @@
+using System;
+using System.ComponentModel;
+using System.Globalization;
+using System.Reflection;
+using System.Web;
+
+namespace Microsoft.TestCommon
+{
+ public partial class AssertEx
+ {
+ /// <summary>
+ /// Determines if your thread's current culture and current UI culture is English.
+ /// </summary>
+ public static bool CurrentCultureIsEnglish
+ {
+ get
+ {
+ return String.Equals(CultureInfo.CurrentCulture.TwoLetterISOLanguageName, "en", StringComparison.OrdinalIgnoreCase)
+ && String.Equals(CultureInfo.CurrentUICulture.TwoLetterISOLanguageName, "en", StringComparison.OrdinalIgnoreCase);
+ }
+ }
+
+ /// <summary>
+ /// Determines whether the specified exception is of the given type (or optionally of a derived type).
+ /// The exception is not allowed to be null;
+ /// </summary>
+ /// <param name="exceptionType">The type of the exception to test for.</param>
+ /// <param name="exception">The exception to be tested.</param>
+ /// <param name="expectedMessage">The expected exception message (only verified on US English OSes).</param>
+ /// <param name="allowDerivedExceptions">Pass true to allow exceptions which derive from TException; pass false, otherwise</param>
+ public static void IsException(Type exceptionType, Exception exception, string expectedMessage = null, bool allowDerivedExceptions = false)
+ {
+ exception = UnwrapException(exception);
+ NotNull(exception);
+
+ if (allowDerivedExceptions)
+ IsAssignableFrom(exceptionType, exception);
+ else
+ IsType(exceptionType, exception);
+
+ VerifyExceptionMessage(exception, expectedMessage, partialMatch: false);
+ }
+
+ /// <summary>
+ /// Determines whether the specified exception is of the given type (or optionally of a derived type).
+ /// The exception is not allowed to be null;
+ /// </summary>
+ /// <typeparam name="TException">The type of the exception to test for.</typeparam>
+ /// <param name="exception">The exception to be tested.</param>
+ /// <param name="expectedMessage">The expected exception message (only verified on US English OSes).</param>
+ /// <param name="allowDerivedExceptions">Pass true to allow exceptions which derive from TException; pass false, otherwise</param>
+ /// <returns>The exception cast to TException.</returns>
+ public static TException IsException<TException>(Exception exception, string expectedMessage = null, bool allowDerivedExceptions = false)
+ where TException : Exception
+ {
+ TException result;
+
+ exception = UnwrapException(exception);
+ NotNull(exception);
+
+ if (allowDerivedExceptions)
+ result = IsAssignableFrom<TException>(exception);
+ else
+ result = IsType<TException>(exception);
+
+ VerifyExceptionMessage(exception, expectedMessage, partialMatch: false);
+ return result;
+ }
+
+ // We've re-implemented all the xUnit.net Throws code so that we can get this
+ // updated implementation of RecordException which silently unwraps any instances
+ // of AggregateException. This lets our tests better simulate what "await" would do
+ // and thus makes them easier to port to .NET 4.5.
+ private static Exception RecordException(Action testCode)
+ {
+ try
+ {
+ testCode();
+ return null;
+ }
+ catch (Exception exception)
+ {
+ return UnwrapException(exception);
+ }
+ }
+
+ /// <summary>
+ /// Verifies that the exact exception is thrown (and not a derived exception type).
+ /// </summary>
+ /// <typeparam name="T">The type of the exception expected to be thrown</typeparam>
+ /// <param name="testCode">A delegate to the code to be tested</param>
+ /// <returns>The exception that was thrown, when successful</returns>
+ /// <exception cref="ThrowsException">Thrown when an exception was not thrown, or when an exception of the incorrect type is thrown</exception>
+ public static T Throws<T>(Action testCode)
+ where T : Exception
+ {
+ return (T)Throws(typeof(T), testCode);
+ }
+
+ /// <summary>
+ /// Verifies that the exact exception is thrown (and not a derived exception type).
+ /// Generally used to test property accessors.
+ /// </summary>
+ /// <typeparam name="T">The type of the exception expected to be thrown</typeparam>
+ /// <param name="testCode">A delegate to the code to be tested</param>
+ /// <returns>The exception that was thrown, when successful</returns>
+ /// <exception cref="ThrowsException">Thrown when an exception was not thrown, or when an exception of the incorrect type is thrown</exception>
+ public static T Throws<T>(Func<object> testCode)
+ where T : Exception
+ {
+ return (T)Throws(typeof(T), testCode);
+ }
+
+ /// <summary>
+ /// Verifies that the exact exception is thrown (and not a derived exception type).
+ /// </summary>
+ /// <param name="exceptionType">The type of the exception expected to be thrown</param>
+ /// <param name="testCode">A delegate to the code to be tested</param>
+ /// <returns>The exception that was thrown, when successful</returns>
+ /// <exception cref="ThrowsException">Thrown when an exception was not thrown, or when an exception of the incorrect type is thrown</exception>
+ public static Exception Throws(Type exceptionType, Action testCode)
+ {
+ Exception exception = RecordException(testCode);
+
+ if (exception == null)
+ throw new ThrowsException(exceptionType);
+
+ if (!exceptionType.Equals(exception.GetType()))
+ throw new ThrowsException(exceptionType, exception);
+
+ return exception;
+ }
+
+ /// <summary>
+ /// Verifies that the exact exception is thrown (and not a derived exception type).
+ /// Generally used to test property accessors.
+ /// </summary>
+ /// <param name="exceptionType">The type of the exception expected to be thrown</param>
+ /// <param name="testCode">A delegate to the code to be tested</param>
+ /// <returns>The exception that was thrown, when successful</returns>
+ /// <exception cref="ThrowsException">Thrown when an exception was not thrown, or when an exception of the incorrect type is thrown</exception>
+ public static Exception Throws(Type exceptionType, Func<object> testCode)
+ {
+ return Throws(exceptionType, () => { object unused = testCode(); });
+ }
+
+ /// <summary>
+ /// Verifies that an exception of the given type (or optionally a derived type) is thrown.
+ /// </summary>
+ /// <typeparam name="TException">The type of the exception expected to be thrown</typeparam>
+ /// <param name="testCode">A delegate to the code to be tested</param>
+ /// <param name="allowDerivedExceptions">Pass true to allow exceptions which derive from TException; pass false, otherwise</param>
+ /// <returns>The exception that was thrown, when successful</returns>
+ /// <exception cref="ThrowsException">Thrown when an exception was not thrown, or when an exception of the incorrect type is thrown</exception>
+ public static TException Throws<TException>(Action testCode, bool allowDerivedExceptions)
+ where TException : Exception
+ {
+ Type exceptionType = typeof(TException);
+ Exception exception = RecordException(testCode);
+
+ TargetInvocationException tie = exception as TargetInvocationException;
+ if (tie != null)
+ {
+ exception = tie.InnerException;
+ }
+
+ if (exception == null)
+ {
+ throw new ThrowsException(exceptionType);
+ }
+
+ var typedException = exception as TException;
+ if (typedException == null || (!allowDerivedExceptions && typedException.GetType() != typeof(TException)))
+ {
+ throw new ThrowsException(exceptionType, exception);
+ }
+
+ return typedException;
+ }
+
+ /// <summary>
+ /// Verifies that an exception of the given type (or optionally a derived type) is thrown.
+ /// Generally used to test property accessors.
+ /// </summary>
+ /// <typeparam name="TException">The type of the exception expected to be thrown</typeparam>
+ /// <param name="testCode">A delegate to the code to be tested</param>
+ /// <param name="allowDerivedExceptions">Pass true to allow exceptions which derive from TException; pass false, otherwise</param>
+ /// <returns>The exception that was thrown, when successful</returns>
+ /// <exception cref="ThrowsException">Thrown when an exception was not thrown, or when an exception of the incorrect type is thrown</exception>
+ public static TException Throws<TException>(Func<object> testCode, bool allowDerivedExceptions)
+ where TException : Exception
+ {
+ return Throws<TException>(() => { testCode(); }, allowDerivedExceptions);
+ }
+
+ /// <summary>
+ /// Verifies that an exception of the given type (or optionally a derived type) is thrown.
+ /// Also verified that the exception message matches if the current thread locale is English.
+ /// </summary>
+ /// <typeparam name="TException">The type of the exception expected to be thrown</typeparam>
+ /// <param name="testCode">A delegate to the code to be tested</param>
+ /// <param name="exceptionMessage">The exception message to verify</param>
+ /// <param name="allowDerivedExceptions">Pass true to allow exceptions which derive from TException; pass false, otherwise</param>
+ /// <returns>The exception that was thrown, when successful</returns>
+ /// <exception cref="ThrowsException">Thrown when an exception was not thrown, or when an exception of the incorrect type is thrown</exception>
+ public static TException Throws<TException>(Action testCode, string exceptionMessage, bool allowDerivedExceptions = false)
+ where TException : Exception
+ {
+ var ex = Throws<TException>(testCode, allowDerivedExceptions);
+ VerifyExceptionMessage(ex, exceptionMessage);
+ return ex;
+ }
+
+ /// <summary>
+ /// Verifies that an exception of the given type (or optionally a derived type) is thrown.
+ /// Also verified that the exception message matches if the current thread locale is English.
+ /// </summary>
+ /// <typeparam name="TException">The type of the exception expected to be thrown</typeparam>
+ /// <param name="testCode">A delegate to the code to be tested</param>
+ /// <param name="exceptionMessage">The exception message to verify</param>
+ /// <param name="allowDerivedExceptions">Pass true to allow exceptions which derive from TException; pass false, otherwise</param>
+ /// <returns>The exception that was thrown, when successful</returns>
+ /// <exception cref="ThrowsException">Thrown when an exception was not thrown, or when an exception of the incorrect type is thrown</exception>
+ public static TException Throws<TException>(Func<object> testCode, string exceptionMessage, bool allowDerivedExceptions = false)
+ where TException : Exception
+ {
+ return Throws<TException>(() => { testCode(); }, exceptionMessage, allowDerivedExceptions);
+ }
+
+ /// <summary>
+ /// Verifies that the code throws an <see cref="ArgumentException"/> (or optionally any exception which derives from it).
+ /// </summary>
+ /// <param name="testCode">A delegate to the code to be tested</param>
+ /// <param name="paramName">The name of the parameter that should throw the exception</param>
+ /// <param name="allowDerivedExceptions">Pass true to allow exceptions which derive from TException; pass false, otherwise</param>
+ /// <returns>The exception that was thrown, when successful</returns>
+ /// <exception cref="ThrowsException">Thrown when an exception was not thrown, or when an exception of the incorrect type is thrown</exception>
+ public static ArgumentException ThrowsArgument(Action testCode, string paramName, bool allowDerivedExceptions = false)
+ {
+ var ex = Throws<ArgumentException>(testCode, allowDerivedExceptions);
+
+ if (paramName != null)
+ {
+ Equal(paramName, ex.ParamName);
+ }
+
+ return ex;
+ }
+
+ /// <summary>
+ /// Verifies that the code throws an <see cref="ArgumentException"/> (or optionally any exception which derives from it).
+ /// </summary>
+ /// <param name="testCode">A delegate to the code to be tested</param>
+ /// <param name="paramName">The name of the parameter that should throw the exception</param>
+ /// <param name="exceptionMessage">The exception message to verify</param>
+ /// <param name="allowDerivedExceptions">Pass true to allow exceptions which derive from TException; pass false, otherwise</param>
+ /// <returns>The exception that was thrown, when successful</returns>
+ /// <exception cref="ThrowsException">Thrown when an exception was not thrown, or when an exception of the incorrect type is thrown</exception>
+ public static ArgumentException ThrowsArgument(Action testCode, string paramName, string exceptionMessage, bool allowDerivedExceptions = false)
+ {
+ var ex = Throws<ArgumentException>(testCode, allowDerivedExceptions);
+
+ if (paramName != null)
+ {
+ Equal(paramName, ex.ParamName);
+ }
+
+ VerifyExceptionMessage(ex, exceptionMessage, partialMatch: true);
+
+ return ex;
+ }
+
+ /// <summary>
+ /// Verifies that the code throws an ArgumentException (or optionally any exception which derives from it).
+ /// </summary>
+ /// <param name="testCode">A delegate to the code to be tested</param>
+ /// <param name="paramName">The name of the parameter that should throw the exception</param>
+ /// <param name="allowDerivedExceptions">Pass true to allow exceptions which derive from TException; pass false, otherwise</param>
+ /// <returns>The exception that was thrown, when successful</returns>
+ /// <exception cref="ThrowsException">Thrown when an exception was not thrown, or when an exception of the incorrect type is thrown</exception>
+ public static ArgumentException ThrowsArgument(Func<object> testCode, string paramName, bool allowDerivedExceptions = false)
+ {
+ var ex = Throws<ArgumentException>(testCode, allowDerivedExceptions);
+
+ if (paramName != null)
+ {
+ Equal(paramName, ex.ParamName);
+ }
+
+ return ex;
+ }
+
+ /// <summary>
+ /// Verifies that the code throws an ArgumentNullException (or optionally any exception which derives from it).
+ /// </summary>
+ /// <param name="testCode">A delegate to the code to be tested</param>
+ /// <param name="paramName">The name of the parameter that should throw the exception</param>
+ /// <returns>The exception that was thrown, when successful</returns>
+ /// <exception cref="ThrowsException">Thrown when an exception was not thrown, or when an exception of the incorrect type is thrown</exception>
+ public static ArgumentNullException ThrowsArgumentNull(Action testCode, string paramName)
+ {
+ var ex = Throws<ArgumentNullException>(testCode, allowDerivedExceptions: false);
+
+ if (paramName != null)
+ {
+ Equal(paramName, ex.ParamName);
+ }
+
+ return ex;
+ }
+
+ /// <summary>
+ /// Verifies that the code throws an ArgumentNullException with the expected message that indicates that the value cannot
+ /// be null or empty.
+ /// </summary>
+ /// <param name="testCode">A delegate to the code to be tested</param>
+ /// <param name="paramName">The name of the parameter that should throw the exception</param>
+ /// <returns>The exception that was thrown, when successful</returns>
+ /// <exception cref="ThrowsException">Thrown when an exception was not thrown, or when an exception of the incorrect type is thrown</exception>
+ public static ArgumentException ThrowsArgumentNullOrEmpty(Action testCode, string paramName)
+ {
+ return Throws<ArgumentException>(testCode, "Value cannot be null or empty.\r\nParameter name: " + paramName, allowDerivedExceptions: false);
+ }
+
+ /// <summary>
+ /// Verifies that the code throws an ArgumentNullException with the expected message that indicates that the value cannot
+ /// be null or empty string.
+ /// </summary>
+ /// <param name="testCode">A delegate to the code to be tested</param>
+ /// <param name="paramName">The name of the parameter that should throw the exception</param>
+ /// <returns>The exception that was thrown, when successful</returns>
+ /// <exception cref="ThrowsException">Thrown when an exception was not thrown, or when an exception of the incorrect type is thrown</exception>
+ public static ArgumentException ThrowsArgumentNullOrEmptyString(Action testCode, string paramName)
+ {
+ return ThrowsArgument(testCode, paramName, "Value cannot be null or an empty string.", allowDerivedExceptions: true);
+ }
+
+ /// <summary>
+ /// Verifies that the code throws an ArgumentOutOfRangeException (or optionally any exception which derives from it).
+ /// </summary>
+ /// <param name="testCode">A delegate to the code to be tested</param>
+ /// <param name="paramName">The name of the parameter that should throw the exception</param>
+ /// <param name="exceptionMessage">The exception message to verify</param>
+ /// <param name="allowDerivedExceptions">Pass true to allow exceptions which derive from TException; pass false, otherwise</param>
+ /// <returns>The exception that was thrown, when successful</returns>
+ /// <exception cref="ThrowsException">Thrown when an exception was not thrown, or when an exception of the incorrect type is thrown</exception>
+ public static ArgumentOutOfRangeException ThrowsArgumentOutOfRange(Action testCode, string paramName, string exceptionMessage, bool allowDerivedExceptions = false)
+ {
+ exceptionMessage = exceptionMessage != null
+ ? exceptionMessage + "\r\nParameter name: " + paramName
+ : exceptionMessage;
+ var ex = Throws<ArgumentOutOfRangeException>(testCode, exceptionMessage, allowDerivedExceptions);
+
+ if (paramName != null)
+ {
+ Equal(paramName, ex.ParamName);
+ }
+
+ return ex;
+ }
+
+ /// <summary>
+ /// Verifies that the code throws an <see cref="ArgumentOutOfRangeException"/> with the expected message that indicates that
+ /// the value must be greater than the given value.
+ /// </summary>
+ /// <param name="testCode">A delegate to the code to be tested</param>
+ /// <param name="paramName">The name of the parameter that should throw the exception</param>
+ /// <param name="value">The expected limit value.</param>
+ /// <returns>The exception that was thrown, when successful</returns>
+ /// <exception cref="ThrowsException">Thrown when an exception was not thrown, or when an exception of the incorrect type is thrown</exception>
+ public static ArgumentOutOfRangeException ThrowsArgumentGreaterThan(Action testCode, string paramName, string value)
+ {
+ return ThrowsArgumentOutOfRange(testCode, paramName, String.Format("Value must be greater than {0}.", value));
+ }
+
+ /// <summary>
+ /// Verifies that the code throws an <see cref="ArgumentOutOfRangeException"/> with the expected message that indicates that
+ /// the value must be greater than or equal to the given value.
+ /// </summary>
+ /// <param name="testCode">A delegate to the code to be tested</param>
+ /// <param name="paramName">The name of the parameter that should throw the exception</param>
+ /// <param name="value">The expected limit value.</param>
+ /// <returns>The exception that was thrown, when successful</returns>
+ /// <exception cref="ThrowsException">Thrown when an exception was not thrown, or when an exception of the incorrect type is thrown</exception>
+ public static ArgumentOutOfRangeException ThrowsArgumentGreaterThanOrEqualTo(Action testCode, string paramName, string value)
+ {
+ return ThrowsArgumentOutOfRange(testCode, paramName, String.Format("Value must be greater than or equal to {0}.", value));
+ }
+
+ /// <summary>
+ /// Verifies that the code throws an <see cref="ArgumentOutOfRangeException"/> with the expected message that indicates that
+ /// the value must be less than the given value.
+ /// </summary>
+ /// <param name="testCode">A delegate to the code to be tested</param>
+ /// <param name="paramName">The name of the parameter that should throw the exception</param>
+ /// <param name="value">The expected limit value.</param>
+ /// <returns>The exception that was thrown, when successful</returns>
+ /// <exception cref="ThrowsException">Thrown when an exception was not thrown, or when an exception of the incorrect type is thrown</exception>
+ public static ArgumentOutOfRangeException ThrowsArgumentLessThan(Action testCode, string paramName, string value)
+ {
+ return ThrowsArgumentOutOfRange(testCode, paramName, String.Format("Value must be less than {0}.", value));
+ }
+
+ /// <summary>
+ /// Verifies that the code throws an <see cref="ArgumentOutOfRangeException"/> with the expected message that indicates that
+ /// the value must be less than or equal to the given value.
+ /// </summary>
+ /// <param name="testCode">A delegate to the code to be tested</param>
+ /// <param name="paramName">The name of the parameter that should throw the exception</param>
+ /// <param name="value">The expected limit value.</param>
+ /// <returns>The exception that was thrown, when successful</returns>
+ /// <exception cref="ThrowsException">Thrown when an exception was not thrown, or when an exception of the incorrect type is thrown</exception>
+ public static ArgumentOutOfRangeException ThrowsArgumentLessThanOrEqualTo(Action testCode, string paramName, string value)
+ {
+ return ThrowsArgumentOutOfRange(testCode, paramName, String.Format("Value must be less than or equal to {0}.", value));
+ }
+
+ /// <summary>
+ /// Verifies that the code throws an HttpException (or optionally any exception which derives from it).
+ /// </summary>
+ /// <param name="testCode">A delegate to the code to be tested</param>
+ /// <param name="exceptionMessage">The exception message to verify</param>
+ /// <param name="httpCode">The expected HTTP status code of the exception</param>
+ /// <param name="allowDerivedExceptions">Pass true to allow exceptions which derive from TException; pass false, otherwise</param>
+ /// <returns>The exception that was thrown, when successful</returns>
+ /// <exception cref="ThrowsException">Thrown when an exception was not thrown, or when an exception of the incorrect type is thrown</exception>
+ public static HttpException ThrowsHttpException(Action testCode, string exceptionMessage, int httpCode, bool allowDerivedExceptions = false)
+ {
+ var ex = Throws<HttpException>(testCode, exceptionMessage, allowDerivedExceptions);
+ Equal(httpCode, ex.GetHttpCode());
+ return ex;
+ }
+
+ /// <summary>
+ /// Verifies that the code throws an InvalidEnumArgumentException (or optionally any exception which derives from it).
+ /// </summary>
+ /// <param name="testCode">A delegate to the code to be tested</param>
+ /// <param name="paramName">The name of the parameter that should throw the exception</param>
+ /// <param name="invalidValue">The expected invalid value that should appear in the message</param>
+ /// <param name="enumType">The type of the enumeration</param>
+ /// <param name="allowDerivedExceptions">Pass true to allow exceptions which derive from TException; pass false, otherwise</param>
+ /// <returns>The exception that was thrown, when successful</returns>
+ /// <exception cref="ThrowsException">Thrown when an exception was not thrown, or when an exception of the incorrect type is thrown</exception>
+ public static InvalidEnumArgumentException ThrowsInvalidEnumArgument(Action testCode, string paramName, int invalidValue, Type enumType, bool allowDerivedExceptions = false)
+ {
+ return Throws<InvalidEnumArgumentException>(
+ testCode,
+ String.Format("The value of argument '{0}' ({1}) is invalid for Enum type '{2}'.{3}Parameter name: {0}", paramName, invalidValue, enumType.Name, Environment.NewLine),
+ allowDerivedExceptions
+ );
+ }
+
+ /// <summary>
+ /// Verifies that the code throws an HttpException (or optionally any exception which derives from it).
+ /// </summary>
+ /// <param name="testCode">A delegate to the code to be tested</param>
+ /// <param name="objectName">The name of the object that was dispose</param>
+ /// <param name="allowDerivedExceptions">Pass true to allow exceptions which derive from TException; pass false, otherwise</param>
+ /// <returns>The exception that was thrown, when successful</returns>
+ /// <exception cref="ThrowsException">Thrown when an exception was not thrown, or when an exception of the incorrect type is thrown</exception>
+ public static ObjectDisposedException ThrowsObjectDisposed(Action testCode, string objectName, bool allowDerivedExceptions = false)
+ {
+ var ex = Throws<ObjectDisposedException>(testCode, allowDerivedExceptions);
+
+ if (objectName != null)
+ {
+ Equal(objectName, ex.ObjectName);
+ }
+
+ return ex;
+ }
+
+ private static Exception UnwrapException(Exception exception)
+ {
+ AggregateException aggEx;
+ while ((aggEx = exception as AggregateException) != null)
+ exception = aggEx.GetBaseException();
+
+ return exception;
+ }
+
+ private static void VerifyExceptionMessage(Exception exception, string expectedMessage, bool partialMatch = false)
+ {
+ if (expectedMessage != null && CurrentCultureIsEnglish)
+ {
+ if (!partialMatch)
+ {
+ Equal(expectedMessage, exception.Message);
+ }
+ else
+ {
+ Contains(expectedMessage, exception.Message);
+ }
+ }
+ }
+
+ // Custom ThrowsException so we can filter the stack trace.
+ private class ThrowsException : Xunit.Sdk.ThrowsException
+ {
+ public ThrowsException(Type type) : base(type) { }
+
+ public ThrowsException(Type type, Exception ex) : base(type, ex) { }
+
+ protected override bool ExcludeStackFrame(string stackFrame)
+ {
+ if (stackFrame.StartsWith("at Microsoft.TestCommon.AssertEx.", StringComparison.OrdinalIgnoreCase))
+ {
+ return true;
+ }
+
+ return base.ExcludeStackFrame(stackFrame);
+ }
+ }
+ }
+}
diff --git a/test/Microsoft.TestCommon/MemberHelper.cs b/test/Microsoft.TestCommon/MemberHelper.cs
new file mode 100644
index 00000000..c09c4278
--- /dev/null
+++ b/test/Microsoft.TestCommon/MemberHelper.cs
@@ -0,0 +1,381 @@
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Reflection;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.TestUtil
+{
+ public static class MemberHelper
+ {
+ private static ConstructorInfo GetConstructorInfo(object instance, Type[] parameterTypes)
+ {
+ if (instance == null)
+ {
+ throw new ArgumentNullException("instance");
+ }
+ ConstructorInfo constructorInfo = instance.GetType().GetConstructor(parameterTypes);
+ if (constructorInfo == null)
+ {
+ throw new ArgumentException(String.Format(
+ "A matching constructor on type '{0}' could not be found.",
+ instance.GetType().FullName));
+ }
+ return constructorInfo;
+ }
+
+ private static EventInfo GetEventInfo(object instance, string eventName)
+ {
+ if (instance == null)
+ {
+ throw new ArgumentNullException("instance");
+ }
+ if (String.IsNullOrEmpty(eventName))
+ {
+ throw new ArgumentException("An event must be specified.", "eventName");
+ }
+ EventInfo eventInfo = instance.GetType().GetEvent(eventName);
+ if (eventInfo == null)
+ {
+ throw new ArgumentException(String.Format(
+ "An event named '{0}' on type '{1}' could not be found.",
+ eventName, instance.GetType().FullName));
+ }
+ return eventInfo;
+ }
+
+ private static MethodInfo GetMethodInfo(object instance, string methodName, Type[] types = null, MethodAttributes attrs = MethodAttributes.Public)
+ {
+ if (instance == null)
+ {
+ throw new ArgumentNullException("instance");
+ }
+ if (String.IsNullOrEmpty(methodName))
+ {
+ throw new ArgumentException("A method must be specified.", "methodName");
+ }
+
+ MethodInfo methodInfo;
+ if (types != null)
+ {
+ methodInfo = instance.GetType().GetMethod(methodName,
+ BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic,
+ null,
+ types,
+ null);
+ }
+ else
+ {
+ methodInfo = instance.GetType().GetMethod(methodName,
+ BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
+ }
+
+ if (methodInfo == null)
+ {
+ throw new ArgumentException(String.Format(
+ "A method named '{0}' on type '{1}' could not be found.",
+ methodName, instance.GetType().FullName));
+ }
+
+ if ((methodInfo.Attributes & attrs) != attrs)
+ {
+ throw new ArgumentException(String.Format(
+ "Method '{0}' on type '{1}' with attributes '{2}' does not match the attributes '{3}'.",
+ methodName, instance.GetType().FullName, methodInfo.Attributes, attrs));
+ }
+
+ return methodInfo;
+ }
+
+ private static PropertyInfo GetPropertyInfo(object instance, string propertyName)
+ {
+ if (instance == null)
+ {
+ throw new ArgumentNullException("instance");
+ }
+ if (String.IsNullOrEmpty(propertyName))
+ {
+ throw new ArgumentException("A property must be specified.", "propertyName");
+ }
+ PropertyInfo propInfo = instance.GetType().GetProperty(propertyName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
+ if (propInfo == null)
+ {
+ throw new ArgumentException(String.Format(
+ "A property named '{0}' on type '{1}' could not be found.",
+ propertyName, instance.GetType().FullName));
+ }
+ return propInfo;
+ }
+
+ private static void TestAttribute<TAttribute>(MemberInfo memberInfo, TAttribute attributeValue)
+ where TAttribute : Attribute
+ {
+ object[] attrs = memberInfo.GetCustomAttributes(typeof(TAttribute), true);
+
+ if (attributeValue == null)
+ {
+ Assert.True(attrs.Length == 0, "Member should not have an attribute of type " + typeof(TAttribute));
+ }
+ else
+ {
+ Assert.True(attrs != null && attrs.Length > 0,
+ "Member does not have an attribute of type " + typeof(TAttribute));
+ Assert.Equal(attributeValue, attrs[0]);
+ }
+ }
+
+ public static void TestBooleanProperty(object instance, string propertyName, bool initialValue, bool testDefaultValue)
+ {
+ // Assert initial value
+ TestGetPropertyValue(instance, propertyName, initialValue);
+
+ if (testDefaultValue)
+ {
+ // Assert DefaultValue attribute matches inital value
+ TestDefaultValue(instance, propertyName);
+ }
+
+ TestPropertyValue(instance, propertyName, true);
+ TestPropertyValue(instance, propertyName, false);
+ }
+
+ public static void TestDefaultValue(object instance, string propertyName)
+ {
+ PropertyInfo propInfo = GetPropertyInfo(instance, propertyName);
+
+ object initialValue = propInfo.GetValue(instance, null);
+ TestAttribute(propInfo, new DefaultValueAttribute(initialValue));
+ }
+
+ public static void TestEvent<TEventArgs>(object instance, string eventName, TEventArgs eventArgs) where TEventArgs : EventArgs
+ {
+ EventInfo eventInfo = GetEventInfo(instance, eventName);
+
+ // Assert category "Action"
+ TestAttribute(eventInfo, new CategoryAttribute("Action"));
+
+ // Call protected method with no event handlers, assert no error
+ MethodInfo methodInfo = GetMethodInfo(instance, "On" + eventName, attrs: MethodAttributes.Family | MethodAttributes.Virtual);
+ methodInfo.Invoke(instance, new object[] { eventArgs });
+
+ // Attach handler, call method, assert fires once
+ List<object> eventHandlerArgs = new List<object>();
+ EventHandler<TEventArgs> handler = new EventHandler<TEventArgs>(delegate(object sender, TEventArgs t)
+ {
+ eventHandlerArgs.Add(sender);
+ eventHandlerArgs.Add(t);
+ });
+ eventInfo.AddEventHandler(instance, handler);
+ methodInfo.Invoke(instance, new object[] { eventArgs });
+ Assert.Equal(new[] { instance, eventArgs }, eventHandlerArgs.ToArray());
+
+ // Detach handler, call method, assert not fired
+ eventHandlerArgs = new List<object>();
+ eventInfo.RemoveEventHandler(instance, handler);
+ methodInfo.Invoke(instance, new object[] { eventArgs });
+ Assert.Empty(eventHandlerArgs);
+ }
+
+ public static void TestGetPropertyValue(object instance, string propertyName, object valueToCheck)
+ {
+ PropertyInfo propInfo = GetPropertyInfo(instance, propertyName);
+ object value = propInfo.GetValue(instance, null);
+ Assert.Equal(valueToCheck, value);
+ }
+
+ public static void TestEnumProperty<TEnumValue>(object instance, string propertyName, TEnumValue initialValue, bool testDefaultValue)
+ {
+ // Assert initial value
+ TestGetPropertyValue(instance, propertyName, initialValue);
+
+ if (testDefaultValue)
+ {
+ // Assert DefaultValue attribute matches inital value
+ TestDefaultValue(instance, propertyName);
+ }
+
+ PropertyInfo propInfo = GetPropertyInfo(instance, propertyName);
+
+ // Values are sorted numerically
+ TEnumValue[] values = (TEnumValue[])Enum.GetValues(propInfo.PropertyType);
+
+ // Assert get/set works for all valid enum values
+ foreach (TEnumValue value in values)
+ {
+ TestPropertyValue(instance, propertyName, value);
+ }
+
+ // Assert ArgumentOutOfRangeException is thrown for value one less than smallest
+ // enum value, and one more than largest enum value
+ var targetException = Assert.Throws<TargetInvocationException>(() => propInfo.SetValue(instance, Convert.ToInt32(values[0]) - 1, null));
+ Assert.IsType<ArgumentOutOfRangeException>(targetException.InnerException);
+
+ targetException = Assert.Throws<TargetInvocationException>(() => propInfo.SetValue(instance, Convert.ToInt32(values[values.Length - 1]) + 1, null));
+ Assert.IsType<ArgumentOutOfRangeException>(targetException.InnerException);
+ }
+
+ public static void TestInt32Property(object instance, string propertyName, int value1, int value2)
+ {
+ TestPropertyValue(instance, propertyName, value1);
+ TestPropertyValue(instance, propertyName, value2);
+ }
+
+ public static void TestPropertyWithDefaultInstance(object instance, string propertyName, object valueToSet)
+ {
+ PropertyInfo propInfo = GetPropertyInfo(instance, propertyName);
+
+ // Set to explicit property
+ propInfo.SetValue(instance, valueToSet, null);
+ object value = propInfo.GetValue(instance, null);
+ Assert.Equal(valueToSet, value);
+
+ // Set to null
+ propInfo.SetValue(instance, null, null);
+ value = propInfo.GetValue(instance, null);
+ Assert.IsAssignableFrom(propInfo.PropertyType, value);
+ }
+
+ public static void TestPropertyWithDefaultInstance(object instance, string propertyName, object valueToSet, object defaultValue)
+ {
+ PropertyInfo propInfo = GetPropertyInfo(instance, propertyName);
+
+ // Set to explicit property
+ propInfo.SetValue(instance, valueToSet, null);
+ object value = propInfo.GetValue(instance, null);
+ Assert.Same(valueToSet, value);
+
+ // Set to null
+ propInfo.SetValue(instance, null, null);
+ value = propInfo.GetValue(instance, null);
+ Assert.Equal(defaultValue, value);
+ }
+
+ public static void TestPropertyValue(object instance, string propertyName, object value)
+ {
+ TestPropertyValue(instance, propertyName, value, value);
+ }
+
+ public static void TestPropertyValue(object instance, string propertyName, object valueToSet, object valueToCheck)
+ {
+ PropertyInfo propInfo = GetPropertyInfo(instance, propertyName);
+ propInfo.SetValue(instance, valueToSet, null);
+ object value = propInfo.GetValue(instance, null);
+ Assert.Equal(valueToCheck, value);
+ }
+
+ public static void TestStringParams(object instance, Type[] constructorParameterTypes, object[] parameters)
+ {
+ ConstructorInfo ctor = GetConstructorInfo(instance, constructorParameterTypes);
+ TestStringParams(ctor, instance, parameters);
+ }
+
+ public static void TestStringParams(object instance, string methodName, object[] parameters)
+ {
+ TestStringParams(instance, methodName, null, parameters);
+ }
+
+ public static void TestStringParams(object instance, string methodName, Type[] types, object[] parameters)
+ {
+ MethodInfo method = GetMethodInfo(instance, methodName, types);
+ TestStringParams(method, instance, parameters);
+ }
+
+ private static void TestStringParams(MethodBase method, object instance, object[] parameters)
+ {
+ ParameterInfo[] parameterInfos = method.GetParameters();
+ foreach (ParameterInfo parameterInfo in parameterInfos)
+ {
+ if (parameterInfo.ParameterType == typeof(String))
+ {
+ object originalParameter = parameters[parameterInfo.Position];
+
+ parameters[parameterInfo.Position] = null;
+ Assert.ThrowsArgumentNullOrEmpty(
+ delegate()
+ {
+ try
+ {
+ method.Invoke(instance, parameters);
+ }
+ catch (TargetInvocationException e)
+ {
+ throw e.InnerException;
+ }
+ },
+ parameterInfo.Name);
+
+ parameters[parameterInfo.Position] = String.Empty;
+ Assert.ThrowsArgumentNullOrEmpty(
+ delegate()
+ {
+ try
+ {
+ method.Invoke(instance, parameters);
+ }
+ catch (TargetInvocationException e)
+ {
+ throw e.InnerException;
+ }
+ },
+ parameterInfo.Name);
+
+ parameters[parameterInfo.Position] = originalParameter;
+ }
+ }
+ }
+
+ public static void TestStringProperty(object instance, string propertyName, string initialValue,
+ bool testDefaultValueAttribute = false, bool allowNullAndEmpty = true,
+ string nullAndEmptyReturnValue = "")
+ {
+ // Assert initial value
+ TestGetPropertyValue(instance, propertyName, initialValue);
+
+ if (testDefaultValueAttribute)
+ {
+ // Assert DefaultValue attribute matches inital value
+ TestDefaultValue(instance, propertyName);
+ }
+
+ if (allowNullAndEmpty)
+ {
+ // Assert get/set works for null
+ TestPropertyValue(instance, propertyName, null, nullAndEmptyReturnValue);
+
+ // Assert get/set works for String.Empty
+ TestPropertyValue(instance, propertyName, String.Empty, nullAndEmptyReturnValue);
+ }
+ else
+ {
+ Assert.ThrowsArgumentNullOrEmpty(
+ delegate()
+ {
+ try
+ {
+ TestPropertyValue(instance, propertyName, null);
+ }
+ catch (TargetInvocationException e)
+ {
+ throw e.InnerException;
+ }
+ },
+ "value");
+ Assert.ThrowsArgumentNullOrEmpty(
+ delegate()
+ {
+ try
+ {
+ TestPropertyValue(instance, propertyName, String.Empty);
+ }
+ catch (TargetInvocationException e)
+ {
+ throw e.InnerException;
+ }
+ },
+ "value");
+ }
+
+ // Assert get/set works for arbitrary value
+ TestPropertyValue(instance, propertyName, "TestValue");
+ }
+ }
+}
diff --git a/test/Microsoft.TestCommon/Microsoft.TestCommon.csproj b/test/Microsoft.TestCommon/Microsoft.TestCommon.csproj
new file mode 100644
index 00000000..41cc12c7
--- /dev/null
+++ b/test/Microsoft.TestCommon/Microsoft.TestCommon.csproj
@@ -0,0 +1,116 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory),Runtime.sln))\tools\WebStack.settings.targets" />
+ <PropertyGroup>
+ <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+ <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+ <ProductVersion>9.0.30729</ProductVersion>
+ <SchemaVersion>2.0</SchemaVersion>
+ <ProjectGuid>{FCCC4CB7-BAF7-4A57-9F89-E5766FE536C0}</ProjectGuid>
+ <OutputType>Library</OutputType>
+ <AppDesignerFolder>Properties</AppDesignerFolder>
+ <RootNamespace>Microsoft.TestCommon</RootNamespace>
+ <AssemblyName>Microsoft.TestCommon</AssemblyName>
+ <FileAlignment>512</FileAlignment>
+ <ProjectTypeGuids>{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
+ <ErrorReport>prompt</ErrorReport>
+ <WarningLevel>4</WarningLevel>
+ <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
+ <DebugSymbols>true</DebugSymbols>
+ <DebugType>full</DebugType>
+ <Optimize>false</Optimize>
+ <OutputPath>$(WebStackRootPath)\bin\Debug\Test\</OutputPath>
+ <DefineConstants>TRACE;DEBUG</DefineConstants>
+ <CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
+ <DebugType>pdbonly</DebugType>
+ <Optimize>true</Optimize>
+ <OutputPath>$(WebStackRootPath)\bin\Release\Test\</OutputPath>
+ <DefineConstants>TRACE</DefineConstants>
+ <CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)' == 'CodeCoverage' ">
+ <DebugSymbols>true</DebugSymbols>
+ <DebugType>full</DebugType>
+ <Optimize>false</Optimize>
+ <OutputPath>$(WebStackRootPath)\bin\CodeCoverage\Test\</OutputPath>
+ <DefineConstants>TRACE;DEBUG</DefineConstants>
+ <CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
+ </PropertyGroup>
+ <ItemGroup>
+ <Reference Include="System" />
+ <Reference Include="System.Core" />
+ <Reference Include="System.Net.Http">
+ <HintPath>..\..\packages\Microsoft.Net.Http.2.0.20302.1\lib\net40\System.Net.Http.dll</HintPath>
+ </Reference>
+ <Reference Include="System.Net.Http.WebRequest">
+ <HintPath>..\..\packages\Microsoft.Net.Http.2.0.20302.1\lib\net40\System.Net.Http.WebRequest.dll</HintPath>
+ </Reference>
+ <Reference Include="System.Runtime.Serialization" />
+ <Reference Include="System.Web" />
+ <Reference Include="System.Xml" />
+ <Reference Include="System.Xml.Linq" />
+ <Reference Include="xunit">
+ <HintPath>..\..\packages\xunit.1.9.0.1566\lib\xunit.dll</HintPath>
+ </Reference>
+ <Reference Include="xunit.extensions">
+ <HintPath>..\..\packages\xunit.extensions.1.9.0.1566\lib\xunit.extensions.dll</HintPath>
+ </Reference>
+ <Reference Include="Microsoft.VisualStudio.QualityTools.UnitTestFramework, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL" />
+ </ItemGroup>
+ <ItemGroup>
+ <Compile Include="AppDomainUtils.cs" />
+ <Compile Include="AssertEx.cs">
+ <SubType>Code</SubType>
+ </Compile>
+ <Compile Include="CultureReplacer.cs" />
+ <Compile Include="DefaultTimeoutFactAttribute.cs" />
+ <Compile Include="DefaultTimeoutTheoryAttribute.cs" />
+ <Compile Include="DictionaryEqualityComparer.cs" />
+ <Compile Include="ExceptionAssertions.cs">
+ <SubType>Code</SubType>
+ </Compile>
+ <Compile Include="MemberHelper.cs" />
+ <Compile Include="Microsoft\TestCommon\DataSets\RefTypeTestData.cs" />
+ <Compile Include="Microsoft\TestCommon\DataSets\TestDataVariations.cs" />
+ <Compile Include="Microsoft\TestCommon\DataSets\ValueTypeTestData.cs" />
+ <Compile Include="Microsoft\TestCommon\GenericTypeAssert.cs" />
+ <Compile Include="Microsoft\TestCommon\HttpAssert.cs" />
+ <Compile Include="Microsoft\TestCommon\MediaTypeAssert.cs" />
+ <Compile Include="Microsoft\TestCommon\MediaTypeHeaderValueComparer.cs" />
+ <Compile Include="Microsoft\TestCommon\ParsedMediaTypeHeaderValue.cs" />
+ <Compile Include="Microsoft\TestCommon\RegexReplacement.cs" />
+ <Compile Include="Microsoft\TestCommon\RuntimeEnvironment.cs" />
+ <Compile Include="Microsoft\TestCommon\SerializerAssert.cs" />
+ <Compile Include="Microsoft\TestCommon\StreamAssert.cs" />
+ <Compile Include="Microsoft\TestCommon\TaskAssert.cs" />
+ <Compile Include="Microsoft\TestCommon\DataSets\TestData.cs" />
+ <Compile Include="Microsoft\TestCommon\TestDataSetAttribute.cs" />
+ <Compile Include="Microsoft\TestCommon\TimeoutConstant.cs" />
+ <Compile Include="Microsoft\TestCommon\TypeAssert.cs" />
+ <Compile Include="Microsoft\TestCommon\Types\FlagsEnum.cs" />
+ <Compile Include="Microsoft\TestCommon\Types\INameAndIdContainer.cs" />
+ <Compile Include="Microsoft\TestCommon\Types\ISerializableType.cs" />
+ <Compile Include="Microsoft\TestCommon\Types\LongEnum.cs" />
+ <Compile Include="Microsoft\TestCommon\Types\SimpleEnum.cs" />
+ <Compile Include="Microsoft\TestCommon\DataSets\CommonUnitTestDataSets.cs" />
+ <Compile Include="Microsoft\TestCommon\XmlAssert.cs" />
+ <Compile Include="PreAppStartTestHelper.cs" />
+ <Compile Include="PreserveSyncContextAttribute.cs" />
+ <Compile Include="Properties\AssemblyInfo.cs" />
+ <Compile Include="ReflectionAssert.cs" />
+ <Compile Include="TaskExtensions.cs" />
+ <Compile Include="TestFile.cs" />
+ <Compile Include="TestHelper.cs" />
+ <Compile Include="TheoryDataSet.cs" />
+ <Compile Include="ThreadPoolSyncContext.cs" />
+ <Compile Include="WebUtils.cs" />
+ </ItemGroup>
+ <ItemGroup>
+ <None Include="packages.config" />
+ </ItemGroup>
+ <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
+</Project> \ No newline at end of file
diff --git a/test/Microsoft.TestCommon/Microsoft/TestCommon/DataSets/CommonUnitTestDataSets.cs b/test/Microsoft.TestCommon/Microsoft/TestCommon/DataSets/CommonUnitTestDataSets.cs
new file mode 100644
index 00000000..e3c19e43
--- /dev/null
+++ b/test/Microsoft.TestCommon/Microsoft/TestCommon/DataSets/CommonUnitTestDataSets.cs
@@ -0,0 +1,38 @@
+using System;
+using System.Collections.ObjectModel;
+using Microsoft.TestCommon.Types;
+
+namespace Microsoft.TestCommon
+{
+ public class CommonUnitTestDataSets
+ {
+ public static ValueTypeTestData<char> Chars { get { return TestData.CharTestData; } }
+ public static ValueTypeTestData<int> Ints { get { return TestData.IntTestData; } }
+ public static ValueTypeTestData<uint> Uints { get { return TestData.UintTestData; } }
+ public static ValueTypeTestData<short> Shorts { get { return TestData.ShortTestData; } }
+ public static ValueTypeTestData<ushort> Ushorts { get { return TestData.UshortTestData; } }
+ public static ValueTypeTestData<long> Longs { get { return TestData.LongTestData; } }
+ public static ValueTypeTestData<ulong> Ulongs { get { return TestData.UlongTestData; } }
+ public static ValueTypeTestData<byte> Bytes { get { return TestData.ByteTestData; } }
+ public static ValueTypeTestData<sbyte> SBytes { get { return TestData.SByteTestData; } }
+ public static ValueTypeTestData<bool> Bools { get { return TestData.BoolTestData; } }
+ public static ValueTypeTestData<double> Doubles { get { return TestData.DoubleTestData; } }
+ public static ValueTypeTestData<float> Floats { get { return TestData.FloatTestData; } }
+ public static ValueTypeTestData<DateTime> DateTimes { get { return TestData.DateTimeTestData; } }
+ public static ValueTypeTestData<Decimal> Decimals { get { return TestData.DecimalTestData; } }
+ public static ValueTypeTestData<TimeSpan> TimeSpans { get { return TestData.TimeSpanTestData; } }
+ public static ValueTypeTestData<Guid> Guids { get { return TestData.GuidTestData; } }
+ public static ValueTypeTestData<DateTimeOffset> DateTimeOffsets { get { return TestData.DateTimeOffsetTestData; } }
+ public static ValueTypeTestData<SimpleEnum> SimpleEnums { get { return TestData.SimpleEnumTestData; } }
+ public static ValueTypeTestData<LongEnum> LongEnums { get { return TestData.LongEnumTestData; } }
+ public static ValueTypeTestData<FlagsEnum> FlagsEnums { get { return TestData.FlagsEnumTestData; } }
+ public static TestData<string> EmptyStrings { get { return TestData.EmptyStrings; } }
+ public static RefTypeTestData<string> Strings { get { return TestData.StringTestData; } }
+ public static TestData<string> NonNullEmptyStrings { get { return TestData.NonNullEmptyStrings; } }
+ public static RefTypeTestData<ISerializableType> ISerializableTypes { get { return TestData.ISerializableTypeTestData; } }
+ public static ReadOnlyCollection<TestData> ValueTypeTestDataCollection { get { return TestData.ValueTypeTestDataCollection; } }
+ public static ReadOnlyCollection<TestData> RefTypeTestDataCollection { get { return TestData.RefTypeTestDataCollection; } }
+ public static ReadOnlyCollection<TestData> ValueAndRefTypeTestDataCollection { get { return TestData.ValueTypeTestDataCollection; } }
+ public static ReadOnlyCollection<TestData> RepresentativeValueAndRefTypeTestDataCollection { get { return TestData.RepresentativeValueAndRefTypeTestDataCollection; } }
+ }
+} \ No newline at end of file
diff --git a/test/Microsoft.TestCommon/Microsoft/TestCommon/DataSets/RefTypeTestData.cs b/test/Microsoft.TestCommon/Microsoft/TestCommon/DataSets/RefTypeTestData.cs
new file mode 100644
index 00000000..21a3dc6f
--- /dev/null
+++ b/test/Microsoft.TestCommon/Microsoft/TestCommon/DataSets/RefTypeTestData.cs
@@ -0,0 +1,67 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace Microsoft.TestCommon
+{
+ public class RefTypeTestData<T> : TestData<T> where T : class
+ {
+ private Func<IEnumerable<T>> testDataProvider;
+ private Func<IEnumerable<T>> derivedTypeTestDataProvider;
+ private Func<IEnumerable<T>> knownTypeTestDataProvider;
+
+ public RefTypeTestData(Func<IEnumerable<T>> testDataProvider)
+ {
+ if (testDataProvider == null)
+ {
+ throw new ArgumentNullException("testDataProvider");
+ }
+
+ this.testDataProvider = testDataProvider;
+ }
+
+ public RefTypeTestData(
+ Func<IEnumerable<T>> testDataProvider,
+ Func<IEnumerable<T>> derivedTypeTestDataProvider,
+ Func<IEnumerable<T>> knownTypeTestDataProvider)
+ : this(testDataProvider)
+ {
+ this.derivedTypeTestDataProvider = derivedTypeTestDataProvider;
+ if (this.derivedTypeTestDataProvider != null)
+ {
+ this.RegisterTestDataVariation(TestDataVariations.AsDerivedType, this.Type, this.GetTestDataAsDerivedType);
+ }
+
+ this.knownTypeTestDataProvider = knownTypeTestDataProvider;
+ if (this.knownTypeTestDataProvider != null)
+ {
+ this.RegisterTestDataVariation(TestDataVariations.AsKnownType, this.Type, this.GetTestDataAsDerivedKnownType);
+ }
+ }
+
+ public IEnumerable<T> GetTestDataAsDerivedType()
+ {
+ if (this.derivedTypeTestDataProvider != null)
+ {
+ return this.derivedTypeTestDataProvider();
+ }
+
+ return Enumerable.Empty<T>();
+ }
+
+ public IEnumerable<T> GetTestDataAsDerivedKnownType()
+ {
+ if (this.knownTypeTestDataProvider != null)
+ {
+ return this.knownTypeTestDataProvider();
+ }
+
+ return Enumerable.Empty<T>();
+ }
+
+ protected override IEnumerable<T> GetTypedTestData()
+ {
+ return this.testDataProvider();
+ }
+ }
+}
diff --git a/test/Microsoft.TestCommon/Microsoft/TestCommon/DataSets/TestData.cs b/test/Microsoft.TestCommon/Microsoft/TestCommon/DataSets/TestData.cs
new file mode 100644
index 00000000..4af24f48
--- /dev/null
+++ b/test/Microsoft.TestCommon/Microsoft/TestCommon/DataSets/TestData.cs
@@ -0,0 +1,444 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Linq;
+using Microsoft.TestCommon.Types;
+
+namespace Microsoft.TestCommon
+{
+ /// <summary>
+ /// A base class for test data. A <see cref="TestData"/> instance is associated with a given type, and the <see cref="TestData"/> instance can
+ /// provide instances of the given type to use as data in tests. The same <see cref="TestData"/> instance can also provide instances
+ /// of types related to the given type, such as a <see cref="List<>"/> of the type. See the <see cref="TestDataVariations"/> enum for all the
+ /// variations of test data that a <see cref="TestData"/> instance can provide.
+ /// </summary>
+ public abstract class TestData
+ {
+ /// <summary>
+ /// Common <see cref="TestData"/> for a <see cref="char"/>.
+ /// </summary>
+ public static readonly ValueTypeTestData<char> CharTestData = new ValueTypeTestData<char>('a', Char.MinValue, Char.MaxValue);
+
+ /// <summary>
+ /// Common <see cref="TestData"/> for a <see cref="int"/>.
+ /// </summary>
+ public static readonly ValueTypeTestData<int> IntTestData = new ValueTypeTestData<int>(-1, 0, 1, Int32.MinValue, Int32.MaxValue);
+
+ /// <summary>
+ /// Common <see cref="TestData"/> for a <see cref="uint"/>.
+ /// </summary>
+ public static readonly ValueTypeTestData<uint> UintTestData = new ValueTypeTestData<uint>(0, 1, UInt32.MinValue, UInt32.MaxValue);
+
+ /// <summary>
+ /// Common <see cref="TestData"/> for a <see cref="short"/>.
+ /// </summary>
+ public static readonly ValueTypeTestData<short> ShortTestData = new ValueTypeTestData<short>(-1, 0, 1, Int16.MinValue, Int16.MaxValue);
+
+ /// <summary>
+ /// Common <see cref="TestData"/> for a <see cref="ushort"/>.
+ /// </summary>
+ public static readonly ValueTypeTestData<ushort> UshortTestData = new ValueTypeTestData<ushort>(0, 1, UInt16.MinValue, UInt16.MaxValue);
+
+ /// <summary>
+ /// Common <see cref="TestData"/> for a <see cref="long"/>.
+ /// </summary>
+ public static readonly ValueTypeTestData<long> LongTestData = new ValueTypeTestData<long>(-1, 0, 1, Int64.MinValue, Int64.MaxValue);
+
+ /// <summary>
+ /// Common <see cref="TestData"/> for a <see cref="ulong"/>.
+ /// </summary>
+ public static readonly ValueTypeTestData<ulong> UlongTestData = new ValueTypeTestData<ulong>(0, 1, UInt64.MinValue, UInt64.MaxValue);
+
+ /// <summary>
+ /// Common <see cref="TestData"/> for a <see cref="byte"/>.
+ /// </summary>
+ public static readonly ValueTypeTestData<byte> ByteTestData = new ValueTypeTestData<byte>(0, 1, Byte.MinValue, Byte.MaxValue);
+
+ /// <summary>
+ /// Common <see cref="TestData"/> for a <see cref="sbyte"/>.
+ /// </summary>
+ public static readonly ValueTypeTestData<sbyte> SByteTestData = new ValueTypeTestData<sbyte>(-1, 0, 1, SByte.MinValue, SByte.MaxValue);
+
+ /// <summary>
+ /// Common <see cref="TestData"/> for a <see cref="bool"/>.
+ /// </summary>
+ public static readonly ValueTypeTestData<bool> BoolTestData = new ValueTypeTestData<bool>(true, false);
+
+ /// <summary>
+ /// Common <see cref="TestData"/> for a <see cref="double"/>.
+ /// </summary>
+ public static readonly ValueTypeTestData<double> DoubleTestData = new ValueTypeTestData<double>(
+ -1.0,
+ 0.0,
+ 1.0,
+ double.MinValue,
+ double.MaxValue,
+ double.PositiveInfinity,
+ double.NegativeInfinity);
+
+ /// <summary>
+ /// Common <see cref="TestData"/> for a <see cref="float"/>.
+ /// </summary>
+ public static readonly ValueTypeTestData<float> FloatTestData = new ValueTypeTestData<float>(
+ -1.0f,
+ 0.0f,
+ 1.0f,
+ float.MinValue,
+ float.MaxValue,
+ float.PositiveInfinity,
+ float.NegativeInfinity);
+
+ /// <summary>
+ /// Common <see cref="TestData"/> for a <see cref="decimal"/>.
+ /// </summary>
+ public static readonly ValueTypeTestData<decimal> DecimalTestData = new ValueTypeTestData<decimal>(
+ -1M,
+ 0M,
+ 1M,
+ decimal.MinValue,
+ decimal.MaxValue);
+
+ /// <summary>
+ /// Common <see cref="TestData"/> for a <see cref="DateTime"/>.
+ /// </summary>
+ public static readonly ValueTypeTestData<DateTime> DateTimeTestData = new ValueTypeTestData<DateTime>(
+ DateTime.Now,
+ DateTime.UtcNow,
+ DateTime.MaxValue,
+ DateTime.MinValue);
+
+ /// <summary>
+ /// Common <see cref="TestData"/> for a <see cref="TimeSpan"/>.
+ /// </summary>
+ public static readonly ValueTypeTestData<TimeSpan> TimeSpanTestData = new ValueTypeTestData<TimeSpan>(
+ TimeSpan.MinValue,
+ TimeSpan.MaxValue);
+
+ /// <summary>
+ /// Common <see cref="TestData"/> for a <see cref="Guid"/>.
+ /// </summary>
+ public static readonly ValueTypeTestData<Guid> GuidTestData = new ValueTypeTestData<Guid>(
+ Guid.NewGuid(),
+ Guid.Empty);
+
+ /// <summary>
+ /// Common <see cref="TestData"/> for a <see cref="DateTimeOffset"/>.
+ /// </summary>
+ public static readonly ValueTypeTestData<DateTimeOffset> DateTimeOffsetTestData = new ValueTypeTestData<DateTimeOffset>(
+ DateTimeOffset.MaxValue,
+ DateTimeOffset.MinValue,
+ new DateTimeOffset(DateTime.Now));
+
+ /// <summary>
+ /// Common <see cref="TestData"/> for an <c>enum</c>.
+ /// </summary>
+ public static readonly ValueTypeTestData<SimpleEnum> SimpleEnumTestData = new ValueTypeTestData<SimpleEnum>(
+ SimpleEnum.First,
+ SimpleEnum.Second,
+ SimpleEnum.Third);
+
+ /// <summary>
+ /// Common <see cref="TestData"/> for an <c>enum</c> implemented with a <see cref="long"/>.
+ /// </summary>
+ public static readonly ValueTypeTestData<LongEnum> LongEnumTestData = new ValueTypeTestData<LongEnum>(
+ LongEnum.FirstLong,
+ LongEnum.SecondLong,
+ LongEnum.ThirdLong);
+
+ /// <summary>
+ /// Common <see cref="TestData"/> for an <c>enum</c> decorated with a <see cref="FlagsAttribtute"/>.
+ /// </summary>
+ public static readonly ValueTypeTestData<FlagsEnum> FlagsEnumTestData = new ValueTypeTestData<FlagsEnum>(
+ FlagsEnum.One,
+ FlagsEnum.Two,
+ FlagsEnum.Four);
+
+ /// <summary>
+ /// Expected permutations of non supported file paths.
+ /// </summary>
+ public static readonly TestData<string> NotSupportedFilePaths = new RefTypeTestData<string>(() => new List<string>() {
+ "cc:\\a\\b",
+ });
+
+ /// <summary>
+ /// Expected permutations of invalid file paths.
+ /// </summary>
+ public static readonly TestData<string> InvalidNonNullFilePaths = new RefTypeTestData<string>(() => new List<string>() {
+ String.Empty,
+ "",
+ " ",
+ " ",
+ "\t\t \n ",
+ "c:\\a<b",
+ "c:\\a>b",
+ "c:\\a\"b",
+ "c:\\a\tb",
+ "c:\\a|b",
+ "c:\\a\bb",
+ "c:\\a\0b",
+ });
+
+ /// <summary>
+ /// All expected permutations of an empty string.
+ /// </summary>
+ public static readonly TestData<string> NonNullEmptyStrings = new RefTypeTestData<string>(() => new List<string>() { String.Empty, "", " ", "\t\r\n" });
+
+ /// <summary>
+ /// All expected permutations of an empty string.
+ /// </summary>
+ public static readonly TestData<string> EmptyStrings = new RefTypeTestData<string>(() => new List<string>() { null, String.Empty, "", " ", "\t\r\n" });
+
+ /// <summary>
+ /// Common <see cref="TestData"/> for a <see cref="string"/>.
+ /// </summary>
+ public static readonly RefTypeTestData<string> StringTestData = new RefTypeTestData<string>(() => new List<string>() {
+ "",
+ " ", // one space
+ " ", // multiple spaces
+ " data ", // leading and trailing whitespace
+ "\t\t \n ",
+ "Some String!"});
+
+ /// <summary>
+ /// Common <see cref="TestData"/> for a class that implements <see cref="ISerializable"/>.
+ /// </summary>
+ public static readonly RefTypeTestData<ISerializableType> ISerializableTypeTestData = new RefTypeTestData<ISerializableType>(
+ ISerializableType.GetTestData);
+
+ /// <summary>
+ /// A read-only collection of value type test data.
+ /// </summary>
+ public static readonly ReadOnlyCollection<TestData> ValueTypeTestDataCollection = new ReadOnlyCollection<TestData>(new TestData[] {
+ CharTestData,
+ IntTestData,
+ UintTestData,
+ ShortTestData,
+ UshortTestData,
+ LongTestData,
+ UlongTestData,
+ ByteTestData,
+ SByteTestData,
+ BoolTestData,
+ DoubleTestData,
+ FloatTestData,
+ DecimalTestData,
+ TimeSpanTestData,
+ GuidTestData,
+ DateTimeOffsetTestData,
+ SimpleEnumTestData,
+ LongEnumTestData,
+ FlagsEnumTestData});
+
+ /// <summary>
+ /// A read-only collection of reference type test data.
+ /// </summary>
+ public static readonly ReadOnlyCollection<TestData> RefTypeTestDataCollection = new ReadOnlyCollection<TestData>(new TestData[] {
+ StringTestData,
+ ISerializableTypeTestData});
+
+ /// <summary>
+ /// A read-only collection of value and reference type test data.
+ /// </summary>
+ public static readonly ReadOnlyCollection<TestData> ValueAndRefTypeTestDataCollection = new ReadOnlyCollection<TestData>(
+ ValueTypeTestDataCollection.Concat(RefTypeTestDataCollection).ToList());
+
+ /// <summary>
+ /// A read-only collection of representative values and reference type test data.
+ /// Uses where exhaustive coverage is not required.
+ /// </summary>
+ public static readonly ReadOnlyCollection<TestData> RepresentativeValueAndRefTypeTestDataCollection = new ReadOnlyCollection<TestData>(new TestData[] {
+ IntTestData,
+ BoolTestData,
+ SimpleEnumTestData,
+ StringTestData,
+ });
+
+ private Dictionary<TestDataVariations, TestDataVariationProvider> registeredTestDataVariations;
+
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="TestData"/> class.
+ /// </summary>
+ /// <param name="type">The type associated with the <see cref="TestData"/> instance.</param>
+ protected TestData(Type type)
+ {
+ if (type.ContainsGenericParameters)
+ {
+ throw new InvalidOperationException("Only closed generic types are supported.");
+ }
+
+ this.Type = type;
+ this.registeredTestDataVariations = new Dictionary<TestDataVariations, TestDataVariationProvider>();
+ }
+
+ /// <summary>
+ /// Gets the type associated with the <see cref="TestData"/> instance.
+ /// </summary>
+ public Type Type { get; private set; }
+
+
+ /// <summary>
+ /// Gets the supported test data variations.
+ /// </summary>
+ /// <returns></returns>
+ public IEnumerable<TestDataVariations> GetSupportedTestDataVariations()
+ {
+ return this.registeredTestDataVariations.Keys;
+ }
+
+ /// <summary>
+ /// Gets the related type for the given test data variation or returns null if the <see cref="TestData"/> instance
+ /// doesn't support the given variation.
+ /// </summary>
+ /// <param name="variation">The test data variation with which to create the related <see cref="Type"/>.</param>
+ /// <returns>The related <see cref="Type"/> for the <see cref="TestData.Type"/> as given by the test data variation.</returns>
+ /// <example>
+ /// For example, if the given <see cref="TestData"/> was created for <see cref="string"/> test data and the varation parameter
+ /// was <see cref="TestDataVariations.AsList"/> then the returned type would be <see cref="List<string>"/>.
+ /// </example>
+ public Type GetAsTypeOrNull(TestDataVariations variation)
+ {
+ TestDataVariationProvider testDataVariation = null;
+ if (this.registeredTestDataVariations.TryGetValue(variation, out testDataVariation))
+ {
+ return testDataVariation.Type;
+ }
+
+ return null;
+ }
+
+ /// <summary>
+ /// Gets test data for the given test data variation or returns null if the <see cref="TestData"/> instance
+ /// doesn't support the given variation.
+ /// </summary>
+ /// <param name="variation">The test data variation with which to create the related test data.</param>
+ /// <returns>Test data of the type specified by the <see cref="TestData.GetAsTypeOrNull"/> method.</returns>
+ public object GetAsTestDataOrNull(TestDataVariations variation)
+ {
+ TestDataVariationProvider testDataVariation = null;
+ if (this.registeredTestDataVariations.TryGetValue(variation, out testDataVariation))
+ {
+ return testDataVariation.TestDataProvider();
+ }
+
+ return null;
+ }
+
+
+ /// <summary>
+ /// Allows derived classes to register a <paramref name="testDataProvider "/> <see cref="Func<>"/> that will
+ /// provide test data for a given variation.
+ /// </summary>
+ /// <param name="variation">The variation with which to register the <paramref name="testDataProvider "/>r.</param>
+ /// <param name="type">The type of the test data created by the <paramref name="testDataProvider "/></param>
+ /// <param name="testDataProvider">A <see cref="Func<>"/> that will provide test data.</param>
+ protected void RegisterTestDataVariation(TestDataVariations variation, Type type, Func<object> testDataProvider)
+ {
+ this.registeredTestDataVariations.Add(variation, new TestDataVariationProvider(type, testDataProvider));
+ }
+
+ private class TestDataVariationProvider
+ {
+ public TestDataVariationProvider(Type type, Func<object> testDataProvider)
+ {
+ this.Type = type;
+ this.TestDataProvider = testDataProvider;
+ }
+
+
+ public Func<object> TestDataProvider { get; private set; }
+
+ public Type Type { get; private set; }
+ }
+ }
+
+
+ /// <summary>
+ /// A generic base class for test data.
+ /// </summary>
+ /// <typeparam name="T">The type associated with the test data.</typeparam>
+ public abstract class TestData<T> : TestData, IEnumerable<T>
+ {
+ private static readonly Type OpenIEnumerableType = typeof(IEnumerable<>);
+ private static readonly Type OpenListType = typeof(List<>);
+ private static readonly Type OpenIQueryableType = typeof(IQueryable<>);
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="TestData&lt;T&gt;"/> class.
+ /// </summary>
+ protected TestData()
+ : base(typeof(T))
+ {
+ Type[] typeParams = new Type[] { this.Type };
+
+ Type arrayType = this.Type.MakeArrayType();
+ Type listType = OpenListType.MakeGenericType(typeParams);
+ Type iEnumerableType = OpenIEnumerableType.MakeGenericType(typeParams);
+ Type iQueryableType = OpenIQueryableType.MakeGenericType(typeParams);
+
+ Type[] typeArrayParams = new Type[] { arrayType };
+ Type[] typeListParams = new Type[] { listType };
+ Type[] typeIEnumerableParams = new Type[] { iEnumerableType };
+ Type[] typeIQueryableParams = new Type[] { iQueryableType };
+
+ this.RegisterTestDataVariation(TestDataVariations.AsInstance, this.Type, () => GetTypedTestData());
+ this.RegisterTestDataVariation(TestDataVariations.AsArray, arrayType, GetTestDataAsArray);
+ this.RegisterTestDataVariation(TestDataVariations.AsIEnumerable, iEnumerableType, GetTestDataAsIEnumerable);
+ this.RegisterTestDataVariation(TestDataVariations.AsIQueryable, iQueryableType, GetTestDataAsIQueryable);
+ this.RegisterTestDataVariation(TestDataVariations.AsList, listType, GetTestDataAsList);
+ }
+
+ public IEnumerator<T> GetEnumerator()
+ {
+ return (IEnumerator<T>)this.GetTypedTestData().ToList().GetEnumerator();
+ }
+
+ IEnumerator IEnumerable.GetEnumerator()
+ {
+ return (IEnumerator)this.GetTypedTestData().ToList().GetEnumerator();
+ }
+
+ /// <summary>
+ /// Gets the test data as an array.
+ /// </summary>
+ /// <returns>An array of test data of the given type.</returns>
+ public T[] GetTestDataAsArray()
+ {
+ return this.GetTypedTestData().ToArray();
+ }
+
+ /// <summary>
+ /// Gets the test data as a <see cref="List<>"/>.
+ /// </summary>
+ /// <returns>A <see cref="List<>"/> of test data of the given type.</returns>
+ public List<T> GetTestDataAsList()
+ {
+ return this.GetTypedTestData().ToList();
+ }
+
+ /// <summary>
+ /// Gets the test data as an <see cref="IEnumerable<>"/>.
+ /// </summary>
+ /// <returns>An <see cref="IEnumerable<>"/> of test data of the given type.</returns>
+ public IEnumerable<T> GetTestDataAsIEnumerable()
+ {
+ return this.GetTypedTestData().AsEnumerable();
+ }
+
+ /// <summary>
+ /// Gets the test data as an <see cref="IQueryable<>"/>.
+ /// </summary>
+ /// <returns>An <see cref="IQueryable<>"/> of test data of the given type.</returns>
+ public IQueryable<T> GetTestDataAsIQueryable()
+ {
+ return this.GetTypedTestData().AsQueryable();
+ }
+
+ /// <summary>
+ /// Must be implemented by derived types to return test data of the given type.
+ /// </summary>
+ /// <returns>Test data of the given type.</returns>
+ protected abstract IEnumerable<T> GetTypedTestData();
+ }
+}
diff --git a/test/Microsoft.TestCommon/Microsoft/TestCommon/DataSets/TestDataVariations.cs b/test/Microsoft.TestCommon/Microsoft/TestCommon/DataSets/TestDataVariations.cs
new file mode 100644
index 00000000..9bd61c2e
--- /dev/null
+++ b/test/Microsoft.TestCommon/Microsoft/TestCommon/DataSets/TestDataVariations.cs
@@ -0,0 +1,95 @@
+using System;
+
+namespace Microsoft.TestCommon
+{
+ /// <summary>
+ /// An flags enum that can be used to indicate different variations of a given
+ /// <see cref="TestData"/> instance.
+ /// </summary>
+ [Flags]
+ public enum TestDataVariations
+ {
+ /// <summary>
+ /// An individual instance of a given <see cref="TestData"/> type.
+ /// </summary>
+ AsInstance = 0x1,
+
+ /// <summary>
+ /// An individual instance of a type that derives from a given <see cref="TestData"/> type.
+ /// </summary>
+ AsDerivedType = 0x2,
+
+ /// <summary>
+ /// An individual instance of a given <see cref="TestData"/> type that has a property value
+ /// that is a known type of the declared property type.
+ /// </summary>
+ AsKnownType = 0x4,
+
+ /// <summary>
+ /// A <see cref="Nullable<>"/> instance of a given <see cref="TestData"/> type. Only applies to
+ /// instances of <see cref="ValueTypeTestData"/>.
+ /// </summary>
+ AsNullable = 0x8,
+
+ /// <summary>
+ /// An instance of a <see cref="System.Collections.Generic.List<>"/> of a given <see cref="TestData"/> type.
+ /// </summary>
+ AsList = 0x10,
+
+ /// <summary>
+ /// An instance of a array of the <see cref="TestData"/> type.
+ /// </summary>
+ AsArray = 0x20,
+
+ /// <summary>
+ /// An instance of an <see cref="System.Collections.Generic.IEnumerable<>"/> of a given <see cref="TestData"/> type.
+ /// </summary>
+ AsIEnumerable = 0x40,
+
+ /// <summary>
+ /// An instance of an <see cref="System.Linq.IQueryable<>"/> of a given <see cref="TestData"/> type.
+ /// </summary>
+ AsIQueryable = 0x80,
+
+ /// <summary>
+ /// An instance of a DataContract type in which a given <see cref="TestData"/> type is a member.
+ /// </summary>
+ AsDataMember = 0x100,
+
+ /// <summary>
+ /// An instance of a type in which a given <see cref="TestData"/> type is decorated with a
+ /// <see cref="System.Xml.Serialization.XmlElementAttribute"/>.
+ /// </summary>
+ AsXmlElementProperty = 0x200,
+
+ /// <summary>
+ /// All of the flags for single instance variations of a given <see cref="TestData"/> type.
+ /// </summary>
+ AllSingleInstances = AsInstance | AsDerivedType | AsKnownType | AsNullable,
+
+ /// <summary>
+ /// All of the flags for collection variations of a given <see cref="TestData"/> type.
+ /// </summary>
+ AllCollections = AsList | AsArray | AsIEnumerable | AsIQueryable,
+
+ /// <summary>
+ /// All of the flags for variations in which a given <see cref="TestData"/> type is a property on another type.
+ /// </summary>
+ AllProperties = AsDataMember | AsXmlElementProperty,
+
+ /// <summary>
+ /// All of the flags for interface collection variations of a given <see cref="TestData"/> type.
+ /// </summary>
+ AllInterfaces = AsIEnumerable | AsIQueryable,
+
+ /// <summary>
+ /// All of the flags except for the interface collection variations of a given <see cref="TestData"/> type.
+ /// </summary>
+ AllNonInterfaces = All & ~AllInterfaces,
+
+ /// <summary>
+ /// All of the flags for all of the variations of a given <see cref="TestData"/> type.
+ /// </summary>
+ All = AllSingleInstances | AllCollections | AllProperties
+ }
+}
diff --git a/test/Microsoft.TestCommon/Microsoft/TestCommon/DataSets/ValueTypeTestData.cs b/test/Microsoft.TestCommon/Microsoft/TestCommon/DataSets/ValueTypeTestData.cs
new file mode 100644
index 00000000..6d730e4b
--- /dev/null
+++ b/test/Microsoft.TestCommon/Microsoft/TestCommon/DataSets/ValueTypeTestData.cs
@@ -0,0 +1,31 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace Microsoft.TestCommon
+{
+ public class ValueTypeTestData<T> : TestData<T> where T : struct
+ {
+ private static readonly Type OpenNullableType = typeof(Nullable<>);
+ private T[] testData;
+
+ public ValueTypeTestData(params T[] testData)
+ : base()
+ {
+ this.testData = testData;
+
+ Type[] typeParams = new Type[] { this.Type };
+ this.RegisterTestDataVariation(TestDataVariations.AsNullable, OpenNullableType.MakeGenericType(typeParams), GetTestDataAsNullable);
+ }
+
+ public IEnumerable<Nullable<T>> GetTestDataAsNullable()
+ {
+ return this.GetTypedTestData().Select(d => new Nullable<T>(d));
+ }
+
+ protected override IEnumerable<T> GetTypedTestData()
+ {
+ return this.testData;
+ }
+ }
+}
diff --git a/test/Microsoft.TestCommon/Microsoft/TestCommon/GenericTypeAssert.cs b/test/Microsoft.TestCommon/Microsoft/TestCommon/GenericTypeAssert.cs
new file mode 100644
index 00000000..45990138
--- /dev/null
+++ b/test/Microsoft.TestCommon/Microsoft/TestCommon/GenericTypeAssert.cs
@@ -0,0 +1,489 @@
+using System;
+using System.Linq;
+using System.Reflection;
+using Xunit;
+
+namespace Microsoft.TestCommon
+{
+ /// <summary>
+ /// MSTest assertion class to provide convenience and assert methods for generic types
+ /// whose type parameters are not known at compile time.
+ /// </summary>
+ public class GenericTypeAssert
+ {
+ private static readonly GenericTypeAssert singleton = new GenericTypeAssert();
+
+ public static GenericTypeAssert Singleton { get { return singleton; } }
+
+ /// <summary>
+ /// Asserts the given <paramref name="genericBaseType"/> is a generic type and creates a new
+ /// bound generic type using <paramref name="genericParameterType"/>. It then asserts there
+ /// is a constructor that will accept <paramref name="parameterTypes"/> and returns it.
+ /// </summary>
+ /// <param name="genericBaseType">The unbound generic base type.</param>
+ /// <param name="genericParameterType">The type of the single generic parameter to apply to create a bound generic type.</param>
+ /// <param name="parameterTypes">The list of parameter types for a constructor that must exist.</param>
+ /// <returns>The <see cref="ConstructorInfo"/> of that constructor which may be invoked to create that new generic type.</returns>
+ public ConstructorInfo GetConstructor(Type genericBaseType, Type genericParameterType, params Type[] parameterTypes)
+ {
+ Assert.NotNull(genericBaseType);
+ Assert.True(genericBaseType.IsGenericTypeDefinition);
+ Assert.NotNull(genericParameterType);
+ Assert.NotNull(parameterTypes);
+
+ Type genericType = genericBaseType.MakeGenericType(new Type[] { genericParameterType });
+ ConstructorInfo ctor = genericType.GetConstructor(parameterTypes);
+ Assert.True(ctor != null, String.Format("Test error: failed to locate generic ctor for type '{0}<{1}>',", genericBaseType.Name, genericParameterType.Name));
+ return ctor;
+ }
+
+ /// <summary>
+ /// Asserts the given <paramref name="genericBaseType"/> is a generic type and creates a new
+ /// bound generic type using <paramref name="genericParameterType"/>. It then asserts there
+ /// is a constructor that will accept <paramref name="parameterTypes"/> and returns it.
+ /// </summary>
+ /// <param name="genericBaseType">The unbound generic base type.</param>
+ /// <param name="genericParameterTypes">The types of the generic parameters to apply to create a bound generic type.</param>
+ /// <param name="parameterTypes">The list of parameter types for a constructor that must exist.</param>
+ /// <returns>The <see cref="ConstructorInfo"/> of that constructor which may be invoked to create that new generic type.</returns>
+ public ConstructorInfo GetConstructor(Type genericBaseType, Type[] genericParameterTypes, params Type[] parameterTypes)
+ {
+ Assert.NotNull(genericBaseType);
+ Assert.True(genericBaseType.IsGenericTypeDefinition);
+ Assert.NotNull(genericParameterTypes);
+ Assert.NotNull(parameterTypes);
+
+ Type genericType = genericBaseType.MakeGenericType(genericParameterTypes);
+ ConstructorInfo ctor = genericType.GetConstructor(parameterTypes);
+ Assert.True(ctor != null, String.Format("Test error: failed to locate generic ctor for type '{0}<>',", genericBaseType.Name));
+ return ctor;
+ }
+
+ /// <summary>
+ /// Creates a new bound generic type and invokes the constructor matched from <see cref="parameterTypes"/>.
+ /// </summary>
+ /// <param name="genericBaseType">The unbound generic base type.</param>
+ /// <param name="genericParameterType">The type of the single generic parameter to apply to create a bound generic type.</param>
+ /// <param name="parameterTypes">The list of parameter types for a constructor that must exist.</param>
+ /// <param name="parameterValues">The list of values to supply to the constructor</param>
+ /// <returns>The instance created by calling that constructor.</returns>
+ public object InvokeConstructor(Type genericBaseType, Type genericParameterType, Type[] parameterTypes, object[] parameterValues)
+ {
+ ConstructorInfo ctor = GetConstructor(genericBaseType, genericParameterType, parameterTypes);
+ Assert.NotNull(parameterValues);
+ Assert.Equal(parameterTypes.Length, parameterValues.Length);
+ return ctor.Invoke(parameterValues);
+ }
+
+ /// <summary>
+ /// Creates a new bound generic type and invokes the constructor matched from <see cref="parameterTypes"/>.
+ /// </summary>
+ /// <param name="genericBaseType">The unbound generic base type.</param>
+ /// <param name="genericParameterTypse">The types of the generic parameters to apply to create a bound generic type.</param>
+ /// <param name="parameterTypes">The list of parameter types for a constructor that must exist.</param>
+ /// <param name="parameterValues">The list of values to supply to the constructor</param>
+ /// <returns>The instance created by calling that constructor.</returns>
+ public object InvokeConstructor(Type genericBaseType, Type[] genericParameterTypes, Type[] parameterTypes, object[] parameterValues)
+ {
+ ConstructorInfo ctor = GetConstructor(genericBaseType, genericParameterTypes, parameterTypes);
+ Assert.NotNull(parameterValues);
+ Assert.Equal(parameterTypes.Length, parameterValues.Length);
+ return ctor.Invoke(parameterValues);
+ }
+
+ /// <summary>
+ /// Creates a new bound generic type and invokes the constructor matched from the types of <paramref name="parameterValues"/>.
+ /// </summary>
+ /// <param name="genericBaseType">The unbound generic base type.</param>
+ /// <param name="genericParameterType">The type of the single generic parameter to apply to create a bound generic type.</param>
+ /// <param name="parameterValues">The list of values to supply to the constructor. It must be possible to determine the</param>
+ /// <returns>The instance created by calling that constructor.</returns>
+ public object InvokeConstructor(Type genericBaseType, Type genericParameterType, params object[] parameterValues)
+ {
+ Assert.NotNull(genericBaseType);
+ Assert.True(genericBaseType.IsGenericTypeDefinition);
+ Assert.NotNull(genericParameterType);
+
+ Type genericType = genericBaseType.MakeGenericType(new Type[] { genericParameterType });
+
+ ConstructorInfo ctor = FindConstructor(genericType, parameterValues);
+ Assert.True(ctor != null, String.Format("Test error: failed to locate generic ctor for type '{0}<{1}>',", genericBaseType.Name, genericParameterType.Name));
+ return ctor.Invoke(parameterValues);
+ }
+
+ /// <summary>
+ /// Creates a new bound generic type and invokes the constructor matched from the types of <paramref name="parameterValues"/>.
+ /// </summary>
+ /// <param name="genericBaseType">The unbound generic base type.</param>
+ /// <param name="genericParameterTypes">The types of the generic parameters to apply to create a bound generic type.</param>
+ /// <param name="parameterValues">The list of values to supply to the constructor. It must be possible to determine the</param>
+ /// <returns>The instance created by calling that constructor.</returns>
+ public object InvokeConstructor(Type genericBaseType, Type[] genericParameterTypes, params object[] parameterValues)
+ {
+ Assert.NotNull(genericBaseType);
+ Assert.True(genericBaseType.IsGenericTypeDefinition);
+ Assert.NotNull(genericParameterTypes);
+
+ Type genericType = genericBaseType.MakeGenericType(genericParameterTypes);
+
+ ConstructorInfo ctor = FindConstructor(genericType, parameterValues);
+ Assert.True(ctor != null, String.Format("Test error: failed to locate generic ctor for type '{0}<>',", genericBaseType.Name));
+ return ctor.Invoke(parameterValues);
+ }
+
+ /// <summary>
+ /// Creates a new bound generic type and invokes the constructor matched from <see cref="parameterTypes"/>.
+ /// </summary>
+ /// <typeparam name="T">The type of object the constuctor is expected to yield.</typeparam>
+ /// <param name="genericBaseType">The unbound generic base type.</param>
+ /// <param name="genericParameterType">The type of the single generic parameter to apply to create a bound generic type.</param>
+ /// <param name="parameterTypes">The list of parameter types for a constructor that must exist.</param>
+ /// <param name="parameterValues">The list of values to supply to the constructor</param>
+ /// <returns>An instance of type <typeparamref name="T"/>.</returns>
+ public T InvokeConstructor<T>(Type genericBaseType, Type genericParameterType, Type[] parameterTypes, object[] parameterValues)
+ {
+ ConstructorInfo ctor = GetConstructor(genericBaseType, genericParameterType, parameterTypes);
+ Assert.NotNull(parameterValues);
+ Assert.Equal(parameterTypes.Length, parameterValues.Length);
+ return (T)ctor.Invoke(parameterValues);
+ }
+
+ /// <summary>
+ /// Creates a new bound generic type and invokes the constructor matched from <see cref="parameterTypes"/>.
+ /// </summary>
+ /// <typeparam name="T">The type of object the constuctor is expected to yield.</typeparam>
+ /// <param name="genericBaseType">The unbound generic base type.</param>
+ /// <param name="genericParameterTypes">The types of the generic parameters to apply to create a bound generic type.</param>
+ /// <param name="parameterTypes">The list of parameter types for a constructor that must exist.</param>
+ /// <param name="parameterValues">The list of values to supply to the constructor</param>
+ /// <returns>An instance of type <typeparamref name="T"/>.</returns>
+ public T InvokeConstructor<T>(Type genericBaseType, Type[] genericParameterTypes, Type[] parameterTypes, object[] parameterValues)
+ {
+ ConstructorInfo ctor = GetConstructor(genericBaseType, genericParameterTypes, parameterTypes);
+ Assert.NotNull(parameterValues);
+ Assert.Equal(parameterTypes.Length, parameterValues.Length);
+ return (T)ctor.Invoke(parameterValues);
+ }
+
+ /// <summary>
+ /// Creates a new bound generic type and invokes the constructor matched from <see cref="parameterTypes"/>.
+ /// </summary>
+ /// <typeparam name="T">The type of object the constuctor is expected to yield.</typeparam>
+ /// <param name="genericBaseType">The unbound generic base type.</param>
+ /// <param name="genericParameterType">The type of the single generic parameter to apply to create a bound generic type.</param>
+ /// <param name="parameterValues">The list of values to supply to the constructor. It must be possible to determine the</param>
+ /// <returns>The instance created by calling that constructor.</returns>
+ /// <returns>An instance of type <typeparamref name="T"/>.</returns>
+ public T InvokeConstructor<T>(Type genericBaseType, Type genericParameterType, params object[] parameterValues)
+ {
+ Assert.NotNull(genericBaseType == null);
+ Assert.True(genericBaseType.IsGenericTypeDefinition);
+ Assert.NotNull(genericParameterType);
+
+ Type genericType = genericBaseType.MakeGenericType(new Type[] { genericParameterType });
+
+ ConstructorInfo ctor = FindConstructor(genericType, parameterValues);
+ Assert.True(ctor != null, String.Format("Test error: failed to locate generic ctor for type '{0}<{1}>',", genericBaseType.Name, genericParameterType.Name));
+ return (T)ctor.Invoke(parameterValues);
+ }
+
+ /// <summary>
+ /// Creates a new bound generic type and invokes the constructor matched from <see cref="parameterTypes"/>.
+ /// </summary>
+ /// <typeparam name="T">The type of object the constuctor is expected to yield.</typeparam>
+ /// <param name="genericBaseType">The unbound generic base type.</param>
+ /// <param name="genericParameterTypes">The types of the generic parameters to apply to create a bound generic type.</param>
+ /// <param name="parameterValues">The list of values to supply to the constructor. It must be possible to determine the</param>
+ /// <returns>The instance created by calling that constructor.</returns>
+ /// <returns>An instance of type <typeparamref name="T"/>.</returns>
+ public T InvokeConstructor<T>(Type genericBaseType, Type[] genericParameterTypes, params object[] parameterValues)
+ {
+ Assert.NotNull(genericBaseType);
+ Assert.True(genericBaseType.IsGenericTypeDefinition);
+ Assert.NotNull(genericParameterTypes);
+
+ Type genericType = genericBaseType.MakeGenericType(genericParameterTypes);
+
+ ConstructorInfo ctor = FindConstructor(genericType, parameterValues);
+ Assert.True(ctor != null, String.Format("Test error: failed to locate generic ctor for type '{0}<>',", genericBaseType.Name));
+ return (T)ctor.Invoke(parameterValues);
+ }
+
+ /// <summary>
+ /// Asserts the given instance is one from a generic type of the specified parameter type.
+ /// </summary>
+ /// <typeparam name="T">The type of instance.</typeparam>
+ /// <param name="instance">The instance to test.</param>
+ /// <param name="genericTypeParameter">The type of the generic parameter to which the instance's generic type should have been bound.</param>
+ public void IsCorrectGenericType<T>(T instance, Type genericTypeParameter)
+ {
+ Assert.NotNull(instance);
+ Assert.NotNull(genericTypeParameter);
+ Assert.True(instance.GetType().IsGenericType);
+ Type[] genericArguments = instance.GetType().GetGenericArguments();
+ Assert.Equal(1, genericArguments.Length);
+ Assert.Equal(genericTypeParameter, genericArguments[0]);
+ }
+
+ /// <summary>
+ /// Invokes via Reflection the method on the given instance.
+ /// </summary>
+ /// <param name="instance">The instance to use.</param>
+ /// <param name="methodName">The name of the method to call.</param>
+ /// <param name="parameterTypes">The types of the parameters to the method.</param>
+ /// <param name="parameterValues">The values to supply to the method.</param>
+ /// <returns>The results of the method.</returns>
+ public object InvokeMethod(object instance, string methodName, Type[] parameterTypes, object[] parameterValues)
+ {
+ Assert.NotNull(instance);
+ Assert.NotNull(parameterTypes);
+ Assert.NotNull(parameterValues);
+ Assert.Equal(parameterTypes.Length, parameterValues.Length);
+ MethodInfo methodInfo = instance.GetType().GetMethod(methodName, parameterTypes);
+ Assert.NotNull(methodInfo);
+ return methodInfo.Invoke(instance, parameterValues);
+ }
+
+ /// <summary>
+ /// Invokes via Reflection the static method on the given type.
+ /// </summary>
+ /// <param name="type">The type containing the method.</param>
+ /// <param name="methodName">The name of the method to call.</param>
+ /// <param name="parameterTypes">The types of the parameters to the method.</param>
+ /// <param name="parameterValues">The values to supply to the method.</param>
+ /// <returns>The results of the method.</returns>
+ public object InvokeMethod(Type type, string methodName, Type[] parameterTypes, object[] parameterValues)
+ {
+ Assert.NotNull(type);
+ Assert.NotNull(parameterTypes);
+ Assert.NotNull(parameterValues);
+ Assert.Equal(parameterTypes.Length, parameterValues.Length);
+ MethodInfo methodInfo = type.GetMethod(methodName, parameterTypes);
+ Assert.NotNull(methodInfo);
+ return methodInfo.Invoke(null, parameterValues);
+ }
+
+ /// <summary>
+ /// Invokes via Reflection the static method on the given type.
+ /// </summary>
+ /// <param name="type">The type containing the method.</param>
+ /// <param name="methodName">The name of the method to call.</param>
+ /// <param name="genericParameterType">The generic parameter type of the method.</param>
+ /// <param name="parameterTypes">The types of the parameters to the method.</param>
+ /// <param name="parameterValues">The values to supply to the method.</param>
+ /// <returns>The results of the method.</returns>
+ public MethodInfo CreateGenericMethod(Type type, string methodName, Type genericParameterType, Type[] parameterTypes)
+ {
+ Assert.NotNull(type);
+ Assert.NotNull(parameterTypes);
+ Assert.NotNull(genericParameterType);
+ //MethodInfo methodInfo = type.GetMethod(methodName, parameterTypes);
+ MethodInfo methodInfo = type.GetMethods().Where((m) => m.Name.Equals(methodName, StringComparison.OrdinalIgnoreCase) && m.IsGenericMethod && AreAssignableFrom(m.GetParameters(), parameterTypes)).FirstOrDefault();
+ Assert.NotNull(methodInfo);
+ Assert.True(methodInfo.IsGenericMethod);
+ MethodInfo genericMethod = methodInfo.MakeGenericMethod(genericParameterType);
+ Assert.NotNull(genericMethod);
+ return genericMethod;
+ }
+
+ /// <summary>
+ /// Invokes via Reflection the static generic method on the given type.
+ /// </summary>
+ /// <param name="type">The type containing the method.</param>
+ /// <param name="methodName">The name of the method to call.</param>
+ /// <param name="genericParameterType">The generic parameter type of the method.</param>
+ /// <param name="parameterTypes">The types of the parameters to the method.</param>
+ /// <param name="parameterValues">The values to supply to the method.</param>
+ /// <returns>The results of the method.</returns>
+ public object InvokeGenericMethod(Type type, string methodName, Type genericParameterType, Type[] parameterTypes, object[] parameterValues)
+ {
+ MethodInfo methodInfo = CreateGenericMethod(type, methodName, genericParameterType, parameterTypes);
+ Assert.Equal(parameterTypes.Length, parameterValues.Length);
+ return methodInfo.Invoke(null, parameterValues);
+ }
+
+ /// <summary>
+ /// Invokes via Reflection the generic method on the given instance.
+ /// </summary>
+ /// <param name="instance">The instance on which to invoke the method.</param>
+ /// <param name="methodName">The name of the method to call.</param>
+ /// <param name="genericParameterType">The generic parameter type of the method.</param>
+ /// <param name="parameterTypes">The types of the parameters to the method.</param>
+ /// <param name="parameterValues">The values to supply to the method.</param>
+ /// <returns>The results of the method.</returns>
+ public object InvokeGenericMethod(object instance, string methodName, Type genericParameterType, Type[] parameterTypes, object[] parameterValues)
+ {
+ Assert.NotNull(instance);
+ MethodInfo methodInfo = CreateGenericMethod(instance.GetType(), methodName, genericParameterType, parameterTypes);
+ Assert.Equal(parameterTypes.Length, parameterValues.Length);
+ return methodInfo.Invoke(instance, parameterValues);
+ }
+
+ /// <summary>
+ /// Invokes via Reflection the generic method on the given instance.
+ /// </summary>
+ /// <typeparam name="T">The type of the return value from the method.</typeparam>
+ /// <param name="instance">The instance on which to invoke the method.</param>
+ /// <param name="methodName">The name of the method to call.</param>
+ /// <param name="genericParameterType">The generic parameter type of the method.</param>
+ /// <param name="parameterTypes">The types of the parameters to the method.</param>
+ /// <param name="parameterValues">The values to supply to the method.</param>
+ /// <returns>The results of the method.</returns>
+ public T InvokeGenericMethod<T>(object instance, string methodName, Type genericParameterType, Type[] parameterTypes, object[] parameterValues)
+ {
+ return (T)InvokeGenericMethod(instance, methodName, genericParameterType, parameterTypes, parameterValues);
+ }
+
+ /// <summary>
+ /// Invokes via Reflection the method on the given instance.
+ /// </summary>
+ /// <param name="instance">The instance to use.</param>
+ /// <param name="methodName">The name of the method to call.</param>
+ /// <param name="parameterValues">The values to supply to the method.</param>
+ /// <returns>The results of the method.</returns>
+ public object InvokeMethod(object instance, string methodName, params object[] parameterValues)
+ {
+ Assert.NotNull(instance);
+ MethodInfo methodInfo = FindMethod(instance.GetType(), methodName, parameterValues);
+ Assert.NotNull(methodInfo);
+ return methodInfo.Invoke(instance, parameterValues);
+ }
+
+ /// <summary>
+ /// Invokes via Reflection the static method on the given type.
+ /// </summary>
+ /// <param name="instance">The instance to use.</param>
+ /// <param name="methodName">The name of the method to call.</param>
+ /// <param name="parameterValues">The values to supply to the method.</param>
+ /// <returns>The results of the method.</returns>
+ public object InvokeMethod(Type type, string methodName, params object[] parameterValues)
+ {
+ Assert.NotNull(type);
+ MethodInfo methodInfo = FindMethod(type, methodName, parameterValues);
+ Assert.NotNull(methodInfo);
+ return methodInfo.Invoke(null, parameterValues);
+ }
+
+ /// <summary>
+ /// Invokes via Reflection the method on the given instance.
+ /// </summary>
+ /// <param name="instance">The instance to use.</param>
+ /// <param name="methodName">The name of the method to call.</param>
+ /// <param name="genericParameterType">The type of the generic parameter.</param>
+ /// <param name="parameterValues">The values to supply to the method.</param>
+ /// <returns>The results of the method.</returns>
+ public object InvokeGenericMethod(object instance, string methodName, Type genericParameterType, params object[] parameterValues)
+ {
+ Assert.NotNull(instance);
+ Assert.NotNull(genericParameterType);
+ MethodInfo methodInfo = FindMethod(instance.GetType(), methodName, parameterValues);
+ Assert.NotNull(methodInfo);
+ Assert.True(methodInfo.IsGenericMethod);
+ MethodInfo genericMethod = methodInfo.MakeGenericMethod(genericParameterType);
+ return genericMethod.Invoke(instance, parameterValues);
+ }
+
+ /// <summary>
+ /// Invokes via Reflection the method on the given instance.
+ /// </summary>
+ /// <param name="instance">The instance to use.</param>
+ /// <param name="methodName">The name of the method to call.</param>
+ /// <param name="genericParameterType">The type of the generic parameter.</param>
+ /// <param name="parameterValues">The values to supply to the method.</param>
+ /// <returns>The results of the method.</returns>
+ public object InvokeGenericMethod(Type type, string methodName, Type genericParameterType, params object[] parameterValues)
+ {
+ Assert.NotNull(type);
+ Assert.NotNull(genericParameterType);
+ MethodInfo methodInfo = FindMethod(type, methodName, parameterValues);
+ Assert.NotNull(methodInfo);
+ Assert.True(methodInfo.IsGenericMethod);
+ MethodInfo genericMethod = methodInfo.MakeGenericMethod(genericParameterType);
+ return genericMethod.Invoke(null, parameterValues);
+ }
+
+ /// <summary>
+ /// Retrieves the value from the specified property.
+ /// </summary>
+ /// <param name="instance">The instance containing the property value.</param>
+ /// <param name="propertyName">The name of the property.</param>
+ /// <param name="failureMessage">The error message to prefix any test assertions.</param>
+ /// <returns>The value returned from the property.</returns>
+ public object GetProperty(object instance, string propertyName, string failureMessage)
+ {
+ PropertyInfo propertyInfo = instance.GetType().GetProperty(propertyName);
+ Assert.NotNull(propertyInfo);
+ return propertyInfo.GetValue(instance, null);
+ }
+
+ private static bool AreAssignableFrom(Type[] parameterTypes, params object[] parameterValues)
+ {
+ Assert.NotNull(parameterTypes);
+ Assert.NotNull(parameterValues);
+ if (parameterTypes.Length != parameterValues.Length)
+ {
+ return false;
+ }
+
+ for (int i = 0; i < parameterTypes.Length; ++i)
+ {
+ if (!parameterTypes[i].IsInstanceOfType(parameterValues[i]))
+ {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ private static bool AreAssignableFrom(ParameterInfo[] parameterInfos, params Type[] parameterTypes)
+ {
+ Assert.NotNull(parameterInfos);
+ Assert.NotNull(parameterTypes);
+ Type[] parameterInfoTypes = parameterInfos.Select<ParameterInfo, Type>((info) => info.ParameterType).ToArray();
+ if (parameterInfoTypes.Length != parameterTypes.Length)
+ {
+ return false;
+ }
+
+ for (int i = 0; i < parameterInfoTypes.Length; ++i)
+ {
+ // Generic parameters are assumed to be assignable
+ if (parameterInfoTypes[i].IsGenericParameter)
+ {
+ continue;
+ }
+
+ if (!parameterInfoTypes[i].IsAssignableFrom(parameterTypes[i]))
+ {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ private static bool AreAssignableFrom(ParameterInfo[] parameterInfos, params object[] parameterValues)
+ {
+ Assert.NotNull(parameterInfos);
+ Assert.NotNull(parameterValues);
+ Type[] parameterTypes = parameterInfos.Select<ParameterInfo, Type>((info) => info.ParameterType).ToArray();
+ return AreAssignableFrom(parameterTypes, parameterValues);
+ }
+
+ private static ConstructorInfo FindConstructor(Type type, params object[] parameterValues)
+ {
+ Assert.NotNull(type);
+ Assert.NotNull(parameterValues);
+ return type.GetConstructors().FirstOrDefault((c) => AreAssignableFrom(c.GetParameters(), parameterValues));
+ }
+
+ private static MethodInfo FindMethod(Type type, string methodName, params object[] parameterValues)
+ {
+ Assert.NotNull(type);
+ Assert.False(String.IsNullOrWhiteSpace(methodName));
+ Assert.NotNull(parameterValues);
+ return type.GetMethods().FirstOrDefault((m) => String.Equals(m.Name, methodName, StringComparison.Ordinal) && AreAssignableFrom(m.GetParameters(), parameterValues));
+ }
+ }
+}
diff --git a/test/Microsoft.TestCommon/Microsoft/TestCommon/HttpAssert.cs b/test/Microsoft.TestCommon/Microsoft/TestCommon/HttpAssert.cs
new file mode 100644
index 00000000..645be48a
--- /dev/null
+++ b/test/Microsoft.TestCommon/Microsoft/TestCommon/HttpAssert.cs
@@ -0,0 +1,252 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Linq;
+using System.Net.Http;
+using System.Net.Http.Headers;
+using System.Text.RegularExpressions;
+using Xunit;
+using MsTest = Microsoft.VisualStudio.TestTools.UnitTesting;
+
+namespace Microsoft.TestCommon
+{
+ /// <summary>
+ /// MSTest utility for testing <see cref="HttpResponseMessage"/> instances.
+ /// </summary>
+ public class HttpAssert
+ {
+ private const string CommaSeperator = ", ";
+ private static readonly HttpAssert singleton = new HttpAssert();
+
+ public static HttpAssert Singleton { get { return singleton; } }
+
+ /// <summary>
+ /// Asserts that the expected <see cref="HttpRequestMessage"/> is equal to the actual <see cref="HttpRequestMessage"/>.
+ /// </summary>
+ /// <param name="expected">The expected <see cref="HttpRequestMessage"/>. Should not be <c>null</c>.</param>
+ /// <param name="actual">The actual <see cref="HttpRequestMessage"/>. Should not be <c>null</c>.</param>
+ public void Equal(HttpRequestMessage expected, HttpRequestMessage actual)
+ {
+ Assert.NotNull(expected);
+ Assert.NotNull(actual);
+
+ Assert.Equal(expected.Version, actual.Version);
+ Equal(expected.Headers, actual.Headers);
+
+ if (expected.Content == null)
+ {
+ Assert.Null(actual.Content);
+ }
+ else
+ {
+ string expectedContent = CleanContentString(expected.Content.ReadAsStringAsync().Result);
+ string actualContent = CleanContentString(actual.Content.ReadAsStringAsync().Result);
+ Assert.Equal(expectedContent, actualContent);
+ Equal(expected.Content.Headers, actual.Content.Headers);
+ }
+ }
+
+ /// <summary>
+ /// Asserts that the expected <see cref="HttpResponseMessage"/> is equal to the actual <see cref="HttpResponseMessage"/>.
+ /// </summary>
+ /// <param name="expected">The expected <see cref="HttpResponseMessage"/>. Should not be <c>null</c>.</param>
+ /// <param name="actual">The actual <see cref="HttpResponseMessage"/>. Should not be <c>null</c>.</param>
+ public void Equal(HttpResponseMessage expected, HttpResponseMessage actual)
+ {
+ Equal(expected, actual, null);
+ }
+
+ /// <summary>
+ /// Asserts that the expected <see cref="HttpResponseMessage"/> is equal to the actual <see cref="HttpResponseMessage"/>.
+ /// </summary>
+ /// <param name="expected">The expected <see cref="HttpResponseMessage"/>. Should not be <c>null</c>.</param>
+ /// <param name="actual">The actual <see cref="HttpResponseMessage"/>. Should not be <c>null</c>.</param>
+ /// <param name="verifyContentCallback">The callback to verify the Content string. If it is null, Assert.Equal will be used. </param>
+ public void Equal(HttpResponseMessage expected, HttpResponseMessage actual, Action<string, string> verifyContentStringCallback)
+ {
+ Assert.NotNull(expected);
+ Assert.NotNull(actual);
+
+ Assert.Equal(expected.StatusCode, actual.StatusCode);
+ Assert.Equal(expected.ReasonPhrase, actual.ReasonPhrase);
+ Assert.Equal(expected.Version, actual.Version);
+ Equal(expected.Headers, actual.Headers);
+
+ if (expected.Content == null)
+ {
+ Assert.Null(actual.Content);
+ }
+ else
+ {
+ string expectedContent = CleanContentString(expected.Content.ReadAsStringAsync().Result);
+ string actualContent = CleanContentString(actual.Content.ReadAsStringAsync().Result);
+ if (verifyContentStringCallback != null)
+ {
+ verifyContentStringCallback(expectedContent, actualContent);
+ }
+ else
+ {
+ Assert.Equal(expectedContent, actualContent);
+ }
+ Equal(expected.Content.Headers, actual.Content.Headers);
+ }
+ }
+
+ /// <summary>
+ /// Asserts that the expected <see cref="HttpHeaders"/> instance is equal to the actual <see cref="actualHeaders"/> instance.
+ /// </summary>
+ /// <param name="expectedHeaders">The expected <see cref="HttpHeaders"/> instance. Should not be <c>null</c>.</param>
+ /// <param name="actualHeaders">The actual <see cref="HttpHeaders"/> instance. Should not be <c>null</c>.</param>
+ public void Equal(HttpHeaders expectedHeaders, HttpHeaders actualHeaders)
+ {
+ Assert.NotNull(expectedHeaders);
+ Assert.NotNull(actualHeaders);
+
+ Assert.Equal(expectedHeaders.Count(), actualHeaders.Count());
+
+ foreach (KeyValuePair<string, IEnumerable<string>> expectedHeader in expectedHeaders)
+ {
+ KeyValuePair<string, IEnumerable<string>> actualHeader = actualHeaders.FirstOrDefault(h => h.Key == expectedHeader.Key);
+ Assert.NotNull(actualHeader);
+
+ if (expectedHeader.Key == "Date")
+ {
+ HandleDateHeader(expectedHeader.Value.ToArray(), actualHeader.Value.ToArray());
+ }
+ else
+ {
+ string expectedHeaderStr = string.Join(CommaSeperator, expectedHeader.Value);
+ string actualHeaderStr = string.Join(CommaSeperator, actualHeader.Value);
+ Assert.Equal(expectedHeaderStr, actualHeaderStr);
+ }
+ }
+ }
+
+ /// <summary>
+ /// Asserts the given <see cref="HttpHeaders"/> contain the given <paramref name="values"/>
+ /// for the given <paramref name="name"/>.
+ /// </summary>
+ /// <param name="headers">The <see cref="HttpHeaders"/> to examine. It cannot be <c>null</c>.</param>
+ /// <param name="name">The name of the header. It cannot be empty.</param>
+ /// <param name="values">The values that must all be present. It cannot be null.</param>
+ public void Contains(HttpHeaders headers, string name, params string[] values)
+ {
+ Assert.NotNull(headers);
+ Assert.False(String.IsNullOrWhiteSpace(name), "Test error: name cannot be empty.");
+ Assert.NotNull(values);
+
+ IEnumerable<string> headerValues = null;
+ bool foundIt = headers.TryGetValues(name, out headerValues);
+ Assert.True(foundIt);
+ MsTest.CollectionAssert.IsSubsetOf(values.ToList(), headerValues.ToList(), "Headers did not contain any or all of the expected headers.");
+ }
+
+ public bool IsKnownUnserializableType(Type type, Func<Type, bool> isTypeUnserializableCallback)
+ {
+ if (isTypeUnserializableCallback != null && isTypeUnserializableCallback(type))
+ {
+ return true;
+ }
+
+ if (type.IsGenericType)
+ {
+ if (typeof(IEnumerable).IsAssignableFrom(type))
+ {
+ if (type.GetMethod("Add") == null)
+ {
+ return true;
+ }
+ }
+
+ // Generic type -- recursively analyze generic arguments
+ return IsKnownUnserializableType(type.GetGenericArguments()[0], isTypeUnserializableCallback);
+ }
+
+ if (type.HasElementType && IsKnownUnserializableType(type.GetElementType(), isTypeUnserializableCallback))
+ {
+ return true;
+ }
+
+ return false;
+ }
+
+ public bool IsKnownUnserializable(Type type, object obj, Func<Type, bool> isTypeUnserializableCallback)
+ {
+ if (IsKnownUnserializableType(type, isTypeUnserializableCallback))
+ {
+ return true;
+ }
+
+ return obj != null && IsKnownUnserializableType(obj.GetType(), isTypeUnserializableCallback);
+ }
+
+ public bool IsKnownUnserializable(Type type, object obj)
+ {
+ return IsKnownUnserializable(type, obj, null);
+ }
+
+ public bool CanRoundTrip(Type type)
+ {
+ if (typeof(TimeSpan).IsAssignableFrom(type))
+ {
+ return false;
+ }
+
+ if (typeof(DateTimeOffset).IsAssignableFrom(type))
+ {
+ return false;
+ }
+
+ if (type.IsGenericType)
+ {
+ foreach (Type genericParameterType in type.GetGenericArguments())
+ {
+ if (!CanRoundTrip(genericParameterType))
+ {
+ return false;
+ }
+ }
+ }
+
+ if (type.HasElementType)
+ {
+ return CanRoundTrip(type.GetElementType());
+ }
+
+ return true;
+ }
+
+ private static void HandleDateHeader(string[] expectedDateHeaderValues, string[] actualDateHeaderValues)
+ {
+ Assert.Equal(expectedDateHeaderValues.Length, actualDateHeaderValues.Length);
+
+ for (int i = 0; i < expectedDateHeaderValues.Length; i++)
+ {
+ DateTime expectedDateTime = DateTime.Parse(expectedDateHeaderValues[i]);
+ DateTime actualDateTime = DateTime.Parse(actualDateHeaderValues[i]);
+
+ Assert.Equal(expectedDateTime.Year, actualDateTime.Year);
+ Assert.Equal(expectedDateTime.Month, actualDateTime.Month);
+ Assert.Equal(expectedDateTime.Day, actualDateTime.Day);
+
+ int hourDifference = Math.Abs(actualDateTime.Hour - expectedDateTime.Hour);
+ Assert.True(hourDifference <= 1);
+
+ int minuteDifference = Math.Abs(actualDateTime.Minute - expectedDateTime.Minute);
+ Assert.True(minuteDifference <= 1);
+ }
+ }
+
+ private static string CleanContentString(string content)
+ {
+ Assert.Null(content);
+
+ string cleanedContent = null;
+
+ // remove any port numbers from Uri's
+ cleanedContent = Regex.Replace(content, ":\\d+", "");
+
+ return cleanedContent;
+ }
+ }
+}
diff --git a/test/Microsoft.TestCommon/Microsoft/TestCommon/MediaTypeAssert.cs b/test/Microsoft.TestCommon/Microsoft/TestCommon/MediaTypeAssert.cs
new file mode 100644
index 00000000..39841350
--- /dev/null
+++ b/test/Microsoft.TestCommon/Microsoft/TestCommon/MediaTypeAssert.cs
@@ -0,0 +1,53 @@
+using System;
+using System.Net.Http.Headers;
+using Xunit;
+
+namespace Microsoft.TestCommon
+{
+ public class MediaTypeAssert
+ {
+ private static readonly MediaTypeAssert singleton = new MediaTypeAssert();
+
+ public static MediaTypeAssert Singleton { get { return singleton; } }
+
+ public void AreEqual(MediaTypeHeaderValue expected, MediaTypeHeaderValue actual, string errorMessage)
+ {
+ if (expected != null || actual != null)
+ {
+ Assert.NotNull(expected);
+ Assert.Equal(0, new MediaTypeHeaderValueComparer().Compare(expected, actual));
+ }
+ }
+
+ public void AreEqual(MediaTypeHeaderValue expected, string actual, string errorMessage)
+ {
+ if (expected != null || !String.IsNullOrEmpty(actual))
+ {
+ MediaTypeHeaderValue actualMediaType = new MediaTypeHeaderValue(actual);
+ Assert.NotNull(expected);
+ Assert.Equal(0, new MediaTypeHeaderValueComparer().Compare(expected, actualMediaType));
+ }
+ }
+
+ public void AreEqual(string expected, string actual, string errorMessage)
+ {
+ if (!String.IsNullOrEmpty(expected) || !String.IsNullOrEmpty(actual))
+ {
+ Assert.NotNull(expected);
+ MediaTypeHeaderValue expectedMediaType = new MediaTypeHeaderValue(expected);
+ MediaTypeHeaderValue actualMediaType = new MediaTypeHeaderValue(actual);
+ Assert.Equal(0, new MediaTypeHeaderValueComparer().Compare(expectedMediaType, actualMediaType));
+ }
+ }
+
+ public void AreEqual(string expected, MediaTypeHeaderValue actual, string errorMessage)
+ {
+ if (!String.IsNullOrEmpty(expected) || actual != null)
+ {
+ Assert.NotNull(expected);
+ MediaTypeHeaderValue expectedMediaType = new MediaTypeHeaderValue(expected); ;
+ Assert.Equal(0, new MediaTypeHeaderValueComparer().Compare(expectedMediaType, actual));
+ }
+ }
+ }
+}
diff --git a/test/Microsoft.TestCommon/Microsoft/TestCommon/MediaTypeHeaderValueComparer.cs b/test/Microsoft.TestCommon/Microsoft/TestCommon/MediaTypeHeaderValueComparer.cs
new file mode 100644
index 00000000..8da097f5
--- /dev/null
+++ b/test/Microsoft.TestCommon/Microsoft/TestCommon/MediaTypeHeaderValueComparer.cs
@@ -0,0 +1,88 @@
+using System;
+using System.Collections.Generic;
+using System.Net.Http.Headers;
+
+namespace Microsoft.TestCommon
+{
+ public class MediaTypeHeaderValueComparer : IComparer<MediaTypeHeaderValue>
+ {
+ private static readonly MediaTypeHeaderValueComparer mediaTypeComparer = new MediaTypeHeaderValueComparer();
+
+ public MediaTypeHeaderValueComparer()
+ {
+ }
+
+ public static MediaTypeHeaderValueComparer Comparer
+ {
+ get
+ {
+ return mediaTypeComparer;
+ }
+ }
+
+ public int Compare(MediaTypeHeaderValue mediaType1, MediaTypeHeaderValue mediaType2)
+ {
+ ParsedMediaTypeHeaderValue parsedMediaType1 = new ParsedMediaTypeHeaderValue(mediaType1);
+ ParsedMediaTypeHeaderValue parsedMediaType2 = new ParsedMediaTypeHeaderValue(mediaType2);
+
+ int returnValue = CompareBasedOnQualityFactor(parsedMediaType1, parsedMediaType2);
+
+ if (returnValue == 0)
+ {
+ if (!String.Equals(parsedMediaType1.Type, parsedMediaType2.Type, StringComparison.OrdinalIgnoreCase))
+ {
+ if (parsedMediaType1.IsAllMediaRange)
+ {
+ return 1;
+ }
+ else if (parsedMediaType2.IsAllMediaRange)
+ {
+ return -1;
+ }
+ }
+ else if (!String.Equals(parsedMediaType1.SubType, parsedMediaType2.SubType, StringComparison.OrdinalIgnoreCase))
+ {
+ if (parsedMediaType1.IsSubTypeMediaRange)
+ {
+ return 1;
+ }
+ else if (parsedMediaType2.IsSubTypeMediaRange)
+ {
+ return -1;
+ }
+ }
+ else
+ {
+ if (!parsedMediaType1.HasNonQualityFactorParameter)
+ {
+ if (parsedMediaType2.HasNonQualityFactorParameter)
+ {
+ return 1;
+ }
+ }
+ else if (!parsedMediaType2.HasNonQualityFactorParameter)
+ {
+ return -1;
+ }
+ }
+ }
+
+ return returnValue;
+ }
+
+ private static int CompareBasedOnQualityFactor(ParsedMediaTypeHeaderValue parsedMediaType1, ParsedMediaTypeHeaderValue parsedMediaType2)
+ {
+ double qualityDifference = parsedMediaType1.QualityFactor - parsedMediaType2.QualityFactor;
+ if (qualityDifference < 0)
+ {
+ return 1;
+ }
+ else if (qualityDifference > 0)
+ {
+ return -1;
+ }
+
+ return 0;
+ }
+ }
+} \ No newline at end of file
diff --git a/test/Microsoft.TestCommon/Microsoft/TestCommon/ParsedMediaTypeHeaderValue.cs b/test/Microsoft.TestCommon/Microsoft/TestCommon/ParsedMediaTypeHeaderValue.cs
new file mode 100644
index 00000000..95fd2a69
--- /dev/null
+++ b/test/Microsoft.TestCommon/Microsoft/TestCommon/ParsedMediaTypeHeaderValue.cs
@@ -0,0 +1,109 @@
+using System;
+using System.Net.Http.Headers;
+
+namespace Microsoft.TestCommon
+{
+ internal class ParsedMediaTypeHeaderValue
+ {
+ private const string MediaRangeAsterisk = "*";
+ private const char MediaTypeSubTypeDelimiter = '/';
+ private const string QualityFactorParameterName = "q";
+ private const double DefaultQualityFactor = 1.0;
+
+ private MediaTypeHeaderValue mediaType;
+ private string type;
+ private string subType;
+ private bool? hasNonQualityFactorParameter;
+ private double? qualityFactor;
+
+ public ParsedMediaTypeHeaderValue(MediaTypeHeaderValue mediaType)
+ {
+ this.mediaType = mediaType;
+ string[] splitMediaType = mediaType.MediaType.Split(MediaTypeSubTypeDelimiter);
+ this.type = splitMediaType[0];
+ this.subType = splitMediaType[1];
+ }
+
+ public string Type
+ {
+ get
+ {
+ return this.type;
+ }
+ }
+
+ public string SubType
+ {
+ get
+ {
+ return this.subType;
+ }
+ }
+
+ public bool IsAllMediaRange
+ {
+ get
+ {
+ return this.IsSubTypeMediaRange && String.Equals(MediaRangeAsterisk, this.Type, StringComparison.Ordinal);
+ }
+ }
+
+ public bool IsSubTypeMediaRange
+ {
+ get
+ {
+ return String.Equals(MediaRangeAsterisk, this.SubType, StringComparison.Ordinal);
+ }
+ }
+
+ public bool HasNonQualityFactorParameter
+ {
+ get
+ {
+ if (!this.hasNonQualityFactorParameter.HasValue)
+ {
+ this.hasNonQualityFactorParameter = false;
+ foreach (NameValueHeaderValue param in this.mediaType.Parameters)
+ {
+ if (!String.Equals(QualityFactorParameterName, param.Name, StringComparison.Ordinal))
+ {
+ this.hasNonQualityFactorParameter = true;
+ }
+ }
+ }
+
+ return this.hasNonQualityFactorParameter.Value;
+ }
+ }
+
+ public string CharSet
+ {
+ get
+ {
+ return this.mediaType.CharSet;
+ }
+ }
+
+ public double QualityFactor
+ {
+ get
+ {
+ if (!this.qualityFactor.HasValue)
+ {
+ MediaTypeWithQualityHeaderValue mediaTypeWithQuality = this.mediaType as MediaTypeWithQualityHeaderValue;
+ if (mediaTypeWithQuality != null)
+ {
+ this.qualityFactor = mediaTypeWithQuality.Quality;
+ }
+
+ if (!this.qualityFactor.HasValue)
+ {
+ this.qualityFactor = DefaultQualityFactor;
+ }
+ }
+
+ return this.qualityFactor.Value;
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/test/Microsoft.TestCommon/Microsoft/TestCommon/RegexReplacement.cs b/test/Microsoft.TestCommon/Microsoft/TestCommon/RegexReplacement.cs
new file mode 100644
index 00000000..46737f2f
--- /dev/null
+++ b/test/Microsoft.TestCommon/Microsoft/TestCommon/RegexReplacement.cs
@@ -0,0 +1,38 @@
+using System.Text.RegularExpressions;
+
+namespace Microsoft.TestCommon
+{
+ public class RegexReplacement
+ {
+ Regex regex;
+ string replacement;
+
+ public RegexReplacement(Regex regex, string replacement)
+ {
+ this.regex = regex;
+ this.replacement = replacement;
+ }
+
+ public RegexReplacement(string regex, string replacement)
+ {
+ this.regex = new Regex(regex);
+ this.replacement = replacement;
+ }
+
+ public Regex Regex
+ {
+ get
+ {
+ return this.regex;
+ }
+ }
+
+ public string Replacement
+ {
+ get
+ {
+ return this.replacement;
+ }
+ }
+ }
+}
diff --git a/test/Microsoft.TestCommon/Microsoft/TestCommon/RuntimeEnvironment.cs b/test/Microsoft.TestCommon/Microsoft/TestCommon/RuntimeEnvironment.cs
new file mode 100644
index 00000000..4a3b523d
--- /dev/null
+++ b/test/Microsoft.TestCommon/Microsoft/TestCommon/RuntimeEnvironment.cs
@@ -0,0 +1,31 @@
+using System;
+using Microsoft.Win32;
+
+namespace Microsoft.TestCommon
+{
+ public static class RuntimeEnvironment
+ {
+ private const string NetFx40FullSubKey = @"SOFTWARE\Microsoft\NET Framework Setup\NDP\v4\Full";
+ private const string Version = "Version";
+
+ static RuntimeEnvironment()
+ {
+ object runtimeVersion = Registry.LocalMachine.OpenSubKey(RuntimeEnvironment.NetFx40FullSubKey).GetValue(RuntimeEnvironment.Version);
+ string versionFor40String = runtimeVersion as string;
+ if (versionFor40String != null)
+ {
+ VersionFor40 = new Version(versionFor40String);
+ }
+ }
+
+ private static Version VersionFor40;
+
+ public static bool IsVersion45Installed
+ {
+ get
+ {
+ return VersionFor40.Major > 4 || (VersionFor40.Major == 4 && VersionFor40.Minor >= 5);
+ }
+ }
+ }
+}
diff --git a/test/Microsoft.TestCommon/Microsoft/TestCommon/SerializerAssert.cs b/test/Microsoft.TestCommon/Microsoft/TestCommon/SerializerAssert.cs
new file mode 100644
index 00000000..316017b5
--- /dev/null
+++ b/test/Microsoft.TestCommon/Microsoft/TestCommon/SerializerAssert.cs
@@ -0,0 +1,150 @@
+using System;
+using System.IO;
+using System.Runtime.Serialization;
+using System.Runtime.Serialization.Json;
+using System.Xml.Serialization;
+
+namespace Microsoft.TestCommon
+{
+ /// <summary>
+ /// MSTest utility for testing code operating against a stream.
+ /// </summary>
+ public class SerializerAssert
+ {
+ private static SerializerAssert singleton = new SerializerAssert();
+
+ public static SerializerAssert Singleton { get { return singleton; } }
+
+ /// <summary>
+ /// Creates a <see cref="Stream"/>, serializes <paramref name="objectInstance"/> to it using
+ /// <see cref="XmlSerializer"/>, rewinds the stream and calls <see cref="codeThatChecks"/>.
+ /// </summary>
+ /// <param name="type">The type to serialize. It cannot be <c>null</c>.</param>
+ /// <param name="objectInstance">The value to serialize.</param>
+ /// <param name="codeThatChecks">Code to check the contents of the stream.</param>
+ public void UsingXmlSerializer(Type type, object objectInstance, Action<Stream> codeThatChecks)
+ {
+ if (type == null)
+ {
+ throw new ArgumentNullException("type");
+ }
+
+ if (codeThatChecks == null)
+ {
+ throw new ArgumentNullException("codeThatChecks");
+ }
+
+ XmlSerializer serializer = new XmlSerializer(type);
+
+ using (MemoryStream stream = new MemoryStream())
+ {
+ serializer.Serialize(stream, objectInstance);
+
+ stream.Flush();
+ stream.Seek(0L, SeekOrigin.Begin);
+
+ codeThatChecks(stream);
+ }
+ }
+
+ /// <summary>
+ /// Creates a <see cref="Stream"/>, serializes <paramref name="objectInstance"/> to it using
+ /// <see cref="XmlSerializer"/>, rewinds the stream and calls <see cref="codeThatChecks"/>.
+ /// </summary>
+ /// <typeparam name="T">The type to serialize.</typeparam>
+ /// <param name="objectInstance">The value to serialize.</param>
+ /// <param name="codeThatChecks">Code to check the contents of the stream.</param>
+ public void UsingXmlSerializer<T>(T objectInstance, Action<Stream> codeThatChecks)
+ {
+ UsingXmlSerializer(typeof(T), objectInstance, codeThatChecks);
+ }
+
+ /// <summary>
+ /// Creates a <see cref="Stream"/>, serializes <paramref name="objectInstance"/> to it using
+ /// <see cref="DataContractSerializer"/>, rewinds the stream and calls <see cref="codeThatChecks"/>.
+ /// </summary>
+ /// <param name="type">The type to serialize. It cannot be <c>null</c>.</param>
+ /// <param name="objectInstance">The value to serialize.</param>
+ /// <param name="codeThatChecks">Code to check the contents of the stream.</param>
+ public void UsingDataContractSerializer(Type type, object objectInstance, Action<Stream> codeThatChecks)
+ {
+ if (type == null)
+ {
+ throw new ArgumentNullException("type");
+ }
+
+ if (codeThatChecks == null)
+ {
+ throw new ArgumentNullException("codeThatChecks");
+ }
+
+ DataContractSerializer serializer = new DataContractSerializer(type);
+
+ using (MemoryStream stream = new MemoryStream())
+ {
+ serializer.WriteObject(stream, objectInstance);
+
+ stream.Flush();
+ stream.Seek(0L, SeekOrigin.Begin);
+
+ codeThatChecks(stream);
+ }
+ }
+
+ /// <summary>
+ /// Creates a <see cref="Stream"/>, serializes <paramref name="objectInstance"/> to it using
+ /// <see cref="DataContractSerializer"/>, rewinds the stream and calls <see cref="codeThatChecks"/>.
+ /// </summary>
+ /// <typeparam name="T">The type to serialize.</typeparam>
+ /// <param name="objectInstance">The value to serialize.</param>
+ /// <param name="codeThatChecks">Code to check the contents of the stream.</param>
+ public void UsingDataContractSerializer<T>(T objectInstance, Action<Stream> codeThatChecks)
+ {
+ UsingDataContractSerializer(typeof(T), objectInstance, codeThatChecks);
+ }
+
+ /// <summary>
+ /// Creates a <see cref="Stream"/>, serializes <paramref name="objectInstance"/> to it using
+ /// <see cref="DataContractJsonSerializer"/>, rewinds the stream and calls <see cref="codeThatChecks"/>.
+ /// </summary>
+ /// <param name="type">The type to serialize. It cannot be <c>null</c>.</param>
+ /// <param name="objectInstance">The value to serialize.</param>
+ /// <param name="codeThatChecks">Code to check the contents of the stream.</param>
+ public static void UsingDataContractJsonSerializer(Type type, object objectInstance, Action<Stream> codeThatChecks)
+ {
+ if (type == null)
+ {
+ throw new ArgumentNullException("type");
+ }
+
+ if (codeThatChecks == null)
+ {
+ throw new ArgumentNullException("codeThatChecks");
+ }
+
+ DataContractJsonSerializer serializer = new DataContractJsonSerializer(type);
+
+ using (MemoryStream stream = new MemoryStream())
+ {
+ serializer.WriteObject(stream, objectInstance);
+
+ stream.Flush();
+ stream.Seek(0L, SeekOrigin.Begin);
+
+ codeThatChecks(stream);
+ }
+ }
+
+ /// <summary>
+ /// Creates a <see cref="Stream"/>, serializes <paramref name="objectInstance"/> to it using
+ /// <see cref="DataContractJsonSerializer"/>, rewinds the stream and calls <see cref="codeThatChecks"/>.
+ /// </summary>
+ /// <typeparam name="T">The type to serialize.</typeparam>
+ /// <param name="objectInstance">The value to serialize.</param>
+ /// <param name="codeThatChecks">Code to check the contents of the stream.</param>
+ public void UsingDataContractJsonSerializer<T>(T objectInstance, Action<Stream> codeThatChecks)
+ {
+ UsingDataContractJsonSerializer(typeof(T), objectInstance, codeThatChecks);
+ }
+ }
+}
diff --git a/test/Microsoft.TestCommon/Microsoft/TestCommon/StreamAssert.cs b/test/Microsoft.TestCommon/Microsoft/TestCommon/StreamAssert.cs
new file mode 100644
index 00000000..5f86df06
--- /dev/null
+++ b/test/Microsoft.TestCommon/Microsoft/TestCommon/StreamAssert.cs
@@ -0,0 +1,94 @@
+using System;
+using System.IO;
+
+namespace Microsoft.TestCommon
+{
+ //// TODO RONCAIN using System.Runtime.Serialization.Json;
+
+ /// <summary>
+ /// MSTest utility for testing code operating against a stream.
+ /// </summary>
+ public class StreamAssert
+ {
+ private static StreamAssert singleton = new StreamAssert();
+
+ public static StreamAssert Singleton { get { return singleton; } }
+
+ /// <summary>
+ /// Creates a <see cref="Stream"/>, invokes <paramref name="codeThatWrites"/> to write to it,
+ /// rewinds the stream to the beginning and invokes <paramref name="codeThatReads"/>.
+ /// </summary>
+ /// <param name="codeThatWrites">Code to write to the stream. It cannot be <c>null</c>.</param>
+ /// <param name="codeThatReads">Code that reads from the stream. It cannot be <c>null</c>.</param>
+ public void WriteAndRead(Action<Stream> codeThatWrites, Action<Stream> codeThatReads)
+ {
+ if (codeThatWrites == null)
+ {
+ throw new ArgumentNullException("codeThatWrites");
+ }
+
+ if (codeThatReads == null)
+ {
+ throw new ArgumentNullException("codeThatReads");
+ }
+
+ using (MemoryStream stream = new MemoryStream())
+ {
+ codeThatWrites(stream);
+
+ stream.Flush();
+ stream.Seek(0L, SeekOrigin.Begin);
+
+ codeThatReads(stream);
+ }
+ }
+
+ /// <summary>
+ /// Creates a <see cref="Stream"/>, invokes <paramref name="codeThatWrites"/> to write to it,
+ /// rewinds the stream to the beginning and invokes <paramref name="codeThatReads"/> to obtain
+ /// the result to return from this method.
+ /// </summary>
+ /// <param name="codeThatWrites">Code to write to the stream. It cannot be <c>null</c>.</param>
+ /// <param name="codeThatReads">Code that reads from the stream and returns the result. It cannot be <c>null</c>.</param>
+ /// <returns>The value returned from <paramref name="codeThatReads"/>.</returns>
+ public static object WriteAndReadResult(Action<Stream> codeThatWrites, Func<Stream, object> codeThatReads)
+ {
+ if (codeThatWrites == null)
+ {
+ throw new ArgumentNullException("codeThatWrites");
+ }
+
+ if (codeThatReads == null)
+ {
+ throw new ArgumentNullException("codeThatReads");
+ }
+
+ object result = null;
+ using (MemoryStream stream = new MemoryStream())
+ {
+ codeThatWrites(stream);
+
+ stream.Flush();
+ stream.Seek(0L, SeekOrigin.Begin);
+
+ result = codeThatReads(stream);
+ }
+
+ return result;
+ }
+
+ /// <summary>
+ /// Creates a <see cref="Stream"/>, invokes <paramref name="codeThatWrites"/> to write to it,
+ /// rewinds the stream to the beginning and invokes <paramref name="codeThatReads"/> to obtain
+ /// the result to return from this method.
+ /// </summary>
+ /// <typeparam name="T">The type of the result expected.</typeparam>
+ /// <param name="codeThatWrites">Code to write to the stream. It cannot be <c>null</c>.</param>
+ /// <param name="codeThatReads">Code that reads from the stream and returns the result. It cannot be <c>null</c>.</param>
+ /// <returns>The value returned from <paramref name="codeThatReads"/>.</returns>
+ public T WriteAndReadResult<T>(Action<Stream> codeThatWrites, Func<Stream, object> codeThatReads)
+ {
+ return (T)WriteAndReadResult(codeThatWrites, codeThatReads);
+ }
+ }
+}
diff --git a/test/Microsoft.TestCommon/Microsoft/TestCommon/TaskAssert.cs b/test/Microsoft.TestCommon/Microsoft/TestCommon/TaskAssert.cs
new file mode 100644
index 00000000..fc3f872a
--- /dev/null
+++ b/test/Microsoft.TestCommon/Microsoft/TestCommon/TaskAssert.cs
@@ -0,0 +1,99 @@
+using System;
+using System.Reflection;
+using System.Threading.Tasks;
+using Xunit;
+
+namespace Microsoft.TestCommon
+{
+ /// <summary>
+ /// MSTest assert class to make assertions about tests using <see cref="Task"/>.
+ /// </summary>
+ public class TaskAssert
+ {
+ private static int timeOutMs = System.Diagnostics.Debugger.IsAttached ? TimeoutConstant.DefaultTimeout : TimeoutConstant.DefaultTimeout * 10;
+ private static TaskAssert singleton = new TaskAssert();
+
+ public static TaskAssert Singleton { get { return singleton; } }
+
+ /// <summary>
+ /// Asserts the given task has been started. TAP guidelines are that all
+ /// <see cref="Task"/> objects returned from public API's have been started.
+ /// </summary>
+ /// <param name="task">The <see cref="Task"/> to test.</param>
+ public void IsStarted(Task task)
+ {
+ Assert.NotNull(task);
+ Assert.True(task.Status != TaskStatus.Created);
+ }
+
+ /// <summary>
+ /// Asserts the given task completes successfully. This method will block the
+ /// current thread waiting for the task, but will timeout if it does not complete.
+ /// </summary>
+ /// <param name="task">The <see cref="Task"/> to test.</param>
+ public void Succeeds(Task task)
+ {
+ IsStarted(task);
+ task.Wait(timeOutMs);
+ AggregateException aggregateException = task.Exception;
+ Exception innerException = aggregateException == null ? null : aggregateException.InnerException;
+ Assert.Null(innerException);
+ }
+
+ /// <summary>
+ /// Asserts the given task completes successfully and returns a result.
+ /// Use this overload for a generic <see cref="Task"/> whose generic parameter is not known at compile time.
+ /// This method will block the current thread waiting for the task, but will timeout if it does not complete.
+ /// </summary>
+ /// <param name="task">The <see cref="Task"/> to test.</param>
+ /// <returns>The result from that task.</returns>
+ public object SucceedsWithResult(Task task)
+ {
+ Succeeds(task);
+ Assert.True(task.GetType().IsGenericType);
+ Type[] genericArguments = task.GetType().GetGenericArguments();
+ Assert.Equal(1, genericArguments.Length);
+ PropertyInfo resultProperty = task.GetType().GetProperty("Result");
+ Assert.NotNull(resultProperty);
+ return resultProperty.GetValue(task, null);
+ }
+
+ /// <summary>
+ /// Asserts the given task completes successfully and returns a <typeparamref name="T"/> result.
+ /// This method will block the current thread waiting for the task, but will timeout if it does not complete.
+ /// </summary>
+ /// <typeparam name="T">The result of the <see cref="Task"/>.</typeparam>
+ /// <param name="task">The <see cref="Task"/> to test.</param>
+ /// <returns>The result from that task.</returns>
+ public T SucceedsWithResult<T>(Task<T> task)
+ {
+ Succeeds(task);
+ return task.Result;
+ }
+
+ /// <summary>
+ /// Asserts the given <see cref="Task"/> completes successfully and yields
+ /// the expected result.
+ /// </summary>
+ /// <param name="task">The <see cref="Task"/> to test.</param>
+ /// <param name="expectedObj">The expected result.</param>
+ public void ResultEquals(Task task, object expectedObj)
+ {
+ object actualObj = SucceedsWithResult(task);
+ Assert.Equal(expectedObj, actualObj);
+ }
+
+ /// <summary>
+ /// Asserts the given <see cref="Task"/> completes successfully and yields
+ /// the expected result.
+ /// </summary>
+ /// <typeparam name="T">The type the task will return.</typeparam>
+ /// <param name="task">The task to test.</param>
+ /// <param name="expectedObj">The expected result.</param>
+ public void ResultEquals<T>(Task<T> task, T expectedObj)
+ {
+ T actualObj = SucceedsWithResult<T>(task);
+ Assert.Equal(expectedObj, actualObj);
+ }
+ }
+}
diff --git a/test/Microsoft.TestCommon/Microsoft/TestCommon/TestDataSetAttribute.cs b/test/Microsoft.TestCommon/Microsoft/TestCommon/TestDataSetAttribute.cs
new file mode 100644
index 00000000..3490a3ca
--- /dev/null
+++ b/test/Microsoft.TestCommon/Microsoft/TestCommon/TestDataSetAttribute.cs
@@ -0,0 +1,193 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection;
+using Xunit.Extensions;
+
+namespace Microsoft.TestCommon
+{
+ public class TestDataSetAttribute : DataAttribute
+ {
+ public Type DeclaringType { get; set; }
+
+ public string PropertyName { get; set; }
+
+ public TestDataVariations TestDataVariations { get; set; }
+
+ private IEnumerable<Tuple<Type, string>> ExtraDataSets { get; set; }
+
+ public TestDataSetAttribute(Type declaringType, string propertyName, TestDataVariations testDataVariations = TestCommon.TestDataVariations.All)
+ {
+ DeclaringType = declaringType;
+ PropertyName = propertyName;
+ TestDataVariations = testDataVariations;
+ ExtraDataSets = new List<Tuple<Type, string>>();
+ }
+
+ public TestDataSetAttribute(Type declaringType, string propertyName,
+ Type declaringType1, string propertyName1,
+ TestDataVariations testDataVariations = TestCommon.TestDataVariations.All)
+ : this(declaringType, propertyName, testDataVariations)
+ {
+ ExtraDataSets = new List<Tuple<Type, string>> { Tuple.Create(declaringType1, propertyName1) };
+ }
+
+ public TestDataSetAttribute(Type declaringType, string propertyName,
+ Type declaringType1, string propertyName1,
+ Type declaringType2, string propertyName2,
+ TestDataVariations testDataVariations = TestCommon.TestDataVariations.All)
+ : this(declaringType, propertyName, testDataVariations)
+ {
+ ExtraDataSets = new List<Tuple<Type, string>> { Tuple.Create(declaringType1, propertyName1), Tuple.Create(declaringType2, propertyName2) };
+ }
+
+ public TestDataSetAttribute(Type declaringType, string propertyName,
+ Type declaringType1, string propertyName1,
+ Type declaringType2, string propertyName2,
+ Type declaringType3, string propertyName3,
+ TestDataVariations testDataVariations = TestCommon.TestDataVariations.All)
+ : this(declaringType, propertyName, testDataVariations)
+ {
+ ExtraDataSets = new List<Tuple<Type, string>> { Tuple.Create(declaringType1, propertyName1), Tuple.Create(declaringType2, propertyName2), Tuple.Create(declaringType3, propertyName3) };
+ }
+
+ public TestDataSetAttribute(Type declaringType, string propertyName,
+ Type declaringType1, string propertyName1,
+ Type declaringType2, string propertyName2,
+ Type declaringType3, string propertyName3,
+ Type declaringType4, string propertyName4,
+ TestDataVariations testDataVariations = TestCommon.TestDataVariations.All)
+ : this(declaringType, propertyName, testDataVariations)
+ {
+ ExtraDataSets = new List<Tuple<Type, string>>
+ {
+ Tuple.Create(declaringType1, propertyName1), Tuple.Create(declaringType2, propertyName2),
+ Tuple.Create(declaringType3, propertyName3), Tuple.Create(declaringType4, propertyName4)
+ };
+ }
+
+ public override IEnumerable<object[]> GetData(MethodInfo methodUnderTest, Type[] parameterTypes)
+ {
+ IEnumerable<object[]> baseDataSet = GetBaseDataSet(DeclaringType, PropertyName, TestDataVariations);
+ IEnumerable<IEnumerable<object[]>> extraDataSets = GetExtraDataSets();
+
+ IEnumerable<IEnumerable<object[]>> finalDataSets = (new[] { baseDataSet }).Concat(extraDataSets);
+
+ var datasets = CrossProduct(finalDataSets);
+
+ return datasets;
+ }
+
+ private static IEnumerable<object[]> CrossProduct(IEnumerable<IEnumerable<object[]>> datasets)
+ {
+ if (datasets.Count() == 1)
+ {
+ foreach (var dataset in datasets.First())
+ {
+ yield return dataset;
+ }
+ }
+ else
+ {
+ IEnumerable<object[]> datasetLeft = datasets.First();
+ IEnumerable<object[]> datasetRight = CrossProduct(datasets.Skip(1));
+
+ foreach (var dataLeft in datasetLeft)
+ {
+ foreach (var dataRight in datasetRight)
+ {
+ yield return dataLeft.Concat(dataRight).ToArray();
+ }
+ }
+ }
+ }
+
+ // The base data set(first one) can either be a TestDataSet or a TestDataSetCollection
+ private static IEnumerable<object[]> GetBaseDataSet(Type declaringType, string propertyName, TestDataVariations variations)
+ {
+ return TryGetDataSetFromTestDataCollection(declaringType, propertyName, variations) ?? GetDataSet(declaringType, propertyName);
+ }
+
+ private IEnumerable<IEnumerable<object[]>> GetExtraDataSets()
+ {
+ foreach (var tuple in ExtraDataSets)
+ {
+ yield return GetDataSet(tuple.Item1, tuple.Item2);
+ }
+ }
+
+ private static object GetTestDataPropertyValue(Type declaringType, string propertyName)
+ {
+ PropertyInfo property = declaringType.GetProperty(propertyName, BindingFlags.Static | BindingFlags.Public);
+
+ if (property == null)
+ {
+ throw new ArgumentException(string.Format("Could not find public static property {0} on {1}", propertyName, declaringType.FullName));
+ }
+ else
+ {
+ return property.GetValue(null, null);
+ }
+ }
+
+ private static IEnumerable<object[]> GetDataSet(Type declaringType, string propertyName)
+ {
+ object propertyValue = GetTestDataPropertyValue(declaringType, propertyName);
+
+ // box the dataset items if the property is not a RefTypeTestData
+ IEnumerable<object> value = (propertyValue as IEnumerable<object>) ?? (propertyValue as IEnumerable).Cast<object>();
+ if (value == null)
+ {
+ throw new InvalidOperationException(string.Format("{0}.{1} is either null or does not implement IEnumerable", declaringType.FullName, propertyName));
+ }
+
+ return value.Select((data) => new object[] { data });
+ }
+
+ private static IEnumerable<object[]> TryGetDataSetFromTestDataCollection(Type declaringType, string propertyName, TestDataVariations variations)
+ {
+ object propertyValue = GetTestDataPropertyValue(declaringType, propertyName);
+
+ IEnumerable<TestData> testDataCollection = propertyValue as IEnumerable<TestData>;
+
+ return testDataCollection == null ? null : GetDataSetFromTestDataCollection(testDataCollection, variations);
+ }
+
+ private static IEnumerable<object[]> GetDataSetFromTestDataCollection(IEnumerable<TestData> testDataCollection, TestDataVariations variations)
+ {
+ foreach (TestData testdataInstance in testDataCollection)
+ {
+ foreach (TestDataVariations variation in testdataInstance.GetSupportedTestDataVariations())
+ {
+ if ((variation & variations) == variation)
+ {
+ Type variationType = testdataInstance.GetAsTypeOrNull(variation);
+ object testData = testdataInstance.GetAsTestDataOrNull(variation);
+ if (AsSingleInstances(variation))
+ {
+ foreach (object obj in (IEnumerable)testData)
+ {
+ yield return new object[] { variationType, obj };
+ }
+ }
+ else
+ {
+ yield return new object[] { variationType, testData };
+ }
+ }
+ }
+ }
+ }
+
+ private static bool AsSingleInstances(TestDataVariations variation)
+ {
+ return variation == TestDataVariations.AsInstance ||
+ variation == TestDataVariations.AsNullable ||
+ variation == TestDataVariations.AsDerivedType ||
+ variation == TestDataVariations.AsKnownType ||
+ variation == TestDataVariations.AsDataMember ||
+ variation == TestDataVariations.AsXmlElementProperty;
+ }
+ }
+}
diff --git a/test/Microsoft.TestCommon/Microsoft/TestCommon/TimeoutConstant.cs b/test/Microsoft.TestCommon/Microsoft/TestCommon/TimeoutConstant.cs
new file mode 100644
index 00000000..a1ad512b
--- /dev/null
+++ b/test/Microsoft.TestCommon/Microsoft/TestCommon/TimeoutConstant.cs
@@ -0,0 +1,20 @@
+namespace Microsoft.TestCommon
+{
+ /// <summary>
+ /// MSTest timeout constants for use with the <see cref="Microsoft.VisualStudio.TestTools.UnitTesting.TimeoutAttribute"/>.
+ /// </summary>
+ public class TimeoutConstant
+ {
+ private const int seconds = 1000;
+
+ /// <summary>
+ /// The default timeout for test methods.
+ /// </summary>
+ public const int DefaultTimeout = 30 * seconds;
+
+ /// <summary>
+ /// An extendedn timeout for longer running test methods.
+ /// </summary>
+ public const int ExtendedTimeout = 240 * seconds;
+ }
+}
diff --git a/test/Microsoft.TestCommon/Microsoft/TestCommon/TypeAssert.cs b/test/Microsoft.TestCommon/Microsoft/TestCommon/TypeAssert.cs
new file mode 100644
index 00000000..9c869849
--- /dev/null
+++ b/test/Microsoft.TestCommon/Microsoft/TestCommon/TypeAssert.cs
@@ -0,0 +1,162 @@
+using System;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace Microsoft.TestCommon
+{
+ /// <summary>
+ /// MSTest utility for testing that a given type has the expected properties such as being public, sealed, etc.
+ /// </summary>
+ public class TypeAssert
+ {
+ /// <summary>
+ /// Specifies a set of type properties to test for using the <see cref="CheckProperty"/> method.
+ /// This enumeration has a <see cref="FlagsAttribute"/> attribute that allows a bitwise combination of its member values.
+ /// </summary>
+ [Flags]
+ public enum TypeProperties
+ {
+ /// <summary>
+ /// Indicates that the type must be abstract.
+ /// </summary>
+ IsAbstract = 0x1,
+
+ /// <summary>
+ /// Indicates that the type must be a class.
+ /// </summary>
+ IsClass = 0x2,
+
+ /// <summary>
+ /// Indicates that the type must be a COM object.
+ /// </summary>
+ IsComObject = 0x4,
+
+ /// <summary>
+ /// Indicates that the type must be disposable.
+ /// </summary>
+ IsDisposable = 0x8,
+
+ /// <summary>
+ /// Indicates that the type must be an enum.
+ /// </summary>
+ IsEnum = 0x10,
+
+ /// <summary>
+ /// Indicates that the type must be a generic type.
+ /// </summary>
+ IsGenericType = 0x20,
+
+ /// <summary>
+ /// Indicates that the type must be a generic type definition.
+ /// </summary>
+ IsGenericTypeDefinition = 0x40,
+
+ /// <summary>
+ /// Indicates that the type must be an interface.
+ /// </summary>
+ IsInterface = 0x80,
+
+ /// <summary>
+ /// Indicates that the type must be nested and declared private.
+ /// </summary>
+ IsNestedPrivate = 0x100,
+
+ /// <summary>
+ /// Indicates that the type must be nested and declared public.
+ /// </summary>
+ IsNestedPublic = 0x200,
+
+ /// <summary>
+ /// Indicates that the type must be public.
+ /// </summary>
+ IsPublic = 0x400,
+
+ /// <summary>
+ /// Indicates that the type must be sealed.
+ /// </summary>
+ IsSealed = 0x800,
+
+ /// <summary>
+ /// Indicates that the type must be visible outside the assembly.
+ /// </summary>
+ IsVisible = 0x1000,
+
+ /// <summary>
+ /// Indicates that the type must be static.
+ /// </summary>
+ IsStatic = TypeAssert.TypeProperties.IsAbstract | TypeAssert.TypeProperties.IsSealed,
+
+ /// <summary>
+ /// Indicates that the type must be a public, visible class.
+ /// </summary>
+ IsPublicVisibleClass = TypeAssert.TypeProperties.IsClass | TypeAssert.TypeProperties.IsPublic | TypeAssert.TypeProperties.IsVisible
+ }
+
+ private static void CheckProperty(Type type, bool expected, bool actual, string property)
+ {
+ Assert.NotNull(type);
+ Assert.True(expected == actual, String.Format("Type '{0}' should{1} be {2}.", type.FullName, expected ? "" : " NOT", property));
+ }
+
+ /// <summary>
+ /// Determines whether the specified type has a given set of properties such as being public, sealed, etc.
+ /// The method asserts if one or more of the properties are not satisfied.
+ /// </summary>
+ /// <typeparam name="T">The type to test for properties.</typeparam>
+ /// <param name="typeProperties">The set of type properties to test for.</param>
+ public void HasProperties<T>(TypeProperties typeProperties)
+ {
+ HasProperties(typeof(T), typeProperties);
+ }
+
+ /// <summary>
+ /// Determines whether the specified type has a given set of properties such as being public, sealed, etc.
+ /// The method asserts if one or more of the properties are not satisfied.
+ /// </summary>
+ /// <typeparam name="T">The type to test for properties.</typeparam>
+ /// <typeparam name="TIsAssignableFrom">Verify that the type to test is assignable from this type.</typeparam>
+ /// <param name="typeProperties">The set of type properties to test for.</param>
+ public void HasProperties<T, TIsAssignableFrom>(TypeProperties typeProperties)
+ {
+ HasProperties(typeof(T), typeProperties, typeof(TIsAssignableFrom));
+ }
+
+ /// <summary>
+ /// Determines whether the specified type has a given set of properties such as being public, sealed, etc.
+ /// The method asserts if one or more of the properties are not satisfied.
+ /// </summary>
+ /// <param name="type">The type to test for properties.</param>
+ /// <param name="typeProperties">The set of type properties to test for.</param>
+ public void HasProperties(Type type, TypeProperties typeProperties)
+ {
+ HasProperties(type, typeProperties, null);
+ }
+
+ /// <summary>
+ /// Determines whether the specified type has a given set of properties such as being public, sealed, etc.
+ /// The method asserts if one or more of the properties are not satisfied.
+ /// </summary>
+ /// <param name="type">The type to test for properties.</param>
+ /// <param name="typeProperties">The set of type properties to test for.</param>
+ /// <param name="isAssignableFrom">Verify that the type to test is assignable from this type.</param>
+ public void HasProperties(Type type, TypeProperties typeProperties, Type isAssignableFrom)
+ {
+ TypeAssert.CheckProperty(type, (typeProperties & TypeProperties.IsAbstract) > 0, type.IsAbstract, "abstract");
+ TypeAssert.CheckProperty(type, (typeProperties & TypeProperties.IsClass) > 0, type.IsClass, "a class");
+ TypeAssert.CheckProperty(type, (typeProperties & TypeProperties.IsComObject) > 0, type.IsCOMObject, "a COM object");
+ TypeAssert.CheckProperty(type, (typeProperties & TypeProperties.IsDisposable) > 0, typeof(IDisposable).IsAssignableFrom(type), "disposable");
+ TypeAssert.CheckProperty(type, (typeProperties & TypeProperties.IsEnum) > 0, type.IsEnum, "an enum");
+ TypeAssert.CheckProperty(type, (typeProperties & TypeProperties.IsGenericType) > 0, type.IsGenericType, "a generic type");
+ TypeAssert.CheckProperty(type, (typeProperties & TypeProperties.IsGenericTypeDefinition) > 0, type.IsGenericTypeDefinition, "a generic type definition");
+ TypeAssert.CheckProperty(type, (typeProperties & TypeProperties.IsInterface) > 0, type.IsInterface, "an interface");
+ TypeAssert.CheckProperty(type, (typeProperties & TypeProperties.IsNestedPrivate) > 0, type.IsNestedPrivate, "nested private");
+ TypeAssert.CheckProperty(type, (typeProperties & TypeProperties.IsNestedPublic) > 0, type.IsNestedPublic, "nested public");
+ TypeAssert.CheckProperty(type, (typeProperties & TypeProperties.IsPublic) > 0, type.IsPublic, "public");
+ TypeAssert.CheckProperty(type, (typeProperties & TypeProperties.IsSealed) > 0, type.IsSealed, "sealed");
+ TypeAssert.CheckProperty(type, (typeProperties & TypeProperties.IsVisible) > 0, type.IsVisible, "visible");
+ if (isAssignableFrom != null)
+ {
+ TypeAssert.CheckProperty(type, true, isAssignableFrom.IsAssignableFrom(type), String.Format("assignable from {0}", isAssignableFrom.FullName));
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/test/Microsoft.TestCommon/Microsoft/TestCommon/Types/FlagsEnum.cs b/test/Microsoft.TestCommon/Microsoft/TestCommon/Types/FlagsEnum.cs
new file mode 100644
index 00000000..b2e7548d
--- /dev/null
+++ b/test/Microsoft.TestCommon/Microsoft/TestCommon/Types/FlagsEnum.cs
@@ -0,0 +1,12 @@
+using System;
+
+namespace Microsoft.TestCommon.Types
+{
+ [Flags]
+ public enum FlagsEnum
+ {
+ One = 0x1,
+ Two = 0x2,
+ Four = 0x4
+ }
+}
diff --git a/test/Microsoft.TestCommon/Microsoft/TestCommon/Types/INameAndIdContainer.cs b/test/Microsoft.TestCommon/Microsoft/TestCommon/Types/INameAndIdContainer.cs
new file mode 100644
index 00000000..708daa78
--- /dev/null
+++ b/test/Microsoft.TestCommon/Microsoft/TestCommon/Types/INameAndIdContainer.cs
@@ -0,0 +1,12 @@
+namespace Microsoft.TestCommon.Types
+{
+ /// <summary>
+ /// Tagging interface to assist comparing instances of these types.
+ /// </summary>
+ public interface INameAndIdContainer
+ {
+ string Name { get; set; }
+
+ int Id { get; set; }
+ }
+}
diff --git a/test/Microsoft.TestCommon/Microsoft/TestCommon/Types/ISerializableType.cs b/test/Microsoft.TestCommon/Microsoft/TestCommon/Types/ISerializableType.cs
new file mode 100644
index 00000000..e749ed94
--- /dev/null
+++ b/test/Microsoft.TestCommon/Microsoft/TestCommon/Types/ISerializableType.cs
@@ -0,0 +1,73 @@
+using System;
+using System.Collections.Generic;
+using System.Runtime.Serialization;
+
+namespace Microsoft.TestCommon.Types
+{
+ [Serializable]
+ public class ISerializableType : ISerializable, INameAndIdContainer
+ {
+ private int id;
+ private string name;
+
+ public ISerializableType()
+ {
+ }
+
+ public ISerializableType(int id, string name)
+ {
+ this.id = id;
+ this.name = name;
+ }
+
+ public ISerializableType(SerializationInfo information, StreamingContext context)
+ {
+ this.id = information.GetInt32("Id");
+ this.name = information.GetString("Name");
+ }
+
+ public int Id
+ {
+ get
+ {
+ return this.id;
+ }
+
+ set
+ {
+ this.IdSet = true;
+ this.id = value;
+ }
+ }
+
+ public string Name
+ {
+ get
+ {
+ return this.name;
+ }
+
+ set
+ {
+ this.NameSet = true;
+ this.name = value;
+ }
+
+ }
+
+ public bool IdSet { get; private set; }
+
+ public bool NameSet { get; private set; }
+
+ public void GetObjectData(SerializationInfo info, StreamingContext context)
+ {
+ info.AddValue("Id", this.Id);
+ info.AddValue("Name", this.Name);
+ }
+
+ public static IEnumerable<ISerializableType> GetTestData()
+ {
+ return new ISerializableType[] { new ISerializableType(), new ISerializableType(1, "SomeName") };
+ }
+ }
+}
diff --git a/test/Microsoft.TestCommon/Microsoft/TestCommon/Types/LongEnum.cs b/test/Microsoft.TestCommon/Microsoft/TestCommon/Types/LongEnum.cs
new file mode 100644
index 00000000..df94d84e
--- /dev/null
+++ b/test/Microsoft.TestCommon/Microsoft/TestCommon/Types/LongEnum.cs
@@ -0,0 +1,9 @@
+namespace Microsoft.TestCommon.Types
+{
+ public enum LongEnum : long
+ {
+ FirstLong,
+ SecondLong,
+ ThirdLong
+ }
+}
diff --git a/test/Microsoft.TestCommon/Microsoft/TestCommon/Types/SimpleEnum.cs b/test/Microsoft.TestCommon/Microsoft/TestCommon/Types/SimpleEnum.cs
new file mode 100644
index 00000000..8bff4843
--- /dev/null
+++ b/test/Microsoft.TestCommon/Microsoft/TestCommon/Types/SimpleEnum.cs
@@ -0,0 +1,9 @@
+namespace Microsoft.TestCommon.Types
+{
+ public enum SimpleEnum
+ {
+ First,
+ Second,
+ Third
+ }
+}
diff --git a/test/Microsoft.TestCommon/Microsoft/TestCommon/XmlAssert.cs b/test/Microsoft.TestCommon/Microsoft/TestCommon/XmlAssert.cs
new file mode 100644
index 00000000..52d8fe9c
--- /dev/null
+++ b/test/Microsoft.TestCommon/Microsoft/TestCommon/XmlAssert.cs
@@ -0,0 +1,77 @@
+using System.Collections.Generic;
+using System.Linq;
+using System.Web;
+using System.Xml.Linq;
+using Xunit;
+
+namespace Microsoft.TestCommon
+{
+ /// <summary>
+ /// Assert class that compares two XML strings for equality. Namespaces are ignored during comparison
+ /// </summary>
+ public class XmlAssert
+ {
+ public void Equal(string expected, string actual, params RegexReplacement[] regexReplacements)
+ {
+ if (regexReplacements != null)
+ {
+ for (int i = 0; i < regexReplacements.Length; i++)
+ {
+ actual = regexReplacements[i].Regex.Replace(actual, regexReplacements[i].Replacement);
+ }
+ }
+
+ Equal(XElement.Parse(expected), XElement.Parse(actual));
+ }
+
+ public void Equal(XElement expected, XElement actual)
+ {
+ Assert.Equal(Normalize(expected).ToString(), Normalize(actual).ToString());
+ }
+
+ private static XElement Normalize(XElement element)
+ {
+ if (element.HasElements)
+ {
+ return new XElement(
+ Encode(element.Name),
+ Normalize(element.Attributes()),
+ Normalize(element.Elements()));
+ }
+
+ if (element.IsEmpty)
+ {
+ return new XElement(
+ Encode(element.Name),
+ Normalize(element.Attributes()));
+ }
+ else
+ {
+ return new XElement(
+ Encode(element.Name),
+ Normalize(element.Attributes()),
+ element.Value);
+ }
+ }
+
+ private static IEnumerable<XAttribute> Normalize(IEnumerable<XAttribute> attributes)
+ {
+ return attributes
+ .Where((attrib) => !attrib.IsNamespaceDeclaration)
+ .Select((attrib) => new XAttribute(Encode(attrib.Name), attrib.Value))
+ .OrderBy(a => a.Name.ToString());
+ }
+
+ private static IEnumerable<XElement> Normalize(IEnumerable<XElement> elements)
+ {
+ return elements
+ .Select(e => Normalize(e))
+ .OrderBy(a => a.ToString());
+ }
+
+ private static string Encode(XName name)
+ {
+ return string.Format("{0}_{1}", HttpUtility.UrlEncode(name.NamespaceName).Replace('%', '_'), name.LocalName);
+ }
+ }
+} \ No newline at end of file
diff --git a/test/Microsoft.TestCommon/PreAppStartTestHelper.cs b/test/Microsoft.TestCommon/PreAppStartTestHelper.cs
new file mode 100644
index 00000000..79f90320
--- /dev/null
+++ b/test/Microsoft.TestCommon/PreAppStartTestHelper.cs
@@ -0,0 +1,25 @@
+using System.ComponentModel;
+using System.Reflection;
+using Xunit;
+
+namespace System.Web.WebPages.TestUtils
+{
+ public static class PreAppStartTestHelper
+ {
+ public static void TestPreAppStartClass(Type preAppStartType)
+ {
+ string typeMessage = String.Format("The type '{0}' must be static, public, and named 'PreApplicationStartCode'.", preAppStartType.FullName);
+ Assert.True(preAppStartType.IsSealed && preAppStartType.IsAbstract && preAppStartType.IsPublic && preAppStartType.Name == "PreApplicationStartCode", typeMessage);
+
+ string editorBrowsableMessage = String.Format("The only attribute on type '{0}' must be [EditorBrowsable(EditorBrowsableState.Never)].", preAppStartType.FullName);
+ object[] attrs = preAppStartType.GetCustomAttributes(typeof(EditorBrowsableAttribute), true);
+ Assert.True(attrs.Length == 1 && ((EditorBrowsableAttribute)attrs[0]).State == EditorBrowsableState.Never, editorBrowsableMessage);
+
+ string startMethodMessage = String.Format("The only public member on type '{0}' must be a method called Start().", preAppStartType.FullName);
+ MemberInfo[] publicMembers = preAppStartType.GetMembers(BindingFlags.Public | BindingFlags.Static);
+ Assert.True(publicMembers.Length == 1, startMethodMessage);
+ Assert.True(publicMembers[0].MemberType == MemberTypes.Method, startMethodMessage);
+ Assert.True(publicMembers[0].Name == "Start", startMethodMessage);
+ }
+ }
+}
diff --git a/test/Microsoft.TestCommon/PreserveSyncContextAttribute.cs b/test/Microsoft.TestCommon/PreserveSyncContextAttribute.cs
new file mode 100644
index 00000000..f47e9b62
--- /dev/null
+++ b/test/Microsoft.TestCommon/PreserveSyncContextAttribute.cs
@@ -0,0 +1,24 @@
+using System.Threading;
+using Xunit;
+
+namespace Microsoft.TestCommon
+{
+ /// <summary>
+ /// Preserves the current <see cref="SynchronizationContext"/>. Use this attribute on
+ /// tests which modify the current <see cref="SynchronizationContext"/>.
+ /// </summary>
+ public class PreserveSyncContextAttribute : BeforeAfterTestAttribute
+ {
+ private SynchronizationContext _syncContext;
+
+ public override void Before(System.Reflection.MethodInfo methodUnderTest)
+ {
+ _syncContext = SynchronizationContext.Current;
+ }
+
+ public override void After(System.Reflection.MethodInfo methodUnderTest)
+ {
+ SynchronizationContext.SetSynchronizationContext(_syncContext);
+ }
+ }
+}
diff --git a/test/Microsoft.TestCommon/Properties/AssemblyInfo.cs b/test/Microsoft.TestCommon/Properties/AssemblyInfo.cs
new file mode 100644
index 00000000..46cecb32
--- /dev/null
+++ b/test/Microsoft.TestCommon/Properties/AssemblyInfo.cs
@@ -0,0 +1,35 @@
+using System.Reflection;
+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("Microsoft.TestCommon")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("Microsoft")]
+[assembly: AssemblyProduct("Microsoft.TestCommon")]
+[assembly: AssemblyCopyright("Copyright © Microsoft 2011")]
+[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("d128ebbb-a536-472f-8f8a-0fcb0966624e")]
+
+// 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.*")]
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
diff --git a/test/Microsoft.TestCommon/ReflectionAssert.cs b/test/Microsoft.TestCommon/ReflectionAssert.cs
new file mode 100644
index 00000000..a2b3eb59
--- /dev/null
+++ b/test/Microsoft.TestCommon/ReflectionAssert.cs
@@ -0,0 +1,115 @@
+using System;
+using System.Linq.Expressions;
+using System.Reflection;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace Microsoft.TestCommon
+{
+ public class ReflectionAssert
+ {
+ private static PropertyInfo GetPropertyInfo<T, TProp>(Expression<Func<T, TProp>> property)
+ {
+ if (property.Body is MemberExpression)
+ {
+ return (PropertyInfo)((MemberExpression)property.Body).Member;
+ }
+ else if (property.Body is UnaryExpression && property.Body.NodeType == ExpressionType.Convert)
+ {
+ return (PropertyInfo)((MemberExpression)((UnaryExpression)property.Body).Operand).Member;
+ }
+ else
+ {
+ throw new InvalidOperationException("Could not determine property from lambda expression.");
+ }
+ }
+
+ private static void TestPropertyValue<TInstance, TValue>(TInstance instance, Func<TInstance, TValue> getFunc, Action<TInstance, TValue> setFunc, TValue valueToSet, TValue valueToCheck)
+ {
+ setFunc(instance, valueToSet);
+ TValue newValue = getFunc(instance);
+ Assert.Equal(valueToCheck, newValue);
+ }
+
+ private static void TestPropertyValue<TInstance, TValue>(TInstance instance, Func<TInstance, TValue> getFunc, Action<TInstance, TValue> setFunc, TValue value)
+ {
+ TestPropertyValue(instance, getFunc, setFunc, value, value);
+ }
+
+ public void Property<T, TResult>(T instance, Expression<Func<T, TResult>> propertyGetter, TResult expectedDefaultValue, bool allowNull = false, TResult roundTripTestValue = null) where TResult : class
+ {
+ PropertyInfo property = GetPropertyInfo(propertyGetter);
+ Func<T, TResult> getFunc = (obj) => (TResult)property.GetValue(obj, index: null);
+ Action<T, TResult> setFunc = (obj, value) => property.SetValue(obj, value, index: null);
+
+ Assert.Equal(expectedDefaultValue, getFunc(instance));
+
+ if (allowNull)
+ {
+ TestPropertyValue(instance, getFunc, setFunc, null);
+ }
+ else
+ {
+ Assert.ThrowsArgumentNull(() =>
+ {
+ setFunc(instance, null);
+ }, "value");
+ }
+
+ if (roundTripTestValue != null)
+ {
+ TestPropertyValue(instance, getFunc, setFunc, roundTripTestValue);
+ }
+ }
+
+ public void StringProperty<T>(T instance, Expression<Func<T, string>> propertyGetter, string expectedDefaultValue,
+ bool allowNullAndEmpty = true, string nullAndEmptyReturnValue = "")
+ {
+ PropertyInfo property = GetPropertyInfo(propertyGetter);
+ Func<T, string> getFunc = (obj) => (string)property.GetValue(obj, index: null);
+ Action<T, string> setFunc = (obj, value) => property.SetValue(obj, value, index: null);
+
+ Assert.Equal(expectedDefaultValue, getFunc(instance));
+
+ if (allowNullAndEmpty)
+ {
+ // Assert get/set works for null
+ TestPropertyValue(instance, getFunc, setFunc, null, nullAndEmptyReturnValue);
+
+ // Assert get/set works for String.Empty
+ TestPropertyValue(instance, getFunc, setFunc, String.Empty, nullAndEmptyReturnValue);
+ }
+ else
+ {
+ Assert.ThrowsArgumentNullOrEmpty(
+ delegate()
+ {
+ try
+ {
+ TestPropertyValue(instance, getFunc, setFunc, null);
+ }
+ catch (TargetInvocationException e)
+ {
+ throw e.InnerException;
+ }
+ },
+ "value");
+ Assert.ThrowsArgumentNullOrEmpty(
+ delegate()
+ {
+ try
+ {
+ TestPropertyValue(instance, getFunc, setFunc, String.Empty);
+ }
+ catch (TargetInvocationException e)
+ {
+ throw e.InnerException;
+ }
+ },
+ "value");
+ }
+
+ // Assert get/set works for arbitrary value
+ TestPropertyValue(instance, getFunc, setFunc, "TestValue");
+ }
+ }
+}
diff --git a/test/Microsoft.TestCommon/TaskExtensions.cs b/test/Microsoft.TestCommon/TaskExtensions.cs
new file mode 100644
index 00000000..b0cf4aa9
--- /dev/null
+++ b/test/Microsoft.TestCommon/TaskExtensions.cs
@@ -0,0 +1,25 @@
+using System;
+using System.Threading.Tasks;
+
+// No namespace so that these extensions are available for all test classes
+
+public static class TaskExtensions
+{
+ /// <summary>
+ /// Waits until the given task finishes executing and completes in any of the 3 states.
+ /// </summary>
+ /// <param name="task">A task</param>
+ public static void WaitUntilCompleted(this Task task)
+ {
+ if (task == null) throw new ArgumentNullException("task");
+ task.ContinueWith(prev =>
+ {
+ if (prev.IsFaulted)
+ {
+ // Observe the exception in the faulted case to avoid an unobserved exception leaking and
+ // killing the thread finalizer.
+ var e = prev.Exception;
+ }
+ }, TaskContinuationOptions.ExecuteSynchronously).Wait();
+ }
+}
diff --git a/test/Microsoft.TestCommon/TestFile.cs b/test/Microsoft.TestCommon/TestFile.cs
new file mode 100644
index 00000000..b77236db
--- /dev/null
+++ b/test/Microsoft.TestCommon/TestFile.cs
@@ -0,0 +1,73 @@
+using System.IO;
+using System.Reflection;
+using Xunit;
+
+namespace System.Web.WebPages.TestUtils
+{
+ public class TestFile
+ {
+ public const string ResourceNameFormat = "{0}.TestFiles.{1}";
+
+ public string ResourceName { get; set; }
+ public Assembly Assembly { get; set; }
+
+ public TestFile(string resName, Assembly asm)
+ {
+ ResourceName = resName;
+ Assembly = asm;
+ }
+
+ public static TestFile Create(string localResourceName)
+ {
+ return new TestFile(String.Format(ResourceNameFormat, Assembly.GetCallingAssembly().GetName().Name, localResourceName), Assembly.GetCallingAssembly());
+ }
+
+ public Stream OpenRead()
+ {
+ Stream strm = Assembly.GetManifestResourceStream(ResourceName);
+ if (strm == null)
+ {
+ Assert.True(false, String.Format("Manifest resource: {0} not found", ResourceName));
+ }
+ return strm;
+ }
+
+ public byte[] ReadAllBytes()
+ {
+ using (Stream stream = OpenRead())
+ {
+ byte[] buffer = new byte[stream.Length];
+ stream.Read(buffer, 0, buffer.Length);
+ return buffer;
+ }
+ }
+
+ public string ReadAllText()
+ {
+ using (StreamReader reader = new StreamReader(OpenRead()))
+ {
+ return reader.ReadToEnd();
+ }
+ }
+
+ /// <summary>
+ /// Saves the file to the specified path.
+ /// </summary>
+ public void Save(string filePath)
+ {
+ var directory = Path.GetDirectoryName(filePath);
+ if (!Directory.Exists(directory))
+ {
+ Directory.CreateDirectory(directory);
+ }
+
+ using (Stream outStream = File.Create(filePath))
+ {
+ using (Stream inStream = OpenRead())
+ {
+ inStream.CopyTo(outStream);
+ }
+ }
+ }
+ }
+}
diff --git a/test/Microsoft.TestCommon/TestHelper.cs b/test/Microsoft.TestCommon/TestHelper.cs
new file mode 100644
index 00000000..7a59d370
--- /dev/null
+++ b/test/Microsoft.TestCommon/TestHelper.cs
@@ -0,0 +1,29 @@
+using System.Globalization;
+using System.Linq;
+using Xunit;
+
+namespace System.Web.TestUtil
+{
+ public static class UnitTestHelper
+ {
+ public static bool EnglishBuildAndOS
+ {
+ get
+ {
+ bool englishBuild = String.Equals(CultureInfo.CurrentCulture.TwoLetterISOLanguageName, "en",
+ StringComparison.OrdinalIgnoreCase);
+ bool englishOS = String.Equals(CultureInfo.CurrentCulture.TwoLetterISOLanguageName, "en",
+ StringComparison.OrdinalIgnoreCase);
+ return englishBuild && englishOS;
+ }
+ }
+
+ public static void AssertEqualsIgnoreWhitespace(string expected, string actual)
+ {
+ expected = new String(expected.Where(c => !Char.IsWhiteSpace(c)).ToArray());
+ actual = new String(actual.Where(c => !Char.IsWhiteSpace(c)).ToArray());
+
+ Assert.Equal(expected, actual, StringComparer.OrdinalIgnoreCase);
+ }
+ }
+}
diff --git a/test/Microsoft.TestCommon/TheoryDataSet.cs b/test/Microsoft.TestCommon/TheoryDataSet.cs
new file mode 100644
index 00000000..2fa4b34a
--- /dev/null
+++ b/test/Microsoft.TestCommon/TheoryDataSet.cs
@@ -0,0 +1,86 @@
+using System.Collections;
+using System.Collections.Generic;
+
+namespace Microsoft.TestCommon
+{
+ /// <summary>
+ /// Helper class for generating test data for XUnit's <see cref="Xunit.Extensions.TheoryAttribute"/>-based tests.
+ /// Should be used in combination with <see cref="Xunit.Extensions.PropertyDataAttribute"/>.
+ /// </summary>
+ /// <typeparam name="TParam">First parameter type</typeparam>
+ public class TheoryDataSet<TParam> : TheoryDataSet
+ {
+ public void Add(TParam p)
+ {
+ AddItem(p);
+ }
+ }
+
+ /// <summary>
+ /// Helper class for generating test data for XUnit's <see cref="Xunit.Extensions.TheoryAttribute"/>-based tests.
+ /// Should be used in combination with <see cref="Xunit.Extensions.PropertyDataAttribute"/>.
+ /// </summary>
+ /// <typeparam name="TParam1">First parameter type</typeparam>
+ /// <typeparam name="TParam2">Second parameter type</typeparam>
+ public class TheoryDataSet<TParam1, TParam2> : TheoryDataSet
+ {
+ public void Add(TParam1 p1, TParam2 p2)
+ {
+ AddItem(p1, p2);
+ }
+ }
+
+ /// <summary>
+ /// Helper class for generating test data for XUnit's <see cref="Xunit.Extensions.TheoryAttribute"/>-based tests.
+ /// Should be used in combination with <see cref="Xunit.Extensions.PropertyDataAttribute"/>.
+ /// </summary>
+ /// <typeparam name="TParam1">First parameter type</typeparam>
+ /// <typeparam name="TParam2">Second parameter type</typeparam>
+ /// <typeparam name="TParam3">Third parameter type</typeparam>
+ public class TheoryDataSet<TParam1, TParam2, TParam3> : TheoryDataSet
+ {
+ public void Add(TParam1 p1, TParam2 p2, TParam3 p3)
+ {
+ AddItem(p1, p2, p3);
+ }
+ }
+
+ /// <summary>
+ /// Helper class for generating test data for XUnit's <see cref="Xunit.Extensions.TheoryAttribute"/>-based tests.
+ /// Should be used in combination with <see cref="Xunit.Extensions.PropertyDataAttribute"/>.
+ /// </summary>
+ /// <typeparam name="TParam1">First parameter type</typeparam>
+ /// <typeparam name="TParam2">Second parameter type</typeparam>
+ /// <typeparam name="TParam3">Third parameter type</typeparam>
+ /// <typeparam name="TParam4">Fourth parameter type</typeparam>
+ public class TheoryDataSet<TParam1, TParam2, TParam3, TParam4> : TheoryDataSet
+ {
+ public void Add(TParam1 p1, TParam2 p2, TParam3 p3, TParam4 p4)
+ {
+ AddItem(p1, p2, p3, p4);
+ }
+ }
+
+ /// <summary>
+ /// Base class for <c>TheoryDataSet</c> classes.
+ /// </summary>
+ public abstract class TheoryDataSet : IEnumerable<object[]>
+ {
+ private readonly List<object[]> data = new List<object[]>();
+
+ protected void AddItem(params object[] values)
+ {
+ data.Add(values);
+ }
+
+ public IEnumerator<object[]> GetEnumerator()
+ {
+ return data.GetEnumerator();
+ }
+
+ IEnumerator IEnumerable.GetEnumerator()
+ {
+ return GetEnumerator();
+ }
+ }
+}
diff --git a/test/Microsoft.TestCommon/ThreadPoolSyncContext.cs b/test/Microsoft.TestCommon/ThreadPoolSyncContext.cs
new file mode 100644
index 00000000..63552323
--- /dev/null
+++ b/test/Microsoft.TestCommon/ThreadPoolSyncContext.cs
@@ -0,0 +1,31 @@
+using System.Threading;
+
+namespace Microsoft.TestCommon
+{
+ /// <summary>
+ /// This is an implementation of SynchronizationContext that not only queues things on the thread pool for
+ /// later work, but also ensures that it sets itself back as the synchronization context (something that the
+ /// default implementatation of SynchronizationContext does not do).
+ /// </summary>
+ public class ThreadPoolSyncContext : SynchronizationContext
+ {
+ public override void Post(SendOrPostCallback d, object state)
+ {
+ ThreadPool.QueueUserWorkItem(_ =>
+ {
+ SynchronizationContext oldContext = SynchronizationContext.Current;
+ SynchronizationContext.SetSynchronizationContext(this);
+ d.Invoke(state);
+ SynchronizationContext.SetSynchronizationContext(oldContext);
+ }, null);
+ }
+
+ public override void Send(SendOrPostCallback d, object state)
+ {
+ SynchronizationContext oldContext = SynchronizationContext.Current;
+ SynchronizationContext.SetSynchronizationContext(this);
+ d.Invoke(state);
+ SynchronizationContext.SetSynchronizationContext(oldContext);
+ }
+ }
+}
diff --git a/test/Microsoft.TestCommon/WebUtils.cs b/test/Microsoft.TestCommon/WebUtils.cs
new file mode 100644
index 00000000..070fe219
--- /dev/null
+++ b/test/Microsoft.TestCommon/WebUtils.cs
@@ -0,0 +1,87 @@
+using System.IO;
+using System.Reflection;
+using System.Web.UI;
+
+namespace System.Web.WebPages.TestUtils
+{
+ public static class WebUtils
+ {
+ /// <summary>
+ /// Creates an instance of HttpRuntime and assigns it (using magic) to the singleton instance of HttpRuntime.
+ /// Ensure that the returned value is disposed at the end of the test.
+ /// </summary>
+ /// <returns>Returns an IDisposable that restores the original HttpRuntime.</returns>
+ public static IDisposable CreateHttpRuntime(string appVPath, string appPath = null)
+ {
+ var runtime = new HttpRuntime();
+ var appDomainAppVPathField = typeof(HttpRuntime).GetField("_appDomainAppVPath", BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance);
+ appDomainAppVPathField.SetValue(runtime, CreateVirtualPath(appVPath));
+
+ if (appPath != null)
+ {
+ var appDomainAppPathField = typeof(HttpRuntime).GetField("_appDomainAppPath", BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance);
+ appDomainAppPathField.SetValue(runtime, Path.GetFullPath(appPath));
+ }
+
+ GetTheRuntime().SetValue(null, runtime);
+ var appDomainIdField = typeof(HttpRuntime).GetField("_appDomainId", BindingFlags.NonPublic | BindingFlags.Instance);
+ appDomainIdField.SetValue(runtime, "test");
+
+ return new DisposableAction(RestoreHttpRuntime);
+ }
+
+ internal static FieldInfo GetTheRuntime()
+ {
+ return typeof(HttpRuntime).GetField("_theRuntime", BindingFlags.NonPublic | BindingFlags.Static);
+ }
+
+ internal static void RestoreHttpRuntime()
+ {
+ GetTheRuntime().SetValue(null, null);
+ }
+
+ internal static object CreateVirtualPath(string path)
+ {
+ var vPath = typeof(Page).Assembly.GetType("System.Web.VirtualPath");
+ var method = vPath.GetMethod("CreateNonRelativeTrailingSlash", BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public);
+ return method.Invoke(null, new object[] { path });
+ }
+
+ private class DisposableAction : IDisposable
+ {
+ private Action _action;
+ private bool _hasDisposed;
+
+ public DisposableAction(Action action)
+ {
+ if (action == null)
+ {
+ throw new ArgumentNullException("action");
+ }
+ _action = action;
+ }
+
+ public void Dispose()
+ {
+ Dispose(true);
+ GC.SuppressFinalize(this);
+ }
+
+ protected virtual void Dispose(bool disposing)
+ {
+ // If we were disposed by the finalizer it's because the user didn't use a "using" block, so don't do anything!
+ if (disposing)
+ {
+ lock (this)
+ {
+ if (!_hasDisposed)
+ {
+ _hasDisposed = true;
+ _action();
+ }
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/test/Microsoft.TestCommon/packages.config b/test/Microsoft.TestCommon/packages.config
new file mode 100644
index 00000000..684e330f
--- /dev/null
+++ b/test/Microsoft.TestCommon/packages.config
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<packages>
+ <package id="Microsoft.Net.Http" version="2.0.20302.1" />
+ <package id="xunit" version="1.9.0.1566" />
+ <package id="xunit.extensions" version="1.9.0.1566" />
+</packages> \ No newline at end of file
diff --git a/test/Microsoft.Web.Helpers.Test/AnalyticsTest.cs b/test/Microsoft.Web.Helpers.Test/AnalyticsTest.cs
new file mode 100644
index 00000000..92c738fd
--- /dev/null
+++ b/test/Microsoft.Web.Helpers.Test/AnalyticsTest.cs
@@ -0,0 +1,133 @@
+using System.Web.TestUtil;
+using Xunit;
+
+namespace Microsoft.Web.Helpers.Test
+{
+ /// <summary>
+ ///This is a test class for AnalyticsTest and is intended
+ ///to contain all AnalyticsTest Unit Tests
+ ///</summary>
+ public class AnalyticsTest
+ {
+ /// <summary>
+ ///A test for GetYahooAnalyticsHtml
+ ///</summary>
+ [Fact]
+ public void GetYahooAnalyticsHtmlTest()
+ {
+ string account = "My_yahoo_account";
+ string actual = Analytics.GetYahooHtml(account).ToString();
+ Assert.True(actual.Contains(".yahoo.com") && actual.Contains("My_yahoo_account"));
+ }
+
+ /// <summary>
+ ///A test for GetStatCounterAnalyticsHtml
+ ///</summary>
+ [Fact]
+ public void GetStatCounterAnalyticsHtmlTest()
+ {
+ int project = 31553;
+ string security = "stat_security";
+ string actual = Analytics.GetStatCounterHtml(project, security).ToString();
+ Assert.True(actual.Contains("statcounter.com/counter/counter_xhtml.js") &&
+ actual.Contains(project.ToString()) && actual.Contains(security));
+ }
+
+ /// <summary>
+ ///A test for GetGoogleAnalyticsHtml
+ ///</summary>
+ [Fact]
+ public void GetGoogleAnalyticsHtmlTest()
+ {
+ string account = "My_google_account";
+ string actual = Analytics.GetGoogleHtml(account).ToString();
+ Assert.True(actual.Contains("google-analytics.com/ga.js") && actual.Contains("My_google_account"));
+ }
+
+ [Fact]
+ public void GetGoogleAnalyticsEscapesJavascript()
+ {
+ string account = "My_\"google_account";
+ string actual = Analytics.GetGoogleHtml(account).ToString();
+ string expected = "<script type=\"text/javascript\">\n" +
+ "var gaJsHost = ((\"https:\" == document.location.protocol) ? \"https://ssl.\" : \"http://www.\");\n" +
+ "document.write(unescape(\"%3Cscript src='\" + gaJsHost + \"google-analytics.com/ga.js' type='text/javascript'%3E%3C/script%3E\"));\n" +
+ "</script>\n" +
+ "<script type=\"text/javascript\">\n" +
+ "try{\n" +
+ "var pageTracker = _gat._getTracker(\"My_\\\"google_account\");\n" +
+ "pageTracker._trackPageview();\n" +
+ "} catch(err) {}\n" +
+ "</script>\n";
+ UnitTestHelper.AssertEqualsIgnoreWhitespace(expected, actual);
+ }
+
+ [Fact]
+ public void GetGoogleAnalyticsAsyncHtmlTest()
+ {
+ string account = "My_google_account";
+ string actual = Analytics.GetGoogleAsyncHtml(account).ToString();
+ Assert.True(actual.Contains("google-analytics.com/ga.js") && actual.Contains("My_google_account"));
+ }
+
+ [Fact]
+ public void GetGoogleAnalyticsAsyncHtmlEscapesJavaScript()
+ {
+ string account = "My_\"google_account";
+ string actual = Analytics.GetGoogleAsyncHtml(account).ToString();
+ string expected = "<script type=\"text/javascript\">\n" +
+ "var _gaq = _gaq || [];\n" +
+ "_gaq.push(['_setAccount', 'My_\\\"google_account']);\n" +
+ "_gaq.push(['_trackPageview']);\n" +
+ "(function() {\n" +
+ "var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;\n" +
+ "ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';\n" +
+ "var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);\n" +
+ "})();\n" +
+ "</script>\n";
+ UnitTestHelper.AssertEqualsIgnoreWhitespace(expected, actual);
+ }
+
+ [Fact]
+ public void GetYahooAnalyticsEscapesJavascript()
+ {
+ string account = "My_\"yahoo_account";
+ string actual = Analytics.GetYahooHtml(account).ToString();
+ string expected = "<script type=\"text/javascript\">\n" +
+ "window.ysm_customData = new Object();\n" +
+ "window.ysm_customData.conversion = \"transId=,currency=,amount=\";\n" +
+ "var ysm_accountid = \"My_\\\"yahoo_account\";\n" +
+ "document.write(\"<SCR\" + \"IPT language='JavaScript' type='text/javascript' \"\n" +
+ "+ \"SRC=//\" + \"srv3.wa.marketingsolutions.yahoo.com\" + \"/script/ScriptServlet\" + \"?aid=\" + ysm_accountid\n" +
+ "+ \"></SCR\" + \"IPT>\");\n" +
+ "</script>\n";
+ UnitTestHelper.AssertEqualsIgnoreWhitespace(expected, actual);
+ }
+
+ [Fact]
+ public void GetStatCounterAnalyticsEscapesCorrectly()
+ {
+ string account = "My_\"stat_account";
+ string actual = Analytics.GetStatCounterHtml(2, account).ToString();
+ string expected = "<script type=\"text/javascript\">\n" +
+ "var sc_project=2;\n" +
+ "var sc_invisible=1;\n" +
+ "var sc_security=\"My_\\\"stat_account\";\n" +
+ "var sc_text=2;\n" +
+ "var sc_https=1;\n" +
+ "var scJsHost = ((\"https:\" == document.location.protocol) ? \"https://secure.\" : \"http://www.\");\n" +
+ "document.write(\"<sc\" + \"ript type='text/javascript' src='\" + " +
+ "scJsHost + \"statcounter.com/counter/counter_xhtml.js'></\" + \"script>\");\n" +
+ "</script>\n\n" +
+ "<noscript>" +
+ "<div class=\"statcounter\">" +
+ "<a title=\"tumblrstatistics\" class=\"statcounter\" href=\"http://www.statcounter.com/tumblr/\">" +
+ "<img class=\"statcounter\" src=\"https://c.statcounter.com/2/0/My_&quot;stat_account/1/\" alt=\"tumblr statistics\"/>" +
+ "</a>" +
+ "</div>" +
+ "</noscript>";
+
+ UnitTestHelper.AssertEqualsIgnoreWhitespace(expected, actual);
+ }
+ }
+}
diff --git a/test/Microsoft.Web.Helpers.Test/BingTest.cs b/test/Microsoft.Web.Helpers.Test/BingTest.cs
new file mode 100644
index 00000000..1d2bce96
--- /dev/null
+++ b/test/Microsoft.Web.Helpers.Test/BingTest.cs
@@ -0,0 +1,252 @@
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Text;
+using System.Web;
+using System.Web.Helpers.Test;
+using System.Web.TestUtil;
+using System.Web.WebPages.Scope;
+using Moq;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace Microsoft.Web.Helpers.Test
+{
+ public class BingTest
+ {
+ private static readonly IDictionary<object, object> _emptyStateStorage = new Dictionary<object, object>();
+
+ private const string BasicBingSearchTemplate = @"<form action=""http://www.bing.com/search"" class=""BingSearch"" method=""get"" target=""_blank"">"
+ + @"<input name=""FORM"" type=""hidden"" value=""FREESS"" /><input name=""cp"" type=""hidden"" value=""{0}"" />"
+ + @"<table cellpadding=""0"" cellspacing=""0"" style=""width:{1}px;""><tr style=""height: 32px"">"
+ + @"<td style=""width: 100%; border:solid 1px #ccc; border-right-style:none; padding-left:10px; padding-right:10px; vertical-align:middle;"">"
+ + @"<input name=""q"" style=""background-image:url(http://www.bing.com/siteowner/s/siteowner/searchbox_background_k.png); background-position:right; background-repeat:no-repeat; font-family:Arial; font-size:14px; color:#000; width:100%; border:none 0 transparent;"" title=""Search Bing"" type=""text"" />"
+ + @"</td><td style=""border:solid 1px #ccc; border-left-style:none; padding-left:0px; padding-right:3px;"">"
+ + @"<input alt=""Search"" src=""http://www.bing.com/siteowner/s/siteowner/searchbutton_normal_k.gif"" style=""border:none 0 transparent; height:24px; width:24px; vertical-align:top;"" type=""image"" />"
+ + @"</td></tr>";
+
+ private const string BasicBingSearchFooter = "</table></form>";
+
+ private const string BasicBingSearchLocalSiteSearch = @"<tr><td colspan=""2"" style=""font-size: small""><label><input checked=""checked"" name=""q1"" type=""radio"" value=""site:{0}"" />{1}</label>&nbsp;<label><input name=""q1"" type=""radio"" value="""" />Search Web</label></td></tr>";
+
+ [Fact]
+ public void SiteTitleThrowsWhenSetToNull()
+ {
+ Assert.ThrowsArgumentNull(() => Bing.SiteTitle = null, "SiteTitle");
+ }
+
+ [Fact]
+ public void SiteTitleUsesScopeStorage()
+ {
+ // Arrange
+ var value = "value";
+
+ // Act
+ Bing.SiteTitle = value;
+
+ // Assert
+ Assert.Equal(Bing.SiteTitle, value);
+ Assert.Equal(ScopeStorage.CurrentScope[Bing._siteTitleKey], value);
+ }
+
+ [Fact]
+ public void SiteUrlThrowsWhenSetToNull()
+ {
+ Assert.ThrowsArgumentNull(() => Bing.SiteUrl = null, "SiteUrl");
+ }
+
+ [Fact]
+ public void SiteUrlUsesScopeStorage()
+ {
+ // Arrange
+ var value = "value";
+
+ // Act
+ Bing.SiteUrl = value;
+
+ // Assert
+ Assert.Equal(Bing.SiteUrl, value);
+ Assert.Equal(ScopeStorage.CurrentScope[Bing._siteUrlKey], value);
+ }
+
+ [Fact]
+ public void SearchBoxGeneratesValidHtml()
+ {
+ // Act & Assert
+ XhtmlAssert.Validate1_0(
+ Bing._SearchBox("322px", null, null, GetContextForSearchBox(), _emptyStateStorage), true
+ );
+ }
+
+ [Fact]
+ public void SearchBoxDoesNotContainSearchLocalWhenSiteUrlIsNull()
+ {
+ // Arrange
+ var encoding = Encoding.UTF8;
+ var expectedHtml = String.Format(CultureInfo.InvariantCulture, BasicBingSearchTemplate, encoding.CodePage, 322) + BasicBingSearchFooter;
+
+ // Act
+ var actualHtml = Bing._SearchBox("322px", null, null, GetContextForSearchBox(encoding), _emptyStateStorage).ToString();
+
+ // Assert
+ UnitTestHelper.AssertEqualsIgnoreWhitespace(expectedHtml, actualHtml);
+ }
+
+ [Fact]
+ public void SearchBoxDoesNotContainSearchLocalWhenSiteUrlIsEmpty()
+ {
+ // Arrange
+ var encoding = Encoding.UTF8;
+ var expectedHtml = String.Format(CultureInfo.InvariantCulture, BasicBingSearchTemplate, encoding.CodePage, 322) + BasicBingSearchFooter;
+
+ // Act
+ var actualHtml = Bing._SearchBox("322px", String.Empty, String.Empty, GetContextForSearchBox(encoding), _emptyStateStorage).ToString();
+
+ // Assert
+ UnitTestHelper.AssertEqualsIgnoreWhitespace(expectedHtml, actualHtml);
+ }
+
+ [Fact]
+ public void SearchBoxUsesResponseEncodingToDetermineCodePage()
+ {
+ // Arrange
+ var encoding = Encoding.GetEncoding(51932); //euc-jp
+ var expectedHtml = String.Format(CultureInfo.InvariantCulture, BasicBingSearchTemplate, encoding.CodePage, 322) + BasicBingSearchFooter;
+
+ // Act
+ var actualHtml = Bing._SearchBox("322px", String.Empty, String.Empty, GetContextForSearchBox(encoding), _emptyStateStorage).ToString();
+
+ // Assert
+ UnitTestHelper.AssertEqualsIgnoreWhitespace(expectedHtml, actualHtml);
+ }
+
+ [Fact]
+ public void SearchBoxUsesWidthToSetBingSearchTableSize()
+ {
+ // Arrange
+ var encoding = Encoding.UTF8;
+ var expectedHtml = String.Format(CultureInfo.InvariantCulture, BasicBingSearchTemplate, encoding.CodePage, 609) + BasicBingSearchFooter;
+
+ // Act
+ var actualHtml = Bing._SearchBox("609px", String.Empty, String.Empty, GetContextForSearchBox(encoding), _emptyStateStorage).ToString();
+
+ // Assert
+ UnitTestHelper.AssertEqualsIgnoreWhitespace(expectedHtml, actualHtml);
+ }
+
+ [Fact]
+ public void SearchBoxUsesWithSiteUrlProducesLocalSearchOptions()
+ {
+ // Arrange
+ var encoding = Encoding.Default;
+ var expectedHtml = String.Format(CultureInfo.InvariantCulture, BasicBingSearchTemplate, encoding.CodePage, 322) +
+ String.Format(CultureInfo.InvariantCulture, BasicBingSearchLocalSiteSearch, "www.asp.net", "Search Site") + BasicBingSearchFooter;
+
+ // Act
+ var actualHtml = Bing._SearchBox("322px", "www.asp.net", String.Empty, GetContextForSearchBox(encoding), _emptyStateStorage).ToString();
+
+ // Assert
+ UnitTestHelper.AssertEqualsIgnoreWhitespace(expectedHtml, actualHtml);
+ }
+
+ [Fact]
+ public void SearchBoxUsesWithSiteUrlAndSiteTitleProducesLocalSearchOptions()
+ {
+ // Arrange
+ var encoding = Encoding.Default;
+ var expectedHtml = String.Format(CultureInfo.InvariantCulture, BasicBingSearchTemplate, encoding.CodePage, 322) +
+ String.Format(CultureInfo.InvariantCulture, BasicBingSearchLocalSiteSearch, "www.microsoft.com", "Custom Search") + BasicBingSearchFooter;
+
+ // Act
+ var actualHtml = Bing._SearchBox("322px", "www.microsoft.com", "Custom Search", GetContextForSearchBox(encoding), _emptyStateStorage).ToString();
+
+ // Assert
+ UnitTestHelper.AssertEqualsIgnoreWhitespace(expectedHtml, actualHtml);
+ }
+
+ [Fact]
+ public void SearchBoxWithLocalSiteOptionUsesResponseEncoding()
+ {
+ // Arrange
+ var encoding = Encoding.GetEncoding(1258); //windows-1258
+ var expectedHtml = String.Format(CultureInfo.InvariantCulture, BasicBingSearchTemplate, encoding.CodePage, 322) +
+ String.Format(CultureInfo.InvariantCulture, BasicBingSearchLocalSiteSearch, "www.asp.net", "Search Site") + BasicBingSearchFooter;
+
+ // Act
+ var actualHtml = Bing._SearchBox("322px", "www.asp.net", String.Empty, GetContextForSearchBox(encoding), _emptyStateStorage).ToString();
+
+ // Assert
+ UnitTestHelper.AssertEqualsIgnoreWhitespace(expectedHtml, actualHtml);
+ }
+
+ [Fact]
+ public void SearchBoxUsesScopeStorageIfSiteTitleParameterIsNull()
+ {
+ // Arrange
+ var encoding = Encoding.GetEncoding(1258); //windows-1258
+ var expectedHtml = String.Format(CultureInfo.InvariantCulture, BasicBingSearchTemplate, encoding.CodePage, 322) +
+ String.Format(CultureInfo.InvariantCulture, BasicBingSearchLocalSiteSearch, "www.asp.net", "foobar") + BasicBingSearchFooter;
+
+ // Act
+ var actualHtml = Bing._SearchBox("322px", "www.asp.net", null, GetContextForSearchBox(encoding), new Dictionary<object, object> { { Bing._siteTitleKey, "foobar" } }).ToString();
+
+ // Assert
+ UnitTestHelper.AssertEqualsIgnoreWhitespace(expectedHtml, actualHtml);
+ }
+
+ [Fact]
+ public void SearchBoxUsesScopeStorageIfSiteTitleParameterIsEmpty()
+ {
+ // Arrange
+ var encoding = Encoding.GetEncoding(1258); //windows-1258
+ var expectedHtml = String.Format(CultureInfo.InvariantCulture, BasicBingSearchTemplate, encoding.CodePage, 322) +
+ String.Format(CultureInfo.InvariantCulture, BasicBingSearchLocalSiteSearch, "www.asptest.net", "bazbiz") + BasicBingSearchFooter;
+
+ // Act
+ var actualHtml = Bing._SearchBox("322px", "www.asptest.net", String.Empty, GetContextForSearchBox(encoding), new Dictionary<object, object> { { Bing._siteTitleKey, "bazbiz" } }).ToString();
+
+ // Assert
+ UnitTestHelper.AssertEqualsIgnoreWhitespace(expectedHtml, actualHtml);
+ }
+
+ [Fact]
+ public void SearchBoxUsesScopeStorageIfSiteUrlParameterIsNull()
+ {
+ // Arrange
+ var encoding = Encoding.GetEncoding(1258); //windows-1258
+ var expectedHtml = String.Format(CultureInfo.InvariantCulture, BasicBingSearchTemplate, encoding.CodePage, 322) +
+ String.Format(CultureInfo.InvariantCulture, BasicBingSearchLocalSiteSearch, "www.myawesomesite.net", "my-test-string") + BasicBingSearchFooter;
+
+ // Act
+ var actualHtml = Bing._SearchBox("322px", null, "my-test-string", GetContextForSearchBox(encoding), new Dictionary<object, object> { { Bing._siteUrlKey, "www.myawesomesite.net" } }).ToString();
+
+ // Assert
+ UnitTestHelper.AssertEqualsIgnoreWhitespace(expectedHtml, actualHtml);
+ }
+
+ [Fact]
+ public void SearchBoxUsesScopeStorageIfSiteUrlParameterIsEmpty()
+ {
+ // Arrange
+ var encoding = Encoding.GetEncoding(1258); //windows-1258
+ var expectedHtml = String.Format(CultureInfo.InvariantCulture, BasicBingSearchTemplate, encoding.CodePage, 322) +
+ String.Format(CultureInfo.InvariantCulture, BasicBingSearchLocalSiteSearch, "www.myawesomesite.net", "my-test-string") + BasicBingSearchFooter;
+
+ // Act
+ var actualHtml = Bing._SearchBox("322px", String.Empty, "my-test-string", GetContextForSearchBox(encoding), new Dictionary<object, object> { { Bing._siteUrlKey, "www.myawesomesite.net" } }).ToString();
+
+ // Assert
+ UnitTestHelper.AssertEqualsIgnoreWhitespace(expectedHtml, actualHtml);
+ }
+
+ private HttpContextBase GetContextForSearchBox(Encoding contentEncoding = null)
+ {
+ Mock<HttpContextBase> context = new Mock<HttpContextBase>();
+ Mock<HttpResponseBase> response = new Mock<HttpResponseBase>();
+ response.Setup(c => c.ContentEncoding).Returns(contentEncoding ?? Encoding.Default);
+ context.Setup(c => c.Response).Returns(response.Object);
+
+ return context.Object;
+ }
+ }
+}
diff --git a/test/Microsoft.Web.Helpers.Test/FacebookTest.cs b/test/Microsoft.Web.Helpers.Test/FacebookTest.cs
new file mode 100644
index 00000000..b5b7a1f6
--- /dev/null
+++ b/test/Microsoft.Web.Helpers.Test/FacebookTest.cs
@@ -0,0 +1,270 @@
+using System;
+using System.Collections.Generic;
+using System.Web;
+using System.Web.TestUtil;
+using Microsoft.TestCommon;
+using Moq;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace Microsoft.Web.Helpers.Test
+{
+ public class FacebookTest
+ {
+ public FacebookTest()
+ {
+ Facebook.AppId = "myapp'";
+ Facebook.AppSecret = "myappsecret";
+ Facebook.Language = "french";
+ }
+
+ [Fact]
+ public void GetFacebookCookieInfoReturnsEmptyStringIfCookieIsNotPresent()
+ {
+ // Arrange
+ var context = CreateHttpContext();
+
+ // Act
+ var info = Facebook.GetFacebookCookieInfo(context, "foo");
+
+ // Assert
+ Assert.Equal("", info);
+ }
+
+ [Fact]
+ public void GetFacebookCookieInfoThrowsIfCookieIsNotValid()
+ {
+ // Arrange
+
+ var context = CreateHttpContext(new Dictionary<string, string>
+ {
+ {"fbs_myapp'", "sig=malformed-signature&name=foo&val=bar&uid=MyFacebookName"},
+ {"fbs_uid", "MyFacebookName"},
+ });
+
+ // Act and Assert
+ Assert.Throws<InvalidOperationException>(() => Facebook.GetFacebookCookieInfo(context, "uid"), "Invalid Facebook cookie.");
+ }
+
+ [Fact]
+ public void GetFacebookCookieReturnsUserIdIfCookieIsValid()
+ {
+ // Arrange
+ var context = CreateHttpContext(new Dictionary<string, string>
+ {
+ {"fbs_myapp'", "sig=B2E6B3A21D0C9FA72E612BD6C3084807&name=foo&val=bar&uid=MyFacebookName"},
+ });
+
+ // Act
+ var info = Facebook.GetFacebookCookieInfo(context, "uid");
+
+ // Assert
+ Assert.Equal("MyFacebookName", info);
+ }
+
+ [Fact]
+ public void GetInitScriptsJSEncodesParameters()
+ {
+ // Arrange
+ var expectedText = @"
+ <div id=""fb-root""></div>
+ <script type=""text/javascript"">
+ window.fbAsyncInit = function () {
+ FB.init({ appId: 'MyApp\u0027', status: true, cookie: true, xfbml: true });
+ };
+ (function () {
+ var e = document.createElement('script'); e.async = true;
+ e.src = document.location.protocol +
+ '//connect.facebook.net/French/all.js';
+ document.getElementById('fb-root').appendChild(e);
+ } ());
+
+ function loginRedirect(url) { window.location = url; }
+ </script>
+ ";
+
+ // Act
+ var actualText = Facebook.GetInitializationScripts();
+
+ // Assert
+ UnitTestHelper.AssertEqualsIgnoreWhitespace(expectedText, actualText.ToString());
+ }
+
+ [Fact]
+ public void LoginButtonTest()
+ {
+ // Arrange
+ var expected40 = @"<fb:login-button autologoutlink=""True"" size=""extra-small"" length=""extra-long"" onlogin=""loginRedirect(&#39;http://www.test.com/facebook/?registerUrl=http%3a%2f%2fwww.test.com%2f&amp;returnUrl=http%3a%2f%2fww.test.com%2fLogin.cshtml&#39;)"" show-faces=""True"" perms=""email,none&quot;"">Awesome &quot;button text&quot;</fb:login-button>";
+ var expected45 = @"<fb:login-button autologoutlink=""True"" size=""extra-small"" length=""extra-long"" onlogin=""loginRedirect(&#39;http://www.test.com/facebook/?registerUrl=http%3a%2f%2fwww.test.com%2f\u0026returnUrl=http%3a%2f%2fww.test.com%2fLogin.cshtml&#39;)"" show-faces=""True"" perms=""email,none&quot;"">Awesome &quot;button text&quot;</fb:login-button>";
+
+ // Act
+ var actualText = Facebook.LoginButton("http://www.test.com", "http://ww.test.com/Login.cshtml", "http://www.test.com/facebook/", "Awesome \"button text\"", true, "extra-small", "extra-long", true, "none\"");
+
+ // Assert
+ UnitTestHelper.AssertEqualsIgnoreWhitespace(RuntimeEnvironment.IsVersion45Installed ? expected45 : expected40, actualText.ToString());
+ }
+
+ [Fact]
+ public void LoginButtonOnlyTagTest()
+ {
+ // Arrange
+ var expectedText = @"<fb:login-button autologoutlink=""True"" size=""small"" length=""medium"" onlogin=""foobar();"" show-faces=""True"" perms=""none&quot;"">&quot;Awesome button text&quot;</fb:login-button>";
+
+ // Act
+ var actualText = Facebook.LoginButtonTagOnly("\"Awesome button text\"", true, "small", "medium", "foobar();", true, "none\"");
+
+ // Assert
+ UnitTestHelper.AssertEqualsIgnoreWhitespace(expectedText, actualText.ToString());
+ }
+
+ [Fact]
+ public void LikeButtonTest()
+ {
+ // Arrange
+ var expectedText = @"<iframe src=""http://www.facebook.com/plugins/like.php?href=http%3a%2f%2fsomewebsite&amp;layout=modern&amp;show_faces=false&amp;width=300&amp;action=hop&amp;colorscheme=lighter&amp;height=30&amp;font=Comic+Sans&amp;locale=French&amp;ref=foo+bar"" scrolling=""no"" frameborder=""0"" style=""border:none; overflow:hidden; width:300px; height:30px;"" allowTransparency=""true""></iframe>";
+
+ // Act
+ var actualText = Facebook.LikeButton("http://somewebsite", "modern", false, 300, 30, "hop", "Comic Sans", "lighter", "foo bar");
+
+ // Assert
+ UnitTestHelper.AssertEqualsIgnoreWhitespace(expectedText, actualText.ToString());
+ }
+
+ [Fact]
+ public void CommentsWithNoXidTest()
+ {
+ // Arrange
+ var expectedText = @"<fb:comments numposts=""3"" width=""300"" reverse=""true"" simple=""true"" ></fb:comments>";
+
+ // Act
+ var actualText = Facebook.Comments(null, 300, 3, true, true);
+
+ // Assert
+ UnitTestHelper.AssertEqualsIgnoreWhitespace(expectedText, actualText.ToString());
+ }
+
+ [Fact]
+ public void CommentsWithXidTest()
+ {
+ // Arrange
+ var expectedText = @"<fb:comments xid=""bar"" numposts=""3"" width=""300"" reverse=""true"" simple=""true"" ></fb:comments>";
+
+ // Act
+ var actualText = Facebook.Comments("bar", 300, 3, true, true);
+
+ // Assert
+ UnitTestHelper.AssertEqualsIgnoreWhitespace(expectedText, actualText.ToString());
+ }
+
+ [Fact]
+ public void RecommendationsTest()
+ {
+ // Arrange
+ var expectedText = @"<iframe src=""http://www.facebook.com/plugins/recommendations.php?site=http%3a%2f%2fsomesite&amp;width=100&amp;height=200&amp;header=False&amp;colorscheme=blue&amp;font=none&amp;border_color=black&amp;filter=All+posts&amp;ref=ref+label&amp;locale=french"" scrolling=""no"" frameborder=""0"" style=""border:none; overflow:hidden; width:100px; height:200px;"" allowTransparency=""true""></iframe>";
+
+ // Act
+ var actualText = Facebook.Recommendations("http://somesite", 100, 200, false, "blue", "none", "black", "All posts", "ref label");
+
+ // Assert
+ UnitTestHelper.AssertEqualsIgnoreWhitespace(expectedText, actualText.ToString());
+ }
+
+ [Fact]
+ public void LikeBoxTest()
+ {
+ // Arrange
+ var expectedText = @"<iframe src=""http://www.facebook.com/plugins/recommendations.php?href=http%3a%2f%2fsomesite&amp;width=100&amp;height=200&amp;header=False&amp;colorscheme=blue&amp;connections=5&amp;stream=True&amp;header=False&amp;locale=french"" scrolling=""no"" frameborder=""0"" style=""border:none; overflow:hidden; width:100px; height:200px;"" allowTransparency=""true""></iframe>";
+
+ // Act
+ var actualText = Facebook.LikeBox("http://somesite", 100, 200, "blue", 5, true, false);
+
+ // Assert
+ UnitTestHelper.AssertEqualsIgnoreWhitespace(expectedText, actualText.ToString());
+ }
+
+ [Fact]
+ public void FacepileTest()
+ {
+ // Arrange
+ var expectedText = @"<fb:facepile max-rows=""3"" width=""100""></fb:facepile>";
+
+ // Act
+ var actualText = Facebook.Facepile(3, 100);
+
+ // Assert
+ UnitTestHelper.AssertEqualsIgnoreWhitespace(expectedText, actualText.ToString());
+ }
+
+ [Fact]
+ public void LiveStreamWithEmptyXidTest()
+ {
+ // Arrange
+ var expectedText = @"<iframe src=""http://www.facebook.com/plugins/live_stream_box.php?app_id=myapp%27&amp;width=100&amp;height=100&amp;always_post_to_friends=True&amp;locale=french"" scrolling=""no"" frameborder=""0"" style=""border:none; overflow:hidden; width:100px; height:100px;"" allowTransparency=""true""></iframe>";
+
+ // Act
+ var actualText = Facebook.LiveStream(100, 100, "", "", true);
+
+ // Assert
+ UnitTestHelper.AssertEqualsIgnoreWhitespace(expectedText, actualText.ToString());
+ }
+
+ [Fact]
+ public void LiveStreamWithXidValueTest()
+ {
+ // Arrange
+ var expectedText = @"<iframe src=""http://www.facebook.com/plugins/live_stream_box.php?app_id=myapp%27&amp;width=100&amp;height=100&amp;always_post_to_friends=True&amp;locale=french&amp;xid=some-val&amp;via_url=http%3a%2f%2fmysite"" scrolling=""no"" frameborder=""0"" style=""border:none; overflow:hidden; width:100px; height:100px;"" allowTransparency=""true""></iframe>";
+
+ // Act
+ var actualText = Facebook.LiveStream(100, 100, "some-val", "http://mysite", true);
+
+ // Assert
+ UnitTestHelper.AssertEqualsIgnoreWhitespace(expectedText, actualText.ToString());
+ }
+
+ [Fact]
+ public void ActivityStreamTest()
+ {
+ // Arrange
+ var expectedText = @"<iframe src=""http://www.facebook.com/plugins/activity.php?site=http%3a%2f%2fmysite&amp;width=100&amp;height=120&amp;header=False&amp;colorscheme=gray&amp;font=Arial&amp;border_color=blue&amp;recommendations=True&amp;locale=french"" scrolling=""no"" frameborder=""0"" style=""border:none; overflow:hidden; width:300px; height:300px;"" allowTransparency=""true""></iframe>";
+
+ // Act
+ var actualText = Facebook.ActivityFeed("http://mysite", 100, 120, false, "gray", "Arial", "blue", true);
+
+ // Assert
+ UnitTestHelper.AssertEqualsIgnoreWhitespace(expectedText, actualText.ToString());
+ }
+
+ [Fact]
+ public void FbmlNamespacesTest()
+ {
+ // Arrange
+ var expectedText = @"xmlns:fb=""http://www.facebook.com/2008/fbml"" xmlns:og=""http://opengraphprotocol.org/schema/""";
+
+ // Act
+ var actualText = Facebook.FbmlNamespaces();
+
+ // Assert
+ Assert.Equal(expectedText, actualText.ToString());
+ }
+
+ private static HttpContextBase CreateHttpContext(IDictionary<string, string> cookieValues = null)
+ {
+ var context = new Mock<HttpContextBase>();
+ var httpRequest = new Mock<HttpRequestBase>();
+ var cookies = new HttpCookieCollection();
+ httpRequest.Setup(c => c.Cookies).Returns(cookies);
+
+ context.Setup(c => c.Request).Returns(httpRequest.Object);
+
+ if (cookieValues != null)
+ {
+ foreach (var item in cookieValues)
+ {
+ cookies.Add(new HttpCookie(item.Key, item.Value));
+ }
+ }
+
+ return context.Object;
+ }
+ }
+}
diff --git a/test/Microsoft.Web.Helpers.Test/FileUploadTest.cs b/test/Microsoft.Web.Helpers.Test/FileUploadTest.cs
new file mode 100644
index 00000000..c343eaf3
--- /dev/null
+++ b/test/Microsoft.Web.Helpers.Test/FileUploadTest.cs
@@ -0,0 +1,199 @@
+using System.Collections;
+using System.Web;
+using System.Web.Helpers.Test;
+using System.Web.TestUtil;
+using Moq;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace Microsoft.Web.Helpers.Test
+{
+ public class FileUploadTest
+ {
+ private const string _fileUploadScript = "<script type=\"text/javascript\"> if (!window[\"FileUploadHelper\"]) window[\"FileUploadHelper\"] = {}; FileUploadHelper.addInputElement = function(index, name) { var inputElem = document.createElement(\"input\"); inputElem.type = \"file\"; inputElem.name = name; var divElem = document.createElement(\"div\"); divElem.appendChild(inputElem.cloneNode(false)); var inputs = document.getElementById(\"file-upload-\" + index); inputs.appendChild(divElem); } </script>";
+
+ [Fact]
+ public void RenderThrowsWhenNumberOfFilesIsLessThanZero()
+ {
+ // Act and Assert
+ Assert.ThrowsArgumentGreaterThanOrEqualTo(
+ () => FileUpload._GetHtml(GetContext(), name: null, initialNumberOfFiles: -2, allowMoreFilesToBeAdded: false, includeFormTag: false, addText: "", uploadText: "").ToString(),
+ "initialNumberOfFiles", "0");
+ }
+
+ [Fact]
+ public void ResultIncludesFormTagAndSubmitButtonWhenRequested()
+ {
+ // Arrange
+ string expectedResult = @"<form action="""" enctype=""multipart/form-data"" method=""post""><div class=""file-upload"" id=""file-upload-0"">"
+ + @"<div><input name=""fileUpload"" type=""file"" /></div></div>"
+ + @"<div class=""file-upload-buttons""><input type=""submit"" value=""Upload"" /></div></form>";
+
+ // Act
+ var actualResult = FileUpload._GetHtml(GetContext(), name: null, initialNumberOfFiles: 1, allowMoreFilesToBeAdded: false, includeFormTag: true, addText: null, uploadText: null);
+
+ // Assert
+ UnitTestHelper.AssertEqualsIgnoreWhitespace(expectedResult, actualResult.ToString());
+ }
+
+ [Fact]
+ public void ResultDoesNotIncludeFormTagAndSubmitButtonWhenNotRequested()
+ {
+ // Arrange
+ string expectedResult = @"<div class=""file-upload"" id=""file-upload-0""><div><input name=""fileUpload"" type=""file"" /></div></div>";
+
+ // Act
+ var actualResult = FileUpload._GetHtml(GetContext(), name: null, initialNumberOfFiles: 1, allowMoreFilesToBeAdded: false, includeFormTag: false, addText: null, uploadText: null);
+
+ // Assert
+ UnitTestHelper.AssertEqualsIgnoreWhitespace(expectedResult, actualResult.ToString());
+ }
+
+ [Fact]
+ public void ResultIncludesCorrectNumberOfInputFields()
+ {
+ // Arrange
+ string expectedResult = @"<div class=""file-upload"" id=""file-upload-0""><div><input name=""fileUpload"" type=""file"" /></div><div><input name=""fileUpload"" type=""file"" /></div>"
+ + @"<div><input name=""fileUpload"" type=""file"" /></div></div>";
+
+ // Act
+ var actualResult = FileUpload._GetHtml(GetContext(), name: null, initialNumberOfFiles: 3, allowMoreFilesToBeAdded: false, includeFormTag: false, addText: null, uploadText: null);
+
+ // Assert
+ UnitTestHelper.AssertEqualsIgnoreWhitespace(expectedResult, actualResult.ToString());
+ }
+
+ [Fact]
+ public void ResultIncludesAnchorTagWithCorrectAddText()
+ {
+ // Arrange
+ string customAddText = "Add More";
+ string expectedResult = _fileUploadScript
+ + @"<div class=""file-upload"" id=""file-upload-0""><div><input name=""fileUpload"" type=""file"" /></div></div>"
+ + @"<div class=""file-upload-buttons""><a href=""#"" onclick=""FileUploadHelper.addInputElement(0, &quot;fileUpload&quot;); return false;"">" + customAddText + "</a></div>";
+
+ // Act
+ var result = FileUpload._GetHtml(GetContext(), name: null, allowMoreFilesToBeAdded: true, includeFormTag: false, addText: customAddText, initialNumberOfFiles: 1, uploadText: null);
+
+ // Assert
+ UnitTestHelper.AssertEqualsIgnoreWhitespace(expectedResult, result.ToString());
+ }
+
+ [Fact]
+ public void ResultDoesNotIncludeAnchorTagNorAddTextWhenNotRequested()
+ {
+ // Arrange
+ string customAddText = "Add More";
+ string expectedResult = @"<div class=""file-upload"" id=""file-upload-0""><div><input name=""fileUpload"" type=""file"" /></div></div>";
+
+ // Act
+ var result = FileUpload._GetHtml(GetContext(), name: null, allowMoreFilesToBeAdded: false, includeFormTag: false, addText: customAddText, uploadText: null, initialNumberOfFiles: 1);
+
+ // Assert
+ UnitTestHelper.AssertEqualsIgnoreWhitespace(expectedResult, result.ToString());
+ }
+
+ [Fact]
+ public void ResultIncludesSubmitInputTagWithCustomUploadText()
+ {
+ // Arrange
+ string customUploadText = "Now!";
+ string expectedResult = @"<form action="""" enctype=""multipart/form-data"" method=""post""><div class=""file-upload"" id=""file-upload-0"">"
+ + @"<div><input name=""fileUpload"" type=""file"" /></div></div>"
+ + @"<div class=""file-upload-buttons""><input type=""submit"" value=""" + customUploadText + @""" /></div></form>";
+
+ // Act
+ var result = FileUpload._GetHtml(GetContext(), name: null, includeFormTag: true, uploadText: customUploadText, allowMoreFilesToBeAdded: false, initialNumberOfFiles: 1, addText: null);
+
+ // Assert
+ UnitTestHelper.AssertEqualsIgnoreWhitespace(expectedResult, result.ToString());
+ }
+
+ [Fact]
+ public void FileUploadGeneratesUniqueIdsForMultipleCallsForCommonRequest()
+ {
+ // Arrange
+ var context = GetContext();
+ string expectedResult1 = @"<div class=""file-upload"" id=""file-upload-0""><div><input name=""fileUpload"" type=""file"" /></div><div><input name=""fileUpload"" type=""file"" /></div>"
+ + @"<div><input name=""fileUpload"" type=""file"" /></div></div>";
+ string expectedResult2 = @"<form action="""" enctype=""multipart/form-data"" method=""post""><div class=""file-upload"" id=""file-upload-1""><div><input name=""fileUpload"" type=""file"" /></div>"
+ + @"<div><input name=""fileUpload"" type=""file"" /></div></div><div class=""file-upload-buttons""><input type=""submit"" value=""Upload"" /></div></form>";
+
+ // Act
+ var result1 = FileUpload._GetHtml(context, name: null, initialNumberOfFiles: 3, allowMoreFilesToBeAdded: false, includeFormTag: false, addText: null, uploadText: null);
+ var result2 = FileUpload._GetHtml(context, name: null, initialNumberOfFiles: 2, allowMoreFilesToBeAdded: false, includeFormTag: true, addText: null, uploadText: null);
+
+ // Assert
+ UnitTestHelper.AssertEqualsIgnoreWhitespace(expectedResult1, result1.ToString());
+ UnitTestHelper.AssertEqualsIgnoreWhitespace(expectedResult2, result2.ToString());
+ }
+
+ [Fact]
+ public void FileUploadGeneratesScriptOncePerRequest()
+ {
+ // Arrange
+ var context = GetContext();
+ string expectedResult1 = _fileUploadScript
+ + @"<div class=""file-upload"" id=""file-upload-0""><div><input name=""fileUpload"" type=""file"" /></div></div>"
+ + @"<div class=""file-upload-buttons""><a href=""#"" onclick=""FileUploadHelper.addInputElement(0, &quot;fileUpload&quot;); return false;"">Add more files</a></div>";
+ string expectedResult2 = @"<form action="""" enctype=""multipart/form-data"" method=""post""><div class=""file-upload"" id=""file-upload-1""><div><input name=""fileUpload"" type=""file"" /></div></div>"
+ + @"<div class=""file-upload-buttons""><a href=""#"" onclick=""FileUploadHelper.addInputElement(1, &quot;fileUpload&quot;); return false;"">Add more files</a><input type=""submit"" value=""Upload"" /></div></form>";
+ string expectedResult3 = @"<form action="""" enctype=""multipart/form-data"" method=""post""><div class=""file-upload"" id=""file-upload-2"">"
+ + @"<div><input name=""fileUpload"" type=""file"" /></div></div>"
+ + @"<div class=""file-upload-buttons""><input type=""submit"" value=""Upload"" /></div></form>";
+
+ // Act
+ var result1 = FileUpload._GetHtml(context, name: null, initialNumberOfFiles: 1, allowMoreFilesToBeAdded: true, includeFormTag: false, addText: null, uploadText: null);
+ var result2 = FileUpload._GetHtml(context, name: null, initialNumberOfFiles: 1, allowMoreFilesToBeAdded: true, includeFormTag: true, addText: null, uploadText: null);
+ var result3 = FileUpload._GetHtml(context, name: null, initialNumberOfFiles: 1, allowMoreFilesToBeAdded: false, includeFormTag: true, addText: null, uploadText: null);
+
+ // Assert
+ UnitTestHelper.AssertEqualsIgnoreWhitespace(expectedResult1, result1.ToString());
+ UnitTestHelper.AssertEqualsIgnoreWhitespace(expectedResult2, result2.ToString());
+ UnitTestHelper.AssertEqualsIgnoreWhitespace(expectedResult3, result3.ToString());
+ }
+
+ [Fact]
+ public void FileUploadUsesNamePropertyInJavascript()
+ {
+ // Arrange
+ var context = GetContext();
+ string name = "foobar";
+ string expectedResult = _fileUploadScript
+ + @"<form action="""" enctype=""multipart/form-data"" method=""post""><div class=""file-upload"" id=""file-upload-0""><div><input name=""foobar"" type=""file"" /></div></div>"
+ + @"<div class=""file-upload-buttons""><a href=""#"" onclick=""FileUploadHelper.addInputElement(0, &quot;foobar&quot;); return false;"">Add more files</a><input type=""submit"" value=""Upload"" /></div></form>";
+
+ // Act
+ var result = FileUpload._GetHtml(context, name: name, initialNumberOfFiles: 1, allowMoreFilesToBeAdded: true, includeFormTag: true, addText: null, uploadText: null);
+
+ // Assert
+ UnitTestHelper.AssertEqualsIgnoreWhitespace(expectedResult, result.ToString());
+ }
+
+ [Fact]
+ public void _GetHtmlWithDefaultArgumentsProducesValidXhtml()
+ {
+ // Act
+ var result = FileUpload._GetHtml(GetContext(), name: null, initialNumberOfFiles: 1, includeFormTag: false, allowMoreFilesToBeAdded: false, addText: null, uploadText: null);
+
+ // Assert
+ XhtmlAssert.Validate1_1(result, "div");
+ }
+
+ [Fact]
+ public void _GetHtmlWithoutFormTagProducesValidXhtml()
+ {
+ // Act
+ var result = FileUpload._GetHtml(GetContext(), name: null, includeFormTag: false, initialNumberOfFiles: 1, allowMoreFilesToBeAdded: false, addText: null, uploadText: null);
+
+ XhtmlAssert.Validate1_1(result, "div");
+ }
+
+ private HttpContextBase GetContext()
+ {
+ var context = new Mock<HttpContextBase>();
+ context.Setup(c => c.Items).Returns(new Hashtable());
+ return context.Object;
+ }
+ }
+}
diff --git a/test/Microsoft.Web.Helpers.Test/GamerCardTest.cs b/test/Microsoft.Web.Helpers.Test/GamerCardTest.cs
new file mode 100644
index 00000000..5923a78c
--- /dev/null
+++ b/test/Microsoft.Web.Helpers.Test/GamerCardTest.cs
@@ -0,0 +1,59 @@
+using System;
+using System.Web.Helpers.Test;
+using System.Web.TestUtil;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace Microsoft.Web.Helpers.Test
+{
+ public class GamerCardTest
+ {
+ [Fact]
+ public void RenderThrowsWhenGamertagIsEmpty()
+ {
+ // Act & Assert
+ Assert.ThrowsArgumentNullOrEmptyString(() => { GamerCard.GetHtml(String.Empty).ToString(); }, "gamerTag");
+ }
+
+ [Fact]
+ public void RenderThrowsWhenGamertagIsNull()
+ {
+ // Act & Assert
+ Assert.ThrowsArgumentNullOrEmptyString(() => { GamerCard.GetHtml(null).ToString(); }, "gamerTag");
+ }
+
+ [Fact]
+ public void RenderGeneratesProperMarkupWithSimpleGamertag()
+ {
+ // Arrange
+ string expectedHtml = "<iframe frameborder=\"0\" height=\"140\" scrolling=\"no\" src=\"http://gamercard.xbox.com/osbornm.card\" width=\"204\">osbornm</iframe>";
+
+ // Act
+ string html = GamerCard.GetHtml("osbornm").ToHtmlString();
+
+ // Assert
+ UnitTestHelper.AssertEqualsIgnoreWhitespace(expectedHtml, html);
+ }
+
+ [Fact]
+ public void RenderGeneratesProperMarkupWithComplexGamertag()
+ {
+ // Arrange
+ string expectedHtml = "<iframe frameborder=\"0\" height=\"140\" scrolling=\"no\" src=\"http://gamercard.xbox.com/matthew%20osborn&#39;s.card\" width=\"204\">matthew osborn&#39;s</iframe>";
+
+ // Act
+ string html = GamerCard.GetHtml("matthew osborn's").ToHtmlString();
+
+ // Assert
+ UnitTestHelper.AssertEqualsIgnoreWhitespace(expectedHtml, html);
+ }
+
+ [Fact]
+ public void RenderGeneratesValidXhtml()
+ {
+ XhtmlAssert.Validate1_0(
+ GamerCard.GetHtml("osbornm")
+ );
+ }
+ }
+}
diff --git a/test/Microsoft.Web.Helpers.Test/GravatarTest.cs b/test/Microsoft.Web.Helpers.Test/GravatarTest.cs
new file mode 100644
index 00000000..af2e039f
--- /dev/null
+++ b/test/Microsoft.Web.Helpers.Test/GravatarTest.cs
@@ -0,0 +1,142 @@
+using System;
+using System.Web.Helpers.Test;
+using System.Web.WebPages.TestUtils;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace Microsoft.Web.Helpers.Test
+{
+ public class GravatarTest
+ {
+ [Fact]
+ public void GetUrlDefaults()
+ {
+ string url = Gravatar.GetUrl("foo@bar.com");
+ Assert.Equal("http://www.gravatar.com/avatar/f3ada405ce890b6f8204094deb12d8a8?s=80", url);
+ }
+
+ [Fact]
+ public void RenderEncodesDefaultImageUrl()
+ {
+ string render = Gravatar.GetHtml("foo@bar.com", defaultImage: "http://example.com/images/example.jpg").ToString();
+ Assert.Equal(
+ "<img src=\"http://www.gravatar.com/avatar/f3ada405ce890b6f8204094deb12d8a8?s=80&amp;d=http%3a%2f%2fexample.com%2fimages%2fexample.jpg\" alt=\"gravatar\" />",
+ render);
+ }
+
+ [Fact]
+ public void RenderLowerCasesEmail()
+ {
+ string render = Gravatar.GetHtml("FOO@BAR.COM").ToString();
+ Assert.Equal(
+ "<img src=\"http://www.gravatar.com/avatar/f3ada405ce890b6f8204094deb12d8a8?s=80\" alt=\"gravatar\" />",
+ render);
+ }
+
+ [Fact]
+ public void RendersValidXhtml()
+ {
+ XhtmlAssert.Validate1_1(Gravatar.GetHtml("foo@bar.com"));
+ }
+
+ [Fact]
+ public void RenderThrowsWhenEmailIsEmpty()
+ {
+ Assert.ThrowsArgumentNullOrEmptyString(() => { Gravatar.GetHtml(String.Empty); }, "email");
+ }
+
+ [Fact]
+ public void RenderThrowsWhenEmailIsNull()
+ {
+ Assert.ThrowsArgumentNullOrEmptyString(() => { Gravatar.GetHtml(null); }, "email");
+ }
+
+ [Fact]
+ public void RenderThrowsWhenImageSizeIsLessThanZero()
+ {
+ Assert.ThrowsArgument(() => { Gravatar.GetHtml("foo@bar.com", imageSize: -1); }, "imageSize", "The Gravatar image size must be between 1 and 512 pixels.");
+ }
+
+ [Fact]
+ public void RenderThrowsWhenImageSizeIsZero()
+ {
+ Assert.ThrowsArgument(() => { Gravatar.GetHtml("foo@bar.com", imageSize: 0); }, "imageSize", "The Gravatar image size must be between 1 and 512 pixels.");
+ }
+
+ [Fact]
+ public void RenderThrowsWhenImageSizeIsGreaterThan512()
+ {
+ Assert.ThrowsArgument(() => { Gravatar.GetHtml("foo@bar.com", imageSize: 513); }, "imageSize", "The Gravatar image size must be between 1 and 512 pixels.");
+ }
+
+ [Fact]
+ public void RenderTrimsEmail()
+ {
+ string render = Gravatar.GetHtml(" \t foo@bar.com\t\r\n").ToString();
+ Assert.Equal(
+ "<img src=\"http://www.gravatar.com/avatar/f3ada405ce890b6f8204094deb12d8a8?s=80\" alt=\"gravatar\" />",
+ render);
+ }
+
+ [Fact]
+ public void RenderUsesDefaultImage()
+ {
+ string render = Gravatar.GetHtml("foo@bar.com", defaultImage: "wavatar").ToString();
+ Assert.Equal(
+ "<img src=\"http://www.gravatar.com/avatar/f3ada405ce890b6f8204094deb12d8a8?s=80&amp;d=wavatar\" alt=\"gravatar\" />",
+ render);
+ }
+
+ [Fact]
+ public void RenderUsesImageSize()
+ {
+ string render = Gravatar.GetHtml("foo@bar.com", imageSize: 512).ToString();
+ Assert.Equal(
+ "<img src=\"http://www.gravatar.com/avatar/f3ada405ce890b6f8204094deb12d8a8?s=512\" alt=\"gravatar\" />",
+ render);
+ }
+
+ [Fact]
+ public void RenderUsesRating()
+ {
+ string render = Gravatar.GetHtml("foo@bar.com", rating: GravatarRating.G).ToString();
+ Assert.Equal(
+ "<img src=\"http://www.gravatar.com/avatar/f3ada405ce890b6f8204094deb12d8a8?s=80&amp;r=g\" alt=\"gravatar\" />",
+ render);
+ }
+
+ [Fact]
+ public void RenderWithAttributes()
+ {
+ string render = Gravatar.GetHtml("foo@bar.com",
+ attributes: new { id = "gravatar", alT = "<b>foo@bar.com</b>", srC = "ignored" }).ToString();
+ // beware of attributes ordering in tests
+ Assert.Equal(
+ "<img src=\"http://www.gravatar.com/avatar/f3ada405ce890b6f8204094deb12d8a8?s=80\" alT=\"&lt;b>foo@bar.com&lt;/b>\" id=\"gravatar\" />",
+ render);
+ }
+
+ [Fact]
+ public void RenderWithDefaults()
+ {
+ string render = Gravatar.GetHtml("foo@bar.com").ToString();
+ Assert.Equal(
+ "<img src=\"http://www.gravatar.com/avatar/f3ada405ce890b6f8204094deb12d8a8?s=80\" alt=\"gravatar\" />",
+ render);
+ }
+
+ [Fact]
+ public void RenderWithExtension()
+ {
+ string render = Gravatar.GetHtml("foo@bar.com", imageExtension: ".png").ToString();
+ Assert.Equal(
+ "<img src=\"http://www.gravatar.com/avatar/f3ada405ce890b6f8204094deb12d8a8.png?s=80\" alt=\"gravatar\" />",
+ render);
+
+ render = Gravatar.GetHtml("foo@bar.com", imageExtension: "xyz").ToString();
+ Assert.Equal(
+ "<img src=\"http://www.gravatar.com/avatar/f3ada405ce890b6f8204094deb12d8a8.xyz?s=80\" alt=\"gravatar\" />",
+ render);
+ }
+ }
+}
diff --git a/test/Microsoft.Web.Helpers.Test/LinkShareTest.cs b/test/Microsoft.Web.Helpers.Test/LinkShareTest.cs
new file mode 100644
index 00000000..2d9bc4a2
--- /dev/null
+++ b/test/Microsoft.Web.Helpers.Test/LinkShareTest.cs
@@ -0,0 +1,188 @@
+using System;
+using System.Linq;
+using System.Web;
+using System.Web.Helpers.Test;
+using System.Web.TestUtil;
+using System.Web.WebPages.Scope;
+using System.Web.WebPages.TestUtils;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace Microsoft.Web.Helpers.Test
+{
+ public class LinkShareTest
+ {
+ private static LinkShareSite[] _allLinkShareSites = new[]
+ {
+ LinkShareSite.Delicious, LinkShareSite.Digg, LinkShareSite.GoogleBuzz,
+ LinkShareSite.Facebook, LinkShareSite.Reddit, LinkShareSite.StumbleUpon, LinkShareSite.Twitter
+ };
+
+ [Fact]
+ public void RenderWithFacebookFirst_ReturnsHtmlWithFacebookAndThenOthersTest()
+ {
+ string pageTitle = "page1";
+ string pageLinkBack = "page link back";
+ string twitterUserName = String.Empty;
+ string twitterTag = String.Empty;
+ string actual;
+ actual = LinkShare.GetHtml(pageTitle, pageLinkBack, twitterUserName, twitterTag, LinkShareSite.Facebook, LinkShareSite.All).ToString();
+ Assert.True(actual.Contains("twitter.com"));
+ int pos = actual.IndexOf("facebook.com");
+ Assert.True(pos > 0);
+ int pos2 = actual.IndexOf("reddit.com");
+ Assert.True(pos2 > pos);
+ pos2 = actual.IndexOf("digg.com");
+ Assert.True(pos2 > pos);
+ }
+
+ [Fact]
+ public void BitlyApiKeyThrowsWhenSetToNull()
+ {
+ Assert.ThrowsArgumentNull(() => LinkShare.BitlyApiKey = null, "value");
+ }
+
+ [Fact]
+ public void BitlyApiKeyUsesScopeStorage()
+ {
+ // Arrange
+ var value = "value";
+
+ // Act
+ LinkShare.BitlyApiKey = value;
+
+ // Assert
+ Assert.Equal(LinkShare.BitlyApiKey, value);
+ Assert.Equal(ScopeStorage.CurrentScope[LinkShare._bitlyApiKey], value);
+ }
+
+ [Fact]
+ public void BitlyLoginThrowsWhenSetToNull()
+ {
+ Assert.ThrowsArgumentNull(() => LinkShare.BitlyLogin = null, "value");
+ }
+
+ [Fact]
+ public void BitlyLoginUsesScopeStorage()
+ {
+ // Arrange
+ var value = "value";
+
+ // Act
+ LinkShare.BitlyLogin = value;
+
+ // Assert
+ Assert.Equal(LinkShare.BitlyLogin, value);
+ Assert.Equal(ScopeStorage.CurrentScope[LinkShare._bitlyLogin], value);
+ }
+
+ [Fact]
+ public void RenderWithNullPageTitle_ThrowsException()
+ {
+ Assert.ThrowsArgumentNullOrEmptyString(
+ () => LinkShare.GetHtml(null).ToString(),
+ "pageTitle");
+ }
+
+ [Fact]
+ public void Render_WithFacebook_Works()
+ {
+ string actualHTML = LinkShare.GetHtml("page-title", "www.foo.com", linkSites: LinkShareSite.Facebook).ToString();
+ string expectedHTML =
+ "<a href=\"http://www.facebook.com/sharer.php?u=www.foo.com&amp;t=page-title\" target=\"_blank\" title=\"Share on Facebook\"><img alt=\"Share on Facebook\" src=\"http://www.facebook.com/favicon.ico\" style=\"border:0; height:16px; width:16px; margin:0 1px;\" title=\"Share on Facebook\" /></a>";
+ UnitTestHelper.AssertEqualsIgnoreWhitespace(actualHTML, expectedHTML);
+ }
+
+ [Fact]
+ public void Render_WithFacebookAndDigg_Works()
+ {
+ string actualHTML = LinkShare.GetHtml("page-title", "www.foo.com", linkSites: new[] { LinkShareSite.Facebook, LinkShareSite.Digg }).ToString();
+ string expectedHTML =
+ "<a href=\"http://www.facebook.com/sharer.php?u=www.foo.com&amp;t=page-title\" target=\"_blank\" title=\"Share on Facebook\"><img alt=\"Share on Facebook\" src=\"http://www.facebook.com/favicon.ico\" style=\"border:0; height:16px; width:16px; margin:0 1px;\" title=\"Share on Facebook\" /></a><a href=\"http://digg.com/submit?url=www.foo.com&amp;title=page-title\" target=\"_blank\" title=\"Digg!\"><img alt=\"Digg!\" src=\"http://digg.com/img/badges/16x16-digg-guy.gif\" style=\"border:0; height:16px; width:16px; margin:0 1px;\" title=\"Digg!\" /></a>";
+ UnitTestHelper.AssertEqualsIgnoreWhitespace(actualHTML, expectedHTML);
+ }
+
+ [Fact]
+ public void Render_WithFacebook_RendersAnchorTitle()
+ {
+ string actualHTML = LinkShare.GetHtml("page-title", "www.foo.com", linkSites: LinkShareSite.Facebook).ToString();
+ string expectedHtml = @"<a href=""http://www.facebook.com/sharer.php?u=www.foo.com&amp;t=page-title"" target=""_blank"" title=""Share on Facebook"">
+ <img alt=""Share on Facebook"" src=""http://www.facebook.com/favicon.ico"" style=""border:0; height:16px; width:16px; margin:0 1px;"" title=""Share on Facebook"" />
+ </a>";
+
+ UnitTestHelper.AssertEqualsIgnoreWhitespace(expectedHtml, actualHTML);
+ }
+
+ [Fact]
+ public void LinkShare_GetSitesInOrderReturnsAllSitesWhenArgumentIsNull()
+ {
+ // Act and Assert
+ var result = LinkShare.GetSitesInOrder(linkSites: null);
+
+ Assert.Equal(_allLinkShareSites, result.ToArray());
+ }
+
+ [Fact]
+ public void LinkShare_GetSitesInOrderReturnsAllSitesWhenArgumentIEmpty()
+ {
+ // Act
+ var result = LinkShare.GetSitesInOrder(linkSites: new LinkShareSite[] { });
+
+ // Assert
+ Assert.Equal(_allLinkShareSites, result.ToArray());
+ }
+
+ [Fact]
+ public void LinkShare_GetSitesInOrderReturnsAllSitesWhenAllIsFirstItem()
+ {
+ // Act
+ var result = LinkShare.GetSitesInOrder(linkSites: new[] { LinkShareSite.All, LinkShareSite.Reddit });
+
+ // Assert
+ Assert.Equal(_allLinkShareSites, result.ToArray());
+ }
+
+ [Fact]
+ public void LinkShare_GetSitesInOrderReturnsSitesInOrderWhenAllIsNotFirstItem()
+ {
+ // Act
+ var result = LinkShare.GetSitesInOrder(linkSites: new[] { LinkShareSite.Reddit, LinkShareSite.Facebook, LinkShareSite.All });
+
+ // Assert
+ Assert.Equal(new[]
+ {
+ LinkShareSite.Reddit, LinkShareSite.Facebook, LinkShareSite.Delicious, LinkShareSite.Digg,
+ LinkShareSite.GoogleBuzz, LinkShareSite.StumbleUpon, LinkShareSite.Twitter
+ }, result.ToArray());
+ }
+
+ [Fact]
+ public void LinkShare_EncodesParameters()
+ {
+ // Arrange
+ var expectedHtml =
+ @"<a href=""http://reddit.com/submit?url=www.foo.com&amp;title=%26%26"" target=""_blank"" title=""Reddit!"">
+ <img alt=""Reddit!"" src=""http://www.Reddit.com/favicon.ico"" style=""border:0; height:16px; width:16px; margin:0 1px;"" title=""Reddit!"" />
+ </a>
+ <a href=""http://twitter.com/home/?status=%26%26%3a+www.foo.com%2c+(via+%40%40%3cTweeter+Bot%3e)+I+%3c3+Tweets"" target=""_blank"" title=""Share on Twitter"">
+ <img alt=""Share on Twitter"" src=""http://twitter.com/favicon.ico"" style=""border:0; height:16px; width:16px; margin:0 1px;"" title=""Share on Twitter"" />
+ </a>";
+
+ // Act
+ var actualHtml = LinkShare.GetHtml("&&", "www.foo.com", "<Tweeter Bot>", "I <3 Tweets", LinkShareSite.Reddit, LinkShareSite.Twitter).ToString();
+
+ // Assert
+ UnitTestHelper.AssertEqualsIgnoreWhitespace(expectedHtml, actualHtml);
+ }
+
+ [Fact]
+ public void LinkshareRendersValidXhtml()
+ {
+ string result = "<html> <head> \n <title> </title> \n </head> \n <body> <div> \n" +
+ LinkShare.GetHtml("any<>title", "my test page <>") +
+ "\n </div> </body> \n </html>";
+ HtmlString htmlResult = new HtmlString(result);
+ XhtmlAssert.Validate1_0(htmlResult);
+ }
+ }
+}
diff --git a/test/Microsoft.Web.Helpers.Test/MapsTest.cs b/test/Microsoft.Web.Helpers.Test/MapsTest.cs
new file mode 100644
index 00000000..ec9aacf0
--- /dev/null
+++ b/test/Microsoft.Web.Helpers.Test/MapsTest.cs
@@ -0,0 +1,55 @@
+using System;
+using System.Linq;
+using Xunit;
+
+namespace Microsoft.Web.Helpers.Test
+{
+ public class MapsTest
+ {
+ [Fact]
+ public void GetDirectionsQueryReturnsLocationIfNotEmpty()
+ {
+ // Arrange
+ var location = "a%";
+ var latitude = "12.34";
+ var longitude = "-56.78";
+
+ // Act
+ string result = Maps.GetDirectionsQuery(location, latitude, longitude);
+
+ // Assert
+ Assert.Equal("a%25", result);
+ }
+
+ [Fact]
+ public void GetDirectionsQueryReturnsLatitudeLongitudeIfLocationIsEmpty()
+ {
+ // Arrange
+ var location = "";
+ var latitude = "12.34%";
+ var longitude = "-&56.78";
+
+ // Act
+ string result = Maps.GetDirectionsQuery(location, latitude, longitude);
+
+ // Assert
+ Assert.Equal("12.34%25%2c-%2656.78", result);
+ }
+
+ [Fact]
+ public void GetDirectionsQueryUsesSpecifiedEncoder()
+ {
+ // Arrange
+ var location = "24 gnidliuB tfosorciM";
+ var latitude = "12.34%";
+ var longitude = "-&56.78";
+ Func<string, string> encoder = k => new String(k.Reverse().ToArray());
+
+ // Act
+ string result = Maps.GetDirectionsQuery(location, latitude, longitude, encoder);
+
+ // Assert
+ Assert.Equal("Microsoft Building 42", result);
+ }
+ }
+}
diff --git a/test/Microsoft.Web.Helpers.Test/Microsoft.Web.Helpers.Test.csproj b/test/Microsoft.Web.Helpers.Test/Microsoft.Web.Helpers.Test.csproj
new file mode 100644
index 00000000..a7b7feb9
--- /dev/null
+++ b/test/Microsoft.Web.Helpers.Test/Microsoft.Web.Helpers.Test.csproj
@@ -0,0 +1,108 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory),Runtime.sln))\tools\WebStack.settings.targets" />
+ <PropertyGroup>
+ <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+ <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+ <ProductVersion>9.0.30729</ProductVersion>
+ <SchemaVersion>2.0</SchemaVersion>
+ <ProjectGuid>{2C653A66-8159-4A41-954F-A67915DFDA87}</ProjectGuid>
+ <OutputType>Library</OutputType>
+ <AppDesignerFolder>Properties</AppDesignerFolder>
+ <RootNamespace>Microsoft.Web.Helpers.Test</RootNamespace>
+ <AssemblyName>Microsoft.Web.Helpers.Test</AssemblyName>
+ <FileAlignment>512</FileAlignment>
+ <ProjectTypeGuids>{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
+ <ErrorReport>prompt</ErrorReport>
+ <WarningLevel>4</WarningLevel>
+ <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
+ <DebugSymbols>true</DebugSymbols>
+ <DebugType>full</DebugType>
+ <Optimize>false</Optimize>
+ <OutputPath>$(WebStackRootPath)\bin\Debug\Test\</OutputPath>
+ <DefineConstants>TRACE;DEBUG</DefineConstants>
+ <CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
+ <DebugType>pdbonly</DebugType>
+ <Optimize>true</Optimize>
+ <OutputPath>$(WebStackRootPath)\bin\Release\Test\</OutputPath>
+ <DefineConstants>TRACE</DefineConstants>
+ <CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)' == 'CodeCoverage' ">
+ <DebugSymbols>true</DebugSymbols>
+ <DebugType>full</DebugType>
+ <Optimize>false</Optimize>
+ <OutputPath>$(WebStackRootPath)\bin\CodeCoverage\Test\</OutputPath>
+ <DefineConstants>TRACE;DEBUG</DefineConstants>
+ <CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
+ </PropertyGroup>
+ <ItemGroup>
+ <Reference Include="Microsoft.CSharp" />
+ <Reference Include="Moq, Version=4.0.10827.0, Culture=neutral, PublicKeyToken=69f491c39445e920, processorArchitecture=MSIL">
+ <HintPath>..\..\packages\Moq.4.0.10827\lib\NET40\Moq.dll</HintPath>
+ </Reference>
+ <Reference Include="System" />
+ <Reference Include="System.Web" />
+ <Reference Include="xunit">
+ <HintPath>..\..\packages\xunit.1.9.0.1566\lib\xunit.dll</HintPath>
+ </Reference>
+ <Reference Include="xunit.extensions">
+ <HintPath>..\..\packages\xunit.extensions.1.9.0.1566\lib\xunit.extensions.dll</HintPath>
+ </Reference>
+ </ItemGroup>
+ <ItemGroup>
+ <Compile Include="AnalyticsTest.cs" />
+ <Compile Include="BingTest.cs" />
+ <Compile Include="FacebookTest.cs" />
+ <Compile Include="FileUploadTest.cs" />
+ <Compile Include="GamerCardTest.cs" />
+ <Compile Include="GravatarTest.cs" />
+ <Compile Include="LinkShareTest.cs" />
+ <Compile Include="MapsTest.cs" />
+ <Compile Include="PreAppStartCodeTest.cs" />
+ <Compile Include="ThemesTest.cs" />
+ <Compile Include="TwitterTest.cs" />
+ <Compile Include="VideoTest.cs" />
+ <Compile Include="Properties\AssemblyInfo.cs" />
+ <Compile Include="ReCaptchaTest.cs" />
+ <Compile Include="UrlBuilderTest.cs" />
+ </ItemGroup>
+ <ItemGroup>
+ <ProjectReference Include="..\..\src\Microsoft.Web.Helpers\Microsoft.Web.Helpers.csproj">
+ <Project>{0C7CE809-0F72-4C19-8C64-D6573E4D9521}</Project>
+ <Name>Microsoft.Web.Helpers</Name>
+ </ProjectReference>
+ <ProjectReference Include="..\..\src\System.Web.Helpers\System.Web.Helpers.csproj">
+ <Project>{9B7E3740-6161-4548-833C-4BBCA43B970E}</Project>
+ <Name>System.Web.Helpers</Name>
+ </ProjectReference>
+ <ProjectReference Include="..\..\src\System.Web.Razor\System.Web.Razor.csproj">
+ <Project>{8F18041B-9410-4C36-A9C5-067813DF5F31}</Project>
+ <Name>System.Web.Razor</Name>
+ </ProjectReference>
+ <ProjectReference Include="..\..\src\System.Web.WebPages.Razor\System.Web.WebPages.Razor.csproj">
+ <Project>{0939B11A-FE4E-4BA1-8AD6-D97741EE314F}</Project>
+ <Name>System.Web.WebPages.Razor</Name>
+ </ProjectReference>
+ <ProjectReference Include="..\..\src\System.Web.WebPages\System.Web.WebPages.csproj">
+ <Project>{76EFA9C5-8D7E-4FDF-B710-E20F8B6B00D2}</Project>
+ <Name>System.Web.WebPages</Name>
+ </ProjectReference>
+ <ProjectReference Include="..\Microsoft.TestCommon\Microsoft.TestCommon.csproj">
+ <Project>{FCCC4CB7-BAF7-4A57-9F89-E5766FE536C0}</Project>
+ <Name>Microsoft.TestCommon</Name>
+ </ProjectReference>
+ <ProjectReference Include="..\System.Web.Helpers.Test\System.Web.Helpers.Test.csproj">
+ <Project>{D3313BDF-8071-4AC8-9D98-ABF7F9E88A57}</Project>
+ <Name>System.Web.Helpers.Test</Name>
+ </ProjectReference>
+ </ItemGroup>
+ <ItemGroup>
+ <None Include="packages.config" />
+ </ItemGroup>
+ <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
+</Project> \ No newline at end of file
diff --git a/test/Microsoft.Web.Helpers.Test/PreAppStartCodeTest.cs b/test/Microsoft.Web.Helpers.Test/PreAppStartCodeTest.cs
new file mode 100644
index 00000000..3f79b448
--- /dev/null
+++ b/test/Microsoft.Web.Helpers.Test/PreAppStartCodeTest.cs
@@ -0,0 +1,31 @@
+using System.Linq;
+using System.Web.WebPages.Razor;
+using System.Web.WebPages.TestUtils;
+using Xunit;
+
+namespace Microsoft.Web.Helpers.Test
+{
+ public class PreApplicationStartCodeTest
+ {
+ [Fact]
+ public void StartTest()
+ {
+ AppDomainUtils.RunInSeparateAppDomain(() =>
+ {
+ // Act
+ AppDomainUtils.SetPreAppStartStage();
+ PreApplicationStartCode.Start();
+
+ // Assert
+ var imports = WebPageRazorHost.GetGlobalImports();
+ Assert.True(imports.Any(ns => ns.Equals("Microsoft.Web.Helpers")));
+ });
+ }
+
+ [Fact]
+ public void TestPreAppStartClass()
+ {
+ PreAppStartTestHelper.TestPreAppStartClass(typeof(PreApplicationStartCode));
+ }
+ }
+}
diff --git a/test/Microsoft.Web.Helpers.Test/Properties/AssemblyInfo.cs b/test/Microsoft.Web.Helpers.Test/Properties/AssemblyInfo.cs
new file mode 100644
index 00000000..1b4a4e5a
--- /dev/null
+++ b/test/Microsoft.Web.Helpers.Test/Properties/AssemblyInfo.cs
@@ -0,0 +1,34 @@
+using System.Reflection;
+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("Microsoft.Web.Helpers.Test")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("MSIT")]
+[assembly: AssemblyProduct("Microsoft.Web.Helpers.Test")]
+[assembly: AssemblyCopyright("Copyright MSIT 2010")]
+[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)]
+
+// 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/test/Microsoft.Web.Helpers.Test/ReCaptchaTest.cs b/test/Microsoft.Web.Helpers.Test/ReCaptchaTest.cs
new file mode 100644
index 00000000..dbcc3dad
--- /dev/null
+++ b/test/Microsoft.Web.Helpers.Test/ReCaptchaTest.cs
@@ -0,0 +1,251 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Collections.Specialized;
+using System.Web;
+using System.Web.Helpers.Test;
+using System.Web.TestUtil;
+using System.Web.WebPages.Scope;
+using Moq;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace Microsoft.Web.Helpers.Test
+{
+ public class ReCaptchaTest
+ {
+ [Fact]
+ public void ReCaptchaOptionsMissingWhenNoOptionsAndDefaultRendering()
+ {
+ var html = ReCaptcha.GetHtml(GetContext(), "PUBLIC_KEY");
+ UnitTestHelper.AssertEqualsIgnoreWhitespace(
+ @"<script src=""http://www.google.com/recaptcha/api/challenge?k=PUBLIC_KEY"" type=""text/javascript""></script>" +
+ @"<noscript>" +
+ @"<iframe frameborder=""0"" height=""300px"" src=""http://www.google.com/recaptcha/api/noscript?k=PUBLIC_KEY"" width=""500px""></iframe><br/><br/>" +
+ @"<textarea cols=""40"" name=""recaptcha_challenge_field"" rows=""3""></textarea>" +
+ @"<input name=""recaptcha_response_field"" type=""hidden"" value=""manual_challenge""/>" +
+ @"</noscript>",
+ html.ToString());
+ XhtmlAssert.Validate1_0(html, addRoot: true);
+ }
+
+ [Fact]
+ public void ReCaptchaOptionsWhenOneOptionAndDefaultRendering()
+ {
+ var html = ReCaptcha.GetHtml(GetContext(), "PUBLIC_KEY", options: new { theme = "white" });
+ UnitTestHelper.AssertEqualsIgnoreWhitespace(
+ @"<script type=""text/javascript"">var RecaptchaOptions={""theme"":""white""};</script>" +
+ @"<script src=""http://www.google.com/recaptcha/api/challenge?k=PUBLIC_KEY"" type=""text/javascript""></script>" +
+ @"<noscript>" +
+ @"<iframe frameborder=""0"" height=""300px"" src=""http://www.google.com/recaptcha/api/noscript?k=PUBLIC_KEY"" width=""500px""></iframe><br/><br/>" +
+ @"<textarea cols=""40"" name=""recaptcha_challenge_field"" rows=""3""></textarea>" +
+ @"<input name=""recaptcha_response_field"" type=""hidden"" value=""manual_challenge""/>" +
+ @"</noscript>",
+ html.ToString());
+ XhtmlAssert.Validate1_0(html, addRoot: true);
+ }
+
+ [Fact]
+ public void ReCaptchaOptionsWhenMultipleOptionsAndDefaultRendering()
+ {
+ var html = ReCaptcha.GetHtml(GetContext(), "PUBLIC_KEY", options: new { theme = "white", tabindex = 5 });
+ UnitTestHelper.AssertEqualsIgnoreWhitespace(
+ @"<script type=""text/javascript"">var RecaptchaOptions={""theme"":""white"",""tabindex"":5};</script>" +
+ @"<script src=""http://www.google.com/recaptcha/api/challenge?k=PUBLIC_KEY"" type=""text/javascript""></script>" +
+ @"<noscript>" +
+ @"<iframe frameborder=""0"" height=""300px"" src=""http://www.google.com/recaptcha/api/noscript?k=PUBLIC_KEY"" width=""500px""></iframe><br/><br/>" +
+ @"<textarea cols=""40"" name=""recaptcha_challenge_field"" rows=""3""></textarea>" +
+ @"<input name=""recaptcha_response_field"" type=""hidden"" value=""manual_challenge""/>" +
+ @"</noscript>",
+ html.ToString());
+ XhtmlAssert.Validate1_0(html, addRoot: true);
+ }
+
+ [Fact]
+ public void ReCaptchaOptionsWhenMultipleOptionsFromDictionaryAndDefaultRendering()
+ {
+ // verifies that a dictionary will serialize the same as a projection
+ var options = new Dictionary<string, object> { { "theme", "white" }, { "tabindex", 5 } };
+ var html = ReCaptcha.GetHtml(GetContext(), "PUBLIC_KEY", options: options);
+ UnitTestHelper.AssertEqualsIgnoreWhitespace(
+ @"<script type=""text/javascript"">var RecaptchaOptions={""theme"":""white"",""tabindex"":5};</script>" +
+ @"<script src=""http://www.google.com/recaptcha/api/challenge?k=PUBLIC_KEY"" type=""text/javascript""></script>" +
+ @"<noscript>" +
+ @"<iframe frameborder=""0"" height=""300px"" src=""http://www.google.com/recaptcha/api/noscript?k=PUBLIC_KEY"" width=""500px""></iframe><br/><br/>" +
+ @"<textarea cols=""40"" name=""recaptcha_challenge_field"" rows=""3""></textarea>" +
+ @"<input name=""recaptcha_response_field"" type=""hidden"" value=""manual_challenge""/>" +
+ @"</noscript>",
+ html.ToString());
+ XhtmlAssert.Validate1_0(html, addRoot: true);
+ }
+
+ [Fact]
+ public void RenderUsesLastError()
+ {
+ HttpContextBase context = GetContext();
+ ReCaptcha.HandleValidateResponse(context, "false\nincorrect-captcha-sol");
+ var html = ReCaptcha.GetHtml(context, "PUBLIC_KEY");
+ UnitTestHelper.AssertEqualsIgnoreWhitespace(
+ @"<script src=""http://www.google.com/recaptcha/api/challenge?k=PUBLIC_KEY&amp;error=incorrect-captcha-sol"" type=""text/javascript""></script>" +
+ @"<noscript>" +
+ @"<iframe frameborder=""0"" height=""300px"" src=""http://www.google.com/recaptcha/api/noscript?k=PUBLIC_KEY"" width=""500px""></iframe><br/><br/>" +
+ @"<textarea cols=""40"" name=""recaptcha_challenge_field"" rows=""3""></textarea>" +
+ @"<input name=""recaptcha_response_field"" type=""hidden"" value=""manual_challenge""/>" +
+ @"</noscript>",
+ html.ToString());
+ XhtmlAssert.Validate1_0(html, addRoot: true);
+ }
+
+ [Fact]
+ public void RenderWhenConnectionIsSecure()
+ {
+ var html = ReCaptcha.GetHtml(GetContext(isSecure: true), "PUBLIC_KEY");
+ UnitTestHelper.AssertEqualsIgnoreWhitespace(
+ @"<script src=""https://www.google.com/recaptcha/api/challenge?k=PUBLIC_KEY"" type=""text/javascript""></script>" +
+ @"<noscript>" +
+ @"<iframe frameborder=""0"" height=""300px"" src=""https://www.google.com/recaptcha/api/noscript?k=PUBLIC_KEY"" width=""500px""></iframe><br/><br/>" +
+ @"<textarea cols=""40"" name=""recaptcha_challenge_field"" rows=""3""></textarea>" +
+ @"<input name=""recaptcha_response_field"" type=""hidden"" value=""manual_challenge""/>" +
+ @"</noscript>",
+ html.ToString());
+ XhtmlAssert.Validate1_0(html, addRoot: true);
+ }
+
+ [Fact]
+ public void ValidateThrowsWhenRemoteAddressNotAvailable()
+ {
+ HttpContextBase context = GetContext();
+ VirtualPathUtilityBase virtualPathUtility = GetVirtualPathUtility();
+ context.Request.Form["recaptcha_challenge_field"] = "CHALLENGE";
+ context.Request.Form["recaptcha_response_field"] = "RESPONSE";
+
+ Assert.Throws<InvalidOperationException>(() => { ReCaptcha.Validate(context, privateKey: "PRIVATE_KEY", virtualPathUtility: virtualPathUtility).ToString(); }, "The captcha cannot be validated because the remote address was not found in the request.");
+ }
+
+ [Fact]
+ public void ValidateReturnsFalseWhenChallengeNotPosted()
+ {
+ HttpContextBase context = GetContext();
+ VirtualPathUtilityBase virtualPathUtility = GetVirtualPathUtility();
+ context.Request.ServerVariables["REMOTE_ADDR"] = "127.0.0.1";
+
+ Assert.False(ReCaptcha.Validate(context, privateKey: "PRIVATE_KEY", virtualPathUtility: virtualPathUtility));
+ }
+
+ [Fact]
+ public void ValidatePostData()
+ {
+ HttpContextBase context = GetContext();
+ VirtualPathUtilityBase virtualPathUtility = GetVirtualPathUtility();
+ context.Request.ServerVariables["REMOTE_ADDR"] = "127.0.0.1";
+ context.Request.Form["recaptcha_challenge_field"] = "CHALLENGE";
+ context.Request.Form["recaptcha_response_field"] = "RESPONSE";
+
+ Assert.Equal("privatekey=PRIVATE_KEY&remoteip=127.0.0.1&challenge=CHALLENGE&response=RESPONSE",
+ ReCaptcha.GetValidatePostData(context, "PRIVATE_KEY", virtualPathUtility));
+ }
+
+ [Fact]
+ public void ValidatePostDataWhenNoResponse()
+ {
+ // Arrange
+ HttpContextBase context = GetContext();
+ VirtualPathUtilityBase virtualPathUtility = GetVirtualPathUtility();
+ context.Request.ServerVariables["REMOTE_ADDR"] = "127.0.0.1";
+ context.Request.Form["recaptcha_challenge_field"] = "CHALLENGE";
+
+ // Act
+ var validatePostData = ReCaptcha.GetValidatePostData(context, "PRIVATE_KEY", virtualPathUtility);
+
+ // Assert
+ Assert.Equal("privatekey=PRIVATE_KEY&remoteip=127.0.0.1&challenge=CHALLENGE&response=", validatePostData);
+ }
+
+ [Fact]
+ public void ValidateResponseReturnsFalseOnEmptyReCaptchaResponse()
+ {
+ HttpContextBase context = GetContext();
+ Assert.False(ReCaptcha.HandleValidateResponse(context, ""));
+ Assert.Equal(String.Empty, ReCaptcha.GetLastError(context));
+ }
+
+ [Fact]
+ public void ValidateResponseReturnsTrueOnSuccess()
+ {
+ HttpContextBase context = GetContext();
+ Assert.True(ReCaptcha.HandleValidateResponse(context, "true\nsuccess"));
+ Assert.Equal(String.Empty, ReCaptcha.GetLastError(context));
+ }
+
+ [Fact]
+ public void ValidateResponseReturnsFalseOnError()
+ {
+ HttpContextBase context = GetContext();
+ Assert.False(ReCaptcha.HandleValidateResponse(context, "false\nincorrect-captcha-sol"));
+ Assert.Equal("incorrect-captcha-sol", ReCaptcha.GetLastError(context));
+ }
+
+ [Fact]
+ public void ReCaptchaPrivateKeyThowsWhenSetToNull()
+ {
+ Assert.ThrowsArgumentNull(() => ReCaptcha.PrivateKey = null, "value");
+ }
+
+ [Fact]
+ public void ReCaptchaPrivateKeyUsesScopeStorage()
+ {
+ // Arrange
+ var value = "value";
+
+ // Act
+ ReCaptcha.PrivateKey = value;
+
+ // Assert
+ Assert.Equal(ReCaptcha.PrivateKey, value);
+ Assert.Equal(ScopeStorage.CurrentScope[ReCaptcha._privateKey], value);
+ }
+
+ [Fact]
+ public void PublicKeyThowsWhenSetToNull()
+ {
+ Assert.ThrowsArgumentNull(() => ReCaptcha.PublicKey = null, "value");
+ }
+
+ [Fact]
+ public void ReCaptchaPublicKeyUsesScopeStorage()
+ {
+ // Arrange
+ var value = "value";
+
+ // Act
+ ReCaptcha.PublicKey = value;
+
+ // Assert
+ Assert.Equal(ReCaptcha.PublicKey, value);
+ Assert.Equal(ScopeStorage.CurrentScope[ReCaptcha._publicKey], value);
+ }
+
+ private HttpContextBase GetContext(bool isSecure = false)
+ {
+ // mock HttpRequest
+ Mock<HttpRequestBase> requestMock = new Mock<HttpRequestBase>();
+ requestMock.Setup(request => request.IsSecureConnection).Returns(isSecure);
+ requestMock.Setup(request => request.Form).Returns(new NameValueCollection());
+ requestMock.Setup(request => request.ServerVariables).Returns(new NameValueCollection());
+
+ // mock HttpContext
+ Mock<HttpContextBase> contextMock = new Mock<HttpContextBase>();
+ contextMock.Setup(context => context.Items).Returns(new Hashtable());
+ contextMock.Setup(context => context.Request).Returns(requestMock.Object);
+ return contextMock.Object;
+ }
+
+ private static VirtualPathUtilityBase GetVirtualPathUtility()
+ {
+ var virtualPathUtility = new Mock<VirtualPathUtilityBase>();
+ virtualPathUtility.Setup(c => c.ToAbsolute(It.IsAny<string>())).Returns<string>(_ => _);
+
+ return virtualPathUtility.Object;
+ }
+ }
+}
diff --git a/test/Microsoft.Web.Helpers.Test/ThemesTest.cs b/test/Microsoft.Web.Helpers.Test/ThemesTest.cs
new file mode 100644
index 00000000..d89a1d82
--- /dev/null
+++ b/test/Microsoft.Web.Helpers.Test/ThemesTest.cs
@@ -0,0 +1,373 @@
+using System;
+using System.Collections.Generic;
+using System.Web.Hosting;
+using System.Web.WebPages.Scope;
+using System.Web.WebPages.TestUtils;
+using Moq;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace Microsoft.Web.Helpers.Test
+{
+ public class ThemesTest
+ {
+ [Fact]
+ public void Initialize_WithBadParams_Throws()
+ {
+ // Arrange
+ var mockVpp = new Mock<VirtualPathProvider>().Object;
+ var scope = new ScopeStorageDictionary();
+
+ // Act and Assert
+ Assert.ThrowsArgumentNullOrEmptyString(() => new ThemesImplementation(mockVpp, scope).Initialize(null, "foo"), "themeDirectory");
+ Assert.ThrowsArgumentNullOrEmptyString(() => new ThemesImplementation(mockVpp, scope).Initialize("", "foo"), "themeDirectory");
+
+ Assert.ThrowsArgumentNullOrEmptyString(() => new ThemesImplementation(mockVpp, scope).Initialize("~/folder", null), "defaultTheme");
+ Assert.ThrowsArgumentNullOrEmptyString(() => new ThemesImplementation(mockVpp, scope).Initialize("~/folder", ""), "defaultTheme");
+ }
+
+ [Fact]
+ public void CurrentThemeThrowsIfAssignedNullOrEmpty()
+ {
+ // Arrange
+ var mockVpp = new Mock<VirtualPathProvider>().Object;
+ var scope = new ScopeStorageDictionary();
+ var themesImpl = new ThemesImplementation(mockVpp, scope);
+
+ // Act and Assert
+ Assert.ThrowsArgumentNullOrEmptyString(() => { themesImpl.CurrentTheme = null; }, "value");
+ Assert.ThrowsArgumentNullOrEmptyString(() => { themesImpl.CurrentTheme = String.Empty; }, "value");
+ }
+
+ [Fact]
+ public void InvokingPropertiesAndMethodsBeforeInitializationThrows()
+ {
+ // Arrange
+ var mockVpp = new Mock<VirtualPathProvider>().Object;
+ var scope = new ScopeStorageDictionary();
+ var themesImpl = new ThemesImplementation(mockVpp, scope);
+
+ // Act and Assert
+ Assert.Throws<InvalidOperationException>(() => themesImpl.CurrentTheme = "Foo",
+ @"You must call the ""Themes.Initialize"" method before you call any other method of the ""Themes"" class.");
+
+ Assert.Throws<InvalidOperationException>(() => { var x = themesImpl.CurrentTheme; },
+ @"You must call the ""Themes.Initialize"" method before you call any other method of the ""Themes"" class.");
+
+ Assert.Throws<InvalidOperationException>(() => { var x = themesImpl.AvailableThemes; },
+ @"You must call the ""Themes.Initialize"" method before you call any other method of the ""Themes"" class.");
+
+ Assert.Throws<InvalidOperationException>(() => { var x = themesImpl.DefaultTheme; },
+ @"You must call the ""Themes.Initialize"" method before you call any other method of the ""Themes"" class.");
+
+ Assert.Throws<InvalidOperationException>(() => { var x = themesImpl.GetResourcePath("baz"); },
+ @"You must call the ""Themes.Initialize"" method before you call any other method of the ""Themes"" class.");
+
+ Assert.Throws<InvalidOperationException>(() => { var x = themesImpl.GetResourcePath("baz", "some-file"); },
+ @"You must call the ""Themes.Initialize"" method before you call any other method of the ""Themes"" class.");
+ }
+
+ [Fact]
+ public void InitializeThrowsIfDefaultThemeDirectoryDoesNotExist()
+ {
+ // Arrange
+ var defaultTheme = "default-theme";
+ var themeDirectory = "theme-directory";
+
+ var scope = new ScopeStorageDictionary();
+ var themesImpl = new ThemesImplementation(GetVirtualPathProvider(themeDirectory, new Dir("not-default-theme")), scope);
+
+ // Act And Assert
+ Assert.ThrowsArgument(
+ () => themesImpl.Initialize(themeDirectory: themeDirectory, defaultTheme: defaultTheme),
+ "defaultTheme",
+ "Unknown theme 'default-theme'. Ensure that a directory labeled 'default-theme' exists under the theme directory.");
+ }
+
+ [Fact]
+ public void ThemesImplUsesScopeStorageToStoreProperties()
+ {
+ // Arrange
+ var defaultTheme = "default-theme";
+ var themeDirectory = "theme-directory";
+
+ var scope = new ScopeStorageDictionary();
+ var themesImpl = new ThemesImplementation(GetVirtualPathProvider(themeDirectory, new Dir(defaultTheme)), scope);
+
+ // Act
+ themesImpl.Initialize(themeDirectory: themeDirectory, defaultTheme: defaultTheme);
+
+ // Ensure Theme use scope storage to store properties
+ Assert.Equal(scope[ThemesImplementation.ThemesInitializedKey], true);
+ Assert.Equal(scope[ThemesImplementation.ThemeDirectoryKey], themeDirectory);
+ Assert.Equal(scope[ThemesImplementation.DefaultThemeKey], defaultTheme);
+ }
+
+ [Fact]
+ public void ThemesImplUsesDefaultThemeWhenNoCurrentThemeIsSpecified()
+ {
+ // Arrange
+ var defaultTheme = "default-theme";
+ var themeDirectory = "theme-directory";
+
+ var scope = new ScopeStorageDictionary();
+ var themesImpl = new ThemesImplementation(GetVirtualPathProvider(themeDirectory, new Dir(defaultTheme)), scope);
+ themesImpl.Initialize(themeDirectory, defaultTheme);
+
+ // Act and Assert
+ // CurrentTheme falls back to default theme when null
+ Assert.Equal(themesImpl.CurrentTheme, defaultTheme);
+ }
+
+ [Fact]
+ public void ThemesImplThrowsIfCurrentThemeIsInvalid()
+ {
+ // Arrange
+ var defaultTheme = "default-theme";
+ var themeDirectory = "theme-directory";
+ var themesImpl = new ThemesImplementation(GetVirtualPathProvider(themeDirectory, new Dir(defaultTheme), new Dir("not-a-random-value")), new ScopeStorageDictionary());
+ themesImpl.Initialize(themeDirectory, defaultTheme);
+
+ // Act and Assert
+ Assert.ThrowsArgument(() => themesImpl.CurrentTheme = "random-value",
+ "value",
+ "Unknown theme 'random-value'. Ensure that a directory labeled 'random-value' exists under the theme directory.");
+ }
+
+ [Fact]
+ public void ThemesImplUsesScopeStorageToStoreCurrentTheme()
+ {
+ // Arrange
+ var defaultTheme = "default-theme";
+ var themeDirectory = "theme-directory";
+ var currentThemeDir = "custom-theme-dir";
+ var scope = new ScopeStorageDictionary();
+ var themesImpl = new ThemesImplementation(GetVirtualPathProvider(themeDirectory, new Dir(defaultTheme), new Dir("custom-theme-dir")), scope);
+
+ // Act
+ themesImpl.Initialize(themeDirectory, defaultTheme);
+ themesImpl.CurrentTheme = currentThemeDir;
+
+ // Assert
+ Assert.Equal(scope[ThemesImplementation.CurrentThemeKey], currentThemeDir);
+ }
+
+ [Fact]
+ public void GetResourcePathThrowsIfCurrentDirectoryIsNull()
+ {
+ // Arrange
+ var themesImpl = new ThemesImplementation(scopeStorage: new ScopeStorageDictionary(),
+ vpp: GetVirtualPathProvider("themes", new Dir("default"), new Dir("mobile"), new Dir(@"mobile", "wp7.css")));
+ themesImpl.Initialize("themes", "default");
+
+ // Act and Assert
+ Assert.ThrowsArgumentNull(() => themesImpl.GetResourcePath(folder: null, fileName: "wp7.css"), "folder");
+ }
+
+ [Fact]
+ public void GetResourcePathThrowsIfFileNameIsNullOrEmpty()
+ {
+ // Arrange
+ var themesImpl = new ThemesImplementation(scopeStorage: new ScopeStorageDictionary(),
+ vpp: GetVirtualPathProvider("themes", new Dir("default"), new Dir("mobile"), new Dir(@"mobile", "wp7.css")));
+ themesImpl.Initialize("themes", "default");
+
+ // Act and Assert
+ Assert.ThrowsArgumentNullOrEmptyString(() => themesImpl.GetResourcePath(folder: String.Empty, fileName: null), "fileName");
+ Assert.ThrowsArgumentNullOrEmptyString(() => themesImpl.GetResourcePath(folder: String.Empty, fileName: String.Empty), "fileName");
+ }
+
+ [Fact]
+ public void GetResourcePathReturnsItemFromThemeRootIfAvailable()
+ {
+ // Arrange
+ var themesImpl = new ThemesImplementation(scopeStorage: new ScopeStorageDictionary(),
+ vpp: GetVirtualPathProvider("themes", new Dir("default"), new Dir("mobile"), new Dir(@"mobile", "wp7.css")));
+ themesImpl.Initialize("themes", "default");
+
+ // Act
+ themesImpl.CurrentTheme = "mobile";
+ var themePath = themesImpl.GetResourcePath(fileName: "wp7.css");
+
+ // Assert
+ Assert.Equal(themePath, @"themes/mobile/wp7.css");
+ }
+
+ [Fact]
+ public void GetResourcePathReturnsItemFromCurrentThemeDirectoryIfAvailable()
+ {
+ // Arrange
+ var themesImpl = new ThemesImplementation(scopeStorage: new ScopeStorageDictionary(),
+ vpp: GetVirtualPathProvider("themes", new Dir("default"), new Dir("mobile"), new Dir(@"mobile\styles", "wp7.css"), new Dir(@"default\styles", "main.css")));
+ themesImpl.Initialize("themes", "default");
+
+ // Act
+ themesImpl.CurrentTheme = "mobile";
+ var themePath = themesImpl.GetResourcePath(folder: "styles", fileName: "wp7.css");
+
+ // Assert
+ Assert.Equal(themePath, @"themes/mobile/styles/wp7.css");
+ }
+
+ [Fact]
+ public void GetResourcePathReturnsItemFromDefaultThemeDirectoryIfNotFoundInCurrentThemeDirectory()
+ {
+ // Arrange
+ var themesImpl = new ThemesImplementation(scopeStorage: new ScopeStorageDictionary(),
+ vpp: GetVirtualPathProvider("themes", new Dir("default"), new Dir("mobile"), new Dir(@"mobile\styles", "wp7.css"), new Dir(@"default\styles", "main.css")));
+ themesImpl.Initialize("themes", "default");
+
+ // Act
+ themesImpl.CurrentTheme = "mobile";
+ var themePath = themesImpl.GetResourcePath(folder: "styles", fileName: "main.css");
+
+ // Assert
+ Assert.Equal(themePath, @"themes/default/styles/main.css");
+ }
+
+ [Fact]
+ public void GetResourcePathReturnsNullIfDirectoryDoesNotExist()
+ {
+ // Arrange
+ var themesImpl = new ThemesImplementation(scopeStorage: new ScopeStorageDictionary(),
+ vpp: GetVirtualPathProvider("themes", new Dir("default"), new Dir("mobile"), new Dir(@"mobile\styles", "wp7.css"), new Dir(@"default\styles", "main.css")));
+ themesImpl.Initialize("themes", "default");
+
+ // Act
+ themesImpl.CurrentTheme = "mobile";
+ var themePath = themesImpl.GetResourcePath(folder: "does-not-exist", fileName: "main.css");
+
+ // Assert
+ Assert.Null(themePath);
+ }
+
+ [Fact]
+ public void GetResourcePathReturnsNullIfItemNotFoundInCurrentAndDefaultThemeDirectories()
+ {
+ // Arrange
+ var themesImpl = new ThemesImplementation(scopeStorage: new ScopeStorageDictionary(),
+ vpp: GetVirtualPathProvider("themes", new Dir("default"), new Dir("mobile"), new Dir(@"mobile\styles", "wp7.css"), new Dir(@"default\styles", "main.css")));
+ themesImpl.Initialize("themes", "default");
+
+ // Act
+ themesImpl.CurrentTheme = "mobile";
+ var themePath = themesImpl.GetResourcePath(folder: "styles", fileName: "awesome-blinking-text.css");
+
+ // Assert
+ Assert.Null(themePath);
+ }
+
+ [Fact]
+ public void AvaliableThemesReturnsTopLevelDirectoriesUnderThemeDirectory()
+ {
+ // Arrange
+ var themesImpl = new ThemesImplementation(scopeStorage: new ScopeStorageDictionary(),
+ vpp: GetVirtualPathProvider("themes", new Dir("default"), new Dir("mobile"), new Dir("rotary-phone")));
+ // Act
+ themesImpl.Initialize("themes", "default");
+ var themes = themesImpl.AvailableThemes;
+
+ // Assert
+ Assert.Equal(3, themes.Count);
+ Assert.Equal(themes[0], "default");
+ Assert.Equal(themes[1], "mobile");
+ Assert.Equal(themes[2], "rotary-phone");
+ }
+
+ /// <remarks>
+ /// // folder structure:
+ /// // /root
+ /// // /foo
+ /// // /bar.cs
+ /// // testing that a file specified as foo/bar in folder root will return null
+ /// </remarks>
+ [Fact]
+ public void FileWithSlash_ReturnsNull()
+ {
+ // Arrange
+ var themesImpl = new ThemesImplementation(scopeStorage: new ScopeStorageDictionary(),
+ vpp: GetVirtualPathProvider("themes", new Dir("default"), new Dir("root"), new Dir(@"root\foo", "wp7.css"), new Dir(@"default\styles", "main.css")));
+
+ // Act
+ var actual = themesImpl.FindMatchingFile("root", "foo/bar.cs");
+
+ // Assert
+ Assert.Null(actual);
+ }
+
+ [Fact]
+ public void DirectoryWithNoFilesReturnsNull()
+ {
+ // Arrange
+ var themesImpl = new ThemesImplementation(scopeStorage: new ScopeStorageDictionary(),
+ vpp: GetVirtualPathProvider("themes", new Dir("default"), new Dir("empty-dir")));
+
+ // Act
+ var actual = themesImpl.FindMatchingFile(@"themes\empty-dir", "main.css");
+
+ // Assert
+ Assert.Null(actual);
+ }
+
+ [Fact]
+ public void MatchingFiles_ReturnsCorrectFile()
+ {
+ // Arrange
+ var themesImpl = new ThemesImplementation(scopeStorage: new ScopeStorageDictionary(),
+ vpp: GetVirtualPathProvider("themes", new Dir(@"nomatchingfiles", "foo.cs")));
+
+ // Act
+ var bar = themesImpl.FindMatchingFile(@"themes\nomatchingfiles", "bar.cs");
+ var foo = themesImpl.FindMatchingFile(@"themes\nomatchingfiles", "foo.cs");
+
+ // Assert
+ Assert.Null(bar);
+ Assert.Equal(@"themes/nomatchingfiles/foo.cs", foo);
+ }
+
+ private static VirtualPathProvider GetVirtualPathProvider(string themeRoot, params Dir[] fileSystem)
+ {
+ var mockVpp = new Mock<VirtualPathProvider>();
+ var dirRoot = new Mock<VirtualDirectory>(themeRoot);
+
+ var themeDirectories = new List<VirtualDirectory>();
+ foreach (var directory in fileSystem)
+ {
+ var dir = new Mock<VirtualDirectory>(directory.Name);
+ var directoryPath = themeRoot + '\\' + directory.Name;
+ dir.SetupGet(d => d.Name).Returns(directory.Name);
+ mockVpp.Setup(c => c.GetDirectory(It.Is<string>(p => p.Equals(directoryPath, StringComparison.OrdinalIgnoreCase)))).Returns(dir.Object);
+
+ var fileList = new List<VirtualFile>();
+ foreach (var item in directory.Files)
+ {
+ var filePath = directoryPath + '\\' + item;
+ var file = new Mock<VirtualFile>(filePath);
+ file.SetupGet(f => f.Name).Returns(item);
+ fileList.Add(file.Object);
+ }
+
+ dir.SetupGet(c => c.Files).Returns(fileList);
+ themeDirectories.Add(dir.Object);
+ }
+
+ dirRoot.SetupGet(c => c.Directories).Returns(themeDirectories);
+ mockVpp.Setup(c => c.GetDirectory(themeRoot)).Returns(dirRoot.Object);
+
+ return mockVpp.Object;
+ }
+
+ private class Dir
+ {
+ public Dir(string name, params string[] files)
+ {
+ Name = name;
+ Files = files;
+ }
+
+ public string Name { get; private set; }
+
+ public IEnumerable<string> Files { get; private set; }
+ }
+ }
+}
diff --git a/test/Microsoft.Web.Helpers.Test/TwitterTest.cs b/test/Microsoft.Web.Helpers.Test/TwitterTest.cs
new file mode 100644
index 00000000..ca0d0d24
--- /dev/null
+++ b/test/Microsoft.Web.Helpers.Test/TwitterTest.cs
@@ -0,0 +1,481 @@
+using System.Web;
+using System.Web.Helpers;
+using System.Web.Helpers.Test;
+using System.Web.TestUtil;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace Microsoft.Web.Helpers.Test
+{
+ public class TwitterTest
+ {
+ /// <summary>
+ ///A test for Profile
+ ///</summary>
+ [Fact]
+ public void Profile_ReturnsValidData()
+ {
+ // Arrange
+ string twitterUserName = "my-userName";
+ int width = 100;
+ int height = 100;
+ string backgroundShellColor = "my-backgroundShellColor";
+ string shellColor = "my-shellColor";
+ string tweetsBackgroundColor = "tweetsBackgroundColor";
+ string tweetsColor = "tweets-color";
+ string tweetsLinksColor = "tweets Linkcolor";
+ bool scrollBar = false;
+ bool loop = false;
+ bool live = false;
+ bool hashTags = false;
+ bool timestamp = false;
+ bool avatars = false;
+ var behavior = "all";
+ int searchInterval = 10;
+
+ // Act
+ string actual = Twitter.Profile(twitterUserName, width, height, backgroundShellColor, shellColor, tweetsBackgroundColor, tweetsColor, tweetsLinksColor,
+ 5, scrollBar, loop, live, hashTags, timestamp, avatars, behavior, searchInterval).ToString();
+
+ // Assert
+ var json = GetTwitterJSObject(actual);
+ Assert.Equal(json.type, "profile");
+ Assert.Equal(json.interval, 1000 * searchInterval);
+ Assert.Equal(json.width.ToString(), width.ToString());
+ Assert.Equal(json.height.ToString(), height.ToString());
+ Assert.Equal(json.theme.shell.background, backgroundShellColor);
+ Assert.Equal(json.theme.shell.color, shellColor);
+ Assert.Equal(json.theme.tweets.background, tweetsBackgroundColor);
+ Assert.Equal(json.theme.tweets.color, tweetsColor);
+ Assert.Equal(json.theme.tweets.links, tweetsLinksColor);
+ Assert.Equal(json.features.scrollbar, scrollBar);
+ Assert.Equal(json.features.loop, loop);
+ Assert.Equal(json.features.live, live);
+ Assert.Equal(json.features.hashtags, hashTags);
+ Assert.Equal(json.features.avatars, avatars);
+ Assert.Equal(json.features.behavior, behavior.ToLowerInvariant());
+ Assert.True(actual.Contains(".setUser('my-userName')"));
+ }
+
+ [Fact]
+ public void ProfileJSEncodesParameters()
+ {
+ // Arrange
+ string twitterUserName = "\"my-userName\'";
+ string backgroundShellColor = "\\foo";
+ string shellColor = "<shellColor>\\";
+ string tweetsBackgroundColor = "<tweetsBackgroundColor";
+ string tweetsColor = "<tweetsColor>";
+ string tweetsLinksColor = "<tweetsLinkColor>";
+
+ // Act
+ string actual = Twitter.Profile(twitterUserName, 100, 100, backgroundShellColor, shellColor, tweetsBackgroundColor, tweetsColor, tweetsLinksColor).ToString();
+
+ // Assert
+ Assert.True(actual.Contains("background: '\\\\foo"));
+ Assert.True(actual.Contains("color: '\\u003cshellColor\\u003e\\\\"));
+ Assert.True(actual.Contains("background: '\\u003ctweetsBackgroundColor"));
+ Assert.True(actual.Contains("color: '\\u003ctweetsColor\\u003e"));
+ Assert.True(actual.Contains("links: '\\u003ctweetsLinkColor\\u003e"));
+ Assert.True(actual.Contains(".setUser('\\\"my-userName\\u0027')"));
+ }
+
+ [Fact]
+ public void Search_ReturnsValidData()
+ {
+ // Arrange
+ string search = "awesome-search-term";
+ int width = 100;
+ int height = 100;
+ string title = "cust-title";
+ string caption = "some caption";
+ string backgroundShellColor = "background-shell-color";
+ string shellColor = "shellColorValue";
+ string tweetsBackgroundColor = "tweetsBackgroundColor";
+ string tweetsColor = "tweetsColorVal";
+ string tweetsLinksColor = "tweetsLinkColor";
+ bool scrollBar = false;
+ bool loop = false;
+ bool live = true;
+ bool hashTags = false;
+ bool timestamp = true;
+ bool avatars = true;
+ bool topTweets = true;
+ var behavior = "default";
+ int searchInterval = 10;
+
+ // Act
+ string actual = Twitter.Search(search, width, height, title, caption, backgroundShellColor, shellColor, tweetsBackgroundColor, tweetsColor, tweetsLinksColor, scrollBar,
+ loop, live, hashTags, timestamp, avatars, topTweets, behavior, searchInterval).ToString();
+
+ // Assert
+ var json = GetTwitterJSObject(actual);
+ Assert.Equal(json.type, "search");
+ Assert.Equal(json.search, search);
+ Assert.Equal(json.interval, 1000 * searchInterval);
+ Assert.Equal(json.title, title);
+ Assert.Equal(json.subject, caption);
+ Assert.Equal(json.width.ToString(), width.ToString());
+ Assert.Equal(json.height.ToString(), height.ToString());
+ Assert.Equal(json.theme.shell.background, backgroundShellColor);
+ Assert.Equal(json.theme.shell.color, shellColor);
+ Assert.Equal(json.theme.tweets.background, tweetsBackgroundColor);
+ Assert.Equal(json.theme.tweets.color, tweetsColor);
+ Assert.Equal(json.theme.tweets.links, tweetsLinksColor);
+ Assert.Equal(json.features.scrollbar, scrollBar);
+ Assert.Equal(json.features.loop, loop);
+ Assert.Equal(json.features.live, live);
+ Assert.Equal(json.features.hashtags, hashTags);
+ Assert.Equal(json.features.avatars, avatars);
+ Assert.Equal(json.features.toptweets, topTweets);
+ Assert.Equal(json.features.behavior, behavior.ToLowerInvariant());
+ }
+
+ [Fact]
+ public void SearchJavascriptEncodesParameters()
+ {
+ // Arrange
+ string search = "<script>";
+ int width = 100;
+ int height = 100;
+ string title = "'title'";
+ string caption = "<caption>";
+ string backgroundShellColor = "\\foo";
+ string shellColor = "<shellColor>\\";
+ string tweetsBackgroundColor = "<tweetsBackgroundColor";
+ string tweetsColor = "<tweetsColor>";
+ string tweetsLinksColor = "<tweetsLinkColor>";
+
+ // Act
+ string actual = Twitter.Search(search, width, height, title, caption, backgroundShellColor, shellColor, tweetsBackgroundColor, tweetsColor, tweetsLinksColor).ToString();
+
+ // Assert
+ Assert.True(actual.Contains("search: '\\u003cscript\\u003e'"));
+ Assert.True(actual.Contains("title: '\\u0027title\\u0027'"));
+ Assert.True(actual.Contains("subject: '\\u003ccaption\\u003e'"));
+ Assert.True(actual.Contains("background: '\\\\foo"));
+ Assert.True(actual.Contains("color: '\\u003cshellColor\\u003e\\\\"));
+ Assert.True(actual.Contains("background: '\\u003ctweetsBackgroundColor"));
+ Assert.True(actual.Contains("color: '\\u003ctweetsColor\\u003e"));
+ Assert.True(actual.Contains("links: '\\u003ctweetsLinkColor\\u003e"));
+ }
+
+ [Fact]
+ public void SearchWithInvalidArgs_ThrowsArgumentException()
+ {
+ Assert.ThrowsArgumentNullOrEmptyString(() => Twitter.Search(null).ToString(), "searchQuery");
+ }
+
+ [Fact]
+ public void ProfileWithInvalidArgs_ThrowsArgumentException()
+ {
+ Assert.ThrowsArgumentNullOrEmptyString(() => Twitter.Profile(null).ToString(), "userName");
+ }
+
+ [Fact]
+ public void SearchRendersRendersValidXhtml()
+ {
+ string result = "<html> <head> \n <title> </title> \n </head> \n <body> \n" +
+ Twitter.Search("any<>term") +
+ "\n </body> \n </html>";
+ HtmlString htmlResult = new HtmlString(result);
+ XhtmlAssert.Validate1_1(htmlResult);
+ }
+
+ [Fact]
+ public void SearchEncodesSearchTerms()
+ {
+ // Act
+ string result = Twitter.Search("'any term'", backgroundShellColor: "\"bad-color").ToString();
+
+ // Assert
+ Assert.True(result.Contains(@"background: '\""bad-color',"));
+ //Assert.True(result.Contains(@"search: @"'\u0027any term\u0027'","));
+ }
+
+ [Fact]
+ public void ProfileRendersRendersValidXhtml()
+ {
+ string result = "<html> <head> \n <title> </title> \n </head> \n <body> \n" +
+ Twitter.Profile("any<>Name") +
+ "\n </body> \n </html>";
+ HtmlString htmlResult = new HtmlString(result);
+ XhtmlAssert.Validate1_1(htmlResult);
+ }
+
+ [Fact]
+ public void ProfileEncodesSearchTerms()
+ {
+ // Act
+ string result = Twitter.Profile("'some user'", backgroundShellColor: "\"malformed-color").ToString();
+
+ // Assert
+ Assert.True(result.Contains(@"background: '\""malformed-color'"));
+ Assert.True(result.Contains("setUser('\\u0027some user\\u0027')"));
+ }
+
+ [Fact]
+ public void TweetButtonWithDefaultAttributes()
+ {
+ // Arrange
+ string expected = @"<a href=""http://twitter.com/share"" class=""twitter-share-button"" data-count=""vertical"">Tweet</a><script type=""text/javascript"" src=""http://platform.twitter.com/widgets.js""></script>";
+
+ // Act
+ string result = Twitter.TweetButton().ToString();
+
+ // Assert
+ UnitTestHelper.AssertEqualsIgnoreWhitespace(result, expected);
+ }
+
+ [Fact]
+ public void TweetButtonWithSpeicifedAttributes()
+ {
+ // Arrange
+ string expected = @"<a href=""http://twitter.com/share"" class=""twitter-share-button"" data-text=""tweet-text"" data-url=""http://www.microsoft.com"" data-via=""userName"" data-related=""related-userName:rel-desc"" data-count=""none"">my-share-text</a><script type=""text/javascript"" src=""http://platform.twitter.com/widgets.js""></script>";
+
+ // Act
+ string result = Twitter.TweetButton("none", shareText: "my-share-text", tweetText: "tweet-text", url: "http://www.microsoft.com", language: "en", userName: "userName", relatedUserName: "related-userName", relatedUserDescription: "rel-desc").ToString();
+
+ // Assert
+ UnitTestHelper.AssertEqualsIgnoreWhitespace(result, expected);
+ }
+
+ [Fact]
+ public void TweetButtonHtmlEncodesValues()
+ {
+ // Arrange
+ string expected = @"<a href=""http://twitter.com/share"" class=""twitter-share-button"" data-text=""&lt;tweet-text>"" data-url=""&lt;http://www.microsoft.com>"" data-via=""&lt;userName>"" data-related=""&lt;related-userName>:&lt;rel-desc>"" data-count=""none"">&lt;Tweet</a><script type=""text/javascript"" src=""http://platform.twitter.com/widgets.js""></script>";
+ // Act
+ string result = Twitter.TweetButton("none", shareText: "<Tweet", tweetText: "<tweet-text>", url: "<http://www.microsoft.com>", language: "en", userName: "<userName>", relatedUserName: "<related-userName>", relatedUserDescription: "<rel-desc>").ToString();
+
+ // Assert
+ UnitTestHelper.AssertEqualsIgnoreWhitespace(result, expected);
+ }
+
+ [Fact]
+ public void FollowButtonWithDefaultParameters()
+ {
+ // Arrange
+ string expected = @"<a href=""http://www.twitter.com/my-twitter-userName""><img src=""http://twitter-badges.s3.amazonaws.com/follow_me-a.png"" alt=""Follow my-twitter-userName on Twitter""/></a>";
+
+ // Act
+ string result = Twitter.FollowButton("my-twitter-userName").ToString();
+
+ // Assert
+ UnitTestHelper.AssertEqualsIgnoreWhitespace(result, expected);
+ }
+
+ [Fact]
+ public void FollowButtonWithSpecifiedParmeters()
+ {
+ // Arrange
+ string expected = @"<a href=""http://www.twitter.com/my-twitter-userName""><img src=""http://twitter-badges.s3.amazonaws.com/t_logo-b.png"" alt=""Follow my-twitter-userName on Twitter""/></a>";
+
+ // Act
+ string result = Twitter.FollowButton("my-twitter-userName", "t_logo", "b").ToString();
+
+ // Assert
+ UnitTestHelper.AssertEqualsIgnoreWhitespace(result, expected);
+ }
+
+ [Fact]
+ public void FollowButtonEncodesParameters()
+ {
+ // Arrange
+ string expected = @"<a href=""http://www.twitter.com/http%3a%2f%2fmy-twitter-userName%3cscript""><img src=""http://twitter-badges.s3.amazonaws.com/t_logo-b.png"" alt=""Follow http://my-twitter-userName&lt;script on Twitter""/></a>";
+
+ // Act
+ string result = Twitter.FollowButton("http://my-twitter-userName<script", "t_logo", "b").ToString();
+
+ // Assert
+ UnitTestHelper.AssertEqualsIgnoreWhitespace(result, expected);
+ }
+
+ [Fact]
+ public void FavesReturnsValidData()
+ {
+ // Arrange
+ string twitterUserName = "my-userName";
+ string title = "my-title";
+ string caption = "my-caption";
+ int width = 100;
+ int height = 100;
+ string backgroundShellColor = "my-backgroundShellColor";
+ string shellColor = "my-shellColor";
+ string tweetsBackgroundColor = "tweetsBackgroundColor";
+ string tweetsColor = "tweets-color";
+ string tweetsLinksColor = "tweets Linkcolor";
+ int numTweets = 4;
+ bool scrollBar = false;
+ bool loop = false;
+ bool live = false;
+ bool hashTags = false;
+ bool timestamp = false;
+ bool avatars = false;
+ var behavior = "all";
+ int searchInterval = 10;
+
+ // Act
+ string actual = Twitter.Faves(twitterUserName, width, height, title, caption, backgroundShellColor, shellColor, tweetsBackgroundColor, tweetsColor, tweetsLinksColor,
+ numTweets, scrollBar, loop, live, hashTags, timestamp, avatars, "all", searchInterval).ToString();
+
+ // Assert
+ var json = GetTwitterJSObject(actual);
+ Assert.Equal(json.type, "faves");
+ Assert.Equal(json.interval, 1000 * searchInterval);
+ Assert.Equal(json.width.ToString(), width.ToString());
+ Assert.Equal(json.height.ToString(), height.ToString());
+ Assert.Equal(json.title, title);
+ Assert.Equal(json.subject, caption);
+ Assert.Equal(json.theme.shell.background, backgroundShellColor);
+ Assert.Equal(json.theme.shell.color, shellColor);
+ Assert.Equal(json.theme.tweets.background, tweetsBackgroundColor);
+ Assert.Equal(json.theme.tweets.color, tweetsColor);
+ Assert.Equal(json.theme.tweets.links, tweetsLinksColor);
+ Assert.Equal(json.features.scrollbar, scrollBar);
+ Assert.Equal(json.features.loop, loop);
+ Assert.Equal(json.features.live, live);
+ Assert.Equal(json.features.hashtags, hashTags);
+ Assert.Equal(json.features.avatars, avatars);
+ Assert.Equal(json.features.behavior, behavior.ToLowerInvariant());
+ Assert.True(actual.Contains(".setUser('my-userName')"));
+ }
+
+ [Fact]
+ public void FavesJavascriptEncodesParameters()
+ {
+ // Arrange
+ string twitterUserName = "<my-userName>";
+ string title = "<my-title>";
+ string caption = "<my-caption>";
+ int width = 100;
+ int height = 100;
+ string backgroundShellColor = "<my-backgroundShellColor>";
+ string shellColor = "<my-shellColor>";
+ string tweetsBackgroundColor = "<tweetsBackgroundColor>";
+ string tweetsColor = "<tweets-color>";
+ string tweetsLinksColor = "<tweets Linkcolor>";
+
+ // Act
+ string actual = Twitter.Faves(twitterUserName, width, height, title, caption, backgroundShellColor, shellColor, tweetsBackgroundColor, tweetsColor, tweetsLinksColor).ToString();
+
+ // Assert
+ Assert.True(actual.Contains("title: '\\u003cmy-title\\u003e'"));
+ Assert.True(actual.Contains("subject: '\\u003cmy-caption\\u003e'"));
+ Assert.True(actual.Contains("background: '\\u003cmy-backgroundShellColor\\u003e'"));
+ Assert.True(actual.Contains("color: '\\u003cmy-shellColor\\u003e'"));
+ Assert.True(actual.Contains("background: '\\u003ctweetsBackgroundColor\\u003e'"));
+ Assert.True(actual.Contains("color: '\\u003ctweets-color\\u003e"));
+ Assert.True(actual.Contains("links: '\\u003ctweets Linkcolor\\u003e'"));
+ Assert.True(actual.Contains(".setUser('\\u003cmy-userName\\u003e')"));
+ }
+
+ [Fact]
+ public void FavesRendersRendersValidXhtml()
+ {
+ string result = "<html> <head> \n <title> </title> \n </head> \n <body> \n" +
+ Twitter.Faves("any<>Name") +
+ "\n </body> \n </html>";
+ HtmlString htmlResult = new HtmlString(result);
+ XhtmlAssert.Validate1_1(htmlResult);
+ }
+
+ [Fact]
+ public void ListReturnsValidData()
+ {
+ // Arrange
+ string twitterUserName = "my-userName";
+ string list = "my-list";
+ string title = "my-title";
+ string caption = "my-caption";
+ int width = 100;
+ int height = 100;
+ string backgroundShellColor = "my-backgroundShellColor";
+ string shellColor = "my-shellColor";
+ string tweetsBackgroundColor = "tweetsBackgroundColor";
+ string tweetsColor = "tweets-color";
+ string tweetsLinksColor = "tweets Linkcolor";
+ int numTweets = 4;
+ bool scrollBar = false;
+ bool loop = false;
+ bool live = false;
+ bool hashTags = false;
+ bool timestamp = false;
+ bool avatars = false;
+ var behavior = "all";
+ int searchInterval = 10;
+
+ // Act
+ string actual = Twitter.List(twitterUserName, list, width, height, title, caption, backgroundShellColor, shellColor, tweetsBackgroundColor, tweetsColor, tweetsLinksColor,
+ numTweets, scrollBar, loop, live, hashTags, timestamp, avatars, "all", searchInterval).ToString();
+
+ // Assert
+ var json = GetTwitterJSObject(actual);
+ Assert.Equal(json.type, "list");
+ Assert.Equal(json.interval, 1000 * searchInterval);
+ Assert.Equal(json.width.ToString(), width.ToString());
+ Assert.Equal(json.height.ToString(), height.ToString());
+ Assert.Equal(json.title, title);
+ Assert.Equal(json.subject, caption);
+ Assert.Equal(json.theme.shell.background, backgroundShellColor);
+ Assert.Equal(json.theme.shell.color, shellColor);
+ Assert.Equal(json.theme.tweets.background, tweetsBackgroundColor);
+ Assert.Equal(json.theme.tweets.color, tweetsColor);
+ Assert.Equal(json.theme.tweets.links, tweetsLinksColor);
+ Assert.Equal(json.features.scrollbar, scrollBar);
+ Assert.Equal(json.features.loop, loop);
+ Assert.Equal(json.features.live, live);
+ Assert.Equal(json.features.hashtags, hashTags);
+ Assert.Equal(json.features.avatars, avatars);
+ Assert.Equal(json.features.behavior, behavior.ToLowerInvariant());
+ Assert.True(actual.Contains(".setList('my-userName', 'my-list')"));
+ }
+
+ [Fact]
+ public void ListJavascriptEncodesParameters()
+ {
+ // Arrange
+ string twitterUserName = "<my-userName>";
+ string list = "<my-list>";
+ string title = "<my-title>";
+ string caption = "<my-caption>";
+ int width = 100;
+ int height = 100;
+ string backgroundShellColor = "<my-backgroundShellColor>";
+ string shellColor = "<my-shellColor>";
+ string tweetsBackgroundColor = "<tweetsBackgroundColor>";
+ string tweetsColor = "<tweets-color>";
+ string tweetsLinksColor = "<tweets Linkcolor>";
+
+ // Act
+ string actual = Twitter.List(twitterUserName, list, width, height, title, caption, backgroundShellColor, shellColor, tweetsBackgroundColor, tweetsColor, tweetsLinksColor).ToString();
+
+ // Assert
+ Assert.True(actual.Contains("title: '\\u003cmy-title\\u003e'"));
+ Assert.True(actual.Contains("subject: '\\u003cmy-caption\\u003e'"));
+ Assert.True(actual.Contains("background: '\\u003cmy-backgroundShellColor\\u003e'"));
+ Assert.True(actual.Contains("color: '\\u003cmy-shellColor\\u003e'"));
+ Assert.True(actual.Contains("background: '\\u003ctweetsBackgroundColor\\u003e'"));
+ Assert.True(actual.Contains("color: '\\u003ctweets-color\\u003e"));
+ Assert.True(actual.Contains("links: '\\u003ctweets Linkcolor\\u003e'"));
+ Assert.True(actual.Contains(".setList('\\u003cmy-userName\\u003e', '\\u003cmy-list\\u003e')"));
+ }
+
+ [Fact]
+ public void ListRendersRendersValidXhtml()
+ {
+ string result = "<html> <head> \n <title> </title> \n </head> \n <body> \n" +
+ Twitter.List("any<>Name", "my-list") +
+ "\n </body> \n </html>";
+ HtmlString htmlResult = new HtmlString(result);
+ XhtmlAssert.Validate1_1(htmlResult);
+ }
+
+ private static dynamic GetTwitterJSObject(string twitterOutput)
+ {
+ const string startString = "Widget(";
+ int start = twitterOutput.IndexOf(startString) + startString.Length, end = twitterOutput.IndexOf(')');
+ return Json.Decode(twitterOutput.Substring(start, end - start));
+ }
+ }
+}
diff --git a/test/Microsoft.Web.Helpers.Test/UrlBuilderTest.cs b/test/Microsoft.Web.Helpers.Test/UrlBuilderTest.cs
new file mode 100644
index 00000000..80240253
--- /dev/null
+++ b/test/Microsoft.Web.Helpers.Test/UrlBuilderTest.cs
@@ -0,0 +1,413 @@
+using System;
+using System.Collections;
+using System.IO;
+using System.Reflection;
+using System.Text;
+using System.Web;
+using System.Web.UI;
+using System.Web.WebPages;
+using Moq;
+using Xunit;
+
+namespace Microsoft.Web.Helpers.Test
+{
+ public class UrlBuilderTest
+ {
+ private static TestVirtualPathUtility _virtualPathUtility = new TestVirtualPathUtility();
+
+ [Fact]
+ public void UrlBuilderUsesPathAsIsIfPathIsAValidUri()
+ {
+ // Arrange
+ var pagePath = "http://www.test.com/page-path";
+
+ // Act
+ var builder = new UrlBuilder(GetContext(), _virtualPathUtility, pagePath, null);
+
+ // Assert
+ Assert.Equal("http://www.test.com/page-path", builder.Path);
+ }
+
+ [Fact]
+ public void UrlBuilderUsesQueryComponentsIfPathIsAValidUri()
+ {
+ // Arrange
+ var pagePath = "http://www.test.com/page-path.vbhtml?param=value&baz=biz";
+
+ // Act
+ var builder = new UrlBuilder(GetContext(), _virtualPathUtility, pagePath, null);
+
+ // Assert
+ Assert.Equal("http://www.test.com/page-path.vbhtml", builder.Path);
+ Assert.Equal("?param=value&baz=biz", builder.QueryString);
+ }
+
+ [Fact]
+ public void UrlBuilderUsesUsesObjectParameterAsQueryString()
+ {
+ // Arrange
+ var pagePath = "http://www.test.com/page-path.vbhtml?param=value&baz=biz";
+
+ // Act
+ var builder = new UrlBuilder(GetContext(), _virtualPathUtility, pagePath, null);
+
+ // Assert
+ Assert.Equal("http://www.test.com/page-path.vbhtml", builder.Path);
+ Assert.Equal("?param=value&baz=biz", builder.QueryString);
+ }
+
+ [Fact]
+ public void UrlBuilderAppendsObjectParametersToPathWithQueryString()
+ {
+ // Arrange
+ var pagePath = "http://www.test.com/page-path.vbhtml?param=value&baz=biz";
+
+ // Act
+ var builder = new UrlBuilder(GetContext(), _virtualPathUtility, pagePath, new { param2 = "param2val" });
+
+ // Assert
+ Assert.Equal("http://www.test.com/page-path.vbhtml", builder.Path);
+ Assert.Equal("?param=value&baz=biz&param2=param2val", builder.QueryString);
+ }
+
+ [Fact]
+ public void UrlBuilderWithVirtualPathUsesVirtualPathUtility()
+ {
+ // Arrange
+ var pagePath = "~/page";
+
+ // Act
+ var builder = new UrlBuilder(GetContext(), _virtualPathUtility, pagePath, null);
+
+ // Assert
+ Assert.Equal("page", builder.Path);
+ }
+
+ [Fact]
+ public void UrlBuilderDoesNotResolvePathIfContextIsNull()
+ {
+ // Arrange
+ var pagePath = "~/foo/bar";
+
+ // Act
+ var builder = new UrlBuilder(null, _virtualPathUtility, pagePath, null);
+
+ // Assert
+ Assert.Equal(pagePath, builder.Path);
+ }
+
+ [Fact]
+ public void UrlBuilderSetsPathToNullIfContextIsNullAndPagePathIsNotSpecified()
+ {
+ // Act
+ var builder = new UrlBuilder(null, null, null, null);
+
+ // Assert
+ Assert.Null(builder.Path);
+ Assert.True(String.IsNullOrEmpty(builder.ToString()));
+ }
+
+ [Fact]
+ public void UrlBuilderWithVirtualPathAppendsObjectAsQueryString()
+ {
+ // Arrange
+ var pagePath = "~/page";
+
+ // Act
+ var builder = new UrlBuilder(GetContext(), _virtualPathUtility, pagePath, new { Foo = "bar", baz = "qux" });
+
+ // Assert
+ Assert.Equal("page", builder.Path);
+ Assert.Equal("?Foo=bar&baz=qux", builder.QueryString);
+ }
+
+ [Fact]
+ public void UrlBuilderWithVirtualPathExtractsQueryStringParameterFromPath()
+ {
+ // Arrange
+ var pagePath = "~/dir/page?someparam=value";
+
+ // Act
+ var builder = new UrlBuilder(GetContext(), _virtualPathUtility, pagePath, null);
+
+ // Assert
+ Assert.Equal("dir/page", builder.Path);
+ Assert.Equal("?someparam=value", builder.QueryString);
+ }
+
+ [Fact]
+ public void UrlBuilderWithVirtualPathAndQueryStringAppendsObjectAsQueryStringParams()
+ {
+ // Arrange
+ var pagePath = "~/dir/page?someparam=value";
+
+ // Act
+ var builder = new UrlBuilder(GetContext(), _virtualPathUtility, pagePath, new { someotherparam = "value2" });
+
+ // Assert
+ Assert.Equal("dir/page", builder.Path);
+ Assert.Equal("?someparam=value&someotherparam=value2", builder.QueryString);
+ }
+
+ [Fact]
+ public void AddPathAddsPathPortionToRelativeUrl()
+ {
+ // Arrange
+ var pagePath = "~/dir/page?someparam=value";
+
+ // Act
+ var builder = new UrlBuilder(GetContext(), _virtualPathUtility, pagePath, null);
+ builder.AddPath("foo").AddPath("bar/baz");
+
+ // Assert
+ Assert.Equal("dir/page/foo/bar/baz", builder.Path);
+ Assert.Equal("?someparam=value", builder.QueryString);
+ }
+
+ [Fact]
+ public void AddPathEncodesPathParams()
+ {
+ // Arrange
+ var pagePath = "~/dir/page?someparam=value";
+
+ // Act
+ var builder = new UrlBuilder(GetContext(), _virtualPathUtility, pagePath, null);
+ builder.AddPath("foo bar").AddPath("baz biz", "qux");
+
+ // Assert
+ Assert.Equal("dir/page/foo%20bar/baz%20biz/qux", builder.Path);
+ }
+
+ [Fact]
+ public void AddPathAddsPathPortionToAbsoluteUrl()
+ {
+ // Arrange
+ var pagePath = "http://some-site/dir/page?someparam=value";
+
+ // Act
+ var builder = new UrlBuilder(GetContext(), _virtualPathUtility, pagePath, null);
+ builder.AddPath("foo").AddPath("bar/baz");
+
+ // Assert
+ Assert.Equal("http://some-site/dir/page/foo/bar/baz", builder.Path);
+ Assert.Equal("?someparam=value", builder.QueryString);
+ }
+
+ [Fact]
+ public void AddPathWithParamsArrayAddsPathPortionToRelativeUrl()
+ {
+ // Arrange
+ var pagePath = "~/dir/page/?someparam=value";
+
+ // Act
+ var builder = new UrlBuilder(GetContext(), _virtualPathUtility, pagePath, null);
+ builder.AddPath("foo", "bar", "baz").AddPath("qux");
+
+ // Assert
+ Assert.Equal("dir/page/foo/bar/baz/qux", builder.Path);
+ Assert.Equal("?someparam=value", builder.QueryString);
+ }
+
+ [Fact]
+ public void AddPathEnsuresSlashesAreNotRepeated()
+ {
+ // Arrange
+ var pagePath = "~/dir/page/";
+
+ // Act
+ var builder = new UrlBuilder(GetContext(), _virtualPathUtility, pagePath, null);
+ builder.AddPath("foo").AddPath("/bar/").AddPath("/baz");
+
+ // Assert
+ Assert.Equal("dir/page/foo/bar/baz", builder.Path);
+ }
+
+ [Fact]
+ public void AddPathWithParamsEnsuresSlashAreNotRepeated()
+ {
+ // Arrange
+ var pagePath = "~/dir/page/";
+
+ // Act
+ var builder = new UrlBuilder(GetContext(), _virtualPathUtility, pagePath, null);
+ builder.AddPath("foo", "/bar/", "/baz").AddPath("qux/");
+
+ // Assert
+ Assert.Equal("dir/page/foo/bar/baz/qux/", builder.Path);
+ }
+
+ [Fact]
+ public void AddPathWithParamsArrayAddsPathPortionToAbsoluteUrl()
+ {
+ // Arrange
+ var pagePath = "http://www.test.com/dir/page/?someparam=value";
+
+ // Act
+ var builder = new UrlBuilder(GetContext(), _virtualPathUtility, pagePath, null);
+ builder.AddPath("foo", "bar", "baz").AddPath("qux");
+
+ // Assert
+ Assert.Equal("http://www.test.com/dir/page/foo/bar/baz/qux", builder.Path);
+ Assert.Equal("?someparam=value", builder.QueryString);
+ }
+
+ [Fact]
+ public void UrlBuilderEncodesParameters()
+ {
+ // Arrange
+ var pagePath = "~/dir/page/?someparam=value";
+
+ // Act
+ var builder = new UrlBuilder(GetContext(), _virtualPathUtility, pagePath, new { Λ = "λ" });
+ builder.AddParam(new { π = "is not a lie" }).AddParam("Π", "maybe a lie");
+ // Assert
+ Assert.Equal("?someparam=value&%ce%9b=%ce%bb&%cf%80=is+not+a+lie&%ce%a0=maybe+a+lie", builder.QueryString);
+ }
+
+ [Fact]
+ public void AddParamAddsParamToQueryString()
+ {
+ // Arrange
+ var pagePath = "http://www.test.com/dir/page/?someparam=value";
+
+ // Act
+ var builder = new UrlBuilder(GetContext(), _virtualPathUtility, pagePath, null);
+ builder.AddParam("foo", "bar");
+
+ // Assert
+ Assert.Equal("?someparam=value&foo=bar", builder.QueryString);
+ }
+
+ [Fact]
+ public void AddParamAddsQuestionMarkToQueryStringIfFirstParam()
+ {
+ // Arrange
+ var pagePath = "~/dir/page";
+
+ // Act
+ var builder = new UrlBuilder(GetContext(), _virtualPathUtility, pagePath, null);
+ builder.AddParam("foo", "bar").AddParam(new { baz = "qux", biz = "quark" });
+
+ // Assert
+ Assert.Equal("?foo=bar&baz=qux&biz=quark", builder.QueryString);
+ }
+
+ [Fact]
+ public void AddParamIgnoresParametersWithEmptyKey()
+ {
+ // Arrange
+ var pagePath = "~/dir/page";
+
+ // Act
+ var builder = new UrlBuilder(GetContext(), _virtualPathUtility, pagePath, null);
+ builder.AddParam("", "bar").AddParam(new { baz = "", biz = "quark" }).AddParam("qux", null).AddParam(null, "somevalue");
+
+ // Assert
+ Assert.Equal("?baz=&biz=quark&qux=", builder.QueryString);
+ }
+
+ [Fact]
+ public void ToStringConcatsPathAndQueryString()
+ {
+ // Arrange
+ var pagePath = "~/dir/page";
+
+ // Act
+ var builder = new UrlBuilder(GetContext(), _virtualPathUtility, pagePath, null);
+ builder.AddParam("foo", "bar").AddParam(new { baz = "qux", biz = "quark" });
+
+ // Assert
+ Assert.Equal("dir/page?foo=bar&baz=qux&biz=quark", builder.ToString());
+ }
+
+ [Fact]
+ public void UrlBuilderWithRealVirtualPathUtilityTest()
+ {
+ // Arrange
+ var pagePath = "~/world/test.aspx";
+ try
+ {
+ // Act
+ CreateHttpContext("default.aspx", "http://localhost/WebSite1/subfolder1/default.aspx");
+ CreateHttpRuntime("/WebSite1/");
+ var builder = new UrlBuilder(pagePath, null);
+
+ // Assert
+ Assert.Equal(@"/WebSite1/world/test.aspx", builder.Path);
+ }
+ finally
+ {
+ RestoreHttpRuntime();
+ }
+ }
+
+ private static HttpContextBase GetContext(params string[] virtualPaths)
+ {
+ var httpContext = new Mock<HttpContextBase>();
+ var table = new Hashtable();
+ httpContext.SetupGet(c => c.Items).Returns(table);
+
+ foreach (var item in virtualPaths)
+ {
+ var page = new Mock<ITemplateFile>();
+ page.SetupGet(p => p.TemplateInfo).Returns(new TemplateFileInfo(item));
+ TemplateStack.Push(httpContext.Object, page.Object);
+ }
+
+ return httpContext.Object;
+ }
+
+ private class TestVirtualPathUtility : VirtualPathUtilityBase
+ {
+ public override string Combine(string basePath, string relativePath)
+ {
+ return basePath + '/' + relativePath.TrimStart('~', '/');
+ }
+
+ public override string ToAbsolute(string virtualPath)
+ {
+ return virtualPath.TrimStart('~', '/');
+ }
+ }
+
+ internal static void CreateHttpRuntime(string appVPath)
+ {
+ var runtime = new HttpRuntime();
+ var appDomainAppVPathField = typeof(HttpRuntime).GetField("_appDomainAppVPath", BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance);
+ appDomainAppVPathField.SetValue(runtime, CreateVirtualPath(appVPath));
+ GetTheRuntime().SetValue(null, runtime);
+ var appDomainIdField = typeof(HttpRuntime).GetField("_appDomainId", BindingFlags.NonPublic | BindingFlags.Instance);
+ appDomainIdField.SetValue(runtime, "test");
+ }
+
+ internal static FieldInfo GetTheRuntime()
+ {
+ return typeof(HttpRuntime).GetField("_theRuntime", BindingFlags.NonPublic | BindingFlags.Static);
+ }
+
+ internal static void RestoreHttpRuntime()
+ {
+ GetTheRuntime().SetValue(null, null);
+ }
+
+ // E.g. "default.aspx", "http://localhost/WebSite1/subfolder1/default.aspx"
+ internal static void CreateHttpContext(string filename, string url)
+ {
+ var request = new HttpRequest(filename, url, null);
+ var httpContext = new HttpContext(request, new HttpResponse(new StringWriter(new StringBuilder())));
+ HttpContext.Current = httpContext;
+ }
+
+ internal static void RestoreHttpContext()
+ {
+ HttpContext.Current = null;
+ }
+
+ internal static object CreateVirtualPath(string path)
+ {
+ var vPath = typeof(Page).Assembly.GetType("System.Web.VirtualPath");
+ var method = vPath.GetMethod("CreateNonRelativeTrailingSlash", BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public);
+ return method.Invoke(null, new object[] { path });
+ }
+ }
+}
diff --git a/test/Microsoft.Web.Helpers.Test/VideoTest.cs b/test/Microsoft.Web.Helpers.Test/VideoTest.cs
new file mode 100644
index 00000000..0a266c1a
--- /dev/null
+++ b/test/Microsoft.Web.Helpers.Test/VideoTest.cs
@@ -0,0 +1,300 @@
+using System;
+using System.Reflection;
+using System.Text.RegularExpressions;
+using System.Web;
+using System.Web.WebPages.TestUtils;
+using Moq;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace Microsoft.Web.Helpers.Test
+{
+ public class VideoTest
+ {
+ private VirtualPathUtilityWrapper _pathUtility = new VirtualPathUtilityWrapper();
+
+ [Fact]
+ public void FlashCannotOverrideHtmlAttributes()
+ {
+ Assert.ThrowsArgument(() => { Video.Flash(GetContext(), _pathUtility, "http://foo.bar.com/foo.swf", htmlAttributes: new { cLASSid = "CanNotOverride" }); }, "htmlAttributes", "Property \"cLASSid\" cannot be set through this argument.");
+ }
+
+ [Fact]
+ public void FlashDefaults()
+ {
+ string html = Video.Flash(GetContext(), _pathUtility, "http://foo.bar.com/foo.swf").ToString().Replace("\r\n", "");
+ Assert.True(html.StartsWith(
+ "<object classid=\"clsid:d27cdb6e-ae6d-11cf-96b8-444553540000\" " +
+ "codebase=\"http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab\" type=\"application/x-oleobject\" >"
+ ));
+ Assert.True(html.Contains("<param name=\"movie\" value=\"http://foo.bar.com/foo.swf\" />"));
+ Assert.True(html.Contains("<embed src=\"http://foo.bar.com/foo.swf\" type=\"application/x-shockwave-flash\" />"));
+ Assert.True(html.EndsWith("</object>"));
+ }
+
+ [Fact]
+ public void FlashThrowsWhenPathIsEmpty()
+ {
+ Assert.ThrowsArgumentNullOrEmptyString(() => { Video.Flash(GetContext(), _pathUtility, String.Empty); }, "path");
+ }
+
+ [Fact]
+ public void FlashThrowsWhenPathIsNull()
+ {
+ Assert.ThrowsArgumentNullOrEmptyString(() => { Video.Flash(GetContext(), _pathUtility, null); }, "path");
+ }
+
+ [Fact]
+ public void FlashWithExposedOptions()
+ {
+ string html = Video.Flash(GetContext(), _pathUtility, "http://foo.bar.com/foo.swf", width: "100px", height: "100px",
+ play: false, loop: false, menu: false, backgroundColor: "#000", quality: "Q", scale: "S", windowMode: "WM",
+ baseUrl: "http://foo.bar.com/", version: "1.0.0.0", htmlAttributes: new { id = "fl" }, embedName: "efl").ToString().Replace("\r\n", "");
+
+ Assert.True(html.StartsWith(
+ "<object classid=\"clsid:d27cdb6e-ae6d-11cf-96b8-444553540000\" " +
+ "codebase=\"http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=1,0,0,0\" " +
+ "height=\"100px\" id=\"fl\" type=\"application/x-oleobject\" width=\"100px\" >"
+ ));
+ Assert.True(html.Contains("<param name=\"play\" value=\"False\" />"));
+ Assert.True(html.Contains("<param name=\"loop\" value=\"False\" />"));
+ Assert.True(html.Contains("<param name=\"menu\" value=\"False\" />"));
+ Assert.True(html.Contains("<param name=\"bgColor\" value=\"#000\" />"));
+ Assert.True(html.Contains("<param name=\"quality\" value=\"Q\" />"));
+ Assert.True(html.Contains("<param name=\"scale\" value=\"S\" />"));
+ Assert.True(html.Contains("<param name=\"wmode\" value=\"WM\" />"));
+ Assert.True(html.Contains("<param name=\"base\" value=\"http://foo.bar.com/\" />"));
+
+ var embed = new Regex("<embed.*/>").Match(html);
+ Assert.True(embed.Success);
+ Assert.True(embed.Value.StartsWith("<embed src=\"http://foo.bar.com/foo.swf\" width=\"100px\" height=\"100px\" name=\"efl\" type=\"application/x-shockwave-flash\" "));
+ Assert.True(embed.Value.Contains("play=\"False\""));
+ Assert.True(embed.Value.Contains("loop=\"False\""));
+ Assert.True(embed.Value.Contains("menu=\"False\""));
+ Assert.True(embed.Value.Contains("bgColor=\"#000\""));
+ Assert.True(embed.Value.Contains("quality=\"Q\""));
+ Assert.True(embed.Value.Contains("scale=\"S\""));
+ Assert.True(embed.Value.Contains("wmode=\"WM\""));
+ Assert.True(embed.Value.Contains("base=\"http://foo.bar.com/\""));
+ }
+
+ [Fact]
+ public void FlashWithUnexposedOptions()
+ {
+ string html = Video.Flash(GetContext(), _pathUtility, "http://foo.bar.com/foo.swf", options: new { X = "Y", Z = 123 }).ToString().Replace("\r\n", "");
+ Assert.True(html.Contains("<param name=\"X\" value=\"Y\" />"));
+ Assert.True(html.Contains("<param name=\"Z\" value=\"123\" />"));
+ // note - can't guarantee order of optional params:
+ Assert.True(
+ html.Contains("<embed src=\"http://foo.bar.com/foo.swf\" type=\"application/x-shockwave-flash\" X=\"Y\" Z=\"123\" />") ||
+ html.Contains("<embed src=\"http://foo.bar.com/foo.swf\" type=\"application/x-shockwave-flash\" Z=\"123\" X=\"Y\" />")
+ );
+ }
+
+ [Fact]
+ public void MediaPlayerCannotOverrideHtmlAttributes()
+ {
+ Assert.ThrowsArgument(() => { Video.MediaPlayer(GetContext(), _pathUtility, "http://foo.bar.com/foo.wmv", htmlAttributes: new { cODEbase = "CanNotOverride" }); }, "htmlAttributes", "Property \"cODEbase\" cannot be set through this argument.");
+ }
+
+ [Fact]
+ public void MediaPlayerDefaults()
+ {
+ string html = Video.MediaPlayer(GetContext(), _pathUtility, "http://foo.bar.com/foo.wmv").ToString().Replace("\r\n", "");
+ Assert.True(html.StartsWith(
+ "<object classid=\"clsid:6BF52A52-394A-11D3-B153-00C04F79FAA6\" >"
+ ));
+ Assert.True(html.Contains("<param name=\"URL\" value=\"http://foo.bar.com/foo.wmv\" />"));
+ Assert.True(html.Contains("<embed src=\"http://foo.bar.com/foo.wmv\" type=\"application/x-mplayer2\" />"));
+ Assert.True(html.EndsWith("</object>"));
+ }
+
+ [Fact]
+ public void MediaPlayerThrowsWhenPathIsEmpty()
+ {
+ Assert.ThrowsArgumentNullOrEmptyString(() => { Video.MediaPlayer(GetContext(), _pathUtility, String.Empty); }, "path");
+ }
+
+ [Fact]
+ public void MediaPlayerThrowsWhenPathIsNull()
+ {
+ Assert.ThrowsArgumentNullOrEmptyString(() => { Video.MediaPlayer(GetContext(), _pathUtility, null); }, "path");
+ }
+
+ [Fact]
+ public void MediaPlayerWithExposedOptions()
+ {
+ string html = Video.MediaPlayer(GetContext(), _pathUtility, "http://foo.bar.com/foo.wmv", width: "100px", height: "100px",
+ autoStart: false, playCount: 2, uiMode: "UIMODE", stretchToFit: true, enableContextMenu: false, mute: true,
+ volume: 1, baseUrl: "http://foo.bar.com/", htmlAttributes: new { id = "mp" }, embedName: "emp").ToString().Replace("\r\n", "");
+ Assert.True(html.StartsWith(
+ "<object classid=\"clsid:6BF52A52-394A-11D3-B153-00C04F79FAA6\" height=\"100px\" id=\"mp\" width=\"100px\" >"
+ ));
+ Assert.True(html.Contains("<param name=\"URL\" value=\"http://foo.bar.com/foo.wmv\" />"));
+ Assert.True(html.Contains("<param name=\"autoStart\" value=\"False\" />"));
+ Assert.True(html.Contains("<param name=\"playCount\" value=\"2\" />"));
+ Assert.True(html.Contains("<param name=\"uiMode\" value=\"UIMODE\" />"));
+ Assert.True(html.Contains("<param name=\"stretchToFit\" value=\"True\" />"));
+ Assert.True(html.Contains("<param name=\"enableContextMenu\" value=\"False\" />"));
+ Assert.True(html.Contains("<param name=\"mute\" value=\"True\" />"));
+ Assert.True(html.Contains("<param name=\"volume\" value=\"1\" />"));
+ Assert.True(html.Contains("<param name=\"baseURL\" value=\"http://foo.bar.com/\" />"));
+
+ var embed = new Regex("<embed.*/>").Match(html);
+ Assert.True(embed.Success);
+ Assert.True(embed.Value.StartsWith("<embed src=\"http://foo.bar.com/foo.wmv\" width=\"100px\" height=\"100px\" name=\"emp\" type=\"application/x-mplayer2\" "));
+ Assert.True(embed.Value.Contains("autoStart=\"False\""));
+ Assert.True(embed.Value.Contains("playCount=\"2\""));
+ Assert.True(embed.Value.Contains("uiMode=\"UIMODE\""));
+ Assert.True(embed.Value.Contains("stretchToFit=\"True\""));
+ Assert.True(embed.Value.Contains("enableContextMenu=\"False\""));
+ Assert.True(embed.Value.Contains("mute=\"True\""));
+ Assert.True(embed.Value.Contains("volume=\"1\""));
+ Assert.True(embed.Value.Contains("baseURL=\"http://foo.bar.com/\""));
+ }
+
+ [Fact]
+ public void MediaPlayerWithUnexposedOptions()
+ {
+ string html = Video.MediaPlayer(GetContext(), _pathUtility, "http://foo.bar.com/foo.wmv", options: new { X = "Y", Z = 123 }).ToString().Replace("\r\n", "");
+ Assert.True(html.Contains("<param name=\"X\" value=\"Y\" />"));
+ Assert.True(html.Contains("<param name=\"Z\" value=\"123\" />"));
+ Assert.True(
+ html.Contains("<embed src=\"http://foo.bar.com/foo.wmv\" type=\"application/x-mplayer2\" X=\"Y\" Z=\"123\" />") ||
+ html.Contains("<embed src=\"http://foo.bar.com/foo.wmv\" type=\"application/x-mplayer2\" Z=\"123\" X=\"Y\" />")
+ );
+ }
+
+ [Fact]
+ public void SilverlightCannotOverrideHtmlAttributes()
+ {
+ Assert.ThrowsArgument(() =>
+ {
+ Video.Silverlight(GetContext(), _pathUtility, "http://foo.bar.com/foo.xap", "100px", "100px",
+ htmlAttributes: new { WIDTH = "CanNotOverride" });
+ }, "htmlAttributes", "Property \"WIDTH\" cannot be set through this argument.");
+ }
+
+ [Fact]
+ public void SilverlightDefaults()
+ {
+ string html = Video.Silverlight(GetContext(), _pathUtility, "http://foo.bar.com/foo.xap", "100px", "100px").ToString().Replace("\r\n", "");
+ Assert.True(html.StartsWith(
+ "<object data=\"data:application/x-silverlight-2,\" height=\"100px\" type=\"application/x-silverlight-2\" " +
+ "width=\"100px\" >"
+ ));
+ Assert.True(html.Contains("<param name=\"source\" value=\"http://foo.bar.com/foo.xap\" />"));
+ Assert.True(html.Contains(
+ "<a href=\"http://go.microsoft.com/fwlink/?LinkID=149156\" style=\"text-decoration:none\">" +
+ "<img src=\"http://go.microsoft.com/fwlink?LinkId=108181\" alt=\"Get Microsoft Silverlight\" " +
+ "style=\"border-style:none\"/></a>"));
+ Assert.True(html.EndsWith("</object>"));
+ }
+
+ [Fact]
+ public void SilverlightThrowsWhenPathIsEmpty()
+ {
+ Assert.ThrowsArgumentNullOrEmptyString(() => { Video.Silverlight(GetContext(), _pathUtility, String.Empty, "100px", "100px"); }, "path");
+ }
+
+ [Fact]
+ public void SilverlightThrowsWhenPathIsNull()
+ {
+ Assert.ThrowsArgumentNullOrEmptyString(() => { Video.Silverlight(GetContext(), _pathUtility, null, "100px", "100px"); }, "path");
+ }
+
+ [Fact]
+ public void SilverlightThrowsWhenHeightIsEmpty()
+ {
+ Assert.ThrowsArgumentNullOrEmptyString(() => { Video.Silverlight(GetContext(), _pathUtility, "http://foo.bar.com/foo.xap", "100px", String.Empty); }, "height");
+ }
+
+ [Fact]
+ public void SilverlightThrowsWhenHeightIsNull()
+ {
+ Assert.ThrowsArgumentNullOrEmptyString(() => { Video.Silverlight(GetContext(), _pathUtility, "http://foo.bar.com/foo.xap", "100px", null); }, "height");
+ }
+
+ [Fact]
+ public void SilverlightThrowsWhenWidthIsEmpty()
+ {
+ Assert.ThrowsArgumentNullOrEmptyString(() => { Video.Silverlight(GetContext(), _pathUtility, "http://foo.bar.com/foo.xap", String.Empty, "100px"); }, "width");
+ }
+
+ [Fact]
+ public void SilverlightThrowsWhenWidthIsNull()
+ {
+ Assert.ThrowsArgumentNullOrEmptyString(() => { Video.Silverlight(GetContext(), _pathUtility, "http://foo.bar.com/foo.xap", null, "100px"); }, "width");
+ }
+
+ [Fact]
+ public void SilverlightWithExposedOptions()
+ {
+ string html = Video.Silverlight(GetContext(), _pathUtility, "http://foo.bar.com/foo.xap", width: "85%", height: "85%",
+ backgroundColor: "red", initParameters: "X=Y", minimumVersion: "1.0.0.0", autoUpgrade: false,
+ htmlAttributes: new { id = "sl" }).ToString().Replace("\r\n", "");
+ Assert.True(html.StartsWith(
+ "<object data=\"data:application/x-silverlight-2,\" height=\"85%\" id=\"sl\" " +
+ "type=\"application/x-silverlight-2\" width=\"85%\" >"
+ ));
+ Assert.True(html.Contains("<param name=\"background\" value=\"red\" />"));
+ Assert.True(html.Contains("<param name=\"initparams\" value=\"X=Y\" />"));
+ Assert.True(html.Contains("<param name=\"minruntimeversion\" value=\"1.0.0.0\" />"));
+ Assert.True(html.Contains("<param name=\"autoUpgrade\" value=\"False\" />"));
+
+ var embed = new Regex("<embed.*/>").Match(html);
+ Assert.False(embed.Success);
+ }
+
+ [Fact]
+ public void SilverlightWithUnexposedOptions()
+ {
+ string html = Video.Silverlight(GetContext(), _pathUtility, "http://foo.bar.com/foo.xap", width: "50px", height: "50px",
+ options: new { X = "Y", Z = 123 }).ToString().Replace("\r\n", "");
+ Assert.True(html.Contains("<param name=\"X\" value=\"Y\" />"));
+ Assert.True(html.Contains("<param name=\"Z\" value=\"123\" />"));
+ }
+
+ [Fact]
+ public void ValidatePathResolvesExistingLocalPath()
+ {
+ string path = Assembly.GetExecutingAssembly().Location;
+ Mock<VirtualPathUtilityBase> pathUtility = new Mock<VirtualPathUtilityBase>();
+ pathUtility.Setup(p => p.Combine(It.IsAny<string>(), It.IsAny<string>())).Returns(path);
+ pathUtility.Setup(p => p.ToAbsolute(It.IsAny<string>())).Returns(path);
+
+ Mock<HttpServerUtilityBase> serverMock = new Mock<HttpServerUtilityBase>();
+ serverMock.Setup(s => s.MapPath(It.IsAny<string>())).Returns(path);
+ HttpContextBase context = GetContext(serverMock.Object);
+
+ string html = Video.Flash(context, pathUtility.Object, "foo.bar").ToString();
+ Assert.True(html.StartsWith("<object"));
+ Assert.True(html.Contains(HttpUtility.HtmlAttributeEncode(HttpUtility.UrlPathEncode(path))));
+ }
+
+ [Fact]
+ public void ValidatePathThrowsForNonExistingLocalPath()
+ {
+ string path = "c:\\does\\not\\exist.swf";
+ Mock<VirtualPathUtilityBase> pathUtility = new Mock<VirtualPathUtilityBase>();
+ pathUtility.Setup(p => p.Combine(It.IsAny<string>(), It.IsAny<string>())).Returns(path);
+ pathUtility.Setup(p => p.ToAbsolute(It.IsAny<string>())).Returns(path);
+
+ Mock<HttpServerUtilityBase> serverMock = new Mock<HttpServerUtilityBase>();
+ serverMock.Setup(s => s.MapPath(It.IsAny<string>())).Returns(path);
+ HttpContextBase context = GetContext(serverMock.Object);
+
+ Assert.Throws<InvalidOperationException>(() => { Video.Flash(context, pathUtility.Object, "exist.swf"); }, "The media file \"exist.swf\" does not exist.");
+ }
+
+ private static HttpContextBase GetContext(HttpServerUtilityBase serverUtility = null)
+ {
+ // simple mocked context - won't reference as long as path starts with 'http'
+ Mock<HttpRequestBase> requestMock = new Mock<HttpRequestBase>();
+ Mock<HttpContextBase> contextMock = new Mock<HttpContextBase>();
+ contextMock.Setup(context => context.Request).Returns(requestMock.Object);
+ contextMock.Setup(context => context.Server).Returns(serverUtility);
+ return contextMock.Object;
+ }
+ }
+}
diff --git a/test/Microsoft.Web.Helpers.Test/packages.config b/test/Microsoft.Web.Helpers.Test/packages.config
new file mode 100644
index 00000000..d5aa6401
--- /dev/null
+++ b/test/Microsoft.Web.Helpers.Test/packages.config
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<packages>
+ <package id="Moq" version="4.0.10827" />
+ <package id="xunit" version="1.9.0.1566" />
+ <package id="xunit.extensions" version="1.9.0.1566" />
+</packages> \ No newline at end of file
diff --git a/test/Microsoft.Web.Http.Data.Test/ChangeSetTests.cs b/test/Microsoft.Web.Http.Data.Test/ChangeSetTests.cs
new file mode 100644
index 00000000..cf7d6cdd
--- /dev/null
+++ b/test/Microsoft.Web.Http.Data.Test/ChangeSetTests.cs
@@ -0,0 +1,164 @@
+using System.Collections.Generic;
+using System.Linq;
+using Microsoft.Web.Http.Data;
+using Microsoft.Web.Http.Data.Test.Models;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.ServiceModel.DomainServices.Server.Test
+{
+ public class ChangeSetTests
+ {
+ /// <summary>
+ /// Verify ChangeSet validation when specifying/requesting original for Insert operations.
+ /// </summary>
+ [Fact]
+ public void Changeset_OriginalInvalidForInserts()
+ {
+ // can't specify an original for an insert operation
+ Product curr = new Product { ProductID = 1 };
+ Product orig = new Product { ProductID = 1 };
+ ChangeSetEntry entry = new ChangeSetEntry { Id = 1, Entity = curr, OriginalEntity = orig, Operation = ChangeOperation.Insert };
+ ChangeSet cs = null;
+ Assert.Throws<InvalidOperationException>(delegate
+ {
+ cs = new ChangeSet(new ChangeSetEntry[] { entry });
+ },
+ String.Format(Resource.InvalidChangeSet, Resource.InvalidChangeSet_InsertsCantHaveOriginal));
+
+ // get original should throw for insert operations
+ entry = new ChangeSetEntry { Id = 1, Entity = curr, OriginalEntity = null, Operation = ChangeOperation.Insert };
+ cs = new ChangeSet(new ChangeSetEntry[] { entry });
+ Assert.Throws<InvalidOperationException>(delegate
+ {
+ cs.GetOriginal(curr);
+ },
+ String.Format(Resource.ChangeSet_OriginalNotValidForInsert));
+ }
+
+ [Fact]
+ public void Constructor_HasErrors()
+ {
+ ChangeSet changeSet = this.GenerateChangeSet();
+ Assert.False(changeSet.HasError);
+
+ changeSet = this.GenerateChangeSet();
+ changeSet.ChangeSetEntries.First().ValidationErrors = new List<ValidationResultInfo>() { new ValidationResultInfo("Error", new[] { "Error" }) };
+ Assert.True(changeSet.HasError, "Expected ChangeSet to have errors");
+ }
+
+ [Fact]
+ public void GetOriginal()
+ {
+ ChangeSet changeSet = this.GenerateChangeSet();
+ ChangeSetEntry op = changeSet.ChangeSetEntries.First();
+
+ Product currentEntity = new Product();
+ Product originalEntity = new Product();
+
+ op.Entity = currentEntity;
+ op.OriginalEntity = originalEntity;
+
+ Product changeSetOriginalEntity = changeSet.GetOriginal(currentEntity);
+
+ // Verify we returned the original
+ Assert.Same(originalEntity, changeSetOriginalEntity);
+ }
+
+ [Fact]
+ public void GetOriginal_EntityExistsMoreThanOnce()
+ {
+ ChangeSet changeSet = this.GenerateChangeSet();
+ ChangeSetEntry op1 = changeSet.ChangeSetEntries.Skip(0).First();
+ ChangeSetEntry op2 = changeSet.ChangeSetEntries.Skip(1).First();
+ ChangeSetEntry op3 = changeSet.ChangeSetEntries.Skip(2).First();
+
+ Product currentEntity = new Product(), originalEntity = new Product();
+
+ op1.Entity = currentEntity;
+ op1.OriginalEntity = originalEntity;
+
+ op2.Entity = currentEntity;
+ op2.OriginalEntity = originalEntity;
+
+ op3.Entity = currentEntity;
+ op3.OriginalEntity = null;
+
+ Product changeSetOriginalEntity = changeSet.GetOriginal(currentEntity);
+
+ // Verify we returned the original
+ Assert.Same(originalEntity, changeSetOriginalEntity);
+ }
+
+ [Fact]
+ public void GetOriginal_InvalidArgs()
+ {
+ ChangeSet changeSet = this.GenerateChangeSet();
+
+ Assert.ThrowsArgumentNull(
+ () => changeSet.GetOriginal<Product>(null),
+ "clientEntity");
+ }
+
+ [Fact]
+ public void GetOriginal_EntityOperationNotFound()
+ {
+ ChangeSet changeSet = this.GenerateChangeSet();
+ Assert.Throws<ArgumentException>(
+ () => changeSet.GetOriginal(new Product()),
+ Resource.ChangeSet_ChangeSetEntryNotFound);
+ }
+
+ private ChangeSet GenerateChangeSet()
+ {
+ return new ChangeSet(this.GenerateEntityOperations(false));
+ }
+
+ private IEnumerable<ChangeSetEntry> GenerateEntityOperations(bool alternateTypes)
+ {
+ List<ChangeSetEntry> ops = new List<ChangeSetEntry>(10);
+
+ int id = 1;
+ for (int i = 0; i < ops.Capacity; ++i)
+ {
+ object entity, originalEntity;
+
+ if (!alternateTypes || i % 2 == 0)
+ {
+ entity = new MockEntity1() { FullName = String.Format("FName{0} LName{0}", i) };
+ originalEntity = new MockEntity1() { FullName = String.Format("OriginalFName{0} OriginalLName{0}", i) };
+ }
+ else
+ {
+ entity = new MockEntity2() { FullNameAndID = String.Format("FName{0} LName{0} ID{0}", i) };
+ originalEntity = new MockEntity2() { FullNameAndID = String.Format("OriginalFName{0} OriginalLName{0} OriginalID{0}", i) };
+ }
+
+ ops.Add(new ChangeSetEntry { Id = id++, Entity = entity, OriginalEntity = originalEntity, Operation = ChangeOperation.Update });
+ }
+
+ return ops;
+ }
+
+ public class MockStoreEntity
+ {
+ public int ID { get; set; }
+ public string FirstName { get; set; }
+ public string LastName { get; set; }
+ }
+
+ public class MockEntity1
+ {
+ public string FullName { get; set; }
+ }
+
+ public class MockEntity2
+ {
+ public string FullNameAndID { get; set; }
+ }
+
+ public class MockDerivedEntity : MockEntity1
+ {
+ }
+ }
+}
diff --git a/test/Microsoft.Web.Http.Data.Test/Controllers/CatalogController.cs b/test/Microsoft.Web.Http.Data.Test/Controllers/CatalogController.cs
new file mode 100644
index 00000000..d78fb874
--- /dev/null
+++ b/test/Microsoft.Web.Http.Data.Test/Controllers/CatalogController.cs
@@ -0,0 +1,71 @@
+using System.Collections.Generic;
+using System.Linq;
+using System.Security.Principal;
+using System.Web.Http;
+using Microsoft.Web.Http.Data.Test.Models;
+
+namespace Microsoft.Web.Http.Data.Test
+{
+ public class CatalogController : DataController
+ {
+ private Product[] products;
+
+ public CatalogController()
+ {
+ this.products = new Product[] {
+ new Product { ProductID = 1, ProductName = "Frish Gnarbles", UnitPrice = 12.33M, UnitsInStock = 55 },
+ new Product { ProductID = 2, ProductName = "Crispy Snarfs", UnitPrice = 4.22M, UnitsInStock = 11 },
+ new Product { ProductID = 1, ProductName = "Cheezy Snax", UnitPrice = 2.99M, UnitsInStock = 21 },
+ new Product { ProductID = 1, ProductName = "Fruit Yummies", UnitPrice = 5.55M, UnitsInStock = 88 },
+ new Product { ProductID = 1, ProductName = "Choco Wafers", UnitPrice = 1.87M, UnitsInStock = 109 },
+ new Product { ProductID = 1, ProductName = "Fritter Flaps", UnitPrice = 2.45M, UnitsInStock = 444 },
+ new Product { ProductID = 1, ProductName = "Yummy Bears", UnitPrice = 2.00M, UnitsInStock = 27 },
+ new Product { ProductID = 1, ProductName = "Cheddar Gnomes", UnitPrice = 3.99M, UnitsInStock = 975 },
+ new Product { ProductID = 1, ProductName = "Beefcicles", UnitPrice = 0.99M, UnitsInStock = 634 },
+ new Product { ProductID = 1, ProductName = "Butterscotchies", UnitPrice = 1.00M, UnitsInStock = 789 }
+ };
+ }
+
+ [ResultLimit(9)]
+ public IQueryable<Product> GetProducts()
+ {
+ return this.products.AsQueryable();
+ }
+
+ [ResultLimit(5)]
+ public IEnumerable<Product> GetProductsEnumerable()
+ {
+ return this.products.AsQueryable();
+ }
+
+ public IQueryable<Order> GetOrders()
+ {
+ return new Order[] {
+ new Order { OrderID = 1, CustomerID = "ALFKI" },
+ new Order { OrderID = 2, CustomerID = "CHOPS" }
+ }.AsQueryable();
+ }
+
+ public IEnumerable<Order_Detail> GetDetails(int orderId)
+ {
+ return Enumerable.Empty<Order_Detail>();
+ }
+
+ public void InsertOrder(Order order)
+ {
+
+ }
+
+ [Authorize]
+ public void UpdateProduct(Product product)
+ {
+ // demonstrate that the current ActionContext can be accessed by
+ // controller actions
+ IPrincipal user = this.ActionContext.ControllerContext.Request.GetUserPrincipal();
+ }
+
+ public void InsertOrderDetail(Order_Detail detail)
+ {
+ }
+ }
+}
diff --git a/test/Microsoft.Web.Http.Data.Test/Controllers/CitiesController.cs b/test/Microsoft.Web.Http.Data.Test/Controllers/CitiesController.cs
new file mode 100644
index 00000000..547a5eeb
--- /dev/null
+++ b/test/Microsoft.Web.Http.Data.Test/Controllers/CitiesController.cs
@@ -0,0 +1,15 @@
+using System.Linq;
+using Microsoft.Web.Http.Data.Test.Models;
+
+namespace Microsoft.Web.Http.Data.Test
+{
+ public class CitiesController : DataController
+ {
+ private CityData cityData = new CityData();
+
+ public IQueryable<City> GetCities()
+ {
+ return this.cityData.Cities.AsQueryable();
+ }
+ }
+}
diff --git a/test/Microsoft.Web.Http.Data.Test/Controllers/NorthwindEFController.cs b/test/Microsoft.Web.Http.Data.Test/Controllers/NorthwindEFController.cs
new file mode 100644
index 00000000..dfa032f5
--- /dev/null
+++ b/test/Microsoft.Web.Http.Data.Test/Controllers/NorthwindEFController.cs
@@ -0,0 +1,45 @@
+using System.ComponentModel.DataAnnotations;
+using System.Linq;
+using Microsoft.Web.Http.Data.EntityFramework;
+using Microsoft.Web.Http.Data.Test.Models.EF;
+
+namespace Microsoft.Web.Http.Data.Test
+{
+ public class NorthwindEFTestController : LinqToEntitiesDataController<NorthwindEntities>
+ {
+ public IQueryable<Product> GetProducts()
+ {
+ return this.ObjectContext.Products;
+ }
+
+ public void InsertProduct(Product product)
+ {
+ }
+
+ public void UpdateProduct(Product product)
+ {
+ }
+
+ protected override NorthwindEntities CreateObjectContext()
+ {
+ return new NorthwindEntities(TestHelpers.GetTestEFConnectionString());
+ }
+ }
+}
+
+namespace Microsoft.Web.Http.Data.Test.Models.EF
+{
+ [MetadataType(typeof(ProductMetadata))]
+ public partial class Product
+ {
+ internal sealed class ProductMetadata
+ {
+ [Editable(false, AllowInitialValue = true)]
+ [StringLength(777, MinimumLength = 2)]
+ public string QuantityPerUnit { get; set; }
+
+ [Range(0, 1000000)]
+ public string UnitPrice { get; set; }
+ }
+ }
+}
diff --git a/test/Microsoft.Web.Http.Data.Test/DataControllerDescriptionTest.cs b/test/Microsoft.Web.Http.Data.Test/DataControllerDescriptionTest.cs
new file mode 100644
index 00000000..f2778b87
--- /dev/null
+++ b/test/Microsoft.Web.Http.Data.Test/DataControllerDescriptionTest.cs
@@ -0,0 +1,192 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.ComponentModel.DataAnnotations;
+using System.Linq;
+using System.Threading.Tasks;
+using System.Web.Http.Controllers;
+using Microsoft.Web.Http.Data.EntityFramework;
+using Microsoft.Web.Http.Data.EntityFramework.Metadata;
+using Microsoft.Web.Http.Data.Test.Models;
+using System.Web.Http;
+using System.Web.Http.Filters;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace Microsoft.Web.Http.Data.Test
+{
+ public class DataControllerDescriptionTest
+ {
+ // verify that the LinqToEntitiesMetadataProvider is registered by default for
+ // LinqToEntitiesDataController<T> derived types
+ [Fact]
+ public void EFMetadataProvider_AttributeInference()
+ {
+ HttpConfiguration configuration = new HttpConfiguration();
+ HttpControllerDescriptor controllerDescriptor = new HttpControllerDescriptor
+ {
+ Configuration = configuration,
+ ControllerType = typeof(NorthwindEFTestController),
+ };
+ DataControllerDescription description = GetDataControllerDescription(typeof(NorthwindEFTestController));
+ PropertyDescriptorCollection properties = TypeDescriptor.GetProperties(typeof(Microsoft.Web.Http.Data.Test.Models.EF.Product));
+
+ // verify key attribute
+ Assert.NotNull(properties["ProductID"].Attributes[typeof(KeyAttribute)]);
+ Assert.Null(properties["ProductName"].Attributes[typeof(KeyAttribute)]);
+
+ // verify StringLengthAttribute
+ StringLengthAttribute sla = (StringLengthAttribute)properties["ProductName"].Attributes[typeof(StringLengthAttribute)];
+ Assert.NotNull(sla);
+ Assert.Equal(40, sla.MaximumLength);
+
+ // verify RequiredAttribute
+ RequiredAttribute ra = (RequiredAttribute)properties["ProductName"].Attributes[typeof(RequiredAttribute)];
+ Assert.NotNull(ra);
+ Assert.False(ra.AllowEmptyStrings);
+
+ // verify association attribute
+ AssociationAttribute aa = (AssociationAttribute)properties["Category"].Attributes[typeof(AssociationAttribute)];
+ Assert.NotNull(aa);
+ Assert.Equal("Category_Product", aa.Name);
+ Assert.True(aa.IsForeignKey);
+ Assert.Equal("CategoryID", aa.ThisKey);
+ Assert.Equal("CategoryID", aa.OtherKey);
+
+ // verify metadata from "buddy class"
+ PropertyDescriptor pd = properties["QuantityPerUnit"];
+ sla = (StringLengthAttribute)pd.Attributes[typeof(StringLengthAttribute)];
+ Assert.NotNull(sla);
+ Assert.Equal(777, sla.MaximumLength);
+ EditableAttribute ea = (EditableAttribute)pd.Attributes[typeof(EditableAttribute)];
+ Assert.False(ea.AllowEdit);
+ Assert.True(ea.AllowInitialValue);
+ }
+
+ [Fact]
+ public void EFTypeDescriptor_ExcludedEntityMembers()
+ {
+ PropertyDescriptor pd = TypeDescriptor.GetProperties(typeof(Microsoft.Web.Http.Data.Test.Models.EF.Product))["EntityState"];
+ Assert.True(LinqToEntitiesTypeDescriptor.ShouldExcludeEntityMember(pd));
+
+ pd = TypeDescriptor.GetProperties(typeof(Microsoft.Web.Http.Data.Test.Models.EF.Product))["EntityState"];
+ Assert.True(LinqToEntitiesTypeDescriptor.ShouldExcludeEntityMember(pd));
+
+ pd = TypeDescriptor.GetProperties(typeof(Microsoft.Web.Http.Data.Test.Models.EF.Product))["SupplierReference"];
+ Assert.True(LinqToEntitiesTypeDescriptor.ShouldExcludeEntityMember(pd));
+ }
+
+ [Fact]
+ public void DescriptionValidation_NonAuthorizationFilter()
+ {
+ Assert.Throws<NotSupportedException>(
+ () => GetDataControllerDescription(typeof(InvalidController_NonAuthMethodFilter)),
+ String.Format(String.Format(Resource.InvalidAction_UnsupportedFilterType, "InvalidController_NonAuthMethodFilter", "UpdateProduct")));
+ }
+
+ /// <summary>
+ /// Verify that associated entities are correctly registered in the description when
+ /// using explicit data contracts
+ /// </summary>
+ [Fact]
+ public void AssociatedEntityTypeDiscovery_ExplicitDataContract()
+ {
+ DataControllerDescription description = GetDataControllerDescription(typeof(IncludedAssociationTestController_ExplicitDataContract));
+ List<Type> entityTypes = description.EntityTypes.ToList();
+ Assert.Equal(8, entityTypes.Count);
+ Assert.True(entityTypes.Contains(typeof(Microsoft.Web.Http.Data.Test.Models.EF.Order)));
+ Assert.True(entityTypes.Contains(typeof(Microsoft.Web.Http.Data.Test.Models.EF.Order_Detail)));
+ Assert.True(entityTypes.Contains(typeof(Microsoft.Web.Http.Data.Test.Models.EF.Customer)));
+ Assert.True(entityTypes.Contains(typeof(Microsoft.Web.Http.Data.Test.Models.EF.Employee)));
+ Assert.True(entityTypes.Contains(typeof(Microsoft.Web.Http.Data.Test.Models.EF.Product)));
+ Assert.True(entityTypes.Contains(typeof(Microsoft.Web.Http.Data.Test.Models.EF.Category)));
+ Assert.True(entityTypes.Contains(typeof(Microsoft.Web.Http.Data.Test.Models.EF.Supplier)));
+ Assert.True(entityTypes.Contains(typeof(Microsoft.Web.Http.Data.Test.Models.EF.Shipper)));
+ }
+
+ /// <summary>
+ /// Verify that associated entities are correctly registered in the description when
+ /// using implicit data contracts
+ /// </summary>
+ [Fact]
+ public void AssociatedEntityTypeDiscovery_ImplicitDataContract()
+ {
+ DataControllerDescription description = GetDataControllerDescription(typeof(IncludedAssociationTestController_ImplicitDataContract));
+ List<Type> entityTypes = description.EntityTypes.ToList();
+ Assert.Equal(3, entityTypes.Count);
+ Assert.True(entityTypes.Contains(typeof(Microsoft.Web.Http.Data.Test.Models.Customer)));
+ Assert.True(entityTypes.Contains(typeof(Microsoft.Web.Http.Data.Test.Models.Order)));
+ Assert.True(entityTypes.Contains(typeof(Microsoft.Web.Http.Data.Test.Models.Order_Detail)));
+ }
+
+ /// <summary>
+ /// Verify that DataControllerDescription correctly handles Task returning actions and discovers
+ /// entity types from those as well (unwrapping the task type).
+ /// </summary>
+ [Fact]
+ public void TaskReturningGetActions()
+ {
+ DataControllerDescription desc = GetDataControllerDescription(typeof(TaskReturningGetActionsController));
+ Assert.Equal(4, desc.EntityTypes.Count());
+ Assert.True(desc.EntityTypes.Contains(typeof(City)));
+ Assert.True(desc.EntityTypes.Contains(typeof(CityWithInfo)));
+ Assert.True(desc.EntityTypes.Contains(typeof(CityWithEditHistory)));
+ Assert.True(desc.EntityTypes.Contains(typeof(State)));
+ }
+
+ internal static DataControllerDescription GetDataControllerDescription(Type controllerType)
+ {
+ HttpConfiguration configuration = new HttpConfiguration();
+ HttpControllerDescriptor controllerDescriptor = new HttpControllerDescriptor
+ {
+ Configuration = configuration,
+ ControllerType = controllerType
+ };
+ return DataControllerDescription.GetDescription(controllerDescriptor);
+ }
+ }
+
+ internal class InvalidController_NonAuthMethodFilter : DataController
+ {
+ // attempt to apply a non-auth filter
+ [TestActionFilter]
+ public void UpdateProduct(Microsoft.Web.Http.Data.Test.Models.EF.Product product)
+ {
+ }
+
+ // the restriction doesn't apply for non CUD actions
+ [TestActionFilter]
+ public IEnumerable<Microsoft.Web.Http.Data.Test.Models.EF.Product> GetProducts()
+ {
+ return null;
+ }
+ }
+
+ internal class TaskReturningGetActionsController : DataController
+ {
+ public Task<IEnumerable<City>> GetCities()
+ {
+ return null;
+ }
+
+ public Task<State> GetState(string name)
+ {
+ return null;
+ }
+ }
+
+ [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = true, AllowMultiple = true)]
+ public class TestActionFilterAttribute : ActionFilterAttribute
+ {
+ }
+
+ internal class IncludedAssociationTestController_ExplicitDataContract : LinqToEntitiesDataController<Microsoft.Web.Http.Data.Test.Models.EF.NorthwindEntities>
+ {
+ public IQueryable<Microsoft.Web.Http.Data.Test.Models.EF.Order> GetOrders() { return null; }
+ }
+
+ internal class IncludedAssociationTestController_ImplicitDataContract : DataController
+ {
+ public IQueryable<Microsoft.Web.Http.Data.Test.Models.Customer> GetCustomers() { return null; }
+ }
+}
diff --git a/test/Microsoft.Web.Http.Data.Test/DataControllerQueryTests.cs b/test/Microsoft.Web.Http.Data.Test/DataControllerQueryTests.cs
new file mode 100644
index 00000000..806ea680
--- /dev/null
+++ b/test/Microsoft.Web.Http.Data.Test/DataControllerQueryTests.cs
@@ -0,0 +1,231 @@
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Net.Http;
+using System.Net.Http.Formatting;
+using System.Threading;
+using System.Threading.Tasks;
+using System.Web.Http;
+using System.Web.Http.Dispatcher;
+using System.Web.Http.Routing;
+using Microsoft.Web.Http.Data.Test.Models;
+using Xunit;
+
+namespace Microsoft.Web.Http.Data.Test
+{
+ public class DataControllerQueryTests
+ {
+ /// <summary>
+ /// Execute a simple query with limited results
+ /// </summary>
+ [Fact]
+ public void GetProducts()
+ {
+ HttpConfiguration config = GetTestConfiguration();
+ HttpServer server = GetTestCatalogServer(config);
+
+ HttpRequestMessage request = TestHelpers.CreateTestMessage(TestConstants.CatalogUrl + "GetProducts", HttpMethod.Get, config);
+ HttpResponseMessage response = server.SubmitRequestAsync(request, CancellationToken.None).Result;
+
+ Product[] products = response.Content.ReadAsAsync<IQueryable<Product>>().Result.ToArray();
+ Assert.Equal(9, products.Length);
+ }
+
+ /// <summary>
+ /// Execute a query with an OData filter specified
+ /// </summary>
+ [Fact]
+ public void Query_Filter()
+ {
+ HttpConfiguration config = GetTestConfiguration();
+ HttpServer server = GetTestCatalogServer(config);
+
+ string query = "?$filter=UnitPrice lt 5.0";
+ HttpRequestMessage request = TestHelpers.CreateTestMessage(TestConstants.CatalogUrl + "GetProducts" + query, HttpMethod.Get, config);
+ HttpResponseMessage response = server.SubmitRequestAsync(request, CancellationToken.None).Result;
+
+ Product[] products = response.Content.ReadAsAsync<IQueryable<Product>>().Result.ToArray();
+ Assert.Equal(8, products.Length);
+ }
+
+ /// <summary>
+ /// Verify that the json/xml formatter instances are not shared between controllers, since
+ /// their serializers are configured per controller.
+ /// </summary>
+ [Fact(Skip = "Need to verify if this test still makes sense given changed ObjectContent design")]
+ public void Query_VerifyFormatterConfiguration()
+ {
+ HttpConfiguration config = GetTestConfiguration();
+ HttpServer catalogServer = GetTestCatalogServer(config);
+ HttpServer citiesServer = GetTestCitiesServer(config);
+
+ // verify products query
+ HttpRequestMessage request = TestHelpers.CreateTestMessage(TestConstants.CatalogUrl + "GetProducts", HttpMethod.Get, config);
+ HttpResponseMessage response = catalogServer.SubmitRequestAsync(request, CancellationToken.None).Result;
+ Product[] products = response.Content.ReadAsAsync<IQueryable<Product>>().Result.ToArray();
+ Assert.Equal(9, products.Length);
+
+ // verify serialization
+ QueryResult qr = new QueryResult(products, products.Length);
+ ObjectContent oc = (ObjectContent)response.Content;
+ MemoryStream ms = new MemoryStream();
+ Task task = new JsonMediaTypeFormatter().WriteToStreamAsync(typeof(QueryResult), qr, ms, oc.Headers, null);
+ task.Wait();
+ Assert.True(ms.Length > 0);
+
+ // verify cities query
+ request = TestHelpers.CreateTestMessage(TestConstants.CitiesUrl + "GetCities", HttpMethod.Get, config);
+ response = citiesServer.SubmitRequestAsync(request, CancellationToken.None).Result;
+ City[] cities = response.Content.ReadAsAsync<IQueryable<City>>().Result.ToArray();
+ Assert.Equal(11, cities.Length);
+
+ // verify serialization
+ qr = new QueryResult(cities, cities.Length);
+ oc = (ObjectContent)response.Content;
+ ms = new MemoryStream();
+ task = new JsonMediaTypeFormatter().WriteToStreamAsync(typeof(QueryResult), qr, ms, oc.Headers, null);
+ task.Wait();
+ Assert.True(ms.Length > 0);
+ }
+
+ /// <summary>
+ /// Execute a query that requests an inline count with a paging query applied.
+ /// </summary>
+ [Fact]
+ public void Query_InlineCount_SkipTop()
+ {
+ HttpConfiguration config = GetTestConfiguration();
+ HttpServer server = GetTestCatalogServer(config);
+
+ string query = "?$filter=UnitPrice lt 5.0&$skip=2&$top=5&$inlinecount=allpages";
+ HttpRequestMessage request = TestHelpers.CreateTestMessage(TestConstants.CatalogUrl + "GetProducts" + query, HttpMethod.Get, config);
+ HttpResponseMessage response = server.SubmitRequestAsync(request, CancellationToken.None).Result;
+
+ QueryResult queryResult = response.Content.ReadAsAsync<QueryResult>().Result;
+ Assert.Equal(5, queryResult.Results.Cast<object>().Count());
+ Assert.Equal(8, queryResult.TotalCount);
+ }
+
+ /// <summary>
+ /// Execute a query that requests an inline count with only a top operation applied in the query.
+ /// Expect the total count to not inlcude the take operation.
+ /// </summary>
+ [Fact]
+ public void Query_IncludeTotalCount_Top()
+ {
+ HttpConfiguration config = GetTestConfiguration();
+ HttpServer server = GetTestCatalogServer(config);
+
+ string query = "?$filter=UnitPrice lt 5.0&$top=5&$inlinecount=allpages";
+ HttpRequestMessage request = TestHelpers.CreateTestMessage(TestConstants.CatalogUrl + "GetProducts" + query, HttpMethod.Get, config);
+ HttpResponseMessage response = server.SubmitRequestAsync(request, CancellationToken.None).Result;
+
+ QueryResult queryResult = response.Content.ReadAsAsync<QueryResult>().Result;
+ Assert.Equal(5, queryResult.Results.Cast<object>().Count());
+ Assert.Equal(8, queryResult.TotalCount);
+ }
+
+ /// <summary>
+ /// Execute a query that requests an inline count with no paging operations specified in the
+ /// user query. There is however still a server specified limit.
+ /// </summary>
+ [Fact]
+ public void Query_IncludeTotalCount_NoPaging()
+ {
+ HttpConfiguration config = GetTestConfiguration();
+ HttpServer server = GetTestCatalogServer(config);
+
+ string query = "?$filter=UnitPrice lt 5.0&$inlinecount=allpages";
+ HttpRequestMessage request = TestHelpers.CreateTestMessage(TestConstants.CatalogUrl + "GetProducts" + query, HttpMethod.Get, config);
+ HttpResponseMessage response = server.SubmitRequestAsync(request, CancellationToken.None).Result;
+
+ QueryResult queryResult = response.Content.ReadAsAsync<QueryResult>().Result;
+ Assert.Equal(8, queryResult.Results.Cast<object>().Count());
+ Assert.Equal(8, queryResult.TotalCount);
+ }
+
+ /// <summary>
+ /// Execute a query that sets the inlinecount option explicitly to 'none', and verify count is not returned.
+ /// </summary>
+ [Fact]
+ public void Query_IncludeTotalCount_False()
+ {
+ HttpConfiguration config = GetTestConfiguration();
+ HttpServer server = GetTestCatalogServer(config);
+
+ string query = "?$filter=UnitPrice lt 5.0&$inlinecount=none";
+ HttpRequestMessage request = TestHelpers.CreateTestMessage(TestConstants.CatalogUrl + "GetProducts" + query, HttpMethod.Get, config);
+ HttpResponseMessage response = server.SubmitRequestAsync(request, CancellationToken.None).Result;
+
+ Product[] products = response.Content.ReadAsAsync<IQueryable<Product>>().Result.ToArray();
+ Assert.Equal(8, products.Length);
+ }
+
+ /// <summary>
+ /// Verify that result limits are applied to IEnumerable returning actions as well.
+ /// </summary>
+ [Fact]
+ public void Query_ResultLimit_Enumerable()
+ {
+ HttpConfiguration config = GetTestConfiguration();
+ HttpServer server = GetTestCatalogServer(config);
+
+ HttpRequestMessage request = TestHelpers.CreateTestMessage(TestConstants.CatalogUrl + "GetProductsEnumerable", HttpMethod.Get, config);
+ HttpResponseMessage response = server.SubmitRequestAsync(request, CancellationToken.None).Result;
+
+ Product[] products = response.Content.ReadAsAsync<IEnumerable<Product>>().Result.ToArray();
+ Assert.Equal(5, products.Length);
+ }
+
+ /// <summary>
+ /// Verify that when no skip/top query operations are performed (and no result limits are active
+ /// on the action server side), the total count returned is -1, indicating that the total count
+ /// equals the result count. This avoids the fx having to double enumerate the query results to
+ /// set the count server side.
+ /// </summary>
+ [Fact]
+ public void Query_TotalCount_Equals_ResultCount()
+ {
+ HttpConfiguration config = GetTestConfiguration();
+ HttpServer server = GetTestCatalogServer(config);
+
+ string query = "?$inlinecount=allpages";
+ HttpRequestMessage request = TestHelpers.CreateTestMessage(TestConstants.CatalogUrl + "GetOrders" + query, HttpMethod.Get, config);
+ HttpResponseMessage response = server.SubmitRequestAsync(request, CancellationToken.None).Result;
+
+ QueryResult result = response.Content.ReadAsAsync<QueryResult>().Result;
+ Assert.Equal(2, result.Results.Cast<object>().Count());
+ Assert.Equal(-1, result.TotalCount);
+ }
+
+ private HttpConfiguration GetTestConfiguration()
+ {
+ HttpConfiguration config = new HttpConfiguration();
+ return config;
+ }
+
+ private HttpServer GetTestCatalogServer(HttpConfiguration config)
+ {
+ HttpControllerDispatcher dispatcher = new HttpControllerDispatcher(config);
+
+ HttpRoute route = new HttpRoute("{controller}/{action}", new HttpRouteValueDictionary("Catalog"));
+ config.Routes.Add("catalog", route);
+
+ HttpServer server = new HttpServer(config, dispatcher);
+
+ return server;
+ }
+
+ private HttpServer GetTestCitiesServer(HttpConfiguration config)
+ {
+ HttpControllerDispatcher dispatcher = new HttpControllerDispatcher(config);
+
+ HttpRoute route = new HttpRoute("{controller}/{action}", new HttpRouteValueDictionary("Cities"));
+ config.Routes.Add("cities", route);
+
+ HttpServer server = new HttpServer(config, dispatcher);
+
+ return server;
+ }
+ }
+}
diff --git a/test/Microsoft.Web.Http.Data.Test/DataControllerSubmitTests.cs b/test/Microsoft.Web.Http.Data.Test/DataControllerSubmitTests.cs
new file mode 100644
index 00000000..62bc8602
--- /dev/null
+++ b/test/Microsoft.Web.Http.Data.Test/DataControllerSubmitTests.cs
@@ -0,0 +1,372 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Net;
+using System.Net.Http;
+using System.Net.Http.Headers;
+using System.Runtime.Serialization;
+using System.Text;
+using System.Threading;
+using System.Web.Http;
+using System.Web.Http.Controllers;
+using System.Web.Http.Dispatcher;
+using System.Web.Http.Filters;
+using System.Web.Http.Routing;
+using Microsoft.Web.Http.Data.Test.Models;
+using Newtonsoft.Json;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace Microsoft.Web.Http.Data.Test
+{
+ public class DataControllerSubmitTests
+ {
+ // Verify that POSTs directly to CUD actions still go through the submit pipeline
+ [Fact]
+ public void Submit_Proxy_Insert()
+ {
+ Order order = new Order { OrderID = 1, OrderDate = DateTime.Now };
+
+ HttpResponseMessage response = this.ExecuteSelfHostRequest(TestConstants.CatalogUrl + "InsertOrder", "Catalog", order);
+ Order resultOrder = response.Content.ReadAsAsync<Order>().Result;
+ Assert.NotNull(resultOrder);
+ }
+
+ // Submit a changeset with multiple entries
+ [Fact]
+ public void Submit_Multiple_Success()
+ {
+ Order order = new Order { OrderID = 1, OrderDate = DateTime.Now };
+ Product product = new Product { ProductID = 1, ProductName = "Choco Wafers" };
+ ChangeSetEntry[] changeSet = new ChangeSetEntry[] {
+ new ChangeSetEntry { Id = 1, Entity = order, Operation = ChangeOperation.Insert },
+ new ChangeSetEntry { Id = 2, Entity = product, Operation = ChangeOperation.Update }
+ };
+
+ ChangeSetEntry[] resultChangeSet = this.ExecuteSubmit(TestConstants.CatalogUrl + "Submit", "Catalog", changeSet);
+ Assert.Equal(2, resultChangeSet.Length);
+ Assert.True(resultChangeSet.All(p => !p.HasError));
+ }
+
+ // Submit a changeset with one parent object and multiple dependent children
+ [Fact]
+ public void Submit_Tree_Success()
+ {
+ Order order = new Order { OrderID = 1, OrderDate = DateTime.Now };
+ Order_Detail d1 = new Order_Detail { ProductID = 1 };
+ Order_Detail d2 = new Order_Detail { ProductID = 2 };
+ Dictionary<string, int[]> detailsAssociation = new Dictionary<string, int[]>();
+ detailsAssociation.Add("Order_Details", new int[] { 2, 3 });
+ ChangeSetEntry[] changeSet = new ChangeSetEntry[] {
+ new ChangeSetEntry { Id = 1, Entity = order, Operation = ChangeOperation.Insert, Associations = detailsAssociation },
+ new ChangeSetEntry { Id = 2, Entity = d1, Operation = ChangeOperation.Insert },
+ new ChangeSetEntry { Id = 3, Entity = d2, Operation = ChangeOperation.Insert }
+ };
+
+ ChangeSetEntry[] resultChangeSet = this.ExecuteSubmit(TestConstants.CatalogUrl + "Submit", "Catalog", changeSet);
+ Assert.Equal(3, resultChangeSet.Length);
+ Assert.True(resultChangeSet.All(p => !p.HasError));
+ }
+
+ /// <summary>
+ /// End to end validation scenario showing changeset validation. DataAnnotations validation attributes are applied to
+ /// the model by DataController metadata providers (metadata coming all the way from the EF model, as well as "buddy
+ /// class" metadata), and these are validated during changeset validation. The validation results per entity/member are
+ /// returned via the changeset and verified.
+ /// </summary>
+ [Fact]
+ public void Submit_Validation_Failure()
+ {
+ Microsoft.Web.Http.Data.Test.Models.EF.Product newProduct = new Microsoft.Web.Http.Data.Test.Models.EF.Product { ProductID = 1, ProductName = String.Empty, UnitPrice = -1 };
+ Microsoft.Web.Http.Data.Test.Models.EF.Product updateProduct = new Microsoft.Web.Http.Data.Test.Models.EF.Product { ProductID = 1, ProductName = new string('x', 50), UnitPrice = 55.77M };
+ ChangeSetEntry[] changeSet = new ChangeSetEntry[] {
+ new ChangeSetEntry { Id = 1, Entity = newProduct, Operation = ChangeOperation.Insert },
+ new ChangeSetEntry { Id = 2, Entity = updateProduct, Operation = ChangeOperation.Update }
+ };
+
+ HttpResponseMessage response = this.ExecuteSelfHostRequest("http://testhost/NorthwindEFTest/Submit", "NorthwindEFTest", changeSet);
+ changeSet = response.Content.ReadAsAsync<ChangeSetEntry[]>().Result;
+
+ // errors for the new product
+ ValidationResultInfo[] errors = changeSet[0].ValidationErrors.ToArray();
+ Assert.Equal(2, errors.Length);
+ Assert.True(changeSet[0].HasError);
+
+ // validation rule inferred from EF model
+ Assert.Equal("ProductName", errors[0].SourceMemberNames.Single());
+ Assert.Equal("The ProductName field is required.", errors[0].Message);
+
+ // validation rule coming from buddy class
+ Assert.Equal("UnitPrice", errors[1].SourceMemberNames.Single());
+ Assert.Equal("The field UnitPrice must be between 0 and 1000000.", errors[1].Message);
+
+ // errors for the updated product
+ errors = changeSet[1].ValidationErrors.ToArray();
+ Assert.Equal(1, errors.Length);
+ Assert.True(changeSet[1].HasError);
+
+ // validation rule inferred from EF model
+ Assert.Equal("ProductName", errors[0].SourceMemberNames.Single());
+ Assert.Equal("The field ProductName must be a string with a maximum length of 40.", errors[0].Message);
+ }
+
+ [Fact]
+ public void Submit_Authorization_Success()
+ {
+ TestAuthAttribute.Reset();
+
+ Product product = new Product { ProductID = 1, ProductName = "Choco Wafers" };
+ ChangeSetEntry[] changeSet = new ChangeSetEntry[] {
+ new ChangeSetEntry { Id = 1, Entity = product, Operation = ChangeOperation.Update }
+ };
+
+ ChangeSetEntry[] resultChangeSet = this.ExecuteSubmit("http://testhost/TestAuth/Submit", "TestAuth", changeSet);
+ Assert.Equal(1, resultChangeSet.Length);
+ Assert.True(TestAuthAttribute.Log.SequenceEqual(new string[] { "Global", "Class", "SubmitMethod", "UserMethod" }));
+ }
+
+ [Fact]
+ public void Submit_Authorization_Fail_UserMethod()
+ {
+ TestAuthAttribute.Reset();
+
+ Product product = new Product { ProductID = 1, ProductName = "Choco Wafers" };
+ ChangeSetEntry[] changeSet = new ChangeSetEntry[] {
+ new ChangeSetEntry { Id = 1, Entity = product, Operation = ChangeOperation.Update }
+ };
+
+ TestAuthAttribute.FailLevel = "UserMethod";
+ HttpResponseMessage response = this.ExecuteSelfHostRequest("http://testhost/TestAuth/Submit", "TestAuth", changeSet);
+
+ Assert.True(TestAuthAttribute.Log.SequenceEqual(new string[] { "Global", "Class", "SubmitMethod", "UserMethod" }));
+ Assert.Equal("Not Authorized", response.ReasonPhrase);
+ Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode);
+ }
+
+ [Fact]
+ public void Submit_Authorization_Fail_SubmitMethod()
+ {
+ TestAuthAttribute.Reset();
+
+ Product product = new Product { ProductID = 1, ProductName = "Choco Wafers" };
+ ChangeSetEntry[] changeSet = new ChangeSetEntry[] {
+ new ChangeSetEntry { Id = 1, Entity = product, Operation = ChangeOperation.Update }
+ };
+
+ TestAuthAttribute.FailLevel = "SubmitMethod";
+ HttpResponseMessage response = this.ExecuteSelfHostRequest("http://testhost/TestAuth/Submit", "TestAuth", changeSet);
+
+ Assert.True(TestAuthAttribute.Log.SequenceEqual(new string[] { "Global", "Class", "SubmitMethod" }));
+ Assert.Equal("Not Authorized", response.ReasonPhrase);
+ Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode);
+ }
+
+ [Fact]
+ public void Submit_Authorization_Fail_Class()
+ {
+ TestAuthAttribute.Reset();
+
+ Product product = new Product { ProductID = 1, ProductName = "Choco Wafers" };
+ ChangeSetEntry[] changeSet = new ChangeSetEntry[] {
+ new ChangeSetEntry { Id = 1, Entity = product, Operation = ChangeOperation.Update }
+ };
+
+ TestAuthAttribute.FailLevel = "Class";
+ HttpResponseMessage response = this.ExecuteSelfHostRequest("http://testhost/TestAuth/Submit", "TestAuth", changeSet);
+
+ Assert.True(TestAuthAttribute.Log.SequenceEqual(new string[] { "Global", "Class" }));
+ Assert.Equal("Not Authorized", response.ReasonPhrase);
+ Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode);
+ }
+
+ [Fact]
+ public void Submit_Authorization_Fail_Global()
+ {
+ TestAuthAttribute.Reset();
+
+ Product product = new Product { ProductID = 1, ProductName = "Choco Wafers" };
+ ChangeSetEntry[] changeSet = new ChangeSetEntry[] {
+ new ChangeSetEntry { Id = 1, Entity = product, Operation = ChangeOperation.Update }
+ };
+
+ TestAuthAttribute.FailLevel = "Global";
+ HttpResponseMessage response = this.ExecuteSelfHostRequest("http://testhost/TestAuth/Submit", "TestAuth", changeSet);
+
+ Assert.True(TestAuthAttribute.Log.SequenceEqual(new string[] { "Global" }));
+ Assert.Equal("Not Authorized", response.ReasonPhrase);
+ Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode);
+ }
+
+ // Verify that a CUD operation that isn't supported for a given entity type
+ // results in a server error
+ [Fact]
+ public void Submit_ResolveActions_UnsupportedAction()
+ {
+ Product product = new Product { ProductID = 1, ProductName = "Choco Wafers" };
+ ChangeSetEntry[] changeSet = new ChangeSetEntry[] {
+ new ChangeSetEntry { Id = 1, Entity = product, Operation = ChangeOperation.Delete }
+ };
+
+ HttpConfiguration configuration = new HttpConfiguration();
+ HttpControllerDescriptor controllerDescriptor = new HttpControllerDescriptor(configuration, "NorthwindEFTestController", typeof(NorthwindEFTestController));
+ DataControllerDescription description = DataControllerDescription.GetDescription(controllerDescriptor);
+ Assert.Throws<InvalidOperationException>(
+ () => DataController.ResolveActions(description, changeSet),
+ String.Format(Resource.DataController_InvalidAction, "Delete", "Product"));
+ }
+
+ /// <summary>
+ /// Execute a full roundtrip Submit request for the specified changeset, going through
+ /// the full serialization pipeline.
+ /// </summary>
+ private ChangeSetEntry[] ExecuteSubmit(string url, string controllerName, ChangeSetEntry[] changeSet)
+ {
+ HttpResponseMessage response = this.ExecuteSelfHostRequest(url, controllerName, changeSet);
+ ChangeSetEntry[] resultChangeSet = GetChangesetResponse(response);
+ return changeSet;
+ }
+
+ private HttpResponseMessage ExecuteSelfHostRequest(string url, string controller, object data)
+ {
+ return ExecuteSelfHostRequest(url, controller, data, "application/json");
+ }
+
+ private HttpResponseMessage ExecuteSelfHostRequest(string url, string controller, object data, string mediaType)
+ {
+ HttpConfiguration config = new HttpConfiguration();
+ IHttpRoute routeData;
+ if (!config.Routes.TryGetValue(controller, out routeData))
+ {
+ HttpRoute route = new HttpRoute("{controller}/{action}", new HttpRouteValueDictionary(controller));
+ config.Routes.Add(controller, route);
+ }
+
+ HttpControllerDispatcher dispatcher = new HttpControllerDispatcher(config);
+ HttpServer server = new HttpServer(config, dispatcher);
+
+ string serializedChangeSet = String.Empty;
+ if (mediaType == "application/json")
+ {
+ JsonSerializer serializer = new JsonSerializer() { PreserveReferencesHandling = PreserveReferencesHandling.Objects, TypeNameHandling = TypeNameHandling.All };
+ MemoryStream ms = new MemoryStream();
+ JsonWriter writer = new JsonTextWriter(new StreamWriter(ms));
+ serializer.Serialize(writer, data);
+ writer.Flush();
+ ms.Seek(0, 0);
+ serializedChangeSet = Encoding.UTF8.GetString(ms.GetBuffer()).TrimEnd('\0');
+ }
+ else
+ {
+ DataContractSerializer ser = new DataContractSerializer(data.GetType(), GetTestKnownTypes());
+ MemoryStream ms = new MemoryStream();
+ ser.WriteObject(ms, data);
+ ms.Flush();
+ ms.Seek(0, 0);
+ serializedChangeSet = Encoding.UTF8.GetString(ms.GetBuffer()).TrimEnd('\0');
+ }
+
+ HttpRequestMessage request = TestHelpers.CreateTestMessage(url, HttpMethod.Post, config);
+ request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue(mediaType));
+ request.Content = new StringContent(serializedChangeSet, Encoding.UTF8, mediaType);
+
+ return server.SubmitRequestAsync(request, CancellationToken.None).Result;
+ }
+
+ /// <summary>
+ /// For the given Submit response, serialize and deserialize the content. This forces the
+ /// formatter pipeline to run so we can verify that registered serializers are being used
+ /// properly.
+ /// </summary>
+ private ChangeSetEntry[] GetChangesetResponse(HttpResponseMessage responseMessage)
+ {
+ // serialize the content to a stream
+ ObjectContent content = (ObjectContent)responseMessage.Content;
+ MemoryStream ms = new MemoryStream();
+ content.CopyToAsync(ms).Wait();
+ ms.Flush();
+ ms.Seek(0, 0);
+
+ // deserialize based on content type
+ ChangeSetEntry[] changeSet = null;
+ string mediaType = responseMessage.RequestMessage.Content.Headers.ContentType.MediaType;
+ if (mediaType == "application/json")
+ {
+ JsonSerializer ser = new JsonSerializer() { PreserveReferencesHandling = PreserveReferencesHandling.Objects, TypeNameHandling = TypeNameHandling.All };
+ changeSet = (ChangeSetEntry[])ser.Deserialize(new JsonTextReader(new StreamReader(ms)), content.ObjectType);
+ }
+ else
+ {
+ DataContractSerializer ser = new DataContractSerializer(content.ObjectType, GetTestKnownTypes());
+ changeSet = (ChangeSetEntry[])ser.ReadObject(ms);
+ }
+
+ return changeSet;
+ }
+
+ private IEnumerable<Type> GetTestKnownTypes()
+ {
+ List<Type> knownTypes = new List<Type>(new Type[] { typeof(Order), typeof(Product), typeof(Order_Detail) });
+ knownTypes.AddRange(new Type[] { typeof(Microsoft.Web.Http.Data.Test.Models.EF.Order), typeof(Microsoft.Web.Http.Data.Test.Models.EF.Product), typeof(Microsoft.Web.Http.Data.Test.Models.EF.Order_Detail) });
+ return knownTypes;
+ }
+ }
+
+ /// <summary>
+ /// Test controller used for multi-level authorization testing
+ /// </summary>
+ [TestAuth(Level = "Class")]
+ public class TestAuthController : DataController
+ {
+ [TestAuth(Level = "UserMethod")]
+ public void UpdateProduct(Product product)
+ {
+ }
+
+ [TestAuth(Level = "SubmitMethod")]
+ public override bool Submit(ChangeSet changeSet)
+ {
+ return base.Submit(changeSet);
+ }
+
+ protected override void Initialize(HttpControllerContext controllerContext)
+ {
+ controllerContext.Configuration.Filters.Add(new TestAuthAttribute() { Level = "Global" });
+
+ base.Initialize(controllerContext);
+ }
+ }
+
+ /// <summary>
+ /// Test authorization attribute used to verify authorization behavior.
+ /// </summary>
+ [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, Inherited = true, AllowMultiple = true)]
+ public class TestAuthAttribute : AuthorizationFilterAttribute
+ {
+ public string Level;
+
+ public static string FailLevel;
+
+ public static List<string> Log = new List<string>();
+
+ public override void OnAuthorization(HttpActionContext context)
+ {
+ TestAuthAttribute.Log.Add(Level);
+
+ if (FailLevel != null && FailLevel == Level)
+ {
+ HttpResponseMessage response = new HttpResponseMessage(HttpStatusCode.Unauthorized);
+ response.ReasonPhrase = "Not Authorized";
+ context.Response = response;
+ }
+
+ base.OnAuthorization(context);
+ }
+
+ public static void Reset()
+ {
+ FailLevel = null;
+ Log.Clear();
+ }
+ }
+}
diff --git a/test/Microsoft.Web.Http.Data.Test/MetadataExtensionsTests.cs b/test/Microsoft.Web.Http.Data.Test/MetadataExtensionsTests.cs
new file mode 100644
index 00000000..651efa40
--- /dev/null
+++ b/test/Microsoft.Web.Http.Data.Test/MetadataExtensionsTests.cs
@@ -0,0 +1,59 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel.DataAnnotations;
+using System.Json;
+using System.Linq;
+using Microsoft.Web.Http.Data.Helpers;
+using Xunit;
+
+namespace Microsoft.Web.Http.Data.Test
+{
+ public class MetadataExtensionsTests
+ {
+ /// <summary>
+ /// Serialize metadata for a test controller exposing types with various
+ /// metadata annotations.
+ /// </summary>
+ [Fact]
+ public void TestMetadataSerialization()
+ {
+ JsonValue metadata = GenerateMetadata(typeof(TestController));
+ string s = metadata.ToString();
+ Assert.True(s.Contains("{\"range\":[-10,20.5]}"));
+ }
+
+ private static JsonValue GenerateMetadata(Type dataControllerType)
+ {
+ DataControllerDescription desc = DataControllerDescriptionTest.GetDataControllerDescription(dataControllerType);
+ var metadata = DataControllerMetadataGenerator.GetMetadata(desc);
+
+ var jsonData = metadata.Select(m => new KeyValuePair<string, JsonValue>(m.EncodedTypeName, m.ToJsonValue()));
+ JsonValue metadataValue = new JsonObject(jsonData);
+
+ return metadataValue;
+ }
+ }
+
+ public class TestClass
+ {
+ [Key]
+ public int ID { get; set; }
+
+ [Required]
+ public string Name { get; set; }
+
+ [Range(-10.0, 20.5)]
+ public double Number { get; set; }
+
+ [StringLength(5)]
+ public string Address { get; set; }
+ }
+
+ public class TestController : DataController
+ {
+ public TestClass GetTestClass(int id)
+ {
+ return null;
+ }
+ }
+}
diff --git a/test/Microsoft.Web.Http.Data.Test/Microsoft.Web.Http.Data.Test.csproj b/test/Microsoft.Web.Http.Data.Test/Microsoft.Web.Http.Data.Test.csproj
new file mode 100644
index 00000000..bc26417b
--- /dev/null
+++ b/test/Microsoft.Web.Http.Data.Test/Microsoft.Web.Http.Data.Test.csproj
@@ -0,0 +1,143 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory),Runtime.sln))\tools\WebStack.settings.targets" />
+ <PropertyGroup>
+ <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+ <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+ <ProductVersion>9.0.30729</ProductVersion>
+ <SchemaVersion>2.0</SchemaVersion>
+ <ProjectGuid>{81876811-6C36-492A-9609-F0E85990FBC9}</ProjectGuid>
+ <OutputType>Library</OutputType>
+ <AppDesignerFolder>Properties</AppDesignerFolder>
+ <RootNamespace>Microsoft.Web.Http.Data.Test</RootNamespace>
+ <AssemblyName>Microsoft.Web.Http.Data.Test</AssemblyName>
+ <FileAlignment>512</FileAlignment>
+ <ProjectTypeGuids>{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
+ <ErrorReport>prompt</ErrorReport>
+ <WarningLevel>4</WarningLevel>
+ <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
+ <DebugSymbols>true</DebugSymbols>
+ <DebugType>full</DebugType>
+ <Optimize>false</Optimize>
+ <OutputPath>$(WebStackRootPath)\bin\Debug\Test\</OutputPath>
+ <DefineConstants>TRACE;DEBUG</DefineConstants>
+ <CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
+ <DebugType>pdbonly</DebugType>
+ <Optimize>true</Optimize>
+ <OutputPath>$(WebStackRootPath)\bin\Release\Test\</OutputPath>
+ <DefineConstants>TRACE</DefineConstants>
+ <CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)' == 'CodeCoverage' ">
+ <DebugSymbols>true</DebugSymbols>
+ <DebugType>full</DebugType>
+ <Optimize>false</Optimize>
+ <OutputPath>$(WebStackRootPath)\bin\CodeCoverage\Test\</OutputPath>
+ <DefineConstants>TRACE;DEBUG</DefineConstants>
+ <CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
+ </PropertyGroup>
+ <ItemGroup>
+ <Reference Include="EntityFramework">
+ <HintPath>..\..\packages\EntityFramework.4.1.10331.0\lib\EntityFramework.dll</HintPath>
+ </Reference>
+ <Reference Include="Moq">
+ <HintPath>..\..\packages\Moq.4.0.10827\lib\NET40\Moq.dll</HintPath>
+ </Reference>
+ <Reference Include="Newtonsoft.Json, Version=4.0.8.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
+ <SpecificVersion>False</SpecificVersion>
+ <HintPath>..\..\packages\Newtonsoft.Json.4.0.8\lib\net40\Newtonsoft.Json.dll</HintPath>
+ </Reference>
+ <Reference Include="System" />
+ <Reference Include="System.ComponentModel.DataAnnotations" />
+ <Reference Include="System.Core">
+ <RequiredTargetFramework>3.5</RequiredTargetFramework>
+ </Reference>
+ <Reference Include="System.Data" />
+ <Reference Include="System.Data.Entity" />
+ <Reference Include="System.Net.Http">
+ <HintPath>..\..\packages\Microsoft.Net.Http.2.0.20302.1\lib\net40\System.Net.Http.dll</HintPath>
+ </Reference>
+ <Reference Include="System.Net.Http.WebRequest">
+ <HintPath>..\..\packages\Microsoft.Net.Http.2.0.20302.1\lib\net40\System.Net.Http.WebRequest.dll</HintPath>
+ </Reference>
+ <Reference Include="System.Runtime.Serialization" />
+ <Reference Include="System.Web" />
+ <Reference Include="System.Web.Routing" />
+ <Reference Include="System.XML" />
+ <Reference Include="xunit">
+ <HintPath>..\..\packages\xunit.1.9.0.1566\lib\xunit.dll</HintPath>
+ </Reference>
+ <Reference Include="xunit.extensions">
+ <HintPath>..\..\packages\xunit.extensions.1.9.0.1566\lib\xunit.extensions.dll</HintPath>
+ </Reference>
+ </ItemGroup>
+ <ItemGroup>
+ <CodeAnalysisDependentAssemblyPaths Condition=" '$(VS100COMNTOOLS)' != '' " Include="$(VS100COMNTOOLS)..\IDE\PrivateAssemblies">
+ <Visible>False</Visible>
+ </CodeAnalysisDependentAssemblyPaths>
+ </ItemGroup>
+ <ItemGroup>
+ <Compile Include="ChangeSetTests.cs" />
+ <Compile Include="Controllers\CitiesController.cs" />
+ <Compile Include="Controllers\NorthwindEFController.cs" />
+ <Compile Include="DataControllerDescriptionTest.cs" />
+ <Compile Include="DataControllerQueryTests.cs" />
+ <Compile Include="DataControllerSubmitTests.cs" />
+ <Compile Include="MetadataExtensionsTests.cs" />
+ <Compile Include="Models\CatalogEntities.cs" />
+ <Compile Include="Models\Cities.cs" />
+ <Compile Include="Models\Northwind.Designer.cs">
+ <AutoGen>True</AutoGen>
+ <DesignTime>True</DesignTime>
+ <DependentUpon>Northwind.edmx</DependentUpon>
+ </Compile>
+ <Compile Include="Properties\AssemblyInfo.cs" />
+ <Compile Include="Controllers\CatalogController.cs" />
+ <Compile Include="TestHelpers.cs" />
+ </ItemGroup>
+ <ItemGroup>
+ <ProjectReference Include="..\..\src\System.Json\System.Json.csproj">
+ <Project>{F0441BE9-BDC0-4629-BE5A-8765FFAA2481}</Project>
+ <Name>System.Json</Name>
+ </ProjectReference>
+ <ProjectReference Include="..\..\src\System.Net.Http.Formatting\System.Net.Http.Formatting.csproj">
+ <Project>{668E9021-CE84-49D9-98FB-DF125A9FCDB0}</Project>
+ <Name>System.Net.Http.Formatting</Name>
+ </ProjectReference>
+ <ProjectReference Include="..\..\src\Microsoft.Web.Http.Data.Helpers\Microsoft.Web.Http.Data.Helpers.csproj">
+ <Project>{B6895A1B-382F-4A69-99EC-E965E19B0AB3}</Project>
+ <Name>Microsoft.Web.Http.Data.Helpers</Name>
+ </ProjectReference>
+ <ProjectReference Include="..\..\src\System.Web.Http\System.Web.Http.csproj">
+ <Project>{DDC1CE0C-486E-4E35-BB3B-EAB61F8F9440}</Project>
+ <Name>System.Web.Http</Name>
+ </ProjectReference>
+ <ProjectReference Include="..\..\src\Microsoft.Web.Http.Data.EntityFramework\Microsoft.Web.Http.Data.EntityFramework.csproj">
+ <Project>{653F3946-541C-42D3-BBC1-CE89B392BDA9}</Project>
+ <Name>Microsoft.Web.Http.Data.EntityFramework</Name>
+ </ProjectReference>
+ <ProjectReference Include="..\..\src\Microsoft.Web.Http.Data\Microsoft.Web.Http.Data.csproj">
+ <Project>{ACE91549-D86E-4EB6-8C2A-5FF51386BB68}</Project>
+ <Name>Microsoft.Web.Http.Data</Name>
+ </ProjectReference>
+ <ProjectReference Include="..\Microsoft.TestCommon\Microsoft.TestCommon.csproj">
+ <Project>{FCCC4CB7-BAF7-4A57-9F89-E5766FE536C0}</Project>
+ <Name>Microsoft.TestCommon</Name>
+ </ProjectReference>
+ </ItemGroup>
+ <ItemGroup>
+ <EntityDeploy Include="Models\Northwind.edmx">
+ <Generator>EntityModelCodeGenerator</Generator>
+ <LastGenOutput>Northwind.Designer.cs</LastGenOutput>
+ <CustomToolNamespace>System.Web.Http.Data.Test.Models.EF</CustomToolNamespace>
+ </EntityDeploy>
+ </ItemGroup>
+ <ItemGroup>
+ <None Include="packages.config" />
+ </ItemGroup>
+ <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
+</Project> \ No newline at end of file
diff --git a/test/Microsoft.Web.Http.Data.Test/Models/CatalogEntities.cs b/test/Microsoft.Web.Http.Data.Test/Models/CatalogEntities.cs
new file mode 100644
index 00000000..a2c497e7
--- /dev/null
+++ b/test/Microsoft.Web.Http.Data.Test/Models/CatalogEntities.cs
@@ -0,0 +1,169 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel.DataAnnotations;
+
+namespace Microsoft.Web.Http.Data.Test.Models
+{
+ public partial class Category
+ {
+ public Category()
+ {
+ this.Products = new HashSet<Product>();
+ }
+
+ [Key]
+ public int CategoryID { get; set; }
+ public string CategoryName { get; set; }
+ public string Description { get; set; }
+ public byte[] Picture { get; set; }
+
+ public ICollection<Product> Products { get; set; }
+ }
+
+ public partial class Customer
+ {
+ public Customer()
+ {
+ this.Orders = new HashSet<Order>();
+ }
+
+ [Key]
+ public string CustomerID { get; set; }
+ public string CompanyName { get; set; }
+ public string ContactName { get; set; }
+ public string ContactTitle { get; set; }
+ public string Address { get; set; }
+ public string City { get; set; }
+ public string Region { get; set; }
+ public string PostalCode { get; set; }
+ public string Country { get; set; }
+ public string Phone { get; set; }
+ public string Fax { get; set; }
+
+ [Association("Customer_Orders", "CustomerID", "CustomerID")]
+ public ICollection<Order> Orders { get; set; }
+ }
+
+ public partial class Order
+ {
+ private List<Order_Detail> _details;
+
+ [Key]
+ public int OrderID { get; set; }
+ public string CustomerID { get; set; }
+ public Nullable<int> EmployeeID { get; set; }
+ public Nullable<System.DateTime> OrderDate { get; set; }
+ public Nullable<System.DateTime> RequiredDate { get; set; }
+ public Nullable<System.DateTime> ShippedDate { get; set; }
+ public Nullable<int> ShipVia { get; set; }
+ public Nullable<decimal> Freight { get; set; }
+ [StringLength(50, MinimumLength = 0)]
+ public string ShipName { get; set; }
+ public string ShipAddress { get; set; }
+ public string ShipCity { get; set; }
+ public string ShipRegion { get; set; }
+ public string ShipPostalCode { get; set; }
+ public string ShipCountry { get; set; }
+
+ [Association("Customer_Orders", "CustomerID", "CustomerID", IsForeignKey = true)]
+ public Customer Customer { get; set; }
+
+ [Association("Order_Details", "OrderID", "OrderID")]
+ public List<Order_Detail> Order_Details
+ {
+ get
+ {
+ if (this._details == null)
+ {
+ this._details = new List<Order_Detail>();
+ }
+ return this._details;
+ }
+ set
+ {
+ this._details = value;
+ }
+ }
+
+ public Shipper Shipper { get; set; }
+ }
+
+ public partial class Order_Detail
+ {
+ [Key]
+ [Column(Order = 1)]
+ public int OrderID { get; set; }
+ [Key]
+ [Column(Order = 2)]
+ public int ProductID { get; set; }
+ public decimal UnitPrice { get; set; }
+ public short Quantity { get; set; }
+ public float Discount { get; set; }
+
+ public Order Order { get; set; }
+ public Product Product { get; set; }
+ }
+
+ public partial class Shipper
+ {
+ public Shipper()
+ {
+ this.Orders = new HashSet<Order>();
+ }
+
+ [Key]
+ public int ShipperID { get; set; }
+ public string CompanyName { get; set; }
+ public string Phone { get; set; }
+
+ public ICollection<Order> Orders { get; set; }
+ }
+
+ public partial class Product
+ {
+ public Product()
+ {
+ this.Order_Details = new HashSet<Order_Detail>();
+ }
+
+ [Key]
+ public int ProductID { get; set; }
+ public string ProductName { get; set; }
+ public Nullable<int> SupplierID { get; set; }
+ public Nullable<int> CategoryID { get; set; }
+ public string QuantityPerUnit { get; set; }
+ public Nullable<decimal> UnitPrice { get; set; }
+ public Nullable<short> UnitsInStock { get; set; }
+ public Nullable<short> UnitsOnOrder { get; set; }
+ public Nullable<short> ReorderLevel { get; set; }
+ public bool Discontinued { get; set; }
+
+ public Category Category { get; set; }
+ public ICollection<Order_Detail> Order_Details { get; set; }
+ public Supplier Supplier { get; set; }
+ }
+
+ public partial class Supplier
+ {
+ public Supplier()
+ {
+ this.Products = new HashSet<Product>();
+ }
+
+ [Key]
+ public int SupplierID { get; set; }
+ public string CompanyName { get; set; }
+ public string ContactName { get; set; }
+ public string ContactTitle { get; set; }
+ public string Address { get; set; }
+ public string City { get; set; }
+ public string Region { get; set; }
+ public string PostalCode { get; set; }
+ public string Country { get; set; }
+ public string Phone { get; set; }
+ public string Fax { get; set; }
+ public string HomePage { get; set; }
+
+ public virtual ICollection<Product> Products { get; set; }
+ }
+} \ No newline at end of file
diff --git a/test/Microsoft.Web.Http.Data.Test/Models/Cities.cs b/test/Microsoft.Web.Http.Data.Test/Models/Cities.cs
new file mode 100644
index 00000000..2b8360a8
--- /dev/null
+++ b/test/Microsoft.Web.Http.Data.Test/Models/Cities.cs
@@ -0,0 +1,302 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel.DataAnnotations;
+using System.Linq;
+using System.Runtime.Serialization;
+
+namespace Microsoft.Web.Http.Data.Test.Models
+{
+ /// <summary>
+ /// Sample data class
+ /// </summary>
+ /// <remarks>
+ /// This class exposes several data types (City, County, State and Zip) and some sample
+ /// data for each.
+ /// </remarks>
+ public partial class CityData
+ {
+ private List<State> _states;
+ private List<County> _counties;
+ private List<City> _cities;
+ private List<Zip> _zips;
+ private List<ZipWithInfo> _zipsWithInfo;
+ private List<CityWithInfo> _citiesWithInfo;
+
+ public CityData()
+ {
+ _states = new List<State>()
+ {
+ new State() { Name="WA", FullName="Washington", TimeZone = TimeZone.Pacific },
+ new State() { Name="OR", FullName="Oregon", TimeZone = TimeZone.Pacific },
+ new State() { Name="CA", FullName="California", TimeZone = TimeZone.Pacific },
+ new State() { Name="OH", FullName="Ohio", TimeZone = TimeZone.Eastern, ShippingZone=ShippingZone.Eastern }
+ };
+
+ _counties = new List<County>()
+ {
+ new County() { Name="King", StateName="WA" },
+ new County() { Name="Pierce", StateName="WA" },
+ new County() { Name="Snohomish", StateName="WA" },
+
+ new County() { Name="Tillamook", StateName="OR" },
+ new County() { Name="Wallowa", StateName="OR" },
+ new County() { Name="Jackson", StateName="OR" },
+
+ new County() { Name="Orange", StateName="CA" },
+ new County() { Name="Santa Barbara",StateName="CA" },
+
+ new County() { Name="Lucas", StateName="OH" }
+ };
+ foreach (State state in _states)
+ {
+ foreach (County county in _counties.Where(p => p.StateName == state.Name))
+ {
+ state.Counties.Add(county);
+ county.State = state;
+ }
+ }
+
+ _cities = new List<City>()
+ {
+ new CityWithInfo() {Name="Redmond", CountyName="King", StateName="WA", Info="Has Microsoft campus", LastUpdated=DateTime.Now},
+ new CityWithInfo() {Name="Bellevue", CountyName="King", StateName="WA", Info="Means beautiful view", LastUpdated=DateTime.Now},
+ new City() {Name="Duvall", CountyName="King", StateName="WA"},
+ new City() {Name="Carnation", CountyName="King", StateName="WA"},
+ new City() {Name="Everett", CountyName="King", StateName="WA"},
+ new City() {Name="Tacoma", CountyName="Pierce", StateName="WA"},
+
+ new City() {Name="Ashland", CountyName="Jackson", StateName="OR"},
+
+ new City() {Name="Santa Barbara", CountyName="Santa Barbara", StateName="CA"},
+ new City() {Name="Orange", CountyName="Orange", StateName="CA"},
+
+ new City() {Name="Oregon", CountyName="Lucas", StateName="OH"},
+ new City() {Name="Toledo", CountyName="Lucas", StateName="OH"}
+ };
+
+ _citiesWithInfo = new List<CityWithInfo>(this._cities.OfType<CityWithInfo>());
+
+ foreach (County county in _counties)
+ {
+ foreach (City city in _cities.Where(p => p.CountyName == county.Name && p.StateName == county.StateName))
+ {
+ county.Cities.Add(city);
+ city.County = county;
+ }
+ }
+
+ _zips = new List<Zip>()
+ {
+ new Zip() { Code=98053, FourDigit=8625, CityName="Redmond", CountyName="King", StateName="WA" },
+ new ZipWithInfo() { Code=98052, FourDigit=8300, CityName="Redmond", CountyName="King", StateName="WA", Info="Microsoft" },
+ new Zip() { Code=98052, FourDigit=6399, CityName="Redmond", CountyName="King", StateName="WA" },
+ };
+
+ _zipsWithInfo = new List<ZipWithInfo>(this._zips.OfType<ZipWithInfo>());
+
+ foreach (City city in _cities)
+ {
+ foreach (Zip zip in _zips.Where(p => p.CityName == city.Name && p.CountyName == city.CountyName && p.StateName == city.StateName))
+ {
+ city.ZipCodes.Add(zip);
+ zip.City = city;
+ }
+ }
+
+ foreach (CityWithInfo city in _citiesWithInfo)
+ {
+ foreach (ZipWithInfo zip in _zipsWithInfo.Where(p => p.CityName == city.Name && p.CountyName == city.CountyName && p.StateName == city.StateName))
+ {
+ city.ZipCodesWithInfo.Add(zip);
+ zip.City = city;
+ }
+ }
+ }
+
+ public List<State> States { get { return this._states; } }
+ public List<County> Counties { get { return this._counties; } }
+ public List<City> Cities { get { return this._cities; } }
+ public List<CityWithInfo> CitiesWithInfo { get { return this._citiesWithInfo; } }
+ public List<Zip> Zips { get { return this._zips; } }
+ public List<ZipWithInfo> ZipsWithInfo { get { return this._zipsWithInfo; } }
+ }
+
+ /// <summary>
+ /// These types are simple data types that can be used to build
+ /// mocks and simple data stores.
+ /// </summary>
+ public partial class State
+ {
+ private readonly List<County> _counties = new List<County>();
+
+ [Key]
+ public string Name { get; set; }
+ [Key]
+ public string FullName { get; set; }
+ public TimeZone TimeZone { get; set; }
+ public ShippingZone ShippingZone { get; set; }
+ public List<County> Counties { get { return this._counties; } }
+ }
+
+ [DataContract(Name = "CityName", Namespace = "CityNamespace")]
+ public enum ShippingZone
+ {
+ [EnumMember(Value = "P")]
+ Pacific = 0, // default
+
+ [EnumMember(Value = "C")]
+ Central,
+
+ [EnumMember(Value = "E")]
+ Eastern
+ }
+
+ public enum TimeZone
+ {
+ Central,
+ Mountain,
+ Eastern,
+ Pacific
+ }
+
+ public partial class County
+ {
+ public County()
+ {
+ Cities = new List<City>();
+ }
+
+ [Key]
+ public string Name { get; set; }
+ [Key]
+ public string StateName { get; set; }
+
+ [IgnoreDataMember]
+ public State State { get; set; }
+
+ public List<City> Cities { get; set; }
+ }
+
+ [KnownType(typeof(CityWithEditHistory))]
+ [KnownType(typeof(CityWithInfo))]
+ public partial class City
+ {
+ public City()
+ {
+ ZipCodes = new List<Zip>();
+ }
+
+ [Key]
+ public string Name { get; set; }
+ [Key]
+ public string CountyName { get; set; }
+ [Key]
+ public string StateName { get; set; }
+
+ [IgnoreDataMember]
+ public County County { get; set; }
+ public string ZoneName { get; set; }
+ public string CalculatedCounty { get { return this.CountyName; } set { } }
+ public int ZoneID { get; set; }
+
+ public List<Zip> ZipCodes { get; set; }
+
+ public override string ToString()
+ {
+ return this.GetType().Name + " Name=" + this.Name + ", State=" + this.StateName + ", County=" + this.CountyName;
+ }
+
+ public int this[int index]
+ {
+ get
+ {
+ return index;
+ }
+ set
+ {
+ }
+ }
+ }
+
+ public abstract partial class CityWithEditHistory : City
+ {
+ private string _editHistory;
+
+ public CityWithEditHistory()
+ {
+ this.EditHistory = "new";
+ }
+
+ // Edit history always appends, never overwrites
+ public string EditHistory
+ {
+ get
+ {
+ return this._editHistory;
+ }
+ set
+ {
+ this._editHistory = this._editHistory == null ? value : (this._editHistory + "," + value);
+ this.LastUpdated = DateTime.Now;
+ }
+ }
+
+ public DateTime LastUpdated
+ {
+ get;
+ set;
+ }
+
+ public override string ToString()
+ {
+ return base.ToString() + ", History=" + this.EditHistory + ", Updated=" + this.LastUpdated;
+ }
+
+ }
+
+ public partial class CityWithInfo : CityWithEditHistory
+ {
+ public CityWithInfo()
+ {
+ ZipCodesWithInfo = new List<ZipWithInfo>();
+ }
+
+ public string Info
+ {
+ get;
+ set;
+ }
+
+ public List<ZipWithInfo> ZipCodesWithInfo { get; set; }
+
+ public override string ToString()
+ {
+ return base.ToString() + ", Info=" + this.Info;
+ }
+
+ }
+
+ [KnownType(typeof(ZipWithInfo))]
+ public partial class Zip
+ {
+ [Key]
+ public int Code { get; set; }
+ [Key]
+ public int FourDigit { get; set; }
+ public string CityName { get; set; }
+ public string CountyName { get; set; }
+ public string StateName { get; set; }
+
+ [IgnoreDataMember]
+ public City City { get; set; }
+ }
+
+ public partial class ZipWithInfo : Zip
+ {
+ public string Info
+ {
+ get;
+ set;
+ }
+ }
+}
diff --git a/test/Microsoft.Web.Http.Data.Test/Models/Northwind.Designer.cs b/test/Microsoft.Web.Http.Data.Test/Models/Northwind.Designer.cs
new file mode 100644
index 00000000..6d749e42
--- /dev/null
+++ b/test/Microsoft.Web.Http.Data.Test/Models/Northwind.Designer.cs
@@ -0,0 +1,3411 @@
+//------------------------------------------------------------------------------
+// <auto-generated>
+// This code was generated from a template.
+//
+// Manual changes to this file may cause unexpected behavior in your application.
+// Manual changes to this file will be overwritten if the code is regenerated.
+// </auto-generated>
+//------------------------------------------------------------------------------
+
+using System;
+using System.Data.Objects;
+using System.Data.Objects.DataClasses;
+using System.Data.EntityClient;
+using System.ComponentModel;
+using System.Xml.Serialization;
+using System.Runtime.Serialization;
+
+[assembly: EdmSchemaAttribute()]
+#region EDM Relationship Metadata
+
+[assembly: EdmRelationshipAttribute("northwindModel", "FK_Products_Categories", "Categories", System.Data.Metadata.Edm.RelationshipMultiplicity.ZeroOrOne, typeof(Microsoft.Web.Http.Data.Test.Models.EF.Category), "Products", System.Data.Metadata.Edm.RelationshipMultiplicity.Many, typeof(Microsoft.Web.Http.Data.Test.Models.EF.Product), true)]
+[assembly: EdmRelationshipAttribute("northwindModel", "FK_Orders_Customers", "Customers", System.Data.Metadata.Edm.RelationshipMultiplicity.ZeroOrOne, typeof(Microsoft.Web.Http.Data.Test.Models.EF.Customer), "Orders", System.Data.Metadata.Edm.RelationshipMultiplicity.Many, typeof(Microsoft.Web.Http.Data.Test.Models.EF.Order), true)]
+[assembly: EdmRelationshipAttribute("northwindModel", "FK_Employees_Employees", "Employees", System.Data.Metadata.Edm.RelationshipMultiplicity.ZeroOrOne, typeof(Microsoft.Web.Http.Data.Test.Models.EF.Employee), "Employees1", System.Data.Metadata.Edm.RelationshipMultiplicity.Many, typeof(Microsoft.Web.Http.Data.Test.Models.EF.Employee), true)]
+[assembly: EdmRelationshipAttribute("northwindModel", "FK_Orders_Employees", "Employees", System.Data.Metadata.Edm.RelationshipMultiplicity.ZeroOrOne, typeof(Microsoft.Web.Http.Data.Test.Models.EF.Employee), "Orders", System.Data.Metadata.Edm.RelationshipMultiplicity.Many, typeof(Microsoft.Web.Http.Data.Test.Models.EF.Order), true)]
+[assembly: EdmRelationshipAttribute("northwindModel", "FK_Order_Details_Orders", "Orders", System.Data.Metadata.Edm.RelationshipMultiplicity.One, typeof(Microsoft.Web.Http.Data.Test.Models.EF.Order), "Order_Details", System.Data.Metadata.Edm.RelationshipMultiplicity.Many, typeof(Microsoft.Web.Http.Data.Test.Models.EF.Order_Detail), true)]
+[assembly: EdmRelationshipAttribute("northwindModel", "FK_Order_Details_Products", "Products", System.Data.Metadata.Edm.RelationshipMultiplicity.One, typeof(Microsoft.Web.Http.Data.Test.Models.EF.Product), "Order_Details", System.Data.Metadata.Edm.RelationshipMultiplicity.Many, typeof(Microsoft.Web.Http.Data.Test.Models.EF.Order_Detail), true)]
+[assembly: EdmRelationshipAttribute("northwindModel", "FK_Orders_Shippers", "Shippers", System.Data.Metadata.Edm.RelationshipMultiplicity.ZeroOrOne, typeof(Microsoft.Web.Http.Data.Test.Models.EF.Shipper), "Orders", System.Data.Metadata.Edm.RelationshipMultiplicity.Many, typeof(Microsoft.Web.Http.Data.Test.Models.EF.Order), true)]
+[assembly: EdmRelationshipAttribute("northwindModel", "FK_Products_Suppliers", "Suppliers", System.Data.Metadata.Edm.RelationshipMultiplicity.ZeroOrOne, typeof(Microsoft.Web.Http.Data.Test.Models.EF.Supplier), "Products", System.Data.Metadata.Edm.RelationshipMultiplicity.Many, typeof(Microsoft.Web.Http.Data.Test.Models.EF.Product), true)]
+[assembly: EdmRelationshipAttribute("northwindModel", "FK_Territories_Region", "Region", System.Data.Metadata.Edm.RelationshipMultiplicity.One, typeof(Microsoft.Web.Http.Data.Test.Models.EF.Region), "Territories", System.Data.Metadata.Edm.RelationshipMultiplicity.Many, typeof(Microsoft.Web.Http.Data.Test.Models.EF.Territory), true)]
+[assembly: EdmRelationshipAttribute("northwindModel", "CustomerCustomerDemo", "CustomerDemographics", System.Data.Metadata.Edm.RelationshipMultiplicity.Many, typeof(Microsoft.Web.Http.Data.Test.Models.EF.CustomerDemographic), "Customers", System.Data.Metadata.Edm.RelationshipMultiplicity.Many, typeof(Microsoft.Web.Http.Data.Test.Models.EF.Customer))]
+[assembly: EdmRelationshipAttribute("northwindModel", "EmployeeTerritories", "Employees", System.Data.Metadata.Edm.RelationshipMultiplicity.Many, typeof(Microsoft.Web.Http.Data.Test.Models.EF.Employee), "Territories", System.Data.Metadata.Edm.RelationshipMultiplicity.Many, typeof(Microsoft.Web.Http.Data.Test.Models.EF.Territory))]
+
+#endregion
+
+namespace Microsoft.Web.Http.Data.Test.Models.EF
+{
+ #region Contexts
+
+ /// <summary>
+ /// No Metadata Documentation available.
+ /// </summary>
+ public partial class NorthwindEntities : ObjectContext
+ {
+ #region Constructors
+
+ /// <summary>
+ /// Initializes a new NorthwindEntities object using the connection string found in the 'NorthwindEntities' section of the application configuration file.
+ /// </summary>
+ public NorthwindEntities() : base("name=NorthwindEntities", "NorthwindEntities")
+ {
+ this.ContextOptions.LazyLoadingEnabled = true;
+ OnContextCreated();
+ }
+
+ /// <summary>
+ /// Initialize a new NorthwindEntities object.
+ /// </summary>
+ public NorthwindEntities(string connectionString) : base(connectionString, "NorthwindEntities")
+ {
+ this.ContextOptions.LazyLoadingEnabled = true;
+ OnContextCreated();
+ }
+
+ /// <summary>
+ /// Initialize a new NorthwindEntities object.
+ /// </summary>
+ public NorthwindEntities(EntityConnection connection) : base(connection, "NorthwindEntities")
+ {
+ this.ContextOptions.LazyLoadingEnabled = true;
+ OnContextCreated();
+ }
+
+ #endregion
+
+ #region Partial Methods
+
+ partial void OnContextCreated();
+
+ #endregion
+
+ #region ObjectSet Properties
+
+ /// <summary>
+ /// No Metadata Documentation available.
+ /// </summary>
+ public ObjectSet<Category> Categories
+ {
+ get
+ {
+ if ((_Categories == null))
+ {
+ _Categories = base.CreateObjectSet<Category>("Categories");
+ }
+ return _Categories;
+ }
+ }
+ private ObjectSet<Category> _Categories;
+
+ /// <summary>
+ /// No Metadata Documentation available.
+ /// </summary>
+ public ObjectSet<CustomerDemographic> CustomerDemographics
+ {
+ get
+ {
+ if ((_CustomerDemographics == null))
+ {
+ _CustomerDemographics = base.CreateObjectSet<CustomerDemographic>("CustomerDemographics");
+ }
+ return _CustomerDemographics;
+ }
+ }
+ private ObjectSet<CustomerDemographic> _CustomerDemographics;
+
+ /// <summary>
+ /// No Metadata Documentation available.
+ /// </summary>
+ public ObjectSet<Customer> Customers
+ {
+ get
+ {
+ if ((_Customers == null))
+ {
+ _Customers = base.CreateObjectSet<Customer>("Customers");
+ }
+ return _Customers;
+ }
+ }
+ private ObjectSet<Customer> _Customers;
+
+ /// <summary>
+ /// No Metadata Documentation available.
+ /// </summary>
+ public ObjectSet<Employee> Employees
+ {
+ get
+ {
+ if ((_Employees == null))
+ {
+ _Employees = base.CreateObjectSet<Employee>("Employees");
+ }
+ return _Employees;
+ }
+ }
+ private ObjectSet<Employee> _Employees;
+
+ /// <summary>
+ /// No Metadata Documentation available.
+ /// </summary>
+ public ObjectSet<Order_Detail> Order_Details
+ {
+ get
+ {
+ if ((_Order_Details == null))
+ {
+ _Order_Details = base.CreateObjectSet<Order_Detail>("Order_Details");
+ }
+ return _Order_Details;
+ }
+ }
+ private ObjectSet<Order_Detail> _Order_Details;
+
+ /// <summary>
+ /// No Metadata Documentation available.
+ /// </summary>
+ public ObjectSet<Order> Orders
+ {
+ get
+ {
+ if ((_Orders == null))
+ {
+ _Orders = base.CreateObjectSet<Order>("Orders");
+ }
+ return _Orders;
+ }
+ }
+ private ObjectSet<Order> _Orders;
+
+ /// <summary>
+ /// No Metadata Documentation available.
+ /// </summary>
+ public ObjectSet<Product> Products
+ {
+ get
+ {
+ if ((_Products == null))
+ {
+ _Products = base.CreateObjectSet<Product>("Products");
+ }
+ return _Products;
+ }
+ }
+ private ObjectSet<Product> _Products;
+
+ /// <summary>
+ /// No Metadata Documentation available.
+ /// </summary>
+ public ObjectSet<Region> Regions
+ {
+ get
+ {
+ if ((_Regions == null))
+ {
+ _Regions = base.CreateObjectSet<Region>("Regions");
+ }
+ return _Regions;
+ }
+ }
+ private ObjectSet<Region> _Regions;
+
+ /// <summary>
+ /// No Metadata Documentation available.
+ /// </summary>
+ public ObjectSet<Shipper> Shippers
+ {
+ get
+ {
+ if ((_Shippers == null))
+ {
+ _Shippers = base.CreateObjectSet<Shipper>("Shippers");
+ }
+ return _Shippers;
+ }
+ }
+ private ObjectSet<Shipper> _Shippers;
+
+ /// <summary>
+ /// No Metadata Documentation available.
+ /// </summary>
+ public ObjectSet<Supplier> Suppliers
+ {
+ get
+ {
+ if ((_Suppliers == null))
+ {
+ _Suppliers = base.CreateObjectSet<Supplier>("Suppliers");
+ }
+ return _Suppliers;
+ }
+ }
+ private ObjectSet<Supplier> _Suppliers;
+
+ /// <summary>
+ /// No Metadata Documentation available.
+ /// </summary>
+ public ObjectSet<Territory> Territories
+ {
+ get
+ {
+ if ((_Territories == null))
+ {
+ _Territories = base.CreateObjectSet<Territory>("Territories");
+ }
+ return _Territories;
+ }
+ }
+ private ObjectSet<Territory> _Territories;
+
+ #endregion
+ #region AddTo Methods
+
+ /// <summary>
+ /// Deprecated Method for adding a new object to the Categories EntitySet. Consider using the .Add method of the associated ObjectSet&lt;T&gt; property instead.
+ /// </summary>
+ public void AddToCategories(Category category)
+ {
+ base.AddObject("Categories", category);
+ }
+
+ /// <summary>
+ /// Deprecated Method for adding a new object to the CustomerDemographics EntitySet. Consider using the .Add method of the associated ObjectSet&lt;T&gt; property instead.
+ /// </summary>
+ public void AddToCustomerDemographics(CustomerDemographic customerDemographic)
+ {
+ base.AddObject("CustomerDemographics", customerDemographic);
+ }
+
+ /// <summary>
+ /// Deprecated Method for adding a new object to the Customers EntitySet. Consider using the .Add method of the associated ObjectSet&lt;T&gt; property instead.
+ /// </summary>
+ public void AddToCustomers(Customer customer)
+ {
+ base.AddObject("Customers", customer);
+ }
+
+ /// <summary>
+ /// Deprecated Method for adding a new object to the Employees EntitySet. Consider using the .Add method of the associated ObjectSet&lt;T&gt; property instead.
+ /// </summary>
+ public void AddToEmployees(Employee employee)
+ {
+ base.AddObject("Employees", employee);
+ }
+
+ /// <summary>
+ /// Deprecated Method for adding a new object to the Order_Details EntitySet. Consider using the .Add method of the associated ObjectSet&lt;T&gt; property instead.
+ /// </summary>
+ public void AddToOrder_Details(Order_Detail order_Detail)
+ {
+ base.AddObject("Order_Details", order_Detail);
+ }
+
+ /// <summary>
+ /// Deprecated Method for adding a new object to the Orders EntitySet. Consider using the .Add method of the associated ObjectSet&lt;T&gt; property instead.
+ /// </summary>
+ public void AddToOrders(Order order)
+ {
+ base.AddObject("Orders", order);
+ }
+
+ /// <summary>
+ /// Deprecated Method for adding a new object to the Products EntitySet. Consider using the .Add method of the associated ObjectSet&lt;T&gt; property instead.
+ /// </summary>
+ public void AddToProducts(Product product)
+ {
+ base.AddObject("Products", product);
+ }
+
+ /// <summary>
+ /// Deprecated Method for adding a new object to the Regions EntitySet. Consider using the .Add method of the associated ObjectSet&lt;T&gt; property instead.
+ /// </summary>
+ public void AddToRegions(Region region)
+ {
+ base.AddObject("Regions", region);
+ }
+
+ /// <summary>
+ /// Deprecated Method for adding a new object to the Shippers EntitySet. Consider using the .Add method of the associated ObjectSet&lt;T&gt; property instead.
+ /// </summary>
+ public void AddToShippers(Shipper shipper)
+ {
+ base.AddObject("Shippers", shipper);
+ }
+
+ /// <summary>
+ /// Deprecated Method for adding a new object to the Suppliers EntitySet. Consider using the .Add method of the associated ObjectSet&lt;T&gt; property instead.
+ /// </summary>
+ public void AddToSuppliers(Supplier supplier)
+ {
+ base.AddObject("Suppliers", supplier);
+ }
+
+ /// <summary>
+ /// Deprecated Method for adding a new object to the Territories EntitySet. Consider using the .Add method of the associated ObjectSet&lt;T&gt; property instead.
+ /// </summary>
+ public void AddToTerritories(Territory territory)
+ {
+ base.AddObject("Territories", territory);
+ }
+
+ #endregion
+ }
+
+
+ #endregion
+
+ #region Entities
+
+ /// <summary>
+ /// No Metadata Documentation available.
+ /// </summary>
+ [EdmEntityTypeAttribute(NamespaceName="northwindModel", Name="Category")]
+ [Serializable()]
+ [DataContractAttribute(IsReference=true)]
+ public partial class Category : EntityObject
+ {
+ #region Factory Method
+
+ /// <summary>
+ /// Create a new Category object.
+ /// </summary>
+ /// <param name="categoryID">Initial value of the CategoryID property.</param>
+ /// <param name="categoryName">Initial value of the CategoryName property.</param>
+ public static Category CreateCategory(global::System.Int32 categoryID, global::System.String categoryName)
+ {
+ Category category = new Category();
+ category.CategoryID = categoryID;
+ category.CategoryName = categoryName;
+ return category;
+ }
+
+ #endregion
+ #region Primitive Properties
+
+ /// <summary>
+ /// No Metadata Documentation available.
+ /// </summary>
+ [EdmScalarPropertyAttribute(EntityKeyProperty=true, IsNullable=false)]
+ [DataMemberAttribute()]
+ public global::System.Int32 CategoryID
+ {
+ get
+ {
+ return _CategoryID;
+ }
+ set
+ {
+ if (_CategoryID != value)
+ {
+ OnCategoryIDChanging(value);
+ ReportPropertyChanging("CategoryID");
+ _CategoryID = StructuralObject.SetValidValue(value);
+ ReportPropertyChanged("CategoryID");
+ OnCategoryIDChanged();
+ }
+ }
+ }
+ private global::System.Int32 _CategoryID;
+ partial void OnCategoryIDChanging(global::System.Int32 value);
+ partial void OnCategoryIDChanged();
+
+ /// <summary>
+ /// No Metadata Documentation available.
+ /// </summary>
+ [EdmScalarPropertyAttribute(EntityKeyProperty=false, IsNullable=false)]
+ [DataMemberAttribute()]
+ public global::System.String CategoryName
+ {
+ get
+ {
+ return _CategoryName;
+ }
+ set
+ {
+ OnCategoryNameChanging(value);
+ ReportPropertyChanging("CategoryName");
+ _CategoryName = StructuralObject.SetValidValue(value, false);
+ ReportPropertyChanged("CategoryName");
+ OnCategoryNameChanged();
+ }
+ }
+ private global::System.String _CategoryName;
+ partial void OnCategoryNameChanging(global::System.String value);
+ partial void OnCategoryNameChanged();
+
+ /// <summary>
+ /// No Metadata Documentation available.
+ /// </summary>
+ [EdmScalarPropertyAttribute(EntityKeyProperty=false, IsNullable=true)]
+ [DataMemberAttribute()]
+ public global::System.String Description
+ {
+ get
+ {
+ return _Description;
+ }
+ set
+ {
+ OnDescriptionChanging(value);
+ ReportPropertyChanging("Description");
+ _Description = StructuralObject.SetValidValue(value, true);
+ ReportPropertyChanged("Description");
+ OnDescriptionChanged();
+ }
+ }
+ private global::System.String _Description;
+ partial void OnDescriptionChanging(global::System.String value);
+ partial void OnDescriptionChanged();
+
+ /// <summary>
+ /// No Metadata Documentation available.
+ /// </summary>
+ [EdmScalarPropertyAttribute(EntityKeyProperty=false, IsNullable=true)]
+ [DataMemberAttribute()]
+ public global::System.Byte[] Picture
+ {
+ get
+ {
+ return StructuralObject.GetValidValue(_Picture);
+ }
+ set
+ {
+ OnPictureChanging(value);
+ ReportPropertyChanging("Picture");
+ _Picture = StructuralObject.SetValidValue(value, true);
+ ReportPropertyChanged("Picture");
+ OnPictureChanged();
+ }
+ }
+ private global::System.Byte[] _Picture;
+ partial void OnPictureChanging(global::System.Byte[] value);
+ partial void OnPictureChanged();
+
+ #endregion
+
+ #region Navigation Properties
+
+ /// <summary>
+ /// No Metadata Documentation available.
+ /// </summary>
+ [XmlIgnoreAttribute()]
+ [SoapIgnoreAttribute()]
+ [DataMemberAttribute()]
+ [EdmRelationshipNavigationPropertyAttribute("northwindModel", "FK_Products_Categories", "Products")]
+ public EntityCollection<Product> Products
+ {
+ get
+ {
+ return ((IEntityWithRelationships)this).RelationshipManager.GetRelatedCollection<Product>("northwindModel.FK_Products_Categories", "Products");
+ }
+ set
+ {
+ if ((value != null))
+ {
+ ((IEntityWithRelationships)this).RelationshipManager.InitializeRelatedCollection<Product>("northwindModel.FK_Products_Categories", "Products", value);
+ }
+ }
+ }
+
+ #endregion
+ }
+
+ /// <summary>
+ /// No Metadata Documentation available.
+ /// </summary>
+ [EdmEntityTypeAttribute(NamespaceName="northwindModel", Name="Customer")]
+ [Serializable()]
+ [DataContractAttribute(IsReference=true)]
+ public partial class Customer : EntityObject
+ {
+ #region Factory Method
+
+ /// <summary>
+ /// Create a new Customer object.
+ /// </summary>
+ /// <param name="customerID">Initial value of the CustomerID property.</param>
+ /// <param name="companyName">Initial value of the CompanyName property.</param>
+ public static Customer CreateCustomer(global::System.String customerID, global::System.String companyName)
+ {
+ Customer customer = new Customer();
+ customer.CustomerID = customerID;
+ customer.CompanyName = companyName;
+ return customer;
+ }
+
+ #endregion
+ #region Primitive Properties
+
+ /// <summary>
+ /// No Metadata Documentation available.
+ /// </summary>
+ [EdmScalarPropertyAttribute(EntityKeyProperty=true, IsNullable=false)]
+ [DataMemberAttribute()]
+ public global::System.String CustomerID
+ {
+ get
+ {
+ return _CustomerID;
+ }
+ set
+ {
+ if (_CustomerID != value)
+ {
+ OnCustomerIDChanging(value);
+ ReportPropertyChanging("CustomerID");
+ _CustomerID = StructuralObject.SetValidValue(value, false);
+ ReportPropertyChanged("CustomerID");
+ OnCustomerIDChanged();
+ }
+ }
+ }
+ private global::System.String _CustomerID;
+ partial void OnCustomerIDChanging(global::System.String value);
+ partial void OnCustomerIDChanged();
+
+ /// <summary>
+ /// No Metadata Documentation available.
+ /// </summary>
+ [EdmScalarPropertyAttribute(EntityKeyProperty=false, IsNullable=false)]
+ [DataMemberAttribute()]
+ public global::System.String CompanyName
+ {
+ get
+ {
+ return _CompanyName;
+ }
+ set
+ {
+ OnCompanyNameChanging(value);
+ ReportPropertyChanging("CompanyName");
+ _CompanyName = StructuralObject.SetValidValue(value, false);
+ ReportPropertyChanged("CompanyName");
+ OnCompanyNameChanged();
+ }
+ }
+ private global::System.String _CompanyName;
+ partial void OnCompanyNameChanging(global::System.String value);
+ partial void OnCompanyNameChanged();
+
+ /// <summary>
+ /// No Metadata Documentation available.
+ /// </summary>
+ [EdmScalarPropertyAttribute(EntityKeyProperty=false, IsNullable=true)]
+ [DataMemberAttribute()]
+ public global::System.String ContactName
+ {
+ get
+ {
+ return _ContactName;
+ }
+ set
+ {
+ OnContactNameChanging(value);
+ ReportPropertyChanging("ContactName");
+ _ContactName = StructuralObject.SetValidValue(value, true);
+ ReportPropertyChanged("ContactName");
+ OnContactNameChanged();
+ }
+ }
+ private global::System.String _ContactName;
+ partial void OnContactNameChanging(global::System.String value);
+ partial void OnContactNameChanged();
+
+ /// <summary>
+ /// No Metadata Documentation available.
+ /// </summary>
+ [EdmScalarPropertyAttribute(EntityKeyProperty=false, IsNullable=true)]
+ [DataMemberAttribute()]
+ public global::System.String ContactTitle
+ {
+ get
+ {
+ return _ContactTitle;
+ }
+ set
+ {
+ OnContactTitleChanging(value);
+ ReportPropertyChanging("ContactTitle");
+ _ContactTitle = StructuralObject.SetValidValue(value, true);
+ ReportPropertyChanged("ContactTitle");
+ OnContactTitleChanged();
+ }
+ }
+ private global::System.String _ContactTitle;
+ partial void OnContactTitleChanging(global::System.String value);
+ partial void OnContactTitleChanged();
+
+ /// <summary>
+ /// No Metadata Documentation available.
+ /// </summary>
+ [EdmScalarPropertyAttribute(EntityKeyProperty=false, IsNullable=true)]
+ [DataMemberAttribute()]
+ public global::System.String Address
+ {
+ get
+ {
+ return _Address;
+ }
+ set
+ {
+ OnAddressChanging(value);
+ ReportPropertyChanging("Address");
+ _Address = StructuralObject.SetValidValue(value, true);
+ ReportPropertyChanged("Address");
+ OnAddressChanged();
+ }
+ }
+ private global::System.String _Address;
+ partial void OnAddressChanging(global::System.String value);
+ partial void OnAddressChanged();
+
+ /// <summary>
+ /// No Metadata Documentation available.
+ /// </summary>
+ [EdmScalarPropertyAttribute(EntityKeyProperty=false, IsNullable=true)]
+ [DataMemberAttribute()]
+ public global::System.String City
+ {
+ get
+ {
+ return _City;
+ }
+ set
+ {
+ OnCityChanging(value);
+ ReportPropertyChanging("City");
+ _City = StructuralObject.SetValidValue(value, true);
+ ReportPropertyChanged("City");
+ OnCityChanged();
+ }
+ }
+ private global::System.String _City;
+ partial void OnCityChanging(global::System.String value);
+ partial void OnCityChanged();
+
+ /// <summary>
+ /// No Metadata Documentation available.
+ /// </summary>
+ [EdmScalarPropertyAttribute(EntityKeyProperty=false, IsNullable=true)]
+ [DataMemberAttribute()]
+ public global::System.String Region
+ {
+ get
+ {
+ return _Region;
+ }
+ set
+ {
+ OnRegionChanging(value);
+ ReportPropertyChanging("Region");
+ _Region = StructuralObject.SetValidValue(value, true);
+ ReportPropertyChanged("Region");
+ OnRegionChanged();
+ }
+ }
+ private global::System.String _Region;
+ partial void OnRegionChanging(global::System.String value);
+ partial void OnRegionChanged();
+
+ /// <summary>
+ /// No Metadata Documentation available.
+ /// </summary>
+ [EdmScalarPropertyAttribute(EntityKeyProperty=false, IsNullable=true)]
+ [DataMemberAttribute()]
+ public global::System.String PostalCode
+ {
+ get
+ {
+ return _PostalCode;
+ }
+ set
+ {
+ OnPostalCodeChanging(value);
+ ReportPropertyChanging("PostalCode");
+ _PostalCode = StructuralObject.SetValidValue(value, true);
+ ReportPropertyChanged("PostalCode");
+ OnPostalCodeChanged();
+ }
+ }
+ private global::System.String _PostalCode;
+ partial void OnPostalCodeChanging(global::System.String value);
+ partial void OnPostalCodeChanged();
+
+ /// <summary>
+ /// No Metadata Documentation available.
+ /// </summary>
+ [EdmScalarPropertyAttribute(EntityKeyProperty=false, IsNullable=true)]
+ [DataMemberAttribute()]
+ public global::System.String Country
+ {
+ get
+ {
+ return _Country;
+ }
+ set
+ {
+ OnCountryChanging(value);
+ ReportPropertyChanging("Country");
+ _Country = StructuralObject.SetValidValue(value, true);
+ ReportPropertyChanged("Country");
+ OnCountryChanged();
+ }
+ }
+ private global::System.String _Country;
+ partial void OnCountryChanging(global::System.String value);
+ partial void OnCountryChanged();
+
+ /// <summary>
+ /// No Metadata Documentation available.
+ /// </summary>
+ [EdmScalarPropertyAttribute(EntityKeyProperty=false, IsNullable=true)]
+ [DataMemberAttribute()]
+ public global::System.String Phone
+ {
+ get
+ {
+ return _Phone;
+ }
+ set
+ {
+ OnPhoneChanging(value);
+ ReportPropertyChanging("Phone");
+ _Phone = StructuralObject.SetValidValue(value, true);
+ ReportPropertyChanged("Phone");
+ OnPhoneChanged();
+ }
+ }
+ private global::System.String _Phone;
+ partial void OnPhoneChanging(global::System.String value);
+ partial void OnPhoneChanged();
+
+ /// <summary>
+ /// No Metadata Documentation available.
+ /// </summary>
+ [EdmScalarPropertyAttribute(EntityKeyProperty=false, IsNullable=true)]
+ [DataMemberAttribute()]
+ public global::System.String Fax
+ {
+ get
+ {
+ return _Fax;
+ }
+ set
+ {
+ OnFaxChanging(value);
+ ReportPropertyChanging("Fax");
+ _Fax = StructuralObject.SetValidValue(value, true);
+ ReportPropertyChanged("Fax");
+ OnFaxChanged();
+ }
+ }
+ private global::System.String _Fax;
+ partial void OnFaxChanging(global::System.String value);
+ partial void OnFaxChanged();
+
+ #endregion
+
+ #region Navigation Properties
+
+ /// <summary>
+ /// No Metadata Documentation available.
+ /// </summary>
+ [XmlIgnoreAttribute()]
+ [SoapIgnoreAttribute()]
+ [DataMemberAttribute()]
+ [EdmRelationshipNavigationPropertyAttribute("northwindModel", "FK_Orders_Customers", "Orders")]
+ public EntityCollection<Order> Orders
+ {
+ get
+ {
+ return ((IEntityWithRelationships)this).RelationshipManager.GetRelatedCollection<Order>("northwindModel.FK_Orders_Customers", "Orders");
+ }
+ set
+ {
+ if ((value != null))
+ {
+ ((IEntityWithRelationships)this).RelationshipManager.InitializeRelatedCollection<Order>("northwindModel.FK_Orders_Customers", "Orders", value);
+ }
+ }
+ }
+
+ /// <summary>
+ /// No Metadata Documentation available.
+ /// </summary>
+ [XmlIgnoreAttribute()]
+ [SoapIgnoreAttribute()]
+ [DataMemberAttribute()]
+ [EdmRelationshipNavigationPropertyAttribute("northwindModel", "CustomerCustomerDemo", "CustomerDemographics")]
+ public EntityCollection<CustomerDemographic> CustomerDemographics
+ {
+ get
+ {
+ return ((IEntityWithRelationships)this).RelationshipManager.GetRelatedCollection<CustomerDemographic>("northwindModel.CustomerCustomerDemo", "CustomerDemographics");
+ }
+ set
+ {
+ if ((value != null))
+ {
+ ((IEntityWithRelationships)this).RelationshipManager.InitializeRelatedCollection<CustomerDemographic>("northwindModel.CustomerCustomerDemo", "CustomerDemographics", value);
+ }
+ }
+ }
+
+ #endregion
+ }
+
+ /// <summary>
+ /// No Metadata Documentation available.
+ /// </summary>
+ [EdmEntityTypeAttribute(NamespaceName="northwindModel", Name="CustomerDemographic")]
+ [Serializable()]
+ [DataContractAttribute(IsReference=true)]
+ public partial class CustomerDemographic : EntityObject
+ {
+ #region Factory Method
+
+ /// <summary>
+ /// Create a new CustomerDemographic object.
+ /// </summary>
+ /// <param name="customerTypeID">Initial value of the CustomerTypeID property.</param>
+ public static CustomerDemographic CreateCustomerDemographic(global::System.String customerTypeID)
+ {
+ CustomerDemographic customerDemographic = new CustomerDemographic();
+ customerDemographic.CustomerTypeID = customerTypeID;
+ return customerDemographic;
+ }
+
+ #endregion
+ #region Primitive Properties
+
+ /// <summary>
+ /// No Metadata Documentation available.
+ /// </summary>
+ [EdmScalarPropertyAttribute(EntityKeyProperty=true, IsNullable=false)]
+ [DataMemberAttribute()]
+ public global::System.String CustomerTypeID
+ {
+ get
+ {
+ return _CustomerTypeID;
+ }
+ set
+ {
+ if (_CustomerTypeID != value)
+ {
+ OnCustomerTypeIDChanging(value);
+ ReportPropertyChanging("CustomerTypeID");
+ _CustomerTypeID = StructuralObject.SetValidValue(value, false);
+ ReportPropertyChanged("CustomerTypeID");
+ OnCustomerTypeIDChanged();
+ }
+ }
+ }
+ private global::System.String _CustomerTypeID;
+ partial void OnCustomerTypeIDChanging(global::System.String value);
+ partial void OnCustomerTypeIDChanged();
+
+ /// <summary>
+ /// No Metadata Documentation available.
+ /// </summary>
+ [EdmScalarPropertyAttribute(EntityKeyProperty=false, IsNullable=true)]
+ [DataMemberAttribute()]
+ public global::System.String CustomerDesc
+ {
+ get
+ {
+ return _CustomerDesc;
+ }
+ set
+ {
+ OnCustomerDescChanging(value);
+ ReportPropertyChanging("CustomerDesc");
+ _CustomerDesc = StructuralObject.SetValidValue(value, true);
+ ReportPropertyChanged("CustomerDesc");
+ OnCustomerDescChanged();
+ }
+ }
+ private global::System.String _CustomerDesc;
+ partial void OnCustomerDescChanging(global::System.String value);
+ partial void OnCustomerDescChanged();
+
+ #endregion
+
+ #region Navigation Properties
+
+ /// <summary>
+ /// No Metadata Documentation available.
+ /// </summary>
+ [XmlIgnoreAttribute()]
+ [SoapIgnoreAttribute()]
+ [DataMemberAttribute()]
+ [EdmRelationshipNavigationPropertyAttribute("northwindModel", "CustomerCustomerDemo", "Customers")]
+ public EntityCollection<Customer> Customers
+ {
+ get
+ {
+ return ((IEntityWithRelationships)this).RelationshipManager.GetRelatedCollection<Customer>("northwindModel.CustomerCustomerDemo", "Customers");
+ }
+ set
+ {
+ if ((value != null))
+ {
+ ((IEntityWithRelationships)this).RelationshipManager.InitializeRelatedCollection<Customer>("northwindModel.CustomerCustomerDemo", "Customers", value);
+ }
+ }
+ }
+
+ #endregion
+ }
+
+ /// <summary>
+ /// No Metadata Documentation available.
+ /// </summary>
+ [EdmEntityTypeAttribute(NamespaceName="northwindModel", Name="Employee")]
+ [Serializable()]
+ [DataContractAttribute(IsReference=true)]
+ public partial class Employee : EntityObject
+ {
+ #region Factory Method
+
+ /// <summary>
+ /// Create a new Employee object.
+ /// </summary>
+ /// <param name="employeeID">Initial value of the EmployeeID property.</param>
+ /// <param name="lastName">Initial value of the LastName property.</param>
+ /// <param name="firstName">Initial value of the FirstName property.</param>
+ public static Employee CreateEmployee(global::System.Int32 employeeID, global::System.String lastName, global::System.String firstName)
+ {
+ Employee employee = new Employee();
+ employee.EmployeeID = employeeID;
+ employee.LastName = lastName;
+ employee.FirstName = firstName;
+ return employee;
+ }
+
+ #endregion
+ #region Primitive Properties
+
+ /// <summary>
+ /// No Metadata Documentation available.
+ /// </summary>
+ [EdmScalarPropertyAttribute(EntityKeyProperty=true, IsNullable=false)]
+ [DataMemberAttribute()]
+ public global::System.Int32 EmployeeID
+ {
+ get
+ {
+ return _EmployeeID;
+ }
+ set
+ {
+ if (_EmployeeID != value)
+ {
+ OnEmployeeIDChanging(value);
+ ReportPropertyChanging("EmployeeID");
+ _EmployeeID = StructuralObject.SetValidValue(value);
+ ReportPropertyChanged("EmployeeID");
+ OnEmployeeIDChanged();
+ }
+ }
+ }
+ private global::System.Int32 _EmployeeID;
+ partial void OnEmployeeIDChanging(global::System.Int32 value);
+ partial void OnEmployeeIDChanged();
+
+ /// <summary>
+ /// No Metadata Documentation available.
+ /// </summary>
+ [EdmScalarPropertyAttribute(EntityKeyProperty=false, IsNullable=false)]
+ [DataMemberAttribute()]
+ public global::System.String LastName
+ {
+ get
+ {
+ return _LastName;
+ }
+ set
+ {
+ OnLastNameChanging(value);
+ ReportPropertyChanging("LastName");
+ _LastName = StructuralObject.SetValidValue(value, false);
+ ReportPropertyChanged("LastName");
+ OnLastNameChanged();
+ }
+ }
+ private global::System.String _LastName;
+ partial void OnLastNameChanging(global::System.String value);
+ partial void OnLastNameChanged();
+
+ /// <summary>
+ /// No Metadata Documentation available.
+ /// </summary>
+ [EdmScalarPropertyAttribute(EntityKeyProperty=false, IsNullable=false)]
+ [DataMemberAttribute()]
+ public global::System.String FirstName
+ {
+ get
+ {
+ return _FirstName;
+ }
+ set
+ {
+ OnFirstNameChanging(value);
+ ReportPropertyChanging("FirstName");
+ _FirstName = StructuralObject.SetValidValue(value, false);
+ ReportPropertyChanged("FirstName");
+ OnFirstNameChanged();
+ }
+ }
+ private global::System.String _FirstName;
+ partial void OnFirstNameChanging(global::System.String value);
+ partial void OnFirstNameChanged();
+
+ /// <summary>
+ /// No Metadata Documentation available.
+ /// </summary>
+ [EdmScalarPropertyAttribute(EntityKeyProperty=false, IsNullable=true)]
+ [DataMemberAttribute()]
+ public global::System.String Title
+ {
+ get
+ {
+ return _Title;
+ }
+ set
+ {
+ OnTitleChanging(value);
+ ReportPropertyChanging("Title");
+ _Title = StructuralObject.SetValidValue(value, true);
+ ReportPropertyChanged("Title");
+ OnTitleChanged();
+ }
+ }
+ private global::System.String _Title;
+ partial void OnTitleChanging(global::System.String value);
+ partial void OnTitleChanged();
+
+ /// <summary>
+ /// No Metadata Documentation available.
+ /// </summary>
+ [EdmScalarPropertyAttribute(EntityKeyProperty=false, IsNullable=true)]
+ [DataMemberAttribute()]
+ public global::System.String TitleOfCourtesy
+ {
+ get
+ {
+ return _TitleOfCourtesy;
+ }
+ set
+ {
+ OnTitleOfCourtesyChanging(value);
+ ReportPropertyChanging("TitleOfCourtesy");
+ _TitleOfCourtesy = StructuralObject.SetValidValue(value, true);
+ ReportPropertyChanged("TitleOfCourtesy");
+ OnTitleOfCourtesyChanged();
+ }
+ }
+ private global::System.String _TitleOfCourtesy;
+ partial void OnTitleOfCourtesyChanging(global::System.String value);
+ partial void OnTitleOfCourtesyChanged();
+
+ /// <summary>
+ /// No Metadata Documentation available.
+ /// </summary>
+ [EdmScalarPropertyAttribute(EntityKeyProperty=false, IsNullable=true)]
+ [DataMemberAttribute()]
+ public Nullable<global::System.DateTime> BirthDate
+ {
+ get
+ {
+ return _BirthDate;
+ }
+ set
+ {
+ OnBirthDateChanging(value);
+ ReportPropertyChanging("BirthDate");
+ _BirthDate = StructuralObject.SetValidValue(value);
+ ReportPropertyChanged("BirthDate");
+ OnBirthDateChanged();
+ }
+ }
+ private Nullable<global::System.DateTime> _BirthDate;
+ partial void OnBirthDateChanging(Nullable<global::System.DateTime> value);
+ partial void OnBirthDateChanged();
+
+ /// <summary>
+ /// No Metadata Documentation available.
+ /// </summary>
+ [EdmScalarPropertyAttribute(EntityKeyProperty=false, IsNullable=true)]
+ [DataMemberAttribute()]
+ public Nullable<global::System.DateTime> HireDate
+ {
+ get
+ {
+ return _HireDate;
+ }
+ set
+ {
+ OnHireDateChanging(value);
+ ReportPropertyChanging("HireDate");
+ _HireDate = StructuralObject.SetValidValue(value);
+ ReportPropertyChanged("HireDate");
+ OnHireDateChanged();
+ }
+ }
+ private Nullable<global::System.DateTime> _HireDate;
+ partial void OnHireDateChanging(Nullable<global::System.DateTime> value);
+ partial void OnHireDateChanged();
+
+ /// <summary>
+ /// No Metadata Documentation available.
+ /// </summary>
+ [EdmScalarPropertyAttribute(EntityKeyProperty=false, IsNullable=true)]
+ [DataMemberAttribute()]
+ public global::System.String Address
+ {
+ get
+ {
+ return _Address;
+ }
+ set
+ {
+ OnAddressChanging(value);
+ ReportPropertyChanging("Address");
+ _Address = StructuralObject.SetValidValue(value, true);
+ ReportPropertyChanged("Address");
+ OnAddressChanged();
+ }
+ }
+ private global::System.String _Address;
+ partial void OnAddressChanging(global::System.String value);
+ partial void OnAddressChanged();
+
+ /// <summary>
+ /// No Metadata Documentation available.
+ /// </summary>
+ [EdmScalarPropertyAttribute(EntityKeyProperty=false, IsNullable=true)]
+ [DataMemberAttribute()]
+ public global::System.String City
+ {
+ get
+ {
+ return _City;
+ }
+ set
+ {
+ OnCityChanging(value);
+ ReportPropertyChanging("City");
+ _City = StructuralObject.SetValidValue(value, true);
+ ReportPropertyChanged("City");
+ OnCityChanged();
+ }
+ }
+ private global::System.String _City;
+ partial void OnCityChanging(global::System.String value);
+ partial void OnCityChanged();
+
+ /// <summary>
+ /// No Metadata Documentation available.
+ /// </summary>
+ [EdmScalarPropertyAttribute(EntityKeyProperty=false, IsNullable=true)]
+ [DataMemberAttribute()]
+ public global::System.String Region
+ {
+ get
+ {
+ return _Region;
+ }
+ set
+ {
+ OnRegionChanging(value);
+ ReportPropertyChanging("Region");
+ _Region = StructuralObject.SetValidValue(value, true);
+ ReportPropertyChanged("Region");
+ OnRegionChanged();
+ }
+ }
+ private global::System.String _Region;
+ partial void OnRegionChanging(global::System.String value);
+ partial void OnRegionChanged();
+
+ /// <summary>
+ /// No Metadata Documentation available.
+ /// </summary>
+ [EdmScalarPropertyAttribute(EntityKeyProperty=false, IsNullable=true)]
+ [DataMemberAttribute()]
+ public global::System.String PostalCode
+ {
+ get
+ {
+ return _PostalCode;
+ }
+ set
+ {
+ OnPostalCodeChanging(value);
+ ReportPropertyChanging("PostalCode");
+ _PostalCode = StructuralObject.SetValidValue(value, true);
+ ReportPropertyChanged("PostalCode");
+ OnPostalCodeChanged();
+ }
+ }
+ private global::System.String _PostalCode;
+ partial void OnPostalCodeChanging(global::System.String value);
+ partial void OnPostalCodeChanged();
+
+ /// <summary>
+ /// No Metadata Documentation available.
+ /// </summary>
+ [EdmScalarPropertyAttribute(EntityKeyProperty=false, IsNullable=true)]
+ [DataMemberAttribute()]
+ public global::System.String Country
+ {
+ get
+ {
+ return _Country;
+ }
+ set
+ {
+ OnCountryChanging(value);
+ ReportPropertyChanging("Country");
+ _Country = StructuralObject.SetValidValue(value, true);
+ ReportPropertyChanged("Country");
+ OnCountryChanged();
+ }
+ }
+ private global::System.String _Country;
+ partial void OnCountryChanging(global::System.String value);
+ partial void OnCountryChanged();
+
+ /// <summary>
+ /// No Metadata Documentation available.
+ /// </summary>
+ [EdmScalarPropertyAttribute(EntityKeyProperty=false, IsNullable=true)]
+ [DataMemberAttribute()]
+ public global::System.String HomePhone
+ {
+ get
+ {
+ return _HomePhone;
+ }
+ set
+ {
+ OnHomePhoneChanging(value);
+ ReportPropertyChanging("HomePhone");
+ _HomePhone = StructuralObject.SetValidValue(value, true);
+ ReportPropertyChanged("HomePhone");
+ OnHomePhoneChanged();
+ }
+ }
+ private global::System.String _HomePhone;
+ partial void OnHomePhoneChanging(global::System.String value);
+ partial void OnHomePhoneChanged();
+
+ /// <summary>
+ /// No Metadata Documentation available.
+ /// </summary>
+ [EdmScalarPropertyAttribute(EntityKeyProperty=false, IsNullable=true)]
+ [DataMemberAttribute()]
+ public global::System.String Extension
+ {
+ get
+ {
+ return _Extension;
+ }
+ set
+ {
+ OnExtensionChanging(value);
+ ReportPropertyChanging("Extension");
+ _Extension = StructuralObject.SetValidValue(value, true);
+ ReportPropertyChanged("Extension");
+ OnExtensionChanged();
+ }
+ }
+ private global::System.String _Extension;
+ partial void OnExtensionChanging(global::System.String value);
+ partial void OnExtensionChanged();
+
+ /// <summary>
+ /// No Metadata Documentation available.
+ /// </summary>
+ [EdmScalarPropertyAttribute(EntityKeyProperty=false, IsNullable=true)]
+ [DataMemberAttribute()]
+ public global::System.Byte[] Photo
+ {
+ get
+ {
+ return StructuralObject.GetValidValue(_Photo);
+ }
+ set
+ {
+ OnPhotoChanging(value);
+ ReportPropertyChanging("Photo");
+ _Photo = StructuralObject.SetValidValue(value, true);
+ ReportPropertyChanged("Photo");
+ OnPhotoChanged();
+ }
+ }
+ private global::System.Byte[] _Photo;
+ partial void OnPhotoChanging(global::System.Byte[] value);
+ partial void OnPhotoChanged();
+
+ /// <summary>
+ /// No Metadata Documentation available.
+ /// </summary>
+ [EdmScalarPropertyAttribute(EntityKeyProperty=false, IsNullable=true)]
+ [DataMemberAttribute()]
+ public global::System.String Notes
+ {
+ get
+ {
+ return _Notes;
+ }
+ set
+ {
+ OnNotesChanging(value);
+ ReportPropertyChanging("Notes");
+ _Notes = StructuralObject.SetValidValue(value, true);
+ ReportPropertyChanged("Notes");
+ OnNotesChanged();
+ }
+ }
+ private global::System.String _Notes;
+ partial void OnNotesChanging(global::System.String value);
+ partial void OnNotesChanged();
+
+ /// <summary>
+ /// No Metadata Documentation available.
+ /// </summary>
+ [EdmScalarPropertyAttribute(EntityKeyProperty=false, IsNullable=true)]
+ [DataMemberAttribute()]
+ public Nullable<global::System.Int32> ReportsTo
+ {
+ get
+ {
+ return _ReportsTo;
+ }
+ set
+ {
+ OnReportsToChanging(value);
+ ReportPropertyChanging("ReportsTo");
+ _ReportsTo = StructuralObject.SetValidValue(value);
+ ReportPropertyChanged("ReportsTo");
+ OnReportsToChanged();
+ }
+ }
+ private Nullable<global::System.Int32> _ReportsTo;
+ partial void OnReportsToChanging(Nullable<global::System.Int32> value);
+ partial void OnReportsToChanged();
+
+ /// <summary>
+ /// No Metadata Documentation available.
+ /// </summary>
+ [EdmScalarPropertyAttribute(EntityKeyProperty=false, IsNullable=true)]
+ [DataMemberAttribute()]
+ public global::System.String PhotoPath
+ {
+ get
+ {
+ return _PhotoPath;
+ }
+ set
+ {
+ OnPhotoPathChanging(value);
+ ReportPropertyChanging("PhotoPath");
+ _PhotoPath = StructuralObject.SetValidValue(value, true);
+ ReportPropertyChanged("PhotoPath");
+ OnPhotoPathChanged();
+ }
+ }
+ private global::System.String _PhotoPath;
+ partial void OnPhotoPathChanging(global::System.String value);
+ partial void OnPhotoPathChanged();
+
+ #endregion
+
+ #region Navigation Properties
+
+ /// <summary>
+ /// No Metadata Documentation available.
+ /// </summary>
+ [XmlIgnoreAttribute()]
+ [SoapIgnoreAttribute()]
+ [DataMemberAttribute()]
+ [EdmRelationshipNavigationPropertyAttribute("northwindModel", "FK_Employees_Employees", "Employees1")]
+ public EntityCollection<Employee> Employees1
+ {
+ get
+ {
+ return ((IEntityWithRelationships)this).RelationshipManager.GetRelatedCollection<Employee>("northwindModel.FK_Employees_Employees", "Employees1");
+ }
+ set
+ {
+ if ((value != null))
+ {
+ ((IEntityWithRelationships)this).RelationshipManager.InitializeRelatedCollection<Employee>("northwindModel.FK_Employees_Employees", "Employees1", value);
+ }
+ }
+ }
+
+ /// <summary>
+ /// No Metadata Documentation available.
+ /// </summary>
+ [XmlIgnoreAttribute()]
+ [SoapIgnoreAttribute()]
+ [DataMemberAttribute()]
+ [EdmRelationshipNavigationPropertyAttribute("northwindModel", "FK_Employees_Employees", "Employees")]
+ public Employee Employee1
+ {
+ get
+ {
+ return ((IEntityWithRelationships)this).RelationshipManager.GetRelatedReference<Employee>("northwindModel.FK_Employees_Employees", "Employees").Value;
+ }
+ set
+ {
+ ((IEntityWithRelationships)this).RelationshipManager.GetRelatedReference<Employee>("northwindModel.FK_Employees_Employees", "Employees").Value = value;
+ }
+ }
+ /// <summary>
+ /// No Metadata Documentation available.
+ /// </summary>
+ [BrowsableAttribute(false)]
+ [DataMemberAttribute()]
+ public EntityReference<Employee> Employee1Reference
+ {
+ get
+ {
+ return ((IEntityWithRelationships)this).RelationshipManager.GetRelatedReference<Employee>("northwindModel.FK_Employees_Employees", "Employees");
+ }
+ set
+ {
+ if ((value != null))
+ {
+ ((IEntityWithRelationships)this).RelationshipManager.InitializeRelatedReference<Employee>("northwindModel.FK_Employees_Employees", "Employees", value);
+ }
+ }
+ }
+
+ /// <summary>
+ /// No Metadata Documentation available.
+ /// </summary>
+ [XmlIgnoreAttribute()]
+ [SoapIgnoreAttribute()]
+ [DataMemberAttribute()]
+ [EdmRelationshipNavigationPropertyAttribute("northwindModel", "FK_Orders_Employees", "Orders")]
+ public EntityCollection<Order> Orders
+ {
+ get
+ {
+ return ((IEntityWithRelationships)this).RelationshipManager.GetRelatedCollection<Order>("northwindModel.FK_Orders_Employees", "Orders");
+ }
+ set
+ {
+ if ((value != null))
+ {
+ ((IEntityWithRelationships)this).RelationshipManager.InitializeRelatedCollection<Order>("northwindModel.FK_Orders_Employees", "Orders", value);
+ }
+ }
+ }
+
+ /// <summary>
+ /// No Metadata Documentation available.
+ /// </summary>
+ [XmlIgnoreAttribute()]
+ [SoapIgnoreAttribute()]
+ [DataMemberAttribute()]
+ [EdmRelationshipNavigationPropertyAttribute("northwindModel", "EmployeeTerritories", "Territories")]
+ public EntityCollection<Territory> Territories
+ {
+ get
+ {
+ return ((IEntityWithRelationships)this).RelationshipManager.GetRelatedCollection<Territory>("northwindModel.EmployeeTerritories", "Territories");
+ }
+ set
+ {
+ if ((value != null))
+ {
+ ((IEntityWithRelationships)this).RelationshipManager.InitializeRelatedCollection<Territory>("northwindModel.EmployeeTerritories", "Territories", value);
+ }
+ }
+ }
+
+ #endregion
+ }
+
+ /// <summary>
+ /// No Metadata Documentation available.
+ /// </summary>
+ [EdmEntityTypeAttribute(NamespaceName="northwindModel", Name="Order")]
+ [Serializable()]
+ [DataContractAttribute(IsReference=true)]
+ public partial class Order : EntityObject
+ {
+ #region Factory Method
+
+ /// <summary>
+ /// Create a new Order object.
+ /// </summary>
+ /// <param name="orderID">Initial value of the OrderID property.</param>
+ public static Order CreateOrder(global::System.Int32 orderID)
+ {
+ Order order = new Order();
+ order.OrderID = orderID;
+ return order;
+ }
+
+ #endregion
+ #region Primitive Properties
+
+ /// <summary>
+ /// No Metadata Documentation available.
+ /// </summary>
+ [EdmScalarPropertyAttribute(EntityKeyProperty=true, IsNullable=false)]
+ [DataMemberAttribute()]
+ public global::System.Int32 OrderID
+ {
+ get
+ {
+ return _OrderID;
+ }
+ set
+ {
+ if (_OrderID != value)
+ {
+ OnOrderIDChanging(value);
+ ReportPropertyChanging("OrderID");
+ _OrderID = StructuralObject.SetValidValue(value);
+ ReportPropertyChanged("OrderID");
+ OnOrderIDChanged();
+ }
+ }
+ }
+ private global::System.Int32 _OrderID;
+ partial void OnOrderIDChanging(global::System.Int32 value);
+ partial void OnOrderIDChanged();
+
+ /// <summary>
+ /// No Metadata Documentation available.
+ /// </summary>
+ [EdmScalarPropertyAttribute(EntityKeyProperty=false, IsNullable=true)]
+ [DataMemberAttribute()]
+ public global::System.String CustomerID
+ {
+ get
+ {
+ return _CustomerID;
+ }
+ set
+ {
+ OnCustomerIDChanging(value);
+ ReportPropertyChanging("CustomerID");
+ _CustomerID = StructuralObject.SetValidValue(value, true);
+ ReportPropertyChanged("CustomerID");
+ OnCustomerIDChanged();
+ }
+ }
+ private global::System.String _CustomerID;
+ partial void OnCustomerIDChanging(global::System.String value);
+ partial void OnCustomerIDChanged();
+
+ /// <summary>
+ /// No Metadata Documentation available.
+ /// </summary>
+ [EdmScalarPropertyAttribute(EntityKeyProperty=false, IsNullable=true)]
+ [DataMemberAttribute()]
+ public Nullable<global::System.Int32> EmployeeID
+ {
+ get
+ {
+ return _EmployeeID;
+ }
+ set
+ {
+ OnEmployeeIDChanging(value);
+ ReportPropertyChanging("EmployeeID");
+ _EmployeeID = StructuralObject.SetValidValue(value);
+ ReportPropertyChanged("EmployeeID");
+ OnEmployeeIDChanged();
+ }
+ }
+ private Nullable<global::System.Int32> _EmployeeID;
+ partial void OnEmployeeIDChanging(Nullable<global::System.Int32> value);
+ partial void OnEmployeeIDChanged();
+
+ /// <summary>
+ /// No Metadata Documentation available.
+ /// </summary>
+ [EdmScalarPropertyAttribute(EntityKeyProperty=false, IsNullable=true)]
+ [DataMemberAttribute()]
+ public Nullable<global::System.DateTime> OrderDate
+ {
+ get
+ {
+ return _OrderDate;
+ }
+ set
+ {
+ OnOrderDateChanging(value);
+ ReportPropertyChanging("OrderDate");
+ _OrderDate = StructuralObject.SetValidValue(value);
+ ReportPropertyChanged("OrderDate");
+ OnOrderDateChanged();
+ }
+ }
+ private Nullable<global::System.DateTime> _OrderDate;
+ partial void OnOrderDateChanging(Nullable<global::System.DateTime> value);
+ partial void OnOrderDateChanged();
+
+ /// <summary>
+ /// No Metadata Documentation available.
+ /// </summary>
+ [EdmScalarPropertyAttribute(EntityKeyProperty=false, IsNullable=true)]
+ [DataMemberAttribute()]
+ public Nullable<global::System.DateTime> RequiredDate
+ {
+ get
+ {
+ return _RequiredDate;
+ }
+ set
+ {
+ OnRequiredDateChanging(value);
+ ReportPropertyChanging("RequiredDate");
+ _RequiredDate = StructuralObject.SetValidValue(value);
+ ReportPropertyChanged("RequiredDate");
+ OnRequiredDateChanged();
+ }
+ }
+ private Nullable<global::System.DateTime> _RequiredDate;
+ partial void OnRequiredDateChanging(Nullable<global::System.DateTime> value);
+ partial void OnRequiredDateChanged();
+
+ /// <summary>
+ /// No Metadata Documentation available.
+ /// </summary>
+ [EdmScalarPropertyAttribute(EntityKeyProperty=false, IsNullable=true)]
+ [DataMemberAttribute()]
+ public Nullable<global::System.DateTime> ShippedDate
+ {
+ get
+ {
+ return _ShippedDate;
+ }
+ set
+ {
+ OnShippedDateChanging(value);
+ ReportPropertyChanging("ShippedDate");
+ _ShippedDate = StructuralObject.SetValidValue(value);
+ ReportPropertyChanged("ShippedDate");
+ OnShippedDateChanged();
+ }
+ }
+ private Nullable<global::System.DateTime> _ShippedDate;
+ partial void OnShippedDateChanging(Nullable<global::System.DateTime> value);
+ partial void OnShippedDateChanged();
+
+ /// <summary>
+ /// No Metadata Documentation available.
+ /// </summary>
+ [EdmScalarPropertyAttribute(EntityKeyProperty=false, IsNullable=true)]
+ [DataMemberAttribute()]
+ public Nullable<global::System.Int32> ShipVia
+ {
+ get
+ {
+ return _ShipVia;
+ }
+ set
+ {
+ OnShipViaChanging(value);
+ ReportPropertyChanging("ShipVia");
+ _ShipVia = StructuralObject.SetValidValue(value);
+ ReportPropertyChanged("ShipVia");
+ OnShipViaChanged();
+ }
+ }
+ private Nullable<global::System.Int32> _ShipVia;
+ partial void OnShipViaChanging(Nullable<global::System.Int32> value);
+ partial void OnShipViaChanged();
+
+ /// <summary>
+ /// No Metadata Documentation available.
+ /// </summary>
+ [EdmScalarPropertyAttribute(EntityKeyProperty=false, IsNullable=true)]
+ [DataMemberAttribute()]
+ public Nullable<global::System.Decimal> Freight
+ {
+ get
+ {
+ return _Freight;
+ }
+ set
+ {
+ OnFreightChanging(value);
+ ReportPropertyChanging("Freight");
+ _Freight = StructuralObject.SetValidValue(value);
+ ReportPropertyChanged("Freight");
+ OnFreightChanged();
+ }
+ }
+ private Nullable<global::System.Decimal> _Freight;
+ partial void OnFreightChanging(Nullable<global::System.Decimal> value);
+ partial void OnFreightChanged();
+
+ /// <summary>
+ /// No Metadata Documentation available.
+ /// </summary>
+ [EdmScalarPropertyAttribute(EntityKeyProperty=false, IsNullable=true)]
+ [DataMemberAttribute()]
+ public global::System.String ShipName
+ {
+ get
+ {
+ return _ShipName;
+ }
+ set
+ {
+ OnShipNameChanging(value);
+ ReportPropertyChanging("ShipName");
+ _ShipName = StructuralObject.SetValidValue(value, true);
+ ReportPropertyChanged("ShipName");
+ OnShipNameChanged();
+ }
+ }
+ private global::System.String _ShipName;
+ partial void OnShipNameChanging(global::System.String value);
+ partial void OnShipNameChanged();
+
+ /// <summary>
+ /// No Metadata Documentation available.
+ /// </summary>
+ [EdmScalarPropertyAttribute(EntityKeyProperty=false, IsNullable=true)]
+ [DataMemberAttribute()]
+ public global::System.String ShipAddress
+ {
+ get
+ {
+ return _ShipAddress;
+ }
+ set
+ {
+ OnShipAddressChanging(value);
+ ReportPropertyChanging("ShipAddress");
+ _ShipAddress = StructuralObject.SetValidValue(value, true);
+ ReportPropertyChanged("ShipAddress");
+ OnShipAddressChanged();
+ }
+ }
+ private global::System.String _ShipAddress;
+ partial void OnShipAddressChanging(global::System.String value);
+ partial void OnShipAddressChanged();
+
+ /// <summary>
+ /// No Metadata Documentation available.
+ /// </summary>
+ [EdmScalarPropertyAttribute(EntityKeyProperty=false, IsNullable=true)]
+ [DataMemberAttribute()]
+ public global::System.String ShipCity
+ {
+ get
+ {
+ return _ShipCity;
+ }
+ set
+ {
+ OnShipCityChanging(value);
+ ReportPropertyChanging("ShipCity");
+ _ShipCity = StructuralObject.SetValidValue(value, true);
+ ReportPropertyChanged("ShipCity");
+ OnShipCityChanged();
+ }
+ }
+ private global::System.String _ShipCity;
+ partial void OnShipCityChanging(global::System.String value);
+ partial void OnShipCityChanged();
+
+ /// <summary>
+ /// No Metadata Documentation available.
+ /// </summary>
+ [EdmScalarPropertyAttribute(EntityKeyProperty=false, IsNullable=true)]
+ [DataMemberAttribute()]
+ public global::System.String ShipRegion
+ {
+ get
+ {
+ return _ShipRegion;
+ }
+ set
+ {
+ OnShipRegionChanging(value);
+ ReportPropertyChanging("ShipRegion");
+ _ShipRegion = StructuralObject.SetValidValue(value, true);
+ ReportPropertyChanged("ShipRegion");
+ OnShipRegionChanged();
+ }
+ }
+ private global::System.String _ShipRegion;
+ partial void OnShipRegionChanging(global::System.String value);
+ partial void OnShipRegionChanged();
+
+ /// <summary>
+ /// No Metadata Documentation available.
+ /// </summary>
+ [EdmScalarPropertyAttribute(EntityKeyProperty=false, IsNullable=true)]
+ [DataMemberAttribute()]
+ public global::System.String ShipPostalCode
+ {
+ get
+ {
+ return _ShipPostalCode;
+ }
+ set
+ {
+ OnShipPostalCodeChanging(value);
+ ReportPropertyChanging("ShipPostalCode");
+ _ShipPostalCode = StructuralObject.SetValidValue(value, true);
+ ReportPropertyChanged("ShipPostalCode");
+ OnShipPostalCodeChanged();
+ }
+ }
+ private global::System.String _ShipPostalCode;
+ partial void OnShipPostalCodeChanging(global::System.String value);
+ partial void OnShipPostalCodeChanged();
+
+ /// <summary>
+ /// No Metadata Documentation available.
+ /// </summary>
+ [EdmScalarPropertyAttribute(EntityKeyProperty=false, IsNullable=true)]
+ [DataMemberAttribute()]
+ public global::System.String ShipCountry
+ {
+ get
+ {
+ return _ShipCountry;
+ }
+ set
+ {
+ OnShipCountryChanging(value);
+ ReportPropertyChanging("ShipCountry");
+ _ShipCountry = StructuralObject.SetValidValue(value, true);
+ ReportPropertyChanged("ShipCountry");
+ OnShipCountryChanged();
+ }
+ }
+ private global::System.String _ShipCountry;
+ partial void OnShipCountryChanging(global::System.String value);
+ partial void OnShipCountryChanged();
+
+ #endregion
+
+ #region Navigation Properties
+
+ /// <summary>
+ /// No Metadata Documentation available.
+ /// </summary>
+ [XmlIgnoreAttribute()]
+ [SoapIgnoreAttribute()]
+ [DataMemberAttribute()]
+ [EdmRelationshipNavigationPropertyAttribute("northwindModel", "FK_Orders_Customers", "Customers")]
+ public Customer Customer
+ {
+ get
+ {
+ return ((IEntityWithRelationships)this).RelationshipManager.GetRelatedReference<Customer>("northwindModel.FK_Orders_Customers", "Customers").Value;
+ }
+ set
+ {
+ ((IEntityWithRelationships)this).RelationshipManager.GetRelatedReference<Customer>("northwindModel.FK_Orders_Customers", "Customers").Value = value;
+ }
+ }
+ /// <summary>
+ /// No Metadata Documentation available.
+ /// </summary>
+ [BrowsableAttribute(false)]
+ [DataMemberAttribute()]
+ public EntityReference<Customer> CustomerReference
+ {
+ get
+ {
+ return ((IEntityWithRelationships)this).RelationshipManager.GetRelatedReference<Customer>("northwindModel.FK_Orders_Customers", "Customers");
+ }
+ set
+ {
+ if ((value != null))
+ {
+ ((IEntityWithRelationships)this).RelationshipManager.InitializeRelatedReference<Customer>("northwindModel.FK_Orders_Customers", "Customers", value);
+ }
+ }
+ }
+
+ /// <summary>
+ /// No Metadata Documentation available.
+ /// </summary>
+ [XmlIgnoreAttribute()]
+ [SoapIgnoreAttribute()]
+ [DataMemberAttribute()]
+ [EdmRelationshipNavigationPropertyAttribute("northwindModel", "FK_Orders_Employees", "Employees")]
+ public Employee Employee
+ {
+ get
+ {
+ return ((IEntityWithRelationships)this).RelationshipManager.GetRelatedReference<Employee>("northwindModel.FK_Orders_Employees", "Employees").Value;
+ }
+ set
+ {
+ ((IEntityWithRelationships)this).RelationshipManager.GetRelatedReference<Employee>("northwindModel.FK_Orders_Employees", "Employees").Value = value;
+ }
+ }
+ /// <summary>
+ /// No Metadata Documentation available.
+ /// </summary>
+ [BrowsableAttribute(false)]
+ [DataMemberAttribute()]
+ public EntityReference<Employee> EmployeeReference
+ {
+ get
+ {
+ return ((IEntityWithRelationships)this).RelationshipManager.GetRelatedReference<Employee>("northwindModel.FK_Orders_Employees", "Employees");
+ }
+ set
+ {
+ if ((value != null))
+ {
+ ((IEntityWithRelationships)this).RelationshipManager.InitializeRelatedReference<Employee>("northwindModel.FK_Orders_Employees", "Employees", value);
+ }
+ }
+ }
+
+ /// <summary>
+ /// No Metadata Documentation available.
+ /// </summary>
+ [XmlIgnoreAttribute()]
+ [SoapIgnoreAttribute()]
+ [DataMemberAttribute()]
+ [EdmRelationshipNavigationPropertyAttribute("northwindModel", "FK_Order_Details_Orders", "Order_Details")]
+ public EntityCollection<Order_Detail> Order_Details
+ {
+ get
+ {
+ return ((IEntityWithRelationships)this).RelationshipManager.GetRelatedCollection<Order_Detail>("northwindModel.FK_Order_Details_Orders", "Order_Details");
+ }
+ set
+ {
+ if ((value != null))
+ {
+ ((IEntityWithRelationships)this).RelationshipManager.InitializeRelatedCollection<Order_Detail>("northwindModel.FK_Order_Details_Orders", "Order_Details", value);
+ }
+ }
+ }
+
+ /// <summary>
+ /// No Metadata Documentation available.
+ /// </summary>
+ [XmlIgnoreAttribute()]
+ [SoapIgnoreAttribute()]
+ [DataMemberAttribute()]
+ [EdmRelationshipNavigationPropertyAttribute("northwindModel", "FK_Orders_Shippers", "Shippers")]
+ public Shipper Shipper
+ {
+ get
+ {
+ return ((IEntityWithRelationships)this).RelationshipManager.GetRelatedReference<Shipper>("northwindModel.FK_Orders_Shippers", "Shippers").Value;
+ }
+ set
+ {
+ ((IEntityWithRelationships)this).RelationshipManager.GetRelatedReference<Shipper>("northwindModel.FK_Orders_Shippers", "Shippers").Value = value;
+ }
+ }
+ /// <summary>
+ /// No Metadata Documentation available.
+ /// </summary>
+ [BrowsableAttribute(false)]
+ [DataMemberAttribute()]
+ public EntityReference<Shipper> ShipperReference
+ {
+ get
+ {
+ return ((IEntityWithRelationships)this).RelationshipManager.GetRelatedReference<Shipper>("northwindModel.FK_Orders_Shippers", "Shippers");
+ }
+ set
+ {
+ if ((value != null))
+ {
+ ((IEntityWithRelationships)this).RelationshipManager.InitializeRelatedReference<Shipper>("northwindModel.FK_Orders_Shippers", "Shippers", value);
+ }
+ }
+ }
+
+ #endregion
+ }
+
+ /// <summary>
+ /// No Metadata Documentation available.
+ /// </summary>
+ [EdmEntityTypeAttribute(NamespaceName="northwindModel", Name="Order_Detail")]
+ [Serializable()]
+ [DataContractAttribute(IsReference=true)]
+ public partial class Order_Detail : EntityObject
+ {
+ #region Factory Method
+
+ /// <summary>
+ /// Create a new Order_Detail object.
+ /// </summary>
+ /// <param name="orderID">Initial value of the OrderID property.</param>
+ /// <param name="productID">Initial value of the ProductID property.</param>
+ /// <param name="unitPrice">Initial value of the UnitPrice property.</param>
+ /// <param name="quantity">Initial value of the Quantity property.</param>
+ /// <param name="discount">Initial value of the Discount property.</param>
+ public static Order_Detail CreateOrder_Detail(global::System.Int32 orderID, global::System.Int32 productID, global::System.Decimal unitPrice, global::System.Int16 quantity, global::System.Single discount)
+ {
+ Order_Detail order_Detail = new Order_Detail();
+ order_Detail.OrderID = orderID;
+ order_Detail.ProductID = productID;
+ order_Detail.UnitPrice = unitPrice;
+ order_Detail.Quantity = quantity;
+ order_Detail.Discount = discount;
+ return order_Detail;
+ }
+
+ #endregion
+ #region Primitive Properties
+
+ /// <summary>
+ /// No Metadata Documentation available.
+ /// </summary>
+ [EdmScalarPropertyAttribute(EntityKeyProperty=true, IsNullable=false)]
+ [DataMemberAttribute()]
+ public global::System.Int32 OrderID
+ {
+ get
+ {
+ return _OrderID;
+ }
+ set
+ {
+ if (_OrderID != value)
+ {
+ OnOrderIDChanging(value);
+ ReportPropertyChanging("OrderID");
+ _OrderID = StructuralObject.SetValidValue(value);
+ ReportPropertyChanged("OrderID");
+ OnOrderIDChanged();
+ }
+ }
+ }
+ private global::System.Int32 _OrderID;
+ partial void OnOrderIDChanging(global::System.Int32 value);
+ partial void OnOrderIDChanged();
+
+ /// <summary>
+ /// No Metadata Documentation available.
+ /// </summary>
+ [EdmScalarPropertyAttribute(EntityKeyProperty=true, IsNullable=false)]
+ [DataMemberAttribute()]
+ public global::System.Int32 ProductID
+ {
+ get
+ {
+ return _ProductID;
+ }
+ set
+ {
+ if (_ProductID != value)
+ {
+ OnProductIDChanging(value);
+ ReportPropertyChanging("ProductID");
+ _ProductID = StructuralObject.SetValidValue(value);
+ ReportPropertyChanged("ProductID");
+ OnProductIDChanged();
+ }
+ }
+ }
+ private global::System.Int32 _ProductID;
+ partial void OnProductIDChanging(global::System.Int32 value);
+ partial void OnProductIDChanged();
+
+ /// <summary>
+ /// No Metadata Documentation available.
+ /// </summary>
+ [EdmScalarPropertyAttribute(EntityKeyProperty=false, IsNullable=false)]
+ [DataMemberAttribute()]
+ public global::System.Decimal UnitPrice
+ {
+ get
+ {
+ return _UnitPrice;
+ }
+ set
+ {
+ OnUnitPriceChanging(value);
+ ReportPropertyChanging("UnitPrice");
+ _UnitPrice = StructuralObject.SetValidValue(value);
+ ReportPropertyChanged("UnitPrice");
+ OnUnitPriceChanged();
+ }
+ }
+ private global::System.Decimal _UnitPrice;
+ partial void OnUnitPriceChanging(global::System.Decimal value);
+ partial void OnUnitPriceChanged();
+
+ /// <summary>
+ /// No Metadata Documentation available.
+ /// </summary>
+ [EdmScalarPropertyAttribute(EntityKeyProperty=false, IsNullable=false)]
+ [DataMemberAttribute()]
+ public global::System.Int16 Quantity
+ {
+ get
+ {
+ return _Quantity;
+ }
+ set
+ {
+ OnQuantityChanging(value);
+ ReportPropertyChanging("Quantity");
+ _Quantity = StructuralObject.SetValidValue(value);
+ ReportPropertyChanged("Quantity");
+ OnQuantityChanged();
+ }
+ }
+ private global::System.Int16 _Quantity;
+ partial void OnQuantityChanging(global::System.Int16 value);
+ partial void OnQuantityChanged();
+
+ /// <summary>
+ /// No Metadata Documentation available.
+ /// </summary>
+ [EdmScalarPropertyAttribute(EntityKeyProperty=false, IsNullable=false)]
+ [DataMemberAttribute()]
+ public global::System.Single Discount
+ {
+ get
+ {
+ return _Discount;
+ }
+ set
+ {
+ OnDiscountChanging(value);
+ ReportPropertyChanging("Discount");
+ _Discount = StructuralObject.SetValidValue(value);
+ ReportPropertyChanged("Discount");
+ OnDiscountChanged();
+ }
+ }
+ private global::System.Single _Discount;
+ partial void OnDiscountChanging(global::System.Single value);
+ partial void OnDiscountChanged();
+
+ #endregion
+
+ #region Navigation Properties
+
+ /// <summary>
+ /// No Metadata Documentation available.
+ /// </summary>
+ [XmlIgnoreAttribute()]
+ [SoapIgnoreAttribute()]
+ [DataMemberAttribute()]
+ [EdmRelationshipNavigationPropertyAttribute("northwindModel", "FK_Order_Details_Orders", "Orders")]
+ public Order Order
+ {
+ get
+ {
+ return ((IEntityWithRelationships)this).RelationshipManager.GetRelatedReference<Order>("northwindModel.FK_Order_Details_Orders", "Orders").Value;
+ }
+ set
+ {
+ ((IEntityWithRelationships)this).RelationshipManager.GetRelatedReference<Order>("northwindModel.FK_Order_Details_Orders", "Orders").Value = value;
+ }
+ }
+ /// <summary>
+ /// No Metadata Documentation available.
+ /// </summary>
+ [BrowsableAttribute(false)]
+ [DataMemberAttribute()]
+ public EntityReference<Order> OrderReference
+ {
+ get
+ {
+ return ((IEntityWithRelationships)this).RelationshipManager.GetRelatedReference<Order>("northwindModel.FK_Order_Details_Orders", "Orders");
+ }
+ set
+ {
+ if ((value != null))
+ {
+ ((IEntityWithRelationships)this).RelationshipManager.InitializeRelatedReference<Order>("northwindModel.FK_Order_Details_Orders", "Orders", value);
+ }
+ }
+ }
+
+ /// <summary>
+ /// No Metadata Documentation available.
+ /// </summary>
+ [XmlIgnoreAttribute()]
+ [SoapIgnoreAttribute()]
+ [DataMemberAttribute()]
+ [EdmRelationshipNavigationPropertyAttribute("northwindModel", "FK_Order_Details_Products", "Products")]
+ public Product Product
+ {
+ get
+ {
+ return ((IEntityWithRelationships)this).RelationshipManager.GetRelatedReference<Product>("northwindModel.FK_Order_Details_Products", "Products").Value;
+ }
+ set
+ {
+ ((IEntityWithRelationships)this).RelationshipManager.GetRelatedReference<Product>("northwindModel.FK_Order_Details_Products", "Products").Value = value;
+ }
+ }
+ /// <summary>
+ /// No Metadata Documentation available.
+ /// </summary>
+ [BrowsableAttribute(false)]
+ [DataMemberAttribute()]
+ public EntityReference<Product> ProductReference
+ {
+ get
+ {
+ return ((IEntityWithRelationships)this).RelationshipManager.GetRelatedReference<Product>("northwindModel.FK_Order_Details_Products", "Products");
+ }
+ set
+ {
+ if ((value != null))
+ {
+ ((IEntityWithRelationships)this).RelationshipManager.InitializeRelatedReference<Product>("northwindModel.FK_Order_Details_Products", "Products", value);
+ }
+ }
+ }
+
+ #endregion
+ }
+
+ /// <summary>
+ /// No Metadata Documentation available.
+ /// </summary>
+ [EdmEntityTypeAttribute(NamespaceName="northwindModel", Name="Product")]
+ [Serializable()]
+ [DataContractAttribute(IsReference=true)]
+ public partial class Product : EntityObject
+ {
+ #region Factory Method
+
+ /// <summary>
+ /// Create a new Product object.
+ /// </summary>
+ /// <param name="productID">Initial value of the ProductID property.</param>
+ /// <param name="productName">Initial value of the ProductName property.</param>
+ /// <param name="discontinued">Initial value of the Discontinued property.</param>
+ public static Product CreateProduct(global::System.Int32 productID, global::System.String productName, global::System.Boolean discontinued)
+ {
+ Product product = new Product();
+ product.ProductID = productID;
+ product.ProductName = productName;
+ product.Discontinued = discontinued;
+ return product;
+ }
+
+ #endregion
+ #region Primitive Properties
+
+ /// <summary>
+ /// No Metadata Documentation available.
+ /// </summary>
+ [EdmScalarPropertyAttribute(EntityKeyProperty=true, IsNullable=false)]
+ [DataMemberAttribute()]
+ public global::System.Int32 ProductID
+ {
+ get
+ {
+ return _ProductID;
+ }
+ set
+ {
+ if (_ProductID != value)
+ {
+ OnProductIDChanging(value);
+ ReportPropertyChanging("ProductID");
+ _ProductID = StructuralObject.SetValidValue(value);
+ ReportPropertyChanged("ProductID");
+ OnProductIDChanged();
+ }
+ }
+ }
+ private global::System.Int32 _ProductID;
+ partial void OnProductIDChanging(global::System.Int32 value);
+ partial void OnProductIDChanged();
+
+ /// <summary>
+ /// No Metadata Documentation available.
+ /// </summary>
+ [EdmScalarPropertyAttribute(EntityKeyProperty=false, IsNullable=false)]
+ [DataMemberAttribute()]
+ public global::System.String ProductName
+ {
+ get
+ {
+ return _ProductName;
+ }
+ set
+ {
+ OnProductNameChanging(value);
+ ReportPropertyChanging("ProductName");
+ _ProductName = StructuralObject.SetValidValue(value, false);
+ ReportPropertyChanged("ProductName");
+ OnProductNameChanged();
+ }
+ }
+ private global::System.String _ProductName;
+ partial void OnProductNameChanging(global::System.String value);
+ partial void OnProductNameChanged();
+
+ /// <summary>
+ /// No Metadata Documentation available.
+ /// </summary>
+ [EdmScalarPropertyAttribute(EntityKeyProperty=false, IsNullable=true)]
+ [DataMemberAttribute()]
+ public Nullable<global::System.Int32> SupplierID
+ {
+ get
+ {
+ return _SupplierID;
+ }
+ set
+ {
+ OnSupplierIDChanging(value);
+ ReportPropertyChanging("SupplierID");
+ _SupplierID = StructuralObject.SetValidValue(value);
+ ReportPropertyChanged("SupplierID");
+ OnSupplierIDChanged();
+ }
+ }
+ private Nullable<global::System.Int32> _SupplierID;
+ partial void OnSupplierIDChanging(Nullable<global::System.Int32> value);
+ partial void OnSupplierIDChanged();
+
+ /// <summary>
+ /// No Metadata Documentation available.
+ /// </summary>
+ [EdmScalarPropertyAttribute(EntityKeyProperty=false, IsNullable=true)]
+ [DataMemberAttribute()]
+ public Nullable<global::System.Int32> CategoryID
+ {
+ get
+ {
+ return _CategoryID;
+ }
+ set
+ {
+ OnCategoryIDChanging(value);
+ ReportPropertyChanging("CategoryID");
+ _CategoryID = StructuralObject.SetValidValue(value);
+ ReportPropertyChanged("CategoryID");
+ OnCategoryIDChanged();
+ }
+ }
+ private Nullable<global::System.Int32> _CategoryID;
+ partial void OnCategoryIDChanging(Nullable<global::System.Int32> value);
+ partial void OnCategoryIDChanged();
+
+ /// <summary>
+ /// No Metadata Documentation available.
+ /// </summary>
+ [EdmScalarPropertyAttribute(EntityKeyProperty=false, IsNullable=true)]
+ [DataMemberAttribute()]
+ public global::System.String QuantityPerUnit
+ {
+ get
+ {
+ return _QuantityPerUnit;
+ }
+ set
+ {
+ OnQuantityPerUnitChanging(value);
+ ReportPropertyChanging("QuantityPerUnit");
+ _QuantityPerUnit = StructuralObject.SetValidValue(value, true);
+ ReportPropertyChanged("QuantityPerUnit");
+ OnQuantityPerUnitChanged();
+ }
+ }
+ private global::System.String _QuantityPerUnit;
+ partial void OnQuantityPerUnitChanging(global::System.String value);
+ partial void OnQuantityPerUnitChanged();
+
+ /// <summary>
+ /// No Metadata Documentation available.
+ /// </summary>
+ [EdmScalarPropertyAttribute(EntityKeyProperty=false, IsNullable=true)]
+ [DataMemberAttribute()]
+ public Nullable<global::System.Decimal> UnitPrice
+ {
+ get
+ {
+ return _UnitPrice;
+ }
+ set
+ {
+ OnUnitPriceChanging(value);
+ ReportPropertyChanging("UnitPrice");
+ _UnitPrice = StructuralObject.SetValidValue(value);
+ ReportPropertyChanged("UnitPrice");
+ OnUnitPriceChanged();
+ }
+ }
+ private Nullable<global::System.Decimal> _UnitPrice;
+ partial void OnUnitPriceChanging(Nullable<global::System.Decimal> value);
+ partial void OnUnitPriceChanged();
+
+ /// <summary>
+ /// No Metadata Documentation available.
+ /// </summary>
+ [EdmScalarPropertyAttribute(EntityKeyProperty=false, IsNullable=true)]
+ [DataMemberAttribute()]
+ public Nullable<global::System.Int16> UnitsInStock
+ {
+ get
+ {
+ return _UnitsInStock;
+ }
+ set
+ {
+ OnUnitsInStockChanging(value);
+ ReportPropertyChanging("UnitsInStock");
+ _UnitsInStock = StructuralObject.SetValidValue(value);
+ ReportPropertyChanged("UnitsInStock");
+ OnUnitsInStockChanged();
+ }
+ }
+ private Nullable<global::System.Int16> _UnitsInStock;
+ partial void OnUnitsInStockChanging(Nullable<global::System.Int16> value);
+ partial void OnUnitsInStockChanged();
+
+ /// <summary>
+ /// No Metadata Documentation available.
+ /// </summary>
+ [EdmScalarPropertyAttribute(EntityKeyProperty=false, IsNullable=true)]
+ [DataMemberAttribute()]
+ public Nullable<global::System.Int16> UnitsOnOrder
+ {
+ get
+ {
+ return _UnitsOnOrder;
+ }
+ set
+ {
+ OnUnitsOnOrderChanging(value);
+ ReportPropertyChanging("UnitsOnOrder");
+ _UnitsOnOrder = StructuralObject.SetValidValue(value);
+ ReportPropertyChanged("UnitsOnOrder");
+ OnUnitsOnOrderChanged();
+ }
+ }
+ private Nullable<global::System.Int16> _UnitsOnOrder;
+ partial void OnUnitsOnOrderChanging(Nullable<global::System.Int16> value);
+ partial void OnUnitsOnOrderChanged();
+
+ /// <summary>
+ /// No Metadata Documentation available.
+ /// </summary>
+ [EdmScalarPropertyAttribute(EntityKeyProperty=false, IsNullable=true)]
+ [DataMemberAttribute()]
+ public Nullable<global::System.Int16> ReorderLevel
+ {
+ get
+ {
+ return _ReorderLevel;
+ }
+ set
+ {
+ OnReorderLevelChanging(value);
+ ReportPropertyChanging("ReorderLevel");
+ _ReorderLevel = StructuralObject.SetValidValue(value);
+ ReportPropertyChanged("ReorderLevel");
+ OnReorderLevelChanged();
+ }
+ }
+ private Nullable<global::System.Int16> _ReorderLevel;
+ partial void OnReorderLevelChanging(Nullable<global::System.Int16> value);
+ partial void OnReorderLevelChanged();
+
+ /// <summary>
+ /// No Metadata Documentation available.
+ /// </summary>
+ [EdmScalarPropertyAttribute(EntityKeyProperty=false, IsNullable=false)]
+ [DataMemberAttribute()]
+ public global::System.Boolean Discontinued
+ {
+ get
+ {
+ return _Discontinued;
+ }
+ set
+ {
+ OnDiscontinuedChanging(value);
+ ReportPropertyChanging("Discontinued");
+ _Discontinued = StructuralObject.SetValidValue(value);
+ ReportPropertyChanged("Discontinued");
+ OnDiscontinuedChanged();
+ }
+ }
+ private global::System.Boolean _Discontinued;
+ partial void OnDiscontinuedChanging(global::System.Boolean value);
+ partial void OnDiscontinuedChanged();
+
+ #endregion
+
+ #region Navigation Properties
+
+ /// <summary>
+ /// No Metadata Documentation available.
+ /// </summary>
+ [XmlIgnoreAttribute()]
+ [SoapIgnoreAttribute()]
+ [DataMemberAttribute()]
+ [EdmRelationshipNavigationPropertyAttribute("northwindModel", "FK_Products_Categories", "Categories")]
+ public Category Category
+ {
+ get
+ {
+ return ((IEntityWithRelationships)this).RelationshipManager.GetRelatedReference<Category>("northwindModel.FK_Products_Categories", "Categories").Value;
+ }
+ set
+ {
+ ((IEntityWithRelationships)this).RelationshipManager.GetRelatedReference<Category>("northwindModel.FK_Products_Categories", "Categories").Value = value;
+ }
+ }
+ /// <summary>
+ /// No Metadata Documentation available.
+ /// </summary>
+ [BrowsableAttribute(false)]
+ [DataMemberAttribute()]
+ public EntityReference<Category> CategoryReference
+ {
+ get
+ {
+ return ((IEntityWithRelationships)this).RelationshipManager.GetRelatedReference<Category>("northwindModel.FK_Products_Categories", "Categories");
+ }
+ set
+ {
+ if ((value != null))
+ {
+ ((IEntityWithRelationships)this).RelationshipManager.InitializeRelatedReference<Category>("northwindModel.FK_Products_Categories", "Categories", value);
+ }
+ }
+ }
+
+ /// <summary>
+ /// No Metadata Documentation available.
+ /// </summary>
+ [XmlIgnoreAttribute()]
+ [SoapIgnoreAttribute()]
+ [DataMemberAttribute()]
+ [EdmRelationshipNavigationPropertyAttribute("northwindModel", "FK_Order_Details_Products", "Order_Details")]
+ public EntityCollection<Order_Detail> Order_Details
+ {
+ get
+ {
+ return ((IEntityWithRelationships)this).RelationshipManager.GetRelatedCollection<Order_Detail>("northwindModel.FK_Order_Details_Products", "Order_Details");
+ }
+ set
+ {
+ if ((value != null))
+ {
+ ((IEntityWithRelationships)this).RelationshipManager.InitializeRelatedCollection<Order_Detail>("northwindModel.FK_Order_Details_Products", "Order_Details", value);
+ }
+ }
+ }
+
+ /// <summary>
+ /// No Metadata Documentation available.
+ /// </summary>
+ [XmlIgnoreAttribute()]
+ [SoapIgnoreAttribute()]
+ [DataMemberAttribute()]
+ [EdmRelationshipNavigationPropertyAttribute("northwindModel", "FK_Products_Suppliers", "Suppliers")]
+ public Supplier Supplier
+ {
+ get
+ {
+ return ((IEntityWithRelationships)this).RelationshipManager.GetRelatedReference<Supplier>("northwindModel.FK_Products_Suppliers", "Suppliers").Value;
+ }
+ set
+ {
+ ((IEntityWithRelationships)this).RelationshipManager.GetRelatedReference<Supplier>("northwindModel.FK_Products_Suppliers", "Suppliers").Value = value;
+ }
+ }
+ /// <summary>
+ /// No Metadata Documentation available.
+ /// </summary>
+ [BrowsableAttribute(false)]
+ [DataMemberAttribute()]
+ public EntityReference<Supplier> SupplierReference
+ {
+ get
+ {
+ return ((IEntityWithRelationships)this).RelationshipManager.GetRelatedReference<Supplier>("northwindModel.FK_Products_Suppliers", "Suppliers");
+ }
+ set
+ {
+ if ((value != null))
+ {
+ ((IEntityWithRelationships)this).RelationshipManager.InitializeRelatedReference<Supplier>("northwindModel.FK_Products_Suppliers", "Suppliers", value);
+ }
+ }
+ }
+
+ #endregion
+ }
+
+ /// <summary>
+ /// No Metadata Documentation available.
+ /// </summary>
+ [EdmEntityTypeAttribute(NamespaceName="northwindModel", Name="Region")]
+ [Serializable()]
+ [DataContractAttribute(IsReference=true)]
+ public partial class Region : EntityObject
+ {
+ #region Factory Method
+
+ /// <summary>
+ /// Create a new Region object.
+ /// </summary>
+ /// <param name="regionID">Initial value of the RegionID property.</param>
+ /// <param name="regionDescription">Initial value of the RegionDescription property.</param>
+ public static Region CreateRegion(global::System.Int32 regionID, global::System.String regionDescription)
+ {
+ Region region = new Region();
+ region.RegionID = regionID;
+ region.RegionDescription = regionDescription;
+ return region;
+ }
+
+ #endregion
+ #region Primitive Properties
+
+ /// <summary>
+ /// No Metadata Documentation available.
+ /// </summary>
+ [EdmScalarPropertyAttribute(EntityKeyProperty=true, IsNullable=false)]
+ [DataMemberAttribute()]
+ public global::System.Int32 RegionID
+ {
+ get
+ {
+ return _RegionID;
+ }
+ set
+ {
+ if (_RegionID != value)
+ {
+ OnRegionIDChanging(value);
+ ReportPropertyChanging("RegionID");
+ _RegionID = StructuralObject.SetValidValue(value);
+ ReportPropertyChanged("RegionID");
+ OnRegionIDChanged();
+ }
+ }
+ }
+ private global::System.Int32 _RegionID;
+ partial void OnRegionIDChanging(global::System.Int32 value);
+ partial void OnRegionIDChanged();
+
+ /// <summary>
+ /// No Metadata Documentation available.
+ /// </summary>
+ [EdmScalarPropertyAttribute(EntityKeyProperty=false, IsNullable=false)]
+ [DataMemberAttribute()]
+ public global::System.String RegionDescription
+ {
+ get
+ {
+ return _RegionDescription;
+ }
+ set
+ {
+ OnRegionDescriptionChanging(value);
+ ReportPropertyChanging("RegionDescription");
+ _RegionDescription = StructuralObject.SetValidValue(value, false);
+ ReportPropertyChanged("RegionDescription");
+ OnRegionDescriptionChanged();
+ }
+ }
+ private global::System.String _RegionDescription;
+ partial void OnRegionDescriptionChanging(global::System.String value);
+ partial void OnRegionDescriptionChanged();
+
+ #endregion
+
+ #region Navigation Properties
+
+ /// <summary>
+ /// No Metadata Documentation available.
+ /// </summary>
+ [XmlIgnoreAttribute()]
+ [SoapIgnoreAttribute()]
+ [DataMemberAttribute()]
+ [EdmRelationshipNavigationPropertyAttribute("northwindModel", "FK_Territories_Region", "Territories")]
+ public EntityCollection<Territory> Territories
+ {
+ get
+ {
+ return ((IEntityWithRelationships)this).RelationshipManager.GetRelatedCollection<Territory>("northwindModel.FK_Territories_Region", "Territories");
+ }
+ set
+ {
+ if ((value != null))
+ {
+ ((IEntityWithRelationships)this).RelationshipManager.InitializeRelatedCollection<Territory>("northwindModel.FK_Territories_Region", "Territories", value);
+ }
+ }
+ }
+
+ #endregion
+ }
+
+ /// <summary>
+ /// No Metadata Documentation available.
+ /// </summary>
+ [EdmEntityTypeAttribute(NamespaceName="northwindModel", Name="Shipper")]
+ [Serializable()]
+ [DataContractAttribute(IsReference=true)]
+ public partial class Shipper : EntityObject
+ {
+ #region Factory Method
+
+ /// <summary>
+ /// Create a new Shipper object.
+ /// </summary>
+ /// <param name="shipperID">Initial value of the ShipperID property.</param>
+ /// <param name="companyName">Initial value of the CompanyName property.</param>
+ public static Shipper CreateShipper(global::System.Int32 shipperID, global::System.String companyName)
+ {
+ Shipper shipper = new Shipper();
+ shipper.ShipperID = shipperID;
+ shipper.CompanyName = companyName;
+ return shipper;
+ }
+
+ #endregion
+ #region Primitive Properties
+
+ /// <summary>
+ /// No Metadata Documentation available.
+ /// </summary>
+ [EdmScalarPropertyAttribute(EntityKeyProperty=true, IsNullable=false)]
+ [DataMemberAttribute()]
+ public global::System.Int32 ShipperID
+ {
+ get
+ {
+ return _ShipperID;
+ }
+ set
+ {
+ if (_ShipperID != value)
+ {
+ OnShipperIDChanging(value);
+ ReportPropertyChanging("ShipperID");
+ _ShipperID = StructuralObject.SetValidValue(value);
+ ReportPropertyChanged("ShipperID");
+ OnShipperIDChanged();
+ }
+ }
+ }
+ private global::System.Int32 _ShipperID;
+ partial void OnShipperIDChanging(global::System.Int32 value);
+ partial void OnShipperIDChanged();
+
+ /// <summary>
+ /// No Metadata Documentation available.
+ /// </summary>
+ [EdmScalarPropertyAttribute(EntityKeyProperty=false, IsNullable=false)]
+ [DataMemberAttribute()]
+ public global::System.String CompanyName
+ {
+ get
+ {
+ return _CompanyName;
+ }
+ set
+ {
+ OnCompanyNameChanging(value);
+ ReportPropertyChanging("CompanyName");
+ _CompanyName = StructuralObject.SetValidValue(value, false);
+ ReportPropertyChanged("CompanyName");
+ OnCompanyNameChanged();
+ }
+ }
+ private global::System.String _CompanyName;
+ partial void OnCompanyNameChanging(global::System.String value);
+ partial void OnCompanyNameChanged();
+
+ /// <summary>
+ /// No Metadata Documentation available.
+ /// </summary>
+ [EdmScalarPropertyAttribute(EntityKeyProperty=false, IsNullable=true)]
+ [DataMemberAttribute()]
+ public global::System.String Phone
+ {
+ get
+ {
+ return _Phone;
+ }
+ set
+ {
+ OnPhoneChanging(value);
+ ReportPropertyChanging("Phone");
+ _Phone = StructuralObject.SetValidValue(value, true);
+ ReportPropertyChanged("Phone");
+ OnPhoneChanged();
+ }
+ }
+ private global::System.String _Phone;
+ partial void OnPhoneChanging(global::System.String value);
+ partial void OnPhoneChanged();
+
+ #endregion
+
+ #region Navigation Properties
+
+ /// <summary>
+ /// No Metadata Documentation available.
+ /// </summary>
+ [XmlIgnoreAttribute()]
+ [SoapIgnoreAttribute()]
+ [DataMemberAttribute()]
+ [EdmRelationshipNavigationPropertyAttribute("northwindModel", "FK_Orders_Shippers", "Orders")]
+ public EntityCollection<Order> Orders
+ {
+ get
+ {
+ return ((IEntityWithRelationships)this).RelationshipManager.GetRelatedCollection<Order>("northwindModel.FK_Orders_Shippers", "Orders");
+ }
+ set
+ {
+ if ((value != null))
+ {
+ ((IEntityWithRelationships)this).RelationshipManager.InitializeRelatedCollection<Order>("northwindModel.FK_Orders_Shippers", "Orders", value);
+ }
+ }
+ }
+
+ #endregion
+ }
+
+ /// <summary>
+ /// No Metadata Documentation available.
+ /// </summary>
+ [EdmEntityTypeAttribute(NamespaceName="northwindModel", Name="Supplier")]
+ [Serializable()]
+ [DataContractAttribute(IsReference=true)]
+ public partial class Supplier : EntityObject
+ {
+ #region Factory Method
+
+ /// <summary>
+ /// Create a new Supplier object.
+ /// </summary>
+ /// <param name="supplierID">Initial value of the SupplierID property.</param>
+ /// <param name="companyName">Initial value of the CompanyName property.</param>
+ public static Supplier CreateSupplier(global::System.Int32 supplierID, global::System.String companyName)
+ {
+ Supplier supplier = new Supplier();
+ supplier.SupplierID = supplierID;
+ supplier.CompanyName = companyName;
+ return supplier;
+ }
+
+ #endregion
+ #region Primitive Properties
+
+ /// <summary>
+ /// No Metadata Documentation available.
+ /// </summary>
+ [EdmScalarPropertyAttribute(EntityKeyProperty=true, IsNullable=false)]
+ [DataMemberAttribute()]
+ public global::System.Int32 SupplierID
+ {
+ get
+ {
+ return _SupplierID;
+ }
+ set
+ {
+ if (_SupplierID != value)
+ {
+ OnSupplierIDChanging(value);
+ ReportPropertyChanging("SupplierID");
+ _SupplierID = StructuralObject.SetValidValue(value);
+ ReportPropertyChanged("SupplierID");
+ OnSupplierIDChanged();
+ }
+ }
+ }
+ private global::System.Int32 _SupplierID;
+ partial void OnSupplierIDChanging(global::System.Int32 value);
+ partial void OnSupplierIDChanged();
+
+ /// <summary>
+ /// No Metadata Documentation available.
+ /// </summary>
+ [EdmScalarPropertyAttribute(EntityKeyProperty=false, IsNullable=false)]
+ [DataMemberAttribute()]
+ public global::System.String CompanyName
+ {
+ get
+ {
+ return _CompanyName;
+ }
+ set
+ {
+ OnCompanyNameChanging(value);
+ ReportPropertyChanging("CompanyName");
+ _CompanyName = StructuralObject.SetValidValue(value, false);
+ ReportPropertyChanged("CompanyName");
+ OnCompanyNameChanged();
+ }
+ }
+ private global::System.String _CompanyName;
+ partial void OnCompanyNameChanging(global::System.String value);
+ partial void OnCompanyNameChanged();
+
+ /// <summary>
+ /// No Metadata Documentation available.
+ /// </summary>
+ [EdmScalarPropertyAttribute(EntityKeyProperty=false, IsNullable=true)]
+ [DataMemberAttribute()]
+ public global::System.String ContactName
+ {
+ get
+ {
+ return _ContactName;
+ }
+ set
+ {
+ OnContactNameChanging(value);
+ ReportPropertyChanging("ContactName");
+ _ContactName = StructuralObject.SetValidValue(value, true);
+ ReportPropertyChanged("ContactName");
+ OnContactNameChanged();
+ }
+ }
+ private global::System.String _ContactName;
+ partial void OnContactNameChanging(global::System.String value);
+ partial void OnContactNameChanged();
+
+ /// <summary>
+ /// No Metadata Documentation available.
+ /// </summary>
+ [EdmScalarPropertyAttribute(EntityKeyProperty=false, IsNullable=true)]
+ [DataMemberAttribute()]
+ public global::System.String ContactTitle
+ {
+ get
+ {
+ return _ContactTitle;
+ }
+ set
+ {
+ OnContactTitleChanging(value);
+ ReportPropertyChanging("ContactTitle");
+ _ContactTitle = StructuralObject.SetValidValue(value, true);
+ ReportPropertyChanged("ContactTitle");
+ OnContactTitleChanged();
+ }
+ }
+ private global::System.String _ContactTitle;
+ partial void OnContactTitleChanging(global::System.String value);
+ partial void OnContactTitleChanged();
+
+ /// <summary>
+ /// No Metadata Documentation available.
+ /// </summary>
+ [EdmScalarPropertyAttribute(EntityKeyProperty=false, IsNullable=true)]
+ [DataMemberAttribute()]
+ public global::System.String Address
+ {
+ get
+ {
+ return _Address;
+ }
+ set
+ {
+ OnAddressChanging(value);
+ ReportPropertyChanging("Address");
+ _Address = StructuralObject.SetValidValue(value, true);
+ ReportPropertyChanged("Address");
+ OnAddressChanged();
+ }
+ }
+ private global::System.String _Address;
+ partial void OnAddressChanging(global::System.String value);
+ partial void OnAddressChanged();
+
+ /// <summary>
+ /// No Metadata Documentation available.
+ /// </summary>
+ [EdmScalarPropertyAttribute(EntityKeyProperty=false, IsNullable=true)]
+ [DataMemberAttribute()]
+ public global::System.String City
+ {
+ get
+ {
+ return _City;
+ }
+ set
+ {
+ OnCityChanging(value);
+ ReportPropertyChanging("City");
+ _City = StructuralObject.SetValidValue(value, true);
+ ReportPropertyChanged("City");
+ OnCityChanged();
+ }
+ }
+ private global::System.String _City;
+ partial void OnCityChanging(global::System.String value);
+ partial void OnCityChanged();
+
+ /// <summary>
+ /// No Metadata Documentation available.
+ /// </summary>
+ [EdmScalarPropertyAttribute(EntityKeyProperty=false, IsNullable=true)]
+ [DataMemberAttribute()]
+ public global::System.String Region
+ {
+ get
+ {
+ return _Region;
+ }
+ set
+ {
+ OnRegionChanging(value);
+ ReportPropertyChanging("Region");
+ _Region = StructuralObject.SetValidValue(value, true);
+ ReportPropertyChanged("Region");
+ OnRegionChanged();
+ }
+ }
+ private global::System.String _Region;
+ partial void OnRegionChanging(global::System.String value);
+ partial void OnRegionChanged();
+
+ /// <summary>
+ /// No Metadata Documentation available.
+ /// </summary>
+ [EdmScalarPropertyAttribute(EntityKeyProperty=false, IsNullable=true)]
+ [DataMemberAttribute()]
+ public global::System.String PostalCode
+ {
+ get
+ {
+ return _PostalCode;
+ }
+ set
+ {
+ OnPostalCodeChanging(value);
+ ReportPropertyChanging("PostalCode");
+ _PostalCode = StructuralObject.SetValidValue(value, true);
+ ReportPropertyChanged("PostalCode");
+ OnPostalCodeChanged();
+ }
+ }
+ private global::System.String _PostalCode;
+ partial void OnPostalCodeChanging(global::System.String value);
+ partial void OnPostalCodeChanged();
+
+ /// <summary>
+ /// No Metadata Documentation available.
+ /// </summary>
+ [EdmScalarPropertyAttribute(EntityKeyProperty=false, IsNullable=true)]
+ [DataMemberAttribute()]
+ public global::System.String Country
+ {
+ get
+ {
+ return _Country;
+ }
+ set
+ {
+ OnCountryChanging(value);
+ ReportPropertyChanging("Country");
+ _Country = StructuralObject.SetValidValue(value, true);
+ ReportPropertyChanged("Country");
+ OnCountryChanged();
+ }
+ }
+ private global::System.String _Country;
+ partial void OnCountryChanging(global::System.String value);
+ partial void OnCountryChanged();
+
+ /// <summary>
+ /// No Metadata Documentation available.
+ /// </summary>
+ [EdmScalarPropertyAttribute(EntityKeyProperty=false, IsNullable=true)]
+ [DataMemberAttribute()]
+ public global::System.String Phone
+ {
+ get
+ {
+ return _Phone;
+ }
+ set
+ {
+ OnPhoneChanging(value);
+ ReportPropertyChanging("Phone");
+ _Phone = StructuralObject.SetValidValue(value, true);
+ ReportPropertyChanged("Phone");
+ OnPhoneChanged();
+ }
+ }
+ private global::System.String _Phone;
+ partial void OnPhoneChanging(global::System.String value);
+ partial void OnPhoneChanged();
+
+ /// <summary>
+ /// No Metadata Documentation available.
+ /// </summary>
+ [EdmScalarPropertyAttribute(EntityKeyProperty=false, IsNullable=true)]
+ [DataMemberAttribute()]
+ public global::System.String Fax
+ {
+ get
+ {
+ return _Fax;
+ }
+ set
+ {
+ OnFaxChanging(value);
+ ReportPropertyChanging("Fax");
+ _Fax = StructuralObject.SetValidValue(value, true);
+ ReportPropertyChanged("Fax");
+ OnFaxChanged();
+ }
+ }
+ private global::System.String _Fax;
+ partial void OnFaxChanging(global::System.String value);
+ partial void OnFaxChanged();
+
+ /// <summary>
+ /// No Metadata Documentation available.
+ /// </summary>
+ [EdmScalarPropertyAttribute(EntityKeyProperty=false, IsNullable=true)]
+ [DataMemberAttribute()]
+ public global::System.String HomePage
+ {
+ get
+ {
+ return _HomePage;
+ }
+ set
+ {
+ OnHomePageChanging(value);
+ ReportPropertyChanging("HomePage");
+ _HomePage = StructuralObject.SetValidValue(value, true);
+ ReportPropertyChanged("HomePage");
+ OnHomePageChanged();
+ }
+ }
+ private global::System.String _HomePage;
+ partial void OnHomePageChanging(global::System.String value);
+ partial void OnHomePageChanged();
+
+ #endregion
+
+ #region Navigation Properties
+
+ /// <summary>
+ /// No Metadata Documentation available.
+ /// </summary>
+ [XmlIgnoreAttribute()]
+ [SoapIgnoreAttribute()]
+ [DataMemberAttribute()]
+ [EdmRelationshipNavigationPropertyAttribute("northwindModel", "FK_Products_Suppliers", "Products")]
+ public EntityCollection<Product> Products
+ {
+ get
+ {
+ return ((IEntityWithRelationships)this).RelationshipManager.GetRelatedCollection<Product>("northwindModel.FK_Products_Suppliers", "Products");
+ }
+ set
+ {
+ if ((value != null))
+ {
+ ((IEntityWithRelationships)this).RelationshipManager.InitializeRelatedCollection<Product>("northwindModel.FK_Products_Suppliers", "Products", value);
+ }
+ }
+ }
+
+ #endregion
+ }
+
+ /// <summary>
+ /// No Metadata Documentation available.
+ /// </summary>
+ [EdmEntityTypeAttribute(NamespaceName="northwindModel", Name="Territory")]
+ [Serializable()]
+ [DataContractAttribute(IsReference=true)]
+ public partial class Territory : EntityObject
+ {
+ #region Factory Method
+
+ /// <summary>
+ /// Create a new Territory object.
+ /// </summary>
+ /// <param name="territoryID">Initial value of the TerritoryID property.</param>
+ /// <param name="territoryDescription">Initial value of the TerritoryDescription property.</param>
+ /// <param name="regionID">Initial value of the RegionID property.</param>
+ public static Territory CreateTerritory(global::System.String territoryID, global::System.String territoryDescription, global::System.Int32 regionID)
+ {
+ Territory territory = new Territory();
+ territory.TerritoryID = territoryID;
+ territory.TerritoryDescription = territoryDescription;
+ territory.RegionID = regionID;
+ return territory;
+ }
+
+ #endregion
+ #region Primitive Properties
+
+ /// <summary>
+ /// No Metadata Documentation available.
+ /// </summary>
+ [EdmScalarPropertyAttribute(EntityKeyProperty=true, IsNullable=false)]
+ [DataMemberAttribute()]
+ public global::System.String TerritoryID
+ {
+ get
+ {
+ return _TerritoryID;
+ }
+ set
+ {
+ if (_TerritoryID != value)
+ {
+ OnTerritoryIDChanging(value);
+ ReportPropertyChanging("TerritoryID");
+ _TerritoryID = StructuralObject.SetValidValue(value, false);
+ ReportPropertyChanged("TerritoryID");
+ OnTerritoryIDChanged();
+ }
+ }
+ }
+ private global::System.String _TerritoryID;
+ partial void OnTerritoryIDChanging(global::System.String value);
+ partial void OnTerritoryIDChanged();
+
+ /// <summary>
+ /// No Metadata Documentation available.
+ /// </summary>
+ [EdmScalarPropertyAttribute(EntityKeyProperty=false, IsNullable=false)]
+ [DataMemberAttribute()]
+ public global::System.String TerritoryDescription
+ {
+ get
+ {
+ return _TerritoryDescription;
+ }
+ set
+ {
+ OnTerritoryDescriptionChanging(value);
+ ReportPropertyChanging("TerritoryDescription");
+ _TerritoryDescription = StructuralObject.SetValidValue(value, false);
+ ReportPropertyChanged("TerritoryDescription");
+ OnTerritoryDescriptionChanged();
+ }
+ }
+ private global::System.String _TerritoryDescription;
+ partial void OnTerritoryDescriptionChanging(global::System.String value);
+ partial void OnTerritoryDescriptionChanged();
+
+ /// <summary>
+ /// No Metadata Documentation available.
+ /// </summary>
+ [EdmScalarPropertyAttribute(EntityKeyProperty=false, IsNullable=false)]
+ [DataMemberAttribute()]
+ public global::System.Int32 RegionID
+ {
+ get
+ {
+ return _RegionID;
+ }
+ set
+ {
+ OnRegionIDChanging(value);
+ ReportPropertyChanging("RegionID");
+ _RegionID = StructuralObject.SetValidValue(value);
+ ReportPropertyChanged("RegionID");
+ OnRegionIDChanged();
+ }
+ }
+ private global::System.Int32 _RegionID;
+ partial void OnRegionIDChanging(global::System.Int32 value);
+ partial void OnRegionIDChanged();
+
+ #endregion
+
+ #region Navigation Properties
+
+ /// <summary>
+ /// No Metadata Documentation available.
+ /// </summary>
+ [XmlIgnoreAttribute()]
+ [SoapIgnoreAttribute()]
+ [DataMemberAttribute()]
+ [EdmRelationshipNavigationPropertyAttribute("northwindModel", "FK_Territories_Region", "Region")]
+ public Region Region
+ {
+ get
+ {
+ return ((IEntityWithRelationships)this).RelationshipManager.GetRelatedReference<Region>("northwindModel.FK_Territories_Region", "Region").Value;
+ }
+ set
+ {
+ ((IEntityWithRelationships)this).RelationshipManager.GetRelatedReference<Region>("northwindModel.FK_Territories_Region", "Region").Value = value;
+ }
+ }
+ /// <summary>
+ /// No Metadata Documentation available.
+ /// </summary>
+ [BrowsableAttribute(false)]
+ [DataMemberAttribute()]
+ public EntityReference<Region> RegionReference
+ {
+ get
+ {
+ return ((IEntityWithRelationships)this).RelationshipManager.GetRelatedReference<Region>("northwindModel.FK_Territories_Region", "Region");
+ }
+ set
+ {
+ if ((value != null))
+ {
+ ((IEntityWithRelationships)this).RelationshipManager.InitializeRelatedReference<Region>("northwindModel.FK_Territories_Region", "Region", value);
+ }
+ }
+ }
+
+ /// <summary>
+ /// No Metadata Documentation available.
+ /// </summary>
+ [XmlIgnoreAttribute()]
+ [SoapIgnoreAttribute()]
+ [DataMemberAttribute()]
+ [EdmRelationshipNavigationPropertyAttribute("northwindModel", "EmployeeTerritories", "Employees")]
+ public EntityCollection<Employee> Employees
+ {
+ get
+ {
+ return ((IEntityWithRelationships)this).RelationshipManager.GetRelatedCollection<Employee>("northwindModel.EmployeeTerritories", "Employees");
+ }
+ set
+ {
+ if ((value != null))
+ {
+ ((IEntityWithRelationships)this).RelationshipManager.InitializeRelatedCollection<Employee>("northwindModel.EmployeeTerritories", "Employees", value);
+ }
+ }
+ }
+
+ #endregion
+ }
+
+ #endregion
+
+}
diff --git a/test/Microsoft.Web.Http.Data.Test/Models/Northwind.edmx b/test/Microsoft.Web.Http.Data.Test/Models/Northwind.edmx
new file mode 100644
index 00000000..f74d16cb
--- /dev/null
+++ b/test/Microsoft.Web.Http.Data.Test/Models/Northwind.edmx
@@ -0,0 +1,933 @@
+<?xml version="1.0" encoding="utf-8"?>
+<edmx:Edmx Version="2.0" xmlns:edmx="http://schemas.microsoft.com/ado/2008/10/edmx">
+ <!-- EF Runtime content -->
+ <edmx:Runtime>
+ <!-- SSDL content -->
+ <edmx:StorageModels>
+ <Schema Namespace="northwindModel.Store" Alias="Self" Provider="System.Data.SqlClient" ProviderManifestToken="2005" xmlns:store="http://schemas.microsoft.com/ado/2007/12/edm/EntityStoreSchemaGenerator" xmlns="http://schemas.microsoft.com/ado/2009/02/edm/ssdl">
+ <EntityContainer Name="northwindModelStoreContainer">
+ <EntitySet Name="Categories" EntityType="northwindModel.Store.Categories" store:Type="Tables" Schema="dbo" />
+ <EntitySet Name="CustomerCustomerDemo" EntityType="northwindModel.Store.CustomerCustomerDemo" store:Type="Tables" Schema="dbo" />
+ <EntitySet Name="CustomerDemographics" EntityType="northwindModel.Store.CustomerDemographics" store:Type="Tables" Schema="dbo" />
+ <EntitySet Name="Customers" EntityType="northwindModel.Store.Customers" store:Type="Tables" Schema="dbo" />
+ <EntitySet Name="Employees" EntityType="northwindModel.Store.Employees" store:Type="Tables" Schema="dbo" />
+ <EntitySet Name="EmployeeTerritories" EntityType="northwindModel.Store.EmployeeTerritories" store:Type="Tables" Schema="dbo" />
+ <EntitySet Name="Order Details" EntityType="northwindModel.Store.Order Details" store:Type="Tables" Schema="dbo" />
+ <EntitySet Name="Orders" EntityType="northwindModel.Store.Orders" store:Type="Tables" Schema="dbo" />
+ <EntitySet Name="Products" EntityType="northwindModel.Store.Products" store:Type="Tables" Schema="dbo" />
+ <EntitySet Name="Region" EntityType="northwindModel.Store.Region" store:Type="Tables" Schema="dbo" />
+ <EntitySet Name="Shippers" EntityType="northwindModel.Store.Shippers" store:Type="Tables" Schema="dbo" />
+ <EntitySet Name="Suppliers" EntityType="northwindModel.Store.Suppliers" store:Type="Tables" Schema="dbo" />
+ <EntitySet Name="Territories" EntityType="northwindModel.Store.Territories" store:Type="Tables" Schema="dbo" />
+ <AssociationSet Name="FK_CustomerCustomerDemo" Association="northwindModel.Store.FK_CustomerCustomerDemo">
+ <End Role="CustomerDemographics" EntitySet="CustomerDemographics" />
+ <End Role="CustomerCustomerDemo" EntitySet="CustomerCustomerDemo" />
+ </AssociationSet>
+ <AssociationSet Name="FK_CustomerCustomerDemo_Customers" Association="northwindModel.Store.FK_CustomerCustomerDemo_Customers">
+ <End Role="Customers" EntitySet="Customers" />
+ <End Role="CustomerCustomerDemo" EntitySet="CustomerCustomerDemo" />
+ </AssociationSet>
+ <AssociationSet Name="FK_Employees_Employees" Association="northwindModel.Store.FK_Employees_Employees">
+ <End Role="Employees" EntitySet="Employees" />
+ <End Role="Employees1" EntitySet="Employees" />
+ </AssociationSet>
+ <AssociationSet Name="FK_EmployeeTerritories_Employees" Association="northwindModel.Store.FK_EmployeeTerritories_Employees">
+ <End Role="Employees" EntitySet="Employees" />
+ <End Role="EmployeeTerritories" EntitySet="EmployeeTerritories" />
+ </AssociationSet>
+ <AssociationSet Name="FK_EmployeeTerritories_Territories" Association="northwindModel.Store.FK_EmployeeTerritories_Territories">
+ <End Role="Territories" EntitySet="Territories" />
+ <End Role="EmployeeTerritories" EntitySet="EmployeeTerritories" />
+ </AssociationSet>
+ <AssociationSet Name="FK_Order_Details_Orders" Association="northwindModel.Store.FK_Order_Details_Orders">
+ <End Role="Orders" EntitySet="Orders" />
+ <End Role="Order Details" EntitySet="Order Details" />
+ </AssociationSet>
+ <AssociationSet Name="FK_Order_Details_Products" Association="northwindModel.Store.FK_Order_Details_Products">
+ <End Role="Products" EntitySet="Products" />
+ <End Role="Order Details" EntitySet="Order Details" />
+ </AssociationSet>
+ <AssociationSet Name="FK_Orders_Customers" Association="northwindModel.Store.FK_Orders_Customers">
+ <End Role="Customers" EntitySet="Customers" />
+ <End Role="Orders" EntitySet="Orders" />
+ </AssociationSet>
+ <AssociationSet Name="FK_Orders_Employees" Association="northwindModel.Store.FK_Orders_Employees">
+ <End Role="Employees" EntitySet="Employees" />
+ <End Role="Orders" EntitySet="Orders" />
+ </AssociationSet>
+ <AssociationSet Name="FK_Orders_Shippers" Association="northwindModel.Store.FK_Orders_Shippers">
+ <End Role="Shippers" EntitySet="Shippers" />
+ <End Role="Orders" EntitySet="Orders" />
+ </AssociationSet>
+ <AssociationSet Name="FK_Products_Categories" Association="northwindModel.Store.FK_Products_Categories">
+ <End Role="Categories" EntitySet="Categories" />
+ <End Role="Products" EntitySet="Products" />
+ </AssociationSet>
+ <AssociationSet Name="FK_Products_Suppliers" Association="northwindModel.Store.FK_Products_Suppliers">
+ <End Role="Suppliers" EntitySet="Suppliers" />
+ <End Role="Products" EntitySet="Products" />
+ </AssociationSet>
+ <AssociationSet Name="FK_Territories_Region" Association="northwindModel.Store.FK_Territories_Region">
+ <End Role="Region" EntitySet="Region" />
+ <End Role="Territories" EntitySet="Territories" />
+ </AssociationSet>
+ </EntityContainer>
+ <EntityType Name="Categories">
+ <Key>
+ <PropertyRef Name="CategoryID" />
+ </Key>
+ <Property Name="CategoryID" Type="int" Nullable="false" StoreGeneratedPattern="Identity" />
+ <Property Name="CategoryName" Type="nvarchar" Nullable="false" MaxLength="15" />
+ <Property Name="Description" Type="ntext" />
+ <Property Name="Picture" Type="image" />
+ </EntityType>
+ <EntityType Name="CustomerCustomerDemo">
+ <Key>
+ <PropertyRef Name="CustomerID" />
+ <PropertyRef Name="CustomerTypeID" />
+ </Key>
+ <Property Name="CustomerID" Type="nchar" Nullable="false" MaxLength="5" />
+ <Property Name="CustomerTypeID" Type="nchar" Nullable="false" MaxLength="10" />
+ </EntityType>
+ <EntityType Name="CustomerDemographics">
+ <Key>
+ <PropertyRef Name="CustomerTypeID" />
+ </Key>
+ <Property Name="CustomerTypeID" Type="nchar" Nullable="false" MaxLength="10" />
+ <Property Name="CustomerDesc" Type="ntext" />
+ </EntityType>
+ <EntityType Name="Customers">
+ <Key>
+ <PropertyRef Name="CustomerID" />
+ </Key>
+ <Property Name="CustomerID" Type="nchar" Nullable="false" MaxLength="5" />
+ <Property Name="CompanyName" Type="nvarchar" Nullable="false" MaxLength="40" />
+ <Property Name="ContactName" Type="nvarchar" MaxLength="30" />
+ <Property Name="ContactTitle" Type="nvarchar" MaxLength="30" />
+ <Property Name="Address" Type="nvarchar" MaxLength="60" />
+ <Property Name="City" Type="nvarchar" MaxLength="15" />
+ <Property Name="Region" Type="nvarchar" MaxLength="15" />
+ <Property Name="PostalCode" Type="nvarchar" MaxLength="10" />
+ <Property Name="Country" Type="nvarchar" MaxLength="15" />
+ <Property Name="Phone" Type="nvarchar" MaxLength="24" />
+ <Property Name="Fax" Type="nvarchar" MaxLength="24" />
+ </EntityType>
+ <EntityType Name="Employees">
+ <Key>
+ <PropertyRef Name="EmployeeID" />
+ </Key>
+ <Property Name="EmployeeID" Type="int" Nullable="false" StoreGeneratedPattern="Identity" />
+ <Property Name="LastName" Type="nvarchar" Nullable="false" MaxLength="20" />
+ <Property Name="FirstName" Type="nvarchar" Nullable="false" MaxLength="10" />
+ <Property Name="Title" Type="nvarchar" MaxLength="30" />
+ <Property Name="TitleOfCourtesy" Type="nvarchar" MaxLength="25" />
+ <Property Name="BirthDate" Type="datetime" />
+ <Property Name="HireDate" Type="datetime" />
+ <Property Name="Address" Type="nvarchar" MaxLength="60" />
+ <Property Name="City" Type="nvarchar" MaxLength="15" />
+ <Property Name="Region" Type="nvarchar" MaxLength="15" />
+ <Property Name="PostalCode" Type="nvarchar" MaxLength="10" />
+ <Property Name="Country" Type="nvarchar" MaxLength="15" />
+ <Property Name="HomePhone" Type="nvarchar" MaxLength="24" />
+ <Property Name="Extension" Type="nvarchar" MaxLength="4" />
+ <Property Name="Photo" Type="image" />
+ <Property Name="Notes" Type="ntext" />
+ <Property Name="ReportsTo" Type="int" />
+ <Property Name="PhotoPath" Type="nvarchar" MaxLength="255" />
+ </EntityType>
+ <EntityType Name="EmployeeTerritories">
+ <Key>
+ <PropertyRef Name="EmployeeID" />
+ <PropertyRef Name="TerritoryID" />
+ </Key>
+ <Property Name="EmployeeID" Type="int" Nullable="false" />
+ <Property Name="TerritoryID" Type="nvarchar" Nullable="false" MaxLength="20" />
+ </EntityType>
+ <EntityType Name="Order Details">
+ <Key>
+ <PropertyRef Name="OrderID" />
+ <PropertyRef Name="ProductID" />
+ </Key>
+ <Property Name="OrderID" Type="int" Nullable="false" />
+ <Property Name="ProductID" Type="int" Nullable="false" />
+ <Property Name="UnitPrice" Type="money" Nullable="false" />
+ <Property Name="Quantity" Type="smallint" Nullable="false" />
+ <Property Name="Discount" Type="real" Nullable="false" />
+ </EntityType>
+ <EntityType Name="Orders">
+ <Key>
+ <PropertyRef Name="OrderID" />
+ </Key>
+ <Property Name="OrderID" Type="int" Nullable="false" StoreGeneratedPattern="Identity" />
+ <Property Name="CustomerID" Type="nchar" MaxLength="5" />
+ <Property Name="EmployeeID" Type="int" />
+ <Property Name="OrderDate" Type="datetime" />
+ <Property Name="RequiredDate" Type="datetime" />
+ <Property Name="ShippedDate" Type="datetime" />
+ <Property Name="ShipVia" Type="int" />
+ <Property Name="Freight" Type="money" />
+ <Property Name="ShipName" Type="nvarchar" MaxLength="40" />
+ <Property Name="ShipAddress" Type="nvarchar" MaxLength="60" />
+ <Property Name="ShipCity" Type="nvarchar" MaxLength="15" />
+ <Property Name="ShipRegion" Type="nvarchar" MaxLength="15" />
+ <Property Name="ShipPostalCode" Type="nvarchar" MaxLength="10" />
+ <Property Name="ShipCountry" Type="nvarchar" MaxLength="15" />
+ </EntityType>
+ <EntityType Name="Products">
+ <Key>
+ <PropertyRef Name="ProductID" />
+ </Key>
+ <Property Name="ProductID" Type="int" Nullable="false" StoreGeneratedPattern="Identity" />
+ <Property Name="ProductName" Type="nvarchar" Nullable="false" MaxLength="40" />
+ <Property Name="SupplierID" Type="int" />
+ <Property Name="CategoryID" Type="int" />
+ <Property Name="QuantityPerUnit" Type="nvarchar" MaxLength="20" />
+ <Property Name="UnitPrice" Type="money" />
+ <Property Name="UnitsInStock" Type="smallint" />
+ <Property Name="UnitsOnOrder" Type="smallint" />
+ <Property Name="ReorderLevel" Type="smallint" />
+ <Property Name="Discontinued" Type="bit" Nullable="false" />
+ </EntityType>
+ <EntityType Name="Region">
+ <Key>
+ <PropertyRef Name="RegionID" />
+ </Key>
+ <Property Name="RegionID" Type="int" Nullable="false" />
+ <Property Name="RegionDescription" Type="nchar" Nullable="false" MaxLength="50" />
+ </EntityType>
+ <EntityType Name="Shippers">
+ <Key>
+ <PropertyRef Name="ShipperID" />
+ </Key>
+ <Property Name="ShipperID" Type="int" Nullable="false" StoreGeneratedPattern="Identity" />
+ <Property Name="CompanyName" Type="nvarchar" Nullable="false" MaxLength="40" />
+ <Property Name="Phone" Type="nvarchar" MaxLength="24" />
+ </EntityType>
+ <EntityType Name="Suppliers">
+ <Key>
+ <PropertyRef Name="SupplierID" />
+ </Key>
+ <Property Name="SupplierID" Type="int" Nullable="false" StoreGeneratedPattern="Identity" />
+ <Property Name="CompanyName" Type="nvarchar" Nullable="false" MaxLength="40" />
+ <Property Name="ContactName" Type="nvarchar" MaxLength="30" />
+ <Property Name="ContactTitle" Type="nvarchar" MaxLength="30" />
+ <Property Name="Address" Type="nvarchar" MaxLength="60" />
+ <Property Name="City" Type="nvarchar" MaxLength="15" />
+ <Property Name="Region" Type="nvarchar" MaxLength="15" />
+ <Property Name="PostalCode" Type="nvarchar" MaxLength="10" />
+ <Property Name="Country" Type="nvarchar" MaxLength="15" />
+ <Property Name="Phone" Type="nvarchar" MaxLength="24" />
+ <Property Name="Fax" Type="nvarchar" MaxLength="24" />
+ <Property Name="HomePage" Type="ntext" />
+ </EntityType>
+ <EntityType Name="Territories">
+ <Key>
+ <PropertyRef Name="TerritoryID" />
+ </Key>
+ <Property Name="TerritoryID" Type="nvarchar" Nullable="false" MaxLength="20" />
+ <Property Name="TerritoryDescription" Type="nchar" Nullable="false" MaxLength="50" />
+ <Property Name="RegionID" Type="int" Nullable="false" />
+ </EntityType>
+ <Association Name="FK_CustomerCustomerDemo">
+ <End Role="CustomerDemographics" Type="northwindModel.Store.CustomerDemographics" Multiplicity="1" />
+ <End Role="CustomerCustomerDemo" Type="northwindModel.Store.CustomerCustomerDemo" Multiplicity="*" />
+ <ReferentialConstraint>
+ <Principal Role="CustomerDemographics">
+ <PropertyRef Name="CustomerTypeID" />
+ </Principal>
+ <Dependent Role="CustomerCustomerDemo">
+ <PropertyRef Name="CustomerTypeID" />
+ </Dependent>
+ </ReferentialConstraint>
+ </Association>
+ <Association Name="FK_CustomerCustomerDemo_Customers">
+ <End Role="Customers" Type="northwindModel.Store.Customers" Multiplicity="1" />
+ <End Role="CustomerCustomerDemo" Type="northwindModel.Store.CustomerCustomerDemo" Multiplicity="*" />
+ <ReferentialConstraint>
+ <Principal Role="Customers">
+ <PropertyRef Name="CustomerID" />
+ </Principal>
+ <Dependent Role="CustomerCustomerDemo">
+ <PropertyRef Name="CustomerID" />
+ </Dependent>
+ </ReferentialConstraint>
+ </Association>
+ <Association Name="FK_Employees_Employees">
+ <End Role="Employees" Type="northwindModel.Store.Employees" Multiplicity="0..1" />
+ <End Role="Employees1" Type="northwindModel.Store.Employees" Multiplicity="*" />
+ <ReferentialConstraint>
+ <Principal Role="Employees">
+ <PropertyRef Name="EmployeeID" />
+ </Principal>
+ <Dependent Role="Employees1">
+ <PropertyRef Name="ReportsTo" />
+ </Dependent>
+ </ReferentialConstraint>
+ </Association>
+ <Association Name="FK_EmployeeTerritories_Employees">
+ <End Role="Employees" Type="northwindModel.Store.Employees" Multiplicity="1" />
+ <End Role="EmployeeTerritories" Type="northwindModel.Store.EmployeeTerritories" Multiplicity="*" />
+ <ReferentialConstraint>
+ <Principal Role="Employees">
+ <PropertyRef Name="EmployeeID" />
+ </Principal>
+ <Dependent Role="EmployeeTerritories">
+ <PropertyRef Name="EmployeeID" />
+ </Dependent>
+ </ReferentialConstraint>
+ </Association>
+ <Association Name="FK_EmployeeTerritories_Territories">
+ <End Role="Territories" Type="northwindModel.Store.Territories" Multiplicity="1" />
+ <End Role="EmployeeTerritories" Type="northwindModel.Store.EmployeeTerritories" Multiplicity="*" />
+ <ReferentialConstraint>
+ <Principal Role="Territories">
+ <PropertyRef Name="TerritoryID" />
+ </Principal>
+ <Dependent Role="EmployeeTerritories">
+ <PropertyRef Name="TerritoryID" />
+ </Dependent>
+ </ReferentialConstraint>
+ </Association>
+ <Association Name="FK_Order_Details_Orders">
+ <End Role="Orders" Type="northwindModel.Store.Orders" Multiplicity="1" />
+ <End Role="Order Details" Type="northwindModel.Store.Order Details" Multiplicity="*" />
+ <ReferentialConstraint>
+ <Principal Role="Orders">
+ <PropertyRef Name="OrderID" />
+ </Principal>
+ <Dependent Role="Order Details">
+ <PropertyRef Name="OrderID" />
+ </Dependent>
+ </ReferentialConstraint>
+ </Association>
+ <Association Name="FK_Order_Details_Products">
+ <End Role="Products" Type="northwindModel.Store.Products" Multiplicity="1" />
+ <End Role="Order Details" Type="northwindModel.Store.Order Details" Multiplicity="*" />
+ <ReferentialConstraint>
+ <Principal Role="Products">
+ <PropertyRef Name="ProductID" />
+ </Principal>
+ <Dependent Role="Order Details">
+ <PropertyRef Name="ProductID" />
+ </Dependent>
+ </ReferentialConstraint>
+ </Association>
+ <Association Name="FK_Orders_Customers">
+ <End Role="Customers" Type="northwindModel.Store.Customers" Multiplicity="0..1" />
+ <End Role="Orders" Type="northwindModel.Store.Orders" Multiplicity="*" />
+ <ReferentialConstraint>
+ <Principal Role="Customers">
+ <PropertyRef Name="CustomerID" />
+ </Principal>
+ <Dependent Role="Orders">
+ <PropertyRef Name="CustomerID" />
+ </Dependent>
+ </ReferentialConstraint>
+ </Association>
+ <Association Name="FK_Orders_Employees">
+ <End Role="Employees" Type="northwindModel.Store.Employees" Multiplicity="0..1" />
+ <End Role="Orders" Type="northwindModel.Store.Orders" Multiplicity="*" />
+ <ReferentialConstraint>
+ <Principal Role="Employees">
+ <PropertyRef Name="EmployeeID" />
+ </Principal>
+ <Dependent Role="Orders">
+ <PropertyRef Name="EmployeeID" />
+ </Dependent>
+ </ReferentialConstraint>
+ </Association>
+ <Association Name="FK_Orders_Shippers">
+ <End Role="Shippers" Type="northwindModel.Store.Shippers" Multiplicity="0..1" />
+ <End Role="Orders" Type="northwindModel.Store.Orders" Multiplicity="*" />
+ <ReferentialConstraint>
+ <Principal Role="Shippers">
+ <PropertyRef Name="ShipperID" />
+ </Principal>
+ <Dependent Role="Orders">
+ <PropertyRef Name="ShipVia" />
+ </Dependent>
+ </ReferentialConstraint>
+ </Association>
+ <Association Name="FK_Products_Categories">
+ <End Role="Categories" Type="northwindModel.Store.Categories" Multiplicity="0..1" />
+ <End Role="Products" Type="northwindModel.Store.Products" Multiplicity="*" />
+ <ReferentialConstraint>
+ <Principal Role="Categories">
+ <PropertyRef Name="CategoryID" />
+ </Principal>
+ <Dependent Role="Products">
+ <PropertyRef Name="CategoryID" />
+ </Dependent>
+ </ReferentialConstraint>
+ </Association>
+ <Association Name="FK_Products_Suppliers">
+ <End Role="Suppliers" Type="northwindModel.Store.Suppliers" Multiplicity="0..1" />
+ <End Role="Products" Type="northwindModel.Store.Products" Multiplicity="*" />
+ <ReferentialConstraint>
+ <Principal Role="Suppliers">
+ <PropertyRef Name="SupplierID" />
+ </Principal>
+ <Dependent Role="Products">
+ <PropertyRef Name="SupplierID" />
+ </Dependent>
+ </ReferentialConstraint>
+ </Association>
+ <Association Name="FK_Territories_Region">
+ <End Role="Region" Type="northwindModel.Store.Region" Multiplicity="1" />
+ <End Role="Territories" Type="northwindModel.Store.Territories" Multiplicity="*" />
+ <ReferentialConstraint>
+ <Principal Role="Region">
+ <PropertyRef Name="RegionID" />
+ </Principal>
+ <Dependent Role="Territories">
+ <PropertyRef Name="RegionID" />
+ </Dependent>
+ </ReferentialConstraint>
+ </Association>
+ </Schema>
+ </edmx:StorageModels>
+ <!-- CSDL content -->
+ <edmx:ConceptualModels>
+ <Schema Namespace="northwindModel" Alias="Self" xmlns:annotation="http://schemas.microsoft.com/ado/2009/02/edm/annotation" xmlns="http://schemas.microsoft.com/ado/2008/09/edm">
+ <EntityContainer Name="NorthwindEntities" annotation:LazyLoadingEnabled="true">
+ <EntitySet Name="Categories" EntityType="northwindModel.Category" />
+ <EntitySet Name="CustomerDemographics" EntityType="northwindModel.CustomerDemographic" />
+ <EntitySet Name="Customers" EntityType="northwindModel.Customer" />
+ <EntitySet Name="Employees" EntityType="northwindModel.Employee" />
+ <EntitySet Name="Order_Details" EntityType="northwindModel.Order_Detail" />
+ <EntitySet Name="Orders" EntityType="northwindModel.Order" />
+ <EntitySet Name="Products" EntityType="northwindModel.Product" />
+ <EntitySet Name="Regions" EntityType="northwindModel.Region" />
+ <EntitySet Name="Shippers" EntityType="northwindModel.Shipper" />
+ <EntitySet Name="Suppliers" EntityType="northwindModel.Supplier" />
+ <EntitySet Name="Territories" EntityType="northwindModel.Territory" />
+ <AssociationSet Name="FK_Products_Categories" Association="northwindModel.FK_Products_Categories">
+ <End Role="Categories" EntitySet="Categories" />
+ <End Role="Products" EntitySet="Products" />
+ </AssociationSet>
+ <AssociationSet Name="FK_Orders_Customers" Association="northwindModel.FK_Orders_Customers">
+ <End Role="Customers" EntitySet="Customers" />
+ <End Role="Orders" EntitySet="Orders" />
+ </AssociationSet>
+ <AssociationSet Name="FK_Employees_Employees" Association="northwindModel.FK_Employees_Employees">
+ <End Role="Employees" EntitySet="Employees" />
+ <End Role="Employees1" EntitySet="Employees" />
+ </AssociationSet>
+ <AssociationSet Name="FK_Orders_Employees" Association="northwindModel.FK_Orders_Employees">
+ <End Role="Employees" EntitySet="Employees" />
+ <End Role="Orders" EntitySet="Orders" />
+ </AssociationSet>
+ <AssociationSet Name="FK_Order_Details_Orders" Association="northwindModel.FK_Order_Details_Orders">
+ <End Role="Orders" EntitySet="Orders" />
+ <End Role="Order_Details" EntitySet="Order_Details" />
+ </AssociationSet>
+ <AssociationSet Name="FK_Order_Details_Products" Association="northwindModel.FK_Order_Details_Products">
+ <End Role="Products" EntitySet="Products" />
+ <End Role="Order_Details" EntitySet="Order_Details" />
+ </AssociationSet>
+ <AssociationSet Name="FK_Orders_Shippers" Association="northwindModel.FK_Orders_Shippers">
+ <End Role="Shippers" EntitySet="Shippers" />
+ <End Role="Orders" EntitySet="Orders" />
+ </AssociationSet>
+ <AssociationSet Name="FK_Products_Suppliers" Association="northwindModel.FK_Products_Suppliers">
+ <End Role="Suppliers" EntitySet="Suppliers" />
+ <End Role="Products" EntitySet="Products" />
+ </AssociationSet>
+ <AssociationSet Name="FK_Territories_Region" Association="northwindModel.FK_Territories_Region">
+ <End Role="Region" EntitySet="Regions" />
+ <End Role="Territories" EntitySet="Territories" />
+ </AssociationSet>
+ <AssociationSet Name="CustomerCustomerDemo" Association="northwindModel.CustomerCustomerDemo">
+ <End Role="CustomerDemographics" EntitySet="CustomerDemographics" />
+ <End Role="Customers" EntitySet="Customers" />
+ </AssociationSet>
+ <AssociationSet Name="EmployeeTerritories" Association="northwindModel.EmployeeTerritories">
+ <End Role="Employees" EntitySet="Employees" />
+ <End Role="Territories" EntitySet="Territories" />
+ </AssociationSet>
+ </EntityContainer>
+ <EntityType Name="Category">
+ <Key>
+ <PropertyRef Name="CategoryID" />
+ </Key>
+ <Property Name="CategoryID" Type="Int32" Nullable="false" annotation:StoreGeneratedPattern="Identity" ConcurrencyMode="Fixed" />
+ <Property Name="CategoryName" Type="String" Nullable="false" MaxLength="15" Unicode="true" FixedLength="false" ConcurrencyMode="None" />
+ <Property Name="Description" Type="String" MaxLength="Max" Unicode="true" FixedLength="false" ConcurrencyMode="None" />
+ <Property Name="Picture" Type="Binary" MaxLength="Max" FixedLength="false" />
+ <NavigationProperty Name="Products" Relationship="northwindModel.FK_Products_Categories" FromRole="Categories" ToRole="Products" />
+ </EntityType>
+ <EntityType Name="CustomerDemographic">
+ <Key>
+ <PropertyRef Name="CustomerTypeID" />
+ </Key>
+ <Property Name="CustomerTypeID" Type="String" Nullable="false" MaxLength="10" Unicode="true" FixedLength="true" />
+ <Property Name="CustomerDesc" Type="String" MaxLength="Max" Unicode="true" FixedLength="false" />
+ <NavigationProperty Name="Customers" Relationship="northwindModel.CustomerCustomerDemo" FromRole="CustomerDemographics" ToRole="Customers" />
+ </EntityType>
+ <EntityType Name="Customer">
+ <Key>
+ <PropertyRef Name="CustomerID" />
+ </Key>
+ <Property Name="CustomerID" Type="String" Nullable="false" MaxLength="5" Unicode="true" FixedLength="true" ConcurrencyMode="Fixed" />
+ <Property Name="CompanyName" Type="String" Nullable="false" MaxLength="40" Unicode="true" FixedLength="false" ConcurrencyMode="Fixed" />
+ <Property Name="ContactName" Type="String" MaxLength="30" Unicode="true" FixedLength="false" ConcurrencyMode="Fixed" />
+ <Property Name="ContactTitle" Type="String" MaxLength="30" Unicode="true" FixedLength="false" ConcurrencyMode="Fixed" />
+ <Property Name="Address" Type="String" MaxLength="60" Unicode="true" FixedLength="false" ConcurrencyMode="Fixed" />
+ <Property Name="City" Type="String" MaxLength="15" Unicode="true" FixedLength="false" ConcurrencyMode="Fixed" />
+ <Property Name="Region" Type="String" MaxLength="15" Unicode="true" FixedLength="false" ConcurrencyMode="Fixed" />
+ <Property Name="PostalCode" Type="String" MaxLength="10" Unicode="true" FixedLength="false" ConcurrencyMode="Fixed" />
+ <Property Name="Country" Type="String" MaxLength="15" Unicode="true" FixedLength="false" ConcurrencyMode="Fixed" />
+ <Property Name="Phone" Type="String" MaxLength="24" Unicode="true" FixedLength="false" ConcurrencyMode="Fixed" />
+ <Property Name="Fax" Type="String" MaxLength="24" Unicode="true" FixedLength="false" ConcurrencyMode="Fixed" />
+ <NavigationProperty Name="Orders" Relationship="northwindModel.FK_Orders_Customers" FromRole="Customers" ToRole="Orders" />
+ <NavigationProperty Name="CustomerDemographics" Relationship="northwindModel.CustomerCustomerDemo" FromRole="Customers" ToRole="CustomerDemographics" />
+ </EntityType>
+ <EntityType Name="Employee">
+ <Key>
+ <PropertyRef Name="EmployeeID" />
+ </Key>
+ <Property Name="EmployeeID" Type="Int32" Nullable="false" annotation:StoreGeneratedPattern="Identity" ConcurrencyMode="Fixed" />
+ <Property Name="LastName" Type="String" Nullable="false" MaxLength="20" Unicode="true" FixedLength="false" ConcurrencyMode="Fixed" />
+ <Property Name="FirstName" Type="String" Nullable="false" MaxLength="10" Unicode="true" FixedLength="false" ConcurrencyMode="Fixed" />
+ <Property Name="Title" Type="String" MaxLength="30" Unicode="true" FixedLength="false" ConcurrencyMode="Fixed" />
+ <Property Name="TitleOfCourtesy" Type="String" MaxLength="25" Unicode="true" FixedLength="false" ConcurrencyMode="Fixed" />
+ <Property Name="BirthDate" Type="DateTime" ConcurrencyMode="Fixed" />
+ <Property Name="HireDate" Type="DateTime" ConcurrencyMode="Fixed" />
+ <Property Name="Address" Type="String" MaxLength="60" Unicode="true" FixedLength="false" ConcurrencyMode="Fixed" />
+ <Property Name="City" Type="String" MaxLength="15" Unicode="true" FixedLength="false" ConcurrencyMode="Fixed" />
+ <Property Name="Region" Type="String" MaxLength="15" Unicode="true" FixedLength="false" ConcurrencyMode="Fixed" />
+ <Property Name="PostalCode" Type="String" MaxLength="10" Unicode="true" FixedLength="false" ConcurrencyMode="Fixed" />
+ <Property Name="Country" Type="String" MaxLength="15" Unicode="true" FixedLength="false" ConcurrencyMode="Fixed" />
+ <Property Name="HomePhone" Type="String" MaxLength="24" Unicode="true" FixedLength="false" ConcurrencyMode="Fixed" />
+ <Property Name="Extension" Type="String" MaxLength="4" Unicode="true" FixedLength="false" ConcurrencyMode="Fixed" />
+ <Property Name="Photo" Type="Binary" MaxLength="Max" FixedLength="false" />
+ <Property Name="Notes" Type="String" MaxLength="Max" Unicode="true" FixedLength="false" />
+ <Property Name="ReportsTo" Type="Int32" ConcurrencyMode="Fixed" />
+ <Property Name="PhotoPath" Type="String" MaxLength="255" Unicode="true" FixedLength="false" />
+ <NavigationProperty Name="Employees1" Relationship="northwindModel.FK_Employees_Employees" FromRole="Employees" ToRole="Employees1" />
+ <NavigationProperty Name="Employee1" Relationship="northwindModel.FK_Employees_Employees" FromRole="Employees1" ToRole="Employees" />
+ <NavigationProperty Name="Orders" Relationship="northwindModel.FK_Orders_Employees" FromRole="Employees" ToRole="Orders" />
+ <NavigationProperty Name="Territories" Relationship="northwindModel.EmployeeTerritories" FromRole="Employees" ToRole="Territories" />
+ </EntityType>
+ <EntityType Name="Order_Detail">
+ <Key>
+ <PropertyRef Name="OrderID" />
+ <PropertyRef Name="ProductID" />
+ </Key>
+ <Property Name="OrderID" Type="Int32" Nullable="false" ConcurrencyMode="Fixed" />
+ <Property Name="ProductID" Type="Int32" Nullable="false" ConcurrencyMode="Fixed" />
+ <Property Name="UnitPrice" Type="Decimal" Nullable="false" Precision="19" Scale="4" ConcurrencyMode="Fixed" />
+ <Property Name="Quantity" Type="Int16" Nullable="false" ConcurrencyMode="Fixed" />
+ <Property Name="Discount" Type="Single" Nullable="false" ConcurrencyMode="Fixed" />
+ <NavigationProperty Name="Order" Relationship="northwindModel.FK_Order_Details_Orders" FromRole="Order_Details" ToRole="Orders" />
+ <NavigationProperty Name="Product" Relationship="northwindModel.FK_Order_Details_Products" FromRole="Order_Details" ToRole="Products" />
+ </EntityType>
+ <EntityType Name="Order">
+ <Key>
+ <PropertyRef Name="OrderID" />
+ </Key>
+ <Property Name="OrderID" Type="Int32" Nullable="false" annotation:StoreGeneratedPattern="Identity" ConcurrencyMode="Fixed" />
+ <Property Name="CustomerID" Type="String" MaxLength="5" Unicode="true" FixedLength="true" ConcurrencyMode="Fixed" />
+ <Property Name="EmployeeID" Type="Int32" ConcurrencyMode="Fixed" />
+ <Property Name="OrderDate" Type="DateTime" ConcurrencyMode="Fixed" />
+ <Property Name="RequiredDate" Type="DateTime" ConcurrencyMode="Fixed" />
+ <Property Name="ShippedDate" Type="DateTime" ConcurrencyMode="Fixed" />
+ <Property Name="ShipVia" Type="Int32" ConcurrencyMode="Fixed" />
+ <Property Name="Freight" Type="Decimal" Precision="19" Scale="4" ConcurrencyMode="Fixed" />
+ <Property Name="ShipName" Type="String" MaxLength="40" Unicode="true" FixedLength="false" ConcurrencyMode="Fixed" />
+ <Property Name="ShipAddress" Type="String" MaxLength="60" Unicode="true" FixedLength="false" ConcurrencyMode="Fixed" />
+ <Property Name="ShipCity" Type="String" MaxLength="15" Unicode="true" FixedLength="false" ConcurrencyMode="Fixed" />
+ <Property Name="ShipRegion" Type="String" MaxLength="15" Unicode="true" FixedLength="false" ConcurrencyMode="Fixed" />
+ <Property Name="ShipPostalCode" Type="String" MaxLength="10" Unicode="true" FixedLength="false" ConcurrencyMode="Fixed" />
+ <Property Name="ShipCountry" Type="String" MaxLength="15" Unicode="true" FixedLength="false" ConcurrencyMode="Fixed" />
+ <NavigationProperty Name="Customer" Relationship="northwindModel.FK_Orders_Customers" FromRole="Orders" ToRole="Customers" />
+ <NavigationProperty Name="Employee" Relationship="northwindModel.FK_Orders_Employees" FromRole="Orders" ToRole="Employees" />
+ <NavigationProperty Name="Order_Details" Relationship="northwindModel.FK_Order_Details_Orders" FromRole="Orders" ToRole="Order_Details" />
+ <NavigationProperty Name="Shipper" Relationship="northwindModel.FK_Orders_Shippers" FromRole="Orders" ToRole="Shippers" />
+ </EntityType>
+ <EntityType Name="Product">
+ <Key>
+ <PropertyRef Name="ProductID" />
+ </Key>
+ <Property Name="ProductID" Type="Int32" Nullable="false" annotation:StoreGeneratedPattern="Identity" ConcurrencyMode="None" />
+ <Property Name="ProductName" Type="String" Nullable="false" MaxLength="40" Unicode="true" FixedLength="false" ConcurrencyMode="Fixed" />
+ <Property Name="SupplierID" Type="Int32" ConcurrencyMode="Fixed" />
+ <Property Name="CategoryID" Type="Int32" ConcurrencyMode="Fixed" />
+ <Property Name="QuantityPerUnit" Type="String" MaxLength="20" Unicode="true" FixedLength="false" ConcurrencyMode="Fixed" />
+ <Property Name="UnitPrice" Type="Decimal" Precision="19" Scale="4" ConcurrencyMode="Fixed" />
+ <Property Name="UnitsInStock" Type="Int16" ConcurrencyMode="Fixed" />
+ <Property Name="UnitsOnOrder" Type="Int16" ConcurrencyMode="Fixed" />
+ <Property Name="ReorderLevel" Type="Int16" ConcurrencyMode="Fixed" />
+ <Property Name="Discontinued" Type="Boolean" Nullable="false" ConcurrencyMode="Fixed" />
+ <NavigationProperty Name="Category" Relationship="northwindModel.FK_Products_Categories" FromRole="Products" ToRole="Categories" />
+ <NavigationProperty Name="Order_Details" Relationship="northwindModel.FK_Order_Details_Products" FromRole="Products" ToRole="Order_Details" />
+ <NavigationProperty Name="Supplier" Relationship="northwindModel.FK_Products_Suppliers" FromRole="Products" ToRole="Suppliers" />
+ </EntityType>
+ <EntityType Name="Region">
+ <Key>
+ <PropertyRef Name="RegionID" />
+ </Key>
+ <Property Name="RegionID" Type="Int32" Nullable="false" />
+ <Property Name="RegionDescription" Type="String" Nullable="false" MaxLength="50" Unicode="true" FixedLength="true" />
+ <NavigationProperty Name="Territories" Relationship="northwindModel.FK_Territories_Region" FromRole="Region" ToRole="Territories" />
+ </EntityType>
+ <EntityType Name="Shipper">
+ <Key>
+ <PropertyRef Name="ShipperID" />
+ </Key>
+ <Property Name="ShipperID" Type="Int32" Nullable="false" annotation:StoreGeneratedPattern="Identity" />
+ <Property Name="CompanyName" Type="String" Nullable="false" MaxLength="40" Unicode="true" FixedLength="false" />
+ <Property Name="Phone" Type="String" MaxLength="24" Unicode="true" FixedLength="false" />
+ <NavigationProperty Name="Orders" Relationship="northwindModel.FK_Orders_Shippers" FromRole="Shippers" ToRole="Orders" />
+ </EntityType>
+ <EntityType Name="Supplier">
+ <Key>
+ <PropertyRef Name="SupplierID" />
+ </Key>
+ <Property Name="SupplierID" Type="Int32" Nullable="false" annotation:StoreGeneratedPattern="Identity" />
+ <Property Name="CompanyName" Type="String" Nullable="false" MaxLength="40" Unicode="true" FixedLength="false" />
+ <Property Name="ContactName" Type="String" MaxLength="30" Unicode="true" FixedLength="false" />
+ <Property Name="ContactTitle" Type="String" MaxLength="30" Unicode="true" FixedLength="false" />
+ <Property Name="Address" Type="String" MaxLength="60" Unicode="true" FixedLength="false" />
+ <Property Name="City" Type="String" MaxLength="15" Unicode="true" FixedLength="false" />
+ <Property Name="Region" Type="String" MaxLength="15" Unicode="true" FixedLength="false" />
+ <Property Name="PostalCode" Type="String" MaxLength="10" Unicode="true" FixedLength="false" />
+ <Property Name="Country" Type="String" MaxLength="15" Unicode="true" FixedLength="false" />
+ <Property Name="Phone" Type="String" MaxLength="24" Unicode="true" FixedLength="false" />
+ <Property Name="Fax" Type="String" MaxLength="24" Unicode="true" FixedLength="false" />
+ <Property Name="HomePage" Type="String" MaxLength="Max" Unicode="true" FixedLength="false" />
+ <NavigationProperty Name="Products" Relationship="northwindModel.FK_Products_Suppliers" FromRole="Suppliers" ToRole="Products" />
+ </EntityType>
+ <EntityType Name="Territory">
+ <Key>
+ <PropertyRef Name="TerritoryID" />
+ </Key>
+ <Property Name="TerritoryID" Type="String" Nullable="false" MaxLength="20" Unicode="true" FixedLength="false" />
+ <Property Name="TerritoryDescription" Type="String" Nullable="false" MaxLength="50" Unicode="true" FixedLength="true" />
+ <Property Name="RegionID" Type="Int32" Nullable="false" />
+ <NavigationProperty Name="Region" Relationship="northwindModel.FK_Territories_Region" FromRole="Territories" ToRole="Region" />
+ <NavigationProperty Name="Employees" Relationship="northwindModel.EmployeeTerritories" FromRole="Territories" ToRole="Employees" />
+ </EntityType>
+ <Association Name="FK_Products_Categories">
+ <End Role="Categories" Type="northwindModel.Category" Multiplicity="0..1" />
+ <End Role="Products" Type="northwindModel.Product" Multiplicity="*" />
+ <ReferentialConstraint>
+ <Principal Role="Categories">
+ <PropertyRef Name="CategoryID" />
+ </Principal>
+ <Dependent Role="Products">
+ <PropertyRef Name="CategoryID" />
+ </Dependent>
+ </ReferentialConstraint>
+ </Association>
+ <Association Name="FK_Orders_Customers">
+ <End Role="Customers" Type="northwindModel.Customer" Multiplicity="0..1" />
+ <End Role="Orders" Type="northwindModel.Order" Multiplicity="*" />
+ <ReferentialConstraint>
+ <Principal Role="Customers">
+ <PropertyRef Name="CustomerID" />
+ </Principal>
+ <Dependent Role="Orders">
+ <PropertyRef Name="CustomerID" />
+ </Dependent>
+ </ReferentialConstraint>
+ </Association>
+ <Association Name="FK_Employees_Employees">
+ <End Role="Employees" Type="northwindModel.Employee" Multiplicity="0..1" />
+ <End Role="Employees1" Type="northwindModel.Employee" Multiplicity="*" />
+ <ReferentialConstraint>
+ <Principal Role="Employees">
+ <PropertyRef Name="EmployeeID" />
+ </Principal>
+ <Dependent Role="Employees1">
+ <PropertyRef Name="ReportsTo" />
+ </Dependent>
+ </ReferentialConstraint>
+ </Association>
+ <Association Name="FK_Orders_Employees">
+ <End Role="Employees" Type="northwindModel.Employee" Multiplicity="0..1" />
+ <End Role="Orders" Type="northwindModel.Order" Multiplicity="*" />
+ <ReferentialConstraint>
+ <Principal Role="Employees">
+ <PropertyRef Name="EmployeeID" />
+ </Principal>
+ <Dependent Role="Orders">
+ <PropertyRef Name="EmployeeID" />
+ </Dependent>
+ </ReferentialConstraint>
+ </Association>
+ <Association Name="FK_Order_Details_Orders">
+ <End Role="Orders" Type="northwindModel.Order" Multiplicity="1" />
+ <End Role="Order_Details" Type="northwindModel.Order_Detail" Multiplicity="*" />
+ <ReferentialConstraint>
+ <Principal Role="Orders">
+ <PropertyRef Name="OrderID" />
+ </Principal>
+ <Dependent Role="Order_Details">
+ <PropertyRef Name="OrderID" />
+ </Dependent>
+ </ReferentialConstraint>
+ </Association>
+ <Association Name="FK_Order_Details_Products">
+ <End Role="Products" Type="northwindModel.Product" Multiplicity="1" />
+ <End Role="Order_Details" Type="northwindModel.Order_Detail" Multiplicity="*" />
+ <ReferentialConstraint>
+ <Principal Role="Products">
+ <PropertyRef Name="ProductID" />
+ </Principal>
+ <Dependent Role="Order_Details">
+ <PropertyRef Name="ProductID" />
+ </Dependent>
+ </ReferentialConstraint>
+ </Association>
+ <Association Name="FK_Orders_Shippers">
+ <End Role="Shippers" Type="northwindModel.Shipper" Multiplicity="0..1" />
+ <End Role="Orders" Type="northwindModel.Order" Multiplicity="*" />
+ <ReferentialConstraint>
+ <Principal Role="Shippers">
+ <PropertyRef Name="ShipperID" />
+ </Principal>
+ <Dependent Role="Orders">
+ <PropertyRef Name="ShipVia" />
+ </Dependent>
+ </ReferentialConstraint>
+ </Association>
+ <Association Name="FK_Products_Suppliers">
+ <End Role="Suppliers" Type="northwindModel.Supplier" Multiplicity="0..1" />
+ <End Role="Products" Type="northwindModel.Product" Multiplicity="*" />
+ <ReferentialConstraint>
+ <Principal Role="Suppliers">
+ <PropertyRef Name="SupplierID" />
+ </Principal>
+ <Dependent Role="Products">
+ <PropertyRef Name="SupplierID" />
+ </Dependent>
+ </ReferentialConstraint>
+ </Association>
+ <Association Name="FK_Territories_Region">
+ <End Role="Region" Type="northwindModel.Region" Multiplicity="1" />
+ <End Role="Territories" Type="northwindModel.Territory" Multiplicity="*" />
+ <ReferentialConstraint>
+ <Principal Role="Region">
+ <PropertyRef Name="RegionID" />
+ </Principal>
+ <Dependent Role="Territories">
+ <PropertyRef Name="RegionID" />
+ </Dependent>
+ </ReferentialConstraint>
+ </Association>
+ <Association Name="CustomerCustomerDemo">
+ <End Role="CustomerDemographics" Type="northwindModel.CustomerDemographic" Multiplicity="*" />
+ <End Role="Customers" Type="northwindModel.Customer" Multiplicity="*" />
+ </Association>
+ <Association Name="EmployeeTerritories">
+ <End Role="Employees" Type="northwindModel.Employee" Multiplicity="*" />
+ <End Role="Territories" Type="northwindModel.Territory" Multiplicity="*" />
+ </Association>
+ </Schema>
+ </edmx:ConceptualModels>
+ <!-- C-S mapping content -->
+ <edmx:Mappings>
+ <Mapping Space="C-S" xmlns="http://schemas.microsoft.com/ado/2008/09/mapping/cs">
+ <EntityContainerMapping StorageEntityContainer="northwindModelStoreContainer" CdmEntityContainer="NorthwindEntities">
+ <EntitySetMapping Name="Categories"><EntityTypeMapping TypeName="northwindModel.Category"><MappingFragment StoreEntitySet="Categories">
+ <ScalarProperty Name="CategoryID" ColumnName="CategoryID" />
+ <ScalarProperty Name="CategoryName" ColumnName="CategoryName" />
+ <ScalarProperty Name="Description" ColumnName="Description" />
+ <ScalarProperty Name="Picture" ColumnName="Picture" />
+ </MappingFragment></EntityTypeMapping></EntitySetMapping>
+ <EntitySetMapping Name="CustomerDemographics"><EntityTypeMapping TypeName="northwindModel.CustomerDemographic"><MappingFragment StoreEntitySet="CustomerDemographics">
+ <ScalarProperty Name="CustomerTypeID" ColumnName="CustomerTypeID" />
+ <ScalarProperty Name="CustomerDesc" ColumnName="CustomerDesc" />
+ </MappingFragment></EntityTypeMapping></EntitySetMapping>
+ <EntitySetMapping Name="Customers"><EntityTypeMapping TypeName="northwindModel.Customer"><MappingFragment StoreEntitySet="Customers">
+ <ScalarProperty Name="CustomerID" ColumnName="CustomerID" />
+ <ScalarProperty Name="CompanyName" ColumnName="CompanyName" />
+ <ScalarProperty Name="ContactName" ColumnName="ContactName" />
+ <ScalarProperty Name="ContactTitle" ColumnName="ContactTitle" />
+ <ScalarProperty Name="Address" ColumnName="Address" />
+ <ScalarProperty Name="City" ColumnName="City" />
+ <ScalarProperty Name="Region" ColumnName="Region" />
+ <ScalarProperty Name="PostalCode" ColumnName="PostalCode" />
+ <ScalarProperty Name="Country" ColumnName="Country" />
+ <ScalarProperty Name="Phone" ColumnName="Phone" />
+ <ScalarProperty Name="Fax" ColumnName="Fax" />
+ </MappingFragment></EntityTypeMapping></EntitySetMapping>
+ <EntitySetMapping Name="Employees"><EntityTypeMapping TypeName="northwindModel.Employee"><MappingFragment StoreEntitySet="Employees">
+ <ScalarProperty Name="EmployeeID" ColumnName="EmployeeID" />
+ <ScalarProperty Name="LastName" ColumnName="LastName" />
+ <ScalarProperty Name="FirstName" ColumnName="FirstName" />
+ <ScalarProperty Name="Title" ColumnName="Title" />
+ <ScalarProperty Name="TitleOfCourtesy" ColumnName="TitleOfCourtesy" />
+ <ScalarProperty Name="BirthDate" ColumnName="BirthDate" />
+ <ScalarProperty Name="HireDate" ColumnName="HireDate" />
+ <ScalarProperty Name="Address" ColumnName="Address" />
+ <ScalarProperty Name="City" ColumnName="City" />
+ <ScalarProperty Name="Region" ColumnName="Region" />
+ <ScalarProperty Name="PostalCode" ColumnName="PostalCode" />
+ <ScalarProperty Name="Country" ColumnName="Country" />
+ <ScalarProperty Name="HomePhone" ColumnName="HomePhone" />
+ <ScalarProperty Name="Extension" ColumnName="Extension" />
+ <ScalarProperty Name="Photo" ColumnName="Photo" />
+ <ScalarProperty Name="Notes" ColumnName="Notes" />
+ <ScalarProperty Name="ReportsTo" ColumnName="ReportsTo" />
+ <ScalarProperty Name="PhotoPath" ColumnName="PhotoPath" />
+ </MappingFragment></EntityTypeMapping></EntitySetMapping>
+ <EntitySetMapping Name="Order_Details"><EntityTypeMapping TypeName="northwindModel.Order_Detail"><MappingFragment StoreEntitySet="Order Details">
+ <ScalarProperty Name="OrderID" ColumnName="OrderID" />
+ <ScalarProperty Name="ProductID" ColumnName="ProductID" />
+ <ScalarProperty Name="UnitPrice" ColumnName="UnitPrice" />
+ <ScalarProperty Name="Quantity" ColumnName="Quantity" />
+ <ScalarProperty Name="Discount" ColumnName="Discount" />
+ </MappingFragment></EntityTypeMapping></EntitySetMapping>
+ <EntitySetMapping Name="Orders"><EntityTypeMapping TypeName="northwindModel.Order"><MappingFragment StoreEntitySet="Orders">
+ <ScalarProperty Name="OrderID" ColumnName="OrderID" />
+ <ScalarProperty Name="CustomerID" ColumnName="CustomerID" />
+ <ScalarProperty Name="EmployeeID" ColumnName="EmployeeID" />
+ <ScalarProperty Name="OrderDate" ColumnName="OrderDate" />
+ <ScalarProperty Name="RequiredDate" ColumnName="RequiredDate" />
+ <ScalarProperty Name="ShippedDate" ColumnName="ShippedDate" />
+ <ScalarProperty Name="ShipVia" ColumnName="ShipVia" />
+ <ScalarProperty Name="Freight" ColumnName="Freight" />
+ <ScalarProperty Name="ShipName" ColumnName="ShipName" />
+ <ScalarProperty Name="ShipAddress" ColumnName="ShipAddress" />
+ <ScalarProperty Name="ShipCity" ColumnName="ShipCity" />
+ <ScalarProperty Name="ShipRegion" ColumnName="ShipRegion" />
+ <ScalarProperty Name="ShipPostalCode" ColumnName="ShipPostalCode" />
+ <ScalarProperty Name="ShipCountry" ColumnName="ShipCountry" />
+ </MappingFragment></EntityTypeMapping></EntitySetMapping>
+ <EntitySetMapping Name="Products"><EntityTypeMapping TypeName="northwindModel.Product"><MappingFragment StoreEntitySet="Products">
+ <ScalarProperty Name="ProductID" ColumnName="ProductID" />
+ <ScalarProperty Name="ProductName" ColumnName="ProductName" />
+ <ScalarProperty Name="SupplierID" ColumnName="SupplierID" />
+ <ScalarProperty Name="CategoryID" ColumnName="CategoryID" />
+ <ScalarProperty Name="QuantityPerUnit" ColumnName="QuantityPerUnit" />
+ <ScalarProperty Name="UnitPrice" ColumnName="UnitPrice" />
+ <ScalarProperty Name="UnitsInStock" ColumnName="UnitsInStock" />
+ <ScalarProperty Name="UnitsOnOrder" ColumnName="UnitsOnOrder" />
+ <ScalarProperty Name="ReorderLevel" ColumnName="ReorderLevel" />
+ <ScalarProperty Name="Discontinued" ColumnName="Discontinued" />
+ </MappingFragment></EntityTypeMapping></EntitySetMapping>
+ <EntitySetMapping Name="Regions"><EntityTypeMapping TypeName="northwindModel.Region"><MappingFragment StoreEntitySet="Region">
+ <ScalarProperty Name="RegionID" ColumnName="RegionID" />
+ <ScalarProperty Name="RegionDescription" ColumnName="RegionDescription" />
+ </MappingFragment></EntityTypeMapping></EntitySetMapping>
+ <EntitySetMapping Name="Shippers"><EntityTypeMapping TypeName="northwindModel.Shipper"><MappingFragment StoreEntitySet="Shippers">
+ <ScalarProperty Name="ShipperID" ColumnName="ShipperID" />
+ <ScalarProperty Name="CompanyName" ColumnName="CompanyName" />
+ <ScalarProperty Name="Phone" ColumnName="Phone" />
+ </MappingFragment></EntityTypeMapping></EntitySetMapping>
+ <EntitySetMapping Name="Suppliers"><EntityTypeMapping TypeName="northwindModel.Supplier"><MappingFragment StoreEntitySet="Suppliers">
+ <ScalarProperty Name="SupplierID" ColumnName="SupplierID" />
+ <ScalarProperty Name="CompanyName" ColumnName="CompanyName" />
+ <ScalarProperty Name="ContactName" ColumnName="ContactName" />
+ <ScalarProperty Name="ContactTitle" ColumnName="ContactTitle" />
+ <ScalarProperty Name="Address" ColumnName="Address" />
+ <ScalarProperty Name="City" ColumnName="City" />
+ <ScalarProperty Name="Region" ColumnName="Region" />
+ <ScalarProperty Name="PostalCode" ColumnName="PostalCode" />
+ <ScalarProperty Name="Country" ColumnName="Country" />
+ <ScalarProperty Name="Phone" ColumnName="Phone" />
+ <ScalarProperty Name="Fax" ColumnName="Fax" />
+ <ScalarProperty Name="HomePage" ColumnName="HomePage" />
+ </MappingFragment></EntityTypeMapping></EntitySetMapping>
+ <EntitySetMapping Name="Territories"><EntityTypeMapping TypeName="northwindModel.Territory"><MappingFragment StoreEntitySet="Territories">
+ <ScalarProperty Name="TerritoryID" ColumnName="TerritoryID" />
+ <ScalarProperty Name="TerritoryDescription" ColumnName="TerritoryDescription" />
+ <ScalarProperty Name="RegionID" ColumnName="RegionID" />
+ </MappingFragment></EntityTypeMapping></EntitySetMapping>
+ <AssociationSetMapping Name="CustomerCustomerDemo" TypeName="northwindModel.CustomerCustomerDemo" StoreEntitySet="CustomerCustomerDemo">
+ <EndProperty Name="CustomerDemographics">
+ <ScalarProperty Name="CustomerTypeID" ColumnName="CustomerTypeID" />
+ </EndProperty>
+ <EndProperty Name="Customers">
+ <ScalarProperty Name="CustomerID" ColumnName="CustomerID" />
+ </EndProperty>
+ </AssociationSetMapping>
+ <AssociationSetMapping Name="EmployeeTerritories" TypeName="northwindModel.EmployeeTerritories" StoreEntitySet="EmployeeTerritories">
+ <EndProperty Name="Employees">
+ <ScalarProperty Name="EmployeeID" ColumnName="EmployeeID" />
+ </EndProperty>
+ <EndProperty Name="Territories">
+ <ScalarProperty Name="TerritoryID" ColumnName="TerritoryID" />
+ </EndProperty>
+ </AssociationSetMapping>
+ </EntityContainerMapping>
+ </Mapping>
+ </edmx:Mappings>
+ </edmx:Runtime>
+ <!-- EF Designer content (DO NOT EDIT MANUALLY BELOW HERE) -->
+ <Designer xmlns="http://schemas.microsoft.com/ado/2008/10/edmx">
+ <Connection>
+ <DesignerInfoPropertySet>
+ <DesignerProperty Name="MetadataArtifactProcessing" Value="EmbedInOutputAssembly" />
+ </DesignerInfoPropertySet>
+ </Connection>
+ <Options>
+ <DesignerInfoPropertySet>
+ <DesignerProperty Name="ValidateOnBuild" Value="true" />
+ <DesignerProperty Name="EnablePluralization" Value="True" />
+ <DesignerProperty Name="IncludeForeignKeysInModel" Value="True" />
+ </DesignerInfoPropertySet>
+ </Options>
+ <!-- Diagram content (shape and connector positions) -->
+ <Diagrams>
+ <Diagram Name="Northwind">
+ <EntityTypeShape EntityType="northwindModel.Category" Width="1.5" PointX="3" PointY="14.5" Height="1.9802864583333317" IsExpanded="true" />
+ <EntityTypeShape EntityType="northwindModel.CustomerDemographic" Width="1.5" PointX="0.75" PointY="2.5" Height="1.5956835937500005" IsExpanded="true" />
+ <EntityTypeShape EntityType="northwindModel.Customer" Width="1.5" PointX="3" PointY="1.5" Height="3.5186979166666661" IsExpanded="true" />
+ <EntityTypeShape EntityType="northwindModel.Employee" Width="1.5" PointX="3" PointY="8.375" Height="5.2494108072916674" IsExpanded="true" />
+ <EntityTypeShape EntityType="northwindModel.Order_Detail" Width="1.5" PointX="7.5" PointY="2.125" Height="2.3648893229166656" IsExpanded="true" />
+ <EntityTypeShape EntityType="northwindModel.Order" Width="1.5" PointX="5.25" PointY="1" Height="4.480205078125" IsExpanded="true" />
+ <EntityTypeShape EntityType="northwindModel.Product" Width="1.5" PointX="5.25" PointY="15.875" Height="3.5186979166666674" IsExpanded="true" />
+ <EntityTypeShape EntityType="northwindModel.Region" Width="1.5" PointX="5.75" PointY="7.125" Height="1.5956835937499996" IsExpanded="true" />
+ <EntityTypeShape EntityType="northwindModel.Shipper" Width="1.5" PointX="3" PointY="5.875" Height="1.7879850260416674" IsExpanded="true" />
+ <EntityTypeShape EntityType="northwindModel.Supplier" Width="1.5" PointX="3" PointY="17.25" Height="3.5186979166666674" IsExpanded="true" />
+ <EntityTypeShape EntityType="northwindModel.Territory" Width="1.5" PointX="8" PointY="9.375" Height="1.9802864583333353" IsExpanded="true" />
+ <AssociationConnector Association="northwindModel.FK_Products_Categories" ManuallyRouted="false">
+ <ConnectorPoint PointX="4.5" PointY="16.177643229166666" />
+ <ConnectorPoint PointX="5.25" PointY="16.177643229166666" /></AssociationConnector>
+ <AssociationConnector Association="northwindModel.FK_Orders_Customers" ManuallyRouted="false">
+ <ConnectorPoint PointX="4.5" PointY="3.2593489583333328" />
+ <ConnectorPoint PointX="5.25" PointY="3.2593489583333328" /></AssociationConnector>
+ <AssociationConnector Association="northwindModel.FK_Employees_Employees" ManuallyRouted="false">
+ <ConnectorPoint PointX="3.5319230769230767" PointY="13.624410807291667" />
+ <ConnectorPoint PointX="3.5319230769230767" PointY="13.874410807291667" />
+ <ConnectorPoint PointX="3.9784615384615383" PointY="13.874410807291667" />
+ <ConnectorPoint PointX="3.9784615384615383" PointY="13.624410807291667" /></AssociationConnector>
+ <AssociationConnector Association="northwindModel.FK_Orders_Employees" ManuallyRouted="false">
+ <ConnectorPoint PointX="4.5" PointY="11.203797200520834" />
+ <ConnectorPoint PointX="5.46875" PointY="11.203797200520834" />
+ <ConnectorPoint PointX="5.46875" PointY="5.480205078125" /></AssociationConnector>
+ <AssociationConnector Association="northwindModel.FK_Order_Details_Orders" ManuallyRouted="false">
+ <ConnectorPoint PointX="6.75" PointY="3.3074446614583328" />
+ <ConnectorPoint PointX="7.5" PointY="3.3074446614583328" /></AssociationConnector>
+ <AssociationConnector Association="northwindModel.FK_Order_Details_Products" ManuallyRouted="false">
+ <ConnectorPoint PointX="6.75" PointY="17.634348958333334" />
+ <ConnectorPoint PointX="7.71875" PointY="17.634348958333334" />
+ <ConnectorPoint PointX="7.71875" PointY="4.4898893229166656" /></AssociationConnector>
+ <AssociationConnector Association="northwindModel.FK_Orders_Shippers" ManuallyRouted="false">
+ <ConnectorPoint PointX="4.5" PointY="6.46875" />
+ <ConnectorPoint PointX="5.3281225" PointY="6.46875" />
+ <ConnectorPoint PointX="5.3281225" PointY="5.480205078125" /></AssociationConnector>
+ <AssociationConnector Association="northwindModel.FK_Products_Suppliers" ManuallyRouted="false">
+ <ConnectorPoint PointX="4.5" PointY="18.321848958333334" />
+ <ConnectorPoint PointX="5.25" PointY="18.321848958333334" /></AssociationConnector>
+ <AssociationConnector Association="northwindModel.FK_Territories_Region" ManuallyRouted="false">
+ <ConnectorPoint PointX="7.25" PointY="7.922841796875" />
+ <ConnectorPoint PointX="7.635416666666667" PointY="7.9228417968749989" />
+ <ConnectorPoint PointX="7.802083333333333" PointY="7.922841796875" />
+ <ConnectorPoint PointX="8.75" PointY="7.922841796875" />
+ <ConnectorPoint PointX="8.75" PointY="9.375" /></AssociationConnector>
+ <AssociationConnector Association="northwindModel.CustomerCustomerDemo" ManuallyRouted="false">
+ <ConnectorPoint PointX="2.25" PointY="3.2978417968750002" />
+ <ConnectorPoint PointX="3" PointY="3.2978417968750002" /></AssociationConnector>
+ <AssociationConnector Association="northwindModel.EmployeeTerritories" ManuallyRouted="false">
+ <ConnectorPoint PointX="4.5" PointY="10.226898600260416" />
+ <ConnectorPoint PointX="5.385416666666667" PointY="10.226898600260416" />
+ <ConnectorPoint PointX="5.552083333333333" PointY="10.226898600260416" />
+ <ConnectorPoint PointX="7.635416666666667" PointY="10.226898600260416" />
+ <ConnectorPoint PointX="7.802083333333333" PointY="10.226898600260416" />
+ <ConnectorPoint PointX="8" PointY="10.226898600260416" /></AssociationConnector></Diagram></Diagrams>
+ </Designer>
+</edmx:Edmx> \ No newline at end of file
diff --git a/test/Microsoft.Web.Http.Data.Test/Properties/AssemblyInfo.cs b/test/Microsoft.Web.Http.Data.Test/Properties/AssemblyInfo.cs
new file mode 100644
index 00000000..4cebce01
--- /dev/null
+++ b/test/Microsoft.Web.Http.Data.Test/Properties/AssemblyInfo.cs
@@ -0,0 +1,34 @@
+using System.Reflection;
+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("Microsoft.Web.Http.Data.Test")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("Microsoft")]
+[assembly: AssemblyProduct("Microsoft.Web.Http.Data.Test")]
+[assembly: AssemblyCopyright("Copyright © Microsoft 2011")]
+[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("8e9662e7-dc7b-4ffa-8cb0-1002491aae66")]
+
+// 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/test/Microsoft.Web.Http.Data.Test/TestHelpers.cs b/test/Microsoft.Web.Http.Data.Test/TestHelpers.cs
new file mode 100644
index 00000000..8315be78
--- /dev/null
+++ b/test/Microsoft.Web.Http.Data.Test/TestHelpers.cs
@@ -0,0 +1,114 @@
+using System;
+using System.Data.EntityClient;
+using System.Net.Http;
+using System.Security.Principal;
+using System.Web;
+using System.Web.Http;
+using System.Web.Http.Hosting;
+using System.Web.Http.Routing;
+using Moq;
+
+namespace Microsoft.Web.Http.Data.Test
+{
+ internal static class TestHelpers
+ {
+ internal static HttpRequestMessage CreateTestMessage(string url, HttpMethod httpMethod, HttpConfiguration config)
+ {
+ HttpRequestMessage requestMessage = new HttpRequestMessage(httpMethod, url);
+ IHttpRouteData rd = config.Routes[0].GetRouteData("/", requestMessage);
+ requestMessage.Properties.Add(HttpPropertyKeys.HttpRouteDataKey, rd);
+ var principalMock = new Mock<IPrincipal>();
+ principalMock.Setup(p => p.Identity.IsAuthenticated).Returns(true);
+ requestMessage.Properties.Add(HttpPropertyKeys.UserPrincipalKey, principalMock.Object);
+ return requestMessage;
+ }
+
+ // Return a non-functional connection string for an EF context. This will
+ // allow a context to be instantiated, but not used.
+ internal static string GetTestEFConnectionString()
+ {
+ string connectionString = new EntityConnectionStringBuilder
+ {
+ Metadata = "res://*",
+ Provider = "System.Data.SqlClient",
+ ProviderConnectionString = new System.Data.SqlClient.SqlConnectionStringBuilder
+ {
+ InitialCatalog = "Northwind",
+ DataSource = "xyz",
+ IntegratedSecurity = false,
+ UserID = "xyz",
+ Password = "xyz",
+ }.ConnectionString
+ }.ConnectionString;
+
+ return connectionString;
+ }
+ }
+
+ internal static class TestConstants
+ {
+ public static string BaseUrl = "http://testhost/";
+ public static string CatalogUrl = "http://testhost/Catalog/";
+ public static string CitiesUrl = "http://testhost/Cities/";
+ }
+
+ internal class HttpContextStub : HttpContextBase
+ {
+ private HttpRequestStub request;
+
+ public HttpContextStub(Uri baseAddress, HttpRequestMessage request)
+ {
+ this.request = new HttpRequestStub(baseAddress, request);
+ }
+
+ public override HttpRequestBase Request
+ {
+ get
+ {
+ return this.request;
+ }
+ }
+ }
+
+ internal class HttpRequestStub : HttpRequestBase
+ {
+ private const string AppRelativePrefix = "~/";
+ private string appRelativeCurrentExecutionFilePath;
+
+ public HttpRequestStub(Uri baseAddress, HttpRequestMessage request)
+ {
+ this.appRelativeCurrentExecutionFilePath = GetAppRelativeCurrentExecutionFilePath(baseAddress.AbsoluteUri, request.RequestUri.AbsoluteUri);
+ }
+
+ public override string AppRelativeCurrentExecutionFilePath
+ {
+ get
+ {
+ return this.appRelativeCurrentExecutionFilePath;
+ }
+ }
+
+ public override string PathInfo
+ {
+ get
+ {
+ return String.Empty;
+ }
+ }
+
+ private static string GetAppRelativeCurrentExecutionFilePath(string baseAddress, string requestUri)
+ {
+ int queryPos = requestUri.IndexOf('?');
+ string requestUriNoQuery = queryPos < 0 ? requestUri : requestUri.Substring(0, queryPos);
+
+ if (baseAddress.Length >= requestUriNoQuery.Length)
+ {
+ return AppRelativePrefix;
+ }
+ else
+ {
+ return AppRelativePrefix + requestUriNoQuery.Substring(baseAddress.Length);
+ }
+ }
+ }
+}
diff --git a/test/Microsoft.Web.Http.Data.Test/packages.config b/test/Microsoft.Web.Http.Data.Test/packages.config
new file mode 100644
index 00000000..881d2097
--- /dev/null
+++ b/test/Microsoft.Web.Http.Data.Test/packages.config
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<packages>
+ <package id="EntityFramework" version="4.1.10331.0" />
+ <package id="Microsoft.Net.Http" version="2.0.20302.1" />
+ <package id="Moq" version="4.0.10827" />
+ <package id="Newtonsoft.Json" version="4.0.8" />
+ <package id="xunit" version="1.9.0.1566" />
+ <package id="xunit.extensions" version="1.9.0.1566" />
+</packages> \ No newline at end of file
diff --git a/test/Microsoft.Web.Mvc.Test/Controls/Test/DesignModeSite.cs b/test/Microsoft.Web.Mvc.Test/Controls/Test/DesignModeSite.cs
new file mode 100644
index 00000000..19d6db0f
--- /dev/null
+++ b/test/Microsoft.Web.Mvc.Test/Controls/Test/DesignModeSite.cs
@@ -0,0 +1,34 @@
+using System;
+using System.ComponentModel;
+
+namespace Microsoft.Web.Mvc.Controls.Test
+{
+ public class DesignModeSite : ISite
+ {
+ IComponent ISite.Component
+ {
+ get { throw new NotImplementedException(); }
+ }
+
+ IContainer ISite.Container
+ {
+ get { throw new NotImplementedException(); }
+ }
+
+ bool ISite.DesignMode
+ {
+ get { return true; }
+ }
+
+ string ISite.Name
+ {
+ get { throw new NotImplementedException(); }
+ set { throw new NotImplementedException(); }
+ }
+
+ object IServiceProvider.GetService(Type serviceType)
+ {
+ throw new NotImplementedException();
+ }
+ }
+}
diff --git a/test/Microsoft.Web.Mvc.Test/Controls/Test/DropDownListTest.cs b/test/Microsoft.Web.Mvc.Test/Controls/Test/DropDownListTest.cs
new file mode 100644
index 00000000..26322d09
--- /dev/null
+++ b/test/Microsoft.Web.Mvc.Test/Controls/Test/DropDownListTest.cs
@@ -0,0 +1,128 @@
+using System.Web.Mvc;
+using Xunit;
+
+namespace Microsoft.Web.Mvc.Controls.Test
+{
+ public class DropDownListTest
+ {
+ [Fact]
+ public void NameProperty()
+ {
+ // TODO: This
+ }
+
+ [Fact]
+ public void RenderWithNoNameNotInDesignModeThrows()
+ {
+ // TODO: This
+ }
+
+ [Fact]
+ public void RenderWithNoNameInDesignModeRendersWithSampleData()
+ {
+ // Setup
+ DropDownList c = new DropDownList();
+
+ // Execute
+ string html = MvcTestHelper.GetControlRendering(c, true);
+
+ // Verify
+ Assert.Equal(@"<select>
+ <option>
+ Sample Item
+ </option>
+</select>", html);
+ }
+
+ [Fact]
+ public void RenderWithNoAttributes()
+ {
+ // Setup
+ DropDownList c = new DropDownList();
+ c.Name = "nameKey";
+
+ ViewDataContainer vdc = new ViewDataContainer();
+ vdc.Controls.Add(c);
+ vdc.ViewData = new ViewDataDictionary();
+ vdc.ViewData["nameKey"] = new SelectList(new[] { "aaa", "bbb", "ccc" }, "bbb");
+
+ // Execute
+ string html = MvcTestHelper.GetControlRendering(c, false);
+
+ // Verify
+ Assert.Equal(@"<select name=""nameKey"">
+ <option>
+ aaa
+ </option><option selected=""selected"">
+ bbb
+ </option><option>
+ ccc
+ </option>
+</select>", html);
+ }
+
+ [Fact]
+ public void RenderWithTextsAndValues()
+ {
+ // Setup
+ DropDownList c = new DropDownList();
+ c.Name = "nameKey";
+
+ ViewDataContainer vdc = new ViewDataContainer();
+ vdc.Controls.Add(c);
+ vdc.ViewData = new ViewDataDictionary();
+ vdc.ViewData["nameKey"] = new SelectList(
+ new[]
+ {
+ new { Text = "aaa", Value = "111" },
+ new { Text = "bbb", Value = "222" },
+ new { Text = "ccc", Value = "333" }
+ },
+ "Value",
+ "Text",
+ "222");
+
+ // Execute
+ string html = MvcTestHelper.GetControlRendering(c, false);
+
+ // Verify
+ Assert.Equal(@"<select name=""nameKey"">
+ <option value=""111"">
+ aaa
+ </option><option value=""222"" selected=""selected"">
+ bbb
+ </option><option value=""333"">
+ ccc
+ </option>
+</select>", html);
+ }
+
+ [Fact]
+ public void RenderWithNameAndIdRendersNameAndIdAttribute()
+ {
+ // Setup
+ DropDownList c = new DropDownList();
+ c.Name = "nameKey";
+ c.ID = "someID";
+
+ ViewDataContainer vdc = new ViewDataContainer();
+ vdc.Controls.Add(c);
+ vdc.ViewData = new ViewDataDictionary();
+ vdc.ViewData["nameKey"] = new SelectList(new[] { "aaa", "bbb", "ccc" }, "bbb");
+
+ // Execute
+ string html = MvcTestHelper.GetControlRendering(c, false);
+
+ // Verify
+ Assert.Equal(@"<select id=""someID"" name=""nameKey"">
+ <option>
+ aaa
+ </option><option selected=""selected"">
+ bbb
+ </option><option>
+ ccc
+ </option>
+</select>", html);
+ }
+ }
+}
diff --git a/test/Microsoft.Web.Mvc.Test/Controls/Test/MvcControlTest.cs b/test/Microsoft.Web.Mvc.Test/Controls/Test/MvcControlTest.cs
new file mode 100644
index 00000000..66275637
--- /dev/null
+++ b/test/Microsoft.Web.Mvc.Test/Controls/Test/MvcControlTest.cs
@@ -0,0 +1,80 @@
+using System.Collections.Generic;
+using System.Web.Mvc;
+using System.Web.UI;
+using Xunit;
+
+namespace Microsoft.Web.Mvc.Controls.Test
+{
+ public class MvcControlTest
+ {
+ [Fact]
+ public void AttributesProperty()
+ {
+ // Setup
+ DummyMvcControl c = new DummyMvcControl();
+
+ // Execute
+ IDictionary<string, string> attrs = c.Attributes;
+
+ // Verify
+ Assert.NotNull(attrs);
+ Assert.Empty(attrs);
+ }
+
+ [Fact]
+ public void GetSetAttributes()
+ {
+ // Setup
+ DummyMvcControl c = new DummyMvcControl();
+ IAttributeAccessor attrAccessor = c;
+ IDictionary<string, string> attrs = c.Attributes;
+
+ // Execute and Verify
+ string value;
+ value = attrAccessor.GetAttribute("xyz");
+ Assert.Null(value);
+
+ attrAccessor.SetAttribute("a1", "v1");
+ value = attrAccessor.GetAttribute("a1");
+ Assert.Equal("v1", value);
+ Assert.Single(attrs);
+ value = c.Attributes["a1"];
+ Assert.Equal("v1", value);
+ }
+
+ [Fact]
+ public void EnableViewStateProperty()
+ {
+ DummyMvcControl c = new DummyMvcControl();
+ Assert.True(c.EnableViewState);
+ Assert.True((c).EnableViewState);
+
+ c.EnableViewState = false;
+ Assert.False(c.EnableViewState);
+ Assert.False((c).EnableViewState);
+
+ c.EnableViewState = true;
+ Assert.True(c.EnableViewState);
+ Assert.True((c).EnableViewState);
+ }
+
+ [Fact]
+ public void ViewContextWithNoPageIsNull()
+ {
+ // Setup
+ DummyMvcControl c = new DummyMvcControl();
+ Control c1 = new Control();
+ c1.Controls.Add(c);
+
+ // Execute
+ ViewContext vc = c.ViewContext;
+
+ // Verify
+ Assert.Null(vc);
+ }
+
+ private sealed class DummyMvcControl : MvcControl
+ {
+ }
+ }
+}
diff --git a/test/Microsoft.Web.Mvc.Test/Controls/Test/MvcTestHelper.cs b/test/Microsoft.Web.Mvc.Test/Controls/Test/MvcTestHelper.cs
new file mode 100644
index 00000000..fbd4b60a
--- /dev/null
+++ b/test/Microsoft.Web.Mvc.Test/Controls/Test/MvcTestHelper.cs
@@ -0,0 +1,19 @@
+using System.IO;
+using System.Web.UI;
+
+namespace Microsoft.Web.Mvc.Controls.Test
+{
+ public static class MvcTestHelper
+ {
+ public static string GetControlRendering(Control c, bool designMode)
+ {
+ if (designMode)
+ {
+ c.Site = new DesignModeSite();
+ }
+ HtmlTextWriter writer = new HtmlTextWriter(new StringWriter());
+ c.RenderControl(writer);
+ return writer.InnerWriter.ToString();
+ }
+ }
+}
diff --git a/test/Microsoft.Web.Mvc.Test/Controls/Test/ViewDataContainer.cs b/test/Microsoft.Web.Mvc.Test/Controls/Test/ViewDataContainer.cs
new file mode 100644
index 00000000..a0c1a842
--- /dev/null
+++ b/test/Microsoft.Web.Mvc.Test/Controls/Test/ViewDataContainer.cs
@@ -0,0 +1,10 @@
+using System.Web.Mvc;
+using System.Web.UI;
+
+namespace Microsoft.Web.Mvc.Controls.Test
+{
+ public class ViewDataContainer : Control, IViewDataContainer
+ {
+ public ViewDataDictionary ViewData { get; set; }
+ }
+}
diff --git a/test/Microsoft.Web.Mvc.Test/Microsoft.Web.Mvc.Test.csproj b/test/Microsoft.Web.Mvc.Test/Microsoft.Web.Mvc.Test.csproj
new file mode 100644
index 00000000..89289b40
--- /dev/null
+++ b/test/Microsoft.Web.Mvc.Test/Microsoft.Web.Mvc.Test.csproj
@@ -0,0 +1,167 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory),Runtime.sln))\tools\WebStack.settings.targets" />
+ <PropertyGroup>
+ <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+ <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+ <ProductVersion>9.0.30729</ProductVersion>
+ <SchemaVersion>2.0</SchemaVersion>
+ <ProjectGuid>{6C28DA70-60F1-4442-967F-591BF3962EC5}</ProjectGuid>
+ <OutputType>Library</OutputType>
+ <AppDesignerFolder>Properties</AppDesignerFolder>
+ <RootNamespace>Microsoft.Web</RootNamespace>
+ <AssemblyName>Microsoft.Web.Mvc.Test</AssemblyName>
+ <FileAlignment>512</FileAlignment>
+ <ProjectTypeGuids>{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
+ <ErrorReport>prompt</ErrorReport>
+ <WarningLevel>4</WarningLevel>
+ <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
+ <!-- Force signing off -->
+ <SignAssembly>false</SignAssembly>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
+ <DebugSymbols>true</DebugSymbols>
+ <DebugType>full</DebugType>
+ <Optimize>false</Optimize>
+ <OutputPath>$(WebStackRootPath)\bin\Debug\Test\</OutputPath>
+ <DefineConstants>TRACE;DEBUG</DefineConstants>
+ <CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
+ <DebugType>pdbonly</DebugType>
+ <Optimize>true</Optimize>
+ <OutputPath>$(WebStackRootPath)\bin\Release\Test\</OutputPath>
+ <DefineConstants>TRACE</DefineConstants>
+ <CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)' == 'CodeCoverage' ">
+ <DebugSymbols>true</DebugSymbols>
+ <DebugType>full</DebugType>
+ <Optimize>false</Optimize>
+ <OutputPath>$(WebStackRootPath)\bin\CodeCoverage\Test\</OutputPath>
+ <DefineConstants>TRACE;DEBUG</DefineConstants>
+ <CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
+ </PropertyGroup>
+ <ItemGroup>
+ <Reference Include="Microsoft.CSharp" />
+ <Reference Include="Moq, Version=4.0.10827.0, Culture=neutral, PublicKeyToken=69f491c39445e920, processorArchitecture=MSIL">
+ <HintPath>..\..\packages\Moq.4.0.10827\lib\NET40\Moq.dll</HintPath>
+ </Reference>
+ <Reference Include="System" />
+ <Reference Include="System.ComponentModel.DataAnnotations" />
+ <Reference Include="System.Core" />
+ <Reference Include="System.Data.Linq" />
+ <Reference Include="System.Web" />
+ <Reference Include="System.Web.Abstractions" />
+ <Reference Include="System.Web.Routing" />
+ <Reference Include="xunit">
+ <HintPath>..\..\packages\xunit.1.9.0.1566\lib\xunit.dll</HintPath>
+ </Reference>
+ <Reference Include="xunit.extensions">
+ <HintPath>..\..\packages\xunit.extensions.1.9.0.1566\lib\xunit.extensions.dll</HintPath>
+ </Reference>
+ </ItemGroup>
+ <ItemGroup>
+ <Compile Include="Controls\Test\DesignModeSite.cs" />
+ <Compile Include="Controls\Test\DropDownListTest.cs" />
+ <Compile Include="Controls\Test\MvcControlTest.cs" />
+ <Compile Include="Controls\Test\MvcTestHelper.cs" />
+ <Compile Include="Controls\Test\ViewDataContainer.cs" />
+ <Compile Include="ModelBinding\Test\BindingBehaviorAttributeTest.cs" />
+ <Compile Include="ModelBinding\Test\BinaryDataModelBinderProviderTest.cs" />
+ <Compile Include="ModelBinding\Test\ExtensibleModelBinderAdapterTest.cs" />
+ <Compile Include="ModelBinding\Test\ExtensibleModelBindingContextTest.cs" />
+ <Compile Include="ModelBinding\Test\GenericModelBinderProviderTest.cs" />
+ <Compile Include="Test\AreaHelpersTest.cs" />
+ <Compile Include="ModelBinding\Test\CollectionModelBinderProviderTest.cs" />
+ <Compile Include="ModelBinding\Test\CollectionModelBinderTest.cs" />
+ <Compile Include="ModelBinding\Test\CollectionModelBinderUtilTest.cs" />
+ <Compile Include="ModelBinding\Test\ComplexModelDtoResultTest.cs" />
+ <Compile Include="ModelBinding\Test\ComplexModelDtoTest.cs" />
+ <Compile Include="ModelBinding\Test\ComplexModelDtoModelBinderTest.cs" />
+ <Compile Include="ModelBinding\Test\ComplexModelDtoModelBinderProviderTest.cs" />
+ <Compile Include="ModelBinding\Test\ArrayModelBinderTest.cs" />
+ <Compile Include="ModelBinding\Test\ArrayModelBinderProviderTest.cs" />
+ <Compile Include="ModelBinding\Test\DictionaryModelBinderProviderTest.cs" />
+ <Compile Include="ModelBinding\Test\DictionaryModelBinderTest.cs" />
+ <Compile Include="ModelBinding\Test\KeyValuePairModelBinderUtilTest.cs" />
+ <Compile Include="ModelBinding\Test\KeyValuePairModelBinderTest.cs" />
+ <Compile Include="ModelBinding\Test\KeyValuePairModelBinderProviderTest.cs" />
+ <Compile Include="ModelBinding\Test\MutableObjectModelBinderProviderTest.cs" />
+ <Compile Include="ModelBinding\Test\MutableObjectModelBinderTest.cs" />
+ <Compile Include="ModelBinding\Test\TypeConverterModelBinderTest.cs" />
+ <Compile Include="ModelBinding\Test\TypeConverterModelBinderProviderTest.cs" />
+ <Compile Include="ModelBinding\Test\ModelBinderConfigTest.cs" />
+ <Compile Include="ModelBinding\Test\SimpleModelBinderProviderTest.cs" />
+ <Compile Include="ModelBinding\Test\TypeMatchModelBinderProviderTest.cs" />
+ <Compile Include="ModelBinding\Test\TypeMatchModelBinderTest.cs" />
+ <Compile Include="ModelBinding\Test\ModelBinderProviderCollectionTest.cs" />
+ <Compile Include="ModelBinding\Test\ModelBinderUtilTest.cs" />
+ <Compile Include="ModelBinding\Test\ModelValidationNodeTest.cs" />
+ <Compile Include="ModelBinding\Test\ModelBinderProvidersTest.cs" />
+ <Compile Include="Test\ButtonTest.cs" />
+ <Compile Include="Test\ContentTypeAttributeTest.cs" />
+ <Compile Include="Test\ControllerExtensionsTest.cs" />
+ <Compile Include="Test\CookieTempDataProviderTest.cs" />
+ <Compile Include="Test\AjaxOnlyAttributeTest.cs" />
+ <Compile Include="Test\AsyncManagerExtensionsTest.cs" />
+ <Compile Include="Test\CookieValueProviderFactoryTest.cs" />
+ <Compile Include="Test\CreditCardAttributeTest.cs" />
+ <Compile Include="Test\DynamicReflectionObjectTest.cs" />
+ <Compile Include="Test\DynamicViewDataDictionaryTest.cs" />
+ <Compile Include="Test\DynamicViewPageTest.cs" />
+ <Compile Include="Test\EmailAddressAttribueTest.cs" />
+ <Compile Include="Test\FileExtensionsAttributeTest.cs" />
+ <Compile Include="Test\ModelCopierTest.cs" />
+ <Compile Include="Test\ElementalValueProviderTest.cs" />
+ <Compile Include="Test\UrlAttributeTest.cs" />
+ <Compile Include="Test\ValueProviderUtilTest.cs" />
+ <Compile Include="Test\TempDataValueProviderFactoryTest.cs" />
+ <Compile Include="Test\SessionValueProviderFactoryTest.cs" />
+ <Compile Include="Test\ServerVariablesValueProviderFactoryTest.cs" />
+ <Compile Include="Test\CopyAsyncParametersAttributeTest.cs" />
+ <Compile Include="Test\CssExtensionsTests.cs" />
+ <Compile Include="Test\DeserializeAttributeTest.cs" />
+ <Compile Include="Test\ScriptExtensionsTest.cs" />
+ <Compile Include="Test\SerializationExtensionsTest.cs" />
+ <Compile Include="Test\MvcSerializerTest.cs" />
+ <Compile Include="Test\ExpressionHelperTest.cs" />
+ <Compile Include="Test\MailToExtensionsTest.cs" />
+ <Compile Include="Test\ReaderWriterCacheTest.cs" />
+ <Compile Include="Test\RenderActionTest.cs" />
+ <Compile Include="Test\SkipBindingAttributeTest.cs" />
+ <Compile Include="Test\FormExtensionsTest.cs" />
+ <Compile Include="Test\RadioExtensionsTest.cs" />
+ <Compile Include="Test\SubmitImageExtensionsTest.cs" />
+ <Compile Include="Test\ImageExtensionsTest.cs" />
+ <Compile Include="Test\SubmitButtonExtensionsTest.cs" />
+ <Compile Include="Test\TypeHelpersTest.cs" />
+ <Compile Include="Properties\AssemblyInfo.cs" />
+ </ItemGroup>
+ <ItemGroup>
+ <ProjectReference Include="..\..\src\Microsoft.Web.Mvc\Microsoft.Web.Mvc.csproj">
+ <Project>{D3CF7430-6DA4-42B0-BD90-CA39D16687B2}</Project>
+ <Name>Microsoft.Web.Mvc</Name>
+ </ProjectReference>
+ <ProjectReference Include="..\..\src\System.Web.Mvc\System.Web.Mvc.csproj">
+ <Project>{3D3FFD8A-624D-4E9B-954B-E1C105507975}</Project>
+ <Name>System.Web.Mvc</Name>
+ </ProjectReference>
+ <ProjectReference Include="..\..\src\System.Web.WebPages\System.Web.WebPages.csproj">
+ <Project>{76EFA9C5-8D7E-4FDF-B710-E20F8B6B00D2}</Project>
+ <Name>System.Web.WebPages</Name>
+ </ProjectReference>
+ <ProjectReference Include="..\Microsoft.TestCommon\Microsoft.TestCommon.csproj">
+ <Project>{FCCC4CB7-BAF7-4A57-9F89-E5766FE536C0}</Project>
+ <Name>Microsoft.TestCommon</Name>
+ </ProjectReference>
+ <ProjectReference Include="..\System.Web.Mvc.Test\System.Web.Mvc.Test.csproj">
+ <Project>{8AC2A2E4-2F11-4D40-A887-62E2583A65E6}</Project>
+ <Name>System.Web.Mvc.Test</Name>
+ </ProjectReference>
+ </ItemGroup>
+ <ItemGroup>
+ <None Include="packages.config" />
+ </ItemGroup>
+ <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
+</Project> \ No newline at end of file
diff --git a/test/Microsoft.Web.Mvc.Test/ModelBinding/Test/ArrayModelBinderProviderTest.cs b/test/Microsoft.Web.Mvc.Test/ModelBinding/Test/ArrayModelBinderProviderTest.cs
new file mode 100644
index 00000000..34219007
--- /dev/null
+++ b/test/Microsoft.Web.Mvc.Test/ModelBinding/Test/ArrayModelBinderProviderTest.cs
@@ -0,0 +1,100 @@
+using System.Collections.Generic;
+using System.Web.Mvc;
+using Microsoft.Web.UnitTestUtil;
+using Xunit;
+
+namespace Microsoft.Web.Mvc.ModelBinding.Test
+{
+ public class ArrayModelBinderProviderTest
+ {
+ [Fact]
+ public void GetBinder_CorrectModelTypeAndValueProviderEntries_ReturnsBinder()
+ {
+ // Arrange
+ ExtensibleModelBindingContext bindingContext = new ExtensibleModelBindingContext
+ {
+ ModelMetadata = new EmptyModelMetadataProvider().GetMetadataForType(null, typeof(int[])),
+ ModelName = "foo",
+ ValueProvider = new SimpleValueProvider
+ {
+ { "foo[0]", "42" },
+ }
+ };
+
+ ArrayModelBinderProvider binderProvider = new ArrayModelBinderProvider();
+
+ // Act
+ IExtensibleModelBinder binder = binderProvider.GetBinder(null, bindingContext);
+
+ // Assert
+ Assert.IsType<ArrayModelBinder<int>>(binder);
+ }
+
+ [Fact]
+ public void GetBinder_ModelMetadataReturnsReadOnly_ReturnsNull()
+ {
+ // Arrange
+ ExtensibleModelBindingContext bindingContext = new ExtensibleModelBindingContext
+ {
+ ModelMetadata = new EmptyModelMetadataProvider().GetMetadataForType(null, typeof(int[])),
+ ModelName = "foo",
+ ValueProvider = new SimpleValueProvider
+ {
+ { "foo[0]", "42" },
+ }
+ };
+ bindingContext.ModelMetadata.IsReadOnly = true;
+
+ ArrayModelBinderProvider binderProvider = new ArrayModelBinderProvider();
+
+ // Act
+ IExtensibleModelBinder binder = binderProvider.GetBinder(null, bindingContext);
+
+ // Assert
+ Assert.Null(binder);
+ }
+
+ [Fact]
+ public void GetBinder_ModelTypeIsIncorrect_ReturnsNull()
+ {
+ // Arrange
+ ExtensibleModelBindingContext bindingContext = new ExtensibleModelBindingContext
+ {
+ ModelMetadata = new EmptyModelMetadataProvider().GetMetadataForType(null, typeof(ICollection<int>)),
+ ModelName = "foo",
+ ValueProvider = new SimpleValueProvider
+ {
+ { "foo[0]", "42" },
+ }
+ };
+
+ ArrayModelBinderProvider binderProvider = new ArrayModelBinderProvider();
+
+ // Act
+ IExtensibleModelBinder binder = binderProvider.GetBinder(null, bindingContext);
+
+ // Assert
+ Assert.Null(binder);
+ }
+
+ [Fact]
+ public void GetBinder_ValueProviderDoesNotContainPrefix_ReturnsNull()
+ {
+ // Arrange
+ ExtensibleModelBindingContext bindingContext = new ExtensibleModelBindingContext
+ {
+ ModelMetadata = new EmptyModelMetadataProvider().GetMetadataForType(null, typeof(int[])),
+ ModelName = "foo",
+ ValueProvider = new SimpleValueProvider()
+ };
+
+ ArrayModelBinderProvider binderProvider = new ArrayModelBinderProvider();
+
+ // Act
+ IExtensibleModelBinder binder = binderProvider.GetBinder(null, bindingContext);
+
+ // Assert
+ Assert.Null(binder);
+ }
+ }
+}
diff --git a/test/Microsoft.Web.Mvc.Test/ModelBinding/Test/ArrayModelBinderTest.cs b/test/Microsoft.Web.Mvc.Test/ModelBinding/Test/ArrayModelBinderTest.cs
new file mode 100644
index 00000000..0a9229fd
--- /dev/null
+++ b/test/Microsoft.Web.Mvc.Test/ModelBinding/Test/ArrayModelBinderTest.cs
@@ -0,0 +1,48 @@
+using System.Web.Mvc;
+using Microsoft.Web.UnitTestUtil;
+using Moq;
+using Xunit;
+
+namespace Microsoft.Web.Mvc.ModelBinding.Test
+{
+ public class ArrayModelBinderTest
+ {
+ [Fact]
+ public void BindModel()
+ {
+ // Arrange
+ ControllerContext controllerContext = new ControllerContext();
+ ExtensibleModelBindingContext bindingContext = new ExtensibleModelBindingContext
+ {
+ ModelMetadata = new EmptyModelMetadataProvider().GetMetadataForType(null, typeof(int[])),
+ ModelName = "someName",
+ ModelBinderProviders = new ModelBinderProviderCollection(),
+ ValueProvider = new SimpleValueProvider
+ {
+ { "someName[0]", "42" },
+ { "someName[1]", "84" }
+ }
+ };
+
+ Mock<IExtensibleModelBinder> mockIntBinder = new Mock<IExtensibleModelBinder>();
+ mockIntBinder
+ .Setup(o => o.BindModel(controllerContext, It.IsAny<ExtensibleModelBindingContext>()))
+ .Returns(
+ delegate(ControllerContext cc, ExtensibleModelBindingContext mbc)
+ {
+ mbc.Model = mbc.ValueProvider.GetValue(mbc.ModelName).ConvertTo(mbc.ModelType);
+ return true;
+ });
+ bindingContext.ModelBinderProviders.RegisterBinderForType(typeof(int), mockIntBinder.Object, false /* suppressPrefixCheck */);
+
+ // Act
+ bool retVal = new ArrayModelBinder<int>().BindModel(controllerContext, bindingContext);
+
+ // Assert
+ Assert.True(retVal);
+
+ int[] array = bindingContext.Model as int[];
+ Assert.Equal(new[] { 42, 84 }, array);
+ }
+ }
+}
diff --git a/test/Microsoft.Web.Mvc.Test/ModelBinding/Test/BinaryDataModelBinderProviderTest.cs b/test/Microsoft.Web.Mvc.Test/ModelBinding/Test/BinaryDataModelBinderProviderTest.cs
new file mode 100644
index 00000000..8c1563db
--- /dev/null
+++ b/test/Microsoft.Web.Mvc.Test/ModelBinding/Test/BinaryDataModelBinderProviderTest.cs
@@ -0,0 +1,159 @@
+using System.Data.Linq;
+using System.Web.Mvc;
+using Microsoft.Web.UnitTestUtil;
+using Xunit;
+
+namespace Microsoft.Web.Mvc.ModelBinding.Test
+{
+ public class BinaryDataModelBinderProviderTest
+ {
+ private static readonly byte[] _base64Bytes = new byte[] { 0x12, 0x20, 0x34, 0x40 };
+ private const string _base64String = "EiA0QA==";
+
+ [Fact]
+ public void BindModel_BadValue_Fails()
+ {
+ // Arrange
+ ExtensibleModelBindingContext bindingContext = new ExtensibleModelBindingContext
+ {
+ ModelMetadata = new EmptyModelMetadataProvider().GetMetadataForType(null, typeof(byte[])),
+ ModelName = "foo",
+ ValueProvider = new SimpleValueProvider
+ {
+ { "foo", "not base64 encoded!" }
+ }
+ };
+
+ BinaryDataModelBinderProvider binderProvider = new BinaryDataModelBinderProvider();
+
+ // Act
+ IExtensibleModelBinder binder = binderProvider.GetBinder(null, bindingContext);
+ bool retVal = binder.BindModel(null, bindingContext);
+
+ // Assert
+ Assert.False(retVal);
+ }
+
+ [Fact]
+ public void BindModel_EmptyValue_Fails()
+ {
+ // Arrange
+ ExtensibleModelBindingContext bindingContext = new ExtensibleModelBindingContext
+ {
+ ModelMetadata = new EmptyModelMetadataProvider().GetMetadataForType(null, typeof(byte[])),
+ ModelName = "foo",
+ ValueProvider = new SimpleValueProvider
+ {
+ { "foo", "" }
+ }
+ };
+
+ BinaryDataModelBinderProvider binderProvider = new BinaryDataModelBinderProvider();
+
+ // Act
+ IExtensibleModelBinder binder = binderProvider.GetBinder(null, bindingContext);
+ bool retVal = binder.BindModel(null, bindingContext);
+
+ // Assert
+ Assert.False(retVal);
+ }
+
+ [Fact]
+ public void BindModel_GoodValue_ByteArray_Succeeds()
+ {
+ // Arrange
+ ExtensibleModelBindingContext bindingContext = new ExtensibleModelBindingContext
+ {
+ ModelMetadata = new EmptyModelMetadataProvider().GetMetadataForType(null, typeof(byte[])),
+ ModelName = "foo",
+ ValueProvider = new SimpleValueProvider
+ {
+ { "foo", _base64String }
+ }
+ };
+
+ BinaryDataModelBinderProvider binderProvider = new BinaryDataModelBinderProvider();
+
+ // Act
+ IExtensibleModelBinder binder = binderProvider.GetBinder(null, bindingContext);
+ bool retVal = binder.BindModel(null, bindingContext);
+
+ // Assert
+ Assert.True(retVal);
+ Assert.Equal(_base64Bytes, (byte[])bindingContext.Model);
+ }
+
+ [Fact]
+ public void BindModel_GoodValue_LinqBinary_Succeeds()
+ {
+ // Arrange
+ ExtensibleModelBindingContext bindingContext = new ExtensibleModelBindingContext
+ {
+ ModelMetadata = new EmptyModelMetadataProvider().GetMetadataForType(null, typeof(Binary)),
+ ModelName = "foo",
+ ValueProvider = new SimpleValueProvider
+ {
+ { "foo", _base64String }
+ }
+ };
+
+ BinaryDataModelBinderProvider binderProvider = new BinaryDataModelBinderProvider();
+
+ // Act
+ IExtensibleModelBinder binder = binderProvider.GetBinder(null, bindingContext);
+ bool retVal = binder.BindModel(null, bindingContext);
+
+ // Assert
+ Assert.True(retVal);
+ Binary binaryModel = Assert.IsType<Binary>(bindingContext.Model);
+ Assert.Equal(_base64Bytes, binaryModel.ToArray());
+ }
+
+ [Fact]
+ public void BindModel_NoValue_Fails()
+ {
+ // Arrange
+ ExtensibleModelBindingContext bindingContext = new ExtensibleModelBindingContext
+ {
+ ModelMetadata = new EmptyModelMetadataProvider().GetMetadataForType(null, typeof(byte[])),
+ ModelName = "foo",
+ ValueProvider = new SimpleValueProvider
+ {
+ { "foo.bar", _base64String }
+ }
+ };
+
+ BinaryDataModelBinderProvider binderProvider = new BinaryDataModelBinderProvider();
+
+ // Act
+ IExtensibleModelBinder binder = binderProvider.GetBinder(null, bindingContext);
+ bool retVal = binder.BindModel(null, bindingContext);
+
+ // Assert
+ Assert.False(retVal);
+ }
+
+ [Fact]
+ public void GetBinder_WrongModelType_ReturnsNull()
+ {
+ // Arrange
+ ExtensibleModelBindingContext bindingContext = new ExtensibleModelBindingContext
+ {
+ ModelMetadata = new EmptyModelMetadataProvider().GetMetadataForType(null, typeof(object)),
+ ModelName = "foo",
+ ValueProvider = new SimpleValueProvider
+ {
+ { "foo", _base64String }
+ }
+ };
+
+ BinaryDataModelBinderProvider binderProvider = new BinaryDataModelBinderProvider();
+
+ // Act
+ IExtensibleModelBinder modelBinder = binderProvider.GetBinder(null, bindingContext);
+
+ // Assert
+ Assert.Null(modelBinder);
+ }
+ }
+}
diff --git a/test/Microsoft.Web.Mvc.Test/ModelBinding/Test/BindingBehaviorAttributeTest.cs b/test/Microsoft.Web.Mvc.Test/ModelBinding/Test/BindingBehaviorAttributeTest.cs
new file mode 100644
index 00000000..7f7de8cc
--- /dev/null
+++ b/test/Microsoft.Web.Mvc.Test/ModelBinding/Test/BindingBehaviorAttributeTest.cs
@@ -0,0 +1,51 @@
+using Xunit;
+
+namespace Microsoft.Web.Mvc.ModelBinding.Test
+{
+ public class BindingBehaviorAttributeTest
+ {
+ [Fact]
+ public void Behavior_Property()
+ {
+ // Arrange
+ BindingBehavior expectedBehavior = (BindingBehavior)(-20);
+
+ // Act
+ BindingBehaviorAttribute attr = new BindingBehaviorAttribute(expectedBehavior);
+
+ // Assert
+ Assert.Equal(expectedBehavior, attr.Behavior);
+ }
+
+ [Fact]
+ public void TypeId_ReturnsSameValue()
+ {
+ // Arrange
+ BindNeverAttribute neverAttr = new BindNeverAttribute();
+ BindRequiredAttribute requiredAttr = new BindRequiredAttribute();
+
+ // Act & assert
+ Assert.Same(neverAttr.TypeId, requiredAttr.TypeId);
+ }
+
+ [Fact]
+ public void BindNever_SetsBehavior()
+ {
+ // Act
+ BindingBehaviorAttribute attr = new BindNeverAttribute();
+
+ // Assert
+ Assert.Equal(BindingBehavior.Never, attr.Behavior);
+ }
+
+ [Fact]
+ public void BindRequired_SetsBehavior()
+ {
+ // Act
+ BindingBehaviorAttribute attr = new BindRequiredAttribute();
+
+ // Assert
+ Assert.Equal(BindingBehavior.Required, attr.Behavior);
+ }
+ }
+}
diff --git a/test/Microsoft.Web.Mvc.Test/ModelBinding/Test/CollectionModelBinderProviderTest.cs b/test/Microsoft.Web.Mvc.Test/ModelBinding/Test/CollectionModelBinderProviderTest.cs
new file mode 100644
index 00000000..db156ac1
--- /dev/null
+++ b/test/Microsoft.Web.Mvc.Test/ModelBinding/Test/CollectionModelBinderProviderTest.cs
@@ -0,0 +1,76 @@
+using System.Collections.Generic;
+using System.Web.Mvc;
+using Microsoft.Web.UnitTestUtil;
+using Xunit;
+
+namespace Microsoft.Web.Mvc.ModelBinding.Test
+{
+ public class CollectionModelBinderProviderTest
+ {
+ [Fact]
+ public void GetBinder_CorrectModelTypeAndValueProviderEntries_ReturnsBinder()
+ {
+ // Arrange
+ ExtensibleModelBindingContext bindingContext = new ExtensibleModelBindingContext
+ {
+ ModelMetadata = new EmptyModelMetadataProvider().GetMetadataForType(null, typeof(IEnumerable<int>)),
+ ModelName = "foo",
+ ValueProvider = new SimpleValueProvider
+ {
+ { "foo[0]", "42" },
+ }
+ };
+
+ CollectionModelBinderProvider binderProvider = new CollectionModelBinderProvider();
+
+ // Act
+ IExtensibleModelBinder binder = binderProvider.GetBinder(null, bindingContext);
+
+ // Assert
+ Assert.IsType<CollectionModelBinder<int>>(binder);
+ }
+
+ [Fact]
+ public void GetBinder_ModelTypeIsIncorrect_ReturnsNull()
+ {
+ // Arrange
+ ExtensibleModelBindingContext bindingContext = new ExtensibleModelBindingContext
+ {
+ ModelMetadata = new EmptyModelMetadataProvider().GetMetadataForType(null, typeof(int)),
+ ModelName = "foo",
+ ValueProvider = new SimpleValueProvider
+ {
+ { "foo[0]", "42" },
+ }
+ };
+
+ CollectionModelBinderProvider binderProvider = new CollectionModelBinderProvider();
+
+ // Act
+ IExtensibleModelBinder binder = binderProvider.GetBinder(null, bindingContext);
+
+ // Assert
+ Assert.Null(binder);
+ }
+
+ [Fact]
+ public void GetBinder_ValueProviderDoesNotContainPrefix_ReturnsNull()
+ {
+ // Arrange
+ ExtensibleModelBindingContext bindingContext = new ExtensibleModelBindingContext
+ {
+ ModelMetadata = new EmptyModelMetadataProvider().GetMetadataForType(null, typeof(IEnumerable<int>)),
+ ModelName = "foo",
+ ValueProvider = new SimpleValueProvider()
+ };
+
+ CollectionModelBinderProvider binderProvider = new CollectionModelBinderProvider();
+
+ // Act
+ IExtensibleModelBinder binder = binderProvider.GetBinder(null, bindingContext);
+
+ // Assert
+ Assert.Null(binder);
+ }
+ }
+}
diff --git a/test/Microsoft.Web.Mvc.Test/ModelBinding/Test/CollectionModelBinderTest.cs b/test/Microsoft.Web.Mvc.Test/ModelBinding/Test/CollectionModelBinderTest.cs
new file mode 100644
index 00000000..238ac466
--- /dev/null
+++ b/test/Microsoft.Web.Mvc.Test/ModelBinding/Test/CollectionModelBinderTest.cs
@@ -0,0 +1,245 @@
+using System.Collections.Generic;
+using System.Globalization;
+using System.Linq;
+using System.Web.Mvc;
+using Microsoft.Web.UnitTestUtil;
+using Moq;
+using Xunit;
+
+namespace Microsoft.Web.Mvc.ModelBinding.Test
+{
+ public class CollectionModelBinderTest
+ {
+ [Fact]
+ public void BindComplexCollectionFromIndexes_FiniteIndexes()
+ {
+ // Arrange
+ ControllerContext controllerContext = new ControllerContext();
+ CultureInfo culture = CultureInfo.GetCultureInfo("fr-FR");
+ ExtensibleModelBindingContext bindingContext = new ExtensibleModelBindingContext
+ {
+ ModelMetadata = new EmptyModelMetadataProvider().GetMetadataForType(null, typeof(int)),
+ ModelName = "someName",
+ ModelBinderProviders = new ModelBinderProviderCollection(),
+ ValueProvider = new SimpleValueProvider
+ {
+ { "someName[foo]", "42" },
+ { "someName[baz]", "200" }
+ }
+ };
+
+ Mock<IExtensibleModelBinder> mockIntBinder = new Mock<IExtensibleModelBinder>();
+ mockIntBinder
+ .Setup(o => o.BindModel(controllerContext, It.IsAny<ExtensibleModelBindingContext>()))
+ .Returns(
+ delegate(ControllerContext cc, ExtensibleModelBindingContext mbc)
+ {
+ mbc.Model = mbc.ValueProvider.GetValue(mbc.ModelName).ConvertTo(mbc.ModelType);
+ return true;
+ });
+ bindingContext.ModelBinderProviders.RegisterBinderForType(typeof(int), mockIntBinder.Object, false /* suppressPrefixCheck */);
+
+ // Act
+ List<int> boundCollection = CollectionModelBinder<int>.BindComplexCollectionFromIndexes(controllerContext, bindingContext, new[] { "foo", "bar", "baz" });
+
+ // Assert
+ Assert.Equal(new[] { 42, 0, 200 }, boundCollection.ToArray());
+ Assert.Equal(new[] { "someName[foo]", "someName[baz]" }, bindingContext.ValidationNode.ChildNodes.Select(o => o.ModelStateKey).ToArray());
+ }
+
+ [Fact]
+ public void BindComplexCollectionFromIndexes_InfiniteIndexes()
+ {
+ // Arrange
+ ControllerContext controllerContext = new ControllerContext();
+ CultureInfo culture = CultureInfo.GetCultureInfo("fr-FR");
+ ExtensibleModelBindingContext bindingContext = new ExtensibleModelBindingContext
+ {
+ ModelMetadata = new EmptyModelMetadataProvider().GetMetadataForType(null, typeof(int)),
+ ModelName = "someName",
+ ModelBinderProviders = new ModelBinderProviderCollection(),
+ ValueProvider = new SimpleValueProvider
+ {
+ { "someName[0]", "42" },
+ { "someName[1]", "100" },
+ { "someName[3]", "400" }
+ }
+ };
+
+ Mock<IExtensibleModelBinder> mockIntBinder = new Mock<IExtensibleModelBinder>();
+ mockIntBinder
+ .Setup(o => o.BindModel(controllerContext, It.IsAny<ExtensibleModelBindingContext>()))
+ .Returns(
+ delegate(ControllerContext cc, ExtensibleModelBindingContext mbc)
+ {
+ mbc.Model = mbc.ValueProvider.GetValue(mbc.ModelName).ConvertTo(mbc.ModelType);
+ return true;
+ });
+ bindingContext.ModelBinderProviders.RegisterBinderForType(typeof(int), mockIntBinder.Object, false /* suppressPrefixCheck */);
+
+ // Act
+ List<int> boundCollection = CollectionModelBinder<int>.BindComplexCollectionFromIndexes(controllerContext, bindingContext, null /* indexNames */);
+
+ // Assert
+ Assert.Equal(new[] { 42, 100 }, boundCollection.ToArray());
+ Assert.Equal(new[] { "someName[0]", "someName[1]" }, bindingContext.ValidationNode.ChildNodes.Select(o => o.ModelStateKey).ToArray());
+ }
+
+ [Fact]
+ public void BindModel_ComplexCollection()
+ {
+ // Arrange
+ ControllerContext controllerContext = new ControllerContext();
+ CultureInfo culture = CultureInfo.GetCultureInfo("fr-FR");
+ ExtensibleModelBindingContext bindingContext = new ExtensibleModelBindingContext
+ {
+ ModelMetadata = new EmptyModelMetadataProvider().GetMetadataForType(null, typeof(int)),
+ ModelName = "someName",
+ ModelBinderProviders = new ModelBinderProviderCollection(),
+ ValueProvider = new SimpleValueProvider
+ {
+ { "someName.index", new[] { "foo", "bar", "baz" } },
+ { "someName[foo]", "42" },
+ { "someName[bar]", "100" },
+ { "someName[baz]", "200" }
+ }
+ };
+
+ Mock<IExtensibleModelBinder> mockIntBinder = new Mock<IExtensibleModelBinder>();
+ mockIntBinder
+ .Setup(o => o.BindModel(controllerContext, It.IsAny<ExtensibleModelBindingContext>()))
+ .Returns(
+ delegate(ControllerContext cc, ExtensibleModelBindingContext mbc)
+ {
+ mbc.Model = mbc.ValueProvider.GetValue(mbc.ModelName).ConvertTo(mbc.ModelType);
+ return true;
+ });
+ bindingContext.ModelBinderProviders.RegisterBinderForType(typeof(int), mockIntBinder.Object, true /* suppressPrefixCheck */);
+
+ CollectionModelBinder<int> modelBinder = new CollectionModelBinder<int>();
+
+ // Act
+ bool retVal = modelBinder.BindModel(controllerContext, bindingContext);
+
+ // Assert
+ Assert.Equal(new[] { 42, 100, 200 }, ((List<int>)bindingContext.Model).ToArray());
+ }
+
+ [Fact]
+ public void BindModel_SimpleCollection()
+ {
+ // Arrange
+ ControllerContext controllerContext = new ControllerContext();
+ CultureInfo culture = CultureInfo.GetCultureInfo("fr-FR");
+ ExtensibleModelBindingContext bindingContext = new ExtensibleModelBindingContext
+ {
+ ModelMetadata = new EmptyModelMetadataProvider().GetMetadataForType(null, typeof(int)),
+ ModelName = "someName",
+ ModelBinderProviders = new ModelBinderProviderCollection(),
+ ValueProvider = new SimpleValueProvider
+ {
+ { "someName", new[] { "42", "100", "200" } }
+ }
+ };
+
+ Mock<IExtensibleModelBinder> mockIntBinder = new Mock<IExtensibleModelBinder>();
+ mockIntBinder
+ .Setup(o => o.BindModel(controllerContext, It.IsAny<ExtensibleModelBindingContext>()))
+ .Returns(
+ delegate(ControllerContext cc, ExtensibleModelBindingContext mbc)
+ {
+ mbc.Model = mbc.ValueProvider.GetValue(mbc.ModelName).ConvertTo(mbc.ModelType);
+ return true;
+ });
+ bindingContext.ModelBinderProviders.RegisterBinderForType(typeof(int), mockIntBinder.Object, true /* suppressPrefixCheck */);
+
+ CollectionModelBinder<int> modelBinder = new CollectionModelBinder<int>();
+
+ // Act
+ bool retVal = modelBinder.BindModel(controllerContext, bindingContext);
+
+ // Assert
+ Assert.True(retVal);
+ Assert.Equal(new[] { 42, 100, 200 }, ((List<int>)bindingContext.Model).ToArray());
+ }
+
+ [Fact]
+ public void BindSimpleCollection_RawValueIsEmptyCollection_ReturnsEmptyList()
+ {
+ // Act
+ List<int> boundCollection = CollectionModelBinder<int>.BindSimpleCollection(null, null, new object[0], null);
+
+ // Assert
+ Assert.NotNull(boundCollection);
+ Assert.Empty(boundCollection);
+ }
+
+ [Fact]
+ public void BindSimpleCollection_RawValueIsNull_ReturnsNull()
+ {
+ // Act
+ List<int> boundCollection = CollectionModelBinder<int>.BindSimpleCollection(null, null, null, null);
+
+ // Assert
+ Assert.Null(boundCollection);
+ }
+
+ [Fact]
+ public void BindSimpleCollection_SubBinderDoesNotExist()
+ {
+ // Arrange
+ ControllerContext controllerContext = new ControllerContext();
+ CultureInfo culture = CultureInfo.GetCultureInfo("fr-FR");
+ ExtensibleModelBindingContext bindingContext = new ExtensibleModelBindingContext
+ {
+ ModelMetadata = new EmptyModelMetadataProvider().GetMetadataForType(null, typeof(int)),
+ ModelName = "someName",
+ ModelBinderProviders = new ModelBinderProviderCollection(),
+ ValueProvider = new SimpleValueProvider()
+ };
+
+ // Act
+ List<int> boundCollection = CollectionModelBinder<int>.BindSimpleCollection(controllerContext, bindingContext, new int[1], culture);
+
+ // Assert
+ Assert.Equal(new[] { 0 }, boundCollection.ToArray());
+ Assert.Empty(bindingContext.ValidationNode.ChildNodes);
+ }
+
+ [Fact]
+ public void BindSimpleCollection_SubBindingSucceeds()
+ {
+ // Arrange
+ ControllerContext controllerContext = new ControllerContext();
+ CultureInfo culture = CultureInfo.GetCultureInfo("fr-FR");
+ ExtensibleModelBindingContext bindingContext = new ExtensibleModelBindingContext
+ {
+ ModelMetadata = new EmptyModelMetadataProvider().GetMetadataForType(null, typeof(int)),
+ ModelName = "someName",
+ ModelBinderProviders = new ModelBinderProviderCollection(),
+ ValueProvider = new SimpleValueProvider()
+ };
+
+ ModelValidationNode childValidationNode = null;
+ Mock<IExtensibleModelBinder> mockIntBinder = new Mock<IExtensibleModelBinder>();
+ mockIntBinder
+ .Setup(o => o.BindModel(controllerContext, It.IsAny<ExtensibleModelBindingContext>()))
+ .Returns(
+ delegate(ControllerContext cc, ExtensibleModelBindingContext mbc)
+ {
+ Assert.Equal("someName", mbc.ModelName);
+ childValidationNode = mbc.ValidationNode;
+ mbc.Model = 42;
+ return true;
+ });
+ bindingContext.ModelBinderProviders.RegisterBinderForType(typeof(int), mockIntBinder.Object, true /* suppressPrefixCheck */);
+
+ // Act
+ List<int> boundCollection = CollectionModelBinder<int>.BindSimpleCollection(controllerContext, bindingContext, new int[1], culture);
+
+ // Assert
+ Assert.Equal(new[] { 42 }, boundCollection.ToArray());
+ Assert.Equal(new[] { childValidationNode }, bindingContext.ValidationNode.ChildNodes.ToArray());
+ }
+ }
+}
diff --git a/test/Microsoft.Web.Mvc.Test/ModelBinding/Test/CollectionModelBinderUtilTest.cs b/test/Microsoft.Web.Mvc.Test/ModelBinding/Test/CollectionModelBinderUtilTest.cs
new file mode 100644
index 00000000..073508ae
--- /dev/null
+++ b/test/Microsoft.Web.Mvc.Test/ModelBinding/Test/CollectionModelBinderUtilTest.cs
@@ -0,0 +1,391 @@
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Linq;
+using System.Web.Mvc;
+using Xunit;
+
+namespace Microsoft.Web.Mvc.ModelBinding.Test
+{
+ public class CollectionModelBinderUtilTest
+ {
+ [Fact]
+ public void CreateOrReplaceCollection_OriginalModelImmutable_CreatesNewInstance()
+ {
+ // Arrange
+ ExtensibleModelBindingContext bindingContext = new ExtensibleModelBindingContext
+ {
+ ModelMetadata = new EmptyModelMetadataProvider().GetMetadataForType(() => new ReadOnlyCollection<int>(new int[0]), typeof(ICollection<int>))
+ };
+
+ // Act
+ CollectionModelBinderUtil.CreateOrReplaceCollection(bindingContext, new[] { 10, 20, 30 }, () => new List<int>());
+
+ // Assert
+ int[] newModel = (bindingContext.Model as ICollection<int>).ToArray();
+ Assert.Equal(new[] { 10, 20, 30 }, newModel);
+ }
+
+ [Fact]
+ public void CreateOrReplaceCollection_OriginalModelMutable_UpdatesOriginalInstance()
+ {
+ // Arrange
+ List<int> originalInstance = new List<int> { 10, 20, 30 };
+ ExtensibleModelBindingContext bindingContext = new ExtensibleModelBindingContext
+ {
+ ModelMetadata = new EmptyModelMetadataProvider().GetMetadataForType(() => originalInstance, typeof(ICollection<int>))
+ };
+
+ // Act
+ CollectionModelBinderUtil.CreateOrReplaceCollection(bindingContext, new[] { 40, 50, 60 }, () => new List<int>());
+
+ // Assert
+ Assert.Same(originalInstance, bindingContext.Model);
+ Assert.Equal(new[] { 40, 50, 60 }, originalInstance.ToArray());
+ }
+
+ [Fact]
+ public void CreateOrReplaceCollection_OriginalModelNotCollection_CreatesNewInstance()
+ {
+ // Arrange
+ ExtensibleModelBindingContext bindingContext = new ExtensibleModelBindingContext
+ {
+ ModelMetadata = new EmptyModelMetadataProvider().GetMetadataForType(null, typeof(ICollection<int>))
+ };
+
+ // Act
+ CollectionModelBinderUtil.CreateOrReplaceCollection(bindingContext, new[] { 10, 20, 30 }, () => new List<int>());
+
+ // Assert
+ int[] newModel = (bindingContext.Model as ICollection<int>).ToArray();
+ Assert.Equal(new[] { 10, 20, 30 }, newModel);
+ }
+
+ [Fact]
+ public void CreateOrReplaceDictionary_DisallowsDuplicateKeys()
+ {
+ // Arrange
+ ExtensibleModelBindingContext bindingContext = new ExtensibleModelBindingContext
+ {
+ ModelMetadata = new EmptyModelMetadataProvider().GetMetadataForType(null, typeof(Dictionary<string, int>))
+ };
+
+ // Act
+ CollectionModelBinderUtil.CreateOrReplaceDictionary(
+ bindingContext,
+ new[]
+ {
+ new KeyValuePair<string, int>("forty-two", 40),
+ new KeyValuePair<string, int>("forty-two", 2),
+ new KeyValuePair<string, int>("forty-two", 42)
+ },
+ () => new Dictionary<string, int>());
+
+ // Assert
+ IDictionary<string, int> newModel = bindingContext.Model as IDictionary<string, int>;
+ Assert.Equal(new[] { "forty-two" }, newModel.Keys.ToArray());
+ Assert.Equal(42, newModel["forty-two"]);
+ }
+
+ [Fact]
+ public void CreateOrReplaceDictionary_DisallowsNullKeys()
+ {
+ // Arrange
+ ExtensibleModelBindingContext bindingContext = new ExtensibleModelBindingContext
+ {
+ ModelMetadata = new EmptyModelMetadataProvider().GetMetadataForType(null, typeof(Dictionary<string, int>))
+ };
+
+ // Act
+ CollectionModelBinderUtil.CreateOrReplaceDictionary(
+ bindingContext,
+ new[]
+ {
+ new KeyValuePair<string, int>("forty-two", 42),
+ new KeyValuePair<string, int>(null, 84)
+ },
+ () => new Dictionary<string, int>());
+
+ // Assert
+ IDictionary<string, int> newModel = bindingContext.Model as IDictionary<string, int>;
+ Assert.Equal(new[] { "forty-two" }, newModel.Keys.ToArray());
+ Assert.Equal(42, newModel["forty-two"]);
+ }
+
+ [Fact]
+ public void CreateOrReplaceDictionary_OriginalModelImmutable_CreatesNewInstance()
+ {
+ // Arrange
+ ReadOnlyDictionary<string, string> originalModel = new ReadOnlyDictionary<string, string>();
+
+ ExtensibleModelBindingContext bindingContext = new ExtensibleModelBindingContext
+ {
+ ModelMetadata = new EmptyModelMetadataProvider().GetMetadataForType(() => originalModel, typeof(IDictionary<string, string>))
+ };
+
+ // Act
+ CollectionModelBinderUtil.CreateOrReplaceDictionary(
+ bindingContext,
+ new Dictionary<string, string>
+ {
+ { "Hello", "World" }
+ },
+ () => new Dictionary<string, string>());
+
+ // Assert
+ IDictionary<string, string> newModel = bindingContext.Model as IDictionary<string, string>;
+ Assert.NotSame(originalModel, newModel);
+ Assert.Equal(new[] { "Hello" }, newModel.Keys.ToArray());
+ Assert.Equal("World", newModel["Hello"]);
+ }
+
+ [Fact]
+ public void CreateOrReplaceDictionary_OriginalModelMutable_UpdatesOriginalInstance()
+ {
+ // Arrange
+ Dictionary<string, string> originalInstance = new Dictionary<string, string>
+ {
+ { "dog", "Canidae" },
+ { "cat", "Felidae" }
+ };
+ ExtensibleModelBindingContext bindingContext = new ExtensibleModelBindingContext
+ {
+ ModelMetadata = new EmptyModelMetadataProvider().GetMetadataForType(() => originalInstance, typeof(IDictionary<string, string>))
+ };
+
+ // Act
+ CollectionModelBinderUtil.CreateOrReplaceDictionary(
+ bindingContext,
+ new Dictionary<string, string>
+ {
+ { "horse", "Equidae" },
+ { "bear", "Ursidae" }
+ },
+ () => new Dictionary<string, string>());
+
+ // Assert
+ Assert.Same(originalInstance, bindingContext.Model);
+ Assert.Equal(new[] { "horse", "bear" }, originalInstance.Keys.ToArray());
+ Assert.Equal("Equidae", originalInstance["horse"]);
+ Assert.Equal("Ursidae", originalInstance["bear"]);
+ }
+
+ [Fact]
+ public void CreateOrReplaceDictionary_OriginalModelNotDictionary_CreatesNewInstance()
+ {
+ // Arrange
+ ExtensibleModelBindingContext bindingContext = new ExtensibleModelBindingContext
+ {
+ ModelMetadata = new EmptyModelMetadataProvider().GetMetadataForType(null, typeof(IDictionary<string, string>))
+ };
+
+ // Act
+ CollectionModelBinderUtil.CreateOrReplaceDictionary(
+ bindingContext,
+ new Dictionary<string, string>
+ {
+ { "horse", "Equidae" },
+ { "bear", "Ursidae" }
+ },
+ () => new Dictionary<string, string>());
+
+ // Assert
+ IDictionary<string, string> newModel = bindingContext.Model as IDictionary<string, string>;
+ Assert.Equal(new[] { "horse", "bear" }, newModel.Keys.ToArray());
+ Assert.Equal("Equidae", newModel["horse"]);
+ Assert.Equal("Ursidae", newModel["bear"]);
+ }
+
+ [Fact]
+ public void GetIndexNamesFromValueProviderResult_ValueProviderResultIsNull_ReturnsNull()
+ {
+ // Act
+ IEnumerable<string> indexNames = CollectionModelBinderUtil.GetIndexNamesFromValueProviderResult(null);
+
+ // Assert
+ Assert.Null(indexNames);
+ }
+
+ [Fact]
+ public void GetIndexNamesFromValueProviderResult_ValueProviderResultReturnsEmptyArray_ReturnsNull()
+ {
+ // Arrange
+ ValueProviderResult vpResult = new ValueProviderResult(new string[0], "", null);
+
+ // Act
+ IEnumerable<string> indexNames = CollectionModelBinderUtil.GetIndexNamesFromValueProviderResult(vpResult);
+
+ // Assert
+ Assert.Null(indexNames);
+ }
+
+ [Fact]
+ public void GetIndexNamesFromValueProviderResult_ValueProviderResultReturnsNonEmptyArray_ReturnsArray()
+ {
+ // Arrange
+ ValueProviderResult vpResult = new ValueProviderResult(new[] { "foo", "bar", "baz" }, "foo,bar,baz", null);
+
+ // Act
+ IEnumerable<string> indexNames = CollectionModelBinderUtil.GetIndexNamesFromValueProviderResult(vpResult);
+
+ // Assert
+ Assert.NotNull(indexNames);
+ Assert.Equal(new[] { "foo", "bar", "baz" }, indexNames.ToArray());
+ }
+
+ [Fact]
+ public void GetIndexNamesFromValueProviderResult_ValueProviderResultReturnsNull_ReturnsNull()
+ {
+ // Arrange
+ ValueProviderResult vpResult = new ValueProviderResult(null, null, null);
+
+ // Act
+ IEnumerable<string> indexNames = CollectionModelBinderUtil.GetIndexNamesFromValueProviderResult(vpResult);
+
+ // Assert
+ Assert.Null(indexNames);
+ }
+
+ [Fact]
+ public void GetTypeArgumentsForUpdatableGenericCollection_ModelTypeNotGeneric_Fail()
+ {
+ // Arrange
+ ModelMetadata modelMetadata = new EmptyModelMetadataProvider().GetMetadataForType(null, typeof(int));
+
+ // Act
+ Type[] typeArguments = CollectionModelBinderUtil.GetTypeArgumentsForUpdatableGenericCollection(null, null, modelMetadata);
+
+ // Assert
+ Assert.Null(typeArguments);
+ }
+
+ [Fact]
+ public void GetTypeArgumentsForUpdatableGenericCollection_ModelTypeOpenGeneric_Fail()
+ {
+ // Arrange
+ ModelMetadata modelMetadata = new EmptyModelMetadataProvider().GetMetadataForType(null, typeof(IList<>));
+
+ // Act
+ Type[] typeArguments = CollectionModelBinderUtil.GetTypeArgumentsForUpdatableGenericCollection(null, null, modelMetadata);
+
+ // Assert
+ Assert.Null(typeArguments);
+ }
+
+ [Fact]
+ public void GetTypeArgumentsForUpdatableGenericCollection_ModelTypeWrongNumberOfGenericArguments_Fail()
+ {
+ // Arrange
+ ModelMetadata modelMetadata = new EmptyModelMetadataProvider().GetMetadataForType(null, typeof(KeyValuePair<int, string>));
+
+ // Act
+ Type[] typeArguments = CollectionModelBinderUtil.GetTypeArgumentsForUpdatableGenericCollection(typeof(ICollection<>), null, modelMetadata);
+
+ // Assert
+ Assert.Null(typeArguments);
+ }
+
+ [Fact]
+ public void GetTypeArgumentsForUpdatableGenericCollection_ReadOnlyReference_ModelInstanceImmutable_Valid()
+ {
+ // Arrange
+ ModelMetadata modelMetadata = new EmptyModelMetadataProvider().GetMetadataForType(() => new int[0], typeof(IList<int>));
+ modelMetadata.IsReadOnly = true;
+
+ // Act
+ Type[] typeArguments = CollectionModelBinderUtil.GetTypeArgumentsForUpdatableGenericCollection(typeof(IList<>), typeof(List<>), modelMetadata);
+
+ // Assert
+ Assert.Null(typeArguments);
+ }
+
+ [Fact]
+ public void GetTypeArgumentsForUpdatableGenericCollection_ReadOnlyReference_ModelInstanceMutable_Valid()
+ {
+ // Arrange
+ ModelMetadata modelMetadata = new EmptyModelMetadataProvider().GetMetadataForType(() => new List<int>(), typeof(IList<int>));
+ modelMetadata.IsReadOnly = true;
+
+ // Act
+ Type[] typeArguments = CollectionModelBinderUtil.GetTypeArgumentsForUpdatableGenericCollection(typeof(IList<>), typeof(List<>), modelMetadata);
+
+ // Assert
+ Assert.Equal(new[] { typeof(int) }, typeArguments);
+ }
+
+ [Fact]
+ public void GetTypeArgumentsForUpdatableGenericCollection_ReadOnlyReference_ModelInstanceOfWrongType_Fail()
+ {
+ // Arrange
+ ModelMetadata modelMetadata = new EmptyModelMetadataProvider().GetMetadataForType(() => new HashSet<int>(), typeof(ICollection<int>));
+ modelMetadata.IsReadOnly = true;
+
+ // Act
+ Type[] typeArguments = CollectionModelBinderUtil.GetTypeArgumentsForUpdatableGenericCollection(typeof(IList<>), typeof(List<>), modelMetadata);
+
+ // Assert
+ // HashSet<> is not an IList<>, so we can't update
+ Assert.Null(typeArguments);
+ }
+
+ [Fact]
+ public void GetTypeArgumentsForUpdatableGenericCollection_ReadOnlyReference_ModelIsNull_Fail()
+ {
+ // Arrange
+ ModelMetadata modelMetadata = new EmptyModelMetadataProvider().GetMetadataForType(null, typeof(IList<int>));
+ modelMetadata.IsReadOnly = true;
+
+ // Act
+ Type[] typeArguments = CollectionModelBinderUtil.GetTypeArgumentsForUpdatableGenericCollection(typeof(ICollection<>), typeof(List<>), modelMetadata);
+
+ // Assert
+ Assert.Null(typeArguments);
+ }
+
+ [Fact]
+ public void GetTypeArgumentsForUpdatableGenericCollection_ReadWriteReference_NewInstanceAssignableToModelType_Success()
+ {
+ // Arrange
+ ModelMetadata modelMetadata = new EmptyModelMetadataProvider().GetMetadataForType(null, typeof(IList<int>));
+ modelMetadata.IsReadOnly = false;
+
+ // Act
+ Type[] typeArguments = CollectionModelBinderUtil.GetTypeArgumentsForUpdatableGenericCollection(typeof(ICollection<>), typeof(List<>), modelMetadata);
+
+ // Assert
+ Assert.Equal(new[] { typeof(int) }, typeArguments);
+ }
+
+ [Fact]
+ public void GetTypeArgumentsForUpdatableGenericCollection_ReadWriteReference_NewInstanceNotAssignableToModelType_MutableInstance_Success()
+ {
+ // Arrange
+ ModelMetadata modelMetadata = new EmptyModelMetadataProvider().GetMetadataForType(() => new Collection<int>(), typeof(Collection<int>));
+ modelMetadata.IsReadOnly = false;
+
+ // Act
+ Type[] typeArguments = CollectionModelBinderUtil.GetTypeArgumentsForUpdatableGenericCollection(typeof(ICollection<>), typeof(List<>), modelMetadata);
+
+ // Assert
+ Assert.Equal(new[] { typeof(int) }, typeArguments);
+ }
+
+ [Fact]
+ public void GetZeroBasedIndexes()
+ {
+ // Act
+ string[] indexes = CollectionModelBinderUtil.GetZeroBasedIndexes().Take(5).ToArray();
+
+ // Assert
+ Assert.Equal(new[] { "0", "1", "2", "3", "4" }, indexes);
+ }
+
+ private class ReadOnlyDictionary<TKey, TValue> : Dictionary<TKey, TValue>, ICollection<KeyValuePair<TKey, TValue>>
+ {
+ bool ICollection<KeyValuePair<TKey, TValue>>.IsReadOnly
+ {
+ get { return true; }
+ }
+ }
+ }
+}
diff --git a/test/Microsoft.Web.Mvc.Test/ModelBinding/Test/ComplexModelDtoModelBinderProviderTest.cs b/test/Microsoft.Web.Mvc.Test/ModelBinding/Test/ComplexModelDtoModelBinderProviderTest.cs
new file mode 100644
index 00000000..0b80d699
--- /dev/null
+++ b/test/Microsoft.Web.Mvc.Test/ModelBinding/Test/ComplexModelDtoModelBinderProviderTest.cs
@@ -0,0 +1,45 @@
+using System;
+using System.Web.Mvc;
+using Xunit;
+
+namespace Microsoft.Web.Mvc.ModelBinding.Test
+{
+ public class ComplexModelDtoModelBinderProviderTest
+ {
+ [Fact]
+ public void GetBinder_TypeDoesNotMatch_ReturnsNull()
+ {
+ // Arrange
+ ComplexModelDtoModelBinderProvider provider = new ComplexModelDtoModelBinderProvider();
+ ExtensibleModelBindingContext bindingContext = GetBindingContext(typeof(object));
+
+ // Act
+ IExtensibleModelBinder binder = provider.GetBinder(null, bindingContext);
+
+ // Assert
+ Assert.Null(binder);
+ }
+
+ [Fact]
+ public void GetBinder_TypeMatches_ReturnsBinder()
+ {
+ // Arrange
+ ComplexModelDtoModelBinderProvider provider = new ComplexModelDtoModelBinderProvider();
+ ExtensibleModelBindingContext bindingContext = GetBindingContext(typeof(ComplexModelDto));
+
+ // Act
+ IExtensibleModelBinder binder = provider.GetBinder(null, bindingContext);
+
+ // Assert
+ Assert.IsType<ComplexModelDtoModelBinder>(binder);
+ }
+
+ private static ExtensibleModelBindingContext GetBindingContext(Type modelType)
+ {
+ return new ExtensibleModelBindingContext
+ {
+ ModelMetadata = new EmptyModelMetadataProvider().GetMetadataForType(() => null, modelType)
+ };
+ }
+ }
+}
diff --git a/test/Microsoft.Web.Mvc.Test/ModelBinding/Test/ComplexModelDtoModelBinderTest.cs b/test/Microsoft.Web.Mvc.Test/ModelBinding/Test/ComplexModelDtoModelBinderTest.cs
new file mode 100644
index 00000000..74bb8174
--- /dev/null
+++ b/test/Microsoft.Web.Mvc.Test/ModelBinding/Test/ComplexModelDtoModelBinderTest.cs
@@ -0,0 +1,106 @@
+using System;
+using System.Linq;
+using System.Web.Mvc;
+using Moq;
+using Xunit;
+
+namespace Microsoft.Web.Mvc.ModelBinding.Test
+{
+ public class ComplexModelDtoModelBinderTest
+ {
+ [Fact]
+ public void BindModel()
+ {
+ // Arrange
+ ControllerContext controllerContext = new ControllerContext();
+ MyModel model = new MyModel();
+ ModelMetadata modelMetadata = new EmptyModelMetadataProvider().GetMetadataForType(() => model, typeof(MyModel));
+ ComplexModelDto dto = new ComplexModelDto(modelMetadata, modelMetadata.Properties);
+
+ Mock<IExtensibleModelBinder> mockStringBinder = new Mock<IExtensibleModelBinder>();
+ mockStringBinder
+ .Setup(b => b.BindModel(controllerContext, It.IsAny<ExtensibleModelBindingContext>()))
+ .Returns(
+ delegate(ControllerContext cc, ExtensibleModelBindingContext mbc)
+ {
+ Assert.Equal(typeof(string), mbc.ModelType);
+ Assert.Equal("theModel.StringProperty", mbc.ModelName);
+ mbc.ValidationNode = new ModelValidationNode(mbc.ModelMetadata, "theModel.StringProperty");
+ mbc.Model = "someStringValue";
+ return true;
+ });
+
+ Mock<IExtensibleModelBinder> mockIntBinder = new Mock<IExtensibleModelBinder>();
+ mockIntBinder
+ .Setup(b => b.BindModel(controllerContext, It.IsAny<ExtensibleModelBindingContext>()))
+ .Returns(
+ delegate(ControllerContext cc, ExtensibleModelBindingContext mbc)
+ {
+ Assert.Equal(typeof(int), mbc.ModelType);
+ Assert.Equal("theModel.IntProperty", mbc.ModelName);
+ mbc.ValidationNode = new ModelValidationNode(mbc.ModelMetadata, "theModel.IntProperty");
+ mbc.Model = 42;
+ return true;
+ });
+
+ Mock<IExtensibleModelBinder> mockDateTimeBinder = new Mock<IExtensibleModelBinder>();
+ mockDateTimeBinder
+ .Setup(b => b.BindModel(controllerContext, It.IsAny<ExtensibleModelBindingContext>()))
+ .Returns(
+ delegate(ControllerContext cc, ExtensibleModelBindingContext mbc)
+ {
+ Assert.Equal(typeof(DateTime), mbc.ModelType);
+ Assert.Equal("theModel.DateTimeProperty", mbc.ModelName);
+ return false;
+ });
+
+ ModelBinderProviderCollection binders = new ModelBinderProviderCollection();
+ binders.RegisterBinderForType(typeof(string), mockStringBinder.Object, true /* suppressPrefixCheck */);
+ binders.RegisterBinderForType(typeof(int), mockIntBinder.Object, true /* suppressPrefixCheck */);
+ binders.RegisterBinderForType(typeof(DateTime), mockDateTimeBinder.Object, true /* suppressPrefixCheck */);
+
+ ExtensibleModelBindingContext parentBindingContext = new ExtensibleModelBindingContext
+ {
+ ModelMetadata = new EmptyModelMetadataProvider().GetMetadataForType(() => dto, typeof(ComplexModelDto)),
+ ModelName = "theModel",
+ ModelBinderProviders = binders
+ };
+
+ ComplexModelDtoModelBinder binder = new ComplexModelDtoModelBinder();
+
+ // Act
+ bool retVal = binder.BindModel(controllerContext, parentBindingContext);
+
+ // Assert
+ Assert.True(retVal);
+ Assert.Equal(dto, parentBindingContext.Model);
+
+ ComplexModelDtoResult stringDtoResult = dto.Results[dto.PropertyMetadata.Where(m => m.ModelType == typeof(string)).First()];
+ Assert.Equal("someStringValue", stringDtoResult.Model);
+ Assert.Equal("theModel.StringProperty", stringDtoResult.ValidationNode.ModelStateKey);
+
+ ComplexModelDtoResult intDtoResult = dto.Results[dto.PropertyMetadata.Where(m => m.ModelType == typeof(int)).First()];
+ Assert.Equal(42, intDtoResult.Model);
+ Assert.Equal("theModel.IntProperty", intDtoResult.ValidationNode.ModelStateKey);
+
+ ComplexModelDtoResult dateTimeDtoResult = dto.Results[dto.PropertyMetadata.Where(m => m.ModelType == typeof(DateTime)).First()];
+ Assert.Null(dateTimeDtoResult);
+ }
+
+ private static ModelBindingContext GetBindingContext(Type modelType)
+ {
+ return new ModelBindingContext
+ {
+ ModelMetadata = new ModelMetadata(new Mock<ModelMetadataProvider>().Object, null, null, modelType, "SomeProperty")
+ };
+ }
+
+ private sealed class MyModel
+ {
+ public string StringProperty { get; set; }
+ public int IntProperty { get; set; }
+ public object ObjectProperty { get; set; } // no binding should happen since no registered binder
+ public DateTime DateTimeProperty { get; set; } // registered binder returns false
+ }
+ }
+}
diff --git a/test/Microsoft.Web.Mvc.Test/ModelBinding/Test/ComplexModelDtoResultTest.cs b/test/Microsoft.Web.Mvc.Test/ModelBinding/Test/ComplexModelDtoResultTest.cs
new file mode 100644
index 00000000..ebb45189
--- /dev/null
+++ b/test/Microsoft.Web.Mvc.Test/ModelBinding/Test/ComplexModelDtoResultTest.cs
@@ -0,0 +1,38 @@
+using System.Web.Mvc;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace Microsoft.Web.Mvc.ModelBinding.Test
+{
+ public class ComplexModelDtoResultTest
+ {
+ [Fact]
+ public void Constructor_ThrowsIfValidationNodeIsNull()
+ {
+ // Act & assert
+ Assert.ThrowsArgumentNull(
+ delegate { new ComplexModelDtoResult("some string", null); }, "validationNode");
+ }
+
+ [Fact]
+ public void Constructor_SetsProperties()
+ {
+ // Arrange
+ ModelValidationNode validationNode = GetValidationNode();
+
+ // Act
+ ComplexModelDtoResult result = new ComplexModelDtoResult("some string", validationNode);
+
+ // Assert
+ Assert.Equal("some string", result.Model);
+ Assert.Equal(validationNode, result.ValidationNode);
+ }
+
+ private static ModelValidationNode GetValidationNode()
+ {
+ EmptyModelMetadataProvider provider = new EmptyModelMetadataProvider();
+ ModelMetadata metadata = provider.GetMetadataForType(null, typeof(object));
+ return new ModelValidationNode(metadata, "someKey");
+ }
+ }
+}
diff --git a/test/Microsoft.Web.Mvc.Test/ModelBinding/Test/ComplexModelDtoTest.cs b/test/Microsoft.Web.Mvc.Test/ModelBinding/Test/ComplexModelDtoTest.cs
new file mode 100644
index 00000000..a8cfe6b7
--- /dev/null
+++ b/test/Microsoft.Web.Mvc.Test/ModelBinding/Test/ComplexModelDtoTest.cs
@@ -0,0 +1,50 @@
+using System.Linq;
+using System.Web.Mvc;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace Microsoft.Web.Mvc.ModelBinding.Test
+{
+ public class ComplexModelDtoTest
+ {
+ [Fact]
+ public void ConstructorThrowsIfModelMetadataIsNull()
+ {
+ // Act & assert
+ Assert.ThrowsArgumentNull(
+ delegate { new ComplexModelDto(null, Enumerable.Empty<ModelMetadata>()); }, "modelMetadata");
+ }
+
+ [Fact]
+ public void ConstructorThrowsIfPropertyMetadataIsNull()
+ {
+ // Arrange
+ ModelMetadata modelMetadata = GetModelMetadata();
+
+ // Act & assert
+ Assert.ThrowsArgumentNull(
+ delegate { new ComplexModelDto(modelMetadata, null); }, "propertyMetadata");
+ }
+
+ [Fact]
+ public void ConstructorSetsProperties()
+ {
+ // Arrange
+ ModelMetadata modelMetadata = GetModelMetadata();
+ ModelMetadata[] propertyMetadata = new ModelMetadata[0];
+
+ // Act
+ ComplexModelDto dto = new ComplexModelDto(modelMetadata, propertyMetadata);
+
+ // Assert
+ Assert.Equal(modelMetadata, dto.ModelMetadata);
+ Assert.Equal(propertyMetadata, dto.PropertyMetadata.ToArray());
+ Assert.Empty(dto.Results);
+ }
+
+ private static ModelMetadata GetModelMetadata()
+ {
+ return new ModelMetadata(new EmptyModelMetadataProvider(), typeof(object), null, typeof(object), "PropertyName");
+ }
+ }
+}
diff --git a/test/Microsoft.Web.Mvc.Test/ModelBinding/Test/DictionaryModelBinderProviderTest.cs b/test/Microsoft.Web.Mvc.Test/ModelBinding/Test/DictionaryModelBinderProviderTest.cs
new file mode 100644
index 00000000..d64b2e14
--- /dev/null
+++ b/test/Microsoft.Web.Mvc.Test/ModelBinding/Test/DictionaryModelBinderProviderTest.cs
@@ -0,0 +1,76 @@
+using System.Collections.Generic;
+using System.Web.Mvc;
+using Microsoft.Web.UnitTestUtil;
+using Xunit;
+
+namespace Microsoft.Web.Mvc.ModelBinding.Test
+{
+ public class DictionaryModelBinderProviderTest
+ {
+ [Fact]
+ public void GetBinder_CorrectModelTypeAndValueProviderEntries_ReturnsBinder()
+ {
+ // Arrange
+ ExtensibleModelBindingContext bindingContext = new ExtensibleModelBindingContext
+ {
+ ModelMetadata = new EmptyModelMetadataProvider().GetMetadataForType(null, typeof(IDictionary<int, string>)),
+ ModelName = "foo",
+ ValueProvider = new SimpleValueProvider
+ {
+ { "foo[0]", "42" },
+ }
+ };
+
+ DictionaryModelBinderProvider binderProvider = new DictionaryModelBinderProvider();
+
+ // Act
+ IExtensibleModelBinder binder = binderProvider.GetBinder(null, bindingContext);
+
+ // Assert
+ Assert.IsType<DictionaryModelBinder<int, string>>(binder);
+ }
+
+ [Fact]
+ public void GetBinder_ModelTypeIsIncorrect_ReturnsNull()
+ {
+ // Arrange
+ ExtensibleModelBindingContext bindingContext = new ExtensibleModelBindingContext
+ {
+ ModelMetadata = new EmptyModelMetadataProvider().GetMetadataForType(null, typeof(int)),
+ ModelName = "foo",
+ ValueProvider = new SimpleValueProvider
+ {
+ { "foo[0]", "42" },
+ }
+ };
+
+ DictionaryModelBinderProvider binderProvider = new DictionaryModelBinderProvider();
+
+ // Act
+ IExtensibleModelBinder binder = binderProvider.GetBinder(null, bindingContext);
+
+ // Assert
+ Assert.Null(binder);
+ }
+
+ [Fact]
+ public void GetBinder_ValueProviderDoesNotContainPrefix_ReturnsNull()
+ {
+ // Arrange
+ ExtensibleModelBindingContext bindingContext = new ExtensibleModelBindingContext
+ {
+ ModelMetadata = new EmptyModelMetadataProvider().GetMetadataForType(null, typeof(IDictionary<int, string>)),
+ ModelName = "foo",
+ ValueProvider = new SimpleValueProvider()
+ };
+
+ DictionaryModelBinderProvider binderProvider = new DictionaryModelBinderProvider();
+
+ // Act
+ IExtensibleModelBinder binder = binderProvider.GetBinder(null, bindingContext);
+
+ // Assert
+ Assert.Null(binder);
+ }
+ }
+}
diff --git a/test/Microsoft.Web.Mvc.Test/ModelBinding/Test/DictionaryModelBinderTest.cs b/test/Microsoft.Web.Mvc.Test/ModelBinding/Test/DictionaryModelBinderTest.cs
new file mode 100644
index 00000000..d9b571ae
--- /dev/null
+++ b/test/Microsoft.Web.Mvc.Test/ModelBinding/Test/DictionaryModelBinderTest.cs
@@ -0,0 +1,52 @@
+using System.Collections.Generic;
+using System.Web.Mvc;
+using Microsoft.Web.UnitTestUtil;
+using Moq;
+using Xunit;
+
+namespace Microsoft.Web.Mvc.ModelBinding.Test
+{
+ public class DictionaryModelBinderTest
+ {
+ [Fact]
+ public void BindModel()
+ {
+ // Arrange
+ ControllerContext controllerContext = new ControllerContext();
+ ExtensibleModelBindingContext bindingContext = new ExtensibleModelBindingContext
+ {
+ ModelMetadata = new EmptyModelMetadataProvider().GetMetadataForType(null, typeof(IDictionary<int, string>)),
+ ModelName = "someName",
+ ModelBinderProviders = new ModelBinderProviderCollection(),
+ ValueProvider = new SimpleValueProvider
+ {
+ { "someName[0]", new KeyValuePair<int, string>(42, "forty-two") },
+ { "someName[1]", new KeyValuePair<int, string>(84, "eighty-four") }
+ }
+ };
+
+ Mock<IExtensibleModelBinder> mockKvpBinder = new Mock<IExtensibleModelBinder>();
+ mockKvpBinder
+ .Setup(o => o.BindModel(controllerContext, It.IsAny<ExtensibleModelBindingContext>()))
+ .Returns(
+ delegate(ControllerContext cc, ExtensibleModelBindingContext mbc)
+ {
+ mbc.Model = mbc.ValueProvider.GetValue(mbc.ModelName).ConvertTo(mbc.ModelType);
+ return true;
+ });
+ bindingContext.ModelBinderProviders.RegisterBinderForType(typeof(KeyValuePair<int, string>), mockKvpBinder.Object, false /* suppressPrefixCheck */);
+
+ // Act
+ bool retVal = new DictionaryModelBinder<int, string>().BindModel(controllerContext, bindingContext);
+
+ // Assert
+ Assert.True(retVal);
+
+ var dictionary = Assert.IsAssignableFrom<IDictionary<int, string>>(bindingContext.Model);
+ Assert.NotNull(dictionary);
+ Assert.Equal(2, dictionary.Count);
+ Assert.Equal("forty-two", dictionary[42]);
+ Assert.Equal("eighty-four", dictionary[84]);
+ }
+ }
+}
diff --git a/test/Microsoft.Web.Mvc.Test/ModelBinding/Test/ExtensibleModelBinderAdapterTest.cs b/test/Microsoft.Web.Mvc.Test/ModelBinding/Test/ExtensibleModelBinderAdapterTest.cs
new file mode 100644
index 00000000..41e0f8c0
--- /dev/null
+++ b/test/Microsoft.Web.Mvc.Test/ModelBinding/Test/ExtensibleModelBinderAdapterTest.cs
@@ -0,0 +1,210 @@
+using System;
+using System.Collections.Generic;
+using System.Web.Mvc;
+using Microsoft.Web.UnitTestUtil;
+using Moq;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace Microsoft.Web.Mvc.ModelBinding.Test
+{
+ public class ExtensibleModelBinderAdapterTest
+ {
+ [Fact]
+ public void BindModel_PropertyFilterIsSet_Throws()
+ {
+ // Arrange
+ ControllerContext controllerContext = GetControllerContext();
+
+ ModelBindingContext bindingContext = new ModelBindingContext
+ {
+ FallbackToEmptyPrefix = true,
+ ModelMetadata = new DataAnnotationsModelMetadataProvider().GetMetadataForType(null, typeof(SimpleModel)),
+ PropertyFilter = (new BindAttribute { Include = "FirstName " }).IsPropertyAllowed
+ };
+
+ ModelBinderProviderCollection binderProviders = new ModelBinderProviderCollection();
+ ExtensibleModelBinderAdapter shimBinder = new ExtensibleModelBinderAdapter(binderProviders);
+
+ // Act & assert
+
+ Assert.Throws<InvalidOperationException>(
+ delegate { shimBinder.BindModel(controllerContext, bindingContext); },
+ @"The new model binding system cannot be used when a property whitelist or blacklist has been specified in [Bind] or via the call to UpdateModel() / TryUpdateModel(). Use the [BindRequired] and [BindNever] attributes on the model type or its properties instead.");
+ }
+
+ [Fact]
+ public void BindModel_SuccessfulBind_RunsValidationAndReturnsModel()
+ {
+ // Arrange
+ ControllerContext controllerContext = GetControllerContext();
+ bool validationCalled = false;
+
+ ModelBindingContext bindingContext = new ModelBindingContext
+ {
+ FallbackToEmptyPrefix = true,
+ ModelMetadata = new DataAnnotationsModelMetadataProvider().GetMetadataForType(null, typeof(int)),
+ ModelName = "someName",
+ ModelState = controllerContext.Controller.ViewData.ModelState,
+ PropertyFilter = _ => true,
+ ValueProvider = new SimpleValueProvider
+ {
+ { "someName", "dummyValue" }
+ }
+ };
+
+ Mock<IExtensibleModelBinder> mockIntBinder = new Mock<IExtensibleModelBinder>();
+ mockIntBinder
+ .Setup(o => o.BindModel(controllerContext, It.IsAny<ExtensibleModelBindingContext>()))
+ .Returns(
+ delegate(ControllerContext cc, ExtensibleModelBindingContext mbc)
+ {
+ Assert.Same(bindingContext.ModelMetadata, mbc.ModelMetadata);
+ Assert.Equal("someName", mbc.ModelName);
+ Assert.Same(bindingContext.ValueProvider, mbc.ValueProvider);
+
+ mbc.Model = 42;
+ mbc.ValidationNode.Validating += delegate { validationCalled = true; };
+ return true;
+ });
+
+ ModelBinderProviderCollection binderProviders = new ModelBinderProviderCollection();
+ binderProviders.RegisterBinderForType(typeof(int), mockIntBinder.Object, false /* suppressPrefixCheck */);
+ ExtensibleModelBinderAdapter shimBinder = new ExtensibleModelBinderAdapter(binderProviders);
+
+ // Act
+ object retVal = shimBinder.BindModel(controllerContext, bindingContext);
+
+ // Assert
+ Assert.Equal(42, retVal);
+ Assert.True(validationCalled);
+ Assert.True(bindingContext.ModelState.IsValid);
+ }
+
+ [Fact]
+ public void BindModel_SuccessfulBind_ComplexTypeFallback_RunsValidationAndReturnsModel()
+ {
+ // Arrange
+ ControllerContext controllerContext = GetControllerContext();
+
+ bool validationCalled = false;
+ List<int> expectedModel = new List<int> { 1, 2, 3, 4, 5 };
+
+ ModelBindingContext bindingContext = new ModelBindingContext
+ {
+ FallbackToEmptyPrefix = true,
+ ModelMetadata = new DataAnnotationsModelMetadataProvider().GetMetadataForType(null, typeof(List<int>)),
+ ModelName = "someName",
+ ModelState = controllerContext.Controller.ViewData.ModelState,
+ PropertyFilter = _ => true,
+ ValueProvider = new SimpleValueProvider
+ {
+ { "someOtherName", "dummyValue" }
+ }
+ };
+
+ Mock<IExtensibleModelBinder> mockIntBinder = new Mock<IExtensibleModelBinder>();
+ mockIntBinder
+ .Setup(o => o.BindModel(controllerContext, It.IsAny<ExtensibleModelBindingContext>()))
+ .Returns(
+ delegate(ControllerContext cc, ExtensibleModelBindingContext mbc)
+ {
+ Assert.Same(bindingContext.ModelMetadata, mbc.ModelMetadata);
+ Assert.Equal("", mbc.ModelName);
+ Assert.Same(bindingContext.ValueProvider, mbc.ValueProvider);
+
+ mbc.Model = expectedModel;
+ mbc.ValidationNode.Validating += delegate { validationCalled = true; };
+ return true;
+ });
+
+ ModelBinderProviderCollection binderProviders = new ModelBinderProviderCollection();
+ binderProviders.RegisterBinderForType(typeof(List<int>), mockIntBinder.Object, false /* suppressPrefixCheck */);
+ ExtensibleModelBinderAdapter shimBinder = new ExtensibleModelBinderAdapter(binderProviders);
+
+ // Act
+ object retVal = shimBinder.BindModel(controllerContext, bindingContext);
+
+ // Assert
+ Assert.Equal(expectedModel, retVal);
+ Assert.True(validationCalled);
+ Assert.True(bindingContext.ModelState.IsValid);
+ }
+
+ [Fact]
+ public void BindModel_UnsuccessfulBind_BinderFails_ReturnsNull()
+ {
+ // Arrange
+ ControllerContext controllerContext = GetControllerContext();
+ Mock<IExtensibleModelBinder> mockListBinder = new Mock<IExtensibleModelBinder>();
+ mockListBinder.Setup(o => o.BindModel(controllerContext, It.IsAny<ExtensibleModelBindingContext>())).Returns(false).Verifiable();
+
+ ModelBinderProviderCollection binderProviders = new ModelBinderProviderCollection();
+ binderProviders.RegisterBinderForType(typeof(List<int>), mockListBinder.Object, true /* suppressPrefixCheck */);
+ ExtensibleModelBinderAdapter shimBinder = new ExtensibleModelBinderAdapter(binderProviders);
+
+ ModelBindingContext bindingContext = new ModelBindingContext
+ {
+ FallbackToEmptyPrefix = false,
+ ModelMetadata = new EmptyModelMetadataProvider().GetMetadataForType(null, typeof(List<int>)),
+ ModelState = controllerContext.Controller.ViewData.ModelState
+ };
+
+ // Act
+ object retVal = shimBinder.BindModel(controllerContext, bindingContext);
+
+ // Assert
+ Assert.Null(retVal);
+ Assert.True(bindingContext.ModelState.IsValid);
+ mockListBinder.Verify();
+ }
+
+ [Fact]
+ public void BindModel_UnsuccessfulBind_SimpleTypeNoFallback_ReturnsNull()
+ {
+ // Arrange
+ ControllerContext controllerContext = GetControllerContext();
+ Mock<ModelBinderProvider> mockBinderProvider = new Mock<ModelBinderProvider>();
+ mockBinderProvider.Setup(o => o.GetBinder(controllerContext, It.IsAny<ExtensibleModelBindingContext>())).Returns((IExtensibleModelBinder)null).Verifiable();
+ ModelBinderProviderCollection binderProviders = new ModelBinderProviderCollection
+ {
+ mockBinderProvider.Object
+ };
+ ExtensibleModelBinderAdapter shimBinder = new ExtensibleModelBinderAdapter(binderProviders);
+
+ ModelBindingContext bindingContext = new ModelBindingContext
+ {
+ FallbackToEmptyPrefix = true,
+ ModelMetadata = new DataAnnotationsModelMetadataProvider().GetMetadataForType(null, typeof(int)),
+ ModelState = controllerContext.Controller.ViewData.ModelState
+ };
+
+ // Act
+ object retVal = shimBinder.BindModel(controllerContext, bindingContext);
+
+ // Assert
+ Assert.Null(retVal);
+ Assert.True(bindingContext.ModelState.IsValid);
+ mockBinderProvider.Verify();
+ mockBinderProvider.Verify(o => o.GetBinder(controllerContext, It.IsAny<ExtensibleModelBindingContext>()), Times.AtMostOnce());
+ }
+
+ private static ControllerContext GetControllerContext()
+ {
+ return new ControllerContext
+ {
+ Controller = new SimpleController()
+ };
+ }
+
+ private class SimpleController : Controller
+ {
+ }
+
+ private class SimpleModel
+ {
+ public string FirstName { get; set; }
+ public string LastName { get; set; }
+ }
+ }
+}
diff --git a/test/Microsoft.Web.Mvc.Test/ModelBinding/Test/ExtensibleModelBindingContextTest.cs b/test/Microsoft.Web.Mvc.Test/ModelBinding/Test/ExtensibleModelBindingContextTest.cs
new file mode 100644
index 00000000..e746bcb9
--- /dev/null
+++ b/test/Microsoft.Web.Mvc.Test/ModelBinding/Test/ExtensibleModelBindingContextTest.cs
@@ -0,0 +1,136 @@
+using System;
+using System.Web.Mvc;
+using System.Web.TestUtil;
+using Microsoft.Web.UnitTestUtil;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace Microsoft.Web.Mvc.ModelBinding.Test
+{
+ public class ExtensibleModelBindingContextTest
+ {
+ [Fact]
+ public void CopyConstructor()
+ {
+ // Arrange
+ ExtensibleModelBindingContext originalBindingContext = new ExtensibleModelBindingContext
+ {
+ ModelMetadata = new EmptyModelMetadataProvider().GetMetadataForType(null, typeof(object)),
+ ModelName = "theName",
+ ModelState = new ModelStateDictionary(),
+ ValueProvider = new SimpleValueProvider()
+ };
+
+ // Act
+ ExtensibleModelBindingContext newBindingContext = new ExtensibleModelBindingContext(originalBindingContext);
+
+ // Assert
+ Assert.Null(newBindingContext.ModelMetadata);
+ Assert.Equal("", newBindingContext.ModelName);
+ Assert.Equal(originalBindingContext.ModelState, newBindingContext.ModelState);
+ Assert.Equal(originalBindingContext.ValueProvider, newBindingContext.ValueProvider);
+ }
+
+ [Fact]
+ public void ModelBinderProvidersProperty()
+ {
+ // Arrange
+ ExtensibleModelBindingContext bindingContext = new ExtensibleModelBindingContext();
+
+ // Act & assert
+ MemberHelper.TestPropertyWithDefaultInstance(bindingContext, "ModelBinderProviders", new ModelBinderProviderCollection(), ModelBinderProviders.Providers);
+ }
+
+ [Fact]
+ public void ModelProperty()
+ {
+ // Arrange
+ ExtensibleModelBindingContext bindingContext = new ExtensibleModelBindingContext
+ {
+ ModelMetadata = new EmptyModelMetadataProvider().GetMetadataForType(null, typeof(int))
+ };
+
+ // Act & assert
+ MemberHelper.TestPropertyValue(bindingContext, "Model", 42);
+ }
+
+ [Fact]
+ public void ModelProperty_ThrowsIfModelMetadataDoesNotExist()
+ {
+ // Arrange
+ ExtensibleModelBindingContext bindingContext = new ExtensibleModelBindingContext();
+
+ // Act & assert
+ Assert.Throws<InvalidOperationException>(
+ delegate { bindingContext.Model = null; },
+ "The ModelMetadata property must be set before accessing this property.");
+ }
+
+ [Fact]
+ public void ModelNameProperty()
+ {
+ // Arrange
+ ExtensibleModelBindingContext bindingContext = new ExtensibleModelBindingContext();
+
+ // Act & assert
+ Assert.Reflection.StringProperty(bindingContext, (context) => context.ModelName, String.Empty);
+ }
+
+ [Fact]
+ public void ModelStateProperty()
+ {
+ // Arrange
+ ExtensibleModelBindingContext bindingContext = new ExtensibleModelBindingContext();
+ ModelStateDictionary modelState = new ModelStateDictionary();
+
+ // Act & assert
+ MemberHelper.TestPropertyWithDefaultInstance(bindingContext, "ModelState", modelState);
+ }
+
+ [Fact]
+ public void ModelAndModelTypeAreFedFromModelMetadata()
+ {
+ // Act
+ ExtensibleModelBindingContext bindingContext = new ExtensibleModelBindingContext
+ {
+ ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(() => 42, typeof(int))
+ };
+
+ // Assert
+ Assert.Equal(42, bindingContext.Model);
+ Assert.Equal(typeof(int), bindingContext.ModelType);
+ }
+
+ [Fact]
+ public void ValidationNodeProperty()
+ {
+ // Act
+ ExtensibleModelBindingContext bindingContext = new ExtensibleModelBindingContext
+ {
+ ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(() => 42, typeof(int))
+ };
+
+ // Act & assert
+ MemberHelper.TestPropertyWithDefaultInstance(bindingContext, "ValidationNode", new ModelValidationNode(bindingContext.ModelMetadata, "someName"));
+ }
+
+ [Fact]
+ public void ValidationNodeProperty_DefaultValues()
+ {
+ // Act
+ ExtensibleModelBindingContext bindingContext = new ExtensibleModelBindingContext
+ {
+ ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(() => 42, typeof(int)),
+ ModelName = "theInt"
+ };
+
+ // Act
+ ModelValidationNode validationNode = bindingContext.ValidationNode;
+
+ // Assert
+ Assert.NotNull(validationNode);
+ Assert.Equal(bindingContext.ModelMetadata, validationNode.ModelMetadata);
+ Assert.Equal(bindingContext.ModelName, validationNode.ModelStateKey);
+ }
+ }
+}
diff --git a/test/Microsoft.Web.Mvc.Test/ModelBinding/Test/GenericModelBinderProviderTest.cs b/test/Microsoft.Web.Mvc.Test/ModelBinding/Test/GenericModelBinderProviderTest.cs
new file mode 100644
index 00000000..9a8a60f7
--- /dev/null
+++ b/test/Microsoft.Web.Mvc.Test/ModelBinding/Test/GenericModelBinderProviderTest.cs
@@ -0,0 +1,245 @@
+using System;
+using System.Collections.Generic;
+using System.Web.Mvc;
+using Microsoft.Web.UnitTestUtil;
+using Moq;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace Microsoft.Web.Mvc.ModelBinding.Test
+{
+ public class GenericModelBinderProviderTest
+ {
+ [Fact]
+ public void Constructor_WithFactory_ThrowsIfModelBinderFactoryIsNull()
+ {
+ // Act & assert
+ Assert.ThrowsArgumentNull(
+ delegate { new GenericModelBinderProvider(typeof(List<>), (Func<Type[], IExtensibleModelBinder>)null); }, "modelBinderFactory");
+ }
+
+ [Fact]
+ public void Constructor_WithFactory_ThrowsIfModelTypeIsNotOpenGeneric()
+ {
+ // Act & assert
+ Assert.Throws<ArgumentException>(
+ delegate { new GenericModelBinderProvider(typeof(List<int>), _ => null); },
+ @"The type 'System.Collections.Generic.List`1[System.Int32]' is not an open generic type.
+Parameter name: modelType");
+ }
+
+ [Fact]
+ public void Constructor_WithFactory_ThrowsIfModelTypeIsNull()
+ {
+ // Act & assert
+ Assert.ThrowsArgumentNull(
+ delegate { new GenericModelBinderProvider(null, _ => null); }, "modelType");
+ }
+
+ [Fact]
+ public void Constructor_WithInstance_ThrowsIfModelBinderIsNull()
+ {
+ // Act & assert
+ Assert.ThrowsArgumentNull(
+ delegate { new GenericModelBinderProvider(typeof(List<>), (IExtensibleModelBinder)null); }, "modelBinder");
+ }
+
+ [Fact]
+ public void Constructor_WithInstance_ThrowsIfModelTypeIsNotOpenGeneric()
+ {
+ // Act & assert
+ Assert.Throws<ArgumentException>(
+ delegate { new GenericModelBinderProvider(typeof(List<int>), new MutableObjectModelBinder()); },
+ @"The type 'System.Collections.Generic.List`1[System.Int32]' is not an open generic type.
+Parameter name: modelType");
+ }
+
+ [Fact]
+ public void Constructor_WithInstance_ThrowsIfModelTypeIsNull()
+ {
+ // Act & assert
+ Assert.ThrowsArgumentNull(
+ delegate { new GenericModelBinderProvider(null, new MutableObjectModelBinder()); }, "modelType");
+ }
+
+ [Fact]
+ public void Constructor_WithType_ThrowsIfModelBinderTypeIsNotModelBinder()
+ {
+ // Act & assert
+ Assert.Throws<ArgumentException>(
+ delegate { new GenericModelBinderProvider(typeof(List<>), typeof(string)); },
+ @"The type 'System.String' does not implement the interface 'Microsoft.Web.Mvc.ModelBinding.IExtensibleModelBinder'.
+Parameter name: modelBinderType");
+ }
+
+ [Fact]
+ public void Constructor_WithType_ThrowsIfModelBinderTypeIsNull()
+ {
+ // Act & assert
+ Assert.ThrowsArgumentNull(
+ delegate { new GenericModelBinderProvider(typeof(List<>), (Type)null); }, "modelBinderType");
+ }
+
+ [Fact]
+ public void Constructor_WithType_ThrowsIfModelBinderTypeTypeArgumentMismatch()
+ {
+ // Act & assert
+ Assert.Throws<ArgumentException>(
+ delegate { new GenericModelBinderProvider(typeof(List<>), typeof(DictionaryModelBinder<,>)); },
+ @"The open model type 'System.Collections.Generic.List`1[T]' has 1 generic type argument(s), but the open binder type 'Microsoft.Web.Mvc.ModelBinding.DictionaryModelBinder`2[TKey,TValue]' has 2 generic type argument(s). The binder type must not be an open generic type or must have the same number of generic arguments as the open model type.
+Parameter name: modelBinderType");
+ }
+
+ [Fact]
+ public void Constructor_WithType_ThrowsIfModelTypeIsNotOpenGeneric()
+ {
+ // Act & assert
+ Assert.Throws<ArgumentException>(
+ delegate { new GenericModelBinderProvider(typeof(List<int>), typeof(MutableObjectModelBinder)); },
+ @"The type 'System.Collections.Generic.List`1[System.Int32]' is not an open generic type.
+Parameter name: modelType");
+ }
+
+ [Fact]
+ public void Constructor_WithType_ThrowsIfModelTypeIsNull()
+ {
+ // Act & assert
+ Assert.ThrowsArgumentNull(
+ delegate { new GenericModelBinderProvider(null, typeof(MutableObjectModelBinder)); }, "modelType");
+ }
+
+ [Fact]
+ public void GetBinder_TypeDoesNotMatch_ModelTypeIsInterface_ReturnsNull()
+ {
+ // Arrange
+ GenericModelBinderProvider provider = new GenericModelBinderProvider(typeof(IEnumerable<>), typeof(CollectionModelBinder<>))
+ {
+ SuppressPrefixCheck = true
+ };
+ ExtensibleModelBindingContext bindingContext = GetBindingContext(typeof(object));
+
+ // Act
+ IExtensibleModelBinder binder = provider.GetBinder(null, bindingContext);
+
+ // Assert
+ Assert.Null(binder);
+ }
+
+ [Fact]
+ public void GetBinder_TypeDoesNotMatch_ModelTypeIsNotInterface_ReturnsNull()
+ {
+ // Arrange
+ GenericModelBinderProvider provider = new GenericModelBinderProvider(typeof(List<>), typeof(CollectionModelBinder<>))
+ {
+ SuppressPrefixCheck = true
+ };
+ ExtensibleModelBindingContext bindingContext = GetBindingContext(typeof(object));
+
+ // Act
+ IExtensibleModelBinder binder = provider.GetBinder(null, bindingContext);
+
+ // Assert
+ Assert.Null(binder);
+ }
+
+ [Fact]
+ public void GetBinder_TypeMatches_PrefixNotFound_ReturnsNull()
+ {
+ // Arrange
+ IExtensibleModelBinder binderInstance = new Mock<IExtensibleModelBinder>().Object;
+ GenericModelBinderProvider provider = new GenericModelBinderProvider(typeof(List<>), binderInstance);
+
+ ExtensibleModelBindingContext bindingContext = GetBindingContext(typeof(List<int>));
+ bindingContext.ValueProvider = new SimpleValueProvider();
+
+ // Act
+ IExtensibleModelBinder returnedBinder = provider.GetBinder(null, bindingContext);
+
+ // Assert
+ Assert.Null(returnedBinder);
+ }
+
+ [Fact]
+ public void GetBinder_TypeMatches_Success_Factory_ReturnsBinder()
+ {
+ // Arrange
+ IExtensibleModelBinder binderInstance = new Mock<IExtensibleModelBinder>().Object;
+
+ Func<Type[], IExtensibleModelBinder> binderFactory = typeArguments =>
+ {
+ Assert.Equal(new[] { typeof(int) }, typeArguments);
+ return binderInstance;
+ };
+
+ GenericModelBinderProvider provider = new GenericModelBinderProvider(typeof(IList<>), binderFactory)
+ {
+ SuppressPrefixCheck = true
+ };
+
+ ExtensibleModelBindingContext bindingContext = GetBindingContext(typeof(List<int>));
+
+ // Act
+ IExtensibleModelBinder returnedBinder = provider.GetBinder(null, bindingContext);
+
+ // Assert
+ Assert.Same(binderInstance, returnedBinder);
+ }
+
+ [Fact]
+ public void GetBinder_TypeMatches_Success_Instance_ReturnsBinder()
+ {
+ // Arrange
+ IExtensibleModelBinder binderInstance = new Mock<IExtensibleModelBinder>().Object;
+
+ GenericModelBinderProvider provider = new GenericModelBinderProvider(typeof(List<>), binderInstance)
+ {
+ SuppressPrefixCheck = true
+ };
+
+ ExtensibleModelBindingContext bindingContext = GetBindingContext(typeof(List<int>));
+
+ // Act
+ IExtensibleModelBinder returnedBinder = provider.GetBinder(null, bindingContext);
+
+ // Assert
+ Assert.Same(binderInstance, returnedBinder);
+ }
+
+ [Fact]
+ public void GetBinder_TypeMatches_Success_TypeActivation_ReturnsBinder()
+ {
+ // Arrange
+ GenericModelBinderProvider provider = new GenericModelBinderProvider(typeof(List<>), typeof(CollectionModelBinder<>))
+ {
+ SuppressPrefixCheck = true
+ };
+
+ ExtensibleModelBindingContext bindingContext = GetBindingContext(typeof(List<int>));
+
+ // Act
+ IExtensibleModelBinder returnedBinder = provider.GetBinder(null, bindingContext);
+
+ // Assert
+ Assert.IsType<CollectionModelBinder<int>>(returnedBinder);
+ }
+
+ [Fact]
+ public void GetBinderThrowsIfBindingContextIsNull()
+ {
+ // Arrange
+ GenericModelBinderProvider provider = new GenericModelBinderProvider(typeof(IEnumerable<>), typeof(CollectionModelBinder<>));
+
+ // Act & assert
+ Assert.ThrowsArgumentNull(
+ delegate { provider.GetBinder(null, null); }, "bindingContext");
+ }
+
+ private static ExtensibleModelBindingContext GetBindingContext(Type modelType)
+ {
+ return new ExtensibleModelBindingContext
+ {
+ ModelMetadata = new EmptyModelMetadataProvider().GetMetadataForType(() => null, modelType)
+ };
+ }
+ }
+}
diff --git a/test/Microsoft.Web.Mvc.Test/ModelBinding/Test/KeyValuePairModelBinderProviderTest.cs b/test/Microsoft.Web.Mvc.Test/ModelBinding/Test/KeyValuePairModelBinderProviderTest.cs
new file mode 100644
index 00000000..79db5694
--- /dev/null
+++ b/test/Microsoft.Web.Mvc.Test/ModelBinding/Test/KeyValuePairModelBinderProviderTest.cs
@@ -0,0 +1,104 @@
+using System.Collections.Generic;
+using System.Web.Mvc;
+using Microsoft.Web.UnitTestUtil;
+using Xunit;
+
+namespace Microsoft.Web.Mvc.ModelBinding.Test
+{
+ public class KeyValuePairModelBinderProviderTest
+ {
+ [Fact]
+ public void GetBinder_CorrectModelTypeAndValueProviderEntries_ReturnsBinder()
+ {
+ // Arrange
+ ExtensibleModelBindingContext bindingContext = new ExtensibleModelBindingContext
+ {
+ ModelMetadata = new EmptyModelMetadataProvider().GetMetadataForType(null, typeof(KeyValuePair<int, string>)),
+ ModelName = "foo",
+ ValueProvider = new SimpleValueProvider
+ {
+ { "foo.key", 42 },
+ { "foo.value", "someValue" }
+ }
+ };
+
+ KeyValuePairModelBinderProvider binderProvider = new KeyValuePairModelBinderProvider();
+
+ // Act
+ IExtensibleModelBinder binder = binderProvider.GetBinder(null, bindingContext);
+
+ // Assert
+ Assert.IsType<KeyValuePairModelBinder<int, string>>(binder);
+ }
+
+ [Fact]
+ public void GetBinder_ModelTypeIsIncorrect_ReturnsNull()
+ {
+ // Arrange
+ ExtensibleModelBindingContext bindingContext = new ExtensibleModelBindingContext
+ {
+ ModelMetadata = new EmptyModelMetadataProvider().GetMetadataForType(null, typeof(List<int>)),
+ ModelName = "foo",
+ ValueProvider = new SimpleValueProvider
+ {
+ { "foo.key", 42 },
+ { "foo.value", "someValue" }
+ }
+ };
+
+ KeyValuePairModelBinderProvider binderProvider = new KeyValuePairModelBinderProvider();
+
+ // Act
+ IExtensibleModelBinder binder = binderProvider.GetBinder(null, bindingContext);
+
+ // Assert
+ Assert.Null(binder);
+ }
+
+ [Fact]
+ public void GetBinder_ValueProviderDoesNotContainKeyProperty_ReturnsNull()
+ {
+ // Arrange
+ ExtensibleModelBindingContext bindingContext = new ExtensibleModelBindingContext
+ {
+ ModelMetadata = new EmptyModelMetadataProvider().GetMetadataForType(null, typeof(KeyValuePair<int, string>)),
+ ModelName = "foo",
+ ValueProvider = new SimpleValueProvider
+ {
+ { "foo.value", "someValue" }
+ }
+ };
+
+ KeyValuePairModelBinderProvider binderProvider = new KeyValuePairModelBinderProvider();
+
+ // Act
+ IExtensibleModelBinder binder = binderProvider.GetBinder(null, bindingContext);
+
+ // Assert
+ Assert.Null(binder);
+ }
+
+ [Fact]
+ public void GetBinder_ValueProviderDoesNotContainValueProperty_ReturnsNull()
+ {
+ // Arrange
+ ExtensibleModelBindingContext bindingContext = new ExtensibleModelBindingContext
+ {
+ ModelMetadata = new EmptyModelMetadataProvider().GetMetadataForType(null, typeof(KeyValuePair<int, string>)),
+ ModelName = "foo",
+ ValueProvider = new SimpleValueProvider
+ {
+ { "foo.key", 42 }
+ }
+ };
+
+ KeyValuePairModelBinderProvider binderProvider = new KeyValuePairModelBinderProvider();
+
+ // Act
+ IExtensibleModelBinder binder = binderProvider.GetBinder(null, bindingContext);
+
+ // Assert
+ Assert.Null(binder);
+ }
+ }
+}
diff --git a/test/Microsoft.Web.Mvc.Test/ModelBinding/Test/KeyValuePairModelBinderTest.cs b/test/Microsoft.Web.Mvc.Test/ModelBinding/Test/KeyValuePairModelBinderTest.cs
new file mode 100644
index 00000000..35fd4a14
--- /dev/null
+++ b/test/Microsoft.Web.Mvc.Test/ModelBinding/Test/KeyValuePairModelBinderTest.cs
@@ -0,0 +1,116 @@
+using System.Collections.Generic;
+using System.Linq;
+using System.Web.Mvc;
+using Microsoft.Web.UnitTestUtil;
+using Moq;
+using Xunit;
+
+namespace Microsoft.Web.Mvc.ModelBinding.Test
+{
+ public class KeyValuePairModelBinderTest
+ {
+ [Fact]
+ public void BindModel_MissingKey_ReturnsFalse()
+ {
+ // Arrange
+ ControllerContext controllerContext = new ControllerContext();
+ ExtensibleModelBindingContext bindingContext = new ExtensibleModelBindingContext
+ {
+ ModelMetadata = new EmptyModelMetadataProvider().GetMetadataForType(null, typeof(KeyValuePair<int, string>)),
+ ModelName = "someName",
+ ModelBinderProviders = new ModelBinderProviderCollection(),
+ ValueProvider = new SimpleValueProvider()
+ };
+
+ KeyValuePairModelBinder<int, string> binder = new KeyValuePairModelBinder<int, string>();
+
+ // Act
+ bool retVal = binder.BindModel(controllerContext, bindingContext);
+
+ // Assert
+ Assert.False(retVal);
+ Assert.Null(bindingContext.Model);
+ Assert.Empty(bindingContext.ValidationNode.ChildNodes);
+ }
+
+ [Fact]
+ public void BindModel_MissingValue_ReturnsTrue()
+ {
+ // Arrange
+ ControllerContext controllerContext = new ControllerContext();
+ ExtensibleModelBindingContext bindingContext = new ExtensibleModelBindingContext
+ {
+ ModelMetadata = new EmptyModelMetadataProvider().GetMetadataForType(null, typeof(KeyValuePair<int, string>)),
+ ModelName = "someName",
+ ModelBinderProviders = new ModelBinderProviderCollection(),
+ ValueProvider = new SimpleValueProvider()
+ };
+
+ Mock<IExtensibleModelBinder> mockIntBinder = new Mock<IExtensibleModelBinder>();
+ mockIntBinder
+ .Setup(o => o.BindModel(controllerContext, It.IsAny<ExtensibleModelBindingContext>()))
+ .Returns(
+ delegate(ControllerContext cc, ExtensibleModelBindingContext mbc)
+ {
+ mbc.Model = 42;
+ return true;
+ });
+ bindingContext.ModelBinderProviders.RegisterBinderForType(typeof(int), mockIntBinder.Object, true /* suppressPrefixCheck */);
+
+ KeyValuePairModelBinder<int, string> binder = new KeyValuePairModelBinder<int, string>();
+
+ // Act
+ bool retVal = binder.BindModel(controllerContext, bindingContext);
+
+ // Assert
+ Assert.True(retVal);
+ Assert.Null(bindingContext.Model);
+ Assert.Equal(new[] { "someName.key" }, bindingContext.ValidationNode.ChildNodes.Select(n => n.ModelStateKey).ToArray());
+ }
+
+ [Fact]
+ public void BindModel_SubBindingSucceeds()
+ {
+ // Arrange
+ ControllerContext controllerContext = new ControllerContext();
+ ExtensibleModelBindingContext bindingContext = new ExtensibleModelBindingContext
+ {
+ ModelMetadata = new EmptyModelMetadataProvider().GetMetadataForType(null, typeof(KeyValuePair<int, string>)),
+ ModelName = "someName",
+ ModelBinderProviders = new ModelBinderProviderCollection(),
+ ValueProvider = new SimpleValueProvider()
+ };
+
+ Mock<IExtensibleModelBinder> mockIntBinder = new Mock<IExtensibleModelBinder>();
+ mockIntBinder
+ .Setup(o => o.BindModel(controllerContext, It.IsAny<ExtensibleModelBindingContext>()))
+ .Returns(
+ delegate(ControllerContext cc, ExtensibleModelBindingContext mbc)
+ {
+ mbc.Model = 42;
+ return true;
+ });
+ bindingContext.ModelBinderProviders.RegisterBinderForType(typeof(int), mockIntBinder.Object, true /* suppressPrefixCheck */);
+ Mock<IExtensibleModelBinder> mockStringBinder = new Mock<IExtensibleModelBinder>();
+ mockStringBinder
+ .Setup(o => o.BindModel(controllerContext, It.IsAny<ExtensibleModelBindingContext>()))
+ .Returns(
+ delegate(ControllerContext cc, ExtensibleModelBindingContext mbc)
+ {
+ mbc.Model = "forty-two";
+ return true;
+ });
+ bindingContext.ModelBinderProviders.RegisterBinderForType(typeof(string), mockStringBinder.Object, true /* suppressPrefixCheck */);
+
+ KeyValuePairModelBinder<int, string> binder = new KeyValuePairModelBinder<int, string>();
+
+ // Act
+ bool retVal = binder.BindModel(controllerContext, bindingContext);
+
+ // Assert
+ Assert.True(retVal);
+ Assert.Equal(new KeyValuePair<int, string>(42, "forty-two"), bindingContext.Model);
+ Assert.Equal(new[] { "someName.key", "someName.value" }, bindingContext.ValidationNode.ChildNodes.Select(n => n.ModelStateKey).ToArray());
+ }
+ }
+}
diff --git a/test/Microsoft.Web.Mvc.Test/ModelBinding/Test/KeyValuePairModelBinderUtilTest.cs b/test/Microsoft.Web.Mvc.Test/ModelBinding/Test/KeyValuePairModelBinderUtilTest.cs
new file mode 100644
index 00000000..39a37673
--- /dev/null
+++ b/test/Microsoft.Web.Mvc.Test/ModelBinding/Test/KeyValuePairModelBinderUtilTest.cs
@@ -0,0 +1,108 @@
+using System.Web.Mvc;
+using Microsoft.Web.UnitTestUtil;
+using Moq;
+using Xunit;
+
+namespace Microsoft.Web.Mvc.ModelBinding.Test
+{
+ public class KeyValuePairModelBinderUtilTest
+ {
+ [Fact]
+ public void TryBindStrongModel_BinderExists_BinderReturnsCorrectlyTypedObject_ReturnsTrue()
+ {
+ // Arrange
+ ControllerContext controllerContext = new ControllerContext();
+ ExtensibleModelBindingContext bindingContext = new ExtensibleModelBindingContext
+ {
+ ModelMetadata = new EmptyModelMetadataProvider().GetMetadataForType(null, typeof(int)),
+ ModelName = "someName",
+ ModelState = new ModelStateDictionary(),
+ ModelBinderProviders = new ModelBinderProviderCollection(),
+ ValueProvider = new SimpleValueProvider()
+ };
+
+ Mock<IExtensibleModelBinder> mockIntBinder = new Mock<IExtensibleModelBinder>();
+ mockIntBinder
+ .Setup(o => o.BindModel(controllerContext, It.IsAny<ExtensibleModelBindingContext>()))
+ .Returns(
+ delegate(ControllerContext cc, ExtensibleModelBindingContext mbc)
+ {
+ Assert.Equal("someName.key", mbc.ModelName);
+ mbc.Model = 42;
+ return true;
+ });
+ bindingContext.ModelBinderProviders.RegisterBinderForType(typeof(int), mockIntBinder.Object, true /* suppressPrefixCheck */);
+
+ // Act
+ int model;
+ bool retVal = KeyValuePairModelBinderUtil.TryBindStrongModel(controllerContext, bindingContext, "key", new EmptyModelMetadataProvider(), out model);
+
+ // Assert
+ Assert.True(retVal);
+ Assert.Equal(42, model);
+ Assert.Single(bindingContext.ValidationNode.ChildNodes);
+ Assert.Empty(bindingContext.ModelState);
+ }
+
+ [Fact]
+ public void TryBindStrongModel_BinderExists_BinderReturnsIncorrectlyTypedObject_ReturnsTrue()
+ {
+ // Arrange
+ ControllerContext controllerContext = new ControllerContext();
+ ExtensibleModelBindingContext bindingContext = new ExtensibleModelBindingContext
+ {
+ ModelMetadata = new EmptyModelMetadataProvider().GetMetadataForType(null, typeof(int)),
+ ModelName = "someName",
+ ModelState = new ModelStateDictionary(),
+ ModelBinderProviders = new ModelBinderProviderCollection(),
+ ValueProvider = new SimpleValueProvider()
+ };
+
+ Mock<IExtensibleModelBinder> mockIntBinder = new Mock<IExtensibleModelBinder>();
+ mockIntBinder
+ .Setup(o => o.BindModel(controllerContext, It.IsAny<ExtensibleModelBindingContext>()))
+ .Returns(
+ delegate(ControllerContext cc, ExtensibleModelBindingContext mbc)
+ {
+ Assert.Equal("someName.key", mbc.ModelName);
+ return true;
+ });
+ bindingContext.ModelBinderProviders.RegisterBinderForType(typeof(int), mockIntBinder.Object, true /* suppressPrefixCheck */);
+
+ // Act
+ int model;
+ bool retVal = KeyValuePairModelBinderUtil.TryBindStrongModel(controllerContext, bindingContext, "key", new EmptyModelMetadataProvider(), out model);
+
+ // Assert
+ Assert.True(retVal);
+ Assert.Equal(default(int), model);
+ Assert.Single(bindingContext.ValidationNode.ChildNodes);
+ Assert.Empty(bindingContext.ModelState);
+ }
+
+ [Fact]
+ public void TryBindStrongModel_NoBinder_ReturnsFalse()
+ {
+ // Arrange
+ ControllerContext controllerContext = new ControllerContext();
+ ExtensibleModelBindingContext bindingContext = new ExtensibleModelBindingContext
+ {
+ ModelMetadata = new EmptyModelMetadataProvider().GetMetadataForType(null, typeof(int)),
+ ModelName = "someName",
+ ModelState = new ModelStateDictionary(),
+ ModelBinderProviders = new ModelBinderProviderCollection(),
+ ValueProvider = new SimpleValueProvider()
+ };
+
+ // Act
+ int model;
+ bool retVal = KeyValuePairModelBinderUtil.TryBindStrongModel(controllerContext, bindingContext, "key", new EmptyModelMetadataProvider(), out model);
+
+ // Assert
+ Assert.False(retVal);
+ Assert.Equal(default(int), model);
+ Assert.Empty(bindingContext.ValidationNode.ChildNodes);
+ Assert.Empty(bindingContext.ModelState);
+ }
+ }
+}
diff --git a/test/Microsoft.Web.Mvc.Test/ModelBinding/Test/ModelBinderConfigTest.cs b/test/Microsoft.Web.Mvc.Test/ModelBinding/Test/ModelBinderConfigTest.cs
new file mode 100644
index 00000000..cbeefe1c
--- /dev/null
+++ b/test/Microsoft.Web.Mvc.Test/ModelBinding/Test/ModelBinderConfigTest.cs
@@ -0,0 +1,166 @@
+using System;
+using System.Globalization;
+using System.Web;
+using System.Web.Mvc;
+using System.Web.TestUtil;
+using Moq;
+using Xunit;
+
+namespace Microsoft.Web.Mvc.ModelBinding.Test
+{
+ public class ModelBinderConfigTest
+ {
+ [Fact]
+ public void GetUserResourceString_NullControllerContext_ReturnsNull()
+ {
+ // Act
+ string customResourceString = ModelBinderConfig.GetUserResourceString(null /* controllerContext */, "someResourceName", "someResourceClassKey");
+
+ // Assert
+ Assert.Null(customResourceString);
+ }
+
+ [Fact]
+ public void GetUserResourceString_NullHttpContext_ReturnsNull()
+ {
+ Mock<ControllerContext> mockControllerContext = new Mock<ControllerContext>();
+ mockControllerContext.Setup(o => o.HttpContext).Returns((HttpContextBase)null);
+
+ // Act
+ string customResourceString = ModelBinderConfig.GetUserResourceString(mockControllerContext.Object, "someResourceName", "someResourceClassKey");
+
+ // Assert
+ Assert.Null(customResourceString);
+ }
+
+ [Fact]
+ public void GetUserResourceString_NullResourceKey_ReturnsNull()
+ {
+ Mock<ControllerContext> mockControllerContext = new Mock<ControllerContext>();
+
+ // Act
+ string customResourceString = ModelBinderConfig.GetUserResourceString(mockControllerContext.Object, "someResourceName", null /* resourceClassKey */);
+
+ // Assert
+ mockControllerContext.Verify(o => o.HttpContext, Times.Never());
+ Assert.Null(customResourceString);
+ }
+
+ [Fact]
+ public void GetUserResourceString_ValidResourceObject_ReturnsResourceString()
+ {
+ Mock<ControllerContext> mockControllerContext = new Mock<ControllerContext>();
+ mockControllerContext.Setup(o => o.HttpContext.GetGlobalResourceObject("someResourceClassKey", "someResourceName", CultureInfo.CurrentUICulture)).Returns("My custom resource string");
+
+ // Act
+ string customResourceString = ModelBinderConfig.GetUserResourceString(mockControllerContext.Object, "someResourceName", "someResourceClassKey");
+
+ // Assert
+ Assert.Equal("My custom resource string", customResourceString);
+ }
+
+ [Fact]
+ public void Initialize_ReplacesOriginalCollection()
+ {
+ // Arrange
+ ModelBinderDictionary oldBinders = new ModelBinderDictionary();
+ oldBinders[typeof(int)] = new Mock<IModelBinder>().Object;
+ ModelBinderProviderCollection newBinderProviders = new ModelBinderProviderCollection();
+
+ // Act
+ ModelBinderConfig.Initialize(oldBinders, newBinderProviders);
+
+ // Assert
+ Assert.Empty(oldBinders);
+
+ var shimBinder = Assert.IsType<ExtensibleModelBinderAdapter>(oldBinders.DefaultBinder);
+ Assert.Same(newBinderProviders, shimBinder.Providers);
+ }
+
+ [Fact]
+ public void TypeConversionErrorMessageProvider_DefaultValue()
+ {
+ // Arrange
+ ModelMetadata metadata = new ModelMetadata(new Mock<ModelMetadataProvider>().Object, null, null, typeof(int), "SomePropertyName");
+
+ // Act
+ string errorString = ModelBinderConfig.TypeConversionErrorMessageProvider(null, metadata, "some incoming value");
+
+ // Assert
+ Assert.Equal("The value 'some incoming value' is not valid for SomePropertyName.", errorString);
+ }
+
+ [Fact]
+ public void TypeConversionErrorMessageProvider_Property()
+ {
+ // Arrange
+ ModelBinderConfigWrapper wrapper = new ModelBinderConfigWrapper();
+
+ // Act & assert
+ try
+ {
+ MemberHelper.TestPropertyWithDefaultInstance(wrapper, "TypeConversionErrorMessageProvider", (ModelBinderErrorMessageProvider)DummyErrorSelector);
+ }
+ finally
+ {
+ wrapper.Reset();
+ }
+ }
+
+ [Fact]
+ public void ValueRequiredErrorMessageProvider_DefaultValue()
+ {
+ // Arrange
+ ModelMetadata metadata = new ModelMetadata(new Mock<ModelMetadataProvider>().Object, null, null, typeof(int), "SomePropertyName");
+
+ // Act
+ string errorString = ModelBinderConfig.ValueRequiredErrorMessageProvider(null, metadata, "some incoming value");
+
+ // Assert
+ Assert.Equal("A value is required.", errorString);
+ }
+
+ [Fact]
+ public void ValueRequiredErrorMessageProvider_Property()
+ {
+ // Arrange
+ ModelBinderConfigWrapper wrapper = new ModelBinderConfigWrapper();
+
+ // Act & assert
+ try
+ {
+ MemberHelper.TestPropertyWithDefaultInstance(wrapper, "ValueRequiredErrorMessageProvider", (ModelBinderErrorMessageProvider)DummyErrorSelector);
+ }
+ finally
+ {
+ wrapper.Reset();
+ }
+ }
+
+ private string DummyErrorSelector(ControllerContext controllerContext, ModelMetadata modelMetadata, object incomingValue)
+ {
+ throw new NotImplementedException();
+ }
+
+ private sealed class ModelBinderConfigWrapper
+ {
+ public ModelBinderErrorMessageProvider TypeConversionErrorMessageProvider
+ {
+ get { return ModelBinderConfig.TypeConversionErrorMessageProvider; }
+ set { ModelBinderConfig.TypeConversionErrorMessageProvider = value; }
+ }
+
+ public ModelBinderErrorMessageProvider ValueRequiredErrorMessageProvider
+ {
+ get { return ModelBinderConfig.ValueRequiredErrorMessageProvider; }
+ set { ModelBinderConfig.ValueRequiredErrorMessageProvider = value; }
+ }
+
+ public void Reset()
+ {
+ ModelBinderConfig.TypeConversionErrorMessageProvider = null;
+ ModelBinderConfig.ValueRequiredErrorMessageProvider = null;
+ }
+ }
+ }
+}
diff --git a/test/Microsoft.Web.Mvc.Test/ModelBinding/Test/ModelBinderProviderCollectionTest.cs b/test/Microsoft.Web.Mvc.Test/ModelBinding/Test/ModelBinderProviderCollectionTest.cs
new file mode 100644
index 00000000..064dfb03
--- /dev/null
+++ b/test/Microsoft.Web.Mvc.Test/ModelBinding/Test/ModelBinderProviderCollectionTest.cs
@@ -0,0 +1,528 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel.DataAnnotations;
+using System.Linq;
+using System.Web.Mvc;
+using System.Web.TestUtil;
+using Microsoft.Web.UnitTestUtil;
+using Moq;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace Microsoft.Web.Mvc.ModelBinding.Test
+{
+ public class ModelBinderProviderCollectionTest
+ {
+ [Fact]
+ public void ListWrappingConstructor()
+ {
+ // Arrange
+ ModelBinderProvider[] providers = new[]
+ {
+ new Mock<ModelBinderProvider>().Object,
+ new Mock<ModelBinderProvider>().Object
+ };
+
+ // Act
+ ModelBinderProviderCollection collection = new ModelBinderProviderCollection(providers);
+
+ // Assert
+ Assert.Equal(providers, collection.ToArray());
+ }
+
+ [Fact]
+ public void DefaultConstructor()
+ {
+ // Act
+ ModelBinderProviderCollection collection = new ModelBinderProviderCollection();
+
+ // Assert
+ Assert.Empty(collection);
+ }
+
+ [Fact]
+ public void AddNullProviderThrows()
+ {
+ // Arrange
+ ModelBinderProviderCollection collection = new ModelBinderProviderCollection();
+
+ // Act & Assert
+ Assert.ThrowsArgumentNull(
+ delegate { collection.Add(null); },
+ "item");
+ }
+
+ [Fact]
+ public void RegisterBinderForGenericType_Factory()
+ {
+ // Arrange
+ ModelBinderProvider mockProvider = new Mock<ModelBinderProvider>().Object;
+ IExtensibleModelBinder mockBinder = new Mock<IExtensibleModelBinder>().Object;
+
+ ModelBinderProviderCollection collection = new ModelBinderProviderCollection
+ {
+ mockProvider
+ };
+
+ // Act
+ collection.RegisterBinderForGenericType(typeof(List<>), _ => mockBinder);
+
+ // Assert
+ var genericProvider = Assert.IsType<GenericModelBinderProvider>(collection[0]);
+ Assert.Equal(typeof(List<>), genericProvider.ModelType);
+ Assert.Equal(mockProvider, collection[1]);
+ }
+
+ [Fact]
+ public void RegisterBinderForGenericType_Instance()
+ {
+ // Arrange
+ ModelBinderProvider mockProvider = new Mock<ModelBinderProvider>().Object;
+ IExtensibleModelBinder mockBinder = new Mock<IExtensibleModelBinder>().Object;
+
+ ModelBinderProviderCollection collection = new ModelBinderProviderCollection
+ {
+ mockProvider
+ };
+
+ // Act
+ collection.RegisterBinderForGenericType(typeof(List<>), mockBinder);
+
+ // Assert
+ var genericProvider = Assert.IsType<GenericModelBinderProvider>(collection[0]);
+ Assert.Equal(typeof(List<>), genericProvider.ModelType);
+ Assert.Equal(mockProvider, collection[1]);
+ }
+
+ [Fact]
+ public void RegisterBinderForGenericType_Type()
+ {
+ // Arrange
+ ModelBinderProvider mockProvider = new Mock<ModelBinderProvider>().Object;
+ IExtensibleModelBinder mockBinder = new Mock<IExtensibleModelBinder>().Object;
+
+ ModelBinderProviderCollection collection = new ModelBinderProviderCollection
+ {
+ mockProvider
+ };
+
+ // Act
+ collection.RegisterBinderForGenericType(typeof(List<>), typeof(CollectionModelBinder<>));
+
+ // Assert
+ var genericProvider = Assert.IsType<GenericModelBinderProvider>(collection[0]);
+ Assert.Equal(typeof(List<>), genericProvider.ModelType);
+ Assert.Equal(mockProvider, collection[1]);
+ }
+
+ [Fact]
+ public void RegisterBinderForType_Factory()
+ {
+ // Arrange
+ ModelBinderProvider mockProvider = new Mock<ModelBinderProvider>().Object;
+ IExtensibleModelBinder mockBinder = new Mock<IExtensibleModelBinder>().Object;
+
+ ModelBinderProviderCollection collection = new ModelBinderProviderCollection
+ {
+ mockProvider
+ };
+
+ // Act
+ collection.RegisterBinderForType(typeof(int), () => mockBinder);
+
+ // Assert
+ var simpleProvider = Assert.IsType<SimpleModelBinderProvider>(collection[0]);
+ Assert.Equal(typeof(int), simpleProvider.ModelType);
+ Assert.Equal(mockProvider, collection[1]);
+ }
+
+ [Fact]
+ public void RegisterBinderForType_Instance()
+ {
+ // Arrange
+ ModelBinderProvider mockProvider = new Mock<ModelBinderProvider>().Object;
+ IExtensibleModelBinder mockBinder = new Mock<IExtensibleModelBinder>().Object;
+
+ ModelBinderProviderCollection collection = new ModelBinderProviderCollection
+ {
+ mockProvider
+ };
+
+ // Act
+ collection.RegisterBinderForType(typeof(int), mockBinder);
+
+ // Assert
+ var simpleProvider = Assert.IsType<SimpleModelBinderProvider>(collection[0]);
+ Assert.Equal(typeof(int), simpleProvider.ModelType);
+ Assert.Equal(mockProvider, collection[1]);
+ }
+
+ [Fact]
+ public void RegisterBinderForType_Instance_InsertsNewProviderBehindFrontOfListProviders()
+ {
+ // Arrange
+ ModelBinderProvider frontOfListProvider = new ProviderAtFront();
+ IExtensibleModelBinder mockBinder = new Mock<IExtensibleModelBinder>().Object;
+
+ ModelBinderProviderCollection collection = new ModelBinderProviderCollection
+ {
+ frontOfListProvider
+ };
+
+ // Act
+ collection.RegisterBinderForType(typeof(int), mockBinder);
+
+ // Assert
+ Assert.Equal(
+ new[] { typeof(ProviderAtFront), typeof(SimpleModelBinderProvider) },
+ collection.Select(o => o.GetType()).ToArray());
+ }
+
+ [Fact]
+ public void SetItem()
+ {
+ // Arrange
+ ModelBinderProvider provider0 = new Mock<ModelBinderProvider>().Object;
+ ModelBinderProvider provider1 = new Mock<ModelBinderProvider>().Object;
+ ModelBinderProvider provider2 = new Mock<ModelBinderProvider>().Object;
+
+ ModelBinderProviderCollection collection = new ModelBinderProviderCollection();
+ collection.Add(provider0);
+ collection.Add(provider1);
+
+ // Act
+ collection[1] = provider2;
+
+ // Assert
+ Assert.Equal(new[] { provider0, provider2 }, collection.ToArray());
+ }
+
+ [Fact]
+ public void SetNullProviderThrows()
+ {
+ // Arrange
+ ModelBinderProviderCollection collection = new ModelBinderProviderCollection();
+ collection.Add(new Mock<ModelBinderProvider>().Object);
+
+ // Act & Assert
+ Assert.ThrowsArgumentNull(
+ delegate { collection[0] = null; },
+ "item");
+ }
+
+ [Fact]
+ public void GetBinder_FromAttribute_BadAttribute_Throws()
+ {
+ // Arrange
+ ControllerContext controllerContext = new ControllerContext();
+ ExtensibleModelBindingContext bindingContext = new ExtensibleModelBindingContext
+ {
+ ModelMetadata = new EmptyModelMetadataProvider().GetMetadataForType(null, typeof(ModelWithProviderAttribute_BadAttribute))
+ };
+
+ ModelBinderProviderCollection providers = new ModelBinderProviderCollection();
+
+ // Act & assert
+ Assert.Throws<InvalidOperationException>(
+ delegate { providers.GetBinder(controllerContext, bindingContext); },
+ @"The type 'System.Object' does not subclass Microsoft.Web.Mvc.ModelBinding.ModelBinderProvider or implement the interface Microsoft.Web.Mvc.ModelBinding.IExtensibleModelBinder.");
+ }
+
+ [Fact]
+ public void GetBinder_FromAttribute_Binder_Generic_ReturnsBinder()
+ {
+ // Arrange
+ ControllerContext controllerContext = new ControllerContext();
+ ExtensibleModelBindingContext bindingContext = new ExtensibleModelBindingContext
+ {
+ ModelMetadata = new EmptyModelMetadataProvider().GetMetadataForType(null, typeof(ModelWithProviderAttribute_Binder_Generic<int>)),
+ ModelName = "foo",
+ ValueProvider = new SimpleValueProvider
+ {
+ { "foo", "fooValue" }
+ }
+ };
+
+ ModelBinderProviderCollection providers = new ModelBinderProviderCollection();
+ providers.RegisterBinderForType(typeof(ModelWithProviderAttribute_Binder_Generic<int>), new Mock<IExtensibleModelBinder>().Object, true /* suppressPrefix */);
+
+ // Act
+ IExtensibleModelBinder binder = providers.GetBinder(controllerContext, bindingContext);
+
+ // Assert
+ Assert.IsType<CustomGenericBinder<int>>(binder);
+ }
+
+ [Fact]
+ public void GetBinder_FromAttribute_Binder_SuppressPrefixCheck_ReturnsBinder()
+ {
+ // Arrange
+ ControllerContext controllerContext = new ControllerContext();
+ ExtensibleModelBindingContext bindingContext = new ExtensibleModelBindingContext
+ {
+ ModelMetadata = new EmptyModelMetadataProvider().GetMetadataForType(null, typeof(ModelWithProviderAttribute_Binder_SuppressPrefix)),
+ ModelName = "foo",
+ ValueProvider = new SimpleValueProvider
+ {
+ { "bar", "barValue" }
+ }
+ };
+
+ ModelBinderProviderCollection providers = new ModelBinderProviderCollection();
+ providers.RegisterBinderForType(typeof(ModelWithProviderAttribute_Binder_SuppressPrefix), new Mock<IExtensibleModelBinder>().Object, true /* suppressPrefix */);
+
+ // Act
+ IExtensibleModelBinder binder = providers.GetBinder(controllerContext, bindingContext);
+
+ // Assert
+ Assert.IsType<CustomBinder>(binder);
+ }
+
+ [Fact]
+ public void GetBinder_FromAttribute_Binder_ValueNotPresent_ReturnsNull()
+ {
+ // Arrange
+ ControllerContext controllerContext = new ControllerContext();
+ ExtensibleModelBindingContext bindingContext = new ExtensibleModelBindingContext
+ {
+ ModelMetadata = new EmptyModelMetadataProvider().GetMetadataForType(null, typeof(ModelWithProviderAttribute_Binder)),
+ ModelName = "foo",
+ ValueProvider = new SimpleValueProvider
+ {
+ { "bar", "barValue" }
+ }
+ };
+
+ ModelBinderProviderCollection providers = new ModelBinderProviderCollection();
+ providers.RegisterBinderForType(typeof(ModelWithProviderAttribute_Binder), new Mock<IExtensibleModelBinder>().Object, true /* suppressPrefix */);
+
+ // Act
+ IExtensibleModelBinder binder = providers.GetBinder(controllerContext, bindingContext);
+
+ // Assert
+ Assert.Null(binder);
+ }
+
+ [Fact]
+ public void GetBinder_FromAttribute_Binder_ValuePresent_ReturnsBinder()
+ {
+ // Arrange
+ ControllerContext controllerContext = new ControllerContext();
+ ExtensibleModelBindingContext bindingContext = new ExtensibleModelBindingContext
+ {
+ ModelMetadata = new EmptyModelMetadataProvider().GetMetadataForType(null, typeof(ModelWithProviderAttribute_Binder)),
+ ModelName = "foo",
+ ValueProvider = new SimpleValueProvider
+ {
+ { "foo", "fooValue" }
+ }
+ };
+
+ ModelBinderProviderCollection providers = new ModelBinderProviderCollection();
+ providers.RegisterBinderForType(typeof(ModelWithProviderAttribute_Binder), new Mock<IExtensibleModelBinder>().Object, true /* suppressPrefix */);
+
+ // Act
+ IExtensibleModelBinder binder = providers.GetBinder(controllerContext, bindingContext);
+
+ // Assert
+ Assert.IsType<CustomBinder>(binder);
+ }
+
+ [Fact]
+ public void GetBinder_FromAttribute_Provider_ReturnsBinder()
+ {
+ // Arrange
+ ControllerContext controllerContext = new ControllerContext();
+ ExtensibleModelBindingContext bindingContext = new ExtensibleModelBindingContext
+ {
+ ModelMetadata = new EmptyModelMetadataProvider().GetMetadataForType(null, typeof(ModelWithProviderAttribute_Provider))
+ };
+
+ ModelBinderProviderCollection providers = new ModelBinderProviderCollection();
+ providers.RegisterBinderForType(typeof(ModelWithProviderAttribute_Provider), new Mock<IExtensibleModelBinder>().Object, true /* suppressPrefix */);
+
+ // Act
+ IExtensibleModelBinder binder = providers.GetBinder(controllerContext, bindingContext);
+
+ // Assert
+ Assert.IsType<CustomBinder>(binder);
+ }
+
+ [Fact]
+ public void GetBinderReturnsFirstBinderFromProviders()
+ {
+ // Arrange
+ ControllerContext controllerContext = new ControllerContext();
+ ExtensibleModelBindingContext bindingContext = new ExtensibleModelBindingContext
+ {
+ ModelMetadata = new EmptyModelMetadataProvider().GetMetadataForType(null, typeof(object))
+ };
+ IExtensibleModelBinder expectedBinder = new Mock<IExtensibleModelBinder>().Object;
+
+ Mock<ModelBinderProvider> mockProvider = new Mock<ModelBinderProvider>();
+ mockProvider.Setup(p => p.GetBinder(controllerContext, bindingContext)).Returns(expectedBinder);
+
+ ModelBinderProviderCollection collection = new ModelBinderProviderCollection(new[]
+ {
+ new Mock<ModelBinderProvider>().Object,
+ mockProvider.Object,
+ new Mock<ModelBinderProvider>().Object
+ });
+
+ // Act
+ IExtensibleModelBinder returned = collection.GetBinder(controllerContext, bindingContext);
+
+ // Assert
+ Assert.Equal(expectedBinder, returned);
+ }
+
+ [Fact]
+ public void GetBinderReturnsNullIfNoProviderMatches()
+ {
+ // Arrange
+ ControllerContext controllerContext = new ControllerContext();
+ ExtensibleModelBindingContext bindingContext = new ExtensibleModelBindingContext
+ {
+ ModelMetadata = new EmptyModelMetadataProvider().GetMetadataForType(null, typeof(object))
+ };
+
+ ModelBinderProviderCollection collection = new ModelBinderProviderCollection(new[]
+ {
+ new Mock<ModelBinderProvider>().Object,
+ });
+
+ // Act
+ IExtensibleModelBinder returned = collection.GetBinder(controllerContext, bindingContext);
+
+ // Assert
+ Assert.Null(returned);
+ }
+
+ [Fact]
+ public void GetBinderThrowsIfBindingContextIsNull()
+ {
+ // Arrange
+ ModelBinderProviderCollection collection = new ModelBinderProviderCollection();
+
+ // Act & assert
+ Assert.ThrowsArgumentNull(
+ delegate { collection.GetBinder(new ControllerContext(), null); }, "bindingContext");
+ }
+
+ [Fact]
+ public void GetBinderThrowsIfControllerContextIsNull()
+ {
+ // Arrange
+ ModelBinderProviderCollection collection = new ModelBinderProviderCollection();
+
+ // Act & assert
+ Assert.ThrowsArgumentNull(
+ delegate { collection.GetBinder(null, new ExtensibleModelBindingContext()); }, "controllerContext");
+ }
+
+ [Fact]
+ public void GetBinderThrowsIfModelTypeHasBindAttribute()
+ {
+ // Arrange
+ ControllerContext controllerContext = new ControllerContext();
+ ExtensibleModelBindingContext bindingContext = new ExtensibleModelBindingContext
+ {
+ ModelMetadata = new EmptyModelMetadataProvider().GetMetadataForType(null, typeof(ModelWithBindAttribute))
+ };
+ ModelBinderProviderCollection collection = new ModelBinderProviderCollection();
+
+ // Act & assert
+ Assert.Throws<InvalidOperationException>(
+ delegate { collection.GetBinder(controllerContext, bindingContext); },
+ @"The model of type 'Microsoft.Web.Mvc.ModelBinding.Test.ModelBinderProviderCollectionTest+ModelWithBindAttribute' has a [Bind] attribute. The new model binding system cannot be used with models that have type-level [Bind] attributes. Use the [BindRequired] and [BindNever] attributes on the model type or its properties instead.");
+ }
+
+ [Fact]
+ public void GetRequiredBinderThrowsIfNoProviderMatches()
+ {
+ // Arrange
+ ControllerContext controllerContext = new ControllerContext();
+ ExtensibleModelBindingContext bindingContext = new ExtensibleModelBindingContext
+ {
+ ModelMetadata = new EmptyModelMetadataProvider().GetMetadataForType(null, typeof(int))
+ };
+
+ ModelBinderProviderCollection collection = new ModelBinderProviderCollection(new[]
+ {
+ new Mock<ModelBinderProvider>().Object,
+ });
+
+ // Act & assert
+ Assert.Throws<InvalidOperationException>(
+ delegate { collection.GetRequiredBinder(controllerContext, bindingContext); },
+ @"A binder for type System.Int32 could not be located.");
+ }
+
+ [MetadataType(typeof(ModelWithBindAttribute_Buddy))]
+ private class ModelWithBindAttribute
+ {
+ [Bind]
+ private class ModelWithBindAttribute_Buddy
+ {
+ }
+ }
+
+ [ModelBinderProviderOptions(FrontOfList = true)]
+ private class ProviderAtFront : ModelBinderProvider
+ {
+ public override IExtensibleModelBinder GetBinder(ControllerContext controllerContext, ExtensibleModelBindingContext bindingContext)
+ {
+ throw new NotImplementedException();
+ }
+ }
+
+ [ExtensibleModelBinder(typeof(object))]
+ private class ModelWithProviderAttribute_BadAttribute
+ {
+ }
+
+ [ExtensibleModelBinder(typeof(CustomBinder))]
+ private class ModelWithProviderAttribute_Binder
+ {
+ }
+
+ [ExtensibleModelBinder(typeof(CustomGenericBinder<>))]
+ private class ModelWithProviderAttribute_Binder_Generic<T>
+ {
+ }
+
+ [ExtensibleModelBinder(typeof(CustomBinder), SuppressPrefixCheck = true)]
+ private class ModelWithProviderAttribute_Binder_SuppressPrefix
+ {
+ }
+
+ [ExtensibleModelBinder(typeof(CustomProvider))]
+ private class ModelWithProviderAttribute_Provider
+ {
+ }
+
+ private class CustomProvider : ModelBinderProvider
+ {
+ public override IExtensibleModelBinder GetBinder(ControllerContext controllerContext, ExtensibleModelBindingContext bindingContext)
+ {
+ return new CustomBinder();
+ }
+ }
+
+ private class CustomBinder : IExtensibleModelBinder
+ {
+ public bool BindModel(ControllerContext controllerContext, ExtensibleModelBindingContext bindingContext)
+ {
+ throw new NotImplementedException();
+ }
+ }
+
+ private class CustomGenericBinder<T> : IExtensibleModelBinder
+ {
+ public bool BindModel(ControllerContext controllerContext, ExtensibleModelBindingContext bindingContext)
+ {
+ throw new NotImplementedException();
+ }
+ }
+ }
+}
diff --git a/test/Microsoft.Web.Mvc.Test/ModelBinding/Test/ModelBinderProvidersTest.cs b/test/Microsoft.Web.Mvc.Test/ModelBinding/Test/ModelBinderProvidersTest.cs
new file mode 100644
index 00000000..15437490
--- /dev/null
+++ b/test/Microsoft.Web.Mvc.Test/ModelBinding/Test/ModelBinderProvidersTest.cs
@@ -0,0 +1,33 @@
+using System;
+using System.Linq;
+using Xunit;
+
+namespace Microsoft.Web.Mvc.ModelBinding.Test
+{
+ public class ModelBinderProvidersTest
+ {
+ [Fact]
+ public void CollectionDefaults()
+ {
+ // Arrange
+ Type[] expectedTypes = new[]
+ {
+ typeof(TypeMatchModelBinderProvider),
+ typeof(BinaryDataModelBinderProvider),
+ typeof(KeyValuePairModelBinderProvider),
+ typeof(ComplexModelDtoModelBinderProvider),
+ typeof(ArrayModelBinderProvider),
+ typeof(DictionaryModelBinderProvider),
+ typeof(CollectionModelBinderProvider),
+ typeof(TypeConverterModelBinderProvider),
+ typeof(MutableObjectModelBinderProvider)
+ };
+
+ // Act
+ Type[] actualTypes = ModelBinderProviders.Providers.Select(p => p.GetType()).ToArray();
+
+ // Assert
+ Assert.Equal(expectedTypes, actualTypes);
+ }
+ }
+}
diff --git a/test/Microsoft.Web.Mvc.Test/ModelBinding/Test/ModelBinderUtilTest.cs b/test/Microsoft.Web.Mvc.Test/ModelBinding/Test/ModelBinderUtilTest.cs
new file mode 100644
index 00000000..1f6faf17
--- /dev/null
+++ b/test/Microsoft.Web.Mvc.Test/ModelBinding/Test/ModelBinderUtilTest.cs
@@ -0,0 +1,324 @@
+using System;
+using System.Collections.Generic;
+using System.Web.Mvc;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace Microsoft.Web.Mvc.ModelBinding.Test
+{
+ public class ModelBinderUtilTest
+ {
+ [Fact]
+ public void CastOrDefault_CorrectType_ReturnsInput()
+ {
+ // Act
+ int retVal = ModelBinderUtil.CastOrDefault<int>(42);
+
+ // Assert
+ Assert.Equal(42, retVal);
+ }
+
+ [Fact]
+ public void CastOrDefault_IncorrectType_ReturnsDefaultTModel()
+ {
+ // Act
+ DateTime retVal = ModelBinderUtil.CastOrDefault<DateTime>(42);
+
+ // Assert
+ Assert.Equal(default(DateTime), retVal);
+ }
+
+ [Fact]
+ public void CreateIndexModelName_EmptyParentName()
+ {
+ // Act
+ string fullChildName = ModelBinderUtil.CreateIndexModelName("", 42);
+
+ // Assert
+ Assert.Equal("[42]", fullChildName);
+ }
+
+ [Fact]
+ public void CreateIndexModelName_IntIndex()
+ {
+ // Act
+ string fullChildName = ModelBinderUtil.CreateIndexModelName("parentName", 42);
+
+ // Assert
+ Assert.Equal("parentName[42]", fullChildName);
+ }
+
+ [Fact]
+ public void CreateIndexModelName_StringIndex()
+ {
+ // Act
+ string fullChildName = ModelBinderUtil.CreateIndexModelName("parentName", "index");
+
+ // Assert
+ Assert.Equal("parentName[index]", fullChildName);
+ }
+
+ [Fact]
+ public void CreatePropertyModelName()
+ {
+ // Act
+ string fullChildName = ModelBinderUtil.CreatePropertyModelName("parentName", "childName");
+
+ // Assert
+ Assert.Equal("parentName.childName", fullChildName);
+ }
+
+ [Fact]
+ public void CreatePropertyModelName_EmptyParentName()
+ {
+ // Act
+ string fullChildName = ModelBinderUtil.CreatePropertyModelName("", "childName");
+
+ // Assert
+ Assert.Equal("childName", fullChildName);
+ }
+
+ [Fact]
+ public void GetPossibleBinderInstance_Match_ReturnsBinder()
+ {
+ // Act
+ IExtensibleModelBinder binder = ModelBinderUtil.GetPossibleBinderInstance(typeof(List<int>), typeof(List<>), typeof(SampleGenericBinder<>));
+
+ // Assert
+ Assert.IsType<SampleGenericBinder<int>>(binder);
+ }
+
+ [Fact]
+ public void GetPossibleBinderInstance_NoMatch_ReturnsNull()
+ {
+ // Act
+ IExtensibleModelBinder binder = ModelBinderUtil.GetPossibleBinderInstance(typeof(ArraySegment<int>), typeof(List<>), typeof(SampleGenericBinder<>));
+
+ // Assert
+ Assert.Null(binder);
+ }
+
+ [Fact]
+ public void RawValueToObjectArray_RawValueIsEnumerable_ReturnsInputAsArray()
+ {
+ // Assert
+ List<int> original = new List<int> { 1, 2, 3, 4 };
+
+ // Act
+ object[] retVal = ModelBinderUtil.RawValueToObjectArray(original);
+
+ // Assert
+ Assert.Equal(new object[] { 1, 2, 3, 4 }, retVal);
+ }
+
+ [Fact]
+ public void RawValueToObjectArray_RawValueIsObject_WrapsObjectInSingleElementArray()
+ {
+ // Act
+ object[] retVal = ModelBinderUtil.RawValueToObjectArray(42);
+
+ // Assert
+ Assert.Equal(new object[] { 42 }, retVal);
+ }
+
+ [Fact]
+ public void RawValueToObjectArray_RawValueIsObjectArray_ReturnsInputInstance()
+ {
+ // Assert
+ object[] original = new object[2];
+
+ // Act
+ object[] retVal = ModelBinderUtil.RawValueToObjectArray(original);
+
+ // Assert
+ Assert.Same(original, retVal);
+ }
+
+ [Fact]
+ public void RawValueToObjectArray_RawValueIsString_WrapsStringInSingleElementArray()
+ {
+ // Act
+ object[] retVal = ModelBinderUtil.RawValueToObjectArray("hello");
+
+ // Assert
+ Assert.Equal(new object[] { "hello" }, retVal);
+ }
+
+ [Fact]
+ public void ReplaceEmptyStringWithNull_ConvertEmptyStringToNullDisabled_ModelIsEmptyString_LeavesModelAlone()
+ {
+ // Arrange
+ ModelMetadata modelMetadata = GetMetadata(typeof(string));
+ modelMetadata.ConvertEmptyStringToNull = false;
+
+ // Act
+ object model = "";
+ ModelBinderUtil.ReplaceEmptyStringWithNull(modelMetadata, ref model);
+
+ // Assert
+ Assert.Equal("", model);
+ }
+
+ [Fact]
+ public void ReplaceEmptyStringWithNull_ConvertEmptyStringToNullEnabled_ModelIsEmptyString_ReplacesModelWithNull()
+ {
+ // Arrange
+ ModelMetadata modelMetadata = GetMetadata(typeof(string));
+ modelMetadata.ConvertEmptyStringToNull = true;
+
+ // Act
+ object model = "";
+ ModelBinderUtil.ReplaceEmptyStringWithNull(modelMetadata, ref model);
+
+ // Assert
+ Assert.Null(model);
+ }
+
+ [Fact]
+ public void ReplaceEmptyStringWithNull_ConvertEmptyStringToNullEnabled_ModelIsWhitespaceString_ReplacesModelWithNull()
+ {
+ // Arrange
+ ModelMetadata modelMetadata = GetMetadata(typeof(string));
+ modelMetadata.ConvertEmptyStringToNull = true;
+
+ // Act
+ object model = " "; // whitespace
+ ModelBinderUtil.ReplaceEmptyStringWithNull(modelMetadata, ref model);
+
+ // Assert
+ Assert.Null(model);
+ }
+
+ [Fact]
+ public void ReplaceEmptyStringWithNull_ConvertEmptyStringToNullDisabled_ModelIsNotEmptyString_LeavesModelAlone()
+ {
+ // Arrange
+ ModelMetadata modelMetadata = GetMetadata(typeof(string));
+ modelMetadata.ConvertEmptyStringToNull = true;
+
+ // Act
+ object model = 42;
+ ModelBinderUtil.ReplaceEmptyStringWithNull(modelMetadata, ref model);
+
+ // Assert
+ Assert.Equal(42, model);
+ }
+
+ [Fact]
+ public void ValidateBindingContext_SuccessWithNonNullModel()
+ {
+ // Arrange
+ ExtensibleModelBindingContext bindingContext = new ExtensibleModelBindingContext
+ {
+ ModelMetadata = GetMetadata(typeof(string))
+ };
+ bindingContext.ModelMetadata.Model = "hello!";
+
+ // Act
+ ModelBinderUtil.ValidateBindingContext(bindingContext, typeof(string), false);
+
+ // Assert
+ // Nothing to do - if we got this far without throwing, the test succeeded
+ }
+
+ [Fact]
+ public void ValidateBindingContext_SuccessWithNullModel()
+ {
+ // Arrange
+ ExtensibleModelBindingContext bindingContext = new ExtensibleModelBindingContext
+ {
+ ModelMetadata = GetMetadata(typeof(string))
+ };
+
+ // Act
+ ModelBinderUtil.ValidateBindingContext(bindingContext, typeof(string), true);
+
+ // Assert
+ // Nothing to do - if we got this far without throwing, the test succeeded
+ }
+
+ [Fact]
+ public void ValidateBindingContextThrowsIfBindingContextIsNull()
+ {
+ // Act & assert
+ Assert.ThrowsArgumentNull(
+ delegate { ModelBinderUtil.ValidateBindingContext(null, typeof(string), true); }, "bindingContext");
+ }
+
+ [Fact]
+ public void ValidateBindingContextThrowsIfModelInstanceIsWrongType()
+ {
+ // Arrange
+ ExtensibleModelBindingContext bindingContext = new ExtensibleModelBindingContext
+ {
+ ModelMetadata = GetMetadata(typeof(string))
+ };
+ bindingContext.ModelMetadata.Model = 42;
+
+ // Act & assert
+ Assert.Throws<ArgumentException>(
+ delegate { ModelBinderUtil.ValidateBindingContext(bindingContext, typeof(string), true); },
+ @"The binding context has a Model of type 'System.Int32', but this binder can only operate on models of type 'System.String'.
+Parameter name: bindingContext");
+ }
+
+ [Fact]
+ public void ValidateBindingContextThrowsIfModelIsNullButCannotBe()
+ {
+ // Arrange
+ ExtensibleModelBindingContext bindingContext = new ExtensibleModelBindingContext
+ {
+ ModelMetadata = GetMetadata(typeof(string))
+ };
+
+ // Act & assert
+ Assert.Throws<ArgumentException>(
+ delegate { ModelBinderUtil.ValidateBindingContext(bindingContext, typeof(string), false); },
+ @"The binding context has a null Model, but this binder requires a non-null model of type 'System.String'.
+Parameter name: bindingContext");
+ }
+
+ [Fact]
+ public void ValidateBindingContextThrowsIfModelMetadataIsNull()
+ {
+ // Arrange
+ ExtensibleModelBindingContext bindingContext = new ExtensibleModelBindingContext();
+
+ // Act & assert
+ Assert.Throws<ArgumentException>(
+ delegate { ModelBinderUtil.ValidateBindingContext(bindingContext, typeof(string), true); },
+ @"The binding context cannot have a null ModelMetadata.
+Parameter name: bindingContext");
+ }
+
+ [Fact]
+ public void ValidateBindingContextThrowsIfModelTypeIsWrong()
+ {
+ // Arrange
+ ExtensibleModelBindingContext bindingContext = new ExtensibleModelBindingContext
+ {
+ ModelMetadata = GetMetadata(typeof(object))
+ };
+
+ // Act & assert
+ Assert.Throws<ArgumentException>(
+ delegate { ModelBinderUtil.ValidateBindingContext(bindingContext, typeof(string), true); },
+ @"The binding context has a ModelType of 'System.Object', but this binder can only operate on models of type 'System.String'.
+Parameter name: bindingContext");
+ }
+
+ private static ModelMetadata GetMetadata(Type modelType)
+ {
+ EmptyModelMetadataProvider provider = new EmptyModelMetadataProvider();
+ return provider.GetMetadataForType(null, modelType);
+ }
+
+ private class SampleGenericBinder<T> : IExtensibleModelBinder
+ {
+ public bool BindModel(ControllerContext controllerContext, ExtensibleModelBindingContext bindingContext)
+ {
+ throw new NotImplementedException();
+ }
+ }
+ }
+}
diff --git a/test/Microsoft.Web.Mvc.Test/ModelBinding/Test/ModelValidationNodeTest.cs b/test/Microsoft.Web.Mvc.Test/ModelBinding/Test/ModelValidationNodeTest.cs
new file mode 100644
index 00000000..893e8e85
--- /dev/null
+++ b/test/Microsoft.Web.Mvc.Test/ModelBinding/Test/ModelValidationNodeTest.cs
@@ -0,0 +1,389 @@
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.ComponentModel.DataAnnotations;
+using System.Linq;
+using System.Web.Mvc;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace Microsoft.Web.Mvc.ModelBinding.Test
+{
+ public class ModelValidationNodeTest
+ {
+ [Fact]
+ public void ConstructorSetsCollectionInstance()
+ {
+ // Arrange
+ ModelMetadata metadata = GetModelMetadata();
+ string modelStateKey = "someKey";
+ ModelValidationNode[] childNodes = new[]
+ {
+ new ModelValidationNode(metadata, "someKey0"),
+ new ModelValidationNode(metadata, "someKey1")
+ };
+
+ // Act
+ ModelValidationNode node = new ModelValidationNode(metadata, modelStateKey, childNodes);
+
+ // Assert
+ Assert.Equal(childNodes, node.ChildNodes.ToArray());
+ }
+
+ [Fact]
+ public void ConstructorThrowsIfModelMetadataIsNull()
+ {
+ // Act & assert
+ Assert.ThrowsArgumentNull(
+ delegate { new ModelValidationNode(null, "someKey"); }, "modelMetadata");
+ }
+
+ [Fact]
+ public void ConstructorThrowsIfModelStateKeyIsNull()
+ {
+ // Arrange
+ ModelMetadata metadata = GetModelMetadata();
+
+ // Act & assert
+ Assert.ThrowsArgumentNull(
+ delegate { new ModelValidationNode(metadata, null); }, "modelStateKey");
+ }
+
+ [Fact]
+ public void PropertiesAreSet()
+ {
+ // Arrange
+ ModelMetadata metadata = GetModelMetadata();
+ string modelStateKey = "someKey";
+
+ // Act
+ ModelValidationNode node = new ModelValidationNode(metadata, modelStateKey);
+
+ // Assert
+ Assert.Equal(metadata, node.ModelMetadata);
+ Assert.Equal(modelStateKey, node.ModelStateKey);
+ Assert.NotNull(node.ChildNodes);
+ Assert.Empty(node.ChildNodes);
+ }
+
+ [Fact]
+ public void CombineWith()
+ {
+ // Arrange
+ List<string> log = new List<string>();
+
+ ModelValidationNode[] allChildNodes = new[]
+ {
+ new ModelValidationNode(GetModelMetadata(), "key1"),
+ new ModelValidationNode(GetModelMetadata(), "key2"),
+ new ModelValidationNode(GetModelMetadata(), "key3"),
+ };
+
+ ModelValidationNode parentNode1 = new ModelValidationNode(GetModelMetadata(), "parent1");
+ parentNode1.ChildNodes.Add(allChildNodes[0]);
+ parentNode1.Validating += delegate { log.Add("Validating parent1."); };
+ parentNode1.Validated += delegate { log.Add("Validated parent1."); };
+
+ ModelValidationNode parentNode2 = new ModelValidationNode(GetModelMetadata(), "parent2");
+ parentNode2.ChildNodes.Add(allChildNodes[1]);
+ parentNode2.ChildNodes.Add(allChildNodes[2]);
+ parentNode2.Validating += delegate { log.Add("Validating parent2."); };
+ parentNode2.Validated += delegate { log.Add("Validated parent2."); };
+
+ // Act
+ parentNode1.CombineWith(parentNode2);
+ parentNode1.Validate(new ControllerContext { Controller = new EmptyController() });
+
+ // Assert
+ Assert.Equal(new[] { "Validating parent1.", "Validating parent2.", "Validated parent1.", "Validated parent2." }, log.ToArray());
+ Assert.Equal(allChildNodes, parentNode1.ChildNodes.ToArray());
+ }
+
+ [Fact]
+ public void CombineWith_OtherNodeIsSuppressed_DoesNothing()
+ {
+ // Arrange
+ List<string> log = new List<string>();
+
+ ModelValidationNode[] allChildNodes = new[]
+ {
+ new ModelValidationNode(GetModelMetadata(), "key1"),
+ new ModelValidationNode(GetModelMetadata(), "key2"),
+ new ModelValidationNode(GetModelMetadata(), "key3"),
+ };
+
+ ModelValidationNode[] expectedChildNodes = new[]
+ {
+ allChildNodes[0]
+ };
+
+ ModelValidationNode parentNode1 = new ModelValidationNode(GetModelMetadata(), "parent1");
+ parentNode1.ChildNodes.Add(allChildNodes[0]);
+ parentNode1.Validating += delegate { log.Add("Validating parent1."); };
+ parentNode1.Validated += delegate { log.Add("Validated parent1."); };
+
+ ModelValidationNode parentNode2 = new ModelValidationNode(GetModelMetadata(), "parent2");
+ parentNode2.ChildNodes.Add(allChildNodes[1]);
+ parentNode2.ChildNodes.Add(allChildNodes[2]);
+ parentNode2.Validating += delegate { log.Add("Validating parent2."); };
+ parentNode2.Validated += delegate { log.Add("Validated parent2."); };
+ parentNode2.SuppressValidation = true;
+
+ // Act
+ parentNode1.CombineWith(parentNode2);
+ parentNode1.Validate(new ControllerContext { Controller = new EmptyController() });
+
+ // Assert
+ Assert.Equal(new[] { "Validating parent1.", "Validated parent1." }, log.ToArray());
+ Assert.Equal(expectedChildNodes, parentNode1.ChildNodes.ToArray());
+ }
+
+ [Fact]
+ public void Validate_Ordering()
+ {
+ // Proper order of invocation:
+ // 1. OnValidating()
+ // 2. Child validators
+ // 3. This validator
+ // 4. OnValidated()
+
+ // Arrange
+ List<string> log = new List<string>();
+ LoggingDataErrorInfoModel model = new LoggingDataErrorInfoModel(log);
+ ModelMetadata modelMetadata = GetModelMetadata(model);
+
+ ControllerContext controllerContext = new ControllerContext
+ {
+ Controller = new EmptyController()
+ };
+ ModelValidationNode node = new ModelValidationNode(modelMetadata, "theKey");
+
+ ModelMetadata childMetadata = new EmptyModelMetadataProvider().GetMetadataForProperty(() => model, model.GetType(), "ValidStringProperty");
+ node.ChildNodes.Add(new ModelValidationNode(childMetadata, "theKey.ValidStringProperty"));
+
+ node.Validating += delegate { log.Add("In OnValidating()"); };
+ node.Validated += delegate { log.Add("In OnValidated()"); };
+
+ // Act
+ node.Validate(controllerContext);
+
+ // Assert
+ Assert.Equal(new[] { "In OnValidating()", "In IDataErrorInfo.get_Item('ValidStringProperty')", "In IDataErrorInfo.get_Error()", "In OnValidated()" }, log.ToArray());
+ }
+
+ [Fact]
+ public void Validate_PassesNullContainerInstanceIfCannotBeConvertedToProperType()
+ {
+ // Arrange
+ List<string> log1 = new List<string>();
+ LoggingDataErrorInfoModel model1 = new LoggingDataErrorInfoModel(log1);
+ ModelMetadata modelMetadata1 = GetModelMetadata(model1);
+
+ List<string> log2 = new List<string>();
+ LoggingDataErrorInfoModel model2 = new LoggingDataErrorInfoModel(log2);
+ ModelMetadata modelMetadata2 = GetModelMetadata(model2);
+
+ ControllerContext controllerContext = new ControllerContext
+ {
+ Controller = new EmptyController()
+ };
+ ModelValidationNode node = new ModelValidationNode(modelMetadata1, "theKey");
+ node.ChildNodes.Add(new ModelValidationNode(modelMetadata2, "theKey.SomeProperty"));
+
+ // Act
+ node.Validate(controllerContext);
+
+ // Assert
+ Assert.Equal(new[] { "In IDataErrorInfo.get_Error()" }, log1.ToArray());
+ Assert.Equal(new[] { "In IDataErrorInfo.get_Error()" }, log2.ToArray());
+ }
+
+ [Fact]
+ public void Validate_SkipsRemainingValidationIfModelStateIsInvalid()
+ {
+ // Because a property validator fails, the model validator shouldn't run
+
+ // Arrange
+ List<string> log = new List<string>();
+ LoggingDataErrorInfoModel model = new LoggingDataErrorInfoModel(log);
+ ModelMetadata modelMetadata = GetModelMetadata(model);
+
+ ControllerContext controllerContext = new ControllerContext
+ {
+ Controller = new EmptyController()
+ };
+ ModelValidationNode node = new ModelValidationNode(modelMetadata, "theKey");
+
+ ModelMetadata childMetadata = new EmptyModelMetadataProvider().GetMetadataForProperty(() => model, model.GetType(), "InvalidStringProperty");
+ node.ChildNodes.Add(new ModelValidationNode(childMetadata, "theKey.InvalidStringProperty"));
+
+ node.Validating += delegate { log.Add("In OnValidating()"); };
+ node.Validated += delegate { log.Add("In OnValidated()"); };
+
+ // Act
+ node.Validate(controllerContext);
+
+ // Assert
+ Assert.Equal(new[] { "In OnValidating()", "In IDataErrorInfo.get_Item('InvalidStringProperty')", "In OnValidated()" }, log.ToArray());
+ Assert.Equal("Sample error message", controllerContext.Controller.ViewData.ModelState["theKey.InvalidStringProperty"].Errors[0].ErrorMessage);
+ }
+
+ [Fact]
+ public void Validate_SkipsValidationIfHandlerCancels()
+ {
+ // Arrange
+ List<string> log = new List<string>();
+ LoggingDataErrorInfoModel model = new LoggingDataErrorInfoModel(log);
+ ModelMetadata modelMetadata = GetModelMetadata(model);
+
+ ControllerContext controllerContext = new ControllerContext
+ {
+ Controller = new EmptyController()
+ };
+ ModelValidationNode node = new ModelValidationNode(modelMetadata, "theKey");
+
+ node.Validating += (sender, e) =>
+ {
+ log.Add("In OnValidating()");
+ e.Cancel = true;
+ };
+ node.Validated += delegate { log.Add("In OnValidated()"); };
+
+ // Act
+ node.Validate(controllerContext);
+
+ // Assert
+ Assert.Equal(new[] { "In OnValidating()" }, log.ToArray());
+ }
+
+ [Fact]
+ public void Validate_SkipsValidationIfSuppressed()
+ {
+ // Arrange
+ List<string> log = new List<string>();
+ LoggingDataErrorInfoModel model = new LoggingDataErrorInfoModel(log);
+ ModelMetadata modelMetadata = GetModelMetadata(model);
+
+ ControllerContext controllerContext = new ControllerContext
+ {
+ Controller = new EmptyController()
+ };
+ ModelValidationNode node = new ModelValidationNode(modelMetadata, "theKey")
+ {
+ SuppressValidation = true
+ };
+
+ node.Validating += (sender, e) => { log.Add("In OnValidating()"); };
+ node.Validated += delegate { log.Add("In OnValidated()"); };
+
+ // Act
+ node.Validate(controllerContext);
+
+ // Assert
+ Assert.Empty(log);
+ }
+
+ [Fact]
+ public void Validate_ThrowsIfControllerContextIsNull()
+ {
+ // Arrange
+ ModelValidationNode node = new ModelValidationNode(GetModelMetadata(), "someKey");
+
+ // Act & assert
+ Assert.ThrowsArgumentNull(
+ delegate { node.Validate(null); }, "controllerContext");
+ }
+
+ [Fact]
+ public void Validate_ValidateAllProperties_AddsValidationErrors()
+ {
+ // Arrange
+ ValidateAllPropertiesModel model = new ValidateAllPropertiesModel
+ {
+ RequiredString = null /* error */,
+ RangedInt = 0 /* error */,
+ ValidString = "dog"
+ };
+
+ ModelMetadata modelMetadata = GetModelMetadata(model);
+ ControllerContext controllerContext = new ControllerContext
+ {
+ Controller = new EmptyController()
+ };
+ ModelValidationNode node = new ModelValidationNode(modelMetadata, "theKey")
+ {
+ ValidateAllProperties = true
+ };
+
+ controllerContext.Controller.ViewData.ModelState.AddModelError("theKey.RequiredString.Dummy", "existing Error Text");
+
+ // Act
+ node.Validate(controllerContext);
+
+ // Assert
+ Assert.Null(controllerContext.Controller.ViewData.ModelState["theKey.RequiredString"]);
+ Assert.Equal("existing Error Text", controllerContext.Controller.ViewData.ModelState["theKey.RequiredString.Dummy"].Errors[0].ErrorMessage);
+ Assert.Equal("The field RangedInt must be between 10 and 30.", controllerContext.Controller.ViewData.ModelState["theKey.RangedInt"].Errors[0].ErrorMessage);
+ Assert.Null(controllerContext.Controller.ViewData.ModelState["theKey.ValidString"]);
+ Assert.Null(controllerContext.Controller.ViewData.ModelState["theKey"]);
+ }
+
+ private static ModelMetadata GetModelMetadata()
+ {
+ EmptyModelMetadataProvider provider = new EmptyModelMetadataProvider();
+ return provider.GetMetadataForType(null, typeof(object));
+ }
+
+ private static ModelMetadata GetModelMetadata(object o)
+ {
+ DataAnnotationsModelMetadataProvider provider = new DataAnnotationsModelMetadataProvider();
+ return provider.GetMetadataForType(() => o, o.GetType());
+ }
+
+ private sealed class EmptyController : Controller
+ {
+ }
+
+ private sealed class LoggingDataErrorInfoModel : IDataErrorInfo
+ {
+ private readonly IList<string> _log;
+
+ public LoggingDataErrorInfoModel(IList<string> log)
+ {
+ _log = log;
+ }
+
+ string IDataErrorInfo.Error
+ {
+ get
+ {
+ _log.Add("In IDataErrorInfo.get_Error()");
+ return null;
+ }
+ }
+
+ string IDataErrorInfo.this[string columnName]
+ {
+ get
+ {
+ _log.Add("In IDataErrorInfo.get_Item('" + columnName + "')");
+ return (columnName == "ValidStringProperty") ? null : "Sample error message";
+ }
+ }
+
+ public string ValidStringProperty { get; set; }
+ public string InvalidStringProperty { get; set; }
+ }
+
+ private class ValidateAllPropertiesModel
+ {
+ [Required]
+ public string RequiredString { get; set; }
+
+ [Range(10, 30)]
+ public int RangedInt { get; set; }
+
+ [RegularExpression("dog")]
+ public string ValidString { get; set; }
+ }
+ }
+}
diff --git a/test/Microsoft.Web.Mvc.Test/ModelBinding/Test/MutableObjectModelBinderProviderTest.cs b/test/Microsoft.Web.Mvc.Test/ModelBinding/Test/MutableObjectModelBinderProviderTest.cs
new file mode 100644
index 00000000..f4c06ac5
--- /dev/null
+++ b/test/Microsoft.Web.Mvc.Test/ModelBinding/Test/MutableObjectModelBinderProviderTest.cs
@@ -0,0 +1,76 @@
+using System.Web.Mvc;
+using Microsoft.Web.UnitTestUtil;
+using Xunit;
+
+namespace Microsoft.Web.Mvc.ModelBinding.Test
+{
+ public class MutableObjectModelBinderProviderTest
+ {
+ [Fact]
+ public void GetBinder_NoPrefixInValueProvider_ReturnsNull()
+ {
+ // Arrange
+ ExtensibleModelBindingContext bindingContext = new ExtensibleModelBindingContext
+ {
+ ModelMetadata = new EmptyModelMetadataProvider().GetMetadataForType(() => 42, typeof(int)),
+ ModelName = "foo",
+ ValueProvider = new SimpleValueProvider()
+ };
+
+ MutableObjectModelBinderProvider binderProvider = new MutableObjectModelBinderProvider();
+
+ // Act
+ IExtensibleModelBinder binder = binderProvider.GetBinder(null, bindingContext);
+
+ // Assert
+ Assert.Null(binder);
+ }
+
+ [Fact]
+ public void GetBinder_PrefixInValueProvider_ReturnsBinder()
+ {
+ // Arrange
+ ExtensibleModelBindingContext bindingContext = new ExtensibleModelBindingContext
+ {
+ ModelMetadata = new EmptyModelMetadataProvider().GetMetadataForType(() => 42, typeof(int)),
+ ModelName = "foo",
+ ValueProvider = new SimpleValueProvider
+ {
+ { "foo.bar", "someValue" }
+ }
+ };
+
+ MutableObjectModelBinderProvider binderProvider = new MutableObjectModelBinderProvider();
+
+ // Act
+ IExtensibleModelBinder binder = binderProvider.GetBinder(null, bindingContext);
+
+ // Assert
+ Assert.NotNull(binder);
+ Assert.IsType<MutableObjectModelBinder>(binder);
+ }
+
+ [Fact]
+ public void GetBinder_TypeIsComplexModelDto_ReturnsNull()
+ {
+ // Arrange
+ ExtensibleModelBindingContext bindingContext = new ExtensibleModelBindingContext
+ {
+ ModelMetadata = new EmptyModelMetadataProvider().GetMetadataForType(null, typeof(ComplexModelDto)),
+ ModelName = "foo",
+ ValueProvider = new SimpleValueProvider
+ {
+ { "foo.bar", "someValue" }
+ }
+ };
+
+ MutableObjectModelBinderProvider binderProvider = new MutableObjectModelBinderProvider();
+
+ // Act
+ IExtensibleModelBinder binder = binderProvider.GetBinder(null, bindingContext);
+
+ // Assert
+ Assert.Null(binder);
+ }
+ }
+}
diff --git a/test/Microsoft.Web.Mvc.Test/ModelBinding/Test/MutableObjectModelBinderTest.cs b/test/Microsoft.Web.Mvc.Test/ModelBinding/Test/MutableObjectModelBinderTest.cs
new file mode 100644
index 00000000..ddaafc08
--- /dev/null
+++ b/test/Microsoft.Web.Mvc.Test/ModelBinding/Test/MutableObjectModelBinderTest.cs
@@ -0,0 +1,769 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.ComponentModel.DataAnnotations;
+using System.Linq;
+using System.Web.Mvc;
+using Moq;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace Microsoft.Web.Mvc.ModelBinding.Test
+{
+ public class MutableObjectModelBinderTest
+ {
+ [Fact]
+ public void BindModel()
+ {
+ // Arrange
+ ControllerContext controllerContext = new ControllerContext();
+ ExtensibleModelBindingContext bindingContext = new ExtensibleModelBindingContext
+ {
+ ModelBinderProviders = new ModelBinderProviderCollection(),
+ ModelMetadata = GetMetadataForObject(new Person()),
+ ModelName = "someName"
+ };
+
+ Mock<IExtensibleModelBinder> mockDtoBinder = new Mock<IExtensibleModelBinder>();
+ mockDtoBinder
+ .Setup(o => o.BindModel(controllerContext, It.IsAny<ExtensibleModelBindingContext>()))
+ .Returns(
+ delegate(ControllerContext cc, ExtensibleModelBindingContext mbc2)
+ {
+ return true; // just return the DTO unchanged
+ });
+ bindingContext.ModelBinderProviders.RegisterBinderForType(typeof(ComplexModelDto), mockDtoBinder.Object, true /* suppressPrefixCheck */);
+
+ Mock<TestableMutableObjectModelBinder> mockTestableBinder = new Mock<TestableMutableObjectModelBinder> { CallBase = true };
+ mockTestableBinder.Setup(o => o.EnsureModelPublic(controllerContext, bindingContext)).Verifiable();
+ mockTestableBinder.Setup(o => o.GetMetadataForPropertiesPublic(controllerContext, bindingContext)).Returns(new ModelMetadata[0]).Verifiable();
+ TestableMutableObjectModelBinder testableBinder = mockTestableBinder.Object;
+ testableBinder.MetadataProvider = new DataAnnotationsModelMetadataProvider();
+
+ // Act
+ bool retValue = testableBinder.BindModel(controllerContext, bindingContext);
+
+ // Assert
+ Assert.True(retValue);
+ Assert.IsType<Person>(bindingContext.Model);
+ Assert.True(bindingContext.ValidationNode.ValidateAllProperties);
+ mockTestableBinder.Verify();
+ }
+
+ [Fact]
+ public void CanUpdateProperty_HasPublicSetter_ReturnsTrue()
+ {
+ // Arrange
+ ModelMetadata propertyMetadata = GetMetadataForCanUpdateProperty("ReadWriteString");
+
+ // Act
+ bool canUpdate = MutableObjectModelBinder.CanUpdatePropertyInternal(propertyMetadata);
+
+ // Assert
+ Assert.True(canUpdate);
+ }
+
+ [Fact]
+ public void CanUpdateProperty_ReadOnlyArray_ReturnsFalse()
+ {
+ // Arrange
+ ModelMetadata propertyMetadata = GetMetadataForCanUpdateProperty("ReadOnlyArray");
+
+ // Act
+ bool canUpdate = MutableObjectModelBinder.CanUpdatePropertyInternal(propertyMetadata);
+
+ // Assert
+ Assert.False(canUpdate);
+ }
+
+ [Fact]
+ public void CanUpdateProperty_ReadOnlyReferenceTypeNotBlacklisted_ReturnsTrue()
+ {
+ // Arrange
+ ModelMetadata propertyMetadata = GetMetadataForCanUpdateProperty("ReadOnlyObject");
+
+ // Act
+ bool canUpdate = MutableObjectModelBinder.CanUpdatePropertyInternal(propertyMetadata);
+
+ // Assert
+ Assert.True(canUpdate);
+ }
+
+ [Fact]
+ public void CanUpdateProperty_ReadOnlyString_ReturnsFalse()
+ {
+ // Arrange
+ ModelMetadata propertyMetadata = GetMetadataForCanUpdateProperty("ReadOnlyString");
+
+ // Act
+ bool canUpdate = MutableObjectModelBinder.CanUpdatePropertyInternal(propertyMetadata);
+
+ // Assert
+ Assert.False(canUpdate);
+ }
+
+ [Fact]
+ public void CanUpdateProperty_ReadOnlyValueType_ReturnsFalse()
+ {
+ // Arrange
+ ModelMetadata propertyMetadata = GetMetadataForCanUpdateProperty("ReadOnlyInt");
+
+ // Act
+ bool canUpdate = MutableObjectModelBinder.CanUpdatePropertyInternal(propertyMetadata);
+
+ // Assert
+ Assert.False(canUpdate);
+ }
+
+ [Fact]
+ public void CreateModel()
+ {
+ // Arrange
+ ExtensibleModelBindingContext bindingContext = new ExtensibleModelBindingContext
+ {
+ ModelMetadata = GetMetadataForType(typeof(Person))
+ };
+
+ TestableMutableObjectModelBinder testableBinder = new TestableMutableObjectModelBinder();
+
+ // Act
+ object retModel = testableBinder.CreateModelPublic(null, bindingContext);
+
+ // Assert
+ Assert.IsType<Person>(retModel);
+ }
+
+ [Fact]
+ public void EnsureModel_ModelIsNotNull_DoesNothing()
+ {
+ // Arrange
+ ExtensibleModelBindingContext bindingContext = new ExtensibleModelBindingContext
+ {
+ ModelMetadata = GetMetadataForObject(new Person())
+ };
+
+ Mock<TestableMutableObjectModelBinder> mockTestableBinder = new Mock<TestableMutableObjectModelBinder> { CallBase = true };
+ TestableMutableObjectModelBinder testableBinder = mockTestableBinder.Object;
+
+ // Act
+ object originalModel = bindingContext.Model;
+ testableBinder.EnsureModelPublic(null, bindingContext);
+ object newModel = bindingContext.Model;
+
+ // Assert
+ Assert.Same(originalModel, newModel);
+ mockTestableBinder.Verify(o => o.CreateModelPublic(null, bindingContext), Times.Never());
+ }
+
+ [Fact]
+ public void EnsureModel_ModelIsNull_CallsCreateModel()
+ {
+ // Arrange
+ ExtensibleModelBindingContext bindingContext = new ExtensibleModelBindingContext
+ {
+ ModelMetadata = GetMetadataForType(typeof(Person))
+ };
+
+ Mock<TestableMutableObjectModelBinder> mockTestableBinder = new Mock<TestableMutableObjectModelBinder> { CallBase = true };
+ mockTestableBinder.Setup(o => o.CreateModelPublic(null, bindingContext)).Returns(new Person()).Verifiable();
+ TestableMutableObjectModelBinder testableBinder = mockTestableBinder.Object;
+
+ // Act
+ object originalModel = bindingContext.Model;
+ testableBinder.EnsureModelPublic(null, bindingContext);
+ object newModel = bindingContext.Model;
+
+ // Assert
+ Assert.Null(originalModel);
+ Assert.IsType<Person>(newModel);
+ mockTestableBinder.Verify();
+ }
+
+ [Fact]
+ public void GetMetadataForProperties_WithBindAttribute()
+ {
+ // Arrange
+ string[] expectedPropertyNames = new[] { "FirstName", "LastName" };
+
+ ExtensibleModelBindingContext bindingContext = new ExtensibleModelBindingContext
+ {
+ ModelMetadata = GetMetadataForType(typeof(PersonWithBindExclusion))
+ };
+
+ TestableMutableObjectModelBinder testableBinder = new TestableMutableObjectModelBinder();
+
+ // Act
+ IEnumerable<ModelMetadata> propertyMetadatas = testableBinder.GetMetadataForPropertiesPublic(null, bindingContext);
+ string[] returnedPropertyNames = propertyMetadatas.Select(o => o.PropertyName).ToArray();
+
+ // Assert
+ Assert.Equal(expectedPropertyNames, returnedPropertyNames);
+ }
+
+ [Fact]
+ public void GetMetadataForProperties_WithoutBindAttribute()
+ {
+ // Arrange
+ string[] expectedPropertyNames = new[] { "DateOfBirth", "DateOfDeath", "ValueTypeRequired", "FirstName", "LastName", "PropertyWithDefaultValue" };
+
+ ExtensibleModelBindingContext bindingContext = new ExtensibleModelBindingContext
+ {
+ ModelMetadata = GetMetadataForType(typeof(Person))
+ };
+
+ TestableMutableObjectModelBinder testableBinder = new TestableMutableObjectModelBinder();
+
+ // Act
+ IEnumerable<ModelMetadata> propertyMetadatas = testableBinder.GetMetadataForPropertiesPublic(null, bindingContext);
+ string[] returnedPropertyNames = propertyMetadatas.Select(o => o.PropertyName).ToArray();
+
+ // Assert
+ Assert.Equal(expectedPropertyNames, returnedPropertyNames);
+ }
+
+ [Fact]
+ public void GetRequiredPropertiesCollection_MixedAttributes()
+ {
+ // Arrange
+ Type modelType = typeof(ModelWithMixedBindingBehaviors);
+
+ // Act
+ HashSet<string> requiredProperties;
+ HashSet<string> skipProperties;
+ MutableObjectModelBinder.GetRequiredPropertiesCollection(modelType, out requiredProperties, out skipProperties);
+
+ // Assert
+ Assert.Equal(new[] { "Required" }, requiredProperties.ToArray());
+ Assert.Equal(new[] { "Never" }, skipProperties.ToArray());
+ }
+
+ [Fact]
+ public void NullCheckFailedHandler_ModelStateAlreadyInvalid_DoesNothing()
+ {
+ // Arrange
+ ControllerContext controllerContext = new ControllerContext
+ {
+ Controller = new EmptyController()
+ };
+ controllerContext.Controller.ViewData.ModelState.AddModelError("foo.bar", "Some existing error.");
+
+ ModelMetadata modelMetadata = GetMetadataForType(typeof(Person));
+ ModelValidationNode validationNode = new ModelValidationNode(modelMetadata, "foo");
+ ModelValidatedEventArgs e = new ModelValidatedEventArgs(controllerContext, null /* parentNode */);
+
+ // Act
+ EventHandler<ModelValidatedEventArgs> handler = MutableObjectModelBinder.CreateNullCheckFailedHandler(controllerContext, modelMetadata, null /* incomingValue */);
+ handler(validationNode, e);
+
+ // Assert
+ Assert.False(controllerContext.Controller.ViewData.ModelState.ContainsKey("foo"));
+ }
+
+ [Fact]
+ public void NullCheckFailedHandler_ModelStateValid_AddsErrorString()
+ {
+ // Arrange
+ ControllerContext controllerContext = new ControllerContext
+ {
+ Controller = new EmptyController()
+ };
+
+ ModelMetadata modelMetadata = GetMetadataForType(typeof(Person));
+ ModelValidationNode validationNode = new ModelValidationNode(modelMetadata, "foo");
+ ModelValidatedEventArgs e = new ModelValidatedEventArgs(controllerContext, null /* parentNode */);
+
+ // Act
+ EventHandler<ModelValidatedEventArgs> handler = MutableObjectModelBinder.CreateNullCheckFailedHandler(controllerContext, modelMetadata, null /* incomingValue */);
+ handler(validationNode, e);
+
+ // Assert
+ Assert.True(controllerContext.Controller.ViewData.ModelState.ContainsKey("foo"));
+ Assert.Equal("A value is required.", controllerContext.Controller.ViewData.ModelState["foo"].Errors[0].ErrorMessage);
+ }
+
+ [Fact]
+ public void NullCheckFailedHandler_ModelStateValid_CallbackReturnsNull_DoesNothing()
+ {
+ // Arrange
+ ControllerContext controllerContext = new ControllerContext
+ {
+ Controller = new EmptyController()
+ };
+
+ ModelMetadata modelMetadata = GetMetadataForType(typeof(Person));
+ ModelValidationNode validationNode = new ModelValidationNode(modelMetadata, "foo");
+ ModelValidatedEventArgs e = new ModelValidatedEventArgs(controllerContext, null /* parentNode */);
+
+ // Act
+ ModelBinderErrorMessageProvider originalProvider = ModelBinderConfig.ValueRequiredErrorMessageProvider;
+ try
+ {
+ ModelBinderConfig.ValueRequiredErrorMessageProvider = delegate { return null; };
+ EventHandler<ModelValidatedEventArgs> handler = MutableObjectModelBinder.CreateNullCheckFailedHandler(controllerContext, modelMetadata, null /* incomingValue */);
+ handler(validationNode, e);
+ }
+ finally
+ {
+ ModelBinderConfig.ValueRequiredErrorMessageProvider = originalProvider;
+ }
+
+ // Assert
+ Assert.True(controllerContext.Controller.ViewData.ModelState.IsValid);
+ }
+
+ [Fact]
+ public void ProcessDto_BindRequiredFieldMissing_Throws()
+ {
+ // Arrange
+ ModelWithBindRequired model = new ModelWithBindRequired
+ {
+ Name = "original value",
+ Age = -20
+ };
+
+ ModelMetadata containerMetadata = GetMetadataForObject(model);
+
+ ControllerContext controllerContext = new ControllerContext();
+ ExtensibleModelBindingContext bindingContext = new ExtensibleModelBindingContext
+ {
+ ModelMetadata = containerMetadata,
+ ModelName = "theModel"
+ };
+ ComplexModelDto dto = new ComplexModelDto(containerMetadata, containerMetadata.Properties);
+
+ ModelMetadata nameProperty = dto.PropertyMetadata.Single(o => o.PropertyName == "Name");
+ dto.Results[nameProperty] = new ComplexModelDtoResult("John Doe", new ModelValidationNode(nameProperty, ""));
+
+ TestableMutableObjectModelBinder testableBinder = new TestableMutableObjectModelBinder();
+
+ // Act & assert
+ Assert.Throws<InvalidOperationException>(
+ delegate { testableBinder.ProcessDto(controllerContext, bindingContext, dto); },
+ @"A value for 'theModel.Age' is required but was not present in the request.");
+
+ Assert.Equal("original value", model.Name);
+ Assert.Equal(-20, model.Age);
+ }
+
+ [Fact]
+ public void ProcessDto_Success()
+ {
+ // Arrange
+ DateTime dob = new DateTime(2001, 1, 1);
+ Person model = new Person
+ {
+ DateOfBirth = dob
+ };
+ ModelMetadata containerMetadata = GetMetadataForObject(model);
+
+ ControllerContext controllerContext = new ControllerContext();
+ ExtensibleModelBindingContext bindingContext = new ExtensibleModelBindingContext
+ {
+ ModelMetadata = containerMetadata
+ };
+ ComplexModelDto dto = new ComplexModelDto(containerMetadata, containerMetadata.Properties);
+
+ ModelMetadata firstNameProperty = dto.PropertyMetadata.Single(o => o.PropertyName == "FirstName");
+ dto.Results[firstNameProperty] = new ComplexModelDtoResult("John", new ModelValidationNode(firstNameProperty, ""));
+ ModelMetadata lastNameProperty = dto.PropertyMetadata.Single(o => o.PropertyName == "LastName");
+ dto.Results[lastNameProperty] = new ComplexModelDtoResult("Doe", new ModelValidationNode(lastNameProperty, ""));
+ ModelMetadata dobProperty = dto.PropertyMetadata.Single(o => o.PropertyName == "DateOfBirth");
+ dto.Results[dobProperty] = null;
+
+ TestableMutableObjectModelBinder testableBinder = new TestableMutableObjectModelBinder();
+
+ // Act
+ testableBinder.ProcessDto(controllerContext, bindingContext, dto);
+
+ // Assert
+ Assert.Equal("John", model.FirstName);
+ Assert.Equal("Doe", model.LastName);
+ Assert.Equal(dob, model.DateOfBirth);
+ Assert.True(bindingContext.ModelState.IsValid);
+ }
+
+ [Fact]
+ public void SetProperty_PropertyHasDefaultValue_SetsDefaultValue()
+ {
+ // Arrange
+ ControllerContext controllerContext = new ControllerContext
+ {
+ Controller = new EmptyController()
+ };
+ ExtensibleModelBindingContext bindingContext = new ExtensibleModelBindingContext
+ {
+ ModelMetadata = GetMetadataForObject(new Person())
+ };
+
+ ModelMetadata propertyMetadata = bindingContext.ModelMetadata.Properties.Single(o => o.PropertyName == "PropertyWithDefaultValue");
+ ModelValidationNode validationNode = new ModelValidationNode(propertyMetadata, "foo");
+ ComplexModelDtoResult dtoResult = new ComplexModelDtoResult(null /* model */, validationNode);
+
+ TestableMutableObjectModelBinder testableBinder = new TestableMutableObjectModelBinder();
+
+ // Act
+ testableBinder.SetPropertyPublic(controllerContext, bindingContext, propertyMetadata, dtoResult);
+
+ // Assert
+ var person = Assert.IsType<Person>(bindingContext.Model);
+ Assert.Equal(123.456m, person.PropertyWithDefaultValue);
+ Assert.True(controllerContext.Controller.ViewData.ModelState.IsValid);
+ }
+
+ [Fact]
+ public void SetProperty_PropertyIsReadOnly_DoesNothing()
+ {
+ // Arrange
+ ExtensibleModelBindingContext bindingContext = new ExtensibleModelBindingContext
+ {
+ ModelMetadata = GetMetadataForType(typeof(Person))
+ };
+
+ ModelMetadata propertyMetadata = bindingContext.ModelMetadata.Properties.Single(o => o.PropertyName == "NonUpdateableProperty");
+ ModelValidationNode validationNode = new ModelValidationNode(propertyMetadata, "foo");
+ ComplexModelDtoResult dtoResult = new ComplexModelDtoResult(null /* model */, validationNode);
+
+ TestableMutableObjectModelBinder testableBinder = new TestableMutableObjectModelBinder();
+
+ // Act
+ testableBinder.SetPropertyPublic(null, bindingContext, propertyMetadata, dtoResult);
+
+ // Assert
+ // If didn't throw, success!
+ }
+
+ [Fact]
+ public void SetProperty_PropertyIsSettable_CallsSetter()
+ {
+ // Arrange
+ Person model = new Person();
+ ControllerContext controllerContext = new ControllerContext
+ {
+ Controller = new EmptyController()
+ };
+ ExtensibleModelBindingContext bindingContext = new ExtensibleModelBindingContext
+ {
+ ModelMetadata = GetMetadataForObject(model)
+ };
+
+ ModelMetadata propertyMetadata = bindingContext.ModelMetadata.Properties.Single(o => o.PropertyName == "DateOfBirth");
+ ModelValidationNode validationNode = new ModelValidationNode(propertyMetadata, "foo");
+ ComplexModelDtoResult dtoResult = new ComplexModelDtoResult(new DateTime(2001, 1, 1), validationNode);
+
+ TestableMutableObjectModelBinder testableBinder = new TestableMutableObjectModelBinder();
+
+ // Act
+ testableBinder.SetPropertyPublic(controllerContext, bindingContext, propertyMetadata, dtoResult);
+
+ // Assert
+ validationNode.Validate(controllerContext);
+ Assert.True(controllerContext.Controller.ViewData.ModelState.IsValid);
+ Assert.Equal(new DateTime(2001, 1, 1), model.DateOfBirth);
+ }
+
+ [Fact]
+ public void SetProperty_PropertyIsSettable_SetterThrows_RecordsError()
+ {
+ // Arrange
+ Person model = new Person
+ {
+ DateOfBirth = new DateTime(1900, 1, 1)
+ };
+ ExtensibleModelBindingContext bindingContext = new ExtensibleModelBindingContext
+ {
+ ModelMetadata = GetMetadataForObject(model)
+ };
+
+ ModelMetadata propertyMetadata = bindingContext.ModelMetadata.Properties.Single(o => o.PropertyName == "DateOfDeath");
+ ModelValidationNode validationNode = new ModelValidationNode(propertyMetadata, "foo");
+ ComplexModelDtoResult dtoResult = new ComplexModelDtoResult(new DateTime(1800, 1, 1), validationNode);
+
+ TestableMutableObjectModelBinder testableBinder = new TestableMutableObjectModelBinder();
+
+ // Act
+ testableBinder.SetPropertyPublic(null, bindingContext, propertyMetadata, dtoResult);
+
+ // Assert
+ Assert.Equal(@"Date of death can't be before date of birth.
+Parameter name: value", bindingContext.ModelState["foo"].Errors[0].Exception.Message);
+ }
+
+ [Fact]
+ public void SetProperty_SettingNonNullableValueTypeToNull_RequiredValidatorNotPresent_RegistersValidationCallback()
+ {
+ // Arrange
+ ControllerContext controllerContext = new ControllerContext
+ {
+ Controller = new EmptyController()
+ };
+ ExtensibleModelBindingContext bindingContext = new ExtensibleModelBindingContext
+ {
+ ModelMetadata = GetMetadataForObject(new Person()),
+ };
+
+ ModelMetadata propertyMetadata = bindingContext.ModelMetadata.Properties.Single(o => o.PropertyName == "DateOfBirth");
+ ModelValidationNode validationNode = new ModelValidationNode(propertyMetadata, "foo");
+ ComplexModelDtoResult dtoResult = new ComplexModelDtoResult(null /* model */, validationNode);
+
+ TestableMutableObjectModelBinder testableBinder = new TestableMutableObjectModelBinder();
+
+ // Act
+ testableBinder.SetPropertyPublic(controllerContext, bindingContext, propertyMetadata, dtoResult);
+
+ // Assert
+ Assert.True(controllerContext.Controller.ViewData.ModelState.IsValid);
+ validationNode.Validate(controllerContext, bindingContext.ValidationNode);
+ Assert.False(controllerContext.Controller.ViewData.ModelState.IsValid);
+ }
+
+ [Fact]
+ public void SetProperty_SettingNonNullableValueTypeToNull_RequiredValidatorPresent_AddsModelError()
+ {
+ // Arrange
+ ControllerContext controllerContext = new ControllerContext
+ {
+ Controller = new EmptyController()
+ };
+ ExtensibleModelBindingContext bindingContext = new ExtensibleModelBindingContext
+ {
+ ModelMetadata = GetMetadataForObject(new Person()),
+ ModelName = "foo"
+ };
+
+ ModelMetadata propertyMetadata = bindingContext.ModelMetadata.Properties.Single(o => o.PropertyName == "ValueTypeRequired");
+ ModelValidationNode validationNode = new ModelValidationNode(propertyMetadata, "foo.ValueTypeRequired");
+ ComplexModelDtoResult dtoResult = new ComplexModelDtoResult(null /* model */, validationNode);
+
+ TestableMutableObjectModelBinder testableBinder = new TestableMutableObjectModelBinder();
+
+ // Act
+ testableBinder.SetPropertyPublic(controllerContext, bindingContext, propertyMetadata, dtoResult);
+
+ // Assert
+ Assert.False(bindingContext.ModelState.IsValid);
+ Assert.Equal("Sample message", bindingContext.ModelState["foo.ValueTypeRequired"].Errors[0].ErrorMessage);
+ }
+
+ [Fact]
+ public void SetProperty_SettingNullableTypeToNull_RequiredValidatorNotPresent_PropertySetterThrows_AddsRequiredMessageString()
+ {
+ // Arrange
+ ControllerContext controllerContext = new ControllerContext
+ {
+ Controller = new EmptyController()
+ };
+ ExtensibleModelBindingContext bindingContext = new ExtensibleModelBindingContext
+ {
+ ModelMetadata = GetMetadataForObject(new ModelWhosePropertySetterThrows()),
+ ModelName = "foo"
+ };
+
+ ModelMetadata propertyMetadata = bindingContext.ModelMetadata.Properties.Single(o => o.PropertyName == "NameNoAttribute");
+ ModelValidationNode validationNode = new ModelValidationNode(propertyMetadata, "foo.NameNoAttribute");
+ ComplexModelDtoResult dtoResult = new ComplexModelDtoResult(null /* model */, validationNode);
+
+ TestableMutableObjectModelBinder testableBinder = new TestableMutableObjectModelBinder();
+
+ // Act
+ testableBinder.SetPropertyPublic(controllerContext, bindingContext, propertyMetadata, dtoResult);
+
+ // Assert
+ Assert.False(bindingContext.ModelState.IsValid);
+ Assert.Equal(1, bindingContext.ModelState["foo.NameNoAttribute"].Errors.Count);
+ Assert.Equal(@"This is a different exception.
+Parameter name: value", bindingContext.ModelState["foo.NameNoAttribute"].Errors[0].Exception.Message);
+ }
+
+ [Fact]
+ public void SetProperty_SettingNullableTypeToNull_RequiredValidatorPresent_PropertySetterThrows_AddsRequiredMessageString()
+ {
+ // Arrange
+ ControllerContext controllerContext = new ControllerContext
+ {
+ Controller = new EmptyController()
+ };
+ ExtensibleModelBindingContext bindingContext = new ExtensibleModelBindingContext
+ {
+ ModelMetadata = GetMetadataForObject(new ModelWhosePropertySetterThrows()),
+ ModelName = "foo"
+ };
+
+ ModelMetadata propertyMetadata = bindingContext.ModelMetadata.Properties.Single(o => o.PropertyName == "Name");
+ ModelValidationNode validationNode = new ModelValidationNode(propertyMetadata, "foo.Name");
+ ComplexModelDtoResult dtoResult = new ComplexModelDtoResult(null /* model */, validationNode);
+
+ TestableMutableObjectModelBinder testableBinder = new TestableMutableObjectModelBinder();
+
+ // Act
+ testableBinder.SetPropertyPublic(controllerContext, bindingContext, propertyMetadata, dtoResult);
+
+ // Assert
+ Assert.False(bindingContext.ModelState.IsValid);
+ Assert.Equal(1, bindingContext.ModelState["foo.Name"].Errors.Count);
+ Assert.Equal("This message comes from the [Required] attribute.", bindingContext.ModelState["foo.Name"].Errors[0].ErrorMessage);
+ }
+
+ private static ModelMetadata GetMetadataForCanUpdateProperty(string propertyName)
+ {
+ DataAnnotationsModelMetadataProvider metadataProvider = new DataAnnotationsModelMetadataProvider();
+ return metadataProvider.GetMetadataForProperty(null, typeof(MyModelTestingCanUpdateProperty), propertyName);
+ }
+
+ private static ModelMetadata GetMetadataForObject(object o)
+ {
+ DataAnnotationsModelMetadataProvider metadataProvider = new DataAnnotationsModelMetadataProvider();
+ return metadataProvider.GetMetadataForType(() => o, o.GetType());
+ }
+
+ private static ModelMetadata GetMetadataForType(Type t)
+ {
+ DataAnnotationsModelMetadataProvider metadataProvider = new DataAnnotationsModelMetadataProvider();
+ return metadataProvider.GetMetadataForType(null, t);
+ }
+
+ private class Person
+ {
+ private DateTime? _dateOfDeath;
+
+ public DateTime DateOfBirth { get; set; }
+
+ public DateTime? DateOfDeath
+ {
+ get { return _dateOfDeath; }
+ set
+ {
+ if (value < DateOfBirth)
+ {
+ throw new ArgumentOutOfRangeException("value", "Date of death can't be before date of birth.");
+ }
+ _dateOfDeath = value;
+ }
+ }
+
+ [Required(ErrorMessage = "Sample message")]
+ public int ValueTypeRequired { get; set; }
+
+ public string FirstName { get; set; }
+ public string LastName { get; set; }
+ public string NonUpdateableProperty { get; private set; }
+
+ [DefaultValue(typeof(decimal), "123.456")]
+ public decimal PropertyWithDefaultValue { get; set; }
+ }
+
+ private class PersonWithBindExclusion
+ {
+ [BindNever]
+ public DateTime DateOfBirth { get; set; }
+
+ [BindNever]
+ public DateTime? DateOfDeath { get; set; }
+
+ public string FirstName { get; set; }
+ public string LastName { get; set; }
+ public string NonUpdateableProperty { get; private set; }
+ }
+
+ private class ModelWithBindRequired
+ {
+ public string Name { get; set; }
+
+ [BindRequired]
+ public int Age { get; set; }
+ }
+
+ [BindRequired]
+ private class ModelWithMixedBindingBehaviors
+ {
+ public string Required { get; set; }
+
+ [BindNever]
+ public string Never { get; set; }
+
+ [BindingBehavior(BindingBehavior.Optional)]
+ public string Optional { get; set; }
+ }
+
+ private sealed class MyModelTestingCanUpdateProperty
+ {
+ public int ReadOnlyInt { get; private set; }
+ public string ReadOnlyString { get; private set; }
+ public string[] ReadOnlyArray { get; private set; }
+ public object ReadOnlyObject { get; private set; }
+ public string ReadWriteString { get; set; }
+ }
+
+ private sealed class ModelWhosePropertySetterThrows
+ {
+ [Required(ErrorMessage = "This message comes from the [Required] attribute.")]
+ public string Name
+ {
+ get { return null; }
+ set { throw new ArgumentException("This is an exception.", "value"); }
+ }
+
+ public string NameNoAttribute
+ {
+ get { return null; }
+ set { throw new ArgumentException("This is a different exception.", "value"); }
+ }
+ }
+
+ public class TestableMutableObjectModelBinder : MutableObjectModelBinder
+ {
+ public virtual bool CanUpdatePropertyPublic(ModelMetadata propertyMetadata)
+ {
+ return base.CanUpdateProperty(propertyMetadata);
+ }
+
+ protected override bool CanUpdateProperty(ModelMetadata propertyMetadata)
+ {
+ return CanUpdatePropertyPublic(propertyMetadata);
+ }
+
+ public virtual object CreateModelPublic(ControllerContext controllerContext, ExtensibleModelBindingContext bindingContext)
+ {
+ return base.CreateModel(controllerContext, bindingContext);
+ }
+
+ protected override object CreateModel(ControllerContext controllerContext, ExtensibleModelBindingContext bindingContext)
+ {
+ return CreateModelPublic(controllerContext, bindingContext);
+ }
+
+ public virtual void EnsureModelPublic(ControllerContext controllerContext, ExtensibleModelBindingContext bindingContext)
+ {
+ base.EnsureModel(controllerContext, bindingContext);
+ }
+
+ protected override void EnsureModel(ControllerContext controllerContext, ExtensibleModelBindingContext bindingContext)
+ {
+ EnsureModelPublic(controllerContext, bindingContext);
+ }
+
+ public virtual IEnumerable<ModelMetadata> GetMetadataForPropertiesPublic(ControllerContext controllerContext, ExtensibleModelBindingContext bindingContext)
+ {
+ return base.GetMetadataForProperties(controllerContext, bindingContext);
+ }
+
+ protected override IEnumerable<ModelMetadata> GetMetadataForProperties(ControllerContext controllerContext, ExtensibleModelBindingContext bindingContext)
+ {
+ return GetMetadataForPropertiesPublic(controllerContext, bindingContext);
+ }
+
+ public virtual void SetPropertyPublic(ControllerContext controllerContext, ExtensibleModelBindingContext bindingContext, ModelMetadata propertyMetadata, ComplexModelDtoResult dtoResult)
+ {
+ base.SetProperty(controllerContext, bindingContext, propertyMetadata, dtoResult);
+ }
+
+ protected override void SetProperty(ControllerContext controllerContext, ExtensibleModelBindingContext bindingContext, ModelMetadata propertyMetadata, ComplexModelDtoResult dtoResult)
+ {
+ SetPropertyPublic(controllerContext, bindingContext, propertyMetadata, dtoResult);
+ }
+ }
+
+ private class EmptyController : Controller
+ {
+ }
+ }
+}
diff --git a/test/Microsoft.Web.Mvc.Test/ModelBinding/Test/SimpleModelBinderProviderTest.cs b/test/Microsoft.Web.Mvc.Test/ModelBinding/Test/SimpleModelBinderProviderTest.cs
new file mode 100644
index 00000000..33ab6f42
--- /dev/null
+++ b/test/Microsoft.Web.Mvc.Test/ModelBinding/Test/SimpleModelBinderProviderTest.cs
@@ -0,0 +1,152 @@
+using System;
+using System.Web.Mvc;
+using Microsoft.Web.UnitTestUtil;
+using Moq;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace Microsoft.Web.Mvc.ModelBinding.Test
+{
+ public class SimpleModelBinderProviderTest
+ {
+ [Fact]
+ public void ConstructorWithFactoryThrowsIfModelBinderFactoryIsNull()
+ {
+ // Act & assert
+ Assert.ThrowsArgumentNull(
+ delegate { new SimpleModelBinderProvider(typeof(object), (Func<IExtensibleModelBinder>)null); }, "modelBinderFactory");
+ }
+
+ [Fact]
+ public void ConstructorWithFactoryThrowsIfModelTypeIsNull()
+ {
+ // Act & assert
+ Assert.ThrowsArgumentNull(
+ delegate { new SimpleModelBinderProvider(null, () => null); }, "modelType");
+ }
+
+ [Fact]
+ public void ConstructorWithInstanceThrowsIfModelBinderIsNull()
+ {
+ // Act & assert
+ Assert.ThrowsArgumentNull(
+ delegate { new SimpleModelBinderProvider(typeof(object), (IExtensibleModelBinder)null); }, "modelBinder");
+ }
+
+ [Fact]
+ public void ConstructorWithInstanceThrowsIfModelTypeIsNull()
+ {
+ // Act & assert
+ Assert.ThrowsArgumentNull(
+ delegate { new SimpleModelBinderProvider(null, new Mock<IExtensibleModelBinder>().Object); }, "modelType");
+ }
+
+ [Fact]
+ public void GetBinder_TypeDoesNotMatch_ReturnsNull()
+ {
+ // Arrange
+ SimpleModelBinderProvider provider = new SimpleModelBinderProvider(typeof(string), new Mock<IExtensibleModelBinder>().Object)
+ {
+ SuppressPrefixCheck = true
+ };
+ ExtensibleModelBindingContext bindingContext = GetBindingContext(typeof(object));
+
+ // Act
+ IExtensibleModelBinder binder = provider.GetBinder(null, bindingContext);
+
+ // Assert
+ Assert.Null(binder);
+ }
+
+ [Fact]
+ public void GetBinder_TypeMatches_PrefixNotFound_ReturnsNull()
+ {
+ // Arrange
+ IExtensibleModelBinder binderInstance = new Mock<IExtensibleModelBinder>().Object;
+ SimpleModelBinderProvider provider = new SimpleModelBinderProvider(typeof(string), binderInstance);
+
+ ExtensibleModelBindingContext bindingContext = GetBindingContext(typeof(string));
+ bindingContext.ValueProvider = new SimpleValueProvider();
+
+ // Act
+ IExtensibleModelBinder returnedBinder = provider.GetBinder(null, bindingContext);
+
+ // Assert
+ Assert.Null(returnedBinder);
+ }
+
+ [Fact]
+ public void GetBinder_TypeMatches_PrefixSuppressed_ReturnsFactoryInstance()
+ {
+ // Arrange
+ int numExecutions = 0;
+ IExtensibleModelBinder theBinderInstance = new Mock<IExtensibleModelBinder>().Object;
+ Func<IExtensibleModelBinder> factory = delegate
+ {
+ numExecutions++;
+ return theBinderInstance;
+ };
+
+ SimpleModelBinderProvider provider = new SimpleModelBinderProvider(typeof(string), factory)
+ {
+ SuppressPrefixCheck = true
+ };
+ ExtensibleModelBindingContext bindingContext = GetBindingContext(typeof(string));
+
+ // Act
+ IExtensibleModelBinder returnedBinder = provider.GetBinder(null, bindingContext);
+ returnedBinder = provider.GetBinder(null, bindingContext);
+
+ // Assert
+ Assert.Equal(2, numExecutions);
+ Assert.Equal(theBinderInstance, returnedBinder);
+ }
+
+ [Fact]
+ public void GetBinder_TypeMatches_PrefixSuppressed_ReturnsInstance()
+ {
+ // Arrange
+ IExtensibleModelBinder theBinderInstance = new Mock<IExtensibleModelBinder>().Object;
+ SimpleModelBinderProvider provider = new SimpleModelBinderProvider(typeof(string), theBinderInstance)
+ {
+ SuppressPrefixCheck = true
+ };
+ ExtensibleModelBindingContext bindingContext = GetBindingContext(typeof(string));
+
+ // Act
+ IExtensibleModelBinder returnedBinder = provider.GetBinder(null, bindingContext);
+
+ // Assert
+ Assert.Equal(theBinderInstance, returnedBinder);
+ }
+
+ [Fact]
+ public void GetBinderThrowsIfBindingContextIsNull()
+ {
+ // Arrange
+ SimpleModelBinderProvider provider = new SimpleModelBinderProvider(typeof(string), new Mock<IExtensibleModelBinder>().Object);
+
+ // Act & assert
+ Assert.ThrowsArgumentNull(
+ delegate { provider.GetBinder(null, null); }, "bindingContext");
+ }
+
+ [Fact]
+ public void ModelTypeProperty()
+ {
+ // Arrange
+ SimpleModelBinderProvider provider = new SimpleModelBinderProvider(typeof(string), new Mock<IExtensibleModelBinder>().Object);
+
+ // Act & assert
+ Assert.Equal(typeof(string), provider.ModelType);
+ }
+
+ private static ExtensibleModelBindingContext GetBindingContext(Type modelType)
+ {
+ return new ExtensibleModelBindingContext
+ {
+ ModelMetadata = new EmptyModelMetadataProvider().GetMetadataForType(() => null, modelType)
+ };
+ }
+ }
+}
diff --git a/test/Microsoft.Web.Mvc.Test/ModelBinding/Test/TypeConverterModelBinderProviderTest.cs b/test/Microsoft.Web.Mvc.Test/ModelBinding/Test/TypeConverterModelBinderProviderTest.cs
new file mode 100644
index 00000000..6c6b41a3
--- /dev/null
+++ b/test/Microsoft.Web.Mvc.Test/ModelBinding/Test/TypeConverterModelBinderProviderTest.cs
@@ -0,0 +1,69 @@
+using System;
+using System.Web.Mvc;
+using Microsoft.Web.UnitTestUtil;
+using Xunit;
+
+namespace Microsoft.Web.Mvc.ModelBinding.Test
+{
+ public class TypeConverterModelBinderProviderTest
+ {
+ [Fact]
+ public void GetBinder_NoTypeConverterExistsFromString_ReturnsNull()
+ {
+ // Arrange
+ ExtensibleModelBindingContext bindingContext = GetBindingContext(typeof(void)); // no TypeConverter exists Void -> String
+
+ TypeConverterModelBinderProvider provider = new TypeConverterModelBinderProvider();
+
+ // Act
+ IExtensibleModelBinder binder = provider.GetBinder(null, bindingContext);
+
+ // Assert
+ Assert.Null(binder);
+ }
+
+ [Fact]
+ public void GetBinder_NullValueProviderResult_ReturnsNull()
+ {
+ // Arrange
+ ExtensibleModelBindingContext bindingContext = GetBindingContext(typeof(int));
+ bindingContext.ValueProvider = new SimpleValueProvider(); // clear the ValueProvider
+
+ TypeConverterModelBinderProvider provider = new TypeConverterModelBinderProvider();
+
+ // Act
+ IExtensibleModelBinder binder = provider.GetBinder(null, bindingContext);
+
+ // Assert
+ Assert.Null(binder);
+ }
+
+ [Fact]
+ public void GetBinder_TypeConverterExistsFromString_ReturnsNull()
+ {
+ // Arrange
+ ExtensibleModelBindingContext bindingContext = GetBindingContext(typeof(int)); // TypeConverter exists Int32 -> String
+
+ TypeConverterModelBinderProvider provider = new TypeConverterModelBinderProvider();
+
+ // Act
+ IExtensibleModelBinder binder = provider.GetBinder(null, bindingContext);
+
+ // Assert
+ Assert.IsType<TypeConverterModelBinder>(binder);
+ }
+
+ private static ExtensibleModelBindingContext GetBindingContext(Type modelType)
+ {
+ return new ExtensibleModelBindingContext
+ {
+ ModelMetadata = new EmptyModelMetadataProvider().GetMetadataForType(null, modelType),
+ ModelName = "theModelName",
+ ValueProvider = new SimpleValueProvider
+ {
+ { "theModelName", "someValue" }
+ }
+ };
+ }
+ }
+}
diff --git a/test/Microsoft.Web.Mvc.Test/ModelBinding/Test/TypeConverterModelBinderTest.cs b/test/Microsoft.Web.Mvc.Test/ModelBinding/Test/TypeConverterModelBinderTest.cs
new file mode 100644
index 00000000..7788c92a
--- /dev/null
+++ b/test/Microsoft.Web.Mvc.Test/ModelBinding/Test/TypeConverterModelBinderTest.cs
@@ -0,0 +1,171 @@
+using System;
+using System.ComponentModel;
+using System.Globalization;
+using System.Web.Mvc;
+using Microsoft.Web.UnitTestUtil;
+using Xunit;
+
+namespace Microsoft.Web.Mvc.ModelBinding.Test
+{
+ public class TypeConverterModelBinderTest
+ {
+ [Fact]
+ public void BindModel_Error_FormatExceptionsTurnedIntoStringsInModelState()
+ {
+ // Arrange
+ ExtensibleModelBindingContext bindingContext = GetBindingContext(typeof(int));
+ bindingContext.ValueProvider = new SimpleValueProvider
+ {
+ { "theModelName", "not an integer" }
+ };
+
+ TypeConverterModelBinder binder = new TypeConverterModelBinder();
+
+ // Act
+ bool retVal = binder.BindModel(null, bindingContext);
+
+ // Assert
+ Assert.False(retVal);
+ Assert.Equal("The value 'not an integer' is not valid for Int32.", bindingContext.ModelState["theModelName"].Errors[0].ErrorMessage);
+ }
+
+ [Fact]
+ public void BindModel_Error_FormatExceptionsTurnedIntoStringsInModelState_ErrorNotAddedIfCallbackReturnsNull()
+ {
+ // Arrange
+ ExtensibleModelBindingContext bindingContext = GetBindingContext(typeof(int));
+ bindingContext.ValueProvider = new SimpleValueProvider
+ {
+ { "theModelName", "not an integer" }
+ };
+
+ TypeConverterModelBinder binder = new TypeConverterModelBinder();
+
+ // Act
+ ModelBinderErrorMessageProvider originalProvider = ModelBinderConfig.TypeConversionErrorMessageProvider;
+ bool retVal;
+ try
+ {
+ ModelBinderConfig.TypeConversionErrorMessageProvider = delegate { return null; };
+ retVal = binder.BindModel(null, bindingContext);
+ }
+ finally
+ {
+ ModelBinderConfig.TypeConversionErrorMessageProvider = originalProvider;
+ }
+
+ // Assert
+ Assert.False(retVal);
+ Assert.Null(bindingContext.Model);
+ Assert.True(bindingContext.ModelState.IsValid);
+ }
+
+ [Fact]
+ public void BindModel_Error_GeneralExceptionsSavedInModelState()
+ {
+ // Arrange
+ ExtensibleModelBindingContext bindingContext = GetBindingContext(typeof(Dummy));
+ bindingContext.ValueProvider = new SimpleValueProvider
+ {
+ { "theModelName", "foo" }
+ };
+
+ TypeConverterModelBinder binder = new TypeConverterModelBinder();
+
+ // Act
+ bool retVal = binder.BindModel(null, bindingContext);
+
+ // Assert
+ Assert.False(retVal);
+ Assert.Null(bindingContext.Model);
+ Assert.Equal("The parameter conversion from type 'System.String' to type 'Microsoft.Web.Mvc.ModelBinding.Test.TypeConverterModelBinderTest+Dummy' failed. See the inner exception for more information.", bindingContext.ModelState["theModelName"].Errors[0].Exception.Message);
+ Assert.Equal("From DummyTypeConverter: foo", bindingContext.ModelState["theModelName"].Errors[0].Exception.InnerException.Message);
+ }
+
+ [Fact]
+ public void BindModel_NullValueProviderResult_ReturnsFalse()
+ {
+ // Arrange
+ ExtensibleModelBindingContext bindingContext = GetBindingContext(typeof(int));
+
+ TypeConverterModelBinder binder = new TypeConverterModelBinder();
+
+ // Act
+ bool retVal = binder.BindModel(null, bindingContext);
+
+ // Assert
+ Assert.False(retVal, "BindModel should have returned null.");
+ Assert.Empty(bindingContext.ModelState);
+ }
+
+ [Fact]
+ public void BindModel_ValidValueProviderResult_ConvertEmptyStringsToNull()
+ {
+ // Arrange
+ ExtensibleModelBindingContext bindingContext = GetBindingContext(typeof(string));
+ bindingContext.ValueProvider = new SimpleValueProvider
+ {
+ { "theModelName", "" }
+ };
+
+ TypeConverterModelBinder binder = new TypeConverterModelBinder();
+
+ // Act
+ bool retVal = binder.BindModel(null, bindingContext);
+
+ // Assert
+ Assert.True(retVal);
+ Assert.Null(bindingContext.Model);
+ Assert.True(bindingContext.ModelState.ContainsKey("theModelName"));
+ }
+
+ [Fact]
+ public void BindModel_ValidValueProviderResult_ReturnsModel()
+ {
+ // Arrange
+ ExtensibleModelBindingContext bindingContext = GetBindingContext(typeof(int));
+ bindingContext.ValueProvider = new SimpleValueProvider
+ {
+ { "theModelName", "42" }
+ };
+
+ TypeConverterModelBinder binder = new TypeConverterModelBinder();
+
+ // Act
+ bool retVal = binder.BindModel(null, bindingContext);
+
+ // Assert
+ Assert.True(retVal);
+ Assert.Equal(42, bindingContext.Model);
+ Assert.True(bindingContext.ModelState.ContainsKey("theModelName"));
+ }
+
+ private static ExtensibleModelBindingContext GetBindingContext(Type modelType)
+ {
+ return new ExtensibleModelBindingContext
+ {
+ ModelMetadata = new EmptyModelMetadataProvider().GetMetadataForType(null, modelType),
+ ModelName = "theModelName",
+ ValueProvider = new SimpleValueProvider() // empty
+ };
+ }
+
+ [TypeConverter(typeof(DummyTypeConverter))]
+ private struct Dummy
+ {
+ }
+
+ private sealed class DummyTypeConverter : TypeConverter
+ {
+ public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
+ {
+ return (sourceType == typeof(string)) || base.CanConvertFrom(context, sourceType);
+ }
+
+ public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
+ {
+ throw new InvalidOperationException(String.Format("From DummyTypeConverter: {0}", value));
+ }
+ }
+ }
+}
diff --git a/test/Microsoft.Web.Mvc.Test/ModelBinding/Test/TypeMatchModelBinderProviderTest.cs b/test/Microsoft.Web.Mvc.Test/ModelBinding/Test/TypeMatchModelBinderProviderTest.cs
new file mode 100644
index 00000000..d6df4673
--- /dev/null
+++ b/test/Microsoft.Web.Mvc.Test/ModelBinding/Test/TypeMatchModelBinderProviderTest.cs
@@ -0,0 +1,73 @@
+using System;
+using System.Linq;
+using System.Web.Mvc;
+using Microsoft.Web.UnitTestUtil;
+using Xunit;
+
+namespace Microsoft.Web.Mvc.ModelBinding.Test
+{
+ public class TypeMatchModelBinderProviderTest
+ {
+ [Fact]
+ public void ProviderIsMarkedFrontOfList()
+ {
+ // Arrange
+ Type t = typeof(TypeMatchModelBinderProvider);
+
+ // Act & assert
+ Assert.True(t.GetCustomAttributes(typeof(ModelBinderProviderOptionsAttribute), true /* inherit */).Cast<ModelBinderProviderOptionsAttribute>().Single().FrontOfList);
+ }
+
+ [Fact]
+ public void GetBinder_InvalidValueProviderResult_ReturnsNull()
+ {
+ // Arrange
+ ExtensibleModelBindingContext bindingContext = GetBindingContext();
+ bindingContext.ValueProvider = new SimpleValueProvider
+ {
+ { "theModelName", "not an integer" }
+ };
+
+ TypeMatchModelBinderProvider provider = new TypeMatchModelBinderProvider();
+
+ // Act
+ IExtensibleModelBinder binder = provider.GetBinder(null, bindingContext);
+
+ // Assert
+ Assert.Null(binder);
+ }
+
+ [Fact]
+ public void BindModel_ValidValueProviderResult_ReturnsBinder()
+ {
+ // Arrange
+ ExtensibleModelBindingContext bindingContext = GetBindingContext();
+ bindingContext.ValueProvider = new SimpleValueProvider
+ {
+ { "theModelName", 42 }
+ };
+
+ TypeMatchModelBinderProvider provider = new TypeMatchModelBinderProvider();
+
+ // Act
+ IExtensibleModelBinder binder = provider.GetBinder(null, bindingContext);
+
+ // Assert
+ Assert.IsType<TypeMatchModelBinder>(binder);
+ }
+
+ private static ExtensibleModelBindingContext GetBindingContext()
+ {
+ return GetBindingContext(typeof(int));
+ }
+
+ private static ExtensibleModelBindingContext GetBindingContext(Type modelType)
+ {
+ return new ExtensibleModelBindingContext
+ {
+ ModelMetadata = new EmptyModelMetadataProvider().GetMetadataForType(null, modelType),
+ ModelName = "theModelName"
+ };
+ }
+ }
+}
diff --git a/test/Microsoft.Web.Mvc.Test/ModelBinding/Test/TypeMatchModelBinderTest.cs b/test/Microsoft.Web.Mvc.Test/ModelBinding/Test/TypeMatchModelBinderTest.cs
new file mode 100644
index 00000000..8c8298e3
--- /dev/null
+++ b/test/Microsoft.Web.Mvc.Test/ModelBinding/Test/TypeMatchModelBinderTest.cs
@@ -0,0 +1,113 @@
+using System;
+using System.Web.Mvc;
+using Microsoft.Web.UnitTestUtil;
+using Xunit;
+
+namespace Microsoft.Web.Mvc.ModelBinding.Test
+{
+ public class TypeMatchModelBinderTest
+ {
+ [Fact]
+ public void BindModel_InvalidValueProviderResult_ReturnsFalse()
+ {
+ // Arrange
+ ExtensibleModelBindingContext bindingContext = GetBindingContext();
+ bindingContext.ValueProvider = new SimpleValueProvider
+ {
+ { "theModelName", "not an integer" }
+ };
+
+ TypeMatchModelBinder binder = new TypeMatchModelBinder();
+
+ // Act
+ bool retVal = binder.BindModel(null, bindingContext);
+
+ // Assert
+ Assert.False(retVal);
+ Assert.Empty(bindingContext.ModelState);
+ }
+
+ [Fact]
+ public void BindModel_ValidValueProviderResult_ReturnsTrue()
+ {
+ // Arrange
+ ExtensibleModelBindingContext bindingContext = GetBindingContext();
+ bindingContext.ValueProvider = new SimpleValueProvider
+ {
+ { "theModelName", 42 }
+ };
+
+ TypeMatchModelBinder binder = new TypeMatchModelBinder();
+
+ // Act
+ bool retVal = binder.BindModel(null, bindingContext);
+
+ // Assert
+ Assert.True(retVal);
+ Assert.Equal(42, bindingContext.Model);
+ Assert.True(bindingContext.ModelState.ContainsKey("theModelName"));
+ }
+
+ [Fact]
+ public void GetCompatibleValueProviderResult_ValueProviderResultRawValueIncorrect_ReturnsNull()
+ {
+ // Arrange
+ ExtensibleModelBindingContext bindingContext = GetBindingContext();
+ bindingContext.ValueProvider = new SimpleValueProvider
+ {
+ { "theModelName", "not an integer" }
+ };
+
+ // Act
+ ValueProviderResult vpResult = TypeMatchModelBinder.GetCompatibleValueProviderResult(bindingContext);
+
+ // Assert
+ Assert.Null(vpResult); // Raw value is the wrong type
+ }
+
+ [Fact]
+ public void GetCompatibleValueProviderResult_ValueProviderResultValid_ReturnsValueProviderResult()
+ {
+ // Arrange
+ ExtensibleModelBindingContext bindingContext = GetBindingContext();
+ bindingContext.ValueProvider = new SimpleValueProvider
+ {
+ { "theModelName", 42 }
+ };
+
+ // Act
+ ValueProviderResult vpResult = TypeMatchModelBinder.GetCompatibleValueProviderResult(bindingContext);
+
+ // Assert
+ Assert.NotNull(vpResult);
+ }
+
+ [Fact]
+ public void GetCompatibleValueProviderResult_ValueProviderReturnsNull_ReturnsNull()
+ {
+ // Arrange
+ ExtensibleModelBindingContext bindingContext = GetBindingContext();
+ bindingContext.ValueProvider = new SimpleValueProvider();
+
+ // Act
+ ValueProviderResult vpResult = TypeMatchModelBinder.GetCompatibleValueProviderResult(bindingContext);
+
+ // Assert
+ Assert.Null(vpResult); // No key matched
+ }
+
+ private static ExtensibleModelBindingContext GetBindingContext()
+ {
+ return GetBindingContext(typeof(int));
+ }
+
+ private static ExtensibleModelBindingContext GetBindingContext(Type modelType)
+ {
+ return new ExtensibleModelBindingContext
+ {
+ ModelMetadata = new EmptyModelMetadataProvider().GetMetadataForType(null, modelType),
+ ModelName = "theModelName"
+ };
+ }
+ }
+}
diff --git a/test/Microsoft.Web.Mvc.Test/Properties/AssemblyInfo.cs b/test/Microsoft.Web.Mvc.Test/Properties/AssemblyInfo.cs
new file mode 100644
index 00000000..76660dea
--- /dev/null
+++ b/test/Microsoft.Web.Mvc.Test/Properties/AssemblyInfo.cs
@@ -0,0 +1,38 @@
+using System.Reflection;
+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("MvcToolkitTest")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("MvcToolkitTest")]
+[assembly: AssemblyCopyright("Copyright © 2008")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Setting ComVisible to false makes the types in this assembly not visible
+// to COM componenets. 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("f2404c7a-e41f-4a7f-b952-e38a056df1ff")]
+
+// 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 Revision and Build Numbers
+// by using the '*' as shown below:
+
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
diff --git a/test/Microsoft.Web.Mvc.Test/Test/AjaxOnlyAttributeTest.cs b/test/Microsoft.Web.Mvc.Test/Test/AjaxOnlyAttributeTest.cs
new file mode 100644
index 00000000..4cf3b17b
--- /dev/null
+++ b/test/Microsoft.Web.Mvc.Test/Test/AjaxOnlyAttributeTest.cs
@@ -0,0 +1,66 @@
+using System.Collections.Specialized;
+using System.Web.Mvc;
+using Moq;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace Microsoft.Web.Mvc.Test
+{
+ public class AjaxOnlyAttributeTest
+ {
+ [Fact]
+ public void IsValidForRequestReturnsFalseIfHeaderNotPresent()
+ {
+ // Arrange
+ AjaxOnlyAttribute attr = new AjaxOnlyAttribute();
+ ControllerContext controllerContext = GetControllerContext(containsHeader: false);
+
+ // Act
+ bool isValid = attr.IsValidForRequest(controllerContext, null);
+
+ // Assert
+ Assert.False(isValid);
+ }
+
+ [Fact]
+ public void IsValidForRequestReturnsTrueIfHeaderIsPresent()
+ {
+ // Arrange
+ AjaxOnlyAttribute attr = new AjaxOnlyAttribute();
+ ControllerContext controllerContext = GetControllerContext(containsHeader: true);
+
+ // Act
+ bool isValid = attr.IsValidForRequest(controllerContext, null);
+
+ // Assert
+ Assert.True(isValid);
+ }
+
+ [Fact]
+ public void IsValidForRequestThrowsIfControllerContextIsNull()
+ {
+ // Arrange
+ AjaxOnlyAttribute attr = new AjaxOnlyAttribute();
+
+ // Act & assert
+ Assert.ThrowsArgumentNull(
+ delegate { attr.IsValidForRequest(null, null); }, "controllerContext");
+ }
+
+ private static ControllerContext GetControllerContext(bool containsHeader)
+ {
+ Mock<ControllerContext> mockContext = new Mock<ControllerContext> { DefaultValue = DefaultValue.Mock };
+
+ NameValueCollection nvc = new NameValueCollection();
+ if (containsHeader)
+ {
+ nvc["X-Requested-With"] = "XMLHttpRequest";
+ }
+
+ mockContext.Setup(o => o.HttpContext.Request.Headers).Returns(nvc);
+ mockContext.Setup(o => o.HttpContext.Request["X-Requested-With"]).Returns("XMLHttpRequest"); // always assume the request contains this, e.g. as a form value
+
+ return mockContext.Object;
+ }
+ }
+}
diff --git a/test/Microsoft.Web.Mvc.Test/Test/AreaHelpersTest.cs b/test/Microsoft.Web.Mvc.Test/Test/AreaHelpersTest.cs
new file mode 100644
index 00000000..df9aace3
--- /dev/null
+++ b/test/Microsoft.Web.Mvc.Test/Test/AreaHelpersTest.cs
@@ -0,0 +1,100 @@
+using System;
+using System.Web;
+using System.Web.Mvc;
+using System.Web.Routing;
+using Xunit;
+
+namespace Microsoft.Web.Mvc.Test
+{
+ public class AreaHelpersTest
+ {
+ [Fact]
+ public void GetAreaNameFromAreaRouteCollectionRoute()
+ {
+ // Arrange
+ RouteCollection routes = new RouteCollection();
+ AreaRegistrationContext context = new AreaRegistrationContext("area_name", routes);
+ Route route = context.MapRoute(null, "the_url");
+
+ // Act
+ string areaName = AreaHelpers.GetAreaName(route);
+
+ // Assert
+ Assert.Equal("area_name", areaName);
+ }
+
+ [Fact]
+ public void GetAreaNameFromIAreaAssociatedItem()
+ {
+ // Arrange
+ CustomRouteWithArea route = new CustomRouteWithArea();
+
+ // Act
+ string areaName = AreaHelpers.GetAreaName(route);
+
+ // Assert
+ Assert.Equal("area_name", areaName);
+ }
+
+ [Fact]
+ public void GetAreaNameFromRouteData()
+ {
+ // Arrange
+ RouteData routeData = new RouteData();
+ routeData.DataTokens["area"] = "area_name";
+
+ // Act
+ string areaName = AreaHelpers.GetAreaName(routeData);
+
+ // Assert
+ Assert.Equal("area_name", areaName);
+ }
+
+ [Fact]
+ public void GetAreaNameFromRouteDataFallsBackToRoute()
+ {
+ // Arrange
+ RouteCollection routes = new RouteCollection();
+ AreaRegistrationContext context = new AreaRegistrationContext("area_name", routes);
+ Route route = context.MapRoute(null, "the_url");
+ RouteData routeData = new RouteData(route, new MvcRouteHandler());
+
+ // Act
+ string areaName = AreaHelpers.GetAreaName(routeData);
+
+ // Assert
+ Assert.Equal("area_name", areaName);
+ }
+
+ [Fact]
+ public void GetAreaNameReturnsNullIfRouteNotAreaAware()
+ {
+ // Arrange
+ Route route = new Route("the_url", new MvcRouteHandler());
+
+ // Act
+ string areaName = AreaHelpers.GetAreaName(route);
+
+ // Assert
+ Assert.Null(areaName);
+ }
+
+ private class CustomRouteWithArea : RouteBase, IRouteWithArea
+ {
+ public string Area
+ {
+ get { return "area_name"; }
+ }
+
+ public override RouteData GetRouteData(HttpContextBase httpContext)
+ {
+ throw new NotImplementedException();
+ }
+
+ public override VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary values)
+ {
+ throw new NotImplementedException();
+ }
+ }
+ }
+}
diff --git a/test/Microsoft.Web.Mvc.Test/Test/AsyncManagerExtensionsTest.cs b/test/Microsoft.Web.Mvc.Test/Test/AsyncManagerExtensionsTest.cs
new file mode 100644
index 00000000..0eab7918
--- /dev/null
+++ b/test/Microsoft.Web.Mvc.Test/Test/AsyncManagerExtensionsTest.cs
@@ -0,0 +1,189 @@
+using System;
+using System.Threading;
+using System.Web.Mvc.Async;
+using System.Web.TestUtil;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace Microsoft.Web.Mvc.Test
+{
+ public class AsyncManagerExtensionsTest
+ {
+ [Fact]
+ public void RegisterTask_AsynchronousCompletion()
+ {
+ // Arrange
+ SimpleSynchronizationContext syncContext = new SimpleSynchronizationContext();
+ AsyncManager asyncManager = new AsyncManager(syncContext);
+ bool endDelegateWasCalled = false;
+
+ ManualResetEvent waitHandle = new ManualResetEvent(false /* initialState */);
+
+ Func<AsyncCallback, IAsyncResult> beginDelegate = callback =>
+ {
+ Assert.Equal(1, asyncManager.OutstandingOperations.Count);
+ MockAsyncResult asyncResult = new MockAsyncResult(false /* completedSynchronously */);
+ ThreadPool.QueueUserWorkItem(_ =>
+ {
+ Assert.Equal(1, asyncManager.OutstandingOperations.Count);
+ callback(asyncResult);
+ waitHandle.Set();
+ });
+ return asyncResult;
+ };
+ Action<IAsyncResult> endDelegate = delegate { endDelegateWasCalled = true; };
+
+ // Act
+ asyncManager.RegisterTask(beginDelegate, endDelegate);
+ waitHandle.WaitOne();
+
+ // Assert
+ Assert.True(endDelegateWasCalled);
+ Assert.True(syncContext.SendWasCalled);
+ Assert.Equal(0, asyncManager.OutstandingOperations.Count);
+ }
+
+ [Fact]
+ public void RegisterTask_AsynchronousCompletion_SwallowsExceptionsThrownByEndDelegate()
+ {
+ // Arrange
+ SimpleSynchronizationContext syncContext = new SimpleSynchronizationContext();
+ AsyncManager asyncManager = new AsyncManager(syncContext);
+ bool endDelegateWasCalled = false;
+
+ ManualResetEvent waitHandle = new ManualResetEvent(false /* initialState */);
+
+ Func<AsyncCallback, IAsyncResult> beginDelegate = callback =>
+ {
+ MockAsyncResult asyncResult = new MockAsyncResult(false /* completedSynchronously */);
+ ThreadPool.QueueUserWorkItem(_ =>
+ {
+ callback(asyncResult);
+ waitHandle.Set();
+ });
+ return asyncResult;
+ };
+ Action<IAsyncResult> endDelegate = delegate
+ {
+ endDelegateWasCalled = true;
+ throw new Exception("This is a sample exception.");
+ };
+
+ // Act
+ asyncManager.RegisterTask(beginDelegate, endDelegate);
+ waitHandle.WaitOne();
+
+ // Assert
+ Assert.True(endDelegateWasCalled);
+ Assert.Equal(0, asyncManager.OutstandingOperations.Count);
+ }
+
+ [Fact]
+ public void RegisterTask_ResetsOutstandingOperationCountIfBeginMethodThrows()
+ {
+ // Arrange
+ SimpleSynchronizationContext syncContext = new SimpleSynchronizationContext();
+ AsyncManager asyncManager = new AsyncManager(syncContext);
+
+ Func<AsyncCallback, IAsyncResult> beginDelegate = cb => { throw new InvalidOperationException("BeginDelegate throws."); };
+ Action<IAsyncResult> endDelegate = ar => { Assert.True(false, "This should never be called."); };
+
+ // Act & assert
+ Assert.Throws<InvalidOperationException>(
+ delegate { asyncManager.RegisterTask(beginDelegate, endDelegate); }, "BeginDelegate throws.");
+
+ Assert.Equal(0, asyncManager.OutstandingOperations.Count);
+ }
+
+ [Fact]
+ public void RegisterTask_SynchronousCompletion()
+ {
+ // Arrange
+ SimpleSynchronizationContext syncContext = new SimpleSynchronizationContext();
+ AsyncManager asyncManager = new AsyncManager(syncContext);
+ bool endDelegateWasCalled = false;
+
+ Func<AsyncCallback, IAsyncResult> beginDelegate = callback =>
+ {
+ Assert.Equal(1, asyncManager.OutstandingOperations.Count);
+ MockAsyncResult asyncResult = new MockAsyncResult(true /* completedSynchronously */);
+ callback(asyncResult);
+ return asyncResult;
+ };
+ Action<IAsyncResult> endDelegate = delegate { endDelegateWasCalled = true; };
+
+ // Act
+ asyncManager.RegisterTask(beginDelegate, endDelegate);
+
+ // Assert
+ Assert.True(endDelegateWasCalled);
+ Assert.False(syncContext.SendWasCalled);
+ Assert.Equal(0, asyncManager.OutstandingOperations.Count);
+ }
+
+ [Fact]
+ public void RegisterTask_ThrowsIfAsyncManagerIsNull()
+ {
+ // Act & assert
+ Assert.ThrowsArgumentNull(
+ delegate { AsyncManagerExtensions.RegisterTask(null, _ => null, _ => { }); }, "asyncManager");
+ }
+
+ [Fact]
+ public void RegisterTask_ThrowsIfBeginDelegateIsNull()
+ {
+ // Act & assert
+ Assert.ThrowsArgumentNull(
+ delegate { new AsyncManager().RegisterTask(null, _ => { }); }, "beginDelegate");
+ }
+
+ [Fact]
+ public void RegisterTask_ThrowsIfEndDelegateIsNull()
+ {
+ // Act & assert
+ Assert.ThrowsArgumentNull(
+ delegate { new AsyncManager().RegisterTask(_ => null, null); }, "endDelegate");
+ }
+
+ private class SimpleSynchronizationContext : SynchronizationContext
+ {
+ public bool SendWasCalled;
+
+ public override void Send(SendOrPostCallback d, object state)
+ {
+ SendWasCalled = true;
+ d(state);
+ }
+ }
+
+ private class MockAsyncResult : IAsyncResult
+ {
+ private readonly bool _completedSynchronously;
+
+ public MockAsyncResult(bool completedSynchronously)
+ {
+ _completedSynchronously = completedSynchronously;
+ }
+
+ public object AsyncState
+ {
+ get { throw new NotImplementedException(); }
+ }
+
+ public WaitHandle AsyncWaitHandle
+ {
+ get { throw new NotImplementedException(); }
+ }
+
+ public bool CompletedSynchronously
+ {
+ get { return _completedSynchronously; }
+ }
+
+ public bool IsCompleted
+ {
+ get { throw new NotImplementedException(); }
+ }
+ }
+ }
+}
diff --git a/test/Microsoft.Web.Mvc.Test/Test/ButtonTest.cs b/test/Microsoft.Web.Mvc.Test/Test/ButtonTest.cs
new file mode 100644
index 00000000..3f8d4c7d
--- /dev/null
+++ b/test/Microsoft.Web.Mvc.Test/Test/ButtonTest.cs
@@ -0,0 +1,66 @@
+using System.Web.Mvc;
+using System.Web.Routing;
+using Microsoft.Web.UnitTestUtil;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace Microsoft.Web.Mvc.Test
+{
+ public class ButtonTest
+ {
+ [Fact]
+ public void ButtonWithNullNameThrowsArgumentNullException()
+ {
+ HtmlHelper html = MvcHelper.GetHtmlHelper(new ViewDataDictionary());
+ Assert.ThrowsArgumentNull(() => html.Button(null, "text", HtmlButtonType.Button), "name");
+ }
+
+ [Fact]
+ public void ButtonRendersBaseAttributes()
+ {
+ HtmlHelper html = MvcHelper.GetHtmlHelper(new ViewDataDictionary());
+ MvcHtmlString result = html.Button("nameAttr", "buttonText", HtmlButtonType.Reset, "onclickAttr");
+ Assert.Equal("<button name=\"nameAttr\" onclick=\"onclickAttr\" type=\"reset\">buttonText</button>", result.ToHtmlString());
+ }
+
+ [Fact]
+ public void ButtonWithoutOnClickDoesNotRenderOnclickAttribute()
+ {
+ HtmlHelper html = MvcHelper.GetHtmlHelper(new ViewDataDictionary());
+ MvcHtmlString result = html.Button("nameAttr", "buttonText", HtmlButtonType.Reset);
+ Assert.Equal("<button name=\"nameAttr\" type=\"reset\">buttonText</button>", result.ToHtmlString());
+ }
+
+ [Fact]
+ public void ButtonAllowsInnerHtml()
+ {
+ HtmlHelper html = MvcHelper.GetHtmlHelper(new ViewDataDictionary());
+ MvcHtmlString result = html.Button("nameAttr", "<img src=\"puppy.jpg\" />", HtmlButtonType.Submit, "onclickAttr");
+ Assert.Equal("<button name=\"nameAttr\" onclick=\"onclickAttr\" type=\"submit\"><img src=\"puppy.jpg\" /></button>", result.ToHtmlString());
+ }
+
+ [Fact]
+ public void ButtonRendersExplicitAttributes()
+ {
+ HtmlHelper html = MvcHelper.GetHtmlHelper(new ViewDataDictionary());
+ MvcHtmlString result = html.Button("nameAttr", "buttonText", HtmlButtonType.Reset, "onclickAttr", new { title = "the-title" });
+ Assert.Equal("<button name=\"nameAttr\" onclick=\"onclickAttr\" title=\"the-title\" type=\"reset\">buttonText</button>", result.ToHtmlString());
+ }
+
+ [Fact]
+ public void ButtonRendersExplicitAttributesWithUnderscores()
+ {
+ HtmlHelper html = MvcHelper.GetHtmlHelper(new ViewDataDictionary());
+ MvcHtmlString result = html.Button("nameAttr", "buttonText", HtmlButtonType.Reset, "onclickAttr", new { foo_bar = "baz" });
+ Assert.Equal("<button foo-bar=\"baz\" name=\"nameAttr\" onclick=\"onclickAttr\" type=\"reset\">buttonText</button>", result.ToHtmlString());
+ }
+
+ [Fact]
+ public void ButtonRendersExplicitDictionaryAttributes()
+ {
+ HtmlHelper html = MvcHelper.GetHtmlHelper(new ViewDataDictionary());
+ MvcHtmlString result = html.Button("nameAttr", "buttonText", HtmlButtonType.Button, "onclickAttr", new RouteValueDictionary(new { title = "the-title" }));
+ Assert.Equal("<button name=\"nameAttr\" onclick=\"onclickAttr\" title=\"the-title\" type=\"button\">buttonText</button>", result.ToHtmlString());
+ }
+ }
+}
diff --git a/test/Microsoft.Web.Mvc.Test/Test/ContentTypeAttributeTest.cs b/test/Microsoft.Web.Mvc.Test/Test/ContentTypeAttributeTest.cs
new file mode 100644
index 00000000..39dbdff1
--- /dev/null
+++ b/test/Microsoft.Web.Mvc.Test/Test/ContentTypeAttributeTest.cs
@@ -0,0 +1,43 @@
+using System.Web;
+using System.Web.Mvc;
+using System.Web.Routing;
+using Moq;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace Microsoft.Web.Mvc.Test
+{
+ public class ContentTypeAttributeTest
+ {
+ [Fact]
+ public void ContentTypeSetInCtor()
+ {
+ var attr = new ContentTypeAttribute("text/html");
+ Assert.Equal("text/html", attr.ContentType);
+ }
+
+ [Fact]
+ public void ContentTypeCtorThrowsArgumentExceptionWhenContentTypeIsNull()
+ {
+ Assert.ThrowsArgumentNullOrEmpty(() => new ContentTypeAttribute(null), "contentType");
+ }
+
+ [Fact]
+ public void ExecuteResultSetsContentType()
+ {
+ var mockHttpResponse = new Mock<HttpResponseBase>();
+ var mockHttpContext = new Mock<HttpContextBase>();
+ mockHttpContext.Setup(c => c.Response).Returns(mockHttpResponse.Object);
+
+ var mockController = new Mock<Controller>();
+ var controllerContext = new ControllerContext(new RequestContext(mockHttpContext.Object, new RouteData()), mockController.Object);
+ var result = new ContentResult { Content = "blah blah" };
+ var filterContext = new ResultExecutingContext(controllerContext, result);
+
+ var filter = new ContentTypeAttribute("text/xml");
+ filter.OnResultExecuting(filterContext);
+
+ mockHttpResponse.VerifySet(r => r.ContentType = "text/xml");
+ }
+ }
+}
diff --git a/test/Microsoft.Web.Mvc.Test/Test/ControllerExtensionsTest.cs b/test/Microsoft.Web.Mvc.Test/Test/ControllerExtensionsTest.cs
new file mode 100644
index 00000000..ae2a2e1c
--- /dev/null
+++ b/test/Microsoft.Web.Mvc.Test/Test/ControllerExtensionsTest.cs
@@ -0,0 +1,67 @@
+using System;
+using System.Web.Mvc;
+using Microsoft.Web.UnitTestUtil;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace Microsoft.Web.Mvc.Test
+{
+ public class ControllerExtensionsTest
+ {
+ private const string AppPathModifier = MvcHelper.AppPathModifier;
+
+ [Fact]
+ public void RedirectToAction_DifferentController()
+ {
+ // Act
+ RedirectToRouteResult result = new SampleController().RedirectToAction<DifferentController>(x => x.SomeOtherMethod(84));
+
+ // Assert
+ Assert.NotNull(result);
+ Assert.Equal("", result.RouteName);
+ Assert.Equal(3, result.RouteValues.Count);
+ Assert.Equal("Different", result.RouteValues["controller"]);
+ Assert.Equal("SomeOtherMethod", result.RouteValues["action"]);
+ Assert.Equal(84, result.RouteValues["someOtherParameter"]);
+ }
+
+ [Fact]
+ public void RedirectToAction_SameController()
+ {
+ // Act
+ RedirectToRouteResult result = new SampleController().RedirectToAction(x => x.SomeMethod(42));
+
+ // Assert
+ Assert.NotNull(result);
+ Assert.Equal("", result.RouteName);
+ Assert.Equal(3, result.RouteValues.Count);
+ Assert.Equal("Sample", result.RouteValues["controller"]);
+ Assert.Equal("SomeMethod", result.RouteValues["action"]);
+ Assert.Equal(42, result.RouteValues["someParameter"]);
+ }
+
+ [Fact]
+ public void RedirectToAction_ThrowsIfControllerIsNull()
+ {
+ // Act & assert
+ Assert.ThrowsArgumentNull(
+ delegate { ((SampleController)null).RedirectToAction(x => x.SomeMethod(42)); }, "controller");
+ }
+
+ private class SampleController : Controller
+ {
+ public ActionResult SomeMethod(int someParameter)
+ {
+ throw new NotImplementedException();
+ }
+ }
+
+ private class DifferentController : Controller
+ {
+ public ActionResult SomeOtherMethod(int someOtherParameter)
+ {
+ throw new NotImplementedException();
+ }
+ }
+ }
+}
diff --git a/test/Microsoft.Web.Mvc.Test/Test/CookieTempDataProviderTest.cs b/test/Microsoft.Web.Mvc.Test/Test/CookieTempDataProviderTest.cs
new file mode 100644
index 00000000..799f7564
--- /dev/null
+++ b/test/Microsoft.Web.Mvc.Test/Test/CookieTempDataProviderTest.cs
@@ -0,0 +1,171 @@
+using System;
+using System.Collections.Generic;
+using System.Web;
+using System.Web.Mvc;
+using Moq;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace Microsoft.Web.Mvc.Test
+{
+ public class CookieTempDataProviderTest
+ {
+ [Fact]
+ public void ConstructProviderThrowsOnNullHttpContext()
+ {
+ Assert.ThrowsArgumentNull(
+ delegate { new CookieTempDataProvider(null); },
+ "httpContext");
+ }
+
+ [Fact]
+ public void CtorSetsHttpContextProperty()
+ {
+ var httpContext = new Mock<HttpContextBase>().Object;
+ var provider = new CookieTempDataProvider(httpContext);
+
+ Assert.Equal(httpContext, provider.HttpContext);
+ }
+
+ [Fact]
+ public void LoadTempDataWithEmptyCookieReturnsEmptyDictionary()
+ {
+ HttpCookie cookie = new HttpCookie("__ControllerTempData");
+ cookie.Value = String.Empty;
+ var cookies = new HttpCookieCollection();
+ cookies.Add(cookie);
+
+ var requestMock = new Mock<HttpRequestBase>();
+ requestMock.Setup(r => r.Cookies).Returns(cookies);
+
+ var httpContextMock = new Mock<HttpContextBase>();
+ httpContextMock.Setup(c => c.Request).Returns(requestMock.Object);
+
+ ITempDataProvider provider = new CookieTempDataProvider(httpContextMock.Object);
+
+ IDictionary<string, object> tempData = provider.LoadTempData(null /* controllerContext */);
+ Assert.NotNull(tempData);
+ Assert.Equal(0, tempData.Count);
+ }
+
+ [Fact]
+ public void LoadTempDataWithNullCookieReturnsEmptyTempDataDictionary()
+ {
+ var cookies = new HttpCookieCollection();
+
+ var requestMock = new Mock<HttpRequestBase>();
+ requestMock.Setup(r => r.Cookies).Returns(cookies);
+
+ var httpContextMock = new Mock<HttpContextBase>();
+ httpContextMock.Setup(c => c.Request).Returns(requestMock.Object);
+
+ ITempDataProvider provider = new CookieTempDataProvider(httpContextMock.Object);
+
+ IDictionary<string, object> tempData = provider.LoadTempData(null /* controllerContext */);
+ Assert.NotNull(tempData);
+ Assert.Equal(0, tempData.Count);
+ }
+
+ [Fact]
+ public void LoadTempDataIgnoresNullResponseCookieDoesNotThrowException()
+ {
+ HttpCookie cookie = new HttpCookie("__ControllerTempData");
+ var initialTempData = new Dictionary<string, object>();
+ initialTempData.Add("WhatIsInHere?", "Stuff");
+ cookie.Value = CookieTempDataProvider.DictionaryToBase64String(initialTempData);
+ var cookies = new HttpCookieCollection();
+ cookies.Add(cookie);
+
+ var requestMock = new Mock<HttpRequestBase>();
+ requestMock.Setup(r => r.Cookies).Returns(cookies);
+
+ var responseMock = new Mock<HttpResponseBase>();
+ responseMock.Setup(r => r.Cookies).Returns((HttpCookieCollection)null);
+
+ var httpContextMock = new Mock<HttpContextBase>();
+ httpContextMock.Setup(c => c.Request).Returns(requestMock.Object);
+ httpContextMock.Setup(c => c.Response).Returns(responseMock.Object);
+
+ ITempDataProvider provider = new CookieTempDataProvider(httpContextMock.Object);
+
+ IDictionary<string, object> tempData = provider.LoadTempData(null /* controllerContext */);
+ Assert.Equal("Stuff", tempData["WhatIsInHere?"]);
+ }
+
+ [Fact]
+ public void LoadTempDataWithNullResponseDoesNotThrowException()
+ {
+ HttpCookie cookie = new HttpCookie("__ControllerTempData");
+ var initialTempData = new Dictionary<string, object>();
+ initialTempData.Add("WhatIsInHere?", "Stuff");
+ cookie.Value = CookieTempDataProvider.DictionaryToBase64String(initialTempData);
+ var cookies = new HttpCookieCollection();
+ cookies.Add(cookie);
+
+ var requestMock = new Mock<HttpRequestBase>();
+ requestMock.Setup(r => r.Cookies).Returns(cookies);
+
+ var httpContextMock = new Mock<HttpContextBase>();
+ httpContextMock.Setup(c => c.Request).Returns(requestMock.Object);
+ httpContextMock.Setup(c => c.Response).Returns((HttpResponseBase)null);
+
+ ITempDataProvider provider = new CookieTempDataProvider(httpContextMock.Object);
+
+ IDictionary<string, object> tempData = provider.LoadTempData(null /* controllerContext */);
+ Assert.Equal("Stuff", tempData["WhatIsInHere?"]);
+ }
+
+ [Fact]
+ public void SaveTempDataStoresSerializedFormInCookie()
+ {
+ var cookies = new HttpCookieCollection();
+ var responseMock = new Mock<HttpResponseBase>();
+ responseMock.Setup(r => r.Cookies).Returns(cookies);
+
+ var httpContextMock = new Mock<HttpContextBase>();
+ httpContextMock.Setup(c => c.Response).Returns(responseMock.Object);
+
+ ITempDataProvider provider = new CookieTempDataProvider(httpContextMock.Object);
+ var tempData = new Dictionary<string, object>();
+ tempData.Add("Testing", "Turn it up to 11");
+ tempData.Add("Testing2", 1.23);
+
+ provider.SaveTempData(null, tempData);
+ HttpCookie cookie = cookies["__ControllerTempData"];
+ string serialized = cookie.Value;
+ IDictionary<string, object> deserializedTempData = CookieTempDataProvider.Base64StringToDictionary(serialized);
+ Assert.Equal("Turn it up to 11", deserializedTempData["Testing"]);
+ Assert.Equal(1.23, deserializedTempData["Testing2"]);
+ }
+
+ [Fact]
+ public void CanLoadTempDataFromCookie()
+ {
+ var tempData = new Dictionary<string, object>();
+ tempData.Add("abc", "easy as 123");
+ tempData.Add("price", 1.234);
+ string serializedTempData = CookieTempDataProvider.DictionaryToBase64String(tempData);
+
+ var cookies = new HttpCookieCollection();
+ var httpCookie = new HttpCookie("__ControllerTempData");
+ httpCookie.Value = serializedTempData;
+ cookies.Add(httpCookie);
+
+ var requestMock = new Mock<HttpRequestBase>();
+ requestMock.Setup(r => r.Cookies).Returns(cookies);
+
+ var responseMock = new Mock<HttpResponseBase>();
+ responseMock.Setup(r => r.Cookies).Returns(cookies);
+
+ var httpContextMock = new Mock<HttpContextBase>();
+ httpContextMock.Setup(c => c.Request).Returns(requestMock.Object);
+ httpContextMock.Setup(c => c.Response).Returns(responseMock.Object);
+
+ ITempDataProvider provider = new CookieTempDataProvider(httpContextMock.Object);
+ IDictionary<string, object> loadedTempData = provider.LoadTempData(null /* controllerContext */);
+ Assert.Equal(2, loadedTempData.Count);
+ Assert.Equal("easy as 123", loadedTempData["abc"]);
+ Assert.Equal(1.234, loadedTempData["price"]);
+ }
+ }
+}
diff --git a/test/Microsoft.Web.Mvc.Test/Test/CookieValueProviderFactoryTest.cs b/test/Microsoft.Web.Mvc.Test/Test/CookieValueProviderFactoryTest.cs
new file mode 100644
index 00000000..36cebfe6
--- /dev/null
+++ b/test/Microsoft.Web.Mvc.Test/Test/CookieValueProviderFactoryTest.cs
@@ -0,0 +1,38 @@
+using System.Globalization;
+using System.Web;
+using System.Web.Mvc;
+using Moq;
+using Xunit;
+
+namespace Microsoft.Web.Mvc.Test
+{
+ public class CookieValueProviderFactoryTest
+ {
+ [Fact]
+ public void GetValueProvider()
+ {
+ // Arrange
+ HttpCookieCollection cookies = new HttpCookieCollection
+ {
+ new HttpCookie("foo", "fooValue"),
+ new HttpCookie("bar.baz", "barBazValue"),
+ new HttpCookie("", "emptyValue"),
+ new HttpCookie(null, "nullValue")
+ };
+
+ Mock<ControllerContext> mockControllerContext = new Mock<ControllerContext>();
+ mockControllerContext.Setup(o => o.HttpContext.Request.Cookies).Returns(cookies);
+
+ CookieValueProviderFactory factory = new CookieValueProviderFactory();
+
+ // Act
+ IValueProvider provider = factory.GetValueProvider(mockControllerContext.Object);
+
+ // Assert
+ Assert.Null(provider.GetValue(""));
+ Assert.True(provider.ContainsPrefix("bar"));
+ Assert.Equal("fooValue", provider.GetValue("foo").AttemptedValue);
+ Assert.Equal(CultureInfo.InvariantCulture, provider.GetValue("foo").Culture);
+ }
+ }
+}
diff --git a/test/Microsoft.Web.Mvc.Test/Test/CopyAsyncParametersAttributeTest.cs b/test/Microsoft.Web.Mvc.Test/Test/CopyAsyncParametersAttributeTest.cs
new file mode 100644
index 00000000..87244f62
--- /dev/null
+++ b/test/Microsoft.Web.Mvc.Test/Test/CopyAsyncParametersAttributeTest.cs
@@ -0,0 +1,78 @@
+using System;
+using System.Collections.Generic;
+using System.Web.Mvc;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace Microsoft.Web.Mvc.Test
+{
+ public class CopyAsyncParametersAttributeTest
+ {
+ [Fact]
+ public void OnActionExecuting_CopiesParametersIfControllerIsAsync()
+ {
+ // Arrange
+ CopyAsyncParametersAttribute attr = new CopyAsyncParametersAttribute();
+ SampleAsyncController controller = new SampleAsyncController();
+
+ ActionExecutingContext filterContext = new ActionExecutingContext
+ {
+ ActionParameters = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase),
+ Controller = controller
+ };
+ filterContext.ActionParameters["foo"] = "fooAction";
+ filterContext.ActionParameters["bar"] = "barAction";
+ controller.AsyncManager.Parameters["bar"] = "barAsync";
+ controller.AsyncManager.Parameters["baz"] = "bazAsync";
+
+ // Act
+ attr.OnActionExecuting(filterContext);
+
+ // Assert
+ Assert.Equal("fooAction", controller.AsyncManager.Parameters["foo"]);
+ Assert.Equal("barAction", controller.AsyncManager.Parameters["bar"]);
+ Assert.Equal("bazAsync", controller.AsyncManager.Parameters["baz"]);
+ }
+
+ [Fact]
+ public void OnActionExecuting_DoesNothingIfControllerNotAsync()
+ {
+ // Arrange
+ CopyAsyncParametersAttribute attr = new CopyAsyncParametersAttribute();
+ SampleSyncController controller = new SampleSyncController();
+
+ ActionExecutingContext filterContext = new ActionExecutingContext
+ {
+ ActionParameters = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase),
+ Controller = controller
+ };
+ filterContext.ActionParameters["foo"] = "originalFoo";
+ filterContext.ActionParameters["bar"] = "originalBar";
+
+ // Act
+ attr.OnActionExecuting(filterContext);
+
+ // Assert
+ // If we got this far without crashing, life is good :)
+ }
+
+ [Fact]
+ public void OnActionExecuting_ThrowsIfFilterContextIsNull()
+ {
+ // Arrange
+ CopyAsyncParametersAttribute attr = new CopyAsyncParametersAttribute();
+
+ // Act & assert
+ Assert.ThrowsArgumentNull(
+ delegate { attr.OnActionExecuting(null); }, "filterContext");
+ }
+
+ private class SampleSyncController : Controller
+ {
+ }
+
+ private class SampleAsyncController : AsyncController
+ {
+ }
+ }
+}
diff --git a/test/Microsoft.Web.Mvc.Test/Test/CreditCardAttributeTest.cs b/test/Microsoft.Web.Mvc.Test/Test/CreditCardAttributeTest.cs
new file mode 100644
index 00000000..af5bb98b
--- /dev/null
+++ b/test/Microsoft.Web.Mvc.Test/Test/CreditCardAttributeTest.cs
@@ -0,0 +1,42 @@
+using System.Linq;
+using System.Web.Mvc;
+using Moq;
+using Xunit;
+
+namespace Microsoft.Web.Mvc.Test
+{
+ public class CreditCardAttributeTest
+ {
+ [Fact]
+ public void ClientRule()
+ {
+ // Arrange
+ var attribute = new CreditCardAttribute();
+ var provider = new Mock<ModelMetadataProvider>();
+ var metadata = new ModelMetadata(provider.Object, null, null, typeof(string), "PropertyName");
+
+ // Act
+ ModelClientValidationRule clientRule = attribute.GetClientValidationRules(metadata, null).Single();
+
+ // Assert
+ Assert.Equal("creditcard", clientRule.ValidationType);
+ Assert.Equal("The PropertyName field is not a valid credit card number.", clientRule.ErrorMessage);
+ Assert.Empty(clientRule.ValidationParameters);
+ }
+
+ [Fact]
+ public void IsValidTests()
+ {
+ // Arrange
+ var attribute = new CreditCardAttribute();
+
+ // Act & Assert
+ Assert.True(attribute.IsValid(null)); // Optional values are always valid
+ Assert.True(attribute.IsValid("0000000000000000")); // Simplest valid value
+ Assert.True(attribute.IsValid("1234567890123452")); // Good checksum
+ Assert.True(attribute.IsValid("1234-5678-9012-3452")); // Good checksum, with dashes
+ Assert.False(attribute.IsValid("0000000000000001")); // Bad checksum
+ Assert.False(attribute.IsValid(0)); // Non-string
+ }
+ }
+}
diff --git a/test/Microsoft.Web.Mvc.Test/Test/CssExtensionsTests.cs b/test/Microsoft.Web.Mvc.Test/Test/CssExtensionsTests.cs
new file mode 100644
index 00000000..f15918dc
--- /dev/null
+++ b/test/Microsoft.Web.Mvc.Test/Test/CssExtensionsTests.cs
@@ -0,0 +1,138 @@
+using System;
+using System.Web.Mvc;
+using Microsoft.Web.UnitTestUtil;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace Microsoft.Web.Mvc.Test
+{
+ public class CssExtensionsTests
+ {
+ [Fact]
+ public void CssWithoutFileThrowsArgumentNullException()
+ {
+ // Arrange
+ HtmlHelper html = MvcHelper.GetHtmlHelperWithPath(new ViewDataDictionary());
+
+ // Assert
+ Assert.ThrowsArgumentNullOrEmpty(() => html.Css(null), "file");
+ }
+
+ [Fact]
+ public void CssWithRootedPathRendersProperElement()
+ {
+ // Arrange
+ HtmlHelper html = MvcHelper.GetHtmlHelperWithPath(new ViewDataDictionary());
+
+ // Act
+ MvcHtmlString result = html.Css("~/Correct/Path.css");
+
+ // Assert
+ Assert.Equal("<link href=\"/$(SESSION)/Correct/Path.css\" rel=\"stylesheet\" type=\"text/css\" />", result.ToHtmlString());
+ }
+
+ [Fact]
+ public void CssWithRelativePathRendersProperElement()
+ {
+ // Arrange
+ HtmlHelper html = MvcHelper.GetHtmlHelperWithPath(new ViewDataDictionary());
+
+ // Act
+ MvcHtmlString result = html.Css("../../Correct/Path.css");
+
+ // Assert
+ Assert.Equal("<link href=\"../../Correct/Path.css\" rel=\"stylesheet\" type=\"text/css\" />", result.ToHtmlString());
+ }
+
+ [Fact]
+ public void CssWithRelativeCurrentPathRendersProperElement()
+ {
+ // Arrange
+ HtmlHelper html = MvcHelper.GetHtmlHelperWithPath(new ViewDataDictionary());
+
+ // Act
+ MvcHtmlString result = html.Css("/Correct/Path.css");
+
+ // Assert
+ Assert.Equal("<link href=\"/Correct/Path.css\" rel=\"stylesheet\" type=\"text/css\" />", result.ToHtmlString());
+ }
+
+ [Fact]
+ public void CssWithContentRelativePathRendersProperElement()
+ {
+ // Arrange
+ HtmlHelper html = MvcHelper.GetHtmlHelperWithPath(new ViewDataDictionary());
+
+ // Act
+ MvcHtmlString result = html.Css("Correct/Path.css");
+
+ // Assert
+ Assert.Equal("<link href=\"/$(SESSION)/Content/Correct/Path.css\" rel=\"stylesheet\" type=\"text/css\" />", result.ToHtmlString());
+ }
+
+ [Fact]
+ public void CssWithNullMediaTypeRendersProperElement()
+ {
+ // Arrange
+ HtmlHelper html = MvcHelper.GetHtmlHelperWithPath(new ViewDataDictionary());
+
+ // Act
+ MvcHtmlString result = html.Css("Correct/Path.css", null);
+
+ // Assert
+ Assert.Equal("<link href=\"/$(SESSION)/Content/Correct/Path.css\" rel=\"stylesheet\" type=\"text/css\" />", result.ToHtmlString());
+ }
+
+ [Fact]
+ public void CssWithEmptyMediaTypeRendersProperElement()
+ {
+ // Arrange
+ HtmlHelper html = MvcHelper.GetHtmlHelperWithPath(new ViewDataDictionary());
+
+ // Act
+ MvcHtmlString result = html.Css("Correct/Path.css", String.Empty);
+
+ // Assert
+ Assert.Equal("<link href=\"/$(SESSION)/Content/Correct/Path.css\" media=\"\" rel=\"stylesheet\" type=\"text/css\" />", result.ToHtmlString());
+ }
+
+ [Fact]
+ public void CssWithMediaTypeRendersProperElement()
+ {
+ // Arrange
+ HtmlHelper html = MvcHelper.GetHtmlHelperWithPath(new ViewDataDictionary());
+
+ // Act
+ MvcHtmlString result = html.Css("Correct/Path.css", "Print");
+
+ // Assert
+ Assert.Equal("<link href=\"/$(SESSION)/Content/Correct/Path.css\" media=\"Print\" rel=\"stylesheet\" type=\"text/css\" />", result.ToHtmlString());
+ }
+
+ [Fact]
+ public void CssWithUrlRendersProperElement()
+ {
+ // Arrange
+ HtmlHelper html = MvcHelper.GetHtmlHelperWithPath(new ViewDataDictionary());
+
+ // Act
+ MvcHtmlString result = html.Css("http://ajax.Correct.com/Path.js");
+
+ // Assert
+ Assert.Equal("<link href=\"http://ajax.Correct.com/Path.js\" rel=\"stylesheet\" type=\"text/css\" />", result.ToHtmlString());
+ }
+
+ [Fact]
+ public void CssWithSecureUrlRendersProperElement()
+ {
+ // Arrange
+ HtmlHelper html = MvcHelper.GetHtmlHelperWithPath(new ViewDataDictionary());
+
+ // Act
+ MvcHtmlString result = html.Css("https://ajax.Correct.com/Path.js");
+
+ // Assert
+ Assert.Equal("<link href=\"https://ajax.Correct.com/Path.js\" rel=\"stylesheet\" type=\"text/css\" />", result.ToHtmlString());
+ }
+ }
+}
diff --git a/test/Microsoft.Web.Mvc.Test/Test/DeserializeAttributeTest.cs b/test/Microsoft.Web.Mvc.Test/Test/DeserializeAttributeTest.cs
new file mode 100644
index 00000000..41739173
--- /dev/null
+++ b/test/Microsoft.Web.Mvc.Test/Test/DeserializeAttributeTest.cs
@@ -0,0 +1,105 @@
+using System;
+using System.Runtime.Serialization;
+using System.Web.Mvc;
+using Microsoft.Web.UnitTestUtil;
+using Moq;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace Microsoft.Web.Mvc.Test
+{
+ public class DeserializeAttributeTest
+ {
+ [Fact]
+ public void BinderReturnsDeserializedValue()
+ {
+ // Arrange
+ Mock<MvcSerializer> mockSerializer = new Mock<MvcSerializer>();
+ mockSerializer.Setup(o => o.Deserialize("some-value", SerializationMode.EncryptedAndSigned)).Returns(42);
+ DeserializeAttribute attr = new DeserializeAttribute(SerializationMode.EncryptedAndSigned) { Serializer = mockSerializer.Object };
+
+ IModelBinder binder = attr.GetBinder();
+ ModelBindingContext mbContext = new ModelBindingContext
+ {
+ ModelName = "someKey",
+ ValueProvider = new SimpleValueProvider
+ {
+ { "someKey", "some-value" }
+ }
+ };
+
+ // Act
+ object retVal = binder.BindModel(null, mbContext);
+
+ // Assert
+ Assert.Equal(42, retVal);
+ }
+
+ [Fact]
+ public void BinderReturnsNullIfValueProviderDoesNotContainKey()
+ {
+ // Arrange
+ DeserializeAttribute attr = new DeserializeAttribute();
+ IModelBinder binder = attr.GetBinder();
+ ModelBindingContext mbContext = new ModelBindingContext
+ {
+ ModelName = "someKey",
+ ValueProvider = new SimpleValueProvider()
+ };
+
+ // Act
+ object retVal = binder.BindModel(null, mbContext);
+
+ // Assert
+ Assert.Null(retVal);
+ }
+
+ [Fact]
+ public void BinderThrowsIfBindingContextIsNull()
+ {
+ // Arrange
+ DeserializeAttribute attr = new DeserializeAttribute();
+ IModelBinder binder = attr.GetBinder();
+
+ // Act & assert
+ Assert.ThrowsArgumentNull(
+ delegate { binder.BindModel(null, null); }, "bindingContext");
+ }
+
+ [Fact]
+ public void BinderThrowsIfDataCorrupt()
+ {
+ // Arrange
+ Mock<MvcSerializer> mockSerializer = new Mock<MvcSerializer>();
+ mockSerializer.Setup(o => o.Deserialize(It.IsAny<string>(), It.IsAny<SerializationMode>())).Throws(new SerializationException());
+ DeserializeAttribute attr = new DeserializeAttribute { Serializer = mockSerializer.Object };
+
+ IModelBinder binder = attr.GetBinder();
+ ModelBindingContext mbContext = new ModelBindingContext
+ {
+ ModelName = "someKey",
+ ValueProvider = new SimpleValueProvider
+ {
+ { "someKey", "This data is corrupted." }
+ }
+ };
+
+ // Act & assert
+ Exception exception = Assert.Throws<SerializationException>(
+ delegate { binder.BindModel(null, mbContext); });
+ }
+
+ [Fact]
+ public void ModeDefaultsToSigned()
+ {
+ // Arrange
+ DeserializeAttribute attr = new DeserializeAttribute();
+
+ // Act
+ SerializationMode defaultMode = attr.Mode;
+
+ // Assert
+ Assert.Equal(SerializationMode.Signed, defaultMode);
+ }
+ }
+}
diff --git a/test/Microsoft.Web.Mvc.Test/Test/DynamicReflectionObjectTest.cs b/test/Microsoft.Web.Mvc.Test/Test/DynamicReflectionObjectTest.cs
new file mode 100644
index 00000000..fd5f8a11
--- /dev/null
+++ b/test/Microsoft.Web.Mvc.Test/Test/DynamicReflectionObjectTest.cs
@@ -0,0 +1,54 @@
+using System;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace Microsoft.Web.Mvc.Test
+{
+ public class DynamicReflectionObjectTest
+ {
+ [Fact]
+ public void NoPropertiesThrows()
+ {
+ // Arrange
+ dynamic dro = DynamicReflectionObject.Wrap(new { });
+
+ // Act & Assert
+ Assert.Throws<InvalidOperationException>(
+ () => dro.baz,
+ "The property baz doesn't exist. There are no public properties on this object.");
+ }
+
+ [Fact]
+ public void UnknownPropertyThrows()
+ {
+ // Arrange
+ dynamic dro = DynamicReflectionObject.Wrap(new { foo = 3.4, biff = "Two", bar = 1 });
+
+ // Act & Assert
+ Assert.Throws<InvalidOperationException>(
+ () => dro.baz,
+ "The property baz doesn't exist. Supported properties are: bar, biff, foo.");
+ }
+
+ [Fact]
+ public void CanAccessProperties()
+ {
+ // Arrange
+ dynamic dro = DynamicReflectionObject.Wrap(new { foo = "Hello world!", bar = 42 });
+
+ // Act & Assert
+ Assert.Equal("Hello world!", dro.foo);
+ Assert.Equal(42, dro.bar);
+ }
+
+ [Fact]
+ public void CanAccessNestedAnonymousProperties()
+ {
+ // Arrange
+ dynamic dro = DynamicReflectionObject.Wrap(new { foo = new { bar = "Hello world!" } });
+
+ // Act & Assert
+ Assert.Equal("Hello world!", dro.foo.bar);
+ }
+ }
+}
diff --git a/test/Microsoft.Web.Mvc.Test/Test/DynamicViewDataDictionaryTest.cs b/test/Microsoft.Web.Mvc.Test/Test/DynamicViewDataDictionaryTest.cs
new file mode 100644
index 00000000..09666ed9
--- /dev/null
+++ b/test/Microsoft.Web.Mvc.Test/Test/DynamicViewDataDictionaryTest.cs
@@ -0,0 +1,115 @@
+using System;
+using System.Web.Mvc;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace Microsoft.Web.Mvc.Test
+{
+ public class DynamicViewDataDictionaryTest
+ {
+ // Property-style accessor
+
+ [Fact]
+ public void Property_UnknownItemReturnsEmptyString()
+ {
+ // Arrange
+ ViewDataDictionary vdd = new ViewDataDictionary();
+ dynamic dvdd = DynamicViewDataDictionary.Wrap(vdd);
+
+ // Act
+ object result = dvdd.Foo;
+
+ // Assert
+ Assert.Equal(String.Empty, result);
+ }
+
+ [Fact]
+ public void Property_CanAccessViewDataValues()
+ {
+ // Arrange
+ ViewDataDictionary vdd = new ViewDataDictionary();
+ vdd["Foo"] = "Value for Foo";
+ dynamic dvdd = DynamicViewDataDictionary.Wrap(vdd);
+
+ // Act
+ object result = dvdd.Foo;
+
+ // Assert
+ Assert.Equal("Value for Foo", result);
+ }
+
+ [Fact]
+ public void Property_CanAccessModelProperties()
+ {
+ ViewDataDictionary vdd = new ViewDataDictionary(new { Foo = "Value for Foo" });
+ dynamic dvdd = DynamicViewDataDictionary.Wrap(vdd);
+
+ // Act
+ object result = dvdd.Foo;
+
+ // Assert
+ Assert.Equal("Value for Foo", result);
+ }
+
+ // Index-style accessor
+
+ [Fact]
+ public void Indexer_GuardClauses()
+ {
+ // Arrange
+ ViewDataDictionary vdd = new ViewDataDictionary();
+ dynamic dvdd = DynamicViewDataDictionary.Wrap(vdd);
+
+ // Act & Assert
+ Assert.Throws<ArgumentException>(
+ () => { var x = dvdd["foo", "bar"]; },
+ "DynamicViewDataDictionary only supports single indexers.");
+
+ Assert.Throws<ArgumentException>(
+ () => { var x = dvdd[42]; },
+ "DynamicViewDataDictionary only supports string-based indexers.");
+ }
+
+ [Fact]
+ public void Indexer_UnknownItemReturnsEmptyString()
+ {
+ // Arrange
+ ViewDataDictionary vdd = new ViewDataDictionary();
+ dynamic dvdd = DynamicViewDataDictionary.Wrap(vdd);
+
+ // Act
+ object result = dvdd["Foo"];
+
+ // Assert
+ Assert.Equal(String.Empty, result);
+ }
+
+ [Fact]
+ public void Indexer_CanAccessViewDataValues()
+ {
+ // Arrange
+ ViewDataDictionary vdd = new ViewDataDictionary();
+ vdd["Foo"] = "Value for Foo";
+ dynamic dvdd = DynamicViewDataDictionary.Wrap(vdd);
+
+ // Act
+ object result = dvdd["Foo"];
+
+ // Assert
+ Assert.Equal("Value for Foo", result);
+ }
+
+ [Fact]
+ public void Indexer_CanAccessModelProperties()
+ {
+ ViewDataDictionary vdd = new ViewDataDictionary(new { Foo = "Value for Foo" });
+ dynamic dvdd = DynamicViewDataDictionary.Wrap(vdd);
+
+ // Act
+ object result = dvdd["Foo"];
+
+ // Assert
+ Assert.Equal("Value for Foo", result);
+ }
+ }
+}
diff --git a/test/Microsoft.Web.Mvc.Test/Test/DynamicViewPageTest.cs b/test/Microsoft.Web.Mvc.Test/Test/DynamicViewPageTest.cs
new file mode 100644
index 00000000..d0ca8c65
--- /dev/null
+++ b/test/Microsoft.Web.Mvc.Test/Test/DynamicViewPageTest.cs
@@ -0,0 +1,53 @@
+using Xunit;
+
+namespace Microsoft.Web.Mvc.Test
+{
+ public class DynamicViewPageTest
+ {
+ // DynamicViewPage
+
+ [Fact]
+ public void AnonymousObjectsAreWrapped()
+ {
+ // Arrange
+ DynamicViewPage page = new DynamicViewPage();
+ page.ViewData.Model = new { foo = "Hello world!" };
+
+ // Act & Assert
+ Assert.Equal("Microsoft.Web.Mvc.DynamicReflectionObject", page.Model.GetType().FullName);
+ }
+
+ [Fact]
+ public void NonAnonymousObjectsAreNotWrapped()
+ {
+ // Arrange
+ DynamicViewPage page = new DynamicViewPage();
+ page.ViewData.Model = "Hello world!";
+
+ // Act & Assert
+ Assert.Equal(typeof(string), page.Model.GetType());
+ }
+
+ [Fact]
+ public void ViewDataDictionaryIsWrapped()
+ {
+ // Arrange
+ DynamicViewPage page = new DynamicViewPage();
+
+ // Act & Assert
+ Assert.Equal("Microsoft.Web.Mvc.DynamicViewDataDictionary", page.ViewData.GetType().FullName);
+ }
+
+ // DynamicViewPage<T>
+
+ [Fact]
+ public void Generic_ViewDataDictionaryIsWrapped()
+ {
+ // Arrange
+ DynamicViewPage<object> page = new DynamicViewPage<object>();
+
+ // Act & Assert
+ Assert.Equal("Microsoft.Web.Mvc.DynamicViewDataDictionary", page.ViewData.GetType().FullName);
+ }
+ }
+}
diff --git a/test/Microsoft.Web.Mvc.Test/Test/ElementalValueProviderTest.cs b/test/Microsoft.Web.Mvc.Test/Test/ElementalValueProviderTest.cs
new file mode 100644
index 00000000..5179f515
--- /dev/null
+++ b/test/Microsoft.Web.Mvc.Test/Test/ElementalValueProviderTest.cs
@@ -0,0 +1,54 @@
+using System;
+using System.Globalization;
+using System.Web.Mvc;
+using Xunit;
+
+namespace Microsoft.Web.Mvc.Test
+{
+ public class ElementalValueProviderTest
+ {
+ [Fact]
+ public void ContainsPrefix()
+ {
+ // Arrange
+ ElementalValueProvider valueProvider = new ElementalValueProvider("foo", 42, null);
+
+ // Act & assert
+ Assert.True(valueProvider.ContainsPrefix("foo"));
+ Assert.False(valueProvider.ContainsPrefix("bar"));
+ }
+
+ [Fact]
+ public void GetValue_NameDoesNotMatch_ReturnsNull()
+ {
+ // Arrange
+ CultureInfo culture = CultureInfo.GetCultureInfo("fr-FR");
+ DateTime rawValue = new DateTime(2001, 1, 2);
+ ElementalValueProvider valueProvider = new ElementalValueProvider("foo", rawValue, culture);
+
+ // Act
+ ValueProviderResult vpResult = valueProvider.GetValue("bar");
+
+ // Assert
+ Assert.Null(vpResult);
+ }
+
+ [Fact]
+ public void GetValue_NameMatches_ReturnsValueProviderResult()
+ {
+ // Arrange
+ CultureInfo culture = CultureInfo.GetCultureInfo("fr-FR");
+ DateTime rawValue = new DateTime(2001, 1, 2);
+ ElementalValueProvider valueProvider = new ElementalValueProvider("foo", rawValue, culture);
+
+ // Act
+ ValueProviderResult vpResult = valueProvider.GetValue("FOO");
+
+ // Assert
+ Assert.NotNull(vpResult);
+ Assert.Equal(rawValue, vpResult.RawValue);
+ Assert.Equal("02/01/2001 00:00:00", vpResult.AttemptedValue);
+ Assert.Equal(culture, vpResult.Culture);
+ }
+ }
+}
diff --git a/test/Microsoft.Web.Mvc.Test/Test/EmailAddressAttribueTest.cs b/test/Microsoft.Web.Mvc.Test/Test/EmailAddressAttribueTest.cs
new file mode 100644
index 00000000..0fc5ab8c
--- /dev/null
+++ b/test/Microsoft.Web.Mvc.Test/Test/EmailAddressAttribueTest.cs
@@ -0,0 +1,42 @@
+using System.Linq;
+using System.Web.Mvc;
+using Moq;
+using Xunit;
+
+namespace Microsoft.Web.Mvc.Test
+{
+ public class EmailAddressAttribueTest
+ {
+ [Fact]
+ public void ClientRule()
+ {
+ // Arrange
+ var attribute = new EmailAddressAttribute();
+ var provider = new Mock<ModelMetadataProvider>();
+ var metadata = new ModelMetadata(provider.Object, null, null, typeof(string), "PropertyName");
+
+ // Act
+ ModelClientValidationRule clientRule = attribute.GetClientValidationRules(metadata, null).Single();
+
+ // Assert
+ Assert.Equal("email", clientRule.ValidationType);
+ Assert.Equal("The PropertyName field is not a valid e-mail address.", clientRule.ErrorMessage);
+ Assert.Empty(clientRule.ValidationParameters);
+ }
+
+ [Fact]
+ public void IsValidTests()
+ {
+ // Arrange
+ var attribute = new EmailAddressAttribute();
+
+ // Act & Assert
+ Assert.True(attribute.IsValid(null)); // Optional values are always valid
+ Assert.True(attribute.IsValid("joe@contoso.com"));
+ Assert.True(attribute.IsValid("joe%fred@contoso.com"));
+ Assert.False(attribute.IsValid("joe"));
+ Assert.False(attribute.IsValid("joe@"));
+ Assert.False(attribute.IsValid("joe@contoso"));
+ }
+ }
+}
diff --git a/test/Microsoft.Web.Mvc.Test/Test/ExpressionHelperTest.cs b/test/Microsoft.Web.Mvc.Test/Test/ExpressionHelperTest.cs
new file mode 100644
index 00000000..3f96bf70
--- /dev/null
+++ b/test/Microsoft.Web.Mvc.Test/Test/ExpressionHelperTest.cs
@@ -0,0 +1,302 @@
+using System;
+using System.Linq.Expressions;
+using System.Web.Mvc;
+using System.Web.Routing;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+using ExpressionHelper = Microsoft.Web.Mvc.Internal.ExpressionHelper;
+
+namespace Microsoft.Web.Mvc.Test
+{
+ public class ExpressionHelperTest
+ {
+ [Fact]
+ public void BuildRouteValueDictionary_TargetsAsynchronousAsyncMethod_StripsSuffix()
+ {
+ // Arrange
+ Expression<Action<TestAsyncController>> expr = (c => c.AsynchronousAsync());
+
+ // Act
+ RouteValueDictionary rvd = ExpressionHelper.GetRouteValuesFromExpression(expr);
+
+ // Assert
+ Assert.Equal("Asynchronous", rvd["action"]);
+ Assert.Equal("TestAsync", rvd["controller"]);
+ Assert.False(rvd.ContainsKey("area"));
+ }
+
+ [Fact]
+ public void BuildRouteValueDictionary_TargetsAsynchronousCompletedMethod_Throws()
+ {
+ // Arrange
+ Expression<Action<TestAsyncController>> expr = (c => c.AsynchronousCompleted());
+
+ // Act & assert
+ Assert.Throws<InvalidOperationException>(
+ delegate { ExpressionHelper.GetRouteValuesFromExpression(expr); },
+ @"The method 'AsynchronousCompleted' is an asynchronous completion method and cannot be called directly.");
+ }
+
+ [Fact]
+ public void BuildRouteValueDictionary_TargetsControllerWithAreaAttribute_AddsAreaName()
+ {
+ // Arrange
+ Expression<Action<ControllerWithAreaController>> expr = c => c.Index();
+
+ // Act
+ RouteValueDictionary rvd = ExpressionHelper.GetRouteValuesFromExpression(expr);
+
+ // Assert
+ Assert.Equal("Index", rvd["action"]);
+ Assert.Equal("ControllerWithArea", rvd["controller"]);
+ Assert.Equal("the area name", rvd["area"]);
+ }
+
+ [Fact]
+ public void BuildRouteValueDictionary_TargetsNonActionMethod_Throws()
+ {
+ // Arrange
+ Expression<Action<TestController>> expr = (c => c.NotAnAction());
+
+ // Act & assert
+ Assert.Throws<InvalidOperationException>(
+ delegate { ExpressionHelper.GetRouteValuesFromExpression(expr); },
+ @"The method 'NotAnAction' is marked [NonAction] and cannot be called directly.");
+ }
+
+ [Fact]
+ public void BuildRouteValueDictionary_TargetsRenamedMethod_UsesNewName()
+ {
+ // Arrange
+ Expression<Action<TestController>> expr = (c => c.Renamed());
+
+ // Act
+ RouteValueDictionary rvd = ExpressionHelper.GetRouteValuesFromExpression(expr);
+
+ // Assert
+ Assert.Equal("NewName", rvd["action"]);
+ Assert.Equal("Test", rvd["controller"]);
+ Assert.False(rvd.ContainsKey("area"));
+ }
+
+ [Fact]
+ public void BuildRouteValueDictionary_TargetsSynchronousMethodOnAsyncController_ReturnsOriginalName()
+ {
+ // Arrange
+ Expression<Action<TestAsyncController>> expr = (c => c.Synchronous());
+
+ // Act
+ RouteValueDictionary rvd = ExpressionHelper.GetRouteValuesFromExpression(expr);
+
+ // Assert
+ Assert.Equal("Synchronous", rvd["action"]);
+ Assert.Equal("TestAsync", rvd["controller"]);
+ Assert.False(rvd.ContainsKey("area"));
+ }
+
+ [Fact]
+ public void BuildRouteValueDictionaryWithNullExpressionThrowsArgumentNullException()
+ {
+ Assert.ThrowsArgumentNull(
+ () => ExpressionHelper.GetRouteValuesFromExpression<TestController>(null),
+ "action");
+ }
+
+ [Fact]
+ public void BuildRouteValueDictionaryWithNonMethodExpressionThrowsInvalidOperationException()
+ {
+ // Arrange
+ Expression<Action<TestController>> expression = c => new TestController();
+
+ // Act & Assert
+ Assert.Throws<ArgumentException>(
+ () => ExpressionHelper.GetRouteValuesFromExpression(expression),
+ "Expression must be a method call." + Environment.NewLine + "Parameter name: action");
+ }
+
+ [Fact]
+ public void BuildRouteValueDictionaryWithoutControllerSuffixThrowsInvalidOperationException()
+ {
+ // Arrange
+ Expression<Action<TestControllerNot>> index = (c => c.Index(123));
+
+ // Act & Assert
+ Assert.Throws<ArgumentException>(
+ () => ExpressionHelper.GetRouteValuesFromExpression(index),
+ "Controller name must end in 'Controller'." + Environment.NewLine + "Parameter name: action");
+ }
+
+ [Fact]
+ public void BuildRouteValueDictionaryWithControllerBaseClassThrowsInvalidOperationException()
+ {
+ // Arrange
+ Expression<Action<Controller>> index = (c => c.Dispose());
+
+ // Act & Assert
+ Assert.Throws<ArgumentException>(
+ () => ExpressionHelper.GetRouteValuesFromExpression(index),
+ "Cannot route to class named 'Controller'." + Environment.NewLine + "Parameter name: action");
+ }
+
+ [Fact]
+ public void BuildRouteValueDictionaryAddsControllerNameToDictionary()
+ {
+ // Arrange
+ Expression<Action<TestController>> index = (c => c.Index(123));
+
+ // Act
+ RouteValueDictionary rvd = ExpressionHelper.GetRouteValuesFromExpression(index);
+
+ // Assert
+ Assert.Equal("Test", rvd["Controller"]);
+ }
+
+ [Fact]
+ public void BuildRouteValueDictionaryFromExpressionReturnsCorrectDictionary()
+ {
+ // Arrange
+ Expression<Action<TestController>> index = (c => c.Index(123));
+
+ // Act
+ RouteValueDictionary rvd = ExpressionHelper.GetRouteValuesFromExpression(index);
+
+ // Assert
+ Assert.Equal("Test", rvd["Controller"]);
+ Assert.Equal("Index", rvd["Action"]);
+ Assert.Equal(123, rvd["page"]);
+ }
+
+ [Fact]
+ public void BuildRouteValueDictionaryFromNonConstantExpressionReturnsCorrectDictionary()
+ {
+ // Arrange
+ Expression<Action<TestController>> index = (c => c.About(Foo));
+
+ // Act
+ RouteValueDictionary rvd = ExpressionHelper.GetRouteValuesFromExpression(index);
+
+ // Assert
+ Assert.Equal("Test", rvd["Controller"]);
+ Assert.Equal("About", rvd["Action"]);
+ Assert.Equal("FooValue", rvd["s"]);
+ }
+
+ [Fact]
+ public void GetInputNameFromPropertyExpressionReturnsPropertyName()
+ {
+ // Arrange
+ Expression<Func<TestModel, int>> expression = m => m.IntProperty;
+
+ // Act
+ string name = ExpressionHelper.GetInputName(expression);
+
+ // Assert
+ Assert.Equal("IntProperty", name);
+ }
+
+ [Fact]
+ public void GetInputNameFromPropertyWithMethodCallExpressionReturnsPropertyName()
+ {
+ // Arrange
+ Expression<Func<TestModel, string>> expression = m => m.IntProperty.ToString();
+
+ // Act
+ string name = ExpressionHelper.GetInputName(expression);
+
+ // Assert
+ Assert.Equal("IntProperty", name);
+ }
+
+ [Fact]
+ public void GetInputNameFromPropertyWithTwoMethodCallExpressionReturnsPropertyName()
+ {
+ // Arrange
+ Expression<Func<TestModel, string>> expression = m => m.IntProperty.ToString().ToUpper();
+
+ // Act
+ string name = ExpressionHelper.GetInputName(expression);
+
+ // Assert
+ Assert.Equal("IntProperty", name);
+ }
+
+ [Fact]
+ public void GetInputNameFromExpressionWithTwoPropertiesUsesWholeExpression()
+ {
+ // Arrange
+ Expression<Func<TestModel, int>> expression = m => m.StringProperty.Length;
+
+ // Act
+ string name = ExpressionHelper.GetInputName(expression);
+
+ // Assert
+ Assert.Equal("StringProperty.Length", name);
+ }
+
+ public class TestController : Controller
+ {
+ public ActionResult Index(int page)
+ {
+ return null;
+ }
+
+ public string About(string s)
+ {
+ return "The value is " + s;
+ }
+
+ [ActionName("NewName")]
+ public void Renamed()
+ {
+ }
+
+ [NonAction]
+ public void NotAnAction()
+ {
+ }
+ }
+
+ public class TestAsyncController : AsyncController
+ {
+ public void Synchronous()
+ {
+ }
+
+ public void AsynchronousAsync()
+ {
+ }
+
+ public void AsynchronousCompleted()
+ {
+ }
+ }
+
+ public string Foo
+ {
+ get { return "FooValue"; }
+ }
+
+ public class TestControllerNot : Controller
+ {
+ public ActionResult Index(int page)
+ {
+ return null;
+ }
+ }
+
+ [ActionLinkArea("the area name")]
+ public class ControllerWithAreaController : Controller
+ {
+ public ActionResult Index()
+ {
+ return null;
+ }
+ }
+
+ public class TestModel
+ {
+ public int IntProperty { get; set; }
+ public string StringProperty { get; set; }
+ }
+ }
+}
diff --git a/test/Microsoft.Web.Mvc.Test/Test/FileExtensionsAttributeTest.cs b/test/Microsoft.Web.Mvc.Test/Test/FileExtensionsAttributeTest.cs
new file mode 100644
index 00000000..35386414
--- /dev/null
+++ b/test/Microsoft.Web.Mvc.Test/Test/FileExtensionsAttributeTest.cs
@@ -0,0 +1,53 @@
+using System.Linq;
+using System.Web.Mvc;
+using Moq;
+using Xunit;
+
+namespace Microsoft.Web.Mvc.Test
+{
+ public class FileExtensionsAttributeTest
+ {
+ [Fact]
+ public void DefaultExtensions()
+ {
+ Assert.Equal("png,jpg,jpeg,gif", new FileExtensionsAttribute().Extensions);
+ }
+
+ [Fact]
+ public void ClientRule()
+ {
+ // Arrange
+ var attribute = new FileExtensionsAttribute { Extensions = " FoO, .bar,baz " };
+ var provider = new Mock<ModelMetadataProvider>();
+ var metadata = new ModelMetadata(provider.Object, null, null, typeof(string), "PropertyName");
+
+ // Act
+ ModelClientValidationRule clientRule = attribute.GetClientValidationRules(metadata, null).Single();
+
+ // Assert
+ Assert.Equal("accept", clientRule.ValidationType);
+ Assert.Equal("The PropertyName field only accepts files with the following extensions: .foo, .bar, .baz", clientRule.ErrorMessage);
+ Assert.Single(clientRule.ValidationParameters);
+ Assert.Equal("foo,bar,baz", clientRule.ValidationParameters["exts"]);
+ }
+
+ [Fact]
+ public void IsValidTests()
+ {
+ // Arrange
+ var attribute = new FileExtensionsAttribute();
+
+ // Act & Assert
+ Assert.True(attribute.IsValid(null)); // Optional values are always valid
+ Assert.True(attribute.IsValid("foo.png"));
+ Assert.True(attribute.IsValid("foo.jpeg"));
+ Assert.True(attribute.IsValid("foo.jpg"));
+ Assert.True(attribute.IsValid("foo.gif"));
+ Assert.True(attribute.IsValid(@"C:\Foo\baz.jpg"));
+ Assert.False(attribute.IsValid("foo"));
+ Assert.False(attribute.IsValid("foo.png.pif"));
+ Assert.False(attribute.IsValid(@"C:\foo.png\bar"));
+ Assert.False(attribute.IsValid("\0foo.png")); // Illegal character
+ }
+ }
+}
diff --git a/test/Microsoft.Web.Mvc.Test/Test/FormExtensionsTest.cs b/test/Microsoft.Web.Mvc.Test/Test/FormExtensionsTest.cs
new file mode 100644
index 00000000..5b9f8874
--- /dev/null
+++ b/test/Microsoft.Web.Mvc.Test/Test/FormExtensionsTest.cs
@@ -0,0 +1,98 @@
+using System;
+using System.IO;
+using System.Web;
+using System.Web.Mvc;
+using System.Web.Routing;
+using Moq;
+using Xunit;
+
+namespace Microsoft.Web.Mvc.Test
+{
+ public class FormExtensionsTest
+ {
+ internal const string AppPathModifier = "/$(SESSION)";
+
+ [Fact]
+ public void FormWithPostAction()
+ {
+ // Arrange
+ StringWriter writer;
+ HtmlHelper htmlHelper = GetFormHelper(out writer);
+
+ // Act
+ IDisposable formDisposable = htmlHelper.BeginForm<FormController>(action => action.About());
+ formDisposable.Dispose();
+
+ // Assert
+ Assert.Equal(@"<form action=""" + AppPathModifier + @"/Form/About"" method=""post""></form>", writer.ToString());
+ }
+
+ [Fact]
+ public void FormWithPostActionAndObjectAttributes()
+ {
+ // Arrange
+ StringWriter writer;
+ HtmlHelper htmlHelper = GetFormHelper(out writer);
+
+ // Act
+ IDisposable formDisposable = htmlHelper.BeginForm<FormController>(action => action.About(), FormMethod.Get, new { baz = "baz" });
+ formDisposable.Dispose();
+
+ // Assert
+ Assert.Equal(@"<form action=""" + AppPathModifier + @"/Form/About"" baz=""baz"" method=""get""></form>", writer.ToString());
+ }
+
+ [Fact]
+ public void FormWithPostActionAndObjectAttributesWithUnderscores()
+ {
+ // Arrange
+ StringWriter writer;
+ HtmlHelper htmlHelper = GetFormHelper(out writer);
+
+ // Act
+ IDisposable formDisposable = htmlHelper.BeginForm<FormController>(action => action.About(), FormMethod.Get, new { foo_baz = "baz" });
+ formDisposable.Dispose();
+
+ // Assert
+ Assert.Equal(@"<form action=""" + AppPathModifier + @"/Form/About"" foo-baz=""baz"" method=""get""></form>", writer.ToString());
+ }
+
+ public class FormController : Controller
+ {
+ public ActionResult About()
+ {
+ return RedirectToAction("foo");
+ }
+ }
+
+ private static HtmlHelper GetFormHelper(out StringWriter writer)
+ {
+ Mock<HttpRequestBase> mockHttpRequest = new Mock<HttpRequestBase>();
+ mockHttpRequest.Setup(r => r.Url).Returns(new Uri("http://www.contoso.com/some/path"));
+ Mock<HttpResponseBase> mockHttpResponse = new Mock<HttpResponseBase>(MockBehavior.Strict);
+
+ mockHttpResponse.Setup(r => r.ApplyAppPathModifier(It.IsAny<string>())).Returns<string>(r => AppPathModifier + r);
+ Mock<HttpContextBase> mockHttpContext = new Mock<HttpContextBase>();
+ mockHttpContext.Setup(c => c.Request).Returns(mockHttpRequest.Object);
+ mockHttpContext.Setup(c => c.Response).Returns(mockHttpResponse.Object);
+ RouteCollection rt = new RouteCollection();
+ rt.Add(new Route("{controller}/{action}/{id}", null) { Defaults = new RouteValueDictionary(new { id = "defaultid" }) });
+ rt.Add("namedroute", new Route("named/{controller}/{action}/{id}", null) { Defaults = new RouteValueDictionary(new { id = "defaultid" }) });
+ RouteData rd = new RouteData();
+ rd.Values.Add("controller", "home");
+ rd.Values.Add("action", "oldaction");
+
+ Mock<ViewContext> mockViewContext = new Mock<ViewContext>();
+ mockViewContext.Setup(c => c.HttpContext).Returns(mockHttpContext.Object);
+ mockViewContext.Setup(c => c.RouteData).Returns(rd);
+ writer = new StringWriter();
+ mockViewContext.Setup(c => c.Writer).Returns(writer);
+
+ HtmlHelper helper = new HtmlHelper(
+ mockViewContext.Object,
+ new Mock<IViewDataContainer>().Object,
+ rt);
+ return helper;
+ }
+ }
+}
diff --git a/test/Microsoft.Web.Mvc.Test/Test/ImageExtensionsTest.cs b/test/Microsoft.Web.Mvc.Test/Test/ImageExtensionsTest.cs
new file mode 100644
index 00000000..ab5ddb74
--- /dev/null
+++ b/test/Microsoft.Web.Mvc.Test/Test/ImageExtensionsTest.cs
@@ -0,0 +1,130 @@
+using System.Web.Mvc;
+using System.Web.Routing;
+using Microsoft.Web.UnitTestUtil;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace Microsoft.Web.Mvc.Test
+{
+ public class ImageExtensionsTest
+ {
+ [Fact]
+ public void ImageWithEmptyRelativeUrlThrowsArgumentNullException()
+ {
+ HtmlHelper html = MvcHelper.GetHtmlHelperWithPath(new ViewDataDictionary());
+ Assert.ThrowsArgumentNullOrEmpty(() => html.Image(null), "imageRelativeUrl");
+ }
+
+ [Fact]
+ public void ImageStaticWithEmptyRelativeUrlThrowsArgumentNullException()
+ {
+ Assert.ThrowsArgumentNullOrEmpty(() => ImageExtensions.Image((string)null, "alt", null), "imageUrl");
+ }
+
+ [Fact]
+ public void ImageWithRelativeUrlRendersProperImageTag()
+ {
+ HtmlHelper html = MvcHelper.GetHtmlHelperWithPath(new ViewDataDictionary());
+ MvcHtmlString imageResult = html.Image("/system/web/mvc.jpg");
+ // NOTE: Although XHTML requires an alt tag, we don't construct one for you. Specify it yourself.
+ Assert.Equal("<img src=\"/system/web/mvc.jpg\" />", imageResult.ToHtmlString());
+ }
+
+ [Fact]
+ public void ImageWithWithAttributesWithUnderscores()
+ {
+ HtmlHelper html = MvcHelper.GetHtmlHelperWithPath(new ViewDataDictionary());
+ MvcHtmlString imageResult = html.Image("/system/web/mvc.jpg", new { foo_bar = "baz" });
+ Assert.Equal("<img foo-bar=\"baz\" src=\"/system/web/mvc.jpg\" />", imageResult.ToHtmlString());
+ }
+
+ [Fact]
+ public void ImageWithAltValueRendersImageWithAltTag()
+ {
+ HtmlHelper html = MvcHelper.GetHtmlHelperWithPath(new ViewDataDictionary());
+ MvcHtmlString imageResult = html.Image("/system/web/mvc.jpg", "this is an alt value");
+ Assert.Equal("<img alt=\"this is an alt value\" src=\"/system/web/mvc.jpg\" title=\"this is an alt value\" />", imageResult.ToHtmlString());
+ }
+
+ [Fact]
+ public void ImageWithAltValueInObjectDictionaryRendersImageWithAltAndTitleTag()
+ {
+ HtmlHelper html = MvcHelper.GetHtmlHelperWithPath(new ViewDataDictionary());
+ MvcHtmlString imageResult = html.Image("/system/web/mvc.jpg", new { alt = "this is an alt value" });
+ Assert.Equal("<img alt=\"this is an alt value\" src=\"/system/web/mvc.jpg\" title=\"this is an alt value\" />", imageResult.ToHtmlString());
+ }
+
+ [Fact]
+ public void ImageWithAltValueHtmlAttributeEncodesAltTag()
+ {
+ HtmlHelper html = MvcHelper.GetHtmlHelperWithPath(new ViewDataDictionary());
+ MvcHtmlString imageResult = html.Image("/system/web/mvc.jpg", @"<"">");
+ Assert.Equal("<img alt=\"&lt;&quot;>\" src=\"/system/web/mvc.jpg\" title=\"&lt;&quot;>\" />", imageResult.ToHtmlString());
+ }
+
+ [Fact]
+ public void ImageWithAltValueInObjectDictionaryHtmlAttributeEncodesAltTag()
+ {
+ HtmlHelper html = MvcHelper.GetHtmlHelperWithPath(new ViewDataDictionary());
+ MvcHtmlString imageResult = html.Image("/system/web/mvc.jpg", new { alt = "this is an alt value" });
+ Assert.Equal("<img alt=\"this is an alt value\" src=\"/system/web/mvc.jpg\" title=\"this is an alt value\" />", imageResult.ToHtmlString());
+ }
+
+ [Fact]
+ public void ImageWithAltSpecifiedAndInDictionaryRendersExplicit()
+ {
+ HtmlHelper html = MvcHelper.GetHtmlHelperWithPath(new ViewDataDictionary());
+ MvcHtmlString imageResult = html.Image("/system/web/mvc.jpg", "specified-alt", new { alt = "object-dictionary-alt" });
+ Assert.Equal("<img alt=\"object-dictionary-alt\" src=\"/system/web/mvc.jpg\" title=\"object-dictionary-alt\" />", imageResult.ToHtmlString());
+ }
+
+ [Fact]
+ public void ImageWithAltAndAttributesWithUnderscores()
+ {
+ HtmlHelper html = MvcHelper.GetHtmlHelperWithPath(new ViewDataDictionary());
+ MvcHtmlString imageResult = html.Image("/system/web/mvc.jpg", "specified-alt", new { foo_bar = "baz" });
+ Assert.Equal("<img alt=\"specified-alt\" foo-bar=\"baz\" src=\"/system/web/mvc.jpg\" title=\"specified-alt\" />", imageResult.ToHtmlString());
+ }
+
+ [Fact]
+ public void ImageWithSrcSpecifiedAndInDictionaryRendersExplicit()
+ {
+ HtmlHelper html = MvcHelper.GetHtmlHelperWithPath(new ViewDataDictionary());
+ MvcHtmlString imageResult = html.Image("/system/web/mvc.jpg", new { src = "explicit.jpg" });
+ Assert.Equal("<img src=\"explicit.jpg\" />", imageResult.ToHtmlString());
+ }
+
+ [Fact]
+ public void ImageWithOtherAttributesRendersThoseAttributesCaseSensitively()
+ {
+ HtmlHelper html = MvcHelper.GetHtmlHelperWithPath(new ViewDataDictionary());
+ MvcHtmlString imageResult = html.Image("/system/web/mvc.jpg", new { width = 100, Height = 200 });
+ Assert.Equal("<img Height=\"200\" src=\"/system/web/mvc.jpg\" width=\"100\" />", imageResult.ToHtmlString());
+ }
+
+ [Fact]
+ public void ImageWithUrlAndDictionaryRendersAttributes()
+ {
+ HtmlHelper html = MvcHelper.GetHtmlHelperWithPath(new ViewDataDictionary());
+ var attributes = new RouteValueDictionary(new { width = 125 });
+ MvcHtmlString imageResult = html.Image("/system/web/mvc.jpg", attributes);
+ Assert.Equal("<img src=\"/system/web/mvc.jpg\" width=\"125\" />", imageResult.ToHtmlString());
+ }
+
+ [Fact]
+ public void ImageWithTildePathAndAppPathResolvesCorrectly()
+ {
+ HtmlHelper html = MvcHelper.GetHtmlHelperWithPath(new ViewDataDictionary(), "/app");
+ MvcHtmlString imageResult = html.Image("~/system/web/mvc.jpg");
+ Assert.Equal("<img src=\"/$(SESSION)/app/system/web/mvc.jpg\" />", imageResult.ToHtmlString());
+ }
+
+ [Fact]
+ public void ImageWithTildePathWithoutAppPathResolvesCorrectly()
+ {
+ HtmlHelper html = MvcHelper.GetHtmlHelperWithPath(new ViewDataDictionary(), "/");
+ MvcHtmlString imageResult = html.Image("~/system/web/mvc.jpg");
+ Assert.Equal("<img src=\"/$(SESSION)/system/web/mvc.jpg\" />", imageResult.ToHtmlString());
+ }
+ }
+}
diff --git a/test/Microsoft.Web.Mvc.Test/Test/MailToExtensionsTest.cs b/test/Microsoft.Web.Mvc.Test/Test/MailToExtensionsTest.cs
new file mode 100644
index 00000000..eb35f84e
--- /dev/null
+++ b/test/Microsoft.Web.Mvc.Test/Test/MailToExtensionsTest.cs
@@ -0,0 +1,132 @@
+using System.Web.Mvc;
+using System.Web.Routing;
+using Microsoft.Web.UnitTestUtil;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace Microsoft.Web.Mvc.Test
+{
+ public class MailToExtensionsTest
+ {
+ [Fact]
+ public void MailToWithoutEmailThrowsArgumentNullException()
+ {
+ HtmlHelper html = MvcHelper.GetHtmlHelperWithPath(new ViewDataDictionary());
+ Assert.ThrowsArgumentNull(() => html.Mailto("link text", null), "emailAddress");
+ }
+
+ [Fact]
+ public void MailToWithoutLinkTextThrowsArgumentNullException()
+ {
+ HtmlHelper html = MvcHelper.GetHtmlHelperWithPath(new ViewDataDictionary());
+ Assert.ThrowsArgumentNull(() => html.Mailto(null, "somebody@example.com"), "linkText");
+ }
+
+ [Fact]
+ public void MailToWithLinkTextAndEmailRendersProperElement()
+ {
+ HtmlHelper html = MvcHelper.GetHtmlHelperWithPath(new ViewDataDictionary());
+ MvcHtmlString result = html.Mailto("This is a test", "test@example.com");
+ Assert.Equal("<a href=\"mailto:test@example.com\">This is a test</a>", result.ToHtmlString());
+ }
+
+ [Fact]
+ public void MailToWithLinkTextEmailAndHtmlAttributesRendersAttributes()
+ {
+ HtmlHelper html = MvcHelper.GetHtmlHelperWithPath(new ViewDataDictionary());
+ MvcHtmlString result = html.Mailto("This is a test", "test@example.com", new { title = "this is a test" });
+ Assert.Equal("<a href=\"mailto:test@example.com\" title=\"this is a test\">This is a test</a>", result.ToHtmlString());
+ }
+
+ [Fact]
+ public void MailToWithLinkTextEmailAndHtmlAttributesDictionaryRendersAttributes()
+ {
+ HtmlHelper html = MvcHelper.GetHtmlHelperWithPath(new ViewDataDictionary());
+ MvcHtmlString result = html.Mailto("This is a test", "test@example.com", new RouteValueDictionary(new { title = "this is a test" }));
+ Assert.Equal("<a href=\"mailto:test@example.com\" title=\"this is a test\">This is a test</a>", result.ToHtmlString());
+ }
+
+ [Fact]
+ public void MailToWithSubjectAndHtmlAttributesRendersAttributes()
+ {
+ HtmlHelper html = MvcHelper.GetHtmlHelperWithPath(new ViewDataDictionary());
+ MvcHtmlString result = html.Mailto("This is a test", "test@example.com", "The subject", new { title = "this is a test" });
+ Assert.Equal("<a href=\"mailto:test@example.com?subject=The subject\" title=\"this is a test\">This is a test</a>", result.ToHtmlString());
+ }
+
+ [Fact]
+ public void MailToWithSubjectAndHtmlAttributesDictionaryRendersAttributes()
+ {
+ HtmlHelper html = MvcHelper.GetHtmlHelperWithPath(new ViewDataDictionary());
+ MvcHtmlString result = html.Mailto("This is a test", "test@example.com", "The subject", new RouteValueDictionary(new { title = "this is a test" }));
+ Assert.Equal("<a href=\"mailto:test@example.com?subject=The subject\" title=\"this is a test\">This is a test</a>", result.ToHtmlString());
+ }
+
+ [Fact]
+ public void MailToAttributeEncodesEmail()
+ {
+ HtmlHelper html = MvcHelper.GetHtmlHelperWithPath(new ViewDataDictionary());
+ MvcHtmlString result = html.Mailto("This is a test", "te\">st@example.com");
+ Assert.Equal("<a href=\"mailto:te&quot;>st@example.com\">This is a test</a>", result.ToHtmlString());
+ }
+
+ [Fact]
+ public void MailToWithMultipleRecipientsRendersWithCommas()
+ {
+ HtmlHelper html = MvcHelper.GetHtmlHelperWithPath(new ViewDataDictionary());
+ MvcHtmlString result = html.Mailto("This is a test", "te\">st@example.com,test2@example.com");
+ Assert.Equal("<a href=\"mailto:te&quot;>st@example.com,test2@example.com\">This is a test</a>", result.ToHtmlString());
+ }
+
+ [Fact]
+ public void MailToWithSubjectAppendsSubjectQuery()
+ {
+ HtmlHelper html = MvcHelper.GetHtmlHelperWithPath(new ViewDataDictionary());
+ MvcHtmlString result = html.Mailto("This is a test", "test@example.com", "This is the subject");
+ Assert.Equal("<a href=\"mailto:test@example.com?subject=This is the subject\">This is a test</a>", result.ToHtmlString());
+ }
+
+ [Fact]
+ public void MailToWithCopyOnlyAppendsCopyQuery()
+ {
+ HtmlHelper html = MvcHelper.GetHtmlHelperWithPath(new ViewDataDictionary());
+ MvcHtmlString result = html.Mailto("This is a test", "test@example.com", null, null, "cctest@example.com", null, null);
+ Assert.Equal("<a href=\"mailto:test@example.com?cc=cctest@example.com\">This is a test</a>", result.ToHtmlString());
+ }
+
+ [Fact]
+ public void MailToWithMultipartBodyRendersProperMailtoEncoding()
+ {
+ HtmlHelper html = MvcHelper.GetHtmlHelperWithPath(new ViewDataDictionary());
+ string body = @"Line one
+Line two
+Line three";
+ MvcHtmlString result = html.Mailto("email me", "test@example.com", null, body, null, null, null);
+ Assert.Equal("<a href=\"mailto:test@example.com?body=Line one%0ALine two%0ALine three\">email me</a>", result.ToHtmlString());
+ }
+
+ [Fact]
+ public void MailToWithAllValuesProvidedRendersCorrectTag()
+ {
+ HtmlHelper html = MvcHelper.GetHtmlHelperWithPath(new ViewDataDictionary());
+ string body = @"Line one
+Line two
+Line three";
+ MvcHtmlString result = html.Mailto("email me", "test@example.com", "the subject", body, "cc@example.com", "bcc@example.com", new { title = "email test" });
+ string expected = @"<a href=""mailto:test@example.com?subject=the subject&amp;cc=cc@example.com&amp;bcc=bcc@example.com&amp;body=Line one%0ALine two%0ALine three"" title=""email test"">email me</a>";
+ Assert.Equal(expected, result.ToHtmlString());
+ }
+
+ [Fact]
+ public void MailToWithAttributesWithUnderscores()
+ {
+ HtmlHelper html = MvcHelper.GetHtmlHelperWithPath(new ViewDataDictionary());
+ string body = @"Line one
+Line two
+Line three";
+ MvcHtmlString result = html.Mailto("email me", "test@example.com", "the subject", body, "cc@example.com", "bcc@example.com", new { foo_bar = "baz" });
+ string expected = @"<a foo-bar=""baz"" href=""mailto:test@example.com?subject=the subject&amp;cc=cc@example.com&amp;bcc=bcc@example.com&amp;body=Line one%0ALine two%0ALine three"">email me</a>";
+ Assert.Equal(expected, result.ToHtmlString());
+ }
+ }
+}
diff --git a/test/Microsoft.Web.Mvc.Test/Test/ModelCopierTest.cs b/test/Microsoft.Web.Mvc.Test/Test/ModelCopierTest.cs
new file mode 100644
index 00000000..6f250090
--- /dev/null
+++ b/test/Microsoft.Web.Mvc.Test/Test/ModelCopierTest.cs
@@ -0,0 +1,223 @@
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Linq;
+using Xunit;
+
+namespace Microsoft.Web.Mvc.Test
+{
+ public class ModelCopierTest
+ {
+ [Fact]
+ public void CopyCollection_FromIsNull_DoesNothing()
+ {
+ // Arrange
+ int[] from = null;
+ List<int> to = new List<int> { 1, 2, 3 };
+
+ // Act
+ ModelCopier.CopyCollection(from, to);
+
+ // Assert
+ Assert.Equal(new[] { 1, 2, 3 }, to.ToArray());
+ }
+
+ [Fact]
+ public void CopyCollection_ToIsImmutable_DoesNothing()
+ {
+ // Arrange
+ List<int> from = new List<int> { 1, 2, 3 };
+ ICollection<int> to = new ReadOnlyCollection<int>(new[] { 4, 5, 6 });
+
+ // Act
+ ModelCopier.CopyCollection(from, to);
+
+ // Assert
+ Assert.Equal(new[] { 1, 2, 3 }, from.ToArray());
+ Assert.Equal(new[] { 4, 5, 6 }, to.ToArray());
+ }
+
+ [Fact]
+ public void CopyCollection_ToIsMmutable_ClearsAndCopies()
+ {
+ // Arrange
+ List<int> from = new List<int> { 1, 2, 3 };
+ ICollection<int> to = new List<int> { 4, 5, 6 };
+
+ // Act
+ ModelCopier.CopyCollection(from, to);
+
+ // Assert
+ Assert.Equal(new[] { 1, 2, 3 }, from.ToArray());
+ Assert.Equal(new[] { 1, 2, 3 }, to.ToArray());
+ }
+
+ [Fact]
+ public void CopyCollection_ToIsNull_DoesNothing()
+ {
+ // Arrange
+ List<int> from = new List<int> { 1, 2, 3 };
+ List<int> to = null;
+
+ // Act
+ ModelCopier.CopyCollection(from, to);
+
+ // Assert
+ Assert.Equal(new[] { 1, 2, 3 }, from.ToArray());
+ }
+
+ [Fact]
+ public void CopyModel_ExactTypeMatch_Copies()
+ {
+ // Arrange
+ GenericModel<int> from = new GenericModel<int> { TheProperty = 21 };
+ GenericModel<int> to = new GenericModel<int> { TheProperty = 42 };
+
+ // Act
+ ModelCopier.CopyModel(from, to);
+
+ // Assert
+ Assert.Equal(21, from.TheProperty);
+ Assert.Equal(21, to.TheProperty);
+ }
+
+ [Fact]
+ public void CopyModel_FromIsNull_DoesNothing()
+ {
+ // Arrange
+ GenericModel<int> from = null;
+ GenericModel<int> to = new GenericModel<int> { TheProperty = 42 };
+
+ // Act
+ ModelCopier.CopyModel(from, to);
+
+ // Assert
+ Assert.Equal(42, to.TheProperty);
+ }
+
+ [Fact]
+ public void CopyModel_LiftedTypeMatch_ActualValueIsNotNull_Copies()
+ {
+ // Arrange
+ GenericModel<int?> from = new GenericModel<int?> { TheProperty = 21 };
+ GenericModel<int> to = new GenericModel<int> { TheProperty = 42 };
+
+ // Act
+ ModelCopier.CopyModel(from, to);
+
+ // Assert
+ Assert.Equal(21, from.TheProperty);
+ Assert.Equal(21, to.TheProperty);
+ }
+
+ [Fact]
+ public void CopyModel_LiftedTypeMatch_ActualValueIsNull_DoesNothing()
+ {
+ // Arrange
+ GenericModel<int?> from = new GenericModel<int?> { TheProperty = null };
+ GenericModel<int> to = new GenericModel<int> { TheProperty = 42 };
+
+ // Act
+ ModelCopier.CopyModel(from, to);
+
+ // Assert
+ Assert.Null(from.TheProperty);
+ Assert.Equal(42, to.TheProperty);
+ }
+
+ [Fact]
+ public void CopyModel_NoTypeMatch_DoesNothing()
+ {
+ // Arrange
+ GenericModel<int> from = new GenericModel<int> { TheProperty = 21 };
+ GenericModel<long> to = new GenericModel<long> { TheProperty = 42 };
+
+ // Act
+ ModelCopier.CopyModel(from, to);
+
+ // Assert
+ Assert.Equal(21, from.TheProperty);
+ Assert.Equal(42, to.TheProperty);
+ }
+
+ [Fact]
+ public void CopyModel_SubclassedTypeMatch_Copies()
+ {
+ // Arrange
+ string originalModel = "Hello, world!";
+
+ GenericModel<string> from = new GenericModel<string> { TheProperty = originalModel };
+ GenericModel<object> to = new GenericModel<object> { TheProperty = 42 };
+
+ // Act
+ ModelCopier.CopyModel(from, to);
+
+ // Assert
+ Assert.Same(originalModel, from.TheProperty);
+ Assert.Same(originalModel, to.TheProperty);
+ }
+
+ [Fact]
+ public void CopyModel_ToDoesNotContainProperty_DoesNothing()
+ {
+ // Arrange
+ GenericModel<int> from = new GenericModel<int> { TheProperty = 21 };
+ OtherGenericModel<int> to = new OtherGenericModel<int> { SomeOtherProperty = 42 };
+
+ // Act
+ ModelCopier.CopyModel(from, to);
+
+ // Assert
+ Assert.Equal(21, from.TheProperty);
+ Assert.Equal(42, to.SomeOtherProperty);
+ }
+
+ [Fact]
+ public void CopyModel_ToIsNull_DoesNothing()
+ {
+ // Arrange
+ GenericModel<int> from = new GenericModel<int> { TheProperty = 21 };
+ GenericModel<int> to = null;
+
+ // Act
+ ModelCopier.CopyModel(from, to);
+
+ // Assert
+ Assert.Equal(21, from.TheProperty);
+ }
+
+ [Fact]
+ public void CopyModel_ToIsReadOnly_DoesNothing()
+ {
+ // Arrange
+ GenericModel<int> from = new GenericModel<int> { TheProperty = 21 };
+ ReadOnlyGenericModel<int> to = new ReadOnlyGenericModel<int>(42);
+
+ // Act
+ ModelCopier.CopyModel(from, to);
+
+ // Assert
+ Assert.Equal(21, from.TheProperty);
+ Assert.Equal(42, to.TheProperty);
+ }
+
+ private class GenericModel<T>
+ {
+ public T TheProperty { get; set; }
+ }
+
+ private class OtherGenericModel<T>
+ {
+ public T SomeOtherProperty { get; set; }
+ }
+
+ private class ReadOnlyGenericModel<T>
+ {
+ public ReadOnlyGenericModel(T propertyValue)
+ {
+ TheProperty = propertyValue;
+ }
+
+ public T TheProperty { get; private set; }
+ }
+ }
+}
diff --git a/test/Microsoft.Web.Mvc.Test/Test/MvcSerializerTest.cs b/test/Microsoft.Web.Mvc.Test/Test/MvcSerializerTest.cs
new file mode 100644
index 00000000..b51bb344
--- /dev/null
+++ b/test/Microsoft.Web.Mvc.Test/Test/MvcSerializerTest.cs
@@ -0,0 +1,122 @@
+using System;
+using System.Runtime.Serialization;
+using System.Web.Security;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace Microsoft.Web.Mvc.Test
+{
+ public class MvcSerializerTest
+ {
+ [Fact]
+ public void DeserializeThrowsIfModeIsOutOfRange()
+ {
+ // Arrange
+ MvcSerializer serializer = new MvcSerializer();
+
+ // Act & assert
+ Assert.ThrowsArgumentOutOfRange(
+ delegate { serializer.Serialize("blah", (SerializationMode)(-1)); },
+ "mode",
+ @"The provided SerializationMode is invalid.");
+ }
+
+ [Fact]
+ public void DeserializeThrowsIfSerializedValueIsCorrupt()
+ {
+ // Arrange
+ IMachineKey machineKey = new MockMachineKey();
+
+ // Act & assert
+ Exception exception = Assert.Throws<SerializationException>(
+ delegate { MvcSerializer.Deserialize("This is a corrupted value.", SerializationMode.Signed, machineKey); },
+ @"Deserialization failed. Verify that the data is being deserialized using the same SerializationMode with which it was serialized. Otherwise see the inner exception.");
+
+ Assert.NotNull(exception.InnerException);
+ }
+
+ [Fact]
+ public void DeserializeThrowsIfSerializedValueIsEmpty()
+ {
+ // Arrange
+ MvcSerializer serializer = new MvcSerializer();
+
+ // Act & assert
+ Assert.ThrowsArgumentNullOrEmpty(
+ delegate { serializer.Deserialize("", SerializationMode.Signed); }, "serializedValue");
+ }
+
+ [Fact]
+ public void DeserializeThrowsIfSerializedValueIsNull()
+ {
+ // Arrange
+ MvcSerializer serializer = new MvcSerializer();
+
+ // Act & assert
+ Assert.ThrowsArgumentNullOrEmpty(
+ delegate { serializer.Deserialize(null, SerializationMode.Signed); }, "serializedValue");
+ }
+
+ [Fact]
+ public void SerializeAllowsNullValues()
+ {
+ // Arrange
+ IMachineKey machineKey = new MockMachineKey();
+
+ // Act
+ string serializedValue = MvcSerializer.Serialize(null, SerializationMode.EncryptedAndSigned, machineKey);
+
+ // Assert
+ Assert.Equal(@"All-LPgGI1dzEbp3B2FueVR5cGUuA25pbIYJAXozaHR0cDovL3NjaGVtYXMubWljcm9zb2Z0LmNvbS8yMDAzLzEwL1NlcmlhbGl6YXRpb24vCQFpKWh0dHA6Ly93d3cudzMub3JnLzIwMDEvWE1MU2NoZW1hLWluc3RhbmNlAQ==", serializedValue);
+ }
+
+ [Fact]
+ public void SerializeAndDeserializeRoundTripsValue()
+ {
+ // Arrange
+ IMachineKey machineKey = new MockMachineKey();
+
+ // Act
+ string serializedValue = MvcSerializer.Serialize(42, SerializationMode.EncryptedAndSigned, machineKey);
+ object deserializedValue = MvcSerializer.Deserialize(serializedValue, SerializationMode.EncryptedAndSigned, machineKey);
+
+ // Assert
+ Assert.Equal(42, deserializedValue);
+ }
+
+ [Fact]
+ public void SerializeThrowsIfModeIsOutOfRange()
+ {
+ // Arrange
+ MvcSerializer serializer = new MvcSerializer();
+
+ // Act & assert
+ Assert.ThrowsArgumentOutOfRange(
+ delegate { serializer.Serialize(null, (SerializationMode)(-1)); },
+ "mode",
+ @"The provided SerializationMode is invalid.");
+ }
+
+ private sealed class MockMachineKey : IMachineKey
+ {
+ public byte[] Decode(string encodedData, MachineKeyProtection protectionOption)
+ {
+ string optionString = protectionOption.ToString();
+ if (encodedData.StartsWith(optionString, StringComparison.Ordinal))
+ {
+ encodedData = encodedData.Substring(optionString.Length + 1);
+ }
+ else
+ {
+ throw new Exception("Corrupted data.");
+ }
+ return Convert.FromBase64String(encodedData);
+ }
+
+ public string Encode(byte[] data, MachineKeyProtection protectionOption)
+ {
+ return protectionOption.ToString() + "-" + Convert.ToBase64String(data);
+ }
+ }
+ }
+}
diff --git a/test/Microsoft.Web.Mvc.Test/Test/RadioExtensionsTest.cs b/test/Microsoft.Web.Mvc.Test/Test/RadioExtensionsTest.cs
new file mode 100644
index 00000000..699e8708
--- /dev/null
+++ b/test/Microsoft.Web.Mvc.Test/Test/RadioExtensionsTest.cs
@@ -0,0 +1,199 @@
+using System.Collections.Generic;
+using System.Web.Mvc;
+using System.Web.Routing;
+using Microsoft.Web.UnitTestUtil;
+using Xunit;
+
+namespace Microsoft.Web.Mvc.Test
+{
+ public class RadioExtensionsTest
+ {
+ [Fact]
+ public void RadioButtonListNothingSelected()
+ {
+ // Arrange
+ HtmlHelper htmlHelper = MvcHelper.GetHtmlHelper(new ViewDataDictionary());
+
+ // Act
+ MvcHtmlString[] html = htmlHelper.RadioButtonList("FooList", GetRadioButtonListData(false));
+
+ // Assert
+ Assert.Equal(@"<input id=""FooList"" name=""FooList"" type=""radio"" value=""foo"" />", html[0].ToHtmlString());
+ Assert.Equal(@"<input id=""FooList"" name=""FooList"" type=""radio"" value=""bar"" />", html[1].ToHtmlString());
+ Assert.Equal(@"<input id=""FooList"" name=""FooList"" type=""radio"" value=""baz"" />", html[2].ToHtmlString());
+ }
+
+ [Fact]
+ public void RadioButtonListItemSelected()
+ {
+ // Arrange
+ HtmlHelper htmlHelper = MvcHelper.GetHtmlHelper(new ViewDataDictionary());
+
+ // Act
+ MvcHtmlString[] html = htmlHelper.RadioButtonList("FooList", GetRadioButtonListData(true));
+
+ // Assert
+ Assert.Equal(@"<input id=""FooList"" name=""FooList"" type=""radio"" value=""foo"" />", html[0].ToHtmlString());
+ Assert.Equal(@"<input id=""FooList"" name=""FooList"" type=""radio"" value=""bar"" />", html[1].ToHtmlString());
+ Assert.Equal(@"<input checked=""checked"" id=""FooList"" name=""FooList"" type=""radio"" value=""baz"" />", html[2].ToHtmlString());
+ }
+
+ [Fact]
+ public void RadioButtonListItemSelectedWithValueFromViewData()
+ {
+ // Arrange
+ HtmlHelper htmlHelper = MvcHelper.GetHtmlHelper(new ViewDataDictionary(new { foolist = "bar" }));
+
+ // Act
+ MvcHtmlString[] html = htmlHelper.RadioButtonList("FooList", GetRadioButtonListData(false));
+
+ // Assert
+ Assert.Equal(@"<input id=""FooList"" name=""FooList"" type=""radio"" value=""foo"" />", html[0].ToHtmlString());
+ Assert.Equal(@"<input checked=""checked"" id=""FooList"" name=""FooList"" type=""radio"" value=""bar"" />", html[1].ToHtmlString());
+ Assert.Equal(@"<input id=""FooList"" name=""FooList"" type=""radio"" value=""baz"" />", html[2].ToHtmlString());
+ }
+
+ [Fact]
+ public void RadioButtonListWithObjectAttributes()
+ {
+ // Arrange
+ HtmlHelper htmlHelper = MvcHelper.GetHtmlHelper(new ViewDataDictionary());
+
+ // Act
+ MvcHtmlString[] html = htmlHelper.RadioButtonList("FooList", GetRadioButtonListData(true), new { attr1 = "value1" });
+
+ // Assert
+ Assert.Equal(@"<input attr1=""value1"" id=""FooList"" name=""FooList"" type=""radio"" value=""foo"" />", html[0].ToHtmlString());
+ Assert.Equal(@"<input attr1=""value1"" id=""FooList"" name=""FooList"" type=""radio"" value=""bar"" />", html[1].ToHtmlString());
+ Assert.Equal(@"<input attr1=""value1"" checked=""checked"" id=""FooList"" name=""FooList"" type=""radio"" value=""baz"" />", html[2].ToHtmlString());
+ }
+
+ [Fact]
+ public void RadioButtonListWithObjectAttributesWithUnderscores()
+ {
+ // Arrange
+ HtmlHelper htmlHelper = MvcHelper.GetHtmlHelper(new ViewDataDictionary());
+
+ // Act
+ MvcHtmlString[] html = htmlHelper.RadioButtonList("FooList", GetRadioButtonListData(true), new { foo_bar = "baz" });
+
+ // Assert
+ Assert.Equal(@"<input foo-bar=""baz"" id=""FooList"" name=""FooList"" type=""radio"" value=""foo"" />", html[0].ToHtmlString());
+ Assert.Equal(@"<input foo-bar=""baz"" id=""FooList"" name=""FooList"" type=""radio"" value=""bar"" />", html[1].ToHtmlString());
+ Assert.Equal(@"<input checked=""checked"" foo-bar=""baz"" id=""FooList"" name=""FooList"" type=""radio"" value=""baz"" />", html[2].ToHtmlString());
+ }
+
+ [Fact]
+ public void RadioButtonListWithDictionaryAttributes()
+ {
+ // Arrange
+ HtmlHelper htmlHelper = MvcHelper.GetHtmlHelper(new ViewDataDictionary());
+
+ // Act
+ MvcHtmlString[] html = htmlHelper.RadioButtonList("FooList", GetRadioButtonListData(true), new RouteValueDictionary(new { attr1 = "value1" }));
+
+ // Assert
+ Assert.Equal(@"<input attr1=""value1"" id=""FooList"" name=""FooList"" type=""radio"" value=""foo"" />", html[0].ToHtmlString());
+ Assert.Equal(@"<input attr1=""value1"" id=""FooList"" name=""FooList"" type=""radio"" value=""bar"" />", html[1].ToHtmlString());
+ Assert.Equal(@"<input attr1=""value1"" checked=""checked"" id=""FooList"" name=""FooList"" type=""radio"" value=""baz"" />", html[2].ToHtmlString());
+ }
+
+ [Fact]
+ public void RadioButtonListNothingSelectedWithSelectListFromViewData()
+ {
+ // Arrange
+ HtmlHelper htmlHelper = MvcHelper.GetHtmlHelper(GetRadioButtonListViewData(false));
+
+ // Act
+ MvcHtmlString[] html = htmlHelper.RadioButtonList("FooList");
+
+ // Assert
+ Assert.Equal(@"<input id=""FooList"" name=""FooList"" type=""radio"" value=""foo"" />", html[0].ToHtmlString());
+ Assert.Equal(@"<input id=""FooList"" name=""FooList"" type=""radio"" value=""bar"" />", html[1].ToHtmlString());
+ Assert.Equal(@"<input id=""FooList"" name=""FooList"" type=""radio"" value=""baz"" />", html[2].ToHtmlString());
+ }
+
+ [Fact]
+ public void RadioButtonListItemSelectedWithSelectListFromViewData()
+ {
+ // Arrange
+ HtmlHelper htmlHelper = MvcHelper.GetHtmlHelper(GetRadioButtonListViewData(true));
+
+ // Act
+ MvcHtmlString[] html = htmlHelper.RadioButtonList("FooList");
+
+ // Assert
+ Assert.Equal(@"<input id=""FooList"" name=""FooList"" type=""radio"" value=""foo"" />", html[0].ToHtmlString());
+ Assert.Equal(@"<input id=""FooList"" name=""FooList"" type=""radio"" value=""bar"" />", html[1].ToHtmlString());
+ Assert.Equal(@"<input checked=""checked"" id=""FooList"" name=""FooList"" type=""radio"" value=""baz"" />", html[2].ToHtmlString());
+ }
+
+ [Fact]
+ public void RadioButtonListWithObjectAttributesWithSelectListFromViewData()
+ {
+ // Arrange
+ HtmlHelper htmlHelper = MvcHelper.GetHtmlHelper(GetRadioButtonListViewData(true));
+
+ // Act
+ MvcHtmlString[] html = htmlHelper.RadioButtonList("FooList", new { attr1 = "value1" });
+
+ // Assert
+ Assert.Equal(@"<input attr1=""value1"" id=""FooList"" name=""FooList"" type=""radio"" value=""foo"" />", html[0].ToHtmlString());
+ Assert.Equal(@"<input attr1=""value1"" id=""FooList"" name=""FooList"" type=""radio"" value=""bar"" />", html[1].ToHtmlString());
+ Assert.Equal(@"<input attr1=""value1"" checked=""checked"" id=""FooList"" name=""FooList"" type=""radio"" value=""baz"" />", html[2].ToHtmlString());
+ }
+
+ [Fact]
+ public void RadioButtonListWithObjectAttributesWithUnderscoresWithSelectListFromViewData()
+ {
+ // Arrange
+ HtmlHelper htmlHelper = MvcHelper.GetHtmlHelper(GetRadioButtonListViewData(true));
+
+ // Act
+ MvcHtmlString[] html = htmlHelper.RadioButtonList("FooList", new { foo_bar = "baz" });
+
+ // Assert
+ Assert.Equal(@"<input foo-bar=""baz"" id=""FooList"" name=""FooList"" type=""radio"" value=""foo"" />", html[0].ToHtmlString());
+ Assert.Equal(@"<input foo-bar=""baz"" id=""FooList"" name=""FooList"" type=""radio"" value=""bar"" />", html[1].ToHtmlString());
+ Assert.Equal(@"<input checked=""checked"" foo-bar=""baz"" id=""FooList"" name=""FooList"" type=""radio"" value=""baz"" />", html[2].ToHtmlString());
+ }
+
+ [Fact]
+ public void RadioButtonListWithDictionaryAttributesWithSelectListFromViewData()
+ {
+ // Arrange
+ HtmlHelper htmlHelper = MvcHelper.GetHtmlHelper(GetRadioButtonListViewData(true));
+
+ // Act
+ MvcHtmlString[] html = htmlHelper.RadioButtonList("FooList", new RouteValueDictionary(new { attr1 = "value1" }));
+
+ // Assert
+ Assert.Equal(@"<input attr1=""value1"" id=""FooList"" name=""FooList"" type=""radio"" value=""foo"" />", html[0].ToHtmlString());
+ Assert.Equal(@"<input attr1=""value1"" id=""FooList"" name=""FooList"" type=""radio"" value=""bar"" />", html[1].ToHtmlString());
+ Assert.Equal(@"<input attr1=""value1"" checked=""checked"" id=""FooList"" name=""FooList"" type=""radio"" value=""baz"" />", html[2].ToHtmlString());
+ }
+
+ private static SelectList GetRadioButtonListData(bool selectBaz)
+ {
+ List<RadioItem> list = new List<RadioItem>();
+ list.Add(new RadioItem { Text = "text-foo", Value = "foo" });
+ list.Add(new RadioItem { Text = "text-bar", Value = "bar" });
+ list.Add(new RadioItem { Text = "text-baz", Value = "baz" });
+ return new SelectList(list, "value", "TEXT", selectBaz ? "baz" : "something-else");
+ }
+
+ private static ViewDataDictionary GetRadioButtonListViewData(bool selectBaz)
+ {
+ ViewDataDictionary viewData = new ViewDataDictionary();
+ viewData["FooList"] = GetRadioButtonListData(selectBaz);
+ return viewData;
+ }
+
+ private class RadioItem
+ {
+ public string Text { get; set; }
+
+ public string Value { get; set; }
+ }
+ }
+}
diff --git a/test/Microsoft.Web.Mvc.Test/Test/ReaderWriterCacheTest.cs b/test/Microsoft.Web.Mvc.Test/Test/ReaderWriterCacheTest.cs
new file mode 100644
index 00000000..360f4874
--- /dev/null
+++ b/test/Microsoft.Web.Mvc.Test/Test/ReaderWriterCacheTest.cs
@@ -0,0 +1,77 @@
+using System;
+using System.Collections.Generic;
+using Xunit;
+
+namespace Microsoft.Web.Mvc.Test
+{
+ public class ReaderWriterCacheTest
+ {
+ [Fact]
+ public void PublicFetchOrCreateItemCreatesItemIfNotAlreadyInCache()
+ {
+ // Arrange
+ ReaderWriterCacheHelper<int, string> helper = new ReaderWriterCacheHelper<int, string>();
+ Dictionary<int, string> cache = helper.PublicCache;
+
+ // Act
+ string item = helper.PublicFetchOrCreateItem(42, () => "new");
+
+ // Assert
+ Assert.Equal("new", cache[42]);
+ Assert.Equal("new", item);
+ }
+
+ [Fact]
+ public void PublicFetchOrCreateItemReturnsExistingItemIfFound()
+ {
+ // Arrange
+ ReaderWriterCacheHelper<int, string> helper = new ReaderWriterCacheHelper<int, string>();
+ Dictionary<int, string> cache = helper.PublicCache;
+ helper.PublicCache[42] = "original";
+
+ // Act
+ string item = helper.PublicFetchOrCreateItem(42, () => "new");
+
+ // Assert
+ Assert.Equal("original", cache[42]);
+ Assert.Equal("original", item);
+ }
+
+ [Fact]
+ public void PublicFetchOrCreateItemReturnsFirstItemIfTwoThreadsUpdateCacheSimultaneously()
+ {
+ // Arrange
+ ReaderWriterCacheHelper<int, string> helper = new ReaderWriterCacheHelper<int, string>();
+ Dictionary<int, string> cache = helper.PublicCache;
+ Func<string> creator = delegate
+ {
+ // fake a second thread coming along when we weren't looking
+ string firstItem = helper.PublicFetchOrCreateItem(42, () => "original");
+
+ Assert.Equal("original", cache[42]);
+ Assert.Equal("original", firstItem);
+ return "new";
+ };
+
+ // Act
+ string secondItem = helper.PublicFetchOrCreateItem(42, creator);
+
+ // Assert
+ Assert.Equal("original", cache[42]);
+ Assert.Equal("original", secondItem);
+ }
+
+ private class ReaderWriterCacheHelper<TKey, TValue> : ReaderWriterCache<TKey, TValue>
+ {
+ public Dictionary<TKey, TValue> PublicCache
+ {
+ get { return Cache; }
+ }
+
+ public TValue PublicFetchOrCreateItem(TKey key, Func<TValue> creator)
+ {
+ return FetchOrCreateItem(key, creator);
+ }
+ }
+ }
+}
diff --git a/test/Microsoft.Web.Mvc.Test/Test/RenderActionTest.cs b/test/Microsoft.Web.Mvc.Test/Test/RenderActionTest.cs
new file mode 100644
index 00000000..f6fdb85f
--- /dev/null
+++ b/test/Microsoft.Web.Mvc.Test/Test/RenderActionTest.cs
@@ -0,0 +1,115 @@
+using System;
+using System.IO;
+using System.Reflection;
+using System.Web;
+using System.Web.Mvc;
+using System.Web.Routing;
+using Microsoft.Web.UnitTestUtil;
+using Moq;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace Microsoft.Web.Mvc.Test
+{
+ public class RenderActionTest
+ {
+ [Fact]
+ public void RenderActionUsingExpressionWithParametersInViewContextRendersCorrectly()
+ {
+ // Arrange
+ Func<RequestContext> requestContextAccessor;
+ HtmlHelper html = GetHtmlHelper(out requestContextAccessor);
+ html.ViewContext.RouteData.Values.Add("stuff", "42");
+
+ // Act
+ html.RenderAction<TestController>(c => c.Stuff());
+ RequestContext requestContext = requestContextAccessor();
+
+ // Assert
+ Assert.NotNull(requestContext);
+ Assert.Equal("Test", requestContext.RouteData.Values["controller"]);
+ Assert.Equal("Stuff", requestContext.RouteData.Values["action"]);
+ Assert.Equal("42", requestContext.RouteData.Values["stuff"]);
+ }
+
+ [Fact]
+ public void RenderActionUsingExpressionRendersCorrectly()
+ {
+ // Arrange
+ Func<RequestContext> requestContextAccessor;
+ HtmlHelper html = GetHtmlHelper(out requestContextAccessor);
+
+ // Act
+ html.RenderAction<TestController>(c => c.About(76));
+ RequestContext requestContext = requestContextAccessor();
+
+ // Assert
+ Assert.NotNull(requestContext);
+ Assert.Equal("Test", requestContext.RouteData.Values["controller"]);
+ Assert.Equal("About", requestContext.RouteData.Values["action"]);
+ Assert.Equal(76, requestContext.RouteData.Values["page"]);
+ }
+
+ [Fact]
+ public void RenderRouteWithNullRouteValueDictionaryThrowsException()
+ {
+ HtmlHelper html = MvcHelper.GetHtmlHelperWithPath(new ViewDataDictionary(), "/");
+ Assert.ThrowsArgumentNull(() => html.RenderRoute(null), "routeValues");
+ }
+
+ [Fact]
+ public void RenderRouteWithActionAndControllerSpecifiedRendersCorrectAction()
+ {
+ // Arrange
+ Func<RequestContext> requestContextAccessor;
+ HtmlHelper html = GetHtmlHelper(out requestContextAccessor);
+
+ // Act
+ html.RenderRoute(new RouteValueDictionary(new { action = "Index", controller = "Test" }));
+ RequestContext requestContext = requestContextAccessor();
+
+ // Assert
+ Assert.NotNull(requestContext);
+ Assert.Equal("Test", requestContext.RouteData.Values["controller"]);
+ Assert.Equal("Index", requestContext.RouteData.Values["action"]);
+ }
+
+ private static HtmlHelper GetHtmlHelper(out Func<RequestContext> requestContextAccessor)
+ {
+ RequestContext requestContext = null;
+
+ HtmlHelper html = MvcHelper.GetHtmlHelperWithPath(new ViewDataDictionary(), "/");
+
+ html.RouteCollection.MapRoute(null, "{*dummy}");
+ Mock.Get(html.ViewContext.HttpContext)
+ .Setup(o => o.Server.Execute(It.IsAny<IHttpHandler>(), It.IsAny<TextWriter>(), It.IsAny<bool>()))
+ .Callback<IHttpHandler, TextWriter, bool>((_h, _w, _pf) =>
+ {
+ MvcHandler mvcHandler = _h.GetType().GetProperty("InnerHandler", BindingFlags.Instance | BindingFlags.NonPublic).GetValue(_h, null) as MvcHandler;
+ requestContext = mvcHandler.RequestContext;
+ });
+
+ requestContextAccessor = () => requestContext;
+ return html;
+ }
+
+ public class TestController : Controller
+ {
+ public string Index()
+ {
+ return "It Worked!";
+ }
+
+ public string About(int page)
+ {
+ return "This is page #" + page;
+ }
+
+ public string Stuff()
+ {
+ string stuff = ControllerContext.RouteData.Values["stuff"] as string;
+ return "Argument was " + stuff;
+ }
+ }
+ }
+}
diff --git a/test/Microsoft.Web.Mvc.Test/Test/ScriptExtensionsTest.cs b/test/Microsoft.Web.Mvc.Test/Test/ScriptExtensionsTest.cs
new file mode 100644
index 00000000..8667970a
--- /dev/null
+++ b/test/Microsoft.Web.Mvc.Test/Test/ScriptExtensionsTest.cs
@@ -0,0 +1,123 @@
+using System.Web.Mvc;
+using Microsoft.Web.UnitTestUtil;
+using Moq;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace Microsoft.Web.Mvc.Test
+{
+ public class ScriptExtensionsTest
+ {
+ [Fact]
+ public void ScriptWithoutReleaseFileThrowsArgumentNullException()
+ {
+ // Arrange
+ HtmlHelper html = MvcHelper.GetHtmlHelperWithPath(new ViewDataDictionary());
+
+ // Assert
+ Assert.ThrowsArgumentNullOrEmpty(() => html.Script(null, "file"), "releaseFile");
+ }
+
+ [Fact]
+ public void ScriptWithoutDebugFileThrowsArgumentNullException()
+ {
+ // Arrange
+ HtmlHelper html = MvcHelper.GetHtmlHelperWithPath(new ViewDataDictionary());
+
+ // Assert
+ Assert.ThrowsArgumentNullOrEmpty(() => html.Script("File", null), "debugFile");
+ }
+
+ [Fact]
+ public void ScriptWithRootedPathRendersProperElement()
+ {
+ // Arrange
+ HtmlHelper html = MvcHelper.GetHtmlHelperWithPath(new ViewDataDictionary());
+
+ // Act
+ MvcHtmlString result = html.Script("~/Correct/Path.js", "~/Correct/Debug/Path.js");
+
+ // Assert
+ Assert.Equal("<script src=\"/$(SESSION)/Correct/Path.js\" type=\"text/javascript\"></script>", result.ToHtmlString());
+ }
+
+ [Fact]
+ public void ScriptWithRelativePathRendersProperElement()
+ {
+ // Arrange
+ HtmlHelper html = MvcHelper.GetHtmlHelperWithPath(new ViewDataDictionary());
+
+ // Act
+ MvcHtmlString result = html.Script("../../Correct/Path.js", "../../Correct/Debug/Path.js");
+
+ // Assert
+ Assert.Equal("<script src=\"../../Correct/Path.js\" type=\"text/javascript\"></script>", result.ToHtmlString());
+ }
+
+ [Fact]
+ public void ScriptWithRelativeCurrentPathRendersProperElement()
+ {
+ // Arrange
+ HtmlHelper html = MvcHelper.GetHtmlHelperWithPath(new ViewDataDictionary());
+
+ // Act
+ MvcHtmlString result = html.Script("/Correct/Path.js", "/Correct/Debug/Path.js");
+
+ // Assert
+ Assert.Equal("<script src=\"/Correct/Path.js\" type=\"text/javascript\"></script>", result.ToHtmlString());
+ }
+
+ [Fact]
+ public void ScriptWithScriptRelativePathRendersProperElement()
+ {
+ // Arrange
+ HtmlHelper html = MvcHelper.GetHtmlHelperWithPath(new ViewDataDictionary());
+
+ // Act
+ MvcHtmlString result = html.Script("Correct/Path.js", "Correct/Debug/Path.js");
+
+ // Assert
+ Assert.Equal("<script src=\"/$(SESSION)/Scripts/Correct/Path.js\" type=\"text/javascript\"></script>", result.ToHtmlString());
+ }
+
+ [Fact]
+ public void ScriptWithUrlRendersProperElement()
+ {
+ // Arrange
+ HtmlHelper html = MvcHelper.GetHtmlHelperWithPath(new ViewDataDictionary());
+
+ // Act
+ MvcHtmlString result = html.Script("http://ajax.Correct.com/Path.js", "http://ajax.Debug.com/Path.js");
+
+ // Assert
+ Assert.Equal("<script src=\"http://ajax.Correct.com/Path.js\" type=\"text/javascript\"></script>", result.ToHtmlString());
+ }
+
+ [Fact]
+ public void ScriptWithSecureUrlRendersProperElement()
+ {
+ // Arrange
+ HtmlHelper html = MvcHelper.GetHtmlHelperWithPath(new ViewDataDictionary());
+
+ // Act
+ MvcHtmlString result = html.Script("https://ajax.Correct.com/Path.js", "https://ajax.Debug.com/Path.js");
+
+ // Assert
+ Assert.Equal("<script src=\"https://ajax.Correct.com/Path.js\" type=\"text/javascript\"></script>", result.ToHtmlString());
+ }
+
+ [Fact]
+ public void ScriptWithDebuggingOnUsesDebugUrl()
+ {
+ // Arrange
+ HtmlHelper html = MvcHelper.GetHtmlHelperWithPath(new ViewDataDictionary());
+ Mock.Get(html.ViewContext.HttpContext).Setup(v => v.IsDebuggingEnabled).Returns(true);
+
+ // Act
+ MvcHtmlString result = html.Script("Correct/Path.js", "Correct/Debug/Path.js");
+
+ // Assert
+ Assert.Equal("<script src=\"/$(SESSION)/Scripts/Correct/Debug/Path.js\" type=\"text/javascript\"></script>", result.ToHtmlString());
+ }
+ }
+}
diff --git a/test/Microsoft.Web.Mvc.Test/Test/SerializationExtensionsTest.cs b/test/Microsoft.Web.Mvc.Test/Test/SerializationExtensionsTest.cs
new file mode 100644
index 00000000..aefc6712
--- /dev/null
+++ b/test/Microsoft.Web.Mvc.Test/Test/SerializationExtensionsTest.cs
@@ -0,0 +1,78 @@
+using System.Web.Mvc;
+using Microsoft.Web.UnitTestUtil;
+using Moq;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace Microsoft.Web.Mvc.Test
+{
+ public class SerializationExtensionsTest
+ {
+ [Fact]
+ public void SerializeFromProvidedValueOverridesViewData()
+ {
+ // Arrange
+ ViewDataDictionary vdd = new ViewDataDictionary
+ {
+ { "someKey", 42 }
+ };
+ HtmlHelper helper = MvcHelper.GetHtmlHelper(vdd);
+
+ Mock<MvcSerializer> mockSerializer = new Mock<MvcSerializer>();
+ mockSerializer.Setup(o => o.Serialize("Hello!", SerializationMode.Signed)).Returns("some-value");
+
+ // Act
+ MvcHtmlString htmlString = helper.Serialize("someKey", "Hello!", SerializationMode.Signed, mockSerializer.Object);
+
+ // Assert
+ Assert.Equal(@"<input name=""someKey"" type=""hidden"" value=""some-value"" />", htmlString.ToHtmlString());
+ }
+
+ [Fact]
+ public void SerializeFromViewData()
+ {
+ // Arrange
+ ViewDataDictionary vdd = new ViewDataDictionary
+ {
+ { "someKey", 42 }
+ };
+ HtmlHelper helper = MvcHelper.GetHtmlHelper(vdd);
+
+ Mock<MvcSerializer> mockSerializer = new Mock<MvcSerializer>();
+ mockSerializer.Setup(o => o.Serialize(42, SerializationMode.EncryptedAndSigned)).Returns("some-other-value");
+
+ // Act
+ MvcHtmlString htmlString = helper.Serialize("someKey", SerializationMode.EncryptedAndSigned, mockSerializer.Object);
+
+ // Assert
+ Assert.Equal(@"<input name=""someKey"" type=""hidden"" value=""some-other-value"" />", htmlString.ToHtmlString());
+ }
+
+ [Fact]
+ public void SerializeThrowsIfHtmlHelperIsNull()
+ {
+ Assert.ThrowsArgumentNull(
+ delegate { SerializationExtensions.Serialize(null, "someName"); }, "htmlHelper");
+ }
+
+ [Fact]
+ public void SerializeThrowsIfNameIsEmpty()
+ {
+ // Arrange
+ HtmlHelper helper = MvcHelper.GetHtmlHelper(new ViewDataDictionary());
+
+ Assert.ThrowsArgumentNullOrEmpty(
+ delegate { helper.Serialize(""); }, "name");
+ }
+
+ [Fact]
+ public void SerializeThrowsIfNameIsNull()
+ {
+ // Arrange
+ HtmlHelper helper = MvcHelper.GetHtmlHelper(new ViewDataDictionary());
+
+ Assert.ThrowsArgumentNullOrEmpty(
+ delegate { helper.Serialize(null); }, "name");
+ }
+ }
+}
diff --git a/test/Microsoft.Web.Mvc.Test/Test/ServerVariablesValueProviderFactoryTest.cs b/test/Microsoft.Web.Mvc.Test/Test/ServerVariablesValueProviderFactoryTest.cs
new file mode 100644
index 00000000..d57a63cc
--- /dev/null
+++ b/test/Microsoft.Web.Mvc.Test/Test/ServerVariablesValueProviderFactoryTest.cs
@@ -0,0 +1,35 @@
+using System.Collections.Specialized;
+using System.Globalization;
+using System.Web.Mvc;
+using Moq;
+using Xunit;
+
+namespace Microsoft.Web.Mvc.Test
+{
+ public class ServerVariablesValueProviderFactoryTest
+ {
+ [Fact]
+ public void GetValueProvider()
+ {
+ // Arrange
+ NameValueCollection serverVars = new NameValueCollection
+ {
+ { "foo", "fooValue" },
+ { "bar.baz", "barBazValue" }
+ };
+
+ Mock<ControllerContext> mockControllerContext = new Mock<ControllerContext>();
+ mockControllerContext.Setup(o => o.HttpContext.Request.ServerVariables).Returns(serverVars);
+
+ ServerVariablesValueProviderFactory factory = new ServerVariablesValueProviderFactory();
+
+ // Act
+ IValueProvider provider = factory.GetValueProvider(mockControllerContext.Object);
+
+ // Assert
+ Assert.True(provider.ContainsPrefix("bar"));
+ Assert.Equal("fooValue", provider.GetValue("foo").AttemptedValue);
+ Assert.Equal(CultureInfo.InvariantCulture, provider.GetValue("foo").Culture);
+ }
+ }
+}
diff --git a/test/Microsoft.Web.Mvc.Test/Test/SessionValueProviderFactoryTest.cs b/test/Microsoft.Web.Mvc.Test/Test/SessionValueProviderFactoryTest.cs
new file mode 100644
index 00000000..e75b577f
--- /dev/null
+++ b/test/Microsoft.Web.Mvc.Test/Test/SessionValueProviderFactoryTest.cs
@@ -0,0 +1,60 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Web;
+using System.Web.Mvc;
+using Moq;
+using Xunit;
+
+namespace Microsoft.Web.Mvc.Test
+{
+ public class SessionValueProviderFactoryTest
+ {
+ [Fact]
+ public void GetValueProvider()
+ {
+ // Arrange
+ Dictionary<string, object> backingStore = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase)
+ {
+ { "foo", "fooValue" },
+ { "bar.baz", "barBazValue" }
+ };
+ MockSessionState session = new MockSessionState(backingStore);
+
+ Mock<ControllerContext> mockControllerContext = new Mock<ControllerContext>();
+ mockControllerContext.Setup(o => o.HttpContext.Session).Returns(session);
+
+ SessionValueProviderFactory factory = new SessionValueProviderFactory();
+
+ // Act
+ IValueProvider provider = factory.GetValueProvider(mockControllerContext.Object);
+
+ // Assert
+ Assert.True(provider.ContainsPrefix("bar"));
+ Assert.Equal("fooValue", provider.GetValue("foo").AttemptedValue);
+ Assert.Equal(CultureInfo.InvariantCulture, provider.GetValue("foo").Culture);
+ }
+
+ private sealed class MockSessionState : HttpSessionStateBase
+ {
+ private readonly IDictionary<string, object> _backingStore;
+
+ public MockSessionState(IDictionary<string, object> backingStore)
+ {
+ _backingStore = backingStore;
+ }
+
+ public override object this[string name]
+ {
+ get { return _backingStore[name]; }
+ set { _backingStore[name] = value; }
+ }
+
+ public override IEnumerator GetEnumerator()
+ {
+ return _backingStore.Keys.GetEnumerator();
+ }
+ }
+ }
+}
diff --git a/test/Microsoft.Web.Mvc.Test/Test/SkipBindingAttributeTest.cs b/test/Microsoft.Web.Mvc.Test/Test/SkipBindingAttributeTest.cs
new file mode 100644
index 00000000..4d3836a9
--- /dev/null
+++ b/test/Microsoft.Web.Mvc.Test/Test/SkipBindingAttributeTest.cs
@@ -0,0 +1,22 @@
+using System.Web.Mvc;
+using Xunit;
+
+namespace Microsoft.Web.Mvc.Test
+{
+ public class SkipBindingAttributeTest
+ {
+ [Fact]
+ public void GetBinderReturnsModelBinderWhichReturnsNull()
+ {
+ // Arrange
+ CustomModelBinderAttribute attr = new SkipBindingAttribute();
+ IModelBinder binder = attr.GetBinder();
+
+ // Act
+ object result = binder.BindModel(null, null);
+
+ // Assert
+ Assert.Null(result);
+ }
+ }
+}
diff --git a/test/Microsoft.Web.Mvc.Test/Test/SubmitButtonExtensionsTest.cs b/test/Microsoft.Web.Mvc.Test/Test/SubmitButtonExtensionsTest.cs
new file mode 100644
index 00000000..20e9e375
--- /dev/null
+++ b/test/Microsoft.Web.Mvc.Test/Test/SubmitButtonExtensionsTest.cs
@@ -0,0 +1,82 @@
+using System.Web.Mvc;
+using System.Web.Routing;
+using Microsoft.Web.UnitTestUtil;
+using Xunit;
+
+namespace Microsoft.Web.Mvc.Test
+{
+ public class SubmitButtonExtensionsTest
+ {
+ [Fact]
+ public void SubmitButtonRendersWithJustTypeAttribute()
+ {
+ HtmlHelper html = MvcHelper.GetHtmlHelper(new ViewDataDictionary());
+ MvcHtmlString button = html.SubmitButton();
+ Assert.Equal("<input type=\"submit\" />", button.ToHtmlString());
+ }
+
+ [Fact]
+ public void SubmitButtonWithAttributesWithUnderscores()
+ {
+ HtmlHelper html = MvcHelper.GetHtmlHelper(new ViewDataDictionary());
+ MvcHtmlString button = html.SubmitButton(null, "blah", new { foo_bar = "baz" });
+ Assert.Equal("<input foo-bar=\"baz\" type=\"submit\" value=\"blah\" />", button.ToHtmlString());
+ }
+
+ [Fact]
+ public void SubmitButtonWithNameRendersButtonWithNameAttribute()
+ {
+ HtmlHelper html = MvcHelper.GetHtmlHelper(new ViewDataDictionary());
+ MvcHtmlString button = html.SubmitButton("button-name");
+ Assert.Equal("<input id=\"button-name\" name=\"button-name\" type=\"submit\" />", button.ToHtmlString());
+ }
+
+ [Fact]
+ public void SubmitButtonWithIdDifferentFromNameRendersButtonWithId()
+ {
+ HtmlHelper html = MvcHelper.GetHtmlHelper(new ViewDataDictionary());
+ MvcHtmlString button = html.SubmitButton("button-name", "blah", new { id = "foo" });
+ Assert.Equal("<input id=\"foo\" name=\"button-name\" type=\"submit\" value=\"blah\" />", button.ToHtmlString());
+ }
+
+ [Fact]
+ public void SubmitButtonWithNameAndTextRendersAttributes()
+ {
+ HtmlHelper html = MvcHelper.GetHtmlHelper(new ViewDataDictionary());
+ MvcHtmlString button = html.SubmitButton("button-name", "button-text");
+ Assert.Equal("<input id=\"button-name\" name=\"button-name\" type=\"submit\" value=\"button-text\" />", button.ToHtmlString());
+ }
+
+ [Fact]
+ public void SubmitButtonWithNameAndValueRendersBothAttributes()
+ {
+ HtmlHelper html = MvcHelper.GetHtmlHelper(new ViewDataDictionary());
+ MvcHtmlString button = html.SubmitButton("button-name", "button-value", new { id = "button-id" });
+ Assert.Equal("<input id=\"button-id\" name=\"button-name\" type=\"submit\" value=\"button-value\" />", button.ToHtmlString());
+ }
+
+ [Fact]
+ public void SubmitButtonWithNameAndIdRendersBothAttributesCorrectly()
+ {
+ HtmlHelper html = MvcHelper.GetHtmlHelper(new ViewDataDictionary());
+ MvcHtmlString button = html.SubmitButton("button-name", "button-value", new { id = "button-id" });
+ Assert.Equal("<input id=\"button-id\" name=\"button-name\" type=\"submit\" value=\"button-value\" />", button.ToHtmlString());
+ }
+
+ [Fact]
+ public void SubmitButtonWithTypeAttributeRendersCorrectType()
+ {
+ HtmlHelper html = MvcHelper.GetHtmlHelper(new ViewDataDictionary());
+ MvcHtmlString button = html.SubmitButton("specified-name", "button-value", new { type = "not-submit" });
+ Assert.Equal("<input id=\"specified-name\" name=\"specified-name\" type=\"not-submit\" value=\"button-value\" />", button.ToHtmlString());
+ }
+
+ [Fact]
+ public void SubmitButtonWithNameAndValueSpecifiedAndPassedInAsAttributeChoosesSpecified()
+ {
+ HtmlHelper html = MvcHelper.GetHtmlHelper(new ViewDataDictionary());
+ MvcHtmlString button = html.SubmitButton("specified-name", "button-value", new RouteValueDictionary(new { name = "name-attribute-value", value = "value-attribute" }));
+ Assert.Equal("<input id=\"specified-name\" name=\"name-attribute-value\" type=\"submit\" value=\"value-attribute\" />", button.ToHtmlString());
+ }
+ }
+}
diff --git a/test/Microsoft.Web.Mvc.Test/Test/SubmitImageExtensionsTest.cs b/test/Microsoft.Web.Mvc.Test/Test/SubmitImageExtensionsTest.cs
new file mode 100644
index 00000000..489c0a4d
--- /dev/null
+++ b/test/Microsoft.Web.Mvc.Test/Test/SubmitImageExtensionsTest.cs
@@ -0,0 +1,66 @@
+using System.Web.Mvc;
+using System.Web.Routing;
+using Microsoft.Web.UnitTestUtil;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace Microsoft.Web.Mvc.Test
+{
+ public class SubmitImageExtensionsTest
+ {
+ [Fact]
+ public void SubmitImageWithEmptyImageSrcThrowsArgumentNullException()
+ {
+ HtmlHelper html = MvcHelper.GetHtmlHelper(new ViewDataDictionary());
+ Assert.ThrowsArgumentNull(() => html.SubmitImage("name", null), "imageSrc");
+ }
+
+ [Fact]
+ public void SubmitImageWithAttributesWithUnderscores()
+ {
+ HtmlHelper html = MvcHelper.GetHtmlHelper(new ViewDataDictionary());
+ MvcHtmlString button = html.SubmitImage("specified-name", "/mvc.jpg", new { foo_bar = "baz" });
+ Assert.Equal("<input foo-bar=\"baz\" id=\"specified-name\" name=\"specified-name\" src=\"/mvc.jpg\" type=\"image\" />", button.ToHtmlString());
+ }
+
+ [Fact]
+ public void SubmitImageWithTypeAttributeRendersExplicitTypeAttribute()
+ {
+ HtmlHelper html = MvcHelper.GetHtmlHelper(new ViewDataDictionary());
+ MvcHtmlString button = html.SubmitImage("specified-name", "/mvc.jpg", new { type = "not-image" });
+ Assert.Equal("<input id=\"specified-name\" name=\"specified-name\" src=\"/mvc.jpg\" type=\"not-image\" />", button.ToHtmlString());
+ }
+
+ [Fact]
+ public void SubmitImageWithNameAndImageUrlRendersNameAndSrcAttributes()
+ {
+ HtmlHelper html = MvcHelper.GetHtmlHelper(new ViewDataDictionary());
+ MvcHtmlString button = html.SubmitImage("button-name", "/mvc.gif");
+ Assert.Equal("<input id=\"button-name\" name=\"button-name\" src=\"/mvc.gif\" type=\"image\" />", button.ToHtmlString());
+ }
+
+ [Fact]
+ public void SubmitImageWithImageUrlStartingWithTildeRendersAppPath()
+ {
+ HtmlHelper html = MvcHelper.GetHtmlHelperWithPath(new ViewDataDictionary(), "/app");
+ MvcHtmlString button = html.SubmitImage("button-name", "~/mvc.gif");
+ Assert.Equal("<input id=\"button-name\" name=\"button-name\" src=\"/$(SESSION)/app/mvc.gif\" type=\"image\" />", button.ToHtmlString());
+ }
+
+ [Fact]
+ public void SubmitImageWithNameAndIdRendersBothAttributesCorrectly()
+ {
+ HtmlHelper html = MvcHelper.GetHtmlHelper(new ViewDataDictionary());
+ MvcHtmlString button = html.SubmitImage("button-name", "/mvc.png", new { id = "button-id" });
+ Assert.Equal("<input id=\"button-id\" name=\"button-name\" src=\"/mvc.png\" type=\"image\" />", button.ToHtmlString());
+ }
+
+ [Fact]
+ public void SubmitButtonWithNameAndValueSpecifiedAndPassedInAsAttributeChoosesExplicitAttributes()
+ {
+ HtmlHelper html = MvcHelper.GetHtmlHelper(new ViewDataDictionary());
+ MvcHtmlString button = html.SubmitImage("specified-name", "/specified-src.bmp", new RouteValueDictionary(new { name = "name-attribute", src = "src-attribute" }));
+ Assert.Equal("<input id=\"specified-name\" name=\"name-attribute\" src=\"src-attribute\" type=\"image\" />", button.ToHtmlString());
+ }
+ }
+}
diff --git a/test/Microsoft.Web.Mvc.Test/Test/TempDataValueProviderFactoryTest.cs b/test/Microsoft.Web.Mvc.Test/Test/TempDataValueProviderFactoryTest.cs
new file mode 100644
index 00000000..51e99248
--- /dev/null
+++ b/test/Microsoft.Web.Mvc.Test/Test/TempDataValueProviderFactoryTest.cs
@@ -0,0 +1,86 @@
+using System.Collections.Generic;
+using System.Globalization;
+using System.Linq;
+using System.Web.Mvc;
+using Moq;
+using Xunit;
+
+namespace Microsoft.Web.Mvc.Test
+{
+ public class TempDataValueProviderFactoryTest
+ {
+ [Fact]
+ public void GetValueProvider_CorrectlyRetainsOrRemovesKeys()
+ {
+ // Arrange
+ string[] expectedRetainedKeys = new[]
+ {
+ "retainMe"
+ };
+
+ TempDataDictionary tempData = new TempDataDictionary
+ {
+ { "retainMe", "retainMeValue" },
+ { "removeMe", "removeMeValue" },
+ { "previouslyRemoved", "previouslyRemovedValue" }
+ };
+ object dummy = tempData["previouslyRemoved"]; // mark value for removal
+
+ ControllerContext controllerContext = GetControllerContext(tempData);
+
+ TempDataValueProviderFactory factory = new TempDataValueProviderFactory();
+
+ // Act
+ IValueProvider valueProvider = factory.GetValueProvider(controllerContext);
+ ValueProviderResult nonExistentResult = valueProvider.GetValue("nonExistent");
+ ValueProviderResult removeMeResult = valueProvider.GetValue("removeme");
+
+ // Assert
+ Assert.Null(nonExistentResult);
+ Assert.Equal("removeMeValue", removeMeResult.AttemptedValue);
+ Assert.Equal(CultureInfo.InvariantCulture, removeMeResult.Culture);
+
+ // Verify that keys have been removed or retained correctly by the provider
+ Mock<ITempDataProvider> mockTempDataProvider = new Mock<ITempDataProvider>();
+ string[] retainedKeys = null;
+ mockTempDataProvider
+ .Setup(o => o.SaveTempData(controllerContext, It.IsAny<IDictionary<string, object>>()))
+ .Callback(
+ delegate(ControllerContext cc, IDictionary<string, object> d) { retainedKeys = d.Keys.ToArray(); });
+
+ tempData.Save(controllerContext, mockTempDataProvider.Object);
+ Assert.Equal(expectedRetainedKeys, retainedKeys);
+ }
+
+ [Fact]
+ public void GetValueProvider_EmptyTempData_ReturnsNull()
+ {
+ // Arrange
+ TempDataDictionary tempData = new TempDataDictionary();
+ ControllerContext controllerContext = GetControllerContext(tempData);
+
+ TempDataValueProviderFactory factory = new TempDataValueProviderFactory();
+
+ // Act
+ IValueProvider provider = factory.GetValueProvider(controllerContext);
+
+ // Assert
+ Assert.Null(provider);
+ }
+
+ private static ControllerContext GetControllerContext(TempDataDictionary tempData)
+ {
+ return new ControllerContext
+ {
+ Controller = new EmptyController
+ {
+ TempData = tempData
+ }
+ };
+ }
+
+ private sealed class EmptyController : Controller
+ {
+ }
+ }
+}
diff --git a/test/Microsoft.Web.Mvc.Test/Test/TypeHelpersTest.cs b/test/Microsoft.Web.Mvc.Test/Test/TypeHelpersTest.cs
new file mode 100644
index 00000000..e934b165
--- /dev/null
+++ b/test/Microsoft.Web.Mvc.Test/Test/TypeHelpersTest.cs
@@ -0,0 +1,121 @@
+using System;
+using System.Collections.Generic;
+using Xunit;
+
+namespace Microsoft.Web.Mvc.Test
+{
+ public class TypeHelpersTest
+ {
+ [Fact]
+ public void GetTypeArgumentsIfMatch_ClosedTypeIsGenericAndMatches_ReturnsType()
+ {
+ // Act
+ Type[] typeArguments = TypeHelpers.GetTypeArgumentsIfMatch(typeof(List<int>), typeof(List<>));
+
+ // Assert
+ Assert.Equal(new[] { typeof(int) }, typeArguments);
+ }
+
+ [Fact]
+ public void GetTypeArgumentsIfMatch_ClosedTypeIsGenericButDoesNotMatch_ReturnsNull()
+ {
+ // Act
+ Type[] typeArguments = TypeHelpers.GetTypeArgumentsIfMatch(typeof(int?), typeof(List<>));
+
+ // Assert
+ Assert.Null(typeArguments);
+ }
+
+ [Fact]
+ public void GetTypeArgumentsIfMatch_ClosedTypeIsNotGeneric_ReturnsNull()
+ {
+ // Act
+ Type[] typeArguments = TypeHelpers.GetTypeArgumentsIfMatch(typeof(int), null);
+
+ // Assert
+ Assert.Null(typeArguments);
+ }
+
+ [Fact]
+ public void IsCompatibleObjectReturnsTrueIfTypeIsNotNullableAndValueIsNull()
+ {
+ // Act
+ bool retVal = TypeHelpers.IsCompatibleObject(typeof(int), null);
+
+ // Assert
+ Assert.False(retVal);
+ }
+
+ [Fact]
+ public void IsCompatibleObjectReturnsFalseIfValueIsIncorrectType()
+ {
+ // Arrange
+ object value = new[] { "Hello", "world" };
+
+ // Act
+ bool retVal = TypeHelpers.IsCompatibleObject(typeof(int), value);
+
+ // Assert
+ Assert.False(retVal);
+ }
+
+ [Fact]
+ public void IsCompatibleObjectReturnsTrueIfTypeIsNullableAndValueIsNull()
+ {
+ // Act
+ bool retVal = TypeHelpers.IsCompatibleObject(typeof(int?), null);
+
+ // Assert
+ Assert.True(retVal);
+ }
+
+ [Fact]
+ public void IsCompatibleObjectReturnsTrueIfValueIsOfCorrectType()
+ {
+ // Arrange
+ object value = new[] { "Hello", "world" };
+
+ // Act
+ bool retVal = TypeHelpers.IsCompatibleObject(typeof(IEnumerable<string>), value);
+
+ // Assert
+ Assert.True(retVal);
+ }
+
+ [Fact]
+ public void TypeAllowsNullValueReturnsFalseForNonNullableGenericValueType()
+ {
+ Assert.False(TypeHelpers.TypeAllowsNullValue(typeof(KeyValuePair<int, string>)));
+ }
+
+ [Fact]
+ public void TypeAllowsNullValueReturnsFalseForNonNullableGenericValueTypeDefinition()
+ {
+ Assert.False(TypeHelpers.TypeAllowsNullValue(typeof(KeyValuePair<,>)));
+ }
+
+ [Fact]
+ public void TypeAllowsNullValueReturnsFalseForNonNullableValueType()
+ {
+ Assert.False(TypeHelpers.TypeAllowsNullValue(typeof(int)));
+ }
+
+ [Fact]
+ public void TypeAllowsNullValueReturnsTrueForInterfaceType()
+ {
+ Assert.True(TypeHelpers.TypeAllowsNullValue(typeof(IDisposable)));
+ }
+
+ [Fact]
+ public void TypeAllowsNullValueReturnsTrueForNullableType()
+ {
+ Assert.True(TypeHelpers.TypeAllowsNullValue(typeof(int?)));
+ }
+
+ [Fact]
+ public void TypeAllowsNullValueReturnsTrueForReferenceType()
+ {
+ Assert.True(TypeHelpers.TypeAllowsNullValue(typeof(object)));
+ }
+ }
+}
diff --git a/test/Microsoft.Web.Mvc.Test/Test/UrlAttributeTest.cs b/test/Microsoft.Web.Mvc.Test/Test/UrlAttributeTest.cs
new file mode 100644
index 00000000..462f4bb2
--- /dev/null
+++ b/test/Microsoft.Web.Mvc.Test/Test/UrlAttributeTest.cs
@@ -0,0 +1,44 @@
+using System.Linq;
+using System.Web.Mvc;
+using Moq;
+using Xunit;
+
+namespace Microsoft.Web.Mvc.Test
+{
+ public class UrlAttributeTest
+ {
+ [Fact]
+ public void ClientRule()
+ {
+ // Arrange
+ var attribute = new UrlAttribute();
+ var provider = new Mock<ModelMetadataProvider>();
+ var metadata = new ModelMetadata(provider.Object, null, null, typeof(string), "PropertyName");
+
+ // Act
+ ModelClientValidationRule clientRule = attribute.GetClientValidationRules(metadata, null).Single();
+
+ // Assert
+ Assert.Equal("url", clientRule.ValidationType);
+ Assert.Equal("The PropertyName field is not a valid fully-qualified http, https, or ftp URL.", clientRule.ErrorMessage);
+ Assert.Empty(clientRule.ValidationParameters);
+ }
+
+ [Fact]
+ public void IsValidTests()
+ {
+ // Arrange
+ var attribute = new UrlAttribute();
+
+ // Act & Assert
+ Assert.True(attribute.IsValid(null)); // Optional values are always valid
+ Assert.True(attribute.IsValid("http://foo.bar"));
+ Assert.True(attribute.IsValid("https://foo.bar"));
+ Assert.True(attribute.IsValid("ftp://foo.bar"));
+ Assert.False(attribute.IsValid("file:///foo.bar"));
+ Assert.False(attribute.IsValid("http://user%password@foo.bar/"));
+ Assert.False(attribute.IsValid("foo.png"));
+ Assert.False(attribute.IsValid("\0foo.png")); // Illegal character
+ }
+ }
+}
diff --git a/test/Microsoft.Web.Mvc.Test/Test/ValueProviderUtilTest.cs b/test/Microsoft.Web.Mvc.Test/Test/ValueProviderUtilTest.cs
new file mode 100644
index 00000000..ed920b30
--- /dev/null
+++ b/test/Microsoft.Web.Mvc.Test/Test/ValueProviderUtilTest.cs
@@ -0,0 +1,46 @@
+using Xunit;
+
+namespace Microsoft.Web.Mvc.Test
+{
+ public class ValueProviderUtilTest
+ {
+ [Fact]
+ public void IsPrefixMatch_Misses()
+ {
+ // Arrange
+ var tests = new[]
+ {
+ new { Prefix = "Prefix", TestString = (string)null, Reason = "Null test string shouldn't match anything." },
+ new { Prefix = "Foo", TestString = "NotFoo", Reason = "Prefix 'foo' doesn't match 'notfoo'." },
+ new { Prefix = "Foo", TestString = "FooBar", Reason = "Prefix 'foo' was not followed by a delimiter in the test string." }
+ };
+
+ // Act & assert
+ foreach (var test in tests)
+ {
+ bool retVal = ValueProviderUtil.IsPrefixMatch(test.Prefix, test.TestString);
+ Assert.False(retVal, test.Reason);
+ }
+ }
+
+ [Fact]
+ public void IsPrefixMatch_Hits()
+ {
+ // Arrange
+ var tests = new[]
+ {
+ new { Prefix = "", TestString = "SomeTestString", Reason = "Empty prefix should match any non-null test string." },
+ new { Prefix = "SomeString", TestString = "SomeString", Reason = "This was an exact match." },
+ new { Prefix = "Foo", TestString = "foo.bar", Reason = "Prefix 'foo' matched." },
+ new { Prefix = "Foo", TestString = "foo[bar]", Reason = "Prefix 'foo' matched." },
+ };
+
+ // Act & assert
+ foreach (var test in tests)
+ {
+ bool retVal = ValueProviderUtil.IsPrefixMatch(test.Prefix, test.TestString);
+ Assert.True(retVal, test.Reason);
+ }
+ }
+ }
+}
diff --git a/test/Microsoft.Web.Mvc.Test/packages.config b/test/Microsoft.Web.Mvc.Test/packages.config
new file mode 100644
index 00000000..d5aa6401
--- /dev/null
+++ b/test/Microsoft.Web.Mvc.Test/packages.config
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<packages>
+ <package id="Moq" version="4.0.10827" />
+ <package id="xunit" version="1.9.0.1566" />
+ <package id="xunit.extensions" version="1.9.0.1566" />
+</packages> \ No newline at end of file
diff --git a/test/Microsoft.Web.WebPages.OAuth.Test/Microsoft.Web.WebPages.OAuth.Test.csproj b/test/Microsoft.Web.WebPages.OAuth.Test/Microsoft.Web.WebPages.OAuth.Test.csproj
new file mode 100644
index 00000000..74b256dc
--- /dev/null
+++ b/test/Microsoft.Web.WebPages.OAuth.Test/Microsoft.Web.WebPages.OAuth.Test.csproj
@@ -0,0 +1,104 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory),Runtime.sln))\tools\WebStack.settings.targets" />
+ <PropertyGroup>
+ <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+ <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+ <ProductVersion>8.0.30703</ProductVersion>
+ <SchemaVersion>2.0</SchemaVersion>
+ <ProjectGuid>{694C6EDF-EA52-438F-B745-82B025ECC0E7}</ProjectGuid>
+ <OutputType>Library</OutputType>
+ <AppDesignerFolder>Properties</AppDesignerFolder>
+ <RootNamespace>Microsoft.Web.WebPages.OAuth.Test</RootNamespace>
+ <AssemblyName>Microsoft.Web.WebPages.OAuth.Test</AssemblyName>
+ <TargetFrameworkVersion>v4.0</TargetFrameworkVersion>
+ <FileAlignment>512</FileAlignment>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
+ <DebugSymbols>true</DebugSymbols>
+ <DebugType>full</DebugType>
+ <Optimize>false</Optimize>
+ <OutputPath>$(WebStackRootPath)\bin\Debug\Test\</OutputPath>
+ <DefineConstants>TRACE;DEBUG</DefineConstants>
+ <CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
+ <DebugType>pdbonly</DebugType>
+ <Optimize>true</Optimize>
+ <OutputPath>$(WebStackRootPath)\bin\Release\Test\</OutputPath>
+ <DefineConstants>TRACE</DefineConstants>
+ <CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)' == 'CodeCoverage' ">
+ <DebugSymbols>true</DebugSymbols>
+ <DebugType>full</DebugType>
+ <Optimize>false</Optimize>
+ <OutputPath>$(WebStackRootPath)\bin\CodeCoverage\Test\</OutputPath>
+ <DefineConstants>TRACE;DEBUG</DefineConstants>
+ <CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
+ </PropertyGroup>
+ <ItemGroup>
+ <Reference Include="DotNetOpenAuth.AspNet, Version=4.0.0.12065, Culture=neutral, PublicKeyToken=2780ccd10d57b246, processorArchitecture=MSIL">
+ <SpecificVersion>False</SpecificVersion>
+ <HintPath>..\..\packages\DotNetOpenAuth.AspNet.4.0.0-beta2\lib\net40-full\DotNetOpenAuth.AspNet.dll</HintPath>
+ </Reference>
+ <Reference Include="DotNetOpenAuth.Core, Version=4.0.0.12065, Culture=neutral, PublicKeyToken=2780ccd10d57b246, processorArchitecture=MSIL">
+ <SpecificVersion>False</SpecificVersion>
+ <HintPath>..\..\packages\DotNetOpenAuth.Core.4.0.0-beta2\lib\net40-full\DotNetOpenAuth.Core.dll</HintPath>
+ </Reference>
+ <Reference Include="DotNetOpenAuth.OAuth, Version=4.0.0.12065, Culture=neutral, PublicKeyToken=2780ccd10d57b246, processorArchitecture=MSIL">
+ <SpecificVersion>False</SpecificVersion>
+ <HintPath>..\..\packages\DotNetOpenAuth.OAuth.Core.4.0.0-beta2\lib\net40-full\DotNetOpenAuth.OAuth.dll</HintPath>
+ </Reference>
+ <Reference Include="DotNetOpenAuth.OAuth.Consumer, Version=4.0.0.12065, Culture=neutral, PublicKeyToken=2780ccd10d57b246, processorArchitecture=MSIL">
+ <SpecificVersion>False</SpecificVersion>
+ <HintPath>..\..\packages\DotNetOpenAuth.OAuth.Consumer.4.0.0-beta2\lib\net40-full\DotNetOpenAuth.OAuth.Consumer.dll</HintPath>
+ </Reference>
+ <Reference Include="DotNetOpenAuth.OpenId, Version=4.0.0.12065, Culture=neutral, PublicKeyToken=2780ccd10d57b246, processorArchitecture=MSIL">
+ <SpecificVersion>False</SpecificVersion>
+ <HintPath>..\..\packages\DotNetOpenAuth.OpenId.Core.4.0.0-beta2\lib\net40-full\DotNetOpenAuth.OpenId.dll</HintPath>
+ </Reference>
+ <Reference Include="DotNetOpenAuth.OpenId.RelyingParty, Version=4.0.0.12065, Culture=neutral, PublicKeyToken=2780ccd10d57b246, processorArchitecture=MSIL">
+ <SpecificVersion>False</SpecificVersion>
+ <HintPath>..\..\packages\DotNetOpenAuth.OpenId.RelyingParty.4.0.0-beta2\lib\net40-full\DotNetOpenAuth.OpenId.RelyingParty.dll</HintPath>
+ </Reference>
+ <Reference Include="Moq">
+ <HintPath>..\..\packages\Moq.4.0.10827\lib\NET40\Moq.dll</HintPath>
+ </Reference>
+ <Reference Include="System" />
+ <Reference Include="System.Configuration" />
+ <Reference Include="System.Core" />
+ <Reference Include="System.Web" />
+ <Reference Include="xunit">
+ <HintPath>..\..\packages\xunit.1.9.0.1566\lib\xunit.dll</HintPath>
+ </Reference>
+ </ItemGroup>
+ <ItemGroup>
+ <Compile Include="OAuthWebSecurityTest.cs">
+ <SubType>Code</SubType>
+ </Compile>
+ <Compile Include="PreAppStartCodeTest.cs" />
+ <Compile Include="Properties\AssemblyInfo.cs" />
+ </ItemGroup>
+ <ItemGroup>
+ <None Include="packages.config" />
+ </ItemGroup>
+ <ItemGroup>
+ <ProjectReference Include="..\..\src\Microsoft.Web.WebPages.OAuth\Microsoft.Web.WebPages.OAuth.csproj">
+ <Project>{4CBFC7D3-1600-4CE5-BC6B-AC7BC2D6F853}</Project>
+ <Name>Microsoft.Web.WebPages.OAuth</Name>
+ </ProjectReference>
+ <ProjectReference Include="..\Microsoft.TestCommon\Microsoft.TestCommon.csproj">
+ <Project>{FCCC4CB7-BAF7-4A57-9F89-E5766FE536C0}</Project>
+ <Name>Microsoft.TestCommon</Name>
+ </ProjectReference>
+ </ItemGroup>
+ <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
+ <!-- To modify your build process, add your task inside one of the targets below and uncomment it.
+ Other similar extension points exist, see Microsoft.Common.targets.
+ <Target Name="BeforeBuild">
+ </Target>
+ <Target Name="AfterBuild">
+ </Target>
+ -->
+</Project> \ No newline at end of file
diff --git a/test/Microsoft.Web.WebPages.OAuth.Test/OAuthWebSecurityTest.cs b/test/Microsoft.Web.WebPages.OAuth.Test/OAuthWebSecurityTest.cs
new file mode 100644
index 00000000..45129d79
--- /dev/null
+++ b/test/Microsoft.Web.WebPages.OAuth.Test/OAuthWebSecurityTest.cs
@@ -0,0 +1,389 @@
+using System;
+using System.Collections.Specialized;
+using System.Web;
+using System.Web.Security;
+using DotNetOpenAuth.AspNet;
+using Moq;
+using Xunit;
+using Microsoft.TestCommon;
+
+namespace Microsoft.Web.WebPages.OAuth.Test
+{
+ public class OAuthWebSecurityTest : IDisposable
+ {
+ [Fact]
+ public void RegisterClientThrowsOnNullValue()
+ {
+ AssertEx.ThrowsArgumentNull(() => OAuthWebSecurity.RegisterClient(null), "client");
+ }
+
+ [Fact]
+ public void RegisterClientThrowsIfProviderNameIsEmpty()
+ {
+ // Arrange
+ var client = new Mock<IAuthenticationClient>();
+ client.Setup(c => c.ProviderName).Returns((string)null);
+
+ // Act & Assert
+ AssertEx.ThrowsArgument(() => OAuthWebSecurity.RegisterClient(client.Object), "client");
+
+ client.Setup(c => c.ProviderName).Returns("");
+
+ // Act & Assert
+ AssertEx.ThrowsArgument(() => OAuthWebSecurity.RegisterClient(client.Object), "client");
+ }
+
+ [Fact]
+ public void RegisterClientThrowsRegisterMoreThanOneClientWithTheSameName()
+ {
+ // Arrange
+ var client1 = new Mock<IAuthenticationClient>();
+ client1.Setup(c => c.ProviderName).Returns("provider");
+
+ var client2 = new Mock<IAuthenticationClient>();
+ client2.Setup(c => c.ProviderName).Returns("provider");
+
+ OAuthWebSecurity.RegisterClient(client1.Object);
+
+ // Act & Assert
+ AssertEx.ThrowsArgument(() => OAuthWebSecurity.RegisterClient(client2.Object), null);
+ }
+
+ [Fact]
+ public void RegisterOAuthClient()
+ {
+ // Arrange
+ var clients = new BuiltInOAuthClient[]
+ {
+ BuiltInOAuthClient.Facebook,
+ BuiltInOAuthClient.Twitter,
+ BuiltInOAuthClient.LinkedIn,
+ BuiltInOAuthClient.WindowsLive
+ };
+ var clientNames = new string[]
+ {
+ "Facebook",
+ "Twitter",
+ "LinkedIn",
+ "WindowsLive"
+ };
+
+ for (int i = 0; i < clients.Length; i++)
+ {
+ // Act
+ OAuthWebSecurity.RegisterOAuthClient(clients[i], "key", "secret");
+
+ var client = new Mock<IAuthenticationClient>();
+ client.Setup(c => c.ProviderName).Returns(clientNames[i]);
+
+ // Assert
+ Assert.Throws(typeof(ArgumentException), () => OAuthWebSecurity.RegisterClient(client.Object));
+ }
+ }
+
+ [Fact]
+ public void RegisterOpenIDClient()
+ {
+ // Arrange
+ var clients = new BuiltInOpenIDClient[]
+ {
+ BuiltInOpenIDClient.Google,
+ BuiltInOpenIDClient.Yahoo
+ };
+ var clientNames = new string[]
+ {
+ "Google",
+ "Yahoo"
+ };
+
+ for (int i = 0; i < clients.Length; i++)
+ {
+ // Act
+ OAuthWebSecurity.RegisterOpenIDClient(clients[i]);
+
+ var client = new Mock<IAuthenticationClient>();
+ client.Setup(c => c.ProviderName).Returns(clientNames[i]);
+
+ // Assert
+ AssertEx.ThrowsArgument(() => OAuthWebSecurity.RegisterClient(client.Object), null);
+ }
+ }
+
+ [Fact]
+ public void RequestAuthenticationRedirectsToProviderWithNullReturnUrl()
+ {
+ // Arrange
+ var context = new Mock<HttpContextBase>();
+ context.Setup(c => c.Request.ServerVariables).Returns(
+ new NameValueCollection());
+ context.Setup(c => c.Request.Url).Returns(new Uri("http://live.com/login.aspx"));
+ context.Setup(c => c.Request.RawUrl).Returns("/login.aspx");
+
+ var client = new Mock<IAuthenticationClient>();
+ client.Setup(c => c.ProviderName).Returns("windowslive");
+ client.Setup(c => c.RequestAuthentication(
+ context.Object,
+ It.Is<Uri>(u => u.AbsoluteUri.Equals("http://live.com/login.aspx?__provider__=windowslive", StringComparison.OrdinalIgnoreCase))))
+ .Verifiable();
+
+ OAuthWebSecurity.RegisterClient(client.Object);
+
+ // Act
+ OAuthWebSecurity.RequestAuthenticationCore(context.Object, "windowslive", null);
+
+ // Assert
+ client.Verify();
+ }
+
+ [Fact]
+ public void RequestAuthenticationRedirectsToProviderWithReturnUrl()
+ {
+ // Arrange
+ var context = new Mock<HttpContextBase>();
+ context.Setup(c => c.Request.ServerVariables).Returns(
+ new NameValueCollection());
+ context.Setup(c => c.Request.Url).Returns(new Uri("http://live.com/login.aspx"));
+ context.Setup(c => c.Request.RawUrl).Returns("/login.aspx");
+
+ var client = new Mock<IAuthenticationClient>();
+ client.Setup(c => c.ProviderName).Returns("yahoo");
+ client.Setup(c => c.RequestAuthentication(
+ context.Object,
+ It.Is<Uri>(u => u.AbsoluteUri.Equals("http://yahoo.com/?__provider__=yahoo", StringComparison.OrdinalIgnoreCase))))
+ .Verifiable();
+
+ OAuthWebSecurity.RegisterClient(client.Object);
+
+ // Act
+ OAuthWebSecurity.RequestAuthenticationCore(context.Object, "yahoo", "http://yahoo.com");
+
+ // Assert
+ client.Verify();
+ }
+
+ [Fact]
+ public void VerifyAuthenticationSucceed()
+ {
+ // Arrange
+ var queryStrings = new NameValueCollection();
+ queryStrings.Add("__provider__", "facebook");
+
+ var context = new Mock<HttpContextBase>();
+ context.Setup(c => c.Request.QueryString).Returns(queryStrings);
+
+ var client = new Mock<IAuthenticationClient>(MockBehavior.Strict);
+ client.Setup(c => c.ProviderName).Returns("facebook");
+ client.Setup(c => c.VerifyAuthentication(context.Object)).Returns(new AuthenticationResult(true, "facebook", "123",
+ "super", null));
+
+ var anotherClient = new Mock<IAuthenticationClient>(MockBehavior.Strict);
+ anotherClient.Setup(c => c.ProviderName).Returns("twitter");
+ anotherClient.Setup(c => c.VerifyAuthentication(context.Object)).Returns(AuthenticationResult.Failed);
+
+ OAuthWebSecurity.RegisterClient(client.Object);
+ OAuthWebSecurity.RegisterClient(anotherClient.Object);
+
+ // Act
+ AuthenticationResult result = OAuthWebSecurity.VerifyAuthenticationCore(context.Object);
+
+ // Assert
+ Assert.True(result.IsSuccessful);
+ Assert.Equal("facebook", result.Provider);
+ Assert.Equal("123", result.ProviderUserId);
+ Assert.Equal("super", result.UserName);
+ Assert.Null(result.Error);
+ Assert.Null(result.ExtraData);
+ }
+
+ [Fact]
+ public void VerifyAuthenticationFail()
+ {
+ // Arrange
+ var queryStrings = new NameValueCollection();
+ queryStrings.Add("__provider__", "twitter");
+
+ var context = new Mock<HttpContextBase>();
+ context.Setup(c => c.Request.QueryString).Returns(queryStrings);
+
+ var client = new Mock<IAuthenticationClient>(MockBehavior.Strict);
+ client.Setup(c => c.ProviderName).Returns("facebook");
+ client.Setup(c => c.VerifyAuthentication(context.Object)).Returns(new AuthenticationResult(true, "facebook", "123",
+ "super", null));
+
+ var anotherClient = new Mock<IAuthenticationClient>(MockBehavior.Strict);
+ anotherClient.Setup(c => c.ProviderName).Returns("twitter");
+ anotherClient.Setup(c => c.VerifyAuthentication(context.Object)).Returns(AuthenticationResult.Failed);
+
+ OAuthWebSecurity.RegisterClient(client.Object);
+ OAuthWebSecurity.RegisterClient(anotherClient.Object);
+
+ // Act
+ AuthenticationResult result = OAuthWebSecurity.VerifyAuthenticationCore(context.Object);
+
+ // Assert
+ Assert.False(result.IsSuccessful);
+ Assert.Equal("twitter", result.Provider);
+ }
+
+ [Fact]
+ public void VerifyAuthenticationFailIfNoProviderInQueryString()
+ {
+ // Arrange
+ var context = new Mock<HttpContextBase>();
+ context.Setup(c => c.Request.QueryString).Returns(new NameValueCollection());
+
+ var client = new Mock<IAuthenticationClient>(MockBehavior.Strict);
+ client.Setup(c => c.ProviderName).Returns("facebook");
+
+ var anotherClient = new Mock<IAuthenticationClient>(MockBehavior.Strict);
+ anotherClient.Setup(c => c.ProviderName).Returns("twitter");
+
+ OAuthWebSecurity.RegisterClient(client.Object);
+ OAuthWebSecurity.RegisterClient(anotherClient.Object);
+
+ // Act
+ AuthenticationResult result = OAuthWebSecurity.VerifyAuthenticationCore(context.Object);
+
+ // Assert
+ Assert.False(result.IsSuccessful);
+ Assert.Null(result.Provider);
+ }
+
+ [Fact]
+ public void LoginSetAuthenticationTicketIfSuccessful()
+ {
+ // Arrange
+ var cookies = new HttpCookieCollection();
+ var context = new Mock<HttpContextBase>();
+ context.Setup(c => c.Request.IsSecureConnection).Returns(true);
+ context.Setup(c => c.Response.Cookies).Returns(cookies);
+
+ var dataProvider = new Mock<IOpenAuthDataProvider>(MockBehavior.Strict);
+ dataProvider.Setup(p => p.GetUserNameFromOpenAuth("twitter", "12345")).Returns("hola");
+ OAuthWebSecurity.OAuthDataProvider = dataProvider.Object;
+
+ OAuthWebSecurity.RegisterOAuthClient(BuiltInOAuthClient.Twitter, "sdfdsfsd", "dfdsfdsf");
+
+ // Act
+ bool successful = OAuthWebSecurity.LoginCore(context.Object, "twitter", "12345", createPersistentCookie: false);
+
+ // Assert
+ Assert.True(successful);
+
+ Assert.Equal(1, cookies.Count);
+ HttpCookie addedCookie = cookies[0];
+
+ Assert.Equal(FormsAuthentication.FormsCookieName, addedCookie.Name);
+ Assert.True(addedCookie.HttpOnly);
+ Assert.Equal("/", addedCookie.Path);
+ Assert.False(addedCookie.Secure);
+ Assert.False(String.IsNullOrEmpty(addedCookie.Value));
+
+ FormsAuthenticationTicket ticket = FormsAuthentication.Decrypt(addedCookie.Value);
+ Assert.NotNull(ticket);
+ Assert.Equal(2, ticket.Version);
+ Assert.Equal("hola", ticket.Name);
+ Assert.Equal("OAuth", ticket.UserData);
+ Assert.False(ticket.IsPersistent);
+ }
+
+ [Fact]
+ public void LoginFailIfUserIsNotFound()
+ {
+ // Arrange
+ var context = new Mock<HttpContextBase>();
+ OAuthWebSecurity.RegisterOAuthClient(BuiltInOAuthClient.Twitter, "consumerKey", "consumerSecrte");
+
+ var dataProvider = new Mock<IOpenAuthDataProvider>();
+ dataProvider.Setup(p => p.GetUserNameFromOpenAuth("twitter", "12345")).Returns((string)null);
+ OAuthWebSecurity.OAuthDataProvider = dataProvider.Object;
+
+ // Act
+ bool successful = OAuthWebSecurity.LoginCore(context.Object, "twitter", "12345", createPersistentCookie: false);
+
+ // Assert
+ Assert.False(successful);
+ }
+
+ [Fact]
+ public void GetOAuthClientReturnsTheCorrectClient()
+ {
+ // Arrange
+ var client = new Mock<IAuthenticationClient>();
+ client.Setup(c => c.ProviderName).Returns("facebook");
+ OAuthWebSecurity.RegisterClient(client.Object);
+
+ var anotherClient = new Mock<IAuthenticationClient>();
+ anotherClient.Setup(c => c.ProviderName).Returns("hulu");
+ OAuthWebSecurity.RegisterClient(anotherClient.Object);
+
+ // Act
+ var expectedClient = OAuthWebSecurity.GetOAuthClient("facebook");
+
+ // Assert
+ Assert.Same(expectedClient, client.Object);
+ }
+
+ [Fact]
+ public void GetOAuthClientThrowsIfClientIsNotFound()
+ {
+ // Arrange
+ var client = new Mock<IAuthenticationClient>();
+ client.Setup(c => c.ProviderName).Returns("facebook");
+ OAuthWebSecurity.RegisterClient(client.Object);
+
+ var anotherClient = new Mock<IAuthenticationClient>();
+ anotherClient.Setup(c => c.ProviderName).Returns("hulu");
+ OAuthWebSecurity.RegisterClient(anotherClient.Object);
+
+ // Act & Assert
+ Assert.Throws<ArgumentException>(() => OAuthWebSecurity.GetOAuthClient("live"));
+ }
+
+ [Fact]
+ public void TryGetOAuthClientSucceeds()
+ {
+ // Arrange
+ var client = new Mock<IAuthenticationClient>();
+ client.Setup(c => c.ProviderName).Returns("facebook");
+ OAuthWebSecurity.RegisterClient(client.Object);
+
+ var anotherClient = new Mock<IAuthenticationClient>();
+ anotherClient.Setup(c => c.ProviderName).Returns("hulu");
+ OAuthWebSecurity.RegisterClient(anotherClient.Object);
+
+ // Act
+ IAuthenticationClient expectedClient;
+ bool result = OAuthWebSecurity.TryGetOAuthClient("facebook", out expectedClient);
+
+ // Assert
+ Assert.Same(expectedClient, client.Object);
+ Assert.True(result);
+ }
+
+ [Fact]
+ public void TryGetOAuthClientFail()
+ {
+ // Arrange
+ var client = new Mock<IAuthenticationClient>();
+ client.Setup(c => c.ProviderName).Returns("facebook");
+ OAuthWebSecurity.RegisterClient(client.Object);
+
+ var anotherClient = new Mock<IAuthenticationClient>();
+ anotherClient.Setup(c => c.ProviderName).Returns("hulu");
+ OAuthWebSecurity.RegisterClient(anotherClient.Object);
+
+ // Act
+ IAuthenticationClient expectedClient;
+ bool result = OAuthWebSecurity.TryGetOAuthClient("live", out expectedClient);
+
+ // Assert
+ Assert.Null(expectedClient);
+ Assert.False(result);
+ }
+
+ public void Dispose() {
+ OAuthWebSecurity.ClearProviders();
+ }
+ }
+}
diff --git a/test/Microsoft.Web.WebPages.OAuth.Test/PreAppStartCodeTest.cs b/test/Microsoft.Web.WebPages.OAuth.Test/PreAppStartCodeTest.cs
new file mode 100644
index 00000000..bd386c39
--- /dev/null
+++ b/test/Microsoft.Web.WebPages.OAuth.Test/PreAppStartCodeTest.cs
@@ -0,0 +1,12 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace Microsoft.Web.WebPages.OAuth.Test
+{
+ public class PreAppStartCodeTest
+ {
+
+ }
+}
diff --git a/test/Microsoft.Web.WebPages.OAuth.Test/Properties/AssemblyInfo.cs b/test/Microsoft.Web.WebPages.OAuth.Test/Properties/AssemblyInfo.cs
new file mode 100644
index 00000000..6f956b96
--- /dev/null
+++ b/test/Microsoft.Web.WebPages.OAuth.Test/Properties/AssemblyInfo.cs
@@ -0,0 +1,7 @@
+using System.Reflection;
+
+// 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("Microsoft.Web.DotNetOpenAuth.Test")]
+[assembly: AssemblyDescription("")] \ No newline at end of file
diff --git a/test/Microsoft.Web.WebPages.OAuth.Test/packages.config b/test/Microsoft.Web.WebPages.OAuth.Test/packages.config
new file mode 100644
index 00000000..27bf7a5b
--- /dev/null
+++ b/test/Microsoft.Web.WebPages.OAuth.Test/packages.config
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="utf-8"?>
+<packages>
+ <package id="DotNetOpenAuth.AspNet" version="4.0.0-beta2" />
+ <package id="DotNetOpenAuth.Core" version="4.0.0-beta2" />
+ <package id="DotNetOpenAuth.OAuth.Consumer" version="4.0.0-beta2" />
+ <package id="DotNetOpenAuth.OAuth.Core" version="4.0.0-beta2" />
+ <package id="DotNetOpenAuth.OpenId.Core" version="4.0.0-beta2" />
+ <package id="DotNetOpenAuth.OpenId.RelyingParty" version="4.0.0-beta2" />
+ <package id="Moq" version="4.0.10827" />
+ <package id="xunit" version="1.9.0.1566" />
+</packages> \ No newline at end of file
diff --git a/test/SPA.Test/Index.html b/test/SPA.Test/Index.html
new file mode 100644
index 00000000..996d602a
--- /dev/null
+++ b/test/SPA.Test/Index.html
@@ -0,0 +1,47 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+ <title>Unit tests for Upshot</title>
+ <meta http-equiv="X-UA-Compatible" content="IE=Edge" />
+
+ <link href="css/qunit.css" rel="stylesheet" type="text/css" />
+ <link href="css/tests.css" rel="stylesheet" type="text/css" />
+
+ <script src="_Scripts/qunit/qunit.js" type="text/javascript"></script>
+
+ <script src="_Scripts/jquery/jquery-1.6.2.js" type="text/javascript"></script>
+ <script src="_Scripts/jquery/jquery.customfunctions.js" type="text/javascript"></script>
+ <!-- jquery.ui requirements for upshot.dataview.js -->
+ <script src="_Scripts/jquery/jquery.ui.widget.js" type="text/javascript"></script>
+ <script src="_Scripts/jquery/jquery.ui.observable.js" type="text/javascript"></script>
+ <script src="_Scripts/jquery/jquery.ui.dataview.js" type="text/javascript"></script>
+
+ <script src="_Scripts/knockout/knockout-2.0.0.js" type="text/javascript"></script>
+
+ <script src="_Scripts/upshot/upshot.js" type="text/javascript"></script>
+ <!-- Knockout compat is loaded before jQueryUI so it's not the default -->
+ <script src="_Scripts/upshot/Upshot.Compat.Knockout.js" type="text/javascript"></script>
+ <script src="_Scripts/upshot/Upshot.Compat.jQueryUI.js" type="text/javascript"></script>
+ <script src="_Scripts/upshot/upshot.dataview.js" type="text/javascript"></script>
+
+ <!-- Prepare test bed -->
+ <script src="Scripts/TestSetup.js" type="text/javascript"></script>
+
+ <script src="upshot/Init.js" type="text/javascript"></script>
+</head>
+<body>
+ <h2 id="qunit-banner">
+ </h2>
+ <h1>Unit Tests</h1>
+ <div class="test-results">
+ <div id="qunit-testrunner-toolbar">
+ </div>
+ <h2 id="qunit-userAgent">
+ </h2>
+ <ol id="qunit-tests">
+ </ol>
+ <div id="qunit-fixture">
+ test markup, will be hidden</div>
+ </div>
+</body>
+</html> \ No newline at end of file
diff --git a/test/SPA.Test/Properties/AssemblyInfo.cs b/test/SPA.Test/Properties/AssemblyInfo.cs
new file mode 100644
index 00000000..b8c1d123
--- /dev/null
+++ b/test/SPA.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("SPA.Test")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("Microsoft IT")]
+[assembly: AssemblyProduct("SPA.Test")]
+[assembly: AssemblyCopyright("Copyright © Microsoft IT 2012")]
+[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("eec1a4da-c756-420c-8d83-c4a82fd490ab")]
+
+// 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 Revision and Build Numbers
+// by using the '*' as shown below:
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
diff --git a/test/SPA.Test/SPA.Test.csproj b/test/SPA.Test/SPA.Test.csproj
new file mode 100644
index 00000000..3edad23d
--- /dev/null
+++ b/test/SPA.Test/SPA.Test.csproj
@@ -0,0 +1,133 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory),Runtime.sln))\tools\WebStack.settings.targets" />
+ <PropertyGroup>
+ <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+ <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+ <ProductVersion>
+ </ProductVersion>
+ <SchemaVersion>2.0</SchemaVersion>
+ <ProjectGuid>{7B8601F8-8D1F-4B9C-8C20-772B673A2FA6}</ProjectGuid>
+ <ProjectTypeGuids>{349c5851-65df-11da-9384-00065b846f21};{fae04ec0-301f-11d3-bf4b-00c04f79efbc}</ProjectTypeGuids>
+ <OutputType>Library</OutputType>
+ <AppDesignerFolder>Properties</AppDesignerFolder>
+ <RootNamespace>SPA.Test</RootNamespace>
+ <AssemblyName>SPA.Test</AssemblyName>
+ <TargetFrameworkVersion>v4.0</TargetFrameworkVersion>
+ <UseIISExpress>false</UseIISExpress>
+ <SrcOutputPath>$(OutputPath)</SrcOutputPath>
+ <OutputPath>$(OutputPath)\Test\</OutputPath>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
+ <DebugSymbols>true</DebugSymbols>
+ <DebugType>full</DebugType>
+ <Optimize>false</Optimize>
+ <DefineConstants>DEBUG;TRACE</DefineConstants>
+ <ErrorReport>prompt</ErrorReport>
+ <WarningLevel>4</WarningLevel>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
+ <DebugType>pdbonly</DebugType>
+ <Optimize>true</Optimize>
+ <DefineConstants>TRACE</DefineConstants>
+ <ErrorReport>prompt</ErrorReport>
+ <WarningLevel>4</WarningLevel>
+ </PropertyGroup>
+ <ItemGroup>
+ <Reference Include="Microsoft.CSharp" />
+ <Reference Include="System.Web.DynamicData" />
+ <Reference Include="System.Web.Entity" />
+ <Reference Include="System.Web.ApplicationServices" />
+ <Reference Include="System" />
+ <Reference Include="System.Data" />
+ <Reference Include="System.Core" />
+ <Reference Include="System.Data.DataSetExtensions" />
+ <Reference Include="System.Web.Extensions" />
+ <Reference Include="System.Xml.Linq" />
+ <Reference Include="System.Drawing" />
+ <Reference Include="System.Web" />
+ <Reference Include="System.Xml" />
+ <Reference Include="System.Configuration" />
+ <Reference Include="System.Web.Services" />
+ <Reference Include="System.EnterpriseServices" />
+ </ItemGroup>
+ <ItemGroup>
+ <Content Include="css\qunit.css" />
+ <Content Include="css\tests.css" />
+ <Content Include="Index.html" />
+ <Content Include="Scripts\IntellisenseFix.js" />
+ <Content Include="Scripts\References.js" />
+ <Content Include="Scripts\TestSetup.js" />
+ <Content Include="upshot\ChangeTracking.tests.js" />
+ <Content Include="upshot\Consistency.tests.js" />
+ <Content Include="upshot\Core.tests.js" />
+ <Content Include="upshot\DataContext.tests.js" />
+ <Content Include="upshot\DataProvider.tests.js" />
+ <Content Include="upshot\Datasets.js" />
+ <Content Include="upshot\DataSource.Common.js" />
+ <Content Include="upshot\DataSource.Tests.js" />
+ <Content Include="upshot\Delete.Tests.js" />
+ <Content Include="upshot\EntitySet.tests.js" />
+ <Content Include="upshot\Init.js" />
+ <Content Include="upshot\jQuery.DataView.Tests.js" />
+ <Content Include="upshot\Mapping.tests.js" />
+ <Content Include="upshot\RecordSet.js" />
+ <Content Include="Web.config" />
+ <Content Include="Web.Debug.config">
+ <DependentUpon>Web.config</DependentUpon>
+ </Content>
+ <Content Include="Web.Release.config">
+ <DependentUpon>Web.config</DependentUpon>
+ </Content>
+ </ItemGroup>
+ <ItemGroup>
+ <Compile Include="Properties\AssemblyInfo.cs" />
+ </ItemGroup>
+ <ItemGroup>
+ <ProjectReference Include="..\..\src\SPA\SPA.csproj">
+ <Project>{1ACEF677-B6A0-4680-A076-7893DE176D6B}</Project>
+ <Name>SPA</Name>
+ </ProjectReference>
+ </ItemGroup>
+ <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
+ <Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v10.0\WebApplications\Microsoft.WebApplication.targets" />
+ <ProjectExtensions>
+ <VisualStudio>
+ <FlavorProperties GUID="{349c5851-65df-11da-9384-00065b846f21}">
+ <WebProjectProperties>
+ <UseIIS>False</UseIIS>
+ <AutoAssignPort>True</AutoAssignPort>
+ <DevelopmentServerPort>34597</DevelopmentServerPort>
+ <DevelopmentServerVPath>/</DevelopmentServerVPath>
+ <IISUrl>
+ </IISUrl>
+ <NTLMAuthentication>False</NTLMAuthentication>
+ <UseCustomServer>False</UseCustomServer>
+ <CustomServerUrl>
+ </CustomServerUrl>
+ <SaveServerSettingsInUserFile>False</SaveServerSettingsInUserFile>
+ </WebProjectProperties>
+ </FlavorProperties>
+ </VisualStudio>
+ </ProjectExtensions>
+ <Target Name="AfterBuild">
+ <ItemGroup>
+ <SpaFiles Include="$(SrcOutputPath)\upshot.js" />
+ <SpaFiles Include="$(SrcOutputPath)\Upshot.Compat.Knockout.js" />
+ <SpaFiles Include="$(SrcOutputPath)\Upshot.Compat.jQueryUI.js" />
+ <SpaFiles Include="$(SrcOutputPath)\upshot.dataview.js" />
+ </ItemGroup>
+ <Copy SourceFiles="@(SpaFiles)" DestinationFolder="_Scripts\upshot" />
+ </Target>
+ <Target Name="AfterClean">
+ <ItemGroup>
+ <SpaFiles Include="_Scripts\upshot\*" />
+ </ItemGroup>
+ <Delete Files="@(SpaFiles)" />
+ </Target>
+ <!-- To modify your build process, add your task inside one of the targets below and uncomment it.
+ Other similar extension points exist, see Microsoft.Common.targets.
+ <Target Name="BeforeBuild">
+ </Target>
+ -->
+</Project> \ No newline at end of file
diff --git a/test/SPA.Test/Scripts/IntellisenseFix.js b/test/SPA.Test/Scripts/IntellisenseFix.js
new file mode 100644
index 00000000..8527233e
--- /dev/null
+++ b/test/SPA.Test/Scripts/IntellisenseFix.js
@@ -0,0 +1,32 @@
+// These changes remove Intellisense "Function expected" or "Object expected" messages due to
+// code that relies on running in the browser. Simply include a reference in
+// before QUnit or jQuery like so: /// <reference path="{path}/IntellisenseFix.js" />
+// There is no need to include this in the actual html page
+
+//QUnit fixes
+(function () {
+ var test = document.getElementById("");
+ if (test && !test.style) {
+ var oldGet = document.getElementById;
+ document.getElementById = function (id) {
+ var el = oldGet(id);
+ el.style = el.getAttribute("style");
+ return el;
+ };
+ }
+
+ if (window.location && !window.location.search) {
+ window.location.search = "";
+ }
+})();
+
+//jQuery fixes 1.6.1
+(function () {
+ if (!document.documentElement.childNodes[0]) {
+ document.documentElement.childNodes = [{ nodeType: null}];
+ }
+
+ if (!location.href) {
+ location.href = "";
+ }
+})(); \ No newline at end of file
diff --git a/test/SPA.Test/Scripts/References.js b/test/SPA.Test/Scripts/References.js
new file mode 100644
index 00000000..8aa83134
--- /dev/null
+++ b/test/SPA.Test/Scripts/References.js
@@ -0,0 +1,20 @@
+/// <reference path="IntellisenseFix.js" />
+/// <reference path="qunit/qunit.js" />
+/// <reference path="SharedScripts/jquery-1.6.2.js" />
+/// <reference path="SharedScripts/jquery.tmpl.js" />
+/// <reference path="SharedScripts/jquery.ui.observable.js" />
+/// <reference path="SharedScripts/jquery.datalink.js" />
+/// <reference path="SharedScripts/jquery.customfunctions.js" />
+/// <reference path="UpshotScripts/upshot.js" />
+/// <reference path="UpshotScripts/Upshot.Compat.Knockout.js" />
+/// <reference path="UpshotScripts/Upshot.Compat.jQueryUI.js" />
+/// <reference path="UpshotScripts/Upshot.Compat.JsViews.js" />
+/// <reference path="SharedScripts/jquery-ui.js" />
+/// <reference path="SharedScripts/jquery.render.js" />
+/// <reference path="SharedScripts/jquery.views.js" />
+/// <reference path="SharedScripts/jquery.list.js" />
+/// <reference path="SharedScripts/knockout-2.0.0.debug.js" />
+
+/// <reference path="TestSetup.js" />
+
+// This file enables VS IntelliSense in JavaScript, otherwise it can be removed \ No newline at end of file
diff --git a/test/SPA.Test/Scripts/TestSetup.js b/test/SPA.Test/Scripts/TestSetup.js
new file mode 100644
index 00000000..37799b34
--- /dev/null
+++ b/test/SPA.Test/Scripts/TestSetup.js
@@ -0,0 +1,320 @@
+/// <reference path="../../Scripts/References.js" />
+
+if (window.sessionStorage) {
+ window.sessionStorage.clear();
+}
+module("DataSource Setup");
+
+var defaultTestTimeout = 10000,
+ retryLimit = 10;
+
+QUnit.config.testTimeout = defaultTestTimeout;
+
+var testHelper = {
+ isChrome: /chrome/.test(navigator.userAgent.toLowerCase()),
+ isFirefox: /mozilla/.test(navigator.userAgent.toLowerCase()) && (!/(compatible|webkit)/.test(navigator.userAgent.toLowerCase())),
+ initService: function (url, typeOrOptions) {
+ QUnit.ok(true, "start Init service:" + url + ", time: " + new Date().toLocaleString());
+ QUnit.config.testTimeout = 120 * 1000;
+ stop();
+ var beat = setInterval(function () {
+ if (window.TestSwarm && window.TestSwarm.heartbeat) {
+ window.TestSwarm.heartbeat();
+ }
+ }, 1000);
+ testHelper.setCookie("dbi", null, -1);
+
+ var options;
+ if (typeof(typeOrOptions) === "string") {
+ options = { type: typeOrOptions };
+ } else {
+ options = typeOrOptions;
+ }
+
+ testHelper.serviceCall(url, options, 0);
+ },
+ serviceCall: function (url, options, retried, beat) {
+ jQuery.ajax(url, options).then(function () {
+ QUnit.config.testTimeout = defaultTestTimeout;
+ clearInterval(beat);
+ QUnit.ok(true, "Service request succeeded, tried: " + (retried + 1) + " times, timestamp:" + new Date().toLocaleString());
+ start();
+ }, function () {
+ QUnit.ok(true, "Service request failed, retrying " + (retryLimit - retried - 1) + " more times, timestamp:" + new Date().toLocaleString());
+ setTimeout(function () {
+ retried++;
+ if (retried < retryLimit) {
+ testHelper.serviceCall(url, options, retried);
+ } else {
+ // Fail the rest quickly
+ QUnit.ok(false, "Service call to " + url + " failed");
+ QUnit.config.testTimeout = 100;
+ clearInterval(beat);
+ start();
+ }
+ }, 1000);
+ })
+ },
+
+ setCookie: function (name, value, days) {
+ if (days) { var date = new Date(); date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000)); var expires = "; expires=" + date.toGMTString(); } else var expires = ""; document.cookie = name + "=" + value + expires + "; path=/";
+ },
+
+ startOnPageSetupComplete: function (url) {
+ stop();
+ jQuery("#testFrame").one("load", function () {
+ // make $ reference the jQuery in the iframe, intentionally global for tests to use
+ var testFrame = window.frames[0];
+ $ = testFrame.jQuery;
+
+ //Wait for the page to trigger complete
+ $(testFrame).one("PageSetupComplete", function () {
+ start()
+ });
+ });
+ jQuery("#testFrame").attr("src", url);
+ },
+ startOnPageLoad: function (url, cond) {
+ stop();
+ jQuery("#testFrame").one("load", function () {
+ // make $ reference the jQuery in the iframe, intentionally global for tests to use
+ var testFrame = window.frames[0];
+ $ = testFrame.jQuery;
+ testFrame.alert = function (error) { ok(false, "'alert(" + error + ")' called, failing test"); };
+ if (cond) {
+ var checkCond = function () {
+ if (cond()) {
+ start();
+ } else {
+ setTimeout(function () { checkCond(); }, 200);
+ }
+ }
+ checkCond();
+ } else {
+ start();
+ }
+ });
+ jQuery("#testFrame").attr("src", url);
+ },
+ wrapFunctions: function (functionNames, triggerName) {
+ // Allow string or array of strings to be passed
+ functionNames = $.isArray(functionNames) ? functionNames : [functionNames];
+
+ $.each(functionNames, function (index, functionName) {
+ // Only allow one wrapping per function
+ if ($.fn[functionName].__oldFunction__) {
+ testHelper.unwrapFunctions([functionName]);
+ }
+ var newFunction = function () {
+ // Create trigger arguments with function's name that was wrapped
+ var returnedArgs = [functionName].concat(Array.prototype.slice.call(arguments, 0)),
+ returnValue = newFunction.__oldFunction__.apply(this, arguments);
+ // trigger after applying to allow state to be checked in callback
+ $(this).trigger(triggerName, returnedArgs);
+ // return the results so wrapped function behaves the same
+ return returnValue;
+ };
+ // Store the new function on the old one
+ newFunction.__oldFunction__ = $.fn[functionName];
+ $.fn[functionName] = newFunction;
+ });
+ return function () {
+ //do cleanup
+ testHelper.unwrapFunctions(functionNames);
+ };
+ },
+ unwrapFunctions: function (functionNames) {
+ $.each(functionNames, function (index, name) {
+ $.fn[name] = $.fn[name].__oldFunction__;
+ });
+ },
+ curryStartOnPageLoad: function (url) {
+ // Return a function with url already set, helpful for passing a callback to a module
+ return function () {
+ stop();
+ jQuery("#testFrame").one("load", function () {
+ // make $ reference the jQuery in the iframe, intentionally global for tests to use
+ window.$ = window.frames[0].jQuery;
+ // TODO: Fix this workaround for page load being too slow to load a new one every test function
+ // especially slow when debugging
+ setTimeout(QUnit.start, 100);
+ });
+ jQuery("#testFrame").attr("src", url);
+ }
+ },
+ curryPost: function (url) {
+ return function () {
+ QUnit.stop();
+ //REVIEW: This should be an implementation detail of DataSource / DomainSerivceProxy
+ jQuery.post(url, function () {
+ QUnit.start();
+ }, "json");
+ }
+ },
+ setLatency: function (url, queryDelay, cudDelay, success) {
+ jQuery.ajax({
+ type: "POST",
+ url: url + "/SetLatency",
+ data: '{"queryDelay":' + queryDelay + ',"cudDelay":' + cudDelay + '}',
+ dataType: "json",
+ contentType: "application/json",
+ success: function () {
+ success();
+ },
+ error: function () {
+ ok(false, "setLatency request failed");
+ start();
+ }
+ });
+ },
+ wrapOrAddFunction: function (baseObject, functionName, options) {
+ // This function either:
+ // 1) replaces the existing function making callbacks before and after the function would have been invoked
+ // 2) creates the function making callbacks before and after the function would have been invoked
+ // If the function existed it will be called as if it were not replaced
+ // After the function is invoked once it will be reverted back to its original state unless
+ // revertToOriginal: false is given via the options parameter
+ // The original function is the return value which allows for the function to be reverted at any time (not after a single callback as usual)
+
+ var before = options.before,
+ after = options.after,
+ revertToOriginal = options.revertToOriginal !== false, // Default to true when unsupplied
+ backupOfOriginalFunction;
+
+ // If function exists back it up
+ if (baseObject[functionName]) {
+ backupOfOriginalFunction = baseObject[functionName];
+ }
+
+ // Overwrite or create function
+ baseObject[functionName] = function () {
+ // Revert to original method, revertToOriginal is true by default
+ if (revertToOriginal) {
+ if (backupOfOriginalFunction) {
+ baseObject[functionName] = backupOfOriginalFunction;
+ } else {
+ // If there was no original function remove the one added by wrapOrAddFunction
+ delete baseObject[functionName];
+ }
+ }
+
+ if (before) {
+ before.apply(this, Array.prototype.slice.call(arguments));
+ }
+
+ if (backupOfOriginalFunction) {
+ backupOfOriginalFunction.apply(this, Array.prototype.slice.call(arguments));
+ }
+
+ if (after) {
+ after.apply(this, Array.prototype.slice.call(arguments));
+ }
+ }
+
+ // Allows one to revert to original state in their own code when revertToOriginal is false
+ return backupOfOriginalFunction;
+ },
+ whenCondition: function (callback, interval, timeout) {
+ var retry,
+ deferred = $.Deferred();
+ interval = interval || 500;
+ timeout = timeout || QUnit.config.testTimeout;
+ retry = timeout / interval;
+
+ if (!callback()) {
+ var pollCallback = function () {
+ if (!callback()) {
+ --retry;
+ if (retry > 0) {
+ setTimeout(pollCallback, interval);
+ } else {
+ deferred.reject();
+ }
+ } else {
+ deferred.resolve();
+ }
+ };
+ setTimeout(pollCallback, interval);
+ } else {
+ deferred.resolve();
+ }
+
+ return deferred.promise();
+ },
+ startOnCondition: function (callback, interval, timeout) {
+ var retry;
+ interval = interval || 500;
+ timeout = timeout || QUnit.config.testTimeout;
+ retry = timeout / interval;
+ stop();
+ testHelper.whenCondition(callback, interval)
+ .then(start);
+ },
+ changeValueAndWait: function (selector, value, delay) {
+ stop();
+ var input = $(selector);
+ if (input.length !== 1) {
+ throw "Only support one input";
+ }
+ var test = function () {
+ input.unbind("change", test);
+ setTimeout(function () { start(); }, delay ? delay : 100);
+ }
+ input.one("change", test).focusin().val(value).focusout().trigger("change");
+ },
+ // this will allow one-time ajax mock.
+ mockAjaxOnce: function (url, result, statusText, error) {
+ if ($.ajax._simulatedurl) {
+ throw "cannot simulate ajax concurrently (prev=" + $.ajax._simulatedurl + ")";
+ }
+ var $ajax = $.ajax;
+ $.ajax = function (settings) {
+ if (settings.url.indexOf(url) == 0) {
+ // revert $.ajax to orginal
+ $.ajax = $ajax;
+ var deferred = $.Deferred();
+ setTimeout(function () {
+ // this follows $.ajax().fail() and $.ajax().done() signature
+ if (statusText) {
+ if (settings.error) {
+ settings.error.apply(null, [{ responseText: statusText, status: 200 }, statusText, error]);
+ }
+ deferred.reject(undefined, statusText, error);
+ } else {
+ if (settings.type === "POST") {
+ var data = JSON.parse(settings.data);
+ var ret = result || {
+ SubmitChangesResult: [{ Entity: data.changeSet[0].Entity}]
+ };
+ if (settings.success) {
+ settings.success.apply(null, [ret, "statusText", { status: 200}]);
+ }
+ deferred.resolve(ret);
+ } else {
+ var ret = result || {
+ EmptyResult: {
+ RootResults: [],
+ Metadata: [{ type: "dummy"}]
+ }
+ };
+ if (settings.success) {
+ settings.success.apply(null, [ret]);
+ }
+ deferred.resolve(ret);
+ }
+ }
+ }, 10);
+ return deferred.promise();
+ } else {
+ return $ajax(settings);
+ }
+ }
+ $.ajax._simulatedurl = url;
+ $.ajax._$ajax = $ajax;
+ },
+ unmockAjax: function () {
+ if ($.ajax._$ajax) {
+ $.ajax = $.ajax._$ajax;
+ }
+ }
+}; \ No newline at end of file
diff --git a/test/SPA.Test/Web.Debug.config b/test/SPA.Test/Web.Debug.config
new file mode 100644
index 00000000..2c6dd51a
--- /dev/null
+++ b/test/SPA.Test/Web.Debug.config
@@ -0,0 +1,30 @@
+<?xml version="1.0"?>
+
+<!-- For more information on using web.config transformation visit http://go.microsoft.com/fwlink/?LinkId=125889 -->
+
+<configuration xmlns:xdt="http://schemas.microsoft.com/XML-Document-Transform">
+ <!--
+ In the example below, the "SetAttributes" transform will change the value of
+ "connectionString" to use "ReleaseSQLServer" only when the "Match" locator
+ finds an atrribute "name" that has a value of "MyDB".
+
+ <connectionStrings>
+ <add name="MyDB"
+ connectionString="Data Source=ReleaseSQLServer;Initial Catalog=MyReleaseDB;Integrated Security=True"
+ xdt:Transform="SetAttributes" xdt:Locator="Match(name)"/>
+ </connectionStrings>
+ -->
+ <system.web>
+ <!--
+ In the example below, the "Replace" transform will replace the entire
+ <customErrors> section of your web.config file.
+ Note that because there is only one customErrors section under the
+ <system.web> node, there is no need to use the "xdt:Locator" attribute.
+
+ <customErrors defaultRedirect="GenericError.htm"
+ mode="RemoteOnly" xdt:Transform="Replace">
+ <error statusCode="500" redirect="InternalError.htm"/>
+ </customErrors>
+ -->
+ </system.web>
+</configuration> \ No newline at end of file
diff --git a/test/SPA.Test/Web.Release.config b/test/SPA.Test/Web.Release.config
new file mode 100644
index 00000000..4122d79b
--- /dev/null
+++ b/test/SPA.Test/Web.Release.config
@@ -0,0 +1,31 @@
+<?xml version="1.0"?>
+
+<!-- For more information on using web.config transformation visit http://go.microsoft.com/fwlink/?LinkId=125889 -->
+
+<configuration xmlns:xdt="http://schemas.microsoft.com/XML-Document-Transform">
+ <!--
+ In the example below, the "SetAttributes" transform will change the value of
+ "connectionString" to use "ReleaseSQLServer" only when the "Match" locator
+ finds an atrribute "name" that has a value of "MyDB".
+
+ <connectionStrings>
+ <add name="MyDB"
+ connectionString="Data Source=ReleaseSQLServer;Initial Catalog=MyReleaseDB;Integrated Security=True"
+ xdt:Transform="SetAttributes" xdt:Locator="Match(name)"/>
+ </connectionStrings>
+ -->
+ <system.web>
+ <compilation xdt:Transform="RemoveAttributes(debug)" />
+ <!--
+ In the example below, the "Replace" transform will replace the entire
+ <customErrors> section of your web.config file.
+ Note that because there is only one customErrors section under the
+ <system.web> node, there is no need to use the "xdt:Locator" attribute.
+
+ <customErrors defaultRedirect="GenericError.htm"
+ mode="RemoteOnly" xdt:Transform="Replace">
+ <error statusCode="500" redirect="InternalError.htm"/>
+ </customErrors>
+ -->
+ </system.web>
+</configuration> \ No newline at end of file
diff --git a/test/SPA.Test/Web.config b/test/SPA.Test/Web.config
new file mode 100644
index 00000000..ea5e4d6e
--- /dev/null
+++ b/test/SPA.Test/Web.config
@@ -0,0 +1,13 @@
+<?xml version="1.0"?>
+
+<!--
+ For more information on how to configure your ASP.NET application, please visit
+ http://go.microsoft.com/fwlink/?LinkId=169433
+ -->
+
+<configuration>
+ <system.web>
+ <compilation debug="true" targetFramework="4.0" />
+ </system.web>
+
+</configuration>
diff --git a/test/SPA.Test/css/qunit.css b/test/SPA.Test/css/qunit.css
new file mode 100644
index 00000000..e114ea06
--- /dev/null
+++ b/test/SPA.Test/css/qunit.css
@@ -0,0 +1,226 @@
+/**
+ * QUnit 1.2.0pre - A JavaScript Unit Testing Framework
+ *
+ * http://docs.jquery.com/QUnit
+ *
+ * Copyright (c) 2011 John Resig, Jörn Zaefferer
+ * Dual licensed under the MIT (MIT-LICENSE.txt)
+ * or GPL (GPL-LICENSE.txt) licenses.
+ */
+
+/** Font Family and Sizes */
+
+#qunit-tests, #qunit-header, #qunit-banner, #qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult {
+ font-family: "Helvetica Neue Light", "HelveticaNeue-Light", "Helvetica Neue", Calibri, Helvetica, Arial, sans-serif;
+}
+
+#qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult, #qunit-tests li { font-size: small; }
+#qunit-tests { font-size: smaller; }
+
+
+/** Resets */
+
+#qunit-tests, #qunit-tests ol, #qunit-header, #qunit-banner, #qunit-userAgent, #qunit-testresult {
+ margin: 0;
+ padding: 0;
+}
+
+
+/** Header */
+
+#qunit-header {
+ padding: 0.5em 0 0.5em 1em;
+
+ color: #8699a4;
+ background-color: #0d3349;
+
+ font-size: 1.5em;
+ line-height: 1em;
+ font-weight: normal;
+
+ border-radius: 15px 15px 0 0;
+ -moz-border-radius: 15px 15px 0 0;
+ -webkit-border-top-right-radius: 15px;
+ -webkit-border-top-left-radius: 15px;
+}
+
+#qunit-header a {
+ text-decoration: none;
+ color: #c2ccd1;
+}
+
+#qunit-header a:hover,
+#qunit-header a:focus {
+ color: #fff;
+}
+
+#qunit-banner {
+ height: 5px;
+}
+
+#qunit-testrunner-toolbar {
+ padding: 0.5em 0 0.5em 2em;
+ color: #5E740B;
+ background-color: #eee;
+}
+
+#qunit-userAgent {
+ padding: 0.5em 0 0.5em 2.5em;
+ background-color: #2b81af;
+ color: #fff;
+ text-shadow: rgba(0, 0, 0, 0.5) 2px 2px 1px;
+}
+
+
+/** Tests: Pass/Fail */
+
+#qunit-tests {
+ list-style-position: inside;
+}
+
+#qunit-tests li {
+ padding: 0.4em 0.5em 0.4em 2.5em;
+ border-bottom: 1px solid #fff;
+ list-style-position: inside;
+}
+
+#qunit-tests.hidepass li.pass, #qunit-tests.hidepass li.running {
+ display: none;
+}
+
+#qunit-tests li strong {
+ cursor: pointer;
+}
+
+#qunit-tests li a {
+ padding: 0.5em;
+ color: #c2ccd1;
+ text-decoration: none;
+}
+#qunit-tests li a:hover,
+#qunit-tests li a:focus {
+ color: #000;
+}
+
+#qunit-tests ol {
+ margin-top: 0.5em;
+ padding: 0.5em;
+
+ background-color: #fff;
+
+ border-radius: 15px;
+ -moz-border-radius: 15px;
+ -webkit-border-radius: 15px;
+
+ box-shadow: inset 0px 2px 13px #999;
+ -moz-box-shadow: inset 0px 2px 13px #999;
+ -webkit-box-shadow: inset 0px 2px 13px #999;
+}
+
+#qunit-tests table {
+ border-collapse: collapse;
+ margin-top: .2em;
+}
+
+#qunit-tests th {
+ text-align: right;
+ vertical-align: top;
+ padding: 0 .5em 0 0;
+}
+
+#qunit-tests td {
+ vertical-align: top;
+}
+
+#qunit-tests pre {
+ margin: 0;
+ white-space: pre-wrap;
+ word-wrap: break-word;
+}
+
+#qunit-tests del {
+ background-color: #e0f2be;
+ color: #374e0c;
+ text-decoration: none;
+}
+
+#qunit-tests ins {
+ background-color: #ffcaca;
+ color: #500;
+ text-decoration: none;
+}
+
+/*** Test Counts */
+
+#qunit-tests b.counts { color: black; }
+#qunit-tests b.passed { color: #5E740B; }
+#qunit-tests b.failed { color: #710909; }
+
+#qunit-tests li li {
+ margin: 0.5em;
+ padding: 0.4em 0.5em 0.4em 0.5em;
+ background-color: #fff;
+ border-bottom: none;
+ list-style-position: inside;
+}
+
+/*** Passing Styles */
+
+#qunit-tests li li.pass {
+ color: #5E740B;
+ background-color: #fff;
+ border-left: 26px solid #C6E746;
+}
+
+#qunit-tests .pass { color: #528CE0; background-color: #D2E0E6; }
+#qunit-tests .pass .test-name { color: #366097; }
+
+#qunit-tests .pass .test-actual,
+#qunit-tests .pass .test-expected { color: #999999; }
+
+#qunit-banner.qunit-pass { background-color: #C6E746; }
+
+/*** Failing Styles */
+
+#qunit-tests li li.fail {
+ color: #710909;
+ background-color: #fff;
+ border-left: 26px solid #EE5757;
+ white-space: pre;
+}
+
+#qunit-tests > li:last-child {
+ border-radius: 0 0 15px 15px;
+ -moz-border-radius: 0 0 15px 15px;
+ -webkit-border-bottom-right-radius: 15px;
+ -webkit-border-bottom-left-radius: 15px;
+}
+
+#qunit-tests .fail { color: #000000; background-color: #EE5757; }
+#qunit-tests .fail .test-name,
+#qunit-tests .fail .module-name { color: #000000; }
+
+#qunit-tests .fail .test-actual { color: #EE5757; }
+#qunit-tests .fail .test-expected { color: green; }
+
+#qunit-banner.qunit-fail { background-color: #EE5757; }
+
+
+/** Result */
+
+#qunit-testresult {
+ padding: 0.5em 0.5em 0.5em 2.5em;
+
+ color: #2b81af;
+ background-color: #D2E0E6;
+
+ border-bottom: 1px solid white;
+}
+
+/** Fixture */
+
+#qunit-fixture {
+ position: absolute;
+ top: -10000px;
+ left: -10000px;
+}
diff --git a/test/SPA.Test/css/tests.css b/test/SPA.Test/css/tests.css
new file mode 100644
index 00000000..cfd317c9
--- /dev/null
+++ b/test/SPA.Test/css/tests.css
@@ -0,0 +1,15 @@
+body
+{
+ padding: 0px;
+ margin: 0px;
+}
+h1
+{
+ margin: 5px;
+}
+div.test-results
+{
+ width: 500px;
+ height: 700px;
+ margin:5px;
+}
diff --git a/test/SPA.Test/upshot/ChangeTracking.tests.js b/test/SPA.Test/upshot/ChangeTracking.tests.js
new file mode 100644
index 00000000..aacafce7
--- /dev/null
+++ b/test/SPA.Test/upshot/ChangeTracking.tests.js
@@ -0,0 +1,348 @@
+/// <reference path="../Scripts/References.js" />
+
+(function (upshot, ko, undefined) {
+
+ module("ChangeTracking");
+
+ var observability = upshot.observability;
+
+ test("Explicit commit multiple property edits", 5, function () {
+ var submitCount = 0;
+ var propertyChangedCount = 0;
+
+ var dc = createTestContext(function () {
+ submitCount++;
+ }, true);
+
+ var entitySet = dc.getEntitySet("Product");
+ var prod = entitySet.getEntities()[0];
+ var state = entitySet.getEntityState(prod);
+ equal(state, upshot.EntityState.Unmodified);
+
+ entitySet.bind("propertyChanged", function () {
+ propertyChangedCount++;
+ });
+
+ $.observable(prod).property("Name", "xyz");
+ $.observable(prod).property("Name", "foo");
+ $.observable(prod).property("Price", 9.99);
+
+ equal(propertyChangedCount, 3);
+
+ state = entitySet.getEntityState(prod);
+ equal(state, upshot.EntityState.ClientUpdated);
+
+ equal(submitCount, 0);
+
+ dc.commitChanges();
+
+ equal(submitCount, 1);
+ });
+
+ asyncTest("Implicit commit multiple property edits", 5, function () {
+ var submitCount = 0;
+ var propertyChangedCount = 0;
+
+ var dc = createTestContext(function () {
+ submitCount++;
+ }, false);
+
+ var entitySet = dc.getEntitySet("Product");
+ var prod = entitySet.getEntities()[0];
+ var state = entitySet.getEntityState(prod);
+ equal(state, upshot.EntityState.Unmodified);
+
+ entitySet.bind("propertyChanged", function () {
+ propertyChangedCount++;
+ });
+
+ $.observable(prod).property("Name", "xyz");
+ $.observable(prod).property("Name", "foo");
+ $.observable(prod).property("Price", 9.99);
+
+ // before the timeout has expired we shouldn't have committed anything
+ equal(submitCount, 0);
+
+ equal(propertyChangedCount, 3);
+
+ state = entitySet.getEntityState(prod);
+ equal(state, upshot.EntityState.ClientUpdated);
+
+ // Here we queue the test verification and start so that it runs
+ // AFTER the queued commit
+ setTimeout(function () {
+ equal(submitCount, 1);
+ start();
+ }, 0);
+
+ });
+
+ asyncTest("Implicit commit multiple array operations", 5, function () {
+ var submitCount = 0;
+
+ var dc = createTestContext(function (options, editedEntities) {
+ submitCount++;
+ equal(editedEntities.length, 2);
+ }, false);
+
+ var entitySet = dc.getEntitySet("Product");
+ var prod = entitySet.getEntities()[0];
+
+ // do an insert
+ var newProd = {
+ ID: 2,
+ Name: "Frish Gnarbles",
+ Category: "Snacks",
+ Price: 7.99
+ };
+ $.observable(entitySet.getEntities()).insert(newProd);
+ var state = entitySet.getEntityState(newProd);
+ equal(state, upshot.EntityState.ClientAdded);
+
+ // do a delete
+ entitySet.deleteEntity(prod);
+ state = entitySet.getEntityState(prod);
+ equal(state, upshot.EntityState.ClientDeleted);
+
+ // before the timeout has expired we shouldn't have committed anything
+ equal(submitCount, 0);
+
+ // Here we queue the test verification and start so that it runs
+ // AFTER the queued commit
+ setTimeout(function () {
+ equal(submitCount, 1);
+ start();
+ }, 0);
+ });
+
+/* TODO: We forced managed associations on for Dev11 beta, since unmanaged associations are broken.
+ test("Nested entities can be added to an entity, and navigation properties are untracked", 16, function () {
+ try {
+ upshot.observability.configuration = observability.knockout;
+
+ var manageAssociations = false;
+ var dc = createKoTestContext(function () { }, false, manageAssociations);
+
+ var orders = dc.getEntitySet("Order"),
+ orderDetails = dc.getEntitySet("OrderDetail"),
+ order = orders.getEntities()()[0],
+ order2 = orders.getEntities()()[1],
+ orderDetail = orderDetails.getEntities()()[0];
+
+ order.OrderDetails.remove(orderDetail);
+ equal(1, order.OrderDetails().length, "There should only be a single order detail");
+ equal(upshot.EntityState.Unmodified, orders.getEntityState(order), "The order should not be modified");
+ equal(upshot.EntityState.Unmodified, orderDetails.getEntityState(orderDetail), "The order detail should not be modified");
+
+ order.OrderDetails.push(orderDetail);
+ equal(2, order.OrderDetails().length, "There should be two order details");
+ equal(upshot.EntityState.Unmodified, orders.getEntityState(order), "The order should not be modified");
+ equal(upshot.EntityState.Unmodified, orderDetails.getEntityState(orderDetail), "The order detail should not be modified");
+
+ orderDetail.Order(order2);
+ equal(orderDetail.OrderId(), order2.Id(), "Ids should be equal");
+ equal(upshot.EntityState.Unmodified, orders.getEntityState(order2), "The order should not be modified");
+ equal(upshot.EntityState.ClientUpdated, orderDetails.getEntityState(orderDetail), "The order detail should not be modified");
+ equal(true, orderDetails.isUpdated(orderDetail, "OrderId"), "The OrderId should be modified");
+ equal(false, orderDetails.isUpdated(orderDetail, "Order"), "The Order should not be tracked");
+
+ var properties = [];
+ $.each(observability.knockout.unmap(orderDetail, "OrderDetail"), function (key, value) {
+ properties.push(key);
+ equal(ko.utils.unwrapObservable(orderDetail[key]), value, "Properties should be equal");
+ });
+ equal(properties.length, 4, "The should be 4 serialized properties");
+
+ } finally {
+ upshot.observability.configuration = observability.jquery;
+ }
+ });
+*/
+
+ test("Nested entities can be added to an entity, and navigation properties are computed", 39, function () {
+ try {
+ upshot.observability.configuration = observability.knockout;
+
+ var dc = createKoTestContext(function () { }, false);
+
+ var orders = dc.getEntitySet("Order"),
+ orderDetails = dc.getEntitySet("OrderDetail"),
+ order = orders.getEntities()()[0],
+ order2 = orders.getEntities()()[1],
+ orderDetail = orderDetails.getEntities()()[0];
+
+ ok(order.OrderDetails.indexOf(orderDetail) >= 0 && order2.OrderDetails.indexOf(orderDetail) < 0 && orderDetail.Order() === order, "The order detail is a child of order1");
+ equal(orders.getEntityState(order), upshot.EntityState.Unmodified, "order1 should not be modified");
+ equal(orders.getEntityState(order2), upshot.EntityState.Unmodified, "order2 should not be modified");
+ equal(orderDetails.getEntityState(orderDetail), upshot.EntityState.Unmodified, "The order detail should not be modified");
+ equal(orderDetails.isUpdated(orderDetail, "OrderId"), false, "The OrderId should not be modified");
+ equal(orderDetails.isUpdated(orderDetail, "Order"), false, "The Order should not be tracked");
+
+ orderDetail.OrderId(order2.Id());
+ ok(order2.OrderDetails.indexOf(orderDetail) >= 0 && order.OrderDetails.indexOf(orderDetail) < 0 && orderDetail.Order() === order2, "The order detail is a child of order2");
+ equal(orders.getEntityState(order), upshot.EntityState.Unmodified, "order1 should not be modified");
+ equal(orders.getEntityState(order2), upshot.EntityState.Unmodified, "order2 should not be modified");
+ equal(orderDetails.getEntityState(orderDetail), upshot.EntityState.ClientUpdated, "The order detail should be ClientUpdated");
+ equal(orderDetails.isUpdated(orderDetail, "OrderId"), true, "The OrderId should be modified");
+ equal(orderDetails.isUpdated(orderDetail, "Order"), false, "The Order should not be tracked");
+
+ orderDetails.revertUpdates(orderDetail);
+ ok(order.OrderDetails.indexOf(orderDetail) >= 0 && order2.OrderDetails.indexOf(orderDetail) < 0 && orderDetail.Order() === order, "The order detail is a child of order1");
+ equal(orders.getEntityState(order), upshot.EntityState.Unmodified, "order1 should not be modified");
+ equal(orders.getEntityState(order2), upshot.EntityState.Unmodified, "order2 should not be modified");
+ equal(orderDetails.getEntityState(orderDetail), upshot.EntityState.Unmodified, "The order detail should not be modified");
+ equal(orderDetails.isUpdated(orderDetail, "OrderId"), false, "The OrderId should not be modified");
+ equal(orderDetails.isUpdated(orderDetail, "Order"), false, "The Order should not be tracked");
+
+ orderDetail.Order(order2);
+ ok(orderDetail.OrderId() === order2.Id(), "Ids should be equal");
+ equal(orders.getEntityState(order2), upshot.EntityState.Unmodified, "The order should not be modified");
+ equal(orderDetails.getEntityState(orderDetail), upshot.EntityState.ClientUpdated, "The order detail should be ClientUpdated");
+ equal(orderDetails.isUpdated(orderDetail, "OrderId"), true, "The OrderId should be modified");
+ equal(orderDetails.isUpdated(orderDetail, "Order"), false, "The Order should not be tracked");
+
+ orderDetails.revertUpdates(orderDetail);
+ ok(order.OrderDetails.indexOf(orderDetail) >= 0 && order2.OrderDetails.indexOf(orderDetail) < 0 && orderDetail.Order() === order, "The order detail is a child of order1");
+ equal(orders.getEntityState(order), upshot.EntityState.Unmodified, "order1 should not be modified");
+ equal(orders.getEntityState(order2), upshot.EntityState.Unmodified, "order2 should not be modified");
+ equal(orderDetails.getEntityState(orderDetail), upshot.EntityState.Unmodified, "The order detail should not be modified");
+ equal(orderDetails.isUpdated(orderDetail, "OrderId"), false, "The OrderId should not be modified");
+ equal(orderDetails.isUpdated(orderDetail, "Order"), false, "The Order should not be tracked");
+
+ orderDetail.Order(null);
+ equal(orderDetail.OrderId(), null, "FK should be null");
+ equal(orders.getEntityState(order), upshot.EntityState.Unmodified, "The old order should not be modified");
+ equal(orderDetails.getEntityState(orderDetail), upshot.EntityState.ClientUpdated, "The order detail should be ClientUpdated");
+ equal(orderDetails.isUpdated(orderDetail, "OrderId"), true, "The OrderId should be modified");
+ equal(orderDetails.isUpdated(orderDetail, "Order"), false, "The Order should not be tracked");
+
+ var properties = [];
+ $.each(observability.knockout.unmap(orderDetail, "OrderDetail"), function (key, value) {
+ properties.push(key);
+ equal(ko.utils.unwrapObservable(orderDetail[key]), value, "Properties should be equal");
+ });
+ equal(properties.length, 4, "The should be 4 serialized properties");
+
+ } finally {
+ upshot.observability.configuration = observability.jquery;
+ }
+ });
+
+ test("Nested entities can be added to an entity, and property changes do not bubble to the parent", 2, function () {
+ try {
+ upshot.observability.configuration = observability.knockout;
+
+ var dc = createKoTestContext(function () { }, false);
+
+ var orders = dc.getEntitySet("Order"),
+ orderDetails = dc.getEntitySet("OrderDetail"),
+ order = orders.getEntities()()[0],
+ orderDetail = orderDetails.getEntities()()[0];
+
+ orderDetail.Name("asdf");
+ equal(orders.getEntityState(order), upshot.EntityState.Unmodified, "The order should not be modified");
+ equal(orderDetails.getEntityState(orderDetail), upshot.EntityState.ClientUpdated, "The order detail should not be modified");
+ } finally {
+ upshot.observability.configuration = observability.jquery;
+ }
+ });
+
+ // Create and return a context using the specified submitChanges mock
+ function createTestContext(submitChangesMock, bufferChanges) {
+ var dataProvider = new upshot.DataProvider();
+ var implicitCommitHandler;
+ if (!bufferChanges) {
+ implicitCommitHandler = function () {
+ dc._commitChanges({ providerParameters: {} });
+ }
+ }
+ var dc = new upshot.DataContext(dataProvider, implicitCommitHandler);
+ dc._submitChanges = submitChangesMock;
+
+ // add a single product to the context
+ var type = "Product";
+ var products = [];
+ products.push({
+ ID: 1,
+ Name: "Crispy Snarfs",
+ Category: "Snacks",
+ Price: 12.99
+ });
+ products.push({
+ ID: 2,
+ Name: "Cheezy Snax",
+ Category: "Snacks",
+ Price: 1.99
+ });
+
+ // mock out enough metadata to do the attach
+ upshot.metadata(type, { key: ["ID"] });
+
+ dc.merge(products, type, null);
+
+ return dc;
+ }
+
+ function createKoTestContext(submitChangesMock, bufferChanges) {
+ var dataProvider = new upshot.DataProvider();
+ var implicitCommitHandler;
+ if (!bufferChanges) {
+ implicitCommitHandler = function () {
+ dc._commitChanges({ providerParameters: {} });
+ }
+ }
+ var dc = new upshot.DataContext(dataProvider, implicitCommitHandler);
+ dc._submitChanges = submitChangesMock;
+
+ // add a single product to the context
+ var manageAssociations = true; // TODO: Lift this to a createKoTestContext parameter when unmanaged associations is supported.
+ var OrderDetail = function (data) {
+ this.Id = ko.observable(data.Id);
+ this.Name = ko.observable(data.Name);
+ this.Order = ko.observable();
+ this.OrderId = manageAssociations ? ko.observable(data.OrderId) : ko.computed(function () {
+ return this.Order() ? this.Order().Id() : data.OrderId;
+ }, this);
+ this.Extra = ko.observable("extra");
+ };
+ var Order = function (data) {
+ this.Id = ko.observable(data.Id);
+ this.Name = ko.observable(data.Name);
+ this.OrderDetails = ko.observableArray(ko.utils.arrayMap(data.OrderDetails, function (od) { return new OrderDetail(od); }));
+ this.Extra = ko.observable("extra");
+ };
+
+ var orders = [],
+ order = {
+ Id: 1,
+ Name: "Order 1",
+ OrderDetails: []
+ };
+ orders.push(new Order(order));
+ orders.push(new Order({
+ Id: 2,
+ Name: "Order 2",
+ OrderDetails: []
+ }));
+ var orderDetails = [];
+ orderDetails.push(new OrderDetail({
+ Id: 1,
+ Name: "Order Detail 1",
+ OrderId: order.Id
+ }));
+ orderDetails.push(new OrderDetail({
+ Id: 2,
+ Name: "Order Detail 2",
+ OrderId: order.Id
+ }));
+ orders[0].OrderDetails(orderDetails);
+
+ // mock out enough metadata to do the attach
+ upshot.metadata("Order", { key: ["Id"], fields: { Id: { type: "Int32:#System" }, Name: { type: "String:#System" }, OrderDetails: { type: "OrderDetail", association: { Name: "O_OD", isForeignKey: false, thisKey: ["Id"], otherKey: ["OrderId"] }, array: true } } });
+ upshot.metadata("OrderDetail", { key: ["Id"], fields: { Id: { type: "Int32:#System" }, Name: { type: "String:#System" }, Order: { type: "Order", association: { Name: "O_OD", isForeignKey: true, thisKey: ["OrderId"], otherKey: ["Id"]} }, OrderId: { type: "Int32:#System"}} });
+
+ dc.merge(orders, "Order", null);
+
+ return dc;
+ }
+})(upshot, ko);
diff --git a/test/SPA.Test/upshot/Consistency.tests.js b/test/SPA.Test/upshot/Consistency.tests.js
new file mode 100644
index 00000000..38a2c249
--- /dev/null
+++ b/test/SPA.Test/upshot/Consistency.tests.js
@@ -0,0 +1,158 @@
+/// <reference path="../Scripts/References.js" />
+(function (global, upshot, undefined) {
+
+ module("Consistency.tests.js");
+
+ function getEntitySet() {
+ upshot.metadata("Employee", {
+ key: ["Id"],
+ fields: {
+ Name: {
+ type: "String:#System"
+ },
+ Manager: {
+ type: "Employee",
+ association: {
+ name: "Employee_Employee",
+ thisKey: ["ManagerId"],
+ otherKey: ["Id"],
+ isForeignKey: true
+ }
+ },
+ Reports: {
+ type: "Employee",
+ array: true,
+ association: {
+ name: "Employee_Employee2",
+ thisKey: ["Id"],
+ otherKey: ["ManagerId"],
+ isForeignKey: false
+ }
+ },
+ Id: {
+ type: "Int32:#System"
+ },
+ ManagerId: {
+ type: "Int32:#System"
+ }
+ }
+ });
+
+ var context = new upshot.DataContext();
+ context.merge([
+ { Id:1, Name: "Fred", ManagerId: 2 },
+ { Id:2, Name: "Bob" },
+ { Id:3, Name: "Jane" }
+ ], "Employee");
+
+ return context.getEntitySet("Employee");
+ }
+
+ test("AssociatedEntitiesView wrt FK update and revert", 6, function () {
+ var entitySet = getEntitySet(),
+ entities = entitySet.getEntities();
+
+ ok(entities[1].Reports.length === 1, "Bob should have Fred as a report");
+ ok(entities[2].Reports.length === 0, "Jane has no reports");
+
+ $.observable(entities[0]).property("ManagerId", 3);
+
+ ok(entities[1].Reports.length === 0, "Bob should have no reports");
+ ok(entities[2].Reports.length === 1, "Jane should have Fred as a report");
+
+ entitySet.revertChanges();
+
+ ok(entities[1].Reports.length === 1, "Bob should have Fred as a report");
+ ok(entities[2].Reports.length === 0, "Jane has no reports");
+ });
+
+ test("AssociatedEntitiesView wrt insert and revert", 3, function () {
+ var entitySet = getEntitySet(),
+ entities = entitySet.getEntities();
+
+ ok(entities[1].Reports.length === 1, "Bob should have Fred as a report");
+
+ $.observable(entities[1].Reports).insert(entities[2]);
+
+ ok(entities[1].Reports.length === 2, "Bob should have Fred and Jane as reports");
+
+ entitySet.revertChanges();
+
+ ok(entities[1].Reports.length === 1, "Bob should have Fred as a report");
+ });
+
+ test("LocalDataSource auto-refresh over AssociatedEntitiesView", 9, function () {
+ stop();
+
+ var entitySet = getEntitySet(),
+ entities = entitySet.getEntities();
+
+ ok(entities[1].Reports.length === 1, "Bob should have Fred as a report");
+ ok(entities[2].Reports.length === 0, "Jane has no reports");
+
+ var localDataSource = new upshot.LocalDataSource({
+ source: entities[1].Reports,
+ autoRefresh: true,
+ filter: { property: "Name", operator: "!=", value: "Joan" }
+ });
+ localDataSource.refresh(function () {
+ ok(localDataSource.getEntities().length === 1, "Bob should have Fred as a report");
+
+ $.observable(entities[0]).property("ManagerId", 3);
+
+ ok(entities[1].Reports.length === 0, "Bob should have no reports");
+ ok(entities[2].Reports.length === 1, "Jane should have Fred as a report");
+ ok(localDataSource.getEntities().length === 0, "Bob should have no reports");
+
+ entitySet.revertChanges();
+
+ ok(entities[1].Reports.length === 1, "Bob should have Fred as a report");
+ ok(entities[2].Reports.length === 0, "Jane has no reports");
+ ok(localDataSource.getEntities().length === 1, "Bob should have Fred as a report");
+
+ start();
+ });
+ });
+
+ test("LocalDataSource auto-refresh over EntitySet wrt property updates and revert", 3, function () {
+ stop();
+
+ var entitySet = getEntitySet();
+
+ var localDataSource = new upshot.LocalDataSource({
+ source: entitySet,
+ autoRefresh: true,
+ filter: { property: "Name", value: "Fred" }
+ });
+ localDataSource.refresh(function () {
+ ok(localDataSource.getEntities().length === 1, "We have an employee named Fred");
+
+ $.observable(localDataSource.getEntities()[0]).property("Name", "Fredrick");
+
+ ok(localDataSource.getEntities().length === 0, "We have no employees named Fred");
+
+ entitySet.revertChanges();
+
+ ok(localDataSource.getEntities().length === 1, "We have an employee named Fred");
+
+ start();
+ });
+ });
+
+ test("EntitySet wrt insert and revert", 4, function () {
+ var entitySet = getEntitySet();
+
+ ok(entitySet.getEntities().length === 3, "Have 3 entities");
+
+ var newEmployee = { Name: "Barb" };
+ $.observable(entitySet.getEntities()).insert(newEmployee);
+
+ ok(entitySet.getEntities().length === 4, "Now 4 entities");
+
+ entitySet.revertChanges();
+
+ ok(entitySet.getEntities().length === 3, "Have 3 entities");
+ ok((entitySet.getEntityState(newEmployee) || upshot.EntityState.Deleted) === upshot.EntityState.Deleted);
+ });
+
+})(this, upshot);
diff --git a/test/SPA.Test/upshot/Core.tests.js b/test/SPA.Test/upshot/Core.tests.js
new file mode 100644
index 00000000..b1d7153c
--- /dev/null
+++ b/test/SPA.Test/upshot/Core.tests.js
@@ -0,0 +1,293 @@
+
+(function(upshot) {
+
+ module("Core.js");
+
+ var Custom = upshot.defineClass(null);
+
+ test("classof utility test", 50, function () {
+ var testCases = [
+ [ true, "boolean"],
+ [ null, "null"],
+ [ 1, "number"],
+ [ [], "array"],
+ [ "s", "string"],
+ [ {}, "object"],
+ [ new Custom(), "object" ],
+ [ Boolean(true), "boolean" ],
+ [ new Boolean(true), "boolean" ],
+ [ 1, "number" ],
+ [ 1.0, "number" ],
+ [ Number(1), "number" ],
+ [ new Number(1), "number" ],
+ [ Number.MAX_VALUE, "number" ],
+ [ Number.MIN_VALUE, "number" ],
+ [ Number.NaN, "number" ],
+ [ Number.NEGATIVE_INFINITY, "number" ],
+ [ Number.POSITIVE_INFINITY, "number" ],
+ [ "A", "string" ],
+ [ String("A"), "string" ],
+ [ new String("A"), "string" ],
+ [ new Date(), "date" ],
+ [ undefined, "undefined" ],
+ [ function () {}, "function" ],
+ [ /./, "regexp" ]
+ ];
+
+ for(var i = 0; i < testCases.length; i++) {
+ // verify our classof function
+ var testPair = testCases[i];
+ var result = upshot.classof(testPair[0]);
+ equal(result, testPair[1]);
+
+ // verify that we produce the same results as the jQuery function
+ var jQueryResult = $.type(testPair[0]);
+ equal(result, jQueryResult);
+ }
+ });
+
+ test("isArray utility test", 2, function () {
+ equal(upshot.isArray([]), true);
+ equal(upshot.isArray(5), false);
+ });
+
+ test("HelloWorldNs test", 2, function () {
+ equal(typeof upshot.defineNamespace, "function", "pre test");
+ upshot.defineNamespace("HelloWorldNs");
+ equal(typeof HelloWorldNs, "object", "HelloWorldNs is defined");
+ });
+
+ test("HelloWorldCls simple test", 4, function () {
+ var HelloWorldCls = upshot.defineClass(
+ function(result) {
+ this.result = result;
+ },
+ {
+ add: function(value) {
+ this.result += value;
+ },
+ subtract: function(value) {
+ this.result -= value;
+ }
+ }
+ );
+ equal(typeof HelloWorldCls, "function", "HelloWorldCls is defined");
+ var val = 5;
+ var calc = new HelloWorldCls(val);
+ equal(calc.result, val, "precheck result");
+ val += 3;
+ calc.add(3);
+ equal(calc.result, val, "add check");
+ val -= 4;
+ calc.subtract(4);
+ equal(calc.result, val, "subtract check");
+ });
+
+ test("HelloWorldCls inherit test: prototype inheritance", 2, function () {
+ var result;
+ var HelloWorldBase = upshot.defineClass(
+ function() {
+ this.id = 1;
+ },
+ {
+ foo: function(val) {
+ result += this.id + ".HelloWorldBase.foo(" + val + ")";
+ }
+ }
+ );
+ var HelloWorldCls = upshot.deriveClass(
+ HelloWorldBase.prototype,
+ function() {
+ this.id = 2;
+ },
+ {
+ bar: function(val) {
+ result += this.id + ".HelloWorldCls.bar(" + val + ")";
+ }
+ }
+ );
+
+ var tmp = new HelloWorldCls();
+ result = "";
+ tmp.foo("hi");
+ equal(result, "2.HelloWorldBase.foo(hi)", "test foo");
+ result = "";
+ tmp.bar("hey");
+ equal(result, "2.HelloWorldCls.bar(hey)", "test bar");
+ });
+
+ test("HelloWorldCls inherit test: override ctor", 1, function () {
+ var result;
+ var HelloWorldBase = upshot.defineClass(
+ function(val) {
+ result += "HelloWorldBase.ctor(" + val + ")";
+ }
+ );
+ var base = HelloWorldBase.prototype,
+ HelloWorldCls = upshot.deriveClass(
+ base,
+ function(val) {
+ result += "HelloWorldCls.ctor(" + val + ")";
+ base.constructor.call(this, val);
+ }
+ );
+
+ result = "";
+ var tmp = new HelloWorldCls("hey");
+ equal(result, "HelloWorldCls.ctor(hey)HelloWorldBase.ctor(hey)", "test ctor");
+ });
+
+ test("HelloWorldCls inherit test: override method", 1, function () {
+ var result;
+ var HelloWorldBase = upshot.defineClass(
+ function() {
+ this.id = 1;
+ },
+ {
+ foo: function(val) {
+ result += this.id + ".HelloWorldBase.foo(" + val + ")";
+ }
+ }
+ );
+ var base = HelloWorldBase.prototype,
+ HelloWorldCls = upshot.deriveClass(
+ base,
+ function() {
+ this.id = 2;
+ },
+ {
+ foo: function(val) {
+ result += this.id + ".HelloWorldCls.foo(" + val + ")";
+ base.foo.call(this, val);
+ }
+ }
+ );
+
+ var tmp = new HelloWorldCls();
+ result = "";
+ tmp.foo("hey");
+ equal(result, "2.HelloWorldCls.foo(hey)2.HelloWorldBase.foo(hey)", "test foo");
+ });
+
+ test("HelloWorldCls inherit test: multi prototype inheritance", 3, function () {
+ var result;
+ var HelloWorldBase = upshot.defineClass(
+ function() {
+ this.id = 1;
+ },
+ {
+ foo: function(val) {
+ result += this.id + ".HelloWorldBase.foo(" + val + ")";
+ }
+ }
+ );
+ var HelloWorldInt = upshot.deriveClass(
+ HelloWorldBase.prototype,
+ function() {
+ this.id = 2;
+ },
+ {
+ fred: function(val) {
+ result += this.id + ".HelloWorldInt.fred(" + val + ")";
+ }
+ }
+ );
+ var HelloWorldCls = upshot.deriveClass(
+ HelloWorldInt.prototype,
+ function() {
+ this.id = 3;
+ },
+ {
+ bar: function(val) {
+ result += this.id + ".HelloWorldCls.bar(" + val + ")";
+ }
+ }
+ );
+
+ var tmp = new HelloWorldCls();
+ result = "";
+ tmp.foo("hi");
+ equal(result, "3.HelloWorldBase.foo(hi)", "test foo");
+ result = "";
+ tmp.fred("huh");
+ equal(result, "3.HelloWorldInt.fred(huh)", "test fred");
+ result = "";
+ tmp.bar("hey");
+ equal(result, "3.HelloWorldCls.bar(hey)", "test bar");
+ });
+
+ test("HelloWorldCls inherit test: multi override ctor", 1, function () {
+ var result;
+ var HelloWorldBase = upshot.defineClass(
+ function(val) {
+ result += "HelloWorldBase.ctor(" + val + ")";
+ }
+ );
+ var base = HelloWorldBase.prototype,
+ HelloWorldInt = upshot.deriveClass(
+ base,
+ function(val) {
+ result += "HelloWorldInt.ctor(" + val + ")";
+ base.constructor.call(this, val);
+ }
+ );
+ var baseInt = HelloWorldInt.prototype,
+ HelloWorldCls = upshot.deriveClass(
+ baseInt,
+ function(val) {
+ result += "HelloWorldCls.ctor(" + val + ")";
+ baseInt.constructor.call(this, val);
+ }
+ );
+
+ result = "";
+ var tmp = new HelloWorldCls("hey");
+ equal(result, "HelloWorldCls.ctor(hey)HelloWorldInt.ctor(hey)HelloWorldBase.ctor(hey)", "test ctor");
+ });
+
+ test("HelloWorldCls inherit test: multi override method", 1, function () {
+ var result;
+ var HelloWorldBase = upshot.defineClass(
+ function() {
+ this.id = 1;
+ },
+ {
+ foo: function(val) {
+ result += this.id + ".HelloWorldBase.foo(" + val + ")";
+ }
+ }
+ );
+ var base = HelloWorldBase.prototype,
+ HelloWorldInt = upshot.deriveClass(
+ base,
+ function() {
+ this.id = 2;
+ },
+ {
+ foo: function(val) {
+ result += this.id + ".HelloWorldInt.foo(" + val + ")";
+ base.foo.call(this, val);
+ }
+ }
+ );
+ var baseInt = HelloWorldInt.prototype,
+ HelloWorldCls = upshot.deriveClass(
+ baseInt,
+ function() {
+ this.id = 3;
+ },
+ {
+ foo: function(val) {
+ result += this.id + ".HelloWorldCls.foo(" + val + ")";
+ baseInt.foo.call(this, val);
+ }
+ }
+ );
+
+ var tmp = new HelloWorldCls();
+ result = "";
+ tmp.foo("hey");
+ equal(result, "3.HelloWorldCls.foo(hey)3.HelloWorldInt.foo(hey)3.HelloWorldBase.foo(hey)", "test foo");
+ });
+
+})(upshot); \ No newline at end of file
diff --git a/test/SPA.Test/upshot/DataContext.tests.js b/test/SPA.Test/upshot/DataContext.tests.js
new file mode 100644
index 00000000..e816f8ad
--- /dev/null
+++ b/test/SPA.Test/upshot/DataContext.tests.js
@@ -0,0 +1,7 @@
+/// <reference path="../Scripts/References.js" />
+
+(function (upshot) {
+
+ module("DataContext tests");
+
+})(upshot); \ No newline at end of file
diff --git a/test/SPA.Test/upshot/DataProvider.tests.js b/test/SPA.Test/upshot/DataProvider.tests.js
new file mode 100644
index 00000000..12762f51
--- /dev/null
+++ b/test/SPA.Test/upshot/DataProvider.tests.js
@@ -0,0 +1,156 @@
+/// <reference path="../Scripts/References.js" />
+(function (global, upshot, undefined) {
+
+ module("DataProvider tests");
+
+ function getTestProvider(verifyGet, verifySubmit) {
+ return {
+ get: function (getParameters, queryParameters, success, error) {
+ var queryResult = {
+ entities: [{ ID: 1, Name: "Mathew" }, { ID: 2, Name: "Amy"}],
+ totalCount: 2
+ };
+ if (verifyGet) {
+ verifyGet(getParameters, queryParameters);
+ }
+ success(queryResult);
+ },
+ submit: function (submitParameters, changeSet, success, error) {
+ if (verifySubmit) {
+ verifySubmit(submitParameters, changeSet);
+ }
+ success(changeSet);
+ }
+ };
+ }
+
+ function getTestContext() {
+ var dc = new upshot.DataContext(getTestProvider());
+ upshot.metadata(getTestMetadata());
+ return dc;
+ }
+
+ function getTestMetadata() {
+ return { Contact: { key: ["ID"] } };
+ }
+
+ // Verify an offline type scenario where a DataProvider is used directly and results
+ // are "offlined" and rehydrated back into the context (metadata and entities)
+ test("Offline scenario", 4, function () {
+ // execute a direct query using the dataprovider and simulate
+ // caching of the results
+ var provider = getTestProvider();
+ var cachedEntities;
+ provider.get({ operationName: "contacts" }, null, function (result) {
+ cachedEntities = result.entities;
+ });
+ equal(2, cachedEntities.length);
+
+ // create a new context and load the cached entities into it
+ var dataContext = getTestContext();
+ var mergedEntities = dataContext.merge(cachedEntities, "Contact", null);
+
+ // verify that after merge, entities are annotated with their type
+ var entitySet = dataContext.getEntitySet("Contact");
+ var contact = entitySet.getEntities()[0];
+ equal(contact.__type, "Contact");
+
+ // verify that the "rehydrated" context is fully functional
+ equal(entitySet.getEntityState(contact), upshot.EntityState.Unmodified);
+ $.observable(contact).property("Name", "xyz");
+ equal(entitySet.getEntityState(contact), upshot.EntityState.ClientUpdated);
+ });
+
+ test("Custom data provider", 4, function () {
+ var verifySubmit = function (submitParameters, changeSet) {
+ // verify we got the expected changeset
+ equal(changeSet.length, 1);
+ equal(changeSet[0].Entity.Name, "foo");
+ };
+ var provider = getTestProvider(null, verifySubmit);
+
+ // create a datasource using the provider and verify an E2E query + update cycle
+ var ds = upshot.RemoteDataSource({
+ providerParameters: { operationName: "contacts" },
+ entityType: "Contact",
+ provider: provider,
+ bufferChanges: true,
+ refreshSuccess: function (entities) {
+ equal(entities.length, 2);
+
+ // modify an entity
+ $.observable(entities[0]).property("Name", "foo");
+
+ ds.commitChanges(function () {
+ ok(true);
+ });
+ }
+ });
+ upshot.metadata(getTestMetadata());
+ ds.refresh();
+ });
+
+ test("Verify get/submit parameter handling", 15, function () {
+ var verifySubmit = function (submitParameters, changeSet) {
+ // verify "outer params" are pushed in
+ equal(submitParameters.outerA, "outerA");
+ equal(submitParameters.outerB, "outerB");
+
+ // verify submit only params
+ equal(submitParameters.submitA, "submitA");
+ equal(submitParameters.submitB, "submitB");
+
+ equal(submitParameters.getA, undefined);
+ equal(submitParameters.getB, undefined);
+ };
+ var verifyGet = function (getParameters) {
+ // verify "outer params" are pushed in
+ equal(getParameters.outerA, "outerA");
+ equal(getParameters.outerB, "outerB");
+
+ // verify get only params
+ equal(getParameters.getA, "getA");
+ equal(getParameters.getB, "getB");
+
+ equal(getParameters.submitA, undefined);
+ equal(getParameters.submitB, undefined);
+
+ // verify original provider parameter objects weren't modified
+ equal(providerParameters.get.url, undefined);
+ };
+ var provider = getTestProvider(verifyGet, verifySubmit);
+
+ var providerParameters = {
+ outerA: "outerA",
+ outerB: "outerB",
+ get: {
+ getA: "getA",
+ getB: "getB"
+ },
+ submit: {
+ submitA: "submitA",
+ submitB: "submitB"
+ }
+ };
+
+ var ds = upshot.RemoteDataSource({
+ providerParameters: providerParameters,
+ entityType: "Contact",
+ provider: provider,
+ bufferChanges: true,
+ refreshSuccess: function (entities) {
+ equal(entities.length, 2);
+
+ // modify an entity
+ $.observable(entities[0]).property("Name", "foo");
+
+ ds.commitChanges(function () {
+ ok(true);
+ });
+ }
+ });
+ upshot.metadata(getTestMetadata());
+ ds.refresh();
+ });
+
+})(this, upshot); \ No newline at end of file
diff --git a/test/SPA.Test/upshot/DataSource.Common.js b/test/SPA.Test/upshot/DataSource.Common.js
new file mode 100644
index 00000000..acf04282
--- /dev/null
+++ b/test/SPA.Test/upshot/DataSource.Common.js
@@ -0,0 +1,161 @@
+/// <reference path="../Scripts/References.js" />
+
+
+var dsTestDriver;
+
+(function (global, upshot, undefined) {
+
+ if ($.isPlainObject(dsTestDriver)) {
+ return;
+ }
+
+ function createProductsResult () {
+ return {
+ GetProductsResult: {
+ TotalCount: 3,
+ RootResults: [
+ { ID: 1, Manufacturer: "Canon", Price: 200 },
+ { ID: 2, Manufacturer: "Nikon", Price: 400 },
+ { ID: 3, Manufacturer: "Pentax", Price: 500 }
+ ],
+ Metadata: [
+ {
+ type: "Product:#Sample.Models",
+ key: ["ID"],
+ fields: {
+ ID: { type: "Int32:#System" },
+ Manufacturer: { type: "String:#System" },
+ Price: { type: "Decimal:#System" }
+ },
+ rules: {
+ ID: { required: true },
+ Price: { range: [0, 1000] }
+ }
+ }
+ ]
+ }
+ };
+ }
+
+ dsTestDriver = {
+ ds: null,
+ simulatedSuccess: true,
+ errorStatus: "ErrorStatus",
+ errorValue: "ErrorValue",
+ validationError: {
+ SubmitChangesResult: [{
+ ValidationErrors: [{
+ Message: "The ID field is required!"
+ }]
+ }]
+ },
+
+ simulateSuccessService: function (results) {
+ testHelper.mockAjaxOnce("unused", this.productsResult = results || createProductsResult());
+ this.simulatedSuccess = true;
+ },
+ simulatePostSuccessService: function (results) {
+ testHelper.mockAjaxOnce("unused", results);
+ this.simulatedSuccess = true;
+ },
+ simulateErrorService: function () {
+ testHelper.mockAjaxOnce("unused", null, this.errorStatus, this.errorValue);
+ this.simulatedSuccess = false;
+ },
+ simulateValidationErrorService: function () {
+ testHelper.mockAjaxOnce("unused", this.validationError);
+ this.simulatedSuccess = false;
+ },
+
+ onRefreshStartEvent: function (event) {
+ equal(event.type, "refreshStart", "Event triggered");
+ var args = Array.prototype.slice.call(arguments);
+ args.shift();
+ dsTestDriver.onRefreshStart.apply(this, args);
+ },
+ onRefreshStart: function () {
+ ok(true, "Callback called");
+ equal(arguments.length, 0, "Argument checked");
+ ok(this === dsTestDriver.ds, "Context checked");
+ },
+ onRefreshSuccessEvent: function (event, entities, totalCount) {
+ equal(event.type, "refreshSuccess", "Event triggered");
+ var args = Array.prototype.slice.call(arguments);
+ args.shift();
+ dsTestDriver.onRefreshSuccess.apply(this, args);
+ },
+ onRefreshSuccess: function (entities, totalCount) {
+ ok(true, "Callback called");
+ ok(dsTestDriver.simulatedSuccess, "Simulation checked");
+ equal(arguments.length, 2, "Argument checked");
+ ok(this === dsTestDriver.ds, "Context checked");
+ equal(totalCount, dsTestDriver.productsResult.GetProductsResult.TotalCount, "Count checked");
+ var lastIndex = dsTestDriver.ds._take || (dsTestDriver.ds.dataSource && dsTestDriver.ds.dataSource._take) || totalCount;
+ equal(entities[lastIndex - 1].Price, dsTestDriver.productsResult.GetProductsResult.RootResults[lastIndex - 1].Price, "Price checked");
+ start();
+ },
+ onRefreshErrorEvent: function (event) {
+ equal(event.type, "refreshError", "Event triggered");
+ var args = Array.prototype.slice.call(arguments);
+ args.shift();
+ dsTestDriver.onRefreshError.apply(this, args);
+ },
+ onRefreshError: function (httpStatus, errorText, jqXHR) {
+ ok(true, "Callback called");
+ ok(!dsTestDriver.simulatedSuccess, "Simulation checked");
+ equal(arguments.length, 3, "Argument checked");
+ ok(this === dsTestDriver.ds, "Context checked");
+ equal(errorText, dsTestDriver.errorValue, "error checked");
+ start();
+ },
+ onCommitStartEvent: function (event) {
+ equal(event.type, "commitStart", "Event triggered");
+ var args = Array.prototype.slice.call(arguments);
+ args.shift();
+ dsTestDriver.onCommitStart.apply(this, args);
+ },
+ onCommitStart: function () {
+ ok(true, "Callback called");
+ equal(arguments.length, 0, "Argument checked");
+ ok(this === dsTestDriver.ds, "Context checked");
+ },
+ onCommitSuccessEvent: function (event) {
+ equal(event.type, "commitSuccess", "Event triggered");
+ var args = Array.prototype.slice.call(arguments);
+ args.shift();
+ dsTestDriver.onCommitSuccess.apply(this, args);
+ },
+ onCommitSuccess: function () {
+ ok(true, "Callback called");
+ ok(dsTestDriver.simulatedSuccess, "Simulation checked");
+ equal(arguments.length, 1, "Argument checked");
+ var submitResults = arguments[0];
+ equal(submitResults.length, 1, "submitResults checked");
+ ok(this === dsTestDriver.ds, "Context checked");
+ start();
+ },
+ onCommitErrorEvent: function (event) {
+ equal(event.type, "commitError", "Event triggered");
+ var args = Array.prototype.slice.call(arguments);
+ args.shift();
+ dsTestDriver.onCommitError.apply(this, args);
+ },
+ onCommitError: function (httpStatus, errorText, jqXHR, submitResult) {
+ ok(true, "Callback called");
+ ok(!dsTestDriver.simulatedSuccess, "Simulation checked");
+ equal(arguments.length, 4, "Argument checked");
+ ok(this === dsTestDriver.ds, "Context checked");
+ equal(errorText, dsTestDriver.errorValue, "errorText checked");
+ start();
+ },
+ onCommitValidationError: function (httpStatus, errorText, jqXHR, submitResult) {
+ ok(true, "Callback called");
+ ok(!dsTestDriver.simulatedSuccess, "Simulation checked");
+ equal(arguments.length, 4, "Argument checked");
+ ok(this === dsTestDriver.ds, "Context checked");
+ equal(errorText, dsTestDriver.validationError.SubmitChangesResult[0].ValidationErrors[0].Message, "Validation text checked");
+ start();
+ }
+ };
+
+})(this, upshot);
diff --git a/test/SPA.Test/upshot/DataSource.Tests.js b/test/SPA.Test/upshot/DataSource.Tests.js
new file mode 100644
index 00000000..b0fc0735
--- /dev/null
+++ b/test/SPA.Test/upshot/DataSource.Tests.js
@@ -0,0 +1,349 @@
+/// <reference path="../Scripts/References.js" />
+(function (global, upshot, undefined) {
+
+ module("DataSource.tests.js", {
+ teardown: function () {
+ testHelper.unmockAjax();
+ }
+ });
+
+ function createRemoteDataSource(options) {
+ options = $.extend({ providerParameters: { url: "unused", operationName: "" }, provider: upshot.riaDataProvider }, options || {});
+ return new upshot.RemoteDataSource(options);
+ }
+
+ function createTestDataContext() {
+ return new upshot.DataContext(new upshot.riaDataProvider());
+ }
+
+ // refreshStart
+ test("refreshStart RemoteDataSource", 3, function () {
+ stop();
+ dsTestDriver.simulateSuccessService();
+ dsTestDriver.ds = upshot.RemoteDataSource({ providerParameters: { url: "unused", operationName: "" }, provider: upshot.riaDataProvider });
+ dsTestDriver.ds.bind("refreshStart", dsTestDriver.onRefreshStart);
+ dsTestDriver.ds.refresh(function () { start(); });
+ });
+
+ test("refreshStart observer LocalDataSource over RemoteDataSource", 3, function () {
+ stop();
+ dsTestDriver.simulateSuccessService();
+ dsTestDriver.ds = upshot.LocalDataSource({ source: createRemoteDataSource() });
+ dsTestDriver.ds.bind({
+ refreshStart: dsTestDriver.onRefreshStart,
+ refreshSuccess: function () { start(); }
+ });
+ dsTestDriver.ds.refresh({ all: true });
+ });
+
+ test("refreshStart observer LocalDataSource over EntitySet", 3, function () {
+ stop();
+ dsTestDriver.simulateSuccessService();
+ var dataContext = upshot.DataContext(new upshot.riaDataProvider());
+ dataContext.__load({ providerParameters: { url: "unused", operationName: ""} }, function (entitySet) {
+ dsTestDriver.ds = new upshot.LocalDataSource({ source: entitySet });
+ dsTestDriver.ds.bind("refreshStart", dsTestDriver.onRefreshStart);
+ dsTestDriver.ds.bind("refreshSuccess", function () { start(); });
+ dsTestDriver.ds.refresh();
+ });
+ });
+
+ // refreshSuccess
+ test("refreshSuccess RemoteDataSource", 6, function () {
+ stop();
+ dsTestDriver.simulateSuccessService();
+ dsTestDriver.ds = createRemoteDataSource();
+ dsTestDriver.ds.refresh(dsTestDriver.onRefreshSuccess, dsTestDriver.onRefreshError);
+ });
+
+ test("refreshSuccess observer RemoteDataSource", 6, function () {
+ stop();
+ dsTestDriver.simulateSuccessService();
+ dsTestDriver.ds = createRemoteDataSource();
+ dsTestDriver.ds.bind("refreshSuccess", dsTestDriver.onRefreshSuccess);
+ dsTestDriver.ds.bind("refreshError", dsTestDriver.onRefreshError);
+ dsTestDriver.ds.refresh();
+ });
+
+ test("refreshSuccess LocalDataSource over RemoteDataSource", 6, function () {
+ stop();
+ dsTestDriver.simulateSuccessService();
+ dsTestDriver.ds = new upshot.LocalDataSource({ source: createRemoteDataSource() });
+ dsTestDriver.ds.refresh({ all: true }, dsTestDriver.onRefreshSuccess, dsTestDriver.onRefreshError);
+ });
+
+ test("refreshSuccess observer LocalDataSource over RemoteDataSource", 6, function () {
+ stop();
+ dsTestDriver.simulateSuccessService();
+ dsTestDriver.ds = new upshot.LocalDataSource({ source: createRemoteDataSource() })
+ .bind("refreshSuccess", dsTestDriver.onRefreshSuccess)
+ .bind("refreshError", dsTestDriver.onRefreshError);
+ dsTestDriver.ds.refresh({ all: true });
+ });
+
+ test("refreshSuccess LocalDataSource over EntitySet", 6, function () {
+ stop();
+ dsTestDriver.simulateSuccessService();
+ var dataContext = createTestDataContext();
+ dataContext.__load({ providerParameters: { url: "unused"} }, function (entitySet) {
+ dsTestDriver.ds = new upshot.LocalDataSource({ source: entitySet });
+ dsTestDriver.ds.refresh(dsTestDriver.onRefreshSuccess, dsTestDriver.onRefreshError);
+ });
+ });
+
+ test("refreshSuccess observer LocalDataSource over EntitySet", 6, function () {
+ stop();
+ dsTestDriver.simulateSuccessService();
+ var dataContext = createTestDataContext();
+ dataContext.__load({ providerParameters: { url: "unused"} }, function (entitySet) {
+ dsTestDriver.ds = new upshot.LocalDataSource({ source: entitySet })
+ .bind({
+ refreshSuccess: dsTestDriver.onRefreshSuccess,
+ refreshError: dsTestDriver.onRefreshError
+ });
+ dsTestDriver.ds.refresh();
+ });
+ });
+
+ // refreshError
+ test("refreshError RemoteDataSource", 5, function () {
+ stop();
+ dsTestDriver.simulateErrorService();
+ dsTestDriver.ds = createRemoteDataSource();
+ dsTestDriver.ds.refresh(dsTestDriver.onRefreshSuccess, dsTestDriver.onRefreshError);
+ });
+
+ test("refreshError observer RemoteDataSource", 5, function () {
+ stop();
+ dsTestDriver.simulateErrorService();
+ dsTestDriver.ds = createRemoteDataSource();
+ dsTestDriver.ds.bind("refreshSuccess", dsTestDriver.onRefreshSuccess);
+ dsTestDriver.ds.bind("refreshError", dsTestDriver.onRefreshError);
+ dsTestDriver.ds.refresh();
+ });
+
+ test("refreshError LocalDataSource over RemoteDataSource", 5, function () {
+ stop();
+ dsTestDriver.simulateErrorService();
+ dsTestDriver.ds = new upshot.LocalDataSource({ source: createRemoteDataSource() });
+ dsTestDriver.ds.refresh({ all: true }, dsTestDriver.onRefreshSuccess, dsTestDriver.onRefreshError);
+ });
+
+ test("refreshError observer LocalDataSource over RemoteDataSource", 5, function () {
+ stop();
+ dsTestDriver.simulateErrorService();
+ dsTestDriver.ds = new upshot.LocalDataSource({ source: createRemoteDataSource() });
+ dsTestDriver.ds.bind("refreshSuccess", dsTestDriver.onRefreshSuccess);
+ dsTestDriver.ds.bind("refreshError", dsTestDriver.onRefreshError);
+ dsTestDriver.ds.refresh({ all: true });
+ });
+
+ // entityChanged
+ asyncTest("entityChanged event is raised on a successful update", 12, function () {
+ var listener = {
+ events: [],
+ onEntityUpdated: function (entity, path, eventArgs) {
+ listener.events.push({ entity: entity, path: path, eventArgs: eventArgs });
+ }
+ };
+ dsTestDriver.ds = new upshot.RemoteDataSource({ providerParameters: { url: "unused", operationName: "" }, provider: upshot.riaDataProvider, entityType: "Product:#Sample.Models", bufferChanges: true });
+ dsTestDriver.ds.bind("entityUpdated", listener.onEntityUpdated);
+
+ dsTestDriver.simulateSuccessService();
+ dsTestDriver.ds.refresh(function (entities) {
+ var entity = entities[0];
+
+ equal(dsTestDriver.ds.isUpdated(entity), false, "The entity should not have changes");
+ equal(listener.events.length, 0, "There should have been one event");
+
+ $.observable(entity).property("Price", 700);
+
+ equal(dsTestDriver.ds.isUpdated(entity), true, "The entity should have changes");
+ equal(listener.events.length, 1, "There should have been one event");
+ equal(listener.events[0].entity, entity, "The event should have been for the entity");
+ equal(listener.events[0].path, "", "The event should have been directly on the entity");
+ equal(listener.events[0].eventArgs.newValues.Price, 700, "The event should have been a property change");
+
+ listener.events.length = 0;
+
+ dsTestDriver.simulatePostSuccessService();
+ dsTestDriver.ds.commitChanges(function () {
+ equal(dsTestDriver.ds.isUpdated(entity), false, "The entity should no longer have changes");
+ equal(listener.events.length, 1, "There should have been one event");
+ equal(listener.events[0].entity, entity, "The event should have been for the entity");
+ equal(listener.events[0].path, undefined, "The event should not include a path");
+ equal(listener.events[0].eventArgs, undefined, "The event should not include args");
+ start();
+ });
+ });
+ });
+
+ test("insert without initial refresh over RemoteDataSource", 5, function () {
+ stop();
+
+ dsTestDriver.ds = new upshot.RemoteDataSource({
+ providerParameters: { url: "unused" },
+ provider: upshot.riaDataProvider,
+ entityType: "Foo"
+ });
+ dsTestDriver.ds.bind("commitSuccess", dsTestDriver.onCommitSuccess);
+ upshot.metadata("Foo", { key: ["FooId"] });
+
+ dsTestDriver.simulatePostSuccessService();
+ $.observable(dsTestDriver.ds.getEntities()).insert({ FooId: 1 });
+ });
+
+ test("revertChanges without initial refresh over RemoteDataSource", 2, function () {
+ stop();
+
+ var entities = [],
+ ds = new upshot.RemoteDataSource({
+ entityType: "Foo",
+ result: entities,
+ bufferChanges: true,
+ provider: upshot.riaDataProvider
+ });
+ upshot.metadata("Foo", { key: ["FooId"] });
+
+ $.observable(entities).insert({});
+ equal(entities.length, 1);
+
+ ds.revertChanges();
+ equal(entities.length, 0);
+
+ start();
+ });
+
+ test("DataSource reset", 23, function () {
+ stop();
+ dsTestDriver.simulateSuccessService();
+
+ var rds = dsTestDriver.ds = createRemoteDataSource();
+ rds.refresh(function () {
+ stop();
+ dsTestDriver.onRefreshSuccess.apply(this, arguments);
+
+ var lds = upshot.LocalDataSource({ source: rds });
+ lds.refresh(function () {
+
+ // RDS and LDS should be loaded and in sync.
+ equal(lds.refreshNeeded(), false, "LocalDataSource should not need refresh");
+ ok(upshot.sameArrayContents(rds.getEntities(), lds.getEntities()), "LocalDataSource refreshed properly");
+
+ rds.reset();
+ // RDS should have been reset. LDS should need a refresh.
+ equal(rds.getEntities().length, 0, "RemoteDataSource has no entities");
+ equal(rds.getTotalEntityCount(), undefined, "RemoteDataSource has no total count");
+ equal(lds.refreshNeeded(), true, "LocalDataSource should need a refresh");
+
+ lds.reset();
+ // LDS should have been reset.
+ equal(lds.getEntities().length, 0, "LocalDataSource has no entities");
+ equal(lds.getTotalEntityCount(), undefined, "LocalDataSource has no total count");
+
+ // Put the RDS back in its loaded state.
+ dsTestDriver.simulateSuccessService();
+ rds.refresh(function () {
+ stop();
+ dsTestDriver.onRefreshSuccess.apply(this, arguments);
+
+ // Put the LDS back in sync with the RDS.
+ equal(lds.refreshNeeded(), true, "LocalDataSource should need a refresh");
+ lds.refresh(function () {
+ ok(upshot.sameArrayContents(rds.getEntities(), lds.getEntities()), "LocalDataSource refreshed properly");
+
+ lds.reset();
+ // LDS should have been reset.
+ equal(lds.getEntities().length, 0, "LocalDataSource has no entities");
+ equal(lds.getTotalEntityCount(), undefined, "LocalDataSource has no total count");
+
+ start();
+ });
+ });
+ });
+ });
+ });
+
+ test("refresh with edits", 5, function () {
+ stop();
+
+ var rds = dsTestDriver.ds = createRemoteDataSource();
+
+ dsTestDriver.simulateSuccessService();
+ rds.refresh(function (entities) {
+ ok(entities.length === 3, "Successful initial refresh");
+
+ var entity = entities[0];
+ $.observable(entity).property("Manufacturer", "Foo");
+
+ var exception;
+ try {
+ rds.refresh();
+ } catch (ex) {
+ exception = true;
+ }
+ ok(!!exception, "Can't load with edits to DataSource entities");
+ rds.revertChanges();
+
+ setTimeout(function () {
+ var emptyProductsResult = {
+ GetProductsResult: {
+ TotalCount: 3,
+ RootResults: [],
+ Metadata: [ $.extend({}, { type: "Product:#Sample.Models" }, upshot.metadata()["Product:#Sample.Models"]) ]
+ }
+ };
+ dsTestDriver.simulateSuccessService(emptyProductsResult);
+ rds.refresh(function (entities) {
+ ok(entities.length === 0, "'Canon' isn't in filtered result");
+
+ $.observable(entity).property("Manufacturer", "Foo");
+
+ setTimeout(function () {
+ var exception;
+ try {
+ dsTestDriver.simulateSuccessService(emptyProductsResult);
+ rds.refresh(function(entities) {
+ ok(entities.length === 0, "Can load with edits not included in DataSource entities");
+ start();
+ });
+ } catch (ex) {
+ exception = true;
+ }
+ ok(!exception, "Can load with edits not included in DataSource entities");
+ }, 0);
+ });
+ }, 0);
+ });
+ });
+
+ test("refresh with edits and 'allowRefreshWithEdits' option", 3, function () {
+ stop();
+
+ var rds = dsTestDriver.ds = createRemoteDataSource({ allowRefreshWithEdits: true });
+
+ dsTestDriver.simulateSuccessService();
+ rds.refresh(function (entities) {
+ ok(entities.length === 3, "Successful initial refresh");
+
+ var entity = entities[0];
+ $.observable(entity).property("Manufacturer", "Foo");
+
+ setTimeout(function () {
+ var exception;
+ try {
+ dsTestDriver.simulateSuccessService();
+ rds.refresh(function (entities) {
+ ok(entities.length === 3, "Can load with edits to DataSource entities (using allowRefreshWithEdits)");
+ start();
+ });
+ } catch (ex) {
+ exception = true;
+ }
+
+ ok(!exception, "Can load with edits to DataSource entities (using allowRefreshWithEdits)");
+ }, 0);
+ });
+ });
+
+})(this, upshot);
diff --git a/test/SPA.Test/upshot/Datasets.js b/test/SPA.Test/upshot/Datasets.js
new file mode 100644
index 00000000..1ca63c47
--- /dev/null
+++ b/test/SPA.Test/upshot/Datasets.js
@@ -0,0 +1,257 @@
+/// <reference path="../Scripts/References.js" />
+
+(function (global, upshot, undefined) {
+
+ var datasets = {
+ primitives: {
+ create: function (id) {
+ return {
+ Id: id || 0,
+ B: true,
+ N: 1,
+ S: "A"
+ };
+ },
+ count: 4
+ },
+
+ scalars: {
+ create: function (id) {
+ return {
+ Id: id || 0,
+ B: true,
+ N: 1,
+ S: "A",
+ D: new Date(2011, 0, 1),
+ _: null
+ };
+ },
+ count: 6
+ },
+
+ nested: {
+ create: function (id) {
+ return {
+ Id: id || 0,
+ B: true,
+ N: 1,
+ S: "A",
+ O: {
+ B: true,
+ N: 1,
+ S: "A"
+ }
+ };
+ },
+ count: 8
+ },
+
+ tree: {
+ create: function (id) {
+ return {
+ Id: id || 0,
+ B: true,
+ N: 1,
+ S: "A",
+ O: {
+ B: true,
+ N: 1,
+ S: "A",
+ O1: {
+ B: true,
+ N: 1,
+ S: "A"
+ },
+ O2: {
+ B: true,
+ N: 1,
+ S: "A"
+ }
+ }
+ };
+ },
+ count: 16
+ },
+
+ array: {
+ create: function (id) {
+ return {
+ Id: id || 0,
+ B: true,
+ N: 1,
+ S: "A",
+ A: [{
+ B: true,
+ N: 1,
+ S: "A"
+ },
+ {
+ B: true,
+ N: 1,
+ S: "A"
+ }
+ ]
+ };
+ },
+ count: 13
+ },
+
+ nestedArrays: {
+ create: function (id) {
+ return {
+ Id: id || 0,
+ B: true,
+ N: 1,
+ S: "A",
+ A: [{
+ B: true,
+ N: 1,
+ S: "A",
+ A: [{
+ B: true,
+ N: 1,
+ S: "A"
+ }
+ ]
+ },
+ {
+ B: true,
+ N: 1,
+ S: "A",
+ A: [{
+ B: true,
+ N: 1,
+ S: "A"
+ }
+ ]
+ }
+ ]
+ };
+ },
+ count: 23
+ },
+
+ ko_primitives: function (id, extend) {
+ var obj = {
+ Id: ko.observable(id || 0),
+ B: ko.observable(true),
+ N: ko.observable(1),
+ S: ko.observable("A")
+ };
+ if (extend) {
+ upshot.addEntityProperties(obj);
+ upshot.addUpdatedProperties(obj);
+ }
+ return obj;
+ },
+
+ ko_tree: function (id, extend) {
+ var obj = {
+ Id: ko.observable(id || 0),
+ B: ko.observable(true),
+ N: ko.observable(1),
+ S: ko.observable("A"),
+ O: ko.observable({
+ B: ko.observable(true),
+ N: ko.observable(1),
+ S: ko.observable("A"),
+ O1: ko.observable({
+ B: ko.observable(true),
+ N: ko.observable(1),
+ S: ko.observable("A")
+ }),
+ O2: ko.observable({
+ B: ko.observable(true),
+ N: ko.observable(1),
+ S: ko.observable("A")
+ })
+ })
+ };
+ if (extend) {
+ upshot.addEntityProperties(obj);
+ upshot.addUpdatedProperties(obj);
+ upshot.addUpdatedProperties(obj.O());
+ upshot.addUpdatedProperties(obj.O().O1());
+ upshot.addUpdatedProperties(obj.O().O2());
+ }
+ return obj;
+ },
+
+ ko_array: function (id, extend) {
+ var obj = {
+ Id: ko.observable(id || 0),
+ B: ko.observable(true),
+ N: ko.observable(1),
+ S: ko.observable("A"),
+ A: ko.observableArray([
+ {
+ B: ko.observable(true),
+ N: ko.observable(1),
+ S: ko.observable("A")
+ },
+ {
+ B: ko.observable(true),
+ N: ko.observable(1),
+ S: ko.observable("A")
+ }
+ ])
+ };
+ if (extend) {
+ upshot.addEntityProperties(obj);
+ upshot.addUpdatedProperties(obj);
+ upshot.addUpdatedProperties(obj.A()[0]);
+ upshot.addUpdatedProperties(obj.A()[1]);
+ }
+ return obj;
+ },
+
+ ko_nestedArrays: function (id, extend) {
+ var obj = {
+ Id: ko.observable(id || 0),
+ B: ko.observable(true),
+ N: ko.observable(1),
+ S: ko.observable("A"),
+ A: ko.observableArray([
+ {
+ B: ko.observable(true),
+ N: ko.observable(1),
+ S: ko.observable("A"),
+ A: ko.observableArray([
+ {
+ B: ko.observable(true),
+ N: ko.observable(1),
+ S: ko.observable("A")
+ }
+ ])
+ },
+ {
+ B: ko.observable(true),
+ N: ko.observable(1),
+ S: ko.observable("A"),
+ A: ko.observableArray([
+ {
+ B: ko.observable(true),
+ N: ko.observable(1),
+ S: ko.observable("A")
+ }
+ ])
+ }
+ ])
+ };
+ if (extend) {
+ upshot.addEntityProperties(obj);
+ upshot.addUpdatedProperties(obj);
+ upshot.addUpdatedProperties(obj.A()[0]);
+ upshot.addUpdatedProperties(obj.A()[0].A()[0]);
+ upshot.addUpdatedProperties(obj.A()[1]);
+ upshot.addUpdatedProperties(obj.A()[1].A()[0]);
+ }
+ return obj;
+
+ }
+ };
+
+ upshot.test || (upshot.test = {});
+ upshot.test.datasets = datasets;
+
+})(this, upshot); \ No newline at end of file
diff --git a/test/SPA.Test/upshot/Delete.Tests.js b/test/SPA.Test/upshot/Delete.Tests.js
new file mode 100644
index 00000000..6efb9175
--- /dev/null
+++ b/test/SPA.Test/upshot/Delete.Tests.js
@@ -0,0 +1,753 @@
+/// <reference path="../Scripts/References.js" />
+(function (global, upshot, undefined) {
+
+ module("Delete.tests.js", {
+ teardown: function () {
+ testHelper.unmockAjax();
+ }
+ });
+
+ function createDataSource(useLocal, result, bufferChanges) {
+ if (useLocal) {
+ var lds = new upshot.LocalDataSource({
+ source: createDataSource(false, null, !!bufferChanges),
+ result: result,
+ filter: [{ property: "Price", operator: ">", value: 300 }, { property: "Price", operator: "<", value: 700 }]
+ });
+ return lds;
+ } else {
+ return new upshot.RemoteDataSource({
+ providerParameters: { url: "unused", operationName: "" },
+ provider: upshot.riaDataProvider,
+ result: result,
+ bufferChanges: !!bufferChanges
+ });
+ }
+ }
+
+
+ function createProductsResult() {
+ return {
+ GetProductsResult: {
+ TotalCount: 5,
+ RootResults: [
+ { ID: 1, Manufacturer: "Canon", Price: 200 },
+ { ID: 2, Manufacturer: "Nikon", Price: 400 },
+ { ID: 3, Manufacturer: "Pentax", Price: 500 },
+ { ID: 4, Manufacturer: "Sony", Price: 600 },
+ { ID: 5, Manufacturer: "Olympus", Price: 800 }
+ ],
+ Metadata: [
+ {
+ type: "Product:#Sample.Models",
+ key: ["ID"],
+ fields: {
+ ID: { type: "Int32:#System" },
+ Manufacturer: { type: "String:#System" },
+ Price: { type: "Decimal:#System" }
+ },
+ rules: {
+ ID: { required: true },
+ Price: { range: [0, 1000] }
+ }
+ }
+ ]
+ }
+ };
+ }
+
+ function createProfileResult() {
+ return {
+ GetProfileForProfileUpdateResult: {
+ TotalCount: -1,
+ IncludedResults: [
+ { __type: "Friend:#BigShelf.Models", FriendId: 2, Id: 1, ProfileId: 1 },
+ { __type: "Friend:#BigShelf.Models", FriendId: 3, Id: 2, ProfileId: 1 },
+ { __type: "Friend:#BigShelf.Models", FriendId: 6, Id: 4, ProfileId: 1 },
+ { __type: "Profile:#BigShelf.Models", AspNetUserGuid: "5ad7976c-7a95-47aa-87ba-29c3fc80643e", EmailAddress: "deepm@microsoft.com", Id: 2, Name: "Deepesh Mohnani" },
+ { __type: "Profile:#BigShelf.Models", AspNetUserGuid: "9330619d-9a8c-4269-9bde-ba4cb1b7b354", EmailAddress: "jeffhand@microsoft.com", Id: 3, Name: "Jeff Handley" },
+ { __type: "Profile:#BigShelf.Models", AspNetUserGuid: "990c62c5-30a4-4ca9-92c5-f248241f6d44", EmailAddress: "gblock@microsoft.com", Id: 6, Name: "Glenn Block" }
+ ],
+ RootResults: [{ AspNetUserGuid: "3730F12D-1A2A-4499-859B-9F586B2858A4", EmailAddress: "demo@microsoft.com", Id: 1, Name: "Demo User"}],
+ Metadata: [
+ {
+ type: "Profile:#BigShelf.Models",
+ key: ["Id"],
+ fields: {
+ AspNetUserGuid: { type: "String:#System" },
+ EmailAddress: { type: "String:#System" },
+ Friends: { type: "Friend:#BigShelf.Models", array: true, association: { name: "Profile_Friend", thisKey: ["Id"], otherKey: ["ProfileId"], isForeignKey: false} },
+ Id: { type: "Int32:#System" },
+ Name: { type: "String:#System" }
+ },
+ rules: {
+ EmailAddress: { required: true, email: true },
+ Name: { required: true }
+ }
+ },
+ {
+ type: "Friend:#BigShelf.Models",
+ key: ["Id"],
+ fields: {
+ FriendId: { type: "Int32:#System" },
+ FriendProfile: { type: "Profile:#BigShelf.Models", association: { name: "Profile_Friend1", thisKey: ["FriendId"], otherKey: ["Id"], isForeignKey: true} },
+ Id: { type: "Int32:#System" },
+ Profile: { type: "Profile:#BigShelf.Models", association: { name: "Profile_Friend", thisKey: ["ProfileId"], otherKey: ["Id"], isForeignKey: true} },
+ ProfileId: { type: "Int32:#System" }
+ }
+ }
+ ]
+ }
+ };
+ }
+
+ // "GetProfileForProfileUpdateResult":{"TotalCount":-1,"IncludedResults":[{"__type":"Friend:#BigShelf.Models","FriendId":2,"Id":1,"ProfileId":1},{"__type":"Friend:#BigShelf.Models","FriendId":3,"Id":2,"ProfileId":1},{"__type":"Friend:#BigShelf.Models","FriendId":6,"Id":4,"ProfileId":1},{"__type":"Profile:#BigShelf.Models","AspNetUserGuid":"5ad7976c-7a95-47aa-87ba-29c3fc80643e","EmailAddress":"deepm@microsoft.com","Id":2,"Name":"Deepesh Mohnani"},{"__type":"Profile:#BigShelf.Models","AspNetUserGuid":"9330619d-9a8c-4269-9bde-ba4cb1b7b354","EmailAddress":"jeffhand@microsoft.com","Id":3,"Name":"Jeff Handley"},{"__type":"Profile:#BigShelf.Models","AspNetUserGuid":"990c62c5-30a4-4ca9-92c5-f248241f6d44","EmailAddress":"gblock@microsoft.com","Id":6,"Name":"Glenn Block"}],"RootResults":[{"AspNetUserGuid":"3730F12D-1A2A-4499-859B-9F586B2858A4","EmailAddress":"demo@microsoft.com","Id":1,"Name":"Demo User"}],"Metadata":[{"type":"Profile:#BigShelf.Models","key":["Id"],"fields":{"AspNetUserGuid":{"type":"String:#System"},"Categories":{"type":"Category:#BigShelf.Models","array":true,"association":{"name":"Profile_Category","thisKey":["Id"],"otherKey":["ProfileId"],"isForeignKey":false}},"EmailAddress":{"type":"String:#System"},"FlaggedBooks":{"type":"FlaggedBook:#BigShelf.Models","array":true,"association":{"name":"Profile_FlaggedBook","thisKey":["Id"],"otherKey":["ProfileId"],"isForeignKey":false}},"Friends":{"type":"Friend:#BigShelf.Models","array":true,"association":{"name":"Profile_Friend","thisKey":["Id"],"otherKey":["ProfileId"],"isForeignKey":false}},"Id":{"type":"Int32:#System"},"Name":{"type":"String:#System"}},"rules":{"EmailAddress":{"required":true,"email":true},"Name":{"required":true}}},{"type":"Book:#BigShelf.Models","key":["Id"],"fields":{"AddedDate":{"type":"DateTime:#System"},"ASIN":{"type":"String:#System"},"Author":{"type":"String:#System"},"CategoryId":{"type":"Int32:#System"},"CategoryName":{"type":"CategoryName:#BigShelf.Models","association":{"name":"CategoryName_Book","thisKey":["CategoryId"],"otherKey":["Id"],"isForeignKey":true}},"Description":{"type":"String:#System"},"FlaggedBooks":{"type":"FlaggedBook:#BigShelf.Models","array":true,"association":{"name":"Book_FlaggedBook","thisKey":["Id"],"otherKey":["BookId"],"isForeignKey":false}},"Id":{"type":"Int32:#System"},"PublishDate":{"type":"DateTime:#System"},"Title":{"type":"String:#System"}}},{"type":"FlaggedBook:#BigShelf.Models","key":["Id"],"fields":{"Book":{"type":"Book:#BigShelf.Models","association":{"name":"Book_FlaggedBook","thisKey":["BookId"],"otherKey":["Id"],"isForeignKey":true}},"BookId":{"type":"Int32:#System"},"Id":{"type":"Int32:#System"},"IsFlaggedToRead":{"type":"Int32:#System"},"Profile":{"type":"Profile:#BigShelf.Models","association":{"name":"Profile_FlaggedBook","thisKey":["ProfileId"],"otherKey":["Id"],"isForeignKey":true}},"ProfileId":{"type":"Int32:#System"},"Rating":{"type":"Int32:#System"}}},{"type":"Friend:#BigShelf.Models","key":["Id"],"fields":{"FriendId":{"type":"Int32:#System"},"FriendProfile":{"type":"Profile:#BigShelf.Models","association":{"name":"Profile_Friend1","thisKey":["FriendId"],"otherKey":["Id"],"isForeignKey":true}},"Id":{"type":"Int32:#System"},"Profile":{"type":"Profile:#BigShelf.Models","association":{"name":"Profile_Friend","thisKey":["ProfileId"],"otherKey":["Id"],"isForeignKey":true}},"ProfileId":{"type":"Int32:#System"}}},{"type":"Category:#BigShelf.Models","key":["Id"],"fields":{"CategoryId":{"type":"Int32:#System"},"CategoryName":{"type":"CategoryName:#BigShelf.Models","association":{"name":"CategoryName_Category","thisKey":["CategoryId"],"otherKey":["Id"],"isForeignKey":true}},"Id":{"type":"Int32:#System"},"Profile":{"type":"Profile:#BigShelf.Models","association":{"name":"Profile_Category","thisKey":["ProfileId"],"otherKey":["Id"],"isForeignKey":true}},"ProfileId":{"type":"Int32:#System"}}},{"type":"CategoryName:#BigShelf.Models","key":["Id"],"fields":{"Books":{"type":"Book:#BigShelf.Models","array":true,"association":{"name":"CategoryName_Book","thisKey":["Id"],"otherKey":["CategoryId"],"isForeignKey":false}},"Categories":{"type":"Category:#BigShelf.Models","array":true,"association":{"name":"CategoryName_Category","thisKey":["Id"],"otherKey":["CategoryId"],"isForeignKey":false}},"Id":{"type":"Int32:#System"},"Name":{"type":"String:#System"}}}]}}" String
+
+ function createSubmitResult(results) {
+ var changeResult = [];
+ for (var i = 0; i < results.length; ++i) {
+ var result = {
+ Entity: results[i].entity,
+ EntityActions: null,
+ HasMemberChanges: false,
+ Id: i,
+ Operation: 4
+ };
+ if (results[i].errors) {
+ result.ValidationErrors = results[i].errors;
+ }
+ changeResult.push(result);
+ }
+ return { SubmitChangesResult: changeResult };
+ }
+
+ function deleteEntities(products, results, bufferChanges, destructive, revert) {
+ dsTestDriver.simulatePostSuccessService(createSubmitResult(results));
+ setTimeout(function () {
+ for (var i = 0; i < results.length; ++i) {
+ if (destructive) {
+ $.observable(products).remove(results[i].entity);
+ equal(upshot.EntitySource.as(products).getEntityState(results[i].entity), upshot.EntityState.ClientDeleted, "expect delete state");
+ } else {
+ upshot.EntitySource.as(products).deleteEntity(results[i].entity);
+ equal(upshot.EntitySource.as(products).getEntityState(results[i].entity), upshot.EntityState.ClientDeleted, "expect delete state");
+ }
+ }
+ if (bufferChanges) {
+ if (revert) {
+ upshot.EntitySource.as(products).revertChanges();
+ for (var i = 0; i < results.length; ++i) {
+ equal(upshot.EntitySource.as(products).getEntityState(results[i].entity), upshot.EntityState.Unmodified, "expect revert state");
+ }
+ revert();
+ } else {
+ upshot.EntitySource.as(products).commitChanges();
+ }
+ }
+ }, 10);
+ }
+
+ for (var d = 0; d < 1; ++d) { // TODO: Destructive delete tests disabled until we can redevelop this feature.
+ for (var b = 0; b < 2; ++b) {
+ (function (bufferChanges, destructive) {
+
+ test((!destructive ? "Non-destructive " : "Destructive ") + "LDS over RDS" + (bufferChanges ? " batch-" : " ") + "success", 7, function () {
+ stop();
+ dsTestDriver.simulateSuccessService(createProductsResult());
+ var rproducts = [];
+ var lproducts = [];
+ var refreshNeeded = 0;
+ var rds = createDataSource(false, rproducts, bufferChanges);
+ var lds = new upshot.LocalDataSource({
+ source: rds,
+ result: lproducts,
+ filter: [{ property: "Price", operator: ">", value: 300 }, { property: "Price", operator: "<", value: 700}]
+ });
+
+ $([lproducts]).bind("replaceAll", function () {
+ equal(lproducts.length, 3, "lcount matched");
+ equal(rproducts.length, 5, "rcount matched");
+ deleteEntities(rproducts, [{ entity: rproducts[1] }, { entity: rproducts[3]}], bufferChanges, destructive);
+ });
+
+ lds.bind({
+ refreshNeeded: function () {
+ ++refreshNeeded;
+ }
+ });
+
+ rds.bind({
+ commitSuccess: function () {
+ // the lproducts is purged automatically thru purge sequence
+ equal(lproducts.length, 1, "lcount matched");
+ equal(rproducts.length, 3, "rcount matched");
+ equal(refreshNeeded, destructive ? 1 : 0, "refreshNeeded matched"); // Non-destructive will only trigger refreshNeeded when the LDS has paging parameters.
+ start();
+ }
+ });
+
+ lds.refresh({ all: true });
+ });
+
+ if (bufferChanges) {
+ test((!destructive ? "Non-destructive " : "Destructive ") + "LDS over RDS" + (bufferChanges ? " batch-" : " ") + "revert", 9, function () {
+ stop();
+ dsTestDriver.simulateSuccessService(createProductsResult());
+ var rproducts = [];
+ var lproducts = [];
+ var refreshNeeded = 0;
+ var rds = createDataSource(false, rproducts, bufferChanges);
+ var lds = new upshot.LocalDataSource({
+ source: rds,
+ result: lproducts,
+ filter: [{ property: "Price", operator: ">", value: 300 }, { property: "Price", operator: "<", value: 700 }]
+ });
+
+ $([lproducts]).bind("replaceAll", function () {
+ var revert = function () {
+ equal(lproducts.length, 3, "lcount matched");
+ equal(rproducts.length, destructive ? 3 : 5, "rcount matched");
+ equal(refreshNeeded, destructive ? 1 : 0, "refreshNeeded matched"); // Non-destructive will only see entity states change. No reason to refresh.
+ start();
+ }
+ equal(lproducts.length, 3, "lcount matched");
+ equal(rproducts.length, 5, "rcount matched");
+ deleteEntities(rproducts, [{ entity: rproducts[1] }, { entity: rproducts[3]}], bufferChanges, destructive, revert);
+ });
+
+ lds.bind({
+ refreshNeeded: function () {
+ ++refreshNeeded;
+ }
+ });
+
+ lds.refresh({ all: true });
+ });
+ }
+ })(!!b, !!d);
+ }
+ }
+
+ for (var d = 0; d < 1; ++d) { // TODO: Destructive delete tests disabled until we can redevelop this feature.
+ for (var b = 0; b < 2; ++b) {
+ (function (bufferChanges, destructive) {
+ // AssociatedEntitiesView does not support batch commit
+ if (!bufferChanges) {
+ test((!destructive ? "Non-destructive " : "Destructive ") + "AssociatedEntitiesView" + (bufferChanges ? " batch-" : " ") + "success", 4, function () {
+ stop();
+ dsTestDriver.simulateSuccessService(createProfileResult());
+ var profiles = [];
+ var profile;
+ var rds = createDataSource(false, profiles, bufferChanges);
+
+ $([profiles]).bind("replaceAll", function () {
+ equal(profiles.length, 1, "profiles length");
+ profile = profiles[0];
+ var friends = profile.Friends;
+ equal(friends.length, 3, "friends length");
+ deleteEntities(friends, [{ entity: friends[1]}], bufferChanges, destructive);
+ });
+
+ rds.bind({
+ commitSuccess: function () {
+ // the lproducts is purged automatically thru purge sequence
+ var friends = profile.Friends;
+ equal(friends.length, 2, "friends length");
+ start();
+ }
+ });
+
+ rds.refresh();
+ });
+ }
+
+ if (bufferChanges) {
+ test((!destructive ? "Non-destructive " : "Destructive ") + "AssociatedEntitiesView" + (bufferChanges ? " batch-" : " ") + "revert", 5, function () {
+ stop();
+ dsTestDriver.simulateSuccessService(createProfileResult());
+ var profiles = [];
+ var profile;
+ var rds = createDataSource(false, profiles, bufferChanges);
+
+ $([profiles]).bind("replaceAll", function () {
+ equal(profiles.length, 1, "profiles length");
+ profile = profiles[0];
+ var friends = profile.Friends;
+ equal(friends.length, 3, "friends length");
+
+ var revert = function () {
+ // the lproducts is purged automatically thru purge sequence
+ var friends = profile.Friends;
+ equal(friends.length, destructive ? 2 : 3, "friends length");
+ start();
+ };
+ deleteEntities(friends, [{ entity: friends[1]}], bufferChanges, destructive, revert);
+ });
+
+ rds.refresh();
+ });
+ }
+
+ })(!!b, !!d);
+ }
+ }
+
+ // return; // TODO, suwatch: below tests multiple variations and scenarios.
+ // commented out for now as it might be hard to debug. Will only be run by me.
+
+ // Testing a combination of ..
+ // LDS vs. RDS
+ // Batch vs. Auto commit
+ // Destructive vs. Non-destructive delete
+ // success vs. failure (failure-revert vs. failure-recommit) vs. simply revert
+ for (var d = 0; d < 1; ++d) { // TODO: Destructive delete tests disabled until we can redevelop this feature.
+ for (var l = 0; l < 2; ++l) {
+ for (var b = 0; b < 2; ++b) {
+
+ (function (useLocal, bufferChanges, destructive) {
+
+ test((!destructive ? "Non-destructive " : "Destructive ") + (!!useLocal ? "LDS" : "RDS") + (bufferChanges ? " batch-" : " ") + "success", 13, function () {
+ stop();
+ dsTestDriver.simulateSuccessService(createProductsResult());
+ var products = [];
+ var removes = [];
+ var refreshNeeded = 0;
+
+ $([products]).bind("replaceAll", function () {
+ if (useLocal) {
+ equal(products.length, 3, "count matched");
+ deleteEntities(products, [{ entity: products[0] }, { entity: products[2]}], bufferChanges, destructive);
+ } else {
+ equal(products.length, 5, "count matched");
+ deleteEntities(products, [{ entity: products[1] }, { entity: products[3]}], bufferChanges, destructive);
+ }
+ });
+
+ $([products]).bind("remove insert", function (event, args) {
+ equal(event.type, "remove", "expect event");
+ removes.push(args.items[0]);
+ });
+
+ var ds = createDataSource(useLocal, products, bufferChanges);
+
+ var refreshNeededObserver = {
+ refreshNeeded: function () {
+ ++refreshNeeded;
+ }
+ };
+
+ var commitObserver = {
+ commitSuccess: function () {
+ equal(removes.length, 2, "removes matched");
+ equal(removes[0].ID, 2, "REMOVE0 matched");
+ equal(removes[1].ID, 4, "REMOVE1 matched");
+ removes = [];
+ if (useLocal) {
+ equal(products.length, 1, "count matched");
+ equal(products[0].ID, 3, "ID0 matched");
+ equal(products[1], undefined, "ID1 matched");
+ equal(products[2], undefined, "ID2 matched");
+ } else {
+ equal(products.length, 3, "count matched");
+ equal(products[0].ID, 1, "ID0 matched");
+ equal(products[1].ID, 3, "ID1 matched");
+ equal(products[2].ID, 5, "ID2 matched");
+ }
+ equal(refreshNeeded, 0, "refreshNeeded event"); // Non-destructive will only trigger refreshNeeded when the LDS has paging parameters.
+ start();
+ }
+ };
+
+ if (useLocal) {
+ // LDS does not have Commit api
+ ds.commitChanges = ds.commitChanges || function () { ds._entitySource.commitChanges(); };
+ ds.bind(refreshNeededObserver);
+ ds._entitySource.bind(commitObserver);
+ } else {
+ ds.bind(commitObserver);
+ }
+
+ ds.refresh(useLocal && { all: true });
+ });
+
+ if (bufferChanges) {
+ test((!destructive ? "Non-destructive " : "Destructive ") + (!!useLocal ? "LDS" : "RDS") + " batch-revert", 8, function () {
+ stop();
+ dsTestDriver.simulateSuccessService(createProductsResult());
+ var products = [];
+ var removes = [];
+ var refreshNeeded = 0;
+ $([products]).bind("replaceAll", function () {
+ var revert = function () {
+ equal(removes.length, destructive ? 2 : 0, "removes length matched");
+ removes = [];
+ if (useLocal) {
+ equal(products.length, destructive ? 1 : 3, "count matched");
+ } else {
+ equal(products.length, destructive ? 3 : 5, "count matched");
+ }
+ // revert destructive should raise refreshNeeded event
+ equal(refreshNeeded, (useLocal && destructive) ? 1 : 0, "refreshNeeded event"); // Non-destructive will only see entity states change. No reason to refresh.
+ start();
+ };
+ if (useLocal) {
+ equal(products.length, 3, "count matched");
+ deleteEntities(products, [{ entity: products[0] }, { entity: products[2]}], bufferChanges, destructive, revert);
+ } else {
+ equal(products.length, 5, "count matched");
+ deleteEntities(products, [{ entity: products[1] }, { entity: products[3]}], bufferChanges, destructive, revert);
+ }
+ });
+ $([products]).bind("remove", function (event, args) {
+ //equal(event.type, "remove", "expect event");
+ removes.push(args.items[0]);
+ });
+ var ds = createDataSource(useLocal, products, bufferChanges);
+ if (useLocal) {
+ ds.bind({ refreshNeeded: function () { refreshNeeded++; } });
+ }
+ ds.refresh(useLocal && { all: true });
+ });
+ }
+
+ if (!destructive) {
+ test((!destructive ? "Non-destructive " : "Destructive ") + (!!useLocal ? "LDS" : "RDS") + (bufferChanges ? " batch-" : " ") + "failure-recommit", 21, function () {
+ stop();
+ dsTestDriver.simulateSuccessService(createProductsResult());
+ var products = [];
+ var removes = [];
+ var refreshNeeded = 0;
+ $([products]).bind("replaceAll", function () {
+ if (useLocal) {
+ equal(products.length, 3, "count matched");
+ deleteEntities(products, [{ entity: products[0] }, { entity: products[2], errors: [{ Message: "Simulated failure!"}]}], bufferChanges, destructive);
+ } else {
+ equal(products.length, 5, "count matched");
+ deleteEntities(products, [{ entity: products[1] }, { entity: products[3], errors: [{ Message: "Simulated failure!"}]}], bufferChanges, destructive);
+ }
+ });
+ $([products]).bind("remove", function (event, args) {
+ //equal(event.type, "remove", "expect event");
+ removes.push(args.items[0]);
+ });
+ var ds = createDataSource(useLocal, products, bufferChanges);
+
+ var refreshNeededObserver = {
+ refreshNeeded: function () {
+ ++refreshNeeded;
+ }
+ };
+
+ var commitObserver = {
+ commitSuccess: function () {
+ equal(removes.length, 1, "removes matched");
+ equal(removes[0].ID, 4, "REMOVE0 matched");
+ removes = [];
+ if (useLocal) {
+ equal(products.length, 1, "count matched");
+ equal(products[0].ID, 3, "ID1 matched");
+ equal(products[1], undefined, "ID1 matched");
+ equal(products[2], undefined, "ID2 matched");
+ } else {
+ equal(products.length, 3, "count matched");
+ equal(products[0].ID, 1, "ID0 matched");
+ equal(products[1].ID, 3, "ID1 matched");
+ equal(products[2].ID, 5, "ID2 matched");
+ }
+ equal(refreshNeeded, (useLocal && destructive) ? 1 : 0, "refreshNeeded event"); // Non-destructive will only trigger refreshNeeded when the LDS has paging parameters.
+ start();
+ },
+ commitError: function (httpStatus, errorText) {
+ equal(removes.length, destructive ? 2 : 1, "removes matched");
+ equal(removes[0].ID, 2, "REMOVE0 matched");
+ var removed;
+ if (destructive) {
+ equal(removes[1].ID, 4, "REMOVE1 matched");
+ removed = removes[1];
+ } else {
+ equal(removes[1], undefined, "REMOVE1 matched");
+ }
+ removes = [];
+ if (useLocal) {
+ equal(products.length, destructive ? 1 : 2, "count matched");
+ equal(errorText, "Simulated failure!", "errorText matched");
+ equal(products[0].ID, 3, "ID0 matched");
+ if (destructive) {
+ equal(products[1], undefined, "ID1 matched");
+ equal(this.getEntityState(removed), upshot.EntityState.ClientDeleted, "state checked");
+ } else {
+ equal(products[1].ID, 4, "ID1 matched");
+ equal(this.getEntityState(products[1]), upshot.EntityState.ClientDeleted, "state checked");
+ }
+ equal(products[2], undefined, "ID2 matched");
+ equal(products[3], undefined, "ID3 matched");
+ deleteEntities(products, [{ entity: removed || products[1]}], bufferChanges, destructive);
+ } else {
+ equal(products.length, destructive ? 3 : 4, "count matched");
+ equal(errorText, "Simulated failure!", "errorText matched");
+ equal(products[0].ID, 1, "ID0 matched");
+ equal(products[1].ID, 3, "ID1 matched");
+ if (destructive) {
+ equal(products[2].ID, 5, "ID2 matched");
+ equal(products[3], undefined, "ID3 matched");
+ equal(this.getEntityState(removed), undefined, "state checked");
+ } else {
+ equal(products[2].ID, 4, "ID2 matched");
+ equal(products[3].ID, 5, "ID3 matched");
+ equal(this.getEntityState(products[2]), upshot.EntityState.ClientDeleted, "state checked");
+ }
+ deleteEntities(products, [{ entity: removed || products[2]}], bufferChanges, destructive);
+ }
+ }
+ };
+
+ if (useLocal) {
+ // LDS does not have Commit api
+ ds.commitChanges = ds.commitChanges || function () { ds._entitySource.commitChanges(); };
+ ds.bind(refreshNeededObserver);
+ ds._entitySource.bind(commitObserver);
+ } else {
+ ds.bind(commitObserver);
+ }
+
+ ds.refresh(useLocal && { all: true });
+ });
+ }
+
+ test((!destructive ? "Non-destructive " : "Destructive ") + (!!useLocal ? "LDS" : "RDS") + (bufferChanges ? " batch-" : " ") + "failure-revert", 15, function () {
+ stop();
+ dsTestDriver.simulateSuccessService(createProductsResult());
+ var products = [];
+ var removes = [];
+ var refreshNeeded = 0;
+
+ $([products]).bind("replaceAll", function () {
+ if (useLocal) {
+ equal(products.length, 3, "count matched");
+ deleteEntities(products, [{ entity: products[0] }, { entity: products[2], errors: [{ Message: "Simulated failure!"}]}], bufferChanges, destructive);
+ } else {
+ equal(products.length, 5, "count matched");
+ deleteEntities(products, [{ entity: products[1] }, { entity: products[3], errors: [{ Message: "Simulated failure!"}]}], bufferChanges, destructive);
+ }
+ });
+
+ $([products]).bind("remove", function (event, args) {
+ //equal(event.type, "remove", "expect event");
+ removes.push(args.items[0]);
+ });
+
+ var ds = createDataSource(useLocal, products, bufferChanges);
+
+ var refreshNeededObserver = {
+ refreshNeeded: function () {
+ refreshNeeded++;
+ }
+ };
+
+ var commitObserver = {
+ commitError: function (httpStatus, errorText) {
+ equal(removes.length, destructive ? 2 : 1, "removes matched");
+ equal(removes[0].ID, 2, "REMOVE0 matched");
+ var tmpProducts = [];
+ $.each(products, function (unused, product) {
+ tmpProducts.push(product);
+ });
+
+ if (destructive) {
+ equal(removes[1].ID, 4, "REMOVE1 matched");
+ tmpProducts.splice(useLocal ? 1 : 2, 0, removes[1]);
+ } else {
+ equal(removes[1], undefined, "REMOVE1 matched");
+ }
+ removes = [];
+
+ if (useLocal) {
+ equal(tmpProducts.length, 2, "count matched");
+ equal(errorText, "Simulated failure!", "errorText matched");
+ equal(tmpProducts[0].ID, 3, "ID0 matched");
+ equal(tmpProducts[1].ID, 4, "ID1 matched");
+ equal(tmpProducts[2], undefined, "ID2 matched");
+ equal(tmpProducts[3], undefined, "ID3 matched");
+ equal(this.getEntityState(tmpProducts[1]), upshot.EntityState.ClientDeleted, "state checked");
+ this.revertChanges();
+ equal(this.getEntityState(tmpProducts[1]), upshot.EntityState.Unmodified, "state checked");
+ } else {
+ equal(tmpProducts.length, 4, "count matched");
+ equal(errorText, "Simulated failure!", "errorText matched");
+ equal(tmpProducts[0].ID, 1, "ID0 matched");
+ equal(tmpProducts[1].ID, 3, "ID1 matched");
+ equal(tmpProducts[2].ID, 4, "ID2 matched");
+ equal(tmpProducts[3].ID, 5, "ID3 matched");
+ equal(this.getEntityState(tmpProducts[2]), upshot.EntityState.ClientDeleted, "state checked");
+ this.revertChanges();
+ equal(this.getEntityState(tmpProducts[2]), upshot.EntityState.Unmodified, "state checked");
+ }
+
+ equal(refreshNeeded, (useLocal && destructive) ? 1 : 0, "refreshNeeded event"); // Non-destructive will only see entity states change. No reason to refresh.
+ start();
+ }
+ };
+
+ if (useLocal) {
+ // LDS does not have Commit api
+ ds.commitChanges = ds.commitChanges || function () { ds._entitySource.commitChanges(); };
+ ds.bind(refreshNeededObserver);
+ ds._entitySource.bind(commitObserver);
+ } else {
+ ds.bind(commitObserver);
+ }
+
+ ds.refresh(useLocal && { all: true });
+ });
+
+ })(!!l, !!b, !!d);
+ }
+ }
+ }
+
+ for (var d = 0; d < 1; ++d) { // TODO: Destructive delete tests disabled until we can redevelop this feature.
+ for (var b = 0; b < 2; ++b) {
+ (function (bufferChanges, destructive) {
+
+ test((!destructive ? "Non-destructive " : "Destructive ") + "LDS over RDS" + (bufferChanges ? " batch-" : " ") + "success", 7, function () {
+ stop();
+ dsTestDriver.simulateSuccessService(createProductsResult());
+ var rproducts = [];
+ var lproducts = [];
+ var refreshNeeded = 0;
+ var rds = createDataSource(false, rproducts, bufferChanges);
+ var lds = new upshot.LocalDataSource({
+ source: rds,
+ result: lproducts,
+ filter: [{ property: "Price", operator: ">", value: 300 }, { property: "Price", operator: "<", value: 700 }]
+ });
+
+ $([lproducts]).bind("replaceAll", function () {
+ equal(lproducts.length, 3, "lcount matched");
+ equal(rproducts.length, 5, "rcount matched");
+ deleteEntities(rproducts, [{ entity: rproducts[1] }, { entity: rproducts[3]}], bufferChanges, destructive);
+ });
+
+ lds.bind({
+ refreshNeeded: function () {
+ ++refreshNeeded;
+ }
+ });
+
+ rds.bind({
+ commitSuccess: function () {
+ // the lproducts is purged automatically thru purge sequence
+ equal(lproducts.length, 1, "lcount matched");
+ equal(rproducts.length, 3, "rcount matched");
+ equal(refreshNeeded, destructive ? 1 : 0, "refreshNeeded matched"); // Non-destructive will only trigger refreshNeeded when the LDS has paging parameters.
+ start();
+ }
+ });
+
+ lds.refresh({ all: true });
+ });
+
+ if (bufferChanges) {
+ test((!destructive ? "Non-destructive " : "Destructive ") + "LDS over RDS" + (bufferChanges ? " batch-" : " ") + "revert", 9, function () {
+ stop();
+ dsTestDriver.simulateSuccessService(createProductsResult());
+ var rproducts = [];
+ var lproducts = [];
+ var refreshNeeded = 0;
+ var rds = createDataSource(false, rproducts, bufferChanges);
+ var lds = new upshot.LocalDataSource({
+ source: rds,
+ result: lproducts,
+ filter: [{ property: "Price", operator: ">", value: 300 }, { property: "Price", operator: "<", value: 700 }]
+ });
+
+ $([lproducts]).bind("replaceAll", function () {
+ var revert = function () {
+ equal(lproducts.length, 3, "lcount matched");
+ equal(rproducts.length, destructive ? 3 : 5, "rcount matched");
+ equal(refreshNeeded, destructive ? 1 : 0, "refreshNeeded matched"); // Non-destructive will only see entity states change. No reason to refresh.
+ start();
+ }
+ equal(lproducts.length, 3, "lcount matched");
+ equal(rproducts.length, 5, "rcount matched");
+ deleteEntities(rproducts, [{ entity: rproducts[1] }, { entity: rproducts[3]}], bufferChanges, destructive, revert);
+ });
+
+ lds.bind({
+ refreshNeeded: function () {
+ ++refreshNeeded;
+ }
+ });
+
+ lds.refresh({ all: true });
+ });
+ }
+ })(!!b, !!d);
+ }
+ }
+
+ for (var d = 0; d < 1; ++d) { // TODO: Destructive delete tests disabled until we can redevelop this feature.
+ for (var b = 0; b < 2; ++b) {
+ (function (bufferChanges, destructive) {
+ // AssociatedEntitiesView does not support batch commit
+ if (!bufferChanges) {
+ test((!destructive ? "Non-destructive " : "Destructive ") + "AssociatedEntitiesView" + (bufferChanges ? " batch-" : " ") + "success", 4, function () {
+ stop();
+ dsTestDriver.simulateSuccessService(createProfileResult());
+ var profiles = [];
+ var profile;
+ var rds = createDataSource(false, profiles, bufferChanges);
+
+ $([profiles]).bind("replaceAll", function () {
+ equal(profiles.length, 1, "profiles length");
+ profile = profiles[0];
+ var friends = profile.Friends;
+ equal(friends.length, 3, "friends length");
+ deleteEntities(friends, [{ entity: friends[1]}], bufferChanges, destructive);
+ });
+
+ rds.bind({
+ commitSuccess: function () {
+ // the lproducts is purged automatically thru purge sequence
+ var friends = profile.Friends;
+ equal(friends.length, 2, "friends length");
+ start();
+ }
+ });
+
+ rds.refresh();
+ });
+ }
+
+ if (bufferChanges) {
+ test((!destructive ? "Non-destructive " : "Destructive ") + "AssociatedEntitiesView" + (bufferChanges ? " batch-" : " ") + "revert", 5, function () {
+ stop();
+ dsTestDriver.simulateSuccessService(createProfileResult());
+ var profiles = [];
+ var profile;
+ var rds = createDataSource(false, profiles, bufferChanges);
+
+ $([profiles]).bind("replaceAll", function () {
+ equal(profiles.length, 1, "profiles length");
+ profile = profiles[0];
+ var friends = profile.Friends;
+ equal(friends.length, 3, "friends length");
+
+ var revert = function () {
+ // the lproducts is purged automatically thru purge sequence
+ var friends = profile.Friends;
+ equal(friends.length, destructive ? 2 : 3, "friends length");
+ start();
+ };
+ deleteEntities(friends, [{ entity: friends[1]}], bufferChanges, destructive, revert);
+ });
+
+ rds.refresh();
+ });
+ }
+
+ })(!!b, !!d);
+ }
+ }
+
+})(this, upshot);
diff --git a/test/SPA.Test/upshot/EntitySet.tests.js b/test/SPA.Test/upshot/EntitySet.tests.js
new file mode 100644
index 00000000..d9b60acb
--- /dev/null
+++ b/test/SPA.Test/upshot/EntitySet.tests.js
@@ -0,0 +1,1525 @@
+/// <reference path="../Scripts/References.js" />
+
+(function (global, $, upshot, undefined) {
+
+ module("EntitySet.tests.js");
+
+ var datasets = upshot.test.datasets,
+ observability = upshot.observability;
+
+ // direct tests against the default identity algorithm
+ test("Compute identity tests", 14, function () {
+ var metadata = {
+ A: { key: ["k1"] },
+ B: { key: ["k1", "k2", "k3"] },
+ C: {}, // missing key metadata
+ D: { key: ["a.b.k1", "a.b.k2"] }
+ };
+ var dc = new upshot.DataContext(new upshot.riaDataProvider());
+ upshot.metadata(metadata);
+
+ // valid single part key scenarios
+ equal(upshot.EntitySet.__getIdentity({ k1: "1" }, "A"), "1");
+ equal(upshot.EntitySet.__getIdentity({ k1: 1234 }, "A"), "1234");
+ equal(upshot.EntitySet.__getIdentity({ k1: 1.234 }, "A"), "1.234");
+ equal(upshot.EntitySet.__getIdentity({ k1: "E93CF47D-E7A6-44B2-8E30-0869B187BFB4" }, "A"), "E93CF47D-E7A6-44B2-8E30-0869B187BFB4");
+
+ // valid multipart key scenarios
+ equal(upshot.EntitySet.__getIdentity({ k1: "1", k2: "2", k3: "3" }, "B"), "1,2,3");
+ equal(upshot.EntitySet.__getIdentity({ k1: 1, k2: 2, k3: 3 }, "B"), "1,2,3");
+ equal(upshot.EntitySet.__getIdentity({ k1: true, k2: "E93CF47D-E7A6-44B2-8E30-0869B187BFB4", k3: 5 }, "B"), "true,E93CF47D-E7A6-44B2-8E30-0869B187BFB4,5");
+ equal(upshot.EntitySet.__getIdentity({ k1: 1, k2: null, k3: undefined }, "B"), "1,null,undefined");
+
+ // verify pathing scenarios (e.g. used by the OData provider)
+ equal(upshot.EntitySet.__getIdentity({ a: { b: { k1: "1", k2: "2"}} }, "D"), "1,2");
+
+ // no key metadata specified
+ try {
+ upshot.EntitySet.__getIdentity({ k1: "1", k2: "2", k3: "3" }, "C", "C");
+ }
+ catch (e) {
+ equal(e, "No key metadata specified for entity type 'C'");
+ }
+
+ // invalid key member specification - missing member
+ try {
+ upshot.EntitySet.__getIdentity({ k1: "1", k2: "2" /* missing k3 member */ }, "B");
+ }
+ catch (e) {
+ equal(e, "Key member 'k3' doesn't exist on entity type 'B'");
+ }
+
+ // invalid path specification - missing member
+ try {
+ upshot.EntitySet.__getIdentity({ a: { b: { k1: "1" /* k2 member missing */}} }, "D");
+ }
+ catch (e) {
+ equal(e, "Key member 'a.b.k2' doesn't exist on entity type 'D'");
+ }
+
+ // invalid path specification - null path part
+ try {
+ upshot.EntitySet.__getIdentity({ a: { b: null} }, "D");
+ }
+ catch (e) {
+ equal(e, "Key member 'a.b.k1' doesn't exist on entity type 'D'");
+ }
+
+ // no metadata registered for type
+ try {
+ upshot.EntitySet.__getIdentity({}, "E");
+ }
+ catch (e) {
+ equal(e, "No metadata available for type 'E'. Register metadata using 'upshot.metadata(...)'.");
+ }
+ });
+
+ test("Load entities with multipart keys", 4, function () {
+ var metadata = {
+ A: { key: ["k1"] },
+ B: { key: ["k1", "k2", "k3"] }
+ };
+ var dc = new upshot.DataContext(new upshot.riaDataProvider());
+ upshot.metadata(metadata);
+
+ var entities = [{ k1: "1" }, { k1: 1234 }, { k1: 1.234 }, { k1: "E93CF47D-E7A6-44B2-8E30-0869B187BFB4"}];
+ dc.merge(entities, "A", null);
+
+ entities = [
+ { k1: "1", k2: "2", k3: "3" },
+ { k1: 1, k2: 2, k3: 3 },
+ { k1: true, k2: "E93CF47D-E7A6-44B2-8E30-0869B187BFB4", k3: 5 }
+ ];
+ dc.merge(entities, "B", null);
+
+ // verify the entities are in the cache
+ equal(dc.getEntitySet("A")._entityStates["1"], upshot.EntityState.Unmodified);
+ equal(dc.getEntitySet("A")._entityStates["E93CF47D-E7A6-44B2-8E30-0869B187BFB4"], upshot.EntityState.Unmodified);
+ equal(dc.getEntitySet("B")._entityStates["1,2,3"], upshot.EntityState.Unmodified);
+ equal(dc.getEntitySet("B")._entityStates["true,E93CF47D-E7A6-44B2-8E30-0869B187BFB4,5"], upshot.EntityState.Unmodified);
+ });
+
+ test("Raises events when primitive values change", 10, function () {
+ var es = createEntitySet(),
+ entity = loadEntities(es, datasets.primitives.create(1));
+
+ equal(es.getEntities().length, 1, "There should be a single entity loaded");
+
+ var tracker = createEventTracker(es);
+
+ $.observable(entity).property("B", false);
+
+ equal(tracker.events.length, 2, "There should have been two events");
+ equal(tracker.events[0].type, "propertyChanged", "The event should be 'propertyChanged'");
+ equal(tracker.events[0].entity, entity, "The entity should have been modified");
+ equal(tracker.events[0].path, "B", "Property 'B' should have been set");
+ equal(tracker.events[0].value, entity.B, "The new value should be false");
+ equal(tracker.events[1].type, "entityUpdated", "The event should be 'entityUpdated'");
+ equal(tracker.events[1].entity, entity, "The entity should have been modified");
+ equal(tracker.events[1].path, "", "The event should have occured on the entity");
+ notEqual(tracker.events[1].eventArgs, null, "The event args should not be null");
+ });
+
+ test("Raises events when scalar values change", 10, function () {
+ var es = createEntitySet(),
+ entity = loadEntities(es, datasets.scalars.create(1));
+
+ equal(es.getEntities().length, 1, "There should be a single entity loaded");
+
+ var tracker = createEventTracker(es);
+
+ $.observable(entity).property("D", new Date());
+
+ equal(tracker.events.length, 2, "There should have been two events");
+ equal(tracker.events[0].type, "propertyChanged", "The event should be 'propertyChanged'");
+ equal(tracker.events[0].entity, entity, "The entity should have been modified");
+ equal(tracker.events[0].path, "D", "Property 'D' should have been set");
+ equal(tracker.events[0].value, entity.D, "The new value should be false");
+ equal(tracker.events[1].type, "entityUpdated", "The event should be 'entityUpdated'");
+ equal(tracker.events[1].entity, entity, "The entity should have been modified");
+ equal(tracker.events[1].path, "", "The event should have occured on the entity");
+ notEqual(tracker.events[1].eventArgs, null, "The event args should not be null");
+ });
+
+ test("Raises event when nested values change", 6, function () {
+ var es = createEntitySet(),
+ entity = loadEntities(es, datasets.nested.create(1));
+
+ equal(es.getEntities().length, 1, "There should be a single entity loaded");
+
+ var tracker = createEventTracker(es);
+
+ $.observable(entity.O).property("B", false);
+
+ equal(tracker.events.length, 1, "There should have been one event");
+ equal(tracker.events[0].type, "entityUpdated", "The event should be 'entityUpdated'");
+ equal(tracker.events[0].entity, entity, "The entity should have been modified");
+ equal(tracker.events[0].path, "O", "The event should have occured on 'O'");
+ notEqual(tracker.events[0].eventArgs, null, "The event args should not be null");
+ });
+
+ test("Raises event when tree values change", 6, function () {
+ var es = createEntitySet(),
+ entity = loadEntities(es, datasets.tree.create(1));
+
+ equal(es.getEntities().length, 1, "There should be a single entity loaded");
+
+ var tracker = createEventTracker(es);
+
+ $.observable(entity.O.O1).property("B", false);
+
+ equal(tracker.events.length, 1, "There should have been one event");
+ equal(tracker.events[0].type, "entityUpdated", "The event should be 'entityUpdated'");
+ equal(tracker.events[0].entity, entity, "The entity should have been modified");
+ equal(tracker.events[0].path, "O.O1", "The event should have occured on 'O.O1'");
+ notEqual(tracker.events[0].eventArgs, null, "The event args should not be null");
+ });
+
+ test("Raises event when array values change", 6, function () {
+ var es = createEntitySet(),
+ entity = loadEntities(es, datasets.array.create(1));
+
+ equal(es.getEntities().length, 1, "There should be a single entity loaded");
+
+ var tracker = createEventTracker(es);
+
+ $.observable(entity.A[0]).property("B", false);
+
+ equal(tracker.events.length, 1, "There should have been one event");
+ equal(tracker.events[0].type, "entityUpdated", "The event should be 'entityUpdated'");
+ equal(tracker.events[0].entity, entity, "The entity should have been modified");
+ equal(tracker.events[0].path, "A[0]", "The event should have occured on 'A[0]'");
+ notEqual(tracker.events[0].eventArgs, null, "The event args should not be null");
+ });
+
+ test("Raises event when nested array values change", 6, function () {
+ var es = createEntitySet(),
+ entity = loadEntities(es, datasets.nestedArrays.create(1));
+
+ equal(es.getEntities().length, 1, "There should be a single entity loaded");
+
+ var tracker = createEventTracker(es);
+
+ $.observable(entity.A[0].A[0]).property("B", false);
+
+ equal(tracker.events.length, 1, "There should have been one event");
+ equal(tracker.events[0].type, "entityUpdated", "The event should be 'entityUpdated'");
+ equal(tracker.events[0].entity, entity, "The entity should have been modified");
+ equal(tracker.events[0].path, "A[0].A[0]", "The event should have occured on 'A[0].A[0]'");
+ notEqual(tracker.events[0].eventArgs, null, "The event args should not be null");
+ });
+
+ test("Raises events on revertChanges", 11, function () {
+ var es = createEntitySet(),
+ entity = loadEntities(es, datasets.primitives.create(1));
+
+ equal(es.getEntities().length, 1, "There should be a single entity loaded");
+
+ var tracker = createEventTracker(es);
+
+ $.observable(entity).property("B", false);
+
+ equal(tracker.events.length, 2, "There should have been two events");
+
+ tracker.events.length = 0;
+
+ es.revertChanges();
+
+ equal(tracker.events.length, 2, "There should have been two events");
+ equal(tracker.events[0].type, "propertyChanged", "The event should be 'propertyChanged'");
+ equal(tracker.events[0].entity, entity, "The entity should have been modified");
+ equal(tracker.events[0].path, "B", "Property 'B' should have been set");
+ equal(tracker.events[0].value, entity.B, "The reverted value should be true");
+ equal(tracker.events[1].type, "entityUpdated", "The event should be 'entityUpdated'");
+ equal(tracker.events[1].entity, entity, "The entity should have been modified");
+ equal(tracker.events[1].path, undefined, "The event path should be undefined");
+ equal(tracker.events[1].eventArgs, undefined, "The event args should be undefined");
+ });
+
+ test("Raises events on revertChanges with entity", 11, function () {
+ var es = createEntitySet(),
+ entity = loadEntities(es, datasets.primitives.create(1));
+
+ equal(es.getEntities().length, 1, "There should be a single entity loaded");
+
+ var tracker = createEventTracker(es);
+
+ $.observable(entity).property("B", false);
+
+ equal(tracker.events.length, 2, "There should have been two events");
+
+ tracker.events.length = 0;
+
+ es.revertChanges(entity);
+
+ equal(tracker.events.length, 2, "There should have been two events");
+ equal(tracker.events[0].type, "propertyChanged", "The event should be 'propertyChanged'");
+ equal(tracker.events[0].entity, entity, "The entity should have been modified");
+ equal(tracker.events[0].path, "B", "Property 'B' should have been set");
+ equal(tracker.events[0].value, entity.B, "The reverted value should be true");
+ equal(tracker.events[1].type, "entityUpdated", "The event should be 'entityUpdated'");
+ equal(tracker.events[1].entity, entity, "The entity should have been modified");
+ equal(tracker.events[1].path, undefined, "The event path should be undefined");
+ equal(tracker.events[1].eventArgs, undefined, "The event args should be undefined");
+ });
+
+ test("Raises events on revertUpdates", 11, function () {
+ var es = createEntitySet(),
+ entity = loadEntities(es, datasets.primitives.create(1));
+
+ equal(es.getEntities().length, 1, "There should be a single entity loaded");
+
+ var tracker = createEventTracker(es);
+
+ $.observable(entity).property("B", false);
+
+ equal(tracker.events.length, 2, "There should have been two events");
+
+ tracker.events.length = 0;
+
+ es.revertUpdates(entity);
+
+ equal(tracker.events.length, 2, "There should have been two events");
+ equal(tracker.events[0].type, "propertyChanged", "The event should be 'propertyChanged'");
+ equal(tracker.events[0].entity, entity, "The entity should have been modified");
+ equal(tracker.events[0].path, "B", "Property 'B' should have been set");
+ equal(tracker.events[0].value, entity.B, "The reverted value should be true");
+ equal(tracker.events[1].type, "entityUpdated", "The event should be 'entityUpdated'");
+ equal(tracker.events[1].entity, entity, "The entity should have been modified");
+ equal(tracker.events[1].path, undefined, "The event path should be undefined");
+ equal(tracker.events[1].eventArgs, undefined, "The event args should be undefined");
+ });
+
+ test("Raises events on revertUpdates for property", 11, function () {
+ var es = createEntitySet(),
+ entity = loadEntities(es, datasets.primitives.create(1));
+
+ equal(es.getEntities().length, 1, "There should be a single entity loaded");
+
+ var tracker = createEventTracker(es);
+
+ $.observable(entity).property("B", false);
+
+ equal(tracker.events.length, 2, "There should have been two events");
+
+ tracker.events.length = 0;
+
+ es.revertUpdates(entity, "B");
+
+ equal(tracker.events.length, 2, "There should have been two events");
+ equal(tracker.events[0].type, "propertyChanged", "The event should be 'propertyChanged'");
+ equal(tracker.events[0].entity, entity, "The entity should have been modified");
+ equal(tracker.events[0].path, "B", "Property 'B' should have been set");
+ equal(tracker.events[0].value, entity.B, "The reverted value should be true");
+ equal(tracker.events[1].type, "entityUpdated", "The event should be 'entityUpdated'");
+ equal(tracker.events[1].entity, entity, "The entity should have been modified");
+ equal(tracker.events[1].path, undefined, "The event path should be undefined");
+ equal(tracker.events[1].eventArgs, undefined, "The event args should be undefined");
+ });
+
+ test("Raises event when knockout array values change", 6, function () {
+ try {
+ upshot.observability.configuration = observability.knockout;
+
+ var es = createEntitySet(),
+ entity = loadEntities(es, datasets.ko_array(1));
+
+ equal(es.getEntities()().length, 1, "There should be a single entity loaded");
+
+ var tracker = createEventTracker(es);
+
+ entity.A()[0].B(false);
+
+ equal(tracker.events.length, 1, "There should have been one event");
+ equal(tracker.events[0].type, "entityUpdated", "The event should be 'entityUpdated'");
+ equal(tracker.events[0].entity, entity, "The entity should have been modified");
+ equal(tracker.events[0].path, "A[0]", "The event should have occured on 'A[0]'");
+ notEqual(tracker.events[0].eventArgs, null, "The event args should not be null");
+ } finally {
+ upshot.observability.configuration = observability.jquery;
+ }
+ });
+
+ test("Subscriptions are adjusted when nested values change", 5, function () {
+ var es = createEntitySet(),
+ entity = loadEntities(es, datasets.nested.create(1));
+
+ equal(es.getEntities().length, 1, "There should be a single entity loaded");
+
+ var original = entity.O,
+ _new = { B: false };
+ var tracker = createEventTracker(es);
+
+ $.observable(original).property("B", false);
+
+ equal(tracker.events.length, 1, "There should have been one event");
+
+ tracker.events.length = 0;
+
+ $.observable(entity).property("O", _new);
+
+ equal(tracker.events.length, 2, "There should have been two events");
+
+ tracker.events.length = 0;
+
+ $.observable(original).property("B", true);
+
+ equal(tracker.events.length, 0, "There should not have been any events");
+
+ $.observable(_new).property("B", true);
+
+ equal(tracker.events.length, 1, "There should have been one event");
+ });
+
+ test("Subscriptions are adjusted when tree values change", 5, function () {
+ var es = createEntitySet(),
+ entity = loadEntities(es, datasets.tree.create(1));
+
+ equal(es.getEntities().length, 1, "There should be a single entity loaded");
+
+ var original = entity.O,
+ _new = { O1: { B: false} };
+ var tracker = createEventTracker(es);
+
+ $.observable(original.O1).property("B", false);
+
+ equal(tracker.events.length, 1, "There should have been one event");
+
+ tracker.events.length = 0;
+
+ $.observable(entity).property("O", _new);
+
+ equal(tracker.events.length, 2, "There should have been two events");
+
+ tracker.events.length = 0;
+
+ $.observable(original.O1).property("B", true);
+
+ equal(tracker.events.length, 0, "There should not have been any events");
+
+ tracker.events.length = 0;
+
+ $.observable(_new.O1).property("B", true);
+
+ equal(tracker.events.length, 1, "There should have been one event");
+ });
+
+ test("Subscriptions are added on array insert", 3, function () {
+ var es = createEntitySet(),
+ entity = loadEntities(es, datasets.array.create(1));
+
+ equal(es.getEntities().length, 1, "There should be a single entity loaded");
+
+ var _new = { B: false };
+ var tracker = createEventTracker(es);
+
+ $.observable(entity.A).insert(entity.A.length, _new);
+
+ equal(tracker.events.length, 1, "There should have been one event");
+
+ tracker.events.length = 0;
+
+ $.observable(_new).property("B", true);
+
+ equal(tracker.events.length, 1, "There should have been one event");
+ });
+
+ test("Subscriptions are removed on array remove", 4, function () {
+ var es = createEntitySet(),
+ entity = loadEntities(es, datasets.array.create(1));
+
+ equal(es.getEntities().length, 1, "There should be a single entity loaded");
+
+ var original = entity.A[0];
+ var tracker = createEventTracker(es);
+
+ $.observable(original).property("B", false);
+
+ equal(tracker.events.length, 1, "There should have been one event");
+
+ tracker.events.length = 0;
+
+ $.observable(entity.A).remove(0, 1);
+
+ equal(tracker.events.length, 1, "There should have been one event");
+
+ tracker.events.length = 0;
+
+ $.observable(original).property("B", true);
+
+ equal(tracker.events.length, 0, "There should not have been any events");
+ });
+
+ test("Subscriptions are adjusted on array replaceAll", 5, function () {
+ var es = createEntitySet(),
+ entity = loadEntities(es, datasets.array.create(1));
+
+ equal(es.getEntities().length, 1, "There should be a single entity loaded");
+
+ var original = entity.A[0],
+ _new = { B: false };
+ var tracker = createEventTracker(es);
+
+ $.observable(original).property("B", false);
+
+ equal(tracker.events.length, 1, "There should have been one event");
+
+ tracker.events.length = 0;
+
+ $.observable(entity.A).replaceAll([_new]);
+
+ equal(tracker.events.length, 1, "There should have been one event");
+
+ tracker.events.length = 0;
+
+ $.observable(original).property("B", true);
+
+ equal(tracker.events.length, 0, "There should not have been any events");
+
+ tracker.events.length = 0;
+
+ $.observable(_new).property("B", true);
+
+ equal(tracker.events.length, 1, "There should have been one event");
+ });
+
+ testWithRevertVariations(function (revertFn) {
+ test("isUpdated returns true when primitive values change", 17, function () {
+ var es = createEntitySet(),
+ entity = loadEntities(es, datasets.primitives.create(1));
+
+ equal(es.getEntities().length, 1, "There should be a single entity loaded");
+
+ equal(es.getEntityState(entity), upshot.EntityState.Unmodified, "The entity state should be 'Unmodified'");
+ equal(es.isUpdated(entity), false, "The entity should not have changes");
+ equal(es.isUpdated(entity, "B"), false, "The entity should not have changes for 'B'");
+
+ $.observable(entity).property("B", false);
+
+ equal(es.getEntityState(entity), upshot.EntityState.ClientUpdated, "The entity state should be 'ClientUpdated'");
+ equal(es.isUpdated(entity), true, "The entity should have changes");
+ equal(es.isUpdated(entity, "B"), true, "The entity should have changes for 'B'");
+ equal(es.isUpdated(entity, "N"), false, "The entity should not have changes for 'N'");
+
+ $.observable(entity).property("B", true);
+
+ equal(es.getEntityState(entity), upshot.EntityState.ClientUpdated, "The entity state should still be 'ClientUpdated'");
+ equal(es.isUpdated(entity), true, "The entity should still have changes");
+ equal(es.isUpdated(entity, "B"), true, "The entity should still have changes for 'B'");
+ equal(es.isUpdated(entity, "N"), false, "The entity should still not have changes for 'N'");
+
+ revertFn(es, entity, "B");
+
+ equal(es.isUpdated(entity, "B"), false, "The entity should no longer have changes for 'B'");
+ dataEqual(entity, datasets.primitives.create(1), "The entity should be reverted");
+ });
+ });
+
+ testWithRevertVariations(function (revertFn) {
+ test("isUpdated returns true when scalar values change", 12, function () {
+ var es = createEntitySet(),
+ entity = loadEntities(es, datasets.scalars.create(1));
+
+ equal(es.getEntities().length, 1, "There should be a single entity loaded");
+
+ equal(es.getEntityState(entity), upshot.EntityState.Unmodified, "The entity state should be 'Unmodified'");
+ equal(es.isUpdated(entity), false, "The entity should not have changes");
+ equal(es.isUpdated(entity, "D"), false, "The entity should not have changes for 'D'");
+
+ $.observable(entity).property("D", new Date());
+
+ equal(es.getEntityState(entity), upshot.EntityState.ClientUpdated, "The entity state should be 'ClientUpdated'");
+ equal(es.isUpdated(entity), true, "The entity should have changes");
+ equal(es.isUpdated(entity, "D"), true, "The entity should have changes for 'D'");
+
+ revertFn(es, entity, "D");
+
+ equal(es.isUpdated(entity, "D"), false, "The entity should no longer have changes for 'D'");
+ dataEqual(entity, datasets.scalars.create(1), "The entity should be reverted");
+ });
+ });
+
+ testWithRevertVariations(true, function (revertFn) {
+ test("isUpdated returns true when nested values change", 15, function () {
+ var es = createEntitySet(),
+ entity = loadEntities(es, datasets.nested.create(1));
+
+ equal(es.getEntities().length, 1, "There should be a single entity loaded");
+
+ equal(es.getEntityState(entity), upshot.EntityState.Unmodified, "The entity state should be 'Unmodified'");
+ equal(es.isUpdated(entity), false, "The entity should not have changes");
+ equal(es.isUpdated(entity, "O"), false, "The nested object 'O' should not have changes");
+ equal(es.isUpdated(entity, "O.B"), false, "The nested object 'O' should not have changes for 'B'");
+
+ $.observable(entity.O).property("B", false);
+
+ equal(es.getEntityState(entity), upshot.EntityState.ClientUpdated, "The entity state should be 'ClientUpdated'");
+ equal(es.isUpdated(entity), true, "The entity should have changes");
+ equal(es.isUpdated(entity, "O"), true, "The nested object 'O' should have changes");
+ equal(es.isUpdated(entity, "O.B"), true, "The nested object 'O' should have changes for 'B'");
+ equal(es.isUpdated(entity, "O", true), false, "The entity should not have changes for 'O'");
+
+ revertFn(es, entity, "O");
+
+ equal(es.isUpdated(entity, "O"), false, "The nested object 'O' should no longer have changes");
+ equal(es.isUpdated(entity, "O.B"), false, "The nested object 'O' should no longer have changes for 'B'");
+ dataEqual(entity, datasets.nested.create(1), "The entity should be reverted");
+ });
+ });
+
+ testWithRevertVariations(true, function (revertFn) {
+ test("isUpdated returns true when tree values change", 19, function () {
+ var es = createEntitySet(),
+ entity = loadEntities(es, datasets.tree.create(1));
+
+ equal(es.getEntities().length, 1, "There should be a single entity loaded");
+
+ equal(es.getEntityState(entity), upshot.EntityState.Unmodified, "The entity state should be 'Unmodified'");
+ equal(es.isUpdated(entity), false, "The entity should not have changes");
+ equal(es.isUpdated(entity, "O"), false, "The nested object 'O' should not have changes");
+ equal(es.isUpdated(entity, "O.O1"), false, "The nested object 'O.O1' should not have changes");
+ equal(es.isUpdated(entity, "O.O1.B"), false, "The nested object 'O.O1' should not have changes for 'B'");
+
+ $.observable(entity.O.O1).property("B", false);
+
+ equal(es.getEntityState(entity), upshot.EntityState.ClientUpdated, "The entity state should be 'ClientUpdated'");
+ equal(es.isUpdated(entity), true, "The entity should have changes");
+ equal(es.isUpdated(entity, "O"), true, "The nested object 'O' should have changes");
+ equal(es.isUpdated(entity, "O.O1"), true, "The nested object 'O.O1' should have changes");
+ equal(es.isUpdated(entity, "O.O1.B"), true, "The nested object 'O.O1' should have changes for 'B'");
+ equal(es.isUpdated(entity, "O", true), false, "The entity should not have changes for 'O'");
+ equal(es.isUpdated(entity, "O.O1", true), false, "The nested object 'O' should not have changes for 'O1'");
+
+ revertFn(es, entity, "O");
+
+ equal(es.isUpdated(entity, "O"), false, "The nested object 'O' should no longer have changes");
+ equal(es.isUpdated(entity, "O.O1"), false, "The nested object 'O.O1' should no longer have changes");
+ equal(es.isUpdated(entity, "O.O1.B"), false, "The nested object 'O.O1' should no longer have changes for 'B'");
+ dataEqual(entity, datasets.tree.create(1), "The entity should be reverted");
+ });
+ });
+
+ testWithRevertVariations(true, function (revertFn) {
+ test("isUpdated returns true when array values change", 18, function () {
+ var es = createEntitySet(),
+ entity = loadEntities(es, datasets.array.create(1));
+
+ equal(es.getEntities().length, 1, "There should be a single entity loaded");
+
+ equal(es.getEntityState(entity), upshot.EntityState.Unmodified, "The entity state should be 'Unmodified'");
+ equal(es.isUpdated(entity), false, "The entity should not have changes");
+ equal(es.isUpdated(entity, "A"), false, "The array should not have changes");
+ equal(es.isUpdated(entity, "A[0]"), false, "The object at index 0 should not have changes");
+ equal(es.isUpdated(entity, "A[0].B"), false, "The object at index 0 should not have changes for 'B'");
+
+ $.observable(entity.A[0]).property("B", false);
+
+ equal(es.getEntityState(entity), upshot.EntityState.ClientUpdated, "The entity state should be 'ClientUpdated'");
+ equal(es.isUpdated(entity), true, "The entity should have changes");
+ equal(es.isUpdated(entity, "A"), true, "The array should have changes");
+ equal(es.isUpdated(entity, "A[0]"), true, "The object at index 0 should have changes");
+ equal(es.isUpdated(entity, "A[0].B"), true, "The object at index 0 should have changes for 'B'");
+ equal(es.isUpdated(entity, "A", true), false, "The entity should not have changes for 'A'");
+
+ revertFn(es, entity, "A");
+
+ equal(es.isUpdated(entity, "A"), false, "The array should no longer have changes");
+ equal(es.isUpdated(entity, "A[0]"), false, "The object at index 0 should no longer have changes");
+ equal(es.isUpdated(entity, "A[0].B"), false, "The object at index 0 should no longer have changes for 'B'");
+ dataEqual(entity, datasets.array.create(1), "The entity should be reverted");
+ });
+ });
+
+ testWithRevertVariations(true, function (revertFn) {
+ test("isUpdated returns true when nested array values change", 25, function () {
+ var es = createEntitySet(),
+ entity = loadEntities(es, datasets.nestedArrays.create(1));
+
+ equal(es.getEntities().length, 1, "There should be a single entity loaded");
+
+ equal(es.getEntityState(entity), upshot.EntityState.Unmodified, "The entity state should be 'Unmodified'");
+ equal(es.isUpdated(entity), false, "The entity should not have changes");
+ equal(es.isUpdated(entity, "A"), false, "The array should not have changes");
+ equal(es.isUpdated(entity, "A[0]"), false, "The object at index 0 should not have changes");
+ equal(es.isUpdated(entity, "A[0].A"), false, "The nested array should not have changes");
+ equal(es.isUpdated(entity, "A[0].A[0]"), false, "The nested object at index 0 should not have changes");
+ equal(es.isUpdated(entity, "A[0].A[0].B"), false, "The nested object at index 0 should not have changes for 'B'");
+
+ $.observable(entity.A[0].A[0]).property("B", false);
+
+ equal(es.getEntityState(entity), upshot.EntityState.ClientUpdated, "The entity state should be 'ClientUpdated'");
+ equal(es.isUpdated(entity), true, "The entity should have changes");
+ equal(es.isUpdated(entity, "A"), true, "The array should have changes");
+ equal(es.isUpdated(entity, "A[0]"), true, "The object at index 0 should have changes");
+ equal(es.isUpdated(entity, "A[0].A"), true, "The nested array should have changes");
+ equal(es.isUpdated(entity, "A[0].A[0]"), true, "The nested object at index 0 should have changes");
+ equal(es.isUpdated(entity, "A[0].A[0].B"), true, "The nested object at index 0 should have changes for 'B'");
+ equal(es.isUpdated(entity, "A", true), false, "The entity should not have changes for 'A'");
+ equal(es.isUpdated(entity, "A[0].A", true), false, "The object at index 0 not have changes for 'A'");
+
+ revertFn(es, entity, "A");
+
+ equal(es.isUpdated(entity, "A"), false, "The array should no longer have changes");
+ equal(es.isUpdated(entity, "A[0]"), false, "The object at index 0 should no longer have changes");
+ equal(es.isUpdated(entity, "A[0].A"), false, "The nested array should no longer have changes");
+ equal(es.isUpdated(entity, "A[0].A[0]"), false, "The nested object at index 0 should no longer have changes");
+ equal(es.isUpdated(entity, "A[0].A[0].B"), false, "The nested object at index 0 should no longer have changes for 'B'");
+ dataEqual(entity, datasets.nestedArrays.create(1), "The entity should be reverted");
+ });
+ });
+
+ testWithRevertVariations(function (revertFn) {
+ test("isUpdated returns true when knockout primitive values change", 27, function () {
+ try {
+ upshot.observability.configuration = observability.knockout;
+
+ var es = createEntitySet(),
+ entity = loadEntities(es, datasets.ko_primitives(1, true));
+
+ equal(es.getEntities()().length, 1, "There should be a single entity loaded");
+
+ equal(es.getEntityState(entity), upshot.EntityState.Unmodified, "The entity state should be 'Unmodified'");
+ equal(es.isUpdated(entity), false, "The entity should not have changes");
+ equal(es.isUpdated(entity, "B"), false, "The entity should not have changes for 'B'");
+ equal(entity.IsUpdated(), false, "The entity should not be changed");
+ equal(entity.B.IsUpdated(), false, "The entity should not be changed for 'B'");
+
+ entity.B(false);
+
+ equal(es.getEntityState(entity), upshot.EntityState.ClientUpdated, "The entity state should be 'ClientUpdated'");
+ equal(es.isUpdated(entity), true, "The entity should have changes");
+ equal(es.isUpdated(entity, "B"), true, "The entity should have changes for 'B'");
+ equal(es.isUpdated(entity, "N"), false, "The entity should not have changes for 'N'");
+ equal(entity.IsUpdated(), true, "The entity should be changed");
+ equal(entity.B.IsUpdated(), true, "The entity should be changed for 'B'");
+ equal(entity.N.IsUpdated(), false, "The entity should not be changed for 'N'");
+
+ entity.B(true);
+
+ equal(es.getEntityState(entity), upshot.EntityState.ClientUpdated, "The entity state should still be 'ClientUpdated'");
+ equal(es.isUpdated(entity), true, "The entity should still have changes");
+ equal(es.isUpdated(entity, "B"), true, "The entity should still have changes for 'B'");
+ equal(es.isUpdated(entity, "N"), false, "The entity should still not have changes for 'N'");
+ equal(entity.IsUpdated(), true, "The entity should still be changed");
+ equal(entity.B.IsUpdated(), true, "The entity should still be changed for 'B'");
+ equal(entity.N.IsUpdated(), false, "The entity should still not be changed for 'N'");
+
+ revertFn(es, entity, "B");
+
+ equal(es.isUpdated(entity, "B"), false, "The entity should no longer have changes for 'B'");
+ equal(entity.IsUpdated(), false, "The entity should no longer be changed");
+ equal(entity.B.IsUpdated(), false, "The entity should no longer be changed for 'B'");
+ dataEqual(entity, datasets.ko_primitives(1), "The entity should be reverted");
+ } finally {
+ upshot.observability.configuration = observability.jquery;
+ }
+ });
+ });
+
+ testWithRevertVariations(true, function (revertFn) {
+ test("isUpdated returns true when knockout tree values change", 29, function () {
+ try {
+ upshot.observability.configuration = observability.knockout;
+
+ var es = createEntitySet(),
+ entity = loadEntities(es, datasets.ko_tree(1, true));
+
+ equal(es.getEntities()().length, 1, "There should be a single entity loaded");
+
+ equal(es.getEntityState(entity), upshot.EntityState.Unmodified, "The entity state should be 'Unmodified'");
+ equal(es.isUpdated(entity), false, "The entity should not have changes");
+ equal(es.isUpdated(entity, "O"), false, "The nested object 'O' should not have changes");
+ equal(es.isUpdated(entity, "O.O1"), false, "The nested object 'O.O1' should not have changes");
+ equal(es.isUpdated(entity, "O.O1.B"), false, "The nested object 'O.O1' should not have changes for 'B'");
+ equal(entity.IsUpdated(), false, "The entity should not be changed");
+ equal(entity.O.IsUpdated(), false, "The nested object 'O' should not be changed");
+ equal(entity.O().O1.IsUpdated(), false, "The nested object 'O.O1' should not be changed");
+ equal(entity.O().O1().B.IsUpdated(), false, "The nested object 'O.O1' should not be changed for 'B'");
+
+ entity.O().O1().B(false);
+
+ equal(es.getEntityState(entity), upshot.EntityState.ClientUpdated, "The entity state should be 'ClientUpdated'");
+ equal(es.isUpdated(entity), true, "The entity should have changes");
+ equal(es.isUpdated(entity, "O"), true, "The nested object 'O' should have changes");
+ equal(es.isUpdated(entity, "O.O1"), true, "The nested object 'O.O1' should have changes");
+ equal(es.isUpdated(entity, "O.O1.B"), true, "The nested object 'O.O1' should have changes for 'B'");
+ equal(es.isUpdated(entity, "O", true), false, "The entity should not have changes for 'O'");
+ equal(es.isUpdated(entity, "O.O1", true), false, "The nested object 'O' should not have changes for 'O1'");
+ equal(entity.IsUpdated(), true, "The entity should be changed");
+ equal(entity.O.IsUpdated(), false, "The nested object 'O' should still not be changed");
+ equal(entity.O().O1.IsUpdated(), false, "The nested object 'O.O1' should still not be changed");
+ equal(entity.O().O1().B.IsUpdated(), true, "The nested object 'O.O1' should be changed for 'B'");
+
+ revertFn(es, entity, "O");
+
+ equal(es.isUpdated(entity, "O"), false, "The nested object 'O' should no longer have changes");
+ equal(es.isUpdated(entity, "O.O1"), false, "The nested object 'O.O1' should no longer have changes");
+ equal(es.isUpdated(entity, "O.O1.B"), false, "The nested object 'O.O1' should no longer have changes for 'B'");
+ equal(entity.IsUpdated(), false, "The entity should no longer be changed");
+ equal(entity.O().O1().B.IsUpdated(), false, "The nested object 'O.O1' should no longer be changed for 'B'");
+ dataEqual(entity, datasets.ko_tree(1), "The entity should be reverted");
+ } finally {
+ upshot.observability.configuration = observability.jquery;
+ }
+ });
+ });
+
+ testWithRevertVariations(true, function (revertFn) {
+ test("isUpdated returns true when knockout array values change", 23, function () {
+ try {
+ upshot.observability.configuration = observability.knockout;
+
+ var es = createEntitySet(),
+ entity = loadEntities(es, datasets.ko_array(1, true));
+
+ equal(es.getEntities()().length, 1, "There should be a single entity loaded");
+
+ equal(es.getEntityState(entity), "Unmodified", "The entity state should be 'Unmodified'");
+ equal(es.isUpdated(entity), false, "The entity should not have changes");
+ equal(es.isUpdated(entity, "A[0]"), false, "The object at index 0 should not have changes");
+ equal(es.isUpdated(entity, "A[0].B"), false, "The object at index 0 should not have changes for 'B'");
+ equal(entity.IsUpdated(), false, "The entity should not be changed");
+ equal(entity.A.IsUpdated(), false, "The entity should not be changed for 'A'");
+ equal(entity.A()[0].B.IsUpdated(), false, "The object at index 0 should not be changed for 'B'");
+
+ entity.A()[0].B(false);
+
+ equal(es.getEntityState(entity), "ClientUpdated", "The entity state should be 'ClientUpdated'");
+ equal(es.isUpdated(entity), true, "The entity should have changes");
+ equal(es.isUpdated(entity, "A[0]"), true, "The object at index 0 should have changes");
+ equal(es.isUpdated(entity, "A[0].B"), true, "The object at index 0 should have changes for 'B'");
+ equal(es.isUpdated(entity, "A", true), false, "The entity should not have changes for 'A'");
+ equal(entity.IsUpdated(), true, "The entity should be changed");
+ equal(entity.A.IsUpdated(), false, "The entity should not be changed for 'A'");
+ equal(entity.A()[0].B.IsUpdated(), true, "The object at index 0 should be changed for 'B'");
+
+ revertFn(es, entity, "A");
+
+ equal(es.isUpdated(entity, "A[0]"), false, "The object at index 0 should no longer have changes");
+ equal(es.isUpdated(entity, "A[0].B"), false, "The object at index 0 should no longer have changes for 'B'");
+ equal(entity.IsUpdated(), false, "The entity should no longer be changed");
+ equal(entity.A()[0].B.IsUpdated(), false, "The object at index 0 should no longer be changed for 'B'");
+ dataEqual(entity, datasets.ko_array(1), "The entity should be reverted");
+ } finally {
+ upshot.observability.configuration = observability.jquery;
+ }
+ });
+ });
+
+ testWithRevertVariations(true, function (revertFn) {
+ test("isUpdated returns true when knockout nested array values change", 35, function () {
+ try {
+ upshot.observability.configuration = observability.knockout;
+
+ var es = createEntitySet(),
+ entity = loadEntities(es, datasets.ko_nestedArrays(1, true));
+
+ equal(es.getEntities()().length, 1, "There should be a single entity loaded");
+
+ equal(es.getEntityState(entity), upshot.EntityState.Unmodified, "The entity state should be 'Unmodified'");
+ equal(es.isUpdated(entity), false, "The entity should not have changes");
+ equal(es.isUpdated(entity, "A"), false, "The array should not have changes");
+ equal(es.isUpdated(entity, "A[0]"), false, "The object at index 0 should not have changes");
+ equal(es.isUpdated(entity, "A[0].A"), false, "The nested array should not have changes");
+ equal(es.isUpdated(entity, "A[0].A[0]"), false, "The nested object at index 0 should not have changes");
+ equal(es.isUpdated(entity, "A[0].A[0].B"), false, "The nested object at index 0 should not have changes for 'B'");
+ equal(entity.IsUpdated(), false, "The entity should not be changed");
+ equal(entity.A.IsUpdated(), false, "The entity should not be changed for 'A'");
+ equal(entity.A()[0].A.IsUpdated(), false, "The object at index 0 should not be changed for 'A'");
+ equal(entity.A()[0].A()[0].B.IsUpdated(), false, "The nested object at index 0 should not be changed for 'B'");
+
+ entity.A()[0].A()[0].B(false);
+
+ equal(es.getEntityState(entity), upshot.EntityState.ClientUpdated, "The entity state should be 'ClientUpdated'");
+ equal(es.isUpdated(entity), true, "The entity should have changes");
+ equal(es.isUpdated(entity, "A"), true, "The array should have changes");
+ equal(es.isUpdated(entity, "A[0]"), true, "The object at index 0 should have changes");
+ equal(es.isUpdated(entity, "A[0].A"), true, "The nested array should have changes");
+ equal(es.isUpdated(entity, "A[0].A[0]"), true, "The nested object at index 0 should have changes");
+ equal(es.isUpdated(entity, "A[0].A[0].B"), true, "The nested object at index 0 should have changes for 'B'");
+ equal(es.isUpdated(entity, "A", true), false, "The entity should not have changes for 'A'");
+ equal(es.isUpdated(entity, "A[0].A", true), false, "The object at index 0 not have changes for 'A'");
+ equal(entity.IsUpdated(), true, "The entity should not be changed");
+ equal(entity.A.IsUpdated(), false, "The entity should still not be changed for 'A'");
+ equal(entity.A()[0].A.IsUpdated(), false, "The object at index 0 should still not be changed for 'A'");
+ equal(entity.A()[0].A()[0].B.IsUpdated(), true, "The nested object at index 0 should be changed for 'B'");
+
+ revertFn(es, entity, "A");
+
+ equal(es.isUpdated(entity, "A"), false, "The array should no longer have changes");
+ equal(es.isUpdated(entity, "A[0]"), false, "The object at index 0 should no longer have changes");
+ equal(es.isUpdated(entity, "A[0].A"), false, "The nested array should no longer have changes");
+ equal(es.isUpdated(entity, "A[0].A[0]"), false, "The nested object at index 0 should no longer have changes");
+ equal(es.isUpdated(entity, "A[0].A[0].B"), false, "The nested object at index 0 should no longer have changes for 'B'");
+ equal(entity.IsUpdated(), false, "The entity should no longer be changed");
+ equal(entity.A()[0].A()[0].B.IsUpdated(), false, "The nested object at index 0 should no longer be changed for 'B'");
+ dataEqual(entity, datasets.ko_nestedArrays(1), "The entity should be reverted");
+ } finally {
+ upshot.observability.configuration = observability.jquery;
+ }
+ });
+ });
+
+ testWithRevertVariations(function (revertFn) {
+ test("isUpdated returns true when multiple properties are updated", 16, function () {
+ var es = createEntitySet(),
+ entity = loadEntities(es, datasets.primitives.create(1));
+
+ equal(es.getEntities().length, 1, "There should be a single entity loaded");
+
+ equal(es.isUpdated(entity, "B"), false, "The entity should not have changes for 'B'");
+ equal(es.isUpdated(entity, "N"), false, "The entity should not have changes for 'N'");
+
+ $.observable(entity).property("B", false);
+
+ equal(es.isUpdated(entity, "B"), true, "The entity should have changes for 'B'");
+ equal(es.isUpdated(entity, "N"), false, "The entity should still not have changes for 'N'");
+
+ $.observable(entity).property("N", -1);
+
+ equal(es.isUpdated(entity, "B"), true, "The entity should still have changes for 'B'");
+ equal(es.isUpdated(entity, "N"), true, "The entity should have changes for 'N'");
+
+ revertFn(es, entity, "B", true);
+ revertFn(es, entity, "N");
+
+ equal(es.isUpdated(entity, "B"), false, "The entity should no longer have changes for 'B'");
+ equal(es.isUpdated(entity, "N"), false, "The entity should no longer have changes for 'N'");
+ dataEqual(entity, datasets.primitives.create(1), "The entity should be reverted");
+ });
+ });
+
+ testWithRevertVariations(true, function (revertFn) {
+ test("isUpdated returns true when multiple nested properties are updated", 14, function () {
+ var es = createEntitySet(),
+ entity = loadEntities(es, datasets.nested.create(1));
+
+ equal(es.getEntities().length, 1, "There should be a single entity loaded");
+
+ equal(es.isUpdated(entity, "B"), false, "The entity should not have changes for 'B'");
+ equal(es.isUpdated(entity, "O.B"), false, "The nested object 'O' should not have changes for 'B'");
+
+ $.observable(entity).property("B", false);
+
+ equal(es.isUpdated(entity, "B"), true, "The entity should have changes for 'B'");
+ equal(es.isUpdated(entity, "O.B"), false, "The nested object 'O' should still not have changes for 'B'");
+
+ $.observable(entity.O).property("B", false);
+
+ equal(es.isUpdated(entity, "B"), true, "The entity should still have changes for 'B'");
+ equal(es.isUpdated(entity, "O.B"), true, "The nested object 'O' should have changes for 'B'");
+
+ revertFn(es, entity, "B", true);
+ revertFn(es, entity, "O");
+
+ equal(es.isUpdated(entity, "B"), false, "The entity should no longer have changes for 'B'");
+ equal(es.isUpdated(entity, "O.B"), false, "The nested object 'O' should no longer have changes for 'B'");
+ dataEqual(entity, datasets.nested.create(1), "The entity should be reverted");
+ });
+ });
+
+ test("_clearChanges resets tracking on multiple nested properties updates", 11, function () {
+ var es = createEntitySet(),
+ entity = loadEntities(es, datasets.nested.create(1));
+
+ equal(es.getEntities().length, 1, "There should be a single entity loaded");
+
+ equal(es.isUpdated(entity, "B"), false, "The entity should not have changes for 'B'");
+ equal(es.isUpdated(entity, "O.B"), false, "The nested object 'O' should not have changes for 'B'");
+
+ $.observable(entity).property("B", false);
+
+ equal(es.isUpdated(entity, "B"), true, "The entity should have changes for 'B'");
+ equal(es.isUpdated(entity, "O.B"), false, "The nested object 'O' should still not have changes for 'B'");
+
+ $.observable(entity.O).property("B", false);
+
+ equal(es.isUpdated(entity, "B"), true, "The entity should still have changes for 'B'");
+ equal(es.isUpdated(entity, "O.B"), true, "The nested object 'O' should have changes for 'B'");
+
+ es._clearChanges(entity, false);
+
+ equal(es.isUpdated(entity), false, "The entity should no longer have changes");
+ equal(es.isUpdated(entity, "O"), false, "The nested object should no longer have changes");
+ equal(es.isUpdated(entity, "B"), false, "The entity should no longer have changes for 'B'");
+ equal(es.isUpdated(entity, "O.B"), false, "The nested object 'O' should no longer have changes for 'B'");
+ });
+
+ testWithRevertVariations(true, function (revertFn) {
+ test("isUpdated returns true when multiple knockout nested properties are updated", 23, function () {
+ try {
+ upshot.observability.configuration = observability.knockout;
+
+ var es = createEntitySet(),
+ entity = loadEntities(es, datasets.ko_tree(1, true));
+
+ equal(es.getEntities()().length, 1, "There should be a single entity loaded");
+
+ equal(es.isUpdated(entity, "B"), false, "The entity should not have changes for 'B'");
+ equal(es.isUpdated(entity, "O.B"), false, "The nested object 'O' should not have changes for 'B'");
+ equal(entity.B.IsUpdated(), false, "The entity should not be changed for 'B'");
+ equal(entity.O().B.IsUpdated(), false, "The nested object 'O' should not be changed for 'B'");
+
+ entity.B(false);
+
+ equal(es.isUpdated(entity, "B"), true, "The entity should have changes for 'B'");
+ equal(es.isUpdated(entity, "O.B"), false, "The nested object 'O' should still not have changes for 'B'");
+ equal(entity.B.IsUpdated(), true, "The entity should be changed for 'B'");
+ equal(entity.O().B.IsUpdated(), false, "The nested object 'O' should still not be changed for 'B'");
+
+ entity.O().B(false);
+
+ equal(es.isUpdated(entity, "B"), true, "The entity should still have changes for 'B'");
+ equal(es.isUpdated(entity, "O.B"), true, "The nested object 'O' should have changes for 'B'");
+ equal(entity.B.IsUpdated(), true, "The entity should still be changed for 'B'");
+ equal(entity.O().B.IsUpdated(), true, "The nested object 'O' should be changed for 'B'");
+
+ revertFn(es, entity, "B", true);
+ revertFn(es, entity, "O");
+
+ equal(es.isUpdated(entity, "B"), false, "The entity should no longer have changes for 'B'");
+ equal(es.isUpdated(entity, "O.B"), false, "The nested object 'O' should no longer have changes for 'B'");
+ equal(entity.IsUpdated(), false, "The entity should no longer be changed");
+ equal(entity.B.IsUpdated(), false, "The entity should no longer be changed for 'B'");
+ equal(entity.O().B.IsUpdated(), false, "The nested object 'O' should no longer be changed for 'B'");
+ dataEqual(entity, datasets.ko_tree(1), "The entity should be reverted");
+ } finally {
+ upshot.observability.configuration = observability.jquery;
+ }
+ });
+ });
+
+ test("_clearChanges resets tracking on multiple knockout nested properties updates", 19, function () {
+ try {
+ upshot.observability.configuration = observability.knockout;
+
+ var es = createEntitySet(),
+ entity = loadEntities(es, datasets.ko_tree(1, true));
+
+ equal(es.getEntities()().length, 1, "There should be a single entity loaded");
+
+ equal(es.isUpdated(entity, "B"), false, "The entity should not have changes for 'B'");
+ equal(es.isUpdated(entity, "O.B"), false, "The nested object 'O' should not have changes for 'B'");
+ equal(entity.B.IsUpdated(), false, "The entity should not be changed for 'B'");
+ equal(entity.O().B.IsUpdated(), false, "The nested object 'O' should not be changed for 'B'");
+
+ entity.B(false);
+
+ equal(es.isUpdated(entity, "B"), true, "The entity should have changes for 'B'");
+ equal(es.isUpdated(entity, "O.B"), false, "The nested object 'O' should still not have changes for 'B'");
+ equal(entity.B.IsUpdated(), true, "The entity should be changed for 'B'");
+ equal(entity.O().B.IsUpdated(), false, "The nested object 'O' should still not be changed for 'B'");
+
+ entity.O().B(false);
+
+ equal(es.isUpdated(entity, "B"), true, "The entity should still have changes for 'B'");
+ equal(es.isUpdated(entity, "O.B"), true, "The nested object 'O' should have changes for 'B'");
+ equal(entity.B.IsUpdated(), true, "The entity should still be changed for 'B'");
+ equal(entity.O().B.IsUpdated(), true, "The nested object 'O' should be changed for 'B'");
+
+ es._clearChanges(entity, false);
+
+ equal(es.isUpdated(entity), false, "The entity should no longer have changes");
+ equal(es.isUpdated(entity, "O"), false, "The nested object should no longer have changes");
+ equal(es.isUpdated(entity, "B"), false, "The entity should no longer have changes for 'B'");
+ equal(es.isUpdated(entity, "O.B"), false, "The nested object 'O' should no longer have changes for 'B'");
+ equal(entity.B.IsUpdated(), false, "The entity should no longer be changed for 'B'");
+ equal(entity.O().B.IsUpdated(), false, "The nested object 'O' should no longer be changed for 'B'");
+ } finally {
+ upshot.observability.configuration = observability.jquery;
+ }
+ });
+
+ testWithRevertVariations(true, function (revertFn) {
+ test("isUpdated returns true on array insert", 13, function () {
+ var es = createEntitySet(),
+ entity = loadEntities(es, datasets.array.create(1));
+
+ equal(es.getEntities().length, 1, "There should be a single entity loaded");
+
+ equal(es.getEntityState(entity), upshot.EntityState.Unmodified, "The entity state should be 'Unmodified'");
+ equal(es.isUpdated(entity), false, "The entity should not have changes");
+ equal(es.isUpdated(entity, "A"), false, "The array should not have changes");
+
+ var length = entity.A.length,
+ _new = { B: false };
+
+ $.observable(entity.A).insert(length, _new);
+
+ equal(es.getEntityState(entity), upshot.EntityState.ClientUpdated, "The entity state should be 'ClientUpdated'");
+ equal(es.isUpdated(entity), true, "The entity should have changes");
+ equal(es.isUpdated(entity, "A"), true, "The array should have changes");
+ equal(es.isUpdated(entity, "A[1]"), false, "The last object should not have changes");
+ equal(entity.A.length, length + 1, "The updated array lengths should be equal");
+
+ revertFn(es, entity, "A");
+
+ equal(es.isUpdated(entity, "A"), false, "The array should no longer have changes");
+ dataEqual(entity, datasets.array.create(1), "The entity should be reverted");
+ });
+ });
+
+ testWithRevertVariations(true, function (revertFn) {
+ test("isUpdated returns true on array remove", 12, function () {
+ var es = createEntitySet(),
+ entity = loadEntities(es, datasets.array.create(1));
+
+ equal(es.getEntities().length, 1, "There should be a single entity loaded");
+
+ equal(es.getEntityState(entity), upshot.EntityState.Unmodified, "The entity state should be 'Unmodified'");
+ equal(es.isUpdated(entity), false, "The entity should not have changes");
+ equal(es.isUpdated(entity, "A"), false, "The array should not have changes");
+
+ var length = entity.A.length;
+
+ $.observable(entity.A).remove(0, 1);
+
+ equal(es.getEntityState(entity), upshot.EntityState.ClientUpdated, "The entity state should be 'ClientUpdated'");
+ equal(es.isUpdated(entity), true, "The entity should have changes");
+ equal(es.isUpdated(entity, "A"), true, "The array should have changes");
+ equal(entity.A.length, length - 1, "The updated array lengths should be equal");
+
+ revertFn(es, entity, "A");
+
+ equal(es.isUpdated(entity, "A"), false, "The array should no longer have changes");
+ dataEqual(entity, datasets.array.create(1), "The entity should be reverted");
+ });
+ });
+
+ testWithRevertVariations(true, function (revertFn) {
+ test("isUpdated returns true on array replaceAll", 13, function () {
+ var es = createEntitySet(),
+ entity = loadEntities(es, datasets.array.create(1));
+
+ equal(es.getEntities().length, 1, "There should be a single entity loaded");
+
+ equal(es.getEntityState(entity), upshot.EntityState.Unmodified, "The entity state should be 'Unmodified'");
+ equal(es.isUpdated(entity), false, "The entity should not have changes");
+ equal(es.isUpdated(entity, "A"), false, "The array should not have changes");
+
+ var length = entity.A.length,
+ _new = { B: false };
+
+ $.observable(entity.A).replaceAll([_new]);
+
+ equal(es.getEntityState(entity), upshot.EntityState.ClientUpdated, "The entity state should be 'ClientUpdated'");
+ equal(es.isUpdated(entity), true, "The entity should have changes");
+ equal(es.isUpdated(entity, "A"), true, "The array should have changes");
+ equal(es.isUpdated(entity, "A[0]"), false, "The object at index '0' should not have changes");
+ equal(entity.A.length, 1, "The updated array lengths should be equal");
+
+ revertFn(es, entity, "A");
+
+ equal(es.isUpdated(entity, "A"), false, "The array should no longer have changes");
+ dataEqual(entity, datasets.array.create(1), "The entity should be reverted");
+ });
+ });
+
+ testWithRevertVariations(true, function (revertFn) {
+ test("isUpdated returns true on multiple array updates", 16, function () {
+ var es = createEntitySet(),
+ entity = loadEntities(es, datasets.array.create(1));
+
+ equal(es.getEntities().length, 1, "There should be a single entity loaded");
+
+ equal(es.getEntityState(entity), upshot.EntityState.Unmodified, "The entity state should be 'Unmodified'");
+ equal(es.isUpdated(entity), false, "The entity should not have changes");
+ equal(es.isUpdated(entity, "A"), false, "The array should not have changes");
+
+ var length = entity.A.length,
+ _new = { B: false };
+
+ $.observable(entity.A).insert(length, _new);
+
+ equal(es.getEntityState(entity), upshot.EntityState.ClientUpdated, "The entity state should be 'ClientUpdated'");
+ equal(es.isUpdated(entity), true, "The entity should have changes");
+ equal(es.isUpdated(entity, "A"), true, "The array should have changes");
+ equal(es.isUpdated(entity, "A[2]"), false, "The last object should not have changes");
+ equal(entity.A.length, length + 1, "The updated array lengths should be equal");
+
+ $.observable(entity.A).remove(1, 2);
+
+ equal(es.isUpdated(entity), true, "The entity should still have changes");
+ equal(es.isUpdated(entity, "A"), true, "The array should still have changes");
+ equal(entity.A.length, length - 1, "The updated array lengths should be equal");
+
+ revertFn(es, entity, "A");
+
+ equal(es.isUpdated(entity, "A"), false, "The array should no longer have changes");
+ dataEqual(entity, datasets.array.create(1), "The entity should be reverted");
+ });
+ });
+
+ testWithRevertVariations(true, function (revertFn) {
+ test("isUpdated returns true on knockout array insert", 17, function () {
+ try {
+ upshot.observability.configuration = observability.knockout;
+
+ var es = createEntitySet(),
+ entity = loadEntities(es, datasets.ko_array(1, true));
+
+ equal(es.getEntities()().length, 1, "There should be a single entity loaded");
+
+ equal(es.getEntityState(entity), "Unmodified", "The entity state should be 'Unmodified'");
+ equal(es.isUpdated(entity), false, "The entity should not have changes");
+ equal(es.isUpdated(entity, "A"), false, "The array should not have changes");
+ equal(entity.A.IsUpdated(), false, "The entity should not be changed for 'A'");
+
+ var length = entity.A().length,
+ _new = { B: ko.observable(false) };
+
+ entity.A.push(_new);
+
+ equal(es.getEntityState(entity), "ClientUpdated", "The entity state should be 'ClientUpdated'");
+ equal(es.isUpdated(entity), true, "The entity should have changes");
+ equal(es.isUpdated(entity, "A"), true, "The array should have changes");
+ equal(es.isUpdated(entity, "A[1]"), false, "The last object should not have changes");
+ equal(entity.A().length, length + 1, "The updated array lengths should be equal");
+ equal(entity.A.IsUpdated(), true, "The entity should be changed for 'A'");
+
+ revertFn(es, entity, "A");
+
+ equal(es.isUpdated(entity, "A"), false, "The array should no longer have changes");
+ equal(entity.IsUpdated(), false, "The entity should no longer be changed");
+ equal(entity.A.IsUpdated(), false, "The entity should no longer be changed for 'A'");
+ dataEqual(entity, datasets.ko_array(1), "The entity should be reverted");
+ } finally {
+ upshot.observability.configuration = observability.jquery;
+ }
+ });
+ });
+
+ testWithRevertVariations(true, function (revertFn) {
+ test("isUpdated returns true on knockout array remove", 16, function () {
+ try {
+ upshot.observability.configuration = observability.knockout;
+
+ var es = createEntitySet(),
+ entity = loadEntities(es, datasets.ko_array(1, true));
+
+ equal(es.getEntities()().length, 1, "There should be a single entity loaded");
+
+ equal(es.getEntityState(entity), "Unmodified", "The entity state should be 'Unmodified'");
+ equal(es.isUpdated(entity), false, "The entity should not have changes");
+ equal(es.isUpdated(entity, "A"), false, "The array should not have changes");
+ equal(entity.A.IsUpdated(), false, "The entity should not be changed for 'A'");
+
+ var length = entity.A().length;
+
+ entity.A.shift();
+
+ equal(es.getEntityState(entity), "ClientUpdated", "The entity state should be 'ClientUpdated'");
+ equal(es.isUpdated(entity), true, "The entity should have changes");
+ equal(es.isUpdated(entity, "A"), true, "The array should have changes");
+ equal(entity.A().length, length - 1, "The updated array lengths should be equal");
+ equal(entity.A.IsUpdated(), true, "The entity should be changed for 'A'");
+
+ revertFn(es, entity, "A");
+
+ equal(es.isUpdated(entity, "A"), false, "The array should no longer have changes");
+ equal(entity.IsUpdated(), false, "The entity should no longer be changed");
+ equal(entity.A.IsUpdated(), false, "The entity should no longer be changed for 'A'");
+ dataEqual(entity, datasets.ko_array(1), "The entity should be reverted");
+ } finally {
+ upshot.observability.configuration = observability.jquery;
+ }
+ });
+ });
+
+ testWithRevertVariations(true, function (revertFn) {
+ test("isUpdated returns true on multiple knockout array updates", 21, function () {
+ try {
+ upshot.observability.configuration = observability.knockout;
+
+ var es = createEntitySet(),
+ entity = loadEntities(es, datasets.ko_array(1, true));
+
+ equal(es.getEntities()().length, 1, "There should be a single entity loaded");
+
+ equal(es.getEntityState(entity), upshot.EntityState.Unmodified, "The entity state should be 'Unmodified'");
+ equal(es.isUpdated(entity), false, "The entity should not have changes");
+ equal(es.isUpdated(entity, "A"), false, "The array should not have changes");
+ equal(entity.A.IsUpdated(), false, "The entity should not be changed for 'A'");
+
+ var length = entity.A().length,
+ _new = { B: false };
+
+ entity.A.push(_new);
+
+ equal(es.getEntityState(entity), upshot.EntityState.ClientUpdated, "The entity state should be 'ClientUpdated'");
+ equal(es.isUpdated(entity), true, "The entity should have changes");
+ equal(es.isUpdated(entity, "A"), true, "The array should have changes");
+ equal(es.isUpdated(entity, "A[1]"), false, "The last object should not have changes");
+ equal(entity.A().length, length + 1, "The updated array lengths should be equal");
+ equal(entity.A.IsUpdated(), true, "The entity should be changed for 'A'");
+
+ entity.A.pop();
+ entity.A.pop();
+
+ equal(es.isUpdated(entity), true, "The entity should still have changes");
+ equal(es.isUpdated(entity, "A"), true, "The array should still have changes");
+ equal(entity.A().length, length - 1, "The updated array lengths should be equal");
+ equal(entity.A.IsUpdated(), true, "The entity should still be changed for 'A'");
+
+ revertFn(es, entity, "A");
+
+ equal(es.isUpdated(entity, "A"), false, "The array should no longer have changes");
+ equal(entity.IsUpdated(), false, "The entity should no longer be changed");
+ equal(entity.A.IsUpdated(), false, "The entity should no longer be changed for 'A'");
+ dataEqual(entity, datasets.ko_array(1), "The entity should be reverted");
+ } finally {
+ upshot.observability.configuration = observability.jquery;
+ }
+ });
+ });
+
+ testWithRevertVariations(true, function (revertFn) {
+ test("changes to a nested object are cleared on revert changes", 14, function () {
+ var es = createEntitySet(),
+ entity = loadEntities(es, datasets.nested.create(1));
+
+ equal(es.getEntities().length, 1, "There should be a single entity loaded");
+
+ equal(es.getEntityState(entity), upshot.EntityState.Unmodified, "The entity state should be 'Unmodified'");
+ equal(es.isUpdated(entity), false, "The entity should not have changes");
+ equal(es.isUpdated(entity, "O"), false, "The nested object should not have changes");
+
+ $.observable(entity.O).property("B", false);
+
+ equal(es.getEntityState(entity), upshot.EntityState.ClientUpdated, "The entity state should be 'ClientUpdated'");
+ equal(es.isUpdated(entity), true, "The entity should have changes");
+ equal(es.isUpdated(entity, "O"), true, "The nested object should have changes");
+ equal(es.isUpdated(entity, "O.B"), true, "The nested object should have changes for 'B'");
+
+ var original = entity.O;
+ $.observable(entity).property("O", null);
+
+ equal(es.isUpdated(entity), true, "The entity should have changes");
+ equal(es.isUpdated(entity, "O"), true, "The entity should have changes for 'O'");
+
+ revertFn(es, entity, "O");
+
+ equal(es.isUpdated(entity, "O"), false, "The nested object should no longer have changes");
+ dataEqual(entity, datasets.nested.create(1), "The entity should be reverted");
+ });
+ });
+
+ testWithRevertVariations(true, function (revertFn) {
+ test("changes to an array are cleared on revert changes", 20, function () {
+ var es = createEntitySet(),
+ entity = loadEntities(es, datasets.nestedArrays.create(1));
+
+ equal(es.getEntities().length, 1, "There should be a single entity loaded");
+
+ equal(es.getEntityState(entity), upshot.EntityState.Unmodified, "The entity state should be 'Unmodified'");
+ equal(es.isUpdated(entity), false, "The entity should not have changes");
+ equal(es.isUpdated(entity, "A"), false, "The array should not have changes");
+ equal(es.isUpdated(entity, "A[0]"), false, "The object at index 0 should not have changes");
+ equal(es.isUpdated(entity, "A[0].A"), false, "The nested array should not have changes");
+
+ $.observable(entity.A[0].A).remove(0, 1);
+
+ equal(es.getEntityState(entity), upshot.EntityState.ClientUpdated, "The entity state should be 'ClientUpdated'");
+ equal(es.isUpdated(entity), true, "The entity should have changes");
+ equal(es.isUpdated(entity, "A"), true, "The array should have changes");
+ equal(es.isUpdated(entity, "A[0]"), true, "The object at index 0 should have changes");
+ equal(es.isUpdated(entity, "A[0].A"), true, "The nested array should have changes");
+
+ var original = entity.A[0];
+ $.observable(entity.A).remove(0, 1);
+
+ equal(es.isUpdated(entity), true, "The entity should have changes");
+ equal(es.isUpdated(entity, "A"), true, "The array should have changes");
+ equal(es.isUpdated(entity, "A[0]"), false, "The object at index 0 should not have changes");
+
+ revertFn(es, entity, "A");
+
+ equal(es.isUpdated(entity, "A"), false, "The array should no longer have changes");
+ equal(es.isUpdated(entity, "A[0]"), false, "The object at index 0 should no longer have changes");
+ equal(es.isUpdated(entity, "A[0].A"), false, "The nested array should no longer have changes");
+ dataEqual(entity, datasets.nestedArrays.create(1), "The entity should be reverted");
+ });
+ });
+
+ test("_getOriginalValue returns the original value", 4, function () {
+ var es = createEntitySet(),
+ entity = loadEntities(es, datasets.nestedArrays.create(1));
+
+ equal(es.getEntities().length, 1, "There should be a single entity loaded");
+
+ equal(es.getEntityState(entity), upshot.EntityState.Unmodified, "The entity state should be 'Unmodified'");
+
+ $.observable(entity).property("B", false);
+ $.observable(entity).property("S", "S");
+ $.observable(entity.A).insert(entity.A.length, [{ B: true}]);
+ $.observable(entity.A[1]).property("N", -1);
+ $.observable(entity.A[0].A).replaceAll([{ B: true }, { B: true}]);
+
+ equal(es.isUpdated(entity), true, "The entity should have changes");
+
+ dataEqual(es._getOriginalValue(entity), datasets.nestedArrays.create(1), "Original values should be equal.");
+ });
+
+ test("_merge sets new values into entity", 2, function () {
+ var es = createEntitySet(),
+ entity = loadEntities(es, datasets.nestedArrays.create(1));
+
+ equal(es.getEntities().length, 1, "There should be a single entity loaded");
+
+ var _new = datasets.nestedArrays.create(1);
+
+ _new.B = false;
+ _new.N = 3;
+ _new.A[0].B = false;
+ _new.A[1].A.push({ B: true });
+
+ es._merge(entity, _new);
+
+ dataEqual(entity, _new, "The entity should equal new values");
+ });
+
+ testWithRevertVariations(function (revertFn) {
+ test("_merge does not update a modified entity", 15, function () {
+ var es = createEntitySet(),
+ entity = loadEntities(es, datasets.nestedArrays.create(1));
+
+ equal(es.getEntities().length, 1, "There should be a single entity loaded");
+
+ equal(es.getEntityState(entity), upshot.EntityState.Unmodified, "The entity state should be 'Unmodified'");
+
+ $.observable(entity).property("N", -1);
+
+ equal(es.isUpdated(entity), true, "The entity should have changes");
+ equal(es.isUpdated(entity, "N"), true, "The entity should have changes for 'N'");
+
+ var original = es._getOriginalValue(entity),
+ _new = datasets.nestedArrays.create(1);
+ _new.B = false;
+
+ es._merge(entity, _new);
+
+ equal(es.isUpdated(entity), true, "The entity should have changes");
+ equal(es.isUpdated(entity, "N"), true, "The entity should have changes for 'N'");
+ equal(entity.B, original.B, "The value of 'B' should match the original value");
+ notEqual(entity.B, _new.B, "The value of 'B' should not match the new value");
+
+ revertFn(es, entity, "N");
+
+ equal(es.isUpdated(entity, "N"), false, "The entity should no longer have changes for 'N'");
+ equal(entity.B, original.B, "The value of 'B' should match the original value");
+ notEqual(entity.B, _new.B, "The value of 'B' should not match the new value");
+ dataEqual(entity, original, "The entity should equal the original values");
+ });
+ });
+
+
+ function createEntitySet() {
+ var dataContext = new upshot.DataContext(new upshot.riaDataProvider()),
+ entitySet = new upshot.EntitySet(dataContext, "entityType");
+
+ upshot.metadata("entityType", {
+ key: ["Id"]
+ });
+ return entitySet;
+ }
+
+ function loadEntities(entitySet, entities) {
+ var entities2 = entities;
+ if (!upshot.isArray(entities)) {
+ entities2 = [entities];
+ }
+ entities2 = entitySet.__loadEntities(entities2);
+ if (!upshot.isArray(entities)) {
+ return entities2[0];
+ }
+ return entities2;
+ }
+
+ function createEventTracker(entitySet) {
+ var e = [],
+ tracker = {
+ events: e,
+ propertyChanged: function (entity, path, value) {
+ e.push({ type: "propertyChanged", entity: entity, path: path, value: value });
+ },
+ entityUpdated: function (entity, path, eventArgs) {
+ e.push({ type: "entityUpdated", entity: entity, path: path, eventArgs: eventArgs });
+ }
+ };
+ entitySet.bind("propertyChanged", tracker.propertyChanged);
+ entitySet.bind("entityUpdated", tracker.entityUpdated);
+ return tracker;
+ }
+
+ function dataEqual(actual, expected, message) {
+ var count = 0;
+ function compare(actualValue, expectedValue, property) {
+ count++;
+ if (actualValue !== expectedValue) {
+ return count + ": property '" + property + "' with value '" + actualValue + "' does not equal expected value '" + expectedValue + "'.\n";
+ }
+ return "";
+ }
+
+ equal(dataEqualRecursive(actual, expected, compare), "", message);
+ }
+
+ function dataEqualRecursive(actual, expected, compare, property) {
+ var difference = "";
+ if (upshot.isArray(actual) && upshot.isArray(expected)) {
+ $.each(expected, function (index, value) {
+ difference += dataEqualRecursive(actual[index], value, compare, index);
+ });
+ } else if (upshot.isObject(actual) && upshot.isObject(expected)) {
+ $.each(expected, function (key, value) {
+ difference += dataEqualRecursive(actual[key], value, compare, key);
+ });
+ } else if (ko.isObservable(actual) && ko.isObservable(expected)) {
+ difference = dataEqualRecursive(ko.utils.unwrapObservable(actual), ko.utils.unwrapObservable(expected), compare, property);
+ } else if (upshot.isDate(actual) && upshot.isDate(expected)) {
+ difference = compare(actual.toString(), expected.toString(), property);
+ } else {
+ difference = compare(actual, expected, property);
+ }
+ return difference;
+ }
+
+ function testWithRevertVariations(nested, testFn) {
+ if ($.isFunction(nested)) {
+ testFn = nested;
+ nested = false;
+ }
+ testFn(function (es, entity, propertyName) {
+ var observer;
+ if (!nested) {
+ observer = function () {
+ equal(es.isUpdated(entity, propertyName), false, "The entity should not have property changes during callbacks");
+ };
+ es.bind("propertyChanged", observer);
+ }
+ es.revertChanges();
+ if (!nested) {
+ es.unbind("propertyChanged", observer);
+ }
+ equal(es.getEntityState(entity), upshot.EntityState.Unmodified, "The entity state should be 'Unmodified' again");
+ equal(es.isUpdated(entity), false, "The entity should no longer have changes");
+ });
+ testFn(function (es, entity, propertyName, isUpdated) {
+ var observer;
+ if (!nested) {
+ observer = function () {
+ equal(es.isUpdated(entity, propertyName), false, "The entity should not have property changes during callbacks");
+ };
+ es.bind("propertyChanged", observer);
+ }
+ es.revertUpdates(entity, propertyName);
+ if (!nested) {
+ es.unbind("propertyChanged", observer);
+ }
+ var expectedState = !!isUpdated ? upshot.EntityState.ClientUpdated : upshot.EntityState.Unmodified;
+ equal(es.getEntityState(entity), expectedState, "The entity state should still be '" + expectedState + "'");
+ equal(es.isUpdated(entity), !!isUpdated, "The entity should " + (!!isUpdated ? "" : "no longer") + " have changes");
+ });
+ }
+
+})(this, jQuery, upshot);
diff --git a/test/SPA.Test/upshot/Init.js b/test/SPA.Test/upshot/Init.js
new file mode 100644
index 00000000..7b980e40
--- /dev/null
+++ b/test/SPA.Test/upshot/Init.js
@@ -0,0 +1,41 @@
+/// <reference path="../Scripts/References.js" />
+/// <reference path="TestGen.js" />
+
+// Start the tests after all scripts have been dynamically loaded
+QUnit.config.autostart = false;
+
+// Resets the database and starts test generation
+var TestPri = (function ($) {
+ /// <param name="$" type="jQuery" />
+
+ function loadScripts() {
+ // Scripts are now a variable that can be dynamically modified to switch to vsdoc or minified.
+ var scriptsToLoad = [
+ "upshot/ChangeTracking.tests.js",
+ "upshot/Consistency.tests.js",
+ "upshot/Core.tests.js",
+ "upshot/DataContext.tests.js",
+ "upshot/DataProvider.tests.js",
+ "upshot/DataSource.Common.js",
+ "upshot/DataSource.tests.js",
+ "upshot/Delete.tests.js",
+ "upshot/EntitySet.tests.js",
+ "upshot/jQuery.DataView.tests.js",
+ "upshot/Mapping.tests.js",
+ "upshot/RecordSet.js"
+ ];
+
+ // Avoid browser caching by appending a query string to the url ?13099...
+ $.each(scriptsToLoad, function (i, item) {
+ scriptsToLoad[i] = item + "?" + (new Date()).getTime();
+ });
+
+ return $.getScriptByReference("upshot/Datasets.js").pipe(function () {
+ return $.whenAll($.getScriptsByReference(scriptsToLoad));
+ });
+ }
+
+ $(window).load(function () { loadScripts().then(QUnit.start); }); // QUnit initializes on window load. Don't call QUnit.start until then.
+
+ return 0;
+})(jQuery); \ No newline at end of file
diff --git a/test/SPA.Test/upshot/Mapping.tests.js b/test/SPA.Test/upshot/Mapping.tests.js
new file mode 100644
index 00000000..1c9e6d40
--- /dev/null
+++ b/test/SPA.Test/upshot/Mapping.tests.js
@@ -0,0 +1,441 @@
+/// <reference path="../Scripts/References.js" />
+
+(function (upshot, $, ko, undefined) {
+
+ var obsOld = upshot.observability.configuration;
+ module("mapping.tests.js", {
+ teardown: function () {
+ upshot.observability.configuration = obsOld;
+ testHelper.unmockAjax();
+ }
+ });
+
+ function createRemoteDataSource(options) {
+ options = $.extend({ providerParameters: { url: "unused", operationName: ""} }, options);
+ return new upshot.RemoteDataSource(options);
+ }
+
+ function createCustomersResult() {
+ return [
+ {
+ ID: 1,
+ Name: "Joe",
+ Orders: [
+ { ID: 97, ProductName: "Smarties", CustomerID: 1 }
+ ]
+ },
+ {
+ ID: 2,
+ Name: "Stan",
+ Orders: [
+ { ID: 99, ProductName: "Shreddies", CustomerID: 2 }
+ ]
+ },
+ {
+ ID: 3,
+ Name: "Fred",
+ Orders: [
+ { ID: 98, ProductName: "Wheatabix", CustomerID: 3 },
+ { ID: 96, ProductName: "Red Rose Tea", CustomerID: 3 }
+ ]
+ }
+ ];
+ }
+
+ var customersMetadata = {
+ Customer_Mapping: {
+ key: ["ID"],
+ fields: {
+ ID: { type: "Int32:#System" },
+ Name: { type: "String:#System" },
+ Orders: {
+ type: "Order_Mapping",
+ array: true,
+ association: {
+ thisKey: ["ID"],
+ otherKey: ["CustomerID"]
+ }
+ }
+ }
+ },
+ Order_Mapping: {
+ key: ["ID"],
+ fields: {
+ ID: { type: "Int32:#System" },
+ ProductName: { type: "String:#System" },
+ CustomerID: { type: "Int32:#System" },
+ Customer: {
+ type: "Customer_Mapping",
+ association: {
+ isForeignKey: true,
+ thisKey: ["CustomerID"],
+ otherKey: ["ID"]
+ }
+ }
+ }
+ },
+ Entity_Mapping: {
+ key: ["Id"],
+ fields: {
+ Id: { type: "Int32:#System" },
+ String: { type: "String:#System" },
+ Number: { type: "Int32:#System" }
+ }
+ },
+ CT_Mapping: {
+ fields: {
+ String: { type: "String:#System" },
+ Number: { type: "Int32:#System" },
+ CT: { type: "CT_Mapping" }
+ }
+ }
+ };
+
+ function happyMapper(entityType) {
+ return function (data) {
+ var mapped = upshot.map(data, entityType);
+ mapped.Happy = true;
+ return mapped;
+ }
+ }
+
+ (function () {
+ var mappingOptions = [
+ {
+ entityType: "Customer_Mapping",
+ mapping: happyMapper("Customer_Mapping")
+ },
+ {
+ entityType: "Customer_Mapping",
+ mapping: {
+ map: happyMapper("Customer_Mapping"),
+ unmap: function () { throw "Not reached"; }
+ }
+ },
+ {
+ entityType: "Customer_Mapping",
+ mapping: {
+ Customer_Mapping: happyMapper("Customer_Mapping")
+ }
+ },
+ {
+ entityType: "Customer_Mapping",
+ mapping: {
+ Customer_Mapping: {
+ map: happyMapper("Customer_Mapping"),
+ unmap: function () { throw "Not reached"; }
+ }
+ }
+ }
+ ];
+ for (var i = 0; i < mappingOptions.length; i++) {
+ (function (options) {
+ test("Verify customer parent mapping, default child mapping", 3, function () {
+ stop();
+
+ upshot.observability.configuration = upshot.observability.knockout;
+ upshot.metadata(customersMetadata);
+ dsTestDriver.simulateSuccessService(createCustomersResult());
+
+ var rds = createRemoteDataSource(options);
+ rds.refresh(function (entities) {
+ equal(entities[1].Orders()[0].ProductName(), "Shreddies", "Mapping and associations applied");
+ equal($.grep(entities, function (entity) { return entity.Happy; }).length, 3, "Customers mapped with custom mapping");
+ equal($.grep(rds.getDataContext().getEntitySet("Order_Mapping").getEntities()(), function (entity) { return entity.Happy; }).length, 0, "Orders mapped with default mapping");
+
+ start();
+ });
+ });
+ })(mappingOptions[i]);
+ }
+ })();
+
+ (function () {
+ var mappingOptions = [
+ {
+ entityType: "Customer_Mapping",
+ mapping: {
+ Order_Mapping: happyMapper("Order_Mapping")
+ }
+ },
+ {
+ entityType: "Customer_Mapping",
+ mapping: {
+ Order_Mapping: {
+ map: happyMapper("Order_Mapping"),
+ unmap: function () { throw "Not reached"; }
+ }
+ }
+ }
+ ];
+ for (var i = 0; i < mappingOptions.length; i++) {
+ (function (options) {
+ test("Verify default parent mapping, custom child mapping", 3, function () {
+ stop();
+
+ upshot.observability.configuration = upshot.observability.knockout;
+ upshot.metadata(customersMetadata);
+ dsTestDriver.simulateSuccessService(createCustomersResult());
+
+ var rds = createRemoteDataSource(options);
+ rds.refresh(function (entities) {
+ equal(entities[1].Orders()[0].ProductName(), "Shreddies", "Mapping and associations applied");
+ equal($.grep(entities, function (entity) { return entity.Happy; }).length, 0, "Customers mapped with default mapping");
+ equal($.grep(rds.getDataContext().getEntitySet("Order_Mapping").getEntities()(), function (entity) { return entity.Happy; }).length, 4, "Orders mapped with custom mapping");
+
+ start();
+ });
+ });
+ })(mappingOptions[i]);
+ }
+ })();
+
+ function Customer(data) {
+ this.ID = ko.observable(data.ID);
+ this.Orders = upshot.map(data.Orders, "Order_Mapping");
+
+ this.Happy = true;
+ }
+
+ test("Verify use of ctor as map", 3, function () {
+ stop();
+
+ upshot.observability.configuration = upshot.observability.knockout;
+ upshot.metadata(customersMetadata);
+ dsTestDriver.simulateSuccessService(createCustomersResult());
+
+ var rds = createRemoteDataSource({
+ entityType: "Customer_Mapping",
+ mapping: Customer
+ });
+ rds.refresh(function (entities) {
+ equal(entities[1].Orders()[0].ProductName(), "Shreddies", "Mapping and associations applied");
+ equal($.grep(entities, function (entity) { return entity.Happy; }).length, 3, "Customers mapped with custom mapping");
+ equal($.grep(rds.getDataContext().getEntitySet("Order_Mapping").getEntities()(), function (entity) { return entity.Happy; }).length, 0, "Orders mapped with default mapping");
+
+ start();
+ });
+ });
+
+ // TODO: Factor our KO test setup elsewhere and move this test to a better home.
+ test("LDS over ASEV produces correct filtered result", 2, function () {
+ stop();
+
+ upshot.observability.configuration = upshot.observability.knockout;
+ upshot.metadata(customersMetadata);
+ dsTestDriver.simulateSuccessService(createCustomersResult());
+
+ var rds = createRemoteDataSource({
+ entityType: "Customer_Mapping",
+ mapping: Customer
+ });
+
+ rds.refresh(function (entities) {
+ var lds = new upshot.LocalDataSource({
+ source: entities[2].Orders,
+ filter: { property: "ProductName", value: "Wheatabix" }
+ });
+ lds.refresh(function (entities2) {
+ ok(entities2.length === 1 && entities2[0].ProductName() === "Wheatabix", "Correct LDS refresh result");
+
+ var lds2 = new upshot.LocalDataSource({
+ source: upshot.EntitySource.as(entities[2].Orders),
+ filter: { property: "ProductName", value: "Wheatabix" }
+ });
+ lds2.refresh(function (entities3) {
+ ok(entities3.length === 1 && entities3[0].ProductName() === "Wheatabix", "Correct LDS refresh result");
+
+ start();
+ });
+ });
+ });
+ });
+
+ test("Default knockout mapping adds entity and updated properties", 2, function () {
+ upshot.observability.configuration = upshot.observability.knockout;
+ upshot.metadata(customersMetadata);
+
+ var entity = upshot.map({
+ Id: 1,
+ String: "String",
+ Number: 1
+ }, "Entity_Mapping");
+
+ equal(entity.hasOwnProperty("EntityState"), true, "Entity should have 'EntityState' property");
+ equal(entity.String.hasOwnProperty("IsUpdated"), true, "String should have 'IsUpdated' property");
+ });
+
+ test("Default knockout mapping for a complex type adds updated properties", 4, function () {
+ upshot.observability.configuration = upshot.observability.knockout;
+ upshot.metadata(customersMetadata);
+
+ var ct = upshot.map({
+ String: "String",
+ Number: 1,
+ CT: {
+ String: "String2",
+ Number: 2,
+ CT: null
+ }
+ }, "CT_Mapping");
+
+ equal(ct.hasOwnProperty("EntityState"), false, "CT should not have 'EntityState' property");
+ equal(ct.String.hasOwnProperty("IsUpdated"), true, "String should have 'IsUpdated' property");
+ equal(ct.CT().hasOwnProperty("EntityState"), false, "Nested CT should not have 'EntityState' property");
+ equal(ct.CT().String.hasOwnProperty("IsUpdated"), true, "Nested String should have 'IsUpdated' property");
+ });
+
+ test("upshot.addEntityProperties for knockout adds entity properties", 12, function () {
+ upshot.observability.configuration = upshot.observability.knockout;
+ upshot.metadata(customersMetadata);
+
+ var entity = upshot.map({
+ Id: 1,
+ String: "String",
+ Number: 1
+ }, "Entity_Mapping");
+
+ equal(entity.hasOwnProperty("EntityState"), true, "Entity should have 'EntityState' property");
+ equal(entity.EntityState(), upshot.EntityState.Unmodified, "EntityState should be unmodified");
+ equal(entity.hasOwnProperty("EntityError"), true, "Entity should have 'EntityError' property");
+ equal(entity.EntityError(), null, "EntityError should be null");
+ equal(entity.hasOwnProperty("IsUpdated"), true, "Entity should have 'IsUpdated' property");
+ equal(entity.IsUpdated(), false, "IsUpdated should be false");
+ equal(entity.hasOwnProperty("IsAdded"), true, "Entity should have 'IsAdded' property");
+ equal(entity.IsAdded(), false, "IsAdded should be false");
+ equal(entity.hasOwnProperty("IsDeleted"), true, "Entity should have 'IsDeleted' property");
+ equal(entity.IsDeleted(), false, "IsDeleted should be false");
+ equal(entity.hasOwnProperty("IsChanged"), true, "Entity should have 'IsChanged' property");
+ equal(entity.IsChanged(), false, "IsChanged should be false");
+ });
+
+ test("upshot.addUpdatedProperties for knockout adds updated properties", 6, function () {
+ upshot.observability.configuration = upshot.observability.knockout;
+ upshot.metadata(customersMetadata);
+
+ var entity = upshot.map({
+ Id: 1,
+ String: "String",
+ Number: 1
+ }, "Entity_Mapping");
+
+ equal(entity.Id.hasOwnProperty("IsUpdated"), true, "Id should have 'IsUpdated' property");
+ equal(entity.Id.IsUpdated(), false, "Id.IsUpdated should be false");
+ equal(entity.String.hasOwnProperty("IsUpdated"), true, "String should have 'IsUpdated' property");
+ equal(entity.String.IsUpdated(), false, "String.IsUpdated should be false");
+ equal(entity.Number.hasOwnProperty("IsUpdated"), true, "Number should have 'IsUpdated' property");
+ equal(entity.Number.IsUpdated(), false, "Number.IsUpdated should be false");
+ });
+
+ function getEntityStates() {
+ var states = {};
+ $.each(upshot.EntityState, function (key, value) {
+ if (typeof value === "string") {
+ states[value] = false;
+ }
+ });
+ return states;
+ }
+
+ test("knockout entity.IsUpdated should reflect EntityState", 8, function () {
+ upshot.observability.configuration = upshot.observability.knockout;
+ upshot.metadata(customersMetadata);
+
+ var entity = upshot.map({
+ Id: 1,
+ String: "String",
+ Number: 1
+ }, "Entity_Mapping");
+
+ var states = getEntityStates();
+ states[upshot.EntityState.ClientUpdated] = true;
+ states[upshot.EntityState.ServerUpdating] = true;
+ $.each(states, function (key, value) {
+ entity.EntityState(key);
+ equal(entity.IsUpdated(), value, "IsUpdated should be " + value + " for state " + key);
+ });
+ });
+
+ test("knockout entity.IsAdded should reflect EntityState", 8, function () {
+ upshot.observability.configuration = upshot.observability.knockout;
+ upshot.metadata(customersMetadata);
+
+ var entity = upshot.map({
+ Id: 1,
+ String: "String",
+ Number: 1
+ }, "Entity_Mapping");
+
+ var states = getEntityStates();
+ states[upshot.EntityState.ClientAdded] = true;
+ states[upshot.EntityState.ServerAdding] = true;
+ $.each(states, function (key, value) {
+ entity.EntityState(key);
+ equal(entity.IsAdded(), value, "IsAdded should be " + value + " for state " + key);
+ });
+ });
+
+ test("knockout entity.IsDeleted should reflect EntityState", 8, function () {
+ upshot.observability.configuration = upshot.observability.knockout;
+ upshot.metadata(customersMetadata);
+
+ var entity = upshot.map({
+ Id: 1,
+ String: "String",
+ Number: 1
+ }, "Entity_Mapping");
+
+ var states = getEntityStates();
+ states[upshot.EntityState.ClientDeleted] = true;
+ states[upshot.EntityState.ServerDeleting] = true;
+ states[upshot.EntityState.Deleted] = true;
+ $.each(states, function (key, value) {
+ entity.EntityState(key);
+ equal(entity.IsDeleted(), value, "IsDeleted should be " + value + " for state " + key);
+ });
+ });
+
+ test("knockout entity.IsChanged should reflect EntityState", 8, function () {
+ upshot.observability.configuration = upshot.observability.knockout;
+ upshot.metadata(customersMetadata);
+
+ var entity = upshot.map({
+ Id: 1,
+ String: "String",
+ Number: 1
+ }, "Entity_Mapping");
+
+ var states = getEntityStates();
+ states[upshot.EntityState.Unmodified] = true;
+ states[upshot.EntityState.Deleted] = true;
+ $.each(states, function (key, value) {
+ entity.EntityState(key);
+ equal(entity.IsChanged(), !value, "IsChanged should be " + !value + " for state " + key);
+ });
+ });
+
+ test("upshot.registerType and upshot.type use", 4, function () {
+ upshot.registerType("FooType", function () { return Foo; });
+ function Foo() {};
+ equal(upshot.type(Foo), "FooType", "upshot.registerType works before key declaration");
+
+ function Foo2() {};
+ upshot.registerType("FooType", function () { return Foo2; });
+ equal(upshot.type(Foo2), "FooType", "upshot.registerType works after key declaration");
+
+ upshot.registerType({ Bar1Type: function () { return Bar1; }, Bar2Type: function () { return Bar2; } });
+ function Bar1() {};
+ function Bar2() {};
+ ok(upshot.type(Bar1) === "Bar1Type" && upshot.type(Bar2) === "Bar2Type", "upshot.registerType supports multiple registrations");
+
+ var exception;
+ try {
+ function Zip() {};
+ upshot.type(Zip);
+ } catch (ex) {
+ exception = true;
+ }
+ ok(!!exception, "upshot.type with no preceding upshot.registerType throws exception");
+ });
+
+})(upshot, jQuery, ko);
diff --git a/test/SPA.Test/upshot/RecordSet.js b/test/SPA.Test/upshot/RecordSet.js
new file mode 100644
index 00000000..3018fc7f
--- /dev/null
+++ b/test/SPA.Test/upshot/RecordSet.js
@@ -0,0 +1,67 @@
+module("EntitySet.js");
+
+(function (global, upshot, undefined) {
+
+ function mockDs() {
+ var mockDs = new upshot.DataContext();
+ upshot.metadata("mockType", { key: ["mockId"] });
+ return mockDs;
+ }
+
+ function mockType() {
+ return "mockType";
+ }
+
+ function mockNewEs() {
+ return new upshot.EntitySet(mockDs(), mockType());
+ }
+
+ test("EntitySource Bind event", 2, function () {
+
+ var es = mockNewEs(),
+ event = "dummyevent",
+ eventArg = "dummyarg";
+
+ es.bind(event, function() {
+ equal(this, es, "Context matched");
+ equal(arguments[0], eventArg, "arg matched");
+ })._trigger(event, eventArg);
+ });
+
+ test("EntitySource Unbind event", 0, function () {
+
+ var es = mockNewEs(),
+ event = "dummyevent",
+ eventArg = "dummyarg",
+ eventCallback = function() {
+ ok(false, "should not callback unbind event");
+ };
+
+ es.bind(event, eventCallback).unbind(event, eventCallback)._trigger(event, eventArg);
+ });
+
+ test("DataContext Bind event", 2, function () {
+
+ var es = upshot.DataContext("unused"),
+ event = "dummyevent",
+ eventArg = "dummyarg";
+
+ es.bind(event, function() {
+ equal(this, es, "Context matched");
+ equal(arguments[0], eventArg, "arg matched");
+ })._trigger(event, eventArg);
+ });
+
+ test("DataContext Unbind event", 0, function () {
+
+ var es = upshot.DataContext("unused"),
+ event = "dummyevent",
+ eventArg = "dummyarg",
+ eventCallback = function() {
+ ok(false, "should not callback unbind event");
+ };
+
+ es.bind(event, eventCallback).unbind(event, eventCallback)._trigger(event, eventArg);
+ });
+
+})(this, upshot);
diff --git a/test/SPA.Test/upshot/jQuery.DataView.Tests.js b/test/SPA.Test/upshot/jQuery.DataView.Tests.js
new file mode 100644
index 00000000..550f4c80
--- /dev/null
+++ b/test/SPA.Test/upshot/jQuery.DataView.Tests.js
@@ -0,0 +1,287 @@
+/// <reference path="../Scripts/References.js" />
+(function (global, upshot, undefined) {
+
+ module("jQuery.dataview.Tests.js", {
+ teardown: function () {
+ testHelper.unmockAjax();
+ }
+ });
+
+ // refreshStart
+ test("refreshStart ctor", 3, function () {
+ stop();
+ dsTestDriver.simulateSuccessService();
+ dsTestDriver.ds = $.upshot.remoteDataview({
+ providerParameters: { url: "unused" },
+ provider: upshot.riaDataProvider,
+ refreshStart: dsTestDriver.onRefreshStart
+ });
+ dsTestDriver.ds.refresh(function () { start(); });
+ });
+
+ test("refreshStart bind", 4, function () {
+ stop();
+ dsTestDriver.simulateSuccessService();
+ dsTestDriver.ds = $.upshot.remoteDataview({ providerParameters: { url: "unused"}, provider: upshot.riaDataProvider });
+ $(dsTestDriver.ds).bind("refreshStart", dsTestDriver.onRefreshStartEvent);
+ dsTestDriver.ds.refresh(function () { start(); });
+ });
+
+ // refreshSuccess
+ test("refreshSuccess ctor", 6, function () {
+ stop();
+ dsTestDriver.simulateSuccessService();
+ dsTestDriver.ds = $.upshot.remoteDataview({
+ providerParameters: { url: "unused" },
+ provider: upshot.riaDataProvider,
+ refreshSuccess: dsTestDriver.onRefreshSuccess
+ });
+ dsTestDriver.ds.refresh();
+ });
+
+ test("refreshSuccess refresh simplest option", 6, function () {
+ stop();
+ dsTestDriver.simulateSuccessService();
+ dsTestDriver.ds = $.upshot.remoteDataview({ providerParameters: { url: "unused"}, provider: upshot.riaDataProvider, })
+ .refresh(dsTestDriver.onRefreshSuccess);
+ });
+
+ test("refreshSuccess bind", 7, function () {
+ stop();
+ dsTestDriver.simulateSuccessService();
+ dsTestDriver.ds = $.upshot.remoteDataview({ providerParameters: { url: "unused"}, provider: upshot.riaDataProvider, });
+ $(dsTestDriver.ds).one("refreshSuccess", dsTestDriver.onRefreshSuccessEvent);
+ dsTestDriver.ds.refresh();
+ });
+
+ test("refreshSuccess localDataview ctor", 6, function () {
+ stop();
+ dsTestDriver.simulateSuccessService();
+ dsTestDriver.ds = $.upshot.remoteDataview({
+ providerParameters: { url: "unused" },
+ provider: upshot.riaDataProvider,
+ refreshSuccess: function () {
+ dsTestDriver.ds = $.upshot.localDataview({
+ input: this,
+ paging: { limit: 2 },
+ refreshSuccess: dsTestDriver.onRefreshSuccess
+ });
+ setTimeout(function () { dsTestDriver.ds.refresh(); }, 10);
+ }
+ });
+ dsTestDriver.ds.refresh();
+ });
+
+ test("refreshSuccess localDataview refresh simplest option", 6, function () {
+ stop();
+ dsTestDriver.simulateSuccessService();
+ dsTestDriver.ds = $.upshot.remoteDataview({ providerParameters: { url: "unused"}, provider: upshot.riaDataProvider })
+ .refresh(function () {
+ dsTestDriver.ds = $.upshot.localDataview({
+ input: this,
+ paging: { limit: 2 }
+ });
+ setTimeout(function () { dsTestDriver.ds.refresh(dsTestDriver.onRefreshSuccess); }, 10);
+ });
+ });
+
+ test("refreshSuccess localDataview bind", 7, function () {
+ stop();
+ dsTestDriver.simulateSuccessService();
+ dsTestDriver.ds = $.upshot.remoteDataview({ providerParameters: { url: "unused"}, provider: upshot.riaDataProvider, });
+ $(dsTestDriver.ds).one("refreshSuccess", function () {
+ dsTestDriver.ds = $.upshot.localDataview({
+ input: this,
+ paging: { limit: 1 }
+ });
+ $(dsTestDriver.ds).one("refreshSuccess", dsTestDriver.onRefreshSuccessEvent);
+ setTimeout(function () { dsTestDriver.ds.refresh(); }, 10);
+ });
+ dsTestDriver.ds.refresh();
+ });
+
+ // refreshError
+ test("refreshError ctor", 5, function () {
+ stop();
+ dsTestDriver.simulateErrorService();
+ dsTestDriver.ds = $.upshot.remoteDataview({
+ providerParameters: { url: "unused" },
+ provider: upshot.riaDataProvider,
+ refreshError: dsTestDriver.onRefreshError
+ }).refresh();
+ });
+
+ test("refreshError refresh simplest option", 5, function () {
+ stop();
+ dsTestDriver.simulateErrorService();
+ dsTestDriver.ds = $.upshot.remoteDataview({ providerParameters: { url: "unused"}, provider: upshot.riaDataProvider, });
+ dsTestDriver.ds.refresh(null, dsTestDriver.onRefreshError);
+ });
+
+ test("refreshError bind", 6, function () {
+ stop();
+ dsTestDriver.simulateErrorService();
+ dsTestDriver.ds = $.upshot.remoteDataview({ providerParameters: { url: "unused"}, provider: upshot.riaDataProvider });
+ $(dsTestDriver.ds).one("refreshError", dsTestDriver.onRefreshErrorEvent);
+ dsTestDriver.ds.refresh();
+ });
+
+ // commitStart
+ test("commitStart ctor", 4, function () {
+ stop();
+ dsTestDriver.simulateSuccessService();
+ var products = [];
+ dsTestDriver.ds = $.upshot.remoteDataview({
+ providerParameters: { url: "unused" },
+ provider: upshot.riaDataProvider,
+ commitStart: dsTestDriver.onCommitStart,
+ result: products,
+ commitSuccess: function () { start(); }
+ }).refresh(function () {
+ equal(products.length, dsTestDriver.productsResult.GetProductsResult.TotalCount, "Count checked");
+ dsTestDriver.simulatePostSuccessService();
+ $.observable(products[0]).property("Price", products[0].Price + 100);
+ });
+ });
+
+ test("commitStart bind ctor", 5, function () {
+ stop();
+ dsTestDriver.simulateSuccessService();
+ var products = [];
+ dsTestDriver.ds = $.upshot.remoteDataview({
+ providerParameters: { url: "unused" },
+ provider: upshot.riaDataProvider,
+ result: products,
+ commitSuccess: function () { start(); }
+ });
+ $(dsTestDriver.ds).bind("commitStart", dsTestDriver.onCommitStartEvent);
+ dsTestDriver.ds.refresh(function () {
+ equal(products.length, dsTestDriver.productsResult.GetProductsResult.TotalCount, "Count checked");
+ dsTestDriver.simulatePostSuccessService();
+ $.observable(products[0]).property("Price", products[0].Price + 100);
+ });
+ });
+
+ // commitSuccess
+ test("commitSuccess ctor", 6, function () {
+ stop();
+ dsTestDriver.simulateSuccessService();
+ var products = [];
+ dsTestDriver.ds = $.upshot.remoteDataview({
+ providerParameters: { url: "unused" },
+ provider: upshot.riaDataProvider,
+ result: products,
+ commitSuccess: dsTestDriver.onCommitSuccess
+ }).refresh(function () {
+ equal(products.length, dsTestDriver.productsResult.GetProductsResult.TotalCount, "Count checked");
+ dsTestDriver.simulatePostSuccessService();
+ $.observable(products[0]).property("Price", products[0].Price + 100);
+ });
+ });
+
+ test("commitSuccess commit callback", 6, function () {
+ stop();
+ dsTestDriver.simulateSuccessService();
+ var products = [];
+ dsTestDriver.ds = $.upshot.remoteDataview({
+ providerParameters: { url: "unused" },
+ provider: upshot.riaDataProvider,
+ result: products,
+ bufferChanges: true
+ });
+ dsTestDriver.ds.refresh(function () {
+ equal(products.length, dsTestDriver.productsResult.GetProductsResult.TotalCount, "Count checked");
+ dsTestDriver.simulatePostSuccessService();
+ $.observable(products[0]).property("Price", products[0].Price + 100);
+ this.commitChanges(dsTestDriver.onCommitSuccess, dsTestDriver.onCommitError);
+ });
+ });
+
+ test("commitSuccess bind", 7, function () {
+ stop();
+ dsTestDriver.simulateSuccessService();
+ var products = [];
+ dsTestDriver.ds = $.upshot.remoteDataview({
+ providerParameters: { url: "unused" },
+ provider: upshot.riaDataProvider,
+ result: products
+ });
+ $(dsTestDriver.ds).bind("commitSuccess", dsTestDriver.onCommitSuccessEvent);
+ dsTestDriver.ds.refresh(function () {
+ equal(products.length, dsTestDriver.productsResult.GetProductsResult.TotalCount, "Count checked");
+ dsTestDriver.simulatePostSuccessService();
+ $.observable(products[0]).property("Price", products[0].Price + 100);
+ });
+ });
+
+ // commitError
+ test("commitError ctor", 6, function () {
+ stop();
+ dsTestDriver.simulateSuccessService();
+ var products = [];
+ dsTestDriver.ds = $.upshot.remoteDataview({
+ providerParameters: { url: "unused" },
+ provider: upshot.riaDataProvider,
+ result: products,
+ commitError: dsTestDriver.onCommitError
+ }).refresh(function () {
+ equal(products.length, dsTestDriver.productsResult.GetProductsResult.TotalCount, "Count checked");
+ dsTestDriver.simulateErrorService();
+ $.observable(products[0]).property("Price", products[0].Price + 100);
+ });
+ });
+
+ test("commitError commit callback", 6, function () {
+ stop();
+ dsTestDriver.simulateSuccessService();
+ var products = [];
+ dsTestDriver.ds = $.upshot.remoteDataview({
+ providerParameters: { url: "unused" },
+ provider: upshot.riaDataProvider,
+ result: products,
+ bufferChanges: true
+ });
+ dsTestDriver.ds.refresh(function () {
+ equal(products.length, dsTestDriver.productsResult.GetProductsResult.TotalCount, "Count checked");
+ dsTestDriver.simulateErrorService();
+ $.observable(products[0]).property("Price", products[0].Price + 100);
+ this.commitChanges(dsTestDriver.onCommitSuccess, dsTestDriver.onCommitError);
+ });
+ });
+
+ test("commitError bind", 7, function () {
+ stop();
+ dsTestDriver.simulateSuccessService();
+ var products = [];
+ dsTestDriver.ds = $.upshot.remoteDataview({
+ providerParameters: { url: "unused" },
+ provider: upshot.riaDataProvider,
+ result: products
+ });
+ $(dsTestDriver.ds).bind("commitError", dsTestDriver.onCommitErrorEvent);
+ dsTestDriver.ds.refresh(function () {
+ equal(products.length, dsTestDriver.productsResult.GetProductsResult.TotalCount, "Count checked");
+ dsTestDriver.simulateErrorService();
+ $.observable(products[0]).property("Price", products[0].Price + 100);
+ });
+ });
+
+ test("commitValidationError commit callback", 6, function () {
+ stop();
+ dsTestDriver.simulateSuccessService();
+ var products = [];
+ dsTestDriver.ds = $.upshot.remoteDataview({
+ providerParameters: { url: "unused" },
+ provider: upshot.riaDataProvider,
+ result: products,
+ bufferChanges: true
+ });
+ dsTestDriver.ds.refresh(function () {
+ equal(products.length, dsTestDriver.productsResult.GetProductsResult.TotalCount, "Count checked");
+ dsTestDriver.simulateValidationErrorService();
+ $.observable(products).insert(0, { ID: 4, Manufacturer: "Kodak", Price: 800 });
+ this.commitChanges(dsTestDriver.onCommitSuccess, dsTestDriver.onCommitValidationError);
+ });
+ });
+
+})(this, upshot);
diff --git a/test/Settings.StyleCop b/test/Settings.StyleCop
new file mode 100644
index 00000000..d65b20f9
--- /dev/null
+++ b/test/Settings.StyleCop
@@ -0,0 +1,145 @@
+<StyleCopSettings Version="4.3">
+ <GlobalSettings>
+ <BooleanProperty Name="RulesEnabledByDefault">False</BooleanProperty>
+ <StringProperty Name="MergeSettingsFiles">NoMerge</StringProperty>
+ </GlobalSettings>
+ <Analyzers>
+
+ <Analyzer AnalyzerId="Microsoft.StyleCop.CSharp.NamingRules">
+ <AnalyzerSettings>
+ <CollectionProperty Name="Hungarian">
+ <Value>as</Value>
+ <Value>db</Value>
+ <Value>dc</Value>
+ <Value>do</Value>
+ <Value>ef</Value>
+ <Value>id</Value>
+ <Value>if</Value>
+ <Value>in</Value>
+ <Value>is</Value>
+ <Value>my</Value>
+ <Value>no</Value>
+ <Value>on</Value>
+ <Value>sl</Value>
+ <Value>to</Value>
+ <Value>ui</Value>
+ <Value>vs</Value>
+ </CollectionProperty>
+ </AnalyzerSettings>
+ </Analyzer>
+
+ <Analyzer AnalyzerId="Microsoft.StyleCop.CSharp.DocumentationRules">
+ <AnalyzerSettings>
+ <BooleanProperty Name="IgnorePrivates">True</BooleanProperty>
+ <BooleanProperty Name="IgnoreInternals">True</BooleanProperty>
+ <BooleanProperty Name="IncludeFields">False</BooleanProperty>
+
+ <StringProperty Name="Copyright">Copyright (c) Microsoft Corporation. All rights reserved.</StringProperty>
+ </AnalyzerSettings>
+
+ <Rules>
+ <!-- For non-product code, do not require enum items to be documented. -->
+ <Rule Name="EnumerationItemsMustBeDocumented">
+ <RuleSettings>
+ <BooleanProperty Name="Enabled">False</BooleanProperty>
+ </RuleSettings>
+ </Rule>
+
+ <!-- For non-product code, do not require specific wording of property and indexer summary. -->
+ <Rule Name="PropertySummaryDocumentationMustMatchAccessors">
+ <RuleSettings>
+ <BooleanProperty Name="Enabled">False</BooleanProperty>
+ </RuleSettings>
+ </Rule>
+ <Rule Name="PropertySummaryDocumentationMustOmitSetAccessorWithRestrictedAccess">
+ <RuleSettings>
+ <BooleanProperty Name="Enabled">False</BooleanProperty>
+ </RuleSettings>
+ </Rule>
+
+ <!-- For non-product code, do not require specific wording of constuctor and destructor summary. -->
+ <Rule Name="ConstructorSummaryDocumentationMustBeginWithStandardText">
+ <RuleSettings>
+ <BooleanProperty Name="Enabled">False</BooleanProperty>
+ </RuleSettings>
+ </Rule>
+ <Rule Name="DestructorSummaryDocumentationMustBeginWithStandardText">
+ <RuleSettings>
+ <BooleanProperty Name="Enabled">False</BooleanProperty>
+ </RuleSettings>
+ </Rule>
+
+ <!-- Documentation headers can contain blank lines, since they are not directly consumed for external documentation. -->
+ <Rule Name="DocumentationHeadersMustNotContainBlankLines">
+ <RuleSettings>
+ <BooleanProperty Name="Enabled">False</BooleanProperty>
+ </RuleSettings>
+ </Rule>
+
+ <!-- Do not require the file header to contain the name of the file. -->
+ <Rule Name="FileHeaderMustContainFileName">
+ <RuleSettings>
+ <BooleanProperty Name="Enabled">False</BooleanProperty>
+ </RuleSettings>
+ </Rule>
+ <Rule Name="FileHeaderFileNameDocumentationMustMatchFileName">
+ <RuleSettings>
+ <BooleanProperty Name="Enabled">False</BooleanProperty>
+ </RuleSettings>
+ </Rule>
+
+ <!-- Do not require the file header to contain a Company attribute. -->
+ <Rule Name="FileHeaderMustHaveValidCompanyText">
+ <RuleSettings>
+ <BooleanProperty Name="Enabled">False</BooleanProperty>
+ </RuleSettings>
+ </Rule>
+ </Rules>
+ </Analyzer>
+
+ <Analyzer AnalyzerId="Microsoft.StyleCop.CSharp.OrderingRules">
+ <!-- For non-product code, do not enforce specific ordering of elements. -->
+ <Rules>
+ <Rule Name="ElementsMustBeOrderedByAccess">
+ <RuleSettings>
+ <BooleanProperty Name="Enabled">False</BooleanProperty>
+ </RuleSettings>
+ </Rule>
+ <Rule Name="StaticElementsMustAppearBeforeInstanceElements">
+ <RuleSettings>
+ <BooleanProperty Name="Enabled">False</BooleanProperty>
+ </RuleSettings>
+ </Rule>
+ </Rules>
+ <AnalyzerSettings />
+ </Analyzer>
+
+ <Analyzer AnalyzerId="Microsoft.StyleCop.CSharp.MaintainabilityRules">
+ <Rules>
+ <!-- For non-product code, allow multiple classes and namespaces within a file. -->
+ <Rule Name="FileMayOnlyContainASingleClass">
+ <RuleSettings>
+ <BooleanProperty Name="Enabled">False</BooleanProperty>
+ </RuleSettings>
+ </Rule>
+ <Rule Name="FileMayOnlyContainASingleNamespace">
+ <RuleSettings>
+ <BooleanProperty Name="Enabled">False</BooleanProperty>
+ </RuleSettings>
+ </Rule>
+ </Rules>
+ </Analyzer>
+
+ <Analyzer AnalyzerId="Microsoft.StyleCop.CSharp.ReadabilityRules">
+ <Rules>
+ <!-- Per ADP guidelines, method parameter are allowed to span across multiple lines (rather than having to be assigned to a temporary variable). -->
+ <Rule Name="ParameterMustNotSpanMultipleLines">
+ <RuleSettings>
+ <BooleanProperty Name="Enabled">False</BooleanProperty>
+ </RuleSettings>
+ </Rule>
+ </Rules>
+ </Analyzer>
+
+ </Analyzers>
+</StyleCopSettings>
diff --git a/test/System.Json.Test.Integration/Common/InstanceCreator.cs b/test/System.Json.Test.Integration/Common/InstanceCreator.cs
new file mode 100644
index 00000000..80d25e7c
--- /dev/null
+++ b/test/System.Json.Test.Integration/Common/InstanceCreator.cs
@@ -0,0 +1,1585 @@
+using System.Collections.Generic;
+using System.Reflection;
+using System.Runtime.Serialization;
+using System.Text;
+using System.Text.RegularExpressions;
+
+namespace System.Json
+{
+ /// <summary>
+ /// Settings used by the <see cref="InstanceCreator"/> class.
+ /// </summary>
+ public static class CreatorSettings
+ {
+ static CreatorSettings()
+ {
+ MaxArrayLength = 10;
+ MaxListLength = 10;
+ MaxStringLength = 100;
+ CreateOnlyAsciiChars = false;
+ DontCreateSurrogateChars = false;
+ CreateDateTimeWithSubMilliseconds = true;
+ NullValueProbability = 0.01;
+ AvoidStackOverflowDueToTypeCycles = false;
+ CreatorSurrogate = null;
+ }
+
+ /// <summary>
+ /// Gets or sets the maximum length of arrays created by the <see cref="InstanceCreator"/>.
+ /// </summary>
+ public static int MaxArrayLength { get; set; }
+
+ /// <summary>
+ /// Gets or sets the maximum length of lists created by the <see cref="InstanceCreator"/>.
+ /// </summary>
+ public static int MaxListLength { get; set; }
+
+ /// <summary>
+ /// Gets or sets the maximum length of strings created by the <see cref="InstanceCreator"/>.
+ /// </summary>
+ public static int MaxStringLength { get; set; }
+
+ /// <summary>
+ /// Gets or sets a flag indicating whether only ascii chars should be used when creating strings.
+ /// </summary>
+ public static bool CreateOnlyAsciiChars { get; set; }
+
+ /// <summary>
+ /// Gets or sets a flag indicating whether chars in the surrogate range can be returned by the
+ /// <see cref="InstanceCreator"/> when creating char instances.
+ /// </summary>
+ public static bool DontCreateSurrogateChars { get; set; }
+
+ /// <summary>
+ /// Gets or sets a flag indicating whether <see cref="DateTime"/> values created by the
+ /// <see cref="InstanceCreator"/> can have submillisecond precision.
+ /// </summary>
+ public static bool CreateDateTimeWithSubMilliseconds { get; set; }
+
+ /// <summary>
+ /// Gets or sets a value (0-1) indicating the probability of the <see cref="InstanceCreator"/>
+ /// returning a <code>null</code> value when creating instances of class types.
+ /// </summary>
+ public static double NullValueProbability { get; set; }
+
+ /// <summary>
+ /// Gets or sets a flag indicating whether the protection against stack overflow
+ /// for cyclic types is enabled. If this flag is set, whenever a type which has already
+ /// been created up in the stack is created again, the <see cref="InstanceCreator"/>
+ /// will return the default value for that type.
+ /// </summary>
+ public static bool AvoidStackOverflowDueToTypeCycles { get; set; }
+
+ /// <summary>
+ /// Gets or sets the instance of an <see cref="InstanceCreatorSurrogate"/> which can intercept
+ /// requests to create instances on the <see cref="InstanceCreator"/>.
+ /// </summary>
+ public static InstanceCreatorSurrogate CreatorSurrogate { get; set; }
+ }
+
+ /// <summary>
+ /// Utility class used to create test instances of primitive types.
+ /// </summary>
+ public static class PrimitiveCreator
+ {
+ static readonly Regex RelativeIPv6UriRegex = new Regex(@"^\/\/(.+\@)?\[\:\:\d\]");
+ static Dictionary<Type, MethodInfo> creators;
+
+ static PrimitiveCreator()
+ {
+ Type primitiveCreatorType = typeof(PrimitiveCreator);
+ creators = new Dictionary<Type, MethodInfo>();
+ creators.Add(typeof(bool), primitiveCreatorType.GetMethod("CreateInstanceOfBoolean", BindingFlags.Public | BindingFlags.Static));
+ creators.Add(typeof(byte), primitiveCreatorType.GetMethod("CreateInstanceOfByte", BindingFlags.Public | BindingFlags.Static));
+ creators.Add(typeof(char), primitiveCreatorType.GetMethod("CreateInstanceOfChar", BindingFlags.Public | BindingFlags.Static));
+ creators.Add(typeof(DateTime), primitiveCreatorType.GetMethod("CreateInstanceOfDateTime", BindingFlags.Public | BindingFlags.Static));
+ creators.Add(typeof(DateTimeOffset), primitiveCreatorType.GetMethod("CreateInstanceOfDateTimeOffset", BindingFlags.Public | BindingFlags.Static));
+ creators.Add(typeof(decimal), primitiveCreatorType.GetMethod("CreateInstanceOfDecimal", BindingFlags.Public | BindingFlags.Static));
+ creators.Add(typeof(double), primitiveCreatorType.GetMethod("CreateInstanceOfDouble", BindingFlags.Public | BindingFlags.Static));
+ creators.Add(typeof(Guid), primitiveCreatorType.GetMethod("CreateInstanceOfGuid", BindingFlags.Public | BindingFlags.Static));
+ creators.Add(typeof(short), primitiveCreatorType.GetMethod("CreateInstanceOfInt16", BindingFlags.Public | BindingFlags.Static));
+ creators.Add(typeof(int), primitiveCreatorType.GetMethod("CreateInstanceOfInt32", BindingFlags.Public | BindingFlags.Static));
+ creators.Add(typeof(long), primitiveCreatorType.GetMethod("CreateInstanceOfInt64", BindingFlags.Public | BindingFlags.Static));
+ creators.Add(typeof(object), primitiveCreatorType.GetMethod("CreateInstanceOfObject", BindingFlags.Public | BindingFlags.Static));
+ creators.Add(typeof(sbyte), primitiveCreatorType.GetMethod("CreateInstanceOfSByte", BindingFlags.Public | BindingFlags.Static));
+ creators.Add(typeof(float), primitiveCreatorType.GetMethod("CreateInstanceOfSingle", BindingFlags.Public | BindingFlags.Static));
+ creators.Add(typeof(string), primitiveCreatorType.GetMethod("CreateInstanceOfString", BindingFlags.Public | BindingFlags.Static, null, new Type[] { typeof(Random) }, null));
+ creators.Add(typeof(ushort), primitiveCreatorType.GetMethod("CreateInstanceOfUInt16", BindingFlags.Public | BindingFlags.Static));
+ creators.Add(typeof(uint), primitiveCreatorType.GetMethod("CreateInstanceOfUInt32", BindingFlags.Public | BindingFlags.Static));
+ creators.Add(typeof(ulong), primitiveCreatorType.GetMethod("CreateInstanceOfUInt64", BindingFlags.Public | BindingFlags.Static));
+ creators.Add(typeof(Uri), primitiveCreatorType.GetMethod("CreateInstanceOfUri", BindingFlags.Public | BindingFlags.Static));
+ }
+
+ /// <summary>
+ /// Creates an instance of the <see cref="Boolean"/> type.
+ /// </summary>
+ /// <param name="rndGen">A <see cref="Random"/> used to create the instance.</param>
+ /// <returns>An instance of the <see cref="Boolean"/> type.</returns>
+ public static bool CreateInstanceOfBoolean(Random rndGen)
+ {
+ return rndGen.Next(2) == 0;
+ }
+
+ /// <summary>
+ /// Creates an instance of the <see cref="Byte"/> type.
+ /// </summary>
+ /// <param name="rndGen">A <see cref="Random"/> used to create the instance.</param>
+ /// <returns>An instance of the <see cref="Byte"/> type.</returns>
+ public static byte CreateInstanceOfByte(Random rndGen)
+ {
+ byte[] rndValue = new byte[1];
+ rndGen.NextBytes(rndValue);
+ return rndValue[0];
+ }
+
+ /// <summary>
+ /// Creates an instance of the <see cref="Char"/> type.
+ /// </summary>
+ /// <param name="rndGen">A <see cref="Random"/> used to create the instance.</param>
+ /// <returns>An instance of the <see cref="Char"/> type.</returns>
+ public static char CreateInstanceOfChar(Random rndGen)
+ {
+ if (CreatorSettings.CreateOnlyAsciiChars)
+ {
+ return (char)rndGen.Next(0x20, 0x7F);
+ }
+ else if (CreatorSettings.DontCreateSurrogateChars)
+ {
+ char c;
+ do
+ {
+ c = (char)rndGen.Next((int)Char.MinValue, (int)Char.MaxValue);
+ }
+ while (Char.IsSurrogate(c));
+ return c;
+ }
+ else
+ {
+ return (char)rndGen.Next((int)Char.MinValue, (int)Char.MaxValue + 1);
+ }
+ }
+
+ /// <summary>
+ /// Creates an instance of the <see cref="DateTime"/> type.
+ /// </summary>
+ /// <param name="rndGen">A <see cref="Random"/> used to create the instance.</param>
+ /// <returns>An instance of the <see cref="DateTime"/> type.</returns>
+ public static System.DateTime CreateInstanceOfDateTime(Random rndGen)
+ {
+ long temp = CreateInstanceOfInt64(rndGen);
+ temp = Math.Abs(temp);
+ DateTime result;
+ try
+ {
+ result = new DateTime(temp % (DateTime.MaxValue.Ticks + 1));
+ }
+ catch (ArgumentOutOfRangeException)
+ {
+ result = DateTime.Now;
+ }
+
+ int kind = rndGen.Next(3);
+ switch (kind)
+ {
+ case 0:
+ result = DateTime.SpecifyKind(result, DateTimeKind.Local);
+ break;
+ case 1:
+ result = DateTime.SpecifyKind(result, DateTimeKind.Unspecified);
+ break;
+ default:
+ result = DateTime.SpecifyKind(result, DateTimeKind.Utc);
+ break;
+ }
+
+ if (!CreatorSettings.CreateDateTimeWithSubMilliseconds)
+ {
+ result = new DateTime(
+ result.Year,
+ result.Month,
+ result.Day,
+ result.Hour,
+ result.Minute,
+ result.Second,
+ result.Millisecond,
+ result.Kind);
+ }
+
+ return result;
+ }
+
+ /// <summary>
+ /// Creates an instance of the <see cref="DateTimeOffset"/> type.
+ /// </summary>
+ /// <param name="rndGen">A <see cref="Random"/> used to create the instance.</param>
+ /// <returns>An instance of the <see cref="DateTimeOffset"/> type.</returns>
+ public static System.DateTimeOffset CreateInstanceOfDateTimeOffset(Random rndGen)
+ {
+ DateTime temp = CreateInstanceOfDateTime(rndGen);
+ temp = DateTime.SpecifyKind(temp, DateTimeKind.Unspecified);
+ int offsetMinutes = rndGen.Next(-14 * 60, 14 * 60);
+ DateTimeOffset result = new DateTimeOffset(temp, TimeSpan.FromMinutes(offsetMinutes));
+ return result;
+ }
+
+ /// <summary>
+ /// Creates an instance of the <see cref="Decimal"/> type.
+ /// </summary>
+ /// <param name="rndGen">A <see cref="Random"/> used to create the instance.</param>
+ /// <returns>An instance of the <see cref="Decimal"/> type.</returns>
+ public static decimal CreateInstanceOfDecimal(Random rndGen)
+ {
+ int low = CreateInstanceOfInt32(rndGen);
+ int mid = CreateInstanceOfInt32(rndGen);
+ int high = CreateInstanceOfInt32(rndGen);
+ bool isNegative = rndGen.Next(2) == 0;
+ const int MaxDecimalScale = 28;
+ byte scale = (byte)rndGen.Next(0, MaxDecimalScale + 1);
+ return new decimal(low, mid, high, isNegative, scale);
+ }
+
+ /// <summary>
+ /// Creates an instance of the <see cref="Double"/> type.
+ /// </summary>
+ /// <param name="rndGen">A <see cref="Random"/> used to create the instance.</param>
+ /// <returns>An instance of the <see cref="Double"/> type.</returns>
+ public static double CreateInstanceOfDouble(Random rndGen)
+ {
+ bool negative = rndGen.Next(2) == 0;
+ int temp = rndGen.Next(40);
+ double result;
+ switch (temp)
+ {
+ case 0: return Double.NaN;
+ case 1: return Double.PositiveInfinity;
+ case 2: return Double.NegativeInfinity;
+ case 3: return Double.MinValue;
+ case 4: return Double.MaxValue;
+ case 5: return Double.Epsilon;
+ default:
+ result = (double)(rndGen.NextDouble() * 100000);
+ if (negative)
+ {
+ result = -result;
+ }
+
+ return result;
+ }
+ }
+
+ /// <summary>
+ /// Creates an instance of the <see cref="Guid"/> type.
+ /// </summary>
+ /// <param name="rndGen">A <see cref="Random"/> used to create the instance.</param>
+ /// <returns>An instance of the <see cref="Guid"/> type.</returns>
+ public static System.Guid CreateInstanceOfGuid(Random rndGen)
+ {
+ byte[] temp = new byte[16];
+ rndGen.NextBytes(temp);
+ return new Guid(temp);
+ }
+
+ /// <summary>
+ /// Creates an instance of the <see cref="Int16"/> type.
+ /// </summary>
+ /// <param name="rndGen">A <see cref="Random"/> used to create the instance.</param>
+ /// <returns>An instance of the <see cref="Int16"/> type.</returns>
+ public static short CreateInstanceOfInt16(Random rndGen)
+ {
+ byte[] rndValue = new byte[2];
+ rndGen.NextBytes(rndValue);
+ short result = 0;
+ for (int i = 0; i < rndValue.Length; i++)
+ {
+ result = (short)(result << 8);
+ result = (short)(result | (short)rndValue[i]);
+ }
+
+ return result;
+ }
+
+ /// <summary>
+ /// Creates an instance of the <see cref="Int32"/> type.
+ /// </summary>
+ /// <param name="rndGen">A <see cref="Random"/> used to create the instance.</param>
+ /// <returns>An instance of the <see cref="Int32"/> type.</returns>
+ public static int CreateInstanceOfInt32(Random rndGen)
+ {
+ byte[] rndValue = new byte[4];
+ rndGen.NextBytes(rndValue);
+ int result = 0;
+ for (int i = 0; i < rndValue.Length; i++)
+ {
+ result = (int)(result << 8);
+ result = (int)(result | (int)rndValue[i]);
+ }
+
+ return result;
+ }
+
+ /// <summary>
+ /// Creates an instance of the <see cref="Int64"/> type.
+ /// </summary>
+ /// <param name="rndGen">A <see cref="Random"/> used to create the instance.</param>
+ /// <returns>An instance of the <see cref="Int64"/> type.</returns>
+ public static long CreateInstanceOfInt64(Random rndGen)
+ {
+ byte[] rndValue = new byte[8];
+ rndGen.NextBytes(rndValue);
+ long result = 0;
+ for (int i = 0; i < rndValue.Length; i++)
+ {
+ result = (long)(result << 8);
+ result = (long)(result | (long)rndValue[i]);
+ }
+
+ return result;
+ }
+
+ /// <summary>
+ /// Creates an instance of the <see cref="Object"/> type.
+ /// </summary>
+ /// <param name="rndGen">A <see cref="Random"/> used to create the instance.</param>
+ /// <returns>An instance of the <see cref="Object"/> type.</returns>
+ public static object CreateInstanceOfObject(Random rndGen)
+ {
+ return (rndGen.Next(5) == 0) ? null : new object();
+ }
+
+ /// <summary>
+ /// Creates an instance of the <see cref="SByte"/> type.
+ /// </summary>
+ /// <param name="rndGen">A <see cref="Random"/> used to create the instance.</param>
+ /// <returns>An instance of the <see cref="SByte"/> type.</returns>
+ [CLSCompliant(false)]
+ public static sbyte CreateInstanceOfSByte(Random rndGen)
+ {
+ byte[] rndValue = new byte[1];
+ rndGen.NextBytes(rndValue);
+ sbyte result = (sbyte)rndValue[0];
+ return result;
+ }
+
+ /// <summary>
+ /// Creates an instance of the <see cref="Single"/> type.
+ /// </summary>
+ /// <param name="rndGen">A <see cref="Random"/> used to create the instance.</param>
+ /// <returns>An instance of the <see cref="Single"/> type.</returns>
+ public static float CreateInstanceOfSingle(Random rndGen)
+ {
+ bool negative = rndGen.Next(2) == 0;
+ int temp = rndGen.Next(40);
+ float result;
+ switch (temp)
+ {
+ case 0: return Single.NaN;
+ case 1: return Single.PositiveInfinity;
+ case 2: return Single.NegativeInfinity;
+ case 3: return Single.MinValue;
+ case 4: return Single.MaxValue;
+ case 5: return Single.Epsilon;
+ default:
+ result = (float)(rndGen.NextDouble() * 100000);
+ if (negative)
+ {
+ result = -result;
+ }
+
+ return result;
+ }
+ }
+
+ /// <summary>
+ /// Creates an instance of the <see cref="String"/> type.
+ /// </summary>
+ /// <param name="rndGen">A <see cref="Random"/> used to create the instance.</param>
+ /// <param name="size">The size of the string to be creted.</param>
+ /// <param name="charsToUse">The characters to use when creating the string.</param>
+ /// <returns>An instance of the <see cref="String"/> type.</returns>
+ public static string CreateRandomString(Random rndGen, int size, string charsToUse)
+ {
+ int maxSize = CreatorSettings.MaxStringLength;
+
+ // invalid per the XML spec (http://www.w3.org/TR/REC-xml/#charsets), cannot be sent as XML
+ string invalidXmlChars = "\u0000\u0001\u0002\u0003\u0004\u0005\u0006\u0007\u0008\u000B\u000C\u000E\u000F\u0010\u0011\u0012\u0013\u0014\u0015\u0016\u0017\u0018\u0019\u001A\u001B\u001C\u001D\u001E\u001F\uFFFE\uFFFF";
+
+ const int LowSurrogateMin = 0xDC00;
+ const int LowSurrogateMax = 0xDFFF;
+ const int HighSurrogateMin = 0xD800;
+ const int HighSurrogateMax = 0xDBFF;
+
+ if (size < 0)
+ {
+ double rndNumber = rndGen.NextDouble();
+ if (rndNumber < CreatorSettings.NullValueProbability)
+ {
+ return null; // 1% chance of null value
+ }
+
+ size = (int)Math.Pow(maxSize, rndNumber); // this will create more small strings than large ones
+ size--;
+ }
+
+ StringBuilder sb = new StringBuilder();
+ for (int i = 0; i < size; i++)
+ {
+ char c;
+ if (charsToUse != null)
+ {
+ c = charsToUse[rndGen.Next(charsToUse.Length)];
+ sb.Append(c);
+ }
+ else
+ {
+ if (CreatorSettings.CreateOnlyAsciiChars || rndGen.Next(2) == 0)
+ {
+ c = (char)rndGen.Next(0x20, 0x7F); // low-ascii chars
+ sb.Append(c);
+ }
+ else
+ {
+ do
+ {
+ c = (char)rndGen.Next((int)Char.MinValue, (int)Char.MaxValue + 1);
+ }
+ while ((LowSurrogateMin <= c && c <= LowSurrogateMax) || (invalidXmlChars.IndexOf(c) >= 0));
+
+ sb.Append(c);
+ if (HighSurrogateMin <= c && c <= HighSurrogateMax)
+ {
+ // need to add a low surrogate
+ c = (char)rndGen.Next(LowSurrogateMin, LowSurrogateMax + 1);
+ sb.Append(c);
+ }
+ }
+ }
+ }
+
+ return sb.ToString();
+ }
+
+ /// <summary>
+ /// Creates an instance of the <see cref="String"/> type.
+ /// </summary>
+ /// <param name="rndGen">A <see cref="Random"/> used to create the instance.</param>
+ /// <returns>An instance of the <see cref="String"/> type.</returns>
+ public static string CreateInstanceOfString(Random rndGen)
+ {
+ return CreateInstanceOfString(rndGen, true);
+ }
+
+ /// <summary>
+ /// Creates an instance of the <see cref="String"/> type.
+ /// </summary>
+ /// <param name="rndGen">A <see cref="Random"/> used to create the instance.</param>
+ /// <param name="allowNull">A flag indicating whether null values can be returned.</param>
+ /// <returns>An instance of the <see cref="String"/> type.</returns>
+ public static string CreateInstanceOfString(Random rndGen, bool allowNull)
+ {
+ string result;
+ do
+ {
+ result = CreateRandomString(rndGen, -1, null);
+ }
+ while (result == null && !allowNull);
+
+ return result;
+ }
+
+ /// <summary>
+ /// Creates an instance of the <see cref="String"/> type.
+ /// </summary>
+ /// <param name="rndGen">A <see cref="Random"/> used to create the instance.</param>
+ /// <param name="size">The size of the string to be creted.</param>
+ /// <param name="charsToUse">The characters to use when creating the string.</param>
+ /// <returns>An instance of the <see cref="String"/> type.</returns>
+ public static string CreateInstanceOfString(Random rndGen, int size, string charsToUse)
+ {
+ return CreateRandomString(rndGen, size, charsToUse);
+ }
+
+ /// <summary>
+ /// Creates an instance of the <see cref="UInt16"/> type.
+ /// </summary>
+ /// <param name="rndGen">A <see cref="Random"/> used to create the instance.</param>
+ /// <returns>An instance of the <see cref="UInt16"/> type.</returns>
+ [CLSCompliant(false)]
+ public static ushort CreateInstanceOfUInt16(Random rndGen)
+ {
+ byte[] rndValue = new byte[2];
+ rndGen.NextBytes(rndValue);
+ ushort result = 0;
+ for (int i = 0; i < rndValue.Length; i++)
+ {
+ result = (ushort)(result << 8);
+ result = (ushort)(result | (ushort)rndValue[i]);
+ }
+
+ return result;
+ }
+
+ /// <summary>
+ /// Creates an instance of the <see cref="UInt32"/> type.
+ /// </summary>
+ /// <param name="rndGen">A <see cref="Random"/> used to create the instance.</param>
+ /// <returns>An instance of the <see cref="UInt32"/> type.</returns>
+ [CLSCompliant(false)]
+ public static uint CreateInstanceOfUInt32(Random rndGen)
+ {
+ byte[] rndValue = new byte[4];
+ rndGen.NextBytes(rndValue);
+ uint result = 0;
+ for (int i = 0; i < rndValue.Length; i++)
+ {
+ result = (uint)(result << 8);
+ result = (uint)(result | (uint)rndValue[i]);
+ }
+
+ return result;
+ }
+
+ /// <summary>
+ /// Creates an instance of the <see cref="UInt64"/> type.
+ /// </summary>
+ /// <param name="rndGen">A <see cref="Random"/> used to create the instance.</param>
+ /// <returns>An instance of the <see cref="UInt64"/> type.</returns>
+ [CLSCompliant(false)]
+ public static ulong CreateInstanceOfUInt64(Random rndGen)
+ {
+ byte[] rndValue = new byte[8];
+ rndGen.NextBytes(rndValue);
+ ulong result = 0;
+ for (int i = 0; i < rndValue.Length; i++)
+ {
+ result = (ulong)(result << 8);
+ result = (ulong)(result | (ulong)rndValue[i]);
+ }
+
+ return result;
+ }
+
+ /// <summary>
+ /// Creates an instance of the <see cref="Uri"/> type.
+ /// </summary>
+ /// <param name="rndGen">A <see cref="Random"/> used to create the instance.</param>
+ /// <returns>An instance of the <see cref="Uri"/> type.</returns>
+ public static System.Uri CreateInstanceOfUri(Random rndGen)
+ {
+ Uri result;
+ UriKind kind;
+ try
+ {
+ string uriString;
+ do
+ {
+ uriString = UriCreator.CreateUri(rndGen, out kind);
+ }
+ while (IsRelativeIPv6Uri(uriString, kind));
+ result = new Uri(uriString, kind);
+ }
+ catch (ArgumentException)
+ {
+ result = new Uri("my.schema://userName:password@my.domain/path1/path2?query1=123&query2=%22hello%22");
+ }
+
+ return result;
+ }
+
+ /// <summary>
+ /// Creates an instance of the a string which represents an <see cref="Uri"/>.
+ /// </summary>
+ /// <param name="rndGen">A <see cref="Random"/> used to create the instance.</param>
+ /// <returns>An instance of the a string which represents an <see cref="Uri"/>.</returns>
+ public static string CreateInstanceOfUriString(Random rndGen)
+ {
+ UriKind kind;
+ return UriCreator.CreateUri(rndGen, out kind);
+ }
+
+ /// <summary>
+ /// Checks whether this creator can create an instance of the given type.
+ /// </summary>
+ /// <param name="type">The type to be created.</param>
+ /// <returns><code>true</code> if this creator can create an instance of the given type; <code>false</code> otherwise.</returns>
+ public static bool CanCreateInstanceOf(Type type)
+ {
+ return creators.ContainsKey(type);
+ }
+
+ /// <summary>
+ /// Creates an instance of the given primitive type.
+ /// </summary>
+ /// <param name="type">The type to create an instance.</param>
+ /// <param name="rndGen">A <see cref="Random"/> used to create the instance.</param>
+ /// <returns>An instance of the given type.</returns>
+ public static object CreatePrimitiveInstance(Type type, Random rndGen)
+ {
+ if (creators.ContainsKey(type))
+ {
+ return creators[type].Invoke(null, new object[] { rndGen });
+ }
+ else
+ {
+ throw new ArgumentException("Type " + type.FullName + " not supported");
+ }
+ }
+
+ private static bool IsRelativeIPv6Uri(string uriString, UriKind kind)
+ {
+ return kind == UriKind.Relative && RelativeIPv6UriRegex.Match(uriString).Success;
+ }
+
+ /// <summary>
+ /// Creates URI instances based on RFC 2396
+ /// </summary>
+ internal static class UriCreator
+ {
+ static readonly string digit;
+ static readonly string upalpha;
+ static readonly string lowalpha;
+ static readonly string alpha;
+ static readonly string alphanum;
+ static readonly string hex;
+ static readonly string mark;
+ static readonly string unreserved;
+ static readonly string reserved;
+
+ static UriCreator()
+ {
+ digit = "0123456789";
+ upalpha = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
+ lowalpha = upalpha.ToLower();
+ alpha = upalpha + lowalpha;
+ alphanum = alpha + digit;
+ hex = digit + "ABCDEFabcdef";
+ mark = "-_.!~*'()";
+ unreserved = alphanum + mark;
+ reserved = ";/?:@&=+$,";
+ }
+
+ internal static string CreateUri(Random rndGen, out UriKind kind)
+ {
+ StringBuilder sb = new StringBuilder();
+ kind = UriKind.Relative;
+ if (rndGen.Next(3) > 0)
+ {
+ // Add URI scheme
+ CreateScheme(sb, rndGen);
+ kind = UriKind.Absolute;
+ }
+
+ if (rndGen.Next(3) > 0)
+ {
+ // Add URI host
+ sb.Append("//");
+ if (rndGen.Next(10) == 0)
+ {
+ CreateUserInfo(sb, rndGen);
+ }
+
+ CreateHost(sb, rndGen);
+ if (rndGen.Next(2) > 0)
+ {
+ sb.Append(':');
+ sb.Append(rndGen.Next(65536));
+ }
+ }
+
+ if (rndGen.Next(4) > 0)
+ {
+ // Add URI path
+ for (int i = 0; i < rndGen.Next(1, 4); i++)
+ {
+ sb.Append('/');
+ AddPathSegment(sb, rndGen);
+ }
+ }
+
+ if (rndGen.Next(3) == 0)
+ {
+ // Add URI query string
+ sb.Append('?');
+ AddUriC(sb, rndGen);
+ }
+
+ return sb.ToString();
+ }
+
+ private static void CreateScheme(StringBuilder sb, Random rndGen)
+ {
+ int size = rndGen.Next(1, 10);
+ AddChars(sb, rndGen, alpha, 1);
+ string schemeChars = alpha + digit + "+-.";
+ AddChars(sb, rndGen, schemeChars, size);
+ sb.Append(':');
+ }
+
+ private static void CreateIPv4Address(StringBuilder sb, Random rndGen)
+ {
+ for (int i = 0; i < 4; i++)
+ {
+ if (i > 0)
+ {
+ sb.Append('.');
+ }
+
+ sb.Append(rndGen.Next(1000));
+ }
+ }
+
+ private static void AddIPv6AddressPart(StringBuilder sb, Random rndGen)
+ {
+ int size = rndGen.Next(1, 10);
+ if (size > 4)
+ {
+ size = 4;
+ }
+
+ AddChars(sb, rndGen, hex, size);
+ }
+
+ private static void CreateIPv6Address(StringBuilder sb, Random rndGen)
+ {
+ sb.Append('[');
+ int temp = rndGen.Next(6);
+ int i;
+ switch (temp)
+ {
+ case 0:
+ sb.Append("::");
+ break;
+ case 1:
+ sb.Append("::1");
+ break;
+ case 2:
+ sb.Append("FF01::101");
+ break;
+ case 3:
+ sb.Append("::1");
+ break;
+ case 4:
+ for (i = 0; i < 3; i++)
+ {
+ AddIPv6AddressPart(sb, rndGen);
+ sb.Append(':');
+ }
+
+ for (i = 0; i < 3; i++)
+ {
+ sb.Append(':');
+ AddIPv6AddressPart(sb, rndGen);
+ }
+
+ break;
+ default:
+ for (i = 0; i < 8; i++)
+ {
+ if (i > 0)
+ {
+ sb.Append(':');
+ }
+
+ AddIPv6AddressPart(sb, rndGen);
+ }
+
+ break;
+ }
+
+ sb.Append(']');
+ }
+
+ private static void AddChars(StringBuilder sb, Random rndGen, string validChars, int size)
+ {
+ for (int i = 0; i < size; i++)
+ {
+ sb.Append(validChars[rndGen.Next(validChars.Length)]);
+ }
+ }
+
+ private static void CreateHostName(StringBuilder sb, Random rndGen)
+ {
+ int domainLabelCount = rndGen.Next(4);
+ int size;
+ for (int i = 0; i < domainLabelCount; i++)
+ {
+ AddChars(sb, rndGen, alphanum, 1);
+ size = rndGen.Next(10) - 1;
+ if (size > 0)
+ {
+ AddChars(sb, rndGen, alphanum + "-", size);
+ AddChars(sb, rndGen, alphanum, 1);
+ }
+
+ sb.Append('.');
+ }
+
+ AddChars(sb, rndGen, alpha, 1);
+ size = rndGen.Next(10) - 1;
+ if (size > 0)
+ {
+ AddChars(sb, rndGen, alphanum + "-", size);
+ AddChars(sb, rndGen, alphanum, 1);
+ }
+ }
+
+ private static void CreateHost(StringBuilder sb, Random rndGen)
+ {
+ int temp = rndGen.Next(3);
+ switch (temp)
+ {
+ case 0:
+ CreateIPv4Address(sb, rndGen);
+ break;
+ case 1:
+ CreateIPv6Address(sb, rndGen);
+ break;
+ case 2:
+ CreateHostName(sb, rndGen);
+ break;
+ }
+ }
+
+ private static void CreateUserInfo(StringBuilder sb, Random rndGen)
+ {
+ AddChars(sb, rndGen, alpha, rndGen.Next(1, 10));
+ if (rndGen.Next(3) > 0)
+ {
+ sb.Append(':');
+ AddChars(sb, rndGen, alpha, rndGen.Next(1, 10));
+ }
+
+ sb.Append('@');
+ }
+
+ private static void AddEscapedChar(StringBuilder sb, Random rndGen)
+ {
+ sb.Append('%');
+ AddChars(sb, rndGen, hex, 2);
+ }
+
+ private static void AddPathSegment(StringBuilder sb, Random rndGen)
+ {
+ string pchar = unreserved + ":@&=+$,";
+ int size = rndGen.Next(1, 10);
+ for (int i = 0; i < size; i++)
+ {
+ if (rndGen.Next(pchar.Length + 1) > 0)
+ {
+ AddChars(sb, rndGen, pchar, 1);
+ }
+ else
+ {
+ AddEscapedChar(sb, rndGen);
+ }
+ }
+ }
+
+ private static void AddUriC(StringBuilder sb, Random rndGen)
+ {
+ int size = rndGen.Next(20);
+ string reservedPlusUnreserved = reserved + unreserved;
+ for (int i = 0; i < size; i++)
+ {
+ if (rndGen.Next(5) > 0)
+ {
+ AddChars(sb, rndGen, reservedPlusUnreserved, 1);
+ }
+ else
+ {
+ AddEscapedChar(sb, rndGen);
+ }
+ }
+ }
+ }
+ }
+
+ /// <summary>
+ /// Utility class used to create test instances of arbitrary types.
+ /// </summary>
+ public static class InstanceCreator
+ {
+ private static Stack<Type> typesInCreationStack = new Stack<Type>();
+
+ /// <summary>
+ /// Creates an instance of an array type.
+ /// </summary>
+ /// <param name="arrayType">The array type.</param>
+ /// <param name="rndGen">A <see cref="Random"/> used to create the instance.</param>
+ /// <returns>An instance of the given array type.</returns>
+ public static object CreateInstanceOfArray(Type arrayType, Random rndGen)
+ {
+ Type type = arrayType.GetElementType();
+ double rndNumber = rndGen.NextDouble();
+ if (rndNumber < CreatorSettings.NullValueProbability)
+ {
+ return null; // 1% chance of null value
+ }
+
+ int size = (int)Math.Pow(CreatorSettings.MaxArrayLength, rndNumber); // this will create more small arrays than large ones
+ size--;
+ Array result = Array.CreateInstance(type, size);
+ for (int i = 0; i < size; i++)
+ {
+ result.SetValue(CreateInstanceOf(type, rndGen), i);
+ }
+
+ return result;
+ }
+
+ /// <summary>
+ /// Creates an instance of a <see cref="List{T}"/>.
+ /// </summary>
+ /// <param name="listType">The List&lt;T&gt; type.</param>
+ /// <param name="rndGen">A <see cref="Random"/> used to create the instance.</param>
+ /// <returns>An instance of the given list type.</returns>
+ public static object CreateInstanceOfListOfT(Type listType, Random rndGen)
+ {
+ Type type = listType.GetGenericArguments()[0];
+ double rndNumber = rndGen.NextDouble();
+ if (rndNumber < CreatorSettings.NullValueProbability)
+ {
+ return null; // 1% chance of null value
+ }
+
+ int size = (int)Math.Pow(CreatorSettings.MaxListLength, rndNumber); // this will create more small lists than large ones
+ size--;
+ object result = Activator.CreateInstance(listType);
+ MethodInfo addMethod = listType.GetMethod("Add");
+ for (int i = 0; i < size; i++)
+ {
+ addMethod.Invoke(result, new object[] { CreateInstanceOf(type, rndGen) });
+ }
+
+ return result;
+ }
+
+ /// <summary>
+ /// Creates an instance of a <see cref="LinkedList{T}"/>.
+ /// </summary>
+ /// <param name="listType">The LinkedList&lt;T&gt; type.</param>
+ /// <param name="rndGen">A <see cref="Random"/> used to create the instance.</param>
+ /// <returns>An instance of the given list type.</returns>
+ public static object CreateInstanceOfLinkedListOfT(Type listType, Random rndGen)
+ {
+ Type type = listType.GetGenericArguments()[0];
+ double rndNumber = rndGen.NextDouble();
+ if (rndNumber < CreatorSettings.NullValueProbability)
+ {
+ return null; // 1% chance of null value
+ }
+
+ int size = (int)Math.Pow(CreatorSettings.MaxListLength, rndNumber); // this will create more small lists than large ones
+ size--;
+ object result = Activator.CreateInstance(listType);
+ MethodInfo addMethod = listType.GetMethod("AddLast", new Type[] { type });
+ for (int i = 0; i < size; i++)
+ {
+ addMethod.Invoke(result, new object[] { CreateInstanceOf(type, rndGen) });
+ }
+
+ return result;
+ }
+
+ /// <summary>
+ /// Creates an instance of a <see cref="IEnumerable{T}"/>.
+ /// </summary>
+ /// <param name="enumerableOfTType">The IEnumerable&lt;T&gt; type.</param>
+ /// <param name="enumeredType">The type to be enumerated.</param>
+ /// <param name="rndGen">A <see cref="Random"/> used to create the instance.</param>
+ /// <returns>An instance of the given enumerable type.</returns>
+ public static object CreateInstanceOfIEnumerableOfT(Type enumerableOfTType, Type enumeredType, Random rndGen)
+ {
+ double rndNumber = rndGen.NextDouble();
+ if (!enumerableOfTType.IsValueType && rndNumber < CreatorSettings.NullValueProbability)
+ {
+ return null; // 1% chance of null value
+ }
+
+ int size = (int)Math.Pow(CreatorSettings.MaxListLength, rndNumber); // this will create more small lists than large ones
+ size--;
+ object result = Activator.CreateInstance(enumerableOfTType);
+ MethodInfo addMethod = enumerableOfTType.GetMethod("Add", new Type[] { enumeredType });
+ if (addMethod == null)
+ {
+ throw new ArgumentException("Cannot create an instance of an IEnumerable<T> type which does not have a public Add method");
+ }
+
+ for (int i = 0; i < size; i++)
+ {
+ addMethod.Invoke(result, new object[] { CreateInstanceOf(enumeredType, rndGen) });
+ }
+
+ return result;
+ }
+
+ /// <summary>
+ /// Creates an instance of a <see cref="Nullable{T}"/>.
+ /// </summary>
+ /// <param name="nullableOfTType">The Nullable&lt;T&gt; type.</param>
+ /// <param name="rndGen">A <see cref="Random"/> used to create the instance.</param>
+ /// <returns>An instance of the given nullable type.</returns>
+ public static object CreateInstanceOfNullableOfT(Type nullableOfTType, Random rndGen)
+ {
+ if (rndGen.Next(5) == 0)
+ {
+ return null;
+ }
+
+ Type type = nullableOfTType.GetGenericArguments()[0];
+ return CreateInstanceOf(type, rndGen);
+ }
+
+ /// <summary>
+ /// Creates an instance of an enum type..
+ /// </summary>
+ /// <param name="enumType">The enum type.</param>
+ /// <param name="rndGen">A <see cref="Random"/> used to create the instance.</param>
+ /// <returns>An instance of the given enum type.</returns>
+ public static object CreateInstanceOfEnum(Type enumType, Random rndGen)
+ {
+ bool hasFlags = enumType.GetCustomAttributes(typeof(FlagsAttribute), true).Length > 0;
+ Array possibleValues = Enum.GetValues(enumType);
+ if (possibleValues.Length == 0)
+ {
+ return 0;
+ }
+
+ if (!hasFlags)
+ {
+ return possibleValues.GetValue(rndGen.Next(possibleValues.Length));
+ }
+ else
+ {
+ Type underlyingType = Enum.GetUnderlyingType(enumType);
+ string strResult;
+ if (underlyingType.FullName == typeof(ulong).FullName)
+ {
+ ulong result = 0;
+ if (rndGen.Next(10) > 0)
+ {
+ // 10% chance of value zero
+ foreach (object value in possibleValues)
+ {
+ if (rndGen.Next(2) == 0)
+ {
+ result |= ((IConvertible)value).ToUInt64(null);
+ }
+ }
+ }
+
+ strResult = result.ToString();
+ }
+ else
+ {
+ long result = 0;
+ if (rndGen.Next(10) > 0)
+ {
+ // 10% chance of value zero
+ foreach (object value in possibleValues)
+ {
+ if (rndGen.Next(2) == 0)
+ {
+ result |= ((IConvertible)value).ToInt64(null);
+ }
+ }
+ }
+
+ strResult = result.ToString();
+ }
+
+ return Enum.Parse(enumType, strResult, true);
+ }
+ }
+
+ /// <summary>
+ /// Creates an instance of a <see cref="Dictionary{K,V}"/>.
+ /// </summary>
+ /// <param name="dictionaryType">The Dictionary&lt;K,V&gt; type.</param>
+ /// <param name="rndGen">A <see cref="Random"/> used to create the instance.</param>
+ /// <returns>An instance of the given dictionary type.</returns>
+ public static object CreateInstanceOfDictionaryOfKAndV(Type dictionaryType, Random rndGen)
+ {
+ Type[] genericArgs = dictionaryType.GetGenericArguments();
+ Type typeK = genericArgs[0];
+ Type typeV = genericArgs[1];
+ double rndNumber = rndGen.NextDouble();
+ if (rndNumber < CreatorSettings.NullValueProbability)
+ {
+ return null; // 1% chance of null value
+ }
+
+ int size = (int)Math.Pow(CreatorSettings.MaxListLength, rndNumber); // this will create more small dictionaries than large ones
+ size--;
+ object result = Activator.CreateInstance(dictionaryType);
+ MethodInfo addMethod = dictionaryType.GetMethod("Add");
+ MethodInfo containsKeyMethod = dictionaryType.GetMethod("ContainsKey");
+ for (int i = 0; i < size; i++)
+ {
+ object newKey;
+ do
+ {
+ newKey = CreateInstanceOf(typeK, rndGen);
+ }
+ while (newKey == null);
+
+ bool containsKey = (bool)containsKeyMethod.Invoke(result, new object[] { newKey });
+ if (!containsKey)
+ {
+ object newValue = CreateInstanceOf(typeV, rndGen);
+ addMethod.Invoke(result, new object[] { newKey, newValue });
+ }
+ }
+
+ return result;
+ }
+
+ /// <summary>
+ /// Creates an instance of the given type.
+ /// </summary>
+ /// <typeparam name="T">The type to create an instance from.</typeparam>
+ /// <param name="rndGen">A random generator used to populate the instance.</param>
+ /// <returns>An instance of the given type.</returns>
+ public static T CreateInstanceOf<T>(Random rndGen)
+ {
+ return (T)InstanceCreator.CreateInstanceOf(typeof(T), rndGen);
+ }
+
+ /// <summary>
+ /// Creates an instance of the given type.
+ /// </summary>
+ /// <param name="type">The type to create an instance from.</param>
+ /// <param name="rndGen">A random generator used to populate the instance.</param>
+ /// <param name="allowNulls">A flag indicating whether null values can be returned by this method.</param>
+ /// <returns>An instance of the given type.</returns>
+ public static object CreateInstanceOf(Type type, Random rndGen, bool allowNulls)
+ {
+ double currentNullProbability = CreatorSettings.NullValueProbability;
+ if (!allowNulls)
+ {
+ CreatorSettings.NullValueProbability = 0;
+ }
+
+ object result = CreateInstanceOf(type, rndGen);
+ if (!allowNulls)
+ {
+ CreatorSettings.NullValueProbability = currentNullProbability;
+ }
+
+ return result;
+ }
+
+ /// <summary>
+ /// Creates an instance of the given type.
+ /// </summary>
+ /// <param name="type">The type to create an instance from.</param>
+ /// <param name="rndGen">A random generator used to populate the instance.</param>
+ /// <returns>An instance of the given type.</returns>
+ public static object CreateInstanceOf(Type type, Random rndGen)
+ {
+ if (CreatorSettings.CreatorSurrogate != null)
+ {
+ if (CreatorSettings.CreatorSurrogate.CanCreateInstanceOf(type))
+ {
+ return CreatorSettings.CreatorSurrogate.CreateInstanceOf(type, rndGen);
+ }
+ }
+
+ ConstructorInfo randomConstructor = type.GetConstructor(new Type[] { typeof(Random) });
+ if (randomConstructor != null)
+ {
+ // it's possible that a class with a Type.GetConstructor will return a constructor
+ // which takes a System.Object; we only want to use if it really takes a Random argument.
+ ParameterInfo[] ctorParameters = randomConstructor.GetParameters();
+ if (ctorParameters.Length == 1 && ctorParameters[0].ParameterType == typeof(Random))
+ {
+ return randomConstructor.Invoke(new object[] { rndGen });
+ }
+ }
+
+ // Allow for a static factory method (called 'CreateInstance' to allow for inheritance scenarios
+ MethodInfo randomFactoryMethod = type.GetMethod("CreateInstance", BindingFlags.Public | BindingFlags.Static, null, new Type[] { typeof(Random) }, null);
+ if (randomFactoryMethod != null)
+ {
+ ParameterInfo[] methodParameters = randomFactoryMethod.GetParameters();
+ if (methodParameters.Length == 1 && methodParameters[0].ParameterType == typeof(Random))
+ {
+ return randomFactoryMethod.Invoke(null, new object[] { rndGen });
+ }
+ }
+
+ object result = null;
+ Type genericElementType = null;
+ if (CreatorSettings.AvoidStackOverflowDueToTypeCycles)
+ {
+ if (typesInCreationStack.Contains(type))
+ {
+ if (type.IsValueType)
+ {
+ return Activator.CreateInstance(type);
+ }
+ else
+ {
+ return null;
+ }
+ }
+
+ typesInCreationStack.Push(type);
+ }
+
+ if (PrimitiveCreator.CanCreateInstanceOf(type))
+ {
+ result = PrimitiveCreator.CreatePrimitiveInstance(type, rndGen);
+ }
+ else if (type.IsEnum)
+ {
+ result = CreateInstanceOfEnum(type, rndGen);
+ }
+ else if (type.IsArray)
+ {
+ result = CreateInstanceOfArray(type, rndGen);
+ }
+ else if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>))
+ {
+ result = CreateInstanceOfNullableOfT(type, rndGen);
+ }
+ else if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(List<>))
+ {
+ result = CreateInstanceOfListOfT(type, rndGen);
+ }
+ else if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(LinkedList<>))
+ {
+ result = CreateInstanceOfLinkedListOfT(type, rndGen);
+ }
+ else if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Dictionary<,>))
+ {
+ result = CreateInstanceOfDictionaryOfKAndV(type, rndGen);
+ }
+ else if (ContainsAttribute(type, typeof(DataContractAttribute)))
+ {
+ result = ClassInstanceCreator.DataContractCreator.CreateInstanceOf(type, rndGen);
+ }
+ else if (IsIEnumerableOfT(type, out genericElementType))
+ {
+ result = CreateInstanceOfIEnumerableOfT(type, genericElementType, rndGen);
+ }
+ else if (type.IsPublic || type.IsNestedPublic)
+ {
+ result = ClassInstanceCreator.POCOCreator.CreateInstanceOf(type, rndGen);
+ }
+ else
+ {
+ result = Activator.CreateInstance(type);
+ }
+
+ if (CreatorSettings.AvoidStackOverflowDueToTypeCycles)
+ {
+ typesInCreationStack.Pop();
+ }
+
+ return result;
+ }
+
+ internal static bool ContainsAttribute(MemberInfo member, Type attributeType)
+ {
+ object[] attributes = member.GetCustomAttributes(attributeType, false);
+ return attributes != null && attributes.Length > 0;
+ }
+
+ static bool IsIEnumerableOfT(Type type, out Type genericArgumentType)
+ {
+ genericArgumentType = null;
+ foreach (Type interfaceType in type.GetInterfaces())
+ {
+ if (interfaceType.IsGenericType && interfaceType.GetGenericTypeDefinition() == typeof(IEnumerable<>))
+ {
+ genericArgumentType = interfaceType.GetGenericArguments()[0];
+ return true;
+ }
+ }
+
+ return false;
+ }
+ }
+
+ /// <summary>
+ /// Helper class used to create test instances.
+ /// </summary>
+ public class ClassInstanceCreator
+ {
+ static ClassInstanceCreator dataContractCreatorWithNonPublicMembers;
+ static ClassInstanceCreator dataContractCreator;
+ static ClassInstanceCreator pocoCreator;
+
+ bool includeNonPublicMembers;
+ GetMemberNameDelegate getMemberNameDelegate;
+ ShouldBeIncludedDelegate shouldBeIncludedDelegate;
+
+ private ClassInstanceCreator(GetMemberNameDelegate getMemberNameDelegate, ShouldBeIncludedDelegate shouldBeIncludedDelegate, bool includeNonPublicMembers)
+ : this(getMemberNameDelegate, shouldBeIncludedDelegate)
+ {
+ this.includeNonPublicMembers = includeNonPublicMembers;
+ }
+
+ private ClassInstanceCreator(GetMemberNameDelegate getMemberNameDelegate, ShouldBeIncludedDelegate shouldBeIncludedDelegate)
+ {
+ this.getMemberNameDelegate = getMemberNameDelegate;
+ this.shouldBeIncludedDelegate = shouldBeIncludedDelegate;
+ }
+
+ delegate string GetMemberNameDelegate(MemberInfo member);
+
+ delegate bool ShouldBeIncludedDelegate(MemberInfo member);
+
+ /// <summary>
+ /// Gets an instance of a creator which knows how to create instance of types
+ /// decorated with the <see cref="DataContractAttribute"/>.
+ /// </summary>
+ public static ClassInstanceCreator DataContractCreator
+ {
+ get
+ {
+ if (dataContractCreator == null)
+ {
+ dataContractCreator = new ClassInstanceCreator(GetDataMemberName, IncludeDataMembersOnly);
+ }
+
+ return dataContractCreator;
+ }
+ }
+
+ /// <summary>
+ /// Gets an instance of a creator which knows how to create instance of types
+ /// which only sets members decorated with the <see cref="DataMemberAttribute"/>.
+ /// </summary>
+ public static ClassInstanceCreator DataContractCreatorWithNonPublicMembers
+ {
+ get
+ {
+ if (dataContractCreatorWithNonPublicMembers == null)
+ {
+ dataContractCreatorWithNonPublicMembers = new ClassInstanceCreator(GetDataMemberName, IncludeDataMembersOnly, true);
+ }
+
+ return dataContractCreatorWithNonPublicMembers;
+ }
+ }
+
+ /// <summary>
+ /// Gets an instance of a creator which knows how to create instances of POCO
+ /// types (public types, not decorated with the <see cref="DataContractAttribute"/> and
+ /// which have a parameterless public constructor).
+ /// </summary>
+ public static ClassInstanceCreator POCOCreator
+ {
+ get
+ {
+ if (pocoCreator == null)
+ {
+ pocoCreator = new ClassInstanceCreator(
+ delegate(MemberInfo member) { return member.Name; },
+ DoNotIncludeExcludedMembers);
+ }
+
+ return pocoCreator;
+ }
+ }
+
+ /// <summary>
+ /// Creates an instance of the given type.
+ /// </summary>
+ /// <param name="type">The type to create an instance from.</param>
+ /// <param name="rndGen">A random generator used to populate the instance.</param>
+ /// <returns>An instance of the given type.</returns>
+ public object CreateInstanceOf(Type type, Random rndGen)
+ {
+ object result = null;
+ if (rndGen.NextDouble() < CreatorSettings.NullValueProbability && !type.IsValueType)
+ {
+ // 1% chance of null object, if it is not a struct
+ return null;
+ }
+
+ ConstructorInfo randomConstructor = type.GetConstructor(new Type[] { typeof(Random) });
+ if (randomConstructor != null && randomConstructor.GetParameters()[0].ParameterType == typeof(Random))
+ {
+ result = randomConstructor.Invoke(new object[] { rndGen });
+ }
+ else
+ {
+ ConstructorInfo defaultConstructor = type.GetConstructor(new Type[0]);
+ if (defaultConstructor != null || type.IsValueType)
+ {
+ result = Activator.CreateInstance(type);
+ this.SetFieldsAndProperties(type, result, rndGen);
+ }
+ else
+ {
+ throw new ArgumentException("Don't know how to create an instance of " + type.FullName);
+ }
+ }
+
+ return result;
+ }
+
+ private static string GetDataMemberName(MemberInfo member)
+ {
+ DataMemberAttribute[] dataMemberAttr = (DataMemberAttribute[])member.GetCustomAttributes(typeof(DataMemberAttribute), false);
+ if (dataMemberAttr == null || dataMemberAttr.Length == 0 || dataMemberAttr[0].Name == null)
+ {
+ return member.Name;
+ }
+ else
+ {
+ return dataMemberAttr[0].Name;
+ }
+ }
+
+ private static bool ContainsAttribute(MemberInfo member, string attributeName)
+ {
+ object[] customAttributes = member.GetCustomAttributes(false);
+ foreach (object attribute in customAttributes)
+ {
+ if (attribute != null && attribute.GetType().Name == attributeName)
+ {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ private static bool IncludeDataMembersOnly(MemberInfo member)
+ {
+ return ContainsAttribute(member, "DataMemberAttribute");
+ }
+
+ private static bool DoNotIncludeExcludedMembers(MemberInfo member)
+ {
+ return !ContainsAttribute(member, "IgnoreDataMemberAttribute");
+ }
+
+ int CompareDataMembers(MemberInfo member1, MemberInfo member2)
+ {
+ return this.getMemberNameDelegate(member1).CompareTo(this.getMemberNameDelegate(member2));
+ }
+
+ private void SetFieldsAndProperties(Type type, object obj, Random rndGen)
+ {
+ List<MemberInfo> members = new List<MemberInfo>();
+ BindingFlags bindingFlags = BindingFlags.Instance | BindingFlags.Public;
+ if (this.includeNonPublicMembers)
+ {
+ bindingFlags |= BindingFlags.NonPublic;
+ }
+
+ FieldInfo[] fields = type.GetFields(bindingFlags);
+ PropertyInfo[] properties = type.GetProperties(bindingFlags);
+
+ foreach (FieldInfo field in fields)
+ {
+ if (this.shouldBeIncludedDelegate(field))
+ {
+ members.Add(field);
+ }
+ }
+
+ foreach (PropertyInfo prop in properties)
+ {
+ if (this.shouldBeIncludedDelegate(prop))
+ {
+ members.Add(prop);
+ }
+ }
+
+ members.Sort(new Comparison<MemberInfo>(this.CompareDataMembers));
+
+ foreach (MemberInfo member in members)
+ {
+ if (member is FieldInfo)
+ {
+ object fieldValue = InstanceCreator.CreateInstanceOf(((FieldInfo)member).FieldType, rndGen);
+ ((FieldInfo)member).SetValue(obj, fieldValue);
+ }
+ else
+ {
+ PropertyInfo propInfo = (PropertyInfo)member;
+ if (propInfo.CanWrite)
+ {
+ object propertyValue = InstanceCreator.CreateInstanceOf(propInfo.PropertyType, rndGen);
+ propInfo.SetValue(obj, propertyValue, null);
+ }
+ else
+ {
+ if (!this.TrySettingMembersOfGetOnlyCollection(rndGen, propInfo, obj))
+ {
+ throw new ArgumentException("Cannot set property " + propInfo.Name + " of type " + type.FullName);
+ }
+ }
+ }
+ }
+ }
+
+ private bool TrySettingMembersOfGetOnlyCollection(Random rndGen, PropertyInfo propInfo, object obj)
+ {
+ Type propType = propInfo.PropertyType;
+ object propValue = propInfo.GetValue(obj, null);
+ if (propValue == null)
+ {
+ // need something to set the value to
+ return false;
+ }
+
+ if (propType.IsArray)
+ {
+ Array propArray = (Array)propValue;
+ Type arrayType = propType.GetElementType();
+ for (int i = 0; i < propArray.Length; i++)
+ {
+ object elementValue = InstanceCreator.CreateInstanceOf(arrayType, rndGen);
+ propArray.SetValue(elementValue, i);
+ }
+
+ return true;
+ }
+ else if (propType.IsGenericType && propType.GetGenericTypeDefinition() == typeof(List<>))
+ {
+ Type listType = propType.GetGenericArguments()[0];
+ MethodInfo addMethod = propType.GetMethod("Add", new Type[] { listType });
+ double rndNumber = rndGen.NextDouble();
+ int size = (int)Math.Pow(CreatorSettings.MaxListLength, rndNumber); // this will create more small lists than large ones
+ size--;
+ for (int i = 0; i < size; i++)
+ {
+ object listValue = InstanceCreator.CreateInstanceOf(listType, rndGen);
+ addMethod.Invoke(propValue, new object[] { listValue });
+ }
+
+ return true;
+ }
+
+ return false;
+ }
+ }
+
+ /// <summary>
+ /// Enables tests to create specific instances of certain types.
+ /// </summary>
+ public abstract class InstanceCreatorSurrogate
+ {
+ /// <summary>
+ /// Checks whether this surrogate can create instances of a given type.
+ /// </summary>
+ /// <param name="type">The type which needs to be created.</param>
+ /// <returns>A true value if this surrogate can create the given type; a
+ /// false value otherwise.</returns>
+ public abstract bool CanCreateInstanceOf(Type type);
+
+ /// <summary>
+ /// Creates an instance of the given type.
+ /// </summary>
+ /// <param name="type">The type to create an instance for.</param>
+ /// <param name="rndGen">A Random generator to assist in creating the instance.</param>
+ /// <returns>An instance of the given type.</returns>
+ public abstract object CreateInstanceOf(Type type, Random rndGen);
+ }
+}
diff --git a/test/System.Json.Test.Integration/Common/JsonValueCreatorSurrogate.cs b/test/System.Json.Test.Integration/Common/JsonValueCreatorSurrogate.cs
new file mode 100644
index 00000000..83373920
--- /dev/null
+++ b/test/System.Json.Test.Integration/Common/JsonValueCreatorSurrogate.cs
@@ -0,0 +1,149 @@
+namespace System.Json
+{
+ public class JsonValueCreatorSurrogate : InstanceCreatorSurrogate
+ {
+ private const int MaxDepth = 4;
+
+ public override bool CanCreateInstanceOf(Type type)
+ {
+ return (type == typeof(JsonValue) || type == typeof(JsonArray) || type == typeof(JsonObject) || type == typeof(JsonPrimitive));
+ }
+
+ public override object CreateInstanceOf(Type type, Random rndGen)
+ {
+ if (!this.CanCreateInstanceOf(type))
+ {
+ return null;
+ }
+
+ if (type == typeof(JsonValue))
+ {
+ return CreateJsonValue(rndGen, 0);
+ }
+ else if (type == typeof(JsonArray))
+ {
+ return CreateJsonArray(rndGen, 0);
+ }
+ else if (type == typeof(JsonObject))
+ {
+ return CreateJsonObject(rndGen, 0);
+ }
+ else
+ {
+ return CreateJsonPrimitive(rndGen);
+ }
+ }
+
+ private static JsonValue CreateJsonValue(Random rndGen, int depth)
+ {
+ if (rndGen.Next() < CreatorSettings.NullValueProbability)
+ {
+ return null;
+ }
+
+ if (depth < MaxDepth)
+ {
+ switch (rndGen.Next(10))
+ {
+ case 0:
+ case 1:
+ case 2:
+ // 30% chance to create an array
+ return CreateJsonArray(rndGen, depth);
+ case 3:
+ case 4:
+ case 5:
+ // 30% chance to create an object
+ return CreateJsonObject(rndGen, depth);
+ default:
+ // 40% chance to create a primitive
+ break;
+ }
+ }
+
+ return CreateJsonPrimitive(rndGen);
+ }
+
+ static JsonValue CreateJsonPrimitive(Random rndGen)
+ {
+ switch (rndGen.Next(17))
+ {
+ case 0:
+ return PrimitiveCreator.CreateInstanceOfChar(rndGen);
+ case 1:
+ return PrimitiveCreator.CreateInstanceOfByte(rndGen);
+ case 2:
+ return PrimitiveCreator.CreateInstanceOfSByte(rndGen);
+ case 3:
+ return PrimitiveCreator.CreateInstanceOfInt16(rndGen);
+ case 4:
+ return PrimitiveCreator.CreateInstanceOfUInt16(rndGen);
+ case 5:
+ return PrimitiveCreator.CreateInstanceOfInt32(rndGen);
+ case 6:
+ return PrimitiveCreator.CreateInstanceOfUInt32(rndGen);
+ case 7:
+ return PrimitiveCreator.CreateInstanceOfInt64(rndGen);
+ case 8:
+ return PrimitiveCreator.CreateInstanceOfUInt64(rndGen);
+ case 9:
+ return PrimitiveCreator.CreateInstanceOfDecimal(rndGen);
+ case 10:
+ return PrimitiveCreator.CreateInstanceOfDouble(rndGen);
+ case 11:
+ return PrimitiveCreator.CreateInstanceOfSingle(rndGen);
+ case 12:
+ return PrimitiveCreator.CreateInstanceOfDateTime(rndGen);
+ case 13:
+ return PrimitiveCreator.CreateInstanceOfDateTimeOffset(rndGen);
+ case 14:
+ case 15:
+ // TODO: 199532 fix uri comparer
+ return PrimitiveCreator.CreateInstanceOfString(rndGen);
+ default:
+ return PrimitiveCreator.CreateInstanceOfBoolean(rndGen);
+ }
+ }
+
+ static JsonArray CreateJsonArray(Random rndGen, int depth)
+ {
+ int size = rndGen.Next(CreatorSettings.MaxArrayLength);
+ if (CreatorSettings.NullValueProbability == 0 && size == 0)
+ {
+ size++;
+ }
+
+ JsonArray result = new JsonArray();
+ for (int i = 0; i < size; i++)
+ {
+ result.Add(CreateJsonValue(rndGen, depth + 1));
+ }
+
+ return result;
+ }
+
+ static JsonObject CreateJsonObject(Random rndGen, int depth)
+ {
+ const string keyChars = "abcdefghijklmnopqrstuvwxyz0123456789";
+ int size = rndGen.Next(CreatorSettings.MaxArrayLength);
+ if (CreatorSettings.NullValueProbability == 0 && size == 0)
+ {
+ size++;
+ }
+
+ JsonObject result = new JsonObject();
+ for (int i = 0; i < size; i++)
+ {
+ string key;
+ do
+ {
+ key = PrimitiveCreator.CreateInstanceOfString(rndGen, 10, keyChars);
+ } while (result.ContainsKey(key));
+
+ result.Add(key, CreateJsonValue(rndGen, depth + 1));
+ }
+
+ return result;
+ }
+ }
+} \ No newline at end of file
diff --git a/test/System.Json.Test.Integration/Common/Log.cs b/test/System.Json.Test.Integration/Common/Log.cs
new file mode 100644
index 00000000..1504101d
--- /dev/null
+++ b/test/System.Json.Test.Integration/Common/Log.cs
@@ -0,0 +1,10 @@
+namespace System.Json
+{
+ internal static class Log
+ {
+ public static void Info(string text, params object[] args)
+ {
+ Console.WriteLine(text, args);
+ }
+ }
+}
diff --git a/test/System.Json.Test.Integration/Common/TypeLibrary.cs b/test/System.Json.Test.Integration/Common/TypeLibrary.cs
new file mode 100644
index 00000000..01c90afa
--- /dev/null
+++ b/test/System.Json.Test.Integration/Common/TypeLibrary.cs
@@ -0,0 +1,1943 @@
+using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
+using System.Globalization;
+using System.Runtime.Serialization;
+using System.Text;
+
+namespace System.Json
+{
+ /// <summary>
+ /// Test type.
+ /// </summary>
+ [DataContract]
+ public enum EnumType_17
+ {
+ /// <summary>
+ /// Test member.
+ /// </summary>
+ [EnumMember]
+ member_0,
+
+ /// <summary>
+ /// Test member.
+ /// </summary>
+ [EnumMember]
+ member_1 = 2,
+
+ /// <summary>
+ /// Test member.
+ /// </summary>
+ [EnumMember]
+ member_2,
+
+ /// <summary>
+ /// Test member.
+ /// </summary>
+ [EnumMember]
+ member_3,
+
+ /// <summary>
+ /// Test member.
+ /// </summary>
+ [EnumMember]
+ member_4,
+
+ /// <summary>
+ /// Test member.
+ /// </summary>
+ [EnumMember]
+ member_5,
+
+ /// <summary>
+ /// Test member.
+ /// </summary>
+ [EnumMember]
+ member_6,
+
+ /// <summary>
+ /// Test member.
+ /// </summary>
+ [EnumMember]
+ member_7,
+
+ /// <summary>
+ /// Test member.
+ /// </summary>
+ [EnumMember]
+ member_8 = 9,
+
+ /// <summary>
+ /// Test member.
+ /// </summary>
+ [EnumMember]
+ member_9,
+
+ /// <summary>
+ /// Test member.
+ /// </summary>
+ [EnumMember]
+ member_10,
+
+ /// <summary>
+ /// Test member.
+ /// </summary>
+ [EnumMember]
+ member_11 = 12,
+
+ /// <summary>
+ /// Test member.
+ /// </summary>
+ [EnumMember]
+ member_12 = 13,
+
+ /// <summary>
+ /// Test member.
+ /// </summary>
+ [EnumMember]
+ member_13,
+
+ /// <summary>
+ /// Test member.
+ /// </summary>
+ [EnumMember]
+ member_14,
+
+ /// <summary>
+ /// Test member.
+ /// </summary>
+ [EnumMember]
+ member_15,
+
+ /// <summary>
+ /// Test member.
+ /// </summary>
+ [EnumMember]
+ member_16,
+
+ /// <summary>
+ /// Test member.
+ /// </summary>
+ [EnumMember]
+ member_17,
+ }
+
+ /// <summary>
+ /// Test type.
+ /// </summary>
+ [DataContract]
+ public enum EnumType_35
+ {
+ /// <summary>
+ /// Test member.
+ /// </summary>
+ [EnumMember]
+ member_0,
+
+ /// <summary>
+ /// Test member.
+ /// </summary>
+ [EnumMember]
+ member_1,
+
+ /// <summary>
+ /// Test member.
+ /// </summary>
+ [EnumMember]
+ member_2,
+
+ /// <summary>
+ /// Test member.
+ /// </summary>
+ [EnumMember]
+ member_3,
+
+ /// <summary>
+ /// Test member.
+ /// </summary>
+ [EnumMember]
+ member_4,
+
+ /// <summary>
+ /// Test member.
+ /// </summary>
+ [EnumMember]
+ member_5,
+
+ /// <summary>
+ /// Test member.
+ /// </summary>
+ [EnumMember]
+ member_6,
+
+ /// <summary>
+ /// Test member.
+ /// </summary>
+ [EnumMember]
+ member_7,
+
+ /// <summary>
+ /// Test member.
+ /// </summary>
+ [EnumMember]
+ member_8,
+ }
+
+ /// <summary>
+ /// Test type.
+ /// </summary>
+ public interface IEmptyInterface
+ {
+ }
+
+ /// <summary>
+ /// Test type.
+ /// </summary>
+ [DataContract]
+ public struct StructInt16
+ {
+ /// <summary>
+ /// Test member.
+ /// </summary>
+ [DataMember]
+ public short Int16Member;
+ }
+
+ /// <summary>
+ /// Test type.
+ /// </summary>
+ [DataContract]
+ public struct StructGuid
+ {
+ /// <summary>
+ /// Test member.
+ /// </summary>
+ [DataMember]
+ public Guid GuidMember;
+ }
+
+ /// <summary>
+ /// Test type.
+ /// </summary>
+ [DataContract]
+ public class DCType_1
+ {
+ /// <summary>
+ /// Test member.
+ /// </summary>
+ [DataMember]
+ public byte Member0 { get; set; }
+
+ /// <summary>
+ /// Compares this instance with the given object.
+ /// </summary>
+ /// <param name="obj">The object to compare.</param>
+ /// <returns><code>true</code> if the given instance is equal to this one; <code>false</code> otherwise.</returns>
+ public override bool Equals(object obj)
+ {
+ DCType_1 other = obj as DCType_1;
+ return (other != null) && this.Member0.Equals(other.Member0);
+ }
+
+ /// <summary>
+ /// Returns a hash code for this instance.
+ /// </summary>
+ /// <returns>A hash code for this instance.</returns>
+ public override int GetHashCode()
+ {
+ int result = this.Member0.GetHashCode();
+ return result;
+ }
+
+ /// <summary>
+ /// Returns a debug representation for this instance.
+ /// </summary>
+ /// <returns>A debug representation for this instance.</returns>
+ public override string ToString()
+ {
+ return String.Format(CultureInfo.InvariantCulture, "DCType_1<Member0={0:X2}>", (int)this.Member0);
+ }
+ }
+
+ /// <summary>
+ /// Test type.
+ /// </summary>
+ [CLSCompliant(false)]
+ [DataContract]
+ public class DCType_3
+ {
+ /// <summary>
+ /// Test member.
+ /// </summary>
+ [DataMember]
+ public ulong Member2 { get; set; }
+
+ /// <summary>
+ /// Compares this instance with the given object.
+ /// </summary>
+ /// <param name="obj">The object to compare.</param>
+ /// <returns><code>true</code> if the given instance is equal to this one; <code>false</code> otherwise.</returns>
+ public override bool Equals(object obj)
+ {
+ DCType_3 other = obj as DCType_3;
+ return (other != null) && this.Member2.Equals(other.Member2);
+ }
+
+ /// <summary>
+ /// Returns a hash code for this instance.
+ /// </summary>
+ /// <returns>A hash code for this instance.</returns>
+ public override int GetHashCode()
+ {
+ int result = this.Member2.GetHashCode();
+ return result;
+ }
+ }
+
+ /// <summary>
+ /// Test type.
+ /// </summary>
+ [Serializable]
+ public class SerType_4
+ {
+ /// <summary>
+ /// Test member.
+ /// </summary>
+ [SuppressMessage("Microsoft.StyleCop.CSharp.MaintainabilityRules", "SA1401:FieldsMustBePrivate",
+ Justification = "Testing serialization of [Serializable] types, which needs public fields.")]
+ public char Member0;
+
+ /// <summary>
+ /// Test member.
+ /// </summary>
+ [SuppressMessage("Microsoft.StyleCop.CSharp.MaintainabilityRules", "SA1401:FieldsMustBePrivate",
+ Justification = "Testing serialization of [Serializable] types, which needs public fields.")]
+ public short? Member1;
+
+ /// <summary>
+ /// Compares this instance with the given object.
+ /// </summary>
+ /// <param name="obj">The object to compare.</param>
+ /// <returns><code>true</code> if the given instance is equal to this one; <code>false</code> otherwise.</returns>
+ public override bool Equals(object obj)
+ {
+ SerType_4 other = obj as SerType_4;
+ return (other != null) && this.Member0.Equals(other.Member0) && Util.CompareNullable<short>(this.Member1, other.Member1);
+ }
+
+ /// <summary>
+ /// Returns a hash code for this instance.
+ /// </summary>
+ /// <returns>A hash code for this instance.</returns>
+ public override int GetHashCode()
+ {
+ int result = 0;
+ result ^= this.Member0.GetHashCode();
+ result ^= (this.Member1 == null) ? 0 : this.Member1.GetHashCode();
+ return result;
+ }
+
+ /// <summary>
+ /// Returns a debug representation for this instance.
+ /// </summary>
+ /// <returns>A debug representation for this instance.</returns>
+ public override string ToString()
+ {
+ return String.Format(CultureInfo.InvariantCulture, "SerType_4<Member0=(char){0},Member1={1}>", (int)this.Member0, Util.EscapeString(this.Member1));
+ }
+ }
+
+ /// <summary>
+ /// Test type.
+ /// </summary>
+ [CLSCompliant(false)]
+ [Serializable]
+ public class SerType_5
+ {
+ /// <summary>
+ /// Test member.
+ /// </summary>
+ [SuppressMessage("Microsoft.StyleCop.CSharp.MaintainabilityRules", "SA1401:FieldsMustBePrivate",
+ Justification = "Testing serialization of [Serializable] types, which needs public fields.")]
+ public char Member0;
+
+ /// <summary>
+ /// Test member.
+ /// </summary>
+ [SuppressMessage("Microsoft.StyleCop.CSharp.MaintainabilityRules", "SA1401:FieldsMustBePrivate",
+ Justification = "Testing serialization of [Serializable] types, which needs public fields.")]
+ public byte? Member1;
+
+ /// <summary>
+ /// Test member.
+ /// </summary>
+ [SuppressMessage("Microsoft.StyleCop.CSharp.MaintainabilityRules", "SA1401:FieldsMustBePrivate",
+ Justification = "Testing serialization of [Serializable] types, which needs public fields.")]
+ public char Member2;
+
+ /// <summary>
+ /// Test member.
+ /// </summary>
+ [SuppressMessage("Microsoft.StyleCop.CSharp.MaintainabilityRules", "SA1401:FieldsMustBePrivate",
+ Justification = "Testing serialization of [Serializable] types, which needs public fields.")]
+ public bool Member3;
+
+ /// <summary>
+ /// Test member.
+ /// </summary>
+ [SuppressMessage("Microsoft.StyleCop.CSharp.MaintainabilityRules", "SA1401:FieldsMustBePrivate",
+ Justification = "Testing serialization of [Serializable] types, which needs public fields.")]
+ public sbyte Member4;
+
+ /// <summary>
+ /// Compares this instance with the given object.
+ /// </summary>
+ /// <param name="obj">The object to compare.</param>
+ /// <returns><code>true</code> if the given instance is equal to this one; <code>false</code> otherwise.</returns>
+ public override bool Equals(object obj)
+ {
+ SerType_5 other = obj as SerType_5;
+ return (other != null) &&
+ this.Member0.Equals(other.Member0) &&
+ Util.CompareNullable<byte>(this.Member1, other.Member1) &&
+ this.Member2.Equals(other.Member2) &&
+ this.Member3.Equals(other.Member3) &&
+ this.Member4.Equals(other.Member4);
+ }
+
+ /// <summary>
+ /// Returns a hash code for this instance.
+ /// </summary>
+ /// <returns>A hash code for this instance.</returns>
+ public override int GetHashCode()
+ {
+ int result = 0;
+ result ^= this.Member0.GetHashCode();
+ result ^= (this.Member1 == null) ? 0 : this.Member1.GetHashCode();
+ result ^= this.Member2.GetHashCode();
+ result ^= this.Member3.GetHashCode();
+ result ^= this.Member4.GetHashCode();
+ return result;
+ }
+ }
+
+ /// <summary>
+ /// Test type.
+ /// </summary>
+ [CLSCompliant(false)]
+ [DataContract]
+ public class DCType_7
+ {
+ /// <summary>
+ /// Test member.
+ /// </summary>
+ [DataMember]
+ public long? Member1 { get; set; }
+
+ /// <summary>
+ /// Test member.
+ /// </summary>
+ [DataMember]
+ public sbyte Member2 { get; set; }
+
+ /// <summary>
+ /// Test member.
+ /// </summary>
+ [DataMember]
+ public byte[] Member3 { get; set; }
+
+ /// <summary>
+ /// Compares this instance with the given object.
+ /// </summary>
+ /// <param name="obj">The object to compare.</param>
+ /// <returns><code>true</code> if the given instance is equal to this one; <code>false</code> otherwise.</returns>
+ public override bool Equals(object obj)
+ {
+ DCType_7 other = obj as DCType_7;
+ return (other != null) &&
+ Util.CompareNullable<long>(this.Member1, other.Member1) &&
+ this.Member2.Equals(other.Member2) &&
+ Util.CompareArrays(this.Member3, other.Member3);
+ }
+
+ /// <summary>
+ /// Returns a hash code for this instance.
+ /// </summary>
+ /// <returns>A hash code for this instance.</returns>
+ public override int GetHashCode()
+ {
+ int result = 0;
+ result ^= (this.Member1 == null) ? 0 : this.Member1.GetHashCode();
+ result ^= this.Member2.GetHashCode();
+ result ^= Util.ComputeArrayHashCode(this.Member3);
+ return result;
+ }
+ }
+
+ /// <summary>
+ /// Test type.
+ /// </summary>
+ [CLSCompliant(false)]
+ [DataContract]
+ public class DCType_9
+ {
+ /// <summary>
+ /// Test member.
+ /// </summary>
+ [DataMember]
+ public sbyte? Member0 { get; set; }
+
+ /// <summary>
+ /// Test member.
+ /// </summary>
+ [DataMember]
+ public Guid Member1 { get; set; }
+
+ /// <summary>
+ /// Compares this instance with the given object.
+ /// </summary>
+ /// <param name="obj">The object to compare.</param>
+ /// <returns><code>true</code> if the given instance is equal to this one; <code>false</code> otherwise.</returns>
+ public override bool Equals(object obj)
+ {
+ DCType_9 other = obj as DCType_9;
+ return (other != null) &&
+ Util.CompareNullable<sbyte>(this.Member0, other.Member0) &&
+ this.Member1.Equals(other.Member1);
+ }
+
+ /// <summary>
+ /// Returns a hash code for this instance.
+ /// </summary>
+ /// <returns>A hash code for this instance.</returns>
+ public override int GetHashCode()
+ {
+ int result = 0;
+ result ^= (this.Member0 == null) ? 0 : this.Member0.GetHashCode();
+ result ^= this.Member1.GetHashCode();
+ return result;
+ }
+ }
+
+ /// <summary>
+ /// Test type.
+ /// </summary>
+ [Serializable]
+ public class SerType_11
+ {
+ /// <summary>
+ /// Test member.
+ /// </summary>
+ [SuppressMessage("Microsoft.StyleCop.CSharp.MaintainabilityRules", "SA1401:FieldsMustBePrivate",
+ Justification = "Testing serialization of [Serializable] types, which needs public fields.")]
+ public float Member0;
+
+ /// <summary>
+ /// Compares this instance with the given object.
+ /// </summary>
+ /// <param name="obj">The object to compare.</param>
+ /// <returns><code>true</code> if the given instance is equal to this one; <code>false</code> otherwise.</returns>
+ public override bool Equals(object obj)
+ {
+ SerType_11 other = obj as SerType_11;
+ return (other != null) && this.Member0.Equals(other.Member0);
+ }
+
+ /// <summary>
+ /// Returns a hash code for this instance.
+ /// </summary>
+ /// <returns>A hash code for this instance.</returns>
+ public override int GetHashCode()
+ {
+ int result = this.Member0.GetHashCode();
+ return result;
+ }
+ }
+
+ /// <summary>
+ /// Test type.
+ /// </summary>
+ [CLSCompliant(false)]
+ [DataContract]
+ public class DCType_15
+ {
+ /// <summary>
+ /// Test member.
+ /// </summary>
+ public byte[] Member0 { get; set; }
+
+ /// <summary>
+ /// Test member.
+ /// </summary>
+ public ushort Member1 { get; set; }
+
+ /// <summary>
+ /// Test member.
+ /// </summary>
+ [DataMember]
+ public Guid Member2 { get; set; }
+
+ /// <summary>
+ /// Test member.
+ /// </summary>
+ public DCType_1 Member3 { get; set; }
+
+ /// <summary>
+ /// Test member.
+ /// </summary>
+ [DataMember]
+ public DCType_7 Member5 { get; set; }
+
+ /// <summary>
+ /// Test member.
+ /// </summary>
+ [DataMember]
+ public int Member6 { get; set; }
+
+ /// <summary>
+ /// Test member.
+ /// </summary>
+ [DataMember]
+ public DCType_9 Member7 { get; set; }
+
+ /// <summary>
+ /// Compares this instance with the given object.
+ /// </summary>
+ /// <param name="obj">The object to compare.</param>
+ /// <returns><code>true</code> if the given instance is equal to this one; <code>false</code> otherwise.</returns>
+ public override bool Equals(object obj)
+ {
+ DCType_15 other = obj as DCType_15;
+ return (other != null) &&
+ this.Member2.Equals(other.Member2) &&
+ Util.CompareObjects<DCType_7>(this.Member5, other.Member5) &&
+ this.Member6.Equals(other.Member6) &&
+ Util.CompareObjects<DCType_9>(this.Member7, other.Member7);
+ }
+
+ /// <summary>
+ /// Returns a hash code for this instance.
+ /// </summary>
+ /// <returns>A hash code for this instance.</returns>
+ public override int GetHashCode()
+ {
+ int result = 0;
+ result ^= this.Member2.GetHashCode();
+ result ^= (this.Member5 == null) ? 0 : this.Member5.GetHashCode();
+ result ^= this.Member6.GetHashCode();
+ result ^= (this.Member7 == null) ? 0 : this.Member7.GetHashCode();
+ return result;
+ }
+ }
+
+ /// <summary>
+ /// Test type.
+ /// </summary>
+ [DataContract]
+ public class DCType_16
+ {
+ /// <summary>
+ /// Test member.
+ /// </summary>
+ [DataMember]
+ public decimal Member0 { get; set; }
+
+ /// <summary>
+ /// Test member.
+ /// </summary>
+ [DataMember]
+ public SerType_11 Member1 { get; set; }
+
+ /// <summary>
+ /// Compares this instance with the given object.
+ /// </summary>
+ /// <param name="obj">The object to compare.</param>
+ /// <returns><code>true</code> if the given instance is equal to this one; <code>false</code> otherwise.</returns>
+ public override bool Equals(object obj)
+ {
+ DCType_16 other = obj as DCType_16;
+ return (other != null) &&
+ this.Member0.Equals(other.Member0) &&
+ Util.CompareObjects<SerType_11>(this.Member1, other.Member1);
+ }
+
+ /// <summary>
+ /// Returns a hash code for this instance.
+ /// </summary>
+ /// <returns>A hash code for this instance.</returns>
+ public override int GetHashCode()
+ {
+ int result = 0;
+ result ^= this.Member0.GetHashCode();
+ result ^= (this.Member1 == null) ? 0 : this.Member1.GetHashCode();
+ return result;
+ }
+ }
+
+ /// <summary>
+ /// Test type.
+ /// </summary>
+ [CLSCompliant(false)]
+ [DataContract]
+ public class DCType_18
+ {
+ /// <summary>
+ /// Test member.
+ /// </summary>
+ [DataMember]
+ public SerType_5 Member0 { get; set; }
+
+ /// <summary>
+ /// Test member.
+ /// </summary>
+ [DataMember]
+ public short Member1 { get; set; }
+
+ /// <summary>
+ /// Compares this instance with the given object.
+ /// </summary>
+ /// <param name="obj">The object to compare.</param>
+ /// <returns><code>true</code> if the given instance is equal to this one; <code>false</code> otherwise.</returns>
+ public override bool Equals(object obj)
+ {
+ DCType_18 other = obj as DCType_18;
+ return (other != null) &&
+ Util.CompareObjects<SerType_5>(this.Member0, other.Member0) &&
+ this.Member1.Equals(other.Member1);
+ }
+
+ /// <summary>
+ /// Returns a hash code for this instance.
+ /// </summary>
+ /// <returns>A hash code for this instance.</returns>
+ public override int GetHashCode()
+ {
+ int result = 0;
+ result ^= (this.Member0 == null) ? 0 : this.Member0.GetHashCode();
+ result ^= this.Member1.GetHashCode();
+ return result;
+ }
+ }
+
+ /// <summary>
+ /// Test type.
+ /// </summary>
+ [CLSCompliant(false)]
+ [DataContract]
+ public class DCType_19
+ {
+ /// <summary>
+ /// Test member.
+ /// </summary>
+ [DataMember]
+ public uint? Member0 { get; set; }
+
+ /// <summary>
+ /// Test member.
+ /// </summary>
+ [DataMember]
+ public byte? Member1 { get; set; }
+
+ /// <summary>
+ /// Test member.
+ /// </summary>
+ [DataMember]
+ public long? Member2 { get; set; }
+
+ /// <summary>
+ /// Compares this instance with the given object.
+ /// </summary>
+ /// <param name="obj">The object to compare.</param>
+ /// <returns><code>true</code> if the given instance is equal to this one; <code>false</code> otherwise.</returns>
+ public override bool Equals(object obj)
+ {
+ DCType_19 other = obj as DCType_19;
+ return (other != null) &&
+ Util.CompareNullable<uint>(this.Member0, other.Member0) &&
+ Util.CompareNullable<byte>(this.Member1, other.Member1) &&
+ Util.CompareNullable<long>(this.Member2, other.Member2);
+ }
+
+ /// <summary>
+ /// Returns a hash code for this instance.
+ /// </summary>
+ /// <returns>A hash code for this instance.</returns>
+ public override int GetHashCode()
+ {
+ int result = 0;
+ result ^= (this.Member0 == null) ? 0 : this.Member0.GetHashCode();
+ result ^= (this.Member1 == null) ? 0 : this.Member1.GetHashCode();
+ result ^= (this.Member2 == null) ? 0 : this.Member2.GetHashCode();
+ return result;
+ }
+ }
+
+ /// <summary>
+ /// Test type.
+ /// </summary>
+ [CLSCompliant(false)]
+ [DataContract]
+ public class DCType_20
+ {
+ /// <summary>
+ /// Test member.
+ /// </summary>
+ [DataMember]
+ public DCType_9 Member0 { get; set; }
+
+ /// <summary>
+ /// Compares this instance with the given object.
+ /// </summary>
+ /// <param name="obj">The object to compare.</param>
+ /// <returns><code>true</code> if the given instance is equal to this one; <code>false</code> otherwise.</returns>
+ public override bool Equals(object obj)
+ {
+ DCType_20 other = obj as DCType_20;
+ return (other != null) &&
+ Util.CompareObjects<DCType_9>(this.Member0, other.Member0);
+ }
+
+ /// <summary>
+ /// Returns a hash code for this instance.
+ /// </summary>
+ /// <returns>A hash code for this instance.</returns>
+ public override int GetHashCode()
+ {
+ int result = (this.Member0 == null) ? 0 : this.Member0.GetHashCode();
+ return result;
+ }
+ }
+
+ /// <summary>
+ /// Test type.
+ /// </summary>
+ [Serializable]
+ public class SerType_22
+ {
+ /// <summary>
+ /// Test member.
+ /// </summary>
+ [SuppressMessage("Microsoft.StyleCop.CSharp.MaintainabilityRules", "SA1401:FieldsMustBePrivate",
+ Justification = "Testing serialization of [Serializable] types, which needs public fields.")]
+ public byte Member0;
+
+ /// <summary>
+ /// Compares this instance with the given object.
+ /// </summary>
+ /// <param name="obj">The object to compare.</param>
+ /// <returns><code>true</code> if the given instance is equal to this one; <code>false</code> otherwise.</returns>
+ public override bool Equals(object obj)
+ {
+ SerType_22 other = obj as SerType_22;
+ return (other != null) &&
+ this.Member0.Equals(other.Member0);
+ }
+
+ /// <summary>
+ /// Returns a hash code for this instance.
+ /// </summary>
+ /// <returns>A hash code for this instance.</returns>
+ public override int GetHashCode()
+ {
+ int result = this.Member0.GetHashCode();
+ return result;
+ }
+ }
+
+ /// <summary>
+ /// Test type.
+ /// </summary>
+ [DataContract]
+ public class DCType_25
+ {
+ /// <summary>
+ /// Test member.
+ /// </summary>
+ [DataMember]
+ public SerType_22 Member0 { get; set; }
+
+ /// <summary>
+ /// Compares this instance with the given object.
+ /// </summary>
+ /// <param name="obj">The object to compare.</param>
+ /// <returns><code>true</code> if the given instance is equal to this one; <code>false</code> otherwise.</returns>
+ public override bool Equals(object obj)
+ {
+ DCType_25 other = obj as DCType_25;
+ return (other != null) &&
+ Util.CompareObjects<SerType_22>(this.Member0, other.Member0);
+ }
+
+ /// <summary>
+ /// Returns a hash code for this instance.
+ /// </summary>
+ /// <returns>A hash code for this instance.</returns>
+ public override int GetHashCode()
+ {
+ int result = (this.Member0 == null) ? 0 : this.Member0.GetHashCode();
+ return result;
+ }
+ }
+
+ /// <summary>
+ /// Test type.
+ /// </summary>
+ [CLSCompliant(false)]
+ [Serializable]
+ public class SerType_26
+ {
+ /// <summary>
+ /// Test member.
+ /// </summary>
+ [SuppressMessage("Microsoft.StyleCop.CSharp.MaintainabilityRules", "SA1401:FieldsMustBePrivate",
+ Justification = "Testing serialization of [Serializable] types, which needs public fields.")]
+ public char Member0;
+
+ /// <summary>
+ /// Test member.
+ /// </summary>
+ [SuppressMessage("Microsoft.StyleCop.CSharp.MaintainabilityRules", "SA1401:FieldsMustBePrivate",
+ Justification = "Testing serialization of [Serializable] types, which needs public fields.")]
+ public short? Member1;
+
+ /// <summary>
+ /// Test member.
+ /// </summary>
+ [SuppressMessage("Microsoft.StyleCop.CSharp.MaintainabilityRules", "SA1401:FieldsMustBePrivate",
+ Justification = "Testing serialization of [Serializable] types, which needs public fields.")]
+ public SerType_4 Member2;
+
+ /// <summary>
+ /// Test member.
+ /// </summary>
+ [SuppressMessage("Microsoft.StyleCop.CSharp.MaintainabilityRules", "SA1401:FieldsMustBePrivate",
+ Justification = "Testing serialization of [Serializable] types, which needs public fields.")]
+ public decimal Member3;
+
+ /// <summary>
+ /// Test member.
+ /// </summary>
+ [SuppressMessage("Microsoft.StyleCop.CSharp.MaintainabilityRules", "SA1401:FieldsMustBePrivate",
+ Justification = "Testing serialization of [Serializable] types, which needs public fields.")]
+ [SuppressMessage("Microsoft.Usage", "CA2235:MarkAllNonSerializableFields",
+ Justification = "The type is serializable (it contains a [DataContract] attribute).")]
+ public DCType_3 Member4;
+
+ /// <summary>
+ /// Compares this instance with the given object.
+ /// </summary>
+ /// <param name="obj">The object to compare.</param>
+ /// <returns><code>true</code> if the given instance is equal to this one; <code>false</code> otherwise.</returns>
+ public override bool Equals(object obj)
+ {
+ SerType_26 other = obj as SerType_26;
+ return (other != null) &&
+ this.Member0.Equals(other.Member0) &&
+ Util.CompareNullable<short>(this.Member1, other.Member1) &&
+ Util.CompareObjects<SerType_4>(this.Member2, other.Member2) &&
+ this.Member3.Equals(other.Member3) &&
+ Util.CompareObjects<DCType_3>(this.Member4, other.Member4);
+ }
+
+ /// <summary>
+ /// Returns a hash code for this instance.
+ /// </summary>
+ /// <returns>A hash code for this instance.</returns>
+ public override int GetHashCode()
+ {
+ int result = 0;
+ result ^= this.Member0.GetHashCode();
+ result ^= (this.Member1 == null) ? 0 : this.Member1.GetHashCode();
+ result ^= (this.Member2 == null) ? 0 : this.Member2.GetHashCode();
+ result ^= this.Member3.GetHashCode();
+ result ^= (this.Member4 == null) ? 0 : this.Member4.GetHashCode();
+ return result;
+ }
+ }
+
+ /// <summary>
+ /// Test type.
+ /// </summary>
+ [DataContract]
+ public class DCType_31
+ {
+ /// <summary>
+ /// Test member.
+ /// </summary>
+ [DataMember]
+ public SerType_22 Member0 { get; set; }
+
+ /// <summary>
+ /// Test member.
+ /// </summary>
+ [DataMember]
+ public byte Member1 { get; set; }
+
+ /// <summary>
+ /// Compares this instance with the given object.
+ /// </summary>
+ /// <param name="obj">The object to compare.</param>
+ /// <returns><code>true</code> if the given instance is equal to this one; <code>false</code> otherwise.</returns>
+ public override bool Equals(object obj)
+ {
+ DCType_31 other = obj as DCType_31;
+ return (other != null) &&
+ Util.CompareObjects<SerType_22>(this.Member0, other.Member0) &&
+ this.Member1.Equals(other.Member1);
+ }
+
+ /// <summary>
+ /// Returns a hash code for this instance.
+ /// </summary>
+ /// <returns>A hash code for this instance.</returns>
+ public override int GetHashCode()
+ {
+ int result = 0;
+ result ^= (this.Member0 == null) ? 0 : this.Member0.GetHashCode();
+ result ^= this.Member1.GetHashCode();
+ return result;
+ }
+ }
+
+ /// <summary>
+ /// Test type.
+ /// </summary>
+ [CLSCompliant(false)]
+ [DataContract]
+ public class DCType_32
+ {
+ /// <summary>
+ /// Test member.
+ /// </summary>
+ [DataMember]
+ public int Member0 { get; set; }
+
+ /// <summary>
+ /// Test member.
+ /// </summary>
+ [DataMember]
+ public DCType_20 Member1 { get; set; }
+
+ /// <summary>
+ /// Compares this instance with the given object.
+ /// </summary>
+ /// <param name="obj">The object to compare.</param>
+ /// <returns><code>true</code> if the given instance is equal to this one; <code>false</code> otherwise.</returns>
+ public override bool Equals(object obj)
+ {
+ DCType_32 other = obj as DCType_32;
+ return (other != null) &&
+ this.Member0.Equals(other.Member0) &&
+ Util.CompareObjects<DCType_20>(this.Member1, other.Member1);
+ }
+
+ /// <summary>
+ /// Returns a hash code for this instance.
+ /// </summary>
+ /// <returns>A hash code for this instance.</returns>
+ public override int GetHashCode()
+ {
+ int result = 0;
+ result ^= this.Member0.GetHashCode();
+ result ^= (this.Member1 == null) ? 0 : this.Member1.GetHashCode();
+ return result;
+ }
+ }
+
+ /// <summary>
+ /// Test type.
+ /// </summary>
+ [CLSCompliant(false)]
+ [Serializable]
+ public class SerType_33
+ {
+ /// <summary>
+ /// Test member.
+ /// </summary>
+ [SuppressMessage("Microsoft.StyleCop.CSharp.MaintainabilityRules", "SA1401:FieldsMustBePrivate",
+ Justification = "Testing serialization of [Serializable] types, which needs public fields.")]
+ public long? Member0;
+
+ /// <summary>
+ /// Test member.
+ /// </summary>
+ [SuppressMessage("Microsoft.StyleCop.CSharp.MaintainabilityRules", "SA1401:FieldsMustBePrivate",
+ Justification = "Testing serialization of [Serializable] types, which needs public fields.")]
+ [SuppressMessage("Microsoft.Usage", "CA2235:MarkAllNonSerializableFields",
+ Justification = "The type is serializable (it contains a [DataContract] attribute).")]
+ public DCType_20 Member1;
+
+ /// <summary>
+ /// Compares this instance with the given object.
+ /// </summary>
+ /// <param name="obj">The object to compare.</param>
+ /// <returns><code>true</code> if the given instance is equal to this one; <code>false</code> otherwise.</returns>
+ public override bool Equals(object obj)
+ {
+ SerType_33 other = obj as SerType_33;
+ return (other != null) &&
+ this.Member0.Equals(other.Member0) &&
+ Util.CompareObjects<DCType_20>(this.Member1, other.Member1);
+ }
+
+ /// <summary>
+ /// Returns a hash code for this instance.
+ /// </summary>
+ /// <returns>A hash code for this instance.</returns>
+ public override int GetHashCode()
+ {
+ int result = 0;
+ result ^= (this.Member0 == null) ? 0 : this.Member0.GetHashCode();
+ result ^= (this.Member1 == null) ? 0 : this.Member1.GetHashCode();
+ return result;
+ }
+ }
+
+ /// <summary>
+ /// Test type.
+ /// </summary>
+ [DataContract]
+ public class DCType_34
+ {
+ /// <summary>
+ /// Test member.
+ /// </summary>
+ [DataMember]
+ public short? Member0 { get; set; }
+
+ /// <summary>
+ /// Test member.
+ /// </summary>
+ [DataMember]
+ public float Member1 { get; set; }
+
+ /// <summary>
+ /// Test member.
+ /// </summary>
+ [DataMember]
+ public EnumType_17 Member2 { get; set; }
+
+ /// <summary>
+ /// Compares this instance with the given object.
+ /// </summary>
+ /// <param name="obj">The object to compare.</param>
+ /// <returns><code>true</code> if the given instance is equal to this one; <code>false</code> otherwise.</returns>
+ public override bool Equals(object obj)
+ {
+ DCType_34 other = obj as DCType_34;
+ return (other != null) &&
+ Util.CompareNullable<short>(this.Member0, other.Member0) &&
+ this.Member1.Equals(other.Member1) &&
+ this.Member2.Equals(other.Member2);
+ }
+
+ /// <summary>
+ /// Returns a hash code for this instance.
+ /// </summary>
+ /// <returns>A hash code for this instance.</returns>
+ public override int GetHashCode()
+ {
+ int result = 0;
+ result ^= (this.Member0 == null) ? 0 : this.Member0.GetHashCode();
+ result ^= this.Member1.GetHashCode();
+ result ^= this.Member2.GetHashCode();
+ return result;
+ }
+ }
+
+ /// <summary>
+ /// Test type.
+ /// </summary>
+ [DataContract]
+ public class DCType_36
+ {
+ /// <summary>
+ /// Test member.
+ /// </summary>
+ [DataMember]
+ public bool? Member0 { get; set; }
+
+ /// <summary>
+ /// Compares this instance with the given object.
+ /// </summary>
+ /// <param name="obj">The object to compare.</param>
+ /// <returns><code>true</code> if the given instance is equal to this one; <code>false</code> otherwise.</returns>
+ public override bool Equals(object obj)
+ {
+ DCType_36 other = obj as DCType_36;
+ return (other != null) &&
+ Util.CompareNullable<bool>(this.Member0, other.Member0);
+ }
+
+ /// <summary>
+ /// Returns a hash code for this instance.
+ /// </summary>
+ /// <returns>A hash code for this instance.</returns>
+ public override int GetHashCode()
+ {
+ int result = (this.Member0 == null) ? 0 : this.Member0.GetHashCode();
+ return result;
+ }
+ }
+
+ /// <summary>
+ /// Test type.
+ /// </summary>
+ [CLSCompliant(false)]
+ [DataContract]
+ public class DCType_38
+ {
+ /// <summary>
+ /// Test member.
+ /// </summary>
+ [DataMember]
+ public ulong Member0 { get; set; }
+
+ /// <summary>
+ /// Compares this instance with the given object.
+ /// </summary>
+ /// <param name="obj">The object to compare.</param>
+ /// <returns><code>true</code> if the given instance is equal to this one; <code>false</code> otherwise.</returns>
+ public override bool Equals(object obj)
+ {
+ DCType_38 other = obj as DCType_38;
+ return (other != null) &&
+ this.Member0.Equals(other.Member0);
+ }
+
+ /// <summary>
+ /// Returns a hash code for this instance.
+ /// </summary>
+ /// <returns>A hash code for this instance.</returns>
+ public override int GetHashCode()
+ {
+ int result = this.Member0.GetHashCode();
+ return result;
+ }
+ }
+
+ /// <summary>
+ /// Test type.
+ /// </summary>
+ [DataContract]
+ public class DCType_40
+ {
+ /// <summary>
+ /// Test member.
+ /// </summary>
+ [DataMember]
+ public SerType_22 Member0 { get; set; }
+
+ /// <summary>
+ /// Test member.
+ /// </summary>
+ [DataMember]
+ public short Member1 { get; set; }
+
+ /// <summary>
+ /// Compares this instance with the given object.
+ /// </summary>
+ /// <param name="obj">The object to compare.</param>
+ /// <returns><code>true</code> if the given instance is equal to this one; <code>false</code> otherwise.</returns>
+ public override bool Equals(object obj)
+ {
+ DCType_40 other = obj as DCType_40;
+ return (other != null) &&
+ Util.CompareObjects<SerType_22>(this.Member0, other.Member0) &&
+ this.Member1.Equals(other.Member1);
+ }
+
+ /// <summary>
+ /// Returns a hash code for this instance.
+ /// </summary>
+ /// <returns>A hash code for this instance.</returns>
+ public override int GetHashCode()
+ {
+ int result = 0;
+ result ^= (this.Member0 == null) ? 0 : this.Member0.GetHashCode();
+ result ^= this.Member1.GetHashCode();
+ return result;
+ }
+ }
+
+ /// <summary>
+ /// Test type.
+ /// </summary>
+ [CLSCompliant(false)]
+ [DataContract]
+ public class DCType_42
+ {
+ /// <summary>
+ /// Test member.
+ /// </summary>
+ [DataMember]
+ public SerType_11 Member0 { get; set; }
+
+ /// <summary>
+ /// Test member.
+ /// </summary>
+ [DataMember]
+ public EnumType_35 Member1 { get; set; }
+
+ /// <summary>
+ /// Test member.
+ /// </summary>
+ [DataMember]
+ public SerType_5 Member2 { get; set; }
+
+ /// <summary>
+ /// Test member.
+ /// </summary>
+ [DataMember]
+ public DCType_3 Member3 { get; set; }
+
+ /// <summary>
+ /// Compares this instance with the given object.
+ /// </summary>
+ /// <param name="obj">The object to compare.</param>
+ /// <returns><code>true</code> if the given instance is equal to this one; <code>false</code> otherwise.</returns>
+ public override bool Equals(object obj)
+ {
+ DCType_42 other = obj as DCType_42;
+ return (other != null) &&
+ Util.CompareObjects<SerType_11>(this.Member0, other.Member0) &&
+ this.Member1.Equals(other.Member1) &&
+ Util.CompareObjects<SerType_5>(this.Member2, other.Member2) &&
+ Util.CompareObjects<DCType_3>(this.Member3, other.Member3);
+ }
+
+ /// <summary>
+ /// Returns a hash code for this instance.
+ /// </summary>
+ /// <returns>A hash code for this instance.</returns>
+ public override int GetHashCode()
+ {
+ int result = 0;
+ result ^= (this.Member0 == null) ? 0 : this.Member0.GetHashCode();
+ result ^= this.Member1.GetHashCode();
+ result ^= (this.Member2 == null) ? 0 : this.Member2.GetHashCode();
+ result ^= (this.Member3 == null) ? 0 : this.Member3.GetHashCode();
+ return result;
+ }
+ }
+
+ /// <summary>
+ /// Test type.
+ /// </summary>
+ [CLSCompliant(false)]
+ [DataContract]
+ public class DCType_65
+ {
+ /// <summary>
+ /// Test member.
+ /// </summary>
+ [DataMember]
+ public ulong? Member0 { get; set; }
+
+ /// <summary>
+ /// Test member.
+ /// </summary>
+ [DataMember]
+ public DCType_7 Member1 { get; set; }
+
+ /// <summary>
+ /// Test member.
+ /// </summary>
+ [DataMember]
+ public DCType_36 Member4 { get; set; }
+
+ /// <summary>
+ /// Test member.
+ /// </summary>
+ [DataMember]
+ public uint Member5 { get; set; }
+
+ /// <summary>
+ /// Compares this instance with the given object.
+ /// </summary>
+ /// <param name="obj">The object to compare.</param>
+ /// <returns><code>true</code> if the given instance is equal to this one; <code>false</code> otherwise.</returns>
+ public override bool Equals(object obj)
+ {
+ DCType_65 other = obj as DCType_65;
+ return (other != null) &&
+ Util.CompareNullable<ulong>(this.Member0, other.Member0) &&
+ Util.CompareObjects<DCType_7>(this.Member1, other.Member1) &&
+ Util.CompareObjects<DCType_36>(this.Member4, other.Member4) &&
+ this.Member5.Equals(other.Member5);
+ }
+
+ /// <summary>
+ /// Returns a hash code for this instance.
+ /// </summary>
+ /// <returns>A hash code for this instance.</returns>
+ public override int GetHashCode()
+ {
+ int result = 0;
+ result ^= (this.Member0 == null) ? 0 : this.Member0.GetHashCode();
+ result ^= (this.Member1 == null) ? 0 : this.Member1.GetHashCode();
+ result ^= (this.Member4 == null) ? 0 : this.Member4.GetHashCode();
+ result ^= this.Member5.GetHashCode();
+ return result;
+ }
+ }
+
+ /// <summary>
+ /// Test type.
+ /// </summary>
+ [CLSCompliant(false)]
+ public class ListType_1
+ {
+ /// <summary>
+ /// Test member.
+ /// </summary>
+ public List<DCType_15> Member0 { get; set; }
+
+ /// <summary>
+ /// Test member.
+ /// </summary>
+ public List<DCType_34> Member1 { get; set; }
+
+ /// <summary>
+ /// Test member.
+ /// </summary>
+ public List<SerType_33> Member2 { get; set; }
+
+ /// <summary>
+ /// Compares this instance with the given object.
+ /// </summary>
+ /// <param name="obj">The object to compare.</param>
+ /// <returns><code>true</code> if the given instance is equal to this one; <code>false</code> otherwise.</returns>
+ public override bool Equals(object obj)
+ {
+ ListType_1 other = obj as ListType_1;
+ return (other != null) &&
+ Util.CompareLists<DCType_15>(this.Member0, other.Member0) &&
+ Util.CompareLists<DCType_34>(this.Member1, other.Member1) &&
+ Util.CompareLists<SerType_33>(this.Member2, other.Member2);
+ }
+
+ /// <summary>
+ /// Returns a hash code for this instance.
+ /// </summary>
+ /// <returns>A hash code for this instance.</returns>
+ public override int GetHashCode()
+ {
+ int result = 0;
+ if (this.Member0 != null)
+ {
+ result ^= Util.ComputeArrayHashCode(this.Member0.ToArray());
+ }
+
+ if (this.Member1 != null)
+ {
+ result ^= Util.ComputeArrayHashCode(this.Member0.ToArray());
+ }
+
+ if (this.Member2 != null)
+ {
+ result ^= Util.ComputeArrayHashCode(this.Member0.ToArray());
+ }
+
+ return result;
+ }
+ }
+
+ /// <summary>
+ /// Test type.
+ /// </summary>
+ [CLSCompliant(false)]
+ [DataContract]
+ public class ListType_2
+ {
+ /// <summary>
+ /// Test member.
+ /// </summary>
+ [DataMember]
+ public SerType_4[] Member0 { get; set; }
+
+ /// <summary>
+ /// Test member.
+ /// </summary>
+ [DataMember]
+ public DCType_32[] Member1 { get; set; }
+
+ /// <summary>
+ /// Compares this instance with the given object.
+ /// </summary>
+ /// <param name="obj">The object to compare.</param>
+ /// <returns><code>true</code> if the given instance is equal to this one; <code>false</code> otherwise.</returns>
+ public override bool Equals(object obj)
+ {
+ ListType_2 other = obj as ListType_2;
+ return (other != null) &&
+ Util.CompareArrays(this.Member0, other.Member0) &&
+ Util.CompareArrays(this.Member1, other.Member1);
+ }
+
+ /// <summary>
+ /// Returns a hash code for this instance.
+ /// </summary>
+ /// <returns>A hash code for this instance.</returns>
+ public override int GetHashCode()
+ {
+ int result = 0;
+ result ^= Util.ComputeArrayHashCode(this.Member0);
+ result ^= Util.ComputeArrayHashCode(this.Member0);
+ return result;
+ }
+ }
+
+ /// <summary>
+ /// Test type.
+ /// </summary>
+ [DataContract]
+ [KnownType(typeof(DerivedType))]
+ public class BaseType
+ {
+ /// <summary>
+ /// Test member.
+ /// </summary>
+ [DataMember]
+ public string Member0 { get; set; }
+
+ /// <summary>
+ /// Test member.
+ /// </summary>
+ [DataMember]
+ public DCType_1 Member1 { get; set; }
+
+ /// <summary>
+ /// Creates an instance of this type.
+ /// </summary>
+ /// <param name="rndGen">The random generator used to populate this type.</param>
+ /// <returns>An instance of the <see cref="DerivedType"/>.</returns>
+ public static BaseType CreateInstance(Random rndGen)
+ {
+ return new DerivedType(rndGen);
+ }
+
+ /// <summary>
+ /// Returns a debug representation for this instance.
+ /// </summary>
+ /// <returns>A debug representation for this instance.</returns>
+ public override string ToString()
+ {
+ return String.Format(CultureInfo.InvariantCulture, "BaseType<Member0={0},Member1={1}>", Util.EscapeString(this.Member0), Util.EscapeString(this.Member1));
+ }
+ }
+
+ /// <summary>
+ /// Test type.
+ /// </summary>
+ [DataContract]
+ public class DerivedType : BaseType, IEmptyInterface
+ {
+ /// <summary>
+ /// Initializes an instance of this type.
+ /// </summary>
+ /// <param name="rndGen">The random generator used to populate this type.</param>
+ public DerivedType(Random rndGen)
+ {
+ this.Member0 = InstanceCreator.CreateInstanceOf<string>(rndGen);
+ this.Member1 = InstanceCreator.CreateInstanceOf<DCType_1>(rndGen);
+ this.Member2 = InstanceCreator.CreateInstanceOf<SerType_4>(rndGen);
+ this.Member3 = InstanceCreator.CreateInstanceOf<decimal>(rndGen);
+ }
+
+ /// <summary>
+ /// Test member.
+ /// </summary>
+ [DataMember]
+ public SerType_4 Member2 { get; set; }
+
+ /// <summary>
+ /// Test member.
+ /// </summary>
+ [DataMember]
+ public decimal Member3 { get; set; }
+
+ /// <summary>
+ /// Compares this instance with the given object.
+ /// </summary>
+ /// <param name="obj">The object to compare.</param>
+ /// <returns><code>true</code> if the given instance is equal to this one; <code>false</code> otherwise.</returns>
+ public override bool Equals(object obj)
+ {
+ DerivedType other = obj as DerivedType;
+ return (other != null) &&
+ Util.CompareObjects<string>(this.Member0, other.Member0) &&
+ Util.CompareObjects<DCType_1>(this.Member1, other.Member1) &&
+ Util.CompareObjects<SerType_4>(this.Member2, other.Member2) &&
+ this.Member3.Equals(other.Member3);
+ }
+
+ /// <summary>
+ /// Returns a hash code for this instance.
+ /// </summary>
+ /// <returns>A hash code for this instance.</returns>
+ public override int GetHashCode()
+ {
+ int result = 0;
+ result ^= this.Member0 == null ? 0 : this.Member0.GetHashCode();
+ result ^= this.Member1 == null ? 0 : this.Member1.GetHashCode();
+ result ^= this.Member2 == null ? 0 : this.Member2.GetHashCode();
+ result ^= this.Member3.GetHashCode();
+ return result;
+ }
+
+ /// <summary>
+ /// Returns a debug representation for this instance.
+ /// </summary>
+ /// <returns>A debug representation for this instance.</returns>
+ public override string ToString()
+ {
+ return String.Format(
+ CultureInfo.InvariantCulture,
+ "DerivedType<Base={0},Member2={1},Member3={2}>",
+ base.ToString(),
+ Util.EscapeString(this.Member2),
+ Util.EscapeString(this.Member3));
+ }
+ }
+
+ /// <summary>
+ /// Test type.
+ /// </summary>
+ [DataContract]
+ public class PolymorphicMember
+ {
+ /// <summary>
+ /// Test member.
+ /// </summary>
+ [DataMember]
+ public BaseType Member_0 { get; set; }
+
+ /// <summary>
+ /// Compares this instance with the given object.
+ /// </summary>
+ /// <param name="obj">The object to compare.</param>
+ /// <returns><code>true</code> if the given instance is equal to this one; <code>false</code> otherwise.</returns>
+ public override bool Equals(object obj)
+ {
+ PolymorphicMember other = obj as PolymorphicMember;
+ return (other != null) &&
+ Util.CompareObjects<BaseType>(this.Member_0, other.Member_0);
+ }
+
+ /// <summary>
+ /// Returns a hash code for this instance.
+ /// </summary>
+ /// <returns>A hash code for this instance.</returns>
+ public override int GetHashCode()
+ {
+ return this.Member_0 == null ? 0 : this.Member_0.GetHashCode();
+ }
+ }
+
+ /// <summary>
+ /// Test type.
+ /// </summary>
+ [DataContract, KnownType(typeof(DerivedType))]
+ public class PolymorphicAsInterfaceMember
+ {
+ /// <summary>
+ /// Test member.
+ /// </summary>
+ [DataMember]
+ public IEmptyInterface Member_0 { get; set; }
+
+ /// <summary>
+ /// Compares this instance with the given object.
+ /// </summary>
+ /// <param name="obj">The object to compare.</param>
+ /// <returns><code>true</code> if the given instance is equal to this one; <code>false</code> otherwise.</returns>
+ public override bool Equals(object obj)
+ {
+ PolymorphicAsInterfaceMember other = obj as PolymorphicAsInterfaceMember;
+ return (other != null) &&
+ Util.CompareObjects<IEmptyInterface>(this.Member_0, other.Member_0);
+ }
+
+ /// <summary>
+ /// Returns a hash code for this instance.
+ /// </summary>
+ /// <returns>A hash code for this instance.</returns>
+ public override int GetHashCode()
+ {
+ return this.Member_0 == null ? 0 : this.Member_0.GetHashCode();
+ }
+ }
+
+ /// <summary>
+ /// Test type.
+ /// </summary>
+ [DataContract]
+ [KnownType(typeof(DerivedType))]
+ public class CollectionsWithPolymorphicMember
+ {
+ /// <summary>
+ /// Test member.
+ /// </summary>
+ [DataMember]
+ public List<BaseType> ListOfBase { get; set; }
+
+ /// <summary>
+ /// Test member.
+ /// </summary>
+ [DataMember]
+ public List<IEmptyInterface> ListOfInterface { get; set; }
+
+ /// <summary>
+ /// Test member.
+ /// </summary>
+ [DataMember]
+ public Dictionary<string, BaseType> DictionaryOfBase { get; set; }
+
+ /// <summary>
+ /// Test member.
+ /// </summary>
+ [DataMember]
+ public Dictionary<string, IEmptyInterface> DictionaryOfInterface { get; set; }
+
+ /// <summary>
+ /// Compares this instance with the given object.
+ /// </summary>
+ /// <param name="obj">The object to compare.</param>
+ /// <returns><code>true</code> if the given instance is equal to this one; <code>false</code> otherwise.</returns>
+ public override bool Equals(object obj)
+ {
+ CollectionsWithPolymorphicMember other = obj as CollectionsWithPolymorphicMember;
+ return (other != null) &&
+ Util.CompareLists<BaseType>(this.ListOfBase, other.ListOfBase) &&
+ Util.CompareLists<IEmptyInterface>(this.ListOfInterface, other.ListOfInterface) &&
+ Util.CompareDictionaries<string, BaseType>(this.DictionaryOfBase, other.DictionaryOfBase) &&
+ Util.CompareDictionaries<string, IEmptyInterface>(this.DictionaryOfInterface, other.DictionaryOfInterface);
+ }
+
+ /// <summary>
+ /// Returns a hash code for this instance.
+ /// </summary>
+ /// <returns>A hash code for this instance.</returns>
+ public override int GetHashCode()
+ {
+ int result = 0;
+ result ^= this.ListOfBase == null ? 0 : Util.ComputeArrayHashCode(this.ListOfBase.ToArray());
+ result ^= this.ListOfInterface == null ? 0 : Util.ComputeArrayHashCode(this.ListOfInterface.ToArray());
+ result ^= this.DictionaryOfBase == null ? 0 : Util.ComputeArrayHashCode(new List<string>(this.DictionaryOfBase.Keys).ToArray());
+ result ^= this.DictionaryOfBase == null ? 0 : Util.ComputeArrayHashCode(new List<BaseType>(this.DictionaryOfBase.Values).ToArray());
+ result ^= this.DictionaryOfInterface == null ? 0 : Util.ComputeArrayHashCode(new List<string>(this.DictionaryOfInterface.Keys).ToArray());
+ result ^= this.DictionaryOfInterface == null ? 0 : Util.ComputeArrayHashCode(new List<IEmptyInterface>(this.DictionaryOfInterface.Values).ToArray());
+ return result;
+ }
+
+ /// <summary>
+ /// Returns a debug representation for this instance.
+ /// </summary>
+ /// <returns>A debug representation for this instance.</returns>
+ public override string ToString()
+ {
+ StringBuilder sb = new StringBuilder();
+ sb.Append("CollectionsWithPolymorphicMember<");
+ PrintList(sb, "ListOfBase", this.ListOfBase);
+ sb.Append(", ");
+ PrintList(sb, "ListOfInterface", this.ListOfBase);
+ sb.Append(", ");
+ PrintDictionary(sb, "DictionaryOfBase", this.DictionaryOfBase);
+ sb.Append(", ");
+ PrintDictionary(sb, "DictionaryOfInterface", this.DictionaryOfInterface);
+ sb.Append('>');
+ return sb.ToString();
+ }
+
+ private static void PrintList<T>(StringBuilder sb, string name, List<T> list)
+ {
+ sb.Append(name);
+ sb.Append('=');
+ if (list == null)
+ {
+ sb.Append("<<null>>");
+ }
+ else
+ {
+ sb.Append('[');
+ for (int i = 0; i < list.Count; i++)
+ {
+ if (i > 0)
+ {
+ sb.Append(',');
+ }
+
+ sb.Append(Util.EscapeString(list[i]));
+ }
+
+ sb.Append(']');
+ }
+ }
+
+ private static void PrintDictionary<T>(StringBuilder sb, string name, Dictionary<string, T> dict)
+ {
+ sb.Append(name);
+ sb.Append('=');
+ if (dict == null)
+ {
+ sb.Append("<<null>>");
+ }
+ else
+ {
+ sb.Append('{');
+ bool first = true;
+ foreach (string key in dict.Keys)
+ {
+ if (first)
+ {
+ first = false;
+ }
+ else
+ {
+ sb.Append(',');
+ }
+
+ sb.AppendFormat("\"{0}\":", Util.EscapeString(key));
+ sb.Append(Util.EscapeString(dict[key]));
+ }
+
+ sb.Append('}');
+ }
+ }
+ }
+
+ /// <summary>
+ /// Test type.
+ /// </summary>
+ public class Person
+ {
+ internal const string Letters = "abcdefghijklmnopqrstuvwxyz";
+
+ /// <summary>
+ /// Initializes an instance of this class.
+ /// </summary>
+ public Person()
+ {
+ this.Friends = new List<Person>();
+ }
+
+ /// <summary>
+ /// Initializes an instance of this class.
+ /// </summary>
+ /// <param name="rndGen">The random generator used to populate this type.</param>
+ public Person(Random rndGen)
+ {
+ this.Name = PrimitiveCreator.CreateInstanceOfString(rndGen, rndGen.Next(5, 15), Letters);
+ this.Age = PrimitiveCreator.CreateInstanceOfInt32(rndGen);
+ this.Address = new Address(rndGen);
+ this.Friends = new List<Person>();
+ }
+
+ /// <summary>
+ /// Test member.
+ /// </summary>
+ public string Name { get; set; }
+
+ /// <summary>
+ /// Test member.
+ /// </summary>
+ public int Age { get; set; }
+
+ /// <summary>
+ /// Test member.
+ /// </summary>
+ public Address Address { get; set; }
+
+ /// <summary>
+ /// Test member.
+ /// </summary>
+ public List<Person> Friends { get; set; }
+
+ /// <summary>
+ /// Adds new instances of <see cref="Person"/> as <see cref="Person.Friends"/> of this instance.
+ /// </summary>
+ /// <param name="count">The number of instances to add.</param>
+ /// <param name="rndGen">The random generator used to populate the instances.</param>
+ public void AddFriends(int count, Random rndGen)
+ {
+ for (int i = 0; i < count; i++)
+ {
+ this.Friends.Add(new Person(rndGen));
+ }
+ }
+
+ /// <summary>
+ /// Returns a string representation of the "friends" of this instance, for logging purposes.
+ /// </summary>
+ /// <returns>A string representation of the "friends" of this instance.</returns>
+ public string FriendsToString()
+ {
+ string s = "";
+
+ foreach (Person p in this.Friends)
+ {
+ s += p + ",";
+ }
+
+ return s;
+ }
+
+ /// <summary>
+ /// Returns a readable representation of a <see cref="Person"/> instance.
+ /// </summary>
+ /// <returns>A readable representation of this instance.</returns>
+ public override string ToString()
+ {
+ return String.Format("Person{{{0}, {1}, [{2}], Friends=[{3}]}}", this.Name, this.Age, this.Address, this.FriendsToString());
+ }
+ }
+
+ /// <summary>
+ /// Test type.
+ /// </summary>
+ public class Address
+ {
+ /// <summary>
+ /// Initializes an instance of this class.
+ /// </summary>
+ public Address()
+ {
+ }
+
+ /// <summary>
+ /// Initializes an instance of this class.
+ /// </summary>
+ /// <param name="rndGen">The random generator used to populate this type.</param>
+ public Address(Random rndGen)
+ {
+ this.Street = PrimitiveCreator.CreateInstanceOfString(rndGen, rndGen.Next(5, 15), Person.Letters);
+ this.City = PrimitiveCreator.CreateInstanceOfString(rndGen, rndGen.Next(5, 15), Person.Letters);
+ this.State = PrimitiveCreator.CreateInstanceOfString(rndGen, rndGen.Next(5, 15), Person.Letters);
+ }
+
+ /// <summary>
+ /// Test member.
+ /// </summary>
+ public string Street { get; set; }
+
+ /// <summary>
+ /// Test member.
+ /// </summary>
+ public string City { get; set; }
+
+ /// <summary>
+ /// Test member.
+ /// </summary>
+ public string State { get; set; }
+
+ /// <summary>
+ /// Returns a readable representation of a <see cref="Address"/> instance.
+ /// </summary>
+ /// <returns>A readable representation of this instance.</returns>
+ public override string ToString()
+ {
+ return String.Format("Address{{{0}, {1}, {2}}}", this.Street, this.City, this.State);
+ }
+ }
+}
+
diff --git a/test/System.Json.Test.Integration/Common/Util.cs b/test/System.Json.Test.Integration/Common/Util.cs
new file mode 100644
index 00000000..26b65231
--- /dev/null
+++ b/test/System.Json.Test.Integration/Common/Util.cs
@@ -0,0 +1,201 @@
+using System.Collections;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace System.Json
+{
+ internal static class Util
+ {
+ public static bool CompareObjects<T>(T o1, T o2) where T : class
+ {
+ if ((o1 == null) != (o2 == null))
+ {
+ return false;
+ }
+
+ return (o1 == null) || o1.Equals(o2);
+ }
+
+ public static bool CompareNullable<T>(Nullable<T> n1, Nullable<T> n2) where T : struct
+ {
+ if (n1.HasValue != n2.HasValue)
+ {
+ return false;
+ }
+
+ return (!n1.HasValue) || n1.Value.Equals(n2.Value);
+ }
+
+ public static bool CompareLists<T>(List<T> list1, List<T> list2)
+ {
+ if (list1 == null)
+ {
+ return list2 == null;
+ }
+
+ if (list2 == null)
+ {
+ return false;
+ }
+
+ return CompareArrays(list1.ToArray(), list2.ToArray());
+ }
+
+ public static bool CompareDictionaries<K, V>(Dictionary<K, V> dict1, Dictionary<K, V> dict2)
+ where K : IComparable
+ where V : class
+ {
+ if (dict1 == null)
+ {
+ return dict2 == null;
+ }
+
+ if (dict2 == null)
+ {
+ return false;
+ }
+
+ List<K> keys1 = new List<K>(dict1.Keys);
+ List<K> keys2 = new List<K>(dict2.Keys);
+ keys1.Sort();
+ keys2.Sort();
+ if (!CompareLists<K>(keys1, keys2))
+ {
+ return false;
+ }
+
+ foreach (K key in keys1)
+ {
+ V value1 = dict1[key];
+ V value2 = dict2[key];
+ if (!CompareObjects<V>(value1, value2))
+ {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ public static bool CompareArrays(Array array1, Array array2)
+ {
+ if (array1 == null)
+ {
+ return array2 == null;
+ }
+
+ if (array2 == null || array1.Length != array2.Length)
+ {
+ return false;
+ }
+
+ for (int i = 0; i < array1.Length; i++)
+ {
+ object o1 = array1.GetValue(i);
+ object o2 = array2.GetValue(i);
+ if ((o1 == null) != (o2 == null))
+ {
+ return false;
+ }
+
+ if (o1 != null)
+ {
+ if ((o1 is Array) && (o2 is Array))
+ {
+ if (!CompareArrays((Array)o1, (Array)o2))
+ {
+ return false;
+ }
+ }
+ else if (o1 is IEnumerable && o2 is IEnumerable)
+ {
+ if (!CompareArrays(ToObjectArray((IEnumerable)o1), ToObjectArray((IEnumerable)o2)))
+ {
+ return false;
+ }
+ }
+ else
+ {
+ if (!o1.Equals(o2))
+ {
+ return false;
+ }
+ }
+ }
+ }
+
+ return true;
+ }
+
+ public static int ComputeArrayHashCode(Array array)
+ {
+ if (array == null)
+ {
+ return 0;
+ }
+
+ int result = 0;
+ result += array.Length;
+ for (int i = 0; i < array.Length; i++)
+ {
+ object o = array.GetValue(i);
+ if (o != null)
+ {
+ if (o is Array)
+ {
+ result ^= ComputeArrayHashCode((Array)o);
+ }
+ else if (o is Enumerable)
+ {
+ result ^= ComputeArrayHashCode(ToObjectArray((IEnumerable)o));
+ }
+ else
+ {
+ result ^= o.GetHashCode();
+ }
+ }
+ }
+
+ return result;
+ }
+
+ public static string EscapeString(object obj)
+ {
+ StringBuilder sb = new StringBuilder();
+ if (obj == null)
+ {
+ return "<<null>>";
+ }
+ else
+ {
+ string str = obj.ToString();
+ for (int i = 0; i < str.Length; i++)
+ {
+ char c = str[i];
+ if (c < ' ' || c > '~')
+ {
+ sb.AppendFormat("\\u{0:X4}", (int)c);
+ }
+ else
+ {
+ sb.Append(c);
+ }
+ }
+ }
+
+ return sb.ToString();
+ }
+
+ static object[] ToObjectArray(IEnumerable enumerable)
+ {
+ List<object> result = new List<object>();
+ foreach (var item in enumerable)
+ {
+ result.Add(item);
+ }
+
+ return result.ToArray();
+ }
+ }
+} \ No newline at end of file
diff --git a/test/System.Json.Test.Integration/JObjectFunctionalTest.cs b/test/System.Json.Test.Integration/JObjectFunctionalTest.cs
new file mode 100644
index 00000000..6a1cdd86
--- /dev/null
+++ b/test/System.Json.Test.Integration/JObjectFunctionalTest.cs
@@ -0,0 +1,990 @@
+using System.Collections.Generic;
+using System.Globalization;
+using System.Text;
+using System.Threading;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Json
+{
+ /// <summary>
+ /// Functional tests for the JsonObject class.
+ /// </summary>
+ public class JObjectFunctionalTest
+ {
+ static int iterationCount = 500;
+ static int arrayLength = 10;
+
+ /// <summary>
+ /// Validates round-trip of a JsonArray containing both primitives and objects.
+ /// </summary>
+ [Fact]
+ public void MixedJsonTypeFunctionalTest()
+ {
+ bool oldValue = CreatorSettings.CreateDateTimeWithSubMilliseconds;
+ CreatorSettings.CreateDateTimeWithSubMilliseconds = false;
+ try
+ {
+ int seed = 1;
+
+ for (int i = 0; i < iterationCount; i++)
+ {
+ seed++;
+ Log.Info("Seed: {0}", seed);
+ Random rndGen = new Random(seed);
+
+ JsonArray sourceJson = new JsonArray(new List<JsonValue>()
+ {
+ PrimitiveCreator.CreateInstanceOfBoolean(rndGen),
+ PrimitiveCreator.CreateInstanceOfByte(rndGen),
+ PrimitiveCreator.CreateInstanceOfDateTime(rndGen),
+ PrimitiveCreator.CreateInstanceOfDateTimeOffset(rndGen),
+ PrimitiveCreator.CreateInstanceOfDecimal(rndGen),
+ PrimitiveCreator.CreateInstanceOfDouble(rndGen),
+ PrimitiveCreator.CreateInstanceOfInt16(rndGen),
+ PrimitiveCreator.CreateInstanceOfInt32(rndGen),
+ PrimitiveCreator.CreateInstanceOfInt64(rndGen),
+ PrimitiveCreator.CreateInstanceOfSByte(rndGen),
+ PrimitiveCreator.CreateInstanceOfSingle(rndGen),
+ PrimitiveCreator.CreateInstanceOfString(rndGen),
+ PrimitiveCreator.CreateInstanceOfUInt16(rndGen),
+ PrimitiveCreator.CreateInstanceOfUInt32(rndGen),
+ PrimitiveCreator.CreateInstanceOfUInt64(rndGen),
+ new JsonObject(new Dictionary<string, JsonValue>()
+ {
+ { "Boolean", PrimitiveCreator.CreateInstanceOfBoolean(rndGen) },
+ { "Byte", PrimitiveCreator.CreateInstanceOfByte(rndGen) },
+ { "DateTime", PrimitiveCreator.CreateInstanceOfDateTime(rndGen) },
+ { "DateTimeOffset", PrimitiveCreator.CreateInstanceOfDateTimeOffset(rndGen) },
+ { "Decimal", PrimitiveCreator.CreateInstanceOfDecimal(rndGen) },
+ { "Double", PrimitiveCreator.CreateInstanceOfDouble(rndGen) },
+ { "Int16", PrimitiveCreator.CreateInstanceOfInt16(rndGen) },
+ { "Int32", PrimitiveCreator.CreateInstanceOfInt32(rndGen) },
+ { "Int64", PrimitiveCreator.CreateInstanceOfInt64(rndGen) },
+ { "SByte", PrimitiveCreator.CreateInstanceOfSByte(rndGen) },
+ { "Single", PrimitiveCreator.CreateInstanceOfSingle(rndGen) },
+ { "String", PrimitiveCreator.CreateInstanceOfString(rndGen) },
+ { "UInt16", PrimitiveCreator.CreateInstanceOfUInt16(rndGen) },
+ { "UInt32", PrimitiveCreator.CreateInstanceOfUInt32(rndGen) },
+ { "UInt64", PrimitiveCreator.CreateInstanceOfUInt64(rndGen) }
+ })
+ });
+
+ JsonArray newJson = (JsonArray)JsonValue.Parse(sourceJson.ToString());
+ Assert.True(JsonValueVerifier.Compare(sourceJson, newJson));
+ }
+ }
+ finally
+ {
+ CreatorSettings.CreateDateTimeWithSubMilliseconds = oldValue;
+ }
+ }
+
+ /// <summary>
+ /// Tests for the <see cref="System.Json.JsonArray.CopyTo"/> method.
+ /// </summary>
+ [Fact]
+ public void JsonArrayCopytoFunctionalTest()
+ {
+ int seed = 1;
+
+ for (int i = 0; i < iterationCount / 10; i++)
+ {
+ seed++;
+ Log.Info("Seed: {0}", seed);
+ Random rndGen = new Random(seed);
+
+ bool retValue = true;
+
+ JsonArray sourceJson = SpecialJsonValueHelper.CreatePrePopulatedJsonArray(seed, arrayLength);
+ JsonValue[] destJson = new JsonValue[arrayLength];
+ sourceJson.CopyTo(destJson, 0);
+
+ for (int k = 0; k < destJson.Length; k++)
+ {
+ if (destJson[k] != sourceJson[k])
+ {
+ retValue = false;
+ }
+ }
+
+ Assert.True(retValue, "[JsonArrayCopytoFunctionalTest] JsonArray.CopyTo() failed to function properly. destJson.GetLength(0) = " + destJson.GetLength(0));
+ }
+ }
+
+ /// <summary>
+ /// Tests for add and remove methods in the <see cref="System.Json.JsonArray"/> class.
+ /// </summary>
+ [Fact]
+ public void JsonArrayAddRemoveFunctionalTest()
+ {
+ int seed = 1;
+
+ for (int i = 0; i < iterationCount / 10; i++)
+ {
+ seed++;
+ Log.Info("Seed: {0}", seed);
+ Random rndGen = new Random(seed);
+ bool retValue = true;
+
+ JsonArray sourceJson = SpecialJsonValueHelper.CreatePrePopulatedJsonArray(seed, arrayLength);
+ JsonValue[] cloneJson = SpecialJsonValueHelper.CreatePrePopulatedJsonValueArray(seed, 3);
+
+ // JsonArray.AddRange(JsonValue[])
+ sourceJson.AddRange(cloneJson);
+ if (sourceJson.Count != arrayLength + cloneJson.Length)
+ {
+ Log.Info("[JsonArrayAddRemoveFunctionalTest] JsonArray.AddRange(JsonValue[]) failed to function properly.");
+ retValue = false;
+ }
+ else
+ {
+ Log.Info("[JsonArrayAddRemoveFunctionalTest] JsonArray.AddRange(JsonValue[]) passed test.");
+ }
+
+ // JsonArray.RemoveAt(int)
+ int count = sourceJson.Count;
+ for (int j = 0; j < count; j++)
+ {
+ sourceJson.RemoveAt(0);
+ }
+
+ if (sourceJson.Count > 0)
+ {
+ Log.Info("[JsonArrayAddRemoveFunctionalTest] JsonArray.RemoveAt(int) failed to function properly.");
+ retValue = false;
+ }
+ else
+ {
+ Log.Info("[JsonArrayAddRemoveFunctionalTest] JsonArray.RemoveAt(int) passed test.");
+ }
+
+ // JsonArray.JsonType
+ if (sourceJson.JsonType != JsonType.Array)
+ {
+ Log.Info("[JsonArrayAddRemoveFunctionalTest] JsonArray.JsonType failed to function properly.");
+ retValue = false;
+ }
+
+ // JsonArray.Clear()
+ sourceJson = SpecialJsonValueHelper.CreatePrePopulatedJsonArray(seed, arrayLength);
+ sourceJson.Clear();
+ if (sourceJson.Count > 0)
+ {
+ Log.Info("[JsonArrayAddRemoveFunctionalTest] JsonArray.Clear() failed to function properly.");
+ retValue = false;
+ }
+ else
+ {
+ Log.Info("[JsonArrayAddRemoveFunctionalTest] JsonArray.Clear() passed test.");
+ }
+
+ // JsonArray.AddRange(JsonValue)
+ sourceJson = SpecialJsonValueHelper.CreatePrePopulatedJsonArray(seed, arrayLength);
+
+ // adding one additional value to the array
+ sourceJson.AddRange(SpecialJsonValueHelper.GetRandomJsonPrimitives(seed));
+ if (sourceJson.Count != arrayLength + 1)
+ {
+ Log.Info("[JsonArrayAddRemoveFunctionalTest] JsonArray.AddRange(JsonValue) failed to function properly.");
+ retValue = false;
+ }
+ else
+ {
+ Log.Info("[JsonArrayAddRemoveFunctionalTest] JsonArray.AddRange(JsonValue) passed test.");
+ }
+
+ // JsonArray.AddRange(IEnumerable<JsonValue> items)
+ sourceJson = SpecialJsonValueHelper.CreatePrePopulatedJsonArray(seed, arrayLength);
+ MyJsonValueCollection<JsonValue> myCols = new MyJsonValueCollection<JsonValue>();
+ myCols.Add(new JsonPrimitive(PrimitiveCreator.CreateInstanceOfUInt32(rndGen)));
+ string str;
+ do
+ {
+ str = PrimitiveCreator.CreateInstanceOfString(rndGen);
+ } while (str == null);
+
+ myCols.Add(new JsonPrimitive(str));
+ myCols.Add(new JsonPrimitive(PrimitiveCreator.CreateInstanceOfDateTime(rndGen)));
+
+ // adding 3 additional value to the array
+ sourceJson.AddRange(myCols);
+ if (sourceJson.Count != arrayLength + 3)
+ {
+ Log.Info("[JsonArrayAddRemoveFunctionalTest] JsonArray.AddRange(IEnumerable<JsonValue> items) failed to function properly.");
+ retValue = false;
+ }
+ else
+ {
+ Log.Info("[JsonArrayAddRemoveFunctionalTest] JsonArray.AddRange(IEnumerable<JsonValue> items) passed test.");
+ }
+
+ // JsonArray[index].set_Item
+ sourceJson = SpecialJsonValueHelper.CreatePrePopulatedJsonArray(seed, arrayLength);
+ string temp;
+ do
+ {
+ temp = PrimitiveCreator.CreateInstanceOfString(rndGen);
+ } while (temp == null);
+
+ sourceJson[1] = temp;
+ if ((string)sourceJson[1] != temp)
+ {
+ Log.Info("[JsonArrayAddRemoveFunctionalTest] JsonArray[index].set_Item failed to function properly.");
+ retValue = false;
+ }
+ else
+ {
+ Log.Info("[JsonArrayAddRemoveFunctionalTest] JsonArray[index].set_Item passed test.");
+ }
+
+ // JsonArray.Remove(JsonValue)
+ count = sourceJson.Count;
+ for (int j = 0; j < count; j++)
+ {
+ sourceJson.Remove(sourceJson[0]);
+ }
+
+ if (sourceJson.Count > 0)
+ {
+ Log.Info("[JsonArrayAddRemoveFunctionalTest] JsonArray.Remove(JsonValue) failed to function properly.");
+ retValue = false;
+ }
+
+ Assert.True(retValue);
+ }
+ }
+
+ /// <summary>
+ /// Tests for indexers in the <see cref="System.Json.JsonArray"/> class.
+ /// </summary>
+ [Fact]
+ public void JsonArrayItemsFunctionalTest()
+ {
+ int seed = 1;
+
+ for (int i = 0; i < iterationCount / 10; i++)
+ {
+ seed++;
+ Log.Info("Seed: {0}", seed);
+ Random rndGen = new Random(seed);
+ bool retValue = true;
+
+ // JsonArray.Contains(JsonValue)
+ // JsonArray.IndexOf(JsonValue)
+ JsonArray sourceJson = SpecialJsonValueHelper.CreatePrePopulatedJsonArray(seed, arrayLength);
+ for (int j = 0; j < sourceJson.Count; j++)
+ {
+ if (!sourceJson.Contains(sourceJson[j]))
+ {
+ Log.Info("[JsonArrayItemsFunctionalTest] JsonArray.Contains(JsonValue) failed to function properly.");
+ retValue = false;
+ }
+ else
+ {
+ Log.Info("[JsonArrayItemsFunctionalTest] JsonArray.Contains(JsonValue) passed test.");
+ }
+
+ if (sourceJson.IndexOf(sourceJson[j]) != j)
+ {
+ Log.Info("[JsonArrayItemsFunctionalTest] JsonArray.IndexOf(JsonValue) failed to function properly.");
+ retValue = false;
+ }
+ else
+ {
+ Log.Info("[JsonArrayItemsFunctionalTest] JsonArray.IndexOf(JsonValue) passed test.");
+ }
+ }
+
+ // JsonArray.Insert(int, JsonValue)
+ JsonValue newItem = SpecialJsonValueHelper.GetRandomJsonPrimitives(seed);
+ sourceJson.Insert(3, newItem);
+ if (sourceJson[3] != newItem || sourceJson.Count != arrayLength + 1)
+ {
+ Log.Info("[JsonArrayItemsFunctionalTest] JsonArray.Insert(int, JsonValue) failed to function properly.");
+ retValue = false;
+ }
+ else
+ {
+ Log.Info("[JsonArrayItemsFunctionalTest] JsonArray.Insert(int, JsonValue) passed test.");
+ }
+
+ Assert.True(retValue);
+ }
+ }
+
+ /// <summary>
+ /// Tests for the CopyTo methods in the <see cref="System.Json.JsonObject"/> class.
+ /// </summary>
+ [Fact]
+ public void JsonObjectCopytoFunctionalTest()
+ {
+ int seed = 1;
+
+ for (int i = 0; i < iterationCount / 10; i++)
+ {
+ seed++;
+ Log.Info("Seed: {0}", seed);
+ Random rndGen = new Random(seed);
+
+ bool retValue = true;
+
+ JsonObject sourceJson = SpecialJsonValueHelper.CreateIndexPopulatedJsonObject(seed, arrayLength);
+ KeyValuePair<string, JsonValue>[] destJson = new KeyValuePair<string, JsonValue>[arrayLength];
+ if (sourceJson != null && destJson != null)
+ {
+ sourceJson.CopyTo(destJson, 0);
+ }
+ else
+ {
+ Log.Info("[JsonObjectCopytoFunctionalTest] sourceJson.ToString() = " + sourceJson.ToString());
+ Log.Info("[JsonObjectCopytoFunctionalTest] destJson.ToString() = " + destJson.ToString());
+ Assert.False(true, "[JsonObjectCopytoFunctionalTest] failed to create the source JsonObject object.");
+ return;
+ }
+
+ if (destJson.Length == arrayLength)
+ {
+ for (int k = 0; k < destJson.Length; k++)
+ {
+ JsonValue temp;
+ sourceJson.TryGetValue(k.ToString(), out temp);
+ if (!(temp != null && destJson[k].Value == temp))
+ {
+ retValue = false;
+ }
+ }
+ }
+ else
+ {
+ retValue = false;
+ }
+
+ Assert.True(retValue, "[JsonObjectCopytoFunctionalTest] JsonObject.CopyTo() failed to function properly. destJson.GetLength(0) = " + destJson.GetLength(0));
+ }
+ }
+
+ /// <summary>
+ /// Tests for the add and remove methods in the <see cref="System.Json.JsonObject"/> class.
+ /// </summary>
+ [Fact]
+ public void JsonObjectAddRemoveFunctionalTest()
+ {
+ int seed = 1;
+
+ for (int i = 0; i < iterationCount / 10; i++)
+ {
+ seed++;
+ Log.Info("Seed: {0}", seed);
+ Random rndGen = new Random(seed);
+ bool retValue = true;
+
+ JsonObject sourceJson = SpecialJsonValueHelper.CreateIndexPopulatedJsonObject(seed, arrayLength);
+
+ // JsonObject.JsonType
+ if (sourceJson.JsonType != JsonType.Object)
+ {
+ Log.Info("[JsonObjectAddRemoveFunctionalTest] JsonArray.JsonType failed to function properly.");
+ retValue = false;
+ }
+
+ // JsonObject.Add(KeyValuePair<string, JsonValue> item)
+ // JsonObject.Add(string key, JsonValue value)
+ // + various numers below so .AddRange() won't try to add an already existing value
+ sourceJson.Add(SpecialJsonValueHelper.GetUniqueNonNullInstanceOfString(seed + 3, sourceJson), SpecialJsonValueHelper.GetUniqueValue(seed, sourceJson));
+ KeyValuePair<string, JsonValue> kvp;
+ int startingSeed = seed + 1;
+ do
+ {
+ kvp = SpecialJsonValueHelper.CreatePrePopulatedKeyValuePair(startingSeed);
+ startingSeed++;
+ }
+ while (sourceJson.ContainsKey(kvp.Key));
+
+ sourceJson.Add(kvp);
+ do
+ {
+ kvp = SpecialJsonValueHelper.CreatePrePopulatedKeyValuePair(startingSeed);
+ startingSeed++;
+ }
+ while (sourceJson.ContainsKey(kvp.Key));
+
+ sourceJson.Add(kvp);
+ if (sourceJson.Count != arrayLength + 3)
+ {
+ Log.Info("[JsonObjectAddRemoveFunctionalTest] JsonObject.Add() failed to function properly.");
+ retValue = false;
+ }
+ else
+ {
+ Log.Info("[JsonObjectAddRemoveFunctionalTest] JsonObject.Add() passed test.");
+ }
+
+ // JsonObject.Clear()
+ sourceJson.Clear();
+ if (sourceJson.Count > 0)
+ {
+ Log.Info("[JsonObjectAddRemoveFunctionalTest] JsonObject.Clear() failed to function properly.");
+ retValue = false;
+ }
+ else
+ {
+ Log.Info("[JsonObjectAddRemoveFunctionalTest] JsonObject.Clear() passed test.");
+ }
+
+ // JsonObject.AddRange(IEnumerable<KeyValuePair<string, JsonValue>> items)
+ sourceJson = SpecialJsonValueHelper.CreateIndexPopulatedJsonObject(seed, arrayLength);
+
+ // + various numers below so .AddRange() won't try to add an already existing value
+ sourceJson.AddRange(SpecialJsonValueHelper.CreatePrePopulatedListofKeyValuePair(seed + 13 + (arrayLength * 2), 5));
+ if (sourceJson.Count != arrayLength + 5)
+ {
+ Log.Info("[JsonObjectAddRemoveFunctionalTest] JsonObject.AddRange(IEnumerable<KeyValuePair<string, JsonValue>> items) failed to function properly.");
+ retValue = false;
+ }
+ else
+ {
+ Log.Info("[JsonObjectAddRemoveFunctionalTest] JsonObject.AddRange(IEnumerable<KeyValuePair<string, JsonValue>> items) passed test.");
+ }
+
+ // JsonObject.AddRange(params KeyValuePair<string, JsonValue>[] items)
+ sourceJson = SpecialJsonValueHelper.CreateIndexPopulatedJsonObject(seed, arrayLength);
+
+ // + various numers below so .AddRange() won't try to add an already existing value
+ KeyValuePair<string, JsonValue> item1 = SpecialJsonValueHelper.CreatePrePopulatedKeyValuePair(seed + arrayLength + 41);
+ KeyValuePair<string, JsonValue> item2 = SpecialJsonValueHelper.CreatePrePopulatedKeyValuePair(seed + arrayLength + 47);
+ KeyValuePair<string, JsonValue> item3 = SpecialJsonValueHelper.CreatePrePopulatedKeyValuePair(seed + arrayLength + 53);
+ sourceJson.AddRange(new KeyValuePair<string, JsonValue>[] { item1, item2, item3 });
+ if (sourceJson.Count != arrayLength + 3)
+ {
+ Log.Info("[JsonObjectAddRemoveFunctionalTest] JsonObject.AddRange(params KeyValuePair<string, JsonValue>[] items) failed to function properly.");
+ retValue = false;
+ }
+ else
+ {
+ Log.Info("[JsonObjectAddRemoveFunctionalTest] JsonObject.AddRange(params KeyValuePair<string, JsonValue>[] items) passed test.");
+ }
+
+ sourceJson.Clear();
+
+ // JsonObject.Remove(Key)
+ sourceJson = SpecialJsonValueHelper.CreateIndexPopulatedJsonObject(seed, arrayLength);
+ int count = sourceJson.Count;
+ List<string> keys = new List<string>(sourceJson.Keys);
+ foreach (string key in keys)
+ {
+ sourceJson.Remove(key);
+ }
+
+ if (sourceJson.Count > 0)
+ {
+ Log.Info("[JsonObjectAddRemoveFunctionalTest] JsonObject.Remove(Key) failed to function properly.");
+ retValue = false;
+ }
+ else
+ {
+ Log.Info("[JsonObjectAddRemoveFunctionalTest] JsonObject.Remove(Key) passed test.");
+ }
+
+ Assert.True(retValue);
+ }
+ }
+
+ /// <summary>
+ /// Tests for the indexers in the <see cref="System.Json.JsonObject"/> class.
+ /// </summary>
+ [Fact]
+ public void JsonObjectItemsFunctionalTest()
+ {
+ int seed = 1;
+
+ for (int i = 0; i < iterationCount / 10; i++)
+ {
+ seed++;
+ Log.Info("Seed: {0}", seed);
+ Random rndGen = new Random(seed);
+ bool retValue = true;
+
+ JsonObject sourceJson = SpecialJsonValueHelper.CreateIndexPopulatedJsonObject(seed, arrayLength);
+
+ // JsonObject[key].set_Item
+ sourceJson["1"] = new JsonPrimitive(true);
+ if (sourceJson["1"].ToString() != "true")
+ {
+ Log.Info("[JsonObjectItemsFunctionalTest] JsonObject[key].set_Item failed to function properly.");
+ retValue = false;
+ }
+ else
+ {
+ Log.Info("[JsonObjectItemsFunctionalTest] JsonObject[key].set_Item passed test.");
+ }
+
+ // ICollection<KeyValuePair<string, JsonValue>>.Contains(KeyValuePair<string, JsonValue> item)
+ KeyValuePair<string, System.Json.JsonValue> kp = new KeyValuePair<string, JsonValue>("5", sourceJson["5"]);
+ if (!((ICollection<KeyValuePair<string, JsonValue>>)sourceJson).Contains(kp))
+ {
+ Log.Info("[JsonObjectItemsFunctionalTest] ICollection<KeyValuePair<string, JsonValue>>.Contains(KeyValuePair<string, JsonValue> item) failed to function properly.");
+ retValue = false;
+ }
+ else
+ {
+ Log.Info("[JsonObjectItemsFunctionalTest] ICollection<KeyValuePair<string, JsonValue>>.Contains(KeyValuePair<string, JsonValue> item) passed test.");
+ }
+
+ // ICollection<KeyValuePair<string, JsonValue>>.IsReadOnly
+ if (((ICollection<KeyValuePair<string, JsonValue>>)sourceJson).IsReadOnly)
+ {
+ Log.Info("[JsonObjectItemsFunctionalTest] ICollection<KeyValuePair<string, JsonValue>>.IsReadOnly failed to function properly.");
+ retValue = false;
+ }
+ else
+ {
+ Log.Info("[JsonObjectItemsFunctionalTest] ICollection<KeyValuePair<string, JsonValue>>.IsReadOnly passed test.");
+ }
+
+ // ICollection<KeyValuePair<string, JsonValue>>.Add(KeyValuePair<string, JsonValue> item)
+ kp = new KeyValuePair<string, JsonValue>("100", new JsonPrimitive(100));
+ ((ICollection<KeyValuePair<string, JsonValue>>)sourceJson).Add(kp);
+ if (sourceJson.Count != arrayLength + 1)
+ {
+ Log.Info("[JsonObjectItemsFunctionalTest] ICollection<KeyValuePair<string, JsonValue>>.Add(KeyValuePair<string, JsonValue> item) failed to function properly.");
+ retValue = false;
+ }
+ else
+ {
+ Log.Info("[JsonObjectItemsFunctionalTest] ICollection<KeyValuePair<string, JsonValue>>.Add(KeyValuePair<string, JsonValue> item) passed test.");
+ }
+
+ // ICollection<KeyValuePair<string, JsonValue>>.Remove(KeyValuePair<string, JsonValue> item)
+ ((ICollection<KeyValuePair<string, JsonValue>>)sourceJson).Remove(kp);
+ if (sourceJson.Count != arrayLength)
+ {
+ Log.Info("[JsonObjectItemsFunctionalTest] ICollection<KeyValuePair<string, JsonValue>>.Remove(KeyValuePair<string, JsonValue> item) failed to function properly.");
+ retValue = false;
+ }
+ else
+ {
+ Log.Info("[JsonObjectItemsFunctionalTest] ICollection<KeyValuePair<string, JsonValue>>.Remove(KeyValuePair<string, JsonValue> item) passed test.");
+ }
+
+ // ICollection<KeyValuePair<string, JsonValue>>.GetEnumerator()
+ JsonObject jo = new JsonObject { { "member 1", 123 }, { "member 2", new JsonArray { 1, 2, 3 } } };
+ List<string> expected = new List<string> { "member 1 - 123", "member 2 - [1,2,3]" };
+ expected.Sort();
+ IEnumerator<KeyValuePair<string, JsonValue>> ko = ((ICollection<KeyValuePair<string, JsonValue>>)jo).GetEnumerator();
+ List<string> actual = new List<string>();
+ ko.Reset();
+ ko.MoveNext();
+ do
+ {
+ actual.Add(String.Format("{0} - {1}", ko.Current.Key, ko.Current.Value.ToString()));
+ Log.Info("added one item: {0}", String.Format("{0} - {1}", ko.Current.Key, ko.Current.Value));
+ ko.MoveNext();
+ }
+ while (ko.Current.Value != null);
+
+ actual.Sort();
+ if (!JsonValueVerifier.CompareStringLists(expected, actual))
+ {
+ Log.Info("[JsonObjectItemsFunctionalTest] ICollection<KeyValuePair<string, JsonValue>>.GetEnumerator() failed to function properly.");
+ retValue = false;
+ }
+ else
+ {
+ Log.Info("[JsonObjectItemsFunctionalTest] ICollection<KeyValuePair<string, JsonValue>>.GetEnumerator() passed test.");
+ }
+
+ // JsonObject.Values
+ sourceJson = SpecialJsonValueHelper.CreateIndexPopulatedJsonObject(seed, arrayLength);
+ JsonValue[] manyValues = SpecialJsonValueHelper.CreatePrePopulatedJsonValueArray(seed, arrayLength);
+ JsonObject jov = new JsonObject();
+ for (int j = 0; j < manyValues.Length; j++)
+ {
+ jov.Add("member" + j, manyValues[j]);
+ }
+
+ List<string> expectedList = new List<string>();
+ foreach (JsonValue v in manyValues)
+ {
+ expectedList.Add(v.ToString());
+ }
+
+ expectedList.Sort();
+ List<string> actualList = new List<string>();
+ foreach (JsonValue v in jov.Values)
+ {
+ actualList.Add(v.ToString());
+ }
+
+ actualList.Sort();
+ if (!JsonValueVerifier.CompareStringLists(expectedList, actualList))
+ {
+ Log.Info("[JsonObjectItemsFunctionalTest] JsonObject.Values failed to function properly.");
+ retValue = false;
+ }
+ else
+ {
+ Log.Info("[JsonObjectItemsFunctionalTest] JsonObject.Values passed test.");
+ }
+
+ for (int j = 0; j < sourceJson.Count; j++)
+ {
+ // JsonObject.Contains(Key)
+ if (!sourceJson.ContainsKey(j.ToString()))
+ {
+ Log.Info("[JsonObjectItemsFunctionalTest] JsonObject.Contains(Key) failed to function properly.");
+ retValue = false;
+ }
+ else
+ {
+ Log.Info("[JsonObjectItemsFunctionalTest] JsonObject.Contains(Key) passed test.");
+ }
+
+ // JsonObject.TryGetValue(String, out JsonValue)
+ JsonValue retJson;
+ if (!sourceJson.TryGetValue(j.ToString(), out retJson))
+ {
+ Log.Info("[JsonObjectItemsFunctionalTest] JsonObject.TryGetValue(String, out JsonValue) failed to function properly.");
+ retValue = false;
+ }
+ else if (retJson != sourceJson[j.ToString()])
+ {
+ // JsonObjectthis[string key]
+ Log.Info("[JsonObjectItemsFunctionalTest] JsonObject[string key] or JsonObject.TryGetValue(String, out JsonValue) failed to function properly.");
+ retValue = false;
+ }
+ else
+ {
+ Log.Info("[JsonObjectItemsFunctionalTest] JsonObject.TryGetValue(String, out JsonValue) & JsonObject[string key] passed test.");
+ }
+ }
+
+ Assert.True(retValue);
+ }
+ }
+
+ /// <summary>
+ /// Tests for casting to integer values.
+ /// </summary>
+ [Fact]
+ public void GettingIntegerValueTest()
+ {
+ string json = "{\"byte\":160,\"sbyte\":-89,\"short\":12345,\"ushort\":65530," +
+ "\"int\":1234567890,\"uint\":3000000000,\"long\":1234567890123456," +
+ "\"ulong\":10000000000000000000}";
+ Dictionary<string, object> expected = new Dictionary<string, object>();
+ expected.Add("byte", (byte)160);
+ expected.Add("sbyte", (sbyte)-89);
+ expected.Add("short", (short)12345);
+ expected.Add("ushort", (ushort)65530);
+ expected.Add("int", (int)1234567890);
+ expected.Add("uint", (uint)3000000000);
+ expected.Add("long", (long)1234567890123456L);
+ expected.Add("ulong", (((ulong)5000000000000000000L) * 2));
+ JsonObject jo = (JsonObject)JsonValue.Parse(json);
+ bool success = true;
+ foreach (string key in jo.Keys)
+ {
+ object expectedObj = expected[key];
+ Log.Info("Testing for type = {0}", key);
+ try
+ {
+ switch (key)
+ {
+ case "byte":
+ Assert.Equal<byte>((byte)expectedObj, (byte)jo[key]);
+ break;
+ case "sbyte":
+ Assert.Equal<sbyte>((sbyte)expectedObj, (sbyte)jo[key]);
+ break;
+ case "short":
+ Assert.Equal<short>((short)expectedObj, (short)jo[key]);
+ break;
+ case "ushort":
+ Assert.Equal<ushort>((ushort)expectedObj, (ushort)jo[key]);
+ break;
+ case "int":
+ Assert.Equal<int>((int)expectedObj, (int)jo[key]);
+ break;
+ case "uint":
+ Assert.Equal<uint>((uint)expectedObj, (uint)jo[key]);
+ break;
+ case "long":
+ Assert.Equal<long>((long)expectedObj, (long)jo[key]);
+ break;
+ case "ulong":
+ Assert.Equal<ulong>((ulong)expectedObj, (ulong)jo[key]);
+ break;
+ }
+ }
+ catch (InvalidCastException e)
+ {
+ Log.Info("Caught InvalidCastException: {0}", e);
+ success = false;
+ }
+ }
+
+ Assert.True(success);
+ }
+
+ /// <summary>
+ /// Tests for casting to floating point values.
+ /// </summary>
+ [Fact]
+ public void GettingFloatingPointValueTest()
+ {
+ string json = "{\"float\":1.23,\"double\":1.23e+290,\"decimal\":1234567890.123456789}";
+ Dictionary<string, object> expected = new Dictionary<string, object>();
+ expected.Add("float", 1.23f);
+ expected.Add("double", 1.23e+290);
+ expected.Add("decimal", 1234567890.123456789m);
+ JsonObject jo = (JsonObject)JsonValue.Parse(json);
+ bool success = true;
+ foreach (string key in jo.Keys)
+ {
+ object expectedObj = expected[key];
+ Log.Info("Testing for type = {0}", key);
+ try
+ {
+ switch (key)
+ {
+ case "float":
+ Assert.Equal<float>((float)expectedObj, (float)jo[key]);
+ break;
+ case "double":
+ Assert.Equal<double>((double)expectedObj, (double)jo[key]);
+ break;
+ case "decimal":
+ Assert.Equal<decimal>((decimal)expectedObj, (decimal)jo[key]);
+ break;
+ }
+ }
+ catch (InvalidCastException e)
+ {
+ Log.Info("Caught InvalidCastException: {0}", e);
+ success = false;
+ }
+ }
+
+ Assert.True(success);
+ }
+
+ /// <summary>
+ /// Negative tests for invalid operations.
+ /// </summary>
+ [Fact]
+ public void TestInvalidOperations()
+ {
+ JsonArray ja = new JsonArray { 1, null, "hello" };
+ JsonObject jo = new JsonObject
+ {
+ { "first", 1 },
+ { "second", null },
+ { "third", "hello" },
+ };
+ JsonPrimitive jp = new JsonPrimitive("hello");
+
+ Assert.Throws<InvalidOperationException>(() => "jp[\"hello\"] should fail: " + jp["hello"].ToString());
+
+ Assert.Throws<InvalidOperationException>(() => "ja[\"hello\"] should fail: " + ja["hello"].ToString());
+
+
+ Assert.Throws<InvalidOperationException>(() => jp["hello"] = "This shouldn't happen");
+
+
+ Assert.Throws<InvalidOperationException>(() => ja["hello"] = "This shouldn't happen");
+
+ Assert.Throws<InvalidOperationException>(() => ("jp[1] should fail: " + jp[1].ToString()));
+
+ Assert.Throws<InvalidOperationException>(() => "jo[0] should fail: " + jo[1].ToString());
+
+ Assert.Throws<InvalidOperationException>(() => jp[0] = "This shouldn't happen");
+
+ Assert.Throws<InvalidOperationException>(() => jo[0] = "This shouldn't happen");
+
+ Assert.Throws<InvalidCastException>(() => "(DateTimeOffset)jp[\"hello\"] should fail: " + (DateTimeOffset)jp);
+
+ Assert.Throws<InvalidCastException>(() => ("(Char)jp[\"hello\"] should fail: " + (char)jp));
+
+ Assert.Throws<InvalidCastException>(() =>
+ {
+ short jprim = (short)new JsonPrimitive(false);
+ });
+ }
+
+ /// <summary>
+ /// Test for consuming deeply nested object graphs.
+ /// </summary>
+ [Fact]
+ public void TestDeeplyNestedObjectGraph()
+ {
+ JsonObject jo = new JsonObject();
+ JsonObject current = jo;
+ StringBuilder builderExpected = new StringBuilder();
+ builderExpected.Append('{');
+ int depth = 10000;
+ for (int i = 0; i < depth; i++)
+ {
+ JsonObject next = new JsonObject();
+ string key = i.ToString(CultureInfo.InvariantCulture);
+ builderExpected.AppendFormat("\"{0}\":{{", key);
+ current.Add(key, next);
+ current = next;
+ }
+
+ for (int i = 0; i < depth + 1; i++)
+ {
+ builderExpected.Append('}');
+ }
+
+ Assert.Equal(builderExpected.ToString(), jo.ToString());
+ }
+
+ /// <summary>
+ /// Test for consuming deeply nested array graphs.
+ /// </summary>
+ [Fact]
+ public void TestDeeplyNestedArrayGraph()
+ {
+ JsonArray ja = new JsonArray();
+ JsonArray current = ja;
+ StringBuilder builderExpected = new StringBuilder();
+ builderExpected.Append('[');
+ int depth = 10000;
+ for (int i = 0; i < depth; i++)
+ {
+ JsonArray next = new JsonArray();
+ builderExpected.Append('[');
+ current.Add(next);
+ current = next;
+ }
+
+ for (int i = 0; i < depth + 1; i++)
+ {
+ builderExpected.Append(']');
+ }
+
+ Assert.Equal(builderExpected.ToString(), ja.ToString());
+ }
+
+ /// <summary>
+ /// Test for consuming deeply nested object and array graphs.
+ /// </summary>
+ [Fact]
+ public void TestDeeplyNestedObjectAndArrayGraph()
+ {
+ JsonObject jo = new JsonObject();
+ JsonObject current = jo;
+ StringBuilder builderExpected = new StringBuilder();
+ builderExpected.Append('{');
+ int depth = 10000;
+ for (int i = 0; i < depth; i++)
+ {
+ JsonObject next = new JsonObject();
+ string key = i.ToString(CultureInfo.InvariantCulture);
+ builderExpected.AppendFormat("\"{0}\":[{{", key);
+ current.Add(key, new JsonArray(next));
+ current = next;
+ }
+
+ for (int i = 0; i < depth; i++)
+ {
+ builderExpected.Append("}]");
+ }
+
+ builderExpected.Append('}');
+
+ Assert.Equal(builderExpected.ToString(), jo.ToString());
+ }
+
+ /// <summary>
+ /// Test for calling <see cref="JsonValue.ToString()"/> on the same instance in different threads.
+ /// </summary>
+ [Fact]
+ public void TestConcurrentToString()
+ {
+ bool exceptionThrown = false;
+ bool incorrectValue = false;
+ JsonObject jo = new JsonObject();
+ StringBuilder sb = new StringBuilder();
+ sb.Append('{');
+ for (int i = 0; i < 100000; i++)
+ {
+ if (i > 0)
+ {
+ sb.Append(',');
+ }
+
+ string key = i.ToString(CultureInfo.InvariantCulture);
+ jo.Add(key, i);
+ sb.AppendFormat("\"{0}\":{0}", key);
+ }
+
+ sb.Append('}');
+ string expected = sb.ToString();
+
+ int numberOfThreads = 5;
+ Thread[] threads = new Thread[numberOfThreads];
+ for (int i = 0; i < numberOfThreads; i++)
+ {
+ threads[i] = new Thread(new ThreadStart(delegate
+ {
+ for (int j = 0; j < 10; j++)
+ {
+ try
+ {
+ string str = jo.ToString();
+ if (str != expected)
+ {
+ incorrectValue = true;
+ Log.Info("Value is incorrect");
+ }
+ }
+ catch (Exception e)
+ {
+ exceptionThrown = true;
+ Log.Info("Exception thrown: {0}", e);
+ }
+ }
+ }));
+ }
+
+ for (int i = 0; i < numberOfThreads; i++)
+ {
+ threads[i].Start();
+ }
+
+ for (int i = 0; i < numberOfThreads; i++)
+ {
+ threads[i].Join();
+ }
+
+ Assert.False(incorrectValue);
+ Assert.False(exceptionThrown);
+ }
+
+ class MyJsonValueCollection<JsonValue> : System.Collections.Generic.IEnumerable<JsonValue>
+ {
+ List<JsonValue> internalList = new List<JsonValue>();
+
+ public MyJsonValueCollection()
+ {
+ }
+
+ public void Add(JsonValue obj)
+ {
+ this.internalList.Add(obj);
+ }
+
+ public IEnumerator<JsonValue> GetEnumerator()
+ {
+ return this.internalList.GetEnumerator();
+ }
+
+ System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
+ {
+ return this.GetEnumerator();
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/test/System.Json.Test.Integration/JsonPrimitiveTests.cs b/test/System.Json.Test.Integration/JsonPrimitiveTests.cs
new file mode 100644
index 00000000..5ffcb89c
--- /dev/null
+++ b/test/System.Json.Test.Integration/JsonPrimitiveTests.cs
@@ -0,0 +1,1068 @@
+using System.Collections.Generic;
+using System.Globalization;
+using System.IO;
+using System.Reflection;
+using System.Runtime.Serialization.Json;
+using System.Text;
+using Xunit;
+
+namespace System.Json
+{
+ /// <summary>
+ /// JsonPrimitive unit tests
+ /// </summary>
+ public class JsonPrimitiveTests
+ {
+ const string DateTimeFormat = "yyyy-MM-ddTHH:mm:ss.fffK";
+
+ /// <summary>
+ /// Validates round-trip of <see cref="JsonPrimitive"/> values created from <see cref="Int16"/> values.
+ /// </summary>
+ [Fact]
+ public void JsonPrimitiveFromInt16()
+ {
+ short[] values = new short[] { Int16.MinValue, Int16.MaxValue, 1 };
+ for (int i = 0; i < values.Length; i++)
+ {
+ this.ValidateJson(new JsonPrimitive(values[i]), GetExpectedRepresentation(values[i]), JsonType.Number);
+ this.TestReadAsRoundtrip<short>(new JsonPrimitive(values[i]), values[i]);
+ }
+ }
+
+ /// <summary>
+ /// Validates round-trip of <see cref="JsonPrimitive"/> values created from <see cref="Int32"/> values.
+ /// </summary>
+ [Fact]
+ public void JsonPrimitiveFromInt32()
+ {
+ int[] values = new int[] { Int32.MinValue, Int32.MaxValue, 12345678 };
+ for (int i = 0; i < values.Length; i++)
+ {
+ this.ValidateJson(new JsonPrimitive(values[i]), GetExpectedRepresentation(values[i]), JsonType.Number);
+ this.TestReadAsRoundtrip<int>(new JsonPrimitive(values[i]), values[i]);
+ }
+ }
+
+ /// <summary>
+ /// Validates round-trip of <see cref="JsonPrimitive"/> values created from <see cref="Int64"/> values.
+ /// </summary>
+ [Fact]
+ public void JsonPrimitiveFromInt64()
+ {
+ long[] values = new long[] { Int64.MinValue, Int64.MaxValue, 12345678901232L };
+ for (int i = 0; i < values.Length; i++)
+ {
+ this.ValidateJson(new JsonPrimitive(values[i]), GetExpectedRepresentation(values[i]), JsonType.Number);
+ this.TestReadAsRoundtrip<long>(new JsonPrimitive(values[i]), values[i]);
+ }
+ }
+
+ /// <summary>
+ /// Validates round-trip of <see cref="JsonPrimitive"/> values created from <see cref="UInt64"/> values.
+ /// </summary>
+ [Fact]
+ public void JsonPrimitiveFromUInt64()
+ {
+ ulong[] values = new ulong[] { UInt64.MinValue, UInt64.MaxValue, 12345678901232L };
+ for (int i = 0; i < values.Length; i++)
+ {
+ this.ValidateJson(new JsonPrimitive(values[i]), GetExpectedRepresentation(values[i]), JsonType.Number);
+ this.TestReadAsRoundtrip<ulong>(new JsonPrimitive(values[i]), values[i]);
+ }
+ }
+
+ /// <summary>
+ /// Validates round-trip of <see cref="JsonPrimitive"/> values created from <see cref="UInt32"/> values.
+ /// </summary>
+ [Fact]
+ public void JsonPrimitiveFromUInt32()
+ {
+ uint[] values = new uint[] { UInt32.MinValue, UInt32.MaxValue, 3234567890 };
+ for (int i = 0; i < values.Length; i++)
+ {
+ this.ValidateJson(new JsonPrimitive(values[i]), GetExpectedRepresentation(values[i]), JsonType.Number);
+ this.TestReadAsRoundtrip<uint>(new JsonPrimitive(values[i]), values[i]);
+ }
+ }
+
+ /// <summary>
+ /// Validates round-trip of <see cref="JsonPrimitive"/> values created from <see cref="UInt16"/> values.
+ /// </summary>
+ [Fact]
+ public void JsonPrimitiveFromUInt16()
+ {
+ ushort[] values = new ushort[] { UInt16.MinValue, UInt16.MaxValue, 33333 };
+ for (int i = 0; i < values.Length; i++)
+ {
+ this.ValidateJson(new JsonPrimitive(values[i]), GetExpectedRepresentation(values[i]), JsonType.Number);
+ this.TestReadAsRoundtrip<ushort>(new JsonPrimitive(values[i]), values[i]);
+ }
+ }
+
+ /// <summary>
+ /// Validates round-trip of <see cref="JsonPrimitive"/> values created from <see cref="Byte"/> values.
+ /// </summary>
+ [Fact]
+ public void JsonPrimitiveFromByte()
+ {
+ byte[] values = new byte[] { Byte.MinValue, Byte.MaxValue, 0x83 };
+ for (int i = 0; i < values.Length; i++)
+ {
+ this.ValidateJson(new JsonPrimitive(values[i]), GetExpectedRepresentation(values[i]), JsonType.Number);
+ this.TestReadAsRoundtrip<byte>(new JsonPrimitive(values[i]), values[i]);
+ }
+ }
+
+ /// <summary>
+ /// Validates round-trip of <see cref="JsonPrimitive"/> values created from <see cref="SByte"/> values.
+ /// </summary>
+ [Fact]
+ public void JsonPrimitiveFromSByte()
+ {
+ sbyte[] values = new sbyte[] { SByte.MinValue, SByte.MaxValue, -0x33 };
+ for (int i = 0; i < values.Length; i++)
+ {
+ this.ValidateJson(new JsonPrimitive(values[i]), GetExpectedRepresentation(values[i]), JsonType.Number);
+ this.TestReadAsRoundtrip<sbyte>(new JsonPrimitive(values[i]), values[i]);
+ }
+ }
+
+ /// <summary>
+ /// Validates round-trip of <see cref="JsonPrimitive"/> values created from <see cref="Single"/> values.
+ /// </summary>
+ [Fact]
+ public void JsonPrimitiveFromFloat()
+ {
+ float[] values = new float[] { float.MinValue, float.MaxValue, 1.234f, float.PositiveInfinity, float.NegativeInfinity, float.NaN };
+ for (int i = 0; i < values.Length; i++)
+ {
+ this.ValidateJson(new JsonPrimitive(values[i]), GetExpectedRepresentation(values[i]), JsonType.Number);
+ this.TestReadAsRoundtrip<float>(new JsonPrimitive(values[i]), values[i]);
+ }
+ }
+
+ /// <summary>
+ /// Validates round-trip of <see cref="JsonPrimitive"/> values created from <see cref="Double"/> values.
+ /// </summary>
+ [Fact]
+ public void JsonPrimitiveFromDouble()
+ {
+ double[] values = new double[] { double.MinValue, double.MaxValue, 1.234, double.PositiveInfinity, double.NegativeInfinity, double.NaN };
+ for (int i = 0; i < values.Length; i++)
+ {
+ this.ValidateJson(new JsonPrimitive(values[i]), GetExpectedRepresentation(values[i]), JsonType.Number);
+ this.TestReadAsRoundtrip<double>(new JsonPrimitive(values[i]), values[i]);
+ }
+ }
+
+ /// <summary>
+ /// Validates round-trip of <see cref="JsonPrimitive"/> values created from <see cref="Decimal"/> values.
+ /// </summary>
+ [Fact]
+ public void JsonPrimitiveFromDecimal()
+ {
+ decimal[] values = new decimal[] { decimal.MinValue, decimal.MaxValue, 123456789.123456789m };
+ for (int i = 0; i < values.Length; i++)
+ {
+ this.ValidateJson(new JsonPrimitive(values[i]), GetExpectedRepresentation(values[i]), JsonType.Number);
+ this.TestReadAsRoundtrip<decimal>(new JsonPrimitive(values[i]), values[i]);
+ }
+ }
+
+ /// <summary>
+ /// Validates round-trip of <see cref="JsonPrimitive"/> values created from <see cref="Boolean"/> values.
+ /// </summary>
+ [Fact]
+ public void JsonPrimitiveFromBoolean()
+ {
+ bool[] values = new bool[] { true, false };
+ for (int i = 0; i < values.Length; i++)
+ {
+ this.ValidateJson(new JsonPrimitive(values[i]), GetExpectedRepresentation(values[i]), JsonType.Boolean);
+ this.TestReadAsRoundtrip<bool>(new JsonPrimitive(values[i]), values[i]);
+ }
+ }
+
+ /// <summary>
+ /// Validates round-trip of <see cref="JsonPrimitive"/> values created from <see cref="Char"/> values.
+ /// </summary>
+ [Fact]
+ public void JsonPrimitiveFromChar()
+ {
+ char[] values = new char[] { 'H', '\0', '\uffff' };
+ for (int i = 0; i < values.Length; i++)
+ {
+ this.ValidateJson(new JsonPrimitive(values[i]), GetExpectedRepresentation(values[i]), JsonType.String);
+ this.TestReadAsRoundtrip<char>(new JsonPrimitive(values[i]), values[i]);
+ }
+ }
+
+ /// <summary>
+ /// Validates round-trip of <see cref="JsonPrimitive"/> values created from <see cref="String"/> values.
+ /// </summary>
+ [Fact]
+ public void JsonPrimitiveFromString()
+ {
+ string[] values = new string[] { "Hello", "abcdef", "\r\t123\n32" };
+ for (int i = 0; i < values.Length; i++)
+ {
+ this.ValidateJson(new JsonPrimitive(values[i]), GetExpectedRepresentation(values[i]), JsonType.String);
+ this.TestReadAsRoundtrip<string>(new JsonPrimitive(values[i]), values[i]);
+ }
+ }
+
+ /// <summary>
+ /// Validates round-trip of <see cref="JsonPrimitive"/> values created from <see cref="DateTime"/> values.
+ /// </summary>
+ [Fact]
+ public void JsonPrimitiveFromDateTime()
+ {
+ DateTime[] values = new DateTime[]
+ {
+ new DateTime(2000, 10, 16, 8, 0, 0, DateTimeKind.Utc),
+ new DateTime(2000, 10, 16, 8, 0, 0, DateTimeKind.Local),
+ };
+ for (int i = 0; i < values.Length; i++)
+ {
+ this.ValidateJson(new JsonPrimitive(values[i]), GetExpectedRepresentation(values[i]), JsonType.String);
+ this.TestReadAsRoundtrip<DateTime>(new JsonPrimitive(values[i]), values[i]);
+ }
+ }
+
+ /// <summary>
+ /// Validates round-trip of <see cref="JsonPrimitive"/> values created from <see cref="Uri"/> values.
+ /// </summary>
+ [Fact]
+ public void JsonPrimitiveFromUri()
+ {
+ Uri[] values = new Uri[] { new Uri("http://tempuri.org"), new Uri("foo/bar", UriKind.Relative) };
+ for (int i = 0; i < values.Length; i++)
+ {
+ this.ValidateJson(new JsonPrimitive(values[i]), GetExpectedRepresentation(values[i]), JsonType.String);
+ this.TestReadAsRoundtrip<Uri>(new JsonPrimitive(values[i]), values[i]);
+ }
+ }
+
+ /// <summary>
+ /// Validates round-trip of <see cref="JsonPrimitive"/> values created from <see cref="Guid"/> values.
+ /// </summary>
+ [Fact]
+ public void JsonPrimitiveFromGuid()
+ {
+ Guid[] values = new Guid[] { Guid.NewGuid(), Guid.Empty, Guid.NewGuid() };
+ for (int i = 0; i < values.Length; i++)
+ {
+ this.ValidateJson(new JsonPrimitive(values[i]), GetExpectedRepresentation(values[i]), JsonType.String);
+ this.TestReadAsRoundtrip<Guid>(new JsonPrimitive(values[i]), values[i]);
+ }
+ }
+
+ /// <summary>
+ /// Validates round-trip of <see cref="JsonPrimitive"/> values created from different types of values.
+ /// </summary>
+ [Fact]
+ public void JsonPrimitiveFromObject()
+ {
+ List<KeyValuePair<object, JsonType>> values = new List<KeyValuePair<object, JsonType>>
+ {
+ new KeyValuePair<object, JsonType>(true, JsonType.Boolean),
+ new KeyValuePair<object, JsonType>((short)1, JsonType.Number),
+ new KeyValuePair<object, JsonType>(234, JsonType.Number),
+ new KeyValuePair<object, JsonType>(3435434233443L, JsonType.Number),
+ new KeyValuePair<object, JsonType>(UInt64.MaxValue, JsonType.Number),
+ new KeyValuePair<object, JsonType>(UInt32.MaxValue, JsonType.Number),
+ new KeyValuePair<object, JsonType>(UInt16.MaxValue, JsonType.Number),
+ new KeyValuePair<object, JsonType>(Byte.MaxValue, JsonType.Number),
+ new KeyValuePair<object, JsonType>(SByte.MinValue, JsonType.Number),
+ new KeyValuePair<object, JsonType>(double.MaxValue, JsonType.Number),
+ new KeyValuePair<object, JsonType>(float.Epsilon, JsonType.Number),
+ new KeyValuePair<object, JsonType>(decimal.MinusOne, JsonType.Number),
+ new KeyValuePair<object, JsonType>("hello", JsonType.String),
+ new KeyValuePair<object, JsonType>(Guid.NewGuid(), JsonType.String),
+ new KeyValuePair<object, JsonType>(DateTime.UtcNow, JsonType.String),
+ new KeyValuePair<object, JsonType>(new Uri("http://www.microsoft.com"), JsonType.String),
+ };
+
+ foreach (var value in values)
+ {
+ string json = GetExpectedRepresentation(value.Key);
+ JsonValue jsonValue = JsonValue.Parse(json);
+ Assert.IsType(typeof(JsonPrimitive), jsonValue);
+ this.ValidateJson((JsonPrimitive)jsonValue, json, value.Value);
+ }
+ }
+
+ /// <summary>
+ /// Negative tests for <see cref="JsonPrimitive"/> constructors with null values.
+ /// </summary>
+ [Fact]
+ public void NullChecks()
+ {
+ ExpectException<ArgumentNullException>(() => new JsonPrimitive((string)null));
+ ExpectException<ArgumentNullException>(() => new JsonPrimitive((Uri)null));
+ }
+
+ /// <summary>
+ /// Tests for casting string values into non-string values.
+ /// </summary>
+ [Fact]
+ public void CastingFromStringTests()
+ {
+ int seed = MethodBase.GetCurrentMethod().Name.GetHashCode();
+ Random rndGen = new Random(seed);
+
+ Assert.Equal(false, (bool)(new JsonPrimitive("false")));
+ Assert.Equal(false, (bool)(new JsonPrimitive("False")));
+ Assert.Equal(true, (bool)(new JsonPrimitive("true")));
+ Assert.Equal(true, (bool)(new JsonPrimitive("True")));
+
+ byte b = PrimitiveCreator.CreateInstanceOfByte(rndGen);
+ Assert.Equal(b, (byte)(new JsonPrimitive(b.ToString(CultureInfo.InvariantCulture))));
+
+ decimal dec = PrimitiveCreator.CreateInstanceOfDecimal(rndGen);
+ Assert.Equal(dec, (decimal)(new JsonPrimitive(dec.ToString(CultureInfo.InvariantCulture))));
+
+ double dbl = rndGen.NextDouble() * rndGen.Next();
+ Assert.Equal(dbl, (double)(new JsonPrimitive(dbl.ToString("R", CultureInfo.InvariantCulture))));
+
+ Assert.Equal(Double.PositiveInfinity, (double)(new JsonPrimitive("Infinity")));
+ Assert.Equal(Double.NegativeInfinity, (double)(new JsonPrimitive("-Infinity")));
+ Assert.Equal(Double.NaN, (double)(new JsonPrimitive("NaN")));
+
+ ExpectException<InvalidCastException>(delegate { var d = (double)(new JsonPrimitive("INF")); });
+ ExpectException<InvalidCastException>(delegate { var d = (double)(new JsonPrimitive("-INF")); });
+ ExpectException<InvalidCastException>(delegate { var d = (double)(new JsonPrimitive("infinity")); });
+ ExpectException<InvalidCastException>(delegate { var d = (double)(new JsonPrimitive("INFINITY")); });
+ ExpectException<InvalidCastException>(delegate { var d = (double)(new JsonPrimitive("nan")); });
+ ExpectException<InvalidCastException>(delegate { var d = (double)(new JsonPrimitive("Nan")); });
+
+ float flt = (float)(rndGen.NextDouble() * rndGen.Next());
+ Assert.Equal(flt, (float)(new JsonPrimitive(flt.ToString("R", CultureInfo.InvariantCulture))));
+
+ Assert.Equal(Single.PositiveInfinity, (float)(new JsonPrimitive("Infinity")));
+ Assert.Equal(Single.NegativeInfinity, (float)(new JsonPrimitive("-Infinity")));
+ Assert.Equal(Single.NaN, (float)(new JsonPrimitive("NaN")));
+
+ ExpectException<InvalidCastException>(delegate { var f = (float)(new JsonPrimitive("INF")); });
+ ExpectException<InvalidCastException>(delegate { var f = (float)(new JsonPrimitive("-INF")); });
+ ExpectException<InvalidCastException>(delegate { var f = (float)(new JsonPrimitive("infinity")); });
+ ExpectException<InvalidCastException>(delegate { var f = (float)(new JsonPrimitive("INFINITY")); });
+ ExpectException<InvalidCastException>(delegate { var f = (float)(new JsonPrimitive("nan")); });
+ ExpectException<InvalidCastException>(delegate { var f = (float)(new JsonPrimitive("Nan")); });
+
+ int i = PrimitiveCreator.CreateInstanceOfInt32(rndGen);
+ Assert.Equal(i, (int)(new JsonPrimitive(i.ToString(CultureInfo.InvariantCulture))));
+
+ long l = PrimitiveCreator.CreateInstanceOfInt64(rndGen);
+ Assert.Equal(l, (long)(new JsonPrimitive(l.ToString(CultureInfo.InvariantCulture))));
+
+ sbyte sb = PrimitiveCreator.CreateInstanceOfSByte(rndGen);
+ Assert.Equal(sb, (sbyte)(new JsonPrimitive(sb.ToString(CultureInfo.InvariantCulture))));
+
+ short s = PrimitiveCreator.CreateInstanceOfInt16(rndGen);
+ Assert.Equal(s, (short)(new JsonPrimitive(s.ToString(CultureInfo.InvariantCulture))));
+
+ ushort ui16 = PrimitiveCreator.CreateInstanceOfUInt16(rndGen);
+ Assert.Equal(ui16, (ushort)(new JsonPrimitive(ui16.ToString(CultureInfo.InvariantCulture))));
+
+ uint ui32 = PrimitiveCreator.CreateInstanceOfUInt32(rndGen);
+ Assert.Equal(ui32, (uint)(new JsonPrimitive(ui32.ToString(CultureInfo.InvariantCulture))));
+
+ ulong ui64 = PrimitiveCreator.CreateInstanceOfUInt64(rndGen);
+ Assert.Equal(ui64, (ulong)(new JsonPrimitive(ui64.ToString(CultureInfo.InvariantCulture))));
+ }
+
+ /// <summary>
+ /// Tests for casting <see cref="JsonPrimitive"/> created from special floating point values (infinity, NaN).
+ /// </summary>
+ [Fact]
+ public void CastingNumbersTest()
+ {
+ Assert.Equal(float.PositiveInfinity, (float)(new JsonPrimitive(double.PositiveInfinity)));
+ Assert.Equal(float.NegativeInfinity, (float)(new JsonPrimitive(double.NegativeInfinity)));
+ Assert.Equal(float.NaN, (float)(new JsonPrimitive(double.NaN)));
+
+ Assert.Equal(double.PositiveInfinity, (double)(new JsonPrimitive(float.PositiveInfinity)));
+ Assert.Equal(double.NegativeInfinity, (double)(new JsonPrimitive(float.NegativeInfinity)));
+ Assert.Equal(double.NaN, (double)(new JsonPrimitive(float.NaN)));
+ }
+
+ /// <summary>
+ /// Tests for the many formats which can be cast to a <see cref="DateTime"/>.
+ /// </summary>
+ [Fact]
+ public void CastingDateTimeTest()
+ {
+ int seed = MethodBase.GetCurrentMethod().Name.GetHashCode();
+ Random rndGen = new Random(seed);
+ DateTime dt = new DateTime(
+ rndGen.Next(1000, 3000), // year
+ rndGen.Next(1, 13), // month
+ rndGen.Next(1, 28), // day
+ rndGen.Next(0, 24), // hour
+ rndGen.Next(0, 60), // minute
+ rndGen.Next(0, 60), // second
+ DateTimeKind.Utc);
+ Log.Info("dt = {0}", dt);
+
+ const string JsonDateFormat = "yyyy-MM-ddTHH:mm:ssZ";
+ string dateString = dt.ToString(JsonDateFormat, CultureInfo.InvariantCulture);
+ JsonValue jv = dateString;
+ DateTime dt2 = (DateTime)jv;
+ Assert.Equal(dt.ToUniversalTime(), dt2.ToUniversalTime());
+
+ const string DateTimeLocalFormat = "yyyy-MM-ddTHH:mm:ss";
+ const string DateLocalFormat = "yyyy-MM-dd";
+ const string TimeLocalFormat = "HH:mm:ss";
+
+ for (int i = 0; i < 100; i++)
+ {
+ DateTime dateLocal = PrimitiveCreator.CreateInstanceOfDateTime(rndGen).ToLocalTime();
+ dateLocal = new DateTime(dateLocal.Year, dateLocal.Month, dateLocal.Day, dateLocal.Hour, dateLocal.Minute, dateLocal.Second, DateTimeKind.Local);
+ string localDateTime = dateLocal.ToString(DateTimeLocalFormat, CultureInfo.InvariantCulture);
+ string localDate = dateLocal.ToString(DateLocalFormat, CultureInfo.InvariantCulture);
+ string localTime = dateLocal.ToString(TimeLocalFormat, CultureInfo.InvariantCulture);
+
+ Assert.Equal(dateLocal, new JsonPrimitive(localDateTime).ReadAs<DateTime>());
+ Assert.Equal(dateLocal.Date, new JsonPrimitive(localDate).ReadAs<DateTime>());
+ DateTime timeOnly = new JsonPrimitive(localTime).ReadAs<DateTime>();
+ Assert.Equal(dateLocal.Hour, timeOnly.Hour);
+ Assert.Equal(dateLocal.Minute, timeOnly.Minute);
+ Assert.Equal(dateLocal.Second, timeOnly.Second);
+
+ DataContractJsonSerializer dcjs = new DataContractJsonSerializer(typeof(DateTime));
+ using (MemoryStream ms = new MemoryStream())
+ {
+ dcjs.WriteObject(ms, dateLocal);
+ ms.Position = 0;
+ JsonValue jvFromString = JsonValue.Load(ms);
+ Assert.Equal(dateLocal, jvFromString.ReadAs<DateTime>());
+ }
+
+ using (MemoryStream ms = new MemoryStream())
+ {
+ DateTime dateUtc = dateLocal.ToUniversalTime();
+ dcjs.WriteObject(ms, dateUtc);
+ ms.Position = 0;
+ JsonValue jvFromString = JsonValue.Load(ms);
+ Assert.Equal(dateUtc, jvFromString.ReadAs<DateTime>());
+ }
+ }
+ }
+
+ /// <summary>
+ /// Tests for date parsing form the RFC2822 format.
+ /// </summary>
+ [Fact]
+ public void Rfc2822DateTimeFormatTest()
+ {
+ string[] localFormats = new string[]
+ {
+ "ddd, d MMM yyyy HH:mm:ss zzz",
+ "d MMM yyyy HH:mm:ss zzz",
+ "ddd, dd MMM yyyy HH:mm:ss zzz",
+ "ddd, dd MMM yyyy HH:mm zzz",
+ };
+
+ string[] utcFormats = new string[]
+ {
+ @"ddd, d MMM yyyy HH:mm:ss \U\T\C",
+ "d MMM yyyy HH:mm:ssZ",
+ @"ddd, dd MMM yyyy HH:mm:ss \U\T\C",
+ "ddd, dd MMM yyyy HH:mmZ",
+ };
+
+ DateTime today = DateTime.Today;
+ int seed = today.Year * 10000 + today.Month * 100 + today.Day;
+ Log.Info("Seed: {0}", seed);
+ Random rndGen = new Random(seed);
+ const int DatesToTry = 100;
+ const string DateTraceFormat = "ddd yyyy/MM/dd HH:mm:ss.fffZ";
+
+ for (int i = 0; i < DatesToTry; i++)
+ {
+ DateTime dt = PrimitiveCreator.CreateInstanceOfDateTime(rndGen);
+ dt = new DateTime(dt.Year, dt.Month, dt.Day, dt.Hour, dt.Minute, dt.Second, dt.Kind);
+ Log.Info("Test with date: {0} ({1})", dt.ToString(DateTraceFormat, CultureInfo.InvariantCulture), dt.Kind);
+ string[] formatsToTest = dt.Kind == DateTimeKind.Utc ? utcFormats : localFormats;
+ foreach (string format in formatsToTest)
+ {
+ string strDate = dt.ToString(format, CultureInfo.InvariantCulture);
+ Log.Info("As string: {0} (format = {1})", strDate, format);
+ JsonPrimitive jp = new JsonPrimitive(strDate);
+ DateTime parsedDate = jp.ReadAs<DateTime>();
+ Log.Info("Parsed date: {0} ({1})", parsedDate.ToString(DateTraceFormat, CultureInfo.InvariantCulture), parsedDate.Kind);
+
+ DateTime dtExpected = dt;
+ DateTime dtActual = parsedDate;
+
+ if (dt.Kind != parsedDate.Kind)
+ {
+ dtExpected = dtExpected.ToUniversalTime();
+ dtActual = dtActual.ToUniversalTime();
+ }
+
+ Assert.Equal(dtExpected.Year, dtActual.Year);
+ Assert.Equal(dtExpected.Month, dtActual.Month);
+ Assert.Equal(dtExpected.Day, dtActual.Day);
+ Assert.Equal(dtExpected.Hour, dtActual.Hour);
+ Assert.Equal(dtExpected.Minute, dtActual.Minute);
+ if (format.Contains(":ss"))
+ {
+ Assert.Equal(dtExpected.Second, dtActual.Second);
+ }
+ else
+ {
+ Assert.Equal(0, parsedDate.Second);
+ }
+ }
+
+ Log.Info("");
+ }
+ }
+
+ /// <summary>
+ /// Tests for the <see cref="System.Json.JsonValue.ReadAs{T}()"/> function from string values.
+ /// </summary>
+ [Fact]
+ public void ReadAsFromStringTests()
+ {
+ int seed = MethodBase.GetCurrentMethod().Name.GetHashCode();
+ Random rndGen = new Random(seed);
+
+ TestReadAsFromStringRoundtrip<bool>(false, "false");
+ TestReadAsFromStringRoundtrip<bool>(false, "False");
+ TestReadAsFromStringRoundtrip<bool>(true, "true");
+ TestReadAsFromStringRoundtrip<bool>(true, "True");
+ TestReadAsFromStringRoundtrip<byte>(PrimitiveCreator.CreateInstanceOfByte(rndGen));
+ TestReadAsFromStringRoundtrip<char>(PrimitiveCreator.CreateInstanceOfChar(rndGen));
+ TestReadAsFromStringRoundtrip<decimal>(PrimitiveCreator.CreateInstanceOfDecimal(rndGen));
+ TestReadAsFromStringRoundtrip<int>(PrimitiveCreator.CreateInstanceOfInt32(rndGen));
+ TestReadAsFromStringRoundtrip<long>(PrimitiveCreator.CreateInstanceOfInt64(rndGen));
+ TestReadAsFromStringRoundtrip<sbyte>(PrimitiveCreator.CreateInstanceOfSByte(rndGen));
+ TestReadAsFromStringRoundtrip<short>(PrimitiveCreator.CreateInstanceOfInt16(rndGen));
+ TestReadAsFromStringRoundtrip<ushort>(PrimitiveCreator.CreateInstanceOfUInt16(rndGen));
+ TestReadAsFromStringRoundtrip<uint>(PrimitiveCreator.CreateInstanceOfUInt32(rndGen));
+ TestReadAsFromStringRoundtrip<ulong>(PrimitiveCreator.CreateInstanceOfUInt64(rndGen));
+ double dbl = rndGen.NextDouble() * rndGen.Next();
+ TestReadAsFromStringRoundtrip<double>(dbl, dbl.ToString("R", CultureInfo.InvariantCulture));
+ TestReadAsFromStringRoundtrip<double>(double.PositiveInfinity, "Infinity");
+ TestReadAsFromStringRoundtrip<double>(double.NegativeInfinity, "-Infinity");
+ TestReadAsFromStringRoundtrip<double>(double.NaN, "NaN");
+ float flt = (float)(rndGen.NextDouble() * rndGen.Next());
+ TestReadAsFromStringRoundtrip<float>(flt, flt.ToString("R", CultureInfo.InvariantCulture));
+ TestReadAsFromStringRoundtrip<float>(float.PositiveInfinity, "Infinity");
+ TestReadAsFromStringRoundtrip<float>(float.NegativeInfinity, "-Infinity");
+ TestReadAsFromStringRoundtrip<float>(float.NaN, "NaN");
+ Guid guid = PrimitiveCreator.CreateInstanceOfGuid(rndGen);
+ TestReadAsFromStringRoundtrip<Guid>(guid, guid.ToString("N", CultureInfo.InvariantCulture));
+ TestReadAsFromStringRoundtrip<Guid>(guid, guid.ToString("D", CultureInfo.InvariantCulture));
+ TestReadAsFromStringRoundtrip<Guid>(guid, guid.ToString("B", CultureInfo.InvariantCulture));
+ TestReadAsFromStringRoundtrip<Guid>(guid, guid.ToString("P", CultureInfo.InvariantCulture));
+ TestReadAsFromStringRoundtrip<Guid>(guid, guid.ToString("X", CultureInfo.InvariantCulture));
+ TestReadAsFromStringRoundtrip<Guid>(guid, guid.ToString("X", CultureInfo.InvariantCulture).Replace("0x", "0X"));
+ TestReadAsFromStringRoundtrip<Guid>(guid, guid.ToString("X", CultureInfo.InvariantCulture).Replace("{", " { "));
+ TestReadAsFromStringRoundtrip<Guid>(guid, guid.ToString("X", CultureInfo.InvariantCulture).Replace("}", " } "));
+ TestReadAsFromStringRoundtrip<Guid>(guid, guid.ToString("X", CultureInfo.InvariantCulture).Replace(",", " , "));
+ TestReadAsFromStringRoundtrip<Guid>(guid, guid.ToString("X", CultureInfo.InvariantCulture).Replace("0x", "0x0000"));
+ Uri uri = null;
+ do
+ {
+ try
+ {
+ uri = PrimitiveCreator.CreateInstanceOfUri(rndGen);
+ }
+ catch (UriFormatException)
+ {
+ }
+ } while (uri == null);
+
+ TestReadAsFromStringRoundtrip<Uri>(uri);
+ TestReadAsFromStringRoundtrip<string>(PrimitiveCreator.CreateInstanceOfString(rndGen));
+
+ // Roundtrip reference DateTime to remove some of the precision in the ticks. Otherwise, value is too precise.
+ DateTimeOffset dateTimeOffset = PrimitiveCreator.CreateInstanceOfDateTimeOffset(rndGen);
+ const string ISO8601Format = "yyyy-MM-ddTHH:mm:sszzz";
+ dateTimeOffset = DateTimeOffset.ParseExact(dateTimeOffset.ToString(ISO8601Format, CultureInfo.InvariantCulture), ISO8601Format, CultureInfo.InvariantCulture);
+ DateTime dateTime = dateTimeOffset.UtcDateTime;
+ TestReadAsFromStringRoundtrip<DateTime>(dateTime, dateTimeOffset.ToUniversalTime().ToString(@"ddd, d MMM yyyy HH:mm:ss \U\T\C"));
+ TestReadAsFromStringRoundtrip<DateTime>(dateTime, dateTimeOffset.ToUniversalTime().ToString(@"ddd, d MMM yyyy HH:mm:ss \G\M\T"));
+ TestReadAsFromStringRoundtrip<DateTime>(dateTime.ToLocalTime(), dateTimeOffset.ToString(@"ddd, d MMM yyyy HH:mm:ss zzz"));
+ TestReadAsFromStringRoundtrip<DateTime>(dateTime, dateTime.ToString("yyyy-MM-ddTHH:mm:ssK"));
+ TestReadAsFromStringRoundtrip<DateTime>(dateTime.ToLocalTime(), dateTimeOffset.ToString(@"ddd, d MMM yyyy HH:mm:ss zzz"));
+ TestReadAsFromStringRoundtrip<DateTime>(dateTime.ToLocalTime(), dateTimeOffset.ToString("yyyy-MM-ddTHH:mm:sszzz"));
+ TestReadAsFromStringRoundtrip<DateTimeOffset>(dateTimeOffset.UtcDateTime, dateTimeOffset.ToUniversalTime().ToString(@"ddd, d MMM yyyy HH:mm:ss \U\T\C"));
+ TestReadAsFromStringRoundtrip<DateTimeOffset>(dateTimeOffset.UtcDateTime, dateTimeOffset.ToUniversalTime().ToString(@"ddd, d MMM yyyy HH:mm:ss \G\M\T"));
+ TestReadAsFromStringRoundtrip<DateTimeOffset>(dateTimeOffset, dateTimeOffset.ToUniversalTime().ToString(@"ddd, d MMM yyyy HH:mm:ss zzz"));
+ TestReadAsFromStringRoundtrip<DateTimeOffset>(dateTimeOffset, dateTime.ToString("yyyy-MM-ddTHH:mm:ssK"));
+ TestReadAsFromStringRoundtrip<DateTimeOffset>(dateTimeOffset, dateTimeOffset.ToString("yyyy-MM-ddTHH:mm:sszzz"));
+ TestReadAsFromStringRoundtrip<DateTimeOffset>(dateTimeOffset, dateTimeOffset.ToString(@"ddd, d MMM yyyy HH:mm:ss zzz"));
+
+ // Create ASPNetFormat DateTime
+ long unixEpochMilliseconds = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc).Ticks / 10000;
+ long millisecondsFromUnixEpoch = dateTime.Ticks / 10000 - unixEpochMilliseconds;
+ string AspNetFormattedDateTime = String.Format("/Date({0})/", millisecondsFromUnixEpoch);
+ string AspNetFormattedDateTimeWithValidTZ = String.Format("/Date({0}+0700)/", millisecondsFromUnixEpoch);
+ string AspNetFormattedDateTimeInvalid1 = String.Format("/Date({0}+99999)/", millisecondsFromUnixEpoch);
+ string AspNetFormattedDateTimeInvalid2 = String.Format("/Date({0}+07z0)/", millisecondsFromUnixEpoch);
+ TestReadAsFromStringRoundtrip<DateTime>(dateTime, AspNetFormattedDateTime);
+ TestReadAsFromStringRoundtrip<DateTime>(dateTime.ToLocalTime(), AspNetFormattedDateTimeWithValidTZ);
+ TestReadAsFromStringRoundtrip<DateTimeOffset>(dateTimeOffset, AspNetFormattedDateTime);
+ TestReadAsFromStringRoundtrip<DateTimeOffset>(dateTimeOffset, AspNetFormattedDateTimeWithValidTZ);
+
+ ExpectException<FormatException>(delegate { new JsonPrimitive(AspNetFormattedDateTimeInvalid1).ReadAs<DateTime>(); });
+ ExpectException<FormatException>(delegate { new JsonPrimitive(AspNetFormattedDateTimeInvalid2).ReadAs<DateTime>(); });
+ ExpectException<FormatException>(delegate { new JsonPrimitive(AspNetFormattedDateTimeInvalid1).ReadAs<DateTimeOffset>(); });
+ ExpectException<FormatException>(delegate { new JsonPrimitive(AspNetFormattedDateTimeInvalid2).ReadAs<DateTimeOffset>(); });
+
+ ExpectException<FormatException>(delegate { new JsonPrimitive("INF").ReadAs<float>(); });
+ ExpectException<FormatException>(delegate { new JsonPrimitive("-INF").ReadAs<float>(); });
+ ExpectException<FormatException>(delegate { new JsonPrimitive("infinity").ReadAs<float>(); });
+ ExpectException<FormatException>(delegate { new JsonPrimitive("INFINITY").ReadAs<float>(); });
+ ExpectException<FormatException>(delegate { new JsonPrimitive("nan").ReadAs<float>(); });
+ ExpectException<FormatException>(delegate { new JsonPrimitive("Nan").ReadAs<float>(); });
+
+ ExpectException<FormatException>(delegate { new JsonPrimitive("INF").ReadAs<double>(); });
+ ExpectException<FormatException>(delegate { new JsonPrimitive("-INF").ReadAs<double>(); });
+ ExpectException<FormatException>(delegate { new JsonPrimitive("infinity").ReadAs<double>(); });
+ ExpectException<FormatException>(delegate { new JsonPrimitive("INFINITY").ReadAs<double>(); });
+ ExpectException<FormatException>(delegate { new JsonPrimitive("nan").ReadAs<double>(); });
+ ExpectException<FormatException>(delegate { new JsonPrimitive("Nan").ReadAs<double>(); });
+ }
+
+ /// <summary>
+ /// Tests for the <see cref="System.Json.JsonValue.ReadAs{T}()">JsonValue.ReadAs&lt;string&gt;</see> method from number values.
+ /// </summary>
+ [Fact]
+ public void TestReadAsStringFromNumbers()
+ {
+ int seed = MethodBase.GetCurrentMethod().Name.GetHashCode();
+ Random rndGen = new Random(seed);
+
+ int intValue = PrimitiveCreator.CreateInstanceOfInt32(rndGen);
+ JsonValue jv = intValue;
+ Assert.Equal(intValue.ToString(CultureInfo.InvariantCulture), jv.ToString());
+ Assert.Equal(intValue.ToString(CultureInfo.InvariantCulture), jv.ReadAs<string>());
+
+ uint uintValue = PrimitiveCreator.CreateInstanceOfUInt32(rndGen);
+ jv = uintValue;
+ Assert.Equal(uintValue.ToString(CultureInfo.InvariantCulture), jv.ToString());
+ Assert.Equal(uintValue.ToString(CultureInfo.InvariantCulture), jv.ReadAs<string>());
+
+ long longValue = PrimitiveCreator.CreateInstanceOfInt64(rndGen);
+ jv = longValue;
+ Assert.Equal(longValue.ToString(CultureInfo.InvariantCulture), jv.ToString());
+ Assert.Equal(longValue.ToString(CultureInfo.InvariantCulture), jv.ReadAs<string>());
+
+ ulong ulongValue = PrimitiveCreator.CreateInstanceOfUInt64(rndGen);
+ jv = ulongValue;
+ Assert.Equal(ulongValue.ToString(CultureInfo.InvariantCulture), jv.ToString());
+ Assert.Equal(ulongValue.ToString(CultureInfo.InvariantCulture), jv.ReadAs<string>());
+
+ short shortValue = PrimitiveCreator.CreateInstanceOfInt16(rndGen);
+ jv = shortValue;
+ Assert.Equal(shortValue.ToString(CultureInfo.InvariantCulture), jv.ToString());
+ Assert.Equal(shortValue.ToString(CultureInfo.InvariantCulture), jv.ReadAs<string>());
+
+ ushort ushortValue = PrimitiveCreator.CreateInstanceOfUInt16(rndGen);
+ jv = ushortValue;
+ Assert.Equal(ushortValue.ToString(CultureInfo.InvariantCulture), jv.ToString());
+ Assert.Equal(ushortValue.ToString(CultureInfo.InvariantCulture), jv.ReadAs<string>());
+
+ byte byteValue = PrimitiveCreator.CreateInstanceOfByte(rndGen);
+ jv = byteValue;
+ Assert.Equal(byteValue.ToString(CultureInfo.InvariantCulture), jv.ToString());
+ Assert.Equal(byteValue.ToString(CultureInfo.InvariantCulture), jv.ReadAs<string>());
+
+ sbyte sbyteValue = PrimitiveCreator.CreateInstanceOfSByte(rndGen);
+ jv = sbyteValue;
+ Assert.Equal(sbyteValue.ToString(CultureInfo.InvariantCulture), jv.ToString());
+ Assert.Equal(sbyteValue.ToString(CultureInfo.InvariantCulture), jv.ReadAs<string>());
+
+ decimal decValue = PrimitiveCreator.CreateInstanceOfDecimal(rndGen);
+ jv = decValue;
+ Assert.Equal(decValue.ToString(CultureInfo.InvariantCulture), jv.ToString());
+ Assert.Equal(decValue.ToString(CultureInfo.InvariantCulture), jv.ReadAs<string>());
+
+ float fltValue = PrimitiveCreator.CreateInstanceOfSingle(rndGen);
+ jv = fltValue;
+ Assert.Equal(fltValue.ToString("R", CultureInfo.InvariantCulture), jv.ToString());
+ Assert.Equal(fltValue.ToString("R", CultureInfo.InvariantCulture), jv.ReadAs<string>());
+
+ double dblValue = PrimitiveCreator.CreateInstanceOfDouble(rndGen);
+ jv = dblValue;
+ Assert.Equal(dblValue.ToString("R", CultureInfo.InvariantCulture), jv.ToString());
+ Assert.Equal(dblValue.ToString("R", CultureInfo.InvariantCulture), jv.ReadAs<string>());
+ }
+
+ /// <summary>
+ /// Tests for the <see cref="System.Json.JsonValue.ReadAs{T}()">JsonValue.ReadAs&lt;string&gt;</see> method from date values.
+ /// </summary>
+ [Fact]
+ public void TestReadAsStringFromDates()
+ {
+ int seed = MethodBase.GetCurrentMethod().Name.GetHashCode();
+ Random rndGen = new Random(seed);
+
+ DateTime dateTimeValue = PrimitiveCreator.CreateInstanceOfDateTime(rndGen);
+ JsonValue jv = dateTimeValue;
+ Assert.Equal("\"" + dateTimeValue.ToString(DateTimeFormat, CultureInfo.InvariantCulture) + "\"", jv.ToString());
+ Assert.Equal(dateTimeValue.ToString(DateTimeFormat, CultureInfo.InvariantCulture), jv.ReadAs<string>());
+ }
+
+ /// <summary>
+ /// Tests for the <see cref="System.Json.JsonValue.ReadAs{T}()">JsonValue.ReadAs&lt;string&gt;</see> method from char values.
+ /// </summary>
+ [Fact]
+ public void TestReadAsStringFromChar()
+ {
+ char[] chars = "abc\u0000\b\f\r\n\t\ufedc".ToCharArray();
+
+ foreach (char c in chars)
+ {
+ string expected = new string(c, 1);
+ JsonValue jv = c;
+ string actual1 = jv.ReadAs<string>();
+ string actual2 = (string)jv;
+
+ Assert.Equal(expected, actual1);
+ Assert.Equal(expected, actual2);
+ }
+ }
+
+ /// <summary>
+ /// Tests for the <see cref="System.Json.JsonValue.ReadAs{T}()"/> method where T is a number type and the value is created from a string.
+ /// </summary>
+ [Fact]
+ public void TestReadAsNumberFromStrings()
+ {
+ Dictionary<object, List<Type>> valuesToNonOverflowingTypesMapping = new Dictionary<object, List<Type>>
+ {
+ { double.NaN.ToString("R", CultureInfo.InvariantCulture), new List<Type> { typeof(float), typeof(double) } },
+ { double.NegativeInfinity.ToString("R", CultureInfo.InvariantCulture), new List<Type> { typeof(float), typeof(double) } },
+ { double.PositiveInfinity.ToString("R", CultureInfo.InvariantCulture), new List<Type> { typeof(float), typeof(double) } },
+ { double.MaxValue.ToString("R", CultureInfo.InvariantCulture), new List<Type> { typeof(double), typeof(float) } },
+ { double.MinValue.ToString("R", CultureInfo.InvariantCulture), new List<Type> { typeof(double), typeof(float) } },
+ { float.MaxValue.ToString("R", CultureInfo.InvariantCulture), new List<Type> { typeof(double), typeof(float) } },
+ { float.MinValue.ToString("R", CultureInfo.InvariantCulture), new List<Type> { typeof(double), typeof(float) } },
+ { Int64.MaxValue.ToString(), new List<Type> { typeof(double), typeof(float), typeof(decimal), typeof(long), typeof(ulong) } },
+ { Int64.MinValue.ToString(), new List<Type> { typeof(double), typeof(float), typeof(decimal), typeof(long) } },
+ { Int32.MaxValue.ToString(), new List<Type> { typeof(double), typeof(float), typeof(decimal), typeof(long), typeof(int), typeof(ulong), typeof(uint) } },
+ { Int32.MinValue.ToString(), new List<Type> { typeof(double), typeof(float), typeof(decimal), typeof(long), typeof(int) } },
+ { Int16.MaxValue.ToString(), new List<Type> { typeof(double), typeof(float), typeof(decimal), typeof(long), typeof(int), typeof(short), typeof(ulong), typeof(uint), typeof(ushort) } },
+ { Int16.MinValue.ToString(), new List<Type> { typeof(double), typeof(float), typeof(decimal), typeof(long), typeof(int), typeof(short) } },
+ { SByte.MaxValue.ToString(), new List<Type> { typeof(double), typeof(float), typeof(decimal), typeof(long), typeof(int), typeof(short), typeof(sbyte), typeof(ulong), typeof(uint), typeof(ushort), typeof(byte) } },
+ { SByte.MinValue.ToString(), new List<Type> { typeof(double), typeof(float), typeof(decimal), typeof(long), typeof(int), typeof(short), typeof(sbyte) } },
+ { UInt64.MaxValue.ToString(), new List<Type> { typeof(double), typeof(float), typeof(decimal), typeof(ulong) } },
+ { UInt32.MaxValue.ToString(), new List<Type> { typeof(double), typeof(float), typeof(decimal), typeof(long), typeof(ulong), typeof(uint) } },
+ { UInt16.MaxValue.ToString(), new List<Type> { typeof(double), typeof(float), typeof(decimal), typeof(long), typeof(int), typeof(ulong), typeof(uint), typeof(ushort) } },
+ { Byte.MaxValue.ToString(), new List<Type> { typeof(double), typeof(float), typeof(decimal), typeof(long), typeof(int), typeof(short), typeof(ulong), typeof(uint), typeof(ushort), typeof(byte) } },
+ { Byte.MinValue.ToString(), new List<Type> { typeof(double), typeof(float), typeof(decimal), typeof(long), typeof(int), typeof(short), typeof(sbyte), typeof(ulong), typeof(uint), typeof(ushort), typeof(byte) } },
+ { "1", new List<Type> { typeof(double), typeof(float), typeof(decimal), typeof(long), typeof(int), typeof(short), typeof(sbyte), typeof(ulong), typeof(uint), typeof(ushort), typeof(byte) } },
+ { "+01", new List<Type> { typeof(double), typeof(float), typeof(decimal), typeof(long), typeof(int), typeof(short), typeof(sbyte), typeof(ulong), typeof(uint), typeof(ushort), typeof(byte) } },
+ { "01.1e+01", new List<Type> { typeof(double), typeof(float), typeof(decimal), typeof(long), typeof(int), typeof(short), typeof(sbyte), typeof(ulong), typeof(uint), typeof(ushort), typeof(byte) } },
+ { "1e1", new List<Type> { typeof(double), typeof(float), typeof(decimal), typeof(long), typeof(int), typeof(short), typeof(sbyte), typeof(ulong), typeof(uint), typeof(ushort), typeof(byte) } },
+ { "1.0", new List<Type> { typeof(double), typeof(float), typeof(decimal), typeof(long), typeof(int), typeof(short), typeof(sbyte), typeof(ulong), typeof(uint), typeof(ushort), typeof(byte) } },
+ { "01.0", new List<Type> { typeof(double), typeof(float), typeof(decimal), typeof(long), typeof(int), typeof(short), typeof(sbyte), typeof(ulong), typeof(uint), typeof(ushort), typeof(byte) } },
+ { "-1", new List<Type> { typeof(double), typeof(float), typeof(decimal), typeof(long), typeof(int), typeof(short), typeof(sbyte) } },
+ { "-1.0", new List<Type> { typeof(double), typeof(float), typeof(decimal), typeof(long), typeof(int), typeof(short), typeof(sbyte) } },
+ { "-01.0", new List<Type> { typeof(double), typeof(float), typeof(decimal), typeof(long), typeof(int), typeof(short), typeof(sbyte) } },
+ { "-01.0e+01", new List<Type> { typeof(double), typeof(float), typeof(decimal), typeof(long), typeof(int), typeof(short), typeof(sbyte) } },
+ { "-01.0e-01", new List<Type> { typeof(double), typeof(float), typeof(decimal), typeof(long), typeof(int), typeof(short), typeof(sbyte), typeof(ulong), typeof(uint), typeof(ushort), typeof(byte) } },
+ { "-.1", new List<Type> { typeof(double), typeof(float), typeof(decimal), typeof(long), typeof(int), typeof(short), typeof(sbyte), typeof(ulong), typeof(uint), typeof(ushort), typeof(byte) } },
+ { "-0100.0e-1", new List<Type> { typeof(double), typeof(float), typeof(decimal), typeof(long), typeof(int), typeof(short), typeof(sbyte) } },
+ };
+
+ foreach (KeyValuePair<object, List<Type>> mapping in valuesToNonOverflowingTypesMapping)
+ {
+ ConvertValueToNumber<double>(mapping);
+ ConvertValueToNumber<float>(mapping);
+ ConvertValueToNumber<decimal>(mapping);
+ ConvertValueToNumber<long>(mapping);
+ ConvertValueToNumber<int>(mapping);
+ ConvertValueToNumber<short>(mapping);
+ ConvertValueToNumber<sbyte>(mapping);
+ ConvertValueToNumber<ulong>(mapping);
+ ConvertValueToNumber<uint>(mapping);
+ ConvertValueToNumber<ushort>(mapping);
+ ConvertValueToNumber<byte>(mapping);
+ }
+
+ Dictionary<object, List<Type>> valuesThatAreInvalidNumber = new Dictionary<object, List<Type>>
+ {
+ { "1L", new List<Type> { } },
+ { "0x1", new List<Type> { } },
+ { "1e309", new List<Type> { } },
+ { "", new List<Type> { } },
+ { "-", new List<Type> { } },
+ { "e10", new List<Type> { } },
+ };
+
+ foreach (KeyValuePair<object, List<Type>> mapping in valuesThatAreInvalidNumber)
+ {
+ ConvertValueToNumber<double, FormatException>(mapping);
+ ConvertValueToNumber<float, FormatException>(mapping);
+ ConvertValueToNumber<decimal, FormatException>(mapping);
+ ConvertValueToNumber<long, FormatException>(mapping);
+ ConvertValueToNumber<int, FormatException>(mapping);
+ ConvertValueToNumber<short, FormatException>(mapping);
+ ConvertValueToNumber<sbyte, FormatException>(mapping);
+ ConvertValueToNumber<ulong, FormatException>(mapping);
+ ConvertValueToNumber<uint, FormatException>(mapping);
+ ConvertValueToNumber<ushort, FormatException>(mapping);
+ ConvertValueToNumber<byte, FormatException>(mapping);
+ }
+ }
+
+ /// <summary>
+ /// Tests for the <see cref="System.Json.JsonValue.ReadAs{T}()"/> method where T is a number type and the value is created from a number.
+ /// This is essentially a number conversion test.
+ /// </summary>
+ [Fact]
+ public void TestReadAsNumberFromNumber()
+ {
+ Dictionary<object, List<Type>> valuesToNonOverflowingTypesMapping = new Dictionary<object, List<Type>>
+ {
+ { double.NaN, new List<Type> { typeof(float), typeof(double) } },
+ { double.NegativeInfinity, new List<Type> { typeof(float), typeof(double) } },
+ { double.PositiveInfinity, new List<Type> { typeof(float), typeof(double) } },
+ { float.NaN, new List<Type> { typeof(float), typeof(double) } },
+ { float.NegativeInfinity, new List<Type> { typeof(float), typeof(double) } },
+ { float.PositiveInfinity, new List<Type> { typeof(float), typeof(double) } },
+ { double.MaxValue, new List<Type> { typeof(double), typeof(float) } },
+ { double.MinValue, new List<Type> { typeof(double), typeof(float) } },
+ { float.MaxValue, new List<Type> { typeof(double), typeof(float) } },
+ { float.MinValue, new List<Type> { typeof(double), typeof(float) } },
+ { Int64.MaxValue, new List<Type> { typeof(double), typeof(float), typeof(decimal), typeof(long), typeof(ulong) } },
+ { Int64.MinValue, new List<Type> { typeof(double), typeof(float), typeof(decimal), typeof(long) } },
+ { Int32.MaxValue, new List<Type> { typeof(double), typeof(float), typeof(decimal), typeof(long), typeof(int), typeof(ulong), typeof(uint) } },
+ { Int32.MinValue, new List<Type> { typeof(double), typeof(float), typeof(decimal), typeof(long), typeof(int) } },
+ { Int16.MaxValue, new List<Type> { typeof(double), typeof(float), typeof(decimal), typeof(long), typeof(int), typeof(short), typeof(ulong), typeof(uint), typeof(ushort) } },
+ { Int16.MinValue, new List<Type> { typeof(double), typeof(float), typeof(decimal), typeof(long), typeof(int), typeof(short) } },
+ { SByte.MaxValue, new List<Type> { typeof(double), typeof(float), typeof(decimal), typeof(long), typeof(int), typeof(short), typeof(sbyte), typeof(ulong), typeof(uint), typeof(ushort), typeof(byte) } },
+ { SByte.MinValue, new List<Type> { typeof(double), typeof(float), typeof(decimal), typeof(long), typeof(int), typeof(short), typeof(sbyte) } },
+ { UInt64.MaxValue, new List<Type> { typeof(double), typeof(float), typeof(decimal), typeof(ulong) } },
+ { UInt64.MinValue, new List<Type> { typeof(double), typeof(float), typeof(decimal), typeof(long), typeof(int), typeof(short), typeof(sbyte), typeof(ulong), typeof(uint), typeof(ushort), typeof(byte) } },
+ { UInt32.MaxValue, new List<Type> { typeof(double), typeof(float), typeof(decimal), typeof(long), typeof(ulong), typeof(uint) } },
+ { UInt32.MinValue, new List<Type> { typeof(double), typeof(float), typeof(decimal), typeof(long), typeof(int), typeof(short), typeof(sbyte), typeof(ulong), typeof(uint), typeof(ushort), typeof(byte) } },
+ { UInt16.MaxValue, new List<Type> { typeof(double), typeof(float), typeof(decimal), typeof(long), typeof(int), typeof(ulong), typeof(uint), typeof(ushort) } },
+ { UInt16.MinValue, new List<Type> { typeof(double), typeof(float), typeof(decimal), typeof(long), typeof(int), typeof(short), typeof(sbyte), typeof(ulong), typeof(uint), typeof(ushort), typeof(byte) } },
+ { Byte.MaxValue, new List<Type> { typeof(double), typeof(float), typeof(decimal), typeof(long), typeof(int), typeof(short), typeof(ulong), typeof(uint), typeof(ushort), typeof(byte) } },
+ { Byte.MinValue, new List<Type> { typeof(double), typeof(float), typeof(decimal), typeof(long), typeof(int), typeof(short), typeof(sbyte), typeof(ulong), typeof(uint), typeof(ushort), typeof(byte) } },
+ { (double)1, new List<Type> { typeof(double), typeof(float), typeof(decimal), typeof(long), typeof(int), typeof(short), typeof(sbyte), typeof(ulong), typeof(uint), typeof(ushort), typeof(byte) } },
+ { (float)1, new List<Type> { typeof(double), typeof(float), typeof(decimal), typeof(long), typeof(int), typeof(short), typeof(sbyte), typeof(ulong), typeof(uint), typeof(ushort), typeof(byte) } },
+ { (decimal)1, new List<Type> { typeof(double), typeof(float), typeof(decimal), typeof(long), typeof(int), typeof(short), typeof(sbyte), typeof(ulong), typeof(uint), typeof(ushort), typeof(byte) } },
+ { (long)1, new List<Type> { typeof(double), typeof(float), typeof(decimal), typeof(long), typeof(int), typeof(short), typeof(sbyte), typeof(ulong), typeof(uint), typeof(ushort), typeof(byte) } },
+ { (int)1, new List<Type> { typeof(double), typeof(float), typeof(decimal), typeof(long), typeof(int), typeof(short), typeof(sbyte), typeof(ulong), typeof(uint), typeof(ushort), typeof(byte) } },
+ { (short)1, new List<Type> { typeof(double), typeof(float), typeof(decimal), typeof(long), typeof(int), typeof(short), typeof(sbyte), typeof(ulong), typeof(uint), typeof(ushort), typeof(byte) } },
+ { (sbyte)1, new List<Type> { typeof(double), typeof(float), typeof(decimal), typeof(long), typeof(int), typeof(short), typeof(sbyte), typeof(ulong), typeof(uint), typeof(ushort), typeof(byte) } },
+ { (ulong)1, new List<Type> { typeof(double), typeof(float), typeof(decimal), typeof(long), typeof(int), typeof(short), typeof(sbyte), typeof(ulong), typeof(uint), typeof(ushort), typeof(byte) } },
+ { (uint)1, new List<Type> { typeof(double), typeof(float), typeof(decimal), typeof(long), typeof(int), typeof(short), typeof(sbyte), typeof(ulong), typeof(uint), typeof(ushort), typeof(byte) } },
+ { (ushort)1, new List<Type> { typeof(double), typeof(float), typeof(decimal), typeof(long), typeof(int), typeof(short), typeof(sbyte), typeof(ulong), typeof(uint), typeof(ushort), typeof(byte) } },
+ { (byte)1, new List<Type> { typeof(double), typeof(float), typeof(decimal), typeof(long), typeof(int), typeof(short), typeof(sbyte), typeof(ulong), typeof(uint), typeof(ushort), typeof(byte) } },
+ };
+
+ foreach (KeyValuePair<object, List<Type>> mapping in valuesToNonOverflowingTypesMapping)
+ {
+ ConvertValueToNumber<double>(mapping);
+ ConvertValueToNumber<float>(mapping);
+ ConvertValueToNumber<decimal>(mapping);
+ ConvertValueToNumber<long>(mapping);
+ ConvertValueToNumber<int>(mapping);
+ ConvertValueToNumber<short>(mapping);
+ ConvertValueToNumber<sbyte>(mapping);
+ ConvertValueToNumber<ulong>(mapping);
+ ConvertValueToNumber<uint>(mapping);
+ ConvertValueToNumber<ushort>(mapping);
+ ConvertValueToNumber<byte>(mapping);
+ }
+ }
+
+ static void ConvertValueToNumber<T>(KeyValuePair<object, List<Type>> mapping)
+ {
+ ConvertValueToNumber<T, OverflowException>(mapping);
+ }
+
+ static void ConvertValueToNumber<T, TException>(KeyValuePair<object, List<Type>> mapping)
+ where TException : Exception
+ {
+ JsonValue jsonValue = CastToJsonValue(mapping.Key);
+
+ Log.Info("Converting value {0} of type {1} to type {2}.", mapping.Key, mapping.Key.GetType().Name, typeof(T).Name);
+
+ if (mapping.Value.Contains(typeof(T)))
+ {
+ Console.Write("Conversion should work... ");
+ T valueOfT;
+ Assert.True(jsonValue.TryReadAs<T>(out valueOfT));
+ if (mapping.Key.GetType() != typeof(string))
+ {
+ Console.Write("and original value casted to {0} should be the same as the retrieved value... ", typeof(T).Name);
+ T castValue = (T)Convert.ChangeType(mapping.Key, typeof(T), CultureInfo.InvariantCulture);
+ Assert.Equal<T>(castValue, valueOfT);
+ }
+ }
+ else
+ {
+ Console.Write("Conversion should fail... ");
+ T valueOfT;
+ Assert.False(jsonValue.TryReadAs<T>(out valueOfT), String.Format("It was possible to read the value as {0}", valueOfT));
+ ExpectException<TException>(delegate
+ {
+ jsonValue.ReadAs<T>();
+ });
+ }
+
+ Log.Info("Success!");
+ }
+
+ static JsonValue CastToJsonValue(object o)
+ {
+ switch (Type.GetTypeCode(o.GetType()))
+ {
+ case TypeCode.Boolean:
+ return (JsonValue)(bool)o;
+ case TypeCode.Byte:
+ return (JsonValue)(byte)o;
+ case TypeCode.Char:
+ return (JsonValue)(char)o;
+ case TypeCode.DateTime:
+ return (JsonValue)(DateTime)o;
+ case TypeCode.Decimal:
+ return (JsonValue)(decimal)o;
+ case TypeCode.Double:
+ return (JsonValue)(double)o;
+ case TypeCode.Int16:
+ return (JsonValue)(short)o;
+ case TypeCode.Int32:
+ return (JsonValue)(int)o;
+ case TypeCode.Int64:
+ return (JsonValue)(long)o;
+ case TypeCode.SByte:
+ return (JsonValue)(sbyte)o;
+ case TypeCode.Single:
+ return (JsonValue)(float)o;
+ case TypeCode.String:
+ return (JsonValue)(string)o;
+ case TypeCode.UInt16:
+ return (JsonValue)(ushort)o;
+ case TypeCode.UInt32:
+ return (JsonValue)(uint)o;
+ case TypeCode.UInt64:
+ return (JsonValue)(ulong)o;
+ default:
+ if (o.GetType() == typeof(DateTimeOffset))
+ {
+ return (JsonValue)(DateTimeOffset)o;
+ }
+
+ if (o.GetType() == typeof(Guid))
+ {
+ return (JsonValue)(Guid)o;
+ }
+
+ if (o.GetType() == typeof(Uri))
+ {
+ return (JsonValue)(Uri)o;
+ }
+
+ break;
+ }
+
+ return (JsonObject)o;
+ }
+
+ static void ExpectException<T>(Action action) where T : Exception
+ {
+ JsonValueTests.ExpectException<T>(action);
+ }
+
+ static string GetExpectedRepresentation(object obj)
+ {
+ if (obj is double)
+ {
+ double dbl = (double)obj;
+ if (Double.IsPositiveInfinity(dbl))
+ {
+ return "Infinity";
+ }
+ else if (Double.IsNegativeInfinity(dbl))
+ {
+ return "-Infinity";
+ }
+ }
+ else if (obj is float)
+ {
+ float flt = (float)obj;
+ if (Single.IsPositiveInfinity(flt))
+ {
+ return "Infinity";
+ }
+ else if (Single.IsNegativeInfinity(flt))
+ {
+ return "-Infinity";
+ }
+ }
+ else if (obj is DateTime)
+ {
+ DateTime dt = (DateTime)obj;
+ return "\"" + dt.ToString(DateTimeFormat, CultureInfo.InvariantCulture) + "\"";
+ }
+
+ using (MemoryStream ms = new MemoryStream())
+ {
+ DataContractJsonSerializer dcjs = new DataContractJsonSerializer(obj.GetType());
+ dcjs.WriteObject(ms, obj);
+ return Encoding.UTF8.GetString(ms.GetBuffer(), 0, (int)ms.Position);
+ }
+ }
+
+ void ValidateJson(JsonPrimitive jsonPrim, string expectedJson, JsonType expectedJsonType)
+ {
+ Assert.Equal(expectedJson, jsonPrim.ToString());
+ Assert.Equal(expectedJsonType, jsonPrim.JsonType);
+ }
+
+ void TestReadAsRoundtrip<T>(JsonPrimitive jsonPrim, T myOriginalObjectOfT)
+ {
+ T myReadObjectOfT = jsonPrim.ReadAs<T>();
+ T myTryReadObjectOfT;
+ Assert.True(jsonPrim.TryReadAs<T>(out myTryReadObjectOfT));
+ Assert.Equal(myOriginalObjectOfT, myReadObjectOfT);
+ Assert.Equal(myOriginalObjectOfT, myTryReadObjectOfT);
+
+ string stringValue;
+ Assert.True(jsonPrim.TryReadAs<string>(out stringValue));
+ if (typeof(T) == typeof(bool))
+ {
+ // bool returns a lowercase version. make sure we get something usable by doing another roundtrip of the value in .NET
+ Assert.Equal(String.Format(CultureInfo.InvariantCulture, "{0}", myOriginalObjectOfT), bool.Parse(stringValue).ToString(CultureInfo.InvariantCulture));
+ }
+ else if (typeof(T) == typeof(float) || typeof(T) == typeof(double))
+ {
+ Assert.Equal(String.Format(CultureInfo.InvariantCulture, "{0:R}", myOriginalObjectOfT), stringValue);
+ }
+ else if (typeof(T) == typeof(DateTime))
+ {
+ Assert.Equal(String.Format(CultureInfo.InvariantCulture, "{0:" + DateTimeFormat + "}", myOriginalObjectOfT), stringValue);
+ }
+ else
+ {
+ Assert.Equal(String.Format(CultureInfo.InvariantCulture, "{0}", myOriginalObjectOfT), stringValue);
+ }
+ }
+
+ void TestReadAsFromStringRoundtrip<T>(T value)
+ {
+ TestReadAsFromStringRoundtrip<T>(value, String.Format(CultureInfo.InvariantCulture, "{0}", value));
+ }
+
+ void TestReadAsFromStringRoundtrip<T>(T value, string valueString)
+ {
+ T tempOfT;
+ JsonPrimitive jsonPrim = new JsonPrimitive(valueString);
+ Assert.True(jsonPrim.TryReadAs<T>(out tempOfT));
+ Assert.Equal<T>(value, tempOfT);
+ }
+ }
+}
diff --git a/test/System.Json.Test.Integration/JsonStringRoundTripTests.cs b/test/System.Json.Test.Integration/JsonStringRoundTripTests.cs
new file mode 100644
index 00000000..6ef61aa3
--- /dev/null
+++ b/test/System.Json.Test.Integration/JsonStringRoundTripTests.cs
@@ -0,0 +1,583 @@
+using System.Collections.Generic;
+using System.Globalization;
+using System.IO;
+using Xunit;
+
+namespace System.Json
+{
+ /// <summary>
+ /// Tests for round-tripping <see cref="JsonValue"/> instances via JSON strings.
+ /// </summary>
+ public class JsonStringRoundTripTests
+ {
+ /// <summary>
+ /// Tests for <see cref="JsonObject"/> round-trip.
+ /// </summary>
+ [Fact]
+ public void ValidJsonObjectRoundTrip()
+ {
+ bool oldValue = CreatorSettings.CreateDateTimeWithSubMilliseconds;
+ CreatorSettings.CreateDateTimeWithSubMilliseconds = false;
+ try
+ {
+ int seed = 1;
+ Log.Info("Seed: {0}", seed);
+ Random rndGen = new Random(seed);
+
+ JsonObject sourceJson = new JsonObject(new Dictionary<string, JsonValue>()
+ {
+ { "Name", PrimitiveCreator.CreateInstanceOfString(rndGen) },
+ { "Age", PrimitiveCreator.CreateInstanceOfInt32(rndGen) },
+ { "DateTimeOffset", PrimitiveCreator.CreateInstanceOfDateTimeOffset(rndGen) },
+ { "Birthday", PrimitiveCreator.CreateInstanceOfDateTime(rndGen) }
+ });
+ sourceJson.Add("NewItem1", PrimitiveCreator.CreateInstanceOfString(rndGen));
+ sourceJson.Add(new KeyValuePair<string, JsonValue>("NewItem2", PrimitiveCreator.CreateInstanceOfString(rndGen)));
+
+ JsonObject newJson = (JsonObject)JsonValue.Parse(sourceJson.ToString());
+
+ newJson.Remove("NewItem1");
+ sourceJson.Remove("NewItem1");
+
+ Assert.False(newJson.ContainsKey("NewItem1"));
+
+ Assert.False(!JsonValueVerifier.Compare(sourceJson, newJson));
+ }
+ finally
+ {
+ CreatorSettings.CreateDateTimeWithSubMilliseconds = oldValue;
+ }
+ }
+
+ /// <summary>
+ /// Test for <see cref="JsonPrimitive"/> round-trip created via <see cref="DateTime"/>.
+ /// </summary>
+ [Fact]
+ public void SimpleDateTimeTest()
+ {
+ JsonValue jv = DateTime.Now;
+ JsonValue jv2 = JsonValue.Parse(jv.ToString());
+ Assert.Equal(jv.ToString(), jv2.ToString());
+ }
+
+ /// <summary>
+ /// Test for <see cref="JsonPrimitive"/> round-trip created via <see cref="DateTimeOffset"/>.
+ /// </summary>
+ [Fact]
+ public void ValidJsonObjectDateTimeOffsetRoundTrip()
+ {
+ int seed = 1;
+ Log.Info("Seed: {0}", seed);
+ Random rndGen = new Random(seed);
+
+ JsonPrimitive sourceJson = new JsonPrimitive(PrimitiveCreator.CreateInstanceOfDateTimeOffset(rndGen));
+ JsonPrimitive newJson = (JsonPrimitive)JsonValue.Parse(sourceJson.ToString());
+
+ Assert.True(JsonValueVerifier.Compare(sourceJson, newJson));
+ }
+
+ /// <summary>
+ /// Tests for <see cref="JsonArray"/> round-trip.
+ /// </summary>
+ [Fact]
+ public void ValidJsonArrayRoundTrip()
+ {
+ bool oldValue = CreatorSettings.CreateDateTimeWithSubMilliseconds;
+ CreatorSettings.CreateDateTimeWithSubMilliseconds = false;
+ try
+ {
+ int seed = 1;
+ Log.Info("Seed: {0}", seed);
+ Random rndGen = new Random(seed);
+
+ JsonArray sourceJson = new JsonArray(new JsonValue[]
+ {
+ PrimitiveCreator.CreateInstanceOfBoolean(rndGen),
+ PrimitiveCreator.CreateInstanceOfByte(rndGen),
+ PrimitiveCreator.CreateInstanceOfDateTime(rndGen),
+ PrimitiveCreator.CreateInstanceOfDateTimeOffset(rndGen),
+ PrimitiveCreator.CreateInstanceOfDecimal(rndGen),
+ PrimitiveCreator.CreateInstanceOfDouble(rndGen),
+ PrimitiveCreator.CreateInstanceOfInt16(rndGen),
+ PrimitiveCreator.CreateInstanceOfInt32(rndGen),
+ PrimitiveCreator.CreateInstanceOfInt64(rndGen),
+ PrimitiveCreator.CreateInstanceOfSByte(rndGen),
+ PrimitiveCreator.CreateInstanceOfSingle(rndGen),
+ PrimitiveCreator.CreateInstanceOfString(rndGen),
+ PrimitiveCreator.CreateInstanceOfUInt16(rndGen),
+ PrimitiveCreator.CreateInstanceOfUInt32(rndGen),
+ PrimitiveCreator.CreateInstanceOfUInt64(rndGen)
+ });
+
+ JsonArray newJson = (JsonArray)JsonValue.Parse(sourceJson.ToString());
+
+ Log.Info("Original JsonArray object is: {0}", sourceJson);
+ Log.Info("Round-tripped JsonArray object is: {0}", newJson);
+
+ Assert.True(JsonValueVerifier.Compare(sourceJson, newJson));
+ }
+ finally
+ {
+ CreatorSettings.CreateDateTimeWithSubMilliseconds = oldValue;
+ }
+ }
+
+ /// <summary>
+ /// Test for <see cref="JsonPrimitive"/> round-trip created via <see cref="String"/>.
+ /// </summary>
+ [Fact]
+ public void ValidPrimitiveStringRoundTrip()
+ {
+ Assert.True(this.TestPrimitiveType("String"));
+ }
+
+ /// <summary>
+ /// Test for <see cref="JsonPrimitive"/> round-trip created via <see cref="DateTime"/>.
+ /// </summary>
+ [Fact]
+ public void ValidPrimitiveDateTimeRoundTrip()
+ {
+ bool oldValue = CreatorSettings.CreateDateTimeWithSubMilliseconds;
+ CreatorSettings.CreateDateTimeWithSubMilliseconds = false;
+ try
+ {
+ Assert.True(this.TestPrimitiveType("DateTime"));
+ }
+ finally
+ {
+ CreatorSettings.CreateDateTimeWithSubMilliseconds = oldValue;
+ }
+ }
+
+ /// <summary>
+ /// Test for <see cref="JsonPrimitive"/> round-trip created via <see cref="Boolean"/>.
+ /// </summary>
+ [Fact]
+ public void ValidPrimitiveBooleanRoundTrip()
+ {
+ Assert.True(this.TestPrimitiveType("Boolean"));
+ }
+
+ /// <summary>
+ /// Test for <see cref="JsonPrimitive"/> round-trip created via <see cref="Byte"/>.
+ /// </summary>
+ [Fact]
+ public void ValidPrimitiveByteRoundTrip()
+ {
+ Assert.True(this.TestPrimitiveType("Byte"));
+ }
+
+ /// <summary>
+ /// Test for <see cref="JsonPrimitive"/> round-trip created via <see cref="Decimal"/>.
+ /// </summary>
+ [Fact]
+ public void ValidPrimitiveDecimalRoundTrip()
+ {
+ Assert.True(this.TestPrimitiveType("Decimal"));
+ }
+
+ /// <summary>
+ /// Test for <see cref="JsonPrimitive"/> round-trip created via <see cref="Double"/>.
+ /// </summary>
+ [Fact]
+ public void ValidPrimitiveDoubleRoundTrip()
+ {
+ Assert.True(this.TestPrimitiveType("Double"));
+ }
+
+ /// <summary>
+ /// Test for <see cref="JsonPrimitive"/> round-trip created via <see cref="Int16"/>.
+ /// </summary>
+ [Fact]
+ public void ValidPrimitiveInt16RoundTrip()
+ {
+ Assert.True(this.TestPrimitiveType("Int16"));
+ }
+
+ /// <summary>
+ /// Test for <see cref="JsonPrimitive"/> round-trip created via <see cref="Int32"/>.
+ /// </summary>
+ [Fact]
+ public void ValidPrimitiveInt32RoundTrip()
+ {
+ Assert.True(this.TestPrimitiveType("Int32"));
+ }
+
+ /// <summary>
+ /// Test for <see cref="JsonPrimitive"/> round-trip created via <see cref="Int64"/>.
+ /// </summary>
+ [Fact]
+ public void ValidPrimitiveInt64RoundTrip()
+ {
+ Assert.True(this.TestPrimitiveType("Int64"));
+ }
+
+ /// <summary>
+ /// Test for <see cref="JsonPrimitive"/> round-trip created via <see cref="SByte"/>.
+ /// </summary>
+ [Fact]
+ public void ValidPrimitiveSByteRoundTrip()
+ {
+ Assert.True(this.TestPrimitiveType("SByte"));
+ }
+
+ /// <summary>
+ /// Test for <see cref="JsonPrimitive"/> round-trip created via <see cref="UInt16"/>.
+ /// </summary>
+ [Fact]
+ public void ValidPrimitiveUInt16RoundTrip()
+ {
+ Assert.True(this.TestPrimitiveType("Uint16"));
+ }
+
+ /// <summary>
+ /// Test for <see cref="JsonPrimitive"/> round-trip created via <see cref="UInt32"/>.
+ /// </summary>
+ [Fact]
+ public void ValidPrimitiveUInt32RoundTrip()
+ {
+ Assert.True(this.TestPrimitiveType("UInt32"));
+ }
+
+ /// <summary>
+ /// Test for <see cref="JsonPrimitive"/> round-trip created via <see cref="UInt64"/>.
+ /// </summary>
+ [Fact]
+ public void ValidPrimitiveUInt64RoundTrip()
+ {
+ Assert.True(this.TestPrimitiveType("UInt64"));
+ }
+
+ /// <summary>
+ /// Test for <see cref="JsonPrimitive"/> round-trip created via <see cref="Char"/>.
+ /// </summary>
+ [Fact]
+ public void ValidPrimitiveCharRoundTrip()
+ {
+ Assert.True(this.TestPrimitiveType("Char"));
+ }
+
+ /// <summary>
+ /// Test for <see cref="JsonPrimitive"/> round-trip created via <see cref="Guid"/>.
+ /// </summary>
+ [Fact]
+ public void ValidPrimitiveGuidRoundTrip()
+ {
+ Assert.True(this.TestPrimitiveType("Guid"));
+ }
+
+ /// <summary>
+ /// Test for <see cref="JsonPrimitive"/> round-trip created via <see cref="Uri"/>.
+ /// </summary>
+ [Fact]
+ public void ValidPrimitiveUriRoundTrip()
+ {
+ Assert.True(this.TestPrimitiveType("Uri"));
+ }
+
+ /// <summary>
+ /// Tests for <see cref="JsonValue"/> round-trip created via <code>null</code> values.
+ /// </summary>
+ [Fact]
+ public void ValidPrimitiveNullRoundTrip()
+ {
+ Assert.True(this.TestPrimitiveType("Null"));
+ }
+
+ /// <summary>
+ /// Tests for round-tripping <see cref="JsonPrimitive"/> objects via casting to CLR instances.
+ /// </summary>
+ [Fact]
+ public void JsonValueRoundTripCastTests()
+ {
+ int seed = 1;
+ Log.Info("Seed: {0}", seed);
+ Random rndGen = new Random(seed);
+
+ this.DoRoundTripCasting(String.Empty, typeof(string));
+ this.DoRoundTripCasting("null", typeof(string));
+ string str;
+ do
+ {
+ str = PrimitiveCreator.CreateInstanceOfString(rndGen);
+ } while (str == null);
+
+ this.DoRoundTripCasting(str, typeof(string));
+ this.DoRoundTripCasting(PrimitiveCreator.CreateInstanceOfInt16(rndGen), typeof(int));
+ this.DoRoundTripCasting(PrimitiveCreator.CreateInstanceOfInt32(rndGen), typeof(int));
+ this.DoRoundTripCasting(PrimitiveCreator.CreateInstanceOfInt64(rndGen), typeof(int));
+ this.DoRoundTripCasting(PrimitiveCreator.CreateInstanceOfUInt16(rndGen), typeof(int));
+ this.DoRoundTripCasting(PrimitiveCreator.CreateInstanceOfUInt32(rndGen), typeof(int));
+ this.DoRoundTripCasting(PrimitiveCreator.CreateInstanceOfUInt64(rndGen), typeof(int));
+ this.DoRoundTripCasting(PrimitiveCreator.CreateInstanceOfGuid(rndGen), typeof(Guid));
+ this.DoRoundTripCasting(new Uri("http://bug/test?param=hello%0a"), typeof(Uri));
+ this.DoRoundTripCasting(PrimitiveCreator.CreateInstanceOfChar(rndGen), typeof(char));
+ this.DoRoundTripCasting(PrimitiveCreator.CreateInstanceOfBoolean(rndGen), typeof(bool));
+ this.DoRoundTripCasting(PrimitiveCreator.CreateInstanceOfDateTime(rndGen), typeof(DateTime));
+ this.DoRoundTripCasting(PrimitiveCreator.CreateInstanceOfDateTimeOffset(rndGen), typeof(DateTimeOffset));
+ this.DoRoundTripCasting(PrimitiveCreator.CreateInstanceOfDouble(rndGen), typeof(double));
+ this.DoRoundTripCasting(PrimitiveCreator.CreateInstanceOfDouble(rndGen), typeof(float));
+ this.DoRoundTripCasting(0.12345f, typeof(double));
+ this.DoRoundTripCasting(0.12345f, typeof(float));
+ }
+
+ private bool TestPrimitiveType(string typeName)
+ {
+ bool retValue = true;
+ bool specialCase = false;
+
+ int seed = 1;
+ Log.Info("Seed: {0}", seed);
+ Random rndGen = new Random(seed);
+
+ JsonPrimitive sourceJson = null;
+ JsonPrimitive sourceJson2;
+ object tempValue = null;
+ switch (typeName.ToLower())
+ {
+ case "boolean":
+ tempValue = PrimitiveCreator.CreateInstanceOfBoolean(rndGen);
+ sourceJson = (JsonPrimitive)JsonValue.Parse(tempValue.ToString().ToLower());
+ sourceJson2 = new JsonPrimitive((bool)tempValue);
+ break;
+ case "byte":
+ tempValue = PrimitiveCreator.CreateInstanceOfByte(rndGen);
+ sourceJson = (JsonPrimitive)JsonValue.Parse(tempValue.ToString());
+ sourceJson2 = new JsonPrimitive((byte)tempValue);
+ break;
+ case "char":
+ sourceJson2 = new JsonPrimitive((char)PrimitiveCreator.CreateInstanceOfChar(rndGen));
+ specialCase = true;
+ break;
+ case "datetime":
+ tempValue = PrimitiveCreator.CreateInstanceOfDateTime(rndGen);
+ sourceJson2 = new JsonPrimitive((DateTime)tempValue);
+ sourceJson = (JsonPrimitive)JsonValue.Parse(sourceJson2.ToString());
+ break;
+ case "decimal":
+ tempValue = PrimitiveCreator.CreateInstanceOfDecimal(rndGen);
+ sourceJson = (JsonPrimitive)JsonValue.Parse(((decimal)tempValue).ToString(NumberFormatInfo.InvariantInfo));
+ sourceJson2 = new JsonPrimitive((decimal)tempValue);
+ break;
+ case "double":
+ double tempDouble = PrimitiveCreator.CreateInstanceOfDouble(rndGen);
+ sourceJson = (JsonPrimitive)JsonValue.Parse(tempDouble.ToString("R", NumberFormatInfo.InvariantInfo));
+ sourceJson2 = new JsonPrimitive(tempDouble);
+ break;
+ case "guid":
+ sourceJson2 = new JsonPrimitive(PrimitiveCreator.CreateInstanceOfGuid(rndGen));
+ specialCase = true;
+ break;
+ case "int16":
+ tempValue = PrimitiveCreator.CreateInstanceOfInt16(rndGen);
+ sourceJson = (JsonPrimitive)JsonValue.Parse(tempValue.ToString());
+ sourceJson2 = new JsonPrimitive((short)tempValue);
+ break;
+ case "int32":
+ tempValue = PrimitiveCreator.CreateInstanceOfInt32(rndGen);
+ sourceJson = (JsonPrimitive)JsonValue.Parse(tempValue.ToString());
+ sourceJson2 = new JsonPrimitive((int)tempValue);
+ break;
+ case "int64":
+ tempValue = PrimitiveCreator.CreateInstanceOfInt64(rndGen);
+ sourceJson = (JsonPrimitive)JsonValue.Parse(tempValue.ToString());
+ sourceJson2 = new JsonPrimitive((long)tempValue);
+ break;
+ case "sbyte":
+ tempValue = PrimitiveCreator.CreateInstanceOfSByte(rndGen);
+ sourceJson = (JsonPrimitive)JsonValue.Parse(tempValue.ToString());
+ sourceJson2 = new JsonPrimitive((sbyte)tempValue);
+ break;
+ case "single":
+ float fltValue = PrimitiveCreator.CreateInstanceOfSingle(rndGen);
+ sourceJson = (JsonPrimitive)JsonValue.Parse(fltValue.ToString("R", NumberFormatInfo.InvariantInfo));
+ sourceJson2 = new JsonPrimitive(fltValue);
+ break;
+ case "string":
+ do
+ {
+ tempValue = PrimitiveCreator.CreateInstanceOfString(rndGen);
+ } while (tempValue == null);
+
+ sourceJson2 = new JsonPrimitive((string)tempValue);
+ sourceJson = (JsonPrimitive)JsonValue.Parse(sourceJson2.ToString());
+ break;
+ case "uint16":
+ tempValue = PrimitiveCreator.CreateInstanceOfUInt16(rndGen);
+ sourceJson = (JsonPrimitive)JsonValue.Parse(tempValue.ToString());
+ sourceJson2 = new JsonPrimitive((ushort)tempValue);
+ break;
+ case "uint32":
+ tempValue = PrimitiveCreator.CreateInstanceOfUInt32(rndGen);
+ sourceJson = (JsonPrimitive)JsonValue.Parse(tempValue.ToString());
+ sourceJson2 = new JsonPrimitive((uint)tempValue);
+ break;
+ case "uint64":
+ tempValue = PrimitiveCreator.CreateInstanceOfUInt64(rndGen);
+ sourceJson = (JsonPrimitive)JsonValue.Parse(tempValue.ToString());
+ sourceJson2 = new JsonPrimitive((ulong)tempValue);
+ break;
+ case "uri":
+ Uri uri = null;
+ do
+ {
+ try
+ {
+ uri = PrimitiveCreator.CreateInstanceOfUri(rndGen);
+ }
+ catch (UriFormatException)
+ {
+ }
+ } while (uri == null);
+
+ sourceJson2 = new JsonPrimitive(uri);
+ specialCase = true;
+ break;
+ case "null":
+ sourceJson = (JsonPrimitive)JsonValue.Parse("null");
+ sourceJson2 = null;
+ break;
+ default:
+ sourceJson = null;
+ sourceJson2 = null;
+ break;
+ }
+
+ if (!specialCase)
+ {
+ // comparison between two constructors
+ if (!JsonValueVerifier.Compare(sourceJson, sourceJson2))
+ {
+ Log.Info("(JsonPrimitive)JsonValue.Parse(string) failed to match the results from default JsonPrimitive(obj)constructor for type {0}", typeName);
+ retValue = false;
+ }
+
+ if (sourceJson != null)
+ {
+ // test JsonValue.Load(TextReader)
+ JsonPrimitive newJson = null;
+ using (StringReader sr = new StringReader(sourceJson.ToString()))
+ {
+ newJson = (JsonPrimitive)JsonValue.Load(sr);
+ }
+
+ if (!JsonValueVerifier.Compare(sourceJson, newJson))
+ {
+ Log.Info("JsonValue.Load(TextReader) failed to function properly for type {0}", typeName);
+ retValue = false;
+ }
+
+ // test JsonValue.Load(Stream) is located in the JObjectFromGenoTypeLib test case
+
+ // test JsonValue.Parse(string)
+ newJson = null;
+ newJson = (JsonPrimitive)JsonValue.Parse(sourceJson.ToString());
+ if (!JsonValueVerifier.Compare(sourceJson, newJson))
+ {
+ Log.Info("JsonValue.Parse(string) failed to function properly for type {0}", typeName);
+ retValue = false;
+ }
+ }
+ }
+ else
+ {
+ // test JsonValue.Load(TextReader)
+ JsonPrimitive newJson2 = null;
+ using (StringReader sr = new StringReader(sourceJson2.ToString()))
+ {
+ newJson2 = (JsonPrimitive)JsonValue.Load(sr);
+ }
+
+ if (!JsonValueVerifier.Compare(sourceJson2, newJson2))
+ {
+ Log.Info("JsonValue.Load(TextReader) failed to function properly for type {0}", typeName);
+ retValue = false;
+ }
+
+ // test JsonValue.Load(Stream) is located in the JObjectFromGenoTypeLib test case
+
+ // test JsonValue.Parse(string)
+ newJson2 = null;
+ newJson2 = (JsonPrimitive)JsonValue.Parse(sourceJson2.ToString());
+ if (!JsonValueVerifier.Compare(sourceJson2, newJson2))
+ {
+ Log.Info("JsonValue.Parse(string) failed to function properly for type {0}", typeName);
+ retValue = false;
+ }
+ }
+
+ return retValue;
+ }
+
+ private void DoRoundTripCasting(JsonValue jo, Type type)
+ {
+ bool result = false;
+
+ // Casting
+ if (jo.JsonType == JsonType.String)
+ {
+ JsonValue jstr = (string)jo;
+ if (type == typeof(DateTime))
+ {
+ Log.Info("{0} Value:{1}", type.Name, ((DateTime)jstr).ToString(DateTimeFormatInfo.InvariantInfo));
+ }
+ else if (type == typeof(DateTimeOffset))
+ {
+ Log.Info("{0} Value:{1}", type.Name, ((DateTimeOffset)jstr).ToString(DateTimeFormatInfo.InvariantInfo));
+ }
+ else if (type == typeof(Guid))
+ {
+ Log.Info("{0} Value:{1}", type.Name, (Guid)jstr);
+ }
+ else if (type == typeof(char))
+ {
+ Log.Info("{0} Value:{1}", type.Name, (char)jstr);
+ }
+ else if (type == typeof(Uri))
+ {
+ Log.Info("{0} Value:{1}", type.Name, ((Uri)jstr).AbsoluteUri);
+ }
+ else
+ {
+ Log.Info("{0} Value:{1}", type.Name, (string)jstr);
+ }
+
+ if (jo.ToString() == jstr.ToString())
+ {
+ result = true;
+ }
+ }
+ else if (jo.JsonType == JsonType.Object)
+ {
+ JsonObject jobj = new JsonObject((JsonObject)jo);
+
+ if (jo.ToString() == jobj.ToString())
+ {
+ result = true;
+ }
+ }
+ else if (jo.JsonType == JsonType.Number)
+ {
+ JsonPrimitive jprim = (JsonPrimitive)jo;
+ Log.Info("{0} Value:{1}", type.Name, jprim);
+
+ if (jo.ToString() == jprim.ToString())
+ {
+ result = true;
+ }
+ }
+ else if (jo.JsonType == JsonType.Boolean)
+ {
+ JsonPrimitive jprim = (JsonPrimitive)jo;
+ Log.Info("{0} Value:{1}", type.Name, (bool)jprim);
+
+ if (jo.ToString() == jprim.ToString())
+ {
+ result = true;
+ }
+ }
+
+ Assert.True(result);
+ }
+ }
+}
diff --git a/test/System.Json.Test.Integration/JsonValueAndComplexTypesTests.cs b/test/System.Json.Test.Integration/JsonValueAndComplexTypesTests.cs
new file mode 100644
index 00000000..3b878d04
--- /dev/null
+++ b/test/System.Json.Test.Integration/JsonValueAndComplexTypesTests.cs
@@ -0,0 +1,329 @@
+using System.Collections.Generic;
+using System.Dynamic;
+using System.IO;
+using System.Runtime.Serialization.Json;
+using System.Text;
+using Xunit;
+
+namespace System.Json
+{
+ /// <summary>
+ /// Tests for the methods to convert between <see cref="JsonValue"/> instances and complex types.
+ /// </summary>
+ public class JsonValueAndComplexTypesTests
+ {
+ static readonly Type[] testTypes = new Type[]
+ {
+ typeof(DCType_1),
+ typeof(StructGuid),
+ typeof(StructInt16),
+ typeof(DCType_3),
+ typeof(SerType_4),
+ typeof(SerType_5),
+ typeof(DCType_7),
+ typeof(DCType_9),
+ typeof(SerType_11),
+ typeof(DCType_15),
+ typeof(DCType_16),
+ typeof(DCType_18),
+ typeof(DCType_19),
+ typeof(DCType_20),
+ typeof(SerType_22),
+ typeof(DCType_25),
+ typeof(SerType_26),
+ typeof(DCType_31),
+ typeof(DCType_32),
+ typeof(SerType_33),
+ typeof(DCType_34),
+ typeof(DCType_36),
+ typeof(DCType_38),
+ typeof(DCType_40),
+ typeof(DCType_42),
+ typeof(DCType_65),
+ typeof(ListType_1),
+ typeof(ListType_2),
+ typeof(BaseType),
+ typeof(PolymorphicMember),
+ typeof(PolymorphicAsInterfaceMember),
+ typeof(CollectionsWithPolymorphicMember),
+ };
+
+ /// <summary>
+ /// Tests for the <see cref="JsonValueExtensions.CreateFrom"/> method.
+ /// </summary>
+ [Fact]
+ public void CreateFromTests()
+ {
+ InstanceCreatorSurrogate oldSurrogate = CreatorSettings.CreatorSurrogate;
+ try
+ {
+ CreatorSettings.CreatorSurrogate = new NoInfinityFloatSurrogate();
+ DateTime now = DateTime.Now;
+ int seed = (10000 * now.Year) + (100 * now.Month) + now.Day;
+ Log.Info("Seed: {0}", seed);
+ Random rndGen = new Random(seed);
+ foreach (Type testType in testTypes)
+ {
+ object instance = InstanceCreator.CreateInstanceOf(testType, rndGen);
+ JsonValue jv = JsonValueExtensions.CreateFrom(instance);
+
+ if (instance == null)
+ {
+ Assert.Null(jv);
+ }
+ else
+ {
+ DataContractJsonSerializer dcjs = new DataContractJsonSerializer(instance == null ? testType : instance.GetType());
+ string fromDCJS;
+ using (MemoryStream ms = new MemoryStream())
+ {
+ dcjs.WriteObject(ms, instance);
+ fromDCJS = Encoding.UTF8.GetString(ms.ToArray());
+ }
+
+ Log.Info("{0}: {1}", testType.Name, fromDCJS);
+
+ if (instance == null)
+ {
+ Assert.Null(jv);
+ }
+ else
+ {
+ string fromJsonValue = jv.ToString();
+ Assert.Equal(fromDCJS, fromJsonValue);
+ }
+ }
+ }
+ }
+ finally
+ {
+ CreatorSettings.CreatorSurrogate = oldSurrogate;
+ }
+ }
+
+ /// <summary>
+ /// Tests for the <see cref="JsonValueExtensions.ReadAsType{T}(JsonValue)"/> method.
+ /// </summary>
+ [Fact]
+ public void ReadAsTests()
+ {
+ InstanceCreatorSurrogate oldSurrogate = CreatorSettings.CreatorSurrogate;
+ try
+ {
+ CreatorSettings.CreatorSurrogate = new NoInfinityFloatSurrogate();
+ DateTime now = DateTime.Now;
+ int seed = (10000 * now.Year) + (100 * now.Month) + now.Day;
+ Log.Info("Seed: {0}", seed);
+ Random rndGen = new Random(seed);
+
+ this.ReadAsTest<DCType_1>(rndGen);
+ this.ReadAsTest<StructGuid>(rndGen);
+ this.ReadAsTest<StructInt16>(rndGen);
+ this.ReadAsTest<DCType_3>(rndGen);
+ this.ReadAsTest<SerType_4>(rndGen);
+ this.ReadAsTest<SerType_5>(rndGen);
+ this.ReadAsTest<DCType_7>(rndGen);
+ this.ReadAsTest<DCType_9>(rndGen);
+ this.ReadAsTest<SerType_11>(rndGen);
+ this.ReadAsTest<DCType_15>(rndGen);
+ this.ReadAsTest<DCType_16>(rndGen);
+ this.ReadAsTest<DCType_18>(rndGen);
+ this.ReadAsTest<DCType_19>(rndGen);
+ this.ReadAsTest<DCType_20>(rndGen);
+ this.ReadAsTest<SerType_22>(rndGen);
+ this.ReadAsTest<DCType_25>(rndGen);
+ this.ReadAsTest<SerType_26>(rndGen);
+ this.ReadAsTest<DCType_31>(rndGen);
+ this.ReadAsTest<DCType_32>(rndGen);
+ this.ReadAsTest<SerType_33>(rndGen);
+ this.ReadAsTest<DCType_34>(rndGen);
+ this.ReadAsTest<DCType_36>(rndGen);
+ this.ReadAsTest<DCType_38>(rndGen);
+ this.ReadAsTest<DCType_40>(rndGen);
+ this.ReadAsTest<DCType_42>(rndGen);
+ this.ReadAsTest<DCType_65>(rndGen);
+ this.ReadAsTest<ListType_1>(rndGen);
+ this.ReadAsTest<ListType_2>(rndGen);
+ this.ReadAsTest<BaseType>(rndGen);
+ this.ReadAsTest<PolymorphicMember>(rndGen);
+ this.ReadAsTest<PolymorphicAsInterfaceMember>(rndGen);
+ this.ReadAsTest<CollectionsWithPolymorphicMember>(rndGen);
+ }
+ finally
+ {
+ CreatorSettings.CreatorSurrogate = oldSurrogate;
+ }
+ }
+
+ /// <summary>
+ /// Tests for the <see cref="JsonValueExtensions.CreateFrom"/> for <see cref="DateTime"/>
+ /// and <see cref="DateTimeOffset"/> values.
+ /// </summary>
+ [Fact]
+ public void CreateFromDateTimeTest()
+ {
+ DateTime dt = DateTime.Now;
+ DateTimeOffset dto = DateTimeOffset.Now;
+
+ JsonValue jvDt1 = (JsonValue)dt;
+ JsonValue jvDt2 = JsonValueExtensions.CreateFrom(dt);
+
+ JsonValue jvDto1 = (JsonValue)dto;
+ JsonValue jvDto2 = JsonValueExtensions.CreateFrom(dto);
+
+ Assert.Equal(dt, (DateTime)jvDt1);
+ Assert.Equal(dt, (DateTime)jvDt2);
+
+ Assert.Equal(dto, (DateTimeOffset)jvDto1);
+ Assert.Equal(dto, (DateTimeOffset)jvDto2);
+
+ Assert.Equal(dt, jvDt1.ReadAs<DateTime>());
+ Assert.Equal(dt, jvDt2.ReadAs<DateTime>());
+
+ Assert.Equal(dto, jvDto1.ReadAs<DateTimeOffset>());
+ Assert.Equal(dto, jvDto2.ReadAs<DateTimeOffset>());
+
+ Assert.Equal(jvDt1.ToString(), jvDt2.ToString());
+ Assert.Equal(jvDto1.ToString(), jvDto2.ToString());
+ }
+
+ /// <summary>
+ /// Tests for creating <see cref="JsonValue"/> instances from dynamic objects.
+ /// </summary>
+ [Fact]
+ public void CreateFromDynamic()
+ {
+ string expectedJson = "{\"int\":12,\"str\":\"hello\",\"jv\":[1,{\"a\":true}],\"dyn\":{\"char\":\"c\",\"null\":null}}";
+ MyDynamicObject obj = new MyDynamicObject();
+ obj.fields.Add("int", 12);
+ obj.fields.Add("str", "hello");
+ obj.fields.Add("jv", new JsonArray(1, new JsonObject { { "a", true } }));
+ MyDynamicObject dyn = new MyDynamicObject();
+ obj.fields.Add("dyn", dyn);
+ dyn.fields.Add("char", 'c');
+ dyn.fields.Add("null", null);
+
+ JsonValue jv = JsonValueExtensions.CreateFrom(obj);
+ Assert.Equal(expectedJson, jv.ToString());
+ }
+
+ void ReadAsTest<T>(Random rndGen)
+ {
+ T instance = InstanceCreator.CreateInstanceOf<T>(rndGen);
+ Log.Info("ReadAsTest<{0}>, instance = {1}", typeof(T).Name, instance);
+ DataContractJsonSerializer dcjs = new DataContractJsonSerializer(typeof(T));
+ JsonValue jv;
+ using (MemoryStream ms = new MemoryStream())
+ {
+ dcjs.WriteObject(ms, instance);
+ Log.Info("{0}: {1}", typeof(T).Name, Encoding.UTF8.GetString(ms.ToArray()));
+ ms.Position = 0;
+ jv = JsonValue.Load(ms);
+ }
+
+ if (instance == null)
+ {
+ Assert.Null(jv);
+ }
+ else
+ {
+ T newInstance = jv.ReadAsType<T>();
+ Assert.Equal(instance, newInstance);
+ }
+ }
+
+ /// <summary>
+ /// Test class.
+ /// </summary>
+ public class MyDynamicObject : DynamicObject
+ {
+ /// <summary>
+ /// Test member
+ /// </summary>
+ public Dictionary<string, object> fields = new Dictionary<string, object>();
+
+ /// <summary>
+ /// Returnes the member names in this dynamic object.
+ /// </summary>
+ /// <returns>The member names in this dynamic object.</returns>
+ public override IEnumerable<string> GetDynamicMemberNames()
+ {
+ return fields.Keys;
+ }
+
+ /// <summary>
+ /// Attempts to get a named member from this dynamic object.
+ /// </summary>
+ /// <param name="binder">The dynamic binder which contains the member name.</param>
+ /// <param name="result">The value of the member, if it exists in this dynamic object.</param>
+ /// <returns><code>true</code> if the member can be returned; <code>false</code> otherwise.</returns>
+ public override bool TryGetMember(GetMemberBinder binder, out object result)
+ {
+ if (binder != null && binder.Name != null && this.fields.ContainsKey(binder.Name))
+ {
+ result = this.fields[binder.Name];
+ return true;
+ }
+
+ return base.TryGetMember(binder, out result);
+ }
+
+ /// <summary>
+ /// Attempts to set a named member from this dynamic object.
+ /// </summary>
+ /// <param name="binder">The dynamic binder which contains the member name.</param>
+ /// <param name="value">The value of the member to be set.</param>
+ /// <returns><code>true</code> if the member can be set; <code>false</code> otherwise.</returns>
+ public override bool TrySetMember(SetMemberBinder binder, object value)
+ {
+ if (binder != null && binder.Name != null)
+ {
+ this.fields[binder.Name] = value;
+ return true;
+ }
+
+ return base.TrySetMember(binder, value);
+ }
+ }
+
+ // Currently there are some differences in treatment of infinity between
+ // JsonValue (which writes them as Infinity/-Infinity) and DataContractJsonSerializer
+ // (which writes them as INF/-INF). This prevents those values from being used in the test.
+ // This also allows the creation of an instance of an IEmptyInterface type, used in the test.
+ class NoInfinityFloatSurrogate : InstanceCreatorSurrogate
+ {
+ public override bool CanCreateInstanceOf(Type type)
+ {
+ return type == typeof(float) || type == typeof(double) || type == typeof(IEmptyInterface) || type == typeof(BaseType);
+ }
+
+ public override object CreateInstanceOf(Type type, Random rndGen)
+ {
+ if (type == typeof(float))
+ {
+ float result;
+ do
+ {
+ result = PrimitiveCreator.CreateInstanceOfSingle(rndGen);
+ }
+ while (float.IsInfinity(result));
+ return result;
+ }
+ else if (type == typeof(double))
+ {
+ double result;
+ do
+ {
+ result = PrimitiveCreator.CreateInstanceOfDouble(rndGen);
+ }
+ while (double.IsInfinity(result));
+ return result;
+ }
+ else
+ {
+ return new DerivedType(rndGen);
+ }
+ }
+ }
+ }
+}
diff --git a/test/System.Json.Test.Integration/JsonValueDynamicTests.cs b/test/System.Json.Test.Integration/JsonValueDynamicTests.cs
new file mode 100644
index 00000000..7a21757a
--- /dev/null
+++ b/test/System.Json.Test.Integration/JsonValueDynamicTests.cs
@@ -0,0 +1,1108 @@
+using System.Collections.Generic;
+using System.Dynamic;
+using System.Globalization;
+using System.Reflection;
+using System.Runtime.Serialization.Json;
+using Xunit;
+
+namespace System.Json
+{
+ /// <summary>
+ /// Tests for the dynamic support for <see cref="JsonValue"/>.
+ /// </summary>
+ public class JsonValueDynamicTests
+ {
+ string teamNameValue = "WCF RIA Base";
+ string[] teamMembersValues = { "Carlos", "Chris", "Joe", "Miguel", "Yavor" };
+
+ /// <summary>
+ /// Tests for the dynamic getters in <see cref="JsonObject"/> instances.
+ /// </summary>
+ [Fact]
+ public void JsonObjectDynamicGetters()
+ {
+ dynamic team = new JsonObject();
+ team["TeamSize"] = this.teamMembersValues.Length;
+ team["TeamName"] = this.teamNameValue;
+ team["TeamMascots"] = null;
+ team["TeamMembers"] = new JsonArray
+ {
+ this.teamMembersValues[0], this.teamMembersValues[1], this.teamMembersValues[2],
+ this.teamMembersValues[3], this.teamMembersValues[4]
+ };
+
+ Assert.Equal(this.teamMembersValues.Length, (int)team.TeamSize);
+ Assert.Equal(this.teamNameValue, (string)team.TeamName);
+ Assert.NotNull(team.TeamMascots);
+ Assert.True(team.TeamMascots is JsonValue); // default
+
+ for (int i = 0; i < this.teamMembersValues.Length; i++)
+ {
+ Assert.Equal(this.teamMembersValues[i], (string)team.TeamMembers[i]);
+ }
+
+ for (int i = 0; i < this.teamMembersValues.Length; i++)
+ {
+ Assert.Equal(this.teamMembersValues[i], (string)team.TeamMembers[i]);
+ }
+
+ // Negative tests for getters
+ JsonValueTests.ExpectException<InvalidCastException>(delegate { int fail = (int)team.NonExistentProp; });
+ }
+
+ /// <summary>
+ /// Tests for the dynamic setters in <see cref="JsonObject"/> instances.
+ /// </summary>
+ [Fact]
+ public void JsonObjectDynamicSetters()
+ {
+ dynamic team = new JsonObject();
+ team.TeamSize = this.teamMembersValues.Length;
+ team.TeamName = this.teamNameValue;
+ team.TeamMascots = null;
+ team.TeamMembers = new JsonArray
+ {
+ this.teamMembersValues[0], this.teamMembersValues[1], this.teamMembersValues[2],
+ this.teamMembersValues[3], this.teamMembersValues[4]
+ };
+
+ Assert.Equal(this.teamMembersValues.Length, (int)team["TeamSize"]);
+ Assert.Equal(this.teamNameValue, (string)team["TeamName"]);
+ Assert.NotNull(team["TeamMascots"]);
+ Assert.True(team["TeamMascots"] is JsonValue);
+
+ for (int i = 0; i < this.teamMembersValues.Length; i++)
+ {
+ Assert.Equal(this.teamMembersValues[i], (string)team["TeamMembers"][i]);
+ }
+
+ // Could not come up with negative setter
+ }
+
+ /// <summary>
+ /// Tests for the dynamic indexers in <see cref="JsonArray"/> instances.
+ /// </summary>
+ [Fact]
+ public void JsonArrayDynamicSanity()
+ {
+ // Sanity test for JsonArray to ensure [] still works even if dynamic
+ dynamic people = new JsonArray();
+ foreach (string member in this.teamMembersValues)
+ {
+ people.Add(member);
+ }
+
+ Assert.Equal(this.teamMembersValues[0], (string)people[0]);
+ Assert.Equal(this.teamMembersValues[1], (string)people[1]);
+ Assert.Equal(this.teamMembersValues[2], (string)people[2]);
+ Assert.Equal(this.teamMembersValues[3], (string)people[3]);
+ Assert.Equal(this.teamMembersValues[4], (string)people[4]);
+
+ // Note: this test and the above execute the dynamic binder differently.
+ for (int i = 0; i < people.Count; i++)
+ {
+ Assert.Equal(this.teamMembersValues[i], (string)people[i]);
+ }
+
+ people.Add(this.teamMembersValues.Length);
+ people.Add(this.teamNameValue);
+
+ Assert.Equal(this.teamMembersValues.Length, (int)people[5]);
+ Assert.Equal(this.teamNameValue, (string)people[6]);
+ }
+
+ /// <summary>
+ /// Tests for calling methods in dynamic references to <see cref="JsonValue"/> instances.
+ /// </summary>
+ [Fact]
+ public void DynamicMethodCalling()
+ {
+ JsonObject jo = new JsonObject();
+ dynamic dyn = jo;
+ dyn.Foo = "bar";
+ Assert.Equal(1, jo.Count);
+ Assert.Equal(1, dyn.Count);
+ dyn.Remove("Foo");
+ Assert.Equal(0, jo.Count);
+ }
+
+ /// <summary>
+ /// Tests for using boolean operators in dynamic references to <see cref="JsonValue"/> instances.
+ /// </summary>
+ [Fact(Skip = "Ignore")]
+ public void DynamicBooleanOperators()
+ {
+ JsonValue jv;
+ dynamic dyn;
+ foreach (bool value in new bool[] { true, false })
+ {
+ jv = value;
+ dyn = jv;
+ Log.Info("IsTrue, {0}", jv);
+ if (dyn)
+ {
+ Assert.True(value, "Boolean evaluation should not enter 'if' clause.");
+ }
+ else
+ {
+ Assert.False(value, "Boolean evaluation should not enter 'else' clause.");
+ }
+ }
+
+ foreach (string value in new string[] { "true", "false", "True", "False" })
+ {
+ bool isTrueValue = value.Equals("true", StringComparison.InvariantCultureIgnoreCase);
+ jv = new JsonPrimitive(value);
+ dyn = jv;
+ Log.Info("IsTrue, {0}", jv);
+ if (dyn)
+ {
+ Assert.True(isTrueValue, "Boolean evaluation should not enter 'if' clause.");
+ }
+ else
+ {
+ Assert.False(isTrueValue, "Boolean evaluation should not enter 'else' clause.");
+ }
+ }
+
+ foreach (bool first in new bool[] { false, true })
+ {
+ dynamic dyn1 = new JsonPrimitive(first);
+ Log.Info("Negation, {0}", first);
+ Assert.Equal(!first, !dyn1);
+ foreach (bool second in new bool[] { false, true })
+ {
+ dynamic dyn2 = new JsonPrimitive(second);
+ Log.Info("Boolean AND, {0} && {1}", first, second);
+ Assert.Equal(first && second, (bool)(dyn1 && dyn2));
+ Log.Info("Boolean OR, {0} && {1}", first, second);
+ Assert.Equal(first || second, (bool)(dyn1 || dyn2));
+ }
+ }
+
+ Log.Info("Invalid boolean operator usage");
+ dynamic boolDyn = new JsonPrimitive(true);
+ dynamic intDyn = new JsonPrimitive(1);
+ dynamic strDyn = new JsonPrimitive("hello");
+
+ JsonValueTests.ExpectException<InvalidOperationException>(() => { Log.Info("{0}", !intDyn); });
+
+ JsonValueTests.ExpectException<InvalidOperationException>(() => { Log.Info("{0}", !strDyn); });
+ JsonValueTests.ExpectException<InvalidCastException>(() => { Log.Info("{0}", intDyn && intDyn); });
+ JsonValueTests.ExpectException<InvalidCastException>(() => { Log.Info("{0}", intDyn || true); });
+ JsonValueTests.ExpectException<InvalidOperationException>(() => { Log.Info("{0}", boolDyn && 1); });
+ JsonValueTests.ExpectException<InvalidOperationException>(() => { Log.Info("{0}", boolDyn && intDyn); });
+ JsonValueTests.ExpectException<InvalidOperationException>(() => { Log.Info("{0}", boolDyn && "hello"); });
+ JsonValueTests.ExpectException<InvalidOperationException>(() => { Log.Info("{0}", boolDyn && strDyn); });
+ JsonValueTests.ExpectException<FormatException>(() => { Log.Info("{0}", strDyn && boolDyn); });
+ JsonValueTests.ExpectException<FormatException>(() => { Log.Info("{0}", strDyn || true); });
+
+ JsonValueTests.ExpectException<InvalidOperationException>(() => { Log.Info("{0}", !intDyn.NotHere); });
+ JsonValueTests.ExpectException<InvalidOperationException>(() => { Log.Info("{0}", !intDyn.NotHere && true); });
+ JsonValueTests.ExpectException<InvalidOperationException>(() => { Log.Info("{0}", !intDyn.NotHere || false); });
+ }
+
+ /// <summary>
+ /// Tests for using relational operators in dynamic references to <see cref="JsonValue"/> instances.
+ /// </summary>
+ [Fact(Skip = "Ignore")]
+ public void DynamicRelationalOperators()
+ {
+ JsonValue jv = new JsonObject { { "one", 1 }, { "one_point_two", 1.2 }, { "decimal_one_point_one", 1.1m }, { "trueValue", true }, { "str", "hello" } };
+ dynamic dyn = jv;
+ JsonValue defaultJsonValue = jv.ValueOrDefault(-1);
+
+ Log.Info("Equality");
+ Assert.True(dyn.one == 1);
+ Assert.True(dyn.one_point_two == 1.2);
+ Assert.False(dyn.one == 1.2);
+ Assert.False(dyn.one_point_two == 1);
+ Assert.False(dyn.one == 2);
+ Assert.False(dyn.one_point_two == 1.3);
+ Assert.True(dyn.one == 1m);
+ Assert.False(dyn.one == 2m);
+ Assert.True(dyn.decimal_one_point_one == 1.1m);
+
+ Assert.True(dyn.NotHere == null);
+ Assert.True(dyn.NotHere == dyn.NotHere);
+ Assert.True(dyn.NotHere == defaultJsonValue);
+ // DISABLED, 197375, Assert.False(dyn.NotHere == 1);
+ Assert.False(dyn.NotHere == jv);
+
+ Log.Info("Inequality");
+ Assert.False(dyn.one != 1);
+ Assert.False(dyn.one_point_two != 1.2);
+ Assert.True(dyn.one != 1.2);
+ Assert.True(dyn.one_point_two != 1);
+ Assert.True(dyn.one != 2);
+ Assert.True(dyn.one_point_two != 1.3);
+ Assert.False(dyn.one != 1m);
+ Assert.True(dyn.one != 2m);
+
+ Assert.False(dyn.NotHere != null);
+ Assert.False(dyn.NotHere != dyn.NotHere);
+ Assert.False(dyn.NotHere != defaultJsonValue);
+ // DISABLED, 197375, Assert.True(dyn.NotHere != 1);
+ Assert.True(dyn.NotHere != jv);
+
+ Log.Info("Less than");
+ Assert.True(dyn.one < 2);
+ Assert.False(dyn.one < 1);
+ Assert.False(dyn.one < 0);
+ Assert.True(dyn.one_point_two < 1.3);
+ Assert.False(dyn.one_point_two < 1.2);
+ Assert.False(dyn.one_point_two < 1.1);
+
+ Assert.True(dyn.one < 1.1);
+ Assert.Equal(1 < 1.0, dyn.one < 1.0);
+ Assert.False(dyn.one < 0.9);
+ Assert.True(dyn.one_point_two < 2);
+ Assert.False(dyn.one_point_two < 1);
+ Assert.Equal(1.2 < 1.2f, dyn.one_point_two < 1.2f);
+
+ Log.Info("Greater than");
+ Assert.False(dyn.one > 2);
+ Assert.False(dyn.one > 1);
+ Assert.True(dyn.one > 0);
+ Assert.False(dyn.one_point_two > 1.3);
+ Assert.False(dyn.one_point_two > 1.2);
+ Assert.True(dyn.one_point_two > 1.1);
+
+ Assert.False(dyn.one > 1.1);
+ Assert.Equal(1 > 1.0, dyn.one > 1.0);
+ Assert.True(dyn.one > 0.9);
+ Assert.False(dyn.one_point_two > 2);
+ Assert.True(dyn.one_point_two > 1);
+ Assert.Equal(1.2 > 1.2f, dyn.one_point_two > 1.2f);
+
+ Log.Info("Less than or equals");
+ Assert.True(dyn.one <= 2);
+ Assert.True(dyn.one <= 1);
+ Assert.False(dyn.one <= 0);
+ Assert.True(dyn.one_point_two <= 1.3);
+ Assert.True(dyn.one_point_two <= 1.2);
+ Assert.False(dyn.one_point_two <= 1.1);
+
+ Assert.True(dyn.one <= 1.1);
+ Assert.Equal(1 <= 1.0, dyn.one <= 1.0);
+ Assert.False(dyn.one <= 0.9);
+ Assert.True(dyn.one_point_two <= 2);
+ Assert.False(dyn.one_point_two <= 1);
+ Assert.Equal(1.2 <= 1.2f, dyn.one_point_two <= 1.2f);
+
+ Log.Info("Greater than or equals");
+ Assert.False(dyn.one >= 2);
+ Assert.True(dyn.one >= 1);
+ Assert.True(dyn.one >= 0);
+ Assert.False(dyn.one_point_two >= 1.3);
+ Assert.True(dyn.one_point_two >= 1.2);
+ Assert.True(dyn.one_point_two >= 1.1);
+
+ Assert.False(dyn.one >= 1.1);
+ Assert.Equal(1 >= 1.0, dyn.one >= 1.0);
+ Assert.True(dyn.one >= 0.9);
+ Assert.False(dyn.one_point_two >= 2);
+ Assert.True(dyn.one_point_two >= 1);
+ Assert.Equal(1.2 >= 1.2f, dyn.one_point_two >= 1.2f);
+
+ Log.Info("Invalid number conversions");
+ JsonValueTests.ExpectException<InvalidOperationException>(() => { Log.Info(dyn.decimal_one_point_one == 1.1); });
+ JsonValueTests.ExpectException<InvalidOperationException>(() => { Log.Info(dyn.one != (uint)2); });
+
+ Log.Info("Invalid data types for relational operators");
+ JsonValueTests.ExpectException<InvalidOperationException>(() => { Log.Info(dyn.trueValue >= dyn.trueValue); });
+ JsonValueTests.ExpectException<InvalidOperationException>(() => { Log.Info(dyn.NotHere < dyn.NotHere); });
+ JsonValueTests.ExpectException<InvalidOperationException>(() => { Log.Info(dyn.str < "Jello"); });
+
+ // DISABLED, 197315
+ Log.Info("Conversions from string");
+ jv = new JsonObject { { "one", "1" }, { "twelve_point_two", "1.22e1" } };
+ dyn = jv;
+ Assert.True(dyn.one == 1);
+ Assert.True(dyn.twelve_point_two == 1.22e1);
+ Assert.True(dyn.one >= 0.5f);
+ Assert.True(dyn.twelve_point_two <= 13);
+ Assert.True(dyn.one < 2);
+ Assert.Equal(dyn.twelve_point_two.ReadAs<int>() > 12, dyn.twelve_point_two > 12);
+ }
+
+ /// <summary>
+ /// Tests for using arithmetic operators in dynamic references to <see cref="JsonValue"/> instances.
+ /// </summary>
+ [Fact(Skip = "Ignore")]
+ public void ArithmeticOperators()
+ {
+ int seed = MethodBase.GetCurrentMethod().Name.GetHashCode();
+ Log.Info("Seed: {0}", seed);
+ Random rndGen = new Random(seed);
+ int i1 = rndGen.Next(-10000, 10000);
+ int i2 = rndGen.Next(-10000, 10000);
+ JsonValue jv1 = i1;
+ JsonValue jv2 = i2;
+ Log.Info("jv1 = {0}, jv2 = {1}", jv1, jv2);
+ dynamic dyn1 = jv1;
+ dynamic dyn2 = jv2;
+
+ string str1 = i1.ToString(CultureInfo.InvariantCulture);
+ string str2 = i2.ToString(CultureInfo.InvariantCulture);
+ JsonValue jvstr1 = str1;
+ JsonValue jvstr2 = str2;
+
+ Log.Info("Unary +");
+ Assert.Equal<int>(+i1, +dyn1);
+ Assert.Equal<int>(+i2, +dyn2);
+
+ Log.Info("Unary -");
+ Assert.Equal<int>(-i1, -dyn1);
+ Assert.Equal<int>(-i2, -dyn2);
+
+ Log.Info("Unary ~ (bitwise NOT)");
+ Assert.Equal<int>(~i1, ~dyn1);
+ Assert.Equal<int>(~i2, ~dyn2);
+
+ Log.Info("Binary +: {0}", i1 + i2);
+ Assert.Equal<int>(i1 + i2, dyn1 + dyn2);
+ Assert.Equal<int>(i1 + i2, dyn2 + dyn1);
+ Assert.Equal<int>(i1 + i2, dyn1 + i2);
+ Assert.Equal<int>(i1 + i2, dyn2 + i1);
+
+ // DISABLED, 197394
+ // Assert.Equal<int>(i1 + i2, dyn1 + str2);
+ // Assert.Equal<int>(i1 + i2, dyn1 + jvstr2);
+
+ Log.Info("Binary -: {0}, {1}", i1 - i2, i2 - i1);
+ Assert.Equal<int>(i1 - i2, dyn1 - dyn2);
+ Assert.Equal<int>(i2 - i1, dyn2 - dyn1);
+ Assert.Equal<int>(i1 - i2, dyn1 - i2);
+ Assert.Equal<int>(i2 - i1, dyn2 - i1);
+
+ Log.Info("Binary *: {0}", i1 * i2);
+ Assert.Equal<int>(i1 * i2, dyn1 * dyn2);
+ Assert.Equal<int>(i1 * i2, dyn2 * dyn1);
+ Assert.Equal<int>(i1 * i2, dyn1 * i2);
+ Assert.Equal<int>(i1 * i2, dyn2 * i1);
+
+ while (i1 == 0)
+ {
+ i1 = rndGen.Next(-10000, 10000);
+ jv1 = i1;
+ dyn1 = jv1;
+ Log.Info("Using new (non-zero) i1 value: {0}", i1);
+ }
+
+ while (i2 == 0)
+ {
+ i2 = rndGen.Next(-10000, 10000);
+ jv2 = i2;
+ dyn2 = jv2;
+ Log.Info("Using new (non-zero) i2 value: {0}", i2);
+ }
+
+ Log.Info("Binary / (integer division): {0}, {1}", i1 / i2, i2 / i1);
+ Assert.Equal<int>(i1 / i2, dyn1 / dyn2);
+ Assert.Equal<int>(i2 / i1, dyn2 / dyn1);
+ Assert.Equal<int>(i1 / i2, dyn1 / i2);
+ Assert.Equal<int>(i2 / i1, dyn2 / i1);
+
+ Log.Info("Binary % (modulo): {0}, {1}", i1 % i2, i2 % i1);
+ Assert.Equal<int>(i1 % i2, dyn1 % dyn2);
+ Assert.Equal<int>(i2 % i1, dyn2 % dyn1);
+ Assert.Equal<int>(i1 % i2, dyn1 % i2);
+ Assert.Equal<int>(i2 % i1, dyn2 % i1);
+
+ Log.Info("Binary & (bitwise AND): {0}", i1 & i2);
+ Assert.Equal<int>(i1 & i2, dyn1 & dyn2);
+ Assert.Equal<int>(i1 & i2, dyn2 & dyn1);
+ Assert.Equal<int>(i1 & i2, dyn1 & i2);
+ Assert.Equal<int>(i1 & i2, dyn2 & i1);
+
+ Log.Info("Binary | (bitwise OR): {0}", i1 | i2);
+ Assert.Equal<int>(i1 | i2, dyn1 | dyn2);
+ Assert.Equal<int>(i1 | i2, dyn2 | dyn1);
+ Assert.Equal<int>(i1 | i2, dyn1 | i2);
+ Assert.Equal<int>(i1 | i2, dyn2 | i1);
+
+ Log.Info("Binary ^ (bitwise XOR): {0}", i1 ^ i2);
+ Assert.Equal<int>(i1 ^ i2, dyn1 ^ dyn2);
+ Assert.Equal<int>(i1 ^ i2, dyn2 ^ dyn1);
+ Assert.Equal<int>(i1 ^ i2, dyn1 ^ i2);
+ Assert.Equal<int>(i1 ^ i2, dyn2 ^ i1);
+
+ i1 = rndGen.Next(1, 10);
+ i2 = rndGen.Next(1, 10);
+ jv1 = i1;
+ jv2 = i2;
+ dyn1 = jv1;
+ dyn2 = jv2;
+ Log.Info("New i1, i2: {0}, {1}", i1, i2);
+
+ Log.Info("Left shift: {0}", i1 << i2);
+ Assert.Equal<int>(i1 << i2, dyn1 << dyn2);
+ Assert.Equal<int>(i1 << i2, dyn1 << i2);
+
+ i1 = i1 << i2;
+ jv1 = i1;
+ dyn1 = jv1;
+ Log.Info("New i1: {0}", i1);
+ Log.Info("Right shift: {0}", i1 >> i2);
+ Assert.Equal<int>(i1 >> i2, dyn1 >> dyn2);
+ Assert.Equal<int>(i1 >> i2, dyn1 >> i2);
+
+ i2 += 4;
+ jv2 = i2;
+ dyn2 = jv2;
+ Log.Info("New i2: {0}", i2);
+ Log.Info("Right shift: {0}", i1 >> i2);
+ Assert.Equal<int>(i1 >> i2, dyn1 >> dyn2);
+ Assert.Equal<int>(i1 >> i2, dyn1 >> i2);
+ }
+
+ /// <summary>
+ /// Tests for conversions between data types in arithmetic operations.
+ /// </summary>
+ [Fact(Skip = "Ignore")]
+ public void ArithmeticConversion()
+ {
+ JsonObject jo = new JsonObject
+ {
+ { "byteVal", (byte)10 },
+ { "sbyteVal", (sbyte)10 },
+ { "shortVal", (short)10 },
+ { "ushortVal", (ushort)10 },
+ { "intVal", 10 },
+ { "uintVal", (uint)10 },
+ { "longVal", 10L },
+ { "ulongVal", (ulong)10 },
+ { "charVal", (char)10 },
+ { "decimalVal", 10m },
+ { "doubleVal", 10.0 },
+ { "floatVal", 10f },
+ };
+ dynamic dyn = jo;
+
+ Log.Info("Conversion from byte");
+ // DISABLED, 197387, ValidateResult<int>(dyn.byteVal + (byte)10, 20);
+ JsonValueTests.ExpectException<InvalidOperationException>(() => Log.Info("{0}", dyn.byteVal + (sbyte)10));
+ ValidateResult<short>(dyn.byteVal + (short)10, 20);
+ ValidateResult<ushort>(dyn.byteVal + (ushort)10, 20);
+ ValidateResult<int>(dyn.byteVal + (int)10, 20);
+ ValidateResult<uint>(dyn.byteVal + (uint)10, 20);
+ ValidateResult<long>(dyn.byteVal + 10L, 20);
+ ValidateResult<ulong>(dyn.byteVal + (ulong)10, 20);
+ ValidateResult<decimal>(dyn.byteVal + 10m, 20);
+ ValidateResult<float>(dyn.byteVal + 10f, 20);
+ ValidateResult<double>(dyn.byteVal + 10.0, 20);
+
+ Log.Info("Conversion from sbyte");
+ JsonValueTests.ExpectException<InvalidOperationException>(() => Log.Info("{0}", dyn.sbyteVal + (byte)10));
+ // DISABLED, 197387, ValidateResult<int>(dyn.sbyteVal + (sbyte)10, 20);
+ ValidateResult<short>(dyn.sbyteVal + (short)10, 20);
+ JsonValueTests.ExpectException<InvalidOperationException>(() => Log.Info("{0}", dyn.sbyteVal + (ushort)10));
+ ValidateResult<int>(dyn.sbyteVal + (int)10, 20);
+ JsonValueTests.ExpectException<InvalidOperationException>(() => Log.Info("{0}", dyn.sbyteVal + (uint)10));
+ ValidateResult<long>(dyn.sbyteVal + 10L, 20);
+ JsonValueTests.ExpectException<InvalidOperationException>(() => Log.Info("{0}", dyn.sbyteVal + (ulong)10));
+ ValidateResult<decimal>(dyn.sbyteVal + 10m, 20);
+ ValidateResult<float>(dyn.sbyteVal + 10f, 20);
+ ValidateResult<double>(dyn.sbyteVal + 10.0, 20);
+
+ Log.Info("Conversion from short");
+ ValidateResult<short>(dyn.shortVal + (byte)10, 20);
+ ValidateResult<short>(dyn.shortVal + (sbyte)10, 20);
+ ValidateResult<short>(dyn.shortVal + (short)10, 20);
+ JsonValueTests.ExpectException<InvalidOperationException>(() => Log.Info("{0}", dyn.shortVal + (ushort)10));
+ ValidateResult<int>(dyn.shortVal + (int)10, 20);
+ JsonValueTests.ExpectException<InvalidOperationException>(() => Log.Info("{0}", dyn.shortVal + (uint)10));
+ ValidateResult<long>(dyn.shortVal + 10L, 20);
+ JsonValueTests.ExpectException<InvalidOperationException>(() => Log.Info("{0}", dyn.shortVal + (ulong)10));
+ ValidateResult<decimal>(dyn.shortVal + 10m, 20);
+ ValidateResult<float>(dyn.shortVal + 10f, 20);
+ ValidateResult<double>(dyn.shortVal + 10.0, 20);
+
+ Log.Info("Conversion from ushort");
+ ValidateResult<ushort>(dyn.ushortVal + (byte)10, 20);
+ JsonValueTests.ExpectException<InvalidOperationException>(() => Log.Info("{0}", dyn.ushortVal + (sbyte)10));
+ JsonValueTests.ExpectException<InvalidOperationException>(() => Log.Info("{0}", dyn.ushortVal + (short)10));
+ ValidateResult<ushort>(dyn.ushortVal + (ushort)10, 20);
+ ValidateResult<int>(dyn.ushortVal + (int)10, 20);
+ ValidateResult<uint>(dyn.ushortVal + (uint)10, 20);
+ ValidateResult<long>(dyn.ushortVal + 10L, 20);
+ ValidateResult<ulong>(dyn.ushortVal + (ulong)10, 20);
+ ValidateResult<decimal>(dyn.ushortVal + 10m, 20);
+ ValidateResult<float>(dyn.ushortVal + 10f, 20);
+ ValidateResult<double>(dyn.ushortVal + 10.0, 20);
+
+ Log.Info("Conversion from int");
+ ValidateResult<int>(dyn.intVal + (byte)10, 20);
+ ValidateResult<int>(dyn.intVal + (sbyte)10, 20);
+ ValidateResult<int>(dyn.intVal + (short)10, 20);
+ ValidateResult<int>(dyn.intVal + (ushort)10, 20);
+ ValidateResult<int>(dyn.intVal + (int)10, 20);
+ JsonValueTests.ExpectException<InvalidOperationException>(() => Log.Info("{0}", dyn.intVal + (uint)10));
+ ValidateResult<long>(dyn.intVal + 10L, 20);
+ JsonValueTests.ExpectException<InvalidOperationException>(() => Log.Info("{0}", dyn.intVal + (ulong)10));
+ ValidateResult<decimal>(dyn.intVal + 10m, 20);
+ ValidateResult<float>(dyn.intVal + 10f, 20);
+ ValidateResult<double>(dyn.intVal + 10.0, 20);
+
+ Log.Info("Conversion from uint");
+ ValidateResult<uint>(dyn.uintVal + (byte)10, 20);
+ JsonValueTests.ExpectException<InvalidOperationException>(() => Log.Info("{0}", dyn.uintVal + (sbyte)10));
+ JsonValueTests.ExpectException<InvalidOperationException>(() => Log.Info("{0}", dyn.uintVal + (short)10));
+ ValidateResult<uint>(dyn.uintVal + (ushort)10, 20);
+ JsonValueTests.ExpectException<InvalidOperationException>(() => Log.Info("{0}", dyn.uintVal + (int)10));
+ ValidateResult<uint>(dyn.uintVal + (uint)10, 20);
+ ValidateResult<long>(dyn.uintVal + 10L, 20);
+ ValidateResult<ulong>(dyn.uintVal + (ulong)10, 20);
+ ValidateResult<decimal>(dyn.uintVal + 10m, 20);
+ ValidateResult<float>(dyn.uintVal + 10f, 20);
+ ValidateResult<double>(dyn.uintVal + 10.0, 20);
+
+ Log.Info("Conversion from long");
+ ValidateResult<long>(dyn.longVal + (byte)10, 20);
+ ValidateResult<long>(dyn.longVal + (sbyte)10, 20);
+ ValidateResult<long>(dyn.longVal + (short)10, 20);
+ ValidateResult<long>(dyn.longVal + (ushort)10, 20);
+ ValidateResult<long>(dyn.longVal + (int)10, 20);
+ ValidateResult<long>(dyn.longVal + (uint)10, 20);
+ ValidateResult<long>(dyn.longVal + 10L, 20);
+ JsonValueTests.ExpectException<InvalidOperationException>(() => Log.Info("{0}", dyn.longVal + (ulong)10));
+ ValidateResult<decimal>(dyn.longVal + 10m, 20);
+ ValidateResult<float>(dyn.longVal + 10f, 20);
+ ValidateResult<double>(dyn.longVal + 10.0, 20);
+
+ Log.Info("Conversion from ulong");
+ ValidateResult<ulong>(dyn.ulongVal + (byte)10, 20);
+ JsonValueTests.ExpectException<InvalidOperationException>(() => Log.Info("{0}", dyn.ulongVal + (sbyte)10));
+ JsonValueTests.ExpectException<InvalidOperationException>(() => Log.Info("{0}", dyn.ulongVal + (short)10));
+ ValidateResult<ulong>(dyn.ulongVal + (ushort)10, 20);
+ JsonValueTests.ExpectException<InvalidOperationException>(() => Log.Info("{0}", dyn.ulongVal + (int)10));
+ ValidateResult<ulong>(dyn.ulongVal + (uint)10, 20);
+ JsonValueTests.ExpectException<InvalidOperationException>(() => Log.Info("{0}", dyn.ulongVal + (long)10));
+ ValidateResult<ulong>(dyn.ulongVal + (ulong)10, 20);
+ ValidateResult<decimal>(dyn.ulongVal + 10m, 20);
+ ValidateResult<float>(dyn.ulongVal + 10f, 20);
+ ValidateResult<double>(dyn.ulongVal + 10.0, 20);
+
+ Log.Info("Conversion from float");
+ ValidateResult<float>(dyn.floatVal + (byte)10, 20);
+ ValidateResult<float>(dyn.floatVal + (sbyte)10, 20);
+ ValidateResult<float>(dyn.floatVal + (short)10, 20);
+ ValidateResult<float>(dyn.floatVal + (ushort)10, 20);
+ ValidateResult<float>(dyn.floatVal + (int)10, 20);
+ ValidateResult<float>(dyn.floatVal + (uint)10, 20);
+ ValidateResult<float>(dyn.floatVal + 10L, 20);
+ ValidateResult<float>(dyn.floatVal + (ulong)10, 20);
+ JsonValueTests.ExpectException<InvalidOperationException>(() => Log.Info("{0}", dyn.floatVal + 10m));
+ ValidateResult<float>(dyn.floatVal + 10f, 20);
+ ValidateResult<double>(dyn.floatVal + 10.0, 20);
+
+ Log.Info("Conversion from double");
+ ValidateResult<double>(dyn.doubleVal + (byte)10, 20);
+ ValidateResult<double>(dyn.doubleVal + (sbyte)10, 20);
+ ValidateResult<double>(dyn.doubleVal + (short)10, 20);
+ ValidateResult<double>(dyn.doubleVal + (ushort)10, 20);
+ ValidateResult<double>(dyn.doubleVal + (int)10, 20);
+ ValidateResult<double>(dyn.doubleVal + (uint)10, 20);
+ ValidateResult<double>(dyn.doubleVal + 10L, 20);
+ ValidateResult<double>(dyn.doubleVal + (ulong)10, 20);
+ JsonValueTests.ExpectException<InvalidOperationException>(() => Log.Info("{0}", dyn.doubleVal + 10m));
+ ValidateResult<double>(dyn.doubleVal + 10f, 20);
+ ValidateResult<double>(dyn.doubleVal + 10.0, 20);
+
+ Log.Info("Conversion from decimal");
+ ValidateResult<decimal>(dyn.decimalVal + (byte)10, 20);
+ ValidateResult<decimal>(dyn.decimalVal + (sbyte)10, 20);
+ ValidateResult<decimal>(dyn.decimalVal + (short)10, 20);
+ ValidateResult<decimal>(dyn.decimalVal + (ushort)10, 20);
+ ValidateResult<decimal>(dyn.decimalVal + (int)10, 20);
+ ValidateResult<decimal>(dyn.decimalVal + (uint)10, 20);
+ ValidateResult<decimal>(dyn.decimalVal + 10L, 20);
+ ValidateResult<decimal>(dyn.decimalVal + (ulong)10, 20);
+ ValidateResult<decimal>(dyn.decimalVal + 10m, 20);
+ JsonValueTests.ExpectException<InvalidOperationException>(() => Log.Info("{0}", dyn.decimalVal + 10f));
+ JsonValueTests.ExpectException<InvalidOperationException>(() => Log.Info("{0}", dyn.decimalVal + 10.0));
+ }
+
+ /// <summary>
+ /// Tests for implicit casts between dynamic references to <see cref="JsonPrimitive"/> instances
+ /// and the supported CLR types.
+ /// </summary>
+ [Fact]
+ public void ImplicitPrimitiveCastTests()
+ {
+ DateTime now = DateTime.Now;
+ int seed = now.Year * 10000 + now.Month * 100 + now.Day;
+ Log.Info("Seed: {0}", seed);
+ Random rndGen = new Random(seed);
+ int intValue = rndGen.Next(1, 127);
+ Log.Info("Value: {0}", intValue);
+
+ uint uintValue = (uint)intValue;
+ short shortValue = (short)intValue;
+ ushort ushortValue = (ushort)intValue;
+ long longValue = (long)intValue;
+ ulong ulongValue = (ulong)intValue;
+ byte byteValue = (byte)intValue;
+ sbyte sbyteValue = (sbyte)intValue;
+ float floatValue = (float)intValue;
+ double doubleValue = (double)intValue;
+ decimal decimalValue = (decimal)intValue;
+ string stringValue = intValue.ToString(CultureInfo.InvariantCulture);
+
+ dynamic dyn = new JsonObject
+ {
+ { "Byte", byteValue },
+ { "SByte", sbyteValue },
+ { "Int16", shortValue },
+ { "UInt16", ushortValue },
+ { "Int32", intValue },
+ { "UInt32", uintValue },
+ { "Int64", longValue },
+ { "UInt64", ulongValue },
+ { "Double", doubleValue },
+ { "Single", floatValue },
+ { "Decimal", decimalValue },
+ { "String", stringValue },
+ { "True", "true" },
+ { "False", "false" },
+ };
+
+ Log.Info("dyn: {0}", dyn);
+
+ Log.Info("Casts to Byte");
+
+ byte byteFromByte = dyn.Byte;
+ byte byteFromSByte = dyn.SByte;
+ byte byteFromShort = dyn.Int16;
+ byte byteFromUShort = dyn.UInt16;
+ byte byteFromInt = dyn.Int32;
+ byte byteFromUInt = dyn.UInt32;
+ byte byteFromLong = dyn.Int64;
+ byte byteFromULong = dyn.UInt64;
+ byte byteFromDouble = dyn.Double;
+ byte byteFromFloat = dyn.Single;
+ byte byteFromDecimal = dyn.Decimal;
+ byte byteFromString = dyn.String;
+
+ Assert.Equal<byte>(byteValue, byteFromByte);
+ Assert.Equal<byte>(byteValue, byteFromSByte);
+ Assert.Equal<byte>(byteValue, byteFromShort);
+ Assert.Equal<byte>(byteValue, byteFromUShort);
+ Assert.Equal<byte>(byteValue, byteFromInt);
+ Assert.Equal<byte>(byteValue, byteFromUInt);
+ Assert.Equal<byte>(byteValue, byteFromLong);
+ Assert.Equal<byte>(byteValue, byteFromULong);
+ Assert.Equal<byte>(byteValue, byteFromDouble);
+ Assert.Equal<byte>(byteValue, byteFromFloat);
+ Assert.Equal<byte>(byteValue, byteFromDecimal);
+ Assert.Equal<byte>(byteValue, byteFromString);
+
+ Log.Info("Casts to SByte");
+
+ sbyte sbyteFromByte = dyn.Byte;
+ sbyte sbyteFromSByte = dyn.SByte;
+ sbyte sbyteFromShort = dyn.Int16;
+ sbyte sbyteFromUShort = dyn.UInt16;
+ sbyte sbyteFromInt = dyn.Int32;
+ sbyte sbyteFromUInt = dyn.UInt32;
+ sbyte sbyteFromLong = dyn.Int64;
+ sbyte sbyteFromULong = dyn.UInt64;
+ sbyte sbyteFromDouble = dyn.Double;
+ sbyte sbyteFromFloat = dyn.Single;
+ sbyte sbyteFromDecimal = dyn.Decimal;
+ sbyte sbyteFromString = dyn.String;
+
+ Assert.Equal<sbyte>(sbyteValue, sbyteFromByte);
+ Assert.Equal<sbyte>(sbyteValue, sbyteFromSByte);
+ Assert.Equal<sbyte>(sbyteValue, sbyteFromShort);
+ Assert.Equal<sbyte>(sbyteValue, sbyteFromUShort);
+ Assert.Equal<sbyte>(sbyteValue, sbyteFromInt);
+ Assert.Equal<sbyte>(sbyteValue, sbyteFromUInt);
+ Assert.Equal<sbyte>(sbyteValue, sbyteFromLong);
+ Assert.Equal<sbyte>(sbyteValue, sbyteFromULong);
+ Assert.Equal<sbyte>(sbyteValue, sbyteFromDouble);
+ Assert.Equal<sbyte>(sbyteValue, sbyteFromFloat);
+ Assert.Equal<sbyte>(sbyteValue, sbyteFromDecimal);
+ Assert.Equal<sbyte>(sbyteValue, sbyteFromString);
+
+ Log.Info("Casts to Short");
+
+ short shortFromByte = dyn.Byte;
+ short shortFromSByte = dyn.SByte;
+ short shortFromShort = dyn.Int16;
+ short shortFromUShort = dyn.UInt16;
+ short shortFromInt = dyn.Int32;
+ short shortFromUInt = dyn.UInt32;
+ short shortFromLong = dyn.Int64;
+ short shortFromULong = dyn.UInt64;
+ short shortFromDouble = dyn.Double;
+ short shortFromFloat = dyn.Single;
+ short shortFromDecimal = dyn.Decimal;
+ short shortFromString = dyn.String;
+
+ Assert.Equal<short>(shortValue, shortFromByte);
+ Assert.Equal<short>(shortValue, shortFromSByte);
+ Assert.Equal<short>(shortValue, shortFromShort);
+ Assert.Equal<short>(shortValue, shortFromUShort);
+ Assert.Equal<short>(shortValue, shortFromInt);
+ Assert.Equal<short>(shortValue, shortFromUInt);
+ Assert.Equal<short>(shortValue, shortFromLong);
+ Assert.Equal<short>(shortValue, shortFromULong);
+ Assert.Equal<short>(shortValue, shortFromDouble);
+ Assert.Equal<short>(shortValue, shortFromFloat);
+ Assert.Equal<short>(shortValue, shortFromDecimal);
+ Assert.Equal<short>(shortValue, shortFromString);
+
+ Log.Info("Casts to UShort");
+
+ ushort ushortFromByte = dyn.Byte;
+ ushort ushortFromSByte = dyn.SByte;
+ ushort ushortFromShort = dyn.Int16;
+ ushort ushortFromUShort = dyn.UInt16;
+ ushort ushortFromInt = dyn.Int32;
+ ushort ushortFromUInt = dyn.UInt32;
+ ushort ushortFromLong = dyn.Int64;
+ ushort ushortFromULong = dyn.UInt64;
+ ushort ushortFromDouble = dyn.Double;
+ ushort ushortFromFloat = dyn.Single;
+ ushort ushortFromDecimal = dyn.Decimal;
+ ushort ushortFromString = dyn.String;
+
+ Assert.Equal<ushort>(ushortValue, ushortFromByte);
+ Assert.Equal<ushort>(ushortValue, ushortFromSByte);
+ Assert.Equal<ushort>(ushortValue, ushortFromShort);
+ Assert.Equal<ushort>(ushortValue, ushortFromUShort);
+ Assert.Equal<ushort>(ushortValue, ushortFromInt);
+ Assert.Equal<ushort>(ushortValue, ushortFromUInt);
+ Assert.Equal<ushort>(ushortValue, ushortFromLong);
+ Assert.Equal<ushort>(ushortValue, ushortFromULong);
+ Assert.Equal<ushort>(ushortValue, ushortFromDouble);
+ Assert.Equal<ushort>(ushortValue, ushortFromFloat);
+ Assert.Equal<ushort>(ushortValue, ushortFromDecimal);
+ Assert.Equal<ushort>(ushortValue, ushortFromString);
+
+ Log.Info("Casts to Int");
+
+ int intFromByte = dyn.Byte;
+ int intFromSByte = dyn.SByte;
+ int intFromShort = dyn.Int16;
+ int intFromUShort = dyn.UInt16;
+ int intFromInt = dyn.Int32;
+ int intFromUInt = dyn.UInt32;
+ int intFromLong = dyn.Int64;
+ int intFromULong = dyn.UInt64;
+ int intFromDouble = dyn.Double;
+ int intFromFloat = dyn.Single;
+ int intFromDecimal = dyn.Decimal;
+ int intFromString = dyn.String;
+
+ Assert.Equal<int>(intValue, intFromByte);
+ Assert.Equal<int>(intValue, intFromSByte);
+ Assert.Equal<int>(intValue, intFromShort);
+ Assert.Equal<int>(intValue, intFromUShort);
+ Assert.Equal<int>(intValue, intFromInt);
+ Assert.Equal<int>(intValue, intFromUInt);
+ Assert.Equal<int>(intValue, intFromLong);
+ Assert.Equal<int>(intValue, intFromULong);
+ Assert.Equal<int>(intValue, intFromDouble);
+ Assert.Equal<int>(intValue, intFromFloat);
+ Assert.Equal<int>(intValue, intFromDecimal);
+ Assert.Equal<int>(intValue, intFromString);
+
+ Log.Info("Casts to UInt");
+
+ uint uintFromByte = dyn.Byte;
+ uint uintFromSByte = dyn.SByte;
+ uint uintFromShort = dyn.Int16;
+ uint uintFromUShort = dyn.UInt16;
+ uint uintFromInt = dyn.Int32;
+ uint uintFromUInt = dyn.UInt32;
+ uint uintFromLong = dyn.Int64;
+ uint uintFromULong = dyn.UInt64;
+ uint uintFromDouble = dyn.Double;
+ uint uintFromFloat = dyn.Single;
+ uint uintFromDecimal = dyn.Decimal;
+ uint uintFromString = dyn.String;
+
+ Assert.Equal<uint>(uintValue, uintFromByte);
+ Assert.Equal<uint>(uintValue, uintFromSByte);
+ Assert.Equal<uint>(uintValue, uintFromShort);
+ Assert.Equal<uint>(uintValue, uintFromUShort);
+ Assert.Equal<uint>(uintValue, uintFromInt);
+ Assert.Equal<uint>(uintValue, uintFromUInt);
+ Assert.Equal<uint>(uintValue, uintFromLong);
+ Assert.Equal<uint>(uintValue, uintFromULong);
+ Assert.Equal<uint>(uintValue, uintFromDouble);
+ Assert.Equal<uint>(uintValue, uintFromFloat);
+ Assert.Equal<uint>(uintValue, uintFromDecimal);
+ Assert.Equal<uint>(uintValue, uintFromString);
+
+ Log.Info("Casts to Long");
+
+ long longFromByte = dyn.Byte;
+ long longFromSByte = dyn.SByte;
+ long longFromShort = dyn.Int16;
+ long longFromUShort = dyn.UInt16;
+ long longFromInt = dyn.Int32;
+ long longFromUInt = dyn.UInt32;
+ long longFromLong = dyn.Int64;
+ long longFromULong = dyn.UInt64;
+ long longFromDouble = dyn.Double;
+ long longFromFloat = dyn.Single;
+ long longFromDecimal = dyn.Decimal;
+ long longFromString = dyn.String;
+
+ Assert.Equal<long>(longValue, longFromByte);
+ Assert.Equal<long>(longValue, longFromSByte);
+ Assert.Equal<long>(longValue, longFromShort);
+ Assert.Equal<long>(longValue, longFromUShort);
+ Assert.Equal<long>(longValue, longFromInt);
+ Assert.Equal<long>(longValue, longFromUInt);
+ Assert.Equal<long>(longValue, longFromLong);
+ Assert.Equal<long>(longValue, longFromULong);
+ Assert.Equal<long>(longValue, longFromDouble);
+ Assert.Equal<long>(longValue, longFromFloat);
+ Assert.Equal<long>(longValue, longFromDecimal);
+ Assert.Equal<long>(longValue, longFromString);
+
+ Log.Info("Casts to ULong");
+
+ ulong ulongFromByte = dyn.Byte;
+ ulong ulongFromSByte = dyn.SByte;
+ ulong ulongFromShort = dyn.Int16;
+ ulong ulongFromUShort = dyn.UInt16;
+ ulong ulongFromInt = dyn.Int32;
+ ulong ulongFromUInt = dyn.UInt32;
+ ulong ulongFromLong = dyn.Int64;
+ ulong ulongFromULong = dyn.UInt64;
+ ulong ulongFromDouble = dyn.Double;
+ ulong ulongFromFloat = dyn.Single;
+ ulong ulongFromDecimal = dyn.Decimal;
+ ulong ulongFromString = dyn.String;
+
+ Assert.Equal<ulong>(ulongValue, ulongFromByte);
+ Assert.Equal<ulong>(ulongValue, ulongFromSByte);
+ Assert.Equal<ulong>(ulongValue, ulongFromShort);
+ Assert.Equal<ulong>(ulongValue, ulongFromUShort);
+ Assert.Equal<ulong>(ulongValue, ulongFromInt);
+ Assert.Equal<ulong>(ulongValue, ulongFromUInt);
+ Assert.Equal<ulong>(ulongValue, ulongFromLong);
+ Assert.Equal<ulong>(ulongValue, ulongFromULong);
+ Assert.Equal<ulong>(ulongValue, ulongFromDouble);
+ Assert.Equal<ulong>(ulongValue, ulongFromFloat);
+ Assert.Equal<ulong>(ulongValue, ulongFromDecimal);
+ Assert.Equal<ulong>(ulongValue, ulongFromString);
+
+ Log.Info("Casts to Float");
+
+ float floatFromByte = dyn.Byte;
+ float floatFromSByte = dyn.SByte;
+ float floatFromShort = dyn.Int16;
+ float floatFromUShort = dyn.UInt16;
+ float floatFromInt = dyn.Int32;
+ float floatFromUInt = dyn.UInt32;
+ float floatFromLong = dyn.Int64;
+ float floatFromULong = dyn.UInt64;
+ float floatFromDouble = dyn.Double;
+ float floatFromFloat = dyn.Single;
+ float floatFromDecimal = dyn.Decimal;
+ float floatFromString = dyn.String;
+
+ Assert.Equal<float>(floatValue, floatFromByte);
+ Assert.Equal<float>(floatValue, floatFromSByte);
+ Assert.Equal<float>(floatValue, floatFromShort);
+ Assert.Equal<float>(floatValue, floatFromUShort);
+ Assert.Equal<float>(floatValue, floatFromInt);
+ Assert.Equal<float>(floatValue, floatFromUInt);
+ Assert.Equal<float>(floatValue, floatFromLong);
+ Assert.Equal<float>(floatValue, floatFromULong);
+ Assert.Equal<float>(floatValue, floatFromDouble);
+ Assert.Equal<float>(floatValue, floatFromFloat);
+ Assert.Equal<float>(floatValue, floatFromDecimal);
+ Assert.Equal<float>(floatValue, floatFromString);
+
+ Log.Info("Casts to Double");
+
+ double doubleFromByte = dyn.Byte;
+ double doubleFromSByte = dyn.SByte;
+ double doubleFromShort = dyn.Int16;
+ double doubleFromUShort = dyn.UInt16;
+ double doubleFromInt = dyn.Int32;
+ double doubleFromUInt = dyn.UInt32;
+ double doubleFromLong = dyn.Int64;
+ double doubleFromULong = dyn.UInt64;
+ double doubleFromDouble = dyn.Double;
+ double doubleFromFloat = dyn.Single;
+ double doubleFromDecimal = dyn.Decimal;
+ double doubleFromString = dyn.String;
+
+ Assert.Equal<double>(doubleValue, doubleFromByte);
+ Assert.Equal<double>(doubleValue, doubleFromSByte);
+ Assert.Equal<double>(doubleValue, doubleFromShort);
+ Assert.Equal<double>(doubleValue, doubleFromUShort);
+ Assert.Equal<double>(doubleValue, doubleFromInt);
+ Assert.Equal<double>(doubleValue, doubleFromUInt);
+ Assert.Equal<double>(doubleValue, doubleFromLong);
+ Assert.Equal<double>(doubleValue, doubleFromULong);
+ Assert.Equal<double>(doubleValue, doubleFromDouble);
+ Assert.Equal<double>(doubleValue, doubleFromFloat);
+ Assert.Equal<double>(doubleValue, doubleFromDecimal);
+ Assert.Equal<double>(doubleValue, doubleFromString);
+
+ Log.Info("Casts to Decimal");
+
+ decimal decimalFromByte = dyn.Byte;
+ decimal decimalFromSByte = dyn.SByte;
+ decimal decimalFromShort = dyn.Int16;
+ decimal decimalFromUShort = dyn.UInt16;
+ decimal decimalFromInt = dyn.Int32;
+ decimal decimalFromUInt = dyn.UInt32;
+ decimal decimalFromLong = dyn.Int64;
+ decimal decimalFromULong = dyn.UInt64;
+ decimal decimalFromDouble = dyn.Double;
+ decimal decimalFromFloat = dyn.Single;
+ decimal decimalFromDecimal = dyn.Decimal;
+ decimal decimalFromString = dyn.String;
+
+ Assert.Equal<decimal>(decimalValue, decimalFromByte);
+ Assert.Equal<decimal>(decimalValue, decimalFromSByte);
+ Assert.Equal<decimal>(decimalValue, decimalFromShort);
+ Assert.Equal<decimal>(decimalValue, decimalFromUShort);
+ Assert.Equal<decimal>(decimalValue, decimalFromInt);
+ Assert.Equal<decimal>(decimalValue, decimalFromUInt);
+ Assert.Equal<decimal>(decimalValue, decimalFromLong);
+ Assert.Equal<decimal>(decimalValue, decimalFromULong);
+ Assert.Equal<decimal>(decimalValue, decimalFromDouble);
+ Assert.Equal<decimal>(decimalValue, decimalFromFloat);
+ Assert.Equal<decimal>(decimalValue, decimalFromDecimal);
+ Assert.Equal<decimal>(decimalValue, decimalFromString);
+
+ Log.Info("Casts to String");
+
+ string stringFromByte = dyn.Byte;
+ string stringFromSByte = dyn.SByte;
+ string stringFromShort = dyn.Int16;
+ string stringFromUShort = dyn.UInt16;
+ string stringFromInt = dyn.Int32;
+ string stringFromUInt = dyn.UInt32;
+ string stringFromLong = dyn.Int64;
+ string stringFromULong = dyn.UInt64;
+ string stringFromDouble = dyn.Double;
+ string stringFromFloat = dyn.Single;
+ string stringFromDecimal = dyn.Decimal;
+ string stringFromString = dyn.String;
+
+ Assert.Equal(stringValue, stringFromByte);
+ Assert.Equal(stringValue, stringFromSByte);
+ Assert.Equal(stringValue, stringFromShort);
+ Assert.Equal(stringValue, stringFromUShort);
+ Assert.Equal(stringValue, stringFromInt);
+ Assert.Equal(stringValue, stringFromUInt);
+ Assert.Equal(stringValue, stringFromLong);
+ Assert.Equal(stringValue, stringFromULong);
+ Assert.Equal(stringValue, stringFromDouble);
+ Assert.Equal(stringValue, stringFromFloat);
+ Assert.Equal(stringValue, stringFromDecimal);
+ Assert.Equal(stringValue, stringFromString);
+
+ Log.Info("Casts to Boolean");
+
+ bool bTrue = dyn.True;
+ bool bFalse = dyn.False;
+ Assert.True(bTrue);
+ Assert.False(bFalse);
+ }
+
+ /// <summary>
+ /// Test for creating a JsonValue from a deep-nested dynamic object.
+ /// </summary>
+ [Fact]
+ public void CreateFromDeepNestedDynamic()
+ {
+ int count = 5000;
+ string expected = "";
+
+ dynamic dyn = new TestDynamicObject();
+ dynamic cur = dyn;
+
+ for (int i = 0; i < count; i++)
+ {
+ expected += "{\"" + i + "\":";
+ cur[i.ToString()] = new TestDynamicObject();
+ cur = cur[i.ToString()];
+ }
+
+ expected += "{}";
+
+ for (int i = 0; i < count; i++)
+ {
+ expected += "}";
+ }
+
+ JsonValue jv = JsonValueExtensions.CreateFrom(dyn);
+ Assert.Equal(expected, jv.ToString());
+ }
+
+ private void ValidateResult<ResultType>(dynamic value, ResultType expectedResult)
+ {
+ Assert.IsAssignableFrom(typeof(ResultType), value);
+ Assert.Equal<ResultType>(expectedResult, (ResultType)value);
+ }
+
+ /// <summary>
+ /// Concrete DynamicObject class for testing purposes.
+ /// </summary>
+ internal class TestDynamicObject : DynamicObject
+ {
+ private IDictionary<string, object> _values = new Dictionary<string, object>();
+
+ public override IEnumerable<string> GetDynamicMemberNames()
+ {
+ return _values.Keys;
+ }
+
+ public override bool TrySetMember(SetMemberBinder binder, object value)
+ {
+ _values[binder.Name] = value;
+ return true;
+ }
+
+ public override bool TryGetMember(GetMemberBinder binder, out object result)
+ {
+ return _values.TryGetValue(binder.Name, out result);
+ }
+
+ public override bool TrySetIndex(SetIndexBinder binder, object[] indexes, object value)
+ {
+ string key = indexes[0].ToString();
+
+ if (_values.ContainsKey(key))
+ {
+ _values[key] = value;
+ }
+ else
+ {
+ _values.Add(key, value);
+ }
+ return true;
+ }
+
+ public override bool TryGetIndex(GetIndexBinder binder, object[] indexes, out object result)
+ {
+ string key = indexes[0].ToString();
+
+ if (_values.ContainsKey(key))
+ {
+ result = _values[key];
+ return true;
+ }
+ else
+ {
+ result = null;
+ return false;
+ }
+ }
+ }
+ }
+}
diff --git a/test/System.Json.Test.Integration/JsonValueEventsTests.cs b/test/System.Json.Test.Integration/JsonValueEventsTests.cs
new file mode 100644
index 00000000..9b64b46a
--- /dev/null
+++ b/test/System.Json.Test.Integration/JsonValueEventsTests.cs
@@ -0,0 +1,518 @@
+using System.Collections.Generic;
+using System.Linq;
+using Xunit;
+
+namespace System.Json
+{
+ /// <summary>
+ /// Tests for events on <see cref="JsonValue"/> instances.
+ /// </summary>
+ public class JsonValueEventsTests
+ {
+ /// <summary>
+ /// Events tests for JsonArray, test all method the causes change and all change type and validate changing/changed child and sub/unsub
+ /// </summary>
+ [Fact]
+ public void JsonArrayEventsTest()
+ {
+ int seed = 1;
+ const int maxArrayLength = 1024;
+ Random rand = new Random(seed);
+ JsonArray ja = SpecialJsonValueHelper.CreatePrePopulatedJsonArray(seed, rand.Next(maxArrayLength));
+ int addPosition = ja.Count;
+ JsonValue insertValue = SpecialJsonValueHelper.GetRandomJsonPrimitives(seed);
+
+ TestEvents(
+ ja,
+ arr => arr.Add(insertValue),
+ new List<Tuple<bool, JsonValue, JsonValueChangeEventArgs>>
+ {
+ new Tuple<bool, JsonValue, JsonValueChangeEventArgs>(true, ja, new JsonValueChangeEventArgs(insertValue, JsonValueChange.Add, addPosition)),
+ new Tuple<bool, JsonValue, JsonValueChangeEventArgs>(false, ja, new JsonValueChangeEventArgs(insertValue, JsonValueChange.Add, addPosition)),
+ });
+
+ addPosition = ja.Count;
+ JsonValue jv1 = SpecialJsonValueHelper.GetRandomJsonPrimitives(seed);
+ JsonValue jv2 = SpecialJsonValueHelper.GetRandomJsonPrimitives(seed);
+ TestEvents(
+ ja,
+ arr => arr.AddRange(jv1, jv2),
+ new List<Tuple<bool, JsonValue, JsonValueChangeEventArgs>>
+ {
+ new Tuple<bool, JsonValue, JsonValueChangeEventArgs>(true, ja,
+ new JsonValueChangeEventArgs(
+ jv1,
+ JsonValueChange.Add, addPosition)),
+ new Tuple<bool, JsonValue, JsonValueChangeEventArgs>(true, ja,
+ new JsonValueChangeEventArgs(
+ jv2,
+ JsonValueChange.Add, addPosition + 1)),
+ new Tuple<bool, JsonValue, JsonValueChangeEventArgs>(false, ja,
+ new JsonValueChangeEventArgs(
+ jv1,
+ JsonValueChange.Add, addPosition)),
+ new Tuple<bool, JsonValue, JsonValueChangeEventArgs>(false, ja,
+ new JsonValueChangeEventArgs(
+ jv2,
+ JsonValueChange.Add, addPosition + 1)),
+ });
+
+ int replacePosition = rand.Next(ja.Count - 1);
+ JsonValue oldValue = ja[replacePosition];
+ JsonValue newValue = SpecialJsonValueHelper.GetRandomJsonPrimitives(seed);
+ TestEvents(
+ ja,
+ arr => arr[replacePosition] = newValue,
+ new List<Tuple<bool, JsonValue, JsonValueChangeEventArgs>>
+ {
+ new Tuple<bool, JsonValue, JsonValueChangeEventArgs>(true, ja, new JsonValueChangeEventArgs(newValue, JsonValueChange.Replace, replacePosition)),
+ new Tuple<bool, JsonValue, JsonValueChangeEventArgs>(false, ja, new JsonValueChangeEventArgs(oldValue, JsonValueChange.Replace, replacePosition)),
+ });
+
+ int insertPosition = rand.Next(ja.Count - 1);
+ insertValue = SpecialJsonValueHelper.GetRandomJsonPrimitives(seed);
+
+ TestEvents(
+ ja,
+ arr => arr.Insert(insertPosition, insertValue),
+ new List<Tuple<bool, JsonValue, JsonValueChangeEventArgs>>
+ {
+ new Tuple<bool, JsonValue, JsonValueChangeEventArgs>(true, ja, new JsonValueChangeEventArgs(insertValue, JsonValueChange.Add, insertPosition)),
+ new Tuple<bool, JsonValue, JsonValueChangeEventArgs>(false, ja, new JsonValueChangeEventArgs(insertValue, JsonValueChange.Add, insertPosition)),
+ });
+
+ TestEvents(
+ ja,
+ arr => arr.RemoveAt(insertPosition),
+ new List<Tuple<bool, JsonValue, JsonValueChangeEventArgs>>
+ {
+ new Tuple<bool, JsonValue, JsonValueChangeEventArgs>(true, ja, new JsonValueChangeEventArgs(insertValue, JsonValueChange.Remove, insertPosition)),
+ new Tuple<bool, JsonValue, JsonValueChangeEventArgs>(false, ja, new JsonValueChangeEventArgs(insertValue, JsonValueChange.Remove, insertPosition)),
+ });
+
+ ja.Insert(0, insertValue);
+ TestEvents(
+ ja,
+ arr => arr.Remove(insertValue),
+ new List<Tuple<bool, JsonValue, JsonValueChangeEventArgs>>
+ {
+ new Tuple<bool, JsonValue, JsonValueChangeEventArgs>(true, ja, new JsonValueChangeEventArgs(insertValue, JsonValueChange.Remove, 0)),
+ new Tuple<bool, JsonValue, JsonValueChangeEventArgs>(false, ja, new JsonValueChangeEventArgs(insertValue, JsonValueChange.Remove, 0)),
+ });
+
+ TestEvents(
+ ja,
+ arr => arr.Clear(),
+ new List<Tuple<bool, JsonValue, JsonValueChangeEventArgs>>
+ {
+ new Tuple<bool, JsonValue, JsonValueChangeEventArgs>(true, ja, new JsonValueChangeEventArgs(null, JsonValueChange.Clear, 0)),
+ new Tuple<bool, JsonValue, JsonValueChangeEventArgs>(false, ja, new JsonValueChangeEventArgs(null, JsonValueChange.Clear, 0)),
+ });
+
+ ja = new JsonArray(1, 2, 3);
+ TestEvents(
+ ja,
+ arr => arr.Remove(new JsonPrimitive("Not there")),
+ new List<Tuple<bool, JsonValue, JsonValueChangeEventArgs>>());
+
+ JsonValue elementInArray = ja[1];
+ TestEvents(
+ ja,
+ arr => arr.Remove(elementInArray),
+ new List<Tuple<bool, JsonValue, JsonValueChangeEventArgs>>
+ {
+ new Tuple<bool, JsonValue, JsonValueChangeEventArgs>(true, ja, new JsonValueChangeEventArgs(elementInArray, JsonValueChange.Remove, 1)),
+ new Tuple<bool, JsonValue, JsonValueChangeEventArgs>(false, ja, new JsonValueChangeEventArgs(elementInArray, JsonValueChange.Remove, 1)),
+ });
+ }
+
+ /// <summary>
+ /// Tests for events for <see cref="JsonValue"/> instances when using the dynamic programming.
+ /// </summary>
+ [Fact]
+ public void DynamicEventsTest()
+ {
+ int seed = 1;
+ int maxObj = 10;
+ JsonArray ja = new JsonArray();
+ dynamic d = ja.AsDynamic();
+ TestEventsDynamic(
+ d,
+ (Action<dynamic>)(arr => arr.Add(1)),
+ new List<Tuple<bool, JsonValue, JsonValueChangeEventArgs>>
+ {
+ new Tuple<bool, JsonValue, JsonValueChangeEventArgs>(true, ja, new JsonValueChangeEventArgs(1, JsonValueChange.Add, 0)),
+ new Tuple<bool, JsonValue, JsonValueChangeEventArgs>(false, ja, new JsonValueChangeEventArgs(1, JsonValueChange.Add, 0)),
+ });
+
+ const string key1 = "first";
+ const string key2 = "second";
+ JsonObject jo = new JsonObject
+ {
+ { key1, SpecialJsonValueHelper.GetRandomJsonPrimitives(seed) },
+ };
+
+ JsonObject objectToAdd = SpecialJsonValueHelper.CreateRandomPopulatedJsonObject(seed, maxObj);
+ dynamic d2 = jo.AsDynamic();
+ TestEventsDynamic(
+ d2,
+ (Action<dynamic>)(obj => obj[key2] = objectToAdd),
+ new List<Tuple<bool, JsonValue, JsonValueChangeEventArgs>>
+ {
+ new Tuple<bool, JsonValue, JsonValueChangeEventArgs>(true, jo, new JsonValueChangeEventArgs(objectToAdd, JsonValueChange.Add, key2)),
+ new Tuple<bool, JsonValue, JsonValueChangeEventArgs>(false, jo, new JsonValueChangeEventArgs(objectToAdd, JsonValueChange.Add, key2)),
+ });
+
+ TestEventsDynamic(
+ d2,
+ (Action<dynamic>)(obj => obj[key2] = objectToAdd),
+ new List<Tuple<bool, JsonValue, JsonValueChangeEventArgs>>
+ {
+ new Tuple<bool, JsonValue, JsonValueChangeEventArgs>(true, jo, new JsonValueChangeEventArgs(objectToAdd, JsonValueChange.Replace, key2)),
+ new Tuple<bool, JsonValue, JsonValueChangeEventArgs>(false, jo, new JsonValueChangeEventArgs(objectToAdd, JsonValueChange.Replace, key2)),
+ });
+ }
+
+ /// <summary>
+ /// Tests for events in <see cref="JsonObject"/> instances.
+ /// </summary>
+ [Fact]
+ public void JsonObjectEventsTest()
+ {
+ int seed = 1;
+ const int maxObj = 10;
+
+ const string key1 = "first";
+ const string key2 = "second";
+ const string key3 = "third";
+ const string key4 = "fourth";
+ const string key5 = "fifth";
+ JsonObject jo = new JsonObject
+ {
+ { key1, SpecialJsonValueHelper.GetRandomJsonPrimitives(seed) },
+ { key2, SpecialJsonValueHelper.GetRandomJsonPrimitives(seed) },
+ { key3, null },
+ };
+
+ JsonObject objToAdd = SpecialJsonValueHelper.CreateRandomPopulatedJsonObject(seed, maxObj);
+ TestEvents(
+ jo,
+ obj => obj.Add(key4, objToAdd),
+ new List<Tuple<bool, JsonValue, JsonValueChangeEventArgs>>
+ {
+ new Tuple<bool, JsonValue, JsonValueChangeEventArgs>(true, jo, new JsonValueChangeEventArgs(objToAdd, JsonValueChange.Add, key4)),
+ new Tuple<bool, JsonValue, JsonValueChangeEventArgs>(false, jo, new JsonValueChangeEventArgs(objToAdd, JsonValueChange.Add, key4)),
+ },
+ obj => obj.Add("key44", objToAdd));
+
+ JsonArray jaToAdd = SpecialJsonValueHelper.CreatePrePopulatedJsonArray(seed, maxObj);
+ JsonValue replaced = jo[key2];
+ TestEvents(
+ jo,
+ obj => obj[key2] = jaToAdd,
+ new List<Tuple<bool, JsonValue, JsonValueChangeEventArgs>>
+ {
+ new Tuple<bool, JsonValue, JsonValueChangeEventArgs>(true, jo, new JsonValueChangeEventArgs(jaToAdd, JsonValueChange.Replace, key2)),
+ new Tuple<bool, JsonValue, JsonValueChangeEventArgs>(false, jo, new JsonValueChangeEventArgs(replaced, JsonValueChange.Replace, key2)),
+ });
+
+ JsonValue jpToAdd = SpecialJsonValueHelper.GetRandomJsonPrimitives(seed);
+ TestEvents(
+ jo,
+ obj => obj[key5] = jpToAdd,
+ new List<Tuple<bool, JsonValue, JsonValueChangeEventArgs>>
+ {
+ new Tuple<bool, JsonValue, JsonValueChangeEventArgs>(true, jo, new JsonValueChangeEventArgs(jpToAdd, JsonValueChange.Add, key5)),
+ new Tuple<bool, JsonValue, JsonValueChangeEventArgs>(false, jo, new JsonValueChangeEventArgs(jpToAdd, JsonValueChange.Add, key5)),
+ });
+
+ jo.Remove(key4);
+ jo.Remove(key5);
+
+ JsonValue jp1 = SpecialJsonValueHelper.GetRandomJsonPrimitives(seed);
+ JsonValue jp2 = SpecialJsonValueHelper.GetRandomJsonPrimitives(seed);
+ TestEvents(
+ jo,
+ obj => obj.AddRange(new JsonObject { { key4, jp1 }, { key5, jp1 } }),
+ new List<Tuple<bool, JsonValue, JsonValueChangeEventArgs>>
+ {
+ new Tuple<bool, JsonValue, JsonValueChangeEventArgs>(true, jo, new JsonValueChangeEventArgs(jp1, JsonValueChange.Add, key4)),
+ new Tuple<bool, JsonValue, JsonValueChangeEventArgs>(true, jo, new JsonValueChangeEventArgs(jp2, JsonValueChange.Add, key5)),
+ new Tuple<bool, JsonValue, JsonValueChangeEventArgs>(false, jo, new JsonValueChangeEventArgs(jp1, JsonValueChange.Add, key4)),
+ new Tuple<bool, JsonValue, JsonValueChangeEventArgs>(false, jo, new JsonValueChangeEventArgs(jp2, JsonValueChange.Add, key5)),
+ },
+ obj => obj.AddRange(new JsonObject { { "new key", jp1 }, { "newnewKey", jp2 } }));
+
+ TestEvents(
+ jo,
+ obj => obj.Remove(key5),
+ new List<Tuple<bool, JsonValue, JsonValueChangeEventArgs>>
+ {
+ new Tuple<bool, JsonValue, JsonValueChangeEventArgs>(true, jo, new JsonValueChangeEventArgs(jp2, JsonValueChange.Remove, key5)),
+ new Tuple<bool, JsonValue, JsonValueChangeEventArgs>(false, jo, new JsonValueChangeEventArgs(jp2, JsonValueChange.Remove, key5)),
+ });
+
+ TestEvents(
+ jo,
+ obj => obj.Remove("not there"),
+ new List<Tuple<bool, JsonValue, JsonValueChangeEventArgs>>());
+
+ jo = new JsonObject { { key1, 1 }, { key2, 2 }, { key3, 3 } };
+
+ TestEvents(
+ jo,
+ obj => obj.Clear(),
+ new List<Tuple<bool, JsonValue, JsonValueChangeEventArgs>>
+ {
+ new Tuple<bool, JsonValue, JsonValueChangeEventArgs>(true, jo, new JsonValueChangeEventArgs(null, JsonValueChange.Clear, null)),
+ new Tuple<bool, JsonValue, JsonValueChangeEventArgs>(false, jo, new JsonValueChangeEventArgs(null, JsonValueChange.Clear, null)),
+ });
+
+ jo = new JsonObject { { key1, 1 }, { key2, 2 }, { key3, 3 } };
+ TestEvents(
+ jo,
+ obj => ((IDictionary<string, JsonValue>)obj).Remove(new KeyValuePair<string, JsonValue>(key2, jo[key2])),
+ new List<Tuple<bool, JsonValue, JsonValueChangeEventArgs>>
+ {
+ new Tuple<bool, JsonValue, JsonValueChangeEventArgs>(true, jo, new JsonValueChangeEventArgs(2, JsonValueChange.Remove, key2)),
+ new Tuple<bool, JsonValue, JsonValueChangeEventArgs>(false, jo, new JsonValueChangeEventArgs(2, JsonValueChange.Remove, key2)),
+ },
+ obj => ((IDictionary<string, JsonValue>)obj).Remove(new KeyValuePair<string, JsonValue>(key1, jo[key1])));
+ }
+
+ /// <summary>
+ /// Tests for events in <see cref="JsonValue"/> instances when multiple listeners are registered.
+ /// </summary>
+ [Fact]
+ public void MultipleListenersTest()
+ {
+ const string key1 = "first";
+ const string key2 = "second";
+ const string key3 = "third";
+
+ for (int changingListeners = 0; changingListeners <= 3; changingListeners++)
+ {
+ for (int changedListeners = 0; changedListeners <= 3; changedListeners++)
+ {
+ MultipleListenersTestInternal<JsonObject>(
+ () => new JsonObject { { key1, 1 }, { key2, 2 } },
+ delegate(JsonObject obj)
+ {
+ obj[key2] = "hello";
+ obj.Remove(key1);
+ obj.Add(key3, "world");
+ obj.Clear();
+ },
+ new List<JsonValueChangeEventArgs>
+ {
+ new JsonValueChangeEventArgs("hello", JsonValueChange.Replace, key2),
+ new JsonValueChangeEventArgs(1, JsonValueChange.Remove, key1),
+ new JsonValueChangeEventArgs("world", JsonValueChange.Add, key3),
+ new JsonValueChangeEventArgs(null, JsonValueChange.Clear, null),
+ },
+ new List<JsonValueChangeEventArgs>
+ {
+ new JsonValueChangeEventArgs(2, JsonValueChange.Replace, key2),
+ new JsonValueChangeEventArgs(1, JsonValueChange.Remove, key1),
+ new JsonValueChangeEventArgs("world", JsonValueChange.Add, key3),
+ new JsonValueChangeEventArgs(null, JsonValueChange.Clear, null),
+ },
+ changingListeners,
+ changedListeners);
+
+ MultipleListenersTestInternal<JsonArray>(
+ () => new JsonArray(1, 2),
+ delegate(JsonArray arr)
+ {
+ arr[1] = "hello";
+ arr.RemoveAt(0);
+ arr.Add("world");
+ arr.Clear();
+ },
+ new List<JsonValueChangeEventArgs>
+ {
+ new JsonValueChangeEventArgs("hello", JsonValueChange.Replace, 1),
+ new JsonValueChangeEventArgs(1, JsonValueChange.Remove, 0),
+ new JsonValueChangeEventArgs("world", JsonValueChange.Add, 1),
+ new JsonValueChangeEventArgs(null, JsonValueChange.Clear, 0),
+ },
+ new List<JsonValueChangeEventArgs>
+ {
+ new JsonValueChangeEventArgs(2, JsonValueChange.Replace, 1),
+ new JsonValueChangeEventArgs(1, JsonValueChange.Remove, 0),
+ new JsonValueChangeEventArgs("world", JsonValueChange.Add, 1),
+ new JsonValueChangeEventArgs(null, JsonValueChange.Clear, 0),
+ },
+ changingListeners,
+ changedListeners);
+ }
+ }
+ }
+
+ internal static void TestEvents<JsonValueType>(JsonValueType target, Action<JsonValueType> actionToTriggerEvent, List<Tuple<bool, JsonValue, JsonValueChangeEventArgs>> expectedEvents, Action<JsonValueType> actionToTriggerEvent2 = null) where JsonValueType : JsonValue
+ {
+ var actualEvents = new List<Tuple<bool, JsonValue, JsonValueChangeEventArgs>>();
+ EventHandler<JsonValueChangeEventArgs> changingHandler = (sender, e) => actualEvents.Add(new Tuple<bool, JsonValue, JsonValueChangeEventArgs>(true, sender as JsonValue, e));
+ EventHandler<JsonValueChangeEventArgs> changedHandler = (sender, e) => actualEvents.Add(new Tuple<bool, JsonValue, JsonValueChangeEventArgs>(false, sender as JsonValue, e));
+
+ target.Changing += changingHandler;
+ target.Changed += changedHandler;
+ actionToTriggerEvent(target);
+
+ target.Changing -= changingHandler;
+ target.Changed -= changedHandler;
+ ValidateExpectedEvents(expectedEvents, actualEvents);
+ if (actionToTriggerEvent2 == null)
+ {
+ actionToTriggerEvent(target);
+ }
+ else
+ {
+ actionToTriggerEvent2(target);
+ }
+
+ ValidateExpectedEvents(expectedEvents, actualEvents);
+ }
+
+ internal static void TestEventsDynamic(dynamic target, Action<dynamic> actionToTriggerEvent, List<Tuple<bool, JsonValue, JsonValueChangeEventArgs>> expectedEvents)
+ {
+ var actualEvents = new List<Tuple<bool, JsonValue, JsonValueChangeEventArgs>>();
+ EventHandler<JsonValueChangeEventArgs> changingHandler = (sender, e) => actualEvents.Add(new Tuple<bool, JsonValue, JsonValueChangeEventArgs>(true, sender as JsonValue, e));
+ EventHandler<JsonValueChangeEventArgs> changedHandler = (sender, e) => actualEvents.Add(new Tuple<bool, JsonValue, JsonValueChangeEventArgs>(false, sender as JsonValue, e));
+
+ target.Changing += changingHandler;
+ target.Changed += changedHandler;
+ actionToTriggerEvent(target);
+
+ target.Changing -= changingHandler;
+ target.Changed -= changedHandler;
+ ValidateExpectedEvents(expectedEvents, actualEvents);
+
+ actionToTriggerEvent(target);
+ ValidateExpectedEvents(expectedEvents, actualEvents);
+ }
+
+ private static void ValidateExpectedEvents(List<Tuple<bool, JsonValue, JsonValueChangeEventArgs>> expectedEvents, List<Tuple<bool, JsonValue, JsonValueChangeEventArgs>> actualEvents)
+ {
+ Assert.Equal(expectedEvents.Count, actualEvents.Count);
+ for (int i = 0; i < expectedEvents.Count; i++)
+ {
+ bool expectedIsChanging = expectedEvents[i].Item1;
+ bool actualIsChanging = expectedEvents[i].Item1;
+ Assert.Equal(expectedIsChanging, actualIsChanging);
+
+ JsonValue expectedSender = expectedEvents[i].Item2;
+ JsonValue actualSender = actualEvents[i].Item2;
+ Assert.Equal(expectedSender, actualSender);
+
+ JsonValueChangeEventArgs expectedEventArgs = expectedEvents[i].Item3;
+ JsonValueChangeEventArgs actualEventArgs = actualEvents[i].Item3;
+ Assert.Equal(expectedEventArgs.Change, actualEventArgs.Change);
+ Assert.Equal(expectedEventArgs.Index, actualEventArgs.Index);
+ Assert.Equal(expectedEventArgs.Key, actualEventArgs.Key);
+
+ string expectedChild = expectedEventArgs.Child == null ? "null" : expectedEventArgs.Child.ToString();
+ string actualChild = actualEventArgs.Child == null ? "null" : actualEventArgs.Child.ToString();
+ Assert.Equal(expectedChild, actualChild);
+ }
+ }
+
+ internal static void MultipleListenersTestInternal<JsonValueType>(
+ Func<JsonValueType> createTarget,
+ Action<JsonValueType> actionToTriggerEvents,
+ List<JsonValueChangeEventArgs> expectedChangingEventArgs,
+ List<JsonValueChangeEventArgs> expectedChangedEventArgs,
+ int changingListeners,
+ int changedListeners) where JsonValueType : JsonValue
+ {
+ Log.Info("Testing events on a {0} for {1} changING listeners and {2} changED listeners", typeof(JsonValueType).Name, changingListeners, changedListeners);
+ JsonValueType target = createTarget();
+ List<Tuple<bool, JsonValue, JsonValueChangeEventArgs>>[] actualChangingEvents = new List<Tuple<bool, JsonValue, JsonValueChangeEventArgs>>[changingListeners];
+ List<Tuple<bool, JsonValue, JsonValueChangeEventArgs>>[] actualChangedEvents = new List<Tuple<bool, JsonValue, JsonValueChangeEventArgs>>[changedListeners];
+ List<Tuple<bool, JsonValue, JsonValueChangeEventArgs>> expectedChangingEvents = new List<Tuple<bool, JsonValue, JsonValueChangeEventArgs>>(
+ expectedChangingEventArgs.Select((args) => new Tuple<bool, JsonValue, JsonValueChangeEventArgs>(true, target, args)));
+ List<Tuple<bool, JsonValue, JsonValueChangeEventArgs>> expectedChangedEvents = new List<Tuple<bool, JsonValue, JsonValueChangeEventArgs>>(
+ expectedChangedEventArgs.Select((args) => new Tuple<bool, JsonValue, JsonValueChangeEventArgs>(false, target, args)));
+
+ for (int i = 0; i < changingListeners; i++)
+ {
+ actualChangingEvents[i] = new List<Tuple<bool, JsonValue, JsonValueChangeEventArgs>>();
+ var index = i;
+ target.Changing += delegate(object sender, JsonValueChangeEventArgs e)
+ {
+ actualChangingEvents[index].Add(new Tuple<bool, JsonValue, JsonValueChangeEventArgs>(true, sender as JsonValue, e));
+ };
+ }
+
+ for (int i = 0; i < changedListeners; i++)
+ {
+ actualChangedEvents[i] = new List<Tuple<bool, JsonValue, JsonValueChangeEventArgs>>();
+ var index = i;
+ target.Changed += delegate(object sender, JsonValueChangeEventArgs e)
+ {
+ actualChangedEvents[index].Add(new Tuple<bool, JsonValue, JsonValueChangeEventArgs>(false, sender as JsonValue, e));
+ };
+ }
+
+ actionToTriggerEvents(target);
+ for (int i = 0; i < changingListeners; i++)
+ {
+ Log.Info("Validating Changing events for listener {0}", i);
+ ValidateExpectedEvents(expectedChangingEvents, actualChangingEvents[i]);
+ }
+
+ for (int i = 0; i < changedListeners; i++)
+ {
+ Log.Info("Validating Changed events for listener {0}", i);
+ ValidateExpectedEvents(expectedChangedEvents, actualChangedEvents[i]);
+ }
+
+ for (int i = 0; i < changingListeners; i++)
+ {
+ actualChangingEvents[i] = new List<Tuple<bool, JsonValue, JsonValueChangeEventArgs>>();
+ var index = i;
+ target.Changing -= delegate(object sender, JsonValueChangeEventArgs e)
+ {
+ actualChangingEvents[i].Add(new Tuple<bool, JsonValue, JsonValueChangeEventArgs>(true, sender as JsonValue, e));
+ };
+ }
+
+ for (int i = 0; i < changedListeners; i++)
+ {
+ actualChangedEvents[i] = new List<Tuple<bool, JsonValue, JsonValueChangeEventArgs>>();
+ var index = i;
+ target.Changed -= delegate(object sender, JsonValueChangeEventArgs e)
+ {
+ actualChangedEvents[i].Add(new Tuple<bool, JsonValue, JsonValueChangeEventArgs>(false, sender as JsonValue, e));
+ };
+ }
+
+ target = createTarget();
+ expectedChangingEvents.Clear();
+ expectedChangedEvents.Clear();
+ actionToTriggerEvents(target);
+
+ for (int i = 0; i < changingListeners; i++)
+ {
+ Log.Info("Validating Changing events for listener {0}", i);
+ ValidateExpectedEvents(expectedChangingEvents, actualChangingEvents[i]);
+ }
+
+ for (int i = 0; i < changedListeners; i++)
+ {
+ Log.Info("Validating Changed events for listener {0}", i);
+ ValidateExpectedEvents(expectedChangedEvents, actualChangedEvents[i]);
+ }
+ }
+
+ private static void ValidateJsonArrayItems(JsonArray jsonArray, IEnumerable<JsonValue> expectedItems)
+ {
+ List<JsonValue> expected = new List<JsonValue>(expectedItems);
+ Assert.Equal(expected.Count, jsonArray.Count);
+ for (int i = 0; i < expected.Count; i++)
+ {
+ Assert.Equal(expected[i], jsonArray[i]);
+ }
+ }
+ }
+}
diff --git a/test/System.Json.Test.Integration/JsonValueLinqExtensionsIntegrationTest.cs b/test/System.Json.Test.Integration/JsonValueLinqExtensionsIntegrationTest.cs
new file mode 100644
index 00000000..1e556f4a
--- /dev/null
+++ b/test/System.Json.Test.Integration/JsonValueLinqExtensionsIntegrationTest.cs
@@ -0,0 +1,68 @@
+using System.Linq;
+using Xunit;
+
+namespace System.Json
+{
+ /// <summary>
+ /// Tests for the Linq extensions to the <see cref="JsonValue"/> types.
+ /// </summary>
+ public class JsonValueLinqExtensionsIntegrationTest
+ {
+ /// <summary>
+ /// Test for the <see cref="JsonValueLinqExtensions.ToJsonArray"/> method.
+ /// </summary>
+ [Fact]
+ public void ToJsonArrayTest()
+ {
+ string json = "{\"SearchResponse\":{\"Phonebook\":{\"Results\":[{\"name\":1,\"rating\":1}, {\"name\":2,\"rating\":2}, {\"name\":3,\"rating\":3}]}}}";
+ string expected = "[{\"name\":2,\"rating\":2},{\"name\":3,\"rating\":3}]";
+
+ JsonValue jv = JsonValue.Parse(json);
+ double rating = 1;
+ var jsonResult = from n in jv.ValueOrDefault("SearchResponse", "Phonebook", "Results")
+ where n.Value.ValueOrDefault("rating").ReadAs<double>(0.0) > rating
+ select n.Value;
+ var ja = jsonResult.ToJsonArray();
+
+ Assert.Equal(expected, ja.ToString());
+ }
+
+ /// <summary>
+ /// Test for the <see cref="JsonValueLinqExtensions.ToJsonObject"/> method.
+ /// </summary>
+ [Fact]
+ public void ToJsonObjectTest()
+ {
+ string json = "{\"Name\":\"Bill Gates\",\"Age\":23,\"AnnualIncome\":45340.45,\"MaritalStatus\":\"Single\",\"EducationLevel\":\"MiddleSchool\",\"SSN\":432332453,\"CellNumber\":2340393420}";
+ string expected = "{\"AnnualIncome\":45340.45,\"SSN\":432332453,\"CellNumber\":2340393420}";
+
+ JsonValue jv = JsonValue.Parse(json);
+ decimal decVal;
+ var jsonResult = from n in jv
+ where n.Value.TryReadAs<decimal>(out decVal) && decVal > 100
+ select n;
+ var jo = jsonResult.ToJsonObject();
+
+ Assert.Equal(expected, jo.ToString());
+ }
+
+ /// <summary>
+ /// Test for the <see cref="JsonValueLinqExtensions.ToJsonObject"/> method where the origin is a <see cref="JsonArray"/>.
+ /// </summary>
+ [Fact]
+ public void ToJsonObjectFromArrayTest()
+ {
+ string json = "{\"SearchResponse\":{\"Phonebook\":{\"Results\":[{\"name\":1,\"rating\":1}, {\"name\":2,\"rating\":2}, {\"name\":3,\"rating\":3}]}}}";
+ string expected = "{\"1\":{\"name\":2,\"rating\":2},\"2\":{\"name\":3,\"rating\":3}}";
+
+ JsonValue jv = JsonValue.Parse(json);
+ double rating = 1;
+ var jsonResult = from n in jv.ValueOrDefault("SearchResponse", "Phonebook", "Results")
+ where n.Value.ValueOrDefault("rating").ReadAs<double>(0.0) > rating
+ select n;
+ var jo = jsonResult.ToJsonObject();
+
+ Assert.Equal(expected, jo.ToString());
+ }
+ }
+}
diff --git a/test/System.Json.Test.Integration/JsonValuePartialTrustTests.cs b/test/System.Json.Test.Integration/JsonValuePartialTrustTests.cs
new file mode 100644
index 00000000..69ce8531
--- /dev/null
+++ b/test/System.Json.Test.Integration/JsonValuePartialTrustTests.cs
@@ -0,0 +1,186 @@
+using System.IO;
+using System.Reflection;
+using System.Runtime.Serialization.Json;
+using System.Security;
+using System.Security.Policy;
+using Xunit;
+
+namespace System.Json
+{
+ /// <summary>
+ /// Tests for using the <see cref="JsonValue"/> types in partial trust.
+ /// </summary>
+ [Serializable]
+ public class JsonValuePartialTrustTests
+ {
+ /// <summary>
+ /// Validates the condition, throwing an exception if it is false.
+ /// </summary>
+ /// <param name="condition">The condition to be evaluated.</param>
+ /// <param name="msg">The exception message to be thrown, in case the condition is false.</param>
+ public static void AssertIsTrue(bool condition, string msg)
+ {
+ if (!condition)
+ {
+ throw new InvalidOperationException(msg);
+ }
+ }
+
+ /// <summary>
+ /// Validates that the two objects are equal, throwing an exception if it is false.
+ /// </summary>
+ /// <param name="obj1">The first object to be compared.</param>
+ /// <param name="obj2">The second object to be compared.</param>
+ /// <param name="msg">The exception message to be thrown, in case the condition is false.</param>
+ public static void AssertAreEqual(object obj1, object obj2, string msg)
+ {
+ if (obj1 == obj2)
+ {
+ return;
+ }
+
+ if (obj1 == null || obj2 == null || !obj1.Equals(obj2))
+ {
+ throw new InvalidOperationException(String.Format("[{0}, {2}] and [{1}, {3}] expected to be equal. {4}", obj1, obj2, obj1.GetType().Name, obj2.GetType().Name, msg));
+ }
+ }
+
+ /// <summary>
+ /// Partial trust tests for <see cref="JsonValue"/> instances where no dynamic references are used.
+ /// </summary>
+ [Fact(Skip = "Re-enable when CSDMain 216528: 'Partial trust support for Web API' has been fixed")]
+ public void RunNonDynamicTest()
+ {
+ RunInPartialTrust(this.NonDynamicTest);
+ }
+
+ /// <summary>
+ /// Partial trust tests for <see cref="JsonValue"/> with dynamic references.
+ /// </summary>
+ [Fact(Skip = "Re-enable when CSDMain 216528: 'Partial trust support for Web API' has been fixed")]
+ public void RunDynamicTest()
+ {
+ RunInPartialTrust(this.DynamicTest);
+ }
+
+ /// <summary>
+ /// Tests for <see cref="JsonValue"/> instances without dynamic references.
+ /// </summary>
+ public void NonDynamicTest()
+ {
+ int seed = GetRandomSeed();
+ Log.Info("Seed: {0}", seed);
+ Random rndGen = new Random(seed);
+
+ AssertIsTrue(Assembly.GetExecutingAssembly().IsFullyTrusted == false, "Executing assembly not expected to be fully trusted!");
+
+ Person person = new Person(rndGen);
+ Person person2 = new Person(rndGen);
+
+ person.AddFriends(3, rndGen);
+ person2.AddFriends(3, rndGen);
+
+ JsonValue jo = JsonValueExtensions.CreateFrom(person);
+ JsonValue jo2 = JsonValueExtensions.CreateFrom(person2);
+
+ AssertAreEqual(person.Address.City, jo["Address"]["City"].ReadAs<string>(), "Address.City");
+ AssertAreEqual(person.Friends[1].Age, jo["Friends"][1]["Age"].ReadAs<int>(), "Friends[1].Age");
+
+ string newCityName = "Bellevue";
+
+ jo["Address"]["City"] = newCityName;
+ AssertAreEqual(newCityName, (string)jo["Address"]["City"], "Address.City2");
+
+ jo["Friends"][1] = jo2;
+ AssertAreEqual(person2.Age, (int)jo["Friends"][1]["Age"], "Friends[1].Age2");
+
+ AssertAreEqual(person2.Address.City, jo.ValueOrDefault("Friends").ValueOrDefault(1).ValueOrDefault("Address").ValueOrDefault("City").ReadAs<string>(), "Address.City3");
+ AssertAreEqual(person2.Age, (int)jo.ValueOrDefault("Friends").ValueOrDefault(1).ValueOrDefault("Age"), "Friends[1].Age3");
+
+ AssertAreEqual(person2.Address.City, jo.ValueOrDefault("Friends", 1, "Address", "City").ReadAs<string>(), "Address.City3");
+ AssertAreEqual(person2.Age, (int)jo.ValueOrDefault("Friends", 1, "Age"), "Friends[1].Age3");
+
+ int newAge = 42;
+ JsonValue ageValue = jo["Friends"][1]["Age"] = newAge;
+ AssertAreEqual(newAge, (int)ageValue, "Friends[1].Age4");
+ }
+
+ /// <summary>
+ /// Tests for <see cref="JsonValue"/> instances with dynamic references.
+ /// </summary>
+ public void DynamicTest()
+ {
+ int seed = GetRandomSeed();
+ Log.Info("Seed: {0}", seed);
+ Random rndGen = new Random(seed);
+
+ AssertIsTrue(Assembly.GetExecutingAssembly().IsFullyTrusted == false, "Executing assembly not expected to be fully trusted!");
+
+ Person person = new Person(rndGen);
+ person.AddFriends(1, rndGen);
+
+ dynamic jo = JsonValueExtensions.CreateFrom(person);
+
+ AssertAreEqual(person.Friends[0].Name, jo.Friends[0].Name.ReadAs<string>(), "Friends[0].Name");
+ AssertAreEqual(person.Address.City, jo.Address.City.ReadAs<string>(), "Address.City");
+ AssertAreEqual(person.Friends[0].Age, (int)jo.Friends[0].Age, "Friends[0].Age");
+
+ string newCityName = "Bellevue";
+
+ jo.Address.City = newCityName;
+ AssertAreEqual(newCityName, (string)jo.Address.City, "Address.City2");
+
+ AssertAreEqual(person.Friends[0].Address.City, jo.ValueOrDefault("Friends").ValueOrDefault(0).ValueOrDefault("Address").ValueOrDefault("City").ReadAs<string>(), "Friends[0].Address.City");
+ AssertAreEqual(person.Friends[0].Age, (int)jo.ValueOrDefault("Friends").ValueOrDefault(0).ValueOrDefault("Age"), "Friends[0].Age2");
+
+ AssertAreEqual(person.Friends[0].Address.City, jo.ValueOrDefault("Friends", 0, "Address", "City").ReadAs<string>(), "Friends[0].Address.City");
+ AssertAreEqual(person.Friends[0].Age, (int)jo.ValueOrDefault("Friends", 0, "Age"), "Friends[0].Age2");
+
+ int newAge = 42;
+ JsonValue ageValue = jo.Friends[0].Age = newAge;
+ AssertAreEqual(newAge, (int)ageValue, "Friends[0].Age3");
+
+ AssertIsTrue(jo.NonExistentProperty is JsonValue, "Expected a JsonValue");
+ AssertIsTrue(jo.NonExistentProperty.JsonType == JsonType.Default, "Expected default JsonValue");
+ }
+
+ private static void RunInPartialTrust(CrossAppDomainDelegate testMethod)
+ {
+ Assert.True(Assembly.GetExecutingAssembly().IsFullyTrusted);
+
+ AppDomainSetup setup = new AppDomainSetup();
+ setup.ApplicationBase = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
+ PermissionSet perms = PermissionsHelper.InternetZone;
+ AppDomain domain = AppDomain.CreateDomain("PartialTrustSandBox", null, setup, perms);
+
+ domain.DoCallBack(testMethod);
+ }
+
+ private static int GetRandomSeed()
+ {
+ DateTime now = DateTime.Now;
+ return (now.Year * 10000) + (now.Month * 100) + now.Day;
+ }
+
+ internal static class PermissionsHelper
+ {
+ private static PermissionSet internetZone;
+
+ public static PermissionSet InternetZone
+ {
+ get
+ {
+ if (internetZone == null)
+ {
+ Evidence evidence = new Evidence();
+ evidence.AddHostEvidence(new Zone(SecurityZone.Internet));
+
+ internetZone = SecurityManager.GetStandardSandbox(evidence);
+ }
+
+ return internetZone;
+ }
+ }
+ }
+ }
+}
diff --git a/test/System.Json.Test.Integration/JsonValueTestHelper.cs b/test/System.Json.Test.Integration/JsonValueTestHelper.cs
new file mode 100644
index 00000000..8896fe64
--- /dev/null
+++ b/test/System.Json.Test.Integration/JsonValueTestHelper.cs
@@ -0,0 +1,609 @@
+using System.Collections.Generic;
+using System.Globalization;
+
+namespace System.Json
+{
+ internal static class JsonValueVerifier
+ {
+ public static bool Compare(JsonValue objA, JsonValue objB)
+ {
+ if (objA == null && objB == null)
+ {
+ return true;
+ }
+
+ if ((objA == null && objB != null) || (objA != null && objB == null))
+ {
+ Log.Info("JsonValueVerifier Error: At least one of the JsonValue compared is null");
+ return false;
+ }
+
+ if (objA.JsonType != objB.JsonType)
+ {
+ Log.Info("JsonValueVerifier Error: These two JsonValues are not of the same Type!");
+ Log.Info("objA is of type {0} while objB is of type {1}", objA.JsonType.ToString(), objB.JsonType.ToString());
+ return false;
+ }
+
+ return CompareJsonValues(objA, objB);
+ }
+
+ public static bool CompareStringLists(List<string> strListA, List<string> strListB)
+ {
+ bool retValue = true;
+ if (strListA.Count != strListB.Count)
+ {
+ retValue = false;
+ }
+ else
+ {
+ for (int i = 0; i < strListA.Count; i++)
+ {
+ if (strListA[i] != strListB[i])
+ {
+ retValue = false;
+ break;
+ }
+ }
+ }
+
+ return retValue;
+ }
+
+ // Because we are currently taking a "flat design" model on JsonValues, the intellense doesn't work, and we have to be smart about what to verify
+ // and what not to so to avoid any potentially invalid access
+ private static bool CompareJsonValues(JsonValue objA, JsonValue objB)
+ {
+ bool retValue = false;
+ switch (objA.JsonType)
+ {
+ case JsonType.Array:
+ retValue = CompareJsonArrayTypes((JsonArray)objA, (JsonArray)objB);
+ break;
+ case JsonType.Object:
+ retValue = CompareJsonObjectTypes((JsonObject)objA, (JsonObject)objB);
+ break;
+ case JsonType.Boolean:
+ case JsonType.Number:
+ case JsonType.String:
+ retValue = CompareJsonPrimitiveTypes((JsonPrimitive)objA, (JsonPrimitive)objB);
+ break;
+ default:
+ Log.Info("JsonValueVerifier Error: the JsonValue isn’t an array, a complex type or a primitive type!");
+ break;
+ }
+
+ return retValue;
+ }
+
+ private static bool CompareJsonArrayTypes(JsonArray objA, JsonArray objB)
+ {
+ bool retValue = true;
+
+ if (objA == null || objB == null || objA.Count != objB.Count || objA.IsReadOnly != objB.IsReadOnly)
+ {
+ return false;
+ }
+
+ try
+ {
+ for (int i = 0; i < objA.Count; i++)
+ {
+ if (!Compare(objA[i], objB[i]))
+ {
+ Log.Info("JsonValueVerifier (JsonArrayType) Error: objA[{0}] = {1}", i, objA[i].ToString());
+ Log.Info("JsonValueVerifier (JsonArrayType) Error: objB[{0}] = {1}", i, objB[i].ToString());
+ return false;
+ }
+ }
+ }
+ catch (Exception e)
+ {
+ Log.Info("JsonValueVerifier (JsonArrayType) Error: An Exception was thrown: " + e);
+ return false;
+ }
+
+ return retValue;
+ }
+
+ private static bool CompareJsonObjectTypes(JsonObject objA, JsonObject objB)
+ {
+ bool retValue = true;
+
+ try
+ {
+ if (objA.Keys.Count != objB.Keys.Count)
+ {
+ Log.Info("JsonValueVerifier (JsonObjectTypes) Error: objA.Keys.Count does not match objB.Keys.Count!");
+ Log.Info("JsonValueVerifier (JsonObjectTypes) Error: objA.Keys.Count = {0}, objB.Keys.Count = {1}", objA.Keys.Count, objB.Keys.Count);
+ return false;
+ }
+
+ if (objA.Keys.IsReadOnly != objB.Keys.IsReadOnly)
+ {
+ Log.Info("JsonValueVerifier (JsonObjectTypes) Error: objA.Keys.IsReadOnly does not match objB.Keys.IsReadOnly!");
+ Log.Info("JsonValueVerifier (JsonObjectTypes) Error: objA.Keys.IsReadOnly = {0}, objB.Keys.IsReadOnly = {1}", objA.Keys.IsReadOnly, objB.Keys.IsReadOnly);
+ return false;
+ }
+ else
+ {
+ foreach (string keyA in objA.Keys)
+ {
+ if (!objB.ContainsKey(keyA))
+ {
+ Log.Info("JsonValueVerifier (JsonObjectTypes) Error: objB does not contain Key " + keyA + "!");
+ return false;
+ }
+
+ if (!Compare(objA[keyA], objB[keyA]))
+ {
+ Log.Info("JsonValueVerifier (JsonObjectTypes) Error: objA[" + keyA + "] = " + objA[keyA]);
+ Log.Info("JsonValueVerifier (JsonObjectTypes) Error: objB[" + keyA + "] = " + objB[keyA]);
+ return false;
+ }
+ }
+ }
+ }
+ catch (Exception e)
+ {
+ Log.Info("JsonValueVerifier (JsonObjectTypes) Error: An Exception was thrown: " + e);
+ return false;
+ }
+
+ return retValue;
+ }
+
+ private static bool CompareJsonPrimitiveTypes(JsonPrimitive objA, JsonPrimitive objB)
+ {
+ try
+ {
+ if (objA.ToString() != objB.ToString())
+ {
+ // Special case due to daylight saving hours change: every March on the morning of the third Sunday, we adjust the time
+ // from 2am to 3am straight, so for that one hour 2:13am = 3:15am. We must result to the UTC ticks to verify the actual
+ // time is always the same, regardless of the loc/glob setup on the machine
+ if (objA.ToString().StartsWith("\"\\/Date(") && objA.ToString().EndsWith(")\\/\""))
+ {
+ return GetUTCTicks(objA) == GetUTCTicks(objB);
+ }
+ else
+ {
+ Log.Info("JsonValueVerifier (JsonPrimitiveTypes) Error: objA = " + objA.ToString());
+ Log.Info("JsonValueVerifier (JsonPrimitiveTypes) Error: objB = " + objB.ToString());
+ return false;
+ }
+ }
+ else
+ {
+ return true;
+ }
+ }
+ catch (Exception e)
+ {
+ Log.Info("JsonValueVerifier (JsonPrimitiveTypes) Error: An Exception was thrown: " + e);
+ return false;
+ }
+ }
+
+ // the input JsonPrimitive DateTime format is "\/Date(24735422733034-0700)\/" or "\/Date(24735422733034)\/"
+ // the only thing useful for us is the UTC ticks "24735422733034"
+ // everything after - if present - is just an optional offset between the local time and UTC
+ private static string GetUTCTicks(JsonPrimitive jprim)
+ {
+ string retValue = String.Empty;
+
+ string origStr = jprim.ToString();
+ int startIndex = origStr.IndexOf("Date(") + 5;
+ int endIndex = origStr.IndexOf('-', startIndex + 1); // the UTC ticks can start with a '-' sign (dates prior to 1970/01/01)
+
+ // if the optional offset is present in the data format, we want to take only the UTC ticks
+ if (startIndex < endIndex)
+ {
+ retValue = origStr.Substring(startIndex, endIndex - startIndex);
+ }
+ else
+ {
+ // otherwise we assume the time format is without the oiptional offset, or unexpected, and use the whole string for comparison.
+ retValue = origStr;
+ }
+
+ return retValue;
+ }
+ }
+
+ internal static class SpecialJsonValueHelper
+ {
+ public static JsonArray CreateDeepLevelJsonValuePair(int seed, out JsonArray newOrderJson)
+ {
+ Log.Info("Seed: {0}", seed);
+ Random rndGen = new Random(seed);
+
+ bool myBool = PrimitiveCreator.CreateInstanceOfBoolean(rndGen);
+ byte myByte = PrimitiveCreator.CreateInstanceOfByte(rndGen);
+ DateTime myDatetime = PrimitiveCreator.CreateInstanceOfDateTime(rndGen);
+ DateTimeOffset myDateTimeOffset = PrimitiveCreator.CreateInstanceOfDateTimeOffset(rndGen);
+ decimal myDecimal = PrimitiveCreator.CreateInstanceOfDecimal(rndGen);
+ double myDouble = PrimitiveCreator.CreateInstanceOfDouble(rndGen);
+ short myInt16 = PrimitiveCreator.CreateInstanceOfInt16(rndGen);
+ int myInt32 = PrimitiveCreator.CreateInstanceOfInt32(rndGen);
+ long myInt64 = PrimitiveCreator.CreateInstanceOfInt64(rndGen);
+ sbyte mySByte = PrimitiveCreator.CreateInstanceOfSByte(rndGen);
+ float mySingle = PrimitiveCreator.CreateInstanceOfSingle(rndGen);
+ string myString = PrimitiveCreator.CreateInstanceOfString(rndGen, 20, null);
+ ushort myUInt16 = PrimitiveCreator.CreateInstanceOfUInt16(rndGen);
+ uint myUInt32 = PrimitiveCreator.CreateInstanceOfUInt32(rndGen);
+ ulong myUInt64 = PrimitiveCreator.CreateInstanceOfUInt64(rndGen);
+ JsonArray myArray = new JsonArray { myBool, myByte, myDatetime, myDateTimeOffset, myDecimal, myDouble, myInt16, myInt32, myInt64, mySByte, mySingle, myString, myUInt16, myUInt32, myUInt64 };
+ JsonArray myArrayLevel2 = new JsonArray { myArray, myArray, myArray };
+ JsonArray myArrayLevel3 = new JsonArray { myArrayLevel2, myArrayLevel2, myArrayLevel2 };
+ JsonArray myArrayLevel4 = new JsonArray { myArrayLevel3, myArrayLevel3, myArrayLevel3 };
+ JsonArray myArrayLevel5 = new JsonArray { myArrayLevel4, myArrayLevel4, myArrayLevel4 };
+ JsonArray myArrayLevel6 = new JsonArray { myArrayLevel5, myArrayLevel5, myArrayLevel5 };
+ JsonArray myArrayLevel7 = new JsonArray { myArrayLevel6, myArrayLevel6, myArrayLevel6 };
+
+ JsonArray sourceJson = BuildJsonArrayinSequence1(myBool, myByte, myDatetime, myDateTimeOffset, myDecimal, myDouble, myInt16, myInt32, myInt64, mySByte, mySingle, myString, myUInt16, myUInt32, myUInt64, myArray, myArrayLevel2, myArrayLevel3, myArrayLevel4, myArrayLevel5, myArrayLevel6, myArrayLevel7);
+
+ newOrderJson = BuildJsonArrayinSequence2(myBool, myByte, myDatetime, myDateTimeOffset, myDecimal, myDouble, myInt16, myInt32, myInt64, mySByte, mySingle, myString, myUInt16, myUInt32, myUInt64, myArray, myArrayLevel2, myArrayLevel3, myArrayLevel4, myArrayLevel5, myArrayLevel6, myArrayLevel7);
+
+ return sourceJson;
+ }
+
+ public static JsonValue CreateDeepLevelJsonValue()
+ {
+ int seed = Environment.TickCount;
+ Log.Info("Seed: {0}", seed);
+ Random rndGen = new Random(seed);
+
+ bool myBool = PrimitiveCreator.CreateInstanceOfBoolean(rndGen);
+ byte myByte = PrimitiveCreator.CreateInstanceOfByte(rndGen);
+ DateTime myDatetime = PrimitiveCreator.CreateInstanceOfDateTime(rndGen);
+ DateTimeOffset myDateTimeOffset = PrimitiveCreator.CreateInstanceOfDateTimeOffset(rndGen);
+ decimal myDecimal = PrimitiveCreator.CreateInstanceOfDecimal(rndGen);
+ double myDouble = PrimitiveCreator.CreateInstanceOfDouble(rndGen);
+ short myInt16 = PrimitiveCreator.CreateInstanceOfInt16(rndGen);
+ int myInt32 = PrimitiveCreator.CreateInstanceOfInt32(rndGen);
+ long myInt64 = PrimitiveCreator.CreateInstanceOfInt64(rndGen);
+ sbyte mySByte = PrimitiveCreator.CreateInstanceOfSByte(rndGen);
+ float mySingle = PrimitiveCreator.CreateInstanceOfSingle(rndGen);
+ string myString = PrimitiveCreator.CreateInstanceOfString(rndGen, 20, null);
+ ushort myUInt16 = PrimitiveCreator.CreateInstanceOfUInt16(rndGen);
+ uint myUInt32 = PrimitiveCreator.CreateInstanceOfUInt32(rndGen);
+ ulong myUInt64 = PrimitiveCreator.CreateInstanceOfUInt64(rndGen);
+ JsonArray myArray = new JsonArray { myBool, myByte, myDatetime, myDateTimeOffset, myDecimal, myDouble, myInt16, myInt32, myInt64, mySByte, mySingle, myString, myUInt16, myUInt32, myUInt64 };
+ JsonArray myArrayLevel2 = new JsonArray { myArray, myArray, myArray };
+ JsonArray myArrayLevel3 = new JsonArray { myArrayLevel2, myArrayLevel2, myArrayLevel2 };
+ JsonArray myArrayLevel4 = new JsonArray { myArrayLevel3, myArrayLevel3, myArrayLevel3 };
+ JsonArray myArrayLevel5 = new JsonArray { myArrayLevel4, myArrayLevel4, myArrayLevel4 };
+ JsonArray myArrayLevel6 = new JsonArray { myArrayLevel5, myArrayLevel5, myArrayLevel5 };
+ JsonArray myArrayLevel7 = new JsonArray { myArrayLevel6, myArrayLevel6, myArrayLevel6 };
+
+ JsonArray sourceJson = BuildJsonArrayinSequence1(myBool, myByte, myDatetime, myDateTimeOffset, myDecimal, myDouble, myInt16, myInt32, myInt64, mySByte, mySingle, myString, myUInt16, myUInt32, myUInt64, myArray, myArrayLevel2, myArrayLevel3, myArrayLevel4, myArrayLevel5, myArrayLevel6, myArrayLevel7);
+
+ return sourceJson;
+ }
+
+ public static JsonObject CreateRandomPopulatedJsonObject(int seed, int length)
+ {
+ JsonObject myObject;
+
+ myObject = new JsonObject(
+ new Dictionary<string, JsonValue>()
+ {
+ { "Name", "myArray" },
+ { "Index", 1 }
+ });
+
+ for (int i = myObject.Count; i < length / 2; i++)
+ {
+ myObject.Add(PrimitiveCreator.CreateInstanceOfString(new Random(seed + i)), GetRandomJsonPrimitives(seed + (i * 2)));
+ }
+
+ for (int i = myObject.Count; i < length; i++)
+ {
+ myObject.Add(new KeyValuePair<string, JsonValue>(PrimitiveCreator.CreateInstanceOfString(new Random(seed + (i * 10))), GetRandomJsonPrimitives(seed + (i * 20))));
+ }
+
+ return myObject;
+ }
+
+ public static JsonArray CreatePrePopulatedJsonArray(int seed, int length)
+ {
+ JsonArray myObject;
+
+ myObject = new JsonArray(new List<JsonValue>());
+
+ for (int i = myObject.Count; i < length; i++)
+ {
+ myObject.Add(GetRandomJsonPrimitives(seed + i));
+ }
+
+ return myObject;
+ }
+
+ public static JsonObject CreateIndexPopulatedJsonObject(int seed, int length)
+ {
+ JsonObject myObject;
+ myObject = new JsonObject(new Dictionary<string, JsonValue>() { });
+
+ for (int i = myObject.Count; i < length; i++)
+ {
+ myObject.Add(i.ToString(CultureInfo.InvariantCulture), GetRandomJsonPrimitives(seed + i));
+ }
+
+ return myObject;
+ }
+
+ public static JsonValue[] CreatePrePopulatedJsonValueArray(int seed, int length)
+ {
+ JsonValue[] myObject = new JsonValue[length];
+
+ for (int i = 0; i < length; i++)
+ {
+ myObject[i] = GetRandomJsonPrimitives(seed + i);
+ }
+
+ return myObject;
+ }
+
+ public static KeyValuePair<string, JsonValue> CreatePrePopulatedKeyValuePair(int seed)
+ {
+ KeyValuePair<string, JsonValue> myObject = new KeyValuePair<string, JsonValue>(seed.ToString(), GetRandomJsonPrimitives(seed));
+ return myObject;
+ }
+
+ public static List<KeyValuePair<string, JsonValue>> CreatePrePopulatedListofKeyValuePair(int seed, int length)
+ {
+ List<KeyValuePair<string, JsonValue>> myObject = new List<KeyValuePair<string, JsonValue>>();
+
+ for (int i = 0; i < length; i++)
+ {
+ myObject.Add(CreatePrePopulatedKeyValuePair(seed + i));
+ }
+
+ return myObject;
+ }
+
+ public static JsonPrimitive GetRandomJsonPrimitives(int seed)
+ {
+ JsonPrimitive myObject;
+ Random rndGen = new Random(seed);
+
+ int mod = seed % 13;
+ switch (mod)
+ {
+ case 1:
+ myObject = new JsonPrimitive(PrimitiveCreator.CreateInstanceOfBoolean(rndGen));
+ break;
+ case 2:
+ myObject = new JsonPrimitive(PrimitiveCreator.CreateInstanceOfByte(rndGen));
+ break;
+ case 3:
+ myObject = new JsonPrimitive(PrimitiveCreator.CreateInstanceOfDateTime(rndGen));
+ break;
+ case 4:
+ myObject = new JsonPrimitive(PrimitiveCreator.CreateInstanceOfDecimal(rndGen));
+ break;
+ case 5:
+ myObject = new JsonPrimitive(PrimitiveCreator.CreateInstanceOfInt16(rndGen));
+ break;
+ case 6:
+ myObject = new JsonPrimitive(PrimitiveCreator.CreateInstanceOfInt32(rndGen));
+ break;
+ case 7:
+ myObject = new JsonPrimitive(PrimitiveCreator.CreateInstanceOfInt64(rndGen));
+ break;
+ case 8:
+ myObject = new JsonPrimitive(PrimitiveCreator.CreateInstanceOfSByte(rndGen));
+ break;
+ case 9:
+ myObject = new JsonPrimitive(PrimitiveCreator.CreateInstanceOfSingle(rndGen));
+ break;
+ case 10:
+ string temp;
+ do
+ {
+ temp = PrimitiveCreator.CreateInstanceOfString(rndGen);
+ }
+ while (temp == null);
+
+ myObject = new JsonPrimitive(temp);
+ break;
+ case 11:
+ myObject = new JsonPrimitive(PrimitiveCreator.CreateInstanceOfUInt16(rndGen));
+ break;
+ case 12:
+ myObject = new JsonPrimitive(PrimitiveCreator.CreateInstanceOfUInt32(rndGen));
+ break;
+ default:
+ myObject = new JsonPrimitive(PrimitiveCreator.CreateInstanceOfUInt64(rndGen));
+ break;
+ }
+
+ return myObject;
+ }
+
+ public static string GetUniqueNonNullInstanceOfString(int seed, JsonObject sourceJson)
+ {
+ string retValue = String.Empty;
+ Random rndGen = new Random(seed);
+ do
+ {
+ retValue = PrimitiveCreator.CreateInstanceOfString(rndGen);
+ }
+ while (retValue == null || sourceJson.Keys.Contains(retValue));
+
+ return retValue;
+ }
+
+ public static JsonPrimitive GetUniqueValue(int seed, JsonObject sourceJson)
+ {
+ JsonPrimitive newValue;
+ int i = 0;
+ do
+ {
+ newValue = SpecialJsonValueHelper.GetRandomJsonPrimitives(seed + i);
+ i++;
+ }
+ while (sourceJson.ToString().IndexOf(newValue.ToString()) > 0);
+
+ return newValue;
+ }
+
+ private static JsonArray BuildJsonArrayinSequence2(bool myBool, byte myByte, DateTime myDatetime, DateTimeOffset myDateTimeOffset, decimal myDecimal, double myDouble, short myInt16, int myInt32, long myInt64, sbyte mySByte, float mySingle, string myString, ushort myUInt16, uint myUInt32, ulong myUInt64, JsonArray myArray, JsonArray myArrayLevel2, JsonArray myArrayLevel3, JsonArray myArrayLevel4, JsonArray myArrayLevel5, JsonArray myArrayLevel6, JsonArray myArrayLevel7)
+ {
+ JsonArray newOrderJson;
+ newOrderJson = new JsonArray
+ {
+ new JsonObject { { "Name", "myArray" }, { "Index", 1 }, { "Obj", myArray } },
+ new JsonObject { { "Name", "myArrayLevel2" }, { "Index", 2 }, { "Obj", myArrayLevel2 } },
+ new JsonObject { { "Name", "myArrayLevel2" }, { "Index", 2 }, { "Obj", myArrayLevel2 } },
+ new JsonObject { { "Name", "myUInt32" }, { "Index", 21 }, { "Obj", myUInt32 } },
+ new JsonObject { { "Name", "myUInt32" }, { "Index", 21 }, { "Obj", myUInt32 } },
+ new JsonObject { { "Name", "myArrayLevel3" }, { "Index", 3 }, { "Obj", myArrayLevel3 } },
+ new JsonObject { { "Name", "myArrayLevel4" }, { "Index", 4 }, { "Obj", myArrayLevel4 } },
+ new JsonObject { { "Name", "myArrayLevel4" }, { "Index", 4 }, { "Obj", myArrayLevel4 } },
+ new JsonObject { { "Name", "myArrayLevel5" }, { "Index", 5 }, { "Obj", myArrayLevel5 } },
+ new JsonObject { { "Name", "myArrayLevel3" }, { "Index", 3 }, { "Obj", myArrayLevel3 } },
+ new JsonObject { { "Name", "myInt64" }, { "Index", 16 }, { "Obj", myInt64 } },
+ new JsonObject { { "Name", "myArrayLevel5" }, { "Index", 5 }, { "Obj", myArrayLevel5 } },
+ new JsonObject { { "Name", "myArrayLevel5" }, { "Index", 5 }, { "Obj", myArrayLevel5 } },
+ new JsonObject { { "Name", "myArrayLevel4" }, { "Index", 4 }, { "Obj", myArrayLevel4 } },
+ new JsonObject { { "Name", "myArrayLevel6" }, { "Index", 6 }, { "Obj", myArrayLevel6 } },
+ new JsonObject { { "Name", "myInt64" }, { "Index", 16 }, { "Obj", myInt64 } },
+ new JsonObject { { "Name", "myArrayLevel5" }, { "Index", 5 }, { "Obj", myArrayLevel5 } },
+ new JsonObject { { "Name", "myArrayLevel5" }, { "Index", 5 }, { "Obj", myArrayLevel5 } },
+ new JsonObject { { "Name", "myArrayLevel7" }, { "Index", 7 }, { "Obj", myArrayLevel7 } },
+ new JsonObject { { "Name", "myDecimal" }, { "Index", 12 }, { "Obj", myDecimal } },
+ new JsonObject { { "Name", "myArrayLevel7" }, { "Index", 7 }, { "Obj", myArrayLevel7 } },
+ new JsonObject { { "Name", "myArrayLevel6" }, { "Index", 6 }, { "Obj", myArrayLevel6 } },
+ new JsonObject { { "Name", "myArrayLevel4" }, { "Index", 4 }, { "Obj", myArrayLevel4 } },
+ new JsonObject { { "Name", "myArrayLevel6" }, { "Index", 6 }, { "Obj", myArrayLevel6 } },
+ new JsonObject { { "Name", "myArrayLevel7" }, { "Index", 7 }, { "Obj", myArrayLevel7 } },
+ new JsonObject { { "Name", "myBool" }, { "Index", 8 }, { "Obj", myBool } },
+ new JsonObject { { "Name", "myByte" }, { "Index", 9 }, { "Obj", myByte } },
+ null,
+ new JsonObject { { "Name", "myDecimal" }, { "Index", 12 }, { "Obj", myDecimal } },
+ new JsonObject { { "Name", "myArrayLevel7" }, { "Index", 7 }, { "Obj", myArrayLevel7 } },
+ new JsonObject { { "Name", "myByte" }, { "Index", 9 }, { "Obj", myByte } },
+ new JsonObject { { "Name", "myArrayLevel7" }, { "Index", 7 }, { "Obj", myArrayLevel7 } },
+ new JsonObject { { "Name", "myArrayLevel6" }, { "Index", 6 }, { "Obj", myArrayLevel6 } },
+ new JsonObject { { "Name", "myDatetime" }, { "Index", 10 }, { "Obj", myDatetime } },
+ new JsonObject { { "Name", "myArrayLevel6" }, { "Index", 6 }, { "Obj", myArrayLevel6 } },
+ new JsonObject { { "Name", "myDatetime" }, { "Index", 10 }, { "Obj", myDatetime } },
+ new JsonObject { { "Name", "myDateTimeOffset" }, { "Index", 11 }, { "Obj", myDateTimeOffset } },
+ new JsonObject { { "Name", "myArrayLevel7" }, { "Index", 7 }, { "Obj", myArrayLevel7 } },
+ new JsonObject { { "Name", "myUInt16" }, { "Index", 20 }, { "Obj", myUInt16 } },
+ new JsonObject { { "Name", "myDateTimeOffset" }, { "Index", 11 }, { "Obj", myDateTimeOffset } },
+ new JsonObject { { "Name", "myDecimal" }, { "Index", 12 }, { "Obj", myDecimal } },
+ new JsonObject { { "Name", "myInt32" }, { "Index", 15 }, { "Obj", myInt32 } },
+ new JsonObject { { "Name", "mySByte" }, { "Index", 17 }, { "Obj", mySByte } },
+ new JsonObject { { "Name", "myDecimal" }, { "Index", 12 }, { "Obj", myDecimal } },
+ new JsonObject { { "Name", "myDouble" }, { "Index", 13 }, { "Obj", myDouble } },
+ new JsonObject { { "Name", "myInt16" }, { "Index", 14 }, { "Obj", myInt16 } },
+ new JsonObject { { "Name", "myString" }, { "Index", 19 }, { "Obj", myString } },
+ new JsonObject { { "Name", "myUInt16" }, { "Index", 20 }, { "Obj", myUInt16 } },
+ new JsonObject { { "Name", "myUInt16" }, { "Index", 20 }, { "Obj", myUInt16 } },
+ new JsonObject { { "Name", "myArrayLevel7" }, { "Index", 7 }, { "Obj", myArrayLevel7 } },
+ new JsonObject { { "Name", "myUInt32" }, { "Index", 21 }, { "Obj", myUInt32 } },
+ new JsonObject { { "Name", "myInt16" }, { "Index", 14 }, { "Obj", myInt16 } },
+ new JsonObject { { "Name", "myInt32" }, { "Index", 15 }, { "Obj", myInt32 } },
+ new JsonObject { { "Name", "mySByte" }, { "Index", 17 }, { "Obj", mySByte } },
+ new JsonObject { { "Name", "myDateTimeOffset" }, { "Index", 11 }, { "Obj", myDateTimeOffset } },
+ new JsonObject { { "Name", "myArrayLevel6" }, { "Index", 6 }, { "Obj", myArrayLevel6 } },
+ new JsonObject { { "Name", "myDatetime" }, { "Index", 10 }, { "Obj", myDatetime } },
+ new JsonObject { { "Name", "mySByte" }, { "Index", 17 }, { "Obj", mySByte } },
+ new JsonObject { { "Name", "myInt32" }, { "Index", 15 }, { "Obj", myInt32 } },
+ new JsonObject { { "Name", "myInt64" }, { "Index", 16 }, { "Obj", myInt64 } },
+ new JsonObject { { "Name", "myUInt32" }, { "Index", 21 }, { "Obj", myUInt32 } },
+ new JsonObject { { "Name", "myArrayLevel3" }, { "Index", 3 }, { "Obj", myArrayLevel3 } },
+ new JsonObject { { "Name", "myInt64" }, { "Index", 16 }, { "Obj", myInt64 } },
+ new JsonObject { { "Name", "mySByte" }, { "Index", 17 }, { "Obj", mySByte } },
+ new JsonObject { { "Name", "mySByte" }, { "Index", 17 }, { "Obj", mySByte } },
+ new JsonObject { { "Name", "mySingle" }, { "Index", 18 }, { "Obj", mySingle } },
+ new JsonObject { { "Name", "myDateTimeOffset" }, { "Index", 11 }, { "Obj", myDateTimeOffset } },
+ new JsonObject { { "Name", "myDecimal" }, { "Index", 12 }, { "Obj", myDecimal } },
+ new JsonObject { { "Name", "myString" }, { "Index", 19 }, { "Obj", myString } },
+ new JsonObject { { "Name", "myUInt64" }, { "Index", 22 }, { "Obj", myUInt64 } }
+ };
+ return newOrderJson;
+ }
+
+ private static JsonArray BuildJsonArrayinSequence1(bool myBool, byte myByte, DateTime myDatetime, DateTimeOffset myDateTimeOffset, decimal myDecimal, double myDouble, short myInt16, int myInt32, long myInt64, sbyte mySByte, float mySingle, string myString, ushort myUInt16, uint myUInt32, ulong myUInt64, JsonArray myArray, JsonArray myArrayLevel2, JsonArray myArrayLevel3, JsonArray myArrayLevel4, JsonArray myArrayLevel5, JsonArray myArrayLevel6, JsonArray myArrayLevel7)
+ {
+ JsonArray sourceJson = new JsonArray
+ {
+ new JsonObject { { "Name", "myArray" }, { "Index", 1 }, { "Obj", myArray } },
+ new JsonObject { { "Name", "myArrayLevel2" }, { "Index", 2 }, { "Obj", myArrayLevel2 } },
+ new JsonObject { { "Name", "myArrayLevel2" }, { "Index", 2 }, { "Obj", myArrayLevel2 } },
+ new JsonObject { { "Name", "myArrayLevel3" }, { "Index", 3 }, { "Obj", myArrayLevel3 } },
+ new JsonObject { { "Name", "myArrayLevel3" }, { "Index", 3 }, { "Obj", myArrayLevel3 } },
+ new JsonObject { { "Name", "myArrayLevel3" }, { "Index", 3 }, { "Obj", myArrayLevel3 } },
+ new JsonObject { { "Name", "myArrayLevel4" }, { "Index", 4 }, { "Obj", myArrayLevel4 } },
+ new JsonObject { { "Name", "myArrayLevel4" }, { "Index", 4 }, { "Obj", myArrayLevel4 } },
+ new JsonObject { { "Name", "myArrayLevel4" }, { "Index", 4 }, { "Obj", myArrayLevel4 } },
+ new JsonObject { { "Name", "myArrayLevel4" }, { "Index", 4 }, { "Obj", myArrayLevel4 } },
+ new JsonObject { { "Name", "myArrayLevel5" }, { "Index", 5 }, { "Obj", myArrayLevel5 } },
+ new JsonObject { { "Name", "myArrayLevel5" }, { "Index", 5 }, { "Obj", myArrayLevel5 } },
+ new JsonObject { { "Name", "myArrayLevel5" }, { "Index", 5 }, { "Obj", myArrayLevel5 } },
+ new JsonObject { { "Name", "myArrayLevel5" }, { "Index", 5 }, { "Obj", myArrayLevel5 } },
+ new JsonObject { { "Name", "myArrayLevel5" }, { "Index", 5 }, { "Obj", myArrayLevel5 } },
+ new JsonObject { { "Name", "myArrayLevel6" }, { "Index", 6 }, { "Obj", myArrayLevel6 } },
+ new JsonObject { { "Name", "myArrayLevel6" }, { "Index", 6 }, { "Obj", myArrayLevel6 } },
+ new JsonObject { { "Name", "myArrayLevel6" }, { "Index", 6 }, { "Obj", myArrayLevel6 } },
+ new JsonObject { { "Name", "myArrayLevel6" }, { "Index", 6 }, { "Obj", myArrayLevel6 } },
+ new JsonObject { { "Name", "myArrayLevel6" }, { "Index", 6 }, { "Obj", myArrayLevel6 } },
+ new JsonObject { { "Name", "myArrayLevel6" }, { "Index", 6 }, { "Obj", myArrayLevel6 } },
+ new JsonObject { { "Name", "myArrayLevel7" }, { "Index", 7 }, { "Obj", myArrayLevel7 } },
+ new JsonObject { { "Name", "myArrayLevel7" }, { "Index", 7 }, { "Obj", myArrayLevel7 } },
+ new JsonObject { { "Name", "myArrayLevel7" }, { "Index", 7 }, { "Obj", myArrayLevel7 } },
+ new JsonObject { { "Name", "myArrayLevel7" }, { "Index", 7 }, { "Obj", myArrayLevel7 } },
+ new JsonObject { { "Name", "myArrayLevel7" }, { "Index", 7 }, { "Obj", myArrayLevel7 } },
+ new JsonObject { { "Name", "myArrayLevel7" }, { "Index", 7 }, { "Obj", myArrayLevel7 } },
+ new JsonObject { { "Name", "myArrayLevel7" }, { "Index", 7 }, { "Obj", myArrayLevel7 } },
+ new JsonObject { { "Name", "myBool" }, { "Index", 8 }, { "Obj", myBool } },
+ new JsonObject { { "Name", "myByte" }, { "Index", 9 }, { "Obj", myByte } },
+ null,
+ new JsonObject { { "Name", "myByte" }, { "Index", 9 }, { "Obj", myByte } },
+ new JsonObject { { "Name", "myDatetime" }, { "Index", 10 }, { "Obj", myDatetime } },
+ new JsonObject { { "Name", "myDatetime" }, { "Index", 10 }, { "Obj", myDatetime } },
+ new JsonObject { { "Name", "myDatetime" }, { "Index", 10 }, { "Obj", myDatetime } },
+ new JsonObject { { "Name", "myDateTimeOffset" }, { "Index", 11 }, { "Obj", myDateTimeOffset } },
+ new JsonObject { { "Name", "myDateTimeOffset" }, { "Index", 11 }, { "Obj", myDateTimeOffset } },
+ new JsonObject { { "Name", "myDateTimeOffset" }, { "Index", 11 }, { "Obj", myDateTimeOffset } },
+ new JsonObject { { "Name", "myDateTimeOffset" }, { "Index", 11 }, { "Obj", myDateTimeOffset } },
+ new JsonObject { { "Name", "myDecimal" }, { "Index", 12 }, { "Obj", myDecimal } },
+ new JsonObject { { "Name", "myDecimal" }, { "Index", 12 }, { "Obj", myDecimal } },
+ new JsonObject { { "Name", "myDecimal" }, { "Index", 12 }, { "Obj", myDecimal } },
+ new JsonObject { { "Name", "myDecimal" }, { "Index", 12 }, { "Obj", myDecimal } },
+ new JsonObject { { "Name", "myDecimal" }, { "Index", 12 }, { "Obj", myDecimal } },
+ new JsonObject { { "Name", "myDouble" }, { "Index", 13 }, { "Obj", myDouble } },
+ new JsonObject { { "Name", "myInt16" }, { "Index", 14 }, { "Obj", myInt16 } },
+ new JsonObject { { "Name", "myInt16" }, { "Index", 14 }, { "Obj", myInt16 } },
+ new JsonObject { { "Name", "myInt32" }, { "Index", 15 }, { "Obj", myInt32 } },
+ new JsonObject { { "Name", "myInt32" }, { "Index", 15 }, { "Obj", myInt32 } },
+ new JsonObject { { "Name", "myInt32" }, { "Index", 15 }, { "Obj", myInt32 } },
+ new JsonObject { { "Name", "myInt64" }, { "Index", 16 }, { "Obj", myInt64 } },
+ new JsonObject { { "Name", "myInt64" }, { "Index", 16 }, { "Obj", myInt64 } },
+ new JsonObject { { "Name", "myInt64" }, { "Index", 16 }, { "Obj", myInt64 } },
+ new JsonObject { { "Name", "myInt64" }, { "Index", 16 }, { "Obj", myInt64 } },
+ new JsonObject { { "Name", "mySByte" }, { "Index", 17 }, { "Obj", mySByte } },
+ new JsonObject { { "Name", "mySByte" }, { "Index", 17 }, { "Obj", mySByte } },
+ new JsonObject { { "Name", "mySByte" }, { "Index", 17 }, { "Obj", mySByte } },
+ new JsonObject { { "Name", "mySByte" }, { "Index", 17 }, { "Obj", mySByte } },
+ new JsonObject { { "Name", "mySByte" }, { "Index", 17 }, { "Obj", mySByte } },
+ new JsonObject { { "Name", "mySingle" }, { "Index", 18 }, { "Obj", mySingle } },
+ new JsonObject { { "Name", "myString" }, { "Index", 19 }, { "Obj", myString } },
+ new JsonObject { { "Name", "myString" }, { "Index", 19 }, { "Obj", myString } },
+ new JsonObject { { "Name", "myUInt16" }, { "Index", 20 }, { "Obj", myUInt16 } },
+ new JsonObject { { "Name", "myUInt16" }, { "Index", 20 }, { "Obj", myUInt16 } },
+ new JsonObject { { "Name", "myUInt16" }, { "Index", 20 }, { "Obj", myUInt16 } },
+ new JsonObject { { "Name", "myUInt32" }, { "Index", 21 }, { "Obj", myUInt32 } },
+ new JsonObject { { "Name", "myUInt32" }, { "Index", 21 }, { "Obj", myUInt32 } },
+ new JsonObject { { "Name", "myUInt32" }, { "Index", 21 }, { "Obj", myUInt32 } },
+ new JsonObject { { "Name", "myUInt32" }, { "Index", 21 }, { "Obj", myUInt32 } },
+ new JsonObject { { "Name", "myUInt64" }, { "Index", 22 }, { "Obj", myUInt64 } }
+ };
+ return sourceJson;
+ }
+ }
+}
diff --git a/test/System.Json.Test.Integration/JsonValueTests.cs b/test/System.Json.Test.Integration/JsonValueTests.cs
new file mode 100644
index 00000000..89cc169c
--- /dev/null
+++ b/test/System.Json.Test.Integration/JsonValueTests.cs
@@ -0,0 +1,380 @@
+using System.Collections.Generic;
+using System.Globalization;
+using System.IO;
+using System.Text;
+using Xunit;
+using Xunit.Extensions;
+
+namespace System.Json
+{
+ /// <summary>
+ /// JsonValue unit tests
+ /// </summary>
+ public class JsonValueTests
+ {
+
+ public static IEnumerable<object[]> StreamLoadingTestData
+ {
+ get
+ {
+ bool[] useSeekableStreams = new bool[] { true, false };
+ Dictionary<string, Encoding> allEncodings = new Dictionary<string, Encoding>
+ {
+ { "UTF8, no BOM", new UTF8Encoding(false) },
+ { "Unicode, no BOM", new UnicodeEncoding(false, false) },
+ { "BigEndianUnicode, no BOM", new UnicodeEncoding(true, false) },
+ };
+
+ string[] jsonStrings = { "[1, 2, null, false, {\"foo\": 1, \"bar\":true, \"baz\":null}, 1.23e+56]", "4" };
+
+ foreach (string jsonString in jsonStrings)
+ {
+ foreach (bool useSeekableStream in useSeekableStreams)
+ {
+ foreach (var kvp in allEncodings)
+ {
+ yield return new object[] { jsonString, useSeekableStream, kvp.Key, kvp.Value };
+ }
+ }
+ }
+ }
+ }
+
+ /// <summary>
+ /// Tests for <see cref="JsonValue.Load(Stream)"/>.
+ /// </summary>
+ [Theory]
+ [PropertyData("StreamLoadingTestData")]
+ public void StreamLoading(string jsonString, bool useSeekableStream, string encodingName, Encoding encoding)
+ {
+ using (MemoryStream ms = new MemoryStream())
+ {
+ StreamWriter sw = new StreamWriter(ms, encoding);
+ sw.Write(jsonString);
+ sw.Flush();
+ Log.Info("[{0}] {1}: size of the json stream: {2}", useSeekableStream ? "seekable" : "non-seekable", encodingName, ms.Position);
+ ms.Position = 0;
+ JsonValue parsed = JsonValue.Parse(jsonString);
+ JsonValue loaded = useSeekableStream ? JsonValue.Load(ms) : JsonValue.Load(new NonSeekableStream(ms));
+ using (StringReader sr = new StringReader(jsonString))
+ {
+ JsonValue loadedFromTextReader = JsonValue.Load(sr);
+ Assert.Equal(parsed.ToString(), loaded.ToString());
+ Assert.Equal(parsed.ToString(), loadedFromTextReader.ToString());
+ }
+ }
+ }
+
+ [Fact]
+ public void ZeroedStreamLoadingThrowsFormatException()
+ {
+ ExpectException<FormatException>(delegate
+ {
+ using (MemoryStream ms = new MemoryStream(new byte[10]))
+ {
+ JsonValue.Load(ms);
+ }
+ });
+ }
+
+ /// <summary>
+ /// Tests for handling with escaped characters.
+ /// </summary>
+ [Fact]
+ public void EscapedCharacters()
+ {
+ string str = null;
+ JsonValue value = null;
+ str = (string)value;
+ Assert.Null(str);
+ value = "abc\b\t\r\u1234\uDC80\uDB11def\\\0ghi";
+ str = (string)value;
+ Assert.Equal("\"abc\\u0008\\u0009\\u000d\u1234\\udc80\\udb11def\\\\\\u0000ghi\"", value.ToString());
+ value = '\u0000';
+ str = (string)value;
+ Assert.Equal("\u0000", str);
+ }
+
+ /// <summary>
+ /// Tests for JSON objects with the special '__type' object member.
+ /// </summary>
+ [Fact]
+ public void TypeHintAttributeTests()
+ {
+ string json = "{\"__type\":\"TypeHint\",\"a\":123}";
+ JsonValue jv = JsonValue.Parse(json);
+ string newJson = jv.ToString();
+ Assert.Equal(json, newJson);
+
+ json = "{\"b\":567,\"__type\":\"TypeHint\",\"a\":123}";
+ jv = JsonValue.Parse(json);
+ newJson = jv.ToString();
+ Assert.Equal(json, newJson);
+
+ json = "[12,{\"__type\":\"TypeHint\",\"a\":123,\"obj\":{\"__type\":\"hint2\",\"b\":333}},null]";
+ jv = JsonValue.Parse(json);
+ newJson = jv.ToString();
+ Assert.Equal(json, newJson);
+ }
+
+ /// <summary>
+ /// Tests for reading JSON with different member names.
+ /// </summary>
+ [Fact]
+ public void ObjectNameTests()
+ {
+ string[] objectNames = new string[]
+ {
+ "simple",
+ "with spaces",
+ "with<>brackets",
+ "",
+ };
+
+ foreach (string objectName in objectNames)
+ {
+ string json = String.Format(CultureInfo.InvariantCulture, "{{\"{0}\":123}}", objectName);
+ JsonValue jv = JsonValue.Parse(json);
+ Assert.Equal(123, jv[objectName].ReadAs<int>());
+ string newJson = jv.ToString();
+ Assert.Equal(json, newJson);
+
+ JsonObject jo = new JsonObject { { objectName, 123 } };
+ Assert.Equal(123, jo[objectName].ReadAs<int>());
+ newJson = jo.ToString();
+ Assert.Equal(json, newJson);
+ }
+
+ ExpectException<FormatException>(() => JsonValue.Parse("{\"nonXmlChar\u0000\":123}"));
+ }
+
+ /// <summary>
+ /// Miscellaneous tests for parsing JSON.
+ /// </summary>
+ [Fact]
+ public void ParseMiscellaneousTest()
+ {
+ string[] jsonValues =
+ {
+ "[]",
+ "[1]",
+ "[1,2,3,[4.1,4.2],5]",
+ "{}",
+ "{\"a\":1}",
+ "{\"a\":1,\"b\":2,\"c\":3,\"d\":4}",
+ "{\"a\":1,\"b\":[2,3],\"c\":3}",
+ "{\"a\":1,\"b\":2,\"c\":[1,2,3,[4.1,4.2],5],\"d\":4}",
+ "{\"a\":1,\"b\":[2.1,2.2],\"c\":3,\"d\":4,\"e\":[4.1,4.2,4.3,[4.41,4.42],4.4],\"f\":5}",
+ "{\"a\":1,\"b\":[2.1,2.2,[[[{\"b1\":2.21}]]],2.3],\"c\":{\"d\":4,\"e\":[4.1,4.2,4.3,[4.41,4.42],4.4],\"f\":5}}"
+ };
+
+ foreach (string json in jsonValues)
+ {
+ JsonValue jv = JsonValue.Parse(json);
+ Log.Info("{0}", jv.ToString());
+
+ string jvstr = jv.ToString();
+ Assert.Equal(json, jvstr);
+ }
+ }
+
+ /// <summary>
+ /// Negative tests for parsing "unbalanced" JSON (i.e., JSON documents which aren't properly closed).
+ /// </summary>
+ [Fact]
+ public void ParseUnbalancedJsonTest()
+ {
+ string[] jsonValues =
+ {
+ "[",
+ "[1,{]",
+ "[1,2,3,{{}}",
+ "}",
+ "{\"a\":}",
+ "{\"a\":1,\"b\":[,\"c\":3,\"d\":4}",
+ "{\"a\":1,\"b\":[2,\"c\":3}",
+ "{\"a\":1,\"b\":[2.1,2.2,\"c\":3,\"d\":4,\"e\":[4.1,4.2,4.3,[4.41,4.42],4.4],\"f\":5}",
+ "{\"a\":1,\"b\":[2.1,2.2,[[[[{\"b1\":2.21}]]],\"c\":{\"d\":4,\"e\":[4.1,4.2,4.3,[4.41,4.42],4.4],\"f\":5}}"
+ };
+
+ foreach (string json in jsonValues)
+ {
+ Log.Info("Testing unbalanced JSON: {0}", json);
+ ExpectException<FormatException>(() => JsonValue.Parse(json));
+ }
+ }
+
+ /// <summary>
+ /// Test for parsing a deeply nested JSON object.
+ /// </summary>
+ [Fact]
+ public void ParseDeeplyNestedJsonObjectString()
+ {
+ StringBuilder builderExpected = new StringBuilder();
+ builderExpected.Append('{');
+ int depth = 10000;
+ for (int i = 0; i < depth; i++)
+ {
+ string key = i.ToString(CultureInfo.InvariantCulture);
+ builderExpected.AppendFormat("\"{0}\":{{", key);
+ }
+
+ for (int i = 0; i < depth + 1; i++)
+ {
+ builderExpected.Append('}');
+ }
+
+ string json = builderExpected.ToString();
+ JsonValue jsonValue = JsonValue.Parse(json);
+ string jvstr = jsonValue.ToString();
+
+ Assert.Equal(json, jvstr);
+ }
+
+ /// <summary>
+ /// Test for parsing a deeply nested JSON array.
+ /// </summary>
+ [Fact]
+ public void ParseDeeplyNestedJsonArrayString()
+ {
+ StringBuilder builderExpected = new StringBuilder();
+ builderExpected.Append('[');
+ int depth = 10000;
+ for (int i = 0; i < depth; i++)
+ {
+ builderExpected.Append('[');
+ }
+
+ for (int i = 0; i < depth + 1; i++)
+ {
+ builderExpected.Append(']');
+ }
+
+ string json = builderExpected.ToString();
+ JsonValue jsonValue = JsonValue.Parse(json);
+ string jvstr = jsonValue.ToString();
+
+ Assert.Equal(json, jvstr);
+ }
+
+ /// <summary>
+ /// Test for parsing a deeply nested JSON graph, containing both objects and arrays.
+ /// </summary>
+ [Fact]
+ public void ParseDeeplyNestedJsonString()
+ {
+ StringBuilder builderExpected = new StringBuilder();
+ builderExpected.Append('{');
+ int depth = 10000;
+ for (int i = 0; i < depth; i++)
+ {
+ string key = i.ToString(CultureInfo.InvariantCulture);
+ builderExpected.AppendFormat("\"{0}\":[{{", key);
+ }
+
+ for (int i = 0; i < depth; i++)
+ {
+ builderExpected.Append("}]");
+ }
+
+ builderExpected.Append('}');
+
+ string json = builderExpected.ToString();
+ JsonValue jsonValue = JsonValue.Parse(json);
+ string jvstr = jsonValue.ToString();
+
+ Assert.Equal(json, jvstr);
+ }
+
+ internal static void ExpectException<T>(Action action) where T : Exception
+ {
+ ExpectException<T>(action, null);
+ }
+
+ internal static void ExpectException<T>(Action action, string partOfExceptionString) where T : Exception
+ {
+ try
+ {
+ action();
+ Assert.False(true, "This should have thrown");
+ }
+ catch (T e)
+ {
+ if (partOfExceptionString != null)
+ {
+ Assert.True(e.Message.Contains(partOfExceptionString));
+ }
+ }
+ }
+
+ internal class NonSeekableStream : Stream
+ {
+ Stream innerStream;
+
+ public NonSeekableStream(Stream innerStream)
+ {
+ this.innerStream = innerStream;
+ }
+
+ public override bool CanRead
+ {
+ get { return true; }
+ }
+
+ public override bool CanSeek
+ {
+ get { return false; }
+ }
+
+ public override bool CanWrite
+ {
+ get { return false; }
+ }
+
+ public override long Position
+ {
+ get
+ {
+ throw new NotSupportedException();
+ }
+
+ set
+ {
+ throw new NotSupportedException();
+ }
+ }
+
+ public override long Length
+ {
+ get
+ {
+ throw new NotSupportedException();
+ }
+ }
+
+ public override void Flush()
+ {
+ }
+
+ public override int Read(byte[] buffer, int offset, int count)
+ {
+ return this.innerStream.Read(buffer, offset, count);
+ }
+
+ public override long Seek(long offset, SeekOrigin origin)
+ {
+ throw new NotSupportedException();
+ }
+
+ public override void SetLength(long value)
+ {
+ throw new NotSupportedException();
+ }
+
+ public override void Write(byte[] buffer, int offset, int count)
+ {
+ throw new NotSupportedException();
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/test/System.Json.Test.Integration/JsonValueUsageTest.cs b/test/System.Json.Test.Integration/JsonValueUsageTest.cs
new file mode 100644
index 00000000..3f2a5dd2
--- /dev/null
+++ b/test/System.Json.Test.Integration/JsonValueUsageTest.cs
@@ -0,0 +1,433 @@
+using System.Collections.Generic;
+using System.Linq;
+using Xunit;
+
+namespace System.Json
+{
+ /// <summary>
+ /// Test class for some scenario usages for <see cref="JsonValue"/> types.
+ /// </summary>
+ public class JsonValueUsageTest
+ {
+ /// <summary>
+ /// Test for consuming <see cref="JsonValue"/> objects in a Linq query.
+ /// </summary>
+ [Fact]
+ public void JLinqSimpleCreationQueryTest()
+ {
+ int seed = 1;
+ Random rndGen = new Random(seed);
+
+ JsonArray sourceJson = new JsonArray
+ {
+ new JsonObject { { "Name", "Alex" }, { "Age", 18 }, { "Birthday", PrimitiveCreator.CreateInstanceOfDateTime(rndGen) } },
+ new JsonObject { { "Name", "Joe" }, { "Age", 19 }, { "Birthday", DateTime.MinValue } },
+ new JsonObject { { "Name", "Chris" }, { "Age", 20 }, { "Birthday", DateTime.Now } },
+ new JsonObject { { "Name", "Jeff" }, { "Age", 21 }, { "Birthday", DateTime.MaxValue } },
+ new JsonObject { { "Name", "Carlos" }, { "Age", 22 }, { "Birthday", PrimitiveCreator.CreateInstanceOfDateTime(rndGen) } },
+ new JsonObject { { "Name", "Mohammad" }, { "Age", 23 }, { "Birthday", PrimitiveCreator.CreateInstanceOfDateTime(rndGen) } },
+ new JsonObject { { "Name", "Sara" }, { "Age", 24 }, { "Birthday", new DateTime(1998, 3, 20) } },
+ new JsonObject { { "Name", "Tomasz" }, { "Age", 25 }, { "Birthday", PrimitiveCreator.CreateInstanceOfDateTime(rndGen) } },
+ new JsonObject { { "Name", "Suwat" }, { "Age", 26 }, { "Birthday", new DateTime(1500, 12, 20) } },
+ new JsonObject { { "Name", "Eugene" }, { "Age", 27 }, { "Birthday", PrimitiveCreator.CreateInstanceOfDateTime(rndGen) } }
+ };
+
+ var adults = from JsonValue adult in sourceJson
+ where (int)adult["Age"] > 21
+ select adult;
+ Log.Info("Team contains: ");
+ int count = 0;
+ foreach (JsonValue adult in adults)
+ {
+ count++;
+ Log.Info((string)adult["Name"]);
+ }
+
+ Assert.Equal(count, 6);
+ }
+
+ /// <summary>
+ /// Test for consuming <see cref="JsonValue"/> arrays in a Linq query.
+ /// </summary>
+ [Fact]
+ public void JLinqSimpleQueryTest()
+ {
+ JsonArray sourceJson = this.CreateArrayOfPeople();
+
+ var adults = from JsonValue adult in sourceJson
+ where (int)adult["Age"] > 21
+ select adult;
+ Log.Info("Team contains: ");
+ int count = 0;
+ foreach (JsonValue adult in adults)
+ {
+ count++;
+ Log.Info((string)adult["Name"]);
+ }
+
+ Assert.Equal(count, 6);
+ }
+
+ /// <summary>
+ /// Test for consuming deep <see cref="JsonValue"/> objects in a Linq query.
+ /// </summary>
+ [Fact]
+ public void JLinqDeepQueryTest()
+ {
+ int seed = 1;
+
+ JsonArray mixedOrderJsonObj;
+ JsonArray myJsonObj = SpecialJsonValueHelper.CreateDeepLevelJsonValuePair(seed, out mixedOrderJsonObj);
+
+ if (myJsonObj != null && mixedOrderJsonObj != null)
+ {
+ bool retValue = true;
+
+ var dict = new Dictionary<string, int>
+ {
+ { "myArray", 1 },
+ { "myArrayLevel2", 2 },
+ { "myArrayLevel3", 3 },
+ { "myArrayLevel4", 4 },
+ { "myArrayLevel5", 5 },
+ { "myArrayLevel6", 6 },
+ { "myArrayLevel7", 7 },
+ { "myBool", 8 },
+ { "myByte", 9 },
+ { "myDatetime", 10 },
+ { "myDateTimeOffset", 11 },
+ { "myDecimal", 12 },
+ { "myDouble", 13 },
+ { "myInt16", 14 },
+ { "myInt32", 15 },
+ { "myInt64", 16 },
+ { "mySByte", 17 },
+ { "mySingle", 18 },
+ { "myString", 19 },
+ { "myUInt16", 20 },
+ { "myUInt32", 21 },
+ { "myUInt64", 22 }
+ };
+
+ foreach (string name in dict.Keys)
+ {
+ if (!this.InternalVerificationViaLinqQuery(myJsonObj, name, dict[name]))
+ {
+ retValue = false;
+ }
+
+ if (!this.InternalVerificationViaLinqQuery(mixedOrderJsonObj, name, dict[name]))
+ {
+ retValue = false;
+ }
+
+ if (!this.CrossJsonValueVerificationOnNameViaLinqQuery(myJsonObj, mixedOrderJsonObj, name))
+ {
+ retValue = false;
+ }
+
+ if (!this.CrossJsonValueVerificationOnIndexViaLinqQuery(myJsonObj, mixedOrderJsonObj, dict[name]))
+ {
+ retValue = false;
+ }
+ }
+
+ Assert.True(retValue, "The JsonValue did not verify as expected!");
+ }
+ else
+ {
+ Assert.True(false, "Failed to create the pair of JsonValues!");
+ }
+ }
+
+ /// <summary>
+ /// Test for consuming <see cref="JsonValue"/> objects in a Linq query using the dynamic notation.
+ /// </summary>
+ [Fact]
+ public void LinqToDynamicJsonArrayTest()
+ {
+ JsonValue people = this.CreateArrayOfPeople();
+
+ var match = from person in people select person;
+ Assert.True(match.Count() == people.Count, "IEnumerable returned different number of elements that JsonArray contains");
+
+ int sum = 0;
+ foreach (KeyValuePair<string, JsonValue> kv in match)
+ {
+ sum += Int32.Parse(kv.Key);
+ }
+
+ Assert.True(sum == (people.Count * (people.Count - 1) / 2), "Not all elements of the array were enumerated exactly once");
+
+ match = from person in people
+ where person.Value.AsDynamic().Name.ReadAs<string>().StartsWith("S")
+ && person.Value.AsDynamic().Age.ReadAs<int>() > 20
+ select person;
+ Assert.True(match.Count() == 2, "Number of matches was expected to be 2 but was " + match.Count());
+ }
+
+ /// <summary>
+ /// Test for consuming <see cref="JsonValue"/> objects in a Linq query.
+ /// </summary>
+ [Fact]
+ public void LinqToJsonObjectTest()
+ {
+ JsonValue person = this.CreateArrayOfPeople()[0];
+ var match = from nameValue in person select nameValue;
+ Assert.True(match.Count() == 3, "IEnumerable of JsonObject returned a different number of elements than there are name value pairs in the JsonObject" + match.Count());
+
+ List<string> missingNames = new List<string>(new string[] { "Name", "Age", "Birthday" });
+ foreach (KeyValuePair<string, JsonValue> kv in match)
+ {
+ Assert.Equal(person[kv.Key], kv.Value);
+ missingNames.Remove(kv.Key);
+ }
+
+ Assert.True(missingNames.Count == 0, "Not all JsonObject properties were present in the enumeration");
+ }
+
+ /// <summary>
+ /// Test for consuming <see cref="JsonValue"/> objects in a Linq query.
+ /// </summary>
+ [Fact]
+ public void LinqToJsonObjectAsAssociativeArrayTest()
+ {
+ JsonValue gameScores = new JsonObject(new Dictionary<string, JsonValue>()
+ {
+ { "tomek", 12 },
+ { "suwat", 27 },
+ { "carlos", 127 },
+ { "miguel", 57 },
+ { "henrik", 2 },
+ { "joe", 15 }
+ });
+
+ var match = from score in gameScores
+ where score.Key.Contains("o") && score.Value.ReadAs<int>() > 100
+ select score;
+ Assert.True(match.Count() == 1, "Incorrect number of matching game scores");
+ }
+
+ /// <summary>
+ /// Test for consuming <see cref="JsonPrimitive"/> objects in a Linq query.
+ /// </summary>
+ [Fact]
+ public void LinqToJsonPrimitiveTest()
+ {
+ JsonValue primitive = 12;
+
+ var match = from m in primitive select m;
+ KeyValuePair<string, JsonValue>[] kv = match.ToArray();
+ Assert.True(kv.Length == 0);
+ }
+
+ /// <summary>
+ /// Test for consuming <see cref="JsonValue"/> objects with <see cref="JsonType">JsonType.Default</see> in a Linq query.
+ /// </summary>
+ [Fact]
+ public void LinqToJsonUndefinedTest()
+ {
+ JsonValue primitive = 12;
+
+ var match = from m in primitive.ValueOrDefault("idontexist")
+ select m;
+ Assert.True(match.Count() == 0);
+ }
+
+ /// <summary>
+ /// Test for consuming calling <see cref="JsonValue.ReadAs{T}(T)"/> in a Linq query.
+ /// </summary>
+ [Fact]
+ public void LinqToDynamicJsonUndefinedWithFallbackTest()
+ {
+ JsonValue people = this.CreateArrayOfPeople();
+
+ var match = from person in people
+ where person.Value.AsDynamic().IDontExist.IAlsoDontExist.ReadAs<int>(5) > 2
+ select person;
+ Assert.True(match.Count() == people.Count, "Number of matches was expected to be " + people.Count + " but was " + match.Count());
+
+ match = from person in people
+ where person.Value.AsDynamic().Age.ReadAs<int>(1) < 21
+ select person;
+ Assert.True(match.Count() == 3);
+ }
+
+ private JsonArray CreateArrayOfPeople()
+ {
+ int seed = 1;
+ Random rndGen = new Random(seed);
+ return new JsonArray(new List<JsonValue>()
+ {
+ new JsonObject(new Dictionary<string, JsonValue>()
+ {
+ { "Name", "Alex" },
+ { "Age", 18 },
+ { "Birthday", PrimitiveCreator.CreateInstanceOfDateTime(rndGen) }
+ }),
+ new JsonObject(new Dictionary<string, JsonValue>()
+ {
+ { "Name", "Joe" },
+ { "Age", 19 },
+ { "Birthday", DateTime.MinValue }
+ }),
+ new JsonObject(new Dictionary<string, JsonValue>()
+ {
+ { "Name", "Chris" },
+ { "Age", 20 },
+ { "Birthday", DateTime.Now }
+ }),
+ new JsonObject(new Dictionary<string, JsonValue>()
+ {
+ { "Name", "Jeff" },
+ { "Age", 21 },
+ { "Birthday", DateTime.MaxValue }
+ }),
+ new JsonObject(new Dictionary<string, JsonValue>()
+ {
+ { "Name", "Carlos" },
+ { "Age", 22 },
+ { "Birthday", PrimitiveCreator.CreateInstanceOfDateTime(rndGen) }
+ }),
+ new JsonObject(new Dictionary<string, JsonValue>()
+ {
+ { "Name", "Mohammad" },
+ { "Age", 23 },
+ { "Birthday", PrimitiveCreator.CreateInstanceOfDateTime(rndGen) }
+ }),
+ new JsonObject(new Dictionary<string, JsonValue>()
+ {
+ { "Name", "Sara" },
+ { "Age", 24 },
+ { "Birthday", new DateTime(1998, 3, 20) }
+ }),
+ new JsonObject(new Dictionary<string, JsonValue>()
+ {
+ { "Name", "Tomasz" },
+ { "Age", 25 },
+ { "Birthday", PrimitiveCreator.CreateInstanceOfDateTime(rndGen) }
+ }),
+ new JsonObject(new Dictionary<string, JsonValue>()
+ {
+ { "Name", "Suwat" },
+ { "Age", 26 },
+ { "Birthday", new DateTime(1500, 12, 20) }
+ }),
+ new JsonObject(new Dictionary<string, JsonValue>()
+ {
+ { "Name", "Eugene" },
+ { "Age", 27 },
+ { "Birthday", PrimitiveCreator.CreateInstanceOfDateTime(rndGen) }
+ })
+ });
+ }
+
+ private bool InternalVerificationViaLinqQuery(JsonArray sourceJson, string name, int index)
+ {
+ var itemsByName = from JsonValue itemByName in sourceJson
+ where (itemByName != null && (string)itemByName["Name"] == name)
+ select itemByName;
+ int countByName = 0;
+ foreach (JsonValue a in itemsByName)
+ {
+ countByName++;
+ }
+
+ Log.Info("Collection contains: " + countByName + " item By Name " + name);
+
+ var itemsByIndex = from JsonValue itemByIndex in sourceJson
+ where (itemByIndex != null && (int)itemByIndex["Index"] == index)
+ select itemByIndex;
+ int countByIndex = 0;
+ foreach (JsonValue a in itemsByIndex)
+ {
+ countByIndex++;
+ }
+
+ Log.Info("Collection contains: " + countByIndex + " item By Index " + index);
+
+ if (countByIndex != countByName)
+ {
+ Log.Info("Count by Name = " + countByName + "; Count by Index = " + countByIndex);
+ Log.Info("The number of items matching the provided Name does NOT equal to that matching the provided Index, The two JsonValues are not equal!");
+ return false;
+ }
+ else
+ {
+ return true;
+ }
+ }
+
+ private bool CrossJsonValueVerificationOnNameViaLinqQuery(JsonArray sourceJson, JsonArray newJson, string name)
+ {
+ var itemsByName = from JsonValue itemByName in sourceJson
+ where (itemByName != null && (string)itemByName["Name"] == name)
+ select itemByName;
+ int countByName = 0;
+ foreach (JsonValue a in itemsByName)
+ {
+ countByName++;
+ }
+
+ Log.Info("Original Collection contains: " + countByName + " item By Name " + name);
+
+ var newItemsByName = from JsonValue newItemByName in newJson
+ where (newItemByName != null && (string)newItemByName["Name"] == name)
+ select newItemByName;
+ int newcountByName = 0;
+ foreach (JsonValue a in newItemsByName)
+ {
+ newcountByName++;
+ }
+
+ Log.Info("New Collection contains: " + newcountByName + " item By Name " + name);
+
+ if (countByName != newcountByName)
+ {
+ Log.Info("Count by Original JsonValue = " + countByName + "; Count by New JsonValue = " + newcountByName);
+ Log.Info("The number of items matching the provided Name does NOT equal between these two JsonValues!");
+ return false;
+ }
+ else
+ {
+ return true;
+ }
+ }
+
+ private bool CrossJsonValueVerificationOnIndexViaLinqQuery(JsonArray sourceJson, JsonArray newJson, int index)
+ {
+ var itemsByIndex = from JsonValue itemByIndex in sourceJson
+ where (itemByIndex != null && (int)itemByIndex["Index"] == index)
+ select itemByIndex;
+ int countByIndex = 0;
+ foreach (JsonValue a in itemsByIndex)
+ {
+ countByIndex++;
+ }
+
+ Log.Info("Original Collection contains: " + countByIndex + " item By Index " + index);
+
+ var newItemsByIndex = from JsonValue newItemByIndex in newJson
+ where (newItemByIndex != null && (int)newItemByIndex["Index"] == index)
+ select newItemByIndex;
+ int newcountByIndex = 0;
+ foreach (JsonValue a in newItemsByIndex)
+ {
+ newcountByIndex++;
+ }
+
+ Log.Info("New Collection contains: " + newcountByIndex + " item By Index " + index);
+
+ if (countByIndex != newcountByIndex)
+ {
+ Log.Info("Count by Original JsonValue = " + countByIndex + "; Count by New JsonValue = " + newcountByIndex);
+ Log.Info("The number of items matching the provided Index does NOT equal between these two JsonValues!");
+ return false;
+ }
+ else
+ {
+ return true;
+ }
+ }
+ }
+}
diff --git a/test/System.Json.Test.Integration/Properties/AssemblyInfo.cs b/test/System.Json.Test.Integration/Properties/AssemblyInfo.cs
new file mode 100644
index 00000000..5a9c850b
--- /dev/null
+++ b/test/System.Json.Test.Integration/Properties/AssemblyInfo.cs
@@ -0,0 +1,34 @@
+using System;
+using System.Reflection;
+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("System.Json.Test.Integration")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("Microsoft")]
+[assembly: AssemblyProduct("System.Json.Test.Integration")]
+[assembly: AssemblyCopyright("Copyright © Microsoft 2011")]
+[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)]
+[assembly: CLSCompliant(false)]
+
+// The following GUID is for the ID of the typelib if this project is exposed to COM
+[assembly: Guid("750a87c6-d9c9-476b-89ab-b3b3bce96bec")]
+
+// Version information for an assembly consists of the following four values:
+//
+// Major Version
+// Minor Version
+// Build Number
+// Revision
+//
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
diff --git a/test/System.Json.Test.Integration/System.Json.Test.Integration.csproj b/test/System.Json.Test.Integration/System.Json.Test.Integration.csproj
new file mode 100644
index 00000000..88d12bd5
--- /dev/null
+++ b/test/System.Json.Test.Integration/System.Json.Test.Integration.csproj
@@ -0,0 +1,88 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory),Runtime.sln))\tools\WebStack.settings.targets" />
+ <PropertyGroup>
+ <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+ <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+ <ProductVersion>9.0.30729</ProductVersion>
+ <SchemaVersion>2.0</SchemaVersion>
+ <ProjectGuid>{A7B1264E-BCE5-42A8-8B5E-001A5360B128}</ProjectGuid>
+ <OutputType>Library</OutputType>
+ <AppDesignerFolder>Properties</AppDesignerFolder>
+ <RootNamespace>System.Json.Test.Integration</RootNamespace>
+ <AssemblyName>System.Json.Test.Integration</AssemblyName>
+ <FileAlignment>512</FileAlignment>
+ <ProjectTypeGuids>{3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
+ <ErrorReport>prompt</ErrorReport>
+ <WarningLevel>4</WarningLevel>
+ <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
+ <DebugSymbols>true</DebugSymbols>
+ <DebugType>full</DebugType>
+ <Optimize>false</Optimize>
+ <OutputPath>$(WebStackRootPath)\bin\Debug\Test\</OutputPath>
+ <DefineConstants>TRACE;DEBUG</DefineConstants>
+ <CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
+ <DebugType>pdbonly</DebugType>
+ <Optimize>true</Optimize>
+ <OutputPath>$(WebStackRootPath)\bin\Release\Test\</OutputPath>
+ <DefineConstants>TRACE</DefineConstants>
+ <CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)' == 'CodeCoverage' ">
+ <DebugSymbols>true</DebugSymbols>
+ <DebugType>full</DebugType>
+ <Optimize>false</Optimize>
+ <OutputPath>$(WebStackRootPath)\bin\CodeCoverage\Test\</OutputPath>
+ <DefineConstants>TRACE;DEBUG</DefineConstants>
+ <CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
+ </PropertyGroup>
+ <ItemGroup>
+ <Reference Include="Microsoft.CSharp" />
+ <Reference Include="System" />
+ <Reference Include="System.Runtime.Serialization" />
+ <Reference Include="System.XML" />
+ <Reference Include="xunit, Version=1.9.0.1566, Culture=neutral, PublicKeyToken=8d05b1bb7a6fdb6c, processorArchitecture=MSIL">
+ <HintPath>..\..\packages\xunit.1.9.0.1566\lib\xunit.dll</HintPath>
+ </Reference>
+ <Reference Include="xunit.extensions, Version=1.9.0.1566, Culture=neutral, PublicKeyToken=8d05b1bb7a6fdb6c, processorArchitecture=MSIL">
+ <HintPath>..\..\packages\xunit.extensions.1.9.0.1566\lib\xunit.extensions.dll</HintPath>
+ </Reference>
+ </ItemGroup>
+ <ItemGroup>
+ <Compile Include="Properties\AssemblyInfo.cs" />
+ <Compile Include="Common\InstanceCreator.cs" />
+ <Compile Include="Common\JsonValueCreatorSurrogate.cs" />
+ <Compile Include="Common\Log.cs" />
+ <Compile Include="Common\TypeLibrary.cs" />
+ <Compile Include="Common\Util.cs" />
+ <Compile Include="JObjectFunctionalTest.cs" />
+ <Compile Include="JsonPrimitiveTests.cs" />
+ <Compile Include="JsonStringRoundTripTests.cs" />
+ <Compile Include="JsonValueAndComplexTypesTests.cs" />
+ <Compile Include="JsonValueDynamicTests.cs" />
+ <Compile Include="JsonValueEventsTests.cs" />
+ <Compile Include="JsonValueLinqExtensionsIntegrationTest.cs" />
+ <Compile Include="JsonValuePartialTrustTests.cs" />
+ <Compile Include="JsonValueTestHelper.cs" />
+ <Compile Include="JsonValueTests.cs" />
+ <Compile Include="JsonValueUsageTest.cs" />
+ </ItemGroup>
+ <ItemGroup>
+ <ProjectReference Include="..\..\src\System.Json\System.Json.csproj">
+ <Project>{F0441BE9-BDC0-4629-BE5A-8765FFAA2481}</Project>
+ <Name>System.Json</Name>
+ </ProjectReference>
+ <ProjectReference Include="..\Microsoft.TestCommon\Microsoft.TestCommon.csproj">
+ <Project>{FCCC4CB7-BAF7-4A57-9F89-E5766FE536C0}</Project>
+ <Name>Microsoft.TestCommon</Name>
+ </ProjectReference>
+ </ItemGroup>
+ <ItemGroup>
+ <None Include="packages.config" />
+ </ItemGroup>
+ <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
+</Project> \ No newline at end of file
diff --git a/test/System.Json.Test.Integration/packages.config b/test/System.Json.Test.Integration/packages.config
new file mode 100644
index 00000000..d82739c0
--- /dev/null
+++ b/test/System.Json.Test.Integration/packages.config
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<packages>
+ <package id="xunit" version="1.9.0.1566" />
+ <package id="xunit.extensions" version="1.9.0.1566" />
+</packages> \ No newline at end of file
diff --git a/test/System.Json.Test.Unit/Common/AnyInstance.cs b/test/System.Json.Test.Unit/Common/AnyInstance.cs
new file mode 100644
index 00000000..899614fb
--- /dev/null
+++ b/test/System.Json.Test.Unit/Common/AnyInstance.cs
@@ -0,0 +1,220 @@
+using System.Collections.Generic;
+using System.Dynamic;
+using System.Linq.Expressions;
+using System.Reflection;
+
+namespace System.Json
+{
+ public static class AnyInstance
+ {
+ public const bool AnyBool = true;
+ public const string AnyString = "hello";
+ public const string AnyString2 = "world";
+ public const char AnyChar = 'c';
+ public const int AnyInt = 123456789;
+ [CLSCompliant(false)]
+ public const uint AnyUInt = 3123456789;
+ public const long AnyLong = 123456789012345L;
+ [CLSCompliant(false)]
+ public const ulong AnyULong = UInt64.MaxValue;
+ public const short AnyShort = -12345;
+ [CLSCompliant(false)]
+ public const ushort AnyUShort = 40000;
+ public const byte AnyByte = 0xDC;
+ [CLSCompliant(false)]
+ public const sbyte AnySByte = -34;
+ public const double AnyDouble = 123.45;
+ public const float AnyFloat = 23.4f;
+ public const decimal AnyDecimal = 1234.5678m;
+ public static readonly Guid AnyGuid = new Guid(0x11223344, 0x5566, 0x7788, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF, 0x00);
+ public static readonly DateTime AnyDateTime = new DateTime(2010, 02, 15, 22, 45, 20, DateTimeKind.Utc);
+ public static readonly DateTimeOffset AnyDateTimeOffset = new DateTimeOffset(2010, 2, 5, 15, 45, 20, TimeSpan.FromHours(-3));
+ public static readonly Uri AnyUri = new Uri("http://tempuri.org/");
+
+ public static readonly JsonArray AnyJsonArray;
+ public static readonly JsonObject AnyJsonObject;
+
+ public static readonly JsonPrimitive AnyJsonPrimitive = new JsonPrimitive("hello");
+
+ public static readonly JsonValue AnyJsonValue1 = AnyJsonPrimitive;
+ public static readonly JsonValue AnyJsonValue2;
+ public static readonly JsonValue AnyJsonValue3 = null;
+
+ public static readonly JsonValue DefaultJsonValue = GetDefaultJsonValue();
+
+ public static readonly Person AnyPerson = Person.CreateSample();
+ public static readonly Address AnyAddress = Address.CreateSample();
+
+ public static readonly dynamic AnyDynamic = TestDynamicObject.CreatePersonAsDynamic(AnyPerson);
+
+ public static JsonValue[] AnyJsonValueArray
+ {
+ get
+ {
+ return new JsonValue[]
+ {
+ AnyInstance.AnyJsonArray,
+ AnyInstance.AnyJsonObject,
+ AnyInstance.AnyJsonPrimitive,
+ AnyInstance.DefaultJsonValue
+ };
+ }
+ }
+
+ static AnyInstance()
+ {
+ AnyJsonArray = new JsonArray { 1, 2, 3 };
+ AnyJsonObject = new JsonObject { { "one", 1 }, { "two", 2 } };
+ AnyJsonArray.Changing += new EventHandler<JsonValueChangeEventArgs>(PreventChanging);
+ AnyJsonObject.Changing += new EventHandler<JsonValueChangeEventArgs>(PreventChanging);
+ AnyJsonValue2 = AnyJsonArray;
+ }
+
+ private static void PreventChanging(object sender, JsonValueChangeEventArgs e)
+ {
+ throw new InvalidOperationException("AnyInstance.AnyJsonArray or AnyJsonObject cannot be modified; please clone the instance if the test needs to change it.");
+ }
+
+ private static JsonValue GetDefaultJsonValue()
+ {
+ PropertyInfo propInfo = typeof(JsonValue).GetProperty("DefaultInstance", BindingFlags.Static | BindingFlags.NonPublic);
+ return propInfo.GetValue(null, null) as JsonValue;
+ }
+ }
+
+ public class Person
+ {
+ public string Name { get; set; }
+
+ public int Age { get; set; }
+
+ public Address Address { get; set; }
+
+ public List<Person> Friends { get; set; }
+
+ public static Person CreateSample()
+ {
+ Person anyObject = new Person
+ {
+ Name = AnyInstance.AnyString,
+ Age = AnyInstance.AnyInt,
+ Address = Address.CreateSample(),
+ Friends = new List<Person> { new Person { Name = "Bill Gates", Age = 23, Address = Address.CreateSample() }, new Person { Name = "Steve Ballmer", Age = 19, Address = Address.CreateSample() } }
+ };
+
+ return anyObject;
+ }
+
+ public string FriendsToString()
+ {
+ string s = "";
+
+ if (this.Friends != null)
+ {
+ foreach (Person p in this.Friends)
+ {
+ s += p + ",";
+ }
+ }
+
+ return s;
+ }
+
+ public override string ToString()
+ {
+ return String.Format("{0}, {1}, [{2}], Friends=[{3}]", this.Name, this.Age, this.Address, this.FriendsToString());
+ }
+ }
+
+ public class Address
+ {
+ public const string AnyStreet = "123 1st Ave";
+
+ public const string AnyCity = "Springfield";
+
+ public const string AnyState = "ZZ";
+
+ public string Street { get; set; }
+
+ public string City { get; set; }
+
+ public string State { get; set; }
+
+ public static Address CreateSample()
+ {
+ Address address = new Address
+ {
+ Street = AnyStreet,
+ City = AnyCity,
+ State = AnyState,
+ };
+
+ return address;
+ }
+
+ public override string ToString()
+ {
+ return String.Format("{0}, {1}, {2}", this.Street, this.City, this.State);
+ }
+ }
+
+ public class TestDynamicObject : DynamicObject
+ {
+ private IDictionary<string, object> _values = new Dictionary<string, object>();
+
+ public bool UseFallbackMethod { get; set; }
+ public bool UseErrorSuggestion { get; set; }
+
+ public string TestProperty { get; set; }
+
+ public override IEnumerable<string> GetDynamicMemberNames()
+ {
+ return _values.Keys;
+ }
+
+ public override bool TrySetMember(SetMemberBinder binder, object value)
+ {
+ _values[binder.Name] = value;
+ return true;
+ }
+
+ public override bool TryGetMember(GetMemberBinder binder, out object result)
+ {
+ if (this.UseFallbackMethod)
+ {
+ DynamicMetaObject target = new DynamicMetaObject(Expression.Parameter(this.GetType()), BindingRestrictions.Empty);
+ DynamicMetaObject errorSuggestion = null;
+
+ if (this.UseErrorSuggestion)
+ {
+ errorSuggestion = new DynamicMetaObject(Expression.Throw(Expression.Constant(new TestDynamicObjectException())), BindingRestrictions.Empty);
+ }
+
+ DynamicMetaObject metaObj = binder.FallbackGetMember(target, errorSuggestion);
+ Expression<Action> lambda = Expression.Lambda<Action>(metaObj.Expression, new ParameterExpression[] { });
+ lambda.Compile().Invoke();
+ }
+
+ return _values.TryGetValue(binder.Name, out result);
+ }
+
+ public static dynamic CreatePersonAsDynamic(Person person)
+ {
+ dynamic dynObj = new TestDynamicObject();
+
+ dynObj.Name = person.Name;
+ dynObj.Age = person.Age;
+ dynObj.Address = new Address();
+ dynObj.Address.City = person.Address.City;
+ dynObj.Address.Street = person.Address.Street;
+ dynObj.Address.State = person.Address.State;
+ dynObj.Friends = person.Friends;
+
+ return dynObj;
+ }
+
+ public class TestDynamicObjectException : Exception
+ {
+ }
+ }
+}
diff --git a/test/System.Json.Test.Unit/Common/ExceptionTestHelper.cs b/test/System.Json.Test.Unit/Common/ExceptionTestHelper.cs
new file mode 100644
index 00000000..254afcda
--- /dev/null
+++ b/test/System.Json.Test.Unit/Common/ExceptionTestHelper.cs
@@ -0,0 +1,27 @@
+using Xunit;
+
+namespace System.Json
+{
+ public static class ExceptionHelper
+ {
+ public static void Throws<TException>(Assert.ThrowsDelegate act, Action<TException> exceptionAssert) where TException : Exception
+ {
+ Exception ex = Record.Exception(act);
+ Assert.NotNull(ex);
+ TException tex = Assert.IsAssignableFrom<TException>(ex);
+ exceptionAssert(tex);
+ }
+
+ public static void Throws<TException>(Assert.ThrowsDelegate act, string message) where TException : Exception
+ {
+ Throws<TException>(act, ex => Assert.Equal(message, ex.Message));
+ }
+
+ public static void Throws<TException>(Assert.ThrowsDelegate act) where TException : Exception
+ {
+ Throws<TException>(act, _ => { });
+ }
+
+
+ }
+}
diff --git a/test/System.Json.Test.Unit/Extensions/JsonValueExtensionsTest.cs b/test/System.Json.Test.Unit/Extensions/JsonValueExtensionsTest.cs
new file mode 100644
index 00000000..ac7a7f68
--- /dev/null
+++ b/test/System.Json.Test.Unit/Extensions/JsonValueExtensionsTest.cs
@@ -0,0 +1,476 @@
+using System.Collections;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Runtime.Serialization;
+using System.Runtime.Serialization.Json;
+using Xunit;
+using Xunit.Extensions;
+
+namespace System.Json
+{
+ public class JsonValueExtensionsTest
+ {
+ const string DynamicPropertyNotDefined = "'{0}' does not contain a definition for property '{1}'.";
+ const string OperationNotSupportedOnJsonTypeMsgFormat = "Operation not supported on JsonValue instance of 'JsonType.{0}' type.";
+
+ [Fact]
+ public void CreateFromTypeTest()
+ {
+ JsonValue[] values =
+ {
+ AnyInstance.AnyJsonObject,
+ AnyInstance.AnyJsonArray,
+ AnyInstance.AnyJsonPrimitive,
+ AnyInstance.DefaultJsonValue
+ };
+
+ foreach (JsonValue value in values)
+ {
+ Assert.Same(value, JsonValueExtensions.CreateFrom(value));
+ }
+ }
+
+ public static IEnumerable<object[]> PrimitiveTestData
+ {
+ get
+ {
+ yield return new object[] { AnyInstance.AnyBool };
+ yield return new object[] { AnyInstance.AnyByte };
+ yield return new object[] { AnyInstance.AnyChar };
+ yield return new object[] { AnyInstance.AnyDateTime };
+ yield return new object[] { AnyInstance.AnyDateTimeOffset };
+ yield return new object[] { AnyInstance.AnyDecimal };
+ yield return new object[] { AnyInstance.AnyDouble };
+ yield return new object[] { AnyInstance.AnyFloat };
+ yield return new object[] { AnyInstance.AnyGuid };
+ yield return new object[] { AnyInstance.AnyLong };
+ yield return new object[] { AnyInstance.AnySByte };
+ yield return new object[] { AnyInstance.AnyShort };
+ yield return new object[] { AnyInstance.AnyUInt };
+ yield return new object[] { AnyInstance.AnyULong };
+ yield return new object[] { AnyInstance.AnyUri };
+ yield return new object[] { AnyInstance.AnyUShort };
+ yield return new object[] { AnyInstance.AnyInt };
+ yield return new object[] { AnyInstance.AnyString };
+ }
+ }
+
+ [Theory]
+ [PropertyData("PrimitiveTestData")]
+ public void CreateFromPrimitiveTest(object value)
+ {
+ Type valueType = value.GetType();
+ Assert.Equal(value, JsonValueExtensions.CreateFrom(value).ReadAs(valueType));
+ }
+
+ [Fact]
+ public void CreateFromComplexTest()
+ {
+ JsonValue target = JsonValueExtensions.CreateFrom(AnyInstance.AnyPerson);
+
+ Assert.Equal(AnyInstance.AnyPerson.Name, (string)target["Name"]);
+ Assert.Equal(AnyInstance.AnyPerson.Age, (int)target["Age"]);
+ Assert.Equal(AnyInstance.AnyPerson.Address.City, (string)target.ValueOrDefault("Address", "City"));
+ }
+
+ [Fact]
+ public void CreateFromDynamicSimpleTest()
+ {
+ JsonValue target;
+
+ target = JsonValueExtensions.CreateFrom(AnyInstance.AnyDynamic);
+ Assert.NotNull(target);
+
+ string expected = "{\"Name\":\"Bill Gates\",\"Age\":21,\"Grades\":[\"F\",\"B-\",\"C\"]}";
+ dynamic obj = new TestDynamicObject();
+ obj.Name = "Bill Gates";
+ obj.Age = 21;
+ obj.Grades = new[] { "F", "B-", "C" };
+
+ target = JsonValueExtensions.CreateFrom(obj);
+ Assert.Equal<string>(expected, target.ToString());
+
+ target = JsonValueExtensions.CreateFrom(new TestDynamicObject());
+ Assert.Equal<string>("{}", target.ToString());
+ }
+
+ [Fact]
+ public void CreateFromDynamicComplextTest()
+ {
+ JsonValue target;
+ Person person = AnyInstance.AnyPerson;
+ dynamic dyn = TestDynamicObject.CreatePersonAsDynamic(person);
+
+ dyn.TestProperty = AnyInstance.AnyString;
+
+ target = JsonValueExtensions.CreateFrom(dyn);
+ Assert.NotNull(target);
+ Assert.Equal<string>(AnyInstance.AnyString, dyn.TestProperty);
+ Person jvPerson = target.ReadAsType<Person>();
+ Assert.Equal(person.ToString(), jvPerson.ToString());
+
+ Person p1 = Person.CreateSample();
+ Person p2 = Person.CreateSample();
+
+ p2.Name += "__2";
+ p2.Age += 10;
+ p2.Address.City += "__2";
+
+ Person[] friends = new Person[] { p1, p2 };
+ target = JsonValueExtensions.CreateFrom(friends);
+ Person[] personArr = target.ReadAsType<Person[]>();
+ Assert.Equal<int>(friends.Length, personArr.Length);
+ Assert.Equal<string>(friends[0].ToString(), personArr[0].ToString());
+ Assert.Equal<string>(friends[1].ToString(), personArr[1].ToString());
+ }
+
+ [Fact]
+ public void CreateFromDynamicBinderFallbackTest()
+ {
+ JsonValue target;
+ Person person = AnyInstance.AnyPerson;
+ dynamic dyn = new TestDynamicObject();
+ dyn.Name = AnyInstance.AnyString;
+
+ dyn.UseFallbackMethod = true;
+ string expectedMessage = String.Format(DynamicPropertyNotDefined, dyn.GetType().FullName, "Name");
+ ExceptionHelper.Throws<InvalidOperationException>(() => target = JsonValueExtensions.CreateFrom(dyn), expectedMessage);
+
+ dyn.UseErrorSuggestion = true;
+ ExceptionHelper.Throws<TestDynamicObject.TestDynamicObjectException>(() => target = JsonValueExtensions.CreateFrom(dyn));
+ }
+
+ [Fact]
+ public void CreateFromNestedDynamicTest()
+ {
+ JsonValue target;
+ string expected = "{\"Name\":\"Root\",\"Level1\":{\"Name\":\"Level1\",\"Level2\":{\"Name\":\"Level2\"}}}";
+ dynamic dyn = new TestDynamicObject();
+ dyn.Name = "Root";
+ dyn.Level1 = new TestDynamicObject();
+ dyn.Level1.Name = "Level1";
+ dyn.Level1.Level2 = new TestDynamicObject();
+ dyn.Level1.Level2.Name = "Level2";
+
+ target = JsonValueExtensions.CreateFrom(dyn);
+ Assert.NotNull(target);
+ Assert.Equal<string>(expected, target.ToString());
+ }
+
+ [Fact]
+ public void CreateFromDynamicWithJsonValueChildrenTest()
+ {
+ JsonValue target;
+ string level3 = "{\"Name\":\"Level3\",\"Null\":null}";
+ string level2 = "{\"Name\":\"Level2\",\"JsonObject\":" + AnyInstance.AnyJsonObject.ToString() + ",\"JsonArray\":" + AnyInstance.AnyJsonArray.ToString() + ",\"Level3\":" + level3 + "}";
+ string level1 = "{\"Name\":\"Level1\",\"JsonPrimitive\":" + AnyInstance.AnyJsonPrimitive.ToString() + ",\"Level2\":" + level2 + "}";
+ string expected = "{\"Name\":\"Root\",\"Level1\":" + level1 + "}";
+
+ dynamic dyn = new TestDynamicObject();
+ dyn.Name = "Root";
+ dyn.Level1 = new TestDynamicObject();
+ dyn.Level1.Name = "Level1";
+ dyn.Level1.JsonPrimitive = AnyInstance.AnyJsonPrimitive;
+ dyn.Level1.Level2 = new TestDynamicObject();
+ dyn.Level1.Level2.Name = "Level2";
+ dyn.Level1.Level2.JsonObject = AnyInstance.AnyJsonObject;
+ dyn.Level1.Level2.JsonArray = AnyInstance.AnyJsonArray;
+ dyn.Level1.Level2.Level3 = new TestDynamicObject();
+ dyn.Level1.Level2.Level3.Name = "Level3";
+ dyn.Level1.Level2.Level3.Null = null;
+
+ target = JsonValueExtensions.CreateFrom(dyn);
+ Assert.Equal<string>(expected, target.ToString());
+ }
+
+ [Fact]
+ public void CreateFromDynamicJVTest()
+ {
+ JsonValue target;
+
+ dynamic[] values = new dynamic[]
+ {
+ AnyInstance.AnyJsonArray,
+ AnyInstance.AnyJsonObject,
+ AnyInstance.AnyJsonPrimitive,
+ AnyInstance.DefaultJsonValue
+ };
+
+ foreach (dynamic dyn in values)
+ {
+ target = JsonValueExtensions.CreateFrom(dyn);
+ Assert.Same(dyn, target);
+ }
+ }
+
+ [Fact]
+ public void ReadAsTypeFallbackTest()
+ {
+ JsonValue jv = AnyInstance.AnyInt;
+ Person personFallback = Person.CreateSample();
+
+ Person personResult = jv.ReadAsType<Person>(personFallback);
+ Assert.Same(personFallback, personResult);
+
+ int intFallback = 45;
+ int intValue = jv.ReadAsType<int>(intFallback);
+ Assert.Equal<int>(AnyInstance.AnyInt, intValue);
+ }
+
+ [Fact(Skip = "See bug #228569 in CSDMain")]
+ public void ReadAsTypeCollectionTest()
+ {
+ JsonValue jsonValue;
+ jsonValue = JsonValue.Parse("[1,2,3]");
+
+ List<object> list = jsonValue.ReadAsType<List<object>>();
+ Array array = jsonValue.ReadAsType<Array>();
+ object[] objArr = jsonValue.ReadAsType<object[]>();
+
+ IList[] collections =
+ {
+ list, array, objArr
+ };
+
+ foreach (IList collection in collections)
+ {
+ Assert.Equal<int>(jsonValue.Count, collection.Count);
+
+ for (int i = 0; i < jsonValue.Count; i++)
+ {
+ Assert.Equal<int>((int)jsonValue[i], (int)collection[i]);
+ }
+ }
+
+ jsonValue = JsonValue.Parse("{\"A\":1,\"B\":2,\"C\":3}");
+ Dictionary<string, object> dictionary = jsonValue.ReadAsType<Dictionary<string, object>>();
+
+ Assert.Equal<int>(jsonValue.Count, dictionary.Count);
+ foreach (KeyValuePair<string, JsonValue> pair in jsonValue)
+ {
+ Assert.Equal((int)jsonValue[pair.Key], (int)dictionary[pair.Key]);
+ }
+ }
+
+ [Fact]
+ public void TryReadAsInvalidCollectionTest()
+ {
+ JsonValue jo = AnyInstance.AnyJsonObject;
+ JsonValue ja = AnyInstance.AnyJsonArray;
+ JsonValue jp = AnyInstance.AnyJsonPrimitive;
+ JsonValue jd = AnyInstance.DefaultJsonValue;
+
+ JsonValue[] invalidArrays =
+ {
+ jo, jp, jd
+ };
+
+ JsonValue[] invalidDictionaries =
+ {
+ ja, jp, jd
+ };
+
+ bool success;
+ object[] array;
+ Dictionary<string, object> dictionary;
+
+ foreach (JsonValue value in invalidArrays)
+ {
+ success = value.TryReadAsType<object[]>(out array);
+ Console.WriteLine("Try reading {0} as object[]; success = {1}", value.ToString(), success);
+ Assert.False(success);
+ Assert.Null(array);
+ }
+
+ foreach (JsonValue value in invalidDictionaries)
+ {
+ success = value.TryReadAsType<Dictionary<string, object>>(out dictionary);
+ Console.WriteLine("Try reading {0} as Dictionary<string, object>; success = {1}", value.ToString(), success);
+ Assert.False(success);
+ Assert.Null(dictionary);
+ }
+ }
+
+ [Fact]
+ public void ReadAsExtensionsOnDynamicTest()
+ {
+ dynamic jv = JsonValueExtensions.CreateFrom(AnyInstance.AnyPerson);
+ bool success;
+ object obj;
+
+ success = jv.TryReadAsType(typeof(Person), out obj);
+ Assert.True(success);
+ Assert.NotNull(obj);
+ Assert.Equal<string>(AnyInstance.AnyPerson.ToString(), obj.ToString());
+
+ obj = jv.ReadAsType(typeof(Person));
+ Assert.NotNull(obj);
+ Assert.Equal<string>(AnyInstance.AnyPerson.ToString(), obj.ToString());
+ }
+
+#if CODEPLEX
+ [Fact]
+ public void ToCollectionTest()
+ {
+ JsonValue target;
+ object[] array;
+
+ target = AnyInstance.AnyJsonArray;
+ array = target.ToObjectArray();
+
+ Assert.Equal(target.Count, array.Length);
+
+ for (int i = 0; i < target.Count; i++)
+ {
+ Assert.Equal(array[i], target[i].ReadAs(array[i].GetType()));
+ }
+
+ target = AnyInstance.AnyJsonObject;
+ IDictionary<string, object> dictionary = target.ToDictionary();
+
+ Assert.Equal(target.Count, dictionary.Count);
+
+ foreach (KeyValuePair<string, JsonValue> pair in target)
+ {
+ Assert.True(dictionary.ContainsKey(pair.Key));
+ Assert.Equal<string>(target[pair.Key].ToString(), dictionary[pair.Key].ToString());
+ }
+ }
+
+ [Fact]
+ public void ToCollectionsNestedTest()
+ {
+ JsonArray ja = JsonValue.Parse("[1, {\"A\":[1,2,3]}, 5]") as JsonArray;
+ JsonObject jo = JsonValue.Parse("{\"A\":1,\"B\":[1,2,3]}") as JsonObject;
+
+ object[] objArray = ja.ToObjectArray();
+ Assert.NotNull(objArray);
+ Assert.Equal<int>(ja.Count, objArray.Length);
+ Assert.Equal((int)ja[0], (int)objArray[0]);
+ Assert.Equal((int)ja[2], (int)objArray[2]);
+
+ IDictionary<string, object> dict = objArray[1] as IDictionary<string, object>;
+ Assert.NotNull(dict);
+
+ objArray = dict["A"] as object[];
+ Assert.NotNull(objArray);
+ for (int i = 0; i < 3; i++)
+ {
+ Assert.Equal(i + 1, (int)objArray[i]);
+ }
+
+ dict = jo.ToDictionary();
+ Assert.NotNull(dict);
+ Assert.Equal<int>(jo.Count, dict.Count);
+ Assert.Equal<int>(1, (int)dict["A"]);
+
+ objArray = dict["B"] as object[];
+ Assert.NotNull(objArray);
+ for (int i = 1; i < 3; i++)
+ {
+ Assert.Equal(i + 1, (int)objArray[i]);
+ }
+ }
+
+ [Fact]
+ public void ToCollectionsInvalidTest()
+ {
+ JsonValue jo = AnyInstance.AnyJsonObject;
+ JsonValue ja = AnyInstance.AnyJsonArray;
+ JsonValue jp = AnyInstance.AnyJsonPrimitive;
+ JsonValue jd = AnyInstance.DefaultJsonValue;
+
+ ExceptionTestHelper.ExpectException<NotSupportedException>(delegate { var ret = jd.ToObjectArray(); }, String.Format(OperationNotSupportedOnJsonTypeMsgFormat, jd.JsonType));
+ ExceptionTestHelper.ExpectException<NotSupportedException>(delegate { var ret = jd.ToDictionary(); }, String.Format(OperationNotSupportedOnJsonTypeMsgFormat, jd.JsonType));
+
+ ExceptionTestHelper.ExpectException<NotSupportedException>(delegate { var ret = jp.ToObjectArray(); }, String.Format(OperationNotSupportedOnJsonTypeMsgFormat, jp.JsonType));
+ ExceptionTestHelper.ExpectException<NotSupportedException>(delegate { var ret = jp.ToDictionary(); }, String.Format(OperationNotSupportedOnJsonTypeMsgFormat, jp.JsonType));
+
+ ExceptionTestHelper.ExpectException<NotSupportedException>(delegate { var ret = jo.ToObjectArray(); }, String.Format(OperationNotSupportedOnJsonTypeMsgFormat, jo.JsonType));
+ ExceptionTestHelper.ExpectException<NotSupportedException>(delegate { var ret = ja.ToDictionary(); }, String.Format(OperationNotSupportedOnJsonTypeMsgFormat, ja.JsonType));
+ }
+
+ // 195843 JsonValue to support generic extension methods defined in JsonValueExtensions.
+ // 195867 Consider creating extension point for allowing new extension methods to be callable via dynamic interface
+ //[Fact] This requires knowledge of the C# binder to be able to get the generic call parameters.
+ public void ReadAsGenericExtensionsOnDynamicTest()
+ {
+ dynamic jv = JsonValueExtensions.CreateFrom(AnyInstance.AnyPerson);
+ Person person;
+ bool success;
+
+ person = jv.ReadAsType<Person>();
+ Assert.NotNull(person);
+ Assert.Equal<string>(AnyInstance.AnyPerson.ToString(), person.ToString());
+
+ success = jv.TryReadAsType<Person>(out person);
+ Assert.True(success);
+ Assert.NotNull(person);
+ Assert.Equal<string>(AnyInstance.AnyPerson.ToString(), person.ToString());
+ }
+#else
+ [Fact(Skip = "See bug #228569 in CSDMain")]
+ public void TestDataContractJsonSerializerSettings()
+ {
+ TestTypeForSerializerSettings instance = new TestTypeForSerializerSettings
+ {
+ BaseRef = new DerivedType(),
+ Date = AnyInstance.AnyDateTime,
+ Dict = new Dictionary<string, object>
+ {
+ { "one", 1 },
+ { "two", 2 },
+ { "two point five", 2.5 },
+ }
+ };
+
+ JsonObject dict = new JsonObject
+ {
+ { "one", 1 },
+ { "two", 2 },
+ { "two point five", 2.5 },
+ };
+
+ JsonObject equivalentJsonObject = new JsonObject
+ {
+ { "BaseRef", new JsonObject { { "__type", "DerivedType:NS" } } },
+ { "Date", AnyInstance.AnyDateTime },
+ { "Dict", dict },
+ };
+
+ JsonObject createdFromType = JsonValueExtensions.CreateFrom(instance) as JsonObject;
+ Assert.Equal(equivalentJsonObject.ToString(), createdFromType.ToString());
+
+ TestTypeForSerializerSettings newInstance = equivalentJsonObject.ReadAsType<TestTypeForSerializerSettings>();
+ // DISABLED, 198487 - Assert.Equal(instance.Date, newInstance.Date);
+ Assert.Equal(instance.BaseRef.GetType().FullName, newInstance.BaseRef.GetType().FullName);
+ Assert.Equal(3, newInstance.Dict.Count);
+ Assert.Equal(1, newInstance.Dict["one"]);
+ Assert.Equal(2, newInstance.Dict["two"]);
+ Assert.Equal(2.5, Convert.ToDouble(newInstance.Dict["two point five"], CultureInfo.InvariantCulture));
+ }
+
+ [DataContract]
+ public class TestTypeForSerializerSettings
+ {
+ [DataMember]
+ public BaseType BaseRef { get; set; }
+ [DataMember]
+ public DateTime Date { get; set; }
+ [DataMember]
+ public Dictionary<string, object> Dict { get; set; }
+ }
+
+ [DataContract(Name = "BaseType", Namespace = "NS")]
+ [KnownType(typeof(DerivedType))]
+ public class BaseType
+ {
+ }
+
+ [DataContract(Name = "DerivedType", Namespace = "NS")]
+ public class DerivedType : BaseType
+ {
+ }
+#endif
+ }
+}
diff --git a/test/System.Json.Test.Unit/FormUrlEncodedJsonTests.cs b/test/System.Json.Test.Unit/FormUrlEncodedJsonTests.cs
new file mode 100644
index 00000000..a114abba
--- /dev/null
+++ b/test/System.Json.Test.Unit/FormUrlEncodedJsonTests.cs
@@ -0,0 +1,74 @@
+using System.Collections.Generic;
+using Microsoft.TestCommon;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Json
+{
+ public class FormUrlEncodedJsonTests
+ {
+ [Fact]
+ public void TypeIsCorrect()
+ {
+ Assert.Type.HasProperties(typeof(FormUrlEncodedJson), TypeAssert.TypeProperties.IsPublicVisibleClass | TypeAssert.TypeProperties.IsStatic);
+ }
+
+ [Fact]
+ public void ParseThrowsOnNull()
+ {
+ Assert.ThrowsArgumentNull(() => FormUrlEncodedJson.Parse(null), null);
+ }
+
+ [Fact]
+ public void ParseThrowsInvalidMaxDepth()
+ {
+ Assert.ThrowsArgument(() => FormUrlEncodedJson.Parse(CreateQuery(), -1), "maxDepth");
+ Assert.ThrowsArgument(() => FormUrlEncodedJson.Parse(CreateQuery(), 0), "maxDepth");
+ }
+
+ [Fact]
+ public void ParseThrowsMaxDepthExceeded()
+ {
+ // Depth of 'a[b]=1' is 3
+ IEnumerable<KeyValuePair<string, string>> query = CreateQuery(new KeyValuePair<string, string>("a[b]", "1"));
+ Assert.ThrowsArgument(() => { FormUrlEncodedJson.Parse(query, 2); }, null);
+
+ // This should succeed
+ Assert.NotNull(FormUrlEncodedJson.Parse(query, 3));
+ }
+
+ [Fact]
+ public void TryParseThrowsOnNull()
+ {
+ JsonObject value;
+ Assert.ThrowsArgumentNull(() => FormUrlEncodedJson.TryParse(null, out value), null);
+ }
+
+ [Fact]
+ public void TryParseThrowsInvalidMaxDepth()
+ {
+ JsonObject value;
+ Assert.ThrowsArgument(() => FormUrlEncodedJson.TryParse(CreateQuery(), -1, out value), "maxDepth");
+ Assert.ThrowsArgument(() => FormUrlEncodedJson.TryParse(CreateQuery(), 0, out value), "maxDepth");
+ }
+
+ [Fact]
+ public void TryParseReturnsFalseMaxDepthExceeded()
+ {
+ JsonObject value;
+
+ // Depth of 'a[b]=1' is 3
+ IEnumerable<KeyValuePair<string, string>> query = CreateQuery(new KeyValuePair<string, string>("a[b]", "1"));
+ Assert.False(FormUrlEncodedJson.TryParse(query, 2, out value), "Parse should have failed due to too high depth.");
+
+ // This should succeed
+ Assert.True(FormUrlEncodedJson.TryParse(query, 3, out value), "Expected non-null JsonObject instance");
+ Assert.NotNull(value);
+ }
+
+ private static IEnumerable<KeyValuePair<string, string>> CreateQuery(params KeyValuePair<string, string>[] namevaluepairs)
+ {
+ return new List<KeyValuePair<string, string>>(namevaluepairs);
+ }
+ }
+} \ No newline at end of file
diff --git a/test/System.Json.Test.Unit/JsonArrayTest.cs b/test/System.Json.Test.Unit/JsonArrayTest.cs
new file mode 100644
index 00000000..1d351d05
--- /dev/null
+++ b/test/System.Json.Test.Unit/JsonArrayTest.cs
@@ -0,0 +1,604 @@
+using System.Collections;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Linq;
+using System.Runtime.Serialization.Json;
+using Xunit;
+
+namespace System.Json
+{
+ public class JsonArrayTest
+ {
+ [Fact]
+ public void JsonArrayConstructorParamsTest()
+ {
+ JsonArray target;
+
+ target = new JsonArray();
+ Assert.Equal(0, target.Count);
+
+ target = new JsonArray(null);
+ Assert.Equal(0, target.Count);
+
+ List<JsonValue> items = new List<JsonValue> { AnyInstance.AnyJsonValue1, AnyInstance.AnyJsonValue2 };
+ target = new JsonArray(items.ToArray());
+ ValidateJsonArrayItems(target, items);
+
+ target = new JsonArray(items[0], items[1]);
+ ValidateJsonArrayItems(target, items);
+
+ // Invalide tests
+ items.Add(AnyInstance.DefaultJsonValue);
+ ExceptionHelper.Throws<ArgumentException>(() => new JsonArray(items.ToArray()));
+ ExceptionHelper.Throws<ArgumentException>(() => new JsonArray(items[0], items[1], items[2]));
+ }
+
+ [Fact]
+ public void JsonArrayConstructorEnumTest()
+ {
+ List<JsonValue> items = new List<JsonValue> { AnyInstance.AnyJsonValue1, AnyInstance.AnyJsonValue2, AnyInstance.AnyJsonValue3 };
+ JsonArray target;
+
+ target = new JsonArray(items);
+ ValidateJsonArrayItems(target, items);
+
+ ExceptionHelper.Throws<ArgumentNullException>(() => new JsonArray((IEnumerable<JsonValue>)null));
+
+ items.Add(AnyInstance.DefaultJsonValue);
+ ExceptionHelper.Throws<ArgumentException>(() => new JsonArray(items));
+ }
+
+ [Fact]
+ public void AddTest()
+ {
+ JsonArray target = new JsonArray();
+ JsonValue item = AnyInstance.AnyJsonValue1;
+ Assert.False(target.Contains(item));
+ target.Add(item);
+ Assert.Equal(1, target.Count);
+ Assert.Equal(item, target[0]);
+ Assert.True(target.Contains(item));
+
+ ExceptionHelper.Throws<ArgumentException>(() => target.Add(AnyInstance.DefaultJsonValue));
+ }
+
+ [Fact]
+ public void AddRangeEnumTest()
+ {
+ List<JsonValue> items = new List<JsonValue> { AnyInstance.AnyJsonValue1, AnyInstance.AnyJsonValue2 };
+
+ JsonArray target = new JsonArray();
+ target.AddRange(items);
+ ValidateJsonArrayItems(target, items);
+
+ ExceptionHelper.Throws<ArgumentNullException>(() => new JsonArray().AddRange((IEnumerable<JsonValue>)null));
+
+ items.Add(AnyInstance.DefaultJsonValue);
+ ExceptionHelper.Throws<ArgumentException>(() => new JsonArray().AddRange(items));
+ }
+
+ [Fact]
+ public void AddRangeParamsTest()
+ {
+ List<JsonValue> items = new List<JsonValue> { AnyInstance.AnyJsonValue1, AnyInstance.AnyJsonValue2, AnyInstance.AnyJsonValue3 };
+ JsonArray target;
+
+ target = new JsonArray();
+ target.AddRange(items[0], items[1], items[2]);
+ ValidateJsonArrayItems(target, items);
+
+ target = new JsonArray();
+ target.AddRange(items.ToArray());
+ ValidateJsonArrayItems(target, items);
+
+ target.AddRange();
+ ValidateJsonArrayItems(target, items);
+
+ items.Add(AnyInstance.DefaultJsonValue);
+ ExceptionHelper.Throws<ArgumentException>(() => new JsonArray().AddRange(items[items.Count - 1]));
+ ExceptionHelper.Throws<ArgumentException>(() => new JsonArray().AddRange(items));
+ }
+
+ [Fact]
+ public void ClearTest()
+ {
+ JsonArray target = new JsonArray(AnyInstance.AnyJsonValue1, AnyInstance.AnyJsonValue2);
+ Assert.Equal(2, target.Count);
+ target.Clear();
+ Assert.Equal(0, target.Count);
+ }
+
+ [Fact]
+ public void ContainsTest()
+ {
+ JsonValue item1 = AnyInstance.AnyJsonValue1;
+ JsonValue item2 = AnyInstance.AnyJsonValue2;
+ JsonArray target = new JsonArray(item1);
+ Assert.True(target.Contains(item1));
+ Assert.False(target.Contains(item2));
+
+ target.Add(item2);
+ Assert.True(target.Contains(item1));
+ Assert.True(target.Contains(item2));
+
+ target.Remove(item1);
+ Assert.False(target.Contains(item1));
+ Assert.True(target.Contains(item2));
+ }
+
+ [Fact]
+ public void ReadAsComplexTypeTest()
+ {
+ JsonArray target = new JsonArray(AnyInstance.AnyInt, AnyInstance.AnyInt + 1, AnyInstance.AnyInt + 2);
+ int[] intArray1 = (int[])target.ReadAsType(typeof(int[]));
+ int[] intArray2 = target.ReadAsType<int[]>();
+
+ Assert.Equal(((JsonArray)target).Count, intArray1.Length);
+ Assert.Equal(((JsonArray)target).Count, intArray2.Length);
+
+ for (int i = 0; i < intArray1.Length; i++)
+ {
+ Assert.Equal(AnyInstance.AnyInt + i, intArray1[i]);
+ Assert.Equal(AnyInstance.AnyInt + i, intArray2[i]);
+ }
+ }
+
+ [Fact]
+ public void CopyToTest()
+ {
+ JsonValue item1 = AnyInstance.AnyJsonValue1;
+ JsonValue item2 = AnyInstance.AnyJsonValue2;
+ JsonArray target = new JsonArray(item1, item2);
+ JsonValue[] array = new JsonValue[target.Count + 1];
+
+ target.CopyTo(array, 0);
+ Assert.Equal(item1, array[0]);
+ Assert.Equal(item2, array[1]);
+
+ target.CopyTo(array, 1);
+ Assert.Equal(item1, array[1]);
+ Assert.Equal(item2, array[2]);
+
+ ExceptionHelper.Throws<ArgumentNullException>(() => target.CopyTo(null, 0));
+ ExceptionHelper.Throws<ArgumentOutOfRangeException>(() => target.CopyTo(array, -1));
+ ExceptionHelper.Throws<ArgumentException>(() => target.CopyTo(array, array.Length - target.Count + 1));
+ }
+
+ [Fact]
+ public void IndexOfTest()
+ {
+ JsonValue item1 = AnyInstance.AnyJsonValue1;
+ JsonValue item2 = AnyInstance.AnyJsonValue2;
+ JsonValue item3 = AnyInstance.AnyJsonValue3;
+ JsonArray target = new JsonArray(item1, item2);
+
+ Assert.Equal(0, target.IndexOf(item1));
+ Assert.Equal(1, target.IndexOf(item2));
+ Assert.Equal(-1, target.IndexOf(item3));
+ }
+
+ [Fact]
+ public void InsertTest()
+ {
+ JsonValue item1 = AnyInstance.AnyJsonValue1;
+ JsonValue item2 = AnyInstance.AnyJsonValue2;
+ JsonValue item3 = AnyInstance.AnyJsonValue3;
+ JsonArray target = new JsonArray(item1);
+
+ Assert.Equal(1, target.Count);
+ target.Insert(0, item2);
+ Assert.Equal(2, target.Count);
+ Assert.Equal(item2, target[0]);
+ Assert.Equal(item1, target[1]);
+
+ target.Insert(1, item3);
+ Assert.Equal(3, target.Count);
+ Assert.Equal(item2, target[0]);
+ Assert.Equal(item3, target[1]);
+ Assert.Equal(item1, target[2]);
+
+ target.Insert(target.Count, item2);
+ Assert.Equal(4, target.Count);
+ Assert.Equal(item2, target[0]);
+ Assert.Equal(item3, target[1]);
+ Assert.Equal(item1, target[2]);
+ Assert.Equal(item2, target[3]);
+
+ ExceptionHelper.Throws<ArgumentOutOfRangeException>(() => target.Insert(-1, item3));
+ ExceptionHelper.Throws<ArgumentOutOfRangeException>(() => target.Insert(target.Count + 1, item1));
+ ExceptionHelper.Throws<ArgumentException>(() => target.Insert(0, AnyInstance.DefaultJsonValue));
+ }
+
+ [Fact]
+ public void RemoveTest()
+ {
+ JsonValue item1 = AnyInstance.AnyJsonValue1;
+ JsonValue item2 = AnyInstance.AnyJsonValue2;
+ JsonValue item3 = AnyInstance.AnyJsonValue3;
+ JsonArray target = new JsonArray(item1, item2, item3);
+
+ Assert.True(target.Remove(item2));
+ Assert.Equal(2, target.Count);
+ Assert.Equal(item1, target[0]);
+ Assert.Equal(item3, target[1]);
+
+ Assert.False(target.Remove(item2));
+ Assert.Equal(2, target.Count);
+ }
+
+ [Fact]
+ public void RemoveAtTest()
+ {
+ JsonValue item1 = AnyInstance.AnyJsonValue1;
+ JsonValue item2 = AnyInstance.AnyJsonValue2;
+ JsonValue item3 = AnyInstance.AnyJsonValue3;
+ JsonArray target = new JsonArray(item1, item2, item3);
+
+ target.RemoveAt(1);
+ Assert.Equal(2, target.Count);
+ Assert.Equal(item1, target[0]);
+ Assert.Equal(item3, target[1]);
+
+ ExceptionHelper.Throws<ArgumentOutOfRangeException>(() => target.RemoveAt(-1));
+ ExceptionHelper.Throws<ArgumentOutOfRangeException>(() => target.RemoveAt(target.Count));
+ }
+
+ [Fact]
+ public void ToStringTest()
+ {
+ JsonArray target;
+ JsonValue item1 = AnyInstance.AnyJsonValue1;
+ JsonValue item2 = null;
+ JsonValue item3 = AnyInstance.AnyJsonValue2;
+
+ target = new JsonArray(item1, item2, item3);
+
+ string expected = String.Format(CultureInfo.InvariantCulture, "[{0},null,{1}]", item1.ToString(), item3.ToString());
+ Assert.Equal(expected, target.ToString());
+
+ string json = "[\r\n \"hello\",\r\n null,\r\n [\r\n 1,\r\n 2,\r\n 3\r\n ]\r\n]";
+ target = JsonValue.Parse(json) as JsonArray;
+
+ Assert.Equal<string>(json.Replace("\r\n", "").Replace(" ", ""), target.ToString());
+ }
+
+ [Fact]
+ public void GetEnumeratorTest()
+ {
+ JsonValue item1 = AnyInstance.AnyJsonValue1;
+ JsonValue item2 = AnyInstance.AnyJsonValue2;
+
+ IEnumerable<JsonValue> target = new JsonArray(item1, item2);
+ IEnumerator<JsonValue> enumerator = target.GetEnumerator();
+ Assert.True(enumerator.MoveNext());
+ Assert.Equal(item1, enumerator.Current);
+ Assert.True(enumerator.MoveNext());
+ Assert.Equal(item2, enumerator.Current);
+ Assert.False(enumerator.MoveNext());
+ }
+
+ [Fact]
+ public void GetEnumeratorTest1()
+ {
+ JsonValue item1 = AnyInstance.AnyJsonValue1;
+ JsonValue item2 = AnyInstance.AnyJsonValue2;
+
+ IEnumerable target = new JsonArray(item1, item2);
+ IEnumerator enumerator = target.GetEnumerator();
+ Assert.True(enumerator.MoveNext());
+ Assert.Equal(item1, enumerator.Current);
+ Assert.True(enumerator.MoveNext());
+ Assert.Equal(item2, enumerator.Current);
+ Assert.False(enumerator.MoveNext());
+ }
+
+ [Fact]
+ public void CountTest()
+ {
+ JsonValue item1 = AnyInstance.AnyJsonValue1;
+ JsonValue item2 = AnyInstance.AnyJsonValue2;
+
+ JsonArray target = new JsonArray();
+ Assert.Equal(0, target.Count);
+ target.Add(item1);
+ Assert.Equal(1, target.Count);
+ target.Add(item2);
+ Assert.Equal(2, target.Count);
+ target.Remove(item1);
+ Assert.Equal(1, target.Count);
+ }
+
+ [Fact]
+ public void IsReadOnlyTest()
+ {
+ JsonArray target = AnyInstance.AnyJsonArray;
+ Assert.False(target.IsReadOnly);
+ }
+
+ [Fact]
+ public void ItemTest()
+ {
+ JsonValue item1 = AnyInstance.AnyJsonValue1;
+ JsonValue item2 = AnyInstance.AnyJsonValue2;
+
+ JsonArray target = new JsonArray(item1);
+ Assert.Equal(item1, target[0]);
+ target[0] = item2;
+ Assert.Equal(item2, target[0]);
+ Assert.Equal(item2, target[(short)0]);
+ Assert.Equal(item2, target[(ushort)0]);
+ Assert.Equal(item2, target[(byte)0]);
+ Assert.Equal(item2, target[(sbyte)0]);
+ Assert.Equal(item2, target[(char)0]);
+
+ ExceptionHelper.Throws<ArgumentOutOfRangeException>(delegate { var i = target[-1]; });
+ ExceptionHelper.Throws<ArgumentOutOfRangeException>(delegate { var i = target[target.Count]; });
+ ExceptionHelper.Throws<ArgumentOutOfRangeException>(delegate { target[-1] = AnyInstance.AnyJsonValue1; });
+ ExceptionHelper.Throws<ArgumentOutOfRangeException>(delegate { target[target.Count] = AnyInstance.AnyJsonValue2; });
+ ExceptionHelper.Throws<ArgumentException>(delegate { target[0] = AnyInstance.DefaultJsonValue; });
+ }
+
+ [Fact]
+ public void ChangingEventsTest()
+ {
+ JsonArray ja = new JsonArray(AnyInstance.AnyInt, AnyInstance.AnyBool, null);
+ TestEvents(
+ ja,
+ arr => arr.Add(1),
+ new List<Tuple<bool, JsonValue, JsonValueChangeEventArgs>>
+ {
+ new Tuple<bool, JsonValue, JsonValueChangeEventArgs>(true, ja, new JsonValueChangeEventArgs(1, JsonValueChange.Add, 3)),
+ new Tuple<bool, JsonValue, JsonValueChangeEventArgs>(false, ja, new JsonValueChangeEventArgs(1, JsonValueChange.Add, 3)),
+ });
+
+ TestEvents(
+ ja,
+ arr => arr.AddRange(AnyInstance.AnyString, AnyInstance.AnyDouble),
+ new List<Tuple<bool, JsonValue, JsonValueChangeEventArgs>>
+ {
+ new Tuple<bool, JsonValue, JsonValueChangeEventArgs>(true, ja, new JsonValueChangeEventArgs(AnyInstance.AnyString, JsonValueChange.Add, 4)),
+ new Tuple<bool, JsonValue, JsonValueChangeEventArgs>(true, ja, new JsonValueChangeEventArgs(AnyInstance.AnyDouble, JsonValueChange.Add, 5)),
+ new Tuple<bool, JsonValue, JsonValueChangeEventArgs>(false, ja, new JsonValueChangeEventArgs(AnyInstance.AnyString, JsonValueChange.Add, 4)),
+ new Tuple<bool, JsonValue, JsonValueChangeEventArgs>(false, ja, new JsonValueChangeEventArgs(AnyInstance.AnyDouble, JsonValueChange.Add, 5)),
+ });
+
+ TestEvents(
+ ja,
+ arr => arr[1] = 2,
+ new List<Tuple<bool, JsonValue, JsonValueChangeEventArgs>>
+ {
+ new Tuple<bool, JsonValue, JsonValueChangeEventArgs>(true, ja, new JsonValueChangeEventArgs(2, JsonValueChange.Replace, 1)),
+ new Tuple<bool, JsonValue, JsonValueChangeEventArgs>(false, ja, new JsonValueChangeEventArgs(AnyInstance.AnyBool, JsonValueChange.Replace, 1)),
+ });
+
+ ja = new JsonArray { 1, 2, 3 };
+ TestEvents(
+ ja,
+ arr => arr.Insert(1, "new value"),
+ new List<Tuple<bool, JsonValue, JsonValueChangeEventArgs>>
+ {
+ new Tuple<bool, JsonValue, JsonValueChangeEventArgs>(true, ja, new JsonValueChangeEventArgs("new value", JsonValueChange.Add, 1)),
+ new Tuple<bool, JsonValue, JsonValueChangeEventArgs>(false, ja, new JsonValueChangeEventArgs("new value", JsonValueChange.Add, 1)),
+ });
+
+ TestEvents(
+ ja,
+ arr => arr.RemoveAt(1),
+ new List<Tuple<bool, JsonValue, JsonValueChangeEventArgs>>
+ {
+ new Tuple<bool, JsonValue, JsonValueChangeEventArgs>(true, ja, new JsonValueChangeEventArgs("new value", JsonValueChange.Remove, 1)),
+ new Tuple<bool, JsonValue, JsonValueChangeEventArgs>(false, ja, new JsonValueChangeEventArgs("new value", JsonValueChange.Remove, 1)),
+ });
+
+ TestEvents(
+ ja,
+ arr => arr.Clear(),
+ new List<Tuple<bool, JsonValue, JsonValueChangeEventArgs>>
+ {
+ new Tuple<bool, JsonValue, JsonValueChangeEventArgs>(true, ja, new JsonValueChangeEventArgs(null, JsonValueChange.Clear, 0)),
+ new Tuple<bool, JsonValue, JsonValueChangeEventArgs>(false, ja, new JsonValueChangeEventArgs(null, JsonValueChange.Clear, 0)),
+ });
+
+ ja = new JsonArray(1, 2, 3);
+ TestEvents(
+ ja,
+ arr => arr.Remove(new JsonPrimitive("Not there")),
+ new List<Tuple<bool, JsonValue, JsonValueChangeEventArgs>>());
+
+ JsonValue elementInArray = ja[1];
+ TestEvents(
+ ja,
+ arr => arr.Remove(elementInArray),
+ new List<Tuple<bool, JsonValue, JsonValueChangeEventArgs>>
+ {
+ new Tuple<bool, JsonValue, JsonValueChangeEventArgs>(true, ja, new JsonValueChangeEventArgs(elementInArray, JsonValueChange.Remove, 1)),
+ new Tuple<bool, JsonValue, JsonValueChangeEventArgs>(false, ja, new JsonValueChangeEventArgs(elementInArray, JsonValueChange.Remove, 1)),
+ });
+ }
+
+ [Fact]
+ public void NestedChangingEventTest()
+ {
+ JsonArray target = new JsonArray { new JsonArray { 1, 2 }, new JsonArray { 3, 4 } };
+ JsonArray child = target[1] as JsonArray;
+ TestEvents(
+ target,
+ arr => ((JsonArray)arr[1]).Add(5),
+ new List<Tuple<bool, JsonValue, JsonValueChangeEventArgs>>());
+
+ target = new JsonArray();
+ child = new JsonArray(1, 2);
+ TestEvents(
+ target,
+ arr =>
+ {
+ arr.Add(child);
+ ((JsonArray)arr[0]).Add(5);
+ },
+ new List<Tuple<bool, JsonValue, JsonValueChangeEventArgs>>
+ {
+ new Tuple<bool, JsonValue, JsonValueChangeEventArgs>(true, target, new JsonValueChangeEventArgs(child, JsonValueChange.Add, 0)),
+ new Tuple<bool, JsonValue, JsonValueChangeEventArgs>(false, target, new JsonValueChangeEventArgs(child, JsonValueChange.Add, 0)),
+ });
+ }
+
+ [Fact]
+ public void MultipleListenersTest()
+ {
+ for (int changingListeners = 0; changingListeners <= 2; changingListeners++)
+ {
+ for (int changedListeners = 0; changedListeners <= 2; changedListeners++)
+ {
+ MultipleListenersTestHelper<JsonArray>(
+ () => new JsonArray(1, 2),
+ delegate(JsonArray arr)
+ {
+ arr[1] = "hello";
+ arr.RemoveAt(0);
+ arr.Add("world");
+ arr.Clear();
+ },
+ new List<JsonValueChangeEventArgs>
+ {
+ new JsonValueChangeEventArgs("hello", JsonValueChange.Replace, 1),
+ new JsonValueChangeEventArgs(1, JsonValueChange.Remove, 0),
+ new JsonValueChangeEventArgs("world", JsonValueChange.Add, 1),
+ new JsonValueChangeEventArgs(null, JsonValueChange.Clear, 0),
+ },
+ new List<JsonValueChangeEventArgs>
+ {
+ new JsonValueChangeEventArgs(2, JsonValueChange.Replace, 1),
+ new JsonValueChangeEventArgs(1, JsonValueChange.Remove, 0),
+ new JsonValueChangeEventArgs("world", JsonValueChange.Add, 1),
+ new JsonValueChangeEventArgs(null, JsonValueChange.Clear, 0),
+ },
+ changingListeners,
+ changedListeners);
+ }
+ }
+ }
+
+ [Fact]
+ public void JsonTypeTest()
+ {
+ JsonArray target = AnyInstance.AnyJsonArray;
+ Assert.Equal(JsonType.Array, target.JsonType);
+ }
+
+ internal static void TestEvents<JsonValueType>(JsonValueType target, Action<JsonValueType> actionToTriggerEvent, List<Tuple<bool, JsonValue, JsonValueChangeEventArgs>> expectedEvents) where JsonValueType : JsonValue
+ {
+ List<Tuple<bool, JsonValue, JsonValueChangeEventArgs>> actualEvents = new List<Tuple<bool, JsonValue, JsonValueChangeEventArgs>>();
+ EventHandler<JsonValueChangeEventArgs> changingHandler = delegate(object sender, JsonValueChangeEventArgs e)
+ {
+ actualEvents.Add(new Tuple<bool, JsonValue, JsonValueChangeEventArgs>(true, sender as JsonValue, e));
+ };
+
+ EventHandler<JsonValueChangeEventArgs> changedHandler = delegate(object sender, JsonValueChangeEventArgs e)
+ {
+ actualEvents.Add(new Tuple<bool, JsonValue, JsonValueChangeEventArgs>(false, sender as JsonValue, e));
+ };
+
+ target.Changing += new EventHandler<JsonValueChangeEventArgs>(changingHandler);
+ target.Changed += new EventHandler<JsonValueChangeEventArgs>(changedHandler);
+
+ actionToTriggerEvent(target);
+
+ target.Changing -= new EventHandler<JsonValueChangeEventArgs>(changingHandler);
+ target.Changed -= new EventHandler<JsonValueChangeEventArgs>(changedHandler);
+
+ ValidateExpectedEvents(expectedEvents, actualEvents);
+ }
+
+ private static void TestEvents(JsonArray array, Action<JsonArray> actionToTriggerEvent, List<Tuple<bool, JsonValue, JsonValueChangeEventArgs>> expectedEvents)
+ {
+ TestEvents<JsonArray>(array, actionToTriggerEvent, expectedEvents);
+ }
+
+ private static void ValidateExpectedEvents(List<Tuple<bool, JsonValue, JsonValueChangeEventArgs>> expectedEvents, List<Tuple<bool, JsonValue, JsonValueChangeEventArgs>> actualEvents)
+ {
+ Assert.Equal(expectedEvents.Count, actualEvents.Count);
+ for (int i = 0; i < expectedEvents.Count; i++)
+ {
+ bool expectedIsChanging = expectedEvents[i].Item1;
+ bool actualIsChanging = expectedEvents[i].Item1;
+ Assert.Equal(expectedIsChanging, actualIsChanging);
+
+ JsonValue expectedSender = expectedEvents[i].Item2;
+ JsonValue actualSender = actualEvents[i].Item2;
+ Assert.Same(expectedSender, actualSender);
+
+ JsonValueChangeEventArgs expectedEventArgs = expectedEvents[i].Item3;
+ JsonValueChangeEventArgs actualEventArgs = actualEvents[i].Item3;
+ Assert.Equal(expectedEventArgs.Change, actualEventArgs.Change);
+ Assert.Equal(expectedEventArgs.Index, actualEventArgs.Index);
+ Assert.Equal(expectedEventArgs.Key, actualEventArgs.Key);
+
+ string expectedChild = expectedEventArgs.Child == null ? "null" : expectedEventArgs.Child.ToString();
+ string actualChild = actualEventArgs.Child == null ? "null" : actualEventArgs.Child.ToString();
+ Assert.Equal(expectedChild, actualChild);
+ }
+ }
+
+ internal static void MultipleListenersTestHelper<JsonValueType>(
+ Func<JsonValueType> createTarget,
+ Action<JsonValueType> actionToTriggerEvents,
+ List<JsonValueChangeEventArgs> expectedChangingEventArgs,
+ List<JsonValueChangeEventArgs> expectedChangedEventArgs,
+ int changingListeners,
+ int changedListeners) where JsonValueType : JsonValue
+ {
+ Console.WriteLine("Testing events on a {0} for {1} changING listeners and {2} changED listeners", typeof(JsonValueType).Name, changingListeners, changedListeners);
+ JsonValueType target = createTarget();
+ List<Tuple<bool, JsonValue, JsonValueChangeEventArgs>>[] actualChangingEvents = new List<Tuple<bool, JsonValue, JsonValueChangeEventArgs>>[changingListeners];
+ List<Tuple<bool, JsonValue, JsonValueChangeEventArgs>>[] actualChangedEvents = new List<Tuple<bool, JsonValue, JsonValueChangeEventArgs>>[changedListeners];
+ List<Tuple<bool, JsonValue, JsonValueChangeEventArgs>> expectedChangingEvents = new List<Tuple<bool, JsonValue, JsonValueChangeEventArgs>>(
+ expectedChangingEventArgs.Select((args) => new Tuple<bool, JsonValue, JsonValueChangeEventArgs>(true, target, args)));
+ List<Tuple<bool, JsonValue, JsonValueChangeEventArgs>> expectedChangedEvents = new List<Tuple<bool, JsonValue, JsonValueChangeEventArgs>>(
+ expectedChangedEventArgs.Select((args) => new Tuple<bool, JsonValue, JsonValueChangeEventArgs>(false, target, args)));
+
+ for (int i = 0; i < changingListeners; i++)
+ {
+ actualChangingEvents[i] = new List<Tuple<bool, JsonValue, JsonValueChangeEventArgs>>();
+ int index = i;
+ target.Changing += delegate(object sender, JsonValueChangeEventArgs e)
+ {
+ actualChangingEvents[index].Add(new Tuple<bool, JsonValue, JsonValueChangeEventArgs>(true, sender as JsonValue, e));
+ };
+ }
+
+ for (int i = 0; i < changedListeners; i++)
+ {
+ actualChangedEvents[i] = new List<Tuple<bool, JsonValue, JsonValueChangeEventArgs>>();
+ int index = i;
+ target.Changed += delegate(object sender, JsonValueChangeEventArgs e)
+ {
+ actualChangedEvents[index].Add(new Tuple<bool, JsonValue, JsonValueChangeEventArgs>(false, sender as JsonValue, e));
+ };
+ }
+
+ actionToTriggerEvents(target);
+
+ for (int i = 0; i < changingListeners; i++)
+ {
+ Console.WriteLine("Validating Changing events for listener {0}", i);
+ ValidateExpectedEvents(expectedChangingEvents, actualChangingEvents[i]);
+ }
+
+ for (int i = 0; i < changedListeners; i++)
+ {
+ Console.WriteLine("Validating Changed events for listener {0}", i);
+ ValidateExpectedEvents(expectedChangedEvents, actualChangedEvents[i]);
+ }
+ }
+
+ private static void ValidateJsonArrayItems(JsonArray jsonArray, IEnumerable<JsonValue> expectedItems)
+ {
+ List<JsonValue> expected = new List<JsonValue>(expectedItems);
+ Assert.Equal(expected.Count, jsonArray.Count);
+ for (int i = 0; i < expected.Count; i++)
+ {
+ Assert.Equal(expected[i], jsonArray[i]);
+ }
+ }
+ }
+}
diff --git a/test/System.Json.Test.Unit/JsonDefaultTest.cs b/test/System.Json.Test.Unit/JsonDefaultTest.cs
new file mode 100644
index 00000000..15549d66
--- /dev/null
+++ b/test/System.Json.Test.Unit/JsonDefaultTest.cs
@@ -0,0 +1,153 @@
+using System.IO;
+using System.Runtime.Serialization.Json;
+using Xunit;
+
+namespace System.Json
+{
+ public class JsonDefaultTest
+ {
+ const string IndexerNotSupportedMsgFormat = "'{0}' type indexer is not supported on JsonValue of 'JsonType.Default' type.";
+ const string OperationNotAllowedOnDefaultMsgFormat = "Operation not supported on JsonValue instances of 'JsonType.Default' type.";
+
+ [Fact]
+ public void PropertiesTest()
+ {
+ JsonValue target = AnyInstance.DefaultJsonValue;
+
+ Assert.Equal(JsonType.Default, target.JsonType);
+ Assert.Equal(0, target.Count);
+ Assert.Equal(false, target.ContainsKey("hello"));
+ Assert.Equal(false, target.ContainsKey(String.Empty));
+ }
+
+ [Fact]
+ public void SaveTest()
+ {
+ JsonValue target = AnyInstance.DefaultJsonValue;
+ using (MemoryStream ms = new MemoryStream())
+ {
+ ExceptionHelper.Throws<InvalidOperationException>(() => target.Save(ms), "Operation not supported on JsonValue instances of 'JsonType.Default' type.");
+ }
+ }
+
+ [Fact]
+ public void ToStringTest()
+ {
+ JsonValue target;
+
+ target = AnyInstance.DefaultJsonValue;
+ Assert.Equal(target.ToString(), "Default");
+ }
+
+ [Fact(Skip = "See bug #228569 in CSDMain")]
+ public void ReadAsTests()
+ {
+ JsonValue target = AnyInstance.DefaultJsonValue;
+ string typeName = target.GetType().FullName;
+
+ string errorMsgFormat = "Cannot read '{0}' as '{1}' type.";
+
+ ExceptionHelper.Throws<NotSupportedException>(delegate { target.ReadAs(typeof(bool)); }, String.Format(errorMsgFormat, typeName, typeof(bool)));
+ ExceptionHelper.Throws<NotSupportedException>(delegate { target.ReadAs(typeof(string)); }, String.Format(errorMsgFormat, typeName, typeof(string)));
+ ExceptionHelper.Throws<NotSupportedException>(delegate { target.ReadAs(typeof(JsonObject)); }, String.Format(errorMsgFormat, typeName, typeof(JsonObject)));
+
+ ExceptionHelper.Throws<NotSupportedException>(delegate { target.ReadAs<bool>(); }, String.Format(errorMsgFormat, typeName, typeof(bool)));
+ ExceptionHelper.Throws<NotSupportedException>(delegate { target.ReadAs<string>(); }, String.Format(errorMsgFormat, typeName, typeof(string)));
+ ExceptionHelper.Throws<NotSupportedException>(delegate { target.ReadAs<JsonObject>(); }, String.Format(errorMsgFormat, typeName, typeof(JsonObject)));
+
+ bool boolValue;
+ string stringValue;
+ JsonObject objValue;
+
+ object value;
+
+ Assert.False(target.TryReadAs(typeof(bool), out value), "TryReadAs expected to return false");
+ Assert.Null(value);
+
+ Assert.False(target.TryReadAs(typeof(string), out value), "TryReadAs expected to return false");
+ Assert.Null(value);
+
+ Assert.False(target.TryReadAs(typeof(JsonObject), out value), "TryReadAs expected to return false");
+ Assert.Null(value);
+
+ Assert.False(target.TryReadAs<bool>(out boolValue), "TryReadAs expected to return false");
+ Assert.False(boolValue);
+
+ Assert.False(target.TryReadAs<string>(out stringValue), "TryReadAs expected to return false");
+ Assert.Null(stringValue);
+
+ Assert.False(target.TryReadAs<JsonObject>(out objValue), "TryReadAs expected to return false");
+ Assert.Null(objValue);
+ }
+
+ [Fact]
+ public void ItemTests()
+ {
+ JsonValue target = AnyInstance.DefaultJsonValue;
+
+ ExceptionHelper.Throws<InvalidOperationException>(delegate { var v = target["MissingProperty"]; }, String.Format(IndexerNotSupportedMsgFormat, typeof(string).FullName));
+ ExceptionHelper.Throws<InvalidOperationException>(delegate { target["NewProperty"] = AnyInstance.AnyJsonValue1; }, String.Format(IndexerNotSupportedMsgFormat, typeof(string).FullName));
+ }
+
+ [Fact]
+ public void DynamicItemTests()
+ {
+ dynamic target = AnyInstance.DefaultJsonValue;
+
+ var getByKey = target["SomeKey"];
+ Assert.Same(getByKey, AnyInstance.DefaultJsonValue);
+
+ var getByIndex = target[10];
+ Assert.Same(getByIndex, AnyInstance.DefaultJsonValue);
+
+ ExceptionHelper.Throws<InvalidOperationException>(delegate { target["SomeKey"] = AnyInstance.AnyJsonObject; }, String.Format(IndexerNotSupportedMsgFormat, typeof(string).FullName));
+ ExceptionHelper.Throws<InvalidOperationException>(delegate { target[10] = AnyInstance.AnyJsonObject; }, String.Format(IndexerNotSupportedMsgFormat, typeof(int).FullName));
+ }
+
+ [Fact(Skip = "See bug #228569 in CSDMain")]
+ public void InvalidAssignmentValueTest()
+ {
+ JsonValue target;
+ JsonValue value = AnyInstance.DefaultJsonValue;
+
+ target = AnyInstance.AnyJsonArray;
+ ExceptionHelper.Throws<ArgumentException>(delegate { target[0] = value; }, OperationNotAllowedOnDefaultMsgFormat);
+
+ target = AnyInstance.AnyJsonObject;
+ ExceptionHelper.Throws<ArgumentException>(delegate { target["key"] = value; }, OperationNotAllowedOnDefaultMsgFormat);
+ }
+
+ [Fact]
+ public void DefaultConcatTest()
+ {
+ JsonValue jv = JsonValueExtensions.CreateFrom(AnyInstance.AnyPerson);
+ dynamic target = JsonValueExtensions.CreateFrom(AnyInstance.AnyPerson);
+ Person person = AnyInstance.AnyPerson;
+
+ Assert.Equal(JsonType.Default, target.Friends[100000].Name.JsonType);
+ Assert.Equal(JsonType.Default, target.Friends[0].Age.Minutes.JsonType);
+
+ JsonValue jv1 = target.MissingProperty as JsonValue;
+ Assert.NotNull(jv1);
+
+ JsonValue jv2 = target.MissingProperty1.MissingProperty2 as JsonValue;
+ Assert.NotNull(jv2);
+
+ Assert.Same(jv1, jv2);
+ Assert.Same(target.Person.Name.MissingProperty, AnyInstance.DefaultJsonValue);
+ }
+
+ [Fact]
+ public void CastingDefaultValueTest()
+ {
+ JsonValue jv = AnyInstance.DefaultJsonValue;
+ dynamic d = jv;
+
+ ExceptionHelper.Throws<InvalidCastException>(delegate { float p = (float)d; });
+ ExceptionHelper.Throws<InvalidCastException>(delegate { byte p = (byte)d; });
+ ExceptionHelper.Throws<InvalidCastException>(delegate { int p = (int)d; });
+
+ Assert.Null((string)d);
+ }
+ }
+}
diff --git a/test/System.Json.Test.Unit/JsonObjectTest.cs b/test/System.Json.Test.Unit/JsonObjectTest.cs
new file mode 100644
index 00000000..7f8bb4a4
--- /dev/null
+++ b/test/System.Json.Test.Unit/JsonObjectTest.cs
@@ -0,0 +1,787 @@
+using System.Collections;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Runtime.Serialization.Json;
+using Xunit;
+
+namespace System.Json
+{
+ public class JsonObjectTest
+ {
+ [Fact]
+ public void JsonObjectConstructorEnumTest()
+ {
+ string key1 = AnyInstance.AnyString;
+ string key2 = AnyInstance.AnyString2;
+ JsonValue value1 = AnyInstance.AnyJsonValue1;
+ JsonValue value2 = AnyInstance.AnyJsonValue2;
+
+ List<KeyValuePair<string, JsonValue>> items = new List<KeyValuePair<string, JsonValue>>()
+ {
+ new KeyValuePair<string, JsonValue>(key1, value1),
+ new KeyValuePair<string, JsonValue>(key2, value2),
+ };
+
+ JsonObject target = new JsonObject(null);
+ Assert.Equal(0, target.Count);
+
+ target = new JsonObject(items);
+ Assert.Equal(2, target.Count);
+ ValidateJsonObjectItems(target, key1, value1, key2, value2);
+
+ // Invalid tests
+ items.Add(new KeyValuePair<string, JsonValue>(key1, AnyInstance.DefaultJsonValue));
+ ExceptionHelper.Throws<ArgumentException>(delegate { new JsonObject(items); });
+ }
+
+ [Fact]
+ public void JsonObjectConstructorParmsTest()
+ {
+ JsonObject target = new JsonObject();
+ Assert.Equal(0, target.Count);
+
+ string key1 = AnyInstance.AnyString;
+ string key2 = AnyInstance.AnyString2;
+ JsonValue value1 = AnyInstance.AnyJsonValue1;
+ JsonValue value2 = AnyInstance.AnyJsonValue2;
+
+ List<KeyValuePair<string, JsonValue>> items = new List<KeyValuePair<string, JsonValue>>()
+ {
+ new KeyValuePair<string, JsonValue>(key1, value1),
+ new KeyValuePair<string, JsonValue>(key2, value2),
+ };
+
+ target = new JsonObject(items[0], items[1]);
+ Assert.Equal(2, target.Count);
+ ValidateJsonObjectItems(target, key1, value1, key2, value2);
+
+ target = new JsonObject(items.ToArray());
+ Assert.Equal(2, target.Count);
+ ValidateJsonObjectItems(target, key1, value1, key2, value2);
+
+ // Invalid tests
+ items.Add(new KeyValuePair<string, JsonValue>(key1, AnyInstance.DefaultJsonValue));
+ ExceptionHelper.Throws<ArgumentException>(delegate { new JsonObject(items[0], items[1], items[2]); });
+ ExceptionHelper.Throws<ArgumentException>(delegate { new JsonObject(items.ToArray()); });
+ }
+
+ [Fact]
+ public void AddTest()
+ {
+ string key1 = AnyInstance.AnyString;
+ string key2 = AnyInstance.AnyString2;
+ JsonValue value1 = AnyInstance.AnyJsonValue1;
+ JsonValue value2 = AnyInstance.AnyJsonValue2;
+
+ JsonObject target;
+
+ target = new JsonObject();
+ target.Add(new KeyValuePair<string, JsonValue>(key1, value1));
+ Assert.Equal(1, target.Count);
+ Assert.True(target.ContainsKey(key1));
+ Assert.Equal(value1, target[key1]);
+
+ target.Add(key2, value2);
+ Assert.Equal(2, target.Count);
+ Assert.True(target.ContainsKey(key2));
+ Assert.Equal(value2, target[key2]);
+
+ ExceptionHelper.Throws<ArgumentNullException>(delegate { new JsonObject().Add(null, value1); });
+ ExceptionHelper.Throws<ArgumentNullException>(delegate { new JsonObject().Add(new KeyValuePair<string, JsonValue>(null, value1)); });
+
+ ExceptionHelper.Throws<ArgumentException>(delegate { new JsonObject().Add(key1, AnyInstance.DefaultJsonValue); });
+ ExceptionHelper.Throws<ArgumentException>(delegate { new JsonArray().Add(AnyInstance.DefaultJsonValue); });
+ }
+
+ [Fact]
+ public void AddRangeParamsTest()
+ {
+ string key1 = AnyInstance.AnyString;
+ string key2 = AnyInstance.AnyString2;
+ JsonValue value1 = AnyInstance.AnyJsonValue1;
+ JsonValue value2 = AnyInstance.AnyJsonValue2;
+
+ List<KeyValuePair<string, JsonValue>> items = new List<KeyValuePair<string, JsonValue>>()
+ {
+ new KeyValuePair<string, JsonValue>(key1, value1),
+ new KeyValuePair<string, JsonValue>(key2, value2),
+ };
+
+ JsonObject target;
+
+ target = new JsonObject();
+ target.AddRange(items[0], items[1]);
+ Assert.Equal(2, target.Count);
+ ValidateJsonObjectItems(target, key1, value1, key2, value2);
+
+ target = new JsonObject();
+ target.AddRange(items.ToArray());
+ Assert.Equal(2, target.Count);
+ ValidateJsonObjectItems(target, key1, value1, key2, value2);
+
+ ExceptionHelper.Throws<ArgumentNullException>(delegate { new JsonObject().AddRange((KeyValuePair<string, JsonValue>[])null); });
+ ExceptionHelper.Throws<ArgumentNullException>(delegate { new JsonObject().AddRange((IEnumerable<KeyValuePair<string, JsonValue>>)null); });
+
+ items[1] = new KeyValuePair<string, JsonValue>(key2, AnyInstance.DefaultJsonValue);
+ ExceptionHelper.Throws<ArgumentException>(delegate { new JsonObject().AddRange(items.ToArray()); });
+ ExceptionHelper.Throws<ArgumentException>(delegate { new JsonObject().AddRange(items[0], items[1]); });
+ }
+
+ [Fact]
+ public void AddRangeEnumTest()
+ {
+ string key1 = AnyInstance.AnyString;
+ string key2 = AnyInstance.AnyString2;
+ JsonValue value1 = AnyInstance.AnyJsonValue1;
+ JsonValue value2 = AnyInstance.AnyJsonValue2;
+
+ List<KeyValuePair<string, JsonValue>> items = new List<KeyValuePair<string, JsonValue>>()
+ {
+ new KeyValuePair<string, JsonValue>(key1, value1),
+ new KeyValuePair<string, JsonValue>(key2, value2),
+ };
+
+ JsonObject target;
+
+ target = new JsonObject();
+ target.AddRange(items);
+ Assert.Equal(2, target.Count);
+ ValidateJsonObjectItems(target, key1, value1, key2, value2);
+
+ ExceptionHelper.Throws<ArgumentNullException>(delegate { new JsonObject().AddRange(null); });
+
+ items[1] = new KeyValuePair<string, JsonValue>(key2, AnyInstance.DefaultJsonValue);
+ ExceptionHelper.Throws<ArgumentException>(delegate { new JsonObject().AddRange(items); });
+ }
+
+ [Fact]
+ public void ClearTest()
+ {
+ string key1 = AnyInstance.AnyString;
+ string key2 = AnyInstance.AnyString2;
+ JsonValue value1 = AnyInstance.AnyJsonValue1;
+ JsonValue value2 = AnyInstance.AnyJsonValue2;
+
+ JsonObject target = new JsonObject();
+ target.Add(key1, value1);
+ target.Clear();
+ Assert.Equal(0, target.Count);
+ Assert.False(target.ContainsKey(key1));
+
+ target.Add(key2, value2);
+ Assert.Equal(1, target.Count);
+ Assert.False(target.ContainsKey(key1));
+ Assert.True(target.ContainsKey(key2));
+ }
+
+ [Fact]
+ public void ContainsKeyTest()
+ {
+ string key1 = AnyInstance.AnyString;
+ JsonValue value1 = AnyInstance.AnyJsonValue1;
+
+ JsonObject target = new JsonObject();
+ Assert.False(target.ContainsKey(key1));
+ target.Add(key1, value1);
+ Assert.True(target.ContainsKey(key1));
+ target.Clear();
+ Assert.False(target.ContainsKey(key1));
+
+ ExceptionHelper.Throws<ArgumentNullException>(delegate { new JsonObject().ContainsKey(null); });
+ }
+
+ [Fact]
+ public void CopyToTest()
+ {
+ string key1 = AnyInstance.AnyString;
+ string key2 = AnyInstance.AnyString2;
+ JsonValue value1 = AnyInstance.AnyJsonValue1;
+ JsonValue value2 = AnyInstance.AnyJsonValue2;
+
+ JsonObject target = new JsonObject { { key1, value1 }, { key2, value2 } };
+
+ KeyValuePair<string, JsonValue>[] array = new KeyValuePair<string, JsonValue>[target.Count + 1];
+
+ target.CopyTo(array, 1);
+ int index1 = key1 == array[1].Key ? 1 : 2;
+ int index2 = index1 == 1 ? 2 : 1;
+
+ Assert.Equal(key1, array[index1].Key);
+ Assert.Equal(value1, array[index1].Value);
+ Assert.Equal(key2, array[index2].Key);
+ Assert.Equal(value2, array[index2].Value);
+
+ ExceptionHelper.Throws<ArgumentNullException>(() => target.CopyTo(null, 0));
+ ExceptionHelper.Throws<ArgumentOutOfRangeException>(() => target.CopyTo(array, -1));
+ ExceptionHelper.Throws<ArgumentException>(() => target.CopyTo(array, array.Length - target.Count + 1));
+ }
+
+ [Fact]
+ public void CreateFromComplexTypeTest()
+ {
+ Assert.Null(JsonValueExtensions.CreateFrom(null));
+
+ Person anyObject = AnyInstance.AnyPerson;
+
+ JsonObject jv = JsonValueExtensions.CreateFrom(anyObject) as JsonObject;
+ Assert.NotNull(jv);
+ Assert.Equal(4, jv.Count);
+ foreach (string key in "Name Age Address".Split())
+ {
+ Assert.True(jv.ContainsKey(key));
+ }
+
+ Assert.Equal(AnyInstance.AnyString, (string)jv["Name"]);
+ Assert.Equal(AnyInstance.AnyInt, (int)jv["Age"]);
+
+ JsonObject nestedObject = jv["Address"] as JsonObject;
+ Assert.NotNull(nestedObject);
+ Assert.Equal(3, nestedObject.Count);
+ foreach (string key in "Street City State".Split())
+ {
+ Assert.True(nestedObject.ContainsKey(key));
+ }
+
+ Assert.Equal(Address.AnyStreet, (string)nestedObject["Street"]);
+ Assert.Equal(Address.AnyCity, (string)nestedObject["City"]);
+ Assert.Equal(Address.AnyState, (string)nestedObject["State"]);
+ }
+
+ [Fact]
+ public void ReadAsComplexTypeTest()
+ {
+ JsonObject target = new JsonObject
+ {
+ { "Name", AnyInstance.AnyString },
+ { "Age", AnyInstance.AnyInt },
+ { "Address", new JsonObject { { "Street", Address.AnyStreet }, { "City", Address.AnyCity }, { "State", Address.AnyState } } },
+ };
+
+ Person person = target.ReadAsType<Person>();
+ Assert.Equal(AnyInstance.AnyString, person.Name);
+ Assert.Equal(AnyInstance.AnyInt, person.Age);
+ Assert.NotNull(person.Address);
+ Assert.Equal(Address.AnyStreet, person.Address.Street);
+ Assert.Equal(Address.AnyCity, person.Address.City);
+ Assert.Equal(Address.AnyState, person.Address.State);
+ }
+
+ [Fact]
+ public void GetEnumeratorTest()
+ {
+ string key1 = AnyInstance.AnyString;
+ string key2 = AnyInstance.AnyString2;
+ JsonValue value1 = AnyInstance.AnyJsonValue1;
+ JsonValue value2 = AnyInstance.AnyJsonValue2;
+
+ JsonObject target = new JsonObject { { key1, value1 }, { key2, value2 } };
+
+ IEnumerator<KeyValuePair<string, JsonValue>> enumerator = target.GetEnumerator();
+ Assert.True(enumerator.MoveNext());
+ bool key1IsFirst = key1 == enumerator.Current.Key;
+ if (key1IsFirst)
+ {
+ Assert.Equal(key1, enumerator.Current.Key);
+ Assert.Equal(value1, enumerator.Current.Value);
+ }
+ else
+ {
+ Assert.Equal(key2, enumerator.Current.Key);
+ Assert.Equal(value2, enumerator.Current.Value);
+ }
+
+ Assert.True(enumerator.MoveNext());
+ if (key1IsFirst)
+ {
+ Assert.Equal(key2, enumerator.Current.Key);
+ Assert.Equal(value2, enumerator.Current.Value);
+ }
+ else
+ {
+ Assert.Equal(key1, enumerator.Current.Key);
+ Assert.Equal(value1, enumerator.Current.Value);
+ }
+
+ Assert.False(enumerator.MoveNext());
+ }
+
+ [Fact]
+ public void RemoveTest()
+ {
+ string key1 = AnyInstance.AnyString;
+ string key2 = AnyInstance.AnyString2;
+ JsonValue value1 = AnyInstance.AnyJsonValue1;
+ JsonValue value2 = AnyInstance.AnyJsonValue2;
+
+ JsonObject target = new JsonObject { { key1, value1 }, { key2, value2 } };
+
+ Assert.True(target.ContainsKey(key1));
+ Assert.True(target.ContainsKey(key2));
+ Assert.Equal(2, target.Count);
+
+ Assert.True(target.Remove(key2));
+ Assert.True(target.ContainsKey(key1));
+ Assert.False(target.ContainsKey(key2));
+ Assert.Equal(1, target.Count);
+
+ Assert.False(target.Remove(key2));
+ Assert.True(target.ContainsKey(key1));
+ Assert.False(target.ContainsKey(key2));
+ Assert.Equal(1, target.Count);
+ }
+
+ [Fact]
+ public void ToStringTest()
+ {
+ JsonObject target = new JsonObject();
+
+ JsonValue item1 = AnyInstance.AnyJsonValue1 ?? "not null";
+ JsonValue item2 = null;
+ JsonValue item3 = AnyInstance.AnyJsonValue2 ?? "not null";
+ JsonValue item4 = AnyInstance.AnyJsonValue3 ?? "not null";
+ target.Add("item1", item1);
+ target.Add("item2", item2);
+ target.Add("item3", item3);
+ target.Add("", item4);
+
+ string expected = String.Format(CultureInfo.InvariantCulture, "{{\"item1\":{0},\"item2\":null,\"item3\":{1},\"\":{2}}}", item1.ToString(), item3.ToString(), item4.ToString());
+ Assert.Equal<string>(expected, target.ToString());
+
+ string json = "{\r\n \"item1\": \"hello\",\r\n \"item2\": null,\r\n \"item3\": [\r\n 1,\r\n 2,\r\n 3\r\n ],\r\n \"\": \"notnull\"\r\n}";
+ target = JsonValue.Parse(json) as JsonObject;
+
+ Assert.Equal<string>(json.Replace("\r\n", "").Replace(" ", ""), target.ToString());
+ }
+
+ [Fact]
+ public void ContainsKVPTest()
+ {
+ JsonObject target = new JsonObject();
+ KeyValuePair<string, JsonValue> item = new KeyValuePair<string, JsonValue>(AnyInstance.AnyString, AnyInstance.AnyJsonValue1);
+ KeyValuePair<string, JsonValue> item2 = new KeyValuePair<string, JsonValue>(AnyInstance.AnyString2, AnyInstance.AnyJsonValue2);
+ target.Add(item);
+ Assert.True(((ICollection<KeyValuePair<string, JsonValue>>)target).Contains(item));
+ Assert.False(((ICollection<KeyValuePair<string, JsonValue>>)target).Contains(item2));
+ }
+
+ [Fact]
+ public void RemoveKVPTest()
+ {
+ JsonObject target = new JsonObject();
+ KeyValuePair<string, JsonValue> item1 = new KeyValuePair<string, JsonValue>(AnyInstance.AnyString, AnyInstance.AnyJsonValue1);
+ KeyValuePair<string, JsonValue> item2 = new KeyValuePair<string, JsonValue>(AnyInstance.AnyString2, AnyInstance.AnyJsonValue2);
+ target.AddRange(item1, item2);
+
+ Assert.Equal(2, target.Count);
+ Assert.True(((ICollection<KeyValuePair<string, JsonValue>>)target).Contains(item1));
+ Assert.True(((ICollection<KeyValuePair<string, JsonValue>>)target).Contains(item2));
+
+ Assert.True(((ICollection<KeyValuePair<string, JsonValue>>)target).Remove(item1));
+ Assert.Equal(1, target.Count);
+ Assert.False(((ICollection<KeyValuePair<string, JsonValue>>)target).Contains(item1));
+ Assert.True(((ICollection<KeyValuePair<string, JsonValue>>)target).Contains(item2));
+
+ Assert.False(((ICollection<KeyValuePair<string, JsonValue>>)target).Remove(item1));
+ Assert.Equal(1, target.Count);
+ Assert.False(((ICollection<KeyValuePair<string, JsonValue>>)target).Contains(item1));
+ Assert.True(((ICollection<KeyValuePair<string, JsonValue>>)target).Contains(item2));
+ }
+
+ [Fact]
+ public void GetEnumeratorTest1()
+ {
+ string key1 = AnyInstance.AnyString;
+ string key2 = AnyInstance.AnyString2;
+ JsonValue value1 = AnyInstance.AnyJsonValue1;
+ JsonValue value2 = AnyInstance.AnyJsonValue2;
+
+ JsonObject target = new JsonObject { { key1, value1 }, { key2, value2 } };
+
+ IEnumerator enumerator = ((IEnumerable)target).GetEnumerator();
+ Assert.True(enumerator.MoveNext());
+ Assert.IsType<KeyValuePair<string, JsonValue>>(enumerator.Current);
+ KeyValuePair<string, JsonValue> current = (KeyValuePair<string, JsonValue>)enumerator.Current;
+
+ bool key1IsFirst = key1 == current.Key;
+ if (key1IsFirst)
+ {
+ Assert.Equal(key1, current.Key);
+ Assert.Equal(value1, current.Value);
+ }
+ else
+ {
+ Assert.Equal(key2, current.Key);
+ Assert.Equal(value2, current.Value);
+ }
+
+ Assert.True(enumerator.MoveNext());
+ Assert.IsType<KeyValuePair<string, JsonValue>>(enumerator.Current);
+ current = (KeyValuePair<string, JsonValue>)enumerator.Current;
+ if (key1IsFirst)
+ {
+ Assert.Equal(key2, current.Key);
+ Assert.Equal(value2, current.Value);
+ }
+ else
+ {
+ Assert.Equal(key1, current.Key);
+ Assert.Equal(value1, current.Value);
+ }
+
+ Assert.False(enumerator.MoveNext());
+ }
+
+ [Fact]
+ public void TryGetValueTest()
+ {
+ string key1 = AnyInstance.AnyString;
+ string key2 = AnyInstance.AnyString2;
+ JsonValue value1 = AnyInstance.AnyJsonValue1;
+ JsonValue value2 = AnyInstance.AnyJsonValue2;
+
+ JsonObject target = new JsonObject { { key1, value1 }, { key2, value2 } };
+
+ JsonValue value;
+ Assert.True(target.TryGetValue(key2, out value));
+ Assert.Equal(value2, value);
+
+ Assert.False(target.TryGetValue("not a key", out value));
+ Assert.Null(value);
+ }
+
+ [Fact]
+ public void GetValueOrDefaultTest()
+ {
+ bool boolValue;
+ JsonValue target;
+ JsonValue jsonValue;
+
+ Person person = AnyInstance.AnyPerson;
+ JsonObject jo = JsonValueExtensions.CreateFrom(person) as JsonObject;
+ Assert.Equal<int>(person.Age, jo.ValueOrDefault("Age").ReadAs<int>()); // JsonPrimitive
+
+ Assert.Equal<string>(person.Address.ToString(), jo.ValueOrDefault("Address").ReadAsType<Address>().ToString()); // JsonObject
+ Assert.Equal<int>(person.Friends.Count, jo.ValueOrDefault("Friends").Count); // JsonArray
+
+ target = jo.ValueOrDefault("Address").ValueOrDefault("City"); // JsonPrimitive
+ Assert.NotNull(target);
+ Assert.Equal<string>(person.Address.City, target.ReadAs<string>());
+
+ target = jo.ValueOrDefault("Address", "City"); // JsonPrimitive
+ Assert.NotNull(target);
+ Assert.Equal<string>(person.Address.City, target.ReadAs<string>());
+
+ target = jo.ValueOrDefault("Address").ValueOrDefault("NonExistentProp").ValueOrDefault("NonExistentProp2"); // JsonObject
+ Assert.Equal(JsonType.Default, target.JsonType);
+ Assert.NotNull(target);
+ Assert.False(target.TryReadAs<bool>(out boolValue));
+ Assert.True(target.TryReadAs<JsonValue>(out jsonValue));
+
+ target = jo.ValueOrDefault("Address", "NonExistentProp", "NonExistentProp2"); // JsonObject
+ Assert.Equal(JsonType.Default, target.JsonType);
+ Assert.NotNull(target);
+ Assert.False(target.TryReadAs<bool>(out boolValue));
+ Assert.True(target.TryReadAs<JsonValue>(out jsonValue));
+ Assert.Same(target, jsonValue);
+ }
+
+ [Fact]
+ public void CountTest()
+ {
+ string key1 = AnyInstance.AnyString;
+ string key2 = AnyInstance.AnyString2;
+ JsonValue value1 = AnyInstance.AnyJsonValue1;
+ JsonValue value2 = AnyInstance.AnyJsonValue2;
+
+ JsonObject target = new JsonObject();
+ Assert.Equal(0, target.Count);
+ target.Add(key1, value1);
+ Assert.Equal(1, target.Count);
+ target.Add(key2, value2);
+ Assert.Equal(2, target.Count);
+ target.Remove(key2);
+ Assert.Equal(1, target.Count);
+ }
+
+ [Fact]
+ public void ItemTest()
+ {
+ string key1 = AnyInstance.AnyString;
+ string key2 = AnyInstance.AnyString2;
+ JsonValue value1 = AnyInstance.AnyJsonValue1;
+ JsonValue value2 = AnyInstance.AnyJsonValue2;
+ JsonValue value3 = AnyInstance.AnyJsonValue3;
+
+ JsonObject target;
+
+ target = new JsonObject { { key1, value1 }, { key2, value2 } };
+ Assert.Equal(value1, target[key1]);
+ Assert.Equal(value2, target[key2]);
+ target[key1] = value3;
+ Assert.Equal(value3, target[key1]);
+ Assert.Equal(value2, target[key2]);
+
+ ExceptionHelper.Throws<KeyNotFoundException>(delegate { var o = target["not a key"]; });
+ ExceptionHelper.Throws<ArgumentNullException>(delegate { var o = target[null]; });
+ ExceptionHelper.Throws<ArgumentNullException>(delegate { target[null] = 123; });
+ ExceptionHelper.Throws<ArgumentException>(delegate { target[key1] = AnyInstance.DefaultJsonValue; });
+ }
+
+ [Fact]
+ public void ChangingEventsTest()
+ {
+ const string key1 = "first";
+ const string key2 = "second";
+ const string key3 = "third";
+ const string key4 = "fourth";
+ const string key5 = "fifth";
+ JsonObject jo = new JsonObject
+ {
+ { key1, AnyInstance.AnyString },
+ { key2, AnyInstance.AnyBool },
+ { key3, null },
+ };
+
+ TestEvents(
+ jo,
+ obj => obj.Add(key4, 1),
+ new List<Tuple<bool, JsonValue, JsonValueChangeEventArgs>>
+ {
+ new Tuple<bool, JsonValue, JsonValueChangeEventArgs>(true, jo, new JsonValueChangeEventArgs(1, JsonValueChange.Add, key4)),
+ new Tuple<bool, JsonValue, JsonValueChangeEventArgs>(false, jo, new JsonValueChangeEventArgs(1, JsonValueChange.Add, key4)),
+ });
+
+ TestEvents(
+ jo,
+ obj => obj[key2] = 2,
+ new List<Tuple<bool, JsonValue, JsonValueChangeEventArgs>>
+ {
+ new Tuple<bool, JsonValue, JsonValueChangeEventArgs>(true, jo, new JsonValueChangeEventArgs(2, JsonValueChange.Replace, key2)),
+ new Tuple<bool, JsonValue, JsonValueChangeEventArgs>(false, jo, new JsonValueChangeEventArgs(AnyInstance.AnyBool, JsonValueChange.Replace, key2)),
+ });
+
+ TestEvents(
+ jo,
+ obj => obj[key5] = 3,
+ new List<Tuple<bool, JsonValue, JsonValueChangeEventArgs>>
+ {
+ new Tuple<bool, JsonValue, JsonValueChangeEventArgs>(true, jo, new JsonValueChangeEventArgs(3, JsonValueChange.Add, key5)),
+ new Tuple<bool, JsonValue, JsonValueChangeEventArgs>(false, jo, new JsonValueChangeEventArgs(3, JsonValueChange.Add, key5)),
+ });
+
+ jo.Remove(key4);
+ jo.Remove(key5);
+
+ TestEvents(
+ jo,
+ obj => obj.AddRange(new JsonObject { { key4, AnyInstance.AnyString }, { key5, AnyInstance.AnyDouble } }),
+ new List<Tuple<bool, JsonValue, JsonValueChangeEventArgs>>
+ {
+ new Tuple<bool, JsonValue, JsonValueChangeEventArgs>(true, jo, new JsonValueChangeEventArgs(AnyInstance.AnyString, JsonValueChange.Add, key4)),
+ new Tuple<bool, JsonValue, JsonValueChangeEventArgs>(true, jo, new JsonValueChangeEventArgs(AnyInstance.AnyDouble, JsonValueChange.Add, key5)),
+ new Tuple<bool, JsonValue, JsonValueChangeEventArgs>(false, jo, new JsonValueChangeEventArgs(AnyInstance.AnyString, JsonValueChange.Add, key4)),
+ new Tuple<bool, JsonValue, JsonValueChangeEventArgs>(false, jo, new JsonValueChangeEventArgs(AnyInstance.AnyDouble, JsonValueChange.Add, key5)),
+ });
+
+ TestEvents(
+ jo,
+ obj => obj.Remove(key5),
+ new List<Tuple<bool, JsonValue, JsonValueChangeEventArgs>>
+ {
+ new Tuple<bool, JsonValue, JsonValueChangeEventArgs>(true, jo, new JsonValueChangeEventArgs(AnyInstance.AnyDouble, JsonValueChange.Remove, key5)),
+ new Tuple<bool, JsonValue, JsonValueChangeEventArgs>(false, jo, new JsonValueChangeEventArgs(AnyInstance.AnyDouble, JsonValueChange.Remove, key5)),
+ });
+
+ TestEvents(
+ jo,
+ obj => obj.Remove("not there"),
+ new List<Tuple<bool, JsonValue, JsonValueChangeEventArgs>>());
+
+ jo = new JsonObject { { key1, 1 }, { key2, 2 }, { key3, 3 } };
+
+ TestEvents(
+ jo,
+ obj => obj.Clear(),
+ new List<Tuple<bool, JsonValue, JsonValueChangeEventArgs>>
+ {
+ new Tuple<bool, JsonValue, JsonValueChangeEventArgs>(true, jo, new JsonValueChangeEventArgs(null, JsonValueChange.Clear, null)),
+ new Tuple<bool, JsonValue, JsonValueChangeEventArgs>(false, jo, new JsonValueChangeEventArgs(null, JsonValueChange.Clear, null)),
+ });
+
+ jo = new JsonObject { { key1, 1 }, { key2, 2 }, { key3, 3 } };
+ TestEvents(
+ jo,
+ obj => ((IDictionary<string, JsonValue>)obj).Remove(new KeyValuePair<string, JsonValue>(key2, jo[key2])),
+ new List<Tuple<bool, JsonValue, JsonValueChangeEventArgs>>
+ {
+ new Tuple<bool, JsonValue, JsonValueChangeEventArgs>(true, jo, new JsonValueChangeEventArgs(2, JsonValueChange.Remove, key2)),
+ new Tuple<bool, JsonValue, JsonValueChangeEventArgs>(false, jo, new JsonValueChangeEventArgs(2, JsonValueChange.Remove, key2)),
+ });
+
+ TestEvents(
+ jo,
+ obj => ((IDictionary<string, JsonValue>)obj).Remove(new KeyValuePair<string, JsonValue>("key not in object", jo[key1])),
+ new List<Tuple<bool, JsonValue, JsonValueChangeEventArgs>>
+ {
+ });
+
+ TestEvents(
+ jo,
+ obj => ((IDictionary<string, JsonValue>)obj).Remove(new KeyValuePair<string, JsonValue>(key1, "different object")),
+ new List<Tuple<bool, JsonValue, JsonValueChangeEventArgs>>
+ {
+ });
+
+ ExceptionHelper.Throws<ArgumentNullException>(() => new JsonValueChangeEventArgs(1, JsonValueChange.Add, null));
+ }
+
+ [Fact]
+ public void NestedChangingEventTest()
+ {
+ const string key1 = "first";
+
+ JsonObject target = new JsonObject { { key1, new JsonArray { 1, 2 } } };
+ JsonArray child = target[key1] as JsonArray;
+ TestEvents(
+ target,
+ obj => ((JsonArray)obj[key1]).Add(5),
+ new List<Tuple<bool, JsonValue, JsonValueChangeEventArgs>>());
+
+ target = new JsonObject();
+ child = new JsonArray(1, 2);
+ TestEvents(
+ target,
+ obj =>
+ {
+ obj.Add(key1, child);
+ ((JsonArray)obj[key1]).Add(5);
+ },
+ new List<Tuple<bool, JsonValue, JsonValueChangeEventArgs>>
+ {
+ new Tuple<bool, JsonValue, JsonValueChangeEventArgs>(true, target, new JsonValueChangeEventArgs(child, JsonValueChange.Add, key1)),
+ new Tuple<bool, JsonValue, JsonValueChangeEventArgs>(false, target, new JsonValueChangeEventArgs(child, JsonValueChange.Add, key1)),
+ });
+ }
+
+ [Fact]
+ public void MultipleListenersTest()
+ {
+ const string key1 = "first";
+ const string key2 = "second";
+ const string key3 = "third";
+
+ for (int changingListeners = 0; changingListeners <= 2; changingListeners++)
+ {
+ for (int changedListeners = 0; changedListeners <= 2; changedListeners++)
+ {
+ JsonArrayTest.MultipleListenersTestHelper<JsonObject>(
+ () => new JsonObject { { key1, 1 }, { key2, 2 } },
+ delegate(JsonObject obj)
+ {
+ obj[key2] = "hello";
+ obj.Remove(key1);
+ obj.Add(key3, "world");
+ obj.Clear();
+ },
+ new List<JsonValueChangeEventArgs>
+ {
+ new JsonValueChangeEventArgs("hello", JsonValueChange.Replace, key2),
+ new JsonValueChangeEventArgs(1, JsonValueChange.Remove, key1),
+ new JsonValueChangeEventArgs("world", JsonValueChange.Add, key3),
+ new JsonValueChangeEventArgs(null, JsonValueChange.Clear, null),
+ },
+ new List<JsonValueChangeEventArgs>
+ {
+ new JsonValueChangeEventArgs(2, JsonValueChange.Replace, key2),
+ new JsonValueChangeEventArgs(1, JsonValueChange.Remove, key1),
+ new JsonValueChangeEventArgs("world", JsonValueChange.Add, key3),
+ new JsonValueChangeEventArgs(null, JsonValueChange.Clear, null),
+ },
+ changingListeners,
+ changedListeners);
+ }
+ }
+ }
+
+ [Fact]
+ public void JsonTypeTest()
+ {
+ JsonObject target = AnyInstance.AnyJsonObject;
+ Assert.Equal(JsonType.Object, target.JsonType);
+ }
+
+ [Fact]
+ public void KeysTest()
+ {
+ string key1 = AnyInstance.AnyString;
+ string key2 = AnyInstance.AnyString2;
+ JsonValue value1 = AnyInstance.AnyJsonValue1;
+ JsonValue value2 = AnyInstance.AnyJsonValue2;
+
+ JsonObject target = new JsonObject { { key1, value1 }, { key2, value2 } };
+
+ List<string> expected = new List<string> { key1, key2 };
+ List<string> actual = new List<string>(target.Keys);
+
+ Assert.Equal(expected.Count, actual.Count);
+
+ expected.Sort();
+ actual.Sort();
+ for (int i = 0; i < expected.Count; i++)
+ {
+ Assert.Equal(expected[i], actual[i]);
+ }
+ }
+
+ [Fact]
+ public void IsReadOnlyTest()
+ {
+ JsonObject target = AnyInstance.AnyJsonObject;
+ Assert.False(((ICollection<KeyValuePair<string, JsonValue>>)target).IsReadOnly);
+ }
+
+ [Fact]
+ public void ValuesTest()
+ {
+ string key1 = AnyInstance.AnyString;
+ string key2 = AnyInstance.AnyString2;
+ JsonValue value1 = AnyInstance.AnyJsonValue1;
+ JsonValue value2 = AnyInstance.AnyJsonValue2;
+
+ JsonObject target = new JsonObject { { key1, value1 }, { key2, value2 } };
+
+ List<JsonValue> values = new List<JsonValue>(target.Values);
+ Assert.Equal(2, values.Count);
+ bool value1IsFirst = value1 == values[0];
+ Assert.True(value1IsFirst || value1 == values[1]);
+ Assert.Equal(value2, values[value1IsFirst ? 1 : 0]);
+ }
+
+ private static void ValidateJsonObjectItems(JsonObject jsonObject, params object[] keyValuePairs)
+ {
+ Dictionary<string, JsonValue> expected = new Dictionary<string, JsonValue>();
+ Assert.True((keyValuePairs.Length % 2) == 0, "Test error");
+ for (int i = 0; i < keyValuePairs.Length; i += 2)
+ {
+ Assert.IsType<String>(keyValuePairs[i]);
+ Assert.IsAssignableFrom<JsonValue>(keyValuePairs[i + 1]);
+ expected.Add((string)keyValuePairs[i], (JsonValue)keyValuePairs[i + 1]);
+ }
+ }
+
+ private static void ValidateJsonObjectItems(JsonObject jsonObject, Dictionary<string, JsonValue> expected)
+ {
+ Assert.Equal(expected.Count, jsonObject.Count);
+ foreach (string key in expected.Keys)
+ {
+ Assert.True(jsonObject.ContainsKey(key));
+ Assert.Equal(expected[key], jsonObject[key]);
+ }
+ }
+
+ private static void TestEvents(JsonObject obj, Action<JsonObject> actionToTriggerEvent, List<Tuple<bool, JsonValue, JsonValueChangeEventArgs>> expectedEvents)
+ {
+ JsonArrayTest.TestEvents<JsonObject>(obj, actionToTriggerEvent, expectedEvents);
+ }
+ }
+}
diff --git a/test/System.Json.Test.Unit/JsonPrimitiveTest.cs b/test/System.Json.Test.Unit/JsonPrimitiveTest.cs
new file mode 100644
index 00000000..225d17cf
--- /dev/null
+++ b/test/System.Json.Test.Unit/JsonPrimitiveTest.cs
@@ -0,0 +1,410 @@
+using System.Collections.Generic;
+using System.Globalization;
+using System.IO;
+using System.Runtime.Serialization.Json;
+using System.Text;
+using Xunit;
+
+namespace System.Json
+{
+ public class JsonPrimitiveTest
+ {
+ const string DateTimeFormat = "yyyy-MM-ddTHH:mm:ss.fffK";
+
+ [Fact]
+ public void JsonPrimitiveConstructorTest()
+ {
+ Assert.Equal(AnyInstance.AnyString, (string)(new JsonPrimitive(AnyInstance.AnyString)));
+ Assert.Equal(AnyInstance.AnyChar, (char)(new JsonPrimitive(AnyInstance.AnyChar)));
+ Assert.Equal(AnyInstance.AnyUri, (Uri)(new JsonPrimitive(AnyInstance.AnyUri)));
+ Assert.Equal(AnyInstance.AnyGuid, (Guid)(new JsonPrimitive(AnyInstance.AnyGuid)));
+ Assert.Equal(AnyInstance.AnyDateTime, (DateTime)(new JsonPrimitive(AnyInstance.AnyDateTime)));
+ Assert.Equal(AnyInstance.AnyDateTimeOffset, (DateTimeOffset)(new JsonPrimitive(AnyInstance.AnyDateTimeOffset)));
+ Assert.Equal(AnyInstance.AnyBool, (bool)(new JsonPrimitive(AnyInstance.AnyBool)));
+ Assert.Equal(AnyInstance.AnyByte, (byte)(new JsonPrimitive(AnyInstance.AnyByte)));
+ Assert.Equal(AnyInstance.AnyShort, (short)(new JsonPrimitive(AnyInstance.AnyShort)));
+ Assert.Equal(AnyInstance.AnyInt, (int)(new JsonPrimitive(AnyInstance.AnyInt)));
+ Assert.Equal(AnyInstance.AnyLong, (long)(new JsonPrimitive(AnyInstance.AnyLong)));
+ Assert.Equal(AnyInstance.AnySByte, (sbyte)(new JsonPrimitive(AnyInstance.AnySByte)));
+ Assert.Equal(AnyInstance.AnyUShort, (ushort)(new JsonPrimitive(AnyInstance.AnyUShort)));
+ Assert.Equal(AnyInstance.AnyUInt, (uint)(new JsonPrimitive(AnyInstance.AnyUInt)));
+ Assert.Equal(AnyInstance.AnyULong, (ulong)(new JsonPrimitive(AnyInstance.AnyULong)));
+ Assert.Equal(AnyInstance.AnyDecimal, (decimal)(new JsonPrimitive(AnyInstance.AnyDecimal)));
+ Assert.Equal(AnyInstance.AnyFloat, (float)(new JsonPrimitive(AnyInstance.AnyFloat)));
+ Assert.Equal(AnyInstance.AnyDouble, (double)(new JsonPrimitive(AnyInstance.AnyDouble)));
+ }
+
+ [Fact]
+ public void ValueTest()
+ {
+ object[] values =
+ {
+ AnyInstance.AnyInt, AnyInstance.AnyString, AnyInstance.AnyGuid, AnyInstance.AnyDecimal, AnyInstance.AnyBool, AnyInstance.AnyDateTime
+ };
+
+ foreach (object value in values)
+ {
+ JsonPrimitive jp;
+ bool success = JsonPrimitive.TryCreate(value, out jp);
+ Assert.True(success);
+ Assert.Equal(value, jp.Value);
+ }
+ }
+
+ [Fact]
+ public void TryCreateTest()
+ {
+ object[] numberValues =
+ {
+ AnyInstance.AnyByte, AnyInstance.AnySByte, AnyInstance.AnyShort, AnyInstance.AnyDecimal,
+ AnyInstance.AnyDouble, AnyInstance.AnyShort, AnyInstance.AnyInt, AnyInstance.AnyLong,
+ AnyInstance.AnyUShort, AnyInstance.AnyUInt, AnyInstance.AnyULong, AnyInstance.AnyFloat
+ };
+
+ object[] booleanValues =
+ {
+ true, false
+ };
+
+
+ object[] stringValues =
+ {
+ AnyInstance.AnyString, AnyInstance.AnyChar,
+ AnyInstance.AnyDateTime, AnyInstance.AnyDateTimeOffset,
+ AnyInstance.AnyGuid, AnyInstance.AnyUri
+ };
+
+ CheckValues(numberValues, JsonType.Number);
+ CheckValues(booleanValues, JsonType.Boolean);
+ CheckValues(stringValues, JsonType.String);
+ }
+
+ [Fact]
+ public void TryCreateInvalidTest()
+ {
+ bool success;
+ JsonPrimitive target;
+
+ object[] values =
+ {
+ AnyInstance.AnyJsonArray, AnyInstance.AnyJsonObject, AnyInstance.AnyJsonPrimitive,
+ null, AnyInstance.DefaultJsonValue, AnyInstance.AnyDynamic, AnyInstance.AnyAddress,
+ AnyInstance.AnyPerson
+ };
+
+ foreach (object value in values)
+ {
+ success = JsonPrimitive.TryCreate(value, out target);
+ Assert.False(success);
+ Assert.Null(target);
+ }
+ }
+
+ [Fact]
+ public void NumberToNumberConversionTest()
+ {
+ long longValue;
+ Assert.Equal((long)AnyInstance.AnyInt, (long)(new JsonPrimitive(AnyInstance.AnyInt)));
+ Assert.Equal((long)AnyInstance.AnyUInt, (long)(new JsonPrimitive(AnyInstance.AnyUInt)));
+ Assert.True(new JsonPrimitive(AnyInstance.AnyInt).TryReadAs<long>(out longValue));
+ Assert.Equal((long)AnyInstance.AnyInt, longValue);
+
+ int intValue;
+ Assert.Equal((int)AnyInstance.AnyShort, (int)(new JsonPrimitive(AnyInstance.AnyShort)));
+ Assert.Equal((int)AnyInstance.AnyUShort, (int)(new JsonPrimitive(AnyInstance.AnyUShort)));
+ Assert.True(new JsonPrimitive(AnyInstance.AnyUShort).TryReadAs<int>(out intValue));
+ Assert.Equal((int)AnyInstance.AnyUShort, intValue);
+
+ short shortValue;
+ Assert.Equal((short)AnyInstance.AnyByte, (short)(new JsonPrimitive(AnyInstance.AnyByte)));
+ Assert.Equal((short)AnyInstance.AnySByte, (short)(new JsonPrimitive(AnyInstance.AnySByte)));
+ Assert.True(new JsonPrimitive(AnyInstance.AnyByte).TryReadAs<short>(out shortValue));
+ Assert.Equal((short)AnyInstance.AnyByte, shortValue);
+
+ double dblValue;
+ Assert.Equal((double)AnyInstance.AnyFloat, (double)(new JsonPrimitive(AnyInstance.AnyFloat)));
+ Assert.Equal((double)AnyInstance.AnyDecimal, (double)(new JsonPrimitive(AnyInstance.AnyDecimal)));
+ Assert.True(new JsonPrimitive(AnyInstance.AnyFloat).TryReadAs<double>(out dblValue));
+ Assert.Equal((double)AnyInstance.AnyFloat, dblValue);
+ ExceptionHelper.Throws<OverflowException>(delegate { int i = (int)(new JsonPrimitive(1L << 32)); });
+ Assert.False(new JsonPrimitive(1L << 32).TryReadAs<int>(out intValue));
+ Assert.Equal(default(int), intValue);
+
+ byte byteValue;
+ ExceptionHelper.Throws<OverflowException>(delegate { byte b = (byte)(new JsonPrimitive(1L << 32)); });
+ ExceptionHelper.Throws<OverflowException>(delegate { byte b = (byte)(new JsonPrimitive(SByte.MinValue)); });
+ Assert.False(new JsonPrimitive(SByte.MinValue).TryReadAs<byte>(out byteValue));
+ Assert.Equal(default(byte), byteValue);
+ }
+
+ [Fact]
+ public void NumberToStringConverstionTest()
+ {
+ Dictionary<string, JsonPrimitive> allNumbers = new Dictionary<string, JsonPrimitive>
+ {
+ { AnyInstance.AnyByte.ToString(CultureInfo.InvariantCulture), new JsonPrimitive(AnyInstance.AnyByte) },
+ { AnyInstance.AnySByte.ToString(CultureInfo.InvariantCulture), new JsonPrimitive(AnyInstance.AnySByte) },
+ { AnyInstance.AnyShort.ToString(CultureInfo.InvariantCulture), new JsonPrimitive(AnyInstance.AnyShort) },
+ { AnyInstance.AnyUShort.ToString(CultureInfo.InvariantCulture), new JsonPrimitive(AnyInstance.AnyUShort) },
+ { AnyInstance.AnyInt.ToString(CultureInfo.InvariantCulture), new JsonPrimitive(AnyInstance.AnyInt) },
+ { AnyInstance.AnyUInt.ToString(CultureInfo.InvariantCulture), new JsonPrimitive(AnyInstance.AnyUInt) },
+ { AnyInstance.AnyLong.ToString(CultureInfo.InvariantCulture), new JsonPrimitive(AnyInstance.AnyLong) },
+ { AnyInstance.AnyULong.ToString(CultureInfo.InvariantCulture), new JsonPrimitive(AnyInstance.AnyULong) },
+ { AnyInstance.AnyDecimal.ToString(CultureInfo.InvariantCulture), new JsonPrimitive(AnyInstance.AnyDecimal) },
+ { AnyInstance.AnyDouble.ToString("R", CultureInfo.InvariantCulture), new JsonPrimitive(AnyInstance.AnyDouble) },
+ { AnyInstance.AnyFloat.ToString("R", CultureInfo.InvariantCulture), new JsonPrimitive(AnyInstance.AnyFloat) },
+ };
+
+ foreach (string stringRepresentation in allNumbers.Keys)
+ {
+ JsonPrimitive jp = allNumbers[stringRepresentation];
+ Assert.Equal(stringRepresentation, (string)jp);
+ Assert.Equal(stringRepresentation, jp.ReadAs<string>());
+ }
+ }
+
+ [Fact]
+ public void NonNumberToStringConversionTest()
+ {
+ Dictionary<string, JsonPrimitive> allValues = new Dictionary<string, JsonPrimitive>
+ {
+ { new string(AnyInstance.AnyChar, 1), new JsonPrimitive(AnyInstance.AnyChar) },
+ { AnyInstance.AnyBool.ToString(CultureInfo.InvariantCulture).ToLowerInvariant(), new JsonPrimitive(AnyInstance.AnyBool) },
+ { AnyInstance.AnyGuid.ToString("D", CultureInfo.InvariantCulture), new JsonPrimitive(AnyInstance.AnyGuid) },
+ { AnyInstance.AnyDateTime.ToString(DateTimeFormat, CultureInfo.InvariantCulture), new JsonPrimitive(AnyInstance.AnyDateTime) },
+ { AnyInstance.AnyDateTimeOffset.ToString(DateTimeFormat, CultureInfo.InvariantCulture), new JsonPrimitive(AnyInstance.AnyDateTimeOffset) },
+ };
+
+ foreach (char escapedChar in "\r\n\t\u0000\uffff\u001f\\\"")
+ {
+ allValues.Add(new string(escapedChar, 1), new JsonPrimitive(escapedChar));
+ }
+
+ foreach (string stringRepresentation in allValues.Keys)
+ {
+ JsonPrimitive jp = allValues[stringRepresentation];
+ Assert.Equal(stringRepresentation, (string)jp);
+ Assert.Equal(stringRepresentation, jp.ReadAs<string>());
+ }
+ }
+
+ [Fact]
+ public void NonNumberToNumberConversionTest()
+ {
+ Assert.Equal(1, new JsonPrimitive('1').ReadAs<int>());
+ Assert.Equal<byte>(AnyInstance.AnyByte, new JsonPrimitive(AnyInstance.AnyByte.ToString(CultureInfo.InvariantCulture)).ReadAs<byte>());
+ Assert.Equal<sbyte>(AnyInstance.AnySByte, (sbyte)(new JsonPrimitive(AnyInstance.AnySByte.ToString(CultureInfo.InvariantCulture))));
+ Assert.Equal<short>(AnyInstance.AnyShort, (short)(new JsonPrimitive(AnyInstance.AnyShort.ToString(CultureInfo.InvariantCulture))));
+ Assert.Equal<ushort>(AnyInstance.AnyUShort, new JsonPrimitive(AnyInstance.AnyUShort.ToString(CultureInfo.InvariantCulture)).ReadAs<ushort>());
+ Assert.Equal<int>(AnyInstance.AnyInt, new JsonPrimitive(AnyInstance.AnyInt.ToString(CultureInfo.InvariantCulture)).ReadAs<int>());
+ Assert.Equal<uint>(AnyInstance.AnyUInt, (uint)(new JsonPrimitive(AnyInstance.AnyUInt.ToString(CultureInfo.InvariantCulture))));
+ Assert.Equal<long>(AnyInstance.AnyLong, (long)(new JsonPrimitive(AnyInstance.AnyLong.ToString(CultureInfo.InvariantCulture))));
+ Assert.Equal<ulong>(AnyInstance.AnyULong, new JsonPrimitive(AnyInstance.AnyULong.ToString(CultureInfo.InvariantCulture)).ReadAs<ulong>());
+
+ Assert.Equal<decimal>(AnyInstance.AnyDecimal, (decimal)(new JsonPrimitive(AnyInstance.AnyDecimal.ToString(CultureInfo.InvariantCulture))));
+ Assert.Equal<float>(AnyInstance.AnyFloat, new JsonPrimitive(AnyInstance.AnyFloat.ToString(CultureInfo.InvariantCulture)).ReadAs<float>());
+ Assert.Equal<double>(AnyInstance.AnyDouble, (double)(new JsonPrimitive(AnyInstance.AnyDouble.ToString(CultureInfo.InvariantCulture))));
+
+ Assert.Equal<byte>(Convert.ToByte(1.23, CultureInfo.InvariantCulture), new JsonPrimitive("1.23").ReadAs<byte>());
+ Assert.Equal<int>(Convert.ToInt32(12345.6789, CultureInfo.InvariantCulture), new JsonPrimitive("12345.6789").ReadAs<int>());
+ Assert.Equal<short>(Convert.ToInt16(1.23e2), (short)new JsonPrimitive("1.23e2"));
+ Assert.Equal<float>(Convert.ToSingle(1.23e40), (float)new JsonPrimitive("1.23e40"));
+ Assert.Equal<float>(Convert.ToSingle(1.23e-38), (float)new JsonPrimitive("1.23e-38"));
+
+ ExceptionHelper.Throws<InvalidCastException>(delegate { var n = new JsonPrimitive(AnyInstance.AnyBool).ReadAs<sbyte>(); });
+ ExceptionHelper.Throws<InvalidCastException>(delegate { var n = new JsonPrimitive(AnyInstance.AnyBool).ReadAs<short>(); });
+ ExceptionHelper.Throws<InvalidCastException>(delegate { var n = new JsonPrimitive(AnyInstance.AnyBool).ReadAs<uint>(); });
+ ExceptionHelper.Throws<InvalidCastException>(delegate { var n = new JsonPrimitive(AnyInstance.AnyBool).ReadAs<long>(); });
+ ExceptionHelper.Throws<InvalidCastException>(delegate { var n = new JsonPrimitive(AnyInstance.AnyBool).ReadAs<double>(); });
+
+ ExceptionHelper.Throws<FormatException>(delegate { var n = new JsonPrimitive(AnyInstance.AnyUri).ReadAs<int>(); });
+ ExceptionHelper.Throws<FormatException>(delegate { var n = new JsonPrimitive(AnyInstance.AnyDateTime).ReadAs<float>(); });
+ ExceptionHelper.Throws<InvalidCastException>(delegate { var n = (decimal)(new JsonPrimitive('c')); });
+ ExceptionHelper.Throws<InvalidCastException>(delegate { var n = (byte)(new JsonPrimitive("0xFF")); });
+ ExceptionHelper.Throws<InvalidCastException>(delegate { var n = (sbyte)(new JsonPrimitive(AnyInstance.AnyDateTimeOffset)); });
+ ExceptionHelper.Throws<FormatException>(delegate { var n = new JsonPrimitive(AnyInstance.AnyUri).ReadAs<uint>(); });
+ ExceptionHelper.Throws<FormatException>(delegate { var n = new JsonPrimitive(AnyInstance.AnyDateTime).ReadAs<double>(); });
+ ExceptionHelper.Throws<InvalidCastException>(delegate { var n = (long)(new JsonPrimitive('c')); });
+ ExceptionHelper.Throws<InvalidCastException>(delegate { var n = (ulong)(new JsonPrimitive("0xFF")); });
+ ExceptionHelper.Throws<InvalidCastException>(delegate { var n = (short)(new JsonPrimitive(AnyInstance.AnyDateTimeOffset)); });
+ ExceptionHelper.Throws<InvalidCastException>(delegate { var n = (ushort)(new JsonPrimitive('c')); });
+
+ ExceptionHelper.Throws<OverflowException>(delegate { int i = (int)new JsonPrimitive((1L << 32).ToString(CultureInfo.InvariantCulture)); });
+ ExceptionHelper.Throws<OverflowException>(delegate { byte b = (byte)new JsonPrimitive("-1"); });
+ }
+
+ [Fact]
+ public void StringToNonNumberConversionTest()
+ {
+ const string DateTimeWithOffsetFormat = "yyyy-MM-ddTHH:mm:sszzz";
+ const string DateTimeWithOffsetFormat2 = "yyy-MM-ddTHH:mm:ss.fffK";
+ const string DateTimeWithoutOffsetWithoutTimeFormat = "yyy-MM-dd";
+ const string DateTimeWithoutOffsetFormat = "yyy-MM-ddTHH:mm:ss";
+ const string DateTimeWithoutOffsetFormat2 = "yyy-MM-ddTHH:mm:ss.fff";
+ const string TimeWithoutOffsetFormat = "HH:mm:ss";
+ const string TimeWithoutOffsetFormat2 = "HH:mm";
+
+ Assert.Equal(false, new JsonPrimitive("false").ReadAs<bool>());
+ Assert.Equal(false, (bool)(new JsonPrimitive("False")));
+ Assert.Equal(true, (bool)(new JsonPrimitive("true")));
+ Assert.Equal(true, new JsonPrimitive("True").ReadAs<bool>());
+
+ Assert.Equal<Uri>(AnyInstance.AnyUri, new JsonPrimitive(AnyInstance.AnyUri.ToString()).ReadAs<Uri>());
+ Assert.Equal<char>(AnyInstance.AnyChar, (char)(new JsonPrimitive(new string(AnyInstance.AnyChar, 1))));
+ Assert.Equal<Guid>(AnyInstance.AnyGuid, (Guid)(new JsonPrimitive(AnyInstance.AnyGuid.ToString("D", CultureInfo.InvariantCulture))));
+
+ DateTime anyLocalDateTime = AnyInstance.AnyDateTime.ToLocalTime();
+ DateTime anyUtcDateTime = AnyInstance.AnyDateTime.ToUniversalTime();
+
+ Assert.Equal<DateTime>(anyUtcDateTime, (DateTime)(new JsonPrimitive(anyUtcDateTime.ToString(DateTimeFormat, CultureInfo.InvariantCulture))));
+ Assert.Equal<DateTime>(anyLocalDateTime, new JsonPrimitive(anyLocalDateTime.ToString(DateTimeWithOffsetFormat2, CultureInfo.InvariantCulture)).ReadAs<DateTime>());
+ Assert.Equal<DateTime>(anyUtcDateTime, new JsonPrimitive(anyUtcDateTime.ToString(DateTimeWithOffsetFormat2, CultureInfo.InvariantCulture)).ReadAs<DateTime>());
+ Assert.Equal<DateTime>(anyLocalDateTime.Date, (DateTime)(new JsonPrimitive(anyLocalDateTime.ToString(DateTimeWithoutOffsetWithoutTimeFormat, CultureInfo.InvariantCulture))));
+ Assert.Equal<DateTime>(anyLocalDateTime, new JsonPrimitive(anyLocalDateTime.ToString(DateTimeWithoutOffsetFormat, CultureInfo.InvariantCulture)).ReadAs<DateTime>());
+ Assert.Equal<DateTime>(anyLocalDateTime, new JsonPrimitive(anyLocalDateTime.ToString(DateTimeWithoutOffsetFormat2, CultureInfo.InvariantCulture)).ReadAs<DateTime>());
+
+ DateTime dt = new JsonPrimitive(anyLocalDateTime.ToString(TimeWithoutOffsetFormat, CultureInfo.InvariantCulture)).ReadAs<DateTime>();
+ Assert.Equal(anyLocalDateTime.Hour, dt.Hour);
+ Assert.Equal(anyLocalDateTime.Minute, dt.Minute);
+ Assert.Equal(anyLocalDateTime.Second, dt.Second);
+
+ dt = new JsonPrimitive(anyLocalDateTime.ToString(TimeWithoutOffsetFormat2, CultureInfo.InvariantCulture)).ReadAs<DateTime>();
+ Assert.Equal(anyLocalDateTime.Hour, dt.Hour);
+ Assert.Equal(anyLocalDateTime.Minute, dt.Minute);
+ Assert.Equal(0, dt.Second);
+
+ Assert.Equal<DateTimeOffset>(AnyInstance.AnyDateTimeOffset, new JsonPrimitive(AnyInstance.AnyDateTimeOffset.ToString(DateTimeFormat, CultureInfo.InvariantCulture)).ReadAs<DateTimeOffset>());
+ Assert.Equal<DateTimeOffset>(AnyInstance.AnyDateTimeOffset, new JsonPrimitive(AnyInstance.AnyDateTimeOffset.ToString(DateTimeWithOffsetFormat, CultureInfo.InvariantCulture)).ReadAs<DateTimeOffset>());
+ Assert.Equal<DateTimeOffset>(AnyInstance.AnyDateTimeOffset, new JsonPrimitive(AnyInstance.AnyDateTimeOffset.ToString(DateTimeWithOffsetFormat2, CultureInfo.InvariantCulture)).ReadAs<DateTimeOffset>());
+ Assert.Equal<DateTimeOffset>(AnyInstance.AnyDateTimeOffset.ToLocalTime(), (DateTimeOffset)(new JsonPrimitive(AnyInstance.AnyDateTimeOffset.ToLocalTime().ToString(DateTimeWithoutOffsetFormat, CultureInfo.InvariantCulture))));
+ Assert.Equal<DateTimeOffset>(AnyInstance.AnyDateTimeOffset.ToLocalTime(), (DateTimeOffset)(new JsonPrimitive(AnyInstance.AnyDateTimeOffset.ToLocalTime().ToString(DateTimeWithoutOffsetFormat2, CultureInfo.InvariantCulture))));
+
+ DataContractJsonSerializer dcjs = new DataContractJsonSerializer(typeof(DateTime));
+ MemoryStream ms = new MemoryStream();
+ dcjs.WriteObject(ms, AnyInstance.AnyDateTime);
+ string dcjsSerializedDateTime = Encoding.UTF8.GetString(ms.ToArray());
+ Assert.Equal(AnyInstance.AnyDateTime, JsonValue.Parse(dcjsSerializedDateTime).ReadAs<DateTime>());
+
+ ExceptionHelper.Throws<InvalidCastException>(delegate { var b = (bool)(new JsonPrimitive("notBool")); });
+ ExceptionHelper.Throws<UriFormatException>(delegate { var u = new JsonPrimitive("not an uri - " + new string('r', 100000)).ReadAs<Uri>(); });
+ ExceptionHelper.Throws<FormatException>(delegate { var date = new JsonPrimitive("not a date time").ReadAs<DateTime>(); });
+ ExceptionHelper.Throws<InvalidCastException>(delegate { var dto = (DateTimeOffset)(new JsonPrimitive("not a date time offset")); });
+ ExceptionHelper.Throws<InvalidCastException>(delegate { var c = (char)new JsonPrimitive(""); });
+ ExceptionHelper.Throws<InvalidCastException>(delegate { var c = (char)new JsonPrimitive("cc"); });
+ ExceptionHelper.Throws<FormatException>(delegate { var g = new JsonPrimitive("not a guid").ReadAs<Guid>(); });
+ }
+
+ [Fact]
+ public void AspNetDateTimeFormatConversionTest()
+ {
+ DateTime unixEpochUtc = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
+ DateTime unixEpochLocal = unixEpochUtc.ToLocalTime();
+ Assert.Equal(unixEpochUtc, new JsonPrimitive("/Date(0)/").ReadAs<DateTime>());
+ Assert.Equal(unixEpochLocal, new JsonPrimitive("/Date(0-0900)/").ReadAs<DateTime>());
+ Assert.Equal(unixEpochLocal, new JsonPrimitive("/Date(0+1000)/").ReadAs<DateTime>());
+ }
+
+ [Fact]
+ public void ToStringTest()
+ {
+ char anyUnescapedChar = 'c';
+ string anyUnescapedString = "hello";
+
+ Dictionary<string, JsonPrimitive> toStringResults = new Dictionary<string, JsonPrimitive>
+ {
+ // Boolean types
+ { AnyInstance.AnyBool.ToString(CultureInfo.InvariantCulture).ToLowerInvariant(), new JsonPrimitive(AnyInstance.AnyBool) },
+
+ // Numeric types
+ { AnyInstance.AnyByte.ToString(CultureInfo.InvariantCulture), new JsonPrimitive(AnyInstance.AnyByte) },
+ { AnyInstance.AnySByte.ToString(CultureInfo.InvariantCulture), new JsonPrimitive(AnyInstance.AnySByte) },
+ { AnyInstance.AnyShort.ToString(CultureInfo.InvariantCulture), new JsonPrimitive(AnyInstance.AnyShort) },
+ { AnyInstance.AnyUShort.ToString(CultureInfo.InvariantCulture), new JsonPrimitive(AnyInstance.AnyUShort) },
+ { AnyInstance.AnyInt.ToString(CultureInfo.InvariantCulture), new JsonPrimitive(AnyInstance.AnyInt) },
+ { AnyInstance.AnyUInt.ToString(CultureInfo.InvariantCulture), new JsonPrimitive(AnyInstance.AnyUInt) },
+ { AnyInstance.AnyLong.ToString(CultureInfo.InvariantCulture), new JsonPrimitive(AnyInstance.AnyLong) },
+ { AnyInstance.AnyULong.ToString(CultureInfo.InvariantCulture), new JsonPrimitive(AnyInstance.AnyULong) },
+ { AnyInstance.AnyFloat.ToString("R", CultureInfo.InvariantCulture), new JsonPrimitive(AnyInstance.AnyFloat) },
+ { AnyInstance.AnyDouble.ToString("R", CultureInfo.InvariantCulture), new JsonPrimitive(AnyInstance.AnyDouble) },
+ { AnyInstance.AnyDecimal.ToString(CultureInfo.InvariantCulture), new JsonPrimitive(AnyInstance.AnyDecimal) },
+
+ // String types
+ { "\"" + new string(anyUnescapedChar, 1) + "\"", new JsonPrimitive(anyUnescapedChar) },
+ { "\"" + anyUnescapedString + "\"", new JsonPrimitive(anyUnescapedString) },
+ { "\"" + AnyInstance.AnyDateTime.ToString(DateTimeFormat, CultureInfo.InvariantCulture) + "\"", new JsonPrimitive(AnyInstance.AnyDateTime) },
+ { "\"" + AnyInstance.AnyDateTimeOffset.ToString(DateTimeFormat, CultureInfo.InvariantCulture) + "\"", new JsonPrimitive(AnyInstance.AnyDateTimeOffset) },
+ { "\"" + AnyInstance.AnyUri.GetComponents(UriComponents.SerializationInfoString, UriFormat.UriEscaped).Replace("/", "\\/") + "\"", new JsonPrimitive(AnyInstance.AnyUri) },
+ { "\"" + AnyInstance.AnyGuid.ToString("D", CultureInfo.InvariantCulture) + "\"", new JsonPrimitive(AnyInstance.AnyGuid) },
+ };
+
+ foreach (string stringRepresentation in toStringResults.Keys)
+ {
+ string actualResult = toStringResults[stringRepresentation].ToString();
+ Assert.Equal(stringRepresentation, actualResult);
+ }
+
+ Dictionary<string, JsonPrimitive> escapedValues = new Dictionary<string, JsonPrimitive>
+ {
+ { "\"\\u000d\"", new JsonPrimitive('\r') },
+ { "\"\\u000a\"", new JsonPrimitive('\n') },
+ { "\"\\\\\"", new JsonPrimitive('\\') },
+ { "\"\\/\"", new JsonPrimitive('/') },
+ { "\"\\u000b\"", new JsonPrimitive('\u000b') },
+ { "\"\\\"\"", new JsonPrimitive('\"') },
+ { "\"slash-r-\\u000d-fffe-\\ufffe-ffff-\\uffff-tab-\\u0009\"", new JsonPrimitive("slash-r-\r-fffe-\ufffe-ffff-\uffff-tab-\t") },
+ };
+
+ foreach (string stringRepresentation in escapedValues.Keys)
+ {
+ string actualResult = escapedValues[stringRepresentation].ToString();
+ Assert.Equal(stringRepresentation, actualResult);
+ }
+ }
+
+ [Fact]
+ public void JsonTypeTest()
+ {
+ Assert.Equal(JsonType.Boolean, new JsonPrimitive(AnyInstance.AnyBool).JsonType);
+ Assert.Equal(JsonType.Number, new JsonPrimitive(AnyInstance.AnyByte).JsonType);
+ Assert.Equal(JsonType.Number, new JsonPrimitive(AnyInstance.AnySByte).JsonType);
+ Assert.Equal(JsonType.Number, new JsonPrimitive(AnyInstance.AnyShort).JsonType);
+ Assert.Equal(JsonType.Number, new JsonPrimitive(AnyInstance.AnyUShort).JsonType);
+ Assert.Equal(JsonType.Number, new JsonPrimitive(AnyInstance.AnyInt).JsonType);
+ Assert.Equal(JsonType.Number, new JsonPrimitive(AnyInstance.AnyUInt).JsonType);
+ Assert.Equal(JsonType.Number, new JsonPrimitive(AnyInstance.AnyLong).JsonType);
+ Assert.Equal(JsonType.Number, new JsonPrimitive(AnyInstance.AnyULong).JsonType);
+ Assert.Equal(JsonType.Number, new JsonPrimitive(AnyInstance.AnyDecimal).JsonType);
+ Assert.Equal(JsonType.Number, new JsonPrimitive(AnyInstance.AnyDouble).JsonType);
+ Assert.Equal(JsonType.Number, new JsonPrimitive(AnyInstance.AnyFloat).JsonType);
+ Assert.Equal(JsonType.String, new JsonPrimitive(AnyInstance.AnyChar).JsonType);
+ Assert.Equal(JsonType.String, new JsonPrimitive(AnyInstance.AnyString).JsonType);
+ Assert.Equal(JsonType.String, new JsonPrimitive(AnyInstance.AnyUri).JsonType);
+ Assert.Equal(JsonType.String, new JsonPrimitive(AnyInstance.AnyGuid).JsonType);
+ Assert.Equal(JsonType.String, new JsonPrimitive(AnyInstance.AnyDateTime).JsonType);
+ Assert.Equal(JsonType.String, new JsonPrimitive(AnyInstance.AnyDateTimeOffset).JsonType);
+ }
+
+ [Fact]
+ public void InvalidPropertyTest()
+ {
+ JsonValue target = AnyInstance.AnyJsonPrimitive;
+ Assert.True(target.Count == 0);
+ Assert.False(target.ContainsKey(String.Empty));
+ Assert.False(target.ContainsKey(AnyInstance.AnyString));
+ }
+
+ private void CheckValues(object[] values, JsonType expectedType)
+ {
+ JsonPrimitive target;
+ bool success;
+
+ foreach (object value in values)
+ {
+ success = JsonPrimitive.TryCreate(value, out target);
+ Assert.True(success);
+ Assert.NotNull(target);
+ Assert.Equal(expectedType, target.JsonType);
+ }
+ }
+ }
+}
diff --git a/test/System.Json.Test.Unit/JsonTypeTest.cs b/test/System.Json.Test.Unit/JsonTypeTest.cs
new file mode 100644
index 00000000..aa02df7d
--- /dev/null
+++ b/test/System.Json.Test.Unit/JsonTypeTest.cs
@@ -0,0 +1,26 @@
+using System.Collections.Generic;
+using System.Linq;
+using Xunit;
+
+namespace System.Json
+{
+ public class JsonTypeTest
+ {
+ [Fact]
+ public void JsonTypeValues()
+ {
+ string[] allJsonTypeExpectedValues = new string[] { "Array", "Boolean", "Default", "Number", "Object", "String" };
+ JsonType[] allJsonTypeActualValues = (JsonType[])Enum.GetValues(typeof(JsonType));
+
+ Assert.Equal(allJsonTypeExpectedValues.Length, allJsonTypeActualValues.Length);
+
+ List<string> allJsonTypeActualStringValues = new List<string>(allJsonTypeActualValues.Select((x) => x.ToString()));
+ allJsonTypeActualStringValues.Sort(StringComparer.Ordinal);
+
+ for (int i = 0; i < allJsonTypeExpectedValues.Length; i++)
+ {
+ Assert.Equal(allJsonTypeExpectedValues[i], allJsonTypeActualStringValues[i]);
+ }
+ }
+ }
+}
diff --git a/test/System.Json.Test.Unit/JsonValueDynamicMetaObjectTest.cs b/test/System.Json.Test.Unit/JsonValueDynamicMetaObjectTest.cs
new file mode 100644
index 00000000..e6a2621e
--- /dev/null
+++ b/test/System.Json.Test.Unit/JsonValueDynamicMetaObjectTest.cs
@@ -0,0 +1,534 @@
+using System.Collections.Generic;
+using System.Dynamic;
+using System.Linq.Expressions;
+using Xunit;
+
+namespace System.Json
+{
+ /// <summary>
+ ///This is a test class for JsonValueDynamicMetaObjectTest and is intended to perform sanity tests on this class.
+ ///Extended tests are performed by the JsonValue dynamic feature tests.
+ ///</summary>
+ public class JsonValueDynamicMetaObjectTest
+ {
+ const string NonSingleNonNullIndexNotSupported = "Null index or multidimensional indexing is not supported by this indexer; use 'System.Int32' or 'System.String' for array and object indexing respectively.";
+
+ /// <summary>
+ /// A test for GetMetaObject
+ ///</summary>
+ [Fact]
+ public void GetMetaObjectTest()
+ {
+ ExceptionHelper.Throws<ArgumentNullException>(() => { var target = GetJsonValueDynamicMetaObject(AnyInstance.AnyJsonObject, null); });
+ }
+
+ /// <summary>
+ /// A test for BindInvokeMember
+ ///</summary>
+ [Fact]
+ public void BindInvokeMemberTest()
+ {
+ JsonValue value = AnyInstance.AnyJsonValue1;
+ DynamicMetaObject target = GetJsonValueDynamicMetaObject(value);
+
+ TestInvokeMemberBinder.TestBindParams(target);
+
+ string methodName;
+ object[] arguments;
+ object result = null;
+
+ methodName = "ToString";
+ arguments = new object[] { };
+ TestInvokeMemberBinder.TestMetaObject(target, methodName, arguments);
+
+ methodName = "TryReadAs";
+ arguments = new object[] { typeof(int), result };
+ TestInvokeMemberBinder.TestMetaObject(target, methodName, arguments);
+
+ methodName = "TryReadAsType";
+ arguments = new object[] { typeof(Person), result };
+ TestInvokeMemberBinder.TestMetaObject(target, methodName, arguments, true);
+ }
+
+ /// <summary>
+ /// A test for BindConvert
+ ///</summary>
+ [Fact]
+ public void BindConvertTest()
+ {
+ JsonValue value;
+ DynamicMetaObject target;
+
+ value = (JsonValue)AnyInstance.AnyInt;
+ target = GetJsonValueDynamicMetaObject(value);
+ TestConvertBinder.TestBindParams(target);
+
+ Type[] intTypes = { typeof(int), typeof(uint), typeof(long), };
+
+ foreach (Type type in intTypes)
+ {
+ TestConvertBinder.TestMetaObject(target, type);
+ }
+
+ value = (JsonValue)AnyInstance.AnyString;
+ target = GetJsonValueDynamicMetaObject(value);
+ TestConvertBinder.TestMetaObject(target, typeof(string));
+
+ value = (JsonValue)AnyInstance.AnyJsonValue1;
+ target = GetJsonValueDynamicMetaObject(value);
+ TestConvertBinder.TestMetaObject(target, typeof(JsonValue));
+ TestConvertBinder.TestMetaObject(target, typeof(IEnumerable<KeyValuePair<string, JsonValue>>));
+ TestConvertBinder.TestMetaObject(target, typeof(IDynamicMetaObjectProvider));
+ TestConvertBinder.TestMetaObject(target, typeof(object));
+
+ TestConvertBinder.TestMetaObject(target, typeof(Person), false);
+ }
+
+ /// <summary>
+ /// A test for BindGetIndex
+ ///</summary>
+ [Fact]
+ public void BindGetIndexTest()
+ {
+ JsonValue value = AnyInstance.AnyJsonArray;
+
+ DynamicMetaObject target = GetJsonValueDynamicMetaObject(value);
+
+ TestGetIndexBinder.TestBindParams(target);
+
+ foreach (KeyValuePair<string, JsonValue> pair in value)
+ {
+ TestGetIndexBinder.TestMetaObject(target, Int32.Parse(pair.Key));
+ }
+ }
+
+ /// <summary>
+ /// A test for BindSetIndex
+ ///</summary>
+ [Fact]
+ public void BindSetIndexTest()
+ {
+ JsonValue jsonValue = AnyInstance.AnyJsonArray;
+
+ DynamicMetaObject target = GetJsonValueDynamicMetaObject(jsonValue);
+
+ TestSetIndexBinder.TestBindParams(target);
+
+ int value = 0;
+
+ foreach (KeyValuePair<string, JsonValue> pair in jsonValue)
+ {
+ TestSetIndexBinder.TestMetaObject(target, Int32.Parse(pair.Key), value++);
+ }
+ }
+
+ /// <summary>
+ /// A test for BindGetMember.
+ ///</summary>
+ [Fact]
+ public void BindGetMemberTest()
+ {
+ JsonValue value = AnyInstance.AnyJsonObject;
+
+ DynamicMetaObject target = GetJsonValueDynamicMetaObject(value);
+
+ TestGetMemberBinder.TestBindParams(target);
+
+ foreach (KeyValuePair<string, JsonValue> pair in value)
+ {
+ TestGetMemberBinder.TestMetaObject(target, pair.Key);
+ }
+ }
+
+ /// <summary>
+ /// A test for BindSetMember.
+ ///</summary>
+ [Fact]
+ public void BindSetMemberTest()
+ {
+ JsonValue value = AnyInstance.AnyJsonObject;
+
+ string expectedMethodSignature = "System.Json.JsonValue SetValue(System.String, System.Object)";
+
+ DynamicMetaObject target = GetJsonValueDynamicMetaObject(value);
+ DynamicMetaObject arg = new DynamicMetaObject(Expression.Parameter(typeof(int)), BindingRestrictions.Empty, AnyInstance.AnyInt);
+
+ TestSetMemberBinder.TestBindParams(target, arg);
+
+ foreach (KeyValuePair<string, JsonValue> pair in value)
+ {
+ TestSetMemberBinder.TestMetaObject(target, pair.Key, arg, expectedMethodSignature);
+ }
+ }
+
+ /// <summary>
+ /// A test for GetDynamicMemberNames
+ ///</summary>
+ [Fact]
+ public void GetDynamicMemberNamesTest()
+ {
+ JsonValue[] values = AnyInstance.AnyJsonValueArray;
+
+ foreach (JsonValue value in values)
+ {
+ DynamicMetaObject target = GetJsonValueDynamicMetaObject(value);
+
+ List<string> expected = new List<string>();
+ foreach (KeyValuePair<string, JsonValue> pair in value)
+ {
+ expected.Add(pair.Key);
+ }
+
+ IEnumerable<string> retEnumerable = target.GetDynamicMemberNames();
+ Assert.NotNull(retEnumerable);
+
+ List<string> actual = new List<string>(retEnumerable);
+ Assert.Equal(expected.Count, actual.Count);
+
+ for (int i = 0; i < expected.Count; i++)
+ {
+ Assert.Equal<string>(expected[i], actual[i]);
+ }
+ }
+ }
+
+ /// <summary>
+ /// Helper method for getting a <see cref="JsonValueDynamicMetaObject"/>.
+ /// </summary>
+ /// <param name="jsonValue">The <see cref="JsonValue"/> instance to get the dynamic meta-object from.</param>
+ /// <returns></returns>
+ private static DynamicMetaObject GetJsonValueDynamicMetaObject(JsonValue jsonValue)
+ {
+ return GetJsonValueDynamicMetaObject(jsonValue, Expression.Parameter(typeof(object)));
+ }
+
+ private static DynamicMetaObject GetJsonValueDynamicMetaObject(JsonValue jsonValue, Expression expression)
+ {
+ return ((IDynamicMetaObjectProvider)jsonValue).GetMetaObject(expression);
+ }
+
+ /// <summary>
+ /// Test binder for method call operation.
+ /// </summary>
+ private class TestInvokeMemberBinder : InvokeMemberBinder
+ {
+ public TestInvokeMemberBinder(string name, int argCount)
+ : base(name, false, new CallInfo(argCount, new string[] { }))
+ {
+ }
+
+ public static void TestBindParams(DynamicMetaObject target)
+ {
+ string methodName = "ToString";
+ object[] arguments = new object[] { };
+
+ InvokeMemberBinder binder = new TestInvokeMemberBinder(methodName, arguments.Length);
+ DynamicMetaObject[] args = new DynamicMetaObject[arguments.Length];
+
+ ExceptionHelper.Throws<ArgumentNullException>(() => { var result = target.BindInvokeMember(null, args); });
+ ExceptionHelper.Throws<ArgumentNullException>(() => { var result = target.BindInvokeMember(binder, null); });
+ }
+
+ public static void TestMetaObject(DynamicMetaObject target, string methodName, object[] arguments, bool isExtension = false)
+ {
+ InvokeMemberBinder binder = new TestInvokeMemberBinder(methodName, arguments.Length);
+ DynamicMetaObject[] args = new DynamicMetaObject[arguments.Length];
+
+ for (int idx = 0; idx < args.Length; idx++)
+ {
+ object value = arguments[idx];
+ Type valueType = value != null ? value.GetType() : typeof(object);
+ args[idx] = new DynamicMetaObject(Expression.Parameter(valueType), BindingRestrictions.Empty, value);
+ }
+
+ DynamicMetaObject result = target.BindInvokeMember(binder, args);
+ Assert.NotNull(result);
+
+ if (isExtension)
+ {
+ UnaryExpression expression = result.Expression as UnaryExpression;
+ Assert.NotNull(expression);
+
+ MethodCallExpression callExpression = expression.Operand as MethodCallExpression;
+ Assert.NotNull(callExpression);
+
+ Assert.True(callExpression.Method.ToString().Contains(methodName));
+ }
+ else
+ {
+ Assert.Same(target, result.Value);
+ }
+ }
+
+ public override DynamicMetaObject FallbackInvoke(DynamicMetaObject target, DynamicMetaObject[] args, DynamicMetaObject errorSuggestion)
+ {
+ throw new NotImplementedException();
+ }
+
+ public override DynamicMetaObject FallbackInvokeMember(DynamicMetaObject target, DynamicMetaObject[] args, DynamicMetaObject errorSuggestion)
+ {
+ // This is where the C# binder does the actual binding.
+ return new DynamicMetaObject(Expression.Constant("FallbackInvokeMember called"), BindingRestrictions.Empty, target);
+ }
+ }
+
+ /// <summary>
+ /// The binder for the cast operation.
+ /// </summary>
+ private class TestConvertBinder : ConvertBinder
+ {
+ public TestConvertBinder(Type type)
+ : base(type, false)
+ {
+ }
+
+ public static void TestBindParams(DynamicMetaObject target)
+ {
+ ConvertBinder binder = new TestConvertBinder(typeof(int));
+ ExceptionHelper.Throws<ArgumentNullException>(() => { var result = target.BindConvert(null); });
+ }
+
+ public static void TestMetaObject(DynamicMetaObject target, Type type, bool isValid = true)
+ {
+ ConvertBinder binder = new TestConvertBinder(type);
+ DynamicMetaObject result = target.BindConvert(binder);
+ Assert.NotNull(result);
+
+ // Convert expression
+ UnaryExpression expression = result.Expression as UnaryExpression;
+ Assert.NotNull(expression);
+ Assert.Equal<Type>(binder.Type, expression.Type);
+
+ if (isValid)
+ {
+ MethodCallExpression methodCallExp = expression.Operand as MethodCallExpression;
+
+ if (methodCallExp != null)
+ {
+ Assert.True(methodCallExp.Method.ToString().Contains("CastValue"));
+ }
+ else
+ {
+ ParameterExpression paramExpression = expression.Operand as ParameterExpression;
+ Assert.NotNull(paramExpression);
+ }
+ }
+ else
+ {
+ Expression<Action> throwExp = Expression.Lambda<Action>(Expression.Block(expression), new ParameterExpression[] { });
+ ExceptionHelper.Throws<InvalidCastException>(() => throwExp.Compile().Invoke());
+ }
+ }
+
+ public override DynamicMetaObject FallbackConvert(DynamicMetaObject target, DynamicMetaObject errorSuggestion)
+ {
+ throw new NotImplementedException();
+ }
+ }
+
+ /// <summary>
+ /// Test binder for int indexer getter operation.
+ /// </summary>
+ private class TestGetIndexBinder : GetIndexBinder
+ {
+ public TestGetIndexBinder()
+ : base(new CallInfo(0, new string[] { }))
+ {
+ }
+
+ public static void TestBindParams(DynamicMetaObject target)
+ {
+ GetIndexBinder binder = new TestGetIndexBinder();
+ Expression typeExpression = Expression.Parameter(typeof(int));
+
+ DynamicMetaObject[] indexes =
+ {
+ new DynamicMetaObject(typeExpression, BindingRestrictions.Empty, 0),
+ new DynamicMetaObject(typeExpression, BindingRestrictions.Empty, 1),
+ new DynamicMetaObject(typeExpression, BindingRestrictions.Empty, 2)
+ };
+
+ ExceptionHelper.Throws<ArgumentNullException>(() => { var result = target.BindGetIndex(null, indexes); });
+ ExceptionHelper.Throws<ArgumentNullException>(() => { var result = target.BindGetIndex(binder, null); });
+
+ DynamicMetaObject[][] invalidIndexesParam =
+ {
+ indexes,
+ new DynamicMetaObject[] { new DynamicMetaObject(typeExpression, BindingRestrictions.Empty, null) },
+ new DynamicMetaObject[] { null },
+ new DynamicMetaObject[] { }
+ };
+
+ foreach (DynamicMetaObject[] indexesParam in invalidIndexesParam)
+ {
+ DynamicMetaObject metaObj = target.BindGetIndex(binder, indexesParam);
+
+ Expression<Action> expression = Expression.Lambda<Action>(Expression.Block(metaObj.Expression), new ParameterExpression[] { });
+ ExceptionHelper.Throws<ArgumentException>(() => { expression.Compile().Invoke(); }, NonSingleNonNullIndexNotSupported);
+ }
+ }
+
+ public static void TestMetaObject(DynamicMetaObject target, int index, bool isValid = true)
+ {
+ string expectedMethodSignature = "System.Json.JsonValue GetValue(Int32)";
+
+ GetIndexBinder binder = new TestGetIndexBinder();
+ DynamicMetaObject[] indexes = { new DynamicMetaObject(Expression.Parameter(typeof(int)), BindingRestrictions.Empty, index) };
+
+ DynamicMetaObject result = target.BindGetIndex(binder, indexes);
+ Assert.NotNull(result);
+
+ MethodCallExpression expression = result.Expression as MethodCallExpression;
+ Assert.NotNull(expression);
+ Assert.Equal<string>(expectedMethodSignature, expression.Method.ToString());
+ }
+
+ public override DynamicMetaObject FallbackGetIndex(DynamicMetaObject target, DynamicMetaObject[] indexes, DynamicMetaObject errorSuggestion)
+ {
+ throw new NotImplementedException();
+ }
+ }
+
+ /// <summary>
+ /// Test binder for int indexer setter operation.
+ /// </summary>
+ private class TestSetIndexBinder : SetIndexBinder
+ {
+ public TestSetIndexBinder()
+ : base(new CallInfo(0, new string[] { }))
+ {
+ }
+
+ public static void TestBindParams(DynamicMetaObject target)
+ {
+ SetIndexBinder binder = new TestSetIndexBinder();
+ Expression typeExpression = Expression.Parameter(typeof(int));
+ DynamicMetaObject[] indexes = new DynamicMetaObject[] { new DynamicMetaObject(typeExpression, BindingRestrictions.Empty, 0) };
+ DynamicMetaObject value = new DynamicMetaObject(typeExpression, BindingRestrictions.Empty, (JsonValue)10);
+
+ ExceptionHelper.Throws<ArgumentNullException>(() => { var result = target.BindSetIndex(null, indexes, value); });
+ ExceptionHelper.Throws<ArgumentNullException>(() => { var result = target.BindSetIndex(binder, null, value); });
+ ExceptionHelper.Throws<ArgumentNullException>(() => { var result = target.BindSetIndex(binder, indexes, null); });
+
+ DynamicMetaObject[][] invalidIndexesParam =
+ {
+ new DynamicMetaObject[]
+ {
+ new DynamicMetaObject(typeExpression, BindingRestrictions.Empty, 0),
+ new DynamicMetaObject(typeExpression, BindingRestrictions.Empty, 1),
+ new DynamicMetaObject(typeExpression, BindingRestrictions.Empty, 2)
+ },
+
+ new DynamicMetaObject[]
+ {
+ new DynamicMetaObject(typeExpression, BindingRestrictions.Empty, null)
+ },
+
+ new DynamicMetaObject[]
+ {
+ }
+ };
+
+ foreach (DynamicMetaObject[] indexesParam in invalidIndexesParam)
+ {
+ DynamicMetaObject metaObj = target.BindSetIndex(binder, indexesParam, value);
+
+ Expression<Action> expression = Expression.Lambda<Action>(Expression.Block(metaObj.Expression), new ParameterExpression[] { });
+ ExceptionHelper.Throws<ArgumentException>(() => { expression.Compile().Invoke(); }, NonSingleNonNullIndexNotSupported);
+ }
+ }
+
+ public static void TestMetaObject(DynamicMetaObject target, int index, JsonValue jsonValue, bool isValid = true)
+ {
+ string expectedMethodSignature = "System.Json.JsonValue SetValue(Int32, System.Object)";
+
+ SetIndexBinder binder = new TestSetIndexBinder();
+ DynamicMetaObject[] indexes = { new DynamicMetaObject(Expression.Parameter(typeof(int)), BindingRestrictions.Empty, index) };
+ DynamicMetaObject value = new DynamicMetaObject(Expression.Parameter(jsonValue.GetType()), BindingRestrictions.Empty, jsonValue);
+ DynamicMetaObject result = target.BindSetIndex(binder, indexes, value);
+ Assert.NotNull(result);
+
+ MethodCallExpression expression = result.Expression as MethodCallExpression;
+ Assert.NotNull(expression);
+ Assert.Equal<string>(expectedMethodSignature, expression.Method.ToString());
+ }
+
+ public override DynamicMetaObject FallbackSetIndex(DynamicMetaObject target, DynamicMetaObject[] indexes, DynamicMetaObject value, DynamicMetaObject errorSuggestion)
+ {
+ throw new NotImplementedException();
+ }
+ }
+
+ /// <summary>
+ /// Test binder for key indexer getter.
+ /// </summary>
+ private class TestGetMemberBinder : GetMemberBinder
+ {
+ public TestGetMemberBinder(string name)
+ : base(name, false)
+ {
+ }
+
+ public static void TestBindParams(DynamicMetaObject target)
+ {
+ GetMemberBinder binder = new TestGetMemberBinder("AnyProperty");
+ ExceptionHelper.Throws<ArgumentNullException>(() => { var result = target.BindGetMember(null); });
+ }
+
+ public static void TestMetaObject(DynamicMetaObject target, string name, bool isValid = true)
+ {
+ string expectedMethodSignature = "System.Json.JsonValue GetValue(System.String)";
+
+ GetMemberBinder binder = new TestGetMemberBinder(name);
+
+ DynamicMetaObject result = target.BindGetMember(binder);
+ Assert.NotNull(result);
+
+ MethodCallExpression expression = result.Expression as MethodCallExpression;
+ Assert.NotNull(expression);
+ Assert.Equal<string>(expectedMethodSignature, expression.Method.ToString());
+ }
+
+ public override DynamicMetaObject FallbackGetMember(DynamicMetaObject target, DynamicMetaObject errorSuggestion)
+ {
+ throw new NotImplementedException();
+ }
+ }
+
+ /// <summary>
+ /// Test binder for key indexer setter.
+ /// </summary>
+ private class TestSetMemberBinder : SetMemberBinder
+ {
+ public TestSetMemberBinder(string name)
+ : base(name, false)
+ {
+ }
+
+ public static void TestBindParams(DynamicMetaObject target, DynamicMetaObject value)
+ {
+ SetMemberBinder binder = new TestSetMemberBinder("AnyProperty");
+
+ ExceptionHelper.Throws<ArgumentNullException>(() => { var result = target.BindSetMember(null, value); });
+ ExceptionHelper.Throws<ArgumentNullException>(() => { var result = target.BindSetMember(binder, null); });
+ }
+
+ public static void TestMetaObject(DynamicMetaObject target, string name, DynamicMetaObject value, string expectedMethodSignature, bool isValid = true)
+ {
+ SetMemberBinder binder = new TestSetMemberBinder(name);
+
+ DynamicMetaObject result = target.BindSetMember(binder, value);
+ Assert.NotNull(result);
+
+ MethodCallExpression expression = result.Expression as MethodCallExpression;
+ Assert.NotNull(expression);
+ Assert.Equal<string>(expectedMethodSignature, expression.Method.ToString());
+ }
+
+ public override DynamicMetaObject FallbackSetMember(DynamicMetaObject target, DynamicMetaObject value, DynamicMetaObject errorSuggestion)
+ {
+ throw new NotImplementedException();
+ }
+ }
+ }
+}
diff --git a/test/System.Json.Test.Unit/JsonValueDynamicTest.cs b/test/System.Json.Test.Unit/JsonValueDynamicTest.cs
new file mode 100644
index 00000000..cd0055c0
--- /dev/null
+++ b/test/System.Json.Test.Unit/JsonValueDynamicTest.cs
@@ -0,0 +1,468 @@
+using System.Collections.Generic;
+using System.Dynamic;
+using System.Runtime.Serialization.Json;
+using Xunit;
+
+namespace System.Json
+{
+ public class JsonValueDynamicTest
+ {
+ const string InvalidIndexType = "Invalid '{0}' index type; only 'System.String' and non-negative 'System.Int32' types are supported.";
+ const string NonSingleNonNullIndexNotSupported = "Null index or multidimensional indexing is not supported by this indexer; use 'System.Int32' or 'System.String' for array and object indexing respectively.";
+
+ [Fact]
+ public void SettingDifferentValueTypes()
+ {
+ dynamic dyn = new JsonObject();
+ dyn.boolean = AnyInstance.AnyBool;
+ dyn.int16 = AnyInstance.AnyShort;
+ dyn.int32 = AnyInstance.AnyInt;
+ dyn.int64 = AnyInstance.AnyLong;
+ dyn.uint16 = AnyInstance.AnyUShort;
+ dyn.uint32 = AnyInstance.AnyUInt;
+ dyn.uint64 = AnyInstance.AnyULong;
+ dyn.@char = AnyInstance.AnyChar;
+ dyn.dbl = AnyInstance.AnyDouble;
+ dyn.flt = AnyInstance.AnyFloat;
+ dyn.dec = AnyInstance.AnyDecimal;
+ dyn.str = AnyInstance.AnyString;
+ dyn.uri = AnyInstance.AnyUri;
+ dyn.@byte = AnyInstance.AnyByte;
+ dyn.@sbyte = AnyInstance.AnySByte;
+ dyn.guid = AnyInstance.AnyGuid;
+ dyn.dateTime = AnyInstance.AnyDateTime;
+ dyn.dateTimeOffset = AnyInstance.AnyDateTimeOffset;
+ dyn.JsonArray = AnyInstance.AnyJsonArray;
+ dyn.JsonPrimitive = AnyInstance.AnyJsonPrimitive;
+ dyn.JsonObject = AnyInstance.AnyJsonObject;
+
+ JsonObject jo = (JsonObject)dyn;
+ Assert.Equal(AnyInstance.AnyBool, (bool)jo["boolean"]);
+ Assert.Equal(AnyInstance.AnyShort, (short)jo["int16"]);
+ Assert.Equal(AnyInstance.AnyUShort, (ushort)jo["uint16"]);
+ Assert.Equal(AnyInstance.AnyInt, (int)jo["int32"]);
+ Assert.Equal(AnyInstance.AnyUInt, (uint)jo["uint32"]);
+ Assert.Equal(AnyInstance.AnyLong, (long)jo["int64"]);
+ Assert.Equal(AnyInstance.AnyULong, (ulong)jo["uint64"]);
+ Assert.Equal(AnyInstance.AnySByte, (sbyte)jo["sbyte"]);
+ Assert.Equal(AnyInstance.AnyByte, (byte)jo["byte"]);
+ Assert.Equal(AnyInstance.AnyChar, (char)jo["char"]);
+ Assert.Equal(AnyInstance.AnyDouble, (double)jo["dbl"]);
+ Assert.Equal(AnyInstance.AnyFloat, (float)jo["flt"]);
+ Assert.Equal(AnyInstance.AnyDecimal, (decimal)jo["dec"]);
+ Assert.Equal(AnyInstance.AnyString, (string)jo["str"]);
+ Assert.Equal(AnyInstance.AnyUri, (Uri)jo["uri"]);
+ Assert.Equal(AnyInstance.AnyGuid, (Guid)jo["guid"]);
+ Assert.Equal(AnyInstance.AnyDateTime, (DateTime)jo["dateTime"]);
+ Assert.Equal(AnyInstance.AnyDateTimeOffset, (DateTimeOffset)jo["dateTimeOffset"]);
+ Assert.Same(AnyInstance.AnyJsonArray, jo["JsonArray"]);
+ Assert.Equal(AnyInstance.AnyJsonPrimitive, jo["JsonPrimitive"]);
+ Assert.Same(AnyInstance.AnyJsonObject, jo["JsonObject"]);
+
+ Assert.Equal(AnyInstance.AnyBool, (bool)dyn.boolean);
+ Assert.Equal(AnyInstance.AnyShort, (short)dyn.int16);
+ Assert.Equal(AnyInstance.AnyUShort, (ushort)dyn.uint16);
+ Assert.Equal(AnyInstance.AnyInt, (int)dyn.int32);
+ Assert.Equal(AnyInstance.AnyUInt, (uint)dyn.uint32);
+ Assert.Equal(AnyInstance.AnyLong, (long)dyn.int64);
+ Assert.Equal(AnyInstance.AnyULong, (ulong)dyn.uint64);
+ Assert.Equal(AnyInstance.AnySByte, (sbyte)dyn.@sbyte);
+ Assert.Equal(AnyInstance.AnyByte, (byte)dyn.@byte);
+ Assert.Equal(AnyInstance.AnyChar, (char)dyn.@char);
+ Assert.Equal(AnyInstance.AnyDouble, (double)dyn.dbl);
+ Assert.Equal(AnyInstance.AnyFloat, (float)dyn.flt);
+ Assert.Equal(AnyInstance.AnyDecimal, (decimal)dyn.dec);
+ Assert.Equal(AnyInstance.AnyString, (string)dyn.str);
+ Assert.Equal(AnyInstance.AnyUri, (Uri)dyn.uri);
+ Assert.Equal(AnyInstance.AnyGuid, (Guid)dyn.guid);
+ Assert.Equal(AnyInstance.AnyDateTime, (DateTime)dyn.dateTime);
+ Assert.Equal(AnyInstance.AnyDateTimeOffset, (DateTimeOffset)dyn.dateTimeOffset);
+ Assert.Same(AnyInstance.AnyJsonArray, dyn.JsonArray);
+ Assert.Equal(AnyInstance.AnyJsonPrimitive, dyn.JsonPrimitive);
+ Assert.Same(AnyInstance.AnyJsonObject, dyn.JsonObject);
+
+ ExceptionHelper.Throws<ArgumentException>(delegate { dyn.other = Console.Out; });
+ ExceptionHelper.Throws<ArgumentException>(delegate { dyn.other = dyn.NonExistentProp; });
+ }
+
+ [Fact]
+ public void NullTests()
+ {
+ dynamic dyn = new JsonObject();
+ JsonObject jo = (JsonObject)dyn;
+
+ dyn.@null = null;
+ Assert.Same(dyn.@null, AnyInstance.DefaultJsonValue);
+
+ jo["@null"] = null;
+ Assert.Null(jo["@null"]);
+ }
+
+ [Fact]
+ public void DynamicNotationTest()
+ {
+ bool boolValue;
+ JsonValue jsonValue;
+
+ Person person = Person.CreateSample();
+ dynamic jo = JsonValueExtensions.CreateFrom(person);
+
+ dynamic target = jo;
+ Assert.Equal<int>(person.Age, target.Age.ReadAs<int>()); // JsonPrimitive
+ Assert.Equal<string>(person.Address.ToString(), ((JsonObject)target.Address).ReadAsType<Address>().ToString()); // JsonObject
+
+ target = jo.Address.City; // JsonPrimitive
+ Assert.NotNull(target);
+ Assert.Equal<string>(target.ReadAs<string>(), person.Address.City);
+
+ target = jo.Friends; // JsonArray
+ Assert.NotNull(target);
+ jsonValue = target as JsonValue;
+ Assert.Equal<int>(person.Friends.Count, jsonValue.ReadAsType<List<Person>>().Count);
+
+ target = jo.Friends[1].Address.City;
+ Assert.NotNull(target);
+ Assert.Equal<string>(target.ReadAs<string>(), person.Address.City);
+
+ target = jo.Address.NonExistentProp.NonExistentProp2; // JsonObject (default)
+ Assert.NotNull(target);
+ Assert.True(jo is JsonObject);
+ Assert.False(target.TryReadAs<bool>(out boolValue));
+ Assert.True(target.TryReadAs<JsonValue>(out jsonValue));
+ Assert.Same(target, jsonValue);
+
+ Assert.Same(jo.Address.NonExistent, AnyInstance.DefaultJsonValue);
+ Assert.Same(jo.Friends[1000], AnyInstance.DefaultJsonValue);
+ Assert.Same(jo.Age.NonExistentProp, AnyInstance.DefaultJsonValue);
+ Assert.Same(jo.Friends.NonExistentProp, AnyInstance.DefaultJsonValue);
+ }
+
+ [Fact]
+ public void PropertyAccessTest()
+ {
+ Person p = AnyInstance.AnyPerson;
+ JsonObject jo = JsonValueExtensions.CreateFrom(p) as JsonObject;
+ JsonArray ja = JsonValueExtensions.CreateFrom(p.Friends) as JsonArray;
+ JsonPrimitive jp = AnyInstance.AnyJsonPrimitive;
+ JsonValue jv = AnyInstance.DefaultJsonValue;
+
+ dynamic jod = jo;
+ dynamic jad = ja;
+ dynamic jpd = jp;
+ dynamic jvd = jv;
+
+ Assert.Equal(jo.Count, jod.Count);
+ Assert.Equal(jo.JsonType, jod.JsonType);
+ Assert.Equal(jo.Keys.Count, jod.Keys.Count);
+ Assert.Equal(jo.Values.Count, jod.Values.Count);
+ Assert.Equal(p.Age, (int)jod.Age);
+ Assert.Equal(p.Age, (int)jod["Age"]);
+ Assert.Equal(p.Age, (int)jo["Age"]);
+ Assert.Equal(p.Address.City, (string)jo["Address"]["City"]);
+ Assert.Equal(p.Address.City, (string)jod["Address"]["City"]);
+ Assert.Equal(p.Address.City, (string)jod.Address.City);
+
+ Assert.Equal(p.Friends.Count, ja.Count);
+ Assert.Equal(ja.Count, jad.Count);
+ Assert.Equal(ja.IsReadOnly, jad.IsReadOnly);
+ Assert.Equal(ja.JsonType, jad.JsonType);
+ Assert.Equal(p.Friends[0].Age, (int)ja[0]["Age"]);
+ Assert.Equal(p.Friends[0].Age, (int)jad[0].Age);
+
+ Assert.Equal(jp.JsonType, jpd.JsonType);
+ }
+
+ [Fact]
+ public void ConcatDynamicAssignmentTest()
+ {
+ string value = "MyValue";
+ dynamic dynArray = JsonValue.Parse(AnyInstance.AnyJsonArray.ToString());
+ dynamic dynObj = JsonValue.Parse(AnyInstance.AnyJsonObject.ToString());
+
+ JsonValue target;
+
+ target = dynArray[0] = dynArray[1] = dynArray[2] = value;
+ Assert.Equal((string)target, value);
+ Assert.Equal((string)dynArray[0], value);
+ Assert.Equal((string)dynArray[1], value);
+ Assert.Equal((string)dynArray[2], value);
+
+ target = dynObj["key0"] = dynObj["key1"] = dynObj["key2"] = value;
+ Assert.Equal((string)target, value);
+ Assert.Equal((string)dynObj["key0"], value);
+ Assert.Equal((string)dynObj["key1"], value);
+ Assert.Equal((string)dynObj["key2"], value);
+ foreach (KeyValuePair<string, JsonValue> pair in AnyInstance.AnyJsonObject)
+ {
+ Assert.Equal<string>(AnyInstance.AnyJsonObject[pair.Key].ToString(), dynObj[pair.Key].ToString());
+ }
+ }
+
+ [Fact]
+ public void IndexConversionTest()
+ {
+ dynamic target = AnyInstance.AnyJsonArray;
+ dynamic expected = AnyInstance.AnyJsonArray[0];
+ dynamic result;
+
+ dynamic[] zero_indexes =
+ {
+ (short)0,
+ (ushort)0,
+ (byte)0,
+ (sbyte)0,
+ (char)0,
+ (int)0
+ };
+
+
+ result = target[(short)0];
+ Assert.Same(expected, result);
+ result = target[(ushort)0];
+ Assert.Same(expected, result);
+ result = target[(byte)0];
+ Assert.Same(expected, result);
+ result = target[(sbyte)0];
+ Assert.Same(expected, result);
+ result = target[(char)0];
+ Assert.Same(expected, result);
+
+ foreach (dynamic zero_index in zero_indexes)
+ {
+ result = target[zero_index];
+ Assert.Same(expected, result);
+ }
+ }
+
+ [Fact]
+ public void InvalidIndexTest()
+ {
+ object index1 = new object();
+ bool index2 = true;
+ Person index3 = AnyInstance.AnyPerson;
+ JsonObject jo = AnyInstance.AnyJsonObject;
+
+ dynamic target;
+ object ret;
+
+ JsonValue[] values = { AnyInstance.AnyJsonObject, AnyInstance.AnyJsonArray };
+
+ foreach (JsonValue value in values)
+ {
+ target = value;
+
+ ExceptionHelper.Throws<ArgumentException>(delegate { ret = target[index1]; }, String.Format(InvalidIndexType, index1.GetType().FullName));
+ ExceptionHelper.Throws<ArgumentException>(delegate { ret = target[index2]; }, String.Format(InvalidIndexType, index2.GetType().FullName));
+ ExceptionHelper.Throws<ArgumentException>(delegate { ret = target[index3]; }, String.Format(InvalidIndexType, index3.GetType().FullName));
+ ExceptionHelper.Throws<ArgumentException>(delegate { ret = target[null]; }, NonSingleNonNullIndexNotSupported);
+
+ ExceptionHelper.Throws<ArgumentException>(delegate { ret = target[0, 1]; }, NonSingleNonNullIndexNotSupported);
+ ExceptionHelper.Throws<ArgumentException>(delegate { ret = target["key1", "key2"]; }, NonSingleNonNullIndexNotSupported);
+
+ ExceptionHelper.Throws<ArgumentException>(delegate { ret = target[true]; }, String.Format(InvalidIndexType, true.GetType().FullName));
+
+ ExceptionHelper.Throws<ArgumentException>(delegate { target[index1] = jo; }, String.Format(InvalidIndexType, index1.GetType().FullName));
+ ExceptionHelper.Throws<ArgumentException>(delegate { target[index2] = jo; }, String.Format(InvalidIndexType, index2.GetType().FullName));
+ ExceptionHelper.Throws<ArgumentException>(delegate { target[index3] = jo; }, String.Format(InvalidIndexType, index3.GetType().FullName));
+ ExceptionHelper.Throws<ArgumentException>(delegate { target[null] = jo; }, NonSingleNonNullIndexNotSupported);
+
+ ExceptionHelper.Throws<ArgumentException>(delegate { target[0, 1] = jo; }, NonSingleNonNullIndexNotSupported);
+ ExceptionHelper.Throws<ArgumentException>(delegate { target["key1", "key2"] = jo; }, NonSingleNonNullIndexNotSupported);
+
+ ExceptionHelper.Throws<ArgumentException>(delegate { target[true] = jo; }, String.Format(InvalidIndexType, true.GetType().FullName));
+ }
+ }
+
+ [Fact]
+ public void InvalidCastingTests()
+ {
+ dynamic dyn;
+ string value = "NameValue";
+
+ dyn = AnyInstance.AnyJsonPrimitive;
+ ExceptionHelper.Throws<InvalidOperationException>(delegate { dyn.name = value; });
+
+ dyn = AnyInstance.AnyJsonArray;
+ ExceptionHelper.Throws<InvalidOperationException>(delegate { dyn.name = value; });
+
+ dyn = new JsonObject(AnyInstance.AnyJsonObject);
+ dyn.name = value;
+ Assert.Equal((string)dyn.name, value);
+
+ dyn = AnyInstance.DefaultJsonValue;
+ ExceptionHelper.Throws<InvalidOperationException>(delegate { dyn.name = value; });
+ }
+
+ [Fact]
+ public void CastTests()
+ {
+ dynamic dyn = JsonValueExtensions.CreateFrom(AnyInstance.AnyPerson) as JsonObject;
+ string city = dyn.Address.City;
+
+ Assert.Equal<string>(AnyInstance.AnyPerson.Address.City, dyn.Address.City.ReadAs<string>());
+ Assert.Equal<string>(AnyInstance.AnyPerson.Address.City, city);
+
+ JsonValue[] values =
+ {
+ AnyInstance.AnyInt,
+ AnyInstance.AnyString,
+ AnyInstance.AnyDateTime,
+ AnyInstance.AnyJsonObject,
+ AnyInstance.AnyJsonArray,
+ AnyInstance.DefaultJsonValue
+ };
+
+ int loopCount = 2;
+ bool explicitCast = true;
+
+ while (loopCount > 0)
+ {
+ loopCount--;
+
+ foreach (JsonValue jv in values)
+ {
+ EvaluateNoExceptions<JsonValue>(null, explicitCast);
+ EvaluateNoExceptions<JsonValue>(jv, explicitCast);
+ EvaluateNoExceptions<object>(jv, explicitCast);
+ EvaluateNoExceptions<IDynamicMetaObjectProvider>(jv, explicitCast);
+ EvaluateNoExceptions<IEnumerable<KeyValuePair<string, JsonValue>>>(jv, explicitCast);
+ EvaluateNoExceptions<string>(null, explicitCast);
+
+ EvaluateExpectExceptions<int>(null, explicitCast);
+ EvaluateExpectExceptions<Person>(jv, explicitCast);
+ EvaluateExpectExceptions<Exception>(jv, explicitCast);
+
+ EvaluateIgnoreExceptions<JsonObject>(jv, explicitCast);
+ EvaluateIgnoreExceptions<int>(jv, explicitCast);
+ EvaluateIgnoreExceptions<string>(jv, explicitCast);
+ EvaluateIgnoreExceptions<DateTime>(jv, explicitCast);
+ EvaluateIgnoreExceptions<JsonArray>(jv, explicitCast);
+ EvaluateIgnoreExceptions<JsonPrimitive>(jv, explicitCast);
+ }
+
+ explicitCast = false;
+ }
+
+ EvaluateNoExceptions<IDictionary<string, JsonValue>>(AnyInstance.AnyJsonObject, false);
+ EvaluateNoExceptions<IList<JsonValue>>(AnyInstance.AnyJsonArray, false);
+ }
+
+ static void EvaluateNoExceptions<T>(JsonValue value, bool cast)
+ {
+ Evaluate<T>(value, cast, false, true);
+ }
+
+ static void EvaluateExpectExceptions<T>(JsonValue value, bool cast)
+ {
+ Evaluate<T>(value, cast, true, true);
+ }
+
+ static void EvaluateIgnoreExceptions<T>(JsonValue value, bool cast)
+ {
+ Evaluate<T>(value, cast, true, false);
+ }
+
+ static void Evaluate<T>(JsonValue value, bool cast, bool throwExpected, bool assertExceptions)
+ {
+ T ret2;
+ object obj = null;
+ bool exceptionThrown = false;
+ string retstr2, retstr1;
+
+ Console.WriteLine("Test info: expected:[{0}], explicitCast type:[{1}]", value, typeof(T));
+
+ try
+ {
+ if (typeof(int) == typeof(T))
+ {
+ obj = ((int)value);
+ }
+ else if (typeof(string) == typeof(T))
+ {
+ obj = ((string)value);
+ }
+ else if (typeof(DateTime) == typeof(T))
+ {
+ obj = ((DateTime)value);
+ }
+ else if (typeof(IList<JsonValue>) == typeof(T))
+ {
+ obj = (IList<JsonValue>)value;
+ }
+ else if (typeof(IDictionary<string, JsonValue>) == typeof(T))
+ {
+ obj = (IDictionary<string, JsonValue>)value;
+ }
+ else if (typeof(JsonValue) == typeof(T))
+ {
+ obj = (JsonValue)value;
+ }
+ else if (typeof(JsonObject) == typeof(T))
+ {
+ obj = (JsonObject)value;
+ }
+ else if (typeof(JsonArray) == typeof(T))
+ {
+ obj = (JsonArray)value;
+ }
+ else if (typeof(JsonPrimitive) == typeof(T))
+ {
+ obj = (JsonPrimitive)value;
+ }
+ else
+ {
+ obj = (T)(object)value;
+ }
+
+ retstr1 = obj == null ? "null" : obj.ToString();
+ }
+ catch (Exception ex)
+ {
+ exceptionThrown = true;
+ retstr1 = ex.Message;
+ }
+
+ if (assertExceptions)
+ {
+ Assert.Equal<bool>(throwExpected, exceptionThrown);
+ }
+
+ exceptionThrown = false;
+
+ try
+ {
+ dynamic dyn = value as dynamic;
+ if (cast)
+ {
+ ret2 = (T)dyn;
+ }
+ else
+ {
+ ret2 = dyn;
+ }
+ retstr2 = ret2 != null ? ret2.ToString() : "null";
+ }
+ catch (Exception ex)
+ {
+ exceptionThrown = true;
+ retstr2 = ex.Message;
+ }
+
+ if (assertExceptions)
+ {
+ Assert.Equal<bool>(throwExpected, exceptionThrown);
+ }
+
+ // fixup string
+ retstr1 = retstr1.Replace("\'Person\'", String.Format("\'{0}\'", typeof(Person).FullName));
+ if (retstr1.EndsWith(".")) retstr1 = retstr1.Substring(0, retstr1.Length - 1);
+
+ // fixup string
+ retstr2 = retstr2.Replace("\'string\'", String.Format("\'{0}\'", typeof(string).FullName));
+ retstr2 = retstr2.Replace("\'int\'", String.Format("\'{0}\'", typeof(int).FullName));
+ if (retstr2.EndsWith(".")) retstr2 = retstr2.Substring(0, retstr2.Length - 1);
+
+ Assert.Equal<string>(retstr1, retstr2);
+ }
+ }
+}
diff --git a/test/System.Json.Test.Unit/JsonValueLinqExtensionsTest.cs b/test/System.Json.Test.Unit/JsonValueLinqExtensionsTest.cs
new file mode 100644
index 00000000..7729e128
--- /dev/null
+++ b/test/System.Json.Test.Unit/JsonValueLinqExtensionsTest.cs
@@ -0,0 +1,35 @@
+using System.Collections.Generic;
+using System.Linq;
+using Xunit;
+
+namespace System.Json
+{
+ public class JsonValueLinqExtensionsTest
+ {
+ [Fact]
+ public void ToJsonArrayTest()
+ {
+ var target = (new List<int>(new[] { 1, 2, 3 }).Select(i => (JsonValue)i).ToJsonArray());
+ Assert.Equal("[1,2,3]", target.ToString());
+ }
+
+ [Fact]
+ public void ToJsonObjectTest()
+ {
+ JsonValue jv = new JsonObject { { "one", 1 }, { "two", 2 }, { "three", 3 } };
+
+ var result = from n in jv
+ where n.Value.ReadAs<int>() > 1
+ select n;
+ Assert.Equal("{\"two\":2,\"three\":3}", result.ToJsonObject().ToString());
+ }
+
+ [Fact]
+ public void ToJsonObjectFromArray()
+ {
+ JsonArray ja = new JsonArray("first", "second");
+ JsonObject jo = ja.ToJsonObject();
+ Assert.Equal("{\"0\":\"first\",\"1\":\"second\"}", jo.ToString());
+ }
+ }
+}
diff --git a/test/System.Json.Test.Unit/JsonValueTest.cs b/test/System.Json.Test.Unit/JsonValueTest.cs
new file mode 100644
index 00000000..52f10080
--- /dev/null
+++ b/test/System.Json.Test.Unit/JsonValueTest.cs
@@ -0,0 +1,565 @@
+using System.Collections;
+using System.Collections.Generic;
+using System.Globalization;
+using System.IO;
+using System.Runtime.Serialization;
+using System.Runtime.Serialization.Json;
+using System.Text;
+using Xunit;
+
+namespace System.Json
+{
+ public class JsonValueTest
+ {
+ const string IndexerNotSupportedOnJsonType = "'{0}' type indexer is not supported on JsonValue of 'JsonType.{1}' type.";
+ const string InvalidIndexType = "Invalid '{0}' index type; only 'System.String' and non-negative 'System.Int32' types are supported.\r\nParameter name: indexes";
+
+ [Fact]
+ public void ContainsKeyTest()
+ {
+ JsonObject target = new JsonObject { { AnyInstance.AnyString, AnyInstance.AnyString } };
+ Assert.True(target.ContainsKey(AnyInstance.AnyString));
+ }
+
+ [Fact]
+ public void LoadTest()
+ {
+ string json = "{\"a\":123,\"b\":[false,null,12.34]}";
+ foreach (bool useLoadTextReader in new bool[] { false, true })
+ {
+ JsonValue jv;
+ if (useLoadTextReader)
+ {
+ using (StringReader sr = new StringReader(json))
+ {
+ jv = JsonValue.Load(sr);
+ }
+ }
+ else
+ {
+ using (MemoryStream ms = new MemoryStream(Encoding.UTF8.GetBytes(json)))
+ {
+ jv = JsonValue.Load(ms);
+ }
+ }
+
+ Assert.Equal(json, jv.ToString());
+ }
+
+ ExceptionHelper.Throws<ArgumentNullException>(() => JsonValue.Load((Stream)null));
+ ExceptionHelper.Throws<ArgumentNullException>(() => JsonValue.Load((TextReader)null));
+ }
+
+ [Fact]
+ public void ParseTest()
+ {
+ JsonValue target;
+ string indentedJson = "{\r\n \"a\": 123,\r\n \"b\": [\r\n false,\r\n null,\r\n 12.34\r\n ],\r\n \"with space\": \"hello\",\r\n \"\": \"empty key\",\r\n \"withTypeHint\": {\r\n \"__type\": \"typeHint\"\r\n }\r\n}";
+ string plainJson = indentedJson.Replace("\r\n", "").Replace(" ", "").Replace("emptykey", "empty key").Replace("withspace", "with space");
+
+ target = JsonValue.Parse(indentedJson);
+ Assert.Equal(plainJson, target.ToString());
+
+ target = JsonValue.Parse(plainJson);
+ Assert.Equal(plainJson, target.ToString());
+
+ ExceptionHelper.Throws<ArgumentNullException>(() => JsonValue.Parse(null));
+ ExceptionHelper.Throws<ArgumentException>(() => JsonValue.Parse(""));
+ }
+
+ [Fact]
+ public void ParseNumbersTest()
+ {
+ string json = "{\"long\":12345678901234,\"zero\":0.0,\"double\":1.23e+200}";
+ string expectedJson = "{\"long\":12345678901234,\"zero\":0,\"double\":1.23E+200}";
+ JsonValue jv = JsonValue.Parse(json);
+
+ Assert.Equal(expectedJson, jv.ToString());
+ Assert.Equal(12345678901234L, (long)jv["long"]);
+ Assert.Equal<double>(0, jv["zero"].ReadAs<double>());
+ Assert.Equal<double>(1.23e200, jv["double"].ReadAs<double>());
+
+ ExceptionHelper.Throws<ArgumentException>(() => JsonValue.Parse("[1.2e+400]"));
+ }
+
+ [Fact]
+ public void ReadAsTest()
+ {
+ JsonValue target = new JsonPrimitive(AnyInstance.AnyInt);
+ Assert.Equal(AnyInstance.AnyInt.ToString(CultureInfo.InvariantCulture), target.ReadAs(typeof(string)));
+ Assert.Equal(AnyInstance.AnyInt.ToString(CultureInfo.InvariantCulture), target.ReadAs<string>());
+ object value;
+ double dblValue;
+ Assert.True(target.TryReadAs(typeof(double), out value));
+ Assert.True(target.TryReadAs<double>(out dblValue));
+ Assert.Equal(Convert.ToDouble(AnyInstance.AnyInt, CultureInfo.InvariantCulture), (double)value);
+ Assert.Equal(Convert.ToDouble(AnyInstance.AnyInt, CultureInfo.InvariantCulture), dblValue);
+ Assert.False(target.TryReadAs(typeof(Guid), out value), "TryReadAs should have failed to read a double as a Guid");
+ Assert.Null(value);
+ }
+
+ [Fact(Skip = "See bug #228569 in CSDMain")]
+ public void SaveTest()
+ {
+ JsonObject jo = new JsonObject
+ {
+ { "first", 1 },
+ { "second", 2 },
+ };
+ JsonValue jv = new JsonArray(123, null, jo);
+ string indentedJson = "[\r\n 123,\r\n null,\r\n {\r\n \"first\": 1,\r\n \"second\": 2\r\n }\r\n]";
+ string plainJson = indentedJson.Replace("\r\n", "").Replace(" ", "");
+
+ SaveJsonValue(jv, plainJson, false);
+ SaveJsonValue(jv, plainJson, true);
+
+ JsonValue target = AnyInstance.DefaultJsonValue;
+ using (MemoryStream ms = new MemoryStream())
+ {
+ ExceptionHelper.Throws<InvalidOperationException>(() => target.Save(ms));
+ }
+ }
+
+ private static void SaveJsonValue(JsonValue jv, string expectedJson, bool useStream)
+ {
+ string json;
+ if (useStream)
+ {
+ using (MemoryStream ms = new MemoryStream())
+ {
+ jv.Save(ms);
+ json = Encoding.UTF8.GetString(ms.ToArray());
+ }
+ }
+ else
+ {
+ StringBuilder sb = new StringBuilder();
+ using (TextWriter writer = new StringWriter(sb))
+ {
+ jv.Save(writer);
+ json = sb.ToString();
+ }
+ }
+
+ Assert.Equal(expectedJson, json);
+ }
+
+ [Fact]
+ public void GetEnumeratorTest()
+ {
+ IEnumerable target = new JsonArray(AnyInstance.AnyGuid);
+ IEnumerator enumerator = target.GetEnumerator();
+ Assert.True(enumerator.MoveNext());
+ Assert.Equal(AnyInstance.AnyGuid, (Guid)(JsonValue)enumerator.Current);
+ Assert.False(enumerator.MoveNext());
+
+ target = new JsonObject();
+ enumerator = target.GetEnumerator();
+ Assert.False(enumerator.MoveNext());
+ }
+
+ [Fact]
+ public void IEnumerableTest()
+ {
+ JsonValue target = AnyInstance.AnyJsonArray;
+
+ // Test IEnumerable<JsonValue> on JsonArray
+ int count = 0;
+
+ foreach (JsonValue value in ((JsonArray)target))
+ {
+ Assert.Same(target[count], value);
+ count++;
+ }
+
+ Assert.Equal<int>(target.Count, count);
+
+ // Test IEnumerable<KeyValuePair<string, JsonValue>> on JsonValue
+ count = 0;
+ foreach (KeyValuePair<string, JsonValue> pair in target)
+ {
+ int index = Int32.Parse(pair.Key);
+ Assert.Equal(count, index);
+ Assert.Same(target[index], pair.Value);
+ count++;
+ }
+ Assert.Equal<int>(target.Count, count);
+
+ target = AnyInstance.AnyJsonObject;
+ count = 0;
+ foreach (KeyValuePair<string, JsonValue> pair in target)
+ {
+ count++;
+ Assert.Same(AnyInstance.AnyJsonObject[pair.Key], pair.Value);
+ }
+ Assert.Equal<int>(AnyInstance.AnyJsonObject.Count, count);
+ }
+
+ [Fact]
+ public void GetJsonPrimitiveEnumeratorTest()
+ {
+ JsonValue target = AnyInstance.AnyJsonPrimitive;
+ IEnumerator<KeyValuePair<string, JsonValue>> enumerator = target.GetEnumerator();
+ Assert.False(enumerator.MoveNext());
+ }
+
+ [Fact]
+ public void GetJsonUndefinedEnumeratorTest()
+ {
+ JsonValue target = AnyInstance.AnyJsonPrimitive.AsDynamic().IDontExist;
+ IEnumerator<KeyValuePair<string, JsonValue>> enumerator = target.GetEnumerator();
+ Assert.False(enumerator.MoveNext());
+ }
+
+ [Fact]
+ public void ToStringTest()
+ {
+ JsonObject jo = new JsonObject
+ {
+ { "first", 1 },
+ { "second", 2 },
+ { "third", new JsonObject { { "inner_one", 4 }, { "", null }, { "inner_3", "" } } },
+ { "fourth", new JsonArray { "Item1", 2, false } },
+ { "fifth", null }
+ };
+ JsonValue jv = new JsonArray(123, null, jo);
+ string expectedJson = "[\r\n 123,\r\n null,\r\n {\r\n \"first\": 1,\r\n \"second\": 2,\r\n \"third\": {\r\n \"inner_one\": 4,\r\n \"\": null,\r\n \"inner_3\": \"\"\r\n },\r\n \"fourth\": [\r\n \"Item1\",\r\n 2,\r\n false\r\n ],\r\n \"fifth\": null\r\n }\r\n]";
+ Assert.Equal<string>(expectedJson.Replace("\r\n", "").Replace(" ", ""), jv.ToString());
+ }
+
+ [Fact]
+ public void CastTests()
+ {
+ int value = 10;
+ JsonValue target = new JsonPrimitive(value);
+
+ int v1 = JsonValue.CastValue<int>(target);
+ Assert.Equal<int>(value, v1);
+ v1 = (int)target;
+ Assert.Equal<int>(value, v1);
+
+ long v2 = JsonValue.CastValue<long>(target);
+ Assert.Equal<long>(value, v2);
+ v2 = (long)target;
+ Assert.Equal<long>(value, v2);
+
+ string s = JsonValue.CastValue<string>(target);
+ Assert.Equal<string>(value.ToString(), s);
+ s = (string)target;
+ Assert.Equal<string>(value.ToString(), s);
+
+ object obj = JsonValue.CastValue<object>(target);
+ Assert.Equal(target, obj);
+ obj = (object)target;
+ Assert.Equal(target, obj);
+
+ object nill = JsonValue.CastValue<object>(null);
+ Assert.Null(nill);
+
+ dynamic dyn = target;
+ JsonValue defaultJv = dyn.IamDefault;
+ nill = JsonValue.CastValue<string>(defaultJv);
+ Assert.Null(nill);
+ nill = (string)defaultJv;
+ Assert.Null(nill);
+
+ obj = JsonValue.CastValue<object>(defaultJv);
+ Assert.Same(defaultJv, obj);
+ obj = (object)defaultJv;
+ Assert.Same(defaultJv, obj);
+
+ JsonValue jv = JsonValue.CastValue<JsonValue>(target);
+ Assert.Equal<JsonValue>(target, jv);
+
+ jv = JsonValue.CastValue<JsonValue>(defaultJv);
+ Assert.Equal<JsonValue>(defaultJv, jv);
+
+ jv = JsonValue.CastValue<JsonPrimitive>(target);
+ Assert.Equal<JsonValue>(target, jv);
+
+ ExceptionHelper.Throws<InvalidCastException>(delegate { int i = JsonValue.CastValue<int>(null); });
+ ExceptionHelper.Throws<InvalidCastException>(delegate { int i = JsonValue.CastValue<int>(defaultJv); });
+ ExceptionHelper.Throws<InvalidCastException>(delegate { int i = JsonValue.CastValue<char>(target); });
+ }
+
+ [Fact]
+ public void CastingTests()
+ {
+ JsonValue target = new JsonPrimitive(AnyInstance.AnyInt);
+
+ Assert.Equal(AnyInstance.AnyInt.ToString(CultureInfo.InvariantCulture), (string)target);
+ Assert.Equal(Convert.ToDouble(AnyInstance.AnyInt, CultureInfo.InvariantCulture), (double)target);
+
+ Assert.Equal(AnyInstance.AnyString, (string)(JsonValue)AnyInstance.AnyString);
+ Assert.Equal(AnyInstance.AnyChar, (char)(JsonValue)AnyInstance.AnyChar);
+ Assert.Equal(AnyInstance.AnyUri, (Uri)(JsonValue)AnyInstance.AnyUri);
+ Assert.Equal(AnyInstance.AnyGuid, (Guid)(JsonValue)AnyInstance.AnyGuid);
+ Assert.Equal(AnyInstance.AnyDateTime, (DateTime)(JsonValue)AnyInstance.AnyDateTime);
+ Assert.Equal(AnyInstance.AnyDateTimeOffset, (DateTimeOffset)(JsonValue)AnyInstance.AnyDateTimeOffset);
+ Assert.Equal(AnyInstance.AnyBool, (bool)(JsonValue)AnyInstance.AnyBool);
+ Assert.Equal(AnyInstance.AnyByte, (byte)(JsonValue)AnyInstance.AnyByte);
+ Assert.Equal(AnyInstance.AnyShort, (short)(JsonValue)AnyInstance.AnyShort);
+ Assert.Equal(AnyInstance.AnyInt, (int)(JsonValue)AnyInstance.AnyInt);
+ Assert.Equal(AnyInstance.AnyLong, (long)(JsonValue)AnyInstance.AnyLong);
+ Assert.Equal(AnyInstance.AnySByte, (sbyte)(JsonValue)AnyInstance.AnySByte);
+ Assert.Equal(AnyInstance.AnyUShort, (ushort)(JsonValue)AnyInstance.AnyUShort);
+ Assert.Equal(AnyInstance.AnyUInt, (uint)(JsonValue)AnyInstance.AnyUInt);
+ Assert.Equal(AnyInstance.AnyULong, (ulong)(JsonValue)AnyInstance.AnyULong);
+ Assert.Equal(AnyInstance.AnyDecimal, (decimal)(JsonValue)AnyInstance.AnyDecimal);
+ Assert.Equal(AnyInstance.AnyFloat, (float)(JsonValue)AnyInstance.AnyFloat);
+ Assert.Equal(AnyInstance.AnyDouble, (double)(JsonValue)AnyInstance.AnyDouble);
+
+ Uri uri = null;
+ string str = null;
+
+ JsonValue jv = uri;
+ Assert.Null(jv);
+ uri = (Uri)jv;
+ Assert.Null(uri);
+
+ jv = str;
+ Assert.Null(jv);
+ str = (string)jv;
+ Assert.Null(str);
+
+ ExceptionHelper.Throws<InvalidCastException>(delegate { var s = (string)AnyInstance.AnyJsonArray; });
+ ExceptionHelper.Throws<InvalidCastException>(delegate { var s = (string)AnyInstance.AnyJsonObject; });
+ }
+
+ [Fact]
+ public void InvalidCastTest()
+ {
+ JsonValue nullValue = (JsonValue)null;
+ JsonValue strValue = new JsonPrimitive(AnyInstance.AnyString);
+ JsonValue boolValue = new JsonPrimitive(AnyInstance.AnyBool);
+ JsonValue intValue = new JsonPrimitive(AnyInstance.AnyInt);
+
+ ExceptionHelper.Throws<InvalidCastException>(delegate { var v = (double)nullValue; });
+ ExceptionHelper.Throws<InvalidCastException>(delegate { var v = (double)strValue; });
+ ExceptionHelper.Throws<InvalidCastException>(delegate { var v = (double)boolValue; });
+ Assert.Equal<double>(AnyInstance.AnyInt, (double)intValue);
+
+ ExceptionHelper.Throws<InvalidCastException>(delegate { var v = (float)nullValue; });
+ ExceptionHelper.Throws<InvalidCastException>(delegate { var v = (float)strValue; });
+ ExceptionHelper.Throws<InvalidCastException>(delegate { var v = (float)boolValue; });
+ Assert.Equal<float>(AnyInstance.AnyInt, (float)intValue);
+
+ ExceptionHelper.Throws<InvalidCastException>(delegate { var v = (decimal)nullValue; });
+ ExceptionHelper.Throws<InvalidCastException>(delegate { var v = (decimal)strValue; });
+ ExceptionHelper.Throws<InvalidCastException>(delegate { var v = (decimal)boolValue; });
+ Assert.Equal<decimal>(AnyInstance.AnyInt, (decimal)intValue);
+
+ ExceptionHelper.Throws<InvalidCastException>(delegate { var v = (long)nullValue; });
+ ExceptionHelper.Throws<InvalidCastException>(delegate { var v = (long)strValue; });
+ ExceptionHelper.Throws<InvalidCastException>(delegate { var v = (long)boolValue; });
+ Assert.Equal<long>(AnyInstance.AnyInt, (long)intValue);
+
+ ExceptionHelper.Throws<InvalidCastException>(delegate { var v = (ulong)nullValue; });
+ ExceptionHelper.Throws<InvalidCastException>(delegate { var v = (ulong)strValue; });
+ ExceptionHelper.Throws<InvalidCastException>(delegate { var v = (ulong)boolValue; });
+ Assert.Equal<ulong>(AnyInstance.AnyInt, (ulong)intValue);
+
+ ExceptionHelper.Throws<InvalidCastException>(delegate { var v = (int)nullValue; });
+ ExceptionHelper.Throws<InvalidCastException>(delegate { var v = (int)strValue; });
+ ExceptionHelper.Throws<InvalidCastException>(delegate { var v = (int)boolValue; });
+ Assert.Equal<int>(AnyInstance.AnyInt, (int)intValue);
+
+ ExceptionHelper.Throws<InvalidCastException>(delegate { var v = (uint)nullValue; });
+ ExceptionHelper.Throws<InvalidCastException>(delegate { var v = (uint)strValue; });
+ ExceptionHelper.Throws<InvalidCastException>(delegate { var v = (uint)boolValue; });
+ Assert.Equal<uint>(AnyInstance.AnyInt, (uint)intValue);
+
+ ExceptionHelper.Throws<InvalidCastException>(delegate { var v = (short)nullValue; });
+ ExceptionHelper.Throws<InvalidCastException>(delegate { var v = (short)strValue; });
+ ExceptionHelper.Throws<InvalidCastException>(delegate { var v = (short)boolValue; });
+
+ ExceptionHelper.Throws<InvalidCastException>(delegate { var v = (ushort)nullValue; });
+ ExceptionHelper.Throws<InvalidCastException>(delegate { var v = (ushort)strValue; });
+ ExceptionHelper.Throws<InvalidCastException>(delegate { var v = (ushort)boolValue; });
+
+ ExceptionHelper.Throws<InvalidCastException>(delegate { var v = (sbyte)nullValue; });
+ ExceptionHelper.Throws<InvalidCastException>(delegate { var v = (sbyte)strValue; });
+ ExceptionHelper.Throws<InvalidCastException>(delegate { var v = (sbyte)boolValue; });
+
+ ExceptionHelper.Throws<InvalidCastException>(delegate { var v = (byte)nullValue; });
+ ExceptionHelper.Throws<InvalidCastException>(delegate { var v = (byte)strValue; });
+ ExceptionHelper.Throws<InvalidCastException>(delegate { var v = (byte)boolValue; });
+
+ ExceptionHelper.Throws<InvalidCastException>(delegate { var v = (Guid)nullValue; });
+ ExceptionHelper.Throws<InvalidCastException>(delegate { var v = (Guid)strValue; });
+ ExceptionHelper.Throws<InvalidCastException>(delegate { var v = (Guid)boolValue; });
+ ExceptionHelper.Throws<InvalidCastException>(delegate { var v = (Guid)intValue; });
+
+ ExceptionHelper.Throws<InvalidCastException>(delegate { var v = (DateTime)nullValue; });
+ ExceptionHelper.Throws<InvalidCastException>(delegate { var v = (DateTime)strValue; });
+ ExceptionHelper.Throws<InvalidCastException>(delegate { var v = (DateTime)boolValue; });
+ ExceptionHelper.Throws<InvalidCastException>(delegate { var v = (DateTime)intValue; });
+
+ ExceptionHelper.Throws<InvalidCastException>(delegate { var v = (char)nullValue; });
+ ExceptionHelper.Throws<InvalidCastException>(delegate { var v = (char)strValue; });
+ ExceptionHelper.Throws<InvalidCastException>(delegate { var v = (char)boolValue; });
+ ExceptionHelper.Throws<InvalidCastException>(delegate { var v = (char)intValue; });
+
+ ExceptionHelper.Throws<InvalidCastException>(delegate { var v = (DateTimeOffset)nullValue; });
+ ExceptionHelper.Throws<InvalidCastException>(delegate { var v = (DateTimeOffset)strValue; });
+ ExceptionHelper.Throws<InvalidCastException>(delegate { var v = (DateTimeOffset)boolValue; });
+ ExceptionHelper.Throws<InvalidCastException>(delegate { var v = (DateTimeOffset)intValue; });
+
+ Assert.Null((Uri)nullValue);
+ Assert.Equal(((Uri)strValue).ToString(), (string)strValue);
+ ExceptionHelper.Throws<InvalidCastException>(delegate { var v = (Uri)boolValue; });
+ ExceptionHelper.Throws<InvalidCastException>(delegate { var v = (Uri)intValue; });
+
+ ExceptionHelper.Throws<InvalidCastException>(delegate { var v = (bool)nullValue; });
+ ExceptionHelper.Throws<InvalidCastException>(delegate { var v = (bool)strValue; });
+ Assert.Equal(AnyInstance.AnyBool, (bool)boolValue);
+ ExceptionHelper.Throws<InvalidCastException>(delegate { var v = (bool)intValue; });
+
+ Assert.Equal(null, (string)nullValue);
+ Assert.Equal(AnyInstance.AnyString, (string)strValue);
+ Assert.Equal(AnyInstance.AnyBool.ToString().ToLowerInvariant(), ((string)boolValue).ToLowerInvariant());
+ Assert.Equal(AnyInstance.AnyInt.ToString(CultureInfo.InvariantCulture), (string)intValue);
+
+ ExceptionHelper.Throws<InvalidCastException>(delegate { var v = (int)nullValue; });
+ ExceptionHelper.Throws<InvalidCastException>(delegate { var v = (int)strValue; });
+ ExceptionHelper.Throws<InvalidCastException>(delegate { var v = (int)boolValue; });
+ Assert.Equal(AnyInstance.AnyInt, (int)intValue);
+ }
+
+ [Fact]
+ public void CountTest()
+ {
+ JsonArray ja = new JsonArray(1, 2);
+ Assert.Equal(2, ja.Count);
+
+ JsonObject jo = new JsonObject
+ {
+ { "key1", 123 },
+ { "key2", null },
+ { "key3", "hello" },
+ };
+ Assert.Equal(3, jo.Count);
+ }
+
+ [Fact]
+ public void ItemTest()
+ {
+ //// Positive tests for Item on JsonArray and JsonObject are on JsonArrayTest and JsonObjectTest, respectively.
+
+ JsonValue target;
+ target = AnyInstance.AnyJsonPrimitive;
+ ExceptionHelper.Throws<InvalidOperationException>(delegate { var c = target[1]; }, String.Format(IndexerNotSupportedOnJsonType, typeof(int), target.JsonType));
+ ExceptionHelper.Throws<InvalidOperationException>(delegate { target[0] = 123; }, String.Format(IndexerNotSupportedOnJsonType, typeof(int), target.JsonType));
+ ExceptionHelper.Throws<InvalidOperationException>(delegate { var c = target["key"]; }, String.Format(IndexerNotSupportedOnJsonType, typeof(string), target.JsonType));
+ ExceptionHelper.Throws<InvalidOperationException>(delegate { target["here"] = 123; }, String.Format(IndexerNotSupportedOnJsonType, typeof(string), target.JsonType));
+
+ target = AnyInstance.AnyJsonObject;
+ ExceptionHelper.Throws<InvalidOperationException>(delegate { var c = target[0]; }, String.Format(IndexerNotSupportedOnJsonType, typeof(int), target.JsonType));
+ ExceptionHelper.Throws<InvalidOperationException>(delegate { target[0] = 123; }, String.Format(IndexerNotSupportedOnJsonType, typeof(int), target.JsonType));
+
+ target = AnyInstance.AnyJsonArray;
+ ExceptionHelper.Throws<InvalidOperationException>(delegate { var c = target["key"]; }, String.Format(IndexerNotSupportedOnJsonType, typeof(string), target.JsonType));
+ ExceptionHelper.Throws<InvalidOperationException>(delegate { target["here"] = 123; }, String.Format(IndexerNotSupportedOnJsonType, typeof(string), target.JsonType));
+ }
+
+ [Fact(Skip = "Re-enable when DCS have been removed -- see CSDMain 234538")]
+ public void NonSerializableTest()
+ {
+ DataContractJsonSerializer dcjs = new DataContractJsonSerializer(typeof(JsonValue));
+ ExceptionHelper.Throws<NotSupportedException>(() => dcjs.WriteObject(Stream.Null, AnyInstance.DefaultJsonValue));
+ }
+
+ [Fact]
+ public void DefaultConcatTest()
+ {
+ JsonValue jv = JsonValueExtensions.CreateFrom(AnyInstance.AnyPerson);
+ dynamic target = JsonValueExtensions.CreateFrom(AnyInstance.AnyPerson);
+ Person person = AnyInstance.AnyPerson;
+
+ Assert.Equal(person.Address.City, target.Address.City.ReadAs<string>());
+ Assert.Equal(person.Friends[0].Age, target.Friends[0].Age.ReadAs<int>());
+
+ Assert.Equal(target.ValueOrDefault("Address").ValueOrDefault("City"), target.Address.City);
+ Assert.Equal(target.ValueOrDefault("Address", "City"), target.Address.City);
+
+ Assert.Equal(target.ValueOrDefault("Friends").ValueOrDefault(0).ValueOrDefault("Age"), target.Friends[0].Age);
+ Assert.Equal(target.ValueOrDefault("Friends", 0, "Age"), target.Friends[0].Age);
+
+ Assert.Equal(JsonType.Default, AnyInstance.AnyJsonValue1.ValueOrDefault((object[])null).JsonType);
+ Assert.Equal(JsonType.Default, jv.ValueOrDefault("Friends", null).JsonType);
+ Assert.Equal(JsonType.Default, AnyInstance.AnyJsonValue1.ValueOrDefault((string)null).JsonType);
+ Assert.Equal(JsonType.Default, AnyInstance.AnyJsonPrimitive.ValueOrDefault(AnyInstance.AnyString, AnyInstance.AnyShort).JsonType);
+ Assert.Equal(JsonType.Default, AnyInstance.AnyJsonArray.ValueOrDefault((string)null).JsonType);
+ Assert.Equal(JsonType.Default, AnyInstance.AnyJsonObject.ValueOrDefault(AnyInstance.AnyString, null).JsonType);
+ Assert.Equal(JsonType.Default, AnyInstance.AnyJsonArray.ValueOrDefault(-1).JsonType);
+
+ Assert.Same(AnyInstance.AnyJsonValue1, AnyInstance.AnyJsonValue1.ValueOrDefault());
+
+ Assert.Same(AnyInstance.AnyJsonArray.ValueOrDefault(0), AnyInstance.AnyJsonArray.ValueOrDefault((short)0));
+ Assert.Same(AnyInstance.AnyJsonArray.ValueOrDefault(0), AnyInstance.AnyJsonArray.ValueOrDefault((ushort)0));
+ Assert.Same(AnyInstance.AnyJsonArray.ValueOrDefault(0), AnyInstance.AnyJsonArray.ValueOrDefault((byte)0));
+ Assert.Same(AnyInstance.AnyJsonArray.ValueOrDefault(0), AnyInstance.AnyJsonArray.ValueOrDefault((sbyte)0));
+ Assert.Same(AnyInstance.AnyJsonArray.ValueOrDefault(0), AnyInstance.AnyJsonArray.ValueOrDefault((char)0));
+
+ jv = new JsonObject();
+ jv[AnyInstance.AnyString] = AnyInstance.AnyJsonArray;
+
+ Assert.Same(jv.ValueOrDefault(AnyInstance.AnyString, 0), jv.ValueOrDefault(AnyInstance.AnyString, (short)0));
+ Assert.Same(jv.ValueOrDefault(AnyInstance.AnyString, 0), jv.ValueOrDefault(AnyInstance.AnyString, (ushort)0));
+ Assert.Same(jv.ValueOrDefault(AnyInstance.AnyString, 0), jv.ValueOrDefault(AnyInstance.AnyString, (byte)0));
+ Assert.Same(jv.ValueOrDefault(AnyInstance.AnyString, 0), jv.ValueOrDefault(AnyInstance.AnyString, (sbyte)0));
+ Assert.Same(jv.ValueOrDefault(AnyInstance.AnyString, 0), jv.ValueOrDefault(AnyInstance.AnyString, (char)0));
+
+ jv = AnyInstance.AnyJsonObject;
+
+ ExceptionHelper.Throws<ArgumentException>(delegate { var c = jv.ValueOrDefault(AnyInstance.AnyString, AnyInstance.AnyLong); }, String.Format(InvalidIndexType, typeof(long)));
+ ExceptionHelper.Throws<ArgumentException>(delegate { var c = jv.ValueOrDefault(AnyInstance.AnyString, AnyInstance.AnyUInt); }, String.Format(InvalidIndexType, typeof(uint)));
+ ExceptionHelper.Throws<ArgumentException>(delegate { var c = jv.ValueOrDefault(AnyInstance.AnyString, AnyInstance.AnyBool); }, String.Format(InvalidIndexType, typeof(bool)));
+ }
+
+
+ [Fact]
+ public void DataContractSerializerTest()
+ {
+ ValidateSerialization(new JsonPrimitive(DateTime.Now));
+ ValidateSerialization(new JsonObject { { "a", 1 }, { "b", 2 }, { "c", 3 } });
+ ValidateSerialization(new JsonArray { "a", "b", "c", 1, 2, 3 });
+
+ JsonObject beforeObject = new JsonObject { { "a", 1 }, { "b", 2 }, { "c", 3 } };
+ JsonObject afterObject1 = (JsonObject)ValidateSerialization(beforeObject);
+ beforeObject.Add("d", 4);
+ afterObject1.Add("d", 4);
+ Assert.Equal(beforeObject.ToString(), afterObject1.ToString());
+
+ JsonObject afterObject2 = (JsonObject)ValidateSerialization(beforeObject);
+ beforeObject.Add("e", 5);
+ afterObject2.Add("e", 5);
+ Assert.Equal(beforeObject.ToString(), afterObject2.ToString());
+
+ JsonArray beforeArray = new JsonArray { "a", "b", "c" };
+ JsonArray afterArray1 = (JsonArray)ValidateSerialization(beforeArray);
+ beforeArray.Add("d");
+ afterArray1.Add("d");
+ Assert.Equal(beforeArray.ToString(), afterArray1.ToString());
+
+ JsonArray afterArray2 = (JsonArray)ValidateSerialization(beforeArray);
+ beforeArray.Add("e");
+ afterArray2.Add("e");
+ Assert.Equal(beforeArray.ToString(), afterArray2.ToString());
+ }
+
+ private static JsonValue ValidateSerialization(JsonValue beforeSerialization)
+ {
+ Assert.NotNull(beforeSerialization);
+ NetDataContractSerializer serializer = new NetDataContractSerializer();
+ using (MemoryStream memStream = new MemoryStream())
+ {
+ serializer.Serialize(memStream, beforeSerialization);
+ memStream.Position = 0;
+ JsonValue afterDeserialization = (JsonValue)serializer.Deserialize(memStream);
+ Assert.Equal(beforeSerialization.ToString(), afterDeserialization.ToString());
+ return afterDeserialization;
+ }
+ }
+ }
+}
diff --git a/test/System.Json.Test.Unit/Properties/AssemblyInfo.cs b/test/System.Json.Test.Unit/Properties/AssemblyInfo.cs
new file mode 100644
index 00000000..fe40f430
--- /dev/null
+++ b/test/System.Json.Test.Unit/Properties/AssemblyInfo.cs
@@ -0,0 +1,37 @@
+using System;
+using System.Reflection;
+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("System.Json.Test.Unit")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("Microsoft")]
+[assembly: AssemblyProduct("System.Json.Test.Unit")]
+[assembly: AssemblyCopyright("Copyright © Microsoft 2011")]
+[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)]
+
+[assembly: CLSCompliant(false)]
+
+// The following GUID is for the ID of the typelib if this project is exposed to COM
+[assembly: Guid("2c4d325c-ef22-46b8-92af-79bea6364683")]
+
+// 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/test/System.Json.Test.Unit/System.Json.Test.Unit.csproj b/test/System.Json.Test.Unit/System.Json.Test.Unit.csproj
new file mode 100644
index 00000000..c589897f
--- /dev/null
+++ b/test/System.Json.Test.Unit/System.Json.Test.Unit.csproj
@@ -0,0 +1,85 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory),Runtime.sln))\tools\WebStack.settings.targets" />
+ <PropertyGroup>
+ <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+ <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+ <ProductVersion>9.0.30729</ProductVersion>
+ <SchemaVersion>2.0</SchemaVersion>
+ <ProjectGuid>{EB09CD33-992B-4A31-AB95-8673BA90F1CD}</ProjectGuid>
+ <OutputType>Library</OutputType>
+ <AppDesignerFolder>Properties</AppDesignerFolder>
+ <RootNamespace>System.Json.Test</RootNamespace>
+ <AssemblyName>System.Json.Test</AssemblyName>
+ <FileAlignment>512</FileAlignment>
+ <ProjectTypeGuids>{3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
+ <ErrorReport>prompt</ErrorReport>
+ <WarningLevel>4</WarningLevel>
+ <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
+ <DebugSymbols>true</DebugSymbols>
+ <DebugType>full</DebugType>
+ <Optimize>false</Optimize>
+ <OutputPath>$(WebStackRootPath)\bin\Debug\Test\</OutputPath>
+ <DefineConstants>TRACE;DEBUG</DefineConstants>
+ <CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
+ <DebugType>pdbonly</DebugType>
+ <Optimize>true</Optimize>
+ <OutputPath>$(WebStackRootPath)\bin\Release\Test\</OutputPath>
+ <DefineConstants>TRACE</DefineConstants>
+ <CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)' == 'CodeCoverage' ">
+ <DebugSymbols>true</DebugSymbols>
+ <DebugType>full</DebugType>
+ <Optimize>false</Optimize>
+ <OutputPath>$(WebStackRootPath)\bin\CodeCoverage\Test\</OutputPath>
+ <DefineConstants>TRACE;DEBUG</DefineConstants>
+ <CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
+ </PropertyGroup>
+ <ItemGroup>
+ <Reference Include="Microsoft.CSharp" />
+ <Reference Include="System" />
+ <Reference Include="System.Runtime.Serialization" />
+ <Reference Include="System.XML" />
+ <Reference Include="xunit">
+ <HintPath>..\..\packages\xunit.1.9.0.1566\lib\xunit.dll</HintPath>
+ </Reference>
+ <Reference Include="xunit.extensions">
+ <HintPath>..\..\packages\xunit.extensions.1.9.0.1566\lib\xunit.extensions.dll</HintPath>
+ </Reference>
+ </ItemGroup>
+ <ItemGroup>
+ <Compile Include="Common\AnyInstance.cs" />
+ <Compile Include="Common\ExceptionTestHelper.cs" />
+ <Compile Include="Properties\AssemblyInfo.cs" />
+ <Compile Include="FormUrlEncodedJsonTests.cs" />
+ <Compile Include="JsonArrayTest.cs" />
+ <Compile Include="JsonDefaultTest.cs" />
+ <Compile Include="JsonObjectTest.cs" />
+ <Compile Include="JsonPrimitiveTest.cs" />
+ <Compile Include="JsonTypeTest.cs" />
+ <Compile Include="JsonValueDynamicMetaObjectTest.cs" />
+ <Compile Include="JsonValueDynamicTest.cs" />
+ <Compile Include="JsonValueLinqExtensionsTest.cs" />
+ <Compile Include="JsonValueTest.cs" />
+ <Compile Include="Extensions\JsonValueExtensionsTest.cs" />
+ </ItemGroup>
+ <ItemGroup>
+ <ProjectReference Include="..\Microsoft.TestCommon\Microsoft.TestCommon.csproj">
+ <Project>{FCCC4CB7-BAF7-4A57-9F89-E5766FE536C0}</Project>
+ <Name>Microsoft.TestCommon</Name>
+ </ProjectReference>
+ <ProjectReference Include="..\..\src\System.Json\System.Json.csproj">
+ <Project>{F0441BE9-BDC0-4629-BE5A-8765FFAA2481}</Project>
+ <Name>System.Json</Name>
+ </ProjectReference>
+ </ItemGroup>
+ <ItemGroup>
+ <None Include="packages.config" />
+ </ItemGroup>
+ <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
+</Project> \ No newline at end of file
diff --git a/test/System.Json.Test.Unit/packages.config b/test/System.Json.Test.Unit/packages.config
new file mode 100644
index 00000000..d82739c0
--- /dev/null
+++ b/test/System.Json.Test.Unit/packages.config
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<packages>
+ <package id="xunit" version="1.9.0.1566" />
+ <package id="xunit.extensions" version="1.9.0.1566" />
+</packages> \ No newline at end of file
diff --git a/test/System.Net.Http.Formatting.Test.Integration/FormUrlEncodedFromContentTests.cs b/test/System.Net.Http.Formatting.Test.Integration/FormUrlEncodedFromContentTests.cs
new file mode 100644
index 00000000..4a374c69
--- /dev/null
+++ b/test/System.Net.Http.Formatting.Test.Integration/FormUrlEncodedFromContentTests.cs
@@ -0,0 +1,527 @@
+using System.Collections.Generic;
+using System.Json;
+using System.Net.Http.Formatting.Parsers;
+using System.Net.Http.Internal;
+using System.Text;
+using Xunit;
+using Xunit.Extensions;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Net.Http.Formatting
+{
+ public class FormUrlEncodedJsonFromContentTests
+ {
+ #region Tests
+
+ [Theory,
+ InlineData("abc", "{\"abc\":null}"),
+ InlineData("%2eabc%2e", "{\".abc.\":null}"),
+ InlineData("", "{}"),
+ InlineData("a=1", "{\"a\":\"1\"}")]
+ public void SimpleStringsTest(string encoded, string expectedResult)
+ {
+ ValidateFormUrlEncoded(encoded, expectedResult);
+
+ }
+
+ [Theory,
+ InlineData("a=2", "{\"a\":\"2\"}"),
+ InlineData("b=true", "{\"b\":\"true\"}"),
+ InlineData("c=hello", "{\"c\":\"hello\"}"),
+ InlineData("d=", "{\"d\":\"\"}"),
+ InlineData("e=null", "{\"e\":null}")]
+ public void SimpleObjectsTest(string encoded, string expectedResult)
+ {
+ ValidateFormUrlEncoded(encoded, expectedResult);
+
+ }
+
+ [Fact]
+ public void LegacyArraysTest()
+ {
+ ValidateFormUrlEncoded("a=1&a=hello&a=333", "{\"a\":[\"1\",\"hello\",\"333\"]}");
+
+ // Only valid in shallow serialization
+ ParseInvalidFormUrlEncoded("a[z]=2&a[z]=3");
+ }
+
+ [Theory,
+ InlineData("a[]=1&a[]=hello&a[]=333", "{\"a\":[\"1\",\"hello\",\"333\"]}"),
+ InlineData("a[b][]=1&a[b][]=hello&a[b][]=333", "{\"a\":{\"b\":[\"1\",\"hello\",\"333\"]}}"),
+ InlineData("a[]=", "{\"a\":[\"\"]}"),
+ InlineData("a%5B%5D=2", @"{""a"":[""2""]}"),
+ InlineData("a[x][0]=1&a[x][]=2", @"{""a"":{""x"":[""1"",""2""]}}")]
+ public void ArraysTest(string encoded, string expectedResult)
+ {
+ ValidateFormUrlEncoded(encoded, expectedResult);
+ }
+
+ [Theory,
+ InlineData("a[0][]=1&a[0][]=hello&a[1][]=333", "{\"a\":[[\"1\",\"hello\"],[\"333\"]]}"),
+ InlineData("a[b][0][]=1&a[b][1][]=hello&a[b][1][]=333", "{\"a\":{\"b\":[[\"1\"],[\"hello\",\"333\"]]}}"),
+ InlineData("a[0][0][0][]=1", "{\"a\":[[[[\"1\"]]]]}")]
+ public void MultidimensionalArraysTest(string encoded, string expectedResult)
+ {
+ ValidateFormUrlEncoded(encoded, expectedResult);
+ }
+
+ [Theory,
+ InlineData("a[0][]=hello&a[2][]=333", "{\"a\":{\"0\":[\"hello\"],\"2\":[\"333\"]}}"),
+ InlineData("a[0]=hello", "{\"a\":[\"hello\"]}"),
+ InlineData("a[1][]=hello", "{\"a\":{\"1\":[\"hello\"]}}"),
+ InlineData("a[1][0]=hello", "{\"a\":{\"1\":[\"hello\"]}}")]
+ public void SparseArraysTest(string encoded, string expectedResult)
+ {
+ ValidateFormUrlEncoded(encoded, expectedResult);
+ }
+
+ [Theory,
+ InlineData("b[]=2&b[1][c]=d", "{\"b\":[\"2\",{\"c\":\"d\"}]}")]
+ public void ArraysWithMixedMembers(string encoded, string expectedResult)
+ {
+ ValidateFormUrlEncoded(encoded, expectedResult);
+ }
+
+ [Theory,
+ InlineData("=3", "{\"\":\"3\"}"),
+ InlineData("a=1&=3", "{\"a\":\"1\",\"\":\"3\"}"),
+ InlineData("=3&b=2", "{\"\":\"3\",\"b\":\"2\"}")]
+ public void EmptyKeyTest(string encoded, string expectedResult)
+ {
+ ValidateFormUrlEncoded(encoded, expectedResult);
+ }
+
+ [Theory,
+ InlineData("a[b]=1&a=2"),
+ InlineData("a[b]=1&a[b][]=2"),
+ InlineData("a[x][]=1&a[x][0]=2"),
+ InlineData("a=2&a[b]=1"),
+ InlineData("[]=1"),
+ InlineData("a[][]=0"),
+ InlineData("a[][x]=0"),
+ InlineData("a&a[b]=1"),
+ InlineData("a&a=1")]
+ public void InvalidObjectGraphsTest(string encoded)
+ {
+ ParseInvalidFormUrlEncoded(encoded);
+ }
+
+ [Theory,
+ InlineData("a[b=2"),
+ InlineData("a[[b]=2"),
+ InlineData("a[b]]=2")]
+ public void InvalidFormUrlEncodingTest(string encoded)
+ {
+ ParseInvalidFormUrlEncoded(encoded);
+ }
+
+ /// <summary>
+ /// Tests for parsing form-urlencoded data originated from JS primitives.
+ /// </summary>
+ [Theory,
+ InlineData("abc", @"{""abc"":null}"),
+ InlineData("123", @"{""123"":null}"),
+ InlineData("true", @"{""true"":null}"),
+ InlineData("", "{}"),
+ InlineData("%2fabc%2f", @"{""\/abc\/"":null}")]
+ public void TestJsonPrimitive(string encoded, string expectedResult)
+ {
+ ValidateFormUrlEncoded(encoded, expectedResult);
+ }
+
+ /// <summary>
+ /// Negative tests for parsing form-urlencoded data originated from JS primitives.
+ /// </summary>
+ [Theory,
+ InlineData("a[b]=1&a=2"),
+ InlineData("a=2&a[b]=1"),
+ InlineData("[]=1")]
+ public void TestJsonPrimitiveNegative(string encoded)
+ {
+ ParseInvalidFormUrlEncoded(encoded);
+ }
+
+ /// <summary>
+ /// Tests for parsing form-urlencoded data originated from JS objects.
+ /// </summary>
+ [Theory,
+ InlineData("a=NaN", @"{""a"":""NaN""}"),
+ InlineData("a=false", @"{""a"":""false""}"),
+ InlineData("a=foo", @"{""a"":""foo""}"),
+ InlineData("1=1", "{\"1\":\"1\"}")]
+ public void TestObjects(string encoded, string expectedResult)
+ {
+ ValidateFormUrlEncoded(encoded, expectedResult);
+ }
+
+ /// <summary>
+ /// Tests for parsing form-urlencoded data originated from JS arrays.
+ /// </summary>
+ [Theory,
+ InlineData("a[]=2", @"{""a"":[""2""]}"),
+ InlineData("a[]=", @"{""a"":[""""]}"),
+ InlineData("a[0][0][]=1", @"{""a"":[[[""1""]]]}"),
+ InlineData("z[]=9&z[]=true&z[]=undefined&z[]=", @"{""z"":[""9"",""true"",""undefined"",""""]}"),
+ InlineData("z[]=9&z[]=true&z[]=undefined&z[]=null", @"{""z"":[""9"",""true"",""undefined"",null]}"),
+ InlineData("z[0][]=9&z[0][]=true&z[1][]=undefined&z[1][]=null", @"{""z"":[[""9"",""true""],[""undefined"",null]]}"),
+ InlineData("a[0][x]=2", @"{""a"":[{""x"":""2""}]}"),
+ InlineData("a%5B%5D=2", @"{""a"":[""2""]}"),
+ InlineData("a%5B%5D=", @"{""a"":[""""]}"),
+ InlineData("z%5B%5D=9&z%5B%5D=true&z%5B%5D=undefined&z%5B%5D=", @"{""z"":[""9"",""true"",""undefined"",""""]}"),
+ InlineData("z%5B%5D=9&z%5B%5D=true&z%5B%5D=undefined&z%5B%5D=null", @"{""z"":[""9"",""true"",""undefined"",null]}"),
+ InlineData("z%5B0%5D%5B%5D=9&z%5B0%5D%5B%5D=true&z%5B1%5D%5B%5D=undefined&z%5B1%5D%5B%5D=null", @"{""z"":[[""9"",""true""],[""undefined"",null]]}")]
+ public void TestArray(string encoded, string expectedResult)
+ {
+ ValidateFormUrlEncoded(encoded, expectedResult);
+ }
+
+ /// <summary>
+ /// Tests for parsing form-urlencoded data originated from JS arrays, using the jQuery 1.3 format (no []'s).
+ /// </summary>
+ [Theory,
+ InlineData("z=9&z=true&z=undefined&z=", @"{""z"":[""9"",""true"",""undefined"",""""]}"),
+ InlineData("z=9&z=true&z=undefined&z=null", @"{""z"":[""9"",""true"",""undefined"",null]}"),
+ InlineData("z=9&z=true&z=undefined&z=null&a=hello", @"{""z"":[""9"",""true"",""undefined"",null],""a"":""hello""}")]
+ public void TestArrayCompat(string encoded, string expectedResult)
+ {
+ ValidateFormUrlEncoded(encoded, expectedResult);
+ }
+
+ /// <summary>
+ /// Negative tests for parsing form-urlencoded data originated from JS arrays.
+ /// </summary>
+ [Theory,
+ InlineData("a[z]=2&a[z]=3")]
+ public void TestArrayCompatNegative(string encoded)
+ {
+ ParseInvalidFormUrlEncoded(encoded);
+ }
+
+ /// <summary>
+ /// Tests for form-urlencoded data originated from sparse JS arrays.
+ /// </summary>
+ [Theory,
+ InlineData("a[2]=hello", @"{""a"":{""2"":""hello""}}"),
+ InlineData("a[x][0]=2", @"{""a"":{""x"":[""2""]}}"),
+ InlineData("a[x][1]=2", @"{""a"":{""x"":{""1"":""2""}}}"),
+ InlineData("a[x][0]=0&a[x][1]=1", @"{""a"":{""x"":[""0"",""1""]}}"),
+ InlineData("a[0][0][0]=hello&a[1][0][0][0][]=hello", @"{""a"":[[[""hello""]],[[[[""hello""]]]]]}"),
+ InlineData("a[0][0][0]=hello&a[1][0][0][0]=hello", @"{""a"":[[[""hello""]],[[[""hello""]]]]}"),
+ InlineData("a[1][0][]=1", @"{""a"":{""1"":[[""1""]]}}"),
+ InlineData("a[1][1][]=1", @"{""a"":{""1"":{""1"":[""1""]}}}"),
+ InlineData("a[1][1][0]=1", @"{""a"":{""1"":{""1"":[""1""]}}}"),
+ InlineData("a[0][]=2&a[0][]=3&a[2][]=1", "{\"a\":{\"0\":[\"2\",\"3\"],\"2\":[\"1\"]}}"),
+ InlineData("a[x][]=1&a[x][1]=2", @"{""a"":{""x"":[""1"",""2""]}}"),
+ InlineData("a[x][0]=1&a[x][]=2", @"{""a"":{""x"":[""1"",""2""]}}")]
+ public void TestArraySparse(string encoded, string expectedResult)
+ {
+ ValidateFormUrlEncoded(encoded, expectedResult);
+ }
+
+ /// <summary>
+ /// Negative tests for parsing form-urlencoded arrays.
+ /// </summary>
+ [Theory,
+ InlineData("a[x]=2&a[x][]=3"),
+ InlineData("a[]=1&a[0][]=2"),
+ InlineData("a[]=1&a[0][0][]=2"),
+ InlineData("a[x][]=1&a[x][0]=2"),
+ InlineData("a[][]=0"),
+ InlineData("a[][x]=0")]
+ public void TestArrayIndexNegative(string encoded)
+ {
+ ParseInvalidFormUrlEncoded(encoded);
+ }
+
+ public static IEnumerable<object[]> TestObjectTestData
+ {
+ get
+ {
+ string encoded = "a[]=4&a[]=5&b[x][]=7&b[y]=8&b[z][]=9&b[z][]=true&b[z][]=undefined&b[z][]=&c=1&f=";
+ string resultStr = @"{""a"":[""4"",""5""],""b"":{""x"":[""7""],""y"":""8"",""z"":[""9"",""true"",""undefined"",""""]},""c"":""1"",""f"":""""}";
+ yield return new[] { encoded, resultStr };
+
+ encoded = "customer[Name]=Pete&customer[Address]=Redmond&customer[Age][0][]=23&customer[Age][0][]=24&customer[Age][1][]=25&" +
+ "customer[Age][1][]=26&customer[Phones][]=425+888+1111&customer[Phones][]=425+345+7777&customer[Phones][]=425+888+4564&" +
+ "customer[EnrolmentDate]=%22%5C%2FDate(1276562539537)%5C%2F%22&role=NewRole&changeDate=3&count=15";
+ resultStr = @"{""customer"":{""Name"":""Pete"",""Address"":""Redmond"",""Age"":[[""23"",""24""],[""25"",""26""]]," +
+ @"""Phones"":[""425 888 1111"",""425 345 7777"",""425 888 4564""],""EnrolmentDate"":""\""\\\/Date(1276562539537)\\\/\""""},""role"":""NewRole"",""changeDate"":""3"",""count"":""15""}";
+ yield return new[] { encoded, resultStr };
+
+ encoded = "customers[0][Name]=Pete2&customers[0][Address]=Redmond2&customers[0][Age][0][]=23&customers[0][Age][0][]=24&" +
+ "customers[0][Age][1][]=25&customers[0][Age][1][]=26&customers[0][Phones][]=425+888+1111&customers[0][Phones][]=425+345+7777&" +
+ "customers[0][Phones][]=425+888+4564&customers[0][EnrolmentDate]=%22%5C%2FDate(1276634840700)%5C%2F%22&customers[1][Name]=Pete3&" +
+ "customers[1][Address]=Redmond3&customers[1][Age][0][]=23&customers[1][Age][0][]=24&customers[1][Age][1][]=25&customers[1][Age][1][]=26&" +
+ "customers[1][Phones][]=425+888+1111&customers[1][Phones][]=425+345+7777&customers[1][Phones][]=425+888+4564&customers[1][EnrolmentDate]=%22%5C%2FDate(1276634840700)%5C%2F%22";
+ resultStr = @"{""customers"":[{""Name"":""Pete2"",""Address"":""Redmond2"",""Age"":[[""23"",""24""],[""25"",""26""]]," +
+ @"""Phones"":[""425 888 1111"",""425 345 7777"",""425 888 4564""],""EnrolmentDate"":""\""\\\/Date(1276634840700)\\\/\""""}," +
+ @"{""Name"":""Pete3"",""Address"":""Redmond3"",""Age"":[[""23"",""24""],[""25"",""26""]],""Phones"":[""425 888 1111"",""425 345 7777"",""425 888 4564""],""EnrolmentDate"":""\""\\\/Date(1276634840700)\\\/\""""}]}";
+ yield return new[] { encoded, resultStr };
+
+ encoded = "ab%5B%5D=hello";
+ resultStr = @"{""ab"":[""hello""]}";
+ yield return new[] { encoded, resultStr };
+
+ encoded = "123=hello";
+ resultStr = @"{""123"":""hello""}";
+ yield return new[] { encoded, resultStr };
+
+ encoded = "a%5B%5D=1&a";
+ resultStr = @"{""a"":[""1"",null]}";
+ yield return new[] { encoded, resultStr };
+
+ encoded = "a=1&a";
+ resultStr = @"{""a"":[""1"",null]}";
+ yield return new[] { encoded, resultStr };
+ }
+ }
+
+ /// <summary>
+ /// Tests for parsing complex object graphs form-urlencoded.
+ /// </summary>
+ [Theory]
+ [PropertyData("TestObjectTestData")]
+ public void TestObject(string encoded, string expectedResult)
+ {
+ ValidateFormUrlEncoded(encoded, expectedResult);
+ }
+
+
+ public static IEnumerable<object[]> TestEncodedNameTestData
+ {
+ get
+ {
+ string encoded = "some+thing=10";
+ string resultStr = @"{""some thing"":""10""}";
+ yield return new[] { encoded, resultStr };
+
+ encoded = "%E5%B8%A6%E4%B8%89%E4%B8%AA%E8%A1%A8=bar";
+ resultStr = @"{""带三个表"":""bar""}";
+ yield return new[] { encoded, resultStr };
+
+ encoded = "some+thing=10&%E5%B8%A6%E4%B8%89%E4%B8%AA%E8%A1%A8=bar";
+ resultStr = @"{""some thing"":""10"",""带三个表"":""bar""}";
+ yield return new[] { encoded, resultStr };
+
+ encoded = "a[0\r\n][b]=1";
+ resultStr = "{\"a\":{\"0\\u000d\\u000a\":{\"b\":\"1\"}}}";
+ yield return new[] { encoded, resultStr };
+ yield return new[] { encoded.Replace("\r", "%0D").Replace("\n", "%0A"), resultStr };
+
+ yield return new[] { "a[0\0]=1", "{\"a\":{\"0\\u0000\":\"1\"}}" };
+ yield return new[] { "a[0%00]=1", "{\"a\":{\"0\\u0000\":\"1\"}}" };
+ yield return new[] { "a[\00]=1", "{\"a\":{\"\\u00000\":\"1\"}}" };
+ yield return new[] { "a[%000]=1", "{\"a\":{\"\\u00000\":\"1\"}}" };
+ }
+ }
+ /// <summary>
+ /// Tests for parsing form-urlencoded data with encoded names.
+ /// </summary>
+ [Theory]
+ [PropertyData("TestEncodedNameTestData")]
+ public void TestEncodedName(string encoded, string expectedResult)
+ {
+ ValidateFormUrlEncoded(encoded, expectedResult);
+ }
+
+ /// <summary>
+ /// Tests for malformed form-urlencoded data.
+ /// </summary>
+ [Theory,
+ InlineData("a[b=2"),
+ InlineData("a[[b]=2"),
+ InlineData("a[b]]=2")]
+ public void TestNegative(string encoded)
+ {
+ ParseInvalidFormUrlEncoded(encoded);
+ }
+
+ /// <summary>
+ /// Tests for parsing generated form-urlencoded data.
+ /// </summary>
+ [Fact]
+ public void GeneratedJsonValueTest()
+ {
+ Random rndGen = new Random(1);
+ int oldMaxArray = CreatorSettings.MaxArrayLength;
+ int oldMaxList = CreatorSettings.MaxListLength;
+ int oldMaxStr = CreatorSettings.MaxStringLength;
+ double oldNullProbability = CreatorSettings.NullValueProbability;
+ bool oldCreateAscii = CreatorSettings.CreateOnlyAsciiChars;
+ CreatorSettings.MaxArrayLength = 5;
+ CreatorSettings.MaxListLength = 3;
+ CreatorSettings.MaxStringLength = 3;
+ CreatorSettings.NullValueProbability = 0;
+ CreatorSettings.CreateOnlyAsciiChars = true;
+ JsonValueCreatorSurrogate jsonValueCreator = new JsonValueCreatorSurrogate();
+ try
+ {
+ for (int i = 0; i < 1000; i++)
+ {
+ JsonValue jv = (JsonValue)jsonValueCreator.CreateInstanceOf(typeof(JsonValue), rndGen);
+ if (jv.JsonType == JsonType.Array || jv.JsonType == JsonType.Object)
+ {
+ string jaStr = FormUrlEncoding(jv);
+ byte[] data = Encoding.UTF8.GetBytes(jaStr);
+ for (var cnt = 1; cnt <= data.Length; cnt += 4)
+ {
+ ICollection<KeyValuePair<string, string>> collection;
+ FormUrlEncodedParser parser = FormUrlEncodedParserTests.CreateParser(data.Length + 1, out collection);
+ Assert.NotNull(parser);
+
+ int totalBytesConsumed;
+ ParserState state = FormUrlEncodedParserTests.ParseBufferInSteps(parser, data, cnt, out totalBytesConsumed);
+ Assert.Equal(ParserState.Done, state);
+ Assert.Equal(data.Length, totalBytesConsumed);
+
+ JsonValue deserJv = FormUrlEncodedJson.Parse(collection);
+ Assert.NotNull(deserJv);
+ bool compare = true;
+ if (deserJv is JsonObject && ((JsonObject)deserJv).ContainsKey("JV"))
+ {
+ compare = JsonValueRoundTripComparer.Compare(jv, deserJv["JV"]);
+ }
+ else
+ {
+ compare = JsonValueRoundTripComparer.Compare(jv, deserJv);
+ }
+
+ Assert.True(compare, "Comparison failed for test instance " + i);
+ }
+ }
+ }
+ }
+ finally
+ {
+ CreatorSettings.MaxArrayLength = oldMaxArray;
+ CreatorSettings.MaxListLength = oldMaxList;
+ CreatorSettings.MaxStringLength = oldMaxStr;
+ CreatorSettings.NullValueProbability = oldNullProbability;
+ CreatorSettings.CreateOnlyAsciiChars = oldCreateAscii;
+ }
+ }
+
+ #endregion
+
+ #region Helpers
+
+ private static string FormUrlEncoding(JsonValue jsonValue)
+ {
+ List<string> results = new List<string>();
+ if (jsonValue is JsonPrimitive)
+ {
+ return UriQueryUtility.UrlEncode(((JsonPrimitive)jsonValue).Value.ToString());
+ }
+
+ BuildParams("JV", jsonValue, results);
+ StringBuilder strResult = new StringBuilder();
+ foreach (var result in results)
+ {
+ strResult.Append("&" + result);
+ }
+
+ if (strResult.Length > 0)
+ {
+ return strResult.Remove(0, 1).ToString();
+ }
+
+ return strResult.ToString();
+ }
+
+ private static void BuildParams(string prefix, JsonValue jsonValue, List<string> results)
+ {
+ if (jsonValue is JsonPrimitive)
+ {
+ JsonPrimitive jsonPrimitive = jsonValue as JsonPrimitive;
+ if (jsonPrimitive != null)
+ {
+ if (jsonPrimitive.JsonType == JsonType.String && String.IsNullOrEmpty(jsonPrimitive.Value.ToString()))
+ {
+ results.Add(prefix + "=" + String.Empty);
+ }
+ else
+ {
+ if (jsonPrimitive.Value is DateTime || jsonPrimitive.Value is DateTimeOffset)
+ {
+ string dateStr = jsonPrimitive.ToString();
+ if (!String.IsNullOrEmpty(dateStr) && dateStr.StartsWith("\""))
+ {
+ dateStr = dateStr.Substring(1, dateStr.Length - 2);
+ }
+ results.Add(prefix + "=" + UriQueryUtility.UrlEncode(dateStr));
+ }
+ else
+ {
+ results.Add(prefix + "=" + UriQueryUtility.UrlEncode(jsonPrimitive.Value.ToString()));
+ }
+ }
+ }
+ else
+ {
+ results.Add(prefix + "=" + String.Empty);
+ }
+ }
+ else if (jsonValue is JsonArray)
+ {
+ for (int i = 0; i < jsonValue.Count; i++)
+ {
+ if (jsonValue[i] is JsonArray || jsonValue[i] is JsonObject)
+ {
+ BuildParams(prefix + "[" + i + "]", jsonValue[i], results);
+ }
+ else
+ {
+ BuildParams(prefix + "[]", jsonValue[i], results);
+ }
+ }
+ }
+ else //jsonValue is JsonObject
+ {
+ foreach (KeyValuePair<string, JsonValue> item in jsonValue)
+ {
+ BuildParams(prefix + "[" + item.Key + "]", item.Value, results);
+ }
+ }
+ }
+
+ private static void ParseInvalidFormUrlEncoded(string encoded)
+ {
+ byte[] data = Encoding.UTF8.GetBytes(encoded);
+ for (var cnt = 1; cnt <= data.Length; cnt++)
+ {
+ ICollection<KeyValuePair<string, string>> collection;
+ FormUrlEncodedParser parser = FormUrlEncodedParserTests.CreateParser(data.Length + 1, out collection);
+ Assert.NotNull(parser);
+
+ int totalBytesConsumed;
+ ParserState state = FormUrlEncodedParserTests.ParseBufferInSteps(parser, data, cnt, out totalBytesConsumed);
+ Assert.Equal(ParserState.Done, state);
+ Assert.Equal(data.Length, totalBytesConsumed);
+
+ Assert.ThrowsArgument(() => { FormUrlEncodedJson.Parse(collection); }, null);
+ }
+ }
+
+ private static void ValidateFormUrlEncoded(string encoded, string expectedResult)
+ {
+ byte[] data = Encoding.UTF8.GetBytes(encoded);
+ for (var cnt = 1; cnt <= data.Length; cnt++)
+ {
+ ICollection<KeyValuePair<string, string>> collection;
+ FormUrlEncodedParser parser = FormUrlEncodedParserTests.CreateParser(data.Length + 1, out collection);
+ Assert.NotNull(parser);
+
+ int totalBytesConsumed;
+ ParserState state = FormUrlEncodedParserTests.ParseBufferInSteps(parser, data, cnt, out totalBytesConsumed);
+ Assert.Equal(ParserState.Done, state);
+ Assert.Equal(data.Length, totalBytesConsumed);
+
+ JsonObject result = FormUrlEncodedJson.Parse(collection);
+ Assert.NotNull(result);
+ Assert.Equal(expectedResult, result.ToString());
+ }
+ }
+
+ #endregion
+ }
+} \ No newline at end of file
diff --git a/test/System.Net.Http.Formatting.Test.Integration/FormUrlEncodedFromUriQueryTests.cs b/test/System.Net.Http.Formatting.Test.Integration/FormUrlEncodedFromUriQueryTests.cs
new file mode 100644
index 00000000..66c19964
--- /dev/null
+++ b/test/System.Net.Http.Formatting.Test.Integration/FormUrlEncodedFromUriQueryTests.cs
@@ -0,0 +1,503 @@
+using System.Collections.Generic;
+using System.Json;
+using System.Net.Http.Internal;
+using System.Text;
+using Xunit;
+using Xunit.Extensions;
+
+namespace System.Net.Http.Formatting
+{
+ public class FormUrlEncodedJsonFromUriQueryTests
+ {
+ #region Tests
+
+ [Theory,
+ InlineData("abc", "{\"abc\":null}"),
+ InlineData("%2eabc%2e", "{\".abc.\":null}"),
+ InlineData("", "{}"),
+ InlineData("a=1", "{\"a\":\"1\"}")]
+ public void SimpleStringsTest(string encoded, string expectedResult)
+ {
+ ValidateFormUrlEncoded(encoded, expectedResult);
+ }
+
+ [Theory,
+ InlineData("a=2", "{\"a\":\"2\"}"),
+ InlineData("b=true", "{\"b\":\"true\"}"),
+ InlineData("c=hello", "{\"c\":\"hello\"}"),
+ InlineData("d=", "{\"d\":\"\"}"),
+ InlineData("e=null", "{\"e\":null}")]
+ public void SimpleObjectsTest(string encoded, string expectedResult)
+ {
+ ValidateFormUrlEncoded(encoded, expectedResult);
+ }
+
+ [Fact]
+ public void LegacyArraysTest()
+ {
+ ValidateFormUrlEncoded("a=1&a=hello&a=333", "{\"a\":[\"1\",\"hello\",\"333\"]}");
+
+ // Only valid in shallow serialization
+ ParseInvalidFormUrlEncoded("a[z]=2&a[z]=3");
+ }
+
+ [Theory,
+ InlineData("a[]=1&a[]=hello&a[]=333", "{\"a\":[\"1\",\"hello\",\"333\"]}"),
+ InlineData("a[b][]=1&a[b][]=hello&a[b][]=333", "{\"a\":{\"b\":[\"1\",\"hello\",\"333\"]}}"),
+ InlineData("a[]=", "{\"a\":[\"\"]}"),
+ InlineData("a%5B%5D=2", @"{""a"":[""2""]}"),
+ InlineData("a[x][0]=1&a[x][]=2", @"{""a"":{""x"":[""1"",""2""]}}")]
+ public void ArraysTest(string encoded, string expectedResult)
+ {
+ ValidateFormUrlEncoded(encoded, expectedResult);
+ }
+
+ [Theory,
+ InlineData("a[0][]=1&a[0][]=hello&a[1][]=333", "{\"a\":[[\"1\",\"hello\"],[\"333\"]]}"),
+ InlineData("a[b][0][]=1&a[b][1][]=hello&a[b][1][]=333", "{\"a\":{\"b\":[[\"1\"],[\"hello\",\"333\"]]}}"),
+ InlineData("a[0][0][0][]=1", "{\"a\":[[[[\"1\"]]]]}")]
+ public void MultidimensionalArraysTest(string encoded, string expectedResult)
+ {
+ ValidateFormUrlEncoded(encoded, expectedResult);
+ }
+
+ [Theory,
+ InlineData("a[0][]=hello&a[2][]=333", "{\"a\":{\"0\":[\"hello\"],\"2\":[\"333\"]}}"),
+ InlineData("a[0]=hello", "{\"a\":[\"hello\"]}"),
+ InlineData("a[1][]=hello", "{\"a\":{\"1\":[\"hello\"]}}"),
+ InlineData("a[1][0]=hello", "{\"a\":{\"1\":[\"hello\"]}}")]
+ public void SparseArraysTest(string encoded, string expectedResult)
+ {
+ ValidateFormUrlEncoded(encoded, expectedResult);
+ }
+
+ [Theory,
+ InlineData("b[]=2&b[1][c]=d", "{\"b\":[\"2\",{\"c\":\"d\"}]}")]
+ public void ArraysWithMixedMembers(string encoded, string expectedResult)
+ {
+ ValidateFormUrlEncoded(encoded, expectedResult);
+ }
+
+ [Theory,
+ InlineData("=3", "{\"\":\"3\"}"),
+ InlineData("a=1&=3", "{\"a\":\"1\",\"\":\"3\"}"),
+ InlineData("=3&b=2", "{\"\":\"3\",\"b\":\"2\"}")]
+ public void EmptyKeyTest(string encoded, string expectedResult)
+ {
+ ValidateFormUrlEncoded(encoded, expectedResult);
+ }
+
+ [Theory,
+ InlineData("a[b]=1&a=2"),
+ InlineData("a[b]=1&a[b][]=2"),
+ InlineData("a[x][]=1&a[x][0]=2"),
+ InlineData("a=2&a[b]=1"),
+ InlineData("[]=1"),
+ InlineData("a[][]=0"),
+ InlineData("a[][x]=0"),
+ InlineData("a&a[b]=1"),
+ InlineData("a&a=1")]
+ public void InvalidObjectGraphsTest(string encoded)
+ {
+ ParseInvalidFormUrlEncoded(encoded);
+ }
+
+ [Theory,
+ InlineData("a[b=2"),
+ InlineData("a[[b]=2"),
+ InlineData("a[b]]=2")]
+ public void InvalidFormUrlEncodingTest(string encoded)
+ {
+ ParseInvalidFormUrlEncoded(encoded);
+ }
+
+ /// <summary>
+ /// Tests for parsing form-urlencoded data originated from JS primitives.
+ /// </summary>
+ [Theory,
+ InlineData("abc", @"{""abc"":null}"),
+ InlineData("123", @"{""123"":null}"),
+ InlineData("true", @"{""true"":null}"),
+ InlineData("", "{}"),
+ InlineData("%2fabc%2f", @"{""\/abc\/"":null}")]
+ public void TestJsonPrimitive(string encoded, string expectedResult)
+ {
+ ValidateFormUrlEncoded(encoded, expectedResult);
+ }
+
+ /// <summary>
+ /// Negative tests for parsing form-urlencoded data originated from JS primitives.
+ /// </summary>
+ [Theory,
+ InlineData("a[b]=1&a=2"),
+ InlineData("a=2&a[b]=1"),
+ InlineData("[]=1")]
+ public void TestJsonPrimitiveNegative(string encoded)
+ {
+ ParseInvalidFormUrlEncoded(encoded);
+ }
+
+ /// <summary>
+ /// Tests for parsing form-urlencoded data originated from JS objects.
+ /// </summary>
+ [Theory,
+ InlineData("a=NaN", @"{""a"":""NaN""}"),
+ InlineData("a=false", @"{""a"":""false""}"),
+ InlineData("a=foo", @"{""a"":""foo""}"),
+ InlineData("1=1", "{\"1\":\"1\"}")]
+ public void TestObjects(string encoded, string expectedResult)
+ {
+ ValidateFormUrlEncoded(encoded, expectedResult);
+ }
+
+ /// <summary>
+ /// Tests for parsing form-urlencoded data originated from JS arrays.
+ /// </summary>
+ [Theory,
+ InlineData("a[]=2", @"{""a"":[""2""]}"),
+ InlineData("a[]=", @"{""a"":[""""]}"),
+ InlineData("a[0][0][]=1", @"{""a"":[[[""1""]]]}"),
+ InlineData("z[]=9&z[]=true&z[]=undefined&z[]=", @"{""z"":[""9"",""true"",""undefined"",""""]}"),
+ InlineData("z[]=9&z[]=true&z[]=undefined&z[]=null", @"{""z"":[""9"",""true"",""undefined"",null]}"),
+ InlineData("z[0][]=9&z[0][]=true&z[1][]=undefined&z[1][]=null", @"{""z"":[[""9"",""true""],[""undefined"",null]]}"),
+ InlineData("a[0][x]=2", @"{""a"":[{""x"":""2""}]}"),
+ InlineData("a%5B%5D=2", @"{""a"":[""2""]}"),
+ InlineData("a%5B%5D=", @"{""a"":[""""]}"),
+ InlineData("z%5B%5D=9&z%5B%5D=true&z%5B%5D=undefined&z%5B%5D=", @"{""z"":[""9"",""true"",""undefined"",""""]}"),
+ InlineData("z%5B%5D=9&z%5B%5D=true&z%5B%5D=undefined&z%5B%5D=null", @"{""z"":[""9"",""true"",""undefined"",null]}"),
+ InlineData("z%5B0%5D%5B%5D=9&z%5B0%5D%5B%5D=true&z%5B1%5D%5B%5D=undefined&z%5B1%5D%5B%5D=null", @"{""z"":[[""9"",""true""],[""undefined"",null]]}")]
+ public void TestArray(string encoded, string expectedResult)
+ {
+ ValidateFormUrlEncoded(encoded, expectedResult);
+ }
+
+ /// <summary>
+ /// Tests for parsing form-urlencoded data originated from JS arrays, using the jQuery 1.3 format (no []'s).
+ /// </summary>
+ [Theory,
+ InlineData("z=9&z=true&z=undefined&z=", @"{""z"":[""9"",""true"",""undefined"",""""]}"),
+ InlineData("z=9&z=true&z=undefined&z=null", @"{""z"":[""9"",""true"",""undefined"",null]}"),
+ InlineData("z=9&z=true&z=undefined&z=null&a=hello", @"{""z"":[""9"",""true"",""undefined"",null],""a"":""hello""}")]
+ public void TestArrayCompat(string encoded, string expectedResult)
+ {
+ ValidateFormUrlEncoded(encoded, expectedResult);
+ }
+
+ /// <summary>
+ /// Negative tests for parsing form-urlencoded data originated from JS arrays.
+ /// </summary>
+ [Theory,
+ InlineData("a[z]=2&a[z]=3")]
+ public void TestArrayCompatNegative(string encoded)
+ {
+ ParseInvalidFormUrlEncoded(encoded);
+ }
+
+ /// <summary>
+ /// Tests for form-urlencoded data originated from sparse JS arrays.
+ /// </summary>
+ [Theory,
+ InlineData("a[2]=hello", @"{""a"":{""2"":""hello""}}"),
+ InlineData("a[x][0]=2", @"{""a"":{""x"":[""2""]}}"),
+ InlineData("a[x][1]=2", @"{""a"":{""x"":{""1"":""2""}}}"),
+ InlineData("a[x][0]=0&a[x][1]=1", @"{""a"":{""x"":[""0"",""1""]}}"),
+ InlineData("a[0][0][0]=hello&a[1][0][0][0][]=hello", @"{""a"":[[[""hello""]],[[[[""hello""]]]]]}"),
+ InlineData("a[0][0][0]=hello&a[1][0][0][0]=hello", @"{""a"":[[[""hello""]],[[[""hello""]]]]}"),
+ InlineData("a[1][0][]=1", @"{""a"":{""1"":[[""1""]]}}"),
+ InlineData("a[1][1][]=1", @"{""a"":{""1"":{""1"":[""1""]}}}"),
+ InlineData("a[1][1][0]=1", @"{""a"":{""1"":{""1"":[""1""]}}}"),
+ InlineData("a[0][]=2&a[0][]=3&a[2][]=1", "{\"a\":{\"0\":[\"2\",\"3\"],\"2\":[\"1\"]}}"),
+ InlineData("a[x][]=1&a[x][1]=2", @"{""a"":{""x"":[""1"",""2""]}}"),
+ InlineData("a[x][0]=1&a[x][]=2", @"{""a"":{""x"":[""1"",""2""]}}")]
+ public void TestArraySparse(string encoded, string expectedResult)
+ {
+ ValidateFormUrlEncoded(encoded, expectedResult);
+ }
+
+ /// <summary>
+ /// Negative tests for parsing form-urlencoded arrays.
+ /// </summary>
+ [Theory,
+ InlineData("a[x]=2&a[x][]=3"),
+ InlineData("a[]=1&a[0][]=2"),
+ InlineData("a[]=1&a[0][0][]=2"),
+ InlineData("a[x][]=1&a[x][0]=2"),
+ InlineData("a[][]=0"),
+ InlineData("a[][x]=0")]
+ public void TestArrayIndexNegative(string encoded)
+ {
+ ParseInvalidFormUrlEncoded(encoded);
+ }
+
+
+ public static IEnumerable<object[]> TestObjectPropertyData
+ {
+ get
+ {
+ string encoded = "a[]=4&a[]=5&b[x][]=7&b[y]=8&b[z][]=9&b[z][]=true&b[z][]=undefined&b[z][]=&c=1&f=";
+ string resultStr = @"{""a"":[""4"",""5""],""b"":{""x"":[""7""],""y"":""8"",""z"":[""9"",""true"",""undefined"",""""]},""c"":""1"",""f"":""""}";
+ yield return new[] { encoded, resultStr };
+
+ encoded = "customer[Name]=Pete&customer[Address]=Redmond&customer[Age][0][]=23&customer[Age][0][]=24&customer[Age][1][]=25&" +
+ "customer[Age][1][]=26&customer[Phones][]=425+888+1111&customer[Phones][]=425+345+7777&customer[Phones][]=425+888+4564&" +
+ "customer[EnrolmentDate]=%22%5C%2FDate(1276562539537)%5C%2F%22&role=NewRole&changeDate=3&count=15";
+ resultStr = @"{""customer"":{""Name"":""Pete"",""Address"":""Redmond"",""Age"":[[""23"",""24""],[""25"",""26""]]," +
+ @"""Phones"":[""425 888 1111"",""425 345 7777"",""425 888 4564""],""EnrolmentDate"":""\""\\\/Date(1276562539537)\\\/\""""},""role"":""NewRole"",""changeDate"":""3"",""count"":""15""}";
+ yield return new[] { encoded, resultStr };
+
+ encoded = "customers[0][Name]=Pete2&customers[0][Address]=Redmond2&customers[0][Age][0][]=23&customers[0][Age][0][]=24&" +
+ "customers[0][Age][1][]=25&customers[0][Age][1][]=26&customers[0][Phones][]=425+888+1111&customers[0][Phones][]=425+345+7777&" +
+ "customers[0][Phones][]=425+888+4564&customers[0][EnrolmentDate]=%22%5C%2FDate(1276634840700)%5C%2F%22&customers[1][Name]=Pete3&" +
+ "customers[1][Address]=Redmond3&customers[1][Age][0][]=23&customers[1][Age][0][]=24&customers[1][Age][1][]=25&customers[1][Age][1][]=26&" +
+ "customers[1][Phones][]=425+888+1111&customers[1][Phones][]=425+345+7777&customers[1][Phones][]=425+888+4564&customers[1][EnrolmentDate]=%22%5C%2FDate(1276634840700)%5C%2F%22";
+ resultStr = @"{""customers"":[{""Name"":""Pete2"",""Address"":""Redmond2"",""Age"":[[""23"",""24""],[""25"",""26""]]," +
+ @"""Phones"":[""425 888 1111"",""425 345 7777"",""425 888 4564""],""EnrolmentDate"":""\""\\\/Date(1276634840700)\\\/\""""}," +
+ @"{""Name"":""Pete3"",""Address"":""Redmond3"",""Age"":[[""23"",""24""],[""25"",""26""]],""Phones"":[""425 888 1111"",""425 345 7777"",""425 888 4564""],""EnrolmentDate"":""\""\\\/Date(1276634840700)\\\/\""""}]}";
+ yield return new[] { encoded, resultStr };
+
+ encoded = "ab%5B%5D=hello";
+ resultStr = @"{""ab"":[""hello""]}";
+ yield return new[] { encoded, resultStr };
+
+ encoded = "123=hello";
+ resultStr = @"{""123"":""hello""}";
+ yield return new[] { encoded, resultStr };
+
+ encoded = "a%5B%5D=1&a";
+ resultStr = @"{""a"":[""1"",null]}";
+ yield return new[] { encoded, resultStr };
+
+ encoded = "a=1&a";
+ resultStr = @"{""a"":[""1"",null]}";
+ yield return new[] { encoded, resultStr };
+ }
+ }
+
+ /// <summary>
+ /// Tests for parsing complex object graphs form-urlencoded.
+ /// </summary>
+ [Theory]
+ [PropertyData("TestObjectPropertyData")]
+ public void TestObject(string encoded, string expectedResult)
+ {
+ ValidateFormUrlEncoded(encoded, expectedResult);
+ }
+
+ public static IEnumerable<object[]> TestEncodedNameTestData
+ {
+ get
+ {
+ string encoded = "some+thing=10";
+ string resultStr = @"{""some thing"":""10""}";
+ yield return new[] { encoded, resultStr };
+
+ encoded = "%E5%B8%A6%E4%B8%89%E4%B8%AA%E8%A1%A8=bar";
+ resultStr = @"{""带三个表"":""bar""}";
+ yield return new[] { encoded, resultStr };
+
+ encoded = "some+thing=10&%E5%B8%A6%E4%B8%89%E4%B8%AA%E8%A1%A8=bar";
+ resultStr = @"{""some thing"":""10"",""带三个表"":""bar""}";
+ yield return new[] { encoded, resultStr };
+
+ encoded = "a[0\r\n][b]=1";
+ resultStr = "{\"a\":{\"0\\u000d\\u000a\":{\"b\":\"1\"}}}";
+ yield return new[] { encoded, resultStr };
+ yield return new[] { encoded.Replace("\r", "%0D").Replace("\n", "%0A"), resultStr };
+
+ yield return new[] { "a[0\0]=1", "{\"a\":{\"0\\u0000\":\"1\"}}" };
+ yield return new[] { "a[0%00]=1", "{\"a\":{\"0\\u0000\":\"1\"}}" };
+ yield return new[] { "a[\00]=1", "{\"a\":{\"\\u00000\":\"1\"}}" };
+ yield return new[] { "a[%000]=1", "{\"a\":{\"\\u00000\":\"1\"}}" };
+ }
+ }
+
+ /// <summary>
+ /// Tests for parsing form-urlencoded data with encoded names.
+ /// </summary>
+ [Theory]
+ [PropertyData("TestEncodedNameTestData")]
+ public void TestEncodedName(string encoded, string expectedResult)
+ {
+ ValidateFormUrlEncoded(encoded, expectedResult);
+ }
+
+ /// <summary>
+ /// Tests for malformed form-urlencoded data.
+ /// </summary>
+ [Theory,
+ InlineData("a[b=2"),
+ InlineData("a[[b]=2"),
+ InlineData("a[b]]=2")]
+ public void TestNegative(string encoded)
+ {
+ ParseInvalidFormUrlEncoded(encoded);
+ }
+
+ /// <summary>
+ /// Tests for parsing generated form-urlencoded data.
+ /// </summary>
+ [Fact]
+ public void GeneratedJsonValueTest()
+ {
+ Random rndGen = new Random(1);
+ int oldMaxArray = CreatorSettings.MaxArrayLength;
+ int oldMaxList = CreatorSettings.MaxListLength;
+ int oldMaxStr = CreatorSettings.MaxStringLength;
+ double oldNullProbability = CreatorSettings.NullValueProbability;
+ bool oldCreateAscii = CreatorSettings.CreateOnlyAsciiChars;
+ CreatorSettings.MaxArrayLength = 5;
+ CreatorSettings.MaxListLength = 3;
+ CreatorSettings.MaxStringLength = 3;
+ CreatorSettings.NullValueProbability = 0;
+ CreatorSettings.CreateOnlyAsciiChars = true;
+ JsonValueCreatorSurrogate jsonValueCreator = new JsonValueCreatorSurrogate();
+ try
+ {
+ for (int i = 0; i < 1000; i++)
+ {
+ JsonValue jv = (JsonValue)jsonValueCreator.CreateInstanceOf(typeof(JsonValue), rndGen);
+ if (jv.JsonType == JsonType.Array || jv.JsonType == JsonType.Object)
+ {
+ string jaStr = FormUrlEncoding(jv);
+ JsonValue deserJv = ParseFormUrlEncoded(jaStr);
+ bool compare = true;
+ if (deserJv is JsonObject && ((JsonObject)deserJv).ContainsKey("JV"))
+ {
+ compare = JsonValueRoundTripComparer.Compare(jv, deserJv["JV"]);
+ }
+ else
+ {
+ compare = JsonValueRoundTripComparer.Compare(jv, deserJv);
+ }
+
+ Assert.True(compare, "Comparison failed for test instance " + i);
+ }
+ }
+ }
+ finally
+ {
+ CreatorSettings.MaxArrayLength = oldMaxArray;
+ CreatorSettings.MaxListLength = oldMaxList;
+ CreatorSettings.MaxStringLength = oldMaxStr;
+ CreatorSettings.NullValueProbability = oldNullProbability;
+ CreatorSettings.CreateOnlyAsciiChars = oldCreateAscii;
+ }
+ }
+
+ #endregion
+
+ #region Helpers
+ private static string FormUrlEncoding(JsonValue jsonValue)
+ {
+ List<string> results = new List<string>();
+ if (jsonValue is JsonPrimitive)
+ {
+ return UriQueryUtility.UrlEncode(((JsonPrimitive)jsonValue).Value.ToString());
+ }
+
+ BuildParams("JV", jsonValue, results);
+ StringBuilder strResult = new StringBuilder();
+ foreach (var result in results)
+ {
+ strResult.Append("&" + result);
+ }
+
+ if (strResult.Length > 0)
+ {
+ return strResult.Remove(0, 1).ToString();
+ }
+
+ return strResult.ToString();
+ }
+
+ private static void BuildParams(string prefix, JsonValue jsonValue, List<string> results)
+ {
+ if (jsonValue is JsonPrimitive)
+ {
+ JsonPrimitive jsonPrimitive = jsonValue as JsonPrimitive;
+ if (jsonPrimitive != null)
+ {
+ if (jsonPrimitive.JsonType == JsonType.String && String.IsNullOrEmpty(jsonPrimitive.Value.ToString()))
+ {
+ results.Add(prefix + "=" + String.Empty);
+ }
+ else
+ {
+ if (jsonPrimitive.Value is DateTime || jsonPrimitive.Value is DateTimeOffset)
+ {
+ string dateStr = jsonPrimitive.ToString();
+ if (!String.IsNullOrEmpty(dateStr) && dateStr.StartsWith("\""))
+ {
+ dateStr = dateStr.Substring(1, dateStr.Length - 2);
+ }
+ results.Add(prefix + "=" + UriQueryUtility.UrlEncode(dateStr));
+ }
+ else
+ {
+ results.Add(prefix + "=" + UriQueryUtility.UrlEncode(jsonPrimitive.Value.ToString()));
+ }
+ }
+ }
+ else
+ {
+ results.Add(prefix + "=" + String.Empty);
+ }
+ }
+ else if (jsonValue is JsonArray)
+ {
+ for (int i = 0; i < jsonValue.Count; i++)
+ {
+ if (jsonValue[i] is JsonArray || jsonValue[i] is JsonObject)
+ {
+ BuildParams(prefix + "[" + i + "]", jsonValue[i], results);
+ }
+ else
+ {
+ BuildParams(prefix + "[]", jsonValue[i], results);
+ }
+ }
+ }
+ else //jsonValue is JsonObject
+ {
+ foreach (KeyValuePair<string, JsonValue> item in jsonValue)
+ {
+ BuildParams(prefix + "[" + item.Key + "]", item.Value, results);
+ }
+ }
+ }
+
+ private static Uri GetQueryUri(string query)
+ {
+ UriBuilder uriBuilder = new UriBuilder("http://some.host");
+ uriBuilder.Query = query;
+ return uriBuilder.Uri;
+ }
+
+ private static JsonValue ParseFormUrlEncoded(string encoded)
+ {
+ Uri address = GetQueryUri(encoded);
+ JsonObject result;
+ Assert.True(address.TryReadQueryAsJson(out result), "Expected parsing to return true");
+ return result;
+ }
+
+ private static void ParseInvalidFormUrlEncoded(string encoded)
+ {
+ Uri address = GetQueryUri(encoded);
+ JsonObject result;
+ Assert.False(address.TryReadQueryAsJson(out result), "Expected parsing to return false");
+ Assert.Null(result);
+ }
+
+ private static void ValidateFormUrlEncoded(string encoded, string expectedResult)
+ {
+ Uri address = GetQueryUri(encoded);
+ JsonObject result;
+ Assert.True(address.TryReadQueryAsJson(out result), "Expected parsing to return true");
+ Assert.NotNull(result);
+ Assert.Equal(expectedResult, result.ToString());
+ }
+
+ #endregion
+ }
+} \ No newline at end of file
diff --git a/test/System.Net.Http.Formatting.Test.Integration/JsonNetSerializationTest.cs b/test/System.Net.Http.Formatting.Test.Integration/JsonNetSerializationTest.cs
new file mode 100644
index 00000000..6348e893
--- /dev/null
+++ b/test/System.Net.Http.Formatting.Test.Integration/JsonNetSerializationTest.cs
@@ -0,0 +1,536 @@
+using System.Collections;
+using System.Collections.Generic;
+using System.IO;
+using System.Json;
+using System.Runtime.Serialization;
+using System.Text;
+using System.Threading.Tasks;
+using Microsoft.TestCommon;
+using Newtonsoft.Json;
+using Xunit;
+using Xunit.Extensions;
+
+namespace System.Net.Http.Formatting
+{
+ public class JsonNetSerializationTest
+ {
+ public static IEnumerable<object[]> SerializedJson
+ {
+ get
+ {
+ return new TheoryDataSet<object, string>()
+ {
+ // Primitives
+ { 'f', "\"f\"" },
+ { "abc", "\"abc\"" },
+ { "\"\\", @"""\""\\""" },
+ { 256, "256" },
+ { (ulong)long.MaxValue, long.MaxValue.ToString() },
+ { 45.78m, "45.78" },
+ { .00000457823432, "4.57823432E-06" },
+ { (byte)24, "24" },
+ { false, "false" },
+ { AttributeTargets.Assembly | AttributeTargets.Constructor, "33" },
+ { ConsoleColor.DarkCyan, "3" },
+ { new DateTimeOffset(1999, 5, 27, 4, 34, 45, TimeSpan.Zero), "\"\\/Date(927779685000+0000)\\/\"" },
+ { new TimeSpan(5, 30, 0), "\"05:30:00\"" },
+ { new Uri("http://www.bing.com"), @"""http://www.bing.com/""" },
+ { new Guid("4ed1cd44-11d7-4b27-b623-0b8b553c8906"), "\"4ed1cd44-11d7-4b27-b623-0b8b553c8906\"" },
+
+ // Structs
+ { new Point() { x = 45, Y = -5}, "{\"x\":45,\"Y\":-5}" },
+
+ // Arrays
+ { new object[] {}, "[]" },
+ { new int[] { 1, 2, 3}, "[1,2,3]" },
+ { new string[] { "a", "b"}, "[\"a\",\"b\"]" },
+ { new Point[] { new Point() { x = 10, Y = 10}, new Point() { x = 20, Y = 20}}, "[{\"x\":10,\"Y\":10},{\"x\":20,\"Y\":20}]" },
+
+ // Collections
+ { new List<int> { 1, 2, 3}, "[1,2,3]" },
+ { new List<string> { "a", "b"}, "[\"a\",\"b\"]" },
+ { new List<Point> { new Point() { x = 10, Y = 10}, new Point() { x = 20, Y = 20}}, "[{\"x\":10,\"Y\":10},{\"x\":20,\"Y\":20}]" },
+ { new MyList<int> { 1, 2, 3}, "[1,2,3]" },
+ { new MyList<string> { "a", "b"}, "[\"a\",\"b\"]" },
+ { new MyList<Point> { new Point() { x = 10, Y = 10}, new Point() { x = 20, Y = 20}}, "[{\"x\":10,\"Y\":10},{\"x\":20,\"Y\":20}]" },
+
+ // Dictionaries
+
+ { new Dictionary<string, string> { { "k1", "v1" }, { "k2", "v2" } }, "{\"k1\":\"v1\",\"k2\":\"v2\"}" },
+ { new Dictionary<int, string> { { 1, "v1" }, { 2, "v2" } }, "{\"1\":\"v1\",\"2\":\"v2\"}" },
+
+ // Anonymous types
+ { new { Anon1 = 56, Anon2 = "foo"}, "{\"Anon1\":56,\"Anon2\":\"foo\"}" },
+
+ // Classes
+ { new DataContractType() { s = "foo", i = 49, NotAMember = "Error" }, "{\"s\":\"foo\",\"i\":49}" },
+ { new POCOType() { s = "foo", t = "Error"}, "{\"s\":\"foo\"}" },
+ { new SerializableType("protected") { publicField = "public", protectedInternalField = "protected internal", internalField = "internal", PublicProperty = "private", nonSerializedField = "Error" }, "{\"publicField\":\"public\",\"internalField\":\"internal\",\"protectedInternalField\":\"protected internal\",\"protectedField\":\"protected\",\"privateField\":\"private\"}" },
+
+ // Generics
+ { new KeyValuePair<string, bool>("foo", false), "{\"Key\":\"foo\",\"Value\":false}" },
+
+ // ISerializable types
+ { new ArgumentNullException("param"), "{\"ClassName\":\"System.ArgumentNullException\",\"Message\":\"Value cannot be null.\",\"Data\":null,\"InnerException\":null,\"HelpURL\":null,\"StackTraceString\":null,\"RemoteStackTraceString\":null,\"RemoteStackIndex\":0,\"ExceptionMethod\":null,\"HResult\":-2147467261,\"Source\":null,\"WatsonBuckets\":null,\"ParamName\":\"param\"}" },
+
+ // JSON Values
+ { new JsonPrimitive(false), "false" },
+ { new JsonPrimitive(54), "54" },
+ { new JsonPrimitive("s"), "\"s\"" },
+ { new JsonArray(new JsonPrimitive(1), new JsonPrimitive(2)), "[1,2]" },
+ { new JsonObject(new KeyValuePair<string, JsonValue>("k1", new JsonPrimitive("v1")), new KeyValuePair<string, JsonValue>("k2", new JsonPrimitive("v2"))), "{\"k1\":\"v1\",\"k2\":\"v2\"}" },
+ { new KeyValuePair<JsonValue, JsonValue>(new JsonPrimitive("k"), new JsonArray(new JsonPrimitive("v1"), new JsonPrimitive("v2"))), "{\"Key\":\"k\",\"Value\":[\"v1\",\"v2\"]}" },
+ };
+ }
+ }
+
+ public static IEnumerable<object[]> TypedSerializedJson
+ {
+ get
+ {
+ return new TheoryDataSet<object, string, Type>()
+ {
+ // Null
+ { null, "null", typeof(POCOType) },
+ { null, "null", typeof(JsonValue) },
+
+ // Nullables
+ { new int?(), "null", typeof(int?) },
+ { new Point?(), "null", typeof(Point?) },
+ { new ConsoleColor?(), "null", typeof(ConsoleColor?) },
+ { new int?(45), "45", typeof(int?) },
+ { new Point?(new Point() { x = 45, Y = -5 }), "{\"x\":45,\"Y\":-5}", typeof(Point?) },
+ { new ConsoleColor?(ConsoleColor.DarkMagenta), "5", typeof(ConsoleColor?)},
+ };
+ }
+ }
+
+ [Theory]
+ [PropertyData("SerializedJson")]
+ public void ObjectsSerializeToExpectedJson(object o, string expectedJson)
+ {
+ ObjectsSerializeToExpectedJsonWithProvidedType(o, expectedJson, o.GetType());
+ }
+
+ [Theory]
+ [PropertyData("SerializedJson")]
+ public void JsonDeserializesToExpectedObject(object expectedObject, string json)
+ {
+ JsonDeserializesToExpectedObjectWithProvidedType(expectedObject, json, expectedObject.GetType());
+ }
+
+ [Theory]
+ [PropertyData("TypedSerializedJson")]
+ public void ObjectsSerializeToExpectedJsonWithProvidedType(object o, string expectedJson, Type type)
+ {
+ Assert.Equal(expectedJson, Serialize(o, type));
+ }
+
+ [Theory]
+ [PropertyData("TypedSerializedJson")]
+ public void JsonDeserializesToExpectedObjectWithProvidedType(object expectedObject, string json, Type type)
+ {
+ if (expectedObject == null)
+ {
+ Assert.Null(Deserialize(json, type));
+ }
+ else
+ {
+ Assert.Equal(expectedObject, Deserialize(json, type), new ObjectComparer());
+ }
+ }
+
+ [Fact]
+ public void CallbacksGetCalled()
+ {
+ TypeWithCallbacks o = new TypeWithCallbacks();
+
+ string json = Serialize(o, typeof(TypeWithCallbacks));
+ Assert.Equal("12", o.callbackOrder);
+
+ TypeWithCallbacks deserializedObject = Deserialize(json, typeof(TypeWithCallbacks)) as TypeWithCallbacks;
+ Assert.Equal("34", deserializedObject.callbackOrder);
+ }
+
+ [Fact]
+ public void DerivedTypesArePreserved()
+ {
+ JsonMediaTypeFormatter formatter = new JsonMediaTypeFormatter();
+ formatter.Serializer.TypeNameHandling = TypeNameHandling.Objects;
+ string json = Serialize(new Derived(), typeof(Base), formatter);
+ object deserializedObject = Deserialize(json, typeof(Base), formatter);
+ Assert.IsType(typeof(Derived), deserializedObject);
+ }
+
+ [Fact]
+ public void ArbitraryTypesArentDeserializedByDefault()
+ {
+ string json = "{\"$type\":\"" + typeof(DangerousType).AssemblyQualifiedName + "\"}";
+ object deserializedObject = Deserialize(json, typeof(object));
+ Assert.IsNotType(typeof(DangerousType), deserializedObject);
+ }
+
+ [Fact]
+ public void ReferencesArePreserved()
+ {
+ Ref ref1 = new Ref();
+ Ref ref2 = new Ref();
+ ref1.Reference = ref2;
+ ref2.Reference = ref1;
+
+ string json = Serialize(ref1, typeof(Ref));
+ Ref deserializedObject = Deserialize(json, typeof(Ref)) as Ref;
+
+ Assert.ReferenceEquals(deserializedObject, deserializedObject.Reference.Reference);
+ }
+
+ [Fact]
+ public void DeserializingDeepObjectsThrows()
+ {
+ StringBuilder sb = new StringBuilder();
+ int depth = 1500;
+ for (int i = 0; i < depth; i++)
+ {
+ sb.Append("{\"a\":");
+ }
+ sb.Append("null");
+ for (int i = 0; i < depth; i++)
+ {
+ sb.Append("}");
+ }
+ string json = sb.ToString();
+
+ MediaTypeFormatter.SkipStreamLimitChecks = true;
+ Assert.Throws(typeof(JsonSerializationException), () => Deserialize(json, typeof(object)));
+ }
+
+ [Fact]
+ public void DeserializingDeepArraysThrows()
+ {
+ StringBuilder sb = new StringBuilder();
+ int depth = 1500;
+ for (int i = 0; i < depth; i++)
+ {
+ sb.Append("[");
+ }
+ sb.Append("null");
+ for (int i = 0; i < depth; i++)
+ {
+ sb.Append("]");
+ }
+ string json = sb.ToString();
+
+ Assert.Throws(typeof(JsonSerializationException), () => Deserialize(json, typeof(object)));
+ }
+
+ [Theory]
+ // existing good surrogate pair
+ [InlineData("ABC \\ud800\\udc00 DEF", "ABC \ud800\udc00 DEF")]
+ // invalid surrogates (two high back-to-back)
+ [InlineData("ABC \\ud800\\ud800 DEF", "ABC \ufffd\ufffd DEF")]
+ // invalid high surrogate at end of string
+ [InlineData("ABC \\ud800", "ABC \ufffd")]
+ // high surrogate not followed by low surrogate
+ [InlineData("ABC \\ud800 DEF", "ABC \ufffd DEF")]
+ // low surrogate not preceded by high surrogate
+ [InlineData("ABC \\udc00\\ud800 DEF", "ABC \ufffd\ufffd DEF")]
+ // make sure unencoded invalid surrogate characters don't make it through
+ [InlineData("\udc00\ud800\ud800", "??????")]
+ public void InvalidUnicodeStringsAreFixedUp(string input, string expectedString)
+ {
+ string json = "\"" + input + "\"";
+ string deserializedString = Deserialize(json, typeof(string)) as string;
+
+ Assert.Equal(expectedString, deserializedString);
+
+ }
+
+ string Serialize(object o, Type type, MediaTypeFormatter formatter = null)
+ {
+ formatter = formatter ?? new JsonMediaTypeFormatter();
+ MemoryStream ms = new MemoryStream();
+ formatter.WriteToStreamAsync(type, o, ms, null, null).Wait();
+ ms.Flush();
+ ms.Position = 0;
+ return new StreamReader(ms).ReadToEnd();
+ }
+
+ object Deserialize(string json, Type type, MediaTypeFormatter formatter = null)
+ {
+ formatter = formatter ?? new JsonMediaTypeFormatter();
+ MemoryStream ms = new MemoryStream();
+ byte[] bytes = Encoding.Default.GetBytes(json);
+ ms.Write(bytes, 0, bytes.Length);
+ ms.Flush();
+ ms.Position = 0;
+ Task<object> readTask = formatter.ReadFromStreamAsync(type, ms, contentHeaders: null, formatterLogger: null);
+ readTask.WaitUntilCompleted();
+ if (readTask.IsFaulted)
+ {
+ throw readTask.Exception.GetBaseException();
+ }
+ return readTask.Result;
+ }
+ }
+
+ public class ObjectComparer : IEqualityComparer<object>
+ {
+ bool IEqualityComparer<object>.Equals(object x, object y)
+ {
+ Type xType = x.GetType();
+
+ if (xType == y.GetType())
+ {
+ if (typeof(JsonValue).IsAssignableFrom(xType) || xType == typeof(ArgumentNullException) || xType == typeof(KeyValuePair<JsonValue, JsonValue>))
+ {
+ return x.ToString() == y.ToString();
+ }
+ if (xType == typeof(DataContractType))
+ {
+ return Equals<DataContractType>(x, y);
+ }
+ if (xType == typeof(POCOType))
+ {
+ return Equals<POCOType>(x, y);
+ }
+
+ if (xType == typeof(SerializableType))
+ {
+ return Equals<SerializableType>(x, y);
+ }
+
+ if (xType == typeof(Point))
+ {
+ return Equals<Point>(x, y);
+ }
+
+ if (typeof(IEnumerable).IsAssignableFrom(xType))
+ {
+ IEnumerator xEnumerator = ((IEnumerable)x).GetEnumerator();
+ IEnumerator yEnumerator = ((IEnumerable)y).GetEnumerator();
+ while (xEnumerator.MoveNext())
+ {
+ // if x is longer than y
+ if (!yEnumerator.MoveNext())
+ {
+ return false;
+ }
+ else
+ {
+ if (!xEnumerator.Current.Equals(yEnumerator.Current))
+ {
+ return false;
+ }
+ }
+ }
+ // if y is longer than x
+ if (yEnumerator.MoveNext())
+ {
+ return false;
+ }
+ return true;
+ }
+ }
+
+ return x.Equals(y);
+ }
+
+ int IEqualityComparer<object>.GetHashCode(object obj)
+ {
+ throw new NotImplementedException();
+ }
+
+ bool Equals<T>(object x, object y) where T : IEquatable<T>
+ {
+ IEquatable<T> yEquatable = (IEquatable<T>)y;
+ return yEquatable.Equals((T)x);
+ }
+ }
+
+ // Marked as [Serializable] to check that [DataContract] takes precedence over [Serializable]
+ [DataContract]
+ [Serializable]
+ public class DataContractType : IEquatable<DataContractType>
+ {
+ [DataMember]
+ public string s;
+
+ [DataMember]
+ internal int i;
+
+ public string NotAMember;
+
+ public bool Equals(DataContractType other)
+ {
+ return this.s == other.s && this.i == other.i;
+ }
+ }
+
+ public class POCOType : IEquatable<POCOType>
+ {
+ public string s;
+ internal string t;
+
+ public bool Equals(POCOType other)
+ {
+ return this.s == other.s;
+ }
+ }
+
+ public class MyList<T> : ICollection<T>
+ {
+ List<T> innerList = new List<T>();
+
+ public IEnumerator<T> GetEnumerator()
+ {
+ return innerList.GetEnumerator();
+ }
+
+ public void Add(T item)
+ {
+ innerList.Add(item);
+ }
+
+ public void Clear()
+ {
+ innerList.Clear();
+ }
+
+ public bool Contains(T item)
+ {
+ return innerList.Contains(item);
+ }
+
+ public void CopyTo(T[] array, int arrayIndex)
+ {
+ innerList.CopyTo(array, arrayIndex);
+ }
+
+ public int Count
+ {
+ get { return innerList.Count; }
+ }
+
+ public bool IsReadOnly
+ {
+ get { return ((ICollection<T>)innerList).IsReadOnly; }
+ }
+
+ public bool Remove(T item)
+ {
+ return innerList.Remove(item);
+ }
+
+ IEnumerator IEnumerable.GetEnumerator()
+ {
+ return innerList.GetEnumerator();
+ }
+
+ IEnumerator<T> IEnumerable<T>.GetEnumerator()
+ {
+ return innerList.GetEnumerator();
+ }
+ }
+
+ [Serializable]
+ class SerializableType : IEquatable<SerializableType>
+ {
+ public SerializableType(string protectedFieldValue)
+ {
+ this.protectedField = protectedFieldValue;
+ }
+
+ public string publicField;
+ internal string internalField;
+ protected internal string protectedInternalField;
+ protected string protectedField;
+ private string privateField;
+
+ public string PublicProperty
+ {
+ get
+ {
+ return privateField;
+ }
+ set
+ {
+ this.privateField = value;
+ }
+ }
+
+ [NonSerialized]
+ public string nonSerializedField;
+
+ public bool Equals(SerializableType other)
+ {
+ return this.publicField == other.publicField &&
+ this.internalField == other.internalField &&
+ this.protectedInternalField == other.protectedInternalField &&
+ this.protectedField == other.protectedField &&
+ this.privateField == other.privateField;
+ }
+ }
+
+ public struct Point : IEquatable<Point>
+ {
+ public int x;
+ public int Y { get; set; }
+
+ public bool Equals(Point other)
+ {
+ return this.x == other.x && this.Y == other.Y;
+ }
+ }
+
+ [DataContract(IsReference = true)]
+ public class Ref
+ {
+ [DataMember]
+ public Ref Reference;
+ }
+
+ public class Base
+ {
+
+ }
+
+ public class Derived : Base
+ {
+
+ }
+
+ [DataContract]
+ public class TypeWithCallbacks
+ {
+ public string callbackOrder = "";
+
+ [OnSerializing]
+ public void OnSerializing(StreamingContext c)
+ {
+ callbackOrder += "1";
+ }
+
+ [OnSerialized]
+ public void OnSerialized(StreamingContext c)
+ {
+ callbackOrder += "2";
+ }
+
+ [OnDeserializing]
+ public void OnDeserializing(StreamingContext c)
+ {
+ callbackOrder += "3";
+ }
+
+ [OnDeserialized]
+ public void OnDeserialized(StreamingContext c)
+ {
+ callbackOrder += "4";
+ }
+ }
+
+ public class DangerousType
+ {
+
+ }
+}
diff --git a/test/System.Net.Http.Formatting.Test.Integration/JsonValueRoundTripComparer.cs b/test/System.Net.Http.Formatting.Test.Integration/JsonValueRoundTripComparer.cs
new file mode 100644
index 00000000..8a0fa2e2
--- /dev/null
+++ b/test/System.Net.Http.Formatting.Test.Integration/JsonValueRoundTripComparer.cs
@@ -0,0 +1,83 @@
+using System.Collections.Generic;
+using System.Json;
+using System.Net.Http.Internal;
+
+namespace System.Net.Http.Formatting
+{
+ internal class JsonValueRoundTripComparer
+ {
+ public static bool Compare(JsonValue initValue, JsonValue newValue)
+ {
+ if (initValue == null && newValue == null)
+ {
+ return true;
+ }
+
+ if (initValue == null || newValue == null)
+ {
+ return false;
+ }
+
+ if (initValue is JsonPrimitive)
+ {
+ string initStr;
+ if (initValue.JsonType == JsonType.String)
+ {
+ initStr = initValue.ToString();
+ }
+ else
+ {
+ initStr = String.Format("\"{0}\"", ((JsonPrimitive)initValue).Value.ToString());
+ }
+
+ string newStr;
+ if (newValue is JsonPrimitive)
+ {
+ newStr = newValue.ToString();
+ initStr = UriQueryUtility.UrlDecode(UriQueryUtility.UrlEncode(initStr));
+ return initStr.Equals(newStr);
+ }
+ else if (newValue is JsonObject && newValue.Count == 1)
+ {
+ initStr = String.Format("{0}", initValue.ToString());
+ return ((JsonObject)newValue).Keys.Contains(initStr);
+ }
+
+ return false;
+ }
+
+ if (initValue.Count != newValue.Count)
+ {
+ return false;
+ }
+
+ if (initValue is JsonObject && newValue is JsonObject)
+ {
+ foreach (KeyValuePair<string, JsonValue> item in initValue)
+ {
+ if (!Compare(item.Value, newValue[item.Key]))
+ {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ if (initValue is JsonArray && newValue is JsonArray)
+ {
+ for (int i = 0; i < initValue.Count; i++)
+ {
+ if (!Compare(initValue[i], newValue[i]))
+ {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ return false;
+ }
+ }
+}
diff --git a/test/System.Net.Http.Formatting.Test.Integration/Properties/AssemblyInfo.cs b/test/System.Net.Http.Formatting.Test.Integration/Properties/AssemblyInfo.cs
new file mode 100644
index 00000000..34e306e0
--- /dev/null
+++ b/test/System.Net.Http.Formatting.Test.Integration/Properties/AssemblyInfo.cs
@@ -0,0 +1,34 @@
+using System.Reflection;
+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("System.Net.Http.Formatting.Test.Integration")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("Microsoft")]
+[assembly: AssemblyProduct("System.Net.Http.Formatting.Test.Integration")]
+[assembly: AssemblyCopyright("Copyright © Microsoft 2011")]
+[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("c858cf22-d435-4996-ba01-fa63ebe12a47")]
+
+// 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/test/System.Net.Http.Formatting.Test.Integration/System.Net.Http.Formatting.Test.Integration.csproj b/test/System.Net.Http.Formatting.Test.Integration/System.Net.Http.Formatting.Test.Integration.csproj
new file mode 100644
index 00000000..d4c75d3d
--- /dev/null
+++ b/test/System.Net.Http.Formatting.Test.Integration/System.Net.Http.Formatting.Test.Integration.csproj
@@ -0,0 +1,96 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory),Runtime.sln))\tools\WebStack.settings.targets" />
+ <PropertyGroup>
+ <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+ <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+ <ProductVersion>9.0.30729</ProductVersion>
+ <SchemaVersion>2.0</SchemaVersion>
+ <ProjectGuid>{6C18CC83-1E4C-42D2-B93E-55D6C363850C}</ProjectGuid>
+ <OutputType>Library</OutputType>
+ <AppDesignerFolder>Properties</AppDesignerFolder>
+ <RootNamespace>System.Net.Http.Formatting</RootNamespace>
+ <AssemblyName>System.Net.Http.Formatting.Test.Integration</AssemblyName>
+ <FileAlignment>512</FileAlignment>
+ <ProjectTypeGuids>{3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
+ <ErrorReport>prompt</ErrorReport>
+ <WarningLevel>4</WarningLevel>
+ <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
+ <DebugSymbols>true</DebugSymbols>
+ <DebugType>full</DebugType>
+ <Optimize>false</Optimize>
+ <OutputPath>$(WebStackRootPath)\bin\Debug\Test\</OutputPath>
+ <DefineConstants>TRACE;DEBUG</DefineConstants>
+ <CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
+ <DebugType>pdbonly</DebugType>
+ <Optimize>true</Optimize>
+ <OutputPath>$(WebStackRootPath)\bin\Release\Test\</OutputPath>
+ <DefineConstants>TRACE</DefineConstants>
+ <CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)' == 'CodeCoverage' ">
+ <DebugSymbols>true</DebugSymbols>
+ <DebugType>full</DebugType>
+ <Optimize>false</Optimize>
+ <OutputPath>$(WebStackRootPath)\bin\CodeCoverage\Test\</OutputPath>
+ <DefineConstants>TRACE;DEBUG</DefineConstants>
+ <CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
+ </PropertyGroup>
+ <ItemGroup>
+ <Reference Include="Newtonsoft.Json, Version=4.0.8.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
+ <SpecificVersion>False</SpecificVersion>
+ <HintPath>..\..\packages\Newtonsoft.Json.4.0.8\lib\net40\Newtonsoft.Json.dll</HintPath>
+ </Reference>
+ <Reference Include="System" />
+ <Reference Include="System.Net.Http">
+ <HintPath>..\..\packages\Microsoft.Net.Http.2.0.20302.1\lib\net40\System.Net.Http.dll</HintPath>
+ </Reference>
+ <Reference Include="System.Net.Http.WebRequest">
+ <HintPath>..\..\packages\Microsoft.Net.Http.2.0.20302.1\lib\net40\System.Net.Http.WebRequest.dll</HintPath>
+ </Reference>
+ <Reference Include="System.Runtime.Serialization" />
+ <Reference Include="xunit, Version=1.9.0.1566, Culture=neutral, PublicKeyToken=8d05b1bb7a6fdb6c, processorArchitecture=MSIL">
+ <HintPath>..\..\packages\xunit.1.9.0.1566\lib\xunit.dll</HintPath>
+ </Reference>
+ <Reference Include="xunit.extensions">
+ <HintPath>..\..\packages\xunit.extensions.1.9.0.1566\lib\xunit.extensions.dll</HintPath>
+ </Reference>
+ </ItemGroup>
+ <ItemGroup>
+ <Compile Include="JsonNetSerializationTest.cs" />
+ <Compile Include="Properties\AssemblyInfo.cs" />
+ <Compile Include="FormUrlEncodedFromContentTests.cs" />
+ <Compile Include="FormUrlEncodedFromUriQueryTests.cs" />
+ <Compile Include="JsonValueRoundTripComparer.cs" />
+ </ItemGroup>
+ <ItemGroup>
+ <ProjectReference Include="..\..\src\System.Json\System.Json.csproj">
+ <Project>{F0441BE9-BDC0-4629-BE5A-8765FFAA2481}</Project>
+ <Name>System.Json</Name>
+ </ProjectReference>
+ <ProjectReference Include="..\..\src\System.Net.Http.Formatting\System.Net.Http.Formatting.csproj">
+ <Project>{668E9021-CE84-49D9-98FB-DF125A9FCDB0}</Project>
+ <Name>System.Net.Http.Formatting</Name>
+ </ProjectReference>
+ <ProjectReference Include="..\Microsoft.TestCommon\Microsoft.TestCommon.csproj">
+ <Project>{FCCC4CB7-BAF7-4A57-9F89-E5766FE536C0}</Project>
+ <Name>Microsoft.TestCommon</Name>
+ </ProjectReference>
+ <ProjectReference Include="..\System.Net.Http.Formatting.Test.Unit\System.Net.Http.Formatting.Test.Unit.csproj">
+ <Project>{7AF77741-9158-4D5F-8782-8F21FADF025F}</Project>
+ <Name>System.Net.Http.Formatting.Test.Unit</Name>
+ </ProjectReference>
+ <ProjectReference Include="..\System.Json.Test.Integration\System.Json.Test.Integration.csproj">
+ <Project>{A7B1264E-BCE5-42A8-8B5E-001A5360B128}</Project>
+ <Name>System.Json.Test.Integration</Name>
+ </ProjectReference>
+ </ItemGroup>
+ <ItemGroup>
+ <None Include="packages.config" />
+ </ItemGroup>
+ <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
+</Project> \ No newline at end of file
diff --git a/test/System.Net.Http.Formatting.Test.Integration/packages.config b/test/System.Net.Http.Formatting.Test.Integration/packages.config
new file mode 100644
index 00000000..2ed4df49
--- /dev/null
+++ b/test/System.Net.Http.Formatting.Test.Integration/packages.config
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<packages>
+ <package id="Microsoft.Net.Http" version="2.0.20302.1" />
+ <package id="Newtonsoft.Json" version="4.0.8" />
+ <package id="xunit" version="1.9.0.1566" />
+ <package id="xunit.extensions" version="1.9.0.1566" />
+</packages> \ No newline at end of file
diff --git a/test/System.Net.Http.Formatting.Test.Unit/ContentDispositionHeaderValueExtensionsTests.cs b/test/System.Net.Http.Formatting.Test.Unit/ContentDispositionHeaderValueExtensionsTests.cs
new file mode 100644
index 00000000..c714bf93
--- /dev/null
+++ b/test/System.Net.Http.Formatting.Test.Unit/ContentDispositionHeaderValueExtensionsTests.cs
@@ -0,0 +1,48 @@
+using System.Net.Http.Headers;
+using Microsoft.TestCommon;
+using Xunit;
+using Xunit.Extensions;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Net.Http
+{
+ public class ContentDispositionHeaderValueExtensionsTests
+ {
+ [Fact]
+ public void TypeIsCorrect()
+ {
+ Assert.Type.HasProperties(typeof(ContentDispositionHeaderValueExtensions), TypeAssert.TypeProperties.IsClass | TypeAssert.TypeProperties.IsStatic);
+ }
+
+ [Fact]
+ public void ExtractLocalFileNameThrowsOnNull()
+ {
+ ContentDispositionHeaderValue test = null;
+ Assert.ThrowsArgumentNull(() => ContentDispositionHeaderValueExtensions.ExtractLocalFileName(test), "contentDisposition");
+ }
+
+ [Theory]
+ [TestDataSet(typeof(CommonUnitTestDataSets), "NonNullEmptyStrings")]
+ public void ExtractLocalFileNameThrowsOnQuotedEmpty(string empty)
+ {
+ Assert.ThrowsArgument(
+ () =>
+ {
+ ContentDispositionHeaderValue contentDisposition = null;
+ ContentDispositionHeaderValue.TryParse(String.Format("formdata; filename=\"{0}\"", empty), out contentDisposition);
+ Assert.NotNull(contentDisposition.FileName);
+ ContentDispositionHeaderValueExtensions.ExtractLocalFileName(contentDisposition);
+ }, "contentDisposition");
+ }
+
+ [Fact]
+ public void ExtractLocalFileNamePicksFileNameStarOverFilename()
+ {
+ // ExtractLocalFileName picks filename* over filename.
+ ContentDispositionHeaderValue contentDisposition = null;
+ ContentDispositionHeaderValue.TryParse("formdata; filename=\"aaa\"; filename*=utf-8''%e2BBB", out contentDisposition);
+ string localFilename = ContentDispositionHeaderValueExtensions.ExtractLocalFileName(contentDisposition);
+ Assert.Equal("�BBB", localFilename);
+ }
+ }
+}
diff --git a/test/System.Net.Http.Formatting.Test.Unit/DataSets/HttpUnitTestDataSets.cs b/test/System.Net.Http.Formatting.Test.Unit/DataSets/HttpUnitTestDataSets.cs
new file mode 100644
index 00000000..ed97defa
--- /dev/null
+++ b/test/System.Net.Http.Formatting.Test.Unit/DataSets/HttpUnitTestDataSets.cs
@@ -0,0 +1,103 @@
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Net.Http.Formatting.DataSets.Types;
+using System.Net.Http.Headers;
+using Microsoft.TestCommon;
+
+namespace System.Net.Http.Formatting.DataSets
+{
+ public class HttpUnitTestDataSets
+ {
+ public static TestData<HttpMethod> AllHttpMethods { get { return HttpTestData.AllHttpMethods; } }
+
+ public static TestData<HttpMethod> StandardHttpMethods { get { return HttpTestData.StandardHttpMethods; } }
+
+ public static TestData<HttpMethod> CustomHttpMethods { get { return HttpTestData.CustomHttpMethods; } }
+
+ public static TestData<HttpStatusCode> AllHttpStatusCodes { get { return HttpTestData.AllHttpStatusCodes; } }
+
+ public static TestData<HttpStatusCode> CustomHttpStatusCodes { get { return HttpTestData.CustomHttpStatusCodes; } }
+
+ public static ReadOnlyCollection<TestData> ConvertablePrimitiveValueTypes { get { return HttpTestData.ConvertablePrimitiveValueTypes; } }
+
+ public static ReadOnlyCollection<TestData> ConvertableEnumTypes { get { return HttpTestData.ConvertableEnumTypes; } }
+
+ public static ReadOnlyCollection<TestData> ConvertableValueTypes { get { return HttpTestData.ConvertableValueTypes; } }
+
+ public static TestData<MediaTypeHeaderValue> StandardJsonMediaTypes { get { return HttpTestData.StandardJsonMediaTypes; } }
+
+ public static TestData<MediaTypeHeaderValue> StandardXmlMediaTypes { get { return HttpTestData.StandardXmlMediaTypes; } }
+
+ public static TestData<MediaTypeHeaderValue> StandardODataMediaTypes { get { return HttpTestData.StandardODataMediaTypes; } }
+
+ public static TestData<MediaTypeHeaderValue> StandardFormUrlEncodedMediaTypes { get { return HttpTestData.StandardFormUrlEncodedMediaTypes; } }
+
+ public static TestData<MediaTypeWithQualityHeaderValue> StandardMediaTypesWithQuality { get { return HttpTestData.StandardMediaTypesWithQuality; } }
+
+ public static TestData<string> StandardJsonMediaTypeStrings { get { return HttpTestData.StandardXmlMediaTypeStrings; } }
+
+ public static TestData<string> StandardXmlMediaTypeStrings { get { return HttpTestData.StandardXmlMediaTypeStrings; } }
+
+ public static TestData<string> LegalMediaTypeStrings { get { return HttpTestData.LegalMediaTypeStrings; } }
+
+ public static TestData<string> IllegalMediaTypeStrings { get { return HttpTestData.IllegalMediaTypeStrings; } }
+
+ public static TestData<MediaTypeHeaderValue> LegalMediaTypeHeaderValues { get { return HttpTestData.LegalMediaTypeHeaderValues; } }
+
+ public static TestData<HttpContent> StandardHttpContents { get { return HttpTestData.StandardHttpContents; } }
+
+ public static TestData<MediaTypeMapping> StandardMediaTypeMappings { get { return HttpTestData.StandardMediaTypeMappings; } }
+
+ public static TestData<QueryStringMapping> QueryStringMappings { get { return HttpTestData.QueryStringMappings; } }
+
+ public static TestData<UriPathExtensionMapping> UriPathExtensionMappings { get { return HttpTestData.UriPathExtensionMappings; } }
+
+ public static TestData<MediaRangeMapping> MediaRangeMappings { get { return HttpTestData.MediaRangeMappings; } }
+
+ public static TestData<string> LegalUriPathExtensions { get { return HttpTestData.LegalUriPathExtensions; } }
+
+ public static TestData<string> LegalQueryStringParameterNames { get { return HttpTestData.LegalQueryStringParameterNames; } }
+
+ public static TestData<string> LegalQueryStringParameterValues { get { return HttpTestData.LegalQueryStringParameterValues; } }
+
+ public static TestData<string> LegalHttpHeaderNames { get { return HttpTestData.LegalHttpHeaderNames; } }
+
+ public static TestData<string> LegalHttpHeaderValues { get { return HttpTestData.LegalHttpHeaderValues; } }
+
+ public static TestData<string> LegalMediaRangeStrings { get { return HttpTestData.LegalMediaRangeStrings; } }
+
+ public static TestData<MediaTypeHeaderValue> LegalMediaRangeValues { get { return HttpTestData.LegalMediaRangeValues; } }
+
+ public static TestData<MediaTypeWithQualityHeaderValue> MediaRangeValuesWithQuality { get { return HttpTestData.MediaRangeValuesWithQuality; } }
+
+ public static TestData<string> IllegalMediaRangeStrings { get { return HttpTestData.IllegalMediaRangeStrings; } }
+
+ public static TestData<MediaTypeHeaderValue> IllegalMediaRangeValues { get { return HttpTestData.IllegalMediaRangeValues; } }
+
+ public static TestData<MediaTypeFormatter> StandardFormatters { get { return HttpTestData.StandardFormatters; } }
+
+ public static TestData<Type> StandardFormatterTypes { get { return HttpTestData.StandardFormatterTypes; } }
+
+ public static TestData<MediaTypeFormatter> DerivedFormatters { get { return HttpTestData.DerivedFormatters; } }
+
+ public static TestData<IEnumerable<MediaTypeFormatter>> AllFormatterCollections { get { return HttpTestData.AllFormatterCollections; } }
+
+ public static TestData<string> LegalHttpAddresses { get { return HttpTestData.LegalHttpAddresses; } }
+
+ public static TestData<string> AddressesWithIllegalSchemes { get { return HttpTestData.AddressesWithIllegalSchemes; } }
+
+ public static TestData<HttpRequestMessage> NullContentHttpRequestMessages { get { return HttpTestData.NullContentHttpRequestMessages; } }
+
+ public static ReadOnlyCollection<TestData> RepresentativeValueAndRefTypeTestDataCollection { get { return HttpTestData.RepresentativeValueAndRefTypeTestDataCollection; } }
+
+ public static TestData<string> LegalHttpParameterNames { get { return HttpTestData.LegalHttpParameterNames; } }
+
+ public static TestData<Type> LegalHttpParameterTypes { get { return HttpTestData.LegalHttpParameterTypes; } }
+
+ public static RefTypeTestData<Uri> Uris { get { return HttpTestData.UriTestData; } }
+
+ public static RefTypeTestData<string> UriStrings { get { return HttpTestData.UriTestDataStrings; } }
+
+ public static RefTypeTestData<WcfPocoType> PocoTypesWithNull { get { return HttpTestData.WcfPocoTypeTestDataWithNull; } }
+ }
+}
diff --git a/test/System.Net.Http.Formatting.Test.Unit/DataSets/Types/DataContractEnum.cs b/test/System.Net.Http.Formatting.Test.Unit/DataSets/Types/DataContractEnum.cs
new file mode 100644
index 00000000..853544d5
--- /dev/null
+++ b/test/System.Net.Http.Formatting.Test.Unit/DataSets/Types/DataContractEnum.cs
@@ -0,0 +1,16 @@
+using System.Runtime.Serialization;
+
+namespace System.Net.Http.Formatting.DataSets.Types
+{
+ [DataContract]
+ public enum DataContractEnum
+ {
+ [EnumMember]
+ First,
+
+ [EnumMember]
+ Second,
+
+ Third
+ }
+}
diff --git a/test/System.Net.Http.Formatting.Test.Unit/DataSets/Types/DataContractType.cs b/test/System.Net.Http.Formatting.Test.Unit/DataSets/Types/DataContractType.cs
new file mode 100644
index 00000000..39dbdd7c
--- /dev/null
+++ b/test/System.Net.Http.Formatting.Test.Unit/DataSets/Types/DataContractType.cs
@@ -0,0 +1,75 @@
+using System.Collections.Generic;
+using System.Runtime.Serialization;
+using System.Xml.Serialization;
+using Microsoft.TestCommon.Types;
+
+namespace System.Net.Http.Formatting.DataSets.Types
+{
+ [DataContract]
+ [KnownType(typeof(DerivedDataContractType))]
+ [XmlInclude(typeof(DerivedDataContractType))]
+ public class DataContractType : INameAndIdContainer
+ {
+ private int id;
+ private string name;
+
+ public DataContractType()
+ {
+ }
+
+ public DataContractType(int id, string name)
+ {
+ this.id = id;
+ this.name = name;
+ }
+
+ [DataMember]
+ public int Id
+ {
+ get
+ {
+ return this.id;
+ }
+
+ set
+ {
+ this.IdSet = true;
+ this.id = value;
+ }
+ }
+
+ [DataMember]
+ public string Name
+ {
+ get
+ {
+ return this.name;
+ }
+
+ set
+ {
+ this.NameSet = true;
+ this.name = value;
+ }
+ }
+
+ [XmlIgnore]
+ public bool IdSet { get; private set; }
+
+ [XmlIgnore]
+ public bool NameSet { get; private set; }
+
+ public static IEnumerable<DataContractType> GetTestData()
+ {
+ return new DataContractType[] { new DataContractType(), new DataContractType(1, "SomeName") };
+ }
+
+ public static IEnumerable<DerivedDataContractType> GetDerivedTypeTestData()
+ {
+ return new DerivedDataContractType[] {
+ new DerivedDataContractType(),
+ new DerivedDataContractType(1, "SomeName", null),
+ new DerivedDataContractType(1, "SomeName", new WcfPocoType(2, "SomeOtherName"))};
+ }
+ }
+}
diff --git a/test/System.Net.Http.Formatting.Test.Unit/DataSets/Types/DerivedDataContractType.cs b/test/System.Net.Http.Formatting.Test.Unit/DataSets/Types/DerivedDataContractType.cs
new file mode 100644
index 00000000..0ca4857a
--- /dev/null
+++ b/test/System.Net.Http.Formatting.Test.Unit/DataSets/Types/DerivedDataContractType.cs
@@ -0,0 +1,59 @@
+using System.Collections.Generic;
+using System.Runtime.Serialization;
+using System.Xml.Serialization;
+
+namespace System.Net.Http.Formatting.DataSets.Types
+{
+ [DataContract]
+ [KnownType(typeof(DerivedWcfPocoType))]
+ [KnownType(typeof(DerivedDataContractType))]
+ [XmlInclude(typeof(DerivedWcfPocoType))]
+ [XmlInclude(typeof(DerivedDataContractType))]
+ public class DerivedDataContractType : DataContractType
+ {
+ private WcfPocoType reference;
+
+ public DerivedDataContractType()
+ {
+ }
+
+ public DerivedDataContractType(int id, string name, WcfPocoType reference)
+ : base(id, name)
+ {
+ this.reference = reference;
+ }
+
+ [DataMember]
+ public WcfPocoType Reference
+ {
+ get
+ {
+ return this.reference;
+ }
+
+ set
+ {
+ this.ReferenceSet = true;
+ this.reference = value;
+ }
+ }
+
+ [XmlIgnore]
+ public bool ReferenceSet { get; private set; }
+
+ public static new IEnumerable<DerivedDataContractType> GetTestData()
+ {
+ return new DerivedDataContractType[] {
+ new DerivedDataContractType(),
+ new DerivedDataContractType(1, "SomeName", new WcfPocoType(2, "SomeOtherName")) };
+ }
+
+ public static IEnumerable<DerivedDataContractType> GetKnownTypeTestData()
+ {
+ return new DerivedDataContractType[] {
+ new DerivedDataContractType(),
+ new DerivedDataContractType(1, "SomeName", null),
+ new DerivedDataContractType(1, "SomeName", new DerivedWcfPocoType(2, "SomeOtherName", null))};
+ }
+ }
+}
diff --git a/test/System.Net.Http.Formatting.Test.Unit/DataSets/Types/DerivedFormUrlEncodedMediaTypeFormatter.cs b/test/System.Net.Http.Formatting.Test.Unit/DataSets/Types/DerivedFormUrlEncodedMediaTypeFormatter.cs
new file mode 100644
index 00000000..2b7f80b8
--- /dev/null
+++ b/test/System.Net.Http.Formatting.Test.Unit/DataSets/Types/DerivedFormUrlEncodedMediaTypeFormatter.cs
@@ -0,0 +1,7 @@
+
+namespace System.Net.Http.Formatting.DataSets.Types
+{
+ public class DerivedFormUrlEncodedMediaTypeFormatter : FormUrlEncodedMediaTypeFormatter
+ {
+ }
+}
diff --git a/test/System.Net.Http.Formatting.Test.Unit/DataSets/Types/DerivedJsonMediaTypeFormatter.cs b/test/System.Net.Http.Formatting.Test.Unit/DataSets/Types/DerivedJsonMediaTypeFormatter.cs
new file mode 100644
index 00000000..846635a9
--- /dev/null
+++ b/test/System.Net.Http.Formatting.Test.Unit/DataSets/Types/DerivedJsonMediaTypeFormatter.cs
@@ -0,0 +1,7 @@
+
+namespace System.Net.Http.Formatting.DataSets.Types
+{
+ public class DerivedJsonMediaTypeFormatter : JsonMediaTypeFormatter
+ {
+ }
+}
diff --git a/test/System.Net.Http.Formatting.Test.Unit/DataSets/Types/DerivedWcfPocoType.cs b/test/System.Net.Http.Formatting.Test.Unit/DataSets/Types/DerivedWcfPocoType.cs
new file mode 100644
index 00000000..602d2cf2
--- /dev/null
+++ b/test/System.Net.Http.Formatting.Test.Unit/DataSets/Types/DerivedWcfPocoType.cs
@@ -0,0 +1,38 @@
+using System.Runtime.Serialization;
+using System.Xml.Serialization;
+
+namespace System.Net.Http.Formatting.DataSets.Types
+{
+ public class DerivedWcfPocoType : WcfPocoType
+ {
+ private WcfPocoType reference;
+
+ public DerivedWcfPocoType()
+ {
+ }
+
+ public DerivedWcfPocoType(int id, string name, WcfPocoType reference)
+ : base(id, name)
+ {
+ this.reference = reference;
+ }
+
+ public WcfPocoType Reference
+ {
+ get
+ {
+ return this.reference;
+ }
+
+ set
+ {
+ this.ReferenceSet = true;
+ this.reference = value;
+ }
+ }
+
+ [IgnoreDataMember]
+ [XmlIgnore]
+ public bool ReferenceSet { get; private set; }
+ }
+}
diff --git a/test/System.Net.Http.Formatting.Test.Unit/DataSets/Types/DerivedXmlMediaTypeFormatter.cs b/test/System.Net.Http.Formatting.Test.Unit/DataSets/Types/DerivedXmlMediaTypeFormatter.cs
new file mode 100644
index 00000000..acbb4b7f
--- /dev/null
+++ b/test/System.Net.Http.Formatting.Test.Unit/DataSets/Types/DerivedXmlMediaTypeFormatter.cs
@@ -0,0 +1,7 @@
+
+namespace System.Net.Http.Formatting.DataSets.Types
+{
+ public class DerivedXmlMediaTypeFormatter : XmlMediaTypeFormatter
+ {
+ }
+}
diff --git a/test/System.Net.Http.Formatting.Test.Unit/DataSets/Types/DerivedXmlSerializableType.cs b/test/System.Net.Http.Formatting.Test.Unit/DataSets/Types/DerivedXmlSerializableType.cs
new file mode 100644
index 00000000..a8847770
--- /dev/null
+++ b/test/System.Net.Http.Formatting.Test.Unit/DataSets/Types/DerivedXmlSerializableType.cs
@@ -0,0 +1,56 @@
+using System.Collections.Generic;
+using System.Runtime.Serialization;
+using System.Xml.Serialization;
+
+namespace System.Net.Http.Formatting.DataSets.Types
+{
+ [KnownType(typeof(DerivedWcfPocoType))]
+ [XmlInclude(typeof(DerivedWcfPocoType))]
+ public class DerivedXmlSerializableType : XmlSerializableType, INotJsonSerializable
+ {
+ private WcfPocoType reference;
+
+ public DerivedXmlSerializableType()
+ {
+ }
+
+ public DerivedXmlSerializableType(int id, string name, WcfPocoType reference)
+ : base(id, name)
+ {
+ this.reference = reference;
+ }
+
+ [XmlElement]
+ public WcfPocoType Reference
+ {
+ get
+ {
+ return this.reference;
+ }
+
+ set
+ {
+ this.ReferenceSet = true;
+ this.reference = value;
+ }
+ }
+
+ [XmlIgnore]
+ public bool ReferenceSet { get; private set; }
+
+ public static new IEnumerable<DerivedXmlSerializableType> GetTestData()
+ {
+ return new DerivedXmlSerializableType[] {
+ new DerivedXmlSerializableType(),
+ new DerivedXmlSerializableType(1, "SomeName", new WcfPocoType(2, "SomeOtherName")) };
+ }
+
+ public static IEnumerable<DerivedXmlSerializableType> GetKnownTypeTestData()
+ {
+ return new DerivedXmlSerializableType[] {
+ new DerivedXmlSerializableType(),
+ new DerivedXmlSerializableType(1, "SomeName", null),
+ new DerivedXmlSerializableType(1, "SomeName", new DerivedWcfPocoType(2, "SomeOtherName", null))};
+ }
+ }
+}
diff --git a/test/System.Net.Http.Formatting.Test.Unit/DataSets/Types/HttpTestData.cs b/test/System.Net.Http.Formatting.Test.Unit/DataSets/Types/HttpTestData.cs
new file mode 100644
index 00000000..9a7c4776
--- /dev/null
+++ b/test/System.Net.Http.Formatting.Test.Unit/DataSets/Types/HttpTestData.cs
@@ -0,0 +1,427 @@
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.IO;
+using System.Linq;
+using System.Net.Http.Headers;
+using Microsoft.TestCommon;
+
+namespace System.Net.Http.Formatting.DataSets.Types
+{
+ public static class HttpTestData
+ {
+ public static readonly TestData<HttpMethod> AllHttpMethods = new RefTypeTestData<HttpMethod>(() =>
+ StandardHttpMethods.Concat(CustomHttpMethods).ToList());
+
+ public static readonly TestData<HttpMethod> StandardHttpMethods = new RefTypeTestData<HttpMethod>(() => new List<HttpMethod>()
+ {
+ HttpMethod.Head,
+ HttpMethod.Get,
+ HttpMethod.Post,
+ HttpMethod.Put,
+ HttpMethod.Delete,
+ HttpMethod.Options,
+ HttpMethod.Trace,
+ });
+
+ public static readonly TestData<HttpMethod> CustomHttpMethods = new RefTypeTestData<HttpMethod>(() => new List<HttpMethod>()
+ {
+ new HttpMethod("Custom")
+ });
+
+ public static readonly TestData<HttpStatusCode> AllHttpStatusCodes = new ValueTypeTestData<HttpStatusCode>(new HttpStatusCode[]
+ {
+ HttpStatusCode.Accepted,
+ HttpStatusCode.Ambiguous,
+ HttpStatusCode.BadGateway,
+ HttpStatusCode.BadRequest,
+ HttpStatusCode.Conflict,
+ HttpStatusCode.Continue,
+ HttpStatusCode.Created,
+ HttpStatusCode.ExpectationFailed,
+ HttpStatusCode.Forbidden,
+ HttpStatusCode.Found,
+ HttpStatusCode.GatewayTimeout,
+ HttpStatusCode.Gone,
+ HttpStatusCode.HttpVersionNotSupported,
+ HttpStatusCode.InternalServerError,
+ HttpStatusCode.LengthRequired,
+ HttpStatusCode.MethodNotAllowed,
+ HttpStatusCode.Moved,
+ HttpStatusCode.MovedPermanently,
+ HttpStatusCode.MultipleChoices,
+ HttpStatusCode.NoContent,
+ HttpStatusCode.NonAuthoritativeInformation,
+ HttpStatusCode.NotAcceptable,
+ HttpStatusCode.NotFound,
+ HttpStatusCode.NotImplemented,
+ HttpStatusCode.NotModified,
+ HttpStatusCode.OK,
+ HttpStatusCode.PartialContent,
+ HttpStatusCode.PaymentRequired,
+ HttpStatusCode.PreconditionFailed,
+ HttpStatusCode.ProxyAuthenticationRequired,
+ HttpStatusCode.Redirect,
+ HttpStatusCode.RedirectKeepVerb,
+ HttpStatusCode.RedirectMethod,
+ HttpStatusCode.RequestedRangeNotSatisfiable,
+ HttpStatusCode.RequestEntityTooLarge,
+ HttpStatusCode.RequestTimeout,
+ HttpStatusCode.RequestUriTooLong,
+ HttpStatusCode.ResetContent,
+ HttpStatusCode.SeeOther,
+ HttpStatusCode.ServiceUnavailable,
+ HttpStatusCode.SwitchingProtocols,
+ HttpStatusCode.TemporaryRedirect,
+ HttpStatusCode.Unauthorized,
+ HttpStatusCode.UnsupportedMediaType,
+ HttpStatusCode.Unused,
+ HttpStatusCode.UseProxy
+ });
+
+ public static readonly TestData<HttpStatusCode> CustomHttpStatusCodes = new ValueTypeTestData<HttpStatusCode>(new HttpStatusCode[]
+ {
+ (HttpStatusCode)199,
+ (HttpStatusCode)299,
+ (HttpStatusCode)399,
+ (HttpStatusCode)499,
+ (HttpStatusCode)599,
+ (HttpStatusCode)699,
+ (HttpStatusCode)799,
+ (HttpStatusCode)899,
+ (HttpStatusCode)999,
+ });
+
+ public static readonly ReadOnlyCollection<TestData> ConvertablePrimitiveValueTypes = new ReadOnlyCollection<TestData>(new TestData[] {
+ TestData.CharTestData,
+ TestData.IntTestData,
+ TestData.UintTestData,
+ TestData.ShortTestData,
+ TestData.UshortTestData,
+ TestData.LongTestData,
+ TestData.UlongTestData,
+ TestData.ByteTestData,
+ TestData.SByteTestData,
+ TestData.BoolTestData,
+ TestData.DoubleTestData,
+ TestData.FloatTestData,
+ TestData.DecimalTestData,
+ TestData.TimeSpanTestData,
+ TestData.GuidTestData,
+ TestData.DateTimeTestData,
+ TestData.DateTimeOffsetTestData});
+
+ public static readonly ReadOnlyCollection<TestData> ConvertableEnumTypes = new ReadOnlyCollection<TestData>(new TestData[] {
+ TestData.SimpleEnumTestData,
+ TestData.LongEnumTestData,
+ TestData.FlagsEnumTestData,
+ DataContractEnumTestData});
+
+ public static readonly ReadOnlyCollection<TestData> ConvertableValueTypes = new ReadOnlyCollection<TestData>(
+ ConvertablePrimitiveValueTypes.Concat(ConvertableEnumTypes).ToList());
+
+ public static readonly TestData<MediaTypeHeaderValue> StandardJsonMediaTypes = new RefTypeTestData<MediaTypeHeaderValue>(() => new List<MediaTypeHeaderValue>()
+ {
+ new MediaTypeHeaderValue("application/json"),
+ new MediaTypeHeaderValue("text/json")
+ });
+
+ public static readonly TestData<MediaTypeHeaderValue> StandardXmlMediaTypes = new RefTypeTestData<MediaTypeHeaderValue>(() => new List<MediaTypeHeaderValue>()
+ {
+ new MediaTypeHeaderValue("application/xml"),
+ new MediaTypeHeaderValue("text/xml")
+ });
+
+ public static readonly TestData<MediaTypeHeaderValue> StandardODataMediaTypes = new RefTypeTestData<MediaTypeHeaderValue>(() => new List<MediaTypeHeaderValue>()
+ {
+ new MediaTypeHeaderValue("application/atom+xml"),
+ new MediaTypeHeaderValue("application/json"),
+ });
+
+ public static readonly TestData<MediaTypeHeaderValue> StandardFormUrlEncodedMediaTypes = new RefTypeTestData<MediaTypeHeaderValue>(() => new List<MediaTypeHeaderValue>()
+ {
+ new MediaTypeHeaderValue("application/x-www-form-urlencoded")
+ });
+
+ public static readonly TestData<string> StandardJsonMediaTypeStrings = new RefTypeTestData<string>(() => new List<string>()
+ {
+ "application/json",
+ "text/json"
+ });
+
+ public static readonly TestData<string> StandardXmlMediaTypeStrings = new RefTypeTestData<string>(() => new List<string>()
+ {
+ "application/xml",
+ "text/xml"
+ });
+
+ public static readonly TestData<string> LegalMediaTypeStrings = new RefTypeTestData<string>(() =>
+ StandardXmlMediaTypeStrings.Concat(StandardJsonMediaTypeStrings).ToList());
+
+
+ // Illegal media type strings. These will cause the MediaTypeHeaderValue ctor to throw FormatException
+ public static readonly TestData<string> IllegalMediaTypeStrings = new RefTypeTestData<string>(() => new List<string>()
+ {
+ "\0",
+ "9\r\n"
+ });
+
+ //// TODO: complete this list
+ // Legal MediaTypeHeaderValues
+ public static readonly TestData<MediaTypeHeaderValue> LegalMediaTypeHeaderValues = new RefTypeTestData<MediaTypeHeaderValue>(
+ () => LegalMediaTypeStrings.Select<string, MediaTypeHeaderValue>((mediaType) => new MediaTypeHeaderValue(mediaType)).ToList());
+
+ public static readonly TestData<MediaTypeWithQualityHeaderValue> StandardMediaTypesWithQuality = new RefTypeTestData<MediaTypeWithQualityHeaderValue>(() => new List<MediaTypeWithQualityHeaderValue>()
+ {
+ new MediaTypeWithQualityHeaderValue("application/json", .1) { CharSet="utf-8"},
+ new MediaTypeWithQualityHeaderValue("text/json", .2) { CharSet="utf-8"},
+ new MediaTypeWithQualityHeaderValue("application/xml", .3) { CharSet="utf-8"},
+ new MediaTypeWithQualityHeaderValue("text/xml", .4) { CharSet="utf-8"},
+ new MediaTypeWithQualityHeaderValue("application/atom+xml", .5) { CharSet="utf-8"},
+ });
+
+ public static readonly TestData<HttpContent> StandardHttpContents = new RefTypeTestData<HttpContent>(() => new List<HttpContent>()
+ {
+ new ByteArrayContent(new byte[0]),
+ new FormUrlEncodedContent(new KeyValuePair<string, string>[0]),
+ new MultipartContent(),
+ new StringContent(""),
+ new StreamContent(new MemoryStream())
+ });
+
+ //// TODO: make this list compose from other data?
+ // Collection of legal instances of all standard MediaTypeMapping types
+ public static readonly TestData<MediaTypeMapping> StandardMediaTypeMappings = new RefTypeTestData<MediaTypeMapping>(() =>
+ QueryStringMappings.Cast<MediaTypeMapping>().Concat(
+ UriPathExtensionMappings.Cast<MediaTypeMapping>().Concat(
+ MediaRangeMappings.Cast<MediaTypeMapping>())).ToList()
+ );
+
+ public static readonly TestData<QueryStringMapping> QueryStringMappings = new RefTypeTestData<QueryStringMapping>(() => new List<QueryStringMapping>()
+ {
+ new QueryStringMapping("format", "json", new MediaTypeHeaderValue("application/json"))
+ });
+
+ public static readonly TestData<UriPathExtensionMapping> UriPathExtensionMappings = new RefTypeTestData<UriPathExtensionMapping>(() => new List<UriPathExtensionMapping>()
+ {
+ new UriPathExtensionMapping("xml", new MediaTypeHeaderValue("application/xml")),
+ new UriPathExtensionMapping("json", new MediaTypeHeaderValue("application/json")),
+ });
+
+ public static readonly TestData<MediaRangeMapping> MediaRangeMappings = new RefTypeTestData<MediaRangeMapping>(() => new List<MediaRangeMapping>()
+ {
+ new MediaRangeMapping(new MediaTypeHeaderValue("application/*"), new MediaTypeHeaderValue("application/xml"))
+ });
+
+ public static readonly TestData<string> LegalUriPathExtensions = new RefTypeTestData<string>(() => new List<string>()
+ {
+ "xml",
+ "json"
+ });
+
+ public static readonly TestData<string> LegalQueryStringParameterNames = new RefTypeTestData<string>(() => new List<string>()
+ {
+ "format",
+ "fmt"
+ });
+
+ public static readonly TestData<string> LegalHttpHeaderNames = new RefTypeTestData<string>(() => new List<string>()
+ {
+ "x-requested-with",
+ "some-random-name"
+ });
+
+ public static readonly TestData<string> LegalHttpHeaderValues = new RefTypeTestData<string>(() => new List<string>()
+ {
+ "1",
+ "XMLHttpRequest",
+ "\"quoted-string\""
+ });
+
+ public static readonly TestData<string> LegalQueryStringParameterValues = new RefTypeTestData<string>(() => new List<string>()
+ {
+ "xml",
+ "json"
+ });
+
+ public static readonly TestData<string> LegalMediaRangeStrings = new RefTypeTestData<string>(() => new List<string>()
+ {
+ "application/*",
+ "text/*"
+ });
+
+ public static readonly TestData<MediaTypeHeaderValue> LegalMediaRangeValues = new RefTypeTestData<MediaTypeHeaderValue>(() =>
+ LegalMediaRangeStrings.Select<string, MediaTypeHeaderValue>((s) => new MediaTypeHeaderValue(s)).ToList()
+ );
+
+ public static readonly TestData<MediaTypeWithQualityHeaderValue> MediaRangeValuesWithQuality = new RefTypeTestData<MediaTypeWithQualityHeaderValue>(() => new List<MediaTypeWithQualityHeaderValue>()
+ {
+ new MediaTypeWithQualityHeaderValue("application/*", .1),
+ new MediaTypeWithQualityHeaderValue("text/*", .2),
+ });
+
+ public static readonly TestData<string> IllegalMediaRangeStrings = new RefTypeTestData<string>(() => new List<string>()
+ {
+ "application/xml",
+ "text/xml"
+ });
+
+ public static readonly TestData<MediaTypeHeaderValue> IllegalMediaRangeValues = new RefTypeTestData<MediaTypeHeaderValue>(() =>
+ IllegalMediaRangeStrings.Select<string, MediaTypeHeaderValue>((s) => new MediaTypeHeaderValue(s)).ToList()
+ );
+
+ public static readonly TestData<MediaTypeFormatter> StandardFormatters = new RefTypeTestData<MediaTypeFormatter>(() => new List<MediaTypeFormatter>()
+ {
+ new XmlMediaTypeFormatter(),
+ new JsonMediaTypeFormatter(),
+ new FormUrlEncodedMediaTypeFormatter()
+ });
+
+ public static readonly TestData<Type> StandardFormatterTypes = new RefTypeTestData<Type>(() =>
+ StandardFormatters.Select<MediaTypeFormatter, Type>((m) => m.GetType()));
+
+ public static readonly TestData<MediaTypeFormatter> DerivedFormatters = new RefTypeTestData<MediaTypeFormatter>(() => new List<MediaTypeFormatter>()
+ {
+ new DerivedXmlMediaTypeFormatter(),
+ new DerivedJsonMediaTypeFormatter(),
+ new DerivedFormUrlEncodedMediaTypeFormatter(),
+ });
+
+ public static readonly TestData<IEnumerable<MediaTypeFormatter>> AllFormatterCollections =
+ new RefTypeTestData<IEnumerable<MediaTypeFormatter>>(() => new List<IEnumerable<MediaTypeFormatter>>()
+ {
+ new MediaTypeFormatter[0],
+ StandardFormatters,
+ DerivedFormatters,
+ });
+
+ public static readonly TestData<string> LegalHttpAddresses = new RefTypeTestData<string>(() => new List<string>()
+ {
+ "http://somehost",
+ "https://somehost",
+ });
+
+ public static readonly TestData<string> AddressesWithIllegalSchemes = new RefTypeTestData<string>(() => new List<string>()
+ {
+ "net.tcp://somehost",
+ "file://somehost",
+ "net.pipe://somehost",
+ "mailto:somehost",
+ "ftp://somehost",
+ "news://somehost",
+ "ws://somehost",
+ "abc://somehost"
+ });
+
+ /// <summary>
+ /// A read-only collection of representative values and reference type test data.
+ /// Uses where exhaustive coverage is not required. It includes null values.
+ /// </summary>
+ public static readonly ReadOnlyCollection<TestData> RepresentativeValueAndRefTypeTestDataCollection = new ReadOnlyCollection<TestData>(new TestData[] {
+ TestData.ByteTestData,
+ TestData.IntTestData,
+ TestData.BoolTestData,
+ TestData.SimpleEnumTestData,
+ TestData.StringTestData,
+ TestData.DateTimeTestData,
+ TestData.DateTimeOffsetTestData,
+ TestData.TimeSpanTestData,
+ WcfPocoTypeTestDataWithNull
+ });
+
+ public static readonly TestData<HttpRequestMessage> NullContentHttpRequestMessages = new RefTypeTestData<HttpRequestMessage>(() => new List<HttpRequestMessage>()
+ {
+ new HttpRequestMessage() { Content = null },
+ });
+
+ public static readonly TestData<string> LegalHttpParameterNames = new RefTypeTestData<string>(() => new List<string>()
+ {
+ "文",
+ "A",
+ "a",
+ "b",
+ " a",
+ "arg1",
+ "arg2",
+ "1",
+ "@",
+ "!"
+
+ });
+
+ public static readonly TestData<Type> LegalHttpParameterTypes = new RefTypeTestData<Type>(() => new List<Type>()
+ {
+ typeof(string),
+ typeof(byte[]),
+ typeof(byte[][]),
+ typeof(byte[][]),
+ typeof(char),
+ typeof(DateTime),
+ typeof(decimal),
+ typeof(double),
+ typeof(Guid),
+ typeof(Int16),
+ typeof(Int32),
+ typeof(object),
+ typeof(sbyte),
+ typeof(Single),
+ typeof(TimeSpan),
+ typeof(UInt16),
+ typeof(UInt32),
+ typeof(UInt64),
+ typeof(Uri),
+ typeof(Enum),
+ typeof(Collection<object>),
+ typeof(IList<object>),
+ typeof(System.Runtime.Serialization.ISerializable),
+ typeof(System.Data.DataSet),
+ typeof(System.Xml.Serialization.IXmlSerializable),
+ typeof(Nullable),
+ typeof(Nullable<DateTime>),
+ typeof(Stream),
+ typeof(HttpRequestMessage),
+ typeof(HttpResponseMessage),
+ typeof(ObjectContent),
+ typeof(ObjectContent<object>),
+ typeof(HttpContent),
+ typeof(Delegate),
+ typeof(Action),
+ typeof(System.Threading.Tasks.Task<object>),
+ typeof(System.Threading.Tasks.Task),
+ typeof(List<dynamic>)
+ });
+
+ /// <summary>
+ /// Common <see cref="TestData"/> for an <c>enum</c> decorated with a <see cref="DataContractAttribute"/>.
+ /// </summary>
+ public static readonly ValueTypeTestData<DataContractEnum> DataContractEnumTestData = new ValueTypeTestData<DataContractEnum>(
+ DataContractEnum.First,
+ DataContractEnum.Second);
+
+ /// <summary>
+ /// Common <see cref="TestData"/> for the string form of a <see cref="Uri"/>.
+ /// </summary>
+ public static readonly RefTypeTestData<string> UriTestDataStrings = new RefTypeTestData<string>(() => new List<string>(){
+ "http://somehost",
+ "http://somehost:8080",
+ "http://somehost/",
+ "http://somehost:8080/",
+ "http://somehost/somepath",
+ "http://somehost/somepath/",
+ "http://somehost/somepath?somequery=somevalue"});
+
+ /// <summary>
+ /// Common <see cref="TestData"/> for a <see cref="Uri"/>.
+ /// </summary>
+ public static readonly RefTypeTestData<Uri> UriTestData = new RefTypeTestData<Uri>(() =>
+ UriTestDataStrings.Select<string, Uri>((s) => new Uri(s)).ToList());
+
+ /// <summary>
+ /// Common <see cref="TestData"/> for a POCO class type that includes null values
+ /// for both the base class and derived classes.
+ /// </summary>
+ public static readonly RefTypeTestData<WcfPocoType> WcfPocoTypeTestDataWithNull = new RefTypeTestData<WcfPocoType>(
+ WcfPocoType.GetTestDataWithNull,
+ WcfPocoType.GetDerivedTypeTestDataWithNull,
+ null);
+ }
+} \ No newline at end of file
diff --git a/test/System.Net.Http.Formatting.Test.Unit/DataSets/Types/INotJsonSerializable.cs b/test/System.Net.Http.Formatting.Test.Unit/DataSets/Types/INotJsonSerializable.cs
new file mode 100644
index 00000000..61d9e64f
--- /dev/null
+++ b/test/System.Net.Http.Formatting.Test.Unit/DataSets/Types/INotJsonSerializable.cs
@@ -0,0 +1,9 @@
+namespace System.Net.Http.Formatting.DataSets.Types
+{
+ /// <summary>
+ /// Tagging interface to indicate types which we know Json cannot serialize.
+ /// </summary>
+ public interface INotJsonSerializable
+ {
+ }
+}
diff --git a/test/System.Net.Http.Formatting.Test.Unit/DataSets/Types/WcfPocoType.cs b/test/System.Net.Http.Formatting.Test.Unit/DataSets/Types/WcfPocoType.cs
new file mode 100644
index 00000000..2ced7009
--- /dev/null
+++ b/test/System.Net.Http.Formatting.Test.Unit/DataSets/Types/WcfPocoType.cs
@@ -0,0 +1,86 @@
+using System.Collections.Generic;
+using System.Linq;
+using System.Runtime.Serialization;
+using System.Xml.Serialization;
+using Microsoft.TestCommon.Types;
+
+namespace System.Net.Http.Formatting.DataSets.Types
+{
+ [KnownType(typeof(DerivedWcfPocoType))]
+ [XmlInclude(typeof(DerivedWcfPocoType))]
+ public class WcfPocoType : INameAndIdContainer
+ {
+ private int id;
+ private string name;
+
+ public WcfPocoType()
+ {
+ }
+
+ public WcfPocoType(int id, string name)
+ {
+ this.id = id;
+ this.name = name;
+ }
+
+ public int Id
+ {
+ get
+ {
+ return this.id;
+ }
+
+ set
+ {
+ this.IdSet = true;
+ this.id = value;
+ }
+ }
+
+ public string Name
+ {
+ get
+ {
+ return this.name;
+ }
+
+ set
+ {
+ this.NameSet = true;
+ this.name = value;
+ }
+
+ }
+
+ [IgnoreDataMember]
+ [XmlIgnore]
+ public bool IdSet { get; private set; }
+
+ [IgnoreDataMember]
+ [XmlIgnore]
+ public bool NameSet { get; private set; }
+
+ public static IEnumerable<WcfPocoType> GetTestData()
+ {
+ return new WcfPocoType[] { new WcfPocoType(), new WcfPocoType(1, "SomeName") };
+ }
+
+ public static IEnumerable<WcfPocoType> GetTestDataWithNull()
+ {
+ return GetTestData().Concat(new WcfPocoType[] { null });
+ }
+
+ public static IEnumerable<DerivedWcfPocoType> GetDerivedTypeTestData()
+ {
+ return new DerivedWcfPocoType[] {
+ new DerivedWcfPocoType(),
+ new DerivedWcfPocoType(1, "SomeName", null),
+ new DerivedWcfPocoType(1, "SomeName", new WcfPocoType(2, "SomeOtherName"))};
+ }
+
+ public static IEnumerable<DerivedWcfPocoType> GetDerivedTypeTestDataWithNull()
+ {
+ return GetDerivedTypeTestData().Concat(new DerivedWcfPocoType[] { null });
+ }
+ }
+}
diff --git a/test/System.Net.Http.Formatting.Test.Unit/DataSets/Types/XmlSerializableType.cs b/test/System.Net.Http.Formatting.Test.Unit/DataSets/Types/XmlSerializableType.cs
new file mode 100644
index 00000000..bd4218e4
--- /dev/null
+++ b/test/System.Net.Http.Formatting.Test.Unit/DataSets/Types/XmlSerializableType.cs
@@ -0,0 +1,96 @@
+using System.Collections.Generic;
+using System.Runtime.Serialization;
+using System.Xml.Serialization;
+using Microsoft.TestCommon.Types;
+
+namespace System.Net.Http.Formatting.DataSets.Types
+{
+ [KnownType(typeof(DerivedXmlSerializableType))]
+ [XmlInclude(typeof(DerivedXmlSerializableType))]
+ public class XmlSerializableType : INameAndIdContainer
+ {
+ private int id;
+ private string name;
+
+ public XmlSerializableType()
+ {
+ }
+
+ public XmlSerializableType(int id, string name)
+ {
+ this.id = id;
+ this.name = name;
+ }
+
+ [XmlAttribute]
+ public int Id
+ {
+ get
+ {
+ return this.id;
+ }
+
+ set
+ {
+ this.IdSet = true;
+ this.id = value;
+ }
+ }
+
+ [XmlElement]
+ public string Name
+ {
+ get
+ {
+ return this.name;
+ }
+
+ set
+ {
+ this.NameSet = true;
+ this.name = value;
+ }
+
+ }
+
+ [XmlIgnore]
+ public bool IdSet { get; private set; }
+
+ [XmlIgnore]
+ public bool NameSet { get; private set; }
+
+ public override bool Equals(object obj)
+ {
+ if (Object.ReferenceEquals(this, obj))
+ {
+ return true;
+ }
+
+ XmlSerializableType other = obj as XmlSerializableType;
+ if (Object.ReferenceEquals(other, null))
+ {
+ return false;
+ }
+
+ return String.Equals(this.Name, other.Name, StringComparison.Ordinal) && this.Id == other.Id;
+ }
+
+ public override int GetHashCode()
+ {
+ return base.GetHashCode();
+ }
+
+ public static IEnumerable<XmlSerializableType> GetTestData()
+ {
+ return new XmlSerializableType[] { new XmlSerializableType(), new XmlSerializableType(1, "SomeName") };
+ }
+
+ public static IEnumerable<DerivedXmlSerializableType> GetDerivedTypeTestData()
+ {
+ return new DerivedXmlSerializableType[] {
+ new DerivedXmlSerializableType(),
+ new DerivedXmlSerializableType(1, "SomeName", null),
+ new DerivedXmlSerializableType(1, "SomeName", new WcfPocoType(2, "SomeOtherName"))};
+ }
+ }
+}
diff --git a/test/System.Net.Http.Formatting.Test.Unit/Formatting/BufferedMediaTypeFormatterTests.cs b/test/System.Net.Http.Formatting.Test.Unit/Formatting/BufferedMediaTypeFormatterTests.cs
new file mode 100644
index 00000000..3c17839d
--- /dev/null
+++ b/test/System.Net.Http.Formatting.Test.Unit/Formatting/BufferedMediaTypeFormatterTests.cs
@@ -0,0 +1,95 @@
+using System.IO;
+using System.Net.Http.Headers;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Net.Http.Formatting
+{
+ public class BufferedMediaTypeFormatterTests
+ {
+ private BufferedMediaTypeFormatter _formatter = new TestableBufferedMediaTypeFormatter();
+
+ [Fact]
+ public void WriteToStreamAsync_WhenTypeParameterIsNull_ThrowsException()
+ {
+ Assert.ThrowsArgumentNull(
+ () => _formatter.WriteToStreamAsync(null, new object(), new MemoryStream(), null, null), "type");
+ }
+
+ [Fact]
+ public void WriteToStreamAsync_WhenStreamParameterIsNull_ThrowsException()
+ {
+ Assert.ThrowsArgumentNull(
+ () => _formatter.WriteToStreamAsync(typeof(object), new object(), null, null, null), "stream");
+ }
+
+ [Fact]
+ public void ReadFromStreamAsync_WhenTypeParamterIsNull_ThrowsException()
+ {
+ Assert.ThrowsArgumentNull(() => _formatter.ReadFromStreamAsync(null, new MemoryStream(), null, null), "type");
+ }
+
+ [Fact]
+ public void ReadFromStreamAsync_WhenStreamParamterIsNull_ThrowsException()
+ {
+ Assert.ThrowsArgumentNull(() => _formatter.ReadFromStreamAsync(typeof(object), null, null, null), "stream");
+ }
+
+ [Fact]
+ public void BufferedWrite()
+ {
+ // Arrange. Specifically use the base class with async signatures.
+ MediaTypeFormatter formatter = new TestableBufferedMediaTypeFormatter();
+ MemoryStream stream = new MemoryStream();
+
+ // Act. Call the async signature.
+ string dummyData = "ignored";
+ formatter.WriteToStreamAsync(dummyData.GetType(), dummyData, stream, null, null).Wait();
+
+ // Assert
+ byte[] buffer = stream.GetBuffer();
+ Assert.Equal(123, buffer[0]);
+ }
+
+ [Fact]
+ public void BufferedRead()
+ {
+ // Arrange. Specifically use the base class with async signatures.
+ MediaTypeFormatter formatter = new TestableBufferedMediaTypeFormatter();
+ MemoryStream stream = new MemoryStream();
+ byte data = 45;
+ stream.WriteByte(data);
+ stream.Position = 0;
+
+ // Act. Call the async signature.
+ string dummyData = "ignored";
+ object result = formatter.ReadFromStreamAsync(dummyData.GetType(), stream, null, null).Result;
+
+ // Assert
+ Assert.Equal(data, result);
+ }
+
+ class TestableBufferedMediaTypeFormatter : BufferedMediaTypeFormatter
+ {
+ public override bool CanReadType(Type type)
+ {
+ return true;
+ }
+
+ public override bool CanWriteType(Type type)
+ {
+ return true;
+ }
+ public override object OnReadFromStream(Type type, Stream stream, HttpContentHeaders contentHeaders, IFormatterLogger formatterLogger)
+ {
+ byte data = (byte)stream.ReadByte();
+ return data;
+ }
+
+ public override void OnWriteToStream(Type type, object value, Stream stream, HttpContentHeaders contentHeaders, TransportContext transportContext)
+ {
+ stream.WriteByte(123);
+ }
+ }
+ }
+}
diff --git a/test/System.Net.Http.Formatting.Test.Unit/Formatting/DefaultContentNegotiatorTests.cs b/test/System.Net.Http.Formatting.Test.Unit/Formatting/DefaultContentNegotiatorTests.cs
new file mode 100644
index 00000000..e60e3ea9
--- /dev/null
+++ b/test/System.Net.Http.Formatting.Test.Unit/Formatting/DefaultContentNegotiatorTests.cs
@@ -0,0 +1,220 @@
+using System.Json;
+using System.Linq;
+using System.Net.Http.Formatting.Mocks;
+using System.Net.Http.Headers;
+using Microsoft.TestCommon;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Net.Http.Formatting
+{
+ public class DefaultContentNegotiatorTests
+ {
+ [Fact]
+ public void TypeIsCorrect()
+ {
+ Assert.Type.HasProperties(typeof(DefaultContentNegotiator), TypeAssert.TypeProperties.IsPublicVisibleClass);
+ }
+
+ [Fact]
+ public void Negotiate_WhenTypeParameterIsNull_ThrowsException()
+ {
+ DefaultContentNegotiator selector = new DefaultContentNegotiator();
+ HttpRequestMessage request = new HttpRequestMessage();
+ MediaTypeHeaderValue mediaType;
+
+ Assert.ThrowsArgumentNull(() => selector.Negotiate(null, request, Enumerable.Empty<MediaTypeFormatter>(), out mediaType), "type");
+ }
+
+ [Fact]
+ public void Negotiate_WhenRequestParameterIsNull_ThrowsException()
+ {
+ DefaultContentNegotiator selector = new DefaultContentNegotiator();
+ MediaTypeHeaderValue mediaType;
+
+ Assert.ThrowsArgumentNull(() => selector.Negotiate(typeof(string), null, Enumerable.Empty<MediaTypeFormatter>(), out mediaType), "request");
+ }
+
+ [Fact]
+ public void Negotiate_WhenFormattersParameterIsNull_ThrowsException()
+ {
+ DefaultContentNegotiator selector = new DefaultContentNegotiator();
+ HttpRequestMessage request = new HttpRequestMessage();
+ MediaTypeHeaderValue mediaType;
+
+ Assert.ThrowsArgumentNull(() => selector.Negotiate(typeof(string), request, null, out mediaType), "formatters");
+ }
+
+ [Fact]
+ public void Negotiate_ForEmptyFormatterCollection_ReturnsNull()
+ {
+ DefaultContentNegotiator selector = new DefaultContentNegotiator();
+ HttpRequestMessage request = new HttpRequestMessage();
+ MediaTypeHeaderValue mediaType;
+
+ MediaTypeFormatter formatter = selector.Negotiate(typeof(string), request, Enumerable.Empty<MediaTypeFormatter>(), out mediaType);
+
+ Assert.Null(formatter);
+ Assert.Null(mediaType);
+ }
+
+ [Fact]
+ public void Negotiate_ForRequestReturnsFirstMatchingFormatter()
+ {
+ MediaTypeHeaderValue mediaType = new MediaTypeHeaderValue("application/myMediaType");
+
+ MediaTypeFormatter formatter1 = new MockMediaTypeFormatter()
+ {
+ CanWriteTypeCallback = (Type t) => false
+ };
+
+ MediaTypeFormatter formatter2 = new MockMediaTypeFormatter()
+ {
+ CanWriteTypeCallback = (Type t) => true
+ };
+
+ formatter2.SupportedMediaTypes.Add(mediaType);
+
+ MediaTypeFormatterCollection collection = new MediaTypeFormatterCollection(
+ new MediaTypeFormatter[]
+ {
+ formatter1,
+ formatter2
+ });
+
+ HttpContent content = new StringContent("test");
+ content.Headers.ContentType = mediaType;
+ HttpRequestMessage request = new HttpRequestMessage()
+ {
+ Content = content
+ };
+
+ DefaultContentNegotiator selector = new DefaultContentNegotiator();
+ MediaTypeHeaderValue mediaTypeReturned = null;
+ MediaTypeFormatter formatter = selector.Negotiate(typeof(string), request, collection, out mediaTypeReturned);
+ Assert.Same(formatter2, formatter);
+ Assert.MediaType.AreEqual(mediaType, mediaTypeReturned, "Expected the formatter's media type to be returned.");
+ }
+
+ [Fact]
+ public void Negotiate_SelectsJsonAsDefaultFormatter()
+ {
+ // Arrange
+ HttpRequestMessage request = new HttpRequestMessage()
+ {
+ Content = new StringContent("test")
+ };
+ DefaultContentNegotiator selector = new DefaultContentNegotiator();
+ MediaTypeHeaderValue mediaTypeReturned = null;
+
+ // Act
+ MediaTypeFormatter formatter = selector.Negotiate(typeof(string), request, new MediaTypeFormatterCollection(), out mediaTypeReturned);
+
+ // Assert
+ Assert.IsType<JsonMediaTypeFormatter>(formatter);
+ Assert.Equal(mediaTypeReturned.MediaType, MediaTypeConstants.ApplicationJsonMediaType.MediaType);
+ }
+
+ [Fact]
+ public void Negotiate_SelectsXmlFormatter_ForXhrRequestThatAcceptsXml()
+ {
+ // Arrange
+ HttpRequestMessage request = new HttpRequestMessage()
+ {
+ Content = new StringContent("test")
+ };
+ request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/xml"));
+ request.Headers.Add("x-requested-with", "XMLHttpRequest");
+ DefaultContentNegotiator selector = new DefaultContentNegotiator();
+ MediaTypeHeaderValue mediaTypeReturned = null;
+
+ // Act
+ MediaTypeFormatter formatter = selector.Negotiate(typeof(string), request, new MediaTypeFormatterCollection(), out mediaTypeReturned);
+
+ // Assert
+ Assert.Equal("application/xml", mediaTypeReturned.MediaType);
+ Assert.IsType<XmlMediaTypeFormatter>(formatter);
+ }
+
+ [Fact]
+ public void Negotiate_SelectsJsonFormatter_ForXhrRequestThatDoesNotSpecifyAcceptHeaders()
+ {
+ // Arrange
+ HttpRequestMessage request = new HttpRequestMessage()
+ {
+ Content = new StringContent("test")
+ };
+ request.Headers.Add("x-requested-with", "XMLHttpRequest");
+ DefaultContentNegotiator selector = new DefaultContentNegotiator();
+ MediaTypeHeaderValue mediaTypeReturned = null;
+
+ // Act
+ MediaTypeFormatter formatter = selector.Negotiate(typeof(string), request, new MediaTypeFormatterCollection(), out mediaTypeReturned);
+
+ // Assert
+ Assert.Equal("application/json", mediaTypeReturned.MediaType);
+ Assert.IsType<JsonMediaTypeFormatter>(formatter);
+ }
+
+ [Fact]
+ public void Negotiate_SelectsJsonFormatter_ForXHRAndJsonValueResponse()
+ {
+ // Arrange
+ HttpRequestMessage request = new HttpRequestMessage()
+ {
+ Content = new StringContent("test")
+ };
+ request.Headers.Add("x-requested-with", "XMLHttpRequest");
+ DefaultContentNegotiator selector = new DefaultContentNegotiator();
+ MediaTypeHeaderValue mediaTypeReturned = null;
+
+ // Act
+ MediaTypeFormatter formatter = selector.Negotiate(typeof(JsonValue), request, new MediaTypeFormatterCollection(), out mediaTypeReturned);
+
+ Assert.Equal("application/json", mediaTypeReturned.MediaType);
+ Assert.IsType<JsonMediaTypeFormatter>(formatter);
+ }
+
+ [Fact]
+ public void Negotiate_SelectsJsonFormatter_ForXHRAndMatchAllAcceptHeader()
+ {
+ // Accept
+ HttpRequestMessage request = new HttpRequestMessage()
+ {
+ Content = new StringContent("test")
+ };
+ request.Headers.Add("x-requested-with", "XMLHttpRequest");
+ request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("*/*"));
+ DefaultContentNegotiator selector = new DefaultContentNegotiator();
+ MediaTypeHeaderValue mediaTypeReturned = null;
+
+ // Act
+ MediaTypeFormatter formatter = selector.Negotiate(typeof(string), request, new MediaTypeFormatterCollection(), out mediaTypeReturned);
+
+ // Assert
+ Assert.Equal("application/json", mediaTypeReturned.MediaType);
+ Assert.IsType<JsonMediaTypeFormatter>(formatter);
+ }
+
+ [Fact]
+ public void Negotiate_UsesRequestedFormatterForXHRAndMatchAllPlusOtherAcceptHeader()
+ {
+ // Arrange
+ HttpRequestMessage request = new HttpRequestMessage()
+ {
+ Content = new StringContent("test")
+ };
+ request.Headers.Add("x-requested-with", "XMLHttpRequest");
+ request.Headers.Accept.ParseAdd("text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"); // XHR header sent by Firefox 3b5
+ DefaultContentNegotiator selector = new DefaultContentNegotiator();
+ MediaTypeHeaderValue mediaTypeReturned = null;
+
+ // Act
+ MediaTypeFormatter formatter = selector.Negotiate(typeof(string), request, new MediaTypeFormatterCollection(), out mediaTypeReturned);
+
+ // Assert
+ Assert.Equal("application/xml", mediaTypeReturned.MediaType);
+ Assert.IsType<XmlMediaTypeFormatter>(formatter);
+ }
+ }
+}
diff --git a/test/System.Net.Http.Formatting.Test.Unit/Formatting/FormDataCollectionTests.cs b/test/System.Net.Http.Formatting.Test.Unit/Formatting/FormDataCollectionTests.cs
new file mode 100644
index 00000000..1e250a04
--- /dev/null
+++ b/test/System.Net.Http.Formatting.Test.Unit/Formatting/FormDataCollectionTests.cs
@@ -0,0 +1,107 @@
+using System.Collections.Generic;
+using System.IO;
+using System.Json;
+using System.Linq;
+using System.Net.Http.Formatting.DataSets;
+using System.Net.Http.Formatting.DataSets.Types;
+using System.Net.Http.Headers;
+using System.Runtime.Serialization.Json;
+using System.Text;
+using Microsoft.TestCommon;
+using Xunit;
+using Xunit.Extensions;
+using Assert = Microsoft.TestCommon.AssertEx;
+using System.Collections.Specialized;
+
+namespace System.Net.Http.Formatting
+{
+ public class FormDataCollectionTests
+ {
+ [Fact]
+ public void CreateFromUri()
+ {
+ FormDataCollection form = new FormDataCollection(new Uri("http://foo.com/?x=1&y=2"));
+
+ Assert.Equal("1", form.Get("x"));
+ Assert.Equal("2", form.Get("y"));
+ }
+
+ [Fact]
+ public void CreateFromEmptyUri()
+ {
+ FormDataCollection form = new FormDataCollection(new Uri("http://foo.com"));
+
+ Assert.Equal(0, form.Count());
+ }
+
+ [Fact]
+ public void UriConstructorThrowsNull()
+ {
+ Assert.Throws<ArgumentNullException>(() => new FormDataCollection((Uri)null));
+ }
+
+ [Fact]
+ public void PairConstructorThrowsNull()
+ {
+ var arg = (IEnumerable<KeyValuePair<string, string>>)null;
+ Assert.Throws<ArgumentNullException>(() => new FormDataCollection(arg));
+ }
+
+ [Fact]
+ public void CreateFromPairs()
+ {
+ Dictionary<string, string> pairs = new Dictionary<string,string>
+ {
+ { "x", "1"},
+ { "y" , "2"}
+ };
+
+ var form = new FormDataCollection(pairs);
+
+ Assert.Equal("1", form.Get("x"));
+ Assert.Equal("2", form.Get("y"));
+ }
+
+ [Fact]
+ public void Enumeration()
+ {
+ FormDataCollection form = new FormDataCollection(new Uri("http://foo.com/?x=1&y=2"));
+
+ // Enumeration should be ordered
+ String s = "";
+ foreach (KeyValuePair<string, string> kv in form)
+ {
+ s += string.Format("{0}={1};", kv.Key, kv.Value);
+ }
+
+ Assert.Equal("x=1;y=2;", s);
+ }
+
+ [Fact]
+ public void GetValues()
+ {
+ FormDataCollection form = new FormDataCollection(new Uri("http://foo.com/?x=1&x=2&x=3"));
+
+ Assert.Equal(new string [] { "1", "2", "3"}, form.GetValues("x"));
+ }
+
+ [Fact]
+ public void ToNameValueCollection()
+ {
+ FormDataCollection form = new FormDataCollection(new Uri("http://foo.com/?x=1a&y=2&x=1b&=ValueOnly&KeyOnly"));
+
+ NameValueCollection nvc = form.ReadAsNameValueCollection();
+
+ // y=2
+ // x=1a;x=1b
+ // =ValueOnly
+ // KeyOnly
+ Assert.Equal(4, nvc.Count);
+ Assert.Equal(new string[] { "1a", "1b"}, nvc.GetValues("x"));
+ Assert.Equal("1a,1b", nvc.Get("x"));
+ Assert.Equal("2", nvc.Get("y"));
+ Assert.Equal(null, nvc.Get("KeyOnly"));
+ Assert.Equal("ValueOnly", nvc.Get(""));
+ }
+ }
+} \ No newline at end of file
diff --git a/test/System.Net.Http.Formatting.Test.Unit/Formatting/FormUrlEncodedMediaTypeFormatterTests.cs b/test/System.Net.Http.Formatting.Test.Unit/Formatting/FormUrlEncodedMediaTypeFormatterTests.cs
new file mode 100644
index 00000000..8727fc8b
--- /dev/null
+++ b/test/System.Net.Http.Formatting.Test.Unit/Formatting/FormUrlEncodedMediaTypeFormatterTests.cs
@@ -0,0 +1,148 @@
+using System.IO;
+using System.Net.Http.Formatting.DataSets;
+using System.Net.Http.Headers;
+using Microsoft.TestCommon;
+using Xunit;
+using Xunit.Extensions;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Net.Http.Formatting
+{
+ public class FormUrlEncodedMediaTypeFormatterTests
+ {
+ private const int MinBufferSize = 256;
+ private const int DefaultBufferSize = 32 * 1024;
+
+ [Fact]
+ [Trait("Description", "FormUrlEncodedMediaTypeFormatter is public, concrete, and unsealed.")]
+ public void TypeIsCorrect()
+ {
+ Assert.Type.HasProperties(typeof(FormUrlEncodedMediaTypeFormatter), TypeAssert.TypeProperties.IsPublicVisibleClass);
+ }
+
+ [Theory]
+ [TestDataSet(typeof(HttpUnitTestDataSets), "StandardFormUrlEncodedMediaTypes")]
+ [Trait("Description", "FormUrlEncodedMediaTypeFormatter() constructor sets standard form URL encoded media types in SupportedMediaTypes.")]
+ public void Constructor(MediaTypeHeaderValue mediaType)
+ {
+ FormUrlEncodedMediaTypeFormatter formatter = new FormUrlEncodedMediaTypeFormatter();
+ Assert.True(formatter.SupportedMediaTypes.Contains(mediaType), String.Format("SupportedMediaTypes should have included {0}.", mediaType.ToString()));
+ }
+
+ [Fact]
+ [Trait("Description", "DefaultMediaType property returns application/x-www-form-urlencoded.")]
+ public void DefaultMediaTypeReturnsApplicationJson()
+ {
+ MediaTypeHeaderValue mediaType = FormUrlEncodedMediaTypeFormatter.DefaultMediaType;
+ Assert.NotNull(mediaType);
+ Assert.Equal("application/x-www-form-urlencoded", mediaType.MediaType);
+ }
+
+ [Fact]
+ [Trait("Description", "ReadBufferSize return correct value.")]
+ public void ReadBufferSizeReturnsCorrectValue()
+ {
+ FormUrlEncodedMediaTypeFormatter formatter = new FormUrlEncodedMediaTypeFormatter();
+ Assert.Equal(DefaultBufferSize, formatter.ReadBufferSize);
+ formatter.ReadBufferSize = MinBufferSize;
+ Assert.Equal(MinBufferSize, formatter.ReadBufferSize);
+ }
+
+ [Fact]
+ [Trait("Description", "ReadBufferSize throws on invalid value.")]
+ public void ReadBufferSizeThrowsOnInvalidValue()
+ {
+ FormUrlEncodedMediaTypeFormatter formatter = new FormUrlEncodedMediaTypeFormatter();
+ Assert.ThrowsArgument(() => { formatter.ReadBufferSize = -1; }, "value");
+ Assert.ThrowsArgument(() => { formatter.ReadBufferSize = 0; }, "value");
+ Assert.ThrowsArgument(() => { formatter.ReadBufferSize = MinBufferSize - 1; }, "value");
+ }
+
+ [Fact]
+ [Trait("Description", "CanReadType() throws on null.")]
+ public void CanReadTypeThrowsOnNull()
+ {
+ TestFormUrlEncodedMediaTypeFormatter formatter = new TestFormUrlEncodedMediaTypeFormatter();
+ Assert.ThrowsArgumentNull(() => { formatter.CanReadType(null); }, "type");
+ }
+
+ [Theory]
+ [InlineData(typeof(FormDataCollection))]
+ [InlineData(typeof(System.Json.JsonValue))]
+ [InlineData(typeof(IKeyValueModel))]
+ public void CanReadTypeTrue(Type type)
+ {
+ TestFormUrlEncodedMediaTypeFormatter formatter = new TestFormUrlEncodedMediaTypeFormatter();
+
+ Assert.True(formatter.CanReadType(type));
+ }
+
+
+ [Theory]
+ [TestDataSet(typeof(CommonUnitTestDataSets), "RepresentativeValueAndRefTypeTestDataCollection")]
+ [Trait("Description", "CanReadType(Type) returns false.")]
+ public void CanReadTypeReturnsFalse(Type variationType, object testData)
+ {
+ TestFormUrlEncodedMediaTypeFormatter formatter = new TestFormUrlEncodedMediaTypeFormatter();
+
+ Assert.False(formatter.CanReadType(variationType));
+
+ // Ask a 2nd time to probe whether the cached result is treated the same
+ Assert.False(formatter.CanReadType(variationType));
+ }
+
+
+ [Fact]
+ [Trait("Description", "CanWriteType(Type) throws on null.")]
+ public void CanWriteTypeThrowsOnNull()
+ {
+ TestFormUrlEncodedMediaTypeFormatter formatter = new TestFormUrlEncodedMediaTypeFormatter();
+ Assert.ThrowsArgumentNull(() => { formatter.CanWriteType(null); }, "type");
+ }
+
+ [Theory]
+ [TestDataSet(typeof(CommonUnitTestDataSets), "RepresentativeValueAndRefTypeTestDataCollection")]
+ [Trait("Description", "CanWriteType() returns false.")]
+ public void CanWriteTypeReturnsFalse(Type variationType, object testData)
+ {
+ TestFormUrlEncodedMediaTypeFormatter formatter = new TestFormUrlEncodedMediaTypeFormatter();
+
+ Assert.False(formatter.CanWriteType(variationType), "formatter should have returned false.");
+
+ // Ask a 2nd time to probe whether the cached result is treated the same
+ Assert.False(formatter.CanWriteType(variationType), "formatter should have returned false on 2nd try as well.");
+ }
+
+ [Fact]
+ [Trait("Description", "ReadFromStreamAsync() throws on null.")]
+ public void ReadFromStreamThrowsOnNull()
+ {
+ TestFormUrlEncodedMediaTypeFormatter formatter = new TestFormUrlEncodedMediaTypeFormatter();
+ Assert.ThrowsArgumentNull(() => { formatter.ReadFromStreamAsync(null, Stream.Null, null, null); }, "type");
+ Assert.ThrowsArgumentNull(() => { formatter.ReadFromStreamAsync(typeof(object), null, null, null); }, "stream");
+ }
+
+ [Fact]
+ [Trait("Description", "WriteToStreamAsync() throws not implemented.")]
+ public void WriteToStreamAsyncThrowsNotImplemented()
+ {
+ FormUrlEncodedMediaTypeFormatter formatter = new FormUrlEncodedMediaTypeFormatter();
+ Assert.Throws<NotSupportedException>(
+ () => formatter.WriteToStreamAsync(typeof(object), new object(), Stream.Null, null, null),
+ "The media type formatter of type 'System.Net.Http.Formatting.FormUrlEncodedMediaTypeFormatter' does not support writing since it does not implement the WriteToStreamAsync method.");
+ }
+
+ public class TestFormUrlEncodedMediaTypeFormatter : FormUrlEncodedMediaTypeFormatter
+ {
+ public new bool CanReadType(Type type)
+ {
+ return base.CanReadType(type);
+ }
+
+ public new bool CanWriteType(Type type)
+ {
+ return base.CanWriteType(type);
+ }
+ }
+ }
+}
diff --git a/test/System.Net.Http.Formatting.Test.Unit/Formatting/JsonKeyValueModelTest.cs b/test/System.Net.Http.Formatting.Test.Unit/Formatting/JsonKeyValueModelTest.cs
new file mode 100644
index 00000000..36a5a19e
--- /dev/null
+++ b/test/System.Net.Http.Formatting.Test.Unit/Formatting/JsonKeyValueModelTest.cs
@@ -0,0 +1,39 @@
+using System.Json;
+using Microsoft.TestCommon;
+using Xunit.Extensions;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Net.Http.Formatting
+{
+ public class JsonKeyValueModelTest
+ {
+ [Theory]
+ [PropertyData("JsonKeyValueModelBuildsCorrectlyData")]
+ public void JsonKeyValueModelBuildsCorrectly(string json, string expectedKey, object expectedValue)
+ {
+ JsonKeyValueModel keyValueModel = new JsonKeyValueModel(JsonValue.Parse(json));
+
+ object value;
+ Assert.Contains(expectedKey, keyValueModel.Keys);
+ Assert.True(keyValueModel.TryGetValue(expectedKey, out value));
+ Assert.Equal(expectedValue, value);
+ }
+
+ public static TheoryDataSet<string, string, object> JsonKeyValueModelBuildsCorrectlyData
+ {
+ get
+ {
+ return new TheoryDataSet<string, string, object>
+ {
+ { "{ \"input\" : [1, 2, 3] }", "input[0]", "1" }, // array inside dict
+ { "{ \"input\" : [1, 2, 3] }", "input[1]", "2" }, // array inside dict
+ { "{ \"input\" : [1, 2, 3] }", "input[2]", "3" }, // array inside dict
+ { "[1, 2, 3]" , "[0]", "1" }, // just a json array
+ { "{ \"foo\" : [ { \"bar\" : 1 }, { \"bar\" : 1 } ] }", "foo[1].bar", "1" }, // dict inside array inside dict
+ { "[ [1, 2, 3], [ 2, 3, 4] ]", "[1][2]", "4" }, // array of array
+ { "[ { \"foo\" : \"bar\" }, { \"foo1\" : \"bar1\" }]", "[1].foo1" , "bar1" } // array of dicts
+ };
+ }
+ }
+ }
+}
diff --git a/test/System.Net.Http.Formatting.Test.Unit/Formatting/JsonMediaTypeFormatterTests.cs b/test/System.Net.Http.Formatting.Test.Unit/Formatting/JsonMediaTypeFormatterTests.cs
new file mode 100644
index 00000000..f8daf91c
--- /dev/null
+++ b/test/System.Net.Http.Formatting.Test.Unit/Formatting/JsonMediaTypeFormatterTests.cs
@@ -0,0 +1,244 @@
+using System.Collections.Generic;
+using System.IO;
+using System.Json;
+using System.Linq;
+using System.Net.Http.Formatting.DataSets;
+using System.Net.Http.Formatting.DataSets.Types;
+using System.Net.Http.Headers;
+using System.Runtime.Serialization.Json;
+using System.Text;
+using Microsoft.TestCommon;
+using Xunit;
+using Xunit.Extensions;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Net.Http.Formatting
+{
+ public class JsonMediaTypeFormatterTests
+ {
+ public static List<Type> JsonValueTypes
+ {
+ get
+ {
+ return new List<Type>
+ {
+ typeof(JsonValue),
+ typeof(JsonPrimitive),
+ typeof(JsonArray),
+ typeof(JsonObject)
+ };
+ }
+ }
+
+ public static IEnumerable<TestData> ValueAndRefTypeTestDataCollectionExceptULong
+ {
+ get
+ {
+ return CommonUnitTestDataSets.ValueAndRefTypeTestDataCollection.Except(new[] { CommonUnitTestDataSets.Ulongs });
+ }
+ }
+
+ [Fact]
+ public void TypeIsCorrect()
+ {
+ Assert.Type.HasProperties<JsonMediaTypeFormatter, MediaTypeFormatter>(TypeAssert.TypeProperties.IsPublicVisibleClass);
+ }
+
+ [Fact]
+ [Trait("Description", "JsonMediaTypeFormatter() constructor sets standard Json media types in SupportedMediaTypes.")]
+ public void Constructor()
+ {
+ JsonMediaTypeFormatter formatter = new JsonMediaTypeFormatter();
+ foreach (MediaTypeHeaderValue mediaType in HttpUnitTestDataSets.StandardJsonMediaTypes)
+ {
+ Assert.True(formatter.SupportedMediaTypes.Contains(mediaType), String.Format("SupportedMediaTypes should have included {0}.", mediaType.ToString()));
+ }
+ }
+
+ [Fact]
+ [Trait("Description", "DefaultMediaType property returns application/json.")]
+ public void DefaultMediaTypeReturnsApplicationJson()
+ {
+ MediaTypeHeaderValue mediaType = JsonMediaTypeFormatter.DefaultMediaType;
+ Assert.NotNull(mediaType);
+ Assert.Equal("application/json", mediaType.MediaType);
+ }
+
+
+ [Fact]
+ [Trait("Description", "CharacterEncoding property handles Get/Set correctly.")]
+ public void CharacterEncodingGetSet()
+ {
+ JsonMediaTypeFormatter jsonFormatter = new JsonMediaTypeFormatter();
+ Assert.IsType<UTF8Encoding>(jsonFormatter.CharacterEncoding);
+ jsonFormatter.CharacterEncoding = Encoding.Unicode;
+ Assert.Same(Encoding.Unicode, jsonFormatter.CharacterEncoding);
+ jsonFormatter.CharacterEncoding = Encoding.UTF8;
+ Assert.Same(Encoding.UTF8, jsonFormatter.CharacterEncoding);
+ }
+
+ [Fact]
+ [Trait("Description", "CharacterEncoding property throws on invalid arguments")]
+ public void CharacterEncodingSetThrows()
+ {
+ JsonMediaTypeFormatter jsonFormatter = new JsonMediaTypeFormatter();
+ Assert.ThrowsArgumentNull(() => { jsonFormatter.CharacterEncoding = null; }, "value");
+ Assert.ThrowsArgument(() => { jsonFormatter.CharacterEncoding = Encoding.UTF32; }, "value");
+ }
+
+ [Theory]
+ [TestDataSet(typeof(CommonUnitTestDataSets), "RepresentativeValueAndRefTypeTestDataCollection")]
+ [Trait("Description", "CanReadType() returns the expected results for all known value and reference types.")]
+ public void CanReadTypeReturnsExpectedValues(Type variationType, object testData)
+ {
+ TestJsonMediaTypeFormatter formatter = new TestJsonMediaTypeFormatter();
+
+ bool isSerializable = IsTypeSerializableWithJsonSerializer(variationType, testData);
+ bool canSupport = formatter.CanReadTypeProxy(variationType);
+
+ // If we don't agree, we assert only if the DCJ serializer says it cannot support something we think it should
+ Assert.False(isSerializable != canSupport && isSerializable, String.Format("CanReadType returned wrong value for '{0}'.", variationType));
+
+ // Ask a 2nd time to probe whether the cached result is treated the same
+ canSupport = formatter.CanReadTypeProxy(variationType);
+ Assert.False(isSerializable != canSupport && isSerializable, String.Format("2nd CanReadType returned wrong value for '{0}'.", variationType));
+ }
+
+ [Fact]
+ [Trait("Description", "CanReadType() returns true on JsonValue.")]
+ public void CanReadTypeReturnsTrueOnJsonValue()
+ {
+ TestJsonMediaTypeFormatter formatter = new TestJsonMediaTypeFormatter();
+ foreach (Type type in JsonValueTypes)
+ {
+ Assert.True(formatter.CanReadTypeProxy(type), "formatter should have returned true.");
+ }
+ }
+
+ [Fact]
+ [Trait("Description", "CanWriteType() returns true on JsonValue.")]
+ public void CanWriteTypeReturnsTrueOnJsonValue()
+ {
+ TestJsonMediaTypeFormatter formatter = new TestJsonMediaTypeFormatter();
+ foreach (Type type in JsonValueTypes)
+ {
+ Assert.True(formatter.CanWriteTypeProxy(type), "formatter should have returned false.");
+ }
+ }
+
+ [Theory]
+ [TestDataSet(typeof(JsonMediaTypeFormatterTests), "ValueAndRefTypeTestDataCollectionExceptULong")]
+ [Trait("Description", "ReadFromStream() returns all value and reference types serialized via WriteToStream.")]
+ public void ReadFromAsyncStreamRoundTripsWriteToStreamAsync(Type variationType, object testData)
+ {
+ TestJsonMediaTypeFormatter formatter = new TestJsonMediaTypeFormatter();
+ HttpContentHeaders contentHeaders = new StringContent(String.Empty).Headers;
+
+
+ bool canSerialize = IsTypeSerializableWithJsonSerializer(variationType, testData) && Assert.Http.CanRoundTrip(variationType);
+ if (canSerialize)
+ {
+ object readObj = null;
+ Assert.Stream.WriteAndRead(
+ stream => Assert.Task.Succeeds(formatter.WriteToStreamAsync(variationType, testData, stream, contentHeaders, transportContext: null)),
+ stream => readObj = Assert.Task.SucceedsWithResult<object>(formatter.ReadFromStreamAsync(variationType, stream, contentHeaders, null)));
+ Assert.Equal(testData, readObj);
+ }
+ }
+
+ [Fact]
+ [Trait("Description", "ReadFromStreamAsync() roundtrips JsonValue.")]
+ public void ReadFromStreamAsyncRoundTripsJsonValue()
+ {
+ string beforeMessage = "Hello World";
+ TestJsonMediaTypeFormatter formatter = new TestJsonMediaTypeFormatter();
+ JsonValue before = beforeMessage;
+ MemoryStream memStream = new MemoryStream();
+ before.Save(memStream);
+ memStream.Position = 0;
+
+ JsonValue after = Assert.Task.SucceedsWithResult<object>(formatter.ReadFromStreamAsync(typeof(JsonValue), memStream, null, null)) as JsonValue;
+ Assert.NotNull(after);
+ string afterMessage = after.ReadAs<string>();
+
+ Assert.Equal(beforeMessage, afterMessage);
+ }
+
+ [Theory]
+ [TestDataSet(typeof(CommonUnitTestDataSets), "RepresentativeValueAndRefTypeTestDataCollection")]
+ [Trait("Description", "ReadFromStreamAsync() returns all value and reference types serialized via WriteToStreamAsync.")]
+ public void ReadFromStreamAsyncRoundTripsWriteToStreamAsync(Type variationType, object testData)
+ {
+ TestJsonMediaTypeFormatter formatter = new TestJsonMediaTypeFormatter();
+ HttpContentHeaders contentHeaders = new StringContent(String.Empty).Headers;
+
+ bool canSerialize = IsTypeSerializableWithJsonSerializer(variationType, testData) && Assert.Http.CanRoundTrip(variationType);
+ if (canSerialize)
+ {
+ object readObj = null;
+ Assert.Stream.WriteAndRead(
+ stream => Assert.Task.Succeeds(formatter.WriteToStreamAsync(variationType, testData, stream, contentHeaders, transportContext: null)),
+ stream => readObj = Assert.Task.SucceedsWithResult(formatter.ReadFromStreamAsync(variationType, stream, contentHeaders, null)));
+ Assert.Equal(testData, readObj);
+ }
+
+ }
+
+ [Fact]
+ [Trait("Description", "OnWriteToStreamAsync() throws on null.")]
+ public void WriteToStreamAsyncThrowsOnNull()
+ {
+ TestJsonMediaTypeFormatter formatter = new TestJsonMediaTypeFormatter();
+ Assert.ThrowsArgumentNull(() => { formatter.WriteToStreamAsync(null, new object(), Stream.Null, null, null); }, "type");
+ Assert.ThrowsArgumentNull(() => { formatter.WriteToStreamAsync(typeof(object), new object(), null, null, null); }, "stream");
+ }
+
+ [Fact]
+ [Trait("Description", "OnWriteToStreamAsync() roundtrips JsonValue.")]
+ public void WriteToStreamAsyncRoundTripsJsonValue()
+ {
+ string beforeMessage = "Hello World";
+ TestJsonMediaTypeFormatter formatter = new TestJsonMediaTypeFormatter();
+ JsonValue before = new JsonPrimitive(beforeMessage);
+ MemoryStream memStream = new MemoryStream();
+
+ Assert.Task.Succeeds(formatter.WriteToStreamAsync(typeof(JsonValue), before, memStream, null, null));
+ memStream.Position = 0;
+ JsonValue after = JsonValue.Load(memStream);
+ string afterMessage = after.ReadAs<string>();
+
+ Assert.Equal(beforeMessage, afterMessage);
+ }
+
+ public class TestJsonMediaTypeFormatter : JsonMediaTypeFormatter
+ {
+ public bool CanReadTypeProxy(Type type)
+ {
+ return CanReadType(type);
+ }
+
+ public bool CanWriteTypeProxy(Type type)
+ {
+ return CanWriteType(type);
+ }
+ }
+
+ private bool IsTypeSerializableWithJsonSerializer(Type type, object obj)
+ {
+ try
+ {
+ new DataContractJsonSerializer(type);
+ if (obj != null && obj.GetType() != type)
+ {
+ new DataContractJsonSerializer(obj.GetType());
+ }
+ }
+ catch
+ {
+ return false;
+ }
+
+ return !Assert.Http.IsKnownUnserializable(type, obj, (t) => typeof(INotJsonSerializable).IsAssignableFrom(t));
+ }
+ }
+}
diff --git a/test/System.Net.Http.Formatting.Test.Unit/Formatting/MediaRangeMappingTests.cs b/test/System.Net.Http.Formatting.Test.Unit/Formatting/MediaRangeMappingTests.cs
new file mode 100644
index 00000000..51d4e981
--- /dev/null
+++ b/test/System.Net.Http.Formatting.Test.Unit/Formatting/MediaRangeMappingTests.cs
@@ -0,0 +1,151 @@
+using System.Net.Http.Formatting.DataSets;
+using System.Net.Http.Headers;
+using Microsoft.TestCommon;
+using Xunit;
+using Xunit.Extensions;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Net.Http.Formatting
+{
+ public class MediaRangeMappingTests
+ {
+ [Fact]
+ public void TypeIsCorrect()
+ {
+ Assert.Type.HasProperties<MediaRangeMapping, MediaTypeMapping>(TypeAssert.TypeProperties.IsPublicVisibleClass | TypeAssert.TypeProperties.IsSealed);
+ }
+
+ [Theory]
+ [TestDataSet(
+ typeof(HttpUnitTestDataSets), "LegalMediaRangeValues",
+ typeof(HttpUnitTestDataSets), "LegalMediaTypeHeaderValues")]
+ [Trait("Description", "MediaRangeMapping(MediaTypeHeaderValue, MediaTypeHeaderValue) sets public properties.")]
+ public void Constructor(MediaTypeHeaderValue mediaRange, MediaTypeHeaderValue mediaType)
+ {
+ MediaRangeMapping mapping = new MediaRangeMapping(mediaRange, mediaType);
+ Assert.MediaType.AreEqual(mediaRange, mapping.MediaRange, "MediaRange failed to set.");
+ Assert.MediaType.AreEqual(mediaType, mapping.MediaType, "MediaType failed to set.");
+ }
+
+ [Theory]
+ [TestDataSet(typeof(HttpUnitTestDataSets), "LegalMediaTypeHeaderValues")]
+ [Trait("Description", "MediaRangeMapping(MediaTypeHeaderValue, MediaTypeHeaderValue) throws if the MediaRange parameter is null.")]
+ public void ConstructorThrowsWithNullMediaRange(MediaTypeHeaderValue mediaType)
+ {
+ Assert.ThrowsArgumentNull(() => new MediaRangeMapping((MediaTypeHeaderValue)null, mediaType), "mediaRange");
+ }
+
+ [Theory]
+ [TestDataSet(typeof(HttpUnitTestDataSets), "LegalMediaRangeValues")]
+ [Trait("Description", "MediaRangeMapping(MediaTypeHeaderValue, MediaTypeHeaderValue) throws if the MediaType parameter is null.")]
+ public void ConstructorThrowsWithNullMediaType(MediaTypeHeaderValue mediaRange)
+ {
+ Assert.ThrowsArgumentNull(() => new MediaRangeMapping(mediaRange, (MediaTypeHeaderValue)null), "mediaType");
+ }
+
+ [Theory]
+ [TestDataSet(
+ typeof(HttpUnitTestDataSets), "IllegalMediaRangeValues",
+ typeof(HttpUnitTestDataSets), "LegalMediaTypeHeaderValues")]
+ [Trait("Description", "MediaRangeMapping(MediaTypeHeaderValue, MediaTypeHeaderValue) throws if the MediaRange parameter is not really a media range.")]
+ public void ConstructorThrowsWithIllegalMediaRange(MediaTypeHeaderValue mediaRange, MediaTypeHeaderValue mediaType)
+ {
+ string errorMessage = RS.Format(Properties.Resources.InvalidMediaRange, mediaRange.MediaType);
+ Assert.Throws<InvalidOperationException>(() => new MediaRangeMapping(mediaRange, mediaType), errorMessage);
+ }
+
+ [Theory]
+ [TestDataSet(
+ typeof(HttpUnitTestDataSets), "LegalMediaRangeStrings",
+ typeof(HttpUnitTestDataSets), "LegalMediaTypeStrings")]
+ [Trait("Description", "MediaRangeMapping(string, string) sets public properties.")]
+ public void Constructor1(string mediaRange, string mediaType)
+ {
+ MediaRangeMapping mapping = new MediaRangeMapping(mediaRange, mediaType);
+ Assert.MediaType.AreEqual(mediaRange, mapping.MediaRange, "MediaRange failed to set.");
+ Assert.MediaType.AreEqual(mediaType, mapping.MediaType, "MediaType failed to set.");
+ }
+
+ [Theory]
+ [TestDataSet(
+ typeof(CommonUnitTestDataSets), "EmptyStrings",
+ typeof(HttpUnitTestDataSets), "LegalMediaTypeStrings")]
+ [Trait("Description", "MediaRangeMapping(string, string) throws if the MediaRange parameter is empty.")]
+ public void Constructor1ThrowsWithEmptyMediaRange(string mediaRange, string mediaType)
+ {
+ Assert.ThrowsArgumentNull(() => new MediaRangeMapping(mediaRange, mediaType), "mediaRange");
+ }
+
+ [Theory]
+ [TestDataSet(
+ typeof(HttpUnitTestDataSets), "LegalMediaRangeStrings",
+ typeof(CommonUnitTestDataSets), "EmptyStrings")]
+ [Trait("Description", "MediaRangeMapping(string, string) throws if the MediaType parameter is empty.")]
+ public void Constructor1ThrowsWithEmptyMediaType(string mediaRange, string mediaType)
+ {
+ Assert.ThrowsArgumentNull(() => new MediaRangeMapping(mediaRange, mediaType), "mediaType");
+ }
+
+ [Theory]
+ [TestDataSet(
+ typeof(HttpUnitTestDataSets), "IllegalMediaRangeStrings",
+ typeof(HttpUnitTestDataSets), "LegalMediaTypeStrings")]
+ [Trait("Description", "MediaRangeMapping(string, string) throws if the MediaRange parameter is not really a media range.")]
+ public void Constructor1ThrowsWithIllegalMediaRange(string mediaRange, string mediaType)
+ {
+ string errorMessage = RS.Format(Properties.Resources.InvalidMediaRange, mediaRange);
+ Assert.Throws<InvalidOperationException>(() => new MediaRangeMapping(mediaRange, mediaType), errorMessage);
+ }
+
+ [Theory]
+ [TestDataSet(
+ typeof(HttpUnitTestDataSets), "LegalMediaRangeStrings",
+ typeof(HttpUnitTestDataSets), "LegalMediaTypeStrings")]
+ [Trait("Description", "TryMatchMediaType(HttpRequestMessage) throws with null HttpRequestMessage.")]
+ public void TryMatchMediaTypeThrowsWithNullHttpRequestMessage(string mediaRange, string mediaType)
+ {
+ MediaRangeMapping mapping = new MediaRangeMapping(mediaRange, mediaType);
+ Assert.ThrowsArgumentNull(() => mapping.TryMatchMediaType(request: null), "request");
+ }
+
+ [Theory]
+ [TestDataSet(
+ typeof(HttpUnitTestDataSets), "LegalMediaRangeStrings",
+ typeof(HttpUnitTestDataSets), "LegalMediaTypeStrings")]
+ [Trait("Description", "TryMatchMediaType(HttpRequestMessage) returns 1.0 when the MediaRange is in the accept headers.")]
+ public void TryMatchMediaTypeReturnsOneWithMediaRangeInAcceptHeader(string mediaRange, string mediaType)
+ {
+ MediaRangeMapping mapping = new MediaRangeMapping(mediaRange, mediaType);
+ HttpRequestMessage request = new HttpRequestMessage();
+ request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue(mediaRange));
+ Assert.Equal(1.0, mapping.TryMatchMediaType(request));
+ }
+
+ [Theory]
+ [TestDataSet(
+ typeof(HttpUnitTestDataSets), "MediaRangeValuesWithQuality",
+ typeof(HttpUnitTestDataSets), "LegalMediaTypeHeaderValues")]
+ [Trait("Description", "TryMatchMediaType(HttpRequestMessage) returns quality factor when a MediaRange with quality is in the accept headers.")]
+ public void TryMatchMediaTypeReturnsQualityWithMediaRangeWithQualityInAcceptHeader(MediaTypeWithQualityHeaderValue mediaRangeWithQuality, MediaTypeHeaderValue mediaType)
+ {
+ MediaTypeWithQualityHeaderValue mediaRangeWithNoQuality = new MediaTypeWithQualityHeaderValue(mediaRangeWithQuality.MediaType);
+ MediaRangeMapping mapping = new MediaRangeMapping(mediaRangeWithNoQuality, mediaType);
+ HttpRequestMessage request = new HttpRequestMessage();
+ request.Headers.Accept.Add(mediaRangeWithQuality);
+ double quality = mediaRangeWithQuality.Quality.Value;
+ Assert.Equal(quality, mapping.TryMatchMediaType(request));
+ }
+
+ [Theory]
+ [TestDataSet(
+ typeof(HttpUnitTestDataSets), "LegalMediaRangeStrings",
+ typeof(HttpUnitTestDataSets), "LegalMediaTypeStrings")]
+ [Trait("Description", "TryMatchMediaType(HttpRequestMessage) returns 0.0 when the MediaRange is not in the accept headers.")]
+ public void TryMatchMediaTypeReturnsFalseWithMediaRangeNotInAcceptHeader(string mediaRange, string mediaType)
+ {
+ MediaRangeMapping mapping = new MediaRangeMapping(mediaRange, mediaType);
+ HttpRequestMessage request = new HttpRequestMessage();
+ Assert.Equal(0.0, mapping.TryMatchMediaType(request));
+ }
+ }
+}
diff --git a/test/System.Net.Http.Formatting.Test.Unit/Formatting/MediaTypeConstantsTests.cs b/test/System.Net.Http.Formatting.Test.Unit/Formatting/MediaTypeConstantsTests.cs
new file mode 100644
index 00000000..1df9553f
--- /dev/null
+++ b/test/System.Net.Http.Formatting.Test.Unit/Formatting/MediaTypeConstantsTests.cs
@@ -0,0 +1,74 @@
+using System.Net.Http.Formatting;
+using System.Net.Http.Headers;
+using System.Text;
+using Microsoft.TestCommon;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Net.Http
+{
+ public class MediaTypeConstantsTests
+ {
+ [Fact]
+ [Trait("Description", "Class is internal static type.")]
+ public void TypeIsCorrect()
+ {
+ Assert.Type.HasProperties(typeof(MediaTypeConstants), TypeAssert.TypeProperties.IsClass | TypeAssert.TypeProperties.IsStatic);
+ }
+
+
+ private static void ValidateClones(MediaTypeHeaderValue clone1, MediaTypeHeaderValue clone2, string charset)
+ {
+ Assert.NotNull(clone1);
+ Assert.NotNull(clone2);
+ Assert.NotSame(clone1, clone2);
+ Assert.Equal(clone1.MediaType, clone2.MediaType);
+ Assert.Equal(charset, clone1.CharSet);
+ Assert.Equal(charset, clone2.CharSet);
+ }
+
+ [Fact]
+ [Trait("Description", "HtmlMediaType returns clone")]
+ public void HtmlMediaTypeReturnsClone()
+ {
+ ValidateClones(MediaTypeConstants.HtmlMediaType, MediaTypeConstants.HtmlMediaType, Encoding.UTF8.WebName);
+ }
+
+ [Fact]
+ [Trait("Description", "ApplicationXmlMediaType returns clone")]
+ public void ApplicationXmlMediaTypeReturnsClone()
+ {
+ ValidateClones(MediaTypeConstants.ApplicationXmlMediaType, MediaTypeConstants.ApplicationXmlMediaType, null);
+ }
+
+ [Fact]
+ [Trait("Description", "ApplicationJsonMediaType returns clone")]
+ public void ApplicationJsonMediaTypeReturnsClone()
+ {
+ ValidateClones(MediaTypeConstants.ApplicationJsonMediaType, MediaTypeConstants.ApplicationJsonMediaType, null);
+ }
+
+ [Fact]
+ [Trait("Description", "TextXmlMediaType returns clone")]
+ public void TextXmlMediaTypeReturnsClone()
+ {
+ ValidateClones(MediaTypeConstants.TextXmlMediaType, MediaTypeConstants.TextXmlMediaType, null);
+ }
+
+ [Fact]
+ [Trait("Description", "TextJsonMediaType returns clone")]
+ public void TextJsonMediaTypeReturnsClone()
+ {
+ ValidateClones(MediaTypeConstants.TextJsonMediaType, MediaTypeConstants.TextJsonMediaType, null);
+ }
+
+ [Fact]
+ [Trait("Description", "ApplicationFormUrlEncodedMediaType returns clone")]
+ public void ApplicationFormUrlEncodedMediaTypeReturnsClone()
+ {
+ ValidateClones(MediaTypeConstants.ApplicationFormUrlEncodedMediaType, MediaTypeConstants.ApplicationFormUrlEncodedMediaType, null);
+ }
+
+
+ }
+}
diff --git a/test/System.Net.Http.Formatting.Test.Unit/Formatting/MediaTypeFormatterCollectionTests.cs b/test/System.Net.Http.Formatting.Test.Unit/Formatting/MediaTypeFormatterCollectionTests.cs
new file mode 100644
index 00000000..c713e2d4
--- /dev/null
+++ b/test/System.Net.Http.Formatting.Test.Unit/Formatting/MediaTypeFormatterCollectionTests.cs
@@ -0,0 +1,222 @@
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Linq;
+using System.Net.Http.Formatting.DataSets;
+using Microsoft.TestCommon;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Net.Http.Formatting
+{
+ public class MediaTypeFormatterCollectionTests
+ {
+
+ [Fact]
+ [Trait("Description", "MediaTypeFormatterCollection is public, concrete, and unsealed.")]
+ public void TypeIsCorrect()
+ {
+ Assert.Type.HasProperties(typeof(MediaTypeFormatterCollection), TypeAssert.TypeProperties.IsPublicVisibleClass, typeof(Collection<MediaTypeFormatter>));
+ }
+
+ [Fact]
+ [Trait("Description", "MediaTypeFormatterCollection() initializes default formatters.")]
+ public void Constructor()
+ {
+ MediaTypeFormatterCollection collection = new MediaTypeFormatterCollection();
+ Assert.Equal(3, collection.Count);
+ Assert.NotNull(collection.XmlFormatter);
+ Assert.NotNull(collection.JsonFormatter);
+ Assert.NotNull(collection.FormUrlEncodedFormatter);
+ }
+
+ [Fact]
+ [Trait("Description", "MediaTypeFormatterCollection(IEnumerable<MediaTypeFormatter>) accepts empty collection and does not add to it.")]
+ public void Constructor1AcceptsEmptyList()
+ {
+ MediaTypeFormatterCollection collection = new MediaTypeFormatterCollection(new MediaTypeFormatter[0]);
+ Assert.Equal(0, collection.Count);
+ }
+
+ [Fact]
+ [Trait("Description", "MediaTypeFormatterCollection(IEnumerable<MediaTypeFormatter>) sets XmlFormatter and JsonFormatter for all known collections of formatters that contain them.")]
+ public void Constructor1SetsProperties()
+ {
+ // All combination of formatters presented to ctor should still set XmlFormatter
+ foreach (IEnumerable<MediaTypeFormatter> formatterCollection in HttpUnitTestDataSets.AllFormatterCollections)
+ {
+ MediaTypeFormatterCollection collection = new MediaTypeFormatterCollection(formatterCollection);
+ if (collection.OfType<XmlMediaTypeFormatter>().Any())
+ {
+ Assert.NotNull(collection.XmlFormatter);
+ }
+ else
+ {
+ Assert.Null(collection.XmlFormatter);
+ }
+
+ if (collection.OfType<JsonMediaTypeFormatter>().Any())
+ {
+ Assert.NotNull(collection.JsonFormatter);
+ }
+ else
+ {
+ Assert.Null(collection.JsonFormatter);
+ }
+ }
+ }
+
+ [Fact]
+ [Trait("Description", "MediaTypeFormatterCollection(IEnumerable<MediaTypeFormatter>) sets derived classes of Xml and Json formatters.")]
+ public void Constructor1SetsDerivedFormatters()
+ {
+ // force to array to get stable instances
+ MediaTypeFormatter[] derivedFormatters = HttpUnitTestDataSets.DerivedFormatters.ToArray();
+ MediaTypeFormatterCollection collection = new MediaTypeFormatterCollection(derivedFormatters);
+ Assert.True(derivedFormatters.SequenceEqual(collection));
+ }
+
+ [Fact]
+ [Trait("Description", "MediaTypeFormatterCollection(IEnumerable<MediaTypeFormatter>) throws with null formatters collection.")]
+ public void Constructor1ThrowsWithNullFormatters()
+ {
+ Assert.ThrowsArgumentNull(() => new MediaTypeFormatterCollection(null), "formatters");
+ }
+
+ [Fact]
+ [Trait("Description", "MediaTypeFormatterCollection(IEnumerable<MediaTypeFormatter>) throws with null formatter in formatters collection.")]
+ public void Constructor1ThrowsWithNullFormatterInCollection()
+ {
+ Assert.ThrowsArgument(
+ () => new MediaTypeFormatterCollection(new MediaTypeFormatter[] { null }), "formatters",
+ RS.Format(Properties.Resources.CannotHaveNullInList,
+ typeof(MediaTypeFormatter).Name));
+ }
+
+ [Fact]
+ [Trait("Description", "MediaTypeFormatterCollection(IEnumerable<MediaTypeFormatter>) accepts multiple instances of same formatter type.")]
+ public void Constructor1AcceptsDuplicateFormatterTypes()
+ {
+ MediaTypeFormatter[] formatters = new MediaTypeFormatter[]
+ {
+ new XmlMediaTypeFormatter(),
+ new JsonMediaTypeFormatter(),
+ new FormUrlEncodedMediaTypeFormatter(),
+ new XmlMediaTypeFormatter(),
+ new JsonMediaTypeFormatter(),
+ new FormUrlEncodedMediaTypeFormatter(),
+ };
+
+ MediaTypeFormatterCollection collection = new MediaTypeFormatterCollection(formatters);
+ Assert.True(formatters.SequenceEqual(collection));
+ }
+
+ [Fact]
+ [Trait("Description", "XmlFormatter is set by ctor.")]
+ public void XmlFormatterSetByCtor()
+ {
+ XmlMediaTypeFormatter formatter = new XmlMediaTypeFormatter();
+ MediaTypeFormatterCollection collection = new MediaTypeFormatterCollection(new MediaTypeFormatter[] { formatter });
+ Assert.Same(formatter, collection.XmlFormatter);
+ }
+
+ [Fact]
+ [Trait("Description", "XmlFormatter is cleared by ctor with empty collection.")]
+ public void XmlFormatterClearedByCtor()
+ {
+ MediaTypeFormatterCollection collection = new MediaTypeFormatterCollection(new MediaTypeFormatter[0]);
+ Assert.Null(collection.XmlFormatter);
+ }
+
+
+
+ [Fact]
+ [Trait("Description", "JsonFormatter is set by ctor.")]
+ public void JsonFormatterSetByCtor()
+ {
+ JsonMediaTypeFormatter formatter = new JsonMediaTypeFormatter();
+ MediaTypeFormatterCollection collection = new MediaTypeFormatterCollection(new MediaTypeFormatter[] { formatter });
+ Assert.Same(formatter, collection.JsonFormatter);
+ }
+
+ [Fact]
+ [Trait("Description", "JsonFormatter is cleared by ctor with empty collection.")]
+ public void JsonFormatterClearedByCtor()
+ {
+ MediaTypeFormatterCollection collection = new MediaTypeFormatterCollection(new MediaTypeFormatter[0]);
+ Assert.Null(collection.JsonFormatter);
+ }
+
+
+
+
+ [Fact]
+ [Trait("Description", "FormUrlEncodedFormatter is set by ctor.")]
+ public void FormUrlEncodedFormatterSetByCtor()
+ {
+ FormUrlEncodedMediaTypeFormatter formatter = new FormUrlEncodedMediaTypeFormatter();
+ MediaTypeFormatterCollection collection = new MediaTypeFormatterCollection(new MediaTypeFormatter[] { formatter });
+ Assert.Same(formatter, collection.FormUrlEncodedFormatter);
+ }
+
+ [Fact]
+ [Trait("Description", "FormUrlEncodedFormatter is cleared by ctor with empty collection.")]
+ public void FormUrlEncodedFormatterClearedByCtor()
+ {
+ MediaTypeFormatterCollection collection = new MediaTypeFormatterCollection(new MediaTypeFormatter[0]);
+ Assert.Null(collection.FormUrlEncodedFormatter);
+ }
+
+
+
+
+
+
+ [Fact]
+ [Trait("Description", "Remove(MediaTypeFormatter) sets XmlFormatter to null.")]
+ public void RemoveSetsXmlFormatter()
+ {
+ MediaTypeFormatterCollection collection = new MediaTypeFormatterCollection();
+ int count = collection.Count;
+ collection.Remove(collection.XmlFormatter);
+ Assert.Null(collection.XmlFormatter);
+ Assert.Equal(count - 1, collection.Count);
+ }
+
+ [Fact]
+ [Trait("Description", "Remove(MediaTypeFormatter) sets JsonFormatter to null.")]
+ public void RemoveSetsJsonFormatter()
+ {
+ MediaTypeFormatterCollection collection = new MediaTypeFormatterCollection();
+ int count = collection.Count;
+ collection.Remove(collection.JsonFormatter);
+ Assert.Null(collection.JsonFormatter);
+ Assert.Equal(count - 1, collection.Count);
+ }
+
+ [Fact]
+ [Trait("Description", "Insert(int, MediaTypeFormatter) sets XmlFormatter.")]
+ public void InsertSetsXmlFormatter()
+ {
+ MediaTypeFormatterCollection collection = new MediaTypeFormatterCollection();
+ int count = collection.Count;
+ XmlMediaTypeFormatter formatter = new XmlMediaTypeFormatter();
+ collection.Insert(0, formatter);
+ Assert.Same(formatter, collection.XmlFormatter);
+ Assert.Equal(count + 1, collection.Count);
+ }
+
+ [Fact]
+ [Trait("Description", "Insert(int, MediaTypeFormatter) sets JsonFormatter.")]
+
+ public void InsertSetsJsonFormatter()
+ {
+ MediaTypeFormatterCollection collection = new MediaTypeFormatterCollection();
+ int count = collection.Count;
+ JsonMediaTypeFormatter formatter = new JsonMediaTypeFormatter();
+ collection.Insert(0, formatter);
+ Assert.Same(formatter, collection.JsonFormatter);
+ Assert.Equal(count + 1, collection.Count);
+ }
+
+ }
+}
diff --git a/test/System.Net.Http.Formatting.Test.Unit/Formatting/MediaTypeFormatterExtensionsTests.cs b/test/System.Net.Http.Formatting.Test.Unit/Formatting/MediaTypeFormatterExtensionsTests.cs
new file mode 100644
index 00000000..8a285c25
--- /dev/null
+++ b/test/System.Net.Http.Formatting.Test.Unit/Formatting/MediaTypeFormatterExtensionsTests.cs
@@ -0,0 +1,120 @@
+using System.Collections.Generic;
+using System.Linq;
+using System.Net.Http.Formatting.Mocks;
+using System.Net.Http.Headers;
+using Microsoft.TestCommon;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Net.Http.Formatting
+{
+ public class MediaTypeFormatterExtensionsTests
+ {
+ [Fact]
+ [Trait("Description", "MediaTypeFormatterExtensionMethods is public and static.")]
+ public void TypeIsCorrect()
+ {
+ Assert.Type.HasProperties(typeof(MediaTypeFormatterExtensions), TypeAssert.TypeProperties.IsPublicVisibleClass | TypeAssert.TypeProperties.IsStatic);
+ }
+
+ [Fact]
+ [Trait("Description", "AddQueryStringMapping(MediaTypeFormatter, string, string, MediaTypeHeaderValue) throws for null 'this'.")]
+ public void AddQueryStringMappingThrowsWithNullThis()
+ {
+ MediaTypeFormatter formatter = null;
+ Assert.ThrowsArgumentNull(() => formatter.AddQueryStringMapping("name", "value", new MediaTypeHeaderValue("application/xml")), "formatter");
+ }
+
+ [Fact]
+ [Trait("Description", "AddQueryStringMapping(MediaTypeFormatter, string, string, string) throws for null 'this'.")]
+ public void AddQueryStringMapping1ThrowsWithNullThis()
+ {
+ MediaTypeFormatter formatter = null;
+ Assert.ThrowsArgumentNull(() => formatter.AddQueryStringMapping("name", "value", "application/xml"), "formatter");
+ }
+
+ [Fact]
+ [Trait("Description", "AddUriPathExtensionMapping(MediaTypeFormatter, string, MediaTypeHeaderValue) throws for null 'this'.")]
+ public void AddUriPathExtensionMappingThrowsWithNullThis()
+ {
+ MediaTypeFormatter formatter = null;
+ Assert.ThrowsArgumentNull(() => formatter.AddUriPathExtensionMapping("xml", new MediaTypeHeaderValue("application/xml")), "formatter");
+ }
+
+ [Fact]
+ [Trait("Description", "AddUriPathExtensionMapping(MediaTypeFormatter, string, string) throws for null 'this'.")]
+ public void AddUriPathExtensionMapping1ThrowsWithNullThis()
+ {
+ MediaTypeFormatter formatter = null;
+ Assert.ThrowsArgumentNull(() => formatter.AddUriPathExtensionMapping("xml", "application/xml"), "formatter");
+ }
+
+ [Fact]
+ [Trait("Description", "AddMediaRangeMapping(MediaTypeFormatter, MediaTypeHeaderValue, MediaTypeHeaderValue) throws for null 'this'.")]
+ public void AddMediaRangeMappingThrowsWithNullThis()
+ {
+ MediaTypeFormatter formatter = null;
+ Assert.ThrowsArgumentNull(() => formatter.AddMediaRangeMapping(new MediaTypeHeaderValue("application/*"), new MediaTypeHeaderValue("application/xml")), "formatter");
+ }
+
+ [Fact]
+ [Trait("Description", "AddMediaRangeMapping(MediaTypeFormatter, string, string) throws for null 'this'.")]
+ public void AddMediaRangeMapping1ThrowsWithNullThis()
+ {
+ MediaTypeFormatter formatter = null;
+ Assert.ThrowsArgumentNull(() => formatter.AddMediaRangeMapping("application/*", "application/xml"), "formatter");
+ }
+
+
+
+ [Fact]
+ [Trait("Description", "AddRequestHeaderMapping(MediaTypeFormatter, string, string, StringComparison, bool, MediaTypeHeaderValue) throws for null 'this'.")]
+ public void AddRequestHeaderMappingThrowsWithNullThis()
+ {
+ MediaTypeFormatter formatter = null;
+ Assert.ThrowsArgumentNull(() => formatter.AddRequestHeaderMapping("name", "value", StringComparison.CurrentCulture, true, new MediaTypeHeaderValue("application/xml")), "formatter");
+ }
+
+ [Fact]
+ [Trait("Description", "AddRequestHeaderMapping(MediaTypeFormatter, string, string, StringComparison, bool, MediaTypeHeaderValue) adds formatter on 'this'.")]
+ public void AddRequestHeaderMappingAddsSuccessfully()
+ {
+ MediaTypeFormatter formatter = new MockMediaTypeFormatter();
+ Assert.Equal(0, formatter.MediaTypeMappings.Count);
+ formatter.AddRequestHeaderMapping("name", "value", StringComparison.CurrentCulture, true, new MediaTypeHeaderValue("application/xml"));
+ IEnumerable<RequestHeaderMapping> mappings = formatter.MediaTypeMappings.OfType<RequestHeaderMapping>();
+ Assert.Equal(1, mappings.Count());
+ RequestHeaderMapping mapping = mappings.ElementAt(0);
+ Assert.Equal("name", mapping.HeaderName);
+ Assert.Equal("value", mapping.HeaderValue);
+ Assert.Equal(StringComparison.CurrentCulture, mapping.HeaderValueComparison);
+ Assert.Equal(true, mapping.IsValueSubstring);
+ Assert.Equal(new MediaTypeHeaderValue("application/xml"), mapping.MediaType);
+ }
+
+ [Fact]
+ [Trait("Description", "AddRequestHeaderMapping(MediaTypeFormatter, string, string, StringComparison, bool, string) throws for null 'this'.")]
+ public void AddRequestHeaderMapping1ThrowsWithNullThis()
+ {
+ MediaTypeFormatter formatter = null;
+ Assert.ThrowsArgumentNull(() => formatter.AddRequestHeaderMapping("name", "value", StringComparison.CurrentCulture, true, "application/xml"), "formatter");
+ }
+
+ [Fact]
+ [Trait("Description", "AddRequestHeaderMapping(MediaTypeFormatter, string, string, StringComparison, bool, string) adds formatter on 'this'.")]
+ public void AddRequestHeaderMapping1AddsSuccessfully()
+ {
+ MediaTypeFormatter formatter = new MockMediaTypeFormatter();
+ Assert.Equal(0, formatter.MediaTypeMappings.Count);
+ formatter.AddRequestHeaderMapping("name", "value", StringComparison.CurrentCulture, true, "application/xml");
+ IEnumerable<RequestHeaderMapping> mappings = formatter.MediaTypeMappings.OfType<RequestHeaderMapping>();
+ Assert.Equal(1, mappings.Count());
+ RequestHeaderMapping mapping = mappings.ElementAt(0);
+ Assert.Equal("name", mapping.HeaderName);
+ Assert.Equal("value", mapping.HeaderValue);
+ Assert.Equal(StringComparison.CurrentCulture, mapping.HeaderValueComparison);
+ Assert.Equal(true, mapping.IsValueSubstring);
+ Assert.Equal(new MediaTypeHeaderValue("application/xml"), mapping.MediaType);
+ }
+ }
+}
diff --git a/test/System.Net.Http.Formatting.Test.Unit/Formatting/MediaTypeFormatterTests.cs b/test/System.Net.Http.Formatting.Test.Unit/Formatting/MediaTypeFormatterTests.cs
new file mode 100644
index 00000000..f8476d5b
--- /dev/null
+++ b/test/System.Net.Http.Formatting.Test.Unit/Formatting/MediaTypeFormatterTests.cs
@@ -0,0 +1,401 @@
+using System.Collections.ObjectModel;
+using System.Linq;
+using System.Net.Http.Formatting.DataSets;
+using System.Net.Http.Formatting.Mocks;
+using System.Net.Http.Headers;
+using Microsoft.TestCommon;
+using Moq;
+using Xunit;
+using Xunit.Extensions;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Net.Http.Formatting
+{
+ public class MediaTypeFormatterTests
+ {
+ [Fact]
+ [Trait("Description", "MediaTypeFormatter is public, abstract, and unsealed.")]
+ public void TypeIsCorrect()
+ {
+ Assert.Type.HasProperties(typeof(MediaTypeFormatter), TypeAssert.TypeProperties.IsPublicVisibleClass | TypeAssert.TypeProperties.IsAbstract);
+ }
+
+ [Fact]
+ [Trait("Description", "MediaTypeFormatter() constructor (via derived class) sets SupportedMediaTypes and MediaTypeMappings.")]
+ public void Constructor()
+ {
+ MockMediaTypeFormatter formatter = new MockMediaTypeFormatter();
+ Collection<MediaTypeHeaderValue> supportedMediaTypes = formatter.SupportedMediaTypes;
+ Assert.NotNull(supportedMediaTypes);
+ Assert.Equal(0, supportedMediaTypes.Count);
+
+ Collection<MediaTypeMapping> mappings = formatter.MediaTypeMappings;
+ Assert.NotNull(mappings);
+ Assert.Equal(0, mappings.Count);
+ }
+
+ [Fact]
+ [Trait("Description", "SupportedMediaTypes is a mutable collection.")]
+ public void SupportedMediaTypesIsMutable()
+ {
+ MockMediaTypeFormatter formatter = new MockMediaTypeFormatter();
+ Collection<MediaTypeHeaderValue> supportedMediaTypes = formatter.SupportedMediaTypes;
+ MediaTypeHeaderValue[] mediaTypes = HttpUnitTestDataSets.LegalMediaTypeHeaderValues.ToArray();
+ foreach (MediaTypeHeaderValue mediaType in mediaTypes)
+ {
+ supportedMediaTypes.Add(mediaType);
+ }
+
+ Assert.True(mediaTypes.SequenceEqual(formatter.SupportedMediaTypes));
+ }
+
+ [Fact]
+ [Trait("Description", "SupportedMediaTypes Add throws with a null media type.")]
+ public void SupportedMediaTypesAddThrowsWithNullMediaType()
+ {
+ MockMediaTypeFormatter formatter = new MockMediaTypeFormatter();
+ Collection<MediaTypeHeaderValue> supportedMediaTypes = formatter.SupportedMediaTypes;
+
+ Assert.ThrowsArgumentNull(() => supportedMediaTypes.Add(null), "item");
+ }
+
+ [Theory]
+ [TestDataSet(typeof(HttpUnitTestDataSets), "LegalMediaRangeValues")]
+ [Trait("Description", "SupportedMediaTypes Add throws with a media range.")]
+ public void SupportedMediaTypesAddThrowsWithMediaRange(MediaTypeHeaderValue mediaType)
+ {
+ MockMediaTypeFormatter formatter = new MockMediaTypeFormatter();
+ Collection<MediaTypeHeaderValue> supportedMediaTypes = formatter.SupportedMediaTypes;
+ Assert.ThrowsArgument(() => supportedMediaTypes.Add(mediaType), "item", RS.Format(Properties.Resources.CannotUseMediaRangeForSupportedMediaType, typeof(MediaTypeHeaderValue).Name, mediaType.MediaType));
+ }
+
+ [Fact]
+ [Trait("Description", "SupportedMediaTypes Insert throws with a null media type.")]
+ public void SupportedMediaTypesInsertThrowsWithNullMediaType()
+ {
+ MockMediaTypeFormatter formatter = new MockMediaTypeFormatter();
+ Collection<MediaTypeHeaderValue> supportedMediaTypes = formatter.SupportedMediaTypes;
+
+ Assert.ThrowsArgumentNull(() => supportedMediaTypes.Insert(0, null), "item");
+ }
+
+ [Theory]
+ [TestDataSet(typeof(HttpUnitTestDataSets), "LegalMediaRangeValues")]
+ [Trait("Description", "SupportedMediaTypes Insert throws with a media range.")]
+ public void SupportedMediaTypesInsertThrowsWithMediaRange(MediaTypeHeaderValue mediaType)
+ {
+ MockMediaTypeFormatter formatter = new MockMediaTypeFormatter();
+ Collection<MediaTypeHeaderValue> supportedMediaTypes = formatter.SupportedMediaTypes;
+
+ Assert.ThrowsArgument(() => supportedMediaTypes.Insert(0, mediaType), "item", RS.Format(Properties.Resources.CannotUseMediaRangeForSupportedMediaType, typeof(MediaTypeHeaderValue).Name, mediaType.MediaType));
+ }
+
+ [Fact]
+ [Trait("Description", "MediaTypeMappings is a mutable collection.")]
+ public void MediaTypeMappingsIsMutable()
+ {
+ MockMediaTypeFormatter formatter = new MockMediaTypeFormatter();
+ Collection<MediaTypeMapping> mappings = formatter.MediaTypeMappings;
+ MediaTypeMapping[] standardMappings = HttpUnitTestDataSets.StandardMediaTypeMappings.ToArray();
+ foreach (MediaTypeMapping mapping in standardMappings)
+ {
+ mappings.Add(mapping);
+ }
+
+ Assert.True(standardMappings.SequenceEqual(formatter.MediaTypeMappings));
+ }
+
+ [Theory]
+ [TestDataSet(typeof(HttpUnitTestDataSets), "StandardMediaTypesWithQuality")]
+ [Trait("Description", "TryMatchSupportedMediaType(MediaTypeHeaderValue, out MediaTypeMatch) returns media type and quality.")]
+ public void TryMatchSupportedMediaTypeWithQuality(MediaTypeWithQualityHeaderValue mediaTypeWithQuality)
+ {
+ MockMediaTypeFormatter formatter = new MockMediaTypeFormatter();
+ MediaTypeHeaderValue mediaTypeWithoutQuality = new MediaTypeHeaderValue(mediaTypeWithQuality.MediaType);
+ formatter.SupportedMediaTypes.Add(mediaTypeWithoutQuality);
+ MediaTypeMatch match;
+ bool result = formatter.TryMatchSupportedMediaType(mediaTypeWithQuality, out match);
+ Assert.True(result, String.Format("TryMatchSupportedMediaType should have succeeded for '{0}'.", mediaTypeWithQuality));
+ Assert.NotNull(match);
+ double quality = mediaTypeWithQuality.Quality.Value;
+ Assert.Equal(quality, match.Quality);
+ Assert.NotNull(match.MediaType);
+ Assert.Equal(mediaTypeWithoutQuality.MediaType, match.MediaType.MediaType);
+ }
+
+ [Theory]
+ [TestDataSet(typeof(HttpUnitTestDataSets), "StandardMediaTypesWithQuality")]
+ [Trait("Description", "TryMatchSupportedMediaType(MediaTypeHeaderValue, out MediaTypeMatch) returns cloned media type, not original.")]
+ public void TryMatchSupportedMediaTypeReturnsClone(MediaTypeWithQualityHeaderValue mediaTypeWithQuality)
+ {
+ MockMediaTypeFormatter formatter = new MockMediaTypeFormatter();
+ MediaTypeHeaderValue mediaTypeWithoutQuality = new MediaTypeHeaderValue(mediaTypeWithQuality.MediaType);
+ formatter.SupportedMediaTypes.Add(mediaTypeWithoutQuality);
+ MediaTypeMatch match;
+ bool result = formatter.TryMatchSupportedMediaType(mediaTypeWithQuality, out match);
+
+ Assert.True(result);
+ Assert.NotNull(match);
+ Assert.NotNull(match.MediaType);
+ Assert.NotSame(mediaTypeWithoutQuality, match.MediaType);
+ }
+
+ [Theory]
+ [TestDataSet(typeof(HttpUnitTestDataSets), "MediaRangeValuesWithQuality")]
+ [Trait("Description", "TryMatchMediaTypeMapping(HttpRequestMessage, out MediaTypeMatch) returns media type and quality from media range with quality.")]
+ public void TryMatchMediaTypeMappingWithQuality(MediaTypeWithQualityHeaderValue mediaRangeWithQuality)
+ {
+ MockMediaTypeFormatter formatter = new MockMediaTypeFormatter();
+ MediaTypeHeaderValue mediaRangeWithoutQuality = new MediaTypeHeaderValue(mediaRangeWithQuality.MediaType);
+ MediaTypeHeaderValue mediaType = new MediaTypeHeaderValue("application/xml");
+ MediaRangeMapping mapping = new MediaRangeMapping(mediaRangeWithoutQuality, mediaType);
+ formatter.MediaTypeMappings.Add(mapping);
+
+ HttpRequestMessage request = new HttpRequestMessage();
+ request.Headers.Accept.Add(mediaRangeWithQuality);
+ MediaTypeMatch match;
+ bool result = formatter.TryMatchMediaTypeMapping(request, out match);
+ Assert.True(result, String.Format("TryMatchMediaTypeMapping should have succeeded for '{0}'.", mediaRangeWithQuality));
+ Assert.NotNull(match);
+ double quality = mediaRangeWithQuality.Quality.Value;
+ Assert.Equal(quality, match.Quality);
+ Assert.NotNull(match.MediaType);
+ Assert.Equal(mediaType.MediaType, match.MediaType.MediaType);
+ }
+
+ [Theory]
+ [TestDataSet(typeof(HttpUnitTestDataSets), "MediaRangeValuesWithQuality")]
+ [Trait("Description", "TryMatchMediaTypeMapping(HttpRequestMessage, out MediaTypeMatch) returns a clone of the original media type.")]
+ public void TryMatchMediaTypeMappingClonesMediaType(MediaTypeWithQualityHeaderValue mediaRangeWithQuality)
+ {
+ MockMediaTypeFormatter formatter = new MockMediaTypeFormatter();
+ MediaTypeHeaderValue mediaRangeWithoutQuality = new MediaTypeHeaderValue(mediaRangeWithQuality.MediaType);
+ MediaTypeHeaderValue mediaType = new MediaTypeHeaderValue("application/xml");
+ MediaRangeMapping mapping = new MediaRangeMapping(mediaRangeWithoutQuality, mediaType);
+ formatter.MediaTypeMappings.Add(mapping);
+
+ HttpRequestMessage request = new HttpRequestMessage();
+ request.Headers.Accept.Add(mediaRangeWithQuality);
+ MediaTypeMatch match;
+ formatter.TryMatchMediaTypeMapping(request, out match);
+ Assert.NotNull(match);
+ Assert.NotNull(match.MediaType);
+ Assert.NotSame(mediaType, match.MediaType);
+ }
+
+ [Fact]
+ [Trait("Description", "SelectResponseMediaType(Type, HttpRequestMessage) matches based only on type.")]
+ public void SelectResponseMediaTypeMatchesType()
+ {
+ MockMediaTypeFormatter formatter = new MockMediaTypeFormatter() { CallBase = true };
+ HttpRequestMessage request = new HttpRequestMessage();
+ ResponseMediaTypeMatch match = formatter.SelectResponseMediaType(typeof(string), request);
+
+ Assert.NotNull(match);
+ Assert.Equal(ResponseFormatterSelectionResult.MatchOnCanWriteType, match.ResponseFormatterSelectionResult);
+ Assert.Null(match.MediaTypeMatch.MediaType);
+ }
+
+ [Theory]
+ [TestDataSet(typeof(HttpUnitTestDataSets), "LegalMediaTypeHeaderValues")]
+ [Trait("Description", "SelectResponseMediaType(Type, HttpRequestMessage) matches media type from request content type.")]
+ public void SelectResponseMediaTypeMatchesRequestContentType(MediaTypeHeaderValue mediaType)
+ {
+ MockMediaTypeFormatter formatter = new MockMediaTypeFormatter() { CallBase = true };
+ formatter.SupportedMediaTypes.Add(mediaType);
+ HttpRequestMessage request = new HttpRequestMessage() { Content = new StringContent("fred") };
+ request.Content.Headers.ContentType = mediaType;
+ HttpResponseMessage response = new HttpResponseMessage() { RequestMessage = request };
+ ResponseMediaTypeMatch match = formatter.SelectResponseMediaType(typeof(string), request);
+
+ Assert.NotNull(match);
+ Assert.Equal(ResponseFormatterSelectionResult.MatchOnRequestContentType, match.ResponseFormatterSelectionResult);
+ Assert.NotNull(match.MediaTypeMatch.MediaType);
+ Assert.Equal(mediaType.MediaType, match.MediaTypeMatch.MediaType.MediaType);
+ }
+
+ [TestDataSet(typeof(HttpUnitTestDataSets), "LegalMediaTypeHeaderValues")]
+ [Trait("Description", "SelectResponseMediaType(Type, HttpRequestMessage) matches media type from response content type.")]
+ public void SelectResponseMediaTypeMatchesResponseContentType(MediaTypeHeaderValue mediaType)
+ {
+ MockMediaTypeFormatter formatter = new MockMediaTypeFormatter() { CallBase = true };
+ formatter.SupportedMediaTypes.Add(mediaType);
+ HttpRequestMessage request = new HttpRequestMessage();
+ HttpResponseMessage response = new HttpResponseMessage() { RequestMessage = request, Content = new StringContent("fred") };
+ response.Content.Headers.ContentType = mediaType;
+ ResponseMediaTypeMatch match = formatter.SelectResponseMediaType(typeof(string), request);
+
+ Assert.NotNull(match);
+ Assert.Equal(ResponseFormatterSelectionResult.MatchOnResponseContentType, match.ResponseFormatterSelectionResult);
+ Assert.NotNull(match.MediaTypeMatch.MediaType);
+ Assert.Equal(mediaType.MediaType, match.MediaTypeMatch.MediaType.MediaType);
+ }
+
+ [Theory]
+ [TestDataSet(typeof(HttpUnitTestDataSets), "StandardMediaTypesWithQuality")]
+ [Trait("Description", "SelectResponseMediaType(Type, HttpRequestMessage) matches supported media type from accept headers.")]
+ public void SelectResponseMediaTypeMatchesAcceptHeaderToSupportedMediaTypes(MediaTypeWithQualityHeaderValue mediaTypeWithQuality)
+ {
+ MockMediaTypeFormatter formatter = new MockMediaTypeFormatter() { CallBase = true };
+ MediaTypeHeaderValue mediaTypeWithoutQuality = new MediaTypeHeaderValue(mediaTypeWithQuality.MediaType);
+ formatter.SupportedMediaTypes.Add(mediaTypeWithoutQuality);
+
+ HttpRequestMessage request = new HttpRequestMessage();
+ request.Headers.Accept.Add(mediaTypeWithQuality);
+ ResponseMediaTypeMatch match = formatter.SelectResponseMediaType(typeof(string), request);
+
+ Assert.NotNull(match);
+ Assert.Equal(ResponseFormatterSelectionResult.MatchOnRequestAcceptHeader, match.ResponseFormatterSelectionResult);
+ double quality = mediaTypeWithQuality.Quality.Value;
+ Assert.Equal(quality, match.MediaTypeMatch.Quality);
+ Assert.NotNull(match.MediaTypeMatch.MediaType);
+ Assert.Equal(mediaTypeWithoutQuality.MediaType, match.MediaTypeMatch.MediaType.MediaType);
+ }
+
+ [TestDataSet(typeof(HttpUnitTestDataSets), "MediaRangeValuesWithQuality")]
+ [Trait("Description", "SelectResponseMediaType(Type, HttpRequestMessage) matches media type with quality from media type mapping.")]
+ public void SelectResponseMediaTypeMatchesAcceptHeaderWithMediaTypeMapping(MediaTypeWithQualityHeaderValue mediaRangeWithQuality)
+ {
+ MockMediaTypeFormatter formatter = new MockMediaTypeFormatter() { CallBase = true };
+ MediaTypeHeaderValue mediaRangeWithoutQuality = new MediaTypeHeaderValue(mediaRangeWithQuality.MediaType);
+ MediaTypeHeaderValue mediaType = new MediaTypeHeaderValue("application/xml");
+ MediaRangeMapping mapping = new MediaRangeMapping(mediaRangeWithoutQuality, mediaType);
+ formatter.MediaTypeMappings.Add(mapping);
+
+ HttpRequestMessage request = new HttpRequestMessage();
+ request.Headers.Accept.Add(mediaRangeWithQuality);
+ ResponseMediaTypeMatch match = formatter.SelectResponseMediaType(typeof(string), request);
+
+ Assert.NotNull(match);
+ Assert.Equal(ResponseFormatterSelectionResult.MatchOnRequestAcceptHeaderWithMediaTypeMapping, match.ResponseFormatterSelectionResult);
+ double quality = mediaRangeWithQuality.Quality.Value;
+ Assert.Equal(quality, match.MediaTypeMatch.Quality);
+ Assert.NotNull(match.MediaTypeMatch.MediaType);
+ Assert.Equal(mediaType.MediaType, match.MediaTypeMatch.MediaType.MediaType);
+ }
+
+ [Theory]
+ [TestDataSet(
+ typeof(CommonUnitTestDataSets), "RepresentativeValueAndRefTypeTestDataCollection",
+ typeof(HttpUnitTestDataSets), "LegalMediaTypeStrings")]
+ [Trait("Description", "CanReadAs(Type, MediaTypeHeaderValue) returns true for all standard media types.")]
+ public void CanReadAsReturnsTrue(Type variationType, object testData, string mediaType)
+ {
+ MockMediaTypeFormatter formatter = new MockMediaTypeFormatter() { CallBase = true };
+ string[] legalMediaTypeStrings = HttpUnitTestDataSets.LegalMediaTypeStrings.ToArray();
+ foreach (string legalMediaType in legalMediaTypeStrings)
+ {
+ formatter.SupportedMediaTypes.Add(new MediaTypeHeaderValue(legalMediaType));
+ }
+
+ MediaTypeHeaderValue contentType = new MediaTypeHeaderValue(mediaType);
+ Assert.True(formatter.CanReadAs(variationType, contentType));
+ }
+
+ [Fact]
+ [Trait("Description", "CanReadAs(Type, MediaTypeHeaderValue) throws with null type.")]
+ public void CanReadAsThrowsWithNullType()
+ {
+ MockMediaTypeFormatter formatter = new MockMediaTypeFormatter();
+ Assert.ThrowsArgumentNull(() => formatter.CanReadAs(type: null, mediaType: null), "type");
+ }
+
+ [Fact]
+ [Trait("Description", "CanReadAs(Type, MediaTypeHeaderValue) throws with null formatter context.")]
+ public void CanReadAsThrowsWithNullMediaType()
+ {
+ MockMediaTypeFormatter formatter = new MockMediaTypeFormatter();
+ Assert.ThrowsArgumentNull(() => formatter.CanReadAs(typeof(int), mediaType: null), "mediaType");
+ }
+
+ [Theory]
+ [TestDataSet(typeof(CommonUnitTestDataSets), "RepresentativeValueAndRefTypeTestDataCollection")]
+ [Trait("Description", "CanWriteAs(Type, MediaTypeHeaderValue, out MediaTypeHeaderValue) returns true always for supported media types.")]
+ public void CanWriteAsReturnsTrue(Type variationType, object testData)
+ {
+ MockMediaTypeFormatter formatter = new MockMediaTypeFormatter() { CallBase = true };
+ foreach (string mediaType in HttpUnitTestDataSets.LegalMediaTypeStrings)
+ {
+ formatter.SupportedMediaTypes.Add(new MediaTypeHeaderValue(mediaType));
+ }
+
+ MediaTypeHeaderValue matchedMediaType = null;
+ Assert.True(formatter.CanWriteAs(variationType, formatter.SupportedMediaTypes[0], out matchedMediaType));
+ }
+
+ [Fact]
+ [Trait("Description", "CanWriteAs(Type, MediaTypeHeaderValue, out MediaTypeHeaderValue) throws with null content.")]
+ public void CanWriteAsThrowsWithNullContent()
+ {
+ MockMediaTypeFormatter formatter = new MockMediaTypeFormatter();
+ MediaTypeHeaderValue mediaType = null;
+ Assert.ThrowsArgumentNull(() => formatter.CanWriteAs(typeof(int), null, out mediaType), "mediaType");
+ }
+
+ [Theory]
+ [TestDataSet(typeof(CommonUnitTestDataSets), "RepresentativeValueAndRefTypeTestDataCollection")]
+ [Trait("Description", "CanWriteAs(Type, MediaTypeHeaderValue, out MediaTypeHeaderValue) returns true always for supported media types.")]
+ public void CanWriteAsUsingRequestReturnsTrue(Type variationType, object testData)
+ {
+ MockMediaTypeFormatter formatter = new MockMediaTypeFormatter() { CallBase = true };
+ foreach (string mediaType in HttpUnitTestDataSets.LegalMediaTypeStrings)
+ {
+ formatter.SupportedMediaTypes.Add(new MediaTypeHeaderValue(mediaType));
+ }
+
+ MediaTypeHeaderValue matchedMediaType = null;
+ Assert.True(formatter.CanWriteAs(variationType, formatter.SupportedMediaTypes[0], out matchedMediaType));
+ }
+
+ [Theory]
+ [TestDataSet(
+ typeof(CommonUnitTestDataSets), "RepresentativeValueAndRefTypeTestDataCollection",
+ typeof(HttpUnitTestDataSets), "LegalMediaTypeStrings")]
+ [Trait("Description", "CanReadType(Type) base implementation returns true for all types.")]
+ public void CanReadTypeReturnsTrue(Type variationType, object testData, string mediaType)
+ {
+ MockMediaTypeFormatter formatter = new MockMediaTypeFormatter() { CallBase = true };
+ string[] legalMediaTypeStrings = HttpUnitTestDataSets.LegalMediaTypeStrings.ToArray();
+ foreach (string mediaTypeTmp in legalMediaTypeStrings)
+ {
+ formatter.SupportedMediaTypes.Add(new MediaTypeHeaderValue(mediaTypeTmp));
+ }
+
+ // Invoke CanReadAs because it invokes CanReadType
+ Assert.True(formatter.CanReadAs(variationType, new MediaTypeHeaderValue(mediaType)));
+ }
+
+ [Theory]
+ [TestDataSet(typeof(CommonUnitTestDataSets), "RepresentativeValueAndRefTypeTestDataCollection")]
+ [Trait("Description", "CanWriteType() base implementation returns true always.")]
+ public void CanWriteTypeReturnsTrue(Type variationType, object testData)
+ {
+ MockMediaTypeFormatter formatter = new MockMediaTypeFormatter() { CallBase = true };
+ foreach (string mediaType in HttpUnitTestDataSets.LegalMediaTypeStrings)
+ {
+ formatter.SupportedMediaTypes.Add(new MediaTypeHeaderValue(mediaType));
+ }
+
+ MediaTypeHeaderValue matchedMediaType = null;
+ Assert.True(formatter.CanWriteAs(variationType, formatter.SupportedMediaTypes[0], out matchedMediaType));
+ }
+
+ [Fact]
+ public void ReadFromStreamAsync_ThrowsNotSupportedException()
+ {
+ var formatter = new Mock<MediaTypeFormatter> { CallBase = true }.Object;
+
+ Assert.Throws<NotSupportedException>(() => formatter.ReadFromStreamAsync(null, null, null, null),
+ "The media type formatter of type 'Castle.Proxies.MediaTypeFormatterProxy' does not support reading since it does not implement the ReadFromStreamAsync method.");
+ }
+
+ [Fact]
+ public void WriteToStreamAsync_ThrowsNotSupportedException()
+ {
+ var formatter = new Mock<MediaTypeFormatter> { CallBase = true }.Object;
+
+ Assert.Throws<NotSupportedException>(() => formatter.WriteToStreamAsync(null, null, null, null, null),
+ "The media type formatter of type 'Castle.Proxies.MediaTypeFormatterProxy' does not support writing since it does not implement the WriteToStreamAsync method.");
+ }
+ }
+}
diff --git a/test/System.Net.Http.Formatting.Test.Unit/Formatting/MediaTypeHeadeValueComparerTests.cs b/test/System.Net.Http.Formatting.Test.Unit/Formatting/MediaTypeHeadeValueComparerTests.cs
new file mode 100644
index 00000000..0638d462
--- /dev/null
+++ b/test/System.Net.Http.Formatting.Test.Unit/Formatting/MediaTypeHeadeValueComparerTests.cs
@@ -0,0 +1,209 @@
+using System.Net.Http.Headers;
+using Xunit;
+
+namespace System.Net.Http.Formatting
+{
+ public class MediaTypeHeadeValueComparerTests
+ {
+
+ [Fact]
+ [Trait("Description", "MediaTypeHeadeValueComparer.Comparer returns same MediaTypeHeadeValueComparer instance each time.")]
+ public void Comparer_Returns_MediaTypeHeadeValueComparer()
+ {
+ MediaTypeHeaderValueComparer comparer1 = MediaTypeHeaderValueComparer.Comparer;
+ MediaTypeHeaderValueComparer comparer2 = MediaTypeHeaderValueComparer.Comparer;
+
+ Assert.NotNull(comparer1);
+ Assert.Same(comparer1, comparer2);
+ }
+
+ [Fact]
+ [Trait("Description", "MediaTypeHeadeValueComparer.Compare returns 0 for same MediaTypeHeaderValue instance.")]
+ public void Compare_Returns_0_For_Same_MediaTypeHeaderValue()
+ {
+ MediaTypeHeaderValueComparer comparer = MediaTypeHeaderValueComparer.Comparer;
+ MediaTypeHeaderValue mediaType = new MediaTypeHeaderValue("text/xml");
+
+ Assert.Equal(0, comparer.Compare(mediaType, mediaType));
+ }
+
+ [Fact]
+ [Trait("Description", "MediaTypeHeadeValueComparer.Compare returns 0 for MediaTypeHeaderValue instances that differ only by case.")]
+ public void Compare_Returns_0_For_MediaTypeHeaderValues_Differing_Only_By_Case()
+ {
+ MediaTypeHeaderValueComparer comparer = MediaTypeHeaderValueComparer.Comparer;
+
+ MediaTypeHeaderValue mediaType1 = new MediaTypeHeaderValue("text/Xml");
+ MediaTypeHeaderValue mediaType2 = new MediaTypeHeaderValue("texT/xml");
+ Assert.Equal(0, comparer.Compare(mediaType1, mediaType2));
+
+ mediaType1 = new MediaTypeHeaderValue("application/*");
+ mediaType2 = new MediaTypeHeaderValue("APPLICATION/*");
+ Assert.Equal(0, comparer.Compare(mediaType1, mediaType2));
+
+ mediaType1 = new MediaTypeHeaderValue("*/*");
+ mediaType2 = new MediaTypeHeaderValue("*/*");
+ Assert.Equal(0, comparer.Compare(mediaType1, mediaType2));
+ }
+
+ [Fact]
+ [Trait("Description", "MediaTypeHeadeValueComparer.Compare returns 0 for MediaTypeHeaderValue instances that differ by non-q-value parameters.")]
+ public void Compare_Returns_0_For_MediaTypeHeaderValues_Differ_By_Non_Q_Parameters()
+ {
+ MediaTypeHeaderValueComparer comparer = MediaTypeHeaderValueComparer.Comparer;
+
+ MediaTypeHeaderValue mediaType1 = new MediaTypeHeaderValue("*/*");
+ mediaType1.CharSet = "someCharset";
+ mediaType1.Parameters.Add(new NameValueHeaderValue("someName", "someValue"));
+ MediaTypeHeaderValue mediaType2 = new MediaTypeHeaderValue("*/*");
+ mediaType2.CharSet = "someOtherCharset";
+ mediaType2.Parameters.Add(new NameValueHeaderValue("someName", "someOtherValue"));
+ Assert.Equal(0, comparer.Compare(mediaType1, mediaType2));
+ }
+
+ [Fact]
+ [Trait("Description", "MediaTypeHeadeValueComparer.Compare returns 0 for MediaTypeHeaderValue with the same Q when the Media types are not media ranges or are the same media ranges.")]
+ public void Compare_Returns_0_For_MediaTypeHeaderValues_With_Same_Q_Value()
+ {
+ MediaTypeHeaderValueComparer comparer = MediaTypeHeaderValueComparer.Comparer;
+
+ MediaTypeWithQualityHeaderValue mediaType1 = new MediaTypeWithQualityHeaderValue("text/xml", 0.5);
+ MediaTypeWithQualityHeaderValue mediaType2 = new MediaTypeWithQualityHeaderValue("text/xml", 0.50);
+ Assert.Equal(0, comparer.Compare(mediaType1, mediaType2));
+
+ mediaType1 = new MediaTypeWithQualityHeaderValue("text/xml", .7);
+ mediaType2 = new MediaTypeWithQualityHeaderValue("application/xml", .7);
+ Assert.Equal(0, comparer.Compare(mediaType1, mediaType2));
+
+ mediaType1 = new MediaTypeWithQualityHeaderValue("text/xml");
+ mediaType2 = new MediaTypeWithQualityHeaderValue("text/xml");
+ Assert.Equal(0, comparer.Compare(mediaType1, mediaType2));
+
+ mediaType1 = new MediaTypeWithQualityHeaderValue("text/xml");
+ mediaType2 = new MediaTypeWithQualityHeaderValue("text/plain");
+ Assert.Equal(0, comparer.Compare(mediaType1, mediaType2));
+
+ mediaType1 = new MediaTypeWithQualityHeaderValue("text/*", 0.3);
+ mediaType2 = new MediaTypeWithQualityHeaderValue("text/*", .3);
+ Assert.Equal(0, comparer.Compare(mediaType1, mediaType2));
+
+ mediaType1 = new MediaTypeWithQualityHeaderValue("*/*");
+ mediaType2 = new MediaTypeWithQualityHeaderValue("*/*");
+ Assert.Equal(0, comparer.Compare(mediaType1, mediaType2));
+
+ mediaType1 = new MediaTypeWithQualityHeaderValue("text/*", .1);
+ mediaType2 = new MediaTypeWithQualityHeaderValue("application/xml", .1);
+ Assert.Equal(0, comparer.Compare(mediaType1, mediaType2));
+ }
+
+ [Fact]
+ [Trait("Description", "MediaTypeHeadeValueComparer.Compare returns 1 if the first parameter has a smaller Q value.")]
+ public void Compare_Returns_1_If_MediaType1_Has_Smaller_Q_Value()
+ {
+ MediaTypeHeaderValueComparer comparer = MediaTypeHeaderValueComparer.Comparer;
+
+ MediaTypeWithQualityHeaderValue mediaType1 = new MediaTypeWithQualityHeaderValue("text/xml", 0.49);
+ MediaTypeWithQualityHeaderValue mediaType2 = new MediaTypeWithQualityHeaderValue("text/xml", 0.50);
+ Assert.Equal(1, comparer.Compare(mediaType1, mediaType2));
+
+ mediaType1 = new MediaTypeWithQualityHeaderValue("text/xml", .0);
+ mediaType2 = new MediaTypeWithQualityHeaderValue("application/xml", .7);
+ Assert.Equal(1, comparer.Compare(mediaType1, mediaType2));
+
+ mediaType1 = new MediaTypeWithQualityHeaderValue("text/xml", 0.9);
+ mediaType2 = new MediaTypeWithQualityHeaderValue("text/xml");
+ Assert.Equal(1, comparer.Compare(mediaType1, mediaType2));
+
+ mediaType1 = new MediaTypeWithQualityHeaderValue("text/xml", 0);
+ mediaType2 = new MediaTypeWithQualityHeaderValue("text/plain", 0.1);
+ Assert.Equal(1, comparer.Compare(mediaType1, mediaType2));
+
+ mediaType1 = new MediaTypeWithQualityHeaderValue("*/*", 0.3);
+ mediaType2 = new MediaTypeWithQualityHeaderValue("text/*", .31);
+ Assert.Equal(1, comparer.Compare(mediaType1, mediaType2));
+
+ mediaType1 = new MediaTypeWithQualityHeaderValue("text/*", 0.5);
+ mediaType2 = new MediaTypeWithQualityHeaderValue("*/*", 0.6);
+ Assert.Equal(1, comparer.Compare(mediaType1, mediaType2));
+ }
+
+ [Fact]
+ [Trait("Description", "MediaTypeHeadeValueComparer.Compare returns 1 if the Q values are the same but the first parameter is a less specific media range.")]
+ public void Compare_Returns_1_If_Q_Value_Is_Same_But_MediaType1_Is_Media_Range()
+ {
+ MediaTypeHeaderValueComparer comparer = MediaTypeHeaderValueComparer.Comparer;
+
+ MediaTypeWithQualityHeaderValue mediaType1 = new MediaTypeWithQualityHeaderValue("text/*", 0.50);
+ MediaTypeWithQualityHeaderValue mediaType2 = new MediaTypeWithQualityHeaderValue("text/xml", 0.50);
+ Assert.Equal(1, comparer.Compare(mediaType1, mediaType2));
+
+ mediaType1 = new MediaTypeWithQualityHeaderValue("*/*");
+ mediaType2 = new MediaTypeWithQualityHeaderValue("text/xml");
+ Assert.Equal(1, comparer.Compare(mediaType1, mediaType2));
+
+ mediaType1 = new MediaTypeWithQualityHeaderValue("*/*", 0.2);
+ mediaType2 = new MediaTypeWithQualityHeaderValue("text/*", 0.2);
+ Assert.Equal(1, comparer.Compare(mediaType1, mediaType2));
+
+ mediaType1 = new MediaTypeWithQualityHeaderValue("application/json", 0.2);
+ mediaType2 = new MediaTypeWithQualityHeaderValue("application/json", 0.2);
+ mediaType2.CharSet = "someCharSet";
+ Assert.Equal(1, comparer.Compare(mediaType1, mediaType2));
+ }
+
+ [Fact]
+ [Trait("Description", "MediaTypeHeadeValueComparer.Compare returns -1 if the first parameter has a larger Q value.")]
+ public void Compare_Returns_Negative_1_If_MediaType1_Has_Larger_Q_Value()
+ {
+ MediaTypeHeaderValueComparer comparer = MediaTypeHeaderValueComparer.Comparer;
+
+ MediaTypeWithQualityHeaderValue mediaType1 = new MediaTypeWithQualityHeaderValue("text/xml", 0.51);
+ MediaTypeWithQualityHeaderValue mediaType2 = new MediaTypeWithQualityHeaderValue("text/xml", 0.50);
+ Assert.Equal(-1, comparer.Compare(mediaType1, mediaType2));
+
+ mediaType1 = new MediaTypeWithQualityHeaderValue("text/xml", .7);
+ mediaType2 = new MediaTypeWithQualityHeaderValue("application/xml", .0);
+ Assert.Equal(-1, comparer.Compare(mediaType1, mediaType2));
+
+ mediaType1 = new MediaTypeWithQualityHeaderValue("text/xml");
+ mediaType2 = new MediaTypeWithQualityHeaderValue("text/xml", 0.9);
+ Assert.Equal(-1, comparer.Compare(mediaType1, mediaType2));
+
+ mediaType1 = new MediaTypeWithQualityHeaderValue("text/xml", 0.1);
+ mediaType2 = new MediaTypeWithQualityHeaderValue("text/plain", 0);
+ Assert.Equal(-1, comparer.Compare(mediaType1, mediaType2));
+
+ mediaType1 = new MediaTypeWithQualityHeaderValue("*/*", 0.31);
+ mediaType2 = new MediaTypeWithQualityHeaderValue("text/*", .30);
+ Assert.Equal(-1, comparer.Compare(mediaType1, mediaType2));
+
+ mediaType1 = new MediaTypeWithQualityHeaderValue("text/*", 0.6);
+ mediaType2 = new MediaTypeWithQualityHeaderValue("*/*", 0.5);
+ Assert.Equal(-1, comparer.Compare(mediaType1, mediaType2));
+ }
+
+ [Fact]
+ [Trait("Description", "MediaTypeHeadeValueComparer.Compare returns negative 1 if the Q values are the same but the second parameter is a less specific media range.")]
+ public void Compare_Returns_Negative_1_If_Q_Value_Is_Same_But_MediaType2_Is_Media_Range()
+ {
+ MediaTypeHeaderValueComparer comparer = MediaTypeHeaderValueComparer.Comparer;
+
+ MediaTypeWithQualityHeaderValue mediaType1 = new MediaTypeWithQualityHeaderValue("text/xml", 0.50);
+ MediaTypeWithQualityHeaderValue mediaType2 = new MediaTypeWithQualityHeaderValue("text/*", 0.50);
+ Assert.Equal(-1, comparer.Compare(mediaType1, mediaType2));
+
+ mediaType1 = new MediaTypeWithQualityHeaderValue("x/y");
+ mediaType2 = new MediaTypeWithQualityHeaderValue("*/*");
+ Assert.Equal(-1, comparer.Compare(mediaType1, mediaType2));
+
+ mediaType1 = new MediaTypeWithQualityHeaderValue("application/*", 0.2);
+ mediaType2 = new MediaTypeWithQualityHeaderValue("*/*", 0.2);
+ Assert.Equal(-1, comparer.Compare(mediaType1, mediaType2));
+
+ mediaType1 = new MediaTypeWithQualityHeaderValue("application/json", 0.2);
+ mediaType1.CharSet = "someCharSet";
+ mediaType2 = new MediaTypeWithQualityHeaderValue("application/json", 0.2);
+ Assert.Equal(-1, comparer.Compare(mediaType1, mediaType2));
+ }
+ }
+}
diff --git a/test/System.Net.Http.Formatting.Test.Unit/Formatting/MediaTypeHeadeValueExtensionsTests.cs b/test/System.Net.Http.Formatting.Test.Unit/Formatting/MediaTypeHeadeValueExtensionsTests.cs
new file mode 100644
index 00000000..86b53724
--- /dev/null
+++ b/test/System.Net.Http.Formatting.Test.Unit/Formatting/MediaTypeHeadeValueExtensionsTests.cs
@@ -0,0 +1,176 @@
+using System.Net.Http.Headers;
+using Xunit;
+
+namespace System.Net.Http.Formatting
+{
+ public class MediaTypeHeadeValueExtensionsTests
+ {
+
+ [Fact]
+
+
+ [Trait("Description", "MediaTypeHeadeValueExtensionMethods.IsMediaRange returns true for media ranges.")]
+ public void IsMediaRange_Returns_True_For_Media_Ranges()
+ {
+ MediaTypeHeaderValue mediaType = new MediaTypeHeaderValue("text/*");
+ Assert.True(mediaType.IsMediaRange(), "MediaTypeHeadeValueExtensionMethods.IsMediaRange should have returned true for 'text/*'.");
+
+ mediaType = new MediaTypeHeaderValue("application/*");
+ mediaType.CharSet = "ISO-8859-1";
+ Assert.True(mediaType.IsMediaRange(), "MediaTypeHeadeValueExtensionMethods.IsMediaRange should have returned true for 'application/*'.");
+
+ mediaType = new MediaTypeHeaderValue("someType/*");
+ Assert.True(mediaType.IsMediaRange(), "MediaTypeHeadeValueExtensionMethods.IsMediaRange should have returned true for 'someType/*'.");
+
+ mediaType = new MediaTypeHeaderValue("*/*");
+ Assert.True(mediaType.IsMediaRange(), "MediaTypeHeadeValueExtensionMethods.IsMediaRange should have returned true for '*/*'.");
+ }
+
+ [Fact]
+
+
+ [Trait("Description", "MediaTypeHeadeValueExtensionMethods.IsMediaRange returns false for non-media ranges.")]
+ public void IsMediaRange_Returns_False_For_Non_Media_Ranges()
+ {
+ MediaTypeHeaderValue mediaType = new MediaTypeHeaderValue("text/xml");
+ Assert.False(mediaType.IsMediaRange(), "MediaTypeHeadeValueExtensionMethods.IsMediaRange should have returned false for 'text/xml'.");
+
+ mediaType = new MediaTypeHeaderValue("*/someSubType");
+ Assert.False(mediaType.IsMediaRange(), "MediaTypeHeadeValueExtensionMethods.IsMediaRange should have returned true for '*/someSubType'.");
+ }
+
+
+
+ [Fact]
+
+
+ [Trait("Description", "MediaTypeHeadeValueExtensionMethods.IsWithinMediaRange returns true for media ranges.")]
+ public void IsWithinMediaRange_Returns_True_For_Media_Ranges()
+ {
+ MediaTypeHeaderValue mediaRange = new MediaTypeHeaderValue("text/*");
+ MediaTypeHeaderValue mediaType = new MediaTypeHeaderValue("text/xml");
+ Assert.True(mediaType.IsWithinMediaRange(mediaRange), "MediaTypeHeadeValueExtensionMethods.IsWithinMediaRange should have returned true for 'text/*'.");
+
+ mediaRange = new MediaTypeHeaderValue("*/*");
+ Assert.True(mediaType.IsWithinMediaRange(mediaRange), "MediaTypeHeadeValueExtensionMethods.IsWithinMediaRange should have returned true for '*/*'.");
+ }
+
+ [Fact]
+
+
+ [Trait("Description", "MediaTypeHeadeValueExtensionMethods.IsWithinMediaRange returns true for media ranges regardless of case.")]
+ public void IsWithinMediaRange_Returns_True_For_Media_Ranges_Regardless_Of_Case()
+ {
+ MediaTypeHeaderValue mediaRange = new MediaTypeHeaderValue("Text/*");
+ MediaTypeHeaderValue mediaType = new MediaTypeHeaderValue("texT/xml");
+ Assert.True(mediaType.IsWithinMediaRange(mediaRange), "MediaTypeHeadeValueExtensionMethods.IsWithinMediaRange should have returned true for 'text/*'.");
+ }
+
+ [Fact]
+
+
+ [Trait("Description", "MediaTypeHeadeValueExtensionMethods.IsWithinMediaRange returns true when the media type is equaivalent to the media range.")]
+ public void IsWithinMediaRange_Returns_True_For_Media_Types_Equaivalent_To_The_Media_Range()
+ {
+ MediaTypeHeaderValue mediaRange = new MediaTypeHeaderValue("application/xml");
+ MediaTypeHeaderValue mediaType = new MediaTypeHeaderValue("application/xml");
+ Assert.True(mediaType.IsWithinMediaRange(mediaRange), "MediaTypeHeadeValueExtensionMethods.IsWithinMediaRange should have returned true for 'application/xml'.");
+ }
+
+ [Fact]
+
+
+ [Trait("Description", "MediaTypeHeadeValueExtensionMethods.IsWithinMediaRange returns true when the media type is a media range equaivalent to the given media range.")]
+ public void IsWithinMediaRange_Returns_True_For_Media_Types_That_Are_Equaivalent_Media_Ranges()
+ {
+ MediaTypeHeaderValue mediaRange = new MediaTypeHeaderValue("text/*");
+ MediaTypeHeaderValue mediaType = new MediaTypeHeaderValue("text/*");
+ Assert.True(mediaType.IsWithinMediaRange(mediaRange), "MediaTypeHeadeValueExtensionMethods.IsWithinMediaRange should have returned true for 'text/*'.");
+ }
+
+ [Fact]
+
+
+ [Trait("Description", "MediaTypeHeadeValueExtensionMethods.IsWithinMediaRange returns true when the media type is a media range more specific than the given media range.")]
+ public void IsWithinMediaRange_Returns_True_For_Media_Types_More_Specific_Than_The_Media_Range()
+ {
+ MediaTypeHeaderValue mediaRange = new MediaTypeHeaderValue("*/*");
+ MediaTypeHeaderValue mediaType = new MediaTypeHeaderValue("text/*");
+ Assert.True(mediaType.IsWithinMediaRange(mediaRange), "MediaTypeHeadeValueExtensionMethods.IsWithinMediaRange should have returned true for '*/*'.");
+ }
+
+ [Fact]
+
+
+ [Trait("Description", "MediaTypeHeadeValueExtensionMethods.IsWithinMediaRange returns true when a charset is given.")]
+ public void IsWithinMediaRange_Returns_True_For_Media_Types_With_Charset()
+ {
+ MediaTypeHeaderValue mediaRange = new MediaTypeHeaderValue("text/*");
+ MediaTypeHeaderValue mediaType = new MediaTypeHeaderValue("text/xml");
+ mediaType.CharSet = "US-ASCII";
+ Assert.True(mediaType.IsWithinMediaRange(mediaRange), "MediaTypeHeadeValueExtensionMethods.IsWithinMediaRange should have returned true for 'text/*'.");
+ }
+
+ [Fact]
+
+
+ [Trait("Description", "MediaTypeHeadeValueExtensionMethods.IsWithinMediaRange returns true when the same charset is given for both the media type and the media range.")]
+ public void IsWithinMediaRange_Returns_True_For_Media_Types_With_Charset_And_Media_Ranges_With_Same_Charset()
+ {
+ MediaTypeHeaderValue mediaRange = new MediaTypeHeaderValue("text/*");
+ MediaTypeHeaderValue mediaType = new MediaTypeHeaderValue("text/xml");
+ mediaType.CharSet = "US-ASCII";
+ mediaRange.CharSet = "US-ASCII";
+ Assert.True(mediaType.IsWithinMediaRange(mediaRange), "MediaTypeHeadeValueExtensionMethods.IsWithinMediaRange should have returned true for 'text/*'.");
+ }
+
+ [Fact]
+
+
+ [Trait("Description", "MediaTypeHeadeValueExtensionMethods.IsWithinMediaRange returns true regardless if the media range has a charset.")]
+ public void IsWithinMediaRange_Returns_True_Regardless_Of_Media_Ranges_With_Charset()
+ {
+ MediaTypeHeaderValue mediaRange = new MediaTypeHeaderValue("text/*");
+ MediaTypeHeaderValue mediaType = new MediaTypeHeaderValue("text/xml");
+ mediaRange.CharSet = "US-ASCII";
+ Assert.True(mediaType.IsWithinMediaRange(mediaRange), "MediaTypeHeadeValueExtensionMethods.IsWithinMediaRange should have returned true for 'text/*' even if the media range has a charset.");
+ }
+
+ [Fact]
+
+
+ [Trait("Description", "MediaTypeHeadeValueExtensionMethods.IsMediaRange returns false when the media type and media range have different types.")]
+ public void IsWithinMediaRange_Returns_False_When_Type_Is_Different()
+ {
+ MediaTypeHeaderValue mediaRange = new MediaTypeHeaderValue("text/*");
+ MediaTypeHeaderValue mediaType = new MediaTypeHeaderValue("application/xml");
+ Assert.False(mediaType.IsWithinMediaRange(mediaRange), "MediaTypeHeadeValueExtensionMethods.IsWithinMediaRange should have returned false for 'text/*' because the media type is 'application/xml'.");
+ }
+
+ [Fact]
+
+
+ [Trait("Description", "MediaTypeHeadeValueExtensionMethods.IsMediaRange returns false when the media type and media range have different sub types.")]
+ public void IsWithinMediaRange_Returns_False_When_SubType_Is_Different()
+ {
+ MediaTypeHeaderValue mediaRange = new MediaTypeHeaderValue("application/json");
+ MediaTypeHeaderValue mediaType = new MediaTypeHeaderValue("application/xml");
+ Assert.False(mediaType.IsWithinMediaRange(mediaRange), "MediaTypeHeadeValueExtensionMethods.IsWithinMediaRange should have returned false because of the different sub types.");
+ }
+
+ [Fact]
+
+
+ [Trait("Description", "MediaTypeHeadeValueExtensionMethods.IsMediaRange returns false when the media type and media range have different charsets.")]
+ public void IsWithinMediaRange_Returns_False_When_Charset_Is_Different()
+ {
+ MediaTypeHeaderValue mediaRange = new MediaTypeHeaderValue("application/xml");
+ MediaTypeHeaderValue mediaType = new MediaTypeHeaderValue("application/xml");
+ mediaType.CharSet = "US-ASCII";
+ mediaRange.CharSet = "OtherCharSet";
+ Assert.False(mediaType.IsWithinMediaRange(mediaRange), "MediaTypeHeadeValueExtensionMethods.IsWithinMediaRange should have returned false because of the different charsets.");
+ }
+
+
+ }
+}
diff --git a/test/System.Net.Http.Formatting.Test.Unit/Formatting/MediaTypeHeaderValueEqualityComparerTests.cs b/test/System.Net.Http.Formatting.Test.Unit/Formatting/MediaTypeHeaderValueEqualityComparerTests.cs
new file mode 100644
index 00000000..cba890d3
--- /dev/null
+++ b/test/System.Net.Http.Formatting.Test.Unit/Formatting/MediaTypeHeaderValueEqualityComparerTests.cs
@@ -0,0 +1,199 @@
+using System.Net.Http.Formatting.DataSets;
+using System.Net.Http.Headers;
+using Microsoft.TestCommon;
+using Xunit;
+using Xunit.Extensions;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Net.Http.Formatting
+{
+ public class MediaTypeHeaderValueEqualityComparerTests
+ {
+
+ [Fact]
+ [Trait("Description", "MediaTypeHeaderValueEqualityComparer is internal, concrete, and not sealed.")]
+ public void TypeIsCorrect()
+ {
+ Assert.Type.HasProperties(typeof(MediaTypeHeaderValueEqualityComparer), TypeAssert.TypeProperties.IsClass);
+ }
+
+ [Fact]
+ [Trait("Description", "EqualityComparer returns same MediaTypeHeadeValueEqualityComparer instance each time.")]
+ public void EqualityComparerReturnsMediaTypeHeadeValueEqualityComparer()
+ {
+ MediaTypeHeaderValueEqualityComparer comparer1 = MediaTypeHeaderValueEqualityComparer.EqualityComparer;
+ MediaTypeHeaderValueEqualityComparer comparer2 = MediaTypeHeaderValueEqualityComparer.EqualityComparer;
+
+ Assert.NotNull(comparer1);
+ Assert.Same(comparer1, comparer2);
+ }
+
+ [Fact]
+ [Trait("Description", "GetHashCode(MediaTypeHeaderValue) returns the same hash code for media types that differe only be case.")]
+ public void GetHashCodeReturnsSameHashCodeRegardlessOfCase()
+ {
+ MediaTypeHeaderValueEqualityComparer comparer = MediaTypeHeaderValueEqualityComparer.EqualityComparer;
+
+ MediaTypeHeaderValue mediaType1 = new MediaTypeHeaderValue("text/xml");
+ MediaTypeHeaderValue mediaType2 = new MediaTypeHeaderValue("TEXT/xml");
+ Assert.Equal(comparer.GetHashCode(mediaType1), comparer.GetHashCode(mediaType2));
+
+ mediaType1 = new MediaTypeHeaderValue("text/*");
+ mediaType2 = new MediaTypeHeaderValue("TEXT/*");
+ Assert.Equal(comparer.GetHashCode(mediaType1), comparer.GetHashCode(mediaType2));
+
+ mediaType1 = new MediaTypeHeaderValue("*/*");
+ mediaType2 = new MediaTypeHeaderValue("*/*");
+ Assert.Equal(comparer.GetHashCode(mediaType1), comparer.GetHashCode(mediaType2));
+ }
+
+
+ [Fact]
+ [Trait("Description", "GetHashCode(MediaTypeHeaderValue) returns different hash codes if the media types are different.")]
+ public void GetHashCodeReturnsDifferentHashCodeForDifferentMediaType()
+ {
+ MediaTypeHeaderValueEqualityComparer comparer = MediaTypeHeaderValueEqualityComparer.EqualityComparer;
+
+ MediaTypeHeaderValue mediaType1 = new MediaTypeHeaderValue("text/*");
+ MediaTypeHeaderValue mediaType2 = new MediaTypeHeaderValue("TEXT/xml");
+ Assert.NotEqual(comparer.GetHashCode(mediaType1), comparer.GetHashCode(mediaType2));
+
+ mediaType1 = new MediaTypeHeaderValue("application/*");
+ mediaType2 = new MediaTypeHeaderValue("TEXT/*");
+ Assert.NotEqual(comparer.GetHashCode(mediaType1), comparer.GetHashCode(mediaType2));
+
+ mediaType1 = new MediaTypeHeaderValue("application/*");
+ mediaType2 = new MediaTypeHeaderValue("*/*");
+ Assert.NotEqual(comparer.GetHashCode(mediaType1), comparer.GetHashCode(mediaType2));
+ }
+
+
+ [Fact]
+ [Trait("Description", "Equals(MediaTypeHeaderValue, MediaTypeHeaderValue) returns true if media type 1 is a subset of 2.")]
+ public void EqualsReturnsTrueIfMediaType1IsSubset()
+ {
+ string[] parameters = new string[]
+ {
+ ";name=value",
+ ";q=1.0",
+ ";version=1",
+ };
+
+ MediaTypeHeaderValueEqualityComparer comparer = MediaTypeHeaderValueEqualityComparer.EqualityComparer;
+
+ MediaTypeHeaderValue mediaType1 = new MediaTypeHeaderValue("text/*");
+ mediaType1.CharSet = "someCharset";
+ MediaTypeHeaderValue mediaType2 = new MediaTypeHeaderValue("TEXT/*");
+ mediaType2.CharSet = "SOMECHARSET";
+ Assert.Equal(mediaType1, mediaType2, comparer);
+
+ mediaType1 = new MediaTypeHeaderValue("application/*");
+ mediaType1.CharSet = "";
+ mediaType2 = new MediaTypeHeaderValue("application/*");
+ mediaType2.CharSet = null;
+ Assert.Equal(mediaType1, mediaType2, comparer);
+
+ foreach (string parameter in parameters)
+ {
+ mediaType1 = new MediaTypeHeaderValue("text/xml");
+ mediaType2 = MediaTypeHeaderValue.Parse("TEXT/xml" + parameter);
+ Assert.Equal(mediaType1, mediaType2, comparer);
+
+ mediaType1 = new MediaTypeHeaderValue("text/*");
+ mediaType2 = MediaTypeHeaderValue.Parse("TEXT/*" + parameter);
+ Assert.Equal(mediaType1, mediaType2, comparer);
+
+ mediaType1 = new MediaTypeHeaderValue("*/*");
+ mediaType2 = MediaTypeHeaderValue.Parse("*/*" + parameter);
+ Assert.Equal(mediaType1, mediaType2, comparer);
+ }
+ }
+
+ [Fact]
+ [Trait("Description", "Equals(MediaTypeHeaderValue, MediaTypeHeaderValue) returns false if media type 1 is a superset of 2.")]
+ public void EqualsReturnsFalseIfMediaType1IsSuperset()
+ {
+ string[] parameters = new string[]
+ {
+ ";name=value",
+ ";q=1.0",
+ ";version=1",
+ };
+
+ MediaTypeHeaderValueEqualityComparer comparer = MediaTypeHeaderValueEqualityComparer.EqualityComparer;
+
+ foreach (string parameter in parameters)
+ {
+ MediaTypeHeaderValue mediaType1 = MediaTypeHeaderValue.Parse("text/xml" + parameter);
+ MediaTypeHeaderValue mediaType2 = new MediaTypeHeaderValue("TEXT/xml");
+ Assert.NotEqual(mediaType1, mediaType2, comparer);
+
+ mediaType1 = MediaTypeHeaderValue.Parse("text/*" + parameter);
+ mediaType2 = new MediaTypeHeaderValue("TEXT/*");
+ Assert.NotEqual(mediaType1, mediaType2, comparer);
+
+ mediaType1 = MediaTypeHeaderValue.Parse("*/*" + parameter);
+ mediaType2 = new MediaTypeHeaderValue("*/*");
+ Assert.NotEqual(mediaType1, mediaType2, comparer);
+ }
+ }
+
+ [Fact]
+ [Trait("Description", "Equals(MediaTypeHeaderValue, MediaTypeHeaderValue) returns true if media types and charsets differ only by case.")]
+ public void Equals1ReturnsTrueIfMediaTypesDifferOnlyByCase()
+ {
+ MediaTypeHeaderValueEqualityComparer comparer = MediaTypeHeaderValueEqualityComparer.EqualityComparer;
+
+ MediaTypeHeaderValue mediaType1 = new MediaTypeHeaderValue("text/xml");
+ MediaTypeHeaderValue mediaType2 = new MediaTypeHeaderValue("TEXT/xml");
+ Assert.Equal(mediaType1, mediaType2, comparer);
+
+ mediaType1 = new MediaTypeHeaderValue("text/*");
+ mediaType2 = new MediaTypeHeaderValue("TEXT/*");
+ Assert.Equal(mediaType1, mediaType2, comparer);
+
+ mediaType1 = new MediaTypeHeaderValue("*/*");
+ mediaType2 = new MediaTypeHeaderValue("*/*");
+ Assert.Equal(mediaType1, mediaType2, comparer);
+
+ mediaType1 = new MediaTypeHeaderValue("text/*");
+ mediaType1.CharSet = "someCharset";
+ mediaType2 = new MediaTypeHeaderValue("TEXT/*");
+ mediaType2.CharSet = "SOMECHARSET";
+ Assert.Equal(mediaType1, mediaType2, comparer);
+
+ mediaType1 = new MediaTypeHeaderValue("application/*");
+ mediaType1.CharSet = "";
+ mediaType2 = new MediaTypeHeaderValue("application/*");
+ mediaType2.CharSet = null;
+ Assert.Equal(mediaType1, mediaType2, comparer);
+ }
+
+ [Fact]
+ [Trait("Description", "Equals(MediaTypeHeaderValue, MediaTypeHeaderValue) returns false if media types and charsets differ by more than case.")]
+ public void EqualsReturnsFalseIfMediaTypesDifferByMoreThanCase()
+ {
+ MediaTypeHeaderValueEqualityComparer comparer = MediaTypeHeaderValueEqualityComparer.EqualityComparer;
+
+ MediaTypeHeaderValue mediaType1 = new MediaTypeHeaderValue("text/xml");
+ MediaTypeHeaderValue mediaType2 = new MediaTypeHeaderValue("TEST/xml");
+ Assert.NotEqual(mediaType1, mediaType2, comparer);
+
+ mediaType1 = new MediaTypeHeaderValue("text/*");
+ mediaType1.CharSet = "someCharset";
+ mediaType2 = new MediaTypeHeaderValue("TEXT/*");
+ mediaType2.CharSet = "SOMEOTHERCHARSET";
+ Assert.NotEqual(mediaType1, mediaType2, comparer);
+ }
+
+ [Theory]
+ [TestDataSet(typeof(HttpUnitTestDataSets), "StandardMediaTypesWithQuality")]
+ [Trait("Description", "Equals(MediaTypeHeaderValue, MediaTypeHeaderValue) returns true if media types differ only in quality.")]
+ public void EqualsReturnsTrueIfMediaTypesDifferOnlyByQuality(MediaTypeWithQualityHeaderValue mediaType1)
+ {
+ MediaTypeHeaderValueEqualityComparer comparer = MediaTypeHeaderValueEqualityComparer.EqualityComparer;
+ MediaTypeHeaderValue mediaType2 = new MediaTypeHeaderValue(mediaType1.MediaType);
+ Assert.Equal(mediaType2, mediaType1, comparer);
+ }
+ }
+}
diff --git a/test/System.Net.Http.Formatting.Test.Unit/Formatting/ParsedMediaTypeHeaderValueTests.cs b/test/System.Net.Http.Formatting.Test.Unit/Formatting/ParsedMediaTypeHeaderValueTests.cs
new file mode 100644
index 00000000..64450302
--- /dev/null
+++ b/test/System.Net.Http.Formatting.Test.Unit/Formatting/ParsedMediaTypeHeaderValueTests.cs
@@ -0,0 +1,168 @@
+using System.Net.Http.Headers;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Net.Http.Formatting
+{
+ public class ParsedMediaTypeHeadeValueTests
+ {
+ [Fact]
+ [Trait("Description", "MediaTypeHeaderValue ensures only valid media types are constructed.")]
+ public void MediaTypeHeaderValue_Ensures_Valid_MediaType()
+ {
+ string[] invalidMediaTypes = new string[] { "", " ", "\n", "\t", "text", "text/", "text\\", "\\", "//", "text/[", "text/ ", " text/", " text/ ", "text\\ ", " text\\", " text\\ ", "text\\xml", "text//xml" };
+
+ foreach (string invalidMediaType in invalidMediaTypes)
+ {
+ Assert.Throws<Exception>(() => new MediaTypeHeaderValue(invalidMediaType), exceptionMessage: null, allowDerivedExceptions: true);
+ }
+ }
+
+ [Fact]
+ [Trait("Description", "ParsedMediaTypeHeadeValue.Type returns the media type.")]
+ public void Type_Returns_Just_The_Type()
+ {
+ MediaTypeHeaderValue mediaType = new MediaTypeHeaderValue("text/xml");
+ ParsedMediaTypeHeaderValue parsedMediaType = new ParsedMediaTypeHeaderValue(mediaType);
+ Assert.Equal("text", parsedMediaType.Type);
+
+ mediaType = new MediaTypeHeaderValue("text/*");
+ parsedMediaType = new ParsedMediaTypeHeaderValue(mediaType);
+ Assert.Equal("text", parsedMediaType.Type);
+
+ mediaType = new MediaTypeHeaderValue("*/*");
+ parsedMediaType = new ParsedMediaTypeHeaderValue(mediaType);
+ Assert.Equal("*", parsedMediaType.Type);
+ }
+
+ [Fact]
+ [Trait("Description", "ParsedMediaTypeHeadeValue.SubType returns the media sub-type.")]
+ public void SubType_Returns_Just_The_Sub_Type()
+ {
+ MediaTypeHeaderValue mediaType = new MediaTypeHeaderValue("text/xml");
+ ParsedMediaTypeHeaderValue parsedMediaType = new ParsedMediaTypeHeaderValue(mediaType);
+ Assert.Equal("xml", parsedMediaType.SubType);
+
+ mediaType = new MediaTypeHeaderValue("text/*");
+ parsedMediaType = new ParsedMediaTypeHeaderValue(mediaType);
+ Assert.Equal("*", parsedMediaType.SubType);
+
+ mediaType = new MediaTypeHeaderValue("*/*");
+ parsedMediaType = new ParsedMediaTypeHeaderValue(mediaType);
+ Assert.Equal("*", parsedMediaType.SubType);
+ }
+
+ [Fact]
+ [Trait("Description", "ParsedMediaTypeHeadeValue.IsSubTypeMediaRange returns true for media ranges.")]
+ public void IsSubTypeMediaRange_Returns_True_For_Media_Ranges()
+ {
+ MediaTypeHeaderValue mediaType = new MediaTypeHeaderValue("text/*");
+ ParsedMediaTypeHeaderValue parsedMediaType = new ParsedMediaTypeHeaderValue(mediaType);
+ Assert.True(parsedMediaType.IsSubTypeMediaRange, "ParsedMediaTypeHeadeValue.IsSubTypeMediaRange should have returned true.");
+
+ mediaType = new MediaTypeHeaderValue("*/*");
+ parsedMediaType = new ParsedMediaTypeHeaderValue(mediaType);
+ Assert.True(parsedMediaType.IsSubTypeMediaRange, "ParsedMediaTypeHeadeValue.IsSubTypeMediaRange should have returned true.");
+ }
+
+ [Fact]
+ [Trait("Description", "ParsedMediaTypeHeadeValue.IsAllMediaRange returns true only when both the type and subtype are wildcard characters.")]
+ public void IsAllMediaRange_Returns_True_Only_When_Type_And_SubType_Are_Wildcards()
+ {
+ MediaTypeHeaderValue mediaType = new MediaTypeHeaderValue("text/*");
+ ParsedMediaTypeHeaderValue parsedMediaType = new ParsedMediaTypeHeaderValue(mediaType);
+ Assert.False(parsedMediaType.IsAllMediaRange, "ParsedMediaTypeHeadeValue.IsAllMediaRange should have returned false.");
+
+ mediaType = new MediaTypeHeaderValue("*/*");
+ parsedMediaType = new ParsedMediaTypeHeaderValue(mediaType);
+ Assert.True(parsedMediaType.IsAllMediaRange, "ParsedMediaTypeHeadeValue.IsAllMediaRange should have returned true.");
+
+ mediaType = new MediaTypeHeaderValue("*/xml");
+ parsedMediaType = new ParsedMediaTypeHeaderValue(mediaType);
+ Assert.False(parsedMediaType.IsAllMediaRange, "ParsedMediaTypeHeadeValue.IsAllMediaRange should have returned false.");
+ }
+
+ [Fact]
+
+
+ [Trait("Description", "ParsedMediaTypeHeadeValue.QualityFactor always returns 1.0 for MediaTypeHeaderValue.")]
+ public void QualityFactor_Returns_1_For_MediaTypeHeaderValue()
+ {
+ MediaTypeHeaderValue mediaType = new MediaTypeHeaderValue("text/*");
+ ParsedMediaTypeHeaderValue parsedMediaType = new ParsedMediaTypeHeaderValue(mediaType);
+ Assert.Equal(1.0, parsedMediaType.QualityFactor);
+
+ mediaType = new MediaTypeHeaderValue("*/*");
+ parsedMediaType = new ParsedMediaTypeHeaderValue(mediaType);
+ Assert.Equal(1.0, parsedMediaType.QualityFactor);
+
+ mediaType = new MediaTypeHeaderValue("application/xml");
+ parsedMediaType = new ParsedMediaTypeHeaderValue(mediaType);
+ Assert.Equal(1.0, parsedMediaType.QualityFactor);
+
+ mediaType = new MediaTypeHeaderValue("application/xml");
+ mediaType.Parameters.Add(new NameValueHeaderValue("q", "0.5"));
+ parsedMediaType = new ParsedMediaTypeHeaderValue(mediaType);
+ Assert.Equal(1.0, parsedMediaType.QualityFactor);
+ }
+
+ [Fact]
+
+
+ [Trait("Description", "ParsedMediaTypeHeadeValue.QualityFactor returns q value given by MediaTypeWithQualityHeaderValue.")]
+ public void QualityFactor_Returns_Q_Value_For_MediaTypeWithQualityHeaderValue()
+ {
+ MediaTypeHeaderValue mediaType = new MediaTypeWithQualityHeaderValue("text/*", 0.5);
+ ParsedMediaTypeHeaderValue parsedMediaType = new ParsedMediaTypeHeaderValue(mediaType);
+ Assert.Equal(0.5, parsedMediaType.QualityFactor);
+
+ mediaType = new MediaTypeWithQualityHeaderValue("*/*", 0.0);
+ parsedMediaType = new ParsedMediaTypeHeaderValue(mediaType);
+ Assert.Equal(0.0, parsedMediaType.QualityFactor);
+
+ mediaType = new MediaTypeWithQualityHeaderValue("application/xml", 1.0);
+ parsedMediaType = new ParsedMediaTypeHeaderValue(mediaType);
+ Assert.Equal(1.0, parsedMediaType.QualityFactor);
+
+ mediaType = new MediaTypeWithQualityHeaderValue("application/xml");
+ parsedMediaType = new ParsedMediaTypeHeaderValue(mediaType);
+ Assert.Equal(1.0, parsedMediaType.QualityFactor);
+
+ mediaType = new MediaTypeWithQualityHeaderValue("application/xml");
+ mediaType.Parameters.Add(new NameValueHeaderValue("q", "0.5"));
+ parsedMediaType = new ParsedMediaTypeHeaderValue(mediaType);
+ Assert.Equal(0.5, parsedMediaType.QualityFactor);
+
+ MediaTypeWithQualityHeaderValue mediaTypeWithQuality = new MediaTypeWithQualityHeaderValue("application/xml");
+ mediaTypeWithQuality.Quality = 0.2;
+ parsedMediaType = new ParsedMediaTypeHeaderValue(mediaTypeWithQuality);
+ Assert.Equal(0.2, parsedMediaType.QualityFactor);
+ }
+
+ [Fact]
+
+
+ [Trait("Description", "ParsedMediaTypeHeadeValue.CharSet is just the value of the CharSet from the MediaTypeHeaderValue.")]
+ public void CharSet_Is_CharSet_Of_MediaTypeHeaderValue()
+ {
+ MediaTypeHeaderValue mediaType = new MediaTypeHeaderValue("application/*");
+ ParsedMediaTypeHeaderValue parsedMediaType = new ParsedMediaTypeHeaderValue(mediaType);
+ Assert.Null(parsedMediaType.CharSet);
+
+ mediaType = new MediaTypeHeaderValue("application/*");
+ mediaType.CharSet = "";
+ parsedMediaType = new ParsedMediaTypeHeaderValue(mediaType);
+ Assert.Null(parsedMediaType.CharSet);
+
+ mediaType = new MediaTypeHeaderValue("application/xml");
+ mediaType.CharSet = "someCharSet";
+ parsedMediaType = new ParsedMediaTypeHeaderValue(mediaType);
+ Assert.Equal("someCharSet", parsedMediaType.CharSet);
+
+ mediaType = new MediaTypeHeaderValue("text/xml");
+ mediaType.CharSet = "someCharSet";
+ parsedMediaType = new ParsedMediaTypeHeaderValue(mediaType);
+ Assert.Equal("someCharSet", parsedMediaType.CharSet);
+ }
+ }
+}
diff --git a/test/System.Net.Http.Formatting.Test.Unit/Formatting/Parsers/FormUrlEncodedParserTests.cs b/test/System.Net.Http.Formatting.Test.Unit/Formatting/Parsers/FormUrlEncodedParserTests.cs
new file mode 100644
index 00000000..de09caed
--- /dev/null
+++ b/test/System.Net.Http.Formatting.Test.Unit/Formatting/Parsers/FormUrlEncodedParserTests.cs
@@ -0,0 +1,176 @@
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Linq;
+using System.Text;
+using Microsoft.TestCommon;
+using Xunit;
+using Xunit.Extensions;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Net.Http.Formatting.Parsers
+{
+ public class FormUrlEncodedParserTests
+ {
+ private const int MinMessageSize = 1;
+ private const int Iterations = 16;
+
+ internal static Collection<KeyValuePair<string, string>> CreateCollection()
+ {
+ return new Collection<KeyValuePair<string, string>>();
+ }
+
+ internal static FormUrlEncodedParser CreateParser(int maxMessageSize, out ICollection<KeyValuePair<string, string>> nameValuePairs)
+ {
+ nameValuePairs = CreateCollection();
+ return new FormUrlEncodedParser(nameValuePairs, maxMessageSize);
+ }
+
+ internal static byte[] CreateBuffer(params string[] nameValuePairs)
+ {
+ StringBuilder buffer = new StringBuilder();
+ bool first = true;
+ foreach (var h in nameValuePairs)
+ {
+ if (first)
+ {
+ first = false;
+ }
+ else
+ {
+ buffer.Append('&');
+ }
+
+ buffer.Append(h);
+ }
+
+ return Encoding.UTF8.GetBytes(buffer.ToString());
+ }
+
+ internal static ParserState ParseBufferInSteps(FormUrlEncodedParser parser, byte[] buffer, int readsize, out int totalBytesConsumed)
+ {
+ ParserState state = ParserState.Invalid;
+ totalBytesConsumed = 0;
+ while (totalBytesConsumed <= buffer.Length)
+ {
+ int size = Math.Min(buffer.Length - totalBytesConsumed, readsize);
+ byte[] parseBuffer = new byte[size];
+ Buffer.BlockCopy(buffer, totalBytesConsumed, parseBuffer, 0, size);
+
+ int bytesConsumed = 0;
+ state = parser.ParseBuffer(parseBuffer, parseBuffer.Length, ref bytesConsumed, totalBytesConsumed == buffer.Length - size);
+ totalBytesConsumed += bytesConsumed;
+
+ if (state != ParserState.NeedMoreData)
+ {
+ return state;
+ }
+ }
+
+ return state;
+ }
+
+ [Fact]
+ public void TypeIsCorrect()
+ {
+ Assert.Type.HasProperties<FormUrlEncodedParser>(TypeAssert.TypeProperties.IsClass);
+ }
+
+ [Fact]
+ public void FormUrlEncodedParserThrowsOnNull()
+ {
+ Assert.ThrowsArgumentNull(() => { new FormUrlEncodedParser(null, ParserData.MinHeaderSize); }, "nameValuePairs");
+ }
+
+ [Fact]
+ public void FormUrlEncodedParserThrowsOnInvalidSize()
+ {
+ Assert.ThrowsArgument(() => { new FormUrlEncodedParser(CreateCollection(), MinMessageSize - 1); }, "maxMessageSize");
+
+ FormUrlEncodedParser parser = new FormUrlEncodedParser(CreateCollection(), MinMessageSize);
+ Assert.NotNull(parser);
+
+ parser = new FormUrlEncodedParser(CreateCollection(), MinMessageSize + 1);
+ Assert.NotNull(parser);
+ }
+
+ [Fact]
+ public void ParseBufferThrowsOnNullBuffer()
+ {
+ ICollection<KeyValuePair<string, string>> collection;
+ FormUrlEncodedParser parser = CreateParser(128, out collection);
+ int bytesConsumed = 0;
+ Assert.ThrowsArgumentNull(() => { parser.ParseBuffer(null, 0, ref bytesConsumed, false); }, "buffer");
+ }
+
+ [Fact]
+ public void ParseBufferHandlesEmptyBuffer()
+ {
+ byte[] data = CreateBuffer();
+ ICollection<KeyValuePair<string, string>> collection;
+ FormUrlEncodedParser parser = CreateParser(MinMessageSize, out collection);
+
+ int bytesConsumed = 0;
+ ParserState state = parser.ParseBuffer(data, data.Length, ref bytesConsumed, true);
+ Assert.Equal(ParserState.Done, state);
+ Assert.Equal(data.Length, bytesConsumed);
+ Assert.Equal(0, collection.Count());
+ }
+
+ public static TheoryDataSet<string, string, string> UriQueryData
+ {
+ get
+ {
+ return UriQueryTestData.UriQueryData;
+ }
+ }
+
+ [Theory]
+ [InlineData("N", null, "N")]
+ [InlineData("%26", null, "&")]
+ [PropertyData("UriQueryData")]
+ public void ParseBufferCorrectly(string segment, string name, string value)
+ {
+ for (int index = 1; index < Iterations; index++)
+ {
+ List<string> segments = new List<string>();
+ for (int cnt = 0; cnt < index; cnt++)
+ {
+ segments.Add(segment);
+ }
+
+ byte[] data = CreateBuffer(segments.ToArray());
+ for (var cnt = 1; cnt <= data.Length; cnt++)
+ {
+ ICollection<KeyValuePair<string, string>> collection;
+ FormUrlEncodedParser parser = CreateParser(data.Length + 1, out collection);
+ Assert.NotNull(parser);
+
+ int totalBytesConsumed;
+ ParserState state = ParseBufferInSteps(parser, data, cnt, out totalBytesConsumed);
+ Assert.Equal(ParserState.Done, state);
+ Assert.Equal(data.Length, totalBytesConsumed);
+
+ Assert.Equal(index, collection.Count());
+ foreach (KeyValuePair<string, string> element in collection)
+ {
+ Assert.Equal(name, element.Key);
+ Assert.Equal(value, element.Value);
+ }
+ }
+ }
+ }
+
+ [Fact]
+ public void HeaderParserDataTooBig()
+ {
+ byte[] data = CreateBuffer("N=V");
+ ICollection<KeyValuePair<string, string>> collection;
+ FormUrlEncodedParser parser = CreateParser(MinMessageSize, out collection);
+
+ int bytesConsumed = 0;
+ ParserState state = parser.ParseBuffer(data, data.Length, ref bytesConsumed, true);
+ Assert.Equal(ParserState.DataTooBig, state);
+ Assert.Equal(MinMessageSize, bytesConsumed);
+ }
+ }
+} \ No newline at end of file
diff --git a/test/System.Net.Http.Formatting.Test.Unit/Formatting/Parsers/HttpRequestHeaderParserTests.cs b/test/System.Net.Http.Formatting.Test.Unit/Formatting/Parsers/HttpRequestHeaderParserTests.cs
new file mode 100644
index 00000000..1f8188c4
--- /dev/null
+++ b/test/System.Net.Http.Formatting.Test.Unit/Formatting/Parsers/HttpRequestHeaderParserTests.cs
@@ -0,0 +1,273 @@
+using System.Collections.Generic;
+using System.Linq;
+using System.Net.Http.Formatting.DataSets;
+using System.Text;
+using Microsoft.TestCommon;
+using Xunit;
+using Xunit.Extensions;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Net.Http.Formatting.Parsers
+{
+ public class HttpRequestHeaderParserTests
+ {
+ [Fact]
+ [Trait("Description", "HttpRequestHeaderParser is internal class")]
+ public void TypeIsCorrect()
+ {
+ Assert.Type.HasProperties<HttpRequestHeaderParser>(TypeAssert.TypeProperties.IsClass);
+ }
+
+
+ private static byte[] CreateBuffer(string method, string address, string version, Dictionary<string, string> headers)
+ {
+ const string SP = " ";
+ const string CRLF = "\r\n";
+ string lws = SP;
+
+ StringBuilder request = new StringBuilder();
+ request.AppendFormat("{0}{1}{2}{3}{4}{5}", method, lws, address, lws, version, CRLF);
+ if (headers != null)
+ {
+ foreach (var h in headers)
+ {
+ request.AppendFormat("{0}: {1}{2}", h.Key, h.Value, CRLF);
+ }
+ }
+
+ request.Append(CRLF);
+ return Encoding.UTF8.GetBytes(request.ToString());
+ }
+
+ private static ParserState ParseBufferInSteps(HttpRequestHeaderParser parser, byte[] buffer, int readsize, out int totalBytesConsumed)
+ {
+ ParserState state = ParserState.Invalid;
+ totalBytesConsumed = 0;
+ while (totalBytesConsumed <= buffer.Length)
+ {
+ int size = Math.Min(buffer.Length - totalBytesConsumed, readsize);
+ byte[] parseBuffer = new byte[size];
+ Buffer.BlockCopy(buffer, totalBytesConsumed, parseBuffer, 0, size);
+
+ int bytesConsumed = 0;
+ state = parser.ParseBuffer(parseBuffer, parseBuffer.Length, ref bytesConsumed);
+ totalBytesConsumed += bytesConsumed;
+
+ if (state != ParserState.NeedMoreData)
+ {
+ return state;
+ }
+ }
+
+ return state;
+ }
+
+ private static void ValidateResult(
+ HttpUnsortedRequest requestLine,
+ string method,
+ string requestUri,
+ Version version,
+ Dictionary<string, string> headers)
+ {
+ Assert.Equal(new HttpMethod(method), requestLine.Method);
+ Assert.Equal(requestUri, requestLine.RequestUri);
+ Assert.Equal(version, requestLine.Version);
+
+ if (headers != null)
+ {
+ Assert.Equal(headers.Count, requestLine.HttpHeaders.Count());
+ foreach (var header in headers)
+ {
+ Assert.True(requestLine.HttpHeaders.Contains(header.Key), "Parsed header did not contain expected key " + header.Key);
+ Assert.Equal(header.Value, requestLine.HttpHeaders.GetValues(header.Key).ElementAt(0));
+ }
+ }
+ }
+
+ [Fact]
+ [Trait("Description", "HttpRequestHeaderParser constructor throws on invalid arguments")]
+ public void HttpRequestHeaderParserConstructorTest()
+ {
+ HttpUnsortedRequest result = new HttpUnsortedRequest();
+ Assert.NotNull(result);
+
+ Assert.ThrowsArgument(() => { new HttpRequestHeaderParser(result, ParserData.MinRequestLineSize - 1, ParserData.MinHeaderSize); }, "maxRequestLineSize");
+
+ Assert.ThrowsArgument(() => { new HttpRequestHeaderParser(result, ParserData.MinRequestLineSize, ParserData.MinHeaderSize - 1); }, "maxHeaderSize");
+
+ HttpRequestHeaderParser parser = new HttpRequestHeaderParser(result, ParserData.MinRequestLineSize, ParserData.MinHeaderSize);
+ Assert.NotNull(parser);
+
+ Assert.ThrowsArgumentNull(() => { new HttpRequestHeaderParser(null); }, "httpRequest");
+ }
+
+
+ [Fact]
+ [Trait("Description", "HttpRequestHeaderParser.ParseBuffer throws on null buffer.")]
+ public void RequestHeaderParserNullBuffer()
+ {
+ HttpUnsortedRequest result = new HttpUnsortedRequest();
+ HttpRequestHeaderParser parser = new HttpRequestHeaderParser(result, ParserData.MinRequestLineSize, ParserData.MinHeaderSize);
+ Assert.NotNull(parser);
+ int bytesConsumed = 0;
+ Assert.ThrowsArgumentNull(() => { parser.ParseBuffer(null, 0, ref bytesConsumed); }, "buffer");
+ }
+
+ [Fact]
+ [Trait("Description", "HttpRequestHeaderParser.ParseBuffer parses minimum requestline.")]
+ public void RequestHeaderParserMinimumBuffer()
+ {
+ byte[] data = CreateBuffer("G", "/", "HTTP/1.1", null);
+ HttpUnsortedRequest result = new HttpUnsortedRequest();
+ HttpRequestHeaderParser parser = new HttpRequestHeaderParser(result, ParserData.MinRequestLineSize, ParserData.MinHeaderSize);
+ Assert.NotNull(parser);
+
+ int bytesConsumed = 0;
+ ParserState state = parser.ParseBuffer(data, data.Length, ref bytesConsumed);
+ Assert.Equal(ParserState.Done, state);
+ Assert.Equal(data.Length, bytesConsumed);
+
+ ValidateResult(result, "G", "/", new Version("1.1"), null);
+ }
+
+ [Fact]
+ [Trait("Description", "HttpRequestHeaderParser.ParseBuffer parses standard methods.")]
+ public void RequestHeaderParserAcceptsStandardMethods()
+ {
+ foreach (HttpMethod method in HttpUnitTestDataSets.AllHttpMethods)
+ {
+ byte[] data = CreateBuffer(method.ToString(), "/", "HTTP/1.1", ParserData.ValidHeaders);
+
+ for (var cnt = 1; cnt <= data.Length; cnt++)
+ {
+ HttpUnsortedRequest result = new HttpUnsortedRequest();
+ HttpRequestHeaderParser parser = new HttpRequestHeaderParser(result);
+ Assert.NotNull(parser);
+
+ int totalBytesConsumed = 0;
+ ParserState state = ParseBufferInSteps(parser, data, cnt, out totalBytesConsumed);
+ Assert.Equal(ParserState.Done, state);
+ Assert.Equal(data.Length, totalBytesConsumed);
+
+ ValidateResult(result, method.ToString(), "/", new Version("1.1"), ParserData.ValidHeaders);
+ }
+ }
+ }
+
+ [Fact]
+ [Trait("Description", "HttpRequestHeaderParser.ParseBuffer parses custom methods.")]
+ public void RequestHeaderParserAcceptsCustomMethods()
+ {
+ foreach (HttpMethod method in HttpUnitTestDataSets.CustomHttpMethods)
+ {
+ byte[] data = CreateBuffer(method.ToString(), "/", "HTTP/1.1", ParserData.ValidHeaders);
+
+ for (var cnt = 1; cnt <= data.Length; cnt++)
+ {
+ HttpUnsortedRequest result = new HttpUnsortedRequest();
+ HttpRequestHeaderParser parser = new HttpRequestHeaderParser(result);
+ Assert.NotNull(parser);
+
+ int totalBytesConsumed = 0;
+ ParserState state = ParseBufferInSteps(parser, data, cnt, out totalBytesConsumed);
+ Assert.Equal(ParserState.Done, state);
+ Assert.Equal(data.Length, totalBytesConsumed);
+
+ ValidateResult(result, method.ToString(), "/", new Version("1.1"), ParserData.ValidHeaders);
+ }
+ }
+ }
+
+ [Fact]
+ [Trait("Description", "HttpRequestHeaderParser.ParseBuffer rejects invalid method")]
+ public void RequestHeaderParserRejectsInvalidMethod()
+ {
+ foreach (string invalidMethod in ParserData.InvalidMethods)
+ {
+ byte[] data = CreateBuffer(invalidMethod, "/", "HTTP/1.1", ParserData.ValidHeaders);
+
+ for (var cnt = 1; cnt <= data.Length; cnt++)
+ {
+ HttpUnsortedRequest result = new HttpUnsortedRequest();
+ HttpRequestHeaderParser parser = new HttpRequestHeaderParser(result);
+ Assert.NotNull(parser);
+
+ int totalBytesConsumed = 0;
+ ParserState state = ParseBufferInSteps(parser, data, cnt, out totalBytesConsumed);
+ Assert.Equal(ParserState.Invalid, state);
+ }
+ }
+ }
+
+ [Fact]
+ [Trait("Description", "HttpRequestHeaderParser.ParseBuffer rejects invalid URI.")]
+ public void RequestHeaderParserRejectsInvalidUri()
+ {
+ foreach (string invalidRequestUri in ParserData.InvalidRequestUris)
+ {
+ byte[] data = CreateBuffer("GET", invalidRequestUri, "HTTP/1.1", ParserData.ValidHeaders);
+
+ for (var cnt = 1; cnt <= data.Length; cnt++)
+ {
+ HttpUnsortedRequest result = new HttpUnsortedRequest();
+ HttpRequestHeaderParser parser = new HttpRequestHeaderParser(result);
+ Assert.NotNull(parser);
+
+ int totalBytesConsumed = 0;
+ ParserState state = ParseBufferInSteps(parser, data, cnt, out totalBytesConsumed);
+ Assert.Equal(ParserState.Invalid, state);
+ }
+ }
+ }
+
+ public static IEnumerable<object[]> Versions
+ {
+ get { return ParserData.Versions; }
+ }
+
+ [Theory]
+ [PropertyData("Versions")]
+ [Trait("Description", "HttpRequestHeaderParser.ParseBuffer accepts valid versions.")]
+ public void RequestHeaderParserAcceptsValidVersion(Version version)
+ {
+ byte[] data = CreateBuffer("GET", "/", String.Format("HTTP/{0}", version.ToString(2)), ParserData.ValidHeaders);
+
+ for (var cnt = 1; cnt <= data.Length; cnt++)
+ {
+ HttpUnsortedRequest result = new HttpUnsortedRequest();
+ HttpRequestHeaderParser parser = new HttpRequestHeaderParser(result);
+ Assert.NotNull(parser);
+
+ int totalBytesConsumed = 0;
+ ParserState state = ParseBufferInSteps(parser, data, cnt, out totalBytesConsumed);
+ Assert.Equal(data.Length, totalBytesConsumed);
+
+ ValidateResult(result, "GET", "/", version, ParserData.ValidHeaders);
+ }
+ }
+
+ public static IEnumerable<object[]> InvalidVersions
+ {
+ get { return ParserData.InvalidVersions; }
+ }
+
+ [Theory]
+ [PropertyData("InvalidVersions")]
+ [Trait("Description", "HttpRequestHeaderParser.ParseBuffer rejects lower case protocol version.")]
+ public void RequestHeaderParserRejectsInvalidVersion(string invalidVersion)
+ {
+ byte[] data = CreateBuffer("GET", "/", invalidVersion, ParserData.ValidHeaders);
+
+ for (var cnt = 1; cnt <= data.Length; cnt++)
+ {
+ HttpUnsortedRequest result = new HttpUnsortedRequest();
+ HttpRequestHeaderParser parser = new HttpRequestHeaderParser(result);
+ Assert.NotNull(parser);
+
+ int totalBytesConsumed = 0;
+ ParserState state = ParseBufferInSteps(parser, data, cnt, out totalBytesConsumed);
+ Assert.Equal(ParserState.Invalid, state);
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/test/System.Net.Http.Formatting.Test.Unit/Formatting/Parsers/HttpRequestLineParserTests.cs b/test/System.Net.Http.Formatting.Test.Unit/Formatting/Parsers/HttpRequestLineParserTests.cs
new file mode 100644
index 00000000..dccf41cc
--- /dev/null
+++ b/test/System.Net.Http.Formatting.Test.Unit/Formatting/Parsers/HttpRequestLineParserTests.cs
@@ -0,0 +1,273 @@
+using System.Collections.Generic;
+using System.Net.Http.Formatting.DataSets;
+using System.Text;
+using Microsoft.TestCommon;
+using Xunit;
+using Xunit.Extensions;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Net.Http.Formatting.Parsers
+{
+ public class HttpRequestLineParserTests
+ {
+ [Fact]
+ [Trait("Description", "HttpRequestLineParser is internal class")]
+ public void TypeIsCorrect()
+ {
+ Assert.Type.HasProperties<HttpRequestLineParser>(TypeAssert.TypeProperties.IsClass);
+ }
+
+ internal static byte[] CreateBuffer(string method, string address, string version)
+ {
+ return CreateBuffer(method, address, version, false);
+ }
+
+ private static byte[] CreateBuffer(string method, string address, string version, bool withLws)
+ {
+ const string SP = " ";
+ const string HTAB = "\t";
+ const string CRLF = "\r\n";
+
+ string lws = SP;
+ if (withLws)
+ {
+ lws = SP + SP + HTAB + SP;
+ }
+
+ string requestLine = String.Format("{0}{1}{2}{3}{4}{5}", method, lws, address, lws, version, CRLF);
+ return Encoding.UTF8.GetBytes(requestLine);
+ }
+
+ private static ParserState ParseBufferInSteps(HttpRequestLineParser parser, byte[] buffer, int readsize, out int totalBytesConsumed)
+ {
+ ParserState state = ParserState.Invalid;
+ totalBytesConsumed = 0;
+ while (totalBytesConsumed <= buffer.Length)
+ {
+ int size = Math.Min(buffer.Length - totalBytesConsumed, readsize);
+ byte[] parseBuffer = new byte[size];
+ Buffer.BlockCopy(buffer, totalBytesConsumed, parseBuffer, 0, size);
+
+ int bytesConsumed = 0;
+ state = parser.ParseBuffer(parseBuffer, parseBuffer.Length, ref bytesConsumed);
+ totalBytesConsumed += bytesConsumed;
+
+ if (state != ParserState.NeedMoreData)
+ {
+ return state;
+ }
+ }
+
+ return state;
+ }
+
+ private static void ValidateResult(HttpUnsortedRequest requestLine, string method, string requestUri, Version version)
+ {
+ Assert.Equal(new HttpMethod(method), requestLine.Method);
+ Assert.Equal(requestUri, requestLine.RequestUri);
+ Assert.Equal(version, requestLine.Version);
+ }
+
+ [Fact]
+ [Trait("Description", "HttpRequestLineParser constructor throws on invalid arguments")]
+ public void HttpRequestLineParserConstructorTest()
+ {
+ HttpUnsortedRequest requestLine = new HttpUnsortedRequest();
+ Assert.NotNull(requestLine);
+
+ Assert.ThrowsArgument(() => { new HttpRequestLineParser(requestLine, ParserData.MinRequestLineSize - 1); }, "maxRequestLineSize");
+
+ HttpRequestLineParser parser = new HttpRequestLineParser(requestLine, ParserData.MinRequestLineSize);
+ Assert.NotNull(parser);
+
+ Assert.ThrowsArgumentNull(() => { new HttpRequestLineParser(null, ParserData.MinRequestLineSize); }, "httpRequest");
+ }
+
+
+ [Fact]
+ [Trait("Description", "HttpRequestLineParser.ParseBuffer throws on null buffer.")]
+ public void RequestLineParserNullBuffer()
+ {
+ HttpUnsortedRequest requestLine = new HttpUnsortedRequest();
+ HttpRequestLineParser parser = new HttpRequestLineParser(requestLine, ParserData.MinRequestLineSize);
+ Assert.NotNull(parser);
+ int bytesConsumed = 0;
+ Assert.ThrowsArgumentNull(() => { parser.ParseBuffer(null, 0, ref bytesConsumed); }, "buffer");
+ }
+
+ [Fact]
+ [Trait("Description", "HttpRequestLineParser.ParseBuffer parses minimum requestline.")]
+ public void RequestLineParserMinimumBuffer()
+ {
+ byte[] data = CreateBuffer("G", "/", "HTTP/1.1");
+ HttpUnsortedRequest requestLine = new HttpUnsortedRequest();
+ HttpRequestLineParser parser = new HttpRequestLineParser(requestLine, ParserData.MinRequestLineSize);
+ Assert.NotNull(parser);
+
+ int bytesConsumed = 0;
+ ParserState state = parser.ParseBuffer(data, data.Length, ref bytesConsumed);
+ Assert.Equal(ParserState.Done, state);
+ Assert.Equal(data.Length, bytesConsumed);
+
+ ValidateResult(requestLine, "G", "/", new Version("1.1"));
+ }
+
+ [Fact]
+ [Trait("Description", "HttpRequestLineParser.ParseBuffer rejects LWS requestline.")]
+ public void RequestLineParserRejectsLws()
+ {
+ byte[] data = CreateBuffer("GET", "/", "HTTP/1.1", true);
+
+ for (var cnt = 1; cnt <= data.Length; cnt++)
+ {
+ HttpUnsortedRequest requestLine = new HttpUnsortedRequest();
+ HttpRequestLineParser parser = new HttpRequestLineParser(requestLine, data.Length);
+ Assert.NotNull(parser);
+
+ int totalBytesConsumed = 0;
+ ParserState state = ParseBufferInSteps(parser, data, cnt, out totalBytesConsumed);
+ Assert.Equal(ParserState.Invalid, state);
+ }
+ }
+
+ [Fact]
+ [Trait("Description", "HttpRequestLineParser.ParseBuffer parses standard methods.")]
+ public void RequestLineParserAcceptsStandardMethods()
+ {
+ foreach (HttpMethod method in HttpUnitTestDataSets.AllHttpMethods)
+ {
+ byte[] data = CreateBuffer(method.ToString(), "/", "HTTP/1.1");
+
+ for (var cnt = 1; cnt <= data.Length; cnt++)
+ {
+ HttpUnsortedRequest requestLine = new HttpUnsortedRequest();
+ HttpRequestLineParser parser = new HttpRequestLineParser(requestLine, data.Length);
+ Assert.NotNull(parser);
+
+ int totalBytesConsumed = 0;
+ ParserState state = ParseBufferInSteps(parser, data, cnt, out totalBytesConsumed);
+ Assert.Equal(ParserState.Done, state);
+ Assert.Equal(data.Length, totalBytesConsumed);
+
+ ValidateResult(requestLine, method.ToString(), "/", new Version("1.1"));
+ }
+ }
+ }
+
+ [Fact]
+ [Trait("Description", "HttpRequestLineParser.ParseBuffer parses custom methods.")]
+ public void RequestLineParserAcceptsCustomMethods()
+ {
+ foreach (HttpMethod method in HttpUnitTestDataSets.CustomHttpMethods)
+ {
+ byte[] data = CreateBuffer(method.ToString(), "/", "HTTP/1.1");
+
+ for (var cnt = 1; cnt <= data.Length; cnt++)
+ {
+ HttpUnsortedRequest requestLine = new HttpUnsortedRequest();
+ HttpRequestLineParser parser = new HttpRequestLineParser(requestLine, data.Length);
+ Assert.NotNull(parser);
+
+ int totalBytesConsumed = 0;
+ ParserState state = ParseBufferInSteps(parser, data, cnt, out totalBytesConsumed);
+ Assert.Equal(ParserState.Done, state);
+ Assert.Equal(data.Length, totalBytesConsumed);
+
+ ValidateResult(requestLine, method.ToString(), "/", new Version("1.1"));
+ }
+ }
+ }
+
+ [Fact]
+ [Trait("Description", "HttpRequestLineParser.ParseBuffer rejects invalid method")]
+ public void RequestLineParserRejectsInvalidMethod()
+ {
+ foreach (string invalidMethod in ParserData.InvalidMethods)
+ {
+ byte[] data = CreateBuffer(invalidMethod, "/", "HTTP/1.1");
+
+ for (var cnt = 1; cnt <= data.Length; cnt++)
+ {
+ HttpUnsortedRequest requestLine = new HttpUnsortedRequest();
+ HttpRequestLineParser parser = new HttpRequestLineParser(requestLine, 256);
+ Assert.NotNull(parser);
+
+ int totalBytesConsumed = 0;
+ ParserState state = ParseBufferInSteps(parser, data, cnt, out totalBytesConsumed);
+ Assert.Equal(ParserState.Invalid, state);
+ }
+ }
+ }
+
+ [Fact]
+ [Trait("Description", "HttpRequestLineParser.ParseBuffer rejects invalid URI.")]
+ public void RequestLineParserRejectsInvalidUri()
+ {
+ foreach (string invalidRequestUri in ParserData.InvalidRequestUris)
+ {
+ byte[] data = CreateBuffer("GET", invalidRequestUri, "HTTP/1.1");
+
+ for (var cnt = 1; cnt <= data.Length; cnt++)
+ {
+ HttpUnsortedRequest requestLine = new HttpUnsortedRequest();
+ HttpRequestLineParser parser = new HttpRequestLineParser(requestLine, 256);
+ Assert.NotNull(parser);
+
+ int totalBytesConsumed = 0;
+ ParserState state = ParseBufferInSteps(parser, data, cnt, out totalBytesConsumed);
+ Assert.Equal(ParserState.Invalid, state);
+ }
+ }
+ }
+
+ public static IEnumerable<object[]> Versions
+ {
+ get { return ParserData.Versions; }
+ }
+
+ [Theory]
+ [PropertyData("Versions")]
+ [Trait("Description", "HttpRequestLineParser.ParseBuffer accepts valid versions.")]
+ public void RequestLineParserAcceptsValidVersion(Version version)
+ {
+ byte[] data = CreateBuffer("GET", "/", String.Format("HTTP/{0}", version.ToString(2)));
+
+ for (var cnt = 1; cnt <= data.Length; cnt++)
+ {
+ HttpUnsortedRequest requestLine = new HttpUnsortedRequest();
+ HttpRequestLineParser parser = new HttpRequestLineParser(requestLine, 256);
+ Assert.NotNull(parser);
+
+ int totalBytesConsumed = 0;
+ ParserState state = ParseBufferInSteps(parser, data, cnt, out totalBytesConsumed);
+ Assert.Equal(data.Length, totalBytesConsumed);
+
+ ValidateResult(requestLine, "GET", "/", version);
+ }
+ }
+
+ public static IEnumerable<object[]> InvalidVersions
+ {
+ get { return ParserData.InvalidVersions; }
+ }
+
+ [Theory]
+ [PropertyData("InvalidVersions")]
+ [Trait("Description", "HttpRequestLineParser.ParseBuffer rejects invalid protocol version.")]
+ public void RequestLineParserRejectsInvalidVersion(string invalidVersion)
+ {
+ byte[] data = CreateBuffer("GET", "/", invalidVersion);
+
+ for (var cnt = 1; cnt <= data.Length; cnt++)
+ {
+ HttpUnsortedRequest requestLine = new HttpUnsortedRequest();
+ HttpRequestLineParser parser = new HttpRequestLineParser(requestLine, 256);
+ Assert.NotNull(parser);
+
+ int totalBytesConsumed = 0;
+ ParserState state = ParseBufferInSteps(parser, data, cnt, out totalBytesConsumed);
+ Assert.Equal(ParserState.Invalid, state);
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/test/System.Net.Http.Formatting.Test.Unit/Formatting/Parsers/HttpResponseHeaderParserTests.cs b/test/System.Net.Http.Formatting.Test.Unit/Formatting/Parsers/HttpResponseHeaderParserTests.cs
new file mode 100644
index 00000000..a82cd032
--- /dev/null
+++ b/test/System.Net.Http.Formatting.Test.Unit/Formatting/Parsers/HttpResponseHeaderParserTests.cs
@@ -0,0 +1,272 @@
+using System.Collections.Generic;
+using System.Linq;
+using System.Net.Http.Formatting.DataSets;
+using System.Text;
+using Microsoft.TestCommon;
+using Xunit;
+using Xunit.Extensions;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Net.Http.Formatting.Parsers
+{
+ public class HttpResponseHeaderParserTests
+ {
+ [Fact]
+ [Trait("Description", "HttpResponseHeaderParser is internal class")]
+ public void TypeIsCorrect()
+ {
+ Assert.Type.HasProperties<HttpResponseHeaderParser>(TypeAssert.TypeProperties.IsClass);
+ }
+
+ private static byte[] CreateBuffer(string version, string statusCode, string reasonPhrase, Dictionary<string, string> headers)
+ {
+ const string SP = " ";
+ const string CRLF = "\r\n";
+ string lws = SP;
+
+ StringBuilder response = new StringBuilder();
+ response.AppendFormat("{0}{1}{2}{3}{4}{5}", version, lws, statusCode, lws, reasonPhrase, CRLF);
+ if (headers != null)
+ {
+ foreach (var h in headers)
+ {
+ response.AppendFormat("{0}: {1}{2}", h.Key, h.Value, CRLF);
+ }
+ }
+
+ response.Append(CRLF);
+ return Encoding.UTF8.GetBytes(response.ToString());
+ }
+
+ private static ParserState ParseBufferInSteps(HttpResponseHeaderParser parser, byte[] buffer, int readsize, out int totalBytesConsumed)
+ {
+ ParserState state = ParserState.Invalid;
+ totalBytesConsumed = 0;
+ while (totalBytesConsumed <= buffer.Length)
+ {
+ int size = Math.Min(buffer.Length - totalBytesConsumed, readsize);
+ byte[] parseBuffer = new byte[size];
+ Buffer.BlockCopy(buffer, totalBytesConsumed, parseBuffer, 0, size);
+
+ int bytesConsumed = 0;
+ state = parser.ParseBuffer(parseBuffer, parseBuffer.Length, ref bytesConsumed);
+ totalBytesConsumed += bytesConsumed;
+
+ if (state != ParserState.NeedMoreData)
+ {
+ return state;
+ }
+ }
+
+ return state;
+ }
+
+ private static void ValidateResult(
+ HttpUnsortedResponse statusLine,
+ Version version,
+ HttpStatusCode statusCode,
+ string reasonPhrase,
+ Dictionary<string, string> headers)
+ {
+ Assert.Equal(version, statusLine.Version);
+ Assert.Equal(statusCode, statusLine.StatusCode);
+ Assert.Equal(reasonPhrase, statusLine.ReasonPhrase);
+
+ if (headers != null)
+ {
+ Assert.Equal(headers.Count, statusLine.HttpHeaders.Count());
+ foreach (var header in headers)
+ {
+ Assert.True(statusLine.HttpHeaders.Contains(header.Key), "Parsed header did not contain expected key " + header.Key);
+ Assert.Equal(header.Value, statusLine.HttpHeaders.GetValues(header.Key).ElementAt(0));
+ }
+ }
+ }
+
+ [Fact]
+ [Trait("Description", "HttpResponseHeaderParser constructor throws on invalid arguments")]
+ public void HttpResponseHeaderParserConstructorTest()
+ {
+ HttpUnsortedResponse result = new HttpUnsortedResponse();
+ Assert.NotNull(result);
+
+ Assert.ThrowsArgument(() => { new HttpResponseHeaderParser(result, ParserData.MinStatusLineSize - 1, ParserData.MinHeaderSize); }, "maxStatusLineSize");
+
+ Assert.ThrowsArgument(() => { new HttpResponseHeaderParser(result, ParserData.MinStatusLineSize, ParserData.MinHeaderSize - 1); }, "maxHeaderSize");
+
+ HttpResponseHeaderParser parser = new HttpResponseHeaderParser(result, ParserData.MinStatusLineSize, ParserData.MinHeaderSize);
+ Assert.NotNull(parser);
+
+ Assert.ThrowsArgumentNull(() => { new HttpResponseHeaderParser(null); }, "httpResponse");
+ }
+
+
+ [Fact]
+ [Trait("Description", "HttpResponseHeaderParser.ParseBuffer throws on null buffer.")]
+ public void ResponseHeaderParserNullBuffer()
+ {
+ HttpUnsortedResponse result = new HttpUnsortedResponse();
+ HttpResponseHeaderParser parser = new HttpResponseHeaderParser(result, ParserData.MinStatusLineSize, ParserData.MinHeaderSize);
+ Assert.NotNull(parser);
+ int bytesConsumed = 0;
+ Assert.ThrowsArgumentNull(() => { parser.ParseBuffer(null, 0, ref bytesConsumed); }, "buffer");
+ }
+
+ [Fact]
+ [Trait("Description", "HttpResponseHeaderParser.ParseBuffer parses minimum statusLine.")]
+ public void ResponseHeaderParserMinimumBuffer()
+ {
+ byte[] data = CreateBuffer("HTTP/1.1", "200", "", null);
+ HttpUnsortedResponse result = new HttpUnsortedResponse();
+ HttpResponseHeaderParser parser = new HttpResponseHeaderParser(result, ParserData.MinStatusLineSize, ParserData.MinHeaderSize);
+ Assert.NotNull(parser);
+
+ int bytesConsumed = 0;
+ ParserState state = parser.ParseBuffer(data, data.Length, ref bytesConsumed);
+ Assert.Equal(ParserState.Done, state);
+ Assert.Equal(data.Length, bytesConsumed);
+
+ ValidateResult(result, new Version("1.1"), HttpStatusCode.OK, "", null);
+ }
+
+ [Fact]
+ [Trait("Description", "HttpResponseHeaderParser.ParseBuffer parses standard status codes.")]
+ public void ResponseHeaderParserAcceptsStandardStatusCodes()
+ {
+ foreach (HttpStatusCode status in HttpUnitTestDataSets.AllHttpStatusCodes)
+ {
+ byte[] data = CreateBuffer("HTTP/1.1", ((int)status).ToString(), "Reason", ParserData.ValidHeaders);
+
+ for (var cnt = 1; cnt <= data.Length; cnt++)
+ {
+ HttpUnsortedResponse result = new HttpUnsortedResponse();
+ HttpResponseHeaderParser parser = new HttpResponseHeaderParser(result);
+ Assert.NotNull(parser);
+
+ int totalBytesConsumed = 0;
+ ParserState state = ParseBufferInSteps(parser, data, cnt, out totalBytesConsumed);
+ Assert.Equal(ParserState.Done, state);
+ Assert.Equal(data.Length, totalBytesConsumed);
+
+ ValidateResult(result, new Version("1.1"), status, "Reason", ParserData.ValidHeaders);
+ }
+ }
+ }
+
+ [Fact]
+ [Trait("Description", "HttpResponseHeaderParser.ParseBuffer parses custom status codes.")]
+ public void ResponseHeaderParserAcceptsCustomStatusCodes()
+ {
+ foreach (HttpStatusCode status in HttpUnitTestDataSets.CustomHttpStatusCodes)
+ {
+ byte[] data = CreateBuffer("HTTP/1.1", ((int)status).ToString(), "Reason", ParserData.ValidHeaders);
+
+ for (var cnt = 1; cnt <= data.Length; cnt++)
+ {
+ HttpUnsortedResponse result = new HttpUnsortedResponse();
+ HttpResponseHeaderParser parser = new HttpResponseHeaderParser(result);
+ Assert.NotNull(parser);
+
+ int totalBytesConsumed = 0;
+ ParserState state = ParseBufferInSteps(parser, data, cnt, out totalBytesConsumed);
+ Assert.Equal(ParserState.Done, state);
+ Assert.Equal(data.Length, totalBytesConsumed);
+
+ ValidateResult(result, new Version("1.1"), status, "Reason", ParserData.ValidHeaders);
+ }
+ }
+ }
+
+ [Fact]
+ [Trait("Description", "HttpResponseHeaderParser.ParseBuffer rejects invalid status codes")]
+ public void ResponseHeaderParserRejectsInvalidStatusCodes()
+ {
+ foreach (string invalidStatus in ParserData.InvalidStatusCodes)
+ {
+ byte[] data = CreateBuffer("HTTP/1.1", invalidStatus, "Reason", ParserData.ValidHeaders);
+
+ for (var cnt = 1; cnt <= data.Length; cnt++)
+ {
+ HttpUnsortedResponse result = new HttpUnsortedResponse();
+ HttpResponseHeaderParser parser = new HttpResponseHeaderParser(result);
+ Assert.NotNull(parser);
+
+ int totalBytesConsumed = 0;
+ ParserState state = ParseBufferInSteps(parser, data, cnt, out totalBytesConsumed);
+ Assert.Equal(ParserState.Invalid, state);
+ }
+ }
+ }
+
+ [Fact]
+ [Trait("Description", "HttpResponseHeaderParser.ParseBuffer rejects invalid reason phrase.")]
+ public void ResponseHeaderParserRejectsInvalidReasonPhrase()
+ {
+ foreach (string invalidReason in ParserData.InvalidReasonPhrases)
+ {
+ byte[] data = CreateBuffer("HTTP/1.1", "200", invalidReason, ParserData.ValidHeaders);
+
+ for (var cnt = 1; cnt <= data.Length; cnt++)
+ {
+ HttpUnsortedResponse result = new HttpUnsortedResponse();
+ HttpResponseHeaderParser parser = new HttpResponseHeaderParser(result);
+ Assert.NotNull(parser);
+
+ int totalBytesConsumed = 0;
+ ParserState state = ParseBufferInSteps(parser, data, cnt, out totalBytesConsumed);
+ Assert.Equal(ParserState.Invalid, state);
+ }
+ }
+ }
+
+ public static IEnumerable<object[]> Versions
+ {
+ get { return ParserData.Versions; }
+ }
+
+ [Theory]
+ [PropertyData("Versions")]
+ [Trait("Description", "HttpResponseHeaderParser.ParseBuffer accepts valid versions.")]
+ public void ResponseHeaderParserAcceptsValidVersion(Version version)
+ {
+ byte[] data = CreateBuffer(String.Format("HTTP/{0}", version.ToString(2)), "200", "Reason", ParserData.ValidHeaders);
+
+ for (var cnt = 1; cnt <= data.Length; cnt++)
+ {
+ HttpUnsortedResponse result = new HttpUnsortedResponse();
+ HttpResponseHeaderParser parser = new HttpResponseHeaderParser(result);
+ Assert.NotNull(parser);
+
+ int totalBytesConsumed = 0;
+ ParserState state = ParseBufferInSteps(parser, data, cnt, out totalBytesConsumed);
+ Assert.Equal(data.Length, totalBytesConsumed);
+
+ ValidateResult(result, version, HttpStatusCode.OK, "Reason", ParserData.ValidHeaders);
+ }
+ }
+
+ public static IEnumerable<object[]> InvalidVersions
+ {
+ get { return ParserData.InvalidVersions; }
+ }
+
+ [Theory]
+ [PropertyData("InvalidVersions")]
+ [Trait("Description", "HttpResponseHeaderParser.ParseBuffer rejects invalid protocol version.")]
+ public void ResponseHeaderParserRejectsInvalidVersion(string invalid)
+ {
+ byte[] data = CreateBuffer(invalid, "200", "Reason", ParserData.ValidHeaders);
+
+ for (var cnt = 1; cnt <= data.Length; cnt++)
+ {
+ HttpUnsortedResponse result = new HttpUnsortedResponse();
+ HttpResponseHeaderParser parser = new HttpResponseHeaderParser(result);
+ Assert.NotNull(parser);
+
+ int totalBytesConsumed = 0;
+ ParserState state = ParseBufferInSteps(parser, data, cnt, out totalBytesConsumed);
+ Assert.Equal(ParserState.Invalid, state);
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/test/System.Net.Http.Formatting.Test.Unit/Formatting/Parsers/HttpStatusLineParserTests.cs b/test/System.Net.Http.Formatting.Test.Unit/Formatting/Parsers/HttpStatusLineParserTests.cs
new file mode 100644
index 00000000..c7057b12
--- /dev/null
+++ b/test/System.Net.Http.Formatting.Test.Unit/Formatting/Parsers/HttpStatusLineParserTests.cs
@@ -0,0 +1,284 @@
+using System.Collections.Generic;
+using System.Net.Http.Formatting.DataSets;
+using System.Text;
+using Microsoft.TestCommon;
+using Xunit;
+using Xunit.Extensions;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Net.Http.Formatting.Parsers
+{
+ public class HttpStatusLineParserTests
+ {
+ [Fact]
+ [Trait("Description", "HttpStatusLineParser is internal class")]
+ public void TypeIsCorrect()
+ {
+ Assert.Type.HasProperties<HttpStatusLineParser>(TypeAssert.TypeProperties.IsClass);
+ }
+
+ internal static byte[] CreateBuffer(string version, string statusCode, string reasonPhrase)
+ {
+ return CreateBuffer(version, statusCode, reasonPhrase, false);
+ }
+
+ private static byte[] CreateBuffer(string version, string statusCode, string reasonPhrase, bool withLws)
+ {
+ const string SP = " ";
+ const string HTAB = "\t";
+ const string CRLF = "\r\n";
+
+ string lws = SP;
+ if (withLws)
+ {
+ lws = SP + SP + HTAB + SP;
+ }
+
+ string statusLine = String.Format("{0}{1}{2}{3}{4}{5}", version, lws, statusCode, lws, reasonPhrase, CRLF);
+ return Encoding.UTF8.GetBytes(statusLine);
+ }
+
+ private static ParserState ParseBufferInSteps(HttpStatusLineParser parser, byte[] buffer, int readsize, out int totalBytesConsumed)
+ {
+ ParserState state = ParserState.Invalid;
+ totalBytesConsumed = 0;
+ while (totalBytesConsumed <= buffer.Length)
+ {
+ int size = Math.Min(buffer.Length - totalBytesConsumed, readsize);
+ byte[] parseBuffer = new byte[size];
+ Buffer.BlockCopy(buffer, totalBytesConsumed, parseBuffer, 0, size);
+
+ int bytesConsumed = 0;
+ state = parser.ParseBuffer(parseBuffer, parseBuffer.Length, ref bytesConsumed);
+ totalBytesConsumed += bytesConsumed;
+
+ if (state != ParserState.NeedMoreData)
+ {
+ return state;
+ }
+ }
+
+ return state;
+ }
+
+ private static void ValidateResult(HttpUnsortedResponse statusLine, Version version, HttpStatusCode statusCode, string reasonPhrase)
+ {
+ Assert.Equal(version, statusLine.Version);
+ Assert.Equal(statusCode, statusLine.StatusCode);
+ Assert.Equal(reasonPhrase, statusLine.ReasonPhrase);
+ }
+
+ [Fact]
+ [Trait("Description", "HttpStatusLineParser constructor throws on invalid arguments")]
+ public void HttpStatusLineParserConstructorTest()
+ {
+ HttpUnsortedResponse statusLine = new HttpUnsortedResponse();
+ Assert.NotNull(statusLine);
+
+ Assert.ThrowsArgument(() => { new HttpStatusLineParser(statusLine, ParserData.MinStatusLineSize - 1); }, "maxStatusLineSize");
+
+ HttpStatusLineParser parser = new HttpStatusLineParser(statusLine, ParserData.MinStatusLineSize);
+ Assert.NotNull(parser);
+
+ Assert.ThrowsArgumentNull(() => { new HttpStatusLineParser(null, ParserData.MinStatusLineSize); }, "httpResponse");
+ }
+
+
+ [Fact]
+ [Trait("Description", "HttpStatusLineParser.ParseBuffer throws on null buffer.")]
+ public void StatusLineParserNullBuffer()
+ {
+ HttpUnsortedResponse statusLine = new HttpUnsortedResponse();
+ HttpStatusLineParser parser = new HttpStatusLineParser(statusLine, ParserData.MinStatusLineSize);
+ Assert.NotNull(parser);
+ int bytesConsumed = 0;
+ Assert.ThrowsArgumentNull(() => { parser.ParseBuffer(null, 0, ref bytesConsumed); }, "buffer");
+ }
+
+ [Fact]
+ [Trait("Description", "HttpStatusLineParser.ParseBuffer parses minimum requestline.")]
+ public void StatusLineParserMinimumBuffer()
+ {
+ byte[] data = CreateBuffer("HTTP/1.1", "200", "");
+ HttpUnsortedResponse statusLine = new HttpUnsortedResponse();
+ HttpStatusLineParser parser = new HttpStatusLineParser(statusLine, ParserData.MinStatusLineSize);
+ Assert.NotNull(parser);
+
+ int bytesConsumed = 0;
+ ParserState state = parser.ParseBuffer(data, data.Length, ref bytesConsumed);
+ Assert.Equal(ParserState.Done, state);
+ Assert.Equal(data.Length, bytesConsumed);
+
+ ValidateResult(statusLine, new Version("1.1"), HttpStatusCode.OK, "");
+ }
+
+ [Fact]
+ [Trait("Description", "HttpStatusLineParser.ParseBuffer rejects LWS requestline.")]
+ public void StatusLineParserRejectsLws()
+ {
+ byte[] data = CreateBuffer("HTTP/1.1", "200", "Reason", true);
+ for (var cnt = 1; cnt <= data.Length; cnt++)
+ {
+ HttpUnsortedResponse statusLine = new HttpUnsortedResponse();
+ HttpStatusLineParser parser = new HttpStatusLineParser(statusLine, data.Length);
+ Assert.NotNull(parser);
+
+ int totalBytesConsumed = 0;
+ ParserState state = ParseBufferInSteps(parser, data, cnt, out totalBytesConsumed);
+ Assert.Equal(ParserState.Invalid, state);
+ }
+ }
+
+ [Fact]
+ [Trait("Description", "HttpStatusLineParser.ParseBuffer parses standard status codes.")]
+ public void StatusLineParserAcceptsStandardStatusCodes()
+ {
+ foreach (HttpStatusCode status in HttpUnitTestDataSets.AllHttpStatusCodes)
+ {
+ byte[] data = CreateBuffer("HTTP/1.1", ((int)status).ToString(), "Reason");
+
+ for (var cnt = 1; cnt <= data.Length; cnt++)
+ {
+ HttpUnsortedResponse statusLine = new HttpUnsortedResponse();
+ HttpStatusLineParser parser = new HttpStatusLineParser(statusLine, data.Length);
+ Assert.NotNull(parser);
+
+ int totalBytesConsumed = 0;
+ ParserState state = ParseBufferInSteps(parser, data, cnt, out totalBytesConsumed);
+ Assert.Equal(ParserState.Done, state);
+ Assert.Equal(data.Length, totalBytesConsumed);
+
+ ValidateResult(statusLine, new Version("1.1"), status, "Reason");
+ }
+ }
+ }
+
+ [Fact]
+ [Trait("Description", "HttpStatusLineParser.ParseBuffer parses custom status codes.")]
+ public void StatusLineParserAcceptsCustomStatusCodes()
+ {
+ foreach (HttpStatusCode status in HttpUnitTestDataSets.CustomHttpStatusCodes)
+ {
+ byte[] data = CreateBuffer("HTTP/1.1", ((int)status).ToString(), "Reason");
+
+ for (var cnt = 1; cnt <= data.Length; cnt++)
+ {
+ HttpUnsortedResponse statusLine = new HttpUnsortedResponse();
+ HttpStatusLineParser parser = new HttpStatusLineParser(statusLine, data.Length);
+ Assert.NotNull(parser);
+
+ int totalBytesConsumed = 0;
+ ParserState state = ParseBufferInSteps(parser, data, cnt, out totalBytesConsumed);
+ Assert.Equal(ParserState.Done, state);
+ Assert.Equal(data.Length, totalBytesConsumed);
+
+ ValidateResult(statusLine, new Version("1.1"), status, "Reason");
+ }
+ }
+ }
+
+ [Fact]
+ [Trait("Description", "HttpStatusLineParser.ParseBuffer rejects invalid status codes")]
+ public void StatusLineParserRejectsInvalidStatusCodes()
+ {
+ foreach (string invalidStatus in ParserData.InvalidStatusCodes)
+ {
+ byte[] data = CreateBuffer("HTTP/1.1", invalidStatus, "Reason");
+
+ for (var cnt = 1; cnt <= data.Length; cnt++)
+ {
+ HttpUnsortedResponse statusLine = new HttpUnsortedResponse();
+ HttpStatusLineParser parser = new HttpStatusLineParser(statusLine, 256);
+ Assert.NotNull(parser);
+
+ int totalBytesConsumed = 0;
+ ParserState state = ParseBufferInSteps(parser, data, cnt, out totalBytesConsumed);
+ Assert.Equal(ParserState.Invalid, state);
+ }
+ }
+ }
+
+ public static IEnumerable<object[]> ValidReasonPhrases
+ {
+ get
+ {
+ yield return new object[] { "" };
+ yield return new object[] { "Ok" };
+ yield return new object[] { "public Server Error" };
+ yield return new object[] { "r e a s o n" };
+ yield return new object[] { "reason " };
+ yield return new object[] { " reason " };
+ }
+ }
+
+ [Theory]
+ [PropertyData("ValidReasonPhrases")]
+ [Trait("Description", "HttpStatusLineParser.ParseBuffer accepts valid reason phrase.")]
+ public void StatusLineParserAcceptsValidReasonPhrase(string validReasonPhrase)
+ {
+ byte[] data = CreateBuffer("HTTP/1.1", "200", validReasonPhrase);
+
+ for (var cnt = 1; cnt <= data.Length; cnt++)
+ {
+ HttpUnsortedResponse statusLine = new HttpUnsortedResponse();
+ HttpStatusLineParser parser = new HttpStatusLineParser(statusLine, 256);
+ Assert.NotNull(parser);
+
+ int totalBytesConsumed = 0;
+ ParserState state = ParseBufferInSteps(parser, data, cnt, out totalBytesConsumed);
+
+ ValidateResult(statusLine, new Version("1.1"), HttpStatusCode.OK, validReasonPhrase);
+ }
+ }
+
+ public static IEnumerable<object[]> Versions
+ {
+ get { return ParserData.Versions; }
+ }
+
+ [Theory]
+ [PropertyData("Versions")]
+ [Trait("Description", "HttpStatusLineParser.ParseBuffer accepts valid versions.")]
+ public void StatusLineParserAcceptsValidVersion(Version version)
+ {
+ byte[] data = CreateBuffer(String.Format("HTTP/{0}", version.ToString(2)), "200", "Reason");
+
+ for (var cnt = 1; cnt <= data.Length; cnt++)
+ {
+ HttpUnsortedResponse statusLine = new HttpUnsortedResponse();
+ HttpStatusLineParser parser = new HttpStatusLineParser(statusLine, 256);
+ Assert.NotNull(parser);
+
+ int totalBytesConsumed = 0;
+ ParserState state = ParseBufferInSteps(parser, data, cnt, out totalBytesConsumed);
+ Assert.Equal(data.Length, totalBytesConsumed);
+
+ ValidateResult(statusLine, version, HttpStatusCode.OK, "Reason");
+ }
+ }
+
+ public static IEnumerable<object[]> InvalidVersions
+ {
+ get { return ParserData.InvalidVersions; }
+ }
+
+ [Theory]
+ [PropertyData("InvalidVersions")]
+ [Trait("Description", "HttpStatusLineParser.ParseBuffer rejects invalid protocol version.")]
+ public void StatusLineParserRejectsInvalidVersion(string invalidVersion)
+ {
+ byte[] data = CreateBuffer(invalidVersion, "200", "Reason");
+
+ for (var cnt = 1; cnt <= data.Length; cnt++)
+ {
+ HttpUnsortedResponse statusLine = new HttpUnsortedResponse();
+ HttpStatusLineParser parser = new HttpStatusLineParser(statusLine, 256);
+ Assert.NotNull(parser);
+
+ int totalBytesConsumed = 0;
+ ParserState state = ParseBufferInSteps(parser, data, cnt, out totalBytesConsumed);
+ Assert.Equal(ParserState.Invalid, state);
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/test/System.Net.Http.Formatting.Test.Unit/Formatting/Parsers/InternetMessageFormatHeaderParserTests.cs b/test/System.Net.Http.Formatting.Test.Unit/Formatting/Parsers/InternetMessageFormatHeaderParserTests.cs
new file mode 100644
index 00000000..786d0b7e
--- /dev/null
+++ b/test/System.Net.Http.Formatting.Test.Unit/Formatting/Parsers/InternetMessageFormatHeaderParserTests.cs
@@ -0,0 +1,664 @@
+using System.Collections.Generic;
+using System.Linq;
+using System.Net.Http.Headers;
+using System.Text;
+using Microsoft.TestCommon;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Net.Http.Formatting.Parsers
+{
+ public class InternetMessageFormatHeaderParserTests
+ {
+ [Fact]
+ [Trait("Description", "HeaderParser is internal class")]
+ public void TypeIsCorrect()
+ {
+ Assert.Type.HasProperties<InternetMessageFormatHeaderParser>(TypeAssert.TypeProperties.IsClass);
+ }
+
+ private static IEnumerable<HttpHeaders> CreateHttpHeaders()
+ {
+ return new HttpHeaders[]
+ {
+ new HttpRequestMessage().Headers,
+ new HttpResponseMessage().Headers,
+ new StringContent(String.Empty).Headers,
+ };
+ }
+
+ private static InternetMessageFormatHeaderParser CreateHeaderParser(int maximumHeaderLength, out HttpHeaders headers)
+ {
+ headers = new HttpRequestMessage().Headers;
+ return new InternetMessageFormatHeaderParser(headers, maximumHeaderLength);
+ }
+
+ internal static byte[] CreateBuffer(params string[] headers)
+ {
+ const string CRLF = "\r\n";
+ StringBuilder header = new StringBuilder();
+ foreach (var h in headers)
+ {
+ header.Append(h + CRLF);
+ }
+
+ header.Append(CRLF);
+ return Encoding.UTF8.GetBytes(header.ToString());
+ }
+
+ private static void RunRfc5322SampleTest(string[] testHeaders, Action<HttpHeaders> validation)
+ {
+ byte[] data = InternetMessageFormatHeaderParserTests.CreateBuffer(testHeaders);
+ for (var cnt = 1; cnt <= data.Length; cnt++)
+ {
+ HttpHeaders headers;
+ InternetMessageFormatHeaderParser parser = InternetMessageFormatHeaderParserTests.CreateHeaderParser(data.Length, out headers);
+ Assert.NotNull(parser);
+
+ int totalBytesConsumed = 0;
+ ParserState state = InternetMessageFormatHeaderParserTests.ParseBufferInSteps(parser, data, cnt, out totalBytesConsumed);
+ Assert.Equal(ParserState.Done, state);
+ Assert.Equal(data.Length, totalBytesConsumed);
+
+ validation(headers);
+ }
+ }
+
+ private static ParserState ParseBufferInSteps(InternetMessageFormatHeaderParser parser, byte[] buffer, int readsize, out int totalBytesConsumed)
+ {
+ ParserState state = ParserState.Invalid;
+ totalBytesConsumed = 0;
+ while (totalBytesConsumed <= buffer.Length)
+ {
+ int size = Math.Min(buffer.Length - totalBytesConsumed, readsize);
+ byte[] parseBuffer = new byte[size];
+ Buffer.BlockCopy(buffer, totalBytesConsumed, parseBuffer, 0, size);
+
+ int bytesConsumed = 0;
+ state = parser.ParseBuffer(parseBuffer, parseBuffer.Length, ref bytesConsumed);
+ totalBytesConsumed += bytesConsumed;
+
+ if (state != ParserState.NeedMoreData)
+ {
+ return state;
+ }
+ }
+
+ return state;
+ }
+
+
+ [Fact]
+ [Trait("Description", "HeaderParser constructor throws on invalid arguments")]
+ public void HeaderParserConstructorTest()
+ {
+ IEnumerable<HttpHeaders> headers = InternetMessageFormatHeaderParserTests.CreateHttpHeaders();
+ foreach (var header in headers)
+ {
+ InternetMessageFormatHeaderParser parser = new InternetMessageFormatHeaderParser(header, ParserData.MinHeaderSize);
+ Assert.NotNull(parser);
+ }
+
+ Assert.ThrowsArgument(() => { new InternetMessageFormatHeaderParser(headers.ElementAt(0), -1); }, "maxHeaderSize");
+ Assert.ThrowsArgument(() => { new InternetMessageFormatHeaderParser(headers.ElementAt(0), 0); }, "maxHeaderSize");
+ Assert.ThrowsArgument(() => { new InternetMessageFormatHeaderParser(headers.ElementAt(0), 1); }, "maxHeaderSize");
+
+ Assert.ThrowsArgumentNull(() => { new InternetMessageFormatHeaderParser(null, ParserData.MinHeaderSize); }, "headers");
+ }
+
+
+ [Fact]
+ [Trait("Description", "HeaderParser.ParseBuffer throws on null buffer.")]
+ public void HeaderParserNullBuffer()
+ {
+ HttpHeaders headers;
+ InternetMessageFormatHeaderParser parser = InternetMessageFormatHeaderParserTests.CreateHeaderParser(128, out headers);
+ Assert.NotNull(parser);
+ int bytesConsumed = 0;
+ Assert.ThrowsArgumentNull(() => { parser.ParseBuffer(null, 0, ref bytesConsumed); }, "buffer");
+ }
+
+ [Fact]
+ [Trait("Description", "HeaderParser.ParseBuffer parses empty header.")]
+ public void HeaderParserEmptyBuffer()
+ {
+ byte[] data = InternetMessageFormatHeaderParserTests.CreateBuffer();
+ HttpHeaders headers;
+ InternetMessageFormatHeaderParser parser = InternetMessageFormatHeaderParserTests.CreateHeaderParser(data.Length, out headers);
+ Assert.NotNull(parser);
+
+ int bytesConsumed = 0;
+ ParserState state = parser.ParseBuffer(data, data.Length, ref bytesConsumed);
+ Assert.Equal(ParserState.Done, state);
+ Assert.Equal(data.Length, bytesConsumed);
+
+ Assert.Equal(0, headers.Count());
+ }
+
+ [Fact]
+ [Trait("Description", "HeaderParser.ParseBuffer parses single header field.")]
+ public void HeaderParserSingleNameValueHeader()
+ {
+ byte[] data = InternetMessageFormatHeaderParserTests.CreateBuffer("N:V");
+
+ for (var cnt = 1; cnt <= data.Length; cnt++)
+ {
+ HttpHeaders headers;
+ InternetMessageFormatHeaderParser parser = InternetMessageFormatHeaderParserTests.CreateHeaderParser(data.Length, out headers);
+ Assert.NotNull(parser);
+
+ int totalBytesConsumed;
+ ParserState state = InternetMessageFormatHeaderParserTests.ParseBufferInSteps(parser, data, cnt, out totalBytesConsumed);
+ Assert.Equal(ParserState.Done, state);
+ Assert.Equal(data.Length, totalBytesConsumed);
+
+ Assert.Equal(1, headers.Count());
+ IEnumerable<string> parsedValues = headers.GetValues("N");
+ Assert.Equal(1, parsedValues.Count());
+ Assert.Equal(parsedValues.ElementAt(0), "V");
+ }
+ }
+
+ [Fact]
+ [Trait("Description", "HeaderParser.ParseBuffer parses single header field with name only.")]
+ public void HeaderParserSingleNameHeader()
+ {
+ byte[] data = InternetMessageFormatHeaderParserTests.CreateBuffer("N:");
+
+ for (var cnt = 1; cnt <= data.Length; cnt++)
+ {
+ HttpHeaders headers;
+ InternetMessageFormatHeaderParser parser = InternetMessageFormatHeaderParserTests.CreateHeaderParser(data.Length, out headers);
+ Assert.NotNull(parser);
+
+ int totalBytesConsumed;
+ ParserState state = InternetMessageFormatHeaderParserTests.ParseBufferInSteps(parser, data, cnt, out totalBytesConsumed);
+ Assert.Equal(ParserState.Done, state);
+ Assert.Equal(data.Length, totalBytesConsumed);
+
+ Assert.Equal(1, headers.Count());
+ IEnumerable<string> parsedValues = headers.GetValues("N");
+ Assert.Equal(1, parsedValues.Count());
+ Assert.Equal("", parsedValues.ElementAt(0));
+ }
+ }
+
+ [Fact]
+ [Trait("Description", "HeaderParser.ParseBuffer parses multiple header fields.")]
+ public void HeaderParserMultipleNameHeader()
+ {
+ byte[] data = InternetMessageFormatHeaderParserTests.CreateBuffer("N:V1", "N:V2");
+
+ for (var cnt = 1; cnt <= data.Length; cnt++)
+ {
+ HttpHeaders headers;
+ InternetMessageFormatHeaderParser parser = InternetMessageFormatHeaderParserTests.CreateHeaderParser(data.Length, out headers);
+ Assert.NotNull(parser);
+
+ int totalBytesConsumed;
+ ParserState state = InternetMessageFormatHeaderParserTests.ParseBufferInSteps(parser, data, cnt, out totalBytesConsumed);
+ Assert.Equal(ParserState.Done, state);
+ Assert.Equal(data.Length, totalBytesConsumed);
+
+ Assert.Equal(1, headers.Count());
+ IEnumerable<string> parsedValues = headers.GetValues("N");
+ Assert.Equal(2, parsedValues.Count());
+ Assert.Equal("V1", parsedValues.ElementAt(0));
+ Assert.Equal("V2", parsedValues.ElementAt(1));
+ }
+ }
+
+ [Fact]
+ [Trait("Description", "HeaderParser.ParseBuffer parses multiple header fields with linear white space.")]
+ public void HeaderParserLwsHeader()
+ {
+ byte[] data = InternetMessageFormatHeaderParserTests.CreateBuffer("N1:V1", "N2: V2", "N3:\tV3");
+
+ for (var cnt = 1; cnt <= data.Length; cnt++)
+ {
+ HttpHeaders headers;
+ InternetMessageFormatHeaderParser parser = InternetMessageFormatHeaderParserTests.CreateHeaderParser(data.Length, out headers);
+ Assert.NotNull(parser);
+
+ int totalBytesConsumed = 0;
+ ParserState state = InternetMessageFormatHeaderParserTests.ParseBufferInSteps(parser, data, cnt, out totalBytesConsumed);
+ Assert.Equal(ParserState.Done, state);
+ Assert.Equal(data.Length, totalBytesConsumed);
+
+ Assert.Equal(3, headers.Count());
+
+ IEnumerable<string> parsedValues = headers.GetValues("N1");
+ Assert.Equal(1, parsedValues.Count());
+ Assert.Equal("V1", parsedValues.ElementAt(0));
+
+ parsedValues = headers.GetValues("N2");
+ Assert.Equal(1, parsedValues.Count());
+ Assert.Equal("V2", parsedValues.ElementAt(0));
+
+ parsedValues = headers.GetValues("N3");
+ Assert.Equal(1, parsedValues.Count());
+ Assert.Equal("V3", parsedValues.ElementAt(0));
+ }
+ }
+
+ [Fact]
+ [Trait("Description", "HeaderParser.ParseBuffer parses invalid header field.")]
+ public void HeaderParserInvalidHeader()
+ {
+ byte[] data = InternetMessageFormatHeaderParserTests.CreateBuffer("N1 :V1");
+
+ for (var cnt = 1; cnt <= data.Length; cnt++)
+ {
+ HttpHeaders headers;
+ InternetMessageFormatHeaderParser parser = InternetMessageFormatHeaderParserTests.CreateHeaderParser(data.Length, out headers);
+ Assert.NotNull(parser);
+
+ int totalBytesConsumed = 0;
+ ParserState state = InternetMessageFormatHeaderParserTests.ParseBufferInSteps(parser, data, cnt, out totalBytesConsumed);
+ Assert.Equal(ParserState.Invalid, state);
+ Assert.Equal(data.Length - 2, totalBytesConsumed);
+ }
+ }
+
+ [Fact]
+ [Trait("Description", "HeaderParser.ParseBuffer parses various specialized header fields including JSON, P3P, etc.")]
+ public void HeaderParserSpecializedHeaders()
+ {
+ Dictionary<string, string> headerData = new Dictionary<string, string>
+ {
+ { @"JsonProperties0", @"{ ""SessionId"": ""{27729E1-B37B-4D29-AA0A-E367906C206E}"", ""MessageId"": ""{701332E1-B37B-4D29-AA0A-E367906C206E}"", ""TimeToLive"" : 90, ""CorrelationId"": ""{701332F3-B37B-4D29-AA0A-E367906C206E}"", ""SequenceNumber"" : 12345, ""DeliveryCount"" : 2, ""To"" : ""http://contoso.com/path1"", ""ReplyTo"" : ""http://fabrikam.com/path1"", ""SentTimeUtc"" : ""Sun, 06 Nov 1994 08:49:37 GMT"", ""ScheduledEnqueueTimeUtc"" : ""Sun, 06 Nov 1994 08:49:37 GMT""}" },
+ { @"JsonProperties1", @"{ ""SessionId"": ""{2813D4D2-46A9-4F4D-8904-E9BDE3712B70}"", ""MessageId"": ""{24AE31D6-63B8-46F3-9975-A3DAF1B6D3F4}"", ""TimeToLive"" : 80, ""CorrelationId"": ""{896DD5BD-1645-44D7-9E7C-D7F70958ECD6}"", ""SequenceNumber"" : 54321, ""DeliveryCount"" : 4, ""To"" : ""http://contoso.com/path2"", ""ReplyTo"" : ""http://fabrikam.com/path2"", ""SentTimeUtc"" : ""Sun, 06 Nov 1994 10:49:37 GMT"", ""ScheduledEnqueueTimeUtc"" : ""Sun, 06 Nov 1994 10:49:37 GMT""}" },
+ { @"P3P", @"CP=""ALL IND DSP COR ADM CONo CUR CUSo IVAo IVDo PSA PSD TAI TELo OUR SAMo CNT COM INT NAV ONL PHY PRE PUR UNI""" },
+ { @"Cookie", @"omniID=1297715979621_9f45_1519_3f8a_f22f85346ac6; WT_FPC=id=65.55.227.138-2323234032.30136233:lv=1309374389020:ss=1309374389020; A=I&I=AxUFAAAAAACNCAAADYEZ7CFPss7Swnujy4PXZA!!&M=1&CS=126mAa0002ZB51a02gZB51a; MC1=GUID=568428660ad44d4ab8f46133f4b03738&HASH=6628&LV=20113&V=3; WT_NVR_RU=0=msdn:1=:2=; MUID=A44DE185EA1B4E8088CCF7B348C5D65F; MSID=Microsoft.CreationDate=03/04/2011 23:38:15&Microsoft.LastVisitDate=06/20/2011 04:15:08&Microsoft.VisitStartDate=06/20/2011 04:15:08&Microsoft.CookieId=f658f3f2-e6d6-42ab-b86b-96791b942b6f&Microsoft.TokenId=ffffffff-ffff-ffff-ffff-ffffffffffff&Microsoft.NumberOfVisits=106&Microsoft.CookieFirstVisit=1&Microsoft.IdentityToken=AA==&Microsoft.MicrosoftId=0441-6141-1523-9969; msresearch=%7B%22version%22%3A%224.6%22%2C%22state%22%3A%7B%22name%22%3A%22IDLE%22%2C%22url%22%3Aundefined%2C%22timestamp%22%3A1299281911415%7D%2C%22lastinvited%22%3A1299281911415%2C%22userid%22%3A%2212992819114151265672533023080%22%2C%22vendorid%22%3A1%2C%22surveys%22%3A%5Bundefined%5D%7D; CodeSnippetContainerLang=C#; msdn=L=1033; ADS=SN=175A21EF; s_cc=true; s_sq=%5B%5BB%5D%5D; TocHashCookie=ms310241(n)/aa187916(n)/aa187917(n)/dd273952(n)/dd295083(n)/ff472634(n)/ee667046(n)/ee667070(n)/gg259047(n)/gg618436(n)/; WT_NVR=0=/:1=query|library|en-us:2=en-us/vcsharp|en-us/library" },
+ { @"Set-Cookie", @"A=I&I=AxUFAAAAAADsBgAA1sWZz4FGun/kOeyV4LGZVg!!&M=1; domain=.microsoft.com; expires=Sun, 30-Jun-2041 00:14:40 GMT; path=/" },
+ };
+
+ byte[] data = InternetMessageFormatHeaderParserTests.CreateBuffer(headerData.Select((kv) => { return String.Format("{0}: {1}", kv.Key, kv.Value); }).ToArray());
+ for (var cnt = 1; cnt <= data.Length; cnt++)
+ {
+ HttpHeaders headers;
+ InternetMessageFormatHeaderParser parser = InternetMessageFormatHeaderParserTests.CreateHeaderParser(data.Length, out headers);
+ Assert.NotNull(parser);
+
+ int totalBytesConsumed = 0;
+ ParserState state = InternetMessageFormatHeaderParserTests.ParseBufferInSteps(parser, data, cnt, out totalBytesConsumed);
+ Assert.Equal(ParserState.Done, state);
+ Assert.Equal(data.Length, totalBytesConsumed);
+
+ Assert.Equal(headerData.Count, headers.Count());
+ for (int hCnt = 0; hCnt < headerData.Count; hCnt++)
+ {
+ Assert.Equal(headerData.Keys.ElementAt(hCnt), headers.ElementAt(hCnt).Key);
+ Assert.Equal(headerData.Values.ElementAt(hCnt), headers.ElementAt(hCnt).Value.ElementAt(0));
+ }
+ }
+ }
+
+ [Fact]
+ [Trait("Description", "HeaderParser.ParseBuffer parses multi-line header field.")]
+ public void HeaderParserSplitHeader()
+ {
+ byte[] data = InternetMessageFormatHeaderParserTests.CreateBuffer("N:V1,", " V2,", "\tV3,", " V4,", " \tV5");
+
+ for (var cnt = 1; cnt <= data.Length; cnt++)
+ {
+ HttpHeaders headers;
+ InternetMessageFormatHeaderParser parser = InternetMessageFormatHeaderParserTests.CreateHeaderParser(data.Length, out headers);
+ Assert.NotNull(parser);
+
+ int totalBytesConsumed = 0;
+ ParserState state = InternetMessageFormatHeaderParserTests.ParseBufferInSteps(parser, data, cnt, out totalBytesConsumed);
+ Assert.Equal(ParserState.Done, state);
+ Assert.Equal(data.Length, totalBytesConsumed);
+
+ Assert.Equal(1, headers.Count());
+ IEnumerable<string> parsedValues = headers.GetValues("N");
+ Assert.Equal(1, parsedValues.Count());
+ Assert.Equal("V1, V2, V3, V4, \tV5", parsedValues.ElementAt(0));
+ }
+ }
+
+ [Fact]
+ [Trait("Description", "HeaderParser.ParseBuffer parses too big header with single header field.")]
+ public void HeaderParserDataTooBigSingle()
+ {
+ byte[] data = InternetMessageFormatHeaderParserTests.CreateBuffer("N:V");
+
+ for (var cnt = 1; cnt <= data.Length; cnt++)
+ {
+ HttpHeaders headers;
+ InternetMessageFormatHeaderParser parser = InternetMessageFormatHeaderParserTests.CreateHeaderParser(ParserData.MinHeaderSize, out headers);
+ Assert.NotNull(parser);
+
+ int totalBytesConsumed = 0;
+ ParserState state = InternetMessageFormatHeaderParserTests.ParseBufferInSteps(parser, data, cnt, out totalBytesConsumed);
+ Assert.Equal(ParserState.DataTooBig, state);
+ Assert.Equal(ParserData.MinHeaderSize, totalBytesConsumed);
+ }
+ }
+
+ [Fact]
+ [Trait("Description", "HeaderParser.ParseBuffer parses too big header with multiple header field.")]
+ public void HeaderParserTestDataTooBigMulti()
+ {
+ byte[] data = InternetMessageFormatHeaderParserTests.CreateBuffer("N1:V1", "N2:V2", "N3:V3");
+
+ for (var cnt = 1; cnt <= data.Length; cnt++)
+ {
+ HttpHeaders headers;
+ InternetMessageFormatHeaderParser parser = InternetMessageFormatHeaderParserTests.CreateHeaderParser(10, out headers);
+ Assert.NotNull(parser);
+
+ int totalBytesConsumed = 0;
+ ParserState state = InternetMessageFormatHeaderParserTests.ParseBufferInSteps(parser, data, cnt, out totalBytesConsumed);
+ Assert.Equal(ParserState.DataTooBig, state);
+ Assert.Equal(10, totalBytesConsumed);
+ }
+ }
+
+
+ // Set of samples from RFC 5322 with times adjusted to GMT following HTTP style for date time format.
+
+ static readonly string[] Rfc5322Sample1 = new string[] {
+ @"From: John Doe <jdoe@machine.example>",
+ @"To: Mary Smith <mary@example.net>",
+ @"Subject: Saying Hello",
+ @"Date: Fri, 21 Nov 1997 09:55:06 GMT",
+ @"Message-ID: <1234@local.machine.example>",
+ };
+
+ static readonly string[] Rfc5322Sample2 = new string[] {
+ @"From: John Doe <jdoe@machine.example>",
+ @"Sender: Michael Jones <mjones@machine.example>",
+ @"To: Mary Smith <mary@example.net>",
+ @"Subject: Saying Hello",
+ @"Date: Fri, 21 Nov 1997 09:55:06 GMT",
+ @"Message-ID: <1234@local.machine.example>",
+ };
+
+ static readonly string[] Rfc5322Sample3 = new string[] {
+ @"From: ""Joe Q. Public"" <john.q.public@example.com>",
+ @"To: Mary Smith <mary@x.test>, jdoe@example.org, Who? <one@y.test>",
+ @"Cc: <boss@nil.test>, ""Giant; \""Big\"" Box"" <sysservices@example.net>",
+ @"Date: Tue, 01 Jul 2003 10:52:37 GMT",
+ @"Message-ID: <5678.21-Nov-1997@example.com>",
+ };
+
+ static readonly string[] Rfc5322Sample4 = new string[] {
+ @"From: Pete <pete@silly.example>",
+ @"To: A Group:Ed Jones <c@a.test>,joe@where.test,John <jdoe@one.test>;",
+ @"Cc: Undisclosed recipients:;",
+ @"Date: Thu, 13 Feb 1969 23:32:54 GMT",
+ @"Message-ID: <testabcd.1234@silly.example>",
+ };
+
+ static readonly string[] Rfc5322Sample5 = new string[] {
+ @"From: John Doe <jdoe@machine.example>",
+ @"To: Mary Smith <mary@example.net>",
+ @"Subject: Saying Hello",
+ @"Date: Fri, 21 Nov 1997 09:55:06 GMT",
+ @"Message-ID: <1234@local.machine.example>",
+ };
+
+ static readonly string[] Rfc5322Sample6 = new string[] {
+ @"From: Mary Smith <mary@example.net>",
+ @"To: John Doe <jdoe@machine.example>",
+ @"Reply-To: ""Mary Smith: Personal Account"" <smith@home.example>",
+ @"Subject: Re: Saying Hello",
+ @"Date: Fri, 21 Nov 1997 10:01:10 GMT",
+ @"Message-ID: <3456@example.net>",
+ @"In-Reply-To: <1234@local.machine.example>",
+ @"References: <1234@local.machine.example>",
+ };
+
+ static readonly string[] Rfc5322Sample7 = new string[] {
+ @"To: ""Mary Smith: Personal Account"" <smith@home.example>",
+ @"From: John Doe <jdoe@machine.example>",
+ @"Subject: Re: Saying Hello",
+ @"Date: Fri, 21 Nov 1997 11:00:00 GMT",
+ @"Message-ID: <abcd.1234@local.machine.test>",
+ @"In-Reply-To: <3456@example.net>",
+ @"References: <1234@local.machine.example> <3456@example.net>",
+ };
+
+ static readonly string[] Rfc5322Sample8 = new string[] {
+ @"From: John Doe <jdoe@machine.example>",
+ @"To: Mary Smith <mary@example.net>",
+ @"Subject: Saying Hello",
+ @"Date: Fri, 21 Nov 1997 09:55:06 GMT",
+ @"Message-ID: <1234@local.machine.example>",
+ };
+
+ static readonly string[] Rfc5322Sample9 = new string[] {
+ @"Resent-From: Mary Smith <mary@example.net>",
+ @"Resent-To: Jane Brown <j-brown@other.example>",
+ @"Resent-Date: Mon, 24 Nov 1997 14:22:01 GMT",
+ @"Resent-Message-ID: <78910@example.net>",
+ @"From: John Doe <jdoe@machine.example>",
+ @"To: Mary Smith <mary@example.net>",
+ @"Subject: Saying Hello",
+ @"Date: Fri, 21 Nov 1997 09:55:06 GMT",
+ @"Message-ID: <1234@local.machine.example>",
+ };
+
+ static readonly string[] Rfc5322Sample10 = new string[] {
+ @"Received: from x.y.test",
+ @" by example.net",
+ @" via TCP",
+ @" with ESMTP",
+ @" id ABC12345",
+ @" for <mary@example.net>; 21 Nov 1997 10:05:43 GMT",
+ @"Received: from node.example by x.y.test; 21 Nov 1997 10:01:22 GMT",
+ @"From: John Doe <jdoe@node.example>",
+ @"To: Mary Smith <mary@example.net>",
+ @"Subject: Saying Hello",
+ @"Date: Fri, 21 Nov 1997 09:55:06 GMT",
+ @"Message-ID: <1234@local.node.example>",
+ };
+
+ static readonly string[] Rfc5322Sample11 = new string[] {
+ @"From: Pete(A nice \) chap) <pete(his account)@silly.test(his host)>",
+ @"To:A Group(Some people)",
+ @" :Chris Jones <c@(Chris's host.)public.example>,",
+ @" joe@example.org,",
+ @" John <jdoe@one.test> (my dear friend); (the end of the group)",
+ @"Cc:(Empty list)(start)Hidden recipients :(nobody(that I know)) ;",
+ @"Date: Thu,",
+ @" 13",
+ @" Feb",
+ @" 1969",
+ @" 23:32:00",
+ @" GMT",
+ @"Message-ID: <testabcd.1234@silly.test>",
+ };
+
+ [Fact]
+ [Trait("Description", "HeaderParser.ParseBuffer Rfc5322Sample1 header.")]
+ public void Rfc5322Sample1Test()
+ {
+ RunRfc5322SampleTest(Rfc5322Sample1,
+ (headers) =>
+ {
+ Assert.NotNull(headers);
+ Assert.True(headers.Contains("from"));
+ Assert.True(headers.Contains("to"));
+ Assert.True(headers.Contains("subject"));
+ Assert.True(headers.Contains("date"));
+ Assert.True(headers.Contains("message-id"));
+ });
+ }
+
+ [Fact]
+ [Trait("Description", "HeaderParser.ParseBuffer Rfc5322Sample2 header.")]
+ public void Rfc5322Sample2Test()
+ {
+ RunRfc5322SampleTest(Rfc5322Sample2,
+ (headers) =>
+ {
+ Assert.NotNull(headers);
+ Assert.True(headers.Contains("from"));
+ Assert.True(headers.Contains("sender"));
+ Assert.True(headers.Contains("to"));
+ Assert.True(headers.Contains("subject"));
+ Assert.True(headers.Contains("date"));
+ Assert.True(headers.Contains("message-id"));
+ });
+ }
+
+ [Fact]
+ [Trait("Description", "HeaderParser.ParseBuffer Rfc5322Sample3 header.")]
+ public void Rfc5322Sample3Test()
+ {
+ RunRfc5322SampleTest(Rfc5322Sample3,
+ (headers) =>
+ {
+ Assert.NotNull(headers);
+ Assert.True(headers.Contains("from"));
+ Assert.True(headers.Contains("to"));
+ Assert.True(headers.Contains("cc"));
+ Assert.True(headers.Contains("date"));
+ Assert.True(headers.Contains("message-id"));
+ });
+ }
+
+ [Fact]
+ [Trait("Description", "HeaderParser.ParseBuffer Rfc5322Sample4 header.")]
+ public void Rfc5322Sample4Test()
+ {
+ RunRfc5322SampleTest(Rfc5322Sample4,
+ (headers) =>
+ {
+ Assert.NotNull(headers);
+ Assert.True(headers.Contains("from"));
+ Assert.True(headers.Contains("to"));
+ Assert.True(headers.Contains("cc"));
+ Assert.True(headers.Contains("date"));
+ Assert.True(headers.Contains("message-id"));
+ });
+ }
+
+ [Fact]
+ [Trait("Description", "HeaderParser.ParseBuffer Rfc5322Sample5 header.")]
+ public void Rfc5322Sample5Test()
+ {
+ RunRfc5322SampleTest(Rfc5322Sample5,
+ (headers) =>
+ {
+ Assert.NotNull(headers);
+ Assert.True(headers.Contains("from"));
+ Assert.True(headers.Contains("to"));
+ Assert.True(headers.Contains("subject"));
+ Assert.True(headers.Contains("date"));
+ Assert.True(headers.Contains("message-id"));
+ });
+ }
+
+ [Fact]
+ [Trait("Description", "HeaderParser.ParseBuffer Rfc5322Sample6 header.")]
+ public void Rfc5322Sample6Test()
+ {
+ RunRfc5322SampleTest(Rfc5322Sample6,
+ (headers) =>
+ {
+ Assert.NotNull(headers);
+ Assert.True(headers.Contains("from"));
+ Assert.True(headers.Contains("to"));
+ Assert.True(headers.Contains("reply-to"));
+ Assert.True(headers.Contains("subject"));
+ Assert.True(headers.Contains("date"));
+ Assert.True(headers.Contains("message-id"));
+ Assert.True(headers.Contains("in-reply-to"));
+ Assert.True(headers.Contains("references"));
+ });
+ }
+
+ [Fact]
+ [Trait("Description", "HeaderParser.ParseBuffer Rfc5322Sample7 header.")]
+ public void Rfc5322Sample7Test()
+ {
+ RunRfc5322SampleTest(Rfc5322Sample7,
+ (headers) =>
+ {
+ Assert.NotNull(headers);
+ Assert.True(headers.Contains("to"));
+ Assert.True(headers.Contains("from"));
+ Assert.True(headers.Contains("subject"));
+ Assert.True(headers.Contains("date"));
+ Assert.True(headers.Contains("message-id"));
+ Assert.True(headers.Contains("in-reply-to"));
+ Assert.True(headers.Contains("references"));
+ });
+ }
+
+ [Fact]
+ [Trait("Description", "HeaderParser.ParseBuffer Rfc5322Sample8 header.")]
+ public void Rfc5322Sample8Test()
+ {
+ RunRfc5322SampleTest(Rfc5322Sample8,
+ (headers) =>
+ {
+ Assert.NotNull(headers);
+ Assert.True(headers.Contains("from"));
+ Assert.True(headers.Contains("to"));
+ Assert.True(headers.Contains("subject"));
+ Assert.True(headers.Contains("date"));
+ Assert.True(headers.Contains("message-id"));
+ });
+ }
+
+ [Fact]
+ [Trait("Description", "HeaderParser.ParseBuffer Rfc5322Sample9 header.")]
+ public void Rfc5322Sample9Test()
+ {
+ RunRfc5322SampleTest(Rfc5322Sample9,
+ (headers) =>
+ {
+ Assert.NotNull(headers);
+ Assert.True(headers.Contains("resent-from"));
+ Assert.True(headers.Contains("resent-to"));
+ Assert.True(headers.Contains("resent-date"));
+ Assert.True(headers.Contains("resent-message-id"));
+ Assert.True(headers.Contains("from"));
+ Assert.True(headers.Contains("to"));
+ Assert.True(headers.Contains("subject"));
+ Assert.True(headers.Contains("date"));
+ Assert.True(headers.Contains("message-id"));
+ });
+ }
+
+ [Fact]
+ [Trait("Description", "HeaderParser.ParseBuffer Rfc5322Sample10 header.")]
+ public void Rfc5322Sample10Test()
+ {
+ RunRfc5322SampleTest(Rfc5322Sample10,
+ (headers) =>
+ {
+ Assert.NotNull(headers);
+ Assert.True(headers.Contains("received"));
+ Assert.Equal(2, headers.GetValues("received").Count());
+ Assert.True(headers.Contains("from"));
+ Assert.True(headers.Contains("to"));
+ Assert.True(headers.Contains("subject"));
+ Assert.True(headers.Contains("date"));
+ Assert.True(headers.Contains("message-id"));
+ });
+ }
+
+ [Fact]
+ [Trait("Description", "HeaderParser.ParseBuffer Rfc5322Sample11 header.")]
+ public void Rfc5322Sample11Test()
+ {
+ RunRfc5322SampleTest(Rfc5322Sample11,
+ (headers) =>
+ {
+ Assert.NotNull(headers);
+ Assert.True(headers.Contains("from"));
+ Assert.True(headers.Contains("to"));
+ Assert.True(headers.Contains("cc"));
+ Assert.True(headers.Contains("date"));
+ Assert.True(headers.Contains("message-id"));
+ });
+ }
+
+ }
+} \ No newline at end of file
diff --git a/test/System.Net.Http.Formatting.Test.Unit/Formatting/Parsers/MimeMultipartParserTests.cs b/test/System.Net.Http.Formatting.Test.Unit/Formatting/Parsers/MimeMultipartParserTests.cs
new file mode 100644
index 00000000..ff7b1509
--- /dev/null
+++ b/test/System.Net.Http.Formatting.Test.Unit/Formatting/Parsers/MimeMultipartParserTests.cs
@@ -0,0 +1,482 @@
+using System.Collections.Generic;
+using System.IO;
+using System.Text;
+using Microsoft.TestCommon;
+using Xunit;
+using Xunit.Extensions;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Net.Http.Formatting.Parsers
+{
+ public class MimeMultipartParserTests
+ {
+ [Fact]
+ public void MimeMultipartParserTypeIsCorrect()
+ {
+ Assert.Type.HasProperties<InternetMessageFormatHeaderParser>(TypeAssert.TypeProperties.IsClass);
+ }
+
+ private static MimeMultipartParser CreateMimeMultipartParser(int maximumHeaderLength, string boundary)
+ {
+ return new MimeMultipartParser(boundary, maximumHeaderLength);
+ }
+
+ internal static byte[] CreateBuffer(string boundary, params string[] bodyparts)
+ {
+ return CreateBuffer(boundary, false, bodyparts);
+ }
+
+ internal static string CreateNestedBuffer(int count)
+ {
+ StringBuilder buffer = new StringBuilder("content");
+
+ for (var cnt = 0; cnt < count; cnt++)
+ {
+ byte[] nested = CreateBuffer("N" + cnt.ToString(), buffer.ToString());
+ var message = Encoding.UTF8.GetString(nested);
+ buffer.Length = 0;
+ buffer.AppendLine(message);
+ }
+
+ return buffer.ToString();
+ }
+
+ private static byte[] CreateBuffer(string boundary, bool withLws, params string[] bodyparts)
+ {
+ const string SP = " ";
+ const string HTAB = "\t";
+ const string CRLF = "\r\n";
+ const string DashDash = "--";
+
+ string lws = String.Empty;
+ if (withLws)
+ {
+ lws = SP + SP + HTAB + SP;
+ }
+
+ StringBuilder message = new StringBuilder();
+ message.Append(DashDash + boundary + lws + CRLF);
+ for (var cnt = 0; cnt < bodyparts.Length; cnt++)
+ {
+ message.Append(bodyparts[cnt]);
+ if (cnt < bodyparts.Length - 1)
+ {
+ message.Append(CRLF + DashDash + boundary + lws + CRLF);
+ }
+ }
+
+ // Note: We rely on a final CRLF even though it is not required by the BNF existing application do send it
+ message.Append(CRLF + DashDash + boundary + DashDash + lws + CRLF);
+ return Encoding.UTF8.GetBytes(message.ToString());
+ }
+
+ private static MimeMultipartParser.State ParseBufferInSteps(MimeMultipartParser parser, byte[] buffer, int readsize, out List<string> bodyParts, out int totalBytesConsumed)
+ {
+ MimeMultipartParser.State state = MimeMultipartParser.State.Invalid;
+ totalBytesConsumed = 0;
+ bodyParts = new List<string>();
+ bool isFinal = false;
+ byte[] currentBodyPart = new byte[32 * 1024];
+ int currentBodyLength = 0;
+
+ while (totalBytesConsumed <= buffer.Length)
+ {
+ int size = Math.Min(buffer.Length - totalBytesConsumed, readsize);
+ byte[] parseBuffer = new byte[size];
+ Buffer.BlockCopy(buffer, totalBytesConsumed, parseBuffer, 0, size);
+
+ int bytesConsumed = 0;
+ ArraySegment<byte> out1;
+ ArraySegment<byte> out2;
+ state = parser.ParseBuffer(parseBuffer, parseBuffer.Length, ref bytesConsumed, out out1, out out2, out isFinal);
+ totalBytesConsumed += bytesConsumed;
+
+ Buffer.BlockCopy(out1.Array, out1.Offset, currentBodyPart, currentBodyLength, out1.Count);
+ currentBodyLength += out1.Count;
+
+ Buffer.BlockCopy(out2.Array, out2.Offset, currentBodyPart, currentBodyLength, out2.Count);
+ currentBodyLength += out2.Count;
+
+ if (state == MimeMultipartParser.State.BodyPartCompleted)
+ {
+ var bPart = new byte[currentBodyLength];
+ Buffer.BlockCopy(currentBodyPart, 0, bPart, 0, currentBodyLength);
+ bodyParts.Add(Encoding.UTF8.GetString(bPart));
+ currentBodyLength = 0;
+ if (isFinal)
+ {
+ break;
+ }
+ }
+ else if (state != MimeMultipartParser.State.NeedMoreData)
+ {
+ return state;
+ }
+ }
+
+ Assert.True(isFinal);
+ return state;
+ }
+
+ public static IEnumerable<object[]> Boundaries
+ {
+ get { return ParserData.Boundaries; }
+ }
+
+ [Theory]
+ [PropertyData("Boundaries")]
+ public void MimeMultipartParserConstructorTest(string boundary)
+ {
+ MimeMultipartParser parser = new MimeMultipartParser(boundary, ParserData.MinMessageSize);
+ Assert.NotNull(parser);
+
+ Assert.ThrowsArgument(() => { new MimeMultipartParser("-", ParserData.MinMessageSize - 1); }, "maxMessageSize");
+
+ foreach (string empty in TestData.EmptyStrings)
+ {
+ Assert.ThrowsArgument(() => { new MimeMultipartParser(empty, ParserData.MinMessageSize); }, "boundary", allowDerivedExceptions: true);
+ }
+
+ Assert.ThrowsArgument(() => { new MimeMultipartParser("trailingspace ", ParserData.MinMessageSize); }, "boundary");
+
+ Assert.ThrowsArgumentNull(() => { new MimeMultipartParser(null, ParserData.MinMessageSize); }, "boundary");
+ }
+
+
+ [Fact]
+ public void MultipartParserNullBuffer()
+ {
+ MimeMultipartParser parser = CreateMimeMultipartParser(128, "-");
+ Assert.NotNull(parser);
+
+ int bytesConsumed = 0;
+ ArraySegment<byte> out1;
+ ArraySegment<byte> out2;
+ bool isFinal;
+ Assert.ThrowsArgumentNull(() => { parser.ParseBuffer(null, 0, ref bytesConsumed, out out1, out out2, out isFinal); }, "buffer");
+ }
+
+ [Theory]
+ [PropertyData("Boundaries")]
+ public void MultipartParserEmptyBuffer(string boundary)
+ {
+ byte[] data = CreateBuffer(boundary);
+
+ for (var cnt = 1; cnt <= data.Length; cnt++)
+ {
+ MimeMultipartParser parser = CreateMimeMultipartParser(data.Length, boundary);
+ Assert.NotNull(parser);
+
+ int totalBytesConsumed;
+ List<string> bodyParts;
+ MimeMultipartParser.State state = ParseBufferInSteps(parser, data, cnt, out bodyParts, out totalBytesConsumed);
+ Assert.Equal(MimeMultipartParser.State.BodyPartCompleted, state);
+ Assert.Equal(data.Length, totalBytesConsumed);
+
+ Assert.Equal(2, bodyParts.Count);
+ Assert.Equal(0, bodyParts[0].Length);
+ Assert.Equal(0, bodyParts[1].Length);
+ }
+ }
+
+ [Theory]
+ [PropertyData("Boundaries")]
+ public void MultipartParserSingleShortBodyPart(string boundary)
+ {
+
+ byte[] data = CreateBuffer(boundary, "A");
+
+ for (var cnt = 1; cnt <= data.Length; cnt++)
+ {
+ MimeMultipartParser parser = CreateMimeMultipartParser(data.Length, boundary);
+ Assert.NotNull(parser);
+
+ int totalBytesConsumed;
+ List<string> bodyParts;
+ MimeMultipartParser.State state = ParseBufferInSteps(parser, data, cnt, out bodyParts, out totalBytesConsumed);
+ Assert.Equal(MimeMultipartParser.State.BodyPartCompleted, state);
+ Assert.Equal(data.Length, totalBytesConsumed);
+
+ Assert.Equal(2, bodyParts.Count);
+ Assert.Equal(0, bodyParts[0].Length);
+ Assert.Equal(1, bodyParts[1].Length);
+ Assert.Equal("A", bodyParts[1]);
+ }
+ }
+
+ [Theory]
+ [PropertyData("Boundaries")]
+ public void MultipartParserMultipleShortBodyParts(string boundary)
+ {
+ string[] text = new string[] { "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z" };
+
+ byte[] data = CreateBuffer(boundary, text);
+
+ for (var cnt = 1; cnt <= data.Length; cnt++)
+ {
+ MimeMultipartParser parser = CreateMimeMultipartParser(data.Length, boundary);
+ Assert.NotNull(parser);
+
+ int totalBytesConsumed;
+ List<string> bodyParts;
+ MimeMultipartParser.State state = ParseBufferInSteps(parser, data, cnt, out bodyParts, out totalBytesConsumed);
+ Assert.Equal(MimeMultipartParser.State.BodyPartCompleted, state);
+ Assert.Equal(data.Length, totalBytesConsumed);
+
+ Assert.Equal(text.Length + 1, bodyParts.Count);
+ Assert.Equal(0, bodyParts[0].Length);
+
+ for (var check = 0; check < text.Length; check++)
+ {
+ Assert.Equal(1, bodyParts[check + 1].Length);
+ Assert.Equal(text[check], bodyParts[check + 1]);
+ }
+ }
+ }
+
+ [Theory]
+ [PropertyData("Boundaries")]
+ public void MultipartParserMultipleShortBodyPartsWithLws(string boundary)
+ {
+ string[] text = new string[] { "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z" };
+
+ byte[] data = CreateBuffer(boundary, true, text);
+
+ for (var cnt = 1; cnt <= data.Length; cnt++)
+ {
+ MimeMultipartParser parser = CreateMimeMultipartParser(data.Length, boundary);
+ Assert.NotNull(parser);
+
+ int totalBytesConsumed;
+ List<string> bodyParts;
+ MimeMultipartParser.State state = ParseBufferInSteps(parser, data, cnt, out bodyParts, out totalBytesConsumed);
+ Assert.Equal(MimeMultipartParser.State.BodyPartCompleted, state);
+ Assert.Equal(data.Length, totalBytesConsumed);
+
+ Assert.Equal(text.Length + 1, bodyParts.Count);
+ Assert.Equal(0, bodyParts[0].Length);
+
+ for (var check = 0; check < text.Length; check++)
+ {
+ Assert.Equal(1, bodyParts[check + 1].Length);
+ Assert.Equal(text[check], bodyParts[check + 1]);
+ }
+ }
+ }
+
+ [Theory]
+ [PropertyData("Boundaries")]
+ public void MultipartParserSingleLongBodyPart(string boundary)
+ {
+ const string text = "0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789";
+
+ byte[] data = CreateBuffer(boundary, text);
+
+ for (var cnt = 1; cnt <= data.Length; cnt++)
+ {
+ MimeMultipartParser parser = CreateMimeMultipartParser(data.Length, boundary);
+ Assert.NotNull(parser);
+
+ int totalBytesConsumed;
+ List<string> bodyParts;
+ MimeMultipartParser.State state = ParseBufferInSteps(parser, data, cnt, out bodyParts, out totalBytesConsumed);
+ Assert.Equal(MimeMultipartParser.State.BodyPartCompleted, state);
+ Assert.Equal(data.Length, totalBytesConsumed);
+
+ Assert.Equal(2, bodyParts.Count);
+ Assert.Equal(0, bodyParts[0].Length);
+
+ Assert.Equal(text.Length, bodyParts[1].Length);
+ Assert.Equal(text, bodyParts[1]);
+ }
+ }
+
+ [Theory]
+ [PropertyData("Boundaries")]
+ public void MultipartParserMultipleLongBodyParts(string boundary)
+ {
+ const string middleText = "0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789";
+ string[] text = new string[] {
+ "A" + middleText + "A",
+ "B" + middleText + "B",
+ "C" + middleText + "C",
+ "D" + middleText + "D",
+ "E" + middleText + "E",
+ "F" + middleText + "F",
+ "G" + middleText + "G",
+ "H" + middleText + "H",
+ "I" + middleText + "I",
+ "J" + middleText + "J",
+ "K" + middleText + "K",
+ "L" + middleText + "L",
+ "M" + middleText + "M",
+ "N" + middleText + "N",
+ "O" + middleText + "O",
+ "P" + middleText + "P",
+ "Q" + middleText + "Q",
+ "R" + middleText + "R",
+ "S" + middleText + "S",
+ "T" + middleText + "T",
+ "U" + middleText + "U",
+ "V" + middleText + "V",
+ "W" + middleText + "W",
+ "X" + middleText + "X",
+ "Y" + middleText + "Y",
+ "Z" + middleText + "Z"};
+
+ byte[] data = CreateBuffer(boundary, text);
+
+ for (var cnt = 1; cnt <= data.Length; cnt++)
+ {
+ MimeMultipartParser parser = CreateMimeMultipartParser(data.Length, boundary);
+ Assert.NotNull(parser);
+
+ int totalBytesConsumed;
+ List<string> bodyParts;
+ MimeMultipartParser.State state = ParseBufferInSteps(parser, data, cnt, out bodyParts, out totalBytesConsumed);
+ Assert.Equal(MimeMultipartParser.State.BodyPartCompleted, state);
+ Assert.Equal(data.Length, totalBytesConsumed);
+
+ Assert.Equal(text.Length + 1, bodyParts.Count);
+ Assert.Equal(0, bodyParts[0].Length);
+
+ for (var check = 0; check < text.Length; check++)
+ {
+ Assert.Equal(text[check].Length, bodyParts[check + 1].Length);
+ Assert.Equal(text[check], bodyParts[check + 1]);
+ }
+ }
+ }
+
+ [Theory]
+ [PropertyData("Boundaries")]
+ public void MultipartParserNearMatches(string boundary)
+ {
+ const string CR = "\r";
+ const string CRLF = "\r\n";
+ const string Dash = "-";
+ const string DashDash = "--";
+
+ string[] text = new string[] {
+ CR + Dash + "AAA",
+ CRLF + Dash + "AAA",
+ CRLF + DashDash + "AAA" + CR + "AAA",
+ CRLF,
+ "AAA",
+ "AAA" + CRLF,
+ CRLF + CRLF,
+ CRLF + CRLF + CRLF,
+ "AAA" + DashDash + "AAA",
+ CRLF + "AAA" + DashDash + "AAA" + DashDash,
+ CRLF + DashDash + "AAA" + CRLF,
+ CRLF + DashDash + "AAA" + CRLF + CRLF,
+ CRLF + DashDash + "AAA" + DashDash + CRLF,
+ CRLF + DashDash + "AAA" + DashDash + CRLF + CRLF
+ };
+
+ byte[] data = CreateBuffer(boundary, text);
+
+ for (var cnt = 1; cnt <= data.Length; cnt++)
+ {
+ MimeMultipartParser parser = CreateMimeMultipartParser(data.Length, boundary);
+ Assert.NotNull(parser);
+
+ int totalBytesConsumed;
+ List<string> bodyParts;
+ MimeMultipartParser.State state = ParseBufferInSteps(parser, data, cnt, out bodyParts, out totalBytesConsumed);
+ Assert.Equal(MimeMultipartParser.State.BodyPartCompleted, state);
+ Assert.Equal(data.Length, totalBytesConsumed);
+
+ Assert.Equal(text.Length + 1, bodyParts.Count);
+ Assert.Equal(0, bodyParts[0].Length);
+
+ for (var check = 0; check < text.Length; check++)
+ {
+ Assert.Equal(text[check].Length, bodyParts[check + 1].Length);
+ Assert.Equal(text[check], bodyParts[check + 1]);
+ }
+ }
+ }
+
+ [Theory]
+ [PropertyData("Boundaries")]
+ public void MultipartParserNesting(string boundary)
+ {
+ for (var nesting = 0; nesting < 16; nesting++)
+ {
+ string nested = CreateNestedBuffer(nesting);
+
+ byte[] data = CreateBuffer(boundary, nested);
+
+ for (var cnt = 1; cnt <= data.Length; cnt++)
+ {
+ MimeMultipartParser parser = CreateMimeMultipartParser(data.Length, boundary);
+ Assert.NotNull(parser);
+
+ int totalBytesConsumed;
+ List<string> bodyParts;
+ MimeMultipartParser.State state = ParseBufferInSteps(parser, data, cnt, out bodyParts, out totalBytesConsumed);
+ Assert.Equal(MimeMultipartParser.State.BodyPartCompleted, state);
+ Assert.Equal(data.Length, totalBytesConsumed);
+
+ Assert.Equal(2, bodyParts.Count);
+ Assert.Equal(0, bodyParts[0].Length);
+ Assert.Equal(nested.Length, bodyParts[1].Length);
+ }
+ }
+ }
+
+ [Theory]
+ [PropertyData("Boundaries")]
+ public void MimeMultipartParserTestDataTooBig(string boundary)
+ {
+ byte[] data = CreateBuffer(boundary);
+
+ for (var cnt = 1; cnt <= data.Length; cnt++)
+ {
+ MimeMultipartParser parser = CreateMimeMultipartParser(ParserData.MinMessageSize, boundary);
+ Assert.NotNull(parser);
+
+ int totalBytesConsumed;
+ List<string> bodyParts;
+ MimeMultipartParser.State state = ParseBufferInSteps(parser, data, cnt, out bodyParts, out totalBytesConsumed);
+ Assert.Equal(MimeMultipartParser.State.DataTooBig, state);
+ Assert.Equal(ParserData.MinMessageSize, totalBytesConsumed);
+ }
+ }
+
+ [Theory]
+ [PropertyData("Boundaries")]
+ public void MimeMultipartParserTestMultipartContent(string boundary)
+ {
+ MultipartContent content = new MultipartContent("mixed", boundary);
+ content.Add(new StringContent("A"));
+ content.Add(new StringContent("B"));
+ content.Add(new StringContent("C"));
+
+ MemoryStream memStream = new MemoryStream();
+ content.CopyToAsync(memStream).Wait();
+ memStream.Position = 0;
+ byte[] data = memStream.ToArray();
+
+ for (var cnt = 1; cnt <= data.Length; cnt++)
+ {
+ MimeMultipartParser parser = CreateMimeMultipartParser(data.Length, boundary);
+ Assert.NotNull(parser);
+
+ int totalBytesConsumed;
+ List<string> bodyParts;
+ MimeMultipartParser.State state = ParseBufferInSteps(parser, data, cnt, out bodyParts, out totalBytesConsumed);
+ Assert.Equal(MimeMultipartParser.State.BodyPartCompleted, state);
+ Assert.Equal(data.Length, totalBytesConsumed);
+
+ Assert.Equal(4, bodyParts.Count);
+ Assert.Empty(bodyParts[0]);
+
+ Assert.True(bodyParts[1].EndsWith("A"));
+ Assert.True(bodyParts[2].EndsWith("B"));
+ Assert.True(bodyParts[3].EndsWith("C"));
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/test/System.Net.Http.Formatting.Test.Unit/Formatting/QueryStringMappingTests.cs b/test/System.Net.Http.Formatting.Test.Unit/Formatting/QueryStringMappingTests.cs
new file mode 100644
index 00000000..5385bc03
--- /dev/null
+++ b/test/System.Net.Http.Formatting.Test.Unit/Formatting/QueryStringMappingTests.cs
@@ -0,0 +1,202 @@
+using System.Collections.Generic;
+using System.Linq;
+using System.Net.Http.Formatting.DataSets;
+using System.Net.Http.Headers;
+using Microsoft.TestCommon;
+using Xunit;
+using Xunit.Extensions;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Net.Http.Formatting
+{
+ public class QueryStringMappingTests
+ {
+ public static IEnumerable<string> UriStringsWithoutQuery
+ {
+ get
+ {
+ return HttpUnitTestDataSets.UriStrings.Where((s) => !s.Contains('?'));
+ }
+ }
+
+ [Fact]
+ [Trait("Description", "QueryStringMapping is public, concrete, and sealed.")]
+ public void TypeIsCorrect()
+ {
+ Assert.Type.HasProperties(
+ typeof(QueryStringMapping),
+ TypeAssert.TypeProperties.IsPublicVisibleClass | TypeAssert.TypeProperties.IsSealed,
+ typeof(MediaTypeMapping));
+ }
+
+ [Theory]
+ [TestDataSet(
+ typeof(HttpUnitTestDataSets), "LegalQueryStringParameterNames",
+ typeof(HttpUnitTestDataSets), "LegalQueryStringParameterValues",
+ typeof(HttpUnitTestDataSets), "LegalMediaTypeHeaderValues")]
+ [Trait("Description", "QueryStringMapping(string, string, MediaTypeHeaderValue) sets properties.")]
+ public void Constructor(string queryStringParameterName, string queryStringParameterValue, MediaTypeHeaderValue mediaType)
+ {
+ QueryStringMapping mapping = new QueryStringMapping(queryStringParameterName, queryStringParameterValue, mediaType);
+ Assert.Equal(queryStringParameterName, mapping.QueryStringParameterName);
+ Assert.Equal(queryStringParameterValue, mapping.QueryStringParameterValue);
+ Assert.MediaType.AreEqual(mediaType, mapping.MediaType, "MediaType failed to set.");
+ }
+
+ [Theory]
+ [TestDataSet(
+ typeof(HttpUnitTestDataSets), "LegalMediaTypeHeaderValues",
+ typeof(CommonUnitTestDataSets), "EmptyStrings")]
+ [Trait("Description", "QueryStringMapping(string, string, MediaTypeHeaderValue) throws with empty QueryStringParameterName.")]
+ public void ConstructorThrowsWithEmptyQueryParameterName(MediaTypeHeaderValue mediaType, string queryStringParameterName)
+ {
+ Assert.ThrowsArgumentNull(() => new QueryStringMapping(queryStringParameterName, "json", mediaType), "queryStringParameterName");
+ }
+
+ [Theory]
+ [TestDataSet(
+ typeof(HttpUnitTestDataSets), "LegalMediaTypeHeaderValues",
+ typeof(CommonUnitTestDataSets), "EmptyStrings")]
+ [Trait("Description", "QueryStringMapping(string, string, MediaTypeHeaderValue) throws with empty QueryStringParameterValue.")]
+ public void ConstructorThrowsWithEmptyQueryParameterValue(MediaTypeHeaderValue mediaType, string queryStringParameterValue)
+ {
+ Assert.ThrowsArgumentNull(() => new QueryStringMapping("query", queryStringParameterValue, mediaType), "queryStringParameterValue");
+ }
+
+ [Theory]
+ [TestDataSet(
+ typeof(HttpUnitTestDataSets), "LegalQueryStringParameterNames",
+ typeof(HttpUnitTestDataSets), "LegalQueryStringParameterValues")]
+ [Trait("Description", "QueryStringMapping(string, string, MediaTypeHeaderValue) throws with null MediaTypeHeaderValue.")]
+ public void ConstructorThrowsWithNullMediaTypeHeaderValue(string queryStringParameterName, string queryStringParameterValue)
+ {
+ Assert.ThrowsArgumentNull(() => new QueryStringMapping(queryStringParameterName, queryStringParameterValue, (MediaTypeHeaderValue)null), "mediaType");
+ }
+
+ [Theory]
+ [TestDataSet(
+ typeof(HttpUnitTestDataSets), "LegalQueryStringParameterNames",
+ typeof(HttpUnitTestDataSets), "LegalQueryStringParameterValues",
+ typeof(HttpUnitTestDataSets), "LegalMediaTypeStrings")]
+ [Trait("Description", "QueryStringMapping(string, string, string) sets properties.")]
+ public void Constructor1(string queryStringParameterName, string queryStringParameterValue, string mediaType)
+ {
+ QueryStringMapping mapping = new QueryStringMapping(queryStringParameterName, queryStringParameterValue, mediaType);
+ Assert.Equal(queryStringParameterName, mapping.QueryStringParameterName);
+ Assert.Equal(queryStringParameterValue, mapping.QueryStringParameterValue);
+ Assert.MediaType.AreEqual(mediaType, mapping.MediaType, "MediaType failed to set.");
+ }
+
+ [Theory]
+ [TestDataSet(
+ typeof(HttpUnitTestDataSets), "LegalMediaTypeStrings",
+ typeof(CommonUnitTestDataSets), "EmptyStrings")]
+ [Trait("Description", "QueryStringMapping(string, string, string) throws with empty QueryStringParameterName.")]
+ public void Constructor1ThrowsWithEmptyQueryParameterName(string mediaType, string queryStringParameterName)
+ {
+ Assert.ThrowsArgumentNull(() => new QueryStringMapping(queryStringParameterName, "json", mediaType), "queryStringParameterName");
+ }
+
+ [Theory]
+ [TestDataSet(
+ typeof(HttpUnitTestDataSets), "LegalMediaTypeStrings",
+ typeof(CommonUnitTestDataSets), "EmptyStrings")]
+ [Trait("Description", "QueryStringMapping(string, string, string) throws with empty QueryStringParameterValue.")]
+ public void Constructor1ThrowsWithEmptyQueryParameterValue(string mediaType, string queryStringParameterValue)
+ {
+ Assert.ThrowsArgumentNull(() => new QueryStringMapping("query", queryStringParameterValue, mediaType), "queryStringParameterValue");
+ }
+
+ [Theory]
+ [TestDataSet(
+ typeof(HttpUnitTestDataSets), "LegalQueryStringParameterNames",
+ typeof(HttpUnitTestDataSets), "LegalQueryStringParameterValues",
+ typeof(CommonUnitTestDataSets), "EmptyStrings")]
+ [Trait("Description", "QueryStringMapping(string, string, string) throws with empty MediaType.")]
+ public void Constructor1ThrowsWithEmptyMediaType(string queryStringParameterName, string queryStringParameterValue, string mediaType)
+ {
+ Assert.ThrowsArgumentNull(() => new QueryStringMapping(queryStringParameterName, queryStringParameterValue, (MediaTypeHeaderValue)null), "mediaType");
+ }
+
+ [Theory]
+ [TestDataSet(
+ typeof(HttpUnitTestDataSets), "LegalQueryStringParameterNames",
+ typeof(HttpUnitTestDataSets), "LegalQueryStringParameterValues",
+ typeof(HttpUnitTestDataSets), "LegalMediaTypeStrings",
+ typeof(QueryStringMappingTests), "UriStringsWithoutQuery")]
+ [Trait("Description", "TryMatchMediaType(HttpRequestMessage) returns match when the QueryStringParameterName and QueryStringParameterValue are in the Uri.")]
+ public void TryMatchMediaTypeReturnsMatchWithQueryStringParameterNameAndValueInUri(string queryStringParameterName, string queryStringParameterValue, string mediaType, string uriBase)
+ {
+
+ QueryStringMapping mapping = new QueryStringMapping(queryStringParameterName, queryStringParameterValue, mediaType);
+ string uri = uriBase + "?" + queryStringParameterName + "=" + queryStringParameterValue;
+ HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, uri);
+ Assert.Equal(1.0, mapping.TryMatchMediaType(request));
+ }
+
+ [Theory]
+ [TestDataSet(
+ typeof(HttpUnitTestDataSets), "LegalQueryStringParameterNames",
+ typeof(HttpUnitTestDataSets), "LegalQueryStringParameterValues",
+ typeof(HttpUnitTestDataSets), "LegalMediaTypeStrings",
+ typeof(QueryStringMappingTests), "UriStringsWithoutQuery")]
+ [Trait("Description", "TryMatchMediaType(HttpRequestMessage) returns 0.0 when the QueryStringParameterName is not in the Uri.")]
+ public void TryMatchMediaTypeReturnsZeroWithQueryStringParameterNameNotInUri(string queryStringParameterName, string queryStringParameterValue, string mediaType, string uriBase)
+ {
+ QueryStringMapping mapping = new QueryStringMapping(queryStringParameterName, queryStringParameterValue, mediaType);
+ string uri = uriBase + "?" + "not" + queryStringParameterName + "=" + queryStringParameterValue;
+ HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, uri);
+ Assert.Equal(0.0, mapping.TryMatchMediaType(request));
+ }
+
+ [Theory]
+ [TestDataSet(
+ typeof(HttpUnitTestDataSets), "LegalQueryStringParameterNames",
+ typeof(HttpUnitTestDataSets), "LegalQueryStringParameterValues",
+ typeof(HttpUnitTestDataSets), "LegalMediaTypeStrings",
+ typeof(QueryStringMappingTests), "UriStringsWithoutQuery")]
+ [Trait("Description", "TryMatchMediaType(HttpRequestMessage) returns 0.0 when the QueryStringParameterValue is not in the Uri.")]
+ public void TryMatchMediaTypeReturnsZeroWithQueryStringParameterValueNotInUri(string queryStringParameterName, string queryStringParameterValue, string mediaType, string uriBase)
+ {
+ QueryStringMapping mapping = new QueryStringMapping(queryStringParameterName, queryStringParameterValue, mediaType);
+ string uri = uriBase + "?" + queryStringParameterName + "=" + "not" + queryStringParameterValue;
+ HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, uri);
+ Assert.Equal(0.0, mapping.TryMatchMediaType(request));
+ }
+
+ [Theory]
+ [TestDataSet(
+ typeof(HttpUnitTestDataSets), "LegalQueryStringParameterNames",
+ typeof(HttpUnitTestDataSets), "LegalQueryStringParameterValues",
+ typeof(HttpUnitTestDataSets), "LegalMediaTypeStrings")]
+ [Trait("Description", "TryMatchMediaType(HttpRequestMessage) throws with a null HttpRequestMessage.")]
+ public void TryMatchMediaTypeThrowsWithNullHttpRequestMessage(string queryStringParameterName, string queryStringParameterValue, string mediaType)
+ {
+ QueryStringMapping mapping = new QueryStringMapping(queryStringParameterName, queryStringParameterValue, mediaType);
+ Assert.ThrowsArgumentNull(() => mapping.TryMatchMediaType(request: null), "request");
+ }
+
+ [Theory]
+ [TestDataSet(
+ typeof(HttpUnitTestDataSets), "LegalQueryStringParameterNames",
+ typeof(HttpUnitTestDataSets), "LegalQueryStringParameterValues",
+ typeof(HttpUnitTestDataSets), "LegalMediaTypeStrings")]
+ [Trait("Description", "TryMatchMediaType(HttpRequestMessage) throws with a null Uri in HttpRequestMessage.")]
+ public void TryMatchMediaTypeThrowsWithNullUriInHttpRequestMessage(string queryStringParameterName, string queryStringParameterValue, string mediaType)
+ {
+ QueryStringMapping mapping = new QueryStringMapping(queryStringParameterName, queryStringParameterValue, mediaType);
+ string errorMessage = RS.Format(Properties.Resources.NonNullUriRequiredForMediaTypeMapping, typeof(QueryStringMapping).Name);
+ Assert.Throws<InvalidOperationException>(() => mapping.TryMatchMediaType(new HttpRequestMessage()), errorMessage);
+ }
+
+ [Theory]
+ [InlineData("nAmE", "VaLuE", "name=value")]
+ [InlineData("Format", "Xml", "format=xml")]
+ public void TryMatchMediaTypeIsCaseInsensitive(string name, string value, string query)
+ {
+ QueryStringMapping mapping = new QueryStringMapping(name, value, "application/json");
+ HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, "http://localhost/?" + query);
+ Assert.Equal(1.0, mapping.TryMatchMediaType(request));
+ }
+ }
+}
diff --git a/test/System.Net.Http.Formatting.Test.Unit/Formatting/RequestHeaderMappingTests.cs b/test/System.Net.Http.Formatting.Test.Unit/Formatting/RequestHeaderMappingTests.cs
new file mode 100644
index 00000000..03caeacd
--- /dev/null
+++ b/test/System.Net.Http.Formatting.Test.Unit/Formatting/RequestHeaderMappingTests.cs
@@ -0,0 +1,236 @@
+using System.Net.Http.Formatting.DataSets;
+using System.Net.Http.Headers;
+using Microsoft.TestCommon;
+using Xunit;
+using Xunit.Extensions;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Net.Http.Formatting
+{
+ public class RequestHeaderMappingTests
+ {
+
+ [Fact]
+ [Trait("Description", "RequestHeaderMapping is public, and concrete.")]
+ public void TypeIsCorrect()
+ {
+ Assert.Type.HasProperties(
+ typeof(RequestHeaderMapping),
+ TypeAssert.TypeProperties.IsPublicVisibleClass,
+ typeof(MediaTypeMapping));
+ }
+
+ [Theory]
+ [TestDataSet(
+ typeof(HttpUnitTestDataSets), "LegalHttpHeaderNames",
+ typeof(HttpUnitTestDataSets), "LegalHttpHeaderValues",
+ typeof(HttpUnitTestDataSets), "LegalMediaTypeHeaderValues")]
+ [Trait("Description", "RequestHeaderMapping(string, string, StringComparison, bool, MediaTypeHeaderValue) sets properties.")]
+ public void Constructor(string headerName, string headerValue, MediaTypeHeaderValue mediaType)
+ {
+ RequestHeaderMapping mapping = new RequestHeaderMapping(headerName, headerValue, StringComparison.CurrentCulture, true, mediaType);
+ Assert.Equal(headerName, mapping.HeaderName);
+ Assert.Equal(headerValue, mapping.HeaderValue);
+ Assert.Equal(StringComparison.CurrentCulture, mapping.HeaderValueComparison);
+ Assert.Equal(true, mapping.IsValueSubstring);
+ Assert.MediaType.AreEqual(mediaType, mapping.MediaType, "MediaType failed to set.");
+ }
+
+ [Theory]
+ [TestDataSet(
+ typeof(HttpUnitTestDataSets), "LegalMediaTypeHeaderValues",
+ typeof(CommonUnitTestDataSets), "EmptyStrings")]
+ [Trait("Description", "RequestHeaderMapping(string, string, StringComparison, bool, MediaTypeHeaderValue) throws with empty headerName.")]
+ public void ConstructorThrowsWithEmptyHeaderName(MediaTypeHeaderValue mediaType, string headerName)
+ {
+ Assert.ThrowsArgumentNull(() => new RequestHeaderMapping(headerName, "value", StringComparison.CurrentCulture, false, mediaType), "headerName");
+ }
+
+ [Theory]
+ [TestDataSet(
+ typeof(HttpUnitTestDataSets), "LegalMediaTypeHeaderValues",
+ typeof(CommonUnitTestDataSets), "EmptyStrings")]
+ [Trait("Description", "RequestHeaderMapping(string, string, StringComparison, bool, MediaTypeHeaderValue) throws with empty headerValue.")]
+ public void ConstructorThrowsWithEmptyHeaderValue(MediaTypeHeaderValue mediaType, string headerValue)
+ {
+ Assert.ThrowsArgumentNull(() => new RequestHeaderMapping("name", headerValue, StringComparison.CurrentCulture, false, mediaType), "headerValue");
+ }
+
+ [Theory]
+ [TestDataSet(
+ typeof(HttpUnitTestDataSets), "LegalHttpHeaderNames",
+ typeof(HttpUnitTestDataSets), "LegalHttpHeaderValues")]
+ [Trait("Description", "RequestHeaderMapping(string, string, StringComparison, bool, MediaTypeHeaderValue) throws with null MediaTypeHeaderValue.")]
+ public void ConstructorThrowsWithNullMediaTypeHeaderValue(string headerName, string headerValue)
+ {
+ Assert.ThrowsArgumentNull(() => new RequestHeaderMapping(headerName, headerValue, StringComparison.CurrentCulture, false, (MediaTypeHeaderValue)null), "mediaType");
+ }
+
+ [Theory]
+ [TestDataSet(
+ typeof(HttpUnitTestDataSets), "LegalHttpHeaderNames",
+ typeof(HttpUnitTestDataSets), "LegalHttpHeaderValues",
+ typeof(HttpUnitTestDataSets), "LegalMediaTypeHeaderValues")]
+ [Trait("Description", "RequestHeaderMapping(string, string, StringComparison, bool, MediaTypeHeaderValue) throws with invalid StringComparison.")]
+ public void ConstructorThrowsWithInvalidStringComparison(string headerName, string headerValue, MediaTypeHeaderValue mediaType)
+ {
+ int invalidValue = 999;
+ Assert.ThrowsInvalidEnumArgument(() => new RequestHeaderMapping(headerName, headerValue, (StringComparison)invalidValue, false, mediaType),
+ "valueComparison", invalidValue, typeof(StringComparison));
+ }
+
+ [Theory]
+ [TestDataSet(
+ typeof(HttpUnitTestDataSets), "LegalHttpHeaderNames",
+ typeof(HttpUnitTestDataSets), "LegalHttpHeaderValues",
+ typeof(HttpUnitTestDataSets), "LegalMediaTypeStrings")]
+ [Trait("Description", "RequestHeaderMapping(string, string, StringComparison, bool, string) sets properties.")]
+ public void Constructor1(string headerName, string headerValue, string mediaType)
+ {
+ RequestHeaderMapping mapping = new RequestHeaderMapping(headerName, headerValue, StringComparison.CurrentCulture, true, mediaType);
+ Assert.Equal(headerName, mapping.HeaderName);
+ Assert.Equal(headerValue, mapping.HeaderValue);
+ Assert.Equal(StringComparison.CurrentCulture, mapping.HeaderValueComparison);
+ Assert.Equal(true, mapping.IsValueSubstring);
+ Assert.MediaType.AreEqual(mediaType, mapping.MediaType, "MediaType failed to set.");
+ }
+
+ [Theory]
+ [TestDataSet(
+ typeof(HttpUnitTestDataSets), "LegalMediaTypeStrings",
+ typeof(CommonUnitTestDataSets), "EmptyStrings")]
+ [Trait("Description", "RequestHeaderMapping(string, string, StringComparison, bool, string) throws with empty headerName.")]
+ public void Constructor1ThrowsWithEmptyHeaderName(string mediaType, string headerName)
+ {
+ Assert.ThrowsArgumentNull(() => new RequestHeaderMapping(headerName, "value", StringComparison.CurrentCulture, false, mediaType), "headerName");
+ }
+
+ [Theory]
+ [TestDataSet(
+ typeof(HttpUnitTestDataSets), "LegalMediaTypeStrings",
+ typeof(CommonUnitTestDataSets), "EmptyStrings")]
+ [Trait("Description", "RequestHeaderMapping(string, string, StringComparison, bool, string) throws with empty headerValue.")]
+ public void Constructor1ThrowsWithEmptyHeaderValue(string mediaType, string headerValue)
+ {
+ Assert.ThrowsArgumentNull(() => new RequestHeaderMapping("name", headerValue, StringComparison.CurrentCulture, false, mediaType), "headerValue");
+ }
+
+ [Theory]
+ [TestDataSet(
+ typeof(HttpUnitTestDataSets), "LegalHttpHeaderNames",
+ typeof(HttpUnitTestDataSets), "LegalHttpHeaderValues",
+ typeof(CommonUnitTestDataSets), "EmptyStrings")]
+ [Trait("Description", "RequestHeaderMapping(string, string, StringComparison, bool, string) throws with empty MediaTypeHeaderValue.")]
+ public void Constructor1ThrowsWithEmptyMediaType(string headerName, string headerValue, string mediaType)
+ {
+ Assert.ThrowsArgumentNull(() => new RequestHeaderMapping(headerName, headerValue, StringComparison.CurrentCulture, false, mediaType), "mediaType");
+ }
+
+ [Theory]
+ [TestDataSet(
+ typeof(HttpUnitTestDataSets), "LegalHttpHeaderNames",
+ typeof(HttpUnitTestDataSets), "LegalHttpHeaderValues",
+ typeof(HttpUnitTestDataSets), "LegalMediaTypeStrings")]
+ [Trait("Description", "RequestHeaderMapping(string, string, StringComparison, bool, string) throws with invalid StringComparison.")]
+ public void Constructor1ThrowsWithInvalidStringComparison(string headerName, string headerValue, string mediaType)
+ {
+ int invalidValue = 999;
+ Assert.ThrowsInvalidEnumArgument(
+ () => new RequestHeaderMapping(headerName, headerValue, (StringComparison)invalidValue, false, mediaType),
+ "valueComparison", invalidValue, typeof(StringComparison));
+ }
+
+ [Theory]
+ [TestDataSet(
+ typeof(HttpUnitTestDataSets), "LegalHttpHeaderNames",
+ typeof(HttpUnitTestDataSets), "LegalHttpHeaderValues",
+ typeof(HttpUnitTestDataSets), "LegalMediaTypeStrings",
+ typeof(CommonUnitTestDataSets), "Bools")]
+ [Trait("Description", "TryMatchMediaType(HttpRequestMessage) returns true when the HeaderName and HeaderValue are in the request.")]
+ public void TryMatchMediaTypeReturnsTrueWithNameAndValueInRequest(string headerName, string headerValue, string mediaType, bool subset)
+ {
+ RequestHeaderMapping mapping = new RequestHeaderMapping(headerName, headerValue, StringComparison.Ordinal, subset, mediaType);
+ HttpRequestMessage request = new HttpRequestMessage();
+ request.Headers.Add(headerName, headerValue);
+ Assert.Equal(1.0, mapping.TryMatchMediaType(request));
+ }
+
+ [Theory]
+ [TestDataSet(
+ typeof(HttpUnitTestDataSets), "LegalHttpHeaderNames",
+ typeof(HttpUnitTestDataSets), "LegalHttpHeaderValues",
+ typeof(HttpUnitTestDataSets), "LegalMediaTypeStrings")]
+ [Trait("Description", "TryMatchMediaType(HttpRequestMessage) returns true when the HeaderName and a HeaderValue subset are in the request.")]
+ public void TryMatchMediaTypeReturnsTrueWithNameAndValueSubsetInRequest(string headerName, string headerValue, string mediaType)
+ {
+ RequestHeaderMapping mapping = new RequestHeaderMapping(headerName, headerValue, StringComparison.Ordinal, true, mediaType);
+ HttpRequestMessage request = new HttpRequestMessage();
+ request.Headers.Add(headerName, "prefix" + headerValue);
+ Assert.Equal(1.0, mapping.TryMatchMediaType(request));
+
+ request = new HttpRequestMessage();
+ request.Headers.Add(headerName, headerValue + "postfix");
+ Assert.Equal(1.0, mapping.TryMatchMediaType(request));
+
+ request = new HttpRequestMessage();
+ request.Headers.Add(headerName, "prefix" + headerValue + "postfix");
+ Assert.Equal(1.0, mapping.TryMatchMediaType(request));
+ }
+
+ [Theory]
+ [TestDataSet(
+ typeof(HttpUnitTestDataSets), "LegalHttpHeaderNames",
+ typeof(HttpUnitTestDataSets), "LegalHttpHeaderValues",
+ typeof(HttpUnitTestDataSets), "LegalMediaTypeStrings")]
+ [Trait("Description", "TryMatchMediaType(HttpRequestMessage) returns false when HeaderName is not in the request.")]
+ public void TryMatchMediaTypeReturnsFalseWithNameNotInRequest(string headerName, string headerValue, string mediaType)
+ {
+ RequestHeaderMapping mapping = new RequestHeaderMapping(headerName, headerValue, StringComparison.Ordinal, false, mediaType);
+ HttpRequestMessage request = new HttpRequestMessage();
+ request.Headers.Add("prefix" + headerName, headerValue);
+ Assert.Equal(0.0, mapping.TryMatchMediaType(request));
+
+ request = new HttpRequestMessage();
+ request.Headers.Add(headerName + "postfix", headerValue);
+ Assert.Equal(0.0, mapping.TryMatchMediaType(request));
+
+ request = new HttpRequestMessage();
+ request.Headers.Add("prefix" + headerName + "postfix", headerValue);
+ Assert.Equal(0.0, mapping.TryMatchMediaType(request));
+ }
+
+ [Theory]
+ [TestDataSet(
+ typeof(HttpUnitTestDataSets), "LegalHttpHeaderNames",
+ typeof(HttpUnitTestDataSets), "LegalHttpHeaderValues",
+ typeof(HttpUnitTestDataSets), "LegalMediaTypeStrings")]
+ [Trait("Description", "TryMatchMediaType(HttpRequestMessage) returns false when HeaderValue is not in the request.")]
+ public void TryMatchMediaTypeReturnsFalseWithValueNotInRequest(string headerName, string headerValue, string mediaType)
+ {
+ RequestHeaderMapping mapping = new RequestHeaderMapping(headerName, headerValue, StringComparison.Ordinal, false, mediaType);
+ HttpRequestMessage request = new HttpRequestMessage();
+ request.Headers.Add(headerName, "prefix" + headerValue);
+ Assert.Equal(0.0, mapping.TryMatchMediaType(request));
+
+ request = new HttpRequestMessage();
+ request.Headers.Add(headerName, headerValue + "postfix");
+ Assert.Equal(0.0, mapping.TryMatchMediaType(request));
+
+ request = new HttpRequestMessage();
+ request.Headers.Add(headerName, "prefix" + headerValue + "postfix");
+ Assert.Equal(0.0, mapping.TryMatchMediaType(request));
+ }
+
+ [Theory]
+ [TestDataSet(
+ typeof(HttpUnitTestDataSets), "LegalHttpHeaderNames",
+ typeof(HttpUnitTestDataSets), "LegalHttpHeaderValues",
+ typeof(HttpUnitTestDataSets), "LegalMediaTypeStrings")]
+ [Trait("Description", "TryMatchMediaType(HttpResponseMessage) throws with a null HttpRequestMessage.")]
+ public void TryMatchMediaTypeThrowsWithNullHttpRequestMessage(string headerName, string headerValue, string mediaType)
+ {
+ RequestHeaderMapping mapping = new RequestHeaderMapping(headerName, headerValue, StringComparison.CurrentCulture, true, mediaType);
+ Assert.ThrowsArgumentNull(() => mapping.TryMatchMediaType(request: null), "request");
+ }
+ }
+}
diff --git a/test/System.Net.Http.Formatting.Test.Unit/Formatting/ThresholdStreamTest.cs b/test/System.Net.Http.Formatting.Test.Unit/Formatting/ThresholdStreamTest.cs
new file mode 100644
index 00000000..cdec7c9d
--- /dev/null
+++ b/test/System.Net.Http.Formatting.Test.Unit/Formatting/ThresholdStreamTest.cs
@@ -0,0 +1,114 @@
+using System.IO;
+using System.Net.Http.Internal;
+using System.Text;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Net.Http.Formatting
+{
+ public class ThresholdStreamTests
+ {
+ const string StringData = "abc,,,,,def";
+ const int PassThreshold = 40;
+ const int FailThreshold = 4;
+
+ [Fact]
+ public void HitLimit()
+ {
+ Stream inner = new MemoryStream(Encoding.UTF8.GetBytes(StringData));
+
+ ThresholdStream s = new ThresholdStream(inner, (byte)',', FailThreshold);
+
+ Assert.Throws<InvalidOperationException>(
+ () =>
+ {
+ // Attempt to read the stream. This should cross the limit set above and throw.
+ string content = new StreamReader(s).ReadToEnd();
+ });
+ }
+
+ [Fact]
+ public void NoHitLimit()
+ {
+ Stream inner = new MemoryStream(Encoding.UTF8.GetBytes(StringData));
+ ThresholdStream s = new ThresholdStream(inner, (byte)',', PassThreshold);
+
+ string actual = new StreamReader(s).ReadToEnd();
+
+ Assert.Equal(StringData, actual);
+ }
+
+ [Fact]
+ public void HitLimitAsync()
+ {
+ // Arrange
+ byte[] dataBytes = Encoding.UTF8.GetBytes(StringData);
+ Stream inner = new AsyncTestStream(new MemoryStream(dataBytes));
+ ThresholdStream s = new ThresholdStream(inner, (byte)',', FailThreshold);
+ byte[] buffer = new byte[StringData.Length];
+
+ // Act
+ IAsyncResult r = s.BeginRead(buffer, 0, buffer.Length, null, null);
+
+ Assert.Throws<InvalidOperationException>(
+ () =>
+ {
+ s.EndRead(r);
+ });
+ }
+
+ [Fact]
+ public void NoHitLimitAsync()
+ {
+ // Arrange
+ byte[] dataBytes = Encoding.UTF8.GetBytes(StringData);
+ Stream inner = new AsyncTestStream(new MemoryStream(dataBytes));
+ ThresholdStream s = new ThresholdStream(inner, (byte)',', PassThreshold);
+ byte[] buffer = new byte[StringData.Length];
+
+ // Act
+ IAsyncResult r = s.BeginRead(buffer, 0, buffer.Length, null, null);
+ s.EndRead(r);
+
+
+ // Assert
+ string actual = new StreamReader(new MemoryStream(buffer)).ReadToEnd();
+ Assert.Equal(actual, StringData);
+ }
+
+ // test-only Stream for verifying sync Read operations aren't called.
+ class AsyncTestStream : DelegatingStream
+ {
+ delegate int ReadDelegate(byte[] bytes, int index, int offset);
+ ReadDelegate _read;
+
+ public AsyncTestStream(Stream inner)
+ : base(inner)
+ {
+ }
+ public override int Read(byte[] buffer, int offset, int count)
+ {
+ throw new InvalidOperationException("don't use sync apis");
+ }
+
+ public override IAsyncResult BeginRead(byte[] buffer, int offset, int count, AsyncCallback callback, object state)
+ {
+ Assert.Null(_read);
+ _read = _innerStream.Read;
+ return _read.BeginInvoke(buffer, offset, count, callback, state);
+ }
+ public override int EndRead(IAsyncResult asyncResult)
+ {
+ Assert.NotNull(_read);
+ try
+ {
+ return _read.EndInvoke(asyncResult);
+ }
+ finally
+ {
+ _read = null;
+ }
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/test/System.Net.Http.Formatting.Test.Unit/Formatting/UriPathExtensionMappingTests.cs b/test/System.Net.Http.Formatting.Test.Unit/Formatting/UriPathExtensionMappingTests.cs
new file mode 100644
index 00000000..f116cafd
--- /dev/null
+++ b/test/System.Net.Http.Formatting.Test.Unit/Formatting/UriPathExtensionMappingTests.cs
@@ -0,0 +1,168 @@
+using System.Net.Http.Formatting.DataSets;
+using System.Net.Http.Headers;
+using Microsoft.TestCommon;
+using Xunit;
+using Xunit.Extensions;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Net.Http.Formatting
+{
+ public class UriPathExtensionMappingTests
+ {
+ [Fact]
+ [Trait("Description", "UriPathExtensionMapping is public, concrete, and sealed.")]
+ public void TypeIsCorrect()
+ {
+ Assert.Type.HasProperties(
+ typeof(UriPathExtensionMapping),
+ TypeAssert.TypeProperties.IsPublicVisibleClass | TypeAssert.TypeProperties.IsSealed,
+ typeof(MediaTypeMapping));
+ }
+
+ [Theory]
+ [TestDataSet(
+ typeof(HttpUnitTestDataSets), "LegalUriPathExtensions",
+ typeof(HttpUnitTestDataSets), "LegalMediaTypeStrings")]
+ [Trait("Description", "UriPathExtensionMapping(string, string) sets UriPathExtension and MediaType.")]
+ public void Constructor(string uriPathExtension, string mediaType)
+ {
+ UriPathExtensionMapping mapping = new UriPathExtensionMapping(uriPathExtension, mediaType);
+ Assert.Equal(uriPathExtension, mapping.UriPathExtension);
+ Assert.MediaType.AreEqual(mediaType, mapping.MediaType, "Failed to set MediaType.");
+ }
+
+ [Theory]
+ [TestDataSet(
+ typeof(CommonUnitTestDataSets), "EmptyStrings",
+ typeof(HttpUnitTestDataSets), "LegalMediaTypeStrings")]
+ [Trait("Description", "UriPathExtensionMapping(string, string) throws if the UriPathExtensions parameter is null.")]
+ public void ConstructorThrowsWithEmptyUriPathExtension(string uriPathExtension, string mediaType)
+ {
+ Assert.ThrowsArgumentNull(() => new UriPathExtensionMapping(uriPathExtension, mediaType), "uriPathExtension");
+ }
+
+ [Theory]
+ [TestDataSet(
+ typeof(HttpUnitTestDataSets), "LegalUriPathExtensions",
+ typeof(CommonUnitTestDataSets), "EmptyStrings")]
+ [Trait("Description", "UriPathExtensionMapping(string, string) throws if the MediaType (string) parameter is empty.")]
+ public void ConstructorThrowsWithEmptyMediaType(string uriPathExtension, string mediaType)
+ {
+ Assert.ThrowsArgumentNull(() => new UriPathExtensionMapping(uriPathExtension, mediaType), "mediaType");
+ }
+
+ [Theory]
+ [TestDataSet(
+ typeof(HttpUnitTestDataSets), "LegalUriPathExtensions",
+ typeof(HttpUnitTestDataSets), "LegalMediaTypeHeaderValues")]
+ [Trait("Description", "UriPathExtensionMapping(string, MediaTypeHeaderValue) sets UriPathExtension and MediaType.")]
+ public void Constructor1(string uriPathExtension, MediaTypeHeaderValue mediaType)
+ {
+ UriPathExtensionMapping mapping = new UriPathExtensionMapping(uriPathExtension, mediaType);
+ Assert.Equal(uriPathExtension, mapping.UriPathExtension);
+ Assert.MediaType.AreEqual(mediaType, mapping.MediaType, "Failed to set MediaType.");
+ }
+
+ [Theory]
+ [TestDataSet(
+ typeof(CommonUnitTestDataSets), "EmptyStrings",
+ typeof(HttpUnitTestDataSets), "LegalMediaTypeHeaderValues")]
+ [Trait("Description", "UriPathExtensionMapping(string, MediaTypeHeaderValue) throws if the UriPathExtensions parameter is null.")]
+ public void Constructor1ThrowsWithEmptyUriPathExtension(string uriPathExtension, MediaTypeHeaderValue mediaType)
+ {
+ Assert.ThrowsArgumentNull(() => new UriPathExtensionMapping(uriPathExtension, mediaType), "uriPathExtension");
+ }
+
+ [Theory]
+ [TestDataSet(
+ typeof(HttpUnitTestDataSets), "LegalUriPathExtensions")]
+ [Trait("Description", "UriPathExtensionMapping(string, MediaTypeHeaderValue) constructor throws if the mediaType parameter is null.")]
+ public void Constructor1ThrowsWithNullMediaType(string uriPathExtension)
+ {
+ Assert.ThrowsArgumentNull(() => new UriPathExtensionMapping(uriPathExtension, (MediaTypeHeaderValue)null), "mediaType");
+ }
+
+ [Theory]
+ [TestDataSet(
+ typeof(HttpUnitTestDataSets), "LegalUriPathExtensions",
+ typeof(HttpUnitTestDataSets), "LegalMediaTypeStrings",
+ typeof(HttpUnitTestDataSets), "UriStrings")]
+ [Trait("Description", "TryMatchMediaType(HttpRequestMessage) returns 1.0 when the extension is in the Uri.")]
+ public void TryMatchMediaTypeReturnsMatchWithExtensionInUri(string uriPathExtension, string mediaType, string baseUriString)
+ {
+ UriPathExtensionMapping mapping = new UriPathExtensionMapping(uriPathExtension, mediaType);
+ Uri baseUri = new Uri(baseUriString);
+ Uri uri = new Uri(baseUri, "x." + uriPathExtension);
+ HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, uri);
+ Assert.Equal(1.0, mapping.TryMatchMediaType(request));
+ }
+
+ [Theory]
+ [TestDataSet(
+ typeof(HttpUnitTestDataSets), "LegalUriPathExtensions",
+ typeof(HttpUnitTestDataSets), "LegalMediaTypeStrings",
+ typeof(HttpUnitTestDataSets), "UriStrings")]
+ [Trait("Description", "TryMatchMediaType(HttpRequestMessage) returns 1.0 when the extension is in the Uri but differs in case")]
+ public void TryMatchMediaTypeReturnsMatchWithExtensionInUriDifferCase(string uriPathExtension, string mediaType, string baseUriString)
+ {
+ UriPathExtensionMapping mapping = new UriPathExtensionMapping(uriPathExtension.ToUpper(), mediaType);
+ Uri baseUri = new Uri(baseUriString);
+ Uri uri = new Uri(baseUri, "x." + uriPathExtension.ToLower());
+ HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, uri);
+ Assert.Equal(1.0, mapping.TryMatchMediaType(request));
+ }
+
+ [Theory]
+ [TestDataSet(
+ typeof(HttpUnitTestDataSets), "LegalUriPathExtensions",
+ typeof(HttpUnitTestDataSets), "LegalMediaTypeStrings",
+ typeof(HttpUnitTestDataSets), "UriStrings")]
+ [Trait("Description", "TryMatchMediaType(HttpRequestMessage) returns 0.0 when the extension is not in the Uri.")]
+ public void TryMatchMediaTypeReturnsZeroWithExtensionNotInUri(string uriPathExtension, string mediaType, string baseUriString)
+ {
+ UriPathExtensionMapping mapping = new UriPathExtensionMapping(uriPathExtension, mediaType);
+ Uri baseUri = new Uri(baseUriString);
+ Uri uri = new Uri(baseUri, "x.");
+ HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, uri);
+ Assert.Equal(0.0, mapping.TryMatchMediaType(request));
+ }
+
+ [Theory]
+ [TestDataSet(
+ typeof(HttpUnitTestDataSets), "LegalUriPathExtensions",
+ typeof(HttpUnitTestDataSets), "LegalMediaTypeStrings",
+ typeof(HttpUnitTestDataSets), "UriStrings")]
+ [Trait("Description", "TryMatchMediaType(HttpRequestMessage) returns 0.0 when the uri contains the extension but does not end with it.")]
+ public void TryMatchMediaTypeReturnsZeroWithExtensionNotLastInUri(string uriPathExtension, string mediaType, string baseUriString)
+ {
+ UriPathExtensionMapping mapping = new UriPathExtensionMapping(uriPathExtension, mediaType);
+ Uri baseUri = new Uri(baseUriString);
+ Uri uri = new Uri(baseUri, "x." + uriPathExtension + "z");
+ HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, uri);
+ Assert.Equal(0.0, mapping.TryMatchMediaType(request));
+ }
+
+ [Theory]
+ [TestDataSet(
+ typeof(HttpUnitTestDataSets), "LegalUriPathExtensions",
+ typeof(HttpUnitTestDataSets), "LegalMediaTypeStrings")]
+ [Trait("Description", "TryMatchMediaType(HttpRequestMessage) throws if the request is null.")]
+ public void TryMatchMediaTypeThrowsWithNullHttpRequestMessage(string uriPathExtension, string mediaType)
+ {
+ UriPathExtensionMapping mapping = new UriPathExtensionMapping(uriPathExtension, mediaType);
+ Assert.ThrowsArgumentNull(() => mapping.TryMatchMediaType(request: null), "request");
+ }
+
+ [Theory]
+ [TestDataSet(
+ typeof(HttpUnitTestDataSets), "LegalUriPathExtensions",
+ typeof(HttpUnitTestDataSets), "LegalMediaTypeStrings")]
+ [Trait("Description", "TryMatchMediaType(HttpRequestMessage) throws if the Uri in the request is null.")]
+ public void TryMatchMediaTypeThrowsWithNullUriInHttpRequestMessage(string uriPathExtension, string mediaType)
+ {
+ UriPathExtensionMapping mapping = new UriPathExtensionMapping(uriPathExtension, mediaType);
+ string errorMessage = RS.Format(Properties.Resources.NonNullUriRequiredForMediaTypeMapping, typeof(UriPathExtensionMapping).Name);
+ Assert.Throws<InvalidOperationException>(() => mapping.TryMatchMediaType(new HttpRequestMessage()), errorMessage);
+ }
+ }
+}
diff --git a/test/System.Net.Http.Formatting.Test.Unit/Formatting/XmlMediaTypeFormatterTests.cs b/test/System.Net.Http.Formatting.Test.Unit/Formatting/XmlMediaTypeFormatterTests.cs
new file mode 100644
index 00000000..0d48a97e
--- /dev/null
+++ b/test/System.Net.Http.Formatting.Test.Unit/Formatting/XmlMediaTypeFormatterTests.cs
@@ -0,0 +1,304 @@
+using System.IO;
+using System.Net.Http.Formatting.DataSets;
+using System.Net.Http.Headers;
+using System.Runtime.Serialization;
+using System.Text;
+using System.Xml.Serialization;
+using Microsoft.TestCommon;
+using Xunit;
+using Xunit.Extensions;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Net.Http.Formatting
+{
+ public class XmlMediaTypeFormatterTests
+ {
+ [Fact]
+ [Trait("Description", "XmlMediaTypeFormatter is public, concrete, and unsealed.")]
+ public void TypeIsCorrect()
+ {
+ Assert.Type.HasProperties(typeof(XmlMediaTypeFormatter), TypeAssert.TypeProperties.IsPublicVisibleClass);
+ }
+
+ [Theory]
+ [TestDataSet(typeof(HttpUnitTestDataSets), "StandardXmlMediaTypes")]
+ [Trait("Description", "XmlMediaTypeFormatter() constructor sets standard Xml media types in SupportedMediaTypes.")]
+ public void Constructor(MediaTypeHeaderValue mediaType)
+ {
+ XmlMediaTypeFormatter formatter = new XmlMediaTypeFormatter();
+ Assert.True(formatter.SupportedMediaTypes.Contains(mediaType), String.Format("SupportedMediaTypes should have included {0}.", mediaType.ToString()));
+ }
+
+ [Fact]
+ [Trait("Description", "DefaultMediaType property returns application/xml.")]
+ public void DefaultMediaTypeReturnsApplicationXml()
+ {
+ MediaTypeHeaderValue mediaType = XmlMediaTypeFormatter.DefaultMediaType;
+ Assert.NotNull(mediaType);
+ Assert.Equal("application/xml", mediaType.MediaType);
+ }
+
+ [Fact]
+ [Trait("Description", "CharacterEncoding property handles Get/Set correctly.")]
+ public void CharacterEncodingGetSet()
+ {
+ XmlMediaTypeFormatter xmlFormatter = new XmlMediaTypeFormatter();
+ Assert.IsType<UTF8Encoding>(xmlFormatter.CharacterEncoding);
+ xmlFormatter.CharacterEncoding = Encoding.Unicode;
+ Assert.Same(Encoding.Unicode, xmlFormatter.CharacterEncoding);
+ xmlFormatter.CharacterEncoding = Encoding.UTF8;
+ Assert.Same(Encoding.UTF8, xmlFormatter.CharacterEncoding);
+ }
+
+ [Fact]
+ [Trait("Description", "CharacterEncoding property throws on invalid arguments")]
+ public void CharacterEncodingSetThrows()
+ {
+ XmlMediaTypeFormatter xmlFormatter = new XmlMediaTypeFormatter();
+ Assert.ThrowsArgumentNull(() => { xmlFormatter.CharacterEncoding = null; }, "value");
+ Assert.ThrowsArgument(() => { xmlFormatter.CharacterEncoding = Encoding.UTF32; }, "value");
+ }
+
+ [Fact]
+ [Trait("Description", "UseDataContractSerializer property should be false by default.")]
+ public void UseDataContractSerializer_Default()
+ {
+ XmlMediaTypeFormatter xmlFormatter = new XmlMediaTypeFormatter();
+ Assert.False(xmlFormatter.UseDataContractSerializer, "The UseDataContractSerializer property should be false by default.");
+ }
+
+ [Fact]
+ [Trait("Description", "UseDataContractSerializer property works when set to true.")]
+ public void UseDataContractSerializer_True()
+ {
+ XmlMediaTypeFormatter xmlFormatter = new XmlMediaTypeFormatter { UseDataContractSerializer = true };
+ MemoryStream memoryStream = new MemoryStream();
+ HttpContentHeaders contentHeaders = new StringContent(String.Empty).Headers;
+ Assert.Task.Succeeds(xmlFormatter.WriteToStreamAsync(typeof(SampleType), new SampleType(), memoryStream, contentHeaders, transportContext: null));
+ memoryStream.Position = 0;
+ string serializedString = new StreamReader(memoryStream).ReadToEnd();
+ Assert.True(serializedString.Contains("DataContractSampleType"),
+ "SampleType should be serialized with data contract name DataContractSampleType because UseDataContractSerializer is set to true.");
+ }
+
+ [Fact]
+ [Trait("Description", "UseDataContractSerializer property works when set to false.")]
+ public void UseDataContractSerializer_False()
+ {
+ XmlMediaTypeFormatter xmlFormatter = new XmlMediaTypeFormatter { UseDataContractSerializer = false };
+ MemoryStream memoryStream = new MemoryStream();
+ HttpContentHeaders contentHeaders = new StringContent(String.Empty).Headers;
+ Assert.Task.Succeeds(xmlFormatter.WriteToStreamAsync(typeof(SampleType), new SampleType(), memoryStream, contentHeaders, transportContext: null));
+ memoryStream.Position = 0;
+ string serializedString = new StreamReader(memoryStream).ReadToEnd();
+ Assert.False(serializedString.Contains("DataContractSampleType"),
+ "SampleType should not be serialized with data contract name DataContractSampleType because UseDataContractSerializer is set to false.");
+ }
+
+ [Theory]
+ [TestDataSet(typeof(CommonUnitTestDataSets), "RepresentativeValueAndRefTypeTestDataCollection")]
+ [Trait("Description", "CanReadType() returns the same result as the XmlSerializer constructor.")]
+ public void CanReadTypeReturnsSameResultAsXmlSerializerConstructor(Type variationType, object testData)
+ {
+ TestXmlMediaTypeFormatter formatter = new TestXmlMediaTypeFormatter();
+
+ bool isSerializable = IsSerializableWithXmlSerializer(variationType, testData);
+ bool canSupport = formatter.CanReadTypeCaller(variationType);
+ if (isSerializable != canSupport)
+ {
+ Assert.Equal(isSerializable, canSupport);
+ }
+
+ // Ask a 2nd time to probe whether the cached result is treated the same
+ canSupport = formatter.CanReadTypeCaller(variationType);
+ Assert.Equal(isSerializable, canSupport);
+
+ }
+
+ [Theory]
+ [TestDataSet(typeof(JsonMediaTypeFormatterTests), "JsonValueTypes")]
+ [Trait("Description", "CanReadType() returns false on JsonValue.")]
+ public void CanReadTypeReturnsFalseOnJsonValue(Type type)
+ {
+ TestXmlMediaTypeFormatter formatter = new TestXmlMediaTypeFormatter();
+ Assert.False(formatter.CanReadTypeCaller(type), "formatter should have returned false.");
+ }
+
+ [Theory]
+ [TestDataSet(typeof(JsonMediaTypeFormatterTests), "JsonValueTypes")]
+ [Trait("Description", "CanWriteType() returns false on JsonValue.")]
+ public void CanWriteTypeReturnsFalseOnJsonValue(Type type)
+ {
+ TestXmlMediaTypeFormatter formatter = new TestXmlMediaTypeFormatter();
+ Assert.False(formatter.CanWriteTypeCaller(type), "formatter should have returned false.");
+ }
+
+ [Fact]
+ [Trait("Description", "SetSerializer(Type, XmlSerializer) throws with null type.")]
+ public void SetSerializerThrowsWithNullType()
+ {
+ XmlMediaTypeFormatter formatter = new XmlMediaTypeFormatter();
+ XmlSerializer xmlSerializer = new XmlSerializer(typeof(string));
+ Assert.ThrowsArgumentNull(() => { formatter.SetSerializer(null, xmlSerializer); }, "type");
+ }
+
+ [Fact]
+ [Trait("Description", "SetSerializer(Type, XmlSerializer) throws with null serializer.")]
+ public void SetSerializerThrowsWithNullSerializer()
+ {
+ XmlMediaTypeFormatter formatter = new XmlMediaTypeFormatter();
+ Assert.ThrowsArgumentNull(() => { formatter.SetSerializer(typeof(string), (XmlSerializer)null); }, "serializer");
+ }
+
+ [Fact]
+ [Trait("Description", "SetSerializer<T>(XmlSerializer) throws with null serializer.")]
+ public void SetSerializer1ThrowsWithNullSerializer()
+ {
+ XmlMediaTypeFormatter formatter = new XmlMediaTypeFormatter();
+ Assert.ThrowsArgumentNull(() => { formatter.SetSerializer<string>((XmlSerializer)null); }, "serializer");
+ }
+
+ [Fact]
+ [Trait("Description", "SetSerializer(Type, XmlObjectSerializer) throws with null type.")]
+ public void SetSerializer2ThrowsWithNullType()
+ {
+ XmlMediaTypeFormatter formatter = new XmlMediaTypeFormatter();
+ XmlObjectSerializer xmlObjectSerializer = new DataContractSerializer(typeof(string));
+ Assert.ThrowsArgumentNull(() => { formatter.SetSerializer(null, xmlObjectSerializer); }, "type");
+ }
+
+ [Fact]
+ [Trait("Description", "SetSerializer(Type, XmlObjectSerializer) throws with null serializer.")]
+ public void SetSerializer2ThrowsWithNullSerializer()
+ {
+ XmlMediaTypeFormatter formatter = new XmlMediaTypeFormatter();
+ Assert.ThrowsArgumentNull(() => { formatter.SetSerializer(typeof(string), (XmlObjectSerializer)null); }, "serializer");
+ }
+
+ [Fact]
+ [Trait("Description", "SetSerializer<T>(XmlObjectSerializer) throws with null serializer.")]
+ public void SetSerializer3ThrowsWithNullSerializer()
+ {
+ XmlMediaTypeFormatter formatter = new XmlMediaTypeFormatter();
+ Assert.ThrowsArgumentNull(() => { formatter.SetSerializer<string>((XmlSerializer)null); }, "serializer");
+ }
+
+ [Fact]
+ [Trait("Description", "RemoveSerializer throws with null type.")]
+ public void RemoveSerializerThrowsWithNullType()
+ {
+ XmlMediaTypeFormatter formatter = new XmlMediaTypeFormatter();
+ Assert.ThrowsArgumentNull(() => { formatter.RemoveSerializer(null); }, "type");
+ }
+
+ [Theory]
+ [TestDataSet(typeof(CommonUnitTestDataSets), "RepresentativeValueAndRefTypeTestDataCollection")]
+ [Trait("Description", "ReadFromStreamAsync() returns all value and reference types serialized via WriteToStreamAsync using XmlSerializer.")]
+ public void ReadFromStreamAsyncRoundTripsWriteToStreamAsyncUsingXmlSerializer(Type variationType, object testData)
+ {
+ TestXmlMediaTypeFormatter formatter = new TestXmlMediaTypeFormatter();
+ HttpContentHeaders contentHeaders = new StringContent(String.Empty).Headers;
+
+ bool canSerialize = IsSerializableWithXmlSerializer(variationType, testData) && Assert.Http.CanRoundTrip(variationType);
+ if (canSerialize)
+ {
+ formatter.SetSerializer(variationType, new XmlSerializer(variationType));
+
+ object readObj = null;
+ Assert.Stream.WriteAndRead(
+ stream => Assert.Task.Succeeds(formatter.WriteToStreamAsync(variationType, testData, stream, contentHeaders, transportContext: null)),
+ stream => readObj = Assert.Task.SucceedsWithResult(formatter.ReadFromStreamAsync(variationType, stream, contentHeaders, null))
+ );
+ Assert.Equal(testData, readObj);
+ }
+ }
+
+ [Theory]
+ [TestDataSet(typeof(CommonUnitTestDataSets), "RepresentativeValueAndRefTypeTestDataCollection")]
+ [Trait("Description", "ReadFromStream() returns all value and reference types serialized via WriteToStream using DataContractSerializer.")]
+ public void ReadFromStreamAsyncRoundTripsWriteToStreamUsingDataContractSerializer(Type variationType, object testData)
+ {
+ TestXmlMediaTypeFormatter formatter = new TestXmlMediaTypeFormatter();
+ HttpContentHeaders contentHeaders = new StringContent(String.Empty).Headers;
+
+ bool canSerialize = IsSerializableWithDataContractSerializer(variationType, testData) && Assert.Http.CanRoundTrip(variationType);
+ if (canSerialize)
+ {
+ formatter.SetSerializer(variationType, new DataContractSerializer(variationType));
+
+ object readObj = null;
+ Assert.Stream.WriteAndRead(
+ stream => Assert.Task.Succeeds(formatter.WriteToStreamAsync(variationType, testData, stream, contentHeaders, transportContext: null)),
+ stream => readObj = Assert.Task.SucceedsWithResult(formatter.ReadFromStreamAsync(variationType, stream, contentHeaders, null))
+ );
+ Assert.Equal(testData, readObj);
+ }
+ }
+
+ public class TestXmlMediaTypeFormatter : XmlMediaTypeFormatter
+ {
+ public bool CanReadTypeCaller(Type type)
+ {
+ return CanReadType(type);
+ }
+
+ public bool CanWriteTypeCaller(Type type)
+ {
+ return CanWriteType(type);
+ }
+ }
+
+ [DataContract(Name = "DataContractSampleType")]
+ public class SampleType
+ {
+ [DataMember]
+ public int Number { get; set; }
+ }
+
+ private bool IsSerializableWithXmlSerializer(Type type, object obj)
+ {
+ if (Assert.Http.IsKnownUnserializable(type, obj))
+ {
+ return false;
+ }
+
+ try
+ {
+ new XmlSerializer(type);
+ if (obj != null && obj.GetType() != type)
+ {
+ new XmlSerializer(obj.GetType());
+ }
+ }
+ catch
+ {
+ return false;
+ }
+
+ return true;
+ }
+
+ private bool IsSerializableWithDataContractSerializer(Type type, object obj)
+ {
+ if (Assert.Http.IsKnownUnserializable(type, obj))
+ {
+ return false;
+ }
+
+ try
+ {
+ new DataContractSerializer(type);
+ if (obj != null && obj.GetType() != type)
+ {
+ new DataContractSerializer(obj.GetType());
+ }
+ }
+ catch
+ {
+ return false;
+ }
+
+ return true;
+ }
+ }
+}
diff --git a/test/System.Net.Http.Formatting.Test.Unit/FormattingUtilitiesTests.cs b/test/System.Net.Http.Formatting.Test.Unit/FormattingUtilitiesTests.cs
new file mode 100644
index 00000000..3664c75b
--- /dev/null
+++ b/test/System.Net.Http.Formatting.Test.Unit/FormattingUtilitiesTests.cs
@@ -0,0 +1,95 @@
+using System.Json;
+using System.Linq;
+using System.Net.Http.Headers;
+using Microsoft.TestCommon;
+using Xunit;
+using Xunit.Extensions;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Net.Http
+{
+ public class FormattingUtilitiesTests
+ {
+ public static TheoryDataSet<string, string> QuotedStrings
+ {
+ get
+ {
+ return new TheoryDataSet<string, string>()
+ {
+ { @"""""", @"" },
+ { @"""string""", @"string" },
+ { @"string", @"string" },
+ { @"""str""ing""", @"str""ing" },
+ };
+ }
+ }
+
+ public static TheoryDataSet<string> NotQuotedStrings
+ {
+ get
+ {
+ return new TheoryDataSet<string>
+ {
+ @" """,
+ @" """"",
+ @"string",
+ @"str""ing",
+ @"s""trin""g",
+ };
+ }
+ }
+
+ [Fact]
+ [Trait("Description", "Utilities is internal static type.")]
+ public void TypeIsCorrect()
+ {
+ Assert.Type.HasProperties(typeof(FormattingUtilities), TypeAssert.TypeProperties.IsClass | TypeAssert.TypeProperties.IsStatic);
+ }
+
+ [Fact]
+ [Trait("Description", "IsJsonValueType returns true")]
+ public void IsJsonValueTypeReturnsTrue()
+ {
+ Assert.True(FormattingUtilities.IsJsonValueType(typeof(JsonValue)), "Should return true");
+ Assert.True(FormattingUtilities.IsJsonValueType(typeof(JsonPrimitive)), "Should return true");
+ Assert.True(FormattingUtilities.IsJsonValueType(typeof(JsonObject)), "Should return true");
+ Assert.True(FormattingUtilities.IsJsonValueType(typeof(JsonArray)), "Should return true");
+ }
+
+ [Fact]
+ [Trait("Description", "CreateEmptyContentHeaders returns empty headers")]
+ public void CreateEmptyContentHeadersReturnsEmptyHeaders()
+ {
+ HttpContentHeaders headers = FormattingUtilities.CreateEmptyContentHeaders();
+ Assert.NotNull(headers);
+ Assert.Equal(0, headers.Count());
+ }
+
+ [Theory]
+ [TestDataSet(typeof(CommonUnitTestDataSets), "EmptyStrings")]
+ [Trait("Description", "UnquoteToken returns same string on null, empty strings")]
+ public void UnquoteTokenReturnsSameRefOnEmpty(string empty)
+ {
+ string result = FormattingUtilities.UnquoteToken(empty);
+ Assert.Same(empty, result);
+ }
+
+ [Theory]
+ [PropertyData("NotQuotedStrings")]
+ [Trait("Description", "UnquoteToken returns unquoted strings")]
+ public void UnquoteTokenReturnsSameRefNonQuotedStrings(string test)
+ {
+ string result = FormattingUtilities.UnquoteToken(test);
+ Assert.Equal(test, result);
+ }
+
+ [Theory]
+ [PropertyData("QuotedStrings")]
+ [Trait("Description", "UnquoteToken returns unquoted strings")]
+ public void UnquoteTokenReturnsUnquotedStrings(string token, string expectedResult)
+ {
+ string result = FormattingUtilities.UnquoteToken(token);
+ Assert.Equal(expectedResult, result);
+ }
+ }
+}
diff --git a/test/System.Net.Http.Formatting.Test.Unit/HttpClientExtensionsTest.cs b/test/System.Net.Http.Formatting.Test.Unit/HttpClientExtensionsTest.cs
new file mode 100644
index 00000000..acbb6463
--- /dev/null
+++ b/test/System.Net.Http.Formatting.Test.Unit/HttpClientExtensionsTest.cs
@@ -0,0 +1,256 @@
+using System.Net.Http.Formatting;
+using System.Net.Http.Formatting.Mocks;
+using System.Net.Http.Internal;
+using System.Net.Http.Mocks;
+using System.Threading;
+using Moq;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Net.Http
+{
+ public class HttpClientExtensionsTest
+ {
+ private readonly HttpClient _client;
+
+ public HttpClientExtensionsTest()
+ {
+ Mock<TestableHttpMessageHandler> handlerMock = new Mock<TestableHttpMessageHandler> { CallBase = true };
+ handlerMock
+ .Setup(h => h.SendAsyncPublic(It.IsAny<HttpRequestMessage>(), It.IsAny<CancellationToken>()))
+ .Returns((HttpRequestMessage request, CancellationToken _) => TaskHelpers.FromResult(new HttpResponseMessage() { RequestMessage = request }));
+
+ _client = new HttpClient(handlerMock.Object);
+ }
+
+ [Fact]
+ public void PostAsJsonAsync_WhenClientIsNull_ThrowsException()
+ {
+ HttpClient client = null;
+
+ Assert.ThrowsArgumentNull(() => client.PostAsJsonAsync("http://www.example.com", new object()), "client");
+ }
+
+ [Fact]
+ public void PostAsJsonAsync_WhenUriIsNull_ThrowsException()
+ {
+ Assert.Throws<InvalidOperationException>(() => _client.PostAsJsonAsync(null, new object()),
+ "An invalid request URI was provided. The request URI must either be an absolute URI or BaseAddress must be set.");
+ }
+
+ [Fact]
+ public void PostAsJsonAsync_UsesJsonMediaTypeFormatter()
+ {
+ var result = _client.PostAsJsonAsync("http://example.com", new object());
+
+ var response = result.Result;
+ var content = Assert.IsType<ObjectContent<object>>(response.RequestMessage.Content);
+ Assert.IsType<JsonMediaTypeFormatter>(content.Formatter);
+ }
+
+ [Fact]
+ public void PostAsXmlAsync_WhenClientIsNull_ThrowsException()
+ {
+ HttpClient client = null;
+
+ Assert.ThrowsArgumentNull(() => client.PostAsXmlAsync("http://www.example.com", new object()), "client");
+ }
+
+ [Fact]
+ public void PostAsXmlAsync_WhenUriIsNull_ThrowsException()
+ {
+ Assert.Throws<InvalidOperationException>(() => _client.PostAsXmlAsync(null, new object()),
+ "An invalid request URI was provided. The request URI must either be an absolute URI or BaseAddress must be set.");
+ }
+
+ [Fact]
+ public void PostAsXmlAsync_UsesXmlMediaTypeFormatter()
+ {
+ var result = _client.PostAsXmlAsync("http://example.com", new object());
+
+ var response = result.Result;
+ var content = Assert.IsType<ObjectContent<object>>(response.RequestMessage.Content);
+ Assert.IsType<XmlMediaTypeFormatter>(content.Formatter);
+ }
+
+ [Fact]
+ public void PostAsync_WhenClientIsNull_ThrowsException()
+ {
+ HttpClient client = null;
+
+ Assert.ThrowsArgumentNull(() => client.PostAsync("http://www.example.com", new object(), new JsonMediaTypeFormatter(), "text/json"), "client");
+ }
+
+ [Fact]
+ public void PostAsync_WhenRequestUriIsNull_ThrowsException()
+ {
+ Assert.Throws<InvalidOperationException>(() => _client.PostAsync(null, new object(), new JsonMediaTypeFormatter(), "text/json"),
+ "An invalid request URI was provided. The request URI must either be an absolute URI or BaseAddress must be set.");
+ }
+
+ [Fact]
+ public void PostAsync_WhenRequestUriIsSet_CreatesRequestWithAppropriateUri()
+ {
+ _client.BaseAddress = new Uri("http://example.com/");
+
+ var result = _client.PostAsync("myapi/", new object(), new MockMediaTypeFormatter());
+
+ var request = result.Result.RequestMessage;
+ Assert.Equal("http://example.com/myapi/", request.RequestUri.ToString());
+ }
+
+ [Fact]
+ public void PostAsync_WhenAuthoritativeMediaTypeIsSet_CreatesRequestWithAppropriateContentType()
+ {
+ var result = _client.PostAsync("http://example.com/myapi/", new object(), new MockMediaTypeFormatter(), "foo/bar; charset=utf-16");
+
+ var request = result.Result.RequestMessage;
+ Assert.Equal("foo/bar", request.Content.Headers.ContentType.MediaType);
+ Assert.Equal("utf-16", request.Content.Headers.ContentType.CharSet);
+ }
+
+ [Fact]
+ public void PostAsync_WhenFormatterIsSet_CreatesRequestWithObjectContentAndCorrectFormatter()
+ {
+ var formatter = new MockMediaTypeFormatter();
+
+ var result = _client.PostAsync("http://example.com/myapi/", new object(), formatter, "foo/bar; charset=utf-16");
+
+ var request = result.Result.RequestMessage;
+ var content = Assert.IsType<ObjectContent<object>>(request.Content);
+ Assert.Same(formatter, content.Formatter);
+ }
+
+ [Fact]
+ public void PostAsync_IssuesPostRequest()
+ {
+ var formatter = new MockMediaTypeFormatter();
+
+ var result = _client.PostAsync("http://example.com/myapi/", new object(), formatter);
+
+ var request = result.Result.RequestMessage;
+ Assert.Same(HttpMethod.Post, request.Method);
+ }
+
+ [Fact]
+ public void PostAsync_WhenMediaTypeFormatterIsNull_ThrowsException()
+ {
+ Assert.ThrowsArgumentNull(() => _client.PostAsync("http;//example.com", new object(), formatter: null), "formatter");
+ }
+
+ [Fact]
+ public void PutAsJsonAsync_WhenClientIsNull_ThrowsException()
+ {
+ HttpClient client = null;
+
+ Assert.ThrowsArgumentNull(() => client.PutAsJsonAsync("http://www.example.com", new object()), "client");
+ }
+
+ [Fact]
+ public void PutAsJsonAsync_WhenUriIsNull_ThrowsException()
+ {
+ Assert.Throws<InvalidOperationException>(() => _client.PutAsJsonAsync(null, new object()),
+ "An invalid request URI was provided. The request URI must either be an absolute URI or BaseAddress must be set.");
+ }
+
+ [Fact]
+ public void PutAsJsonAsync_UsesJsonMediaTypeFormatter()
+ {
+ var result = _client.PutAsJsonAsync("http://example.com", new object());
+
+ var response = result.Result;
+ var content = Assert.IsType<ObjectContent<object>>(response.RequestMessage.Content);
+ Assert.IsType<JsonMediaTypeFormatter>(content.Formatter);
+ }
+
+ [Fact]
+ public void PutAsXmlAsync_WhenClientIsNull_ThrowsException()
+ {
+ HttpClient client = null;
+
+ Assert.ThrowsArgumentNull(() => client.PutAsXmlAsync("http://www.example.com", new object()), "client");
+ }
+
+ [Fact]
+ public void PutAsXmlAsync_WhenUriIsNull_ThrowsException()
+ {
+ Assert.Throws<InvalidOperationException>(() => _client.PutAsXmlAsync(null, new object()),
+ "An invalid request URI was provided. The request URI must either be an absolute URI or BaseAddress must be set.");
+ }
+
+ [Fact]
+ public void PutAsXmlAsync_UsesXmlMediaTypeFormatter()
+ {
+ var result = _client.PutAsXmlAsync("http://example.com", new object());
+
+ var response = result.Result;
+ var content = Assert.IsType<ObjectContent<object>>(response.RequestMessage.Content);
+ Assert.IsType<XmlMediaTypeFormatter>(content.Formatter);
+ }
+
+ [Fact]
+ public void PutAsync_WhenClientIsNull_ThrowsException()
+ {
+ HttpClient client = null;
+
+ Assert.ThrowsArgumentNull(() => client.PutAsync("http://www.example.com", new object(), new JsonMediaTypeFormatter(), "text/json"), "client");
+ }
+
+ [Fact]
+ public void PutAsync_WhenRequestUriIsNull_ThrowsException()
+ {
+ Assert.Throws<InvalidOperationException>(() => _client.PutAsync(null, new object(), new JsonMediaTypeFormatter(), "text/json"),
+ "An invalid request URI was provided. The request URI must either be an absolute URI or BaseAddress must be set.");
+ }
+
+ [Fact]
+ public void PutAsync_WhenRequestUriIsSet_CreatesRequestWithAppropriateUri()
+ {
+ _client.BaseAddress = new Uri("http://example.com/");
+
+ var result = _client.PutAsync("myapi/", new object(), new MockMediaTypeFormatter());
+
+ var request = result.Result.RequestMessage;
+ Assert.Equal("http://example.com/myapi/", request.RequestUri.ToString());
+ }
+
+ [Fact]
+ public void PutAsync_WhenAuthoritativeMediaTypeIsSet_CreatesRequestWithAppropriateContentType()
+ {
+ var result = _client.PutAsync("http://example.com/myapi/", new object(), new MockMediaTypeFormatter(), "foo/bar; charset=utf-16");
+
+ var request = result.Result.RequestMessage;
+ Assert.Equal("foo/bar", request.Content.Headers.ContentType.MediaType);
+ Assert.Equal("utf-16", request.Content.Headers.ContentType.CharSet);
+ }
+
+ [Fact]
+ public void PutAsync_WhenFormatterIsSet_CreatesRequestWithObjectContentAndCorrectFormatter()
+ {
+ var formatter = new MockMediaTypeFormatter();
+
+ var result = _client.PutAsync("http://example.com/myapi/", new object(), formatter, "foo/bar; charset=utf-16");
+
+ var request = result.Result.RequestMessage;
+ var content = Assert.IsType<ObjectContent<object>>(request.Content);
+ Assert.Same(formatter, content.Formatter);
+ }
+
+ [Fact]
+ public void PutAsync_IssuesPutRequest()
+ {
+ var formatter = new MockMediaTypeFormatter();
+
+ var result = _client.PutAsync("http://example.com/myapi/", new object(), formatter);
+
+ var request = result.Result.RequestMessage;
+ Assert.Same(HttpMethod.Put, request.Method);
+ }
+
+ [Fact]
+ public void PutAsync_WhenMediaTypeFormatterIsNull_ThrowsException()
+ {
+ Assert.ThrowsArgumentNull(() => _client.PutAsync("http;//example.com", new object(), formatter: null), "formatter");
+ }
+ }
+}
diff --git a/test/System.Net.Http.Formatting.Test.Unit/HttpContentCollectionExtensionsTests.cs b/test/System.Net.Http.Formatting.Test.Unit/HttpContentCollectionExtensionsTests.cs
new file mode 100644
index 00000000..11aec040
--- /dev/null
+++ b/test/System.Net.Http.Formatting.Test.Unit/HttpContentCollectionExtensionsTests.cs
@@ -0,0 +1,336 @@
+using System.Collections.Generic;
+using System.Linq;
+using System.Net.Http.Headers;
+using System.Text;
+using Microsoft.TestCommon;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Net.Http
+{
+ public class HttpContentCollectionExtensionsTests
+ {
+ private const string contentID = "content-id";
+ private const string matchContentID = "matchID";
+ private const string matchContentType = "text/plain";
+ private const string matchDispositionName = "N1";
+ private const string quotedMatchDispositionName = "\"" + matchDispositionName + "\"";
+ private const string matchDispositionType = "form-data";
+ private const string quotedMatchDispositionType = "\"" + matchDispositionType + "\"";
+
+ private const string noMatchContentID = "nomatchID";
+ private const string noMatchContentType = "text/nomatch";
+ private const string noMatchDispositionName = "nomatchName";
+ private const string noMatchDispositionType = "nomatchType";
+
+ [Fact]
+ [Trait("Description", "IEnumerableHttpContentExtensionMethods is a public static class.")]
+ public void TypeIsCorrect()
+ {
+ Assert.Type.HasProperties(
+ typeof(HttpContentCollectionExtensions),
+ TypeAssert.TypeProperties.IsPublicVisibleClass |
+ TypeAssert.TypeProperties.IsStatic);
+ }
+
+
+ private static IEnumerable<HttpContent> CreateContent()
+ {
+ MultipartFormDataContent multipart = new MultipartFormDataContent();
+
+ multipart.Add(new StringContent("A", UTF8Encoding.UTF8, matchContentType), matchDispositionName);
+ multipart.Add(new StringContent("B", UTF8Encoding.UTF8, matchContentType), "N2");
+ multipart.Add(new StringContent("C", UTF8Encoding.UTF8, matchContentType), "N3");
+
+ multipart.Add(new ByteArrayContent(new byte[] { 0x65 }), "N4");
+ multipart.Add(new ByteArrayContent(new byte[] { 0x65 }), "N5");
+ multipart.Add(new ByteArrayContent(new byte[] { 0x65 }), "N6");
+
+ HttpContent cidContent = new StringContent("<html>A</html>", UTF8Encoding.UTF8, "text/html");
+ cidContent.Headers.Add(contentID, matchContentID);
+ multipart.Add(cidContent);
+
+ return multipart;
+ }
+
+ private static void ClearHeaders(IEnumerable<HttpContent> contents)
+ {
+ foreach (var c in contents)
+ {
+ c.Headers.Clear();
+ }
+ }
+
+
+
+
+ [Fact]
+ [Trait("Description", "FindAllContentType(IEnumerable<HttpContent>, string) throws on null.")]
+ public void FindAllContentTypeString()
+ {
+ Assert.ThrowsArgumentNull(() => { HttpContentCollectionExtensions.FindAllContentType(null, "text/plain"); }, "contents");
+
+ IEnumerable<HttpContent> content = HttpContentCollectionExtensionsTests.CreateContent();
+ Assert.ThrowsArgumentNull(() => { HttpContentCollectionExtensions.FindAllContentType(content, (string)null); }, "contentType");
+ foreach (string empty in TestData.EmptyStrings)
+ {
+ Assert.ThrowsArgumentNull(() => { HttpContentCollectionExtensions.FindAllContentType(content, empty); }, "contentType");
+ }
+ }
+
+ [Fact]
+ [Trait("Description", "FindAllContentType(IEnumerable<HttpContent>, string) no match.")]
+ public void FindAllContentTypeStringNoMatch()
+ {
+ IEnumerable<HttpContent> content = HttpContentCollectionExtensionsTests.CreateContent();
+ IEnumerable<HttpContent> result = null;
+ result = content.FindAllContentType(noMatchContentType);
+ Assert.Equal(0, result.Count());
+
+ ClearHeaders(content);
+ result = content.FindAllContentType(noMatchContentType);
+ Assert.Equal(0, result.Count());
+ }
+
+ [Fact]
+ [Trait("Description", "FindAllContentType(IEnumerable<HttpContent>, string) match.")]
+ public void FindAllContentTypeStringMatch()
+ {
+ IEnumerable<HttpContent> content = HttpContentCollectionExtensionsTests.CreateContent();
+ IEnumerable<HttpContent> result = content.FindAllContentType(matchContentType);
+ Assert.Equal(3, result.Count());
+ }
+
+ [Fact]
+ [Trait("Description", "FindAllContentType(IEnumerable<HttpContent>, MediaTypeHeaderValue) throws on null.")]
+ public void FindAllContentTypeMediaTypeThrows()
+ {
+ Assert.ThrowsArgumentNull(() => { HttpContentCollectionExtensions.FindAllContentType(null, new MediaTypeHeaderValue("text/plain")); }, "contents");
+
+ IEnumerable<HttpContent> content = HttpContentCollectionExtensionsTests.CreateContent();
+ Assert.ThrowsArgumentNull(() => { HttpContentCollectionExtensions.FindAllContentType(content, (MediaTypeHeaderValue)null); }, "contentType");
+ }
+
+ [Fact]
+ [Trait("Description", "FindAllContentType(IEnumerable<HttpContent>, MediaTypeHeaderValue) no match.")]
+ public void FindAllContentTypeMediaTypeNoMatch()
+ {
+ IEnumerable<HttpContent> content = HttpContentCollectionExtensionsTests.CreateContent();
+ IEnumerable<HttpContent> result = null;
+
+ result = content.FindAllContentType(new MediaTypeHeaderValue(noMatchContentType));
+ Assert.Equal(0, result.Count());
+
+ ClearHeaders(content);
+ result = content.FindAllContentType(new MediaTypeHeaderValue(noMatchContentType));
+ Assert.Equal(0, result.Count());
+ }
+
+ [Fact]
+ [Trait("Description", "FindAllContentType(IEnumerable<HttpContent>, string) match.")]
+ public void FindAllContentTypeMediaTypeMatch()
+ {
+ IEnumerable<HttpContent> content = HttpContentCollectionExtensionsTests.CreateContent();
+ IEnumerable<HttpContent> result = content.FindAllContentType(new MediaTypeHeaderValue(matchContentType));
+ Assert.Equal(3, result.Count());
+ }
+
+ [Fact]
+ [Trait("Description", "FirstDispositionName(IEnumerable<HttpContent>, string) throws on null.")]
+ public void FirstDispositionNameThrows()
+ {
+ Assert.ThrowsArgumentNull(() => { HttpContentCollectionExtensions.FirstDispositionName(null, "A"); }, "contents");
+
+ IEnumerable<HttpContent> content = HttpContentCollectionExtensionsTests.CreateContent();
+ Assert.ThrowsArgumentNull(() => { HttpContentCollectionExtensions.FirstDispositionName(content, null); }, "dispositionName");
+ foreach (string empty in TestData.EmptyStrings)
+ {
+ Assert.ThrowsArgumentNull(() => { HttpContentCollectionExtensions.FirstDispositionName(content, empty); }, "dispositionName");
+ }
+ }
+
+ [Fact]
+ [Trait("Description", "FirstDispositionName(IEnumerable<HttpContent>, string) no match.")]
+ public void FirstDispositionNameNoMatch()
+ {
+ IEnumerable<HttpContent> content = HttpContentCollectionExtensionsTests.CreateContent();
+ Assert.Null(content.FirstDispositionNameOrDefault(noMatchDispositionName));
+
+ ClearHeaders(content);
+ Assert.Throws<InvalidOperationException>(() => content.FirstDispositionName(noMatchDispositionName));
+ }
+
+ [Fact]
+ [Trait("Description", "FirstDispositionName(IEnumerable<HttpContent>, string) match.")]
+ public void FirstDispositionNameMatch()
+ {
+ IEnumerable<HttpContent> content = HttpContentCollectionExtensionsTests.CreateContent();
+ Assert.NotNull(content.FirstDispositionName(matchDispositionName));
+ Assert.NotNull(content.FirstDispositionName(quotedMatchDispositionName));
+ }
+
+ [Fact]
+ [Trait("Description", "FirstDispositionNameOrDefault(IEnumerable<HttpContent>, string) throws on null.")]
+ public void FirstDispositionNameOrDefaultThrows()
+ {
+ Assert.ThrowsArgumentNull(() => { HttpContentCollectionExtensions.FirstDispositionNameOrDefault(null, "A"); }, "contents");
+
+ IEnumerable<HttpContent> content = HttpContentCollectionExtensionsTests.CreateContent();
+ Assert.ThrowsArgumentNull(() => { HttpContentCollectionExtensions.FirstDispositionNameOrDefault(content, null); }, "dispositionName");
+ foreach (string empty in TestData.EmptyStrings)
+ {
+ Assert.ThrowsArgumentNull(() => { HttpContentCollectionExtensions.FirstDispositionNameOrDefault(content, empty); }, "dispositionName");
+ }
+ }
+
+ [Fact]
+ [Trait("Description", "FirstDispositionName(IEnumerable<HttpContent>, string) no match.")]
+ public void FirstDispositionNameOrDefaultNoMatch()
+ {
+ IEnumerable<HttpContent> content = HttpContentCollectionExtensionsTests.CreateContent();
+ Assert.Null(content.FirstDispositionNameOrDefault(noMatchDispositionName));
+
+ ClearHeaders(content);
+ Assert.Null(content.FirstDispositionNameOrDefault(noMatchDispositionName));
+ }
+
+ [Fact]
+ [Trait("Description", "FirstDispositionName(IEnumerable<HttpContent>, string) match.")]
+ public void FirstDispositionNameOrDefaultMatch()
+ {
+ IEnumerable<HttpContent> content = HttpContentCollectionExtensionsTests.CreateContent();
+ Assert.NotNull(content.FirstDispositionNameOrDefault(matchDispositionName));
+ Assert.NotNull(content.FirstDispositionNameOrDefault(quotedMatchDispositionName));
+ }
+
+ [Fact]
+ [Trait("Description", "FirstDispositionType(IEnumerable<HttpContent>, string) throws on null.")]
+ public void FirstDispositionTypeThrows()
+ {
+ Assert.ThrowsArgumentNull(() => { HttpContentCollectionExtensions.FirstDispositionType(null, "A"); }, "contents");
+
+ IEnumerable<HttpContent> content = HttpContentCollectionExtensionsTests.CreateContent();
+ Assert.ThrowsArgumentNull(() => { HttpContentCollectionExtensions.FirstDispositionType(content, null); }, "dispositionType");
+ foreach (string empty in TestData.EmptyStrings)
+ {
+ Assert.ThrowsArgumentNull(() => { HttpContentCollectionExtensions.FirstDispositionType(content, empty); }, "dispositionType");
+ }
+ }
+
+ [Fact]
+ [Trait("Description", "FirstDispositionType(IEnumerable<HttpContent>, string) no match.")]
+ public void FirstDispositionTypeNoMatch()
+ {
+ IEnumerable<HttpContent> content = HttpContentCollectionExtensionsTests.CreateContent();
+ Assert.Throws<InvalidOperationException>(() => content.FirstDispositionType(noMatchDispositionType));
+
+ Assert.Null(content.FirstDispositionTypeOrDefault(noMatchDispositionType));
+
+ ClearHeaders(content);
+ Assert.Throws<InvalidOperationException>(() => content.FirstDispositionType(noMatchDispositionType));
+ }
+
+ [Fact]
+ [Trait("Description", "FirstDispositionType(IEnumerable<HttpContent>, string) match.")]
+ public void FirstDispositionTypeMatch()
+ {
+ IEnumerable<HttpContent> content = HttpContentCollectionExtensionsTests.CreateContent();
+ Assert.NotNull(content.FirstDispositionType(matchDispositionType));
+ Assert.NotNull(content.FirstDispositionType(quotedMatchDispositionType));
+ }
+
+ [Fact]
+ [Trait("Description", "FirstDispositionTypeOrDefault(IEnumerable<HttpContent>, string) throws on null.")]
+ public void FirstDispositionTypeOrDefaultThrows()
+ {
+ Assert.ThrowsArgumentNull(() => { HttpContentCollectionExtensions.FirstDispositionTypeOrDefault(null, "A"); }, "contents");
+
+ IEnumerable<HttpContent> content = HttpContentCollectionExtensionsTests.CreateContent();
+ Assert.ThrowsArgumentNull(() => { HttpContentCollectionExtensions.FirstDispositionTypeOrDefault(content, null); }, "dispositionType");
+ foreach (string empty in TestData.EmptyStrings)
+ {
+ Assert.ThrowsArgumentNull(() => { HttpContentCollectionExtensions.FirstDispositionTypeOrDefault(content, empty); }, "dispositionType");
+ }
+ }
+
+ [Fact]
+ [Trait("Description", "FirstDispositionTypeOrDefault(IEnumerable<HttpContent>, string) no match.")]
+ public void FirstDispositionTypeOrDefaultNoMatch()
+ {
+ IEnumerable<HttpContent> content = HttpContentCollectionExtensionsTests.CreateContent();
+ Assert.Null(content.FirstDispositionTypeOrDefault(noMatchDispositionType));
+
+ ClearHeaders(content);
+ Assert.Null(content.FirstDispositionTypeOrDefault(noMatchDispositionType));
+ }
+
+ [Fact]
+ [Trait("Description", "FirstDispositionTypeOrDefault(IEnumerable<HttpContent>, string) match.")]
+ public void FirstDispositionTypeOrDefaultMatch()
+ {
+ IEnumerable<HttpContent> content = HttpContentCollectionExtensionsTests.CreateContent();
+ Assert.NotNull(content.FirstDispositionTypeOrDefault(matchDispositionType));
+ Assert.NotNull(content.FirstDispositionTypeOrDefault(quotedMatchDispositionType));
+ }
+
+ [Fact]
+ [Trait("Description", "FirstStart(IEnumerable<HttpContent>, string) throws on null.")]
+ public void FirstStartThrows()
+ {
+ Assert.ThrowsArgumentNull(() => { HttpContentCollectionExtensions.FirstStart(null, "A"); }, "contents");
+
+ IEnumerable<HttpContent> content = HttpContentCollectionExtensionsTests.CreateContent();
+ Assert.ThrowsArgumentNull(() => { HttpContentCollectionExtensions.FirstStart(content, null); }, "start");
+ foreach (string empty in TestData.EmptyStrings)
+ {
+ Assert.ThrowsArgumentNull(() => { HttpContentCollectionExtensions.FirstStart(content, empty); }, "start");
+ }
+ }
+
+ [Fact]
+ [Trait("Description", "FirstStart(IEnumerable<HttpContent>, string) no match.")]
+ public void FirstStartNoMatch()
+ {
+ IEnumerable<HttpContent> content = HttpContentCollectionExtensionsTests.CreateContent();
+ Assert.Throws<InvalidOperationException>(() => content.FirstStart(noMatchContentID));
+ }
+
+ [Fact]
+ [Trait("Description", "FirstStart(IEnumerable<HttpContent>, string) match.")]
+ public void FirstStartMatch()
+ {
+ IEnumerable<HttpContent> content = HttpContentCollectionExtensionsTests.CreateContent();
+ Assert.NotNull(content.FirstStart(matchContentID));
+ }
+
+ [Fact]
+ [Trait("Description", "FirstStartOrDefault(IEnumerable<HttpContent>, string) throws on null.")]
+ public void FirstStartOrDefaultThrows()
+ {
+ Assert.ThrowsArgumentNull(() => { HttpContentCollectionExtensions.FirstStartOrDefault(null, "A"); }, "contents");
+
+ IEnumerable<HttpContent> content = HttpContentCollectionExtensionsTests.CreateContent();
+ Assert.ThrowsArgumentNull(() => { HttpContentCollectionExtensions.FirstStartOrDefault(content, null); }, "start");
+ foreach (string empty in TestData.EmptyStrings)
+ {
+ Assert.ThrowsArgumentNull(() => { HttpContentCollectionExtensions.FirstStartOrDefault(content, empty); }, "start");
+ }
+ }
+
+ [Fact]
+ [Trait("Description", "FirstStartOrDefault(IEnumerable<HttpContent>, string) no match.")]
+ public void FirstStartOrDefaultNoMatch()
+ {
+ IEnumerable<HttpContent> content = HttpContentCollectionExtensionsTests.CreateContent();
+ Assert.Null(content.FirstStartOrDefault(noMatchContentID));
+ }
+
+ [Fact]
+ [Trait("Description", "FirstStartOrDefault(IEnumerable<HttpContent>, string) match.")]
+ public void FirstStartOrDefaultMatch()
+ {
+ IEnumerable<HttpContent> content = HttpContentCollectionExtensionsTests.CreateContent();
+ Assert.NotNull(content.FirstStartOrDefault(matchContentID));
+ }
+ }
+} \ No newline at end of file
diff --git a/test/System.Net.Http.Formatting.Test.Unit/HttpContentExtensionsTest.cs b/test/System.Net.Http.Formatting.Test.Unit/HttpContentExtensionsTest.cs
new file mode 100644
index 00000000..da4b4cc8
--- /dev/null
+++ b/test/System.Net.Http.Formatting.Test.Unit/HttpContentExtensionsTest.cs
@@ -0,0 +1,127 @@
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Net.Http.Formatting;
+using System.Net.Http.Headers;
+using System.Net.Http.Internal;
+using System.Threading.Tasks;
+using Moq;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Net.Http
+{
+ public class HttpContentExtensionsTest
+ {
+ private static readonly IEnumerable<MediaTypeFormatter> _emptyFormatterList = Enumerable.Empty<MediaTypeFormatter>();
+ private readonly Mock<MediaTypeFormatter> _formatterMock = new Mock<MediaTypeFormatter>();
+ private readonly MediaTypeHeaderValue _mediaType = new MediaTypeHeaderValue("foo/bar");
+
+ [Fact]
+ public void ReadAsAsync_WhenContentParameterIsNull_Throws()
+ {
+ Assert.ThrowsArgumentNull(() => HttpContentExtensions.ReadAsAsync(null, typeof(string), _emptyFormatterList), "content");
+ }
+
+ [Fact]
+ public void ReadAsAsync_WhenTypeParameterIsNull_Throws()
+ {
+ Assert.ThrowsArgumentNull(() => HttpContentExtensions.ReadAsAsync(new StringContent(""), null, _emptyFormatterList), "type");
+ }
+
+ [Fact]
+ public void ReadAsAsync_WhenFormattersParameterIsNull_Throws()
+ {
+ Assert.ThrowsArgumentNull(() => HttpContentExtensions.ReadAsAsync(new StringContent(""), typeof(string), null), "formatters");
+ }
+
+ [Fact]
+ public void ReadAsAsyncOfT_WhenContentParameterIsNull_Throws()
+ {
+ Assert.ThrowsArgumentNull(() => HttpContentExtensions.ReadAsAsync<string>(null, _emptyFormatterList), "content");
+ }
+
+ [Fact]
+ public void ReadAsAsyncOfT_WhenFormattersParameterIsNull_Throws()
+ {
+ Assert.ThrowsArgumentNull(() => HttpContentExtensions.ReadAsAsync<string>(new StringContent(""), null), "formatters");
+ }
+
+ [Fact]
+ public void ReadAsAsyncOfT_WhenNoMatchingFormatterFound_Throws()
+ {
+ var content = new StringContent("{}");
+ content.Headers.ContentType = _mediaType;
+ content.Headers.ContentType.CharSet = "utf-16";
+ var formatters = new MediaTypeFormatter[] { new JsonMediaTypeFormatter() };
+
+ Assert.Throws<InvalidOperationException>(() => content.ReadAsAsync<List<string>>(formatters),
+ "No MediaTypeFormatter is available to read an object of type 'List`1' from content with media type 'foo/bar'.");
+ }
+
+ [Fact]
+ public void ReadAsAsyncOfT_WhenNoMatchingFormatterFoundForContentWithNoMediaType_Throws()
+ {
+ var content = new StringContent("{}");
+ content.Headers.ContentType = null;
+ var formatters = new MediaTypeFormatter[] { new JsonMediaTypeFormatter() };
+
+ Assert.Throws<InvalidOperationException>(() => content.ReadAsAsync<List<string>>(formatters),
+ "No MediaTypeFormatter is available to read an object of type 'List`1' from content with media type ''undefined''.");
+ }
+
+ [Fact]
+ public void ReadAsAsyncOfT_ReadsFromContent_ThenInvokesFormattersReadFromStreamMethod()
+ {
+ Stream contentStream = null;
+ string value = "42";
+ var contentMock = new Mock<TestableHttpContent> { CallBase = true };
+ contentMock.Setup(c => c.SerializeToStreamAsyncPublic(It.IsAny<Stream>(), It.IsAny<TransportContext>()))
+ .Returns(TaskHelpers.Completed)
+ .Callback((Stream s, TransportContext _) => contentStream = s)
+ .Verifiable();
+ HttpContent content = contentMock.Object;
+ content.Headers.ContentType = _mediaType;
+ _formatterMock
+ .Setup(f => f.ReadFromStreamAsync(typeof(string), It.IsAny<Stream>(), It.IsAny<HttpContentHeaders>(), It.IsAny<IFormatterLogger>()))
+ .Returns(TaskHelpers.FromResult<object>(value));
+ _formatterMock.Setup(f => f.CanReadType(typeof(string))).Returns(true);
+ _formatterMock.Object.SupportedMediaTypes.Add(_mediaType);
+ var formatters = new[] { _formatterMock.Object };
+
+ var result = content.ReadAsAsync<string>(formatters);
+
+ var resultValue = result.Result;
+ Assert.Same(value, resultValue);
+ contentMock.Verify();
+ _formatterMock.Verify(f => f.ReadFromStreamAsync(typeof(string), contentStream, content.Headers, null), Times.Once());
+ }
+
+ public abstract class TestableHttpContent : HttpContent
+ {
+ protected override Task<Stream> CreateContentReadStreamAsync()
+ {
+ return CreateContentReadStreamAsyncPublic();
+ }
+
+ public virtual Task<Stream> CreateContentReadStreamAsyncPublic()
+ {
+ return base.CreateContentReadStreamAsync();
+ }
+
+ protected override Task SerializeToStreamAsync(Stream stream, TransportContext context)
+ {
+ return SerializeToStreamAsyncPublic(stream, context);
+ }
+
+ public abstract Task SerializeToStreamAsyncPublic(Stream stream, TransportContext context);
+
+ protected override bool TryComputeLength(out long length)
+ {
+ return TryComputeLengthPublic(out length);
+ }
+
+ public abstract bool TryComputeLengthPublic(out long length);
+ }
+ }
+}
diff --git a/test/System.Net.Http.Formatting.Test.Unit/HttpContentMessageExtensionsTests.cs b/test/System.Net.Http.Formatting.Test.Unit/HttpContentMessageExtensionsTests.cs
new file mode 100644
index 00000000..ad0f568d
--- /dev/null
+++ b/test/System.Net.Http.Formatting.Test.Unit/HttpContentMessageExtensionsTests.cs
@@ -0,0 +1,491 @@
+using System.Collections.Generic;
+using System.IO;
+using System.Text;
+using Microsoft.TestCommon;
+using Xunit;
+using Xunit.Extensions;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Net.Http
+{
+ public class HttpContentMessageExtensionsTests
+ {
+ [Fact]
+ public void TypeIsCorrect()
+ {
+ Assert.Type.HasProperties(
+ typeof(HttpContentMessageExtensions),
+ TypeAssert.TypeProperties.IsPublicVisibleClass |
+ TypeAssert.TypeProperties.IsStatic);
+ }
+
+ private static HttpContent CreateContent(bool isRequest, bool hasEntity)
+ {
+ string message;
+ if (isRequest)
+ {
+ message = hasEntity ? ParserData.HttpRequestWithEntity : ParserData.HttpRequest;
+ }
+ else
+ {
+ message = hasEntity ? ParserData.HttpResponseWithEntity : ParserData.HttpResponse;
+ }
+
+ StringContent content = new StringContent(message);
+ content.Headers.ContentType = isRequest ? ParserData.HttpRequestMediaType : ParserData.HttpResponseMediaType;
+ return content;
+ }
+
+ private static HttpContent CreateContent(bool isRequest, IEnumerable<string> header, string body)
+ {
+ StringBuilder message = new StringBuilder();
+ foreach (string h in header)
+ {
+ message.Append(h);
+ message.Append("\r\n");
+ }
+
+ message.Append("\r\n");
+ if (body != null)
+ {
+ message.Append(body);
+ }
+
+ StringContent content = new StringContent(message.ToString());
+ content.Headers.ContentType = isRequest ? ParserData.HttpRequestMediaType : ParserData.HttpResponseMediaType;
+ return content;
+ }
+
+ private static void ValidateEntity(HttpContent content)
+ {
+ Assert.NotNull(content);
+ Assert.Equal(ParserData.TextContentType, content.Headers.ContentType.ToString());
+ string entity = content.ReadAsStringAsync().Result;
+ Assert.Equal(ParserData.HttpMessageEntity, entity);
+ }
+
+ private static void ValidateRequestMessage(HttpRequestMessage request, bool hasEntity)
+ {
+ Assert.NotNull(request);
+ Assert.Equal(Version.Parse("1.2"), request.Version);
+ Assert.Equal(ParserData.HttpMethod, request.Method.ToString());
+ Assert.Equal(ParserData.HttpRequestUri, request.RequestUri);
+ Assert.Equal(ParserData.HttpHostName, request.Headers.Host);
+ Assert.True(request.Headers.Contains("N1"), "request did not contain expected N1 header.");
+ Assert.True(request.Headers.Contains("N2"), "request did not contain expected N2 header.");
+
+ if (hasEntity)
+ {
+ ValidateEntity(request.Content);
+ }
+ }
+
+ private static void ValidateResponseMessage(HttpResponseMessage response, bool hasEntity)
+ {
+ Assert.NotNull(response);
+ Assert.Equal(new Version("1.2"), response.Version);
+ Assert.Equal(ParserData.HttpStatus, response.StatusCode);
+ Assert.Equal(ParserData.HttpReasonPhrase, response.ReasonPhrase);
+ Assert.True(response.Headers.Contains("N1"), "Response did not contain expected N1 header.");
+ Assert.True(response.Headers.Contains("N2"), "Response did not contain expected N2 header.");
+
+ if (hasEntity)
+ {
+ ValidateEntity(response.Content);
+ }
+ }
+
+ [Fact]
+ public void ReadAsHttpRequestMessageVerifyArguments()
+ {
+ Assert.ThrowsArgumentNull(() => HttpContentMessageExtensions.ReadAsHttpRequestMessageAsync(null), "content");
+ Assert.ThrowsArgument(() => new ByteArrayContent(new byte[] { }).ReadAsHttpRequestMessageAsync(), "content");
+ Assert.ThrowsArgument(() => new StringContent(String.Empty).ReadAsHttpRequestMessageAsync(), "content");
+ Assert.ThrowsArgument(() => new StringContent(String.Empty, Encoding.UTF8, "application/http").ReadAsHttpRequestMessageAsync(), "content");
+
+ Assert.ThrowsArgument(() =>
+ {
+ HttpContent content = new StringContent(String.Empty);
+ content.Headers.ContentType = ParserData.HttpResponseMediaType;
+ content.ReadAsHttpRequestMessageAsync();
+ }, "content");
+
+ Assert.ThrowsArgumentNull(() =>
+ {
+ HttpContent content = new StringContent(String.Empty);
+ content.Headers.ContentType = ParserData.HttpRequestMediaType;
+ content.ReadAsHttpRequestMessageAsync(null);
+ }, "uriScheme");
+
+ Assert.ThrowsArgument(() =>
+ {
+ HttpContent content = new StringContent(String.Empty);
+ content.Headers.ContentType = ParserData.HttpRequestMediaType;
+ content.ReadAsHttpRequestMessageAsync("i n v a l i d");
+ }, "uriScheme");
+
+ Assert.ThrowsArgument(() =>
+ {
+ HttpContent content = new StringContent(String.Empty);
+ content.Headers.ContentType = ParserData.HttpRequestMediaType;
+ content.ReadAsHttpRequestMessageAsync(Uri.UriSchemeHttp, ParserData.MinHeaderSize - 1);
+ }, "bufferSize");
+ }
+
+ [Fact]
+ public void ReadAsHttpResponseMessageVerifyArguments()
+ {
+ Assert.ThrowsArgumentNull(() => HttpContentMessageExtensions.ReadAsHttpResponseMessageAsync(null), "content");
+ Assert.ThrowsArgument(() => new ByteArrayContent(new byte[] { }).ReadAsHttpResponseMessageAsync(), "content");
+ Assert.ThrowsArgument(() => new StringContent(String.Empty).ReadAsHttpResponseMessageAsync(), "content");
+ Assert.ThrowsArgument(() => new StringContent(String.Empty, Encoding.UTF8, "application/http").ReadAsHttpResponseMessageAsync(), "content");
+
+ Assert.ThrowsArgument(() =>
+ {
+ HttpContent content = new StringContent(String.Empty);
+ content.Headers.ContentType = ParserData.HttpRequestMediaType;
+ content.ReadAsHttpResponseMessageAsync();
+ }, "content");
+
+ Assert.ThrowsArgument(() =>
+ {
+ HttpContent content = new StringContent(String.Empty);
+ content.Headers.ContentType = ParserData.HttpResponseMediaType;
+ content.ReadAsHttpResponseMessageAsync(ParserData.MinHeaderSize - 1);
+ }, "bufferSize");
+ }
+
+ [Fact]
+ public void IsHttpRequestMessageContentVerifyArguments()
+ {
+ Assert.ThrowsArgumentNull(() => HttpContentMessageExtensions.IsHttpRequestMessageContent(null), "content");
+ }
+
+ [Fact]
+ public void IsHttpResponseMessageContentVerifyArguments()
+ {
+ Assert.ThrowsArgumentNull(() =>
+ {
+ HttpContent content = null;
+ HttpContentMessageExtensions.IsHttpResponseMessageContent(content);
+ }, "content");
+ }
+
+ public static TheoryDataSet<HttpContent> NotHttpMessageContent
+ {
+ get
+ {
+ return new TheoryDataSet<HttpContent>
+ {
+ new ByteArrayContent(new byte[] { }),
+ new StringContent(String.Empty),
+ new StringContent(String.Empty, Encoding.UTF8, "application/http"),
+ };
+ }
+ }
+
+ [Theory]
+ [PropertyData("NotHttpMessageContent")]
+ public void IsHttpRequestMessageContentRespondsFalse(HttpContent notHttpMessageContent)
+ {
+ Assert.False(notHttpMessageContent.IsHttpRequestMessageContent());
+ }
+
+ [Fact]
+ public void IsHttpRequestMessageContentRespondsTrue()
+ {
+ HttpContent content = new StringContent(String.Empty);
+ content.Headers.ContentType = ParserData.HttpRequestMediaType;
+ Assert.True(content.IsHttpRequestMessageContent(), "Content should be HTTP request.");
+ }
+
+ [Theory]
+ [PropertyData("NotHttpMessageContent")]
+ public void IsHttpResponseMessageContent(HttpContent notHttpMessageContent)
+ {
+ Assert.False(notHttpMessageContent.IsHttpResponseMessageContent());
+
+ }
+
+ [Fact]
+ public void IsHttpResponseMessageContentRespondsTrue()
+ {
+ HttpContent content = new StringContent(String.Empty);
+ content.Headers.ContentType = ParserData.HttpResponseMediaType;
+ Assert.True(content.IsHttpResponseMessageContent(), "Content should be HTTP response.");
+ }
+
+ [Fact]
+ public void ReadAsHttpRequestMessageAsync_RequestWithoutEntity_ShouldReturnHttpRequestMessage()
+ {
+ HttpContent content = CreateContent(isRequest: true, hasEntity: false);
+ HttpRequestMessage httpRequest = content.ReadAsHttpRequestMessageAsync().Result;
+ ValidateRequestMessage(httpRequest, hasEntity: false);
+ }
+
+ [Fact]
+ public void ReadAsHttpRequestMessageAsync_RequestWithEntity_ShouldReturnHttpRequestMessage()
+ {
+ HttpContent content = CreateContent(isRequest: true, hasEntity: true);
+ HttpRequestMessage httpRequest = content.ReadAsHttpRequestMessageAsync().Result;
+ ValidateRequestMessage(httpRequest, hasEntity: true);
+ }
+
+ [Fact]
+ public void ReadAsHttpRequestMessageAsync_WithHttpsUriScheme_ReturnsUriWithHttps()
+ {
+ HttpContent content = CreateContent(isRequest: true, hasEntity: true);
+ HttpRequestMessage httpRequest = content.ReadAsHttpRequestMessageAsync(Uri.UriSchemeHttps).Result;
+ Assert.Equal(ParserData.HttpsRequestUri, httpRequest.RequestUri);
+ }
+
+ [Fact]
+ public void ReadAsHttpResponseMessageAsync_ResponseWithoutEntity_ShouldReturnHttpResponseMessage()
+ {
+ HttpContent content = CreateContent(isRequest: false, hasEntity: false);
+ HttpResponseMessage httpResponse = content.ReadAsHttpResponseMessageAsync().Result;
+ ValidateResponseMessage(httpResponse, hasEntity: false);
+ }
+
+ [Fact]
+ public void ReadAsHttpResponseMessageAsync_ResponseWithEntity_ShouldReturnHttpResponseMessage()
+ {
+ HttpContent content = CreateContent(isRequest: false, hasEntity: true);
+ HttpResponseMessage httpResponse = content.ReadAsHttpResponseMessageAsync().Result;
+ ValidateResponseMessage(httpResponse, hasEntity: true);
+ }
+
+ [Fact]
+ public void ReadAsHttpRequestMessageAsync_NoHostHeader_ThrowsIOException()
+ {
+ string[] request = new[] {
+ @"GET / HTTP/1.1",
+ };
+
+ HttpContent content = CreateContent(true, request, null);
+ Assert.Throws<IOException>(() => content.ReadAsHttpRequestMessageAsync().Result);
+ }
+
+ [Fact]
+ [Trait("Description", "ReadAsHttpRequestMessage should return HttpRequestMessage.")]
+ public void ReadAsHttpRequestMessageAsync_TwoHostHeaders_ThrowsIOException()
+ {
+ string[] request = new[] {
+ @"GET / HTTP/1.1",
+ @"Host: somehost.com",
+ @"Host: otherhost.com",
+ };
+
+ HttpContent content = CreateContent(true, request, null);
+ Assert.Throws<IOException>(() => content.ReadAsHttpRequestMessageAsync().Result);
+ }
+
+ [Fact]
+ public void ReadAsHttpRequestMessageAsync_IE_ShouldBeDeserializedCorrectly()
+ {
+ string[] request = new[] {
+ @"GET / HTTP/1.1",
+ @"Accept: text/html, application/xhtml+xml, */*",
+ @"Accept-Language: en-US",
+ @"User-Agent: Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; WOW64; Trident/5.0)",
+ @"Accept-Encoding: gzip, deflate",
+ @"Proxy-Connection: Keep-Alive",
+ @"Host: msdn.microsoft.com",
+ @"Cookie: omniID=1297715979621_9f45_1519_3f8a_f22f85346ac6; WT_FPC=id=65.55.227.138-2323234032.30136233:lv=1309374389020:ss=1309374389020; A=I&I=AxUFAAAAAACNCAAADYEZ7CFPss7Swnujy4PXZA!!&M=1&CS=126mAa0002ZB51a02gZB51a; MC1=GUID=568428660ad44d4ab8f46133f4b03738&HASH=6628&LV=20113&V=3; WT_NVR_RU=0=msdn:1=:2=; MUID=A44DE185EA1B4E8088CCF7B348C5D65F; MSID=Microsoft.CreationDate=03/04/2011 23:38:15&Microsoft.LastVisitDate=06/20/2011 04:15:08&Microsoft.VisitStartDate=06/20/2011 04:15:08&Microsoft.CookieId=f658f3f2-e6d6-42ab-b86b-96791b942b6f&Microsoft.TokenId=ffffffff-ffff-ffff-ffff-ffffffffffff&Microsoft.NumberOfVisits=106&Microsoft.CookieFirstVisit=1&Microsoft.IdentityToken=AA==&Microsoft.MicrosoftId=0441-6141-1523-9969; msresearch=%7B%22version%22%3A%224.6%22%2C%22state%22%3A%7B%22name%22%3A%22IDLE%22%2C%22url%22%3Aundefined%2C%22timestamp%22%3A1299281911415%7D%2C%22lastinvited%22%3A1299281911415%2C%22userid%22%3A%2212992819114151265672533023080%22%2C%22vendorid%22%3A1%2C%22surveys%22%3A%5Bundefined%5D%7D; CodeSnippetContainerLang=C#; msdn=L=1033; ADS=SN=175A21EF; s_cc=true; s_sq=%5B%5BB%5D%5D; TocHashCookie=ms310241(n)/aa187916(n)/aa187917(n)/dd273952(n)/dd295083(n)/ff472634(n)/ee667046(n)/ee667070(n)/gg259047(n)/gg618436(n)/; WT_NVR=0=/:1=query|library|en-us:2=en-us/vcsharp|en-us/library",
+ };
+
+ HttpContent content = CreateContent(true, request, null);
+ HttpRequestMessage httpRequest = content.ReadAsHttpRequestMessageAsync().Result;
+ Assert.True(httpRequest.Headers.Contains("cookie"));
+ }
+
+ [Fact]
+ public void ReadAsHttpRequestMessageAsync_Firefox_ShouldBeDeserializedCorrectly()
+ {
+ string[] request = new[] {
+ @"GET / HTTP/1.1",
+ @"Host: msdn.microsoft.com",
+ @"User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64; rv:5.0) Gecko/20100101 Firefox/5.0",
+ @"Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
+ @"Accept-Language: en-us,en;q=0.5",
+ @"Accept-Encoding: gzip, deflate",
+ @"Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7",
+ @"Proxy-Connection: keep-alive",
+ };
+
+ HttpContent content = CreateContent(true, request, null);
+ HttpRequestMessage httpRequest = content.ReadAsHttpRequestMessageAsync().Result;
+ Assert.True(httpRequest.Headers.Contains("proxy-connection"));
+ }
+
+ [Fact]
+ public void ReadAsHttpRequestMessageAsync_Chrome_ShouldBeDeserializedCorrectly()
+ {
+ string[] request = new string[] {
+ @"GET / HTTP/1.1",
+ @"Host: msdn.microsoft.com",
+ @"Proxy-Connection: keep-alive",
+ @"User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/534.30 (KHTML, like Gecko) Chrome/12.0.742.100 Safari/534.30",
+ @"Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
+ @"Accept-Encoding: gzip,deflate,sdch",
+ @"Accept-Language: en-US,en;q=0.8",
+ @"Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.3",
+ };
+
+ HttpContent content = CreateContent(true, request, null);
+ HttpRequestMessage httpRequest = content.ReadAsHttpRequestMessageAsync().Result;
+ Assert.True(httpRequest.Headers.Contains("accept-charset"));
+ }
+
+ [Fact]
+ public void ReadAsHttpRequestMessageAsync_Safari_ShouldBeDeserializedCorrectly()
+ {
+ string[] request = new string[] {
+ @"GET / HTTP/1.1",
+ @"Host: msdn.microsoft.com",
+ @"User-Agent: Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/533.21.1 (KHTML, like Gecko) Version/5.0.5 Safari/533.21.1",
+ @"Accept: application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5",
+ @"Accept-Language: en-US",
+ @"Accept-Encoding: gzip, deflate",
+ @"Connection: keep-alive",
+ @"Proxy-Connection: keep-alive",
+ };
+
+ HttpContent content = CreateContent(true, request, null);
+ HttpRequestMessage httpRequest = content.ReadAsHttpRequestMessageAsync().Result;
+ Assert.True(httpRequest.Headers.Contains("proxy-connection"));
+ }
+
+ [Fact]
+ public void ReadAsHttpRequestMessageAsync_Opera_ShouldBeDeserializedCorrectly()
+ {
+ string[] request = new string[] {
+ @"GET / HTTP/1.0",
+ @"User-Agent: Opera/9.80 (Windows NT 6.1; U; en) Presto/2.8.131 Version/11.11",
+ @"Host: msdn.microsoft.com",
+ @"Accept: text/html, application/xml;q=0.9, application/xhtml+xml, image/png, image/webp, image/jpeg, image/gif, image/x-xbitmap, */*;q=0.1",
+ @"Accept-Language: en-US,en;q=0.9",
+ @"Accept-Encoding: gzip, deflate",
+ @"Proxy-Connection: Keep-Alive",
+ };
+
+ HttpContent content = CreateContent(true, request, null);
+ HttpRequestMessage httpRequest = content.ReadAsHttpRequestMessageAsync().Result;
+ Assert.True(httpRequest.Headers.Contains("proxy-connection"));
+ }
+
+ [Fact]
+ public void ReadAsHttpResponseMessageAsync_Asp_ShouldBeDeserializedCorrectly()
+ {
+ string[] response = new string[] {
+ @"HTTP/1.1 302 Found",
+ @"Proxy-Connection: Keep-Alive",
+ @"Connection: Keep-Alive",
+ @"Content-Length: 124",
+ @"Via: 1.1 RED-PRXY-23",
+ @"Date: Thu, 30 Jun 2011 00:16:35 GMT",
+ @"Location: /en-us/",
+ @"Content-Type: text/html; charset=utf-8",
+ @"Server: Microsoft-IIS/7.5",
+ @"Cache-Control: private",
+ @"P3P: CP=""ALL IND DSP COR ADM CONo CUR CUSo IVAo IVDo PSA PSD TAI TELo OUR SAMo CNT COM INT NAV ONL PHY PRE PUR UNI""",
+ @"Set-Cookie: A=I&I=AxUFAAAAAAD7BwAA8Jx0njhGoW3MGASDmzeaGw!!&M=1; domain=.microsoft.com; expires=Sun, 30-Jun-2041 00:16:35 GMT; path=/",
+ @"Set-Cookie: ADS=SN=175A21EF; domain=.microsoft.com; path=/",
+ @"Set-Cookie: Sto.UserLocale=en-us; path=/",
+ @"X-AspNetMvc-Version: 3.0",
+ @"X-AspNet-Version: 4.0.30319",
+ @"X-Powered-By: ASP.NET",
+ @"Set-Cookie: A=I&I=AxUFAAAAAAD7BwAA8Jx0njhGoW3MGASDmzeaGw!!&M=1; domain=.microsoft.com; expires=Sun, 30-Jun-2041 00:16:35 GMT; path=/; path=/",
+ @"Set-Cookie: ADS=SN=175A21EF; domain=.microsoft.com; path=/; path=/",
+ @"P3P: CP=""ALL IND DSP COR ADM CONo CUR CUSo IVAo IVDo PSA PSD TAI TELo OUR SAMo CNT COM INT NAV ONL PHY PRE PUR UNI""",
+ @"X-Powered-By: ASP.NET",
+ };
+ string expectedEntity = @"<html><head><title>Object moved</title></head><body><h2>Object moved to <a href=""/en-us/"">here</a>.</h2></body></html>";
+
+ HttpContent content = CreateContent(false, response, expectedEntity);
+ HttpResponseMessage httpResponse = content.ReadAsHttpResponseMessageAsync().Result;
+ Assert.True(httpResponse.Headers.Contains("x-powered-by"));
+ string actualEntity = httpResponse.Content.ReadAsStringAsync().Result;
+ Assert.Equal(expectedEntity, actualEntity);
+ }
+
+ public static TheoryDataSet<IEnumerable<string>> ServerRoundTripData
+ {
+ get
+ {
+ return new TheoryDataSet<IEnumerable<string>>
+ {
+ new string[]
+ {
+ @"HTTP/1.1 200 OK",
+ @"Server: nginx",
+ @"Date: Mon, 26 Dec 2011 16:33:07 GMT",
+ @"Connection: keep-alive",
+ @"Set-Cookie: CG=US:WA:Bellevue; path=/",
+ @"Vary: Accept-Encoding, User-Agent",
+ @"Cache-Control: max-age=60, private",
+ @"Content-Length: 124",
+ @"Content-Type: text/html; charset=UTF-8",
+ },
+ new string[]
+ {
+ @"HTTP/1.1 302 Found",
+ @"Proxy-Connection: Keep-Alive",
+ @"Connection: Keep-Alive",
+ @"Via: 1.1 RED-PRXY-23",
+ @"Date: Thu, 30 Jun 2011 00:16:35 GMT",
+ @"Location: /en-us/",
+ @"Server: Microsoft-IIS/7.5",
+ @"Cache-Control: private",
+ @"P3P: CP=""ALL IND DSP COR ADM CONo CUR CUSo IVAo IVDo PSA PSD TAI TELo OUR SAMo CNT COM INT NAV ONL PHY PRE PUR UNI"", CP=""ALL IND DSP COR ADM CONo CUR CUSo IVAo IVDo PSA PSD TAI TELo OUR SAMo CNT COM INT NAV ONL PHY PRE PUR UNI""",
+ @"Set-Cookie: A=I&I=AxUFAAAAAAD7BwAA8Jx0njhGoW3MGASDmzeaGw!!&M=1; domain=.microsoft.com; expires=Sun, 30-Jun-2041 00:16:35 GMT; path=/",
+ @"Set-Cookie: ADS=SN=175A21EF; domain=.microsoft.com; path=/",
+ @"Set-Cookie: Sto.UserLocale=en-us; path=/",
+ @"Set-Cookie: A=I&I=AxUFAAAAAAD7BwAA8Jx0njhGoW3MGASDmzeaGw!!&M=1; domain=.microsoft.com; expires=Sun, 30-Jun-2041 00:16:35 GMT; path=/; path=/",
+ @"Set-Cookie: ADS=SN=175A21EF; domain=.microsoft.com; path=/; path=/",
+ @"X-AspNetMvc-Version: 3.0",
+ @"X-AspNet-Version: 4.0.30319",
+ @"X-Powered-By: ASP.NET",
+ @"X-Powered-By: ASP.NET",
+ @"Content-Length: 124",
+ @"Content-Type: text/html; charset=utf-8",
+ },
+ new string[]
+ {
+ @"HTTP/1.1 200 OK",
+ @"Proxy-Connection: Keep-Alive",
+ @"Connection: Keep-Alive",
+ @"Transfer-Encoding: chunked",
+ @"Via: 1.1 RED-PRXY-07",
+ @"Date: Mon, 26 Dec 2011 19:11:47 GMT",
+ @"Server: gws",
+ @"Cache-Control: max-age=0, private",
+ @"Set-Cookie: PREF=ID=e91cfd77b562e989:FF=0:TM=1324926707:LM=1324926707:S=4w8_eSySJPXCCjhT; expires=Wed, 25-Dec-2013 19:11:47 GMT; path=/; domain=.google.com",
+ @"Set-Cookie: NID=54=bSMpxl0q0MVlvG-eZYSBtQuYTF1clqrA-TSIZT8wZcbhrrsdkP9G5zPiXGSBmiNu656QR3xfTXKUPkP-HqY_nSnsjj1fb-ipoZ3DUcyXb9oS9_8tjz3NZ3A44GPCmRPx; expires=Tue, 26-Jun-2012 19:11:47 GMT; path=/; domain=.google.com; HttpOnly",
+ @"P3P: CP=""This is not a P3P policy! See http://www.google.com/support/accounts/bin/answer.py?hl=en&answer=151657 for more info.""",
+ @"X-XSS-Protection: 1; mode=block",
+ @"X-Frame-Options: SAMEORIGIN",
+ @"Expires: -1",
+ @"Content-Type: text/html; charset=ISO-8859-1",
+ },
+ };
+ }
+ }
+
+ [Theory]
+ [PropertyData("ServerRoundTripData")]
+ public void RoundtripServerResponse(IEnumerable<string> message)
+ {
+ HttpContent content = CreateContent(false, message, @"<html><head><title>Object moved</title></head><body><h2>Object moved to <a href=""/en-us/"">here</a>.</h2></body></html>");
+ HttpResponseMessage httpResponse = content.ReadAsHttpResponseMessageAsync().Result;
+ HttpMessageContent httpMessageContent = new HttpMessageContent(httpResponse);
+
+ MemoryStream destination = new MemoryStream();
+ httpMessageContent.CopyToAsync(destination).Wait();
+ destination.Seek(0, SeekOrigin.Begin);
+ string destinationMessage = new StreamReader(destination).ReadToEnd();
+ string sourceMessage = content.ReadAsStringAsync().Result;
+ Assert.Equal(sourceMessage, destinationMessage);
+ }
+ }
+}
diff --git a/test/System.Net.Http.Formatting.Test.Unit/HttpContentMultipartExtensionsTests.cs b/test/System.Net.Http.Formatting.Test.Unit/HttpContentMultipartExtensionsTests.cs
new file mode 100644
index 00000000..a266c634
--- /dev/null
+++ b/test/System.Net.Http.Formatting.Test.Unit/HttpContentMultipartExtensionsTests.cs
@@ -0,0 +1,512 @@
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Net.Http.Formatting.Parsers;
+using System.Net.Http.Headers;
+using System.Text;
+using System.Threading.Tasks;
+using Microsoft.TestCommon;
+using Xunit;
+using Xunit.Extensions;
+using Assert = Microsoft.TestCommon.AssertEx;
+using FactAttribute = Microsoft.TestCommon.DefaultTimeoutFactAttribute;
+using TheoryAttribute = Microsoft.TestCommon.DefaultTimeoutTheoryAttribute;
+
+namespace System.Net.Http
+{
+ public class HttpContentMultipartExtensionsTests
+ {
+ private const string DefaultContentType = "text/plain";
+ private const string DefaultContentDisposition = "form-data";
+ private const string ExceptionStreamProviderMessage = "Bad Stream Provider!";
+ private const string ExceptionSyncStreamMessage = "Bad Sync Stream!";
+ private const string ExceptionAsyncStreamMessage = "Bad Async Stream!";
+ private const string LongText = "0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789";
+
+ [Fact]
+ [Trait("Description", "HttpContentMultipartExtensionMethods is a public static class")]
+ public void TypeIsCorrect()
+ {
+ Assert.Type.HasProperties(
+ typeof(HttpContentMultipartExtensions),
+ TypeAssert.TypeProperties.IsPublicVisibleClass |
+ TypeAssert.TypeProperties.IsStatic);
+ }
+
+ private static HttpContent CreateContent(string boundary, params string[] bodyEntity)
+ {
+ List<string> entities = new List<string>();
+ int cnt = 0;
+ foreach (var body in bodyEntity)
+ {
+ byte[] header = InternetMessageFormatHeaderParserTests.CreateBuffer(
+ String.Format("N{0}: V{0}", cnt),
+ String.Format("Content-Type: {0}", DefaultContentType),
+ String.Format("Content-Disposition: {0}; FileName=\"N{1}\"", DefaultContentDisposition, cnt));
+ entities.Add(Encoding.UTF8.GetString(header) + body);
+ cnt++;
+ }
+
+ byte[] message = MimeMultipartParserTests.CreateBuffer(boundary, entities.ToArray());
+ HttpContent result = new ByteArrayContent(message);
+ var contentType = new MediaTypeHeaderValue("multipart/form-data");
+ contentType.Parameters.Add(new NameValueHeaderValue("boundary", String.Format("\"{0}\"", boundary)));
+ result.Headers.ContentType = contentType;
+ return result;
+ }
+
+ private static void ValidateContents(IEnumerable<HttpContent> contents)
+ {
+ int cnt = 0;
+ foreach (var content in contents)
+ {
+ Assert.NotNull(content);
+ Assert.NotNull(content.Headers);
+ Assert.Equal(4, content.Headers.Count());
+
+ IEnumerable<string> parsedValues = content.Headers.GetValues(String.Format("N{0}", cnt));
+ Assert.Equal(1, parsedValues.Count());
+ Assert.Equal(String.Format("V{0}", cnt), parsedValues.ElementAt(0));
+
+ Assert.Equal(DefaultContentType, content.Headers.ContentType.MediaType);
+
+ Assert.Equal(DefaultContentDisposition, content.Headers.ContentDisposition.DispositionType);
+ Assert.Equal(String.Format("\"N{0}\"", cnt), content.Headers.ContentDisposition.FileName);
+
+ cnt++;
+ }
+ }
+
+ [Fact]
+ public void ReadAsMultipartAsync_DetectsNonMultipartContent()
+ {
+ Assert.ThrowsArgumentNull(() => HttpContentMultipartExtensions.IsMimeMultipartContent(null), "content");
+ Assert.ThrowsArgument(() => new ByteArrayContent(new byte[0]).ReadAsMultipartAsync().Result, "content");
+ Assert.ThrowsArgument(() => new StringContent(String.Empty).ReadAsMultipartAsync().Result, "content");
+ Assert.ThrowsArgument(() => new StringContent(String.Empty, Encoding.UTF8, "multipart/form-data").ReadAsMultipartAsync().Result, "content");
+ }
+
+ public static IEnumerable<object[]> Boundaries
+ {
+ get { return ParserData.Boundaries; }
+ }
+
+ [Fact]
+ public void ReadAsMultipartAsync_NullStreamProviderThrows()
+ {
+ HttpContent content = CreateContent("---");
+
+ Assert.ThrowsArgumentNull(() =>
+ {
+ content.ReadAsMultipartAsync(null);
+ }, "streamProvider");
+ }
+
+ [Theory]
+ [PropertyData("Boundaries")]
+ [Trait("Description", "ReadAsMultipartAsync(HttpContent, IMultipartStreamProvider, int) throws on buffersize.")]
+ public void ReadAsMultipartAsyncStreamProviderThrowsOnBufferSize(string boundary)
+ {
+ HttpContent content = CreateContent(boundary);
+ Assert.NotNull(content);
+
+ Assert.ThrowsArgument(() =>
+ {
+ content.ReadAsMultipartAsync(new MemoryStreamProvider(), ParserData.MinBufferSize - 1);
+ }, "bufferSize");
+ }
+
+ [Fact]
+ [Trait("Description", "IsMimeMultipartContent(HttpContent) checks extension method arguments.")]
+ public void IsMimeMultipartContentVerifyArguments()
+ {
+ Assert.ThrowsArgumentNull(() =>
+ {
+ HttpContent content = null;
+ HttpContentMultipartExtensions.IsMimeMultipartContent(content);
+ }, "content");
+ }
+
+ [Fact]
+ public void IsMumeMultipartContentReturnsFalseForEmptyValues()
+ {
+ Assert.False(new ByteArrayContent(new byte[] { }).IsMimeMultipartContent(), "HttpContent should not be valid MIME multipart content");
+
+ Assert.False(new StringContent(String.Empty).IsMimeMultipartContent(), "HttpContent should not be valid MIME multipart content");
+
+ Assert.False(new StringContent(String.Empty, Encoding.UTF8, "multipart/form-data").IsMimeMultipartContent(), "HttpContent should not be valid MIME multipart content");
+ }
+
+ [Theory]
+ [PropertyData("Boundaries")]
+ [Trait("Description", "IsMimeMultipartContent(HttpContent) responds correctly to MIME multipart and other content")]
+ public void IsMimeMultipartContent(string boundary)
+ {
+ HttpContent content = CreateContent(boundary);
+ Assert.NotNull(content);
+ Assert.True(content.IsMimeMultipartContent());
+ }
+
+ [Theory]
+ [PropertyData("Boundaries")]
+ [Trait("Description", "IsMimeMultipartContent(HttpContent, string) throws on null string.")]
+ public void IsMimeMultipartContentThrowsOnNullString(string boundary)
+ {
+ HttpContent content = CreateContent(boundary);
+ Assert.NotNull(content);
+ foreach (var subtype in CommonUnitTestDataSets.EmptyStrings)
+ {
+ Assert.ThrowsArgumentNull(() =>
+ {
+ content.IsMimeMultipartContent(subtype);
+ }, "subtype");
+ }
+ }
+
+ [Fact]
+ public void ReadAsMultipartAsync_SuccessfullyParsesContent()
+ {
+ HttpContent successContent;
+ Task<IEnumerable<HttpContent>> task;
+ IEnumerable<HttpContent> result;
+
+ successContent = CreateContent("boundary", "A", "B", "C");
+ task = successContent.ReadAsMultipartAsync();
+ task.Wait(TimeoutConstant.DefaultTimeout);
+ result = task.Result;
+ Assert.Equal(3, result.Count());
+
+ successContent = CreateContent("boundary", "A", "B", "C");
+ task = successContent.ReadAsMultipartAsync(new MemoryStreamProvider());
+ task.Wait(TimeoutConstant.DefaultTimeout);
+ result = task.Result;
+ Assert.Equal(3, result.Count());
+
+ successContent = CreateContent("boundary", "A", "B", "C");
+ task = successContent.ReadAsMultipartAsync(new MemoryStreamProvider(), 1024);
+ task.Wait(TimeoutConstant.DefaultTimeout);
+ result = task.Result;
+ Assert.Equal(3, result.Count());
+ }
+
+ [Theory]
+ [PropertyData("Boundaries")]
+ public void ReadAsMultipartAsync_ParsesEmptyContentSuccessfully(string boundary)
+ {
+ HttpContent content = CreateContent(boundary);
+ Task<IEnumerable<HttpContent>> task = content.ReadAsMultipartAsync();
+ task.Wait(TimeoutConstant.DefaultTimeout);
+ IEnumerable<HttpContent> result = task.Result;
+ Assert.Equal(0, result.Count());
+ }
+
+ [Fact]
+ public void ReadAsMultipartAsync_WithBadStreamProvider_Throws()
+ {
+ HttpContent content = CreateContent("--", "A", "B", "C");
+
+ var invalidOperationException = Assert.Throws<InvalidOperationException>(
+ () => content.ReadAsMultipartAsync(new BadStreamProvider()).Result,
+ "The stream provider of type 'BadStreamProvider' threw an exception."
+ );
+ Assert.NotNull(invalidOperationException.InnerException);
+ Assert.Equal(ExceptionStreamProviderMessage, invalidOperationException.InnerException.Message);
+ }
+
+ [Fact]
+ public void ReadAsMultipartAsync_NullStreamProvider_Throws()
+ {
+ HttpContent content = CreateContent("--", "A", "B", "C");
+
+ Assert.Throws<InvalidOperationException>(
+ () => content.ReadAsMultipartAsync(new NullStreamProvider()).Result,
+ "The stream provider of type 'NullStreamProvider' returned null. It must return a writable 'Stream' instance."
+ );
+ }
+
+ [Fact]
+ public void ReadAsMultipartAsync_ReadOnlyStream_Throws()
+ {
+ HttpContent content = CreateContent("--", "A", "B", "C");
+
+ Assert.Throws<InvalidOperationException>(
+ () => content.ReadAsMultipartAsync(new ReadOnlyStreamProvider()).Result,
+ "The stream provider of type 'ReadOnlyStreamProvider' returned a read-only stream. It must return a writable 'Stream' instance."
+ );
+ }
+
+ [Fact]
+ public void ReadAsMultipartAsync_PrematureEndOfStream_Throws()
+ {
+ HttpContent content = new StreamContent(Stream.Null);
+ var contentType = new MediaTypeHeaderValue("multipart/form-data");
+ contentType.Parameters.Add(new NameValueHeaderValue("boundary", "\"{--\""));
+ content.Headers.ContentType = contentType;
+
+ Assert.Throws<IOException>(
+ () => content.ReadAsMultipartAsync().Result,
+ "Unexpected end of MIME multipart stream. MIME multipart message is not complete."
+ );
+ }
+
+ [Fact]
+ public void ReadAsMultipartAsync_ReadErrorOnStream_Throws()
+ {
+ HttpContent content = new StreamContent(new ReadErrorStream());
+ var contentType = new MediaTypeHeaderValue("multipart/form-data");
+ contentType.Parameters.Add(new NameValueHeaderValue("boundary", "\"--\""));
+ content.Headers.ContentType = contentType;
+
+ var ioException = Assert.Throws<IOException>(
+ () => content.ReadAsMultipartAsync().Result,
+ "Error reading MIME multipart body part."
+ );
+ Assert.NotNull(ioException.InnerException);
+ Assert.Equal(ExceptionAsyncStreamMessage, ioException.InnerException.Message);
+ }
+
+ [Fact]
+ public void ReadAsMultipartAsync_WriteErrorOnStream_Throws()
+ {
+ HttpContent content = CreateContent("--", "A", "B", "C");
+
+ var ioException = Assert.Throws<IOException>(
+ () => content.ReadAsMultipartAsync(new WriteErrorStreamProvider()).Result,
+ "Error writing MIME multipart body part to output stream."
+ );
+ Assert.NotNull(ioException.InnerException);
+ Assert.Equal(ExceptionAsyncStreamMessage, ioException.InnerException.Message);
+ }
+
+ [Theory]
+ [PropertyData("Boundaries")]
+ public void ReadAsMultipartAsync_SingleShortBodyPart_ParsesSuccessfully(string boundary)
+ {
+ HttpContent content = CreateContent(boundary, "A");
+ IEnumerable<HttpContent> result = content.ReadAsMultipartAsync().Result;
+ Assert.Equal(1, result.Count());
+ Assert.Equal("A", result.ElementAt(0).ReadAsStringAsync().Result);
+ ValidateContents(result);
+ }
+
+ [Theory]
+ [PropertyData("Boundaries")]
+ public void ReadAsMultipartAsync_MultipleShortBodyParts_ParsesSuccessfully(string boundary)
+ {
+ string[] text = new string[] { "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z" };
+
+ HttpContent content = CreateContent(boundary, text);
+ IEnumerable<HttpContent> result = content.ReadAsMultipartAsync().Result;
+ Assert.Equal(text.Length, result.Count());
+ for (var check = 0; check < text.Length; check++)
+ {
+ Assert.Equal(text[check], result.ElementAt(check).ReadAsStringAsync().Result);
+ }
+
+ ValidateContents(result);
+ }
+
+ [Theory]
+ [PropertyData("Boundaries")]
+ [Trait("Description", "ReadAsMultipartAsync(HttpContent) parses with single long body asynchronously.")]
+ public void ReadAsMultipartAsyncSingleLongBodyPartAsync(string boundary)
+ {
+ HttpContent content = CreateContent(boundary, LongText);
+ Task<IEnumerable<HttpContent>> task = content.ReadAsMultipartAsync();
+ task.Wait(TimeoutConstant.DefaultTimeout);
+ IEnumerable<HttpContent> result = task.Result;
+ Assert.Equal(1, result.Count());
+ Assert.Equal(LongText, result.ElementAt(0).ReadAsStringAsync().Result);
+
+ ValidateContents(result);
+ }
+
+ [Theory]
+ [PropertyData("Boundaries")]
+ [Trait("Description", "ReadAsMultipartAsync(HttpContent) parses with multiple long bodies asynchronously.")]
+ public void ReadAsMultipartAsyncMultipleLongBodyPartsAsync(string boundary)
+ {
+ string[] text = new string[] {
+ "A" + LongText + "A",
+ "B" + LongText + "B",
+ "C" + LongText + "C",
+ "D" + LongText + "D",
+ "E" + LongText + "E",
+ "F" + LongText + "F",
+ "G" + LongText + "G",
+ "H" + LongText + "H",
+ "I" + LongText + "I",
+ "J" + LongText + "J",
+ "K" + LongText + "K",
+ "L" + LongText + "L",
+ "M" + LongText + "M",
+ "N" + LongText + "N",
+ "O" + LongText + "O",
+ "P" + LongText + "P",
+ "Q" + LongText + "Q",
+ "R" + LongText + "R",
+ "S" + LongText + "S",
+ "T" + LongText + "T",
+ "U" + LongText + "U",
+ "V" + LongText + "V",
+ "W" + LongText + "W",
+ "X" + LongText + "X",
+ "Y" + LongText + "Y",
+ "Z" + LongText + "Z"};
+
+ HttpContent content = CreateContent(boundary, text);
+ Task<IEnumerable<HttpContent>> task = content.ReadAsMultipartAsync(new MemoryStreamProvider(), ParserData.MinBufferSize);
+ task.Wait(TimeoutConstant.DefaultTimeout);
+ IEnumerable<HttpContent> result = task.Result;
+ Assert.Equal(text.Length, result.Count());
+ for (var check = 0; check < text.Length; check++)
+ {
+ Assert.Equal(text[check], result.ElementAt(check).ReadAsStringAsync().Result);
+ }
+
+ ValidateContents(result);
+ }
+
+ [Theory]
+ [PropertyData("Boundaries")]
+ [Trait("Description", "ReadAsMultipartAsync(HttpContent) parses content generated by MultipartContent asynchronously.")]
+ public void ReadAsMultipartAsyncUsingMultipartContentAsync(string boundary)
+ {
+ MultipartContent content = new MultipartContent("mixed", boundary);
+ content.Add(new StringContent("A"));
+ content.Add(new StringContent("B"));
+ content.Add(new StringContent("C"));
+
+ MemoryStream memStream = new MemoryStream();
+ content.CopyToAsync(memStream).Wait();
+ memStream.Position = 0;
+ byte[] data = memStream.ToArray();
+ var byteContent = new ByteArrayContent(data);
+ byteContent.Headers.ContentType = content.Headers.ContentType;
+
+ Task<IEnumerable<HttpContent>> task = byteContent.ReadAsMultipartAsync();
+ task.Wait(TimeoutConstant.DefaultTimeout);
+ IEnumerable<HttpContent> result = task.Result;
+ Assert.Equal(3, result.Count());
+ Assert.Equal("A", result.ElementAt(0).ReadAsStringAsync().Result);
+ Assert.Equal("B", result.ElementAt(1).ReadAsStringAsync().Result);
+ Assert.Equal("C", result.ElementAt(2).ReadAsStringAsync().Result);
+ }
+
+ [Theory]
+ [PropertyData("Boundaries")]
+ [Trait("Description", "ReadAsMultipartAsync(HttpContent) parses nested content generated by MultipartContent asynchronously.")]
+ public void ReadAsMultipartAsyncNestedMultipartContentAsync(string boundary)
+ {
+ const int nesting = 10;
+ const string innerText = "Content";
+
+ MultipartContent innerContent = new MultipartContent("mixed", boundary);
+ innerContent.Add(new StringContent(innerText));
+ for (var cnt = 0; cnt < nesting; cnt++)
+ {
+ string outerBoundary = String.Format("{0}_{1}", boundary, cnt);
+ MultipartContent outerContent = new MultipartContent("mixed", outerBoundary);
+ outerContent.Add(innerContent);
+ innerContent = outerContent;
+ }
+
+ MemoryStream memStream = new MemoryStream();
+ innerContent.CopyToAsync(memStream).Wait();
+ memStream.Position = 0;
+ byte[] data = memStream.ToArray();
+ HttpContent content = new ByteArrayContent(data);
+ content.Headers.ContentType = innerContent.Headers.ContentType;
+
+ for (var cnt = 0; cnt < nesting + 1; cnt++)
+ {
+ Task<IEnumerable<HttpContent>> task = content.ReadAsMultipartAsync();
+ task.Wait(TimeoutConstant.DefaultTimeout);
+ IEnumerable<HttpContent> result = task.Result;
+ Assert.Equal(1, result.Count());
+ content = result.ElementAt(0);
+ Assert.NotNull(content);
+ }
+
+ string text = content.ReadAsStringAsync().Result;
+ Assert.Equal(innerText, text);
+ }
+
+ public class ReadOnlyStream : MemoryStream
+ {
+ public override bool CanWrite
+ {
+ get
+ {
+ return false;
+ }
+ }
+ }
+
+ public class ReadErrorStream : MemoryStream
+ {
+ public override int Read(byte[] buffer, int offset, int count)
+ {
+ throw new Exception(ExceptionSyncStreamMessage);
+ }
+
+ public override IAsyncResult BeginRead(byte[] buffer, int offset, int count, AsyncCallback callback, object state)
+ {
+ throw new Exception(ExceptionAsyncStreamMessage);
+ }
+ }
+
+ public class WriteErrorStream : MemoryStream
+ {
+ public override void Write(byte[] buffer, int offset, int count)
+ {
+ throw new Exception(ExceptionSyncStreamMessage);
+ }
+
+ public override IAsyncResult BeginWrite(byte[] buffer, int offset, int count, AsyncCallback callback, object state)
+ {
+ throw new Exception(ExceptionAsyncStreamMessage);
+ }
+ }
+
+ public class MemoryStreamProvider : IMultipartStreamProvider
+ {
+ public Stream GetStream(HttpContentHeaders headers)
+ {
+ return new MemoryStream();
+ }
+ }
+
+ public class BadStreamProvider : IMultipartStreamProvider
+ {
+ public Stream GetStream(HttpContentHeaders headers)
+ {
+ throw new Exception(ExceptionStreamProviderMessage);
+ }
+ }
+
+ public class NullStreamProvider : IMultipartStreamProvider
+ {
+ public Stream GetStream(HttpContentHeaders headers)
+ {
+ return null;
+ }
+ }
+
+ public class ReadOnlyStreamProvider : IMultipartStreamProvider
+ {
+ public Stream GetStream(HttpContentHeaders headers)
+ {
+ return new ReadOnlyStream();
+ }
+ }
+
+ public class WriteErrorStreamProvider : IMultipartStreamProvider
+ {
+ public Stream GetStream(HttpContentHeaders headers)
+ {
+ return new WriteErrorStream();
+ }
+ }
+ }
+}
diff --git a/test/System.Net.Http.Formatting.Test.Unit/HttpMessageContentTests.cs b/test/System.Net.Http.Formatting.Test.Unit/HttpMessageContentTests.cs
new file mode 100644
index 00000000..92838147
--- /dev/null
+++ b/test/System.Net.Http.Formatting.Test.Unit/HttpMessageContentTests.cs
@@ -0,0 +1,309 @@
+using System.Net.Http.Headers;
+using System.Threading.Tasks;
+using Microsoft.TestCommon;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Net.Http
+{
+ public class HttpMessageContentTests
+ {
+ private static readonly int iterations = 5;
+
+ private static void AddMessageHeaders(HttpHeaders headers)
+ {
+ headers.Add("N1", new string[] { "V1a", "V1b", "V1c", "V1d", "V1e" });
+ headers.Add("N2", "V2");
+ }
+
+ private static HttpRequestMessage CreateRequest(Uri requestUri, bool containsEntity)
+ {
+ HttpRequestMessage httpRequest = new HttpRequestMessage();
+ httpRequest.Method = new HttpMethod(ParserData.HttpMethod);
+ httpRequest.RequestUri = requestUri;
+ httpRequest.Version = new Version("1.2");
+ AddMessageHeaders(httpRequest.Headers);
+ if (containsEntity)
+ {
+ httpRequest.Content = new StringContent(ParserData.HttpMessageEntity);
+ }
+
+ return httpRequest;
+ }
+
+ private static HttpResponseMessage CreateResponse(bool containsEntity)
+ {
+ HttpResponseMessage httpResponse = new HttpResponseMessage();
+ httpResponse.StatusCode = ParserData.HttpStatus;
+ httpResponse.ReasonPhrase = ParserData.HttpReasonPhrase;
+ httpResponse.Version = new Version("1.2");
+ AddMessageHeaders(httpResponse.Headers);
+ if (containsEntity)
+ {
+ httpResponse.Content = new StringContent(ParserData.HttpMessageEntity);
+ }
+
+ return httpResponse;
+ }
+
+ private static string ReadContentAsync(HttpContent content)
+ {
+ Task task = content.LoadIntoBufferAsync();
+ task.Wait(TimeoutConstant.DefaultTimeout);
+ Assert.Equal(TaskStatus.RanToCompletion, task.Status);
+ return content.ReadAsStringAsync().Result;
+ }
+
+ private static void ValidateRequest(HttpContent content, bool containsEntity)
+ {
+ Assert.Equal(ParserData.HttpRequestMediaType, content.Headers.ContentType);
+ long? length = content.Headers.ContentLength;
+ Assert.NotNull(length);
+
+ string message = ReadContentAsync(content);
+
+ if (containsEntity)
+ {
+ Assert.Equal(ParserData.HttpRequestWithEntity.Length, length);
+ Assert.Equal(ParserData.HttpRequestWithEntity, message);
+ }
+ else
+ {
+ Assert.Equal(ParserData.HttpRequest.Length, length);
+ Assert.Equal(ParserData.HttpRequest, message);
+ }
+ }
+
+ private static void ValidateResponse(HttpContent content, bool containsEntity)
+ {
+ Assert.Equal(ParserData.HttpResponseMediaType, content.Headers.ContentType);
+ long? length = content.Headers.ContentLength;
+ Assert.NotNull(length);
+
+ string message = ReadContentAsync(content);
+
+ if (containsEntity)
+ {
+ Assert.Equal(ParserData.HttpResponseWithEntity.Length, length);
+ Assert.Equal(ParserData.HttpResponseWithEntity, message);
+ }
+ else
+ {
+ Assert.Equal(ParserData.HttpResponse.Length, length);
+ Assert.Equal(ParserData.HttpResponse, message);
+ }
+ }
+
+ [Fact]
+ public void TypeIsCorrect()
+ {
+ Assert.Type.HasProperties<HttpMessageContent, HttpContent>(TypeAssert.TypeProperties.IsPublicVisibleClass | TypeAssert.TypeProperties.IsDisposable);
+ }
+
+ [Fact]
+ public void RequestConstructor()
+ {
+ HttpRequestMessage request = new HttpRequestMessage();
+ HttpMessageContent instance = new HttpMessageContent(request);
+ Assert.NotNull(instance);
+ Assert.Same(request, instance.HttpRequestMessage);
+ Assert.Null(instance.HttpResponseMessage);
+ }
+
+ [Fact]
+ public void RequestConstructorThrowsOnNull()
+ {
+ Assert.ThrowsArgumentNull(() => { new HttpMessageContent((HttpRequestMessage)null); }, "httpRequest");
+ }
+
+ [Fact]
+ public void ResponseConstructor()
+ {
+ HttpResponseMessage response = new HttpResponseMessage();
+ HttpMessageContent instance = new HttpMessageContent(response);
+ Assert.NotNull(instance);
+ Assert.Same(response, instance.HttpResponseMessage);
+ Assert.Null(instance.HttpRequestMessage);
+ }
+
+ [Fact]
+ public void ResponseConstructorThrowsOnNull()
+ {
+ Assert.ThrowsArgumentNull(() => { new HttpMessageContent((HttpResponseMessage)null); }, "httpResponse");
+ }
+
+
+ [Fact]
+ public void SerializeRequest()
+ {
+ for (int cnt = 0; cnt < iterations; cnt++)
+ {
+ HttpRequestMessage request = CreateRequest(ParserData.HttpRequestUri, false);
+ HttpMessageContent instance = new HttpMessageContent(request);
+ ValidateRequest(instance, false);
+ }
+ }
+
+ [Fact]
+ public void SerializeRequestWithExistingHostHeader()
+ {
+ HttpRequestMessage request = CreateRequest(ParserData.HttpRequestUri, false);
+ string host = ParserData.HttpHostName;
+ request.Headers.Host = host;
+ HttpMessageContent instance = new HttpMessageContent(request);
+ string message = ReadContentAsync(instance);
+ Assert.Equal(ParserData.HttpRequestWithHost, message);
+ }
+
+ [Fact]
+ public void SerializeRequestMultipleTimes()
+ {
+ HttpRequestMessage request = CreateRequest(ParserData.HttpRequestUri, false);
+ HttpMessageContent instance = new HttpMessageContent(request);
+ for (int cnt = 0; cnt < iterations; cnt++)
+ {
+ ValidateRequest(instance, false);
+ }
+ }
+
+ [Fact]
+ public void SerializeResponse()
+ {
+ for (int cnt = 0; cnt < iterations; cnt++)
+ {
+ HttpResponseMessage response = CreateResponse(false);
+ HttpMessageContent instance = new HttpMessageContent(response);
+ ValidateResponse(instance, false);
+ }
+ }
+
+ [Fact]
+ public void SerializeResponseMultipleTimes()
+ {
+ HttpResponseMessage response = CreateResponse(false);
+ HttpMessageContent instance = new HttpMessageContent(response);
+ for (int cnt = 0; cnt < iterations; cnt++)
+ {
+ ValidateResponse(instance, false);
+ }
+ }
+
+ [Fact]
+ public void SerializeRequestWithEntity()
+ {
+ for (int cnt = 0; cnt < iterations; cnt++)
+ {
+ HttpRequestMessage request = CreateRequest(ParserData.HttpRequestUri, true);
+ HttpMessageContent instance = new HttpMessageContent(request);
+ ValidateRequest(instance, true);
+ }
+ }
+
+ [Fact]
+ public void SerializeRequestWithEntityMultipleTimes()
+ {
+ HttpRequestMessage request = CreateRequest(ParserData.HttpRequestUri, true);
+ HttpMessageContent instance = new HttpMessageContent(request);
+ for (int cnt = 0; cnt < iterations; cnt++)
+ {
+ ValidateRequest(instance, true);
+ }
+ }
+
+ [Fact]
+ public void SerializeResponseWithEntity()
+ {
+ for (int cnt = 0; cnt < iterations; cnt++)
+ {
+ HttpResponseMessage response = CreateResponse(true);
+ HttpMessageContent instance = new HttpMessageContent(response);
+ ValidateResponse(instance, true);
+ }
+ }
+
+ [Fact]
+ public void SerializeResponseWithEntityMultipleTimes()
+ {
+ HttpResponseMessage response = CreateResponse(true);
+ HttpMessageContent instance = new HttpMessageContent(response);
+ for (int cnt = 0; cnt < iterations; cnt++)
+ {
+ ValidateResponse(instance, true);
+ }
+ }
+
+ [Fact]
+ public void SerializeRequestAsync()
+ {
+ for (int cnt = 0; cnt < iterations; cnt++)
+ {
+ HttpRequestMessage request = CreateRequest(ParserData.HttpRequestUri, false);
+ HttpMessageContent instance = new HttpMessageContent(request);
+ ValidateRequest(instance, false);
+ }
+ }
+
+ [Fact]
+ public void SerializeResponseAsync()
+ {
+ for (int cnt = 0; cnt < iterations; cnt++)
+ {
+ HttpResponseMessage response = CreateResponse(false);
+ HttpMessageContent instance = new HttpMessageContent(response);
+ ValidateResponse(instance, false);
+ }
+ }
+
+ [Fact]
+ public void SerializeRequestWithPortAndQueryAsync()
+ {
+ for (int cnt = 0; cnt < iterations; cnt++)
+ {
+ HttpRequestMessage request = CreateRequest(ParserData.HttpRequestUriWithPortAndQuery, false);
+ HttpMessageContent instance = new HttpMessageContent(request);
+ string message = ReadContentAsync(instance);
+ Assert.Equal(ParserData.HttpRequestWithPortAndQuery, message);
+ }
+ }
+
+ [Fact]
+ public void SerializeRequestWithEntityAsync()
+ {
+ for (int cnt = 0; cnt < iterations; cnt++)
+ {
+ HttpRequestMessage request = CreateRequest(ParserData.HttpRequestUri, true);
+ HttpMessageContent instance = new HttpMessageContent(request);
+ ValidateRequest(instance, true);
+ }
+ }
+
+ [Fact]
+ public void SerializeResponseWithEntityAsync()
+ {
+ for (int cnt = 0; cnt < iterations; cnt++)
+ {
+ HttpResponseMessage response = CreateResponse(true);
+ HttpMessageContent instance = new HttpMessageContent(response);
+ ValidateResponse(instance, true);
+ }
+ }
+
+ [Fact]
+ public void DisposeInnerHttpRequestMessage()
+ {
+ HttpRequestMessage request = CreateRequest(ParserData.HttpRequestUri, false);
+ HttpMessageContent instance = new HttpMessageContent(request);
+ instance.Dispose();
+ Assert.ThrowsObjectDisposed(() => { request.Method = HttpMethod.Get; }, typeof(HttpRequestMessage).FullName);
+ }
+
+ [Fact]
+ public void DisposeInnerHttpResponseMessage()
+ {
+ HttpResponseMessage response = CreateResponse(false);
+ HttpMessageContent instance = new HttpMessageContent(response);
+ instance.Dispose();
+ Assert.ThrowsObjectDisposed(() => { response.StatusCode = HttpStatusCode.OK; }, typeof(HttpResponseMessage).FullName);
+ }
+ }
+}
diff --git a/test/System.Net.Http.Formatting.Test.Unit/Mocks/MockHttpContent.cs b/test/System.Net.Http.Formatting.Test.Unit/Mocks/MockHttpContent.cs
new file mode 100644
index 00000000..58e6665a
--- /dev/null
+++ b/test/System.Net.Http.Formatting.Test.Unit/Mocks/MockHttpContent.cs
@@ -0,0 +1,90 @@
+using System.IO;
+using System.Net.Http.Headers;
+using System.Threading.Tasks;
+
+namespace System.Net.Http.Formatting.Mocks
+{
+ public delegate bool TryComputeLengthDelegate(out long length);
+
+ public class MockHttpContent : HttpContent
+ {
+ public MockHttpContent()
+ {
+ }
+
+ public MockHttpContent(HttpContent innerContent)
+ {
+ InnerContent = innerContent;
+ Headers.ContentType = innerContent.Headers.ContentType;
+ }
+
+ public MockHttpContent(MediaTypeHeaderValue contentType)
+ {
+ if (contentType == null)
+ {
+ throw new ArgumentNullException("contentType");
+ }
+ Headers.ContentType = contentType;
+ }
+
+ public MockHttpContent(string contentType)
+ {
+ if (String.IsNullOrWhiteSpace(contentType))
+ {
+ throw new ArgumentNullException("contentType");
+ }
+ Headers.ContentType = new MediaTypeHeaderValue(contentType);
+ }
+
+ public HttpContent InnerContent { get; set; }
+
+ public Action<bool> DisposeCallback { get; set; }
+ public TryComputeLengthDelegate TryComputeLengthCallback { get; set; }
+ public Action<Stream, TransportContext> SerializeToStreamCallback { get; set; }
+ public Func<Stream, TransportContext, Task> SerializeToStreamAsyncCallback { get; set; }
+
+ protected override void Dispose(bool disposing)
+ {
+ if (DisposeCallback != null)
+ {
+ DisposeCallback(disposing);
+ }
+
+ base.Dispose(disposing);
+ }
+
+ protected override Task SerializeToStreamAsync(Stream stream, TransportContext context)
+ {
+ if (SerializeToStreamAsyncCallback != null)
+ {
+ return SerializeToStreamAsyncCallback(stream, context);
+ }
+ else if (InnerContent != null)
+ {
+ return InnerContent.CopyToAsync(stream, context);
+ }
+ else
+ {
+ throw new InvalidOperationException("Construct with inner HttpContent or set SerializeToStreamCallback first.");
+ }
+ }
+
+ protected override bool TryComputeLength(out long length)
+ {
+ if (TryComputeLengthCallback != null)
+ {
+ return TryComputeLengthCallback(out length);
+ }
+
+ if (InnerContent != null)
+ {
+ long? len = InnerContent.Headers.ContentLength;
+ length = len.HasValue ? len.Value : 0L;
+ return len.HasValue;
+ }
+
+ length = 0L;
+ return false;
+ }
+ }
+}
diff --git a/test/System.Net.Http.Formatting.Test.Unit/Mocks/MockMediaTypeFormatter.cs b/test/System.Net.Http.Formatting.Test.Unit/Mocks/MockMediaTypeFormatter.cs
new file mode 100644
index 00000000..b2381ce8
--- /dev/null
+++ b/test/System.Net.Http.Formatting.Test.Unit/Mocks/MockMediaTypeFormatter.cs
@@ -0,0 +1,30 @@
+
+namespace System.Net.Http.Formatting.Mocks
+{
+ public class MockMediaTypeFormatter : MediaTypeFormatter
+ {
+ public bool CallBase { get; set; }
+ public Func<Type, bool> CanReadTypeCallback { get; set; }
+ public Func<Type, bool> CanWriteTypeCallback { get; set; }
+
+ public override bool CanReadType(Type type)
+ {
+ if (!CallBase && CanReadTypeCallback == null)
+ {
+ throw new InvalidOperationException("CallBase or CanReadTypeCallback must be set first.");
+ }
+
+ return CanReadTypeCallback != null ? CanReadTypeCallback(type) : true;
+ }
+
+ public override bool CanWriteType(Type type)
+ {
+ if (!CallBase && CanWriteTypeCallback == null)
+ {
+ throw new InvalidOperationException("CallBase or CanWriteTypeCallback must be set first.");
+ }
+
+ return CanWriteTypeCallback != null ? CanWriteTypeCallback(type) : true;
+ }
+ }
+}
diff --git a/test/System.Net.Http.Formatting.Test.Unit/Mocks/TestableHttpMessageHandler.cs b/test/System.Net.Http.Formatting.Test.Unit/Mocks/TestableHttpMessageHandler.cs
new file mode 100644
index 00000000..e4c53bdc
--- /dev/null
+++ b/test/System.Net.Http.Formatting.Test.Unit/Mocks/TestableHttpMessageHandler.cs
@@ -0,0 +1,18 @@
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace System.Net.Http.Mocks
+{
+ public class TestableHttpMessageHandler : HttpMessageHandler
+ {
+ public virtual Task<HttpResponseMessage> SendAsyncPublic(HttpRequestMessage request, CancellationToken cancellationToken)
+ {
+ throw new NotImplementedException();
+ }
+
+ protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
+ {
+ return SendAsyncPublic(request, cancellationToken);
+ }
+ }
+}
diff --git a/test/System.Net.Http.Formatting.Test.Unit/MultipartFileStreamProviderTests.cs b/test/System.Net.Http.Formatting.Test.Unit/MultipartFileStreamProviderTests.cs
new file mode 100644
index 00000000..c8bb0fdb
--- /dev/null
+++ b/test/System.Net.Http.Formatting.Test.Unit/MultipartFileStreamProviderTests.cs
@@ -0,0 +1,117 @@
+using System.IO;
+using System.Linq;
+using Microsoft.TestCommon;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Net.Http
+{
+ public class MultipartFileStreamProviderTests
+ {
+ const int defaultBufferSize = 0x1000;
+ const string validPath = @"c:\some\path";
+
+ [Fact]
+ [Trait("Description", "MultipartFileStreamProvider is public, visible type.")]
+ public void TypeIsCorrect()
+ {
+ Assert.Type.HasProperties(
+ typeof(MultipartFileStreamProvider),
+ TypeAssert.TypeProperties.IsPublicVisibleClass,
+ typeof(IMultipartStreamProvider));
+ }
+
+
+ [Fact]
+ [Trait("Description", "MultipartFileStreamProvider default ctor.")]
+ public void DefaultConstructor()
+ {
+ MultipartFileStreamProvider instance = new MultipartFileStreamProvider();
+ Assert.NotNull(instance);
+ }
+
+ [Fact]
+ [Trait("Description", "MultipartFileStreamProvider ctor with invalid root paths.")]
+ public void ConstructorInvalidRootPath()
+ {
+ Assert.ThrowsArgumentNull(() => { new MultipartFileStreamProvider(null); }, "rootPath");
+
+ foreach (string path in TestData.NotSupportedFilePaths)
+ {
+ Assert.Throws<NotSupportedException>(() => new MultipartFileStreamProvider(path, defaultBufferSize));
+ }
+
+ foreach (string path in TestData.InvalidNonNullFilePaths)
+ {
+ // Note: Path.GetFileName doesn't set the argument name when throwing.
+ Assert.ThrowsArgument(() => { new MultipartFileStreamProvider(path, defaultBufferSize); }, null, allowDerivedExceptions: true);
+ }
+ }
+
+ [Fact]
+ [Trait("Description", "MultipartFileStreamProvider ctor with null path.")]
+ public void ConstructorInvalidBufferSize()
+ {
+ Assert.ThrowsArgumentOutOfRange(() => { new MultipartFileStreamProvider(validPath, -1); }, "bufferSize", exceptionMessage: null);
+ Assert.ThrowsArgumentOutOfRange(() => { new MultipartFileStreamProvider(validPath, 0); }, "bufferSize", exceptionMessage: null);
+ }
+
+
+
+ [Fact]
+ [Trait("Description", "BodyPartFileNames empty.")]
+ public void EmptyBodyPartFileNames()
+ {
+ MultipartFileStreamProvider instance = new MultipartFileStreamProvider();
+ Assert.NotNull(instance.BodyPartFileNames);
+ Assert.Equal(0, instance.BodyPartFileNames.Count());
+ }
+
+ [Fact]
+ [Trait("Description", "GetStream(HttpContentHeaders) throws on null.")]
+ public void GetStreamThrowsOnNull()
+ {
+ MultipartFileStreamProvider instance = new MultipartFileStreamProvider();
+ Assert.ThrowsArgumentNull(() => { instance.GetStream(null); }, "headers");
+ }
+
+ [Fact]
+ [Trait("Description", "GetStream(HttpContentHeaders) validation.")]
+ public void GetStreamValidation()
+ {
+ Stream stream0 = null;
+ Stream stream1 = null;
+
+ try
+ {
+ MultipartFormDataContent content = new MultipartFormDataContent();
+ content.Add(new StringContent("Not a file"), "notafile");
+ content.Add(new StringContent("This is a file"), "file", "filename");
+
+ MultipartFileStreamProvider instance = new MultipartFileStreamProvider();
+ stream0 = instance.GetStream(content.ElementAt(0).Headers);
+ Assert.IsType<FileStream>(stream0);
+ stream1 = instance.GetStream(content.ElementAt(1).Headers);
+ Assert.IsType<FileStream>(stream1);
+
+ Assert.Equal(2, instance.BodyPartFileNames.Count());
+ Assert.Contains("BodyPart", instance.BodyPartFileNames.ElementAt(0));
+ Assert.True(instance.BodyPartFileNames.ElementAt(1).EndsWith("filename"));
+ }
+ finally
+ {
+ if (stream0 != null)
+ {
+ stream0.Close();
+ }
+
+ if (stream1 != null)
+ {
+ stream1.Close();
+ }
+ }
+ }
+
+
+ }
+}
diff --git a/test/System.Net.Http.Formatting.Test.Unit/MultipartFormDataStreamProviderTests.cs b/test/System.Net.Http.Formatting.Test.Unit/MultipartFormDataStreamProviderTests.cs
new file mode 100644
index 00000000..2df66233
--- /dev/null
+++ b/test/System.Net.Http.Formatting.Test.Unit/MultipartFormDataStreamProviderTests.cs
@@ -0,0 +1,120 @@
+using System.IO;
+using System.Linq;
+using Microsoft.TestCommon;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Net.Http
+{
+ public class MultipartFormDataStreamProviderTests
+ {
+ const int defaultBufferSize = 0x1000;
+ const string validPath = @"c:\some\path";
+
+ [Fact]
+ [Trait("Description", "MultipartFormDataStreamProvider is public, visible type.")]
+ public void TypeIsCorrect()
+ {
+ Assert.Type.HasProperties(
+ typeof(MultipartFormDataStreamProvider),
+ TypeAssert.TypeProperties.IsPublicVisibleClass,
+ typeof(IMultipartStreamProvider));
+ }
+
+ [Fact]
+ [Trait("Description", "MultipartFormDataStreamProvider default ctor.")]
+ public void DefaultConstructor()
+ {
+ MultipartFormDataStreamProvider instance = new MultipartFormDataStreamProvider();
+ Assert.NotNull(instance);
+ }
+
+ [Fact]
+ [Trait("Description", "MultipartFormDataStreamProvider ctor with invalid root paths.")]
+ public void ConstructorInvalidRootPath()
+ {
+ Assert.ThrowsArgumentNull(() => { new MultipartFormDataStreamProvider(null); }, "rootPath");
+
+ foreach (string path in TestData.NotSupportedFilePaths)
+ {
+ Assert.Throws<NotSupportedException>(() => new MultipartFormDataStreamProvider(path, defaultBufferSize));
+ }
+
+ foreach (string path in TestData.InvalidNonNullFilePaths)
+ {
+ // Note: Path.GetFileName doesn't set the argument name when throwing.
+ Assert.ThrowsArgument(() => { new MultipartFormDataStreamProvider(path, defaultBufferSize); }, null, allowDerivedExceptions: true);
+ }
+ }
+
+ [Fact]
+ [Trait("Description", "MultipartFormDataStreamProvider ctor with null path.")]
+ public void ConstructorInvalidBufferSize()
+ {
+ Assert.ThrowsArgumentOutOfRange(() => { new MultipartFormDataStreamProvider(validPath, -1); }, "bufferSize", exceptionMessage: null);
+ Assert.ThrowsArgumentOutOfRange(() => { new MultipartFormDataStreamProvider(validPath, 0); }, "bufferSize", exceptionMessage: null);
+ }
+
+ [Fact]
+ [Trait("Description", "BodyPartFileNames empty.")]
+ public void EmptyBodyPartFileNames()
+ {
+ MultipartFormDataStreamProvider instance = new MultipartFormDataStreamProvider();
+ Assert.NotNull(instance.BodyPartFileNames);
+ Assert.Equal(0, instance.BodyPartFileNames.Count);
+ }
+
+ [Fact]
+ [Trait("Description", "GetStream(HttpContentHeaders) throws on null.")]
+ public void GetStreamThrowsOnNull()
+ {
+ MultipartFormDataStreamProvider instance = new MultipartFormDataStreamProvider();
+ Assert.ThrowsArgumentNull(() => { instance.GetStream(null); }, "headers");
+ }
+
+ [Fact]
+ [Trait("Description", "GetStream(HttpContentHeaders) throws on no Content-Disposition header.")]
+ public void GetStreamThrowsOnNoContentDisposition()
+ {
+ MultipartFormDataStreamProvider instance = new MultipartFormDataStreamProvider();
+ HttpContent content = new StringContent("text");
+ Assert.Throws<IOException>(() => { instance.GetStream(content.Headers); }, RS.Format(Properties.Resources.MultipartFormDataStreamProviderNoContentDisposition, "Content-Disposition"));
+ }
+
+ [Fact]
+ [Trait("Description", "GetStream(HttpContentHeaders) validation.")]
+ public void GetStreamValidation()
+ {
+ Stream stream0 = null;
+ Stream stream1 = null;
+
+ try
+ {
+ MultipartFormDataContent content = new MultipartFormDataContent();
+ content.Add(new StringContent("Not a file"), "notafile");
+ content.Add(new StringContent("This is a file"), "file", "filename");
+
+ MultipartFormDataStreamProvider instance = new MultipartFormDataStreamProvider();
+ stream0 = instance.GetStream(content.ElementAt(0).Headers);
+ Assert.IsType<MemoryStream>(stream0);
+ stream1 = instance.GetStream(content.ElementAt(1).Headers);
+ Assert.IsType<FileStream>(stream1);
+
+ Assert.Equal(1, instance.BodyPartFileNames.Count);
+ Assert.Equal(content.ElementAt(1).Headers.ContentDisposition.FileName, instance.BodyPartFileNames.Keys.ElementAt(0));
+ }
+ finally
+ {
+ if (stream0 != null)
+ {
+ stream0.Close();
+ }
+
+ if (stream1 != null)
+ {
+ stream1.Close();
+ }
+ }
+ }
+ }
+}
diff --git a/test/System.Net.Http.Formatting.Test.Unit/MultipartMemoryStreamProviderTests.cs b/test/System.Net.Http.Formatting.Test.Unit/MultipartMemoryStreamProviderTests.cs
new file mode 100644
index 00000000..afb0adb3
--- /dev/null
+++ b/test/System.Net.Http.Formatting.Test.Unit/MultipartMemoryStreamProviderTests.cs
@@ -0,0 +1,55 @@
+using System.IO;
+using Microsoft.TestCommon;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Net.Http
+{
+ public class MultipartMemoryStreamProviderTests
+ {
+ [Fact]
+ [Trait("Description", "MultipartMemoryStreamProvider is internal type.")]
+ public void TypeIsCorrect()
+ {
+ Assert.Type.HasProperties(
+ typeof(MultipartMemoryStreamProvider),
+ TypeAssert.TypeProperties.IsClass,
+ typeof(IMultipartStreamProvider));
+ }
+
+ [Fact]
+ [Trait("Description", "MultipartMemoryStreamProvider default ctor.")]
+ public void DefaultConstructor()
+ {
+ MultipartMemoryStreamProvider instance = MultipartMemoryStreamProvider.Instance;
+ Assert.NotNull(instance);
+ }
+
+ [Fact]
+ [Trait("Description", "GetStream(HttpContentHeaders) throws on null.")]
+ public void GetStreamThrowsOnNull()
+ {
+ MultipartMemoryStreamProvider instance = MultipartMemoryStreamProvider.Instance;
+ Assert.ThrowsArgumentNull(() => { instance.GetStream(null); }, "headers");
+ }
+
+ [Fact]
+ [Trait("Description", "GetStream(HttpContentHeaders) throws on no Content-Disposition header.")]
+ public void GetStreamReturnsMemoryStream()
+ {
+ MultipartMemoryStreamProvider instance = MultipartMemoryStreamProvider.Instance;
+ HttpContent content = new StringContent("text");
+
+ Stream stream = instance.GetStream(content.Headers);
+ Assert.NotNull(stream);
+
+ MemoryStream memStream = stream as MemoryStream;
+ Assert.NotNull(stream);
+
+ Assert.Equal(0, stream.Length);
+ Assert.Equal(0, stream.Position);
+
+ Assert.NotSame(memStream, instance.GetStream(content.Headers));
+ }
+ }
+}
diff --git a/test/System.Net.Http.Formatting.Test.Unit/ObjectContentOfTTests.cs b/test/System.Net.Http.Formatting.Test.Unit/ObjectContentOfTTests.cs
new file mode 100644
index 00000000..9503cb57
--- /dev/null
+++ b/test/System.Net.Http.Formatting.Test.Unit/ObjectContentOfTTests.cs
@@ -0,0 +1,38 @@
+using System.Net.Http.Formatting;
+using Moq;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Net.Http
+{
+ public class ObjectContentOfTTests
+ {
+ [Fact]
+ public void Constructor_WhenFormatterParameterIsNull_Throws()
+ {
+ Assert.ThrowsArgumentNull(() => new ObjectContent<string>("", formatter: null), "formatter");
+ Assert.ThrowsArgumentNull(() => new ObjectContent<string>("", formatter: null, mediaType: "foo/bar"), "formatter");
+ }
+
+ [Fact]
+ public void Constructor_SetsFormatterProperty()
+ {
+ var formatter = new Mock<MediaTypeFormatter>().Object;
+
+ var content = new ObjectContent<string>(null, formatter, mediaType: null);
+
+ Assert.Same(formatter, content.Formatter);
+ }
+
+ [Fact]
+ public void Constructor_CallsFormattersGetDefaultContentHeadersMethod()
+ {
+ var formatterMock = new Mock<MediaTypeFormatter>();
+
+ var content = new ObjectContent(typeof(string), "", formatterMock.Object, "foo/bar");
+
+ formatterMock.Verify(f => f.SetDefaultContentHeaders(typeof(string), content.Headers, "foo/bar"),
+ Times.Once());
+ }
+ }
+}
diff --git a/test/System.Net.Http.Formatting.Test.Unit/ObjectContentTests.cs b/test/System.Net.Http.Formatting.Test.Unit/ObjectContentTests.cs
new file mode 100644
index 00000000..e54549b7
--- /dev/null
+++ b/test/System.Net.Http.Formatting.Test.Unit/ObjectContentTests.cs
@@ -0,0 +1,168 @@
+using System.Collections.Generic;
+using System.IO;
+using System.Net.Http.Formatting;
+using System.Net.Http.Headers;
+using System.Threading.Tasks;
+using Microsoft.TestCommon;
+using Moq;
+using Xunit;
+using Xunit.Extensions;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Net.Http
+{
+ public class ObjectContentTests
+ {
+ private readonly object _value = new object();
+ private readonly MediaTypeFormatter _formatter = new TestableMediaTypeFormatter();
+
+ [Fact]
+ public void Constructor_WhenTypeArgumentIsNull_ThrowsEsxception()
+ {
+ Assert.ThrowsArgumentNull(() => new ObjectContent(null, _value, _formatter), "type");
+ Assert.ThrowsArgumentNull(() => new ObjectContent(null, _value, _formatter, mediaType: "foo/bar"), "type");
+ }
+
+ [Fact]
+ public void Constructor_WhenFormatterArgumentIsNull_ThrowsEsxception()
+ {
+ Assert.ThrowsArgumentNull(() => new ObjectContent(typeof(Object), _value, formatter: null), "formatter");
+ Assert.ThrowsArgumentNull(() => new ObjectContent(typeof(Object), _value, formatter: null, mediaType: "foo/bar"), "formatter");
+ }
+
+ [Fact]
+ public void Constructor_WhenValueIsNullAndTypeIsNotCompatible_ThrowsException()
+ {
+ Assert.Throws<InvalidOperationException>(() =>
+ {
+ new ObjectContent(typeof(int), null, new JsonMediaTypeFormatter());
+ }, "The 'ObjectContent' type cannot accept a null value for the value type 'Int32'.");
+ }
+
+ [Fact]
+ public void Constructor_WhenValueIsNotNullButTypeDoesNotMatch_ThrowsException()
+ {
+ Assert.Throws<ArgumentException>(() =>
+ {
+ new ObjectContent(typeof(IList<string>), new Dictionary<string, string>(), new JsonMediaTypeFormatter());
+ }, "An object of type 'Dictionary`2' cannot be used with a type parameter of 'IList`1'.\r\nParameter name: value");
+ }
+
+ [Fact]
+ public void Constructor_SetsFormatterProperty()
+ {
+ var content = new ObjectContent(typeof(object), _value, _formatter, mediaType: null);
+
+ Assert.Same(_formatter, content.Formatter);
+ }
+
+ [Fact]
+ public void Constructor_CallsFormattersGetDefaultContentHeadersMethod()
+ {
+ var formatterMock = new Mock<MediaTypeFormatter>();
+
+ var content = new ObjectContent(typeof(string), "", formatterMock.Object, "foo/bar");
+
+ formatterMock.Verify(f => f.SetDefaultContentHeaders(typeof(string), content.Headers, "foo/bar"),
+ Times.Once());
+ }
+
+ [Theory]
+ [PropertyData("ValidValueTypePairs")]
+ public void Constructor_WhenValueAndTypeAreCompatible_SetsValue(Type type, object value)
+ {
+ var oc = new ObjectContent(type, value, new JsonMediaTypeFormatter());
+
+ Assert.Same(value, oc.Value);
+ Assert.Equal(type, oc.ObjectType);
+ }
+
+ public static TheoryDataSet<Type, object> ValidValueTypePairs
+ {
+ get
+ {
+ return new TheoryDataSet<Type, object>
+ {
+ { typeof(Nullable<int>), null },
+ { typeof(void), null },
+ { typeof(string), null },
+ { typeof(int), 42 },
+ //{ typeof(int), (short)42 }, TODO should this work?
+ { typeof(object), "abc" },
+ { typeof(string), "abc" },
+ { typeof(IList<string>), new List<string>() },
+ };
+ }
+ }
+
+ [Fact]
+ public void SerializeToStreamAsync_CallsUnderlyingFormatter()
+ {
+ var stream = Stream.Null;
+ var context = new Mock<TransportContext>().Object;
+ var formatterMock = new Mock<TestableMediaTypeFormatter> { CallBase = true };
+ var oc = new TestableObjectContent(typeof(string), "abc", formatterMock.Object);
+ var task = new Task(() => { });
+ formatterMock.Setup(f => f.WriteToStreamAsync(typeof(string), "abc", stream, oc.Headers, context))
+ .Returns(task).Verifiable();
+
+ var result = oc.CallSerializeToStreamAsync(stream, context);
+
+ Assert.Same(task, result);
+ formatterMock.VerifyAll();
+ }
+
+ [Fact]
+ public void TryComputeLength_ReturnsFalseAnd0()
+ {
+ var oc = new TestableObjectContent(typeof(string), null, _formatter);
+ long length;
+
+ var result = oc.CallTryComputeLength(out length);
+
+ Assert.False(result);
+ Assert.Equal(-1, length);
+ }
+
+ public class TestableObjectContent : ObjectContent
+ {
+ public TestableObjectContent(Type type, object value, MediaTypeFormatter formatter)
+ : base(type, value, formatter)
+ {
+ }
+
+ public bool CallTryComputeLength(out long length)
+ {
+ return TryComputeLength(out length);
+ }
+
+ public Task CallSerializeToStreamAsync(Stream stream, TransportContext context)
+ {
+ return SerializeToStreamAsync(stream, context);
+ }
+ }
+
+ public class TestableMediaTypeFormatter : MediaTypeFormatter
+ {
+ public TestableMediaTypeFormatter()
+ {
+ SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/json"));
+ }
+
+ public override bool CanReadType(Type type)
+ {
+ return true;
+ }
+
+ public override bool CanWriteType(Type type)
+ {
+ return true;
+ }
+
+ public override Task WriteToStreamAsync(Type type, object value, Stream stream, HttpContentHeaders contentHeaders, TransportContext transportContext)
+ {
+ throw new NotImplementedException();
+ }
+ }
+ }
+}
diff --git a/test/System.Net.Http.Formatting.Test.Unit/ParserData.cs b/test/System.Net.Http.Formatting.Test.Unit/ParserData.cs
new file mode 100644
index 00000000..00ce31d5
--- /dev/null
+++ b/test/System.Net.Http.Formatting.Test.Unit/ParserData.cs
@@ -0,0 +1,192 @@
+using System.Collections.Generic;
+using System.Net.Http.Headers;
+
+namespace System.Net.Http
+{
+ internal static class ParserData
+ {
+ public const int MinHeaderSize = 2;
+
+ public const int MinMessageSize = 10;
+
+ public const int MinRequestLineSize = 14;
+
+ public const int MinStatusLineSize = 15;
+
+ public const int MinBufferSize = 256;
+
+ public static IEnumerable<object[]> Boundaries
+ {
+ get
+ {
+ yield return new object[] { "1" };
+ yield return new object[] { "a" };
+ yield return new object[] { "'" };
+ yield return new object[] { "(" };
+ yield return new object[] { ")" };
+ yield return new object[] { "+" };
+ yield return new object[] { "_" };
+ yield return new object[] { "-" };
+ yield return new object[] { "." };
+ yield return new object[] { "/" };
+ yield return new object[] { ":" };
+ yield return new object[] { "=" };
+ yield return new object[] { "?" };
+ yield return new object[] { "--" };
+ yield return new object[] { "--------------------01234567890123456789" };
+ yield return new object[] { "--------------------01234567890123456789--------------------" };
+ yield return new object[] { "--A--B--C--D--E--F--" };
+ }
+ }
+
+ public static IEnumerable<object[]> Versions
+ {
+ get
+ {
+ yield return new object[] { Version.Parse("1.0") };
+ yield return new object[] { Version.Parse("1.1") };
+ yield return new object[] { Version.Parse("1.2") };
+ yield return new object[] { Version.Parse("2.0") };
+ yield return new object[] { Version.Parse("10.0") };
+ yield return new object[] { Version.Parse("1.15") };
+ }
+ }
+
+ public static IEnumerable<object[]> InvalidVersions
+ {
+ get
+ {
+ yield return new object[] { "" };
+ yield return new object[] { "http/1.1" };
+ yield return new object[] { "HTTP/a.1" };
+ yield return new object[] { "HTTP/1.a" };
+ yield return new object[] { "HTTP 1.1" };
+ yield return new object[] { "HTTP\t1.1" };
+ yield return new object[] { "HTTP 1 1" };
+ yield return new object[] { "\0" };
+ yield return new object[] { "HTTP\01.1" };
+ yield return new object[] { "HTTP/4294967295.4294967295" };
+ }
+ }
+
+ public static readonly string[] InvalidMethods = new string[]
+ {
+ "",
+ "G\tT",
+ "G E T",
+ "\0",
+ "G\0T",
+ "GET\n",
+ };
+
+ public static readonly string[] InvalidReasonPhrases = new string[]
+ {
+ "\0",
+ "\t",
+ "reason\n",
+ };
+
+ // This deliberately only checks for syntac boundaries of the URI, not its content
+ public static readonly string[] InvalidRequestUris = new string[]
+ {
+ "",
+ "p a t h",
+ "path ",
+ " path ",
+ };
+
+ public static readonly string[] InvalidStatusCodes = new string[]
+ {
+ "0",
+ "99",
+ "1a1",
+ "abc",
+ "1001",
+ "2000",
+ Int32.MinValue.ToString(),
+ Int32.MaxValue.ToString(),
+ };
+
+ public static readonly Dictionary<string, string> ValidHeaders = new Dictionary<string, string>
+ {
+ { "N0", "V0"},
+ { "N1", "V1"},
+ { "N2", "V2"},
+ { "N3", "V3"},
+ { "N4", "V4"},
+ { "N5", "V5"},
+ { "N6", "V6"},
+ { "N7", "V7"},
+ { "N8", "V8"},
+ { "N9", "V9"},
+ };
+
+ public static readonly string HttpMethod = "TEG";
+ public static readonly HttpStatusCode HttpStatus = HttpStatusCode.Created;
+ public static readonly string HttpReasonPhrase = "ReasonPhrase";
+ public static readonly string HttpHostName = "example.com";
+ public static readonly int HttpHostPort = 1234;
+ public static readonly string HttpMessageEntity = "1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890";
+
+ public static readonly Uri HttpRequestUri = new Uri("http://" + HttpHostName + "/some/path");
+ public static readonly Uri HttpRequestUriWithPortAndQuery = new Uri("http://" + HttpHostName + ":" + HttpHostPort + "/some/path?%C3%A6%C3%B8%C3%A5");
+ public static readonly Uri HttpsRequestUri = new Uri("https://" + HttpHostName + "/some/path");
+
+ public static readonly string HttpRequest =
+ HttpMethod +
+ " /some/path HTTP/1.2\r\nHost: " +
+ HttpHostName +
+ "\r\nN1: V1a, V1b, V1c, V1d, V1e\r\nN2: V2\r\n\r\n";
+
+ public static readonly string HttpRequestWithHost =
+ HttpMethod +
+ " /some/path HTTP/1.2\r\n" +
+ "N1: V1a, V1b, V1c, V1d, V1e\r\nN2: V2\r\nHost: " +
+ HttpHostName + "\r\n\r\n";
+
+ public static readonly string HttpRequestWithPortAndQuery =
+ HttpMethod +
+ " /some/path?%C3%A6%C3%B8%C3%A5 HTTP/1.2\r\nHost: " +
+ HttpHostName + ":" + HttpHostPort.ToString() +
+ "\r\nN1: V1a, V1b, V1c, V1d, V1e\r\nN2: V2\r\n\r\n";
+
+ public static readonly string HttpResponse =
+ "HTTP/1.2 " +
+ ((int)HttpStatus).ToString() +
+ " " +
+ HttpReasonPhrase +
+ "\r\nN1: V1a, V1b, V1c, V1d, V1e\r\nN2: V2\r\n\r\n";
+
+ public static readonly string TextContentType = "text/plain; charset=utf-8";
+
+ public static readonly string HttpRequestWithEntity =
+ HttpMethod +
+ " /some/path HTTP/1.2\r\nHost: " +
+ HttpHostName +
+ "\r\nN1: V1a, V1b, V1c, V1d, V1e\r\nN2: V2\r\nContent-Type: " +
+ TextContentType +
+ "\r\n\r\n" +
+ HttpMessageEntity;
+
+ public static readonly string HttpResponseWithEntity =
+ "HTTP/1.2 " +
+ ((int)HttpStatus).ToString() +
+ " " +
+ HttpReasonPhrase +
+ "\r\nN1: V1a, V1b, V1c, V1d, V1e\r\nN2: V2\r\nContent-Type: " +
+ TextContentType +
+ "\r\n\r\n" +
+ HttpMessageEntity;
+
+ public static readonly MediaTypeHeaderValue HttpRequestMediaType;
+
+ public static readonly MediaTypeHeaderValue HttpResponseMediaType;
+
+ static ParserData()
+ {
+ MediaTypeHeaderValue.TryParse("application/http; msgtype=request", out HttpRequestMediaType);
+ MediaTypeHeaderValue.TryParse("application/http; msgtype=response", out HttpResponseMediaType);
+ }
+
+ }
+} \ No newline at end of file
diff --git a/test/System.Net.Http.Formatting.Test.Unit/Properties/AssemblyInfo.cs b/test/System.Net.Http.Formatting.Test.Unit/Properties/AssemblyInfo.cs
new file mode 100644
index 00000000..eba7e052
--- /dev/null
+++ b/test/System.Net.Http.Formatting.Test.Unit/Properties/AssemblyInfo.cs
@@ -0,0 +1,21 @@
+using System.Diagnostics.CodeAnalysis;
+using System.Reflection;
+using System.Resources;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+[assembly: AssemblyTitle("System.Net.Http.Formatting.Test.Unit")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("Microsoft")]
+[assembly: AssemblyProduct("System.Net.Http.Formatting.Test.Unit")]
+[assembly: AssemblyCopyright("Copyright © Microsoft 2011")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+[assembly: ComVisible(false)]
+[assembly: Guid("14258965-9ef5-4504-8655-51f948c96ffb")]
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
+[assembly: NeutralResourcesLanguage("en-US")]
+[assembly: InternalsVisibleTo("System.Net.Http.Formatting.Test.Integration, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b5fc90e7027f67871e773a8fde8938c81dd402ba65b9201d60593e96c492651e889cc13f1415ebb53fac1131ae0bd333c5ee6021672d9718ea31a8aebd0da0072f25d87dba6fc90ffd598ed4da35e44c398c454307e8e33b8426143daec9f596836f97c8f74750e5975c64e2189f45def46b2a2b1247adc3652bf5c308055da9")]
+[assembly: SuppressMessage("Microsoft.Design", "CA2210:AssembliesShouldHaveValidStrongNames", Justification = "These assemblies are delay-signed.")]
diff --git a/test/System.Net.Http.Formatting.Test.Unit/System.Net.Http.Formatting.Test.Unit.csproj b/test/System.Net.Http.Formatting.Test.Unit/System.Net.Http.Formatting.Test.Unit.csproj
new file mode 100644
index 00000000..ac6f0ca5
--- /dev/null
+++ b/test/System.Net.Http.Formatting.Test.Unit/System.Net.Http.Formatting.Test.Unit.csproj
@@ -0,0 +1,156 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory),Runtime.sln))\tools\WebStack.settings.targets" />
+ <PropertyGroup>
+ <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+ <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+ <ProductVersion>9.0.30729</ProductVersion>
+ <SchemaVersion>2.0</SchemaVersion>
+ <ProjectGuid>{7AF77741-9158-4D5F-8782-8F21FADF025F}</ProjectGuid>
+ <OutputType>Library</OutputType>
+ <AppDesignerFolder>Properties</AppDesignerFolder>
+ <RootNamespace>System.Net.Http</RootNamespace>
+ <AssemblyName>System.Net.Http.Formatting.Test</AssemblyName>
+ <FileAlignment>512</FileAlignment>
+ <ProjectTypeGuids>{3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
+ <ErrorReport>prompt</ErrorReport>
+ <WarningLevel>4</WarningLevel>
+ <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
+ <DebugSymbols>true</DebugSymbols>
+ <DebugType>full</DebugType>
+ <Optimize>false</Optimize>
+ <OutputPath>$(WebStackRootPath)\bin\Debug\Test\</OutputPath>
+ <DefineConstants>TRACE;DEBUG</DefineConstants>
+ <CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
+ <DebugType>pdbonly</DebugType>
+ <Optimize>true</Optimize>
+ <OutputPath>$(WebStackRootPath)\bin\Release\Test\</OutputPath>
+ <DefineConstants>TRACE</DefineConstants>
+ <CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)' == 'CodeCoverage' ">
+ <DebugSymbols>true</DebugSymbols>
+ <DebugType>full</DebugType>
+ <Optimize>false</Optimize>
+ <OutputPath>$(WebStackRootPath)\bin\CodeCoverage\Test\</OutputPath>
+ <DefineConstants>TRACE;DEBUG</DefineConstants>
+ <CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
+ </PropertyGroup>
+ <ItemGroup>
+ <Reference Include="Moq, Version=4.0.10827.0, Culture=neutral, PublicKeyToken=69f491c39445e920, processorArchitecture=MSIL">
+ <HintPath>..\..\packages\Moq.4.0.10827\lib\NET40\Moq.dll</HintPath>
+ </Reference>
+ <Reference Include="Newtonsoft.Json, Version=4.0.8.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
+ <HintPath>..\..\packages\Newtonsoft.Json.4.0.8\lib\net40\Newtonsoft.Json.dll</HintPath>
+ </Reference>
+ <Reference Include="System" />
+ <Reference Include="System.Data" />
+ <Reference Include="System.Net.Http">
+ <HintPath>..\..\packages\Microsoft.Net.Http.2.0.20302.1\lib\net40\System.Net.Http.dll</HintPath>
+ </Reference>
+ <Reference Include="System.Net.Http.WebRequest">
+ <HintPath>..\..\packages\Microsoft.Net.Http.2.0.20302.1\lib\net40\System.Net.Http.WebRequest.dll</HintPath>
+ </Reference>
+ <Reference Include="System.Runtime.Serialization" />
+ <Reference Include="System.XML" />
+ <Reference Include="xunit">
+ <HintPath>..\..\packages\xunit.1.9.0.1566\lib\xunit.dll</HintPath>
+ </Reference>
+ <Reference Include="xunit.extensions">
+ <HintPath>..\..\packages\xunit.extensions.1.9.0.1566\lib\xunit.extensions.dll</HintPath>
+ </Reference>
+ </ItemGroup>
+ <ItemGroup>
+ <CodeAnalysisDependentAssemblyPaths Condition=" '$(VS100COMNTOOLS)' != '' " Include="$(VS100COMNTOOLS)..\IDE\PrivateAssemblies">
+ <Visible>False</Visible>
+ </CodeAnalysisDependentAssemblyPaths>
+ </ItemGroup>
+ <ItemGroup>
+ <Compile Include="DataSets\Types\DataContractEnum.cs" />
+ <Compile Include="DataSets\Types\DataContractType.cs" />
+ <Compile Include="DataSets\Types\DerivedDataContractType.cs" />
+ <Compile Include="DataSets\Types\DerivedFormUrlEncodedMediaTypeFormatter.cs" />
+ <Compile Include="DataSets\Types\DerivedJsonMediaTypeFormatter.cs" />
+ <Compile Include="DataSets\Types\DerivedWcfPocoType.cs" />
+ <Compile Include="DataSets\Types\DerivedXmlMediaTypeFormatter.cs" />
+ <Compile Include="DataSets\Types\DerivedXmlSerializableType.cs" />
+ <Compile Include="DataSets\Types\HttpTestData.cs" />
+ <Compile Include="DataSets\Types\INotJsonSerializable.cs" />
+ <Compile Include="DataSets\Types\WcfPocoType.cs" />
+ <Compile Include="DataSets\HttpUnitTestDataSets.cs" />
+ <Compile Include="DataSets\Types\XmlSerializableType.cs" />
+ <Compile Include="Formatting\FormDataCollectionTests.cs" />
+ <Compile Include="HttpClientExtensionsTest.cs" />
+ <Compile Include="HttpContentExtensionsTest.cs" />
+ <Compile Include="Mocks\TestableHttpMessageHandler.cs" />
+ <Compile Include="Properties\AssemblyInfo.cs" />
+ <Compile Include="ContentDispositionHeaderValueExtensionsTests.cs" />
+ <Compile Include="Formatting\ThresholdStreamTest.cs" />
+ <Compile Include="UriQueryDataSet.cs" />
+ <Compile Include="UriQueryUtilityTests.cs" />
+ <Compile Include="FormattingUtilitiesTests.cs" />
+ <Compile Include="Formatting\BufferedMediaTypeFormatterTests.cs" />
+ <Compile Include="Formatting\DefaultContentNegotiatorTests.cs" />
+ <Compile Include="Formatting\FormUrlEncodedMediaTypeFormatterTests.cs" />
+ <Compile Include="Formatting\JsonKeyValueModelTest.cs" />
+ <Compile Include="Formatting\JsonMediaTypeFormatterTests.cs" />
+ <Compile Include="Formatting\MediaRangeMappingTests.cs" />
+ <Compile Include="Formatting\MediaTypeConstantsTests.cs" />
+ <Compile Include="Formatting\MediaTypeFormatterCollectionTests.cs" />
+ <Compile Include="Formatting\MediaTypeFormatterExtensionsTests.cs" />
+ <Compile Include="Formatting\MediaTypeFormatterTests.cs" />
+ <Compile Include="Formatting\MediaTypeHeaderValueEqualityComparerTests.cs" />
+ <Compile Include="Formatting\MediaTypeHeadeValueComparerTests.cs" />
+ <Compile Include="Formatting\MediaTypeHeadeValueExtensionsTests.cs" />
+ <Compile Include="Formatting\ParsedMediaTypeHeaderValueTests.cs" />
+ <Compile Include="Formatting\QueryStringMappingTests.cs" />
+ <Compile Include="Formatting\RequestHeaderMappingTests.cs" />
+ <Compile Include="Formatting\UriPathExtensionMappingTests.cs" />
+ <Compile Include="Formatting\XmlMediaTypeFormatterTests.cs" />
+ <Compile Include="Formatting\Parsers\FormUrlEncodedParserTests.cs" />
+ <Compile Include="HttpContentCollectionExtensionsTests.cs" />
+ <Compile Include="HttpContentMessageExtensionsTests.cs" />
+ <Compile Include="HttpContentMultipartExtensionsTests.cs" />
+ <Compile Include="HttpMessageContentTests.cs" />
+ <Compile Include="Formatting\Parsers\HttpRequestHeaderParserTests.cs" />
+ <Compile Include="Formatting\Parsers\HttpRequestLineParserTests.cs" />
+ <Compile Include="Formatting\Parsers\HttpResponseHeaderParserTests.cs" />
+ <Compile Include="Formatting\Parsers\HttpStatusLineParserTests.cs" />
+ <Compile Include="Formatting\Parsers\InternetMessageFormatHeaderParserTests.cs" />
+ <Compile Include="Formatting\Parsers\MimeMultipartParserTests.cs" />
+ <Compile Include="MultipartFileStreamProviderTests.cs" />
+ <Compile Include="MultipartFormDataStreamProviderTests.cs" />
+ <Compile Include="MultipartMemoryStreamProviderTests.cs" />
+ <Compile Include="ObjectContentOfTTests.cs" />
+ <Compile Include="ObjectContentTests.cs" />
+ <Compile Include="ParserData.cs" />
+ <Compile Include="UriExtensionsTests.cs" />
+ <Compile Include="Mocks\MockHttpContent.cs" />
+ <Compile Include="Mocks\MockMediaTypeFormatter.cs" />
+ </ItemGroup>
+ <ItemGroup>
+ <ProjectReference Include="..\..\src\System.Json\System.Json.csproj">
+ <Project>{F0441BE9-BDC0-4629-BE5A-8765FFAA2481}</Project>
+ <Name>System.Json</Name>
+ </ProjectReference>
+ <ProjectReference Include="..\..\src\System.Net.Http.Formatting\System.Net.Http.Formatting.csproj">
+ <Project>{668E9021-CE84-49D9-98FB-DF125A9FCDB0}</Project>
+ <Name>System.Net.Http.Formatting</Name>
+ </ProjectReference>
+ <ProjectReference Include="..\Microsoft.TestCommon\Microsoft.TestCommon.csproj">
+ <Project>{FCCC4CB7-BAF7-4A57-9F89-E5766FE536C0}</Project>
+ <Name>Microsoft.TestCommon</Name>
+ </ProjectReference>
+ </ItemGroup>
+ <ItemGroup>
+ <None Include="packages.config">
+ <SubType>Designer</SubType>
+ </None>
+ </ItemGroup>
+ <ItemGroup />
+ <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
+</Project> \ No newline at end of file
diff --git a/test/System.Net.Http.Formatting.Test.Unit/UriExtensionsTests.cs b/test/System.Net.Http.Formatting.Test.Unit/UriExtensionsTests.cs
new file mode 100644
index 00000000..1d96873f
--- /dev/null
+++ b/test/System.Net.Http.Formatting.Test.Unit/UriExtensionsTests.cs
@@ -0,0 +1,173 @@
+using System.Collections.Specialized;
+using System.Json;
+using System.Net.Http.Formatting.DataSets;
+using Microsoft.TestCommon;
+using Xunit;
+using Xunit.Extensions;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Net.Http
+{
+ public class UriExtensionsTests
+ {
+ private static readonly Uri TestAddress = new Uri("http://www.example.com");
+ private static readonly Type TestType = typeof(string);
+
+ [Fact]
+ [Trait("Description", "UriExtensionMethods is public and static.")]
+ public void TypeIsCorrect()
+ {
+ Assert.Type.HasProperties(typeof(UriExtensions), TypeAssert.TypeProperties.IsPublicVisibleClass | TypeAssert.TypeProperties.IsStatic);
+ }
+
+ [Fact]
+ [Trait("Description", "ParseQueryString(Uri) throws with null 'this'.")]
+ public void ParseQueryStringThrowsWithNull()
+ {
+ Assert.ThrowsArgumentNull(() => ((Uri)null).ParseQueryString(), "address");
+ }
+
+ [Theory]
+ [TestDataSet(typeof(HttpUnitTestDataSets), "Uris")]
+ [Trait("Description", "ParseQueryString(Uri) succeeds with valid URIs.")]
+ public void ParseQueryStringSucceeds(Uri address)
+ {
+ NameValueCollection result = address.ParseQueryString();
+ Assert.NotNull(result);
+
+ bool addressContainsQuery = address.Query.Contains("?");
+ if (!addressContainsQuery)
+ {
+ Assert.Empty(result);
+ }
+ else
+ {
+ Assert.True(result.Count > 0, "Uri with query string should return non-empty set.");
+ }
+ }
+
+ [Fact]
+ [Trait("Description", "TryReadQueryAsJson(Uri, out JsonObject) throws with null 'this'.")]
+ public void TryReadQueryAsJsonThrowsWithNull()
+ {
+ JsonObject value;
+ Assert.ThrowsArgumentNull(() => ((Uri)null).TryReadQueryAsJson(out value), "address");
+ }
+
+ [Theory]
+ [TestDataSet(typeof(HttpUnitTestDataSets), "Uris")]
+ [Trait("Description", "TryReadQueryAsJson(Uri, out JsonObject) succeeds with valid URIs.")]
+ public void TryReadQueryAsJsonSucceeds(Uri address)
+ {
+ JsonObject value;
+ Assert.True(address.TryReadQueryAsJson(out value), "Expected 'true' as result");
+ Assert.NotNull(value);
+ Assert.IsType<JsonObject>(value);
+ }
+
+ [Fact]
+ [Trait("Description", "TryReadQueryAs(Uri, Type, out object) throws with null 'this'.")]
+ public void TryReadQueryAsThrowsWithNull()
+ {
+ object value;
+ Assert.ThrowsArgumentNull(() => ((Uri)null).TryReadQueryAs(TestType, out value), "address");
+ Assert.ThrowsArgumentNull(() => TestAddress.TryReadQueryAs(null, out value), "type");
+ }
+
+ [Fact]
+ [Trait("Description", "TryReadQueryAs(Uri, Type, out object) succeeds with valid URIs.")]
+ public void TryReadQueryAsSucceeds()
+ {
+ object value;
+ UriBuilder address = new UriBuilder("http://some.host");
+
+ address.Query = "a=2";
+ Assert.True(address.Uri.TryReadQueryAs(typeof(SimpleObject1), out value), "Expected 'true' reading valid data");
+ SimpleObject1 so1 = (SimpleObject1)value;
+ Assert.NotNull(so1);
+ Assert.Equal(2, so1.a);
+
+ address.Query = "b=true";
+ Assert.True(address.Uri.TryReadQueryAs(typeof(SimpleObject2), out value), "Expected 'true' reading valid data");
+ SimpleObject2 so2 = (SimpleObject2)value;
+ Assert.NotNull(so2);
+ Assert.True(so2.b, "Value should have been true");
+
+ address.Query = "c=hello";
+ Assert.True(address.Uri.TryReadQueryAs(typeof(SimpleObject3), out value), "Expected 'true' reading valid data");
+ SimpleObject3 so3 = (SimpleObject3)value;
+ Assert.NotNull(so3);
+ Assert.Equal("hello", so3.c);
+
+ address.Query = "c=";
+ Assert.True(address.Uri.TryReadQueryAs(typeof(SimpleObject3), out value), "Expected 'true' reading valid data");
+ so3 = (SimpleObject3)value;
+ Assert.NotNull(so3);
+ Assert.Equal("", so3.c);
+
+ address.Query = "c=null";
+ Assert.True(address.Uri.TryReadQueryAs(typeof(SimpleObject3), out value), "Expected 'true' reading valid data");
+ so3 = (SimpleObject3)value;
+ Assert.NotNull(so3);
+ Assert.Null(so3.c);
+ }
+
+ [Fact]
+ [Trait("Description", "TryReadQueryAs<T>(Uri, out T) throws with null 'this'.")]
+ public void TryReadQueryAsTThrowsWithNull()
+ {
+ object value;
+ Assert.ThrowsArgumentNull(() => ((Uri)null).TryReadQueryAs<object>(out value), "address");
+ }
+
+ [Fact]
+ [Trait("Description", "TryReadQueryAs<T>(Uri, out T) succeeds with valid URIs.")]
+ public void TryReadQueryAsTSucceeds()
+ {
+ UriBuilder address = new UriBuilder("http://some.host");
+ address.Query = "a=2";
+ SimpleObject1 so1;
+ Assert.True(address.Uri.TryReadQueryAs<SimpleObject1>(out so1), "Expected 'true' reading valid data");
+ Assert.NotNull(so1);
+ Assert.Equal(2, so1.a);
+
+ address.Query = "b=true";
+ SimpleObject2 so2;
+ Assert.True(address.Uri.TryReadQueryAs<SimpleObject2>(out so2), "Expected 'true' reading valid data");
+ Assert.NotNull(so2);
+ Assert.True(so2.b, "Value should have been true");
+
+ address.Query = "c=hello";
+ SimpleObject3 so3;
+ Assert.True(address.Uri.TryReadQueryAs<SimpleObject3>(out so3), "Expected 'true' reading valid data");
+ Assert.NotNull(so3);
+ Assert.Equal("hello", so3.c);
+
+ address.Query = "c=";
+ Assert.True(address.Uri.TryReadQueryAs<SimpleObject3>(out so3), "Expected 'true' reading valid data");
+ Assert.NotNull(so3);
+ Assert.Equal("", so3.c);
+
+ address.Query = "c=null";
+ Assert.True(address.Uri.TryReadQueryAs<SimpleObject3>(out so3), "Expected 'true' reading valid data");
+ Assert.NotNull(so3);
+ Assert.Null(so3.c);
+ }
+
+
+ public class SimpleObject1
+ {
+ public int a { get; set; }
+ }
+
+ public class SimpleObject2
+ {
+ public bool b { get; set; }
+ }
+
+ public class SimpleObject3
+ {
+ public string c { get; set; }
+ }
+ }
+}
diff --git a/test/System.Net.Http.Formatting.Test.Unit/UriQueryDataSet.cs b/test/System.Net.Http.Formatting.Test.Unit/UriQueryDataSet.cs
new file mode 100644
index 00000000..dc2b5519
--- /dev/null
+++ b/test/System.Net.Http.Formatting.Test.Unit/UriQueryDataSet.cs
@@ -0,0 +1,31 @@
+using Microsoft.TestCommon;
+
+namespace System.Net.Http
+{
+ public class UriQueryTestData
+ {
+ public static TheoryDataSet<string, string, string> UriQueryData
+ {
+ get
+ {
+ return new TheoryDataSet<string, string, string>
+ {
+ { "=", "", "" },
+ { "N=", "N", "" },
+ { "=N", "", "N" },
+ { "N=V", "N", "V" },
+ { "%26=%26", "&", "&" },
+ { "%3D=%3D", "=", "=" },
+ { "N=A%2BC", "N", "A+C" },
+ { "N=100%25AA%21", "N", "100%AA!"},
+ { "N=%7E%21%40%23%24%25%5E%26%2A%28%29_%2B","N","~!@#$%^&*()_+"},
+ { "N=%601234567890-%3D", "N", "`1234567890-="},
+ { "N=%60%31%32%33%34%35%36%37%38%39%30%2D%3D","N", "`1234567890-="},
+ { "N=%E6%BF%80%E5%85%89%E9%80%99%E5%85%A9%E5%80%8B%E5%AD%97%E6%98%AF%E7%94%9A%E9%BA%BC%E6%84%8F%E6%80%9D", "N", "激光這兩個字是甚麼意思" },
+ { "N=%C3%A6%C3%B8%C3%A5","N","æøå"},
+ { "N=%C3%A6+%C3%B8+%C3%A5","N","æ ø å"},
+ };
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/test/System.Net.Http.Formatting.Test.Unit/UriQueryUtilityTests.cs b/test/System.Net.Http.Formatting.Test.Unit/UriQueryUtilityTests.cs
new file mode 100644
index 00000000..263839e3
--- /dev/null
+++ b/test/System.Net.Http.Formatting.Test.Unit/UriQueryUtilityTests.cs
@@ -0,0 +1,160 @@
+using System.Collections.Generic;
+using System.Collections.Specialized;
+using System.Net.Http.Internal;
+using System.Text;
+using Microsoft.TestCommon;
+using Xunit;
+using Xunit.Extensions;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Net.Http
+{
+ public class UriQueryUtilityTests
+ {
+ [Fact]
+ public void TypeIsCorrect()
+ {
+ Assert.Type.HasProperties(typeof(UriQueryUtility), TypeAssert.TypeProperties.IsClass | TypeAssert.TypeProperties.IsStatic);
+ }
+
+ #region ParseQueryString
+ [Fact]
+ public void ParseQueryStringThrowsOnNullQuery()
+ {
+ Assert.ThrowsArgumentNull(() => UriQueryUtility.ParseQueryString(null), "query");
+ }
+
+ #endregion
+
+ #region UrlEncode
+
+ [Fact]
+ public void UrlEncodeReturnsNull()
+ {
+ Assert.Null(UriQueryUtility.UrlEncode(null));
+ }
+
+ public void UrlEncodeToBytesThrowsOnInvalidArgs()
+ {
+ Assert.Null(UriQueryUtility.UrlEncodeToBytes(null, 0, 0));
+ Assert.ThrowsArgumentNull(() => UriQueryUtility.UrlEncodeToBytes(null, 0, 2), "bytes");
+
+ Assert.ThrowsArgumentOutOfRange(() => UriQueryUtility.UrlEncodeToBytes(new byte[0], -1, 0), "offset", null);
+ Assert.ThrowsArgumentOutOfRange(() => UriQueryUtility.UrlEncodeToBytes(new byte[0], 2, 0), "offset", null);
+
+ Assert.ThrowsArgumentOutOfRange(() => UriQueryUtility.UrlEncodeToBytes(new byte[0], 0, -1), "count", null);
+ Assert.ThrowsArgumentOutOfRange(() => UriQueryUtility.UrlEncodeToBytes(new byte[0], 0, 2), "count", null);
+ }
+
+ #endregion
+
+ #region UrlDecode
+
+ [Fact]
+ public void UrlDecodeReturnsNull()
+ {
+ Assert.Null(UriQueryUtility.UrlDecode(null));
+ }
+
+ [Fact]
+ public void UrlDecodeParsesEmptySegmentsCorrectly()
+ {
+ int iterations = 16;
+ List<string> segments = new List<string>();
+
+ for (int index = 1; index < iterations; index++)
+ {
+ segments.Add("&");
+ string query = string.Join("", segments);
+ NameValueCollection result = UriQueryUtility.ParseQueryString(query);
+ Assert.NotNull(result);
+
+ // Because this is a NameValueCollection, the same name appears only once
+ Assert.Equal(1, result.Count);
+
+ // Values should be a comma separated list of empty strings
+ string[] values = result[""].Split(new char[] { ',' });
+
+ // We expect length+1 segment as the final '&' counts as a segment
+ Assert.Equal(index + 1, values.Length);
+ foreach (var value in values)
+ {
+ Assert.Equal("", value);
+ }
+ }
+ }
+
+ public static TheoryDataSet<string, string, string> UriQueryData
+ {
+ get
+ {
+ return UriQueryTestData.UriQueryData;
+ }
+ }
+
+ private static string CreateQuery(params string[] segments)
+ {
+ StringBuilder buffer = new StringBuilder();
+ bool first = true;
+ foreach (string segment in segments)
+ {
+ if (first)
+ {
+ first = false;
+ }
+ else
+ {
+ buffer.Append('&');
+ }
+
+ buffer.Append(segment);
+ }
+
+ return buffer.ToString();
+ }
+
+ [Theory]
+ [InlineData("N", "", "N")]
+ [InlineData("%26", "", "&")]
+ [PropertyData("UriQueryData")]
+ public void UrlDecodeParsesCorrectly(string segment, string resultName, string resultValue)
+ {
+ int iterations = 16;
+ List<string> segments = new List<string>();
+
+ for (int index = 1; index < iterations; index++)
+ {
+ segments.Add(segment);
+ string query = CreateQuery(segments.ToArray());
+ NameValueCollection result = UriQueryUtility.ParseQueryString(query);
+ Assert.NotNull(result);
+
+ // Because this is a NameValueCollection, the same name appears only once
+ Assert.Equal(1, result.Count);
+
+ // Values should be a comma separated list of resultValue
+ string[] values = result[resultName].Split(new char[] { ',' });
+ Assert.Equal(index, values.Length);
+ foreach (var value in values)
+ {
+ Assert.Equal(resultValue, value);
+ }
+ }
+ }
+
+ public void UrlDecodeToBytesThrowsOnInvalidArgs()
+ {
+ Assert.Null(UriQueryUtility.UrlDecodeToBytes(null, 0, 0));
+ Assert.ThrowsArgumentNull(() => UriQueryUtility.UrlDecodeToBytes(null, 0, 2), "bytes");
+
+ Assert.ThrowsArgumentOutOfRange(() => UriQueryUtility.UrlDecodeToBytes(new byte[0], -1, 0), "offset", null);
+ Assert.ThrowsArgumentOutOfRange(() => UriQueryUtility.UrlDecodeToBytes(new byte[0], 2, 0), "offset", null);
+
+ Assert.ThrowsArgumentOutOfRange(() => UriQueryUtility.UrlDecodeToBytes(new byte[0], 0, -1), "count", null);
+ Assert.ThrowsArgumentOutOfRange(() => UriQueryUtility.UrlDecodeToBytes(new byte[0], 0, 2), "count", null);
+ }
+
+ #endregion
+
+ }
+} \ No newline at end of file
diff --git a/test/System.Net.Http.Formatting.Test.Unit/packages.config b/test/System.Net.Http.Formatting.Test.Unit/packages.config
new file mode 100644
index 00000000..01bb32d0
--- /dev/null
+++ b/test/System.Net.Http.Formatting.Test.Unit/packages.config
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8"?>
+<packages>
+ <package id="Microsoft.Net.Http" version="2.0.20302.1" />
+ <package id="Moq" version="4.0.10827" />
+ <package id="Newtonsoft.Json" version="4.0.8" />
+ <package id="xunit" version="1.9.0.1566" />
+ <package id="xunit.extensions" version="1.9.0.1566" />
+</packages> \ No newline at end of file
diff --git a/test/System.Web.Helpers.Test/ChartTest.cs b/test/System.Web.Helpers.Test/ChartTest.cs
new file mode 100644
index 00000000..7d782fed
--- /dev/null
+++ b/test/System.Web.Helpers.Test/ChartTest.cs
@@ -0,0 +1,653 @@
+using System.Collections;
+using System.Drawing;
+using System.IO;
+using System.Web.Hosting;
+using System.Web.UI.DataVisualization.Charting;
+using Moq;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Helpers.Test
+{
+ public class ChartTest
+ {
+ private byte[] _writeData;
+
+ public ChartTest()
+ {
+ _writeData = null;
+ }
+
+ [Fact]
+ public void BuildChartAddsDefaultArea()
+ {
+ var chart = new Chart(GetContext(), GetVirtualPathProvider(), 100, 100);
+ AssertBuiltChartAction(chart, c =>
+ {
+ Assert.Equal(1, c.ChartAreas.Count);
+ Assert.Equal("Default", c.ChartAreas[0].Name);
+ });
+ }
+
+ [Fact]
+ public void XAxisOverrides()
+ {
+ var chart = new Chart(GetContext(), GetVirtualPathProvider(), 100, 100)
+ .SetXAxis("AxisX", 1, 100);
+ AssertBuiltChartAction(chart, c =>
+ {
+ Assert.Equal(1, c.ChartAreas.Count);
+ Assert.Equal("AxisX", c.ChartAreas[0].AxisX.Title);
+ Assert.Equal(1, c.ChartAreas[0].AxisX.Minimum);
+ Assert.Equal(100, c.ChartAreas[0].AxisX.Maximum);
+ });
+ }
+
+ [Fact]
+ public void YAxisOverrides()
+ {
+ var chart = new Chart(GetContext(), GetVirtualPathProvider(), 100, 100)
+ .SetYAxis("AxisY", 1, 100);
+ AssertBuiltChartAction(chart, c =>
+ {
+ Assert.Equal(1, c.ChartAreas.Count);
+ Assert.Equal("AxisY", c.ChartAreas[0].AxisY.Title);
+ Assert.Equal(1, c.ChartAreas[0].AxisY.Minimum);
+ Assert.Equal(100, c.ChartAreas[0].AxisY.Maximum);
+ });
+ }
+
+ [Fact]
+ public void ConstructorLoadsTemplate()
+ {
+ var template = WriteTemplate(@"<Chart BorderWidth=""2""></Chart>");
+ var chart = new Chart(GetContext(), GetVirtualPathProvider(), 100, 100, themePath: template);
+ AssertBuiltChartAction(chart, c => { Assert.Equal(2, c.BorderWidth); });
+ }
+
+ [Fact]
+ public void ConstructorLoadsTheme()
+ {
+ //Vanilla theme
+ /*
+ * <Chart Palette="SemiTransparent" BorderColor="#000" BorderWidth="2" BorderlineDashStyle="Solid">
+ <ChartAreas>
+ <ChartArea _Template_="All" Name="Default">
+ <AxisX>
+ <MinorGrid Enabled="False" />
+ <MajorGrid Enabled="False" />
+ </AxisX>
+ <AxisY>
+ <MajorGrid Enabled="False" />
+ <MinorGrid Enabled="False" />
+ </AxisY>
+ </ChartArea>
+ </ChartAreas>
+ </Chart>
+ */
+ var chart = new Chart(GetContext(), GetVirtualPathProvider(), 100, 100, theme: ChartTheme.Vanilla);
+ AssertBuiltChartAction(chart, c =>
+ {
+ Assert.Equal(c.Palette, ChartColorPalette.SemiTransparent);
+ Assert.Equal(c.BorderColor, Color.FromArgb(0, Color.Black));
+ Assert.Equal(1, c.ChartAreas.Count);
+ Assert.False(c.ChartAreas[0].AxisX.MajorGrid.Enabled);
+ Assert.False(c.ChartAreas[0].AxisY.MinorGrid.Enabled);
+ });
+ }
+
+ [Fact]
+ public void ConstructorLoadsThemeAndTemplate()
+ {
+ //Vanilla theme
+ /*
+ * <Chart Palette="SemiTransparent" BorderColor="#000" BorderWidth="2" BorderlineDashStyle="Solid">
+ <ChartAreas>
+ <ChartArea _Template_="All" Name="Default">
+ <AxisX>
+ <MinorGrid Enabled="False" />
+ <MajorGrid Enabled="False" />
+ </AxisX>
+ <AxisY>
+ <MajorGrid Enabled="False" />
+ <MinorGrid Enabled="False" />
+ </AxisY>
+ </ChartArea>
+ </ChartAreas>
+ </Chart>
+ */
+ var template = WriteTemplate(@"<Chart BorderlineDashStyle=""DashDot""><Legends><Legend BackColor=""Red"" /></Legends></Chart>");
+ var chart = new Chart(GetContext(), GetVirtualPathProvider(), 100, 100, theme: ChartTheme.Vanilla, themePath: template);
+ AssertBuiltChartAction(chart, c =>
+ {
+ Assert.Equal(c.Palette, ChartColorPalette.SemiTransparent);
+ Assert.Equal(c.BorderColor, Color.FromArgb(0, Color.Black));
+ Assert.Equal(c.BorderlineDashStyle, ChartDashStyle.DashDot);
+ Assert.Equal(1, c.ChartAreas.Count);
+ Assert.Equal(c.Legends.Count, 1);
+ Assert.Equal(c.Legends[0].BackColor, Color.Red);
+ Assert.False(c.ChartAreas[0].AxisX.MajorGrid.Enabled);
+ Assert.False(c.ChartAreas[0].AxisY.MinorGrid.Enabled);
+ });
+ }
+
+ [Fact]
+ public void ConstructorSetsWidthAndHeight()
+ {
+ var chart = new Chart(GetContext(), GetVirtualPathProvider(), 101, 102);
+ Assert.Equal(101, chart.Width);
+ Assert.Equal(102, chart.Height);
+ AssertBuiltChartAction(chart, c =>
+ {
+ Assert.Equal(101, c.Width);
+ Assert.Equal(102, c.Height);
+ });
+ }
+
+ [Fact]
+ public void ConstructorThrowsWhenHeightIsLessThanZero()
+ {
+ Assert.ThrowsArgumentOutOfRange(() => { new Chart(GetContext(), GetVirtualPathProvider(), 100, -1); }, "height", "Value must be greater than or equal to 0.");
+ }
+
+ [Fact]
+ public void ConstructorThrowsWhenTemplateNotFound()
+ {
+ var templateFile = @"FileNotFound.xml";
+ Assert.ThrowsArgument(() => { new Chart(GetContext(), GetVirtualPathProvider(), 100, 100, themePath: templateFile); },
+ "themePath",
+ String.Format("The theme file \"{0}\" could not be found.", VirtualPathUtility.Combine(GetContext().Request.AppRelativeCurrentExecutionFilePath, templateFile)));
+ }
+
+ [Fact]
+ public void ConstructorThrowsWhenWidthIsLessThanZero()
+ {
+ Assert.ThrowsArgumentOutOfRange(() => { new Chart(GetContext(), GetVirtualPathProvider(), -1, 100); }, "width", "Value must be greater than or equal to 0.");
+ }
+
+ [Fact]
+ public void DataBindCrossTable()
+ {
+ var data = new[]
+ {
+ new { GroupBy = "1", YValue = 1 },
+ new { GroupBy = "1", YValue = 2 },
+ new { GroupBy = "2", YValue = 1 }
+ };
+ var chart = new Chart(GetContext(), GetVirtualPathProvider(), 100, 100)
+ .DataBindCrossTable(data, "GroupBy", xField: null, yFields: "YValue");
+ // todo - anything else to verify here?
+ AssertBuiltChartAction(chart, c =>
+ {
+ Assert.Equal(2, c.Series.Count);
+ Assert.Equal(2, c.Series[0].Points.Count);
+ Assert.Equal(1, c.Series[1].Points.Count);
+ });
+ }
+
+ [Fact]
+ public void DataBindCrossTableThrowsWhenDataSourceIsNull()
+ {
+ var chart = new Chart(GetContext(), GetVirtualPathProvider(), 100, 100);
+ Assert.ThrowsArgumentNull(() => { chart.DataBindCrossTable(null, "GroupBy", xField: null, yFields: "yFields"); }, "dataSource");
+ }
+
+ [Fact]
+ public void DataBindCrossTableThrowsWhenDataSourceIsString()
+ {
+ var chart = new Chart(GetContext(), GetVirtualPathProvider(), 100, 100);
+ Assert.ThrowsArgument(() => { chart.DataBindCrossTable("DataSource", "GroupBy", xField: null, yFields: "yFields"); }, "dataSource", "A series cannot be data-bound to a string object.");
+ }
+
+ [Fact]
+ public void DataBindCrossTableThrowsWhenGroupByIsNull()
+ {
+ var chart = new Chart(GetContext(), GetVirtualPathProvider(), 100, 100);
+ Assert.ThrowsArgumentNullOrEmptyString(() => { chart.DataBindCrossTable(new object[0], null, xField: null, yFields: "yFields"); }, "groupByField");
+ }
+
+ [Fact]
+ public void DataBindCrossTableThrowsWhenGroupByIsEmpty()
+ {
+ var chart = new Chart(GetContext(), GetVirtualPathProvider(), 100, 100);
+ Assert.ThrowsArgumentNullOrEmptyString(() => { chart.DataBindCrossTable(new object[0], "", xField: null, yFields: "yFields"); }, "groupByField");
+ }
+
+ [Fact]
+ public void DataBindCrossTableThrowsWhenYFieldsIsNull()
+ {
+ var chart = new Chart(GetContext(), GetVirtualPathProvider(), 100, 100);
+ Assert.ThrowsArgumentNullOrEmptyString(() => { chart.DataBindCrossTable(new object[0], "GroupBy", xField: null, yFields: null); }, "yFields");
+ }
+
+ [Fact]
+ public void DataBindCrossTableThrowsWhenYFieldsIsEmpty()
+ {
+ var chart = new Chart(GetContext(), GetVirtualPathProvider(), 100, 100);
+ Assert.ThrowsArgumentNullOrEmptyString(() => { chart.DataBindCrossTable(new object[0], "GroupBy", xField: null, yFields: ""); }, "yFields");
+ }
+
+ [Fact]
+ public void DataBindTable()
+ {
+ var data = new[]
+ {
+ new { XValue = "1", YValue = 1 },
+ new { XValue = "2", YValue = 2 },
+ new { XValue = "3", YValue = 3 }
+ };
+ var chart = new Chart(GetContext(), GetVirtualPathProvider(), 100, 100)
+ .DataBindTable(data, xField: "XValue");
+ // todo - anything else to verify here?
+ AssertBuiltChartAction(chart, c =>
+ {
+ Assert.Equal(1, c.Series.Count);
+ Assert.Equal(3, c.Series[0].Points.Count);
+ });
+ }
+
+ [Fact]
+ public void DataBindTableWhenXFieldIsNull()
+ {
+ var data = new[]
+ {
+ new { YValue = 1 },
+ new { YValue = 2 },
+ new { YValue = 3 }
+ };
+ var chart = new Chart(GetContext(), GetVirtualPathProvider(), 100, 100)
+ .DataBindTable(data, xField: null);
+ // todo - anything else to verify here?
+ AssertBuiltChartAction(chart, c =>
+ {
+ Assert.Equal(1, c.Series.Count);
+ Assert.Equal(3, c.Series[0].Points.Count);
+ });
+ }
+
+ [Fact]
+ public void DataBindTableThrowsWhenDataSourceIsNull()
+ {
+ var chart = new Chart(GetContext(), GetVirtualPathProvider(), 100, 100);
+ Assert.ThrowsArgumentNull(() => { chart.DataBindTable(null); }, "dataSource");
+ }
+
+ [Fact]
+ public void DataBindTableThrowsWhenDataSourceIsString()
+ {
+ var chart = new Chart(GetContext(), GetVirtualPathProvider(), 100, 100);
+ Assert.ThrowsArgument(() => { chart.DataBindTable(""); }, "dataSource", "A series cannot be data-bound to a string object.");
+ }
+
+ [Fact]
+ public void GetBytesReturnsNonEmptyArray()
+ {
+ var chart = new Chart(GetContext(), GetVirtualPathProvider(), 100, 100);
+ Assert.True(chart.GetBytes().Length > 0);
+ }
+
+ [Fact]
+ public void GetBytesThrowsWhenFormatIsEmpty()
+ {
+ var chart = new Chart(GetContext(), GetVirtualPathProvider(), 100, 100);
+ Assert.ThrowsArgumentNullOrEmptyString(() => { chart.GetBytes(format: String.Empty); }, "format");
+ }
+
+ [Fact]
+ public void GetBytesThrowsWhenFormatIsInvalid()
+ {
+ var chart = new Chart(GetContext(), GetVirtualPathProvider(), 100, 100);
+ Assert.ThrowsArgument(() => { chart.GetBytes(format: "foo"); }, "format", "\"foo\" is invalid image format. Valid values are image format names like: \"JPEG\", \"BMP\", \"GIF\", \"PNG\", etc.");
+ }
+
+ [Fact]
+ public void GetBytesThrowsWhenFormatIsNull()
+ {
+ var chart = new Chart(GetContext(), GetVirtualPathProvider(), 100, 100);
+ Assert.ThrowsArgumentNullOrEmptyString(() => { chart.GetBytes(format: null); }, "format");
+ }
+
+ [Fact]
+ public void LegendDefaults()
+ {
+ var chart = new Chart(GetContext(), GetVirtualPathProvider(), 100, 100).AddLegend();
+ AssertBuiltChartAction(chart, c =>
+ {
+ Assert.Equal(1, c.Legends.Count);
+ // NOTE: Chart.Legends.Add will create default name
+ Assert.Equal("Legend1", c.Legends[0].Name);
+ Assert.Equal(1, c.Legends[0].BorderWidth);
+ });
+ }
+
+ [Fact]
+ public void LegendOverrides()
+ {
+ var chart = new Chart(GetContext(), GetVirtualPathProvider(), 100, 100).AddLegend("Legend1")
+ .AddLegend("Legend2", "Legend2Name");
+ AssertBuiltChartAction(chart, c =>
+ {
+ Assert.Equal(2, c.Legends.Count);
+ Assert.Equal("Legend1", c.Legends[0].Name);
+ Assert.Equal("Legend2", c.Legends[1].Title);
+ Assert.Equal("Legend2Name", c.Legends[1].Name);
+ });
+ }
+
+ [Fact]
+ public void SaveAndWriteFromCache()
+ {
+ var context1 = GetContext();
+ var chart = new Chart(context1, GetVirtualPathProvider(), 100, 100);
+
+ string key = chart.SaveToCache();
+ Assert.Equal(chart, WebCache.Get(key));
+
+ var context2 = GetContext();
+ Assert.Equal(chart, Chart.GetFromCache(context2, key));
+
+ Chart.WriteFromCache(context2, key);
+
+ Assert.Null(context1.Response.ContentType);
+ Assert.Equal("image/jpeg", context2.Response.ContentType);
+ }
+
+ [Fact]
+ public void SaveThrowsWhenFormatIsEmpty()
+ {
+ var chart = new Chart(GetContext(), GetVirtualPathProvider(), 100, 100);
+ Assert.ThrowsArgumentNullOrEmptyString(() => { chart.Save(GetContext(), "chartPath", format: String.Empty); }, "format");
+ }
+
+ [Fact]
+ public void SaveWorksWhenFormatIsJPG()
+ {
+ var chart = new Chart(GetContext(), GetVirtualPathProvider(), 100, 100);
+
+ string fileName = "chartPath";
+
+ chart.Save(GetContext(), "chartPath", format: "jpg");
+ byte[] a = File.ReadAllBytes(fileName);
+
+ chart.Save(GetContext(), "chartPath", format: "jpeg");
+ byte[] b = File.ReadAllBytes(fileName);
+
+ Assert.Equal(a, b);
+ }
+
+ [Fact]
+ public void SaveThrowsWhenFormatIsInvalid()
+ {
+ var chart = new Chart(GetContext(), GetVirtualPathProvider(), 100, 100);
+ Assert.ThrowsArgument(() => { chart.Save(GetContext(), "chartPath", format: "foo"); }, "format", "\"foo\" is invalid image format. Valid values are image format names like: \"JPEG\", \"BMP\", \"GIF\", \"PNG\", etc.");
+ }
+
+ [Fact]
+ public void SaveThrowsWhenFormatIsNull()
+ {
+ var chart = new Chart(GetContext(), GetVirtualPathProvider(), 100, 100);
+ Assert.ThrowsArgumentNullOrEmptyString(() => { chart.Save(GetContext(), "chartPath", format: null); }, "format");
+ }
+
+ [Fact]
+ public void SaveThrowsWhenPathIsEmpty()
+ {
+ var chart = new Chart(GetContext(), GetVirtualPathProvider(), 100, 100);
+ Assert.ThrowsArgumentNullOrEmptyString(() => { chart.Save(GetContext(), path: String.Empty, format: "jpeg"); }, "path");
+ }
+
+ [Fact]
+ public void SaveThrowsWhenPathIsNull()
+ {
+ var chart = new Chart(GetContext(), GetVirtualPathProvider(), 100, 100);
+ Assert.ThrowsArgumentNullOrEmptyString(() => { chart.Save(GetContext(), path: null, format: "jpeg"); }, "path");
+ }
+
+ [Fact]
+ public void SaveWritesToFile()
+ {
+ var chart = new Chart(GetContext(), GetVirtualPathProvider(), 100, 100);
+ chart.Save(GetContext(), "SaveWritesToFile.jpg", format: "image/jpeg");
+ Assert.Equal("SaveWritesToFile.jpg", Path.GetFileName(chart.FileName));
+ Assert.True(File.Exists(chart.FileName));
+ }
+
+ [Fact]
+ public void SaveXmlThrowsWhenPathIsEmpty()
+ {
+ var chart = new Chart(GetContext(), GetVirtualPathProvider(), 100, 100);
+ Assert.ThrowsArgumentNullOrEmptyString(() => { chart.SaveXml(GetContext(), String.Empty); }, "path");
+ }
+
+ [Fact]
+ public void SaveXmlThrowsWhenPathIsNull()
+ {
+ var chart = new Chart(GetContext(), GetVirtualPathProvider(), 100, 100);
+ Assert.ThrowsArgumentNullOrEmptyString(() => { chart.SaveXml(GetContext(), null); }, "path");
+ }
+
+ [Fact]
+ public void SaveXmlWritesToFile()
+ {
+ var template = WriteTemplate(@"<Chart BorderWidth=""2""></Chart>");
+ var chart = new Chart(GetContext(), GetVirtualPathProvider(), 100, 100, themePath: template);
+ chart.SaveXml(GetContext(), "SaveXmlWritesToFile.xml");
+ Assert.True(File.Exists("SaveXmlWritesToFile.xml"));
+ string result = File.ReadAllText("SaveXmlWritesToFile.xml");
+ Assert.True(result.Contains("BorderWidth=\"2\""));
+ }
+
+ [Fact]
+ public void TemplateWithCommentsDoesNotThrow()
+ {
+ var template = WriteTemplate(@"<Chart BorderWidth=""2""><!-- This is a XML comment. --> </Chart>");
+ var chart = new Chart(GetContext(), GetVirtualPathProvider(), 100, 100, themePath: template);
+ Assert.NotNull(chart.ToWebImage());
+ }
+
+ [Fact]
+ public void TemplateWithIncorrectPropertiesThrows()
+ {
+ var template = WriteTemplate(@"<Chart borderWidth=""2""><fjkjkgjklfg /></Chart>");
+ var chart = new Chart(GetContext(), GetVirtualPathProvider(), 100, 100, themePath: template);
+ Assert.Throws<InvalidOperationException>(() => chart.ToWebImage(),
+ "Cannot deserialize property. Unknown property name 'borderWidth' in object \" System.Web.UI.DataVisualization.Charting.Chart");
+ }
+
+ [Fact]
+ public void WriteWorksWithJPGFormat()
+ {
+ var response = new Mock<HttpResponseBase>();
+ var stream = new MemoryStream();
+ response.Setup(c => c.Output).Returns(new StreamWriter(stream));
+
+ var context = new Mock<HttpContextBase>();
+ context.Setup(c => c.Response).Returns(response.Object);
+
+ var chart = new Chart(context.Object, GetVirtualPathProvider(), 100, 100);
+ chart.Write("jpeg");
+
+ byte[] a = stream.GetBuffer();
+
+ stream.SetLength(0);
+ chart.Write("jpg");
+ byte[] b = stream.GetBuffer();
+
+ Assert.Equal(a, b);
+ }
+
+ [Fact]
+ public void WriteThrowsWithInvalidFormat()
+ {
+ var chart = new Chart(GetContext(), GetVirtualPathProvider(), 100, 100);
+ Assert.ThrowsArgument(() => chart.Write("foo"),
+ "format", "\"foo\" is invalid image format. Valid values are image format names like: \"JPEG\", \"BMP\", \"GIF\", \"PNG\", etc.");
+ }
+
+ [Fact]
+ public void SeriesOverrides()
+ {
+ var chart = new Chart(GetContext(), GetVirtualPathProvider(), 100, 100)
+ .AddSeries(chartType: "Bar");
+ AssertBuiltChartAction(chart, c =>
+ {
+ Assert.Equal(1, c.Series.Count);
+ Assert.Equal(SeriesChartType.Bar, c.Series[0].ChartType);
+ });
+ }
+
+ [Fact]
+ public void SeriesThrowsWhenChartTypeIsEmpty()
+ {
+ var chart = new Chart(GetContext(), GetVirtualPathProvider(), 100, 100);
+ Assert.ThrowsArgumentNullOrEmptyString(() => { chart.AddSeries(chartType: ""); }, "chartType");
+ }
+
+ [Fact]
+ public void SeriesThrowsWhenChartTypeIsNull()
+ {
+ var chart = new Chart(GetContext(), GetVirtualPathProvider(), 100, 100);
+ Assert.ThrowsArgumentNullOrEmptyString(() => { chart.AddSeries(chartType: null); }, "chartType");
+ }
+
+ [Fact]
+ public void TitleDefaults()
+ {
+ var chart = new Chart(GetContext(), GetVirtualPathProvider(), 100, 100).AddTitle();
+ AssertBuiltChartAction(chart, c =>
+ {
+ Assert.Equal(1, c.Titles.Count);
+ // NOTE: Chart.Titles.Add will create default name
+ Assert.Equal("Title1", c.Titles[0].Name);
+ Assert.Equal(String.Empty, c.Titles[0].Text);
+ Assert.Equal(1, c.Titles[0].BorderWidth);
+ });
+ }
+
+ [Fact]
+ public void TitleOverrides()
+ {
+ var chart = new Chart(GetContext(), GetVirtualPathProvider(), 100, 100).AddTitle(name: "Title1")
+ .AddTitle("Title2Text", name: "Title2");
+ AssertBuiltChartAction(chart, c =>
+ {
+ Assert.Equal(2, c.Titles.Count);
+ Assert.Equal("Title1", c.Titles[0].Name);
+ Assert.Equal("Title2", c.Titles[1].Name);
+ Assert.Equal("Title2Text", c.Titles[1].Text);
+ });
+ }
+
+ [Fact]
+ public void ToWebImage()
+ {
+ var chart = new Chart(GetContext(), GetVirtualPathProvider(), 100, 100);
+ var image = chart.ToWebImage();
+ Assert.NotNull(image);
+ Assert.Equal("jpeg", image.ImageFormat);
+ }
+
+ [Fact]
+ public void ToWebImageUsesFormat()
+ {
+ var chart = new Chart(GetContext(), GetVirtualPathProvider(), 100, 100);
+ var image = chart.ToWebImage(format: "png");
+ Assert.NotNull(image);
+ Assert.Equal("png", image.ImageFormat);
+ }
+
+ [Fact]
+ public void WriteFromCacheIsNoOpIfNotSavedInCache()
+ {
+ var context = GetContext();
+ Assert.Null(Chart.WriteFromCache(context, Guid.NewGuid().ToString()));
+ Assert.Null(context.Response.ContentType);
+ }
+
+ [Fact]
+ public void WriteUpdatesResponse()
+ {
+ var context = GetContext();
+ var chart = new Chart(context, GetVirtualPathProvider(), 100, 100);
+ chart.Write();
+ Assert.Equal("", context.Response.Charset);
+ Assert.Equal("image/jpeg", context.Response.ContentType);
+ Assert.True((_writeData != null) && (_writeData.Length > 0));
+ }
+
+ private void AssertBuiltChartAction(Chart chart, Action<UI.DataVisualization.Charting.Chart> action)
+ {
+ bool actionCalled = false;
+ chart.ExecuteChartAction(c =>
+ {
+ action(c);
+ actionCalled = true;
+ });
+ Assert.True(actionCalled);
+ }
+
+ private HttpContextBase GetContext()
+ {
+ // Strip drive letter for VirtualPathUtility.Combine
+ var testPath = Directory.GetCurrentDirectory().Substring(2) + "/Out";
+ Mock<HttpRequestBase> request = new Mock<HttpRequestBase>();
+ request.Setup(r => r.AppRelativeCurrentExecutionFilePath).Returns(testPath);
+ request.Setup(r => r.MapPath(It.IsAny<string>())).Returns((string path) => path);
+
+ Mock<HttpResponseBase> response = new Mock<HttpResponseBase>();
+ response.SetupProperty(r => r.ContentType);
+ response.SetupProperty(r => r.Charset);
+ response.Setup(r => r.BinaryWrite(It.IsAny<byte[]>())).Callback((byte[] data) => _writeData = data);
+
+ Mock<HttpServerUtilityBase> server = new Mock<HttpServerUtilityBase>();
+ server.Setup(s => s.MapPath(It.IsAny<string>())).Returns((string s) => s);
+
+ var items = new Hashtable();
+
+ Mock<HttpContextBase> context = new Mock<HttpContextBase>();
+ context.Setup(c => c.Request).Returns(request.Object);
+ context.Setup(c => c.Response).Returns(response.Object);
+ context.Setup(c => c.Server).Returns(server.Object);
+ context.Setup(c => c.Items).Returns(items);
+ return context.Object;
+ }
+
+ private string WriteTemplate(string xml)
+ {
+ var path = Guid.NewGuid() + ".xml";
+ File.WriteAllText(path, xml);
+ return path;
+ }
+
+ private MockVirtualPathProvider GetVirtualPathProvider()
+ {
+ return new MockVirtualPathProvider();
+ }
+
+ class MockVirtualPathProvider : VirtualPathProvider
+ {
+ class MockVirtualFile : VirtualFile
+ {
+ public MockVirtualFile(string virtualPath)
+ : base(virtualPath)
+ {
+ }
+
+ public override Stream Open()
+ {
+ return File.Open(this.VirtualPath, FileMode.Open);
+ }
+ }
+
+ public override bool FileExists(string virtualPath)
+ {
+ return File.Exists(virtualPath);
+ }
+
+ public override VirtualFile GetFile(string virtualPath)
+ {
+ return new MockVirtualFile(virtualPath);
+ }
+ }
+ }
+}
diff --git a/test/System.Web.Helpers.Test/ConversionUtilTest.cs b/test/System.Web.Helpers.Test/ConversionUtilTest.cs
new file mode 100644
index 00000000..e856261f
--- /dev/null
+++ b/test/System.Web.Helpers.Test/ConversionUtilTest.cs
@@ -0,0 +1,76 @@
+using System.Drawing;
+using System.Globalization;
+using Xunit;
+
+namespace System.Web.Helpers.Test
+{
+ public class ConversionUtilTest
+ {
+ [Fact]
+ public void ConversionUtilReturnsStringTypes()
+ {
+ // Arrange
+ string original = "Foo";
+
+ // Act
+ object result;
+ bool success = ConversionUtil.TryFromString(typeof(String), original, out result);
+
+ // Assert
+ Assert.True(success);
+ Assert.Equal(original, result);
+ }
+
+ [Fact]
+ public void ConversionUtilConvertsStringsToColor()
+ {
+ // Arrange
+ string original = "Blue";
+
+ // Act
+ object result;
+ bool success = ConversionUtil.TryFromString(typeof(Color), original, out result);
+
+ // Assert
+ Assert.True(success);
+ Assert.Equal(Color.Blue, result);
+ }
+
+ [Fact]
+ public void ConversionUtilConvertsEnumValues()
+ {
+ // Arrange
+ string original = "Weekday";
+
+ // Act
+ object result;
+ bool success = ConversionUtil.TryFromString(typeof(TestEnum), original, out result);
+
+ // Assert
+ Assert.True(success);
+ Assert.Equal(TestEnum.Weekday, result);
+ }
+
+ [Fact]
+ public void ConversionUtilUsesTypeConverterToConvertArbitraryTypes()
+ {
+ // Arrange
+ var date = new DateTime(2010, 01, 01);
+ string original = date.ToString(CultureInfo.InvariantCulture);
+
+ // Act
+ object result;
+ bool success = ConversionUtil.TryFromString(typeof(DateTime), original, out result);
+
+ // Assert
+ Assert.True(success);
+ Assert.Equal(date, result);
+ }
+
+ private enum TestEnum
+ {
+ Weekend,
+ Weekday
+ }
+ }
+}
diff --git a/test/System.Web.Helpers.Test/CryptoTest.cs b/test/System.Web.Helpers.Test/CryptoTest.cs
new file mode 100644
index 00000000..89b469fe
--- /dev/null
+++ b/test/System.Web.Helpers.Test/CryptoTest.cs
@@ -0,0 +1,170 @@
+using System.IO;
+using System.Security.Cryptography;
+using System.Text;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Helpers.Test
+{
+ /// <summary>
+ ///This is a test class for CryptoTest and is intended
+ ///to contain all CryptoTest Unit Tests
+ ///</summary>
+ public class CryptoTest
+ {
+ [Fact]
+ public void SHA256HashTest_ReturnsValidData()
+ {
+ string data = "foo bar";
+ string expected = "FBC1A9F858EA9E177916964BD88C3D37B91A1E84412765E29950777F265C4B75";
+ string actual;
+ actual = Crypto.SHA256(data);
+ Assert.Equal(expected, actual);
+
+ actual = Crypto.Hash(Encoding.UTF8.GetBytes(data));
+ Assert.Equal(expected, actual);
+ }
+
+ [Fact]
+ public void GenerateSaltTest()
+ {
+ string salt = Crypto.GenerateSalt();
+ salt = Crypto.GenerateSalt(64);
+ Assert.Equal(24, Crypto.GenerateSalt().Length);
+ Assert.Equal(12, Crypto.GenerateSalt(8).Length);
+ Assert.Equal(88, Crypto.GenerateSalt(64).Length);
+ Assert.Equal(44, Crypto.GenerateSalt(32).Length);
+ }
+
+ [Fact]
+ public void HashPassword_PasswordGeneration()
+ {
+ // Act - call helper directly
+ string generatedHash = Crypto.HashPassword("my-password");
+ byte[] salt = new byte[16];
+ Buffer.BlockCopy(Convert.FromBase64String(generatedHash), 1, salt, 0, 16); // extract salt from generated hash
+
+ // Act - perform PBKDF2 directly
+ string generatedHash2;
+ using (var ms = new MemoryStream())
+ {
+ using (var bw = new BinaryWriter(ms))
+ {
+ using (var deriveBytes = new Rfc2898DeriveBytes("my-password", salt, iterations: 1000))
+ {
+ bw.Write((byte)0x00); // version identifier
+ bw.Write(salt); // salt
+ bw.Write(deriveBytes.GetBytes(32)); // subkey
+ }
+
+ generatedHash2 = Convert.ToBase64String(ms.ToArray());
+ }
+ }
+
+ // Assert
+ Assert.Equal(generatedHash2, generatedHash);
+ }
+
+ [Fact]
+ public void HashPassword_RoundTripping()
+ {
+ // Act & assert
+ string password = "ImPepper";
+ Assert.True(Crypto.VerifyHashedPassword(Crypto.HashPassword(password), password));
+ Assert.False(Crypto.VerifyHashedPassword(Crypto.HashPassword(password), "ImSalt"));
+ Assert.False(Crypto.VerifyHashedPassword(Crypto.HashPassword("Impepper"), password));
+ }
+
+ [Fact]
+ public void VerifyHashedPassword_CorrectPassword_ReturnsTrue()
+ {
+ // Arrange
+ string hashedPassword = "ALyuoraY/cIWD1hjo+K81/pf83qo6Q6T+UBYcXN9P3A9WHLvEY10f+lwW5qPG6h9xw=="; // this is for 'my-password'
+
+ // Act
+ bool retVal = Crypto.VerifyHashedPassword(hashedPassword, "my-password");
+
+ // Assert
+ Assert.True(retVal);
+ }
+
+ [Fact]
+ public void VerifyHashedPassword_IncorrectPassword_ReturnsFalse()
+ {
+ // Arrange
+ string hashedPassword = "ALyuoraY/cIWD1hjo+K81/pf83qo6Q6T+UBYcXN9P3A9WHLvEY10f+lwW5qPG6h9xw=="; // this is for 'my-password'
+
+ // Act
+ bool retVal = Crypto.VerifyHashedPassword(hashedPassword, "some-other-password");
+
+ // Assert
+ Assert.False(retVal);
+ }
+
+ [Fact]
+ public void VerifyHashedPassword_InvalidPasswordHash_ReturnsFalse()
+ {
+ // Arrange
+ string hashedPassword = "AAECAw=="; // this is an invalid password hash
+
+ // Act
+ bool retVal = Crypto.VerifyHashedPassword(hashedPassword, "hello-world");
+
+ // Assert
+ Assert.False(retVal);
+ }
+
+ [Fact]
+ public void MD5HashTest_ReturnsValidData()
+ {
+ string data = "foo bar";
+ string expected = "327B6F07435811239BC47E1544353273";
+ string actual;
+ actual = Crypto.Hash(data, algorithm: "md5");
+ Assert.Equal(expected, actual);
+
+ actual = Crypto.Hash(Encoding.UTF8.GetBytes(data), algorithm: "MD5");
+ Assert.Equal(expected, actual);
+ }
+
+ [Fact]
+ public void SHA1HashTest_ReturnsValidData()
+ {
+ string data = "foo bar";
+ string expected = "3773DEA65156909838FA6C22825CAFE090FF8030";
+ string actual;
+ actual = Crypto.SHA1(data);
+ Assert.Equal(expected, actual);
+
+ actual = Crypto.Hash(Encoding.UTF8.GetBytes(data), algorithm: "sha1");
+ Assert.Equal(expected, actual);
+ }
+
+ [Fact]
+ public void SHA1HashTest_WithNull_ThrowsException()
+ {
+ Assert.Throws<ArgumentNullException>(() => Crypto.SHA1((string)null));
+ Assert.Throws<ArgumentNullException>(() => Crypto.Hash((byte[])null, algorithm: "SHa1"));
+ }
+
+ [Fact]
+ public void SHA256HashTest_WithNull_ThrowsException()
+ {
+ Assert.Throws<ArgumentNullException>(() => Crypto.SHA256((string)null));
+ Assert.Throws<ArgumentNullException>(() => Crypto.Hash((byte[])null, algorithm: "sHa256"));
+ }
+
+ [Fact]
+ public void MD5HashTest_WithNull_ThrowsException()
+ {
+ Assert.Throws<ArgumentNullException>(() => Crypto.Hash((string)null, algorithm: "mD5"));
+ Assert.Throws<ArgumentNullException>(() => Crypto.Hash((byte[])null, algorithm: "mD5"));
+ }
+
+ [Fact]
+ public void HashWithUnknownAlg_ThrowsException()
+ {
+ Assert.Throws<InvalidOperationException>(() => Crypto.Hash("sdflksd", algorithm: "hao"), "The hash algorithm 'hao' is not supported, valid values are: sha256, sha1, md5");
+ }
+ }
+}
diff --git a/test/System.Web.Helpers.Test/DynamicDictionary.cs b/test/System.Web.Helpers.Test/DynamicDictionary.cs
new file mode 100644
index 00000000..cdbf598b
--- /dev/null
+++ b/test/System.Web.Helpers.Test/DynamicDictionary.cs
@@ -0,0 +1,103 @@
+using System.Collections.Generic;
+using System.Dynamic;
+using System.Linq.Expressions;
+using System.Reflection;
+
+namespace System.Web.Helpers.Test
+{
+ /// <summary>
+ /// Dynamic object implementation over a dictionary that doesn't implement anything but the interface.
+ /// Used for testing our types that consume dynamic objects to make sure they don't make any assumptions on the implementation.
+ /// </summary>
+ public class DynamicDictionary : IDynamicMetaObjectProvider
+ {
+ private readonly Dictionary<string, object> _values = new Dictionary<string, object>();
+
+ public object this[string name]
+ {
+ get
+ {
+ object result;
+ _values.TryGetValue(name, out result);
+ return result;
+ }
+ set { _values[name] = value; }
+ }
+
+ public DynamicMetaObject GetMetaObject(Expression parameter)
+ {
+ return new DynamicDictionaryMetaObject(parameter, this);
+ }
+
+ private class DynamicDictionaryMetaObject : DynamicMetaObject
+ {
+ private static readonly PropertyInfo ItemPropery = typeof(DynamicDictionary).GetProperty("Item");
+
+ public DynamicDictionaryMetaObject(Expression expression, object value)
+ : base(expression, BindingRestrictions.Empty, value)
+ {
+ }
+
+ private IDictionary<string, object> WrappedDictionary
+ {
+ get { return ((DynamicDictionary)Value)._values; }
+ }
+
+ private Expression GetDynamicExpression()
+ {
+ return Expression.Convert(Expression, typeof(DynamicDictionary));
+ }
+
+ private Expression GetIndexExpression(string key)
+ {
+ return Expression.MakeIndex(
+ GetDynamicExpression(),
+ ItemPropery,
+ new[]
+ {
+ Expression.Constant(key)
+ }
+ );
+ }
+
+ private Expression GetSetValueExpression(string key, object value)
+ {
+ return Expression.Assign(
+ GetIndexExpression(key),
+ Expression.Convert(Expression.Constant(value),
+ typeof(object))
+ );
+ }
+
+ public override DynamicMetaObject BindGetMember(GetMemberBinder binder)
+ {
+ var binderDefault = binder.FallbackGetMember(this);
+
+ var expression = Expression.Convert(GetIndexExpression(binder.Name),
+ typeof(object));
+
+ var dynamicSuggestion = new DynamicMetaObject(expression, BindingRestrictions.GetTypeRestriction(Expression, LimitType)
+ .Merge(binderDefault.Restrictions));
+
+ return binder.FallbackGetMember(this, dynamicSuggestion);
+ }
+
+ public override DynamicMetaObject BindSetMember(SetMemberBinder binder, DynamicMetaObject value)
+ {
+ var binderDefault = binder.FallbackSetMember(this, value);
+
+ Expression expression = GetSetValueExpression(binder.Name, value.Value);
+
+ var dynamicSuggestion = new DynamicMetaObject(expression, BindingRestrictions.GetTypeRestriction(Expression, LimitType)
+ .Merge(binderDefault.Restrictions));
+
+ return binder.FallbackSetMember(this, value, dynamicSuggestion);
+ }
+
+ public override IEnumerable<string> GetDynamicMemberNames()
+ {
+ return WrappedDictionary.Keys;
+ }
+ }
+ }
+}
diff --git a/test/System.Web.Helpers.Test/DynamicHelperTest.cs b/test/System.Web.Helpers.Test/DynamicHelperTest.cs
new file mode 100644
index 00000000..c1851502
--- /dev/null
+++ b/test/System.Web.Helpers.Test/DynamicHelperTest.cs
@@ -0,0 +1,38 @@
+using System.Dynamic;
+using System.Web.WebPages.TestUtils;
+using Microsoft.Internal.Web.Utils;
+using Xunit;
+
+namespace System.Web.Helpers.Test
+{
+ public class DynamicHelperTest
+ {
+ [Fact]
+ public void TryGetMemberValueReturnsValueIfBinderIsNotCSharp()
+ {
+ // Arrange
+ var mockMemberBinder = new MockMemberBinder("Foo");
+ var dynamic = new DynamicWrapper(new { Foo = "Bar" });
+
+ // Act
+ object value;
+ bool result = DynamicHelper.TryGetMemberValue(dynamic, mockMemberBinder, out value);
+
+ // Assert
+ Assert.Equal(value, "Bar");
+ }
+
+ private class MockMemberBinder : GetMemberBinder
+ {
+ public MockMemberBinder(string name)
+ : base(name, false)
+ {
+ }
+
+ public override DynamicMetaObject FallbackGetMember(DynamicMetaObject target, DynamicMetaObject errorSuggestion)
+ {
+ throw new NotImplementedException();
+ }
+ }
+ }
+}
diff --git a/test/System.Web.Helpers.Test/DynamicWrapper.cs b/test/System.Web.Helpers.Test/DynamicWrapper.cs
new file mode 100644
index 00000000..9aec18fb
--- /dev/null
+++ b/test/System.Web.Helpers.Test/DynamicWrapper.cs
@@ -0,0 +1,80 @@
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Dynamic;
+using System.Linq;
+using System.Linq.Expressions;
+using System.Reflection;
+
+namespace System.Web.Helpers.Test
+{
+ /// <summary>
+ /// Dynamic object implementation over a regualar CLR object. Getmember accesses members through reflection.
+ /// </summary>
+ public class DynamicWrapper : IDynamicMetaObjectProvider
+ {
+ private object _object;
+
+ public DynamicWrapper(object obj)
+ {
+ _object = obj;
+ }
+
+ public DynamicMetaObject GetMetaObject(Expression parameter)
+ {
+ return new DynamicWrapperMetaObject(parameter, this);
+ }
+
+ private class DynamicWrapperMetaObject : DynamicMetaObject
+ {
+ public DynamicWrapperMetaObject(Expression expression, object value)
+ : base(expression, BindingRestrictions.Empty, value)
+ {
+ }
+
+ private object WrappedObject
+ {
+ get { return ((DynamicWrapper)Value)._object; }
+ }
+
+ private Expression GetDynamicExpression()
+ {
+ return Expression.Convert(Expression, typeof(DynamicWrapper));
+ }
+
+ private Expression GetWrappedObjectExpression()
+ {
+ FieldInfo fieldInfo = typeof(DynamicWrapper).GetField("_object", BindingFlags.NonPublic | BindingFlags.Instance);
+ Debug.Assert(fieldInfo != null);
+ return Expression.Convert(
+ Expression.Field(GetDynamicExpression(), fieldInfo),
+ WrappedObject.GetType());
+ }
+
+ private Expression GetMemberAccessExpression(string memberName)
+ {
+ return Expression.Property(
+ GetWrappedObjectExpression(),
+ memberName);
+ }
+
+ public override DynamicMetaObject BindGetMember(GetMemberBinder binder)
+ {
+ var binderDefault = binder.FallbackGetMember(this);
+
+ var expression = Expression.Convert(GetMemberAccessExpression(binder.Name), typeof(object));
+
+ var dynamicSuggestion = new DynamicMetaObject(expression, BindingRestrictions.GetTypeRestriction(Expression, LimitType)
+ .Merge(binderDefault.Restrictions));
+
+ return binder.FallbackGetMember(this, dynamicSuggestion);
+ }
+
+ public override IEnumerable<string> GetDynamicMemberNames()
+ {
+ return (from p in WrappedObject.GetType().GetProperties()
+ orderby p.Name
+ select p.Name).ToArray();
+ }
+ }
+ }
+}
diff --git a/test/System.Web.Helpers.Test/HelperResultTest.cs b/test/System.Web.Helpers.Test/HelperResultTest.cs
new file mode 100644
index 00000000..a38cb7bd
--- /dev/null
+++ b/test/System.Web.Helpers.Test/HelperResultTest.cs
@@ -0,0 +1,72 @@
+using System.IO;
+using System.Web.WebPages;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Helpers.Test
+{
+ /// <summary>
+ ///This is a test class for Util is intended
+ ///to contain all HelperResult Unit Tests
+ ///</summary>
+ public class HelperResultTest
+ {
+ [Fact]
+ public void HelperResultConstructorNullTest()
+ {
+ Assert.ThrowsArgumentNull(() => { var helper = new HelperResult(null); }, "action");
+ }
+
+ [Fact]
+ public void ToStringTest()
+ {
+ var text = "Hello";
+ Action<TextWriter> action = tw => tw.Write(text);
+ var helper = new HelperResult(action);
+ Assert.Equal(text, helper.ToString());
+ }
+
+ [Fact]
+ public void WriteToTest()
+ {
+ var text = "Hello";
+ Action<TextWriter> action = tw => tw.Write(text);
+ var helper = new HelperResult(action);
+ var writer = new StringWriter();
+ helper.WriteTo(writer);
+ Assert.Equal(text, writer.ToString());
+ }
+
+ [Fact]
+ public void ToHtmlStringDoesNotEncode()
+ {
+ // Arrange
+ string text = "<strong>This is a test & it uses html.</strong>";
+ Action<TextWriter> action = writer => writer.Write(text);
+ HelperResult helperResult = new HelperResult(action);
+
+ // Act
+ string result = helperResult.ToHtmlString();
+
+ // Assert
+ Assert.Equal(result, text);
+ }
+
+ [Fact]
+ public void ToHtmlStringReturnsSameResultAsWriteTo()
+ {
+ // Arrange
+ string text = "<strong>This is a test & it uses html.</strong>";
+ Action<TextWriter> action = writer => writer.Write(text);
+ HelperResult helperResult = new HelperResult(action);
+ StringWriter stringWriter = new StringWriter();
+
+ // Act
+ string htmlString = helperResult.ToHtmlString();
+ helperResult.WriteTo(stringWriter);
+
+ // Assert
+ Assert.Equal(htmlString, stringWriter.ToString());
+ }
+ }
+}
diff --git a/test/System.Web.Helpers.Test/JsonTest.cs b/test/System.Web.Helpers.Test/JsonTest.cs
new file mode 100644
index 00000000..c4af02b1
--- /dev/null
+++ b/test/System.Web.Helpers.Test/JsonTest.cs
@@ -0,0 +1,369 @@
+using System.Collections.Generic;
+using System.Dynamic;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Helpers.Test
+{
+ public class JsonTest
+ {
+ [Fact]
+ public void EncodeWithDynamicObject()
+ {
+ // Arrange
+ dynamic obj = new DummyDynamicObject();
+ obj.Name = "Hello";
+ obj.Age = 1;
+ obj.Grades = new[] { "A", "B", "C" };
+
+ // Act
+ string json = Json.Encode(obj);
+
+ // Assert
+ Assert.Equal("{\"Name\":\"Hello\",\"Age\":1,\"Grades\":[\"A\",\"B\",\"C\"]}", json);
+ }
+
+ [Fact]
+ public void EncodeArray()
+ {
+ // Arrange
+ object input = new string[] { "one", "2", "three", "4" };
+
+ // Act
+ string json = Json.Encode(input);
+
+ // Assert
+ Assert.Equal("[\"one\",\"2\",\"three\",\"4\"]", json);
+ }
+
+ [Fact]
+ public void EncodeDynamicJsonArrayEncodesAsArray()
+ {
+ // Arrange
+ dynamic array = Json.Decode("[1,2,3]");
+
+ // Act
+ string json = Json.Encode(array);
+
+ // Assert
+ Assert.Equal("[1,2,3]", json);
+ }
+
+ [Fact]
+ public void DecodeDynamicObject()
+ {
+ // Act
+ var obj = Json.Decode("{\"Name\":\"Hello\",\"Age\":1,\"Grades\":[\"A\",\"B\",\"C\"]}");
+
+ // Assert
+ Assert.Equal("Hello", obj.Name);
+ Assert.Equal(1, obj.Age);
+ Assert.Equal(3, obj.Grades.Length);
+ Assert.Equal("A", obj.Grades[0]);
+ Assert.Equal("B", obj.Grades[1]);
+ Assert.Equal("C", obj.Grades[2]);
+ }
+
+ [Fact]
+ public void DecodeDynamicObjectImplicitConversionToDictionary()
+ {
+ // Act
+ IDictionary<string, object> values = Json.Decode("{\"Name\":\"Hello\",\"Age\":1}");
+
+ // Assert
+ Assert.Equal("Hello", values["Name"]);
+ Assert.Equal(1, values["Age"]);
+ }
+
+ [Fact]
+ public void DecodeArrayImplicitConversionToArrayAndObjectArray()
+ {
+ // Act
+ Array array = Json.Decode("[1,2,3]");
+ object[] objArray = Json.Decode("[1,2,3]");
+ IEnumerable<dynamic> dynamicEnumerable = Json.Decode("[{a:1}]");
+
+ // Assert
+ Assert.NotNull(array);
+ Assert.NotNull(objArray);
+ Assert.NotNull(dynamicEnumerable);
+ }
+
+ [Fact]
+ public void DecodeArrayImplicitConversionToArrayArrayValuesAreDynamic()
+ {
+ // Act
+ dynamic[] objArray = Json.Decode("[{\"A\":1}]");
+
+ // Assert
+ Assert.NotNull(objArray);
+ Assert.Equal(1, objArray[0].A);
+ }
+
+ [Fact]
+ public void DecodeDynamicObjectAccessPropertiesByIndexer()
+ {
+ // Arrange
+ var obj = Json.Decode("{\"Name\":\"Hello\",\"Age\":1,\"Grades\":[\"A\",\"B\",\"C\"]}");
+
+ // Assert
+ Assert.Equal("Hello", obj["Name"]);
+ Assert.Equal(1, obj["Age"]);
+ Assert.Equal(3, obj["Grades"].Length);
+ Assert.Equal("A", obj["Grades"][0]);
+ Assert.Equal("B", obj["Grades"][1]);
+ Assert.Equal("C", obj["Grades"][2]);
+ }
+
+ [Fact]
+ public void DecodeDynamicObjectAccessPropertiesByNullIndexerReturnsNull()
+ {
+ // Arrange
+ var obj = Json.Decode("{\"Name\":\"Hello\",\"Age\":1,\"Grades\":[\"A\",\"B\",\"C\"]}");
+
+ // Assert
+ Assert.Null(obj[null]);
+ }
+
+ [Fact]
+ public void DecodeDateTime()
+ {
+ // Act
+ DateTime dateTime = Json.Decode("\"\\/Date(940402800000)\\/\"");
+
+ // Assert
+ Assert.Equal(1999, dateTime.Year);
+ Assert.Equal(10, dateTime.Month);
+ Assert.Equal(20, dateTime.Day);
+ }
+
+ [Fact]
+ public void DecodeNumber()
+ {
+ // Act
+ int number = Json.Decode("1");
+
+ // Assert
+ Assert.Equal(1, number);
+ }
+
+ [Fact]
+ public void DecodeString()
+ {
+ // Act
+ string @string = Json.Decode("\"1\"");
+
+ // Assert
+ Assert.Equal("1", @string);
+ }
+
+ [Fact]
+ public void DecodeArray()
+ {
+ // Act
+ var values = Json.Decode("[11,12,13,14,15]");
+
+ // Assert
+ Assert.Equal(5, values.Length);
+ Assert.Equal(11, values[0]);
+ Assert.Equal(12, values[1]);
+ Assert.Equal(13, values[2]);
+ Assert.Equal(14, values[3]);
+ Assert.Equal(15, values[4]);
+ }
+
+ [Fact]
+ public void DecodeObjectWithArrayProperty()
+ {
+ // Act
+ var obj = Json.Decode("{\"A\":1,\"B\":[1,3,4]}");
+ object[] bValues = obj.B;
+
+ // Assert
+ Assert.Equal(1, obj.A);
+ Assert.Equal(1, bValues[0]);
+ Assert.Equal(3, bValues[1]);
+ Assert.Equal(4, bValues[2]);
+ }
+
+ [Fact]
+ public void DecodeArrayWithObjectValues()
+ {
+ // Act
+ var obj = Json.Decode("[{\"A\":1},{\"B\":3, \"C\": \"hello\"}]");
+
+ // Assert
+ Assert.Equal(2, obj.Length);
+ Assert.Equal(1, obj[0].A);
+ Assert.Equal(3, obj[1].B);
+ Assert.Equal("hello", obj[1].C);
+ }
+
+ [Fact]
+ public void DecodeArraySetValues()
+ {
+ // Arrange
+ var values = Json.Decode("[1,2,3,4,5]");
+ for (int i = 0; i < values.Length; i++)
+ {
+ values[i]++;
+ }
+
+ // Assert
+ Assert.Equal(5, values.Length);
+ Assert.Equal(2, values[0]);
+ Assert.Equal(3, values[1]);
+ Assert.Equal(4, values[2]);
+ Assert.Equal(5, values[3]);
+ Assert.Equal(6, values[4]);
+ }
+
+ [Fact]
+ public void DecodeArrayPassToMethodThatTakesArray()
+ {
+ // Arrange
+ var values = Json.Decode("[3,2,1]");
+
+ // Act
+ int index = Array.IndexOf(values, 2);
+
+ // Assert
+ Assert.Equal(1, index);
+ }
+
+ [Fact]
+ public void DecodeArrayGetEnumerator()
+ {
+ // Arrange
+ var values = Json.Decode("[1,2,3]");
+
+ // Assert
+ int val = 1;
+ foreach (var value in values)
+ {
+ Assert.Equal(val, val);
+ val++;
+ }
+ }
+
+ [Fact]
+ public void DecodeObjectPropertyAccessIsSameObjectInstance()
+ {
+ // Arrange
+ var obj = Json.Decode("{\"Name\":{\"Version:\":4.0, \"Key\":\"Key\"}}");
+
+ // Assert
+ Assert.Same(obj.Name, obj.Name);
+ }
+
+ [Fact]
+ public void DecodeArrayAccessingMembersThatDontExistReturnsNull()
+ {
+ // Act
+ var obj = Json.Decode("[\"a\", \"b\"]");
+
+ // Assert
+ Assert.Null(obj.PropertyThatDoesNotExist);
+ }
+
+ [Fact]
+ public void DecodeObjectSetProperties()
+ {
+ // Act
+ var obj = Json.Decode("{\"A\":{\"B\":100}}");
+ obj.A.B = 20;
+
+ // Assert
+ Assert.Equal(20, obj.A.B);
+ }
+
+ [Fact]
+ public void DecodeObjectSettingObjectProperties()
+ {
+ // Act
+ var obj = Json.Decode("{\"A\":1}");
+ obj.A = new { B = 1, D = 2 };
+
+ // Assert
+ Assert.Equal(1, obj.A.B);
+ Assert.Equal(2, obj.A.D);
+ }
+
+ [Fact]
+ public void DecodeObjectWithArrayPropertyPassPropertyToMethodThatTakesArray()
+ {
+ // Arrange
+ var obj = Json.Decode("{\"A\":[3,2,1]}");
+
+ // Act
+ Array.Sort(obj.A);
+
+ // Assert
+ Assert.Equal(1, obj.A[0]);
+ Assert.Equal(2, obj.A[1]);
+ Assert.Equal(3, obj.A[2]);
+ }
+
+ [Fact]
+ public void DecodeObjectAccessingMembersThatDontExistReturnsNull()
+ {
+ // Act
+ var obj = Json.Decode("{\"A\":1}");
+
+ // Assert
+ Assert.Null(obj.PropertyThatDoesntExist);
+ }
+
+ [Fact]
+ public void DecodeObjectWithSpecificType()
+ {
+ // Act
+ var person = Json.Decode<Person>("{\"Name\":\"David\", \"Age\":2}");
+
+ // Assert
+ Assert.Equal("David", person.Name);
+ Assert.Equal(2, person.Age);
+ }
+
+ [Fact]
+ public void DecodeObjectWithImplicitConversionToNonDynamicTypeThrows()
+ {
+ // Act & Assert
+ Assert.Throws<InvalidOperationException>(() => { Person person = Json.Decode("{\"Name\":\"David\", \"Age\":2, \"Address\":{\"Street\":\"Bellevue\"}}"); }, "Unable to convert to \"System.Web.Helpers.Test.JsonTest+Person\". Use Json.Decode<T> instead.");
+ }
+
+ private class DummyDynamicObject : DynamicObject
+ {
+ private IDictionary<string, object> _values = new Dictionary<string, object>();
+
+ public override IEnumerable<string> GetDynamicMemberNames()
+ {
+ return _values.Keys;
+ }
+
+ public override bool TrySetMember(SetMemberBinder binder, object value)
+ {
+ _values[binder.Name] = value;
+ return true;
+ }
+
+ public override bool TryGetMember(GetMemberBinder binder, out object result)
+ {
+ return _values.TryGetValue(binder.Name, out result);
+ }
+ }
+
+ private class Person
+ {
+ public string Name { get; set; }
+ public int Age { get; set; }
+ public int GPA { get; set; }
+ public Address Address { get; set; }
+ }
+
+ private class Address
+ {
+ public string Street { get; set; }
+ }
+ }
+}
diff --git a/test/System.Web.Helpers.Test/ObjectInfoTest.cs b/test/System.Web.Helpers.Test/ObjectInfoTest.cs
new file mode 100644
index 00000000..f93d471a
--- /dev/null
+++ b/test/System.Web.Helpers.Test/ObjectInfoTest.cs
@@ -0,0 +1,728 @@
+using System.Collections;
+using System.Collections.Generic;
+using System.Collections.Specialized;
+using System.Dynamic;
+using System.Linq;
+using System.Web.TestUtil;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Helpers.Test
+{
+ public class ObjectInfoTest
+ {
+ [Fact]
+ public void PrintWithNegativeDepthThrows()
+ {
+ // Act & Assert
+ Assert.ThrowsArgumentGreaterThanOrEqualTo(() => ObjectInfo.Print(null, depth: -1), "depth", "0");
+ }
+
+ [Fact]
+ public void PrintWithInvalidEnumerationLength()
+ {
+ // Act & Assert
+ Assert.ThrowsArgumentGreaterThan(() => ObjectInfo.Print(null, enumerationLength: -1), "enumerationLength", "0");
+ }
+
+ [Fact]
+ public void PrintWithNull()
+ {
+ // Arrange
+ MockObjectVisitor visitor = CreateObjectVisitor();
+
+ // Act
+ visitor.Print(null);
+
+ // Assert
+ Assert.Equal(1, visitor.Values.Count);
+ Assert.Equal("null", visitor.Values[0]);
+ }
+
+ [Fact]
+ public void PrintWithEmptyString()
+ {
+ // Arrange
+ MockObjectVisitor visitor = CreateObjectVisitor();
+
+ // Act
+ visitor.Print(String.Empty);
+
+ // Assert
+ Assert.Equal(1, visitor.Values.Count);
+ Assert.Equal(String.Empty, visitor.Values[0]);
+ }
+
+ [Fact]
+ public void PrintWithInt()
+ {
+ // Arrange
+ MockObjectVisitor visitor = CreateObjectVisitor();
+
+ // Act
+ visitor.Print(404);
+
+ // Assert
+ Assert.Equal(1, visitor.Values.Count);
+ Assert.Equal("404", visitor.Values[0]);
+ }
+
+ [Fact]
+ public void PrintWithIDictionary()
+ {
+ // Arrange
+ MockObjectVisitor visitor = CreateObjectVisitor();
+ IDictionary dict = new OrderedDictionary();
+ dict.Add("foo", "bar");
+ dict.Add("abc", 500);
+
+ // Act
+ visitor.Print(dict);
+
+ // Assert
+ Assert.Equal("foo = bar", visitor.KeyValuePairs[0]);
+ Assert.Equal("abc = 500", visitor.KeyValuePairs[1]);
+ }
+
+ [Fact]
+ public void PrintWithIEnumerable()
+ {
+ // Arrange
+ MockObjectVisitor visitor = CreateObjectVisitor();
+ var values = Enumerable.Range(0, 10);
+
+ // Act
+ visitor.Print(values);
+
+ // Assert
+ foreach (var num in values)
+ {
+ Assert.True(visitor.Values.Contains(num.ToString()));
+ }
+ }
+
+ [Fact]
+ public void PrintWithGenericIListPrintsIndex()
+ {
+ // Arrange
+ MockObjectVisitor visitor = CreateObjectVisitor();
+ var values = Enumerable.Range(0, 10).ToList();
+
+ // Act
+ visitor.Print(values);
+
+ // Assert
+ for (int i = 0; i < values.Count; i++)
+ {
+ Assert.True(visitor.Values.Contains(values[i].ToString()));
+ Assert.True(visitor.Indexes.Contains(i));
+ }
+ }
+
+ [Fact]
+ public void PrintWithArrayPrintsIndex()
+ {
+ // Arrange
+ MockObjectVisitor visitor = CreateObjectVisitor();
+ var values = Enumerable.Range(0, 10).ToArray();
+
+ // Act
+ visitor.Print(values);
+
+ // Assert
+ for (int i = 0; i < values.Length; i++)
+ {
+ Assert.True(visitor.Values.Contains(values[i].ToString()));
+ Assert.True(visitor.Indexes.Contains(i));
+ }
+ }
+
+ [Fact]
+ public void PrintNameValueCollectionPrintsKeysAndValues()
+ {
+ // Arrange
+ MockObjectVisitor visitor = CreateObjectVisitor();
+ var values = new NameValueCollection();
+ values["a"] = "1";
+ values["b"] = null;
+
+ // Act
+ visitor.Print(values);
+
+ // Assert
+ Assert.Equal("a = 1", visitor.KeyValuePairs[0]);
+ Assert.Equal("b = null", visitor.KeyValuePairs[1]);
+ }
+
+ [Fact]
+ public void PrintDateTime()
+ {
+ using (new CultureReplacer())
+ {
+ // Arrange
+ MockObjectVisitor visitor = CreateObjectVisitor();
+ var dt = new DateTime(2001, 11, 20, 10, 30, 1);
+
+ // Act
+ visitor.Print(dt);
+
+ // Assert
+ Assert.Equal("11/20/2001 10:30:01 AM", visitor.Values[0]);
+ }
+ }
+
+ [Fact]
+ public void PrintCustomObjectPrintsMembers()
+ {
+ // Arrange
+ MockObjectVisitor visitor = CreateObjectVisitor();
+ var person = new Person
+ {
+ Name = "David",
+ Age = 23.3,
+ Dob = new DateTime(1986, 11, 19),
+ LongType = 1000000000,
+ Type = 1
+ };
+
+ using (new CultureReplacer())
+ {
+ // Act
+ visitor.Print(person);
+
+ // Assert
+ Assert.Equal(9, visitor.Members.Count);
+ Assert.True(visitor.Members.Contains("double Age = 23.3"));
+ Assert.True(visitor.Members.Contains("string Name = David"));
+ Assert.True(visitor.Members.Contains("DateTime Dob = 11/19/1986 12:00:00 AM"));
+ Assert.True(visitor.Members.Contains("short Type = 1"));
+ Assert.True(visitor.Members.Contains("float Float = 0"));
+ Assert.True(visitor.Members.Contains("byte Byte = 0"));
+ Assert.True(visitor.Members.Contains("decimal Decimal = 0"));
+ Assert.True(visitor.Members.Contains("bool Bool = False"));
+ }
+ }
+
+ [Fact]
+ public void PrintShowsVisitedWhenCircularReferenceInObjectGraph()
+ {
+ // Arrange
+ MockObjectVisitor visitor = CreateObjectVisitor();
+ PersonNode node = new PersonNode
+ {
+ Person = new Person
+ {
+ Name = "David",
+ Age = 23.3
+ }
+ };
+ node.Next = node;
+
+ // Act
+ visitor.Print(node);
+
+ // Assert
+ Assert.True(visitor.Members.Contains("string Name = David"));
+ Assert.True(visitor.Members.Contains("double Age = 23.3"));
+ Assert.True(visitor.Members.Contains("PersonNode Next = Visited"));
+ }
+
+ [Fact]
+ public void PrintShowsVisitedWhenCircularReferenceIsIEnumerable()
+ {
+ // Arrange
+ MockObjectVisitor visitor = CreateObjectVisitor();
+ List<object> values = new List<object>();
+ values.Add(values);
+
+ // Act
+ visitor.Print(values);
+
+ // Assert
+ Assert.Equal("Visited", visitor.Values[0]);
+ Assert.Equal("Visited " + values.GetHashCode(), visitor.Visited[0]);
+ }
+
+ [Fact]
+ public void PrintShowsVisitedWhenCircularReferenceIsIDictionary()
+ {
+ // Arrange
+ MockObjectVisitor visitor = CreateObjectVisitor();
+ OrderedDictionary values = new OrderedDictionary();
+ values[values] = values;
+
+ // Act
+ visitor.Print(values);
+
+ // Assert
+ Assert.Equal("Visited", visitor.Values[0]);
+ Assert.Equal("Visited " + values.GetHashCode(), visitor.Visited[0]);
+ }
+
+ [Fact]
+ public void PrintShowsVisitedWhenCircularReferenceIsNameValueCollection()
+ {
+ // Arrange
+ MockObjectVisitor visitor = CreateObjectVisitor();
+ NameValueCollection nameValues = new NameValueCollection();
+ nameValues["id"] = "1";
+ List<NameValueCollection> values = new List<NameValueCollection>();
+ values.Add(nameValues);
+ values.Add(nameValues);
+
+ // Act
+ visitor.Print(values);
+
+ // Assert
+ Assert.True(visitor.Values.Contains("Visited"));
+ Assert.True(visitor.Visited.Contains("Visited " + nameValues.GetHashCode()));
+ }
+
+ [Fact]
+ public void PrintExcludesWriteOnlyProperties()
+ {
+ // Arrange
+ MockObjectVisitor visitor = CreateObjectVisitor();
+ ClassWithWriteOnlyProperty cls = new ClassWithWriteOnlyProperty();
+
+ // Act
+ visitor.Print(cls);
+
+ // Assert
+ Assert.Equal(0, visitor.Members.Count);
+ }
+
+ [Fact]
+ public void PrintWritesEnumeratedElementsUntilLimitIsHit()
+ {
+ // Arrange
+ MockObjectVisitor visitor = CreateObjectVisitor();
+ var enumeration = Enumerable.Range(0, 2000);
+
+ // Act
+ visitor.Print(enumeration);
+
+ // Assert
+ for (int i = 0; i <= 2000; i++)
+ {
+ if (i < 1000)
+ {
+ Assert.True(visitor.Values.Contains(i.ToString()));
+ }
+ else
+ {
+ Assert.False(visitor.Values.Contains(i.ToString()));
+ }
+ }
+ Assert.True(visitor.Values.Contains("Limit Exceeded"));
+ }
+
+ [Fact]
+ public void PrintWithAnonymousType()
+ {
+ // Arrange
+ MockObjectVisitor visitor = CreateObjectVisitor();
+ var value = new { Name = "John", X = 1 };
+
+ // Act
+ visitor.Print(value);
+
+ // Assert
+ Assert.True(visitor.Members.Contains("string Name = John"));
+ Assert.True(visitor.Members.Contains("int X = 1"));
+ }
+
+ [Fact]
+ public void PrintClassWithPublicFields()
+ {
+ // Arrange
+ MockObjectVisitor visitor = CreateObjectVisitor();
+ ClassWithFields value = new ClassWithFields();
+ value.Foo = "John";
+ value.Bar = 1;
+
+ // Actt
+ visitor.Print(value);
+
+ // Assert
+ Assert.True(visitor.Members.Contains("string Foo = John"));
+ Assert.True(visitor.Members.Contains("int Bar = 1"));
+ }
+
+ [Fact]
+ public void PrintClassWithDynamicMembersPrintsMembersIfGetDynamicMemberNamesIsImplemented()
+ {
+ // Arrange
+ MockObjectVisitor visitor = CreateObjectVisitor();
+ dynamic d = new DynamicDictionary();
+ d.Cycle = d;
+ d.Name = "Foo";
+ d.Value = null;
+
+ // Act
+ visitor.Print(d);
+
+ // Assert
+ Assert.True(visitor.Members.Contains("DynamicDictionary Cycle = Visited"));
+ Assert.True(visitor.Members.Contains("string Name = Foo"));
+ Assert.True(visitor.Members.Contains("Value = null"));
+ }
+
+ [Fact]
+ public void PrintClassWithDynamicMembersReturningNullPrintsNoMembers()
+ {
+ // Arrange
+ MockObjectVisitor visitor = CreateObjectVisitor();
+ dynamic d = new ClassWithDynamicAnNullMemberNames();
+ d.Cycle = d;
+ d.Name = "Foo";
+ d.Value = null;
+
+ // Act
+ visitor.Print(d);
+
+ // Assert
+ Assert.False(visitor.Members.Any());
+ }
+
+ [Fact]
+ public void PrintUsesToStringOfIConvertibleObjects()
+ {
+ // Arrange
+ MockObjectVisitor visitor = CreateObjectVisitor();
+ ConvertibleClass cls = new ConvertibleClass();
+
+ // Act
+ visitor.Print(cls);
+
+ // Assert
+ Assert.Equal("Test", visitor.Values[0]);
+ }
+
+ [Fact]
+ public void PrintConvertsTypeToString()
+ {
+ // Arrange
+ MockObjectVisitor visitor = CreateObjectVisitor();
+
+ // Act
+ visitor.Print(typeof(string));
+
+ // Assert
+ Assert.Equal("typeof(string)", visitor.Values[0]);
+ }
+
+ [Fact]
+ public void PrintClassWithPropertyThatThrowsExceptionPrintsException()
+ {
+ // Arrange
+ MockObjectVisitor visitor = CreateObjectVisitor();
+ ClassWithPropertyThatThrowsException value = new ClassWithPropertyThatThrowsException();
+
+ // Act
+ visitor.Print(value);
+
+ // Assert
+ Assert.Equal("int MyProperty = Property accessor 'MyProperty' on object 'System.Web.Helpers.Test.ObjectInfoTest+ClassWithPropertyThatThrowsException' threw the following exception:'Property that shows an exception'", visitor.Members[0]);
+ }
+
+ [Fact]
+ public void ConvertEscapeSequencesPrintsStringEscapeSequencesAsLiterals()
+ {
+ // Act
+ string value = HtmlObjectPrinter.ConvertEscapseSequences("\\\'\"\0\a\b\f\n\r\t\v");
+
+ // Assert
+ Assert.Equal("\\\\'\\\"\\0\\a\\b\\f\\n\\r\\t\\v", value);
+ }
+
+ [Fact]
+ public void ConvertEscapeSequencesDoesNotEscapeUnicodeSequences()
+ {
+ // Act
+ string value = HtmlObjectPrinter.ConvertEscapseSequences("\u1023\x2045");
+
+ // Assert
+ Assert.Equal("\u1023\x2045", value);
+ }
+
+ [Fact]
+ public void PrintCharPrintsQuotedString()
+ {
+ // Arrange
+ HtmlObjectPrinter printer = new HtmlObjectPrinter(100, 100);
+ HtmlElement element = new HtmlElement("span");
+ printer.PushElement(element);
+
+ // Act
+ printer.VisitConvertedValue('x', "x");
+
+ // Assert
+ Assert.Equal(1, element.Children.Count);
+ HtmlElement child = element.Children[0];
+ Assert.Equal("'x'", child.InnerText);
+ Assert.Equal("quote", child["class"]);
+ }
+
+ [Fact]
+ public void PrintEscapeCharPrintsEscapedCharAsLiteral()
+ {
+ // Arrange
+ HtmlObjectPrinter printer = new HtmlObjectPrinter(100, 100);
+ HtmlElement element = new HtmlElement("span");
+ printer.PushElement(element);
+
+ // Act
+ printer.VisitConvertedValue('\t', "\t");
+
+ // Assert
+ Assert.Equal(1, element.Children.Count);
+ HtmlElement child = element.Children[0];
+ Assert.Equal("'\\t'", child.InnerText);
+ Assert.Equal("quote", child["class"]);
+ }
+
+ [Fact]
+ public void GetTypeNameConvertsGenericTypesToCsharpSyntax()
+ {
+ // Act
+ string value = ObjectVisitor.GetTypeName(typeof(Func<Func<Func<int, int, object>, Action<int>>>));
+
+ // Assert
+ Assert.Equal("Func<Func<Func<int, int, object>, Action<int>>>", value);
+ }
+
+ private class ConvertibleClass : IConvertible
+ {
+ public TypeCode GetTypeCode()
+ {
+ throw new NotImplementedException();
+ }
+
+ public bool ToBoolean(IFormatProvider provider)
+ {
+ throw new NotImplementedException();
+ }
+
+ public byte ToByte(IFormatProvider provider)
+ {
+ throw new NotImplementedException();
+ }
+
+ public char ToChar(IFormatProvider provider)
+ {
+ throw new NotImplementedException();
+ }
+
+ public DateTime ToDateTime(IFormatProvider provider)
+ {
+ throw new NotImplementedException();
+ }
+
+ public decimal ToDecimal(IFormatProvider provider)
+ {
+ throw new NotImplementedException();
+ }
+
+ public double ToDouble(IFormatProvider provider)
+ {
+ throw new NotImplementedException();
+ }
+
+ public short ToInt16(IFormatProvider provider)
+ {
+ throw new NotImplementedException();
+ }
+
+ public int ToInt32(IFormatProvider provider)
+ {
+ throw new NotImplementedException();
+ }
+
+ public long ToInt64(IFormatProvider provider)
+ {
+ throw new NotImplementedException();
+ }
+
+ public sbyte ToSByte(IFormatProvider provider)
+ {
+ throw new NotImplementedException();
+ }
+
+ public float ToSingle(IFormatProvider provider)
+ {
+ throw new NotImplementedException();
+ }
+
+ public string ToString(IFormatProvider provider)
+ {
+ return "Test";
+ }
+
+ public object ToType(Type conversionType, IFormatProvider provider)
+ {
+ throw new NotImplementedException();
+ }
+
+ public ushort ToUInt16(IFormatProvider provider)
+ {
+ throw new NotImplementedException();
+ }
+
+ public uint ToUInt32(IFormatProvider provider)
+ {
+ throw new NotImplementedException();
+ }
+
+ public ulong ToUInt64(IFormatProvider provider)
+ {
+ throw new NotImplementedException();
+ }
+ }
+
+ private class ClassWithPropertyThatThrowsException
+ {
+ public int MyProperty
+ {
+ get { throw new InvalidOperationException("Property that shows an exception"); }
+ }
+ }
+
+ private class ClassWithDynamicAnNullMemberNames : DynamicObject
+ {
+ public override IEnumerable<string> GetDynamicMemberNames()
+ {
+ return null;
+ }
+
+ public override bool TryGetMember(GetMemberBinder binder, out object result)
+ {
+ result = null;
+ return true;
+ }
+
+ public override bool TrySetMember(SetMemberBinder binder, object value)
+ {
+ return true;
+ }
+ }
+
+ private class Person
+ {
+ public string Name { get; set; }
+ public double Age { get; set; }
+ public DateTime Dob { get; set; }
+ public short Type { get; set; }
+ public long LongType { get; set; }
+ public float Float { get; set; }
+ public byte Byte { get; set; }
+ public decimal Decimal { get; set; }
+ public bool Bool { get; set; }
+ }
+
+ private class ClassWithFields
+ {
+ public string Foo;
+ public int Bar = 13;
+ }
+
+ private class ClassWithWriteOnlyProperty
+ {
+ public int Value
+ {
+ set { }
+ }
+ }
+
+ private class PersonNode
+ {
+ public Person Person { get; set; }
+ public PersonNode Next { get; set; }
+ }
+
+ private MockObjectVisitor CreateObjectVisitor(int recursionLimit = 10, int enumerationLimit = 1000)
+ {
+ return new MockObjectVisitor(recursionLimit, enumerationLimit);
+ }
+
+ private class MockObjectVisitor : ObjectVisitor
+ {
+ public MockObjectVisitor(int recursionLimit, int enumerationLimit)
+ : base(recursionLimit, enumerationLimit)
+ {
+ Values = new List<string>();
+ KeyValuePairs = new List<string>();
+ Members = new List<string>();
+ Indexes = new List<int>();
+ Visited = new List<string>();
+ }
+
+ public List<string> Values { get; set; }
+ public List<string> KeyValuePairs { get; set; }
+ public List<string> Members { get; set; }
+ public List<int> Indexes { get; set; }
+ public List<string> Visited { get; set; }
+
+ public void Print(object value)
+ {
+ Visit(value, 0);
+ }
+
+ public override void VisitObjectVisitorException(ObjectVisitorException exception)
+ {
+ Values.Add(exception.InnerException.Message);
+ }
+
+ public override void VisitStringValue(string stringValue)
+ {
+ Values.Add(stringValue);
+ base.VisitStringValue(stringValue);
+ }
+
+ public override void VisitVisitedObject(string id, object value)
+ {
+ Visited.Add(String.Format("Visited {0}", id));
+ Values.Add("Visited");
+ base.VisitVisitedObject(id, value);
+ }
+
+ public override void VisitIndexedEnumeratedValue(int index, object item, int depth)
+ {
+ Indexes.Add(index);
+ base.VisitIndexedEnumeratedValue(index, item, depth);
+ }
+
+ public override void VisitEnumeratonLimitExceeded()
+ {
+ Values.Add("Limit Exceeded");
+ base.VisitEnumeratonLimitExceeded();
+ }
+
+ public override void VisitMember(string name, Type type, object value, int depth)
+ {
+ base.VisitMember(name, type, value, depth);
+ type = type ?? (value != null ? value.GetType() : null);
+ if (type == null)
+ {
+ Members.Add(String.Format("{0} = null", name));
+ }
+ else
+ {
+ Members.Add(String.Format("{0} {1} = {2}", GetTypeName(type), name, Values.Last()));
+ }
+ }
+
+ public override void VisitNull()
+ {
+ Values.Add("null");
+ base.VisitNull();
+ }
+
+ public override void VisitKeyValue(object key, object value, int depth)
+ {
+ base.VisitKeyValue(key, value, depth);
+ KeyValuePairs.Add(String.Format("{0} = {1}", Values[Values.Count - 2], Values[Values.Count - 1]));
+ }
+ }
+ }
+}
diff --git a/test/System.Web.Helpers.Test/PreComputedGridDataSourceTest.cs b/test/System.Web.Helpers.Test/PreComputedGridDataSourceTest.cs
new file mode 100644
index 00000000..bf33b2c2
--- /dev/null
+++ b/test/System.Web.Helpers.Test/PreComputedGridDataSourceTest.cs
@@ -0,0 +1,41 @@
+using System.Linq;
+using Moq;
+using Xunit;
+
+namespace System.Web.Helpers.Test
+{
+ public class PreComputedGridDataSourceTest
+ {
+ [Fact]
+ public void PreSortedDataSourceReturnsRowCountItWasSpecified()
+ {
+ // Arrange
+ int rows = 20;
+ var dataSource = new PreComputedGridDataSource(new WebGrid(GetContext()), values: Enumerable.Range(0, 10).Cast<dynamic>(), totalRows: rows);
+
+ // Act and Assert
+ Assert.Equal(rows, dataSource.TotalRowCount);
+ }
+
+ [Fact]
+ public void PreSortedDataSourceReturnsAllRows()
+ {
+ // Arrange
+ var grid = new WebGrid(GetContext());
+ var dataSource = new PreComputedGridDataSource(grid: grid, values: Enumerable.Range(0, 10).Cast<dynamic>(), totalRows: 10);
+
+ // Act
+ var rows = dataSource.GetRows(new SortInfo { SortColumn = String.Empty }, 0);
+
+ // Assert
+ Assert.Equal(rows.Count, 10);
+ Assert.Equal(rows.First().Value, 0);
+ Assert.Equal(rows.Last().Value, 9);
+ }
+
+ private HttpContextBase GetContext()
+ {
+ return new Mock<HttpContextBase>().Object;
+ }
+ }
+}
diff --git a/test/System.Web.Helpers.Test/Properties/AssemblyInfo.cs b/test/System.Web.Helpers.Test/Properties/AssemblyInfo.cs
new file mode 100644
index 00000000..6fdbb42e
--- /dev/null
+++ b/test/System.Web.Helpers.Test/Properties/AssemblyInfo.cs
@@ -0,0 +1,34 @@
+using System.Reflection;
+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("System.Web.Helpers.Test")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("MSIT")]
+[assembly: AssemblyProduct("System.Web.Helpers.Test")]
+[assembly: AssemblyCopyright("Copyright © MSIT 2010")]
+[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)]
+
+// 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/test/System.Web.Helpers.Test/ServerInfoTest.cs b/test/System.Web.Helpers.Test/ServerInfoTest.cs
new file mode 100644
index 00000000..b783ac17
--- /dev/null
+++ b/test/System.Web.Helpers.Test/ServerInfoTest.cs
@@ -0,0 +1,162 @@
+using System.Collections.Generic;
+using System.Collections.Specialized;
+using System.Web.WebPages.TestUtils;
+using Moq;
+using Xunit;
+
+namespace System.Web.Helpers.Test
+{
+ public class InfoTest
+ {
+ [Fact]
+ public void ConfigurationReturnsExpectedInfo()
+ {
+ var configInfo = ServerInfo.Configuration();
+
+ // verification
+ // checks only subset of values
+ Assert.NotNull(configInfo);
+ VerifyKey(configInfo, "Machine Name");
+ VerifyKey(configInfo, "OS Version");
+ VerifyKey(configInfo, "ASP.NET Version");
+ }
+
+ [Fact]
+ public void EnvironmentVariablesReturnsExpectedInfo()
+ {
+ var envVariables = ServerInfo.EnvironmentVariables();
+
+ // verification
+ // checks only subset of values
+ Assert.NotNull(envVariables);
+ VerifyKey(envVariables, "Path");
+ VerifyKey(envVariables, "SystemDrive");
+ }
+
+ [Fact]
+ public void ServerVariablesReturnsExpectedInfoWithNoContext()
+ {
+ var serverVariables = ServerInfo.ServerVariables();
+
+ // verification
+ // since there is no HttpContext this will be empty
+ Assert.NotNull(serverVariables);
+ }
+
+ [Fact]
+ public void ServerVariablesReturnsExpectedInfoWthContext()
+ {
+ var serverVariables = new NameValueCollection();
+ serverVariables.Add("foo", "bar");
+
+ var request = new Mock<HttpRequestBase>();
+ request.Setup(c => c.ServerVariables).Returns(serverVariables);
+
+ var context = new Mock<HttpContextBase>();
+ context.Setup(c => c.Request).Returns(request.Object);
+
+ // verification
+ Assert.NotNull(serverVariables);
+
+ IDictionary<string, string> returnedValues = ServerInfo.ServerVariables(context.Object);
+ Assert.Equal(serverVariables.Count, returnedValues.Count);
+ foreach (var item in returnedValues)
+ {
+ Assert.Equal(serverVariables[item.Key], item.Value);
+ }
+ }
+
+ [Fact]
+ public void HttpRuntimeInfoReturnsExpectedInfo()
+ {
+ var httpRuntimeInfo = ServerInfo.HttpRuntimeInfo();
+
+ // verification
+ // checks only subset of values
+ Assert.NotNull(httpRuntimeInfo);
+ VerifyKey(httpRuntimeInfo, "CLR Install Directory");
+ VerifyKey(httpRuntimeInfo, "Asp Install Directory");
+ VerifyKey(httpRuntimeInfo, "On UNC Share");
+ }
+
+ [Fact]
+ public void ServerInfoDoesNotProduceLegacyCasForHomogenousAppDomain()
+ {
+ // Act and Assert
+ Action action = () =>
+ {
+ IDictionary<string, string> configValue = ServerInfo.LegacyCAS(AppDomain.CurrentDomain);
+
+ Assert.NotNull(configValue);
+ Assert.Equal(0, configValue.Count);
+ };
+
+ AppDomainUtils.RunInSeparateAppDomain(GetAppDomainSetup(legacyCasEnabled: false), action);
+ }
+
+ [Fact]
+ public void ServerInfoProducesLegacyCasForNonHomogenousAppDomain()
+ {
+ // Arrange
+ Action action = () =>
+ {
+ // Act and Assert
+ IDictionary<string, string> configValue = ServerInfo.LegacyCAS(AppDomain.CurrentDomain);
+
+ // Assert
+ Assert.True(configValue.ContainsKey("Legacy Code Access Security"));
+ Assert.Equal(configValue["Legacy Code Access Security"], "Legacy Code Access Security has been detected on your system. Microsoft WebPage features require the ASP.NET 4 Code Access Security model. For information about how to resolve this, contact your server administrator.");
+ };
+
+ AppDomainUtils.RunInSeparateAppDomain(GetAppDomainSetup(legacyCasEnabled: true), action);
+ }
+
+ //[Fact]
+ //public void SqlServerInfoReturnsExpectedInfo() {
+ // var sqlInfo = ServerInfo.SqlServerInfo();
+
+ // // verification
+ // // just verifies that we don't get any unexpected exceptions
+ // Assert.NotNull(sqlInfo);
+ //}
+
+ [Fact]
+ public void RenderResultContainsExpectedTags()
+ {
+ var htmlString = ServerInfo.GetHtml().ToString();
+
+ // just verify that the final HTML produced contains some expected info
+ Assert.True(htmlString.Contains("<table class=\"server-info\" dir=\"ltr\">"));
+ Assert.True(htmlString.Contains("</style>"));
+ Assert.True(htmlString.Contains("Server Configuration"));
+ }
+
+ [Fact]
+ public void RenderGeneratesValidXhtml()
+ {
+ // Result does not validate against XHTML 1.1 and HTML5 because ServerInfo generates
+ // <style> inside <body>. This is by design however since we only use ServerInfo
+ // as debugging aid, not something to be permanently added to a web page.
+ XhtmlAssert.Validate1_0(
+ ServerInfo.GetHtml(),
+ addRoot: true
+ );
+ }
+
+ private void VerifyKey(IDictionary<string, string> info, string key)
+ {
+ Assert.True(info.ContainsKey(key));
+ Assert.False(String.IsNullOrEmpty(info[key]));
+ }
+
+ private AppDomainSetup GetAppDomainSetup(bool legacyCasEnabled)
+ {
+ var setup = new AppDomainSetup();
+ if (legacyCasEnabled)
+ {
+ setup.SetCompatibilitySwitches(new[] { "NetFx40_LegacySecurityPolicy" });
+ }
+ return setup;
+ }
+ }
+}
diff --git a/test/System.Web.Helpers.Test/System.Web.Helpers.Test.csproj b/test/System.Web.Helpers.Test/System.Web.Helpers.Test.csproj
new file mode 100644
index 00000000..40487ed7
--- /dev/null
+++ b/test/System.Web.Helpers.Test/System.Web.Helpers.Test.csproj
@@ -0,0 +1,106 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory),Runtime.sln))\tools\WebStack.settings.targets" />
+ <PropertyGroup>
+ <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+ <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+ <ProductVersion>9.0.30729</ProductVersion>
+ <SchemaVersion>2.0</SchemaVersion>
+ <ProjectGuid>{D3313BDF-8071-4AC8-9D98-ABF7F9E88A57}</ProjectGuid>
+ <OutputType>Library</OutputType>
+ <AppDesignerFolder>Properties</AppDesignerFolder>
+ <RootNamespace>System.Web.Helpers.Test</RootNamespace>
+ <AssemblyName>System.Web.Helpers.Test</AssemblyName>
+ <FileAlignment>512</FileAlignment>
+ <ProjectTypeGuids>{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
+ <ErrorReport>prompt</ErrorReport>
+ <WarningLevel>4</WarningLevel>
+ <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
+ <DebugSymbols>true</DebugSymbols>
+ <DebugType>full</DebugType>
+ <Optimize>false</Optimize>
+ <OutputPath>$(WebStackRootPath)\bin\Debug\Test\</OutputPath>
+ <DefineConstants>TRACE;DEBUG</DefineConstants>
+ <CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
+ <DebugType>pdbonly</DebugType>
+ <Optimize>true</Optimize>
+ <OutputPath>$(WebStackRootPath)\bin\Release\Test\</OutputPath>
+ <DefineConstants>TRACE</DefineConstants>
+ <CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)' == 'CodeCoverage' ">
+ <DebugSymbols>true</DebugSymbols>
+ <DebugType>full</DebugType>
+ <Optimize>false</Optimize>
+ <OutputPath>$(WebStackRootPath)\bin\CodeCoverage\Test\</OutputPath>
+ <DefineConstants>TRACE;DEBUG</DefineConstants>
+ <CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
+ </PropertyGroup>
+ <ItemGroup>
+ <Reference Include="Moq, Version=4.0.10827.0, Culture=neutral, PublicKeyToken=69f491c39445e920, processorArchitecture=MSIL">
+ <HintPath>..\..\packages\Moq.4.0.10827\lib\NET40\Moq.dll</HintPath>
+ </Reference>
+ <Reference Include="System" />
+ <Reference Include="System.Core" />
+ <Reference Include="System.Drawing" />
+ <Reference Include="System.Web" />
+ <Reference Include="System.Web.DataVisualization" />
+ <Reference Include="Microsoft.CSharp" />
+ <Reference Include="System.XML" />
+ <Reference Include="xunit">
+ <HintPath>..\..\packages\xunit.1.9.0.1566\lib\xunit.dll</HintPath>
+ </Reference>
+ <Reference Include="xunit.extensions">
+ <HintPath>..\..\packages\xunit.extensions.1.9.0.1566\lib\xunit.extensions.dll</HintPath>
+ </Reference>
+ </ItemGroup>
+ <ItemGroup>
+ <Compile Include="ChartTest.cs" />
+ <Compile Include="ConversionUtilTest.cs" />
+ <Compile Include="CryptoTest.cs" />
+ <Compile Include="DynamicDictionary.cs" />
+ <Compile Include="DynamicHelperTest.cs" />
+ <Compile Include="DynamicWrapper.cs" />
+ <Compile Include="JsonTest.cs" />
+ <Compile Include="ObjectInfoTest.cs" />
+ <Compile Include="PreComputedGridDataSourceTest.cs" />
+ <Compile Include="WebCacheTest.cs" />
+ <Compile Include="HelperResultTest.cs" />
+ <Compile Include="Properties\AssemblyInfo.cs" />
+ <Compile Include="ServerInfoTest.cs" />
+ <Compile Include="WebGridDataSourceTest.cs" />
+ <Compile Include="WebGridTest.cs" />
+ <Compile Include="WebImageTest.cs" />
+ <Compile Include="WebMailTest.cs" />
+ <Compile Include="XhtmlAssert.cs" />
+ </ItemGroup>
+ <ItemGroup>
+ <ProjectReference Include="..\..\src\System.Web.WebPages\System.Web.WebPages.csproj">
+ <Project>{76EFA9C5-8D7E-4FDF-B710-E20F8B6B00D2}</Project>
+ <Name>System.Web.WebPages</Name>
+ </ProjectReference>
+ <ProjectReference Include="..\..\src\System.Web.Helpers\System.Web.Helpers.csproj">
+ <Project>{9B7E3740-6161-4548-833C-4BBCA43B970E}</Project>
+ <Name>System.Web.Helpers</Name>
+ </ProjectReference>
+ <ProjectReference Include="..\Microsoft.TestCommon\Microsoft.TestCommon.csproj">
+ <Project>{FCCC4CB7-BAF7-4A57-9F89-E5766FE536C0}</Project>
+ <Name>Microsoft.TestCommon</Name>
+ </ProjectReference>
+ </ItemGroup>
+ <ItemGroup>
+ <EmbeddedResource Include="TestFiles\HiRes.jpg" />
+ <EmbeddedResource Include="TestFiles\LambdaFinal.jpg" />
+ <EmbeddedResource Include="TestFiles\logo.bmp" />
+ <EmbeddedResource Include="TestFiles\NETLogo.png" />
+ <EmbeddedResource Include="TestFiles\xhtml11-flat.dtd" />
+ </ItemGroup>
+ <ItemGroup>
+ <None Include="packages.config" />
+ </ItemGroup>
+ <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
+</Project> \ No newline at end of file
diff --git a/test/System.Web.Helpers.Test/TestFiles/HiRes.jpg b/test/System.Web.Helpers.Test/TestFiles/HiRes.jpg
new file mode 100644
index 00000000..d3d8e5b1
--- /dev/null
+++ b/test/System.Web.Helpers.Test/TestFiles/HiRes.jpg
Binary files differ
diff --git a/test/System.Web.Helpers.Test/TestFiles/LambdaFinal.jpg b/test/System.Web.Helpers.Test/TestFiles/LambdaFinal.jpg
new file mode 100644
index 00000000..9f32884f
--- /dev/null
+++ b/test/System.Web.Helpers.Test/TestFiles/LambdaFinal.jpg
Binary files differ
diff --git a/test/System.Web.Helpers.Test/TestFiles/NETLogo.png b/test/System.Web.Helpers.Test/TestFiles/NETLogo.png
new file mode 100644
index 00000000..9749ab86
--- /dev/null
+++ b/test/System.Web.Helpers.Test/TestFiles/NETLogo.png
Binary files differ
diff --git a/test/System.Web.Helpers.Test/TestFiles/logo.bmp b/test/System.Web.Helpers.Test/TestFiles/logo.bmp
new file mode 100644
index 00000000..6c70a296
--- /dev/null
+++ b/test/System.Web.Helpers.Test/TestFiles/logo.bmp
Binary files differ
diff --git a/test/System.Web.Helpers.Test/TestFiles/xhtml11-flat.dtd b/test/System.Web.Helpers.Test/TestFiles/xhtml11-flat.dtd
new file mode 100644
index 00000000..b9f5881e
--- /dev/null
+++ b/test/System.Web.Helpers.Test/TestFiles/xhtml11-flat.dtd
@@ -0,0 +1,4513 @@
+<!-- ....................................................................... -->
+<!-- XHTML 1.1 DTD ........................................................ -->
+<!-- file: xhtml11.dtd
+-->
+
+<!-- XHTML 1.1 DTD
+
+ This is XHTML, a reformulation of HTML as a modular XML application.
+
+ The Extensible HyperText Markup Language (XHTML)
+ Copyright 1998-2000 World Wide Web Consortium
+ (Massachusetts Institute of Technology, Institut National de
+ Recherche en Informatique et en Automatique, Keio University).
+ All Rights Reserved.
+
+ Permission to use, copy, modify and distribute the XHTML DTD and its
+ accompanying documentation for any purpose and without fee is hereby
+ granted in perpetuity, provided that the above copyright notice and
+ this paragraph appear in all copies. The copyright holders make no
+ representation about the suitability of the DTD for any purpose.
+
+ It is provided "as is" without expressed or implied warranty.
+
+ Author: Murray M. Altheim <altheim@eng.sun.com>
+ Revision: $Id: xhtml11.dtd,v 1.20 2001/04/05 14:20:51 ahby Exp $
+
+-->
+<!-- This is the driver file for version 1.1 of the XHTML DTD.
+
+ Please use this formal public identifier to identify it:
+
+ "-//W3C//DTD XHTML 1.1//EN"
+-->
+<!ENTITY % XHTML.version "-//W3C//DTD XHTML 1.1//EN" >
+
+<!-- Use this URI to identify the default namespace:
+
+ "http://www.w3.org/1999/xhtml"
+
+ See the Qualified Names module for information
+ on the use of namespace prefixes in the DTD.
+-->
+<!ENTITY % NS.prefixed "IGNORE" >
+<!ENTITY % XHTML.prefix "" >
+
+<!-- Reserved for use with the XLink namespace:
+-->
+<!ENTITY % XLINK.xmlns "" >
+<!ENTITY % XLINK.xmlns.attrib "" >
+
+<!-- For example, if you are using XHTML 1.1 directly, use the FPI
+ in the DOCTYPE declaration, with the xmlns attribute on the
+ document element to identify the default namespace:
+
+ <?xml version="1.0"?>
+ <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "xhtml11.dtd">
+ <html xmlns="http://www.w3.org/1999/xhtml"
+ xml:lang="en">
+ ...
+ </html>
+
+ Revisions:
+ (none)
+-->
+
+<!-- reserved for future use with document profiles -->
+<!ENTITY % XHTML.profile "" >
+
+<!-- Bidirectional Text features
+ This feature-test entity is used to declare elements
+ and attributes used for bidirectional text support.
+-->
+<!ENTITY % XHTML.bidi "INCLUDE" >
+
+<?doc type="doctype" role="title" { XHTML 1.1 } ?>
+
+<!-- ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: -->
+
+<!-- Pre-Framework Redeclaration placeholder .................... -->
+<!-- this serves as a location to insert markup declarations
+ into the DTD prior to the framework declarations.
+-->
+<!ENTITY % xhtml-prefw-redecl.module "IGNORE" >
+<![%xhtml-prefw-redecl.module;[
+%xhtml-prefw-redecl.mod;
+<!-- end of xhtml-prefw-redecl.module -->]]>
+
+<!ENTITY % xhtml-events.module "INCLUDE" >
+
+<!-- Inline Style Module ........................................ -->
+<!ENTITY % xhtml-inlstyle.module "INCLUDE" >
+<![%xhtml-inlstyle.module;[
+<!ENTITY % xhtml-inlstyle.mod
+ PUBLIC "-//W3C//ELEMENTS XHTML Inline Style 1.0//EN"
+ "http://www.w3.org/TR/xhtml-modularization/DTD/xhtml-inlstyle-1.mod" >
+<!-- ...................................................................... -->
+<!-- XHTML Inline Style Module ........................................... -->
+<!-- file: xhtml-inlstyle-1.mod
+
+ This is XHTML, a reformulation of HTML as a modular XML application.
+ Copyright 1998-2001 W3C (MIT, INRIA, Keio), All Rights Reserved.
+ Revision: $Id: xhtml-inlstyle-1.mod,v 4.0 2001/04/02 22:42:49 altheim Exp $
+
+ This DTD module is identified by the PUBLIC and SYSTEM identifiers:
+
+ PUBLIC "-//W3C//ENTITIES XHTML Inline Style 1.0//EN"
+ SYSTEM "http://www.w3.org/TR/xhtml-modularization/DTD/xhtml-inlstyle-1.mod"
+
+ Revisions:
+ (none)
+ ....................................................................... -->
+
+<!-- Inline Style
+
+ This module declares the 'style' attribute, used to support inline
+ style markup. This module must be instantiated prior to the XHTML
+ Common Attributes module in order to be included in %Core.attrib;.
+-->
+
+<!ENTITY % style.attrib
+ "style CDATA #IMPLIED"
+>
+
+
+<!ENTITY % Core.extra.attrib
+ "%style.attrib;"
+>
+
+<!-- end of xhtml-inlstyle-1.mod -->
+]]>
+
+<!-- declare Document Model module instantiated in framework
+-->
+<!ENTITY % xhtml-model.mod
+ PUBLIC "-//W3C//ENTITIES XHTML 1.1 Document Model 1.0//EN"
+ "xhtml11-model-1.mod" >
+
+<!-- Modular Framework Module (required) ......................... -->
+<!ENTITY % xhtml-framework.module "INCLUDE" >
+<![%xhtml-framework.module;[
+<!ENTITY % xhtml-framework.mod
+ PUBLIC "-//W3C//ENTITIES XHTML Modular Framework 1.0//EN"
+ "http://www.w3.org/TR/xhtml-modularization/DTD/xhtml-framework-1.mod" >
+<!-- ...................................................................... -->
+<!-- XHTML Modular Framework Module ...................................... -->
+<!-- file: xhtml-framework-1.mod
+
+ This is XHTML, a reformulation of HTML as a modular XML application.
+ Copyright 1998-2001 W3C (MIT, INRIA, Keio), All Rights Reserved.
+ Revision: $Id: xhtml-framework-1.mod,v 4.0 2001/04/02 22:42:49 altheim Exp $ SMI
+
+ This DTD module is identified by the PUBLIC and SYSTEM identifiers:
+
+ PUBLIC "-//W3C//ENTITIES XHTML Modular Framework 1.0//EN"
+ SYSTEM "http://www.w3.org/TR/xhtml-modularization/DTD/xhtml-framework-1.mod"
+
+ Revisions:
+ (none)
+ ....................................................................... -->
+
+<!-- Modular Framework
+
+ This required module instantiates the modules needed
+ to support the XHTML modularization model, including:
+
+ + notations
+ + datatypes
+ + namespace-qualified names
+ + common attributes
+ + document model
+ + character entities
+
+ The Intrinsic Events module is ignored by default but
+ occurs in this module because it must be instantiated
+ prior to Attributes but after Datatypes.
+-->
+
+<!ENTITY % xhtml-arch.module "IGNORE" >
+<![%xhtml-arch.module;[
+<!ENTITY % xhtml-arch.mod
+ PUBLIC "-//W3C//ELEMENTS XHTML Base Architecture 1.0//EN"
+ "xhtml-arch-1.mod" >
+%xhtml-arch.mod;]]>
+
+<!ENTITY % xhtml-notations.module "INCLUDE" >
+<![%xhtml-notations.module;[
+<!ENTITY % xhtml-notations.mod
+ PUBLIC "-//W3C//NOTATIONS XHTML Notations 1.0//EN"
+ "xhtml-notations-1.mod" >
+<!-- ...................................................................... -->
+<!-- XHTML Notations Module .............................................. -->
+<!-- file: xhtml-notations-1.mod
+
+ This is XHTML, a reformulation of HTML as a modular XML application.
+ Copyright 1998-2001 W3C (MIT, INRIA, Keio), All Rights Reserved.
+ Revision: $Id: xhtml-notations-1.mod,v 4.0 2001/04/02 22:42:49 altheim Exp $
+
+ This DTD module is identified by the PUBLIC and SYSTEM identifiers:
+
+ PUBLIC "-//W3C//NOTATIONS XHTML Notations 1.0//EN"
+ SYSTEM "http://www.w3.org/TR/xhtml-modularization/DTD/xhtml-notations-1.mod"
+
+ Revisions:
+ (none)
+ ....................................................................... -->
+
+<!-- Notations
+
+ defines the following notations, many of these imported from
+ other specifications and standards. When an existing FPI is
+ known, it is incorporated here.
+-->
+
+<!-- XML Notations ..................................... -->
+<!-- SGML and XML Notations ............................ -->
+
+<!-- W3C XML 1.0 Recommendation -->
+<!NOTATION w3c-xml
+ PUBLIC "ISO 8879//NOTATION Extensible Markup Language (XML) 1.0//EN" >
+
+<!-- XML 1.0 CDATA -->
+<!NOTATION cdata
+ PUBLIC "-//W3C//NOTATION XML 1.0: CDATA//EN" >
+
+<!-- SGML Formal Public Identifiers -->
+<!NOTATION fpi
+ PUBLIC "ISO 8879:1986//NOTATION Formal Public Identifier//EN" >
+
+<!-- XHTML Notations ................................... -->
+
+<!-- Length defined for cellpadding/cellspacing -->
+
+<!-- nn for pixels or nn% for percentage length -->
+<!NOTATION length
+ PUBLIC "-//W3C//NOTATION XHTML Datatype: Length//EN" >
+
+<!-- space-separated list of link types -->
+<!NOTATION linkTypes
+ PUBLIC "-//W3C//NOTATION XHTML Datatype: LinkTypes//EN" >
+
+<!-- single or comma-separated list of media descriptors -->
+<!NOTATION mediaDesc
+ PUBLIC "-//W3C//NOTATION XHTML Datatype: MediaDesc//EN" >
+
+<!-- pixel, percentage, or relative -->
+<!NOTATION multiLength
+ PUBLIC "-//W3C//NOTATION XHTML Datatype: MultiLength//EN" >
+
+<!-- one or more digits (NUMBER) -->
+<!NOTATION number
+ PUBLIC "-//W3C//NOTATION XHTML Datatype: Number//EN" >
+
+<!-- integer representing length in pixels -->
+<!NOTATION pixels
+ PUBLIC "-//W3C//NOTATION XHTML Datatype: Pixels//EN" >
+
+<!-- script expression -->
+<!NOTATION script
+ PUBLIC "-//W3C//NOTATION XHTML Datatype: Script//EN" >
+
+<!-- textual content -->
+<!NOTATION text
+ PUBLIC "-//W3C//NOTATION XHTML Datatype: Text//EN" >
+
+<!-- Imported Notations ................................ -->
+
+<!-- a single character from [ISO10646] -->
+<!NOTATION character
+ PUBLIC "-//W3C//NOTATION XHTML Datatype: Character//EN" >
+
+<!-- a character encoding, as per [RFC2045] -->
+<!NOTATION charset
+ PUBLIC "-//W3C//NOTATION XHTML Datatype: Charset//EN" >
+
+<!-- a space separated list of character encodings, as per [RFC2045] -->
+<!NOTATION charsets
+ PUBLIC "-//W3C//NOTATION XHTML Datatype: Charsets//EN" >
+
+<!-- media type, as per [RFC2045] -->
+<!NOTATION contentType
+ PUBLIC "-//W3C//NOTATION XHTML Datatype: ContentType//EN" >
+
+<!-- comma-separated list of media types, as per [RFC2045] -->
+<!NOTATION contentTypes
+ PUBLIC "-//W3C//NOTATION XHTML Datatype: ContentTypes//EN" >
+
+<!-- date and time information. ISO date format -->
+<!NOTATION datetime
+ PUBLIC "-//W3C//NOTATION XHTML Datatype: Datetime//EN" >
+
+<!-- a language code, as per [RFC3066] -->
+<!NOTATION languageCode
+ PUBLIC "-//W3C//NOTATION XHTML Datatype: LanguageCode//EN" >
+
+<!-- a Uniform Resource Identifier, see [URI] -->
+<!NOTATION uri
+ PUBLIC "-//W3C//NOTATION XHTML Datatype: URI//EN" >
+
+<!-- a space-separated list of Uniform Resource Identifiers, see [URI] -->
+<!NOTATION uris
+ PUBLIC "-//W3C//NOTATION XHTML Datatype: URIs//EN" >
+
+<!-- end of xhtml-notations-1.mod -->
+]]>
+
+<!ENTITY % xhtml-datatypes.module "INCLUDE" >
+<![%xhtml-datatypes.module;[
+<!ENTITY % xhtml-datatypes.mod
+ PUBLIC "-//W3C//ENTITIES XHTML Datatypes 1.0//EN"
+ "xhtml-datatypes-1.mod" >
+<!-- ...................................................................... -->
+<!-- XHTML Datatypes Module .............................................. -->
+<!-- file: xhtml-datatypes-1.mod
+
+ This is XHTML, a reformulation of HTML as a modular XML application.
+ Copyright 1998-2001 W3C (MIT, INRIA, Keio), All Rights Reserved.
+ Revision: $Id: xhtml-datatypes-1.mod,v 4.0 2001/04/02 22:42:49 altheim Exp $ SMI
+
+ This DTD module is identified by the PUBLIC and SYSTEM identifiers:
+
+ PUBLIC "-//W3C//ENTITIES XHTML Datatypes 1.0//EN"
+ SYSTEM "http://www.w3.org/TR/xhtml-modularization/DTD/xhtml-datatypes-1.mod"
+
+ Revisions:
+ (none)
+ ....................................................................... -->
+
+<!-- Datatypes
+
+ defines containers for the following datatypes, many of
+ these imported from other specifications and standards.
+-->
+
+<!-- Length defined for cellpadding/cellspacing -->
+
+<!-- nn for pixels or nn% for percentage length -->
+<!ENTITY % Length.datatype "CDATA" >
+
+<!-- space-separated list of link types -->
+<!ENTITY % LinkTypes.datatype "NMTOKENS" >
+
+<!-- single or comma-separated list of media descriptors -->
+<!ENTITY % MediaDesc.datatype "CDATA" >
+
+<!-- pixel, percentage, or relative -->
+<!ENTITY % MultiLength.datatype "CDATA" >
+
+<!-- one or more digits (NUMBER) -->
+<!ENTITY % Number.datatype "CDATA" >
+
+<!-- integer representing length in pixels -->
+<!ENTITY % Pixels.datatype "CDATA" >
+
+<!-- script expression -->
+<!ENTITY % Script.datatype "CDATA" >
+
+<!-- textual content -->
+<!ENTITY % Text.datatype "CDATA" >
+
+<!-- Imported Datatypes ................................ -->
+
+<!-- a single character from [ISO10646] -->
+<!ENTITY % Character.datatype "CDATA" >
+
+<!-- a character encoding, as per [RFC2045] -->
+<!ENTITY % Charset.datatype "CDATA" >
+
+<!-- a space separated list of character encodings, as per [RFC2045] -->
+<!ENTITY % Charsets.datatype "CDATA" >
+
+<!-- media type, as per [RFC2045] -->
+<!ENTITY % ContentType.datatype "CDATA" >
+
+<!-- comma-separated list of media types, as per [RFC2045] -->
+<!ENTITY % ContentTypes.datatype "CDATA" >
+
+<!-- date and time information. ISO date format -->
+<!ENTITY % Datetime.datatype "CDATA" >
+
+<!-- formal public identifier, as per [ISO8879] -->
+<!ENTITY % FPI.datatype "CDATA" >
+
+<!-- a language code, as per [RFC3066] -->
+<!ENTITY % LanguageCode.datatype "NMTOKEN" >
+
+<!-- a Uniform Resource Identifier, see [URI] -->
+<!ENTITY % URI.datatype "CDATA" >
+
+<!-- a space-separated list of Uniform Resource Identifiers, see [URI] -->
+<!ENTITY % URIs.datatype "CDATA" >
+
+<!-- end of xhtml-datatypes-1.mod -->
+]]>
+
+<!-- placeholder for XLink support module -->
+<!ENTITY % xhtml-xlink.mod "" >
+
+
+<!ENTITY % xhtml-qname.module "INCLUDE" >
+<![%xhtml-qname.module;[
+<!ENTITY % xhtml-qname.mod
+ PUBLIC "-//W3C//ENTITIES XHTML Qualified Names 1.0//EN"
+ "xhtml-qname-1.mod" >
+<!-- ....................................................................... -->
+<!-- XHTML Qname Module ................................................... -->
+<!-- file: xhtml-qname-1.mod
+
+ This is XHTML, a reformulation of HTML as a modular XML application.
+ Copyright 1998-2001 W3C (MIT, INRIA, Keio), All Rights Reserved.
+ Revision: $Id: xhtml-qname-1.mod,v 4.0 2001/04/02 22:42:49 altheim Exp $ SMI
+
+ This DTD module is identified by the PUBLIC and SYSTEM identifiers:
+
+ PUBLIC "-//W3C//ENTITIES XHTML Qualified Names 1.0//EN"
+ SYSTEM "http://www.w3.org/TR/xhtml-modularization/DTD/xhtml-qname-1.mod"
+
+ Revisions:
+#2000-10-22: added qname declarations for ruby elements
+ ....................................................................... -->
+
+<!-- XHTML Qname (Qualified Name) Module
+
+ This module is contained in two parts, labeled Section 'A' and 'B':
+
+ Section A declares parameter entities to support namespace-
+ qualified names, namespace declarations, and name prefixing
+ for XHTML and extensions.
+
+ Section B declares parameter entities used to provide
+ namespace-qualified names for all XHTML element types:
+
+ %applet.qname; the xmlns-qualified name for <applet>
+ %base.qname; the xmlns-qualified name for <base>
+ ...
+
+ XHTML extensions would create a module similar to this one.
+ Included in the XHTML distribution is a template module
+ ('template-qname-1.mod') suitable for this purpose.
+-->
+
+<!-- Section A: XHTML XML Namespace Framework :::::::::::::::::::: -->
+
+<!-- 1. Declare a %XHTML.prefixed; conditional section keyword, used
+ to activate namespace prefixing. The default value should
+ inherit '%NS.prefixed;' from the DTD driver, so that unless
+ overridden, the default behaviour follows the overall DTD
+ prefixing scheme.
+-->
+<!ENTITY % NS.prefixed "IGNORE" >
+<!ENTITY % XHTML.prefixed "%NS.prefixed;" >
+
+<!-- 2. Declare a parameter entity (eg., %XHTML.xmlns;) containing
+ the URI reference used to identify the XHTML namespace:
+-->
+<!ENTITY % XHTML.xmlns "http://www.w3.org/1999/xhtml" >
+
+<!-- 3. Declare parameter entities (eg., %XHTML.prefix;) containing
+ the default namespace prefix string(s) to use when prefixing
+ is enabled. This may be overridden in the DTD driver or the
+ internal subset of an document instance. If no default prefix
+ is desired, this may be declared as an empty string.
+
+ NOTE: As specified in [XMLNAMES], the namespace prefix serves
+ as a proxy for the URI reference, and is not in itself significant.
+-->
+<!ENTITY % XHTML.prefix "" >
+
+<!-- 4. Declare parameter entities (eg., %XHTML.pfx;) containing the
+ colonized prefix(es) (eg., '%XHTML.prefix;:') used when
+ prefixing is active, an empty string when it is not.
+-->
+<![%XHTML.prefixed;[
+<!ENTITY % XHTML.pfx "%XHTML.prefix;:" >
+]]>
+<!ENTITY % XHTML.pfx "" >
+
+<!-- declare qualified name extensions here ............ -->
+<!ENTITY % xhtml-qname-extra.mod "" >
+
+
+<!-- 5. The parameter entity %XHTML.xmlns.extra.attrib; may be
+ redeclared to contain any non-XHTML namespace declaration
+ attributes for namespaces embedded in XHTML. The default
+ is an empty string. XLink should be included here if used
+ in the DTD.
+-->
+<!ENTITY % XHTML.xmlns.extra.attrib "" >
+
+<!-- The remainder of Section A is only followed in XHTML, not extensions. -->
+
+<!-- Declare a parameter entity %NS.decl.attrib; containing
+ all XML Namespace declarations used in the DTD, plus the
+ xmlns declaration for XHTML, its form dependent on whether
+ prefixing is active.
+-->
+<![%XHTML.prefixed;[
+<!ENTITY % NS.decl.attrib
+ "xmlns:%XHTML.prefix; %URI.datatype; #FIXED '%XHTML.xmlns;'
+ %XHTML.xmlns.extra.attrib;"
+>
+]]>
+<!ENTITY % NS.decl.attrib
+ "%XHTML.xmlns.extra.attrib;"
+>
+
+<!-- This is a placeholder for future XLink support.
+-->
+<!ENTITY % XLINK.xmlns.attrib "" >
+
+<!-- Declare a parameter entity %NS.decl.attrib; containing all
+ XML namespace declaration attributes used by XHTML, including
+ a default xmlns attribute when prefixing is inactive.
+-->
+<![%XHTML.prefixed;[
+<!ENTITY % XHTML.xmlns.attrib
+ "%NS.decl.attrib;
+ %XLINK.xmlns.attrib;"
+>
+]]>
+<!ENTITY % XHTML.xmlns.attrib
+ "xmlns %URI.datatype; #FIXED '%XHTML.xmlns;'
+ %XLINK.xmlns.attrib;"
+>
+
+<!-- placeholder for qualified name redeclarations -->
+<!ENTITY % xhtml-qname.redecl "" >
+
+
+<!-- Section B: XHTML Qualified Names ::::::::::::::::::::::::::::: -->
+
+<!-- 6. This section declares parameter entities used to provide
+ namespace-qualified names for all XHTML element types.
+-->
+
+<!-- module: xhtml-applet-1.mod -->
+<!ENTITY % applet.qname "%XHTML.pfx;applet" >
+
+<!-- module: xhtml-base-1.mod -->
+<!ENTITY % base.qname "%XHTML.pfx;base" >
+
+<!-- module: xhtml-bdo-1.mod -->
+<!ENTITY % bdo.qname "%XHTML.pfx;bdo" >
+
+<!-- module: xhtml-blkphras-1.mod -->
+<!ENTITY % address.qname "%XHTML.pfx;address" >
+<!ENTITY % blockquote.qname "%XHTML.pfx;blockquote" >
+<!ENTITY % pre.qname "%XHTML.pfx;pre" >
+<!ENTITY % h1.qname "%XHTML.pfx;h1" >
+<!ENTITY % h2.qname "%XHTML.pfx;h2" >
+<!ENTITY % h3.qname "%XHTML.pfx;h3" >
+<!ENTITY % h4.qname "%XHTML.pfx;h4" >
+<!ENTITY % h5.qname "%XHTML.pfx;h5" >
+<!ENTITY % h6.qname "%XHTML.pfx;h6" >
+
+<!-- module: xhtml-blkpres-1.mod -->
+<!ENTITY % hr.qname "%XHTML.pfx;hr" >
+
+<!-- module: xhtml-blkstruct-1.mod -->
+<!ENTITY % div.qname "%XHTML.pfx;div" >
+<!ENTITY % p.qname "%XHTML.pfx;p" >
+
+<!-- module: xhtml-edit-1.mod -->
+<!ENTITY % ins.qname "%XHTML.pfx;ins" >
+<!ENTITY % del.qname "%XHTML.pfx;del" >
+
+<!-- module: xhtml-form-1.mod -->
+<!ENTITY % form.qname "%XHTML.pfx;form" >
+<!ENTITY % label.qname "%XHTML.pfx;label" >
+<!ENTITY % input.qname "%XHTML.pfx;input" >
+<!ENTITY % select.qname "%XHTML.pfx;select" >
+<!ENTITY % optgroup.qname "%XHTML.pfx;optgroup" >
+<!ENTITY % option.qname "%XHTML.pfx;option" >
+<!ENTITY % textarea.qname "%XHTML.pfx;textarea" >
+<!ENTITY % fieldset.qname "%XHTML.pfx;fieldset" >
+<!ENTITY % legend.qname "%XHTML.pfx;legend" >
+<!ENTITY % button.qname "%XHTML.pfx;button" >
+
+<!-- module: xhtml-hypertext-1.mod -->
+<!ENTITY % a.qname "%XHTML.pfx;a" >
+
+<!-- module: xhtml-image-1.mod -->
+<!ENTITY % img.qname "%XHTML.pfx;img" >
+
+<!-- module: xhtml-inlphras-1.mod -->
+<!ENTITY % abbr.qname "%XHTML.pfx;abbr" >
+<!ENTITY % acronym.qname "%XHTML.pfx;acronym" >
+<!ENTITY % cite.qname "%XHTML.pfx;cite" >
+<!ENTITY % code.qname "%XHTML.pfx;code" >
+<!ENTITY % dfn.qname "%XHTML.pfx;dfn" >
+<!ENTITY % em.qname "%XHTML.pfx;em" >
+<!ENTITY % kbd.qname "%XHTML.pfx;kbd" >
+<!ENTITY % q.qname "%XHTML.pfx;q" >
+<!ENTITY % samp.qname "%XHTML.pfx;samp" >
+<!ENTITY % strong.qname "%XHTML.pfx;strong" >
+<!ENTITY % var.qname "%XHTML.pfx;var" >
+
+<!-- module: xhtml-inlpres-1.mod -->
+<!ENTITY % b.qname "%XHTML.pfx;b" >
+<!ENTITY % big.qname "%XHTML.pfx;big" >
+<!ENTITY % i.qname "%XHTML.pfx;i" >
+<!ENTITY % small.qname "%XHTML.pfx;small" >
+<!ENTITY % sub.qname "%XHTML.pfx;sub" >
+<!ENTITY % sup.qname "%XHTML.pfx;sup" >
+<!ENTITY % tt.qname "%XHTML.pfx;tt" >
+
+<!-- module: xhtml-inlstruct-1.mod -->
+<!ENTITY % br.qname "%XHTML.pfx;br" >
+<!ENTITY % span.qname "%XHTML.pfx;span" >
+
+<!-- module: xhtml-ismap-1.mod (also csismap, ssismap) -->
+<!ENTITY % map.qname "%XHTML.pfx;map" >
+<!ENTITY % area.qname "%XHTML.pfx;area" >
+
+<!-- module: xhtml-link-1.mod -->
+<!ENTITY % link.qname "%XHTML.pfx;link" >
+
+<!-- module: xhtml-list-1.mod -->
+<!ENTITY % dl.qname "%XHTML.pfx;dl" >
+<!ENTITY % dt.qname "%XHTML.pfx;dt" >
+<!ENTITY % dd.qname "%XHTML.pfx;dd" >
+<!ENTITY % ol.qname "%XHTML.pfx;ol" >
+<!ENTITY % ul.qname "%XHTML.pfx;ul" >
+<!ENTITY % li.qname "%XHTML.pfx;li" >
+
+<!-- module: xhtml-meta-1.mod -->
+<!ENTITY % meta.qname "%XHTML.pfx;meta" >
+
+<!-- module: xhtml-param-1.mod -->
+<!ENTITY % param.qname "%XHTML.pfx;param" >
+
+<!-- module: xhtml-object-1.mod -->
+<!ENTITY % object.qname "%XHTML.pfx;object" >
+
+<!-- module: xhtml-script-1.mod -->
+<!ENTITY % script.qname "%XHTML.pfx;script" >
+<!ENTITY % noscript.qname "%XHTML.pfx;noscript" >
+
+<!-- module: xhtml-struct-1.mod -->
+<!ENTITY % html.qname "%XHTML.pfx;html" >
+<!ENTITY % head.qname "%XHTML.pfx;head" >
+<!ENTITY % title.qname "%XHTML.pfx;title" >
+<!ENTITY % body.qname "%XHTML.pfx;body" >
+
+<!-- module: xhtml-style-1.mod -->
+<!ENTITY % style.qname "%XHTML.pfx;style" >
+
+<!-- module: xhtml-table-1.mod -->
+<!ENTITY % table.qname "%XHTML.pfx;table" >
+<!ENTITY % caption.qname "%XHTML.pfx;caption" >
+<!ENTITY % thead.qname "%XHTML.pfx;thead" >
+<!ENTITY % tfoot.qname "%XHTML.pfx;tfoot" >
+<!ENTITY % tbody.qname "%XHTML.pfx;tbody" >
+<!ENTITY % colgroup.qname "%XHTML.pfx;colgroup" >
+<!ENTITY % col.qname "%XHTML.pfx;col" >
+<!ENTITY % tr.qname "%XHTML.pfx;tr" >
+<!ENTITY % th.qname "%XHTML.pfx;th" >
+<!ENTITY % td.qname "%XHTML.pfx;td" >
+
+<!-- module: xhtml-ruby-1.mod -->
+
+<!ENTITY % ruby.qname "%XHTML.pfx;ruby" >
+<!ENTITY % rbc.qname "%XHTML.pfx;rbc" >
+<!ENTITY % rtc.qname "%XHTML.pfx;rtc" >
+<!ENTITY % rb.qname "%XHTML.pfx;rb" >
+<!ENTITY % rt.qname "%XHTML.pfx;rt" >
+<!ENTITY % rp.qname "%XHTML.pfx;rp" >
+
+<!-- Provisional XHTML 2.0 Qualified Names ...................... -->
+
+<!-- module: xhtml-image-2.mod -->
+<!ENTITY % alt.qname "%XHTML.pfx;alt" >
+
+<!-- end of xhtml-qname-1.mod -->
+]]>
+
+<!ENTITY % xhtml-events.module "IGNORE" >
+<![%xhtml-events.module;[
+<!ENTITY % xhtml-events.mod
+ PUBLIC "-//W3C//ENTITIES XHTML Intrinsic Events 1.0//EN"
+ "xhtml-events-1.mod" >
+<!-- ...................................................................... -->
+<!-- XHTML Intrinsic Events Module ....................................... -->
+<!-- file: xhtml-events-1.mod
+
+ This is XHTML, a reformulation of HTML as a modular XML application.
+ Copyright 1998-2001 W3C (MIT, INRIA, Keio), All Rights Reserved.
+ Revision: $Id: xhtml-events-1.mod,v 4.0 2001/04/02 22:42:49 altheim Exp $ SMI
+
+ This DTD module is identified by the PUBLIC and SYSTEM identifiers:
+
+ PUBLIC "-//W3C//ENTITIES XHTML Intrinsic Events 1.0//EN"
+ SYSTEM "http://www.w3.org/TR/xhtml-modularization/DTD/xhtml-events-1.mod"
+
+ Revisions:
+ (none)
+ ....................................................................... -->
+
+<!-- Intrinsic Event Attributes
+
+ These are the event attributes defined in HTML 4.0,
+ Section 18.2.3 "Intrinsic Events". This module must be
+ instantiated prior to the Attributes Module but after
+ the Datatype Module in the Modular Framework module.
+
+ "Note: Authors of HTML documents are advised that changes
+ are likely to occur in the realm of intrinsic events
+ (e.g., how scripts are bound to events). Research in
+ this realm is carried on by members of the W3C Document
+ Object Model Working Group (see the W3C Web site at
+ http://www.w3.org/ for more information)."
+-->
+<!-- NOTE: Because the ATTLIST declarations in this module occur
+ before their respective ELEMENT declarations in other
+ modules, there may be a dependency on this module that
+ should be considered if any of the parameter entities used
+ for element type names (eg., %a.qname;) are redeclared.
+-->
+
+<!ENTITY % Events.attrib
+ "onclick %Script.datatype; #IMPLIED
+ ondblclick %Script.datatype; #IMPLIED
+ onmousedown %Script.datatype; #IMPLIED
+ onmouseup %Script.datatype; #IMPLIED
+ onmouseover %Script.datatype; #IMPLIED
+ onmousemove %Script.datatype; #IMPLIED
+ onmouseout %Script.datatype; #IMPLIED
+ onkeypress %Script.datatype; #IMPLIED
+ onkeydown %Script.datatype; #IMPLIED
+ onkeyup %Script.datatype; #IMPLIED"
+>
+
+<!-- additional attributes on anchor element
+-->
+<!ATTLIST %a.qname;
+ onfocus %Script.datatype; #IMPLIED
+ onblur %Script.datatype; #IMPLIED
+>
+
+<!-- additional attributes on form element
+-->
+<!ATTLIST %form.qname;
+ onsubmit %Script.datatype; #IMPLIED
+ onreset %Script.datatype; #IMPLIED
+>
+
+<!-- additional attributes on label element
+-->
+<!ATTLIST %label.qname;
+ onfocus %Script.datatype; #IMPLIED
+ onblur %Script.datatype; #IMPLIED
+>
+
+<!-- additional attributes on input element
+-->
+<!ATTLIST %input.qname;
+ onfocus %Script.datatype; #IMPLIED
+ onblur %Script.datatype; #IMPLIED
+ onselect %Script.datatype; #IMPLIED
+ onchange %Script.datatype; #IMPLIED
+>
+
+<!-- additional attributes on select element
+-->
+<!ATTLIST %select.qname;
+ onfocus %Script.datatype; #IMPLIED
+ onblur %Script.datatype; #IMPLIED
+ onchange %Script.datatype; #IMPLIED
+>
+
+<!-- additional attributes on textarea element
+-->
+<!ATTLIST %textarea.qname;
+ onfocus %Script.datatype; #IMPLIED
+ onblur %Script.datatype; #IMPLIED
+ onselect %Script.datatype; #IMPLIED
+ onchange %Script.datatype; #IMPLIED
+>
+
+<!-- additional attributes on button element
+-->
+<!ATTLIST %button.qname;
+ onfocus %Script.datatype; #IMPLIED
+ onblur %Script.datatype; #IMPLIED
+>
+
+<!-- additional attributes on body element
+-->
+<!ATTLIST %body.qname;
+ onload %Script.datatype; #IMPLIED
+ onunload %Script.datatype; #IMPLIED
+>
+
+<!-- additional attributes on area element
+-->
+<!ATTLIST %area.qname;
+ onfocus %Script.datatype; #IMPLIED
+ onblur %Script.datatype; #IMPLIED
+>
+
+<!-- end of xhtml-events-1.mod -->
+]]>
+
+<!ENTITY % xhtml-attribs.module "INCLUDE" >
+<![%xhtml-attribs.module;[
+<!ENTITY % xhtml-attribs.mod
+ PUBLIC "-//W3C//ENTITIES XHTML Common Attributes 1.0//EN"
+ "xhtml-attribs-1.mod" >
+<!-- ...................................................................... -->
+<!-- XHTML Common Attributes Module ...................................... -->
+<!-- file: xhtml-attribs-1.mod
+
+ This is XHTML, a reformulation of HTML as a modular XML application.
+ Copyright 1998-2001 W3C (MIT, INRIA, Keio), All Rights Reserved.
+ Revision: $Id: xhtml-attribs-1.mod,v 4.0 2001/04/02 22:42:49 altheim Exp $ SMI
+
+ This DTD module is identified by the PUBLIC and SYSTEM identifiers:
+
+ PUBLIC "-//W3C//ENTITIES XHTML Common Attributes 1.0//EN"
+ SYSTEM "http://www.w3.org/TR/xhtml-modularization/DTD/xhtml-attribs-1.mod"
+
+ Revisions:
+ (none)
+ ....................................................................... -->
+
+<!-- Common Attributes
+
+ This module declares many of the common attributes for the XHTML DTD.
+ %NS.decl.attrib; is declared in the XHTML Qname module.
+-->
+
+<!ENTITY % id.attrib
+ "id ID #IMPLIED"
+>
+
+<!ENTITY % class.attrib
+ "class NMTOKENS #IMPLIED"
+>
+
+<!ENTITY % title.attrib
+ "title %Text.datatype; #IMPLIED"
+>
+
+<!ENTITY % Core.extra.attrib "" >
+
+<!ENTITY % Core.attrib
+ "%XHTML.xmlns.attrib;
+ %id.attrib;
+ %class.attrib;
+ %title.attrib;
+ %Core.extra.attrib;"
+>
+
+<!ENTITY % lang.attrib
+ "xml:lang %LanguageCode.datatype; #IMPLIED"
+>
+
+<![%XHTML.bidi;[
+<!ENTITY % dir.attrib
+ "dir ( ltr | rtl ) #IMPLIED"
+>
+
+<!ENTITY % I18n.attrib
+ "%dir.attrib;
+ %lang.attrib;"
+>
+
+]]>
+<!ENTITY % I18n.attrib
+ "%lang.attrib;"
+>
+
+<!ENTITY % Common.extra.attrib "" >
+
+<!-- intrinsic event attributes declared previously
+-->
+<!ENTITY % Events.attrib "" >
+
+<!ENTITY % Common.attrib
+ "%Core.attrib;
+ %I18n.attrib;
+ %Events.attrib;
+ %Common.extra.attrib;"
+>
+
+<!-- end of xhtml-attribs-1.mod -->
+]]>
+
+<!-- placeholder for content model redeclarations -->
+<!ENTITY % xhtml-model.redecl "" >
+
+
+<!ENTITY % xhtml-model.module "INCLUDE" >
+<![%xhtml-model.module;[
+<!-- instantiate the Document Model module declared in the DTD driver
+-->
+<!-- ....................................................................... -->
+<!-- XHTML 1.1 Document Model Module ...................................... -->
+<!-- file: xhtml11-model-1.mod
+
+ This is XHTML 1.1, a reformulation of HTML as a modular XML application.
+ Copyright 1998-2000 W3C (MIT, INRIA, Keio), All Rights Reserved.
+ Revision: $Id: xhtml11-model-1.mod,v 1.12 2000/11/18 18:20:25 ahby Exp $ SMI
+
+ This DTD module is identified by the PUBLIC and SYSTEM identifiers:
+
+ PUBLIC "-//W3C//ENTITIES XHTML 1.1 Document Model 1.0//EN"
+ SYSTEM "http://www.w3.org/TR/xhtml11/DTD/xhtml11-model-1.mod"
+
+ Revisions:
+ (none)
+ ....................................................................... -->
+
+<!-- XHTML 1.1 Document Model
+
+ This module describes the groupings of elements that make up
+ common content models for XHTML elements.
+
+ XHTML has three basic content models:
+
+ %Inline.mix; character-level elements
+ %Block.mix; block-like elements, eg., paragraphs and lists
+ %Flow.mix; any block or inline elements
+
+ Any parameter entities declared in this module may be used
+ to create element content models, but the above three are
+ considered 'global' (insofar as that term applies here).
+
+ The reserved word '#PCDATA' (indicating a text string) is now
+ included explicitly with each element declaration that is
+ declared as mixed content, as XML requires that this token
+ occur first in a content model specification.
+-->
+<!-- Extending the Model
+
+ While in some cases this module may need to be rewritten to
+ accommodate changes to the document model, minor extensions
+ may be accomplished by redeclaring any of the three *.extra;
+ parameter entities to contain extension element types as follows:
+
+ %Misc.extra; whose parent may be any block or
+ inline element.
+
+ %Inline.extra; whose parent may be any inline element.
+
+ %Block.extra; whose parent may be any block element.
+
+ If used, these parameter entities must be an OR-separated
+ list beginning with an OR separator ("|"), eg., "| a | b | c"
+
+ All block and inline *.class parameter entities not part
+ of the *struct.class classes begin with "| " to allow for
+ exclusion from mixes.
+-->
+
+<!-- .............. Optional Elements in head .................. -->
+
+<!ENTITY % HeadOpts.mix
+ "( %script.qname; | %style.qname; | %meta.qname;
+ | %link.qname; | %object.qname; )*"
+>
+
+<!-- ................. Miscellaneous Elements .................. -->
+
+<!-- ins and del are used to denote editing changes
+-->
+<!ENTITY % Edit.class "| %ins.qname; | %del.qname;" >
+
+<!-- script and noscript are used to contain scripts
+ and alternative content
+-->
+<!ENTITY % Script.class "| %script.qname; | %noscript.qname;" >
+
+<!ENTITY % Misc.extra "" >
+
+<!-- These elements are neither block nor inline, and can
+ essentially be used anywhere in the document body.
+-->
+<!ENTITY % Misc.class
+ "%Edit.class;
+ %Script.class;
+ %Misc.extra;"
+>
+
+<!-- .................... Inline Elements ...................... -->
+
+<!ENTITY % InlStruct.class "%br.qname; | %span.qname;" >
+
+<!ENTITY % InlPhras.class
+ "| %em.qname; | %strong.qname; | %dfn.qname; | %code.qname;
+ | %samp.qname; | %kbd.qname; | %var.qname; | %cite.qname;
+ | %abbr.qname; | %acronym.qname; | %q.qname;" >
+
+<!ENTITY % InlPres.class
+ "| %tt.qname; | %i.qname; | %b.qname; | %big.qname;
+ | %small.qname; | %sub.qname; | %sup.qname;" >
+
+<!ENTITY % I18n.class "| %bdo.qname;" >
+
+<!ENTITY % Anchor.class "| %a.qname;" >
+
+<!ENTITY % InlSpecial.class
+ "| %img.qname; | %map.qname;
+ | %object.qname;" >
+
+<!ENTITY % InlForm.class
+ "| %input.qname; | %select.qname; | %textarea.qname;
+ | %label.qname; | %button.qname;" >
+
+<!ENTITY % Inline.extra "" >
+
+<!ENTITY % Ruby.class "| %ruby.qname;" >
+
+<!-- %Inline.class; includes all inline elements,
+ used as a component in mixes
+-->
+<!ENTITY % Inline.class
+ "%InlStruct.class;
+ %InlPhras.class;
+ %InlPres.class;
+ %I18n.class;
+ %Anchor.class;
+ %InlSpecial.class;
+ %InlForm.class;
+ %Ruby.class;
+ %Inline.extra;"
+>
+
+<!-- %InlNoRuby.class; includes all inline elements
+ except ruby, used as a component in mixes
+-->
+<!ENTITY % InlNoRuby.class
+ "%InlStruct.class;
+ %InlPhras.class;
+ %InlPres.class;
+ %I18n.class;
+ %Anchor.class;
+ %InlSpecial.class;
+ %InlForm.class;
+ %Inline.extra;"
+>
+
+<!-- %NoRuby.content; includes all inlines except ruby
+-->
+<!ENTITY % NoRuby.content
+ "( #PCDATA
+ | %InlNoRuby.class;
+ %Misc.class; )*"
+>
+
+<!-- %InlNoAnchor.class; includes all non-anchor inlines,
+ used as a component in mixes
+-->
+<!ENTITY % InlNoAnchor.class
+ "%InlStruct.class;
+ %InlPhras.class;
+ %InlPres.class;
+ %I18n.class;
+ %InlSpecial.class;
+ %InlForm.class;
+ %Ruby.class;
+ %Inline.extra;"
+>
+
+<!-- %InlNoAnchor.mix; includes all non-anchor inlines
+-->
+<!ENTITY % InlNoAnchor.mix
+ "%InlNoAnchor.class;
+ %Misc.class;"
+>
+
+<!-- %Inline.mix; includes all inline elements, including %Misc.class;
+-->
+<!ENTITY % Inline.mix
+ "%Inline.class;
+ %Misc.class;"
+>
+
+<!-- ..................... Block Elements ...................... -->
+
+<!-- In the HTML 4.0 DTD, heading and list elements were included
+ in the %block; parameter entity. The %Heading.class; and
+ %List.class; parameter entities must now be included explicitly
+ on element declarations where desired.
+-->
+
+<!ENTITY % Heading.class
+ "%h1.qname; | %h2.qname; | %h3.qname;
+ | %h4.qname; | %h5.qname; | %h6.qname;" >
+
+<!ENTITY % List.class "%ul.qname; | %ol.qname; | %dl.qname;" >
+
+<!ENTITY % Table.class "| %table.qname;" >
+
+<!ENTITY % Form.class "| %form.qname;" >
+
+<!ENTITY % Fieldset.class "| %fieldset.qname;" >
+
+<!ENTITY % BlkStruct.class "%p.qname; | %div.qname;" >
+
+<!ENTITY % BlkPhras.class
+ "| %pre.qname; | %blockquote.qname; | %address.qname;" >
+
+<!ENTITY % BlkPres.class "| %hr.qname;" >
+
+<!ENTITY % BlkSpecial.class
+ "%Table.class;
+ %Form.class;
+ %Fieldset.class;"
+>
+
+<!ENTITY % Block.extra "" >
+
+<!-- %Block.class; includes all block elements,
+ used as an component in mixes
+-->
+<!ENTITY % Block.class
+ "%BlkStruct.class;
+ %BlkPhras.class;
+ %BlkPres.class;
+ %BlkSpecial.class;
+ %Block.extra;"
+>
+
+<!-- %Block.mix; includes all block elements plus %Misc.class;
+-->
+<!ENTITY % Block.mix
+ "%Heading.class;
+ | %List.class;
+ | %Block.class;
+ %Misc.class;"
+>
+
+<!-- ................ All Content Elements .................. -->
+
+<!-- %Flow.mix; includes all text content, block and inline
+-->
+<!ENTITY % Flow.mix
+ "%Heading.class;
+ | %List.class;
+ | %Block.class;
+ | %Inline.class;
+ %Misc.class;"
+>
+
+<!-- end of xhtml11-model-1.mod -->
+]]>
+
+<!ENTITY % xhtml-charent.module "INCLUDE" >
+<![%xhtml-charent.module;[
+<!ENTITY % xhtml-charent.mod
+ PUBLIC "-//W3C//ENTITIES XHTML Character Entities 1.0//EN"
+ "xhtml-charent-1.mod" >
+<!-- ...................................................................... -->
+<!-- XHTML Character Entities Module ......................................... -->
+<!-- file: xhtml-charent-1.mod
+
+ This is XHTML, a reformulation of HTML as a modular XML application.
+ Copyright 1998-2001 W3C (MIT, INRIA, Keio), All Rights Reserved.
+ Revision: $Id: xhtml-charent-1.mod,v 4.0 2001/04/02 22:42:49 altheim Exp $ SMI
+
+ This DTD module is identified by the PUBLIC and SYSTEM identifiers:
+
+ PUBLIC "-//W3C//ENTITIES XHTML Character Entities 1.0//EN"
+ SYSTEM "http://www.w3.org/TR/xhtml-modularization/DTD/xhtml-charent-1.mod"
+
+ Revisions:
+ (none)
+ ....................................................................... -->
+
+<!-- Character Entities for XHTML
+
+ This module declares the set of character entities for XHTML,
+ including the Latin 1, Symbol and Special character collections.
+-->
+
+<!ENTITY % xhtml-lat1
+ PUBLIC "-//W3C//ENTITIES Latin 1 for XHTML//EN"
+ "xhtml-lat1.ent" >
+<!-- Portions (C) International Organization for Standardization 1986
+ Permission to copy in any form is granted for use with
+ conforming SGML systems and applications as defined in
+ ISO 8879, provided this notice is included in all copies.
+-->
+<!-- Character entity set. Typical invocation:
+ <!ENTITY % HTMLlat1 PUBLIC
+ "-//W3C//ENTITIES Latin 1 for XHTML//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml-lat1.ent">
+ %HTMLlat1;
+-->
+
+<!ENTITY nbsp "&#160;"> <!-- no-break space = non-breaking space,
+ U+00A0 ISOnum -->
+<!ENTITY iexcl "&#161;"> <!-- inverted exclamation mark, U+00A1 ISOnum -->
+<!ENTITY cent "&#162;"> <!-- cent sign, U+00A2 ISOnum -->
+<!ENTITY pound "&#163;"> <!-- pound sign, U+00A3 ISOnum -->
+<!ENTITY curren "&#164;"> <!-- currency sign, U+00A4 ISOnum -->
+<!ENTITY yen "&#165;"> <!-- yen sign = yuan sign, U+00A5 ISOnum -->
+<!ENTITY brvbar "&#166;"> <!-- broken bar = broken vertical bar,
+ U+00A6 ISOnum -->
+<!ENTITY sect "&#167;"> <!-- section sign, U+00A7 ISOnum -->
+<!ENTITY uml "&#168;"> <!-- diaeresis = spacing diaeresis,
+ U+00A8 ISOdia -->
+<!ENTITY copy "&#169;"> <!-- copyright sign, U+00A9 ISOnum -->
+<!ENTITY ordf "&#170;"> <!-- feminine ordinal indicator, U+00AA ISOnum -->
+<!ENTITY laquo "&#171;"> <!-- left-pointing double angle quotation mark
+ = left pointing guillemet, U+00AB ISOnum -->
+<!ENTITY not "&#172;"> <!-- not sign = discretionary hyphen,
+ U+00AC ISOnum -->
+<!ENTITY shy "&#173;"> <!-- soft hyphen = discretionary hyphen,
+ U+00AD ISOnum -->
+<!ENTITY reg "&#174;"> <!-- registered sign = registered trade mark sign,
+ U+00AE ISOnum -->
+<!ENTITY macr "&#175;"> <!-- macron = spacing macron = overline
+ = APL overbar, U+00AF ISOdia -->
+<!ENTITY deg "&#176;"> <!-- degree sign, U+00B0 ISOnum -->
+<!ENTITY plusmn "&#177;"> <!-- plus-minus sign = plus-or-minus sign,
+ U+00B1 ISOnum -->
+<!ENTITY sup2 "&#178;"> <!-- superscript two = superscript digit two
+ = squared, U+00B2 ISOnum -->
+<!ENTITY sup3 "&#179;"> <!-- superscript three = superscript digit three
+ = cubed, U+00B3 ISOnum -->
+<!ENTITY acute "&#180;"> <!-- acute accent = spacing acute,
+ U+00B4 ISOdia -->
+<!ENTITY micro "&#181;"> <!-- micro sign, U+00B5 ISOnum -->
+<!ENTITY para "&#182;"> <!-- pilcrow sign = paragraph sign,
+ U+00B6 ISOnum -->
+<!ENTITY middot "&#183;"> <!-- middle dot = Georgian comma
+ = Greek middle dot, U+00B7 ISOnum -->
+<!ENTITY cedil "&#184;"> <!-- cedilla = spacing cedilla, U+00B8 ISOdia -->
+<!ENTITY sup1 "&#185;"> <!-- superscript one = superscript digit one,
+ U+00B9 ISOnum -->
+<!ENTITY ordm "&#186;"> <!-- masculine ordinal indicator,
+ U+00BA ISOnum -->
+<!ENTITY raquo "&#187;"> <!-- right-pointing double angle quotation mark
+ = right pointing guillemet, U+00BB ISOnum -->
+<!ENTITY frac14 "&#188;"> <!-- vulgar fraction one quarter
+ = fraction one quarter, U+00BC ISOnum -->
+<!ENTITY frac12 "&#189;"> <!-- vulgar fraction one half
+ = fraction one half, U+00BD ISOnum -->
+<!ENTITY frac34 "&#190;"> <!-- vulgar fraction three quarters
+ = fraction three quarters, U+00BE ISOnum -->
+<!ENTITY iquest "&#191;"> <!-- inverted question mark
+ = turned question mark, U+00BF ISOnum -->
+<!ENTITY Agrave "&#192;"> <!-- latin capital letter A with grave
+ = latin capital letter A grave,
+ U+00C0 ISOlat1 -->
+<!ENTITY Aacute "&#193;"> <!-- latin capital letter A with acute,
+ U+00C1 ISOlat1 -->
+<!ENTITY Acirc "&#194;"> <!-- latin capital letter A with circumflex,
+ U+00C2 ISOlat1 -->
+<!ENTITY Atilde "&#195;"> <!-- latin capital letter A with tilde,
+ U+00C3 ISOlat1 -->
+<!ENTITY Auml "&#196;"> <!-- latin capital letter A with diaeresis,
+ U+00C4 ISOlat1 -->
+<!ENTITY Aring "&#197;"> <!-- latin capital letter A with ring above
+ = latin capital letter A ring,
+ U+00C5 ISOlat1 -->
+<!ENTITY AElig "&#198;"> <!-- latin capital letter AE
+ = latin capital ligature AE,
+ U+00C6 ISOlat1 -->
+<!ENTITY Ccedil "&#199;"> <!-- latin capital letter C with cedilla,
+ U+00C7 ISOlat1 -->
+<!ENTITY Egrave "&#200;"> <!-- latin capital letter E with grave,
+ U+00C8 ISOlat1 -->
+<!ENTITY Eacute "&#201;"> <!-- latin capital letter E with acute,
+ U+00C9 ISOlat1 -->
+<!ENTITY Ecirc "&#202;"> <!-- latin capital letter E with circumflex,
+ U+00CA ISOlat1 -->
+<!ENTITY Euml "&#203;"> <!-- latin capital letter E with diaeresis,
+ U+00CB ISOlat1 -->
+<!ENTITY Igrave "&#204;"> <!-- latin capital letter I with grave,
+ U+00CC ISOlat1 -->
+<!ENTITY Iacute "&#205;"> <!-- latin capital letter I with acute,
+ U+00CD ISOlat1 -->
+<!ENTITY Icirc "&#206;"> <!-- latin capital letter I with circumflex,
+ U+00CE ISOlat1 -->
+<!ENTITY Iuml "&#207;"> <!-- latin capital letter I with diaeresis,
+ U+00CF ISOlat1 -->
+<!ENTITY ETH "&#208;"> <!-- latin capital letter ETH, U+00D0 ISOlat1 -->
+<!ENTITY Ntilde "&#209;"> <!-- latin capital letter N with tilde,
+ U+00D1 ISOlat1 -->
+<!ENTITY Ograve "&#210;"> <!-- latin capital letter O with grave,
+ U+00D2 ISOlat1 -->
+<!ENTITY Oacute "&#211;"> <!-- latin capital letter O with acute,
+ U+00D3 ISOlat1 -->
+<!ENTITY Ocirc "&#212;"> <!-- latin capital letter O with circumflex,
+ U+00D4 ISOlat1 -->
+<!ENTITY Otilde "&#213;"> <!-- latin capital letter O with tilde,
+ U+00D5 ISOlat1 -->
+<!ENTITY Ouml "&#214;"> <!-- latin capital letter O with diaeresis,
+ U+00D6 ISOlat1 -->
+<!ENTITY times "&#215;"> <!-- multiplication sign, U+00D7 ISOnum -->
+<!ENTITY Oslash "&#216;"> <!-- latin capital letter O with stroke
+ = latin capital letter O slash,
+ U+00D8 ISOlat1 -->
+<!ENTITY Ugrave "&#217;"> <!-- latin capital letter U with grave,
+ U+00D9 ISOlat1 -->
+<!ENTITY Uacute "&#218;"> <!-- latin capital letter U with acute,
+ U+00DA ISOlat1 -->
+<!ENTITY Ucirc "&#219;"> <!-- latin capital letter U with circumflex,
+ U+00DB ISOlat1 -->
+<!ENTITY Uuml "&#220;"> <!-- latin capital letter U with diaeresis,
+ U+00DC ISOlat1 -->
+<!ENTITY Yacute "&#221;"> <!-- latin capital letter Y with acute,
+ U+00DD ISOlat1 -->
+<!ENTITY THORN "&#222;"> <!-- latin capital letter THORN,
+ U+00DE ISOlat1 -->
+<!ENTITY szlig "&#223;"> <!-- latin small letter sharp s = ess-zed,
+ U+00DF ISOlat1 -->
+<!ENTITY agrave "&#224;"> <!-- latin small letter a with grave
+ = latin small letter a grave,
+ U+00E0 ISOlat1 -->
+<!ENTITY aacute "&#225;"> <!-- latin small letter a with acute,
+ U+00E1 ISOlat1 -->
+<!ENTITY acirc "&#226;"> <!-- latin small letter a with circumflex,
+ U+00E2 ISOlat1 -->
+<!ENTITY atilde "&#227;"> <!-- latin small letter a with tilde,
+ U+00E3 ISOlat1 -->
+<!ENTITY auml "&#228;"> <!-- latin small letter a with diaeresis,
+ U+00E4 ISOlat1 -->
+<!ENTITY aring "&#229;"> <!-- latin small letter a with ring above
+ = latin small letter a ring,
+ U+00E5 ISOlat1 -->
+<!ENTITY aelig "&#230;"> <!-- latin small letter ae
+ = latin small ligature ae, U+00E6 ISOlat1 -->
+<!ENTITY ccedil "&#231;"> <!-- latin small letter c with cedilla,
+ U+00E7 ISOlat1 -->
+<!ENTITY egrave "&#232;"> <!-- latin small letter e with grave,
+ U+00E8 ISOlat1 -->
+<!ENTITY eacute "&#233;"> <!-- latin small letter e with acute,
+ U+00E9 ISOlat1 -->
+<!ENTITY ecirc "&#234;"> <!-- latin small letter e with circumflex,
+ U+00EA ISOlat1 -->
+<!ENTITY euml "&#235;"> <!-- latin small letter e with diaeresis,
+ U+00EB ISOlat1 -->
+<!ENTITY igrave "&#236;"> <!-- latin small letter i with grave,
+ U+00EC ISOlat1 -->
+<!ENTITY iacute "&#237;"> <!-- latin small letter i with acute,
+ U+00ED ISOlat1 -->
+<!ENTITY icirc "&#238;"> <!-- latin small letter i with circumflex,
+ U+00EE ISOlat1 -->
+<!ENTITY iuml "&#239;"> <!-- latin small letter i with diaeresis,
+ U+00EF ISOlat1 -->
+<!ENTITY eth "&#240;"> <!-- latin small letter eth, U+00F0 ISOlat1 -->
+<!ENTITY ntilde "&#241;"> <!-- latin small letter n with tilde,
+ U+00F1 ISOlat1 -->
+<!ENTITY ograve "&#242;"> <!-- latin small letter o with grave,
+ U+00F2 ISOlat1 -->
+<!ENTITY oacute "&#243;"> <!-- latin small letter o with acute,
+ U+00F3 ISOlat1 -->
+<!ENTITY ocirc "&#244;"> <!-- latin small letter o with circumflex,
+ U+00F4 ISOlat1 -->
+<!ENTITY otilde "&#245;"> <!-- latin small letter o with tilde,
+ U+00F5 ISOlat1 -->
+<!ENTITY ouml "&#246;"> <!-- latin small letter o with diaeresis,
+ U+00F6 ISOlat1 -->
+<!ENTITY divide "&#247;"> <!-- division sign, U+00F7 ISOnum -->
+<!ENTITY oslash "&#248;"> <!-- latin small letter o with stroke,
+ = latin small letter o slash,
+ U+00F8 ISOlat1 -->
+<!ENTITY ugrave "&#249;"> <!-- latin small letter u with grave,
+ U+00F9 ISOlat1 -->
+<!ENTITY uacute "&#250;"> <!-- latin small letter u with acute,
+ U+00FA ISOlat1 -->
+<!ENTITY ucirc "&#251;"> <!-- latin small letter u with circumflex,
+ U+00FB ISOlat1 -->
+<!ENTITY uuml "&#252;"> <!-- latin small letter u with diaeresis,
+ U+00FC ISOlat1 -->
+<!ENTITY yacute "&#253;"> <!-- latin small letter y with acute,
+ U+00FD ISOlat1 -->
+<!ENTITY thorn "&#254;"> <!-- latin small letter thorn with,
+ U+00FE ISOlat1 -->
+<!ENTITY yuml "&#255;"> <!-- latin small letter y with diaeresis,
+ U+00FF ISOlat1 -->
+
+
+<!ENTITY % xhtml-symbol
+ PUBLIC "-//W3C//ENTITIES Symbols for XHTML//EN"
+ "xhtml-symbol.ent" >
+<!-- Mathematical, Greek and Symbolic characters for HTML -->
+
+<!-- Character entity set. Typical invocation:
+ <!ENTITY % HTMLsymbol PUBLIC
+ "-//W3C//ENTITIES Symbols for XHTML//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml-symbol.ent">
+ %HTMLsymbol;
+-->
+
+<!-- Portions (C) International Organization for Standardization 1986:
+ Permission to copy in any form is granted for use with
+ conforming SGML systems and applications as defined in
+ ISO 8879, provided this notice is included in all copies.
+-->
+
+<!-- Relevant ISO entity set is given unless names are newly introduced.
+ New names (i.e., not in ISO 8879 list) do not clash with any
+ existing ISO 8879 entity names. ISO 10646 character numbers
+ are given for each character, in hex. values are decimal
+ conversions of the ISO 10646 values and refer to the document
+ character set. Names are Unicode names.
+-->
+
+<!-- Latin Extended-B -->
+<!ENTITY fnof "&#402;"> <!-- latin small f with hook = function
+ = florin, U+0192 ISOtech -->
+
+<!-- Greek -->
+<!ENTITY Alpha "&#913;"> <!-- greek capital letter alpha, U+0391 -->
+<!ENTITY Beta "&#914;"> <!-- greek capital letter beta, U+0392 -->
+<!ENTITY Gamma "&#915;"> <!-- greek capital letter gamma,
+ U+0393 ISOgrk3 -->
+<!ENTITY Delta "&#916;"> <!-- greek capital letter delta,
+ U+0394 ISOgrk3 -->
+<!ENTITY Epsilon "&#917;"> <!-- greek capital letter epsilon, U+0395 -->
+<!ENTITY Zeta "&#918;"> <!-- greek capital letter zeta, U+0396 -->
+<!ENTITY Eta "&#919;"> <!-- greek capital letter eta, U+0397 -->
+<!ENTITY Theta "&#920;"> <!-- greek capital letter theta,
+ U+0398 ISOgrk3 -->
+<!ENTITY Iota "&#921;"> <!-- greek capital letter iota, U+0399 -->
+<!ENTITY Kappa "&#922;"> <!-- greek capital letter kappa, U+039A -->
+<!ENTITY Lambda "&#923;"> <!-- greek capital letter lambda,
+ U+039B ISOgrk3 -->
+<!ENTITY Mu "&#924;"> <!-- greek capital letter mu, U+039C -->
+<!ENTITY Nu "&#925;"> <!-- greek capital letter nu, U+039D -->
+<!ENTITY Xi "&#926;"> <!-- greek capital letter xi, U+039E ISOgrk3 -->
+<!ENTITY Omicron "&#927;"> <!-- greek capital letter omicron, U+039F -->
+<!ENTITY Pi "&#928;"> <!-- greek capital letter pi, U+03A0 ISOgrk3 -->
+<!ENTITY Rho "&#929;"> <!-- greek capital letter rho, U+03A1 -->
+<!-- there is no Sigmaf, and no U+03A2 character either -->
+<!ENTITY Sigma "&#931;"> <!-- greek capital letter sigma,
+ U+03A3 ISOgrk3 -->
+<!ENTITY Tau "&#932;"> <!-- greek capital letter tau, U+03A4 -->
+<!ENTITY Upsilon "&#933;"> <!-- greek capital letter upsilon,
+ U+03A5 ISOgrk3 -->
+<!ENTITY Phi "&#934;"> <!-- greek capital letter phi,
+ U+03A6 ISOgrk3 -->
+<!ENTITY Chi "&#935;"> <!-- greek capital letter chi, U+03A7 -->
+<!ENTITY Psi "&#936;"> <!-- greek capital letter psi,
+ U+03A8 ISOgrk3 -->
+<!ENTITY Omega "&#937;"> <!-- greek capital letter omega,
+ U+03A9 ISOgrk3 -->
+
+<!ENTITY alpha "&#945;"> <!-- greek small letter alpha,
+ U+03B1 ISOgrk3 -->
+<!ENTITY beta "&#946;"> <!-- greek small letter beta, U+03B2 ISOgrk3 -->
+<!ENTITY gamma "&#947;"> <!-- greek small letter gamma,
+ U+03B3 ISOgrk3 -->
+<!ENTITY delta "&#948;"> <!-- greek small letter delta,
+ U+03B4 ISOgrk3 -->
+<!ENTITY epsilon "&#949;"> <!-- greek small letter epsilon,
+ U+03B5 ISOgrk3 -->
+<!ENTITY zeta "&#950;"> <!-- greek small letter zeta, U+03B6 ISOgrk3 -->
+<!ENTITY eta "&#951;"> <!-- greek small letter eta, U+03B7 ISOgrk3 -->
+<!ENTITY theta "&#952;"> <!-- greek small letter theta,
+ U+03B8 ISOgrk3 -->
+<!ENTITY iota "&#953;"> <!-- greek small letter iota, U+03B9 ISOgrk3 -->
+<!ENTITY kappa "&#954;"> <!-- greek small letter kappa,
+ U+03BA ISOgrk3 -->
+<!ENTITY lambda "&#955;"> <!-- greek small letter lambda,
+ U+03BB ISOgrk3 -->
+<!ENTITY mu "&#956;"> <!-- greek small letter mu, U+03BC ISOgrk3 -->
+<!ENTITY nu "&#957;"> <!-- greek small letter nu, U+03BD ISOgrk3 -->
+<!ENTITY xi "&#958;"> <!-- greek small letter xi, U+03BE ISOgrk3 -->
+<!ENTITY omicron "&#959;"> <!-- greek small letter omicron, U+03BF NEW -->
+<!ENTITY pi "&#960;"> <!-- greek small letter pi, U+03C0 ISOgrk3 -->
+<!ENTITY rho "&#961;"> <!-- greek small letter rho, U+03C1 ISOgrk3 -->
+<!ENTITY sigmaf "&#962;"> <!-- greek small letter final sigma,
+ U+03C2 ISOgrk3 -->
+<!ENTITY sigma "&#963;"> <!-- greek small letter sigma,
+ U+03C3 ISOgrk3 -->
+<!ENTITY tau "&#964;"> <!-- greek small letter tau, U+03C4 ISOgrk3 -->
+<!ENTITY upsilon "&#965;"> <!-- greek small letter upsilon,
+ U+03C5 ISOgrk3 -->
+<!ENTITY phi "&#966;"> <!-- greek small letter phi, U+03C6 ISOgrk3 -->
+<!ENTITY chi "&#967;"> <!-- greek small letter chi, U+03C7 ISOgrk3 -->
+<!ENTITY psi "&#968;"> <!-- greek small letter psi, U+03C8 ISOgrk3 -->
+<!ENTITY omega "&#969;"> <!-- greek small letter omega,
+ U+03C9 ISOgrk3 -->
+<!ENTITY thetasym "&#977;"> <!-- greek small letter theta symbol,
+ U+03D1 NEW -->
+<!ENTITY upsih "&#978;"> <!-- greek upsilon with hook symbol,
+ U+03D2 NEW -->
+<!ENTITY piv "&#982;"> <!-- greek pi symbol, U+03D6 ISOgrk3 -->
+
+<!-- General Punctuation -->
+<!ENTITY bull "&#8226;"> <!-- bullet = black small circle,
+ U+2022 ISOpub -->
+<!-- bullet is NOT the same as bullet operator, U+2219 -->
+<!ENTITY hellip "&#8230;"> <!-- horizontal ellipsis = three dot leader,
+ U+2026 ISOpub -->
+<!ENTITY prime "&#8242;"> <!-- prime = minutes = feet, U+2032 ISOtech -->
+<!ENTITY Prime "&#8243;"> <!-- double prime = seconds = inches,
+ U+2033 ISOtech -->
+<!ENTITY oline "&#8254;"> <!-- overline = spacing overscore,
+ U+203E NEW -->
+<!ENTITY frasl "&#8260;"> <!-- fraction slash, U+2044 NEW -->
+
+<!-- Letterlike Symbols -->
+<!ENTITY weierp "&#8472;"> <!-- script capital P = power set
+ = Weierstrass p, U+2118 ISOamso -->
+<!ENTITY image "&#8465;"> <!-- blackletter capital I = imaginary part,
+ U+2111 ISOamso -->
+<!ENTITY real "&#8476;"> <!-- blackletter capital R = real part symbol,
+ U+211C ISOamso -->
+<!ENTITY trade "&#8482;"> <!-- trade mark sign, U+2122 ISOnum -->
+<!ENTITY alefsym "&#8501;"> <!-- alef symbol = first transfinite cardinal,
+ U+2135 NEW -->
+<!-- alef symbol is NOT the same as hebrew letter alef,
+ U+05D0 although the same glyph could be used to depict both characters -->
+
+<!-- Arrows -->
+<!ENTITY larr "&#8592;"> <!-- leftwards arrow, U+2190 ISOnum -->
+<!ENTITY uarr "&#8593;"> <!-- upwards arrow, U+2191 ISOnum-->
+<!ENTITY rarr "&#8594;"> <!-- rightwards arrow, U+2192 ISOnum -->
+<!ENTITY darr "&#8595;"> <!-- downwards arrow, U+2193 ISOnum -->
+<!ENTITY harr "&#8596;"> <!-- left right arrow, U+2194 ISOamsa -->
+<!ENTITY crarr "&#8629;"> <!-- downwards arrow with corner leftwards
+ = carriage return, U+21B5 NEW -->
+<!ENTITY lArr "&#8656;"> <!-- leftwards double arrow, U+21D0 ISOtech -->
+<!-- Unicode does not say that lArr is the same as the 'is implied by' arrow
+ but also does not have any other character for that function. So ? lArr can
+ be used for 'is implied by' as ISOtech suggests -->
+<!ENTITY uArr "&#8657;"> <!-- upwards double arrow, U+21D1 ISOamsa -->
+<!ENTITY rArr "&#8658;"> <!-- rightwards double arrow,
+ U+21D2 ISOtech -->
+<!-- Unicode does not say this is the 'implies' character but does not have
+ another character with this function so ?
+ rArr can be used for 'implies' as ISOtech suggests -->
+<!ENTITY dArr "&#8659;"> <!-- downwards double arrow, U+21D3 ISOamsa -->
+<!ENTITY hArr "&#8660;"> <!-- left right double arrow,
+ U+21D4 ISOamsa -->
+
+<!-- Mathematical Operators -->
+<!ENTITY forall "&#8704;"> <!-- for all, U+2200 ISOtech -->
+<!ENTITY part "&#8706;"> <!-- partial differential, U+2202 ISOtech -->
+<!ENTITY exist "&#8707;"> <!-- there exists, U+2203 ISOtech -->
+<!ENTITY empty "&#8709;"> <!-- empty set = null set = diameter,
+ U+2205 ISOamso -->
+<!ENTITY nabla "&#8711;"> <!-- nabla = backward difference,
+ U+2207 ISOtech -->
+<!ENTITY isin "&#8712;"> <!-- element of, U+2208 ISOtech -->
+<!ENTITY notin "&#8713;"> <!-- not an element of, U+2209 ISOtech -->
+<!ENTITY ni "&#8715;"> <!-- contains as member, U+220B ISOtech -->
+<!-- should there be a more memorable name than 'ni'? -->
+<!ENTITY prod "&#8719;"> <!-- n-ary product = product sign,
+ U+220F ISOamsb -->
+<!-- prod is NOT the same character as U+03A0 'greek capital letter pi' though
+ the same glyph might be used for both -->
+<!ENTITY sum "&#8721;"> <!-- n-ary sumation, U+2211 ISOamsb -->
+<!-- sum is NOT the same character as U+03A3 'greek capital letter sigma'
+ though the same glyph might be used for both -->
+<!ENTITY minus "&#8722;"> <!-- minus sign, U+2212 ISOtech -->
+<!ENTITY lowast "&#8727;"> <!-- asterisk operator, U+2217 ISOtech -->
+<!ENTITY radic "&#8730;"> <!-- square root = radical sign,
+ U+221A ISOtech -->
+<!ENTITY prop "&#8733;"> <!-- proportional to, U+221D ISOtech -->
+<!ENTITY infin "&#8734;"> <!-- infinity, U+221E ISOtech -->
+<!ENTITY ang "&#8736;"> <!-- angle, U+2220 ISOamso -->
+<!ENTITY and "&#8743;"> <!-- logical and = wedge, U+2227 ISOtech -->
+<!ENTITY or "&#8744;"> <!-- logical or = vee, U+2228 ISOtech -->
+<!ENTITY cap "&#8745;"> <!-- intersection = cap, U+2229 ISOtech -->
+<!ENTITY cup "&#8746;"> <!-- union = cup, U+222A ISOtech -->
+<!ENTITY int "&#8747;"> <!-- integral, U+222B ISOtech -->
+<!ENTITY there4 "&#8756;"> <!-- therefore, U+2234 ISOtech -->
+<!ENTITY sim "&#8764;"> <!-- tilde operator = varies with = similar to,
+ U+223C ISOtech -->
+<!-- tilde operator is NOT the same character as the tilde, U+007E,
+ although the same glyph might be used to represent both -->
+<!ENTITY cong "&#8773;"> <!-- approximately equal to, U+2245 ISOtech -->
+<!ENTITY asymp "&#8776;"> <!-- almost equal to = asymptotic to,
+ U+2248 ISOamsr -->
+<!ENTITY ne "&#8800;"> <!-- not equal to, U+2260 ISOtech -->
+<!ENTITY equiv "&#8801;"> <!-- identical to, U+2261 ISOtech -->
+<!ENTITY le "&#8804;"> <!-- less-than or equal to, U+2264 ISOtech -->
+<!ENTITY ge "&#8805;"> <!-- greater-than or equal to,
+ U+2265 ISOtech -->
+<!ENTITY sub "&#8834;"> <!-- subset of, U+2282 ISOtech -->
+<!ENTITY sup "&#8835;"> <!-- superset of, U+2283 ISOtech -->
+<!-- note that nsup, 'not a superset of, U+2283' is not covered by the Symbol
+ font encoding and is not included. Should it be, for symmetry?
+ It is in ISOamsn -->
+<!ENTITY nsub "&#8836;"> <!-- not a subset of, U+2284 ISOamsn -->
+<!ENTITY sube "&#8838;"> <!-- subset of or equal to, U+2286 ISOtech -->
+<!ENTITY supe "&#8839;"> <!-- superset of or equal to,
+ U+2287 ISOtech -->
+<!ENTITY oplus "&#8853;"> <!-- circled plus = direct sum,
+ U+2295 ISOamsb -->
+<!ENTITY otimes "&#8855;"> <!-- circled times = vector product,
+ U+2297 ISOamsb -->
+<!ENTITY perp "&#8869;"> <!-- up tack = orthogonal to = perpendicular,
+ U+22A5 ISOtech -->
+<!ENTITY sdot "&#8901;"> <!-- dot operator, U+22C5 ISOamsb -->
+<!-- dot operator is NOT the same character as U+00B7 middle dot -->
+
+<!-- Miscellaneous Technical -->
+<!ENTITY lceil "&#8968;"> <!-- left ceiling = apl upstile,
+ U+2308 ISOamsc -->
+<!ENTITY rceil "&#8969;"> <!-- right ceiling, U+2309 ISOamsc -->
+<!ENTITY lfloor "&#8970;"> <!-- left floor = apl downstile,
+ U+230A ISOamsc -->
+<!ENTITY rfloor "&#8971;"> <!-- right floor, U+230B ISOamsc -->
+<!ENTITY lang "&#9001;"> <!-- left-pointing angle bracket = bra,
+ U+2329 ISOtech -->
+<!-- lang is NOT the same character as U+003C 'less than'
+ or U+2039 'single left-pointing angle quotation mark' -->
+<!ENTITY rang "&#9002;"> <!-- right-pointing angle bracket = ket,
+ U+232A ISOtech -->
+<!-- rang is NOT the same character as U+003E 'greater than'
+ or U+203A 'single right-pointing angle quotation mark' -->
+
+<!-- Geometric Shapes -->
+<!ENTITY loz "&#9674;"> <!-- lozenge, U+25CA ISOpub -->
+
+<!-- Miscellaneous Symbols -->
+<!ENTITY spades "&#9824;"> <!-- black spade suit, U+2660 ISOpub -->
+<!-- black here seems to mean filled as opposed to hollow -->
+<!ENTITY clubs "&#9827;"> <!-- black club suit = shamrock,
+ U+2663 ISOpub -->
+<!ENTITY hearts "&#9829;"> <!-- black heart suit = valentine,
+ U+2665 ISOpub -->
+<!ENTITY diams "&#9830;"> <!-- black diamond suit, U+2666 ISOpub -->
+
+
+<!ENTITY % xhtml-special
+ PUBLIC "-//W3C//ENTITIES Special for XHTML//EN"
+ "xhtml-special.ent" >
+<!-- Special characters for HTML -->
+
+<!-- Character entity set. Typical invocation:
+ <!ENTITY % HTMLspecial PUBLIC
+ "-//W3C//ENTITIES Special for XHTML//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml-special.ent">
+ %HTMLspecial;
+-->
+
+<!-- Portions (C) International Organization for Standardization 1986:
+ Permission to copy in any form is granted for use with
+ conforming SGML systems and applications as defined in
+ ISO 8879, provided this notice is included in all copies.
+-->
+
+<!-- Relevant ISO entity set is given unless names are newly introduced.
+ New names (i.e., not in ISO 8879 list) do not clash with any
+ existing ISO 8879 entity names. ISO 10646 character numbers
+ are given for each character, in hex. values are decimal
+ conversions of the ISO 10646 values and refer to the document
+ character set. Names are Unicode names.
+-->
+
+<!-- C0 Controls and Basic Latin -->
+<!ENTITY quot "&#34;"> <!-- quotation mark = APL quote,
+ U+0022 ISOnum -->
+<!ENTITY amp "&#38;#38;"> <!-- ampersand, U+0026 ISOnum -->
+<!ENTITY lt "&#38;#60;"> <!-- less-than sign, U+003C ISOnum -->
+<!ENTITY gt "&#62;"> <!-- greater-than sign, U+003E ISOnum -->
+<!ENTITY apos "&#39;"> <!-- apostrophe mark, U+0027 ISOnum -->
+
+<!-- Latin Extended-A -->
+<!ENTITY OElig "&#338;"> <!-- latin capital ligature OE,
+ U+0152 ISOlat2 -->
+<!ENTITY oelig "&#339;"> <!-- latin small ligature oe, U+0153 ISOlat2 -->
+<!-- ligature is a misnomer, this is a separate character in some languages -->
+<!ENTITY Scaron "&#352;"> <!-- latin capital letter S with caron,
+ U+0160 ISOlat2 -->
+<!ENTITY scaron "&#353;"> <!-- latin small letter s with caron,
+ U+0161 ISOlat2 -->
+<!ENTITY Yuml "&#376;"> <!-- latin capital letter Y with diaeresis,
+ U+0178 ISOlat2 -->
+
+<!-- Spacing Modifier Letters -->
+<!ENTITY circ "&#710;"> <!-- modifier letter circumflex accent,
+ U+02C6 ISOpub -->
+<!ENTITY tilde "&#732;"> <!-- small tilde, U+02DC ISOdia -->
+
+<!-- General Punctuation -->
+<!ENTITY ensp "&#8194;"> <!-- en space, U+2002 ISOpub -->
+<!ENTITY emsp "&#8195;"> <!-- em space, U+2003 ISOpub -->
+<!ENTITY thinsp "&#8201;"> <!-- thin space, U+2009 ISOpub -->
+<!ENTITY zwnj "&#8204;"> <!-- zero width non-joiner,
+ U+200C NEW RFC 2070 -->
+<!ENTITY zwj "&#8205;"> <!-- zero width joiner, U+200D NEW RFC 2070 -->
+<!ENTITY lrm "&#8206;"> <!-- left-to-right mark, U+200E NEW RFC 2070 -->
+<!ENTITY rlm "&#8207;"> <!-- right-to-left mark, U+200F NEW RFC 2070 -->
+<!ENTITY ndash "&#8211;"> <!-- en dash, U+2013 ISOpub -->
+<!ENTITY mdash "&#8212;"> <!-- em dash, U+2014 ISOpub -->
+<!ENTITY lsquo "&#8216;"> <!-- left single quotation mark,
+ U+2018 ISOnum -->
+<!ENTITY rsquo "&#8217;"> <!-- right single quotation mark,
+ U+2019 ISOnum -->
+<!ENTITY sbquo "&#8218;"> <!-- single low-9 quotation mark, U+201A NEW -->
+<!ENTITY ldquo "&#8220;"> <!-- left double quotation mark,
+ U+201C ISOnum -->
+<!ENTITY rdquo "&#8221;"> <!-- right double quotation mark,
+ U+201D ISOnum -->
+<!ENTITY bdquo "&#8222;"> <!-- double low-9 quotation mark, U+201E NEW -->
+<!ENTITY dagger "&#8224;"> <!-- dagger, U+2020 ISOpub -->
+<!ENTITY Dagger "&#8225;"> <!-- double dagger, U+2021 ISOpub -->
+<!ENTITY permil "&#8240;"> <!-- per mille sign, U+2030 ISOtech -->
+<!ENTITY lsaquo "&#8249;"> <!-- single left-pointing angle quotation mark,
+ U+2039 ISO proposed -->
+<!-- lsaquo is proposed but not yet ISO standardized -->
+<!ENTITY rsaquo "&#8250;"> <!-- single right-pointing angle quotation mark,
+ U+203A ISO proposed -->
+<!-- rsaquo is proposed but not yet ISO standardized -->
+<!ENTITY euro "&#8364;"> <!-- euro sign, U+20AC NEW -->
+
+
+<!-- end of xhtml-charent-1.mod -->
+]]>
+
+<!-- end of xhtml-framework-1.mod -->
+]]>
+
+<!-- Post-Framework Redeclaration placeholder ................... -->
+<!-- this serves as a location to insert markup declarations
+ into the DTD following the framework declarations.
+-->
+<!ENTITY % xhtml-postfw-redecl.module "IGNORE" >
+<![%xhtml-postfw-redecl.module;[
+%xhtml-postfw-redecl.mod;
+<!-- end of xhtml-postfw-redecl.module -->]]>
+
+<!-- Text Module (Required) ..................................... -->
+<!ENTITY % xhtml-text.module "INCLUDE" >
+<![%xhtml-text.module;[
+<!ENTITY % xhtml-text.mod
+ PUBLIC "-//W3C//ELEMENTS XHTML Text 1.0//EN"
+ "http://www.w3.org/TR/xhtml-modularization/DTD/xhtml-text-1.mod" >
+<!-- ...................................................................... -->
+<!-- XHTML Text Module ................................................... -->
+<!-- file: xhtml-text-1.mod
+
+ This is XHTML, a reformulation of HTML as a modular XML application.
+ Copyright 1998-2001 W3C (MIT, INRIA, Keio), All Rights Reserved.
+ Revision: $Id: xhtml-text-1.mod,v 4.0 2001/04/02 22:42:49 altheim Exp $ SMI
+
+ This DTD module is identified by the PUBLIC and SYSTEM identifiers:
+
+ PUBLIC "-//W3C//ELEMENTS XHTML Text 1.0//EN"
+ SYSTEM "http://www.w3.org/TR/xhtml-modularization/DTD/xhtml-text-1.mod"
+
+ Revisions:
+ (none)
+ ....................................................................... -->
+
+<!-- Textual Content
+
+ The Text module includes declarations for all core
+ text container elements and their attributes.
+-->
+
+<!ENTITY % xhtml-inlstruct.module "INCLUDE" >
+<![%xhtml-inlstruct.module;[
+<!ENTITY % xhtml-inlstruct.mod
+ PUBLIC "-//W3C//ELEMENTS XHTML Inline Structural 1.0//EN"
+ "xhtml-inlstruct-1.mod" >
+<!-- ...................................................................... -->
+<!-- XHTML Inline Structural Module ...................................... -->
+<!-- file: xhtml-inlstruct-1.mod
+
+ This is XHTML, a reformulation of HTML as a modular XML application.
+ Copyright 1998-2001 W3C (MIT, INRIA, Keio), All Rights Reserved.
+ Revision: $Id: xhtml-inlstruct-1.mod,v 4.0 2001/04/02 22:42:49 altheim Exp $ SMI
+
+ This DTD module is identified by the PUBLIC and SYSTEM identifiers:
+
+ PUBLIC "-//W3C//ELEMENTS XHTML Inline Structural 1.0//EN"
+ SYSTEM "http://www.w3.org/TR/xhtml-modularization/DTD/xhtml-inlstruct-1.mod"
+
+ Revisions:
+ (none)
+ ....................................................................... -->
+
+<!-- Inline Structural
+
+ br, span
+
+ This module declares the elements and their attributes
+ used to support inline-level structural markup.
+-->
+
+<!-- br: forced line break ............................. -->
+
+<!ENTITY % br.element "INCLUDE" >
+<![%br.element;[
+
+<!ENTITY % br.content "EMPTY" >
+<!ENTITY % br.qname "br" >
+<!ELEMENT %br.qname; %br.content; >
+
+<!-- end of br.element -->]]>
+
+<!ENTITY % br.attlist "INCLUDE" >
+<![%br.attlist;[
+<!ATTLIST %br.qname;
+ %Core.attrib;
+>
+<!-- end of br.attlist -->]]>
+
+<!-- span: generic inline container .................... -->
+
+<!ENTITY % span.element "INCLUDE" >
+<![%span.element;[
+<!ENTITY % span.content
+ "( #PCDATA | %Inline.mix; )*"
+>
+<!ENTITY % span.qname "span" >
+<!ELEMENT %span.qname; %span.content; >
+<!-- end of span.element -->]]>
+
+<!ENTITY % span.attlist "INCLUDE" >
+<![%span.attlist;[
+<!ATTLIST %span.qname;
+ %Common.attrib;
+>
+<!-- end of span.attlist -->]]>
+
+<!-- end of xhtml-inlstruct-1.mod -->
+]]>
+
+<!ENTITY % xhtml-inlphras.module "INCLUDE" >
+<![%xhtml-inlphras.module;[
+<!ENTITY % xhtml-inlphras.mod
+ PUBLIC "-//W3C//ELEMENTS XHTML Inline Phrasal 1.0//EN"
+ "xhtml-inlphras-1.mod" >
+<!-- ...................................................................... -->
+<!-- XHTML Inline Phrasal Module ......................................... -->
+<!-- file: xhtml-inlphras-1.mod
+
+ This is XHTML, a reformulation of HTML as a modular XML application.
+ Copyright 1998-2001 W3C (MIT, INRIA, Keio), All Rights Reserved.
+ Revision: $Id: xhtml-inlphras-1.mod,v 4.0 2001/04/02 22:42:49 altheim Exp $ SMI
+
+ This DTD module is identified by the PUBLIC and SYSTEM identifiers:
+
+ PUBLIC "-//W3C//ELEMENTS XHTML Inline Phrasal 1.0//EN"
+ SYSTEM "http://www.w3.org/TR/xhtml-modularization/DTD/xhtml-inlphras-1.mod"
+
+ Revisions:
+ (none)
+ ....................................................................... -->
+
+<!-- Inline Phrasal
+
+ abbr, acronym, cite, code, dfn, em, kbd, q, samp, strong, var
+
+ This module declares the elements and their attributes used to
+ support inline-level phrasal markup.
+-->
+
+<!ENTITY % abbr.element "INCLUDE" >
+<![%abbr.element;[
+<!ENTITY % abbr.content
+ "( #PCDATA | %Inline.mix; )*"
+>
+<!ENTITY % abbr.qname "abbr" >
+<!ELEMENT %abbr.qname; %abbr.content; >
+<!-- end of abbr.element -->]]>
+
+<!ENTITY % abbr.attlist "INCLUDE" >
+<![%abbr.attlist;[
+<!ATTLIST %abbr.qname;
+ %Common.attrib;
+>
+<!-- end of abbr.attlist -->]]>
+
+<!ENTITY % acronym.element "INCLUDE" >
+<![%acronym.element;[
+<!ENTITY % acronym.content
+ "( #PCDATA | %Inline.mix; )*"
+>
+<!ENTITY % acronym.qname "acronym" >
+<!ELEMENT %acronym.qname; %acronym.content; >
+<!-- end of acronym.element -->]]>
+
+<!ENTITY % acronym.attlist "INCLUDE" >
+<![%acronym.attlist;[
+<!ATTLIST %acronym.qname;
+ %Common.attrib;
+>
+<!-- end of acronym.attlist -->]]>
+
+<!ENTITY % cite.element "INCLUDE" >
+<![%cite.element;[
+<!ENTITY % cite.content
+ "( #PCDATA | %Inline.mix; )*"
+>
+<!ENTITY % cite.qname "cite" >
+<!ELEMENT %cite.qname; %cite.content; >
+<!-- end of cite.element -->]]>
+
+<!ENTITY % cite.attlist "INCLUDE" >
+<![%cite.attlist;[
+<!ATTLIST %cite.qname;
+ %Common.attrib;
+>
+<!-- end of cite.attlist -->]]>
+
+<!ENTITY % code.element "INCLUDE" >
+<![%code.element;[
+<!ENTITY % code.content
+ "( #PCDATA | %Inline.mix; )*"
+>
+<!ENTITY % code.qname "code" >
+<!ELEMENT %code.qname; %code.content; >
+<!-- end of code.element -->]]>
+
+<!ENTITY % code.attlist "INCLUDE" >
+<![%code.attlist;[
+<!ATTLIST %code.qname;
+ %Common.attrib;
+>
+<!-- end of code.attlist -->]]>
+
+<!ENTITY % dfn.element "INCLUDE" >
+<![%dfn.element;[
+<!ENTITY % dfn.content
+ "( #PCDATA | %Inline.mix; )*"
+>
+<!ENTITY % dfn.qname "dfn" >
+<!ELEMENT %dfn.qname; %dfn.content; >
+<!-- end of dfn.element -->]]>
+
+<!ENTITY % dfn.attlist "INCLUDE" >
+<![%dfn.attlist;[
+<!ATTLIST %dfn.qname;
+ %Common.attrib;
+>
+<!-- end of dfn.attlist -->]]>
+
+<!ENTITY % em.element "INCLUDE" >
+<![%em.element;[
+<!ENTITY % em.content
+ "( #PCDATA | %Inline.mix; )*"
+>
+<!ENTITY % em.qname "em" >
+<!ELEMENT %em.qname; %em.content; >
+<!-- end of em.element -->]]>
+
+<!ENTITY % em.attlist "INCLUDE" >
+<![%em.attlist;[
+<!ATTLIST %em.qname;
+ %Common.attrib;
+>
+<!-- end of em.attlist -->]]>
+
+<!ENTITY % kbd.element "INCLUDE" >
+<![%kbd.element;[
+<!ENTITY % kbd.content
+ "( #PCDATA | %Inline.mix; )*"
+>
+<!ENTITY % kbd.qname "kbd" >
+<!ELEMENT %kbd.qname; %kbd.content; >
+<!-- end of kbd.element -->]]>
+
+<!ENTITY % kbd.attlist "INCLUDE" >
+<![%kbd.attlist;[
+<!ATTLIST %kbd.qname;
+ %Common.attrib;
+>
+<!-- end of kbd.attlist -->]]>
+
+<!ENTITY % q.element "INCLUDE" >
+<![%q.element;[
+<!ENTITY % q.content
+ "( #PCDATA | %Inline.mix; )*"
+>
+<!ENTITY % q.qname "q" >
+<!ELEMENT %q.qname; %q.content; >
+<!-- end of q.element -->]]>
+
+<!ENTITY % q.attlist "INCLUDE" >
+<![%q.attlist;[
+<!ATTLIST %q.qname;
+ %Common.attrib;
+ cite %URI.datatype; #IMPLIED
+>
+<!-- end of q.attlist -->]]>
+
+<!ENTITY % samp.element "INCLUDE" >
+<![%samp.element;[
+<!ENTITY % samp.content
+ "( #PCDATA | %Inline.mix; )*"
+>
+<!ENTITY % samp.qname "samp" >
+<!ELEMENT %samp.qname; %samp.content; >
+<!-- end of samp.element -->]]>
+
+<!ENTITY % samp.attlist "INCLUDE" >
+<![%samp.attlist;[
+<!ATTLIST %samp.qname;
+ %Common.attrib;
+>
+<!-- end of samp.attlist -->]]>
+
+<!ENTITY % strong.element "INCLUDE" >
+<![%strong.element;[
+<!ENTITY % strong.content
+ "( #PCDATA | %Inline.mix; )*"
+>
+<!ENTITY % strong.qname "strong" >
+<!ELEMENT %strong.qname; %strong.content; >
+<!-- end of strong.element -->]]>
+
+<!ENTITY % strong.attlist "INCLUDE" >
+<![%strong.attlist;[
+<!ATTLIST %strong.qname;
+ %Common.attrib;
+>
+<!-- end of strong.attlist -->]]>
+
+<!ENTITY % var.element "INCLUDE" >
+<![%var.element;[
+<!ENTITY % var.content
+ "( #PCDATA | %Inline.mix; )*"
+>
+<!ENTITY % var.qname "var" >
+<!ELEMENT %var.qname; %var.content; >
+<!-- end of var.element -->]]>
+
+<!ENTITY % var.attlist "INCLUDE" >
+<![%var.attlist;[
+<!ATTLIST %var.qname;
+ %Common.attrib;
+>
+<!-- end of var.attlist -->]]>
+
+<!-- end of xhtml-inlphras-1.mod -->
+]]>
+
+<!ENTITY % xhtml-blkstruct.module "INCLUDE" >
+<![%xhtml-blkstruct.module;[
+<!ENTITY % xhtml-blkstruct.mod
+ PUBLIC "-//W3C//ELEMENTS XHTML Block Structural 1.0//EN"
+ "xhtml-blkstruct-1.mod" >
+<!-- ...................................................................... -->
+<!-- XHTML Block Structural Module ....................................... -->
+<!-- file: xhtml-blkstruct-1.mod
+
+ This is XHTML, a reformulation of HTML as a modular XML application.
+ Copyright 1998-2001 W3C (MIT, INRIA, Keio), All Rights Reserved.
+ Revision: $Id: xhtml-blkstruct-1.mod,v 4.0 2001/04/02 22:42:49 altheim Exp $ SMI
+
+ This DTD module is identified by the PUBLIC and SYSTEM identifiers:
+
+ PUBLIC "-//W3C//ELEMENTS XHTML Block Structural 1.0//EN"
+ SYSTEM "http://www.w3.org/TR/xhtml-modularization/DTD/xhtml-blkstruct-1.mod"
+
+ Revisions:
+ (none)
+ ....................................................................... -->
+
+<!-- Block Structural
+
+ div, p
+
+ This module declares the elements and their attributes used to
+ support block-level structural markup.
+-->
+
+<!ENTITY % div.element "INCLUDE" >
+<![%div.element;[
+<!ENTITY % div.content
+ "( #PCDATA | %Flow.mix; )*"
+>
+<!ENTITY % div.qname "div" >
+<!ELEMENT %div.qname; %div.content; >
+<!-- end of div.element -->]]>
+
+<!ENTITY % div.attlist "INCLUDE" >
+<![%div.attlist;[
+<!ATTLIST %div.qname;
+ %Common.attrib;
+>
+<!-- end of div.attlist -->]]>
+
+<!ENTITY % p.element "INCLUDE" >
+<![%p.element;[
+<!ENTITY % p.content
+ "( #PCDATA | %Inline.mix; )*" >
+<!ENTITY % p.qname "p" >
+<!ELEMENT %p.qname; %p.content; >
+<!-- end of p.element -->]]>
+
+<!ENTITY % p.attlist "INCLUDE" >
+<![%p.attlist;[
+<!ATTLIST %p.qname;
+ %Common.attrib;
+>
+<!-- end of p.attlist -->]]>
+
+<!-- end of xhtml-blkstruct-1.mod -->
+]]>
+
+<!ENTITY % xhtml-blkphras.module "INCLUDE" >
+<![%xhtml-blkphras.module;[
+<!ENTITY % xhtml-blkphras.mod
+ PUBLIC "-//W3C//ELEMENTS XHTML Block Phrasal 1.0//EN"
+ "xhtml-blkphras-1.mod" >
+<!-- ...................................................................... -->
+<!-- XHTML Block Phrasal Module .......................................... -->
+<!-- file: xhtml-blkphras-1.mod
+
+ This is XHTML, a reformulation of HTML as a modular XML application.
+ Copyright 1998-2001 W3C (MIT, INRIA, Keio), All Rights Reserved.
+ Revision: $Id: xhtml-blkphras-1.mod,v 4.0 2001/04/02 22:42:49 altheim Exp $ SMI
+
+ This DTD module is identified by the PUBLIC and SYSTEM identifiers:
+
+ PUBLIC "-//W3C//ELEMENTS XHTML Block Phrasal 1.0//EN"
+ SYSTEM "http://www.w3.org/TR/xhtml-modularization/DTD/xhtml-blkphras-1.mod"
+
+ Revisions:
+ (none)
+ ....................................................................... -->
+
+<!-- Block Phrasal
+
+ address, blockquote, pre, h1, h2, h3, h4, h5, h6
+
+ This module declares the elements and their attributes used to
+ support block-level phrasal markup.
+-->
+
+<!ENTITY % address.element "INCLUDE" >
+<![%address.element;[
+<!ENTITY % address.content
+ "( #PCDATA | %Inline.mix; )*" >
+<!ENTITY % address.qname "address" >
+<!ELEMENT %address.qname; %address.content; >
+<!-- end of address.element -->]]>
+
+<!ENTITY % address.attlist "INCLUDE" >
+<![%address.attlist;[
+<!ATTLIST %address.qname;
+ %Common.attrib;
+>
+<!-- end of address.attlist -->]]>
+
+<!ENTITY % blockquote.element "INCLUDE" >
+<![%blockquote.element;[
+<!ENTITY % blockquote.content
+ "( %Block.mix; )+"
+>
+<!ENTITY % blockquote.qname "blockquote" >
+<!ELEMENT %blockquote.qname; %blockquote.content; >
+<!-- end of blockquote.element -->]]>
+
+<!ENTITY % blockquote.attlist "INCLUDE" >
+<![%blockquote.attlist;[
+<!ATTLIST %blockquote.qname;
+ %Common.attrib;
+ cite %URI.datatype; #IMPLIED
+>
+<!-- end of blockquote.attlist -->]]>
+
+<!ENTITY % pre.element "INCLUDE" >
+<![%pre.element;[
+<!ENTITY % pre.content
+ "( #PCDATA
+ | %InlStruct.class;
+ %InlPhras.class;
+ | %tt.qname; | %i.qname; | %b.qname;
+ %I18n.class;
+ %Anchor.class;
+ | %script.qname; | %map.qname;
+ %Inline.extra; )*"
+>
+<!ENTITY % pre.qname "pre" >
+<!ELEMENT %pre.qname; %pre.content; >
+<!-- end of pre.element -->]]>
+
+<!ENTITY % pre.attlist "INCLUDE" >
+<![%pre.attlist;[
+<!ATTLIST %pre.qname;
+ %Common.attrib;
+ xml:space ( preserve ) #FIXED 'preserve'
+>
+<!-- end of pre.attlist -->]]>
+
+<!-- ................... Heading Elements ................... -->
+
+<!ENTITY % Heading.content "( #PCDATA | %Inline.mix; )*" >
+
+<!ENTITY % h1.element "INCLUDE" >
+<![%h1.element;[
+<!ENTITY % h1.qname "h1" >
+<!ELEMENT %h1.qname; %Heading.content; >
+<!-- end of h1.element -->]]>
+
+<!ENTITY % h1.attlist "INCLUDE" >
+<![%h1.attlist;[
+<!ATTLIST %h1.qname;
+ %Common.attrib;
+>
+<!-- end of h1.attlist -->]]>
+
+<!ENTITY % h2.element "INCLUDE" >
+<![%h2.element;[
+<!ENTITY % h2.qname "h2" >
+<!ELEMENT %h2.qname; %Heading.content; >
+<!-- end of h2.element -->]]>
+
+<!ENTITY % h2.attlist "INCLUDE" >
+<![%h2.attlist;[
+<!ATTLIST %h2.qname;
+ %Common.attrib;
+>
+<!-- end of h2.attlist -->]]>
+
+<!ENTITY % h3.element "INCLUDE" >
+<![%h3.element;[
+<!ENTITY % h3.qname "h3" >
+<!ELEMENT %h3.qname; %Heading.content; >
+<!-- end of h3.element -->]]>
+
+<!ENTITY % h3.attlist "INCLUDE" >
+<![%h3.attlist;[
+<!ATTLIST %h3.qname;
+ %Common.attrib;
+>
+<!-- end of h3.attlist -->]]>
+
+<!ENTITY % h4.element "INCLUDE" >
+<![%h4.element;[
+<!ENTITY % h4.qname "h4" >
+<!ELEMENT %h4.qname; %Heading.content; >
+<!-- end of h4.element -->]]>
+
+<!ENTITY % h4.attlist "INCLUDE" >
+<![%h4.attlist;[
+<!ATTLIST %h4.qname;
+ %Common.attrib;
+>
+<!-- end of h4.attlist -->]]>
+
+<!ENTITY % h5.element "INCLUDE" >
+<![%h5.element;[
+<!ENTITY % h5.qname "h5" >
+<!ELEMENT %h5.qname; %Heading.content; >
+<!-- end of h5.element -->]]>
+
+<!ENTITY % h5.attlist "INCLUDE" >
+<![%h5.attlist;[
+<!ATTLIST %h5.qname;
+ %Common.attrib;
+>
+<!-- end of h5.attlist -->]]>
+
+<!ENTITY % h6.element "INCLUDE" >
+<![%h6.element;[
+<!ENTITY % h6.qname "h6" >
+<!ELEMENT %h6.qname; %Heading.content; >
+<!-- end of h6.element -->]]>
+
+<!ENTITY % h6.attlist "INCLUDE" >
+<![%h6.attlist;[
+<!ATTLIST %h6.qname;
+ %Common.attrib;
+>
+<!-- end of h6.attlist -->]]>
+
+<!-- end of xhtml-blkphras-1.mod -->
+]]>
+
+<!-- end of xhtml-text-1.mod -->
+]]>
+
+<!-- Hypertext Module (required) ................................. -->
+<!ENTITY % xhtml-hypertext.module "INCLUDE" >
+<![%xhtml-hypertext.module;[
+<!ENTITY % xhtml-hypertext.mod
+ PUBLIC "-//W3C//ELEMENTS XHTML Hypertext 1.0//EN"
+ "http://www.w3.org/TR/xhtml-modularization/DTD/xhtml-hypertext-1.mod" >
+<!-- ...................................................................... -->
+<!-- XHTML Hypertext Module .............................................. -->
+<!-- file: xhtml-hypertext-1.mod
+
+ This is XHTML, a reformulation of HTML as a modular XML application.
+ Copyright 1998-2001 W3C (MIT, INRIA, Keio), All Rights Reserved.
+ Revision: $Id: xhtml-hypertext-1.mod,v 4.0 2001/04/02 22:42:49 altheim Exp $ SMI
+
+ This DTD module is identified by the PUBLIC and SYSTEM identifiers:
+
+ PUBLIC "-//W3C//ELEMENTS XHTML Hypertext 1.0//EN"
+ SYSTEM "http://www.w3.org/TR/xhtml-modularization/DTD/xhtml-hypertext-1.mod"
+
+ Revisions:
+ (none)
+ ....................................................................... -->
+
+<!-- Hypertext
+
+ a
+
+ This module declares the anchor ('a') element type, which
+ defines the source of a hypertext link. The destination
+ (or link 'target') is identified via its 'id' attribute
+ rather than the 'name' attribute as was used in HTML.
+-->
+
+<!-- ............ Anchor Element ............ -->
+
+<!ENTITY % a.element "INCLUDE" >
+<![%a.element;[
+<!ENTITY % a.content
+ "( #PCDATA | %InlNoAnchor.mix; )*"
+>
+<!ENTITY % a.qname "a" >
+<!ELEMENT %a.qname; %a.content; >
+<!-- end of a.element -->]]>
+
+<!ENTITY % a.attlist "INCLUDE" >
+<![%a.attlist;[
+<!ATTLIST %a.qname;
+ %Common.attrib;
+ href %URI.datatype; #IMPLIED
+ charset %Charset.datatype; #IMPLIED
+ type %ContentType.datatype; #IMPLIED
+ hreflang %LanguageCode.datatype; #IMPLIED
+ rel %LinkTypes.datatype; #IMPLIED
+ rev %LinkTypes.datatype; #IMPLIED
+ accesskey %Character.datatype; #IMPLIED
+ tabindex %Number.datatype; #IMPLIED
+>
+<!-- end of a.attlist -->]]>
+
+<!-- end of xhtml-hypertext-1.mod -->
+]]>
+
+<!-- Lists Module (required) .................................... -->
+<!ENTITY % xhtml-list.module "INCLUDE" >
+<![%xhtml-list.module;[
+<!ENTITY % xhtml-list.mod
+ PUBLIC "-//W3C//ELEMENTS XHTML Lists 1.0//EN"
+ "http://www.w3.org/TR/xhtml-modularization/DTD/xhtml-list-1.mod" >
+<!-- ...................................................................... -->
+<!-- XHTML Lists Module .................................................. -->
+<!-- file: xhtml-list-1.mod
+
+ This is XHTML, a reformulation of HTML as a modular XML application.
+ Copyright 1998-2001 W3C (MIT, INRIA, Keio), All Rights Reserved.
+ Revision: $Id: xhtml-list-1.mod,v 4.0 2001/04/02 22:42:49 altheim Exp $ SMI
+
+ This DTD module is identified by the PUBLIC and SYSTEM identifiers:
+
+ PUBLIC "-//W3C//ELEMENTS XHTML Lists 1.0//EN"
+ SYSTEM "http://www.w3.org/TR/xhtml-modularization/DTD/xhtml-list-1.mod"
+
+ Revisions:
+ (none)
+ ....................................................................... -->
+
+<!-- Lists
+
+ dl, dt, dd, ol, ul, li
+
+ This module declares the list-oriented element types
+ and their attributes.
+-->
+
+<!ENTITY % dl.qname "dl" >
+<!ENTITY % dt.qname "dt" >
+<!ENTITY % dd.qname "dd" >
+<!ENTITY % ol.qname "ol" >
+<!ENTITY % ul.qname "ul" >
+<!ENTITY % li.qname "li" >
+
+<!-- dl: Definition List ............................... -->
+
+<!ENTITY % dl.element "INCLUDE" >
+<![%dl.element;[
+<!ENTITY % dl.content "( %dt.qname; | %dd.qname; )+" >
+<!ELEMENT %dl.qname; %dl.content; >
+<!-- end of dl.element -->]]>
+
+<!ENTITY % dl.attlist "INCLUDE" >
+<![%dl.attlist;[
+<!ATTLIST %dl.qname;
+ %Common.attrib;
+>
+<!-- end of dl.attlist -->]]>
+
+<!-- dt: Definition Term ............................... -->
+
+<!ENTITY % dt.element "INCLUDE" >
+<![%dt.element;[
+<!ENTITY % dt.content
+ "( #PCDATA | %Inline.mix; )*"
+>
+<!ELEMENT %dt.qname; %dt.content; >
+<!-- end of dt.element -->]]>
+
+<!ENTITY % dt.attlist "INCLUDE" >
+<![%dt.attlist;[
+<!ATTLIST %dt.qname;
+ %Common.attrib;
+>
+<!-- end of dt.attlist -->]]>
+
+<!-- dd: Definition Description ........................ -->
+
+<!ENTITY % dd.element "INCLUDE" >
+<![%dd.element;[
+<!ENTITY % dd.content
+ "( #PCDATA | %Flow.mix; )*"
+>
+<!ELEMENT %dd.qname; %dd.content; >
+<!-- end of dd.element -->]]>
+
+<!ENTITY % dd.attlist "INCLUDE" >
+<![%dd.attlist;[
+<!ATTLIST %dd.qname;
+ %Common.attrib;
+>
+<!-- end of dd.attlist -->]]>
+
+<!-- ol: Ordered List (numbered styles) ................ -->
+
+<!ENTITY % ol.element "INCLUDE" >
+<![%ol.element;[
+<!ENTITY % ol.content "( %li.qname; )+" >
+<!ELEMENT %ol.qname; %ol.content; >
+<!-- end of ol.element -->]]>
+
+<!ENTITY % ol.attlist "INCLUDE" >
+<![%ol.attlist;[
+<!ATTLIST %ol.qname;
+ %Common.attrib;
+>
+<!-- end of ol.attlist -->]]>
+
+<!-- ul: Unordered List (bullet styles) ................ -->
+
+<!ENTITY % ul.element "INCLUDE" >
+<![%ul.element;[
+<!ENTITY % ul.content "( %li.qname; )+" >
+<!ELEMENT %ul.qname; %ul.content; >
+<!-- end of ul.element -->]]>
+
+<!ENTITY % ul.attlist "INCLUDE" >
+<![%ul.attlist;[
+<!ATTLIST %ul.qname;
+ %Common.attrib;
+>
+<!-- end of ul.attlist -->]]>
+
+<!-- li: List Item ..................................... -->
+
+<!ENTITY % li.element "INCLUDE" >
+<![%li.element;[
+<!ENTITY % li.content
+ "( #PCDATA | %Flow.mix; )*"
+>
+<!ELEMENT %li.qname; %li.content; >
+<!-- end of li.element -->]]>
+
+<!ENTITY % li.attlist "INCLUDE" >
+<![%li.attlist;[
+<!ATTLIST %li.qname;
+ %Common.attrib;
+>
+<!-- end of li.attlist -->]]>
+
+<!-- end of xhtml-list-1.mod -->
+]]>
+
+<!-- ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: -->
+
+<!-- Edit Module ................................................ -->
+<!ENTITY % xhtml-edit.module "INCLUDE" >
+<![%xhtml-edit.module;[
+<!ENTITY % xhtml-edit.mod
+ PUBLIC "-//W3C//ELEMENTS XHTML Editing Elements 1.0//EN"
+ "http://www.w3.org/TR/xhtml-modularization/DTD/xhtml-edit-1.mod" >
+<!-- ...................................................................... -->
+<!-- XHTML Editing Elements Module ....................................... -->
+<!-- file: xhtml-edit-1.mod
+
+ This is XHTML, a reformulation of HTML as a modular XML application.
+ Copyright 1998-2001 W3C (MIT, INRIA, Keio), All Rights Reserved.
+ Revision: $Id: xhtml-edit-1.mod,v 4.0 2001/04/02 22:42:49 altheim Exp $ SMI
+
+ This DTD module is identified by the PUBLIC and SYSTEM identifiers:
+
+ PUBLIC "-//W3C//ELEMENTS XHTML Editing Markup 1.0//EN"
+ SYSTEM "http://www.w3.org/TR/xhtml-modularization/DTD/xhtml-edit-1.mod"
+
+ Revisions:
+ (none)
+ ....................................................................... -->
+
+<!-- Editing Elements
+
+ ins, del
+
+ This module declares element types and attributes used to indicate
+ inserted and deleted content while editing a document.
+-->
+
+<!-- ins: Inserted Text ............................... -->
+
+<!ENTITY % ins.element "INCLUDE" >
+<![%ins.element;[
+<!ENTITY % ins.content
+ "( #PCDATA | %Flow.mix; )*"
+>
+<!ENTITY % ins.qname "ins" >
+<!ELEMENT %ins.qname; %ins.content; >
+<!-- end of ins.element -->]]>
+
+<!ENTITY % ins.attlist "INCLUDE" >
+<![%ins.attlist;[
+<!ATTLIST %ins.qname;
+ %Common.attrib;
+ cite %URI.datatype; #IMPLIED
+ datetime %Datetime.datatype; #IMPLIED
+>
+<!-- end of ins.attlist -->]]>
+
+<!-- del: Deleted Text ................................ -->
+
+<!ENTITY % del.element "INCLUDE" >
+<![%del.element;[
+<!ENTITY % del.content
+ "( #PCDATA | %Flow.mix; )*"
+>
+<!ENTITY % del.qname "del" >
+<!ELEMENT %del.qname; %del.content; >
+<!-- end of del.element -->]]>
+
+<!ENTITY % del.attlist "INCLUDE" >
+<![%del.attlist;[
+<!ATTLIST %del.qname;
+ %Common.attrib;
+ cite %URI.datatype; #IMPLIED
+ datetime %Datetime.datatype; #IMPLIED
+>
+<!-- end of del.attlist -->]]>
+
+<!-- end of xhtml-edit-1.mod -->
+]]>
+
+<!-- BIDI Override Module ....................................... -->
+<!ENTITY % xhtml-bdo.module "%XHTML.bidi;" >
+<![%xhtml-bdo.module;[
+<!ENTITY % xhtml-bdo.mod
+ PUBLIC "-//W3C//ELEMENTS XHTML BIDI Override Element 1.0//EN"
+ "http://www.w3.org/TR/xhtml-modularization/DTD/xhtml-bdo-1.mod" >
+<!-- ...................................................................... -->
+<!-- XHTML BDO Element Module ............................................. -->
+<!-- file: xhtml-bdo-1.mod
+
+ This is XHTML, a reformulation of HTML as a modular XML application.
+ Copyright 1998-2001 W3C (MIT, INRIA, Keio), All Rights Reserved.
+ Revision: $Id: xhtml-bdo-1.mod,v 4.0 2001/04/02 22:42:49 altheim Exp $ SMI
+
+ This DTD module is identified by the PUBLIC and SYSTEM identifiers:
+
+ PUBLIC "-//W3C//ELEMENTS XHTML BDO Element 1.0//EN"
+ SYSTEM "http://www.w3.org/TR/xhtml-modularization/DTD/xhtml-bdo-1.mod"
+
+ Revisions:
+ (none)
+ ....................................................................... -->
+
+<!-- Bidirectional Override (bdo) Element
+
+ This modules declares the element 'bdo', used to override the
+ Unicode bidirectional algorithm for selected fragments of text.
+
+ DEPENDENCIES:
+ Relies on the conditional section keyword %XHTML.bidi; declared
+ as "INCLUDE". Bidirectional text support includes both the bdo
+ element and the 'dir' attribute.
+-->
+
+<!ENTITY % bdo.element "INCLUDE" >
+<![%bdo.element;[
+<!ENTITY % bdo.content
+ "( #PCDATA | %Inline.mix; )*"
+>
+<!ENTITY % bdo.qname "bdo" >
+<!ELEMENT %bdo.qname; %bdo.content; >
+<!-- end of bdo.element -->]]>
+
+<!ENTITY % bdo.attlist "INCLUDE" >
+<![%bdo.attlist;[
+<!ATTLIST %bdo.qname;
+ %Core.attrib;
+ xml:lang %LanguageCode.datatype; #IMPLIED
+ dir ( ltr | rtl ) #REQUIRED
+>
+]]>
+
+<!-- end of xhtml-bdo-1.mod -->
+]]>
+
+<!-- Ruby Module ................................................ -->
+<!ENTITY % Ruby.common.attlists "INCLUDE" >
+<!ENTITY % Ruby.common.attrib "%Common.attrib;" >
+<!ENTITY % xhtml-ruby.module "INCLUDE" >
+<![%xhtml-ruby.module;[
+<!ENTITY % xhtml-ruby.mod
+ PUBLIC "-//W3C//ELEMENTS XHTML Ruby 1.0//EN"
+ "http://www.w3.org/TR/ruby/xhtml-ruby-1.mod" >
+<!-- ...................................................................... -->
+<!-- XHTML Ruby Module .................................................... -->
+<!-- file: xhtml-ruby-1.mod
+
+ This is XHTML, a reformulation of HTML as a modular XML application.
+ Copyright 1999-2001 W3C (MIT, INRIA, Keio), All Rights Reserved.
+ Revision: $Id: xhtml-ruby-1.mod,v 4.0 2001/04/03 23:14:33 altheim Exp $
+
+ This module is based on the W3C Ruby Annotation Specification:
+
+ http://www.w3.org/TR/ruby
+
+ This DTD module is identified by the PUBLIC and SYSTEM identifiers:
+
+ PUBLIC "-//W3C//ELEMENTS XHTML Ruby 1.0//EN"
+ SYSTEM "http://www.w3.org/TR/ruby/xhtml-ruby-1.mod"
+
+ ...................................................................... -->
+
+<!-- Ruby Elements
+
+ ruby, rbc, rtc, rb, rt, rp
+
+ This module declares the elements and their attributes used to
+ support ruby annotation markup.
+-->
+
+<!-- declare qualified element type names:
+-->
+<!ENTITY % ruby.qname "ruby" >
+<!ENTITY % rbc.qname "rbc" >
+<!ENTITY % rtc.qname "rtc" >
+<!ENTITY % rb.qname "rb" >
+<!ENTITY % rt.qname "rt" >
+<!ENTITY % rp.qname "rp" >
+
+<!-- rp fallback is included by default.
+-->
+<!ENTITY % Ruby.fallback "INCLUDE" >
+<!ENTITY % Ruby.fallback.mandatory "IGNORE" >
+
+<!-- Complex ruby is included by default; it may be
+ overridden by other modules to ignore it.
+-->
+<!ENTITY % Ruby.complex "INCLUDE" >
+
+<!-- Fragments for the content model of the ruby element -->
+<![%Ruby.fallback;[
+<![%Ruby.fallback.mandatory;[
+<!ENTITY % Ruby.content.simple
+ "( %rb.qname;, %rp.qname;, %rt.qname;, %rp.qname; )"
+>
+]]>
+<!ENTITY % Ruby.content.simple
+ "( %rb.qname;, ( %rt.qname; | ( %rp.qname;, %rt.qname;, %rp.qname; ) ) )"
+>
+]]>
+<!ENTITY % Ruby.content.simple "( %rb.qname;, %rt.qname; )" >
+
+<![%Ruby.complex;[
+<!ENTITY % Ruby.content.complex
+ "| ( %rbc.qname;, %rtc.qname;, %rtc.qname;? )"
+>
+]]>
+<!ENTITY % Ruby.content.complex "" >
+
+<!-- Content models of the rb and the rt elements are intended to
+ allow other inline-level elements of its parent markup language,
+ but it should not include ruby descendent elements. The following
+ parameter entity %NoRuby.content; can be used to redefine
+ those content models with minimum effort. It's defined as
+ '( #PCDATA )' by default.
+-->
+<!ENTITY % NoRuby.content "( #PCDATA )" >
+
+<!-- one or more digits (NUMBER) -->
+<!ENTITY % Number.datatype "CDATA" >
+
+<!-- ruby element ...................................... -->
+
+<!ENTITY % ruby.element "INCLUDE" >
+<![%ruby.element;[
+<!ENTITY % ruby.content
+ "( %Ruby.content.simple; %Ruby.content.complex; )"
+>
+<!ELEMENT %ruby.qname; %ruby.content; >
+<!-- end of ruby.element -->]]>
+
+<![%Ruby.complex;[
+<!-- rbc (ruby base component) element ................. -->
+
+<!ENTITY % rbc.element "INCLUDE" >
+<![%rbc.element;[
+<!ENTITY % rbc.content
+ "(%rb.qname;)+"
+>
+<!ELEMENT %rbc.qname; %rbc.content; >
+<!-- end of rbc.element -->]]>
+
+<!-- rtc (ruby text component) element ................. -->
+
+<!ENTITY % rtc.element "INCLUDE" >
+<![%rtc.element;[
+<!ENTITY % rtc.content
+ "(%rt.qname;)+"
+>
+<!ELEMENT %rtc.qname; %rtc.content; >
+<!-- end of rtc.element -->]]>
+]]>
+
+<!-- rb (ruby base) element ............................ -->
+
+<!ENTITY % rb.element "INCLUDE" >
+<![%rb.element;[
+<!-- %rb.content; uses %NoRuby.content; as its content model,
+ which is '( #PCDATA )' by default. It may be overridden
+ by other modules to allow other inline-level elements
+ of its parent markup language, but it should not include
+ ruby descendent elements.
+-->
+<!ENTITY % rb.content "%NoRuby.content;" >
+<!ELEMENT %rb.qname; %rb.content; >
+<!-- end of rb.element -->]]>
+
+<!-- rt (ruby text) element ............................ -->
+
+<!ENTITY % rt.element "INCLUDE" >
+<![%rt.element;[
+<!-- %rt.content; uses %NoRuby.content; as its content model,
+ which is '( #PCDATA )' by default. It may be overridden
+ by other modules to allow other inline-level elements
+ of its parent markup language, but it should not include
+ ruby descendent elements.
+-->
+<!ENTITY % rt.content "%NoRuby.content;" >
+
+<!ELEMENT %rt.qname; %rt.content; >
+<!-- end of rt.element -->]]>
+
+<!-- rbspan attribute is used for complex ruby only ...... -->
+<![%Ruby.complex;[
+<!ENTITY % rt.attlist "INCLUDE" >
+<![%rt.attlist;[
+<!ATTLIST %rt.qname;
+ rbspan %Number.datatype; "1"
+>
+<!-- end of rt.attlist -->]]>
+]]>
+
+<!-- rp (ruby parenthesis) element ..................... -->
+
+<![%Ruby.fallback;[
+<!ENTITY % rp.element "INCLUDE" >
+<![%rp.element;[
+<!ENTITY % rp.content
+ "( #PCDATA )"
+>
+<!ELEMENT %rp.qname; %rp.content; >
+<!-- end of rp.element -->]]>
+]]>
+
+<!-- Ruby Common Attributes
+
+ The following optional ATTLIST declarations provide an easy way
+ to define common attributes for ruby elements. These declarations
+ are ignored by default.
+
+ Ruby elements are intended to have common attributes of its
+ parent markup language. For example, if a markup language defines
+ common attributes as a parameter entity %attrs;, you may add
+ those attributes by just declaring the following parameter entities
+
+ <!ENTITY % Ruby.common.attlists "INCLUDE" >
+ <!ENTITY % Ruby.common.attrib "%attrs;" >
+
+ before including the Ruby module.
+-->
+
+<!ENTITY % Ruby.common.attlists "IGNORE" >
+<![%Ruby.common.attlists;[
+<!ENTITY % Ruby.common.attrib "" >
+
+<!-- common attributes for ruby ........................ -->
+
+<!ENTITY % Ruby.common.attlist "INCLUDE" >
+<![%Ruby.common.attlist;[
+<!ATTLIST %ruby.qname;
+ %Ruby.common.attrib;
+>
+<!-- end of Ruby.common.attlist -->]]>
+
+<![%Ruby.complex;[
+<!-- common attributes for rbc ......................... -->
+
+<!ENTITY % Rbc.common.attlist "INCLUDE" >
+<![%Rbc.common.attlist;[
+<!ATTLIST %rbc.qname;
+ %Ruby.common.attrib;
+>
+<!-- end of Rbc.common.attlist -->]]>
+
+<!-- common attributes for rtc ......................... -->
+
+<!ENTITY % Rtc.common.attlist "INCLUDE" >
+<![%Rtc.common.attlist;[
+<!ATTLIST %rtc.qname;
+ %Ruby.common.attrib;
+>
+<!-- end of Rtc.common.attlist -->]]>
+]]>
+
+<!-- common attributes for rb .......................... -->
+
+<!ENTITY % Rb.common.attlist "INCLUDE" >
+<![%Rb.common.attlist;[
+<!ATTLIST %rb.qname;
+ %Ruby.common.attrib;
+>
+<!-- end of Rb.common.attlist -->]]>
+
+<!-- common attributes for rt .......................... -->
+
+<!ENTITY % Rt.common.attlist "INCLUDE" >
+<![%Rt.common.attlist;[
+<!ATTLIST %rt.qname;
+ %Ruby.common.attrib;
+>
+<!-- end of Rt.common.attlist -->]]>
+
+<![%Ruby.fallback;[
+<!-- common attributes for rp .......................... -->
+
+<!ENTITY % Rp.common.attlist "INCLUDE" >
+<![%Rp.common.attlist;[
+<!ATTLIST %rp.qname;
+ %Ruby.common.attrib;
+>
+<!-- end of Rp.common.attlist -->]]>
+]]>
+]]>
+
+<!-- end of xhtml-ruby-1.mod -->
+]]>
+
+<!-- Presentation Module ........................................ -->
+<!ENTITY % xhtml-pres.module "INCLUDE" >
+<![%xhtml-pres.module;[
+<!ENTITY % xhtml-pres.mod
+ PUBLIC "-//W3C//ELEMENTS XHTML Presentation 1.0//EN"
+ "http://www.w3.org/TR/xhtml-modularization/DTD/xhtml-pres-1.mod" >
+<!-- ...................................................................... -->
+<!-- XHTML Presentation Module ............................................ -->
+<!-- file: xhtml-pres-1.mod
+
+ This is XHTML, a reformulation of HTML as a modular XML application.
+ Copyright 1998-2001 W3C (MIT, INRIA, Keio), All Rights Reserved.
+ Revision: $Id: xhtml-pres-1.mod,v 4.0 2001/04/02 22:42:49 altheim Exp $ SMI
+
+ This DTD module is identified by the PUBLIC and SYSTEM identifiers:
+
+ PUBLIC "-//W3C//ELEMENTS XHTML Presentation 1.0//EN"
+ SYSTEM "http://www.w3.org/TR/xhtml-modularization/DTD/xhtml-pres-1.mod"
+
+ Revisions:
+ (none)
+ ....................................................................... -->
+
+<!-- Presentational Elements
+
+ This module defines elements and their attributes for
+ simple presentation-related markup.
+-->
+
+<!ENTITY % xhtml-inlpres.module "INCLUDE" >
+<![%xhtml-inlpres.module;[
+<!ENTITY % xhtml-inlpres.mod
+ PUBLIC "-//W3C//ELEMENTS XHTML Inline Presentation 1.0//EN"
+ "xhtml-inlpres-1.mod" >
+<!-- ...................................................................... -->
+<!-- XHTML Inline Presentation Module .................................... -->
+<!-- file: xhtml-inlpres-1.mod
+
+ This is XHTML, a reformulation of HTML as a modular XML application.
+ Copyright 1998-2001 W3C (MIT, INRIA, Keio), All Rights Reserved.
+ Revision: $Id: xhtml-inlpres-1.mod,v 4.0 2001/04/02 22:42:49 altheim Exp $ SMI
+
+ This DTD module is identified by the PUBLIC and SYSTEM identifiers:
+
+ PUBLIC "-//W3C//ELEMENTS XHTML Inline Presentation 1.0//EN"
+ SYSTEM "http://www.w3.org/TR/xhtml-modularization/DTD/xhtml-inlpres-1.mod"
+
+ Revisions:
+ (none)
+ ....................................................................... -->
+
+<!-- Inline Presentational Elements
+
+ b, big, i, small, sub, sup, tt
+
+ This module declares the elements and their attributes used to
+ support inline-level presentational markup.
+-->
+
+<!ENTITY % b.element "INCLUDE" >
+<![%b.element;[
+<!ENTITY % b.content
+ "( #PCDATA | %Inline.mix; )*"
+>
+<!ENTITY % b.qname "b" >
+<!ELEMENT %b.qname; %b.content; >
+<!-- end of b.element -->]]>
+
+<!ENTITY % b.attlist "INCLUDE" >
+<![%b.attlist;[
+<!ATTLIST %b.qname;
+ %Common.attrib;
+>
+<!-- end of b.attlist -->]]>
+
+<!ENTITY % big.element "INCLUDE" >
+<![%big.element;[
+<!ENTITY % big.content
+ "( #PCDATA | %Inline.mix; )*"
+>
+<!ENTITY % big.qname "big" >
+<!ELEMENT %big.qname; %big.content; >
+<!-- end of big.element -->]]>
+
+<!ENTITY % big.attlist "INCLUDE" >
+<![%big.attlist;[
+<!ATTLIST %big.qname;
+ %Common.attrib;
+>
+<!-- end of big.attlist -->]]>
+
+<!ENTITY % i.element "INCLUDE" >
+<![%i.element;[
+<!ENTITY % i.content
+ "( #PCDATA | %Inline.mix; )*"
+>
+<!ENTITY % i.qname "i" >
+<!ELEMENT %i.qname; %i.content; >
+<!-- end of i.element -->]]>
+
+<!ENTITY % i.attlist "INCLUDE" >
+<![%i.attlist;[
+<!ATTLIST %i.qname;
+ %Common.attrib;
+>
+<!-- end of i.attlist -->]]>
+
+<!ENTITY % small.element "INCLUDE" >
+<![%small.element;[
+<!ENTITY % small.content
+ "( #PCDATA | %Inline.mix; )*"
+>
+<!ENTITY % small.qname "small" >
+<!ELEMENT %small.qname; %small.content; >
+<!-- end of small.element -->]]>
+
+<!ENTITY % small.attlist "INCLUDE" >
+<![%small.attlist;[
+<!ATTLIST %small.qname;
+ %Common.attrib;
+>
+<!-- end of small.attlist -->]]>
+
+<!ENTITY % sub.element "INCLUDE" >
+<![%sub.element;[
+<!ENTITY % sub.content
+ "( #PCDATA | %Inline.mix; )*"
+>
+<!ENTITY % sub.qname "sub" >
+<!ELEMENT %sub.qname; %sub.content; >
+<!-- end of sub.element -->]]>
+
+<!ENTITY % sub.attlist "INCLUDE" >
+<![%sub.attlist;[
+<!ATTLIST %sub.qname;
+ %Common.attrib;
+>
+<!-- end of sub.attlist -->]]>
+
+<!ENTITY % sup.element "INCLUDE" >
+<![%sup.element;[
+<!ENTITY % sup.content
+ "( #PCDATA | %Inline.mix; )*"
+>
+<!ENTITY % sup.qname "sup" >
+<!ELEMENT %sup.qname; %sup.content; >
+<!-- end of sup.element -->]]>
+
+<!ENTITY % sup.attlist "INCLUDE" >
+<![%sup.attlist;[
+<!ATTLIST %sup.qname;
+ %Common.attrib;
+>
+<!-- end of sup.attlist -->]]>
+
+<!ENTITY % tt.element "INCLUDE" >
+<![%tt.element;[
+<!ENTITY % tt.content
+ "( #PCDATA | %Inline.mix; )*"
+>
+<!ENTITY % tt.qname "tt" >
+<!ELEMENT %tt.qname; %tt.content; >
+<!-- end of tt.element -->]]>
+
+<!ENTITY % tt.attlist "INCLUDE" >
+<![%tt.attlist;[
+<!ATTLIST %tt.qname;
+ %Common.attrib;
+>
+<!-- end of tt.attlist -->]]>
+
+<!-- end of xhtml-inlpres-1.mod -->
+]]>
+
+<!ENTITY % xhtml-blkpres.module "INCLUDE" >
+<![%xhtml-blkpres.module;[
+<!ENTITY % xhtml-blkpres.mod
+ PUBLIC "-//W3C//ELEMENTS XHTML Block Presentation 1.0//EN"
+ "xhtml-blkpres-1.mod" >
+<!-- ...................................................................... -->
+<!-- XHTML Block Presentation Module ..................................... -->
+<!-- file: xhtml-blkpres-1.mod
+
+ This is XHTML, a reformulation of HTML as a modular XML application.
+ Copyright 1998-2001 W3C (MIT, INRIA, Keio), All Rights Reserved.
+ Revision: $Id: xhtml-blkpres-1.mod,v 4.0 2001/04/02 22:42:49 altheim Exp $ SMI
+
+ This DTD module is identified by the PUBLIC and SYSTEM identifiers:
+
+ PUBLIC "-//W3C//ELEMENTS XHTML Block Presentation 1.0//EN"
+ SYSTEM "http://www.w3.org/TR/xhtml-modularization/DTD/xhtml-blkpres-1.mod"
+
+ Revisions:
+ (none)
+ ....................................................................... -->
+
+<!-- Block Presentational Elements
+
+ hr
+
+ This module declares the elements and their attributes used to
+ support block-level presentational markup.
+-->
+
+<!ENTITY % hr.element "INCLUDE" >
+<![%hr.element;[
+<!ENTITY % hr.content "EMPTY" >
+<!ENTITY % hr.qname "hr" >
+<!ELEMENT %hr.qname; %hr.content; >
+<!-- end of hr.element -->]]>
+
+<!ENTITY % hr.attlist "INCLUDE" >
+<![%hr.attlist;[
+<!ATTLIST %hr.qname;
+ %Common.attrib;
+>
+<!-- end of hr.attlist -->]]>
+
+<!-- end of xhtml-blkpres-1.mod -->
+]]>
+
+<!-- end of xhtml-pres-1.mod -->
+]]>
+
+<!-- Link Element Module ........................................ -->
+<!ENTITY % xhtml-link.module "INCLUDE" >
+<![%xhtml-link.module;[
+<!ENTITY % xhtml-link.mod
+ PUBLIC "-//W3C//ELEMENTS XHTML Link Element 1.0//EN"
+ "http://www.w3.org/TR/xhtml-modularization/DTD/xhtml-link-1.mod" >
+<!-- ...................................................................... -->
+<!-- XHTML Link Element Module ........................................... -->
+<!-- file: xhtml-link-1.mod
+
+ This is XHTML, a reformulation of HTML as a modular XML application.
+ Copyright 1998-2001 W3C (MIT, INRIA, Keio), All Rights Reserved.
+ Revision: $Id: xhtml-link-1.mod,v 4.0 2001/04/02 22:42:49 altheim Exp $ SMI
+
+ This DTD module is identified by the PUBLIC and SYSTEM identifiers:
+
+ PUBLIC "-//W3C//ELEMENTS XHTML Link Element 1.0//EN"
+ SYSTEM "http://www.w3.org/TR/xhtml-modularization/DTD/xhtml-link-1.mod"
+
+ Revisions:
+ (none)
+ ....................................................................... -->
+
+<!-- Link element
+
+ link
+
+ This module declares the link element type and its attributes,
+ which could (in principle) be used to define document-level links
+ to external resources such as:
+
+ a) for document specific toolbars/menus, e.g. start, contents,
+ previous, next, index, end, help
+ b) to link to a separate style sheet (rel="stylesheet")
+ c) to make a link to a script (rel="script")
+ d) by stylesheets to control how collections of html nodes are
+ rendered into printed documents
+ e) to make a link to a printable version of this document
+ e.g. a postscript or pdf version (rel="alternate" media="print")
+-->
+
+<!-- link: Media-Independent Link ...................... -->
+
+<!ENTITY % link.element "INCLUDE" >
+<![%link.element;[
+<!ENTITY % link.content "EMPTY" >
+<!ENTITY % link.qname "link" >
+<!ELEMENT %link.qname; %link.content; >
+<!-- end of link.element -->]]>
+
+<!ENTITY % link.attlist "INCLUDE" >
+<![%link.attlist;[
+<!ATTLIST %link.qname;
+ %Common.attrib;
+ charset %Charset.datatype; #IMPLIED
+ href %URI.datatype; #IMPLIED
+ hreflang %LanguageCode.datatype; #IMPLIED
+ type %ContentType.datatype; #IMPLIED
+ rel %LinkTypes.datatype; #IMPLIED
+ rev %LinkTypes.datatype; #IMPLIED
+ media %MediaDesc.datatype; #IMPLIED
+>
+<!-- end of link.attlist -->]]>
+
+<!-- end of xhtml-link-1.mod -->
+]]>
+
+<!-- Document Metainformation Module ............................ -->
+<!ENTITY % xhtml-meta.module "INCLUDE" >
+<![%xhtml-meta.module;[
+<!ENTITY % xhtml-meta.mod
+ PUBLIC "-//W3C//ELEMENTS XHTML Metainformation 1.0//EN"
+ "http://www.w3.org/TR/xhtml-modularization/DTD/xhtml-meta-1.mod" >
+<!-- ...................................................................... -->
+<!-- XHTML Document Metainformation Module ............................... -->
+<!-- file: xhtml-meta-1.mod
+
+ This is XHTML, a reformulation of HTML as a modular XML application.
+ Copyright 1998-2001 W3C (MIT, INRIA, Keio), All Rights Reserved.
+ Revision: $Id: xhtml-meta-1.mod,v 4.0 2001/04/02 22:42:49 altheim Exp $ SMI
+
+ This DTD module is identified by the PUBLIC and SYSTEM identifiers:
+
+ PUBLIC "-//W3C//ELEMENTS XHTML Metainformation 1.0//EN"
+ SYSTEM "http://www.w3.org/TR/xhtml-modularization/DTD/xhtml-meta-1.mod"
+
+ Revisions:
+ (none)
+ ....................................................................... -->
+
+<!-- Meta Information
+
+ meta
+
+ This module declares the meta element type and its attributes,
+ used to provide declarative document metainformation.
+-->
+
+<!-- meta: Generic Metainformation ..................... -->
+
+<!ENTITY % meta.element "INCLUDE" >
+<![%meta.element;[
+<!ENTITY % meta.content "EMPTY" >
+<!ENTITY % meta.qname "meta" >
+<!ELEMENT %meta.qname; %meta.content; >
+<!-- end of meta.element -->]]>
+
+<!ENTITY % meta.attlist "INCLUDE" >
+<![%meta.attlist;[
+<!ATTLIST %meta.qname;
+ %XHTML.xmlns.attrib;
+ %I18n.attrib;
+ http-equiv NMTOKEN #IMPLIED
+ name NMTOKEN #IMPLIED
+ content CDATA #REQUIRED
+ scheme CDATA #IMPLIED
+>
+<!-- end of meta.attlist -->]]>
+
+<!-- end of xhtml-meta-1.mod -->
+]]>
+
+<!-- Base Element Module ........................................ -->
+<!ENTITY % xhtml-base.module "INCLUDE" >
+<![%xhtml-base.module;[
+<!ENTITY % xhtml-base.mod
+ PUBLIC "-//W3C//ELEMENTS XHTML Base Element 1.0//EN"
+ "http://www.w3.org/TR/xhtml-modularization/DTD/xhtml-base-1.mod" >
+<!-- ...................................................................... -->
+<!-- XHTML Base Element Module ........................................... -->
+<!-- file: xhtml-base-1.mod
+
+ This is XHTML, a reformulation of HTML as a modular XML application.
+ Copyright 1998-2001 W3C (MIT, INRIA, Keio), All Rights Reserved.
+ Revision: $Id: xhtml-base-1.mod,v 4.0 2001/04/02 22:42:49 altheim Exp $ SMI
+
+ This DTD module is identified by the PUBLIC and SYSTEM identifiers:
+
+ PUBLIC "-//W3C//ELEMENTS XHTML Base Element 1.0//EN"
+ SYSTEM "http://www.w3.org/TR/xhtml-modularization/DTD/xhtml-base-1.mod"
+
+ Revisions:
+ (none)
+ ....................................................................... -->
+
+<!-- Base element
+
+ base
+
+ This module declares the base element type and its attributes,
+ used to define a base URI against which relative URIs in the
+ document will be resolved.
+
+ Note that this module also redeclares the content model for
+ the head element to include the base element.
+-->
+
+<!-- base: Document Base URI ........................... -->
+
+<!ENTITY % base.element "INCLUDE" >
+<![%base.element;[
+<!ENTITY % base.content "EMPTY" >
+<!ENTITY % base.qname "base" >
+<!ELEMENT %base.qname; %base.content; >
+<!-- end of base.element -->]]>
+
+<!ENTITY % base.attlist "INCLUDE" >
+<![%base.attlist;[
+<!ATTLIST %base.qname;
+ %XHTML.xmlns.attrib;
+ href %URI.datatype; #REQUIRED
+>
+<!-- end of base.attlist -->]]>
+
+<!ENTITY % head.content
+ "( %HeadOpts.mix;,
+ ( ( %title.qname;, %HeadOpts.mix;, ( %base.qname;, %HeadOpts.mix; )? )
+ | ( %base.qname;, %HeadOpts.mix;, ( %title.qname;, %HeadOpts.mix; ))))"
+>
+
+<!-- end of xhtml-base-1.mod -->
+]]>
+
+<!-- Scripting Module ........................................... -->
+<!ENTITY % xhtml-script.module "INCLUDE" >
+<![%xhtml-script.module;[
+<!ENTITY % xhtml-script.mod
+ PUBLIC "-//W3C//ELEMENTS XHTML Scripting 1.0//EN"
+ "http://www.w3.org/TR/xhtml-modularization/DTD/xhtml-script-1.mod" >
+<!-- ...................................................................... -->
+<!-- XHTML Document Scripting Module ..................................... -->
+<!-- file: xhtml-script-1.mod
+
+ This is XHTML, a reformulation of HTML as a modular XML application.
+ Copyright 1998-2001 W3C (MIT, INRIA, Keio), All Rights Reserved.
+ Revision: $Id: xhtml-script-1.mod,v 4.0 2001/04/02 22:42:49 altheim Exp $ SMI
+
+ This DTD module is identified by the PUBLIC and SYSTEM identifiers:
+
+ PUBLIC "-//W3C//ELEMENTS XHTML Scripting 1.0//EN"
+ SYSTEM "http://www.w3.org/TR/xhtml-modularization/DTD/xhtml-script-1.mod"
+
+ Revisions:
+ (none)
+ ....................................................................... -->
+
+<!-- Scripting
+
+ script, noscript
+
+ This module declares element types and attributes used to provide
+ support for executable scripts as well as an alternate content
+ container where scripts are not supported.
+-->
+
+<!-- script: Scripting Statement ....................... -->
+
+<!ENTITY % script.element "INCLUDE" >
+<![%script.element;[
+<!ENTITY % script.content "( #PCDATA )" >
+<!ENTITY % script.qname "script" >
+<!ELEMENT %script.qname; %script.content; >
+<!-- end of script.element -->]]>
+
+<!ENTITY % script.attlist "INCLUDE" >
+<![%script.attlist;[
+<!ATTLIST %script.qname;
+ %XHTML.xmlns.attrib;
+ charset %Charset.datatype; #IMPLIED
+ type %ContentType.datatype; #REQUIRED
+ src %URI.datatype; #IMPLIED
+ defer ( defer ) #IMPLIED
+ xml:space ( preserve ) #FIXED 'preserve'
+>
+<!-- end of script.attlist -->]]>
+
+<!-- noscript: No-Script Alternate Content ............. -->
+
+<!ENTITY % noscript.element "INCLUDE" >
+<![%noscript.element;[
+<!ENTITY % noscript.content
+ "( %Block.mix; )+"
+>
+<!ENTITY % noscript.qname "noscript" >
+<!ELEMENT %noscript.qname; %noscript.content; >
+<!-- end of noscript.element -->]]>
+
+<!ENTITY % noscript.attlist "INCLUDE" >
+<![%noscript.attlist;[
+<!ATTLIST %noscript.qname;
+ %Common.attrib;
+>
+<!-- end of noscript.attlist -->]]>
+
+<!-- end of xhtml-script-1.mod -->
+]]>
+
+<!-- Style Sheets Module ......................................... -->
+<!ENTITY % xhtml-style.module "INCLUDE" >
+<![%xhtml-style.module;[
+<!ENTITY % xhtml-style.mod
+ PUBLIC "-//W3C//ELEMENTS XHTML Style Sheets 1.0//EN"
+ "http://www.w3.org/TR/xhtml-modularization/DTD/xhtml-style-1.mod" >
+<!-- ...................................................................... -->
+<!-- XHTML Document Style Sheet Module .................................... -->
+<!-- file: xhtml-style-1.mod
+
+ This is XHTML, a reformulation of HTML as a modular XML application.
+ Copyright 1998-2001 W3C (MIT, INRIA, Keio), All Rights Reserved.
+ Revision: $Id: xhtml-style-1.mod,v 4.0 2001/04/02 22:42:49 altheim Exp $ SMI
+
+ This DTD module is identified by the PUBLIC and SYSTEM identifiers:
+
+ PUBLIC "-//W3C//DTD XHTML Style Sheets 1.0//EN"
+ SYSTEM "http://www.w3.org/TR/xhtml-modularization/DTD/xhtml-style-1.mod"
+
+ Revisions:
+ (none)
+ ....................................................................... -->
+
+<!-- Style Sheets
+
+ style
+
+ This module declares the style element type and its attributes,
+ used to embed stylesheet information in the document head element.
+-->
+
+<!-- style: Style Sheet Information ..................... -->
+
+<!ENTITY % style.element "INCLUDE" >
+<![%style.element;[
+<!ENTITY % style.content "( #PCDATA )" >
+<!ENTITY % style.qname "style" >
+<!ELEMENT %style.qname; %style.content; >
+<!-- end of style.element -->]]>
+
+<!ENTITY % style.attlist "INCLUDE" >
+<![%style.attlist;[
+<!ATTLIST %style.qname;
+ %XHTML.xmlns.attrib;
+ %title.attrib;
+ %I18n.attrib;
+ type %ContentType.datatype; #REQUIRED
+ media %MediaDesc.datatype; #IMPLIED
+ xml:space ( preserve ) #FIXED 'preserve'
+>
+<!-- end of style.attlist -->]]>
+
+<!-- end of xhtml-style-1.mod -->
+]]>
+
+<!-- Image Module ............................................... -->
+<!ENTITY % xhtml-image.module "INCLUDE" >
+<![%xhtml-image.module;[
+<!ENTITY % xhtml-image.mod
+ PUBLIC "-//W3C//ELEMENTS XHTML Images 1.0//EN"
+ "http://www.w3.org/TR/xhtml-modularization/DTD/xhtml-image-1.mod" >
+<!-- ...................................................................... -->
+<!-- XHTML Images Module ................................................. -->
+<!-- file: xhtml-image-1.mod
+
+ This is XHTML, a reformulation of HTML as a modular XML application.
+ Copyright 1998-2001 W3C (MIT, INRIA, Keio), All Rights Reserved.
+ Rovision: $Id: xhtml-image-1.mod,v 4.0 2001/04/02 22:42:49 altheim Exp $ SMI
+
+ This DTD module is identified by the PUBLIC and SYSTEM identifiers:
+
+ PUBLIC "-//W3C//ELEMENTS XHTML Images 1.0//EN"
+ SYSTEM "http://www.w3.org/TR/xhtml-modularization/DTD/xhtml-image-1.mod"
+
+ Revisions:
+ (none)
+ ....................................................................... -->
+
+<!-- Images
+
+ img
+
+ This module provides markup to support basic image embedding.
+-->
+
+<!-- To avoid problems with text-only UAs as well as to make
+ image content understandable and navigable to users of
+ non-visual UAs, you need to provide a description with
+ the 'alt' attribute, and avoid server-side image maps.
+-->
+
+<!ENTITY % img.element "INCLUDE" >
+<![%img.element;[
+<!ENTITY % img.content "EMPTY" >
+<!ENTITY % img.qname "img" >
+<!ELEMENT %img.qname; %img.content; >
+<!-- end of img.element -->]]>
+
+<!ENTITY % img.attlist "INCLUDE" >
+<![%img.attlist;[
+<!ATTLIST %img.qname;
+ %Common.attrib;
+ src %URI.datatype; #REQUIRED
+ alt %Text.datatype; #REQUIRED
+ longdesc %URI.datatype; #IMPLIED
+ height %Length.datatype; #IMPLIED
+ width %Length.datatype; #IMPLIED
+>
+<!-- end of img.attlist -->]]>
+
+<!-- end of xhtml-image-1.mod -->
+]]>
+
+<!-- Client-side Image Map Module ............................... -->
+<!ENTITY % xhtml-csismap.module "INCLUDE" >
+<![%xhtml-csismap.module;[
+<!ENTITY % xhtml-csismap.mod
+ PUBLIC "-//W3C//ELEMENTS XHTML Client-side Image Maps 1.0//EN"
+ "http://www.w3.org/TR/xhtml-modularization/DTD/xhtml-csismap-1.mod" >
+<!-- ...................................................................... -->
+<!-- XHTML Client-side Image Map Module .................................. -->
+<!-- file: xhtml-csismap-1.mod
+
+ This is XHTML, a reformulation of HTML as a modular XML application.
+ Copyright 1998-2001 W3C (MIT, INRIA, Keio), All Rights Reserved.
+ Revision: $Id: xhtml-csismap-1.mod,v 4.0 2001/04/02 22:42:49 altheim Exp $ SMI
+
+ This DTD module is identified by the PUBLIC and SYSTEM identifiers:
+
+ PUBLIC "-//W3C//ELEMENTS XHTML Client-side Image Maps 1.0//EN"
+ SYSTEM "http://www.w3.org/TR/xhtml-modularization/DTD/xhtml-csismap-1.mod"
+
+ Revisions:
+ (none)
+ ....................................................................... -->
+
+<!-- Client-side Image Maps
+
+ area, map
+
+ This module declares elements and attributes to support client-side
+ image maps. This requires that the Image Module (or a module
+ declaring the img element type) be included in the DTD.
+
+ These can be placed in the same document or grouped in a
+ separate document, although the latter isn't widely supported
+-->
+
+<!ENTITY % area.element "INCLUDE" >
+<![%area.element;[
+<!ENTITY % area.content "EMPTY" >
+<!ENTITY % area.qname "area" >
+<!ELEMENT %area.qname; %area.content; >
+<!-- end of area.element -->]]>
+
+<!ENTITY % Shape.datatype "( rect | circle | poly | default )">
+<!ENTITY % Coords.datatype "CDATA" >
+
+<!ENTITY % area.attlist "INCLUDE" >
+<![%area.attlist;[
+<!ATTLIST %area.qname;
+ %Common.attrib;
+ href %URI.datatype; #IMPLIED
+ shape %Shape.datatype; 'rect'
+ coords %Coords.datatype; #IMPLIED
+ nohref ( nohref ) #IMPLIED
+ alt %Text.datatype; #REQUIRED
+ tabindex %Number.datatype; #IMPLIED
+ accesskey %Character.datatype; #IMPLIED
+>
+<!-- end of area.attlist -->]]>
+
+<!-- modify anchor attribute definition list
+ to allow for client-side image maps
+-->
+<!ATTLIST %a.qname;
+ shape %Shape.datatype; 'rect'
+ coords %Coords.datatype; #IMPLIED
+>
+
+<!-- modify img attribute definition list
+ to allow for client-side image maps
+-->
+<!ATTLIST %img.qname;
+ usemap IDREF #IMPLIED
+>
+
+<!-- modify form input attribute definition list
+ to allow for client-side image maps
+-->
+<!ATTLIST %input.qname;
+ usemap IDREF #IMPLIED
+>
+
+<!-- modify object attribute definition list
+ to allow for client-side image maps
+-->
+<!ATTLIST %object.qname;
+ usemap IDREF #IMPLIED
+>
+
+<!-- 'usemap' points to the 'id' attribute of a <map> element,
+ which must be in the same document; support for external
+ document maps was not widely supported in HTML and is
+ eliminated in XHTML.
+
+ It is considered an error for the element pointed to by
+ a usemap IDREF to occur in anything but a <map> element.
+-->
+
+<!ENTITY % map.element "INCLUDE" >
+<![%map.element;[
+<!ENTITY % map.content
+ "(( %Block.mix; ) | %area.qname; )+"
+>
+<!ENTITY % map.qname "map" >
+<!ELEMENT %map.qname; %map.content; >
+<!-- end of map.element -->]]>
+
+<!ENTITY % map.attlist "INCLUDE" >
+<![%map.attlist;[
+<!ATTLIST %map.qname;
+ %XHTML.xmlns.attrib;
+ id ID #REQUIRED
+ %class.attrib;
+ %title.attrib;
+ %Core.extra.attrib;
+ %I18n.attrib;
+ %Events.attrib;
+>
+<!-- end of map.attlist -->]]>
+
+<!-- end of xhtml-csismap-1.mod -->
+]]>
+
+<!-- Server-side Image Map Module ............................... -->
+<!ENTITY % xhtml-ssismap.module "INCLUDE" >
+<![%xhtml-ssismap.module;[
+<!ENTITY % xhtml-ssismap.mod
+ PUBLIC "-//W3C//ELEMENTS XHTML Server-side Image Maps 1.0//EN"
+ "http://www.w3.org/TR/xhtml-modularization/DTD/xhtml-ssismap-1.mod" >
+<!-- ...................................................................... -->
+<!-- XHTML Server-side Image Map Module .................................. -->
+<!-- file: xhtml-ssismap-1.mod
+
+ This is XHTML, a reformulation of HTML as a modular XML application.
+ Copyright 1998-2001 W3C (MIT, INRIA, Keio), All Rights Reserved.
+ Revision: $Id: xhtml-ssismap-1.mod,v 4.0 2001/04/02 22:42:49 altheim Exp $ SMI
+
+ This DTD module is identified by the PUBLIC and SYSTEM identifiers:
+
+ PUBLIC "-//W3C//ELEMENTS XHTML Server-side Image Maps 1.0//EN"
+ SYSTEM "http://www.w3.org/TR/xhtml-modularization/DTD/xhtml-ssismap-1.mod"
+
+ Revisions:
+#2000-10-22: added declaration for 'ismap' on <input>
+ ....................................................................... -->
+
+<!-- Server-side Image Maps
+
+ This adds the 'ismap' attribute to the img and input elements
+ to support server-side processing of a user selection.
+-->
+
+<!ATTLIST %img.qname;
+ ismap ( ismap ) #IMPLIED
+>
+
+<!ATTLIST %input.qname;
+ ismap ( ismap ) #IMPLIED
+>
+
+<!-- end of xhtml-ssismap-1.mod -->
+]]>
+
+<!-- Param Element Module ....................................... -->
+<!ENTITY % xhtml-param.module "INCLUDE" >
+<![%xhtml-param.module;[
+<!ENTITY % xhtml-param.mod
+ PUBLIC "-//W3C//ELEMENTS XHTML Param Element 1.0//EN"
+ "http://www.w3.org/TR/xhtml-modularization/DTD/xhtml-param-1.mod" >
+<!-- ...................................................................... -->
+<!-- XHTML Param Element Module ..................................... -->
+<!-- file: xhtml-param-1.mod
+
+ This is XHTML, a reformulation of HTML as a modular XML application.
+ Copyright 1998-2001 W3C (MIT, INRIA, Keio), All Rights Reserved.
+ Revision: $Id: xhtml-param-1.mod,v 4.0 2001/04/02 22:42:49 altheim Exp $ SMI
+
+ This DTD module is identified by the PUBLIC and SYSTEM identifiers:
+
+ PUBLIC "-//W3C//ELEMENTS XHTML Param Element 1.0//EN"
+ SYSTEM "http://www.w3.org/TR/xhtml-modularization/DTD/xhtml-param-1.mod"
+
+ Revisions:
+ (none)
+ ....................................................................... -->
+
+<!-- Parameters for Java Applets and Embedded Objects
+
+ param
+
+ This module provides declarations for the param element,
+ used to provide named property values for the applet
+ and object elements.
+-->
+
+<!-- param: Named Property Value ....................... -->
+
+<!ENTITY % param.element "INCLUDE" >
+<![%param.element;[
+<!ENTITY % param.content "EMPTY" >
+<!ENTITY % param.qname "param" >
+<!ELEMENT %param.qname; %param.content; >
+<!-- end of param.element -->]]>
+
+<!ENTITY % param.attlist "INCLUDE" >
+<![%param.attlist;[
+<!ATTLIST %param.qname;
+ %XHTML.xmlns.attrib;
+ %id.attrib;
+ name CDATA #REQUIRED
+ value CDATA #IMPLIED
+ valuetype ( data | ref | object ) 'data'
+ type %ContentType.datatype; #IMPLIED
+>
+<!-- end of param.attlist -->]]>
+
+<!-- end of xhtml-param-1.mod -->
+]]>
+
+<!-- Embedded Object Module ..................................... -->
+<!ENTITY % xhtml-object.module "INCLUDE" >
+<![%xhtml-object.module;[
+<!ENTITY % xhtml-object.mod
+ PUBLIC "-//W3C//ELEMENTS XHTML Embedded Object 1.0//EN"
+ "http://www.w3.org/TR/xhtml-modularization/DTD/xhtml-object-1.mod" >
+<!-- ...................................................................... -->
+<!-- XHTML Embedded Object Module ........................................ -->
+<!-- file: xhtml-object-1.mod
+
+ This is XHTML, a reformulation of HTML as a modular XML application.
+ Copyright 1998-2001 W3C (MIT, INRIA, Keio), All Rights Reserved.
+ Revision: $Id: xhtml-object-1.mod,v 4.0 2001/04/02 22:42:49 altheim Exp $ SMI
+
+ This DTD module is identified by the PUBLIC and SYSTEM identifiers:
+
+ PUBLIC "-//W3C//ELEMENTS XHTML Embedded Object 1.0//EN"
+ SYSTEM "http://www.w3.org/TR/xhtml-modularization/DTD/xhtml-object-1.mod"
+
+ Revisions:
+ (none)
+ ....................................................................... -->
+
+<!-- Embedded Objects
+
+ object
+
+ This module declares the object element type and its attributes, used
+ to embed external objects as part of XHTML pages. In the document,
+ place param elements prior to other content within the object element.
+
+ Note that use of this module requires instantiation of the Param
+ Element Module.
+-->
+
+<!-- object: Generic Embedded Object ................... -->
+
+<!ENTITY % object.element "INCLUDE" >
+<![%object.element;[
+<!ENTITY % object.content
+ "( #PCDATA | %Flow.mix; | %param.qname; )*"
+>
+<!ENTITY % object.qname "object" >
+<!ELEMENT %object.qname; %object.content; >
+<!-- end of object.element -->]]>
+
+<!ENTITY % object.attlist "INCLUDE" >
+<![%object.attlist;[
+<!ATTLIST %object.qname;
+ %Common.attrib;
+ declare ( declare ) #IMPLIED
+ classid %URI.datatype; #IMPLIED
+ codebase %URI.datatype; #IMPLIED
+ data %URI.datatype; #IMPLIED
+ type %ContentType.datatype; #IMPLIED
+ codetype %ContentType.datatype; #IMPLIED
+ archive %URIs.datatype; #IMPLIED
+ standby %Text.datatype; #IMPLIED
+ height %Length.datatype; #IMPLIED
+ width %Length.datatype; #IMPLIED
+ name CDATA #IMPLIED
+ tabindex %Number.datatype; #IMPLIED
+>
+<!-- end of object.attlist -->]]>
+
+<!-- end of xhtml-object-1.mod -->
+]]>
+
+<!-- Tables Module ............................................... -->
+<!ENTITY % xhtml-table.module "INCLUDE" >
+<![%xhtml-table.module;[
+<!ENTITY % xhtml-table.mod
+ PUBLIC "-//W3C//ELEMENTS XHTML Tables 1.0//EN"
+ "http://www.w3.org/TR/xhtml-modularization/DTD/xhtml-table-1.mod" >
+<!-- ...................................................................... -->
+<!-- XHTML Table Module .................................................. -->
+<!-- file: xhtml-table-1.mod
+
+ This is XHTML, a reformulation of HTML as a modular XML application.
+ Copyright 1998-2001 W3C (MIT, INRIA, Keio), All Rights Reserved.
+ Revision: $Id: xhtml-table-1.mod,v 4.0 2001/04/02 22:42:49 altheim Exp $ SMI
+
+ This DTD module is identified by the PUBLIC and SYSTEM identifiers:
+
+ PUBLIC "-//W3C//ELEMENTS XHTML Tables 1.0//EN"
+ SYSTEM "http://www.w3.org/TR/xhtml-modularization/DTD/xhtml-table-1.mod"
+
+ Revisions:
+ (none)
+ ....................................................................... -->
+
+<!-- Tables
+
+ table, caption, thead, tfoot, tbody, colgroup, col, tr, th, td
+
+ This module declares element types and attributes used to provide
+ table markup similar to HTML 4.0, including features that enable
+ better accessibility for non-visual user agents.
+-->
+
+<!-- declare qualified element type names:
+-->
+<!ENTITY % table.qname "table" >
+<!ENTITY % caption.qname "caption" >
+<!ENTITY % thead.qname "thead" >
+<!ENTITY % tfoot.qname "tfoot" >
+<!ENTITY % tbody.qname "tbody" >
+<!ENTITY % colgroup.qname "colgroup" >
+<!ENTITY % col.qname "col" >
+<!ENTITY % tr.qname "tr" >
+<!ENTITY % th.qname "th" >
+<!ENTITY % td.qname "td" >
+
+<!-- The frame attribute specifies which parts of the frame around
+ the table should be rendered. The values are not the same as
+ CALS to avoid a name clash with the valign attribute.
+-->
+<!ENTITY % frame.attrib
+ "frame ( void
+ | above
+ | below
+ | hsides
+ | lhs
+ | rhs
+ | vsides
+ | box
+ | border ) #IMPLIED"
+>
+
+<!-- The rules attribute defines which rules to draw between cells:
+
+ If rules is absent then assume:
+
+ "none" if border is absent or border="0" otherwise "all"
+-->
+<!ENTITY % rules.attrib
+ "rules ( none
+ | groups
+ | rows
+ | cols
+ | all ) #IMPLIED"
+>
+
+<!-- horizontal alignment attributes for cell contents
+-->
+<!ENTITY % CellHAlign.attrib
+ "align ( left
+ | center
+ | right
+ | justify
+ | char ) #IMPLIED
+ char %Character.datatype; #IMPLIED
+ charoff %Length.datatype; #IMPLIED"
+>
+
+<!-- vertical alignment attribute for cell contents
+-->
+<!ENTITY % CellVAlign.attrib
+ "valign ( top
+ | middle
+ | bottom
+ | baseline ) #IMPLIED"
+>
+
+<!-- scope is simpler than axes attribute for common tables
+-->
+<!ENTITY % scope.attrib
+ "scope ( row
+ | col
+ | rowgroup
+ | colgroup ) #IMPLIED"
+>
+
+<!-- table: Table Element .............................. -->
+
+<!ENTITY % table.element "INCLUDE" >
+<![%table.element;[
+<!ENTITY % table.content
+ "( %caption.qname;?, ( %col.qname;* | %colgroup.qname;* ),
+ (( %thead.qname;?, %tfoot.qname;?, %tbody.qname;+ ) | ( %tr.qname;+ )))"
+>
+<!ELEMENT %table.qname; %table.content; >
+<!-- end of table.element -->]]>
+
+<!ENTITY % table.attlist "INCLUDE" >
+<![%table.attlist;[
+<!ATTLIST %table.qname;
+ %Common.attrib;
+ summary %Text.datatype; #IMPLIED
+ width %Length.datatype; #IMPLIED
+ border %Pixels.datatype; #IMPLIED
+ %frame.attrib;
+ %rules.attrib;
+ cellspacing %Length.datatype; #IMPLIED
+ cellpadding %Length.datatype; #IMPLIED
+>
+<!-- end of table.attlist -->]]>
+
+<!-- caption: Table Caption ............................ -->
+
+<!ENTITY % caption.element "INCLUDE" >
+<![%caption.element;[
+<!ENTITY % caption.content
+ "( #PCDATA | %Inline.mix; )*"
+>
+<!ELEMENT %caption.qname; %caption.content; >
+<!-- end of caption.element -->]]>
+
+<!ENTITY % caption.attlist "INCLUDE" >
+<![%caption.attlist;[
+<!ATTLIST %caption.qname;
+ %Common.attrib;
+>
+<!-- end of caption.attlist -->]]>
+
+<!-- thead: Table Header ............................... -->
+
+<!-- Use thead to duplicate headers when breaking table
+ across page boundaries, or for static headers when
+ tbody sections are rendered in scrolling panel.
+-->
+
+<!ENTITY % thead.element "INCLUDE" >
+<![%thead.element;[
+<!ENTITY % thead.content "( %tr.qname; )+" >
+<!ELEMENT %thead.qname; %thead.content; >
+<!-- end of thead.element -->]]>
+
+<!ENTITY % thead.attlist "INCLUDE" >
+<![%thead.attlist;[
+<!ATTLIST %thead.qname;
+ %Common.attrib;
+ %CellHAlign.attrib;
+ %CellVAlign.attrib;
+>
+<!-- end of thead.attlist -->]]>
+
+<!-- tfoot: Table Footer ............................... -->
+
+<!-- Use tfoot to duplicate footers when breaking table
+ across page boundaries, or for static footers when
+ tbody sections are rendered in scrolling panel.
+-->
+
+<!ENTITY % tfoot.element "INCLUDE" >
+<![%tfoot.element;[
+<!ENTITY % tfoot.content "( %tr.qname; )+" >
+<!ELEMENT %tfoot.qname; %tfoot.content; >
+<!-- end of tfoot.element -->]]>
+
+<!ENTITY % tfoot.attlist "INCLUDE" >
+<![%tfoot.attlist;[
+<!ATTLIST %tfoot.qname;
+ %Common.attrib;
+ %CellHAlign.attrib;
+ %CellVAlign.attrib;
+>
+<!-- end of tfoot.attlist -->]]>
+
+<!-- tbody: Table Body ................................. -->
+
+<!-- Use multiple tbody sections when rules are needed
+ between groups of table rows.
+-->
+
+<!ENTITY % tbody.element "INCLUDE" >
+<![%tbody.element;[
+<!ENTITY % tbody.content "( %tr.qname; )+" >
+<!ELEMENT %tbody.qname; %tbody.content; >
+<!-- end of tbody.element -->]]>
+
+<!ENTITY % tbody.attlist "INCLUDE" >
+<![%tbody.attlist;[
+<!ATTLIST %tbody.qname;
+ %Common.attrib;
+ %CellHAlign.attrib;
+ %CellVAlign.attrib;
+>
+<!-- end of tbody.attlist -->]]>
+
+<!-- colgroup: Table Column Group ...................... -->
+
+<!-- colgroup groups a set of col elements. It allows you
+ to group several semantically-related columns together.
+-->
+
+<!ENTITY % colgroup.element "INCLUDE" >
+<![%colgroup.element;[
+<!ENTITY % colgroup.content "( %col.qname; )*" >
+<!ELEMENT %colgroup.qname; %colgroup.content; >
+<!-- end of colgroup.element -->]]>
+
+<!ENTITY % colgroup.attlist "INCLUDE" >
+<![%colgroup.attlist;[
+<!ATTLIST %colgroup.qname;
+ %Common.attrib;
+ span %Number.datatype; '1'
+ width %MultiLength.datatype; #IMPLIED
+ %CellHAlign.attrib;
+ %CellVAlign.attrib;
+>
+<!-- end of colgroup.attlist -->]]>
+
+<!-- col: Table Column ................................. -->
+
+<!-- col elements define the alignment properties for
+ cells in one or more columns.
+
+ The width attribute specifies the width of the
+ columns, e.g.
+
+ width="64" width in screen pixels
+ width="0.5*" relative width of 0.5
+
+ The span attribute causes the attributes of one
+ col element to apply to more than one column.
+-->
+
+<!ENTITY % col.element "INCLUDE" >
+<![%col.element;[
+<!ENTITY % col.content "EMPTY" >
+<!ELEMENT %col.qname; %col.content; >
+<!-- end of col.element -->]]>
+
+<!ENTITY % col.attlist "INCLUDE" >
+<![%col.attlist;[
+<!ATTLIST %col.qname;
+ %Common.attrib;
+ span %Number.datatype; '1'
+ width %MultiLength.datatype; #IMPLIED
+ %CellHAlign.attrib;
+ %CellVAlign.attrib;
+>
+<!-- end of col.attlist -->]]>
+
+<!-- tr: Table Row ..................................... -->
+
+<!ENTITY % tr.element "INCLUDE" >
+<![%tr.element;[
+<!ENTITY % tr.content "( %th.qname; | %td.qname; )+" >
+<!ELEMENT %tr.qname; %tr.content; >
+<!-- end of tr.element -->]]>
+
+<!ENTITY % tr.attlist "INCLUDE" >
+<![%tr.attlist;[
+<!ATTLIST %tr.qname;
+ %Common.attrib;
+ %CellHAlign.attrib;
+ %CellVAlign.attrib;
+>
+<!-- end of tr.attlist -->]]>
+
+<!-- th: Table Header Cell ............................. -->
+
+<!-- th is for header cells, td for data,
+ but for cells acting as both use td
+-->
+
+<!ENTITY % th.element "INCLUDE" >
+<![%th.element;[
+<!ENTITY % th.content
+ "( #PCDATA | %Flow.mix; )*"
+>
+<!ELEMENT %th.qname; %th.content; >
+<!-- end of th.element -->]]>
+
+<!ENTITY % th.attlist "INCLUDE" >
+<![%th.attlist;[
+<!ATTLIST %th.qname;
+ %Common.attrib;
+ abbr %Text.datatype; #IMPLIED
+ axis CDATA #IMPLIED
+ headers IDREFS #IMPLIED
+ %scope.attrib;
+ rowspan %Number.datatype; '1'
+ colspan %Number.datatype; '1'
+ %CellHAlign.attrib;
+ %CellVAlign.attrib;
+>
+<!-- end of th.attlist -->]]>
+
+<!-- td: Table Data Cell ............................... -->
+
+<!ENTITY % td.element "INCLUDE" >
+<![%td.element;[
+<!ENTITY % td.content
+ "( #PCDATA | %Flow.mix; )*"
+>
+<!ELEMENT %td.qname; %td.content; >
+<!-- end of td.element -->]]>
+
+<!ENTITY % td.attlist "INCLUDE" >
+<![%td.attlist;[
+<!ATTLIST %td.qname;
+ %Common.attrib;
+ abbr %Text.datatype; #IMPLIED
+ axis CDATA #IMPLIED
+ headers IDREFS #IMPLIED
+ %scope.attrib;
+ rowspan %Number.datatype; '1'
+ colspan %Number.datatype; '1'
+ %CellHAlign.attrib;
+ %CellVAlign.attrib;
+>
+<!-- end of td.attlist -->]]>
+
+<!-- end of xhtml-table-1.mod -->
+]]>
+
+<!-- Forms Module ............................................... -->
+<!ENTITY % xhtml-form.module "INCLUDE" >
+<![%xhtml-form.module;[
+<!ENTITY % xhtml-form.mod
+ PUBLIC "-//W3C//ELEMENTS XHTML Forms 1.0//EN"
+ "http://www.w3.org/TR/xhtml-modularization/DTD/xhtml-form-1.mod" >
+<!-- ...................................................................... -->
+<!-- XHTML Forms Module .................................................. -->
+<!-- file: xhtml-form-1.mod
+
+ This is XHTML, a reformulation of HTML as a modular XML application.
+ Copyright 1998-2001 W3C (MIT, INRIA, Keio), All Rights Reserved.
+ Revision: $Id: xhtml-form-1.mod,v 4.0 2001/04/02 22:42:49 altheim Exp $ SMI
+
+ This DTD module is identified by the PUBLIC and SYSTEM identifiers:
+
+ PUBLIC "-//W3C//ELEMENTS XHTML Forms 1.0//EN"
+ SYSTEM "http://www.w3.org/TR/xhtml-modularization/DTD/xhtml-form-1.mod"
+
+ Revisions:
+ (none)
+ ....................................................................... -->
+
+<!-- Forms
+
+ form, label, input, select, optgroup, option,
+ textarea, fieldset, legend, button
+
+ This module declares markup to provide support for online
+ forms, based on the features found in HTML 4.0 forms.
+-->
+
+<!-- declare qualified element type names:
+-->
+<!ENTITY % form.qname "form" >
+<!ENTITY % label.qname "label" >
+<!ENTITY % input.qname "input" >
+<!ENTITY % select.qname "select" >
+<!ENTITY % optgroup.qname "optgroup" >
+<!ENTITY % option.qname "option" >
+<!ENTITY % textarea.qname "textarea" >
+<!ENTITY % fieldset.qname "fieldset" >
+<!ENTITY % legend.qname "legend" >
+<!ENTITY % button.qname "button" >
+
+<!-- %BlkNoForm.mix; includes all non-form block elements,
+ plus %Misc.class;
+-->
+<!ENTITY % BlkNoForm.mix
+ "%Heading.class;
+ | %List.class;
+ | %BlkStruct.class;
+ %BlkPhras.class;
+ %BlkPres.class;
+ %Table.class;
+ %Block.extra;
+ %Misc.class;"
+>
+
+<!-- form: Form Element ................................ -->
+
+<!ENTITY % form.element "INCLUDE" >
+<![%form.element;[
+<!ENTITY % form.content
+ "( %BlkNoForm.mix;
+ | %fieldset.qname; )+"
+>
+<!ELEMENT %form.qname; %form.content; >
+<!-- end of form.element -->]]>
+
+<!ENTITY % form.attlist "INCLUDE" >
+<![%form.attlist;[
+<!ATTLIST %form.qname;
+ %Common.attrib;
+ action %URI.datatype; #REQUIRED
+ method ( get | post ) 'get'
+ enctype %ContentType.datatype; 'application/x-www-form-urlencoded'
+ accept-charset %Charsets.datatype; #IMPLIED
+ accept %ContentTypes.datatype; #IMPLIED
+>
+<!-- end of form.attlist -->]]>
+
+<!-- label: Form Field Label Text ...................... -->
+
+<!-- Each label must not contain more than ONE field
+-->
+
+<!ENTITY % label.element "INCLUDE" >
+<![%label.element;[
+<!ENTITY % label.content
+ "( #PCDATA
+ | %input.qname; | %select.qname; | %textarea.qname; | %button.qname;
+ | %InlStruct.class;
+ %InlPhras.class;
+ %I18n.class;
+ %InlPres.class;
+ %Anchor.class;
+ %InlSpecial.class;
+ %Inline.extra;
+ %Misc.class; )*"
+>
+<!ELEMENT %label.qname; %label.content; >
+<!-- end of label.element -->]]>
+
+<!ENTITY % label.attlist "INCLUDE" >
+<![%label.attlist;[
+<!ATTLIST %label.qname;
+ %Common.attrib;
+ for IDREF #IMPLIED
+ accesskey %Character.datatype; #IMPLIED
+>
+<!-- end of label.attlist -->]]>
+
+<!-- input: Form Control ............................... -->
+
+<!ENTITY % input.element "INCLUDE" >
+<![%input.element;[
+<!ENTITY % input.content "EMPTY" >
+<!ELEMENT %input.qname; %input.content; >
+<!-- end of input.element -->]]>
+
+<!ENTITY % input.attlist "INCLUDE" >
+<![%input.attlist;[
+<!ENTITY % InputType.class
+ "( text | password | checkbox | radio | submit
+ | reset | file | hidden | image | button )"
+>
+<!-- attribute 'name' required for all but submit & reset
+-->
+<!ATTLIST %input.qname;
+ %Common.attrib;
+ type %InputType.class; 'text'
+ name CDATA #IMPLIED
+ value CDATA #IMPLIED
+ checked ( checked ) #IMPLIED
+ disabled ( disabled ) #IMPLIED
+ readonly ( readonly ) #IMPLIED
+ size %Number.datatype; #IMPLIED
+ maxlength %Number.datatype; #IMPLIED
+ src %URI.datatype; #IMPLIED
+ alt %Text.datatype; #IMPLIED
+ tabindex %Number.datatype; #IMPLIED
+ accesskey %Character.datatype; #IMPLIED
+ accept %ContentTypes.datatype; #IMPLIED
+>
+<!-- end of input.attlist -->]]>
+
+<!-- select: Option Selector ........................... -->
+
+<!ENTITY % select.element "INCLUDE" >
+<![%select.element;[
+<!ENTITY % select.content
+ "( %optgroup.qname; | %option.qname; )+"
+>
+<!ELEMENT %select.qname; %select.content; >
+<!-- end of select.element -->]]>
+
+<!ENTITY % select.attlist "INCLUDE" >
+<![%select.attlist;[
+<!ATTLIST %select.qname;
+ %Common.attrib;
+ name CDATA #IMPLIED
+ size %Number.datatype; #IMPLIED
+ multiple ( multiple ) #IMPLIED
+ disabled ( disabled ) #IMPLIED
+ tabindex %Number.datatype; #IMPLIED
+>
+<!-- end of select.attlist -->]]>
+
+<!-- optgroup: Option Group ............................ -->
+
+<!ENTITY % optgroup.element "INCLUDE" >
+<![%optgroup.element;[
+<!ENTITY % optgroup.content "( %option.qname; )+" >
+<!ELEMENT %optgroup.qname; %optgroup.content; >
+<!-- end of optgroup.element -->]]>
+
+<!ENTITY % optgroup.attlist "INCLUDE" >
+<![%optgroup.attlist;[
+<!ATTLIST %optgroup.qname;
+ %Common.attrib;
+ disabled ( disabled ) #IMPLIED
+ label %Text.datatype; #REQUIRED
+>
+<!-- end of optgroup.attlist -->]]>
+
+<!-- option: Selectable Choice ......................... -->
+
+<!ENTITY % option.element "INCLUDE" >
+<![%option.element;[
+<!ENTITY % option.content "( #PCDATA )" >
+<!ELEMENT %option.qname; %option.content; >
+<!-- end of option.element -->]]>
+
+<!ENTITY % option.attlist "INCLUDE" >
+<![%option.attlist;[
+<!ATTLIST %option.qname;
+ %Common.attrib;
+ selected ( selected ) #IMPLIED
+ disabled ( disabled ) #IMPLIED
+ label %Text.datatype; #IMPLIED
+ value CDATA #IMPLIED
+>
+<!-- end of option.attlist -->]]>
+
+<!-- textarea: Multi-Line Text Field ................... -->
+
+<!ENTITY % textarea.element "INCLUDE" >
+<![%textarea.element;[
+<!ENTITY % textarea.content "( #PCDATA )" >
+<!ELEMENT %textarea.qname; %textarea.content; >
+<!-- end of textarea.element -->]]>
+
+<!ENTITY % textarea.attlist "INCLUDE" >
+<![%textarea.attlist;[
+<!ATTLIST %textarea.qname;
+ %Common.attrib;
+ name CDATA #IMPLIED
+ rows %Number.datatype; #REQUIRED
+ cols %Number.datatype; #REQUIRED
+ disabled ( disabled ) #IMPLIED
+ readonly ( readonly ) #IMPLIED
+ tabindex %Number.datatype; #IMPLIED
+ accesskey %Character.datatype; #IMPLIED
+>
+<!-- end of textarea.attlist -->]]>
+
+<!-- fieldset: Form Control Group ...................... -->
+
+<!-- #PCDATA is to solve the mixed content problem,
+ per specification only whitespace is allowed
+-->
+
+<!ENTITY % fieldset.element "INCLUDE" >
+<![%fieldset.element;[
+<!ENTITY % fieldset.content
+ "( #PCDATA | %legend.qname; | %Flow.mix; )*"
+>
+<!ELEMENT %fieldset.qname; %fieldset.content; >
+<!-- end of fieldset.element -->]]>
+
+<!ENTITY % fieldset.attlist "INCLUDE" >
+<![%fieldset.attlist;[
+<!ATTLIST %fieldset.qname;
+ %Common.attrib;
+>
+<!-- end of fieldset.attlist -->]]>
+
+<!-- legend: Fieldset Legend ........................... -->
+
+<!ENTITY % legend.element "INCLUDE" >
+<![%legend.element;[
+<!ENTITY % legend.content
+ "( #PCDATA | %Inline.mix; )*"
+>
+<!ELEMENT %legend.qname; %legend.content; >
+<!-- end of legend.element -->]]>
+
+<!ENTITY % legend.attlist "INCLUDE" >
+<![%legend.attlist;[
+<!ATTLIST %legend.qname;
+ %Common.attrib;
+ accesskey %Character.datatype; #IMPLIED
+>
+<!-- end of legend.attlist -->]]>
+
+<!-- button: Push Button ............................... -->
+
+<!ENTITY % button.element "INCLUDE" >
+<![%button.element;[
+<!ENTITY % button.content
+ "( #PCDATA
+ | %BlkNoForm.mix;
+ | %InlStruct.class;
+ %InlPhras.class;
+ %InlPres.class;
+ %I18n.class;
+ %InlSpecial.class;
+ %Inline.extra; )*"
+>
+<!ELEMENT %button.qname; %button.content; >
+<!-- end of button.element -->]]>
+
+<!ENTITY % button.attlist "INCLUDE" >
+<![%button.attlist;[
+<!ATTLIST %button.qname;
+ %Common.attrib;
+ name CDATA #IMPLIED
+ value CDATA #IMPLIED
+ type ( button | submit | reset ) 'submit'
+ disabled ( disabled ) #IMPLIED
+ tabindex %Number.datatype; #IMPLIED
+ accesskey %Character.datatype; #IMPLIED
+>
+<!-- end of button.attlist -->]]>
+
+<!-- end of xhtml-form-1.mod -->
+]]>
+
+<!-- Legacy Markup ............................................... -->
+<!ENTITY % xhtml-legacy.module "IGNORE" >
+<![%xhtml-legacy.module;[
+<!ENTITY % xhtml-legacy.mod
+ PUBLIC "-//W3C//ELEMENTS XHTML Legacy Markup 1.0//EN"
+ "http://www.w3.org/TR/xhtml-modularization/DTD/xhtml-legacy-1.mod" >
+%xhtml-legacy.mod;]]>
+
+<!-- Document Structure Module (required) ....................... -->
+<!ENTITY % xhtml-struct.module "INCLUDE" >
+<![%xhtml-struct.module;[
+<!ENTITY % xhtml-struct.mod
+ PUBLIC "-//W3C//ELEMENTS XHTML Document Structure 1.0//EN"
+ "http://www.w3.org/TR/xhtml-modularization/DTD/xhtml-struct-1.mod" >
+<!-- ...................................................................... -->
+<!-- XHTML Structure Module .............................................. -->
+<!-- file: xhtml-struct-1.mod
+
+ This is XHTML, a reformulation of HTML as a modular XML application.
+ Copyright 1998-2001 W3C (MIT, INRIA, Keio), All Rights Reserved.
+ Revision: $Id: xhtml-struct-1.mod,v 4.0 2001/04/02 22:42:49 altheim Exp $ SMI
+
+ This DTD module is identified by the PUBLIC and SYSTEM identifiers:
+
+ PUBLIC "-//W3C//ELEMENTS XHTML Document Structure 1.0//EN"
+ SYSTEM "http://www.w3.org/TR/xhtml-modularization/DTD/xhtml-struct-1.mod"
+
+ Revisions:
+ (none)
+ ....................................................................... -->
+
+<!-- Document Structure
+
+ title, head, body, html
+
+ The Structure Module defines the major structural elements and
+ their attributes.
+
+ Note that the content model of the head element type is redeclared
+ when the Base Module is included in the DTD.
+
+ The parameter entity containing the XML namespace URI value used
+ for XHTML is '%XHTML.xmlns;', defined in the Qualified Names module.
+-->
+
+<!-- title: Document Title ............................. -->
+
+<!-- The title element is not considered part of the flow of text.
+ It should be displayed, for example as the page header or
+ window title. Exactly one title is required per document.
+-->
+
+<!ENTITY % title.element "INCLUDE" >
+<![%title.element;[
+<!ENTITY % title.content "( #PCDATA )" >
+<!ENTITY % title.qname "title" >
+<!ELEMENT %title.qname; %title.content; >
+<!-- end of title.element -->]]>
+
+<!ENTITY % title.attlist "INCLUDE" >
+<![%title.attlist;[
+<!ATTLIST %title.qname;
+ %XHTML.xmlns.attrib;
+ %I18n.attrib;
+>
+<!-- end of title.attlist -->]]>
+
+<!-- head: Document Head ............................... -->
+
+<!ENTITY % head.element "INCLUDE" >
+<![%head.element;[
+<!ENTITY % head.content
+ "( %HeadOpts.mix;, %title.qname;, %HeadOpts.mix; )"
+>
+<!ENTITY % head.qname "head" >
+<!ELEMENT %head.qname; %head.content; >
+<!-- end of head.element -->]]>
+
+<!ENTITY % head.attlist "INCLUDE" >
+<![%head.attlist;[
+<!-- reserved for future use with document profiles
+-->
+<!ENTITY % profile.attrib
+ "profile %URI.datatype; '%XHTML.profile;'"
+>
+
+<!ATTLIST %head.qname;
+ %XHTML.xmlns.attrib;
+ %I18n.attrib;
+ %profile.attrib;
+>
+<!-- end of head.attlist -->]]>
+
+<!-- body: Document Body ............................... -->
+
+<!ENTITY % body.element "INCLUDE" >
+<![%body.element;[
+<!ENTITY % body.content
+ "( %Block.mix; )+"
+>
+<!ENTITY % body.qname "body" >
+<!ELEMENT %body.qname; %body.content; >
+<!-- end of body.element -->]]>
+
+<!ENTITY % body.attlist "INCLUDE" >
+<![%body.attlist;[
+<!ATTLIST %body.qname;
+ %Common.attrib;
+>
+<!-- end of body.attlist -->]]>
+
+<!-- html: XHTML Document Element ...................... -->
+
+<!ENTITY % html.element "INCLUDE" >
+<![%html.element;[
+<!ENTITY % html.content "( %head.qname;, %body.qname; )" >
+<!ENTITY % html.qname "html" >
+<!ELEMENT %html.qname; %html.content; >
+<!-- end of html.element -->]]>
+
+<!ENTITY % html.attlist "INCLUDE" >
+<![%html.attlist;[
+<!-- version attribute value defined in driver
+-->
+<!ENTITY % XHTML.version.attrib
+ "version %FPI.datatype; #FIXED '%XHTML.version;'"
+>
+
+<!-- see the Qualified Names module for information
+ on how to extend XHTML using XML namespaces
+-->
+<!ATTLIST %html.qname;
+ %XHTML.xmlns.attrib;
+ %XHTML.version.attrib;
+ %I18n.attrib;
+>
+<!-- end of html.attlist -->]]>
+
+<!-- end of xhtml-struct-1.mod -->
+]]>
+
+<!-- end of XHTML 1.1 DTD ................................................. -->
+<!-- ....................................................................... -->
diff --git a/test/System.Web.Helpers.Test/WebCacheTest.cs b/test/System.Web.Helpers.Test/WebCacheTest.cs
new file mode 100644
index 00000000..9cd551dc
--- /dev/null
+++ b/test/System.Web.Helpers.Test/WebCacheTest.cs
@@ -0,0 +1,117 @@
+using System.Collections.Generic;
+using Microsoft.TestCommon;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Helpers.Test
+{
+ public class WebCacheTest
+ {
+ [Fact]
+ public void GetReturnsExpectedValueTest()
+ {
+ string key = DateTime.UtcNow.Ticks.ToString() + "_GetTest";
+ List<string> expected = new List<string>();
+ WebCache.Set(key, expected);
+
+ var actual = WebCache.Get(key);
+
+ Assert.Equal(expected, actual);
+ Assert.Equal(0, actual.Count);
+ }
+
+ [Fact]
+ public void RemoveRemovesRightValueTest()
+ {
+ string key = DateTime.UtcNow.Ticks.ToString() + "_RemoveTest";
+ List<string> expected = new List<string>();
+ WebCache.Set(key, expected);
+
+ var actual = WebCache.Remove(key);
+
+ Assert.Equal(expected, actual);
+ Assert.Equal(0, actual.Count);
+ }
+
+ [Fact]
+ public void RemoveRemovesValueFromCacheTest()
+ {
+ string key = DateTime.UtcNow.Ticks.ToString() + "_RemoveTest2";
+ List<string> expected = new List<string>();
+ WebCache.Set(key, expected);
+
+ var removed = WebCache.Remove(key);
+
+ Assert.Null(WebCache.Get(key));
+ }
+
+ [Fact]
+ public void SetWithAbsoluteExpirationDoesNotThrow()
+ {
+ string key = DateTime.UtcNow.Ticks.ToString() + "SetWithAbsoluteExpirationDoesNotThrow_SetTest";
+ object expected = new object();
+ int minutesToCache = 10;
+ bool slidingExpiration = false;
+ WebCache.Set(key, expected, minutesToCache, slidingExpiration);
+ object actual = WebCache.Get(key);
+ Assert.True(expected == actual);
+ }
+
+ [Fact]
+ public void CanSetWithSlidingExpiration()
+ {
+ string key = DateTime.UtcNow.Ticks.ToString() + "_CanSetWithSlidingExpiration_SetTest";
+ object expected = new object();
+
+ WebCache.Set(key, expected, slidingExpiration: true);
+ object actual = WebCache.Get(key);
+ Assert.True(expected == actual);
+ }
+
+ [Fact]
+ public void SetWithSlidingExpirationForNegativeTime()
+ {
+ string key = DateTime.UtcNow.Ticks.ToString() + "_SetWithSlidingExpirationForNegativeTime_SetTest";
+ object expected = new object();
+ Assert.ThrowsArgumentGreaterThan(() => WebCache.Set(key, expected, -1), "minutesToCache", "0");
+ }
+
+ [Fact]
+ public void SetWithSlidingExpirationForZeroTime()
+ {
+ string key = DateTime.UtcNow.Ticks.ToString() + "_SetWithSlidingExpirationForZeroTime_SetTest";
+ object expected = new object();
+ Assert.ThrowsArgumentGreaterThan(() => WebCache.Set(key, expected, 0), "minutesToCache", "0");
+ }
+
+ [Fact]
+ public void SetWithSlidingExpirationForYear()
+ {
+ string key = DateTime.UtcNow.Ticks.ToString() + "_SetWithSlidingExpirationForYear_SetTest";
+ object expected = new object();
+
+ WebCache.Set(key, expected, 365 * 24 * 60, true);
+ object actual = WebCache.Get(key);
+ Assert.True(expected == actual);
+ }
+
+ [Fact]
+ public void SetWithSlidingExpirationForMoreThanYear()
+ {
+ string key = DateTime.UtcNow.Ticks.ToString() + "_SetWithSlidingExpirationForMoreThanYear_SetTest";
+ object expected = new object();
+ Assert.ThrowsArgumentLessThanOrEqualTo(() => WebCache.Set(key, expected, 365 * 24 * 60 + 1, true), "minutesToCache", (365 * 24 * 60).ToString());
+ }
+
+ [Fact]
+ public void SetWithAbsoluteExpirationForMoreThanYear()
+ {
+ string key = DateTime.UtcNow.Ticks.ToString() + "_SetWithAbsoluteExpirationForMoreThanYear_SetTest";
+ object expected = new object();
+
+ WebCache.Set(key, expected, 365 * 24 * 60, true);
+ object actual = WebCache.Get(key);
+ Assert.True(expected == actual);
+ }
+ }
+}
diff --git a/test/System.Web.Helpers.Test/WebGridDataSourceTest.cs b/test/System.Web.Helpers.Test/WebGridDataSourceTest.cs
new file mode 100644
index 00000000..dec4a09c
--- /dev/null
+++ b/test/System.Web.Helpers.Test/WebGridDataSourceTest.cs
@@ -0,0 +1,305 @@
+using System.Collections.Generic;
+using System.Dynamic;
+using System.Linq;
+using System.Web.WebPages.TestUtils;
+using Moq;
+using Xunit;
+
+namespace System.Web.Helpers.Test
+{
+ public class WebGridDataSourceTest
+ {
+ [Fact]
+ public void WebGridDataSourceReturnsNumberOfItemsAsTotalRowCount()
+ {
+ // Arrange
+ var rows = GetValues();
+ var dataSource = new WebGridDataSource(new WebGrid(GetContext()), values: GetValues(), elementType: typeof(Person), canPage: false, canSort: false);
+
+ // Act and Assert
+ Assert.Equal(rows.Count(), dataSource.TotalRowCount);
+ }
+
+ [Fact]
+ public void WebGridDataSourceReturnsUnsortedListIfSortColumnIsNull()
+ {
+ // Arrange
+ var values = GetValues();
+ var dataSource = new WebGridDataSource(new WebGrid(GetContext()), values: GetValues(), elementType: typeof(Person), canPage: false, canSort: true);
+
+ // Act
+ var rows = dataSource.GetRows(new SortInfo { SortColumn = null }, 0);
+
+ // Assert
+ Assert.True(Enumerable.SequenceEqual<object>(values.ToList(), rows.Select(r => r.Value).ToList(), new PersonComparer()));
+ }
+
+ [Fact]
+ public void WebGridDataSourceReturnsUnsortedListIfSortColumnIsEmpty()
+ {
+ // Arrange
+ var values = GetValues();
+ var dataSource = new WebGridDataSource(new WebGrid(GetContext()), values: GetValues(), elementType: typeof(Person), canPage: false, canSort: true);
+
+ // Act
+ var rows = dataSource.GetRows(new SortInfo { SortColumn = String.Empty }, 0);
+
+ // Assert
+ Assert.True(Enumerable.SequenceEqual<object>(values.ToList(), rows.Select(r => r.Value).ToList(), new PersonComparer()));
+ }
+
+ [Fact]
+ public void WebGridDataSourceReturnsUnsortedListIfSortCannotBeInferred()
+ {
+ // Arrange
+ var values = GetValues();
+ var dataSource = new WebGridDataSource(new WebGrid(GetContext()), values: GetValues(), elementType: typeof(Person), canPage: false, canSort: true);
+
+ // Act
+ var rows = dataSource.GetRows(new SortInfo { SortColumn = "Does-not-exist" }, 0);
+
+ // Assert
+ Assert.True(Enumerable.SequenceEqual<object>(values.ToList(), rows.Select(r => r.Value).ToList(), new PersonComparer()));
+ }
+
+ [Fact]
+ public void WebGridDataSourceReturnsUnsortedListIfDefaultSortCannotBeInferred()
+ {
+ // Arrange
+ var values = GetValues();
+ var defaultSort = new SortInfo { SortColumn = "cannot-be-inferred" };
+ var dataSource = new WebGridDataSource(new WebGrid(GetContext()), values: GetValues(), elementType: typeof(Person), canSort: true, canPage: false) { DefaultSort = defaultSort };
+
+ // Act
+ var rows = dataSource.GetRows(new SortInfo { SortColumn = "Does-not-exist" }, 0);
+
+ // Assert
+ Assert.True(Enumerable.SequenceEqual<object>(values.ToList(), rows.Select(r => r.Value).ToList(), new PersonComparer()));
+ }
+
+ [Fact]
+ public void WebGridDataSourceUsesDefaultSortWhenCurrentSortCannotBeInferred()
+ {
+ // Arrange
+ var values = GetValues();
+ var defaultSort = new SortInfo { SortColumn = "FirstName" };
+ var dataSource = new WebGridDataSource(new WebGrid(GetContext()), values: GetValues(), elementType: typeof(Person), canSort: true, canPage: false) { DefaultSort = defaultSort };
+
+ // Act
+ var rows = dataSource.GetRows(new SortInfo { SortColumn = "Does-not-exist" }, 0);
+
+ // Assert
+ Assert.True(Enumerable.SequenceEqual<object>(values.OrderBy(p => p.FirstName).ToList(), rows.Select(r => r.Value).ToList(), new PersonComparer()));
+ }
+
+ [Fact]
+ public void WebGridDataSourceSortsUsingSpecifiedSort()
+ {
+ // Arrange
+ var defaultSort = new SortInfo { SortColumn = "FirstName", SortDirection = SortDirection.Ascending };
+ IEnumerable<dynamic> values = new[] { new Person { LastName = "Z" }, new Person { LastName = "X" }, new Person { LastName = "Y" } };
+ var dataSource = new WebGridDataSource(new WebGrid(GetContext()), values: values, elementType: typeof(Person), canSort: true, canPage: false) { DefaultSort = defaultSort };
+
+ // Act
+ var rows = dataSource.GetRows(new SortInfo { SortColumn = "LastName" }, 0);
+
+ // Assert
+ Assert.Equal(rows.ElementAt(0).Value.LastName, "X");
+ Assert.Equal(rows.ElementAt(1).Value.LastName, "Y");
+ Assert.Equal(rows.ElementAt(2).Value.LastName, "Z");
+ }
+
+ [Fact]
+ public void WebGridDataSourceSortsDynamicType()
+ {
+ // Arrange
+ IEnumerable<dynamic> values = new[] { new TestDynamicType("col", "val1"), new TestDynamicType("col", "val2"), new TestDynamicType("col", "val3") };
+ var dataSource = new WebGridDataSource(new WebGrid(GetContext()), values: values, elementType: typeof(TestDynamicType), canSort: true, canPage: false);
+
+ // Act
+ var rows = dataSource.GetRows(new SortInfo { SortColumn = "col", SortDirection = SortDirection.Descending }, 0);
+
+ // Assert
+ Assert.Equal(rows.ElementAt(0).Value.col, "val3");
+ Assert.Equal(rows.ElementAt(1).Value.col, "val2");
+ Assert.Equal(rows.ElementAt(2).Value.col, "val1");
+ }
+
+ [Fact]
+ public void WebGridDataSourceWithNestedPropertySortsCorrectly()
+ {
+ // Arrange
+ var element1 = new { Foo = new { Bar = "val2" } };
+ var element2 = new { Foo = new { Bar = "val1" } };
+ var element3 = new { Foo = new { Bar = "val3" } };
+ IEnumerable<dynamic> values = new[] { element1, element2, element3 };
+
+ var dataSource = new WebGridDataSource(new WebGrid(GetContext()), values: values, elementType: element1.GetType(), canSort: true, canPage: false);
+
+ // Act
+ var rows = dataSource.GetRows(new SortInfo { SortColumn = "Foo.Bar", SortDirection = SortDirection.Descending }, 0);
+
+ // Assert
+ Assert.Equal(rows.ElementAt(0).Value.Foo.Bar, "val3");
+ Assert.Equal(rows.ElementAt(1).Value.Foo.Bar, "val2");
+ Assert.Equal(rows.ElementAt(2).Value.Foo.Bar, "val1");
+ }
+
+ [Fact]
+ public void WebGridDataSourceSortsDictionaryBasedDynamicType()
+ {
+ // Arrange
+ var value1 = new DynamicDictionary();
+ value1["col"] = "val1";
+ var value2 = new DynamicDictionary();
+ value2["col"] = "val2";
+ var value3 = new DynamicDictionary();
+ value3["col"] = "val3";
+ IEnumerable<dynamic> values = new[] { value1, value2, value3 };
+ var dataSource = new WebGridDataSource(new WebGrid(GetContext()), values: values, elementType: typeof(TestDynamicType), canSort: true, canPage: false);
+
+ // Act
+ var rows = dataSource.GetRows(new SortInfo { SortColumn = "col", SortDirection = SortDirection.Descending }, 0);
+
+ // Assert
+ Assert.Equal(rows.ElementAt(0).Value.col, "val3");
+ Assert.Equal(rows.ElementAt(1).Value.col, "val2");
+ Assert.Equal(rows.ElementAt(2).Value.col, "val1");
+ }
+
+ [Fact]
+ public void WebGridDataSourceReturnsOriginalDataSourceIfValuesCannotBeSorted()
+ {
+ // Arrange
+ IEnumerable<dynamic> values = new object[] { new TestDynamicType("col", "val1"), new TestDynamicType("col", "val2"), new TestDynamicType("col", DBNull.Value) };
+ var dataSource = new WebGridDataSource(new WebGrid(GetContext()), values: values, elementType: typeof(TestDynamicType), canSort: true, canPage: false);
+
+ // Act
+ var rows = dataSource.GetRows(new SortInfo { SortColumn = "col", SortDirection = SortDirection.Descending }, 0);
+
+ // Assert
+ Assert.Equal(rows.ElementAt(0).Value.col, "val1");
+ Assert.Equal(rows.ElementAt(1).Value.col, "val2");
+ Assert.Equal(rows.ElementAt(2).Value.col, DBNull.Value);
+ }
+
+ [Fact]
+ public void WebGridDataSourceReturnsPagedResultsIfRowsPerPageIsSpecified()
+ {
+ // Arrange
+ IEnumerable<dynamic> values = GetValues();
+ var dataSource = new WebGridDataSource(new WebGrid(GetContext()), values: values, elementType: typeof(Person), canSort: false, canPage: true) { RowsPerPage = 2 };
+
+ // Act
+ var rows = dataSource.GetRows(new SortInfo(), 0);
+
+ // Assert
+ Assert.Equal(rows.Count, 2);
+ Assert.Equal(rows.ElementAt(0).Value.LastName, "B2");
+ Assert.Equal(rows.ElementAt(1).Value.LastName, "A2");
+ }
+
+ [Fact]
+ public void WebGridDataSourceReturnsPagedSortedResultsIfRowsPerPageAndSortAreSpecified()
+ {
+ // Arrange
+ IEnumerable<dynamic> values = GetValues();
+ var dataSource = new WebGridDataSource(new WebGrid(GetContext()), values: values, elementType: typeof(Person), canSort: true, canPage: true) { RowsPerPage = 2 };
+
+ // Act
+ var rows = dataSource.GetRows(new SortInfo { SortColumn = "LastName", SortDirection = SortDirection.Descending }, 0);
+
+ // Assert
+ Assert.Equal(rows.Count, 2);
+ Assert.Equal(rows.ElementAt(0).Value.LastName, "E2");
+ Assert.Equal(rows.ElementAt(1).Value.LastName, "D2");
+ }
+
+ [Fact]
+ public void WebGridDataSourceReturnsFewerThanRowsPerPageIfNumberOfItemsIsInsufficient()
+ {
+ // Arrange
+ IEnumerable<dynamic> values = GetValues();
+ var dataSource = new WebGridDataSource(new WebGrid(GetContext()), values: values, elementType: typeof(Person), canSort: true, canPage: true) { RowsPerPage = 3 };
+
+ // Act
+ var rows = dataSource.GetRows(new SortInfo(), 1);
+
+ // Assert
+ Assert.Equal(rows.Count, 2);
+ Assert.Equal(rows.ElementAt(0).Value.LastName, "C2");
+ Assert.Equal(rows.ElementAt(1).Value.LastName, "E2");
+ }
+
+ [Fact]
+ public void WebGridDataSourceDoesNotThrowIfValuesAreNull()
+ {
+ // Arrange
+ IEnumerable<dynamic> values = new object[] { String.Empty, null, DBNull.Value, null };
+ var dataSource = new WebGridDataSource(new WebGrid(GetContext()), values: values, elementType: typeof(object), canSort: true, canPage: true) { RowsPerPage = 2 };
+
+ // Act
+ var rows = dataSource.GetRows(new SortInfo(), 0);
+
+ // Assert
+ Assert.Equal(rows.Count, 2);
+ Assert.Equal(rows.ElementAt(0).Value, String.Empty);
+ Assert.Null(rows.ElementAt(1).Value);
+ }
+
+ private IEnumerable<Person> GetValues()
+ {
+ return new[]
+ {
+ new Person { FirstName = "B1", LastName = "B2" },
+ new Person { FirstName = "A1", LastName = "A2" },
+ new Person { FirstName = "D1", LastName = "D2" },
+ new Person { FirstName = "C1", LastName = "C2" },
+ new Person { FirstName = "E1", LastName = "E2" },
+ };
+ }
+
+ private class PersonComparer : IEqualityComparer<object>
+ {
+ public new bool Equals(object x, object y)
+ {
+ dynamic xDynamic = x;
+ dynamic yDynamic = y;
+ return (String.Equals(xDynamic.FirstName, yDynamic.FirstName, StringComparison.OrdinalIgnoreCase)
+ && String.Equals(xDynamic.LastName, yDynamic.LastName, StringComparison.OrdinalIgnoreCase));
+ }
+
+ public int GetHashCode(dynamic obj)
+ {
+ return 4; // Random dice roll
+ }
+ }
+
+ private class TestDynamicType : DynamicObject
+ {
+ public Dictionary<string, object> _values = new Dictionary<string, object>();
+
+ public TestDynamicType(string a, object b)
+ {
+ _values[a] = b;
+ }
+
+ public override bool TryGetMember(GetMemberBinder binder, out object result)
+ {
+ return _values.TryGetValue(binder.Name, out result);
+ }
+ }
+
+ private class Person
+ {
+ public string FirstName { get; set; }
+
+ public string LastName { get; set; }
+ }
+
+ private HttpContextBase GetContext()
+ {
+ return new Mock<HttpContextBase>().Object;
+ }
+ }
+}
diff --git a/test/System.Web.Helpers.Test/WebGridTest.cs b/test/System.Web.Helpers.Test/WebGridTest.cs
new file mode 100644
index 00000000..17000236
--- /dev/null
+++ b/test/System.Web.Helpers.Test/WebGridTest.cs
@@ -0,0 +1,2321 @@
+using System.Collections;
+using System.Collections.Generic;
+using System.Collections.Specialized;
+using System.Dynamic;
+using System.Linq;
+using System.Text;
+using System.Web.TestUtil;
+using System.Web.WebPages;
+using Microsoft.TestCommon;
+using Moq;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Helpers.Test
+{
+ public class WebGridTest
+ {
+ [Fact]
+ public void AjaxCheckedOnlyOnce()
+ {
+ var grid = new WebGrid(GetContext(), ajaxUpdateContainerId: "grid")
+ .Bind(new[] { new { First = "First", Second = "Second" } });
+ string html = grid.Table().ToString();
+ Assert.True(html.Contains("<script"));
+ html = grid.Table().ToString();
+ Assert.False(html.Contains("<script"));
+ html = grid.Pager().ToString();
+ Assert.False(html.Contains("<script"));
+ }
+
+ [Fact]
+ public void AjaxCallbackIgnoredIfAjaxUpdateContainerIdIsNotSet()
+ {
+ var grid = new WebGrid(GetContext(), ajaxUpdateCallback: "myCallback")
+ .Bind(new[] { new { First = "First", Second = "Second" } });
+ string html = grid.Table().ToString();
+ Assert.False(html.Contains("<script"));
+ Assert.False(html.Contains("myCallback"));
+ }
+
+ [Fact]
+ public void ColumnNameDefaultsExcludesIndexedProperties()
+ {
+ var grid = new WebGrid(GetContext()).Bind(new[] { "First", "Second" });
+ Assert.Equal(1, grid.ColumnNames.Count());
+ Assert.True(grid.ColumnNames.Contains("Length"));
+ }
+
+ [Fact]
+ public void ColumnNameDefaultsForDynamics()
+ {
+ var grid = new WebGrid(GetContext()).Bind(Dynamics(new { First = "First", Second = "Second" }));
+ Assert.Equal(2, grid.ColumnNames.Count());
+ Assert.True(grid.ColumnNames.Contains("First"));
+ Assert.True(grid.ColumnNames.Contains("Second"));
+ }
+
+ [Fact]
+ public void ColumnNameDefaultsForNonDynamics()
+ {
+ var grid = new WebGrid(GetContext()).Bind(new[] { new { First = "First", Second = "Second" } });
+ Assert.Equal(2, grid.ColumnNames.Count());
+ Assert.True(grid.ColumnNames.Contains("First"));
+ Assert.True(grid.ColumnNames.Contains("Second"));
+ }
+
+ [Fact]
+ public void ColumnNameDefaultsSupportsBindableTypes()
+ {
+ var grid = new WebGrid(GetContext()).Bind(new[]
+ {
+ new
+ {
+ DateTime = DateTime.MinValue,
+ DateTimeOffset = DateTimeOffset.MinValue,
+ Decimal = Decimal.MinValue,
+ Guid = Guid.Empty,
+ Int32 = 1,
+ NullableInt32 = (int?)1,
+ Object = new object(),
+ Projection = new { Foo = "Bar" },
+ TimeSpan = TimeSpan.MinValue
+ }
+ });
+ Assert.Equal(7, grid.ColumnNames.Count());
+ Assert.True(grid.ColumnNames.Contains("DateTime"));
+ Assert.True(grid.ColumnNames.Contains("DateTimeOffset"));
+ Assert.True(grid.ColumnNames.Contains("Decimal"));
+ Assert.True(grid.ColumnNames.Contains("Guid"));
+ Assert.True(grid.ColumnNames.Contains("Int32"));
+ Assert.True(grid.ColumnNames.Contains("NullableInt32"));
+ Assert.True(grid.ColumnNames.Contains("TimeSpan"));
+ Assert.False(grid.ColumnNames.Contains("Object"));
+ Assert.False(grid.ColumnNames.Contains("Projection"));
+ }
+
+ [Fact]
+ public void ColumnsIsNoOp()
+ {
+ var grid = new WebGrid(GetContext()).Bind(new[]
+ {
+ new { First = "First", Second = "Second" }
+ });
+ var columns = new[]
+ {
+ grid.Column("First"), grid.Column("Second")
+ };
+ Assert.Equal(columns, grid.Columns(columns));
+ }
+
+ [Fact]
+ public void ColumnThrowsIfColumnNameIsEmptyAndNoFormat()
+ {
+ var grid = new WebGrid(GetContext()).Bind(new object[0]);
+ Assert.ThrowsArgument(() => { grid.Column(columnName: String.Empty, format: null); }, "columnName", "The column name cannot be null or an empty string unless a custom format is specified.");
+ }
+
+ [Fact]
+ public void ColumnThrowsIfColumnNameIsNullAndNoFormat()
+ {
+ var grid = new WebGrid(GetContext()).Bind(new object[0]);
+ Assert.ThrowsArgument(() => { grid.Column(columnName: null, format: null); }, "columnName", "The column name cannot be null or an empty string unless a custom format is specified.");
+ }
+
+ [Fact]
+ public void BindThrowsIfSourceIsNull()
+ {
+ Assert.ThrowsArgumentNull(() => { new WebGrid(GetContext()).Bind(null); }, "source");
+ }
+
+ [Fact]
+ public void ConstructorThrowsIfRowsPerPageIsLessThanOne()
+ {
+ Assert.ThrowsArgumentOutOfRange(() => { new WebGrid(GetContext(), rowsPerPage: 0); }, "rowsPerPage", "Value must be greater than or equal to 1.", allowDerivedExceptions: true);
+ }
+
+ [Fact]
+ public void GetHtmlDefaults()
+ {
+ var grid = new WebGrid(GetContext(), rowsPerPage: 1)
+ .Bind(new[]
+ {
+ new { P1 = 1, P2 = '2', P3 = "3" },
+ new { P1 = 4, P2 = '5', P3 = "6" }
+ });
+ var html = grid.GetHtml();
+ UnitTestHelper.AssertEqualsIgnoreWhitespace(
+ "<table><thead><tr>" +
+ "<th scope=\"col\"><a href=\"?sort=P1&amp;sortdir=ASC\">P1</a></th>" +
+ "<th scope=\"col\"><a href=\"?sort=P2&amp;sortdir=ASC\">P2</a></th>" +
+ "<th scope=\"col\"><a href=\"?sort=P3&amp;sortdir=ASC\">P3</a></th>" +
+ "</tr></thead>" +
+ "<tfoot><tr>" +
+ "<td colspan=\"3\">1 <a href=\"?page=2\">2</a> <a href=\"?page=2\">&gt;</a> </td>" +
+ "</tr></tfoot>" +
+ "<tbody><tr><td>1</td><td>2</td><td>3</td></tr></tbody>" +
+ "</table>", html.ToString());
+ XhtmlAssert.Validate1_1(html);
+ }
+
+ [Fact]
+ public void WebGridProducesValidHtmlWhenSummaryIsSpecified()
+ {
+ var grid = new WebGrid(GetContext(), rowsPerPage: 1)
+ .Bind(new[]
+ {
+ new { P1 = 1, P2 = '2', P3 = "3" },
+ new { P1 = 4, P2 = '5', P3 = "6" }
+ });
+ var caption = "WebGrid With Caption";
+ var html = grid.GetHtml(caption: caption);
+ UnitTestHelper.AssertEqualsIgnoreWhitespace(
+ "<table>" +
+ "<caption>" + caption + "</caption>" +
+ "<thead><tr>" +
+ "<th scope=\"col\"><a href=\"?sort=P1&amp;sortdir=ASC\">P1</a></th>" +
+ "<th scope=\"col\"><a href=\"?sort=P2&amp;sortdir=ASC\">P2</a></th>" +
+ "<th scope=\"col\"><a href=\"?sort=P3&amp;sortdir=ASC\">P3</a></th>" +
+ "</tr></thead>" +
+ "<tfoot><tr>" +
+ "<td colspan=\"3\">1 <a href=\"?page=2\">2</a> <a href=\"?page=2\">&gt;</a> </td>" +
+ "</tr></tfoot>" +
+ "<tbody><tr><td>1</td><td>2</td><td>3</td></tr></tbody>" +
+ "</table>", html.ToString());
+ XhtmlAssert.Validate1_1(html);
+ }
+
+ [Fact]
+ public void WebGridEncodesCaptionText()
+ {
+ var grid = new WebGrid(GetContext(), rowsPerPage: 1)
+ .Bind(new[]
+ {
+ new { P1 = 1, P2 = '2', P3 = "3" },
+ new { P1 = 4, P2 = '5', P3 = "6" }
+ });
+ var caption = "WebGrid <> With Caption";
+ var html = grid.GetHtml(caption: caption);
+ UnitTestHelper.AssertEqualsIgnoreWhitespace(
+ "<table>" +
+ "<caption>WebGrid &lt;&gt; With Caption</caption>" +
+ "<thead><tr>" +
+ "<th scope=\"col\"><a href=\"?sort=P1&amp;sortdir=ASC\">P1</a></th>" +
+ "<th scope=\"col\"><a href=\"?sort=P2&amp;sortdir=ASC\">P2</a></th>" +
+ "<th scope=\"col\"><a href=\"?sort=P3&amp;sortdir=ASC\">P3</a></th>" +
+ "</tr></thead>" +
+ "<tfoot><tr>" +
+ "<td colspan=\"3\">1 <a href=\"?page=2\">2</a> <a href=\"?page=2\">&gt;</a> </td>" +
+ "</tr></tfoot>" +
+ "<tbody><tr><td>1</td><td>2</td><td>3</td></tr></tbody>" +
+ "</table>", html.ToString());
+ XhtmlAssert.Validate1_1(html);
+ }
+
+ [Fact]
+ public void GetHtmlWhenPageCountIsOne()
+ {
+ var grid = new WebGrid(GetContext())
+ .Bind(new[]
+ {
+ new { P1 = 1, P2 = '2', P3 = "3" }
+ });
+ var html = grid.GetHtml();
+ UnitTestHelper.AssertEqualsIgnoreWhitespace(
+ "<table><thead><tr>" +
+ "<th scope=\"col\"><a href=\"?sort=P1&amp;sortdir=ASC\">P1</a></th>" +
+ "<th scope=\"col\"><a href=\"?sort=P2&amp;sortdir=ASC\">P2</a></th>" +
+ "<th scope=\"col\"><a href=\"?sort=P3&amp;sortdir=ASC\">P3</a></th>" +
+ "</tr></thead>" +
+ "<tbody><tr><td>1</td><td>2</td><td>3</td></tr></tbody>" +
+ "</table>", html.ToString());
+ XhtmlAssert.Validate1_1(html);
+ }
+
+ [Fact]
+ public void GetHtmlWhenPagingAndSortingAreDisabled()
+ {
+ var grid = new WebGrid(GetContext(), rowsPerPage: 1, canPage: false, canSort: false)
+ .Bind(new[]
+ {
+ new { P1 = 1, P2 = '2', P3 = "3" },
+ new { P1 = 4, P2 = '5', P3 = "6" }
+ });
+ var html = grid.GetHtml();
+ UnitTestHelper.AssertEqualsIgnoreWhitespace(
+ "<table><thead><tr>" +
+ "<th scope=\"col\">P1</th>" +
+ "<th scope=\"col\">P2</th>" +
+ "<th scope=\"col\">P3</th>" +
+ "</tr></thead>" +
+ "<tbody>" +
+ "<tr><td>1</td><td>2</td><td>3</td></tr>" +
+ "<tr><td>4</td><td>5</td><td>6</td></tr>" +
+ "</tbody>" +
+ "</table>", html.ToString());
+ XhtmlAssert.Validate1_1(html);
+ }
+
+ [Fact]
+ public void PageIndexCanBeReset()
+ {
+ NameValueCollection queryString = new NameValueCollection();
+ queryString["page"] = "2";
+ var grid = new WebGrid(GetContext(queryString), rowsPerPage: 1)
+ .Bind(new[]
+ {
+ new { P1 = 1, P2 = '2', P3 = "3" },
+ new { P1 = 4, P2 = '5', P3 = "6" }
+ });
+ Assert.Equal(1, grid.PageIndex);
+ grid.PageIndex = 0;
+ Assert.Equal(0, grid.PageIndex);
+ // verify that selection link has updated page
+ Assert.Equal("?page=1&row=1", grid.Rows.FirstOrDefault().GetSelectUrl());
+ }
+
+ [Fact]
+ public void PageIndexCanBeResetToSameValue()
+ {
+ NameValueCollection queryString = new NameValueCollection();
+ queryString["page"] = "2";
+ var grid = new WebGrid(GetContext(queryString), rowsPerPage: 1)
+ .Bind(new[]
+ {
+ new { P1 = 1, P2 = '2', P3 = "3" },
+ new { P1 = 4, P2 = '5', P3 = "6" }
+ });
+ grid.PageIndex = 0;
+ Assert.Equal(0, grid.PageIndex);
+ }
+
+ [Fact]
+ public void PageIndexDefaultsToZero()
+ {
+ var grid = new WebGrid(GetContext(), rowsPerPage: 1)
+ .Bind(new[]
+ {
+ new { P1 = 1, P2 = '2', P3 = "3" },
+ new { P1 = 4, P2 = '5', P3 = "6" }
+ });
+ Assert.Equal(0, grid.PageIndex);
+ Assert.Equal(1, grid.Rows.Count);
+ Assert.Equal(1, grid.Rows.First()["P1"]);
+ }
+
+ [Fact]
+ public void SetPageIndexThrowsExceptionWhenValueIsNegative()
+ {
+ var grid = new WebGrid(GetContext(), rowsPerPage: 1)
+ .Bind(new[]
+ {
+ new { P1 = 1, P2 = '2', P3 = "3" },
+ new { P1 = 4, P2 = '5', P3 = "6" }
+ });
+ Assert.ThrowsArgumentOutOfRange(() => { grid.PageIndex = -1; }, "value", "Value must be between 0 and 1.");
+ }
+
+ [Fact]
+ public void SetPageIndexThrowsExceptionWhenValueIsEqualToPageCount()
+ {
+ var grid = new WebGrid(GetContext(), rowsPerPage: 1)
+ .Bind(new[]
+ {
+ new { P1 = 1, P2 = '2', P3 = "3" },
+ new { P1 = 4, P2 = '5', P3 = "6" }
+ });
+ Assert.ThrowsArgumentOutOfRange(() => { grid.PageIndex = grid.PageCount; }, "value", "Value must be between 0 and 1.");
+ }
+
+ [Fact]
+ public void SetPageIndexThrowsExceptionWhenValueIsGreaterToPageCount()
+ {
+ var grid = new WebGrid(GetContext(), rowsPerPage: 1)
+ .Bind(new[]
+ {
+ new { P1 = 1, P2 = '2', P3 = "3" },
+ new { P1 = 4, P2 = '5', P3 = "6" }
+ });
+ Assert.ThrowsArgumentOutOfRange(() => { grid.PageIndex = grid.PageCount + 1; }, "value", "Value must be between 0 and 1.");
+ }
+
+ [Fact]
+ public void SetPageIndexThrowsExceptionWhenPagingIsDisabled()
+ {
+ var grid = new WebGrid(GetContext(), canPage: false)
+ .Bind(new[]
+ {
+ new { P1 = 1, P2 = '2', P3 = "3" },
+ new { P1 = 4, P2 = '5', P3 = "6" }
+ });
+ Assert.Throws<NotSupportedException>(() => { grid.PageIndex = 1; }, "This operation is not supported when paging is disabled for the \"WebGrid\" object.");
+ }
+
+ [Fact]
+ public void PageIndexResetsToLastPageWhenQueryStringValueGreaterThanPageCount()
+ {
+ NameValueCollection queryString = new NameValueCollection();
+ queryString["page"] = "3";
+ var grid = new WebGrid(GetContext(queryString), rowsPerPage: 1)
+ .Bind(new[]
+ {
+ new { P1 = 1, P2 = '2', P3 = "3" },
+ new { P1 = 4, P2 = '5', P3 = "6" }
+ });
+ Assert.Equal(1, grid.PageIndex);
+ Assert.Equal(1, grid.Rows.Count);
+ Assert.Equal(4, grid.Rows.First()["P1"]);
+ }
+
+ [Fact]
+ public void PageIndexResetWhenQueryStringValueIsInvalid()
+ {
+ NameValueCollection queryString = new NameValueCollection();
+ queryString["page"] = "NotAnInt";
+ var grid = new WebGrid(GetContext(queryString), rowsPerPage: 1)
+ .Bind(new[]
+ {
+ new { P1 = 1, P2 = '2', P3 = "3" },
+ new { P1 = 4, P2 = '5', P3 = "6" }
+ });
+ Assert.Equal(0, grid.PageIndex);
+ Assert.Equal(1, grid.Rows.Count);
+ Assert.Equal(1, grid.Rows.First()["P1"]);
+ }
+
+ [Fact]
+ public void PageIndexResetWhenQueryStringValueLessThanOne()
+ {
+ NameValueCollection queryString = new NameValueCollection();
+ queryString["page"] = "0";
+ var grid = new WebGrid(GetContext(queryString), rowsPerPage: 1)
+ .Bind(new[]
+ {
+ new { P1 = 1, P2 = '2', P3 = "3" },
+ new { P1 = 4, P2 = '5', P3 = "6" }
+ });
+ Assert.Equal(0, grid.PageIndex);
+ Assert.Equal(1, grid.Rows.Count);
+ Assert.Equal(1, grid.Rows.First()["P1"]);
+ }
+
+ [Fact]
+ public void PageIndexUsesCustomQueryString()
+ {
+ NameValueCollection queryString = new NameValueCollection();
+ queryString["g_pg"] = "2";
+ var grid = new WebGrid(GetContext(queryString), rowsPerPage: 1, fieldNamePrefix: "g_", pageFieldName: "pg")
+ .Bind(new[]
+ {
+ new { P1 = 1, P2 = '2', P3 = "3" },
+ new { P1 = 4, P2 = '5', P3 = "6" }
+ });
+ Assert.Equal(1, grid.PageIndex);
+ Assert.Equal(1, grid.Rows.Count);
+ Assert.Equal(4, grid.Rows.First()["P1"]);
+ }
+
+ [Fact]
+ public void PageIndexUsesQueryString()
+ {
+ NameValueCollection queryString = new NameValueCollection();
+ queryString["page"] = "2";
+ var grid = new WebGrid(GetContext(queryString), rowsPerPage: 1)
+ .Bind(new[]
+ {
+ new { P1 = 1, P2 = '2', P3 = "3" },
+ new { P1 = 4, P2 = '5', P3 = "6" }
+ });
+ Assert.Equal(1, grid.PageIndex);
+ Assert.Equal(1, grid.Rows.Count);
+ Assert.Equal(4, grid.Rows.First()["P1"]);
+ }
+
+ [Fact]
+ public void GetPageCountWhenPagingIsTurnedOn()
+ {
+ var grid = new WebGrid(GetContext(), canPage: true, rowsPerPage: 1)
+ .Bind(new[]
+ {
+ new { P1 = 1, P2 = '2', P3 = "3" },
+ new { P1 = 4, P2 = '5', P3 = "6" }
+ });
+ Assert.Equal(2, grid.PageCount);
+ }
+
+ [Fact]
+ public void GetPageIndexWhenPagingIsTurnedOn()
+ {
+ var grid = new WebGrid(GetContext(), canPage: true, rowsPerPage: 1)
+ .Bind(new[]
+ {
+ new { P1 = 1, P2 = '2', P3 = "3" },
+ new { P1 = 4, P2 = '5', P3 = "6" },
+ new { P1 = 4, P2 = '5', P3 = "6" },
+ });
+ grid.PageIndex = 1;
+ Assert.Equal(1, grid.PageIndex);
+ Assert.Equal(3, grid.PageCount);
+ grid.PageIndex = 2;
+ Assert.Equal(2, grid.PageIndex);
+ }
+
+ [Fact]
+ public void GetPageCountWhenPagingIsTurnedOff()
+ {
+ var grid = new WebGrid(GetContext(), canPage: false, rowsPerPage: 1)
+ .Bind(new[]
+ {
+ new { P1 = 1, P2 = '2', P3 = "3" },
+ new { P1 = 4, P2 = '5', P3 = "6" }
+ });
+ Assert.Equal(1, grid.PageCount);
+ }
+
+ [Fact]
+ public void GetPageIndexWhenPagingIsTurnedOff()
+ {
+ var grid = new WebGrid(GetContext(), canPage: false, rowsPerPage: 1)
+ .Bind(new[]
+ {
+ new { P1 = 1, P2 = '2', P3 = "3" },
+ new { P1 = 4, P2 = '5', P3 = "6" },
+ new { P1 = 4, P2 = '5', P3 = "6" },
+ });
+ Assert.Equal(0, grid.PageIndex);
+ Assert.Equal(1, grid.PageCount);
+ }
+
+ [Fact]
+ public void PageUrlResetsSelection()
+ {
+ NameValueCollection queryString = new NameValueCollection();
+ queryString["page"] = "0";
+ queryString["row"] = "0";
+ queryString["sort"] = "P1";
+ queryString["sortdir"] = "DESC";
+ var grid = new WebGrid(GetContext(queryString), rowsPerPage: 1)
+ .Bind(new[]
+ {
+ new { P1 = 1, P2 = '2', P3 = "3" },
+ new { P1 = 4, P2 = '5', P3 = "6" }
+ });
+ string url = grid.GetPageUrl(1);
+ Assert.Equal("?page=2&sort=P1&sortdir=DESC", url);
+ }
+
+ [Fact]
+ public void PageUrlResetsSelectionForAjax()
+ {
+ string expected40 = "$(&quot;#grid-container&quot;).swhgLoad(&quot;?page=2&amp;sort=P1&amp;sortdir=DESC&quot;,&quot;#grid-container&quot;);";
+ string expected45 = "$(&quot;#grid-container&quot;).swhgLoad(&quot;?page=2\\u0026sort=P1\\u0026sortdir=DESC&quot;,&quot;#grid-container&quot;);";
+ NameValueCollection queryString = new NameValueCollection();
+ queryString["page"] = "0";
+ queryString["row"] = "0";
+ queryString["sort"] = "P1";
+ queryString["sortdir"] = "DESC";
+ var grid = new WebGrid(GetContext(queryString), rowsPerPage: 1, ajaxUpdateContainerId: "grid-container")
+ .Bind(new[]
+ {
+ new { P1 = 1, P2 = '2', P3 = "3" },
+ new { P1 = 4, P2 = '5', P3 = "6" }
+ });
+ string html = grid.GetContainerUpdateScript(grid.GetPageUrl(1)).ToString();
+
+ // Assert
+ Assert.Equal(RuntimeEnvironment.IsVersion45Installed ? expected45 : expected40, html);
+ }
+
+ [Fact]
+ public void PageUrlResetsSelectionForAjaxWithCallback()
+ {
+ string expected40 = "$(&quot;#grid&quot;).swhgLoad(&quot;?page=2&amp;sort=P1&amp;sortdir=DESC&quot;,&quot;#grid&quot;,myCallback);";
+ string expected45 = "$(&quot;#grid&quot;).swhgLoad(&quot;?page=2\\u0026sort=P1\\u0026sortdir=DESC&quot;,&quot;#grid&quot;,myCallback);";
+ NameValueCollection queryString = new NameValueCollection();
+ queryString["page"] = "0";
+ queryString["row"] = "0";
+ queryString["sort"] = "P1";
+ queryString["sortdir"] = "DESC";
+ var grid = new WebGrid(GetContext(queryString), rowsPerPage: 1, ajaxUpdateContainerId: "grid", ajaxUpdateCallback: "myCallback")
+ .Bind(new[]
+ {
+ new { P1 = 1, P2 = '2', P3 = "3" },
+ new { P1 = 4, P2 = '5', P3 = "6" }
+ });
+ string html = grid.GetContainerUpdateScript(grid.GetPageUrl(1)).ToString();
+
+ // Assert
+ Assert.Equal(RuntimeEnvironment.IsVersion45Installed ? expected45 : expected40, html);
+ }
+
+ [Fact]
+ public void PageUrlThrowsIfIndexGreaterThanOrEqualToPageCount()
+ {
+ var grid = new WebGrid(GetContext(), rowsPerPage: 1).Bind(new[] { new { }, new { } });
+ Assert.ThrowsArgumentOutOfRange(() => { grid.GetPageUrl(2); }, "pageIndex", "Value must be between 0 and 1.");
+ }
+
+ [Fact]
+ public void PageUrlThrowsIfIndexLessThanZero()
+ {
+ var grid = new WebGrid(GetContext(), rowsPerPage: 1).Bind(new[] { new { }, new { } });
+ Assert.ThrowsArgumentOutOfRange(() => { grid.GetPageUrl(-1); }, "pageIndex", "Value must be between 0 and 1.");
+ }
+
+ [Fact]
+ public void PageUrlThrowsIfPagingIsDisabled()
+ {
+ var grid = new WebGrid(GetContext(), rowsPerPage: 1, canPage: false).Bind(new[] { new { }, new { } });
+ Assert.Throws<NotSupportedException>(() => { grid.GetPageUrl(2); }, "This operation is not supported when paging is disabled for the \"WebGrid\" object.");
+ }
+
+ [Fact]
+ public void PagerRenderingDefaults()
+ {
+ var grid = new WebGrid(GetContext(), rowsPerPage: 1).Bind(new[] { new { }, new { }, new { }, new { } });
+ var html = grid.Pager();
+ Assert.Equal(
+ "1 " +
+ "<a href=\"?page=2\">2</a> " +
+ "<a href=\"?page=3\">3</a> " +
+ "<a href=\"?page=4\">4</a> " +
+ "<a href=\"?page=2\">&gt;</a> ",
+ html.ToString());
+ XhtmlAssert.Validate1_1(html, wrapper: "div");
+ }
+
+ [Fact]
+ public void PagerRenderingOnFirstShowingAll()
+ {
+ var grid = new WebGrid(GetContext(), rowsPerPage: 1).Bind(new[] { new { }, new { }, new { }, new { } });
+ var html = grid.Pager(WebGridPagerModes.All, numericLinksCount: 5);
+ Assert.Equal(
+ "1 " +
+ "<a href=\"?page=2\">2</a> " +
+ "<a href=\"?page=3\">3</a> " +
+ "<a href=\"?page=4\">4</a> " +
+ "<a href=\"?page=2\">&gt;</a> " +
+ "<a href=\"?page=4\">&gt;&gt;</a>",
+ html.ToString());
+ XhtmlAssert.Validate1_1(html, wrapper: "div");
+ }
+
+ [Fact]
+ public void PagerRenderingOnNextToLastShowingAll()
+ {
+ NameValueCollection queryString = new NameValueCollection();
+ queryString["page"] = "3";
+ var grid = new WebGrid(GetContext(queryString), rowsPerPage: 1).Bind(new[]
+ {
+ new { }, new { }, new { }, new { }
+ });
+ var html = grid.Pager(WebGridPagerModes.All, numericLinksCount: 4);
+ Assert.Equal(
+ "<a href=\"?page=1\">&lt;&lt;</a> " +
+ "<a href=\"?page=2\">&lt;</a> " +
+ "<a href=\"?page=1\">1</a> " +
+ "<a href=\"?page=2\">2</a> " +
+ "3 " +
+ "<a href=\"?page=4\">4</a> " +
+ "<a href=\"?page=4\">&gt;</a> ",
+ html.ToString());
+ XhtmlAssert.Validate1_1(html, wrapper: "div");
+ }
+
+ [Fact]
+ public void PagerRenderingOnMiddleShowingAll()
+ {
+ NameValueCollection queryString = new NameValueCollection();
+ queryString["page"] = "3";
+ var grid = new WebGrid(GetContext(queryString), rowsPerPage: 1).Bind(new[]
+ {
+ new { }, new { }, new { }, new { }
+ });
+ var html = grid.Pager(WebGridPagerModes.All, numericLinksCount: 3);
+ Assert.Equal(
+ "<a href=\"?page=1\">&lt;&lt;</a> " +
+ "<a href=\"?page=2\">&lt;</a> " +
+ "<a href=\"?page=2\">2</a> " +
+ "3 " +
+ "<a href=\"?page=4\">4</a> " +
+ "<a href=\"?page=4\">&gt;</a> ",
+ html.ToString());
+ XhtmlAssert.Validate1_1(html, wrapper: "div");
+ }
+
+ [Fact]
+ public void PagerRenderingOnSecondHidingFirstLast()
+ {
+ NameValueCollection queryString = new NameValueCollection();
+ queryString["page"] = "2";
+ var grid = new WebGrid(GetContext(queryString), rowsPerPage: 1).Bind(new[]
+ {
+ new { }, new { }, new { }, new { }
+ });
+ var html = grid.Pager(WebGridPagerModes.NextPrevious | WebGridPagerModes.Numeric, numericLinksCount: 2);
+ Assert.Equal(
+ "<a href=\"?page=1\">&lt;</a> " +
+ "2 " +
+ "<a href=\"?page=3\">3</a> " +
+ "<a href=\"?page=3\">&gt;</a> ",
+ html.ToString());
+ XhtmlAssert.Validate1_1(html, wrapper: "div");
+ }
+
+ [Fact]
+ public void PagerRenderingOnLastHidingFirstLast()
+ {
+ NameValueCollection queryString = new NameValueCollection();
+ queryString["page"] = "4";
+ var grid = new WebGrid(GetContext(queryString), rowsPerPage: 1).Bind(new[]
+ {
+ new { }, new { }, new { }, new { }
+ });
+ var html = grid.Pager(WebGridPagerModes.NextPrevious | WebGridPagerModes.Numeric, numericLinksCount: 1);
+ Assert.Equal(
+ "<a href=\"?page=3\">&lt;</a> " +
+ "4 ",
+ html.ToString());
+ XhtmlAssert.Validate1_1(html, wrapper: "div");
+ }
+
+ [Fact]
+ public void PagerRenderingOnMiddleHidingNextPrevious()
+ {
+ NameValueCollection queryString = new NameValueCollection();
+ queryString["page"] = "3";
+ var grid = new WebGrid(GetContext(queryString), rowsPerPage: 1).Bind(new[]
+ {
+ new { }, new { }, new { }, new { }
+ });
+ var html = grid.Pager(WebGridPagerModes.FirstLast | WebGridPagerModes.Numeric, numericLinksCount: 0);
+ Assert.Equal(
+ "<a href=\"?page=1\">&lt;&lt;</a> ",
+ html.ToString());
+ XhtmlAssert.Validate1_1(html, wrapper: "div");
+ }
+
+ [Fact]
+ public void PagerRenderingOnMiddleWithLinksCountGreaterThanPageCount()
+ {
+ NameValueCollection queryString = new NameValueCollection();
+ queryString["page"] = "3";
+ var grid = new WebGrid(GetContext(queryString), rowsPerPage: 1).Bind(new[]
+ {
+ new { }, new { }, new { }, new { }
+ });
+ var html = grid.Pager(WebGridPagerModes.Numeric, numericLinksCount: 6);
+ Assert.Equal(
+ "<a href=\"?page=1\">1</a> " +
+ "<a href=\"?page=2\">2</a> " +
+ "3 " +
+ "<a href=\"?page=4\">4</a> ",
+ html.ToString());
+ XhtmlAssert.Validate1_1(html, wrapper: "div");
+ }
+
+ [Fact]
+ public void PagerRenderingHidingAll()
+ {
+ var grid = new WebGrid(GetContext(), rowsPerPage: 2).Bind(new[]
+ {
+ new { }, new { }, new { }, new { }
+ });
+ var html = grid.Pager(WebGridPagerModes.Numeric, numericLinksCount: 0);
+ Assert.Equal("", html.ToString());
+ }
+
+ [Fact]
+ public void PagerRenderingTextOverrides()
+ {
+ NameValueCollection queryString = new NameValueCollection();
+ queryString["page"] = "3";
+ var grid = new WebGrid(GetContext(queryString), rowsPerPage: 1).Bind(new[]
+ {
+ new { }, new { }, new { }, new { }, new { }
+ });
+ var html = grid.Pager(WebGridPagerModes.FirstLast | WebGridPagerModes.NextPrevious,
+ firstText: "first", previousText: "previous", nextText: "next", lastText: "last");
+ Assert.Equal(
+ "<a href=\"?page=1\">first</a> " +
+ "<a href=\"?page=2\">previous</a> " +
+ "<a href=\"?page=4\">next</a> " +
+ "<a href=\"?page=5\">last</a>",
+ html.ToString());
+ XhtmlAssert.Validate1_1(html, wrapper: "div");
+ }
+
+ [Fact]
+ public void PagerThrowsIfTextSetAndModeNotEnabled()
+ {
+ var grid = new WebGrid(GetContext(), rowsPerPage: 1).Bind(new[] { new { }, new { } });
+ Assert.ThrowsArgument(() => { grid.Pager(firstText: "first"); }, "firstText", "To use this argument, pager mode \"FirstLast\" must be enabled.");
+ Assert.ThrowsArgument(() => { grid.Pager(mode: WebGridPagerModes.Numeric, previousText: "previous"); }, "previousText", "To use this argument, pager mode \"NextPrevious\" must be enabled.");
+ Assert.ThrowsArgument(() => { grid.Pager(mode: WebGridPagerModes.Numeric, nextText: "next"); }, "nextText", "To use this argument, pager mode \"NextPrevious\" must be enabled.");
+ Assert.ThrowsArgument(() => { grid.Pager(lastText: "last"); }, "lastText", "To use this argument, pager mode \"FirstLast\" must be enabled.");
+ }
+
+ [Fact]
+ public void PagerThrowsIfNumericLinkCountIsLessThanZero()
+ {
+ var grid = new WebGrid(GetContext(), rowsPerPage: 1).Bind(new[] { new { }, new { } });
+ Assert.ThrowsArgumentOutOfRange(() => { grid.Pager(numericLinksCount: -1); }, "numericLinksCount", "Value must be greater than or equal to 0.");
+ }
+
+ [Fact]
+ public void PagerThrowsIfPagingIsDisabled()
+ {
+ var grid = new WebGrid(GetContext(), rowsPerPage: 1, canPage: false).Bind(new[] { new { }, new { } });
+ Assert.Throws<NotSupportedException>(() => { grid.Pager(); }, "This operation is not supported when paging is disabled for the \"WebGrid\" object.");
+ }
+
+ [Fact]
+ public void PagerWithAjax()
+ {
+ var grid = new WebGrid(GetContext(), rowsPerPage: 1, ajaxUpdateContainerId: "grid")
+ .Bind(new[]
+ {
+ new { P1 = 1, P2 = '2', P3 = "3" },
+ new { P1 = 4, P2 = '5', P3 = "6" }
+ });
+ string html = grid.Pager().ToString();
+ Assert.True(html.Contains("<script"));
+ }
+
+ [Fact]
+ public void PagerWithAjaxAndCallback()
+ {
+ var grid = new WebGrid(GetContext(), rowsPerPage: 1, ajaxUpdateContainerId: "grid", ajaxUpdateCallback: "myCallback")
+ .Bind(new[]
+ {
+ new { P1 = 1, P2 = '2', P3 = "3" },
+ new { P1 = 4, P2 = '5', P3 = "6" }
+ });
+ string html = grid.Pager().ToString();
+ Assert.True(html.Contains("<script"));
+ Assert.True(html.Contains("data-swhgcallback=\"myCallback\""));
+ }
+
+ [Fact]
+ public void PropertySettersDoNotThrowBeforePagingAndSorting()
+ {
+ // test with selection because SelectedIndex getter used to do range checking that caused paging and sorting
+ NameValueCollection queryString = new NameValueCollection();
+ queryString["row"] = "1";
+ var grid = new WebGrid(GetContext(queryString), rowsPerPage: 2).Bind(new[]
+ {
+ new { P1 = 1 }, new { P1 = 2 }, new { P1 = 3 }
+ });
+
+ // invoke other WebGrid properties to ensure they don't cause sorting and paging
+ foreach (var prop in typeof(WebGrid).GetProperties())
+ {
+ // exceptions: these do cause sorting and paging
+ if (prop.Name.Equals("Rows") || prop.Name.Equals("SelectedRow") || prop.Name.Equals("ElementType"))
+ {
+ continue;
+ }
+ prop.GetValue(grid, null);
+ }
+
+ grid.PageIndex = 1;
+ grid.SelectedIndex = 0;
+ grid.SortColumn = "P1";
+ grid.SortDirection = SortDirection.Descending;
+ }
+
+ [Fact]
+ public void PropertySettersDoNotThrowAfterPagingAndSortingIfValuesHaveNotChanged()
+ {
+ var grid = new WebGrid(GetContext(), rowsPerPage: 2).Bind(new[]
+ {
+ new { P1 = 1 }, new { P1 = 2 }, new { P1 = 3 }
+ });
+ // calling Rows will sort and page the data
+ Assert.Equal(2, grid.Rows.Count());
+
+ grid.PageIndex = 0;
+ grid.SelectedIndex = -1;
+ grid.SortColumn = String.Empty;
+ grid.SortDirection = SortDirection.Ascending;
+ }
+
+ [Fact]
+ public void PropertySettersThrowAfterPagingAndSorting()
+ {
+ var grid = new WebGrid(GetContext(), rowsPerPage: 2).Bind(new[]
+ {
+ new { P1 = 1 }, new { P1 = 2 }, new { P1 = 3 }
+ });
+ // calling Rows will sort and page the data
+ Assert.Equal(2, grid.Rows.Count());
+
+ var message = "This property cannot be set after the \"WebGrid\" object has been sorted or paged. Make sure that this property is set prior to invoking the \"Rows\" property directly or indirectly through other methods such as \"GetHtml\", \"Pager\", \"Table\", etc.";
+ Assert.Throws<InvalidOperationException>(() => { grid.PageIndex = 1; }, message);
+ Assert.Throws<InvalidOperationException>(() => { grid.SelectedIndex = 0; }, message);
+ Assert.Throws<InvalidOperationException>(() => { grid.SortColumn = "P1"; }, message);
+ Assert.Throws<InvalidOperationException>(() => { grid.SortDirection = SortDirection.Descending; }, message);
+ }
+
+ [Fact]
+ public void RowColumnsAreDynamicMembersForDynamics()
+ {
+ var grid = new WebGrid(GetContext()).Bind(Dynamics(
+ new { P1 = 1, P2 = '2', P3 = "3" }
+ ));
+ dynamic row = grid.Rows.First();
+ Assert.Equal(1, row.P1);
+ Assert.Equal('2', row.P2);
+ Assert.Equal("3", row.P3);
+ }
+
+ [Fact]
+ public void RowColumnsAreDynamicMembersForNonDynamics()
+ {
+ var grid = new WebGrid(GetContext()).Bind(new[]
+ {
+ new { P1 = 1, P2 = '2', P3 = "3" }
+ });
+ dynamic row = grid.Rows.First();
+ Assert.Equal(1, row.P1);
+ Assert.Equal('2', row.P2);
+ Assert.Equal("3", row.P3);
+ }
+
+ [Fact]
+ public void RowExposesRowIndex()
+ {
+ var grid = new WebGrid(GetContext()).Bind(new[]
+ {
+ new { }, new { }, new { }
+ });
+ dynamic row = grid.Rows.First();
+ Assert.Equal(0, row["ROW"]);
+ row = grid.Rows.Skip(1).First();
+ Assert.Equal(1, row.ROW);
+ row = grid.Rows.Skip(2).First();
+ Assert.Equal(2, row.ROW);
+ }
+
+ [Fact]
+ public void RowExposesUnderlyingValue()
+ {
+ var sb = new StringBuilder("Foo");
+ sb.Append("Bar");
+ var grid = new WebGrid(GetContext()).Bind(new[] { sb });
+ var row = grid.Rows.First();
+ Assert.Equal(sb, row.Value);
+ Assert.Equal("FooBar", row.ToString());
+ Assert.Equal(grid, row.WebGrid);
+ }
+
+ [Fact]
+ public void RowIndexerThrowsWhenColumnNameIsEmpty()
+ {
+ var grid = new WebGrid(GetContext()).Bind(new[] { new { } });
+ var row = grid.Rows.First();
+ Assert.ThrowsArgumentNullOrEmptyString(() => { var value = row[String.Empty]; }, "name");
+ }
+
+ [Fact]
+ public void RowIndexerThrowsWhenColumnNameIsNull()
+ {
+ var grid = new WebGrid(GetContext()).Bind(new[] { new { } });
+ var row = grid.Rows.First();
+ Assert.ThrowsArgumentNullOrEmptyString(() => { var value = row[null]; }, "name");
+ }
+
+ [Fact] // todo - should throw ArgumentException?
+ public void RowIndexerThrowsWhenColumnNotFound()
+ {
+ var grid = new WebGrid(GetContext()).Bind(new[] { new { } });
+ var row = grid.Rows.First();
+ Assert.Throws<InvalidOperationException>(() => { var value = row["NotAColumn"]; });
+ }
+
+ [Fact]
+ public void RowIndexerThrowsWhenGreaterThanColumnCount()
+ {
+ var grid = new WebGrid(GetContext()).Bind(new[]
+ {
+ new { P1 = 1, P2 = '2', P3 = "3" }
+ });
+ var row = grid.Rows.First();
+ Assert.Throws<ArgumentOutOfRangeException>(() => { var value = row[4]; });
+ }
+
+ [Fact]
+ public void RowIndexerThrowsWhenLessThanZero()
+ {
+ var grid = new WebGrid(GetContext()).Bind(new[] { new { } });
+ var row = grid.Rows.First();
+ Assert.Throws<ArgumentOutOfRangeException>(() => { var value = row[-1]; });
+ }
+
+ [Fact]
+ public void RowIsEnumerableForDynamics()
+ {
+ var grid = new WebGrid(GetContext()).Bind(Dynamics(
+ new { P1 = 1, P2 = '2', P3 = "3" }
+ ));
+ int i = 0;
+ foreach (var col in (IEnumerable)grid.Rows.First())
+ {
+ i++;
+ }
+ Assert.Equal(3, i);
+ }
+
+ [Fact]
+ public void RowIsEnumerableForNonDynamics()
+ {
+ var grid = new WebGrid(GetContext()).Bind(new[]
+ {
+ new { P1 = 1, P2 = '2', P3 = "3" }
+ });
+ int i = 0;
+ foreach (var col in grid.Rows.First())
+ {
+ i++;
+ }
+ Assert.Equal(3, i);
+ }
+
+ [Fact]
+ public void RowIsIndexableByColumnForDynamics()
+ {
+ var grid = new WebGrid(GetContext()).Bind(Dynamics(
+ new { P1 = 1, P2 = '2', P3 = "3" }
+ ));
+ var row = grid.Rows.First();
+ Assert.Equal(1, row["P1"]);
+ Assert.Equal('2', row["P2"]);
+ Assert.Equal("3", row["P3"]);
+ }
+
+ [Fact]
+ public void RowIsIndexableByColumnForNonDynamics()
+ {
+ var grid = new WebGrid(GetContext()).Bind(new[]
+ {
+ new { P1 = 1, P2 = '2', P3 = "3" }
+ });
+ var row = grid.Rows.First();
+ Assert.Equal(1, row["P1"]);
+ Assert.Equal('2', row["P2"]);
+ Assert.Equal("3", row["P3"]);
+ }
+
+ [Fact]
+ public void RowIsIndexableByIndexForDynamics()
+ {
+ var grid = new WebGrid(GetContext()).Bind(Dynamics(
+ new { P1 = 1, P2 = '2', P3 = "3" }
+ ));
+ var row = grid.Rows.First();
+ Assert.Equal(1, row[0]);
+ Assert.Equal('2', row[1]);
+ Assert.Equal("3", row[2]);
+ }
+
+ [Fact]
+ public void RowIsIndexableByIndexForNonDynamics()
+ {
+ var grid = new WebGrid(GetContext()).Bind(new[]
+ {
+ new { P1 = 1, P2 = '2', P3 = "3" }
+ });
+ var row = grid.Rows.First();
+ Assert.Equal(1, row[0]);
+ Assert.Equal('2', row[1]);
+ Assert.Equal("3", row[2]);
+ }
+
+ [Fact]
+ public void RowsNotPagedWhenPagingIsDisabled()
+ {
+ NameValueCollection queryString = new NameValueCollection();
+ queryString["page"] = "2";
+ var grid = new WebGrid(GetContext(queryString), rowsPerPage: 1, canPage: false)
+ .Bind(new[]
+ {
+ new { P1 = 1, P2 = '2', P3 = "3" },
+ new { P1 = 4, P2 = '5', P3 = "6" }
+ });
+ // review: should we reset PageIndex or Sort when operation disabled?
+ Assert.Equal(0, grid.PageIndex);
+ Assert.Equal(2, grid.Rows.Count);
+ Assert.Equal(1, grid.Rows.First()["P1"]);
+ Assert.Equal(4, grid.Rows.Skip(1).First()["P1"]);
+ }
+
+ [Fact] // todo - should throw ArgumentException?
+ public void RowTryGetMemberReturnsFalseWhenColumnNotFound()
+ {
+ var grid = new WebGrid(GetContext()).Bind(new[] { new { } });
+ var row = grid.Rows.First();
+ object value = null;
+ Assert.False(row.TryGetMember("NotAColumn", out value));
+ }
+
+ [Fact]
+ public void SelectedIndexCanBeReset()
+ {
+ NameValueCollection queryString = new NameValueCollection();
+ queryString["row"] = "2";
+ var grid = new WebGrid(GetContext(queryString)).Bind(new[]
+ {
+ new { P1 = 1, P2 = '2', P3 = "3" },
+ new { P1 = 4, P2 = '5', P3 = "6" }
+ });
+ Assert.Equal(1, grid.SelectedIndex);
+ grid.SelectedIndex = 0;
+ Assert.Equal(0, grid.SelectedIndex);
+ }
+
+ [Fact]
+ public void SelectedIndexCanBeResetToSameValue()
+ {
+ NameValueCollection queryString = new NameValueCollection();
+ queryString["row"] = "2";
+ var grid = new WebGrid(GetContext(queryString)).Bind(new[]
+ {
+ new { P1 = 1, P2 = '2', P3 = "3" },
+ new { P1 = 4, P2 = '5', P3 = "6" }
+ });
+ grid.SelectedIndex = -1;
+ Assert.Equal(-1, grid.SelectedIndex);
+ }
+
+ [Fact]
+ public void SelectedIndexDefaultsToNegative()
+ {
+ var grid = new WebGrid(GetContext()).Bind(new[]
+ {
+ new { P1 = 1, P2 = '2', P3 = "3" },
+ new { P1 = 4, P2 = '5', P3 = "6" }
+ });
+ Assert.False(grid.HasSelection);
+ Assert.Equal(-1, grid.SelectedIndex);
+ Assert.Equal(null, grid.SelectedRow);
+ }
+
+ [Fact]
+ public void SelectedIndexResetWhenQueryStringValueGreaterThanRowsPerPage()
+ {
+ NameValueCollection queryString = new NameValueCollection();
+ queryString["row"] = "3";
+ var grid = new WebGrid(GetContext(queryString), rowsPerPage: 2).Bind(new[]
+ {
+ new { P1 = 1, P2 = '2', P3 = "3" },
+ new { P1 = 4, P2 = '5', P3 = "6" }
+ });
+ Assert.False(grid.HasSelection);
+ Assert.Equal(-1, grid.SelectedIndex);
+ Assert.Equal(null, grid.SelectedRow);
+ }
+
+ [Fact]
+ public void SelectedIndexPersistsWhenPagingTurnedOff()
+ {
+ NameValueCollection queryString = new NameValueCollection();
+ queryString["row"] = "3";
+ var grid = new WebGrid(GetContext(queryString), rowsPerPage: 2, canPage: false).Bind(new[]
+ {
+ new { }, new { }, new { }, new { }
+ });
+ grid.SelectedIndex = 3;
+ Assert.Equal(3, grid.SelectedIndex);
+ }
+
+ [Fact]
+ public void SelectedIndexResetWhenQueryStringValueIsInvalid()
+ {
+ NameValueCollection queryString = new NameValueCollection();
+ queryString["row"] = "NotAnInt";
+ var grid = new WebGrid(GetContext(queryString)).Bind(new[]
+ {
+ new { P1 = 1, P2 = '2', P3 = "3" },
+ new { P1 = 4, P2 = '5', P3 = "6" }
+ });
+ Assert.False(grid.HasSelection);
+ Assert.Equal(-1, grid.SelectedIndex);
+ Assert.Equal(null, grid.SelectedRow);
+ }
+
+ [Fact]
+ public void SelectedIndexResetWhenQueryStringValueLessThanOne()
+ {
+ NameValueCollection queryString = new NameValueCollection();
+ queryString["row"] = "0";
+ var grid = new WebGrid(GetContext(queryString)).Bind(new[]
+ {
+ new { P1 = 1, P2 = '2', P3 = "3" },
+ new { P1 = 4, P2 = '5', P3 = "6" }
+ });
+ Assert.False(grid.HasSelection);
+ Assert.Equal(-1, grid.SelectedIndex);
+ Assert.Equal(null, grid.SelectedRow);
+ }
+
+ [Fact]
+ public void SelectedIndexUsesCustomQueryString()
+ {
+ NameValueCollection queryString = new NameValueCollection();
+ queryString["g_sel"] = "2";
+ var grid = new WebGrid(GetContext(queryString), fieldNamePrefix: "g_", selectionFieldName: "sel").Bind(new[]
+ {
+ new { P1 = 1, P2 = '2', P3 = "3" },
+ new { P1 = 4, P2 = '5', P3 = "6" }
+ });
+ Assert.True(grid.HasSelection);
+ Assert.Equal(1, grid.SelectedIndex);
+ Assert.NotNull(grid.SelectedRow);
+ Assert.Equal(4, grid.SelectedRow["P1"]);
+ }
+
+ [Fact]
+ public void SelectedIndexUsesQueryString()
+ {
+ NameValueCollection queryString = new NameValueCollection();
+ queryString["row"] = "2";
+ var grid = new WebGrid(GetContext(queryString)).Bind(new[]
+ {
+ new { P1 = 1, P2 = '2', P3 = "3" },
+ new { P1 = 4, P2 = '5', P3 = "6" }
+ });
+ Assert.True(grid.HasSelection);
+ Assert.Equal(1, grid.SelectedIndex);
+ Assert.NotNull(grid.SelectedRow);
+ Assert.Equal(4, grid.SelectedRow["P1"]);
+ }
+
+ [Fact]
+ public void SelectLink()
+ {
+ NameValueCollection queryString = new NameValueCollection();
+ queryString["page"] = "1";
+ queryString["row"] = "1";
+ queryString["sort"] = "P1";
+ queryString["sortdir"] = "DESC";
+ var grid = new WebGrid(GetContext(queryString)).Bind(new[]
+ {
+ new { P1 = 1, P2 = '2', P3 = "3" },
+ new { P1 = 4, P2 = '5', P3 = "6" }
+ });
+ string html = grid.Rows[1].GetSelectLink().ToString();
+ Assert.Equal("<a href=\"?page=1&amp;row=2&amp;sort=P1&amp;sortdir=DESC\">Select</a>", html.ToString());
+ }
+
+ [Fact]
+ public void SortCanBeReset()
+ {
+ NameValueCollection queryString = new NameValueCollection();
+ queryString["sort"] = "P1";
+ var grid = new WebGrid(GetContext(queryString)).Bind(new[]
+ {
+ new { P1 = 1, P2 = '2', P3 = "3" },
+ new { P1 = 4, P2 = '5', P3 = "6" }
+ });
+ Assert.Equal("P1", grid.SortColumn);
+ grid.SortColumn = "P2";
+ Assert.Equal("P2", grid.SortColumn);
+ // verify that selection and page links have updated sort
+ Assert.Equal("?sort=P2&row=1", grid.Rows.FirstOrDefault().GetSelectUrl());
+ Assert.Equal("?sort=P2&page=1", grid.GetPageUrl(0));
+ }
+
+ [Fact]
+ public void SortCanBeResetToNull()
+ {
+ NameValueCollection queryString = new NameValueCollection();
+ queryString["sort"] = "P1";
+ var grid = new WebGrid(GetContext(queryString)).Bind(new[]
+ {
+ new { P1 = 1, P2 = '2', P3 = "3" },
+ new { P1 = 4, P2 = '5', P3 = "6" }
+ });
+ Assert.Equal("P1", grid.SortColumn);
+ grid.SortColumn = null;
+ Assert.Equal(String.Empty, grid.SortColumn);
+ // verify that selection and page links have updated sort
+ Assert.Equal("?row=1", grid.Rows.FirstOrDefault().GetSelectUrl());
+ Assert.Equal("?page=1", grid.GetPageUrl(0));
+ }
+
+ [Fact]
+ public void SortCanBeResetToSameValue()
+ {
+ NameValueCollection queryString = new NameValueCollection();
+ queryString["sort"] = "P1";
+ var grid = new WebGrid(GetContext(queryString)).Bind(new[]
+ {
+ new { P1 = 1, P2 = '2', P3 = "3" },
+ new { P1 = 4, P2 = '5', P3 = "6" }
+ });
+ grid.SortColumn = String.Empty;
+ Assert.Equal(String.Empty, grid.SortColumn);
+ }
+
+ [Fact]
+ public void SortColumnDefaultsToEmpty()
+ {
+ var grid = new WebGrid(GetContext()).Bind(new[]
+ {
+ new { P1 = 1, P2 = '2', P3 = "3" }
+ });
+ Assert.Equal(String.Empty, grid.SortColumn);
+ }
+
+ [Fact]
+ public void SortColumnResetWhenQueryStringValueIsInvalid()
+ {
+ NameValueCollection queryString = new NameValueCollection();
+ queryString["sort"] = "P4";
+ var grid = new WebGrid(GetContext(queryString)).Bind(new[]
+ {
+ new { P1 = 1, P2 = '2', P3 = "3" }
+ });
+ Assert.Equal("", grid.SortColumn);
+ }
+
+ [Fact]
+ public void SortColumnUsesCustomQueryString()
+ {
+ NameValueCollection queryString = new NameValueCollection();
+ queryString["g_st"] = "P2";
+ var grid = new WebGrid(GetContext(queryString), fieldNamePrefix: "g_", sortFieldName: "st").Bind(new[]
+ {
+ new { P1 = 1, P2 = '2', P3 = "3" }
+ });
+ Assert.Equal("P2", grid.SortColumn);
+ }
+
+ [Fact]
+ public void SortColumnUsesQueryString()
+ {
+ NameValueCollection queryString = new NameValueCollection();
+ queryString["sort"] = "P2";
+ var grid = new WebGrid(GetContext(queryString)).Bind(new[]
+ {
+ new { P1 = 1, P2 = '2', P3 = "3" }
+ });
+ Assert.Equal("P2", grid.SortColumn);
+ }
+
+ [Fact]
+ public void SortDirectionCanBeReset()
+ {
+ NameValueCollection queryString = new NameValueCollection();
+ queryString["sortdir"] = "DESC";
+ var grid = new WebGrid(GetContext(queryString)).Bind(new[]
+ {
+ new { P1 = 1, P2 = '2', P3 = "3" },
+ new { P1 = 4, P2 = '5', P3 = "6" }
+ });
+ Assert.Equal(SortDirection.Descending, grid.SortDirection);
+ grid.SortDirection = SortDirection.Ascending;
+ Assert.Equal(SortDirection.Ascending, grid.SortDirection);
+ // verify that selection and page links have updated sort
+ Assert.Equal("?sortdir=ASC&row=1", grid.Rows.FirstOrDefault().GetSelectUrl());
+ Assert.Equal("?sortdir=ASC&page=1", grid.GetPageUrl(0));
+ }
+
+ [Fact]
+ public void SortDirectionDefaultsToAscending()
+ {
+ var grid = new WebGrid(GetContext()).Bind(new object[0]);
+ Assert.Equal(SortDirection.Ascending, grid.SortDirection);
+ }
+
+ [Fact]
+ public void SortDirectionResetWhenQueryStringValueIsInvalid()
+ {
+ NameValueCollection queryString = new NameValueCollection();
+ queryString["sortdir"] = "NotASortDir";
+ var grid = new WebGrid(GetContext(queryString)).Bind(new object[0]);
+ Assert.Equal(SortDirection.Ascending, grid.SortDirection);
+ }
+
+ [Fact]
+ public void SortDirectionUsesQueryStringOfAsc()
+ {
+ NameValueCollection queryString = new NameValueCollection();
+ queryString["sortdir"] = "aSc";
+ var grid = new WebGrid(GetContext(queryString)).Bind(new object[0]);
+ Assert.Equal(SortDirection.Ascending, grid.SortDirection);
+ }
+
+ [Fact]
+ public void SortDirectionUsesQueryStringOfAscending()
+ {
+ NameValueCollection queryString = new NameValueCollection();
+ queryString["sortdir"] = "AScendING";
+ var grid = new WebGrid(GetContext(queryString)).Bind(new object[0]);
+ Assert.Equal(SortDirection.Ascending, grid.SortDirection);
+ }
+
+ [Fact]
+ public void SortDirectionUsesQueryStringOfDesc()
+ {
+ NameValueCollection queryString = new NameValueCollection();
+ queryString["sortdir"] = "DeSc";
+ var grid = new WebGrid(GetContext(queryString)).Bind(new object[0]);
+ Assert.Equal(SortDirection.Descending, grid.SortDirection);
+ }
+
+ [Fact]
+ public void SortDirectionUsesQueryStringOfDescending()
+ {
+ NameValueCollection queryString = new NameValueCollection();
+ queryString["g_sd"] = "DeScendING";
+ var grid = new WebGrid(GetContext(queryString), fieldNamePrefix: "g_", sortDirectionFieldName: "sd").Bind(new object[0]);
+ Assert.Equal(SortDirection.Descending, grid.SortDirection);
+ }
+
+ [Fact]
+ public void SortDisabledIfSortIsEmpty()
+ {
+ var grid = new WebGrid(GetContext(), defaultSort: String.Empty).Bind(Dynamics(
+ new { FirstName = "Joe", LastName = "Smith" },
+ new { FirstName = "Bob", LastName = "Johnson" },
+ new { FirstName = "Sam", LastName = "Jones" },
+ new { FirstName = "Tom", LastName = "Anderson" }
+ ));
+ Assert.Equal("Joe", grid.Rows[0]["FirstName"]);
+ Assert.Equal("Bob", grid.Rows[1]["FirstName"]);
+ Assert.Equal("Sam", grid.Rows[2]["FirstName"]);
+ Assert.Equal("Tom", grid.Rows[3]["FirstName"]);
+ }
+
+ [Fact]
+ public void SortDisabledIfSortIsNull()
+ {
+ var grid = new WebGrid(GetContext(), defaultSort: null).Bind(Dynamics(
+ new { FirstName = "Joe", LastName = "Smith" },
+ new { FirstName = "Bob", LastName = "Johnson" },
+ new { FirstName = "Sam", LastName = "Jones" },
+ new { FirstName = "Tom", LastName = "Anderson" }
+ ));
+ Assert.Equal("Joe", grid.Rows[0]["FirstName"]);
+ Assert.Equal("Bob", grid.Rows[1]["FirstName"]);
+ Assert.Equal("Sam", grid.Rows[2]["FirstName"]);
+ Assert.Equal("Tom", grid.Rows[3]["FirstName"]);
+ }
+
+ [Fact]
+ public void SortForDynamics()
+ {
+ var grid = new WebGrid(GetContext(), defaultSort: "FirstName").Bind(Dynamics(
+ new { FirstName = "Joe", LastName = "Smith" },
+ new { FirstName = "Bob", LastName = "Johnson" },
+ new { FirstName = "Sam", LastName = "Jones" },
+ new { FirstName = "Tom", LastName = "Anderson" }
+ ));
+ Assert.Equal("Bob", grid.Rows[0]["FirstName"]);
+ Assert.Equal("Joe", grid.Rows[1]["FirstName"]);
+ Assert.Equal("Sam", grid.Rows[2]["FirstName"]);
+ Assert.Equal("Tom", grid.Rows[3]["FirstName"]);
+ }
+
+ [Fact]
+ public void SortForDynamicsDescending()
+ {
+ NameValueCollection queryString = new NameValueCollection();
+ queryString["sort"] = "LastName";
+ queryString["sortdir"] = "DESCENDING";
+ var grid = new WebGrid(GetContext(queryString), defaultSort: "FirstName").Bind(Dynamics(
+ new { FirstName = "Joe", LastName = "Smith" },
+ new { FirstName = "Bob", LastName = "Johnson" },
+ new { FirstName = "Sam", LastName = "Jones" },
+ new { FirstName = "Tom", LastName = "Anderson" }
+ ));
+ Assert.Equal("Smith", grid.Rows[0]["LastName"]);
+ Assert.Equal("Jones", grid.Rows[1]["LastName"]);
+ Assert.Equal("Johnson", grid.Rows[2]["LastName"]);
+ Assert.Equal("Anderson", grid.Rows[3]["LastName"]);
+ }
+
+ [Fact]
+ public void SortForNonDynamicNavigationColumn()
+ {
+ NameValueCollection queryString = new NameValueCollection();
+ queryString["sort"] = "Not.A.Column";
+ var grid = new WebGrid(GetContext(queryString), defaultSort: "Person.FirstName").Bind(new[]
+ {
+ new { Person = new { FirstName = "Joe", LastName = "Smith" } },
+ new { Person = new { FirstName = "Bob", LastName = "Johnson" } },
+ new { Person = new { FirstName = "Sam", LastName = "Jones" } },
+ new { Person = new { FirstName = "Tom", LastName = "Anderson" } }
+ });
+ Assert.Equal("Not.A.Column", grid.SortColumn); // navigation columns are validated during sort
+ Assert.Equal("Bob", grid.Rows[0]["Person.FirstName"]);
+ Assert.Equal("Joe", grid.Rows[1]["Person.FirstName"]);
+ Assert.Equal("Sam", grid.Rows[2]["Person.FirstName"]);
+ Assert.Equal("Tom", grid.Rows[3]["Person.FirstName"]);
+ }
+
+ [Fact]
+ public void SortForNonDynamics()
+ {
+ var grid = new WebGrid(GetContext(), defaultSort: "FirstName").Bind(new[]
+ {
+ new { FirstName = "Joe", LastName = "Smith" },
+ new { FirstName = "Bob", LastName = "Johnson" },
+ new { FirstName = "Sam", LastName = "Jones" },
+ new { FirstName = "Tom", LastName = "Anderson" }
+ });
+ Assert.Equal("Bob", grid.Rows[0]["FirstName"]);
+ Assert.Equal("Joe", grid.Rows[1]["FirstName"]);
+ Assert.Equal("Sam", grid.Rows[2]["FirstName"]);
+ Assert.Equal("Tom", grid.Rows[3]["FirstName"]);
+ }
+
+ [Fact]
+ public void SortForNonDynamicsDescending()
+ {
+ NameValueCollection queryString = new NameValueCollection();
+ queryString["sort"] = "LastName";
+ queryString["sortdir"] = "DESCENDING";
+ var grid = new WebGrid(GetContext(queryString), defaultSort: "FirstName").Bind(new[]
+ {
+ new { FirstName = "Joe", LastName = "Smith" },
+ new { FirstName = "Bob", LastName = "Johnson" },
+ new { FirstName = "Sam", LastName = "Jones" },
+ new { FirstName = "Tom", LastName = "Anderson" }
+ });
+ Assert.Equal("Smith", grid.Rows[0]["LastName"]);
+ Assert.Equal("Jones", grid.Rows[1]["LastName"]);
+ Assert.Equal("Johnson", grid.Rows[2]["LastName"]);
+ Assert.Equal("Anderson", grid.Rows[3]["LastName"]);
+ }
+
+ [Fact]
+ public void SortForNonDynamicsEnumerable()
+ {
+ var grid = new WebGrid(GetContext(), defaultSort: "FirstName").Bind(new[]
+ {
+ new { FirstName = "Joe", LastName = "Smith" },
+ new { FirstName = "Bob", LastName = "Johnson" },
+ new { FirstName = "Sam", LastName = "Jones" },
+ new { FirstName = "Tom", LastName = "Anderson" }
+ }.ToList());
+ Assert.Equal("Bob", grid.Rows[0]["FirstName"]);
+ Assert.Equal("Joe", grid.Rows[1]["FirstName"]);
+ Assert.Equal("Sam", grid.Rows[2]["FirstName"]);
+ Assert.Equal("Tom", grid.Rows[3]["FirstName"]);
+ }
+
+ [Fact]
+ public void SortForNonDynamicsEnumerableDescending()
+ {
+ NameValueCollection queryString = new NameValueCollection();
+ queryString["sort"] = "LastName";
+ queryString["sortdir"] = "DESCENDING";
+ var grid = new WebGrid(GetContext(queryString), defaultSort: "FirstName").Bind(new[]
+ {
+ new { FirstName = "Joe", LastName = "Smith" },
+ new { FirstName = "Bob", LastName = "Johnson" },
+ new { FirstName = "Sam", LastName = "Jones" },
+ new { FirstName = "Tom", LastName = "Anderson" }
+ }.ToList());
+ Assert.Equal("Smith", grid.Rows[0]["LastName"]);
+ Assert.Equal("Jones", grid.Rows[1]["LastName"]);
+ Assert.Equal("Johnson", grid.Rows[2]["LastName"]);
+ Assert.Equal("Anderson", grid.Rows[3]["LastName"]);
+ }
+
+ [Fact]
+ public void SortForNonGenericEnumerable()
+ {
+ var grid = new WebGrid(GetContext(), defaultSort: "FirstName").Bind(new NonGenericEnumerable(new[]
+ {
+ new Person { FirstName = "Joe", LastName = "Smith" },
+ new Person { FirstName = "Bob", LastName = "Johnson" },
+ new Person { FirstName = "Sam", LastName = "Jones" },
+ new Person { FirstName = "Tom", LastName = "Anderson" }
+ }));
+ Assert.Equal("Bob", grid.Rows[0]["FirstName"]);
+ Assert.Equal("Joe", grid.Rows[1]["FirstName"]);
+ Assert.Equal("Sam", grid.Rows[2]["FirstName"]);
+ Assert.Equal("Tom", grid.Rows[3]["FirstName"]);
+ }
+
+ [Fact]
+ public void SortForNonGenericEnumerableDescending()
+ {
+ NameValueCollection queryString = new NameValueCollection();
+ queryString["sort"] = "LastName";
+ queryString["sortdir"] = "DESCENDING";
+ var grid = new WebGrid(GetContext(queryString), defaultSort: "FirstName").Bind(new NonGenericEnumerable(new[]
+ {
+ new Person { FirstName = "Joe", LastName = "Smith" },
+ new Person { FirstName = "Bob", LastName = "Johnson" },
+ new Person { FirstName = "Sam", LastName = "Jones" },
+ new Person { FirstName = "Tom", LastName = "Anderson" }
+ }));
+ Assert.Equal("Smith", grid.Rows[0]["LastName"]);
+ Assert.Equal("Jones", grid.Rows[1]["LastName"]);
+ Assert.Equal("Johnson", grid.Rows[2]["LastName"]);
+ Assert.Equal("Anderson", grid.Rows[3]["LastName"]);
+ }
+
+ [Fact]
+ public void SortUrlDefaults()
+ {
+ var grid = new WebGrid(GetContext()).Bind(new[]
+ {
+ new { FirstName = "Bob" }
+ });
+ string html = grid.GetSortUrl("FirstName");
+ Assert.Equal("?sort=FirstName&sortdir=ASC", html.ToString());
+ }
+
+ [Fact]
+ public void SortUrlThrowsIfColumnNameIsEmpty()
+ {
+ var grid = new WebGrid(GetContext()).Bind(new[]
+ {
+ new { }, new { }
+ });
+ Assert.ThrowsArgumentNullOrEmptyString(() => { grid.GetSortUrl(String.Empty); }, "column");
+ }
+
+ [Fact]
+ public void SortUrlThrowsIfColumnNameIsNull()
+ {
+ var grid = new WebGrid(GetContext()).Bind(new[]
+ {
+ new { }, new { }
+ });
+ Assert.ThrowsArgumentNullOrEmptyString(() => { grid.GetSortUrl(null); }, "column");
+ }
+
+ [Fact]
+ public void SortUrlThrowsIfSortingIsDisabled()
+ {
+ var grid = new WebGrid(GetContext(), canSort: false).Bind(new[]
+ {
+ new { P1 = 1 }, new { P1 = 2 }
+ });
+ Assert.Throws<NotSupportedException>(() => { grid.GetSortUrl("P1"); }, "This operation is not supported when sorting is disabled for the \"WebGrid\" object.");
+ }
+
+ [Fact]
+ public void SortWhenSortIsDisabled()
+ {
+ var grid = new WebGrid(GetContext(), defaultSort: "FirstName", canSort: false).Bind(new[]
+ {
+ new { FirstName = "Joe", LastName = "Smith" },
+ new { FirstName = "Bob", LastName = "Johnson" },
+ new { FirstName = "Sam", LastName = "Jones" },
+ new { FirstName = "Tom", LastName = "Anderson" }
+ });
+ Assert.Equal("Joe", grid.Rows[0]["FirstName"]);
+ Assert.Equal("Bob", grid.Rows[1]["FirstName"]);
+ Assert.Equal("Sam", grid.Rows[2]["FirstName"]);
+ Assert.Equal("Tom", grid.Rows[3]["FirstName"]);
+ }
+
+ [Fact]
+ public void SortWithNullValues()
+ {
+ var data = new[]
+ {
+ new { FirstName = (object)"Joe", LastName = "Smith" },
+ new { FirstName = (object)"Bob", LastName = "Johnson" },
+ new { FirstName = (object)null, LastName = "Jones" }
+ };
+ var grid = new WebGrid(GetContext(), defaultSort: "FirstName").Bind(data);
+
+ Assert.Equal("Jones", grid.Rows[0]["LastName"]);
+ Assert.Equal("Bob", grid.Rows[1]["FirstName"]);
+ Assert.Equal("Joe", grid.Rows[2]["FirstName"]);
+
+ grid = new WebGrid(GetContext(), defaultSort: "FirstName desc").Bind(data);
+
+ Assert.Equal("Joe", grid.Rows[0]["FirstName"]);
+ Assert.Equal("Bob", grid.Rows[1]["FirstName"]);
+ Assert.Equal("Jones", grid.Rows[2]["LastName"]);
+ }
+
+ [Fact]
+ public void SortWithMultipleNullValues()
+ {
+ var data = new[]
+ {
+ new { FirstName = (object)"Joe", LastName = "Smith" },
+ new { FirstName = (object)"Bob", LastName = "Johnson" },
+ new { FirstName = (object)null, LastName = "Hughes" },
+ new { FirstName = (object)null, LastName = "Jones" }
+ };
+ var grid = new WebGrid(GetContext(), defaultSort: "FirstName").Bind(data);
+
+ Assert.Equal("Hughes", grid.Rows[0]["LastName"]);
+ Assert.Equal("Jones", grid.Rows[1]["LastName"]);
+ Assert.Equal("Bob", grid.Rows[2]["FirstName"]);
+ Assert.Equal("Joe", grid.Rows[3]["FirstName"]);
+
+ grid = new WebGrid(GetContext(), defaultSort: "FirstName desc").Bind(data);
+
+ Assert.Equal("Joe", grid.Rows[0]["FirstName"]);
+ Assert.Equal("Bob", grid.Rows[1]["FirstName"]);
+ Assert.Equal("Hughes", grid.Rows[2]["LastName"]);
+ Assert.Equal("Jones", grid.Rows[3]["LastName"]);
+ }
+
+ [Fact]
+ public void SortWithMixedValuesDoesNotThrow()
+ {
+ var data = new[]
+ {
+ new { FirstName = (object)1, LastName = "Smith" },
+ new { FirstName = (object)"Bob", LastName = "Johnson" },
+ new { FirstName = (object)DBNull.Value, LastName = "Jones" }
+ };
+ var grid = new WebGrid(GetContext(), defaultSort: "FirstName").Bind(data);
+
+ Assert.NotNull(grid.Rows);
+
+ Assert.Equal("Smith", grid.Rows[0]["LastName"]);
+ Assert.Equal("Johnson", grid.Rows[1]["LastName"]);
+ Assert.Equal("Jones", grid.Rows[2]["LastName"]);
+ }
+
+ [Fact]
+ public void SortWithUnsortableDoesNotThrow()
+ {
+ var object1 = new object();
+ var object2 = new object();
+ var data = new[]
+ {
+ new { Value = object1 },
+ new { Value = object2 }
+ };
+ var grid = new WebGrid(GetContext(), defaultSort: "Value").Bind(data);
+
+ Assert.NotNull(grid.Rows);
+
+ Assert.Equal(object1, grid.Rows[0]["Value"]);
+ Assert.Equal(object2, grid.Rows[1]["Value"]);
+ }
+
+ [Fact]
+ public void TableRenderingWithColumnTemplates()
+ {
+ var grid = new WebGrid(GetContext(), rowsPerPage: 3).Bind(new[]
+ {
+ new { P1 = 1, P2 = '2', P3 = "3" }
+ });
+ var html = grid.Table(displayHeader: false,
+ columns: new[]
+ {
+ grid.Column("P1", format: item => { return "<span>P1: " + item.P1 + "</span>"; }),
+ grid.Column("P2", format: item => { return new HtmlString("<span>P2: " + item.P2 + "</span>"); }),
+ grid.Column("P3", format: item => { return new HelperResult(tw => { tw.Write("<span>P3: " + item.P3 + "</span>"); }); })
+ });
+ UnitTestHelper.AssertEqualsIgnoreWhitespace(
+ "<table><tbody><tr>" +
+ "<td>&lt;span&gt;P1: 1&lt;/span&gt;</td>" +
+ "<td><span>P2: 2</span></td>" +
+ "<td><span>P3: 3</span></td>" +
+ "</tr></tbody></table>", html.ToString());
+ XhtmlAssert.Validate1_1(html);
+ }
+
+ [Fact]
+ public void TableRenderingWithDefaultCellValueOfCustom()
+ {
+ var grid = new WebGrid(GetContext(), rowsPerPage: 3).Bind(new[]
+ {
+ new { P1 = String.Empty, P2 = (string)null },
+ });
+ var html = grid.Table(fillEmptyRows: true, emptyRowCellValue: "N/A", displayHeader: false);
+ UnitTestHelper.AssertEqualsIgnoreWhitespace(
+ "<table><tbody>" +
+ "<tr><td></td><td></td></tr>" +
+ "<tr><td>N/A</td><td>N/A</td></tr>" +
+ "<tr><td>N/A</td><td>N/A</td></tr>" +
+ "</tbody></table>", html.ToString());
+ XhtmlAssert.Validate1_1(html);
+ }
+
+ [Fact]
+ public void TableRenderingWithDefaultCellValueOfEmpty()
+ {
+ var grid = new WebGrid(GetContext(), rowsPerPage: 3).Bind(new[]
+ {
+ new { P1 = String.Empty, P2 = (string)null }
+ });
+ var html = grid.Table(fillEmptyRows: true, emptyRowCellValue: "", displayHeader: false);
+ UnitTestHelper.AssertEqualsIgnoreWhitespace(
+ "<table><tbody>" +
+ "<tr><td></td><td></td></tr>" +
+ "<tr><td></td><td></td></tr>" +
+ "<tr><td></td><td></td></tr>" +
+ "</tbody></table>", html.ToString());
+ XhtmlAssert.Validate1_1(html);
+ }
+
+ [Fact]
+ public void TableRenderingWithDefaultCellValueOfNbsp()
+ {
+ var grid = new WebGrid(GetContext(), rowsPerPage: 3).Bind(new[]
+ {
+ new { P1 = String.Empty, P2 = (string)null }
+ });
+ var html = grid.Table(fillEmptyRows: true, displayHeader: false);
+ UnitTestHelper.AssertEqualsIgnoreWhitespace(
+ "<table><tbody>" +
+ "<tr><td></td><td></td></tr>" +
+ "<tr><td>&nbsp;</td><td>&nbsp;</td></tr>" +
+ "<tr><td>&nbsp;</td><td>&nbsp;</td></tr>" +
+ "</tbody></table>", html.ToString());
+ XhtmlAssert.Validate1_1(html);
+ }
+
+ [Fact]
+ public void TableRenderingWithExclusions()
+ {
+ var grid = new WebGrid(GetContext()).Bind(new[]
+ {
+ new { P1 = 1, P2 = '2', P3 = "3" }
+ });
+ var html = grid.Table(exclusions: new string[] { "P2" });
+ UnitTestHelper.AssertEqualsIgnoreWhitespace(
+ "<table><thead><tr>" +
+ "<th scope=\"col\"><a href=\"?sort=P1&amp;sortdir=ASC\">P1</a></th>" +
+ "<th scope=\"col\"><a href=\"?sort=P3&amp;sortdir=ASC\">P3</a></th>" +
+ "</tr></thead>" +
+ "<tbody>" +
+ "<tr><td>1</td><td>3</td></tr>" +
+ "</tbody></table>", html.ToString());
+ XhtmlAssert.Validate1_1(html);
+ }
+
+ [Fact]
+ public void TableRenderingWithNoStylesAndFillEmptyRows()
+ {
+ var grid = new WebGrid(GetContext(), rowsPerPage: 3).Bind(new[]
+ {
+ new { FirstName = "Joe", LastName = "Smith" }
+ });
+ var html = grid.Table(fillEmptyRows: true);
+ UnitTestHelper.AssertEqualsIgnoreWhitespace(
+ "<table><thead><tr>" +
+ "<th scope=\"col\"><a href=\"?sort=FirstName&amp;sortdir=ASC\">FirstName</a></th>" +
+ "<th scope=\"col\"><a href=\"?sort=LastName&amp;sortdir=ASC\">LastName</a></th>" +
+ "</tr></thead>" +
+ "<tbody>" +
+ "<tr><td>Joe</td><td>Smith</td></tr>" +
+ "<tr><td>&nbsp;</td><td>&nbsp;</td></tr>" +
+ "<tr><td>&nbsp;</td><td>&nbsp;</td></tr>" +
+ "</tbody></table>", html.ToString());
+ XhtmlAssert.Validate1_1(html);
+ }
+
+ [Fact]
+ public void TableRenderingWithSortingDisabled()
+ {
+ var grid = new WebGrid(GetContext(), canSort: false).Bind(new[]
+ {
+ new { FirstName = "Joe", LastName = "Smith" }
+ });
+ var html = grid.Table();
+ UnitTestHelper.AssertEqualsIgnoreWhitespace(
+ "<table><thead><tr>" +
+ "<th scope=\"col\">FirstName</th>" +
+ "<th scope=\"col\">LastName</th>" +
+ "</tr></thead>" +
+ "<tbody>" +
+ "<tr><td>Joe</td><td>Smith</td></tr>" +
+ "</tbody></table>", html.ToString());
+ XhtmlAssert.Validate1_1(html);
+ }
+
+ [Fact]
+ public void TableRenderingWithAttributes()
+ {
+ var grid = new WebGrid(GetContext()).Bind(new[]
+ {
+ new { FirstName = "Joe", LastName = "Smith" }
+ });
+ var html = grid.Table(htmlAttributes: new { id = "my-table-id", summary = "Table summary" });
+ UnitTestHelper.AssertEqualsIgnoreWhitespace(
+ "<table id=\"my-table-id\" summary=\"Table summary\"><thead><tr>" +
+ "<th scope=\"col\"><a href=\"?sort=FirstName&amp;sortdir=ASC\">FirstName</a></th>" +
+ "<th scope=\"col\"><a href=\"?sort=LastName&amp;sortdir=ASC\">LastName</a></th>" +
+ "</tr></thead>" +
+ "<tbody>" +
+ "<tr><td>Joe</td><td>Smith</td></tr>" +
+ "</tbody></table>", html.ToString());
+ XhtmlAssert.Validate1_1(html);
+ }
+
+ [Fact]
+ public void TableRenderingEncodesAttributes()
+ {
+ var grid = new WebGrid(GetContext()).Bind(new[]
+ {
+ new { FirstName = "Joe", LastName = "Smith" }
+ });
+ var html = grid.Table(htmlAttributes: new { summary = "\"<Table summary" });
+ UnitTestHelper.AssertEqualsIgnoreWhitespace(
+ "<table summary=\"&quot;&lt;Table summary\"><thead><tr>" +
+ "<th scope=\"col\"><a href=\"?sort=FirstName&amp;sortdir=ASC\">FirstName</a></th>" +
+ "<th scope=\"col\"><a href=\"?sort=LastName&amp;sortdir=ASC\">LastName</a></th>" +
+ "</tr></thead>" +
+ "<tbody>" +
+ "<tr><td>Joe</td><td>Smith</td></tr>" +
+ "</tbody></table>", html.ToString());
+ XhtmlAssert.Validate1_1(html);
+ }
+
+ [Fact]
+ public void TableRenderingIsNotAffectedWhenAttributesIsNull()
+ {
+ var grid = new WebGrid(GetContext()).Bind(new[]
+ {
+ new { FirstName = "Joe", LastName = "Smith" }
+ });
+ var html = grid.Table(htmlAttributes: null);
+ UnitTestHelper.AssertEqualsIgnoreWhitespace(
+ "<table><thead><tr>" +
+ "<th scope=\"col\"><a href=\"?sort=FirstName&amp;sortdir=ASC\">FirstName</a></th>" +
+ "<th scope=\"col\"><a href=\"?sort=LastName&amp;sortdir=ASC\">LastName</a></th>" +
+ "</tr></thead>" +
+ "<tbody>" +
+ "<tr><td>Joe</td><td>Smith</td></tr>" +
+ "</tbody></table>", html.ToString());
+ XhtmlAssert.Validate1_1(html);
+ }
+
+ [Fact]
+ public void TableRenderingIsNotAffectedWhenAttributesIsEmpty()
+ {
+ var grid = new WebGrid(GetContext()).Bind(new[]
+ {
+ new { FirstName = "Joe", LastName = "Smith" }
+ });
+ var html = grid.Table(htmlAttributes: new { });
+ UnitTestHelper.AssertEqualsIgnoreWhitespace(
+ "<table><thead><tr>" +
+ "<th scope=\"col\"><a href=\"?sort=FirstName&amp;sortdir=ASC\">FirstName</a></th>" +
+ "<th scope=\"col\"><a href=\"?sort=LastName&amp;sortdir=ASC\">LastName</a></th>" +
+ "</tr></thead>" +
+ "<tbody>" +
+ "<tr><td>Joe</td><td>Smith</td></tr>" +
+ "</tbody></table>", html.ToString());
+ XhtmlAssert.Validate1_1(html);
+ }
+
+ [Fact]
+ public void TableRenderingWithStyles()
+ {
+ NameValueCollection queryString = new NameValueCollection();
+ queryString["row"] = "1";
+ var grid = new WebGrid(GetContext(queryString), rowsPerPage: 4).Bind(new[]
+ {
+ new { FirstName = "Joe", LastName = "Smith" },
+ new { FirstName = "Bob", LastName = "Johnson" }
+ });
+ var html = grid.Table(tableStyle: "tbl", headerStyle: "hdr", footerStyle: "ftr",
+ rowStyle: "row", alternatingRowStyle: "arow", selectedRowStyle: "sel", fillEmptyRows: true,
+ footer: item => "footer text",
+ columns: new[]
+ {
+ grid.Column("firstName", style: "c1", canSort: false),
+ grid.Column("lastName", style: "c2", canSort: false)
+ });
+ UnitTestHelper.AssertEqualsIgnoreWhitespace(
+ "<table class=\"tbl\"><thead><tr class=\"hdr\">" +
+ "<th scope=\"col\">firstName</th><th scope=\"col\">lastName</th>" +
+ "</tr></thead>" +
+ "<tfoot>" +
+ "<tr class=\"ftr\"><td colspan=\"2\">footer text</td></tr>" +
+ "</tfoot>" +
+ "<tbody>" +
+ "<tr class=\"row sel\"><td class=\"c1\">Joe</td><td class=\"c2\">Smith</td></tr>" +
+ "<tr class=\"arow\"><td class=\"c1\">Bob</td><td class=\"c2\">Johnson</td></tr>" +
+ "<tr class=\"row\"><td class=\"c1\">&nbsp;</td><td class=\"c2\">&nbsp;</td></tr>" +
+ "<tr class=\"arow\"><td class=\"c1\">&nbsp;</td><td class=\"c2\">&nbsp;</td></tr>" +
+ "</tbody></table>", html.ToString());
+ XhtmlAssert.Validate1_1(html);
+ }
+
+ [Fact]
+ public void TableWithAjax()
+ {
+ var grid = new WebGrid(GetContext(), ajaxUpdateContainerId: "grid").Bind(new[]
+ {
+ new { First = "First", Second = "Second" }
+ });
+ string html = grid.Table().ToString();
+ Assert.True(html.Contains("<script"));
+ Assert.True(html.Contains("swhgajax=\"true\""));
+ }
+
+ [Fact]
+ public void TableWithAjaxAndCallback()
+ {
+ var grid = new WebGrid(GetContext(), ajaxUpdateContainerId: "grid", ajaxUpdateCallback: "myCallback").Bind(new[]
+ {
+ new { First = "First", Second = "Second" }
+ });
+ string html = grid.Table().ToString();
+ Assert.True(html.Contains("<script"));
+ Assert.True(html.Contains("myCallback"));
+ }
+
+ [Fact]
+ public void WebGridEncodesAjaxDataStrings()
+ {
+ var grid = new WebGrid(GetContext(), ajaxUpdateContainerId: "'grid'", ajaxUpdateCallback: "'myCallback'").Bind(new[]
+ {
+ new { First = "First", Second = "Second" }
+ });
+ string html = grid.Table().ToString();
+ Assert.True(html.Contains(@"&#39;grid&#39;"));
+ Assert.True(html.Contains(@"&#39;myCallback&#39;"));
+ }
+
+ [Fact]
+ public void WebGridThrowsIfOperationsArePerformedBeforeBinding()
+ {
+ // Arrange
+ string errorMessage = "A data source must be bound before this operation can be performed.";
+ var grid = new WebGrid(GetContext());
+
+ // Act and Assert
+ Assert.Throws<InvalidOperationException>(() => { var rows = grid.Rows; }, errorMessage);
+ Assert.Throws<InvalidOperationException>(() => { int count = grid.TotalRowCount; }, errorMessage);
+ Assert.Throws<InvalidOperationException>(() => grid.GetHtml().ToString(), errorMessage);
+ Assert.Throws<InvalidOperationException>(() => grid.Pager().ToString(), errorMessage);
+ Assert.Throws<InvalidOperationException>(() => grid.Table().ToString(), errorMessage);
+ Assert.Throws<InvalidOperationException>(() =>
+ {
+ grid.SelectedIndex = 1;
+ var row = grid.SelectedRow;
+ }, errorMessage);
+ }
+
+ [Fact]
+ public void WebGridThrowsIfBindingIsPerformedWhenAlreadyBound()
+ {
+ // Arrange
+ var grid = new WebGrid(GetContext());
+ var values = Enumerable.Range(0, 10).Cast<dynamic>();
+
+ // Act
+ grid.Bind(values);
+
+ // Assert
+ Assert.Throws<InvalidOperationException>(() => grid.Bind(values), "The WebGrid instance is already bound to a data source.");
+ }
+
+ [Fact]
+ public void GetElementTypeReturnsDynamicTypeIfElementIsDynamic()
+ {
+ // Arrange
+ IEnumerable<dynamic> elements = Dynamics(new[] { new Person { FirstName = "Foo", LastName = "Bar" } });
+
+ // Act
+ Type type = WebGrid.GetElementType(elements);
+
+ // Assert
+ Assert.Equal(typeof(IDynamicMetaObjectProvider), type);
+ }
+
+ [Fact]
+ public void GetElementTypeReturnsEnumerableTypeIfFirstInstanceIsNotDynamic()
+ {
+ // Arrange
+ IEnumerable<dynamic> elements = Iterator();
+
+ // Act
+ Type type = WebGrid.GetElementType(elements);
+
+ // Assert
+ Assert.Equal(typeof(Person), type);
+ }
+
+ [Fact]
+ public void TableThrowsIfQueryStringDerivedSortColumnIsExcluded()
+ {
+ // Arrange
+ NameValueCollection collection = new NameValueCollection();
+ collection["sort"] = "Salary";
+ var context = GetContext(collection);
+ IList<Employee> employees = new List<Employee>();
+ employees.Add(new Employee { Name = "A", Salary = 5, Manager = new Employee { Name = "-" } });
+ employees.Add(new Employee { Name = "B", Salary = 20, Manager = employees[0] });
+ employees.Add(new Employee { Name = "C", Salary = 15, Manager = employees[0] });
+ employees.Add(new Employee { Name = "D", Salary = 5, Manager = employees[1] });
+
+ var grid = new WebGrid(context, defaultSort: "Name").Bind(employees);
+
+ // Act and Assert
+ Assert.Throws<InvalidOperationException>(() => grid.GetHtml(exclusions: new[] { "Salary" }), "Column \"Salary\" does not exist.");
+ }
+
+ [Fact]
+ public void TableThrowsIfQueryStringDerivedSortColumnDoesNotExistInColumnsArgument()
+ {
+ // Arrange
+ NameValueCollection collection = new NameValueCollection();
+ collection["sort"] = "Salary";
+ var context = GetContext(collection);
+ IList<Employee> employees = new List<Employee>();
+ employees.Add(new Employee { Name = "A", Salary = 5, Manager = new Employee { Name = "-" } });
+ employees.Add(new Employee { Name = "B", Salary = 20, Manager = employees[0] });
+ employees.Add(new Employee { Name = "C", Salary = 15, Manager = employees[0] });
+ employees.Add(new Employee { Name = "D", Salary = 5, Manager = employees[1] });
+
+ var grid = new WebGrid(context, canSort: true, defaultSort: "Name").Bind(employees);
+
+ // Act and Assert
+ Assert.Throws<InvalidOperationException>(
+ () => grid.Table(columns: new[] { new WebGridColumn { ColumnName = "Name" }, new WebGridColumn { ColumnName = "Manager.Name" } }),
+ "Column \"Salary\" does not exist.");
+ }
+
+ [Fact]
+ public void TableDoesNotThrowIfQueryStringDerivedSortColumnIsVisibleButNotSortable()
+ {
+ // Arrange
+ NameValueCollection collection = new NameValueCollection();
+ collection["sort"] = "Salary";
+ collection["sortDir"] = "Desc";
+ var context = GetContext(collection);
+ IList<Employee> employees = new List<Employee>();
+ employees.Add(new Employee { Name = "A", Salary = 5, Manager = new Employee { Name = "-" } });
+ employees.Add(new Employee { Name = "B", Salary = 20, Manager = employees[0] });
+ employees.Add(new Employee { Name = "C", Salary = 15, Manager = employees[0] });
+ employees.Add(new Employee { Name = "D", Salary = 10, Manager = employees[1] });
+
+ var grid = new WebGrid(context, canSort: true).Bind(employees);
+
+ // Act
+ var html = grid.Table(columns: new[] { new WebGridColumn { ColumnName = "Salary", CanSort = false } });
+
+ // Assert
+ Assert.NotNull(html);
+ Assert.Equal(grid.Rows[0]["Salary"], 20);
+ Assert.Equal(grid.Rows[1]["Salary"], 15);
+ Assert.Equal(grid.Rows[2]["Salary"], 10);
+ Assert.Equal(grid.Rows[3]["Salary"], 5);
+ }
+
+ [Fact]
+ public void TableThrowsIfComplexPropertyIsUnsortable()
+ {
+ // Arrange
+ NameValueCollection collection = new NameValueCollection();
+ collection["sort"] = "Manager.Salary";
+ var context = GetContext(collection);
+ IList<Employee> employees = new List<Employee>();
+ employees.Add(new Employee { Name = "A", Salary = 5, Manager = new Employee { Name = "-" } });
+ employees.Add(new Employee { Name = "B", Salary = 20, Manager = employees[0] });
+ employees.Add(new Employee { Name = "C", Salary = 15, Manager = employees[0] });
+ employees.Add(new Employee { Name = "D", Salary = 5, Manager = employees[1] });
+ var grid = new WebGrid(context).Bind(employees, columnNames: new[] { "Name", "Manager.Name" });
+
+ // Act and Assert
+ Assert.Throws<InvalidOperationException>(() => grid.GetHtml(),
+ "Column \"Manager.Salary\" does not exist.");
+ }
+
+ [Fact]
+ public void TableDoesNotThrowIfUnsortableColumnIsExplicitlySpecifiedByUser()
+ {
+ // Arrange
+ var context = GetContext();
+ IList<Employee> employees = new List<Employee>();
+ employees.Add(new Employee { Name = "A", Salary = 5, Manager = new Employee { Name = "-" } });
+ employees.Add(new Employee { Name = "C", Salary = 15, Manager = employees[0] });
+ employees.Add(new Employee { Name = "D", Salary = 10, Manager = employees[1] });
+
+ // Act
+ var grid = new WebGrid(context).Bind(employees, columnNames: new[] { "Name", "Manager.Name" });
+ grid.SortColumn = "Salary";
+ var html = grid.Table();
+
+ // Assert
+ Assert.Equal(grid.Rows[0]["Salary"], 5);
+ Assert.Equal(grid.Rows[1]["Salary"], 10);
+ Assert.Equal(grid.Rows[2]["Salary"], 15);
+
+ UnitTestHelper.AssertEqualsIgnoreWhitespace(
+ "<table><thead><tr>"
+ + "<th scope=\"col\"><a href=\"?sort=Name&amp;sortdir=ASC\">Name</a></th>"
+ + "<th scope=\"col\"><a href=\"?sort=Manager.Name&amp;sortdir=ASC\">Manager.Name</a></th>"
+ + "</tr></thead><tbody>"
+ + "<tr><td>A</td><td>-</td></tr>"
+ + "<tr><td>D</td><td>C</td></tr>"
+ + "<tr><td>C</td><td>A</td></tr>"
+ + "</tbody></table>", html.ToString());
+ }
+
+ [Fact]
+ public void TableDoesNotThrowIfUnsortableColumnIsDefaultSortColumn()
+ {
+ // Arrange
+ var context = GetContext();
+ IList<Employee> employees = new List<Employee>();
+ employees.Add(new Employee { Name = "A", Salary = 5, Manager = new Employee { Name = "-" } });
+ employees.Add(new Employee { Name = "C", Salary = 15, Manager = employees[0] });
+ employees.Add(new Employee { Name = "D", Salary = 10, Manager = employees[1] });
+
+ // Act
+ var grid = new WebGrid(context, defaultSort: "Salary").Bind(employees, columnNames: new[] { "Name", "Manager.Name" });
+ var html = grid.Table();
+
+ // Assert
+ Assert.Equal(grid.Rows[0]["Salary"], 5);
+ Assert.Equal(grid.Rows[1]["Salary"], 10);
+ Assert.Equal(grid.Rows[2]["Salary"], 15);
+
+ UnitTestHelper.AssertEqualsIgnoreWhitespace(
+ "<table><thead><tr>"
+ + "<th scope=\"col\"><a href=\"?sort=Name&amp;sortdir=ASC\">Name</a></th>"
+ + "<th scope=\"col\"><a href=\"?sort=Manager.Name&amp;sortdir=ASC\">Manager.Name</a></th>"
+ + "</tr></thead><tbody>"
+ + "<tr><td>A</td><td>-</td></tr>"
+ + "<tr><td>D</td><td>C</td></tr>"
+ + "<tr><td>C</td><td>A</td></tr>"
+ + "</tbody></table>", html.ToString());
+ }
+
+ private static IEnumerable<Person> Iterator()
+ {
+ yield return new Person { FirstName = "Foo", LastName = "Bar" };
+ }
+
+ [Fact]
+ public void GetElementTypeReturnsEnumerableTypeIfCollectionPassedImplementsEnumerable()
+ {
+ // Arrange
+ IList<Person> listElements = new List<Person> { new Person { FirstName = "Foo", LastName = "Bar" } };
+ HashSet<dynamic> setElements = new HashSet<dynamic> { new DynamicWrapper(new Person { FirstName = "Foo", LastName = "Bar" }) };
+
+ // Act
+ Type listType = WebGrid.GetElementType(listElements);
+ Type setType = WebGrid.GetElementType(setElements);
+
+ // Assert
+ Assert.Equal(typeof(Person), listType);
+ Assert.Equal(typeof(IDynamicMetaObjectProvider), setType);
+ }
+
+ [Fact]
+ public void GetElementTypeReturnsEnumerableTypeIfCollectionImplementsEnumerable()
+ {
+ // Arrange
+ IEnumerable<Person> elements = new NonGenericEnumerable(new[] { new Person { FirstName = "Foo", LastName = "Bar" } });
+ ;
+
+ // Act
+ Type type = WebGrid.GetElementType(elements);
+
+ // Assert
+ Assert.Equal(typeof(Person), type);
+ }
+
+ [Fact]
+ public void GetElementTypeReturnsEnumerableTypeIfCollectionIsIEnumerable()
+ {
+ // Arrange
+ IEnumerable<Person> elements = new GenericEnumerable<Person>(new[] { new Person { FirstName = "Foo", LastName = "Bar" } });
+ ;
+
+ // Act
+ Type type = WebGrid.GetElementType(elements);
+
+ // Assert
+ Assert.Equal(typeof(Person), type);
+ }
+
+ [Fact]
+ public void GetElementTypeDoesNotThrowIfTypeIsNotGeneric()
+ {
+ // Arrange
+ IEnumerable<dynamic> elements = new[] { new Person { FirstName = "Foo", LastName = "Bar" } };
+
+ // Act
+ Type type = WebGrid.GetElementType(elements);
+
+ // Assert
+ Assert.Equal(typeof(Person), type);
+ }
+
+ private static IEnumerable<dynamic> Dynamics(params object[] objects)
+ {
+ return (from o in objects
+ select new DynamicWrapper(o)).ToArray();
+ }
+
+ private static HttpContextBase GetContext(NameValueCollection queryString = null)
+ {
+ Mock<HttpRequestBase> requestMock = new Mock<HttpRequestBase>();
+ requestMock.Setup(request => request.QueryString).Returns(queryString ?? new NameValueCollection());
+
+ Mock<HttpContextBase> contextMock = new Mock<HttpContextBase>();
+ contextMock.Setup(context => context.Request).Returns(requestMock.Object);
+ contextMock.Setup(context => context.Items).Returns(new Hashtable());
+ return contextMock.Object;
+ }
+
+ class Person
+ {
+ public string FirstName { get; set; }
+ public string LastName { get; set; }
+ }
+
+ private class Employee
+ {
+ public string Name { get; set; }
+ public int Salary { get; set; }
+ public Employee Manager { get; set; }
+ }
+
+ class NonGenericEnumerable : IEnumerable<Person>
+ {
+ private IEnumerable<Person> _source;
+
+ public NonGenericEnumerable(IEnumerable<Person> source)
+ {
+ _source = source;
+ }
+
+ public IEnumerator<Person> GetEnumerator()
+ {
+ return _source.GetEnumerator();
+ }
+
+ IEnumerator IEnumerable.GetEnumerator()
+ {
+ return GetEnumerator();
+ }
+ }
+
+ class GenericEnumerable<T> : IEnumerable<T>
+ {
+ private IEnumerable<T> _source;
+
+ public GenericEnumerable(IEnumerable<T> source)
+ {
+ _source = source;
+ }
+
+ public IEnumerator<T> GetEnumerator()
+ {
+ return _source.GetEnumerator();
+ }
+
+ IEnumerator IEnumerable.GetEnumerator()
+ {
+ return GetEnumerator();
+ }
+ }
+ }
+}
diff --git a/test/System.Web.Helpers.Test/WebImageTest.cs b/test/System.Web.Helpers.Test/WebImageTest.cs
new file mode 100644
index 00000000..375be0dd
--- /dev/null
+++ b/test/System.Web.Helpers.Test/WebImageTest.cs
@@ -0,0 +1,1162 @@
+using System.Drawing;
+using System.Drawing.Imaging;
+using System.IO;
+using System.Web.WebPages.TestUtils;
+using Moq;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Helpers.Test
+{
+ public class WebImageTest
+ {
+ private static readonly byte[] _JpgImageBytes = TestFile.Create("LambdaFinal.jpg").ReadAllBytes();
+ private static readonly byte[] _BmpImageBytes = TestFile.Create("logo.bmp").ReadAllBytes();
+ private static readonly byte[] _PngImageBytes = TestFile.Create("NETLogo.png").ReadAllBytes();
+ private static readonly byte[] _HiResImageBytes = TestFile.Create("HiRes.jpg").ReadAllBytes();
+
+ [Fact]
+ public void ConstructorThrowsWhenFilePathIsNull()
+ {
+ Assert.ThrowsArgument(() =>
+ new WebImage(GetContext(), s => new byte[] { }, filePath: null), "filePath", "Value cannot be null or an empty string.");
+ }
+
+ [Fact]
+ public void ConstructorThrowsWhenFilePathIsEmpty()
+ {
+ Assert.ThrowsArgument(() =>
+ new WebImage(GetContext(), s => new byte[] { }, filePath: String.Empty), "filePath", "Value cannot be null or an empty string.");
+ }
+
+ [Fact]
+ public void ConstructorThrowsWhenFilePathIsInvalid()
+ {
+ Assert.Throws<DirectoryNotFoundException>(() =>
+ new WebImage(GetContext(), s => { throw new DirectoryNotFoundException(); }, @"x:\this\does\not\exist.jpg"));
+ }
+
+ [Fact]
+ public void ConstructorThrowsWhenFileContentIsInvalid()
+ {
+ byte[] imageContent = new byte[] { 32, 111, 209, 138, 76, 32 };
+ Assert.ThrowsArgument(() => new WebImage(imageContent), "content",
+ "An image could not be constructed from the content provided.");
+ }
+
+ [Fact]
+ public void FilePathReturnsCorrectPath()
+ {
+ // Arrange
+ string imageName = @"x:\My-test-image.png";
+
+ // Act
+ WebImage image = new WebImage(GetContext(), s => _PngImageBytes, imageName);
+
+ // Assert
+ Assert.Equal(imageName, image.FileName);
+ }
+
+ [Fact]
+ public void FilePathCanBeSet()
+ {
+ // Arrange
+ string originalPath = @"x:\somePath.png";
+ string newPath = @"x:\someOtherPath.jpg";
+
+ // Act
+ WebImage image = new WebImage(GetContext(), s => _PngImageBytes, originalPath);
+ image.FileName = newPath;
+
+ // Assert
+ Assert.Equal(newPath, image.FileName);
+ }
+
+ [Fact]
+ public void SimpleGetBytesClonesArray()
+ {
+ WebImage image = new WebImage(_PngImageBytes);
+
+ byte[] returnedContent = image.GetBytes();
+
+ Assert.False(ReferenceEquals(_PngImageBytes, returnedContent), "GetBytes should clone array.");
+ Assert.Equal(_PngImageBytes, returnedContent);
+ }
+
+ [Fact]
+ public void WebImagePreservesOriginalFormatFromFile()
+ {
+ WebImage image = new WebImage(_PngImageBytes);
+
+ byte[] returnedContent = image.GetBytes();
+
+ // If format was changed; content would be different
+ Assert.Equal(_PngImageBytes, returnedContent);
+ }
+
+ [Fact]
+ public void WebImagePreservesOriginalFormatFromStream()
+ {
+ WebImage image = null;
+ byte[] originalContent = _PngImageBytes;
+ using (MemoryStream stream = new MemoryStream(originalContent))
+ {
+ image = new WebImage(stream);
+ } // dispose stream; WebImage should have no dependency on it
+
+ byte[] returnedContent = image.GetBytes();
+
+ // If format was changed; content would be different
+ Assert.Equal(originalContent, returnedContent);
+ }
+
+ [Fact]
+ public void WebImageCorrectlyReadsFromNoSeekStream()
+ {
+ WebImage image = null;
+
+ byte[] originalContent = _PngImageBytes;
+ using (MemoryStream stream = new MemoryStream(originalContent))
+ {
+ TestStream ts = new TestStream(stream);
+ image = new WebImage(ts);
+ } // dispose stream; WebImage should have no dependency on it
+
+ byte[] returnedContent = image.GetBytes();
+
+ // If chunks are not assembled correctly; content would be different and image would be corrupted.
+ Assert.Equal(originalContent, returnedContent);
+ Assert.Equal("png", image.ImageFormat);
+ }
+
+ [Fact]
+ public void GetBytesWithNullReturnsClonesArray()
+ {
+ byte[] originalContent = _BmpImageBytes;
+ WebImage image = new WebImage(originalContent);
+
+ byte[] returnedContent = image.GetBytes();
+
+ Assert.False(ReferenceEquals(originalContent, returnedContent), "GetBytes with string null should clone array.");
+ Assert.Equal(originalContent, returnedContent);
+ }
+
+ [Fact]
+ public void GetBytesWithSameFormatReturnsSameFormat()
+ {
+ byte[] originalContent = _JpgImageBytes;
+ WebImage image = new WebImage(originalContent);
+
+ byte[] returnedContent = image.GetBytes("jpeg");
+
+ Assert.False(ReferenceEquals(originalContent, returnedContent), "GetBytes with string null should clone array.");
+ Assert.Equal(originalContent, returnedContent);
+ }
+
+ [Fact]
+ public void GetBytesWithDifferentFormatReturnsExpectedFormat()
+ {
+ byte[] originalContent = _BmpImageBytes;
+ WebImage image = new WebImage(originalContent);
+
+ // Request different format
+ byte[] returnedContent = image.GetBytes("jpg");
+
+ Assert.False(ReferenceEquals(originalContent, returnedContent), "GetBytes with string format should clone array.");
+ using (MemoryStream stream = new MemoryStream(returnedContent))
+ {
+ using (Image tempImage = Image.FromStream(stream))
+ {
+ Assert.Equal(ImageFormat.Jpeg, tempImage.RawFormat);
+ }
+ }
+ }
+
+ [Fact]
+ public void GetBytesWithSameFormatReturnsSameFormatWhenCreatedFromFile()
+ {
+ byte[] originalContent = _BmpImageBytes;
+ // Format is not set during construction.
+ WebImage image = new WebImage(_BmpImageBytes);
+
+ byte[] returnedContent = image.GetBytes("bmp");
+
+ Assert.False(ReferenceEquals(originalContent, returnedContent), "GetBytes with string format should clone array.");
+ Assert.Equal(originalContent, returnedContent);
+ }
+
+ [Fact]
+ public void GetBytesWithNoFormatReturnsInitialFormatEvenAfterTransformations()
+ {
+ byte[] originalContent = _BmpImageBytes;
+ // Format is not set during construction.
+ WebImage image = new WebImage(_BmpImageBytes);
+ image.Crop(top: 10, bottom: 10);
+
+ byte[] returnedContent = image.GetBytes();
+
+ Assert.NotEqual(originalContent, returnedContent);
+ using (MemoryStream stream = new MemoryStream(returnedContent))
+ {
+ using (Image tempImage = Image.FromStream(stream))
+ {
+ Assert.Equal(ImageFormat.Bmp, tempImage.RawFormat);
+ }
+ }
+ }
+
+ [Fact]
+ public void GetBytesThrowsOnIncorrectFormat()
+ {
+ WebImage image = new WebImage(_JpgImageBytes);
+ Assert.ThrowsArgument(
+ () => image.GetBytes("bmpx"),
+ "format",
+ "\"bmpx\" is invalid image format. Valid values are image format names like: \"JPEG\", \"BMP\", \"GIF\", \"PNG\", etc.");
+ }
+
+ [Fact]
+ public void GetBytesWithDifferentFormatReturnsExpectedFormatWhenCreatedFromFile()
+ {
+ // Format is not set during construction.
+ WebImage image = new WebImage(_PngImageBytes);
+
+ // Request different format
+ byte[] returnedContent = image.GetBytes("jpg");
+
+ WebImage newImage = new WebImage(returnedContent);
+
+ Assert.Equal("jpeg", newImage.ImageFormat);
+ }
+
+ [Fact]
+ public void GetImageFromRequestReturnsNullForIncorrectMimeType()
+ {
+ // Arrange
+ Mock<HttpPostedFileBase> postedFile = new Mock<HttpPostedFileBase>();
+ postedFile.Setup(c => c.FileName).Returns("index.cshtml");
+ postedFile.Setup(c => c.ContentType).Returns("image/jpg");
+
+ Mock<HttpFileCollectionBase> files = new Mock<HttpFileCollectionBase>();
+ files.Setup(c => c[0]).Returns(postedFile.Object);
+ Mock<HttpRequestBase> request = new Mock<HttpRequestBase>();
+ request.Setup(r => r.Files).Returns(files.Object);
+
+ // Act and Assert
+ Assert.Null(WebImage.GetImageFromRequest(request.Object));
+ }
+
+ [Fact]
+ public void GetImageFromRequestDeterminesMimeTypeFromExtension()
+ {
+ // Arrange
+ Mock<HttpPostedFileBase> postedFile = new Mock<HttpPostedFileBase>();
+ postedFile.Setup(c => c.FileName).Returns("index.jpeg");
+ postedFile.Setup(c => c.ContentType).Returns("application/octet-stream");
+ postedFile.Setup(c => c.ContentLength).Returns(1);
+ postedFile.Setup(c => c.InputStream).Returns(new MemoryStream(_JpgImageBytes));
+
+ Mock<HttpFileCollectionBase> files = new Mock<HttpFileCollectionBase>();
+ files.Setup(c => c.Count).Returns(1);
+ files.Setup(c => c[0]).Returns(postedFile.Object);
+ Mock<HttpRequestBase> request = new Mock<HttpRequestBase>();
+ request.Setup(r => r.Files).Returns(files.Object);
+
+ // Act
+ WebImage image = WebImage.GetImageFromRequest(request.Object);
+
+ // Assert
+ Assert.NotNull(image);
+ Assert.Equal("jpeg", image.ImageFormat);
+ }
+
+ [Fact]
+ public void GetImageFromRequestIsCaseInsensitive()
+ {
+ // Arrange
+ Mock<HttpPostedFileBase> postedFile = new Mock<HttpPostedFileBase>();
+ postedFile.SetupGet(c => c.FileName).Returns("index.JPg");
+ postedFile.SetupGet(c => c.ContentType).Returns("application/octet-stream");
+ postedFile.SetupGet(c => c.ContentLength).Returns(1);
+ postedFile.SetupGet(c => c.InputStream).Returns(new MemoryStream(_JpgImageBytes));
+
+ Mock<HttpFileCollectionBase> files = new Mock<HttpFileCollectionBase>();
+ files.Setup(c => c.Count).Returns(1);
+ files.Setup(c => c[0]).Returns(postedFile.Object);
+ Mock<HttpRequestBase> request = new Mock<HttpRequestBase>();
+ request.Setup(r => r.Files).Returns(files.Object);
+
+ // Act
+ WebImage image = WebImage.GetImageFromRequest(request.Object);
+
+ // Assert
+ Assert.NotNull(image);
+ Assert.Equal("jpeg", image.ImageFormat);
+ }
+
+ [Fact]
+ public void ImagePropertiesAreCorrectForBmpImage()
+ {
+ WebImage image = new WebImage(_BmpImageBytes);
+
+ Assert.Equal("bmp", image.ImageFormat);
+ Assert.Equal(108, image.Width);
+ Assert.Equal(44, image.Height);
+ }
+
+ [Fact]
+ public void ImagePropertiesAreCorrectForPngImage()
+ {
+ WebImage image = new WebImage(_PngImageBytes);
+
+ Assert.Equal("png", image.ImageFormat);
+ Assert.Equal(160, image.Width);
+ Assert.Equal(152, image.Height);
+ }
+
+ [Fact]
+ public void ImagePropertiesAreCorrectForJpgImage()
+ {
+ WebImage image = new WebImage(_JpgImageBytes);
+
+ Assert.Equal("jpeg", image.ImageFormat);
+ Assert.Equal(634, image.Width);
+ Assert.Equal(489, image.Height);
+ }
+
+ [Fact]
+ public void ResizePreservesRatio()
+ {
+ WebImage image = new WebImage(_JpgImageBytes);
+
+ image.Resize(200, 100, preserveAspectRatio: true, preventEnlarge: true);
+
+ Assert.Equal(130, image.Width);
+ Assert.Equal(100, image.Height);
+ }
+
+ [Fact]
+ public void ResizePreservesResolution()
+ {
+ MemoryStream output = null;
+ Action<string, byte[]> saveAction = (_, content) => { output = new MemoryStream(content); };
+
+ WebImage image = new WebImage(_HiResImageBytes);
+
+ image.Resize(200, 100, preserveAspectRatio: true, preventEnlarge: true);
+
+ image.Save(GetContext(), saveAction, @"x:\ResizePreservesResolution.jpg", "jpeg", forceWellKnownExtension: true);
+ using (Image original = Image.FromStream(new MemoryStream(_HiResImageBytes)))
+ {
+ using (Image modified = Image.FromStream(output))
+ {
+ Assert.Equal(original.HorizontalResolution, modified.HorizontalResolution);
+ Assert.Equal(original.VerticalResolution, modified.VerticalResolution);
+ }
+ }
+ }
+
+ [Fact]
+ public void ResizePreservesFormat()
+ {
+ // Arrange
+ WebImage image = new WebImage(_PngImageBytes);
+ MemoryStream output = null;
+ Action<string, byte[]> saveAction = (_, content) => { output = new MemoryStream(content); };
+
+ // Act
+ image.Resize(200, 100, preserveAspectRatio: true, preventEnlarge: true);
+
+ // Assert
+ Assert.Equal(image.ImageFormat, "png");
+ image.Save(GetContext(), saveAction, @"x:\1.png", null, false);
+
+ using (Image modified = Image.FromStream(output))
+ {
+ Assert.Equal(ImageFormat.Png, modified.RawFormat);
+ }
+ }
+
+ [Fact]
+ public void SaveUpdatesFileNameOfWebImageWhenForcingWellKnownExtension()
+ {
+ // Arrange
+ var context = GetContext();
+
+ // Act
+ WebImage image = new WebImage(context, _ => _JpgImageBytes, @"c:\images\foo.jpg");
+
+ image.Save(context, (_, __) => { }, @"x:\1.exe", "jpg", forceWellKnownExtension: true);
+
+ // Assert
+ Assert.Equal(@"x:\1.exe.jpeg", image.FileName);
+ }
+
+ [Fact]
+ public void SaveUpdatesFileNameOfWebImageWhenFormatChanges()
+ {
+ // Arrange
+ string imagePath = @"x:\images\foo.jpg";
+ var context = GetContext();
+
+ // Act
+ WebImage image = new WebImage(context, _ => _JpgImageBytes, imagePath);
+
+ image.Save(context, (_, __) => { }, imagePath, "png", forceWellKnownExtension: true);
+
+ // Assert
+ Assert.Equal(@"x:\images\foo.jpg.png", image.FileName);
+ }
+
+ [Fact]
+ public void SaveKeepsNameIfFormatIsUnchanged()
+ {
+ // Arrange
+ string imagePath = @"x:\images\foo.jpg";
+ var context = GetContext();
+
+ // Act
+ WebImage image = new WebImage(context, _ => _JpgImageBytes, imagePath);
+
+ image.Save(context, (_, __) => { }, imagePath, "jpg", forceWellKnownExtension: true);
+
+ // Assert
+ Assert.Equal(@"x:\images\foo.jpg", image.FileName);
+ }
+
+ [Fact]
+ public void ResizeThrowsOnIncorrectWidthOrHeight()
+ {
+ WebImage image = new WebImage(_JpgImageBytes);
+
+ Assert.ThrowsArgumentGreaterThan(
+ () => image.Resize(-1, 100, preserveAspectRatio: true, preventEnlarge: true),
+ "width",
+ "0");
+
+ Assert.ThrowsArgumentGreaterThan(
+ () => image.Resize(100, -1, preserveAspectRatio: true, preventEnlarge: true),
+ "height",
+ "0");
+ }
+
+ [Fact]
+ public void ResizeAndRotateDoesOperationsInRightOrder()
+ {
+ WebImage image = new WebImage(_JpgImageBytes);
+ image.Resize(200, 100, preserveAspectRatio: true, preventEnlarge: true).RotateLeft();
+
+ Assert.Equal(100, image.Width);
+ Assert.Equal(130, image.Height);
+ }
+
+ [Fact]
+ public void ClonePreservesAllInformation()
+ {
+ WebImage image = new WebImage(_JpgImageBytes);
+ image.Resize(200, 100, preserveAspectRatio: true, preventEnlarge: true).RotateLeft();
+
+ // this should preserve list of transformations
+ WebImage cloned = image.Clone();
+
+ Assert.Equal(100, cloned.Width);
+ Assert.Equal(130, cloned.Height);
+ }
+
+ [Fact]
+ public void ResizePreventsEnlarge()
+ {
+ WebImage image = new WebImage(_JpgImageBytes);
+
+ int height = image.Height;
+ int width = image.Width;
+
+ image.Resize(width * 2, height, preserveAspectRatio: true, preventEnlarge: true);
+ Assert.Equal(width, image.Width);
+ Assert.Equal(height, image.Height);
+ }
+
+ [Fact]
+ public void CropCreatesCroppedImage()
+ {
+ WebImage image = new WebImage(_JpgImageBytes);
+ image.Crop(20, 20, 20, 20);
+
+ Assert.Equal(594, image.Width);
+ Assert.Equal(449, image.Height);
+ }
+
+ [Fact]
+ public void CropThrowsOnIncorrectArguments()
+ {
+ WebImage image = new WebImage(_JpgImageBytes);
+
+ Assert.ThrowsArgumentGreaterThanOrEqualTo(
+ () => image.Crop(top: -1),
+ "top",
+ "0");
+
+ Assert.ThrowsArgumentGreaterThanOrEqualTo(
+ () => image.Crop(left: -1),
+ "left",
+ "0");
+
+ Assert.ThrowsArgumentGreaterThanOrEqualTo(
+ () => image.Crop(bottom: -1),
+ "bottom",
+ "0");
+
+ Assert.ThrowsArgumentGreaterThanOrEqualTo(
+ () => image.Crop(right: -1),
+ "right",
+ "0");
+ }
+
+ [Fact]
+ public void RotateLeftReturnsRotatedImage()
+ {
+ WebImage image = new WebImage(_PngImageBytes);
+ image.RotateLeft();
+
+ Assert.Equal(152, image.Width);
+ Assert.Equal(160, image.Height);
+ }
+
+ [Fact]
+ public void RotateRightReturnsRotatedImage()
+ {
+ WebImage image = new WebImage(_PngImageBytes);
+ image.RotateRight();
+
+ Assert.Equal(152, image.Width);
+ Assert.Equal(160, image.Height);
+ }
+
+ [Fact]
+ public void FlipVerticalReturnsFlippedImage()
+ {
+ WebImage image = new WebImage(_PngImageBytes);
+ image.FlipVertical();
+
+ Assert.Equal(160, image.Width);
+ Assert.Equal(152, image.Height);
+ }
+
+ [Fact]
+ public void FlipHorizontalReturnsFlippedImage()
+ {
+ WebImage image = new WebImage(_PngImageBytes);
+ image.FlipHorizontal();
+
+ Assert.Equal(160, image.Width);
+ Assert.Equal(152, image.Height);
+ }
+
+ [Fact]
+ public void MultipleCombinedOperationsExecuteInRightOrder()
+ {
+ WebImage image = new WebImage(_JpgImageBytes);
+ image.Resize(200, 100, preserveAspectRatio: true, preventEnlarge: true).RotateLeft();
+ image.Crop(top: 10, right: 10).AddTextWatermark("plan9");
+
+ Assert.Equal(90, image.Width);
+ Assert.Equal(120, image.Height);
+ }
+
+ [Fact]
+ public void AddTextWatermarkPreservesImageDimension()
+ {
+ WebImage image = new WebImage(_JpgImageBytes);
+ image.AddTextWatermark("Plan9", fontSize: 16, horizontalAlign: "Left", verticalAlign: "Bottom", opacity: 50);
+
+ Assert.Equal(634, image.Width);
+ Assert.Equal(489, image.Height);
+ }
+
+ [Fact]
+ public void AddTextWatermarkParsesHexColorCorrectly()
+ {
+ WebImage image = new WebImage(_JpgImageBytes);
+ image.AddTextWatermark("Plan9", fontSize: 16, fontColor: "#FF0000", horizontalAlign: "Center", verticalAlign: "Middle");
+
+ Assert.Equal(634, image.Width);
+ Assert.Equal(489, image.Height);
+ }
+
+ [Fact]
+ public void AddTextWatermarkParsesShortHexColorCorrectly()
+ {
+ WebImage image = new WebImage(_JpgImageBytes);
+ image.AddTextWatermark("Plan9", fontSize: 16, fontColor: "#F00", horizontalAlign: "Center", verticalAlign: "Middle");
+
+ Assert.Equal(634, image.Width);
+ Assert.Equal(489, image.Height);
+ }
+
+ [Fact]
+ public void AddTextWatermarkDoesNotChangeImageIfPaddingIsTooBig()
+ {
+ WebImage image = new WebImage(_JpgImageBytes);
+ image.AddTextWatermark("Plan9", padding: 1000);
+
+ Assert.Equal(634, image.Width);
+ Assert.Equal(489, image.Height);
+ }
+
+ [Fact]
+ public void AddTextWatermarkThrowsOnNegativeOpacity()
+ {
+ WebImage image = new WebImage(_JpgImageBytes);
+
+ Assert.ThrowsArgumentOutOfRange(() => image.AddTextWatermark("Plan9", opacity: -1), "opacity", "Value must be between 0 and 100.");
+ }
+
+ [Fact]
+ public void AddTextWatermarkThrowsOnTooBigOpacity()
+ {
+ WebImage image = new WebImage(_JpgImageBytes);
+
+ Assert.ThrowsArgumentOutOfRange(() => image.AddTextWatermark("Plan9", opacity: 155), "opacity", "Value must be between 0 and 100.");
+ }
+
+ [Fact]
+ public void AddTextWatermarkThrowsOnEmptyText()
+ {
+ WebImage image = new WebImage(_JpgImageBytes);
+ Assert.ThrowsArgumentNullOrEmptyString(
+ () => image.AddTextWatermark(""),
+ "text");
+ }
+
+ [Fact]
+ public void AddTextWatermarkThrowsOnIncorrectColorName()
+ {
+ WebImage image = new WebImage(_JpgImageBytes);
+ Assert.Throws<ArgumentException>(
+ () => image.AddTextWatermark("p9", fontColor: "super"),
+ "The \"fontColor\" value is invalid. Valid values are names like \"White\", \"Black\", or \"DarkBlue\", or hexadecimal values in the form \"#RRGGBB\" or \"#RGB\".");
+ }
+
+ [Fact]
+ public void AddTextWatermarkThrowsOnIncorrectHexColorValue()
+ {
+ WebImage image = new WebImage(_JpgImageBytes);
+ Assert.Throws<ArgumentException>(
+ () => image.AddTextWatermark("p9", fontColor: "#XXX"),
+ "The \"fontColor\" value is invalid. Valid values are names like \"White\", \"Black\", or \"DarkBlue\", or hexadecimal values in the form \"#RRGGBB\" or \"#RGB\".");
+ }
+
+ [Fact]
+ public void AddTextWatermarkThrowsOnIncorrectHexColorLength()
+ {
+ WebImage image = new WebImage(_JpgImageBytes);
+ Assert.Throws<ArgumentException>(
+ () => image.AddTextWatermark("p9", fontColor: "#F000"),
+ "The \"fontColor\" value is invalid. Valid values are names like \"White\", \"Black\", or \"DarkBlue\", or hexadecimal values in the form \"#RRGGBB\" or \"#RGB\".");
+ }
+
+ [Fact]
+ public void AddTextWatermarkThrowsOnIncorrectHorizontalAlignment()
+ {
+ WebImage image = new WebImage(_JpgImageBytes);
+ Assert.Throws<ArgumentException>(
+ () => image.AddTextWatermark("p9", horizontalAlign: "Justify"),
+ "The \"horizontalAlign\" value is invalid. Valid values are: \"Right\", \"Left\", and \"Center\".");
+ }
+
+ [Fact]
+ public void AddTextWatermarkThrowsOnIncorrectVerticalAlignment()
+ {
+ WebImage image = new WebImage(_JpgImageBytes);
+ Assert.Throws<ArgumentException>(
+ () => image.AddTextWatermark("p9", verticalAlign: "NotSet"),
+ "The \"verticalAlign\" value is invalid. Valid values are: \"Top\", \"Bottom\", and \"Middle\".");
+ }
+
+ [Fact]
+ public void AddTextWatermarkThrowsOnNegativePadding()
+ {
+ WebImage image = new WebImage(_JpgImageBytes);
+ Assert.ThrowsArgumentGreaterThanOrEqualTo(
+ () => image.AddTextWatermark("p9", padding: -10),
+ "padding",
+ "0");
+ }
+
+ [Fact]
+ public void AddTextWatermarkThrowsOnIncorrectFontSize()
+ {
+ WebImage image = new WebImage(_JpgImageBytes);
+ Assert.ThrowsArgumentGreaterThan(
+ () => image.AddTextWatermark("p9", fontSize: -10),
+ "fontSize",
+ "0");
+
+ Assert.ThrowsArgumentGreaterThan(
+ () => image.AddTextWatermark("p9", fontSize: 0),
+ "fontSize",
+ "0");
+ }
+
+ [Fact]
+ public void AddTextWatermarkThrowsOnIncorrectFontStyle()
+ {
+ WebImage image = new WebImage(_JpgImageBytes);
+
+ Assert.Throws<ArgumentException>(
+ () => image.AddTextWatermark("p9", fontStyle: "something"),
+ "The \"fontStyle\" value is invalid. Valid values are: \"Regular\", \"Bold\", \"Italic\", \"Underline\", and \"Strikeout\".");
+ }
+
+ [Fact]
+ public void AddTextWatermarkThrowsOnIncorrectFontFamily()
+ {
+ WebImage image = new WebImage(_JpgImageBytes);
+
+ Assert.Throws<ArgumentException>(
+ () => image.AddTextWatermark("p9", fontFamily: "something"),
+ "The \"fontFamily\" value is invalid. Valid values are font family names like: \"Arial\", \"Times New Roman\", etc. Make sure that the font family you are trying to use is installed on the server.");
+ }
+
+ [Fact]
+ public void AddImageWatermarkPreservesImageDimension()
+ {
+ WebImage watermark = new WebImage(_BmpImageBytes);
+ WebImage image = new WebImage(_JpgImageBytes);
+ image.AddImageWatermark(watermark, horizontalAlign: "LEFT", verticalAlign: "top", opacity: 50, padding: 10);
+
+ Assert.Equal(634, image.Width);
+ Assert.Equal(489, image.Height);
+ }
+
+ [Fact]
+ public void CanAddTextAndImageWatermarks()
+ {
+ WebImage watermark = new WebImage(_BmpImageBytes);
+ WebImage image = new WebImage(_JpgImageBytes);
+ image.AddImageWatermark(watermark, horizontalAlign: "LEFT", verticalAlign: "top", opacity: 30, padding: 10);
+ image.AddTextWatermark("plan9");
+
+ Assert.Equal(634, image.Width);
+ Assert.Equal(489, image.Height);
+ }
+
+ [Fact]
+ public void AddImageWatermarkDoesNotChangeWatermarkImage()
+ {
+ WebImage watermark = new WebImage(_BmpImageBytes);
+ WebImage image = new WebImage(_JpgImageBytes);
+ image.AddImageWatermark(watermark, width: 54, height: 22, horizontalAlign: "LEFT", verticalAlign: "top", opacity: 50, padding: 10);
+
+ Assert.Equal(108, watermark.Width);
+ Assert.Equal(44, watermark.Height);
+ }
+
+ [Fact]
+ public void AddImageWatermarkThrowsOnNullImage()
+ {
+ WebImage image = new WebImage(_JpgImageBytes);
+
+ Assert.ThrowsArgumentNull(
+ () => image.AddImageWatermark(watermarkImage: null),
+ "watermarkImage");
+ }
+
+ [Fact]
+ public void AddImageWatermarkThrowsWhenJustOneDimensionIsZero()
+ {
+ WebImage watermark = new WebImage(_BmpImageBytes);
+ WebImage image = new WebImage(_JpgImageBytes);
+
+ string message = "Watermark width and height must both be positive or both be zero.";
+ Assert.Throws<ArgumentException>(
+ () => image.AddImageWatermark(watermark, width: 0, height: 22), message);
+
+ Assert.Throws<ArgumentException>(
+ () => image.AddImageWatermark(watermark, width: 100, height: 0), message);
+ }
+
+ [Fact]
+ public void AddImageWatermarkThrowsWhenOpacityIsIncorrect()
+ {
+ WebImage watermark = new WebImage(_BmpImageBytes);
+ WebImage image = new WebImage(_JpgImageBytes);
+
+ Assert.ThrowsArgumentOutOfRange(() => image.AddImageWatermark(watermark, opacity: -1), "opacity", "Value must be between 0 and 100.");
+
+ Assert.ThrowsArgumentOutOfRange(() => image.AddImageWatermark(watermark, opacity: 120), "opacity", "Value must be between 0 and 100.");
+ }
+
+ [Fact]
+ public void AddImageWatermarkThrowsOnNegativeDimensions()
+ {
+ WebImage watermark = new WebImage(_BmpImageBytes);
+ WebImage image = new WebImage(_JpgImageBytes);
+
+ Assert.ThrowsArgumentGreaterThanOrEqualTo(
+ () => image.AddImageWatermark(watermark, width: -1),
+ "width",
+ "0");
+
+ Assert.ThrowsArgumentGreaterThanOrEqualTo(
+ () => image.AddImageWatermark(watermark, height: -1),
+ "height",
+ "0");
+ }
+
+ [Fact]
+ public void AddImageWatermarkThrowsOnIncorrectHorizontalAlignment()
+ {
+ WebImage watermark = new WebImage(_BmpImageBytes);
+ WebImage image = new WebImage(_JpgImageBytes);
+
+ Assert.Throws<ArgumentException>(
+ () => image.AddImageWatermark(watermark, horizontalAlign: "horizontal"),
+ "The \"horizontalAlign\" value is invalid. Valid values are: \"Right\", \"Left\", and \"Center\".");
+ }
+
+ [Fact]
+ public void AddImageWatermarkThrowsOnIncorrectVerticalAlignment()
+ {
+ WebImage watermark = new WebImage(_BmpImageBytes);
+ WebImage image = new WebImage(_JpgImageBytes);
+
+ Assert.Throws<ArgumentException>(
+ () => image.AddImageWatermark(watermark, verticalAlign: "vertical"),
+ "The \"verticalAlign\" value is invalid. Valid values are: \"Top\", \"Bottom\", and \"Middle\".");
+ }
+
+ [Fact]
+ public void AddImageWatermarkThrowsOnNegativePadding()
+ {
+ WebImage watermark = new WebImage(_BmpImageBytes);
+ WebImage image = new WebImage(_JpgImageBytes);
+
+ Assert.ThrowsArgumentGreaterThanOrEqualTo(
+ () => image.AddImageWatermark(watermark, padding: -10),
+ "padding",
+ "0");
+ }
+
+ [Fact]
+ public void AddImageWatermarkDoesNotChangeImageIfWatermarkIsTooBig()
+ {
+ WebImage watermark = new WebImage(_JpgImageBytes);
+ WebImage image = new WebImage(_BmpImageBytes);
+ byte[] originalBytes = image.GetBytes("jpg");
+
+ // This will use original watermark image dimensions which is bigger than the target image.
+ image.AddImageWatermark(watermark);
+ byte[] watermarkedBytes = image.GetBytes("jpg");
+
+ Assert.Equal(originalBytes, watermarkedBytes);
+ }
+
+ [Fact]
+ public void AddImageWatermarkWithFileNameThrowsExceptionWhenWatermarkDirectoryDoesNotExist()
+ {
+ var context = GetContext();
+ WebImage image = new WebImage(_BmpImageBytes);
+
+ Assert.Throws<DirectoryNotFoundException>(
+ () => image.AddImageWatermark(context, s => { throw new DirectoryNotFoundException(); }, @"x:\path\does\not\exist", width: 0, height: 0, horizontalAlign: "Right", verticalAlign: "Bottom", opacity: 100, padding: 5));
+ }
+
+ [Fact]
+ public void AddImageWatermarkWithFileNameThrowsExceptionWhenWatermarkFileDoesNotExist()
+ {
+ var context = GetContext();
+ WebImage image = new WebImage(_BmpImageBytes);
+ Assert.Throws<FileNotFoundException>(
+ () => image.AddImageWatermark(context, s => { throw new FileNotFoundException(); }, @"x:\there-is-no-file.jpg", width: 0, height: 0, horizontalAlign: "Right", verticalAlign: "Bottom", opacity: 100, padding: 5));
+ }
+
+ [Fact]
+ public void AddImageWatermarkWithFileNameThrowsExceptionWhenWatermarkFilePathIsNull()
+ {
+ var context = GetContext();
+
+ WebImage image = new WebImage(_BmpImageBytes);
+ Assert.ThrowsArgument(
+ () => image.AddImageWatermark(context, s => _JpgImageBytes, watermarkImageFilePath: null, width: 0, height: 0, horizontalAlign: "Right", verticalAlign: "Bottom", opacity: 100, padding: 5),
+ "filePath",
+ "Value cannot be null or an empty string.");
+ }
+
+ [Fact]
+ public void AddImageWatermarkWithFileNameThrowsExceptionWhenWatermarkFilePathIsEmpty()
+ {
+ var context = GetContext();
+ WebImage image = new WebImage(_BmpImageBytes);
+ Assert.ThrowsArgument(
+ () => image.AddImageWatermark(context, s => _JpgImageBytes, watermarkImageFilePath: null, width: 0, height: 0, horizontalAlign: "Right", verticalAlign: "Bottom", opacity: 100, padding: 5),
+ "filePath",
+ "Value cannot be null or an empty string.");
+ }
+
+ [Fact]
+ public void CanAddImageWatermarkWithFileName()
+ {
+ // Arrange
+ var context = GetContext();
+ WebImage image = new WebImage(_BmpImageBytes);
+ WebImage watermark = new WebImage(_JpgImageBytes);
+
+ // Act
+ var watermarkedWithImageArgument = image.AddImageWatermark(watermark).GetBytes();
+ var watermarkedWithFilePathArgument = image.AddImageWatermark(context, (name) => _JpgImageBytes, @"x:\jpegimage.jpg", width: 0, height: 0, horizontalAlign: "Right", verticalAlign: "Bottom", opacity: 100, padding: 5).GetBytes();
+
+ Assert.Equal(watermarkedWithImageArgument, watermarkedWithFilePathArgument);
+ }
+
+ [Fact]
+ public void SaveOverwritesExistingFile()
+ {
+ Action<string, byte[]> saveAction = (path, content) => { };
+
+ WebImage image = new WebImage(_BmpImageBytes);
+ string newFileName = @"x:\newImage.bmp";
+
+ image.Save(GetContext(), saveAction, newFileName, imageFormat: null, forceWellKnownExtension: true);
+
+ image.RotateLeft();
+ // just verify this does not throw
+ image.Save(GetContext(), saveAction, newFileName, imageFormat: null, forceWellKnownExtension: true);
+ }
+
+ [Fact]
+ public void SaveThrowsWhenPathIsNull()
+ {
+ Action<string, byte[]> saveAction = (path, content) => { };
+
+ // this constructor will not set path
+ byte[] originalContent = _BmpImageBytes;
+ WebImage image = new WebImage(originalContent);
+
+ Assert.ThrowsArgumentNullOrEmptyString(
+ () => image.Save(GetContext(), saveAction, filePath: null, imageFormat: null, forceWellKnownExtension: true),
+ "filePath");
+ }
+
+ [Fact]
+ public void SaveThrowsWhenPathIsEmpty()
+ {
+ Action<string, byte[]> saveAction = (path, content) => { };
+ WebImage image = new WebImage(_BmpImageBytes);
+
+ Assert.ThrowsArgumentNullOrEmptyString(
+ () => image.Save(GetContext(), saveAction, filePath: String.Empty, imageFormat: null, forceWellKnownExtension: true),
+ "filePath");
+ }
+
+ [Fact]
+ public void SaveUsesOriginalFormatWhenNoFormatIsSpecified()
+ {
+ // Arrange
+ // Use rooted path so we by pass using HttpContext
+ var specifiedOutputFile = @"C:\some-dir\foo.jpg";
+ string actualOutputFile = null;
+ Action<string, byte[]> saveAction = (fileName, content) => { actualOutputFile = fileName; };
+
+ // Act
+ WebImage image = new WebImage(_PngImageBytes);
+ image.Save(GetContext(), saveAction, filePath: specifiedOutputFile, imageFormat: null, forceWellKnownExtension: true);
+
+ // Assert
+ Assert.Equal(Path.GetExtension(actualOutputFile), ".png");
+ }
+
+ [Fact]
+ public void SaveUsesOriginalFormatForStreamsWhenNoFormatIsSpecified()
+ {
+ // Arrange
+ // Use rooted path so we by pass using HttpContext
+ var specifiedOutputFile = @"x:\some-dir\foo.jpg";
+ string actualOutputFile = null;
+ Action<string, byte[]> saveAction = (fileName, content) => { actualOutputFile = fileName; };
+
+ // Act
+ WebImage image = new WebImage(_PngImageBytes);
+ image.Save(GetContext(), saveAction, filePath: specifiedOutputFile, imageFormat: null, forceWellKnownExtension: true);
+
+ // Assert
+ Assert.Equal(Path.GetExtension(actualOutputFile), ".png");
+ }
+
+ [Fact]
+ public void SaveSetsExtensionBasedOnFormatWhenForceExtensionIsSet()
+ {
+ // Arrange
+ // Use rooted path so we by pass using HttpContext
+ var specifiedOutputFile = @"x:\some-dir\foo.exe";
+ string actualOutputFile = null;
+ Action<string, byte[]> saveAction = (fileName, content) => { actualOutputFile = fileName; };
+
+ // Act
+ WebImage image = new WebImage(_BmpImageBytes);
+ image.Save(GetContext(), saveAction, filePath: specifiedOutputFile, imageFormat: "jpg", forceWellKnownExtension: true);
+
+ // Assert
+ Assert.Equal(".jpeg", Path.GetExtension(actualOutputFile));
+ Assert.Equal(specifiedOutputFile + ".jpeg", actualOutputFile);
+ }
+
+ [Fact]
+ public void SaveAppendsExtensionBasedOnFormatWhenForceExtensionIsSet()
+ {
+ // Arrange
+ // Use rooted path so we by pass using HttpContext
+ var specifiedOutputFile = @"x:\some-dir\foo";
+ string actualOutputFile = null;
+ Action<string, byte[]> saveAction = (fileName, content) => { actualOutputFile = fileName; };
+
+ // Act
+ WebImage image = new WebImage(_BmpImageBytes);
+ image.Save(GetContext(), saveAction, filePath: specifiedOutputFile, imageFormat: "jpg", forceWellKnownExtension: true);
+
+ // Assert
+ Assert.Equal(".jpeg", Path.GetExtension(actualOutputFile));
+ }
+
+ [Fact]
+ public void SaveDoesNotModifyExtensionWhenExtensionIsCorrect()
+ {
+ // Arrange
+ // Use rooted path so we by pass using HttpContext
+ var specifiedOutputFile = @"x:\some-dir\foo.jpg";
+ string actualOutputFile = null;
+ Action<string, byte[]> saveAction = (fileName, content) => { actualOutputFile = fileName; };
+
+ // Act
+ WebImage image = new WebImage(_BmpImageBytes);
+ image.Save(GetContext(), saveAction, filePath: specifiedOutputFile, imageFormat: "jpg", forceWellKnownExtension: true);
+
+ // Assert
+ Assert.Equal(specifiedOutputFile, actualOutputFile);
+ }
+
+ [Fact]
+ public void SaveDoesNotModifyExtensionWhenForceCorrectExtensionRenameIsCleared()
+ {
+ // Arrange
+ // Use rooted path so we by pass using HttpContext
+ var specifiedOutputFile = @"x:\some-dir\foo.exe";
+ string actualOutputFile = null;
+ Action<string, byte[]> saveAction = (fileName, content) => { actualOutputFile = fileName; };
+
+ // Act
+ WebImage image = new WebImage(_BmpImageBytes);
+ image.Save(GetContext(), saveAction, filePath: specifiedOutputFile, imageFormat: "jpg", forceWellKnownExtension: false);
+
+ // Assert
+ Assert.Equal(specifiedOutputFile, actualOutputFile);
+ }
+
+ [Fact]
+ public void ImageFormatIsSavedCorrectly()
+ {
+ WebImage image = new WebImage(_BmpImageBytes);
+ Assert.Equal("bmp", image.ImageFormat);
+ }
+
+ [Fact]
+ public void SaveUsesInitialFormatWhenNoFormatIsSpecified()
+ {
+ // Arrange
+ string savePath = @"x:\some-dir\image.png";
+ MemoryStream stream = null;
+ Action<string, byte[]> saveAction = (path, content) => { stream = new MemoryStream(content); };
+ var image = new WebImage(_PngImageBytes);
+
+ // Act
+ image.FlipVertical().FlipHorizontal();
+
+ // Assert
+ image.Save(GetContext(), saveAction, savePath, imageFormat: null, forceWellKnownExtension: true);
+
+ using (Image savedImage = Image.FromStream(stream))
+ {
+ Assert.Equal(savedImage.RawFormat, ImageFormat.Png);
+ }
+ }
+
+ [Fact]
+ public void ImageFormatIsParsedCorrectly()
+ {
+ WebImage image = new WebImage(_BmpImageBytes);
+ Assert.Equal("bmp", image.ImageFormat);
+ }
+
+ private static HttpContextBase GetContext()
+ {
+ var httpContext = new Mock<HttpContextBase>();
+ var httpRequest = new Mock<HttpRequestBase>();
+ httpRequest.Setup(c => c.MapPath(It.IsAny<string>())).Returns((string path) => path);
+ httpContext.Setup(c => c.Request).Returns(httpRequest.Object);
+
+ return httpContext.Object;
+ }
+
+ // Test stream that pretends it can't seek.
+ private class TestStream : Stream
+ {
+ private MemoryStream _memoryStream;
+
+ public TestStream(MemoryStream memoryStream)
+ {
+ _memoryStream = memoryStream;
+ }
+
+ public override bool CanRead
+ {
+ get { return _memoryStream.CanRead; }
+ }
+
+ public override bool CanSeek
+ {
+ get { return false; }
+ }
+
+ public override bool CanWrite
+ {
+ get { return _memoryStream.CanWrite; }
+ }
+
+ public override void Flush()
+ {
+ _memoryStream.Flush();
+ }
+
+ public override long Length
+ {
+ get { throw new NotSupportedException(); }
+ }
+
+ public override long Position
+ {
+ get { return _memoryStream.Position; }
+ set { _memoryStream.Position = value; }
+ }
+
+ public override int Read(byte[] buffer, int offset, int count)
+ {
+ return _memoryStream.Read(buffer, offset, count);
+ }
+
+ public override long Seek(long offset, SeekOrigin origin)
+ {
+ throw new NotSupportedException();
+ }
+
+ public override void SetLength(long value)
+ {
+ _memoryStream.SetLength(value);
+ }
+
+ public override void Write(byte[] buffer, int offset, int count)
+ {
+ _memoryStream.Write(buffer, offset, count);
+ }
+ }
+ }
+}
diff --git a/test/System.Web.Helpers.Test/WebMailTest.cs b/test/System.Web.Helpers.Test/WebMailTest.cs
new file mode 100644
index 00000000..fa3eb5b4
--- /dev/null
+++ b/test/System.Web.Helpers.Test/WebMailTest.cs
@@ -0,0 +1,378 @@
+using System.IO;
+using System.Linq;
+using System.Net.Mail;
+using System.Text;
+using System.Web.WebPages.Scope;
+using System.Web.WebPages.TestUtils;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Helpers.Test
+{
+ public class WebMailTest
+ {
+ const string FromAddress = "abc@123.com";
+ const string Server = "myserver.com";
+ const int Port = 100;
+ const string UserName = "My UserName";
+ const string Password = "My Password";
+
+ [Fact]
+ public void WebMailSmtpServerTests()
+ {
+ // All tests prior to setting smtp server go here
+ // Verify Send throws if no SmtpServer is set
+ Assert.Throws<InvalidOperationException>(
+ () => WebMail.Send(to: "test@test.com", subject: "test", body: "test body"),
+ "\"SmtpServer\" was not specified."
+ );
+
+ // Verify SmtpServer uses scope storage.
+ // Arrange
+ var value = "value";
+
+ // Act
+ WebMail.SmtpServer = value;
+
+ // Assert
+ Assert.Equal(WebMail.SmtpServer, value);
+ Assert.Equal(ScopeStorage.CurrentScope[WebMail.SmtpServerKey], value);
+ }
+
+ [Fact]
+ public void WebMailSendThrowsIfPriorityIsInvalid()
+ {
+ Assert.ThrowsArgument(
+ () => WebMail.Send(to: "test@test.com", subject: "test", body: "test body", priority: "foo"),
+ "priority",
+ "The \"priority\" value is invalid. Valid values are \"Low\", \"Normal\" and \"High\"."
+ );
+ }
+
+ [Fact]
+ public void WebMailUsesScopeStorageForSmtpPort()
+ {
+ // Arrange
+ var value = 4;
+
+ // Act
+ WebMail.SmtpPort = value;
+
+ // Assert
+ Assert.Equal(WebMail.SmtpPort, value);
+ Assert.Equal(ScopeStorage.CurrentScope[WebMail.SmtpPortKey], value);
+ }
+
+ [Fact]
+ public void WebMailUsesScopeStorageForEnableSsl()
+ {
+ // Arrange
+ var value = true;
+
+ // Act
+ WebMail.EnableSsl = value;
+
+ // Assert
+ Assert.Equal(WebMail.EnableSsl, value);
+ Assert.Equal(ScopeStorage.CurrentScope[WebMail.EnableSslKey], value);
+ }
+
+ [Fact]
+ public void WebMailUsesScopeStorageForDefaultCredentials()
+ {
+ // Arrange
+ var value = true;
+
+ // Act
+ WebMail.SmtpUseDefaultCredentials = value;
+
+ // Assert
+ Assert.Equal(WebMail.SmtpUseDefaultCredentials, value);
+ Assert.Equal(ScopeStorage.CurrentScope[WebMail.SmtpUseDefaultCredentialsKey], value);
+ }
+
+ [Fact]
+ public void WebMailUsesScopeStorageForUserName()
+ {
+ // Arrange
+ var value = "value";
+
+ // Act
+ WebMail.UserName = value;
+
+ // Assert
+ Assert.Equal(WebMail.UserName, value);
+ Assert.Equal(ScopeStorage.CurrentScope[WebMail.UserNameKey], value);
+ }
+
+ [Fact]
+ public void WebMailUsesScopeStorageForPassword()
+ {
+ // Arrange
+ var value = "value";
+
+ // Act
+ WebMail.Password = value;
+
+ // Assert
+ Assert.Equal(WebMail.Password, value);
+ Assert.Equal(ScopeStorage.CurrentScope[WebMail.PasswordKey], value);
+ }
+
+ [Fact]
+ public void WebMailUsesScopeStorageForFrom()
+ {
+ // Arrange
+ var value = "value";
+
+ // Act
+ WebMail.From = value;
+
+ // Assert
+ Assert.Equal(WebMail.From, value);
+ Assert.Equal(ScopeStorage.CurrentScope[WebMail.FromKey], value);
+ }
+
+ [Fact]
+ public void WebMailThrowsWhenSmtpServerValueIsNullOrEmpty()
+ {
+ // Act and Assert
+ Assert.ThrowsArgumentNullOrEmptyString(() => WebMail.SmtpServer = null, "SmtpServer");
+ Assert.ThrowsArgumentNullOrEmptyString(() => WebMail.SmtpServer = String.Empty, "SmtpServer");
+ }
+
+ [Fact]
+ public void ParseHeaderParsesStringInKeyValueFormat()
+ {
+ // Arrange
+ string header = "foo: bar";
+
+ // Act
+ string key, value;
+
+ // Assert
+ Assert.True(WebMail.TryParseHeader(header, out key, out value));
+ Assert.Equal("foo", key);
+ Assert.Equal("bar", value);
+ }
+
+ [Fact]
+ public void ParseHeaderReturnsFalseIfHeaderIsNotInCorrectFormat()
+ {
+ // Arrange
+ string header = "foo bar";
+
+ // Act
+ string key, value;
+
+ // Assert
+ Assert.False(WebMail.TryParseHeader(header, out key, out value));
+ Assert.Null(key);
+ Assert.Null(value);
+ }
+
+ [Fact]
+ public void SetPropertiesOnMessageTest_SetsAllInfoCorrectlyOnMailMessageTest()
+ {
+ // Arrange
+ MailMessage message = new MailMessage();
+ string to = "abc123@xyz.com";
+ string subject = "subject1";
+ string body = "body1";
+ string from = FromAddress;
+ string cc = "cc@xyz.com";
+ string attachmentName = "HiRes.jpg";
+ string bcc = "foo@bar.com";
+ string replyTo = "x@y.com,z@pqr.com";
+ string contentEncoding = "utf-8";
+ string headerEncoding = "utf-16";
+ var priority = MailPriority.Low;
+
+ // Act
+ string fileToAttach = Path.GetTempFileName();
+
+ try
+ {
+ TestFile.Create(attachmentName).Save(fileToAttach);
+ bool isBodyHtml = true;
+ var additionalHeaders = new[] { "header1:value1" };
+ WebMail.SetPropertiesOnMessage(message, to, subject, body, from, cc, bcc, replyTo, contentEncoding, headerEncoding, priority, new[] { fileToAttach }, isBodyHtml, additionalHeaders);
+
+ // Assert
+ Assert.Equal(body, message.Body);
+ Assert.Equal(subject, message.Subject);
+ Assert.Equal(to, message.To[0].Address);
+ Assert.Equal(cc, message.CC[0].Address);
+ Assert.Equal(from, message.From.Address);
+ Assert.Equal(bcc, message.Bcc[0].Address);
+ Assert.Equal("x@y.com", message.ReplyToList[0].Address);
+ Assert.Equal("z@pqr.com", message.ReplyToList[1].Address);
+ Assert.Equal(MailPriority.Low, message.Priority);
+ Assert.Equal(Encoding.UTF8, message.BodyEncoding);
+ Assert.Equal(Encoding.Unicode, message.HeadersEncoding);
+
+ Assert.True(message.Headers.AllKeys.Contains("header1"));
+ Assert.True(message.Attachments.Count == 1);
+ }
+ finally
+ {
+ try
+ {
+ File.Delete(fileToAttach);
+ }
+ catch (IOException)
+ {
+ } // Try our best to clean up after ourselves
+ }
+ }
+
+ [Fact]
+ public void MailSendWithNullInCollection_ThrowsArgumentException()
+ {
+ Assert.Throws<ArgumentException>(
+ () => WebMail.Send("foo@bar.com", "sub", "body", filesToAttach: new string[] { "c:\\foo.txt", null }),
+ "A string in the collection is null or empty.\r\nParameter name: filesToAttach"
+ );
+
+ Assert.Throws<ArgumentException>(
+ () => WebMail.Send("foo@bar.com", "sub", "body", additionalHeaders: new string[] { "foo:bar", null }),
+ "A string in the collection is null or empty.\r\nParameter name: additionalHeaders"
+ );
+ }
+
+ [Fact]
+ public void AssignHeaderValuesIgnoresMalformedHeaders()
+ {
+ // Arrange
+ var message = new MailMessage();
+ var headers = new[] { "foo1:bar1", "foo2", "foo3|bar3", "foo4 bar4" };
+
+ // Act
+ WebMail.AssignHeaderValues(message, headers);
+
+ // Assert
+ Assert.Equal(1, message.Headers.Count);
+ Assert.Equal("foo1", message.Headers.AllKeys[0]);
+ Assert.Equal("bar1", message.Headers[0]);
+ }
+
+ [Fact]
+ public void PropertiesDuplicatedAcrossHeaderAndArgumentDoesNotThrow()
+ {
+ // Arrange
+ var message = new MailMessage();
+ var headers = new[] { "to:to@test.com" };
+
+ // Act
+ WebMail.SetPropertiesOnMessage(message, "to@test.com", null, null, "from@test.com", null, null, null, null, null, MailPriority.Normal, null, false, headers);
+
+ // Assert
+ Assert.Equal(2, message.To.Count);
+ Assert.Equal("to@test.com", message.To.First().Address);
+ Assert.Equal("to@test.com", message.To.Last().Address);
+ }
+
+ [Fact]
+ public void AssignHeaderValuesSetsPropertiesForKnownHeaderValues()
+ {
+ // Arrange
+ var message = new MailMessage();
+ var headers = new[]
+ {
+ "cc:cc@test.com", "bcc:bcc@test.com,bcc2@test.com", "from:from@test.com", "priority:high", "reply-to:replyto1@test.com,replyto2@test.com",
+ "sender: sender@test.com", "to:to@test.com"
+ };
+
+ // Act
+ WebMail.AssignHeaderValues(message, headers);
+
+ // Assert
+ Assert.Equal("cc@test.com", message.CC.Single().Address);
+ Assert.Equal("bcc@test.com", message.Bcc.First().Address);
+ Assert.Equal("bcc2@test.com", message.Bcc.Last().Address);
+ Assert.Equal("from@test.com", message.From.Address);
+ Assert.Equal(MailPriority.High, message.Priority);
+ Assert.Equal("replyto1@test.com", message.ReplyToList.First().Address);
+ Assert.Equal("replyto2@test.com", message.ReplyToList.Last().Address);
+ Assert.Equal("sender@test.com", message.Sender.Address);
+ Assert.Equal("to@test.com", message.To.Single().Address);
+
+ // Assert we transparently set header values
+ Assert.Equal(headers.Count(), message.Headers.Count);
+ }
+
+ [Fact]
+ public void AssignHeaderDoesNotThrowIfPriorityValueIsInvalid()
+ {
+ // Arrange
+ var message = new MailMessage();
+ var headers = new[] { "priority:invalid-value" };
+
+ // Act
+ WebMail.AssignHeaderValues(message, headers);
+
+ // Assert
+ Assert.Equal(MailPriority.Normal, message.Priority);
+
+ // Assert we transparently set header values
+ Assert.Equal(1, message.Headers.Count);
+ Assert.Equal("Priority", message.Headers.Keys[0]);
+ Assert.Equal("invalid-value", message.Headers["Priority"]);
+ }
+
+ [Fact]
+ public void AssignHeaderDoesNotThrowIfMailAddressIsInvalid()
+ {
+ // Arrange
+ var message = new MailMessage();
+ var headers = new[] { "to:not-#-email@@" };
+
+ // Act
+ WebMail.AssignHeaderValues(message, headers);
+
+ // Assert
+ Assert.Equal(0, message.To.Count);
+
+ // Assert we transparently set header values
+ Assert.Equal(1, message.Headers.Count);
+ Assert.Equal("To", message.Headers.Keys[0]);
+ Assert.Equal("not-#-email@@", message.Headers["To"]);
+ }
+
+ [Fact]
+ public void AssignHeaderDoesNotThrowIfKnownHeaderValuesAreEmptyOrMalformed()
+ {
+ // Arrange
+ var message = new MailMessage();
+ var headers = new[] { "to:", ":reply-to", "priority:false" };
+
+ // Act
+ WebMail.AssignHeaderValues(message, headers);
+
+ // Assert
+ Assert.Equal(0, message.To.Count);
+
+ // Assert we transparently set header values
+ Assert.Equal(1, message.Headers.Count);
+ Assert.Equal("Priority", message.Headers.Keys[0]);
+ Assert.Equal("false", message.Headers["Priority"]);
+ }
+
+ [Fact]
+ public void ArgumentsToSendTakePriorityOverHeader()
+ {
+ // Arrange
+ var message = new MailMessage();
+ var headers = new[] { "from:header-from@test.com", "cc:header-cc@test.com", "priority:low" };
+
+ // Act
+ WebMail.SetPropertiesOnMessage(message, null, null, null, "direct-from@test.com", "direct-cc@test.com", null, null, null, null, MailPriority.High, null, false, headers);
+
+ // Assert
+ Assert.Equal("direct-from@test.com", message.From.Address);
+ Assert.Equal("header-cc@test.com", message.CC.First().Address);
+ Assert.Equal("direct-cc@test.com", message.CC.Last().Address);
+ Assert.Equal(MailPriority.High, message.Priority);
+ }
+ }
+}
diff --git a/test/System.Web.Helpers.Test/XhtmlAssert.cs b/test/System.Web.Helpers.Test/XhtmlAssert.cs
new file mode 100644
index 00000000..d7a2879e
--- /dev/null
+++ b/test/System.Web.Helpers.Test/XhtmlAssert.cs
@@ -0,0 +1,128 @@
+using System.IO;
+using System.Net;
+using System.Reflection;
+using System.Text.RegularExpressions;
+using System.Web.WebPages;
+using System.Xml;
+using System.Xml.Resolvers;
+using Xunit;
+
+namespace System.Web.Helpers.Test
+{
+ // see: http://msdn.microsoft.com/en-us/library/hdf992b8(v=VS.100).aspx
+ // see: http://blogs.msdn.com/xmlteam/archive/2008/08/14/introducing-the-xmlpreloadedresolver.aspx
+ public class XhtmlAssert
+ {
+ const string Xhtml10Wrapper = "<html xmlns=\"http://www.w3.org/1999/xhtml\"><head></head><body>{0}</body></html>";
+ const string DOCTYPE_XHTML1_1 = "<!DOCTYPE {0} PUBLIC \"-//W3C//DTD XHTML 1.1//EN\" \"xhtml11-flat.dtd\">\r\n";
+
+ public static void Validate1_0(object result, bool addRoot = false)
+ {
+ string html = null;
+ if (addRoot)
+ {
+ html = String.Format(Xhtml10Wrapper, GetHtml(result));
+ }
+ else
+ {
+ html = GetHtml(result);
+ }
+
+ Validate1_0(html);
+ }
+
+ public static void Validate1_1(object result, string wrapper = null)
+ {
+ string root;
+ string html = GetHtml(result);
+ if (String.IsNullOrEmpty(wrapper))
+ {
+ root = GetRoot(html);
+ }
+ else
+ {
+ root = wrapper;
+ html = String.Format("<{0}>{1}</{0}>", wrapper, html);
+ }
+ Validate1_1(root, html);
+ }
+
+ private static string GetHtml(object result)
+ {
+ Assert.True((result is IHtmlString) || (result is HelperResult), "Helpers should return IHTMLString or HelperResult");
+ return result.ToString();
+ }
+
+ private static string GetRoot(string html)
+ {
+ Regex regex = new Regex(@"<(\w+)[\s>]");
+ Match match = regex.Match(html);
+ Assert.True(match.Success, "Could not determine root element");
+ Assert.True(match.Groups.Count > 1, "Could not determine root element");
+ return match.Groups[1].Value;
+ }
+
+ private static void Validate1_0(string html)
+ {
+ XmlReaderSettings settings = new XmlReaderSettings();
+ settings.DtdProcessing = DtdProcessing.Parse;
+ settings.XmlResolver = new XmlPreloadedResolver(XmlKnownDtds.Xhtml10);
+
+ Validate(settings, html);
+ }
+
+ private static void Validate1_1(string root, string html)
+ {
+ var settings = new XmlReaderSettings { DtdProcessing = DtdProcessing.Parse, ValidationType = ValidationType.DTD, XmlResolver = new AssemblyResourceXmlResolver() };
+
+ string docType = String.Format(DOCTYPE_XHTML1_1, root);
+ Validate(settings, docType + html);
+ }
+
+ private static void Validate(XmlReaderSettings settings, string html)
+ {
+ using (StringReader sr = new StringReader(html))
+ {
+ using (XmlReader reader = XmlReader.Create(sr, settings))
+ {
+ while (reader.Read())
+ {
+ // XHTML element and attribute names must be lowercase, since XML is case sensitive.
+ // The W3C validator detects this, but we must manually check since the XmlReader does not.
+ // See: http://www.w3.org/TR/xhtml1/#h-4.2
+ if (reader.NodeType == XmlNodeType.Element)
+ {
+ string element = reader.Name;
+ Assert.True(element == element.ToLowerInvariant());
+ if (reader.HasAttributes)
+ {
+ for (int i = 0; i < reader.AttributeCount; i++)
+ {
+ reader.MoveToAttribute(i);
+ string attribute = reader.Name;
+ Assert.True(attribute == attribute.ToLowerInvariant());
+ }
+ // move back to element node
+ reader.MoveToElement();
+ }
+ }
+ }
+ }
+ }
+ }
+
+ private class AssemblyResourceXmlResolver : XmlResolver
+ {
+ public override ICredentials Credentials
+ {
+ set { throw new NotSupportedException(); }
+ }
+
+ public override object GetEntity(Uri absoluteUri, string role, Type ofObjectToReturn)
+ {
+ Assembly assembly = typeof(XhtmlAssert).Assembly;
+ return assembly.GetManifestResourceStream("System.Web.Helpers.Test.TestFiles.xhtml11-flat.dtd");
+ }
+ }
+ }
+}
diff --git a/test/System.Web.Helpers.Test/packages.config b/test/System.Web.Helpers.Test/packages.config
new file mode 100644
index 00000000..d5aa6401
--- /dev/null
+++ b/test/System.Web.Helpers.Test/packages.config
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<packages>
+ <package id="Moq" version="4.0.10827" />
+ <package id="xunit" version="1.9.0.1566" />
+ <package id="xunit.extensions" version="1.9.0.1566" />
+</packages> \ No newline at end of file
diff --git a/test/System.Web.Http.Common.Test/ErrorTests.cs b/test/System.Web.Http.Common.Test/ErrorTests.cs
new file mode 100644
index 00000000..09c187fb
--- /dev/null
+++ b/test/System.Web.Http.Common.Test/ErrorTests.cs
@@ -0,0 +1,21 @@
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Http.Common
+{
+ public class ErrorTests
+ {
+ [Fact]
+ public void Format()
+ {
+ // Arrange
+ string expected = "The formatted message";
+
+ // Act
+ string actual = Error.Format("The {0} message", "formatted");
+
+ // Assert
+ Assert.Equal(expected, actual);
+ }
+ }
+}
diff --git a/test/System.Web.Http.Common.Test/HttpRequestMessageCommonExtensionsTest.cs b/test/System.Web.Http.Common.Test/HttpRequestMessageCommonExtensionsTest.cs
new file mode 100644
index 00000000..63c36412
--- /dev/null
+++ b/test/System.Web.Http.Common.Test/HttpRequestMessageCommonExtensionsTest.cs
@@ -0,0 +1,57 @@
+using System.Net;
+using System.Net.Http;
+using Microsoft.TestCommon;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Http
+{
+ public class HttpRequestMessageCommonExtensionsTest
+ {
+ [Fact]
+ public void IsCorrectType()
+ {
+ Assert.Type.HasProperties(typeof(HttpRequestMessageCommonExtensions), TypeAssert.TypeProperties.IsStatic | TypeAssert.TypeProperties.IsPublicVisibleClass);
+ }
+
+ [Fact]
+ public void CreateResponseThrowsOnNull()
+ {
+ Assert.ThrowsArgumentNull(() => HttpRequestMessageCommonExtensions.CreateResponse(null), "request");
+ }
+
+ [Fact]
+ public void CreateResponseWithStatusThrowsOnNull()
+ {
+ Assert.ThrowsArgumentNull(() => HttpRequestMessageCommonExtensions.CreateResponse(null, HttpStatusCode.OK), "request");
+ }
+
+ [Fact]
+ public void CreateResponse()
+ {
+ // Arrange
+ HttpRequestMessage request = new HttpRequestMessage();
+
+ // Act
+ HttpResponseMessage response = request.CreateResponse();
+
+ // Assert
+ Assert.Same(request, response.RequestMessage);
+ Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+ }
+
+ [Fact]
+ public void CreateResponseWithStatus()
+ {
+ // Arrange
+ HttpRequestMessage request = new HttpRequestMessage();
+
+ // Act
+ HttpResponseMessage response = request.CreateResponse(HttpStatusCode.NotImplemented);
+
+ // Assert
+ Assert.Same(request, response.RequestMessage);
+ Assert.Equal(HttpStatusCode.NotImplemented, response.StatusCode);
+ }
+ }
+}
diff --git a/test/System.Web.Http.Common.Test/System.Web.Http.Common.Test.csproj b/test/System.Web.Http.Common.Test/System.Web.Http.Common.Test.csproj
new file mode 100644
index 00000000..54cf1980
--- /dev/null
+++ b/test/System.Web.Http.Common.Test/System.Web.Http.Common.Test.csproj
@@ -0,0 +1,82 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory),Runtime.sln))\tools\WebStack.settings.targets" />
+ <PropertyGroup>
+ <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+ <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+ <ProductVersion>9.0.30729</ProductVersion>
+ <SchemaVersion>2.0</SchemaVersion>
+ <ProjectGuid>{7FB5C0C0-5223-4C79-A8DA-D2A0F264A478}</ProjectGuid>
+ <OutputType>Library</OutputType>
+ <AppDesignerFolder>Properties</AppDesignerFolder>
+ <RootNamespace>System.Web.Http.Common</RootNamespace>
+ <AssemblyName>System.Web.Http.Common.Test</AssemblyName>
+ <FileAlignment>512</FileAlignment>
+ <ProjectTypeGuids>{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
+ <ErrorReport>prompt</ErrorReport>
+ <WarningLevel>4</WarningLevel>
+ <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
+ <DebugSymbols>true</DebugSymbols>
+ <DebugType>full</DebugType>
+ <Optimize>false</Optimize>
+ <OutputPath>$(WebStackRootPath)\bin\Debug\Test\</OutputPath>
+ <DefineConstants>TRACE;DEBUG</DefineConstants>
+ <CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
+ <DebugType>pdbonly</DebugType>
+ <Optimize>true</Optimize>
+ <OutputPath>$(WebStackRootPath)\bin\Release\Test\</OutputPath>
+ <DefineConstants>TRACE</DefineConstants>
+ <CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)' == 'CodeCoverage' ">
+ <DebugSymbols>true</DebugSymbols>
+ <DebugType>full</DebugType>
+ <Optimize>false</Optimize>
+ <OutputPath>$(WebStackRootPath)\bin\CodeCoverage\Test\</OutputPath>
+ <DefineConstants>TRACE;DEBUG</DefineConstants>
+ <CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
+ </PropertyGroup>
+ <ItemGroup>
+ <Reference Include="Moq, Version=4.0.10827.0, Culture=neutral, PublicKeyToken=69f491c39445e920, processorArchitecture=MSIL">
+ <HintPath>..\..\packages\Moq.4.0.10827\lib\NET40\Moq.dll</HintPath>
+ </Reference>
+ <Reference Include="System" />
+ <Reference Include="System.Core" />
+ <Reference Include="System.Net.Http">
+ <HintPath>..\..\packages\Microsoft.Net.Http.2.0.20302.1\lib\net40\System.Net.Http.dll</HintPath>
+ </Reference>
+ <Reference Include="System.Net.Http.WebRequest">
+ <HintPath>..\..\packages\Microsoft.Net.Http.2.0.20302.1\lib\net40\System.Net.Http.WebRequest.dll</HintPath>
+ </Reference>
+ <Reference Include="xunit">
+ <HintPath>..\..\packages\xunit.1.9.0.1566\lib\xunit.dll</HintPath>
+ </Reference>
+ <Reference Include="xunit.extensions">
+ <HintPath>..\..\packages\xunit.extensions.1.9.0.1566\lib\xunit.extensions.dll</HintPath>
+ </Reference>
+ </ItemGroup>
+ <ItemGroup>
+ <Compile Include="ErrorTests.cs" />
+ <Compile Include="HttpRequestMessageCommonExtensionsTest.cs" />
+ <Compile Include="TaskHelpersExtensionsTest.cs" />
+ <Compile Include="TaskHelpersTest.cs" />
+ </ItemGroup>
+ <ItemGroup>
+ <ProjectReference Include="..\..\src\System.Web.Http.Common\System.Web.Http.Common.csproj">
+ <Project>{03A5E5F2-2E23-48F2-ABCC-6C41BAC9AC02}</Project>
+ <Name>System.Web.Http.Common</Name>
+ </ProjectReference>
+ <ProjectReference Include="..\Microsoft.TestCommon\Microsoft.TestCommon.csproj">
+ <Project>{FCCC4CB7-BAF7-4A57-9F89-E5766FE536C0}</Project>
+ <Name>Microsoft.TestCommon</Name>
+ </ProjectReference>
+ </ItemGroup>
+ <ItemGroup>
+ <None Include="packages.config" />
+ </ItemGroup>
+ <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
+</Project> \ No newline at end of file
diff --git a/test/System.Web.Http.Common.Test/TaskHelpersExtensionsTest.cs b/test/System.Web.Http.Common.Test/TaskHelpersExtensionsTest.cs
new file mode 100644
index 00000000..d788a1cd
--- /dev/null
+++ b/test/System.Web.Http.Common.Test/TaskHelpersExtensionsTest.cs
@@ -0,0 +1,2169 @@
+using Microsoft.TestCommon;
+using Moq;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+// There are several tests which need unreachable code (return after throw) to guarantee the correct lambda signature
+#pragma warning disable 0162
+
+namespace System.Threading.Tasks
+{
+ public class TaskHelpersExtensionsTest
+ {
+ // -----------------------------------------------------------------
+ // Task.Catch(Func<Exception, Task>)
+
+ [Fact]
+ public Task Catch_NoInputValue_CatchesException_Handled()
+ {
+ // Arrange
+ return TaskHelpers.FromError(new InvalidOperationException())
+
+ // Act
+ .Catch(ex =>
+ {
+ Assert.NotNull(ex);
+ Assert.IsType<InvalidOperationException>(ex);
+ return TaskHelpers.Completed();
+ })
+
+ // Assert
+ .ContinueWith(task =>
+ {
+ Assert.Equal(TaskStatus.RanToCompletion, task.Status);
+ });
+ }
+
+ [Fact]
+ public Task Catch_NoInputValue_CatchesException_Rethrow()
+ {
+ // Arrange
+ return TaskHelpers.FromError(new InvalidOperationException())
+
+ // Act
+ .Catch(ex =>
+ {
+ return TaskHelpers.FromError(ex);
+ })
+
+ // Assert
+ .ContinueWith(task =>
+ {
+ Assert.Equal(TaskStatus.Faulted, task.Status);
+ Assert.IsType<InvalidOperationException>(task.Exception.GetBaseException());
+ });
+ }
+
+ [Fact]
+ public Task Catch_NoInputValue_ReturningNullFromCatchIsProhibited()
+ {
+ // Arrange
+ return TaskHelpers.FromError(new Exception())
+
+ // Act
+ .Catch(ex =>
+ {
+ return null;
+ })
+
+ // Assert
+ .ContinueWith(task =>
+ {
+ Assert.Equal(TaskStatus.Faulted, task.Status);
+ Assert.IsException<InvalidOperationException>(task.Exception, "You cannot return null from the TaskHelpersExtensions.Catch continuation. You must return a valid task or throw an exception.");
+ });
+ }
+
+ [Fact, PreserveSyncContext]
+ public Task Catch_NoInputValue_CompletedTaskOfSuccess_DoesNotRunContinuationAndDoesNotSwitchContexts()
+ {
+ // Arrange
+ bool ranContinuation = false;
+ var syncContext = new Mock<SynchronizationContext> { CallBase = true };
+ SynchronizationContext.SetSynchronizationContext(syncContext.Object);
+
+ return TaskHelpers.Completed()
+
+ // Act
+ .Catch(ex =>
+ {
+ ranContinuation = true;
+ return TaskHelpers.Completed();
+ })
+
+ // Assert
+ .ContinueWith(task =>
+ {
+ Assert.False(ranContinuation);
+ syncContext.Verify(sc => sc.Post(It.IsAny<SendOrPostCallback>(), null), Times.Never());
+ });
+ }
+
+ [Fact, PreserveSyncContext]
+ public Task Catch_NoInputValue_CompletedTaskOfCancellation_DoesNotRunContinuationAndDoesNotSwitchContexts()
+ {
+ // Arrange
+ bool ranContinuation = false;
+ var syncContext = new Mock<SynchronizationContext> { CallBase = true };
+ SynchronizationContext.SetSynchronizationContext(syncContext.Object);
+
+ return TaskHelpers.Canceled()
+
+ // Act
+ .Catch(ex =>
+ {
+ ranContinuation = true;
+ return TaskHelpers.Completed();
+ })
+
+ // Assert
+ .ContinueWith(task =>
+ {
+ Assert.False(ranContinuation);
+ syncContext.Verify(sc => sc.Post(It.IsAny<SendOrPostCallback>(), null), Times.Never());
+ });
+ }
+
+ [Fact, PreserveSyncContext]
+ public Task Catch_NoInputValue_CompletedTaskOfFault_RunsOnSameThreadAndDoesNotPostToSynchronizationContext()
+ {
+ // Arrange
+ int outerThreadId = Thread.CurrentThread.ManagedThreadId;
+ int innerThreadId = Int32.MinValue;
+ Exception thrownException = new Exception();
+ Exception caughtException = null;
+ var syncContext = new Mock<SynchronizationContext> { CallBase = true };
+ SynchronizationContext.SetSynchronizationContext(syncContext.Object);
+
+ return TaskHelpers.FromError(thrownException)
+
+ // Act
+ .Catch(ex =>
+ {
+ caughtException = ex;
+ innerThreadId = Thread.CurrentThread.ManagedThreadId;
+ return TaskHelpers.Completed();
+ })
+
+ // Assert
+ .ContinueWith(task =>
+ {
+ Assert.Same(thrownException, caughtException);
+ Assert.Equal(innerThreadId, outerThreadId);
+ syncContext.Verify(sc => sc.Post(It.IsAny<SendOrPostCallback>(), null), Times.Never());
+ });
+ }
+
+ [Fact, PreserveSyncContext]
+ public Task Catch_NoInputValue_IncompleteTaskOfSuccess_DoesNotRunContinuationAndDoesNotSwitchContexts()
+ {
+ // Arrange
+ bool ranContinuation = false;
+ var syncContext = new Mock<SynchronizationContext> { CallBase = true };
+ SynchronizationContext.SetSynchronizationContext(syncContext.Object);
+
+ Task incompleteTask = new Task(() => { });
+
+ // Act
+ Task resultTask = incompleteTask.Catch(ex =>
+ {
+ ranContinuation = true;
+ return TaskHelpers.Completed();
+ });
+
+ // Assert
+ incompleteTask.Start();
+
+ return resultTask.ContinueWith(task =>
+ {
+ Assert.False(ranContinuation);
+ syncContext.Verify(sc => sc.Post(It.IsAny<SendOrPostCallback>(), null), Times.Never());
+ });
+ }
+
+ [Fact, PreserveSyncContext]
+ public Task Catch_NoInputValue_IncompleteTaskOfCancellation_DoesNotRunContinuationAndDoesNotSwitchContexts()
+ {
+ // Arrange
+ bool ranContinuation = false;
+ var syncContext = new Mock<SynchronizationContext> { CallBase = true };
+ SynchronizationContext.SetSynchronizationContext(syncContext.Object);
+
+ Task incompleteTask = new Task(() => { });
+ Task resultTask = incompleteTask.ContinueWith(task => TaskHelpers.Canceled()).Unwrap();
+
+ // Act
+ resultTask = resultTask.Catch(ex =>
+ {
+ ranContinuation = true;
+ return TaskHelpers.Completed();
+ });
+
+ // Assert
+ incompleteTask.Start();
+
+ return resultTask.ContinueWith(task =>
+ {
+ Assert.False(ranContinuation);
+ syncContext.Verify(sc => sc.Post(It.IsAny<SendOrPostCallback>(), null), Times.Never());
+ });
+ }
+
+ [Fact, PreserveSyncContext]
+ public Task Catch_NoInputValue_IncompleteTaskOfFault_RunsOnNewThreadAndPostsToSynchronizationContext()
+ {
+ // Arrange
+ int outerThreadId = Thread.CurrentThread.ManagedThreadId;
+ int innerThreadId = Int32.MinValue;
+ Exception thrownException = new Exception();
+ Exception caughtException = null;
+ var syncContext = new Mock<SynchronizationContext> { CallBase = true };
+ SynchronizationContext.SetSynchronizationContext(syncContext.Object);
+
+ Task incompleteTask = new Task(() => { throw thrownException; });
+
+ // Act
+ Task resultTask = incompleteTask.Catch(ex =>
+ {
+ caughtException = ex;
+ innerThreadId = Thread.CurrentThread.ManagedThreadId;
+ return TaskHelpers.Completed();
+ });
+
+ // Assert
+ incompleteTask.Start();
+
+ return resultTask.ContinueWith(task =>
+ {
+ Assert.Same(thrownException, caughtException);
+ Assert.NotEqual(innerThreadId, outerThreadId);
+ syncContext.Verify(sc => sc.Post(It.IsAny<SendOrPostCallback>(), null), Times.Once());
+ });
+ }
+
+ // -----------------------------------------------------------------
+ // Task<T>.Catch(Func<Exception, Task<T>>)
+
+ [Fact]
+ public Task Catch_WithInputValue_CatchesException_Handled()
+ {
+ // Arrange
+ return TaskHelpers.FromError<int>(new InvalidOperationException())
+
+ // Act
+ .Catch(ex =>
+ {
+ Assert.NotNull(ex);
+ Assert.IsType<InvalidOperationException>(ex);
+ return TaskHelpers.FromResult(42);
+ })
+
+ // Assert
+ .ContinueWith(task =>
+ {
+ Assert.Equal(TaskStatus.RanToCompletion, task.Status);
+ });
+ }
+
+ [Fact]
+ public Task Catch_WithInputValue_CatchesException_Rethrow()
+ {
+ // Arrange
+ return TaskHelpers.FromError<int>(new InvalidOperationException())
+
+ // Act
+ .Catch(ex =>
+ {
+ return TaskHelpers.FromError<int>(ex);
+ })
+
+ // Assert
+ .ContinueWith(task =>
+ {
+ Assert.Equal(TaskStatus.Faulted, task.Status);
+ Assert.IsType<InvalidOperationException>(task.Exception.GetBaseException());
+ });
+ }
+
+ [Fact]
+ public Task Catch_WithInputValue_ReturningNullFromCatchIsProhibited()
+ {
+ // Arrange
+ return TaskHelpers.FromError<int>(new Exception())
+
+ // Act
+ .Catch(ex =>
+ {
+ return null;
+ })
+
+ // Assert
+ .ContinueWith(task =>
+ {
+ Assert.Equal(TaskStatus.Faulted, task.Status);
+ Assert.IsException<InvalidOperationException>(task.Exception, "You cannot return null from the TaskHelpersExtensions.Catch continuation. You must return a valid task or throw an exception.");
+ });
+ }
+
+ [Fact, PreserveSyncContext]
+ public Task Catch_WithInputValue_CompletedTaskOfSuccess_DoesNotRunContinuationAndDoesNotSwitchContexts()
+ {
+ // Arrange
+ bool ranContinuation = false;
+ var syncContext = new Mock<SynchronizationContext> { CallBase = true };
+ SynchronizationContext.SetSynchronizationContext(syncContext.Object);
+
+ return TaskHelpers.FromResult(21)
+
+ // Act
+ .Catch(ex =>
+ {
+ ranContinuation = true;
+ return TaskHelpers.FromResult(42);
+ })
+
+ // Assert
+ .ContinueWith(task =>
+ {
+ Assert.False(ranContinuation);
+ syncContext.Verify(sc => sc.Post(It.IsAny<SendOrPostCallback>(), null), Times.Never());
+ });
+ }
+
+ [Fact, PreserveSyncContext]
+ public Task Catch_WithInputValue_CompletedTaskOfCancellation_DoesNotRunContinuationAndDoesNotSwitchContexts()
+ {
+ // Arrange
+ bool ranContinuation = false;
+ var syncContext = new Mock<SynchronizationContext> { CallBase = true };
+ SynchronizationContext.SetSynchronizationContext(syncContext.Object);
+
+ return TaskHelpers.Canceled<int>()
+
+ // Act
+ .Catch(ex =>
+ {
+ ranContinuation = true;
+ return TaskHelpers.FromResult(42);
+ })
+
+ // Assert
+ .ContinueWith(task =>
+ {
+ Assert.False(ranContinuation);
+ syncContext.Verify(sc => sc.Post(It.IsAny<SendOrPostCallback>(), null), Times.Never());
+ });
+ }
+
+ [Fact, PreserveSyncContext]
+ public Task Catch_WithInputValue_CompletedTaskOfFault_RunsOnSameThreadAndDoesNotPostToSynchronizationContext()
+ {
+ // Arrange
+ int outerThreadId = Thread.CurrentThread.ManagedThreadId;
+ int innerThreadId = Int32.MinValue;
+ Exception thrownException = new Exception();
+ Exception caughtException = null;
+ var syncContext = new Mock<SynchronizationContext> { CallBase = true };
+ SynchronizationContext.SetSynchronizationContext(syncContext.Object);
+
+ return TaskHelpers.FromError<int>(thrownException)
+
+ // Act
+ .Catch(ex =>
+ {
+ caughtException = ex;
+ innerThreadId = Thread.CurrentThread.ManagedThreadId;
+ return TaskHelpers.FromResult(42);
+ })
+
+ // Assert
+ .ContinueWith(task =>
+ {
+ Assert.Same(thrownException, caughtException);
+ Assert.Equal(innerThreadId, outerThreadId);
+ syncContext.Verify(sc => sc.Post(It.IsAny<SendOrPostCallback>(), null), Times.Never());
+ });
+ }
+
+ [Fact, PreserveSyncContext]
+ public Task Catch_WithInputValue_IncompleteTaskOfSuccess_DoesNotRunContinuationAndDoesNotSwitchContexts()
+ {
+ // Arrange
+ bool ranContinuation = false;
+ var syncContext = new Mock<SynchronizationContext> { CallBase = true };
+ SynchronizationContext.SetSynchronizationContext(syncContext.Object);
+
+ Task<int> incompleteTask = new Task<int>(() => 42);
+
+ // Act
+ Task resultTask = incompleteTask.Catch(ex =>
+ {
+ ranContinuation = true;
+ return TaskHelpers.Completed();
+ });
+
+ // Assert
+ incompleteTask.Start();
+
+ return resultTask.ContinueWith(task =>
+ {
+ Assert.False(ranContinuation);
+ syncContext.Verify(sc => sc.Post(It.IsAny<SendOrPostCallback>(), null), Times.Never());
+ });
+ }
+
+ [Fact, PreserveSyncContext]
+ public Task Catch_WithInputValue_IncompleteTaskOfCancellation_DoesNotRunContinuationAndDoesNotSwitchContexts()
+ {
+ // Arrange
+ bool ranContinuation = false;
+ var syncContext = new Mock<SynchronizationContext> { CallBase = true };
+ SynchronizationContext.SetSynchronizationContext(syncContext.Object);
+
+ Task<int> incompleteTask = new Task<int>(() => 42);
+ Task resultTask = incompleteTask.ContinueWith(task => TaskHelpers.Canceled<int>()).Unwrap();
+
+ // Act
+ resultTask = resultTask.Catch(ex =>
+ {
+ ranContinuation = true;
+ return TaskHelpers.Completed();
+ });
+
+ // Assert
+ incompleteTask.Start();
+
+ return resultTask.ContinueWith(task =>
+ {
+ Assert.False(ranContinuation);
+ syncContext.Verify(sc => sc.Post(It.IsAny<SendOrPostCallback>(), null), Times.Never());
+ });
+ }
+
+ [Fact, PreserveSyncContext]
+ public Task Catch_WithInputValue_IncompleteTaskOfFault_RunsOnNewThreadAndPostsToSynchronizationContext()
+ {
+ // Arrange
+ int outerThreadId = Thread.CurrentThread.ManagedThreadId;
+ int innerThreadId = Int32.MinValue;
+ Exception thrownException = new Exception();
+ Exception caughtException = null;
+ var syncContext = new Mock<SynchronizationContext> { CallBase = true };
+ SynchronizationContext.SetSynchronizationContext(syncContext.Object);
+
+ Task<int> incompleteTask = new Task<int>(() => { throw thrownException; });
+
+ // Act
+ Task resultTask = incompleteTask.Catch(ex =>
+ {
+ caughtException = ex;
+ innerThreadId = Thread.CurrentThread.ManagedThreadId;
+ return TaskHelpers.FromResult(42);
+ });
+
+ // Assert
+ incompleteTask.Start();
+
+ return resultTask.ContinueWith(task =>
+ {
+ Assert.Same(thrownException, caughtException);
+ Assert.NotEqual(innerThreadId, outerThreadId);
+ syncContext.Verify(sc => sc.Post(It.IsAny<SendOrPostCallback>(), null), Times.Once());
+ });
+ }
+
+ // -----------------------------------------------------------------
+ // Task.CopyResultToCompletionSource(Task)
+
+ [Fact]
+ public Task CopyResultToCompletionSource_NoInputValue_SuccessfulTask()
+ {
+ // Arrange
+ var tcs = new TaskCompletionSource<object>();
+ var expectedResult = new object();
+
+ return TaskHelpers.Completed()
+
+ // Act
+ .CopyResultToCompletionSource(tcs, expectedResult)
+
+ // Assert
+ .ContinueWith(task =>
+ {
+ Assert.Equal(TaskStatus.RanToCompletion, task.Status); // Outer task always runs to completion
+ Assert.Equal(TaskStatus.RanToCompletion, tcs.Task.Status);
+ Assert.Same(expectedResult, tcs.Task.Result);
+ });
+ }
+
+ [Fact]
+ public Task CopyResultToCompletionSource_NoInputValue_FaultedTask()
+ {
+ // Arrange
+ var tcs = new TaskCompletionSource<object>();
+ var expectedException = new NotImplementedException();
+
+ return TaskHelpers.FromError(expectedException)
+
+ // Act
+ .CopyResultToCompletionSource(tcs, completionResult: null)
+
+ // Assert
+ .ContinueWith(task =>
+ {
+ Assert.Equal(TaskStatus.RanToCompletion, task.Status); // Outer task always runs to completion
+ Assert.Equal(TaskStatus.Faulted, tcs.Task.Status);
+ Assert.Same(expectedException, tcs.Task.Exception.GetBaseException());
+ });
+ }
+
+ [Fact]
+ public Task CopyResultToCompletionSource_NoInputValue_Canceled()
+ {
+ // Arrange
+ var tcs = new TaskCompletionSource<object>();
+
+ return TaskHelpers.Canceled()
+
+ // Act
+ .CopyResultToCompletionSource(tcs, completionResult: null)
+
+ // Assert
+ .ContinueWith(task =>
+ {
+ Assert.Equal(TaskStatus.RanToCompletion, task.Status); // Outer task always runs to completion
+ Assert.Equal(TaskStatus.Canceled, tcs.Task.Status);
+ });
+ }
+
+ // -----------------------------------------------------------------
+ // Task.CopyResultToCompletionSource(Task<T>)
+
+ [Fact]
+ public Task CopyResultToCompletionSource_WithInputValue_SuccessfulTask()
+ {
+ // Arrange
+ var tcs = new TaskCompletionSource<int>();
+
+ return TaskHelpers.FromResult(42)
+
+ // Act
+ .CopyResultToCompletionSource(tcs)
+
+ // Assert
+ .ContinueWith(task =>
+ {
+ Assert.Equal(TaskStatus.RanToCompletion, task.Status); // Outer task always runs to completion
+ Assert.Equal(TaskStatus.RanToCompletion, tcs.Task.Status);
+ Assert.Equal(42, tcs.Task.Result);
+ });
+ }
+
+ [Fact]
+ public Task CopyResultToCompletionSource_WithInputValue_FaultedTask()
+ {
+ // Arrange
+ var tcs = new TaskCompletionSource<int>();
+ var expectedException = new NotImplementedException();
+
+ return TaskHelpers.FromError<int>(expectedException)
+
+ // Act
+ .CopyResultToCompletionSource(tcs)
+
+ // Assert
+ .ContinueWith(task =>
+ {
+ Assert.Equal(TaskStatus.RanToCompletion, task.Status); // Outer task always runs to completion
+ Assert.Equal(TaskStatus.Faulted, tcs.Task.Status);
+ Assert.Same(expectedException, tcs.Task.Exception.GetBaseException());
+ });
+ }
+
+ [Fact]
+ public Task CopyResultToCompletionSource_WithInputValue_Canceled()
+ {
+ // Arrange
+ var tcs = new TaskCompletionSource<int>();
+
+ return TaskHelpers.Canceled<int>()
+
+ // Act
+ .CopyResultToCompletionSource(tcs)
+
+ // Assert
+ .ContinueWith(task =>
+ {
+ Assert.Equal(TaskStatus.RanToCompletion, task.Status); // Outer task always runs to completion
+ Assert.Equal(TaskStatus.Canceled, tcs.Task.Status);
+ });
+ }
+
+ // -----------------------------------------------------------------
+ // Task.Finally(Action)
+
+ [Fact, PreserveSyncContext]
+ public Task Finally_NoInputValue_CompletedTaskOfSuccess_RunsOnSameThreadAndDoesNotPostToSynchronizationContext()
+ {
+ // Arrange
+ int originalThreadId = Thread.CurrentThread.ManagedThreadId;
+ int callbackThreadId = Int32.MinValue;
+ var syncContext = new Mock<SynchronizationContext> { CallBase = true };
+ SynchronizationContext.SetSynchronizationContext(syncContext.Object);
+
+ return TaskHelpers.Completed()
+
+ // Act
+ .Finally(() =>
+ {
+ callbackThreadId = Thread.CurrentThread.ManagedThreadId;
+ })
+
+ // Assert
+ .ContinueWith(task =>
+ {
+ Assert.Equal(originalThreadId, callbackThreadId);
+ syncContext.Verify(sc => sc.Post(It.IsAny<SendOrPostCallback>(), null), Times.Never());
+ });
+ }
+
+ [Fact, PreserveSyncContext]
+ public Task Finally_NoInputValue_CompletedTaskOfCancellation_RunsOnSameThreadAndDoesNotPostToSynchronizationContext()
+ {
+ // Arrange
+ int originalThreadId = Thread.CurrentThread.ManagedThreadId;
+ int callbackThreadId = Int32.MinValue;
+ var syncContext = new Mock<SynchronizationContext> { CallBase = true };
+ SynchronizationContext.SetSynchronizationContext(syncContext.Object);
+
+ return TaskHelpers.Canceled()
+
+ // Act
+ .Finally(() =>
+ {
+ callbackThreadId = Thread.CurrentThread.ManagedThreadId;
+ })
+
+ // Assert
+ .ContinueWith(task =>
+ {
+ Assert.Equal(originalThreadId, callbackThreadId);
+ syncContext.Verify(sc => sc.Post(It.IsAny<SendOrPostCallback>(), null), Times.Never());
+ });
+ }
+
+ [Fact, PreserveSyncContext]
+ public Task Finally_NoInputValue_CompletedTaskOfFault_RunsOnSameThreadAndDoesNotPostToSynchronizationContext()
+ {
+ // Arrange
+ int originalThreadId = Thread.CurrentThread.ManagedThreadId;
+ int callbackThreadId = Int32.MinValue;
+ var syncContext = new Mock<SynchronizationContext> { CallBase = true };
+ SynchronizationContext.SetSynchronizationContext(syncContext.Object);
+
+ return TaskHelpers.FromError(new InvalidOperationException())
+
+ // Act
+ .Finally(() =>
+ {
+ callbackThreadId = Thread.CurrentThread.ManagedThreadId;
+ })
+
+ // Assert
+ .ContinueWith(task =>
+ {
+ var ex = task.Exception; // Observe the exception
+ Assert.Equal(originalThreadId, callbackThreadId);
+ syncContext.Verify(sc => sc.Post(It.IsAny<SendOrPostCallback>(), null), Times.Never());
+ });
+ }
+
+ [Fact, PreserveSyncContext]
+ public Task Finally_NoInputValue_IncompleteTaskOfSuccess_RunsOnNewThreadAndPostsContinuationToSynchronizationContext()
+ {
+ // Arrange
+ int originalThreadId = Thread.CurrentThread.ManagedThreadId;
+ int callbackThreadId = Int32.MinValue;
+ var syncContext = new Mock<SynchronizationContext> { CallBase = true };
+ SynchronizationContext.SetSynchronizationContext(syncContext.Object);
+
+ Task incompleteTask = new Task(() => { });
+
+ // Act
+ Task resultTask = incompleteTask.Finally(() =>
+ {
+ callbackThreadId = Thread.CurrentThread.ManagedThreadId;
+ });
+
+ // Assert
+ incompleteTask.Start();
+
+ return resultTask.ContinueWith(task =>
+ {
+ Assert.NotEqual(originalThreadId, callbackThreadId);
+ syncContext.Verify(sc => sc.Post(It.IsAny<SendOrPostCallback>(), null), Times.Once());
+ });
+ }
+
+ [Fact, PreserveSyncContext]
+ public Task Finally_NoInputValue_IncompleteTaskOfCancellation_RunsOnNewThreadAndPostsContinuationToSynchronizationContext()
+ {
+ // Arrange
+ int originalThreadId = Thread.CurrentThread.ManagedThreadId;
+ int callbackThreadId = Int32.MinValue;
+ var syncContext = new Mock<SynchronizationContext> { CallBase = true };
+ SynchronizationContext.SetSynchronizationContext(syncContext.Object);
+
+ Task incompleteTask = new Task(() => { });
+ Task resultTask = incompleteTask.ContinueWith(task => TaskHelpers.Canceled()).Unwrap();
+
+ // Act
+ resultTask = resultTask.Finally(() =>
+ {
+ callbackThreadId = Thread.CurrentThread.ManagedThreadId;
+ });
+
+ // Assert
+ incompleteTask.Start();
+
+ return resultTask.ContinueWith(task =>
+ {
+ Assert.NotEqual(originalThreadId, callbackThreadId);
+ syncContext.Verify(sc => sc.Post(It.IsAny<SendOrPostCallback>(), null), Times.Once());
+ });
+ }
+
+ [Fact, PreserveSyncContext]
+ public Task Finally_NoInputValue_IncompleteTaskOfFault_RunsOnNewThreadAndPostsContinuationToSynchronizationContext()
+ {
+ // Arrange
+ int originalThreadId = Thread.CurrentThread.ManagedThreadId;
+ int callbackThreadId = Int32.MinValue;
+ var syncContext = new Mock<SynchronizationContext> { CallBase = true };
+ SynchronizationContext.SetSynchronizationContext(syncContext.Object);
+
+ Task incompleteTask = new Task(() => { throw new InvalidOperationException(); });
+
+ // Act
+ Task resultTask = incompleteTask.Finally(() =>
+ {
+ callbackThreadId = Thread.CurrentThread.ManagedThreadId;
+ });
+
+ // Assert
+ incompleteTask.Start();
+
+ return resultTask.ContinueWith(task =>
+ {
+ var ex = task.Exception; // Observe the exception
+ Assert.NotEqual(originalThreadId, callbackThreadId);
+ syncContext.Verify(sc => sc.Post(It.IsAny<SendOrPostCallback>(), null), Times.Once());
+ });
+ }
+
+ // -----------------------------------------------------------------
+ // Task<T>.Finally(Action)
+
+ [Fact, PreserveSyncContext]
+ public Task Finally_WithInputValue_CompletedTaskOfSuccess_RunsOnSameThreadAndDoesNotPostToSynchronizationContext()
+ {
+ // Arrange
+ int originalThreadId = Thread.CurrentThread.ManagedThreadId;
+ int callbackThreadId = Int32.MinValue;
+ var syncContext = new Mock<SynchronizationContext> { CallBase = true };
+ SynchronizationContext.SetSynchronizationContext(syncContext.Object);
+
+ return TaskHelpers.FromResult(21)
+
+ // Act
+ .Finally(() =>
+ {
+ callbackThreadId = Thread.CurrentThread.ManagedThreadId;
+ })
+
+ // Assert
+ .ContinueWith(task =>
+ {
+ Assert.Equal(21, task.Result);
+ Assert.Equal(originalThreadId, callbackThreadId);
+ syncContext.Verify(sc => sc.Post(It.IsAny<SendOrPostCallback>(), null), Times.Never());
+ });
+ }
+
+ [Fact, PreserveSyncContext]
+ public Task Finally_WithInputValue_CompletedTaskOfCancellation_RunsOnSameThreadAndDoesNotPostToSynchronizationContext()
+ {
+ // Arrange
+ int originalThreadId = Thread.CurrentThread.ManagedThreadId;
+ int callbackThreadId = Int32.MinValue;
+ var syncContext = new Mock<SynchronizationContext> { CallBase = true };
+ SynchronizationContext.SetSynchronizationContext(syncContext.Object);
+
+ return TaskHelpers.Canceled<int>()
+
+ // Act
+ .Finally(() =>
+ {
+ callbackThreadId = Thread.CurrentThread.ManagedThreadId;
+ })
+
+ // Assert
+ .ContinueWith(task =>
+ {
+ Assert.Equal(originalThreadId, callbackThreadId);
+ syncContext.Verify(sc => sc.Post(It.IsAny<SendOrPostCallback>(), null), Times.Never());
+ });
+ }
+
+ [Fact, PreserveSyncContext]
+ public Task Finally_WithInputValue_CompletedTaskOfFault_RunsOnSameThreadAndDoesNotPostToSynchronizationContext()
+ {
+ // Arrange
+ int originalThreadId = Thread.CurrentThread.ManagedThreadId;
+ int callbackThreadId = Int32.MinValue;
+ var syncContext = new Mock<SynchronizationContext> { CallBase = true };
+ SynchronizationContext.SetSynchronizationContext(syncContext.Object);
+
+ return TaskHelpers.FromError<int>(new InvalidOperationException())
+
+ // Act
+ .Finally(() =>
+ {
+ callbackThreadId = Thread.CurrentThread.ManagedThreadId;
+ })
+
+ // Assert
+ .ContinueWith(task =>
+ {
+ var ex = task.Exception; // Observe the exception
+ Assert.Equal(originalThreadId, callbackThreadId);
+ syncContext.Verify(sc => sc.Post(It.IsAny<SendOrPostCallback>(), null), Times.Never());
+ });
+ }
+
+ [Fact, PreserveSyncContext]
+ public Task Finally_WithInputValue_IncompleteTaskOfSuccess_RunsOnNewThreadAndPostsContinuationToSynchronizationContext()
+ {
+ // Arrange
+ int originalThreadId = Thread.CurrentThread.ManagedThreadId;
+ int callbackThreadId = Int32.MinValue;
+ var syncContext = new Mock<SynchronizationContext> { CallBase = true };
+ SynchronizationContext.SetSynchronizationContext(syncContext.Object);
+
+ Task incompleteTask = new Task<int>(() => 21);
+
+ // Act
+ Task resultTask = incompleteTask.Finally(() =>
+ {
+ callbackThreadId = Thread.CurrentThread.ManagedThreadId;
+ });
+
+ // Assert
+ incompleteTask.Start();
+
+ return resultTask.ContinueWith(task =>
+ {
+ Assert.NotEqual(originalThreadId, callbackThreadId);
+ syncContext.Verify(sc => sc.Post(It.IsAny<SendOrPostCallback>(), null), Times.Once());
+ });
+ }
+
+ [Fact, PreserveSyncContext]
+ public Task Finally_WithInputValue_IncompleteTaskOfCancellation_RunsOnNewThreadAndPostsContinuationToSynchronizationContext()
+ {
+ // Arrange
+ int originalThreadId = Thread.CurrentThread.ManagedThreadId;
+ int callbackThreadId = Int32.MinValue;
+ var syncContext = new Mock<SynchronizationContext> { CallBase = true };
+ SynchronizationContext.SetSynchronizationContext(syncContext.Object);
+
+ Task<int> incompleteTask = new Task<int>(() => 42);
+ Task resultTask = incompleteTask.ContinueWith(task => TaskHelpers.Canceled<int>()).Unwrap();
+
+ // Act
+ resultTask = resultTask.Finally(() =>
+ {
+ callbackThreadId = Thread.CurrentThread.ManagedThreadId;
+ });
+
+ // Assert
+ incompleteTask.Start();
+
+ return resultTask.ContinueWith(task =>
+ {
+ Assert.NotEqual(originalThreadId, callbackThreadId);
+ syncContext.Verify(sc => sc.Post(It.IsAny<SendOrPostCallback>(), null), Times.Once());
+ });
+ }
+
+ [Fact, PreserveSyncContext]
+ public Task Finally_WithInputValue_IncompleteTaskOfFault_RunsOnNewThreadAndPostsContinuationToSynchronizationContext()
+ {
+ // Arrange
+ int originalThreadId = Thread.CurrentThread.ManagedThreadId;
+ int callbackThreadId = Int32.MinValue;
+ var syncContext = new Mock<SynchronizationContext> { CallBase = true };
+ SynchronizationContext.SetSynchronizationContext(syncContext.Object);
+
+ Task<int> incompleteTask = new Task<int>(() => { throw new InvalidOperationException(); });
+
+ // Act
+ Task resultTask = incompleteTask.Finally(() =>
+ {
+ callbackThreadId = Thread.CurrentThread.ManagedThreadId;
+ });
+
+ // Assert
+ incompleteTask.Start();
+
+ return resultTask.ContinueWith(task =>
+ {
+ var ex = task.Exception; // Observe the exception
+ Assert.NotEqual(originalThreadId, callbackThreadId);
+ syncContext.Verify(sc => sc.Post(It.IsAny<SendOrPostCallback>(), null), Times.Once());
+ });
+ }
+
+ // -----------------------------------------------------------------
+ // Task Task.Then(Action)
+
+ [Fact]
+ public Task Then_NoInputValue_NoReturnValue_CallsContinuation()
+ {
+ // Arrange
+ bool ranContinuation = false;
+
+ return TaskHelpers.Completed()
+
+ // Act
+ .Then(() =>
+ {
+ ranContinuation = true;
+ })
+
+ // Assert
+ .ContinueWith(task =>
+ {
+ Assert.Equal(TaskStatus.RanToCompletion, task.Status);
+ Assert.True(ranContinuation);
+ });
+ }
+
+ [Fact]
+ public Task Then_NoInputValue_NoReturnValue_ThrownExceptionIsPropagated()
+ {
+ // Arrange
+ return TaskHelpers.Completed()
+
+ // Act
+ .Then(() =>
+ {
+ throw new NotImplementedException();
+ })
+
+ // Assert
+ .ContinueWith(task =>
+ {
+ Assert.Equal(TaskStatus.Faulted, task.Status);
+ var ex = Assert.Single(task.Exception.InnerExceptions);
+ Assert.IsType<NotImplementedException>(ex);
+ });
+ }
+
+ [Fact]
+ public Task Then_NoInputValue_NoReturnValue_FaultPreventsFurtherThenStatementsFromExecuting()
+ {
+ // Arrange
+ bool ranContinuation = false;
+
+ return TaskHelpers.FromError(new NotImplementedException())
+
+ // Act
+ .Then(() =>
+ {
+ ranContinuation = true;
+ })
+
+ // Assert
+ .ContinueWith(task =>
+ {
+ var ex = task.Exception; // Observe the exception
+ Assert.False(ranContinuation);
+ });
+ }
+
+ [Fact]
+ public Task Then_NoInputValue_NoReturnValue_ManualCancellationPreventsFurtherThenStatementsFromExecuting()
+ {
+ // Arrange
+ bool ranContinuation = false;
+
+ return TaskHelpers.Canceled()
+
+ // Act
+ .Then(() =>
+ {
+ ranContinuation = true;
+ })
+
+ // Assert
+ .ContinueWith(task =>
+ {
+ Assert.Equal(TaskStatus.Canceled, task.Status);
+ Assert.False(ranContinuation);
+ });
+ }
+
+ [Fact]
+ public Task Then_NoInputValue_NoReturnValue_TokenCancellationPreventsFurtherThenStatementsFromExecuting()
+ {
+ // Arrange
+ bool ranContinuation = false;
+ CancellationToken cancellationToken = new CancellationToken(canceled: true);
+
+ return TaskHelpers.Completed()
+
+ // Act
+ .Then(() =>
+ {
+ ranContinuation = true;
+ }, cancellationToken)
+
+ // Assert
+ .ContinueWith(task =>
+ {
+ Assert.Equal(TaskStatus.Canceled, task.Status);
+ Assert.False(ranContinuation);
+ });
+ }
+
+ [Fact, PreserveSyncContext]
+ public Task Then_NoInputValue_NoReturnValue_IncompleteTask_RunsOnNewThreadAndPostsContinuationToSynchronizationContext()
+ {
+ // Arrange
+ int originalThreadId = Thread.CurrentThread.ManagedThreadId;
+ int callbackThreadId = Int32.MinValue;
+ var syncContext = new Mock<SynchronizationContext> { CallBase = true };
+ SynchronizationContext.SetSynchronizationContext(syncContext.Object);
+
+ Task incompleteTask = new Task(() => { });
+
+ // Act
+ Task resultTask = incompleteTask.Then(() =>
+ {
+ callbackThreadId = Thread.CurrentThread.ManagedThreadId;
+ });
+
+ // Assert
+ incompleteTask.Start();
+
+ return resultTask.ContinueWith(task =>
+ {
+ Assert.NotEqual(originalThreadId, callbackThreadId);
+ syncContext.Verify(sc => sc.Post(It.IsAny<SendOrPostCallback>(), null), Times.Once());
+ });
+ }
+
+ [Fact, PreserveSyncContext]
+ public Task Then_NoInputValue_NoReturnValue_CompleteTask_RunsOnSameThreadAndDoesNotPostToSynchronizationContext()
+ {
+ // Arrange
+ int originalThreadId = Thread.CurrentThread.ManagedThreadId;
+ int callbackThreadId = Int32.MinValue;
+ var syncContext = new Mock<SynchronizationContext> { CallBase = true };
+ SynchronizationContext.SetSynchronizationContext(syncContext.Object);
+
+ return TaskHelpers.Completed()
+
+ // Act
+ .Then(() =>
+ {
+ callbackThreadId = Thread.CurrentThread.ManagedThreadId;
+ })
+
+ // Assert
+ .ContinueWith(task =>
+ {
+ Assert.Equal(originalThreadId, callbackThreadId);
+ syncContext.Verify(sc => sc.Post(It.IsAny<SendOrPostCallback>(), null), Times.Never());
+ });
+ }
+
+ // -----------------------------------------------------------------
+ // Task Task.Then(Func<Task>)
+
+ [Fact]
+ public Task Then_NoInputValue_ReturnsTask_CallsContinuation()
+ {
+ // Arrange
+ bool ranContinuation = false;
+
+ return TaskHelpers.Completed()
+
+ // Act
+ .Then(() =>
+ {
+ ranContinuation = true;
+ return TaskHelpers.Completed();
+ })
+
+ // Assert
+ .ContinueWith(task =>
+ {
+ Assert.Equal(TaskStatus.RanToCompletion, task.Status);
+ Assert.True(ranContinuation);
+ });
+ }
+
+ [Fact]
+ public Task Then_NoInputValue_ReturnsTask_ThrownExceptionIsPropagated()
+ {
+ // Arrange
+ return TaskHelpers.Completed()
+
+ // Act
+ .Then(() =>
+ {
+ throw new NotImplementedException();
+ return TaskHelpers.Completed(); // Return-after-throw to guarantee correct lambda signature
+ })
+
+ // Assert
+ .ContinueWith(task =>
+ {
+ Assert.Equal(TaskStatus.Faulted, task.Status);
+ var ex = Assert.Single(task.Exception.InnerExceptions);
+ Assert.IsType<NotImplementedException>(ex);
+ });
+ }
+
+ [Fact]
+ public Task Then_NoInputValue_ReturnsTask_FaultPreventsFurtherThenStatementsFromExecuting()
+ {
+ // Arrange
+ bool ranContinuation = false;
+
+ return TaskHelpers.FromError(new NotImplementedException())
+
+ // Act
+ .Then(() =>
+ {
+ ranContinuation = true;
+ return TaskHelpers.Completed();
+ })
+
+ // Assert
+ .ContinueWith(task =>
+ {
+ var ex = task.Exception; // Observe the exception
+ Assert.False(ranContinuation);
+ });
+ }
+
+ [Fact]
+ public Task Then_NoInputValue_ReturnsTask_ManualCancellationPreventsFurtherThenStatementsFromExecuting()
+ {
+ // Arrange
+ bool ranContinuation = false;
+
+ return TaskHelpers.Canceled()
+
+ // Act
+ .Then(() =>
+ {
+ ranContinuation = true;
+ return TaskHelpers.Completed();
+ })
+
+ // Assert
+ .ContinueWith(task =>
+ {
+ Assert.Equal(TaskStatus.Canceled, task.Status);
+ Assert.False(ranContinuation);
+ });
+ }
+
+ [Fact]
+ public Task Then_NoInputValue_ReturnsTask_TokenCancellationPreventsFurtherThenStatementsFromExecuting()
+ {
+ // Arrange
+ bool ranContinuation = false;
+ CancellationToken cancellationToken = new CancellationToken(canceled: true);
+
+ return TaskHelpers.Completed()
+
+ // Act
+ .Then(() =>
+ {
+ ranContinuation = true;
+ return TaskHelpers.Completed();
+ }, cancellationToken)
+
+ // Assert
+ .ContinueWith(task =>
+ {
+ Assert.Equal(TaskStatus.Canceled, task.Status);
+ Assert.False(ranContinuation);
+ });
+ }
+
+ [Fact, PreserveSyncContext]
+ public Task Then_NoInputValue_ReturnsTask_IncompleteTask_RunsOnNewThreadAndPostsContinuationToSynchronizationContext()
+ {
+ // Arrange
+ int originalThreadId = Thread.CurrentThread.ManagedThreadId;
+ int callbackThreadId = Int32.MinValue;
+ var syncContext = new Mock<SynchronizationContext> { CallBase = true };
+ SynchronizationContext.SetSynchronizationContext(syncContext.Object);
+
+ Task incompleteTask = new Task(() => { });
+
+ // Act
+ Task resultTask = incompleteTask.Then(() =>
+ {
+ callbackThreadId = Thread.CurrentThread.ManagedThreadId;
+ return TaskHelpers.Completed();
+ });
+
+ // Assert
+ incompleteTask.Start();
+
+ return resultTask.ContinueWith(task =>
+ {
+ Assert.NotEqual(originalThreadId, callbackThreadId);
+ syncContext.Verify(sc => sc.Post(It.IsAny<SendOrPostCallback>(), null), Times.Once());
+ });
+ }
+
+ [Fact, PreserveSyncContext]
+ public Task Then_NoInputValue_ReturnsTask_CompleteTask_RunsOnSameThreadAndDoesNotPostToSynchronizationContext()
+ {
+ // Arrange
+ int originalThreadId = Thread.CurrentThread.ManagedThreadId;
+ int callbackThreadId = Int32.MinValue;
+ var syncContext = new Mock<SynchronizationContext> { CallBase = true };
+ SynchronizationContext.SetSynchronizationContext(syncContext.Object);
+
+ return TaskHelpers.Completed()
+
+ // Act
+ .Then(() =>
+ {
+ callbackThreadId = Thread.CurrentThread.ManagedThreadId;
+ return TaskHelpers.Completed();
+ })
+
+ // Assert
+ .ContinueWith(task =>
+ {
+ Assert.Equal(originalThreadId, callbackThreadId);
+ syncContext.Verify(sc => sc.Post(It.IsAny<SendOrPostCallback>(), null), Times.Never());
+ });
+ }
+
+ // -----------------------------------------------------------------
+ // Task<T> Task.Then(Func<T>)
+
+ [Fact]
+ public Task Then_NoInputValue_WithReturnValue_CallsContinuation()
+ {
+ // Arrange
+ return TaskHelpers.Completed()
+
+ // Act
+ .Then(() =>
+ {
+ return 42;
+ })
+
+ // Assert
+ .ContinueWith(task =>
+ {
+ Assert.Equal(TaskStatus.RanToCompletion, task.Status);
+ Assert.Equal(42, task.Result);
+ });
+ }
+
+ [Fact]
+ public Task Then_NoInputValue_WithReturnValue_ThrownExceptionIsPropagated()
+ {
+ // Arrange
+ return TaskHelpers.Completed()
+
+ // Act
+ .Then(() =>
+ {
+ throw new NotImplementedException();
+ return 0; // Return-after-throw to guarantee correct lambda signature
+ })
+
+ // Assert
+ .ContinueWith(task =>
+ {
+ Assert.Equal(TaskStatus.Faulted, task.Status);
+ var ex = Assert.Single(task.Exception.InnerExceptions);
+ Assert.IsType<NotImplementedException>(ex);
+ });
+ }
+
+ [Fact]
+ public Task Then_NoInputValue_WithReturnValue_FaultPreventsFurtherThenStatementsFromExecuting()
+ {
+ // Arrange
+ bool ranContinuation = false;
+
+ return TaskHelpers.FromError(new NotImplementedException())
+
+ // Act
+ .Then(() =>
+ {
+ ranContinuation = true;
+ return 42;
+ })
+
+ // Assert
+ .ContinueWith(task =>
+ {
+ var ex = task.Exception; // Observe the exception
+ Assert.False(ranContinuation);
+ });
+ }
+
+ [Fact]
+ public Task Then_NoInputValue_WithReturnValue_ManualCancellationPreventsFurtherThenStatementsFromExecuting()
+ {
+ // Arrange
+ bool ranContinuation = false;
+
+ return TaskHelpers.Canceled()
+
+ // Act
+ .Then(() =>
+ {
+ ranContinuation = true;
+ return 42;
+ })
+
+ // Assert
+ .ContinueWith(task =>
+ {
+ Assert.Equal(TaskStatus.Canceled, task.Status);
+ Assert.False(ranContinuation);
+ });
+ }
+
+ [Fact]
+ public Task Then_NoInputValue_WithReturnValue_TokenCancellationPreventsFurtherThenStatementsFromExecuting()
+ {
+ // Arrange
+ bool ranContinuation = false;
+ CancellationToken cancellationToken = new CancellationToken(canceled: true);
+
+ return TaskHelpers.Completed()
+
+ // Act
+ .Then(() =>
+ {
+ ranContinuation = true;
+ return 42;
+ }, cancellationToken)
+
+ // Assert
+ .ContinueWith(task =>
+ {
+ Assert.Equal(TaskStatus.Canceled, task.Status);
+ Assert.False(ranContinuation);
+ });
+ }
+
+ [Fact, PreserveSyncContext]
+ public Task Then_NoInputValue_WithReturnValue_IncompleteTask_RunsOnNewThreadAndPostsContinuationToSynchronizationContext()
+ {
+ // Arrange
+ int originalThreadId = Thread.CurrentThread.ManagedThreadId;
+ int callbackThreadId = Int32.MinValue;
+ var syncContext = new Mock<SynchronizationContext> { CallBase = true };
+ SynchronizationContext.SetSynchronizationContext(syncContext.Object);
+
+ Task incompleteTask = new Task(() => { });
+
+ // Act
+ Task resultTask = incompleteTask.Then(() =>
+ {
+ callbackThreadId = Thread.CurrentThread.ManagedThreadId;
+ return 42;
+ });
+
+ // Assert
+ incompleteTask.Start();
+
+ return resultTask.ContinueWith(task =>
+ {
+ Assert.NotEqual(originalThreadId, callbackThreadId);
+ syncContext.Verify(sc => sc.Post(It.IsAny<SendOrPostCallback>(), null), Times.Once());
+ });
+ }
+
+ [Fact, PreserveSyncContext]
+ public Task Then_NoInputValue_WithReturnValue_CompleteTask_RunsOnSameThreadAndDoesNotPostToSynchronizationContext()
+ {
+ // Arrange
+ int originalThreadId = Thread.CurrentThread.ManagedThreadId;
+ int callbackThreadId = Int32.MinValue;
+ var syncContext = new Mock<SynchronizationContext> { CallBase = true };
+ SynchronizationContext.SetSynchronizationContext(syncContext.Object);
+
+ return TaskHelpers.Completed()
+
+ // Act
+ .Then(() =>
+ {
+ callbackThreadId = Thread.CurrentThread.ManagedThreadId;
+ return 42;
+ })
+
+ // Assert
+ .ContinueWith(task =>
+ {
+ Assert.Equal(originalThreadId, callbackThreadId);
+ syncContext.Verify(sc => sc.Post(It.IsAny<SendOrPostCallback>(), null), Times.Never());
+ });
+ }
+
+ // -----------------------------------------------------------------
+ // Task<T> Task.Then(Func<Task<T>>)
+
+ [Fact]
+ public Task Then_NoInputValue_WithTaskReturnValue_CallsContinuation()
+ {
+ // Arrange
+ return TaskHelpers.Completed()
+
+ // Act
+ .Then(() =>
+ {
+ return TaskHelpers.FromResult(42);
+ })
+
+ // Assert
+ .ContinueWith(task =>
+ {
+ Assert.Equal(TaskStatus.RanToCompletion, task.Status);
+ Assert.Equal(42, task.Result);
+ });
+ }
+
+ [Fact]
+ public Task Then_NoInputValue_WithTaskReturnValue_ThrownExceptionIsPropagated()
+ {
+ // Arrange
+ return TaskHelpers.Completed()
+
+ // Act
+ .Then(() =>
+ {
+ throw new NotImplementedException();
+ return TaskHelpers.FromResult(0); // Return-after-throw to guarantee correct lambda signature
+ })
+
+ // Assert
+ .ContinueWith(task =>
+ {
+ Assert.Equal(TaskStatus.Faulted, task.Status);
+ var ex = Assert.Single(task.Exception.InnerExceptions);
+ Assert.IsType<NotImplementedException>(ex);
+ });
+ }
+
+ [Fact]
+ public Task Then_NoInputValue_WithTaskReturnValue_FaultPreventsFurtherThenStatementsFromExecuting()
+ {
+ // Arrange
+ bool ranContinuation = false;
+
+ return TaskHelpers.FromError(new NotImplementedException())
+
+ // Act
+ .Then(() =>
+ {
+ ranContinuation = true;
+ return TaskHelpers.FromResult(42);
+ })
+
+ // Assert
+ .ContinueWith(task =>
+ {
+ var ex = task.Exception; // Observe the exception
+ Assert.False(ranContinuation);
+ });
+ }
+
+ [Fact]
+ public Task Then_NoInputValue_WithTaskReturnValue_ManualCancellationPreventsFurtherThenStatementsFromExecuting()
+ {
+ // Arrange
+ bool ranContinuation = false;
+
+ return TaskHelpers.Canceled()
+
+ // Act
+ .Then(() =>
+ {
+ ranContinuation = true;
+ return TaskHelpers.FromResult(42);
+ })
+
+ // Assert
+ .ContinueWith(task =>
+ {
+ Assert.Equal(TaskStatus.Canceled, task.Status);
+ Assert.False(ranContinuation);
+ });
+ }
+
+ [Fact]
+ public Task Then_NoInputValue_WithTaskReturnValue_TokenCancellationPreventsFurtherThenStatementsFromExecuting()
+ {
+ // Arrange
+ bool ranContinuation = false;
+ CancellationToken cancellationToken = new CancellationToken(canceled: true);
+
+ return TaskHelpers.Completed()
+
+ // Act
+ .Then(() =>
+ {
+ ranContinuation = true;
+ return TaskHelpers.FromResult(42);
+ }, cancellationToken)
+
+ // Assert
+ .ContinueWith(task =>
+ {
+ Assert.Equal(TaskStatus.Canceled, task.Status);
+ Assert.False(ranContinuation);
+ });
+ }
+
+ [Fact, PreserveSyncContext]
+ public Task Then_NoInputValue_WithTaskReturnValue_IncompleteTask_RunsOnNewThreadAndPostsContinuationToSynchronizationContext()
+ {
+ // Arrange
+ int originalThreadId = Thread.CurrentThread.ManagedThreadId;
+ int callbackThreadId = Int32.MinValue;
+ var syncContext = new Mock<SynchronizationContext> { CallBase = true };
+ SynchronizationContext.SetSynchronizationContext(syncContext.Object);
+
+ Task incompleteTask = new Task(() => { });
+
+ // Act
+ Task resultTask = incompleteTask.Then(() =>
+ {
+ callbackThreadId = Thread.CurrentThread.ManagedThreadId;
+ return TaskHelpers.FromResult(42);
+ });
+
+ // Assert
+ incompleteTask.Start();
+
+ return resultTask.ContinueWith(task =>
+ {
+ Assert.NotEqual(originalThreadId, callbackThreadId);
+ syncContext.Verify(sc => sc.Post(It.IsAny<SendOrPostCallback>(), null), Times.Once());
+ });
+ }
+
+ [Fact, PreserveSyncContext]
+ public Task Then_NoInputValue_WithTaskReturnValue_CompleteTask_RunsOnSameThreadAndDoesNotPostToSynchronizationContext()
+ {
+ // Arrange
+ int originalThreadId = Thread.CurrentThread.ManagedThreadId;
+ int callbackThreadId = Int32.MinValue;
+ var syncContext = new Mock<SynchronizationContext> { CallBase = true };
+ SynchronizationContext.SetSynchronizationContext(syncContext.Object);
+
+ return TaskHelpers.Completed()
+
+ // Act
+ .Then(() =>
+ {
+ callbackThreadId = Thread.CurrentThread.ManagedThreadId;
+ return TaskHelpers.FromResult(42);
+ })
+
+ // Assert
+ .ContinueWith(task =>
+ {
+ Assert.Equal(originalThreadId, callbackThreadId);
+ syncContext.Verify(sc => sc.Post(It.IsAny<SendOrPostCallback>(), null), Times.Never());
+ });
+ }
+
+ // -----------------------------------------------------------------
+ // Task Task<T>.Then(Action)
+
+ [Fact]
+ public Task Then_WithInputValue_NoReturnValue_CallsContinuationWithPriorTaskResult()
+ {
+ // Arrange
+ int passedResult = 0;
+
+ return TaskHelpers.FromResult(21)
+
+ // Act
+ .Then(result =>
+ {
+ passedResult = result;
+ })
+
+ // Assert
+ .ContinueWith(task =>
+ {
+ Assert.Equal(TaskStatus.RanToCompletion, task.Status);
+ Assert.Equal(21, passedResult);
+ });
+ }
+
+ [Fact]
+ public Task Then_WithInputValue_NoReturnValue_ThrownExceptionIsPropagated()
+ {
+ // Arrange
+ return TaskHelpers.FromResult(21)
+
+ // Act
+ .Then(result =>
+ {
+ throw new NotImplementedException();
+ })
+
+ // Assert
+ .ContinueWith(task =>
+ {
+ Assert.Equal(TaskStatus.Faulted, task.Status);
+ var ex = Assert.Single(task.Exception.InnerExceptions);
+ Assert.IsType<NotImplementedException>(ex);
+ });
+ }
+
+ [Fact]
+ public Task Then_WithInputValue_NoReturnValue_FaultPreventsFurtherThenStatementsFromExecuting()
+ {
+ // Arrange
+ bool ranContinuation = false;
+
+ return TaskHelpers.FromError<int>(new NotImplementedException())
+
+ // Act
+ .Then(result =>
+ {
+ ranContinuation = true;
+ })
+
+ // Assert
+ .ContinueWith(task =>
+ {
+ var ex = task.Exception; // Observe the exception
+ Assert.False(ranContinuation);
+ });
+ }
+
+ [Fact]
+ public Task Then_WithInputValue_NoReturnValue_ManualCancellationPreventsFurtherThenStatementsFromExecuting()
+ {
+ // Arrange
+ bool ranContinuation = false;
+
+ return TaskHelpers.Canceled<int>()
+
+ // Act
+ .Then(result =>
+ {
+ ranContinuation = true;
+ })
+
+ // Assert
+ .ContinueWith(task =>
+ {
+ Assert.Equal(TaskStatus.Canceled, task.Status);
+ Assert.False(ranContinuation);
+ });
+ }
+
+ [Fact]
+ public Task Then_WithInputValue_NoReturnValue_TokenCancellationPreventsFurtherThenStatementsFromExecuting()
+ {
+ // Arrange
+ bool ranContinuation = false;
+ CancellationToken cancellationToken = new CancellationToken(canceled: true);
+
+ return TaskHelpers.FromResult(21)
+
+ // Act
+ .Then(result =>
+ {
+ ranContinuation = true;
+ }, cancellationToken)
+
+ // Assert
+ .ContinueWith(task =>
+ {
+ Assert.Equal(TaskStatus.Canceled, task.Status);
+ Assert.False(ranContinuation);
+ });
+ }
+
+ [Fact, PreserveSyncContext]
+ public Task Then_WithInputValue_NoReturnValue_IncompleteTask_RunsOnNewThreadAndPostsContinuationToSynchronizationContext()
+ {
+ // Arrange
+ int originalThreadId = Thread.CurrentThread.ManagedThreadId;
+ int callbackThreadId = Int32.MinValue;
+ var syncContext = new Mock<SynchronizationContext> { CallBase = true };
+ SynchronizationContext.SetSynchronizationContext(syncContext.Object);
+
+ Task<int> incompleteTask = new Task<int>(() => 21);
+
+ // Act
+ Task resultTask = incompleteTask.Then(result =>
+ {
+ callbackThreadId = Thread.CurrentThread.ManagedThreadId;
+ });
+
+ // Assert
+ incompleteTask.Start();
+
+ return resultTask.ContinueWith(task =>
+ {
+ Assert.NotEqual(originalThreadId, callbackThreadId);
+ syncContext.Verify(sc => sc.Post(It.IsAny<SendOrPostCallback>(), null), Times.Once());
+ });
+ }
+
+ [Fact, PreserveSyncContext]
+ public Task Then_WithInputValue_NoReturnValue_CompleteTask_RunsOnSameThreadAndDoesNotPostToSynchronizationContext()
+ {
+ // Arrange
+ int originalThreadId = Thread.CurrentThread.ManagedThreadId;
+ int callbackThreadId = Int32.MinValue;
+ var syncContext = new Mock<SynchronizationContext> { CallBase = true };
+ SynchronizationContext.SetSynchronizationContext(syncContext.Object);
+
+ return TaskHelpers.FromResult(21)
+
+ // Act
+ .Then(result =>
+ {
+ callbackThreadId = Thread.CurrentThread.ManagedThreadId;
+ })
+
+ // Assert
+ .ContinueWith(task =>
+ {
+ Assert.Equal(originalThreadId, callbackThreadId);
+ syncContext.Verify(sc => sc.Post(It.IsAny<SendOrPostCallback>(), null), Times.Never());
+ });
+ }
+
+ // -----------------------------------------------------------------
+ // Task<T> Task.Then(Func<T>)
+
+ [Fact]
+ public Task Then_WithInputValue_WithReturnValue_CallsContinuation()
+ {
+ // Arrange
+ return TaskHelpers.FromResult(21)
+
+ // Act
+ .Then(result =>
+ {
+ return 42;
+ })
+
+ // Assert
+ .ContinueWith(task =>
+ {
+ Assert.Equal(TaskStatus.RanToCompletion, task.Status);
+ Assert.Equal(42, task.Result);
+ });
+ }
+
+ [Fact]
+ public Task Then_WithInputValue_WithReturnValue_ThrownExceptionIsPropagated()
+ {
+ // Arrange
+ return TaskHelpers.FromResult(21)
+
+ // Act
+ .Then(result =>
+ {
+ throw new NotImplementedException();
+ return 0; // Return-after-throw to guarantee correct lambda signature
+ })
+
+ // Assert
+ .ContinueWith(task =>
+ {
+ Assert.Equal(TaskStatus.Faulted, task.Status);
+ var ex = Assert.Single(task.Exception.InnerExceptions);
+ Assert.IsType<NotImplementedException>(ex);
+ });
+ }
+
+ [Fact]
+ public Task Then_WithInputValue_WithReturnValue_FaultPreventsFurtherThenStatementsFromExecuting()
+ {
+ // Arrange
+ bool ranContinuation = false;
+
+ return TaskHelpers.FromError<int>(new NotImplementedException())
+
+ // Act
+ .Then(result =>
+ {
+ ranContinuation = true;
+ return 42;
+ })
+
+ // Assert
+ .ContinueWith(task =>
+ {
+ var ex = task.Exception; // Observe the exception
+ Assert.False(ranContinuation);
+ });
+ }
+
+ [Fact]
+ public Task Then_WithInputValue_WithReturnValue_ManualCancellationPreventsFurtherThenStatementsFromExecuting()
+ {
+ // Arrange
+ bool ranContinuation = false;
+
+ return TaskHelpers.Canceled<int>()
+
+ // Act
+ .Then(result =>
+ {
+ ranContinuation = true;
+ return 42;
+ })
+
+ // Assert
+ .ContinueWith(task =>
+ {
+ Assert.Equal(TaskStatus.Canceled, task.Status);
+ Assert.False(ranContinuation);
+ });
+ }
+
+ [Fact]
+ public Task Then_WithInputValue_WithReturnValue_TokenCancellationPreventsFurtherThenStatementsFromExecuting()
+ {
+ // Arrange
+ bool ranContinuation = false;
+ CancellationToken cancellationToken = new CancellationToken(canceled: true);
+
+ return TaskHelpers.FromResult(21)
+
+ // Act
+ .Then(result =>
+ {
+ ranContinuation = true;
+ return 42;
+ }, cancellationToken)
+
+ // Assert
+ .ContinueWith(task =>
+ {
+ Assert.Equal(TaskStatus.Canceled, task.Status);
+ Assert.False(ranContinuation);
+ });
+ }
+
+ [Fact, PreserveSyncContext]
+ public Task Then_WithInputValue_WithReturnValue_IncompleteTask_RunsOnNewThreadAndPostsContinuationToSynchronizationContext()
+ {
+ // Arrange
+ int originalThreadId = Thread.CurrentThread.ManagedThreadId;
+ int callbackThreadId = Int32.MinValue;
+ var syncContext = new Mock<SynchronizationContext> { CallBase = true };
+ SynchronizationContext.SetSynchronizationContext(syncContext.Object);
+
+ Task<int> incompleteTask = new Task<int>(() => 21);
+
+ // Act
+ Task resultTask = incompleteTask.Then(result =>
+ {
+ callbackThreadId = Thread.CurrentThread.ManagedThreadId;
+ return 42;
+ });
+
+ // Assert
+ incompleteTask.Start();
+
+ return resultTask.ContinueWith(task =>
+ {
+ Assert.NotEqual(originalThreadId, callbackThreadId);
+ syncContext.Verify(sc => sc.Post(It.IsAny<SendOrPostCallback>(), null), Times.Once());
+ });
+ }
+
+ [Fact, PreserveSyncContext]
+ public Task Then_WithInputValue_WithReturnValue_CompleteTask_RunsOnSameThreadAndDoesNotPostToSynchronizationContext()
+ {
+ // Arrange
+ int originalThreadId = Thread.CurrentThread.ManagedThreadId;
+ int callbackThreadId = Int32.MinValue;
+ var syncContext = new Mock<SynchronizationContext> { CallBase = true };
+ SynchronizationContext.SetSynchronizationContext(syncContext.Object);
+
+ return TaskHelpers.FromResult(21)
+
+ // Act
+ .Then(result =>
+ {
+ callbackThreadId = Thread.CurrentThread.ManagedThreadId;
+ return 42;
+ })
+
+ // Assert
+ .ContinueWith(task =>
+ {
+ Assert.Equal(originalThreadId, callbackThreadId);
+ syncContext.Verify(sc => sc.Post(It.IsAny<SendOrPostCallback>(), null), Times.Never());
+ });
+ }
+
+ // -----------------------------------------------------------------
+ // Task<T> Task.Then(Func<Task<T>>)
+
+ [Fact]
+ public Task Then_WithInputValue_WithTaskReturnValue_CallsContinuation()
+ {
+ // Arrange
+ return TaskHelpers.FromResult(21)
+
+ // Act
+ .Then(result =>
+ {
+ return TaskHelpers.FromResult(42);
+ })
+
+ // Assert
+ .ContinueWith(task =>
+ {
+ Assert.Equal(TaskStatus.RanToCompletion, task.Status);
+ Assert.Equal(42, task.Result);
+ });
+ }
+
+ [Fact]
+ public Task Then_WithInputValue_WithTaskReturnValue_ThrownExceptionIsPropagated()
+ {
+ // Arrange
+ return TaskHelpers.FromResult(21)
+
+ // Act
+ .Then(result =>
+ {
+ throw new NotImplementedException();
+ return TaskHelpers.FromResult(0); // Return-after-throw to guarantee correct lambda signature
+ })
+
+ // Assert
+ .ContinueWith(task =>
+ {
+ Assert.Equal(TaskStatus.Faulted, task.Status);
+ var ex = Assert.Single(task.Exception.InnerExceptions);
+ Assert.IsType<NotImplementedException>(ex);
+ });
+ }
+
+ [Fact]
+ public Task Then_WithInputValue_WithTaskReturnValue_FaultPreventsFurtherThenStatementsFromExecuting()
+ {
+ // Arrange
+ bool ranContinuation = false;
+
+ return TaskHelpers.FromError<int>(new NotImplementedException())
+
+ // Act
+ .Then(result =>
+ {
+ ranContinuation = true;
+ return TaskHelpers.FromResult(42);
+ })
+
+ // Assert
+ .ContinueWith(task =>
+ {
+ var ex = task.Exception; // Observe the exception
+ Assert.False(ranContinuation);
+ });
+ }
+
+ [Fact]
+ public Task Then_WithInputValue_WithTaskReturnValue_ManualCancellationPreventsFurtherThenStatementsFromExecuting()
+ {
+ // Arrange
+ bool ranContinuation = false;
+
+ return TaskHelpers.Canceled<int>()
+
+ // Act
+ .Then(result =>
+ {
+ ranContinuation = true;
+ return TaskHelpers.FromResult(42);
+ })
+
+ // Assert
+ .ContinueWith(task =>
+ {
+ Assert.Equal(TaskStatus.Canceled, task.Status);
+ Assert.False(ranContinuation);
+ });
+ }
+
+ [Fact]
+ public Task Then_WithInputValue_WithTaskReturnValue_TokenCancellationPreventsFurtherThenStatementsFromExecuting()
+ {
+ // Arrange
+ bool ranContinuation = false;
+ CancellationToken cancellationToken = new CancellationToken(canceled: true);
+
+ return TaskHelpers.FromResult(21)
+
+ // Act
+ .Then(result =>
+ {
+ ranContinuation = true;
+ return TaskHelpers.FromResult(42);
+ }, cancellationToken)
+
+ // Assert
+ .ContinueWith(task =>
+ {
+ Assert.Equal(TaskStatus.Canceled, task.Status);
+ Assert.False(ranContinuation);
+ });
+ }
+
+ [Fact, PreserveSyncContext]
+ public Task Then_WithInputValue_WithTaskReturnValue_IncompleteTask_RunsOnNewThreadAndPostsContinuationToSynchronizationContext()
+ {
+ // Arrange
+ int originalThreadId = Thread.CurrentThread.ManagedThreadId;
+ int callbackThreadId = Int32.MinValue;
+ var syncContext = new Mock<SynchronizationContext> { CallBase = true };
+ SynchronizationContext.SetSynchronizationContext(syncContext.Object);
+
+ Task<int> incompleteTask = new Task<int>(() => 21);
+
+ // Act
+ Task resultTask = incompleteTask.Then(result =>
+ {
+ callbackThreadId = Thread.CurrentThread.ManagedThreadId;
+ return TaskHelpers.FromResult(42);
+ });
+
+ // Assert
+ incompleteTask.Start();
+
+ return resultTask.ContinueWith(task =>
+ {
+ Assert.NotEqual(originalThreadId, callbackThreadId);
+ syncContext.Verify(sc => sc.Post(It.IsAny<SendOrPostCallback>(), null), Times.Once());
+ });
+ }
+
+ [Fact, PreserveSyncContext]
+ public Task Then_WithInputValue_WithTaskReturnValue_CompleteTask_RunsOnSameThreadAndDoesNotPostToSynchronizationContext()
+ {
+ // Arrange
+ int originalThreadId = Thread.CurrentThread.ManagedThreadId;
+ int callbackThreadId = Int32.MinValue;
+ var syncContext = new Mock<SynchronizationContext> { CallBase = true };
+ SynchronizationContext.SetSynchronizationContext(syncContext.Object);
+
+ return TaskHelpers.FromResult(21)
+
+ // Act
+ .Then(result =>
+ {
+ callbackThreadId = Thread.CurrentThread.ManagedThreadId;
+ return TaskHelpers.FromResult(42);
+ })
+
+ // Assert
+ .ContinueWith(task =>
+ {
+ Assert.Equal(originalThreadId, callbackThreadId);
+ syncContext.Verify(sc => sc.Post(It.IsAny<SendOrPostCallback>(), null), Times.Never());
+ });
+ }
+
+ // -----------------------------------------------------------------
+ // bool Task.TryGetResult(Task<TResult>, out TResult)
+
+ [Fact]
+ public void TryGetResult_CompleteTask_ReturnsTrueAndGivesResult()
+ {
+ // Arrange
+ var task = TaskHelpers.FromResult(42);
+
+ // Act
+ int value;
+ bool result = task.TryGetResult(out value);
+
+ // Assert
+ Assert.True(result);
+ Assert.Equal(42, value);
+ }
+
+ [Fact]
+ public void TryGetResult_FaultedTask_ReturnsFalse()
+ {
+ // Arrange
+ var task = TaskHelpers.FromError<int>(new Exception());
+
+ // Act
+ int value;
+ bool result = task.TryGetResult(out value);
+
+ // Assert
+ Assert.False(result);
+ var ex = task.Exception; // Observe the task exception
+ }
+
+ [Fact]
+ public void TryGetResult_CanceledTask_ReturnsFalse()
+ {
+ // Arrange
+ var task = TaskHelpers.Canceled<int>();
+
+ // Act
+ int value;
+ bool result = task.TryGetResult(out value);
+
+ // Assert
+ Assert.False(result);
+ }
+
+ [Fact]
+ public Task TryGetResult_IncompleteTask_ReturnsFalse()
+ {
+ // Arrange
+ var incompleteTask = new Task<int>(() => 42);
+
+ // Act
+ int value;
+ bool result = incompleteTask.TryGetResult(out value);
+
+ // Assert
+ Assert.False(result);
+
+ incompleteTask.Start();
+ return incompleteTask; // Make sure the task gets observed
+ }
+ }
+}
diff --git a/test/System.Web.Http.Common.Test/TaskHelpersTest.cs b/test/System.Web.Http.Common.Test/TaskHelpersTest.cs
new file mode 100644
index 00000000..fe6d1d6a
--- /dev/null
+++ b/test/System.Web.Http.Common.Test/TaskHelpersTest.cs
@@ -0,0 +1,574 @@
+using System.Collections.Generic;
+using System.Linq;
+using Microsoft.TestCommon;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Threading.Tasks
+{
+ public class TaskHelpersTest
+ {
+ // -----------------------------------------------------------------
+ // TaskHelpers.Canceled
+
+ [Fact]
+ public void Canceled_ReturnsCanceledTask()
+ {
+ Task result = TaskHelpers.Canceled();
+
+ Assert.NotNull(result);
+ Assert.True(result.IsCanceled);
+ }
+
+ // -----------------------------------------------------------------
+ // TaskHelpers.Canceled<T>
+
+ [Fact]
+ public void Canceled_Generic_ReturnsCanceledTask()
+ {
+ Task<string> result = TaskHelpers.Canceled<string>();
+
+ Assert.NotNull(result);
+ Assert.True(result.IsCanceled);
+ }
+
+ // -----------------------------------------------------------------
+ // TaskHelpers.Completed
+
+ [Fact]
+ public void Completed_ReturnsCompletedTask()
+ {
+ Task result = TaskHelpers.Completed();
+
+ Assert.NotNull(result);
+ Assert.Equal(TaskStatus.RanToCompletion, result.Status);
+ }
+
+ // -----------------------------------------------------------------
+ // TaskHelpers.FromError
+
+ [Fact]
+ public void FromError_ReturnsFaultedTaskWithGivenException()
+ {
+ var exception = new Exception();
+
+ Task result = TaskHelpers.FromError(exception);
+
+ Assert.NotNull(result);
+ Assert.True(result.IsFaulted);
+ Assert.Same(exception, result.Exception.InnerException);
+ }
+
+ // -----------------------------------------------------------------
+ // TaskHelpers.FromError<T>
+
+ [Fact]
+ public void FromError_Generic_ReturnsFaultedTaskWithGivenException()
+ {
+ var exception = new Exception();
+
+ Task<string> result = TaskHelpers.FromError<string>(exception);
+
+ Assert.NotNull(result);
+ Assert.True(result.IsFaulted);
+ Assert.Same(exception, result.Exception.InnerException);
+ }
+
+ // -----------------------------------------------------------------
+ // TaskHelpers.FromErrors
+
+ [Fact]
+ public void FromErrors_ReturnsFaultedTaskWithGivenExceptions()
+ {
+ var exceptions = new[] { new Exception(), new InvalidOperationException() };
+
+ Task result = TaskHelpers.FromErrors(exceptions);
+
+ Assert.NotNull(result);
+ Assert.True(result.IsFaulted);
+ Assert.Equal(exceptions, result.Exception.InnerExceptions.ToArray());
+ }
+
+ // -----------------------------------------------------------------
+ // TaskHelpers.FromErrors<T>
+
+ [Fact]
+ public void FromErrors_Generic_ReturnsFaultedTaskWithGivenExceptions()
+ {
+ var exceptions = new[] { new Exception(), new InvalidOperationException() };
+
+ Task<string> result = TaskHelpers.FromErrors<string>(exceptions);
+
+ Assert.NotNull(result);
+ Assert.True(result.IsFaulted);
+ Assert.Equal(exceptions, result.Exception.InnerExceptions.ToArray());
+ }
+
+ // -----------------------------------------------------------------
+ // TaskHelpers.FromResult<T>
+
+ [Fact]
+ public void FromResult_ReturnsCompletedTaskWithGivenResult()
+ {
+ string s = "ABC";
+
+ Task<string> result = TaskHelpers.FromResult(s);
+
+ Assert.NotNull(result);
+ Assert.True(result.Status == TaskStatus.RanToCompletion);
+ Assert.Same(s, result.Result);
+ }
+
+ // -----------------------------------------------------------------
+ // Task TaskHelpers.Iterate(IEnumerable<Task>)
+
+ [Fact]
+ public void Iterate_NonGeneric_IfProvidedEnumerationContainsNullValue_ReturnsFaultedTaskWithNullReferenceException()
+ {
+ List<string> log = new List<string>();
+
+ var result = TaskHelpers.Iterate(NullTaskEnumerable(log));
+
+ Assert.NotNull(result);
+ result.WaitUntilCompleted();
+ Assert.Equal(TaskStatus.Faulted, result.Status);
+ Assert.IsType<NullReferenceException>(result.Exception.GetBaseException());
+ }
+
+ private static IEnumerable<Task> NullTaskEnumerable(List<string> log)
+ {
+ log.Add("first");
+ yield return null;
+ log.Add("second");
+ }
+
+ [Fact]
+ public void Iterate_NonGeneric_IfProvidedEnumerationThrowsException_ReturnsFaultedTask()
+ {
+ List<string> log = new List<string>();
+ Exception exception = new Exception();
+
+ var result = TaskHelpers.Iterate(ThrowingTaskEnumerable(exception, log));
+
+ Assert.NotNull(result);
+ result.WaitUntilCompleted();
+ Assert.Equal(TaskStatus.Faulted, result.Status);
+ Assert.Same(exception, result.Exception.InnerException);
+ Assert.Equal(new[] { "first" }, log.ToArray());
+ }
+
+ private static IEnumerable<Task> ThrowingTaskEnumerable(Exception e, List<string> log)
+ {
+ log.Add("first");
+ bool a = true; // work around unreachable code warning
+ if (a) throw e;
+ log.Add("second");
+ yield return null;
+ }
+
+ [Fact]
+ public void Iterate_NonGeneric_IfProvidedEnumerableExecutesCancellingTask_ReturnsCanceledTaskAndHaltsEnumeration()
+ {
+ List<string> log = new List<string>();
+
+ var result = TaskHelpers.Iterate(CanceledTaskEnumerable(log));
+
+ Assert.NotNull(result);
+ result.WaitUntilCompleted();
+ Assert.Equal(TaskStatus.Canceled, result.Status);
+ Assert.Equal(new[] { "first" }, log.ToArray());
+ }
+
+ private static IEnumerable<Task> CanceledTaskEnumerable(List<string> log)
+ {
+ log.Add("first");
+ yield return TaskHelpers.Canceled();
+ log.Add("second");
+ }
+
+ [Fact]
+ public void Iterate_NonGeneric_IfProvidedEnumerableExecutesFaultingTask_ReturnsCanceledTaskAndHaltsEnumeration()
+ {
+ List<string> log = new List<string>();
+ Exception exception = new Exception();
+
+ var result = TaskHelpers.Iterate(FaultedTaskEnumerable(exception, log));
+
+ Assert.NotNull(result);
+ result.WaitUntilCompleted();
+ Assert.Equal(TaskStatus.Faulted, result.Status);
+ Assert.Same(exception, result.Exception.InnerException);
+ Assert.Equal(new[] { "first" }, log.ToArray());
+ }
+
+ private static IEnumerable<Task> FaultedTaskEnumerable(Exception e, List<string> log)
+ {
+ log.Add("first");
+ yield return TaskHelpers.FromError(e);
+ log.Add("second");
+ }
+
+ [Fact]
+ public void Iterate_NonGeneric_ExecutesNextTaskOnlyAfterPreviousTaskSucceeded()
+ {
+ List<string> log = new List<string>();
+
+ var result = TaskHelpers.Iterate(SuccessTaskEnumerable(log));
+
+ Assert.NotNull(result);
+ result.WaitUntilCompleted();
+ Assert.Equal(TaskStatus.RanToCompletion, result.Status);
+ Assert.Equal(
+ new[] { "first", "Executing first task. Log size: 1", "second", "Executing second task. Log size: 3" },
+ log.ToArray());
+ }
+
+ private static IEnumerable<Task> SuccessTaskEnumerable(List<string> log)
+ {
+ log.Add("first");
+ yield return Task.Factory.StartNew(() => log.Add("Executing first task. Log size: " + log.Count));
+ log.Add("second");
+ yield return Task.Factory.StartNew(() => log.Add("Executing second task. Log size: " + log.Count));
+ }
+
+ [Fact]
+ public void Iterate_NonGeneric_TasksRunSequentiallyRegardlessOfExecutionTime()
+ {
+ List<string> log = new List<string>();
+
+ Task task = TaskHelpers.Iterate(TasksWithVaryingDelays(log, 100, 1, 50, 2));
+
+ task.WaitUntilCompleted();
+ Assert.Equal(TaskStatus.RanToCompletion, task.Status);
+ Assert.Equal(new[] { "ENTER: 100", "EXIT: 100", "ENTER: 1", "EXIT: 1", "ENTER: 50", "EXIT: 50", "ENTER: 2", "EXIT: 2" }, log);
+ }
+
+ private static IEnumerable<Task> TasksWithVaryingDelays(List<string> log, params int[] delays)
+ {
+ foreach (int delay in delays)
+ yield return Task.Factory.StartNew(timeToSleep =>
+ {
+ log.Add("ENTER: " + timeToSleep);
+ Thread.Sleep((int)timeToSleep);
+ log.Add("EXIT: " + timeToSleep);
+ }, delay);
+ }
+
+ [Fact]
+ public void Iterate_NonGeneric_StopsTaskIterationIfCancellationWasRequested()
+ {
+ List<string> log = new List<string>();
+ CancellationTokenSource cts = new CancellationTokenSource();
+
+ var result = TaskHelpers.Iterate(CancelingTaskEnumerable(log, cts), cts.Token);
+
+ Assert.NotNull(result);
+ result.WaitUntilCompleted();
+ Assert.Equal(TaskStatus.Canceled, result.Status);
+ Assert.Equal(
+ new[] { "first", "Executing first task. Log size: 1" },
+ log.ToArray());
+ }
+
+ private static IEnumerable<Task> CancelingTaskEnumerable(List<string> log, CancellationTokenSource cts)
+ {
+ log.Add("first");
+ yield return Task.Factory.StartNew(() =>
+ {
+ log.Add("Executing first task. Log size: " + log.Count);
+ cts.Cancel();
+ });
+ log.Add("second");
+ yield return Task.Factory.StartNew(() =>
+ {
+ log.Add("Executing second task. Log size: " + log.Count);
+ });
+ }
+
+ [Fact, PreserveSyncContext]
+ public Task Iterate_NonGeneric_IteratorRunsInSynchronizationContext()
+ {
+ ThreadPoolSyncContext sc = new ThreadPoolSyncContext();
+ SynchronizationContext.SetSynchronizationContext(sc);
+
+ return TaskHelpers.Iterate(SyncContextVerifyingEnumerable(sc)).Then(() =>
+ {
+ Assert.Same(sc, SynchronizationContext.Current);
+ });
+ }
+
+ private static IEnumerable<Task> SyncContextVerifyingEnumerable(SynchronizationContext sc)
+ {
+ for (int i = 0; i < 10; i++)
+ {
+ Assert.Same(sc, SynchronizationContext.Current);
+ yield return TaskHelpers.Completed();
+ }
+ }
+
+ // -----------------------------------------------------------------
+ // Task<IEnumerable<T>> TaskHelpers.Iterate(IEnumerable<Task<T>>)
+
+ [Fact]
+ public void Iterate_Generic_IfProvidedEnumerationContainsNullValue_ReturnsFaultedTaskWithNullReferenceException()
+ {
+ List<string> log = new List<string>();
+
+ Task<IEnumerable<object>> result = TaskHelpers.Iterate(NullTaskEnumerable_Generic(log));
+
+ Assert.NotNull(result);
+ result.WaitUntilCompleted();
+ Assert.Equal(TaskStatus.Faulted, result.Status);
+ Assert.IsType<NullReferenceException>(result.Exception.GetBaseException());
+ }
+
+ private static IEnumerable<Task<object>> NullTaskEnumerable_Generic(List<string> log)
+ {
+ log.Add("first");
+ yield return null;
+ log.Add("second");
+ }
+
+ [Fact]
+ public void Iterate_Generic_IfProvidedEnumerationThrowsException_ReturnsFaultedTask()
+ {
+ List<string> log = new List<string>();
+ Exception exception = new Exception();
+
+ Task<IEnumerable<object>> result = TaskHelpers.Iterate(ThrowingTaskEnumerable_Generic(exception, log));
+
+ Assert.NotNull(result);
+ result.WaitUntilCompleted();
+ Assert.Equal(TaskStatus.Faulted, result.Status);
+ Assert.Same(exception, result.Exception.InnerException);
+ Assert.Equal(new[] { "first" }, log.ToArray());
+ }
+
+ private static IEnumerable<Task<object>> ThrowingTaskEnumerable_Generic(Exception e, List<string> log)
+ {
+ log.Add("first");
+ bool a = true; // work around unreachable code warning
+ if (a) throw e;
+ log.Add("second");
+ yield return null;
+ }
+
+ [Fact]
+ public void Iterate_Generic_IfProvidedEnumerableExecutesCancellingTask_ReturnsCanceledTaskAndHaltsEnumeration()
+ {
+ List<string> log = new List<string>();
+
+ Task<IEnumerable<object>> result = TaskHelpers.Iterate(CanceledTaskEnumerable_Generic(log));
+
+ Assert.NotNull(result);
+ result.WaitUntilCompleted();
+ Assert.Equal(TaskStatus.Canceled, result.Status);
+ Assert.Equal(new[] { "first" }, log.ToArray());
+ }
+
+ private static IEnumerable<Task<object>> CanceledTaskEnumerable_Generic(List<string> log)
+ {
+ log.Add("first");
+ yield return TaskHelpers.Canceled<object>();
+ log.Add("second");
+ }
+
+ [Fact]
+ public void Iterate_Generic_IfProvidedEnumerableExecutesFaultingTask_ReturnsCanceledTaskAndHaltsEnumeration()
+ {
+ List<string> log = new List<string>();
+ Exception exception = new Exception();
+
+ Task<IEnumerable<object>> result = TaskHelpers.Iterate(FaultedTaskEnumerable_Generic(exception, log));
+
+ Assert.NotNull(result);
+ result.WaitUntilCompleted();
+ Assert.Equal(TaskStatus.Faulted, result.Status);
+ Assert.Same(exception, result.Exception.InnerException);
+ Assert.Equal(new[] { "first" }, log.ToArray());
+ }
+
+ private static IEnumerable<Task<object>> FaultedTaskEnumerable_Generic(Exception e, List<string> log)
+ {
+ log.Add("first");
+ yield return TaskHelpers.FromError<object>(e);
+ log.Add("second");
+ }
+
+ [Fact]
+ public void Iterate_Generic_ExecutesNextTaskOnlyAfterPreviousTaskSucceeded()
+ {
+ Task<IEnumerable<int>> result = TaskHelpers.Iterate(SuccessTaskEnumerable_Generic());
+
+ Assert.NotNull(result);
+ result.WaitUntilCompleted();
+ Assert.Equal(TaskStatus.RanToCompletion, result.Status);
+ Assert.Equal(new[] { 42, 2112 }, result.Result);
+ }
+
+ private static IEnumerable<Task<int>> SuccessTaskEnumerable_Generic()
+ {
+ yield return Task.Factory.StartNew(() => 42);
+ yield return Task.Factory.StartNew(() => 2112);
+ }
+
+ [Fact]
+ public void Iterate_Generic_TasksRunSequentiallyRegardlessOfExecutionTime()
+ {
+ List<string> log = new List<string>();
+
+ Task<IEnumerable<object>> task = TaskHelpers.Iterate(TasksWithVaryingDelays_Generic(log, 100, 1, 50, 2));
+
+ task.WaitUntilCompleted();
+ Assert.Equal(TaskStatus.RanToCompletion, task.Status);
+ Assert.Equal(new[] { "ENTER: 100", "EXIT: 100", "ENTER: 1", "EXIT: 1", "ENTER: 50", "EXIT: 50", "ENTER: 2", "EXIT: 2" }, log);
+ }
+
+ private static IEnumerable<Task<object>> TasksWithVaryingDelays_Generic(List<string> log, params int[] delays)
+ {
+ foreach (int delay in delays)
+ yield return Task.Factory.StartNew(timeToSleep =>
+ {
+ log.Add("ENTER: " + timeToSleep);
+ Thread.Sleep((int)timeToSleep);
+ log.Add("EXIT: " + timeToSleep);
+ return (object)null;
+ }, delay);
+ }
+
+ [Fact]
+ public void Iterate_Generic_StopsTaskIterationIfCancellationWasRequested()
+ {
+ List<string> log = new List<string>();
+ CancellationTokenSource cts = new CancellationTokenSource();
+
+ var result = TaskHelpers.Iterate(CancelingTaskEnumerable_Generic(log, cts), cts.Token);
+
+ Assert.NotNull(result);
+ result.WaitUntilCompleted();
+ Assert.Equal(TaskStatus.Canceled, result.Status);
+ Assert.Equal(
+ new[] { "first", "Executing first task. Log size: 1" },
+ log.ToArray());
+ }
+
+ private static IEnumerable<Task<object>> CancelingTaskEnumerable_Generic(List<string> log, CancellationTokenSource cts)
+ {
+ log.Add("first");
+ yield return Task.Factory.StartNew(() =>
+ {
+ log.Add("Executing first task. Log size: " + log.Count);
+ cts.Cancel();
+ return (object)null;
+ });
+ log.Add("second");
+ yield return Task.Factory.StartNew(() =>
+ {
+ log.Add("Executing second task. Log size: " + log.Count);
+ return (object)null;
+ });
+ }
+
+ [Fact, PreserveSyncContext]
+ public Task Iterate_Generic_IteratorRunsInSynchronizationContext()
+ {
+ ThreadPoolSyncContext sc = new ThreadPoolSyncContext();
+ SynchronizationContext.SetSynchronizationContext(sc);
+
+ return TaskHelpers.Iterate(SyncContextVerifyingEnumerable_Generic(sc)).Then(result =>
+ {
+ Assert.Same(sc, SynchronizationContext.Current);
+ Assert.Equal(new[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }, result);
+ });
+ }
+
+ private static IEnumerable<Task<int>> SyncContextVerifyingEnumerable_Generic(SynchronizationContext sc)
+ {
+ for (int i = 0; i < 10; i++)
+ {
+ Assert.Same(sc, SynchronizationContext.Current);
+ yield return TaskHelpers.FromResult(i);
+ }
+ }
+
+ // -----------------------------------------------------------------
+ // TaskHelpers.TrySetFromTask<T>
+
+ [Fact]
+ public void TrySetFromTask_IfSourceTaskIsCanceled_CancelsTaskCompletionSource()
+ {
+ TaskCompletionSource<string> tcs = new TaskCompletionSource<string>();
+ Task canceledTask = TaskHelpers.Canceled<object>();
+
+ tcs.TrySetFromTask(canceledTask);
+
+ Assert.Equal(TaskStatus.Canceled, tcs.Task.Status);
+ }
+
+ [Fact]
+ public void TrySetFromTask_IfSourceTaskIsFaulted_FaultsTaskCompletionSource()
+ {
+ TaskCompletionSource<string> tcs = new TaskCompletionSource<string>();
+ Exception exception = new Exception();
+ Task faultedTask = TaskHelpers.FromError<object>(exception);
+
+ tcs.TrySetFromTask(faultedTask);
+
+ Assert.Equal(TaskStatus.Faulted, tcs.Task.Status);
+ Assert.Same(exception, tcs.Task.Exception.InnerException);
+ }
+
+ [Fact]
+ public void TrySetFromTask_IfSourceTaskIsSuccessfulAndOfSameResultType_SucceedsTaskCompletionSourceAndSetsResult()
+ {
+ TaskCompletionSource<string> tcs = new TaskCompletionSource<string>();
+ Task<string> successfulTask = TaskHelpers.FromResult("abc");
+
+ tcs.TrySetFromTask(successfulTask);
+
+ Assert.Equal(TaskStatus.RanToCompletion, tcs.Task.Status);
+ Assert.Equal("abc", tcs.Task.Result);
+ }
+
+ [Fact]
+ public void TrySetFromTask_IfSourceTaskIsSuccessfulAndOfDifferentResultType_SucceedsTaskCompletionSourceAndSetsDefaultValueAsResult()
+ {
+ TaskCompletionSource<string> tcs = new TaskCompletionSource<string>();
+ Task<object> successfulTask = TaskHelpers.FromResult(new object());
+
+ tcs.TrySetFromTask(successfulTask);
+
+ Assert.Equal(TaskStatus.RanToCompletion, tcs.Task.Status);
+ Assert.Equal(null, tcs.Task.Result);
+ }
+
+ // -----------------------------------------------------------------
+ // TaskHelpers.RunSynchronously
+
+ [Fact]
+ public void RunSynchronously_Executes_Action()
+ {
+ bool wasRun = false;
+ Task t = TaskHelpers.RunSynchronously(() => { wasRun = true; });
+ t.WaitUntilCompleted();
+ Assert.True(wasRun);
+ }
+
+ [Fact]
+ public void RunSynchronously_Captures_Exception_In_AggregateException()
+ {
+ Task t = TaskHelpers.RunSynchronously(() => { throw new InvalidOperationException(); });
+ Assert.Throws<InvalidOperationException>(() => t.Wait());
+ }
+
+ [Fact]
+ public void RunSynchronously_Cancels()
+ {
+ CancellationTokenSource cts = new CancellationTokenSource();
+ cts.Cancel();
+
+ Task t = TaskHelpers.RunSynchronously(() => { throw new InvalidOperationException(); }, cts.Token);
+ Assert.Throws<TaskCanceledException>(() => t.Wait());
+ }
+ }
+}
diff --git a/test/System.Web.Http.Common.Test/packages.config b/test/System.Web.Http.Common.Test/packages.config
new file mode 100644
index 00000000..b9070f9e
--- /dev/null
+++ b/test/System.Web.Http.Common.Test/packages.config
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<packages>
+ <package id="Microsoft.Net.Http" version="2.0.20302.1" />
+ <package id="Moq" version="4.0.10827" />
+ <package id="xunit" version="1.9.0.1566" />
+ <package id="xunit.extensions" version="1.9.0.1566" />
+</packages> \ No newline at end of file
diff --git a/test/System.Web.Http.Integration.Test/ApiExplorer/ApiExplorerSettingsTest.cs b/test/System.Web.Http.Integration.Test/ApiExplorer/ApiExplorerSettingsTest.cs
new file mode 100644
index 00000000..65f33d28
--- /dev/null
+++ b/test/System.Web.Http.Integration.Test/ApiExplorer/ApiExplorerSettingsTest.cs
@@ -0,0 +1,62 @@
+using System.Collections.Generic;
+using System.Net.Http;
+using System.Web.Http.Description;
+using System.Web.Http.Dispatcher;
+using Xunit.Extensions;
+
+namespace System.Web.Http.ApiExplorer
+{
+ public class ApiExplorerSettingsTest
+ {
+ public static IEnumerable<object[]> HiddenController_DoesNotShowUpOnDescription_PropertyData
+ {
+ get
+ {
+ object controllerType = typeof(HiddenController);
+ object expectedApiDescriptions = new List<object>();
+ yield return new[] { controllerType, expectedApiDescriptions };
+ }
+ }
+
+ [Theory]
+ [PropertyData("HiddenController_DoesNotShowUpOnDescription_PropertyData")]
+ public void HiddenController_DoesNotShowUpOnDescription(Type controllerType, List<object> expectedResults)
+ {
+ HttpConfiguration config = new HttpConfiguration();
+ config.Routes.MapHttpRoute("Default", "{controller}/{id}", new { id = RouteParameter.Optional });
+
+ DefaultHttpControllerFactory controllerFactory = ApiExplorerHelper.GetStrictControllerFactory(config, controllerType);
+ config.ServiceResolver.SetService(typeof(IHttpControllerFactory), controllerFactory);
+
+ IApiExplorer explorer = config.ServiceResolver.GetApiExplorer();
+ ApiExplorerHelper.VerifyApiDescriptions(explorer.ApiDescriptions, expectedResults);
+ }
+
+ public static IEnumerable<object[]> HiddenAction_DoesNotShowUpOnDescription_PropertyData
+ {
+ get
+ {
+ object controllerType = typeof(HiddenActionController);
+ object expectedApiDescriptions = new List<object>
+ {
+ new { HttpMethod = HttpMethod.Get, RelativePath = "HiddenAction/{id}", HasRequestFormatters = false, HasResponseFormatters = true, NumberOfParameters = 1}
+ };
+ yield return new[] { controllerType, expectedApiDescriptions };
+ }
+ }
+
+ [Theory]
+ [PropertyData("HiddenAction_DoesNotShowUpOnDescription_PropertyData")]
+ public void HiddenAction_DoesNotShowUpOnDescription(Type controllerType, List<object> expectedResults)
+ {
+ HttpConfiguration config = new HttpConfiguration();
+ config.Routes.MapHttpRoute("Default", "{controller}/{id}", new { id = RouteParameter.Optional });
+
+ DefaultHttpControllerFactory controllerFactory = ApiExplorerHelper.GetStrictControllerFactory(config, controllerType);
+ config.ServiceResolver.SetService(typeof(IHttpControllerFactory), controllerFactory);
+
+ IApiExplorer explorer = config.ServiceResolver.GetApiExplorer();
+ ApiExplorerHelper.VerifyApiDescriptions(explorer.ApiDescriptions, expectedResults);
+ }
+ }
+}
diff --git a/test/System.Web.Http.Integration.Test/ApiExplorer/Controllers/DocumentationController.cs b/test/System.Web.Http.Integration.Test/ApiExplorer/Controllers/DocumentationController.cs
new file mode 100644
index 00000000..f0d52c40
--- /dev/null
+++ b/test/System.Web.Http.Integration.Test/ApiExplorer/Controllers/DocumentationController.cs
@@ -0,0 +1,31 @@
+
+namespace System.Web.Http.ApiExplorer
+{
+ public class DocumentationController : ApiController
+ {
+ [ApiDocumentation("Get action")]
+ public string Get()
+ {
+ return string.Empty;
+ }
+
+ [ApiDocumentation("Post action")]
+ [ApiParameterDocumentation("value", "value parameter")]
+ public void Post(string value)
+ {
+ }
+
+ [ApiDocumentation("Put action")]
+ [ApiParameterDocumentation("id", "id parameter")]
+ [ApiParameterDocumentation("value", "value parameter")]
+ public void Put(int id, string value)
+ {
+ }
+
+ [ApiDocumentation("Delete action")]
+ [ApiParameterDocumentation("id", "id parameter")]
+ public void Delete(int id)
+ {
+ }
+ }
+}
diff --git a/test/System.Web.Http.Integration.Test/ApiExplorer/Controllers/HiddenActionController.cs b/test/System.Web.Http.Integration.Test/ApiExplorer/Controllers/HiddenActionController.cs
new file mode 100644
index 00000000..ab6cb702
--- /dev/null
+++ b/test/System.Web.Http.Integration.Test/ApiExplorer/Controllers/HiddenActionController.cs
@@ -0,0 +1,29 @@
+using System.Web.Http.Description;
+namespace System.Web.Http.ApiExplorer
+{
+ public class HiddenActionController : ApiController
+ {
+ public string GetVisibleAction(int id)
+ {
+ return "visible action";
+ }
+
+ [HttpPost]
+ [ApiExplorerSettings(IgnoreApi = true)]
+ public void AddData()
+ {
+ }
+
+ [ApiExplorerSettings(IgnoreApi = true)]
+ public int Get()
+ {
+ return 0;
+ }
+
+ [NonAction]
+ public string GetHiddenAction()
+ {
+ return "Hidden action";
+ }
+ }
+}
diff --git a/test/System.Web.Http.Integration.Test/ApiExplorer/Controllers/HiddenController.cs b/test/System.Web.Http.Integration.Test/ApiExplorer/Controllers/HiddenController.cs
new file mode 100644
index 00000000..990da738
--- /dev/null
+++ b/test/System.Web.Http.Integration.Test/ApiExplorer/Controllers/HiddenController.cs
@@ -0,0 +1,28 @@
+using System.Web.Http.Description;
+namespace System.Web.Http.ApiExplorer
+{
+ [ApiExplorerSettings(IgnoreApi = true)]
+ public class HiddenController : ApiController
+ {
+ public string Get(int id)
+ {
+ return "visible action";
+ }
+
+ [HttpPost]
+ public void AddData()
+ {
+ }
+
+ public int Get()
+ {
+ return 0;
+ }
+
+ [NonAction]
+ public string GetHiddenAction()
+ {
+ return "Hidden action";
+ }
+ }
+}
diff --git a/test/System.Web.Http.Integration.Test/ApiExplorer/Controllers/ItemController.cs b/test/System.Web.Http.Integration.Test/ApiExplorer/Controllers/ItemController.cs
new file mode 100644
index 00000000..03818774
--- /dev/null
+++ b/test/System.Web.Http.Integration.Test/ApiExplorer/Controllers/ItemController.cs
@@ -0,0 +1,32 @@
+namespace System.Web.Http.ApiExplorer
+{
+ public class ItemController : ApiController
+ {
+ public Item GetItem(string name, int series)
+ {
+ return new Item()
+ {
+ Name = name,
+ Series = series
+ };
+ }
+
+ [HttpPost]
+ [HttpPut]
+ public Item PostItem(Item item)
+ {
+ return item;
+ }
+
+ [HttpDelete]
+ public void RemoveItem(int id)
+ {
+ }
+
+ public class Item
+ {
+ public int Series { get; set; }
+ public string Name { get; set; }
+ }
+ }
+}
diff --git a/test/System.Web.Http.Integration.Test/ApiExplorer/Controllers/OverloadsController.cs b/test/System.Web.Http.Integration.Test/ApiExplorer/Controllers/OverloadsController.cs
new file mode 100644
index 00000000..87161e7c
--- /dev/null
+++ b/test/System.Web.Http.Integration.Test/ApiExplorer/Controllers/OverloadsController.cs
@@ -0,0 +1,28 @@
+using System.Collections.Generic;
+
+namespace System.Web.Http.ApiExplorer
+{
+ public class OverloadsController : ApiController
+ {
+ public Person Get(int id) { return null; }
+ public List<Person> Get() { return null; }
+ public List<Person> Get(string name) { return null; }
+ public Person GetPersonByNameAndId(string name, int id) { return null; }
+ public Person GetPersonByNameAndAge(string name, int age) { return null; }
+ public Person GetPersonByNameAgeAndSsn(string name, int age, int ssn) { return null; }
+ public Person GetPersonByNameIdAndSsn(string name, int id, int ssn) { return null; }
+ public Person GetPersonByNameAndSsn(string name, int ssn) { return null; }
+ public Person Post(Person Person) { return null; }
+ public Person Post(string name, int age) { return null; }
+ public void Delete(int id, string name = "Default Name") { }
+ public void Delete(int id, string name, int age) { }
+
+ public class Person
+ {
+ public string Name { get; set; }
+ public int ID { get; set; }
+ public int SSN { get; set; }
+ public int Age { get; set; }
+ }
+ }
+}
diff --git a/test/System.Web.Http.Integration.Test/ApiExplorer/Controllers/ParameterSourceController.cs b/test/System.Web.Http.Integration.Test/ApiExplorer/Controllers/ParameterSourceController.cs
new file mode 100644
index 00000000..dd39a62c
--- /dev/null
+++ b/test/System.Web.Http.Integration.Test/ApiExplorer/Controllers/ParameterSourceController.cs
@@ -0,0 +1,58 @@
+using System.Collections.Generic;
+using System.Linq;
+using System.Web.Http.ModelBinding;
+using System.Web.Http.ValueProviders;
+
+namespace System.Web.Http.ApiExplorer
+{
+ public class ParameterSourceController : ApiController
+ {
+ public void GetCompleTypeFromUri([FromUri]ComplexType value, string name)
+ {
+ }
+
+ public void PostSimpleTypeFromBody([FromBody] string name)
+ {
+ }
+
+ public void GetCustomFromUriAttribute([MyFromUriAttribute] ComplexType value, ComplexType bodyValue)
+ {
+ }
+
+ public void GetFromHeaderAttribute([FromHeaderAttribute] string value)
+ {
+ }
+
+ public class ComplexType
+ {
+ public int Id { get; set; }
+ public string Name { get; set; }
+ }
+
+ private class MyFromUriAttribute : ModelBinderAttribute, IUriValueProviderFactory
+ {
+ public override IEnumerable<ValueProviderFactory> GetValueProviderFactories(HttpConfiguration configuration)
+ {
+ var factories = from f in base.GetValueProviderFactories(configuration) where f is IUriValueProviderFactory select f;
+ return factories;
+ }
+ }
+
+ private class FromHeaderAttribute : ModelBinderAttribute
+ {
+ public override IEnumerable<ValueProviderFactory> GetValueProviderFactories(HttpConfiguration configuration)
+ {
+ var factories = new ValueProviderFactory[] { new HeaderValueProvider() };
+ return factories;
+ }
+ }
+
+ private class HeaderValueProvider : ValueProviderFactory
+ {
+ public override IValueProvider GetValueProvider(Controllers.HttpActionContext actionContext)
+ {
+ throw new NotImplementedException();
+ }
+ }
+ }
+}
diff --git a/test/System.Web.Http.Integration.Test/ApiExplorer/DocumentationProviders/AttributeDocumentationProvider.cs b/test/System.Web.Http.Integration.Test/ApiExplorer/DocumentationProviders/AttributeDocumentationProvider.cs
new file mode 100644
index 00000000..02960b62
--- /dev/null
+++ b/test/System.Web.Http.Integration.Test/ApiExplorer/DocumentationProviders/AttributeDocumentationProvider.cs
@@ -0,0 +1,56 @@
+using System.Linq;
+using System.Web.Http.Controllers;
+using System.Web.Http.Description;
+
+namespace System.Web.Http.ApiExplorer
+{
+ public class AttributeDocumentationProvider : IDocumentationProvider
+ {
+ public string GetDocumentation(HttpActionDescriptor actionDescriptor)
+ {
+ var apiDocumentation = actionDescriptor.GetCustomAttributes<ApiDocumentationAttribute>().FirstOrDefault();
+ if (apiDocumentation != null)
+ {
+ return apiDocumentation.Description;
+ }
+
+ return string.Empty;
+ }
+
+ public string GetDocumentation(HttpParameterDescriptor parameterDescriptor)
+ {
+ var parameterDocumentation = parameterDescriptor.ActionDescriptor.GetCustomAttributes<ApiParameterDocumentationAttribute>().FirstOrDefault(param => param.ParameterName == parameterDescriptor.ParameterName);
+ if (parameterDocumentation != null)
+ {
+ return parameterDocumentation.Description;
+ }
+
+ return string.Empty;
+ }
+ }
+
+ [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
+ public sealed class ApiDocumentationAttribute : Attribute
+ {
+ public ApiDocumentationAttribute(string description)
+ {
+ Description = description;
+ }
+
+ public string Description { get; private set; }
+ }
+
+ [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
+ public sealed class ApiParameterDocumentationAttribute : Attribute
+ {
+ public ApiParameterDocumentationAttribute(string parameterName, string description)
+ {
+ ParameterName = parameterName;
+ Description = description;
+ }
+
+ public string ParameterName { get; private set; }
+
+ public string Description { get; private set; }
+ }
+}
diff --git a/test/System.Web.Http.Integration.Test/ApiExplorer/DocumentationTest.cs b/test/System.Web.Http.Integration.Test/ApiExplorer/DocumentationTest.cs
new file mode 100644
index 00000000..33f8f538
--- /dev/null
+++ b/test/System.Web.Http.Integration.Test/ApiExplorer/DocumentationTest.cs
@@ -0,0 +1,65 @@
+using System.Web.Http.Description;
+using System.Web.Http.Dispatcher;
+using System.Web.Http.Properties;
+using Xunit;
+
+namespace System.Web.Http.ApiExplorer
+{
+ public class DocumentationTest
+ {
+ [Fact]
+ public void VerifyDefaultDocumentationMessage()
+ {
+ HttpConfiguration config = new HttpConfiguration();
+ config.Routes.MapHttpRoute("Default", "{controller}/{id}", new { id = RouteParameter.Optional });
+ ItemFormatter customFormatter = new ItemFormatter();
+ config.Formatters.Add(customFormatter);
+
+ DefaultHttpControllerFactory controllerFactory = ApiExplorerHelper.GetStrictControllerFactory(config, typeof(ItemController));
+ config.ServiceResolver.SetService(typeof(IHttpControllerFactory), controllerFactory);
+
+ IApiExplorer explorer = config.ServiceResolver.GetApiExplorer();
+ foreach (ApiDescription description in explorer.ApiDescriptions)
+ {
+ Assert.Equal(
+ String.Format(SRResources.ApiExplorer_DefaultDocumentation, description.ActionDescriptor.ActionName),
+ description.Documentation);
+ foreach (ApiParameterDescription param in description.ParameterDescriptions)
+ {
+ Assert.Equal(
+ String.Format(SRResources.ApiExplorer_DefaultDocumentation, param.Name),
+ param.Documentation);
+ }
+ }
+ }
+
+ [Fact]
+ public void VerifyCustomDocumentationProviderMessage()
+ {
+ HttpConfiguration config = new HttpConfiguration();
+ config.Routes.MapHttpRoute("Default", "{controller}/{id}", new { id = RouteParameter.Optional });
+ ItemFormatter customFormatter = new ItemFormatter();
+ config.Formatters.Add(customFormatter);
+
+ DefaultHttpControllerFactory controllerFactory = ApiExplorerHelper.GetStrictControllerFactory(config, typeof(DocumentationController));
+ config.ServiceResolver.SetService(typeof(IHttpControllerFactory), controllerFactory);
+
+ AttributeDocumentationProvider documentationProvider = new AttributeDocumentationProvider();
+ config.ServiceResolver.SetService(typeof(IDocumentationProvider), documentationProvider);
+
+ IApiExplorer explorer = config.ServiceResolver.GetApiExplorer();
+ foreach (ApiDescription description in explorer.ApiDescriptions)
+ {
+ Assert.Equal(
+ String.Format("{0} action", description.ActionDescriptor.ActionName),
+ description.Documentation);
+ foreach (ApiParameterDescription param in description.ParameterDescriptions)
+ {
+ Assert.Equal(
+ String.Format("{0} parameter", param.Name),
+ param.Documentation);
+ }
+ }
+ }
+ }
+}
diff --git a/test/System.Web.Http.Integration.Test/ApiExplorer/Formatters/ItemFormatter.cs b/test/System.Web.Http.Integration.Test/ApiExplorer/Formatters/ItemFormatter.cs
new file mode 100644
index 00000000..8c89f59f
--- /dev/null
+++ b/test/System.Web.Http.Integration.Test/ApiExplorer/Formatters/ItemFormatter.cs
@@ -0,0 +1,27 @@
+using System.Net.Http.Formatting;
+
+namespace System.Web.Http.ApiExplorer
+{
+ public class ItemFormatter : BufferedMediaTypeFormatter
+ {
+ public override bool CanReadType(Type type)
+ {
+ return typeof(System.Web.Http.ApiExplorer.ItemController.Item).IsAssignableFrom(type);
+ }
+
+ public override bool CanWriteType(Type type)
+ {
+ return typeof(System.Web.Http.ApiExplorer.ItemController.Item).IsAssignableFrom(type);
+ }
+
+ public override object OnReadFromStream(Type type, IO.Stream stream, Net.Http.Headers.HttpContentHeaders contentHeaders, IFormatterLogger formatterLogger)
+ {
+ return base.OnReadFromStream(type, stream, contentHeaders, formatterLogger);
+ }
+
+ public override void OnWriteToStream(Type type, object value, IO.Stream stream, Net.Http.Headers.HttpContentHeaders contentHeaders, Net.TransportContext transportContext)
+ {
+ base.OnWriteToStream(type, value, stream, contentHeaders, transportContext);
+ }
+ }
+}
diff --git a/test/System.Web.Http.Integration.Test/ApiExplorer/FormattersTest.cs b/test/System.Web.Http.Integration.Test/ApiExplorer/FormattersTest.cs
new file mode 100644
index 00000000..5a370177
--- /dev/null
+++ b/test/System.Web.Http.Integration.Test/ApiExplorer/FormattersTest.cs
@@ -0,0 +1,42 @@
+using System.Linq;
+using System.Web.Http.Description;
+using System.Web.Http.Dispatcher;
+using Xunit;
+
+namespace System.Web.Http.ApiExplorer
+{
+ public class FormattersTest
+ {
+ [Fact]
+ public void CustomRequestBodyFormatters_ShowUpOnDescription()
+ {
+ HttpConfiguration config = new HttpConfiguration();
+ config.Routes.MapHttpRoute("Default", "{controller}/{id}", new { id = RouteParameter.Optional });
+ ItemFormatter customFormatter = new ItemFormatter();
+ config.Formatters.Add(customFormatter);
+
+ DefaultHttpControllerFactory controllerFactory = ApiExplorerHelper.GetStrictControllerFactory(config, typeof(ItemController));
+ config.ServiceResolver.SetService(typeof(IHttpControllerFactory), controllerFactory);
+
+ IApiExplorer explorer = config.ServiceResolver.GetApiExplorer();
+ ApiDescription description = explorer.ApiDescriptions.FirstOrDefault(desc => desc.ActionDescriptor.ActionName == "PostItem");
+ Assert.True(description.SupportedRequestBodyFormatters.Any(formatter => formatter == customFormatter), "Did not find the custom formatter on the SupportedRequestBodyFormatters.");
+ }
+
+ [Fact]
+ public void CustomResponseFormatters_ShowUpOnDescription()
+ {
+ HttpConfiguration config = new HttpConfiguration();
+ config.Routes.MapHttpRoute("Default", "{controller}/{id}", new { id = RouteParameter.Optional });
+ ItemFormatter customFormatter = new ItemFormatter();
+ config.Formatters.Add(customFormatter);
+
+ DefaultHttpControllerFactory controllerFactory = ApiExplorerHelper.GetStrictControllerFactory(config, typeof(ItemController));
+ config.ServiceResolver.SetService(typeof(IHttpControllerFactory), controllerFactory);
+
+ IApiExplorer explorer = config.ServiceResolver.GetApiExplorer();
+ ApiDescription description = explorer.ApiDescriptions.FirstOrDefault(desc => desc.ActionDescriptor.ActionName == "PostItem");
+ Assert.True(description.SupportedResponseFormatters.Any(formatter => formatter == customFormatter), "Did not find the custom formatter on the SupportedResponseFormatters.");
+ }
+ }
+}
diff --git a/test/System.Web.Http.Integration.Test/ApiExplorer/ParameterSourceTest.cs b/test/System.Web.Http.Integration.Test/ApiExplorer/ParameterSourceTest.cs
new file mode 100644
index 00000000..00783ce6
--- /dev/null
+++ b/test/System.Web.Http.Integration.Test/ApiExplorer/ParameterSourceTest.cs
@@ -0,0 +1,57 @@
+using System.Linq;
+using System.Web.Http.Description;
+using System.Web.Http.Dispatcher;
+using Xunit;
+
+namespace System.Web.Http.ApiExplorer
+{
+ public class ParameterSourceTest
+ {
+ [Fact]
+ public void FromUriParameterSource_ShowUpCorrectlyOnDescription()
+ {
+ HttpConfiguration config = new HttpConfiguration();
+ config.Routes.MapHttpRoute("Default", "{controller}/{action}/{id}", new { id = RouteParameter.Optional });
+ DefaultHttpControllerFactory controllerFactory = ApiExplorerHelper.GetStrictControllerFactory(config, typeof(ParameterSourceController));
+ config.ServiceResolver.SetService(typeof(IHttpControllerFactory), controllerFactory);
+ IApiExplorer explorer = config.ServiceResolver.GetApiExplorer();
+
+ ApiDescription description = explorer.ApiDescriptions.FirstOrDefault(desc => desc.ActionDescriptor.ActionName == "GetCompleTypeFromUri");
+ Assert.NotNull(description);
+ Assert.True(description.ParameterDescriptions.All(param => param.Source == ApiParameterSource.FromUri), "All parameters should come from URI.");
+
+ description = explorer.ApiDescriptions.FirstOrDefault(desc => desc.ActionDescriptor.ActionName == "GetCustomFromUriAttribute");
+ Assert.NotNull(description);
+ Assert.True(description.ParameterDescriptions.Any(param => param.Source == ApiParameterSource.FromUri && param.Name == "value"), "The 'value' parameter should come from URI.");
+ Assert.True(description.ParameterDescriptions.Any(param => param.Source == ApiParameterSource.FromBody && param.Name == "bodyValue"), "The 'bodyValue' parameter should come from body.");
+ }
+
+ [Fact]
+ public void FromBodyParameterSource_ShowUpCorrectlyOnDescription()
+ {
+ HttpConfiguration config = new HttpConfiguration();
+ config.Routes.MapHttpRoute("Default", "{controller}/{action}/{id}", new { id = RouteParameter.Optional });
+ DefaultHttpControllerFactory controllerFactory = ApiExplorerHelper.GetStrictControllerFactory(config, typeof(ParameterSourceController));
+ config.ServiceResolver.SetService(typeof(IHttpControllerFactory), controllerFactory);
+ IApiExplorer explorer = config.ServiceResolver.GetApiExplorer();
+
+ ApiDescription description = explorer.ApiDescriptions.FirstOrDefault(desc => desc.ActionDescriptor.ActionName == "PostSimpleTypeFromBody");
+ Assert.NotNull(description);
+ Assert.True(description.ParameterDescriptions.All(param => param.Source == ApiParameterSource.FromBody), "The parameter should come from Body.");
+ }
+
+ [Fact]
+ public void UnknownParameterSource_ShowUpCorrectlyOnDescription()
+ {
+ HttpConfiguration config = new HttpConfiguration();
+ config.Routes.MapHttpRoute("Default", "{controller}/{action}/{id}", new { id = RouteParameter.Optional });
+ DefaultHttpControllerFactory controllerFactory = ApiExplorerHelper.GetStrictControllerFactory(config, typeof(ParameterSourceController));
+ config.ServiceResolver.SetService(typeof(IHttpControllerFactory), controllerFactory);
+ IApiExplorer explorer = config.ServiceResolver.GetApiExplorer();
+
+ ApiDescription description = explorer.ApiDescriptions.FirstOrDefault(desc => desc.ActionDescriptor.ActionName == "GetFromHeaderAttribute");
+ Assert.NotNull(description);
+ Assert.True(description.ParameterDescriptions.All(param => param.Source == ApiParameterSource.Unknown), "The parameter source should be Unknown.");
+ }
+ }
+}
diff --git a/test/System.Web.Http.Integration.Test/ApiExplorer/RouteConstraintsTest.cs b/test/System.Web.Http.Integration.Test/ApiExplorer/RouteConstraintsTest.cs
new file mode 100644
index 00000000..48039aa2
--- /dev/null
+++ b/test/System.Web.Http.Integration.Test/ApiExplorer/RouteConstraintsTest.cs
@@ -0,0 +1,116 @@
+using System.Collections.Generic;
+using System.Net.Http;
+using System.Web.Http.Description;
+using System.Web.Http.Dispatcher;
+using System.Web.Http.Routing;
+using Xunit.Extensions;
+
+namespace System.Web.Http.ApiExplorer
+{
+ public class RouteConstraintsTest
+ {
+ public static IEnumerable<object[]> HttpMethodConstraints_LimitsTheDescriptions_PropertyData
+ {
+ get
+ {
+ object controllerType = typeof(ItemController);
+ object expectedApiDescriptions = new List<object>
+ {
+ new { HttpMethod = HttpMethod.Get, RelativePath = "Item?name={name}&series={series}", HasRequestFormatters = false, HasResponseFormatters = true, NumberOfParameters = 2},
+ new { HttpMethod = HttpMethod.Put, RelativePath = "Item", HasRequestFormatters = true, HasResponseFormatters = true, NumberOfParameters = 1}
+ };
+ yield return new[] { controllerType, expectedApiDescriptions };
+
+ controllerType = typeof(OverloadsController);
+ expectedApiDescriptions = new List<object>
+ {
+ new { HttpMethod = HttpMethod.Get, RelativePath = "Overloads/{id}", HasRequestFormatters = false, HasResponseFormatters = true, NumberOfParameters = 1},
+ new { HttpMethod = HttpMethod.Get, RelativePath = "Overloads", HasRequestFormatters = false, HasResponseFormatters = true, NumberOfParameters = 0},
+ new { HttpMethod = HttpMethod.Get, RelativePath = "Overloads?name={name}", HasRequestFormatters = false, HasResponseFormatters = true, NumberOfParameters = 1},
+ new { HttpMethod = HttpMethod.Get, RelativePath = "Overloads/{id}?name={name}", HasRequestFormatters = false, HasResponseFormatters = true, NumberOfParameters = 2},
+ new { HttpMethod = HttpMethod.Get, RelativePath = "Overloads?name={name}&age={age}", HasRequestFormatters = false, HasResponseFormatters = true, NumberOfParameters = 2},
+ new { HttpMethod = HttpMethod.Get, RelativePath = "Overloads?name={name}&age={age}&ssn={ssn}", HasRequestFormatters = false, HasResponseFormatters = true, NumberOfParameters = 3},
+ new { HttpMethod = HttpMethod.Get, RelativePath = "Overloads/{id}?name={name}&ssn={ssn}", HasRequestFormatters = false, HasResponseFormatters = true, NumberOfParameters = 3},
+ new { HttpMethod = HttpMethod.Get, RelativePath = "Overloads?name={name}&ssn={ssn}", HasRequestFormatters = false, HasResponseFormatters = true, NumberOfParameters = 2}
+ };
+ yield return new[] { controllerType, expectedApiDescriptions };
+ }
+ }
+
+ [Theory]
+ [PropertyData("HttpMethodConstraints_LimitsTheDescriptions_PropertyData")]
+ public void HttpMethodConstraints_LimitsTheDescriptions(Type controllerType, List<object> expectedResults)
+ {
+ HttpConfiguration config = new HttpConfiguration();
+ config.Routes.MapHttpRoute("Default", "{controller}/{id}", new { id = RouteParameter.Optional }, new { routeConstraint = new HttpMethodConstraint(HttpMethod.Get, HttpMethod.Put) });
+
+ DefaultHttpControllerFactory controllerFactory = ApiExplorerHelper.GetStrictControllerFactory(config, controllerType);
+ config.ServiceResolver.SetService(typeof(IHttpControllerFactory), controllerFactory);
+
+ IApiExplorer explorer = config.ServiceResolver.GetApiExplorer();
+ ApiExplorerHelper.VerifyApiDescriptions(explorer.ApiDescriptions, expectedResults);
+ }
+
+ public static IEnumerable<object[]> RegexConstraint_LimitsTheController_PropertyData
+ {
+ get
+ {
+ object[] controllerTypes = new Type[] { typeof(OverloadsController), typeof(ItemController) };
+ object expectedApiDescriptions = new List<object>
+ {
+ new { HttpMethod = HttpMethod.Get, RelativePath = "Item?name={name}&series={series}", HasRequestFormatters = false, HasResponseFormatters = true, NumberOfParameters = 2},
+ new { HttpMethod = HttpMethod.Post, RelativePath = "Item", HasRequestFormatters = true, HasResponseFormatters = true, NumberOfParameters = 1},
+ new { HttpMethod = HttpMethod.Put, RelativePath = "Item", HasRequestFormatters = true, HasResponseFormatters = true, NumberOfParameters = 1},
+ new { HttpMethod = HttpMethod.Delete, RelativePath = "Item/{id}", HasRequestFormatters = false, HasResponseFormatters = false, NumberOfParameters = 1}
+ };
+ yield return new[] { controllerTypes, expectedApiDescriptions };
+ }
+ }
+
+ [Theory]
+ [PropertyData("RegexConstraint_LimitsTheController_PropertyData")]
+ public void RegexConstraint_LimitsTheController(Type[] controllerTypes, List<object> expectedResults)
+ {
+ HttpConfiguration config = new HttpConfiguration();
+ config.Routes.MapHttpRoute("Default", "{controller}/{id}", new { id = RouteParameter.Optional }, new { controller = "It.*" }); // controllers that start with "It"
+
+ DefaultHttpControllerFactory controllerFactory = ApiExplorerHelper.GetStrictControllerFactory(config, controllerTypes);
+ config.ServiceResolver.SetService(typeof(IHttpControllerFactory), controllerFactory);
+
+ IApiExplorer explorer = config.ServiceResolver.GetApiExplorer();
+ ApiExplorerHelper.VerifyApiDescriptions(explorer.ApiDescriptions, expectedResults);
+ }
+
+ public static IEnumerable<object[]> RegexConstraint_LimitsTheAction_PropertyData
+ {
+ get
+ {
+ object[] controllerTypes = new Type[] { typeof(OverloadsController), typeof(ItemController) };
+ object expectedApiDescriptions = new List<object>
+ {
+ new { HttpMethod = HttpMethod.Get, RelativePath = "Item/GetItem?name={name}&series={series}", HasRequestFormatters = false, HasResponseFormatters = true, NumberOfParameters = 2},
+ new { HttpMethod = HttpMethod.Get, RelativePath = "Overloads/GetPersonByNameAndId/{id}?name={name}", HasRequestFormatters = false, HasResponseFormatters = true, NumberOfParameters = 2},
+ new { HttpMethod = HttpMethod.Get, RelativePath = "Overloads/GetPersonByNameAndAge?name={name}&age={age}", HasRequestFormatters = false, HasResponseFormatters = true, NumberOfParameters = 2},
+ new { HttpMethod = HttpMethod.Get, RelativePath = "Overloads/GetPersonByNameAgeAndSsn?name={name}&age={age}&ssn={ssn}", HasRequestFormatters = false, HasResponseFormatters = true, NumberOfParameters = 3},
+ new { HttpMethod = HttpMethod.Get, RelativePath = "Overloads/GetPersonByNameIdAndSsn/{id}?name={name}&ssn={ssn}", HasRequestFormatters = false, HasResponseFormatters = true, NumberOfParameters = 3},
+ new { HttpMethod = HttpMethod.Get, RelativePath = "Overloads/GetPersonByNameAndSsn?name={name}&ssn={ssn}", HasRequestFormatters = false, HasResponseFormatters = true, NumberOfParameters = 2}
+ };
+ yield return new[] { controllerTypes, expectedApiDescriptions };
+ }
+ }
+
+ [Theory]
+ [PropertyData("RegexConstraint_LimitsTheAction_PropertyData")]
+ public void RegexConstraint_LimitsTheAction(Type[] controllerTypes, List<object> expectedResults)
+ {
+ HttpConfiguration config = new HttpConfiguration();
+ config.Routes.MapHttpRoute("Default", "{controller}/{action}/{id}", new { id = RouteParameter.Optional }, new { action = "Get.+" }); // actions that start with "Get" and at least one extra character
+
+ DefaultHttpControllerFactory controllerFactory = ApiExplorerHelper.GetStrictControllerFactory(config, controllerTypes);
+ config.ServiceResolver.SetService(typeof(IHttpControllerFactory), controllerFactory);
+
+ IApiExplorer explorer = config.ServiceResolver.GetApiExplorer();
+ ApiExplorerHelper.VerifyApiDescriptions(explorer.ApiDescriptions, expectedResults);
+ }
+ }
+}
diff --git a/test/System.Web.Http.Integration.Test/ApiExplorer/RoutesTest.cs b/test/System.Web.Http.Integration.Test/ApiExplorer/RoutesTest.cs
new file mode 100644
index 00000000..daf8ed84
--- /dev/null
+++ b/test/System.Web.Http.Integration.Test/ApiExplorer/RoutesTest.cs
@@ -0,0 +1,203 @@
+using System.Collections.Generic;
+using System.Net.Http;
+using System.Web.Http.Description;
+using System.Web.Http.Dispatcher;
+using Xunit;
+using Xunit.Extensions;
+
+namespace System.Web.Http.ApiExplorer
+{
+ public class RoutesTest
+ {
+ [Fact]
+ public void VerifyDescription_OnEmptyRoute()
+ {
+ HttpConfiguration config = new HttpConfiguration();
+ IApiExplorer explorer = config.ServiceResolver.GetApiExplorer();
+
+ Assert.NotNull(explorer);
+ Assert.Equal(0, explorer.ApiDescriptions.Count);
+ }
+
+ public static IEnumerable<object[]> VerifyDescription_OnDefaultRoute_PropertyData
+ {
+ get
+ {
+ object controllerType = typeof(ItemController);
+ object expectedApiDescriptions = new List<object>
+ {
+ new { HttpMethod = HttpMethod.Get, RelativePath = "Item?name={name}&series={series}", HasRequestFormatters = false, HasResponseFormatters = true, NumberOfParameters = 2},
+ new { HttpMethod = HttpMethod.Post, RelativePath = "Item", HasRequestFormatters = true, HasResponseFormatters = true, NumberOfParameters = 1},
+ new { HttpMethod = HttpMethod.Put, RelativePath = "Item", HasRequestFormatters = true, HasResponseFormatters = true, NumberOfParameters = 1},
+ new { HttpMethod = HttpMethod.Delete, RelativePath = "Item/{id}", HasRequestFormatters = false, HasResponseFormatters = false, NumberOfParameters = 1}
+ };
+ yield return new[] { controllerType, expectedApiDescriptions };
+
+ controllerType = typeof(OverloadsController);
+ expectedApiDescriptions = new List<object>
+ {
+ new { HttpMethod = HttpMethod.Get, RelativePath = "Overloads/{id}", HasRequestFormatters = false, HasResponseFormatters = true, NumberOfParameters = 1},
+ new { HttpMethod = HttpMethod.Get, RelativePath = "Overloads", HasRequestFormatters = false, HasResponseFormatters = true, NumberOfParameters = 0},
+ new { HttpMethod = HttpMethod.Get, RelativePath = "Overloads?name={name}", HasRequestFormatters = false, HasResponseFormatters = true, NumberOfParameters = 1},
+ new { HttpMethod = HttpMethod.Get, RelativePath = "Overloads/{id}?name={name}", HasRequestFormatters = false, HasResponseFormatters = true, NumberOfParameters = 2},
+ new { HttpMethod = HttpMethod.Get, RelativePath = "Overloads?name={name}&age={age}", HasRequestFormatters = false, HasResponseFormatters = true, NumberOfParameters = 2},
+ new { HttpMethod = HttpMethod.Get, RelativePath = "Overloads?name={name}&age={age}&ssn={ssn}", HasRequestFormatters = false, HasResponseFormatters = true, NumberOfParameters = 3},
+ new { HttpMethod = HttpMethod.Get, RelativePath = "Overloads/{id}?name={name}&ssn={ssn}", HasRequestFormatters = false, HasResponseFormatters = true, NumberOfParameters = 3},
+ new { HttpMethod = HttpMethod.Get, RelativePath = "Overloads?name={name}&ssn={ssn}", HasRequestFormatters = false, HasResponseFormatters = true, NumberOfParameters = 2},
+ new { HttpMethod = HttpMethod.Post, RelativePath = "Overloads", HasRequestFormatters = true, HasResponseFormatters = true, NumberOfParameters = 1},
+ new { HttpMethod = HttpMethod.Post, RelativePath = "Overloads?name={name}&age={age}", HasRequestFormatters = false, HasResponseFormatters = true, NumberOfParameters = 2},
+ new { HttpMethod = HttpMethod.Delete, RelativePath = "Overloads/{id}?name={name}", HasRequestFormatters = false, HasResponseFormatters = false, NumberOfParameters = 2},
+ new { HttpMethod = HttpMethod.Delete, RelativePath = "Overloads/{id}?name={name}&age={age}", HasRequestFormatters = false, HasResponseFormatters = false, NumberOfParameters = 3}
+ };
+ yield return new[] { controllerType, expectedApiDescriptions };
+ }
+ }
+
+ [Theory]
+ [PropertyData("VerifyDescription_OnDefaultRoute_PropertyData")]
+ public void VerifyDescription_OnDefaultRoute(Type controllerType, List<object> expectedResults)
+ {
+ HttpConfiguration config = new HttpConfiguration();
+ config.Routes.MapHttpRoute("Default", "{controller}/{id}", new { id = RouteParameter.Optional });
+
+ DefaultHttpControllerFactory controllerFactory = ApiExplorerHelper.GetStrictControllerFactory(config, controllerType);
+ config.ServiceResolver.SetService(typeof(IHttpControllerFactory), controllerFactory);
+
+ IApiExplorer explorer = config.ServiceResolver.GetApiExplorer();
+ ApiExplorerHelper.VerifyApiDescriptions(explorer.ApiDescriptions, expectedResults);
+ }
+
+ public static IEnumerable<object[]> VerifyDescription_OnRouteWithControllerOnDefaults_PropertyData
+ {
+ get
+ {
+ object controllerType = typeof(ItemController);
+ object expectedApiDescriptions = new List<object>
+ {
+ new { HttpMethod = HttpMethod.Get, RelativePath = "myitem?name={name}&series={series}", HasRequestFormatters = false, HasResponseFormatters = true, NumberOfParameters = 2},
+ new { HttpMethod = HttpMethod.Post, RelativePath = "myitem", HasRequestFormatters = true, HasResponseFormatters = true, NumberOfParameters = 1},
+ new { HttpMethod = HttpMethod.Put, RelativePath = "myitem", HasRequestFormatters = true, HasResponseFormatters = true, NumberOfParameters = 1},
+ new { HttpMethod = HttpMethod.Delete, RelativePath = "myitem/{id}", HasRequestFormatters = false, HasResponseFormatters = false, NumberOfParameters = 1}
+ };
+ yield return new[] { controllerType, expectedApiDescriptions };
+ }
+ }
+
+ [Theory]
+ [PropertyData("VerifyDescription_OnRouteWithControllerOnDefaults_PropertyData")]
+ public void VerifyDescription_OnRouteWithControllerOnDefaults(Type controllerType, List<object> expectedResults)
+ {
+ HttpConfiguration config = new HttpConfiguration();
+ config.Routes.MapHttpRoute("Default", "myitem/{id}", new { controller = "Item", id = RouteParameter.Optional });
+
+ DefaultHttpControllerFactory controllerFactory = ApiExplorerHelper.GetStrictControllerFactory(config, controllerType);
+ config.ServiceResolver.SetService(typeof(IHttpControllerFactory), controllerFactory);
+
+ IApiExplorer explorer = config.ServiceResolver.GetApiExplorer();
+ ApiExplorerHelper.VerifyApiDescriptions(explorer.ApiDescriptions, expectedResults);
+ }
+
+ public static IEnumerable<object[]> VerifyDescription_OnRouteWithActionVariable_PropertyData
+ {
+ get
+ {
+ object controllerType = typeof(ItemController);
+ object expectedApiDescriptions = new List<object>
+ {
+ new { HttpMethod = HttpMethod.Get, RelativePath = "Item/GetItem?name={name}&series={series}", HasRequestFormatters = false, HasResponseFormatters = true, NumberOfParameters = 2},
+ new { HttpMethod = HttpMethod.Post, RelativePath = "Item/PostItem", HasRequestFormatters = true, HasResponseFormatters = true, NumberOfParameters = 1},
+ new { HttpMethod = HttpMethod.Put, RelativePath = "Item/PostItem", HasRequestFormatters = true, HasResponseFormatters = true, NumberOfParameters = 1},
+ new { HttpMethod = HttpMethod.Delete, RelativePath = "Item/RemoveItem/{id}", HasRequestFormatters = false, HasResponseFormatters = false, NumberOfParameters = 1}
+ };
+ yield return new[] { controllerType, expectedApiDescriptions };
+
+ controllerType = typeof(OverloadsController);
+ expectedApiDescriptions = new List<object>
+ {
+ new { HttpMethod = HttpMethod.Get, RelativePath = "Overloads/Get/{id}", HasRequestFormatters = false, HasResponseFormatters = true, NumberOfParameters = 1},
+ new { HttpMethod = HttpMethod.Get, RelativePath = "Overloads/Get", HasRequestFormatters = false, HasResponseFormatters = true, NumberOfParameters = 0},
+ new { HttpMethod = HttpMethod.Get, RelativePath = "Overloads/Get?name={name}", HasRequestFormatters = false, HasResponseFormatters = true, NumberOfParameters = 1},
+ new { HttpMethod = HttpMethod.Get, RelativePath = "Overloads/GetPersonByNameAndId/{id}?name={name}", HasRequestFormatters = false, HasResponseFormatters = true, NumberOfParameters = 2},
+ new { HttpMethod = HttpMethod.Get, RelativePath = "Overloads/GetPersonByNameAndAge?name={name}&age={age}", HasRequestFormatters = false, HasResponseFormatters = true, NumberOfParameters = 2},
+ new { HttpMethod = HttpMethod.Get, RelativePath = "Overloads/GetPersonByNameAgeAndSsn?name={name}&age={age}&ssn={ssn}", HasRequestFormatters = false, HasResponseFormatters = true, NumberOfParameters = 3},
+ new { HttpMethod = HttpMethod.Get, RelativePath = "Overloads/GetPersonByNameIdAndSsn/{id}?name={name}&ssn={ssn}", HasRequestFormatters = false, HasResponseFormatters = true, NumberOfParameters = 3},
+ new { HttpMethod = HttpMethod.Get, RelativePath = "Overloads/GetPersonByNameAndSsn?name={name}&ssn={ssn}", HasRequestFormatters = false, HasResponseFormatters = true, NumberOfParameters = 2},
+ new { HttpMethod = HttpMethod.Post, RelativePath = "Overloads/Post", HasRequestFormatters = true, HasResponseFormatters = true, NumberOfParameters = 1},
+ new { HttpMethod = HttpMethod.Post, RelativePath = "Overloads/Post?name={name}&age={age}", HasRequestFormatters = false, HasResponseFormatters = true, NumberOfParameters = 2},
+ new { HttpMethod = HttpMethod.Delete, RelativePath = "Overloads/Delete/{id}?name={name}", HasRequestFormatters = false, HasResponseFormatters = false, NumberOfParameters = 2},
+ new { HttpMethod = HttpMethod.Delete, RelativePath = "Overloads/Delete/{id}?name={name}&age={age}", HasRequestFormatters = false, HasResponseFormatters = false, NumberOfParameters = 3}
+ };
+ yield return new[] { controllerType, expectedApiDescriptions };
+ }
+ }
+
+ [Theory]
+ [PropertyData("VerifyDescription_OnRouteWithActionVariable_PropertyData")]
+ public void VerifyDescription_OnRouteWithActionVariable(Type controllerType, List<object> expectedResults)
+ {
+ HttpConfiguration config = new HttpConfiguration();
+ config.Routes.MapHttpRoute("Default", "{controller}/{action}/{id}", new { id = RouteParameter.Optional });
+
+ DefaultHttpControllerFactory controllerFactory = ApiExplorerHelper.GetStrictControllerFactory(config, controllerType);
+ config.ServiceResolver.SetService(typeof(IHttpControllerFactory), controllerFactory);
+
+ IApiExplorer explorer = config.ServiceResolver.GetApiExplorer();
+ ApiExplorerHelper.VerifyApiDescriptions(explorer.ApiDescriptions, expectedResults);
+ }
+
+ public static IEnumerable<object[]> VerifyDescription_On_RouteWithActionOnDefaults_PropertyData
+ {
+ get
+ {
+ object controllerType = typeof(ItemController);
+ object expectedApiDescriptions = new List<object>
+ {
+ new { HttpMethod = HttpMethod.Delete, RelativePath = "Item/{id}", HasRequestFormatters = false, HasResponseFormatters = false, NumberOfParameters = 1}
+ };
+ yield return new[] { controllerType, expectedApiDescriptions };
+ }
+ }
+
+ [Theory]
+ [PropertyData("VerifyDescription_On_RouteWithActionOnDefaults_PropertyData")]
+ public void VerifyDescription_On_RouteWithActionOnDefaults(Type controllerType, List<object> expectedResults)
+ {
+ HttpConfiguration config = new HttpConfiguration();
+ config.Routes.MapHttpRoute("Default", "{controller}/{id}", new { action = "RemoveItem", id = RouteParameter.Optional });
+
+ DefaultHttpControllerFactory controllerFactory = ApiExplorerHelper.GetStrictControllerFactory(config, controllerType);
+ config.ServiceResolver.SetService(typeof(IHttpControllerFactory), controllerFactory);
+
+ IApiExplorer explorer = config.ServiceResolver.GetApiExplorer();
+ ApiExplorerHelper.VerifyApiDescriptions(explorer.ApiDescriptions, expectedResults);
+ }
+
+ [Fact]
+ public void InvalidActionNameOnRoute_DoesNotThrow()
+ {
+ Type controllerType = typeof(OverloadsController);
+ HttpConfiguration config = new HttpConfiguration();
+ config.Routes.MapHttpRoute("Default", "{controller}/{id}", new { action = "ActionThatDoesNotExist", id = RouteParameter.Optional });
+
+ DefaultHttpControllerFactory controllerFactory = ApiExplorerHelper.GetStrictControllerFactory(config, controllerType);
+ config.ServiceResolver.SetService(typeof(IHttpControllerFactory), controllerFactory);
+
+ IApiExplorer explorer = config.ServiceResolver.GetApiExplorer();
+ Assert.Empty(explorer.ApiDescriptions);
+ }
+
+ [Fact]
+ public void InvalidControllerNameOnRoute_DoesNotThrow()
+ {
+ Type controllerType = typeof(OverloadsController);
+ HttpConfiguration config = new HttpConfiguration();
+ config.Routes.MapHttpRoute("Default", "mycontroller/{id}", new { controller = "ControllerThatDoesNotExist", id = RouteParameter.Optional });
+
+ DefaultHttpControllerFactory controllerFactory = ApiExplorerHelper.GetStrictControllerFactory(config, controllerType);
+ config.ServiceResolver.SetService(typeof(IHttpControllerFactory), controllerFactory);
+
+ IApiExplorer explorer = config.ServiceResolver.GetApiExplorer();
+ Assert.Empty(explorer.ApiDescriptions);
+ }
+ }
+}
diff --git a/test/System.Web.Http.Integration.Test/Authentication/BasicOverHttpTest.cs b/test/System.Web.Http.Integration.Test/Authentication/BasicOverHttpTest.cs
new file mode 100644
index 00000000..990cba36
--- /dev/null
+++ b/test/System.Web.Http.Integration.Test/Authentication/BasicOverHttpTest.cs
@@ -0,0 +1,74 @@
+using System.Net;
+using System.Net.Http;
+using System.Web.Http.SelfHost;
+using Xunit;
+
+namespace System.Web.Http
+{
+ public class BasicOverHttpTest
+ {
+ private static readonly string BaseAddress = "http://localhost:8080";
+
+ [Fact]
+ public void AuthenticateWithUsernameTokenSucceed()
+ {
+ RunBasicAuthTest("Sample", "", new NetworkCredential("username", "password"),
+ (response) => Assert.Equal(HttpStatusCode.OK, response.StatusCode)
+ );
+ }
+
+ [Fact]
+ public void AuthenticateWithWrongPasswordFail()
+ {
+ RunBasicAuthTest("Sample", "", new NetworkCredential("username", "wrong password"),
+ (response) => Assert.Equal(HttpStatusCode.Forbidden, response.StatusCode)
+ );
+ }
+
+ [Fact]
+ public void AuthenticateWithNoCredentialFail()
+ {
+ RunBasicAuthTest("Sample", "", null,
+ (response) => Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode)
+ );
+ }
+
+ private static void RunBasicAuthTest(string controllerName, string routeSuffix, NetworkCredential credential, Action<HttpResponseMessage> assert)
+ {
+ // Arrange
+ HttpSelfHostConfiguration config = new HttpSelfHostConfiguration(BaseAddress);
+ config.Routes.MapHttpRoute("Default", "{controller}" + routeSuffix, new { controller = controllerName });
+ config.UserNamePasswordValidator = new CustomUsernamePasswordValidator();
+ config.MessageHandlers.Add(new CustomMessageHandler());
+ HttpSelfHostServer server = new HttpSelfHostServer(config);
+
+ server.OpenAsync().Wait();
+
+ // Create a GET request with correct username and password
+ HttpClientHandler handler = new HttpClientHandler();
+ handler.Credentials = credential;
+ HttpClient client = new HttpClient(handler);
+
+ HttpResponseMessage response = null;
+ try
+ {
+ // Act
+ response = client.GetAsync(BaseAddress).Result;
+
+ // Assert
+ assert(response);
+ }
+ finally
+ {
+ if (response != null)
+ {
+ response.Dispose();
+ }
+ client.Dispose();
+ }
+
+ server.CloseAsync().Wait();
+ }
+
+ }
+}
diff --git a/test/System.Web.Http.Integration.Test/Authentication/CustomMessageHandler.cs b/test/System.Web.Http.Integration.Test/Authentication/CustomMessageHandler.cs
new file mode 100644
index 00000000..ebcb56a9
--- /dev/null
+++ b/test/System.Web.Http.Integration.Test/Authentication/CustomMessageHandler.cs
@@ -0,0 +1,33 @@
+using System.Net.Http;
+using System.Security.Principal;
+using System.ServiceModel;
+using System.ServiceModel.Security;
+using System.Threading;
+using System.Threading.Tasks;
+using System.Web.Http.Hosting;
+using System.Web.Http.SelfHost;
+
+namespace System.Web.Http
+{
+ public class CustomMessageHandler : DelegatingHandler
+ {
+ protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
+ {
+ // here you can see the requestor's identity via the request message
+ // convert the Generic Identity to some IPrincipal object, and set it in the request's property
+ // later the authorization filter will use the role information to authorize request.
+ SecurityMessageProperty property;
+ if (request.Properties.TryGetValue<SecurityMessageProperty>(HttpSelfHostServer.SecurityKey, out property))
+ {
+ ServiceSecurityContext context = property.ServiceSecurityContext;
+
+ if (context.PrimaryIdentity.Name == "username")
+ {
+ request.Properties.Add(HttpPropertyKeys.UserPrincipalKey, new GenericPrincipal(context.PrimaryIdentity, new string[] { "Administrators" }));
+ }
+ }
+
+ return base.SendAsync(request, cancellationToken);
+ }
+ }
+}
diff --git a/test/System.Web.Http.Integration.Test/Authentication/CustomUsernamePasswordValidator.cs b/test/System.Web.Http.Integration.Test/Authentication/CustomUsernamePasswordValidator.cs
new file mode 100644
index 00000000..f4bb589d
--- /dev/null
+++ b/test/System.Web.Http.Integration.Test/Authentication/CustomUsernamePasswordValidator.cs
@@ -0,0 +1,19 @@
+using System.IdentityModel.Selectors;
+
+namespace System.Web.Http
+{
+ public class CustomUsernamePasswordValidator : UserNamePasswordValidator
+ {
+ public override void Validate(string userName, string password)
+ {
+ if (userName == "username" && password == "password")
+ {
+ return;
+ }
+ else
+ {
+ throw new InvalidOperationException();
+ }
+ }
+ }
+}
diff --git a/test/System.Web.Http.Integration.Test/Authentication/RequireAdminAttribute.cs b/test/System.Web.Http.Integration.Test/Authentication/RequireAdminAttribute.cs
new file mode 100644
index 00000000..9ba6d270
--- /dev/null
+++ b/test/System.Web.Http.Integration.Test/Authentication/RequireAdminAttribute.cs
@@ -0,0 +1,21 @@
+using System.Net;
+using System.Net.Http;
+using System.Security.Principal;
+using System.Web.Http.Controllers;
+using System.Web.Http.Filters;
+
+namespace System.Web.Http
+{
+ public class RequireAdminAttribute : AuthorizationFilterAttribute
+ {
+ public override void OnAuthorization(HttpActionContext context)
+ {
+ // do authorization based on the principle.
+ IPrincipal principal = context.ControllerContext.Request.GetUserPrincipal() as IPrincipal;
+ if (principal == null || !principal.IsInRole("Administrators"))
+ {
+ context.Response = new HttpResponseMessage(HttpStatusCode.Unauthorized);
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/test/System.Web.Http.Integration.Test/Authentication/SampleController.cs b/test/System.Web.Http.Integration.Test/Authentication/SampleController.cs
new file mode 100644
index 00000000..8ee582ac
--- /dev/null
+++ b/test/System.Web.Http.Integration.Test/Authentication/SampleController.cs
@@ -0,0 +1,14 @@
+namespace System.Web.Http
+{
+ /// <summary>
+ /// Sample ApiControler
+ /// </summary>
+ public class SampleController : ApiController
+ {
+ [RequireAdmin]
+ public string Get()
+ {
+ return "hello";
+ }
+ }
+} \ No newline at end of file
diff --git a/test/System.Web.Http.Integration.Test/ContentNegotiation/AcceptHeaderTests.cs b/test/System.Web.Http.Integration.Test/ContentNegotiation/AcceptHeaderTests.cs
new file mode 100644
index 00000000..102b56d2
--- /dev/null
+++ b/test/System.Web.Http.Integration.Test/ContentNegotiation/AcceptHeaderTests.cs
@@ -0,0 +1,30 @@
+using System.Net.Http;
+using System.Net.Http.Headers;
+using Xunit;
+using Xunit.Extensions;
+
+namespace System.Web.Http.ContentNegotiation
+{
+ public class AcceptHeaderTests : ContentNegotiationTestBase
+ {
+ [Theory]
+ [InlineData("application/json")]
+ [InlineData("application/xml")]
+ public void Response_Contains_ContentType(string contentType)
+ {
+ // Arrange
+ MediaTypeWithQualityHeaderValue requestContentType = new MediaTypeWithQualityHeaderValue(contentType);
+ MediaTypeHeaderValue responseContentType = null;
+
+ // Act
+ HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, baseUri);
+ request.Headers.Accept.Add(requestContentType);
+ HttpResponseMessage response = httpClient.SendAsync(request).Result;
+ response.EnsureSuccessStatusCode();
+ responseContentType = response.Content.Headers.ContentType;
+
+ // Assert
+ Assert.Equal(requestContentType.MediaType, responseContentType.MediaType);
+ }
+ }
+}
diff --git a/test/System.Web.Http.Integration.Test/ContentNegotiation/ConnegController.cs b/test/System.Web.Http.Integration.Test/ContentNegotiation/ConnegController.cs
new file mode 100644
index 00000000..a5ebbbee
--- /dev/null
+++ b/test/System.Web.Http.Integration.Test/ContentNegotiation/ConnegController.cs
@@ -0,0 +1,19 @@
+namespace System.Web.Http.ContentNegotiation
+{
+ public class ConnegController : ApiController
+ {
+ public ConnegItem GetItem(string name = "Fido", int age = 3)
+ {
+ return new ConnegItem()
+ {
+ Name = name,
+ Age = age
+ };
+ }
+
+ public ConnegItem PostItem(ConnegItem item)
+ {
+ return item;
+ }
+ }
+}
diff --git a/test/System.Web.Http.Integration.Test/ContentNegotiation/ConnegItem.cs b/test/System.Web.Http.Integration.Test/ContentNegotiation/ConnegItem.cs
new file mode 100644
index 00000000..c6bd0862
--- /dev/null
+++ b/test/System.Web.Http.Integration.Test/ContentNegotiation/ConnegItem.cs
@@ -0,0 +1,8 @@
+namespace System.Web.Http.ContentNegotiation
+{
+ public class ConnegItem
+ {
+ public string Name { get; set; }
+ public int Age { get; set; }
+ }
+}
diff --git a/test/System.Web.Http.Integration.Test/ContentNegotiation/ContentNegotiationTestBase.cs b/test/System.Web.Http.Integration.Test/ContentNegotiation/ContentNegotiationTestBase.cs
new file mode 100644
index 00000000..5772a2af
--- /dev/null
+++ b/test/System.Web.Http.Integration.Test/ContentNegotiation/ContentNegotiationTestBase.cs
@@ -0,0 +1,46 @@
+using System.Net.Http;
+using System.Web.Http.SelfHost;
+
+namespace System.Web.Http.ContentNegotiation
+{
+ public class ContentNegotiationTestBase : IDisposable
+ {
+ protected readonly string baseUri = "http://localhost:8080/Conneg";
+ protected HttpSelfHostServer server = null;
+ protected HttpSelfHostConfiguration configuration = null;
+ protected HttpClient httpClient = null;
+
+ public ContentNegotiationTestBase()
+ {
+ this.SetupHost();
+ }
+
+ public void Dispose()
+ {
+ this.CleanupHost();
+ }
+
+ public void SetupHost()
+ {
+ configuration = new HttpSelfHostConfiguration(baseUri);
+ configuration.Routes.MapHttpRoute("Default", "{controller}", new { controller = "Conneg" });
+ server = new HttpSelfHostServer(configuration);
+ server.OpenAsync().Wait();
+
+ httpClient = new HttpClient();
+ }
+
+ public void CleanupHost()
+ {
+ if (server != null)
+ {
+ server.CloseAsync().Wait();
+ }
+
+ if (httpClient != null)
+ {
+ httpClient.Dispose();
+ }
+ }
+ }
+}
diff --git a/test/System.Web.Http.Integration.Test/ContentNegotiation/CustomFormatterTests.cs b/test/System.Web.Http.Integration.Test/ContentNegotiation/CustomFormatterTests.cs
new file mode 100644
index 00000000..266559e0
--- /dev/null
+++ b/test/System.Web.Http.Integration.Test/ContentNegotiation/CustomFormatterTests.cs
@@ -0,0 +1,299 @@
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Net;
+using System.Net.Http;
+using System.Net.Http.Formatting;
+using System.Net.Http.Headers;
+using System.Threading.Tasks;
+using System.Web.Http.SelfHost;
+using Xunit;
+
+namespace System.Web.Http.ContentNegotiation
+{
+ public class CustomFormatterTests : IDisposable
+ {
+ private HttpSelfHostServer server = null;
+ private string baseAddress = null;
+ private HttpClient httpClient = null;
+ private HttpSelfHostConfiguration config = null;
+
+ public CustomFormatterTests()
+ {
+ SetupHost();
+ }
+
+ public void Dispose()
+ {
+ this.CleanupHost();
+ }
+
+ [Fact]
+ public void CustomFormatter_Overrides_SetResponseHeaders_During_Conneg()
+ {
+ Order reqOrdr = new Order() { OrderId = "100", OrderValue = 100.00 };
+ HttpRequestMessage request = new HttpRequestMessage
+ {
+ Content = new ObjectContent<Order>(reqOrdr, new XmlMediaTypeFormatter())
+ };
+ request.RequestUri = new Uri(baseAddress + "/CustomFormatterTests/EchoOrder");
+ request.Method = HttpMethod.Post;
+ request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("text/plainwithversioninfo"));
+
+ HttpResponseMessage response = httpClient.SendAsync(request).Result;
+
+ response.EnsureSuccessStatusCode();
+
+ IEnumerable<string> versionHdr = null;
+ Assert.True(response.Headers.TryGetValues("Version", out versionHdr));
+ Assert.Equal<string>("1.3.5.0", versionHdr.First());
+ Assert.NotNull(response.Content);
+ Assert.NotNull(response.Content.Headers.ContentType);
+ Assert.Equal<string>("text/plainwithversioninfo", response.Content.Headers.ContentType.MediaType);
+ }
+
+ [Fact]
+ public void CustomFormatter_Post_Returns_Request_String_Content()
+ {
+ HttpRequestMessage request = new HttpRequestMessage
+ {
+ Content = new ObjectContent<string>("Hello World!", new PlainTextFormatter())
+ };
+ request.RequestUri = new Uri(baseAddress + "/CustomFormatterTests/EchoString");
+ request.Method = HttpMethod.Post;
+
+ HttpResponseMessage response = httpClient.SendAsync(request).Result;
+
+ response.EnsureSuccessStatusCode();
+ Assert.NotNull(response.Content);
+ Assert.NotNull(response.Content.Headers.ContentType);
+ Assert.Equal<string>("text/plain", response.Content.Headers.ContentType.MediaType);
+ Assert.Equal<string>("Hello World!", response.Content.ReadAsStringAsync().Result);
+ }
+
+ [Fact(Skip = "Test update needed, uses IKeyValueModel")]
+ public void CustomFormatter_Post_Returns_Request_Integer_Content()
+ {
+ HttpRequestMessage request = new HttpRequestMessage
+ {
+ Content = new ObjectContent<int>(100, new PlainTextFormatter())
+ };
+
+ request.RequestUri = new Uri(baseAddress + "/CustomFormatterTests/EchoInt");
+ request.Method = HttpMethod.Post;
+
+ HttpResponseMessage response = httpClient.SendAsync(request).Result;
+
+ response.EnsureSuccessStatusCode();
+ Assert.NotNull(response.Content);
+ Assert.NotNull(response.Content.Headers.ContentType);
+ Assert.Equal<string>("text/plain", response.Content.Headers.ContentType.MediaType);
+ Assert.Equal<int>(100, Convert.ToInt32(response.Content.ReadAsStringAsync().Result));
+ }
+
+
+ [Fact(Skip = "Test update needed, uses IKeyValueModel")]
+ public void CustomFormatter_Post_Returns_Request_ComplexType_Content()
+ {
+ Order reqOrdr = new Order() { OrderId = "100", OrderValue = 100.00 };
+ HttpRequestMessage request = new HttpRequestMessage
+ {
+ Content = new ObjectContent<Order>(reqOrdr, new PlainTextFormatter())
+ };
+ request.RequestUri = new Uri(baseAddress + "/CustomFormatterTests/EchoOrder");
+ request.Method = HttpMethod.Post;
+
+ HttpResponseMessage response = httpClient.SendAsync(request).Result;
+
+ response.EnsureSuccessStatusCode();
+ Assert.NotNull(response.Content);
+ Assert.NotNull(response.Content.Headers.ContentType);
+ Assert.Equal<string>("text/plain", response.Content.Headers.ContentType.MediaType);
+ }
+
+ private void SetupHost()
+ {
+ httpClient = new HttpClient();
+ baseAddress = String.Format("http://{0}", Environment.MachineName);
+ config = new HttpSelfHostConfiguration(baseAddress);
+ config.Routes.MapHttpRoute("Default", "{controller}/{action}", new { controller = "CustomFormatterTests", action = "EchoOrder" });
+ config.Formatters.Add(new PlainTextFormatterWithVersionInfo());
+ config.Formatters.Add(new PlainTextFormatter());
+
+ server = new HttpSelfHostServer(config);
+ server.OpenAsync().Wait();
+ }
+
+ private void CleanupHost()
+ {
+ if (server != null)
+ {
+ server.CloseAsync().Wait();
+ }
+ }
+ }
+
+ public class PlainTextFormatterWithVersionInfo : MediaTypeFormatter
+ {
+ public PlainTextFormatterWithVersionInfo()
+ {
+ this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("text/plainwithversioninfo"));
+ }
+
+ public override bool CanReadType(Type type)
+ {
+ return true;
+ }
+
+ public override bool CanWriteType(Type type)
+ {
+ return true;
+ }
+
+ public override void SetDefaultContentHeaders(Type objectType, HttpContentHeaders contentHeaders, string mediaType)
+ {
+ base.SetDefaultContentHeaders(objectType, contentHeaders, mediaType);
+ contentHeaders.TryAddWithoutValidation("Version", "1.3.5.0");
+ }
+
+ public override Task<object> ReadFromStreamAsync(Type type, Stream stream, HttpContentHeaders contentHeaders, IFormatterLogger formatterLogger)
+ {
+ string content = null;
+
+ using (var reader = new StreamReader(stream))
+ {
+ content = reader.ReadToEnd();
+ }
+
+ TaskCompletionSource<object> tcs = new TaskCompletionSource<object>();
+ tcs.SetResult(type == typeof(IKeyValueModel) ? (object)new StringKeyValueModel(content) : content);
+
+ return tcs.Task;
+ }
+
+ public override Task WriteToStreamAsync(Type type, object value, Stream stream, HttpContentHeaders contentHeaders, TransportContext transportContext)
+ {
+ var output = value.ToString();
+ var writer = new StreamWriter(stream);
+ writer.Write(output);
+ writer.Flush();
+
+ TaskCompletionSource<object> tcs = new TaskCompletionSource<object>();
+ tcs.SetResult(null);
+
+ return tcs.Task;
+ }
+ }
+
+ public class PlainTextFormatter : MediaTypeFormatter
+ {
+ public PlainTextFormatter()
+ {
+ this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("text/plain"));
+ }
+
+ public override bool CanReadType(Type type)
+ {
+ return true;
+ }
+
+ public override bool CanWriteType(Type type)
+ {
+ return true;
+ }
+
+ public override Task<object> ReadFromStreamAsync(Type type, Stream stream, HttpContentHeaders contentHeaders, IFormatterLogger formatterLogger)
+ {
+ string content = null;
+
+ using (var reader = new StreamReader(stream))
+ {
+ content = reader.ReadToEnd();
+ }
+
+ TaskCompletionSource<object> tcs = new TaskCompletionSource<object>();
+ tcs.SetResult(type == typeof(IKeyValueModel) ? (object)new StringKeyValueModel(content) : content);
+ return tcs.Task;
+ }
+
+ public override Task WriteToStreamAsync(Type type, object value, Stream stream, HttpContentHeaders contentHeaders, TransportContext transportContext)
+ {
+ var output = value == null ? String.Empty : value.ToString();
+ var writer = new StreamWriter(stream);
+ writer.Write(output);
+ writer.Flush();
+
+ TaskCompletionSource<object> tcs = new TaskCompletionSource<object>();
+ tcs.SetResult(null);
+ return tcs.Task;
+ }
+ }
+
+ public class CustomFormatterTestsController : ApiController
+ {
+ [HttpPost]
+ public string EchoString([FromBody] string input)
+ {
+ return input;
+ }
+
+ [HttpPost]
+ public int EchoInt([FromBody] int input)
+ {
+ return input;
+ }
+
+ [HttpPost]
+ public Order EchoOrder(Order order)
+ {
+ return order;
+ }
+ }
+
+ public class StringKeyValueModel : IKeyValueModel
+ {
+ private string _value;
+ public StringKeyValueModel(string value)
+ {
+ _value = value;
+ }
+
+ public bool TryGetValue(string key, out object value)
+ {
+ if (key.Length == 0)
+ {
+ value = _value;
+ return true;
+ }
+
+ value = null;
+ return false;
+ }
+
+ public IEnumerable<string> Keys
+ {
+ get { return new string[0]; }
+ }
+ }
+
+ public class Order : IEquatable<Order>, ICloneable
+ {
+ public string OrderId { get; set; }
+ public double OrderValue { get; set; }
+
+ public bool Equals(Order other)
+ {
+ return (this.OrderId.Equals(other.OrderId) && this.OrderValue.Equals(other.OrderValue));
+ }
+
+ public object Clone()
+ {
+ return this.MemberwiseClone();
+ }
+
+ public override string ToString()
+ {
+ return String.Format("OrderId:{0}, OrderValue:{1}", OrderId, OrderValue);
+ }
+ }
+}
diff --git a/test/System.Web.Http.Integration.Test/ContentNegotiation/DefaultContentNegotiatorTests.cs b/test/System.Web.Http.Integration.Test/ContentNegotiation/DefaultContentNegotiatorTests.cs
new file mode 100644
index 00000000..70d6dae7
--- /dev/null
+++ b/test/System.Web.Http.Integration.Test/ContentNegotiation/DefaultContentNegotiatorTests.cs
@@ -0,0 +1,37 @@
+using System.Collections.Generic;
+using System.Net.Http;
+using System.Net.Http.Formatting;
+using System.Net.Http.Headers;
+using Moq;
+using Xunit;
+
+namespace System.Web.Http.ContentNegotiation
+{
+ public class DefaultContentNegotiatorTests : ContentNegotiationTestBase
+ {
+ [Fact]
+ public void Custom_ContentNegotiator_Used_In_Response()
+ {
+ // Arrange
+ configuration.Formatters.Clear();
+ MediaTypeWithQualityHeaderValue requestContentType = new MediaTypeWithQualityHeaderValue("application/xml");
+ MediaTypeHeaderValue responseContentType = null;
+
+ Mock<IContentNegotiator> selector = new Mock<IContentNegotiator>();
+ MediaTypeHeaderValue mediaType;
+ selector.Setup(s => s.Negotiate(It.IsAny<Type>(), It.IsAny<HttpRequestMessage>(), It.IsAny<IEnumerable<MediaTypeFormatter>>(), out mediaType)).Returns(new XmlMediaTypeFormatter());
+
+ configuration.ServiceResolver.SetService(typeof(IContentNegotiator), selector.Object);
+
+ // Act
+ HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, baseUri);
+ request.Headers.Accept.Add(requestContentType);
+ HttpResponseMessage response = httpClient.SendAsync(request).Result;
+ response.EnsureSuccessStatusCode();
+ responseContentType = response.Content.Headers.ContentType;
+
+ // Assert
+ selector.Verify(s => s.Negotiate(It.IsAny<Type>(), It.IsAny<HttpRequestMessage>(), It.IsAny<IEnumerable<MediaTypeFormatter>>(), out mediaType), Times.AtLeastOnce());
+ }
+ }
+}
diff --git a/test/System.Web.Http.Integration.Test/ContentNegotiation/HttpResponseReturnTests.cs b/test/System.Web.Http.Integration.Test/ContentNegotiation/HttpResponseReturnTests.cs
new file mode 100644
index 00000000..a63d0c18
--- /dev/null
+++ b/test/System.Web.Http.Integration.Test/ContentNegotiation/HttpResponseReturnTests.cs
@@ -0,0 +1,132 @@
+using System.Net;
+using System.Net.Http;
+using System.Net.Http.Formatting;
+using System.Net.Http.Headers;
+using System.Web.Http.SelfHost;
+using Xunit;
+using Xunit.Extensions;
+
+namespace System.Web.Http.ContentNegotiation
+{
+ public class HttpResponseReturnTests : IDisposable
+ {
+ private HttpSelfHostServer server = null;
+ private string baseAddress = null;
+ private HttpClient httpClient = null;
+
+ public HttpResponseReturnTests()
+ {
+ this.SetupHost();
+ }
+
+ public void Dispose()
+ {
+ this.CleanupHost();
+ }
+
+ [Theory]
+ [InlineData("ReturnHttpResponseMessage")]
+ [InlineData("ReturnHttpResponseMessageAsObject")]
+ [InlineData("ReturnObjectContentOfT")]
+ [InlineData("ReturnObjectContent")]
+ [InlineData("ReturnString")]
+ public void ActionReturnsHttpResponseMessage(string action)
+ {
+ string expectedResponseValue = "<?xml version='1.0' encoding='utf-8'?><string>Hello</string>".Replace('\'', '"');
+
+ HttpRequestMessage request = new HttpRequestMessage();
+ request.RequestUri = new Uri(baseAddress + String.Format("HttpResponseReturn/{0}", action));
+ request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/xml"));
+ request.Method = HttpMethod.Get;
+
+ HttpResponseMessage response = httpClient.SendAsync(request).Result;
+
+ Assert.NotNull(response.Content);
+ Assert.NotNull(response.Content.Headers.ContentType);
+ Assert.Equal<string>("application/xml", response.Content.Headers.ContentType.MediaType);
+ Assert.Equal<string>(expectedResponseValue, response.Content.ReadAsStringAsync().Result);
+ }
+
+ [Theory]
+ [InlineData("ReturnHttpResponseMessageAsXml")]
+ public void ActionReturnsHttpResponseMessageWithExplicitMediaType(string action)
+ {
+ string expectedResponseValue = "<?xml version='1.0' encoding='utf-8'?><string>Hello</string>".Replace('\'', '"');
+
+ HttpRequestMessage request = new HttpRequestMessage();
+ request.RequestUri = new Uri(baseAddress + String.Format("HttpResponseReturn/{0}", action));
+ request.Method = HttpMethod.Get;
+
+ HttpResponseMessage response = httpClient.SendAsync(request).Result;
+
+ Assert.NotNull(response.Content);
+ Assert.NotNull(response.Content.Headers.ContentType);
+ Assert.Equal<string>("application/xml", response.Content.Headers.ContentType.MediaType);
+ Assert.Equal<string>(expectedResponseValue, response.Content.ReadAsStringAsync().Result);
+ }
+
+ public void SetupHost()
+ {
+ httpClient = new HttpClient();
+
+ baseAddress = String.Format("http://{0}/", Environment.MachineName);
+
+ HttpSelfHostConfiguration config = new HttpSelfHostConfiguration(baseAddress);
+ config.Routes.MapHttpRoute("Default", "{controller}/{action}", new { controller = "HttpResponseReturn" });
+
+ server = new HttpSelfHostServer(config);
+ server.OpenAsync().Wait();
+ }
+
+ public void CleanupHost()
+ {
+ if (server != null)
+ {
+ server.CloseAsync().Wait();
+ }
+ }
+ }
+
+ public class HttpResponseReturnController : ApiController
+ {
+ [HttpGet]
+ public HttpResponseMessage ReturnHttpResponseMessage()
+ {
+ return Request.CreateResponse(HttpStatusCode.OK, "Hello");
+ }
+
+ [HttpGet]
+ public object ReturnHttpResponseMessageAsObject()
+ {
+ return ReturnHttpResponseMessage();
+ }
+
+ [HttpGet]
+ public HttpResponseMessage ReturnHttpResponseMessageAsXml()
+ {
+ HttpResponseMessage response = new HttpResponseMessage()
+ {
+ Content = new ObjectContent<string>("Hello", new XmlMediaTypeFormatter())
+ };
+ return response;
+ }
+
+ [HttpGet]
+ public ObjectContent<string> ReturnObjectContentOfT()
+ {
+ return new ObjectContent<string>("Hello", new XmlMediaTypeFormatter());
+ }
+
+ [HttpGet]
+ public ObjectContent ReturnObjectContent()
+ {
+ return new ObjectContent(typeof(string), "Hello", new XmlMediaTypeFormatter());
+ }
+
+ [HttpGet]
+ public string ReturnString()
+ {
+ return "Hello";
+ }
+ }
+} \ No newline at end of file
diff --git a/test/System.Web.Http.Integration.Test/Controllers/ActionAttributesTest.cs b/test/System.Web.Http.Integration.Test/Controllers/ActionAttributesTest.cs
new file mode 100644
index 00000000..9c061ab3
--- /dev/null
+++ b/test/System.Web.Http.Integration.Test/Controllers/ActionAttributesTest.cs
@@ -0,0 +1,138 @@
+using System.Web.Http.Controllers;
+using System.Web.Http.Properties;
+using Xunit.Extensions;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Http
+{
+ [CLSCompliant(false)]
+ public class ActionAttributesTest
+ {
+ [Theory]
+ [InlineData("GET", "ActionAttributeTest/RetriveUsers", "RetriveUsers")]
+ [InlineData("POST", "ActionAttributeTest/AddUsers", "AddUsers")]
+ [InlineData("PUT", "ActionAttributeTest/UpdateUsers", "UpdateUsers")]
+ [InlineData("DELETE", "ActionAttributeTest/RemoveUsers", "RemoveUsers")]
+ [InlineData("PATCH", "ActionAttributeTest/Users", "Users")]
+ [InlineData("HEAD", "ActionAttributeTest/Users", "Users")]
+ [InlineData("GET", "ActionAttributeTest/Deny", "Deny")]
+ [InlineData("POST", "ActionAttributeTest/Deny", "Deny")]
+ [InlineData("PUT", "ActionAttributeTest/Deny", "Deny")]
+ [InlineData("DELETE", "ActionAttributeTest/Deny", "Deny")]
+ [InlineData("PATCH", "ActionAttributeTest/Deny", "Deny")]
+ [InlineData("WHATEVER", "ActionAttributeTest/Deny", "Deny")]
+ [InlineData("GET", "ActionAttributeTest/Approve", "Approve")]
+ [InlineData("POST", "ActionAttributeTest/Approve", "Approve")]
+ [InlineData("PUT", "ActionAttributeTest/Approve", "Approve")]
+ [InlineData("DELETE", "ActionAttributeTest/Approve", "Approve")]
+ [InlineData("PATCH", "ActionAttributeTest/Approve", "Approve")]
+ [InlineData("WHATEVER", "ActionAttributeTest/Approve", "Approve")]
+ public void SelectAction_OnRouteWithActionParameter(string httpMethod, string requestUrl, string expectedActionName)
+ {
+ string routeUrl = "{controller}/{action}/{id}";
+ object routeDefault = new { id = RouteParameter.Optional };
+
+ HttpControllerContext controllerContext = ApiControllerHelper.CreateControllerContext(httpMethod, requestUrl, routeUrl, routeDefault);
+ controllerContext.ControllerDescriptor = new HttpControllerDescriptor(controllerContext.Configuration, "test", typeof(ActionAttributeTestController));
+ HttpActionDescriptor descriptor = ApiControllerHelper.SelectAction(controllerContext);
+
+ Assert.Equal<string>(expectedActionName, descriptor.ActionName);
+ }
+
+ [Theory]
+ [InlineData("POST", "ActionAttributeTest/RetriveUsers")]
+ [InlineData("DELETE", "ActionAttributeTest/RetriveUsers")]
+ [InlineData("WHATEVER", "ActionAttributeTest/RetriveUsers")]
+ [InlineData("GET", "ActionAttributeTest/AddUsers")]
+ [InlineData("PUT", "ActionAttributeTest/AddUsers")]
+ [InlineData("WHATEVER", "ActionAttributeTest/AddUsers")]
+ [InlineData("GET", "ActionAttributeTest/UpdateUsers")]
+ [InlineData("WHATEVER", "ActionAttributeTest/UpdateUsers")]
+ [InlineData("POST", "ActionAttributeTest/RemoveUsers")]
+ [InlineData("DELETEME", "ActionAttributeTest/RemoveUsers")]
+ [InlineData("GET", "ActionAttributeTest/Users")]
+ [InlineData("POST", "ActionAttributeTest/Users")]
+ [InlineData("PATCHING", "ActionAttributeTest/Users")]
+ public void SelectAction_ThrowsMethodNotSupported_OnRouteWithActionParameter(string httpMethod, string requestUrl)
+ {
+ string routeUrl = "{controller}/{action}/{id}";
+ object routeDefault = new { id = RouteParameter.Optional };
+ HttpControllerContext controllerContext = ApiControllerHelper.CreateControllerContext(httpMethod, requestUrl, routeUrl, routeDefault);
+ Type controllerType = typeof(ActionAttributeTestController);
+ controllerContext.ControllerDescriptor = new HttpControllerDescriptor(controllerContext.Configuration, controllerType.Name, controllerType);
+
+ Assert.Throws<HttpResponseException>(() =>
+ {
+ HttpActionDescriptor descriptor = ApiControllerHelper.SelectAction(controllerContext);
+ },
+ String.Format(SRResources.ApiControllerActionSelector_HttpMethodNotSupported, httpMethod));
+ }
+
+ [Theory]
+ [InlineData("GET", "ActionAttributeTest/NonAction")]
+ [InlineData("POST", "ActionAttributeTest/NonAction")]
+ [InlineData("NonAction", "ActionAttributeTest/NonAction")]
+ public void SelectAction_ThrowsNotFound_OnRouteWithActionParameter(string httpMethod, string requestUrl)
+ {
+ string routeUrl = "{controller}/{action}/{id}";
+ object routeDefault = new { id = RouteParameter.Optional };
+ HttpControllerContext controllerContext = ApiControllerHelper.CreateControllerContext(httpMethod, requestUrl, routeUrl, routeDefault);
+ Type controllerType = typeof(ActionAttributeTestController);
+ controllerContext.ControllerDescriptor = new HttpControllerDescriptor(controllerContext.Configuration, controllerType.Name, controllerType);
+
+
+ Assert.Throws<HttpResponseException>(() =>
+ {
+ HttpActionDescriptor descriptor = ApiControllerHelper.SelectAction(controllerContext);
+ },
+ String.Format(SRResources.ApiControllerActionSelector_ActionNotFound, controllerType.Name));
+ }
+
+ [Theory]
+ [InlineData("GET", "ActionAttributeTest/", "RetriveUsers")]
+ [InlineData("GET", "ActionAttributeTest/3", "RetriveUsers")]
+ [InlineData("GET", "ActionAttributeTest/4?ssn=12345", "RetriveUsers")]
+ [InlineData("POST", "ActionAttributeTest/", "AddUsers")]
+ [InlineData("POST", "ActionAttributeTest/1", "AddUsers")]
+ [InlineData("PUT", "ActionAttributeTest", "UpdateUsers")]
+ [InlineData("PUT", "ActionAttributeTest/4", "UpdateUsers")]
+ [InlineData("PUT", "ActionAttributeTest/4?extra=thing", "UpdateUsers")]
+ [InlineData("DELETE", "ActionAttributeTest", "RemoveUsers")]
+ [InlineData("PATCH", "ActionAttributeTest", "Users")]
+ [InlineData("HEAD", "ActionAttributeTest/", "Users")]
+ public void SelectAction_OnDefaultRoute(string httpMethod, string requestUrl, string expectedActionName)
+ {
+ string routeUrl = "{controller}/{id}";
+ object routeDefault = new { id = RouteParameter.Optional };
+
+ HttpControllerContext controllerContext = ApiControllerHelper.CreateControllerContext(httpMethod, requestUrl, routeUrl, routeDefault);
+ controllerContext.ControllerDescriptor = new HttpControllerDescriptor(controllerContext.Configuration, "test", typeof(ActionAttributeTestController));
+ HttpActionDescriptor descriptor = ApiControllerHelper.SelectAction(controllerContext);
+
+ Assert.Equal<string>(expectedActionName, descriptor.ActionName);
+ }
+
+ [Theory]
+ [InlineData("OPTIONS", "ActionAttributeTest")]
+ [InlineData("TRACE", "ActionAttributeTest")]
+ [InlineData("NonAction", "ActionAttributeTest/")]
+ [InlineData("DENY", "ActionAttributeTest")]
+ [InlineData("APP", "ActionAttributeTest")]
+ public void SelectAction_ThrowsMethodNotSupported_OnDefaultRoute(string httpMethod, string requestUrl)
+ {
+ string routeUrl = "{controller}/{id}";
+ object routeDefault = new { id = RouteParameter.Optional };
+ HttpControllerContext controllerContext = ApiControllerHelper.CreateControllerContext(httpMethod, requestUrl, routeUrl, routeDefault);
+ Type controllerType = typeof(ActionAttributeTestController);
+ controllerContext.ControllerDescriptor = new HttpControllerDescriptor(controllerContext.Configuration, controllerType.Name, controllerType);
+
+
+ Assert.Throws<HttpResponseException>(() =>
+ {
+ HttpActionDescriptor descriptor = ApiControllerHelper.SelectAction(controllerContext);
+ },
+ String.Format(SRResources.ApiControllerActionSelector_HttpMethodNotSupported, httpMethod));
+
+ }
+ }
+}
diff --git a/test/System.Web.Http.Integration.Test/Controllers/ApiControllerActionSelectorTest.cs b/test/System.Web.Http.Integration.Test/Controllers/ApiControllerActionSelectorTest.cs
new file mode 100644
index 00000000..d5099a62
--- /dev/null
+++ b/test/System.Web.Http.Integration.Test/Controllers/ApiControllerActionSelectorTest.cs
@@ -0,0 +1,191 @@
+using System.Web.Http.Controllers;
+using System.Web.Http.Properties;
+using Xunit;
+using Xunit.Extensions;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Http
+{
+ [CLSCompliant(false)]
+ public class ApiControllerActionSelectorTest
+ {
+ [Theory]
+ [InlineData("GET", "Test", "GetUsers")]
+ [InlineData("GET", "Test/2", "GetUser")]
+ [InlineData("GET", "Test/3?name=mario", "GetUserByNameAndId")]
+ [InlineData("GET", "Test/3?name=mario&ssn=123456", "GetUserByNameIdAndSsn")]
+ [InlineData("GET", "Test?name=mario&ssn=123456", "GetUserByNameAndSsn")]
+ [InlineData("GET", "Test?name=mario&ssn=123456&age=3", "GetUserByNameAgeAndSsn")]
+ [InlineData("GET", "Test/4?name=mario&age=20", "GetUserByNameAndId")]
+ [InlineData("GET", "Test/5?random=9", "GetUser")]
+ [InlineData("Post", "Test", "PostUser")]
+ [InlineData("Post", "Test?name=mario&age=10", "PostUserByNameAndAge")]
+ /// Note: Normally the following would not match DeleteUserByIdAndOptName because it has 'id' and 'age' as parameters while the DeleteUserByIdAndOptName action has 'id' and 'name'.
+ /// However, because the default value is provided on action parameter 'name', having the 'id' in the request was enough to match the action.
+ [InlineData("Delete", "Test/6?age=10", "DeleteUserByIdAndOptName")]
+ public void Route_Parameters_Default(string httpMethod, string requestUrl, string expectedActionName)
+ {
+ string routeUrl = "{controller}/{id}";
+ object routeDefault = new { id = RouteParameter.Optional };
+
+ HttpControllerContext context = ApiControllerHelper.CreateControllerContext(httpMethod, requestUrl, routeUrl, routeDefault);
+ context.ControllerDescriptor = new HttpControllerDescriptor(context.Configuration, "test", typeof(TestController));
+ HttpActionDescriptor descriptor = ApiControllerHelper.SelectAction(context);
+
+ Assert.Equal<string>(expectedActionName, descriptor.ActionName);
+ }
+
+ [Theory]
+ [InlineData("GET", "Test", "GetUsers")]
+ [InlineData("GET", "Test/2", "GetUsersByName")]
+ [InlineData("GET", "Test/luigi?ssn=123456", "GetUserByNameAndSsn")]
+ [InlineData("GET", "Test/luigi?ssn=123456&id=2&ssn=12345", "GetUserByNameIdAndSsn")]
+ [InlineData("GET", "Test?age=10&ssn=123456", "GetUsers")]
+ [InlineData("GET", "Test?id=3&ssn=123456&name=luigi", "GetUserByNameIdAndSsn")]
+ [InlineData("POST", "Test/luigi?age=20", "PostUserByNameAndAge")]
+ public void Route_Parameters_Non_Id(string httpMethod, string requestUrl, string expectedActionName)
+ {
+ string routeUrl = "{controller}/{name}";
+ object routeDefault = new { name = RouteParameter.Optional };
+
+ HttpControllerContext context = ApiControllerHelper.CreateControllerContext(httpMethod, requestUrl, routeUrl, routeDefault);
+ context.ControllerDescriptor = new HttpControllerDescriptor(context.Configuration, "test", typeof(TestController));
+ HttpActionDescriptor descriptor = ApiControllerHelper.SelectAction(context);
+
+ Assert.Equal<string>(expectedActionName, descriptor.ActionName);
+ }
+
+ [Theory]
+ [InlineData("GET", "Test/3?NAME=mario", "GetUserByNameAndId")]
+ [InlineData("GET", "Test/3?name=mario&SSN=123456", "GetUserByNameIdAndSsn")]
+ [InlineData("GET", "Test?nAmE=mario&ssn=123456&AgE=3", "GetUserByNameAgeAndSsn")]
+ [InlineData("Delete", "Test/6?AGe=10", "DeleteUserByIdAndOptName")]
+ public void Route_Parameters_Casing(string httpMethod, string requestUrl, string expectedActionName)
+ {
+ string routeUrl = "{controller}/{ID}";
+ object routeDefault = new { id = RouteParameter.Optional };
+
+ HttpControllerContext context = ApiControllerHelper.CreateControllerContext(httpMethod, requestUrl, routeUrl, routeDefault);
+ context.ControllerDescriptor = new HttpControllerDescriptor(context.Configuration, "test", typeof(TestController));
+ HttpActionDescriptor descriptor = ApiControllerHelper.SelectAction(context);
+
+ Assert.Equal<string>(expectedActionName, descriptor.ActionName);
+ }
+
+ [Theory]
+ [InlineData("GET", "Test/GetUsers", "GetUsers")]
+ [InlineData("GET", "Test/GetUser", "GetUser")]
+ [InlineData("GET", "Test/GetUser?id=3", "GetUser")]
+ [InlineData("GET", "Test/GetUser/4?id=3", "GetUser")]
+ [InlineData("GET", "Test/GetUserByNameAgeAndSsn", "GetUserByNameAgeAndSsn")]
+ [InlineData("GET", "Test/GetUserByNameAndSsn", "GetUserByNameAndSsn")]
+ [InlineData("POST", "Test/PostUserByNameAndAddress", "PostUserByNameAndAddress")]
+ public void Route_Action(string httpMethod, string requestUrl, string expectedActionName)
+ {
+ string routeUrl = "{controller}/{action}/{id}";
+ object routeDefault = new { id = RouteParameter.Optional };
+
+ HttpControllerContext context = ApiControllerHelper.CreateControllerContext(httpMethod, requestUrl, routeUrl, routeDefault);
+ context.ControllerDescriptor = new HttpControllerDescriptor(context.Configuration, "test", typeof(TestController));
+ HttpActionDescriptor descriptor = ApiControllerHelper.SelectAction(context);
+
+ Assert.Equal<string>(expectedActionName, descriptor.ActionName);
+ }
+
+ [Theory]
+ [InlineData("GET", "Test/getusers", "GetUsers")]
+ [InlineData("GET", "Test/getuseR", "GetUser")]
+ [InlineData("GET", "Test/Getuser?iD=3", "GetUser")]
+ [InlineData("GET", "Test/GetUser/4?Id=3", "GetUser")]
+ [InlineData("GET", "Test/GetUserByNameAgeandSsn", "GetUserByNameAgeAndSsn")]
+ [InlineData("GET", "Test/getUserByNameAndSsn", "GetUserByNameAndSsn")]
+ [InlineData("POST", "Test/PostUserByNameAndAddress", "PostUserByNameAndAddress")]
+ public void Route_Action_Name_Casing(string httpMethod, string requestUrl, string expectedActionName)
+ {
+ string routeUrl = "{controller}/{action}/{id}";
+ object routeDefault = new { id = RouteParameter.Optional };
+
+ HttpControllerContext context = ApiControllerHelper.CreateControllerContext(httpMethod, requestUrl, routeUrl, routeDefault);
+ context.ControllerDescriptor = new HttpControllerDescriptor(context.Configuration, "test", typeof(TestController));
+ HttpActionDescriptor descriptor = ApiControllerHelper.SelectAction(context);
+
+ Assert.Equal<string>(expectedActionName, descriptor.ActionName);
+ }
+
+ [Theory]
+ [InlineData("GET", "Test", "GetUsers")]
+ [InlineData("GET", "Test/?name=peach", "GetUsersByName")]
+ [InlineData("GET", "Test?name=peach", "GetUsersByName")]
+ [InlineData("GET", "Test?name=peach&ssn=123456", "GetUserByNameAndSsn")]
+ [InlineData("GET", "Test?name=peach&ssn=123456&age=3", "GetUserByNameAgeAndSsn")]
+ public void Route_No_Action(string httpMethod, string requestUrl, string expectedActionName)
+ {
+ string routeUrl = "{controller}";
+
+ HttpControllerContext context = ApiControllerHelper.CreateControllerContext(httpMethod, requestUrl, routeUrl);
+ context.ControllerDescriptor = new HttpControllerDescriptor(context.Configuration, "test", typeof(TestController));
+ HttpActionDescriptor descriptor = ApiControllerHelper.SelectAction(context);
+
+ Assert.Equal<string>(expectedActionName, descriptor.ActionName);
+ }
+
+ [Fact]
+ public void RequestToAmbiguousAction_OnDefaultRoute()
+ {
+ string routeUrl = "{controller}/{id}";
+ object routeDefault = new { id = RouteParameter.Optional };
+ string httpMethod = "Post";
+ string requestUrl = "Test?name=mario";
+
+ // This would result in ambiguous match because complex parameter is not considered for matching.
+ // Therefore, PostUserByNameAndAddress(string name, Address address) would conflicts with PostUserByName(string name)
+ Assert.Throws<HttpResponseException>(() =>
+ {
+ HttpControllerContext context = ApiControllerHelper.CreateControllerContext(httpMethod, requestUrl, routeUrl, routeDefault);
+ context.ControllerDescriptor = new HttpControllerDescriptor(context.Configuration, "test", typeof(TestController));
+ HttpActionDescriptor descriptor = ApiControllerHelper.SelectAction(context);
+ });
+ }
+
+ [Fact]
+ public void RequestToActionWithNotSupportedHttpMethod_OnRouteWithAction()
+ {
+ string routeUrl = "{controller}/{action}/{id}";
+ object routeDefault = new { id = RouteParameter.Optional };
+ string requestUrl = "Test/GetUsers";
+ string httpMethod = "POST";
+ Assert.Throws<HttpResponseException>(() =>
+ {
+ HttpControllerContext context = ApiControllerHelper.CreateControllerContext(httpMethod, requestUrl, routeUrl, routeDefault);
+ context.ControllerDescriptor = new HttpControllerDescriptor(context.Configuration, "test", typeof(TestController));
+ HttpActionDescriptor descriptor = ApiControllerHelper.SelectAction(context);
+ },
+ string.Format(SRResources.ApiControllerActionSelector_HttpMethodNotSupported, httpMethod));
+ }
+
+ [Fact]
+ public void RequestToActionWith_HttpMethodDefinedByAttributeAndActionName()
+ {
+ string routeUrl = "{controller}/{id}";
+ object routeDefault = new { id = RouteParameter.Optional };
+ string requestUrl = "Test";
+ string httpMethod = "PATCH";
+
+ HttpControllerContext context = ApiControllerHelper.CreateControllerContext(httpMethod, requestUrl, routeUrl, routeDefault);
+ context.ControllerDescriptor = new HttpControllerDescriptor(context.Configuration, "test", typeof(TestController));
+ HttpActionDescriptor descriptor = ApiControllerHelper.SelectAction(context);
+
+ Assert.Equal<string>("PutUser", descriptor.ActionName);
+
+ // When you have the HttpMethod attribute, the convention should not be applied.
+ httpMethod = "PUT";
+ Assert.Throws<HttpResponseException>(() =>
+ {
+ context = ApiControllerHelper.CreateControllerContext(httpMethod, requestUrl, routeUrl, routeDefault);
+ context.ControllerDescriptor = new HttpControllerDescriptor(context.Configuration, "test", typeof(TestController));
+ ApiControllerHelper.SelectAction(context);
+ },
+ string.Format(SRResources.ApiControllerActionSelector_HttpMethodNotSupported, httpMethod));
+ }
+ }
+}
diff --git a/test/System.Web.Http.Integration.Test/Controllers/Apis/ActionAttributeTestController.cs b/test/System.Web.Http.Integration.Test/Controllers/Apis/ActionAttributeTestController.cs
new file mode 100644
index 00000000..5e13779e
--- /dev/null
+++ b/test/System.Web.Http.Integration.Test/Controllers/Apis/ActionAttributeTestController.cs
@@ -0,0 +1,29 @@
+namespace System.Web.Http
+{
+ public class ActionAttributeTestController : ApiController
+ {
+ [HttpGet]
+ public void RetriveUsers() { }
+
+ [HttpPost]
+ public void AddUsers(int id) { }
+
+ [HttpPut]
+ public void UpdateUsers(User user) { }
+
+ [HttpDelete]
+ public void RemoveUsers(string name) { }
+
+ [AcceptVerbs("PATCH", "HEAD")]
+ [CLSCompliant(false)]
+ public void Users(double key) { }
+
+ [ActionName("Deny")]
+ public void Reject(int id) { }
+
+ public void Approve(int id) { }
+
+ [NonAction]
+ public void NonAction() { }
+ }
+}
diff --git a/test/System.Web.Http.Integration.Test/Controllers/Apis/TestController.cs b/test/System.Web.Http.Integration.Test/Controllers/Apis/TestController.cs
new file mode 100644
index 00000000..048e2766
--- /dev/null
+++ b/test/System.Web.Http.Integration.Test/Controllers/Apis/TestController.cs
@@ -0,0 +1,28 @@
+using System.Collections.Generic;
+
+namespace System.Web.Http
+{
+ public class TestController : ApiController
+ {
+ public User GetUser(int id) { return null; }
+ public List<User> GetUsers() { return null; }
+
+ [ActionName("GetUsersByName")]
+ public List<User> RetrieveUsersByName(string name) { return null; }
+
+ [AcceptVerbs("PATCH")]
+ public void PutUser(User user) { }
+
+ public User GetUserByNameAndId(string name, int id) { return null; }
+ public User GetUserByNameAndAge(string name, int age) { return null; }
+ public User GetUserByNameAgeAndSsn(string name, int age, int ssn) { return null; }
+ public User GetUserByNameIdAndSsn(string name, int id, int ssn) { return null; }
+ public User GetUserByNameAndSsn(string name, int ssn) { return null; }
+ public User PostUser(User user) { return null; }
+ public User PostUserByNameAndAge(string name, int age) { return null; }
+ public User PostUserByName(string name) { return null; }
+ public User PostUserByNameAndAddress(string name, UserAddress address) { return null; }
+ public User DeleteUserByIdAndOptName(int id, string name = "DefaultName") { return null; }
+ public User DeleteUserByIdNameAndAge(int id, string name, int age) { return null; }
+ }
+}
diff --git a/test/System.Web.Http.Integration.Test/Controllers/Apis/User.cs b/test/System.Web.Http.Integration.Test/Controllers/Apis/User.cs
new file mode 100644
index 00000000..09fe81ff
--- /dev/null
+++ b/test/System.Web.Http.Integration.Test/Controllers/Apis/User.cs
@@ -0,0 +1,9 @@
+
+namespace System.Web.Http
+{
+ public class User
+ {
+ public string FirstName { get; set; }
+ public string LastName { get; set; }
+ }
+}
diff --git a/test/System.Web.Http.Integration.Test/Controllers/Apis/UserAddress.cs b/test/System.Web.Http.Integration.Test/Controllers/Apis/UserAddress.cs
new file mode 100644
index 00000000..f8c78afa
--- /dev/null
+++ b/test/System.Web.Http.Integration.Test/Controllers/Apis/UserAddress.cs
@@ -0,0 +1,9 @@
+
+namespace System.Web.Http
+{
+ public class UserAddress
+ {
+ public string Street;
+ public int ZipCode;
+ }
+}
diff --git a/test/System.Web.Http.Integration.Test/Controllers/CustomControllerFactoryTest.cs b/test/System.Web.Http.Integration.Test/Controllers/CustomControllerFactoryTest.cs
new file mode 100644
index 00000000..1cc1f55b
--- /dev/null
+++ b/test/System.Web.Http.Integration.Test/Controllers/CustomControllerFactoryTest.cs
@@ -0,0 +1,70 @@
+using System.Collections.Generic;
+using System.Net;
+using System.Net.Http;
+using System.Web.Http.Common;
+using System.Web.Http.Dispatcher;
+using System.Web.Http.ModelBinding;
+using System.Web.Http.SelfHost;
+using Xunit;
+
+namespace System.Web.Http.Controllers
+{
+ public class CustomControllerFactoryTest
+ {
+ [Fact]
+ public void Body_WithSingletonControllerInstance_Fails()
+ {
+ // Arrange
+ HttpClient httpClient = new HttpClient();
+ string baseAddress = "http://localhost";
+ string requestUri = baseAddress + "/Test";
+ HttpSelfHostConfiguration configuration = new HttpSelfHostConfiguration(baseAddress);
+ configuration.Routes.MapHttpRoute("Default", "{controller}", new { controller = "Test" });
+ configuration.ServiceResolver.SetService(typeof(IHttpControllerFactory), new MySingletonControllerFactory());
+ HttpSelfHostServer host = new HttpSelfHostServer(configuration);
+ host.OpenAsync().Wait();
+ HttpResponseMessage response = null;
+
+ try
+ {
+ // Act
+ response = httpClient.GetAsync(requestUri).Result;
+ response = httpClient.GetAsync(requestUri).Result;
+ response = httpClient.GetAsync(requestUri).Result;
+
+ // Assert
+ Assert.Equal(HttpStatusCode.InternalServerError, response.StatusCode);
+ }
+ finally
+ {
+ if (response != null)
+ {
+ response.Dispose();
+ }
+ }
+
+ host.CloseAsync().Wait();
+ }
+
+ private class MySingletonControllerFactory : IHttpControllerFactory
+ {
+ private static TestController singleton = new TestController();
+
+ public IHttpController CreateController(HttpControllerContext controllerContext, string controllerName)
+ {
+ return singleton;
+ }
+
+ public void ReleaseController(HttpControllerContext controllerContext, IHttpController controller)
+ {
+ throw new NotImplementedException();
+ }
+
+ public IDictionary<string, HttpControllerDescriptor> GetControllerMapping()
+ {
+ throw new NotImplementedException();
+ }
+ }
+
+ }
+}
diff --git a/test/System.Web.Http.Integration.Test/Controllers/Helpers/ApiControllerHelper.cs b/test/System.Web.Http.Integration.Test/Controllers/Helpers/ApiControllerHelper.cs
new file mode 100644
index 00000000..4389f0c9
--- /dev/null
+++ b/test/System.Web.Http.Integration.Test/Controllers/Helpers/ApiControllerHelper.cs
@@ -0,0 +1,54 @@
+using System.Collections.Generic;
+using System.Linq;
+using System.Net.Http;
+using System.Web.Http.Common;
+using System.Web.Http.Controllers;
+using System.Web.Http.Routing;
+
+namespace System.Web.Http
+{
+ public class ApiControllerHelper
+ {
+ public static HttpActionDescriptor SelectAction(HttpControllerContext controllerContext)
+ {
+ ApiControllerActionSelector selector = new ApiControllerActionSelector();
+ HttpActionDescriptor descriptor = selector.SelectAction(controllerContext);
+ return descriptor;
+ }
+
+ public static HttpControllerContext CreateControllerContext(string httpMethod, string requestUrl, string routeUrl, object routeDefault = null)
+ {
+ string baseAddress = "http://localhost/";
+ HttpConfiguration config = new HttpConfiguration();
+ HttpRoute route = routeDefault != null ? new HttpRoute(routeUrl, new HttpRouteValueDictionary(routeDefault)) : new HttpRoute(routeUrl);
+ config.Routes.Add("test", route);
+
+ HttpRequestMessage request = new HttpRequestMessage(HttpMethodHelper.GetHttpMethod(httpMethod), baseAddress + requestUrl);
+
+ IHttpRouteData routeData = config.Routes.GetRouteData(request);
+ if (routeData == null)
+ {
+ throw new InvalidOperationException("Could not dispatch to controller based on the route.");
+ }
+
+ RemoveOptionalRoutingParameters(routeData.Values);
+
+ HttpControllerContext controllerContext = ContextUtil.CreateControllerContext(config, routeData, request);
+ return controllerContext;
+ }
+
+ private static void RemoveOptionalRoutingParameters(IDictionary<string, object> routeValueDictionary)
+ {
+ // Get all keys for which the corresponding value is 'Optional'.
+ // ToArray() necessary so that we don't manipulate the dictionary while enumerating.
+ string[] matchingKeys = (from entry in routeValueDictionary
+ where entry.Value == RouteParameter.Optional
+ select entry.Key).ToArray();
+
+ foreach (string key in matchingKeys)
+ {
+ routeValueDictionary.Remove(key);
+ }
+ }
+ }
+}
diff --git a/test/System.Web.Http.Integration.Test/ExceptionHandling/DuplicateControllers.cs b/test/System.Web.Http.Integration.Test/ExceptionHandling/DuplicateControllers.cs
new file mode 100644
index 00000000..52952ac0
--- /dev/null
+++ b/test/System.Web.Http.Integration.Test/ExceptionHandling/DuplicateControllers.cs
@@ -0,0 +1,23 @@
+using System.Web.Http;
+
+namespace System.Web.Http
+{
+ public class DuplicateController : ApiController
+ {
+ public string GetAction()
+ {
+ return "dup";
+ }
+ }
+}
+
+namespace System.Web.Http2
+{
+ public class DuplicateController : ApiController
+ {
+ public string GetAction()
+ {
+ return "dup2";
+ }
+ }
+}
diff --git a/test/System.Web.Http.Integration.Test/ExceptionHandling/ExceptionController.cs b/test/System.Web.Http.Integration.Test/ExceptionHandling/ExceptionController.cs
new file mode 100644
index 00000000..b0958a91
--- /dev/null
+++ b/test/System.Web.Http.Integration.Test/ExceptionHandling/ExceptionController.cs
@@ -0,0 +1,102 @@
+using System.Collections.Generic;
+using System.Linq;
+using System.Net;
+using System.Net.Http;
+using System.Threading.Tasks;
+using System.Web.Http.Controllers;
+using System.Web.Http.Filters;
+
+namespace System.Web.Http
+{
+ public class ExceptionController : ApiController
+ {
+ public static string ResponseExceptionHeaderKey = "responseExceptionStatusCode";
+
+ public HttpResponseMessage Unavailable()
+ {
+ throw new HttpResponseException(new HttpResponseMessage(HttpStatusCode.ServiceUnavailable));
+ }
+
+ public Task<HttpResponseMessage> AsyncUnavailable()
+ {
+ throw new HttpResponseException(new HttpResponseMessage(HttpStatusCode.ServiceUnavailable));
+ }
+
+ public Task<HttpResponseMessage> AsyncUnavailableDelegate()
+ {
+ return Task.Factory.StartNew<HttpResponseMessage>(() => { throw new HttpResponseException(new HttpResponseMessage(HttpStatusCode.ServiceUnavailable)); });
+ }
+
+ public HttpResponseMessage ArgumentNull()
+ {
+ throw new ArgumentNullException("foo");
+ }
+
+ public Task<HttpResponseMessage> AsyncArgumentNull()
+ {
+ return Task.Factory.StartNew<HttpResponseMessage>(() => { throw new ArgumentNullException("foo"); });
+ }
+
+ [HttpGet]
+ public string GetException()
+ {
+ return "foo";
+ }
+
+ [HttpGet]
+ public string GetString()
+ {
+ return "bar";
+ }
+
+ [AuthorizationFilterThrows]
+ public void AuthorizationFilter() { }
+
+ [ActionFilterThrows]
+ public void ActionFilter() { }
+
+ [ExceptionFilterThrows]
+ public void ExceptionFilter() { throw new ArgumentException("exception"); }
+
+ private class AuthorizationFilterThrows : AuthorizeAttribute
+ {
+ public override void OnAuthorization(HttpActionContext actionContext)
+ {
+ TryThrowHttpResponseException(actionContext);
+ throw new ArgumentException("authorization");
+ }
+ }
+
+ private class ActionFilterThrows : ActionFilterAttribute
+ {
+ public override void OnActionExecuting(HttpActionContext actionContext)
+ {
+ TryThrowHttpResponseException(actionContext);
+ throw new ArgumentException("action");
+ }
+ }
+
+ private class ExceptionFilterThrows : ExceptionFilterAttribute
+ {
+ public override void OnException(HttpActionExecutedContext actionExecutedContext)
+ {
+ TryThrowHttpResponseException(actionExecutedContext.ActionContext);
+ throw actionExecutedContext.Exception;
+ }
+ }
+
+ private static void TryThrowHttpResponseException(HttpActionContext actionContext)
+ {
+ IEnumerable<string> values;
+ if (actionContext.ControllerContext.Request.Headers.TryGetValues(ResponseExceptionHeaderKey, out values))
+ {
+ string statusString = values.First() as string;
+ if (!String.IsNullOrEmpty(statusString))
+ {
+ HttpStatusCode status = (HttpStatusCode)Enum.Parse(typeof(HttpStatusCode), statusString);
+ throw new HttpResponseException("HttpResponseExceptionMessage", status);
+ }
+ }
+ }
+ }
+}
diff --git a/test/System.Web.Http.Integration.Test/ExceptionHandling/ExceptionHandlingTest.cs b/test/System.Web.Http.Integration.Test/ExceptionHandling/ExceptionHandlingTest.cs
new file mode 100644
index 00000000..b4d7f683
--- /dev/null
+++ b/test/System.Web.Http.Integration.Test/ExceptionHandling/ExceptionHandlingTest.cs
@@ -0,0 +1,225 @@
+using System.Json;
+using System.Net;
+using System.Net.Http;
+using System.Web.Http.Dispatcher;
+using System.Web.Http.Properties;
+using Xunit;
+using Xunit.Extensions;
+
+namespace System.Web.Http
+{
+ public class ExceptionHandlingTest
+ {
+ [Theory]
+ [InlineData("Unavailable")]
+ [InlineData("AsyncUnavailable")]
+ [InlineData("AsyncUnavailableDelegate")]
+ public void ThrowingHttpResponseException_FromAction_GetsReturnedToClient(string actionName)
+ {
+ string controllerName = "Exception";
+ string requestUrl = String.Format("{0}/{1}/{2}", ScenarioHelper.BaseAddress, controllerName, actionName);
+
+ ScenarioHelper.RunTest(
+ controllerName,
+ "/{action}",
+ new HttpRequestMessage(HttpMethod.Get, requestUrl),
+ (response) =>
+ {
+ Assert.Equal(HttpStatusCode.ServiceUnavailable, response.StatusCode);
+ }
+ );
+ }
+
+ [Theory]
+ [InlineData("ArgumentNull")]
+ [InlineData("AsyncArgumentNull")]
+ public void ThrowingArgumentNullException_FromAction_GetsReturnedToClient(string actionName)
+ {
+ string controllerName = "Exception";
+ string requestUrl = String.Format("{0}/{1}/{2}", ScenarioHelper.BaseAddress, controllerName, actionName);
+
+ ScenarioHelper.RunTest(
+ controllerName,
+ "/{action}",
+ new HttpRequestMessage(HttpMethod.Get, requestUrl),
+ (response) =>
+ {
+ Assert.Equal(HttpStatusCode.InternalServerError, response.StatusCode);
+ ExceptionSurrogate exception = response.Content.ReadAsAsync<ExceptionSurrogate>().Result;
+ Assert.Equal(typeof(ArgumentNullException).FullName, exception.ExceptionType.ToString());
+ }
+ );
+ }
+
+ [Theory]
+ [InlineData("ArgumentNull")]
+ [InlineData("AsyncArgumentNull")]
+ public void ThrowingArgumentNullException_FromAction_GetsReturnedToClientParsedAsJson(string actionName)
+ {
+ string controllerName = "Exception";
+ string requestUrl = String.Format("{0}/{1}/{2}", ScenarioHelper.BaseAddress, controllerName, actionName);
+
+ ScenarioHelper.RunTest(
+ controllerName,
+ "/{action}",
+ new HttpRequestMessage(HttpMethod.Get, requestUrl),
+ (response) =>
+ {
+ Assert.Equal(HttpStatusCode.InternalServerError, response.StatusCode);
+ dynamic json = JsonValue.Parse(response.Content.ReadAsStringAsync().Result);
+ string result = json.ExceptionType;
+ Assert.Equal(typeof(ArgumentNullException).FullName, result);
+ }
+ );
+ }
+
+ [Theory]
+ [InlineData("AuthorizationFilter")]
+ [InlineData("ActionFilter")]
+ [InlineData("ExceptionFilter")]
+ public void ThrowingArgumentException_FromFilter_GetsReturnedToClient(string actionName)
+ {
+ string controllerName = "Exception";
+ string requestUrl = String.Format("{0}/{1}/{2}", ScenarioHelper.BaseAddress, controllerName, actionName);
+
+ ScenarioHelper.RunTest(
+ controllerName,
+ "/{action}",
+ new HttpRequestMessage(HttpMethod.Get, requestUrl),
+ (response) =>
+ {
+ Assert.Equal(HttpStatusCode.InternalServerError, response.StatusCode);
+ ExceptionSurrogate exception = response.Content.ReadAsAsync<ExceptionSurrogate>().Result;
+ Assert.Equal(typeof(ArgumentException).FullName, exception.ExceptionType.ToString());
+ }
+ );
+ }
+
+ [Theory]
+ [InlineData("AuthorizationFilter", HttpStatusCode.Forbidden)]
+ [InlineData("ActionFilter", HttpStatusCode.NotAcceptable)]
+ [InlineData("ExceptionFilter", HttpStatusCode.NotImplemented)]
+ public void ThrowingHttpResponseException_FromFilter_GetsReturnedToClient(string actionName, HttpStatusCode responseExceptionStatusCode)
+ {
+ string controllerName = "Exception";
+ string requestUrl = String.Format("{0}/{1}/{2}", ScenarioHelper.BaseAddress, controllerName, actionName);
+ HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, requestUrl);
+ request.Headers.Add(ExceptionController.ResponseExceptionHeaderKey, responseExceptionStatusCode.ToString());
+
+ ScenarioHelper.RunTest(
+ controllerName,
+ "/{action}",
+ request,
+ (response) =>
+ {
+ Assert.Equal(responseExceptionStatusCode, response.StatusCode);
+ Assert.Equal("HttpResponseExceptionMessage", response.Content.ReadAsAsync<string>().Result);
+ }
+ );
+ }
+
+ // TODO: add tests that throws from custom model binders
+
+ [Fact]
+ public void Service_ReturnsNotFound_WhenControllerNameDoesNotExist()
+ {
+ string controllerName = "randomControllerThatCannotBeFound";
+ string requestUrl = String.Format("{0}/{1}", ScenarioHelper.BaseAddress, controllerName);
+
+ ScenarioHelper.RunTest(
+ controllerName,
+ "",
+ new HttpRequestMessage(HttpMethod.Get, requestUrl),
+ (response) =>
+ {
+ Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
+ Assert.Equal(
+ String.Format(SRResources.DefaultControllerFactory_ControllerNameNotFound, controllerName),
+ response.Content.ReadAsAsync<string>().Result);
+ }
+ );
+ }
+
+ [Fact]
+ public void Service_ReturnsNotFound_WhenActionNameDoesNotExist()
+ {
+ string controllerName = "Exception";
+ string actionName = "actionNotFound";
+ string requestUrl = String.Format("{0}/{1}/{2}", ScenarioHelper.BaseAddress, controllerName, actionName);
+
+ ScenarioHelper.RunTest(
+ controllerName,
+ "/{action}",
+ new HttpRequestMessage(HttpMethod.Get, requestUrl),
+ (response) =>
+ {
+ Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
+ Assert.Equal(
+ String.Format(SRResources.ApiControllerActionSelector_ActionNameNotFound, controllerName, actionName),
+ response.Content.ReadAsAsync<string>().Result);
+ }
+ );
+ }
+
+ [Fact]
+ public void Service_ReturnsMethodNotAllowed_WhenActionsDoesNotSupportTheRequestHttpMethod()
+ {
+ string controllerName = "Exception";
+ string actionName = "GetString";
+ HttpMethod requestMethod = HttpMethod.Post;
+ string requestUrl = String.Format("{0}/{1}/{2}", ScenarioHelper.BaseAddress, controllerName, actionName);
+ ScenarioHelper.RunTest(
+ controllerName,
+ "/{action}",
+ new HttpRequestMessage(requestMethod, requestUrl),
+ (response) =>
+ {
+ Assert.Equal(HttpStatusCode.MethodNotAllowed, response.StatusCode);
+ Assert.Equal(
+ String.Format(SRResources.ApiControllerActionSelector_HttpMethodNotSupported, requestMethod.Method),
+ response.Content.ReadAsAsync<string>().Result);
+ }
+ );
+ }
+
+ [Fact]
+ public void Service_ReturnsInternalServerError_WhenMultipleActionsAreFound()
+ {
+ string controllerName = "Exception";
+ string requestUrl = String.Format("{0}/{1}", ScenarioHelper.BaseAddress, controllerName);
+
+ ScenarioHelper.RunTest(
+ controllerName,
+ "",
+ new HttpRequestMessage(HttpMethod.Get, requestUrl),
+ (response) =>
+ {
+ Assert.Equal(HttpStatusCode.InternalServerError, response.StatusCode);
+ Assert.Contains(
+ String.Format(SRResources.ApiControllerActionSelector_AmbiguousMatch, String.Empty),
+ response.Content.ReadAsAsync<string>().Result);
+ }
+ );
+ }
+
+ [Fact]
+ public void Service_ReturnsInternalServerError_WhenMultipleControllersAreFound()
+ {
+ string controllerName = "Duplicate";
+ string requestUrl = String.Format("{0}/{1}", ScenarioHelper.BaseAddress, controllerName);
+
+ ScenarioHelper.RunTest(
+ controllerName,
+ "",
+ new HttpRequestMessage(HttpMethod.Get, requestUrl),
+ (response) =>
+ {
+ Assert.Equal(HttpStatusCode.InternalServerError, response.StatusCode);
+ Assert.Contains(
+ String.Format(SRResources.DefaultControllerFactory_ControllerNameAmbiguous_WithRouteTemplate, controllerName, "{controller}", String.Empty),
+ response.Content.ReadAsAsync<string>().Result);
+ }
+ );
+ }
+ }
+}
diff --git a/test/System.Web.Http.Integration.Test/ExceptionHandling/HttpResponseExceptionTest.cs b/test/System.Web.Http.Integration.Test/ExceptionHandling/HttpResponseExceptionTest.cs
new file mode 100644
index 00000000..0992808c
--- /dev/null
+++ b/test/System.Web.Http.Integration.Test/ExceptionHandling/HttpResponseExceptionTest.cs
@@ -0,0 +1,224 @@
+using System.Collections.Generic;
+using System.IO;
+using System.Net;
+using System.Net.Http;
+using System.Net.Http.Formatting;
+using System.Net.Http.Headers;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+using System.Web.Http.Controllers;
+using System.Web.Http.Filters;
+using Xunit;
+using Xunit.Extensions;
+
+namespace System.Web.Http.ExceptionHandling
+{
+ public class HttpResponseExceptionTest
+ {
+ [Theory]
+ [InlineData("DoNotThrow")]
+ [InlineData("ActionMethod")]
+ // TODO : 332683 - HttpResponseExceptions in message handlers
+ //[InlineData("RequestMessageHandler")]
+ //[InlineData("ResponseMessageHandler")]
+ [InlineData("RequestAuthorization")]
+ [InlineData("BeforeActionExecuted")]
+ [InlineData("AfterActionExecuted")]
+ [InlineData("ContentNegotiatorNegotiate")]
+ [InlineData("ActionMethodAndExceptionFilter")]
+ [InlineData("MediaTypeFormatterReadFromStreamAsync")]
+ public void HttpResponseExceptionWithExplicitStatusCode(string throwAt)
+ {
+ HttpRequestMessage request = new HttpRequestMessage();
+ request.RequestUri = new Uri(ScenarioHelper.BaseAddress + "/ExceptionTests/ReturnString");
+ request.Method = HttpMethod.Post;
+ request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/xml"));
+ request.Content = new StringContent("<string>" + throwAt + "</string>", Encoding.UTF8, "application/xml");
+
+ ScenarioHelper.RunTest(
+ "ExceptionTests",
+ "/{action}",
+ request,
+ response =>
+ {
+ Assert.NotNull(response.Content);
+ Assert.NotNull(response.Content.Headers.ContentType);
+ Assert.Equal(response.Content.Headers.ContentType.MediaType, "application/xml");
+
+ if (throwAt == "DoNotThrow")
+ {
+ Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+ Assert.Equal("Hello World!", response.Content.ReadAsAsync<string>(new List<MediaTypeFormatter>() { new XmlMediaTypeFormatter() }).Result);
+ }
+ else
+ {
+ Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
+ Assert.Equal(String.Format("Error at {0}", throwAt),
+ response.Content.ReadAsAsync<string>(new List<MediaTypeFormatter>() { new XmlMediaTypeFormatter() }).Result);
+ }
+ },
+ config =>
+ {
+ config.ServiceResolver.SetService(typeof(IContentNegotiator), new CustomContentNegotiator(throwAt));
+
+ config.MessageHandlers.Add(new CustomMessageHandler(throwAt));
+ config.Filters.Add(new CustomActionFilterAttribute(throwAt));
+ config.Filters.Add(new CustomAuthorizationFilterAttribute(throwAt));
+ config.Filters.Add(new CustomExceptionFilterAttribute(throwAt));
+ config.Formatters.Clear();
+ config.Formatters.Add(new CustomXmlMediaTypeFormatter(throwAt));
+ }
+ );
+ }
+ }
+
+ public class CustomMessageHandler : DelegatingHandler
+ {
+ private string _throwAt;
+
+ public CustomMessageHandler(string throwAt)
+ {
+ _throwAt = throwAt;
+ }
+
+ protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
+ {
+ ExceptionTestsUtility.CheckForThrow(_throwAt, "RequestMessageHandler");
+
+ return base.SendAsync(request, cancellationToken).ContinueWith<HttpResponseMessage>((tsk) =>
+ {
+ ExceptionTestsUtility.CheckForThrow(_throwAt, "ResponseMessageHandler");
+
+ HttpResponseMessage response = tsk.Result;
+
+ return response;
+ });
+ }
+ }
+
+ public class ExceptionTestsController : ApiController
+ {
+ [HttpPost]
+ public string ReturnString([FromBody] string throwAt)
+ {
+ string message = "Hello World!";
+
+ // check if the test wants to throw from here
+ ExceptionTestsUtility.CheckForThrow(throwAt, "ActionMethod");
+
+ // NOTE: this indicates that we want to throw from here & after this gets intercepted
+ // by the ExceptionFilter, we want to throw from there too
+ ExceptionTestsUtility.CheckForThrow(throwAt, "ActionMethodAndExceptionFilter");
+
+ return message;
+ }
+ }
+
+ public class CustomAuthorizationFilterAttribute : AuthorizationFilterAttribute
+ {
+ private string _throwAt;
+
+ public CustomAuthorizationFilterAttribute(string throwAt)
+ {
+ _throwAt = throwAt;
+ }
+
+ public override void OnAuthorization(HttpActionContext context)
+ {
+ ExceptionTestsUtility.CheckForThrow(_throwAt, "RequestAuthorization");
+ }
+ }
+
+ public class CustomActionFilterAttribute : ActionFilterAttribute
+ {
+ private string _throwAt;
+
+ public CustomActionFilterAttribute(string throwAt)
+ {
+ _throwAt = throwAt;
+ }
+
+ public override void OnActionExecuting(HttpActionContext context)
+ {
+ ExceptionTestsUtility.CheckForThrow(_throwAt, "BeforeActionExecuted");
+ }
+
+ public override void OnActionExecuted(HttpActionExecutedContext context)
+ {
+ ExceptionTestsUtility.CheckForThrow(_throwAt, "AfterActionExecuted");
+ }
+ }
+
+ public class CustomExceptionFilterAttribute : ExceptionFilterAttribute
+ {
+ private string _throwAt;
+
+ public CustomExceptionFilterAttribute(string throwAt)
+ {
+ _throwAt = throwAt;
+ }
+
+ public override void OnException(HttpActionExecutedContext context)
+ {
+ ExceptionTestsUtility.CheckForThrow(_throwAt, "ActionMethodAndExceptionFilter");
+ }
+ }
+
+ public class CustomContentNegotiator : System.Net.Http.Formatting.DefaultContentNegotiator
+ {
+ private string _throwAt;
+
+ public CustomContentNegotiator(string throwAt)
+ {
+ _throwAt = throwAt;
+ }
+
+ public override MediaTypeFormatter Negotiate(Type type, HttpRequestMessage request, IEnumerable<MediaTypeFormatter> formatters, out System.Net.Http.Headers.MediaTypeHeaderValue mediaType)
+ {
+ ExceptionTestsUtility.CheckForThrow(_throwAt, "ContentNegotiatorNegotiate");
+
+ return base.Negotiate(type, request, formatters, out mediaType);
+ }
+ }
+
+ public class CustomXmlMediaTypeFormatter : XmlMediaTypeFormatter
+ {
+ private string _throwAt;
+
+ public CustomXmlMediaTypeFormatter(string throwAt)
+ {
+ _throwAt = throwAt;
+ }
+
+ public override Task<object> ReadFromStreamAsync(Type type, Stream stream, HttpContentHeaders contentHeaders, IFormatterLogger formatterLogger)
+ {
+ ExceptionTestsUtility.CheckForThrow(_throwAt, "MediaTypeFormatterReadFromStreamAsync");
+
+ return base.ReadFromStreamAsync(type, stream, contentHeaders, formatterLogger);
+ }
+
+ public override Task WriteToStreamAsync(Type type, object value, Stream stream, HttpContentHeaders contentHeaders, TransportContext transportContext)
+ {
+ ExceptionTestsUtility.CheckForThrow(_throwAt, "MediaTypeFormatterWriteToStreamAsync");
+
+ return base.WriteToStreamAsync(type, value, stream, contentHeaders, transportContext);
+ }
+ }
+
+ public static class ExceptionTestsUtility
+ {
+ public static void CheckForThrow(string throwAt, string stage)
+ {
+ if (throwAt == stage)
+ {
+ HttpResponseMessage response = new HttpResponseMessage(HttpStatusCode.NotFound)
+ {
+ Content = new ObjectContent<string>(String.Format("Error at {0}", stage), new XmlMediaTypeFormatter())
+ };
+
+ throw new HttpResponseException(response);
+ }
+ }
+ }
+}
diff --git a/test/System.Web.Http.Integration.Test/ExceptionHandling/IncludeErrorDetailTest.cs b/test/System.Web.Http.Integration.Test/ExceptionHandling/IncludeErrorDetailTest.cs
new file mode 100644
index 00000000..53362f4c
--- /dev/null
+++ b/test/System.Web.Http.Integration.Test/ExceptionHandling/IncludeErrorDetailTest.cs
@@ -0,0 +1,76 @@
+using System.Collections.Generic;
+using System.Json;
+using System.Net;
+using System.Net.Http;
+using Xunit;
+using Xunit.Extensions;
+
+namespace System.Web.Http
+{
+ public class IncludeErrorDetailTest
+ {
+ public static IEnumerable<object[]> Data
+ {
+ get
+ {
+ return new object[][]
+ {
+ new object[] { "localhost", null, true },
+ new object[] { "127.0.0.1", null, true },
+ new object[] { "www.foo.com", null, false },
+ new object[] { "localhost", IncludeErrorDetailPolicy.LocalOnly, true },
+ new object[] { "www.foo.com", IncludeErrorDetailPolicy.LocalOnly, false },
+ new object[] { "localhost", IncludeErrorDetailPolicy.Always, true },
+ new object[] { "www.foo.com", IncludeErrorDetailPolicy.Always, true },
+ new object[] { "localhost", IncludeErrorDetailPolicy.Never, false },
+ new object[] { "www.foo.com", IncludeErrorDetailPolicy.Never, false }
+ };
+ }
+ }
+
+ [Theory]
+ [PropertyData("Data")]
+ public void ThrowingOnActionIncludesErrorDetail(string hostName, IncludeErrorDetailPolicy? includeErrorDetail, bool shouldIncludeErrorDetail)
+ {
+ string controllerName = "Exception";
+ string requestUrl = String.Format("{0}/{1}/{2}", "http://" + hostName, controllerName, "ArgumentNull");
+ ScenarioHelper.RunTest(
+ controllerName,
+ "/{action}",
+ new HttpRequestMessage(HttpMethod.Get, requestUrl),
+ (response) =>
+ {
+ if (shouldIncludeErrorDetail)
+ {
+ AssertResponseIncludesErrorDetail(response);
+ }
+ else
+ {
+ AssertResponseDoesNotIncludeErrorDetail(response);
+ }
+ },
+ (config) =>
+ {
+ if (includeErrorDetail.HasValue)
+ {
+ config.IncludeErrorDetailPolicy = includeErrorDetail.Value;
+ }
+ }
+ );
+ }
+
+ private void AssertResponseIncludesErrorDetail(HttpResponseMessage response)
+ {
+ Assert.Equal(HttpStatusCode.InternalServerError, response.StatusCode);
+ dynamic json = JsonValue.Parse(response.Content.ReadAsStringAsync().Result);
+ string result = json.ExceptionType;
+ Assert.Equal(typeof(ArgumentNullException).FullName, result);
+ }
+
+ private void AssertResponseDoesNotIncludeErrorDetail(HttpResponseMessage response)
+ {
+ Assert.Equal(HttpStatusCode.InternalServerError, response.StatusCode);
+ Assert.Null(response.Content);
+ }
+ }
+} \ No newline at end of file
diff --git a/test/System.Web.Http.Integration.Test/Filters/IQueryableFilterPipelineTest.cs b/test/System.Web.Http.Integration.Test/Filters/IQueryableFilterPipelineTest.cs
new file mode 100644
index 00000000..0f98bd0a
--- /dev/null
+++ b/test/System.Web.Http.Integration.Test/Filters/IQueryableFilterPipelineTest.cs
@@ -0,0 +1,99 @@
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Linq;
+using System.Net.Http;
+using System.Net.Http.Formatting;
+using System.Threading;
+using System.Threading.Tasks;
+using System.Web.Http.Controllers;
+using System.Web.Http.ModelBinding;
+using Moq;
+using Xunit;
+
+namespace System.Web.Http.Filters
+{
+ public class IQueryableFilterPipelineTest
+ {
+ [Fact]
+ public void IQueryableRelatedFiltersAreOrderedCorrectly()
+ {
+ // Arrange
+ Mock<HttpControllerDescriptor> controllerDescriptorMock = new Mock<HttpControllerDescriptor>() { CallBase = true };
+ controllerDescriptorMock.Object.Configuration = new HttpConfiguration();
+ controllerDescriptorMock.Object.ControllerType = typeof(IQueryableFilterPipelineTest);
+ Mock<HttpActionDescriptor> actionDescriptorMock = new Mock<HttpActionDescriptor>(controllerDescriptorMock.Object) { CallBase = true };
+ actionDescriptorMock.Setup(ad => ad.GetFilters()).Returns(new IFilter[] { new ResultLimitAttribute(42) });
+ actionDescriptorMock.Setup(ad => ad.ReturnType).Returns(typeof(IQueryable<string>));
+ HttpActionDescriptor actionDescriptor = actionDescriptorMock.Object;
+
+ // Act
+ var filters = actionDescriptor.GetFilterPipeline();
+
+ // Assert
+ Assert.Equal(3, filters.Count);
+ Assert.IsType<EnumerableEvaluatorFilter>(filters[0].Instance);
+ Assert.IsType<ResultLimitAttribute>(filters[1].Instance);
+ Assert.IsType<QueryCompositionFilterAttribute>(filters[2].Instance);
+ }
+
+ [Fact]
+ public void EnumerableEvaluatorFilterExecutesQuerySoThatExceptionsCanBeCaughtByExceptionFilters()
+ {
+ // Arrange
+ Mock<ApiController> controllerMock = new Mock<ApiController> { CallBase = true };
+ Mock<HttpActionDescriptor> actionDescriptorMock = new Mock<HttpActionDescriptor>();
+ actionDescriptorMock.Setup(ad => ad.ReturnType).Returns(typeof(IEnumerable<string>));
+ Mock<IExceptionFilter> exceptionFilterMock = new Mock<IExceptionFilter>();
+ actionDescriptorMock.Setup(ad => ad.GetFilterPipeline())
+ .Returns(new Collection<FilterInfo>(new IFilter[] { EnumerableEvaluatorFilter.Instance, exceptionFilterMock.As<IFilter>().Object }.Select(f => new FilterInfo(f, FilterScope.Action)).ToList()));
+ Mock<IHttpActionSelector> actionSelectorMock = new Mock<IHttpActionSelector>();
+ actionSelectorMock.Setup(actionSelector => actionSelector.SelectAction(It.IsAny<HttpControllerContext>())).Returns(actionDescriptorMock.Object);
+ Mock<IHttpActionInvoker> actionInvokerMock = new Mock<IHttpActionInvoker>();
+ InvalidOperationException exception = new InvalidOperationException("Bad enumeration");
+ IEnumerable<string> actionResult = Enumerable.Range(0, 1).Select<int, string>(i =>
+ {
+ throw exception;
+ });
+ var invocationTask = Task.Factory.StartNew<HttpResponseMessage>(() => new HttpResponseMessage
+ {
+ Content = new ObjectContent<IEnumerable<string>>(actionResult, new JsonMediaTypeFormatter())
+ });
+ actionInvokerMock.Setup(ai => ai.InvokeActionAsync(It.IsAny<HttpActionContext>(), It.IsAny<CancellationToken>()))
+ .Returns(invocationTask);
+ Mock<HttpControllerDescriptor> controllerDescriptorMock = new Mock<HttpControllerDescriptor>();
+ controllerDescriptorMock.Object.HttpActionSelector = actionSelectorMock.Object;
+ controllerDescriptorMock.Object.HttpActionInvoker = actionInvokerMock.Object;
+
+ Mock<IActionValueBinder> binderMock = new Mock<IActionValueBinder>();
+ Mock<HttpActionBinding> actionBindingMock = new Mock<HttpActionBinding>();
+ actionBindingMock.Setup(b => b.ExecuteBindingAsync(It.IsAny<HttpActionContext>(), It.IsAny<CancellationToken>())).Returns(Task.Factory.StartNew(() => { }));
+ binderMock.Setup(b => b.GetBinding(It.IsAny<HttpActionDescriptor>())).Returns(actionBindingMock.Object);
+ controllerDescriptorMock.Object.ActionValueBinder = binderMock.Object;
+
+ HttpConfiguration config = new HttpConfiguration();
+
+ var controllerContext = new HttpControllerContext { ControllerDescriptor = controllerDescriptorMock.Object, Configuration = config };
+
+ // Act
+ var responseTask = controllerMock.Object.ExecuteAsync(controllerContext, CancellationToken.None);
+
+ // Assert
+ responseTask.WaitUntilCompleted();
+ exceptionFilterMock.Verify(ef => ef.ExecuteExceptionFilterAsync(It.Is<HttpActionExecutedContext>(aec => aec.Exception == exception), It.IsAny<CancellationToken>()));
+ }
+
+ public class TestController : ApiController
+ {
+ private Exception _ex;
+ public TestController(Exception ex)
+ {
+ _ex = ex;
+ }
+ public IEnumerable<string> Get()
+ {
+ yield return "cat";
+ throw _ex;
+ }
+ }
+ }
+}
diff --git a/test/System.Web.Http.Integration.Test/ModelBinding/BodyBindingTests.cs b/test/System.Web.Http.Integration.Test/ModelBinding/BodyBindingTests.cs
new file mode 100644
index 00000000..6d0f15c4
--- /dev/null
+++ b/test/System.Web.Http.Integration.Test/ModelBinding/BodyBindingTests.cs
@@ -0,0 +1,120 @@
+using System.Net;
+using System.Net.Http;
+using System.Net.Http.Formatting;
+using System.Net.Http.Headers;
+using System.Text;
+using Xunit;
+using Xunit.Extensions;
+
+namespace System.Web.Http.ModelBinding
+{
+ /// <summary>
+ /// End to end functional tests for model binding via request body
+ /// </summary>
+ public class BodyBindingTests : ModelBindingTests
+ {
+ [Fact]
+ public void Body_Bad_Input_Receives_Validation_Error()
+ {
+ // Arrange
+ string formUrlEncodedString = "Id=101&Name=testFirstNameTooLong";
+ StringContent stringContent = new StringContent(formUrlEncodedString, Encoding.UTF8, "application/x-www-form-urlencoded");
+
+ HttpRequestMessage request = new HttpRequestMessage()
+ {
+ RequestUri = new Uri(baseAddress + "ModelBinding/PostComplexWithValidation"),
+ Method = HttpMethod.Post,
+ Content = stringContent,
+ };
+
+ // Act
+ HttpResponseMessage response = httpClient.SendAsync(request).Result;
+
+ // Assert
+ Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
+ Assert.Equal("Failed to bind customer.Name. The errors are:\nErrorMessage: The field Name must be a string with a maximum length of 6.", response.Content.ReadAsStringAsync().Result);
+ }
+
+ [Fact]
+ public void Body_Good_Input_Succeed()
+ {
+ // Arrange
+ string formUrlEncodedString = "Id=111&Name=John";
+ StringContent stringContent = new StringContent(formUrlEncodedString, Encoding.UTF8, "application/x-www-form-urlencoded");
+
+ HttpRequestMessage request = new HttpRequestMessage()
+ {
+ RequestUri = new Uri(baseAddress + "ModelBinding/PostComplexWithValidation"),
+ Method = HttpMethod.Post,
+ Content = stringContent,
+ };
+
+ // Act
+ HttpResponseMessage response = httpClient.SendAsync(request).Result;
+
+ // Assert
+ Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+ Assert.Equal("111", response.Content.ReadAsStringAsync().Result);
+ }
+
+ [Theory]
+ [InlineData("PostComplexType", "application/json")]
+ [InlineData("PostComplexType", "application/xml")]
+ [InlineData("PostComplexTypeFromBody", "application/json")]
+ [InlineData("PostComplexTypeFromBody", "application/xml")]
+ public void Body_Binds_ComplexType_Type_Key_Value_Read(string action, string mediaType)
+ {
+ // Arrange
+ ModelBindOrder expectedItem = new ModelBindOrder()
+ {
+ ItemName = "Bike",
+ Quantity = 1,
+ Customer = new ModelBindCustomer { Name = "Fred" }
+ };
+ var formatter = new MediaTypeFormatterCollection().Find(mediaType);
+ HttpRequestMessage request = new HttpRequestMessage
+ {
+ Content = new ObjectContent<ModelBindOrder>(expectedItem, formatter),
+ RequestUri = new Uri(baseAddress + String.Format("ModelBinding/{0}", action)),
+ Method = HttpMethod.Post,
+ };
+
+ // Act
+ HttpResponseMessage response = httpClient.SendAsync(request).Result;
+
+ // Assert
+ ModelBindOrder actualItem = response.Content.ReadAsAsync<ModelBindOrder>().Result;
+ Assert.Equal<ModelBindOrder>(expectedItem, actualItem, new ModelBindOrderEqualityComparer());
+ }
+
+ [Theory]
+ [InlineData("PostComplexType", "application/json")]
+ [InlineData("PostComplexType", "application/xml")]
+ [InlineData("PostComplexTypeFromBody", "application/json")]
+ [InlineData("PostComplexTypeFromBody", "application/xml")]
+ public void Body_Binds_ComplexType_Type_Whole_Body_Read(string action, string mediaType)
+ {
+ // Arrange
+ ModelBindOrder expectedItem = new ModelBindOrder()
+ {
+ ItemName = "Bike",
+ Quantity = 1,
+ Customer = new ModelBindCustomer { Name = "Fred" }
+ };
+ var formatter = new MediaTypeFormatterCollection().Find(mediaType);
+ HttpRequestMessage request = new HttpRequestMessage
+ {
+ Content = new ObjectContent<ModelBindOrder>(expectedItem, formatter),
+ RequestUri = new Uri(baseAddress + String.Format("ModelBinding/{0}", action)),
+ Method = HttpMethod.Post,
+ };
+
+ // Act
+ HttpResponseMessage response = httpClient.SendAsync(request).Result;
+
+ // Assert
+ ModelBindOrder actualItem = response.Content.ReadAsAsync<ModelBindOrder>().Result;
+ Assert.Equal<ModelBindOrder>(expectedItem, actualItem, new ModelBindOrderEqualityComparer());
+ }
+ }
+}
diff --git a/test/System.Web.Http.Integration.Test/ModelBinding/CustomBindingTests.cs b/test/System.Web.Http.Integration.Test/ModelBinding/CustomBindingTests.cs
new file mode 100644
index 00000000..d4a4233a
--- /dev/null
+++ b/test/System.Web.Http.Integration.Test/ModelBinding/CustomBindingTests.cs
@@ -0,0 +1,32 @@
+using System.Net.Http;
+using Xunit;
+
+namespace System.Web.Http.ModelBinding
+{
+ /// <summary>
+ /// End to end functional tests for model binding via custom providers
+ /// </summary>
+ public class CustomBindingTests : ModelBindingTests
+ {
+ [Fact]
+ public void Custom_ValueProvider_Binds_Simple_Types_Get()
+ {
+ // Arrange
+ HttpRequestMessage request = new HttpRequestMessage()
+ {
+ RequestUri = new Uri(baseAddress + String.Format("ModelBinding/{0}", "GetIntCustom")),
+ Method = HttpMethod.Get
+ };
+
+ request.Headers.Add("value", "5");
+
+ // Act
+ HttpResponseMessage response = httpClient.SendAsync(request).Result;
+
+ // Assert
+ string responseString = response.Content.ReadAsStringAsync().Result;
+ Assert.Equal<string>("5", responseString);
+ }
+
+ }
+} \ No newline at end of file
diff --git a/test/System.Web.Http.Integration.Test/ModelBinding/DefaultActionValueBinderTest.cs b/test/System.Web.Http.Integration.Test/ModelBinding/DefaultActionValueBinderTest.cs
new file mode 100644
index 00000000..4b9b1b6a
--- /dev/null
+++ b/test/System.Web.Http.Integration.Test/ModelBinding/DefaultActionValueBinderTest.cs
@@ -0,0 +1,990 @@
+using System.Collections.Generic;
+using System.ComponentModel.DataAnnotations;
+using System.Globalization;
+using System.Json;
+using System.Linq;
+using System.Net.Http;
+using System.Net.Http.Formatting;
+using System.Net.Http.Headers;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+using System.Web.Http.Controllers;
+using System.Web.Http.Routing;
+using System.Web.Http.ValueProviders;
+using Microsoft.TestCommon;
+using Xunit;
+
+namespace System.Web.Http.ModelBinding
+{
+ public class DefaultActionValueBinderTest
+ {
+ [Fact]
+ public void BindValuesAsync_Uses_DefaultValues()
+ {
+ // Arrange
+ HttpActionContext context = ContextUtil.CreateActionContext(
+ ContextUtil.CreateControllerContext(),
+ new ReflectedHttpActionDescriptor() { MethodInfo = typeof(ActionValueController).GetMethod("Get") });
+ CancellationToken cancellationToken = new CancellationToken();
+ DefaultActionValueBinder provider = new DefaultActionValueBinder();
+
+ // Act
+ provider.BindValuesAsync(context, cancellationToken).Wait();
+
+ // Assert
+ Dictionary<string, object> expectedResult = new Dictionary<string, object>();
+ expectedResult["id"] = 0;
+ expectedResult["firstName"] = "DefaultFirstName";
+ expectedResult["lastName"] = "DefaultLastName";
+ Assert.Equal(expectedResult, context.ActionArguments, new DictionaryEqualityComparer());
+ }
+
+ [Fact]
+ public void BindValuesAsync_WithObjectContentInRequest_Works()
+ {
+ // Arrange
+ ActionValueItem cust = new ActionValueItem() { FirstName = "FirstName", LastName = "LastName", Id = 1 };
+ HttpActionContext context = ContextUtil.CreateActionContext(
+ ContextUtil.CreateControllerContext(),
+ new ReflectedHttpActionDescriptor() { MethodInfo = typeof(ActionValueController).GetMethod("PostComplexType") });
+ context.ControllerContext.Request = new HttpRequestMessage
+ {
+ Content = new ObjectContent<ActionValueItem>(cust, new JsonMediaTypeFormatter())
+ };
+ CancellationToken cancellationToken = new CancellationToken();
+ DefaultActionValueBinder provider = new DefaultActionValueBinder();
+
+ // Act
+ provider.BindValuesAsync(context, cancellationToken).Wait();
+
+ // Assert
+ Dictionary<string, object> expectedResult = new Dictionary<string, object>();
+ expectedResult["item"] = cust;
+ Assert.Equal(expectedResult, context.ActionArguments, new DictionaryEqualityComparer());
+ }
+
+ #region Query Strings
+
+ [Fact]
+ public void BindValuesAsync_Query_String_Values_To_Simple_Types()
+ {
+ // Arrange
+ CancellationToken cancellationToken = new CancellationToken();
+ HttpActionContext actionContext = ContextUtil.CreateActionContext(
+ ContextUtil.CreateControllerContext(new HttpRequestMessage()
+ {
+ Method = HttpMethod.Get,
+ RequestUri = new Uri("http://localhost?id=5&firstName=queryFirstName&lastName=queryLastName")
+ }),
+ new ReflectedHttpActionDescriptor() { MethodInfo = typeof(ActionValueController).GetMethod("Get") });
+
+ DefaultActionValueBinder provider = new DefaultActionValueBinder();
+
+ // Act
+ provider.BindValuesAsync(actionContext, cancellationToken).Wait();
+
+ // Assert
+ Dictionary<string, object> expectedResult = new Dictionary<string, object>();
+ expectedResult["id"] = 5;
+ expectedResult["firstName"] = "queryFirstName";
+ expectedResult["lastName"] = "queryLastName";
+ Assert.Equal(expectedResult, actionContext.ActionArguments, new DictionaryEqualityComparer());
+ }
+
+ [Fact]
+ public void BindValuesAsync_Query_String_Values_To_Simple_Types_With_FromUriAttribute()
+ {
+ // Arrange
+ CancellationToken cancellationToken = new CancellationToken();
+ HttpActionContext actionContext = ContextUtil.CreateActionContext(
+ ContextUtil.CreateControllerContext(new HttpRequestMessage()
+ {
+ Method = HttpMethod.Get,
+ RequestUri = new Uri("http://localhost?id=5&firstName=queryFirstName&lastName=queryLastName")
+ }),
+ new ReflectedHttpActionDescriptor() { MethodInfo = typeof(ActionValueController).GetMethod("GetFromUri") });
+
+ DefaultActionValueBinder provider = new DefaultActionValueBinder();
+
+ // Act
+ provider.BindValuesAsync(actionContext, cancellationToken).Wait();
+
+ // Assert
+ Dictionary<string, object> expectedResult = new Dictionary<string, object>();
+ expectedResult["id"] = 5;
+ expectedResult["firstName"] = "queryFirstName";
+ expectedResult["lastName"] = "queryLastName";
+ Assert.Equal(expectedResult, actionContext.ActionArguments, new DictionaryEqualityComparer());
+ }
+
+ [Fact]
+ public void BindValuesAsync_Query_String_Values_To_Complex_Types()
+ {
+ // Arrange
+ CancellationToken cancellationToken = new CancellationToken();
+ HttpActionContext actionContext = ContextUtil.CreateActionContext(
+ ContextUtil.CreateControllerContext(new HttpRequestMessage()
+ {
+ Method = HttpMethod.Get,
+ RequestUri = new Uri("http://localhost?id=5&firstName=queryFirstName&lastName=queryLastName")
+ }),
+ new ReflectedHttpActionDescriptor() { MethodInfo = typeof(ActionValueController).GetMethod("GetItem") });
+
+ DefaultActionValueBinder provider = new DefaultActionValueBinder();
+
+ // Act
+ provider.BindValuesAsync(actionContext, cancellationToken).Wait();
+
+ // Assert
+ Assert.True(actionContext.ModelState.IsValid);
+ Assert.Equal(1, actionContext.ActionArguments.Count);
+ ActionValueItem deserializedActionValueItem = Assert.IsType<ActionValueItem>(actionContext.ActionArguments.First().Value);
+ Assert.Equal(5, deserializedActionValueItem.Id);
+ Assert.Equal("queryFirstName", deserializedActionValueItem.FirstName);
+ Assert.Equal("queryLastName", deserializedActionValueItem.LastName);
+ }
+
+ [Fact]
+ public void BindValuesAsync_Query_String_Values_To_Post_Complex_Types()
+ {
+ // Arrange
+ CancellationToken cancellationToken = new CancellationToken();
+ HttpActionContext actionContext = ContextUtil.CreateActionContext(
+ ContextUtil.CreateControllerContext(new HttpRequestMessage()
+ {
+ Method = HttpMethod.Get,
+ RequestUri = new Uri("http://localhost?id=5&firstName=queryFirstName&lastName=queryLastName")
+ }),
+ new ReflectedHttpActionDescriptor() { MethodInfo = typeof(ActionValueController).GetMethod("PostComplexTypeUri") });
+
+ DefaultActionValueBinder provider = new DefaultActionValueBinder();
+
+ // Act
+ provider.BindValuesAsync(actionContext, cancellationToken).Wait();
+
+ // Assert
+ Assert.True(actionContext.ModelState.IsValid);
+ Assert.Equal(1, actionContext.ActionArguments.Count);
+ ActionValueItem deserializedActionValueItem = Assert.IsType<ActionValueItem>(actionContext.ActionArguments.First().Value);
+ Assert.Equal(5, deserializedActionValueItem.Id);
+ Assert.Equal("queryFirstName", deserializedActionValueItem.FirstName);
+ Assert.Equal("queryLastName", deserializedActionValueItem.LastName);
+ }
+
+ [Fact]
+ public void BindValuesAsync_Query_String_Values_To_Post_Enumerable_Complex_Types()
+ {
+ // Arrange
+ CancellationToken cancellationToken = new CancellationToken();
+ HttpActionContext actionContext = ContextUtil.CreateActionContext(
+ ContextUtil.CreateControllerContext(new HttpRequestMessage()
+ {
+ Method = HttpMethod.Get,
+ RequestUri = new Uri("http://localhost?items[0].id=5&items[0].firstName=queryFirstName&items[0].lastName=queryLastName")
+ }),
+ new ReflectedHttpActionDescriptor() { MethodInfo = typeof(ActionValueController).GetMethod("PostEnumerableUri") });
+
+ DefaultActionValueBinder provider = new DefaultActionValueBinder();
+
+ // Act
+ provider.BindValuesAsync(actionContext, cancellationToken).Wait();
+
+ // Assert
+ Assert.True(actionContext.ModelState.IsValid);
+ Assert.Equal(1, actionContext.ActionArguments.Count);
+ IEnumerable<ActionValueItem> items = Assert.IsAssignableFrom<IEnumerable<ActionValueItem>>(actionContext.ActionArguments.First().Value);
+ ActionValueItem deserializedActionValueItem = items.First();
+ Assert.Equal(5, deserializedActionValueItem.Id);
+ Assert.Equal("queryFirstName", deserializedActionValueItem.FirstName);
+ Assert.Equal("queryLastName", deserializedActionValueItem.LastName);
+ }
+
+ [Fact]
+ public void BindValuesAsync_Query_String_Values_To_Post_Enumerable_Complex_Types_No_Index()
+ {
+ // Arrange
+ CancellationToken cancellationToken = new CancellationToken();
+ HttpActionContext actionContext = ContextUtil.CreateActionContext(
+ ContextUtil.CreateControllerContext(new HttpRequestMessage()
+ {
+ Method = HttpMethod.Get,
+ RequestUri = new Uri("http://localhost?id=5&firstName=queryFirstName&items.lastName=queryLastName")
+ }),
+ new ReflectedHttpActionDescriptor() { MethodInfo = typeof(ActionValueController).GetMethod("PostEnumerableUri") });
+
+ DefaultActionValueBinder provider = new DefaultActionValueBinder();
+
+ // Act
+ provider.BindValuesAsync(actionContext, cancellationToken).Wait();
+
+ // Assert
+ Assert.True(actionContext.ModelState.IsValid);
+ Assert.Equal(1, actionContext.ActionArguments.Count);
+ IEnumerable<ActionValueItem> items = Assert.IsAssignableFrom<IEnumerable<ActionValueItem>>(actionContext.ActionArguments.First().Value);
+ Assert.Equal(0, items.Count()); // expect unsuccessful bind but proves we don't loop infinitely
+ }
+
+ [Fact]
+ public void BindValuesAsync_Query_String_Values_To_ComplexType_Using_Prefixes()
+ {
+ // Arrange
+ CancellationToken cancellationToken = new CancellationToken();
+ HttpActionContext actionContext = ContextUtil.CreateActionContext(
+ ContextUtil.CreateControllerContext(new HttpRequestMessage()
+ {
+ Method = HttpMethod.Get,
+ RequestUri = new Uri("http://localhost?item.id=5&item.firstName=queryFirstName&item.lastName=queryLastName")
+ }),
+ new ReflectedHttpActionDescriptor() { MethodInfo = typeof(ActionValueController).GetMethod("GetItem") });
+
+ DefaultActionValueBinder provider = new DefaultActionValueBinder();
+
+ // Act
+ provider.BindValuesAsync(actionContext, cancellationToken).Wait();
+
+ // Assert
+ Assert.Equal(1, actionContext.ActionArguments.Count);
+ ActionValueItem deserializedActionValueItem = Assert.IsType<ActionValueItem>(actionContext.ActionArguments.First().Value);
+ Assert.Equal(5, deserializedActionValueItem.Id);
+ Assert.Equal("queryFirstName", deserializedActionValueItem.FirstName);
+ Assert.Equal("queryLastName", deserializedActionValueItem.LastName);
+ }
+
+ [Fact]
+ public void BindValuesAsync_Query_String_Values_To_ComplexType_Using_FromUriAttribute()
+ {
+ // Arrange
+ CancellationToken cancellationToken = new CancellationToken();
+ HttpActionContext actionContext = ContextUtil.CreateActionContext(
+ ContextUtil.CreateControllerContext(new HttpRequestMessage()
+ {
+ Method = HttpMethod.Get,
+ RequestUri = new Uri("http://localhost?item.id=5&item.firstName=queryFirstName&item.lastName=queryLastName")
+ }),
+ new ReflectedHttpActionDescriptor() { MethodInfo = typeof(ActionValueController).GetMethod("GetItemFromUri") });
+
+ DefaultActionValueBinder provider = new DefaultActionValueBinder();
+
+ // Act
+ provider.BindValuesAsync(actionContext, cancellationToken).Wait();
+
+ // Assert
+ Assert.Equal(1, actionContext.ActionArguments.Count);
+ ActionValueItem deserializedActionValueItem = Assert.IsType<ActionValueItem>(actionContext.ActionArguments.First().Value);
+ Assert.Equal(5, deserializedActionValueItem.Id);
+ Assert.Equal("queryFirstName", deserializedActionValueItem.FirstName);
+ Assert.Equal("queryLastName", deserializedActionValueItem.LastName);
+ }
+
+ [Fact]
+ public void BindValuesAsync_Query_String_Values_Using_Custom_ValueProviderAttribute()
+ {
+ // Arrange
+ CancellationToken cancellationToken = new CancellationToken();
+ HttpActionContext actionContext = ContextUtil.CreateActionContext(
+ ContextUtil.CreateControllerContext(new HttpRequestMessage()
+ {
+ Method = HttpMethod.Get
+ }),
+ new ReflectedHttpActionDescriptor() { MethodInfo = typeof(ActionValueController).GetMethod("GetFromCustom") });
+
+ DefaultActionValueBinder provider = new DefaultActionValueBinder();
+
+ // Act
+ provider.BindValuesAsync(actionContext, cancellationToken).Wait();
+
+ // Assert
+ Dictionary<string, object> expectedResult = new Dictionary<string, object>();
+ expectedResult["id"] = 99;
+ expectedResult["firstName"] = "99";
+ expectedResult["lastName"] = "99";
+ Assert.Equal(expectedResult, actionContext.ActionArguments, new DictionaryEqualityComparer());
+ }
+
+ [Fact]
+ public void BindValuesAsync_Query_String_Values_Using_Prefix_To_Rename()
+ {
+ // Arrange
+ CancellationToken cancellationToken = new CancellationToken();
+ HttpActionContext actionContext = ContextUtil.CreateActionContext(
+ ContextUtil.CreateControllerContext(new HttpRequestMessage()
+ {
+ Method = HttpMethod.Get,
+ RequestUri = new Uri("http://localhost?custid=5&first=renamedFirstName&last=renamedLastName")
+ // notice the query string names match the prefixes in GetFromNamed() and not the actual parameter names
+ }),
+ new ReflectedHttpActionDescriptor() { MethodInfo = typeof(ActionValueController).GetMethod("GetFromNamed") });
+
+ DefaultActionValueBinder provider = new DefaultActionValueBinder();
+
+ // Act
+ provider.BindValuesAsync(actionContext, cancellationToken).Wait();
+
+ // Assert
+ Dictionary<string, object> expectedResult = new Dictionary<string, object>();
+ expectedResult["id"] = 5;
+ expectedResult["firstName"] = "renamedFirstName";
+ expectedResult["lastName"] = "renamedLastName";
+ Assert.Equal(expectedResult, actionContext.ActionArguments, new DictionaryEqualityComparer());
+ }
+
+ [Fact]
+ public void BindValuesAsync_Query_String_Values_To_Complex_Types_With_Validation_Error()
+ {
+ // Arrange
+ CancellationToken cancellationToken = new CancellationToken();
+ HttpActionContext actionContext = ContextUtil.CreateActionContext(
+ ContextUtil.CreateControllerContext(new HttpRequestMessage()
+ {
+ Method = HttpMethod.Get,
+ RequestUri = new Uri("http://localhost?id=100&firstName=queryFirstName&lastName=queryLastName")
+ }),
+ new ReflectedHttpActionDescriptor() { MethodInfo = typeof(ActionValueController).GetMethod("GetItem") });
+
+ DefaultActionValueBinder provider = new DefaultActionValueBinder();
+
+ // Act
+ provider.BindValuesAsync(actionContext, cancellationToken).Wait();
+
+ // Assert
+ Assert.False(actionContext.ModelState.IsValid);
+ }
+
+ #endregion Query Strings
+
+ #region RouteData
+
+ [Fact]
+ public void BindValuesAsync_RouteData_Values_To_Simple_Types()
+ {
+ // Arrange
+ CancellationToken cancellationToken = new CancellationToken();
+ HttpRouteData route = new HttpRouteData(new HttpRoute());
+ route.Values.Add("id", 6);
+ route.Values.Add("firstName", "routeFirstName");
+ route.Values.Add("lastName", "routeLastName");
+
+ HttpActionContext controllerContext = ContextUtil.CreateActionContext(
+ ContextUtil.CreateControllerContext(route, new HttpRequestMessage()
+ {
+ Method = HttpMethod.Get,
+ RequestUri = new Uri("http://localhost")
+ }),
+ new ReflectedHttpActionDescriptor() { MethodInfo = typeof(ActionValueController).GetMethod("Get") });
+
+ DefaultActionValueBinder provider = new DefaultActionValueBinder();
+
+ // Act
+ provider.BindValuesAsync(controllerContext, cancellationToken).Wait();
+
+ // Assert
+ Dictionary<string, object> expectedResult = new Dictionary<string, object>();
+ expectedResult["id"] = 6;
+ expectedResult["firstName"] = "routeFirstName";
+ expectedResult["lastName"] = "routeLastName";
+ Assert.Equal(expectedResult, controllerContext.ActionArguments, new DictionaryEqualityComparer());
+ }
+
+ [Fact]
+ public void BindValuesAsync_RouteData_Values_To_Simple_Types_Using_FromUriAttribute()
+ {
+ // Arrange
+ CancellationToken cancellationToken = new CancellationToken();
+ HttpRouteData route = new HttpRouteData(new HttpRoute());
+ route.Values.Add("id", 6);
+ route.Values.Add("firstName", "routeFirstName");
+ route.Values.Add("lastName", "routeLastName");
+
+ HttpActionContext controllerContext = ContextUtil.CreateActionContext(
+ ContextUtil.CreateControllerContext(route, new HttpRequestMessage()
+ {
+ Method = HttpMethod.Get,
+ RequestUri = new Uri("http://localhost")
+ }),
+ new ReflectedHttpActionDescriptor() { MethodInfo = typeof(ActionValueController).GetMethod("Get") });
+
+ DefaultActionValueBinder provider = new DefaultActionValueBinder();
+
+ // Act
+ provider.BindValuesAsync(controllerContext, cancellationToken).Wait();
+
+ // Assert
+ Dictionary<string, object> expectedResult = new Dictionary<string, object>();
+ expectedResult["id"] = 6;
+ expectedResult["firstName"] = "routeFirstName";
+ expectedResult["lastName"] = "routeLastName";
+ Assert.Equal(expectedResult, controllerContext.ActionArguments, new DictionaryEqualityComparer());
+ }
+
+ [Fact]
+ public void BindValuesAsync_RouteData_Values_To_Complex_Types()
+ {
+ // Arrange
+ CancellationToken cancellationToken = new CancellationToken();
+ HttpRouteData route = new HttpRouteData(new HttpRoute());
+ route.Values.Add("id", 6);
+ route.Values.Add("firstName", "routeFirstName");
+ route.Values.Add("lastName", "routeLastName");
+
+ HttpActionContext controllerContext = ContextUtil.CreateActionContext(
+ ContextUtil.CreateControllerContext(route, new HttpRequestMessage()
+ {
+ Method = HttpMethod.Get,
+ RequestUri = new Uri("http://localhost")
+ }),
+ new ReflectedHttpActionDescriptor() { MethodInfo = typeof(ActionValueController).GetMethod("GetItem") });
+
+ DefaultActionValueBinder provider = new DefaultActionValueBinder();
+
+ // Act
+ provider.BindValuesAsync(controllerContext, cancellationToken).Wait();
+
+ // Assert
+ Assert.Equal(1, controllerContext.ActionArguments.Count);
+ ActionValueItem deserializedActionValueItem = Assert.IsType<ActionValueItem>(controllerContext.ActionArguments.First().Value);
+ Assert.Equal(6, deserializedActionValueItem.Id);
+ Assert.Equal("routeFirstName", deserializedActionValueItem.FirstName);
+ Assert.Equal("routeLastName", deserializedActionValueItem.LastName);
+ }
+
+ [Fact]
+ public void BindValuesAsync_RouteData_Values_To_Complex_Types_Using_FromUriAttribute()
+ {
+ // Arrange
+ CancellationToken cancellationToken = new CancellationToken();
+ HttpRouteData route = new HttpRouteData(new HttpRoute());
+ route.Values.Add("id", 6);
+ route.Values.Add("firstName", "routeFirstName");
+ route.Values.Add("lastName", "routeLastName");
+
+ HttpActionContext controllerContext = ContextUtil.CreateActionContext(
+ ContextUtil.CreateControllerContext(route, new HttpRequestMessage()
+ {
+ Method = HttpMethod.Get,
+ RequestUri = new Uri("http://localhost")
+ }),
+ new ReflectedHttpActionDescriptor() { MethodInfo = typeof(ActionValueController).GetMethod("GetItemFromUri") });
+
+ DefaultActionValueBinder provider = new DefaultActionValueBinder();
+
+ // Act
+ provider.BindValuesAsync(controllerContext, cancellationToken).Wait();
+
+ // Assert
+ Assert.Equal(1, controllerContext.ActionArguments.Count);
+ ActionValueItem deserializedActionValueItem = Assert.IsType<ActionValueItem>(controllerContext.ActionArguments.First().Value);
+ Assert.Equal(6, deserializedActionValueItem.Id);
+ Assert.Equal("routeFirstName", deserializedActionValueItem.FirstName);
+ Assert.Equal("routeLastName", deserializedActionValueItem.LastName);
+ }
+
+ #endregion RouteData
+
+ #region ControllerContext
+ [Fact]
+ public void BindValuesAsync_ControllerContext_CancellationToken()
+ {
+ // Arrange
+ CancellationToken cancellationToken = new CancellationToken();
+ HttpActionContext actionContext = ContextUtil.CreateActionContext(
+ ContextUtil.CreateControllerContext(new HttpRequestMessage()
+ {
+ Method = HttpMethod.Get
+ }),
+ new ReflectedHttpActionDescriptor() { MethodInfo = typeof(ActionValueController).GetMethod("GetFromCancellationToken") });
+
+ DefaultActionValueBinder provider = new DefaultActionValueBinder();
+
+ // Act
+ provider.BindValuesAsync(actionContext, cancellationToken).Wait();
+
+ // Assert
+ Assert.Equal(1, actionContext.ActionArguments.Count);
+ Assert.Equal(cancellationToken, actionContext.ActionArguments.First().Value);
+ }
+ #endregion ControllerContext
+
+ #region Body
+
+ [Fact]
+ public void BindValuesAsync_Body_To_Complex_Type_Json()
+ {
+ // Arrange
+ CancellationToken cancellationToken = new CancellationToken();
+ string jsonString = "{\"Id\":\"7\",\"FirstName\":\"testFirstName\",\"LastName\":\"testLastName\"}";
+ StringContent stringContent = new StringContent(jsonString, Encoding.UTF8, "application/json");
+
+ HttpRequestMessage request = new HttpRequestMessage() { Content = stringContent };
+ HttpActionContext context = ContextUtil.CreateActionContext(
+ ContextUtil.CreateControllerContext(request),
+ new ReflectedHttpActionDescriptor() { MethodInfo = typeof(ActionValueController).GetMethod("PostComplexType") });
+
+ DefaultActionValueBinder provider = new DefaultActionValueBinder();
+
+ // Act
+ provider.BindValuesAsync(context, cancellationToken).Wait();
+
+ // Assert
+ Assert.Equal(1, context.ActionArguments.Count);
+ ActionValueItem deserializedActionValueItem = Assert.IsAssignableFrom<ActionValueItem>(context.ActionArguments.First().Value);
+ Assert.Equal(7, deserializedActionValueItem.Id);
+ Assert.Equal("testFirstName", deserializedActionValueItem.FirstName);
+ Assert.Equal("testLastName", deserializedActionValueItem.LastName);
+ }
+
+ [Fact]
+ public void BindValuesAsync_Body_To_Complex_Type_Json_With_Validation_Error()
+ {
+ // Arrange
+ CancellationToken cancellationToken = new CancellationToken();
+ string jsonString = "{\"Id\":\"100\",\"FirstName\":\"testFirstName\",\"LastName\":\"testLastName\"}";
+ StringContent stringContent = new StringContent(jsonString, Encoding.UTF8, "application/json");
+
+ HttpRequestMessage request = new HttpRequestMessage() { Content = stringContent };
+ HttpActionContext context = ContextUtil.CreateActionContext(
+ ContextUtil.CreateControllerContext(request),
+ new ReflectedHttpActionDescriptor() { MethodInfo = typeof(ActionValueController).GetMethod("PostComplexType") });
+
+ DefaultActionValueBinder provider = new DefaultActionValueBinder();
+
+ // Act
+ provider.BindValuesAsync(context, cancellationToken).Wait();
+
+ // Assert
+ Assert.False(context.ModelState.IsValid);
+ }
+
+ [Fact]
+ public void BindValuesAsync_Body_To_Complex_Type_FormUrlEncoded()
+ {
+ // Arrange
+ CancellationToken cancellationToken = new CancellationToken();
+ string formUrlEncodedString = "Id=7&FirstName=testFirstName&LastName=testLastName";
+ StringContent stringContent = new StringContent(formUrlEncodedString, Encoding.UTF8, "application/x-www-form-urlencoded");
+
+ HttpRequestMessage request = new HttpRequestMessage() { Content = stringContent };
+ HttpActionContext context = ContextUtil.CreateActionContext(
+ ContextUtil.CreateControllerContext(request),
+ new ReflectedHttpActionDescriptor() { MethodInfo = typeof(ActionValueController).GetMethod("PostComplexType") });
+
+ DefaultActionValueBinder provider = new DefaultActionValueBinder();
+
+ // Act
+ provider.BindValuesAsync(context, cancellationToken).Wait();
+
+ // Assert
+ Assert.Equal(1, context.ActionArguments.Count);
+ ActionValueItem deserializedActionValueItem = Assert.IsAssignableFrom<ActionValueItem>(context.ActionArguments.First().Value);
+ Assert.Equal(7, deserializedActionValueItem.Id);
+ Assert.Equal("testFirstName", deserializedActionValueItem.FirstName);
+ Assert.Equal("testLastName", deserializedActionValueItem.LastName);
+ }
+
+ [Fact]
+ public void BindValuesAsync_Body_To_Complex_Type_FormUrlEncoded_With_Validation_Error()
+ {
+ // Arrange
+ CancellationToken cancellationToken = new CancellationToken();
+ string formUrlEncodedString = "Id=101&FirstName=testFirstName&LastName=testLastName";
+ StringContent stringContent = new StringContent(formUrlEncodedString, Encoding.UTF8, "application/x-www-form-urlencoded");
+
+ HttpRequestMessage request = new HttpRequestMessage() { Content = stringContent };
+ HttpActionContext context = ContextUtil.CreateActionContext(
+ ContextUtil.CreateControllerContext(request),
+ new ReflectedHttpActionDescriptor() { MethodInfo = typeof(ActionValueController).GetMethod("PostComplexType") });
+
+ DefaultActionValueBinder provider = new DefaultActionValueBinder();
+
+ // Act
+ provider.BindValuesAsync(context, cancellationToken).Wait();
+
+ // Assert
+ Assert.False(context.ModelState.IsValid);
+ }
+
+ [Fact]
+ public void BindValuesAsync_Body_To_Complex_Type_Xml()
+ {
+ // Arrange
+ CancellationToken cancellationToken = new CancellationToken();
+ MediaTypeHeaderValue mediaType = new MediaTypeHeaderValue("application/xml");
+ ActionValueItem item = new ActionValueItem() { Id = 7, FirstName = "testFirstName", LastName = "testLastName" };
+ ObjectContent<ActionValueItem> tempContent = new ObjectContent<ActionValueItem>(item, new XmlMediaTypeFormatter());
+ StringContent stringContent = new StringContent(tempContent.ReadAsStringAsync().Result);
+ stringContent.Headers.ContentType = mediaType;
+ HttpRequestMessage request = new HttpRequestMessage() { Content = stringContent };
+ HttpActionContext context = ContextUtil.CreateActionContext(
+ ContextUtil.CreateControllerContext(request),
+ new ReflectedHttpActionDescriptor() { MethodInfo = typeof(ActionValueController).GetMethod("PostComplexType") });
+
+ DefaultActionValueBinder provider = new DefaultActionValueBinder();
+
+ // Act
+ provider.BindValuesAsync(context, cancellationToken).Wait();
+
+ // Assert
+ Assert.Equal(1, context.ActionArguments.Count);
+ ActionValueItem deserializedActionValueItem = Assert.IsAssignableFrom<ActionValueItem>(context.ActionArguments.First().Value);
+ Assert.Equal(item.Id, deserializedActionValueItem.Id);
+ Assert.Equal(item.FirstName, deserializedActionValueItem.FirstName);
+ Assert.Equal(item.LastName, deserializedActionValueItem.LastName);
+ }
+
+ [Fact]
+ public void BindValuesAsync_Body_To_Complex_Type_Xml_Structural()
+ {
+ // Arrange
+ CancellationToken cancellationToken = new CancellationToken();
+ MediaTypeHeaderValue mediaType = new MediaTypeHeaderValue("application/xml");
+
+ // Test sending from a non .NET type (raw xml).
+ // The default XML serializer requires that the xml root name matches the C# class name.
+ string xmlSource =
+ @"<?xml version='1.0' encoding='utf-8'?>
+ <ActionValueItem xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance' xmlns:xsd='http://www.w3.org/2001/XMLSchema'>
+ <Id>7</Id>
+ <FirstName>testFirstName</FirstName>
+ <LastName>testLastName</LastName>
+ </ActionValueItem>".Replace('\'', '"');
+
+ StringContent stringContent = new StringContent(xmlSource);
+ stringContent.Headers.ContentType = mediaType;
+ HttpRequestMessage request = new HttpRequestMessage() { Content = stringContent };
+ HttpActionContext context = ContextUtil.CreateActionContext(
+ ContextUtil.CreateControllerContext(request),
+ new ReflectedHttpActionDescriptor() { MethodInfo = typeof(ActionValueController).GetMethod("PostComplexType") });
+
+ DefaultActionValueBinder provider = new DefaultActionValueBinder();
+
+ // Act
+ provider.BindValuesAsync(context, cancellationToken).Wait();
+
+ // Assert
+ Assert.Equal(1, context.ActionArguments.Count);
+ ActionValueItem deserializedActionValueItem = Assert.IsAssignableFrom<ActionValueItem>(context.ActionArguments.First().Value);
+ Assert.Equal(7, deserializedActionValueItem.Id);
+ Assert.Equal("testFirstName", deserializedActionValueItem.FirstName);
+ Assert.Equal("testLastName", deserializedActionValueItem.LastName);
+ }
+
+ [Fact]
+ public void BindValuesAsync_Body_To_Complex_Type_Xml_With_Validation_Error()
+ {
+ // Arrange
+ CancellationToken cancellationToken = new CancellationToken();
+ MediaTypeHeaderValue mediaType = new MediaTypeHeaderValue("application/xml");
+ ActionValueItem item = new ActionValueItem() { Id = 101, FirstName = "testFirstName", LastName = "testLastName" };
+ var tempContent = new ObjectContent<ActionValueItem>(item, new XmlMediaTypeFormatter());
+ StringContent stringContent = new StringContent(tempContent.ReadAsStringAsync().Result);
+ stringContent.Headers.ContentType = mediaType;
+ HttpRequestMessage request = new HttpRequestMessage() { Content = stringContent };
+ HttpActionContext context = ContextUtil.CreateActionContext(
+ ContextUtil.CreateControllerContext(request),
+ new ReflectedHttpActionDescriptor() { MethodInfo = typeof(ActionValueController).GetMethod("PostComplexType") });
+
+ DefaultActionValueBinder provider = new DefaultActionValueBinder();
+
+ // Act
+ provider.BindValuesAsync(context, cancellationToken).Wait();
+
+ // Assert
+ Assert.False(context.ModelState.IsValid);
+ }
+
+ [Fact]
+ public void BindValuesAsync_Body_To_Complex_And_Uri_To_Simple()
+ {
+ // Arrange
+ string jsonString = "{\"Id\":\"7\",\"FirstName\":\"testFirstName\",\"LastName\":\"testLastName\"}";
+ StringContent stringContent = new StringContent(jsonString, Encoding.UTF8, "application/json");
+
+ HttpRequestMessage request = new HttpRequestMessage()
+ {
+ RequestUri = new Uri("http://localhost/ActionValueController/PostFromBody?id=123"),
+ Content = stringContent
+ };
+
+ HttpActionContext context = ContextUtil.CreateActionContext(
+ ContextUtil.CreateControllerContext(request),
+ new ReflectedHttpActionDescriptor() { MethodInfo = typeof(ActionValueController).GetMethod("PostFromBodyAndUri") });
+
+ DefaultActionValueBinder provider = new DefaultActionValueBinder();
+
+ // Act
+ provider.BindValuesAsync(context, CancellationToken.None).Wait();
+
+ // Assert
+ Assert.Equal(2, context.ActionArguments.Count);
+ Assert.Equal(123, context.ActionArguments["id"]);
+
+ ActionValueItem deserializedActionValueItem = Assert.IsAssignableFrom<ActionValueItem>(context.ActionArguments["item"]);
+ Assert.Equal(7, deserializedActionValueItem.Id);
+ Assert.Equal("testFirstName", deserializedActionValueItem.FirstName);
+ Assert.Equal("testLastName", deserializedActionValueItem.LastName);
+ }
+
+ [Fact]
+ public void BindValuesAsync_Body_To_Complex_Type_Using_FromBodyAttribute()
+ {
+ // Arrange
+ CancellationToken cancellationToken = new CancellationToken();
+ string jsonString = "{\"Id\":\"7\",\"FirstName\":\"testFirstName\",\"LastName\":\"testLastName\"}";
+ StringContent stringContent = new StringContent(jsonString, Encoding.UTF8, "application/json");
+
+ HttpRequestMessage request = new HttpRequestMessage() { Content = stringContent };
+
+ HttpActionContext context = ContextUtil.CreateActionContext(
+ ContextUtil.CreateControllerContext(request),
+ new ReflectedHttpActionDescriptor() { MethodInfo = typeof(ActionValueController).GetMethod("PostFromBody") });
+
+ DefaultActionValueBinder provider = new DefaultActionValueBinder();
+
+ // Act
+ provider.BindValuesAsync(context, cancellationToken).Wait();
+
+ // Assert
+ Assert.Equal(1, context.ActionArguments.Count);
+ ActionValueItem deserializedActionValueItem = Assert.IsAssignableFrom<ActionValueItem>(context.ActionArguments.First().Value);
+ Assert.Equal(7, deserializedActionValueItem.Id);
+ Assert.Equal("testFirstName", deserializedActionValueItem.FirstName);
+ Assert.Equal("testLastName", deserializedActionValueItem.LastName);
+ }
+
+ [Fact]
+ public void BindValuesAsync_Body_To_Complex_Type_Using_Formatter_To_Deserialize()
+ {
+ // Arrange
+ CancellationToken cancellationToken = new CancellationToken();
+ string jsonString = "{\"Id\":\"7\",\"FirstName\":\"testFirstName\",\"LastName\":\"testLastName\"}";
+ StringContent stringContent = new StringContent(jsonString, Encoding.UTF8, "application/json");
+
+ HttpRequestMessage request = new HttpRequestMessage() { Content = stringContent };
+ HttpActionContext context = ContextUtil.CreateActionContext(
+ ContextUtil.CreateControllerContext(request),
+ new ReflectedHttpActionDescriptor() { MethodInfo = typeof(ActionValueController).GetMethod("PostComplexType") });
+ DefaultActionValueBinder provider = new DefaultActionValueBinder();
+
+ // Act
+ provider.BindValuesAsync(context, cancellationToken).Wait();
+
+ // Assert
+ Assert.Equal(1, context.ActionArguments.Count);
+ ActionValueItem deserializedActionValueItem = Assert.IsAssignableFrom<ActionValueItem>(context.ActionArguments.First().Value);
+ Assert.Equal(7, deserializedActionValueItem.Id);
+ Assert.Equal("testFirstName", deserializedActionValueItem.FirstName);
+ Assert.Equal("testLastName", deserializedActionValueItem.LastName);
+ }
+
+
+ [Fact]
+ public void BindValuesAsync_Body_To_IEnumerable_Complex_Type_Json()
+ {
+ // ModelBinding will bind T to IEnumerable<T>, but JSON.Net won't. So enclose JSON in [].
+ // Arrange
+ CancellationToken cancellationToken = new CancellationToken();
+ string jsonString = "[{\"Id\":\"7\",\"FirstName\":\"testFirstName\",\"LastName\":\"testLastName\"}]";
+ StringContent stringContent = new StringContent(jsonString, Encoding.UTF8, "application/json");
+
+ HttpRequestMessage request = new HttpRequestMessage() { Content = stringContent };
+ HttpActionContext context = ContextUtil.CreateActionContext(
+ ContextUtil.CreateControllerContext(request),
+ new ReflectedHttpActionDescriptor() { MethodInfo = typeof(ActionValueController).GetMethod("PostEnumerable") });
+
+ DefaultActionValueBinder provider = new DefaultActionValueBinder();
+
+ // Act
+ provider.BindValuesAsync(context, cancellationToken).Wait();
+
+ // Assert
+ Assert.Equal(1, context.ActionArguments.Count);
+ IEnumerable<ActionValueItem> items = Assert.IsAssignableFrom<IEnumerable<ActionValueItem>>(context.ActionArguments.First().Value);
+ ActionValueItem deserializedActionValueItem = items.First();
+ Assert.Equal(7, deserializedActionValueItem.Id);
+ Assert.Equal("testFirstName", deserializedActionValueItem.FirstName);
+ Assert.Equal("testLastName", deserializedActionValueItem.LastName);
+ }
+
+ [Fact]
+ public void BindValuesAsync_Body_To_JsonValue()
+ {
+ // Arrange
+ CancellationToken cancellationToken = new CancellationToken();
+ MediaTypeHeaderValue mediaType = new MediaTypeHeaderValue("application/json");
+ ActionValueItem item = new ActionValueItem() { Id = 7, FirstName = "testFirstName", LastName = "testLastName" };
+ string json = "{\"a\":123,\"b\":[false,null,12.34]}";
+ JsonValue jv = JsonValue.Parse(json);
+ var tempContent = new ObjectContent<JsonValue>(jv, new JsonMediaTypeFormatter());
+ StringContent stringContent = new StringContent(tempContent.ReadAsStringAsync().Result);
+ stringContent.Headers.ContentType = mediaType;
+ HttpRequestMessage request = new HttpRequestMessage() { Content = stringContent };
+ HttpActionContext context = ContextUtil.CreateActionContext(
+ ContextUtil.CreateControllerContext(request),
+ new ReflectedHttpActionDescriptor() { MethodInfo = typeof(ActionValueController).GetMethod("PostJsonValue") });
+
+ DefaultActionValueBinder provider = new DefaultActionValueBinder();
+
+ // Act
+ provider.BindValuesAsync(context, cancellationToken).Wait();
+
+ // Assert
+ Assert.Equal(1, context.ActionArguments.Count);
+ JsonValue deserializedJsonValue = Assert.IsAssignableFrom<JsonValue>(context.ActionArguments.First().Value);
+ string deserializedJsonAsString = deserializedJsonValue.ToString();
+ Assert.Equal(json, deserializedJsonAsString);
+ }
+
+ #endregion Body
+ }
+
+ public class ActionValueController : ApiController
+ {
+ // Demonstrates parameter that can come from route, query string, or defaults
+ public ActionValueItem Get(int id = 0, string firstName = "DefaultFirstName", string lastName = "DefaultLastName")
+ {
+ return new ActionValueItem() { Id = id, FirstName = firstName, LastName = lastName };
+ }
+
+ // Demonstrates an explicit override to obtain parameters from URL
+ public ActionValueItem GetFromUri([FromUri] int id = 0,
+ [FromUri] string firstName = "DefaultFirstName",
+ [FromUri] string lastName = "DefaultLastName")
+ {
+ return new ActionValueItem() { Id = id, FirstName = firstName, LastName = lastName };
+ }
+
+
+ // Complex objects default to body. But we can bind from URI with an attribute.
+ public ActionValueItem GetItem([FromUri] ActionValueItem item)
+ {
+ return item;
+ }
+
+ // Demonstrates ModelBinding a Item object explicitly from Uri
+ public ActionValueItem GetItemFromUri([FromUri] ActionValueItem item)
+ {
+ return item;
+ }
+
+ // Demonstrates use of renaming parameters via prefix
+ public ActionValueItem GetFromNamed([FromUri(Prefix = "custID")] int id,
+ [FromUri(Prefix = "first")] string firstName,
+ [FromUri(Prefix = "last")] string lastName)
+ {
+ return new ActionValueItem() { Id = id, FirstName = firstName, LastName = lastName };
+ }
+
+ // Demonstrates use of custom ValueProvider via attribute
+ public ActionValueItem GetFromCustom([ValueProvider(typeof(ActionValueControllerValueProviderFactory), Prefix = "id")] int id,
+ [ValueProvider(typeof(ActionValueControllerValueProviderFactory), Prefix = "customFirstName")] string firstName,
+ [ValueProvider(typeof(ActionValueControllerValueProviderFactory), Prefix = "customLastName")] string lastName)
+ {
+ return new ActionValueItem() { Id = id, FirstName = firstName, LastName = lastName };
+ }
+
+ // Demonstrates ModelBinding to the CancellationToken of the current request
+ public string GetFromCancellationToken(CancellationToken cancellationToken)
+ {
+ return cancellationToken.ToString();
+ }
+
+ // Demonstrates ModelBinding to the ModelState of the current request
+ public string GetFromModelState(ModelState modelState)
+ {
+ return modelState.ToString();
+ }
+
+ // Demonstrates binding to complex type from body
+ public ActionValueItem PostComplexType(ActionValueItem item)
+ {
+ return item;
+ }
+
+ // Demonstrates binding to complex type from uri
+ public ActionValueItem PostComplexTypeUri([FromUri] ActionValueItem item)
+ {
+ return item;
+ }
+
+ // Demonstrates binding to IEnumerable of complex type from body or Uri
+ public ActionValueItem PostEnumerable(IEnumerable<ActionValueItem> items)
+ {
+ return items.FirstOrDefault();
+ }
+
+ // Demonstrates binding to IEnumerable of complex type from body or Uri
+ public ActionValueItem PostEnumerableUri([FromUri] IEnumerable<ActionValueItem> items)
+ {
+ return items.FirstOrDefault();
+ }
+
+ // Demonstrates binding to JsonValue from body
+ public JsonValue PostJsonValue(JsonValue jsonValue)
+ {
+ return jsonValue;
+ }
+
+ // Demonstrate what we expect to be the common default scenario. No attributes are required.
+ // A complex object comes from the body, and simple objects come from the URI.
+ public ActionValueItem PostFromBodyAndUri(int id, ActionValueItem item)
+ {
+ return item;
+ }
+
+ // Demonstrates binding to complex type explicitly marked as coming from body
+ public ActionValueItem PostFromBody([FromBody] ActionValueItem item)
+ {
+ return item;
+ }
+
+ // Demonstrates how body can be shredded to name/value pairs to bind to simple types
+ public ActionValueItem PostToSimpleTypes(int id, string firstName, string lastName)
+ {
+ return new ActionValueItem() { Id = id, FirstName = firstName, LastName = lastName };
+ }
+
+ // Demonstrates binding to ObjectContent<T> from request body
+ public ActionValueItem PostObjectContentOfItem(ObjectContent<ActionValueItem> item)
+ {
+ return item.ReadAsAsync<ActionValueItem>().Result;
+ }
+
+ public class ActionValueControllerValueProviderFactory : ValueProviderFactory
+ {
+ public override IValueProvider GetValueProvider(HttpActionContext actionContext)
+ {
+ return new ActionValueControllerValueProvider();
+ }
+ }
+
+ public class ActionValueControllerValueProvider : IValueProvider
+ {
+ public bool ContainsPrefix(string prefix)
+ {
+ return true;
+ }
+
+ public ValueProviderResult GetValue(string key)
+ {
+ return new ValueProviderResult("99", "99", CultureInfo.CurrentCulture);
+ }
+ }
+ }
+
+ static class DefaultActionValueBinderExtensions
+ {
+ public static Task BindValuesAsync(this DefaultActionValueBinder binder, HttpActionContext actionContext, CancellationToken cancellationToken)
+ {
+ HttpActionBinding binding = binder.GetBinding(actionContext.ActionDescriptor);
+ return binding.ExecuteBindingAsync(actionContext, cancellationToken);
+ }
+ }
+
+ public class ActionValueItem
+ {
+ [Range(0, 99)]
+ public int Id { get; set; }
+ public string FirstName { get; set; }
+ public string LastName { get; set; }
+ }
+}
diff --git a/test/System.Web.Http.Integration.Test/ModelBinding/HttpContentBindingTests.cs b/test/System.Web.Http.Integration.Test/ModelBinding/HttpContentBindingTests.cs
new file mode 100644
index 00000000..527a2ea1
--- /dev/null
+++ b/test/System.Web.Http.Integration.Test/ModelBinding/HttpContentBindingTests.cs
@@ -0,0 +1,92 @@
+using System.Net;
+using System.Net.Http;
+using System.Net.Http.Formatting;
+using System.Web.Http.SelfHost;
+using Xunit;
+using Xunit.Extensions;
+
+namespace System.Web.Http.ModelBinding
+{
+ /// <summary>
+ /// Tests actions that directly use HttpRequestMessage parameters
+ /// </summary>
+ public class HttpContentBindingTests : IDisposable
+ {
+ public HttpContentBindingTests()
+ {
+ this.SetupHost();
+ }
+
+ public void Dispose()
+ {
+ this.CleanupHost();
+ }
+
+ [Theory]
+ [InlineData("application/xml")]
+ [InlineData("text/xml")]
+ [InlineData("application/json")]
+ [InlineData("text/json")]
+ public void Action_Directly_Reads_HttpRequestMessage(string mediaType)
+ {
+ Order order = new Order() { OrderId = "99", OrderValue = 100.0 };
+ var formatter = new MediaTypeFormatterCollection().Find(mediaType);
+ HttpRequestMessage request = new HttpRequestMessage()
+ {
+ Content = new ObjectContent<Order>(order, formatter, mediaType),
+ RequestUri = new Uri(baseAddress + "/HttpContentBinding/HandleMessage"),
+ Method = HttpMethod.Post
+ };
+
+ HttpResponseMessage response = httpClient.SendAsync(request).Result;
+
+ Order receivedOrder = response.Content.ReadAsAsync<Order>().Result;
+ Assert.Equal(order.OrderId, receivedOrder.OrderId);
+ Assert.Equal(order.OrderValue, receivedOrder.OrderValue);
+ }
+
+ private HttpSelfHostServer server = null;
+ private string baseAddress = null;
+ private HttpClient httpClient = null;
+
+ private void SetupHost()
+ {
+ httpClient = new HttpClient();
+
+ baseAddress = String.Format("http://{0}", Environment.MachineName);
+
+ HttpSelfHostConfiguration config = new HttpSelfHostConfiguration(baseAddress);
+ config.Routes.MapHttpRoute("Default", "{controller}/{action}", new { controller = "HttpContentBinding", action = "HandleMessage" });
+
+ server = new HttpSelfHostServer(config);
+ server.OpenAsync().Wait();
+ }
+
+ private void CleanupHost()
+ {
+ if (server != null)
+ {
+ server.CloseAsync().Wait();
+ }
+ }
+ }
+
+ public class Order
+ {
+ public string OrderId { get; set; }
+ public double OrderValue { get; set; }
+ }
+
+ public class HttpContentBindingController : ApiController
+ {
+ [HttpPost]
+ public HttpResponseMessage HandleMessage()
+ {
+ Order order = Request.Content.ReadAsAsync<Order>().Result;
+ return new HttpResponseMessage(HttpStatusCode.OK)
+ {
+ Content = new ObjectContent<Order>(order, new JsonMediaTypeFormatter())
+ };
+ }
+ }
+} \ No newline at end of file
diff --git a/test/System.Web.Http.Integration.Test/ModelBinding/ModelBindingController.cs b/test/System.Web.Http.Integration.Test/ModelBinding/ModelBindingController.cs
new file mode 100644
index 00000000..09af7b52
--- /dev/null
+++ b/test/System.Web.Http.Integration.Test/ModelBinding/ModelBindingController.cs
@@ -0,0 +1,272 @@
+using System.Collections.Generic;
+using System.ComponentModel.DataAnnotations;
+using System.Globalization;
+using System.Linq;
+using System.Net;
+using System.Net.Http;
+using System.Threading;
+using System.Threading.Tasks;
+using System.Web.Http.Controllers;
+using System.Web.Http.ValueProviders;
+using Xunit;
+
+namespace System.Web.Http.ModelBinding
+{
+ public class ModelBindingController : ApiController
+ {
+ public string GetString(string value)
+ {
+ return value;
+ }
+
+ public string GetStringFromRoute(string controller, string action)
+ {
+ return controller + ":" + action;
+ }
+
+ public int GetInt(int value)
+ {
+ return value;
+ }
+
+ public int GetIntWithDefault(int value = -1)
+ {
+ return value;
+ }
+
+ public int GetIntFromUri([FromUri] int value)
+ {
+ return value;
+ }
+
+ public int GetIntPrefixed([FromUri(Prefix = "somePrefix")] int value)
+ {
+ return value;
+ }
+
+ public int GetIntCustom([ValueProvider(typeof(RequestHeadersValueProviderFactory))] int value)
+ {
+ return value;
+ }
+
+ public Task<int> GetIntAsync(int value, CancellationToken token)
+ {
+ Assert.NotNull(token);
+ TaskCompletionSource<int> tcs = new TaskCompletionSource<int>();
+ tcs.TrySetResult(value);
+ return tcs.Task;
+ }
+
+ public bool GetBool(bool value)
+ {
+ return value;
+ }
+
+ public ModelBindOrder GetComplexType(ModelBindOrder item)
+ {
+ return item;
+ }
+
+ public ModelBindOrder GetComplexTypeFromUri([FromUri] ModelBindOrder item)
+ {
+ return item;
+ }
+
+ public string PostString(string value)
+ {
+ return value;
+ }
+
+ public int PostInt(int value)
+ {
+ return value;
+ }
+
+ public HttpResponseMessage PostComplexWithValidation(CustomerNameMax6 customer)
+ {
+ // Request should not be null
+ if (this.Request == null)
+ {
+ throw new HttpResponseException("ApiController.Request should not be null.");
+ }
+
+ // Configuration should not be null
+ if (this.Configuration == null)
+ {
+ throw new HttpResponseException("ApiController.Configuration should not be null.");
+ }
+
+ // ModelState information
+ if (this.ModelState == null)
+ {
+ throw new HttpResponseException("ApiController.ModelState should not be null.");
+ }
+ else
+ {
+ string errors = String.Empty;
+ foreach (var kv in this.ModelState)
+ {
+ int errorCount = kv.Value.Errors.Count;
+
+ if (errorCount > 0)
+ {
+ errors += String.Format("Failed to bind {0}. The errors are:", kv.Key);
+ for (int i = 0; i < errorCount; i++)
+ {
+ ModelError error = kv.Value.Errors[i];
+ errors += "\nErrorMessage: " + error.ErrorMessage;
+
+ if (error.Exception != null)
+ {
+ errors += "\nException" + error.Exception;
+ }
+ }
+ }
+ }
+
+ if (errors != String.Empty)
+ {
+ // Has validation failure
+ // TODO, 334736, support HttpResponseException which takes ModelState
+ // throw new HttpResponseException(this.ModelState);
+ HttpResponseMessage response = new HttpResponseMessage(HttpStatusCode.BadRequest);
+ response.Content = new StringContent(errors);
+ throw new HttpResponseException(response);
+ }
+ else
+ {
+ // happy path
+ return Request.CreateResponse<int>(HttpStatusCode.OK, customer.Id);
+ }
+ }
+ }
+
+ public int PostIntFromUri([FromUri] int value)
+ {
+ return value;
+ }
+
+ public int PostIntFromBody([FromBody] int value)
+ {
+ return value;
+ }
+
+ public int PostIntUriPrefixed([FromUri(Prefix = "somePrefix")] int value)
+ {
+ return value;
+ }
+
+ public bool PostBool(bool value)
+ {
+ return value;
+ }
+
+ public int PostIntArray([FromUri] int[] value)
+ {
+ return value.Sum();
+ }
+
+ public ModelBindOrder PostComplexType(ModelBindOrder item)
+ {
+ return item;
+ }
+
+ public ModelBindOrder PostComplexTypeFromUri([FromUri] ModelBindOrder item)
+ {
+ return item;
+ }
+
+ public ModelBindOrder PostComplexTypeFromBody([FromBody] ModelBindOrder item)
+ {
+ return item;
+ }
+
+ // check if HttpRequestMessage prevents binding other parameters
+ public int PostComplexTypeHttpRequestMessage(HttpRequestMessage request, ModelBindOrder order)
+ {
+ return Int32.Parse(order.ItemName) + order.Quantity;
+ }
+ }
+
+ public class CustomerNameMax6
+ {
+ [Required]
+ [StringLength(6)]
+ public string Name { get; set; }
+
+ public int Id { get; set; }
+ }
+
+ public class ModelBindCustomer
+ {
+ public string Name { get; set; }
+ }
+
+ public class ModelBindOrder
+ {
+ public string ItemName { get; set; }
+ public int Quantity { get; set; }
+ public ModelBindCustomer Customer { get; set; }
+ }
+
+ public class ModelBindOrderEqualityComparer : IEqualityComparer<ModelBindOrder>
+ {
+ public bool Equals(ModelBindOrder x, ModelBindOrder y)
+ {
+ Assert.True(x != null, "Expected ModelBindOrder cannot be null.");
+ Assert.True(y != null, "Actual ModelBindOrder was null.");
+ Assert.Equal<string>(x.ItemName, y.ItemName);
+ Assert.Equal<int>(x.Quantity, y.Quantity);
+
+ if (x.Customer != null)
+ {
+ Assert.True(y.Customer != null, "Actual Customer was null but expected was " + x.Customer.Name);
+ }
+ else if (x.Customer == null)
+ {
+ Assert.True(y.Customer == null, "Actual Customer was not null but should have been.");
+ }
+ else
+ {
+ Assert.True(String.Equals(x.Customer.Name, y.Customer.Name, StringComparison.Ordinal), String.Format("Expected Customer.Name '{0}' but actual was '{1}'", x.Customer.Name, y.Customer.Name));
+ }
+
+ return true;
+ }
+
+ public int GetHashCode(ModelBindOrder obj)
+ {
+ return obj.GetHashCode();
+ }
+ }
+
+ public class RequestHeadersValueProviderFactory : ValueProviderFactory
+ {
+ public override IValueProvider GetValueProvider(HttpActionContext actionContext)
+ {
+ return new RequestHeaderValueProvider(actionContext);
+ }
+ }
+
+ public class RequestHeaderValueProvider : IValueProvider
+ {
+ HttpActionContext _actionContext;
+ public RequestHeaderValueProvider(HttpActionContext actionContext)
+ {
+ _actionContext = actionContext;
+ }
+
+ public bool ContainsPrefix(string prefix)
+ {
+ return _actionContext.ControllerContext.Request.Headers.Contains(prefix);
+ }
+
+ public ValueProviderResult GetValue(string key)
+ {
+ string result = _actionContext.ControllerContext.Request.Headers.GetValues(key).FirstOrDefault();
+ return result == null
+ ? null
+ : new ValueProviderResult(result, result, CultureInfo.CurrentCulture);
+ }
+ }
+} \ No newline at end of file
diff --git a/test/System.Web.Http.Integration.Test/ModelBinding/ModelBindingTests.cs b/test/System.Web.Http.Integration.Test/ModelBinding/ModelBindingTests.cs
new file mode 100644
index 00000000..86e26892
--- /dev/null
+++ b/test/System.Web.Http.Integration.Test/ModelBinding/ModelBindingTests.cs
@@ -0,0 +1,48 @@
+using System.Net.Http;
+using System.Web.Http.Common;
+using System.Web.Http.SelfHost;
+
+namespace System.Web.Http.ModelBinding
+{
+ /// <summary>
+ /// End to end functional tests for model binding
+ /// </summary>
+ public abstract class ModelBindingTests : IDisposable
+ {
+ protected HttpSelfHostServer server = null;
+ protected HttpSelfHostConfiguration configuration = null;
+ protected string baseAddress = null;
+ protected HttpClient httpClient = null;
+
+ protected ModelBindingTests()
+ {
+ this.SetupHost();
+ }
+
+ public void Dispose()
+ {
+ this.CleanupHost();
+ }
+
+ public void SetupHost()
+ {
+ httpClient = new HttpClient();
+
+ baseAddress = String.Format("http://{0}/", Environment.MachineName);
+
+ configuration = new HttpSelfHostConfiguration(baseAddress);
+ configuration.Routes.MapHttpRoute("Default", "{controller}/{action}", new { controller = "ModelBinding" });
+
+ server = new HttpSelfHostServer(configuration);
+ server.OpenAsync().Wait();
+ }
+
+ public void CleanupHost()
+ {
+ if (server != null)
+ {
+ server.CloseAsync().Wait();
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/test/System.Web.Http.Integration.Test/ModelBinding/QueryStringBindingTests.cs b/test/System.Web.Http.Integration.Test/ModelBinding/QueryStringBindingTests.cs
new file mode 100644
index 00000000..4b774c73
--- /dev/null
+++ b/test/System.Web.Http.Integration.Test/ModelBinding/QueryStringBindingTests.cs
@@ -0,0 +1,115 @@
+using System.Net.Http;
+using Xunit;
+using Xunit.Extensions;
+
+namespace System.Web.Http.ModelBinding
+{
+ /// <summary>
+ /// End to end functional tests for model binding via query strings
+ /// </summary>
+ public class QueryStringBindingTests : ModelBindingTests
+ {
+ [Theory]
+ [InlineData("GetString", "?value=test", "\"test\"")]
+ [InlineData("GetInt", "?value=99", "99")]
+ [InlineData("GetBool", "?value=false", "false")]
+ [InlineData("GetBool", "?value=true", "true")]
+ [InlineData("GetIntWithDefault", "?value=99", "99")] // action has default, but we provide value
+ [InlineData("GetIntWithDefault", "", "-1")] // action has default, we provide no value
+ [InlineData("GetIntFromUri", "?value=99", "99")] // [FromUri]
+ [InlineData("GetIntPrefixed", "?somePrefix=99", "99")] // [FromUri(Prefix=somePrefix)]
+ [InlineData("GetIntAsync", "?value=5", "5")]
+ public void Query_String_Binds_Simple_Types_Get(string action, string queryString, string expectedResponse)
+ {
+ // Arrange
+ HttpRequestMessage request = new HttpRequestMessage()
+ {
+ RequestUri = new Uri(baseAddress + String.Format("ModelBinding/{0}{1}", action, queryString)),
+ Method = HttpMethod.Get
+ };
+
+ // Act
+ HttpResponseMessage response = httpClient.SendAsync(request).Result;
+
+ // Assert
+ string responseString = response.Content.ReadAsStringAsync().Result;
+ Assert.Equal<string>(expectedResponse, responseString);
+ }
+
+ [Theory]
+ [InlineData("PostString", "?value=test", "\"test\"")]
+ [InlineData("PostInt", "?value=99", "99")]
+ [InlineData("PostBool", "?value=false", "false")]
+ [InlineData("PostBool", "?value=true", "true")]
+ [InlineData("PostIntFromUri", "?value=99", "99")] // [FromUri]
+ [InlineData("PostIntUriPrefixed", "?somePrefix=99", "99")] // [FromUri(Prefix=somePrefix)]
+ [InlineData("PostIntArray", "?value={[1,2,3]}", "0")] // TODO: DevDiv2 333257 -- make this array real when fix JsonValue array model binding
+ public void Query_String_Binds_Simple_Types_Post(string action, string queryString, string expectedResponse)
+ {
+ // Arrange
+ HttpRequestMessage request = new HttpRequestMessage()
+ {
+ RequestUri = new Uri(baseAddress + String.Format("ModelBinding/{0}{1}", action, queryString)),
+ Method = HttpMethod.Post
+ };
+
+ // Act
+ HttpResponseMessage response = httpClient.SendAsync(request).Result;
+
+ // Assert
+ string responseString = response.Content.ReadAsStringAsync().Result;
+ Assert.Equal<string>(expectedResponse, responseString);
+ }
+
+ [Theory]
+ [InlineData("GetComplexTypeFromUri", "itemName=Tires&quantity=2&customer.Name=Sue", "Tires", 2, "Sue")]
+ public void Query_String_ComplexType_Type_Get(string action, string queryString, string itemName, int quantity, string customerName)
+ {
+ // Arrange
+ HttpRequestMessage request = new HttpRequestMessage()
+ {
+ RequestUri = new Uri(baseAddress + String.Format("ModelBinding/{0}?{1}", action, queryString)),
+ Method = HttpMethod.Get
+ };
+
+ ModelBindOrder expectedItem = new ModelBindOrder()
+ {
+ ItemName = itemName,
+ Quantity = quantity,
+ Customer = new ModelBindCustomer { Name = customerName }
+ };
+
+ // Act
+ HttpResponseMessage response = httpClient.SendAsync(request).Result;
+
+ // Assert
+ ModelBindOrder actualItem = response.Content.ReadAsAsync<ModelBindOrder>().Result;
+ Assert.Equal<ModelBindOrder>(expectedItem, actualItem, new ModelBindOrderEqualityComparer());
+ }
+
+ [Theory]
+ [InlineData("PostComplexTypeFromUri", "itemName=Tires&quantity=2&customer.Name=Bob", "Tires", 2, "Bob")]
+ public void Query_String_ComplexType_Type_Post(string action, string queryString, string itemName, int quantity, string customerName)
+ {
+ // Arrange
+ HttpRequestMessage request = new HttpRequestMessage()
+ {
+ RequestUri = new Uri(baseAddress + String.Format("ModelBinding/{0}?{1}", action, queryString)),
+ Method = HttpMethod.Post
+ };
+ ModelBindOrder expectedItem = new ModelBindOrder()
+ {
+ ItemName = itemName,
+ Quantity = quantity,
+ Customer = new ModelBindCustomer { Name = customerName }
+ };
+
+ // Act
+ HttpResponseMessage response = httpClient.SendAsync(request).Result;
+
+ // Assert
+ ModelBindOrder actualItem = response.Content.ReadAsAsync<ModelBindOrder>().Result;
+ Assert.Equal<ModelBindOrder>(expectedItem, actualItem, new ModelBindOrderEqualityComparer());
+ }
+ }
+} \ No newline at end of file
diff --git a/test/System.Web.Http.Integration.Test/ModelBinding/RouteBindingTests.cs b/test/System.Web.Http.Integration.Test/ModelBinding/RouteBindingTests.cs
new file mode 100644
index 00000000..0cd7ea50
--- /dev/null
+++ b/test/System.Web.Http.Integration.Test/ModelBinding/RouteBindingTests.cs
@@ -0,0 +1,29 @@
+using System.Net.Http;
+using Xunit;
+
+namespace System.Web.Http.ModelBinding
+{
+ /// <summary>
+ /// End to end functional tests for model binding via routes
+ /// </summary>
+ public class RouteBindingTests : ModelBindingTests
+ {
+ [Fact]
+ public void Route_Binds_Simple_Types_Get()
+ {
+ // Arrange
+ HttpRequestMessage request = new HttpRequestMessage()
+ {
+ RequestUri = new Uri(baseAddress + "ModelBinding/GetStringFromRoute"),
+ Method = HttpMethod.Get
+ };
+
+ // Act
+ HttpResponseMessage response = httpClient.SendAsync(request).Result;
+
+ // Assert
+ string responseString = response.Content.ReadAsStringAsync().Result;
+ Assert.Equal<string>("\"ModelBinding:GetStringFromRoute\"", responseString);
+ }
+ }
+} \ No newline at end of file
diff --git a/test/System.Web.Http.Integration.Test/PartialTrust/BasicScenarioTest.cs b/test/System.Web.Http.Integration.Test/PartialTrust/BasicScenarioTest.cs
new file mode 100644
index 00000000..99d6d1e0
--- /dev/null
+++ b/test/System.Web.Http.Integration.Test/PartialTrust/BasicScenarioTest.cs
@@ -0,0 +1,62 @@
+using System.Net.Http;
+using System.Net.Http.Headers;
+using Xunit;
+using Xunit.Extensions;
+
+namespace System.Web.Http.PartialTrust
+{
+ public class BasicScenarioTest : MarshalByRefObject
+ {
+ [Fact]
+ public void BasicSelfHostedEchoControllerWorks()
+ {
+ ScenarioHelper.RunTest(
+ "Echo",
+ "/{s}",
+ new HttpRequestMessage(HttpMethod.Get, "http://localhost/Echo/foo"),
+ (response) =>
+ {
+ Assert.DoesNotThrow(() => response.EnsureSuccessStatusCode());
+ Assert.Equal("foo", response.Content.ReadAsStringAsync().Result);
+ }
+ );
+ }
+
+ [Theory]
+ [InlineData("application/json")]
+ [InlineData("text/xml")]
+ public void SimpleConNegWorks(string mediaType)
+ {
+ HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, "http://localhost/Echo/ContentNegotiatedEcho/foo");
+ request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue(mediaType));
+
+ ScenarioHelper.RunTest(
+ "Echo",
+ "/{action}/{s}",
+ request,
+ (response) =>
+ {
+ Assert.DoesNotThrow(() => response.EnsureSuccessStatusCode());
+ Assert.Equal(mediaType, response.Content.Headers.ContentType.MediaType);
+ }
+ );
+ }
+ }
+
+ [RunWith(typeof(PartialTrustRunner))]
+ public class PartialTrustBasicScenarioTest : BasicScenarioTest { }
+
+ public class EchoController : ApiController
+ {
+ public HttpResponseMessage Get(string s)
+ {
+ return new HttpResponseMessage() { Content = new StringContent(s) };
+ }
+
+ public string ContentNegotiatedEcho(string s)
+ {
+ return s;
+ }
+ }
+
+}
diff --git a/test/System.Web.Http.Integration.Test/PartialTrust/PartialTrustRunner.cs b/test/System.Web.Http.Integration.Test/PartialTrust/PartialTrustRunner.cs
new file mode 100644
index 00000000..dc2b8cec
--- /dev/null
+++ b/test/System.Web.Http.Integration.Test/PartialTrust/PartialTrustRunner.cs
@@ -0,0 +1,132 @@
+using System.Collections.Generic;
+using System.Data.SqlClient;
+using System.Net;
+using System.Net.Mail;
+using System.Reflection;
+using System.Security;
+using System.Security.Permissions;
+using Xunit;
+using Xunit.Sdk;
+
+namespace System.Web.Http.PartialTrust
+{
+ public class PartialTrustRunner : ITestClassCommand
+ {
+ private AppDomain sandbox;
+
+ // Delegate most of the work to the existing TestClassCommand class so that we
+ // can preserve any existing behavior (like supporting IUseFixture<T>).
+ private readonly TestClassCommand originalTestClassCommand = new TestClassCommand();
+
+ public int ChooseNextTest(ICollection<IMethodInfo> testsLeftToRun)
+ {
+ return this.originalTestClassCommand.ChooseNextTest(testsLeftToRun);
+ }
+
+ public Exception ClassFinish()
+ {
+ Exception result = this.originalTestClassCommand.ClassFinish();
+ if (this.sandbox != null)
+ {
+ AppDomain.Unload(this.sandbox);
+ this.sandbox = null;
+ }
+
+ return result;
+ }
+
+ public Exception ClassStart()
+ {
+ this.GuardTypeUnderTest();
+ Assembly xunitAssembly = typeof(FactAttribute).Assembly;
+ this.sandbox = CreatePartialTrustAppDomain();
+
+ return this.originalTestClassCommand.ClassStart();
+ }
+
+ private static AppDomain CreatePartialTrustAppDomain()
+ {
+ PermissionSet permissions = new PermissionSet(PermissionState.None);
+ permissions.AddPermission(new AspNetHostingPermission(AspNetHostingPermissionLevel.Medium));
+ permissions.AddPermission(new DnsPermission(PermissionState.Unrestricted));
+ permissions.AddPermission(new EnvironmentPermission(EnvironmentPermissionAccess.Read, "TEMP;TMP;USERNAME;OS;COMPUTERNAME"));
+ permissions.AddPermission(new FileIOPermission(FileIOPermissionAccess.AllAccess, AppDomain.CurrentDomain.BaseDirectory));
+ permissions.AddPermission(new IsolatedStorageFilePermission(PermissionState.None) { UsageAllowed = IsolatedStorageContainment.AssemblyIsolationByUser, UserQuota = Int64.MaxValue });
+ permissions.AddPermission(new SecurityPermission(SecurityPermissionFlag.Execution));
+ permissions.AddPermission(new SecurityPermission(SecurityPermissionFlag.ControlThread));
+ permissions.AddPermission(new SecurityPermission(SecurityPermissionFlag.ControlPrincipal));
+ permissions.AddPermission(new SecurityPermission(SecurityPermissionFlag.RemotingConfiguration));
+ permissions.AddPermission(new SmtpPermission(SmtpAccess.Connect));
+ permissions.AddPermission(new SqlClientPermission(PermissionState.Unrestricted));
+ permissions.AddPermission(new TypeDescriptorPermission(PermissionState.Unrestricted));
+ permissions.AddPermission(new WebPermission(PermissionState.Unrestricted));
+ permissions.AddPermission(new ReflectionPermission(ReflectionPermissionFlag.RestrictedMemberAccess));
+
+ AppDomainSetup setup = new AppDomainSetup() { ApplicationBase = AppDomain.CurrentDomain.BaseDirectory };
+
+ setup.PartialTrustVisibleAssemblies = new string[]
+ {
+ "System.Web, PublicKey=002400000480000094000000060200000024000052534131000400000100010007d1fa57c4aed9f0a32e84aa0faefd0de9e8fd6aec8f87fb03766c834c99921eb23be79ad9d5dcc1dd9ad236132102900b723cf980957fc4e177108fc607774f29e8320e92ea05ece4e821c0a5efe8f1645c4c0c93c1ab99285d622caa652c1dfad63d745d6f2de5f17e5eaf0fc4963d261c8a12436518206dc093344d5ad293",
+ "System.Web.Extensions, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b5fc90e7027f67871e773a8fde8938c81dd402ba65b9201d60593e96c492651e889cc13f1415ebb53fac1131ae0bd333c5ee6021672d9718ea31a8aebd0da0072f25d87dba6fc90ffd598ed4da35e44c398c454307e8e33b8426143daec9f596836f97c8f74750e5975c64e2189f45def46b2a2b1247adc3652bf5c308055da9",
+ "System.Web.Abstractions, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b5fc90e7027f67871e773a8fde8938c81dd402ba65b9201d60593e96c492651e889cc13f1415ebb53fac1131ae0bd333c5ee6021672d9718ea31a8aebd0da0072f25d87dba6fc90ffd598ed4da35e44c398c454307e8e33b8426143daec9f596836f97c8f74750e5975c64e2189f45def46b2a2b1247adc3652bf5c308055da9",
+ "System.Web.Routing, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b5fc90e7027f67871e773a8fde8938c81dd402ba65b9201d60593e96c492651e889cc13f1415ebb53fac1131ae0bd333c5ee6021672d9718ea31a8aebd0da0072f25d87dba6fc90ffd598ed4da35e44c398c454307e8e33b8426143daec9f596836f97c8f74750e5975c64e2189f45def46b2a2b1247adc3652bf5c308055da9",
+ "System.ComponentModel.DataAnnotations, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b5fc90e7027f67871e773a8fde8938c81dd402ba65b9201d60593e96c492651e889cc13f1415ebb53fac1131ae0bd333c5ee6021672d9718ea31a8aebd0da0072f25d87dba6fc90ffd598ed4da35e44c398c454307e8e33b8426143daec9f596836f97c8f74750e5975c64e2189f45def46b2a2b1247adc3652bf5c308055da9",
+ "System.Web.DynamicData, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b5fc90e7027f67871e773a8fde8938c81dd402ba65b9201d60593e96c492651e889cc13f1415ebb53fac1131ae0bd333c5ee6021672d9718ea31a8aebd0da0072f25d87dba6fc90ffd598ed4da35e44c398c454307e8e33b8426143daec9f596836f97c8f74750e5975c64e2189f45def46b2a2b1247adc3652bf5c308055da9",
+ "System.Web.DataVisualization, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b5fc90e7027f67871e773a8fde8938c81dd402ba65b9201d60593e96c492651e889cc13f1415ebb53fac1131ae0bd333c5ee6021672d9718ea31a8aebd0da0072f25d87dba6fc90ffd598ed4da35e44c398c454307e8e33b8426143daec9f596836f97c8f74750e5975c64e2189f45def46b2a2b1247adc3652bf5c308055da9",
+ "System.Web.ApplicationServices, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b5fc90e7027f67871e773a8fde8938c81dd402ba65b9201d60593e96c492651e889cc13f1415ebb53fac1131ae0bd333c5ee6021672d9718ea31a8aebd0da0072f25d87dba6fc90ffd598ed4da35e44c398c454307e8e33b8426143daec9f596836f97c8f74750e5975c64e2189f45def46b2a2b1247adc3652bf5c308055da9"
+ };
+
+
+ return AppDomain.CreateDomain("Partial Trust Sandbox", null, setup, permissions);
+ }
+
+ public IEnumerable<ITestCommand> EnumerateTestCommands(IMethodInfo testMethod)
+ {
+ return this.originalTestClassCommand.EnumerateTestCommands(testMethod);
+ }
+
+ public IEnumerable<IMethodInfo> EnumerateTestMethods()
+ {
+ return this.originalTestClassCommand.EnumerateTestMethods();
+ }
+
+ public bool IsTestMethod(IMethodInfo testMethod)
+ {
+ return this.originalTestClassCommand.IsTestMethod(testMethod);
+ }
+
+ public object ObjectUnderTest
+ {
+ get
+ {
+ return sandbox.CreateInstanceAndUnwrap(this.TypeUnderTest.Type.Assembly.FullName, this.TypeUnderTest.Type.FullName);
+ }
+ }
+
+ public ITypeInfo TypeUnderTest
+ {
+ get
+ {
+ return this.originalTestClassCommand.TypeUnderTest;
+ }
+ set
+ {
+ if (!typeof(MarshalByRefObject).IsAssignableFrom(value.Type))
+ {
+ throw new InvalidOperationException("Test types to be run in PT must derive from MarshalByRefObject");
+ }
+
+ this.originalTestClassCommand.TypeUnderTest = value;
+ }
+ }
+
+ private void GuardTypeUnderTest()
+ {
+ if (TypeUnderTest == null)
+ {
+ throw new InvalidOperationException("Forgot to set TypeUnderTest before calling ObjectUnderTest");
+ }
+ }
+ }
+
+} \ No newline at end of file
diff --git a/test/System.Web.Http.Integration.Test/Properties/AssemblyInfo.cs b/test/System.Web.Http.Integration.Test/Properties/AssemblyInfo.cs
new file mode 100644
index 00000000..83cc064e
--- /dev/null
+++ b/test/System.Web.Http.Integration.Test/Properties/AssemblyInfo.cs
@@ -0,0 +1,3 @@
+using System;
+
+[assembly: CLSCompliant(false)] \ No newline at end of file
diff --git a/test/System.Web.Http.Integration.Test/System.Web.Http.Integration.Test.csproj b/test/System.Web.Http.Integration.Test/System.Web.Http.Integration.Test.csproj
new file mode 100644
index 00000000..352b208a
--- /dev/null
+++ b/test/System.Web.Http.Integration.Test/System.Web.Http.Integration.Test.csproj
@@ -0,0 +1,155 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory),Runtime.sln))\tools\WebStack.settings.targets" />
+ <PropertyGroup>
+ <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+ <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+ <ProductVersion>9.0.30729</ProductVersion>
+ <SchemaVersion>2.0</SchemaVersion>
+ <ProjectGuid>{3267DFC6-B34D-4011-BC0F-D3B56AF6F608}</ProjectGuid>
+ <OutputType>Library</OutputType>
+ <AppDesignerFolder>Properties</AppDesignerFolder>
+ <RootNamespace>System.Web.Http</RootNamespace>
+ <AssemblyName>System.Web.Http.Integration.Test</AssemblyName>
+ <FileAlignment>512</FileAlignment>
+ <ProjectTypeGuids>{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
+ <ErrorReport>prompt</ErrorReport>
+ <WarningLevel>4</WarningLevel>
+ <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
+ <DebugSymbols>true</DebugSymbols>
+ <DebugType>full</DebugType>
+ <Optimize>false</Optimize>
+ <OutputPath>$(WebStackRootPath)\bin\Debug\Test\</OutputPath>
+ <DefineConstants>TRACE;DEBUG</DefineConstants>
+ <CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
+ <DebugType>pdbonly</DebugType>
+ <Optimize>true</Optimize>
+ <OutputPath>$(WebStackRootPath)\bin\Release\Test\</OutputPath>
+ <DefineConstants>TRACE</DefineConstants>
+ <CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)' == 'CodeCoverage' ">
+ <DebugSymbols>true</DebugSymbols>
+ <DebugType>full</DebugType>
+ <Optimize>false</Optimize>
+ <OutputPath>$(WebStackRootPath)\bin\CodeCoverage\Test\</OutputPath>
+ <DefineConstants>TRACE;DEBUG</DefineConstants>
+ <CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
+ </PropertyGroup>
+ <ItemGroup>
+ <Reference Include="Moq">
+ <HintPath>..\..\packages\Moq.4.0.10827\lib\NET40\Moq.dll</HintPath>
+ </Reference>
+ <Reference Include="System" />
+ <Reference Include="System.ComponentModel.DataAnnotations" />
+ <Reference Include="System.Core" />
+ <Reference Include="System.IdentityModel" />
+ <Reference Include="System.Net.Http">
+ <HintPath>..\..\packages\Microsoft.Net.Http.2.0.20302.1\lib\net40\System.Net.Http.dll</HintPath>
+ </Reference>
+ <Reference Include="System.Net.Http.WebRequest">
+ <HintPath>..\..\packages\Microsoft.Net.Http.2.0.20302.1\lib\net40\System.Net.Http.WebRequest.dll</HintPath>
+ </Reference>
+ <Reference Include="System.ServiceModel" />
+ <Reference Include="Microsoft.CSharp" />
+ <Reference Include="System.Data" />
+ <Reference Include="xunit">
+ <HintPath>..\..\packages\xunit.1.9.0.1566\lib\xunit.dll</HintPath>
+ </Reference>
+ <Reference Include="xunit.extensions">
+ <HintPath>..\..\packages\xunit.extensions.1.9.0.1566\lib\xunit.extensions.dll</HintPath>
+ </Reference>
+ </ItemGroup>
+ <ItemGroup>
+ <Compile Include="ApiExplorer\Controllers\DocumentationController.cs" />
+ <Compile Include="ApiExplorer\Controllers\ParameterSourceController.cs" />
+ <Compile Include="ApiExplorer\DocumentationProviders\AttributeDocumentationProvider.cs" />
+ <Compile Include="ApiExplorer\FormattersTest.cs" />
+ <Compile Include="ApiExplorer\Formatters\ItemFormatter.cs" />
+ <Compile Include="ApiExplorer\ParameterSourceTest.cs" />
+ <Compile Include="ApiExplorer\DocumentationTest.cs" />
+ <Compile Include="ApiExplorer\RouteConstraintsTest.cs" />
+ <Compile Include="ApiExplorer\RoutesTest.cs" />
+ <Compile Include="Util\ApiExplorerHelper.cs" />
+ <Compile Include="ApiExplorer\ApiExplorerSettingsTest.cs" />
+ <Compile Include="ApiExplorer\Controllers\HiddenController.cs" />
+ <Compile Include="ApiExplorer\Controllers\ItemController.cs" />
+ <Compile Include="ApiExplorer\Controllers\HiddenActionController.cs" />
+ <Compile Include="ApiExplorer\Controllers\OverloadsController.cs" />
+ <Compile Include="Authentication\BasicOverHttpTest.cs" />
+ <Compile Include="Authentication\CustomMessageHandler.cs" />
+ <Compile Include="Authentication\CustomUsernamePasswordValidator.cs" />
+ <Compile Include="Authentication\RequireAdminAttribute.cs" />
+ <Compile Include="Authentication\SampleController.cs" />
+ <Compile Include="ContentNegotiation\ContentNegotiationTestBase.cs" />
+ <Compile Include="ContentNegotiation\CustomFormatterTests.cs" />
+ <Compile Include="ContentNegotiation\DefaultContentNegotiatorTests.cs" />
+ <Compile Include="ContentNegotiation\HttpResponseReturnTests.cs" />
+ <Compile Include="ContentNegotiation\ConnegController.cs" />
+ <Compile Include="ContentNegotiation\ConnegItem.cs" />
+ <Compile Include="ContentNegotiation\AcceptHeaderTests.cs" />
+ <Compile Include="Controllers\ActionAttributesTest.cs" />
+ <Compile Include="Controllers\ApiControllerActionSelectorTest.cs" />
+ <Compile Include="Controllers\Apis\ActionAttributeTestController.cs" />
+ <Compile Include="Controllers\Apis\TestController.cs" />
+ <Compile Include="Controllers\Apis\User.cs" />
+ <Compile Include="Controllers\Apis\UserAddress.cs" />
+ <Compile Include="Controllers\CustomControllerFactoryTest.cs" />
+ <Compile Include="Controllers\Helpers\ApiControllerHelper.cs" />
+ <Compile Include="ExceptionHandling\DuplicateControllers.cs" />
+ <Compile Include="ExceptionHandling\ExceptionController.cs" />
+ <Compile Include="ExceptionHandling\ExceptionHandlingTest.cs" />
+ <Compile Include="ExceptionHandling\HttpResponseExceptionTest.cs" />
+ <Compile Include="ExceptionHandling\IncludeErrorDetailTest.cs" />
+ <Compile Include="Filters\IQueryableFilterPipelineTest.cs" />
+ <Compile Include="ModelBinding\BodyBindingTests.cs" />
+ <Compile Include="ModelBinding\CustomBindingTests.cs" />
+ <Compile Include="ModelBinding\DefaultActionValueBinderTest.cs" />
+ <Compile Include="ModelBinding\ModelBindingController.cs" />
+ <Compile Include="ModelBinding\ModelBindingTests.cs" />
+ <Compile Include="ModelBinding\HttpContentBindingTests.cs" />
+ <Compile Include="ModelBinding\QueryStringBindingTests.cs" />
+ <Compile Include="ModelBinding\RouteBindingTests.cs" />
+ <Compile Include="PartialTrust\BasicScenarioTest.cs" />
+ <Compile Include="PartialTrust\PartialTrustRunner.cs" />
+ <Compile Include="Properties\AssemblyInfo.cs" />
+ <Compile Include="Util\ContextUtil.cs" />
+ <Compile Include="Util\ScenarioHelper.cs" />
+ </ItemGroup>
+ <ItemGroup>
+ <ProjectReference Include="..\..\src\System.Json\System.Json.csproj">
+ <Project>{F0441BE9-BDC0-4629-BE5A-8765FFAA2481}</Project>
+ <Name>System.Json</Name>
+ </ProjectReference>
+ <ProjectReference Include="..\..\src\System.Net.Http.Formatting\System.Net.Http.Formatting.csproj">
+ <Project>{668E9021-CE84-49D9-98FB-DF125A9FCDB0}</Project>
+ <Name>System.Net.Http.Formatting</Name>
+ </ProjectReference>
+ <ProjectReference Include="..\..\src\System.Web.Http.Common\System.Web.Http.Common.csproj">
+ <Project>{03A5E5F2-2E23-48F2-ABCC-6C41BAC9AC02}</Project>
+ <Name>System.Web.Http.Common</Name>
+ </ProjectReference>
+ <ProjectReference Include="..\..\src\System.Web.Http.SelfHost\System.Web.Http.SelfHost.csproj">
+ <Project>{66492E69-CE4C-4FB1-9B1F-88DEE09D06F1}</Project>
+ <Name>System.Web.Http.SelfHost</Name>
+ </ProjectReference>
+ <ProjectReference Include="..\..\src\System.Web.Http\System.Web.Http.csproj">
+ <Project>{DDC1CE0C-486E-4E35-BB3B-EAB61F8F9440}</Project>
+ <Name>System.Web.Http</Name>
+ <Private>True</Private>
+ </ProjectReference>
+ <ProjectReference Include="..\Microsoft.TestCommon\Microsoft.TestCommon.csproj">
+ <Project>{FCCC4CB7-BAF7-4A57-9F89-E5766FE536C0}</Project>
+ <Name>Microsoft.TestCommon</Name>
+ </ProjectReference>
+ </ItemGroup>
+ <ItemGroup>
+ <None Include="packages.config" />
+ </ItemGroup>
+ <ItemGroup />
+ <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
+</Project> \ No newline at end of file
diff --git a/test/System.Web.Http.Integration.Test/Util/ApiExplorerHelper.cs b/test/System.Web.Http.Integration.Test/Util/ApiExplorerHelper.cs
new file mode 100644
index 00000000..0a8c9152
--- /dev/null
+++ b/test/System.Web.Http.Integration.Test/Util/ApiExplorerHelper.cs
@@ -0,0 +1,54 @@
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Linq;
+using System.Net.Http;
+using System.Web.Http.Controllers;
+using System.Web.Http.Description;
+using System.Web.Http.Dispatcher;
+using Moq;
+using Xunit;
+
+namespace System.Web.Http.ApiExplorer
+{
+ public static class ApiExplorerHelper
+ {
+ public static void VerifyApiDescriptions(Collection<ApiDescription> apiDescriptions, List<object> expectedResults)
+ {
+ Assert.Equal(expectedResults.Count, apiDescriptions.Count);
+ ApiDescription[] sortedDescriptions = apiDescriptions.OrderBy(description => description.ID).ToArray();
+ object[] sortedExpectedResults = expectedResults.OrderBy(r =>
+ {
+ dynamic expectedResult = r;
+ HttpMethod expectedHttpMethod = expectedResult.HttpMethod;
+ string expectedRelativePath = expectedResult.RelativePath;
+ return expectedHttpMethod + expectedRelativePath;
+ }).ToArray();
+
+ for (int i = 0; i < sortedDescriptions.Length; i++)
+ {
+ dynamic expectedResult = sortedExpectedResults[i];
+ ApiDescription matchingDescription = sortedDescriptions[i];
+ Assert.Equal(expectedResult.HttpMethod, matchingDescription.HttpMethod);
+ Assert.Equal(expectedResult.RelativePath, matchingDescription.RelativePath);
+ Assert.Equal(expectedResult.HasRequestFormatters, matchingDescription.SupportedRequestBodyFormatters.Count > 0);
+ Assert.Equal(expectedResult.HasResponseFormatters, matchingDescription.SupportedResponseFormatters.Count > 0);
+ Assert.Equal(expectedResult.NumberOfParameters, matchingDescription.ParameterDescriptions.Count);
+ }
+ }
+
+ public static DefaultHttpControllerFactory GetStrictControllerFactory(HttpConfiguration config, params Type[] controllerTypes)
+ {
+ Dictionary<string, HttpControllerDescriptor> controllerMapping = new Dictionary<string, HttpControllerDescriptor>();
+ foreach (Type controllerType in controllerTypes)
+ {
+ string controllerName = controllerType.Name.Substring(0, controllerType.Name.Length - DefaultHttpControllerFactory.ControllerSuffix.Length);
+ var controllerDescriptor = new HttpControllerDescriptor(config, controllerName, controllerType);
+ controllerMapping.Add(controllerDescriptor.ControllerName, controllerDescriptor);
+ }
+
+ Mock<DefaultHttpControllerFactory> factory = new Mock<DefaultHttpControllerFactory>(config);
+ factory.Setup(f => f.GetControllerMapping()).Returns(controllerMapping);
+ return factory.Object;
+ }
+ }
+}
diff --git a/test/System.Web.Http.Integration.Test/Util/ContextUtil.cs b/test/System.Web.Http.Integration.Test/Util/ContextUtil.cs
new file mode 100644
index 00000000..67fcddb3
--- /dev/null
+++ b/test/System.Web.Http.Integration.Test/Util/ContextUtil.cs
@@ -0,0 +1,62 @@
+using System.Net.Http;
+using System.Web.Http.Controllers;
+using System.Web.Http.Routing;
+using Moq;
+
+// TODO: move this class to TestCommon after it has been refactored
+namespace System.Web.Http
+{
+ public static class ContextUtil
+ {
+ public static HttpControllerContext CreateControllerContext()
+ {
+ return CreateControllerContext(null, null, null);
+ }
+
+ public static HttpControllerContext CreateControllerContext(HttpConfiguration configuration)
+ {
+ return CreateControllerContext(configuration, null, null);
+ }
+
+ public static HttpControllerContext CreateControllerContext(HttpRequestMessage request)
+ {
+ return CreateControllerContext(null, null, request);
+ }
+
+ public static HttpControllerContext CreateControllerContext(HttpConfiguration configuration, IHttpRouteData routeData)
+ {
+ return CreateControllerContext(configuration, routeData, null);
+ }
+
+ public static HttpControllerContext CreateControllerContext(IHttpRouteData routeData, HttpRequestMessage request)
+ {
+ return CreateControllerContext(null, routeData, request);
+ }
+
+ public static HttpControllerContext CreateControllerContext(HttpConfiguration configuration, HttpRequestMessage request)
+ {
+ return CreateControllerContext(configuration, null, request);
+ }
+
+ public static HttpControllerContext CreateControllerContext(HttpConfiguration configuration, IHttpRouteData routeData, HttpRequestMessage request)
+ {
+ HttpConfiguration config = configuration ?? new HttpConfiguration();
+ IHttpRouteData route = routeData ?? new HttpRouteData(new HttpRoute());
+ HttpRequestMessage req = request ?? new HttpRequestMessage();
+ return new HttpControllerContext(config, route, req);
+ }
+
+ public static HttpActionContext CreateActionContext(HttpControllerContext controllerContext = null, HttpActionDescriptor actionDescriptor = null)
+ {
+ HttpControllerContext context = controllerContext ?? ContextUtil.CreateControllerContext();
+ HttpActionDescriptor descriptor = actionDescriptor ?? new Mock<HttpActionDescriptor>() { CallBase = true }.Object;
+
+ if (descriptor.Configuration == null)
+ {
+ descriptor.Configuration = controllerContext.Configuration;
+ }
+
+ return new HttpActionContext(context, descriptor);
+ }
+ }
+}
diff --git a/test/System.Web.Http.Integration.Test/Util/ScenarioHelper.cs b/test/System.Web.Http.Integration.Test/Util/ScenarioHelper.cs
new file mode 100644
index 00000000..908e5d2c
--- /dev/null
+++ b/test/System.Web.Http.Integration.Test/Util/ScenarioHelper.cs
@@ -0,0 +1,41 @@
+using System.Net.Http;
+using System.Threading;
+using System.Web.Http.Common;
+
+namespace System.Web.Http
+{
+ public static class ScenarioHelper
+ {
+ public static string BaseAddress = "http://localhost";
+ public static void RunTest(string controllerName, string routeSuffix, HttpRequestMessage request,
+ Action<HttpResponseMessage> assert, Action<HttpConfiguration> configurer = null)
+ {
+ // Arrange
+ HttpConfiguration config = new HttpConfiguration();
+
+ config.Routes.MapHttpRoute("Default", "{controller}" + routeSuffix, new { controller = controllerName });
+ if (configurer != null)
+ {
+ configurer(config);
+ }
+ HttpServer server = new HttpServer(config);
+ HttpResponseMessage response = null;
+ try
+ {
+ // Act
+ response = server.SubmitRequestAsync(request, CancellationToken.None).Result;
+
+ // Assert
+ assert(response);
+ }
+ finally
+ {
+ request.Dispose();
+ if (response != null)
+ {
+ response.Dispose();
+ }
+ }
+ }
+ }
+}
diff --git a/test/System.Web.Http.Integration.Test/packages.config b/test/System.Web.Http.Integration.Test/packages.config
new file mode 100644
index 00000000..b9070f9e
--- /dev/null
+++ b/test/System.Web.Http.Integration.Test/packages.config
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<packages>
+ <package id="Microsoft.Net.Http" version="2.0.20302.1" />
+ <package id="Moq" version="4.0.10827" />
+ <package id="xunit" version="1.9.0.1566" />
+ <package id="xunit.extensions" version="1.9.0.1566" />
+</packages> \ No newline at end of file
diff --git a/test/System.Web.Http.Test/AuthorizeAttributeTest.cs b/test/System.Web.Http.Test/AuthorizeAttributeTest.cs
new file mode 100644
index 00000000..bb1b860f
--- /dev/null
+++ b/test/System.Web.Http.Test/AuthorizeAttributeTest.cs
@@ -0,0 +1,280 @@
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Linq;
+using System.Net;
+using System.Net.Http;
+using System.Security.Principal;
+using System.Web.Http.Controllers;
+using System.Web.Http.Hosting;
+using Microsoft.TestCommon;
+using Moq;
+using Xunit;
+using Xunit.Extensions;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Http
+{
+ public class AuthorizeAttributeTest
+ {
+ private readonly Mock<HttpActionDescriptor> _actionDescriptorMock = new Mock<HttpActionDescriptor>() { CallBase = true };
+ private readonly IEnumerable<AllowAnonymousAttribute> _allowAnonymousAttributeCollection = new[] { new AllowAnonymousAttribute() };
+ private readonly MockableAuthorizeAttribute _attribute;
+ private readonly Mock<MockableAuthorizeAttribute> _attributeMock = new Mock<MockableAuthorizeAttribute>() { CallBase = true };
+ private readonly Mock<HttpControllerDescriptor> _controllerDescriptorMock = new Mock<HttpControllerDescriptor>() { CallBase = true };
+ private readonly HttpControllerContext _controllerContext;
+ private readonly HttpActionContext _actionContext;
+ private readonly Mock<IPrincipal> _principalMock = new Mock<IPrincipal>();
+ private readonly HttpRequestMessage _request = new HttpRequestMessage();
+
+ public AuthorizeAttributeTest()
+ {
+ _attribute = _attributeMock.Object;
+ _controllerContext = new Mock<HttpControllerContext>() { CallBase = true }.Object;
+ _controllerDescriptorMock.Setup(cd => cd.GetCustomAttributes<AllowAnonymousAttribute>()).Returns(Enumerable.Empty<AllowAnonymousAttribute>().ToList().AsReadOnly());
+ _actionDescriptorMock.Setup(ad => ad.GetCustomAttributes<AllowAnonymousAttribute>()).Returns(Enumerable.Empty<AllowAnonymousAttribute>().ToList().AsReadOnly());
+ _controllerContext.ControllerDescriptor = _controllerDescriptorMock.Object;
+ _controllerContext.Request = _request;
+ _actionContext = ContextUtil.CreateActionContext(_controllerContext, _actionDescriptorMock.Object);
+ _request.Properties[HttpPropertyKeys.UserPrincipalKey] = _principalMock.Object;
+ }
+
+ [Fact]
+ public void Roles_Property()
+ {
+ AuthorizeAttribute attribute = new AuthorizeAttribute();
+
+ Assert.Reflection.StringProperty(attribute, a => a.Roles, expectedDefaultValue: String.Empty);
+ }
+
+ [Fact]
+ public void Users_Property()
+ {
+ AuthorizeAttribute attribute = new AuthorizeAttribute();
+
+ Assert.Reflection.StringProperty(attribute, a => a.Users, expectedDefaultValue: String.Empty);
+ }
+
+ [Fact]
+ public void AllowMultiple_ReturnsTrue()
+ {
+ Assert.True(_attribute.AllowMultiple);
+ }
+
+ [Fact]
+ public void TypeId_ReturnsUniqueInstances()
+ {
+ var attribute1 = new AuthorizeAttribute();
+ var attribute2 = new AuthorizeAttribute();
+
+ Assert.NotSame(attribute1.TypeId, attribute2.TypeId);
+ }
+
+ [Fact]
+ public void OnAuthorization_IfContextParameterIsNull_ThrowsException()
+ {
+ Assert.ThrowsArgumentNull(() =>
+ {
+ _attribute.OnAuthorization(actionContext: null);
+ }, "actionContext");
+ }
+
+ [Fact]
+ public void OnAuthorization_IfUserIsAuthenticated_DoesNotShortCircuitRequest()
+ {
+ _principalMock.Setup(p => p.Identity.IsAuthenticated).Returns(true);
+
+ _attribute.OnAuthorization(_actionContext);
+
+ Assert.Null(_actionContext.Response);
+ }
+
+ [Fact]
+ public void OnAuthorization_IfContextDoesNotContainPrincipal_DoesShortCircuitRequest()
+ {
+ _request.Properties.Remove(HttpPropertyKeys.UserPrincipalKey);
+
+ _attribute.OnAuthorization(_actionContext);
+
+ AssertUnauthorizedRequestSet(_actionContext);
+ }
+
+ [Fact]
+ public void OnAuthorization_IfUserIsNotAuthenticated_DoesShortCircuitRequest()
+ {
+ _principalMock.Setup(p => p.Identity.IsAuthenticated).Returns(false).Verifiable();
+
+ _attribute.OnAuthorization(_actionContext);
+
+ AssertUnauthorizedRequestSet(_actionContext);
+ _principalMock.Verify();
+ }
+
+ [Fact]
+ public void OnAuthorization_IfUserIsNotInUsersCollection_DoesShortCircuitRequest()
+ {
+ _attribute.Users = "John";
+ _principalMock.Setup(p => p.Identity.IsAuthenticated).Returns(true).Verifiable();
+ _principalMock.Setup(p => p.Identity.Name).Returns("Mary").Verifiable();
+
+ _attribute.OnAuthorization(_actionContext);
+
+ AssertUnauthorizedRequestSet(_actionContext);
+ _principalMock.Verify();
+ }
+
+ [Fact]
+ public void OnAuthorization_IfUserIsInUsersCollection_DoesNotShortCircuitRequest()
+ {
+ _attribute.Users = " John , Mary ";
+ _principalMock.Setup(p => p.Identity.IsAuthenticated).Returns(true).Verifiable();
+ _principalMock.Setup(p => p.Identity.Name).Returns("Mary").Verifiable();
+
+ _attribute.OnAuthorization(_actionContext);
+
+ Assert.Null(_actionContext.Response);
+ _principalMock.Verify();
+ }
+
+ [Fact]
+ public void OnAuthorization_IfUserIsNotInRolesCollection_DoesShortCircuitRequest()
+ {
+ _attribute.Users = " John , Mary ";
+ _attribute.Roles = "Administrators,PowerUsers";
+ _principalMock.Setup(p => p.Identity.IsAuthenticated).Returns(true).Verifiable();
+ _principalMock.Setup(p => p.Identity.Name).Returns("Mary").Verifiable();
+ _principalMock.Setup(p => p.IsInRole("Administrators")).Returns(false).Verifiable();
+ _principalMock.Setup(p => p.IsInRole("PowerUsers")).Returns(false).Verifiable();
+
+ _attribute.OnAuthorization(_actionContext);
+
+ AssertUnauthorizedRequestSet(_actionContext);
+ _principalMock.Verify();
+ }
+
+ [Fact]
+ public void OnAuthorization_IfUserIsInRolesCollection_DoesNotShortCircuitRequest()
+ {
+ _attribute.Users = " John , Mary ";
+ _attribute.Roles = "Administrators,PowerUsers";
+ _principalMock.Setup(p => p.Identity.IsAuthenticated).Returns(true).Verifiable();
+ _principalMock.Setup(p => p.Identity.Name).Returns("Mary").Verifiable();
+ _principalMock.Setup(p => p.IsInRole("Administrators")).Returns(false).Verifiable();
+ _principalMock.Setup(p => p.IsInRole("PowerUsers")).Returns(true).Verifiable();
+
+ _attribute.OnAuthorization(_actionContext);
+
+ Assert.Null(_actionContext.Response);
+ _principalMock.Verify();
+ }
+
+ [Fact]
+ public void OnAuthorization_IfActionDescriptorIsMarkedWithAllowAnonymousAttribute_DoesNotShortCircuitResponse()
+ {
+ _actionDescriptorMock.Setup(ad => ad.GetCustomAttributes<AllowAnonymousAttribute>()).Returns(_allowAnonymousAttributeCollection);
+ Mock<MockableAuthorizeAttribute> authorizeAttributeMock = new Mock<MockableAuthorizeAttribute>() { CallBase = true };
+ AuthorizeAttribute attribute = authorizeAttributeMock.Object;
+
+ attribute.OnAuthorization(_actionContext);
+
+ Assert.Null(_actionContext.Response);
+ }
+
+ [Fact]
+ public void OnAuthorization_IfControllerDescriptorIsMarkedWithAllowAnonymousAttribute_DoesNotShortCircuitResponse()
+ {
+ _controllerDescriptorMock.Setup(ad => ad.GetCustomAttributes<AllowAnonymousAttribute>()).Returns(_allowAnonymousAttributeCollection);
+ Mock<MockableAuthorizeAttribute> authorizeAttributeMock = new Mock<MockableAuthorizeAttribute>() { CallBase = true };
+ AuthorizeAttribute attribute = authorizeAttributeMock.Object;
+
+ attribute.OnAuthorization(_actionContext);
+
+ Assert.Null(_actionContext.Response);
+ }
+
+ [Fact]
+ public void OnAuthorization_IfRequestNotAuthorized_CallsHandleUnauthorizedRequest()
+ {
+ Mock<MockableAuthorizeAttribute> authorizeAttributeMock = new Mock<MockableAuthorizeAttribute>() { CallBase = true };
+ _principalMock.Setup(p => p.Identity.IsAuthenticated).Returns(false);
+ authorizeAttributeMock.Setup(a => a.HandleUnauthorizedRequestPublic(_actionContext)).Verifiable();
+ AuthorizeAttribute attribute = authorizeAttributeMock.Object;
+
+ attribute.OnAuthorization(_actionContext);
+
+ authorizeAttributeMock.Verify();
+ }
+
+ [Fact]
+ public void HandleUnauthorizedRequest_IfContextParameterIsNull_ThrowsArgumentNullException()
+ {
+ Assert.ThrowsArgumentNull(() =>
+ {
+ _attribute.HandleUnauthorizedRequestPublic(context: null);
+ }, "actionContext");
+ }
+
+ [Fact]
+ public void HandleUnauthorizedRequest_SetsResponseWithUnauthorizedStatusCode()
+ {
+ _attribute.HandleUnauthorizedRequestPublic(_actionContext);
+
+ Assert.NotNull(_actionContext.Response);
+ Assert.Equal(HttpStatusCode.Unauthorized, _actionContext.Response.StatusCode);
+ Assert.Same(_request, _actionContext.Response.RequestMessage);
+ }
+
+ [Theory]
+ [PropertyData("SplitStringTestData")]
+ public void SplitString_SplitsOnCommaAndTrimsWhitespaceAndIgnoresEmptyStrings(string input, params string[] expectedResult)
+ {
+ string[] result = AuthorizeAttribute.SplitString(input);
+
+ Assert.Equal(expectedResult, result);
+ }
+
+ public static IEnumerable<object[]> SplitStringTestData
+ {
+ get
+ {
+ return new ParamsTheoryDataSet<string, string>() {
+ { null },
+ { String.Empty },
+ { " " },
+ { " A ", "A" },
+ { " A, B ", "A", "B" },
+ { " , A, ,B, ", "A", "B" },
+ { " A B ", "A B" },
+ };
+ }
+ }
+
+ [CLSCompliant(false)]
+ public class ParamsTheoryDataSet<TParam1, TParam2> : TheoryDataSet
+ {
+ public void Add(TParam1 p1, params TParam2[] p2)
+ {
+ AddItem(p1, p2);
+ }
+ }
+
+ private static void AssertUnauthorizedRequestSet(HttpActionContext actionContext)
+ {
+ Assert.NotNull(actionContext.Response);
+ Assert.Equal(HttpStatusCode.Unauthorized, actionContext.Response.StatusCode);
+ Assert.Same(actionContext.ControllerContext.Request, actionContext.Response.RequestMessage);
+ }
+
+ public class MockableAuthorizeAttribute : AuthorizeAttribute
+ {
+ protected override void HandleUnauthorizedRequest(HttpActionContext context)
+ {
+ HandleUnauthorizedRequestPublic(context);
+ }
+
+ public virtual void HandleUnauthorizedRequestPublic(HttpActionContext context)
+ {
+ base.HandleUnauthorizedRequest(context);
+ }
+ }
+ }
+}
diff --git a/test/System.Web.Http.Test/Controllers/ApiControllerActionInvokerTest.cs b/test/System.Web.Http.Test/Controllers/ApiControllerActionInvokerTest.cs
new file mode 100644
index 00000000..b1492bd6
--- /dev/null
+++ b/test/System.Web.Http.Test/Controllers/ApiControllerActionInvokerTest.cs
@@ -0,0 +1,49 @@
+using System.Net.Http;
+using System.Threading;
+using System.Threading.Tasks;
+using System.Web.Http.Controllers;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Http
+{
+ public class ApiControllerActionInvokerTest
+ {
+ [Fact]
+ public void InvokeActionAsync_Calls_ActionMethod()
+ {
+ ApiControllerActionInvoker actionInvoker = new ApiControllerActionInvoker();
+ UsersController controller = new UsersController();
+ Func<HttpResponseMessage> actionMethod = controller.Get;
+ HttpActionContext context = ContextUtil.CreateActionContext(
+ ContextUtil.CreateControllerContext(instance: controller),
+ new ReflectedHttpActionDescriptor { MethodInfo = actionMethod.Method });
+
+ HttpResponseMessage response = actionInvoker.InvokeActionAsync(context, CancellationToken.None).Result;
+
+ Assert.Equal("Default User", response.Content.ReadAsStringAsync().Result);
+ }
+
+ [Fact]
+ public void InvokeActionAsync_Cancels_IfCancellationTokenRequested()
+ {
+ ApiControllerActionInvoker actionInvoker = new ApiControllerActionInvoker();
+ CancellationTokenSource cancellationSource = new CancellationTokenSource();
+ cancellationSource.Cancel();
+
+ var response = actionInvoker.InvokeActionAsync(ContextUtil.CreateActionContext(), cancellationSource.Token);
+
+ Assert.Equal<TaskStatus>(TaskStatus.Canceled, response.Status);
+ }
+
+ [Fact]
+ public void InvokeActionAsync_Throws_IfContextIsNull()
+ {
+ ApiControllerActionInvoker actionInvoker = new ApiControllerActionInvoker();
+
+ Assert.ThrowsArgumentNull(
+ () => actionInvoker.InvokeActionAsync(null, CancellationToken.None),
+ "actionContext");
+ }
+ }
+}
diff --git a/test/System.Web.Http.Test/Controllers/ApiControllerActionSelectorTest.cs b/test/System.Web.Http.Test/Controllers/ApiControllerActionSelectorTest.cs
new file mode 100644
index 00000000..ab239132
--- /dev/null
+++ b/test/System.Web.Http.Test/Controllers/ApiControllerActionSelectorTest.cs
@@ -0,0 +1,47 @@
+using System.Net.Http;
+using System.Web.Http.Controllers;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Http
+{
+ public class ApiControllerActionSelectorTest
+ {
+ [Fact]
+ public void SelectAction_With_DifferentExecutionContexts()
+ {
+ ApiControllerActionSelector actionSelector = new ApiControllerActionSelector();
+ HttpControllerContext GetContext = ContextUtil.CreateControllerContext();
+ HttpControllerDescriptor usersControllerDescriptor = new HttpControllerDescriptor(GetContext.Configuration, "Users", typeof(UsersController));
+ usersControllerDescriptor.HttpActionSelector = actionSelector;
+ GetContext.ControllerDescriptor = usersControllerDescriptor;
+ GetContext.Request = new HttpRequestMessage
+ {
+ Method = HttpMethod.Get
+ };
+ HttpControllerContext PostContext = ContextUtil.CreateControllerContext();
+ usersControllerDescriptor.HttpActionSelector = actionSelector;
+ PostContext.ControllerDescriptor = usersControllerDescriptor;
+ PostContext.Request = new HttpRequestMessage
+ {
+ Method = HttpMethod.Post
+ };
+
+ HttpActionDescriptor getActionDescriptor = actionSelector.SelectAction(GetContext);
+ HttpActionDescriptor postActionDescriptor = actionSelector.SelectAction(PostContext);
+
+ Assert.Equal("Get", getActionDescriptor.ActionName);
+ Assert.Equal("Post", postActionDescriptor.ActionName);
+ }
+
+ [Fact]
+ public void SelectAction_Throws_IfContextIsNull()
+ {
+ ApiControllerActionSelector actionSelector = new ApiControllerActionSelector();
+
+ Assert.ThrowsArgumentNull(
+ () => actionSelector.SelectAction(null),
+ "controllerContext");
+ }
+ }
+}
diff --git a/test/System.Web.Http.Test/Controllers/ApiControllerTest.cs b/test/System.Web.Http.Test/Controllers/ApiControllerTest.cs
new file mode 100644
index 00000000..e995c4c7
--- /dev/null
+++ b/test/System.Web.Http.Test/Controllers/ApiControllerTest.cs
@@ -0,0 +1,702 @@
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Globalization;
+using System.Linq;
+using System.Net.Http;
+using System.Net.Http.Formatting;
+using System.Net.Http.Headers;
+using System.Threading;
+using System.Threading.Tasks;
+using System.Web.Http.Controllers;
+using System.Web.Http.Filters;
+using System.Web.Http.ModelBinding;
+using System.Web.Http.Properties;
+using System.Web.Http.Routing;
+using System.Web.Http.Services;
+using Moq;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Http
+{
+ public class ApiControllerTest
+ {
+ private readonly HttpActionContext _actionContextInstance = ContextUtil.CreateActionContext();
+ private readonly HttpConfiguration _configurationInstance = new HttpConfiguration();
+ private readonly HttpActionDescriptor _actionDescriptorInstance = new Mock<HttpActionDescriptor>() { CallBase = true }.Object;
+
+ [Fact]
+ public void Setting_CustomActionInvoker()
+ {
+ // Arrange
+ ApiController api = new UsersController();
+ string responseText = "Hello World";
+ HttpControllerContext controllerContext = ContextUtil.CreateControllerContext();
+
+ HttpControllerDescriptor controllerDescriptor = new HttpControllerDescriptor(controllerContext.Configuration, "test", typeof(UsersController));
+ controllerContext.ControllerDescriptor = controllerDescriptor;
+
+ Mock<IHttpActionInvoker> mockInvoker = new Mock<IHttpActionInvoker>();
+ mockInvoker
+ .Setup(invoker => invoker.InvokeActionAsync(It.IsAny<HttpActionContext>(), It.IsAny<CancellationToken>()))
+ .Returns(() =>
+ {
+ TaskCompletionSource<HttpResponseMessage> tcs = new TaskCompletionSource<HttpResponseMessage>();
+ tcs.TrySetResult(new HttpResponseMessage() { Content = new StringContent(responseText) });
+ return tcs.Task;
+ });
+ controllerDescriptor.HttpActionInvoker = mockInvoker.Object;
+
+ // Act
+ HttpResponseMessage message = api.ExecuteAsync(
+ controllerContext,
+ CancellationToken.None).Result;
+
+ // Assert
+ Assert.Equal(responseText, message.Content.ReadAsStringAsync().Result);
+ }
+
+ [Fact]
+ public void Setting_CustomActionSelector()
+ {
+ // Arrange
+ ApiController api = new UsersController();
+ HttpControllerContext controllerContext = ContextUtil.CreateControllerContext();
+
+ HttpControllerDescriptor controllerDescriptor = new HttpControllerDescriptor(controllerContext.Configuration, "test", typeof(UsersController));
+ controllerContext.ControllerDescriptor = controllerDescriptor;
+
+ Mock<IHttpActionSelector> mockSelector = new Mock<IHttpActionSelector>();
+ mockSelector
+ .Setup(invoker => invoker.SelectAction(It.IsAny<HttpControllerContext>()))
+ .Returns(() =>
+ {
+ Func<HttpResponseMessage> testDelegate =
+ () => new HttpResponseMessage { Content = new StringContent("This is a test") };
+ return new ReflectedHttpActionDescriptor
+ {
+ Configuration = controllerContext.Configuration,
+ ControllerDescriptor = controllerDescriptor,
+ MethodInfo = testDelegate.Method
+ };
+ });
+ controllerDescriptor.HttpActionSelector = mockSelector.Object;
+
+ // Act
+ HttpResponseMessage message = api.ExecuteAsync(
+ controllerContext,
+ CancellationToken.None).Result;
+
+ // Assert
+ Assert.Equal("This is a test", message.Content.ReadAsStringAsync().Result);
+ }
+
+ [Fact]
+ public void Default_Get()
+ {
+ // Arrange
+ ApiController api = new UsersController();
+ HttpControllerContext controllerContext = ContextUtil.CreateControllerContext(instance: api, request: new HttpRequestMessage() { Method = HttpMethod.Get });
+ controllerContext.ControllerDescriptor = new HttpControllerDescriptor(controllerContext.Configuration, "test", typeof(UsersController));
+
+ // Act
+ HttpResponseMessage message = api.ExecuteAsync(
+ controllerContext,
+ CancellationToken.None).Result;
+
+ // Assert
+ Assert.Equal("Default User", message.Content.ReadAsStringAsync().Result);
+ }
+
+ [Fact]
+ public void Default_Post()
+ {
+ // Arrange
+ ApiController api = new UsersController();
+ HttpControllerContext controllerContext = ContextUtil.CreateControllerContext(instance: api, request: new HttpRequestMessage() { Method = HttpMethod.Post });
+ controllerContext.ControllerDescriptor = new HttpControllerDescriptor(controllerContext.Configuration, "test", typeof(UsersController));
+
+ // Act
+ HttpResponseMessage message = api.ExecuteAsync(
+ controllerContext,
+ CancellationToken.None).Result;
+
+ // Assert
+ Assert.Equal("User Posted", message.Content.ReadAsStringAsync().Result);
+ }
+
+ [Fact]
+ public void Default_Put()
+ {
+ // Arrange
+ ApiController api = new UsersController();
+ HttpControllerContext controllerContext = ContextUtil.CreateControllerContext(instance: api, request: new HttpRequestMessage() { Method = HttpMethod.Put });
+ controllerContext.ControllerDescriptor = new HttpControllerDescriptor(controllerContext.Configuration, "test", typeof(UsersController));
+
+ // Act
+ HttpResponseMessage message = api.ExecuteAsync(
+ controllerContext,
+ CancellationToken.None).Result;
+
+ // Assert
+ Assert.Equal("User Updated", message.Content.ReadAsStringAsync().Result);
+ }
+
+ [Fact]
+ public void Default_Delete()
+ {
+ // Arrange
+ ApiController api = new UsersController();
+ HttpControllerContext controllerContext = ContextUtil.CreateControllerContext(instance: api, request: new HttpRequestMessage() { Method = HttpMethod.Delete });
+ controllerContext.ControllerDescriptor = new HttpControllerDescriptor(controllerContext.Configuration, "test", typeof(UsersController));
+
+ // Act
+ HttpResponseMessage message = api.ExecuteAsync(
+ controllerContext,
+ CancellationToken.None).Result;
+
+ // Assert
+ Assert.Equal("User Deleted", message.Content.ReadAsStringAsync().Result);
+ }
+
+ [Fact]
+ public void Route_ActionName()
+ {
+ // Arrange
+ ApiController api = new UsersRpcController();
+ HttpRouteData route = new HttpRouteData(new HttpRoute());
+ route.Values.Add("action", "Admin");
+ HttpControllerContext controllerContext = ContextUtil.CreateControllerContext(instance: api, routeData: route, request: new HttpRequestMessage() { Method = HttpMethod.Get });
+ controllerContext.ControllerDescriptor = new HttpControllerDescriptor(controllerContext.Configuration, "test", typeof(UsersRpcController));
+
+ // Act
+ HttpResponseMessage message = api.ExecuteAsync(controllerContext, CancellationToken.None).Result;
+ User user = message.Content.ReadAsAsync<User>().Result;
+
+ // Assert
+ Assert.Equal("Yao", user.FirstName);
+ Assert.Equal("Huang", user.LastName);
+ }
+
+ [Fact]
+ public void Route_Get_Action_With_Route_Parameters()
+ {
+ // Arrange
+ ApiController api = new UsersRpcController();
+ HttpRouteData route = new HttpRouteData(new HttpRoute());
+ route.Values.Add("action", "EchoUser");
+ route.Values.Add("firstName", "RouteFirstName");
+ route.Values.Add("lastName", "RouteLastName");
+ HttpControllerContext controllerContext = ContextUtil.CreateControllerContext(instance: api, routeData: route, request: new HttpRequestMessage() { Method = HttpMethod.Get });
+ controllerContext.ControllerDescriptor = new HttpControllerDescriptor(controllerContext.Configuration, "test", typeof(UsersRpcController));
+
+ // Act
+ HttpResponseMessage message = api.ExecuteAsync(controllerContext, CancellationToken.None).Result;
+ User user = message.Content.ReadAsAsync<User>().Result;
+
+ // Assert
+ Assert.Equal("RouteFirstName", user.FirstName);
+ Assert.Equal("RouteLastName", user.LastName);
+ }
+
+ [Fact]
+ public void Route_Get_Action_With_Query_Parameters()
+ {
+ // Arrange
+ ApiController api = new UsersRpcController();
+ HttpRouteData route = new HttpRouteData(new HttpRoute());
+ route.Values.Add("action", "EchoUser");
+
+ Uri requestUri = new Uri("http://localhost/?firstName=QueryFirstName&lastName=QueryLastName");
+ HttpControllerContext controllerContext = ContextUtil.CreateControllerContext(instance: api, routeData: route, request: new HttpRequestMessage()
+ {
+ Method = HttpMethod.Get,
+ RequestUri = requestUri
+ });
+ controllerContext.ControllerDescriptor = new HttpControllerDescriptor(controllerContext.Configuration, "test", typeof(UsersRpcController));
+
+ // Act
+ HttpResponseMessage message = api.ExecuteAsync(controllerContext, CancellationToken.None).Result;
+ User user = message.Content.ReadAsAsync<User>().Result;
+
+ // Assert
+ Assert.Equal("QueryFirstName", user.FirstName);
+ Assert.Equal("QueryLastName", user.LastName);
+ }
+
+ [Fact]
+ public void Route_Post_Action_With_Content_Parameter()
+ {
+ // Arrange
+ ApiController api = new UsersRpcController();
+ HttpRouteData route = new HttpRouteData(new HttpRoute());
+ route.Values.Add("action", "EchoUserObject");
+ User postedUser = new User()
+ {
+ FirstName = "SampleFirstName",
+ LastName = "SampleLastName"
+ };
+
+ HttpRequestMessage request = new HttpRequestMessage() { Method = HttpMethod.Post };
+
+ // Create a serialized request because this test directly calls the controller
+ // which would have normally been working with a serialized request content.
+ string serializedUserAsString = null;
+ using (HttpRequestMessage tempRequest = new HttpRequestMessage() { Content = new ObjectContent<User>(postedUser, new XmlMediaTypeFormatter()) })
+ {
+ serializedUserAsString = tempRequest.Content.ReadAsStringAsync().Result;
+ }
+
+ StringContent stringContent = new StringContent(serializedUserAsString);
+ stringContent.Headers.ContentType = new MediaTypeHeaderValue("application/xml");
+ request.Content = stringContent;
+
+ HttpControllerContext controllerContext = ContextUtil.CreateControllerContext(instance: api, routeData: route, request: request);
+ controllerContext.ControllerDescriptor = new HttpControllerDescriptor(controllerContext.Configuration, "test", typeof(UsersRpcController));
+
+ // Act
+ HttpResponseMessage message = api.ExecuteAsync(
+ controllerContext,
+ CancellationToken.None).Result;
+ User user = message.Content.ReadAsAsync<User>().Result;
+
+ // Assert
+ Assert.Equal(postedUser.FirstName, user.FirstName);
+ Assert.Equal(postedUser.LastName, user.LastName);
+ }
+
+ [Fact]
+ public void Invalid_Action_In_Route()
+ {
+ // Arrange
+ ApiController api = new UsersController();
+ HttpRouteData route = new HttpRouteData(new HttpRoute());
+ string actionName = "invalidOp";
+ route.Values.Add("action", actionName);
+ HttpControllerContext controllerContext = ContextUtil.CreateControllerContext(instance: api, routeData: route, request: new HttpRequestMessage() { Method = HttpMethod.Get });
+ Type controllerType = typeof(UsersController);
+ controllerContext.ControllerDescriptor = new HttpControllerDescriptor(controllerContext.Configuration, controllerType.Name, controllerType);
+
+ // Act & Assert
+ Assert.Throws<HttpResponseException>(() =>
+ {
+ HttpResponseMessage message = api.ExecuteAsync(controllerContext, CancellationToken.None).Result;
+ },
+ String.Format(SRResources.ApiControllerActionSelector_ActionNameNotFound, controllerType.Name, actionName));
+ }
+
+ [Fact]
+ public void ExecuteAsync_InvokesAuthorizationFilters_ThenInvokesModelBinding_ThenInvokesActionFilters_ThenInvokesAction()
+ {
+ List<string> log = new List<string>();
+ Mock<ApiController> controllerMock = new Mock<ApiController>() { CallBase = true };
+ var controllerContextMock = new Mock<HttpControllerContext>();
+
+ Mock<IActionValueBinder> binderMock = new Mock<IActionValueBinder>();
+ Mock<HttpActionBinding> actionBindingMock = new Mock<HttpActionBinding>();
+ actionBindingMock.Setup(b => b.ExecuteBindingAsync(It.IsAny<HttpActionContext>(), It.IsAny<CancellationToken>())).Returns(() => Task.Factory.StartNew(() => { log.Add("model binding"); }));
+ binderMock.Setup(b => b.GetBinding(It.IsAny<HttpActionDescriptor>())).Returns(actionBindingMock.Object);
+ HttpConfiguration configuration = new HttpConfiguration();
+
+ HttpControllerContext controllerContext = controllerContextMock.Object;
+ controllerContext.Configuration = configuration;
+ controllerContext.ControllerDescriptor = new HttpControllerDescriptor(configuration, "test", typeof(object));
+ var actionFilterMock = CreateActionFilterMock((ac, ct, cont) =>
+ {
+ log.Add("action filters");
+ return cont();
+ });
+ var authFilterMock = CreateAuthorizationFilterMock((ac, ct, cont) =>
+ {
+ log.Add("auth filters");
+ return cont();
+ });
+ var selectorMock = new Mock<IHttpActionSelector>();
+ selectorMock.Setup(s => s.SelectAction(controllerContext).GetFilterPipeline())
+ .Returns(new Collection<FilterInfo>(new List<FilterInfo>() { new FilterInfo(actionFilterMock.Object, FilterScope.Action), new FilterInfo(authFilterMock.Object, FilterScope.Action) }));
+ ApiController controller = controllerMock.Object;
+ var invokerMock = new Mock<IHttpActionInvoker>();
+ invokerMock.Setup(i => i.InvokeActionAsync(It.IsAny<HttpActionContext>(), It.IsAny<CancellationToken>()))
+ .Returns(() => Task.Factory.StartNew(() =>
+ {
+ log.Add("action");
+ return new HttpResponseMessage();
+ }));
+ controllerContext.ControllerDescriptor.HttpActionInvoker = invokerMock.Object;
+ controllerContext.ControllerDescriptor.HttpActionSelector = selectorMock.Object;
+ controllerContext.ControllerDescriptor.ActionValueBinder = binderMock.Object;
+
+ var task = controller.ExecuteAsync(controllerContext, CancellationToken.None);
+
+ Assert.NotNull(task);
+ task.WaitUntilCompleted();
+ Assert.Equal(new string[] { "auth filters", "model binding", "action filters", "action" }, log.ToArray());
+ }
+
+ [Fact]
+ public void GetFilters_QueriesFilterProvidersFromServiceResolver()
+ {
+ // Arrange
+ Mock<IDependencyResolver> resolverMock = new Mock<IDependencyResolver>();
+ Mock<IFilterProvider> filterProviderMock = new Mock<IFilterProvider>();
+ resolverMock.Setup(r => r.GetServices(typeof(IFilterProvider))).Returns(new object[] { filterProviderMock.Object }).Verifiable();
+ _configurationInstance.ServiceResolver.SetResolver(resolverMock.Object);
+
+ HttpActionDescriptor actionDescriptorMock = new Mock<HttpActionDescriptor>() { CallBase = true }.Object;
+ actionDescriptorMock.Configuration = _configurationInstance;
+
+ // Act
+ actionDescriptorMock.GetFilterPipeline();
+
+ // Assert
+ resolverMock.Verify();
+ }
+
+ [Fact]
+ public void GetFilters_UsesFilterProvidersToGetFilters()
+ {
+ // Arrange
+ Mock<IDependencyResolver> resolverMock = new Mock<IDependencyResolver>();
+ Mock<IFilterProvider> filterProviderMock = new Mock<IFilterProvider>();
+ resolverMock.Setup(r => r.GetServices(typeof(IFilterProvider))).Returns(new[] { filterProviderMock.Object });
+ _configurationInstance.ServiceResolver.SetResolver(resolverMock.Object);
+
+ HttpActionDescriptor actionDescriptorMock = new Mock<HttpActionDescriptor>() { CallBase = true }.Object;
+ actionDescriptorMock.Configuration = _configurationInstance;
+
+ // Act
+ actionDescriptorMock.GetFilterPipeline().ToList();
+
+ // Assert
+ filterProviderMock.Verify(fp => fp.GetFilters(_configurationInstance, actionDescriptorMock));
+ }
+
+ [Fact]
+ public void RequestPropertyGetterSetterWorks()
+ {
+ Assert.Reflection.Property(new Mock<ApiController>().Object,
+ c => c.Request, expectedDefaultValue: null, allowNull: false,
+ roundTripTestValue: new HttpRequestMessage());
+ }
+
+ [Fact]
+ public void ConfigurationPropertyGetterSetterWorks()
+ {
+ Assert.Reflection.Property(new Mock<ApiController>().Object,
+ c => c.Configuration, expectedDefaultValue: null, allowNull: false,
+ roundTripTestValue: new HttpConfiguration());
+ }
+
+ [Fact]
+ public void ModelStatePropertyGetterWorks()
+ {
+ // Arrange
+ ApiController controller = new Mock<ApiController>().Object;
+
+ // Act
+ ModelStateDictionary expected = new ModelStateDictionary();
+ expected.Add("a", new ModelState() { Value = new ValueProviders.ValueProviderResult("result", "attempted", CultureInfo.InvariantCulture) });
+
+ controller.ModelState.Add("a", new ModelState() { Value = new ValueProviders.ValueProviderResult("result", "attempted", CultureInfo.InvariantCulture) });
+
+ // Assert
+ Assert.Equal(expected.Count, controller.ModelState.Count);
+ }
+
+ // TODO: Move these tests to ActionDescriptorTest
+ [Fact]
+ public void GetFilters_OrdersFilters()
+ {
+ // Arrange
+ HttpActionDescriptor actionDescriptorMock = new Mock<HttpActionDescriptor>() { CallBase = true }.Object;
+ actionDescriptorMock.Configuration = _configurationInstance;
+
+ var globalFilter = new FilterInfo(new TestMultiFilter(), FilterScope.Global);
+ var actionFilter = new FilterInfo(new TestMultiFilter(), FilterScope.Action);
+ var controllerFilter = new FilterInfo(new TestMultiFilter(), FilterScope.Controller);
+ Mock<IDependencyResolver> resolverMock = BuildFilterProvidingDependencyResolver(_configurationInstance, actionDescriptorMock, globalFilter, actionFilter, controllerFilter);
+ _configurationInstance.ServiceResolver.SetResolver(resolverMock.Object);
+
+ // Act
+ var result = actionDescriptorMock.GetFilterPipeline().ToArray();
+
+ // Assert
+ Assert.Equal(new[] { globalFilter, controllerFilter, actionFilter }, result);
+ }
+
+ [Fact]
+ public void GetFilters_RemovesDuplicateUniqueFiltersKeepingMostSpecificScope()
+ {
+ // Arrange
+ HttpActionDescriptor actionDescriptorMock = new Mock<HttpActionDescriptor>() { CallBase = true }.Object;
+ actionDescriptorMock.Configuration = _configurationInstance;
+
+ var multiActionFilter = new FilterInfo(new TestMultiFilter(), FilterScope.Action);
+ var multiGlobalFilter = new FilterInfo(new TestMultiFilter(), FilterScope.Global);
+ var uniqueControllerFilter = new FilterInfo(new TestUniqueFilter(), FilterScope.Controller);
+ var uniqueActionFilter = new FilterInfo(new TestUniqueFilter(), FilterScope.Action);
+ Mock<IDependencyResolver> resolverMock = BuildFilterProvidingDependencyResolver(
+ _configurationInstance, actionDescriptorMock,
+ multiActionFilter, multiGlobalFilter, uniqueControllerFilter, uniqueActionFilter);
+ _configurationInstance.ServiceResolver.SetResolver(resolverMock.Object);
+
+ // Act
+ var result = actionDescriptorMock.GetFilterPipeline().ToArray();
+
+ // Assert
+ Assert.Equal(new[] { multiGlobalFilter, multiActionFilter, uniqueActionFilter }, result);
+ }
+
+ [Fact]
+ public void InvokeActionWithActionFilters_ChainsFiltersInOrderFollowedByInnerActionContinuation()
+ {
+ // Arrange
+ List<string> log = new List<string>();
+ Mock<IActionFilter> globalFilterMock = CreateActionFilterMock((ctx, ct, continuation) =>
+ {
+ log.Add("globalFilter");
+ return continuation();
+ });
+ Mock<IActionFilter> actionFilterMock = CreateActionFilterMock((ctx, ct, continuation) =>
+ {
+ log.Add("actionFilter");
+ return continuation();
+ });
+ Func<Task<HttpResponseMessage>> innerAction = () => Task<HttpResponseMessage>.Factory.StartNew(() =>
+ {
+ log.Add("innerAction");
+ return null;
+ });
+ List<IActionFilter> filters = new List<IActionFilter>() {
+ globalFilterMock.Object,
+ actionFilterMock.Object,
+ };
+
+ // Act
+ var result = ApiController.InvokeActionWithActionFilters(_actionContextInstance, CancellationToken.None, filters, innerAction);
+
+ // Assert
+ Assert.NotNull(result);
+ var resultTask = result();
+ Assert.NotNull(resultTask);
+ resultTask.WaitUntilCompleted();
+ Assert.Equal(new[] { "globalFilter", "actionFilter", "innerAction" }, log.ToArray());
+ globalFilterMock.Verify();
+ actionFilterMock.Verify();
+ }
+
+ [Fact]
+ public void InvokeActionWithAuthorizationFilters_ChainsFiltersInOrderFollowedByInnerActionContinuation()
+ {
+ // Arrange
+ List<string> log = new List<string>();
+ Mock<IAuthorizationFilter> globalFilterMock = CreateAuthorizationFilterMock((ctx, ct, continuation) =>
+ {
+ log.Add("globalFilter");
+ return continuation();
+ });
+ Mock<IAuthorizationFilter> actionFilterMock = CreateAuthorizationFilterMock((ctx, ct, continuation) =>
+ {
+ log.Add("actionFilter");
+ return continuation();
+ });
+ Func<Task<HttpResponseMessage>> innerAction = () => Task<HttpResponseMessage>.Factory.StartNew(() =>
+ {
+ log.Add("innerAction");
+ return null;
+ });
+ List<IAuthorizationFilter> filters = new List<IAuthorizationFilter>() {
+ globalFilterMock.Object,
+ actionFilterMock.Object,
+ };
+
+ // Act
+ var result = ApiController.InvokeActionWithAuthorizationFilters(_actionContextInstance, CancellationToken.None, filters, innerAction);
+
+ // Assert
+ Assert.NotNull(result);
+ var resultTask = result();
+ Assert.NotNull(resultTask);
+ resultTask.WaitUntilCompleted();
+ Assert.Equal(new[] { "globalFilter", "actionFilter", "innerAction" }, log.ToArray());
+ globalFilterMock.Verify();
+ actionFilterMock.Verify();
+ }
+
+ [Fact]
+ public void InvokeActionWithExceptionFilters_IfActionTaskIsSuccessful_ReturnsSuccessTask()
+ {
+ // Arrange
+ List<string> log = new List<string>();
+ var response = new HttpResponseMessage();
+ var actionTask = TaskHelpers.FromResult(response);
+ var exceptionFilterMock = CreateExceptionFilterMock((ec, ct) =>
+ {
+ log.Add("exceptionFilter");
+ return Task.Factory.StartNew(() => { });
+ });
+ var filters = new[] { exceptionFilterMock.Object };
+
+ // Act
+ var result = ApiController.InvokeActionWithExceptionFilters(actionTask, _actionContextInstance, CancellationToken.None, filters);
+
+ // Assert
+ Assert.NotNull(result);
+ result.WaitUntilCompleted();
+ Assert.Equal(TaskStatus.RanToCompletion, result.Status);
+ Assert.Same(response, result.Result);
+ Assert.Equal(new string[] { }, log.ToArray());
+ }
+
+ [Fact]
+ public void InvokeActionWithExceptionFilters_IfActionTaskIsCanceled_ReturnsCanceledTask()
+ {
+ // Arrange
+ List<string> log = new List<string>();
+ var actionTask = TaskHelpers.Canceled<HttpResponseMessage>();
+ var exceptionFilterMock = CreateExceptionFilterMock((ec, ct) =>
+ {
+ log.Add("exceptionFilter");
+ return Task.Factory.StartNew(() => { });
+ });
+ var filters = new[] { exceptionFilterMock.Object };
+
+ // Act
+ var result = ApiController.InvokeActionWithExceptionFilters(actionTask, _actionContextInstance, CancellationToken.None, filters);
+
+ // Assert
+ Assert.NotNull(result);
+ result.WaitUntilCompleted();
+ Assert.Equal(TaskStatus.Canceled, result.Status);
+ Assert.Equal(new string[] { }, log.ToArray());
+ }
+
+ [Fact]
+ public void InvokeActionWithExceptionFilters_IfActionTaskIsFaulted_ExecutesFiltersAndReturnsFaultedTaskIfNotHandled()
+ {
+ // Arrange
+ List<string> log = new List<string>();
+ var exception = new Exception();
+ var actionTask = TaskHelpers.FromError<HttpResponseMessage>(exception);
+ Exception exceptionSeenByFilter = null;
+ var exceptionFilterMock = CreateExceptionFilterMock((ec, ct) =>
+ {
+ exceptionSeenByFilter = ec.Exception;
+ log.Add("exceptionFilter");
+ return Task.Factory.StartNew(() => { });
+ });
+ var filters = new[] { exceptionFilterMock.Object };
+
+ // Act
+ var result = ApiController.InvokeActionWithExceptionFilters(actionTask, _actionContextInstance, CancellationToken.None, filters);
+
+ // Assert
+ Assert.NotNull(result);
+ result.WaitUntilCompleted();
+ Assert.Equal(TaskStatus.Faulted, result.Status);
+ Assert.Same(exception, result.Exception.InnerException);
+ Assert.Same(exception, exceptionSeenByFilter);
+ Assert.Equal(new string[] { "exceptionFilter" }, log.ToArray());
+ }
+
+ [Fact]
+ public void InvokeActionWithExceptionFilters_IfActionTaskIsFaulted_ExecutesFiltersAndReturnsResultIfHandled()
+ {
+ // Arrange
+ List<string> log = new List<string>();
+ var exception = new Exception();
+ var actionTask = TaskHelpers.FromError<HttpResponseMessage>(exception);
+ HttpResponseMessage globalFilterResponse = new HttpResponseMessage();
+ HttpResponseMessage actionFilterResponse = new HttpResponseMessage();
+ HttpResponseMessage resultSeenByGlobalFilter = null;
+ var globalFilterMock = CreateExceptionFilterMock((ec, ct) =>
+ {
+ log.Add("globalFilter");
+ resultSeenByGlobalFilter = ec.Result;
+ ec.Result = globalFilterResponse;
+ return Task.Factory.StartNew(() => { });
+ });
+ var actionFilterMock = CreateExceptionFilterMock((ec, ct) =>
+ {
+ log.Add("actionFilter");
+ ec.Result = actionFilterResponse;
+ return Task.Factory.StartNew(() => { });
+ });
+ var filters = new[] { globalFilterMock.Object, actionFilterMock.Object };
+
+ // Act
+ var result = ApiController.InvokeActionWithExceptionFilters(actionTask, _actionContextInstance, CancellationToken.None, filters);
+
+ // Assert
+ Assert.NotNull(result);
+ result.WaitUntilCompleted();
+ Assert.Equal(TaskStatus.RanToCompletion, result.Status);
+ Assert.Same(globalFilterResponse, result.Result);
+ Assert.Same(actionFilterResponse, resultSeenByGlobalFilter);
+ Assert.Equal(new string[] { "actionFilter", "globalFilter" }, log.ToArray());
+ }
+
+ private Mock<IAuthorizationFilter> CreateAuthorizationFilterMock(Func<HttpActionContext, CancellationToken, Func<Task<HttpResponseMessage>>, Task<HttpResponseMessage>> implementation)
+ {
+ Mock<IAuthorizationFilter> filterMock = new Mock<IAuthorizationFilter>();
+ filterMock.Setup(f => f.ExecuteAuthorizationFilterAsync(It.IsAny<HttpActionContext>(),
+ CancellationToken.None,
+ It.IsAny<Func<Task<HttpResponseMessage>>>()))
+ .Returns(implementation)
+ .Verifiable();
+ return filterMock;
+ }
+
+ private Mock<IActionFilter> CreateActionFilterMock(Func<HttpActionContext, CancellationToken, Func<Task<HttpResponseMessage>>, Task<HttpResponseMessage>> implementation)
+ {
+ Mock<IActionFilter> filterMock = new Mock<IActionFilter>();
+ filterMock.Setup(f => f.ExecuteActionFilterAsync(It.IsAny<HttpActionContext>(),
+ CancellationToken.None,
+ It.IsAny<Func<Task<HttpResponseMessage>>>()))
+ .Returns(implementation)
+ .Verifiable();
+ return filterMock;
+ }
+
+ private Mock<IExceptionFilter> CreateExceptionFilterMock(Func<HttpActionExecutedContext, CancellationToken, Task> implementation)
+ {
+ Mock<IExceptionFilter> filterMock = new Mock<IExceptionFilter>();
+ filterMock.Setup(f => f.ExecuteExceptionFilterAsync(It.IsAny<HttpActionExecutedContext>(),
+ CancellationToken.None))
+ .Returns(implementation)
+ .Verifiable();
+ return filterMock;
+ }
+
+ private static Mock<IDependencyResolver> BuildFilterProvidingDependencyResolver(HttpConfiguration configuration, HttpActionDescriptor action, params FilterInfo[] filters)
+ {
+ Mock<IDependencyResolver> resolverMock = new Mock<IDependencyResolver>();
+ Mock<IFilterProvider> filterProviderMock = new Mock<IFilterProvider>();
+ resolverMock.Setup(r => r.GetServices(typeof(IFilterProvider))).Returns(new[] { filterProviderMock.Object });
+ filterProviderMock.Setup(fp => fp.GetFilters(configuration, action)).Returns(filters);
+ return resolverMock;
+ }
+
+ /// <summary>
+ /// Simple IFilter implementation with AllowMultiple = true
+ /// </summary>
+ public class TestMultiFilter : IFilter
+ {
+ public bool AllowMultiple
+ {
+ get { return true; }
+ }
+ }
+
+ /// <summary>
+ /// Simple IFilter implementation with AllowMultiple = false
+ /// </summary>
+ public class TestUniqueFilter : IFilter
+ {
+ public bool AllowMultiple
+ {
+ get { return false; }
+ }
+ }
+ }
+}
diff --git a/test/System.Web.Http.Test/Controllers/Apis/User.cs b/test/System.Web.Http.Test/Controllers/Apis/User.cs
new file mode 100644
index 00000000..09fe81ff
--- /dev/null
+++ b/test/System.Web.Http.Test/Controllers/Apis/User.cs
@@ -0,0 +1,9 @@
+
+namespace System.Web.Http
+{
+ public class User
+ {
+ public string FirstName { get; set; }
+ public string LastName { get; set; }
+ }
+}
diff --git a/test/System.Web.Http.Test/Controllers/Apis/UsersController.cs b/test/System.Web.Http.Test/Controllers/Apis/UsersController.cs
new file mode 100644
index 00000000..b56ba60e
--- /dev/null
+++ b/test/System.Web.Http.Test/Controllers/Apis/UsersController.cs
@@ -0,0 +1,40 @@
+using System.Net;
+using System.Net.Http;
+
+namespace System.Web.Http
+{
+ public class UsersController : ApiController
+ {
+ public HttpResponseMessage Get()
+ {
+ return new HttpResponseMessage(HttpStatusCode.OK)
+ {
+ Content = new StringContent("Default User")
+ };
+ }
+
+ public HttpResponseMessage Post()
+ {
+ return new HttpResponseMessage(HttpStatusCode.OK)
+ {
+ Content = new StringContent("User Posted")
+ };
+ }
+
+ public HttpResponseMessage Put()
+ {
+ return new HttpResponseMessage(HttpStatusCode.OK)
+ {
+ Content = new StringContent("User Updated")
+ };
+ }
+
+ public HttpResponseMessage Delete()
+ {
+ return new HttpResponseMessage(HttpStatusCode.OK)
+ {
+ Content = new StringContent("User Deleted")
+ };
+ }
+ }
+}
diff --git a/test/System.Web.Http.Test/Controllers/Apis/UsersRpcController.cs b/test/System.Web.Http.Test/Controllers/Apis/UsersRpcController.cs
new file mode 100644
index 00000000..d5024aaf
--- /dev/null
+++ b/test/System.Web.Http.Test/Controllers/Apis/UsersRpcController.cs
@@ -0,0 +1,57 @@
+
+namespace System.Web.Http
+{
+ public class UsersRpcController : ApiController
+ {
+ public User EchoUser(string firstName, string lastName)
+ {
+ return new User()
+ {
+ FirstName = firstName,
+ LastName = lastName,
+ };
+ }
+
+ [Authorize]
+ [HttpGet]
+ public User AddAdmin(string firstName, string lastName)
+ {
+ return new User()
+ {
+ FirstName = firstName,
+ LastName = lastName,
+ };
+ }
+
+ public User RetriveUser(int id)
+ {
+ return new User()
+ {
+ LastName = "UserLN" + id,
+ FirstName = "UserFN" + id
+ };
+ }
+
+ public User EchoUserObject(User user)
+ {
+ return user;
+ }
+
+ public User Admin()
+ {
+ return new User
+ {
+ FirstName = "Yao",
+ LastName = "Huang"
+ };
+ }
+
+ public void DeleteAllUsers()
+ {
+ }
+
+ public void AddUser([FromBody] User user)
+ {
+ }
+ }
+}
diff --git a/test/System.Web.Http.Test/Controllers/HttpActionContextTest.cs b/test/System.Web.Http.Test/Controllers/HttpActionContextTest.cs
new file mode 100644
index 00000000..6e878329
--- /dev/null
+++ b/test/System.Web.Http.Test/Controllers/HttpActionContextTest.cs
@@ -0,0 +1,76 @@
+using System.Web.Http.Controllers;
+using Moq;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Http
+{
+ public class HttpActionContextTest
+ {
+ [Fact]
+ public void Default_Constructor()
+ {
+ HttpActionContext actionContext = new HttpActionContext();
+
+ Assert.Null(actionContext.ControllerContext);
+ Assert.Null(actionContext.ActionDescriptor);
+ Assert.Null(actionContext.Response);
+ Assert.Null(actionContext.Request);
+ Assert.NotNull(actionContext.ActionArguments);
+ Assert.NotNull(actionContext.ModelState);
+ }
+
+ [Fact]
+ public void Parameter_Constructor()
+ {
+ HttpControllerContext controllerContext = ContextUtil.CreateControllerContext();
+ HttpActionDescriptor actionDescriptor = new Mock<HttpActionDescriptor>().Object;
+ HttpActionContext actionContext = new HttpActionContext(controllerContext, actionDescriptor);
+
+ Assert.Same(controllerContext, actionContext.ControllerContext);
+ Assert.Same(actionDescriptor, actionContext.ActionDescriptor);
+ Assert.Same(controllerContext.Request, actionContext.Request);
+ Assert.Null(actionContext.Response);
+ Assert.NotNull(actionContext.ActionArguments);
+ Assert.NotNull(actionContext.ModelState);
+ }
+
+ [Fact]
+ public void Constructor_Throws_IfControllerContextIsNull()
+ {
+ Assert.ThrowsArgumentNull(
+ () => new HttpActionContext(null, new Mock<HttpActionDescriptor>().Object),
+ "controllerContext");
+ }
+
+ [Fact]
+ public void Constructor_Throws_IfActionDescriptorIsNull()
+ {
+ Assert.ThrowsArgumentNull(
+ () => new HttpActionContext(ContextUtil.CreateControllerContext(), null),
+ "actionDescriptor");
+ }
+
+ [Fact]
+ public void ControllerContext_Property()
+ {
+ Assert.Reflection.Property<HttpActionContext, HttpControllerContext>(
+ instance: new HttpActionContext(),
+ propertyGetter: ac => ac.ControllerContext,
+ expectedDefaultValue: null,
+ allowNull: false,
+ roundTripTestValue: ContextUtil.CreateControllerContext());
+ }
+
+ [Fact]
+ public void ActionDescriptor_Property()
+ {
+ Assert.Reflection.Property<HttpActionContext, HttpActionDescriptor>(
+ instance: new HttpActionContext(),
+ propertyGetter: ac => ac.ActionDescriptor,
+ expectedDefaultValue: null,
+ allowNull: false,
+ roundTripTestValue: new Mock<HttpActionDescriptor>().Object);
+ }
+ }
+}
diff --git a/test/System.Web.Http.Test/Controllers/HttpConfigurationTest.cs b/test/System.Web.Http.Test/Controllers/HttpConfigurationTest.cs
new file mode 100644
index 00000000..da0e9718
--- /dev/null
+++ b/test/System.Web.Http.Test/Controllers/HttpConfigurationTest.cs
@@ -0,0 +1,48 @@
+using Microsoft.TestCommon;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Http
+{
+ public class HttpConfigurationTest
+ {
+ [Fact]
+ public void TypeIsCorrect()
+ {
+ Assert.Type.HasProperties<HttpConfiguration>(TypeAssert.TypeProperties.IsPublicVisibleClass | TypeAssert.TypeProperties.IsDisposable);
+ }
+
+ [Fact]
+ public void Default_Constructor()
+ {
+ HttpConfiguration configuration = new HttpConfiguration();
+
+ Assert.Empty(configuration.Filters);
+ Assert.NotEmpty(configuration.Formatters);
+ Assert.Empty(configuration.MessageHandlers);
+ Assert.Empty(configuration.Properties);
+ Assert.Empty(configuration.Routes);
+ Assert.NotNull(configuration.ServiceResolver);
+ Assert.Equal("/", configuration.VirtualPathRoot);
+ }
+
+ [Fact]
+ public void Parameter_Constructor()
+ {
+ string path = "/some/path";
+ HttpRouteCollection routes = new HttpRouteCollection(path);
+ HttpConfiguration configuration = new HttpConfiguration(routes);
+
+ Assert.Same(routes, configuration.Routes);
+ Assert.Equal(path, configuration.VirtualPathRoot);
+ }
+
+ [Fact]
+ public void Dispose_Idempotent()
+ {
+ HttpConfiguration configuration = new HttpConfiguration();
+ configuration.Dispose();
+ configuration.Dispose();
+ }
+ }
+}
diff --git a/test/System.Web.Http.Test/Controllers/HttpControllerContextTest.cs b/test/System.Web.Http.Test/Controllers/HttpControllerContextTest.cs
new file mode 100644
index 00000000..c745fd59
--- /dev/null
+++ b/test/System.Web.Http.Test/Controllers/HttpControllerContextTest.cs
@@ -0,0 +1,108 @@
+using System.Net.Http;
+using System.Web.Http.Controllers;
+using System.Web.Http.Routing;
+using Moq;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Http
+{
+ public class HttpControllerContextTest
+ {
+ [Fact]
+ public void Default_Constructor()
+ {
+ HttpControllerContext controllerContext = new HttpControllerContext();
+
+ Assert.Null(controllerContext.Configuration);
+ Assert.Null(controllerContext.Controller);
+ Assert.Null(controllerContext.ControllerDescriptor);
+ Assert.Null(controllerContext.Request);
+ Assert.Null(controllerContext.RouteData);
+ }
+
+ [Fact]
+ public void Parameter_Constructor()
+ {
+ HttpConfiguration config = new HttpConfiguration();
+ IHttpRouteData routeData = new Mock<IHttpRouteData>().Object;
+ HttpRequestMessage request = new HttpRequestMessage();
+ HttpControllerContext controllerContext = new HttpControllerContext(config, routeData, request);
+
+ Assert.Same(config, controllerContext.Configuration);
+ Assert.Same(request, controllerContext.Request);
+ Assert.Same(routeData, controllerContext.RouteData);
+ Assert.Null(controllerContext.Controller);
+ Assert.Null(controllerContext.ControllerDescriptor);
+ }
+
+ [Fact]
+ public void Constructor_Throws_IfConfigurationIsNull()
+ {
+ Assert.ThrowsArgumentNull(
+ () => new HttpControllerContext(null, new Mock<IHttpRouteData>().Object, new HttpRequestMessage()),
+ "configuration");
+ }
+
+ [Fact]
+ public void Constructor_Throws_IfRouteDataIsNull()
+ {
+ Assert.ThrowsArgumentNull(
+ () => new HttpControllerContext(new HttpConfiguration(), null, new HttpRequestMessage()),
+ "routeData");
+ }
+
+ [Fact]
+ public void Constructor_Throws_IfRequestIsNull()
+ {
+ Assert.ThrowsArgumentNull(
+ () => new HttpControllerContext(new HttpConfiguration(), new Mock<IHttpRouteData>().Object, null),
+ "request");
+ }
+
+ [Fact]
+ public void Configuration_Property()
+ {
+ Assert.Reflection.Property<HttpControllerContext, HttpConfiguration>(
+ instance: new HttpControllerContext(),
+ propertyGetter: cc => cc.Configuration,
+ expectedDefaultValue: null,
+ allowNull: false,
+ roundTripTestValue: new HttpConfiguration());
+ }
+
+ [Fact]
+ public void Controller_Property()
+ {
+ Assert.Reflection.Property<HttpControllerContext, IHttpController>(
+ instance: new HttpControllerContext(),
+ propertyGetter: cc => cc.Controller,
+ expectedDefaultValue: null,
+ allowNull: false,
+ roundTripTestValue: new Mock<IHttpController>().Object);
+ }
+
+ [Fact]
+ public void ControllerDescriptor_Property()
+ {
+ Assert.Reflection.Property<HttpControllerContext, HttpControllerDescriptor>(
+ instance: new HttpControllerContext(),
+ propertyGetter: cc => cc.ControllerDescriptor,
+ expectedDefaultValue: null,
+ allowNull: false,
+ roundTripTestValue: new HttpControllerDescriptor());
+ }
+
+ [Fact]
+ public void RouteData_Property()
+ {
+ Assert.Reflection.Property<HttpControllerContext, IHttpRouteData>(
+ instance: new HttpControllerContext(),
+ propertyGetter: cc => cc.RouteData,
+ expectedDefaultValue: null,
+ allowNull: false,
+ roundTripTestValue: new Mock<IHttpRouteData>().Object);
+ }
+
+ }
+}
diff --git a/test/System.Web.Http.Test/Controllers/HttpControllerDescriptorTest.cs b/test/System.Web.Http.Test/Controllers/HttpControllerDescriptorTest.cs
new file mode 100644
index 00000000..62c90f80
--- /dev/null
+++ b/test/System.Web.Http.Test/Controllers/HttpControllerDescriptorTest.cs
@@ -0,0 +1,183 @@
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Web.Http.Controllers;
+using System.Web.Http.Dispatcher;
+using System.Web.Http.Filters;
+using Moq;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Http
+{
+ public class HttpControllerDescriptorTest
+ {
+ [Fact]
+ public void Default_Constructor()
+ {
+ HttpControllerDescriptor controllerDescriptor = new HttpControllerDescriptor();
+
+ Assert.Null(controllerDescriptor.ControllerName);
+ Assert.Null(controllerDescriptor.Configuration);
+ Assert.Null(controllerDescriptor.ControllerType);
+ Assert.Null(controllerDescriptor.HttpActionInvoker);
+ Assert.Null(controllerDescriptor.HttpActionSelector);
+ Assert.Null(controllerDescriptor.HttpControllerActivator);
+ Assert.NotNull(controllerDescriptor.Properties);
+ }
+
+ [Fact]
+ public void Parameter_Constructor()
+ {
+ HttpConfiguration config = new HttpConfiguration();
+ string controllerName = "UsersController";
+ Type controllerType = typeof(UsersController);
+
+ HttpControllerDescriptor controllerDescriptor = new HttpControllerDescriptor(config, controllerName, controllerType);
+ Assert.NotNull(controllerDescriptor.ControllerName);
+ Assert.NotNull(controllerDescriptor.Configuration);
+ Assert.NotNull(controllerDescriptor.ControllerType);
+ Assert.NotNull(controllerDescriptor.HttpActionInvoker);
+ Assert.NotNull(controllerDescriptor.HttpActionSelector);
+ Assert.NotNull(controllerDescriptor.HttpControllerActivator);
+ Assert.NotNull(controllerDescriptor.Properties);
+ Assert.Equal(config, controllerDescriptor.Configuration);
+ Assert.Equal(controllerName, controllerDescriptor.ControllerName);
+ Assert.Equal(controllerType, controllerDescriptor.ControllerType);
+ }
+
+ [Fact]
+ public void Constructor_Throws_IfConfigurationIsNull()
+ {
+ Assert.ThrowsArgumentNull(
+ () => new HttpControllerDescriptor(null, "UsersController", typeof(UsersController)),
+ "configuration");
+ }
+
+ [Fact]
+ public void Constructor_Throws_IfControllerNameIsNull()
+ {
+ Assert.ThrowsArgumentNull(
+ () => new HttpControllerDescriptor(new HttpConfiguration(), null, typeof(UsersController)),
+ "controllerName");
+ }
+
+ [Fact]
+ public void Constructor_Throws_IfControllerTypeIsNull()
+ {
+ Assert.ThrowsArgumentNull(
+ () => new HttpControllerDescriptor(new HttpConfiguration(), "UsersController", null),
+ "controllerType");
+ }
+
+ [Fact]
+ public void Configuration_Property()
+ {
+ HttpConfiguration config = new HttpConfiguration();
+ HttpControllerDescriptor controllerDescriptor = new HttpControllerDescriptor();
+
+ Assert.Reflection.Property<HttpControllerDescriptor, HttpConfiguration>(
+ instance: controllerDescriptor,
+ propertyGetter: cd => cd.Configuration,
+ expectedDefaultValue: null,
+ allowNull: false,
+ roundTripTestValue: config);
+ }
+
+ [Fact]
+ public void ControllerName_Property()
+ {
+ string controllerName = "UsersController";
+ HttpControllerDescriptor controllerDescriptor = new HttpControllerDescriptor();
+
+ Assert.Reflection.Property<HttpControllerDescriptor, string>(
+ instance: controllerDescriptor,
+ propertyGetter: cd => cd.ControllerName,
+ expectedDefaultValue: null,
+ allowNull: false,
+ roundTripTestValue: controllerName);
+ }
+
+ [Fact]
+ public void ControllerType_Property()
+ {
+ Type controllerType = typeof(UsersController);
+ HttpControllerDescriptor controllerDescriptor = new HttpControllerDescriptor();
+
+ Assert.Reflection.Property<HttpControllerDescriptor, Type>(
+ instance: controllerDescriptor,
+ propertyGetter: cd => cd.ControllerType,
+ expectedDefaultValue: null,
+ allowNull: false,
+ roundTripTestValue: controllerType);
+ }
+
+ [Fact]
+ public void HttpActionInvoker_Property()
+ {
+ IHttpActionInvoker invoker = new ApiControllerActionInvoker();
+ HttpControllerDescriptor controllerDescriptor = new HttpControllerDescriptor();
+
+ Assert.Reflection.Property<HttpControllerDescriptor, IHttpActionInvoker>(
+ instance: controllerDescriptor,
+ propertyGetter: cd => cd.HttpActionInvoker,
+ expectedDefaultValue: null,
+ allowNull: false,
+ roundTripTestValue: invoker);
+ }
+
+ [Fact]
+ public void HttpActionSelector_Property()
+ {
+ IHttpActionSelector selector = new ApiControllerActionSelector();
+ HttpControllerDescriptor controllerDescriptor = new HttpControllerDescriptor();
+
+ Assert.Reflection.Property<HttpControllerDescriptor, IHttpActionSelector>(
+ instance: controllerDescriptor,
+ propertyGetter: cd => cd.HttpActionSelector,
+ expectedDefaultValue: null,
+ allowNull: false,
+ roundTripTestValue: selector);
+ }
+
+ [Fact]
+ public void HttpControllerActivator_Property()
+ {
+ IHttpControllerActivator activator = new Mock<IHttpControllerActivator>().Object;
+ HttpControllerDescriptor controllerDescriptor = new HttpControllerDescriptor();
+
+ Assert.Reflection.Property<HttpControllerDescriptor, IHttpControllerActivator>(
+ instance: controllerDescriptor,
+ propertyGetter: cd => cd.HttpControllerActivator,
+ expectedDefaultValue: null,
+ allowNull: false,
+ roundTripTestValue: activator);
+ }
+
+ [Fact]
+ public void ActionValueBinder_Property()
+ {
+ IActionValueBinder activator = new Mock<IActionValueBinder>().Object;
+ HttpControllerDescriptor controllerDescriptor = new HttpControllerDescriptor();
+
+ Assert.Reflection.Property<HttpControllerDescriptor, IActionValueBinder>(
+ instance: controllerDescriptor,
+ propertyGetter: cd => cd.ActionValueBinder,
+ expectedDefaultValue: null,
+ allowNull: false,
+ roundTripTestValue: activator);
+ }
+
+ [Fact]
+ public void GetFilters_InvokesGetCustomAttributesMethod()
+ {
+ var descriptorMock = new Mock<HttpControllerDescriptor> { CallBase = true };
+ var filters = new ReadOnlyCollection<IFilter>(new List<IFilter>());
+ descriptorMock.Setup(d => d.GetCustomAttributes<IFilter>()).Returns(filters).Verifiable();
+
+ var result = descriptorMock.Object.GetFilters();
+
+ Assert.Same(filters, result);
+ descriptorMock.Verify();
+ }
+ }
+}
diff --git a/test/System.Web.Http.Test/Controllers/HttpParameterDescriptorTest.cs b/test/System.Web.Http.Test/Controllers/HttpParameterDescriptorTest.cs
new file mode 100644
index 00000000..44ae4113
--- /dev/null
+++ b/test/System.Web.Http.Test/Controllers/HttpParameterDescriptorTest.cs
@@ -0,0 +1,73 @@
+using System.Collections.Generic;
+using System.Linq;
+using System.Web.Http.Controllers;
+using Moq;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Http
+{
+ public class HttpParameterDescriptorTest
+ {
+ [Fact]
+ public void Default_Constructor()
+ {
+ HttpParameterDescriptor parameterDescriptor = new Mock<HttpParameterDescriptor>().Object;
+
+ Assert.Null(parameterDescriptor.ParameterName);
+ Assert.Null(parameterDescriptor.ParameterType);
+ Assert.Null(parameterDescriptor.Configuration);
+ Assert.Null(parameterDescriptor.Prefix);
+ Assert.Null(parameterDescriptor.ModelBinderAttribute);
+ Assert.Null(parameterDescriptor.ActionDescriptor);
+ Assert.Null(parameterDescriptor.DefaultValue);
+ Assert.NotNull(parameterDescriptor.Properties);
+ }
+
+ [Fact]
+ public void Configuration_Property()
+ {
+ HttpConfiguration config = new HttpConfiguration();
+ HttpParameterDescriptor parameterDescriptor = new Mock<HttpParameterDescriptor> { CallBase = true }.Object;
+
+ Assert.Reflection.Property<HttpParameterDescriptor, HttpConfiguration>(
+ instance: parameterDescriptor,
+ propertyGetter: pd => pd.Configuration,
+ expectedDefaultValue: null,
+ allowNull: false,
+ roundTripTestValue: config);
+ }
+
+ [Fact]
+ public void ActionDescriptor_Property()
+ {
+ HttpParameterDescriptor parameterDescriptor = new Mock<HttpParameterDescriptor> { CallBase = true }.Object;
+ HttpActionDescriptor actionDescriptor = new Mock<HttpActionDescriptor>().Object;
+
+ Assert.Reflection.Property<HttpParameterDescriptor, HttpActionDescriptor>(
+ instance: parameterDescriptor,
+ propertyGetter: pd => pd.ActionDescriptor,
+ expectedDefaultValue: null,
+ allowNull: false,
+ roundTripTestValue: actionDescriptor);
+ }
+
+ [Fact]
+ public void GetCustomAttributes_Returns_EmptyAttributes()
+ {
+ HttpParameterDescriptor parameterDescriptor = new Mock<HttpParameterDescriptor> { CallBase = true }.Object;
+ IEnumerable<object> attributes = parameterDescriptor.GetCustomAttributes<object>();
+
+ Assert.Equal(0, attributes.Count());
+ }
+
+ [Fact]
+ public void GetCustomAttributes_AttributeType_Returns_EmptyAttributes()
+ {
+ HttpParameterDescriptor parameterDescriptor = new Mock<HttpParameterDescriptor> { CallBase = true }.Object;
+ IEnumerable<FromBodyAttribute> attributes = parameterDescriptor.GetCustomAttributes<FromBodyAttribute>();
+
+ Assert.Equal(0, attributes.Count());
+ }
+ }
+}
diff --git a/test/System.Web.Http.Test/Controllers/ReflectedHttpActionDescriptorTest.cs b/test/System.Web.Http.Test/Controllers/ReflectedHttpActionDescriptorTest.cs
new file mode 100644
index 00000000..253fdada
--- /dev/null
+++ b/test/System.Web.Http.Test/Controllers/ReflectedHttpActionDescriptorTest.cs
@@ -0,0 +1,299 @@
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Linq;
+using System.Reflection;
+using System.Web.Http.Common;
+using System.Web.Http.Controllers;
+using System.Web.Http.Filters;
+using System.Web.Http.Properties;
+using Moq;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Http
+{
+ public class ReflectedHttpActionDescriptorTest
+ {
+ [Fact]
+ public void Default_Constructor()
+ {
+ ReflectedHttpActionDescriptor actionDescriptor = new ReflectedHttpActionDescriptor();
+
+ Assert.Null(actionDescriptor.ActionName);
+ Assert.Null(actionDescriptor.Configuration);
+ Assert.Null(actionDescriptor.ControllerDescriptor);
+ Assert.Null(actionDescriptor.MethodInfo);
+ Assert.Null(actionDescriptor.ReturnType);
+ Assert.NotNull(actionDescriptor.Properties);
+ }
+
+ [Fact]
+ public void Parameter_Constructor()
+ {
+ UsersRpcController controller = new UsersRpcController();
+ Func<string, string, User> echoUserMethod = controller.EchoUser;
+ HttpConfiguration config = new HttpConfiguration();
+ HttpControllerDescriptor controllerDescriptor = new HttpControllerDescriptor(config, "", typeof(UsersRpcController));
+ ReflectedHttpActionDescriptor actionDescriptor = new ReflectedHttpActionDescriptor(controllerDescriptor, echoUserMethod.Method);
+
+ Assert.Equal("EchoUser", actionDescriptor.ActionName);
+ Assert.Equal(config, actionDescriptor.Configuration);
+ Assert.Equal(typeof(UsersRpcController), actionDescriptor.ControllerDescriptor.ControllerType);
+ Assert.Equal(echoUserMethod.Method, actionDescriptor.MethodInfo);
+ Assert.Equal(typeof(User), actionDescriptor.ReturnType);
+ Assert.NotNull(actionDescriptor.Properties);
+ }
+
+ [Fact]
+ public void Constructor_Throws_IfMethodInfoIsNull()
+ {
+ Assert.ThrowsArgumentNull(
+ () => new ReflectedHttpActionDescriptor(new HttpControllerDescriptor(), null),
+ "methodInfo");
+ }
+
+ [Fact]
+ public void MethodInfo_Property()
+ {
+ ReflectedHttpActionDescriptor actionDescriptor = new ReflectedHttpActionDescriptor();
+ Action action = new Action(() => { });
+
+ Assert.Reflection.Property<ReflectedHttpActionDescriptor, MethodInfo>(
+ instance: actionDescriptor,
+ propertyGetter: ad => ad.MethodInfo,
+ expectedDefaultValue: null,
+ allowNull: false,
+ roundTripTestValue: action.Method);
+ }
+
+ [Fact]
+ public void ControllerDescriptor_Property()
+ {
+ ReflectedHttpActionDescriptor actionDescriptor = new ReflectedHttpActionDescriptor();
+ HttpControllerDescriptor controllerDescriptor = new HttpControllerDescriptor();
+
+ Assert.Reflection.Property<ReflectedHttpActionDescriptor, HttpControllerDescriptor>(
+ instance: actionDescriptor,
+ propertyGetter: ad => ad.ControllerDescriptor,
+ expectedDefaultValue: null,
+ allowNull: false,
+ roundTripTestValue: controllerDescriptor);
+ }
+
+ [Fact]
+ public void Configuration_Property()
+ {
+ ReflectedHttpActionDescriptor actionDescriptor = new ReflectedHttpActionDescriptor();
+ HttpConfiguration config = new HttpConfiguration();
+
+ Assert.Reflection.Property<ReflectedHttpActionDescriptor, HttpConfiguration>(
+ instance: actionDescriptor,
+ propertyGetter: ad => ad.Configuration,
+ expectedDefaultValue: null,
+ allowNull: false,
+ roundTripTestValue: config);
+ }
+
+ [Fact]
+ public void GetFilter_Returns_AttributedFilter()
+ {
+ UsersRpcController controller = new UsersRpcController();
+ Func<string, string, User> echoUserMethod = controller.AddAdmin;
+ ReflectedHttpActionDescriptor actionDescriptor = new ReflectedHttpActionDescriptor { MethodInfo = echoUserMethod.Method };
+ HttpControllerContext context = ContextUtil.CreateControllerContext(instance: controller);
+ Dictionary<string, object> arguments = new Dictionary<string, object>
+ {
+ {"firstName", "test"},
+ {"lastName", "unit"}
+ };
+
+ IEnumerable<IFilter> filters = actionDescriptor.GetFilters();
+
+ Assert.NotNull(filters);
+ Assert.Equal(1, filters.Count());
+ Assert.Equal(typeof(AuthorizeAttribute), filters.First().GetType());
+ }
+
+ [Fact]
+ public void GetFilterPipeline_Returns_ConfigurationFilters()
+ {
+ IActionFilter actionFilter = new Mock<IActionFilter>().Object;
+ IExceptionFilter exceptionFilter = new Mock<IExceptionFilter>().Object;
+ IAuthorizationFilter authorizationFilter = new AuthorizeAttribute();
+ UsersRpcController controller = new UsersRpcController();
+ Action deleteAllUsersMethod = controller.DeleteAllUsers;
+
+ HttpControllerDescriptor controllerDescriptor = new HttpControllerDescriptor(new HttpConfiguration(), "UsersRpcController", typeof(UsersRpcController));
+ controllerDescriptor.Configuration.Filters.Add(actionFilter);
+ controllerDescriptor.Configuration.Filters.Add(exceptionFilter);
+ controllerDescriptor.Configuration.Filters.Add(authorizationFilter);
+ ReflectedHttpActionDescriptor actionDescriptor = new ReflectedHttpActionDescriptor(controllerDescriptor, deleteAllUsersMethod.Method);
+
+ Collection<FilterInfo> filters = actionDescriptor.GetFilterPipeline();
+
+ Assert.Same(actionFilter, filters[0].Instance);
+ Assert.Same(exceptionFilter, filters[1].Instance);
+ Assert.Same(authorizationFilter, filters[2].Instance);
+ }
+
+ [Fact]
+ public void GetCustomAttributes_Returns_ActionAttributes()
+ {
+ UsersRpcController controller = new UsersRpcController();
+ Func<string, string, User> echoUserMethod = controller.AddAdmin;
+ ReflectedHttpActionDescriptor actionDescriptor = new ReflectedHttpActionDescriptor { MethodInfo = echoUserMethod.Method };
+ HttpControllerContext context = ContextUtil.CreateControllerContext(instance: controller);
+
+ IEnumerable<IFilter> filters = actionDescriptor.GetCustomAttributes<IFilter>();
+ IEnumerable<HttpGetAttribute> httpGet = actionDescriptor.GetCustomAttributes<HttpGetAttribute>();
+
+ Assert.NotNull(filters);
+ Assert.Equal(1, filters.Count());
+ Assert.Equal(typeof(AuthorizeAttribute), filters.First().GetType());
+ Assert.NotNull(httpGet);
+ Assert.Equal(1, httpGet.Count());
+ }
+
+ [Fact]
+ public void GetParameters_Returns_ActionParameters()
+ {
+ UsersRpcController controller = new UsersRpcController();
+ Func<string, string, User> echoUserMethod = controller.EchoUser;
+ ReflectedHttpActionDescriptor actionDescriptor = new ReflectedHttpActionDescriptor { MethodInfo = echoUserMethod.Method };
+ HttpControllerContext context = ContextUtil.CreateControllerContext(instance: controller);
+
+ Collection<HttpParameterDescriptor> parameterDescriptors = actionDescriptor.GetParameters();
+
+ Assert.Equal(2, parameterDescriptors.Count);
+ Assert.NotNull(parameterDescriptors.Where(p => p.ParameterName == "firstName").FirstOrDefault());
+ Assert.NotNull(parameterDescriptors.Where(p => p.ParameterName == "lastName").FirstOrDefault());
+ }
+
+ [Fact]
+ public void Execute_Returns_Null_ForVoidAction()
+ {
+ UsersRpcController controller = new UsersRpcController();
+ Action deleteAllUsersMethod = controller.DeleteAllUsers;
+ ReflectedHttpActionDescriptor actionDescriptor = new ReflectedHttpActionDescriptor { MethodInfo = deleteAllUsersMethod.Method };
+ HttpControllerContext context = ContextUtil.CreateControllerContext(instance: controller);
+ Dictionary<string, object> arguments = new Dictionary<string, object>();
+
+ object returnValue = actionDescriptor.Execute(context, arguments);
+
+ Assert.Null(returnValue);
+ }
+
+ [Fact]
+ public void Execute_Returns_Results_ForNonVoidAction()
+ {
+ UsersRpcController controller = new UsersRpcController();
+ Func<string, string, User> echoUserMethod = controller.EchoUser;
+ ReflectedHttpActionDescriptor actionDescriptor = new ReflectedHttpActionDescriptor { MethodInfo = echoUserMethod.Method };
+ HttpControllerContext context = ContextUtil.CreateControllerContext(instance: controller);
+ Dictionary<string, object> arguments = new Dictionary<string, object>
+ {
+ {"firstName", "test"},
+ {"lastName", "unit"}
+ };
+
+ User returnValue = actionDescriptor.Execute(context, arguments) as User;
+
+ Assert.NotNull(returnValue);
+ Assert.Equal("test", returnValue.FirstName);
+ Assert.Equal("unit", returnValue.LastName);
+ }
+
+ [Fact]
+ public void Execute_Throws_IfContextIsNull()
+ {
+ UsersRpcController controller = new UsersRpcController();
+ Func<string, string, User> echoUserMethod = controller.EchoUser;
+ ReflectedHttpActionDescriptor actionDescriptor = new ReflectedHttpActionDescriptor { MethodInfo = echoUserMethod.Method };
+ Dictionary<string, object> arguments = new Dictionary<string, object>();
+
+ Assert.ThrowsArgumentNull(
+ () => actionDescriptor.Execute(null, arguments),
+ "controllerContext");
+ }
+
+ [Fact]
+ public void Execute_Throws_IfArgumentsIsNull()
+ {
+ UsersRpcController controller = new UsersRpcController();
+ Func<string, string, User> echoUserMethod = controller.EchoUser;
+ ReflectedHttpActionDescriptor actionDescriptor = new ReflectedHttpActionDescriptor { MethodInfo = echoUserMethod.Method };
+ HttpControllerContext context = ContextUtil.CreateControllerContext();
+
+ Assert.ThrowsArgumentNull(
+ () => actionDescriptor.Execute(context, null),
+ "arguments");
+ }
+
+ [Fact]
+ public void Execute_Throws_IfValueTypeArgumentsIsNull()
+ {
+ UsersRpcController controller = new UsersRpcController();
+ Func<int, User> retrieveUserMethod = controller.RetriveUser;
+ ReflectedHttpActionDescriptor actionDescriptor = new ReflectedHttpActionDescriptor { MethodInfo = retrieveUserMethod.Method };
+ HttpControllerContext context = ContextUtil.CreateControllerContext(instance: controller);
+ Dictionary<string, object> arguments = new Dictionary<string, object>
+ {
+ {"id", null}
+ };
+
+ Assert.Throws<HttpResponseException>(
+ () => actionDescriptor.Execute(context, arguments),
+ Error.Format(
+ SRResources.ReflectedActionDescriptor_ParameterCannotBeNull,
+ "id",
+ typeof(int),
+ actionDescriptor.MethodInfo,
+ controller.GetType()));
+ }
+
+ [Fact]
+ public void Execute_Throws_IfArgumentNameIsWrong()
+ {
+ UsersRpcController controller = new UsersRpcController();
+ Func<int, User> retrieveUserMethod = controller.RetriveUser;
+ ReflectedHttpActionDescriptor actionDescriptor = new ReflectedHttpActionDescriptor { MethodInfo = retrieveUserMethod.Method };
+ HttpControllerContext context = ContextUtil.CreateControllerContext(instance: controller);
+ Dictionary<string, object> arguments = new Dictionary<string, object>
+ {
+ {"otherId", 6}
+ };
+
+ Assert.Throws<HttpResponseException>(
+ () => actionDescriptor.Execute(context, arguments),
+ Error.Format(
+ SRResources.ReflectedActionDescriptor_ParameterNotInDictionary,
+ "id",
+ typeof(int),
+ actionDescriptor.MethodInfo,
+ controller.GetType()));
+ }
+
+ [Fact]
+ public void Execute_Throws_IfArgumentTypeIsWrong()
+ {
+ UsersRpcController controller = new UsersRpcController();
+ Func<int, User> retrieveUserMethod = controller.RetriveUser;
+ ReflectedHttpActionDescriptor actionDescriptor = new ReflectedHttpActionDescriptor { MethodInfo = retrieveUserMethod.Method };
+ HttpControllerContext context = ContextUtil.CreateControllerContext(instance: controller);
+ Dictionary<string, object> arguments = new Dictionary<string, object>
+ {
+ {"id", new DateTime()}
+ };
+
+ Assert.Throws<HttpResponseException>(
+ () => actionDescriptor.Execute(context, arguments),
+ Error.Format(
+ SRResources.ReflectedActionDescriptor_ParameterValueHasWrongType,
+ "id",
+ actionDescriptor.MethodInfo,
+ controller.GetType(),
+ typeof(DateTime),
+ typeof(int)));
+ }
+ }
+}
diff --git a/test/System.Web.Http.Test/Controllers/ReflectedHttpParameterDescriptorTest.cs b/test/System.Web.Http.Test/Controllers/ReflectedHttpParameterDescriptorTest.cs
new file mode 100644
index 00000000..69fd841b
--- /dev/null
+++ b/test/System.Web.Http.Test/Controllers/ReflectedHttpParameterDescriptorTest.cs
@@ -0,0 +1,92 @@
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection;
+using System.Web.Http.Controllers;
+using System.Web.Http.ValueProviders;
+using Moq;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Http
+{
+ public class ReflectedHttpParameterDescriptorTest
+ {
+ [Fact]
+ public void Parameter_Constructor()
+ {
+ UsersRpcController controller = new UsersRpcController();
+ Func<string, string, User> echoUserMethod = controller.EchoUser;
+ ReflectedHttpActionDescriptor actionDescriptor = new ReflectedHttpActionDescriptor { MethodInfo = echoUserMethod.Method };
+ ParameterInfo parameterInfo = echoUserMethod.Method.GetParameters()[0];
+ ReflectedHttpParameterDescriptor parameterDescriptor = new ReflectedHttpParameterDescriptor(actionDescriptor, parameterInfo);
+
+ Assert.Equal(actionDescriptor, parameterDescriptor.ActionDescriptor);
+ Assert.Null(parameterDescriptor.DefaultValue);
+ Assert.Equal(parameterInfo, parameterDescriptor.ParameterInfo);
+ Assert.Equal(parameterInfo.Name, parameterDescriptor.ParameterName);
+ Assert.Equal(typeof(string), parameterDescriptor.ParameterType);
+ Assert.Null(parameterDescriptor.Prefix);
+ Assert.Null(parameterDescriptor.ModelBinderAttribute);
+ }
+
+ [Fact]
+ public void Constructor_Throws_IfParameterInfoIsNull()
+ {
+ Assert.ThrowsArgumentNull(
+ () => new ReflectedHttpParameterDescriptor(new Mock<HttpActionDescriptor>().Object, null),
+ "parameterInfo");
+ }
+
+ [Fact]
+ public void Constructor_Throws_IfActionDescriptorIsNull()
+ {
+ Assert.ThrowsArgumentNull(
+ () => new ReflectedHttpParameterDescriptor(null, new Mock<ParameterInfo>().Object),
+ "actionDescriptor");
+ }
+
+ [Fact]
+ public void ParameterInfo_Property()
+ {
+ ParameterInfo referenceParameter = new Mock<ParameterInfo>().Object;
+ Assert.Reflection.Property(new ReflectedHttpParameterDescriptor(), d => d.ParameterInfo, expectedDefaultValue: null, allowNull: false, roundTripTestValue: referenceParameter);
+ }
+
+ [Fact]
+ public void IsDefined_Retruns_True_WhenParameterAttributeIsFound()
+ {
+ UsersRpcController controller = new UsersRpcController();
+ Action<User> addUserMethod = controller.AddUser;
+ ReflectedHttpActionDescriptor actionDescriptor = new ReflectedHttpActionDescriptor { MethodInfo = addUserMethod.Method };
+ ParameterInfo parameterInfo = addUserMethod.Method.GetParameters()[0];
+ ReflectedHttpParameterDescriptor parameterDescriptor = new ReflectedHttpParameterDescriptor(actionDescriptor, parameterInfo);
+ }
+
+ [Fact]
+ public void GetCustomAttributes_Returns_ParameterAttributes()
+ {
+ UsersRpcController controller = new UsersRpcController();
+ Action<User> addUserMethod = controller.AddUser;
+ ReflectedHttpActionDescriptor actionDescriptor = new ReflectedHttpActionDescriptor { MethodInfo = addUserMethod.Method };
+ ParameterInfo parameterInfo = addUserMethod.Method.GetParameters()[0];
+ ReflectedHttpParameterDescriptor parameterDescriptor = new ReflectedHttpParameterDescriptor(actionDescriptor, parameterInfo);
+ object[] attributes = parameterDescriptor.GetCustomAttributes<object>().ToArray();
+
+ Assert.Equal(1, attributes.Length);
+ Assert.Equal(typeof(FromBodyAttribute), attributes[0].GetType());
+ }
+
+ [Fact]
+ public void GetCustomAttributes_AttributeType_Returns_ParameterAttributes()
+ {
+ UsersRpcController controller = new UsersRpcController();
+ Action<User> addUserMethod = controller.AddUser;
+ ReflectedHttpActionDescriptor actionDescriptor = new ReflectedHttpActionDescriptor { MethodInfo = addUserMethod.Method };
+ ParameterInfo parameterInfo = addUserMethod.Method.GetParameters()[0];
+ ReflectedHttpParameterDescriptor parameterDescriptor = new ReflectedHttpParameterDescriptor(actionDescriptor, parameterInfo);
+ IEnumerable<FromBodyAttribute> attributes = parameterDescriptor.GetCustomAttributes<FromBodyAttribute>();
+
+ Assert.Equal(1, attributes.Count());
+ }
+ }
+}
diff --git a/test/System.Web.Http.Test/DictionaryExtensionsTest.cs b/test/System.Web.Http.Test/DictionaryExtensionsTest.cs
new file mode 100644
index 00000000..6362f7f9
--- /dev/null
+++ b/test/System.Web.Http.Test/DictionaryExtensionsTest.cs
@@ -0,0 +1,127 @@
+using System.Collections.Generic;
+using System.Net;
+using Microsoft.TestCommon;
+using Xunit;
+using Xunit.Extensions;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Http
+{
+ public class DictionaryExtensionsTest
+ {
+ [Fact]
+ public void IsCorrectType()
+ {
+ Assert.Type.HasProperties(typeof(DictionaryExtensions), TypeAssert.TypeProperties.IsStatic | TypeAssert.TypeProperties.IsPublicVisibleClass);
+ }
+
+ [Fact]
+ public void TryGetValueThrowsOnNullCollection()
+ {
+ string value;
+ Assert.ThrowsArgumentNull(() => DictionaryExtensions.TryGetValue<string>(null, String.Empty, out value), "collection");
+ }
+
+ [Fact]
+ public void TryGetValueThrowsOnNullKey()
+ {
+ IDictionary<string, object> dict = new Dictionary<string, object>();
+ string value;
+ Assert.ThrowsArgumentNull(() => dict.TryGetValue<string>(null, out value), "key");
+ }
+
+ public static TheoryDataSet<object> DictionaryValues
+ {
+ get
+ {
+ return new TheoryDataSet<object>
+ {
+ "test",
+ new string[] { "A", "B", "C" },
+ 8,
+ new List<int> {1, 2, 3},
+ 1D,
+ (IEnumerable<double>)new List<double> { 1D, 2D, 3D },
+ new Uri("http://some.host"),
+ Guid.NewGuid(),
+ HttpStatusCode.NotImplemented,
+ new HttpStatusCode[] { HttpStatusCode.Accepted, HttpStatusCode.Ambiguous, HttpStatusCode.BadGateway }
+ };
+ }
+ }
+
+ [Fact]
+ public void TryGetValueReturnsFalse()
+ {
+ // Arrange
+ IDictionary<string, object> dict = new Dictionary<string, object>();
+
+ // Act
+ string resultValue = null;
+ bool result = dict.TryGetValue("notfound", out resultValue);
+
+ // Assert
+ Assert.False(result);
+ Assert.Null(resultValue);
+ }
+
+ [Theory]
+ [PropertyData("DictionaryValues")]
+ public void TryGetValueReturnsTrue<T>(T value)
+ {
+ // Arrange
+ IDictionary<string, object> dict = new Dictionary<string, object>()
+ {
+ { "key", value }
+ };
+
+
+ // Act
+ T resultValue;
+ bool result = DictionaryExtensions.TryGetValue(dict, "key", out resultValue);
+
+ // Assert
+ Assert.True(result);
+ Assert.Equal(typeof(T), resultValue.GetType());
+ Assert.Equal(value, resultValue);
+ }
+
+ [Fact]
+ public void GetValueThrowsOnNullCollection()
+ {
+ Assert.ThrowsArgumentNull(() => DictionaryExtensions.GetValue<string>(null, String.Empty), "collection");
+ }
+
+ [Fact]
+ public void GetValueThrowsOnNullKey()
+ {
+ IDictionary<string, object> dict = new Dictionary<string, object>();
+ Assert.ThrowsArgumentNull(() => dict.GetValue<string>(null), "key");
+ }
+
+ [Fact]
+ public void GetValueThrowsOnNotFound()
+ {
+ IDictionary<string, object> dict = new Dictionary<string, object>();
+ Assert.Throws<InvalidOperationException>(() => dict.GetValue<string>("notfound"));
+ }
+
+ [Theory]
+ [PropertyData("DictionaryValues")]
+ public void GetValueReturnsValue<T>(T value)
+ {
+ // Arrange
+ IDictionary<string, object> dict = new Dictionary<string, object>()
+ {
+ { "key", value }
+ };
+
+ // Act
+ T resultValue = DictionaryExtensions.GetValue<T>(dict, "key");
+
+ // Assert
+ Assert.Equal(typeof(T), resultValue.GetType());
+ Assert.Equal(value, resultValue);
+ }
+ }
+}
diff --git a/test/System.Web.Http.Test/Dispatcher/DefaultBuildManagerTest.cs b/test/System.Web.Http.Test/Dispatcher/DefaultBuildManagerTest.cs
new file mode 100644
index 00000000..36ed2c7c
--- /dev/null
+++ b/test/System.Web.Http.Test/Dispatcher/DefaultBuildManagerTest.cs
@@ -0,0 +1,73 @@
+using System.Collections;
+using System.IO;
+using System.Web.Http.Dispatcher;
+using Microsoft.TestCommon;
+using Xunit;
+using Xunit.Extensions;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Http.Filters
+{
+ public class DefaultBuildManagerTest
+ {
+ [Fact]
+ public void TypeIsCorrect()
+ {
+ Assert.Type.HasProperties<DefaultBuildManager, IBuildManager>(TypeAssert.TypeProperties.IsClass);
+ }
+
+ [Fact]
+ public void Constructor()
+ {
+ Assert.NotNull(new DefaultBuildManager());
+ }
+
+ [Theory]
+ [InlineData(null)]
+ [InlineData("")]
+ [InlineData("path")]
+ public void FileExists(string path)
+ {
+ IBuildManager buildManager = new DefaultBuildManager();
+ Assert.False(buildManager.FileExists(path));
+ }
+
+ [Theory]
+ [InlineData(null)]
+ [InlineData("")]
+ [InlineData("path")]
+ public void GetCompiledType(string path)
+ {
+ IBuildManager buildManager = new DefaultBuildManager();
+ Assert.Null(buildManager.GetCompiledType(path));
+ }
+
+ [Fact]
+ public void GetReferencedAssemblies()
+ {
+ IBuildManager buildManager = new DefaultBuildManager();
+ ICollection assemblies = buildManager.GetReferencedAssemblies();
+ Assert.NotEmpty(assemblies);
+ }
+
+ [Theory]
+ [InlineData(null)]
+ [InlineData("")]
+ [InlineData("path")]
+ public void ReadCachedFile(string path)
+ {
+ IBuildManager buildManager = new DefaultBuildManager();
+ Assert.Null(buildManager.ReadCachedFile(path));
+ }
+
+ [Theory]
+ [InlineData(null)]
+ [InlineData("")]
+ [InlineData("path")]
+ public void CreateCachedFile(string path)
+ {
+ IBuildManager buildManager = new DefaultBuildManager();
+ Assert.Same(Stream.Null, buildManager.CreateCachedFile(path));
+ }
+ }
+}
diff --git a/test/System.Web.Http.Test/Filters/ActionDescriptorFilterProviderTest.cs b/test/System.Web.Http.Test/Filters/ActionDescriptorFilterProviderTest.cs
new file mode 100644
index 00000000..709e6674
--- /dev/null
+++ b/test/System.Web.Http.Test/Filters/ActionDescriptorFilterProviderTest.cs
@@ -0,0 +1,75 @@
+using System.Collections.Generic;
+using System.Linq;
+using System.Web.Http.Controllers;
+using Moq;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Http.Filters
+{
+ public class ActionDescriptorFilterProviderTest
+ {
+ private readonly ActionDescriptorFilterProvider _provider = new ActionDescriptorFilterProvider();
+ private static readonly HttpConfiguration _configuration = new HttpConfiguration();
+
+ [Fact]
+ public void GetFilters_IfConfigurationParameterIsNull_ThrowsException()
+ {
+ Assert.ThrowsArgumentNull(() =>
+ {
+ _provider.GetFilters(configuration: null, actionDescriptor: new Mock<HttpActionDescriptor>().Object);
+ }, "configuration");
+ }
+
+ [Fact]
+ public void GetFilters_IfActionDescriptorParameterIsNull_ThrowsException()
+ {
+ Assert.ThrowsArgumentNull(() =>
+ {
+ _provider.GetFilters(_configuration, actionDescriptor: null);
+ }, "actionDescriptor");
+ }
+
+ [Fact]
+ public void GetFilters_GetsFilterObjectsFromActionDescriptorAndItsControllerDescriptor()
+ {
+ // Arrange
+ Mock<HttpActionDescriptor> adMock = new Mock<HttpActionDescriptor>();
+ IFilter filter1 = new Mock<IFilter>().Object;
+ IFilter filter2 = new Mock<IFilter>().Object;
+ IFilter filter3 = new Mock<IFilter>().Object;
+ adMock.Setup(ad => ad.GetFilters()).Returns(new[] { filter1, filter2 }).Verifiable();
+
+ Mock<HttpControllerDescriptor> cdMock = new Mock<HttpControllerDescriptor>();
+ cdMock.Setup(cd => cd.GetFilters()).Returns(new[] { filter3 }).Verifiable();
+
+ HttpActionDescriptor actionDescriptor = adMock.Object;
+ actionDescriptor.ControllerDescriptor = cdMock.Object;
+
+ // Act
+ var result = _provider.GetFilters(_configuration, actionDescriptor).ToList();
+
+ // Assert
+ adMock.Verify();
+ cdMock.Verify();
+ Assert.Equal(3, result.Count);
+ Assert.Equal(new FilterInfo(filter3, FilterScope.Controller), result[0], new TestFilterInfoComparer());
+ Assert.Equal(new FilterInfo(filter1, FilterScope.Action), result[1], new TestFilterInfoComparer());
+ Assert.Equal(new FilterInfo(filter2, FilterScope.Action), result[2], new TestFilterInfoComparer());
+ }
+
+ public class TestFilterInfoComparer : IEqualityComparer<FilterInfo>
+ {
+ public bool Equals(FilterInfo x, FilterInfo y)
+ {
+ return (x == null && y == null) || (Object.ReferenceEquals(x.Instance, y.Instance) && x.Scope == y.Scope);
+ }
+
+ public int GetHashCode(FilterInfo obj)
+ {
+ return obj.GetHashCode();
+ }
+ }
+
+ }
+}
diff --git a/test/System.Web.Http.Test/Filters/ActionFilterAttributeTest.cs b/test/System.Web.Http.Test/Filters/ActionFilterAttributeTest.cs
new file mode 100644
index 00000000..5acbe7ca
--- /dev/null
+++ b/test/System.Web.Http.Test/Filters/ActionFilterAttributeTest.cs
@@ -0,0 +1,334 @@
+using System.Net.Http;
+using System.Threading;
+using System.Threading.Tasks;
+using System.Web.Http.Controllers;
+using System.Web.TestUtil;
+using Moq;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Http.Filters
+{
+ public class ActionFilterAttributeTest
+ {
+ [Fact]
+ public void AllowsMultiple_DefaultReturnsTrue()
+ {
+ ActionFilterAttribute actionFilter = new TestableActionFilter();
+
+ Assert.True(actionFilter.AllowMultiple);
+ }
+
+ [Fact]
+ public void ExecuteActionFilterAsync_IfContextParameterIsNull_ThrowsException()
+ {
+ var filter = new TestableActionFilter() as IActionFilter;
+ Assert.ThrowsArgumentNull(() =>
+ {
+ filter.ExecuteActionFilterAsync(actionContext: null, cancellationToken: CancellationToken.None, continuation: () => null);
+ }, "actionContext");
+ }
+
+ [Fact]
+ public void ExecuteActionFilterAsync_IfContinuationParameterIsNull_ThrowsException()
+ {
+ var filter = new TestableActionFilter() as IActionFilter;
+ Assert.ThrowsArgumentNull(() =>
+ {
+ filter.ExecuteActionFilterAsync(actionContext: ContextUtil.CreateActionContext(), cancellationToken: CancellationToken.None, continuation: null);
+ }, "continuation");
+ }
+
+ [Fact]
+ public void ExecuteActionFilterAsync_InvokesOnActionExecutingBeforeContinuation()
+ {
+ // Arrange
+ HttpActionContext context = ContextUtil.CreateActionContext();
+ Mock<ActionFilterAttribute> filterMock = new Mock<ActionFilterAttribute>() { CallBase = true };
+ bool onActionExecutingInvoked = false;
+ filterMock.Setup(f => f.OnActionExecuting(It.IsAny<HttpActionContext>())).Callback(() =>
+ {
+ onActionExecutingInvoked = true;
+ });
+ bool? flagWhenContinuationInvoked = null;
+ Func<Task<HttpResponseMessage>> continuation = () =>
+ {
+ flagWhenContinuationInvoked = onActionExecutingInvoked;
+ return TaskHelpers.FromResult(new HttpResponseMessage());
+ };
+ var filter = (IActionFilter)filterMock.Object;
+
+ // Act
+ filter.ExecuteActionFilterAsync(context, CancellationToken.None, continuation).Wait();
+ // Assert
+ Assert.True(flagWhenContinuationInvoked.Value);
+ }
+
+ [Fact]
+ public void ExecuteActionFilterAsync_OnActionExecutingMethodGetsPassedControllerContext()
+ {
+ // Arrange
+ HttpActionContext context = ContextUtil.CreateActionContext();
+ Mock<ActionFilterAttribute> filterMock = new Mock<ActionFilterAttribute>() { CallBase = false };
+ var filter = (IActionFilter)filterMock.Object;
+
+ // Act
+ filter.ExecuteActionFilterAsync(context, CancellationToken.None, () =>
+ {
+ return TaskHelpers.FromResult(new HttpResponseMessage());
+ }).Wait();
+
+ // Assert
+ filterMock.Verify(f => f.OnActionExecuting(context));
+ }
+
+ [Fact]
+ public void ExecuteActionFilterAsync_IfOnActionExecutingThrowsException_ReturnsFaultedTask()
+ {
+ // Arrange
+ Exception e = new Exception("{51C81EE9-F8D2-4F63-A1F8-B56052E0F2A4}");
+ HttpActionContext context = ContextUtil.CreateActionContext();
+ Mock<ActionFilterAttribute> filterMock = new Mock<ActionFilterAttribute>();
+ filterMock.Setup(f => f.OnActionExecuting(It.IsAny<HttpActionContext>())).Throws(e);
+ var filter = (IActionFilter)filterMock.Object;
+ bool continuationCalled = false;
+
+ // Act
+ var result = filter.ExecuteActionFilterAsync(context, CancellationToken.None, () =>
+ {
+ continuationCalled = true;
+ return null;
+ });
+
+ // Assert
+ result.WaitUntilCompleted();
+ Assert.True(result.IsFaulted);
+ Assert.Same(e, result.Exception.InnerException);
+ Assert.False(continuationCalled);
+ }
+
+ [Fact]
+ public void ExecuteActionFilterAsync_IfOnActionExecutingSetsResult_ShortCircuits()
+ {
+ // Arrange
+ HttpActionContext context = ContextUtil.CreateActionContext();
+ Mock<ActionFilterAttribute> filterMock = new Mock<ActionFilterAttribute>();
+ HttpResponseMessage response = new HttpResponseMessage();
+ filterMock.Setup(f => f.OnActionExecuting(It.IsAny<HttpActionContext>())).Callback<HttpActionContext>(c =>
+ {
+ c.Response = response;
+ });
+ bool continuationCalled = false;
+ var filter = (IActionFilter)filterMock.Object;
+
+ // Act
+ var result = filter.ExecuteActionFilterAsync(context, CancellationToken.None, () =>
+ {
+ continuationCalled = true;
+ return null;
+ }).Result;
+
+ // Assert
+ Assert.False(continuationCalled);
+ Assert.Same(response, result);
+ }
+
+ [Fact]
+ public void ExecuteActionFilterAsync_IfContinuationTaskWasCanceled_ReturnsCanceledTask()
+ {
+ // Arrange
+ HttpActionContext context = ContextUtil.CreateActionContext();
+ Mock<ActionFilterAttribute> filterMock = new Mock<ActionFilterAttribute>();
+ var filter = (IActionFilter)filterMock.Object;
+
+ // Act
+ var result = filter.ExecuteActionFilterAsync(context, CancellationToken.None, () => TaskHelpers.Canceled<HttpResponseMessage>());
+
+ // Assert
+ result.WaitUntilCompleted();
+ Assert.True(result.IsCanceled);
+ }
+
+ [Fact]
+ public void ExecuteActionFilterAsync_IfContinuationSucceeded_InvokesOnActionExecutedAsSuccess()
+ {
+ // Arrange
+ HttpActionContext context = ContextUtil.CreateActionContext();
+ Mock<ActionFilterAttribute> filterMock = new Mock<ActionFilterAttribute>();
+ var filter = (IActionFilter)filterMock.Object;
+ HttpResponseMessage response = new HttpResponseMessage();
+
+ // Act
+ var result = filter.ExecuteActionFilterAsync(context, CancellationToken.None, () => TaskHelpers.FromResult(response));
+
+ // Assert
+ result.WaitUntilCompleted();
+ filterMock.Verify(f => f.OnActionExecuted(It.Is<HttpActionExecutedContext>(ec =>
+ Object.ReferenceEquals(ec.Result, response)
+ && ec.Exception == null
+ && Object.ReferenceEquals(ec.ActionContext, context)
+ )));
+ }
+
+ [Fact]
+ public void ExecuteActionFilterAsync_IfContinuationFaulted_InvokesOnActionExecutedAsError()
+ {
+ // Arrange
+ HttpActionContext context = ContextUtil.CreateActionContext();
+ Mock<ActionFilterAttribute> filterMock = new Mock<ActionFilterAttribute>();
+ var filter = (IActionFilter)filterMock.Object;
+ Exception exception = new Exception("{ABCC912C-B6D1-4C27-9059-732ABC644A0C}");
+ Func<Task<HttpResponseMessage>> continuation = () => TaskHelpers.FromError<HttpResponseMessage>(new AggregateException(exception));
+
+ // Act
+ var result = filter.ExecuteActionFilterAsync(context, CancellationToken.None, continuation);
+
+ // Assert
+ result.WaitUntilCompleted();
+ filterMock.Verify(f => f.OnActionExecuted(It.Is<HttpActionExecutedContext>(ec =>
+ Object.ReferenceEquals(ec.Exception, exception)
+ && ec.Result == null
+ && Object.ReferenceEquals(ec.ActionContext, context)
+ )));
+ }
+
+ [Fact]
+ public void ExecuteActionFilterAsync_IfOnActionExecutedDoesNotHandleExceptionFromContinuation_ReturnsFaultedTask()
+ {
+ // Arrange
+ HttpActionContext context = ContextUtil.CreateActionContext();
+ Mock<ActionFilterAttribute> filterMock = new Mock<ActionFilterAttribute>();
+ var filter = (IActionFilter)filterMock.Object;
+ Exception exception = new Exception("{1EC330A2-33D0-4892-9335-2D833849D54E}");
+ filterMock.Setup(f => f.OnActionExecuted(It.IsAny<HttpActionExecutedContext>())).Callback<HttpActionExecutedContext>(ec =>
+ {
+ ec.Result = null;
+ });
+
+ // Act
+ var result = filter.ExecuteActionFilterAsync(context, CancellationToken.None, () => TaskHelpers.FromError<HttpResponseMessage>(exception));
+
+ // Assert
+ result.WaitUntilCompleted();
+ Assert.True(result.IsFaulted);
+ Assert.Same(exception, result.Exception.InnerException);
+ }
+
+ [Fact]
+ public void ExecuteActionFilterAsync_IfOnActionExecutedDoesHandleExceptionFromContinuation_ReturnsSuccessfulTask()
+ {
+ // Arrange
+ HttpActionContext context = ContextUtil.CreateActionContext();
+ Mock<ActionFilterAttribute> filterMock = new Mock<ActionFilterAttribute>();
+ var filter = (IActionFilter)filterMock.Object;
+ HttpResponseMessage newResponse = new HttpResponseMessage();
+ filterMock.Setup(f => f.OnActionExecuted(It.IsAny<HttpActionExecutedContext>())).Callback<HttpActionExecutedContext>(ec =>
+ {
+ ec.Result = newResponse;
+ });
+
+ // Act
+ var result = filter.ExecuteActionFilterAsync(context, CancellationToken.None, () => TaskHelpers.FromError<HttpResponseMessage>(new Exception("{ED525C8E-7165-4207-B3F6-4AB095739017}")));
+
+ // Assert
+ result.WaitUntilCompleted();
+ Assert.True(result.IsCompleted);
+ Assert.Same(newResponse, result.Result);
+ }
+
+ [Fact]
+ public void ExecuteActionFilterAsync_IfOnActionExecutedThrowsException_ReturnsFaultedTask()
+ {
+ // Arrange
+ HttpActionContext context = ContextUtil.CreateActionContext();
+ Mock<ActionFilterAttribute> filterMock = new Mock<ActionFilterAttribute>();
+ var filter = (IActionFilter)filterMock.Object;
+ Exception exception = new Exception("{AC32AD02-36A7-45E5-8955-76A4E3B461C6}");
+ filterMock.Setup(f => f.OnActionExecuted(It.IsAny<HttpActionExecutedContext>())).Callback<HttpActionExecutedContext>(ec =>
+ {
+ throw exception;
+ });
+
+ // Act
+ var result = filter.ExecuteActionFilterAsync(context, CancellationToken.None, () => TaskHelpers.FromResult(new HttpResponseMessage()));
+
+ // Assert
+ result.WaitUntilCompleted();
+ Assert.True(result.IsFaulted);
+ Assert.Same(exception, result.Exception.InnerException);
+ }
+
+ [Fact]
+ public void ExecuteActionFilterAsync_IfOnActionExecutedSetsResult_ReturnsNewResult()
+ {
+ // Arrange
+ HttpActionContext context = ContextUtil.CreateActionContext();
+ Mock<ActionFilterAttribute> filterMock = new Mock<ActionFilterAttribute>();
+ var filter = (IActionFilter)filterMock.Object;
+ HttpResponseMessage newResponse = new HttpResponseMessage();
+ filterMock.Setup(f => f.OnActionExecuted(It.IsAny<HttpActionExecutedContext>())).Callback<HttpActionExecutedContext>(ec =>
+ {
+ ec.Result = newResponse;
+ });
+
+ // Act
+ var result = filter.ExecuteActionFilterAsync(context, CancellationToken.None, () => TaskHelpers.FromResult(new HttpResponseMessage()));
+
+ // Assert
+ result.WaitUntilCompleted();
+ Assert.True(result.IsCompleted);
+ Assert.Same(newResponse, result.Result);
+ }
+
+ [Fact]
+ public void ExecuteActionFilterAsync_IfOnActionExecutedDoesNotChangeResult_ReturnsSameResult()
+ {
+ // Arrange
+ HttpActionContext context = ContextUtil.CreateActionContext();
+ Mock<ActionFilterAttribute> filterMock = new Mock<ActionFilterAttribute>();
+ var filter = (IActionFilter)filterMock.Object;
+ HttpResponseMessage response = new HttpResponseMessage();
+ filterMock.Setup(f => f.OnActionExecuted(It.IsAny<HttpActionExecutedContext>())).Callback<HttpActionExecutedContext>(ec =>
+ {
+ ec.Result = ec.Result;
+ });
+
+ // Act
+ var result = filter.ExecuteActionFilterAsync(context, CancellationToken.None, () => TaskHelpers.FromResult(response));
+
+ // Assert
+ result.WaitUntilCompleted();
+ Assert.True(result.IsCompleted);
+ Assert.Same(response, result.Result);
+ }
+
+ [Fact]
+ public void ExecuteActionFilterAsync_IfOnActionExecutedRemovesSuccessfulResult_ReturnsFaultedTask()
+ {
+ // Arrange
+ HttpActionContext context = ContextUtil.CreateActionContext();
+ Mock<ActionFilterAttribute> filterMock = new Mock<ActionFilterAttribute>();
+ var filter = (IActionFilter)filterMock.Object;
+ HttpResponseMessage response = new HttpResponseMessage();
+ filterMock.Setup(f => f.OnActionExecuted(It.IsAny<HttpActionExecutedContext>())).Callback<HttpActionExecutedContext>(ec =>
+ {
+ ec.Result = null;
+ });
+
+ // Act
+ var result = filter.ExecuteActionFilterAsync(context, CancellationToken.None, () => TaskHelpers.FromResult(response));
+
+ // Assert
+ result.WaitUntilCompleted();
+ Assert.Equal(TaskStatus.Faulted, result.Status);
+ Assert.IsException<InvalidOperationException>(
+ exception: result.Exception,
+ expectedMessage: "After calling ActionFilterAttributeProxy.OnActionExecuted, the HttpActionExecutedContext properties Result and Exception were both null. At least one of these values must be non-null. To provide a new response, please set the Result object; to indicate an error, please throw an exception."
+ );
+ }
+
+ public class TestableActionFilter : ActionFilterAttribute
+ {
+ }
+ }
+}
diff --git a/test/System.Web.Http.Test/Filters/AuthorizationFilterAttributeTest.cs b/test/System.Web.Http.Test/Filters/AuthorizationFilterAttributeTest.cs
new file mode 100644
index 00000000..d49c4e74
--- /dev/null
+++ b/test/System.Web.Http.Test/Filters/AuthorizationFilterAttributeTest.cs
@@ -0,0 +1,189 @@
+using System.Net.Http;
+using System.Threading;
+using System.Threading.Tasks;
+using System.Web.Http.Controllers;
+using Moq;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Http.Filters
+{
+ public class AuthorizationFilterAttributeTest
+ {
+ [Fact]
+ public void AllowsMultiple_DefaultReturnsTrue()
+ {
+ AuthorizationFilterAttribute actionFilter = new TestableAuthorizationFilter();
+
+ Assert.True(actionFilter.AllowMultiple);
+ }
+
+ [Fact]
+ public void ExecuteAuthorizationFilterAsync_IfContextParameterIsNull_ThrowsException()
+ {
+ var filter = new TestableAuthorizationFilter() as IAuthorizationFilter;
+ Assert.ThrowsArgumentNull(() =>
+ {
+ filter.ExecuteAuthorizationFilterAsync(actionContext: null, cancellationToken: CancellationToken.None, continuation: () => null);
+ }, "actionContext");
+ }
+
+ [Fact]
+ public void ExecuteAuthorizationFilterAsync_IfContinuationParameterIsNull_ThrowsException()
+ {
+ var filter = new TestableAuthorizationFilter() as IAuthorizationFilter;
+ Assert.ThrowsArgumentNull(() =>
+ {
+ filter.ExecuteAuthorizationFilterAsync(actionContext: ContextUtil.CreateActionContext(), cancellationToken: CancellationToken.None, continuation: null);
+ }, "continuation");
+ }
+
+ [Fact]
+ public void ExecuteAuthorizationFilterAsync_InvokesOnActionExecutingBeforeContinuation()
+ {
+ // Arrange
+ HttpActionContext context = ContextUtil.CreateActionContext();
+ Mock<AuthorizationFilterAttribute> filterMock = new Mock<AuthorizationFilterAttribute>() { CallBase = true };
+ bool onActionExecutingInvoked = false;
+ filterMock.Setup(f => f.OnAuthorization(It.IsAny<HttpActionContext>())).Callback(() =>
+ {
+ onActionExecutingInvoked = true;
+ });
+ bool? flagWhenContinuationInvoked = null;
+ Func<Task<HttpResponseMessage>> continuation = () =>
+ {
+ flagWhenContinuationInvoked = onActionExecutingInvoked;
+ return TaskHelpers.FromResult(new HttpResponseMessage());
+ };
+ var filter = (IAuthorizationFilter)filterMock.Object;
+
+ // Act
+ filter.ExecuteAuthorizationFilterAsync(context, CancellationToken.None, continuation).Wait();
+
+ // Assert
+ Assert.True(flagWhenContinuationInvoked.Value);
+ }
+
+ public void ExecuteAuthorizationFilterAsync_IfOnActionExecutingSetsResult_ShortCircuits()
+ {
+ // Arrange
+ HttpActionContext context = ContextUtil.CreateActionContext();
+ Mock<AuthorizationFilterAttribute> filterMock = new Mock<AuthorizationFilterAttribute>();
+ HttpResponseMessage response = new HttpResponseMessage();
+ filterMock.Setup(f => f.OnAuthorization(It.IsAny<HttpActionContext>())).Callback<HttpActionContext>(c =>
+ {
+ c.Response = response;
+ });
+ bool continuationCalled = false;
+ var filter = (IAuthorizationFilter)filterMock.Object;
+
+ // Act
+ var result = filter.ExecuteAuthorizationFilterAsync(context, CancellationToken.None, () =>
+ {
+ continuationCalled = true;
+ return null;
+ }).Result;
+
+ // Assert
+ Assert.False(continuationCalled);
+ Assert.Same(response, result);
+ }
+
+ [Fact]
+ public void ExecuteAuthorizationFilterAsync_IfOnActionExecutingThrowsException_ReturnsFaultedTask()
+ {
+ // Arrange
+ Exception e = new Exception();
+ HttpActionContext context = ContextUtil.CreateActionContext();
+ Mock<AuthorizationFilterAttribute> filterMock = new Mock<AuthorizationFilterAttribute>();
+ filterMock.Setup(f => f.OnAuthorization(It.IsAny<HttpActionContext>())).Throws(e);
+ var filter = (IAuthorizationFilter)filterMock.Object;
+ bool continuationCalled = false;
+
+ // Act
+ var result = filter.ExecuteAuthorizationFilterAsync(context, CancellationToken.None, () =>
+ {
+ continuationCalled = true;
+ return null;
+ });
+
+ // Assert
+ result.WaitUntilCompleted();
+ Assert.True(result.IsFaulted);
+ Assert.Same(e, result.Exception.InnerException);
+ Assert.False(continuationCalled);
+ }
+
+ [Fact]
+ public void ExecuteAuthorizationFilterAsync_OnActionExecutingMethodGetsPassedControllerContext()
+ {
+ // Arrange
+ HttpActionContext context = ContextUtil.CreateActionContext();
+ Mock<AuthorizationFilterAttribute> filterMock = new Mock<AuthorizationFilterAttribute>() { CallBase = false };
+ var filter = (IAuthorizationFilter)filterMock.Object;
+
+ // Act
+ filter.ExecuteAuthorizationFilterAsync(context, CancellationToken.None, () =>
+ {
+ return TaskHelpers.FromResult(new HttpResponseMessage());
+ }).Wait();
+
+ // Assert
+ filterMock.Verify(f => f.OnAuthorization(context));
+ }
+
+ [Fact]
+ public void ExecuteAuthorizationFilterAsync_IfContinuationTaskWasCanceled_ReturnsCanceledTask()
+ {
+ // Arrange
+ HttpActionContext context = ContextUtil.CreateActionContext();
+ Mock<AuthorizationFilterAttribute> filterMock = new Mock<AuthorizationFilterAttribute>();
+ var filter = (IAuthorizationFilter)filterMock.Object;
+
+ // Act
+ var result = filter.ExecuteAuthorizationFilterAsync(context, CancellationToken.None, () => TaskHelpers.Canceled<HttpResponseMessage>());
+
+ // Assert
+ result.WaitUntilCompleted();
+ Assert.True(result.IsCanceled);
+ }
+
+ [Fact]
+ public void ExecuteAuthorizationFilterAsync_IfContinuationSucceeded_ReturnsSuccessTask()
+ {
+ // Arrange
+ HttpActionContext context = ContextUtil.CreateActionContext();
+ Mock<AuthorizationFilterAttribute> filterMock = new Mock<AuthorizationFilterAttribute>();
+ var filter = (IAuthorizationFilter)filterMock.Object;
+ HttpResponseMessage response = new HttpResponseMessage();
+
+ // Act
+ var result = filter.ExecuteAuthorizationFilterAsync(context, CancellationToken.None, () => TaskHelpers.FromResult(response));
+
+ // Assert
+ result.WaitUntilCompleted();
+ Assert.Same(response, result.Result);
+ }
+
+ [Fact]
+ public void ExecuteAuthorizationFilterAsync_IfContinuationFaulted_ReturnsFaultedTask()
+ {
+ // Arrange
+ HttpActionContext context = ContextUtil.CreateActionContext();
+ Mock<AuthorizationFilterAttribute> filterMock = new Mock<AuthorizationFilterAttribute>();
+ var filter = (IAuthorizationFilter)filterMock.Object;
+ Exception exception = new Exception();
+
+ // Act
+ var result = filter.ExecuteAuthorizationFilterAsync(context, CancellationToken.None, () => TaskHelpers.FromError<HttpResponseMessage>(exception));
+
+ // Assert
+ result.WaitUntilCompleted();
+ Assert.Same(exception, result.Exception.InnerException);
+ }
+ }
+
+ public class TestableAuthorizationFilter : AuthorizationFilterAttribute
+ {
+ }
+}
diff --git a/test/System.Web.Http.Test/Filters/ConfigurationFilterProviderTest.cs b/test/System.Web.Http.Test/Filters/ConfigurationFilterProviderTest.cs
new file mode 100644
index 00000000..3f40af29
--- /dev/null
+++ b/test/System.Web.Http.Test/Filters/ConfigurationFilterProviderTest.cs
@@ -0,0 +1,34 @@
+using System.Linq;
+using Moq;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Http.Filters
+{
+ public class ConfigurationFilterProviderTest
+ {
+ private readonly ConfigurationFilterProvider provider = new ConfigurationFilterProvider();
+
+ [Fact]
+ public void GetFilters_IfContextParameterIsNull_ThrowsException()
+ {
+ Assert.ThrowsArgumentNull(() =>
+ {
+ provider.GetFilters(configuration: null, actionDescriptor: null);
+ }, "configuration");
+ }
+
+ [Fact]
+ public void GetFilters_ReturnsFiltersFromConfiguration()
+ {
+ var config = new HttpConfiguration();
+ IFilter filter1 = new Mock<IFilter>().Object;
+ config.Filters.Add(filter1);
+
+ var result = provider.GetFilters(config, actionDescriptor: null);
+
+ Assert.True(result.All(f => f.Scope == FilterScope.Global));
+ Assert.Same(filter1, result.ToArray()[0].Instance);
+ }
+ }
+}
diff --git a/test/System.Web.Http.Test/Filters/EnumerableEvaluatorFilterProviderTest.cs b/test/System.Web.Http.Test/Filters/EnumerableEvaluatorFilterProviderTest.cs
new file mode 100644
index 00000000..e0997ba5
--- /dev/null
+++ b/test/System.Web.Http.Test/Filters/EnumerableEvaluatorFilterProviderTest.cs
@@ -0,0 +1,82 @@
+using System.Collections;
+using System.Collections.Generic;
+using System.Linq;
+using System.Net.Http;
+using System.Threading.Tasks;
+using System.Web.Http.Controllers;
+using Moq;
+using Xunit;
+using Xunit.Extensions;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Http.Filters
+{
+ public class EnumerableEvaluatorFilterProviderTest
+ {
+ private HttpConfiguration _configuration = new HttpConfiguration();
+ private Mock<HttpActionDescriptor> _actionDescriptorMock = new Mock<HttpActionDescriptor>();
+ private EnumerableEvaluatorFilterProvider _filterProvider = new EnumerableEvaluatorFilterProvider();
+
+ [Fact]
+ public void GetFilters_WhenConfigurationParameterIsNull_ThrowsException()
+ {
+ Assert.ThrowsArgumentNull(() =>
+ {
+ _filterProvider.GetFilters(configuration: null, actionDescriptor: _actionDescriptorMock.Object);
+ }, "configuration");
+ }
+
+ [Fact]
+ public void GetFilters_WhenActionDescriptorParameterIsNull_ThrowsException()
+ {
+ Assert.ThrowsArgumentNull(() =>
+ {
+ _filterProvider.GetFilters(_configuration, actionDescriptor: null);
+ }, "actionDescriptor");
+ }
+
+ [Theory]
+ [InlineData(typeof(IEnumerable<string>))]
+ [InlineData(typeof(IEnumerable<object>))]
+ [InlineData(typeof(IQueryable<string>))]
+ //[InlineData(typeof(HttpResponseMessage))] // static signature problems
+ //[InlineData(typeof(Task<HttpResponseMessage>))] // static signature problems
+ [InlineData(typeof(ObjectContent<IEnumerable<string>>))]
+ [InlineData(typeof(Task<ObjectContent<IEnumerable<string>>>))]
+ public void GetFilters_IfActionResultTypeIsSupported_ReturnsFilterInstance(Type actionReturnType)
+ {
+ // Arrange
+ _actionDescriptorMock.Setup(ad => ad.ReturnType).Returns(actionReturnType);
+
+ // Act
+ IEnumerable<FilterInfo> result = _filterProvider.GetFilters(_configuration, _actionDescriptorMock.Object);
+
+ // Assert
+ FilterInfo filter = result.Single();
+ Assert.NotNull(filter);
+ Assert.IsType<EnumerableEvaluatorFilter>(filter.Instance);
+ Assert.Equal(FilterScope.First, filter.Scope);
+ }
+
+ [Theory]
+ [InlineData(typeof(Object))]
+ [InlineData(typeof(String))]
+ [InlineData(typeof(Int32))]
+ [InlineData(typeof(object[]))]
+ [InlineData(typeof(List<string>))]
+ [InlineData(typeof(IList<string>))]
+ [InlineData(typeof(IEnumerable))]
+ [InlineData(typeof(IQueryable))]
+ public void GetFilters_IfActionResultTypeIsNotSupported_ReturnsEmptyResult(Type actionReturnType)
+ {
+ // Arrange
+ _actionDescriptorMock.Setup(ad => ad.ReturnType).Returns(actionReturnType);
+
+ // Act
+ IEnumerable<FilterInfo> result = _filterProvider.GetFilters(_configuration, _actionDescriptorMock.Object);
+
+ // Assert
+ Assert.Empty(result);
+ }
+ }
+}
diff --git a/test/System.Web.Http.Test/Filters/EnumerableEvaluatorFilterTest.cs b/test/System.Web.Http.Test/Filters/EnumerableEvaluatorFilterTest.cs
new file mode 100644
index 00000000..8f056ceb
--- /dev/null
+++ b/test/System.Web.Http.Test/Filters/EnumerableEvaluatorFilterTest.cs
@@ -0,0 +1,191 @@
+using System.Collections;
+using System.Collections.Generic;
+using System.Linq;
+using System.Net.Http;
+using System.Threading.Tasks;
+using System.Web.Http.Controllers;
+using Microsoft.TestCommon;
+using Moq;
+using Xunit;
+using Xunit.Extensions;
+using Assert = Microsoft.TestCommon.AssertEx;
+using System.Net.Http.Formatting;
+
+namespace System.Web.Http.Filters
+{
+ public class EnumerableEvaluatorFilterTest
+ {
+ private EnumerableEvaluatorFilter _filter = new EnumerableEvaluatorFilter();
+ private HttpActionExecutedContext _actionExecutedContext;
+ private Mock<HttpActionDescriptor> _actionDescriptorMock;
+
+ public EnumerableEvaluatorFilterTest()
+ {
+ _actionDescriptorMock = new Mock<HttpActionDescriptor>();
+ var actionContext = new HttpActionContext { ActionDescriptor = _actionDescriptorMock.Object };
+ _actionExecutedContext = new HttpActionExecutedContext(actionContext, exception: null);
+ }
+
+ [Fact]
+ public void AllowMultiple_ReturnsFalse()
+ {
+ Assert.False(_filter.AllowMultiple);
+ }
+
+ [Fact]
+ public void Instance_IsSingletonProperty()
+ {
+ var first = EnumerableEvaluatorFilter.Instance;
+ Assert.NotNull(first);
+ var second = EnumerableEvaluatorFilter.Instance;
+ Assert.Same(first, second);
+ }
+
+ [Fact]
+ public void OnActionExecuted_IfContextParameterIsNull_ThrowsException()
+ {
+ Assert.ThrowsArgumentNull(() =>
+ {
+ _filter.OnActionExecuted(actionExecutedContext: null);
+ }, "actionExecutedContext");
+ }
+
+ [Fact]
+ public void OnActionExecuted_IfActionContentTypeIsNotIEnumerable_ButResponseContentIsAnIEnumerable_DoesNothing()
+ {
+ // Arrange
+ _actionDescriptorMock.Setup(ad => ad.ReturnType).Returns(typeof(object));
+ _actionExecutedContext.Result = new HttpResponseMessage() { Content = new ObjectContent<IEnumerable<string>>(new List<string>(), new JsonMediaTypeFormatter()) };
+ var content = _actionExecutedContext.Result.Content;
+
+ // Act
+ _filter.OnActionExecuted(_actionExecutedContext);
+
+ // Assert
+ Assert.Same(content, _actionExecutedContext.Result.Content);
+ }
+
+ [Fact]
+ public void OnActionExecuted_IfActionContentTypeIsIEnumerable_ButResponseContentIsNull_DoesNothing()
+ {
+ // Arrange
+ _actionDescriptorMock.Setup(ad => ad.ReturnType).Returns(typeof(IEnumerable<string>));
+ _actionExecutedContext.Result = new HttpResponseMessage() { Content = new ObjectContent<IEnumerable<string>>(null, new JsonMediaTypeFormatter()) };
+ var content = _actionExecutedContext.Result.Content;
+
+ // Act
+ _filter.OnActionExecuted(_actionExecutedContext);
+
+ // Assert
+ Assert.Same(content, _actionExecutedContext.Result.Content);
+ }
+
+ [Theory]
+ [PropertyData("NotSupportedTypesTestData")]
+ public void OnActionExecuted_IfActionContentTypeIsNotIEnumerable_DoesNothing(Type actionReturnType, object input)
+ {
+ // Arrange
+ _actionDescriptorMock.Setup(ad => ad.ReturnType).Returns(actionReturnType);
+ _actionExecutedContext.Result = new HttpResponseMessage() { Content = new ObjectContent<object>(input, new JsonMediaTypeFormatter()) };
+ var content = _actionExecutedContext.Result.Content;
+
+ // Act
+ _filter.OnActionExecuted(_actionExecutedContext);
+
+ // Assert
+ object output;
+ Assert.True(_actionExecutedContext.Result.TryGetObjectValue(out output));
+ Assert.Same(input, output);
+ Assert.Same(content, _actionExecutedContext.Result.Content);
+ }
+
+ public static TheoryDataSet<Type, object> NotSupportedTypesTestData
+ {
+ get
+ {
+ return new TheoryDataSet<Type, object>
+ {
+ {typeof(int), 42},
+ {typeof(object), new object()},
+ {typeof(IEnumerable), new ArrayList()},
+ {typeof(IQueryable), new List<string>().AsQueryable()},
+ {typeof(string), "some value"},
+ {typeof(byte[]), new byte[3]},
+ {typeof(string[]), new string[3]},
+ {typeof(List<string>), new List<string>()},
+ };
+ }
+ }
+
+ [Theory]
+ [InlineData(typeof(IEnumerable<string>))]
+ //[InlineData(typeof(HttpResponseMessage))] // static signature problems
+ // [InlineData(typeof(Task<HttpResponseMessage>))] // static signature problems
+ [InlineData(typeof(ObjectContent<IEnumerable<string>>))]
+ [InlineData(typeof(Task<ObjectContent<IEnumerable<string>>>))]
+ public void OnActionExecuted_IfActionContentTypeIsIEnumerable_AndResponseContentTypeMatches_CopiesContentToNewList(Type actionReturnType)
+ {
+ // Arrange
+ _actionDescriptorMock.Setup(ad => ad.ReturnType).Returns(actionReturnType);
+ List<string> input = Enumerable.Range(0, 3).Select(i => "Item " + i).ToList();
+ _actionExecutedContext.Result = new HttpResponseMessage() { Content = new ObjectContent<List<string>>(input, new JsonMediaTypeFormatter()) };
+
+ // Act
+ _filter.OnActionExecuted(_actionExecutedContext);
+
+ // Assert
+ object output;
+ Assert.True(_actionExecutedContext.Result.TryGetObjectValue(out output));
+ Assert.NotSame(input, output);
+ Assert.IsType<List<string>>(output);
+ }
+
+ [Fact]
+ public void OnActionExecuted_IfActionContentTypeIsIEnumerable_ButResponseContentTypeIsDifferentIEnumerable_DoesNothing()
+ {
+ // Arrange
+ _actionDescriptorMock.Setup(ad => ad.ReturnType).Returns(typeof(IEnumerable<string>));
+ List<int> input = Enumerable.Range(0, 3).ToList();
+ _actionExecutedContext.Result = new HttpResponseMessage() { Content = new ObjectContent<List<int>>(input, new JsonMediaTypeFormatter()) };
+
+ // Act
+ _filter.OnActionExecuted(_actionExecutedContext);
+
+ // Assert
+ List<int> output;
+ Assert.True(_actionExecutedContext.Result.TryGetObjectValue(out output));
+ Assert.Same(input, output);
+ }
+
+ [Fact]
+ public void OnActionExecuted_IfActionContentTypeIsIQueryable_CopiesContentToNewResult()
+ {
+ // Arrange
+ _actionDescriptorMock.Setup(ad => ad.ReturnType).Returns(typeof(IQueryable<string>));
+ IQueryable<string> input = Enumerable.Range(0, 3).Select(i => "Item " + i).AsQueryable();
+ _actionExecutedContext.Result = new HttpResponseMessage() { Content = new ObjectContent<IQueryable<string>>(input, new JsonMediaTypeFormatter()) };
+
+ // Act
+ _filter.OnActionExecuted(_actionExecutedContext);
+
+ // Assert
+ object output;
+ Assert.True(_actionExecutedContext.Result.TryGetObjectValue(out output));
+ Assert.NotSame(input, output);
+ }
+
+ [Fact]
+ public void OnActionExecuted_IfResponseIsNull_DoesNothing()
+ {
+ // Arrange
+ _actionDescriptorMock.Setup(ad => ad.ReturnType).Returns(typeof(IEnumerable<string>));
+ _actionExecutedContext.Result = null;
+
+ // Act
+ _filter.OnActionExecuted(_actionExecutedContext);
+
+ // Assert
+ Assert.Null(_actionExecutedContext.Result);
+ }
+ }
+}
diff --git a/test/System.Web.Http.Test/Filters/ExceptionFilterAttributeTest.cs b/test/System.Web.Http.Test/Filters/ExceptionFilterAttributeTest.cs
new file mode 100644
index 00000000..62371c13
--- /dev/null
+++ b/test/System.Web.Http.Test/Filters/ExceptionFilterAttributeTest.cs
@@ -0,0 +1,82 @@
+using System.Threading;
+using System.Threading.Tasks;
+using Moq;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Http.Filters
+{
+ public class ExceptionFilterAttributeTest
+ {
+ private readonly HttpActionExecutedContext _context = new HttpActionExecutedContext(ContextUtil.CreateActionContext(), new Exception());
+
+ [Fact]
+ public void AllowsMultiple_DefaultReturnsTrue()
+ {
+ ExceptionFilterAttribute actionFilter = new TestableExceptionFilter();
+
+ Assert.True(actionFilter.AllowMultiple);
+ }
+
+ [Fact]
+ public void ExecuteExceptionFilterAsync_IfContextParameterIsNull_ThrowsException()
+ {
+ IExceptionFilter filter = new Mock<ExceptionFilterAttribute>().Object;
+
+ Assert.ThrowsArgumentNull(() =>
+ {
+ filter.ExecuteExceptionFilterAsync(actionExecutedContext: null, cancellationToken: CancellationToken.None);
+ }, "actionExecutedContext");
+ }
+
+ [Fact]
+ public void ExecuteExceptionFilterAsync_IfOnExceptionThrowsException_RethrowsTheSameException()
+ {
+ // Arrange
+ var mockFilter = new Mock<ExceptionFilterAttribute>();
+ Exception exception = new Exception();
+ mockFilter.Setup(f => f.OnException(_context)).Throws(exception);
+ IExceptionFilter filter = mockFilter.Object;
+
+ // Act & Assert
+ var thrownException = Assert.Throws<Exception>(() =>
+ {
+ filter.ExecuteExceptionFilterAsync(_context, CancellationToken.None);
+ });
+ Assert.Same(exception, thrownException);
+ }
+
+ [Fact]
+ public void ExecuteExceptionFilterAsync_InvokesOnExceptionMethod()
+ {
+ // Arrange
+ var mockFilter = new Mock<ExceptionFilterAttribute>();
+ IExceptionFilter filter = mockFilter.Object;
+
+ // Act
+ filter.ExecuteExceptionFilterAsync(_context, CancellationToken.None);
+
+ // Assert
+ mockFilter.Verify(f => f.OnException(_context));
+ }
+
+ [Fact]
+ public void ExecuteExceptionFilterAsync_ReturnsCompletedTask()
+ {
+ // Arrange
+ var mockFilter = new Mock<ExceptionFilterAttribute>();
+ IExceptionFilter filter = mockFilter.Object;
+
+ // Act
+ var result = filter.ExecuteExceptionFilterAsync(_context, CancellationToken.None);
+
+ // Assert
+ Assert.True(result.Status == TaskStatus.RanToCompletion);
+ }
+
+ public sealed class TestableExceptionFilter : ExceptionFilterAttribute
+ {
+
+ }
+ }
+}
diff --git a/test/System.Web.Http.Test/Filters/FilterAttributeTest.cs b/test/System.Web.Http.Test/Filters/FilterAttributeTest.cs
new file mode 100644
index 00000000..909b3478
--- /dev/null
+++ b/test/System.Web.Http.Test/Filters/FilterAttributeTest.cs
@@ -0,0 +1,34 @@
+using Xunit;
+using Xunit.Extensions;
+
+namespace System.Web.Http.Filters
+{
+ [CLSCompliant(false)]
+ public class FilterAttributeTest
+ {
+ [Theory]
+ [InlineData(typeof(UniqueFilterAttribute), false)]
+ [InlineData(typeof(MultiFilterAttribute), true)]
+ [InlineData(typeof(DefaultFilterAttribute), true)]
+ public void AllowMultiple(Type filterType, bool expectedAllowsMultiple)
+ {
+ var attribute = (FilterAttribute)Activator.CreateInstance(filterType);
+
+ Assert.Equal(expectedAllowsMultiple, attribute.AllowMultiple);
+ }
+
+ [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = true, AllowMultiple = false)]
+ public sealed class UniqueFilterAttribute : FilterAttribute
+ {
+ }
+
+ [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = true, AllowMultiple = true)]
+ public sealed class MultiFilterAttribute : FilterAttribute
+ {
+ }
+
+ public sealed class DefaultFilterAttribute : FilterAttribute
+ {
+ }
+ }
+}
diff --git a/test/System.Web.Http.Test/Filters/FilterInfoComparerTest.cs b/test/System.Web.Http.Test/Filters/FilterInfoComparerTest.cs
new file mode 100644
index 00000000..53828ef3
--- /dev/null
+++ b/test/System.Web.Http.Test/Filters/FilterInfoComparerTest.cs
@@ -0,0 +1,37 @@
+using System.Collections.Generic;
+using Microsoft.TestCommon;
+using Moq;
+using Xunit;
+using Xunit.Extensions;
+
+namespace System.Web.Http.Filters
+{
+ public class FilterInfoComparerTest
+ {
+ [Theory]
+ [PropertyData("CompareTestData")]
+ public void Compare(FilterInfo x, FilterInfo y, int expectedSign)
+ {
+ int result = FilterInfoComparer.Instance.Compare(x, y);
+
+ Assert.Equal(expectedSign, Math.Sign(result));
+ }
+
+ public static IEnumerable<object[]> CompareTestData
+ {
+ get
+ {
+ IFilter f = new Mock<IFilter>().Object;
+ return new TheoryDataSet<FilterInfo, FilterInfo, int>()
+ {
+ { null, null, 0 },
+ { new FilterInfo(f, FilterScope.Action), null, 1 },
+ { null, new FilterInfo(f, FilterScope.Action), -1 },
+ { new FilterInfo(f, FilterScope.Action), new FilterInfo(f, FilterScope.Action), 0 },
+ { new FilterInfo(f, FilterScope.First), new FilterInfo(f, FilterScope.Action), -1 },
+ { new FilterInfo(f, FilterScope.Action), new FilterInfo(f, FilterScope.First), 1 },
+ };
+ }
+ }
+ }
+}
diff --git a/test/System.Web.Http.Test/Filters/FilterInfoTest.cs b/test/System.Web.Http.Test/Filters/FilterInfoTest.cs
new file mode 100644
index 00000000..d364aa5c
--- /dev/null
+++ b/test/System.Web.Http.Test/Filters/FilterInfoTest.cs
@@ -0,0 +1,29 @@
+using Moq;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Http.Filters
+{
+ public class FilterInfoTest
+ {
+ [Fact]
+ public void Constructor()
+ {
+ var filterInstance = new Mock<IFilter>().Object;
+
+ FilterInfo filter = new FilterInfo(filterInstance, FilterScope.Controller);
+
+ Assert.Equal(FilterScope.Controller, filter.Scope);
+ Assert.Same(filterInstance, filter.Instance);
+ }
+
+ [Fact]
+ public void Constructor_IfInstanceParameterIsNull_ThrowsException()
+ {
+ Assert.ThrowsArgumentNull(() =>
+ {
+ new FilterInfo(instance: null, scope: FilterScope.Controller);
+ }, "instance");
+ }
+ }
+}
diff --git a/test/System.Web.Http.Test/Filters/HttpActionExecutedContextTest.cs b/test/System.Web.Http.Test/Filters/HttpActionExecutedContextTest.cs
new file mode 100644
index 00000000..a59a9440
--- /dev/null
+++ b/test/System.Web.Http.Test/Filters/HttpActionExecutedContextTest.cs
@@ -0,0 +1,88 @@
+using System.Net.Http;
+using System.Web.Http.Controllers;
+using System.Web.TestUtil;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Http.Filters
+{
+ public class HttpActionExecutedContextTest
+ {
+ [Fact]
+ public void Default_Constructor()
+ {
+ HttpActionExecutedContext actionExecutedContext = new HttpActionExecutedContext();
+
+ Assert.Null(actionExecutedContext.ActionContext);
+ Assert.Null(actionExecutedContext.Exception);
+ Assert.Null(actionExecutedContext.Request);
+ Assert.Null(actionExecutedContext.Result);
+ }
+
+ [Fact]
+ public void Parameter_Constructor()
+ {
+ HttpActionContext context = ContextUtil.CreateActionContext();
+ Exception exception = new Exception();
+
+ var actionContext = new HttpActionExecutedContext(context, exception);
+
+ Assert.Same(context, actionContext.ActionContext);
+ Assert.Same(exception, actionContext.Exception);
+ Assert.Same(context.ControllerContext.Request, actionContext.Request);
+ Assert.Null(actionContext.Result);
+ }
+
+ [Fact]
+ public void Constructor_AllowsNullExceptionParameter()
+ {
+ HttpActionContext context = ContextUtil.CreateActionContext();
+
+ var actionContext = new HttpActionExecutedContext(context, exception: null);
+
+ Assert.Null(actionContext.Exception);
+ }
+
+ [Fact]
+ public void Constructor_IfContextParameterIsNull_ThrowsException()
+ {
+ Assert.ThrowsArgumentNull(() =>
+ {
+ new HttpActionExecutedContext(actionContext: null, exception: null);
+ }, "actionContext");
+ }
+
+ [Fact]
+ public void ActionContext_Property()
+ {
+ Assert.Reflection.Property<HttpActionExecutedContext, HttpActionContext>(
+ instance: new HttpActionExecutedContext(),
+ propertyGetter: aec => aec.ActionContext,
+ expectedDefaultValue: null,
+ allowNull: false,
+ roundTripTestValue: ContextUtil.CreateActionContext());
+ }
+
+ [Fact]
+ public void Exception_Property()
+ {
+ Assert.Reflection.Property<HttpActionExecutedContext, Exception>(
+ instance: new HttpActionExecutedContext(),
+ propertyGetter: aec => aec.Exception,
+ expectedDefaultValue: null,
+ allowNull: true,
+ roundTripTestValue: new ArgumentException());
+ }
+
+ [Fact]
+ public void Result_Property()
+ {
+ Assert.Reflection.Property<HttpActionExecutedContext, HttpResponseMessage>(
+ instance: new HttpActionExecutedContext(),
+ propertyGetter: aec => aec.Result,
+ expectedDefaultValue: null,
+ allowNull: true,
+ roundTripTestValue: new HttpResponseMessage());
+ }
+ }
+}
diff --git a/test/System.Web.Http.Test/Filters/HttpFilterCollectionTest.cs b/test/System.Web.Http.Test/Filters/HttpFilterCollectionTest.cs
new file mode 100644
index 00000000..1fa89d78
--- /dev/null
+++ b/test/System.Web.Http.Test/Filters/HttpFilterCollectionTest.cs
@@ -0,0 +1,94 @@
+using System.Linq;
+using Moq;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Http.Filters
+{
+ public class HttpFilterCollectionTest
+ {
+ private readonly IFilter _filter = new Mock<IFilter>().Object;
+ private readonly HttpFilterCollection _collection = new HttpFilterCollection();
+
+ [Fact]
+ public void Add_WhenFilterParameterIsNull_Throws()
+ {
+ Assert.ThrowsArgumentNull(() => _collection.Add(filter: null), "filter");
+ }
+
+ [Fact]
+ public void Add_AddsFilterWithGlobalScope()
+ {
+ _collection.Add(_filter);
+
+ Assert.Same(_filter, _collection.First().Instance);
+ Assert.Equal(FilterScope.Global, _collection.First().Scope);
+ }
+
+ [Fact]
+ public void Add_AllowsAddingSameInstanceMultipleTimes()
+ {
+ _collection.Add(_filter);
+ _collection.Add(_filter);
+
+ Assert.Equal(2, _collection.Count);
+ }
+
+ [Fact]
+ public void Clear_EmptiesCollection()
+ {
+ _collection.Add(_filter);
+
+ _collection.Clear();
+
+ Assert.Equal(0, _collection.Count);
+ }
+
+ [Fact]
+ public void Contains_WhenFilterNotInCollection_ReturnsFalse()
+ {
+ Assert.False(_collection.Contains(_filter));
+ }
+
+ [Fact]
+ public void Contains_WhenFilterInCollection_ReturnsTrue()
+ {
+ _collection.Add(_filter);
+
+ Assert.True(_collection.Contains(_filter));
+ }
+
+ [Fact]
+ public void Count_WhenCollectionIsEmpty_ReturnsZero()
+ {
+ Assert.Equal(0, _collection.Count);
+ }
+
+ [Fact]
+ public void Count_WhenItemAddedToCollection_ReturnsOne()
+ {
+ _collection.Add(_filter);
+
+ Assert.Equal(1, _collection.Count);
+ }
+
+ [Fact]
+ public void Remove_WhenCollectionDoesNotHaveFilter_DoesNothing()
+ {
+ _collection.Remove(_filter);
+
+ Assert.Equal(0, _collection.Count);
+ }
+
+ [Fact]
+ public void Remove_WhenCollectionHasFilter_RemovesIt()
+ {
+ _collection.Add(_filter);
+ _collection.Add(_filter);
+
+ _collection.Remove(_filter);
+
+ Assert.Equal(0, _collection.Count);
+ }
+ }
+}
diff --git a/test/System.Web.Http.Test/Filters/QueryCompositionFilterAttributeTest.cs b/test/System.Web.Http.Test/Filters/QueryCompositionFilterAttributeTest.cs
new file mode 100644
index 00000000..d79d93e4
--- /dev/null
+++ b/test/System.Web.Http.Test/Filters/QueryCompositionFilterAttributeTest.cs
@@ -0,0 +1,88 @@
+using System.Linq;
+using System.Net.Http;
+using System.Net.Http.Formatting;
+using Xunit;
+using Xunit.Extensions;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Http.Filters
+{
+ public class QueryCompositionFilterAttributeTest
+ {
+ private const string QueryKey = "MS_QueryKey";
+
+ QueryCompositionFilterAttribute _filter = new QueryCompositionFilterAttribute(typeof(int), queryValidator: null);
+
+ [Fact]
+ public void ConstructorThrowsOnNullInput()
+ {
+ Assert.ThrowsArgumentNull(() => new QueryCompositionFilterAttribute(null, queryValidator: null), "queryElementType");
+ }
+
+ [Fact]
+ public void OnActionExecutingSetsQueryPropertyOnRequestMessage()
+ {
+ // Arrange
+ var actionContext = ContextUtil.GetHttpActionContext(new HttpRequestMessage(HttpMethod.Get, "http://localhost/?$top=100"));
+
+ // Act
+ _filter.OnActionExecuting(actionContext);
+ var requestProperties = actionContext.ControllerContext.Request.Properties;
+
+ // Assert
+ Assert.True(requestProperties.ContainsKey(QueryKey));
+ Assert.IsAssignableFrom<IQueryable<int>>(requestProperties[QueryKey]);
+ }
+
+ [Fact]
+ public void OnActionExecutedAppendsQueryToResponse()
+ {
+ // Arrange
+ HttpRequestMessage request = new HttpRequestMessage();
+ request.Properties[QueryKey] = (new int[0]).AsQueryable().Take(100);
+ HttpResponseMessage response = new HttpResponseMessage() { Content = new ObjectContent<IQueryable<int>>(Enumerable.Range(1, 1000).AsQueryable(), new JsonMediaTypeFormatter()) };
+
+ var actionExecutedContext = ContextUtil.GetActionExecutedContext(request, response);
+
+ // Act
+ _filter.OnActionExecuted(actionExecutedContext);
+ HttpResponseMessage result = actionExecutedContext.Result;
+
+ // Assert
+ // TODO: we are depending on the correctness of QueryComposer here to test the filter which
+ // is sub-optimal. Reason being QueryComposer is a static class. cleanup with bug#325697
+ Assert.NotNull(result);
+ Assert.Equal(100, result.Content.ReadAsAsync<IQueryable<int>>().Result.Count());
+ }
+
+ [Theory]
+ [InlineData("$top=error")]
+ [InlineData("$filter=error")]
+ [InlineData("$skip=-100")]
+ public void OnActionExecutingThrowsForIncorrectTopQuery(string query)
+ {
+ // Arrange
+ const string baseAddress = "http://localhost/?{0}";
+ var request = new HttpRequestMessage(HttpMethod.Get, String.Format(baseAddress, query));
+ var actionContext = ContextUtil.GetHttpActionContext(request);
+
+ // Act & Assert
+ Assert.Throws<HttpRequestException>(
+ () => _filter.OnActionExecuting(actionContext),
+ "The query specified in the URI is not valid.");
+ }
+
+ [Fact]
+ public void OnActionExecutedOnNullResponse()
+ {
+ // Arrange
+ HttpRequestMessage request = new HttpRequestMessage();
+ request.Properties[QueryKey] = (new int[0]).AsQueryable().Take(100);
+ var actionContext = ContextUtil.GetActionExecutedContext(request, response: null);
+
+ // Act & Assert
+ Assert.DoesNotThrow(() => _filter.OnActionExecuted(actionContext));
+ Assert.Null(actionContext.Result);
+ }
+ }
+}
diff --git a/test/System.Web.Http.Test/Filters/QueryCompositionFilterProviderTest.cs b/test/System.Web.Http.Test/Filters/QueryCompositionFilterProviderTest.cs
new file mode 100644
index 00000000..98ec3b5d
--- /dev/null
+++ b/test/System.Web.Http.Test/Filters/QueryCompositionFilterProviderTest.cs
@@ -0,0 +1,72 @@
+using System.Collections.Generic;
+using System.Linq;
+using System.Net.Http;
+using System.Threading.Tasks;
+using System.Web.Http.Controllers;
+using Microsoft.TestCommon;
+using Moq;
+using Xunit;
+using Xunit.Extensions;
+
+namespace System.Web.Http.Filters
+{
+ public class QueryCompositionFilterProviderTest
+ {
+ private QueryCompositionFilterProvider filterProvider = new QueryCompositionFilterProvider();
+
+ public static TheoryDataSet<Type> GetFiltersReturnsEmptySetForNonQueryableReturnTypesData
+ {
+ get
+ {
+ return new TheoryDataSet<Type>
+ {
+ { typeof(int) },
+ { typeof(string) },
+ { typeof(void) },
+ { typeof(IEnumerable<int>) },
+ { typeof(List<int>) }
+ };
+ }
+ }
+
+ public static TheoryDataSet<Type, Type> GetFiltersReturnsSingleFilterForQueryableReturnTypesData
+ {
+ get
+ {
+ return new TheoryDataSet<Type, Type>
+ {
+ { typeof(IQueryable<int>), typeof(int) },
+ { typeof(IQueryable<string>), typeof(string)},
+ { typeof(IQueryable<IQueryable<int>>), typeof(IQueryable<int>) },
+ { typeof(Task<IQueryable<int>>), typeof(int) }
+ // { typeof(HttpResponseMessage), typeof(int) } // static signature problems
+ };
+ }
+ }
+
+ [Theory]
+ [PropertyData("GetFiltersReturnsEmptySetForNonQueryableReturnTypesData")]
+ public void GetFiltersReturnsEmptySetForNonQueryableReturnTypes(Type returnType)
+ {
+ Mock<HttpActionDescriptor> mockActionDescriptor = new Mock<HttpActionDescriptor>();
+ mockActionDescriptor.Setup((actionDescriptor) => actionDescriptor.ReturnType).Returns(returnType);
+
+ var filters = filterProvider.GetFilters(configuration: null, actionDescriptor: mockActionDescriptor.Object);
+
+ Assert.Empty(filters);
+ }
+
+ [Theory]
+ [PropertyData("GetFiltersReturnsSingleFilterForQueryableReturnTypesData")]
+ public void GetFiltersReturnsSingleFilterForQueryableReturnTypes(Type returnType, Type queryType)
+ {
+ Mock<HttpActionDescriptor> mockActionDescriptor = new Mock<HttpActionDescriptor>();
+ mockActionDescriptor.Setup((actionDescriptor) => actionDescriptor.ReturnType).Returns(returnType);
+
+ var filters = filterProvider.GetFilters(configuration: null, actionDescriptor: mockActionDescriptor.Object);
+
+ Assert.True(filters.Count() == 1);
+ Assert.Equal(Assert.IsType<QueryCompositionFilterAttribute>(filters.First().Instance).QueryElementType, queryType);
+ }
+ }
+}
diff --git a/test/System.Web.Http.Test/Hosting/HttpRouteTest.cs b/test/System.Web.Http.Test/Hosting/HttpRouteTest.cs
new file mode 100644
index 00000000..6dc0de2e
--- /dev/null
+++ b/test/System.Web.Http.Test/Hosting/HttpRouteTest.cs
@@ -0,0 +1,38 @@
+using System.Collections.Generic;
+using System.Net.Http;
+using System.Web.Http.Routing;
+using Microsoft.TestCommon;
+using Xunit.Extensions;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Http.Hosting
+{
+ public class HttpRouteTest
+ {
+ [Theory]
+ [InlineData("{controller}/{id}", "/SelfHostServer", "http://localhost/SelfHostServer/Customer/999")]
+ [InlineData("{controller}/{id}", "", "http://localhost/Customer/999")]
+ [InlineData("{controller}", "", "http://localhost/")]
+ [InlineData("{controller}", "/SelfHostServer", "http://localhost/SelfHostServer")]
+ [InlineData("{controller}", "", "http://localhost")]
+ [InlineData("{controller}/{id}", "", "http://localhost/")]
+ [InlineData("{controller}/{id}", "/SelfHostServer", "http://localhost/SelfHostServer")]
+ [InlineData("{controller}/{id}", "", "http://localhost")]
+ public void GetRouteDataShouldMatch(string uriTemplate, string virtualPathRoot, string requestUri)
+ {
+ HttpRoute route = new HttpRoute(uriTemplate);
+ route.Defaults.Add("controller", "Customer");
+ route.Defaults.Add("id", "999");
+ HttpRequestMessage request = new HttpRequestMessage();
+ request.RequestUri = new Uri(requestUri);
+ IHttpRouteData data = route.GetRouteData(virtualPathRoot, request);
+
+ // Assert
+ Assert.NotNull(data);
+ IDictionary<string, object> expectedResult = new Dictionary<string, object>();
+ expectedResult["controller"] = "Customer";
+ expectedResult["id"] = "999";
+ Assert.Equal(expectedResult, data.Values, new DictionaryEqualityComparer());
+ }
+ }
+}
diff --git a/test/System.Web.Http.Test/HttpBindingBehaviorAttributeTest.cs b/test/System.Web.Http.Test/HttpBindingBehaviorAttributeTest.cs
new file mode 100644
index 00000000..6e3bb6b3
--- /dev/null
+++ b/test/System.Web.Http.Test/HttpBindingBehaviorAttributeTest.cs
@@ -0,0 +1,51 @@
+using Xunit;
+
+namespace System.Web.Http
+{
+ public class HttpHttpBindingBehaviorAttributeTest
+ {
+ [Fact]
+ public void Behavior_Property()
+ {
+ // Arrange
+ HttpBindingBehavior expectedBehavior = (HttpBindingBehavior)(-20);
+
+ // Act
+ HttpBindingBehaviorAttribute attr = new HttpBindingBehaviorAttribute(expectedBehavior);
+
+ // Assert
+ Assert.Equal(expectedBehavior, attr.Behavior);
+ }
+
+ [Fact]
+ public void TypeId_ReturnsSameValue()
+ {
+ // Arrange
+ HttpBindNeverAttribute neverAttr = new HttpBindNeverAttribute();
+ HttpBindRequiredAttribute requiredAttr = new HttpBindRequiredAttribute();
+
+ // Act & assert
+ Assert.Same(neverAttr.TypeId, requiredAttr.TypeId);
+ }
+
+ [Fact]
+ public void BindNever_SetsBehavior()
+ {
+ // Act
+ HttpBindingBehaviorAttribute attr = new HttpBindNeverAttribute();
+
+ // Assert
+ Assert.Equal(HttpBindingBehavior.Never, attr.Behavior);
+ }
+
+ [Fact]
+ public void BindRequired_SetsBehavior()
+ {
+ // Act
+ HttpBindingBehaviorAttribute attr = new HttpBindRequiredAttribute();
+
+ // Assert
+ Assert.Equal(HttpBindingBehavior.Required, attr.Behavior);
+ }
+ }
+}
diff --git a/test/System.Web.Http.Test/HttpRequestMessageExtensionsTest.cs b/test/System.Web.Http.Test/HttpRequestMessageExtensionsTest.cs
new file mode 100644
index 00000000..a27db862
--- /dev/null
+++ b/test/System.Web.Http.Test/HttpRequestMessageExtensionsTest.cs
@@ -0,0 +1,256 @@
+using System.Collections.Generic;
+using System.Net;
+using System.Net.Http;
+using System.Net.Http.Formatting;
+using System.Net.Http.Headers;
+using System.Security.Principal;
+using System.Threading;
+using System.Web.Http.Hosting;
+using System.Web.Http.Routing;
+using System.Web.Http.Services;
+using Microsoft.TestCommon;
+using Moq;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Http
+{
+ public class HttpRequestMessageExtensionsTest
+ {
+ private readonly HttpRequestMessage _request = new HttpRequestMessage();
+ private readonly HttpConfiguration _config = new HttpConfiguration();
+ private readonly object _value = new object();
+ private readonly Mock<IDisposable> _disposableMock = new Mock<IDisposable>();
+ private readonly IDisposable _disposable;
+
+ public HttpRequestMessageExtensionsTest()
+ {
+ _disposable = _disposableMock.Object;
+ }
+
+ [Fact]
+ public void IsCorrectType()
+ {
+ Assert.Type.HasProperties(typeof(HttpRequestMessageExtensions), TypeAssert.TypeProperties.IsStatic | TypeAssert.TypeProperties.IsPublicVisibleClass);
+ }
+
+ [Fact]
+ public void GetConfigurationThrowsOnNull()
+ {
+ Assert.ThrowsArgumentNull(() => HttpRequestMessageExtensions.GetConfiguration(null), "request");
+ }
+
+ [Fact]
+ public void GetConfiguration()
+ {
+ // Arrange
+ _request.Properties.Add(HttpPropertyKeys.HttpConfigurationKey, _config);
+
+ // Act
+ HttpConfiguration afterConfig = _request.GetConfiguration();
+
+ // Assert
+ Assert.Same(_config, afterConfig);
+ }
+
+ [Fact]
+ public void GetUserPrincipalThrowsOnNull()
+ {
+ Assert.ThrowsArgumentNull(() => HttpRequestMessageExtensions.GetUserPrincipal(null), "request");
+ }
+
+ [Fact]
+ public void GetUserPrincipal()
+ {
+ // Arrange
+ Mock<IPrincipal> principalMock = new Mock<IPrincipal>();
+ IPrincipal beforePrincipal = principalMock.Object;
+ _request.Properties.Add(HttpPropertyKeys.UserPrincipalKey, beforePrincipal);
+
+ // Act
+ IPrincipal afterPrincipal = _request.GetUserPrincipal();
+
+ // Assert
+ Assert.Same(beforePrincipal, afterPrincipal);
+ }
+
+ [Fact]
+ public void GetSynchronizationContextThrowsOnNull()
+ {
+ Assert.ThrowsArgumentNull(() => HttpRequestMessageExtensions.GetSynchronizationContext(null), "request");
+ }
+
+ [Fact]
+ public void GetSynchronizationContext()
+ {
+ // Arrange
+ Mock<SynchronizationContext> syncContextMock = new Mock<SynchronizationContext>();
+ SynchronizationContext beforeSyncContext = syncContextMock.Object;
+ _request.Properties.Add(HttpPropertyKeys.SynchronizationContextKey, beforeSyncContext);
+
+ // Act
+ SynchronizationContext afterSyncContext = _request.GetSynchronizationContext();
+
+ // Assert
+ Assert.Same(beforeSyncContext, afterSyncContext);
+ }
+
+ [Fact]
+ public void GetRouteData()
+ {
+ // Arrange
+ IHttpRouteData routeData = new Mock<IHttpRouteData>().Object;
+ _request.Properties.Add(HttpPropertyKeys.HttpRouteDataKey, routeData);
+
+ // Act
+ var httpRouteData = _request.GetRouteData();
+
+ // Assert
+ Assert.Same(routeData, httpRouteData);
+ }
+
+ [Fact]
+ public void CreateResponse_OnNullRequest_ThrowsException()
+ {
+ Assert.ThrowsArgumentNull(() =>
+ {
+ HttpRequestMessageExtensions.CreateResponse(null, HttpStatusCode.OK, _value);
+ }, "request");
+
+ Assert.ThrowsArgumentNull(() =>
+ {
+ HttpRequestMessageExtensions.CreateResponse(null, HttpStatusCode.OK, _value, configuration: null);
+ }, "request");
+ }
+
+ [Fact]
+ public void CreateResponse_OnNullConfiguration_ThrowsException()
+ {
+ Assert.Throws<InvalidOperationException>(() =>
+ {
+ HttpRequestMessageExtensions.CreateResponse(_request, HttpStatusCode.OK, _value, configuration: null);
+ }, "The request does not have an associated configuration object or the provided configuration was null.");
+ }
+
+ [Fact]
+ public void CreateResponse_RetrievesContentNegotiatorFromServiceResolver()
+ {
+ // Arrange
+ Mock<IDependencyResolver> resolverMock = new Mock<IDependencyResolver>();
+ _config.ServiceResolver.SetResolver(resolverMock.Object);
+
+ // Act
+ HttpRequestMessageExtensions.CreateResponse(_request, HttpStatusCode.OK, _value, _config);
+
+ // Assert
+ resolverMock.Verify(r => r.GetService(typeof(IContentNegotiator)), Times.Once());
+ }
+
+ [Fact]
+ public void CreateResponse_PerformsContentNegotiationAndCreatesContentUsingResults()
+ {
+ // Arrange
+ Mock<IContentNegotiator> resolverMock = new Mock<IContentNegotiator>();
+ MediaTypeHeaderValue mediaType;
+ XmlMediaTypeFormatter formatter = new XmlMediaTypeFormatter();
+ resolverMock.Setup(r => r.Negotiate(typeof(object), _request, _config.Formatters, out mediaType))
+ .Returns(formatter);
+ _config.ServiceResolver.SetService(typeof(IContentNegotiator), resolverMock.Object);
+
+ // Act
+ var response = HttpRequestMessageExtensions.CreateResponse<object>(_request, HttpStatusCode.NoContent, _value, _config);
+
+ // Assert
+ Assert.Equal(HttpStatusCode.NoContent, response.StatusCode);
+ Assert.Same(_request, response.RequestMessage);
+ Assert.IsType<ObjectContent<object>>(response.Content);
+ object contentValue;
+ Assert.True(response.TryGetObjectValue<object>(out contentValue));
+ Assert.Same(_value, contentValue);
+ }
+
+ [Fact]
+ public void RegisterForDispose_WhenRequestParameterIsNull_Throws()
+ {
+ Assert.ThrowsArgumentNull(
+ () => HttpRequestMessageExtensions.RegisterForDispose(request: null, resource: null), "request");
+ }
+
+ [Fact]
+ public void RegisterForDispose_WhenResourceParamterIsNull_DoesNothing()
+ {
+ _request.RegisterForDispose(resource: null);
+
+ Assert.False(_request.Properties.ContainsKey(HttpPropertyKeys.DisposableRequestResourcesKey));
+ }
+
+ [Fact]
+ public void RegisterForDispose_WhenResourceListDoesNotExist_CreatesListAndAddsResource()
+ {
+ _request.Properties.Remove(HttpPropertyKeys.DisposableRequestResourcesKey);
+
+ _request.RegisterForDispose(_disposable);
+
+ var list = Assert.IsType<List<IDisposable>>(_request.Properties[HttpPropertyKeys.DisposableRequestResourcesKey]);
+ Assert.Equal(1, list.Count);
+ Assert.Same(_disposable, list[0]);
+ }
+
+ [Fact]
+ public void RegisterForDispose_WhenResourceListExists_AddsResource()
+ {
+ var list = new List<IDisposable>();
+ _request.Properties[HttpPropertyKeys.DisposableRequestResourcesKey] = list;
+
+ _request.RegisterForDispose(_disposable);
+
+ Assert.Same(list, _request.Properties[HttpPropertyKeys.DisposableRequestResourcesKey]);
+ Assert.Equal(1, list.Count);
+ Assert.Same(_disposable, list[0]);
+ }
+
+ [Fact]
+ public void DisposeRequestResources_WhenRequestParameterIsNull_Throws()
+ {
+ Assert.ThrowsArgumentNull(
+ () => HttpRequestMessageExtensions.DisposeRequestResources(request: null), "request");
+ }
+
+ [Fact]
+ public void DisposeRequestResources_WhenResourceListDoesNotExists_DoesNothing()
+ {
+ _request.Properties.Remove(HttpPropertyKeys.DisposableRequestResourcesKey);
+
+ _request.DisposeRequestResources();
+
+ Assert.False(_request.Properties.ContainsKey(HttpPropertyKeys.DisposableRequestResourcesKey));
+ }
+
+ [Fact]
+ public void DisposeRequestResources_WhenResourceListExists_DisposesResourceAndClearsReferences()
+ {
+ var list = new List<IDisposable> { _disposable };
+ _request.Properties[HttpPropertyKeys.DisposableRequestResourcesKey] = list;
+
+ _request.DisposeRequestResources();
+
+ _disposableMock.Verify(d => d.Dispose());
+ Assert.Equal(0, list.Count);
+ }
+
+ [Fact]
+ public void DisposeRequestResources_WhenResourcesDisposeMethodThrowsException_IgnoresExceptionsAndContinuesDisposingOtherResources()
+ {
+ Mock<IDisposable> throwingDisposableMock = new Mock<IDisposable>();
+ throwingDisposableMock.Setup(d => d.Dispose()).Throws(new Exception());
+ var list = new List<IDisposable> { throwingDisposableMock.Object, _disposable };
+ _request.Properties[HttpPropertyKeys.DisposableRequestResourcesKey] = list;
+
+ _request.DisposeRequestResources();
+
+ throwingDisposableMock.Verify(d => d.Dispose());
+ _disposableMock.Verify(d => d.Dispose());
+ Assert.Equal(0, list.Count);
+ }
+ }
+}
diff --git a/test/System.Web.Http.Test/HttpRouteCollectionExtensionsTest.cs b/test/System.Web.Http.Test/HttpRouteCollectionExtensionsTest.cs
new file mode 100644
index 00000000..ace893f3
--- /dev/null
+++ b/test/System.Web.Http.Test/HttpRouteCollectionExtensionsTest.cs
@@ -0,0 +1,67 @@
+using System.Web.Http.Routing;
+using Microsoft.TestCommon;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Http
+{
+ public class HttpRouteCollectionExtensionsTest
+ {
+ [Fact]
+ public void IsCorrectType()
+ {
+ Assert.Type.HasProperties(typeof(HttpRouteCollectionExtensions), TypeAssert.TypeProperties.IsStatic | TypeAssert.TypeProperties.IsPublicVisibleClass);
+ }
+
+ [Fact]
+ public void MapHttpRoute1ThrowsOnNullRouteCollection()
+ {
+ Assert.ThrowsArgumentNull(() => HttpRouteCollectionExtensions.MapHttpRoute(null, "", "", null), "routes");
+ }
+
+ [Fact]
+ public void MapHttpRoute1CreatesRoute()
+ {
+ // Arrange
+ HttpRouteCollection routes = new HttpRouteCollection();
+ object defaults = new { d1 = "D1" };
+
+ // Act
+ IHttpRoute route = routes.MapHttpRoute("name", "template", defaults);
+
+ // Assert
+ Assert.NotNull(route);
+ Assert.Equal("template", route.RouteTemplate);
+ Assert.Equal(1, route.Defaults.Count);
+ Assert.Equal("D1", route.Defaults["d1"]);
+ Assert.Same(route, routes["name"]);
+ }
+
+ [Fact]
+ public void MapHttpRoute2ThrowsOnNullRouteCollection()
+ {
+ Assert.ThrowsArgumentNull(() => HttpRouteCollectionExtensions.MapHttpRoute(null, "", "", null, null), "routes");
+ }
+
+ [Fact]
+ public void MapHttpRoute2CreatesRoute()
+ {
+ // Arrange
+ HttpRouteCollection routes = new HttpRouteCollection();
+ object defaults = new { d1 = "D1" };
+ object constraints = new { c1 = "C1" };
+
+ // Act
+ IHttpRoute route = routes.MapHttpRoute("name", "template", defaults, constraints);
+
+ // Assert
+ Assert.NotNull(route);
+ Assert.Equal("template", route.RouteTemplate);
+ Assert.Equal(1, route.Defaults.Count);
+ Assert.Equal("D1", route.Defaults["d1"]);
+ Assert.Equal(1, route.Defaults.Count);
+ Assert.Equal("C1", route.Constraints["c1"]);
+ Assert.Same(route, routes["name"]);
+ }
+ }
+}
diff --git a/test/System.Web.Http.Test/HttpServerTest.cs b/test/System.Web.Http.Test/HttpServerTest.cs
new file mode 100644
index 00000000..973b9327
--- /dev/null
+++ b/test/System.Web.Http.Test/HttpServerTest.cs
@@ -0,0 +1,163 @@
+using System.Net;
+using System.Net.Http;
+using System.Threading;
+using System.Threading.Tasks;
+using System.Web.Http.Dispatcher;
+using Microsoft.TestCommon;
+using Moq;
+using Moq.Protected;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Http
+{
+ public class HttpServerTest
+ {
+ [Fact]
+ public void IsCorrectType()
+ {
+ Assert.Type.HasProperties<HttpServer, DelegatingHandler>(TypeAssert.TypeProperties.IsPublicVisibleClass | TypeAssert.TypeProperties.IsDisposable);
+ }
+
+ [Fact]
+ public void DefaultConstructor()
+ {
+ Assert.NotNull(new HttpServer());
+ }
+
+ [Fact]
+ public void ConstructorConfigThrowsOnNull()
+ {
+ Assert.ThrowsArgumentNull(() => new HttpServer((HttpConfiguration)null), "configuration");
+ }
+
+ [Fact]
+ public void ConstructorConfigSetsUpProperties()
+ {
+ // Arrange
+ HttpConfiguration config = new HttpConfiguration();
+
+ // Act
+ HttpServer server = new HttpServer(config);
+
+ // Assert
+ Assert.Same(config, server.Configuration);
+ }
+
+ [Fact]
+ public void ConstructorDispatcherThrowsOnNull()
+ {
+ Assert.ThrowsArgumentNull(() => new HttpServer((HttpControllerDispatcher)null), "dispatcher");
+ }
+
+ [Fact]
+ public void ConstructorDispatcherSetsUpProperties()
+ {
+ // Arrange
+ Mock<HttpControllerDispatcher> controllerDispatcherMock = new Mock<HttpControllerDispatcher>();
+
+ // Act
+ HttpServer server = new HttpServer(controllerDispatcherMock.Object);
+
+ // Assert
+ Assert.Same(controllerDispatcherMock.Object, server.Dispatcher);
+ }
+
+ [Fact]
+ public void ConstructorThrowsOnNull()
+ {
+ Mock<HttpControllerDispatcher> controllerDispatcherMock = new Mock<HttpControllerDispatcher>();
+ Assert.ThrowsArgumentNull(() => new HttpServer((HttpConfiguration)null, controllerDispatcherMock.Object), "configuration");
+ Assert.ThrowsArgumentNull(() => new HttpServer(new HttpConfiguration(), null), "dispatcher");
+ }
+
+ [Fact]
+ public void ConstructorSetsUpProperties()
+ {
+ // Arrange
+ HttpConfiguration config = new HttpConfiguration();
+ Mock<HttpControllerDispatcher> controllerDispatcherMock = new Mock<HttpControllerDispatcher>();
+
+ // Act
+ HttpServer server = new HttpServer(config, controllerDispatcherMock.Object);
+
+ // Assert
+ Assert.Same(config, server.Configuration);
+ Assert.Same(controllerDispatcherMock.Object, server.Dispatcher);
+ }
+
+ [Fact]
+ public Task<HttpResponseMessage> DisposedReturnsServiceUnavailable()
+ {
+ // Arrange
+ Mock<HttpControllerDispatcher> dispatcherMock = new Mock<HttpControllerDispatcher>();
+ HttpServer server = new HttpServer(dispatcherMock.Object);
+ server.Dispose();
+ HttpRequestMessage request = new HttpRequestMessage();
+
+ // Act
+ return server.SubmitRequestAsync(request, CancellationToken.None).ContinueWith(
+ (reqTask) =>
+ {
+ // Assert
+ dispatcherMock.Protected().Verify<Task<HttpResponseMessage>>("SendAsync", Times.Never(), request, CancellationToken.None);
+ Assert.Equal(HttpStatusCode.ServiceUnavailable, reqTask.Result.StatusCode);
+ return reqTask.Result;
+ }
+ );
+ }
+
+ [Fact]
+ public Task<HttpResponseMessage> RequestGetsConfigurationAsParameter()
+ {
+ // Arrange
+ HttpRequestMessage request = new HttpRequestMessage();
+
+ Mock<HttpControllerDispatcher> dispatcherMock = new Mock<HttpControllerDispatcher>();
+ dispatcherMock.Protected().Setup<Task<HttpResponseMessage>>("SendAsync", request, CancellationToken.None).
+ Returns(TaskHelpers.FromResult<HttpResponseMessage>(request.CreateResponse()));
+
+ HttpConfiguration config = new HttpConfiguration();
+ HttpServer server = new HttpServer(config, dispatcherMock.Object);
+
+ // Act
+ return server.SubmitRequestAsync(request, CancellationToken.None).ContinueWith(
+ (reqTask) =>
+ {
+ // Assert
+ dispatcherMock.Protected().Verify<Task<HttpResponseMessage>>("SendAsync", Times.Once(), request, CancellationToken.None);
+ Assert.Same(config, request.GetConfiguration());
+ return reqTask.Result;
+ }
+ );
+ }
+
+ [Fact]
+ public Task<HttpResponseMessage> RequestGetsSyncContextAsParameter()
+ {
+ // Arrange
+ HttpRequestMessage request = new HttpRequestMessage();
+
+ Mock<HttpControllerDispatcher> dispatcherMock = new Mock<HttpControllerDispatcher>();
+ dispatcherMock.Protected().Setup<Task<HttpResponseMessage>>("SendAsync", request, CancellationToken.None).
+ Returns(TaskHelpers.FromResult<HttpResponseMessage>(request.CreateResponse()));
+
+ HttpConfiguration config = new HttpConfiguration();
+ HttpServer server = new HttpServer(config, dispatcherMock.Object);
+
+ SynchronizationContext syncContext = new SynchronizationContext();
+ SynchronizationContext.SetSynchronizationContext(syncContext);
+
+ // Act
+ return server.SubmitRequestAsync(request, CancellationToken.None).ContinueWith(
+ (reqTask) =>
+ {
+ // Assert
+ dispatcherMock.Protected().Verify<Task<HttpResponseMessage>>("SendAsync", Times.Once(), request, CancellationToken.None);
+ Assert.Same(syncContext, request.GetSynchronizationContext());
+ return reqTask.Result;
+ }
+ );
+ }
+ }
+}
diff --git a/test/System.Web.Http.Test/Internal/CollectionModelBinderUtilTest.cs b/test/System.Web.Http.Test/Internal/CollectionModelBinderUtilTest.cs
new file mode 100644
index 00000000..a8806203
--- /dev/null
+++ b/test/System.Web.Http.Test/Internal/CollectionModelBinderUtilTest.cs
@@ -0,0 +1,394 @@
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Linq;
+using System.Web.Http.Metadata;
+using System.Web.Http.Metadata.Providers;
+using System.Web.Http.ModelBinding;
+using System.Web.Http.ValueProviders;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Http.Internal
+{
+ public class CollectionModelBinderUtilTest
+ {
+ [Fact]
+ public void CreateOrReplaceCollection_OriginalModelImmutable_CreatesNewInstance()
+ {
+ // Arrange
+ ModelBindingContext bindingContext = new ModelBindingContext
+ {
+ ModelMetadata = new EmptyModelMetadataProvider().GetMetadataForType(() => new ReadOnlyCollection<int>(new int[0]), typeof(ICollection<int>))
+ };
+
+ // Act
+ CollectionModelBinderUtil.CreateOrReplaceCollection(bindingContext, new[] { 10, 20, 30 }, () => new List<int>());
+
+ // Assert
+ int[] newModel = (bindingContext.Model as ICollection<int>).ToArray();
+ Assert.Equal(new[] { 10, 20, 30 }, newModel);
+ }
+
+ [Fact]
+ public void CreateOrReplaceCollection_OriginalModelMutable_UpdatesOriginalInstance()
+ {
+ // Arrange
+ List<int> originalInstance = new List<int> { 10, 20, 30 };
+ ModelBindingContext bindingContext = new ModelBindingContext
+ {
+ ModelMetadata = new EmptyModelMetadataProvider().GetMetadataForType(() => originalInstance, typeof(ICollection<int>))
+ };
+
+ // Act
+ CollectionModelBinderUtil.CreateOrReplaceCollection(bindingContext, new[] { 40, 50, 60 }, () => new List<int>());
+
+ // Assert
+ Assert.Same(originalInstance, bindingContext.Model);
+ Assert.Equal(new[] { 40, 50, 60 }, originalInstance.ToArray());
+ }
+
+ [Fact]
+ public void CreateOrReplaceCollection_OriginalModelNotCollection_CreatesNewInstance()
+ {
+ // Arrange
+ ModelBindingContext bindingContext = new ModelBindingContext
+ {
+ ModelMetadata = new EmptyModelMetadataProvider().GetMetadataForType(null, typeof(ICollection<int>))
+ };
+
+ // Act
+ CollectionModelBinderUtil.CreateOrReplaceCollection(bindingContext, new[] { 10, 20, 30 }, () => new List<int>());
+
+ // Assert
+ int[] newModel = (bindingContext.Model as ICollection<int>).ToArray();
+ Assert.Equal(new[] { 10, 20, 30 }, newModel);
+ }
+
+ [Fact]
+ public void CreateOrReplaceDictionary_DisallowsDuplicateKeys()
+ {
+ // Arrange
+ ModelBindingContext bindingContext = new ModelBindingContext
+ {
+ ModelMetadata = new EmptyModelMetadataProvider().GetMetadataForType(null, typeof(Dictionary<string, int>))
+ };
+
+ // Act
+ CollectionModelBinderUtil.CreateOrReplaceDictionary(
+ bindingContext,
+ new[]
+ {
+ new KeyValuePair<string, int>("forty-two", 40),
+ new KeyValuePair<string, int>("forty-two", 2),
+ new KeyValuePair<string, int>("forty-two", 42)
+ },
+ () => new Dictionary<string, int>());
+
+ // Assert
+ IDictionary<string, int> newModel = bindingContext.Model as IDictionary<string, int>;
+ Assert.Equal(new[] { "forty-two" }, newModel.Keys.ToArray());
+ Assert.Equal(42, newModel["forty-two"]);
+ }
+
+ [Fact]
+ public void CreateOrReplaceDictionary_DisallowsNullKeys()
+ {
+ // Arrange
+ ModelBindingContext bindingContext = new ModelBindingContext
+ {
+ ModelMetadata = new EmptyModelMetadataProvider().GetMetadataForType(null, typeof(Dictionary<string, int>))
+ };
+
+ // Act
+ CollectionModelBinderUtil.CreateOrReplaceDictionary(
+ bindingContext,
+ new[]
+ {
+ new KeyValuePair<string, int>("forty-two", 42),
+ new KeyValuePair<string, int>(null, 84)
+ },
+ () => new Dictionary<string, int>());
+
+ // Assert
+ IDictionary<string, int> newModel = bindingContext.Model as IDictionary<string, int>;
+ Assert.Equal(new[] { "forty-two" }, newModel.Keys.ToArray());
+ Assert.Equal(42, newModel["forty-two"]);
+ }
+
+ [Fact]
+ public void CreateOrReplaceDictionary_OriginalModelImmutable_CreatesNewInstance()
+ {
+ // Arrange
+ ReadOnlyDictionary<string, string> originalModel = new ReadOnlyDictionary<string, string>();
+
+ ModelBindingContext bindingContext = new ModelBindingContext
+ {
+ ModelMetadata = new EmptyModelMetadataProvider().GetMetadataForType(() => originalModel, typeof(IDictionary<string, string>))
+ };
+
+ // Act
+ CollectionModelBinderUtil.CreateOrReplaceDictionary(
+ bindingContext,
+ new Dictionary<string, string>
+ {
+ { "Hello", "World" }
+ },
+ () => new Dictionary<string, string>());
+
+ // Assert
+ IDictionary<string, string> newModel = bindingContext.Model as IDictionary<string, string>;
+ Assert.NotSame(originalModel, newModel);
+ Assert.Equal(new[] { "Hello" }, newModel.Keys.ToArray());
+ Assert.Equal("World", newModel["Hello"]);
+ }
+
+ [Fact]
+ public void CreateOrReplaceDictionary_OriginalModelMutable_UpdatesOriginalInstance()
+ {
+ // Arrange
+ Dictionary<string, string> originalInstance = new Dictionary<string, string>
+ {
+ { "dog", "Canidae" },
+ { "cat", "Felidae" }
+ };
+ ModelBindingContext bindingContext = new ModelBindingContext
+ {
+ ModelMetadata = new EmptyModelMetadataProvider().GetMetadataForType(() => originalInstance, typeof(IDictionary<string, string>))
+ };
+
+ // Act
+ CollectionModelBinderUtil.CreateOrReplaceDictionary(
+ bindingContext,
+ new Dictionary<string, string>
+ {
+ { "horse", "Equidae" },
+ { "bear", "Ursidae" }
+ },
+ () => new Dictionary<string, string>());
+
+ // Assert
+ Assert.Same(originalInstance, bindingContext.Model);
+ Assert.Equal(new[] { "horse", "bear" }, originalInstance.Keys.ToArray());
+ Assert.Equal("Equidae", originalInstance["horse"]);
+ Assert.Equal("Ursidae", originalInstance["bear"]);
+ }
+
+ [Fact]
+ public void CreateOrReplaceDictionary_OriginalModelNotDictionary_CreatesNewInstance()
+ {
+ // Arrange
+ ModelBindingContext bindingContext = new ModelBindingContext
+ {
+ ModelMetadata = new EmptyModelMetadataProvider().GetMetadataForType(null, typeof(IDictionary<string, string>))
+ };
+
+ // Act
+ CollectionModelBinderUtil.CreateOrReplaceDictionary(
+ bindingContext,
+ new Dictionary<string, string>
+ {
+ { "horse", "Equidae" },
+ { "bear", "Ursidae" }
+ },
+ () => new Dictionary<string, string>());
+
+ // Assert
+ IDictionary<string, string> newModel = bindingContext.Model as IDictionary<string, string>;
+ Assert.Equal(new[] { "horse", "bear" }, newModel.Keys.ToArray());
+ Assert.Equal("Equidae", newModel["horse"]);
+ Assert.Equal("Ursidae", newModel["bear"]);
+ }
+
+ [Fact]
+ public void GetIndexNamesFromValueProviderResult_ValueProviderResultIsNull_ReturnsNull()
+ {
+ // Act
+ IEnumerable<string> indexNames = CollectionModelBinderUtil.GetIndexNamesFromValueProviderResult(null);
+
+ // Assert
+ Assert.Null(indexNames);
+ }
+
+ [Fact]
+ public void GetIndexNamesFromValueProviderResult_ValueProviderResultReturnsEmptyArray_ReturnsNull()
+ {
+ // Arrange
+ ValueProviderResult vpResult = new ValueProviderResult(new string[0], "", null);
+
+ // Act
+ IEnumerable<string> indexNames = CollectionModelBinderUtil.GetIndexNamesFromValueProviderResult(vpResult);
+
+ // Assert
+ Assert.Null(indexNames);
+ }
+
+ [Fact]
+ public void GetIndexNamesFromValueProviderResult_ValueProviderResultReturnsNonEmptyArray_ReturnsArray()
+ {
+ // Arrange
+ ValueProviderResult vpResult = new ValueProviderResult(new[] { "foo", "bar", "baz" }, "foo,bar,baz", null);
+
+ // Act
+ IEnumerable<string> indexNames = CollectionModelBinderUtil.GetIndexNamesFromValueProviderResult(vpResult);
+
+ // Assert
+ Assert.NotNull(indexNames);
+ Assert.Equal(new[] { "foo", "bar", "baz" }, indexNames.ToArray());
+ }
+
+ [Fact]
+ public void GetIndexNamesFromValueProviderResult_ValueProviderResultReturnsNull_ReturnsNull()
+ {
+ // Arrange
+ ValueProviderResult vpResult = new ValueProviderResult(null, null, null);
+
+ // Act
+ IEnumerable<string> indexNames = CollectionModelBinderUtil.GetIndexNamesFromValueProviderResult(vpResult);
+
+ // Assert
+ Assert.Null(indexNames);
+ }
+
+ [Fact]
+ public void GetTypeArgumentsForUpdatableGenericCollection_ModelTypeNotGeneric_Fail()
+ {
+ // Arrange
+ ModelMetadata modelMetadata = new EmptyModelMetadataProvider().GetMetadataForType(null, typeof(int));
+
+ // Act
+ Type[] typeArguments = CollectionModelBinderUtil.GetTypeArgumentsForUpdatableGenericCollection(null, null, modelMetadata);
+
+ // Assert
+ Assert.Null(typeArguments);
+ }
+
+ [Fact]
+ public void GetTypeArgumentsForUpdatableGenericCollection_ModelTypeOpenGeneric_Fail()
+ {
+ // Arrange
+ ModelMetadata modelMetadata = new EmptyModelMetadataProvider().GetMetadataForType(null, typeof(IList<>));
+
+ // Act
+ Type[] typeArguments = CollectionModelBinderUtil.GetTypeArgumentsForUpdatableGenericCollection(null, null, modelMetadata);
+
+ // Assert
+ Assert.Null(typeArguments);
+ }
+
+ [Fact]
+ public void GetTypeArgumentsForUpdatableGenericCollection_ModelTypeWrongNumberOfGenericArguments_Fail()
+ {
+ // Arrange
+ ModelMetadata modelMetadata = new EmptyModelMetadataProvider().GetMetadataForType(null, typeof(KeyValuePair<int, string>));
+
+ // Act
+ Type[] typeArguments = CollectionModelBinderUtil.GetTypeArgumentsForUpdatableGenericCollection(typeof(ICollection<>), null, modelMetadata);
+
+ // Assert
+ Assert.Null(typeArguments);
+ }
+
+ [Fact]
+ public void GetTypeArgumentsForUpdatableGenericCollection_ReadOnlyReference_ModelInstanceImmutable_Valid()
+ {
+ // Arrange
+ ModelMetadata modelMetadata = new EmptyModelMetadataProvider().GetMetadataForType(() => new int[0], typeof(IList<int>));
+ modelMetadata.IsReadOnly = true;
+
+ // Act
+ Type[] typeArguments = CollectionModelBinderUtil.GetTypeArgumentsForUpdatableGenericCollection(typeof(IList<>), typeof(List<>), modelMetadata);
+
+ // Assert
+ Assert.Null(typeArguments);
+ }
+
+ [Fact]
+ public void GetTypeArgumentsForUpdatableGenericCollection_ReadOnlyReference_ModelInstanceMutable_Valid()
+ {
+ // Arrange
+ ModelMetadata modelMetadata = new EmptyModelMetadataProvider().GetMetadataForType(() => new List<int>(), typeof(IList<int>));
+ modelMetadata.IsReadOnly = true;
+
+ // Act
+ Type[] typeArguments = CollectionModelBinderUtil.GetTypeArgumentsForUpdatableGenericCollection(typeof(IList<>), typeof(List<>), modelMetadata);
+
+ // Assert
+ Assert.Equal(new[] { typeof(int) }, typeArguments);
+ }
+
+ [Fact]
+ public void GetTypeArgumentsForUpdatableGenericCollection_ReadOnlyReference_ModelInstanceOfWrongType_Fail()
+ {
+ // Arrange
+ ModelMetadata modelMetadata = new EmptyModelMetadataProvider().GetMetadataForType(() => new HashSet<int>(), typeof(ICollection<int>));
+ modelMetadata.IsReadOnly = true;
+
+ // Act
+ Type[] typeArguments = CollectionModelBinderUtil.GetTypeArgumentsForUpdatableGenericCollection(typeof(IList<>), typeof(List<>), modelMetadata);
+
+ // Assert
+ // HashSet<> is not an IList<>, so we can't update
+ Assert.Null(typeArguments);
+ }
+
+ [Fact]
+ public void GetTypeArgumentsForUpdatableGenericCollection_ReadOnlyReference_ModelIsNull_Fail()
+ {
+ // Arrange
+ ModelMetadata modelMetadata = new EmptyModelMetadataProvider().GetMetadataForType(null, typeof(IList<int>));
+ modelMetadata.IsReadOnly = true;
+
+ // Act
+ Type[] typeArguments = CollectionModelBinderUtil.GetTypeArgumentsForUpdatableGenericCollection(typeof(ICollection<>), typeof(List<>), modelMetadata);
+
+ // Assert
+ Assert.Null(typeArguments);
+ }
+
+ [Fact]
+ public void GetTypeArgumentsForUpdatableGenericCollection_ReadWriteReference_NewInstanceAssignableToModelType_Success()
+ {
+ // Arrange
+ ModelMetadata modelMetadata = new EmptyModelMetadataProvider().GetMetadataForType(null, typeof(IList<int>));
+ modelMetadata.IsReadOnly = false;
+
+ // Act
+ Type[] typeArguments = CollectionModelBinderUtil.GetTypeArgumentsForUpdatableGenericCollection(typeof(ICollection<>), typeof(List<>), modelMetadata);
+
+ // Assert
+ Assert.Equal(new[] { typeof(int) }, typeArguments);
+ }
+
+ [Fact]
+ public void GetTypeArgumentsForUpdatableGenericCollection_ReadWriteReference_NewInstanceNotAssignableToModelType_MutableInstance_Success()
+ {
+ // Arrange
+ ModelMetadata modelMetadata = new EmptyModelMetadataProvider().GetMetadataForType(() => new Collection<int>(), typeof(Collection<int>));
+ modelMetadata.IsReadOnly = false;
+
+ // Act
+ Type[] typeArguments = CollectionModelBinderUtil.GetTypeArgumentsForUpdatableGenericCollection(typeof(ICollection<>), typeof(List<>), modelMetadata);
+
+ // Assert
+ Assert.Equal(new[] { typeof(int) }, typeArguments);
+ }
+
+ [Fact]
+ public void GetZeroBasedIndexes()
+ {
+ // Act
+ string[] indexes = CollectionModelBinderUtil.GetZeroBasedIndexes().Take(5).ToArray();
+
+ // Assert
+ Assert.Equal(new[] { "0", "1", "2", "3", "4" }, indexes);
+ }
+
+ private class ReadOnlyDictionary<TKey, TValue> : Dictionary<TKey, TValue>, ICollection<KeyValuePair<TKey, TValue>>
+ {
+ bool ICollection<KeyValuePair<TKey, TValue>>.IsReadOnly
+ {
+ get { return true; }
+ }
+ }
+ }
+}
diff --git a/test/System.Web.Http.Test/Internal/TypeActivatorTest.cs b/test/System.Web.Http.Test/Internal/TypeActivatorTest.cs
new file mode 100644
index 00000000..1ef026d4
--- /dev/null
+++ b/test/System.Web.Http.Test/Internal/TypeActivatorTest.cs
@@ -0,0 +1,158 @@
+using System.Collections.Generic;
+using System.Net;
+using System.Net.Http;
+using System.Reflection;
+using System.Web.Http.Controllers;
+using Microsoft.TestCommon;
+using Xunit;
+using Xunit.Extensions;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Http.Internal
+{
+ public class TypeActivatorTest
+ {
+ [Fact]
+ public void TypeIsCorrect()
+ {
+ Assert.Type.HasProperties(typeof(TypeActivator), TypeAssert.TypeProperties.IsClass | TypeAssert.TypeProperties.IsStatic);
+ }
+
+ public static TheoryDataSet<Type, Type> ValidTypeParameters
+ {
+ get
+ {
+ return new TheoryDataSet<Type, Type>
+ {
+ { typeof(List<int>), typeof(IList<int>)},
+ { typeof(Dictionary<int, int>), typeof(IDictionary<int, int>)},
+ { typeof(HttpRequestMessage), typeof(HttpRequestMessage)},
+ { typeof(HttpConfiguration), typeof(HttpConfiguration)},
+ { typeof(ReflectedHttpActionDescriptor), typeof(HttpActionDescriptor) },
+ { typeof(ApiControllerActionSelector), typeof(IHttpActionSelector)},
+ { typeof(ApiControllerActionInvoker), typeof(IHttpActionInvoker)},
+ { typeof(List<HttpStatusCode>), typeof(IEnumerable<HttpStatusCode>)},
+ };
+ }
+ }
+
+ [Theory]
+ [PropertyData("ValidTypeParameters")]
+ public void CreateType(Type instanceType, Type baseType)
+ {
+ // Arrange
+ Func<object> instanceDelegate = TypeActivator.Create(instanceType);
+
+ // Act
+ object instance = instanceDelegate();
+
+ // Assert
+ Assert.IsType(instanceType, instance);
+ }
+
+ [Theory]
+ [InlineData(typeof(int))]
+ [InlineData(typeof(Guid))]
+ [InlineData(typeof(HttpStatusCode))]
+ [InlineData(typeof(string))]
+ [InlineData(typeof(Uri))]
+ [InlineData(typeof(IDictionary<object, object>))]
+ [InlineData(typeof(List<>))]
+ public void CreateTypeInvalidThrowsInvalidArgument(Type type)
+ {
+ // Value types, interfaces, and open generics cause ArgumentException
+ Assert.Throws<ArgumentException>(() => TypeActivator.Create(type));
+ }
+
+ [Theory]
+ [InlineData(typeof(HttpContent))]
+ [InlineData(typeof(HttpActionDescriptor))]
+ public void CreateTypeInvalidThrowsInvalidOperation(Type type)
+ {
+ // Abstract types cause InvalidOperationException
+ Assert.Throws<InvalidOperationException>(() => TypeActivator.Create(type));
+ }
+
+ [Theory]
+ [PropertyData("ValidTypeParameters")]
+ public void CreateOfT(Type instanceType, Type baseType)
+ {
+ // Arrange
+ Type activatorType = typeof(TypeActivator);
+ MethodInfo createMethodInfo = activatorType.GetMethod("Create", Type.EmptyTypes);
+ MethodInfo genericCreateMethodInfo = createMethodInfo.MakeGenericMethod(instanceType);
+ Func<object> instanceDelegate = (Func<object>)genericCreateMethodInfo.Invoke(null, null);
+
+ // Act
+ object instance = instanceDelegate();
+
+ // Assert
+ Assert.IsType(instanceType, instance);
+ }
+
+ [Fact]
+ public void CreateOfTInvalid()
+ {
+ // string doesn't have a default ctor
+ Assert.Throws<ArgumentException>(() => TypeActivator.Create<string>());
+
+ // Uri doesn't have a default ctor
+ Assert.Throws<ArgumentException>(() => TypeActivator.Create<Uri>());
+
+ // HttpContent is abstract
+ Assert.Throws<InvalidOperationException>(() => TypeActivator.Create<HttpContent>());
+
+ // HttpActionDescriptor is abstract
+ Assert.Throws<InvalidOperationException>(() => TypeActivator.Create<HttpActionDescriptor>());
+ }
+
+ [Theory]
+ [PropertyData("ValidTypeParameters")]
+ public void CreateOfTBase(Type instanceType, Type baseType)
+ {
+ // Arrange
+ Type activatorType = typeof(TypeActivator);
+ MethodInfo createMethodInfo = null;
+ foreach (MethodInfo methodInfo in activatorType.GetMethods())
+ {
+ ParameterInfo[] parameterInfo = methodInfo.GetParameters();
+ if (methodInfo.Name == "Create" && methodInfo.ContainsGenericParameters && parameterInfo.Length == 1 && parameterInfo[0].ParameterType == typeof(Type))
+ {
+ createMethodInfo = methodInfo;
+ break;
+ }
+ }
+
+ MethodInfo genericCreateMethodInfo = createMethodInfo.MakeGenericMethod(baseType);
+ Func<object> instanceDelegate = (Func<object>)genericCreateMethodInfo.Invoke(null, new object[] { instanceType });
+
+ // Act
+ object instance = instanceDelegate();
+
+ // Assert
+ Assert.IsType(instanceType, instance);
+ }
+
+ [Fact]
+ public void CreateOfTBaseInvalid()
+ {
+ // int not being a ref type
+ Assert.Throws<ArgumentException>(() => TypeActivator.Create<object>(typeof(int)));
+
+ // GUID is not a ref type
+ Assert.Throws<ArgumentException>(() => TypeActivator.Create<object>(typeof(Guid)));
+
+ // HttpStatusCode is not a ref type
+ Assert.Throws<ArgumentException>(() => TypeActivator.Create<object>(typeof(HttpStatusCode)));
+
+ // string does not have a default ctor
+ Assert.Throws<ArgumentException>(() => TypeActivator.Create<string>(typeof(string)));
+
+ // ObjectContent does not have a default ctor
+ Assert.Throws<ArgumentException>(() => TypeActivator.Create<HttpContent>(typeof(ObjectContent)));
+
+ // Base type and instance type flipped
+ Assert.Throws<ArgumentException>(() => TypeActivator.Create<ReflectedHttpActionDescriptor>(typeof(HttpActionDescriptor)));
+ }
+ }
+} \ No newline at end of file
diff --git a/test/System.Web.Http.Test/ModelBinding/Binders/ArrayModelBinderProviderTest.cs b/test/System.Web.Http.Test/ModelBinding/Binders/ArrayModelBinderProviderTest.cs
new file mode 100644
index 00000000..edd08d82
--- /dev/null
+++ b/test/System.Web.Http.Test/ModelBinding/Binders/ArrayModelBinderProviderTest.cs
@@ -0,0 +1,100 @@
+using System.Collections.Generic;
+using System.Web.Http.Metadata.Providers;
+using System.Web.Http.Util;
+using Xunit;
+
+namespace System.Web.Http.ModelBinding.Binders
+{
+ public class ArrayModelBinderProviderTest
+ {
+ [Fact]
+ public void GetBinder_CorrectModelTypeAndValueProviderEntries_ReturnsBinder()
+ {
+ // Arrange
+ ModelBindingContext bindingContext = new ModelBindingContext
+ {
+ ModelMetadata = new EmptyModelMetadataProvider().GetMetadataForType(null, typeof(int[])),
+ ModelName = "foo",
+ ValueProvider = new SimpleHttpValueProvider
+ {
+ { "foo[0]", "42" },
+ }
+ };
+
+ ArrayModelBinderProvider binderProvider = new ArrayModelBinderProvider();
+
+ // Act
+ IModelBinder binder = binderProvider.GetBinder(null, bindingContext);
+
+ // Assert
+ Assert.IsType<ArrayModelBinder<int>>(binder);
+ }
+
+ [Fact]
+ public void GetBinder_ModelMetadataReturnsReadOnly_ReturnsNull()
+ {
+ // Arrange
+ ModelBindingContext bindingContext = new ModelBindingContext
+ {
+ ModelMetadata = new EmptyModelMetadataProvider().GetMetadataForType(null, typeof(int[])),
+ ModelName = "foo",
+ ValueProvider = new SimpleHttpValueProvider
+ {
+ { "foo[0]", "42" },
+ }
+ };
+ bindingContext.ModelMetadata.IsReadOnly = true;
+
+ ArrayModelBinderProvider binderProvider = new ArrayModelBinderProvider();
+
+ // Act
+ IModelBinder binder = binderProvider.GetBinder(null, bindingContext);
+
+ // Assert
+ Assert.Null(binder);
+ }
+
+ [Fact]
+ public void GetBinder_ModelTypeIsIncorrect_ReturnsNull()
+ {
+ // Arrange
+ ModelBindingContext bindingContext = new ModelBindingContext
+ {
+ ModelMetadata = new EmptyModelMetadataProvider().GetMetadataForType(null, typeof(ICollection<int>)),
+ ModelName = "foo",
+ ValueProvider = new SimpleHttpValueProvider
+ {
+ { "foo[0]", "42" },
+ }
+ };
+
+ ArrayModelBinderProvider binderProvider = new ArrayModelBinderProvider();
+
+ // Act
+ IModelBinder binder = binderProvider.GetBinder(null, bindingContext);
+
+ // Assert
+ Assert.Null(binder);
+ }
+
+ [Fact]
+ public void GetBinder_ValueProviderDoesNotContainPrefix_ReturnsNull()
+ {
+ // Arrange
+ ModelBindingContext bindingContext = new ModelBindingContext
+ {
+ ModelMetadata = new EmptyModelMetadataProvider().GetMetadataForType(null, typeof(int[])),
+ ModelName = "foo",
+ ValueProvider = new SimpleHttpValueProvider()
+ };
+
+ ArrayModelBinderProvider binderProvider = new ArrayModelBinderProvider();
+
+ // Act
+ IModelBinder binder = binderProvider.GetBinder(null, bindingContext);
+
+ // Assert
+ Assert.Null(binder);
+ }
+ }
+}
diff --git a/test/System.Web.Http.Test/ModelBinding/Binders/ArrayModelBinderTest.cs b/test/System.Web.Http.Test/ModelBinding/Binders/ArrayModelBinderTest.cs
new file mode 100644
index 00000000..a8ed1136
--- /dev/null
+++ b/test/System.Web.Http.Test/ModelBinding/Binders/ArrayModelBinderTest.cs
@@ -0,0 +1,47 @@
+using System.Web.Http.Controllers;
+using System.Web.Http.Metadata.Providers;
+using System.Web.Http.Util;
+using Moq;
+using Xunit;
+
+namespace System.Web.Http.ModelBinding.Binders
+{
+ public class ArrayModelBinderTest
+ {
+ [Fact]
+ public void BindModel()
+ {
+ // Arrange
+ Mock<IModelBinder> mockIntBinder = new Mock<IModelBinder>();
+ HttpActionContext context = ContextUtil.CreateActionContext();
+ context.ControllerContext.Configuration.ServiceResolver.SetService(typeof(ModelBinderProvider), new SimpleModelBinderProvider(typeof(int), mockIntBinder.Object));
+
+ ModelBindingContext bindingContext = new ModelBindingContext
+ {
+ ModelMetadata = new EmptyModelMetadataProvider().GetMetadataForType(null, typeof(int[])),
+ ModelName = "someName",
+ ValueProvider = new SimpleHttpValueProvider
+ {
+ { "someName[0]", "42" },
+ { "someName[1]", "84" }
+ }
+ };
+ mockIntBinder
+ .Setup(o => o.BindModel(context, It.IsAny<ModelBindingContext>()))
+ .Returns((HttpActionContext ec, ModelBindingContext mbc) =>
+ {
+ mbc.Model = mbc.ValueProvider.GetValue(mbc.ModelName).ConvertTo(mbc.ModelType);
+ return true;
+ });
+
+ // Act
+ bool retVal = new ArrayModelBinder<int>().BindModel(context, bindingContext);
+
+ // Assert
+ Assert.True(retVal);
+
+ int[] array = bindingContext.Model as int[];
+ Assert.Equal(new[] { 42, 84 }, array);
+ }
+ }
+}
diff --git a/test/System.Web.Http.Test/ModelBinding/Binders/BinaryDataModelBinderProviderTest.cs b/test/System.Web.Http.Test/ModelBinding/Binders/BinaryDataModelBinderProviderTest.cs
new file mode 100644
index 00000000..82ec5e63
--- /dev/null
+++ b/test/System.Web.Http.Test/ModelBinding/Binders/BinaryDataModelBinderProviderTest.cs
@@ -0,0 +1,159 @@
+using System.Data.Linq;
+using System.Web.Http.Metadata.Providers;
+using System.Web.Http.Util;
+using Xunit;
+
+namespace System.Web.Http.ModelBinding.Binders
+{
+ public class BinaryDataModelBinderProviderTest
+ {
+ private static readonly byte[] _base64Bytes = new byte[] { 0x12, 0x20, 0x34, 0x40 };
+ private const string _base64String = "EiA0QA==";
+
+ [Fact]
+ public void BindModel_BadValue_Fails()
+ {
+ // Arrange
+ ModelBindingContext bindingContext = new ModelBindingContext
+ {
+ ModelMetadata = new EmptyModelMetadataProvider().GetMetadataForType(null, typeof(byte[])),
+ ModelName = "foo",
+ ValueProvider = new SimpleHttpValueProvider
+ {
+ { "foo", "not base64 encoded!" }
+ }
+ };
+
+ BinaryDataModelBinderProvider binderProvider = new BinaryDataModelBinderProvider();
+
+ // Act
+ IModelBinder binder = binderProvider.GetBinder(null, bindingContext);
+ bool retVal = binder.BindModel(null, bindingContext);
+
+ // Assert
+ Assert.False(retVal);
+ }
+
+ [Fact]
+ public void BindModel_EmptyValue_Fails()
+ {
+ // Arrange
+ ModelBindingContext bindingContext = new ModelBindingContext
+ {
+ ModelMetadata = new EmptyModelMetadataProvider().GetMetadataForType(null, typeof(byte[])),
+ ModelName = "foo",
+ ValueProvider = new SimpleHttpValueProvider
+ {
+ { "foo", "" }
+ }
+ };
+
+ BinaryDataModelBinderProvider binderProvider = new BinaryDataModelBinderProvider();
+
+ // Act
+ IModelBinder binder = binderProvider.GetBinder(null, bindingContext);
+ bool retVal = binder.BindModel(null, bindingContext);
+
+ // Assert
+ Assert.False(retVal);
+ }
+
+ [Fact]
+ public void BindModel_GoodValue_ByteArray_Succeeds()
+ {
+ // Arrange
+ ModelBindingContext bindingContext = new ModelBindingContext
+ {
+ ModelMetadata = new EmptyModelMetadataProvider().GetMetadataForType(null, typeof(byte[])),
+ ModelName = "foo",
+ ValueProvider = new SimpleHttpValueProvider
+ {
+ { "foo", _base64String }
+ }
+ };
+
+ BinaryDataModelBinderProvider binderProvider = new BinaryDataModelBinderProvider();
+
+ // Act
+ IModelBinder binder = binderProvider.GetBinder(null, bindingContext);
+ bool retVal = binder.BindModel(null, bindingContext);
+
+ // Assert
+ Assert.True(retVal);
+ Assert.Equal(_base64Bytes, (byte[])bindingContext.Model);
+ }
+
+ [Fact]
+ public void BindModel_GoodValue_LinqBinary_Succeeds()
+ {
+ // Arrange
+ ModelBindingContext bindingContext = new ModelBindingContext
+ {
+ ModelMetadata = new EmptyModelMetadataProvider().GetMetadataForType(null, typeof(Binary)),
+ ModelName = "foo",
+ ValueProvider = new SimpleHttpValueProvider
+ {
+ { "foo", _base64String }
+ }
+ };
+
+ BinaryDataModelBinderProvider binderProvider = new BinaryDataModelBinderProvider();
+
+ // Act
+ IModelBinder binder = binderProvider.GetBinder(null, bindingContext);
+ bool retVal = binder.BindModel(null, bindingContext);
+
+ // Assert
+ Assert.True(retVal);
+ Binary binaryModel = Assert.IsType<Binary>(bindingContext.Model);
+ Assert.Equal(_base64Bytes, binaryModel.ToArray());
+ }
+
+ [Fact]
+ public void BindModel_NoValue_Fails()
+ {
+ // Arrange
+ ModelBindingContext bindingContext = new ModelBindingContext
+ {
+ ModelMetadata = new EmptyModelMetadataProvider().GetMetadataForType(null, typeof(byte[])),
+ ModelName = "foo",
+ ValueProvider = new SimpleHttpValueProvider
+ {
+ { "foo.bar", _base64String }
+ }
+ };
+
+ BinaryDataModelBinderProvider binderProvider = new BinaryDataModelBinderProvider();
+
+ // Act
+ IModelBinder binder = binderProvider.GetBinder(null, bindingContext);
+ bool retVal = binder.BindModel(null, bindingContext);
+
+ // Assert
+ Assert.False(retVal);
+ }
+
+ [Fact]
+ public void GetBinder_WrongModelType_ReturnsNull()
+ {
+ // Arrange
+ ModelBindingContext bindingContext = new ModelBindingContext
+ {
+ ModelMetadata = new EmptyModelMetadataProvider().GetMetadataForType(null, typeof(object)),
+ ModelName = "foo",
+ ValueProvider = new SimpleHttpValueProvider
+ {
+ { "foo", _base64String }
+ }
+ };
+
+ BinaryDataModelBinderProvider binderProvider = new BinaryDataModelBinderProvider();
+
+ // Act
+ IModelBinder modelBinder = binderProvider.GetBinder(null, bindingContext);
+
+ // Assert
+ Assert.Null(modelBinder);
+ }
+ }
+}
diff --git a/test/System.Web.Http.Test/ModelBinding/Binders/CollectionModelBinderProviderTest.cs b/test/System.Web.Http.Test/ModelBinding/Binders/CollectionModelBinderProviderTest.cs
new file mode 100644
index 00000000..be2f438e
--- /dev/null
+++ b/test/System.Web.Http.Test/ModelBinding/Binders/CollectionModelBinderProviderTest.cs
@@ -0,0 +1,77 @@
+using System.Collections.Generic;
+using System.Web.Http.Metadata.Providers;
+using System.Web.Http.ModelBinding.Binders;
+using System.Web.Http.Util;
+using Xunit;
+
+namespace System.Web.Http.ModelBinding
+{
+ public class CollectionModelBinderProviderTest
+ {
+ [Fact]
+ public void GetBinder_CorrectModelTypeAndValueProviderEntries_ReturnsBinder()
+ {
+ // Arrange
+ ModelBindingContext bindingContext = new ModelBindingContext
+ {
+ ModelMetadata = new EmptyModelMetadataProvider().GetMetadataForType(null, typeof(IEnumerable<int>)),
+ ModelName = "foo",
+ ValueProvider = new SimpleHttpValueProvider
+ {
+ { "foo[0]", "42" },
+ }
+ };
+
+ CollectionModelBinderProvider binderProvider = new CollectionModelBinderProvider();
+
+ // Act
+ IModelBinder binder = binderProvider.GetBinder(null, bindingContext);
+
+ // Assert
+ Assert.IsType<CollectionModelBinder<int>>(binder);
+ }
+
+ [Fact]
+ public void GetBinder_ModelTypeIsIncorrect_ReturnsNull()
+ {
+ // Arrange
+ ModelBindingContext bindingContext = new ModelBindingContext
+ {
+ ModelMetadata = new EmptyModelMetadataProvider().GetMetadataForType(null, typeof(int)),
+ ModelName = "foo",
+ ValueProvider = new SimpleHttpValueProvider
+ {
+ { "foo[0]", "42" },
+ }
+ };
+
+ CollectionModelBinderProvider binderProvider = new CollectionModelBinderProvider();
+
+ // Act
+ IModelBinder binder = binderProvider.GetBinder(null, bindingContext);
+
+ // Assert
+ Assert.Null(binder);
+ }
+
+ [Fact]
+ public void GetBinder_ValueProviderDoesNotContainPrefix_ReturnsNull()
+ {
+ // Arrange
+ ModelBindingContext bindingContext = new ModelBindingContext
+ {
+ ModelMetadata = new EmptyModelMetadataProvider().GetMetadataForType(null, typeof(IEnumerable<int>)),
+ ModelName = "foo",
+ ValueProvider = new SimpleHttpValueProvider()
+ };
+
+ CollectionModelBinderProvider binderProvider = new CollectionModelBinderProvider();
+
+ // Act
+ IModelBinder binder = binderProvider.GetBinder(null, bindingContext);
+
+ // Assert
+ Assert.Null(binder);
+ }
+ }
+}
diff --git a/test/System.Web.Http.Test/ModelBinding/Binders/CollectionModelBinderTest.cs b/test/System.Web.Http.Test/ModelBinding/Binders/CollectionModelBinderTest.cs
new file mode 100644
index 00000000..65cfa2ec
--- /dev/null
+++ b/test/System.Web.Http.Test/ModelBinding/Binders/CollectionModelBinderTest.cs
@@ -0,0 +1,240 @@
+using System.Collections.Generic;
+using System.Globalization;
+using System.Linq;
+using System.Web.Http.Controllers;
+using System.Web.Http.Metadata.Providers;
+using System.Web.Http.ModelBinding.Binders;
+using System.Web.Http.Util;
+using System.Web.Http.Validation;
+using Moq;
+using Xunit;
+
+namespace System.Web.Http.ModelBinding
+{
+ public class CollectionModelBinderTest
+ {
+ [Fact]
+ public void BindComplexCollectionFromIndexes_FiniteIndexes()
+ {
+ // Arrange
+ CultureInfo culture = CultureInfo.GetCultureInfo("fr-FR");
+ Mock<IModelBinder> mockIntBinder = new Mock<IModelBinder>();
+ ModelBindingContext bindingContext = new ModelBindingContext
+ {
+ ModelMetadata = new EmptyModelMetadataProvider().GetMetadataForType(null, typeof(int)),
+ ModelName = "someName",
+ ValueProvider = new SimpleHttpValueProvider
+ {
+ { "someName[foo]", "42" },
+ { "someName[baz]", "200" }
+ }
+ };
+ HttpActionContext context = ContextUtil.CreateActionContext();
+ context.ControllerContext.Configuration.ServiceResolver.SetService(typeof(ModelBinderProvider), new SimpleModelBinderProvider(typeof(int), mockIntBinder.Object));
+
+ mockIntBinder
+ .Setup(o => o.BindModel(context, It.IsAny<ModelBindingContext>()))
+ .Returns((HttpActionContext ec, ModelBindingContext mbc) =>
+ {
+ mbc.Model = mbc.ValueProvider.GetValue(mbc.ModelName).ConvertTo(mbc.ModelType);
+ return true;
+ });
+
+ // Act
+ List<int> boundCollection = CollectionModelBinder<int>.BindComplexCollectionFromIndexes(context, bindingContext, new[] { "foo", "bar", "baz" });
+
+ // Assert
+ Assert.Equal(new[] { 42, 0, 200 }, boundCollection.ToArray());
+ Assert.Equal(new[] { "someName[foo]", "someName[baz]" }, bindingContext.ValidationNode.ChildNodes.Select(o => o.ModelStateKey).ToArray());
+ }
+
+ [Fact]
+ public void BindComplexCollectionFromIndexes_InfiniteIndexes()
+ {
+ // Arrange
+ CultureInfo culture = CultureInfo.GetCultureInfo("fr-FR");
+ Mock<IModelBinder> mockIntBinder = new Mock<IModelBinder>();
+ ModelBindingContext bindingContext = new ModelBindingContext
+ {
+ ModelMetadata = new EmptyModelMetadataProvider().GetMetadataForType(null, typeof(int)),
+ ModelName = "someName",
+ ValueProvider = new SimpleHttpValueProvider
+ {
+ { "someName[0]", "42" },
+ { "someName[1]", "100" },
+ { "someName[3]", "400" }
+ }
+ };
+
+ HttpActionContext context = ContextUtil.CreateActionContext();
+ context.ControllerContext.Configuration.ServiceResolver.SetService(typeof(ModelBinderProvider), new SimpleModelBinderProvider(typeof(int), mockIntBinder.Object));
+
+ mockIntBinder
+ .Setup(o => o.BindModel(context, It.IsAny<ModelBindingContext>()))
+ .Returns((HttpActionContext ec, ModelBindingContext mbc) =>
+ {
+ mbc.Model = mbc.ValueProvider.GetValue(mbc.ModelName).ConvertTo(mbc.ModelType);
+ return true;
+ });
+
+ // Act
+ List<int> boundCollection = CollectionModelBinder<int>.BindComplexCollectionFromIndexes(context, bindingContext, null /* indexNames */);
+
+ // Assert
+ Assert.Equal(new[] { 42, 100 }, boundCollection.ToArray());
+ Assert.Equal(new[] { "someName[0]", "someName[1]" }, bindingContext.ValidationNode.ChildNodes.Select(o => o.ModelStateKey).ToArray());
+ }
+
+ [Fact]
+ public void BindModel_ComplexCollection()
+ {
+ // Arrange
+ CultureInfo culture = CultureInfo.GetCultureInfo("fr-FR");
+ Mock<IModelBinder> mockIntBinder = new Mock<IModelBinder>();
+ ModelBindingContext bindingContext = new ModelBindingContext
+ {
+ ModelMetadata = new EmptyModelMetadataProvider().GetMetadataForType(null, typeof(int)),
+ ModelName = "someName",
+ ValueProvider = new SimpleHttpValueProvider
+ {
+ { "someName.index", new[] { "foo", "bar", "baz" } },
+ { "someName[foo]", "42" },
+ { "someName[bar]", "100" },
+ { "someName[baz]", "200" }
+ }
+ };
+
+ HttpActionContext context = ContextUtil.CreateActionContext();
+ context.ControllerContext.Configuration.ServiceResolver.SetService(typeof(ModelBinderProvider), new SimpleModelBinderProvider(typeof(int), mockIntBinder.Object));
+
+ mockIntBinder
+ .Setup(o => o.BindModel(context, It.IsAny<ModelBindingContext>()))
+ .Returns((HttpActionContext ec, ModelBindingContext mbc) =>
+ {
+ mbc.Model = mbc.ValueProvider.GetValue(mbc.ModelName).ConvertTo(mbc.ModelType);
+ return true;
+ });
+
+ CollectionModelBinder<int> modelBinder = new CollectionModelBinder<int>();
+
+ // Act
+ bool retVal = modelBinder.BindModel(context, bindingContext);
+
+ // Assert
+ Assert.Equal(new[] { 42, 100, 200 }, ((List<int>)bindingContext.Model).ToArray());
+ }
+
+ [Fact]
+ public void BindModel_SimpleCollection()
+ {
+ // Arrange
+ CultureInfo culture = CultureInfo.GetCultureInfo("fr-FR");
+ Mock<IModelBinder> mockIntBinder = new Mock<IModelBinder>();
+ ModelBindingContext bindingContext = new ModelBindingContext
+ {
+ ModelMetadata = new EmptyModelMetadataProvider().GetMetadataForType(null, typeof(int)),
+ ModelName = "someName",
+ ValueProvider = new SimpleHttpValueProvider
+ {
+ { "someName", new[] { "42", "100", "200" } }
+ }
+ };
+ HttpActionContext context = ContextUtil.CreateActionContext();
+ context.ControllerContext.Configuration.ServiceResolver.SetService(typeof(ModelBinderProvider), new SimpleModelBinderProvider(typeof(int), mockIntBinder.Object));
+
+ mockIntBinder
+ .Setup(o => o.BindModel(context, It.IsAny<ModelBindingContext>()))
+ .Returns((HttpActionContext ec, ModelBindingContext mbc) =>
+ {
+ mbc.Model = mbc.ValueProvider.GetValue(mbc.ModelName).ConvertTo(mbc.ModelType);
+ return true;
+ });
+
+ CollectionModelBinder<int> modelBinder = new CollectionModelBinder<int>();
+
+ // Act
+ bool retVal = modelBinder.BindModel(context, bindingContext);
+
+ // Assert
+ Assert.True(retVal);
+ Assert.Equal(new[] { 42, 100, 200 }, ((List<int>)bindingContext.Model).ToArray());
+ }
+
+ [Fact]
+ public void BindSimpleCollection_RawValueIsEmptyCollection_ReturnsEmptyList()
+ {
+ // Act
+ List<int> boundCollection = CollectionModelBinder<int>.BindSimpleCollection(null, null, new object[0], null);
+
+ // Assert
+ Assert.NotNull(boundCollection);
+ Assert.Empty(boundCollection);
+ }
+
+ [Fact]
+ public void BindSimpleCollection_RawValueIsNull_ReturnsNull()
+ {
+ // Act
+ List<int> boundCollection = CollectionModelBinder<int>.BindSimpleCollection(null, null, null, null);
+
+ // Assert
+ Assert.Null(boundCollection);
+ }
+
+ [Fact(Skip = "is this test checking a valid invariant?")]
+ public void BindSimpleCollection_SubBinderDoesNotExist()
+ {
+ // Arrange
+ CultureInfo culture = CultureInfo.GetCultureInfo("fr-FR");
+ ModelBindingContext bindingContext = new ModelBindingContext
+ {
+ ModelMetadata = new EmptyModelMetadataProvider().GetMetadataForType(null, typeof(int)),
+ ModelName = "someName",
+ ValueProvider = new SimpleHttpValueProvider()
+ };
+ HttpActionContext context = ContextUtil.CreateActionContext();
+ context.ControllerContext.Configuration.ServiceResolver.SetService(typeof(ModelBinderProvider), null); // completely remove from resolution?
+
+ // Act
+ List<int> boundCollection = CollectionModelBinder<int>.BindSimpleCollection(context, bindingContext, new int[1], culture);
+
+ // Assert
+ Assert.Equal(new[] { 0 }, boundCollection.ToArray());
+ Assert.Empty(bindingContext.ValidationNode.ChildNodes);
+ }
+
+ [Fact]
+ public void BindSimpleCollection_SubBindingSucceeds()
+ {
+ // Arrange
+ CultureInfo culture = CultureInfo.GetCultureInfo("fr-FR");
+ Mock<IModelBinder> mockIntBinder = new Mock<IModelBinder>();
+ ModelBindingContext bindingContext = new ModelBindingContext
+ {
+ ModelMetadata = new EmptyModelMetadataProvider().GetMetadataForType(null, typeof(int)),
+ ModelName = "someName",
+ ValueProvider = new SimpleHttpValueProvider()
+ };
+ HttpActionContext context = ContextUtil.CreateActionContext();
+ context.ControllerContext.Configuration.ServiceResolver.SetService(typeof(ModelBinderProvider), new SimpleModelBinderProvider(typeof(int), mockIntBinder.Object));
+
+ ModelValidationNode childValidationNode = null;
+ mockIntBinder
+ .Setup(o => o.BindModel(context, It.IsAny<ModelBindingContext>()))
+ .Returns((HttpActionContext ec, ModelBindingContext mbc) =>
+ {
+ Assert.Equal("someName", mbc.ModelName);
+ childValidationNode = mbc.ValidationNode;
+ mbc.Model = 42;
+ return true;
+ });
+
+ // Act
+ List<int> boundCollection = CollectionModelBinder<int>.BindSimpleCollection(context, bindingContext, new int[1], culture);
+
+ // Assert
+ Assert.Equal(new[] { 42 }, boundCollection.ToArray());
+ Assert.Equal(new[] { childValidationNode }, bindingContext.ValidationNode.ChildNodes.ToArray());
+ }
+ }
+}
diff --git a/test/System.Web.Http.Test/ModelBinding/Binders/ComplexModelDtoModelBinderProviderTest.cs b/test/System.Web.Http.Test/ModelBinding/Binders/ComplexModelDtoModelBinderProviderTest.cs
new file mode 100644
index 00000000..51e4e69c
--- /dev/null
+++ b/test/System.Web.Http.Test/ModelBinding/Binders/ComplexModelDtoModelBinderProviderTest.cs
@@ -0,0 +1,44 @@
+using System.Web.Http.Metadata.Providers;
+using Xunit;
+
+namespace System.Web.Http.ModelBinding.Binders
+{
+ public class ComplexModelDtoModelBinderProviderTest
+ {
+ [Fact]
+ public void GetBinder_TypeDoesNotMatch_ReturnsNull()
+ {
+ // Arrange
+ ComplexModelDtoModelBinderProvider provider = new ComplexModelDtoModelBinderProvider();
+ ModelBindingContext bindingContext = GetBindingContext(typeof(object));
+
+ // Act
+ IModelBinder binder = provider.GetBinder(null, bindingContext);
+
+ // Assert
+ Assert.Null(binder);
+ }
+
+ [Fact]
+ public void GetBinder_TypeMatches_ReturnsBinder()
+ {
+ // Arrange
+ ComplexModelDtoModelBinderProvider provider = new ComplexModelDtoModelBinderProvider();
+ ModelBindingContext bindingContext = GetBindingContext(typeof(ComplexModelDto));
+
+ // Act
+ IModelBinder binder = provider.GetBinder(null, bindingContext);
+
+ // Assert
+ Assert.IsType<ComplexModelDtoModelBinder>(binder);
+ }
+
+ private static ModelBindingContext GetBindingContext(Type modelType)
+ {
+ return new ModelBindingContext
+ {
+ ModelMetadata = new EmptyModelMetadataProvider().GetMetadataForType(() => null, modelType)
+ };
+ }
+ }
+}
diff --git a/test/System.Web.Http.Test/ModelBinding/Binders/ComplexModelDtoModelBinderTest.cs b/test/System.Web.Http.Test/ModelBinding/Binders/ComplexModelDtoModelBinderTest.cs
new file mode 100644
index 00000000..533f5209
--- /dev/null
+++ b/test/System.Web.Http.Test/ModelBinding/Binders/ComplexModelDtoModelBinderTest.cs
@@ -0,0 +1,91 @@
+using System.Linq;
+using System.Web.Http.Controllers;
+using System.Web.Http.Metadata;
+using System.Web.Http.Metadata.Providers;
+using System.Web.Http.Validation;
+using Moq;
+using Xunit;
+
+namespace System.Web.Http.ModelBinding.Binders
+{
+ public class ComplexModelDtoModelBinderTest
+ {
+ [Fact]
+ public void BindModel()
+ {
+ // Arrange
+ MyModel model = new MyModel();
+ ModelMetadata modelMetadata = new EmptyModelMetadataProvider().GetMetadataForType(() => model, typeof(MyModel));
+ ComplexModelDto dto = new ComplexModelDto(modelMetadata, modelMetadata.Properties);
+ Mock<IModelBinder> mockStringBinder = new Mock<IModelBinder>();
+ Mock<IModelBinder> mockIntBinder = new Mock<IModelBinder>();
+ Mock<IModelBinder> mockDateTimeBinder = new Mock<IModelBinder>();
+ HttpActionContext context = ContextUtil.CreateActionContext();
+ context.ControllerContext.Configuration.ServiceResolver.SetServices(typeof(ModelBinderProvider),
+ new SimpleModelBinderProvider(typeof(string), mockStringBinder.Object) { SuppressPrefixCheck = true },
+ new SimpleModelBinderProvider(typeof(int), mockIntBinder.Object) { SuppressPrefixCheck = true },
+ new SimpleModelBinderProvider(typeof(DateTime), mockDateTimeBinder.Object) { SuppressPrefixCheck = true });
+
+ mockStringBinder
+ .Setup(b => b.BindModel(context, It.IsAny<ModelBindingContext>()))
+ .Returns((HttpActionContext ec, ModelBindingContext mbc) =>
+ {
+ Assert.Equal(typeof(string), mbc.ModelType);
+ Assert.Equal("theModel.StringProperty", mbc.ModelName);
+ mbc.ValidationNode = new ModelValidationNode(mbc.ModelMetadata, "theModel.StringProperty");
+ mbc.Model = "someStringValue";
+ return true;
+ });
+ mockIntBinder
+ .Setup(b => b.BindModel(context, It.IsAny<ModelBindingContext>()))
+ .Returns((HttpActionContext ec, ModelBindingContext mbc) =>
+ {
+ Assert.Equal(typeof(int), mbc.ModelType);
+ Assert.Equal("theModel.IntProperty", mbc.ModelName);
+ mbc.ValidationNode = new ModelValidationNode(mbc.ModelMetadata, "theModel.IntProperty");
+ mbc.Model = 42;
+ return true;
+ });
+ mockDateTimeBinder
+ .Setup(b => b.BindModel(context, It.IsAny<ModelBindingContext>()))
+ .Returns((HttpActionContext ec, ModelBindingContext mbc) =>
+ {
+ Assert.Equal(typeof(DateTime), mbc.ModelType);
+ Assert.Equal("theModel.DateTimeProperty", mbc.ModelName);
+ return false;
+ });
+ ModelBindingContext parentBindingContext = new ModelBindingContext
+ {
+ ModelMetadata = new EmptyModelMetadataProvider().GetMetadataForType(() => dto, typeof(ComplexModelDto)),
+ ModelName = "theModel",
+ };
+ ComplexModelDtoModelBinder binder = new ComplexModelDtoModelBinder();
+
+ // Act
+ bool retVal = binder.BindModel(context, parentBindingContext);
+
+ // Assert
+ Assert.True(retVal);
+ Assert.Equal(dto, parentBindingContext.Model);
+
+ ComplexModelDtoResult stringDtoResult = dto.Results[dto.PropertyMetadata.Where(m => m.ModelType == typeof(string)).First()];
+ Assert.Equal("someStringValue", stringDtoResult.Model);
+ Assert.Equal("theModel.StringProperty", stringDtoResult.ValidationNode.ModelStateKey);
+
+ ComplexModelDtoResult intDtoResult = dto.Results[dto.PropertyMetadata.Where(m => m.ModelType == typeof(int)).First()];
+ Assert.Equal(42, intDtoResult.Model);
+ Assert.Equal("theModel.IntProperty", intDtoResult.ValidationNode.ModelStateKey);
+
+ ComplexModelDtoResult dateTimeDtoResult = dto.Results[dto.PropertyMetadata.Where(m => m.ModelType == typeof(DateTime)).First()];
+ Assert.Null(dateTimeDtoResult);
+ }
+
+ private sealed class MyModel
+ {
+ public string StringProperty { get; set; }
+ public int IntProperty { get; set; }
+ public object ObjectProperty { get; set; } // no binding should happen since no registered binder
+ public DateTime DateTimeProperty { get; set; } // registered binder returns false
+ }
+ }
+}
diff --git a/test/System.Web.Http.Test/ModelBinding/Binders/ComplexModelDtoResultTest.cs b/test/System.Web.Http.Test/ModelBinding/Binders/ComplexModelDtoResultTest.cs
new file mode 100644
index 00000000..f9cb4ea5
--- /dev/null
+++ b/test/System.Web.Http.Test/ModelBinding/Binders/ComplexModelDtoResultTest.cs
@@ -0,0 +1,41 @@
+using System.Web.Http.Metadata;
+using System.Web.Http.Metadata.Providers;
+using System.Web.Http.Validation;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Http.ModelBinding.Binders
+{
+ public class ComplexModelDtoResultTest
+ {
+ [Fact]
+ public void Constructor_ThrowsIfValidationNodeIsNull()
+ {
+ // Act & assert
+ Assert.ThrowsArgumentNull(
+ () => new ComplexModelDtoResult("some string", null),
+ "validationNode");
+ }
+
+ [Fact]
+ public void Constructor_SetsProperties()
+ {
+ // Arrange
+ ModelValidationNode validationNode = GetValidationNode();
+
+ // Act
+ ComplexModelDtoResult result = new ComplexModelDtoResult("some string", validationNode);
+
+ // Assert
+ Assert.Equal("some string", result.Model);
+ Assert.Equal(validationNode, result.ValidationNode);
+ }
+
+ private static ModelValidationNode GetValidationNode()
+ {
+ EmptyModelMetadataProvider provider = new EmptyModelMetadataProvider();
+ ModelMetadata metadata = provider.GetMetadataForType(null, typeof(object));
+ return new ModelValidationNode(metadata, "someKey");
+ }
+ }
+}
diff --git a/test/System.Web.Http.Test/ModelBinding/Binders/ComplexModelDtoTest.cs b/test/System.Web.Http.Test/ModelBinding/Binders/ComplexModelDtoTest.cs
new file mode 100644
index 00000000..4cce9a1f
--- /dev/null
+++ b/test/System.Web.Http.Test/ModelBinding/Binders/ComplexModelDtoTest.cs
@@ -0,0 +1,53 @@
+using System.Linq;
+using System.Web.Http.Metadata;
+using System.Web.Http.Metadata.Providers;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Http.ModelBinding.Binders
+{
+ public class ComplexModelDtoTest
+ {
+ [Fact]
+ public void ConstructorThrowsIfModelMetadataIsNull()
+ {
+ // Act & assert
+ Assert.ThrowsArgumentNull(
+ () => new ComplexModelDto(null, Enumerable.Empty<ModelMetadata>()),
+ "modelMetadata");
+ }
+
+ [Fact]
+ public void ConstructorThrowsIfPropertyMetadataIsNull()
+ {
+ // Arrange
+ ModelMetadata modelMetadata = GetModelMetadata();
+
+ // Act & assert
+ Assert.ThrowsArgumentNull(
+ () => new ComplexModelDto(modelMetadata, null),
+ "propertyMetadata");
+ }
+
+ [Fact]
+ public void ConstructorSetsProperties()
+ {
+ // Arrange
+ ModelMetadata modelMetadata = GetModelMetadata();
+ ModelMetadata[] propertyMetadata = new ModelMetadata[0];
+
+ // Act
+ ComplexModelDto dto = new ComplexModelDto(modelMetadata, propertyMetadata);
+
+ // Assert
+ Assert.Equal(modelMetadata, dto.ModelMetadata);
+ Assert.Equal(propertyMetadata, dto.PropertyMetadata.ToArray());
+ Assert.Empty(dto.Results);
+ }
+
+ private static ModelMetadata GetModelMetadata()
+ {
+ return new ModelMetadata(new EmptyModelMetadataProvider(), typeof(object), null, typeof(object), "PropertyName");
+ }
+ }
+}
diff --git a/test/System.Web.Http.Test/ModelBinding/Binders/DictionaryModelBinderProviderTest.cs b/test/System.Web.Http.Test/ModelBinding/Binders/DictionaryModelBinderProviderTest.cs
new file mode 100644
index 00000000..d9c73bc0
--- /dev/null
+++ b/test/System.Web.Http.Test/ModelBinding/Binders/DictionaryModelBinderProviderTest.cs
@@ -0,0 +1,76 @@
+using System.Collections.Generic;
+using System.Web.Http.Metadata.Providers;
+using System.Web.Http.Util;
+using Xunit;
+
+namespace System.Web.Http.ModelBinding.Binders
+{
+ public class DictionaryModelBinderProviderTest
+ {
+ [Fact]
+ public void GetBinder_CorrectModelTypeAndValueProviderEntries_ReturnsBinder()
+ {
+ // Arrange
+ ModelBindingContext bindingContext = new ModelBindingContext
+ {
+ ModelMetadata = new EmptyModelMetadataProvider().GetMetadataForType(null, typeof(IDictionary<int, string>)),
+ ModelName = "foo",
+ ValueProvider = new SimpleHttpValueProvider
+ {
+ { "foo[0]", "42" },
+ }
+ };
+
+ DictionaryModelBinderProvider binderProvider = new DictionaryModelBinderProvider();
+
+ // Act
+ IModelBinder binder = binderProvider.GetBinder(null, bindingContext);
+
+ // Assert
+ Assert.IsType<DictionaryModelBinder<int, string>>(binder);
+ }
+
+ [Fact]
+ public void GetBinder_ModelTypeIsIncorrect_ReturnsNull()
+ {
+ // Arrange
+ ModelBindingContext bindingContext = new ModelBindingContext
+ {
+ ModelMetadata = new EmptyModelMetadataProvider().GetMetadataForType(null, typeof(int)),
+ ModelName = "foo",
+ ValueProvider = new SimpleHttpValueProvider
+ {
+ { "foo[0]", "42" },
+ }
+ };
+
+ DictionaryModelBinderProvider binderProvider = new DictionaryModelBinderProvider();
+
+ // Act
+ IModelBinder binder = binderProvider.GetBinder(null, bindingContext);
+
+ // Assert
+ Assert.Null(binder);
+ }
+
+ [Fact]
+ public void GetBinder_ValueProviderDoesNotContainPrefix_ReturnsNull()
+ {
+ // Arrange
+ ModelBindingContext bindingContext = new ModelBindingContext
+ {
+ ModelMetadata = new EmptyModelMetadataProvider().GetMetadataForType(null, typeof(IDictionary<int, string>)),
+ ModelName = "foo",
+ ValueProvider = new SimpleHttpValueProvider()
+ };
+
+ DictionaryModelBinderProvider binderProvider = new DictionaryModelBinderProvider();
+
+ // Act
+ IModelBinder binder = binderProvider.GetBinder(null, bindingContext);
+
+ // Assert
+ Assert.Null(binder);
+ }
+ }
+}
diff --git a/test/System.Web.Http.Test/ModelBinding/Binders/DictionaryModelBinderTest.cs b/test/System.Web.Http.Test/ModelBinding/Binders/DictionaryModelBinderTest.cs
new file mode 100644
index 00000000..38efd146
--- /dev/null
+++ b/test/System.Web.Http.Test/ModelBinding/Binders/DictionaryModelBinderTest.cs
@@ -0,0 +1,51 @@
+using System.Collections.Generic;
+using System.Web.Http.Controllers;
+using System.Web.Http.Metadata.Providers;
+using System.Web.Http.Util;
+using Moq;
+using Xunit;
+
+namespace System.Web.Http.ModelBinding.Binders
+{
+ public class DictionaryModelBinderTest
+ {
+ [Fact]
+ public void BindModel()
+ {
+ // Arrange
+ Mock<IModelBinder> mockKvpBinder = new Mock<IModelBinder>();
+ ModelBindingContext bindingContext = new ModelBindingContext
+ {
+ ModelMetadata = new EmptyModelMetadataProvider().GetMetadataForType(null, typeof(IDictionary<int, string>)),
+ ModelName = "someName",
+ ValueProvider = new SimpleHttpValueProvider
+ {
+ { "someName[0]", new KeyValuePair<int, string>(42, "forty-two") },
+ { "someName[1]", new KeyValuePair<int, string>(84, "eighty-four") }
+ }
+ };
+ HttpActionContext context = ContextUtil.CreateActionContext();
+ context.ControllerContext.Configuration.ServiceResolver.SetService(typeof(ModelBinderProvider), (new SimpleModelBinderProvider(typeof(KeyValuePair<int, string>), mockKvpBinder.Object)));
+
+ mockKvpBinder
+ .Setup(o => o.BindModel(context, It.IsAny<ModelBindingContext>()))
+ .Returns((HttpActionContext cc, ModelBindingContext mbc) =>
+ {
+ mbc.Model = mbc.ValueProvider.GetValue(mbc.ModelName).ConvertTo(mbc.ModelType);
+ return true;
+ });
+
+ // Act
+ bool retVal = new DictionaryModelBinder<int, string>().BindModel(context, bindingContext);
+
+ // Assert
+ Assert.True(retVal);
+
+ var dictionary = Assert.IsAssignableFrom<IDictionary<int, string>>(bindingContext.Model);
+ Assert.NotNull(dictionary);
+ Assert.Equal(2, dictionary.Count);
+ Assert.Equal("forty-two", dictionary[42]);
+ Assert.Equal("eighty-four", dictionary[84]);
+ }
+ }
+}
diff --git a/test/System.Web.Http.Test/ModelBinding/Binders/GenericModelBinderProviderTest.cs b/test/System.Web.Http.Test/ModelBinding/Binders/GenericModelBinderProviderTest.cs
new file mode 100644
index 00000000..6d83cda8
--- /dev/null
+++ b/test/System.Web.Http.Test/ModelBinding/Binders/GenericModelBinderProviderTest.cs
@@ -0,0 +1,251 @@
+using System.Collections.Generic;
+using System.Web.Http.Metadata.Providers;
+using System.Web.Http.Util;
+using Moq;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Http.ModelBinding.Binders
+{
+ public class GenericModelBinderProviderTest
+ {
+ [Fact]
+ public void Constructor_WithFactory_ThrowsIfModelBinderFactoryIsNull()
+ {
+ // Act & assert
+ Assert.ThrowsArgumentNull(
+ () => new GenericModelBinderProvider(typeof(List<>), (Func<Type[], IModelBinder>)null),
+ "modelBinderFactory");
+ }
+
+ [Fact]
+ public void Constructor_WithFactory_ThrowsIfModelTypeIsNotOpenGeneric()
+ {
+ // Act & assert
+ Assert.Throws<ArgumentException>(
+ () => new GenericModelBinderProvider(typeof(List<int>), _ => null),
+ @"The type 'System.Collections.Generic.List`1[System.Int32]' is not an open generic type.
+Parameter name: modelType");
+ }
+
+ [Fact]
+ public void Constructor_WithFactory_ThrowsIfModelTypeIsNull()
+ {
+ // Act & assert
+ Assert.ThrowsArgumentNull(
+ () => new GenericModelBinderProvider(null, _ => null),
+ "modelType");
+ }
+
+ [Fact]
+ public void Constructor_WithInstance_ThrowsIfModelBinderIsNull()
+ {
+ // Act & assert
+ Assert.ThrowsArgumentNull(
+ () => new GenericModelBinderProvider(typeof(List<>), (IModelBinder)null),
+ "modelBinder");
+ }
+
+ [Fact]
+ public void Constructor_WithInstance_ThrowsIfModelTypeIsNotOpenGeneric()
+ {
+ // Act & assert
+ Assert.Throws<ArgumentException>(
+ () => new GenericModelBinderProvider(typeof(List<int>), new MutableObjectModelBinder()),
+ @"The type 'System.Collections.Generic.List`1[System.Int32]' is not an open generic type.
+Parameter name: modelType");
+ }
+
+ [Fact]
+ public void Constructor_WithInstance_ThrowsIfModelTypeIsNull()
+ {
+ // Act & assert
+ Assert.ThrowsArgumentNull(
+ () => new GenericModelBinderProvider(null, new MutableObjectModelBinder()),
+ "modelType");
+ }
+
+ [Fact]
+ public void Constructor_WithType_ThrowsIfModelBinderTypeIsNotModelBinder()
+ {
+ // Act & assert
+ Assert.Throws<ArgumentException>(
+ () => new GenericModelBinderProvider(typeof(List<>), typeof(string)),
+ @"The type 'System.String' does not implement the interface 'System.Web.Http.ModelBinding.IModelBinder'.
+Parameter name: modelBinderType");
+ }
+
+ [Fact]
+ public void Constructor_WithType_ThrowsIfModelBinderTypeIsNull()
+ {
+ // Act & assert
+ Assert.ThrowsArgumentNull(
+ () => new GenericModelBinderProvider(typeof(List<>), (Type)null),
+ "modelBinderType");
+ }
+
+ [Fact]
+ public void Constructor_WithType_ThrowsIfModelBinderTypeTypeArgumentMismatch()
+ {
+ // Act & assert
+ Assert.Throws<ArgumentException>(
+ () => new GenericModelBinderProvider(typeof(List<>), typeof(DictionaryModelBinder<,>)),
+ @"The open model type 'System.Collections.Generic.List`1[T]' has 1 generic type argument(s), but the open binder type 'System.Web.Http.ModelBinding.Binders.DictionaryModelBinder`2[TKey,TValue]' has 2 generic type argument(s). The binder type must not be an open generic type or must have the same number of generic arguments as the open model type.
+Parameter name: modelBinderType");
+ }
+
+ [Fact]
+ public void Constructor_WithType_ThrowsIfModelTypeIsNotOpenGeneric()
+ {
+ // Act & assert
+ Assert.Throws<ArgumentException>(
+ () => new GenericModelBinderProvider(typeof(List<int>), typeof(MutableObjectModelBinder)),
+ @"The type 'System.Collections.Generic.List`1[System.Int32]' is not an open generic type.
+Parameter name: modelType");
+ }
+
+ [Fact]
+ public void Constructor_WithType_ThrowsIfModelTypeIsNull()
+ {
+ // Act & assert
+ Assert.ThrowsArgumentNull(
+ () => new GenericModelBinderProvider(null, typeof(MutableObjectModelBinder)),
+ "modelType");
+ }
+
+ [Fact]
+ public void GetBinder_TypeDoesNotMatch_ModelTypeIsInterface_ReturnsNull()
+ {
+ // Arrange
+ GenericModelBinderProvider provider = new GenericModelBinderProvider(typeof(IEnumerable<>), typeof(CollectionModelBinder<>))
+ {
+ SuppressPrefixCheck = true
+ };
+ ModelBindingContext bindingContext = GetBindingContext(typeof(object));
+
+ // Act
+ IModelBinder binder = provider.GetBinder(null, bindingContext);
+
+ // Assert
+ Assert.Null(binder);
+ }
+
+ [Fact]
+ public void GetBinder_TypeDoesNotMatch_ModelTypeIsNotInterface_ReturnsNull()
+ {
+ // Arrange
+ GenericModelBinderProvider provider = new GenericModelBinderProvider(typeof(List<>), typeof(CollectionModelBinder<>))
+ {
+ SuppressPrefixCheck = true
+ };
+ ModelBindingContext bindingContext = GetBindingContext(typeof(object));
+
+ // Act
+ IModelBinder binder = provider.GetBinder(null, bindingContext);
+
+ // Assert
+ Assert.Null(binder);
+ }
+
+ [Fact]
+ public void GetBinder_TypeMatches_PrefixNotFound_ReturnsNull()
+ {
+ // Arrange
+ IModelBinder binderInstance = new Mock<IModelBinder>().Object;
+ GenericModelBinderProvider provider = new GenericModelBinderProvider(typeof(List<>), binderInstance);
+
+ ModelBindingContext bindingContext = GetBindingContext(typeof(List<int>));
+ bindingContext.ValueProvider = new SimpleHttpValueProvider();
+
+ // Act
+ IModelBinder returnedBinder = provider.GetBinder(null, bindingContext);
+
+ // Assert
+ Assert.Null(returnedBinder);
+ }
+
+ [Fact]
+ public void GetBinder_TypeMatches_Success_Factory_ReturnsBinder()
+ {
+ // Arrange
+ IModelBinder binderInstance = new Mock<IModelBinder>().Object;
+
+ Func<Type[], IModelBinder> binderFactory = typeArguments =>
+ {
+ Assert.Equal(new[] { typeof(int) }, typeArguments);
+ return binderInstance;
+ };
+
+ GenericModelBinderProvider provider = new GenericModelBinderProvider(typeof(IList<>), binderFactory)
+ {
+ SuppressPrefixCheck = true
+ };
+
+ ModelBindingContext bindingContext = GetBindingContext(typeof(List<int>));
+
+ // Act
+ IModelBinder returnedBinder = provider.GetBinder(null, bindingContext);
+
+ // Assert
+ Assert.Same(binderInstance, returnedBinder);
+ }
+
+ [Fact]
+ public void GetBinder_TypeMatches_Success_Instance_ReturnsBinder()
+ {
+ // Arrange
+ IModelBinder binderInstance = new Mock<IModelBinder>().Object;
+
+ GenericModelBinderProvider provider = new GenericModelBinderProvider(typeof(List<>), binderInstance)
+ {
+ SuppressPrefixCheck = true
+ };
+
+ ModelBindingContext bindingContext = GetBindingContext(typeof(List<int>));
+
+ // Act
+ IModelBinder returnedBinder = provider.GetBinder(null, bindingContext);
+
+ // Assert
+ Assert.Same(binderInstance, returnedBinder);
+ }
+
+ [Fact]
+ public void GetBinder_TypeMatches_Success_TypeActivation_ReturnsBinder()
+ {
+ // Arrange
+ GenericModelBinderProvider provider = new GenericModelBinderProvider(typeof(List<>), typeof(CollectionModelBinder<>))
+ {
+ SuppressPrefixCheck = true
+ };
+
+ ModelBindingContext bindingContext = GetBindingContext(typeof(List<int>));
+
+ // Act
+ IModelBinder returnedBinder = provider.GetBinder(null, bindingContext);
+
+ // Assert
+ Assert.IsType<CollectionModelBinder<int>>(returnedBinder);
+ }
+
+ [Fact]
+ public void GetBinderThrowsIfBindingContextIsNull()
+ {
+ // Arrange
+ GenericModelBinderProvider provider = new GenericModelBinderProvider(typeof(IEnumerable<>), typeof(CollectionModelBinder<>));
+
+ // Act & assert
+ Assert.ThrowsArgumentNull(
+ () => provider.GetBinder(null, null),
+ "bindingContext");
+ }
+
+ private static ModelBindingContext GetBindingContext(Type modelType)
+ {
+ return new ModelBindingContext
+ {
+ ModelMetadata = new EmptyModelMetadataProvider().GetMetadataForType(() => null, modelType)
+ };
+ }
+ }
+}
diff --git a/test/System.Web.Http.Test/ModelBinding/Binders/KeyValuePairModelBinderProviderTest.cs b/test/System.Web.Http.Test/ModelBinding/Binders/KeyValuePairModelBinderProviderTest.cs
new file mode 100644
index 00000000..dea8b70e
--- /dev/null
+++ b/test/System.Web.Http.Test/ModelBinding/Binders/KeyValuePairModelBinderProviderTest.cs
@@ -0,0 +1,104 @@
+using System.Collections.Generic;
+using System.Web.Http.Metadata.Providers;
+using System.Web.Http.Util;
+using Xunit;
+
+namespace System.Web.Http.ModelBinding.Binders
+{
+ public class KeyValuePairModelBinderProviderTest
+ {
+ [Fact]
+ public void GetBinder_CorrectModelTypeAndValueProviderEntries_ReturnsBinder()
+ {
+ // Arrange
+ ModelBindingContext bindingContext = new ModelBindingContext
+ {
+ ModelMetadata = new EmptyModelMetadataProvider().GetMetadataForType(null, typeof(KeyValuePair<int, string>)),
+ ModelName = "foo",
+ ValueProvider = new SimpleHttpValueProvider
+ {
+ { "foo.key", 42 },
+ { "foo.value", "someValue" }
+ }
+ };
+
+ KeyValuePairModelBinderProvider binderProvider = new KeyValuePairModelBinderProvider();
+
+ // Act
+ IModelBinder binder = binderProvider.GetBinder(null, bindingContext);
+
+ // Assert
+ Assert.IsType<KeyValuePairModelBinder<int, string>>(binder);
+ }
+
+ [Fact]
+ public void GetBinder_ModelTypeIsIncorrect_ReturnsNull()
+ {
+ // Arrange
+ ModelBindingContext bindingContext = new ModelBindingContext
+ {
+ ModelMetadata = new EmptyModelMetadataProvider().GetMetadataForType(null, typeof(List<int>)),
+ ModelName = "foo",
+ ValueProvider = new SimpleHttpValueProvider
+ {
+ { "foo.key", 42 },
+ { "foo.value", "someValue" }
+ }
+ };
+
+ KeyValuePairModelBinderProvider binderProvider = new KeyValuePairModelBinderProvider();
+
+ // Act
+ IModelBinder binder = binderProvider.GetBinder(null, bindingContext);
+
+ // Assert
+ Assert.Null(binder);
+ }
+
+ [Fact]
+ public void GetBinder_ValueProviderDoesNotContainKeyProperty_ReturnsNull()
+ {
+ // Arrange
+ ModelBindingContext bindingContext = new ModelBindingContext
+ {
+ ModelMetadata = new EmptyModelMetadataProvider().GetMetadataForType(null, typeof(KeyValuePair<int, string>)),
+ ModelName = "foo",
+ ValueProvider = new SimpleHttpValueProvider
+ {
+ { "foo.value", "someValue" }
+ }
+ };
+
+ KeyValuePairModelBinderProvider binderProvider = new KeyValuePairModelBinderProvider();
+
+ // Act
+ IModelBinder binder = binderProvider.GetBinder(null, bindingContext);
+
+ // Assert
+ Assert.Null(binder);
+ }
+
+ [Fact]
+ public void GetBinder_ValueProviderDoesNotContainValueProperty_ReturnsNull()
+ {
+ // Arrange
+ ModelBindingContext bindingContext = new ModelBindingContext
+ {
+ ModelMetadata = new EmptyModelMetadataProvider().GetMetadataForType(null, typeof(KeyValuePair<int, string>)),
+ ModelName = "foo",
+ ValueProvider = new SimpleHttpValueProvider
+ {
+ { "foo.key", 42 }
+ }
+ };
+
+ KeyValuePairModelBinderProvider binderProvider = new KeyValuePairModelBinderProvider();
+
+ // Act
+ IModelBinder binder = binderProvider.GetBinder(null, bindingContext);
+
+ // Assert
+ Assert.Null(binder);
+ }
+ }
+}
diff --git a/test/System.Web.Http.Test/ModelBinding/Binders/KeyValuePairModelBinderTest.cs b/test/System.Web.Http.Test/ModelBinding/Binders/KeyValuePairModelBinderTest.cs
new file mode 100644
index 00000000..4b5fba6c
--- /dev/null
+++ b/test/System.Web.Http.Test/ModelBinding/Binders/KeyValuePairModelBinderTest.cs
@@ -0,0 +1,110 @@
+using System.Collections.Generic;
+using System.Linq;
+using System.Web.Http.Controllers;
+using System.Web.Http.Metadata.Providers;
+using System.Web.Http.Util;
+using Moq;
+using Xunit;
+
+namespace System.Web.Http.ModelBinding.Binders
+{
+ public class KeyValuePairModelBinderTest
+ {
+ [Fact]
+ public void BindModel_MissingKey_ReturnsFalse()
+ {
+ // Arrange
+ KeyValuePairModelBinder<int, string> binder = new KeyValuePairModelBinder<int, string>();
+ ModelBindingContext bindingContext = new ModelBindingContext
+ {
+ ModelMetadata = new EmptyModelMetadataProvider().GetMetadataForType(null, typeof(KeyValuePair<int, string>)),
+ ModelName = "someName",
+ ValueProvider = new SimpleHttpValueProvider()
+ };
+ HttpActionContext context = ContextUtil.CreateActionContext();
+ context.ControllerContext.Configuration.ServiceResolver.SetService(typeof(ModelBinderProvider), new SimpleModelBinderProvider(typeof(KeyValuePair<int, string>), binder));
+
+ // Act
+ bool retVal = binder.BindModel(context, bindingContext);
+
+ // Assert
+ Assert.False(retVal);
+ Assert.Null(bindingContext.Model);
+ Assert.Empty(bindingContext.ValidationNode.ChildNodes);
+ }
+
+ [Fact]
+ public void BindModel_MissingValue_ReturnsTrue()
+ {
+ // Arrange
+ Mock<IModelBinder> mockIntBinder = new Mock<IModelBinder>();
+ ModelBindingContext bindingContext = new ModelBindingContext
+ {
+ ModelMetadata = new EmptyModelMetadataProvider().GetMetadataForType(null, typeof(KeyValuePair<int, string>)),
+ ModelName = "someName",
+ ValueProvider = new SimpleHttpValueProvider()
+ };
+ HttpActionContext context = ContextUtil.CreateActionContext();
+ context.ControllerContext.Configuration.ServiceResolver.SetService(typeof(ModelBinderProvider), new SimpleModelBinderProvider(typeof(int), mockIntBinder.Object) { SuppressPrefixCheck = true });
+
+ mockIntBinder
+ .Setup(o => o.BindModel(context, It.IsAny<ModelBindingContext>()))
+ .Returns((HttpActionContext cc, ModelBindingContext mbc) =>
+ {
+ mbc.Model = 42;
+ return true;
+ });
+ KeyValuePairModelBinder<int, string> binder = new KeyValuePairModelBinder<int, string>();
+
+ // Act
+ bool retVal = binder.BindModel(context, bindingContext);
+
+ // Assert
+ Assert.True(retVal);
+ Assert.Null(bindingContext.Model);
+ Assert.Equal(new[] { "someName.key" }, bindingContext.ValidationNode.ChildNodes.Select(n => n.ModelStateKey).ToArray());
+ }
+
+ [Fact]
+ public void BindModel_SubBindingSucceeds()
+ {
+ // Arrange
+ Mock<IModelBinder> mockIntBinder = new Mock<IModelBinder>();
+ Mock<IModelBinder> mockStringBinder = new Mock<IModelBinder>();
+ ModelBindingContext bindingContext = new ModelBindingContext
+ {
+ ModelMetadata = new EmptyModelMetadataProvider().GetMetadataForType(null, typeof(KeyValuePair<int, string>)),
+ ModelName = "someName",
+ ValueProvider = new SimpleHttpValueProvider()
+ };
+ HttpActionContext context = ContextUtil.CreateActionContext();
+ context.ControllerContext.Configuration.ServiceResolver.SetServices(typeof(ModelBinderProvider),
+ new SimpleModelBinderProvider(typeof(int), mockIntBinder.Object) { SuppressPrefixCheck = true },
+ new SimpleModelBinderProvider(typeof(string), mockStringBinder.Object) { SuppressPrefixCheck = true });
+
+ mockIntBinder
+ .Setup(o => o.BindModel(context, It.IsAny<ModelBindingContext>()))
+ .Returns((HttpActionContext cc, ModelBindingContext mbc) =>
+ {
+ mbc.Model = 42;
+ return true;
+ });
+ mockStringBinder
+ .Setup(o => o.BindModel(context, It.IsAny<ModelBindingContext>()))
+ .Returns((HttpActionContext cc, ModelBindingContext mbc) =>
+ {
+ mbc.Model = "forty-two";
+ return true;
+ });
+ KeyValuePairModelBinder<int, string> binder = new KeyValuePairModelBinder<int, string>();
+
+ // Act
+ bool retVal = binder.BindModel(context, bindingContext);
+
+ // Assert
+ Assert.True(retVal);
+ Assert.Equal(new KeyValuePair<int, string>(42, "forty-two"), bindingContext.Model);
+ Assert.Equal(new[] { "someName.key", "someName.value" }, bindingContext.ValidationNode.ChildNodes.Select(n => n.ModelStateKey).ToArray());
+ }
+ }
+}
diff --git a/test/System.Web.Http.Test/ModelBinding/Binders/KeyValuePairModelBinderUtilTest.cs b/test/System.Web.Http.Test/ModelBinding/Binders/KeyValuePairModelBinderUtilTest.cs
new file mode 100644
index 00000000..a4adce06
--- /dev/null
+++ b/test/System.Web.Http.Test/ModelBinding/Binders/KeyValuePairModelBinderUtilTest.cs
@@ -0,0 +1,105 @@
+using System.Web.Http.Controllers;
+using System.Web.Http.Internal;
+using System.Web.Http.Metadata.Providers;
+using System.Web.Http.Util;
+using Moq;
+using Xunit;
+
+namespace System.Web.Http.ModelBinding.Binders
+{
+ public class KeyValuePairModelBinderUtilTest
+ {
+ [Fact]
+ public void TryBindStrongModel_BinderExists_BinderReturnsCorrectlyTypedObject_ReturnsTrue()
+ {
+ // Arrange
+ Mock<IModelBinder> mockIntBinder = new Mock<IModelBinder>();
+ ModelBindingContext bindingContext = new ModelBindingContext
+ {
+ ModelMetadata = new EmptyModelMetadataProvider().GetMetadataForType(null, typeof(int)),
+ ModelName = "someName",
+ ModelState = new ModelStateDictionary(),
+ ValueProvider = new SimpleHttpValueProvider()
+ };
+ HttpActionContext context = ContextUtil.CreateActionContext();
+ context.ControllerContext.Configuration.ServiceResolver.SetService(typeof(ModelBinderProvider), new SimpleModelBinderProvider(typeof(int), mockIntBinder.Object) { SuppressPrefixCheck = true });
+
+ mockIntBinder
+ .Setup(o => o.BindModel(context, It.IsAny<ModelBindingContext>()))
+ .Returns((HttpActionContext cc, ModelBindingContext mbc) =>
+ {
+ Assert.Equal("someName.key", mbc.ModelName);
+ mbc.Model = 42;
+ return true;
+ });
+
+ // Act
+ int model;
+ bool retVal = context.TryBindStrongModel(bindingContext, "key", new EmptyModelMetadataProvider(), out model);
+
+ // Assert
+ Assert.True(retVal);
+ Assert.Equal(42, model);
+ Assert.Single(bindingContext.ValidationNode.ChildNodes);
+ Assert.Empty(bindingContext.ModelState);
+ }
+
+ [Fact]
+ public void TryBindStrongModel_BinderExists_BinderReturnsIncorrectlyTypedObject_ReturnsTrue()
+ {
+ // Arrange
+ Mock<IModelBinder> mockIntBinder = new Mock<IModelBinder>();
+ ModelBindingContext bindingContext = new ModelBindingContext
+ {
+ ModelMetadata = new EmptyModelMetadataProvider().GetMetadataForType(null, typeof(int)),
+ ModelName = "someName",
+ ModelState = new ModelStateDictionary(),
+ ValueProvider = new SimpleHttpValueProvider()
+ };
+ HttpActionContext context = ContextUtil.CreateActionContext();
+ context.ControllerContext.Configuration.ServiceResolver.SetService(typeof(ModelBinderProvider), new SimpleModelBinderProvider(typeof(int), mockIntBinder.Object) { SuppressPrefixCheck = true });
+
+ mockIntBinder
+ .Setup(o => o.BindModel(context, It.IsAny<ModelBindingContext>()))
+ .Returns((HttpActionContext cc, ModelBindingContext mbc) =>
+ {
+ Assert.Equal("someName.key", mbc.ModelName);
+ return true;
+ });
+
+ // Act
+ int model;
+ bool retVal = context.TryBindStrongModel(bindingContext, "key", new EmptyModelMetadataProvider(), out model);
+
+ // Assert
+ Assert.True(retVal);
+ Assert.Equal(default(int), model);
+ Assert.Single(bindingContext.ValidationNode.ChildNodes);
+ Assert.Empty(bindingContext.ModelState);
+ }
+
+ [Fact]
+ public void TryBindStrongModel_NoBinder_ReturnsFalse()
+ {
+ // Arrange
+ HttpActionContext context = ContextUtil.CreateActionContext();
+ ModelBindingContext bindingContext = new ModelBindingContext
+ {
+ ModelMetadata = new EmptyModelMetadataProvider().GetMetadataForType(null, typeof(int)),
+ ModelName = "someName",
+ ModelState = new ModelStateDictionary(),
+ ValueProvider = new SimpleHttpValueProvider()
+ };
+
+ // Act
+ int model;
+ bool retVal = context.TryBindStrongModel(bindingContext, "key", new EmptyModelMetadataProvider(), out model);
+
+ // Assert
+ Assert.False(retVal);
+ Assert.Equal(default(int), model);
+ Assert.Empty(bindingContext.ValidationNode.ChildNodes);
+ Assert.Empty(bindingContext.ModelState);
+ }
+ }
+}
diff --git a/test/System.Web.Http.Test/ModelBinding/Binders/MutableObjectModelBinderProviderTest.cs b/test/System.Web.Http.Test/ModelBinding/Binders/MutableObjectModelBinderProviderTest.cs
new file mode 100644
index 00000000..60863755
--- /dev/null
+++ b/test/System.Web.Http.Test/ModelBinding/Binders/MutableObjectModelBinderProviderTest.cs
@@ -0,0 +1,103 @@
+using System.Web.Http.Metadata.Providers;
+using System.Web.Http.Util;
+using Xunit;
+
+namespace System.Web.Http.ModelBinding.Binders
+{
+ public class MutableObjectModelBinderProviderTest
+ {
+ [Fact]
+ public void GetBinder_NoPrefixInValueProvider_ReturnsNull()
+ {
+ // Arrange
+ ModelBindingContext bindingContext = new ModelBindingContext
+ {
+ ModelMetadata = new EmptyModelMetadataProvider().GetMetadataForType(() => 42, typeof(int)),
+ ModelName = "foo",
+ ValueProvider = new SimpleHttpValueProvider()
+ };
+
+ MutableObjectModelBinderProvider binderProvider = new MutableObjectModelBinderProvider();
+
+ // Act
+ IModelBinder binder = binderProvider.GetBinder(null, bindingContext);
+
+ // Assert
+ Assert.Null(binder);
+ }
+
+ [Fact]
+ public void GetBinder_PrefixInValueProvider_ComplexType_ReturnsBinder()
+ {
+ // Arrange
+ ModelBindingContext bindingContext = new ModelBindingContext
+ {
+ ModelMetadata = new EmptyModelMetadataProvider().GetMetadataForType(() => new MutableTestType(), typeof(MutableTestType)),
+ ModelName = "foo",
+ ValueProvider = new SimpleHttpValueProvider
+ {
+ { "foo.bar", "someValue" }
+ }
+ };
+
+ MutableObjectModelBinderProvider binderProvider = new MutableObjectModelBinderProvider();
+
+ // Act
+ IModelBinder binder = binderProvider.GetBinder(null, bindingContext);
+
+ // Assert
+ Assert.NotNull(binder);
+ Assert.IsType<MutableObjectModelBinder>(binder);
+ }
+
+ [Fact]
+ public void GetBinder_PrefixInValueProvider_SimpleType_ReturnsNull()
+ {
+ // Arrange
+ ModelBindingContext bindingContext = new ModelBindingContext
+ {
+ ModelMetadata = new EmptyModelMetadataProvider().GetMetadataForType(() => 42, typeof(int)),
+ ModelName = "foo",
+ ValueProvider = new SimpleHttpValueProvider
+ {
+ { "foo.bar", "someValue" }
+ }
+ };
+
+ MutableObjectModelBinderProvider binderProvider = new MutableObjectModelBinderProvider();
+
+ // Act
+ IModelBinder binder = binderProvider.GetBinder(null, bindingContext);
+
+ // Assert
+ Assert.Null(binder);
+ }
+
+ [Fact]
+ public void GetBinder_TypeIsComplexModelDto_ReturnsNull()
+ {
+ // Arrange
+ ModelBindingContext bindingContext = new ModelBindingContext
+ {
+ ModelMetadata = new EmptyModelMetadataProvider().GetMetadataForType(null, typeof(ComplexModelDto)),
+ ModelName = "foo",
+ ValueProvider = new SimpleHttpValueProvider
+ {
+ { "foo.bar", "someValue" }
+ }
+ };
+
+ MutableObjectModelBinderProvider binderProvider = new MutableObjectModelBinderProvider();
+
+ // Act
+ IModelBinder binder = binderProvider.GetBinder(null, bindingContext);
+
+ // Assert
+ Assert.Null(binder);
+ }
+
+ class MutableTestType
+ {
+ }
+ }
+}
diff --git a/test/System.Web.Http.Test/ModelBinding/Binders/MutableObjectModelBinderTest.cs b/test/System.Web.Http.Test/ModelBinding/Binders/MutableObjectModelBinderTest.cs
new file mode 100644
index 00000000..081eda7a
--- /dev/null
+++ b/test/System.Web.Http.Test/ModelBinding/Binders/MutableObjectModelBinderTest.cs
@@ -0,0 +1,737 @@
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.ComponentModel.DataAnnotations;
+using System.Linq;
+using System.Web.Http.Controllers;
+using System.Web.Http.Metadata;
+using System.Web.Http.Metadata.Providers;
+using System.Web.Http.Validation;
+using Moq;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Http.ModelBinding.Binders
+{
+ public class MutableObjectModelBinderTest
+ {
+ [Fact]
+ public void BindModel()
+ {
+ // Arrange
+ Mock<IModelBinder> mockDtoBinder = new Mock<IModelBinder>();
+ ModelBindingContext bindingContext = new ModelBindingContext
+ {
+ ModelMetadata = GetMetadataForObject(new Person()),
+ ModelName = "someName"
+ };
+ HttpActionContext context = ContextUtil.CreateActionContext();
+ context.ControllerContext.Configuration.ServiceResolver.SetService(typeof(ModelBinderProvider), new SimpleModelBinderProvider(typeof(ComplexModelDto), mockDtoBinder.Object) { SuppressPrefixCheck = true });
+
+ mockDtoBinder
+ .Setup(o => o.BindModel(context, It.IsAny<ModelBindingContext>()))
+ .Returns((HttpActionContext cc, ModelBindingContext mbc2) =>
+ {
+ return true; // just return the DTO unchanged
+ });
+
+ Mock<TestableMutableObjectModelBinder> mockTestableBinder = new Mock<TestableMutableObjectModelBinder> { CallBase = true };
+ mockTestableBinder.Setup(o => o.EnsureModelPublic(context, bindingContext)).Verifiable();
+ mockTestableBinder.Setup(o => o.GetMetadataForPropertiesPublic(context, bindingContext)).Returns(new ModelMetadata[0]).Verifiable();
+ TestableMutableObjectModelBinder testableBinder = mockTestableBinder.Object;
+ testableBinder.MetadataProvider = new CachedDataAnnotationsModelMetadataProvider();
+
+ // Act
+ bool retValue = testableBinder.BindModel(context, bindingContext);
+
+ // Assert
+ Assert.True(retValue);
+ Assert.IsType<Person>(bindingContext.Model);
+ Assert.True(bindingContext.ValidationNode.ValidateAllProperties);
+ mockTestableBinder.Verify();
+ }
+
+ [Fact]
+ public void CanUpdateProperty_HasPublicSetter_ReturnsTrue()
+ {
+ // Arrange
+ ModelMetadata propertyMetadata = GetMetadataForCanUpdateProperty("ReadWriteString");
+
+ // Act
+ bool canUpdate = MutableObjectModelBinder.CanUpdatePropertyInternal(propertyMetadata);
+
+ // Assert
+ Assert.True(canUpdate);
+ }
+
+ [Fact]
+ public void CanUpdateProperty_ReadOnlyArray_ReturnsFalse()
+ {
+ // Arrange
+ ModelMetadata propertyMetadata = GetMetadataForCanUpdateProperty("ReadOnlyArray");
+
+ // Act
+ bool canUpdate = MutableObjectModelBinder.CanUpdatePropertyInternal(propertyMetadata);
+
+ // Assert
+ Assert.False(canUpdate);
+ }
+
+ [Fact]
+ public void CanUpdateProperty_ReadOnlyReferenceTypeNotBlacklisted_ReturnsTrue()
+ {
+ // Arrange
+ ModelMetadata propertyMetadata = GetMetadataForCanUpdateProperty("ReadOnlyObject");
+
+ // Act
+ bool canUpdate = MutableObjectModelBinder.CanUpdatePropertyInternal(propertyMetadata);
+
+ // Assert
+ Assert.True(canUpdate);
+ }
+
+ [Fact]
+ public void CanUpdateProperty_ReadOnlyString_ReturnsFalse()
+ {
+ // Arrange
+ ModelMetadata propertyMetadata = GetMetadataForCanUpdateProperty("ReadOnlyString");
+
+ // Act
+ bool canUpdate = MutableObjectModelBinder.CanUpdatePropertyInternal(propertyMetadata);
+
+ // Assert
+ Assert.False(canUpdate);
+ }
+
+ [Fact]
+ public void CanUpdateProperty_ReadOnlyValueType_ReturnsFalse()
+ {
+ // Arrange
+ ModelMetadata propertyMetadata = GetMetadataForCanUpdateProperty("ReadOnlyInt");
+
+ // Act
+ bool canUpdate = MutableObjectModelBinder.CanUpdatePropertyInternal(propertyMetadata);
+
+ // Assert
+ Assert.False(canUpdate);
+ }
+
+ [Fact]
+ public void CreateModel()
+ {
+ // Arrange
+ ModelBindingContext bindingContext = new ModelBindingContext
+ {
+ ModelMetadata = GetMetadataForType(typeof(Person))
+ };
+
+ TestableMutableObjectModelBinder testableBinder = new TestableMutableObjectModelBinder();
+
+ // Act
+ object retModel = testableBinder.CreateModelPublic(null, bindingContext);
+
+ // Assert
+ Assert.IsType<Person>(retModel);
+ }
+
+ [Fact]
+ public void EnsureModel_ModelIsNotNull_DoesNothing()
+ {
+ // Arrange
+ ModelBindingContext bindingContext = new ModelBindingContext
+ {
+ ModelMetadata = GetMetadataForObject(new Person())
+ };
+
+ Mock<TestableMutableObjectModelBinder> mockTestableBinder = new Mock<TestableMutableObjectModelBinder> { CallBase = true };
+ TestableMutableObjectModelBinder testableBinder = mockTestableBinder.Object;
+
+ // Act
+ object originalModel = bindingContext.Model;
+ testableBinder.EnsureModelPublic(null, bindingContext);
+ object newModel = bindingContext.Model;
+
+ // Assert
+ Assert.Same(originalModel, newModel);
+ mockTestableBinder.Verify(o => o.CreateModelPublic(null, bindingContext), Times.Never());
+ }
+
+ [Fact]
+ public void EnsureModel_ModelIsNull_CallsCreateModel()
+ {
+ // Arrange
+ ModelBindingContext bindingContext = new ModelBindingContext
+ {
+ ModelMetadata = GetMetadataForType(typeof(Person))
+ };
+
+ Mock<TestableMutableObjectModelBinder> mockTestableBinder = new Mock<TestableMutableObjectModelBinder> { CallBase = true };
+ mockTestableBinder.Setup(o => o.CreateModelPublic(null, bindingContext)).Returns(new Person()).Verifiable();
+ TestableMutableObjectModelBinder testableBinder = mockTestableBinder.Object;
+
+ // Act
+ object originalModel = bindingContext.Model;
+ testableBinder.EnsureModelPublic(null, bindingContext);
+ object newModel = bindingContext.Model;
+
+ // Assert
+ Assert.Null(originalModel);
+ Assert.IsType<Person>(newModel);
+ mockTestableBinder.Verify();
+ }
+
+ [Fact]
+ public void GetMetadataForProperties_WithBindAttribute()
+ {
+ // Arrange
+ string[] expectedPropertyNames = new[] { "FirstName", "LastName" };
+
+ ModelBindingContext bindingContext = new ModelBindingContext
+ {
+ ModelMetadata = GetMetadataForType(typeof(PersonWithBindExclusion))
+ };
+
+ TestableMutableObjectModelBinder testableBinder = new TestableMutableObjectModelBinder();
+
+ // Act
+ IEnumerable<ModelMetadata> propertyMetadatas = testableBinder.GetMetadataForPropertiesPublic(null, bindingContext);
+ string[] returnedPropertyNames = propertyMetadatas.Select(o => o.PropertyName).ToArray();
+
+ // Assert
+ Assert.Equal(expectedPropertyNames, returnedPropertyNames);
+ }
+
+ [Fact]
+ public void GetMetadataForProperties_WithoutBindAttribute()
+ {
+ // Arrange
+ string[] expectedPropertyNames = new[] { "DateOfBirth", "DateOfDeath", "ValueTypeRequired", "FirstName", "LastName", "PropertyWithDefaultValue" };
+
+ ModelBindingContext bindingContext = new ModelBindingContext
+ {
+ ModelMetadata = GetMetadataForType(typeof(Person))
+ };
+
+ TestableMutableObjectModelBinder testableBinder = new TestableMutableObjectModelBinder();
+
+ // Act
+ IEnumerable<ModelMetadata> propertyMetadatas = testableBinder.GetMetadataForPropertiesPublic(null, bindingContext);
+ string[] returnedPropertyNames = propertyMetadatas.Select(o => o.PropertyName).ToArray();
+
+ // Assert
+ Assert.Equal(expectedPropertyNames, returnedPropertyNames);
+ }
+
+ [Fact]
+ public void GetRequiredPropertiesCollection_MixedAttributes()
+ {
+ // Arrange
+ Type modelType = typeof(ModelWithMixedBindingBehaviors);
+
+ // Act
+ HashSet<string> requiredProperties;
+ HashSet<string> skipProperties;
+ MutableObjectModelBinder.GetRequiredPropertiesCollection(modelType, out requiredProperties, out skipProperties);
+
+ // Assert
+ Assert.Equal(new[] { "Required" }, requiredProperties.ToArray());
+ Assert.Equal(new[] { "Never" }, skipProperties.ToArray());
+ }
+
+ [Fact]
+ public void NullCheckFailedHandler_ModelStateAlreadyInvalid_DoesNothing()
+ {
+ // Arrange
+ HttpActionContext context = ContextUtil.CreateActionContext();
+ context.ModelState.AddModelError("foo.bar", "Some existing error.");
+
+ ModelMetadata modelMetadata = GetMetadataForType(typeof(Person));
+ ModelValidationNode validationNode = new ModelValidationNode(modelMetadata, "foo");
+ ModelValidatedEventArgs e = new ModelValidatedEventArgs(context, null /* parentNode */);
+
+ // Act
+ EventHandler<ModelValidatedEventArgs> handler = MutableObjectModelBinder.CreateNullCheckFailedHandler(modelMetadata, null /* incomingValue */);
+ handler(validationNode, e);
+
+ // Assert
+ Assert.False(context.ModelState.ContainsKey("foo"));
+ }
+
+ [Fact]
+ public void NullCheckFailedHandler_ModelStateValid_AddsErrorString()
+ {
+ // Arrange
+ HttpActionContext context = ContextUtil.CreateActionContext();
+ ModelMetadata modelMetadata = GetMetadataForType(typeof(Person));
+ ModelValidationNode validationNode = new ModelValidationNode(modelMetadata, "foo");
+ ModelValidatedEventArgs e = new ModelValidatedEventArgs(context, null /* parentNode */);
+
+ // Act
+ EventHandler<ModelValidatedEventArgs> handler = MutableObjectModelBinder.CreateNullCheckFailedHandler(modelMetadata, null /* incomingValue */);
+ handler(validationNode, e);
+
+ // Assert
+ Assert.True(context.ModelState.ContainsKey("foo"));
+ Assert.Equal("A value is required.", context.ModelState["foo"].Errors[0].ErrorMessage);
+ }
+
+ [Fact]
+ public void NullCheckFailedHandler_ModelStateValid_CallbackReturnsNull_DoesNothing()
+ {
+ // Arrange
+ HttpActionContext context = ContextUtil.CreateActionContext();
+ ModelMetadata modelMetadata = GetMetadataForType(typeof(Person));
+ ModelValidationNode validationNode = new ModelValidationNode(modelMetadata, "foo");
+ ModelValidatedEventArgs e = new ModelValidatedEventArgs(context, null /* parentNode */);
+
+ // Act
+ ModelBinderErrorMessageProvider originalProvider = ModelBinderConfig.ValueRequiredErrorMessageProvider;
+ try
+ {
+ ModelBinderConfig.ValueRequiredErrorMessageProvider = (ec, mm, value) => null;
+ EventHandler<ModelValidatedEventArgs> handler = MutableObjectModelBinder.CreateNullCheckFailedHandler(modelMetadata, null /* incomingValue */);
+ handler(validationNode, e);
+ }
+ finally
+ {
+ ModelBinderConfig.ValueRequiredErrorMessageProvider = originalProvider;
+ }
+
+ // Assert
+ Assert.True(context.ModelState.IsValid);
+ }
+
+ [Fact]
+ public void ProcessDto_BindRequiredFieldMissing_Throws()
+ {
+ // Arrange
+ ModelWithBindRequired model = new ModelWithBindRequired
+ {
+ Name = "original value",
+ Age = -20
+ };
+
+ ModelMetadata containerMetadata = GetMetadataForObject(model);
+
+ HttpActionContext context = ContextUtil.CreateActionContext();
+ ModelBindingContext bindingContext = new ModelBindingContext
+ {
+ ModelMetadata = containerMetadata,
+ ModelName = "theModel"
+ };
+ ComplexModelDto dto = new ComplexModelDto(containerMetadata, containerMetadata.Properties);
+
+ ModelMetadata nameProperty = dto.PropertyMetadata.Single(o => o.PropertyName == "Name");
+ dto.Results[nameProperty] = new ComplexModelDtoResult("John Doe", new ModelValidationNode(nameProperty, ""));
+
+ TestableMutableObjectModelBinder testableBinder = new TestableMutableObjectModelBinder();
+
+ // Act & assert
+ Assert.Throws<InvalidOperationException>(
+ () => testableBinder.ProcessDto(context, bindingContext, dto),
+ @"A value for 'theModel.Age' is required but was not present in the request.");
+
+ Assert.Equal("original value", model.Name);
+ Assert.Equal(-20, model.Age);
+ }
+
+ [Fact]
+ public void ProcessDto_Success()
+ {
+ // Arrange
+ DateTime dob = new DateTime(2001, 1, 1);
+ Person model = new Person
+ {
+ DateOfBirth = dob
+ };
+ ModelMetadata containerMetadata = GetMetadataForObject(model);
+
+ HttpActionContext context = ContextUtil.CreateActionContext();
+ ModelBindingContext bindingContext = new ModelBindingContext
+ {
+ ModelMetadata = containerMetadata
+ };
+ ComplexModelDto dto = new ComplexModelDto(containerMetadata, containerMetadata.Properties);
+
+ ModelMetadata firstNameProperty = dto.PropertyMetadata.Single(o => o.PropertyName == "FirstName");
+ dto.Results[firstNameProperty] = new ComplexModelDtoResult("John", new ModelValidationNode(firstNameProperty, ""));
+ ModelMetadata lastNameProperty = dto.PropertyMetadata.Single(o => o.PropertyName == "LastName");
+ dto.Results[lastNameProperty] = new ComplexModelDtoResult("Doe", new ModelValidationNode(lastNameProperty, ""));
+ ModelMetadata dobProperty = dto.PropertyMetadata.Single(o => o.PropertyName == "DateOfBirth");
+ dto.Results[dobProperty] = null;
+
+ TestableMutableObjectModelBinder testableBinder = new TestableMutableObjectModelBinder();
+
+ // Act
+ testableBinder.ProcessDto(context, bindingContext, dto);
+
+ // Assert
+ Assert.Equal("John", model.FirstName);
+ Assert.Equal("Doe", model.LastName);
+ Assert.Equal(dob, model.DateOfBirth);
+ Assert.True(bindingContext.ModelState.IsValid);
+ }
+
+ [Fact]
+ public void SetProperty_PropertyHasDefaultValue_SetsDefaultValue()
+ {
+ // Arrange
+ HttpActionContext context = ContextUtil.CreateActionContext();
+
+ ModelBindingContext bindingContext = new ModelBindingContext
+ {
+ ModelMetadata = GetMetadataForObject(new Person())
+ };
+
+ ModelMetadata propertyMetadata = bindingContext.ModelMetadata.Properties.Single(o => o.PropertyName == "PropertyWithDefaultValue");
+ ModelValidationNode validationNode = new ModelValidationNode(propertyMetadata, "foo");
+ ComplexModelDtoResult dtoResult = new ComplexModelDtoResult(null /* model */, validationNode);
+
+ TestableMutableObjectModelBinder testableBinder = new TestableMutableObjectModelBinder();
+
+ // Act
+ testableBinder.SetPropertyPublic(context, bindingContext, propertyMetadata, dtoResult);
+
+ // Assert
+ var person = Assert.IsType<Person>(bindingContext.Model);
+ Assert.Equal(123.456m, person.PropertyWithDefaultValue);
+ Assert.True(context.ModelState.IsValid);
+ }
+
+ [Fact]
+ public void SetProperty_PropertyIsReadOnly_DoesNothing()
+ {
+ // Arrange
+ ModelBindingContext bindingContext = new ModelBindingContext
+ {
+ ModelMetadata = GetMetadataForType(typeof(Person))
+ };
+
+ ModelMetadata propertyMetadata = bindingContext.ModelMetadata.Properties.Single(o => o.PropertyName == "NonUpdateableProperty");
+ ModelValidationNode validationNode = new ModelValidationNode(propertyMetadata, "foo");
+ ComplexModelDtoResult dtoResult = new ComplexModelDtoResult(null /* model */, validationNode);
+
+ TestableMutableObjectModelBinder testableBinder = new TestableMutableObjectModelBinder();
+
+ // Act
+ testableBinder.SetPropertyPublic(null, bindingContext, propertyMetadata, dtoResult);
+
+ // Assert
+ // If didn't throw, success!
+ }
+
+ [Fact]
+ public void SetProperty_PropertyIsSettable_CallsSetter()
+ {
+ // Arrange
+ Person model = new Person();
+ HttpActionContext context = ContextUtil.CreateActionContext();
+ ModelBindingContext bindingContext = new ModelBindingContext
+ {
+ ModelMetadata = GetMetadataForObject(model)
+ };
+
+ ModelMetadata propertyMetadata = bindingContext.ModelMetadata.Properties.Single(o => o.PropertyName == "DateOfBirth");
+ ModelValidationNode validationNode = new ModelValidationNode(propertyMetadata, "foo");
+ ComplexModelDtoResult dtoResult = new ComplexModelDtoResult(new DateTime(2001, 1, 1), validationNode);
+
+ TestableMutableObjectModelBinder testableBinder = new TestableMutableObjectModelBinder();
+
+ // Act
+ testableBinder.SetPropertyPublic(context, bindingContext, propertyMetadata, dtoResult);
+
+ // Assert
+ validationNode.Validate(context);
+ Assert.True(context.ModelState.IsValid);
+ Assert.Equal(new DateTime(2001, 1, 1), model.DateOfBirth);
+ }
+
+ [Fact]
+ public void SetProperty_PropertyIsSettable_SetterThrows_RecordsError()
+ {
+ // Arrange
+ Person model = new Person
+ {
+ DateOfBirth = new DateTime(1900, 1, 1)
+ };
+ ModelBindingContext bindingContext = new ModelBindingContext
+ {
+ ModelMetadata = GetMetadataForObject(model)
+ };
+
+ ModelMetadata propertyMetadata = bindingContext.ModelMetadata.Properties.Single(o => o.PropertyName == "DateOfDeath");
+ ModelValidationNode validationNode = new ModelValidationNode(propertyMetadata, "foo");
+ ComplexModelDtoResult dtoResult = new ComplexModelDtoResult(new DateTime(1800, 1, 1), validationNode);
+
+ TestableMutableObjectModelBinder testableBinder = new TestableMutableObjectModelBinder();
+
+ // Act
+ testableBinder.SetPropertyPublic(null, bindingContext, propertyMetadata, dtoResult);
+
+ // Assert
+ Assert.Equal(@"Date of death can't be before date of birth.
+Parameter name: value", bindingContext.ModelState["foo"].Errors[0].Exception.Message);
+ }
+
+ [Fact]
+ public void SetProperty_SettingNonNullableValueTypeToNull_RequiredValidatorNotPresent_RegistersValidationCallback()
+ {
+ // Arrange
+ HttpActionContext context = ContextUtil.CreateActionContext();
+ ModelBindingContext bindingContext = new ModelBindingContext
+ {
+ ModelMetadata = GetMetadataForObject(new Person()),
+ };
+
+ ModelMetadata propertyMetadata = bindingContext.ModelMetadata.Properties.Single(o => o.PropertyName == "DateOfBirth");
+ ModelValidationNode validationNode = new ModelValidationNode(propertyMetadata, "foo");
+ ComplexModelDtoResult dtoResult = new ComplexModelDtoResult(null /* model */, validationNode);
+
+ TestableMutableObjectModelBinder testableBinder = new TestableMutableObjectModelBinder();
+
+ // Act
+ testableBinder.SetPropertyPublic(context, bindingContext, propertyMetadata, dtoResult);
+
+ // Assert
+ Assert.True(context.ModelState.IsValid);
+ validationNode.Validate(context, bindingContext.ValidationNode);
+ Assert.False(context.ModelState.IsValid);
+ }
+
+ [Fact]
+ public void SetProperty_SettingNonNullableValueTypeToNull_RequiredValidatorPresent_AddsModelError()
+ {
+ // Arrange
+ HttpActionContext context = ContextUtil.CreateActionContext();
+ ModelBindingContext bindingContext = new ModelBindingContext
+ {
+ ModelMetadata = GetMetadataForObject(new Person()),
+ ModelName = "foo"
+ };
+
+ ModelMetadata propertyMetadata = bindingContext.ModelMetadata.Properties.Single(o => o.PropertyName == "ValueTypeRequired");
+ ModelValidationNode validationNode = new ModelValidationNode(propertyMetadata, "foo.ValueTypeRequired");
+ ComplexModelDtoResult dtoResult = new ComplexModelDtoResult(null /* model */, validationNode);
+
+ TestableMutableObjectModelBinder testableBinder = new TestableMutableObjectModelBinder();
+
+ // Act
+ testableBinder.SetPropertyPublic(context, bindingContext, propertyMetadata, dtoResult);
+
+ // Assert
+ Assert.False(bindingContext.ModelState.IsValid);
+ Assert.Equal("Sample message", bindingContext.ModelState["foo.ValueTypeRequired"].Errors[0].ErrorMessage);
+ }
+
+ [Fact]
+ public void SetProperty_SettingNullableTypeToNull_RequiredValidatorNotPresent_PropertySetterThrows_AddsRequiredMessageString()
+ {
+ // Arrange
+ HttpActionContext context = ContextUtil.CreateActionContext();
+ ModelBindingContext bindingContext = new ModelBindingContext
+ {
+ ModelMetadata = GetMetadataForObject(new ModelWhosePropertySetterThrows()),
+ ModelName = "foo"
+ };
+
+ ModelMetadata propertyMetadata = bindingContext.ModelMetadata.Properties.Single(o => o.PropertyName == "NameNoAttribute");
+ ModelValidationNode validationNode = new ModelValidationNode(propertyMetadata, "foo.NameNoAttribute");
+ ComplexModelDtoResult dtoResult = new ComplexModelDtoResult(null /* model */, validationNode);
+
+ TestableMutableObjectModelBinder testableBinder = new TestableMutableObjectModelBinder();
+
+ // Act
+ testableBinder.SetPropertyPublic(context, bindingContext, propertyMetadata, dtoResult);
+
+ // Assert
+ Assert.False(bindingContext.ModelState.IsValid);
+ Assert.Equal(1, bindingContext.ModelState["foo.NameNoAttribute"].Errors.Count);
+ Assert.Equal(@"This is a different exception.
+Parameter name: value", bindingContext.ModelState["foo.NameNoAttribute"].Errors[0].Exception.Message);
+ }
+
+ [Fact]
+ public void SetProperty_SettingNullableTypeToNull_RequiredValidatorPresent_PropertySetterThrows_AddsRequiredMessageString()
+ {
+ // Arrange
+ HttpActionContext context = ContextUtil.CreateActionContext();
+ ModelBindingContext bindingContext = new ModelBindingContext
+ {
+ ModelMetadata = GetMetadataForObject(new ModelWhosePropertySetterThrows()),
+ ModelName = "foo"
+ };
+
+ ModelMetadata propertyMetadata = bindingContext.ModelMetadata.Properties.Single(o => o.PropertyName == "Name");
+ ModelValidationNode validationNode = new ModelValidationNode(propertyMetadata, "foo.Name");
+ ComplexModelDtoResult dtoResult = new ComplexModelDtoResult(null /* model */, validationNode);
+
+ TestableMutableObjectModelBinder testableBinder = new TestableMutableObjectModelBinder();
+
+ // Act
+ testableBinder.SetPropertyPublic(context, bindingContext, propertyMetadata, dtoResult);
+
+ // Assert
+ Assert.False(bindingContext.ModelState.IsValid);
+ Assert.Equal(1, bindingContext.ModelState["foo.Name"].Errors.Count);
+ Assert.Equal("This message comes from the [Required] attribute.", bindingContext.ModelState["foo.Name"].Errors[0].ErrorMessage);
+ }
+
+ private static ModelMetadata GetMetadataForCanUpdateProperty(string propertyName)
+ {
+ CachedDataAnnotationsModelMetadataProvider metadataProvider = new CachedDataAnnotationsModelMetadataProvider();
+ return metadataProvider.GetMetadataForProperty(null, typeof(MyModelTestingCanUpdateProperty), propertyName);
+ }
+
+ private static ModelMetadata GetMetadataForObject(object o)
+ {
+ CachedDataAnnotationsModelMetadataProvider metadataProvider = new CachedDataAnnotationsModelMetadataProvider();
+ return metadataProvider.GetMetadataForType(() => o, o.GetType());
+ }
+
+ private static ModelMetadata GetMetadataForType(Type t)
+ {
+ CachedDataAnnotationsModelMetadataProvider metadataProvider = new CachedDataAnnotationsModelMetadataProvider();
+ return metadataProvider.GetMetadataForType(null, t);
+ }
+
+ private class Person
+ {
+ private DateTime? _dateOfDeath;
+
+ public DateTime DateOfBirth { get; set; }
+
+ public DateTime? DateOfDeath
+ {
+ get { return _dateOfDeath; }
+ set
+ {
+ if (value < DateOfBirth)
+ {
+ throw new ArgumentOutOfRangeException("value", "Date of death can't be before date of birth.");
+ }
+ _dateOfDeath = value;
+ }
+ }
+
+ [Required(ErrorMessage = "Sample message")]
+ public int ValueTypeRequired { get; set; }
+
+ public string FirstName { get; set; }
+ public string LastName { get; set; }
+ public string NonUpdateableProperty { get; private set; }
+
+ [DefaultValue(typeof(decimal), "123.456")]
+ public decimal PropertyWithDefaultValue { get; set; }
+ }
+
+ private class PersonWithBindExclusion
+ {
+ [HttpBindNever]
+ public DateTime DateOfBirth { get; set; }
+
+ [HttpBindNever]
+ public DateTime? DateOfDeath { get; set; }
+
+ public string FirstName { get; set; }
+ public string LastName { get; set; }
+ public string NonUpdateableProperty { get; private set; }
+ }
+
+ private class ModelWithBindRequired
+ {
+ public string Name { get; set; }
+
+ [HttpBindRequired]
+ public int Age { get; set; }
+ }
+
+ [HttpBindRequired]
+ private class ModelWithMixedBindingBehaviors
+ {
+ public string Required { get; set; }
+
+ [HttpBindNever]
+ public string Never { get; set; }
+
+ [HttpBindingBehavior(HttpBindingBehavior.Optional)]
+ public string Optional { get; set; }
+ }
+
+ private sealed class MyModelTestingCanUpdateProperty
+ {
+ public int ReadOnlyInt { get; private set; }
+ public string ReadOnlyString { get; private set; }
+ public string[] ReadOnlyArray { get; private set; }
+ public object ReadOnlyObject { get; private set; }
+ public string ReadWriteString { get; set; }
+ }
+
+ private sealed class ModelWhosePropertySetterThrows
+ {
+ [Required(ErrorMessage = "This message comes from the [Required] attribute.")]
+ public string Name
+ {
+ get { return null; }
+ set { throw new ArgumentException("This is an exception.", "value"); }
+ }
+
+ public string NameNoAttribute
+ {
+ get { return null; }
+ set { throw new ArgumentException("This is a different exception.", "value"); }
+ }
+ }
+
+ public class TestableMutableObjectModelBinder : MutableObjectModelBinder
+ {
+ public virtual bool CanUpdatePropertyPublic(ModelMetadata propertyMetadata)
+ {
+ return base.CanUpdateProperty(propertyMetadata);
+ }
+
+ protected override bool CanUpdateProperty(ModelMetadata propertyMetadata)
+ {
+ return CanUpdatePropertyPublic(propertyMetadata);
+ }
+
+ public virtual object CreateModelPublic(HttpActionContext context, ModelBindingContext bindingContext)
+ {
+ return base.CreateModel(context, bindingContext);
+ }
+
+ protected override object CreateModel(HttpActionContext context, ModelBindingContext bindingContext)
+ {
+ return CreateModelPublic(context, bindingContext);
+ }
+
+ public virtual void EnsureModelPublic(HttpActionContext context, ModelBindingContext bindingContext)
+ {
+ base.EnsureModel(context, bindingContext);
+ }
+
+ protected override void EnsureModel(HttpActionContext context, ModelBindingContext bindingContext)
+ {
+ EnsureModelPublic(context, bindingContext);
+ }
+
+ public virtual IEnumerable<ModelMetadata> GetMetadataForPropertiesPublic(HttpActionContext context, ModelBindingContext bindingContext)
+ {
+ return base.GetMetadataForProperties(context, bindingContext);
+ }
+
+ protected override IEnumerable<ModelMetadata> GetMetadataForProperties(HttpActionContext context, ModelBindingContext bindingContext)
+ {
+ return GetMetadataForPropertiesPublic(context, bindingContext);
+ }
+
+ public virtual void SetPropertyPublic(HttpActionContext context, ModelBindingContext bindingContext, ModelMetadata propertyMetadata, ComplexModelDtoResult dtoResult)
+ {
+ base.SetProperty(context, bindingContext, propertyMetadata, dtoResult);
+ }
+
+ protected override void SetProperty(HttpActionContext context, ModelBindingContext bindingContext, ModelMetadata propertyMetadata, ComplexModelDtoResult dtoResult)
+ {
+ SetPropertyPublic(context, bindingContext, propertyMetadata, dtoResult);
+ }
+ }
+ }
+}
diff --git a/test/System.Web.Http.Test/ModelBinding/Binders/SimpleModelBinderProviderTest.cs b/test/System.Web.Http.Test/ModelBinding/Binders/SimpleModelBinderProviderTest.cs
new file mode 100644
index 00000000..b9565985
--- /dev/null
+++ b/test/System.Web.Http.Test/ModelBinding/Binders/SimpleModelBinderProviderTest.cs
@@ -0,0 +1,155 @@
+using System.Web.Http.Metadata.Providers;
+using System.Web.Http.Util;
+using Moq;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Http.ModelBinding.Binders
+{
+ public class SimpleModelBinderProviderTest
+ {
+ [Fact]
+ public void ConstructorWithFactoryThrowsIfModelBinderFactoryIsNull()
+ {
+ // Act & assert
+ Assert.ThrowsArgumentNull(
+ () => new SimpleModelBinderProvider(typeof(object), (Func<IModelBinder>)null),
+ "modelBinderFactory");
+ }
+
+ [Fact]
+ public void ConstructorWithFactoryThrowsIfModelTypeIsNull()
+ {
+ // Act & assert
+ Assert.ThrowsArgumentNull(
+ () => new SimpleModelBinderProvider(null, () => null),
+ "modelType");
+ }
+
+ [Fact]
+ public void ConstructorWithInstanceThrowsIfModelBinderIsNull()
+ {
+ // Act & assert
+ Assert.ThrowsArgumentNull(
+ () => new SimpleModelBinderProvider(typeof(object), (IModelBinder)null),
+ "modelBinder");
+ }
+
+ [Fact]
+ public void ConstructorWithInstanceThrowsIfModelTypeIsNull()
+ {
+ // Act & assert
+ Assert.ThrowsArgumentNull(
+ () => new SimpleModelBinderProvider(null, new Mock<IModelBinder>().Object),
+ "modelType");
+ }
+
+ [Fact]
+ public void GetBinder_TypeDoesNotMatch_ReturnsNull()
+ {
+ // Arrange
+ SimpleModelBinderProvider provider = new SimpleModelBinderProvider(typeof(string), new Mock<IModelBinder>().Object)
+ {
+ SuppressPrefixCheck = true
+ };
+ ModelBindingContext bindingContext = GetBindingContext(typeof(object));
+
+ // Act
+ IModelBinder binder = provider.GetBinder(null, bindingContext);
+
+ // Assert
+ Assert.Null(binder);
+ }
+
+ [Fact]
+ public void GetBinder_TypeMatches_PrefixNotFound_ReturnsNull()
+ {
+ // Arrange
+ IModelBinder binderInstance = new Mock<IModelBinder>().Object;
+ SimpleModelBinderProvider provider = new SimpleModelBinderProvider(typeof(string), binderInstance);
+
+ ModelBindingContext bindingContext = GetBindingContext(typeof(string));
+ bindingContext.ValueProvider = new SimpleHttpValueProvider();
+
+ // Act
+ IModelBinder returnedBinder = provider.GetBinder(null, bindingContext);
+
+ // Assert
+ Assert.Null(returnedBinder);
+ }
+
+ [Fact]
+ public void GetBinder_TypeMatches_PrefixSuppressed_ReturnsFactoryInstance()
+ {
+ // Arrange
+ int numExecutions = 0;
+ IModelBinder theBinderInstance = new Mock<IModelBinder>().Object;
+ Func<IModelBinder> factory = delegate
+ {
+ numExecutions++;
+ return theBinderInstance;
+ };
+
+ SimpleModelBinderProvider provider = new SimpleModelBinderProvider(typeof(string), factory)
+ {
+ SuppressPrefixCheck = true
+ };
+ ModelBindingContext bindingContext = GetBindingContext(typeof(string));
+
+ // Act
+ IModelBinder returnedBinder = provider.GetBinder(null, bindingContext);
+ returnedBinder = provider.GetBinder(null, bindingContext);
+
+ // Assert
+ Assert.Equal(2, numExecutions);
+ Assert.Equal(theBinderInstance, returnedBinder);
+ }
+
+ [Fact]
+ public void GetBinder_TypeMatches_PrefixSuppressed_ReturnsInstance()
+ {
+ // Arrange
+ IModelBinder theBinderInstance = new Mock<IModelBinder>().Object;
+ SimpleModelBinderProvider provider = new SimpleModelBinderProvider(typeof(string), theBinderInstance)
+ {
+ SuppressPrefixCheck = true
+ };
+ ModelBindingContext bindingContext = GetBindingContext(typeof(string));
+
+ // Act
+ IModelBinder returnedBinder = provider.GetBinder(null, bindingContext);
+
+ // Assert
+ Assert.Equal(theBinderInstance, returnedBinder);
+ }
+
+ [Fact]
+ public void GetBinderThrowsIfBindingContextIsNull()
+ {
+ // Arrange
+ SimpleModelBinderProvider provider = new SimpleModelBinderProvider(typeof(string), new Mock<IModelBinder>().Object);
+
+ // Act & assert
+ Assert.ThrowsArgumentNull(
+ delegate { provider.GetBinder(null, null); }, "bindingContext");
+ }
+
+ [Fact]
+ public void ModelTypeProperty()
+ {
+ // Arrange
+ SimpleModelBinderProvider provider = new SimpleModelBinderProvider(typeof(string), new Mock<IModelBinder>().Object);
+
+ // Act & assert
+ Assert.Equal(typeof(string), provider.ModelType);
+ }
+
+ private static ModelBindingContext GetBindingContext(Type modelType)
+ {
+ return new ModelBindingContext
+ {
+ ModelMetadata = new EmptyModelMetadataProvider().GetMetadataForType(() => null, modelType)
+ };
+ }
+ }
+}
diff --git a/test/System.Web.Http.Test/ModelBinding/Binders/TypeConverterModelBinderProviderTest.cs b/test/System.Web.Http.Test/ModelBinding/Binders/TypeConverterModelBinderProviderTest.cs
new file mode 100644
index 00000000..cdab0348
--- /dev/null
+++ b/test/System.Web.Http.Test/ModelBinding/Binders/TypeConverterModelBinderProviderTest.cs
@@ -0,0 +1,68 @@
+using System.Web.Http.Metadata.Providers;
+using System.Web.Http.Util;
+using Xunit;
+
+namespace System.Web.Http.ModelBinding.Binders
+{
+ public class TypeConverterModelBinderProviderTest
+ {
+ [Fact]
+ public void GetBinder_NoTypeConverterExistsFromString_ReturnsNull()
+ {
+ // Arrange
+ ModelBindingContext bindingContext = GetBindingContext(typeof(void)); // no TypeConverter exists Void -> String
+
+ TypeConverterModelBinderProvider provider = new TypeConverterModelBinderProvider();
+
+ // Act
+ IModelBinder binder = provider.GetBinder(null, bindingContext);
+
+ // Assert
+ Assert.Null(binder);
+ }
+
+ [Fact]
+ public void GetBinder_NullValueProviderResult_ReturnsNull()
+ {
+ // Arrange
+ ModelBindingContext bindingContext = GetBindingContext(typeof(int));
+ bindingContext.ValueProvider = new SimpleHttpValueProvider(); // clear the ValueProvider
+
+ TypeConverterModelBinderProvider provider = new TypeConverterModelBinderProvider();
+
+ // Act
+ IModelBinder binder = provider.GetBinder(null, bindingContext);
+
+ // Assert
+ Assert.Null(binder);
+ }
+
+ [Fact]
+ public void GetBinder_TypeConverterExistsFromString_ReturnsNull()
+ {
+ // Arrange
+ ModelBindingContext bindingContext = GetBindingContext(typeof(int)); // TypeConverter exists Int32 -> String
+
+ TypeConverterModelBinderProvider provider = new TypeConverterModelBinderProvider();
+
+ // Act
+ IModelBinder binder = provider.GetBinder(null, bindingContext);
+
+ // Assert
+ Assert.IsType<TypeConverterModelBinder>(binder);
+ }
+
+ private static ModelBindingContext GetBindingContext(Type modelType)
+ {
+ return new ModelBindingContext
+ {
+ ModelMetadata = new EmptyModelMetadataProvider().GetMetadataForType(null, modelType),
+ ModelName = "theModelName",
+ ValueProvider = new SimpleHttpValueProvider
+ {
+ { "theModelName", "someValue" }
+ }
+ };
+ }
+ }
+}
diff --git a/test/System.Web.Http.Test/ModelBinding/Binders/TypeConverterModelBinderTest.cs b/test/System.Web.Http.Test/ModelBinding/Binders/TypeConverterModelBinderTest.cs
new file mode 100644
index 00000000..6b55e620
--- /dev/null
+++ b/test/System.Web.Http.Test/ModelBinding/Binders/TypeConverterModelBinderTest.cs
@@ -0,0 +1,170 @@
+using System.ComponentModel;
+using System.Globalization;
+using System.Web.Http.Metadata.Providers;
+using System.Web.Http.Util;
+using Xunit;
+
+namespace System.Web.Http.ModelBinding.Binders
+{
+ public class TypeConverterModelBinderTest
+ {
+ [Fact]
+ public void BindModel_Error_FormatExceptionsTurnedIntoStringsInModelState()
+ {
+ // Arrange
+ ModelBindingContext bindingContext = GetBindingContext(typeof(int));
+ bindingContext.ValueProvider = new SimpleHttpValueProvider
+ {
+ { "theModelName", "not an integer" }
+ };
+
+ TypeConverterModelBinder binder = new TypeConverterModelBinder();
+
+ // Act
+ bool retVal = binder.BindModel(null, bindingContext);
+
+ // Assert
+ Assert.False(retVal);
+ Assert.Equal("The value 'not an integer' is not valid for Int32.", bindingContext.ModelState["theModelName"].Errors[0].ErrorMessage);
+ }
+
+ [Fact]
+ public void BindModel_Error_FormatExceptionsTurnedIntoStringsInModelState_ErrorNotAddedIfCallbackReturnsNull()
+ {
+ // Arrange
+ ModelBindingContext bindingContext = GetBindingContext(typeof(int));
+ bindingContext.ValueProvider = new SimpleHttpValueProvider
+ {
+ { "theModelName", "not an integer" }
+ };
+
+ TypeConverterModelBinder binder = new TypeConverterModelBinder();
+
+ // Act
+ ModelBinderErrorMessageProvider originalProvider = ModelBinderConfig.TypeConversionErrorMessageProvider;
+ bool retVal;
+ try
+ {
+ ModelBinderConfig.TypeConversionErrorMessageProvider = delegate { return null; };
+ retVal = binder.BindModel(null, bindingContext);
+ }
+ finally
+ {
+ ModelBinderConfig.TypeConversionErrorMessageProvider = originalProvider;
+ }
+
+ // Assert
+ Assert.False(retVal);
+ Assert.Null(bindingContext.Model);
+ Assert.True(bindingContext.ModelState.IsValid);
+ }
+
+ [Fact]
+ public void BindModel_Error_GeneralExceptionsSavedInModelState()
+ {
+ // Arrange
+ ModelBindingContext bindingContext = GetBindingContext(typeof(Dummy));
+ bindingContext.ValueProvider = new SimpleHttpValueProvider
+ {
+ { "theModelName", "foo" }
+ };
+
+ TypeConverterModelBinder binder = new TypeConverterModelBinder();
+
+ // Act
+ bool retVal = binder.BindModel(null, bindingContext);
+
+ // Assert
+ Assert.False(retVal);
+ Assert.Null(bindingContext.Model);
+ Assert.Equal("The parameter conversion from type 'System.String' to type 'System.Web.Http.ModelBinding.Binders.TypeConverterModelBinderTest+Dummy' failed. See the inner exception for more information.", bindingContext.ModelState["theModelName"].Errors[0].Exception.Message);
+ Assert.Equal("From DummyTypeConverter: foo", bindingContext.ModelState["theModelName"].Errors[0].Exception.InnerException.Message);
+ }
+
+ [Fact]
+ public void BindModel_NullValueProviderResult_ReturnsFalse()
+ {
+ // Arrange
+ ModelBindingContext bindingContext = GetBindingContext(typeof(int));
+
+ TypeConverterModelBinder binder = new TypeConverterModelBinder();
+
+ // Act
+ bool retVal = binder.BindModel(null, bindingContext);
+
+ // Assert
+ Assert.False(retVal, "BindModel should have returned null.");
+ Assert.Empty(bindingContext.ModelState);
+ }
+
+ [Fact]
+ public void BindModel_ValidValueProviderResult_ConvertEmptyStringsToNull()
+ {
+ // Arrange
+ ModelBindingContext bindingContext = GetBindingContext(typeof(string));
+ bindingContext.ValueProvider = new SimpleHttpValueProvider
+ {
+ { "theModelName", "" }
+ };
+
+ TypeConverterModelBinder binder = new TypeConverterModelBinder();
+
+ // Act
+ bool retVal = binder.BindModel(null, bindingContext);
+
+ // Assert
+ Assert.True(retVal);
+ Assert.Null(bindingContext.Model);
+ Assert.True(bindingContext.ModelState.ContainsKey("theModelName"));
+ }
+
+ [Fact]
+ public void BindModel_ValidValueProviderResult_ReturnsModel()
+ {
+ // Arrange
+ ModelBindingContext bindingContext = GetBindingContext(typeof(int));
+ bindingContext.ValueProvider = new SimpleHttpValueProvider
+ {
+ { "theModelName", "42" }
+ };
+
+ TypeConverterModelBinder binder = new TypeConverterModelBinder();
+
+ // Act
+ bool retVal = binder.BindModel(null, bindingContext);
+
+ // Assert
+ Assert.True(retVal);
+ Assert.Equal(42, bindingContext.Model);
+ Assert.True(bindingContext.ModelState.ContainsKey("theModelName"));
+ }
+
+ private static ModelBindingContext GetBindingContext(Type modelType)
+ {
+ return new ModelBindingContext
+ {
+ ModelMetadata = new EmptyModelMetadataProvider().GetMetadataForType(null, modelType),
+ ModelName = "theModelName",
+ ValueProvider = new SimpleHttpValueProvider() // empty
+ };
+ }
+
+ [TypeConverter(typeof(DummyTypeConverter))]
+ private struct Dummy
+ {
+ }
+
+ private sealed class DummyTypeConverter : TypeConverter
+ {
+ public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
+ {
+ return (sourceType == typeof(string)) || base.CanConvertFrom(context, sourceType);
+ }
+
+ public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
+ {
+ throw new InvalidOperationException(String.Format("From DummyTypeConverter: {0}", value));
+ }
+ }
+ }
+}
diff --git a/test/System.Web.Http.Test/ModelBinding/Binders/TypeMatchModelBinderProviderTest.cs b/test/System.Web.Http.Test/ModelBinding/Binders/TypeMatchModelBinderProviderTest.cs
new file mode 100644
index 00000000..7143a483
--- /dev/null
+++ b/test/System.Web.Http.Test/ModelBinding/Binders/TypeMatchModelBinderProviderTest.cs
@@ -0,0 +1,61 @@
+using System.Web.Http.Metadata.Providers;
+using System.Web.Http.Util;
+using Xunit;
+
+namespace System.Web.Http.ModelBinding.Binders
+{
+ public class TypeMatchModelBinderProviderTest
+ {
+ [Fact]
+ public void GetBinder_InvalidValueProviderResult_ReturnsNull()
+ {
+ // Arrange
+ ModelBindingContext bindingContext = GetBindingContext();
+ bindingContext.ValueProvider = new SimpleHttpValueProvider
+ {
+ { "theModelName", "not an integer" }
+ };
+
+ TypeMatchModelBinderProvider provider = new TypeMatchModelBinderProvider();
+
+ // Act
+ IModelBinder binder = provider.GetBinder(null, bindingContext);
+
+ // Assert
+ Assert.Null(binder);
+ }
+
+ [Fact]
+ public void BindModel_ValidValueProviderResult_ReturnsBinder()
+ {
+ // Arrange
+ ModelBindingContext bindingContext = GetBindingContext();
+ bindingContext.ValueProvider = new SimpleHttpValueProvider
+ {
+ { "theModelName", 42 }
+ };
+
+ TypeMatchModelBinderProvider provider = new TypeMatchModelBinderProvider();
+
+ // Act
+ IModelBinder binder = provider.GetBinder(null, bindingContext);
+
+ // Assert
+ Assert.IsType<TypeMatchModelBinder>(binder);
+ }
+
+ private static ModelBindingContext GetBindingContext()
+ {
+ return GetBindingContext(typeof(int));
+ }
+
+ private static ModelBindingContext GetBindingContext(Type modelType)
+ {
+ return new ModelBindingContext
+ {
+ ModelMetadata = new EmptyModelMetadataProvider().GetMetadataForType(null, modelType),
+ ModelName = "theModelName"
+ };
+ }
+ }
+}
diff --git a/test/System.Web.Http.Test/ModelBinding/Binders/TypeMatchModelBinderTest.cs b/test/System.Web.Http.Test/ModelBinding/Binders/TypeMatchModelBinderTest.cs
new file mode 100644
index 00000000..14f4828f
--- /dev/null
+++ b/test/System.Web.Http.Test/ModelBinding/Binders/TypeMatchModelBinderTest.cs
@@ -0,0 +1,113 @@
+using System.Web.Http.Metadata.Providers;
+using System.Web.Http.Util;
+using System.Web.Http.ValueProviders;
+using Xunit;
+
+namespace System.Web.Http.ModelBinding.Binders
+{
+ public class TypeMatchModelBinderTest
+ {
+ [Fact]
+ public void BindModel_InvalidValueProviderResult_ReturnsFalse()
+ {
+ // Arrange
+ ModelBindingContext bindingContext = GetBindingContext();
+ bindingContext.ValueProvider = new SimpleHttpValueProvider
+ {
+ { "theModelName", "not an integer" }
+ };
+
+ TypeMatchModelBinder binder = new TypeMatchModelBinder();
+
+ // Act
+ bool retVal = binder.BindModel(null, bindingContext);
+
+ // Assert
+ Assert.False(retVal);
+ Assert.Empty(bindingContext.ModelState);
+ }
+
+ [Fact]
+ public void BindModel_ValidValueProviderResult_ReturnsTrue()
+ {
+ // Arrange
+ ModelBindingContext bindingContext = GetBindingContext();
+ bindingContext.ValueProvider = new SimpleHttpValueProvider
+ {
+ { "theModelName", 42 }
+ };
+
+ TypeMatchModelBinder binder = new TypeMatchModelBinder();
+
+ // Act
+ bool retVal = binder.BindModel(null, bindingContext);
+
+ // Assert
+ Assert.True(retVal);
+ Assert.Equal(42, bindingContext.Model);
+ Assert.True(bindingContext.ModelState.ContainsKey("theModelName"));
+ }
+
+ [Fact]
+ public void GetCompatibleValueProviderResult_ValueProviderResultRawValueIncorrect_ReturnsNull()
+ {
+ // Arrange
+ ModelBindingContext bindingContext = GetBindingContext();
+ bindingContext.ValueProvider = new SimpleHttpValueProvider
+ {
+ { "theModelName", "not an integer" }
+ };
+
+ // Act
+ ValueProviderResult vpResult = TypeMatchModelBinder.GetCompatibleValueProviderResult(bindingContext);
+
+ // Assert
+ Assert.Null(vpResult); // Raw value is the wrong type
+ }
+
+ [Fact]
+ public void GetCompatibleValueProviderResult_ValueProviderResultValid_ReturnsValueProviderResult()
+ {
+ // Arrange
+ ModelBindingContext bindingContext = GetBindingContext();
+ bindingContext.ValueProvider = new SimpleHttpValueProvider
+ {
+ { "theModelName", 42 }
+ };
+
+ // Act
+ ValueProviderResult vpResult = TypeMatchModelBinder.GetCompatibleValueProviderResult(bindingContext);
+
+ // Assert
+ Assert.NotNull(vpResult);
+ }
+
+ [Fact]
+ public void GetCompatibleValueProviderResult_ValueProviderReturnsNull_ReturnsNull()
+ {
+ // Arrange
+ ModelBindingContext bindingContext = GetBindingContext();
+ bindingContext.ValueProvider = new SimpleHttpValueProvider();
+
+ // Act
+ ValueProviderResult vpResult = TypeMatchModelBinder.GetCompatibleValueProviderResult(bindingContext);
+
+ // Assert
+ Assert.Null(vpResult); // No key matched
+ }
+
+ private static ModelBindingContext GetBindingContext()
+ {
+ return GetBindingContext(typeof(int));
+ }
+
+ private static ModelBindingContext GetBindingContext(Type modelType)
+ {
+ return new ModelBindingContext
+ {
+ ModelMetadata = new EmptyModelMetadataProvider().GetMetadataForType(null, modelType),
+ ModelName = "theModelName"
+ };
+ }
+ }
+}
diff --git a/test/System.Web.Http.Test/ModelBinding/CompositeModelBinderTest.cs b/test/System.Web.Http.Test/ModelBinding/CompositeModelBinderTest.cs
new file mode 100644
index 00000000..cce66b0b
--- /dev/null
+++ b/test/System.Web.Http.Test/ModelBinding/CompositeModelBinderTest.cs
@@ -0,0 +1,312 @@
+using System.Collections.Generic;
+using System.Globalization;
+using System.Web.Http.Controllers;
+using System.Web.Http.Metadata.Providers;
+using System.Web.Http.ModelBinding.Binders;
+using System.Web.Http.ValueProviders;
+using Moq;
+using Xunit;
+
+namespace System.Web.Http.ModelBinding
+{
+ public class CompositeModelBinderTest
+ {
+ //// REVIEW: remove or activate when PropertyFilter is activated
+ ////[Fact]
+ ////public void BindModel_PropertyFilterIsSet_Throws()
+ ////{
+ //// // Arrange
+ //// HttpExecutionContext executionContext = GetHttpExecutionContext();
+
+ //// ModelBindingContext bindingContext = new ModelBindingContext
+ //// {
+ //// FallbackToEmptyPrefix = true,
+ //// ModelMetadata = new EmptyModelMetadataProvider().GetMetadataForType(null, typeof(SimpleModel)),
+ //// //PropertyFilter = (new BindAttribute { Include = "FirstName " }).IsPropertyAllowed
+ //// };
+
+ //// List<ModelBinderProvider> binderProviders = new List<ModelBinderProvider>();
+ //// CompositeModelBinder shimBinder = new CompositeModelBinder(binderProviders);
+
+ //// // Act & assert
+ //// Assert.Throws<InvalidOperationException>(
+ //// delegate { shimBinder.BindModel(executionContext, bindingContext); },
+ //// @"The new model binding system cannot be used when a property allow list or disallow list has been specified in [Bind] or via the call to UpdateModel() / TryUpdateModel(). Use the [BindRequired] and [BindNever] attributes on the model type or its properties instead.");
+ ////}
+
+ [Fact]
+ public void BindModel_SuccessfulBind_RunsValidationAndReturnsModel()
+ {
+ // Arrange
+ HttpActionContext actionContext = ContextUtil.CreateActionContext(GetHttpControllerContext());
+ bool validationCalled = false;
+
+ ModelBindingContext bindingContext = new ModelBindingContext
+ {
+ FallbackToEmptyPrefix = true,
+ ModelMetadata = new EmptyModelMetadataProvider().GetMetadataForType(null, typeof(int)),
+ ModelName = "someName",
+ //ModelState = executionContext.Controller.ViewData.ModelState,
+ //PropertyFilter = _ => true,
+ ValueProvider = new SimpleValueProvider
+ {
+ { "someName", "dummyValue" }
+ }
+ };
+
+ Mock<IModelBinder> mockIntBinder = new Mock<IModelBinder>();
+ mockIntBinder
+ .Setup(o => o.BindModel(actionContext, It.IsAny<ModelBindingContext>()))
+ .Returns(
+ delegate(HttpActionContext cc, ModelBindingContext mbc)
+ {
+ Assert.Same(bindingContext.ModelMetadata, mbc.ModelMetadata);
+ Assert.Equal("someName", mbc.ModelName);
+ Assert.Same(bindingContext.ValueProvider, mbc.ValueProvider);
+
+ mbc.Model = 42;
+ mbc.ValidationNode.Validating += delegate { validationCalled = true; };
+ return true;
+ });
+
+ Mock<ModelBinderProvider> mockBinderProvider = new Mock<ModelBinderProvider>();
+ mockBinderProvider.Setup(o => o.GetBinder(actionContext, It.IsAny<ModelBindingContext>())).Returns((IModelBinder)mockIntBinder.Object).Verifiable();
+ List<ModelBinderProvider> binderProviders = new List<ModelBinderProvider>()
+ {
+ mockBinderProvider.Object
+ };
+
+ //binderProviders.RegisterBinderForType(typeof(int), mockIntBinder.Object, false /* suppressPrefixCheck */);
+ CompositeModelBinder shimBinder = new CompositeModelBinder(binderProviders);
+
+ // Act
+ bool isBound = shimBinder.BindModel(actionContext, bindingContext);
+
+ // Assert
+ Assert.True(isBound);
+ Assert.Equal(42, bindingContext.Model);
+ Assert.True(validationCalled);
+ Assert.True(bindingContext.ModelState.IsValid);
+ }
+
+ [Fact]
+ public void BindModel_SuccessfulBind_ComplexTypeFallback_RunsValidationAndReturnsModel()
+ {
+ // Arrange
+ HttpActionContext actionContext = ContextUtil.CreateActionContext(GetHttpControllerContext());
+
+ bool validationCalled = false;
+ List<int> expectedModel = new List<int> { 1, 2, 3, 4, 5 };
+
+ ModelBindingContext bindingContext = new ModelBindingContext
+ {
+ FallbackToEmptyPrefix = true,
+ ModelMetadata = new EmptyModelMetadataProvider().GetMetadataForType(null, typeof(List<int>)),
+ ModelName = "someName",
+ //ModelState = executionContext.Controller.ViewData.ModelState,
+ //PropertyFilter = _ => true,
+ ValueProvider = new SimpleValueProvider
+ {
+ { "someOtherName", "dummyValue" }
+ }
+ };
+
+ Mock<IModelBinder> mockIntBinder = new Mock<IModelBinder>();
+ mockIntBinder
+ .Setup(o => o.BindModel(actionContext, It.IsAny<ModelBindingContext>()))
+ .Returns(
+ delegate(HttpActionContext cc, ModelBindingContext mbc)
+ {
+ Assert.Same(bindingContext.ModelMetadata, mbc.ModelMetadata);
+ Assert.Equal("", mbc.ModelName);
+ Assert.Same(bindingContext.ValueProvider, mbc.ValueProvider);
+
+ mbc.Model = expectedModel;
+ mbc.ValidationNode.Validating += delegate { validationCalled = true; };
+ return true;
+ });
+
+ List<ModelBinderProvider> binderProviders = new List<ModelBinderProvider>()
+ {
+ new SimpleModelBinderProvider()
+ {
+ Binder = mockIntBinder.Object,
+ OnlyWithEmptyModelName = true
+ }
+ };
+
+ //binderProviders.RegisterBinderForType(typeof(List<int>), mockIntBinder.Object, false /* suppressPrefixCheck */);
+ CompositeModelBinder shimBinder = new CompositeModelBinder(binderProviders);
+
+ // Act
+ bool isBound = shimBinder.BindModel(actionContext, bindingContext);
+
+ // Assert
+ Assert.True(isBound);
+ Assert.Equal(expectedModel, bindingContext.Model);
+ Assert.True(validationCalled);
+ Assert.True(bindingContext.ModelState.IsValid);
+ }
+
+ [Fact]
+ public void BindModel_UnsuccessfulBind_BinderFails_ReturnsNull()
+ {
+ // Arrange
+ HttpActionContext actionContext = ContextUtil.CreateActionContext(GetHttpControllerContext());
+ Mock<IModelBinder> mockListBinder = new Mock<IModelBinder>();
+ mockListBinder.Setup(o => o.BindModel(actionContext, It.IsAny<ModelBindingContext>())).Returns(false).Verifiable();
+
+ Mock<ModelBinderProvider> mockBinderProvider = new Mock<ModelBinderProvider>();
+ mockBinderProvider.Setup(o => o.GetBinder(actionContext, It.IsAny<ModelBindingContext>())).Returns((IModelBinder)mockListBinder.Object).Verifiable();
+ List<ModelBinderProvider> binderProviders = new List<ModelBinderProvider>()
+ {
+ mockBinderProvider.Object
+ };
+
+ CompositeModelBinder shimBinder = new CompositeModelBinder(binderProviders);
+
+ ModelBindingContext bindingContext = new ModelBindingContext
+ {
+ FallbackToEmptyPrefix = false,
+ ModelMetadata = new EmptyModelMetadataProvider().GetMetadataForType(null, typeof(List<int>)),
+ //ModelState = executionContext.Controller.ViewData.ModelState
+ };
+
+ // Act
+ bool isBound = shimBinder.BindModel(actionContext, bindingContext);
+
+ // Assert
+ Assert.False(isBound);
+ Assert.Null(bindingContext.Model);
+ Assert.True(bindingContext.ModelState.IsValid);
+ mockListBinder.Verify();
+ }
+
+ [Fact]
+ public void BindModel_UnsuccessfulBind_SimpleTypeNoFallback_ReturnsNull()
+ {
+ // Arrange
+ HttpActionContext actionContext = ContextUtil.CreateActionContext(GetHttpControllerContext());
+ Mock<ModelBinderProvider> mockBinderProvider = new Mock<ModelBinderProvider>();
+ mockBinderProvider.Setup(o => o.GetBinder(actionContext, It.IsAny<ModelBindingContext>())).Returns((IModelBinder)null).Verifiable();
+ List<ModelBinderProvider> binderProviders = new List<ModelBinderProvider>()
+ {
+ mockBinderProvider.Object
+ };
+ CompositeModelBinder shimBinder = new CompositeModelBinder(binderProviders);
+
+ ModelBindingContext bindingContext = new ModelBindingContext
+ {
+ FallbackToEmptyPrefix = true,
+ ModelMetadata = new EmptyModelMetadataProvider().GetMetadataForType(null, typeof(int)),
+ //ModelState = executionContext.Controller.ViewData.ModelState
+ };
+
+ // Act
+ bool isBound = shimBinder.BindModel(actionContext, bindingContext);
+
+ // Assert
+ Assert.False(isBound);
+ Assert.Null(bindingContext.Model);
+ Assert.True(bindingContext.ModelState.IsValid);
+ mockBinderProvider.Verify();
+ mockBinderProvider.Verify(o => o.GetBinder(actionContext, It.IsAny<ModelBindingContext>()), Times.AtMostOnce());
+ }
+
+ private static HttpControllerContext GetHttpControllerContext()
+ {
+ return ContextUtil.CreateControllerContext();
+ }
+
+ private class SimpleController : ApiController
+ {
+ }
+
+ private class SimpleModel
+ {
+ public string FirstName { get; set; }
+ public string LastName { get; set; }
+ }
+
+ private class SimpleModelBinderProvider : ModelBinderProvider
+ {
+ public IModelBinder Binder { get; set; }
+
+ public bool OnlyWithEmptyModelName { get; set; }
+
+ public override IModelBinder GetBinder(HttpActionContext actionContext, ModelBindingContext bindingContext)
+ {
+ if (OnlyWithEmptyModelName && !String.IsNullOrEmpty(bindingContext.ModelName))
+ {
+ return null;
+ }
+
+ return Binder;
+ }
+ }
+
+ private class SimpleValueProvider : Dictionary<string, object>, IValueProvider
+ {
+ private readonly CultureInfo _culture;
+
+ public SimpleValueProvider()
+ : this(null)
+ {
+ }
+
+ public SimpleValueProvider(CultureInfo culture)
+ : base(StringComparer.OrdinalIgnoreCase)
+ {
+ _culture = culture ?? CultureInfo.InvariantCulture;
+ }
+
+ // copied from ValueProviderUtil
+ public bool ContainsPrefix(string prefix)
+ {
+ foreach (string key in Keys)
+ {
+ if (key != null)
+ {
+ if (prefix.Length == 0)
+ {
+ return true; // shortcut - non-null key matches empty prefix
+ }
+
+ if (key.StartsWith(prefix, StringComparison.OrdinalIgnoreCase))
+ {
+ if (key.Length == prefix.Length)
+ {
+ return true; // exact match
+ }
+ else
+ {
+ switch (key[prefix.Length])
+ {
+ case '.': // known separator characters
+ case '[':
+ return true;
+ }
+ }
+ }
+ }
+ }
+
+ return false; // nothing found
+ }
+
+ public ValueProviderResult GetValue(string key)
+ {
+ object rawValue;
+ if (TryGetValue(key, out rawValue))
+ {
+ return new ValueProviderResult(rawValue, Convert.ToString(rawValue, _culture), _culture);
+ }
+ else
+ {
+ // value not found
+ return null;
+ }
+ }
+ }
+ }
+}
diff --git a/test/System.Web.Http.Test/ModelBinding/DefaultActionValueBinderTest.cs b/test/System.Web.Http.Test/ModelBinding/DefaultActionValueBinderTest.cs
new file mode 100644
index 00000000..72dd3ae8
--- /dev/null
+++ b/test/System.Web.Http.Test/ModelBinding/DefaultActionValueBinderTest.cs
@@ -0,0 +1,490 @@
+using System.ComponentModel;
+using System.Linq;
+using System.Net.Http;
+using System.Reflection;
+using System.Threading;
+using System.Web.Http.Controllers;
+using System.Web.Http.ValueProviders;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Http.ModelBinding
+{
+ // These tests primarily focus on getting the right binding contract. They don't actually execute the contract.
+ public class DefaultActionValueBinderTest
+ {
+ [Fact]
+ public void BindValuesAsync_Throws_Null_ActionDescriptor()
+ {
+ // Arrange
+ HttpActionDescriptor actionDescriptor = new ReflectedHttpActionDescriptor { MethodInfo = (MethodInfo)MethodInfo.GetCurrentMethod() };
+
+ // Act and Assert
+ Assert.ThrowsArgumentNull(
+ () => new DefaultActionValueBinder().GetBinding(null),
+ "actionDescriptor");
+ }
+
+ private void Action_Int(int id) { }
+
+ [Fact]
+ public void Check_Int_Is_ModelBound()
+ {
+ DefaultActionValueBinder binder = new DefaultActionValueBinder();
+
+ var binding = binder.GetBinding(GetAction("Action_Int"));
+
+ Assert.Equal(1, binding.ParameterBindings.Length);
+ AssertIsModelBound(binding, 0);
+ }
+
+ private void Action_Int_FromUri([FromUri] int id) { }
+
+ [Fact]
+ public void Check_Explicit_Int_Is_ModelBound()
+ {
+ // Even though int is implicitly model bound, still ok to specify it explicitly
+ DefaultActionValueBinder binder = new DefaultActionValueBinder();
+
+ var binding = binder.GetBinding(GetAction("Action_Int_FromUri"));
+
+ Assert.Equal(1, binding.ParameterBindings.Length);
+ AssertIsModelBound(binding, 0);
+ }
+
+ // All types in this signature are model bound
+ private void Action_SimpleTypes(char ch, Byte b, Int16 i16, UInt16 u16, Int32 i32, UInt32 u32, Int64 i64, UInt64 u64, string s, DateTime d, Decimal dec, Guid g, DateTimeOffset dateTimeOffset, TimeSpan timespan) { }
+
+ [Fact]
+ public void Check_SimpleTypes_Are_ModelBound()
+ {
+ DefaultActionValueBinder binder = new DefaultActionValueBinder();
+
+ var binding = binder.GetBinding(GetAction("Action_SimpleTypes"));
+
+ for(int i = 0; i < binding.ParameterBindings.Length; i++)
+ {
+ AssertIsModelBound(binding, 0);
+ }
+ }
+
+ private void Action_ComplexTypeWithStringConverter(ComplexTypeWithStringConverter x) { }
+
+ [Fact]
+ public void Check_String_TypeConverter_Is_ModelBound()
+ {
+ DefaultActionValueBinder binder = new DefaultActionValueBinder();
+
+ var binding = binder.GetBinding(GetAction("Action_ComplexTypeWithStringConverter"));
+
+ Assert.Equal(1, binding.ParameterBindings.Length);
+ AssertIsModelBound(binding, 0);
+ }
+
+ private void Action_ComplexTypeWithStringConverter_Body_Override([FromBody] ComplexTypeWithStringConverter x) { }
+
+ [Fact]
+ public void Check_String_TypeConverter_With_Body_Override()
+ {
+ DefaultActionValueBinder binder = new DefaultActionValueBinder();
+
+ var binding = binder.GetBinding(GetAction("Action_ComplexTypeWithStringConverter_Body_Override"));
+
+ Assert.Equal(1, binding.ParameterBindings.Length);
+ AssertIsBody(binding, 0);
+ }
+
+ private void Action_NullableInt(Nullable<int> id) { }
+
+ [Fact]
+ public void Check_NullableInt_Is_ModelBound()
+ {
+ DefaultActionValueBinder binder = new DefaultActionValueBinder();
+
+ var binding = binder.GetBinding(GetAction("Action_NullableInt"));
+
+ Assert.Equal(1, binding.ParameterBindings.Length);
+ AssertIsModelBound(binding, 0);
+ }
+
+ private void Action_Nullable_ValueType(Nullable<ComplexValueType> id) { }
+
+ [Fact]
+ public void Check_Nullable_ValueType_Is_FromBody()
+ {
+ DefaultActionValueBinder binder = new DefaultActionValueBinder();
+
+ var binding = binder.GetBinding(GetAction("Action_Nullable_ValueType"));
+
+ Assert.Equal(1, binding.ParameterBindings.Length);
+ AssertIsBody(binding, 0);
+ }
+
+
+ private void Action_IntArray(int[] arrayFrombody) { }
+
+ [Fact]
+ public void Check_IntArray_Is_FromBody()
+ {
+ DefaultActionValueBinder binder = new DefaultActionValueBinder();
+
+ var binding = binder.GetBinding(GetAction("Action_IntArray"));
+
+ Assert.Equal(1, binding.ParameterBindings.Length);
+ AssertIsBody(binding, 0);
+ }
+
+
+ private void Action_SimpleType_Body([FromBody] int i) { }
+
+ [Fact]
+ public void Check_SimpleType_Body()
+ {
+ DefaultActionValueBinder binder = new DefaultActionValueBinder();
+
+ var binding = binder.GetBinding(GetAction("Action_SimpleType_Body"));
+
+ Assert.Equal(1, binding.ParameterBindings.Length);
+ AssertIsBody(binding, 0);
+ }
+
+ private void Action_Empty() { }
+
+ [Fact]
+ public void Check_Empty_Action()
+ {
+ DefaultActionValueBinder binder = new DefaultActionValueBinder();
+
+ var binding = binder.GetBinding(GetAction("Action_Empty"));
+
+ Assert.NotNull(binding.ParameterBindings);
+ Assert.Equal(0, binding.ParameterBindings.Length);
+ }
+
+ private void Action_String_String(string s1, string s2) { }
+
+ [Fact]
+ public void Check_String_String_IsModelBound()
+ {
+ DefaultActionValueBinder binder = new DefaultActionValueBinder();
+
+ var binding = binder.GetBinding(GetAction("Action_String_String"));
+
+ Assert.Equal(2, binding.ParameterBindings.Length);
+ AssertIsModelBound(binding, 0);
+ AssertIsModelBound(binding, 1);
+ }
+
+ private void Action_Complex_Type(ComplexType complex) { }
+
+ [Fact]
+ public void Check_Complex_Type_FromBody()
+ {
+ DefaultActionValueBinder binder = new DefaultActionValueBinder();
+
+ var binding = binder.GetBinding(GetAction("Action_Complex_Type"));
+
+ Assert.Equal(1, binding.ParameterBindings.Length);
+ AssertIsBody(binding, 0);
+ }
+
+ private void Action_Complex_ValueType(ComplexValueType complex) { }
+
+ [Fact]
+ public void Check_Complex_ValueType_FromBody()
+ {
+ DefaultActionValueBinder binder = new DefaultActionValueBinder();
+
+ var binding = binder.GetBinding(GetAction("Action_Complex_ValueType"));
+
+ Assert.Equal(1, binding.ParameterBindings.Length);
+ AssertIsBody(binding, 0);
+ }
+
+ private void Action_Default_Custom_Model_Binder([ModelBinder] ComplexType complex) { }
+
+ [Fact]
+ public void Check_Customer_Binder()
+ {
+ // Mere presence of a ModelBinder attribute means the type is model bound.
+
+ DefaultActionValueBinder binder = new DefaultActionValueBinder();
+
+ var binding = binder.GetBinding(GetAction("Action_Default_Custom_Model_Binder"));
+
+ Assert.Equal(1, binding.ParameterBindings.Length);
+ AssertIsModelBound(binding, 0);
+ }
+
+ private void Action_Complex_Type_Uri([FromUri] ComplexType complex) { }
+
+ [Fact]
+ public void Check_Complex_Type_FromUri()
+ {
+ // [FromUri] is just a specific instance of ModelBinder attribute
+ DefaultActionValueBinder binder = new DefaultActionValueBinder();
+
+ var binding = binder.GetBinding(GetAction("Action_Complex_Type_Uri"));
+
+ Assert.Equal(1, binding.ParameterBindings.Length);
+ AssertIsModelBound(binding, 0);
+ }
+
+ private void Action_Two_Complex_Types(ComplexType complexBody1, ComplexType complexBody2) { }
+
+ [Fact]
+ public void Check_Two_Complex_Types_FromBody()
+ {
+ DefaultActionValueBinder binder = new DefaultActionValueBinder();
+
+ // It's illegal to have multiple parameters from the body.
+ // But we should still be able to get a binding for it. We just can't execute it.
+ var binding = binder.GetBinding(GetAction("Action_Two_Complex_Types"));
+
+ Assert.Equal(2, binding.ParameterBindings.Length);
+ AssertIsError(binding, 0);
+ AssertIsError(binding, 1);
+ }
+
+ private void Action_Complex_Type_UriAndBody([FromUri] ComplexType complexUri, ComplexType complexBody) { }
+
+ [Fact]
+ public void Check_Complex_Type_FromBody_And_FromUri()
+ {
+ DefaultActionValueBinder binder = new DefaultActionValueBinder();
+
+ var binding = binder.GetBinding(GetAction("Action_Complex_Type_UriAndBody"));
+
+ Assert.Equal(2, binding.ParameterBindings.Length);
+ AssertIsModelBound(binding, 0);
+ AssertIsBody(binding, 1);
+ }
+
+ private void Action_CancellationToken(CancellationToken ct) { }
+
+ [Fact]
+ public void Check_Cancellation_Token()
+ {
+ DefaultActionValueBinder binder = new DefaultActionValueBinder();
+
+ var binding = binder.GetBinding(GetAction("Action_CancellationToken"));
+
+ Assert.Equal(1, binding.ParameterBindings.Length);
+ AssertIsCancellationToken(binding, 0);
+ }
+
+ private void Action_CustomModelBinder_On_Parameter_WithProvider([ModelBinder(typeof(CustomModelBinderProvider))] ComplexType complex) { }
+
+ [Fact]
+ public void Check_CustomModelBinder_On_Parameter()
+ {
+ HttpConfiguration config = new HttpConfiguration();
+ config.ServiceResolver.SetServices(typeof(ValueProviderFactory), new ValueProviderFactory[] {
+ new CustomValueProviderFactory(),
+ });
+
+ DefaultActionValueBinder binder = new DefaultActionValueBinder();
+
+ var binding = binder.GetBinding(GetAction("Action_CustomModelBinder_On_Parameter_WithProvider", config));
+
+ Assert.Equal(1, binding.ParameterBindings.Length);
+ AssertIsModelBound(binding, 0);
+
+ ModelBinderParameterBinding p = (ModelBinderParameterBinding) binding.ParameterBindings[0];
+ Assert.IsType<CustomModelBinderProvider>(p.ModelBinderProvider);
+
+ // Since the ModelBinderAttribute didn't specify the valueproviders, we should pull those from config.
+ Assert.Equal(1, p.ValueProviderFactories.Count());
+ Assert.IsType<CustomValueProviderFactory>(p.ValueProviderFactories.First());
+ }
+
+ // Model binder attribute is on the type's declaration.
+ private void Action_ComplexParameter_With_ModelBinder(ComplexTypeWithModelBinder complex) { }
+
+ [Fact]
+ public void Check_Parameter_With_ModelBinder_Attribute_On_Type()
+ {
+ DefaultActionValueBinder binder = new DefaultActionValueBinder();
+
+ var binding = binder.GetBinding(GetAction("Action_ComplexParameter_With_ModelBinder"));
+
+ Assert.Equal(1, binding.ParameterBindings.Length);
+ AssertIsModelBound(binding, 0);
+ }
+
+ private void Action_Conflicting_Attributes([FromBody][FromUri] int i) { }
+
+ [Fact]
+ public void Error_Conflicting_Attributes()
+ {
+ DefaultActionValueBinder binder = new DefaultActionValueBinder();
+
+ var binding = binder.GetBinding(GetAction("Action_Conflicting_Attributes"));
+
+ // Have 2 attributes that conflict with each other. Still get the contract, but it has an error in it.
+ Assert.Equal(1, binding.ParameterBindings.Length);
+ AssertIsError(binding, 0);
+ }
+
+ private void Action_HttpContent_Parameter(HttpContent c) { }
+
+ [Fact]
+ public void Check_HttpContent()
+ {
+ DefaultActionValueBinder binder = new DefaultActionValueBinder();
+
+ var binding = binder.GetBinding(GetAction("Action_HttpContent_Parameter"));
+
+ Assert.Equal(1, binding.ParameterBindings.Length);
+ AssertIsError(binding, 0);
+ }
+
+ private void Action_Derived_HttpContent_Parameter(StreamContent c) { }
+
+ [Fact]
+ public void Check_Derived_HttpContent()
+ {
+ DefaultActionValueBinder binder = new DefaultActionValueBinder();
+
+ var binding = binder.GetBinding(GetAction("Action_Derived_HttpContent_Parameter"));
+
+ Assert.Equal(1, binding.ParameterBindings.Length);
+ AssertIsError(binding, 0);
+ }
+
+ private void Action_Request_Parameter(HttpRequestMessage request) { }
+
+ [Fact]
+ public void Check_Request_Parameter()
+ {
+ DefaultActionValueBinder binder = new DefaultActionValueBinder();
+
+ var binding = binder.GetBinding(GetAction("Action_Request_Parameter"));
+
+ Assert.Equal(1, binding.ParameterBindings.Length);
+ AssertIsCustomBinder<HttpRequestParameterBinding>(binding, 0);
+ }
+
+
+
+ // Assert that the binding contract says the given parameter comes from the body
+ private void AssertIsBody(HttpActionBinding binding, int paramIdx)
+ {
+ HttpParameterBinding p = binding.ParameterBindings[paramIdx];
+ Assert.NotNull(p);
+ Assert.True(p.IsValid);
+ Assert.True(p.WillReadBody);
+ }
+
+ // Assert that the binding contract says the given parameter is not from the body (will be handled by model binding)
+ private void AssertIsModelBound(HttpActionBinding binding, int paramIdx)
+ {
+ HttpParameterBinding p = binding.ParameterBindings[paramIdx];
+ Assert.NotNull(p);
+ Assert.IsType<ModelBinderParameterBinding>(p);
+ Assert.True(p.IsValid);
+ Assert.False(p.WillReadBody);
+ }
+
+ // Assert that the binding contract says the given parameter will be bound to the cancellation token.
+ private void AssertIsCancellationToken(HttpActionBinding binding, int paramIdx)
+ {
+ AssertIsCustomBinder<CancellationTokenParameterBinding>(binding, paramIdx);
+ }
+
+ private void AssertIsError(HttpActionBinding binding, int paramIdx)
+ {
+ HttpParameterBinding p = binding.ParameterBindings[paramIdx];
+ Assert.NotNull(p);
+ Assert.False(p.IsValid);
+ Assert.False(p.WillReadBody);
+ }
+
+ private void AssertIsCustomBinder<T>(HttpActionBinding binding, int paramIdx)
+ {
+ HttpParameterBinding p = binding.ParameterBindings[paramIdx];
+ Assert.NotNull(p);
+ Assert.IsType<T>(p);
+ Assert.True(p.IsValid);
+ Assert.False(p.WillReadBody);
+ }
+
+
+ // Helper to get an ActionDescriptor for a method name.
+ private HttpActionDescriptor GetAction(string name)
+ {
+ return GetAction(name, new HttpConfiguration());
+ }
+
+ private HttpActionDescriptor GetAction(string name, HttpConfiguration config)
+ {
+ MethodInfo method = this.GetType().GetMethod(name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
+ Assert.NotNull(method);
+ return new ReflectedHttpActionDescriptor { MethodInfo = method, Configuration = config };
+ }
+
+ // Complex type to use with tests
+ class ComplexType
+ {
+ }
+
+ struct ComplexValueType
+ {
+ }
+
+ // Complex type to use with tests
+ [ModelBinder]
+ class ComplexTypeWithModelBinder
+ {
+ }
+
+ // Add Type converter for string, which causes the type to be viewed as a Simple type.
+ [TypeConverter(typeof(MyTypeConverter))]
+ public class ComplexTypeWithStringConverter
+ {
+ public string Data { get; set; }
+ public ComplexTypeWithStringConverter(string data)
+ {
+ Data = data;
+ }
+ }
+
+ // A string type converter
+ public class MyTypeConverter : TypeConverter
+ {
+ public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
+ {
+ if (sourceType == typeof(string))
+ {
+ return true;
+ }
+ return base.CanConvertFrom(context, sourceType);
+ }
+
+ public override object ConvertFrom(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value)
+ {
+ if (value is string)
+ {
+ return new ComplexTypeWithStringConverter((string)value);
+ }
+
+ return base.ConvertFrom(context, culture, value);
+ }
+ }
+
+ class CustomModelBinderProvider : ModelBinderProvider
+ {
+ public override IModelBinder GetBinder(HttpActionContext actionContext, ModelBindingContext bindingContext)
+ {
+ throw new NotImplementedException();
+ }
+ }
+ class CustomValueProviderFactory : ValueProviderFactory
+ {
+ public override IValueProvider GetValueProvider(HttpActionContext actionContext)
+ {
+ throw new NotImplementedException();
+ }
+ }
+ }
+}
diff --git a/test/System.Web.Http.Test/ModelBinding/FormDataCollectionExtensionsTest.cs b/test/System.Web.Http.Test/ModelBinding/FormDataCollectionExtensionsTest.cs
new file mode 100644
index 00000000..533b73ce
--- /dev/null
+++ b/test/System.Web.Http.Test/ModelBinding/FormDataCollectionExtensionsTest.cs
@@ -0,0 +1,229 @@
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Linq;
+using System.Net.Http;
+using System.Net.Http.Formatting;
+using System.Net.Http.Headers;
+using System.Reflection;
+using System.Threading;
+using System.Web.Http.Controllers;
+using System.Web.Http.ValueProviders;
+using Xunit;
+using Xunit.Extensions;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Http.ModelBinding
+{
+ public class FormDataCollectionExtensionsTest
+ {
+ [Theory]
+ [InlineData("", null)]
+ [InlineData("", "")] // empty
+ [InlineData("x", "x")] // normal key
+ [InlineData("", "[]")] // trim []
+ [InlineData("x", "x[]")] // trim []
+ [InlineData("x[234]", "x[234]")] // array index
+ [InlineData("x.y", "x[y]")] // field lookup
+ [InlineData("x.y.z", "x[y][z]")] // nested field lookup
+ [InlineData("x.y[234].x", "x[y][234][x]")] // compound
+ public void TestNormalize(string expectedMvc, string jqueryString)
+ {
+ Assert.Equal(expectedMvc, FormDataCollectionExtensions.NormalizeJQueryToMvc(jqueryString));
+ }
+
+ private static HttpContent FormContent(string s)
+ {
+ HttpContent content = new StringContent(s);
+ content.Headers.ContentType = new MediaTypeHeaderValue("application/x-www-form-urlencoded");
+
+ return content;
+ }
+
+ private T ParseJQuery<T>(string jquery)
+ {
+ HttpContent content = FormContent(jquery);
+ FormDataCollection fd = content.ReadAsAsync<FormDataCollection>().Result;
+ T result = fd.ReadAs<T>();
+ return result;
+ }
+
+ [Fact]
+ public void ReadIntArray()
+ {
+ // No key name means the top level object is an array
+ int[] result = ParseJQuery<int[]>("=30&=40&=50");
+
+ Assert.Equal(new int[] { 30,40,50 } , result);
+ }
+
+ [Fact]
+ public void ReadIntArrayWithBrackets()
+ {
+ // brackets for explicit array
+ int[] result = ParseJQuery<int[]>("[]=30&[]=40&[]=50");
+
+ Assert.Equal(new int[] { 30, 40, 50 }, result);
+ }
+
+ [Fact]
+ public void ReadIntArrayFromSingleElement()
+ {
+ // No key name means the top level object is an array
+ int[] result = ParseJQuery<int[]>("=30");
+
+ Assert.Equal(new int[] { 30 }, result);
+ }
+
+ public class ClassWithArrayField
+ {
+ public int[] x { get; set; }
+ }
+
+ [Fact]
+ public void ReadClassWithIntArray()
+ {
+ // specifying key name 'x=30' means that we have a field named x.
+ // multiple x keys mean that field is an array.
+ var result = ParseJQuery<ClassWithArrayField>("x=30&x=40&x=50");
+
+ Assert.Equal(new int[] { 30, 40, 50 }, result.x);
+ }
+
+ public class ComplexType
+ {
+ public string Str { get; set; }
+ public int I { get; set; }
+ public Point P { get; set; }
+ }
+ public class Point
+ {
+ public int X { get; set; }
+ public int Y { get; set; }
+ }
+
+ [Fact]
+ public void ReadClassWithFields()
+ {
+ // Basic container class with multiple fields
+ var result = ParseJQuery<Point>("X=3&Y=4");
+ Assert.Equal(3, result.X);
+ Assert.Equal(4, result.Y);
+ }
+
+ [Fact]
+ public void ReadClassWithFieldsFromUri()
+ {
+ var uri = new Uri("http://foo.com/?X=3&Y=4&Z=5");
+ FormDataCollection fd = new FormDataCollection(uri);
+ var result = fd.ReadAs<Point>();
+
+ Assert.Equal(3, result.X);
+ Assert.Equal(4, result.Y);
+ }
+
+ [Fact]
+ public void ReadClassWithFieldsAndPartialBind()
+ {
+ // Basic container class with multiple fields
+ // Extra Z=5 field, ignored since we're reading point.
+ var result = ParseJQuery<Point>("X=3&Y=4&Z=5");
+ Assert.Equal(3, result.X);
+ Assert.Equal(4, result.Y);
+ }
+
+ public class ClassWithPointArray
+ {
+ public Point[] Data { get; set; }
+ }
+
+ [Fact]
+ public void ReadArrayOfClasses()
+ {
+ // Array of classes.
+ string s = "Data[0][X]=10&Data[0][Y]=20&Data[1][X]=30&Data[1][Y]=40";
+ var result = ParseJQuery<ClassWithPointArray>(s);
+
+ Assert.NotNull(result.Data);
+ Assert.Equal(2, result.Data.Length);
+ Assert.Equal(10, result.Data[0].X);
+ Assert.Equal(20, result.Data[0].Y);
+ Assert.Equal(30, result.Data[1].X);
+ Assert.Equal(40, result.Data[1].Y);
+ }
+
+
+ [Fact]
+ public void ReadComplexNestedType()
+ {
+ var result = ParseJQuery<ComplexType>("Str=Hello+world&I=123&P[X]=3&P[Y]=4");
+ Assert.Equal("Hello world", result.Str);
+ Assert.Equal(123, result.I);
+ Assert.NotNull(result.P); // failed to find P
+ Assert.Equal(3, result.P.X);
+ Assert.Equal(4, result.P.Y);
+ }
+
+
+
+ class ComplexType2
+ {
+ public class Epsilon
+ {
+ public int[] f { get; set; }
+ }
+
+ public class Beta
+ {
+ public int c { get; set; }
+ public int d { get; set; }
+ }
+
+ public int[] a { get; set; }
+ public Beta[] b { get; set; }
+ public Epsilon e { get; set; }
+ }
+
+ [Fact]
+ public void ReadComplexNestedType2()
+ {
+ // Jquery encoding from this JSON: "{a:[1,2],b:[{c:3,d:4},{c:5,d:6}],e:{f:[7,8,9]}}";
+ string s = "a[]=1&a[]=2&b[0][c]=3&b[0][d]=4&b[1][c]=5&b[1][d]=6&e[f][]=7&e[f][]=8&e[f][]=9";
+ var result = ParseJQuery<ComplexType2>(s);
+
+ Assert.NotNull(result);
+ Assert.Equal(new int[] { 1, 2 }, result.a);
+ Assert.Equal(2, result.b.Length);
+ Assert.Equal(3, result.b[0].c);
+ Assert.Equal(4, result.b[0].d);
+ Assert.Equal(5, result.b[1].c);
+ Assert.Equal(6, result.b[1].d);
+ Assert.Equal(new int[] { 7, 8, 9 }, result.e.f);
+ }
+
+ [Fact]
+ public void ReadJaggedArray()
+ {
+ string s = "[0][]=9&[0][]=10&[1][]=11&[1][]=12&[2][]=13&[2][]=14";
+ var result = ParseJQuery<int[][]>(s);
+
+ Assert.Equal(9, result[0][0]);
+ Assert.Equal(10, result[0][1]);
+ Assert.Equal(11, result[1][0]);
+ Assert.Equal(12, result[1][1]);
+ Assert.Equal(13, result[2][0]);
+ Assert.Equal(14, result[2][1]);
+ }
+
+ [Fact]
+ public void ReadMultipleParameters()
+ {
+ // Basic container class with multiple fields
+ HttpContent content = FormContent("X=3&Y=4");
+ FormDataCollection fd = content.ReadAsAsync<FormDataCollection>().Result;
+
+ Assert.Equal(3, fd.ReadAs<int>("X"));
+ Assert.Equal("3", fd.ReadAs<string>("X"));
+ Assert.Equal(4, fd.ReadAs<int>("Y"));
+ }
+ }
+}
diff --git a/test/System.Web.Http.Test/ModelBinding/ModelBinderAttributeTest.cs b/test/System.Web.Http.Test/ModelBinding/ModelBinderAttributeTest.cs
new file mode 100644
index 00000000..ab42d323
--- /dev/null
+++ b/test/System.Web.Http.Test/ModelBinding/ModelBinderAttributeTest.cs
@@ -0,0 +1,101 @@
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Linq;
+using System.Net.Http;
+using System.Reflection;
+using System.Threading;
+using System.Web.Http.Controllers;
+using System.Web.Http.ValueProviders;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Http.ModelBinding
+{
+ public class ModelBinderAttributeTest
+ {
+ [Fact]
+ public void Empty_BinderType()
+ {
+ HttpConfiguration config = new HttpConfiguration();
+ config.ServiceResolver.SetServices(typeof(ModelBinderProvider), new CustomModelBinderProvider());
+
+ ModelBinderAttribute attr = new ModelBinderAttribute();
+
+ ModelBinderProvider provider = attr.GetModelBinderProvider(config);
+ Assert.IsType<CustomModelBinderProvider>(provider);
+ }
+
+ [Fact]
+ public void Illegal_BinderType()
+ {
+ // Given an illegal type.
+ // Constructor shouldn't throw. But trying to instantiate the model binder provider will throw.
+ ModelBinderAttribute attr = new ModelBinderAttribute(typeof(object));
+
+ Assert.Equal(typeof(object), attr.BinderType); // can still lookup illegal type
+ Assert.Throws<InvalidOperationException>(
+ () => attr.GetModelBinderProvider(new HttpConfiguration())
+ );
+ }
+
+ [Fact]
+ public void BinderType_Provided()
+ {
+ HttpConfiguration config = new HttpConfiguration();
+ ModelBinderAttribute attr = new ModelBinderAttribute(typeof(CustomModelBinderProvider));
+
+ ModelBinderProvider provider = attr.GetModelBinderProvider(config);
+ Assert.IsType<CustomModelBinderProvider>(provider);
+ }
+
+ [Fact]
+ public void BinderType_From_ServiceResolver()
+ {
+ // To test ServiceResolver, the registered type and actual type should be different.
+ HttpConfiguration config = new HttpConfiguration();
+ config.ServiceResolver.SetService(typeof(CustomModelBinderProvider), new SecondCustomModelBinderProvider());
+
+ ModelBinderAttribute attr = new ModelBinderAttribute(typeof(CustomModelBinderProvider));
+
+ ModelBinderProvider provider = attr.GetModelBinderProvider(config);
+ Assert.IsType<SecondCustomModelBinderProvider>(provider);
+ }
+
+ [Fact]
+ public void Set_ModelBinder_And_ValueProviders()
+ {
+ HttpConfiguration config = new HttpConfiguration();
+ ModelBinderAttribute attr = new ValueProviderAttribute(typeof(CustomValueProviderFactory)) { BinderType = typeof(CustomModelBinderProvider) };
+ IEnumerable<ValueProviderFactory> vpfs = attr.GetValueProviderFactories(config);
+
+ Assert.IsType<CustomModelBinderProvider>(attr.GetModelBinderProvider(config));
+ Assert.Equal(1, vpfs.Count());
+ Assert.IsType<CustomValueProviderFactory>(vpfs.First());
+ }
+
+ private class CustomModelBinderProvider : ModelBinderProvider
+ {
+ public override IModelBinder GetBinder(HttpActionContext actionContext, ModelBindingContext bindingContext)
+ {
+ throw new NotImplementedException();
+ }
+ }
+
+ private class SecondCustomModelBinderProvider : ModelBinderProvider
+ {
+ public override IModelBinder GetBinder(HttpActionContext actionContext, ModelBindingContext bindingContext)
+ {
+ throw new NotImplementedException();
+ }
+ }
+
+ private class CustomValueProviderFactory : ValueProviderFactory
+ {
+ public override IValueProvider GetValueProvider(HttpActionContext actionContext)
+ {
+ throw new NotImplementedException();
+ }
+ }
+
+ }
+}
diff --git a/test/System.Web.Http.Test/ModelBinding/ModelBinderConfigTest.cs b/test/System.Web.Http.Test/ModelBinding/ModelBinderConfigTest.cs
new file mode 100644
index 00000000..21adea4c
--- /dev/null
+++ b/test/System.Web.Http.Test/ModelBinding/ModelBinderConfigTest.cs
@@ -0,0 +1,147 @@
+using System.Web.Http.Controllers;
+using System.Web.Http.Metadata;
+using System.Web.TestUtil;
+using Moq;
+using Xunit;
+
+namespace System.Web.Http.ModelBinding
+{
+ public class ModelBinderConfigTest
+ {
+ [Fact]
+ public void GetUserResourceString_NullControllerContext_ReturnsNull()
+ {
+ // Act
+ string customResourceString = ModelBinderConfig.GetUserResourceString(null /* controllerContext */, "someResourceName", "someResourceClassKey");
+
+ // Assert
+ Assert.Null(customResourceString);
+ }
+
+ [Fact(Skip = "This functionality isn't enabled yet")]
+ public void GetUserResourceString_NullHttpContext_ReturnsNull()
+ {
+ Mock<HttpActionContext> context = new Mock<HttpActionContext>();
+ //context.Setup(o => o.HttpContext).Returns((HttpContextBase)null);
+
+ // Act
+ string customResourceString = ModelBinderConfig.GetUserResourceString(context.Object, "someResourceName", "someResourceClassKey");
+
+ // Assert
+ Assert.Null(customResourceString);
+ }
+
+ [Fact(Skip = "This functionality isn't enabled yet")]
+ public void GetUserResourceString_NullResourceKey_ReturnsNull()
+ {
+ Mock<HttpActionContext> context = new Mock<HttpActionContext>();
+
+ // Act
+ string customResourceString = ModelBinderConfig.GetUserResourceString(context.Object, "someResourceName", null /* resourceClassKey */);
+
+ // Assert
+ //context.Verify(o => o.HttpContext, Times.Never());
+ Assert.Null(customResourceString);
+ }
+
+ [Fact(Skip = "This functionality isn't enabled yet")]
+ public void GetUserResourceString_ValidResourceObject_ReturnsResourceString()
+ {
+ Mock<HttpActionContext> context = new Mock<HttpActionContext>();
+ //context.Setup(o => o.HttpContext.GetGlobalResourceObject("someResourceClassKey", "someResourceName", CultureInfo.CurrentUICulture))
+ // .Returns("My custom resource string");
+
+ // Act
+ string customResourceString = ModelBinderConfig.GetUserResourceString(context.Object, "someResourceName", "someResourceClassKey");
+
+ // Assert
+ Assert.Equal("My custom resource string", customResourceString);
+ }
+
+ [Fact]
+ public void TypeConversionErrorMessageProvider_DefaultValue()
+ {
+ // Arrange
+ ModelMetadata metadata = new ModelMetadata(new Mock<ModelMetadataProvider>().Object, null, null, typeof(int), "SomePropertyName");
+
+ // Act
+ string errorString = ModelBinderConfig.TypeConversionErrorMessageProvider(null, metadata, "some incoming value");
+
+ // Assert
+ Assert.Equal("The value 'some incoming value' is not valid for SomePropertyName.", errorString);
+ }
+
+ [Fact]
+ public void TypeConversionErrorMessageProvider_Property()
+ {
+ // Arrange
+ ModelBinderConfigWrapper wrapper = new ModelBinderConfigWrapper();
+
+ // Act & assert
+ try
+ {
+ MemberHelper.TestPropertyWithDefaultInstance(wrapper, "TypeConversionErrorMessageProvider", (ModelBinderErrorMessageProvider)DummyErrorSelector);
+ }
+ finally
+ {
+ wrapper.Reset();
+ }
+ }
+
+ [Fact]
+ public void ValueRequiredErrorMessageProvider_DefaultValue()
+ {
+ // Arrange
+ ModelMetadata metadata = new ModelMetadata(new Mock<ModelMetadataProvider>().Object, null, null, typeof(int), "SomePropertyName");
+
+ // Act
+ string errorString = ModelBinderConfig.ValueRequiredErrorMessageProvider(null, metadata, "some incoming value");
+
+ // Assert
+ Assert.Equal("A value is required.", errorString);
+ }
+
+ [Fact]
+ public void ValueRequiredErrorMessageProvider_Property()
+ {
+ // Arrange
+ ModelBinderConfigWrapper wrapper = new ModelBinderConfigWrapper();
+
+ // Act & assert
+ try
+ {
+ MemberHelper.TestPropertyWithDefaultInstance(wrapper, "ValueRequiredErrorMessageProvider", (ModelBinderErrorMessageProvider)DummyErrorSelector);
+ }
+ finally
+ {
+ wrapper.Reset();
+ }
+ }
+
+ private string DummyErrorSelector(HttpActionContext actionContext, ModelMetadata modelMetadata, object incomingValue)
+ {
+ throw new NotImplementedException();
+ }
+
+ private sealed class ModelBinderConfigWrapper
+ {
+ public ModelBinderErrorMessageProvider TypeConversionErrorMessageProvider
+ {
+ get { return ModelBinderConfig.TypeConversionErrorMessageProvider; }
+ set { ModelBinderConfig.TypeConversionErrorMessageProvider = value; }
+ }
+
+ public ModelBinderErrorMessageProvider ValueRequiredErrorMessageProvider
+ {
+ get { return ModelBinderConfig.ValueRequiredErrorMessageProvider; }
+ set { ModelBinderConfig.ValueRequiredErrorMessageProvider = value; }
+ }
+
+ public void Reset()
+ {
+ ModelBinderConfig.TypeConversionErrorMessageProvider = null;
+ ModelBinderConfig.ValueRequiredErrorMessageProvider = null;
+ }
+ }
+ }
+}
diff --git a/test/System.Web.Http.Test/ModelBinding/ModelBindingContextTest.cs b/test/System.Web.Http.Test/ModelBinding/ModelBindingContextTest.cs
new file mode 100644
index 00000000..f433fb67
--- /dev/null
+++ b/test/System.Web.Http.Test/ModelBinding/ModelBindingContextTest.cs
@@ -0,0 +1,126 @@
+using System.Web.Http.Metadata.Providers;
+using System.Web.Http.Util;
+using System.Web.Http.Validation;
+using System.Web.TestUtil;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Http.ModelBinding
+{
+ public class ModelBindingContextTest
+ {
+ [Fact]
+ public void CopyConstructor()
+ {
+ // Arrange
+ ModelBindingContext originalBindingContext = new ModelBindingContext
+ {
+ ModelMetadata = new EmptyModelMetadataProvider().GetMetadataForType(null, typeof(object)),
+ ModelName = "theName",
+ ModelState = new ModelStateDictionary(),
+ ValueProvider = new SimpleHttpValueProvider()
+ };
+
+ // Act
+ ModelBindingContext newBindingContext = new ModelBindingContext(originalBindingContext);
+
+ // Assert
+ Assert.Null(newBindingContext.ModelMetadata);
+ Assert.Equal("", newBindingContext.ModelName);
+ Assert.Equal(originalBindingContext.ModelState, newBindingContext.ModelState);
+ Assert.Equal(originalBindingContext.ValueProvider, newBindingContext.ValueProvider);
+ }
+
+ [Fact]
+ public void ModelProperty()
+ {
+ // Arrange
+ ModelBindingContext bindingContext = new ModelBindingContext
+ {
+ ModelMetadata = new EmptyModelMetadataProvider().GetMetadataForType(null, typeof(int))
+ };
+
+ // Act & assert
+ MemberHelper.TestPropertyValue(bindingContext, "Model", 42);
+ }
+
+ [Fact]
+ public void ModelProperty_ThrowsIfModelMetadataDoesNotExist()
+ {
+ // Arrange
+ ModelBindingContext bindingContext = new ModelBindingContext();
+
+ // Act & assert
+ Assert.Throws<InvalidOperationException>(
+ () => bindingContext.Model = null,
+ "The ModelMetadata property must be set before accessing this property.");
+ }
+
+ [Fact]
+ public void ModelNameProperty()
+ {
+ // Arrange
+ ModelBindingContext bindingContext = new ModelBindingContext();
+
+ // Act & assert
+ Assert.Reflection.StringProperty(bindingContext, bc => bc.ModelName, String.Empty);
+ }
+
+ [Fact]
+ public void ModelStateProperty()
+ {
+ // Arrange
+ ModelBindingContext bindingContext = new ModelBindingContext();
+ ModelStateDictionary modelState = new ModelStateDictionary();
+
+ // Act & assert
+ MemberHelper.TestPropertyWithDefaultInstance(bindingContext, "ModelState", modelState);
+ }
+
+ [Fact]
+ public void ModelAndModelTypeAreFedFromModelMetadata()
+ {
+ // Act
+ ModelBindingContext bindingContext = new ModelBindingContext
+ {
+ ModelMetadata = new EmptyModelMetadataProvider().GetMetadataForType(() => 42, typeof(int))
+ };
+
+ // Assert
+ Assert.Equal(42, bindingContext.Model);
+ Assert.Equal(typeof(int), bindingContext.ModelType);
+ }
+
+ [Fact]
+ public void ValidationNodeProperty()
+ {
+ // Act
+ ModelBindingContext bindingContext = new ModelBindingContext
+ {
+ ModelMetadata = new EmptyModelMetadataProvider().GetMetadataForType(() => 42, typeof(int))
+ };
+
+ // Act & assert
+ MemberHelper.TestPropertyWithDefaultInstance(bindingContext, "ValidationNode", new ModelValidationNode(bindingContext.ModelMetadata, "someName"));
+ }
+
+ [Fact]
+ public void ValidationNodeProperty_DefaultValues()
+ {
+ // Act
+ ModelBindingContext bindingContext = new ModelBindingContext
+ {
+ ModelMetadata = new EmptyModelMetadataProvider().GetMetadataForType(() => 42, typeof(int)),
+ ModelName = "theInt"
+ };
+
+ // Act
+ ModelValidationNode validationNode = bindingContext.ValidationNode;
+
+ // Assert
+ Assert.NotNull(validationNode);
+ Assert.Equal(bindingContext.ModelMetadata, validationNode.ModelMetadata);
+ Assert.Equal(bindingContext.ModelName, validationNode.ModelStateKey);
+ }
+ }
+}
diff --git a/test/System.Web.Http.Test/ModelBinding/ModelBindingUtilTest.cs b/test/System.Web.Http.Test/ModelBinding/ModelBindingUtilTest.cs
new file mode 100644
index 00000000..db2f76b4
--- /dev/null
+++ b/test/System.Web.Http.Test/ModelBinding/ModelBindingUtilTest.cs
@@ -0,0 +1,388 @@
+using System.Collections.Generic;
+using System.Web.Http.Controllers;
+using System.Web.Http.Metadata;
+using System.Web.Http.Metadata.Providers;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Http.ModelBinding
+{
+ public class ModelBindingUtilTest
+ {
+ [Fact]
+ public void CastOrDefault_CorrectType_ReturnsInput()
+ {
+ // Act
+ int retVal = ModelBindingHelper.CastOrDefault<int>(42);
+
+ // Assert
+ Assert.Equal(42, retVal);
+ }
+
+ [Fact]
+ public void CastOrDefault_IncorrectType_ReturnsDefaultTModel()
+ {
+ // Act
+ DateTime retVal = ModelBindingHelper.CastOrDefault<DateTime>(42);
+
+ // Assert
+ Assert.Equal(default(DateTime), retVal);
+ }
+
+ [Fact]
+ public void CreateIndexModelName_EmptyParentName()
+ {
+ // Act
+ string fullChildName = ModelBindingHelper.CreateIndexModelName("", 42);
+
+ // Assert
+ Assert.Equal("[42]", fullChildName);
+ }
+
+ [Fact]
+ public void CreateIndexModelName_IntIndex()
+ {
+ // Act
+ string fullChildName = ModelBindingHelper.CreateIndexModelName("parentName", 42);
+
+ // Assert
+ Assert.Equal("parentName[42]", fullChildName);
+ }
+
+ [Fact]
+ public void CreateIndexModelName_StringIndex()
+ {
+ // Act
+ string fullChildName = ModelBindingHelper.CreateIndexModelName("parentName", "index");
+
+ // Assert
+ Assert.Equal("parentName[index]", fullChildName);
+ }
+
+ [Fact]
+ public void CreatePropertyModelName()
+ {
+ // Act
+ string fullChildName = ModelBindingHelper.CreatePropertyModelName("parentName", "childName");
+
+ // Assert
+ Assert.Equal("parentName.childName", fullChildName);
+ }
+
+ [Fact]
+ public void CreatePropertyModelName_EmptyParentName()
+ {
+ // Act
+ string fullChildName = ModelBindingHelper.CreatePropertyModelName("", "childName");
+
+ // Assert
+ Assert.Equal("childName", fullChildName);
+ }
+
+ [Fact]
+ public void GetPossibleBinderInstance_Match_ReturnsBinder()
+ {
+ // Act
+ IModelBinder binder = ModelBindingHelper.GetPossibleBinderInstance(typeof(List<int>), typeof(List<>), typeof(SampleGenericBinder<>));
+
+ // Assert
+ Assert.IsType<SampleGenericBinder<int>>(binder);
+ }
+
+ [Fact]
+ public void GetPossibleBinderInstance_NoMatch_ReturnsNull()
+ {
+ // Act
+ IModelBinder binder = ModelBindingHelper.GetPossibleBinderInstance(typeof(ArraySegment<int>), typeof(List<>), typeof(SampleGenericBinder<>));
+
+ // Assert
+ Assert.Null(binder);
+ }
+
+ [Fact]
+ public void RawValueToObjectArray_RawValueIsEnumerable_ReturnsInputAsArray()
+ {
+ // Assert
+ List<int> original = new List<int> { 1, 2, 3, 4 };
+
+ // Act
+ object[] retVal = ModelBindingHelper.RawValueToObjectArray(original);
+
+ // Assert
+ Assert.Equal(new object[] { 1, 2, 3, 4 }, retVal);
+ }
+
+ [Fact]
+ public void RawValueToObjectArray_RawValueIsObject_WrapsObjectInSingleElementArray()
+ {
+ // Act
+ object[] retVal = ModelBindingHelper.RawValueToObjectArray(42);
+
+ // Assert
+ Assert.Equal(new object[] { 42 }, retVal);
+ }
+
+ [Fact]
+ public void RawValueToObjectArray_RawValueIsObjectArray_ReturnsInputInstance()
+ {
+ // Assert
+ object[] original = new object[2];
+
+ // Act
+ object[] retVal = ModelBindingHelper.RawValueToObjectArray(original);
+
+ // Assert
+ Assert.Same(original, retVal);
+ }
+
+ [Fact]
+ public void RawValueToObjectArray_RawValueIsString_WrapsStringInSingleElementArray()
+ {
+ // Act
+ object[] retVal = ModelBindingHelper.RawValueToObjectArray("hello");
+
+ // Assert
+ Assert.Equal(new object[] { "hello" }, retVal);
+ }
+
+ [Fact]
+ public void ReplaceEmptyStringWithNull_ConvertEmptyStringToNullDisabled_ModelIsEmptyString_LeavesModelAlone()
+ {
+ // Arrange
+ ModelMetadata modelMetadata = GetMetadata(typeof(string));
+ modelMetadata.ConvertEmptyStringToNull = false;
+
+ // Act
+ object model = "";
+ ModelBindingHelper.ReplaceEmptyStringWithNull(modelMetadata, ref model);
+
+ // Assert
+ Assert.Equal("", model);
+ }
+
+ [Fact]
+ public void ReplaceEmptyStringWithNull_ConvertEmptyStringToNullEnabled_ModelIsEmptyString_ReplacesModelWithNull()
+ {
+ // Arrange
+ ModelMetadata modelMetadata = GetMetadata(typeof(string));
+ modelMetadata.ConvertEmptyStringToNull = true;
+
+ // Act
+ object model = "";
+ ModelBindingHelper.ReplaceEmptyStringWithNull(modelMetadata, ref model);
+
+ // Assert
+ Assert.Null(model);
+ }
+
+ [Fact]
+ public void ReplaceEmptyStringWithNull_ConvertEmptyStringToNullEnabled_ModelIsWhitespaceString_ReplacesModelWithNull()
+ {
+ // Arrange
+ ModelMetadata modelMetadata = GetMetadata(typeof(string));
+ modelMetadata.ConvertEmptyStringToNull = true;
+
+ // Act
+ object model = " "; // whitespace
+ ModelBindingHelper.ReplaceEmptyStringWithNull(modelMetadata, ref model);
+
+ // Assert
+ Assert.Null(model);
+ }
+
+ [Fact]
+ public void ReplaceEmptyStringWithNull_ConvertEmptyStringToNullDisabled_ModelIsNotEmptyString_LeavesModelAlone()
+ {
+ // Arrange
+ ModelMetadata modelMetadata = GetMetadata(typeof(string));
+ modelMetadata.ConvertEmptyStringToNull = true;
+
+ // Act
+ object model = 42;
+ ModelBindingHelper.ReplaceEmptyStringWithNull(modelMetadata, ref model);
+
+ // Assert
+ Assert.Equal(42, model);
+ }
+
+ [Fact]
+ public void ValidateBindingContext_SuccessWithNonNullModel()
+ {
+ // Arrange
+ ModelBindingContext bindingContext = new ModelBindingContext
+ {
+ ModelMetadata = GetMetadata(typeof(string))
+ };
+ bindingContext.ModelMetadata.Model = "hello!";
+
+ // Act
+ ModelBindingHelper.ValidateBindingContext(bindingContext, typeof(string), false);
+
+ // Assert
+ // Nothing to do - if we got this far without throwing, the test succeeded
+ }
+
+ [Fact]
+ public void ValidateBindingContext_SuccessWithNullModel()
+ {
+ // Arrange
+ ModelBindingContext bindingContext = new ModelBindingContext
+ {
+ ModelMetadata = GetMetadata(typeof(string))
+ };
+
+ // Act
+ ModelBindingHelper.ValidateBindingContext(bindingContext, typeof(string), true);
+
+ // Assert
+ // Nothing to do - if we got this far without throwing, the test succeeded
+ }
+
+ [Fact]
+ public void ValidateBindingContextThrowsIfBindingContextIsNull()
+ {
+ // Act & assert
+ Assert.ThrowsArgumentNull(
+ () => ModelBindingHelper.ValidateBindingContext(null, typeof(string), true),
+ "bindingContext");
+ }
+
+ [Fact]
+ public void ValidateBindingContextThrowsIfModelInstanceIsWrongType()
+ {
+ // Arrange
+ ModelBindingContext bindingContext = new ModelBindingContext
+ {
+ ModelMetadata = GetMetadata(typeof(string))
+ };
+ bindingContext.ModelMetadata.Model = 42;
+
+ // Act & assert
+ Assert.Throws<ArgumentException>(
+ () => ModelBindingHelper.ValidateBindingContext(bindingContext, typeof(string), true),
+ @"The binding context has a Model of type 'System.Int32', but this binder can only operate on models of type 'System.String'.
+Parameter name: bindingContext");
+ }
+
+ [Fact]
+ public void ValidateBindingContextThrowsIfModelIsNullButCannotBe()
+ {
+ // Arrange
+ ModelBindingContext bindingContext = new ModelBindingContext
+ {
+ ModelMetadata = GetMetadata(typeof(string))
+ };
+
+ // Act & assert
+ Assert.Throws<ArgumentException>(
+ () => ModelBindingHelper.ValidateBindingContext(bindingContext, typeof(string), false),
+ @"The binding context has a null Model, but this binder requires a non-null model of type 'System.String'.
+Parameter name: bindingContext");
+ }
+
+ [Fact]
+ public void ValidateBindingContextThrowsIfModelMetadataIsNull()
+ {
+ // Arrange
+ ModelBindingContext bindingContext = new ModelBindingContext();
+
+ // Act & assert
+ Assert.Throws<ArgumentException>(
+ () => ModelBindingHelper.ValidateBindingContext(bindingContext, typeof(string), true),
+ @"The binding context cannot have a null ModelMetadata.
+Parameter name: bindingContext");
+ }
+
+ [Fact]
+ public void ValidateBindingContextThrowsIfModelTypeIsWrong()
+ {
+ // Arrange
+ ModelBindingContext bindingContext = new ModelBindingContext
+ {
+ ModelMetadata = GetMetadata(typeof(object))
+ };
+
+ // Act & assert
+ Assert.Throws<ArgumentException>(
+ () => ModelBindingHelper.ValidateBindingContext(bindingContext, typeof(string), true),
+ @"The binding context has a ModelType of 'System.Object', but this binder can only operate on models of type 'System.String'.
+Parameter name: bindingContext");
+ }
+
+ //[MetadataType(typeof(ModelWithBindAttribute_Buddy))]
+ //private class ModelWithBindAttribute
+ //{
+ // [Bind]
+ // private class ModelWithBindAttribute_Buddy
+ // {
+ // }
+ //}
+
+ //[ModelBinderProviderOptions(FrontOfList = true)]
+ //private class ProviderAtFront : ModelBinderProvider
+ //{
+ // public override IModelBinder GetBinder(HttpExecutionContext context, ModelBindingContext bindingContext)
+ // {
+ // throw new NotImplementedException();
+ // }
+ //}
+
+ //[ModelBinder(typeof(CustomBinder))]
+ //private class ModelWithProviderAttribute_Binder
+ //{
+ //}
+
+ //[ModelBinder(typeof(CustomGenericBinder<>))]
+ //private class ModelWithProviderAttribute_Binder_Generic<T>
+ //{
+ //}
+
+ //[ModelBinder(typeof(CustomBinder), SuppressPrefixCheck = true)]
+ //private class ModelWithProviderAttribute_Binder_SuppressPrefix
+ //{
+ //}
+
+ //[ModelBinder(typeof(CustomProvider))]
+ //private class ModelWithProviderAttribute_Provider
+ //{
+ //}
+
+ //private class CustomProvider : ModelBinderProvider
+ //{
+ // public override IModelBinder GetBinder(HttpExecutionContext context, ModelBindingContext bindingContext)
+ // {
+ // return new CustomBinder();
+ // }
+ //}
+
+ //private class CustomBinder : IModelBinder
+ //{
+ // public bool BindModel(HttpExecutionContext context, ModelBindingContext bindingContext)
+ // {
+ // throw new NotImplementedException();
+ // }
+ //}
+
+ //private class CustomGenericBinder<T> : IModelBinder
+ //{
+ // public bool BindModel(HttpExecutionContext context, ModelBindingContext bindingContext)
+ // {
+ // throw new NotImplementedException();
+ // }
+ //}
+
+ private static ModelMetadata GetMetadata(Type modelType)
+ {
+ EmptyModelMetadataProvider provider = new EmptyModelMetadataProvider();
+ return provider.GetMetadataForType(null, modelType);
+ }
+
+ private class SampleGenericBinder<T> : IModelBinder
+ {
+ public bool BindModel(HttpActionContext actionContext, ModelBindingContext bindingContext)
+ {
+ throw new NotImplementedException();
+ }
+ }
+ }
+}
diff --git a/test/System.Web.Http.Test/Properties/AssemblyInfo.cs b/test/System.Web.Http.Test/Properties/AssemblyInfo.cs
new file mode 100644
index 00000000..9155b469
--- /dev/null
+++ b/test/System.Web.Http.Test/Properties/AssemblyInfo.cs
@@ -0,0 +1,3 @@
+using System;
+
+[assembly: CLSCompliant(false)]
diff --git a/test/System.Web.Http.Test/Query/DataModel.cs b/test/System.Web.Http.Test/Query/DataModel.cs
new file mode 100644
index 00000000..3dcd0894
--- /dev/null
+++ b/test/System.Web.Http.Test/Query/DataModel.cs
@@ -0,0 +1,43 @@
+namespace System.Web.Http.Query
+{
+ public class Product
+ {
+ public int ProductID { get; set; }
+
+ public string ProductName { get; set; }
+ public int SupplierID { get; set; }
+ public int CategoryID { get; set; }
+ public string QuantityPerUnit { get; set; }
+ public decimal UnitPrice { get; set; }
+ public short UnitsInStock { get; set; }
+ public short UnitsOnOrder { get; set; }
+
+ public short ReorderLevel { get; set; }
+ public bool Discontinued { get; set; }
+ public DateTime DiscontinuedDate { get; set; }
+
+ public Category Category { get; set; }
+ }
+
+ public class Category
+ {
+ public int CategoryID { get; set; }
+ public string CategoryName { get; set; }
+ }
+
+ public class DataTypes
+ {
+ public Guid GuidProp { get; set; }
+ public DateTime DateTimeProp { get; set; }
+ public DateTimeOffset DateTimeOffsetProp { get; set; }
+ public byte[] ByteArrayProp { get; set; }
+ public TimeSpan TimeSpanProp { get; set; }
+ public decimal DecimalProp { get; set; }
+ public double DoubleProp { get; set; }
+ public float FloatProp { get; set; }
+ public long LongProp { get; set; }
+ public int IntProp { get; set; }
+
+ public string Inaccessable() { return String.Empty; }
+ }
+}
diff --git a/test/System.Web.Http.Test/Query/ODataQueryDeserializerTests.cs b/test/System.Web.Http.Test/Query/ODataQueryDeserializerTests.cs
new file mode 100644
index 00000000..aa6e6fe1
--- /dev/null
+++ b/test/System.Web.Http.Test/Query/ODataQueryDeserializerTests.cs
@@ -0,0 +1,623 @@
+using System.Linq;
+using System.Linq.Expressions;
+using Microsoft.TestCommon;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Http.Query
+{
+ public class ODataQueryDeserializerTests
+ {
+ [Fact]
+ public void SimpleMultipartQuery()
+ {
+ VerifyQueryDeserialization(
+ "$filter=ProductName eq 'Doritos'&$orderby=UnitPrice&$top=100",
+ "Where(Param_0 => (Param_0.ProductName == \"Doritos\")).OrderBy(Param_1 => Param_1.UnitPrice).Take(100)");
+ }
+
+ #region Ordering
+ [Fact]
+ public void OrderBy()
+ {
+ VerifyQueryDeserialization(
+ "$orderby=UnitPrice",
+ "OrderBy(Param_0 => Param_0.UnitPrice)");
+ }
+
+ [Fact]
+ public void OrderByAscending()
+ {
+ VerifyQueryDeserialization(
+ "$orderby=UnitPrice asc",
+ "OrderBy(Param_0 => Param_0.UnitPrice)");
+ }
+
+ [Fact]
+ public void OrderByDescending()
+ {
+ VerifyQueryDeserialization(
+ "$orderby=UnitPrice desc",
+ "OrderByDescending(Param_0 => Param_0.UnitPrice)");
+ }
+ #endregion
+
+ #region Inequalities
+ [Fact]
+ public void EqualityOperator()
+ {
+ VerifyQueryDeserialization(
+ "$filter=ProductName eq 'Doritos'",
+ "Where(Param_0 => (Param_0.ProductName == \"Doritos\"))");
+ }
+
+ [Fact]
+ public void NotEqualOperator()
+ {
+ VerifyQueryDeserialization(
+ "$filter=ProductName ne 'Doritos'",
+ "Where(Param_0 => (Param_0.ProductName != \"Doritos\"))");
+ }
+
+ [Fact]
+ public void GreaterThanOperator()
+ {
+ VerifyQueryDeserialization(
+ "$filter=UnitPrice gt 5.00",
+ "Where(Param_0 => (Param_0.UnitPrice > 5.00))");
+ }
+
+ [Fact]
+ public void GreaterThanEqualOperator()
+ {
+ VerifyQueryDeserialization(
+ "$filter=UnitPrice ge 5.00",
+ "Where(Param_0 => (Param_0.UnitPrice >= 5.00))");
+ }
+
+ [Fact]
+ public void LessThanOperator()
+ {
+ VerifyQueryDeserialization(
+ "$filter=UnitPrice lt 5.00",
+ "Where(Param_0 => (Param_0.UnitPrice < 5.00))");
+ }
+
+ [Fact]
+ public void LessThanOrEqualOperator()
+ {
+ VerifyQueryDeserialization(
+ "$filter=UnitPrice le 5.00",
+ "Where(Param_0 => (Param_0.UnitPrice <= 5.00))");
+ }
+
+ [Fact]
+ public void NegativeNumbers()
+ {
+ VerifyQueryDeserialization(
+ "$filter=UnitPrice le -5.00",
+ "Where(Param_0 => (Param_0.UnitPrice <= -5.00))");
+ }
+ #endregion
+
+ #region Logical Operators
+ [Fact]
+ public void OrOperator()
+ {
+ VerifyQueryDeserialization(
+ "$filter=UnitPrice eq 5.00 or UnitPrice eq 10.00",
+ "Where(Param_0 => ((Param_0.UnitPrice == 5.00) OrElse (Param_0.UnitPrice == 10.00)))");
+ }
+
+ [Fact]
+ public void AndOperator()
+ {
+ VerifyQueryDeserialization(
+ "$filter=UnitPrice eq 5.00 and UnitPrice eq 10.00",
+ "Where(Param_0 => ((Param_0.UnitPrice == 5.00) AndAlso (Param_0.UnitPrice == 10.00)))");
+ }
+
+ [Fact]
+ public void Negation()
+ {
+ VerifyQueryDeserialization(
+ "$filter=not (UnitPrice eq 5.00)",
+ "Where(Param_0 => Not((Param_0.UnitPrice == 5.00)))");
+ }
+ #endregion
+
+ #region Arithmetic Operators
+ [Fact]
+ public void Subtraction()
+ {
+ VerifyQueryDeserialization(
+ "$filter=UnitPrice sub 1.00 lt 5.00",
+ "Where(Param_0 => ((Param_0.UnitPrice - 1.00) < 5.00))");
+ }
+
+ [Fact]
+ public void Addition()
+ {
+ VerifyQueryDeserialization(
+ "$filter=UnitPrice add 1.00 lt 5.00",
+ "Where(Param_0 => ((Param_0.UnitPrice + 1.00) < 5.00))");
+ }
+
+ [Fact]
+ public void Multiplication()
+ {
+ VerifyQueryDeserialization(
+ "$filter=UnitPrice mul 1.00 lt 5.00",
+ "Where(Param_0 => ((Param_0.UnitPrice * 1.00) < 5.00))");
+ }
+
+ [Fact]
+ public void Division()
+ {
+ VerifyQueryDeserialization(
+ "$filter=UnitPrice div 1.00 lt 5.00",
+ "Where(Param_0 => ((Param_0.UnitPrice / 1.00) < 5.00))");
+ }
+
+ [Fact]
+ public void Modulo()
+ {
+ VerifyQueryDeserialization(
+ "$filter=UnitPrice mod 1.00 lt 5.00",
+ "Where(Param_0 => ((Param_0.UnitPrice % 1.00) < 5.00))");
+ }
+ #endregion
+
+ [Fact]
+ public void Grouping()
+ {
+ VerifyQueryDeserialization(
+ "$filter=((ProductName ne 'Doritos') or (UnitPrice lt 5.00))",
+ "Where(Param_0 => ((Param_0.ProductName != \"Doritos\") OrElse (Param_0.UnitPrice < 5.00)))");
+ }
+
+ [Fact]
+ public void MemberExpressions()
+ {
+ VerifyQueryDeserialization(
+ "$filter=Category/CategoryName eq 'Snacks'",
+ "Where(Param_0 => (Param_0.Category.CategoryName == \"Snacks\"))");
+ }
+
+ #region String Functions
+ [Fact]
+ public void StringSubstringOf()
+ {
+ // In OData, the order of parameters is actually reversed in the resulting
+ // string.Contains expression
+
+ VerifyQueryDeserialization(
+ "$filter=substringof('Abc', ProductName) eq true",
+ "Where(Param_0 => (Param_0.ProductName.Contains(\"Abc\") == True))");
+
+ VerifyQueryDeserialization(
+ "$filter=substringof(ProductName, 'Abc') eq true",
+ "Where(Param_0 => (\"Abc\".Contains(Param_0.ProductName) == True))");
+ }
+
+ [Fact]
+ public void StringStartsWith()
+ {
+ VerifyQueryDeserialization(
+ "$filter=startswith(ProductName, 'Abc') eq true",
+ "Where(Param_0 => (Param_0.ProductName.StartsWith(\"Abc\") == True))");
+ }
+
+ [Fact]
+ public void StringEndsWith()
+ {
+ VerifyQueryDeserialization(
+ "$filter=endswith(ProductName, 'Abc') eq true",
+ "Where(Param_0 => (Param_0.ProductName.EndsWith(\"Abc\") == True))");
+ }
+
+ [Fact]
+ public void StringLength()
+ {
+ VerifyQueryDeserialization(
+ "$filter=length(ProductName) gt 0",
+ "Where(Param_0 => (Param_0.ProductName.Length > 0))");
+ }
+
+ [Fact]
+ public void StringIndexOf()
+ {
+ VerifyQueryDeserialization(
+ "$filter=indexof(ProductName, 'Abc') eq 5",
+ "Where(Param_0 => (Param_0.ProductName.IndexOf(\"Abc\") == 5))");
+ }
+
+ [Fact]
+ public void StringReplace()
+ {
+ VerifyQueryDeserialization(
+ "$filter=replace(ProductName, 'Abc', 'Def') eq \"FooDef\"",
+ "Where(Param_0 => (Param_0.ProductName.Replace(\"Abc\", \"Def\") == \"FooDef\"))");
+ }
+
+ [Fact]
+ public void StringSubstring()
+ {
+ VerifyQueryDeserialization(
+ "$filter=substring(ProductName, 3) eq \"uctName\"",
+ "Where(Param_0 => (Param_0.ProductName.Substring(3) == \"uctName\"))");
+
+ VerifyQueryDeserialization(
+ "$filter=substring(ProductName, 3, 4) eq \"uctN\"",
+ "Where(Param_0 => (Param_0.ProductName.Substring(3, 4) == \"uctN\"))");
+ }
+
+ [Fact]
+ public void StringToLower()
+ {
+ VerifyQueryDeserialization(
+ "$filter=tolower(ProductName) eq 'tasty treats'",
+ "Where(Param_0 => (Param_0.ProductName.ToLower() == \"tasty treats\"))");
+ }
+
+ [Fact]
+ public void StringToUpper()
+ {
+ VerifyQueryDeserialization(
+ "$filter=toupper(ProductName) eq 'TASTY TREATS'",
+ "Where(Param_0 => (Param_0.ProductName.ToUpper() == \"TASTY TREATS\"))");
+ }
+
+ [Fact]
+ public void StringTrim()
+ {
+ VerifyQueryDeserialization(
+ "$filter=trim(ProductName) eq 'Tasty Treats'",
+ "Where(Param_0 => (Param_0.ProductName.Trim() == \"Tasty Treats\"))");
+ }
+
+ [Fact]
+ public void StringConcat()
+ {
+ VerifyQueryDeserialization(
+ "$filter=concat('Foo', 'Bar') eq 'FooBar'",
+ "Where(Param_0 => (Concat(\"Foo\", \"Bar\") == \"FooBar\"))");
+ }
+ #endregion
+
+ #region Date Functions
+ [Fact]
+ public void DateDay()
+ {
+ VerifyQueryDeserialization(
+ "$filter=day(DiscontinuedDate) eq 8",
+ "Where(Param_0 => (Param_0.DiscontinuedDate.Day == 8))");
+ }
+
+ [Fact]
+ public void DateMonth()
+ {
+ VerifyQueryDeserialization(
+ "$filter=month(DiscontinuedDate) eq 8",
+ "Where(Param_0 => (Param_0.DiscontinuedDate.Month == 8))");
+ }
+
+ [Fact]
+ public void DateYear()
+ {
+ VerifyQueryDeserialization(
+ "$filter=year(DiscontinuedDate) eq 1974",
+ "Where(Param_0 => (Param_0.DiscontinuedDate.Year == 1974))");
+ }
+
+ [Fact]
+ public void DateHour()
+ {
+ VerifyQueryDeserialization("$filter=hour(DiscontinuedDate) eq 8",
+ "Where(Param_0 => (Param_0.DiscontinuedDate.Hour == 8))");
+ }
+
+ [Fact]
+ public void DateMinute()
+ {
+ VerifyQueryDeserialization(
+ "$filter=minute(DiscontinuedDate) eq 12",
+ "Where(Param_0 => (Param_0.DiscontinuedDate.Minute == 12))");
+ }
+
+ [Fact]
+ public void DateSecond()
+ {
+ VerifyQueryDeserialization(
+ "$filter=second(DiscontinuedDate) eq 33",
+ "Where(Param_0 => (Param_0.DiscontinuedDate.Second == 33))");
+ }
+ #endregion
+
+ #region Math Functions
+ [Fact]
+ public void MathRound()
+ {
+ VerifyQueryDeserialization(
+ "$filter=round(UnitPrice) gt 5.00",
+ "Where(Param_0 => (Round(Param_0.UnitPrice) > 5.00))");
+ }
+
+ [Fact]
+ public void MathFloor()
+ {
+ VerifyQueryDeserialization(
+ "$filter=floor(UnitPrice) eq 5",
+ "Where(Param_0 => (Floor(Param_0.UnitPrice) == 5))");
+ }
+
+ [Fact]
+ public void MathCeiling()
+ {
+ VerifyQueryDeserialization(
+ "$filter=ceiling(UnitPrice) eq 5",
+ "Where(Param_0 => (Ceiling(Param_0.UnitPrice) == 5))");
+ }
+ #endregion
+
+ #region Data Types
+ [Fact]
+ public void GuidExpression()
+ {
+ VerifyQueryDeserialization<DataTypes>(
+ "$filter=GuidProp eq guid'0EFDAECF-A9F0-42F3-A384-1295917AF95E'",
+ "Where(Param_0 => (Param_0.GuidProp == 0efdaecf-a9f0-42f3-a384-1295917af95e))");
+
+ // verify case insensitivity
+ VerifyQueryDeserialization<DataTypes>(
+ "$filter=GuidProp eq GuiD'0EFDAECF-A9F0-42F3-A384-1295917AF95E'",
+ "Where(Param_0 => (Param_0.GuidProp == 0efdaecf-a9f0-42f3-a384-1295917af95e))");
+ }
+
+ [Fact]
+ public void DateTimeExpression()
+ {
+ VerifyQueryDeserialization<DataTypes>(
+ "$filter=DateTimeProp lt datetime'2000-12-12T12:00'",
+ "Where(Param_0 => (Param_0.DateTimeProp < 12/12/2000 12:00:00 PM))");
+ }
+
+ [Fact]
+ public void DateTimeOffsetExpression()
+ {
+ VerifyQueryDeserialization<DataTypes>(
+ "$filter=DateTimeOffsetProp ge datetimeoffset'2002-10-10T17:00:00Z'",
+ "Where(Param_0 => (Param_0.DateTimeOffsetProp >= 10/10/2002 5:00:00 PM +00:00))");
+ }
+
+ [Fact]
+ public void TimeExpression()
+ {
+ VerifyQueryDeserialization<DataTypes>(
+ "$filter=TimeSpanProp ge time'13:20:00'",
+ "Where(Param_0 => (Param_0.TimeSpanProp >= 13:20:00))");
+
+ // verify parse error for invalid literal format
+ Assert.Throws<ParseException>(delegate
+ {
+ VerifyQueryDeserialization<DataTypes>("$filter=TimeSpanProp ge time'invalid'", String.Empty);
+ },
+ "String was not recognized as a valid TimeSpan. (at index 20)");
+ }
+
+ [Fact]
+ public void BinaryExpression()
+ {
+ string binary = "23ABFF";
+ byte[] bytes = new byte[] {
+ byte.Parse("23", Globalization.NumberStyles.HexNumber),
+ byte.Parse("AB", Globalization.NumberStyles.HexNumber),
+ byte.Parse("FF", Globalization.NumberStyles.HexNumber)
+ };
+
+ VerifyQueryDeserialization<DataTypes>(
+ String.Format("$filter=ByteArrayProp eq binary'{0}'", binary),
+ "Where(Param_0 => (Param_0.ByteArrayProp == value(System.Byte[])))",
+ q =>
+ {
+ // verify that the binary data was deserialized into a constant expression of type byte[]
+ LambdaExpression lex = (LambdaExpression)((UnaryExpression)((MethodCallExpression)q.Expression).Arguments[1]).Operand;
+ BinaryExpression bex = (BinaryExpression)lex.Body;
+ byte[] actualBytes = (byte[])((ConstantExpression)bex.Right).Value;
+ Assert.True(actualBytes.SequenceEqual(bytes));
+ });
+
+ // test alternate 'X' syntax
+ VerifyQueryDeserialization<DataTypes>(
+ String.Format("$filter=ByteArrayProp eq X'{0}'", binary),
+ "Where(Param_0 => (Param_0.ByteArrayProp == value(System.Byte[])))",
+ q =>
+ {
+ // verify that the binary data was deserialized into a constant expression of type byte[]
+ LambdaExpression lex = (LambdaExpression)((UnaryExpression)((MethodCallExpression)q.Expression).Arguments[1]).Operand;
+ BinaryExpression bex = (BinaryExpression)lex.Body;
+ byte[] actualBytes = (byte[])((ConstantExpression)bex.Right).Value;
+ Assert.True(actualBytes.SequenceEqual(bytes));
+ });
+
+ // verify parse error for invalid literal format
+ Assert.Throws<ParseException>(delegate
+ {
+ VerifyQueryDeserialization<DataTypes>(
+ String.Format("$filter=ByteArrayProp eq binary'{0}'", "WXYZ"), String.Empty);
+ },
+ "Input string was not in a correct format. (at index 23)");
+
+ // verify parse error for invalid hex literal (odd hex strings are not supported)
+ Assert.Throws<ParseException>(delegate
+ {
+ VerifyQueryDeserialization<DataTypes>(
+ String.Format("$filter=ByteArrayProp eq binary'23A'", "XYZ"), String.Empty);
+ },
+ "Invalid hexadecimal literal. (at index 23)");
+ }
+
+ [Fact]
+ public void IntegerLiteralSuffix()
+ {
+ // long L
+ VerifyQueryDeserialization<DataTypes>(
+ "$filter=LongProp lt 987654321L and LongProp gt 123456789l",
+ "Where(Param_0 => ((Param_0.LongProp < 987654321) AndAlso (Param_0.LongProp > 123456789)))");
+
+ VerifyQueryDeserialization<DataTypes>(
+ "$filter=LongProp lt -987654321L and LongProp gt -123456789l",
+ "Where(Param_0 => ((Param_0.LongProp < -987654321) AndAlso (Param_0.LongProp > -123456789)))");
+ }
+
+ [Fact]
+ public void RealLiteralSuffixes()
+ {
+ // Float F
+ VerifyQueryDeserialization<DataTypes>(
+ "$filter=FloatProp lt 4321.56F and FloatProp gt 1234.56f",
+ "Where(Param_0 => ((Param_0.FloatProp < 4321.56) AndAlso (Param_0.FloatProp > 1234.56)))");
+
+ // Decimal M
+ VerifyQueryDeserialization<DataTypes>(
+ "$filter=DecimalProp lt 4321.56M and DecimalProp gt 1234.56m",
+ "Where(Param_0 => ((Param_0.DecimalProp < 4321.56) AndAlso (Param_0.DecimalProp > 1234.56)))");
+ }
+ #endregion
+
+ #region Negative tests
+ [Fact]
+ public void InvalidTypeCreationExpression()
+ {
+ // underminated string literal
+ Assert.Throws<ParseException>(delegate
+ {
+ VerifyQueryDeserialization<DataTypes>("$filter=TimeSpanProp ge time'13:20:00", String.Empty);
+ },
+ "Unterminated string literal (at index 29)");
+
+ // use of parens rather than quotes
+ Assert.Throws<ParseException>(delegate
+ {
+ VerifyQueryDeserialization<DataTypes>("$filter=TimeSpanProp ge time(13:20:00)", String.Empty);
+ },
+ "Invalid 'time' type creation expression. (at index 16)");
+
+ // verify the exception returned when type expression that isn't
+ // one of the supported keyword types is used. In this case it falls
+ // through as a member expression
+ Assert.Throws<ParseException>(delegate
+ {
+ VerifyQueryDeserialization("$filter=math'123' eq true", String.Empty);
+ },
+ "No property or field 'math' exists in type 'Product' (at index 0)");
+ }
+
+ [Fact]
+ public void InvalidMethodCall()
+ {
+ // incorrect casing of supported method
+ Assert.Throws<ParseException>(delegate
+ {
+ VerifyQueryDeserialization("$filter=Startswith(ProductName, 'Abc') eq true", String.Empty);
+ },
+ "Unknown identifier 'Startswith' (at index 0)");
+
+ // attempt to access a method defined on the entity type
+ Assert.Throws<ParseException>(delegate
+ {
+ VerifyQueryDeserialization<DataTypes>("$filter=Inaccessable() eq \"Bar\"", String.Empty);
+ },
+ "Unknown identifier 'Inaccessable' (at index 0)");
+
+ // verify that Type methods like string.PadLeft, etc. are not supported.
+ Assert.Throws<ParseException>(delegate
+ {
+ VerifyQueryDeserialization("$filter=ProductName/PadLeft(100000000000000000000) eq \"Foo\"", String.Empty);
+ },
+ "Unknown identifier 'PadLeft' (at index 12)");
+ }
+
+ [Fact]
+ public void InvalidQueryParameterToTop()
+ {
+ Assert.Throws<InvalidOperationException>(
+ () => VerifyQueryDeserialization("$top=-42", String.Empty),
+ "The OData query parameter '$top' has an invalid value. The value should be a positive integer. The provided value was '-42'");
+ }
+
+ [Fact]
+ public void InvalidQueryParameterToSkip()
+ {
+ Assert.Throws<InvalidOperationException>(
+ () => VerifyQueryDeserialization("$skip=-42", String.Empty),
+ "The OData query parameter '$skip' has an invalid value. The value should be a positive integer. The provided value was '-42'");
+ }
+
+ [Fact]
+ public void InvalidFunctionCall_DoesntStartWithOpenParen()
+ {
+ Assert.Throws<ParseException>(
+ () => VerifyQueryDeserialization("$filter=length%n(ProductName) eq 12", String.Empty),
+ "'(' expected (at index 6)");
+ }
+
+ [Fact]
+ public void InvalidFunctionCall_EmptyArguments()
+ {
+ Assert.Throws<ParseException>(
+ () => VerifyQueryDeserialization("$filter=length() eq 12", String.Empty),
+ "No applicable method 'Length' exists in type 'System.String' (at index 0)");
+ }
+ #endregion
+
+ [Fact(DisplayName = "ODataQueryDeserializer is internal.")]
+ public void TypeIsCorrect()
+ {
+ Assert.Type.HasProperties(typeof(ODataQueryDeserializer), TypeAssert.TypeProperties.IsStatic | TypeAssert.TypeProperties.IsClass);
+ }
+
+ /// <summary>
+ /// Call the query deserializer and verify the results
+ /// </summary>
+ /// <param name="queryString">The URL query string to deserialize (e.g. $filter=ProductName eq 'Doritos')</param>
+ /// <param name="expectedResult">The Expression.ToString() representation of the expected result (e.g. Where(Param_0 => (Param_0.ProductName == \"Doritos\"))</param>
+ private void VerifyQueryDeserialization(string queryString, string expectedResult)
+ {
+ VerifyQueryDeserialization<Product>(queryString, expectedResult, null);
+ }
+
+ private void VerifyQueryDeserialization<T>(string queryString, string expectedResult)
+ {
+ VerifyQueryDeserialization<T>(queryString, expectedResult, null);
+ }
+
+ private void VerifyQueryDeserialization<T>(string queryString, string expectedResult, Action<IQueryable<T>> verify)
+ {
+ string uri = "http://myhost/odata.svc/Get?" + queryString;
+
+ IQueryable<T> baseQuery = new T[0].AsQueryable();
+ IQueryable<T> resultQuery = (IQueryable<T>)ODataQueryDeserializer.Deserialize(baseQuery, new Uri(uri));
+ VerifyExpression(resultQuery, expectedResult);
+
+ if (verify != null)
+ {
+ verify(resultQuery);
+ }
+
+ QueryValidator.Instance.Validate(resultQuery);
+ }
+
+ private void VerifyExpression(IQueryable query, string expectedExpression)
+ {
+ // strip off the beginning part of the expression to get to the first
+ // actual query operator
+ string resultExpression = query.Expression.ToString();
+ int startIdx = (query.ElementType.FullName + "[]").Length + 1;
+ resultExpression = resultExpression.Substring(startIdx);
+
+ Assert.True(resultExpression == expectedExpression,
+ String.Format("Expected expression '{0}' but the deserializer produced '{1}'", expectedExpression, resultExpression));
+ }
+ }
+}
diff --git a/test/System.Web.Http.Test/Query/QueryValidatorTest.cs b/test/System.Web.Http.Test/Query/QueryValidatorTest.cs
new file mode 100644
index 00000000..ff5875b4
--- /dev/null
+++ b/test/System.Web.Http.Test/Query/QueryValidatorTest.cs
@@ -0,0 +1,93 @@
+using System.Linq;
+using System.Runtime.Serialization;
+using System.Web.TestUtil;
+using System.Xml.Serialization;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Http.Query
+{
+ public class QueryValidatorTest
+ {
+ QueryValidator _queryValidator = QueryValidator.Instance;
+
+ [Fact]
+ public void XmlIgnoreAttributeCausesInvalidOperation()
+ {
+ IQueryable<QueryValidatorSampleClass> query = new QueryValidatorSampleClass[0].AsQueryable();
+
+ Assert.Throws<InvalidOperationException>(
+ () => _queryValidator.Validate(query.Where((sample) => sample.XmlIgnoreProperty == 0)),
+ "No property or field 'XmlIgnoreProperty' exists in type 'QueryValidatorSampleClass'");
+ }
+
+ [Fact]
+ public void IgnoreDataMemberAttributeCausesInvalidOperation()
+ {
+ IQueryable<QueryValidatorSampleClass> query = new QueryValidatorSampleClass[0].AsQueryable();
+
+ Assert.Throws<InvalidOperationException>(
+ () => _queryValidator.Validate(query.Where((sample) => sample.IgnoreDataMemberProperty == 0)),
+ "No property or field 'IgnoreDataMemberProperty' exists in type 'QueryValidatorSampleClass'");
+ }
+
+ [Fact]
+ public void NonSerializedAttributeCausesInvalidOperation()
+ {
+ IQueryable<QueryValidatorSampleClass> query = new QueryValidatorSampleClass[0].AsQueryable();
+
+ Assert.Throws<InvalidOperationException>(
+ () => _queryValidator.Validate(query.Where((sample) => sample.NonSerializedAttributeField == 0)),
+ "No property or field 'NonSerializedAttributeField' exists in type 'QueryValidatorSampleClass'");
+ }
+
+ [Fact]
+ public void NormalPropertyAccessDoesnotThrow()
+ {
+ IQueryable<QueryValidatorSampleClass> query = new QueryValidatorSampleClass[0].AsQueryable();
+
+ _queryValidator.Validate(query.Where((sample) => sample.NormalProperty == 0));
+ }
+
+ [Fact]
+ public void DataContractDataMemberPropertyAccessDoesnotThrow()
+ {
+ IQueryable<QueryValidatorSampleDataContractClass> query = new QueryValidatorSampleDataContractClass[0].AsQueryable();
+
+ _queryValidator.Validate(query.Where((sample) => sample.DataMemberProperty == 0));
+ }
+
+ [Fact]
+ public void DataContractNonDataMemberPropertyAccessCausesInvalidOperation()
+ {
+ IQueryable<QueryValidatorSampleDataContractClass> query = new QueryValidatorSampleDataContractClass[0].AsQueryable();
+
+ Assert.Throws<InvalidOperationException>(
+ () => _queryValidator.Validate(query.Where((sample) => sample.NonDataMemberProperty == 0)),
+ "No property or field 'NonDataMemberProperty' exists in type 'QueryValidatorSampleDataContractClass'");
+ }
+
+ public class QueryValidatorSampleClass
+ {
+ [XmlIgnore]
+ public int XmlIgnoreProperty { get; set; }
+
+ [IgnoreDataMember]
+ public int IgnoreDataMemberProperty { get; set; }
+
+ [NonSerialized]
+ public int NonSerializedAttributeField = 0;
+
+ public int NormalProperty { get; set; }
+ }
+
+ [DataContract]
+ public class QueryValidatorSampleDataContractClass
+ {
+ [DataMember]
+ public int DataMemberProperty { get; set; }
+
+ public int NonDataMemberProperty { get; set; }
+ }
+ }
+}
diff --git a/test/System.Web.Http.Test/Routing/HttpRouteTest.cs b/test/System.Web.Http.Test/Routing/HttpRouteTest.cs
new file mode 100644
index 00000000..689030ca
--- /dev/null
+++ b/test/System.Web.Http.Test/Routing/HttpRouteTest.cs
@@ -0,0 +1,27 @@
+using System.Net.Http;
+using Xunit;
+using Xunit.Extensions;
+
+namespace System.Web.Http.Routing
+{
+ public class HttpRouteTest
+ {
+ [Theory]
+ [InlineData("123 456")]
+ [InlineData("123 {456")]
+ [InlineData("123 [45]6")]
+ [InlineData("123 (4)56")]
+ [InlineData("abc+56")]
+ [InlineData("abc.56")]
+ [InlineData("abc*56")]
+ [InlineData(@"hello12.1[)]*^$=!@23}")]
+ public void GetRouteData_HandlesUrlEncoding(string id)
+ {
+ HttpRoute route = new HttpRoute("{controller}/{id}");
+ Uri uri = new Uri("http://localhost/test/" + Uri.EscapeDataString(id) + "/");
+ IHttpRouteData routeData = route.GetRouteData("", new HttpRequestMessage(HttpMethod.Get, uri));
+ Assert.Equal("test", routeData.Values["controller"]);
+ Assert.Equal(id, routeData.Values["id"]);
+ }
+ }
+}
diff --git a/test/System.Web.Http.Test/Routing/UrlHelperTest.cs b/test/System.Web.Http.Test/Routing/UrlHelperTest.cs
new file mode 100644
index 00000000..2ba3e4f8
--- /dev/null
+++ b/test/System.Web.Http.Test/Routing/UrlHelperTest.cs
@@ -0,0 +1,149 @@
+using System.Collections.Generic;
+using System.Web.Http.Controllers;
+using Microsoft.TestCommon;
+using Xunit;
+using Xunit.Extensions;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Http.Routing
+{
+ public class UrlHelperTest
+ {
+ [Fact]
+ public void UrlHelper_CtorThrows_WithNullContext()
+ {
+ Assert.ThrowsArgumentNull(
+ () => new UrlHelper(null),
+ "controllerContext");
+ }
+
+ [Fact]
+ public void ControllerContext_HasUrlHelperWithValidContext()
+ {
+ HttpControllerContext cc = new HttpControllerContext();
+
+ Assert.NotNull(cc.Url);
+ Assert.IsType<UrlHelper>(cc.Url);
+ Assert.Same(cc, cc.Url.ControllerContext);
+ }
+
+ [Theory]
+ [PropertyData("UrlGeneratorTestData")]
+ public void UrlHelper_UsesCurrentRouteDataToPopulateValues_WithObjectValues(string controller, int? id, string expectedUrl)
+ {
+ var url = GetUrlHelperForApi();
+
+ object routeValues = null;
+ if (controller == null)
+ {
+ if (id == null)
+ {
+ routeValues = null;
+ }
+ else
+ {
+ routeValues = new { id };
+ }
+ }
+ else
+ {
+ if (id == null)
+ {
+ routeValues = new { controller };
+ }
+ else
+ {
+ routeValues = new { controller, id };
+ }
+ }
+ string generatedUrl = url.Route("route1", routeValues);
+
+ Assert.Equal(expectedUrl, generatedUrl);
+ }
+
+ [Theory]
+ [PropertyData("UrlGeneratorTestData")]
+ public void UrlHelper_UsesCurrentRouteDataToPopulateValues_WithDictionaryValues(string controller, int? id, string expectedUrl)
+ {
+ var url = GetUrlHelperForApi();
+
+ Dictionary<string, object> routeValues = new Dictionary<string, object>();
+ if (controller == null)
+ {
+ if (id == null)
+ {
+ routeValues = null;
+ }
+ else
+ {
+ routeValues.Add("id", id);
+ }
+ }
+ else
+ {
+ if (id == null)
+ {
+ routeValues.Add("controller", controller);
+ }
+ else
+ {
+ routeValues.Add("controller", controller);
+ routeValues.Add("id", id);
+ }
+ }
+ string generatedUrl = url.Route("route1", routeValues);
+
+ Assert.Equal(expectedUrl, generatedUrl);
+ }
+
+ [Fact]
+ public void UrlHelper_Throws_WhenWrongNameUsed_WithObjectValues()
+ {
+ var url = GetUrlHelperForApi();
+ Assert.ThrowsArgument(
+ () => url.Route("route-doesn't-exist", null),
+ "name",
+ "A route named 'route-doesn't-exist' could not be found in the route collection.");
+ }
+
+ [Fact]
+ public void UrlHelper_Throws_WhenWrongNameUsed_WithDictionaryValues()
+ {
+ var url = GetUrlHelperForApi();
+ Assert.ThrowsArgument(
+ () => url.Route("route-doesn't-exist", (IDictionary<string, object>)null),
+ "name",
+ "A route named 'route-doesn't-exist' could not be found in the route collection.");
+ }
+
+ private static UrlHelper GetUrlHelperForApi()
+ {
+ HttpControllerContext cc = new HttpControllerContext();
+
+ // Set up routes
+ var routes = new HttpRouteCollection("/somerootpath");
+ IHttpRoute route = routes.MapHttpRoute("route1", "{controller}/{id}");
+ cc.Configuration = new HttpConfiguration(routes);
+
+ cc.RouteData = new HttpRouteData(route, new HttpRouteValueDictionary(new { controller = "people", id = "123" }));
+
+ return cc.Url;
+ }
+
+ public static IEnumerable<object[]> UrlGeneratorTestData
+ {
+ get
+ {
+ return new TheoryDataSet<string, int?, string>()
+ {
+ { null, 456, "/somerootpath/people/456"}, // Just override ID, so ID is replaced
+ { "people", 456, "/somerootpath/people/456"}, // Just override ID, so ID is replaced
+ { null, null, "/somerootpath/people/123"}, // Override nothing, so everything the same
+ { "people", null, "/somerootpath/people/123"}, // Override nothing, so everything the same
+ { "customers", 456, "/somerootpath/customers/456"}, // Override everything, so everything changed
+ { "customers", null, null}, // Override controller, which clears out the ID, so it doesn't match (i.e. null)
+ };
+ }
+ }
+ }
+}
diff --git a/test/System.Web.Http.Test/Services/DependencyResolverTests.cs b/test/System.Web.Http.Test/Services/DependencyResolverTests.cs
new file mode 100644
index 00000000..0b121c4c
--- /dev/null
+++ b/test/System.Web.Http.Test/Services/DependencyResolverTests.cs
@@ -0,0 +1,219 @@
+using System.Collections.Generic;
+using System.Linq;
+using System.Web.Http.Controllers;
+using Microsoft.TestCommon;
+using Moq;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Http.Services
+{
+ public class DependencyResolverTests
+ {
+ // TODO: Add tests for SetService and GetCachedService
+
+ [Fact]
+ public void TypeIsCorrect()
+ {
+ Assert.Type.HasProperties<DependencyResolver>(TypeAssert.TypeProperties.IsPublicVisibleClass);
+ }
+
+ [Fact]
+ public void ConstructorThrowsOnNullConfig()
+ {
+ Assert.ThrowsArgumentNull(() => new DependencyResolver(null), "configuration");
+ Assert.ThrowsArgumentNull(() => new DependencyResolver(null, null), "configuration");
+ }
+
+ [Fact]
+ public void ConstructorWithUserNullDependencyResolver()
+ {
+ // Arrange
+ HttpConfiguration config = new HttpConfiguration();
+ DependencyResolver resolver = new DependencyResolver(config, null);
+
+ // Act
+ object service = resolver.GetService(typeof(IHttpActionSelector));
+
+ // Assert
+ Assert.Null(service);
+ }
+
+ [Fact]
+ public void ConstructorWithUserDependencyResolver()
+ {
+ // Arrange
+ HttpConfiguration config = new HttpConfiguration();
+ Mock<IDependencyResolver> userResolverMock = new Mock<IDependencyResolver>();
+ IHttpActionSelector actionSelector = new Mock<IHttpActionSelector>().Object;
+ userResolverMock.Setup(ur => ur.GetService(typeof(IHttpActionSelector))).Returns(actionSelector).Verifiable();
+ DependencyResolver resolver = new DependencyResolver(config, userResolverMock.Object);
+
+ // Act
+ object service = resolver.GetService(typeof(IHttpActionSelector));
+
+ // Assert
+ userResolverMock.Verify();
+ Assert.Same(actionSelector, service);
+ }
+
+ [Fact]
+ public void GetServiceThrowsOnNull()
+ {
+ // Arrange
+ HttpConfiguration config = new HttpConfiguration();
+ DependencyResolver resolver = new DependencyResolver(config, null);
+
+ // Act
+ Assert.ThrowsArgumentNull(() => resolver.GetService(null), "serviceType");
+ }
+
+ [Fact]
+ public void GetServiceDoesntEagerlyCreate()
+ {
+ // Arrange
+ HttpConfiguration config = new HttpConfiguration();
+ DependencyResolver resolver = new DependencyResolver(config);
+
+ // Act
+ object result = resolver.GetService(typeof(SomeClass));
+
+ // Assert
+ // Service resolver should not have created an instance or arbitrary class.
+ Assert.Null(result);
+ }
+
+ [Fact]
+ public void GetServicesThrowsOnNull()
+ {
+ // Arrange
+ HttpConfiguration config = new HttpConfiguration();
+ DependencyResolver resolver = new DependencyResolver(config, null);
+
+ // Act
+ Assert.ThrowsArgumentNull(() => resolver.GetServices(null), "serviceType");
+ }
+
+ [Fact]
+ public void GetServicesDoesntEagerlyCreate()
+ {
+ // Arrange
+ HttpConfiguration config = new HttpConfiguration();
+ DependencyResolver resolver = new DependencyResolver(config);
+
+ // Act
+ IEnumerable<object> result = resolver.GetServices(typeof(SomeClass));
+
+ // Assert
+ // Service resolver should not have created an instance or arbitrary class.
+ Assert.Empty(result);
+ }
+
+ // Arbitrary test class that we can use with the service resolver
+ internal class SomeClass
+ {
+ }
+
+ [Fact]
+ public void SetResolverIDependencyResolverThrowsOnNull()
+ {
+ HttpConfiguration config = new HttpConfiguration();
+ DependencyResolver resolver = new DependencyResolver(config);
+ Assert.ThrowsArgumentNull(() => resolver.SetResolver((IDependencyResolver)null), "resolver");
+ }
+
+ [Fact]
+ public void SetResolverIDependencyResolver()
+ {
+ // Arrange
+ HttpConfiguration config = new HttpConfiguration();
+ DependencyResolver resolver = new DependencyResolver(config);
+
+ Mock<IDependencyResolver> userResolverMock = new Mock<IDependencyResolver>();
+ IHttpActionSelector actionSelector = new Mock<IHttpActionSelector>().Object;
+ userResolverMock.Setup(ur => ur.GetService(typeof(IHttpActionSelector))).Returns(actionSelector).Verifiable();
+ userResolverMock.Setup(ur => ur.GetServices(typeof(IHttpActionSelector))).Returns(new List<object> { actionSelector }).Verifiable();
+
+ resolver.SetResolver(userResolverMock.Object);
+
+ // Act
+ object service = resolver.GetService(typeof(IHttpActionSelector));
+ IEnumerable<object> services = resolver.GetServices(typeof(IHttpActionSelector));
+
+ // Assert
+ userResolverMock.Verify();
+ Assert.Same(actionSelector, service);
+ Assert.Same(actionSelector, services.ElementAt(0));
+ }
+
+ [Fact]
+ public void SetResolverCommonServiceLocatorThrowsOnNull()
+ {
+ HttpConfiguration config = new HttpConfiguration();
+ DependencyResolver resolver = new DependencyResolver(config);
+ Assert.ThrowsArgumentNull(() => resolver.SetResolver((object)null), "commonServiceLocator");
+ }
+
+ [Fact]
+ public void SetResolverCommonServiceLocator()
+ {
+ // Arrange
+ HttpConfiguration config = new HttpConfiguration();
+ DependencyResolver resolver = new DependencyResolver(config);
+
+ Mock<CommonServiceLocatorSlim> userResolverMock = new Mock<CommonServiceLocatorSlim>();
+ IHttpActionSelector actionSelector = new Mock<IHttpActionSelector>().Object;
+ userResolverMock.Setup(ur => ur.GetInstance(typeof(IHttpActionSelector))).Returns(actionSelector).Verifiable();
+ userResolverMock.Setup(ur => ur.GetAllInstances(typeof(IHttpActionSelector))).Returns(new List<object> { actionSelector }).Verifiable();
+
+ resolver.SetResolver(userResolverMock.Object);
+
+ // Act
+ object service = resolver.GetService(typeof(IHttpActionSelector));
+ IEnumerable<object> services = resolver.GetServices(typeof(IHttpActionSelector));
+
+ // Assert
+ userResolverMock.Verify();
+ Assert.Same(actionSelector, service);
+ Assert.Same(actionSelector, services.ElementAt(0));
+ }
+
+ public interface CommonServiceLocatorSlim
+ {
+ object GetInstance(Type serviceType);
+
+ IEnumerable<object> GetAllInstances(Type serviceType);
+ }
+
+ [Fact]
+ public void SetResolverFuncThrowsOnNull()
+ {
+ HttpConfiguration config = new HttpConfiguration();
+ DependencyResolver resolver = new DependencyResolver(config);
+ Assert.ThrowsArgumentNull(() => resolver.SetResolver(null, _ => null), "getService");
+ Assert.ThrowsArgumentNull(() => resolver.SetResolver(_ => null, null), "getServices");
+ }
+
+ [Fact]
+ public void SetResolverFunc()
+ {
+ // Arrange
+ HttpConfiguration config = new HttpConfiguration();
+ DependencyResolver resolver = new DependencyResolver(config);
+
+ Mock<CommonServiceLocatorSlim> userResolverMock = new Mock<CommonServiceLocatorSlim>();
+ IHttpActionSelector actionSelector = new Mock<IHttpActionSelector>().Object;
+
+ resolver.SetResolver(_ => actionSelector, _ => new List<object> { actionSelector });
+
+ // Act
+ object service = resolver.GetService(typeof(IHttpActionSelector));
+ IEnumerable<object> services = resolver.GetServices(typeof(IHttpActionSelector));
+
+ // Assert
+ userResolverMock.Verify();
+ Assert.Same(actionSelector, service);
+ Assert.Same(actionSelector, services.ElementAt(0));
+ }
+ }
+}
diff --git a/test/System.Web.Http.Test/System.Web.Http.Test.csproj b/test/System.Web.Http.Test/System.Web.Http.Test.csproj
new file mode 100644
index 00000000..d6e7815d
--- /dev/null
+++ b/test/System.Web.Http.Test/System.Web.Http.Test.csproj
@@ -0,0 +1,247 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory),Runtime.sln))\tools\WebStack.settings.targets" />
+ <PropertyGroup>
+ <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+ <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+ <ProductVersion>9.0.30729</ProductVersion>
+ <SchemaVersion>2.0</SchemaVersion>
+ <ProjectGuid>{7F2C796F-43B2-4F8F-ABFF-A154EC8AAFA1}</ProjectGuid>
+ <OutputType>Library</OutputType>
+ <AppDesignerFolder>Properties</AppDesignerFolder>
+ <RootNamespace>System.Web.Http</RootNamespace>
+ <AssemblyName>System.Web.Http.Test</AssemblyName>
+ <FileAlignment>512</FileAlignment>
+ <ProjectTypeGuids>{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
+ <ErrorReport>prompt</ErrorReport>
+ <WarningLevel>4</WarningLevel>
+ <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
+ <DebugSymbols>true</DebugSymbols>
+ <DebugType>full</DebugType>
+ <Optimize>false</Optimize>
+ <OutputPath>$(WebStackRootPath)\bin\Debug\Test\</OutputPath>
+ <DefineConstants>TRACE;DEBUG</DefineConstants>
+ <CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
+ <DebugType>pdbonly</DebugType>
+ <Optimize>true</Optimize>
+ <OutputPath>$(WebStackRootPath)\bin\Release\Test\</OutputPath>
+ <DefineConstants>TRACE</DefineConstants>
+ <CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)' == 'CodeCoverage' ">
+ <DebugSymbols>true</DebugSymbols>
+ <DebugType>full</DebugType>
+ <Optimize>false</Optimize>
+ <OutputPath>$(WebStackRootPath)\bin\CodeCoverage\Test\</OutputPath>
+ <DefineConstants>TRACE;DEBUG</DefineConstants>
+ <CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
+ </PropertyGroup>
+ <ItemGroup>
+ <Reference Include="Moq, Version=4.0.10827.0, Culture=neutral, PublicKeyToken=69f491c39445e920, processorArchitecture=MSIL">
+ <HintPath>..\..\packages\Moq.4.0.10827\lib\NET40\Moq.dll</HintPath>
+ </Reference>
+ <Reference Include="System" />
+ <Reference Include="System.ComponentModel.DataAnnotations" />
+ <Reference Include="System.Configuration" />
+ <Reference Include="System.Core" />
+ <Reference Include="System.Data.Linq" />
+ <Reference Include="System.Net.Http">
+ <HintPath>..\..\packages\Microsoft.Net.Http.2.0.20302.1\lib\net40\System.Net.Http.dll</HintPath>
+ </Reference>
+ <Reference Include="System.Net.Http.WebRequest">
+ <HintPath>..\..\packages\Microsoft.Net.Http.2.0.20302.1\lib\net40\System.Net.Http.WebRequest.dll</HintPath>
+ </Reference>
+ <Reference Include="System.Runtime.Serialization" />
+ <Reference Include="System.Web" />
+ <Reference Include="System.XML" />
+ <Reference Include="xunit">
+ <HintPath>..\..\packages\xunit.1.9.0.1566\lib\xunit.dll</HintPath>
+ </Reference>
+ <Reference Include="xunit.extensions">
+ <HintPath>..\..\packages\xunit.extensions.1.9.0.1566\lib\xunit.extensions.dll</HintPath>
+ </Reference>
+ </ItemGroup>
+ <ItemGroup>
+ <Compile Include="AuthorizeAttributeTest.cs" />
+ <Compile Include="Controllers\Apis\User.cs" />
+ <Compile Include="Controllers\Apis\UsersRpcController.cs" />
+ <Compile Include="Controllers\HttpControllerContextTest.cs" />
+ <Compile Include="Controllers\HttpConfigurationTest.cs" />
+ <Compile Include="DictionaryExtensionsTest.cs" />
+ <Compile Include="Filters\HttpFilterCollectionTest.cs" />
+ <Compile Include="HttpServerTest.cs" />
+ <Compile Include="HttpRequestMessageExtensionsTest.cs" />
+ <Compile Include="HttpRouteCollectionExtensionsTest.cs" />
+ <Compile Include="Dispatcher\DefaultBuildManagerTest.cs" />
+ <Compile Include="Filters\QueryCompositionFilterAttributeTest.cs" />
+ <Compile Include="Filters\EnumerableEvaluatorFilterProviderTest.cs" />
+ <Compile Include="Controllers\ApiControllerActionInvokerTest.cs" />
+ <Compile Include="Controllers\ApiControllerActionSelectorTest.cs" />
+ <Compile Include="Controllers\HttpParameterDescriptorTest.cs" />
+ <Compile Include="Controllers\ReflectedHttpParameterDescriptorTest.cs" />
+ <Compile Include="Controllers\HttpControllerDescriptorTest.cs" />
+ <Compile Include="Controllers\ReflectedHttpActionDescriptorTest.cs" />
+ <Compile Include="Controllers\HttpActionContextTest.cs" />
+ <Compile Include="Filters\QueryCompositionFilterProviderTest.cs" />
+ <Compile Include="Filters\EnumerableEvaluatorFilterTest.cs" />
+ <Compile Include="Hosting\HttpRouteTest.cs" />
+ <Compile Include="Internal\TypeActivatorTest.cs" />
+ <Compile Include="ModelBinding\FormDataCollectionExtensionsTest.cs" />
+ <Compile Include="ModelBinding\ModelBinderAttributeTest.cs" />
+ <Compile Include="Query\DataModel.cs" />
+ <Compile Include="Query\ODataQueryDeserializerTests.cs" />
+ <Compile Include="Routing\HttpRouteTest.cs" />
+ <Compile Include="Routing\UrlHelperTest.cs" />
+ <Compile Include="Services\DependencyResolverTests.cs" />
+ <Compile Include="Tracing\FormattingUtilitiesTest.cs" />
+ <Compile Include="Tracing\HttpRequestMessageExtensionsTest.cs" />
+ <Compile Include="Tracing\ITraceWriterExtensionsTest.cs" />
+ <Compile Include="Tracing\TestTraceWriter.cs" />
+ <Compile Include="Tracing\TraceManagerTest.cs" />
+ <Compile Include="Tracing\TraceRecordComparer.cs" />
+ <Compile Include="Tracing\Tracers\HttpActionBindingTracerTest.cs" />
+ <Compile Include="Tracing\Tracers\HttpActionDescriptorTracerTest.cs" />
+ <Compile Include="Tracing\Tracers\ActionFilterAttributeTracerTest.cs" />
+ <Compile Include="Tracing\Tracers\ActionFilterTracerTest.cs" />
+ <Compile Include="Tracing\Tracers\HttpActionInvokerTracerTest.cs" />
+ <Compile Include="Tracing\Tracers\HttpActionSelectorTracerTest.cs" />
+ <Compile Include="Tracing\Tracers\ActionValueBinderTracerTest.cs" />
+ <Compile Include="Tracing\Tracers\HttpControllerTracerTest.cs" />
+ <Compile Include="Tracing\Tracers\AuthorizationFilterAttributeTracerTest.cs" />
+ <Compile Include="Tracing\Tracers\AuthorizationFilterTracerTest.cs" />
+ <Compile Include="Tracing\Tracers\ContentNegotiatorTracerTest.cs" />
+ <Compile Include="Tracing\Tracers\HttpControllerActivatorTracerTest.cs" />
+ <Compile Include="Tracing\Tracers\HttpControllerFactoryTracerTest.cs" />
+ <Compile Include="Tracing\Tracers\ExceptionFilterAttributeTracerTest.cs" />
+ <Compile Include="Tracing\Tracers\ExceptionFilterTracerTest.cs" />
+ <Compile Include="Tracing\Tracers\FilterTracerTest.cs" />
+ <Compile Include="Tracing\Tracers\FormatterParameterBindingTracerTest.cs" />
+ <Compile Include="Tracing\Tracers\HttpParameterBindingTracerTest.cs" />
+ <Compile Include="Tracing\Tracers\MediaTypeFormatterTracerTest.cs" />
+ <Compile Include="Tracing\Tracers\MessageHandlerTracerTest.cs" />
+ <Compile Include="Tracing\Tracers\RequestMessageHandlerTracerTest.cs" />
+ <Compile Include="Util\ContextUtil.cs" />
+ <Compile Include="Query\QueryValidatorTest.cs" />
+ <Compile Include="Filters\HttpActionExecutedContextTest.cs" />
+ <Compile Include="Filters\ActionFilterAttributeTest.cs" />
+ <Compile Include="Filters\ActionDescriptorFilterProviderTest.cs" />
+ <Compile Include="Filters\AuthorizationFilterAttributeTest.cs" />
+ <Compile Include="Filters\ExceptionFilterAttributeTest.cs" />
+ <Compile Include="Filters\FilterAttributeTest.cs" />
+ <Compile Include="Filters\FilterInfoComparerTest.cs" />
+ <Compile Include="Filters\FilterInfoTest.cs" />
+ <Compile Include="Filters\ConfigurationFilterProviderTest.cs" />
+ <Compile Include="ModelBinding\CompositeModelBinderTest.cs" />
+ <Compile Include="ModelBinding\DefaultActionValueBinderTest.cs" />
+ <Compile Include="Controllers\ApiControllerTest.cs" />
+ <Compile Include="Controllers\Apis\UsersController.cs" />
+ <Compile Include="HttpBindingBehaviorAttributeTest.cs" />
+ <Compile Include="Internal\CollectionModelBinderUtilTest.cs" />
+ <Compile Include="ModelBinding\Binders\ArrayModelBinderProviderTest.cs" />
+ <Compile Include="ModelBinding\Binders\ArrayModelBinderTest.cs" />
+ <Compile Include="ModelBinding\Binders\BinaryDataModelBinderProviderTest.cs">
+ <SubType>Code</SubType>
+ </Compile>
+ <Compile Include="ModelBinding\Binders\CollectionModelBinderProviderTest.cs">
+ <SubType>Code</SubType>
+ </Compile>
+ <Compile Include="ModelBinding\Binders\CollectionModelBinderTest.cs">
+ <SubType>Code</SubType>
+ </Compile>
+ <Compile Include="ModelBinding\Binders\ComplexModelDtoModelBinderProviderTest.cs">
+ <SubType>Code</SubType>
+ </Compile>
+ <Compile Include="ModelBinding\Binders\ComplexModelDtoModelBinderTest.cs">
+ <SubType>Code</SubType>
+ </Compile>
+ <Compile Include="ModelBinding\Binders\ComplexModelDtoResultTest.cs">
+ <SubType>Code</SubType>
+ </Compile>
+ <Compile Include="ModelBinding\Binders\ComplexModelDtoTest.cs">
+ <SubType>Code</SubType>
+ </Compile>
+ <Compile Include="ModelBinding\Binders\DictionaryModelBinderProviderTest.cs">
+ <SubType>Code</SubType>
+ </Compile>
+ <Compile Include="ModelBinding\Binders\DictionaryModelBinderTest.cs">
+ <SubType>Code</SubType>
+ </Compile>
+ <Compile Include="ModelBinding\Binders\GenericModelBinderProviderTest.cs">
+ <SubType>Code</SubType>
+ </Compile>
+ <Compile Include="ModelBinding\Binders\KeyValuePairModelBinderProviderTest.cs">
+ <SubType>Code</SubType>
+ </Compile>
+ <Compile Include="ModelBinding\Binders\KeyValuePairModelBinderTest.cs">
+ <SubType>Code</SubType>
+ </Compile>
+ <Compile Include="ModelBinding\Binders\KeyValuePairModelBinderUtilTest.cs">
+ <SubType>Code</SubType>
+ </Compile>
+ <Compile Include="ModelBinding\Binders\MutableObjectModelBinderProviderTest.cs">
+ <SubType>Code</SubType>
+ </Compile>
+ <Compile Include="ModelBinding\Binders\MutableObjectModelBinderTest.cs">
+ <SubType>Code</SubType>
+ </Compile>
+ <Compile Include="ModelBinding\Binders\SimpleModelBinderProviderTest.cs">
+ <SubType>Code</SubType>
+ </Compile>
+ <Compile Include="ModelBinding\Binders\TypeConverterModelBinderProviderTest.cs">
+ <SubType>Code</SubType>
+ </Compile>
+ <Compile Include="ModelBinding\Binders\TypeConverterModelBinderTest.cs">
+ <SubType>Code</SubType>
+ </Compile>
+ <Compile Include="ModelBinding\Binders\TypeMatchModelBinderProviderTest.cs">
+ <SubType>Code</SubType>
+ </Compile>
+ <Compile Include="ModelBinding\Binders\TypeMatchModelBinderTest.cs">
+ <SubType>Code</SubType>
+ </Compile>
+ <Compile Include="ModelBinding\ModelBinderConfigTest.cs" />
+ <Compile Include="ModelBinding\ModelBindingUtilTest.cs" />
+ <Compile Include="ModelBinding\ModelBindingContextTest.cs" />
+ <Compile Include="Properties\AssemblyInfo.cs" />
+ <Compile Include="Util\SimpleHttpValueProvider.cs" />
+ <Compile Include="Validation\DefaultBodyModelValidatorTest.cs" />
+ <Compile Include="Validation\ModelValidationNodeTest.cs" />
+ <Compile Include="Validation\ModelValidationRequiredMemberSelectorTest.cs" />
+ <Compile Include="ValueProviders\Providers\KeyValueModelValueProviderTest.cs" />
+ <Compile Include="ValueProviders\Providers\NameValueCollectionValueProviderTest.cs" />
+ <Compile Include="ValueProviders\Providers\QueryStringValueProviderTest.cs" />
+ </ItemGroup>
+ <ItemGroup>
+ <None Include="packages.config" />
+ </ItemGroup>
+ <ItemGroup>
+ <ProjectReference Include="..\..\src\System.Json\System.Json.csproj">
+ <Project>{F0441BE9-BDC0-4629-BE5A-8765FFAA2481}</Project>
+ <Name>System.Json</Name>
+ </ProjectReference>
+ <ProjectReference Include="..\..\src\System.Net.Http.Formatting\System.Net.Http.Formatting.csproj">
+ <Project>{668E9021-CE84-49D9-98FB-DF125A9FCDB0}</Project>
+ <Name>System.Net.Http.Formatting</Name>
+ </ProjectReference>
+ <ProjectReference Include="..\..\src\System.Web.Http.Common\System.Web.Http.Common.csproj">
+ <Project>{03A5E5F2-2E23-48F2-ABCC-6C41BAC9AC02}</Project>
+ <Name>System.Web.Http.Common</Name>
+ </ProjectReference>
+ <ProjectReference Include="..\..\src\System.Web.Http\System.Web.Http.csproj">
+ <Project>{DDC1CE0C-486E-4E35-BB3B-EAB61F8F9440}</Project>
+ <Name>System.Web.Http</Name>
+ </ProjectReference>
+ <ProjectReference Include="..\Microsoft.TestCommon\Microsoft.TestCommon.csproj">
+ <Project>{FCCC4CB7-BAF7-4A57-9F89-E5766FE536C0}</Project>
+ <Name>Microsoft.TestCommon</Name>
+ </ProjectReference>
+ </ItemGroup>
+ <ItemGroup>
+ <Folder Include="Controllers\Helpers\" />
+ </ItemGroup>
+ <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
+</Project> \ No newline at end of file
diff --git a/test/System.Web.Http.Test/Tracing/FormattingUtilitiesTest.cs b/test/System.Web.Http.Test/Tracing/FormattingUtilitiesTest.cs
new file mode 100644
index 00000000..7617959f
--- /dev/null
+++ b/test/System.Web.Http.Test/Tracing/FormattingUtilitiesTest.cs
@@ -0,0 +1,283 @@
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Globalization;
+using System.Linq;
+using System.Net.Http.Formatting;
+using System.Web.Http.Controllers;
+using System.Web.Http.ModelBinding;
+using System.Web.Http.ModelBinding.Binders;
+using System.Web.Http.Routing;
+using System.Web.Http.ValueProviders;
+using System.Web.Http.ValueProviders.Providers;
+using Microsoft.TestCommon;
+using Moq;
+using Xunit;
+using Xunit.Extensions;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Http.Tracing
+{
+ public class FormattingUtilitiesTest
+ {
+ [Theory]
+ [TestDataSet(typeof(CommonUnitTestDataSets), "RefTypeTestDataCollection")]
+ public void ValueToString_Formats(Type variationType, object testData)
+ {
+ // Arrange
+ string expected = Convert.ToString(testData, CultureInfo.CurrentCulture);
+
+ // Act
+ string actual = FormattingUtilities.ValueToString(testData, CultureInfo.CurrentCulture);
+
+ // Assert
+ Assert.Equal(expected, actual);
+ }
+
+ [Fact]
+ public void ValueToString_Formats_Null_Value()
+ {
+ // Arrange & Act
+ string actual = FormattingUtilities.ValueToString(null, CultureInfo.CurrentCulture);
+
+ // Assert
+ Assert.Equal("null", actual);
+ }
+
+ [Fact]
+ public void ActionArgumentsToString_Formats()
+ {
+ // Arrange
+ Dictionary<string, object> arguments = new Dictionary<string, object>()
+ {
+ {"p1", 1},
+ {"p2", true}
+ };
+
+ string expected = String.Format("p1={0}, p2={1}",
+ FormattingUtilities.ValueToString(arguments["p1"], CultureInfo.CurrentCulture),
+ FormattingUtilities.ValueToString(arguments["p2"], CultureInfo.CurrentCulture));
+
+ // Act
+ string actual = FormattingUtilities.ActionArgumentsToString(arguments);
+
+ // Assert
+ Assert.Equal(expected, actual);
+ }
+
+ [Fact]
+ public void ActionDescriptorToString_Formats()
+ {
+ // Arrange
+ Mock<HttpParameterDescriptor> paramDescriptor1 = new Mock<HttpParameterDescriptor>() { CallBase = true };
+ paramDescriptor1.Setup(p => p.ParameterName).Returns("p1");
+ paramDescriptor1.Setup(p => p.ParameterType).Returns(typeof(int));
+ Mock<HttpParameterDescriptor> paramDescriptor2 = new Mock<HttpParameterDescriptor>() { CallBase = true };
+ paramDescriptor2.Setup(p => p.ParameterName).Returns("p2");
+ paramDescriptor2.Setup(p => p.ParameterType).Returns(typeof(bool));
+
+ Collection<HttpParameterDescriptor> parameterCollection = new Collection<HttpParameterDescriptor>(
+ new HttpParameterDescriptor[] { paramDescriptor1.Object, paramDescriptor2.Object });
+ Mock<HttpActionDescriptor> mockActionDescriptor = new Mock<HttpActionDescriptor>() { CallBase = true };
+ mockActionDescriptor.Setup(a => a.GetParameters()).Returns(parameterCollection);
+ mockActionDescriptor.Setup(a => a.ActionName).Returns("SampleAction");
+
+ string expected = String.Format("SampleAction({0} p1, {1} p2)", typeof(int).Name, typeof(bool).Name);
+
+ // Act
+ string actual = FormattingUtilities.ActionDescriptorToString(mockActionDescriptor.Object);
+
+ // Assert
+ Assert.Equal(expected, actual);
+ }
+
+ [Fact]
+ public void ActionInvokeToString_Formats()
+ {
+ // Arrange
+ Dictionary<string, object> arguments = new Dictionary<string, object>()
+ {
+ {"p1", 1},
+ {"p2", true}
+ };
+
+ string expected = String.Format("SampleAction(p1={0}, p2={1})",
+ FormattingUtilities.ValueToString(arguments["p1"], CultureInfo.CurrentCulture),
+ FormattingUtilities.ValueToString(arguments["p2"], CultureInfo.CurrentCulture));
+
+ // Act
+ string actual = FormattingUtilities.ActionInvokeToString("SampleAction", arguments);
+
+ // Assert
+ Assert.Equal(expected, actual);
+ }
+
+ [Fact]
+ public void ActionInvokeToString_With_ActionContext_Formats()
+ {
+ // Arrange
+ Mock<HttpParameterDescriptor> paramDescriptor1 = new Mock<HttpParameterDescriptor>() { CallBase = true };
+ paramDescriptor1.Setup(p => p.ParameterName).Returns("p1");
+ paramDescriptor1.Setup(p => p.ParameterType).Returns(typeof(int));
+ Mock<HttpParameterDescriptor> paramDescriptor2 = new Mock<HttpParameterDescriptor>() { CallBase = true };
+ paramDescriptor2.Setup(p => p.ParameterName).Returns("p2");
+ paramDescriptor2.Setup(p => p.ParameterType).Returns(typeof(bool));
+
+ Collection<HttpParameterDescriptor> parameterCollection = new Collection<HttpParameterDescriptor>(
+ new HttpParameterDescriptor[] { paramDescriptor1.Object, paramDescriptor2.Object });
+ Mock<HttpActionDescriptor> mockActionDescriptor = new Mock<HttpActionDescriptor>() { CallBase = true };
+ mockActionDescriptor.Setup(a => a.GetParameters()).Returns(parameterCollection);
+ mockActionDescriptor.Setup(a => a.ActionName).Returns("SampleAction");
+
+ HttpActionContext actionContext =
+ ContextUtil.CreateActionContext(actionDescriptor: mockActionDescriptor.Object);
+ actionContext.ActionArguments["p1"] = 1;
+ actionContext.ActionArguments["p2"] = true;
+
+ string expected = String.Format("SampleAction(p1={0}, p2={1})",
+ FormattingUtilities.ValueToString(actionContext.ActionArguments["p1"], CultureInfo.CurrentCulture),
+ FormattingUtilities.ValueToString(actionContext.ActionArguments["p2"], CultureInfo.CurrentCulture));
+
+ // Act
+ string actual = FormattingUtilities.ActionInvokeToString(actionContext);
+
+ // Assert
+ Assert.Equal(expected, actual);
+ }
+
+ [Fact]
+ public void FormattersToString_Formats()
+ {
+ // Arrange
+ MediaTypeFormatterCollection formatters = new MediaTypeFormatterCollection();
+ string expected = String.Join(", ", formatters.Select<MediaTypeFormatter, string>((f) => f.GetType().Name));
+
+ // Act
+ string actual = FormattingUtilities.FormattersToString(formatters);
+
+ // Assert
+ Assert.Equal(expected, actual);
+ }
+
+ [Fact]
+ public void ModelBinderToString_Formats()
+ {
+ // Arrange
+ ModelBinderProvider provider = new SimpleModelBinderProvider(typeof (int), () => null);
+ string expected = typeof (SimpleModelBinderProvider).Name;
+
+ // Act
+ string actual = FormattingUtilities.ModelBinderToString(provider);
+
+ // Assert
+ Assert.Equal(expected, actual);
+ }
+
+ [Fact]
+ public void ModelBinderToString_With_CompositeModelBinder_Formats()
+ {
+ // Arrange
+ ModelBinderProvider innerProvider1 = new SimpleModelBinderProvider(typeof(int), () => null);
+ ModelBinderProvider innerProvider2 = new ArrayModelBinderProvider();
+ CompositeModelBinderProvider compositeProvider = new CompositeModelBinderProvider(new ModelBinderProvider[] { innerProvider1, innerProvider2 });
+ string expected = String.Format(
+ "{0}({1}, {2})",
+ typeof(CompositeModelBinderProvider).Name,
+ typeof(SimpleModelBinderProvider).Name,
+ typeof(ArrayModelBinderProvider).Name);
+
+ // Act
+ string actual = FormattingUtilities.ModelBinderToString(compositeProvider);
+
+ // Assert
+ Assert.Equal(expected, actual);
+ }
+
+ [Fact]
+ public void ValueProviderToString_Formats()
+ {
+ // Arrange
+ IValueProvider provider = new ElementalValueProvider("unused", 1, CultureInfo.CurrentCulture);
+ string expected = typeof(ElementalValueProvider).Name;
+
+ // Act
+ string actual = FormattingUtilities.ValueProviderToString(provider);
+
+ // Assert
+ Assert.Equal(expected, actual);
+ }
+
+ [Fact]
+ public void ValueProviderToString_With_CompositeProvider_Formats()
+ {
+ // Arrange
+ List<IValueProvider> providers = new List<IValueProvider>()
+ {
+ new ElementalValueProvider("unused", 1, CultureInfo.CurrentCulture),
+ new NameValueCollectionValueProvider(() => null, CultureInfo.CurrentCulture)
+ };
+
+ CompositeValueProvider compositeProvider = new CompositeValueProvider(providers);
+ string expected = String.Format(
+ "{0}({1}, {2})",
+ typeof(CompositeValueProvider).Name,
+ typeof(ElementalValueProvider).Name,
+ typeof(NameValueCollectionValueProvider).Name);
+
+ // Act
+ string actual = FormattingUtilities.ValueProviderToString(compositeProvider);
+
+ // Assert
+ Assert.Equal(expected, actual);
+ }
+
+ [Fact]
+ public void RouteToString_Formats()
+ {
+ // Arrange
+ Dictionary<string, object> routeDictionary = new Dictionary<string, object>()
+ {
+ {"r1", "c1"},
+ {"r2", "c2"}
+ };
+ Mock<IHttpRouteData> mockRouteData = new Mock<IHttpRouteData>() { CallBase = true};
+ mockRouteData.Setup(r => r.Values).Returns(routeDictionary);
+ string expected = "r1:c1,r2:c2";
+
+ // Act
+ string actual = FormattingUtilities.RouteToString(mockRouteData.Object);
+
+ // Assert
+ Assert.Equal(expected, actual);
+ }
+
+ [Fact]
+ public void ModelStateToString_Formats_With_Valid_ModelState()
+ {
+ // Arrange
+ ModelStateDictionary modelState = new ModelStateDictionary();
+ string expected = String.Empty;
+
+ // Act
+ string actual = FormattingUtilities.ModelStateToString(modelState);
+
+ // Assert
+ Assert.Equal(expected, actual);
+ }
+
+ [Fact]
+ public void ModelStateToString_Formats_With_InValid_ModelState()
+ {
+ // Arrange
+ ModelStateDictionary modelState = new ModelStateDictionary();
+ modelState.AddModelError("p1", "is bad");
+
+ string expected = "p1: is bad";
+
+ // Act
+ string actual = FormattingUtilities.ModelStateToString(modelState);
+
+ // Assert
+ Assert.Equal(expected, actual);
+ }
+ }
+}
diff --git a/test/System.Web.Http.Test/Tracing/HttpRequestMessageExtensionsTest.cs b/test/System.Web.Http.Test/Tracing/HttpRequestMessageExtensionsTest.cs
new file mode 100644
index 00000000..8da38a7f
--- /dev/null
+++ b/test/System.Web.Http.Test/Tracing/HttpRequestMessageExtensionsTest.cs
@@ -0,0 +1,24 @@
+using System.Net.Http;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Http.Tracing
+{
+ public class HttpRequestMessageExtensionsTest
+ {
+ [Fact]
+ public void GetCorrelationId_Returns_Valid_Guid()
+ {
+ // Arrange
+ HttpRequestMessage request = new HttpRequestMessage();
+
+ // Act
+ Guid guid1 = request.GetCorrelationId();
+ Guid guid2 = request.GetCorrelationId();
+
+ // Assert
+ Assert.Equal(guid1, guid2);
+ Assert.NotEqual(guid1, Guid.Empty);
+ }
+ }
+}
diff --git a/test/System.Web.Http.Test/Tracing/ITraceWriterExtensionsTest.cs b/test/System.Web.Http.Test/Tracing/ITraceWriterExtensionsTest.cs
new file mode 100644
index 00000000..facca2cd
--- /dev/null
+++ b/test/System.Web.Http.Test/Tracing/ITraceWriterExtensionsTest.cs
@@ -0,0 +1,1004 @@
+using System.Net.Http;
+using System.Threading;
+using System.Threading.Tasks;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Http.Tracing
+{
+ public class ITraceWriterExtensionsTest
+ {
+ [Fact]
+ public void Debug_With_Message_Traces()
+ {
+ // Arrange
+ TestTraceWriter traceWriter = new TestTraceWriter();
+ HttpRequestMessage request = new HttpRequestMessage();
+ TraceRecord[] expectedTraces = new TraceRecord[]
+ {
+ new TraceRecord(request, "testCategory", TraceLevel.Debug) { Kind = TraceKind.Trace, Message = "The formatted message" },
+ };
+
+ // Act
+ traceWriter.Debug(request, "testCategory", "The {0} message", "formatted");
+
+ // Assert
+ Assert.Equal<TraceRecord>(expectedTraces, traceWriter.Traces, new TraceRecordComparer());
+ }
+
+ [Fact]
+ public void Debug_With_Exception_Traces()
+ {
+ // Arrange
+ TestTraceWriter traceWriter = new TestTraceWriter();
+ HttpRequestMessage request = new HttpRequestMessage();
+ InvalidOperationException exception = new InvalidOperationException();
+ TraceRecord[] expectedTraces = new TraceRecord[]
+ {
+ new TraceRecord(request, "testCategory", TraceLevel.Debug) { Kind = TraceKind.Trace, Exception = exception },
+ };
+
+ // Act
+ traceWriter.Debug(request, "testCategory", exception);
+
+ // Assert
+ Assert.Equal<TraceRecord>(expectedTraces, traceWriter.Traces, new TraceRecordComparer());
+ }
+
+ [Fact]
+ public void Debug_With_Message_And_Exception_Traces()
+ {
+ // Arrange
+ TestTraceWriter traceWriter = new TestTraceWriter();
+ HttpRequestMessage request = new HttpRequestMessage();
+ InvalidOperationException exception = new InvalidOperationException();
+ TraceRecord[] expectedTraces = new TraceRecord[]
+ {
+ new TraceRecord(request, "testCategory", TraceLevel.Debug) { Kind = TraceKind.Trace, Message = "The formatted message", Exception = exception },
+ };
+
+ // Act
+ traceWriter.Debug(request, "testCategory", exception, "The {0} message", "formatted");
+
+ // Assert
+ Assert.Equal<TraceRecord>(expectedTraces, traceWriter.Traces, new TraceRecordComparer());
+ }
+
+ [Fact]
+ public void Info_With_Message_Traces()
+ {
+ // Arrange
+ TestTraceWriter traceWriter = new TestTraceWriter();
+ HttpRequestMessage request = new HttpRequestMessage();
+ TraceRecord[] expectedTraces = new TraceRecord[]
+ {
+ new TraceRecord(request, "testCategory", TraceLevel.Info) { Kind = TraceKind.Trace, Message = "The formatted message" },
+ };
+
+ // Act
+ traceWriter.Info(request, "testCategory", "The {0} message", "formatted");
+
+ // Assert
+ Assert.Equal<TraceRecord>(expectedTraces, traceWriter.Traces, new TraceRecordComparer());
+ }
+
+ [Fact]
+ public void Info_With_Exception_Traces()
+ {
+ // Arrange
+ TestTraceWriter traceWriter = new TestTraceWriter();
+ HttpRequestMessage request = new HttpRequestMessage();
+ InvalidOperationException exception = new InvalidOperationException();
+ TraceRecord[] expectedTraces = new TraceRecord[]
+ {
+ new TraceRecord(request, "testCategory", TraceLevel.Info) { Kind = TraceKind.Trace, Exception = exception },
+ };
+
+ // Act
+ traceWriter.Info(request, "testCategory", exception);
+
+ // Assert
+ Assert.Equal<TraceRecord>(expectedTraces, traceWriter.Traces, new TraceRecordComparer());
+ }
+
+ [Fact]
+ public void Info_With_Message_And_Exception_Traces()
+ {
+ // Arrange
+ TestTraceWriter traceWriter = new TestTraceWriter();
+ HttpRequestMessage request = new HttpRequestMessage();
+ InvalidOperationException exception = new InvalidOperationException();
+ TraceRecord[] expectedTraces = new TraceRecord[]
+ {
+ new TraceRecord(request, "testCategory", TraceLevel.Info) { Kind = TraceKind.Trace, Message = "The formatted message", Exception = exception },
+ };
+
+ // Act
+ traceWriter.Info(request, "testCategory", exception, "The {0} message", "formatted");
+
+ // Assert
+ Assert.Equal<TraceRecord>(expectedTraces, traceWriter.Traces, new TraceRecordComparer());
+ }
+
+ [Fact]
+ public void Warn_With_Message_Traces()
+ {
+ // Arrange
+ TestTraceWriter traceWriter = new TestTraceWriter();
+ HttpRequestMessage request = new HttpRequestMessage();
+ TraceRecord[] expectedTraces = new TraceRecord[]
+ {
+ new TraceRecord(request, "testCategory", TraceLevel.Warn) { Kind = TraceKind.Trace, Message = "The formatted message" },
+ };
+
+ // Act
+ traceWriter.Warn(request, "testCategory", "The {0} message", "formatted");
+
+ // Assert
+ Assert.Equal<TraceRecord>(expectedTraces, traceWriter.Traces, new TraceRecordComparer());
+ }
+
+ [Fact]
+ public void Warn_With_Exception_Traces()
+ {
+ // Arrange
+ TestTraceWriter traceWriter = new TestTraceWriter();
+ HttpRequestMessage request = new HttpRequestMessage();
+ InvalidOperationException exception = new InvalidOperationException();
+ TraceRecord[] expectedTraces = new TraceRecord[]
+ {
+ new TraceRecord(request, "testCategory", TraceLevel.Warn) { Kind = TraceKind.Trace, Exception = exception },
+ };
+
+ // Act
+ traceWriter.Warn(request, "testCategory", exception);
+
+ // Assert
+ Assert.Equal<TraceRecord>(expectedTraces, traceWriter.Traces, new TraceRecordComparer());
+ }
+
+ [Fact]
+ public void Warn_With_Message_And_Exception_Traces()
+ {
+ // Arrange
+ TestTraceWriter traceWriter = new TestTraceWriter();
+ HttpRequestMessage request = new HttpRequestMessage();
+ InvalidOperationException exception = new InvalidOperationException();
+ TraceRecord[] expectedTraces = new TraceRecord[]
+ {
+ new TraceRecord(request, "testCategory", TraceLevel.Warn) { Kind = TraceKind.Trace, Message = "The formatted message", Exception = exception },
+ };
+
+ // Act
+ traceWriter.Warn(request, "testCategory", exception, "The {0} message", "formatted");
+
+ // Assert
+ Assert.Equal<TraceRecord>(expectedTraces, traceWriter.Traces, new TraceRecordComparer());
+ }
+
+ [Fact]
+ public void Error_With_Message_Traces()
+ {
+ // Arrange
+ TestTraceWriter traceWriter = new TestTraceWriter();
+ HttpRequestMessage request = new HttpRequestMessage();
+ TraceRecord[] expectedTraces = new TraceRecord[]
+ {
+ new TraceRecord(request, "testCategory", TraceLevel.Error) { Kind = TraceKind.Trace, Message = "The formatted message" },
+ };
+
+ // Act
+ traceWriter.Error(request, "testCategory", "The {0} message", "formatted");
+
+ // Assert
+ Assert.Equal<TraceRecord>(expectedTraces, traceWriter.Traces, new TraceRecordComparer());
+ }
+
+ [Fact]
+ public void Error_With_Exception_Traces()
+ {
+ // Arrange
+ TestTraceWriter traceWriter = new TestTraceWriter();
+ HttpRequestMessage request = new HttpRequestMessage();
+ InvalidOperationException exception = new InvalidOperationException();
+ TraceRecord[] expectedTraces = new TraceRecord[]
+ {
+ new TraceRecord(request, "testCategory", TraceLevel.Error) { Kind = TraceKind.Trace, Exception = exception },
+ };
+
+ // Act
+ traceWriter.Error(request, "testCategory", exception);
+
+ // Assert
+ Assert.Equal<TraceRecord>(expectedTraces, traceWriter.Traces, new TraceRecordComparer());
+ }
+
+ [Fact]
+ public void Error_With_Message_And_Exception_Traces()
+ {
+ // Arrange
+ TestTraceWriter traceWriter = new TestTraceWriter();
+ HttpRequestMessage request = new HttpRequestMessage();
+ InvalidOperationException exception = new InvalidOperationException();
+ TraceRecord[] expectedTraces = new TraceRecord[]
+ {
+ new TraceRecord(request, "testCategory", TraceLevel.Error) { Kind = TraceKind.Trace, Message = "The formatted message", Exception = exception },
+ };
+
+ // Act
+ traceWriter.Error(request, "testCategory", exception, "The {0} message", "formatted");
+
+ // Assert
+ Assert.Equal<TraceRecord>(expectedTraces, traceWriter.Traces, new TraceRecordComparer());
+ }
+
+ [Fact]
+ public void Fatal_With_Message_Traces()
+ {
+ // Arrange
+ TestTraceWriter traceWriter = new TestTraceWriter();
+ HttpRequestMessage request = new HttpRequestMessage();
+ TraceRecord[] expectedTraces = new TraceRecord[]
+ {
+ new TraceRecord(request, "testCategory", TraceLevel.Fatal) { Kind = TraceKind.Trace, Message = "The formatted message" },
+ };
+
+ // Act
+ traceWriter.Fatal(request, "testCategory", "The {0} message", "formatted");
+
+ // Assert
+ Assert.Equal<TraceRecord>(expectedTraces, traceWriter.Traces, new TraceRecordComparer());
+ }
+
+ [Fact]
+ public void Fatal_With_Exception_Traces()
+ {
+ // Arrange
+ TestTraceWriter traceWriter = new TestTraceWriter();
+ HttpRequestMessage request = new HttpRequestMessage();
+ InvalidOperationException exception = new InvalidOperationException();
+ TraceRecord[] expectedTraces = new TraceRecord[]
+ {
+ new TraceRecord(request, "testCategory", TraceLevel.Fatal) { Kind = TraceKind.Trace, Exception = exception },
+ };
+
+ // Act
+ traceWriter.Fatal(request, "testCategory", exception);
+
+ // Assert
+ Assert.Equal<TraceRecord>(expectedTraces, traceWriter.Traces, new TraceRecordComparer());
+ }
+
+ [Fact]
+ public void Fatal_With_Message_And_Exception_Traces()
+ {
+ // Arrange
+ TestTraceWriter traceWriter = new TestTraceWriter();
+ HttpRequestMessage request = new HttpRequestMessage();
+ InvalidOperationException exception = new InvalidOperationException();
+ TraceRecord[] expectedTraces = new TraceRecord[]
+ {
+ new TraceRecord(request, "testCategory", TraceLevel.Fatal) { Kind = TraceKind.Trace, Message = "The formatted message", Exception = exception },
+ };
+
+ // Act
+ traceWriter.Fatal(request, "testCategory", exception, "The {0} message", "formatted");
+
+ // Assert
+ Assert.Equal<TraceRecord>(expectedTraces, traceWriter.Traces, new TraceRecordComparer());
+ }
+
+ [Fact]
+ public void TraceBeginEnd_Throws_With_Null_This()
+ {
+ // Arrange
+ TestTraceWriter traceWriter = null;
+ HttpRequestMessage request = new HttpRequestMessage();
+
+ // Act & Assert
+ Assert.ThrowsArgumentNull(() => traceWriter.TraceBeginEnd(request,
+ "",
+ TraceLevel.Off,
+ "",
+ "",
+ beginTrace: null,
+ execute: () => { },
+ endTrace: null,
+ errorTrace: null),
+ "traceWriter");
+ }
+
+ [Fact]
+ public void TraceBeginEnd_Throws_With_Null_Execute_Action()
+ {
+ // Arrange
+ TestTraceWriter traceWriter = new TestTraceWriter();
+ HttpRequestMessage request = new HttpRequestMessage();
+
+ // Act & Assert
+ Assert.ThrowsArgumentNull(() => traceWriter.TraceBeginEnd(request,
+ "",
+ TraceLevel.Off,
+ "",
+ "",
+ beginTrace: null,
+ execute: null,
+ endTrace: null,
+ errorTrace: null),
+ "execute");
+ }
+
+ [Fact]
+ public void TraceBeginEnd_Accepts_Null_Trace_Actions()
+ {
+ // Arrange
+ TestTraceWriter traceWriter = new TestTraceWriter();
+ HttpRequestMessage request = new HttpRequestMessage();
+
+ // Act & Assert
+ traceWriter.TraceBeginEnd(request,
+ "",
+ TraceLevel.Off,
+ "",
+ "",
+ beginTrace: null,
+ execute: () => { },
+ endTrace: null,
+ errorTrace: null);
+ }
+
+ [Fact]
+ public void TraceBeginEnd_Invokes_BeginTrace()
+ {
+ // Arrange
+ TestTraceWriter traceWriter = new TestTraceWriter();
+ HttpRequestMessage request = new HttpRequestMessage();
+ bool invoked = false;
+
+ // Act
+ traceWriter.TraceBeginEnd(request,
+ "",
+ TraceLevel.Fatal,
+ "",
+ "",
+ beginTrace: (tr) => { invoked = true; },
+ execute: () => { },
+ endTrace: (tr) => { },
+ errorTrace: (tr) => { });
+
+ // Assert
+ Assert.True(invoked);
+ }
+
+ [Fact]
+ public void TraceBeginEnd_Invokes_Execute()
+ {
+ // Arrange
+ TestTraceWriter traceWriter = new TestTraceWriter();
+ HttpRequestMessage request = new HttpRequestMessage();
+ bool invoked = false;
+
+ // Act
+ traceWriter.TraceBeginEnd(request,
+ "",
+ TraceLevel.Fatal,
+ "",
+ "",
+ beginTrace: (tr) => { },
+ execute: () => { invoked = true; },
+ endTrace: (tr) => { },
+ errorTrace: (tr) => { });
+
+ // Assert
+ Assert.True(invoked);
+ }
+
+
+ [Fact]
+ public void TraceBeginEnd_Invokes_EndTrace()
+ {
+ // Arrange
+ TestTraceWriter traceWriter = new TestTraceWriter();
+ HttpRequestMessage request = new HttpRequestMessage();
+ bool invoked = false;
+
+ // Act
+ traceWriter.TraceBeginEnd(request,
+ "",
+ TraceLevel.Off,
+ "",
+ "",
+ beginTrace: (tr) => { },
+ execute: () => { },
+ endTrace: (tr) => { invoked = true; },
+ errorTrace: (tr) => { });
+
+ // Assert
+ Assert.True(invoked);
+ }
+
+ [Fact]
+ public void TraceBeginEnd_Invokes_ErrorTrace()
+ {
+ // Arrange
+ TestTraceWriter traceWriter = new TestTraceWriter();
+ HttpRequestMessage request = new HttpRequestMessage();
+ Exception exception = new InvalidOperationException();
+ bool invoked = false;
+
+ // Act
+ Exception thrown = Assert.Throws<InvalidOperationException>(
+ () => traceWriter.TraceBeginEnd(request,
+ "",
+ TraceLevel.Off,
+ "",
+ "",
+ beginTrace: (tr) => { },
+ execute: () => { throw exception; },
+ endTrace: (tr) => { },
+ errorTrace: (tr) => { invoked = true; }));
+
+ // Assert
+ Assert.True(invoked);
+ Assert.Same(exception, thrown);
+ }
+
+ [Fact]
+ public void TraceBeginEnd_Traces()
+ {
+ // Arrange
+ TestTraceWriter traceWriter = new TestTraceWriter();
+ HttpRequestMessage request = new HttpRequestMessage();
+ TraceRecord[] expectedTraces = new TraceRecord[]
+ {
+ new TraceRecord(request, "testCategory", TraceLevel.Info) { Kind = TraceKind.Begin, Operator = "tester", Operation = "testOp", Message = "beginMessage" },
+ new TraceRecord(request, "testCategory", TraceLevel.Info) { Kind = TraceKind.End, Operator = "tester", Operation = "testOp", Message = "endMessage" },
+ };
+
+ // Act
+ traceWriter.TraceBeginEnd(request,
+ "testCategory",
+ TraceLevel.Info,
+ "tester",
+ "testOp",
+ beginTrace: (tr) => { tr.Message = "beginMessage"; },
+ execute: () => { },
+ endTrace: (tr) => { tr.Message = "endMessage"; },
+ errorTrace: (tr) => { tr.Message = "won't happen"; });
+
+ // Assert
+ Assert.Equal<TraceRecord>(expectedTraces, traceWriter.Traces, new TraceRecordComparer());
+ }
+
+ [Fact]
+ public void TraceBeginEnd_Traces_And_Throws_When_Execute_Throws()
+ {
+ // Arrange
+ TestTraceWriter traceWriter = new TestTraceWriter();
+ HttpRequestMessage request = new HttpRequestMessage();
+ InvalidOperationException exception = new InvalidOperationException("test exception");
+ TraceRecord[] expectedTraces = new TraceRecord[]
+ {
+ new TraceRecord(request, "testCategory", TraceLevel.Info) { Kind = TraceKind.Begin, Operator = "tester", Operation = "testOp", Message = "beginMessage" },
+ new TraceRecord(request, "testCategory", TraceLevel.Error) { Kind = TraceKind.End, Operator = "tester", Operation = "testOp", Exception = exception, Message = "errorMessage" },
+ };
+
+ // Act
+ Exception thrown = Assert.Throws<InvalidOperationException>(
+ () => traceWriter.TraceBeginEnd(request,
+ "testCategory",
+ TraceLevel.Info,
+ "tester",
+ "testOp",
+ beginTrace: (tr) => { tr.Message = "beginMessage"; },
+ execute: () => { throw exception; },
+ endTrace: (tr) => { tr.Message = "won't happen"; },
+ errorTrace: (tr) => { tr.Message = "errorMessage"; }));
+
+ // Assert
+ Assert.Equal<TraceRecord>(expectedTraces, traceWriter.Traces, new TraceRecordComparer());
+ Assert.Same(exception, thrown);
+ }
+
+ [Fact]
+ public void TraceBeginEndAsync_Throws_With_Null_This()
+ {
+ // Arrange
+ TestTraceWriter traceWriter = null;
+ HttpRequestMessage request = new HttpRequestMessage();
+
+ // Act & Assert
+ Assert.ThrowsArgumentNull(() => traceWriter.TraceBeginEndAsync(request,
+ "",
+ TraceLevel.Off,
+ "",
+ "",
+ beginTrace: null,
+ execute: () => TaskHelpers.Completed(),
+ endTrace: null,
+ errorTrace: null),
+ "traceWriter");
+ }
+
+ [Fact]
+ public void TraceBeginEndAsync_Throws_With_Null_Execute_Action()
+ {
+ // Arrange
+ TestTraceWriter traceWriter = new TestTraceWriter();
+ HttpRequestMessage request = new HttpRequestMessage();
+
+ // Act & Assert
+ Assert.ThrowsArgumentNull(() => traceWriter.TraceBeginEndAsync(request,
+ "",
+ TraceLevel.Off,
+ "",
+ "",
+ beginTrace: null,
+ execute: null,
+ endTrace: null,
+ errorTrace: null),
+ "execute");
+ }
+
+ [Fact]
+ public void TraceBeginEndAsync_Accepts_Null_Trace_Actions()
+ {
+ // Arrange
+ TestTraceWriter traceWriter = new TestTraceWriter();
+ HttpRequestMessage request = new HttpRequestMessage();
+
+ // Act & Assert
+ Task t = traceWriter.TraceBeginEndAsync(request,
+ "",
+ TraceLevel.Off,
+ "",
+ "",
+ beginTrace: null,
+ execute: () => TaskHelpers.Completed(),
+ endTrace: null,
+ errorTrace: null);
+ t.Wait();
+ }
+
+ [Fact]
+ public void TraceBeginEndAsync_Invokes_BeginTrace()
+ {
+ // Arrange
+ TestTraceWriter traceWriter = new TestTraceWriter();
+ HttpRequestMessage request = new HttpRequestMessage();
+ bool invoked = false;
+
+ // Act
+ traceWriter.TraceBeginEndAsync(request,
+ "",
+ TraceLevel.Fatal,
+ "",
+ "",
+ beginTrace: (tr) => { invoked = true; },
+ execute: () => TaskHelpers.Completed(),
+ endTrace: (tr) => { },
+ errorTrace: (tr) => { }).Wait();
+
+ // Assert
+ Assert.True(invoked);
+ }
+
+ [Fact]
+ public void TraceBeginEndAsync_Invokes_Execute()
+ {
+ // Arrange
+ TestTraceWriter traceWriter = new TestTraceWriter();
+ HttpRequestMessage request = new HttpRequestMessage();
+ bool invoked = false;
+
+ // Act
+ traceWriter.TraceBeginEndAsync(request,
+ "",
+ TraceLevel.Fatal,
+ "",
+ "",
+ beginTrace: (tr) => { },
+ execute: () =>
+ {
+ invoked = true;
+ return TaskHelpers.Completed();
+ },
+ endTrace: (tr) => { },
+ errorTrace: (tr) => { }).Wait();
+
+ // Assert
+ Assert.True(invoked);
+ }
+
+ [Fact]
+ public void TraceBeginEndAsync_Invokes_EndTrace()
+ {
+ // Arrange
+ TestTraceWriter traceWriter = new TestTraceWriter();
+ HttpRequestMessage request = new HttpRequestMessage();
+ bool invoked = false;
+
+ // Act
+ traceWriter.TraceBeginEndAsync(request,
+ "",
+ TraceLevel.Off,
+ "",
+ "",
+ beginTrace: (tr) => { },
+ execute: () => TaskHelpers.Completed(),
+ endTrace: (tr) => { invoked = true; },
+ errorTrace: (tr) => { }).Wait();
+
+ // Assert
+ Assert.True(invoked);
+ }
+
+ [Fact]
+ public void TraceBeginEndAsync_Invokes_ErrorTrace()
+ {
+ // Arrange
+ TestTraceWriter traceWriter = new TestTraceWriter();
+ HttpRequestMessage request = new HttpRequestMessage();
+ bool invoked = false;
+ Exception exception = new InvalidOperationException();
+ TaskCompletionSource<object> tcs = new TaskCompletionSource<object>(null);
+ tcs.TrySetException(exception);
+
+ // Act
+ Exception thrown = Assert.Throws<InvalidOperationException>(
+ () => traceWriter.TraceBeginEndAsync(request,
+ "",
+ TraceLevel.Off,
+ "",
+ "",
+ beginTrace: (tr) => { },
+ execute: () => tcs.Task,
+ endTrace: (tr) => { },
+ errorTrace: (tr) => { invoked = true; }).Wait());
+
+ // Assert
+ Assert.True(invoked);
+ Assert.Same(exception, thrown);
+ }
+
+
+ [Fact]
+ public void TraceBeginEndAsync_Traces()
+ {
+ // Arrange
+ TestTraceWriter traceWriter = new TestTraceWriter();
+ HttpRequestMessage request = new HttpRequestMessage();
+ TraceRecord[] expectedTraces = new TraceRecord[]
+ {
+ new TraceRecord(request, "testCategory", TraceLevel.Info) { Kind = TraceKind.Begin, Operator = "tester", Operation = "testOp", Message = "beginMessage" },
+ new TraceRecord(request, "testCategory", TraceLevel.Info) { Kind = TraceKind.End, Operator = "tester", Operation = "testOp", Message = "endMessage" },
+ };
+
+ // Act
+ traceWriter.TraceBeginEndAsync(request,
+ "testCategory",
+ TraceLevel.Info,
+ "tester",
+ "testOp",
+ beginTrace: (tr) => { tr.Message = "beginMessage"; },
+ execute: () => TaskHelpers.Completed(),
+ endTrace: (tr) => { tr.Message = "endMessage"; },
+ errorTrace: (tr) => { tr.Message = "won't happen"; }).Wait();
+
+ // Assert
+ Assert.Equal<TraceRecord>(expectedTraces, traceWriter.Traces, new TraceRecordComparer());
+ }
+
+ [Fact]
+ public void TraceBeginEndAsync_Traces_When_Inner_Cancels()
+ {
+ // Arrange
+ TestTraceWriter traceWriter = new TestTraceWriter();
+ HttpRequestMessage request = new HttpRequestMessage();
+ TraceRecord[] expectedTraces = new TraceRecord[]
+ {
+ new TraceRecord(request, "testCategory", TraceLevel.Info) { Kind = TraceKind.Begin, Operator = "tester", Operation = "testOp", Message = "beginMessage" },
+ new TraceRecord(request, "testCategory", TraceLevel.Warn) { Kind = TraceKind.End, Operator = "tester", Operation = "testOp", Message = "errorMessage" },
+ };
+
+ // Act & Assert
+ Assert.Throws<TaskCanceledException>(
+ () => traceWriter.TraceBeginEndAsync(request,
+ "testCategory",
+ TraceLevel.Info,
+ "tester",
+ "testOp",
+ beginTrace: (tr) => { tr.Message = "beginMessage"; },
+ execute: () => TaskHelpers.Canceled(),
+ endTrace: (tr) => { tr.Message = "won't happen"; },
+ errorTrace: (tr) => { tr.Message = "errorMessage"; }).Wait());
+
+ // Assert
+ Assert.Equal<TraceRecord>(expectedTraces, traceWriter.Traces, new TraceRecordComparer());
+ }
+
+ [Fact]
+ public void TraceBeginAsync_Traces_And_Faults_When_Inner_Faults()
+ {
+ // Arrange
+ TestTraceWriter traceWriter = new TestTraceWriter();
+ HttpRequestMessage request = new HttpRequestMessage();
+ InvalidOperationException exception = new InvalidOperationException();
+ TraceRecord[] expectedTraces = new TraceRecord[]
+ {
+ new TraceRecord(request, "testCategory", TraceLevel.Info) { Kind = TraceKind.Begin, Operator = "tester", Operation = "testOp", Message = "beginMessage" },
+ new TraceRecord(request, "testCategory", TraceLevel.Error) { Kind = TraceKind.End, Operator = "tester", Operation = "testOp", Message = "errorMessage", Exception = exception },
+ };
+
+ TaskCompletionSource<object> tcs = new TaskCompletionSource<object>(null);
+ tcs.TrySetException(exception);
+
+ // Act & Assert
+ InvalidOperationException thrown = Assert.Throws<InvalidOperationException>(
+ () => traceWriter.TraceBeginEndAsync(request,
+ "testCategory",
+ TraceLevel.Info,
+ "tester",
+ "testOp",
+ beginTrace: (tr) => { tr.Message = "beginMessage"; },
+ execute: () => tcs.Task,
+ endTrace: (tr) => { tr.Message = "won't happen"; },
+ errorTrace: (tr) => { tr.Message = "errorMessage"; }).Wait());
+
+ // Assert
+ Assert.Equal<TraceRecord>(expectedTraces, traceWriter.Traces, new TraceRecordComparer());
+ Assert.Same(exception, thrown);
+ }
+
+ [Fact]
+ public void TraceBeginEndAsyncGeneric_Throws_With_Null_This()
+ {
+ // Arrange
+ TestTraceWriter traceWriter = null;
+ HttpRequestMessage request = new HttpRequestMessage();
+
+ // Act & Assert
+ Assert.ThrowsArgumentNull(() => traceWriter.TraceBeginEndAsync<int>(request,
+ "",
+ TraceLevel.Off,
+ "",
+ "",
+ beginTrace: null,
+ execute: () => TaskHelpers.FromResult<int>(1),
+ endTrace: null,
+ errorTrace: null),
+ "traceWriter");
+ }
+
+ [Fact]
+ public void TraceBeginEndAsyncGeneric_Throws_With_Null_Execute_Action()
+ {
+ // Arrange
+ TestTraceWriter traceWriter = new TestTraceWriter();
+ HttpRequestMessage request = new HttpRequestMessage();
+
+ // Act & Assert
+ Assert.ThrowsArgumentNull(() => traceWriter.TraceBeginEndAsync<int>(request,
+ "",
+ TraceLevel.Off,
+ "",
+ "",
+ beginTrace: null,
+ execute: null,
+ endTrace: null,
+ errorTrace: null),
+ "execute");
+ }
+
+ [Fact]
+ public void TraceBeginEndAsyncGeneric_Accepts_Null_Trace_Actions()
+ {
+ // Arrange
+ TestTraceWriter traceWriter = new TestTraceWriter();
+ HttpRequestMessage request = new HttpRequestMessage();
+
+ // Act & Assert
+ Task t = traceWriter.TraceBeginEndAsync<int>(request,
+ "",
+ TraceLevel.Off,
+ "",
+ "",
+ beginTrace: null,
+ execute: () => TaskHelpers.FromResult<int>(1),
+ endTrace: null,
+ errorTrace: null);
+ t.Wait();
+ }
+
+ [Fact]
+ public void TraceBeginEndAsyncGeneric_Invokes_BeginTrace()
+ {
+ // Arrange
+ TestTraceWriter traceWriter = new TestTraceWriter();
+ HttpRequestMessage request = new HttpRequestMessage();
+ bool invoked = false;
+
+ // Act
+ traceWriter.TraceBeginEndAsync<int>(request,
+ "",
+ TraceLevel.Fatal,
+ "",
+ "",
+ beginTrace: (tr) => { invoked = true; },
+ execute: () => TaskHelpers.FromResult<int>(1),
+ endTrace: (tr, value) => { },
+ errorTrace: (tr) => { }).Wait();
+
+ // Assert
+ Assert.True(invoked);
+ }
+
+ [Fact]
+ public void TraceBeginEndAsyncGeneric_Invokes_Execute()
+ {
+ // Arrange
+ TestTraceWriter traceWriter = new TestTraceWriter();
+ HttpRequestMessage request = new HttpRequestMessage();
+ bool invoked = false;
+
+ // Act
+ traceWriter.TraceBeginEndAsync<int>(request,
+ "",
+ TraceLevel.Fatal,
+ "",
+ "",
+ beginTrace: (tr) => { },
+ execute: () =>
+ {
+ invoked = true;
+ return TaskHelpers.FromResult<int>(1);
+ },
+ endTrace: (tr, value) => { },
+ errorTrace: (tr) => { }).Wait();
+
+ // Assert
+ Assert.True(invoked);
+ }
+
+ [Fact]
+ public void TraceBeginEndAsyncGeneric_Invokes_EndTrace()
+ {
+ // Arrange
+ TestTraceWriter traceWriter = new TestTraceWriter();
+ HttpRequestMessage request = new HttpRequestMessage();
+ bool invoked = false;
+ int invokedValue = 0;
+
+ // Act
+ traceWriter.TraceBeginEndAsync<int>(request,
+ "",
+ TraceLevel.Off,
+ "",
+ "",
+ beginTrace: (tr) => { },
+ execute: () => TaskHelpers.FromResult<int>(1),
+ endTrace: (tr, value) => { invoked = true; invokedValue = value; },
+ errorTrace: (tr) => { }).Wait();
+
+ // Assert
+ Assert.True(invoked);
+ Assert.Equal(1, invokedValue);
+ }
+
+ [Fact]
+ public void TraceBeginEndAsyncGeneric_Invokes_ErrorTrace()
+ {
+ // Arrange
+ TestTraceWriter traceWriter = new TestTraceWriter();
+ HttpRequestMessage request = new HttpRequestMessage();
+ bool invoked = false;
+ Exception exception = new InvalidOperationException();
+ TaskCompletionSource<int> tcs = new TaskCompletionSource<int>(0);
+ tcs.TrySetException(exception);
+
+ // Act
+ Exception thrown = Assert.Throws<InvalidOperationException>(
+ () => traceWriter.TraceBeginEndAsync<int>(request,
+ "",
+ TraceLevel.Off,
+ "",
+ "",
+ beginTrace: (tr) => { },
+ execute: () => tcs.Task,
+ endTrace: (tr, value) => { },
+ errorTrace: (tr) => { invoked = true; }).Wait());
+
+ // Assert
+ Assert.True(invoked);
+ Assert.Same(exception, thrown);
+ }
+
+ [Fact]
+ public void TraceBeginEndAsyncGeneric_Traces()
+ {
+ // Arrange
+ TestTraceWriter traceWriter = new TestTraceWriter();
+ HttpRequestMessage request = new HttpRequestMessage();
+ TraceRecord[] expectedTraces = new TraceRecord[]
+ {
+ new TraceRecord(request, "testCategory", TraceLevel.Info) { Kind = TraceKind.Begin, Operator = "tester", Operation = "testOp", Message = "beginMessage" },
+ new TraceRecord(request, "testCategory", TraceLevel.Info) { Kind = TraceKind.End, Operator = "tester", Operation = "testOp", Message = "endMessage1" },
+ };
+
+ // Act
+ traceWriter.TraceBeginEndAsync<int>(request,
+ "testCategory",
+ TraceLevel.Info,
+ "tester",
+ "testOp",
+ beginTrace: (tr) => { tr.Message = "beginMessage"; },
+ execute: () => TaskHelpers.FromResult<int>(1),
+ endTrace: (tr, value) => { tr.Message = "endMessage" + value; },
+ errorTrace: (tr) => { tr.Message = "won't happen"; }).Wait();
+
+ // Assert
+ Assert.Equal<TraceRecord>(expectedTraces, traceWriter.Traces, new TraceRecordComparer());
+ }
+
+ [Fact]
+ public void TraceBeginEndAsyncGeneric_Traces_When_Inner_Cancels()
+ {
+ // Arrange
+ TestTraceWriter traceWriter = new TestTraceWriter();
+ HttpRequestMessage request = new HttpRequestMessage();
+ TraceRecord[] expectedTraces = new TraceRecord[]
+ {
+ new TraceRecord(request, "testCategory", TraceLevel.Info) { Kind = TraceKind.Begin, Operator = "tester", Operation = "testOp", Message = "beginMessage" },
+ new TraceRecord(request, "testCategory", TraceLevel.Warn) { Kind = TraceKind.End, Operator = "tester", Operation = "testOp", Message = "errorMessage" },
+ };
+
+ // Act & Assert
+ Assert.Throws<TaskCanceledException>(
+ () => traceWriter.TraceBeginEndAsync<int>(request,
+ "testCategory",
+ TraceLevel.Info,
+ "tester",
+ "testOp",
+ beginTrace: (tr) => { tr.Message = "beginMessage"; },
+ execute: () => TaskHelpers.Canceled<int>(),
+ endTrace: (tr, value) => { tr.Message = "won't happen"; },
+ errorTrace: (tr) => { tr.Message = "errorMessage"; }).Wait());
+
+ // Assert
+ Assert.Equal<TraceRecord>(expectedTraces, traceWriter.Traces, new TraceRecordComparer());
+ }
+
+ [Fact]
+ public void TraceBeginAsyncGeneric_Traces_And_Faults_When_Inner_Faults()
+ {
+ // Arrange
+ TestTraceWriter traceWriter = new TestTraceWriter();
+ HttpRequestMessage request = new HttpRequestMessage();
+ InvalidOperationException exception = new InvalidOperationException();
+ TraceRecord[] expectedTraces = new TraceRecord[]
+ {
+ new TraceRecord(request, "testCategory", TraceLevel.Info) { Kind = TraceKind.Begin, Operator = "tester", Operation = "testOp", Message = "beginMessage" },
+ new TraceRecord(request, "testCategory", TraceLevel.Error) { Kind = TraceKind.End, Operator = "tester", Operation = "testOp", Message = "errorMessage", Exception = exception },
+ };
+
+ TaskCompletionSource<int> tcs = new TaskCompletionSource<int>(1);
+ tcs.TrySetException(exception);
+
+ // Act & Assert
+ InvalidOperationException thrown = Assert.Throws<InvalidOperationException>(
+ () => traceWriter.TraceBeginEndAsync<int>(request,
+ "testCategory",
+ TraceLevel.Info,
+ "tester",
+ "testOp",
+ beginTrace: (tr) => { tr.Message = "beginMessage"; },
+ execute: () => tcs.Task,
+ endTrace: (tr, value) => { tr.Message = "won't happen"; },
+ errorTrace: (tr) => { tr.Message = "errorMessage"; }).Wait());
+
+ // Assert
+ Assert.Equal<TraceRecord>(expectedTraces, traceWriter.Traces, new TraceRecordComparer());
+ Assert.Same(exception, thrown);
+ }
+ }
+}
diff --git a/test/System.Web.Http.Test/Tracing/TestTraceWriter.cs b/test/System.Web.Http.Test/Tracing/TestTraceWriter.cs
new file mode 100644
index 00000000..34090935
--- /dev/null
+++ b/test/System.Web.Http.Test/Tracing/TestTraceWriter.cs
@@ -0,0 +1,30 @@
+using System.Collections.Generic;
+using System.Net.Http;
+
+namespace System.Web.Http.Tracing
+{
+ /// <summary>
+ /// Test spy used internally to capture <see cref="TraceRecord"/>s.
+ /// </summary>
+ internal class TestTraceWriter : ITraceWriter
+ {
+ private List<TraceRecord> _traceRecords = new List<TraceRecord>();
+
+ public IList<TraceRecord> Traces { get { return _traceRecords; } }
+
+ public bool IsEnabled(string category, TraceLevel level)
+ {
+ return true;
+ }
+
+ public void Trace(HttpRequestMessage request, string category, TraceLevel level, Action<TraceRecord> traceAction)
+ {
+ TraceRecord traceRecord = new TraceRecord(request, category, level);
+ traceAction(traceRecord);
+ lock (_traceRecords)
+ {
+ _traceRecords.Add(traceRecord);
+ }
+ }
+ }
+}
diff --git a/test/System.Web.Http.Test/Tracing/TraceManagerTest.cs b/test/System.Web.Http.Test/Tracing/TraceManagerTest.cs
new file mode 100644
index 00000000..e1ecdfe8
--- /dev/null
+++ b/test/System.Web.Http.Test/Tracing/TraceManagerTest.cs
@@ -0,0 +1,137 @@
+using System.Net.Http;
+using System.Net.Http.Formatting;
+using System.Web.Http.Controllers;
+using System.Web.Http.Dispatcher;
+using System.Web.Http.Tracing.Tracers;
+using Moq;
+using Xunit;
+using Xunit.Extensions;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Http.Tracing
+{
+ public class TraceManagerTest
+ {
+ [Fact]
+ public void TraceManager_Is_In_Default_ServiceResolver()
+ {
+ // Arrange
+ HttpConfiguration config = new HttpConfiguration();
+
+ // Act
+ ITraceManager traceManager = config.ServiceResolver.GetService(typeof (ITraceManager)) as ITraceManager;
+
+ // Assert
+ Assert.IsType<TraceManager>(traceManager);
+ }
+
+ [Theory]
+ [InlineData(typeof(IHttpControllerFactory))]
+ [InlineData(typeof(IHttpControllerActivator))]
+ [InlineData(typeof(IHttpActionSelector))]
+ [InlineData(typeof(IHttpActionInvoker))]
+ [InlineData(typeof(IActionValueBinder))]
+ [InlineData(typeof(IContentNegotiator))]
+ public void Initialize_Does_Not_Alter_Configuration_When_No_TraceWriter_Present(Type serviceType)
+ {
+ // Arrange
+ HttpConfiguration config = new HttpConfiguration();
+ object defaultService = config.ServiceResolver.GetService(serviceType);
+
+ // Act
+ new TraceManager().Initialize(config);
+
+ // Assert
+ Assert.Same(defaultService.GetType(), config.ServiceResolver.GetService(serviceType).GetType());
+ }
+
+ [Theory]
+ [InlineData(typeof(IHttpControllerFactory))]
+ [InlineData(typeof(IHttpControllerActivator))]
+ [InlineData(typeof(IHttpActionSelector))]
+ [InlineData(typeof(IHttpActionInvoker))]
+ [InlineData(typeof(IActionValueBinder))]
+ [InlineData(typeof(IContentNegotiator))]
+ public void Initialize_Alters_Configuration_When_TraceWriter_Present(Type serviceType)
+ {
+ // Arrange
+ HttpConfiguration config = new HttpConfiguration();
+ Mock<ITraceWriter> traceWriter = new Mock<ITraceWriter>() { CallBase = true };
+ config.ServiceResolver.SetService(typeof(ITraceWriter), traceWriter.Object);
+ object defaultService = config.ServiceResolver.GetService(serviceType);
+
+ // Act
+ new TraceManager().Initialize(config);
+
+ // Assert
+ Assert.NotSame(defaultService.GetType(), config.ServiceResolver.GetService(serviceType).GetType());
+ }
+
+ [Fact]
+ public void Initialize_Does_Not_Alter_MessageHandlers_When_No_TraceWriter_Present()
+ {
+ // Arrange
+ HttpConfiguration config = new HttpConfiguration();
+ Mock<DelegatingHandler> mockHandler = new Mock<DelegatingHandler>() { CallBase = true };
+ config.MessageHandlers.Add(mockHandler.Object);
+
+ // Act
+ new TraceManager().Initialize(config);
+
+ // Assert
+ Assert.Equal(config.MessageHandlers[config.MessageHandlers.Count - 1].GetType(), mockHandler.Object.GetType());
+ }
+
+ [Fact]
+ public void Initialize_Alters_MessageHandlers_WhenTraceWriter_Present()
+ {
+ // Arrange
+ HttpConfiguration config = new HttpConfiguration();
+ Mock<ITraceWriter> traceWriter = new Mock<ITraceWriter>() { CallBase = true };
+ config.ServiceResolver.SetService(typeof(ITraceWriter), traceWriter.Object);
+ Mock<DelegatingHandler> mockHandler = new Mock<DelegatingHandler>() { CallBase = true };
+ config.MessageHandlers.Add(mockHandler.Object);
+
+ // Act
+ new TraceManager().Initialize(config);
+
+ // Assert
+ Assert.IsAssignableFrom<RequestMessageHandlerTracer>(config.MessageHandlers[config.MessageHandlers.Count - 1]);
+ Assert.IsAssignableFrom<MessageHandlerTracer>(config.MessageHandlers[config.MessageHandlers.Count - 2]);
+ }
+
+ [Fact]
+ public void Initialize_Does_Not_Alter_MediaTypeFormatters_When_No_TraceWriter_Present()
+ {
+ // Arrange
+ HttpConfiguration config = new HttpConfiguration();
+
+ // Act
+ new TraceManager().Initialize(config);
+
+ // Assert
+ foreach (var formatter in config.Formatters)
+ {
+ Assert.False(typeof(IFormatterTracer).IsAssignableFrom(formatter.GetType()));
+ }
+ }
+
+ [Fact]
+ public void Initialize_Alters_MediaTypeFormatters_WhenTraceWriter_Present()
+ {
+ // Arrange
+ HttpConfiguration config = new HttpConfiguration();
+ Mock<ITraceWriter> traceWriter = new Mock<ITraceWriter>() { CallBase = true };
+ config.ServiceResolver.SetService(typeof(ITraceWriter), traceWriter.Object);
+
+ // Act
+ new TraceManager().Initialize(config);
+
+ // Assert
+ foreach (var formatter in config.Formatters)
+ {
+ Assert.IsAssignableFrom<IFormatterTracer>(formatter);
+ }
+ }
+ }
+}
diff --git a/test/System.Web.Http.Test/Tracing/TraceRecordComparer.cs b/test/System.Web.Http.Test/Tracing/TraceRecordComparer.cs
new file mode 100644
index 00000000..71953b35
--- /dev/null
+++ b/test/System.Web.Http.Test/Tracing/TraceRecordComparer.cs
@@ -0,0 +1,39 @@
+using System.Collections.Generic;
+
+namespace System.Web.Http.Tracing
+{
+ /// <summary>
+ /// Comparer class to allow xUnit asserts for <see cref="TraceRecord"/>.
+ /// </summary>
+ class TraceRecordComparer : IEqualityComparer<TraceRecord>
+ {
+ public bool Equals(TraceRecord x, TraceRecord y)
+ {
+ if (!String.Equals(x.Category, y.Category) ||
+ x.Level != y.Level ||
+ x.Kind != y.Kind ||
+ !Object.ReferenceEquals(x.Request, y.Request))
+ return false;
+
+ // The following must match only if they are present on 'x' -- the expected value
+ if (x.Exception != null && !Object.ReferenceEquals(x.Exception, y.Exception))
+ return false;
+
+ if (!String.IsNullOrEmpty(x.Message) && !String.Equals(x.Message, y.Message))
+ return false;
+
+ if (!String.IsNullOrEmpty(x.Operation) && !String.Equals(x.Operation, y.Operation))
+ return false;
+
+ if (!String.IsNullOrEmpty(x.Operator) && !String.Equals(x.Operator, y.Operator))
+ return false;
+
+ return true;
+ }
+
+ public int GetHashCode(TraceRecord obj)
+ {
+ return obj.GetHashCode();
+ }
+ }
+}
diff --git a/test/System.Web.Http.Test/Tracing/Tracers/ActionFilterAttributeTracerTest.cs b/test/System.Web.Http.Test/Tracing/Tracers/ActionFilterAttributeTracerTest.cs
new file mode 100644
index 00000000..351fd604
--- /dev/null
+++ b/test/System.Web.Http.Test/Tracing/Tracers/ActionFilterAttributeTracerTest.cs
@@ -0,0 +1,108 @@
+using System.Collections.ObjectModel;
+using System.Net.Http;
+using System.Threading;
+using System.Threading.Tasks;
+using System.Web.Http.Controllers;
+using System.Web.Http.Filters;
+using Moq;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Http.Tracing.Tracers
+{
+ public class ActionFilterAttributeTracerTest
+ {
+ [Fact]
+ public void ExecuteActionFilterAsync_Traces_Executing_And_Executed()
+ {
+ // Arrange
+ Mock<ActionFilterAttribute> mockAttr = new Mock<ActionFilterAttribute>() { CallBase = true };
+ Mock<HttpActionDescriptor> mockActionDescriptor = new Mock<HttpActionDescriptor>() {CallBase = true};
+ mockActionDescriptor.Setup(a => a.ActionName).Returns("test");
+ mockActionDescriptor.Setup(a => a.GetParameters()).Returns(new Collection<HttpParameterDescriptor>(new HttpParameterDescriptor[0]));
+ HttpActionContext actionContext = ContextUtil.CreateActionContext(actionDescriptor: mockActionDescriptor.Object);
+ TestTraceWriter traceWriter = new TestTraceWriter();
+ ActionFilterAttributeTracer tracer = new ActionFilterAttributeTracer(mockAttr.Object, traceWriter);
+ Func<Task<HttpResponseMessage>> continuation =
+ () => TaskHelpers.FromResult<HttpResponseMessage>(new HttpResponseMessage());
+ TraceRecord[] expectedTraces = new TraceRecord[]
+ {
+ new TraceRecord(actionContext.Request, TraceCategories.FiltersCategory, TraceLevel.Info) { Kind = TraceKind.Begin, Operation = "ActionExecuting" },
+ new TraceRecord(actionContext.Request, TraceCategories.FiltersCategory, TraceLevel.Info) { Kind = TraceKind.End, Operation = "ActionExecuting" },
+ new TraceRecord(actionContext.Request, TraceCategories.FiltersCategory, TraceLevel.Info) { Kind = TraceKind.Begin, Operation = "ActionExecuted" },
+ new TraceRecord(actionContext.Request, TraceCategories.FiltersCategory, TraceLevel.Info) { Kind = TraceKind.End, Operation = "ActionExecuted" }
+ };
+
+ // Act
+ Task<HttpResponseMessage> task = ((IActionFilter) tracer).ExecuteActionFilterAsync(actionContext, CancellationToken.None, continuation);
+ task.Wait();
+
+ // Assert
+ Assert.Equal<TraceRecord>(expectedTraces, traceWriter.Traces, new TraceRecordComparer());
+ }
+
+ [Fact]
+ public void ExecuteActionFilterAsync_Faults_And_Traces_When_OnExecuting_Faults()
+ {
+ // Arrange
+ Mock<ActionFilterAttribute> mockAttr = new Mock<ActionFilterAttribute>() { CallBase = true };
+ InvalidOperationException exception = new InvalidOperationException("test");
+ mockAttr.Setup(a => a.OnActionExecuting(It.IsAny<HttpActionContext>())).Throws(exception);
+ Mock<HttpActionDescriptor> mockActionDescriptor = new Mock<HttpActionDescriptor>() { CallBase = true };
+ mockActionDescriptor.Setup(a => a.ActionName).Returns("test");
+ mockActionDescriptor.Setup(a => a.GetParameters()).Returns(new Collection<HttpParameterDescriptor>(new HttpParameterDescriptor[0]));
+ HttpActionContext actionContext = ContextUtil.CreateActionContext(actionDescriptor: mockActionDescriptor.Object);
+ TestTraceWriter traceWriter = new TestTraceWriter();
+ ActionFilterAttributeTracer tracer = new ActionFilterAttributeTracer(mockAttr.Object, traceWriter);
+ Func<Task<HttpResponseMessage>> continuation =
+ () => TaskHelpers.FromResult<HttpResponseMessage>(new HttpResponseMessage());
+ TraceRecord[] expectedTraces = new TraceRecord[]
+ {
+ new TraceRecord(actionContext.Request, TraceCategories.FiltersCategory, TraceLevel.Info) { Kind = TraceKind.Begin, Operation = "ActionExecuting" },
+ new TraceRecord(actionContext.Request, TraceCategories.FiltersCategory, TraceLevel.Error) { Kind = TraceKind.End, Operation = "ActionExecuting" }
+ };
+
+ // Act
+ Task<HttpResponseMessage> task = ((IActionFilter)tracer).ExecuteActionFilterAsync(actionContext, CancellationToken.None, continuation);
+
+ // Assert
+ Exception thrown = Assert.Throws<InvalidOperationException>(() => task.Wait());
+ Assert.Same(exception, thrown);
+ Assert.Same(exception, traceWriter.Traces[1].Exception);
+ Assert.Equal<TraceRecord>(expectedTraces, traceWriter.Traces, new TraceRecordComparer());
+ }
+
+ [Fact]
+ public void ExecuteActionFilterAsync_Faults_And_Traces_When_OnExecuted_Faults()
+ {
+ // Arrange
+ Mock<ActionFilterAttribute> mockAttr = new Mock<ActionFilterAttribute>() { CallBase = true };
+ InvalidOperationException exception = new InvalidOperationException("test");
+ mockAttr.Setup(a => a.OnActionExecuted(It.IsAny<HttpActionExecutedContext>())).Throws(exception);
+ Mock<HttpActionDescriptor> mockActionDescriptor = new Mock<HttpActionDescriptor>() { CallBase = true };
+ mockActionDescriptor.Setup(a => a.ActionName).Returns("test");
+ mockActionDescriptor.Setup(a => a.GetParameters()).Returns(new Collection<HttpParameterDescriptor>(new HttpParameterDescriptor[0]));
+ HttpActionContext actionContext = ContextUtil.CreateActionContext(actionDescriptor: mockActionDescriptor.Object);
+ TestTraceWriter traceWriter = new TestTraceWriter();
+ ActionFilterAttributeTracer tracer = new ActionFilterAttributeTracer(mockAttr.Object, traceWriter);
+ Func<Task<HttpResponseMessage>> continuation =
+ () => TaskHelpers.FromResult<HttpResponseMessage>(new HttpResponseMessage());
+ TraceRecord[] expectedTraces = new TraceRecord[]
+ {
+ new TraceRecord(actionContext.Request, TraceCategories.FiltersCategory, TraceLevel.Info) { Kind = TraceKind.Begin, Operation = "ActionExecuting" },
+ new TraceRecord(actionContext.Request, TraceCategories.FiltersCategory, TraceLevel.Info) { Kind = TraceKind.End, Operation = "ActionExecuting" },
+ new TraceRecord(actionContext.Request, TraceCategories.FiltersCategory, TraceLevel.Info) { Kind = TraceKind.Begin, Operation = "ActionExecuted" },
+ new TraceRecord(actionContext.Request, TraceCategories.FiltersCategory, TraceLevel.Error) { Kind = TraceKind.End, Operation = "ActionExecuted" }
+ };
+
+ // Act
+ Task<HttpResponseMessage> task = ((IActionFilter)tracer).ExecuteActionFilterAsync(actionContext, CancellationToken.None, continuation);
+
+ // Assert
+ Exception thrown = Assert.Throws<InvalidOperationException>(() => task.Wait());
+ Assert.Same(exception, thrown);
+ Assert.Same(exception, traceWriter.Traces[3].Exception);
+ Assert.Equal<TraceRecord>(expectedTraces, traceWriter.Traces, new TraceRecordComparer());
+ }
+ }
+}
diff --git a/test/System.Web.Http.Test/Tracing/Tracers/ActionFilterTracerTest.cs b/test/System.Web.Http.Test/Tracing/Tracers/ActionFilterTracerTest.cs
new file mode 100644
index 00000000..4325ee3a
--- /dev/null
+++ b/test/System.Web.Http.Test/Tracing/Tracers/ActionFilterTracerTest.cs
@@ -0,0 +1,82 @@
+using System.Collections.ObjectModel;
+using System.Net.Http;
+using System.Threading;
+using System.Threading.Tasks;
+using System.Web.Http.Controllers;
+using System.Web.Http.Filters;
+using Moq;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Http.Tracing.Tracers
+{
+ public class ActionFilterTracerTest
+ {
+ [Fact]
+ public void ExecuteActionAsync_Traces_ExecuteActionFilterAsync()
+ {
+ // Arrange
+ HttpResponseMessage response = new HttpResponseMessage();
+ Mock<IActionFilter> mockFilter = new Mock<IActionFilter>() { CallBase = true };
+ mockFilter.Setup(
+ f =>
+ f.ExecuteActionFilterAsync(It.IsAny<HttpActionContext>(), It.IsAny<CancellationToken>(),
+ It.IsAny<Func<Task<HttpResponseMessage>>>())).Returns(
+ TaskHelpers.FromResult<HttpResponseMessage>(response));
+ Mock<HttpActionDescriptor> mockActionDescriptor = new Mock<HttpActionDescriptor>() { CallBase = true };
+ mockActionDescriptor.Setup(a => a.ActionName).Returns("test");
+ mockActionDescriptor.Setup(a => a.GetParameters()).Returns(new Collection<HttpParameterDescriptor>(new HttpParameterDescriptor[0]));
+ HttpActionContext actionContext = ContextUtil.CreateActionContext(actionDescriptor: mockActionDescriptor.Object);
+ TestTraceWriter traceWriter = new TestTraceWriter();
+ ActionFilterTracer tracer = new ActionFilterTracer(mockFilter.Object, traceWriter);
+ Func<Task<HttpResponseMessage>> continuation =
+ () => TaskHelpers.FromResult<HttpResponseMessage>(new HttpResponseMessage());
+ TraceRecord[] expectedTraces = new TraceRecord[]
+ {
+ new TraceRecord(actionContext.Request, TraceCategories.FiltersCategory, TraceLevel.Info) { Kind = TraceKind.Begin, Operation = "ExecuteActionFilterAsync" },
+ new TraceRecord(actionContext.Request, TraceCategories.FiltersCategory, TraceLevel.Info) { Kind = TraceKind.End, Operation = "ExecuteActionFilterAsync" },
+ };
+
+ // Act
+ Task<HttpResponseMessage> task = ((IActionFilter)tracer).ExecuteActionFilterAsync(actionContext, CancellationToken.None, continuation);
+ task.Wait();
+
+ // Assert
+ Assert.Equal<TraceRecord>(expectedTraces, traceWriter.Traces, new TraceRecordComparer());
+ }
+
+ [Fact]
+ public void ExecuteActionAsync_Faults_And_Traces_When_Inner_Faults()
+ {
+ // Arrange
+ InvalidOperationException exception = new InvalidOperationException("test");
+ Mock<IActionFilter> mockFilter = new Mock<IActionFilter>() { CallBase = true };
+ TaskCompletionSource<HttpResponseMessage> tcs = new TaskCompletionSource<HttpResponseMessage>(null);
+ tcs.TrySetException(exception);
+ mockFilter.Setup(f => f.ExecuteActionFilterAsync(It.IsAny<HttpActionContext>(), It.IsAny<CancellationToken>(),
+ It.IsAny<Func<Task<HttpResponseMessage>>>())).Returns(tcs.Task);
+ Mock<HttpActionDescriptor> mockActionDescriptor = new Mock<HttpActionDescriptor>() { CallBase = true };
+ mockActionDescriptor.Setup(a => a.ActionName).Returns("test");
+ mockActionDescriptor.Setup(a => a.GetParameters()).Returns(new Collection<HttpParameterDescriptor>(new HttpParameterDescriptor[0]));
+ HttpActionContext actionContext = ContextUtil.CreateActionContext(actionDescriptor: mockActionDescriptor.Object);
+ TestTraceWriter traceWriter = new TestTraceWriter();
+ ActionFilterTracer tracer = new ActionFilterTracer(mockFilter.Object, traceWriter);
+ Func<Task<HttpResponseMessage>> continuation =
+ () => TaskHelpers.FromResult<HttpResponseMessage>(new HttpResponseMessage());
+ TraceRecord[] expectedTraces = new TraceRecord[]
+ {
+ new TraceRecord(actionContext.Request, TraceCategories.FiltersCategory, TraceLevel.Info) { Kind = TraceKind.Begin, Operation = "ExecuteActionFilterAsync" },
+ new TraceRecord(actionContext.Request, TraceCategories.FiltersCategory, TraceLevel.Error) { Kind = TraceKind.End, Operation = "ExecuteActionFilterAsync" },
+ };
+
+ // Act
+ Task<HttpResponseMessage> task = ((IActionFilter)tracer).ExecuteActionFilterAsync(actionContext, CancellationToken.None, continuation);
+
+
+ // Assert
+ Exception thrown = Assert.Throws<InvalidOperationException>(() => task.Wait());
+ Assert.Equal<TraceRecord>(expectedTraces, traceWriter.Traces, new TraceRecordComparer());
+ Assert.Same(exception, traceWriter.Traces[1].Exception);
+ }
+ }
+}
diff --git a/test/System.Web.Http.Test/Tracing/Tracers/ActionValueBinderTracerTest.cs b/test/System.Web.Http.Test/Tracing/Tracers/ActionValueBinderTracerTest.cs
new file mode 100644
index 00000000..459281bd
--- /dev/null
+++ b/test/System.Web.Http.Test/Tracing/Tracers/ActionValueBinderTracerTest.cs
@@ -0,0 +1,71 @@
+using System.Collections.ObjectModel;
+using System.Net.Http;
+using System.Net.Http.Formatting;
+using System.Web.Http.Controllers;
+using System.Web.Http.ModelBinding;
+using Moq;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Http.Tracing.Tracers
+{
+ public class ActionValueBinderTracerTest
+ {
+ [Fact]
+ public void GetBinding_Invokes_Inner_And_Returns_ActionBinder_With_Tracing_HttpParameterBinding()
+ {
+ // Arrange
+ Mock<HttpActionDescriptor> mockActionDescriptor = new Mock<HttpActionDescriptor>() { CallBase = true };
+ mockActionDescriptor.Setup(a => a.ActionName).Returns("test");
+ mockActionDescriptor.Setup(a => a.GetParameters()).Returns(new Collection<HttpParameterDescriptor>(new HttpParameterDescriptor[0]));
+
+ Mock<HttpParameterDescriptor> mockParameterDescriptor = new Mock<HttpParameterDescriptor>() { CallBase = true };
+ Mock<HttpParameterBinding> mockParameterBinding = new Mock<HttpParameterBinding>(mockParameterDescriptor.Object) { CallBase = true };
+ HttpActionBinding actionBinding = new HttpActionBinding(mockActionDescriptor.Object, new HttpParameterBinding[] { mockParameterBinding.Object });
+
+ HttpControllerDescriptor controllerDescriptor = new HttpControllerDescriptor(new HttpConfiguration(), "controller", typeof(ApiController));
+
+ HttpControllerContext controllerContext = ContextUtil.CreateControllerContext(request: new HttpRequestMessage());
+ controllerContext.ControllerDescriptor = controllerDescriptor;
+
+ Mock<IActionValueBinder> mockBinder = new Mock<IActionValueBinder>() {CallBase = true};
+ mockBinder.Setup(b => b.GetBinding(It.IsAny<HttpActionDescriptor>())).Returns(actionBinding);
+ ActionValueBinderTracer tracer = new ActionValueBinderTracer(mockBinder.Object, new TestTraceWriter());
+
+ // Act
+ HttpActionBinding actualBinding = ((IActionValueBinder) tracer).GetBinding(mockActionDescriptor.Object);
+
+ // Assert
+ Assert.IsAssignableFrom<HttpParameterBindingTracer>(actualBinding.ParameterBindings[0]);
+ }
+
+ [Fact]
+ public void GetBinding_Invokes_Inner_And_Returns_ActionBinder_With_Tracing_FormatterParameterBinding()
+ {
+ // Arrange
+ Mock<HttpActionDescriptor> mockActionDescriptor = new Mock<HttpActionDescriptor>() { CallBase = true };
+ mockActionDescriptor.Setup(a => a.ActionName).Returns("test");
+ mockActionDescriptor.Setup(a => a.GetParameters()).Returns(new Collection<HttpParameterDescriptor>(new HttpParameterDescriptor[0]));
+
+ Mock<HttpParameterDescriptor> mockParameterDescriptor = new Mock<HttpParameterDescriptor>() { CallBase = true };
+ Mock<FormatterParameterBinding> mockParameterBinding = new Mock<FormatterParameterBinding>(mockParameterDescriptor.Object, new MediaTypeFormatterCollection(), null) { CallBase = true };
+ HttpActionBinding actionBinding = new HttpActionBinding(mockActionDescriptor.Object, new HttpParameterBinding[] { mockParameterBinding.Object });
+
+ HttpControllerDescriptor controllerDescriptor = new HttpControllerDescriptor(new HttpConfiguration(), "controller", typeof(ApiController));
+
+ HttpControllerContext controllerContext = ContextUtil.CreateControllerContext(request: new HttpRequestMessage());
+ controllerContext.ControllerDescriptor = controllerDescriptor;
+
+ Mock<IActionValueBinder> mockBinder = new Mock<IActionValueBinder>() { CallBase = true };
+ mockBinder.Setup(b => b.GetBinding(It.IsAny<HttpActionDescriptor>())).Returns(actionBinding);
+ ActionValueBinderTracer tracer = new ActionValueBinderTracer(mockBinder.Object, new TestTraceWriter());
+
+ // Act
+ HttpActionBinding actualBinding = ((IActionValueBinder)tracer).GetBinding(mockActionDescriptor.Object);
+
+ // Assert
+ Assert.IsAssignableFrom<FormatterParameterBindingTracer>(actualBinding.ParameterBindings[0]);
+ }
+
+ }
+}
diff --git a/test/System.Web.Http.Test/Tracing/Tracers/AuthorizationFilterAttributeTracerTest.cs b/test/System.Web.Http.Test/Tracing/Tracers/AuthorizationFilterAttributeTracerTest.cs
new file mode 100644
index 00000000..c3c1b60a
--- /dev/null
+++ b/test/System.Web.Http.Test/Tracing/Tracers/AuthorizationFilterAttributeTracerTest.cs
@@ -0,0 +1,72 @@
+using System.Collections.ObjectModel;
+using System.Net.Http;
+using System.Threading;
+using System.Threading.Tasks;
+using System.Web.Http.Controllers;
+using System.Web.Http.Filters;
+using Moq;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Http.Tracing.Tracers
+{
+ public class AuthorizationFilterAttributeTracerTest
+ {
+ [Fact]
+ public void ExecuteAuthorizationFilterAsync_Traces()
+ {
+ // Arrange
+ Mock<AuthorizationFilterAttribute> mockAttr = new Mock<AuthorizationFilterAttribute>() { CallBase = true };
+ Mock<HttpActionDescriptor> mockActionDescriptor = new Mock<HttpActionDescriptor>() { CallBase = true };
+ mockActionDescriptor.Setup(a => a.ActionName).Returns("test");
+ mockActionDescriptor.Setup(a => a.GetParameters()).Returns(new Collection<HttpParameterDescriptor>(new HttpParameterDescriptor[0]));
+ HttpActionContext actionContext = ContextUtil.CreateActionContext(actionDescriptor: mockActionDescriptor.Object);
+ Func<Task<HttpResponseMessage>> continuation = () => TaskHelpers.FromResult<HttpResponseMessage>(new HttpResponseMessage());
+ TestTraceWriter traceWriter = new TestTraceWriter();
+ AuthorizationFilterAttributeTracer tracer = new AuthorizationFilterAttributeTracer(mockAttr.Object, traceWriter);
+ TraceRecord[] expectedTraces = new TraceRecord[]
+ {
+ new TraceRecord(actionContext.Request, TraceCategories.FiltersCategory, TraceLevel.Info) { Kind = TraceKind.Begin, Operation = "OnAuthorization" },
+ new TraceRecord(actionContext.Request, TraceCategories.FiltersCategory, TraceLevel.Info) { Kind = TraceKind.End, Operation = "OnAuthorization" },
+ };
+
+ // Act
+ Task task = ((IAuthorizationFilter)tracer).ExecuteAuthorizationFilterAsync(actionContext, CancellationToken.None, continuation);
+ task.Wait();
+
+ // Assert
+ Assert.Equal<TraceRecord>(expectedTraces, traceWriter.Traces, new TraceRecordComparer());
+ }
+
+ [Fact]
+ public void ExecuteAuthorizationFilterAsync_Throws_And_Traces_When_Inner_OnException_Throws()
+ {
+ // Arrange
+ Mock<AuthorizationFilterAttribute> mockAttr = new Mock<AuthorizationFilterAttribute>() { CallBase = true };
+ InvalidOperationException exception = new InvalidOperationException("test");
+ mockAttr.Setup(a => a.OnAuthorization(It.IsAny<HttpActionContext>())).Throws(exception);
+ Mock<HttpActionDescriptor> mockActionDescriptor = new Mock<HttpActionDescriptor>() { CallBase = true };
+ mockActionDescriptor.Setup(a => a.ActionName).Returns("test");
+ mockActionDescriptor.Setup(a => a.GetParameters()).Returns(new Collection<HttpParameterDescriptor>(new HttpParameterDescriptor[0]));
+ HttpActionContext actionContext = ContextUtil.CreateActionContext(actionDescriptor: mockActionDescriptor.Object);
+ Func<Task<HttpResponseMessage>> continuation = () => TaskHelpers.FromResult<HttpResponseMessage>(new HttpResponseMessage());
+ TestTraceWriter traceWriter = new TestTraceWriter();
+ AuthorizationFilterAttributeTracer tracer = new AuthorizationFilterAttributeTracer(mockAttr.Object, traceWriter);
+ TraceRecord[] expectedTraces = new TraceRecord[]
+ {
+ new TraceRecord(actionContext.Request, TraceCategories.FiltersCategory, TraceLevel.Info) { Kind = TraceKind.Begin, Operation = "OnAuthorization" },
+ new TraceRecord(actionContext.Request, TraceCategories.FiltersCategory, TraceLevel.Error) { Kind = TraceKind.End, Operation = "OnAuthorization" }
+ };
+
+ // Act
+ Exception thrown =
+ Assert.Throws<InvalidOperationException>(
+ () => ((IAuthorizationFilter)tracer).ExecuteAuthorizationFilterAsync(actionContext, CancellationToken.None, continuation).Wait());
+
+ // Assert
+ Assert.Same(exception, thrown);
+ Assert.Same(exception, traceWriter.Traces[1].Exception);
+ Assert.Equal<TraceRecord>(expectedTraces, traceWriter.Traces, new TraceRecordComparer());
+ }
+ }
+}
diff --git a/test/System.Web.Http.Test/Tracing/Tracers/AuthorizationFilterTracerTest.cs b/test/System.Web.Http.Test/Tracing/Tracers/AuthorizationFilterTracerTest.cs
new file mode 100644
index 00000000..7164b6d1
--- /dev/null
+++ b/test/System.Web.Http.Test/Tracing/Tracers/AuthorizationFilterTracerTest.cs
@@ -0,0 +1,76 @@
+using System.Collections.ObjectModel;
+using System.Net.Http;
+using System.Threading;
+using System.Threading.Tasks;
+using System.Web.Http.Controllers;
+using System.Web.Http.Filters;
+using Moq;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Http.Tracing.Tracers
+{
+ public class AuthorizationFilterTracerTest
+ {
+ [Fact]
+ public void ExecuteAuthorizationFilterAsync_Traces()
+ {
+ // Arrange
+ HttpResponseMessage response = new HttpResponseMessage();
+ Mock<IAuthorizationFilter> mockFilter = new Mock<IAuthorizationFilter>() { CallBase = true };
+ mockFilter.Setup(f => f.ExecuteAuthorizationFilterAsync(It.IsAny<HttpActionContext>(), It.IsAny<CancellationToken>(), It.IsAny<Func<Task<HttpResponseMessage>>>())).Returns(TaskHelpers.FromResult(response));
+ Mock<HttpActionDescriptor> mockActionDescriptor = new Mock<HttpActionDescriptor>() { CallBase = true };
+ mockActionDescriptor.Setup(a => a.ActionName).Returns("test");
+ mockActionDescriptor.Setup(a => a.GetParameters()).Returns(new Collection<HttpParameterDescriptor>(new HttpParameterDescriptor[0]));
+ HttpActionContext actionContext = ContextUtil.CreateActionContext(actionDescriptor: mockActionDescriptor.Object);
+ Func<Task<HttpResponseMessage>> continuation = () => TaskHelpers.FromResult<HttpResponseMessage>(new HttpResponseMessage());
+ TestTraceWriter traceWriter = new TestTraceWriter();
+ AuthorizationFilterTracer tracer = new AuthorizationFilterTracer(mockFilter.Object, traceWriter);
+ TraceRecord[] expectedTraces = new TraceRecord[]
+ {
+ new TraceRecord(actionContext.Request, TraceCategories.FiltersCategory, TraceLevel.Info) { Kind = TraceKind.Begin, Operation = "ExecuteAuthorizationFilterAsync" },
+ new TraceRecord(actionContext.Request, TraceCategories.FiltersCategory, TraceLevel.Info) { Kind = TraceKind.End, Operation = "ExecuteAuthorizationFilterAsync" },
+ };
+
+ // Act
+ Task task = ((IAuthorizationFilter)tracer).ExecuteAuthorizationFilterAsync(actionContext, CancellationToken.None, continuation);
+ task.Wait();
+
+ // Assert
+ Assert.Equal<TraceRecord>(expectedTraces, traceWriter.Traces, new TraceRecordComparer());
+ }
+
+ [Fact]
+ public void ExecuteAuthorizationFilterAsync_Faults_And_Traces_When_Inner_Faults()
+ {
+ // Arrange
+ Mock<IAuthorizationFilter> mockAttr = new Mock<IAuthorizationFilter>() { CallBase = true };
+ HttpResponseMessage response = new HttpResponseMessage();
+ InvalidOperationException exception = new InvalidOperationException("test");
+ TaskCompletionSource<HttpResponseMessage> tcs = new TaskCompletionSource<HttpResponseMessage>(response);
+ tcs.TrySetException(exception);
+ mockAttr.Setup(a => a.ExecuteAuthorizationFilterAsync(It.IsAny<HttpActionContext>(), It.IsAny<CancellationToken>(), It.IsAny<Func<Task<HttpResponseMessage>>>())).Returns(tcs.Task);
+ Mock<HttpActionDescriptor> mockActionDescriptor = new Mock<HttpActionDescriptor>() { CallBase = true };
+ mockActionDescriptor.Setup(a => a.ActionName).Returns("test");
+ mockActionDescriptor.Setup(a => a.GetParameters()).Returns(new Collection<HttpParameterDescriptor>(new HttpParameterDescriptor[0]));
+ HttpActionContext actionContext = ContextUtil.CreateActionContext(actionDescriptor: mockActionDescriptor.Object);
+ Func<Task<HttpResponseMessage>> continuation = () => TaskHelpers.FromResult<HttpResponseMessage>(response);
+ TestTraceWriter traceWriter = new TestTraceWriter();
+ AuthorizationFilterTracer tracer = new AuthorizationFilterTracer(mockAttr.Object, traceWriter);
+ TraceRecord[] expectedTraces = new TraceRecord[]
+ {
+ new TraceRecord(actionContext.Request, TraceCategories.FiltersCategory, TraceLevel.Info) { Kind = TraceKind.Begin, Operation = "ExecuteAuthorizationFilterAsync" },
+ new TraceRecord(actionContext.Request, TraceCategories.FiltersCategory, TraceLevel.Error) { Kind = TraceKind.End, Operation = "ExecuteAuthorizationFilterAsync" }
+ };
+
+ // Act & Assert
+ Task task = ((IAuthorizationFilter)tracer).ExecuteAuthorizationFilterAsync(actionContext, CancellationToken.None, continuation);
+ Exception thrown = Assert.Throws<InvalidOperationException>(() => task.Wait());
+
+ // Assert
+ Assert.Same(exception, thrown);
+ Assert.Same(exception, traceWriter.Traces[1].Exception);
+ Assert.Equal<TraceRecord>(expectedTraces, traceWriter.Traces, new TraceRecordComparer());
+ }
+ }
+}
diff --git a/test/System.Web.Http.Test/Tracing/Tracers/ContentNegotiatorTracerTest.cs b/test/System.Web.Http.Test/Tracing/Tracers/ContentNegotiatorTracerTest.cs
new file mode 100644
index 00000000..0d5f84d1
--- /dev/null
+++ b/test/System.Web.Http.Test/Tracing/Tracers/ContentNegotiatorTracerTest.cs
@@ -0,0 +1,227 @@
+using System.Collections.Generic;
+using System.Net.Http;
+using System.Net.Http.Formatting;
+using System.Net.Http.Headers;
+using Moq;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Http.Tracing.Tracers
+{
+ public class ContentNegotiatorTracerTest
+ {
+ [Fact]
+ public void Negotiate_Calls_Inner_Negotiate()
+ {
+ // Arrange
+ MediaTypeHeaderValue innerSelectedMediaType = null;
+ MediaTypeHeaderValue outerSelectedMediaType = null;
+ bool negotiateCalled = false;
+ HttpRequestMessage request = new HttpRequestMessage();
+ Mock<IContentNegotiator> mockNegotiator = new Mock<IContentNegotiator>();
+ mockNegotiator.Setup(
+ n =>
+ n.Negotiate(It.IsAny<Type>(), It.IsAny<HttpRequestMessage>(),
+ It.IsAny<IEnumerable<MediaTypeFormatter>>(), out innerSelectedMediaType)).Callback(
+ () => {negotiateCalled = true;});
+
+ ContentNegotiatorTracer tracer = new ContentNegotiatorTracer(mockNegotiator.Object, new TestTraceWriter());
+
+ // Act
+ ((IContentNegotiator) tracer).Negotiate(typeof (int), request, new MediaTypeFormatter[0], out outerSelectedMediaType);
+
+ // Assert
+ Assert.True(negotiateCalled);
+ }
+
+ [Fact]
+ public void Negotiate_Returns_Inner_MediaType()
+ {
+ // Arrange
+ MediaTypeHeaderValue expectedMediaType = new MediaTypeHeaderValue("application/xml");
+ MediaTypeHeaderValue innerSelectedMediaType = expectedMediaType;
+ MediaTypeHeaderValue outerSelectedMediaType = null;
+ HttpRequestMessage request = new HttpRequestMessage();
+ Mock<IContentNegotiator> mockNegotiator = new Mock<IContentNegotiator>();
+ mockNegotiator.Setup(
+ n =>
+ n.Negotiate(It.IsAny<Type>(), It.IsAny<HttpRequestMessage>(),
+ It.IsAny<IEnumerable<MediaTypeFormatter>>(), out innerSelectedMediaType));
+
+ ContentNegotiatorTracer tracer = new ContentNegotiatorTracer(mockNegotiator.Object, new TestTraceWriter());
+
+ // Act
+ ((IContentNegotiator)tracer).Negotiate(typeof(int), request, new MediaTypeFormatter[0], out outerSelectedMediaType);
+
+ // Assert
+ Assert.Same(expectedMediaType, outerSelectedMediaType);
+ }
+
+ [Fact]
+ public void Negotiate_Returns_Wrapped_Inner_XmlFormatter()
+ {
+ // Arrange
+ MediaTypeHeaderValue mediaType = null;
+ MediaTypeFormatter expectedFormatter = new XmlMediaTypeFormatter();
+ HttpRequestMessage request = new HttpRequestMessage();
+ Mock<IContentNegotiator> mockNegotiator = new Mock<IContentNegotiator>();
+ mockNegotiator.Setup(
+ n =>
+ n.Negotiate(It.IsAny<Type>(), It.IsAny<HttpRequestMessage>(),
+ It.IsAny<IEnumerable<MediaTypeFormatter>>(), out mediaType)).Returns(
+ expectedFormatter);
+ ContentNegotiatorTracer tracer = new ContentNegotiatorTracer(mockNegotiator.Object, new TestTraceWriter());
+
+ // Act
+ var actualFormatter = ((IContentNegotiator)tracer).Negotiate(typeof(int), request, new MediaTypeFormatter[0], out mediaType);
+
+ // Assert
+ Assert.IsType<XmlMediaTypeFormatterTracer>(actualFormatter);
+ }
+
+ [Fact]
+ public void Negotiate_Returns_Wrapped_Inner_JsonFormatter()
+ {
+ // Arrange
+ MediaTypeHeaderValue mediaType = null;
+ MediaTypeFormatter expectedFormatter = new JsonMediaTypeFormatter();
+ HttpRequestMessage request = new HttpRequestMessage();
+ Mock<IContentNegotiator> mockNegotiator = new Mock<IContentNegotiator>();
+ mockNegotiator.Setup(
+ n =>
+ n.Negotiate(It.IsAny<Type>(), It.IsAny<HttpRequestMessage>(),
+ It.IsAny<IEnumerable<MediaTypeFormatter>>(), out mediaType)).Returns(
+ expectedFormatter);
+ ContentNegotiatorTracer tracer = new ContentNegotiatorTracer(mockNegotiator.Object, new TestTraceWriter());
+
+ // Act
+ var actualFormatter = ((IContentNegotiator)tracer).Negotiate(typeof(int), request, new MediaTypeFormatter[0], out mediaType);
+
+ // Assert
+ Assert.IsType<JsonMediaTypeFormatterTracer>(actualFormatter);
+ }
+
+ [Fact]
+ public void Negotiate_Returns_Wrapped_Inner_FormUrlEncodedFormatter()
+ {
+ // Arrange
+ MediaTypeHeaderValue mediaType = null;
+ MediaTypeFormatter expectedFormatter = new FormUrlEncodedMediaTypeFormatter();
+ HttpRequestMessage request = new HttpRequestMessage();
+ Mock<IContentNegotiator> mockNegotiator = new Mock<IContentNegotiator>();
+ mockNegotiator.Setup(
+ n =>
+ n.Negotiate(It.IsAny<Type>(), It.IsAny<HttpRequestMessage>(),
+ It.IsAny<IEnumerable<MediaTypeFormatter>>(), out mediaType)).Returns(
+ expectedFormatter);
+ ContentNegotiatorTracer tracer = new ContentNegotiatorTracer(mockNegotiator.Object, new TestTraceWriter());
+
+ // Act
+ var actualFormatter = ((IContentNegotiator)tracer).Negotiate(typeof(int), request, new MediaTypeFormatter[0], out mediaType);
+
+ // Assert
+ Assert.IsType<FormUrlEncodedMediaTypeFormatterTracer>(actualFormatter);
+ }
+
+ [Fact]
+ public void Negotiate_Returns_Null_Inner_Formatter()
+ {
+ // Arrange
+ MediaTypeHeaderValue mediaType = null;
+ HttpRequestMessage request = new HttpRequestMessage();
+ Mock<IContentNegotiator> mockNegotiator = new Mock<IContentNegotiator>();
+ mockNegotiator.Setup(
+ n =>
+ n.Negotiate(It.IsAny<Type>(), It.IsAny<HttpRequestMessage>(),
+ It.IsAny<IEnumerable<MediaTypeFormatter>>(), out mediaType)).Returns(
+ (MediaTypeFormatter) null);
+ ContentNegotiatorTracer tracer = new ContentNegotiatorTracer(mockNegotiator.Object, new TestTraceWriter());
+
+ // Act
+ var actualFormatter = ((IContentNegotiator)tracer).Negotiate(typeof(int), request, new MediaTypeFormatter[0], out mediaType);
+
+ // Assert
+ Assert.Null(actualFormatter);
+ }
+
+ [Fact]
+ public void Negotiate_Traces_BeginEnd()
+ {
+ // Arrange
+ MediaTypeHeaderValue mediaType = null;
+ MediaTypeFormatter expectedFormatter = new XmlMediaTypeFormatter();
+ HttpRequestMessage request = new HttpRequestMessage();
+ Mock<IContentNegotiator> mockNegotiator = new Mock<IContentNegotiator>();
+ mockNegotiator.Setup(
+ n =>
+ n.Negotiate(It.IsAny<Type>(), It.IsAny<HttpRequestMessage>(),
+ It.IsAny<IEnumerable<MediaTypeFormatter>>(), out mediaType)).Returns(
+ expectedFormatter);
+ TestTraceWriter traceWriter = new TestTraceWriter();
+ ContentNegotiatorTracer tracer = new ContentNegotiatorTracer(mockNegotiator.Object, traceWriter);
+ TraceRecord[] expectedTraces = new TraceRecord[]
+ {
+ new TraceRecord(request, TraceCategories.FormattingCategory, TraceLevel.Info) { Kind = TraceKind.Begin },
+ new TraceRecord(request, TraceCategories.FormattingCategory, TraceLevel.Info) { Kind = TraceKind.End }
+ };
+
+ // Act
+ ((IContentNegotiator)tracer).Negotiate(typeof(int), request, new MediaTypeFormatter[0], out mediaType);
+
+ // Assert
+ Assert.Equal<TraceRecord>(expectedTraces, traceWriter.Traces, new TraceRecordComparer());
+ }
+
+ [Fact]
+ public void Negotiate_Throws_When_Inner_Throws()
+ {
+ // Arrange
+ MediaTypeHeaderValue mediaType = null;
+ MediaTypeFormatter expectedFormatter = new XmlMediaTypeFormatter();
+ HttpRequestMessage request = new HttpRequestMessage();
+ InvalidOperationException expectedException = new InvalidOperationException("test");
+ Mock<IContentNegotiator> mockNegotiator = new Mock<IContentNegotiator>();
+ mockNegotiator.Setup(
+ n =>
+ n.Negotiate(It.IsAny<Type>(), It.IsAny<HttpRequestMessage>(),
+ It.IsAny<IEnumerable<MediaTypeFormatter>>(), out mediaType)).Throws(expectedException);
+ TestTraceWriter traceWriter = new TestTraceWriter();
+ ContentNegotiatorTracer tracer = new ContentNegotiatorTracer(mockNegotiator.Object, traceWriter);
+
+ // Act & Assert
+ InvalidOperationException actualException = Assert.Throws<InvalidOperationException>(() => ((IContentNegotiator)tracer).Negotiate(typeof(int), request, new MediaTypeFormatter[0], out mediaType));
+
+ // Assert
+ Assert.Same(expectedException, actualException);
+ }
+
+ [Fact]
+ public void Negotiate_Traces_BeginEnd_When_Inner_Throws()
+ {
+ // Arrange
+ MediaTypeHeaderValue mediaType = null;
+ MediaTypeFormatter expectedFormatter = new XmlMediaTypeFormatter();
+ HttpRequestMessage request = new HttpRequestMessage();
+ InvalidOperationException expectedException = new InvalidOperationException("test");
+ Mock<IContentNegotiator> mockNegotiator = new Mock<IContentNegotiator>();
+ mockNegotiator.Setup(
+ n =>
+ n.Negotiate(It.IsAny<Type>(), It.IsAny<HttpRequestMessage>(),
+ It.IsAny<IEnumerable<MediaTypeFormatter>>(), out mediaType)).Throws(expectedException);
+ TestTraceWriter traceWriter = new TestTraceWriter();
+ ContentNegotiatorTracer tracer = new ContentNegotiatorTracer(mockNegotiator.Object, traceWriter);
+ TraceRecord[] expectedTraces = new TraceRecord[]
+ {
+ new TraceRecord(request, TraceCategories.FormattingCategory, TraceLevel.Info) { Kind = TraceKind.Begin },
+ new TraceRecord(request, TraceCategories.FormattingCategory, TraceLevel.Error) { Kind = TraceKind.End }
+ };
+
+ // Act & Assert
+ Assert.Throws<InvalidOperationException>(() => ((IContentNegotiator)tracer).Negotiate(typeof(int), request, new MediaTypeFormatter[0], out mediaType));
+
+ // Assert
+ Assert.Equal<TraceRecord>(expectedTraces, traceWriter.Traces, new TraceRecordComparer());
+ Assert.Same(expectedException, traceWriter.Traces[1].Exception);
+ }
+ }
+}
diff --git a/test/System.Web.Http.Test/Tracing/Tracers/ExceptionFilterAttributeTracerTest.cs b/test/System.Web.Http.Test/Tracing/Tracers/ExceptionFilterAttributeTracerTest.cs
new file mode 100644
index 00000000..bed138c1
--- /dev/null
+++ b/test/System.Web.Http.Test/Tracing/Tracers/ExceptionFilterAttributeTracerTest.cs
@@ -0,0 +1,74 @@
+using System.Collections.ObjectModel;
+using System.Net.Http;
+using System.Threading;
+using System.Threading.Tasks;
+using System.Web.Http.Controllers;
+using System.Web.Http.Filters;
+using Moq;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Http.Tracing.Tracers
+{
+ public class ExceptionFilterAttributeTracerTest
+ {
+ [Fact]
+ public void ExecuteExceptionFilterAsync_Traces()
+ {
+ // Arrange
+ HttpRequestMessage request = new HttpRequestMessage();
+ HttpResponseMessage response = new HttpResponseMessage();
+ Mock<ExceptionFilterAttribute> mockAttr = new Mock<ExceptionFilterAttribute>() { CallBase = true };
+ Mock<HttpActionDescriptor> mockActionDescriptor = new Mock<HttpActionDescriptor>() { CallBase = true };
+ mockActionDescriptor.Setup(a => a.ActionName).Returns("test");
+ mockActionDescriptor.Setup(a => a.GetParameters()).Returns(new Collection<HttpParameterDescriptor>(new HttpParameterDescriptor[0]));
+ HttpActionExecutedContext actionExecutedContext = ContextUtil.GetActionExecutedContext(request, response);
+ TestTraceWriter traceWriter = new TestTraceWriter();
+ ExceptionFilterAttributeTracer tracer = new ExceptionFilterAttributeTracer(mockAttr.Object, traceWriter);
+ TraceRecord[] expectedTraces = new TraceRecord[]
+ {
+ new TraceRecord(request, TraceCategories.FiltersCategory, TraceLevel.Info) { Kind = TraceKind.Begin, Operation = "OnException" },
+ new TraceRecord(request, TraceCategories.FiltersCategory, TraceLevel.Info) { Kind = TraceKind.End, Operation = "OnException" },
+ };
+
+ // Act
+ Task task = ((IExceptionFilter)tracer).ExecuteExceptionFilterAsync(actionExecutedContext, CancellationToken.None);
+ task.Wait();
+
+ // Assert
+ Assert.Equal<TraceRecord>(expectedTraces, traceWriter.Traces, new TraceRecordComparer());
+ }
+
+ [Fact]
+ public void ExecuteExceptionFilterAsync_Throws_And_Traces_When_Inner_OnException_Throws()
+ {
+ // Arrange
+ HttpRequestMessage request = new HttpRequestMessage();
+ HttpResponseMessage response = new HttpResponseMessage();
+ Mock<ExceptionFilterAttribute> mockAttr = new Mock<ExceptionFilterAttribute>() { CallBase = true };
+ InvalidOperationException exception = new InvalidOperationException("test");
+ mockAttr.Setup(a => a.OnException(It.IsAny<HttpActionExecutedContext>())).Throws(exception);
+ Mock<HttpActionDescriptor> mockActionDescriptor = new Mock<HttpActionDescriptor>() { CallBase = true };
+ mockActionDescriptor.Setup(a => a.ActionName).Returns("test");
+ mockActionDescriptor.Setup(a => a.GetParameters()).Returns(new Collection<HttpParameterDescriptor>(new HttpParameterDescriptor[0]));
+ HttpActionExecutedContext actionExecutedContext = ContextUtil.GetActionExecutedContext(request, response);
+ TestTraceWriter traceWriter = new TestTraceWriter();
+ ExceptionFilterAttributeTracer tracer = new ExceptionFilterAttributeTracer(mockAttr.Object, traceWriter);
+ TraceRecord[] expectedTraces = new TraceRecord[]
+ {
+ new TraceRecord(request, TraceCategories.FiltersCategory, TraceLevel.Info) { Kind = TraceKind.Begin, Operation = "OnException" },
+ new TraceRecord(request, TraceCategories.FiltersCategory, TraceLevel.Error) { Kind = TraceKind.End, Operation = "OnException" }
+ };
+
+ // Act
+ Exception thrown =
+ Assert.Throws<InvalidOperationException>(
+ () => ((IExceptionFilter) tracer).ExecuteExceptionFilterAsync(actionExecutedContext, CancellationToken.None));
+
+ // Assert
+ Assert.Same(exception, thrown);
+ Assert.Same(exception, traceWriter.Traces[1].Exception);
+ Assert.Equal<TraceRecord>(expectedTraces, traceWriter.Traces, new TraceRecordComparer());
+ }
+ }
+}
diff --git a/test/System.Web.Http.Test/Tracing/Tracers/ExceptionFilterTracerTest.cs b/test/System.Web.Http.Test/Tracing/Tracers/ExceptionFilterTracerTest.cs
new file mode 100644
index 00000000..aedd4e78
--- /dev/null
+++ b/test/System.Web.Http.Test/Tracing/Tracers/ExceptionFilterTracerTest.cs
@@ -0,0 +1,71 @@
+using System.Net.Http;
+using System.Threading;
+using System.Threading.Tasks;
+using System.Web.Http.Filters;
+using Moq;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Http.Tracing.Tracers
+{
+ public class ExceptionFilterTracerTest
+ {
+ [Fact]
+ public void ExecuteExceptionFilterAsync_Traces()
+ {
+ // Arrange
+ HttpRequestMessage request = new HttpRequestMessage();
+ HttpResponseMessage response = new HttpResponseMessage();
+ Mock<IExceptionFilter> mockFilter = new Mock<IExceptionFilter>() { CallBase = true };
+ mockFilter.Setup(
+ f => f.ExecuteExceptionFilterAsync(It.IsAny<HttpActionExecutedContext>(), It.IsAny<CancellationToken>())).
+ Returns(TaskHelpers.Completed());
+ HttpActionExecutedContext actionExecutedContext = ContextUtil.GetActionExecutedContext(request, response);
+ TestTraceWriter traceWriter = new TestTraceWriter();
+ ExceptionFilterTracer tracer = new ExceptionFilterTracer(mockFilter.Object, traceWriter);
+ TraceRecord[] expectedTraces = new TraceRecord[]
+ {
+ new TraceRecord(request, TraceCategories.FiltersCategory, TraceLevel.Info) { Kind = TraceKind.Begin, Operation = "ExecuteExceptionFilterAsync" },
+ new TraceRecord(request, TraceCategories.FiltersCategory, TraceLevel.Info) { Kind = TraceKind.End, Operation = "ExecuteExceptionFilterAsync" },
+ };
+
+ // Act
+ Task task = ((IExceptionFilter)tracer).ExecuteExceptionFilterAsync(actionExecutedContext, CancellationToken.None);
+ task.Wait();
+
+ // Assert
+ Assert.Equal<TraceRecord>(expectedTraces, traceWriter.Traces, new TraceRecordComparer());
+ }
+
+ [Fact]
+ public void ExecuteExceptionFilterAsync_Faults_And_Traces_When_Inner_Faults()
+ {
+ // Arrange
+ HttpRequestMessage request = new HttpRequestMessage();
+ HttpResponseMessage response = new HttpResponseMessage();
+ Mock<IExceptionFilter> mockFilter = new Mock<IExceptionFilter>() { CallBase = true };
+ InvalidOperationException exception = new InvalidOperationException("test");
+ TaskCompletionSource<object> tcs = new TaskCompletionSource<object>(null);
+ tcs.TrySetException(exception);
+ mockFilter.Setup(a => a.ExecuteExceptionFilterAsync(It.IsAny<HttpActionExecutedContext>(), It.IsAny<CancellationToken>())).Returns(tcs.Task);
+ HttpActionExecutedContext actionExecutedContext = ContextUtil.GetActionExecutedContext(request, response);
+ TestTraceWriter traceWriter = new TestTraceWriter();
+ ExceptionFilterTracer tracer = new ExceptionFilterTracer(mockFilter.Object, traceWriter);
+ TraceRecord[] expectedTraces = new TraceRecord[]
+ {
+ new TraceRecord(request, TraceCategories.FiltersCategory, TraceLevel.Info) { Kind = TraceKind.Begin, Operation = "ExecuteExceptionFilterAsync" },
+ new TraceRecord(request, TraceCategories.FiltersCategory, TraceLevel.Error) { Kind = TraceKind.End, Operation = "ExecuteExceptionFilterAsync" }
+ };
+
+ // Act
+ Task task = ((IExceptionFilter)tracer).ExecuteExceptionFilterAsync(actionExecutedContext, CancellationToken.None);
+
+
+ // Assert
+ Exception thrown = Assert.Throws<InvalidOperationException>(() => task.Wait());
+ Assert.Same(exception, thrown);
+ Assert.Same(exception, traceWriter.Traces[1].Exception);
+ Assert.Equal<TraceRecord>(expectedTraces, traceWriter.Traces, new TraceRecordComparer());
+ }
+ }
+}
diff --git a/test/System.Web.Http.Test/Tracing/Tracers/FilterTracerTest.cs b/test/System.Web.Http.Test/Tracing/Tracers/FilterTracerTest.cs
new file mode 100644
index 00000000..76fe50cf
--- /dev/null
+++ b/test/System.Web.Http.Test/Tracing/Tracers/FilterTracerTest.cs
@@ -0,0 +1,274 @@
+using System.Linq;
+using System.Net.Http;
+using System.Threading;
+using System.Threading.Tasks;
+using System.Web.Http.Filters;
+using Moq;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+using System.Web.Http.Controllers;
+
+namespace System.Web.Http.Tracing.Tracers
+{
+ public class FilterTracerTest
+ {
+ [Fact]
+ public void CreateFilterTracers_IFilter_With_IFilter_Returns_Single_Wrapped_IFilter()
+ {
+ // Arrange
+ Mock<IFilter> mockFilter = new Mock<IFilter>();
+
+ // Act
+ IFilter[] wrappedFilters = FilterTracer.CreateFilterTracers(mockFilter.Object, new TestTraceWriter()).ToArray();
+
+ // Assert
+ Assert.Equal(1, wrappedFilters.Length);
+ Assert.IsType<FilterTracer>(wrappedFilters[0]);
+ }
+
+ [Fact]
+ public void CreateFilterTracers_IFilter_With_IActionFilter_Returns_Single_Wrapped_IActionFilter()
+ {
+ // Arrange
+ Mock<IActionFilter> mockFilter = new Mock<IActionFilter>();
+
+ // Act
+ IFilter[] wrappedFilters = FilterTracer.CreateFilterTracers(mockFilter.Object, new TestTraceWriter()).ToArray();
+
+ // Assert
+ Assert.Equal(1, wrappedFilters.Length);
+ Assert.IsType<ActionFilterTracer>(wrappedFilters[0]);
+ }
+
+ [Fact]
+ public void CreateFilterTracers_IFilter_With_IExceptionFilter_Returns_Single_Wrapped_IExceptionFilter()
+ {
+ // Arrange
+ Mock<IExceptionFilter> mockFilter = new Mock<IExceptionFilter>();
+
+ // Act
+ IFilter[] wrappedFilters = FilterTracer.CreateFilterTracers(mockFilter.Object, new TestTraceWriter()).ToArray();
+
+ // Assert
+ Assert.Equal(1, wrappedFilters.Length);
+ Assert.IsType<ExceptionFilterTracer>(wrappedFilters[0]);
+ }
+
+ [Fact]
+ public void CreateFilterTracers_IFilter_With_IAuthorizationFilter_Returns_Single_Wrapped_IAuthorizationFilter()
+ {
+ // Arrange
+ Mock<IAuthorizationFilter> mockFilter = new Mock<IAuthorizationFilter>();
+
+ // Act
+ IFilter[] wrappedFilters = FilterTracer.CreateFilterTracers(mockFilter.Object, new TestTraceWriter()).ToArray();
+
+ // Assert
+ Assert.Equal(1, wrappedFilters.Length);
+ Assert.IsType<AuthorizationFilterTracer>(wrappedFilters[0]);
+ }
+
+ [Fact]
+ public void CreateFilterTracers_IFilter_With_ActionFilterAttribute_Returns_Single_Wrapped_Filter()
+ {
+ // Arrange
+ Mock<ActionFilterAttribute> mockFilter = new Mock<ActionFilterAttribute>();
+
+ // Act
+ IFilter[] wrappedFilters = FilterTracer.CreateFilterTracers(mockFilter.Object, new TestTraceWriter()).ToArray();
+
+ // Assert
+ Assert.Equal(1, wrappedFilters.Length);
+ Assert.IsType<ActionFilterAttributeTracer>(wrappedFilters[0]);
+ }
+
+ [Fact]
+ public void CreateFilterTracers_IFilter_With_ExceptionFilterAttribute_Returns_Single_Wrapped_Filter()
+ {
+ // Arrange
+ Mock<ExceptionFilterAttribute> mockFilter = new Mock<ExceptionFilterAttribute>();
+
+ // Act
+ IFilter[] wrappedFilters = FilterTracer.CreateFilterTracers(mockFilter.Object, new TestTraceWriter()).ToArray();
+
+ // Assert
+ Assert.Equal(1, wrappedFilters.Length);
+ Assert.IsType<ExceptionFilterAttributeTracer>(wrappedFilters[0]);
+ }
+
+ [Fact]
+ public void CreateFilterTracers_IFilter_With_AuthorizationFilterAttribute_Returns_Single_Wrapped_Filter()
+ {
+ // Arrange
+ Mock<AuthorizationFilterAttribute> mockFilter = new Mock<AuthorizationFilterAttribute>();
+
+ // Act
+ IFilter[] wrappedFilters = FilterTracer.CreateFilterTracers(mockFilter.Object, new TestTraceWriter()).ToArray();
+
+ // Assert
+ Assert.Equal(1, wrappedFilters.Length);
+ Assert.IsType<AuthorizationFilterAttributeTracer>(wrappedFilters[0]);
+ }
+
+ [Fact]
+ public void CreateFilterTracers_IFilter_With_All_Filter_Interfaces_Returns_3_Wrapped_Filters()
+ {
+ // Arrange
+ IFilter filter = new TestFilterAllBehaviors();
+
+ // Act
+ IFilter[] wrappedFilters = FilterTracer.CreateFilterTracers(filter, new TestTraceWriter()).ToArray();
+
+ // Assert
+ Assert.Equal(3, wrappedFilters.Length);
+ Assert.Equal(1, wrappedFilters.OfType<ActionFilterTracer>().Count());
+ Assert.Equal(1, wrappedFilters.OfType<AuthorizationFilterTracer>().Count());
+ Assert.Equal(1, wrappedFilters.OfType<ExceptionFilterTracer>().Count());
+ }
+
+ [Fact]
+ public void CreateFilterTracers_With_IFilter_Returns_Single_Wrapped_IFilter()
+ {
+ // Arrange
+ Mock<IFilter> mockFilter = new Mock<IFilter>();
+ FilterInfo filter = new FilterInfo(mockFilter.Object, FilterScope.First);
+
+ // Act
+ FilterInfo[] wrappedFilters = FilterTracer.CreateFilterTracers(filter, new TestTraceWriter()).ToArray();
+
+ // Assert
+ Assert.Equal(1, wrappedFilters.Length);
+ Assert.IsType<FilterTracer>(wrappedFilters[0].Instance);
+ }
+
+ [Fact]
+ public void CreateFilterTracers_With_IActionFilter_Returns_Single_Wrapped_IActionFilter()
+ {
+ // Arrange
+ Mock<IActionFilter> mockFilter = new Mock<IActionFilter>();
+ FilterInfo filter = new FilterInfo(mockFilter.Object, FilterScope.First);
+
+ // Act
+ FilterInfo[] wrappedFilters = FilterTracer.CreateFilterTracers(filter, new TestTraceWriter()).ToArray();
+
+ // Assert
+ Assert.Equal(1, wrappedFilters.Length);
+ Assert.IsType<ActionFilterTracer>(wrappedFilters[0].Instance);
+ }
+
+ [Fact]
+ public void CreateFilterTracers_With_IExceptionFilter_Returns_Single_Wrapped_IExceptionFilter()
+ {
+ // Arrange
+ Mock<IExceptionFilter> mockFilter = new Mock<IExceptionFilter>();
+ FilterInfo filter = new FilterInfo(mockFilter.Object, FilterScope.First);
+
+ // Act
+ FilterInfo[] wrappedFilters = FilterTracer.CreateFilterTracers(filter, new TestTraceWriter()).ToArray();
+
+ // Assert
+ Assert.Equal(1, wrappedFilters.Length);
+ Assert.IsType<ExceptionFilterTracer>(wrappedFilters[0].Instance);
+ }
+
+ [Fact]
+ public void CreateFilterTracers_With_IAuthorizationFilter_Returns_Single_Wrapped_IAuthorizationFilter()
+ {
+ // Arrange
+ Mock<IAuthorizationFilter> mockFilter = new Mock<IAuthorizationFilter>();
+ FilterInfo filter = new FilterInfo(mockFilter.Object, FilterScope.First);
+
+ // Act
+ FilterInfo[] wrappedFilters = FilterTracer.CreateFilterTracers(filter, new TestTraceWriter()).ToArray();
+
+ // Assert
+ Assert.Equal(1, wrappedFilters.Length);
+ Assert.IsType<AuthorizationFilterTracer>(wrappedFilters[0].Instance);
+ }
+
+ [Fact]
+ public void CreateFilterTracers_With_ActionFilterAttribute_Returns_2_Wrapped_Filters()
+ {
+ // Arrange
+ Mock<ActionFilterAttribute> mockFilter = new Mock<ActionFilterAttribute>();
+ FilterInfo filter = new FilterInfo(mockFilter.Object, FilterScope.First);
+
+ // Act
+ FilterInfo[] wrappedFilters = FilterTracer.CreateFilterTracers(filter, new TestTraceWriter()).ToArray();
+
+ // Assert
+ Assert.Equal(1, wrappedFilters.Length);
+ Assert.IsType<ActionFilterAttributeTracer>(wrappedFilters[0].Instance);
+ }
+
+ [Fact]
+ public void CreateFilterTracers_With_ExceptionFilterAttribute_Returns_2_Wrapped_Filters()
+ {
+ // Arrange
+ Mock<ExceptionFilterAttribute> mockFilter = new Mock<ExceptionFilterAttribute>();
+ FilterInfo filter = new FilterInfo(mockFilter.Object, FilterScope.First);
+
+ // Act
+ FilterInfo[] wrappedFilters = FilterTracer.CreateFilterTracers(filter, new TestTraceWriter()).ToArray();
+
+ // Assert
+ Assert.Equal(1, wrappedFilters.Length);
+ Assert.IsType<ExceptionFilterAttributeTracer>(wrappedFilters[0].Instance);
+ }
+
+ [Fact]
+ public void CreateFilterTracers_With_AuthorizationFilterAttribute_Returns_2_Wrapped_Filters()
+ {
+ // Arrange
+ Mock<AuthorizationFilterAttribute> mockFilter = new Mock<AuthorizationFilterAttribute>();
+ FilterInfo filter = new FilterInfo(mockFilter.Object, FilterScope.First);
+
+ // Act
+ FilterInfo[] wrappedFilters = FilterTracer.CreateFilterTracers(filter, new TestTraceWriter()).ToArray();
+
+ // Assert
+ Assert.Equal(1, wrappedFilters.Length);
+ Assert.IsType<AuthorizationFilterAttributeTracer>(wrappedFilters[0].Instance); ;
+ }
+
+ [Fact]
+ public void CreateFilterTracers_With_All_Filter_Interfaces_Returns_3_Wrapped_Filters()
+ {
+ // Arrange
+ FilterInfo filter = new FilterInfo(new TestFilterAllBehaviors(), FilterScope.First);
+
+ // Act
+ FilterInfo[] wrappedFilters = FilterTracer.CreateFilterTracers(filter, new TestTraceWriter()).ToArray();
+
+ // Assert
+ Assert.Equal(3, wrappedFilters.Length);
+ Assert.Equal(1, wrappedFilters.Where(f => f.Instance.GetType() == typeof(ActionFilterTracer)).Count());
+ Assert.Equal(1, wrappedFilters.Where(f => f.Instance.GetType() == typeof(AuthorizationFilterTracer)).Count());
+ Assert.Equal(1, wrappedFilters.Where(f => f.Instance.GetType() == typeof(ExceptionFilterTracer)).Count());
+ }
+
+ // Test filter class that exposes all filter behaviors will cause separate filters for each
+ class TestFilterAllBehaviors : IActionFilter, IExceptionFilter, IAuthorizationFilter
+ {
+ Task<Net.Http.HttpResponseMessage> IActionFilter.ExecuteActionFilterAsync(Controllers.HttpActionContext actionContext, Threading.CancellationToken cancellationToken, Func<Threading.Tasks.Task<Net.Http.HttpResponseMessage>> continuation)
+ {
+ throw new NotImplementedException();
+ }
+
+ bool IFilter.AllowMultiple
+ {
+ get { throw new NotImplementedException(); }
+ }
+
+ Task IExceptionFilter.ExecuteExceptionFilterAsync(HttpActionExecutedContext actionExecutedContext, CancellationToken cancellationToken)
+ {
+ throw new NotImplementedException();
+ }
+
+ Task<HttpResponseMessage> IAuthorizationFilter.ExecuteAuthorizationFilterAsync(HttpActionContext actionContext, CancellationToken cancellationToken, Func<Task<Net.Http.HttpResponseMessage>> continuation)
+ {
+ throw new NotImplementedException();
+ }
+ }
+ }
+}
diff --git a/test/System.Web.Http.Test/Tracing/Tracers/FormatterParameterBindingTracerTest.cs b/test/System.Web.Http.Test/Tracing/Tracers/FormatterParameterBindingTracerTest.cs
new file mode 100644
index 00000000..f09aab48
--- /dev/null
+++ b/test/System.Web.Http.Test/Tracing/Tracers/FormatterParameterBindingTracerTest.cs
@@ -0,0 +1,128 @@
+using System.Net.Http;
+using System.Net.Http.Formatting;
+using System.Net.Http.Headers;
+using System.Threading;
+using System.Threading.Tasks;
+using System.Web.Http.Controllers;
+using System.Web.Http.Metadata;
+using System.Web.Http.Metadata.Providers;
+using System.Web.Http.ModelBinding;
+using Moq;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Http.Tracing.Tracers
+{
+ public class FormatterParameterBindingTracerTest
+ {
+
+ /// <summary>
+ /// This test verifies that our <see cref="FormatterParameterBindingTracer"/>
+ /// intercepts the async bind request and redirects it to use tracing formatters
+ /// correlated to the request.
+ /// </summary>
+ [Fact]
+ public void ExecuteBindingAsync_Traces_And_Invokes_Inner_ReadAsync()
+ {
+ // Arrange
+ Mock<HttpParameterDescriptor> mockParamDescriptor = new Mock<HttpParameterDescriptor>() { CallBase = true };
+ mockParamDescriptor.Setup(d => d.ParameterName).Returns("paramName");
+ mockParamDescriptor.Setup(d => d.ParameterType).Returns(typeof (string));
+ FormatterParameterBinding binding = new FormatterParameterBinding(mockParamDescriptor.Object, new MediaTypeFormatterCollection(), null);
+ TestTraceWriter traceWriter = new TestTraceWriter();
+ FormatterParameterBindingTracer tracer = new FormatterParameterBindingTracer(binding, traceWriter);
+ HttpActionContext actionContext = ContextUtil.CreateActionContext();
+ actionContext.Request.Content = new StringContent("true");
+ actionContext.Request.Content.Headers.ContentType = new MediaTypeHeaderValue("application/json");
+ ModelMetadataProvider metadataProvider = new EmptyModelMetadataProvider();
+
+ TraceRecord[] expectedTraces = new TraceRecord[]
+ {
+ new TraceRecord(actionContext.Request, TraceCategories.ModelBindingCategory, TraceLevel.Info) { Kind = TraceKind.Begin, Operation = "ExecuteBindingAsync" },
+ new TraceRecord(actionContext.Request, TraceCategories.ModelBindingCategory, TraceLevel.Info) { Kind = TraceKind.End, Operation = "ExecuteBindingAsync" }
+ };
+
+ // Act
+ Task task = tracer.ExecuteBindingAsync(metadataProvider, actionContext, CancellationToken.None);
+ task.Wait();
+
+ // Assert
+ Assert.Equal<TraceRecord>(expectedTraces, traceWriter.Traces, new TraceRecordComparer());
+ Assert.Equal("True", actionContext.ActionArguments["paramName"]);
+ }
+
+ [Fact]
+ public void ExecuteBindingAsync_Traces_And_Throws_When_Inner_Throws()
+ {
+ // Arrange
+ Mock<HttpParameterDescriptor> mockParamDescriptor = new Mock<HttpParameterDescriptor>() { CallBase = true };
+ mockParamDescriptor.Setup(d => d.ParameterName).Returns("paramName");
+ mockParamDescriptor.Setup(d => d.ParameterType).Returns(typeof(string));
+ Mock<FormatterParameterBinding> mockBinding = new Mock<FormatterParameterBinding>(mockParamDescriptor.Object, new MediaTypeFormatterCollection(), null) { CallBase = true };
+ InvalidOperationException exception = new InvalidOperationException("test");
+ mockBinding.Setup(
+ b =>
+ b.ExecuteBindingAsync(It.IsAny<ModelMetadataProvider>(), It.IsAny<HttpActionContext>(),
+ It.IsAny<CancellationToken>())).Throws(exception);
+
+ TestTraceWriter traceWriter = new TestTraceWriter();
+ FormatterParameterBindingTracer tracer = new FormatterParameterBindingTracer(mockBinding.Object, traceWriter);
+ HttpActionContext actionContext = ContextUtil.CreateActionContext();
+ ModelMetadataProvider metadataProvider = new EmptyModelMetadataProvider();
+
+ TraceRecord[] expectedTraces = new TraceRecord[]
+ {
+ new TraceRecord(actionContext.Request, TraceCategories.ModelBindingCategory, TraceLevel.Info) { Kind = TraceKind.Begin, Operation = "ExecuteBindingAsync" },
+ new TraceRecord(actionContext.Request, TraceCategories.ModelBindingCategory, TraceLevel.Error) { Kind = TraceKind.End, Operation = "ExecuteBindingAsync" }
+ };
+
+ // Act & Assert
+ Exception thrown = Assert.Throws<InvalidOperationException>(() => tracer.ExecuteBindingAsync(metadataProvider, actionContext, CancellationToken.None));
+
+ // Assert
+ Assert.Same(exception, thrown);
+ Assert.Same(exception, traceWriter.Traces[1].Exception);
+ Assert.Equal<TraceRecord>(expectedTraces, traceWriter.Traces, new TraceRecordComparer());
+ }
+
+ [Fact]
+ public void ExecuteBindingAsync_Traces_And_Faults_When_Inner_Faults()
+ {
+ // Arrange
+
+ Mock<HttpParameterDescriptor> mockParamDescriptor = new Mock<HttpParameterDescriptor>() { CallBase = true };
+ mockParamDescriptor.Setup(d => d.ParameterName).Returns("paramName");
+ mockParamDescriptor.Setup(d => d.ParameterType).Returns(typeof(string));
+ Mock<FormatterParameterBinding> mockBinding = new Mock<FormatterParameterBinding>(mockParamDescriptor.Object, new MediaTypeFormatterCollection(), null) { CallBase = true };
+ InvalidOperationException exception = new InvalidOperationException("test");
+ TaskCompletionSource<object> tcs = new TaskCompletionSource<object>();
+ tcs.TrySetException(exception);
+
+ mockBinding.Setup(
+ b =>
+ b.ExecuteBindingAsync(It.IsAny<ModelMetadataProvider>(), It.IsAny<HttpActionContext>(),
+ It.IsAny<CancellationToken>())).Returns(tcs.Task);
+
+ TestTraceWriter traceWriter = new TestTraceWriter();
+ FormatterParameterBindingTracer tracer = new FormatterParameterBindingTracer(mockBinding.Object, traceWriter);
+ HttpActionContext actionContext = ContextUtil.CreateActionContext();
+ ModelMetadataProvider metadataProvider = new EmptyModelMetadataProvider();
+
+ TraceRecord[] expectedTraces = new TraceRecord[]
+ {
+ new TraceRecord(actionContext.Request, TraceCategories.ModelBindingCategory, TraceLevel.Info) { Kind = TraceKind.Begin, Operation = "ExecuteBindingAsync" },
+ new TraceRecord(actionContext.Request, TraceCategories.ModelBindingCategory, TraceLevel.Error) { Kind = TraceKind.End, Operation = "ExecuteBindingAsync" }
+ };
+
+ // Act & Assert
+ Task task = tracer.ExecuteBindingAsync(metadataProvider, actionContext, CancellationToken.None);
+
+ // Assert
+ Exception thrown = Assert.Throws<InvalidOperationException>(() => task.Wait());
+ Assert.Same(exception, thrown);
+ Assert.Same(exception, traceWriter.Traces[1].Exception);
+ Assert.Equal<TraceRecord>(expectedTraces, traceWriter.Traces, new TraceRecordComparer());
+ }
+
+ }
+}
diff --git a/test/System.Web.Http.Test/Tracing/Tracers/HttpActionBindingTracerTest.cs b/test/System.Web.Http.Test/Tracing/Tracers/HttpActionBindingTracerTest.cs
new file mode 100644
index 00000000..68efce7c
--- /dev/null
+++ b/test/System.Web.Http.Test/Tracing/Tracers/HttpActionBindingTracerTest.cs
@@ -0,0 +1,102 @@
+using System.Collections.ObjectModel;
+using System.Net.Http;
+using System.Threading;
+using System.Threading.Tasks;
+using System.Web.Http.Controllers;
+using System.Web.Http.ModelBinding;
+using Moq;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Http.Tracing.Tracers
+{
+ public class HttpActionBindingTracerTest
+ {
+ private Mock<HttpActionDescriptor> _mockActionDescriptor;
+ private Mock<HttpParameterDescriptor> _mockParameterDescriptor;
+ private Mock<HttpParameterBinding> _mockParameterBinding;
+ private HttpActionBinding _actionBinding;
+ private HttpActionContext _actionContext;
+ private HttpControllerContext _controllerContext;
+ private HttpControllerDescriptor _controllerDescriptor;
+
+ public HttpActionBindingTracerTest()
+ {
+ _mockActionDescriptor = new Mock<HttpActionDescriptor>() { CallBase = true };
+ _mockActionDescriptor.Setup(a => a.ActionName).Returns("test");
+ _mockActionDescriptor.Setup(a => a.GetParameters()).Returns(new Collection<HttpParameterDescriptor>(new HttpParameterDescriptor[0]));
+
+ _mockParameterDescriptor = new Mock<HttpParameterDescriptor>() { CallBase = true };
+ _mockParameterBinding = new Mock<HttpParameterBinding>(_mockParameterDescriptor.Object) { CallBase = true };
+ _actionBinding = new HttpActionBinding(_mockActionDescriptor.Object, new HttpParameterBinding[] { _mockParameterBinding.Object });
+
+ _controllerDescriptor = new HttpControllerDescriptor(new HttpConfiguration(), "controller", typeof(ApiController));
+
+ _controllerContext = ContextUtil.CreateControllerContext(request: new HttpRequestMessage());
+ _controllerContext.ControllerDescriptor = _controllerDescriptor;
+
+ _actionContext = ContextUtil.CreateActionContext(_controllerContext, actionDescriptor: _mockActionDescriptor.Object);
+
+ }
+
+ [Fact]
+ public void BindValuesAsync_Invokes_Inner_And_Traces()
+ {
+ // Arrange
+ bool wasInvoked = false;
+ Mock<HttpActionBinding> mockBinder = new Mock<HttpActionBinding>() { CallBase = true };
+ mockBinder.Setup(b => b.ExecuteBindingAsync(
+ It.IsAny<HttpActionContext>(),
+ It.IsAny<CancellationToken>())).
+ Callback(() => wasInvoked = true).Returns(TaskHelpers.Completed());
+
+ TestTraceWriter traceWriter = new TestTraceWriter();
+ HttpActionBindingTracer tracer = new HttpActionBindingTracer(mockBinder.Object, traceWriter);
+
+ TraceRecord[] expectedTraces = new TraceRecord[]
+ {
+ new TraceRecord(_actionContext.Request, TraceCategories.ModelBindingCategory, TraceLevel.Info) { Kind = TraceKind.Begin },
+ new TraceRecord(_actionContext.Request, TraceCategories.ModelBindingCategory, TraceLevel.Info) { Kind = TraceKind.End }
+ };
+
+ // Act
+ tracer.ExecuteBindingAsync(_actionContext, CancellationToken.None).Wait();
+
+ // Assert
+ Assert.Equal<TraceRecord>(expectedTraces, traceWriter.Traces, new TraceRecordComparer());
+ Assert.True(wasInvoked);
+ }
+
+ [Fact]
+ public void ExecuteBindingAsync_Faults_And_Traces_When_Inner_Faults()
+ {
+ // Arrange
+ InvalidOperationException exception = new InvalidOperationException();
+ TaskCompletionSource<object> tcs = new TaskCompletionSource<object>();
+ tcs.TrySetException(exception);
+ Mock<HttpActionBinding> mockBinder = new Mock<HttpActionBinding>() { CallBase = true };
+ mockBinder.Setup(b => b.ExecuteBindingAsync(
+ It.IsAny<HttpActionContext>(),
+ It.IsAny<CancellationToken>())).
+ Returns(tcs.Task);
+
+ TestTraceWriter traceWriter = new TestTraceWriter();
+ HttpActionBindingTracer tracer = new HttpActionBindingTracer(mockBinder.Object, traceWriter);
+
+ TraceRecord[] expectedTraces = new TraceRecord[]
+ {
+ new TraceRecord(_actionContext.Request, TraceCategories.ModelBindingCategory, TraceLevel.Info) { Kind = TraceKind.Begin },
+ new TraceRecord(_actionContext.Request, TraceCategories.ModelBindingCategory, TraceLevel.Error) { Kind = TraceKind.End }
+ };
+
+ // Act
+ Task task = tracer.ExecuteBindingAsync(_actionContext, CancellationToken.None);
+
+ // Assert
+ Exception thrown = Assert.Throws<InvalidOperationException>(() => task.Wait());
+ Assert.Equal<TraceRecord>(expectedTraces, traceWriter.Traces, new TraceRecordComparer());
+ Assert.Same(exception, thrown);
+ Assert.Same(exception, traceWriter.Traces[1].Exception);
+ }
+ }
+}
diff --git a/test/System.Web.Http.Test/Tracing/Tracers/HttpActionDescriptorTracerTest.cs b/test/System.Web.Http.Test/Tracing/Tracers/HttpActionDescriptorTracerTest.cs
new file mode 100644
index 00000000..3b07adf3
--- /dev/null
+++ b/test/System.Web.Http.Test/Tracing/Tracers/HttpActionDescriptorTracerTest.cs
@@ -0,0 +1,136 @@
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Linq;
+using System.Web.Http.Controllers;
+using System.Web.Http.Filters;
+using Moq;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Http.Tracing.Tracers
+{
+ public class HttpActionDescriptorTracerTest
+ {
+ // This test verifies only one kind of filter is wrapped, proving
+ // the static FilterTracer.CreateFilterTracer was called from GetFilterPipeline.
+ // Deeper testing of FilterTracer.CreateFilterTracer is in FilterTracerTest.
+ [Fact]
+ public void GetFilterPipeline_Returns_Wrapped_Filters()
+ {
+ // Arrange
+ Mock<IFilter> mockFilter = new Mock<IFilter>();
+ FilterInfo filter = new FilterInfo(mockFilter.Object, FilterScope.First);
+ Collection<FilterInfo> filterCollection = new Collection<FilterInfo>(new FilterInfo[] { filter });
+ Mock<HttpActionDescriptor> mockActionDescriptor = new Mock<HttpActionDescriptor>() { CallBase = true };
+ mockActionDescriptor.Setup(a => a.ActionName).Returns("test");
+ mockActionDescriptor.Setup(a => a.GetFilterPipeline()).Returns(filterCollection);
+ HttpControllerContext controllerContext = ContextUtil.CreateControllerContext();
+ controllerContext.ControllerDescriptor = new HttpControllerDescriptor(controllerContext.Configuration, "test", typeof(ApiController));
+ HttpActionDescriptorTracer tracer = new HttpActionDescriptorTracer(controllerContext, mockActionDescriptor.Object, new TestTraceWriter());
+
+ // Act
+ Collection<FilterInfo> wrappedFilterCollection = tracer.GetFilterPipeline();
+
+ // Assert
+ Assert.IsType<FilterTracer>(wrappedFilterCollection[0].Instance);
+ }
+
+ // This test verifies only one kind of filter is wrapped, proving
+ // the static FilterTracer.CreateFilterTracer was called from GetFilterPipeline.
+ // Deeper testing of FilterTracer.CreateFilterTracer is in FilterTracerTest.
+ [Fact]
+ public void GetFilters_Returns_Wrapped_IFilters()
+ {
+ // Arrange
+ Mock<IFilter> mockFilter = new Mock<IFilter>();
+ IFilter[] filters = new IFilter[] { mockFilter.Object };
+ Mock<HttpActionDescriptor> mockActionDescriptor = new Mock<HttpActionDescriptor>() { CallBase = true };
+ mockActionDescriptor.Setup(a => a.ActionName).Returns("test");
+ mockActionDescriptor.Setup(a => a.GetFilters()).Returns(filters);
+ HttpControllerContext controllerContext = ContextUtil.CreateControllerContext();
+ controllerContext.ControllerDescriptor = new HttpControllerDescriptor(controllerContext.Configuration, "test", typeof(ApiController));
+ HttpActionDescriptorTracer tracer = new HttpActionDescriptorTracer(controllerContext, mockActionDescriptor.Object, new TestTraceWriter());
+
+ // Act
+ IFilter[] wrappedFilters = tracer.GetFilters().ToArray();
+
+ // Assert
+ Assert.IsType<FilterTracer>(wrappedFilters[0]);
+ }
+
+ [Fact]
+ public void Execute_Invokes_Inner_Execute()
+ {
+ // Arrange
+ bool executeCalled = false;
+ Mock<HttpActionDescriptor> mockActionDescriptor = new Mock<HttpActionDescriptor>() { CallBase = true };
+ mockActionDescriptor.Setup(a => a.ActionName).Returns("test");
+ mockActionDescriptor.Setup(
+ a => a.Execute(It.IsAny<HttpControllerContext>(), It.IsAny<IDictionary<string, object>>())).Callback(() => executeCalled = true);
+ HttpControllerContext controllerContext = ContextUtil.CreateControllerContext();
+ controllerContext.ControllerDescriptor = new HttpControllerDescriptor(controllerContext.Configuration, "test", typeof(ApiController));
+ IDictionary<string, object> arguments = new Dictionary<string, object>();
+ HttpActionDescriptorTracer tracer = new HttpActionDescriptorTracer(controllerContext, mockActionDescriptor.Object, new TestTraceWriter());
+
+ // Act
+ tracer.Execute(controllerContext, arguments);
+
+ // Assert
+ Assert.True(executeCalled);
+ }
+
+ [Fact]
+ public void Execute_Traces()
+ {
+ // Arrange
+ Mock<HttpActionDescriptor> mockActionDescriptor = new Mock<HttpActionDescriptor>() { CallBase = true };
+ mockActionDescriptor.Setup(a => a.ActionName).Returns("test");
+ mockActionDescriptor.Setup(
+ a => a.Execute(It.IsAny<HttpControllerContext>(), It.IsAny<IDictionary<string, object>>())).Callback(() => {});
+ HttpControllerContext controllerContext = ContextUtil.CreateControllerContext();
+ controllerContext.ControllerDescriptor = new HttpControllerDescriptor(controllerContext.Configuration, "test", typeof(ApiController));
+ IDictionary<string, object> arguments = new Dictionary<string, object>();
+ TestTraceWriter traceWriter = new TestTraceWriter();
+ HttpActionDescriptorTracer tracer = new HttpActionDescriptorTracer(controllerContext, mockActionDescriptor.Object, traceWriter);
+ TraceRecord[] expectedTraces = new TraceRecord[]
+ {
+ new TraceRecord(controllerContext.Request, TraceCategories.ActionCategory, TraceLevel.Info) { Kind = TraceKind.Begin },
+ new TraceRecord(controllerContext.Request, TraceCategories.ActionCategory, TraceLevel.Info) { Kind = TraceKind.End }
+ };
+
+ // Act
+ tracer.Execute(controllerContext, arguments);
+
+ // Assert
+ Assert.Equal<TraceRecord>(expectedTraces, traceWriter.Traces, new TraceRecordComparer());
+ }
+
+ [Fact]
+ public void Execute_Throws_What_Inner_Throws_And_Traces()
+ {
+ // Arrange
+ Mock<HttpActionDescriptor> mockActionDescriptor = new Mock<HttpActionDescriptor>() { CallBase = true };
+ InvalidOperationException exception = new InvalidOperationException("test");
+ mockActionDescriptor.Setup(
+ a => a.Execute(It.IsAny<HttpControllerContext>(), It.IsAny<IDictionary<string, object>>())).Throws(exception);
+ mockActionDescriptor.Setup(a => a.ActionName).Returns("test");
+ HttpControllerContext controllerContext = ContextUtil.CreateControllerContext();
+ controllerContext.ControllerDescriptor = new HttpControllerDescriptor(controllerContext.Configuration, "test", typeof(ApiController));
+ IDictionary<string, object> arguments = new Dictionary<string, object>();
+ TestTraceWriter traceWriter = new TestTraceWriter();
+ HttpActionDescriptorTracer tracer = new HttpActionDescriptorTracer(controllerContext, mockActionDescriptor.Object, traceWriter);
+ TraceRecord[] expectedTraces = new TraceRecord[]
+ {
+ new TraceRecord(controllerContext.Request, TraceCategories.ActionCategory, TraceLevel.Info) { Kind = TraceKind.Begin },
+ new TraceRecord(controllerContext.Request, TraceCategories.ActionCategory, TraceLevel.Error) { Kind = TraceKind.End }
+ };
+
+ // Act & Assert
+ Assert.Throws<InvalidOperationException>(() => tracer.Execute(controllerContext, arguments));
+
+ // Assert
+ Assert.Equal<TraceRecord>(expectedTraces, traceWriter.Traces, new TraceRecordComparer());
+ Assert.Same(exception, traceWriter.Traces[1].Exception);
+ }
+ }
+}
diff --git a/test/System.Web.Http.Test/Tracing/Tracers/HttpActionInvokerTracerTest.cs b/test/System.Web.Http.Test/Tracing/Tracers/HttpActionInvokerTracerTest.cs
new file mode 100644
index 00000000..ad1443d6
--- /dev/null
+++ b/test/System.Web.Http.Test/Tracing/Tracers/HttpActionInvokerTracerTest.cs
@@ -0,0 +1,224 @@
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Net.Http;
+using System.Threading;
+using System.Threading.Tasks;
+using System.Web.Http.Controllers;
+using Moq;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Http.Tracing.Tracers
+{
+ public class HttpActionInvokerTracerTest
+ {
+ private HttpActionContext _actionContext;
+ private ApiController _apiController;
+
+ public HttpActionInvokerTracerTest()
+ {
+ UsersController controller = new UsersController();
+ _apiController = controller;
+
+ Func<HttpResponseMessage> actionMethod = controller.Get;
+ _actionContext = ContextUtil.CreateActionContext(
+ ContextUtil.CreateControllerContext(instance: _apiController),
+ new ReflectedHttpActionDescriptor { MethodInfo = actionMethod.Method });
+ HttpRequestMessage request = new HttpRequestMessage();
+ _actionContext.ControllerContext.Request = request;
+ }
+
+ [Fact]
+ public void InvokeActionAsync_Calls_ActionDescriptor_Execute()
+ {
+ // Arrange
+ Mock<HttpActionDescriptor> mockActionDescriptor = new Mock<HttpActionDescriptor>() { CallBase = true };
+ mockActionDescriptor.Setup(a => a.ActionName).Returns("mockAction");
+ mockActionDescriptor.Setup(a => a.GetParameters()).Returns(new Collection<HttpParameterDescriptor>(new HttpParameterDescriptor[0]));
+ mockActionDescriptor.Setup(a => a.ReturnType).Returns(typeof(void));
+ bool executeWasCalled = false;
+ mockActionDescriptor.Setup(
+ a => a.Execute(It.IsAny<HttpControllerContext>(), It.IsAny<IDictionary<string, object>>())).Callback(
+ () => { executeWasCalled = true; });
+
+ HttpActionContext context = ContextUtil.CreateActionContext(
+ ContextUtil.CreateControllerContext(instance: _apiController),
+ mockActionDescriptor.Object);
+
+ HttpActionInvokerTracer tracer = new HttpActionInvokerTracer(new ApiControllerActionInvoker(), new TestTraceWriter());
+
+ // Act
+ ((IHttpActionInvoker)tracer).InvokeActionAsync(context, CancellationToken.None).Wait();
+
+ // Assert
+ Assert.True(executeWasCalled);
+ }
+
+ [Fact]
+ public void InvokeActionAsync_Traces_Begin_And_End_Info()
+ {
+ // Arrange
+ TestTraceWriter traceWriter = new TestTraceWriter();
+ HttpActionInvokerTracer tracer = new HttpActionInvokerTracer(new ApiControllerActionInvoker(), traceWriter);
+ TraceRecord[] expectedTraces = new TraceRecord[]
+ {
+ new TraceRecord(_actionContext.Request, TraceCategories.ActionCategory, TraceLevel.Info) { Kind = TraceKind.Begin },
+ new TraceRecord(_actionContext.Request, TraceCategories.ActionCategory, TraceLevel.Info) { Kind = TraceKind.End }
+ };
+
+ // Act
+ Task task = ((IHttpActionInvoker)tracer).InvokeActionAsync(_actionContext, CancellationToken.None);
+ task.Wait();
+
+ // Assert
+ Assert.Equal(2, traceWriter.Traces.Count);
+ Assert.Equal<TraceRecord>(expectedTraces, traceWriter.Traces, new TraceRecordComparer());
+ }
+
+ [Fact]
+ public void InvokeActionAsync_Returns_Cancelled_Inner_Task()
+ {
+ // Arrange
+ CancellationTokenSource cancellationSource = new CancellationTokenSource();
+ cancellationSource.Cancel();
+
+ HttpActionInvokerTracer tracer = new HttpActionInvokerTracer(new ApiControllerActionInvoker(), new TestTraceWriter());
+
+ // Act
+ var response = ((IHttpActionInvoker)tracer).InvokeActionAsync(_actionContext, cancellationSource.Token);
+
+ // Assert
+ Assert.Throws<TaskCanceledException>(() => { response.Wait(); });
+ Assert.Equal<TaskStatus>(TaskStatus.Canceled, response.Status);
+ }
+
+ [Fact]
+ public void InvokeActionAsync_Traces_Cancelled_Inner_Task()
+ {
+ // Arrange
+ TestTraceWriter traceWriter = new TestTraceWriter();
+ HttpActionInvokerTracer tracer = new HttpActionInvokerTracer(new ApiControllerActionInvoker(), traceWriter);
+ CancellationTokenSource cancellationSource = new CancellationTokenSource();
+ cancellationSource.Cancel();
+
+ TraceRecord[] expectedTraces = new TraceRecord[]
+ {
+ new TraceRecord(_actionContext.Request, TraceCategories.ActionCategory, TraceLevel.Info) { Kind = TraceKind.Begin },
+ new TraceRecord(_actionContext.Request, TraceCategories.ActionCategory, TraceLevel.Warn) { Kind = TraceKind.End }
+ };
+
+ // Act
+ var response = ((IHttpActionInvoker)tracer).InvokeActionAsync(_actionContext, cancellationSource.Token);
+
+ // Assert
+ Assert.Throws<TaskCanceledException>(() => { response.Wait(); });
+ Assert.Equal<TraceRecord>(expectedTraces, traceWriter.Traces, new TraceRecordComparer());
+ }
+
+ [Fact]
+ public void InvokeActionAsync_Returns_Faulted_Inner_Task()
+ {
+ // Arrange
+ Mock<ApiControllerActionInvoker> mockActionInvoker = new Mock<ApiControllerActionInvoker>() { CallBase = true };
+ InvalidOperationException expectedException = new InvalidOperationException("test message");
+ TaskCompletionSource<HttpResponseMessage> tcs = new TaskCompletionSource<HttpResponseMessage>(null);
+ tcs.TrySetException(expectedException);
+ mockActionInvoker.Setup(
+ a => a.InvokeActionAsync(It.IsAny<HttpActionContext>(), It.IsAny<CancellationToken>())).Returns(tcs.Task);
+ HttpActionInvokerTracer tracer = new HttpActionInvokerTracer(mockActionInvoker.Object, new TestTraceWriter());
+
+ // Act
+ var response = ((IHttpActionInvoker)tracer).InvokeActionAsync(_actionContext, CancellationToken.None);
+
+ // Assert
+ Assert.Throws<InvalidOperationException>(() => response.Wait());
+ Assert.Equal<TaskStatus>(TaskStatus.Faulted, response.Status);
+ Assert.Equal(expectedException.Message, response.Exception.GetBaseException().Message);
+ }
+
+ [Fact]
+ public void InvokeActionAsync_Traces_Faulted_Inner_Task()
+ {
+ // Arrange
+ Mock<ApiControllerActionInvoker> mockActionInvoker = new Mock<ApiControllerActionInvoker>() { CallBase = true };
+ InvalidOperationException expectedException = new InvalidOperationException("test message");
+ TaskCompletionSource<HttpResponseMessage> tcs = new TaskCompletionSource<HttpResponseMessage>(null);
+ tcs.TrySetException(expectedException);
+ mockActionInvoker.Setup(
+ a => a.InvokeActionAsync(It.IsAny<HttpActionContext>(), It.IsAny<CancellationToken>())).Returns(tcs.Task);
+ TestTraceWriter traceWriter = new TestTraceWriter();
+ HttpActionInvokerTracer tracer = new HttpActionInvokerTracer(mockActionInvoker.Object, traceWriter);
+ TraceRecord[] expectedTraces = new TraceRecord[]
+ {
+ new TraceRecord(_actionContext.Request, TraceCategories.ActionCategory, TraceLevel.Info) { Kind = TraceKind.Begin },
+ new TraceRecord(_actionContext.Request, TraceCategories.ActionCategory, TraceLevel.Error) { Kind = TraceKind.End }
+ };
+
+ // Act
+ var response = ((IHttpActionInvoker)tracer).InvokeActionAsync(_actionContext, CancellationToken.None);
+
+ // Assert
+ Assert.Throws<InvalidOperationException>(() => response.Wait());
+ Assert.Equal<TraceRecord>(expectedTraces, traceWriter.Traces, new TraceRecordComparer());
+ Assert.Equal(expectedException, traceWriter.Traces[1].Exception);
+ }
+
+ [Fact]
+ public void InvokeActionAsync_Throws_When_ActionContext_Is_Null()
+ {
+ // Arrange
+ HttpActionInvokerTracer tracer = new HttpActionInvokerTracer(new ApiControllerActionInvoker(), new TestTraceWriter());
+
+ // Act & Assert
+ Assert.ThrowsArgumentNull(
+ () => ((IHttpActionInvoker)tracer).InvokeActionAsync(null, CancellationToken.None),
+ "actionContext");
+ }
+
+ [Fact]
+ public void InvokeActionAsync_Throws_Exception_Thrown_From_Inner()
+ {
+ // Arrange
+ InvalidOperationException expectedException = new InvalidOperationException("test message");
+ Mock<ApiControllerActionInvoker> mockActionInvoker = new Mock<ApiControllerActionInvoker>() {CallBase = true};
+ mockActionInvoker.Setup(
+ a => a.InvokeActionAsync(It.IsAny<HttpActionContext>(), It.IsAny<CancellationToken>())).Throws(expectedException);
+ HttpActionInvokerTracer tracer = new HttpActionInvokerTracer(mockActionInvoker.Object, new TestTraceWriter());
+
+ // Act & Assert
+ InvalidOperationException thrownException = Assert.Throws<InvalidOperationException>(
+ () => ((IHttpActionInvoker)tracer).InvokeActionAsync(_actionContext, CancellationToken.None)
+ );
+
+ // Assert
+ Assert.Equal(expectedException, thrownException);
+ }
+
+ [Fact]
+ public void InvokeActionAsync_Traces_Exception_Thrown_From_Inner()
+ {
+ // Arrange
+ InvalidOperationException expectedException = new InvalidOperationException("test message");
+ Mock<ApiControllerActionInvoker> mockActionInvoker = new Mock<ApiControllerActionInvoker>() { CallBase = true };
+ mockActionInvoker.Setup(
+ a => a.InvokeActionAsync(It.IsAny<HttpActionContext>(), It.IsAny<CancellationToken>())).Throws(expectedException);
+
+ TestTraceWriter traceWriter = new TestTraceWriter();
+ HttpActionInvokerTracer tracer = new HttpActionInvokerTracer(mockActionInvoker.Object, traceWriter);
+ TraceRecord[] expectedTraces = new TraceRecord[]
+ {
+ new TraceRecord(_actionContext.Request, TraceCategories.ActionCategory, TraceLevel.Info) { Kind = TraceKind.Begin },
+ new TraceRecord(_actionContext.Request, TraceCategories.ActionCategory, TraceLevel.Error) { Kind = TraceKind.End }
+ };
+
+ // Act & Assert
+ Assert.Throws<InvalidOperationException>(
+ () => ((IHttpActionInvoker)tracer).InvokeActionAsync(_actionContext, CancellationToken.None)
+ );
+
+ // Assert
+ Assert.Equal<TraceRecord>(expectedTraces, traceWriter.Traces, new TraceRecordComparer());
+ Assert.Equal(expectedException, traceWriter.Traces[1].Exception);
+ }
+ }
+}
diff --git a/test/System.Web.Http.Test/Tracing/Tracers/HttpActionSelectorTracerTest.cs b/test/System.Web.Http.Test/Tracing/Tracers/HttpActionSelectorTracerTest.cs
new file mode 100644
index 00000000..a68029f4
--- /dev/null
+++ b/test/System.Web.Http.Test/Tracing/Tracers/HttpActionSelectorTracerTest.cs
@@ -0,0 +1,80 @@
+using System.Collections.ObjectModel;
+using System.Net.Http;
+using System.Web.Http.Controllers;
+using Moq;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Http.Tracing.Tracers
+{
+ public class HttpActionSelectorTracerTest
+ {
+ private Mock<HttpActionDescriptor> _mockActionDescriptor;
+ private HttpActionContext _actionContext;
+ private HttpControllerContext _controllerContext;
+ private HttpControllerDescriptor _controllerDescriptor;
+
+ public HttpActionSelectorTracerTest()
+ {
+ _mockActionDescriptor = new Mock<HttpActionDescriptor>() { CallBase = true };
+ _mockActionDescriptor.Setup(a => a.ActionName).Returns("test");
+ _mockActionDescriptor.Setup(a => a.GetParameters()).Returns(new Collection<HttpParameterDescriptor>(new HttpParameterDescriptor[0]));
+
+ _controllerDescriptor = new HttpControllerDescriptor(new HttpConfiguration(), "controller", typeof(ApiController));
+
+ _controllerContext = ContextUtil.CreateControllerContext(request: new HttpRequestMessage());
+ _controllerContext.ControllerDescriptor = _controllerDescriptor;
+
+ _actionContext = ContextUtil.CreateActionContext(_controllerContext, actionDescriptor: _mockActionDescriptor.Object);
+ }
+
+ [Fact]
+ public void SelectAction_Traces_And_Returns_ActionDescriptor_Tracer()
+ {
+ // Arrange
+ TestTraceWriter traceWriter = new TestTraceWriter();
+ Mock<IHttpActionSelector> mockSelector = new Mock<IHttpActionSelector>();
+ mockSelector.Setup(s => s.SelectAction(_controllerContext)).Returns(_mockActionDescriptor.Object);
+ HttpActionSelectorTracer tracer = new HttpActionSelectorTracer(mockSelector.Object, traceWriter);
+
+ TraceRecord[] expectedTraces = new TraceRecord[]
+ {
+ new TraceRecord(_actionContext.Request, TraceCategories.ActionCategory, TraceLevel.Info) { Kind = TraceKind.Begin },
+ new TraceRecord(_actionContext.Request, TraceCategories.ActionCategory, TraceLevel.Info) { Kind = TraceKind.End }
+ };
+
+ // Act
+ HttpActionDescriptor selectedActionDescriptor = ((IHttpActionSelector)tracer).SelectAction(_controllerContext);
+
+ // Assert
+ Assert.Equal<TraceRecord>(expectedTraces, traceWriter.Traces, new TraceRecordComparer());
+ Assert.IsAssignableFrom<HttpActionDescriptorTracer>(selectedActionDescriptor);
+ }
+
+
+ [Fact]
+ public void SelectAction_Traces_And_Throws_Exception_Thrown_From_Inner()
+ {
+ // Arrange
+ TestTraceWriter traceWriter = new TestTraceWriter();
+ Mock<IHttpActionSelector> mockSelector = new Mock<IHttpActionSelector>();
+ InvalidOperationException exception = new InvalidOperationException();
+ mockSelector.Setup(s => s.SelectAction(_controllerContext)).Throws(exception);
+ HttpActionSelectorTracer tracer = new HttpActionSelectorTracer(mockSelector.Object, traceWriter);
+
+ TraceRecord[] expectedTraces = new TraceRecord[]
+ {
+ new TraceRecord(_actionContext.Request, TraceCategories.ActionCategory, TraceLevel.Info) { Kind = TraceKind.Begin },
+ new TraceRecord(_actionContext.Request, TraceCategories.ActionCategory, TraceLevel.Error) { Kind = TraceKind.End }
+ };
+
+ // Act
+ Exception thrown = Assert.Throws<InvalidOperationException>(() => ((IHttpActionSelector)tracer).SelectAction(_controllerContext));
+
+ // Assert
+ Assert.Equal<TraceRecord>(expectedTraces, traceWriter.Traces, new TraceRecordComparer());
+ Assert.Same(exception, thrown);
+ Assert.Same(exception, traceWriter.Traces[1].Exception);
+ }
+ }
+}
diff --git a/test/System.Web.Http.Test/Tracing/Tracers/HttpControllerActivatorTracerTest.cs b/test/System.Web.Http.Test/Tracing/Tracers/HttpControllerActivatorTracerTest.cs
new file mode 100644
index 00000000..2b84b4b3
--- /dev/null
+++ b/test/System.Web.Http.Test/Tracing/Tracers/HttpControllerActivatorTracerTest.cs
@@ -0,0 +1,64 @@
+using System.Net.Http;
+using System.Web.Http.Controllers;
+using System.Web.Http.Dispatcher;
+using Moq;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Http.Tracing.Tracers
+{
+ public class HttpControllerActivatorTracerTest
+ {
+ [Fact]
+ public void Create_Invokes_Inner_And_Traces()
+ {
+ // Arrange
+ Mock<ApiController> mockController = new Mock<ApiController>();
+ Mock<IHttpControllerActivator> mockActivator = new Mock<IHttpControllerActivator>() {CallBase = true};
+ mockActivator.Setup(b => b.Create(It.IsAny<HttpControllerContext>(), It.IsAny<Type>())).Returns(mockController.Object);
+ HttpControllerContext controllerContext = ContextUtil.CreateControllerContext(request: new HttpRequestMessage());
+ TestTraceWriter traceWriter = new TestTraceWriter();
+ HttpControllerActivatorTracer tracer = new HttpControllerActivatorTracer(mockActivator.Object, traceWriter);
+
+ TraceRecord[] expectedTraces = new TraceRecord[]
+ {
+ new TraceRecord(controllerContext.Request, TraceCategories.ControllersCategory, TraceLevel.Info) { Kind = TraceKind.Begin, Operation = "Create" },
+ new TraceRecord(controllerContext.Request, TraceCategories.ControllersCategory, TraceLevel.Info) { Kind = TraceKind.End, Operation = "Create" }
+ };
+
+ // Act
+ IHttpController createdController = ((IHttpControllerActivator)tracer).Create(controllerContext, mockController.Object.GetType());
+
+ // Assert
+ Assert.Equal<TraceRecord>(expectedTraces, traceWriter.Traces, new TraceRecordComparer());
+ Assert.IsAssignableFrom<HttpControllerTracer>(createdController);
+ }
+
+ [Fact]
+ public void Create_Throws_And_Traces_When_Inner_Throws()
+ {
+ // Arrange
+ Mock<ApiController> mockController = new Mock<ApiController>();
+ Mock<IHttpControllerActivator> mockActivator = new Mock<IHttpControllerActivator>() { CallBase = true };
+ InvalidOperationException exception = new InvalidOperationException("test");
+ mockActivator.Setup(b => b.Create(It.IsAny<HttpControllerContext>(), It.IsAny<Type>())).Throws(exception);
+ HttpControllerContext controllerContext = ContextUtil.CreateControllerContext(request: new HttpRequestMessage());
+ TestTraceWriter traceWriter = new TestTraceWriter();
+ HttpControllerActivatorTracer tracer = new HttpControllerActivatorTracer(mockActivator.Object, traceWriter);
+
+ TraceRecord[] expectedTraces = new TraceRecord[]
+ {
+ new TraceRecord(controllerContext.Request, TraceCategories.ControllersCategory, TraceLevel.Info) { Kind = TraceKind.Begin, Operation = "Create" },
+ new TraceRecord(controllerContext.Request, TraceCategories.ControllersCategory, TraceLevel.Error) { Kind = TraceKind.End, Operation = "Create" }
+ };
+
+ // Act & Assert
+ Exception thrown = Assert.Throws<InvalidOperationException>(() => ((IHttpControllerActivator)tracer).Create(controllerContext, mockController.Object.GetType()));
+
+ // Assert
+ Assert.Equal<TraceRecord>(expectedTraces, traceWriter.Traces, new TraceRecordComparer());
+ Assert.Same(exception, thrown);
+ Assert.Same(exception, traceWriter.Traces[1].Exception);
+ }
+ }
+}
diff --git a/test/System.Web.Http.Test/Tracing/Tracers/HttpControllerFactoryTracerTest.cs b/test/System.Web.Http.Test/Tracing/Tracers/HttpControllerFactoryTracerTest.cs
new file mode 100644
index 00000000..36c3e4f6
--- /dev/null
+++ b/test/System.Web.Http.Test/Tracing/Tracers/HttpControllerFactoryTracerTest.cs
@@ -0,0 +1,63 @@
+using System.Net.Http;
+using System.Web.Http.Controllers;
+using System.Web.Http.Dispatcher;
+using Moq;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Http.Tracing.Tracers
+{
+ public class HttpControllerFactoryTracerTest
+ {
+ [Fact]
+ public void CreateController_Invokes_Inner_And_Traces()
+ {
+ // Arrange
+ Mock<ApiController> mockController = new Mock<ApiController>();
+ Mock<IHttpControllerFactory> mockFactory = new Mock<IHttpControllerFactory>() { CallBase = true };
+ mockFactory.Setup(b => b.CreateController(It.IsAny<HttpControllerContext>(), It.IsAny<string>())).Returns(mockController.Object);
+ HttpControllerContext controllerContext = ContextUtil.CreateControllerContext(request: new HttpRequestMessage());
+ TestTraceWriter traceWriter = new TestTraceWriter();
+ HttpControllerFactoryTracer tracer = new HttpControllerFactoryTracer(mockFactory.Object, traceWriter);
+
+ TraceRecord[] expectedTraces = new TraceRecord[]
+ {
+ new TraceRecord(controllerContext.Request, TraceCategories.ControllersCategory, TraceLevel.Info) { Kind = TraceKind.Begin, Operation = "CreateController" },
+ new TraceRecord(controllerContext.Request, TraceCategories.ControllersCategory, TraceLevel.Info) { Kind = TraceKind.End, Operation = "CreateController" }
+ };
+
+ // Act
+ IHttpController createdController = ((IHttpControllerFactory)tracer).CreateController(controllerContext, "anyName");
+
+ // Assert
+ Assert.Equal<TraceRecord>(expectedTraces, traceWriter.Traces, new TraceRecordComparer());
+ Assert.IsAssignableFrom<HttpControllerTracer>(createdController);
+ }
+
+ [Fact]
+ public void CreateController_Throws_And_Traces_When_Inner_Throws()
+ {
+ // Arrange
+ Mock<IHttpControllerFactory> mockFactory = new Mock<IHttpControllerFactory>() { CallBase = true };
+ InvalidOperationException exception = new InvalidOperationException("test");
+ mockFactory.Setup(b => b.CreateController(It.IsAny<HttpControllerContext>(), It.IsAny<string>())).Throws(exception);
+ HttpControllerContext controllerContext = ContextUtil.CreateControllerContext(request: new HttpRequestMessage());
+ TestTraceWriter traceWriter = new TestTraceWriter();
+ HttpControllerFactoryTracer tracer = new HttpControllerFactoryTracer(mockFactory.Object, traceWriter);
+
+ TraceRecord[] expectedTraces = new TraceRecord[]
+ {
+ new TraceRecord(controllerContext.Request, TraceCategories.ControllersCategory, TraceLevel.Info) { Kind = TraceKind.Begin, Operation = "CreateController" },
+ new TraceRecord(controllerContext.Request, TraceCategories.ControllersCategory, TraceLevel.Error) { Kind = TraceKind.End, Operation = "CreateController" }
+ };
+
+ // Act
+ Exception thrown = Assert.Throws<InvalidOperationException>(() => ((IHttpControllerFactory)tracer).CreateController(controllerContext, "anyName"));
+
+ // Assert
+ Assert.Equal<TraceRecord>(expectedTraces, traceWriter.Traces, new TraceRecordComparer());
+ Assert.Same(exception, thrown);
+ Assert.Same(exception, traceWriter.Traces[1].Exception);
+ }
+ }
+}
diff --git a/test/System.Web.Http.Test/Tracing/Tracers/HttpControllerTracerTest.cs b/test/System.Web.Http.Test/Tracing/Tracers/HttpControllerTracerTest.cs
new file mode 100644
index 00000000..bd3b50d7
--- /dev/null
+++ b/test/System.Web.Http.Test/Tracing/Tracers/HttpControllerTracerTest.cs
@@ -0,0 +1,121 @@
+using System.Collections.ObjectModel;
+using System.Net.Http;
+using System.Threading;
+using System.Threading.Tasks;
+using System.Web.Http.Controllers;
+using Moq;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Http.Tracing.Tracers
+{
+ public class HttpControllerTracerTest
+ {
+ private Mock<HttpActionDescriptor> _mockActionDescriptor;
+ private HttpControllerDescriptor _controllerDescriptor;
+
+ public HttpControllerTracerTest()
+ {
+ _mockActionDescriptor = new Mock<HttpActionDescriptor>() { CallBase = true };
+ _mockActionDescriptor.Setup(a => a.ActionName).Returns("test");
+ _mockActionDescriptor.Setup(a => a.GetParameters()).Returns(new Collection<HttpParameterDescriptor>(new HttpParameterDescriptor[0]));
+
+ _controllerDescriptor = new HttpControllerDescriptor(new HttpConfiguration(), "controller", typeof(ApiController));
+ }
+
+ [Fact]
+ public void ExecuteAsync_Invokes_Inner_And_Traces()
+ {
+ // Arrange
+ HttpResponseMessage response = new HttpResponseMessage();
+ Mock<ApiController> mockController = new Mock<ApiController>() { CallBase = true };
+ mockController.Setup(b => b.ExecuteAsync(It.IsAny<HttpControllerContext>(), It.IsAny<CancellationToken>())).Returns(TaskHelpers.FromResult<HttpResponseMessage>(response));
+
+ HttpControllerContext controllerContext = ContextUtil.CreateControllerContext(request: new HttpRequestMessage());
+ controllerContext.ControllerDescriptor = _controllerDescriptor;
+ controllerContext.Controller = mockController.Object;
+
+ HttpActionContext actionContext = ContextUtil.CreateActionContext(controllerContext, actionDescriptor: _mockActionDescriptor.Object);
+
+ TestTraceWriter traceWriter = new TestTraceWriter();
+ HttpControllerTracer tracer = new HttpControllerTracer(mockController.Object, traceWriter);
+
+ TraceRecord[] expectedTraces = new TraceRecord[]
+ {
+ new TraceRecord(actionContext.Request, TraceCategories.ControllersCategory, TraceLevel.Info) { Kind = TraceKind.Begin },
+ new TraceRecord(actionContext.Request, TraceCategories.ControllersCategory, TraceLevel.Info) { Kind = TraceKind.End }
+ };
+
+ // Act
+ HttpResponseMessage actualResponse = ((IHttpController)tracer).ExecuteAsync(controllerContext, CancellationToken.None).Result;
+
+ // Assert
+ Assert.Equal<TraceRecord>(expectedTraces, traceWriter.Traces, new TraceRecordComparer());
+ Assert.Same(response, actualResponse);
+ }
+
+ [Fact]
+ public void ExecuteAsync_Faults_And_Traces_When_Inner_Faults()
+ {
+ // Arrange
+ InvalidOperationException exception = new InvalidOperationException();
+ TaskCompletionSource<HttpResponseMessage> tcs = new TaskCompletionSource<HttpResponseMessage>();
+ tcs.TrySetException(exception);
+ Mock<ApiController> mockController = new Mock<ApiController>() { CallBase = true };
+ mockController.Setup(b => b.ExecuteAsync(It.IsAny<HttpControllerContext>(), It.IsAny<CancellationToken>())).Returns(tcs.Task);
+
+ HttpControllerContext controllerContext = ContextUtil.CreateControllerContext(request: new HttpRequestMessage());
+ controllerContext.ControllerDescriptor = _controllerDescriptor;
+ controllerContext.Controller = mockController.Object;
+
+ HttpActionContext actionContext = ContextUtil.CreateActionContext(controllerContext, actionDescriptor: _mockActionDescriptor.Object);
+
+ TestTraceWriter traceWriter = new TestTraceWriter();
+ HttpControllerTracer tracer = new HttpControllerTracer(mockController.Object, traceWriter);
+
+ TraceRecord[] expectedTraces = new TraceRecord[]
+ {
+ new TraceRecord(actionContext.Request, TraceCategories.ControllersCategory, TraceLevel.Info) { Kind = TraceKind.Begin },
+ new TraceRecord(actionContext.Request, TraceCategories.ControllersCategory, TraceLevel.Error) { Kind = TraceKind.End }
+ };
+
+ // Act
+ Exception thrown = Assert.Throws<InvalidOperationException>(() => ((IHttpController)tracer).ExecuteAsync(controllerContext, CancellationToken.None).Wait());
+
+ // Assert
+ Assert.Equal<TraceRecord>(expectedTraces, traceWriter.Traces, new TraceRecordComparer());
+ Assert.Same(exception, thrown);
+ Assert.Same(exception, traceWriter.Traces[1].Exception);
+ }
+
+ [Fact]
+ public void ExecuteAsync_IsCancelled_And_Traces_When_Inner_IsCancelled()
+ {
+ // Arrange
+ Mock<ApiController> mockController = new Mock<ApiController>() { CallBase = true };
+ mockController.Setup(b => b.ExecuteAsync(It.IsAny<HttpControllerContext>(), It.IsAny<CancellationToken>())).Returns(TaskHelpers.Canceled<HttpResponseMessage>());
+
+ HttpControllerContext controllerContext = ContextUtil.CreateControllerContext(request: new HttpRequestMessage());
+ controllerContext.ControllerDescriptor = _controllerDescriptor;
+ controllerContext.Controller = mockController.Object;
+
+ HttpActionContext actionContext = ContextUtil.CreateActionContext(controllerContext, actionDescriptor: _mockActionDescriptor.Object);
+
+ TestTraceWriter traceWriter = new TestTraceWriter();
+ HttpControllerTracer tracer = new HttpControllerTracer(mockController.Object, traceWriter);
+
+ TraceRecord[] expectedTraces = new TraceRecord[]
+ {
+ new TraceRecord(actionContext.Request, TraceCategories.ControllersCategory, TraceLevel.Info) { Kind = TraceKind.Begin },
+ new TraceRecord(actionContext.Request, TraceCategories.ControllersCategory, TraceLevel.Warn) { Kind = TraceKind.End }
+ };
+
+ // Act
+ Task task = ((IHttpController) tracer).ExecuteAsync(controllerContext,CancellationToken.None);
+ Exception thrown = Assert.Throws<TaskCanceledException>(() => task.Wait());
+
+ // Assert
+ Assert.Equal<TraceRecord>(expectedTraces, traceWriter.Traces, new TraceRecordComparer());
+ }
+ }
+}
diff --git a/test/System.Web.Http.Test/Tracing/Tracers/HttpParameterBindingTracerTest.cs b/test/System.Web.Http.Test/Tracing/Tracers/HttpParameterBindingTracerTest.cs
new file mode 100644
index 00000000..a1d5b7c9
--- /dev/null
+++ b/test/System.Web.Http.Test/Tracing/Tracers/HttpParameterBindingTracerTest.cs
@@ -0,0 +1,122 @@
+using System.Threading;
+using System.Threading.Tasks;
+using System.Web.Http.Controllers;
+using System.Web.Http.Metadata;
+using System.Web.Http.Metadata.Providers;
+using System.Web.Http.ModelBinding;
+using Moq;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Http.Tracing.Tracers
+{
+ public class HttpParameterBindingTracerTest
+ {
+ [Fact]
+ public void ExecuteBindingAsync_Traces_And_Invokes_Inner()
+ {
+ // Arrange
+ Mock<HttpParameterDescriptor> mockParamDescriptor = new Mock<HttpParameterDescriptor>() { CallBase = true };
+ mockParamDescriptor.Setup(d => d.ParameterName).Returns("paramName");
+ mockParamDescriptor.Setup(d => d.ParameterType).Returns(typeof(string));
+ Mock<HttpParameterBinding> mockBinding = new Mock<HttpParameterBinding>(mockParamDescriptor.Object) { CallBase = true };
+ bool innerInvoked = false;
+ mockBinding.Setup(
+ b =>
+ b.ExecuteBindingAsync(It.IsAny<ModelMetadataProvider>(), It.IsAny<HttpActionContext>(),
+ It.IsAny<CancellationToken>())).Returns(TaskHelpers.Completed()).Callback(() => innerInvoked = true);
+
+ TestTraceWriter traceWriter = new TestTraceWriter();
+ HttpParameterBindingTracer tracer = new HttpParameterBindingTracer(mockBinding.Object, traceWriter);
+ HttpActionContext actionContext = ContextUtil.CreateActionContext();
+ ModelMetadataProvider metadataProvider = new EmptyModelMetadataProvider();
+
+ TraceRecord[] expectedTraces = new TraceRecord[]
+ {
+ new TraceRecord(actionContext.Request, TraceCategories.ModelBindingCategory, TraceLevel.Info) { Kind = TraceKind.Begin, Operation = "ExecuteBindingAsync" },
+ new TraceRecord(actionContext.Request, TraceCategories.ModelBindingCategory, TraceLevel.Info) { Kind = TraceKind.End, Operation = "ExecuteBindingAsync" }
+ };
+
+ // Act
+ Task task = tracer.ExecuteBindingAsync(metadataProvider, actionContext, CancellationToken.None);
+ task.Wait();
+
+ // Assert
+ Assert.Equal<TraceRecord>(expectedTraces, traceWriter.Traces, new TraceRecordComparer());
+ Assert.True(innerInvoked);
+ }
+
+ [Fact]
+ public void ExecuteBindingAsync_Traces_And_Throws_When_Inner_Throws()
+ {
+ // Arrange
+ Mock<HttpParameterDescriptor> mockParamDescriptor = new Mock<HttpParameterDescriptor>() { CallBase = true };
+ mockParamDescriptor.Setup(d => d.ParameterName).Returns("paramName");
+ mockParamDescriptor.Setup(d => d.ParameterType).Returns(typeof(string));
+ Mock<HttpParameterBinding> mockBinding = new Mock<HttpParameterBinding>(mockParamDescriptor.Object) { CallBase = true };
+ InvalidOperationException exception = new InvalidOperationException("test");
+ mockBinding.Setup(
+ b =>
+ b.ExecuteBindingAsync(It.IsAny<ModelMetadataProvider>(), It.IsAny<HttpActionContext>(),
+ It.IsAny<CancellationToken>())).Throws(exception);
+
+ TestTraceWriter traceWriter = new TestTraceWriter();
+ HttpParameterBindingTracer tracer = new HttpParameterBindingTracer(mockBinding.Object, traceWriter);
+ HttpActionContext actionContext = ContextUtil.CreateActionContext();
+ ModelMetadataProvider metadataProvider = new EmptyModelMetadataProvider();
+
+ TraceRecord[] expectedTraces = new TraceRecord[]
+ {
+ new TraceRecord(actionContext.Request, TraceCategories.ModelBindingCategory, TraceLevel.Info) { Kind = TraceKind.Begin, Operation = "ExecuteBindingAsync" },
+ new TraceRecord(actionContext.Request, TraceCategories.ModelBindingCategory, TraceLevel.Error) { Kind = TraceKind.End, Operation = "ExecuteBindingAsync" }
+ };
+
+ // Act & Assert
+ Exception thrown = Assert.Throws<InvalidOperationException>(() => tracer.ExecuteBindingAsync(metadataProvider, actionContext, CancellationToken.None));
+
+ // Assert
+ Assert.Same(exception, thrown);
+ Assert.Same(exception, traceWriter.Traces[1].Exception);
+ Assert.Equal<TraceRecord>(expectedTraces, traceWriter.Traces, new TraceRecordComparer());
+ }
+
+ [Fact]
+ public void ExecuteBindingAsync_Traces_And_Faults_When_Inner_Faults()
+ {
+ // Arrange
+
+ Mock<HttpParameterDescriptor> mockParamDescriptor = new Mock<HttpParameterDescriptor>() { CallBase = true };
+ mockParamDescriptor.Setup(d => d.ParameterName).Returns("paramName");
+ mockParamDescriptor.Setup(d => d.ParameterType).Returns(typeof(string));
+ Mock<HttpParameterBinding> mockBinding = new Mock<HttpParameterBinding>(mockParamDescriptor.Object) { CallBase = true };
+ InvalidOperationException exception = new InvalidOperationException("test");
+ TaskCompletionSource<object> tcs = new TaskCompletionSource<object>();
+ tcs.TrySetException(exception);
+
+ mockBinding.Setup(
+ b =>
+ b.ExecuteBindingAsync(It.IsAny<ModelMetadataProvider>(), It.IsAny<HttpActionContext>(),
+ It.IsAny<CancellationToken>())).Returns(tcs.Task);
+
+ TestTraceWriter traceWriter = new TestTraceWriter();
+ HttpParameterBindingTracer tracer = new HttpParameterBindingTracer(mockBinding.Object, traceWriter);
+ HttpActionContext actionContext = ContextUtil.CreateActionContext();
+ ModelMetadataProvider metadataProvider = new EmptyModelMetadataProvider();
+
+ TraceRecord[] expectedTraces = new TraceRecord[]
+ {
+ new TraceRecord(actionContext.Request, TraceCategories.ModelBindingCategory, TraceLevel.Info) { Kind = TraceKind.Begin, Operation = "ExecuteBindingAsync" },
+ new TraceRecord(actionContext.Request, TraceCategories.ModelBindingCategory, TraceLevel.Error) { Kind = TraceKind.End, Operation = "ExecuteBindingAsync" }
+ };
+
+ // Act & Assert
+ Task task = tracer.ExecuteBindingAsync(metadataProvider, actionContext, CancellationToken.None);
+
+ // Assert
+ Exception thrown = Assert.Throws<InvalidOperationException>(() => task.Wait());
+ Assert.Same(exception, thrown);
+ Assert.Same(exception, traceWriter.Traces[1].Exception);
+ Assert.Equal<TraceRecord>(expectedTraces, traceWriter.Traces, new TraceRecordComparer());
+ }
+ }
+}
diff --git a/test/System.Web.Http.Test/Tracing/Tracers/MediaTypeFormatterTracerTest.cs b/test/System.Web.Http.Test/Tracing/Tracers/MediaTypeFormatterTracerTest.cs
new file mode 100644
index 00000000..35494d7b
--- /dev/null
+++ b/test/System.Web.Http.Test/Tracing/Tracers/MediaTypeFormatterTracerTest.cs
@@ -0,0 +1,248 @@
+using System.IO;
+using System.Net;
+using System.Net.Http;
+using System.Net.Http.Formatting;
+using System.Net.Http.Headers;
+using System.Threading.Tasks;
+using Moq;
+using Xunit;
+using Xunit.Extensions;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Http.Tracing.Tracers
+{
+ public class MediaTypeFormatterTracerTest
+ {
+ [Fact]
+ public void OnReadFromStreamAsync_Traces()
+ {
+ // Arrange
+ Mock<MediaTypeFormatter> mockFormatter = new Mock<MediaTypeFormatter>() { CallBase = true };
+ mockFormatter.Setup(
+ f => f.ReadFromStreamAsync(It.IsAny<Type>(), It.IsAny<Stream>(), It.IsAny<HttpContentHeaders>(), It.IsAny<IFormatterLogger>())).
+ Returns(TaskHelpers.FromResult<object>("sampleValue"));
+ TestTraceWriter traceWriter = new TestTraceWriter();
+ HttpRequestMessage request = new HttpRequestMessage();
+ request.Content = new StringContent("");
+ MediaTypeFormatterTracer tracer = new MediaTypeFormatterTracer(mockFormatter.Object, traceWriter, request);
+ TraceRecord[] expectedTraces = new TraceRecord[]
+ {
+ new TraceRecord(request, TraceCategories.FormattingCategory, TraceLevel.Info) { Kind = TraceKind.Begin, Operation = "ReadFromStreamAsync" },
+ new TraceRecord(request, TraceCategories.FormattingCategory, TraceLevel.Info) { Kind = TraceKind.End, Operation = "ReadFromStreamAsync" }
+ };
+
+ // Act
+ Task<object> task = tracer.ReadFromStreamAsync(typeof (string), new MemoryStream(), request.Content.Headers, null);
+ string result = task.Result as string;
+
+ // Assert
+ Assert.Equal<TraceRecord>(expectedTraces, traceWriter.Traces, new TraceRecordComparer());
+ Assert.Equal("sampleValue", result);
+ }
+
+ [Fact]
+ public void OnReadFromStreamAsync_Traces_And_Throws_When_Inner_Throws()
+ {
+ // Arrange
+ InvalidOperationException exception = new InvalidOperationException("test");
+ Mock<MediaTypeFormatter> mockFormatter = new Mock<MediaTypeFormatter>() { CallBase = true };
+ mockFormatter.Setup(
+ f => f.ReadFromStreamAsync(It.IsAny<Type>(), It.IsAny<Stream>(), It.IsAny<HttpContentHeaders>(), It.IsAny<IFormatterLogger>())).Throws(exception);
+
+ TestTraceWriter traceWriter = new TestTraceWriter();
+ HttpRequestMessage request = new HttpRequestMessage();
+ request.Content = new StringContent("");
+ MediaTypeFormatterTracer tracer = new MediaTypeFormatterTracer(mockFormatter.Object, traceWriter, request);
+ TraceRecord[] expectedTraces = new TraceRecord[]
+ {
+ new TraceRecord(request, TraceCategories.FormattingCategory, TraceLevel.Info) { Kind = TraceKind.Begin, Operation = "ReadFromStreamAsync" },
+ new TraceRecord(request, TraceCategories.FormattingCategory, TraceLevel.Error) { Kind = TraceKind.End, Operation = "ReadFromStreamAsync" }
+ };
+
+ // Act
+ Exception thrown = Assert.Throws<InvalidOperationException>(() => tracer.ReadFromStreamAsync(typeof(string), new MemoryStream(), request.Content.Headers, null));
+
+ // Assert
+ Assert.Equal<TraceRecord>(expectedTraces, traceWriter.Traces, new TraceRecordComparer());
+ Assert.Same(exception, thrown);
+ Assert.Same(exception, traceWriter.Traces[1].Exception);
+ }
+
+ [Fact]
+ public void OnReadFromStreamAsync_Traces_And_Faults_When_Inner_Faults()
+ {
+ // Arrange
+ InvalidOperationException exception = new InvalidOperationException("test");
+ Mock<MediaTypeFormatter> mockFormatter = new Mock<MediaTypeFormatter>() { CallBase = true };
+ TaskCompletionSource<object> tcs = new TaskCompletionSource<object>();
+ tcs.TrySetException(exception);
+
+ mockFormatter.Setup(
+ f => f.ReadFromStreamAsync(It.IsAny<Type>(), It.IsAny<Stream>(), It.IsAny<HttpContentHeaders>(), It.IsAny<IFormatterLogger>())).
+ Returns(tcs.Task);
+ TestTraceWriter traceWriter = new TestTraceWriter();
+ HttpRequestMessage request = new HttpRequestMessage();
+ request.Content = new StringContent("");
+ MediaTypeFormatterTracer tracer = new MediaTypeFormatterTracer(mockFormatter.Object, traceWriter, request);
+ TraceRecord[] expectedTraces = new TraceRecord[]
+ {
+ new TraceRecord(request, TraceCategories.FormattingCategory, TraceLevel.Info) { Kind = TraceKind.Begin, Operation = "ReadFromStreamAsync" },
+ new TraceRecord(request, TraceCategories.FormattingCategory, TraceLevel.Error) { Kind = TraceKind.End, Operation = "ReadFromStreamAsync" }
+ };
+
+ // Act
+ Task<object> task = tracer.ReadFromStreamAsync(typeof (string), new MemoryStream(), request.Content.Headers, null);
+
+ // Assert
+ Exception thrown = Assert.Throws<InvalidOperationException>(() => task.Wait());
+ Assert.Equal<TraceRecord>(expectedTraces, traceWriter.Traces, new TraceRecordComparer());
+ Assert.Same(exception, thrown);
+ Assert.Same(exception, traceWriter.Traces[1].Exception);
+ }
+
+ [Fact]
+ public void OnWriteToStreamAsync_Traces()
+ {
+ // Arrange
+ Mock<MediaTypeFormatter> mockFormatter = new Mock<MediaTypeFormatter>() { CallBase = true };
+ mockFormatter.Setup(
+ f => f.WriteToStreamAsync(It.IsAny<Type>(), It.IsAny<Object>(), It.IsAny<Stream>(), It.IsAny<HttpContentHeaders>(), It.IsAny<TransportContext>())).
+ Returns(TaskHelpers.Completed());
+ TestTraceWriter traceWriter = new TestTraceWriter();
+ HttpRequestMessage request = new HttpRequestMessage();
+ request.Content = new StringContent("");
+ MediaTypeFormatterTracer tracer = new MediaTypeFormatterTracer(mockFormatter.Object, traceWriter, request);
+ TraceRecord[] expectedTraces = new TraceRecord[]
+ {
+ new TraceRecord(request, TraceCategories.FormattingCategory, TraceLevel.Info) { Kind = TraceKind.Begin, Operation = "WriteToStreamAsync" },
+ new TraceRecord(request, TraceCategories.FormattingCategory, TraceLevel.Info) { Kind = TraceKind.End, Operation = "WriteToStreamAsync" }
+ };
+
+ // Act
+ Task task = tracer.WriteToStreamAsync(typeof(string), "sampleValue", new MemoryStream(), request.Content.Headers, transportContext: null);
+ task.Wait();
+
+ // Assert
+ Assert.Equal<TraceRecord>(expectedTraces, traceWriter.Traces, new TraceRecordComparer());
+ }
+
+ [Fact]
+ public void OnWriteToStreamAsync_Traces_And_Throws_When_Inner_Throws()
+ {
+ // Arrange
+ InvalidOperationException exception = new InvalidOperationException("test");
+ Mock<MediaTypeFormatter> mockFormatter = new Mock<MediaTypeFormatter>() { CallBase = true };
+ mockFormatter.Setup(
+ f =>
+ f.WriteToStreamAsync(It.IsAny<Type>(), It.IsAny<Object>(), It.IsAny<Stream>(),
+ It.IsAny<HttpContentHeaders>(), It.IsAny<TransportContext>())).
+ Throws(exception);
+
+ TestTraceWriter traceWriter = new TestTraceWriter();
+ HttpRequestMessage request = new HttpRequestMessage();
+ request.Content = new StringContent("");
+ MediaTypeFormatterTracer tracer = new MediaTypeFormatterTracer(mockFormatter.Object, traceWriter, request);
+ TraceRecord[] expectedTraces = new TraceRecord[]
+ {
+ new TraceRecord(request, TraceCategories.FormattingCategory, TraceLevel.Info) { Kind = TraceKind.Begin, Operation = "WriteToStreamAsync" },
+ new TraceRecord(request, TraceCategories.FormattingCategory, TraceLevel.Error) { Kind = TraceKind.End, Operation = "WriteToStreamAsync" }
+ };
+
+ // Act
+ Exception thrown = Assert.Throws<InvalidOperationException>(() => tracer.WriteToStreamAsync(typeof(string), "sampleValue", new MemoryStream(), request.Content.Headers, transportContext: null));
+
+ // Assert
+ Assert.Equal<TraceRecord>(expectedTraces, traceWriter.Traces, new TraceRecordComparer());
+ Assert.Same(exception, thrown);
+ Assert.Same(exception, traceWriter.Traces[1].Exception);
+ }
+
+ [Fact]
+ public void OnWriteToStreamAsync_Traces_And_Faults_When_Inner_Faults()
+ {
+ // Arrange
+ InvalidOperationException exception = new InvalidOperationException("test");
+ Mock<MediaTypeFormatter> mockFormatter = new Mock<MediaTypeFormatter>() { CallBase = true };
+ TaskCompletionSource<object> tcs = new TaskCompletionSource<object>();
+ tcs.TrySetException(exception);
+
+ mockFormatter.Setup(
+ f => f.WriteToStreamAsync(It.IsAny<Type>(), It.IsAny<Object>(), It.IsAny<Stream>(), It.IsAny<HttpContentHeaders>(), It.IsAny<TransportContext>())).
+ Returns(tcs.Task);
+
+ TestTraceWriter traceWriter = new TestTraceWriter();
+ HttpRequestMessage request = new HttpRequestMessage();
+ request.Content = new StringContent("");
+ MediaTypeFormatterTracer tracer = new MediaTypeFormatterTracer(mockFormatter.Object, traceWriter, request);
+ TraceRecord[] expectedTraces = new TraceRecord[]
+ {
+ new TraceRecord(request, TraceCategories.FormattingCategory, TraceLevel.Info) { Kind = TraceKind.Begin, Operation = "WriteToStreamAsync" },
+ new TraceRecord(request, TraceCategories.FormattingCategory, TraceLevel.Error) { Kind = TraceKind.End, Operation = "WriteToStreamAsync" }
+ };
+
+ // Act
+ Task task = tracer.WriteToStreamAsync(typeof(string), "sampleValue", new MemoryStream(), request.Content.Headers, transportContext: null);
+
+ // Assert
+ Exception thrown = Assert.Throws<InvalidOperationException>(() => task.Wait());
+ Assert.Equal<TraceRecord>(expectedTraces, traceWriter.Traces, new TraceRecordComparer());
+ Assert.Same(exception, thrown);
+ Assert.Same(exception, traceWriter.Traces[1].Exception);
+ }
+
+ [Fact]
+ public void GetPerRequestFormatterInstance_Returns_Tracing_MediaTypeFormatter()
+ {
+ // Arrange
+ Mock<MediaTypeFormatter> mockReturnFormatter = new Mock<MediaTypeFormatter>() { CallBase = true };
+ Mock<MediaTypeFormatter> mockFormatter = new Mock<MediaTypeFormatter>() { CallBase = true };
+ mockFormatter.Setup(
+ f =>
+ f.GetPerRequestFormatterInstance(It.IsAny<Type>(), It.IsAny<HttpRequestMessage>(),
+ It.IsAny<MediaTypeHeaderValue>())).Returns(mockReturnFormatter.Object);
+
+ TestTraceWriter traceWriter = new TestTraceWriter();
+ HttpRequestMessage request = new HttpRequestMessage();
+ MediaTypeFormatterTracer tracer = new MediaTypeFormatterTracer(mockFormatter.Object, traceWriter, request);
+
+ // Act
+ MediaTypeFormatter actualFormatter = tracer.GetPerRequestFormatterInstance(typeof(string), request, new MediaTypeHeaderValue("application/json"));
+
+ // Assert
+ Assert.IsAssignableFrom<IFormatterTracer>(actualFormatter);
+ }
+
+ [Theory]
+ [InlineDataAttribute(typeof(XmlMediaTypeFormatter))]
+ [InlineDataAttribute(typeof(JsonMediaTypeFormatter))]
+ [InlineDataAttribute(typeof(FormUrlEncodedMediaTypeFormatter))]
+ public void CreateTracer_Returns_Tracing_Formatter(Type formatterType)
+ {
+ // Arrange
+ HttpRequestMessage request = new HttpRequestMessage();
+ MediaTypeFormatter formatter = (MediaTypeFormatter)Activator.CreateInstance(formatterType);
+
+ // Act
+ MediaTypeFormatter tracingFormatter = MediaTypeFormatterTracer.CreateTracer(formatter, new TestTraceWriter(), request);
+
+ // Assert
+ Assert.IsAssignableFrom<IFormatterTracer>(tracingFormatter);
+ Assert.IsAssignableFrom(formatterType, tracingFormatter);
+ }
+
+ [Fact]
+ public void CreateTracer_Returns_Tracing_BufferedFormatter()
+ {
+ // Arrange
+ HttpRequestMessage request = new HttpRequestMessage();
+ MediaTypeFormatter formatter = new Mock<BufferedMediaTypeFormatter>() {CallBase = true}.Object;
+
+ // Act
+ MediaTypeFormatter tracingFormatter = MediaTypeFormatterTracer.CreateTracer(formatter, new TestTraceWriter(), request);
+
+ // Assert
+ Assert.IsAssignableFrom<IFormatterTracer>(tracingFormatter);
+ Assert.IsAssignableFrom<BufferedMediaTypeFormatter>(tracingFormatter);
+ }
+ }
+}
diff --git a/test/System.Web.Http.Test/Tracing/Tracers/MessageHandlerTracerTest.cs b/test/System.Web.Http.Test/Tracing/Tracers/MessageHandlerTracerTest.cs
new file mode 100644
index 00000000..b5ce4cfb
--- /dev/null
+++ b/test/System.Web.Http.Test/Tracing/Tracers/MessageHandlerTracerTest.cs
@@ -0,0 +1,156 @@
+using System.Net.Http;
+using System.Reflection;
+using System.Threading;
+using System.Threading.Tasks;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Http.Tracing.Tracers
+{
+ public class MessageHandlerTracerTest
+ {
+ [Fact]
+ public void SendAsync_Traces_And_Invokes_Inner()
+ {
+ // Arrange
+ HttpResponseMessage response = new HttpResponseMessage();
+ MockDelegatingHandler mockHandler = new MockDelegatingHandler((rqst, cancellation) =>
+ TaskHelpers.FromResult<HttpResponseMessage>(response));
+
+ TestTraceWriter traceWriter = new TestTraceWriter();
+ MessageHandlerTracer tracer = new MessageHandlerTracer(mockHandler, traceWriter);
+ MockHttpMessageHandler mockInnerHandler = new MockHttpMessageHandler((rqst, cancellation) =>
+ TaskHelpers.FromResult<HttpResponseMessage>(response));
+ tracer.InnerHandler = mockInnerHandler;
+
+ HttpRequestMessage request = new HttpRequestMessage();
+ TraceRecord[] expectedTraces = new TraceRecord[]
+ {
+ new TraceRecord(request, TraceCategories.MessageHandlersCategory, TraceLevel.Info) { Kind = TraceKind.Begin, Operation = "SendAsync" },
+ new TraceRecord(request, TraceCategories.MessageHandlersCategory, TraceLevel.Info) { Kind = TraceKind.End, Operation = "SendAsync" }
+ };
+
+ MethodInfo method = typeof (DelegatingHandler).GetMethod("SendAsync",
+ BindingFlags.Public | BindingFlags.NonPublic |
+ BindingFlags.Instance);
+
+ // Act
+ Task<HttpResponseMessage> task = method.Invoke(tracer, new object[] {request, CancellationToken.None}) as Task<HttpResponseMessage>;
+ HttpResponseMessage actualResponse = task.Result;
+
+ // Assert
+ Assert.Equal<TraceRecord>(expectedTraces, traceWriter.Traces, new TraceRecordComparer());
+ Assert.Same(response, actualResponse);
+ }
+
+ [Fact]
+ public void SendAsync_Traces_And_Throws_When_Inner_Throws()
+ {
+ // Arrange
+ InvalidOperationException exception = new InvalidOperationException("test");
+ MockDelegatingHandler mockHandler = new MockDelegatingHandler((rqst, cancellation) => { throw exception; });
+
+ TestTraceWriter traceWriter = new TestTraceWriter();
+ MessageHandlerTracer tracer = new MessageHandlerTracer(mockHandler, traceWriter);
+
+ // DelegatingHandlers require an InnerHandler to run. We create a mock one to simulate what
+ // would happen when a DelegatingHandler executing after the tracer throws.
+ MockHttpMessageHandler mockInnerHandler = new MockHttpMessageHandler((rqst, cancellation) => { throw exception; });
+ tracer.InnerHandler = mockInnerHandler;
+
+ HttpRequestMessage request = new HttpRequestMessage();
+ TraceRecord[] expectedTraces = new TraceRecord[]
+ {
+ new TraceRecord(request, TraceCategories.MessageHandlersCategory, TraceLevel.Info) { Kind = TraceKind.Begin, Operation = "SendAsync" },
+ new TraceRecord(request, TraceCategories.MessageHandlersCategory, TraceLevel.Error) { Kind = TraceKind.End, Operation = "SendAsync" }
+ };
+
+ MethodInfo method = typeof(DelegatingHandler).GetMethod("SendAsync",
+ BindingFlags.Public | BindingFlags.NonPublic |
+ BindingFlags.Instance);
+
+ // Act
+ Exception thrown =
+ Assert.Throws<TargetInvocationException>(
+ () => method.Invoke(tracer, new object[] {request, CancellationToken.None}));
+
+ // Assert
+ Assert.Equal<TraceRecord>(expectedTraces, traceWriter.Traces, new TraceRecordComparer());
+ Assert.Same(exception, thrown.InnerException);
+ Assert.Same(exception, traceWriter.Traces[1].Exception);
+ }
+
+ [Fact]
+ public void SendAsync_Traces_And_Faults_When_Inner_Faults()
+ {
+ // Arrange
+ InvalidOperationException exception = new InvalidOperationException("test");
+ TaskCompletionSource<HttpResponseMessage> tcs = new TaskCompletionSource<HttpResponseMessage>();
+ tcs.TrySetException(exception);
+ MockDelegatingHandler mockHandler = new MockDelegatingHandler((rqst, cancellation) => { return tcs.Task; });
+
+ TestTraceWriter traceWriter = new TestTraceWriter();
+ MessageHandlerTracer tracer = new MessageHandlerTracer(mockHandler, traceWriter);
+
+ // DelegatingHandlers require an InnerHandler to run. We create a mock one to simulate what
+ // would happen when a DelegatingHandler executing after the tracer returns a Task that throws.
+ MockHttpMessageHandler mockInnerHandler = new MockHttpMessageHandler((rqst, cancellation) => { return tcs.Task; });
+ tracer.InnerHandler = mockInnerHandler;
+
+ HttpRequestMessage request = new HttpRequestMessage();
+ TraceRecord[] expectedTraces = new TraceRecord[]
+ {
+ new TraceRecord(request, TraceCategories.MessageHandlersCategory, TraceLevel.Info) { Kind = TraceKind.Begin, Operation = "SendAsync" },
+ new TraceRecord(request, TraceCategories.MessageHandlersCategory, TraceLevel.Error) { Kind = TraceKind.End, Operation = "SendAsync" }
+ };
+
+ MethodInfo method = typeof(DelegatingHandler).GetMethod("SendAsync",
+ BindingFlags.Public | BindingFlags.NonPublic |
+ BindingFlags.Instance);
+
+ // Act
+ Task<HttpResponseMessage> task =
+ method.Invoke(tracer, new object[] {request, CancellationToken.None}) as Task<HttpResponseMessage>;
+
+ // Assert
+ Exception thrown = Assert.Throws<InvalidOperationException>(() => task.Wait());
+ Assert.Equal<TraceRecord>(expectedTraces, traceWriter.Traces, new TraceRecordComparer());
+ Assert.Same(exception, thrown);
+ Assert.Same(exception, traceWriter.Traces[1].Exception);
+ }
+
+
+ // DelegatingHandler cannot be mocked with Moq
+ private class MockDelegatingHandler : DelegatingHandler
+ {
+ private Func<HttpRequestMessage, CancellationToken, Task<HttpResponseMessage>> _callback;
+
+ public MockDelegatingHandler(Func<HttpRequestMessage, CancellationToken, Task<HttpResponseMessage>> callback) : base()
+ {
+ _callback = callback;
+ }
+
+ protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
+ {
+ return _callback(request, cancellationToken);
+ }
+ }
+
+ // HttpMessageHandler cannot be mocked with Moq
+ private class MockHttpMessageHandler : HttpMessageHandler
+ {
+ private Func<HttpRequestMessage, CancellationToken, Task<HttpResponseMessage>> _callback;
+
+ public MockHttpMessageHandler(Func<HttpRequestMessage, CancellationToken, Task<HttpResponseMessage>> callback)
+ : base()
+ {
+ _callback = callback;
+ }
+
+ protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
+ {
+ return _callback(request, cancellationToken);
+ }
+ }
+ }
+}
diff --git a/test/System.Web.Http.Test/Tracing/Tracers/RequestMessageHandlerTracerTest.cs b/test/System.Web.Http.Test/Tracing/Tracers/RequestMessageHandlerTracerTest.cs
new file mode 100644
index 00000000..35ffaa25
--- /dev/null
+++ b/test/System.Web.Http.Test/Tracing/Tracers/RequestMessageHandlerTracerTest.cs
@@ -0,0 +1,150 @@
+using System.Net.Http;
+using System.Reflection;
+using System.Threading;
+using System.Threading.Tasks;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Http.Tracing.Tracers
+{
+ public class RequestMessageHandlerTracerTest
+ {
+ [Fact]
+ public void SendAsync_Traces_And_Invokes_Inner()
+ {
+ // Arrange
+ HttpResponseMessage response = new HttpResponseMessage();
+ TestTraceWriter traceWriter = new TestTraceWriter();
+ RequestMessageHandlerTracer tracer = new RequestMessageHandlerTracer(traceWriter);
+ MockHttpMessageHandler mockInnerHandler = new MockHttpMessageHandler((rqst, cancellation) =>
+ TaskHelpers.FromResult<HttpResponseMessage>(response));
+ tracer.InnerHandler = mockInnerHandler;
+
+ HttpRequestMessage request = new HttpRequestMessage();
+ TraceRecord[] expectedTraces = new TraceRecord[]
+ {
+ new TraceRecord(request, TraceCategories.RequestCategory, TraceLevel.Info) { Kind = TraceKind.Begin },
+ new TraceRecord(request, TraceCategories.RequestCategory, TraceLevel.Info) { Kind = TraceKind.End }
+ };
+
+ MethodInfo method = typeof(DelegatingHandler).GetMethod("SendAsync",
+ BindingFlags.Public | BindingFlags.NonPublic |
+ BindingFlags.Instance);
+
+ // Act
+ Task<HttpResponseMessage> task = method.Invoke(tracer, new object[] { request, CancellationToken.None }) as Task<HttpResponseMessage>;
+ HttpResponseMessage actualResponse = task.Result;
+
+ // Assert
+ Assert.Equal<TraceRecord>(expectedTraces, traceWriter.Traces, new TraceRecordComparer());
+ Assert.Same(response, actualResponse);
+ }
+
+ [Fact]
+ public void SendAsync_Traces_And_Throws_When_Inner_Throws()
+ {
+ // Arrange
+ InvalidOperationException exception = new InvalidOperationException("test");
+ TestTraceWriter traceWriter = new TestTraceWriter();
+ RequestMessageHandlerTracer tracer = new RequestMessageHandlerTracer(traceWriter);
+
+ // DelegatingHandlers require an InnerHandler to run. We create a mock one to simulate what
+ // would happen when a DelegatingHandler executing after the tracer throws.
+ MockHttpMessageHandler mockInnerHandler = new MockHttpMessageHandler((rqst, cancellation) => { throw exception; });
+ tracer.InnerHandler = mockInnerHandler;
+
+ HttpRequestMessage request = new HttpRequestMessage();
+ TraceRecord[] expectedTraces = new TraceRecord[]
+ {
+ new TraceRecord(request, TraceCategories.RequestCategory, TraceLevel.Info) { Kind = TraceKind.Begin },
+ new TraceRecord(request, TraceCategories.RequestCategory, TraceLevel.Error) { Kind = TraceKind.End }
+ };
+
+ MethodInfo method = typeof(DelegatingHandler).GetMethod("SendAsync",
+ BindingFlags.Public | BindingFlags.NonPublic |
+ BindingFlags.Instance);
+
+ // Act
+ Exception thrown =
+ Assert.Throws<TargetInvocationException>(
+ () => method.Invoke(tracer, new object[] { request, CancellationToken.None }));
+
+ // Assert
+ Assert.Equal<TraceRecord>(expectedTraces, traceWriter.Traces, new TraceRecordComparer());
+ Assert.Same(exception, thrown.InnerException);
+ Assert.Same(exception, traceWriter.Traces[1].Exception);
+ }
+
+ [Fact]
+ public void SendAsync_Traces_And_Faults_When_Inner_Faults()
+ {
+ // Arrange
+ InvalidOperationException exception = new InvalidOperationException("test");
+ TaskCompletionSource<HttpResponseMessage> tcs = new TaskCompletionSource<HttpResponseMessage>();
+ tcs.TrySetException(exception);
+ TestTraceWriter traceWriter = new TestTraceWriter();
+ RequestMessageHandlerTracer tracer = new RequestMessageHandlerTracer(traceWriter);
+
+ // DelegatingHandlers require an InnerHandler to run. We create a mock one to simulate what
+ // would happen when a DelegatingHandler executing after the tracer returns a Task that throws.
+ MockHttpMessageHandler mockInnerHandler = new MockHttpMessageHandler((rqst, cancellation) => { return tcs.Task; });
+ tracer.InnerHandler = mockInnerHandler;
+
+ HttpRequestMessage request = new HttpRequestMessage();
+ TraceRecord[] expectedTraces = new TraceRecord[]
+ {
+ new TraceRecord(request, TraceCategories.RequestCategory, TraceLevel.Info) { Kind = TraceKind.Begin },
+ new TraceRecord(request, TraceCategories.RequestCategory, TraceLevel.Error) { Kind = TraceKind.End }
+ };
+
+ MethodInfo method = typeof(DelegatingHandler).GetMethod("SendAsync",
+ BindingFlags.Public | BindingFlags.NonPublic |
+ BindingFlags.Instance);
+
+ // Act
+ Task<HttpResponseMessage> task =
+ method.Invoke(tracer, new object[] { request, CancellationToken.None }) as Task<HttpResponseMessage>;
+
+ // Assert
+ Exception thrown = Assert.Throws<InvalidOperationException>(() => task.Wait());
+ Assert.Equal<TraceRecord>(expectedTraces, traceWriter.Traces, new TraceRecordComparer());
+ Assert.Same(exception, thrown);
+ Assert.Same(exception, traceWriter.Traces[1].Exception);
+ }
+
+
+ // DelegatingHandler cannot be mocked with Moq
+ private class MockDelegatingHandler : DelegatingHandler
+ {
+ private Func<HttpRequestMessage, CancellationToken, Task<HttpResponseMessage>> _callback;
+
+ public MockDelegatingHandler(Func<HttpRequestMessage, CancellationToken, Task<HttpResponseMessage>> callback)
+ : base()
+ {
+ _callback = callback;
+ }
+
+ protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
+ {
+ return _callback(request, cancellationToken);
+ }
+ }
+
+ // HttpMessageHandler cannot be mocked with Moq
+ private class MockHttpMessageHandler : HttpMessageHandler
+ {
+ private Func<HttpRequestMessage, CancellationToken, Task<HttpResponseMessage>> _callback;
+
+ public MockHttpMessageHandler(Func<HttpRequestMessage, CancellationToken, Task<HttpResponseMessage>> callback)
+ : base()
+ {
+ _callback = callback;
+ }
+
+ protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
+ {
+ return _callback(request, cancellationToken);
+ }
+ }
+ }
+}
diff --git a/test/System.Web.Http.Test/Util/ContextUtil.cs b/test/System.Web.Http.Test/Util/ContextUtil.cs
new file mode 100644
index 00000000..e1cc7c68
--- /dev/null
+++ b/test/System.Web.Http.Test/Util/ContextUtil.cs
@@ -0,0 +1,48 @@
+using System.Net.Http;
+using System.Web.Http.Controllers;
+using System.Web.Http.Filters;
+using System.Web.Http.Routing;
+using Moq;
+
+namespace System.Web.Http
+{
+ internal static class ContextUtil
+ {
+ public static HttpControllerContext CreateControllerContext(HttpConfiguration configuration = null, IHttpController instance = null, IHttpRouteData routeData = null, HttpRequestMessage request = null)
+ {
+ HttpConfiguration config = configuration ?? new HttpConfiguration();
+ IHttpRouteData route = routeData ?? new HttpRouteData(new HttpRoute());
+ HttpRequestMessage req = request ?? new HttpRequestMessage();
+
+ HttpControllerContext context = new HttpControllerContext(config, route, req);
+ if (instance != null)
+ {
+ context.Controller = instance;
+ }
+
+ return context;
+ }
+
+ public static HttpActionContext CreateActionContext(HttpControllerContext controllerContext = null, HttpActionDescriptor actionDescriptor = null)
+ {
+ HttpControllerContext context = controllerContext ?? ContextUtil.CreateControllerContext();
+ HttpActionDescriptor descriptor = actionDescriptor ?? new Mock<HttpActionDescriptor>() { CallBase = true }.Object;
+ return new HttpActionContext(context, descriptor);
+ }
+
+ public static HttpActionContext GetHttpActionContext(HttpRequestMessage request)
+ {
+ HttpActionContext actionContext = CreateActionContext();
+ actionContext.ControllerContext.Request = request;
+ return actionContext;
+ }
+
+ public static HttpActionExecutedContext GetActionExecutedContext(HttpRequestMessage request, HttpResponseMessage response)
+ {
+ HttpActionContext actionContext = CreateActionContext();
+ actionContext.ControllerContext.Request = request;
+ HttpActionExecutedContext actionExecutedContext = new HttpActionExecutedContext(actionContext, null) { Result = response };
+ return actionExecutedContext;
+ }
+ }
+}
diff --git a/test/System.Web.Http.Test/Util/SimpleHttpValueProvider.cs b/test/System.Web.Http.Test/Util/SimpleHttpValueProvider.cs
new file mode 100644
index 00000000..31c1ad8a
--- /dev/null
+++ b/test/System.Web.Http.Test/Util/SimpleHttpValueProvider.cs
@@ -0,0 +1,70 @@
+using System.Collections.Generic;
+using System.Globalization;
+using System.Web.Http.ValueProviders;
+
+namespace System.Web.Http.Util
+{
+ public sealed class SimpleHttpValueProvider : Dictionary<string, object>, IValueProvider
+ {
+ private readonly CultureInfo _culture;
+
+ public SimpleHttpValueProvider()
+ : this(null)
+ {
+ }
+
+ public SimpleHttpValueProvider(CultureInfo culture)
+ : base(StringComparer.OrdinalIgnoreCase)
+ {
+ _culture = culture ?? CultureInfo.InvariantCulture;
+ }
+
+ // copied from ValueProviderUtil
+ public bool ContainsPrefix(string prefix)
+ {
+ foreach (string key in Keys)
+ {
+ if (key != null)
+ {
+ if (prefix.Length == 0)
+ {
+ return true; // shortcut - non-null key matches empty prefix
+ }
+
+ if (key.StartsWith(prefix, StringComparison.OrdinalIgnoreCase))
+ {
+ if (key.Length == prefix.Length)
+ {
+ return true; // exact match
+ }
+ else
+ {
+ switch (key[prefix.Length])
+ {
+ case '.': // known separator characters
+ case '[':
+ return true;
+ }
+ }
+ }
+ }
+ }
+
+ return false; // nothing found
+ }
+
+ public ValueProviderResult GetValue(string key)
+ {
+ object rawValue;
+ if (TryGetValue(key, out rawValue))
+ {
+ return new ValueProviderResult(rawValue, Convert.ToString(rawValue, _culture), _culture);
+ }
+ else
+ {
+ // value not found
+ return null;
+ }
+ }
+ }
+}
diff --git a/test/System.Web.Http.Test/Validation/DefaultBodyModelValidatorTest.cs b/test/System.Web.Http.Test/Validation/DefaultBodyModelValidatorTest.cs
new file mode 100644
index 00000000..9d8cfe4e
--- /dev/null
+++ b/test/System.Web.Http.Test/Validation/DefaultBodyModelValidatorTest.cs
@@ -0,0 +1,160 @@
+using System.Collections.Generic;
+using System.ComponentModel.DataAnnotations;
+using System.Web.Http.Controllers;
+using System.Web.Http.Metadata;
+using System.Web.Http.Metadata.Providers;
+using System.Web.Http.ModelBinding;
+using Microsoft.TestCommon;
+using Xunit;
+using Xunit.Extensions;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Http.Validation
+{
+ public class DefaultBodyModelValidatorTest
+ {
+ private static Person LonelyPerson;
+
+ static DefaultBodyModelValidatorTest()
+ {
+ LonelyPerson = new Person() { Name = "Reallllllllly Long Name" };
+ LonelyPerson.Friend = LonelyPerson;
+ }
+
+ public static IEnumerable<object[]> ValidationErrors
+ {
+ get
+ {
+ return new TheoryDataSet<object, Type, Dictionary<string, string>>()
+ {
+ // Primitives
+ { null, typeof(Person), new Dictionary<string, string>() },
+ { 14, typeof(int), new Dictionary<string, string>() },
+ { "foo", typeof(string), new Dictionary<string, string>() },
+
+ // Classes
+ { new Person() { Name = "Rick", Profession = "Astronaut" }, typeof(Person), new Dictionary<string, string>() },
+ { new Person(), typeof(Person), new Dictionary<string, string>()
+ {
+ { "Name", "The Name field is required." },
+ { "Profession", "The Profession field is required." }
+ }
+ },
+
+ { new Person() { Name = "Rick", Friend = new Person() }, typeof(Person), new Dictionary<string, string>()
+ {
+ { "Profession", "The Profession field is required." },
+ { "Friend.Name", "The Name field is required." },
+ { "Friend.Profession", "The Profession field is required." }
+ }
+ },
+
+ // Collections
+ { new Person[] { new Person(), new Person() }, typeof(Person[]), new Dictionary<string, string>()
+ {
+ { "[0].Name", "The Name field is required." },
+ { "[0].Profession", "The Profession field is required." },
+ { "[1].Name", "The Name field is required." },
+ { "[1].Profession", "The Profession field is required." }
+ }
+ },
+
+ { new List<Person> { new Person(), new Person() }, typeof(Person[]), new Dictionary<string, string>()
+ {
+ { "[0].Name", "The Name field is required." },
+ { "[0].Profession", "The Profession field is required." },
+ { "[1].Name", "The Name field is required." },
+ { "[1].Profession", "The Profession field is required." }
+ }
+ },
+
+ { new Dictionary<string, Person> { { "Joe", new Person() } , { "Mark", new Person() } }, typeof(Dictionary<string, Person>), new Dictionary<string, string>()
+ {
+ { "[0].Value.Name", "The Name field is required." },
+ { "[0].Value.Profession", "The Profession field is required." },
+ { "[1].Value.Name", "The Name field is required." },
+ { "[1].Value.Profession", "The Profession field is required." }
+ }
+ },
+
+ // Testing we don't blow up on cycles
+ { LonelyPerson, typeof(Person), new Dictionary<string, string>()
+ {
+ { "Name", "The field Name must be a string with a maximum length of 10." },
+ { "Profession", "The Profession field is required." }
+ }
+ },
+ };
+ }
+ }
+
+ [Theory]
+ [PropertyData("ValidationErrors")]
+ public void ExpectedValidationErrorsRaised(object model, Type type, Dictionary<string, string> expectedErrors)
+ {
+ // Arrange
+ ModelMetadataProvider metadataProvider = new CachedDataAnnotationsModelMetadataProvider();
+ HttpActionContext actionContext = ContextUtil.CreateActionContext();
+
+ // Act
+ Assert.DoesNotThrow(() =>
+ new DefaultBodyModelValidator().Validate(model, type, metadataProvider, actionContext, string.Empty)
+ );
+
+ // Assert
+ Dictionary<string, string> actualErrors = new Dictionary<string, string>();
+ foreach (KeyValuePair<string, ModelState> keyStatePair in actionContext.ModelState)
+ {
+ foreach (ModelError error in keyStatePair.Value.Errors)
+ {
+ actualErrors.Add(keyStatePair.Key, error.ErrorMessage);
+ }
+ }
+
+ Assert.Equal(expectedErrors.Count, actualErrors.Count);
+ foreach (KeyValuePair<string, string> keyErrorPair in expectedErrors)
+ {
+ Assert.Contains(keyErrorPair.Key, actualErrors.Keys);
+ Assert.Equal(keyErrorPair.Value, actualErrors[keyErrorPair.Key]);
+ }
+ }
+
+ [Fact]
+ public void MultipleValidationErrorsOnSameMemberReported()
+ {
+ // Arrange
+ ModelMetadataProvider metadataProvider = new CachedDataAnnotationsModelMetadataProvider();
+ HttpActionContext actionContext = ContextUtil.CreateActionContext();
+ object model = new Address() { Street = "Microsoft Way" };
+
+ // Act
+ Assert.DoesNotThrow(() =>
+ new DefaultBodyModelValidator().Validate(model, typeof(Address), metadataProvider, actionContext, string.Empty)
+ );
+
+ // Assert
+ Assert.Contains("Street", actionContext.ModelState.Keys);
+ ModelState streetState = actionContext.ModelState["Street"];
+ Assert.Equal(2, streetState.Errors.Count);
+ }
+ }
+
+ public class Person
+ {
+ [Required]
+ [StringLength(10)]
+ public string Name { get; set; }
+
+ [Required]
+ public string Profession { get; set; }
+
+ public Person Friend { get; set; }
+ }
+
+ public class Address
+ {
+ [StringLength(5)]
+ [RegularExpression("hehehe")]
+ public string Street { get; set; }
+ }
+}
diff --git a/test/System.Web.Http.Test/Validation/ModelValidationNodeTest.cs b/test/System.Web.Http.Test/Validation/ModelValidationNodeTest.cs
new file mode 100644
index 00000000..9ad5d2d8
--- /dev/null
+++ b/test/System.Web.Http.Test/Validation/ModelValidationNodeTest.cs
@@ -0,0 +1,331 @@
+using System.Collections.Generic;
+using System.ComponentModel.DataAnnotations;
+using System.Linq;
+using System.Web.Http.Controllers;
+using System.Web.Http.Metadata;
+using System.Web.Http.Metadata.Providers;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Http.Validation
+{
+ public class ModelValidationNodeTest
+ {
+ [Fact]
+ public void ConstructorSetsCollectionInstance()
+ {
+ // Arrange
+ ModelMetadata metadata = GetModelMetadata();
+ string modelStateKey = "someKey";
+ ModelValidationNode[] childNodes = new[]
+ {
+ new ModelValidationNode(metadata, "someKey0"),
+ new ModelValidationNode(metadata, "someKey1")
+ };
+
+ // Act
+ ModelValidationNode node = new ModelValidationNode(metadata, modelStateKey, childNodes);
+
+ // Assert
+ Assert.Equal(childNodes, node.ChildNodes.ToArray());
+ }
+
+ [Fact]
+ public void ConstructorThrowsIfModelMetadataIsNull()
+ {
+ // Act & assert
+ Assert.ThrowsArgumentNull(
+ () => new ModelValidationNode(null, "someKey"),
+ "modelMetadata");
+ }
+
+ [Fact]
+ public void ConstructorThrowsIfModelStateKeyIsNull()
+ {
+ // Arrange
+ ModelMetadata metadata = GetModelMetadata();
+
+ // Act & assert
+ Assert.ThrowsArgumentNull(
+ () => new ModelValidationNode(metadata, null),
+ "modelStateKey");
+ }
+
+ [Fact]
+ public void PropertiesAreSet()
+ {
+ // Arrange
+ ModelMetadata metadata = GetModelMetadata();
+ string modelStateKey = "someKey";
+
+ // Act
+ ModelValidationNode node = new ModelValidationNode(metadata, modelStateKey);
+
+ // Assert
+ Assert.Equal(metadata, node.ModelMetadata);
+ Assert.Equal(modelStateKey, node.ModelStateKey);
+ Assert.NotNull(node.ChildNodes);
+ Assert.Empty(node.ChildNodes);
+ }
+
+ [Fact]
+ public void CombineWith()
+ {
+ // Arrange
+ List<string> log = new List<string>();
+
+ ModelValidationNode[] allChildNodes = new[]
+ {
+ new ModelValidationNode(GetModelMetadata(), "key1"),
+ new ModelValidationNode(GetModelMetadata(), "key2"),
+ new ModelValidationNode(GetModelMetadata(), "key3"),
+ };
+
+ ModelValidationNode parentNode1 = new ModelValidationNode(GetModelMetadata(), "parent1");
+ parentNode1.ChildNodes.Add(allChildNodes[0]);
+ parentNode1.Validating += (sender, e) => log.Add("Validating parent1.");
+ parentNode1.Validated += (sender, e) => log.Add("Validated parent1.");
+
+ ModelValidationNode parentNode2 = new ModelValidationNode(GetModelMetadata(), "parent2");
+ parentNode2.ChildNodes.Add(allChildNodes[1]);
+ parentNode2.ChildNodes.Add(allChildNodes[2]);
+ parentNode2.Validating += (sender, e) => log.Add("Validating parent2.");
+ parentNode2.Validated += (sender, e) => log.Add("Validated parent2.");
+
+ // Act
+ parentNode1.CombineWith(parentNode2);
+ parentNode1.Validate(ContextUtil.CreateActionContext());
+
+ // Assert
+ Assert.Equal(new[] { "Validating parent1.", "Validating parent2.", "Validated parent1.", "Validated parent2." }, log.ToArray());
+ Assert.Equal(allChildNodes, parentNode1.ChildNodes.ToArray());
+ }
+
+ [Fact]
+ public void CombineWith_OtherNodeIsSuppressed_DoesNothing()
+ {
+ // Arrange
+ List<string> log = new List<string>();
+
+ ModelValidationNode[] allChildNodes = new[]
+ {
+ new ModelValidationNode(GetModelMetadata(), "key1"),
+ new ModelValidationNode(GetModelMetadata(), "key2"),
+ new ModelValidationNode(GetModelMetadata(), "key3"),
+ };
+
+ ModelValidationNode[] expectedChildNodes = new[]
+ {
+ allChildNodes[0]
+ };
+
+ ModelValidationNode parentNode1 = new ModelValidationNode(GetModelMetadata(), "parent1");
+ parentNode1.ChildNodes.Add(allChildNodes[0]);
+ parentNode1.Validating += (sender, e) => log.Add("Validating parent1.");
+ parentNode1.Validated += (sender, e) => log.Add("Validated parent1.");
+
+ ModelValidationNode parentNode2 = new ModelValidationNode(GetModelMetadata(), "parent2");
+ parentNode2.ChildNodes.Add(allChildNodes[1]);
+ parentNode2.ChildNodes.Add(allChildNodes[2]);
+ parentNode2.Validating += (sender, e) => log.Add("Validating parent2.");
+ parentNode2.Validated += (sender, e) => log.Add("Validated parent2.");
+ parentNode2.SuppressValidation = true;
+
+ // Act
+ parentNode1.CombineWith(parentNode2);
+ parentNode1.Validate(ContextUtil.CreateActionContext());
+
+ // Assert
+ Assert.Equal(new[] { "Validating parent1.", "Validated parent1." }, log.ToArray());
+ Assert.Equal(expectedChildNodes, parentNode1.ChildNodes.ToArray());
+ }
+
+ [Fact]
+ public void Validate_Ordering()
+ {
+ // Proper order of invocation:
+ // 1. OnValidating()
+ // 2. Child validators
+ // 3. This validator
+ // 4. OnValidated()
+
+ // Arrange
+ List<string> log = new List<string>();
+ LoggingValidatableObject model = new LoggingValidatableObject(log);
+ ModelMetadata modelMetadata = GetModelMetadata(model);
+ ModelMetadata childMetadata = new EmptyModelMetadataProvider().GetMetadataForProperty(() => model, model.GetType(), "ValidStringProperty");
+ ModelValidationNode node = new ModelValidationNode(modelMetadata, "theKey");
+ node.Validating += (sender, e) => log.Add("In OnValidating()");
+ node.Validated += (sender, e) => log.Add("In OnValidated()");
+ node.ChildNodes.Add(new ModelValidationNode(childMetadata, "theKey.ValidStringProperty"));
+
+ // Act
+ node.Validate(ContextUtil.CreateActionContext());
+
+ // Assert
+ Assert.Equal(new[] { "In OnValidating()", "In LoggingValidatonAttribute.IsValid()", "In IValidatableObject.Validate()", "In OnValidated()" }, log.ToArray());
+ }
+
+ [Fact]
+ public void Validate_SkipsRemainingValidationIfModelStateIsInvalid()
+ {
+ // Because a property validator fails, the model validator shouldn't run
+
+ // Arrange
+ List<string> log = new List<string>();
+ LoggingValidatableObject model = new LoggingValidatableObject(log);
+ ModelMetadata modelMetadata = GetModelMetadata(model);
+ ModelMetadata childMetadata = new EmptyModelMetadataProvider().GetMetadataForProperty(() => model, model.GetType(), "InvalidStringProperty");
+ ModelValidationNode node = new ModelValidationNode(modelMetadata, "theKey");
+ node.ChildNodes.Add(new ModelValidationNode(childMetadata, "theKey.InvalidStringProperty"));
+ node.Validating += (sender, e) => log.Add("In OnValidating()");
+ node.Validated += (sender, e) => log.Add("In OnValidated()");
+ HttpActionContext context = ContextUtil.CreateActionContext();
+
+ // Act
+ node.Validate(context);
+
+ // Assert
+ Assert.Equal(new[] { "In OnValidating()", "In IValidatableObject.Validate()", "In OnValidated()" }, log.ToArray());
+ Assert.Equal("Sample error message", context.ModelState["theKey.InvalidStringProperty"].Errors[0].ErrorMessage);
+ }
+
+ [Fact]
+ public void Validate_SkipsValidationIfHandlerCancels()
+ {
+ // Arrange
+ List<string> log = new List<string>();
+ LoggingValidatableObject model = new LoggingValidatableObject(log);
+ ModelMetadata modelMetadata = GetModelMetadata(model);
+ ModelValidationNode node = new ModelValidationNode(modelMetadata, "theKey");
+ node.Validating += (sender, e) =>
+ {
+ log.Add("In OnValidating()");
+ e.Cancel = true;
+ };
+ node.Validated += (sender, e) => log.Add("In OnValidated()");
+
+ // Act
+ node.Validate(ContextUtil.CreateActionContext());
+
+ // Assert
+ Assert.Equal(new[] { "In OnValidating()" }, log.ToArray());
+ }
+
+ [Fact]
+ public void Validate_SkipsValidationIfSuppressed()
+ {
+ // Arrange
+ List<string> log = new List<string>();
+ LoggingValidatableObject model = new LoggingValidatableObject(log);
+ ModelMetadata modelMetadata = GetModelMetadata(model);
+ ModelValidationNode node = new ModelValidationNode(modelMetadata, "theKey")
+ {
+ SuppressValidation = true
+ };
+
+ node.Validating += (sender, e) => log.Add("In OnValidating()");
+ node.Validated += (sender, e) => log.Add("In OnValidated()");
+
+ // Act
+ node.Validate(ContextUtil.CreateActionContext());
+
+ // Assert
+ Assert.Empty(log);
+ }
+
+ [Fact]
+ public void Validate_ThrowsIfControllerContextIsNull()
+ {
+ // Arrange
+ ModelValidationNode node = new ModelValidationNode(GetModelMetadata(), "someKey");
+
+ // Act & assert
+ Assert.ThrowsArgumentNull(
+ () => node.Validate(null),
+ "actionContext");
+ }
+
+ [Fact]
+ public void Validate_ValidateAllProperties_AddsValidationErrors()
+ {
+ // Arrange
+ ValidateAllPropertiesModel model = new ValidateAllPropertiesModel
+ {
+ RequiredString = null /* error */,
+ RangedInt = 0 /* error */,
+ ValidString = "dog"
+ };
+
+ ModelMetadata modelMetadata = GetModelMetadata(model);
+ HttpActionContext context = ContextUtil.CreateActionContext();
+ ModelValidationNode node = new ModelValidationNode(modelMetadata, "theKey")
+ {
+ ValidateAllProperties = true
+ };
+ context.ModelState.AddModelError("theKey.RequiredString.Dummy", "existing Error Text");
+
+ // Act
+ node.Validate(context);
+
+ // Assert
+ Assert.Null(context.ModelState["theKey.RequiredString"]);
+ Assert.Equal("existing Error Text", context.ModelState["theKey.RequiredString.Dummy"].Errors[0].ErrorMessage);
+ Assert.Equal("The field RangedInt must be between 10 and 30.", context.ModelState["theKey.RangedInt"].Errors[0].ErrorMessage);
+ Assert.Null(context.ModelState["theKey.ValidString"]);
+ Assert.Null(context.ModelState["theKey"]);
+ }
+
+ private static ModelMetadata GetModelMetadata()
+ {
+ return new EmptyModelMetadataProvider().GetMetadataForType(null, typeof(object));
+ }
+
+ private static ModelMetadata GetModelMetadata(object o)
+ {
+ return new CachedDataAnnotationsModelMetadataProvider().GetMetadataForType(() => o, o.GetType());
+ }
+
+ private sealed class LoggingValidatableObject : IValidatableObject
+ {
+ private readonly IList<string> _log;
+
+ public LoggingValidatableObject(IList<string> log)
+ {
+ _log = log;
+ }
+
+ [LoggingValidation]
+ public string ValidStringProperty { get; set; }
+ public string InvalidStringProperty { get; set; }
+
+ public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
+ {
+ _log.Add("In IValidatableObject.Validate()");
+ yield return new ValidationResult("Sample error message", new[] { "InvalidStringProperty" });
+ }
+
+ private sealed class LoggingValidationAttribute : ValidationAttribute
+ {
+ protected override ValidationResult IsValid(object value, ValidationContext validationContext)
+ {
+ LoggingValidatableObject lvo = (LoggingValidatableObject)value;
+ lvo._log.Add("In LoggingValidatonAttribute.IsValid()");
+ return ValidationResult.Success;
+ }
+ }
+ }
+
+ private class ValidateAllPropertiesModel
+ {
+ [Required]
+ public string RequiredString { get; set; }
+
+ [Range(10, 30)]
+ public int RangedInt { get; set; }
+
+ [RegularExpression("dog")]
+ public string ValidString { get; set; }
+ }
+ }
+}
diff --git a/test/System.Web.Http.Test/Validation/ModelValidationRequiredMemberSelectorTest.cs b/test/System.Web.Http.Test/Validation/ModelValidationRequiredMemberSelectorTest.cs
new file mode 100644
index 00000000..70b8ef20
--- /dev/null
+++ b/test/System.Web.Http.Test/Validation/ModelValidationRequiredMemberSelectorTest.cs
@@ -0,0 +1,43 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.ComponentModel.DataAnnotations;
+using System.Runtime.Serialization;
+using System.Net.Http.Formatting;
+using Xunit;
+using Xunit.Extensions;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Http.Validation
+{
+ public class ModelValidationRequiredMemberSelectorTest
+ {
+ [Theory]
+ [InlineData("Customer", true)]
+ [InlineData("ID", true)]
+ [InlineData("Item", true)]
+ [InlineData("UselessInfo", false)]
+ public void RequiredMembersRecognized(string propertyName, bool isRequired)
+ {
+ IRequiredMemberSelector selector = new ModelValidationRequiredMemberSelector(new HttpConfiguration());
+ Assert.Equal(isRequired, selector.IsRequiredMember(typeof(PurchaseOrder).GetProperty(propertyName)));
+ }
+ }
+
+ [DataContract]
+ public class PurchaseOrder
+ {
+ [Required]
+ public string Customer { get; set; }
+
+ [DataMember(IsRequired=true)]
+ public int ID { get; set; }
+
+ [Required]
+ [DataMember(IsRequired=true)]
+ public string Item { get; set; }
+
+ public string UselessInfo { get; set; }
+ }
+}
diff --git a/test/System.Web.Http.Test/ValueProviders/Providers/KeyValueModelValueProviderTest.cs b/test/System.Web.Http.Test/ValueProviders/Providers/KeyValueModelValueProviderTest.cs
new file mode 100644
index 00000000..5609b3d2
--- /dev/null
+++ b/test/System.Web.Http.Test/ValueProviders/Providers/KeyValueModelValueProviderTest.cs
@@ -0,0 +1,217 @@
+using System.Collections.Generic;
+using System.Globalization;
+using System.Net.Http.Formatting;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Http.ValueProviders.Providers
+{
+ public class KeyValueModelValueProviderTest
+ {
+ private readonly IKeyValueModel _keyValueModel;
+
+ public KeyValueModelValueProviderTest()
+ {
+ Dictionary<string, object> values = new Dictionary<string, object>();
+ values["foo"] = "fooValue";
+ values["int"] = 55;
+ values["bar.baz"] = "someOtherValue";
+
+ _keyValueModel = new KeyValueModel(values);
+ }
+
+ [Fact]
+ public void ContainsPrefix_GuardClauses()
+ {
+ // Arrange
+ var valueProvider = new KeyValueModelValueProvider(_keyValueModel, null);
+
+ // Act & assert
+ Assert.ThrowsArgumentNull(() => valueProvider.ContainsPrefix(null), "prefix");
+ }
+
+ [Fact]
+ public void ContainsPrefix_WithEmptyCollection_ReturnsFalseForEmptyPrefix()
+ {
+ // Arrange
+ var valueProvider = new KeyValueModelValueProvider(new KeyValueModel(new Dictionary<string, object>()), null);
+
+ // Act
+ bool result = valueProvider.ContainsPrefix("");
+
+ // Assert
+ Assert.False(result);
+ }
+
+ [Fact]
+ public void ContainsPrefix_WithNonEmptyCollection_ReturnsTrueForEmptyPrefix()
+ {
+ // Arrange
+ var valueProvider = new KeyValueModelValueProvider(_keyValueModel, null);
+
+ // Act
+ bool result = valueProvider.ContainsPrefix("");
+
+ // Assert
+ Assert.True(result);
+ }
+
+ [Fact]
+ public void ContainsPrefix_WithNonEmptyCollection_ReturnsTrueForKnownPrefixes()
+ {
+ // Arrange
+ var valueProvider = new KeyValueModelValueProvider(_keyValueModel, null);
+
+ // Act & Assert
+ Assert.True(valueProvider.ContainsPrefix("foo"));
+ Assert.True(valueProvider.ContainsPrefix("bar"));
+ Assert.True(valueProvider.ContainsPrefix("bar.baz"));
+ }
+
+ [Fact]
+ public void ContainsPrefix_WithNonEmptyCollection_ReturnsFalseForUnknownPrefix()
+ {
+ // Arrange
+ var valueProvider = new KeyValueModelValueProvider(_keyValueModel, null);
+
+ // Act
+ bool result = valueProvider.ContainsPrefix("biff");
+
+ // Assert
+ Assert.False(result);
+ }
+
+ [Fact]
+ public void GetKeysFromPrefix_GuardClauses()
+ {
+ // Arrange
+ var valueProvider = new KeyValueModelValueProvider(_keyValueModel, null);
+
+ // Act & assert
+ Assert.ThrowsArgumentNull(() => valueProvider.GetKeysFromPrefix(null), "prefix");
+ }
+
+ [Fact]
+ public void GetKeysFromPrefix_EmptyPrefix_ReturnsAllPrefixes()
+ {
+ // Arrange
+ var valueProvider = new KeyValueModelValueProvider(_keyValueModel, null);
+
+ // Act
+ IDictionary<string, string> result = valueProvider.GetKeysFromPrefix("");
+
+ // Assert
+ Assert.Equal(5, result.Count);
+ Assert.Equal("", result[""]);
+ Assert.Equal("foo", result["foo"]);
+ Assert.Equal("int", result["int"]);
+ Assert.Equal("bar", result["bar"]);
+ Assert.Equal("bar.baz", result["bar.baz"]);
+ }
+
+ [Fact]
+ public void GetKeysFromPrefix_UnknownPrefix_ReturnsEmptyDictionary()
+ {
+ // Arrange
+ var valueProvider = new KeyValueModelValueProvider(_keyValueModel, null);
+
+ // Act
+ IDictionary<string, string> result = valueProvider.GetKeysFromPrefix("abc");
+
+ // Assert
+ Assert.Empty(result);
+ }
+
+ [Fact]
+ public void GetKeysFromPrefix_KnownPrefix_ReturnsMatchingItems()
+ {
+ // Arrange
+ var valueProvider = new KeyValueModelValueProvider(_keyValueModel, null);
+
+ // Act
+ IDictionary<string, string> result = valueProvider.GetKeysFromPrefix("bar");
+
+ // Assert
+ KeyValuePair<string, string> kvp = Assert.Single(result);
+ Assert.Equal("baz", kvp.Key);
+ Assert.Equal("bar.baz", kvp.Value);
+ }
+
+ [Fact]
+ public void GetValue_GuardClauses()
+ {
+ // Arrange
+ var valueProvider = new KeyValueModelValueProvider(_keyValueModel, null);
+
+ // Act & assert
+ Assert.ThrowsArgumentNull(() => valueProvider.GetValue(null), "key"); ;
+ }
+
+ [Fact]
+ public void GetValue_SingleValue_String()
+ {
+ // Arrange
+ var culture = CultureInfo.GetCultureInfo("fr-FR");
+ var valueProvider = new KeyValueModelValueProvider(_keyValueModel, culture);
+
+ // Act
+ ValueProviderResult vpResult = valueProvider.GetValue("bar.baz");
+
+ // Assert
+ Assert.NotNull(vpResult);
+ Assert.Equal("someOtherValue", (string)vpResult.RawValue);
+ Assert.Equal("someOtherValue", vpResult.AttemptedValue);
+ Assert.Equal(culture, vpResult.Culture);
+ }
+
+ [Fact]
+ public void GetValue_SingleValue_Not_String()
+ {
+ // Arrange
+ var culture = CultureInfo.GetCultureInfo("fr-FR");
+ var valueProvider = new KeyValueModelValueProvider(_keyValueModel, culture);
+
+ // Act
+ ValueProviderResult vpResult = valueProvider.GetValue("int");
+
+ // Assert
+ Assert.NotNull(vpResult);
+ Assert.Equal(55, (int)vpResult.RawValue);
+ Assert.Equal("55", vpResult.AttemptedValue);
+ Assert.Equal(culture, vpResult.Culture);
+ }
+
+ [Fact]
+ public void GetValue_ReturnsNullIfKeyNotFound()
+ {
+ // Arrange
+ var valueProvider = new KeyValueModelValueProvider(_keyValueModel, null);
+
+ // Act
+ ValueProviderResult vpResult = valueProvider.GetValue("bar");
+
+ // Assert
+ Assert.Null(vpResult);
+ }
+
+ class KeyValueModel : IKeyValueModel
+ {
+ private Dictionary<string, object> _values;
+
+ public KeyValueModel(Dictionary<string, object> values)
+ {
+ _values = values;
+ }
+
+ public bool TryGetValue(string key, out object value)
+ {
+ return _values.TryGetValue(key, out value);
+ }
+
+ public IEnumerable<string> Keys
+ {
+ get { return _values.Keys; }
+ }
+ }
+ }
+}
diff --git a/test/System.Web.Http.Test/ValueProviders/Providers/NameValueCollectionValueProviderTest.cs b/test/System.Web.Http.Test/ValueProviders/Providers/NameValueCollectionValueProviderTest.cs
new file mode 100644
index 00000000..eeb89196
--- /dev/null
+++ b/test/System.Web.Http.Test/ValueProviders/Providers/NameValueCollectionValueProviderTest.cs
@@ -0,0 +1,210 @@
+using System.Collections.Generic;
+using System.Collections.Specialized;
+using System.Globalization;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Http.ValueProviders.Providers
+{
+ public class NameValueCollectionValueProviderTest
+ {
+ private static readonly NameValueCollection _backingStore = new NameValueCollection()
+ {
+ { "foo", "fooValue1" },
+ { "foo", "fooValue2" },
+ { "bar.baz", "someOtherValue" }
+ };
+
+ [Fact]
+ public void Constructor_GuardClauses()
+ {
+ // Act & assert
+ Assert.ThrowsArgumentNull(
+ () => new NameValueCollectionValueProvider(values: null, culture: CultureInfo.InvariantCulture),
+ "values");
+
+ Assert.ThrowsArgumentNull(
+ () => new NameValueCollectionValueProvider(valuesFactory: null, culture: CultureInfo.InvariantCulture),
+ "valuesFactory");
+ }
+
+ [Fact]
+ public void ContainsPrefix_GuardClauses()
+ {
+ // Arrange
+ var valueProvider = new NameValueCollectionValueProvider(_backingStore, null);
+
+ // Act & assert
+ Assert.ThrowsArgumentNull(
+ () => valueProvider.ContainsPrefix(null),
+ "prefix");
+ }
+
+ [Fact]
+ public void ContainsPrefix_WithEmptyCollection_ReturnsFalseForEmptyPrefix()
+ {
+ // Arrange
+ var valueProvider = new NameValueCollectionValueProvider(new NameValueCollection(), null);
+
+ // Act
+ bool result = valueProvider.ContainsPrefix("");
+
+ // Assert
+ Assert.False(result);
+ }
+
+ [Fact]
+ public void ContainsPrefix_WithNonEmptyCollection_ReturnsTrueForEmptyPrefix()
+ {
+ // Arrange
+ var valueProvider = new NameValueCollectionValueProvider(_backingStore, null);
+
+ // Act
+ bool result = valueProvider.ContainsPrefix("");
+
+ // Assert
+ Assert.True(result);
+ }
+
+ [Fact]
+ public void ContainsPrefix_WithNonEmptyCollection_ReturnsTrueForKnownPrefixes()
+ {
+ // Arrange
+ var valueProvider = new NameValueCollectionValueProvider(_backingStore, null);
+
+ // Act & Assert
+ Assert.True(valueProvider.ContainsPrefix("foo"));
+ Assert.True(valueProvider.ContainsPrefix("bar"));
+ Assert.True(valueProvider.ContainsPrefix("bar.baz"));
+ }
+
+ [Fact]
+ public void ContainsPrefix_WithNonEmptyCollection_ReturnsFalseForUnknownPrefix()
+ {
+ // Arrange
+ var valueProvider = new NameValueCollectionValueProvider(_backingStore, null);
+
+ // Act
+ bool result = valueProvider.ContainsPrefix("biff");
+
+ // Assert
+ Assert.False(result);
+ }
+
+ [Fact]
+ public void GetKeysFromPrefix_GuardClauses()
+ {
+ // Arrange
+ var valueProvider = new NameValueCollectionValueProvider(_backingStore, null);
+
+ // Act & assert
+ Assert.ThrowsArgumentNull(
+ () => valueProvider.GetKeysFromPrefix(null),
+ "prefix");
+ }
+
+ [Fact]
+ public void GetKeysFromPrefix_EmptyPrefix_ReturnsAllPrefixes()
+ {
+ // Arrange
+ var valueProvider = new NameValueCollectionValueProvider(_backingStore, null);
+
+ // Act
+ IDictionary<string, string> result = valueProvider.GetKeysFromPrefix("");
+
+ // Assert
+ Assert.Equal(4, result.Count);
+ Assert.Equal("", result[""]);
+ Assert.Equal("foo", result["foo"]);
+ Assert.Equal("bar", result["bar"]);
+ Assert.Equal("bar.baz", result["bar.baz"]);
+ }
+
+ [Fact]
+ public void GetKeysFromPrefix_UnknownPrefix_ReturnsEmptyDictionary()
+ {
+ // Arrange
+ var valueProvider = new NameValueCollectionValueProvider(_backingStore, null);
+
+ // Act
+ IDictionary<string, string> result = valueProvider.GetKeysFromPrefix("abc");
+
+ // Assert
+ Assert.Empty(result);
+ }
+
+ [Fact]
+ public void GetKeysFromPrefix_KnownPrefix_ReturnsMatchingItems()
+ {
+ // Arrange
+ var valueProvider = new NameValueCollectionValueProvider(_backingStore, null);
+
+ // Act
+ IDictionary<string, string> result = valueProvider.GetKeysFromPrefix("bar");
+
+ // Assert
+ KeyValuePair<string, string> kvp = Assert.Single(result);
+ Assert.Equal("baz", kvp.Key);
+ Assert.Equal("bar.baz", kvp.Value);
+ }
+
+ [Fact]
+ public void GetValue_GuardClauses()
+ {
+ // Arrange
+ var valueProvider = new NameValueCollectionValueProvider(_backingStore, null);
+
+ // Act & assert
+ Assert.ThrowsArgumentNull(
+ () => valueProvider.GetValue(null),
+ "key");
+ }
+
+ [Fact]
+ public void GetValue_SingleValue()
+ {
+ // Arrange
+ var culture = CultureInfo.GetCultureInfo("fr-FR");
+ var valueProvider = new NameValueCollectionValueProvider(_backingStore, culture);
+
+ // Act
+ ValueProviderResult vpResult = valueProvider.GetValue("bar.baz");
+
+ // Assert
+ Assert.NotNull(vpResult);
+ Assert.Equal(new[] { "someOtherValue" }, (string[])vpResult.RawValue);
+ Assert.Equal("someOtherValue", vpResult.AttemptedValue);
+ Assert.Equal(culture, vpResult.Culture);
+ }
+
+ [Fact]
+ public void GetValue_MultiValue()
+ {
+ // Arrange
+ var culture = CultureInfo.GetCultureInfo("fr-FR");
+ var valueProvider = new NameValueCollectionValueProvider(_backingStore, culture);
+
+ // Act
+ ValueProviderResult vpResult = valueProvider.GetValue("foo");
+
+ // Assert
+ Assert.NotNull(vpResult);
+ Assert.Equal(new[] { "fooValue1", "fooValue2" }, (string[])vpResult.RawValue);
+ Assert.Equal("fooValue1,fooValue2", vpResult.AttemptedValue);
+ Assert.Equal(culture, vpResult.Culture);
+ }
+
+ [Fact]
+ public void GetValue_ReturnsNullIfKeyNotFound()
+ {
+ // Arrange
+ var valueProvider = new NameValueCollectionValueProvider(_backingStore, null);
+
+ // Act
+ ValueProviderResult vpResult = valueProvider.GetValue("bar");
+
+ // Assert
+ Assert.Null(vpResult);
+ }
+ }
+}
diff --git a/test/System.Web.Http.Test/ValueProviders/Providers/QueryStringValueProviderTest.cs b/test/System.Web.Http.Test/ValueProviders/Providers/QueryStringValueProviderTest.cs
new file mode 100644
index 00000000..d5150251
--- /dev/null
+++ b/test/System.Web.Http.Test/ValueProviders/Providers/QueryStringValueProviderTest.cs
@@ -0,0 +1,152 @@
+using System.Collections.Specialized;
+using Xunit;
+
+namespace System.Web.Http.ValueProviders.Providers
+{
+ public class QueryStringValueProviderTest
+ {
+ [Fact]
+ public void ParseQueryString_Null()
+ {
+ // Act
+ NameValueCollection result = QueryStringValueProvider.ParseQueryString(null);
+
+ // Assert
+ Assert.Equal(0, result.Count);
+ }
+
+ [Fact]
+ public void ParseQueryString_SingleNamelessValue()
+ {
+ // Arrange
+ Uri uri = new Uri("http://localhost/?value1");
+
+ // Act
+ NameValueCollection result = QueryStringValueProvider.ParseQueryString(uri);
+
+ // Assert
+ string key = Assert.Single(result) as string;
+ Assert.Equal("", key);
+ Assert.Equal("value1", result[key]);
+ }
+
+ [Fact]
+ public void ParseQueryString_SingleNamedValue()
+ {
+ // Arrange
+ Uri uri = new Uri("http://localhost/?key1=value1");
+
+ // Act
+ NameValueCollection result = QueryStringValueProvider.ParseQueryString(uri);
+
+ // Assert
+ string key = Assert.Single(result) as string;
+ Assert.Equal("key1", key);
+ Assert.Equal("value1", result[key]);
+ }
+
+ [Fact]
+ public void ParseQueryString_TwoNamedValues()
+ {
+ // Arrange
+ Uri uri = new Uri("http://localhost/?key1=value1&key2=value2");
+
+ // Act
+ NameValueCollection result = QueryStringValueProvider.ParseQueryString(uri);
+
+ // Assert
+ Assert.Equal(2, result.Count);
+ Assert.Equal("value1", result["key1"]);
+ Assert.Equal("value2", result["key2"]);
+ }
+
+ [Fact]
+ public void ParseQueryString_MixedNamedAndUnnamedValues()
+ {
+ // Arrange
+ Uri uri = new Uri("http://localhost/?key1=value1&value2");
+
+ // Act
+ NameValueCollection result = QueryStringValueProvider.ParseQueryString(uri);
+
+ // Assert
+ Assert.Equal(2, result.Count);
+ Assert.Equal("value1", result["key1"]);
+ Assert.Equal("value2", result[""]);
+ }
+
+ [Fact]
+ public void ParseQueryString_MultipleValuesForSingleName()
+ {
+ // Arrange
+ Uri uri = new Uri("http://localhost/?key1=value1&key1=value2");
+
+ // Act
+ NameValueCollection result = QueryStringValueProvider.ParseQueryString(uri);
+
+ // Assert
+ Assert.Equal("value1,value2", result["key1"]);
+ Assert.Equal(new[] { "value1", "value2" }, result.GetValues("key1"));
+ }
+
+ [Fact]
+ public void ParseQueryString_LeadingAmpersand()
+ {
+ // Arrange
+ Uri uri = new Uri("http://localhost/?&key1=value1");
+
+ // Act
+ NameValueCollection result = QueryStringValueProvider.ParseQueryString(uri);
+
+ // Assert
+ Assert.Equal(2, result.Count);
+ Assert.Equal("value1", result["key1"]);
+ Assert.Equal("", result[""]);
+ }
+
+ [Fact]
+ public void ParseQueryString_IntermediateDoubleAmpersand()
+ {
+ // Arrange
+ Uri uri = new Uri("http://localhost/?key1=value1&&key2=value2");
+
+ // Act
+ NameValueCollection result = QueryStringValueProvider.ParseQueryString(uri);
+
+ // Assert
+ Assert.Equal(3, result.Count);
+ Assert.Equal("value1", result["key1"]);
+ Assert.Equal("value2", result["key2"]);
+ Assert.Equal("", result[""]);
+ }
+
+ [Fact]
+ public void ParseQueryString_TrailingAmpersand()
+ {
+ // Arrange
+ Uri uri = new Uri("http://localhost/?key1=value1&");
+
+ // Act
+ NameValueCollection result = QueryStringValueProvider.ParseQueryString(uri);
+
+ // Assert
+ Assert.Equal(2, result.Count);
+ Assert.Equal("value1", result["key1"]);
+ Assert.Equal("", result[""]);
+ }
+
+ [Fact]
+ public void ParseQueryString_EncodedUrlValues()
+ {
+ // Arrange
+ Uri uri = new Uri("http://localhost/?key%31=value%31");
+
+ // Act
+ NameValueCollection result = QueryStringValueProvider.ParseQueryString(uri);
+
+ // Assert
+ Assert.Single(result);
+ Assert.Equal("value1", result["key1"]);
+ }
+ }
+}
diff --git a/test/System.Web.Http.Test/packages.config b/test/System.Web.Http.Test/packages.config
new file mode 100644
index 00000000..b9070f9e
--- /dev/null
+++ b/test/System.Web.Http.Test/packages.config
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<packages>
+ <package id="Microsoft.Net.Http" version="2.0.20302.1" />
+ <package id="Moq" version="4.0.10827" />
+ <package id="xunit" version="1.9.0.1566" />
+ <package id="xunit.extensions" version="1.9.0.1566" />
+</packages> \ No newline at end of file
diff --git a/test/System.Web.Http.WebHost.Test/HttpControllerHandlerTest.cs b/test/System.Web.Http.WebHost.Test/HttpControllerHandlerTest.cs
new file mode 100644
index 00000000..e289ac6c
--- /dev/null
+++ b/test/System.Web.Http.WebHost.Test/HttpControllerHandlerTest.cs
@@ -0,0 +1,85 @@
+using System.IO;
+using System.Net;
+using System.Net.Http;
+using System.Net.Http.Headers;
+using System.Threading.Tasks;
+using Moq;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Http.WebHost
+{
+ public class HttpControllerHandlerTest
+ {
+ [Fact]
+ public void ConvertResponse_IfResponseHasNoCacheControlDefined_SetsNoCacheCacheabilityOnAspNetResponse()
+ {
+ // Arrange
+ Mock<HttpContextBase> contextMock = new Mock<HttpContextBase>() { DefaultValue = DefaultValue.Mock };
+ HttpResponseMessage response = new HttpResponseMessage();
+ HttpRequestMessage request = new HttpRequestMessage();
+
+ // Act
+ HttpControllerHandler.ConvertResponse(contextMock.Object, response, request);
+
+ // Assert
+ contextMock.Verify(c => c.Response.Cache.SetCacheability(HttpCacheability.NoCache));
+ }
+
+ [Fact]
+ public void ConvertResponse_IfResponseHasCacheControlDefined_DoesNotSetCacheCacheabilityOnAspNetResponse()
+ {
+ // Arrange
+ Mock<HttpContextBase> contextMock = new Mock<HttpContextBase>() { DefaultValue = DefaultValue.Mock };
+ HttpResponseMessage response = new HttpResponseMessage();
+ HttpRequestMessage request = new HttpRequestMessage();
+ response.Headers.CacheControl = new CacheControlHeaderValue { Public = true };
+
+ // Act
+ HttpControllerHandler.ConvertResponse(contextMock.Object, response, request);
+
+ // Assert
+ contextMock.Verify(c => c.Response.Cache.SetCacheability(HttpCacheability.NoCache), Times.Never());
+ }
+
+ [Fact]
+ public Task ConvertResponse_DisposesRequestAndResponse()
+ {
+ // Arrange
+ Mock<HttpContextBase> contextMock = new Mock<HttpContextBase>() { DefaultValue = DefaultValue.Mock };
+ contextMock.SetupGet((hcb) => hcb.Response.OutputStream).Returns(Stream.Null);
+
+ HttpRequestMessage request = new HttpRequestMessage();
+ HttpResponseMessage response = new HttpResponseMessage();
+
+ // Act
+ return HttpControllerHandler.ConvertResponse(contextMock.Object, response, request).ContinueWith(
+ _ =>
+ {
+ // Assert
+ Assert.ThrowsObjectDisposed(() => request.Method = HttpMethod.Get, typeof(HttpRequestMessage).FullName);
+ Assert.ThrowsObjectDisposed(() => response.StatusCode = HttpStatusCode.OK, typeof(HttpResponseMessage).FullName);
+ });
+ }
+
+ [Fact]
+ public Task ConvertResponse_DisposesRequestAndResponseWithContent()
+ {
+ // Arrange
+ Mock<HttpContextBase> contextMock = new Mock<HttpContextBase>() { DefaultValue = DefaultValue.Mock };
+ contextMock.SetupGet((hcb) => hcb.Response.OutputStream).Returns(Stream.Null);
+
+ HttpRequestMessage request = new HttpRequestMessage() { Content = new StringContent("request") };
+ HttpResponseMessage response = new HttpResponseMessage() { Content = new StringContent("response") };
+
+ // Act
+ return HttpControllerHandler.ConvertResponse(contextMock.Object, response, request).ContinueWith(
+ _ =>
+ {
+ // Assert
+ Assert.ThrowsObjectDisposed(() => request.Method = HttpMethod.Get, typeof(HttpRequestMessage).FullName);
+ Assert.ThrowsObjectDisposed(() => response.StatusCode = HttpStatusCode.OK, typeof(HttpResponseMessage).FullName);
+ });
+ }
+ }
+}
diff --git a/test/System.Web.Http.WebHost.Test/Properties/AssemblyInfo.cs b/test/System.Web.Http.WebHost.Test/Properties/AssemblyInfo.cs
new file mode 100644
index 00000000..9155b469
--- /dev/null
+++ b/test/System.Web.Http.WebHost.Test/Properties/AssemblyInfo.cs
@@ -0,0 +1,3 @@
+using System;
+
+[assembly: CLSCompliant(false)]
diff --git a/test/System.Web.Http.WebHost.Test/RouteCollectionExtensionsTest.cs b/test/System.Web.Http.WebHost.Test/RouteCollectionExtensionsTest.cs
new file mode 100644
index 00000000..fa90c158
--- /dev/null
+++ b/test/System.Web.Http.WebHost.Test/RouteCollectionExtensionsTest.cs
@@ -0,0 +1,67 @@
+using System.Web.Routing;
+using Microsoft.TestCommon;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Http
+{
+ public class RouteCollectionExtensionsTest
+ {
+ [Fact]
+ public void IsCorrectType()
+ {
+ Assert.Type.HasProperties(typeof(RouteCollectionExtensions), TypeAssert.TypeProperties.IsStatic | TypeAssert.TypeProperties.IsPublicVisibleClass);
+ }
+
+ [Fact]
+ public void MapHttpRoute1ThrowsOnNullRouteCollection()
+ {
+ Assert.ThrowsArgumentNull(() => RouteCollectionExtensions.MapHttpRoute(null, "", "", null), "routes");
+ }
+
+ [Fact]
+ public void MapHttpRoute1CreatesRoute()
+ {
+ // Arrange
+ RouteCollection routes = new RouteCollection();
+ object defaults = new { d1 = "D1" };
+
+ // Act
+ Route route = routes.MapHttpRoute("name", "template", defaults);
+
+ // Assert
+ Assert.NotNull(route);
+ Assert.Equal("template", route.Url);
+ Assert.Equal(1, route.Defaults.Count);
+ Assert.Equal("D1", route.Defaults["d1"]);
+ Assert.Same(route, routes["name"]);
+ }
+
+ [Fact]
+ public void MapHttpRoute2ThrowsOnNullRouteCollection()
+ {
+ Assert.ThrowsArgumentNull(() => RouteCollectionExtensions.MapHttpRoute(null, "", "", null, null), "routes");
+ }
+
+ [Fact]
+ public void MapHttpRoute2CreatesRoute()
+ {
+ // Arrange
+ RouteCollection routes = new RouteCollection();
+ object defaults = new { d1 = "D1" };
+ object constraints = new { c1 = "C1" };
+
+ // Act
+ Route route = routes.MapHttpRoute("name", "template", defaults, constraints);
+
+ // Assert
+ Assert.NotNull(route);
+ Assert.Equal("template", route.Url);
+ Assert.Equal(1, route.Defaults.Count);
+ Assert.Equal("D1", route.Defaults["d1"]);
+ Assert.Equal(1, route.Defaults.Count);
+ Assert.Equal("C1", route.Constraints["c1"]);
+ Assert.Same(route, routes["name"]);
+ }
+ }
+}
diff --git a/test/System.Web.Http.WebHost.Test/Routing/HostedUrlHelperTest.cs b/test/System.Web.Http.WebHost.Test/Routing/HostedUrlHelperTest.cs
new file mode 100644
index 00000000..a652e1a0
--- /dev/null
+++ b/test/System.Web.Http.WebHost.Test/Routing/HostedUrlHelperTest.cs
@@ -0,0 +1,137 @@
+using System.Net.Http;
+using System.Web.Http.Controllers;
+using System.Web.Mvc;
+using System.Web.Routing;
+using Moq;
+using Xunit;
+using Xunit.Extensions;
+using UrlHelper = System.Web.Http.Routing.UrlHelper;
+
+namespace System.Web.Http.WebHost.Routing
+{
+ public class HostedUrlHelperTest
+ {
+ [Theory]
+ [InlineData(WhichRoute.ApiRoute1)]
+ [InlineData(WhichRoute.ApiRoute2)]
+ [InlineData(WhichRoute.WebRoute1)]
+ public void UrlHelper_GeneratesApiUrl_ForMatchingData(WhichRoute whichRoute)
+ {
+ // Mixed mode app with Web API generating URLs to other APIs
+ var url = GetUrlHelperForMixedApp(whichRoute);
+
+ string generatedUrl = url.Route("apiroute2", new { controller = "something", action = "someaction", id = 789 });
+
+ Assert.Equal("$APP$/SOMEAPP/api/something/someaction", generatedUrl);
+ }
+
+ [Theory]
+ [InlineData(WhichRoute.ApiRoute1)]
+ [InlineData(WhichRoute.ApiRoute2)]
+ [InlineData(WhichRoute.WebRoute1)]
+ public void UrlHelper_SkipsApiRoutesAndMatchesMvcUrl_ForMatchingData(WhichRoute whichRoute)
+ {
+ // Mixed mode app with MVC generating URLs to other MVC URLs
+ RouteCollection routes;
+ RequestContext requestContext;
+ var url = GetUrlHelperForMixedApp(whichRoute, out routes, out requestContext);
+
+ // Note: This is generating a URL the "hard" way because it's simulating what a regular MVC
+ // app would do when generating a URL. If we went through the Web API functionality it wouldn't
+ // be testing what would really happen in a mixed app.
+ VirtualPathData virtualPathData = routes.GetVirtualPath(requestContext, new RouteValueDictionary(new { controller = "something", action = "someaction", id = 789 }));
+
+ Assert.NotNull(virtualPathData);
+
+ string generatedUrl = virtualPathData.VirtualPath;
+
+ Assert.Equal("$APP$/SOMEAPP/something/someaction/789", generatedUrl);
+ }
+
+ [Theory]
+ [InlineData(WhichRoute.ApiRoute1)]
+ [InlineData(WhichRoute.ApiRoute2)]
+ [InlineData(WhichRoute.WebRoute1)]
+ public void UrlHelper_MvcAppGeneratesApiRoute_WithSpecialHttpRouteKey(WhichRoute whichRoute)
+ {
+ // Mixed mode app with MVC generating URLs to Web APIs
+ RouteCollection routes;
+ RequestContext requestContext;
+ var url = GetUrlHelperForMixedApp(whichRoute, out routes, out requestContext);
+
+ // Note: This is generating a URL the "hard" way because it's simulating what a regular MVC
+ // app would do when generating a URL. If we went through the Web API functionality it wouldn't
+ // be testing what would really happen in a mixed app.
+ VirtualPathData virtualPathData = routes.GetVirtualPath(requestContext, new RouteValueDictionary(new { controller = "something", action = "someotheraction", id = 789, httproute = true }));
+
+ Assert.NotNull(virtualPathData);
+
+ string generatedUrl = virtualPathData.VirtualPath;
+
+ Assert.Equal("$APP$/SOMEAPP/api/something/someotheraction", generatedUrl);
+ }
+
+ private static UrlHelper GetUrlHelperForMixedApp(WhichRoute whichRoute)
+ {
+ RouteCollection routes;
+ RequestContext requestContext;
+ return GetUrlHelperForMixedApp(whichRoute, out routes, out requestContext);
+ }
+
+ private static UrlHelper GetUrlHelperForMixedApp(WhichRoute whichRoute, out RouteCollection routes, out RequestContext requestContext)
+ {
+ routes = new RouteCollection();
+
+ HttpControllerContext cc = new HttpControllerContext();
+ cc.Request = new HttpRequestMessage();
+ var mockHttpContext = new Mock<HttpContextBase>();
+ var mockHttpRequest = new Mock<HttpRequestBase>();
+ mockHttpRequest.SetupGet<string>(x => x.ApplicationPath).Returns("/SOMEAPP/");
+ var mockHttpResponse = new Mock<HttpResponseBase>();
+ mockHttpResponse.Setup<string>(x => x.ApplyAppPathModifier(It.IsAny<string>())).Returns<string>(x => "$APP$" + x);
+ mockHttpContext.SetupGet<HttpRequestBase>(x => x.Request).Returns(mockHttpRequest.Object);
+ mockHttpContext.SetupGet<HttpResponseBase>(x => x.Response).Returns(mockHttpResponse.Object);
+ cc.Request.Properties["MS_HttpContext"] = mockHttpContext.Object;
+
+ // Set up routes
+ var hostedRoutes = new HostedHttpRouteCollection(routes);
+ Route apiRoute1 = routes.MapHttpRoute("apiroute1", "api/{controller}/{id}", new { action = "someaction" });
+ Route apiRoute2 = routes.MapHttpRoute("apiroute2", "api/{controller}/{action}", new { id = 789 });
+ Route webRoute1 = routes.MapRoute("webroute1", "{controller}/{action}/{id}");
+ cc.Configuration = new HttpConfiguration(hostedRoutes);
+
+ RouteData routeData = new RouteData();
+ routeData.Values.Add("controller", "people");
+ routeData.Values.Add("id", "123");
+
+ // Specify which route we came in on (e.g. what request matching the incoming URL) because
+ // it can affect the generated URL due to the ambient route data.
+ switch (whichRoute)
+ {
+ case WhichRoute.ApiRoute1:
+ routeData.Route = apiRoute1;
+ break;
+ case WhichRoute.ApiRoute2:
+ routeData.Route = apiRoute2;
+ break;
+ case WhichRoute.WebRoute1:
+ routeData.Route = webRoute1;
+ break;
+ default:
+ throw new ArgumentException("Invalid route specified.", "whichRoute");
+ }
+ cc.RouteData = new HostedHttpRouteData(routeData);
+
+ requestContext = new RequestContext(mockHttpContext.Object, routeData);
+
+ return cc.Url;
+ }
+
+ public enum WhichRoute
+ {
+ ApiRoute1,
+ ApiRoute2,
+ WebRoute1,
+ }
+ }
+}
diff --git a/test/System.Web.Http.WebHost.Test/System.Web.Http.WebHost.Test.csproj b/test/System.Web.Http.WebHost.Test/System.Web.Http.WebHost.Test.csproj
new file mode 100644
index 00000000..6e02ad94
--- /dev/null
+++ b/test/System.Web.Http.WebHost.Test/System.Web.Http.WebHost.Test.csproj
@@ -0,0 +1,92 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory),Runtime.sln))\tools\WebStack.settings.targets" />
+ <PropertyGroup>
+ <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+ <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+ <ProductVersion>9.0.30729</ProductVersion>
+ <SchemaVersion>2.0</SchemaVersion>
+ <ProjectGuid>{EA62944F-BD25-4730-9405-9BE8FF5BEACD}</ProjectGuid>
+ <OutputType>Library</OutputType>
+ <AppDesignerFolder>Properties</AppDesignerFolder>
+ <RootNamespace>System.Web.Http.WebHost</RootNamespace>
+ <AssemblyName>System.Web.Http.WebHost.Test</AssemblyName>
+ <FileAlignment>512</FileAlignment>
+ <ProjectTypeGuids>{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
+ <ErrorReport>prompt</ErrorReport>
+ <WarningLevel>4</WarningLevel>
+ <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
+ <DebugSymbols>true</DebugSymbols>
+ <DebugType>full</DebugType>
+ <Optimize>false</Optimize>
+ <OutputPath>$(WebStackRootPath)\bin\Debug\Test\</OutputPath>
+ <DefineConstants>TRACE;DEBUG</DefineConstants>
+ <CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
+ <DebugType>pdbonly</DebugType>
+ <Optimize>true</Optimize>
+ <OutputPath>$(WebStackRootPath)\bin\Release\Test\</OutputPath>
+ <DefineConstants>TRACE</DefineConstants>
+ <CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)' == 'CodeCoverage' ">
+ <DebugSymbols>true</DebugSymbols>
+ <DebugType>full</DebugType>
+ <Optimize>false</Optimize>
+ <OutputPath>$(WebStackRootPath)\bin\CodeCoverage\Test\</OutputPath>
+ <DefineConstants>TRACE;DEBUG</DefineConstants>
+ <CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
+ </PropertyGroup>
+ <ItemGroup>
+ <Reference Include="Moq, Version=4.0.10827.0, Culture=neutral, PublicKeyToken=69f491c39445e920, processorArchitecture=MSIL">
+ <HintPath>..\..\packages\Moq.4.0.10827\lib\NET40\Moq.dll</HintPath>
+ </Reference>
+ <Reference Include="System" />
+ <Reference Include="System.Configuration" />
+ <Reference Include="System.Core" />
+ <Reference Include="System.Net.Http">
+ <HintPath>..\..\packages\Microsoft.Net.Http.2.0.20302.1\lib\net40\System.Net.Http.dll</HintPath>
+ </Reference>
+ <Reference Include="System.Net.Http.WebRequest">
+ <HintPath>..\..\packages\Microsoft.Net.Http.2.0.20302.1\lib\net40\System.Net.Http.WebRequest.dll</HintPath>
+ </Reference>
+ <Reference Include="System.Web" />
+ <Reference Include="xunit">
+ <HintPath>..\..\packages\xunit.1.9.0.1566\lib\xunit.dll</HintPath>
+ </Reference>
+ <Reference Include="xunit.extensions">
+ <HintPath>..\..\packages\xunit.extensions.1.9.0.1566\lib\xunit.extensions.dll</HintPath>
+ </Reference>
+ </ItemGroup>
+ <ItemGroup>
+ <Compile Include="HttpControllerHandlerTest.cs" />
+ <Compile Include="RouteCollectionExtensionsTest.cs" />
+ <Compile Include="Properties\AssemblyInfo.cs" />
+ <Compile Include="Routing\HostedUrlHelperTest.cs" />
+ </ItemGroup>
+ <ItemGroup>
+ <None Include="packages.config" />
+ </ItemGroup>
+ <ItemGroup>
+ <ProjectReference Include="..\..\src\System.Web.Http.WebHost\System.Web.Http.WebHost.csproj">
+ <Project>{A0187BC2-8325-4BB2-8697-7F955CF4173E}</Project>
+ <Name>System.Web.Http.WebHost</Name>
+ </ProjectReference>
+ <ProjectReference Include="..\..\src\System.Web.Http\System.Web.Http.csproj">
+ <Project>{DDC1CE0C-486E-4E35-BB3B-EAB61F8F9440}</Project>
+ <Name>System.Web.Http</Name>
+ </ProjectReference>
+ <ProjectReference Include="..\..\src\System.Web.Mvc\System.Web.Mvc.csproj">
+ <Project>{3D3FFD8A-624D-4E9B-954B-E1C105507975}</Project>
+ <Name>System.Web.Mvc</Name>
+ </ProjectReference>
+ <ProjectReference Include="..\Microsoft.TestCommon\Microsoft.TestCommon.csproj">
+ <Project>{FCCC4CB7-BAF7-4A57-9F89-E5766FE536C0}</Project>
+ <Name>Microsoft.TestCommon</Name>
+ </ProjectReference>
+ </ItemGroup>
+ <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
+</Project> \ No newline at end of file
diff --git a/test/System.Web.Http.WebHost.Test/packages.config b/test/System.Web.Http.WebHost.Test/packages.config
new file mode 100644
index 00000000..b9070f9e
--- /dev/null
+++ b/test/System.Web.Http.WebHost.Test/packages.config
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<packages>
+ <package id="Microsoft.Net.Http" version="2.0.20302.1" />
+ <package id="Moq" version="4.0.10827" />
+ <package id="xunit" version="1.9.0.1566" />
+ <package id="xunit.extensions" version="1.9.0.1566" />
+</packages> \ No newline at end of file
diff --git a/test/System.Web.Mvc.Test/Ajax/Test/AjaxExtensionsTest.cs b/test/System.Web.Mvc.Test/Ajax/Test/AjaxExtensionsTest.cs
new file mode 100644
index 00000000..f583cb1a
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Ajax/Test/AjaxExtensionsTest.cs
@@ -0,0 +1,1975 @@
+using System.Collections;
+using System.Collections.Generic;
+using System.Globalization;
+using System.IO;
+using System.Threading;
+using System.Web.Mvc.Html;
+using System.Web.Routing;
+using Microsoft.Web.UnitTestUtil;
+using Moq;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Mvc.Ajax.Test
+{
+ public class AjaxExtensionsTest
+ {
+ // Guards
+
+ [Fact]
+ public void ActionLinkWithNullOrEmptyLinkTextThrows()
+ {
+ // Arrange
+ AjaxHelper ajaxHelper = GetAjaxHelper();
+
+ // Act & Assert
+ Assert.ThrowsArgumentNullOrEmpty(
+ delegate { MvcHtmlString actionLink = ajaxHelper.ActionLink(String.Empty, String.Empty, null, null, null, null); },
+ "linkText");
+ }
+
+ [Fact]
+ public void RouteLinkWithNullOrEmptyLinkTextThrows()
+ {
+ // Arrange
+ AjaxHelper ajaxHelper = GetAjaxHelper();
+
+ // Act & Assert
+ Assert.ThrowsArgumentNullOrEmpty(
+ delegate { MvcHtmlString actionLink = ajaxHelper.RouteLink(String.Empty, String.Empty, null, null, null); },
+ "linkText");
+ }
+
+ // Form context setup and cleanup
+
+ [Fact]
+ public void BeginFormSetsAndRestoresToDefault()
+ {
+ // Arrange
+ AjaxHelper ajaxHelper = GetAjaxHelper();
+
+ ajaxHelper.ViewContext.FormContext = null;
+ FormContext defaultFormContext = ajaxHelper.ViewContext.FormContext;
+
+ // Act & assert - push
+ MvcForm theForm = ajaxHelper.BeginForm(new AjaxOptions());
+ Assert.NotNull(ajaxHelper.ViewContext.FormContext);
+ Assert.NotEqual(defaultFormContext, ajaxHelper.ViewContext.FormContext);
+
+ // Act & assert - pop
+ theForm.Dispose();
+ Assert.Equal(defaultFormContext, ajaxHelper.ViewContext.FormContext);
+ }
+
+ [Fact]
+ public void DisposeWritesClosingFormTag()
+ {
+ // Arrange
+ AjaxHelper ajaxHelper = GetAjaxHelper();
+ AjaxOptions ajaxOptions = new AjaxOptions { UpdateTargetId = "some-id" };
+ StringWriter writer = new StringWriter();
+ ajaxHelper.ViewContext.Writer = writer;
+
+ // Act
+ IDisposable form = ajaxHelper.BeginForm("Action", "Controller", ajaxOptions);
+ form.Dispose();
+
+ // Assert
+ Assert.True(writer.ToString().EndsWith("</form>"));
+ }
+
+ // GlobalizationScript
+
+ [Fact]
+ public void GlobalizationScriptWithNullCultureInfoThrows()
+ {
+ // Arrange
+ AjaxHelper ajaxHelper = GetAjaxHelper();
+
+ // Act & Assert
+ Assert.ThrowsArgumentNull(
+ delegate { ajaxHelper.GlobalizationScript(null); },
+ "cultureInfo");
+ }
+
+ [Fact]
+ public void GlobalizationScriptUsesCurrentCultureAsDefault()
+ {
+ CultureInfo currentCulture = Thread.CurrentThread.CurrentCulture;
+
+ try
+ {
+ // Arrange
+ AjaxHelper ajaxHelper = GetAjaxHelper();
+ AjaxHelper.GlobalizationScriptPath = null;
+ Thread.CurrentThread.CurrentCulture = CultureInfo.GetCultureInfo("en-GB");
+
+ // Act
+ MvcHtmlString globalizationScript = ajaxHelper.GlobalizationScript();
+
+ // Assert
+ Assert.Equal(@"<script src=""~/Scripts/Globalization/en-GB.js"" type=""text/javascript""></script>", globalizationScript.ToHtmlString());
+ }
+ finally
+ {
+ Thread.CurrentThread.CurrentCulture = currentCulture;
+ }
+ }
+
+ [Fact]
+ public void GlobalizationScriptWithCultureInfo()
+ {
+ CultureInfo currentCulture = Thread.CurrentThread.CurrentCulture;
+
+ try
+ {
+ // Arrange
+ AjaxHelper ajaxHelper = GetAjaxHelper();
+ AjaxHelper.GlobalizationScriptPath = null;
+ Thread.CurrentThread.CurrentCulture = CultureInfo.GetCultureInfo("en-GB");
+
+ // Act
+ MvcHtmlString globalizationScript = ajaxHelper.GlobalizationScript(CultureInfo.GetCultureInfo("en-CA"));
+
+ // Assert
+ Assert.Equal(@"<script src=""~/Scripts/Globalization/en-CA.js"" type=""text/javascript""></script>", globalizationScript.ToHtmlString());
+ }
+ finally
+ {
+ Thread.CurrentThread.CurrentCulture = currentCulture;
+ }
+ }
+
+ [Fact]
+ public void GlobalizationScriptEncodesSource()
+ {
+ // Arrange
+ Mock<CultureInfo> xssCulture = new Mock<CultureInfo>("en-US");
+ xssCulture.Setup(culture => culture.Name).Returns("evil.example.com/<script>alert('XSS!')</script>");
+ string globalizationPath = "~/Scripts&Globalization";
+ string expectedScriptTag = @"<script src=""~/Scripts&amp;Globalization/evil.example.com%2f%3cscript%3ealert(%27XSS!%27)%3c%2fscript%3e.js"" type=""text/javascript""></script>";
+
+ // Act
+ MvcHtmlString globalizationScript = AjaxExtensions.GlobalizationScriptHelper(globalizationPath, xssCulture.Object);
+
+ // Assert
+ Assert.Equal(expectedScriptTag, globalizationScript.ToHtmlString());
+ }
+
+ [Fact]
+ public void GlobalizationScriptWithNullCultureName()
+ {
+ // Arrange
+ Mock<CultureInfo> xssCulture = new Mock<CultureInfo>("en-US");
+ xssCulture.Setup(culture => culture.Name).Returns((string)null);
+
+ AjaxHelper ajaxHelper = GetAjaxHelper();
+ AjaxHelper.GlobalizationScriptPath = null;
+
+ // Act
+ MvcHtmlString globalizationScript = ajaxHelper.GlobalizationScript(xssCulture.Object);
+
+ // Assert
+ Assert.Equal(@"<script src=""~/Scripts/Globalization/.js"" type=""text/javascript""></script>", globalizationScript.ToHtmlString());
+ }
+
+ // ActionLink (traditional JavaScript)
+
+ [Fact]
+ public void ActionLinkWithNullActionName()
+ {
+ // Arrange
+ AjaxHelper ajaxHelper = GetAjaxHelper(unobtrusiveJavaScript: false);
+
+ // Act
+ MvcHtmlString actionLink = ajaxHelper.ActionLink("linkText", null, new AjaxOptions());
+
+ // Assert
+ Assert.Equal(@"<a href=""" + MvcHelper.AppPathModifier + @"/app/home/oldaction"" onclick=""Sys.Mvc.AsyncHyperlink.handleClick(this, new Sys.UI.DomEvent(event), { insertionMode: Sys.Mvc.InsertionMode.replace });"">linkText</a>", actionLink.ToHtmlString());
+ }
+
+ [Fact]
+ public void ActionLinkWithNullActionName_Unobtrusive()
+ {
+ // Arrange
+ AjaxHelper ajaxHelper = GetAjaxHelper(unobtrusiveJavaScript: true);
+
+ // Act
+ MvcHtmlString actionLink = ajaxHelper.ActionLink("linkText", null, new AjaxOptions());
+
+ // Assert
+ Assert.Equal(@"<a data-ajax=""true"" href=""" + MvcHelper.AppPathModifier + @"/app/home/oldaction"">linkText</a>", actionLink.ToHtmlString());
+ }
+
+ [Fact]
+ public void ActionLinkWithNullActionNameAndNullOptions()
+ {
+ // Arrange
+ AjaxHelper ajaxHelper = GetAjaxHelper(unobtrusiveJavaScript: false);
+
+ // Act
+ MvcHtmlString actionLink = ajaxHelper.ActionLink("linkText", null, null);
+
+ // Assert
+ Assert.Equal(@"<a href=""" + MvcHelper.AppPathModifier + @"/app/home/oldaction"" onclick=""Sys.Mvc.AsyncHyperlink.handleClick(this, new Sys.UI.DomEvent(event), { insertionMode: Sys.Mvc.InsertionMode.replace });"">linkText</a>", actionLink.ToHtmlString());
+ }
+
+ [Fact]
+ public void ActionLinkWithNullActionNameAndNullOptions_Unobtrusive()
+ {
+ // Arrange
+ AjaxHelper ajaxHelper = GetAjaxHelper(unobtrusiveJavaScript: true);
+
+ // Act
+ MvcHtmlString actionLink = ajaxHelper.ActionLink("linkText", null, null);
+
+ // Assert
+ Assert.Equal(@"<a data-ajax=""true"" href=""" + MvcHelper.AppPathModifier + @"/app/home/oldaction"">linkText</a>", actionLink.ToHtmlString());
+ }
+
+ [Fact]
+ public void ActionLink()
+ {
+ // Arrange
+ AjaxHelper ajaxHelper = GetAjaxHelper(unobtrusiveJavaScript: false);
+
+ // Act
+ MvcHtmlString actionLink = ajaxHelper.ActionLink("linkText", "Action", new AjaxOptions());
+
+ // Assert
+ Assert.Equal(@"<a href=""" + MvcHelper.AppPathModifier + @"/app/home/Action"" onclick=""Sys.Mvc.AsyncHyperlink.handleClick(this, new Sys.UI.DomEvent(event), { insertionMode: Sys.Mvc.InsertionMode.replace });"">linkText</a>", actionLink.ToHtmlString());
+ }
+
+ [Fact]
+ public void ActionLink_Unobtrusive()
+ {
+ // Arrange
+ AjaxHelper ajaxHelper = GetAjaxHelper(unobtrusiveJavaScript: true);
+
+ // Act
+ MvcHtmlString actionLink = ajaxHelper.ActionLink("linkText", "Action", new AjaxOptions());
+
+ // Assert
+ Assert.Equal(@"<a data-ajax=""true"" href=""" + MvcHelper.AppPathModifier + @"/app/home/Action"">linkText</a>", actionLink.ToHtmlString());
+ }
+
+ [Fact]
+ public void ActionLinkAnonymousValues()
+ {
+ // Arrange
+ AjaxHelper helper = GetAjaxHelper(unobtrusiveJavaScript: false);
+ object values = new { controller = "Controller" };
+ AjaxOptions options = new AjaxOptions { UpdateTargetId = "update-div" };
+
+ // Act
+ MvcHtmlString actionLink = helper.ActionLink("Some Text", "Action", values, options);
+
+ // Assert
+ Assert.Equal(@"<a href=""" + MvcHelper.AppPathModifier + @"/app/Controller/Action"" onclick=""Sys.Mvc.AsyncHyperlink.handleClick(this, new Sys.UI.DomEvent(event), { insertionMode: Sys.Mvc.InsertionMode.replace, updateTargetId: &#39;update-div&#39; });"">Some Text</a>", actionLink.ToHtmlString());
+ }
+
+ [Fact]
+ public void ActionLinkAnonymousValues_Unobtrusive()
+ {
+ // Arrange
+ AjaxHelper helper = GetAjaxHelper(unobtrusiveJavaScript: true);
+ object values = new { controller = "Controller" };
+ AjaxOptions options = new AjaxOptions { UpdateTargetId = "update-div" };
+
+ // Act
+ MvcHtmlString actionLink = helper.ActionLink("Some Text", "Action", values, options);
+
+ // Assert
+ Assert.Equal(@"<a data-ajax=""true"" data-ajax-mode=""replace"" data-ajax-update=""#update-div"" href=""" + MvcHelper.AppPathModifier + @"/app/Controller/Action"">Some Text</a>", actionLink.ToHtmlString());
+ }
+
+ [Fact]
+ public void ActionLinkAnonymousValuesAndAttributes()
+ {
+ // Arrange
+ AjaxHelper helper = GetAjaxHelper(unobtrusiveJavaScript: false);
+ object htmlAttributes = new { foo = "bar", baz = "quux", foo_bar = "baz_quux" };
+ object values = new { controller = "Controller" };
+ AjaxOptions options = new AjaxOptions { UpdateTargetId = "update-div" };
+
+ // Act
+ MvcHtmlString actionLink = helper.ActionLink("Some Text", "Action", values, options, htmlAttributes);
+
+ // Assert
+ Assert.Equal(@"<a baz=""quux"" foo=""bar"" foo-bar=""baz_quux"" href=""" + MvcHelper.AppPathModifier + @"/app/Controller/Action"" onclick=""Sys.Mvc.AsyncHyperlink.handleClick(this, new Sys.UI.DomEvent(event), { insertionMode: Sys.Mvc.InsertionMode.replace, updateTargetId: &#39;update-div&#39; });"">Some Text</a>", actionLink.ToHtmlString());
+ }
+
+ [Fact]
+ public void ActionLinkAnonymousValuesAndAttributes_Unobtrusive()
+ {
+ // Arrange
+ AjaxHelper helper = GetAjaxHelper(unobtrusiveJavaScript: true);
+ object htmlAttributes = new { foo = "bar", baz = "quux", foo_bar = "baz_quux" };
+ object values = new { controller = "Controller" };
+ AjaxOptions options = new AjaxOptions { UpdateTargetId = "update-div" };
+
+ // Act
+ MvcHtmlString actionLink = helper.ActionLink("Some Text", "Action", values, options, htmlAttributes);
+
+ // Assert
+ Assert.Equal(@"<a baz=""quux"" data-ajax=""true"" data-ajax-mode=""replace"" data-ajax-update=""#update-div"" foo=""bar"" foo-bar=""baz_quux"" href=""" + MvcHelper.AppPathModifier + @"/app/Controller/Action"">Some Text</a>", actionLink.ToHtmlString());
+ }
+
+ [Fact]
+ public void ActionLinkTypedValues()
+ {
+ // Arrange
+ AjaxHelper helper = GetAjaxHelper(unobtrusiveJavaScript: false);
+ RouteValueDictionary values = new RouteValueDictionary
+ {
+ { "controller", "Controller" }
+ };
+
+ AjaxOptions options = new AjaxOptions { UpdateTargetId = "update-div" };
+
+ // Act
+ MvcHtmlString actionLink = helper.ActionLink("Some Text", "Action", values, options);
+
+ // Assert
+ Assert.Equal(@"<a href=""" + MvcHelper.AppPathModifier + @"/app/Controller/Action"" onclick=""Sys.Mvc.AsyncHyperlink.handleClick(this, new Sys.UI.DomEvent(event), { insertionMode: Sys.Mvc.InsertionMode.replace, updateTargetId: &#39;update-div&#39; });"">Some Text</a>", actionLink.ToHtmlString());
+ }
+
+ [Fact]
+ public void ActionLinkTypedValues_Unobtrusive()
+ {
+ // Arrange
+ AjaxHelper helper = GetAjaxHelper(unobtrusiveJavaScript: true);
+ RouteValueDictionary values = new RouteValueDictionary
+ {
+ { "controller", "Controller" }
+ };
+
+ AjaxOptions options = new AjaxOptions { UpdateTargetId = "update-div" };
+
+ // Act
+ MvcHtmlString actionLink = helper.ActionLink("Some Text", "Action", values, options);
+
+ // Assert
+ Assert.Equal(@"<a data-ajax=""true"" data-ajax-mode=""replace"" data-ajax-update=""#update-div"" href=""" + MvcHelper.AppPathModifier + @"/app/Controller/Action"">Some Text</a>", actionLink.ToHtmlString());
+ }
+
+ [Fact]
+ public void ActionLinkTypedValuesAndAttributes()
+ {
+ // Arrange
+ AjaxHelper helper = GetAjaxHelper(unobtrusiveJavaScript: false);
+ RouteValueDictionary values = new RouteValueDictionary
+ {
+ { "controller", "Controller" }
+ };
+ Dictionary<string, object> htmlAttributes = new Dictionary<string, object>
+ {
+ { "foo", "bar" },
+ { "baz", "quux" },
+ { "foo_bar", "baz_quux" }
+ };
+ AjaxOptions options = new AjaxOptions { UpdateTargetId = "update-div" };
+
+ // Act
+ MvcHtmlString actionLink = helper.ActionLink("Some Text", "Action", values, options, htmlAttributes);
+
+ // Assert
+ Assert.Equal(@"<a baz=""quux"" foo=""bar"" foo_bar=""baz_quux"" href=""" + MvcHelper.AppPathModifier + @"/app/Controller/Action"" onclick=""Sys.Mvc.AsyncHyperlink.handleClick(this, new Sys.UI.DomEvent(event), { insertionMode: Sys.Mvc.InsertionMode.replace, updateTargetId: &#39;update-div&#39; });"">Some Text</a>", actionLink.ToHtmlString());
+ }
+
+ [Fact]
+ public void ActionLinkTypedValuesAndAttributes_Unobtrusive()
+ {
+ // Arrange
+ AjaxHelper helper = GetAjaxHelper(unobtrusiveJavaScript: true);
+ RouteValueDictionary values = new RouteValueDictionary
+ {
+ { "controller", "Controller" }
+ };
+ Dictionary<string, object> htmlAttributes = new Dictionary<string, object>
+ {
+ { "foo", "bar" },
+ { "baz", "quux" },
+ { "foo_bar", "baz_quux" }
+ };
+ AjaxOptions options = new AjaxOptions { UpdateTargetId = "update-div" };
+
+ // Act
+ MvcHtmlString actionLink = helper.ActionLink("Some Text", "Action", values, options, htmlAttributes);
+
+ // Assert
+ Assert.Equal(@"<a baz=""quux"" data-ajax=""true"" data-ajax-mode=""replace"" data-ajax-update=""#update-div"" foo=""bar"" foo_bar=""baz_quux"" href=""" + MvcHelper.AppPathModifier + @"/app/Controller/Action"">Some Text</a>", actionLink.ToHtmlString());
+ }
+
+ [Fact]
+ public void ActionLinkController()
+ {
+ // Arrange
+ AjaxHelper ajaxHelper = GetAjaxHelper(unobtrusiveJavaScript: false);
+
+ // Act
+ MvcHtmlString actionLink = ajaxHelper.ActionLink("linkText", "Action", "Controller", new AjaxOptions());
+
+ // Assert
+ Assert.Equal(@"<a href=""" + MvcHelper.AppPathModifier + @"/app/Controller/Action"" onclick=""Sys.Mvc.AsyncHyperlink.handleClick(this, new Sys.UI.DomEvent(event), { insertionMode: Sys.Mvc.InsertionMode.replace });"">linkText</a>", actionLink.ToHtmlString());
+ }
+
+ [Fact]
+ public void ActionLinkController_Unobtrusive()
+ {
+ // Arrange
+ AjaxHelper ajaxHelper = GetAjaxHelper(unobtrusiveJavaScript: true);
+
+ // Act
+ MvcHtmlString actionLink = ajaxHelper.ActionLink("linkText", "Action", "Controller", new AjaxOptions());
+
+ // Assert
+ Assert.Equal(@"<a data-ajax=""true"" href=""" + MvcHelper.AppPathModifier + @"/app/Controller/Action"">linkText</a>", actionLink.ToHtmlString());
+ }
+
+ [Fact]
+ public void ActionLinkControllerAnonymousValues()
+ {
+ // Arrange
+ AjaxHelper helper = GetAjaxHelper(unobtrusiveJavaScript: false);
+ object values = new { id = 5 };
+ AjaxOptions options = new AjaxOptions { UpdateTargetId = "update-div" };
+
+ // Act
+ MvcHtmlString actionLink = helper.ActionLink("Some Text", "Action", "Controller", values, options);
+
+ // Assert
+ Assert.Equal(@"<a href=""" + MvcHelper.AppPathModifier + @"/app/Controller/Action/5"" onclick=""Sys.Mvc.AsyncHyperlink.handleClick(this, new Sys.UI.DomEvent(event), { insertionMode: Sys.Mvc.InsertionMode.replace, updateTargetId: &#39;update-div&#39; });"">Some Text</a>", actionLink.ToHtmlString());
+ }
+
+ [Fact]
+ public void ActionLinkControllerAnonymousValues_Unobtrusive()
+ {
+ // Arrange
+ AjaxHelper helper = GetAjaxHelper(unobtrusiveJavaScript: true);
+ object values = new { id = 5 };
+ AjaxOptions options = new AjaxOptions { UpdateTargetId = "update-div" };
+
+ // Act
+ MvcHtmlString actionLink = helper.ActionLink("Some Text", "Action", "Controller", values, options);
+
+ // Assert
+ Assert.Equal(@"<a data-ajax=""true"" data-ajax-mode=""replace"" data-ajax-update=""#update-div"" href=""" + MvcHelper.AppPathModifier + @"/app/Controller/Action/5"">Some Text</a>", actionLink.ToHtmlString());
+ }
+
+ [Fact]
+ public void ActionLinkControllerAnonymousValuesAndAttributes()
+ {
+ // Arrange
+ AjaxHelper helper = GetAjaxHelper(unobtrusiveJavaScript: false);
+ object htmlAttributes = new { foo = "bar", baz = "quux", foo_bar = "baz_quux" };
+ object values = new { id = 5 };
+ AjaxOptions options = new AjaxOptions { UpdateTargetId = "update-div" };
+
+ // Act
+ MvcHtmlString actionLink = helper.ActionLink("Some Text", "Action", "Controller", values, options, htmlAttributes);
+
+ // Assert
+ Assert.Equal(@"<a baz=""quux"" foo=""bar"" foo-bar=""baz_quux"" href=""" + MvcHelper.AppPathModifier + @"/app/Controller/Action/5"" onclick=""Sys.Mvc.AsyncHyperlink.handleClick(this, new Sys.UI.DomEvent(event), { insertionMode: Sys.Mvc.InsertionMode.replace, updateTargetId: &#39;update-div&#39; });"">Some Text</a>", actionLink.ToHtmlString());
+ }
+
+ [Fact]
+ public void ActionLinkControllerAnonymousValuesAndAttributes_Unobtrusive()
+ {
+ // Arrange
+ AjaxHelper helper = GetAjaxHelper(unobtrusiveJavaScript: true);
+ object htmlAttributes = new { foo = "bar", baz = "quux", foo_bar = "baz_quux" };
+ object values = new { id = 5 };
+ AjaxOptions options = new AjaxOptions { UpdateTargetId = "update-div" };
+
+ // Act
+ MvcHtmlString actionLink = helper.ActionLink("Some Text", "Action", "Controller", values, options, htmlAttributes);
+
+ // Assert
+ Assert.Equal(@"<a baz=""quux"" data-ajax=""true"" data-ajax-mode=""replace"" data-ajax-update=""#update-div"" foo=""bar"" foo-bar=""baz_quux"" href=""" + MvcHelper.AppPathModifier + @"/app/Controller/Action/5"">Some Text</a>", actionLink.ToHtmlString());
+ }
+
+ [Fact]
+ public void ActionLinkControllerTypedValues()
+ {
+ // Arrange
+ AjaxHelper helper = GetAjaxHelper(unobtrusiveJavaScript: false);
+ RouteValueDictionary values = new RouteValueDictionary
+ {
+ { "id", 5 }
+ };
+ AjaxOptions options = new AjaxOptions { UpdateTargetId = "update-div" };
+
+ // Act
+ MvcHtmlString actionLink = helper.ActionLink("Some Text", "Action", "Controller", values, options);
+
+ // Assert
+ Assert.Equal(@"<a href=""" + MvcHelper.AppPathModifier + @"/app/Controller/Action/5"" onclick=""Sys.Mvc.AsyncHyperlink.handleClick(this, new Sys.UI.DomEvent(event), { insertionMode: Sys.Mvc.InsertionMode.replace, updateTargetId: &#39;update-div&#39; });"">Some Text</a>", actionLink.ToHtmlString());
+ }
+
+ [Fact]
+ public void ActionLinkControllerTypedValues_Unobtrusive()
+ {
+ // Arrange
+ AjaxHelper helper = GetAjaxHelper(unobtrusiveJavaScript: true);
+ RouteValueDictionary values = new RouteValueDictionary
+ {
+ { "id", 5 }
+ };
+ AjaxOptions options = new AjaxOptions { UpdateTargetId = "update-div" };
+
+ // Act
+ MvcHtmlString actionLink = helper.ActionLink("Some Text", "Action", "Controller", values, options);
+
+ // Assert
+ Assert.Equal(@"<a data-ajax=""true"" data-ajax-mode=""replace"" data-ajax-update=""#update-div"" href=""" + MvcHelper.AppPathModifier + @"/app/Controller/Action/5"">Some Text</a>", actionLink.ToHtmlString());
+ }
+
+ [Fact]
+ public void ActionLinkControllerTypedValuesAndAttributes()
+ {
+ // Arrange
+ AjaxHelper helper = GetAjaxHelper(unobtrusiveJavaScript: false);
+ RouteValueDictionary values = new RouteValueDictionary
+ {
+ { "id", 5 }
+ };
+ Dictionary<string, object> htmlAttributes = new Dictionary<string, object>
+ {
+ { "foo", "bar" },
+ { "baz", "quux" },
+ { "foo_bar", "baz_quux" }
+ };
+ AjaxOptions options = new AjaxOptions { UpdateTargetId = "update-div" };
+
+ // Act
+ MvcHtmlString actionLink = helper.ActionLink("Some Text", "Action", "Controller", values, options, htmlAttributes);
+
+ // Assert
+ Assert.Equal(@"<a baz=""quux"" foo=""bar"" foo_bar=""baz_quux"" href=""" + MvcHelper.AppPathModifier + @"/app/Controller/Action/5"" onclick=""Sys.Mvc.AsyncHyperlink.handleClick(this, new Sys.UI.DomEvent(event), { insertionMode: Sys.Mvc.InsertionMode.replace, updateTargetId: &#39;update-div&#39; });"">Some Text</a>", actionLink.ToHtmlString());
+ }
+
+ [Fact]
+ public void ActionLinkControllerTypedValuesAndAttributes_Unobtrusive()
+ {
+ // Arrange
+ AjaxHelper helper = GetAjaxHelper(unobtrusiveJavaScript: true);
+ RouteValueDictionary values = new RouteValueDictionary
+ {
+ { "id", 5 }
+ };
+ Dictionary<string, object> htmlAttributes = new Dictionary<string, object>
+ {
+ { "foo", "bar" },
+ { "baz", "quux" },
+ { "foo_bar", "baz_quux" }
+ };
+ AjaxOptions options = new AjaxOptions { UpdateTargetId = "update-div" };
+
+ // Act
+ MvcHtmlString actionLink = helper.ActionLink("Some Text", "Action", "Controller", values, options, htmlAttributes);
+
+ // Assert
+ Assert.Equal(@"<a baz=""quux"" data-ajax=""true"" data-ajax-mode=""replace"" data-ajax-update=""#update-div"" foo=""bar"" foo_bar=""baz_quux"" href=""" + MvcHelper.AppPathModifier + @"/app/Controller/Action/5"">Some Text</a>", actionLink.ToHtmlString());
+ }
+
+ [Fact]
+ public void ActionLinkWithOptions()
+ {
+ // Arrange
+ AjaxHelper ajaxHelper = GetAjaxHelper(unobtrusiveJavaScript: false);
+
+ // Act
+ MvcHtmlString actionLink = ajaxHelper.ActionLink("linkText", "Action", "Controller", new AjaxOptions { UpdateTargetId = "some-id" });
+
+ // Assert
+ Assert.Equal(@"<a href=""" + MvcHelper.AppPathModifier + @"/app/Controller/Action"" onclick=""Sys.Mvc.AsyncHyperlink.handleClick(this, new Sys.UI.DomEvent(event), { insertionMode: Sys.Mvc.InsertionMode.replace, updateTargetId: &#39;some-id&#39; });"">linkText</a>", actionLink.ToHtmlString());
+ }
+
+ [Fact]
+ public void ActionLinkWithOptions_Unobtrusive()
+ {
+ // Arrange
+ AjaxHelper ajaxHelper = GetAjaxHelper(unobtrusiveJavaScript: true);
+ AjaxOptions options = new AjaxOptions { UpdateTargetId = "some-id" };
+
+ // Act
+ MvcHtmlString actionLink = ajaxHelper.ActionLink("linkText", "Action", "Controller", options);
+
+ // Assert
+ Assert.Equal(@"<a data-ajax=""true"" data-ajax-mode=""replace"" data-ajax-update=""#some-id"" href=""" + MvcHelper.AppPathModifier + @"/app/Controller/Action"">linkText</a>", actionLink.ToHtmlString());
+ }
+
+ [Fact]
+ public void ActionLinkWithNullHostName()
+ {
+ // Arrange
+ AjaxHelper ajaxHelper = GetAjaxHelper(unobtrusiveJavaScript: false);
+
+ // Act
+ MvcHtmlString actionLink = ajaxHelper.ActionLink("linkText", "Action", "Controller",
+ null, null, null, null, new AjaxOptions { UpdateTargetId = "some-id" }, null);
+
+ // Assert
+ Assert.Equal(@"<a href=""" + MvcHelper.AppPathModifier + @"/app/Controller/Action"" onclick=""Sys.Mvc.AsyncHyperlink.handleClick(this, new Sys.UI.DomEvent(event), { insertionMode: Sys.Mvc.InsertionMode.replace, updateTargetId: &#39;some-id&#39; });"">linkText</a>", actionLink.ToHtmlString());
+ }
+
+ [Fact]
+ public void ActionLinkWithNullHostName_Unobtrusive()
+ {
+ // Arrange
+ AjaxHelper ajaxHelper = GetAjaxHelper(unobtrusiveJavaScript: true);
+
+ // Act
+ MvcHtmlString actionLink = ajaxHelper.ActionLink("linkText", "Action", "Controller",
+ null, null, null, null, new AjaxOptions { UpdateTargetId = "some-id" }, null);
+
+ // Assert
+ Assert.Equal(@"<a data-ajax=""true"" data-ajax-mode=""replace"" data-ajax-update=""#some-id"" href=""" + MvcHelper.AppPathModifier + @"/app/Controller/Action"">linkText</a>", actionLink.ToHtmlString());
+ }
+
+ [Fact]
+ public void ActionLinkWithProtocol()
+ {
+ // Arrange
+ AjaxHelper ajaxHelper = GetAjaxHelper(unobtrusiveJavaScript: false);
+
+ // Act
+ MvcHtmlString actionLink = ajaxHelper.ActionLink("linkText", "Action", "Controller", "https", null, null, null, new AjaxOptions { UpdateTargetId = "some-id" }, null);
+
+ // Assert
+ Assert.Equal(@"<a href=""https://foo.bar.baz" + MvcHelper.AppPathModifier + @"/app/Controller/Action"" onclick=""Sys.Mvc.AsyncHyperlink.handleClick(this, new Sys.UI.DomEvent(event), { insertionMode: Sys.Mvc.InsertionMode.replace, updateTargetId: &#39;some-id&#39; });"">linkText</a>", actionLink.ToHtmlString());
+ }
+
+ [Fact]
+ public void ActionLinkWithProtocol_Unobtrusive()
+ {
+ // Arrange
+ AjaxHelper ajaxHelper = GetAjaxHelper(unobtrusiveJavaScript: true);
+
+ // Act
+ MvcHtmlString actionLink = ajaxHelper.ActionLink("linkText", "Action", "Controller", "https", null, null, null, new AjaxOptions { UpdateTargetId = "some-id" }, null);
+
+ // Assert
+ Assert.Equal(@"<a data-ajax=""true"" data-ajax-mode=""replace"" data-ajax-update=""#some-id"" href=""https://foo.bar.baz" + MvcHelper.AppPathModifier + @"/app/Controller/Action"">linkText</a>", actionLink.ToHtmlString());
+ }
+
+ // RouteLink
+
+ [Fact]
+ public void RouteLinkWithNullOptions()
+ {
+ // Arrange
+ AjaxHelper ajaxHelper = GetAjaxHelper(unobtrusiveJavaScript: false);
+
+ // Act
+ MvcHtmlString routeLink = ajaxHelper.RouteLink("Some Text", new RouteValueDictionary(), null);
+
+ // Assert
+ Assert.Equal(@"<a href=""" + MvcHelper.AppPathModifier + @"/app/home/oldaction"" onclick=""Sys.Mvc.AsyncHyperlink.handleClick(this, new Sys.UI.DomEvent(event), { insertionMode: Sys.Mvc.InsertionMode.replace });"">Some Text</a>", routeLink.ToHtmlString());
+ }
+
+ [Fact]
+ public void RouteLinkWithNullOptions_Unobtrusive()
+ {
+ // Arrange
+ AjaxHelper ajaxHelper = GetAjaxHelper(unobtrusiveJavaScript: true);
+
+ // Act
+ MvcHtmlString routeLink = ajaxHelper.RouteLink("Some Text", new RouteValueDictionary(), null);
+
+ // Assert
+ Assert.Equal(@"<a data-ajax=""true"" href=""" + MvcHelper.AppPathModifier + @"/app/home/oldaction"">Some Text</a>", routeLink.ToHtmlString());
+ }
+
+ [Fact]
+ public void RouteLinkAnonymousValues()
+ {
+ // Arrange
+ AjaxHelper helper = GetAjaxHelper(unobtrusiveJavaScript: false);
+ object values = new
+ {
+ action = "Action",
+ controller = "Controller"
+ };
+ AjaxOptions options = new AjaxOptions { UpdateTargetId = "update-div" };
+
+ // Act
+ MvcHtmlString routeLink = helper.RouteLink("Some Text", values, options);
+
+ // Assert
+ Assert.Equal(@"<a href=""" + MvcHelper.AppPathModifier + @"/app/Controller/Action"" onclick=""Sys.Mvc.AsyncHyperlink.handleClick(this, new Sys.UI.DomEvent(event), { insertionMode: Sys.Mvc.InsertionMode.replace, updateTargetId: &#39;update-div&#39; });"">Some Text</a>", routeLink.ToHtmlString());
+ }
+
+ [Fact]
+ public void RouteLinkAnonymousValues_Unobtrusive()
+ {
+ // Arrange
+ AjaxHelper helper = GetAjaxHelper(unobtrusiveJavaScript: true);
+ object values = new
+ {
+ action = "Action",
+ controller = "Controller"
+ };
+ AjaxOptions options = new AjaxOptions { UpdateTargetId = "update-div" };
+
+ // Act
+ MvcHtmlString routeLink = helper.RouteLink("Some Text", values, options);
+
+ // Assert
+ Assert.Equal(@"<a data-ajax=""true"" data-ajax-mode=""replace"" data-ajax-update=""#update-div"" href=""" + MvcHelper.AppPathModifier + @"/app/Controller/Action"">Some Text</a>", routeLink.ToHtmlString());
+ }
+
+ [Fact]
+ public void RouteLinkAnonymousValuesAndAttributes()
+ {
+ // Arrange
+ AjaxHelper helper = GetAjaxHelper(unobtrusiveJavaScript: false);
+ object htmlAttributes = new
+ {
+ foo = "bar",
+ baz = "quux",
+ foo_bar = "baz_quux"
+ };
+ object values = new
+ {
+ action = "Action",
+ controller = "Controller"
+ };
+ AjaxOptions options = new AjaxOptions { UpdateTargetId = "update-div" };
+
+ // Act
+ MvcHtmlString actionLink = helper.RouteLink("Some Text", values, options, htmlAttributes);
+
+ // Assert
+ Assert.Equal(@"<a baz=""quux"" foo=""bar"" foo-bar=""baz_quux"" href=""" + MvcHelper.AppPathModifier + @"/app/Controller/Action"" onclick=""Sys.Mvc.AsyncHyperlink.handleClick(this, new Sys.UI.DomEvent(event), { insertionMode: Sys.Mvc.InsertionMode.replace, updateTargetId: &#39;update-div&#39; });"">Some Text</a>", actionLink.ToHtmlString());
+ }
+
+ [Fact]
+ public void RouteLinkAnonymousValuesAndAttributes_Unobtrusive()
+ {
+ // Arrange
+ AjaxHelper helper = GetAjaxHelper(unobtrusiveJavaScript: true);
+ object htmlAttributes = new
+ {
+ foo = "bar",
+ baz = "quux",
+ foo_bar = "baz_quux"
+ };
+ object values = new
+ {
+ action = "Action",
+ controller = "Controller"
+ };
+ AjaxOptions options = new AjaxOptions { UpdateTargetId = "update-div" };
+
+ // Act
+ MvcHtmlString actionLink = helper.RouteLink("Some Text", values, options, htmlAttributes);
+
+ // Assert
+ Assert.Equal(@"<a baz=""quux"" data-ajax=""true"" data-ajax-mode=""replace"" data-ajax-update=""#update-div"" foo=""bar"" foo-bar=""baz_quux"" href=""" + MvcHelper.AppPathModifier + @"/app/Controller/Action"">Some Text</a>", actionLink.ToHtmlString());
+ }
+
+ [Fact]
+ public void RouteLinkTypedValues()
+ {
+ // Arrange
+ AjaxHelper helper = GetAjaxHelper(unobtrusiveJavaScript: false);
+ RouteValueDictionary values = new RouteValueDictionary
+ {
+ { "controller", "Controller" },
+ { "action", "Action" }
+ };
+
+ AjaxOptions options = new AjaxOptions { UpdateTargetId = "update-div" };
+
+ // Act
+ MvcHtmlString actionLink = helper.RouteLink("Some Text", values, options);
+
+ // Assert
+ Assert.Equal(@"<a href=""" + MvcHelper.AppPathModifier + @"/app/Controller/Action"" onclick=""Sys.Mvc.AsyncHyperlink.handleClick(this, new Sys.UI.DomEvent(event), { insertionMode: Sys.Mvc.InsertionMode.replace, updateTargetId: &#39;update-div&#39; });"">Some Text</a>", actionLink.ToHtmlString());
+ }
+
+ [Fact]
+ public void RouteLinkTypedValues_Unobtrusive()
+ {
+ // Arrange
+ AjaxHelper helper = GetAjaxHelper(unobtrusiveJavaScript: true);
+ RouteValueDictionary values = new RouteValueDictionary
+ {
+ { "controller", "Controller" },
+ { "action", "Action" }
+ };
+
+ AjaxOptions options = new AjaxOptions { UpdateTargetId = "update-div" };
+
+ // Act
+ MvcHtmlString actionLink = helper.RouteLink("Some Text", values, options);
+
+ // Assert
+ Assert.Equal(@"<a data-ajax=""true"" data-ajax-mode=""replace"" data-ajax-update=""#update-div"" href=""" + MvcHelper.AppPathModifier + @"/app/Controller/Action"">Some Text</a>", actionLink.ToHtmlString());
+ }
+
+ [Fact]
+ public void RouteLinkTypedValuesAndAttributes()
+ {
+ // Arrange
+ AjaxHelper helper = GetAjaxHelper(unobtrusiveJavaScript: false);
+ RouteValueDictionary values = new RouteValueDictionary
+ {
+ { "controller", "Controller" },
+ { "action", "Action" }
+ };
+ Dictionary<string, object> htmlAttributes = new Dictionary<string, object>
+ {
+ { "foo", "bar" },
+ { "baz", "quux" },
+ { "foo_bar", "baz_quux" }
+ };
+ AjaxOptions options = new AjaxOptions { UpdateTargetId = "update-div" };
+
+ // Act
+ MvcHtmlString actionLink = helper.RouteLink("Some Text", values, options, htmlAttributes);
+
+ // Assert
+ Assert.Equal(@"<a baz=""quux"" foo=""bar"" foo_bar=""baz_quux"" href=""" + MvcHelper.AppPathModifier + @"/app/Controller/Action"" onclick=""Sys.Mvc.AsyncHyperlink.handleClick(this, new Sys.UI.DomEvent(event), { insertionMode: Sys.Mvc.InsertionMode.replace, updateTargetId: &#39;update-div&#39; });"">Some Text</a>", actionLink.ToHtmlString());
+ }
+
+ [Fact]
+ public void RouteLinkTypedValuesAndAttributes_Unobtrusive()
+ {
+ // Arrange
+ AjaxHelper helper = GetAjaxHelper(unobtrusiveJavaScript: true);
+ RouteValueDictionary values = new RouteValueDictionary
+ {
+ { "controller", "Controller" },
+ { "action", "Action" }
+ };
+ Dictionary<string, object> htmlAttributes = new Dictionary<string, object>
+ {
+ { "foo", "bar" },
+ { "baz", "quux" },
+ { "foo_bar", "baz_quux" }
+ };
+ AjaxOptions options = new AjaxOptions { UpdateTargetId = "update-div" };
+
+ // Act
+ MvcHtmlString actionLink = helper.RouteLink("Some Text", values, options, htmlAttributes);
+
+ // Assert
+ Assert.Equal(@"<a baz=""quux"" data-ajax=""true"" data-ajax-mode=""replace"" data-ajax-update=""#update-div"" foo=""bar"" foo_bar=""baz_quux"" href=""" + MvcHelper.AppPathModifier + @"/app/Controller/Action"">Some Text</a>", actionLink.ToHtmlString());
+ }
+
+ [Fact]
+ public void RouteLinkNamedRoute()
+ {
+ // Arrange
+ AjaxHelper ajaxHelper = GetAjaxHelper(unobtrusiveJavaScript: false);
+
+ // Act
+ MvcHtmlString actionLink = ajaxHelper.RouteLink("linkText", "namedroute", new AjaxOptions());
+
+ // Assert
+ Assert.Equal(@"<a href=""" + MvcHelper.AppPathModifier + @"/app/named/home/oldaction"" onclick=""Sys.Mvc.AsyncHyperlink.handleClick(this, new Sys.UI.DomEvent(event), { insertionMode: Sys.Mvc.InsertionMode.replace });"">linkText</a>", actionLink.ToHtmlString());
+ }
+
+ [Fact]
+ public void RouteLinkNamedRoute_Unobtrusive()
+ {
+ // Arrange
+ AjaxHelper ajaxHelper = GetAjaxHelper(unobtrusiveJavaScript: true);
+
+ // Act
+ MvcHtmlString actionLink = ajaxHelper.RouteLink("linkText", "namedroute", new AjaxOptions());
+
+ // Assert
+ Assert.Equal(@"<a data-ajax=""true"" href=""" + MvcHelper.AppPathModifier + @"/app/named/home/oldaction"">linkText</a>", actionLink.ToHtmlString());
+ }
+
+ [Fact]
+ public void RouteLinkNamedRouteAnonymousAttributes()
+ {
+ // Arrange
+ AjaxHelper ajaxHelper = GetAjaxHelper(unobtrusiveJavaScript: false);
+ object htmlAttributes = new
+ {
+ foo = "bar",
+ baz = "quux",
+ foo_bar = "baz_quux"
+ };
+ AjaxOptions options = new AjaxOptions { UpdateTargetId = "update-div" };
+
+ // Act
+ MvcHtmlString actionLink = ajaxHelper.RouteLink("Some Text", "namedroute", options, htmlAttributes);
+
+ // Assert
+ Assert.Equal(@"<a baz=""quux"" foo=""bar"" foo-bar=""baz_quux"" href=""" + MvcHelper.AppPathModifier + @"/app/named/home/oldaction"" onclick=""Sys.Mvc.AsyncHyperlink.handleClick(this, new Sys.UI.DomEvent(event), { insertionMode: Sys.Mvc.InsertionMode.replace, updateTargetId: &#39;update-div&#39; });"">Some Text</a>", actionLink.ToHtmlString());
+ }
+
+ [Fact]
+ public void RouteLinkNamedRouteAnonymousAttributes_Unobtrusive()
+ {
+ // Arrange
+ AjaxHelper ajaxHelper = GetAjaxHelper(unobtrusiveJavaScript: true);
+ object htmlAttributes = new
+ {
+ foo = "bar",
+ baz = "quux",
+ foo_bar = "baz_quux"
+ };
+ AjaxOptions options = new AjaxOptions { UpdateTargetId = "update-div" };
+
+ // Act
+ MvcHtmlString actionLink = ajaxHelper.RouteLink("Some Text", "namedroute", options, htmlAttributes);
+
+ // Assert
+ Assert.Equal(@"<a baz=""quux"" data-ajax=""true"" data-ajax-mode=""replace"" data-ajax-update=""#update-div"" foo=""bar"" foo-bar=""baz_quux"" href=""" + MvcHelper.AppPathModifier + @"/app/named/home/oldaction"">Some Text</a>", actionLink.ToHtmlString());
+ }
+
+ [Fact]
+ public void RouteLinkNamedRouteTypedAttributes()
+ {
+ // Arrange
+ AjaxHelper ajaxHelper = GetAjaxHelper(unobtrusiveJavaScript: false);
+ Dictionary<string, object> htmlAttributes = new Dictionary<string, object> { { "foo", "bar" }, { "baz", "quux" }, { "foo_bar", "baz_quux" } };
+ AjaxOptions options = new AjaxOptions { UpdateTargetId = "update-div" };
+
+ // Act
+ MvcHtmlString actionLink = ajaxHelper.RouteLink("Some Text", "namedroute", options, htmlAttributes);
+
+ // Assert
+ Assert.Equal(@"<a baz=""quux"" foo=""bar"" foo_bar=""baz_quux"" href=""" + MvcHelper.AppPathModifier + @"/app/named/home/oldaction"" onclick=""Sys.Mvc.AsyncHyperlink.handleClick(this, new Sys.UI.DomEvent(event), { insertionMode: Sys.Mvc.InsertionMode.replace, updateTargetId: &#39;update-div&#39; });"">Some Text</a>", actionLink.ToHtmlString());
+ }
+
+ [Fact]
+ public void RouteLinkNamedRouteTypedAttributes_Unobtrusive()
+ {
+ // Arrange
+ AjaxHelper ajaxHelper = GetAjaxHelper(unobtrusiveJavaScript: true);
+ Dictionary<string, object> htmlAttributes = new Dictionary<string, object> { { "foo", "bar" }, { "baz", "quux" }, { "foo_bar", "baz_quux" } };
+ AjaxOptions options = new AjaxOptions { UpdateTargetId = "update-div" };
+
+ // Act
+ MvcHtmlString actionLink = ajaxHelper.RouteLink("Some Text", "namedroute", options, htmlAttributes);
+
+ // Assert
+ Assert.Equal(@"<a baz=""quux"" data-ajax=""true"" data-ajax-mode=""replace"" data-ajax-update=""#update-div"" foo=""bar"" foo_bar=""baz_quux"" href=""" + MvcHelper.AppPathModifier + @"/app/named/home/oldaction"">Some Text</a>", actionLink.ToHtmlString());
+ }
+
+ [Fact]
+ public void RouteLinkNamedRouteWithAnonymousValues()
+ {
+ // Arrange
+ AjaxHelper ajaxHelper = GetAjaxHelper(unobtrusiveJavaScript: false);
+ object values = new
+ {
+ action = "Action",
+ controller = "Controller"
+ };
+
+ // Act
+ MvcHtmlString actionLink = ajaxHelper.RouteLink("linkText", "namedroute", values, new AjaxOptions());
+
+ // Assert
+ Assert.Equal(@"<a href=""" + MvcHelper.AppPathModifier + @"/app/named/Controller/Action"" onclick=""Sys.Mvc.AsyncHyperlink.handleClick(this, new Sys.UI.DomEvent(event), { insertionMode: Sys.Mvc.InsertionMode.replace });"">linkText</a>", actionLink.ToHtmlString());
+ }
+
+ [Fact]
+ public void RouteLinkNamedRouteWithAnonymousValues_Unobtrusive()
+ {
+ // Arrange
+ AjaxHelper ajaxHelper = GetAjaxHelper(unobtrusiveJavaScript: true);
+ object values = new
+ {
+ action = "Action",
+ controller = "Controller"
+ };
+
+ // Act
+ MvcHtmlString actionLink = ajaxHelper.RouteLink("linkText", "namedroute", values, new AjaxOptions());
+
+ // Assert
+ Assert.Equal(@"<a data-ajax=""true"" href=""" + MvcHelper.AppPathModifier + @"/app/named/Controller/Action"">linkText</a>", actionLink.ToHtmlString());
+ }
+
+ [Fact]
+ public void RouteLinkNamedRouteAnonymousValuesAndAttributes()
+ {
+ // Arrange
+ AjaxHelper ajaxHelper = GetAjaxHelper(unobtrusiveJavaScript: false);
+ object values = new
+ {
+ action = "Action",
+ controller = "Controller"
+ };
+
+ object htmlAttributes = new
+ {
+ foo = "bar",
+ baz = "quux",
+ foo_bar = "baz_quux"
+ };
+ AjaxOptions options = new AjaxOptions { UpdateTargetId = "update-div" };
+
+ // Act
+ MvcHtmlString actionLink = ajaxHelper.RouteLink("Some Text", "namedroute", values, options, htmlAttributes);
+
+ // Assert
+ Assert.Equal(@"<a baz=""quux"" foo=""bar"" foo-bar=""baz_quux"" href=""" + MvcHelper.AppPathModifier + @"/app/named/Controller/Action"" onclick=""Sys.Mvc.AsyncHyperlink.handleClick(this, new Sys.UI.DomEvent(event), { insertionMode: Sys.Mvc.InsertionMode.replace, updateTargetId: &#39;update-div&#39; });"">Some Text</a>", actionLink.ToHtmlString());
+ }
+
+ [Fact]
+ public void RouteLinkNamedRouteAnonymousValuesAndAttributes_Unobtrusive()
+ {
+ // Arrange
+ AjaxHelper ajaxHelper = GetAjaxHelper(unobtrusiveJavaScript: true);
+ object values = new
+ {
+ action = "Action",
+ controller = "Controller"
+ };
+
+ object htmlAttributes = new
+ {
+ foo = "bar",
+ baz = "quux",
+ foo_bar = "baz_quux"
+ };
+ AjaxOptions options = new AjaxOptions { UpdateTargetId = "update-div" };
+
+ // Act
+ MvcHtmlString actionLink = ajaxHelper.RouteLink("Some Text", "namedroute", values, options, htmlAttributes);
+
+ // Assert
+ Assert.Equal(@"<a baz=""quux"" data-ajax=""true"" data-ajax-mode=""replace"" data-ajax-update=""#update-div"" foo=""bar"" foo-bar=""baz_quux"" href=""" + MvcHelper.AppPathModifier + @"/app/named/Controller/Action"">Some Text</a>", actionLink.ToHtmlString());
+ }
+
+ [Fact]
+ public void RouteLinkNamedRouteWithTypedValues()
+ {
+ // Arrange
+ AjaxHelper ajaxHelper = GetAjaxHelper(unobtrusiveJavaScript: false);
+ RouteValueDictionary values = new RouteValueDictionary
+ {
+ { "controller", "Controller" },
+ { "action", "Action" }
+ };
+
+ // Act
+ MvcHtmlString actionLink = ajaxHelper.RouteLink("linkText", "namedroute", values, new AjaxOptions());
+
+ // Assert
+ Assert.Equal(@"<a href=""" + MvcHelper.AppPathModifier + @"/app/named/Controller/Action"" onclick=""Sys.Mvc.AsyncHyperlink.handleClick(this, new Sys.UI.DomEvent(event), { insertionMode: Sys.Mvc.InsertionMode.replace });"">linkText</a>", actionLink.ToHtmlString());
+ }
+
+ [Fact]
+ public void RouteLinkNamedRouteWithTypedValues_Unobtrusive()
+ {
+ // Arrange
+ AjaxHelper ajaxHelper = GetAjaxHelper(unobtrusiveJavaScript: true);
+ RouteValueDictionary values = new RouteValueDictionary
+ {
+ { "controller", "Controller" },
+ { "action", "Action" }
+ };
+
+ // Act
+ MvcHtmlString actionLink = ajaxHelper.RouteLink("linkText", "namedroute", values, new AjaxOptions());
+
+ // Assert
+ Assert.Equal(@"<a data-ajax=""true"" href=""" + MvcHelper.AppPathModifier + @"/app/named/Controller/Action"">linkText</a>", actionLink.ToHtmlString());
+ }
+
+ [Fact]
+ public void RouteLinkNamedRouteTypedValuesAndAttributes()
+ {
+ // Arrange
+ AjaxHelper ajaxHelper = GetAjaxHelper(unobtrusiveJavaScript: false);
+ RouteValueDictionary values = new RouteValueDictionary
+ {
+ { "controller", "Controller" },
+ { "action", "Action" }
+ };
+
+ Dictionary<string, object> htmlAttributes = new Dictionary<string, object> { { "foo", "bar" }, { "baz", "quux" }, { "foo_bar", "baz_quux" } };
+ AjaxOptions options = new AjaxOptions { UpdateTargetId = "update-div" };
+
+ // Act
+ MvcHtmlString actionLink = ajaxHelper.RouteLink("Some Text", "namedroute", values, options, htmlAttributes);
+
+ // Assert
+ Assert.Equal(@"<a baz=""quux"" foo=""bar"" foo_bar=""baz_quux"" href=""" + MvcHelper.AppPathModifier + @"/app/named/Controller/Action"" onclick=""Sys.Mvc.AsyncHyperlink.handleClick(this, new Sys.UI.DomEvent(event), { insertionMode: Sys.Mvc.InsertionMode.replace, updateTargetId: &#39;update-div&#39; });"">Some Text</a>", actionLink.ToHtmlString());
+ }
+
+ [Fact]
+ public void RouteLinkNamedRouteTypedValuesAndAttributes_Unobtrusive()
+ {
+ // Arrange
+ AjaxHelper ajaxHelper = GetAjaxHelper(unobtrusiveJavaScript: true);
+ RouteValueDictionary values = new RouteValueDictionary
+ {
+ { "controller", "Controller" },
+ { "action", "Action" }
+ };
+
+ Dictionary<string, object> htmlAttributes = new Dictionary<string, object> { { "foo", "bar" }, { "baz", "quux" }, { "foo_bar", "baz_quux" } };
+ AjaxOptions options = new AjaxOptions { UpdateTargetId = "update-div" };
+
+ // Act
+ MvcHtmlString actionLink = ajaxHelper.RouteLink("Some Text", "namedroute", values, options, htmlAttributes);
+
+ // Assert
+ Assert.Equal(@"<a baz=""quux"" data-ajax=""true"" data-ajax-mode=""replace"" data-ajax-update=""#update-div"" foo=""bar"" foo_bar=""baz_quux"" href=""" + MvcHelper.AppPathModifier + @"/app/named/Controller/Action"">Some Text</a>", actionLink.ToHtmlString());
+ }
+
+ [Fact]
+ public void RouteLinkNamedRouteNullValuesAndAttributes()
+ {
+ // Arrange
+ AjaxHelper ajaxHelper = GetAjaxHelper(unobtrusiveJavaScript: false);
+ Dictionary<string, object> htmlAttributes = new Dictionary<string, object> { { "foo", "bar" }, { "baz", "quux" }, { "foo_bar", "baz_quux" } };
+ AjaxOptions options = new AjaxOptions { UpdateTargetId = "update-div" };
+
+ // Act
+ MvcHtmlString actionLink = ajaxHelper.RouteLink("Some Text", "namedroute", null, options, htmlAttributes);
+
+ // Assert
+ Assert.Equal(@"<a baz=""quux"" foo=""bar"" foo_bar=""baz_quux"" href=""" + MvcHelper.AppPathModifier + @"/app/named/home/oldaction"" onclick=""Sys.Mvc.AsyncHyperlink.handleClick(this, new Sys.UI.DomEvent(event), { insertionMode: Sys.Mvc.InsertionMode.replace, updateTargetId: &#39;update-div&#39; });"">Some Text</a>", actionLink.ToHtmlString());
+ }
+
+ [Fact]
+ public void RouteLinkNamedRouteNullValuesAndAttributes_Unobtrusive()
+ {
+ // Arrange
+ AjaxHelper ajaxHelper = GetAjaxHelper(unobtrusiveJavaScript: true);
+ Dictionary<string, object> htmlAttributes = new Dictionary<string, object> { { "foo", "bar" }, { "baz", "quux" }, { "foo_bar", "baz_quux" } };
+ AjaxOptions options = new AjaxOptions { UpdateTargetId = "update-div" };
+
+ // Act
+ MvcHtmlString actionLink = ajaxHelper.RouteLink("Some Text", "namedroute", null, options, htmlAttributes);
+
+ // Assert
+ Assert.Equal(@"<a baz=""quux"" data-ajax=""true"" data-ajax-mode=""replace"" data-ajax-update=""#update-div"" foo=""bar"" foo_bar=""baz_quux"" href=""" + MvcHelper.AppPathModifier + @"/app/named/home/oldaction"">Some Text</a>", actionLink.ToHtmlString());
+ }
+
+ [Fact]
+ public void RouteLinkWithHostName()
+ {
+ // Arrange
+ AjaxHelper ajaxHelper = GetAjaxHelper(unobtrusiveJavaScript: false);
+ Dictionary<string, object> htmlAttributes = new Dictionary<string, object> { { "foo", "bar" }, { "baz", "quux" }, { "foo_bar", "baz_quux" } };
+ AjaxOptions options = new AjaxOptions { UpdateTargetId = "update-div" };
+
+ // Act
+ MvcHtmlString actionLink = ajaxHelper.RouteLink("Some Text", "namedroute", null, "baz.bar.foo", null, null, options, htmlAttributes);
+
+ // Assert
+ Assert.Equal(@"<a baz=""quux"" foo=""bar"" foo_bar=""baz_quux"" href=""http://baz.bar.foo" + MvcHelper.AppPathModifier + @"/app/named/home/oldaction"" onclick=""Sys.Mvc.AsyncHyperlink.handleClick(this, new Sys.UI.DomEvent(event), { insertionMode: Sys.Mvc.InsertionMode.replace, updateTargetId: &#39;update-div&#39; });"">Some Text</a>", actionLink.ToHtmlString());
+ }
+
+ [Fact]
+ public void RouteLinkWithHostName_Unobtrusive()
+ {
+ // Arrange
+ AjaxHelper ajaxHelper = GetAjaxHelper(unobtrusiveJavaScript: true);
+ Dictionary<string, object> htmlAttributes = new Dictionary<string, object> { { "foo", "bar" }, { "baz", "quux" }, { "foo_bar", "baz_quux" } };
+ AjaxOptions options = new AjaxOptions { UpdateTargetId = "update-div" };
+
+ // Act
+ MvcHtmlString actionLink = ajaxHelper.RouteLink("Some Text", "namedroute", null, "baz.bar.foo", null, null, options, htmlAttributes);
+
+ // Assert
+ Assert.Equal(@"<a baz=""quux"" data-ajax=""true"" data-ajax-mode=""replace"" data-ajax-update=""#update-div"" foo=""bar"" foo_bar=""baz_quux"" href=""http://baz.bar.foo" + MvcHelper.AppPathModifier + @"/app/named/home/oldaction"">Some Text</a>", actionLink.ToHtmlString());
+ }
+
+ // BeginForm
+
+ [Fact]
+ public void BeginFormOnlyWithNullOptions()
+ {
+ // Arrange
+ AjaxHelper ajaxHelper = GetAjaxHelper(unobtrusiveJavaScript: false);
+ StringWriter writer = new StringWriter();
+ ajaxHelper.ViewContext.Writer = writer;
+
+ // Act
+ IDisposable form = ajaxHelper.BeginForm(null);
+
+ // Assert
+ Assert.Equal(@"<form action=""/rawUrl"" method=""post"" onclick=""Sys.Mvc.AsyncForm.handleClick(this, new Sys.UI.DomEvent(event));"" onsubmit=""Sys.Mvc.AsyncForm.handleSubmit(this, new Sys.UI.DomEvent(event), { insertionMode: Sys.Mvc.InsertionMode.replace });"">", writer.ToString());
+ }
+
+ [Fact]
+ public void BeginFormOnlyWithNullOptions_Unobtrusive()
+ {
+ // Arrange
+ AjaxHelper ajaxHelper = GetAjaxHelper(unobtrusiveJavaScript: true);
+ StringWriter writer = new StringWriter();
+ ajaxHelper.ViewContext.Writer = writer;
+
+ // Act
+ IDisposable form = ajaxHelper.BeginForm(null);
+
+ // Assert
+ Assert.Equal(@"<form action=""/rawUrl"" data-ajax=""true"" method=""post"">", writer.ToString());
+ }
+
+ [Fact]
+ public void BeginFormWithNullActionName()
+ {
+ // Arrange
+ AjaxHelper ajaxHelper = GetAjaxHelper(unobtrusiveJavaScript: false);
+ AjaxOptions ajaxOptions = new AjaxOptions();
+ StringWriter writer = new StringWriter();
+ ajaxHelper.ViewContext.Writer = writer;
+
+ // Act
+ IDisposable form = ajaxHelper.BeginForm(null, ajaxOptions);
+
+ // Assert
+ Assert.Equal(@"<form action=""" + MvcHelper.AppPathModifier + @"/app/home/oldaction"" method=""post"" onclick=""Sys.Mvc.AsyncForm.handleClick(this, new Sys.UI.DomEvent(event));"" onsubmit=""Sys.Mvc.AsyncForm.handleSubmit(this, new Sys.UI.DomEvent(event), { insertionMode: Sys.Mvc.InsertionMode.replace });"">", writer.ToString());
+ }
+
+ [Fact]
+ public void BeginFormWithNullActionName_Unobtrusive()
+ {
+ // Arrange
+ AjaxHelper ajaxHelper = GetAjaxHelper(unobtrusiveJavaScript: true);
+ AjaxOptions ajaxOptions = new AjaxOptions();
+ StringWriter writer = new StringWriter();
+ ajaxHelper.ViewContext.Writer = writer;
+
+ // Act
+ IDisposable form = ajaxHelper.BeginForm(null, ajaxOptions);
+
+ // Assert
+ Assert.Equal(@"<form action=""" + MvcHelper.AppPathModifier + @"/app/home/oldaction"" data-ajax=""true"" method=""post"">", writer.ToString());
+ }
+
+ [Fact]
+ public void BeginFormWithNullOptions()
+ {
+ // Arrange
+ AjaxHelper ajaxHelper = GetAjaxHelper(unobtrusiveJavaScript: false);
+ AjaxOptions ajaxOptions = new AjaxOptions();
+ StringWriter writer = new StringWriter();
+ ajaxHelper.ViewContext.Writer = writer;
+
+ // Act
+ IDisposable form = ajaxHelper.BeginForm("Action", "Controller", null);
+
+ // Assert
+ Assert.Equal(@"<form action=""" + MvcHelper.AppPathModifier + @"/app/Controller/Action"" method=""post"" onclick=""Sys.Mvc.AsyncForm.handleClick(this, new Sys.UI.DomEvent(event));"" onsubmit=""Sys.Mvc.AsyncForm.handleSubmit(this, new Sys.UI.DomEvent(event), { insertionMode: Sys.Mvc.InsertionMode.replace });"">", writer.ToString());
+ }
+
+ [Fact]
+ public void BeginFormWithNullOptions_Unobtrusive()
+ {
+ // Arrange
+ AjaxHelper ajaxHelper = GetAjaxHelper(unobtrusiveJavaScript: true);
+ AjaxOptions ajaxOptions = new AjaxOptions();
+ StringWriter writer = new StringWriter();
+ ajaxHelper.ViewContext.Writer = writer;
+
+ // Act
+ IDisposable form = ajaxHelper.BeginForm("Action", "Controller", null);
+
+ // Assert
+ Assert.Equal(@"<form action=""" + MvcHelper.AppPathModifier + @"/app/Controller/Action"" data-ajax=""true"" method=""post"">", writer.ToString());
+ }
+
+ [Fact]
+ public void BeginForm()
+ {
+ // Arrange
+ AjaxHelper ajaxHelper = GetAjaxHelper(unobtrusiveJavaScript: false);
+ AjaxOptions ajaxOptions = new AjaxOptions();
+ StringWriter writer = new StringWriter();
+ ajaxHelper.ViewContext.Writer = writer;
+
+ // Act
+ IDisposable form = ajaxHelper.BeginForm(ajaxOptions);
+
+ // Assert
+ Assert.Equal(@"<form action=""/rawUrl"" method=""post"" onclick=""Sys.Mvc.AsyncForm.handleClick(this, new Sys.UI.DomEvent(event));"" onsubmit=""Sys.Mvc.AsyncForm.handleSubmit(this, new Sys.UI.DomEvent(event), { insertionMode: Sys.Mvc.InsertionMode.replace });"">", writer.ToString());
+ }
+
+ [Fact]
+ public void BeginForm_Unobtrusive()
+ {
+ // Arrange
+ AjaxHelper ajaxHelper = GetAjaxHelper(unobtrusiveJavaScript: true);
+ AjaxOptions ajaxOptions = new AjaxOptions();
+ StringWriter writer = new StringWriter();
+ ajaxHelper.ViewContext.Writer = writer;
+
+ // Act
+ IDisposable form = ajaxHelper.BeginForm(ajaxOptions);
+
+ // Assert
+ Assert.Equal(@"<form action=""/rawUrl"" data-ajax=""true"" method=""post"">", writer.ToString());
+ }
+
+ [Fact]
+ public void BeginFormAction()
+ {
+ // Arrange
+ AjaxHelper ajaxHelper = GetAjaxHelper(unobtrusiveJavaScript: false);
+ AjaxOptions ajaxOptions = new AjaxOptions();
+ StringWriter writer = new StringWriter();
+ ajaxHelper.ViewContext.Writer = writer;
+
+ // Act
+ IDisposable form = ajaxHelper.BeginForm("Action", ajaxOptions);
+
+ // Assert
+ Assert.Equal(@"<form action=""" + MvcHelper.AppPathModifier + @"/app/home/Action"" method=""post"" onclick=""Sys.Mvc.AsyncForm.handleClick(this, new Sys.UI.DomEvent(event));"" onsubmit=""Sys.Mvc.AsyncForm.handleSubmit(this, new Sys.UI.DomEvent(event), { insertionMode: Sys.Mvc.InsertionMode.replace });"">", writer.ToString());
+ }
+
+ [Fact]
+ public void BeginFormAction_Unobtrusive()
+ {
+ // Arrange
+ AjaxHelper ajaxHelper = GetAjaxHelper(unobtrusiveJavaScript: true);
+ AjaxOptions ajaxOptions = new AjaxOptions();
+ StringWriter writer = new StringWriter();
+ ajaxHelper.ViewContext.Writer = writer;
+
+ // Act
+ IDisposable form = ajaxHelper.BeginForm("Action", ajaxOptions);
+
+ // Assert
+ Assert.Equal(@"<form action=""" + MvcHelper.AppPathModifier + @"/app/home/Action"" data-ajax=""true"" method=""post"">", writer.ToString());
+ }
+
+ [Fact]
+ public void BeginFormAnonymousValues()
+ {
+ // Arrange
+ AjaxHelper ajaxHelper = GetAjaxHelper(unobtrusiveJavaScript: false);
+ AjaxOptions ajaxOptions = new AjaxOptions();
+ object values = new { controller = "Controller" };
+ StringWriter writer = new StringWriter();
+ ajaxHelper.ViewContext.Writer = writer;
+
+ // Act
+ IDisposable form = ajaxHelper.BeginForm("Action", values, ajaxOptions);
+
+ // Assert
+ Assert.Equal(@"<form action=""" + MvcHelper.AppPathModifier + @"/app/Controller/Action"" method=""post"" onclick=""Sys.Mvc.AsyncForm.handleClick(this, new Sys.UI.DomEvent(event));"" onsubmit=""Sys.Mvc.AsyncForm.handleSubmit(this, new Sys.UI.DomEvent(event), { insertionMode: Sys.Mvc.InsertionMode.replace });"">", writer.ToString());
+ }
+
+ [Fact]
+ public void BeginFormAnonymousValues_Unobtrusive()
+ {
+ // Arrange
+ AjaxHelper ajaxHelper = GetAjaxHelper(unobtrusiveJavaScript: true);
+ AjaxOptions ajaxOptions = new AjaxOptions();
+ object values = new { controller = "Controller" };
+ StringWriter writer = new StringWriter();
+ ajaxHelper.ViewContext.Writer = writer;
+
+ // Act
+ IDisposable form = ajaxHelper.BeginForm("Action", values, ajaxOptions);
+
+ // Assert
+ Assert.Equal(@"<form action=""" + MvcHelper.AppPathModifier + @"/app/Controller/Action"" data-ajax=""true"" method=""post"">", writer.ToString());
+ }
+
+ [Fact]
+ public void BeginFormAnonymousValuesAndAttributes()
+ {
+ // Arrange
+ AjaxHelper ajaxHelper = GetAjaxHelper(unobtrusiveJavaScript: false);
+ AjaxOptions ajaxOptions = new AjaxOptions { UpdateTargetId = "some-id" };
+ object values = new { controller = "Controller" };
+ object htmlAttributes = new { method = "get", foo_bar = "baz_quux" };
+ StringWriter writer = new StringWriter();
+ ajaxHelper.ViewContext.Writer = writer;
+
+ // Act
+ IDisposable form = ajaxHelper.BeginForm("Action", values, ajaxOptions, htmlAttributes);
+
+ // Assert
+ Assert.Equal(@"<form action=""" + MvcHelper.AppPathModifier + @"/app/Controller/Action"" foo-bar=""baz_quux"" method=""get"" onclick=""Sys.Mvc.AsyncForm.handleClick(this, new Sys.UI.DomEvent(event));"" onsubmit=""Sys.Mvc.AsyncForm.handleSubmit(this, new Sys.UI.DomEvent(event), { insertionMode: Sys.Mvc.InsertionMode.replace, updateTargetId: &#39;some-id&#39; });"">", writer.ToString());
+ }
+
+ [Fact]
+ public void BeginFormAnonymousValuesAndAttributes_Unobtrusive()
+ {
+ // Arrange
+ AjaxHelper ajaxHelper = GetAjaxHelper(unobtrusiveJavaScript: true);
+ AjaxOptions ajaxOptions = new AjaxOptions { UpdateTargetId = "some-id" };
+ object values = new { controller = "Controller" };
+ object htmlAttributes = new { method = "get", foo_bar = "baz_quux" };
+ StringWriter writer = new StringWriter();
+ ajaxHelper.ViewContext.Writer = writer;
+
+ // Act
+ IDisposable form = ajaxHelper.BeginForm("Action", values, ajaxOptions, htmlAttributes);
+
+ // Assert
+ Assert.Equal(@"<form action=""" + MvcHelper.AppPathModifier + @"/app/Controller/Action"" data-ajax=""true"" data-ajax-mode=""replace"" data-ajax-update=""#some-id"" foo-bar=""baz_quux"" method=""get"">", writer.ToString());
+ }
+
+ [Fact]
+ public void BeginFormTypedValues()
+ {
+ // Arrange
+ AjaxHelper ajaxHelper = GetAjaxHelper(unobtrusiveJavaScript: false);
+ AjaxOptions ajaxOptions = new AjaxOptions();
+ RouteValueDictionary values = new RouteValueDictionary
+ {
+ { "controller", "Controller" }
+ };
+ StringWriter writer = new StringWriter();
+ ajaxHelper.ViewContext.Writer = writer;
+
+ // Act
+ IDisposable form = ajaxHelper.BeginForm("Action", values, ajaxOptions);
+
+ // Assert
+ Assert.Equal(@"<form action=""" + MvcHelper.AppPathModifier + @"/app/Controller/Action"" method=""post"" onclick=""Sys.Mvc.AsyncForm.handleClick(this, new Sys.UI.DomEvent(event));"" onsubmit=""Sys.Mvc.AsyncForm.handleSubmit(this, new Sys.UI.DomEvent(event), { insertionMode: Sys.Mvc.InsertionMode.replace });"">", writer.ToString());
+ }
+
+ [Fact]
+ public void BeginFormTypedValues_Unobtrusive()
+ {
+ // Arrange
+ AjaxHelper ajaxHelper = GetAjaxHelper(unobtrusiveJavaScript: true);
+ AjaxOptions ajaxOptions = new AjaxOptions();
+ RouteValueDictionary values = new RouteValueDictionary
+ {
+ { "controller", "Controller" }
+ };
+ StringWriter writer = new StringWriter();
+ ajaxHelper.ViewContext.Writer = writer;
+
+ // Act
+ IDisposable form = ajaxHelper.BeginForm("Action", values, ajaxOptions);
+
+ // Assert
+ Assert.Equal(@"<form action=""" + MvcHelper.AppPathModifier + @"/app/Controller/Action"" data-ajax=""true"" method=""post"">", writer.ToString());
+ }
+
+ [Fact]
+ public void BeginFormTypedValuesAndAttributes()
+ {
+ // Arrange
+ AjaxHelper ajaxHelper = GetAjaxHelper(unobtrusiveJavaScript: false);
+ AjaxOptions ajaxOptions = new AjaxOptions { UpdateTargetId = "some-id" };
+ RouteValueDictionary values = new RouteValueDictionary
+ {
+ { "controller", "Controller" }
+ };
+ Dictionary<string, object> htmlAttributes = new Dictionary<string, object>
+ {
+ { "method", "get" },
+ { "foo_bar", "baz_quux" }
+ };
+ StringWriter writer = new StringWriter();
+ ajaxHelper.ViewContext.Writer = writer;
+
+ // Act
+ IDisposable form = ajaxHelper.BeginForm("Action", values, ajaxOptions, htmlAttributes);
+
+ // Assert
+ Assert.Equal(@"<form action=""" + MvcHelper.AppPathModifier + @"/app/Controller/Action"" foo_bar=""baz_quux"" method=""get"" onclick=""Sys.Mvc.AsyncForm.handleClick(this, new Sys.UI.DomEvent(event));"" onsubmit=""Sys.Mvc.AsyncForm.handleSubmit(this, new Sys.UI.DomEvent(event), { insertionMode: Sys.Mvc.InsertionMode.replace, updateTargetId: &#39;some-id&#39; });"">", writer.ToString());
+ }
+
+ [Fact]
+ public void BeginFormTypedValuesAndAttributes_Unobtrusive()
+ {
+ // Arrange
+ AjaxHelper ajaxHelper = GetAjaxHelper(unobtrusiveJavaScript: true);
+ AjaxOptions ajaxOptions = new AjaxOptions { UpdateTargetId = "some-id" };
+ RouteValueDictionary values = new RouteValueDictionary
+ {
+ { "controller", "Controller" }
+ };
+ Dictionary<string, object> htmlAttributes = new Dictionary<string, object>
+ {
+ { "method", "get" },
+ { "foo_bar", "baz_quux" }
+ };
+ StringWriter writer = new StringWriter();
+ ajaxHelper.ViewContext.Writer = writer;
+
+ // Act
+ IDisposable form = ajaxHelper.BeginForm("Action", values, ajaxOptions, htmlAttributes);
+
+ // Assert
+ Assert.Equal(@"<form action=""" + MvcHelper.AppPathModifier + @"/app/Controller/Action"" data-ajax=""true"" data-ajax-mode=""replace"" data-ajax-update=""#some-id"" foo_bar=""baz_quux"" method=""get"">", writer.ToString());
+ }
+
+ [Fact]
+ public void BeginFormController()
+ {
+ // Arrange
+ AjaxHelper ajaxHelper = GetAjaxHelper(unobtrusiveJavaScript: false);
+ AjaxOptions ajaxOptions = new AjaxOptions();
+ StringWriter writer = new StringWriter();
+ ajaxHelper.ViewContext.Writer = writer;
+
+ // Act
+ IDisposable form = ajaxHelper.BeginForm("Action", "Controller", ajaxOptions);
+
+ // Assert
+ Assert.Equal(@"<form action=""" + MvcHelper.AppPathModifier + @"/app/Controller/Action"" method=""post"" onclick=""Sys.Mvc.AsyncForm.handleClick(this, new Sys.UI.DomEvent(event));"" onsubmit=""Sys.Mvc.AsyncForm.handleSubmit(this, new Sys.UI.DomEvent(event), { insertionMode: Sys.Mvc.InsertionMode.replace });"">", writer.ToString());
+ }
+
+ [Fact]
+ public void BeginFormController_Unobtrusive()
+ {
+ // Arrange
+ AjaxHelper ajaxHelper = GetAjaxHelper(unobtrusiveJavaScript: true);
+ AjaxOptions ajaxOptions = new AjaxOptions();
+ StringWriter writer = new StringWriter();
+ ajaxHelper.ViewContext.Writer = writer;
+
+ // Act
+ IDisposable form = ajaxHelper.BeginForm("Action", "Controller", ajaxOptions);
+
+ // Assert
+ Assert.Equal(@"<form action=""" + MvcHelper.AppPathModifier + @"/app/Controller/Action"" data-ajax=""true"" method=""post"">", writer.ToString());
+ }
+
+ [Fact]
+ public void BeginFormControllerAnonymousValues()
+ {
+ // Arrange
+ AjaxHelper ajaxHelper = GetAjaxHelper(unobtrusiveJavaScript: false);
+ AjaxOptions ajaxOptions = new AjaxOptions();
+ object values = new { id = 5 };
+ StringWriter writer = new StringWriter();
+ ajaxHelper.ViewContext.Writer = writer;
+
+ // Act
+ IDisposable form = ajaxHelper.BeginForm("Action", "Controller", values, ajaxOptions);
+
+ // Assert
+ Assert.Equal(@"<form action=""" + MvcHelper.AppPathModifier + @"/app/Controller/Action/5"" method=""post"" onclick=""Sys.Mvc.AsyncForm.handleClick(this, new Sys.UI.DomEvent(event));"" onsubmit=""Sys.Mvc.AsyncForm.handleSubmit(this, new Sys.UI.DomEvent(event), { insertionMode: Sys.Mvc.InsertionMode.replace });"">", writer.ToString());
+ }
+
+ [Fact]
+ public void BeginFormControllerAnonymousValues_Unobtrusive()
+ {
+ // Arrange
+ AjaxHelper ajaxHelper = GetAjaxHelper(unobtrusiveJavaScript: true);
+ AjaxOptions ajaxOptions = new AjaxOptions();
+ object values = new { id = 5 };
+ StringWriter writer = new StringWriter();
+ ajaxHelper.ViewContext.Writer = writer;
+
+ // Act
+ IDisposable form = ajaxHelper.BeginForm("Action", "Controller", values, ajaxOptions);
+
+ // Assert
+ Assert.Equal(@"<form action=""" + MvcHelper.AppPathModifier + @"/app/Controller/Action/5"" data-ajax=""true"" method=""post"">", writer.ToString());
+ }
+
+ [Fact]
+ public void BeginFormControllerAnonymousValuesAndAttributes()
+ {
+ // Arrange
+ AjaxHelper ajaxHelper = GetAjaxHelper(unobtrusiveJavaScript: false);
+ AjaxOptions ajaxOptions = new AjaxOptions();
+ object values = new { id = 5 };
+ object htmlAttributes = new { method = "get", foo_bar = "baz_quux" };
+ StringWriter writer = new StringWriter();
+ ajaxHelper.ViewContext.Writer = writer;
+
+ // Act
+ IDisposable form = ajaxHelper.BeginForm("Action", "Controller", values, ajaxOptions, htmlAttributes);
+
+ // Assert
+ Assert.Equal(@"<form action=""" + MvcHelper.AppPathModifier + @"/app/Controller/Action/5"" foo-bar=""baz_quux"" method=""get"" onclick=""Sys.Mvc.AsyncForm.handleClick(this, new Sys.UI.DomEvent(event));"" onsubmit=""Sys.Mvc.AsyncForm.handleSubmit(this, new Sys.UI.DomEvent(event), { insertionMode: Sys.Mvc.InsertionMode.replace });"">", writer.ToString());
+ }
+
+ [Fact]
+ public void BeginFormControllerAnonymousValuesAndAttributes_Unobtrusive()
+ {
+ // Arrange
+ AjaxHelper ajaxHelper = GetAjaxHelper(unobtrusiveJavaScript: true);
+ AjaxOptions ajaxOptions = new AjaxOptions();
+ object values = new { id = 5 };
+ object htmlAttributes = new { method = "get", foo_bar = "baz_quux" };
+ StringWriter writer = new StringWriter();
+ ajaxHelper.ViewContext.Writer = writer;
+
+ // Act
+ IDisposable form = ajaxHelper.BeginForm("Action", "Controller", values, ajaxOptions, htmlAttributes);
+
+ // Assert
+ Assert.Equal(@"<form action=""" + MvcHelper.AppPathModifier + @"/app/Controller/Action/5"" data-ajax=""true"" foo-bar=""baz_quux"" method=""get"">", writer.ToString());
+ }
+
+ [Fact]
+ public void BeginFormControllerTypedValues()
+ {
+ // Arrange
+ AjaxHelper ajaxHelper = GetAjaxHelper(unobtrusiveJavaScript: false);
+ AjaxOptions ajaxOptions = new AjaxOptions();
+ RouteValueDictionary values = new RouteValueDictionary
+ {
+ { "id", 5 }
+ };
+ StringWriter writer = new StringWriter();
+ ajaxHelper.ViewContext.Writer = writer;
+
+ // Act
+ IDisposable form = ajaxHelper.BeginForm("Action", "Controller", values, ajaxOptions);
+
+ // Assert
+ Assert.Equal(@"<form action=""" + MvcHelper.AppPathModifier + @"/app/Controller/Action/5"" method=""post"" onclick=""Sys.Mvc.AsyncForm.handleClick(this, new Sys.UI.DomEvent(event));"" onsubmit=""Sys.Mvc.AsyncForm.handleSubmit(this, new Sys.UI.DomEvent(event), { insertionMode: Sys.Mvc.InsertionMode.replace });"">", writer.ToString());
+ }
+
+ [Fact]
+ public void BeginFormControllerTypedValues_Unobtrusive()
+ {
+ // Arrange
+ AjaxHelper ajaxHelper = GetAjaxHelper(unobtrusiveJavaScript: true);
+ AjaxOptions ajaxOptions = new AjaxOptions();
+ RouteValueDictionary values = new RouteValueDictionary
+ {
+ { "id", 5 }
+ };
+ StringWriter writer = new StringWriter();
+ ajaxHelper.ViewContext.Writer = writer;
+
+ // Act
+ IDisposable form = ajaxHelper.BeginForm("Action", "Controller", values, ajaxOptions);
+
+ // Assert
+ Assert.Equal(@"<form action=""" + MvcHelper.AppPathModifier + @"/app/Controller/Action/5"" data-ajax=""true"" method=""post"">", writer.ToString());
+ }
+
+ [Fact]
+ public void BeginFormControllerTypedValuesAndAttributes()
+ {
+ // Arrange
+ AjaxHelper ajaxHelper = GetAjaxHelper(unobtrusiveJavaScript: false);
+ AjaxOptions ajaxOptions = new AjaxOptions();
+ RouteValueDictionary values = new RouteValueDictionary
+ {
+ { "id", 5 }
+ };
+ Dictionary<string, object> htmlAttributes = new Dictionary<string, object>
+ {
+ { "method", "get" },
+ { "foo_bar", "baz_quux" }
+ };
+ StringWriter writer = new StringWriter();
+ ajaxHelper.ViewContext.Writer = writer;
+
+ // Act
+ IDisposable form = ajaxHelper.BeginForm("Action", "Controller", values, ajaxOptions, htmlAttributes);
+
+ // Assert
+ Assert.Equal(@"<form action=""" + MvcHelper.AppPathModifier + @"/app/Controller/Action/5"" foo_bar=""baz_quux"" method=""get"" onclick=""Sys.Mvc.AsyncForm.handleClick(this, new Sys.UI.DomEvent(event));"" onsubmit=""Sys.Mvc.AsyncForm.handleSubmit(this, new Sys.UI.DomEvent(event), { insertionMode: Sys.Mvc.InsertionMode.replace });"">", writer.ToString());
+ }
+
+ [Fact]
+ public void BeginFormControllerTypedValuesAndAttributes_Unobtrusive()
+ {
+ // Arrange
+ AjaxHelper ajaxHelper = GetAjaxHelper(unobtrusiveJavaScript: true);
+ AjaxOptions ajaxOptions = new AjaxOptions();
+ RouteValueDictionary values = new RouteValueDictionary
+ {
+ { "id", 5 }
+ };
+ Dictionary<string, object> htmlAttributes = new Dictionary<string, object>
+ {
+ { "method", "get" },
+ { "foo_bar", "baz_quux" }
+ };
+ StringWriter writer = new StringWriter();
+ ajaxHelper.ViewContext.Writer = writer;
+
+ // Act
+ IDisposable form = ajaxHelper.BeginForm("Action", "Controller", values, ajaxOptions, htmlAttributes);
+
+ // Assert
+ Assert.Equal(@"<form action=""" + MvcHelper.AppPathModifier + @"/app/Controller/Action/5"" data-ajax=""true"" foo_bar=""baz_quux"" method=""get"">", writer.ToString());
+ }
+
+ [Fact]
+ public void BeginFormWithTargetId()
+ {
+ // Arrange
+ AjaxHelper ajaxHelper = GetAjaxHelper(unobtrusiveJavaScript: false);
+ AjaxOptions ajaxOptions = new AjaxOptions { UpdateTargetId = "some-id" };
+ StringWriter writer = new StringWriter();
+ ajaxHelper.ViewContext.Writer = writer;
+
+ // Act
+ IDisposable form = ajaxHelper.BeginForm("Action", "Controller", ajaxOptions);
+
+ // Assert
+ Assert.Equal(@"<form action=""" + MvcHelper.AppPathModifier + @"/app/Controller/Action"" method=""post"" onclick=""Sys.Mvc.AsyncForm.handleClick(this, new Sys.UI.DomEvent(event));"" onsubmit=""Sys.Mvc.AsyncForm.handleSubmit(this, new Sys.UI.DomEvent(event), { insertionMode: Sys.Mvc.InsertionMode.replace, updateTargetId: &#39;some-id&#39; });"">", writer.ToString());
+ }
+
+ [Fact]
+ public void BeginFormWithTargetId_Unobtrusive()
+ {
+ // Arrange
+ AjaxHelper ajaxHelper = GetAjaxHelper(unobtrusiveJavaScript: true);
+ AjaxOptions ajaxOptions = new AjaxOptions { UpdateTargetId = "some-id" };
+ StringWriter writer = new StringWriter();
+ ajaxHelper.ViewContext.Writer = writer;
+
+ // Act
+ IDisposable form = ajaxHelper.BeginForm("Action", "Controller", ajaxOptions);
+
+ // Assert
+ Assert.Equal(@"<form action=""" + MvcHelper.AppPathModifier + @"/app/Controller/Action"" data-ajax=""true"" data-ajax-mode=""replace"" data-ajax-update=""#some-id"" method=""post"">", writer.ToString());
+ }
+
+ // BeginRouteForm
+
+ [Fact]
+ public void BeginRouteForm()
+ {
+ // Arrange
+ AjaxHelper ajaxHelper = GetAjaxHelper(unobtrusiveJavaScript: false);
+ AjaxOptions ajaxOptions = new AjaxOptions();
+ StringWriter writer = new StringWriter();
+ ajaxHelper.ViewContext.Writer = writer;
+
+ // Act
+ IDisposable form = ajaxHelper.BeginRouteForm("namedroute", ajaxOptions);
+
+ // Assert
+ Assert.Equal(@"<form action=""" + MvcHelper.AppPathModifier + @"/app/named/home/oldaction"" method=""post"" onclick=""Sys.Mvc.AsyncForm.handleClick(this, new Sys.UI.DomEvent(event));"" onsubmit=""Sys.Mvc.AsyncForm.handleSubmit(this, new Sys.UI.DomEvent(event), { insertionMode: Sys.Mvc.InsertionMode.replace });"">", writer.ToString());
+ }
+
+ [Fact]
+ public void BeginRouteForm_Unobtrusive()
+ {
+ // Arrange
+ AjaxHelper ajaxHelper = GetAjaxHelper(unobtrusiveJavaScript: true);
+ AjaxOptions ajaxOptions = new AjaxOptions();
+ StringWriter writer = new StringWriter();
+ ajaxHelper.ViewContext.Writer = writer;
+
+ // Act
+ IDisposable form = ajaxHelper.BeginRouteForm("namedroute", ajaxOptions);
+
+ // Assert
+ Assert.Equal(@"<form action=""" + MvcHelper.AppPathModifier + @"/app/named/home/oldaction"" data-ajax=""true"" method=""post"">", writer.ToString());
+ }
+
+ [Fact]
+ public void BeginRouteFormAnonymousValues()
+ {
+ // Arrange
+ AjaxHelper ajaxHelper = GetAjaxHelper(unobtrusiveJavaScript: false);
+ AjaxOptions ajaxOptions = new AjaxOptions();
+ AjaxHelper poes = GetAjaxHelper(unobtrusiveJavaScript: false);
+ StringWriter writer = new StringWriter();
+ ajaxHelper.ViewContext.Writer = writer;
+
+ // Act
+ IDisposable form = ajaxHelper.BeginRouteForm("namedroute", null, ajaxOptions);
+
+ // Assert
+ Assert.Equal(@"<form action=""" + MvcHelper.AppPathModifier + @"/app/named/home/oldaction"" method=""post"" onclick=""Sys.Mvc.AsyncForm.handleClick(this, new Sys.UI.DomEvent(event));"" onsubmit=""Sys.Mvc.AsyncForm.handleSubmit(this, new Sys.UI.DomEvent(event), { insertionMode: Sys.Mvc.InsertionMode.replace });"">", writer.ToString());
+ }
+
+ [Fact]
+ public void BeginRouteFormAnonymousValues_Unobtrusive()
+ {
+ // Arrange
+ AjaxHelper ajaxHelper = GetAjaxHelper(unobtrusiveJavaScript: true);
+ AjaxOptions ajaxOptions = new AjaxOptions();
+ AjaxHelper poes = GetAjaxHelper(unobtrusiveJavaScript: true);
+ StringWriter writer = new StringWriter();
+ ajaxHelper.ViewContext.Writer = writer;
+
+ // Act
+ IDisposable form = ajaxHelper.BeginRouteForm("namedroute", null, ajaxOptions);
+
+ // Assert
+ Assert.Equal(@"<form action=""" + MvcHelper.AppPathModifier + @"/app/named/home/oldaction"" data-ajax=""true"" method=""post"">", writer.ToString());
+ }
+
+ [Fact]
+ public void BeginRouteFormAnonymousValuesAndAttributes()
+ {
+ // Arrange
+ AjaxHelper ajaxHelper = GetAjaxHelper(unobtrusiveJavaScript: false);
+ AjaxOptions ajaxOptions = new AjaxOptions { UpdateTargetId = "some-id" };
+ object htmlAttributes = new { method = "get", foo_bar = "baz_quux" };
+ StringWriter writer = new StringWriter();
+ ajaxHelper.ViewContext.Writer = writer;
+
+ // Act
+ IDisposable form = ajaxHelper.BeginRouteForm("namedroute", null, ajaxOptions, htmlAttributes);
+
+ // Assert
+ Assert.Equal(@"<form action=""" + MvcHelper.AppPathModifier + @"/app/named/home/oldaction"" foo-bar=""baz_quux"" method=""get"" onclick=""Sys.Mvc.AsyncForm.handleClick(this, new Sys.UI.DomEvent(event));"" onsubmit=""Sys.Mvc.AsyncForm.handleSubmit(this, new Sys.UI.DomEvent(event), { insertionMode: Sys.Mvc.InsertionMode.replace, updateTargetId: &#39;some-id&#39; });"">", writer.ToString());
+ }
+
+ [Fact]
+ public void BeginRouteFormAnonymousValuesAndAttributes_Unobtrusive()
+ {
+ // Arrange
+ AjaxHelper ajaxHelper = GetAjaxHelper(unobtrusiveJavaScript: true);
+ AjaxOptions ajaxOptions = new AjaxOptions { UpdateTargetId = "some-id" };
+ object htmlAttributes = new { method = "get", foo_bar = "baz_quux" };
+ StringWriter writer = new StringWriter();
+ ajaxHelper.ViewContext.Writer = writer;
+
+ // Act
+ IDisposable form = ajaxHelper.BeginRouteForm("namedroute", null, ajaxOptions, htmlAttributes);
+
+ // Assert
+ Assert.Equal(@"<form action=""" + MvcHelper.AppPathModifier + @"/app/named/home/oldaction"" data-ajax=""true"" data-ajax-mode=""replace"" data-ajax-update=""#some-id"" foo-bar=""baz_quux"" method=""get"">", writer.ToString());
+ }
+
+ [Fact]
+ public void BeginRouteFormCanUseNamedRouteWithoutSpecifyingDefaults()
+ {
+ // DevDiv 217072: Non-mvc specific helpers should not give default values for controller and action
+
+ // Arrange
+ AjaxHelper ajaxHelper = GetAjaxHelper(unobtrusiveJavaScript: false);
+ ajaxHelper.RouteCollection.MapRoute("MyRouteName", "any/url", new { controller = "Charlie" });
+ StringWriter writer = new StringWriter();
+ ajaxHelper.ViewContext.Writer = writer;
+
+ // Act
+ IDisposable form = ajaxHelper.BeginRouteForm("MyRouteName", new AjaxOptions());
+
+ // Assert
+ Assert.Equal(@"<form action=""" + MvcHelper.AppPathModifier + @"/app/any/url"" method=""post"" onclick=""Sys.Mvc.AsyncForm.handleClick(this, new Sys.UI.DomEvent(event));"" onsubmit=""Sys.Mvc.AsyncForm.handleSubmit(this, new Sys.UI.DomEvent(event), { insertionMode: Sys.Mvc.InsertionMode.replace });"">", writer.ToString());
+ }
+
+ [Fact]
+ public void BeginRouteFormCanUseNamedRouteWithoutSpecifyingDefaults_Unobtrusive()
+ {
+ // DevDiv 217072: Non-mvc specific helpers should not give default values for controller and action
+
+ // Arrange
+ AjaxHelper ajaxHelper = GetAjaxHelper(unobtrusiveJavaScript: true);
+ ajaxHelper.RouteCollection.MapRoute("MyRouteName", "any/url", new { controller = "Charlie" });
+ StringWriter writer = new StringWriter();
+ ajaxHelper.ViewContext.Writer = writer;
+
+ // Act
+ IDisposable form = ajaxHelper.BeginRouteForm("MyRouteName", new AjaxOptions());
+
+ // Assert
+ Assert.Equal(@"<form action=""" + MvcHelper.AppPathModifier + @"/app/any/url"" data-ajax=""true"" method=""post"">", writer.ToString());
+ }
+
+ [Fact]
+ public void BeginRouteFormTypedValues()
+ {
+ // Arrange
+ AjaxHelper ajaxHelper = GetAjaxHelper(unobtrusiveJavaScript: false);
+ AjaxOptions ajaxOptions = new AjaxOptions();
+ RouteValueDictionary values = new RouteValueDictionary();
+ StringWriter writer = new StringWriter();
+ ajaxHelper.ViewContext.Writer = writer;
+
+ // Act
+ IDisposable form = ajaxHelper.BeginRouteForm("namedroute", values, ajaxOptions);
+
+ // Assert
+ Assert.Equal(@"<form action=""" + MvcHelper.AppPathModifier + @"/app/named/home/oldaction"" method=""post"" onclick=""Sys.Mvc.AsyncForm.handleClick(this, new Sys.UI.DomEvent(event));"" onsubmit=""Sys.Mvc.AsyncForm.handleSubmit(this, new Sys.UI.DomEvent(event), { insertionMode: Sys.Mvc.InsertionMode.replace });"">", writer.ToString());
+ }
+
+ [Fact]
+ public void BeginRouteFormTypedValues_Unobtrusive()
+ {
+ // Arrange
+ AjaxHelper ajaxHelper = GetAjaxHelper(unobtrusiveJavaScript: true);
+ AjaxOptions ajaxOptions = new AjaxOptions();
+ RouteValueDictionary values = new RouteValueDictionary();
+ StringWriter writer = new StringWriter();
+ ajaxHelper.ViewContext.Writer = writer;
+
+ // Act
+ IDisposable form = ajaxHelper.BeginRouteForm("namedroute", values, ajaxOptions);
+
+ // Assert
+ Assert.Equal(@"<form action=""" + MvcHelper.AppPathModifier + @"/app/named/home/oldaction"" data-ajax=""true"" method=""post"">", writer.ToString());
+ }
+
+ [Fact]
+ public void BeginRouteFormTypedValuesAndAttributes()
+ {
+ // Arrange
+ AjaxHelper ajaxHelper = GetAjaxHelper(unobtrusiveJavaScript: false);
+ Dictionary<string, object> htmlAttributes = new Dictionary<string, object> { { "method", "get" }, { "foo_bar", "baz_quux" } };
+ AjaxOptions ajaxOptions = new AjaxOptions { UpdateTargetId = "some-id" };
+ RouteValueDictionary values = new RouteValueDictionary();
+ StringWriter writer = new StringWriter();
+ ajaxHelper.ViewContext.Writer = writer;
+
+ // Act
+ IDisposable form = ajaxHelper.BeginRouteForm("namedroute", values, ajaxOptions, htmlAttributes);
+
+ // Assert
+ Assert.Equal(@"<form action=""" + MvcHelper.AppPathModifier + @"/app/named/home/oldaction"" foo_bar=""baz_quux"" method=""get"" onclick=""Sys.Mvc.AsyncForm.handleClick(this, new Sys.UI.DomEvent(event));"" onsubmit=""Sys.Mvc.AsyncForm.handleSubmit(this, new Sys.UI.DomEvent(event), { insertionMode: Sys.Mvc.InsertionMode.replace, updateTargetId: &#39;some-id&#39; });"">", writer.ToString());
+ }
+
+ [Fact]
+ public void BeginRouteFormTypedValuesAndAttributes_Unobtrusive()
+ {
+ // Arrange
+ AjaxHelper ajaxHelper = GetAjaxHelper(unobtrusiveJavaScript: true);
+ Dictionary<string, object> htmlAttributes = new Dictionary<string, object> { { "method", "get" }, { "foo_bar", "baz_quux" } };
+ AjaxOptions ajaxOptions = new AjaxOptions { UpdateTargetId = "some-id" };
+ RouteValueDictionary values = new RouteValueDictionary();
+ StringWriter writer = new StringWriter();
+ ajaxHelper.ViewContext.Writer = writer;
+
+ // Act
+ IDisposable form = ajaxHelper.BeginRouteForm("namedroute", values, ajaxOptions, htmlAttributes);
+
+ // Assert
+ Assert.Equal(@"<form action=""" + MvcHelper.AppPathModifier + @"/app/named/home/oldaction"" data-ajax=""true"" data-ajax-mode=""replace"" data-ajax-update=""#some-id"" foo_bar=""baz_quux"" method=""get"">", writer.ToString());
+ }
+
+ // Helpers
+
+ private static AjaxHelper GetAjaxHelper(bool unobtrusiveJavaScript = false)
+ {
+ var mockRequest = new Mock<HttpRequestBase>();
+ mockRequest.Setup(o => o.Url).Returns(new Uri("http://foo.bar.baz"));
+ mockRequest.Setup(o => o.RawUrl).Returns("/rawUrl");
+ mockRequest.Setup(o => o.PathInfo).Returns(String.Empty);
+ mockRequest.Setup(o => o.ApplicationPath).Returns("/app/");
+
+ var mockResponse = new Mock<HttpResponseBase>();
+ mockResponse.Setup(o => o.ApplyAppPathModifier(It.IsAny<string>())).Returns<string>(r => MvcHelper.AppPathModifier + r);
+
+ var mockHttpContext = new Mock<HttpContextBase>();
+ mockHttpContext.Setup(o => o.Request).Returns(mockRequest.Object);
+ mockHttpContext.Setup(o => o.Session).Returns((HttpSessionStateBase)null);
+ mockHttpContext.Setup(o => o.Items).Returns(new Hashtable());
+ mockHttpContext.Setup(o => o.Response).Returns(mockResponse.Object);
+
+ var routes = new RouteCollection();
+ routes.MapRoute("default", "{controller}/{action}/{id}", new { id = "defaultid" });
+ routes.MapRoute("namedroute", "named/{controller}/{action}/{id}", new { id = "defaultid" });
+
+ var routeData = new RouteData();
+ routeData.Values.Add("controller", "home");
+ routeData.Values.Add("action", "oldaction");
+
+ var viewContext = new ViewContext()
+ {
+ HttpContext = mockHttpContext.Object,
+ RouteData = routeData,
+ UnobtrusiveJavaScriptEnabled = unobtrusiveJavaScript,
+ Writer = TextWriter.Null
+ };
+
+ return new AjaxHelper(viewContext, new Mock<IViewDataContainer>().Object, routes);
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Ajax/Test/AjaxOptionsTest.cs b/test/System.Web.Mvc.Test/Ajax/Test/AjaxOptionsTest.cs
new file mode 100644
index 00000000..8e31ce29
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Ajax/Test/AjaxOptionsTest.cs
@@ -0,0 +1,290 @@
+using System.Collections.Generic;
+using System.Web.TestUtil;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Mvc.Ajax.Test
+{
+ public class AjaxOptionsTest
+ {
+ [Fact]
+ public void InsertionModeProperty()
+ {
+ // Arrange
+ AjaxOptions options = new AjaxOptions();
+
+ // Act & Assert
+ MemberHelper.TestEnumProperty(options, "InsertionMode", InsertionMode.Replace, false);
+ }
+
+ [Fact]
+ public void InsertionModePropertyExceptionText()
+ {
+ // Arrange
+ AjaxOptions options = new AjaxOptions();
+
+ // Act & Assert
+ Assert.ThrowsArgumentOutOfRange(
+ delegate { options.InsertionMode = (InsertionMode)4; },
+ "value",
+ @"Specified argument was out of the range of valid values.");
+ }
+
+ [Fact]
+ public void InsertionModeStringTests()
+ {
+ // Act & Assert
+ Assert.Equal(new AjaxOptions { InsertionMode = InsertionMode.Replace }.InsertionModeString, "Sys.Mvc.InsertionMode.replace");
+ Assert.Equal(new AjaxOptions { InsertionMode = InsertionMode.InsertAfter }.InsertionModeString, "Sys.Mvc.InsertionMode.insertAfter");
+ Assert.Equal(new AjaxOptions { InsertionMode = InsertionMode.InsertBefore }.InsertionModeString, "Sys.Mvc.InsertionMode.insertBefore");
+ }
+
+ [Fact]
+ public void InsertionModeUnobtrusiveTests()
+ {
+ // Act & Assert
+ Assert.Equal(new AjaxOptions { InsertionMode = InsertionMode.Replace }.InsertionModeUnobtrusive, "replace");
+ Assert.Equal(new AjaxOptions { InsertionMode = InsertionMode.InsertAfter }.InsertionModeUnobtrusive, "after");
+ Assert.Equal(new AjaxOptions { InsertionMode = InsertionMode.InsertBefore }.InsertionModeUnobtrusive, "before");
+ }
+
+ [Fact]
+ public void HttpMethodProperty()
+ {
+ // Arrange
+ AjaxOptions options = new AjaxOptions();
+
+ // Act & Assert
+ MemberHelper.TestStringProperty(options, "HttpMethod", String.Empty);
+ }
+
+ [Fact]
+ public void OnBeginProperty()
+ {
+ // Arrange
+ AjaxOptions options = new AjaxOptions();
+
+ // Act & Assert
+ MemberHelper.TestStringProperty(options, "OnBegin", String.Empty);
+ }
+
+ [Fact]
+ public void OnFailureProperty()
+ {
+ // Arrange
+ AjaxOptions options = new AjaxOptions();
+
+ // Act & Assert
+ MemberHelper.TestStringProperty(options, "OnFailure", String.Empty);
+ }
+
+ [Fact]
+ public void OnSuccessProperty()
+ {
+ // Arrange
+ AjaxOptions options = new AjaxOptions();
+
+ // Act & Assert
+ MemberHelper.TestStringProperty(options, "OnSuccess", String.Empty);
+ }
+
+ [Fact]
+ public void ToJavascriptStringWithEmptyOptions()
+ {
+ string s = (new AjaxOptions()).ToJavascriptString();
+ Assert.Equal("{ insertionMode: Sys.Mvc.InsertionMode.replace }", s);
+ }
+
+ [Fact]
+ public void ToJavascriptString()
+ {
+ // Arrange
+ AjaxOptions options = new AjaxOptions
+ {
+ InsertionMode = InsertionMode.InsertBefore,
+ Confirm = "confirm",
+ HttpMethod = "POST",
+ LoadingElementId = "loadingElement",
+ UpdateTargetId = "someId",
+ Url = "http://someurl.com",
+ OnBegin = "some_begin_function",
+ OnComplete = "some_complete_function",
+ OnFailure = "some_failure_function",
+ OnSuccess = "some_success_function",
+ };
+
+ // Act
+ string s = options.ToJavascriptString();
+
+ // Assert
+ Assert.Equal("{ insertionMode: Sys.Mvc.InsertionMode.insertBefore, " +
+ "confirm: 'confirm', " +
+ "httpMethod: 'POST', " +
+ "loadingElementId: 'loadingElement', " +
+ "updateTargetId: 'someId', " +
+ "url: 'http://someurl.com', " +
+ "onBegin: Function.createDelegate(this, some_begin_function), " +
+ "onComplete: Function.createDelegate(this, some_complete_function), " +
+ "onFailure: Function.createDelegate(this, some_failure_function), " +
+ "onSuccess: Function.createDelegate(this, some_success_function) }", s);
+ }
+
+ [Fact]
+ public void ToJavascriptStringEscapesQuotesCorrectly()
+ {
+ // Arrange
+ AjaxOptions options = new AjaxOptions
+ {
+ InsertionMode = InsertionMode.InsertBefore,
+ Confirm = @"""confirm""",
+ HttpMethod = "POST",
+ LoadingElementId = "loading'Element'",
+ UpdateTargetId = "someId",
+ Url = "http://someurl.com",
+ OnBegin = "some_begin_function",
+ OnComplete = "some_complete_function",
+ OnFailure = "some_failure_function",
+ OnSuccess = "some_success_function",
+ };
+
+ // Act
+ string s = options.ToJavascriptString();
+
+ // Assert
+ Assert.Equal("{ insertionMode: Sys.Mvc.InsertionMode.insertBefore, " +
+ @"confirm: '""confirm""', " +
+ "httpMethod: 'POST', " +
+ @"loadingElementId: 'loading\'Element\'', " +
+ "updateTargetId: 'someId', " +
+ "url: 'http://someurl.com', " +
+ "onBegin: Function.createDelegate(this, some_begin_function), " +
+ "onComplete: Function.createDelegate(this, some_complete_function), " +
+ "onFailure: Function.createDelegate(this, some_failure_function), " +
+ "onSuccess: Function.createDelegate(this, some_success_function) }", s);
+ }
+
+ [Fact]
+ public void ToJavascriptStringWithOnlyUpdateTargetId()
+ {
+ // Arrange
+ AjaxOptions options = new AjaxOptions { UpdateTargetId = "someId" };
+
+ // Act
+ string s = options.ToJavascriptString();
+
+ // Assert
+ Assert.Equal("{ insertionMode: Sys.Mvc.InsertionMode.replace, updateTargetId: 'someId' }", s);
+ }
+
+ [Fact]
+ public void ToJavascriptStringWithUpdateTargetIdAndExplicitInsertionMode()
+ {
+ // Arrange
+ AjaxOptions options = new AjaxOptions { InsertionMode = InsertionMode.InsertAfter, UpdateTargetId = "someId" };
+
+ // Act
+ string s = options.ToJavascriptString();
+
+ // Assert
+ Assert.Equal("{ insertionMode: Sys.Mvc.InsertionMode.insertAfter, updateTargetId: 'someId' }", s);
+ }
+
+ [Fact]
+ public void ToUnobtrusiveHtmlAttributesWithEmptyOptions()
+ {
+ // Arrange
+ AjaxOptions options = new AjaxOptions();
+
+ // Act
+ IDictionary<string, object> attributes = options.ToUnobtrusiveHtmlAttributes();
+
+ // Assert
+ Assert.Single(attributes);
+ Assert.Equal("true", attributes["data-ajax"]);
+ }
+
+ [Fact]
+ public void ToUnobtrusiveHtmlAttributes()
+ {
+ // Arrange
+ AjaxOptions options = new AjaxOptions
+ {
+ InsertionMode = InsertionMode.InsertBefore,
+ Confirm = "confirm",
+ HttpMethod = "POST",
+ LoadingElementId = "loadingElement",
+ LoadingElementDuration = 450,
+ UpdateTargetId = "someId",
+ Url = "http://someurl.com",
+ OnBegin = "some_begin_function",
+ OnComplete = "some_complete_function",
+ OnFailure = "some_failure_function",
+ OnSuccess = "some_success_function",
+ };
+
+ // Act
+ var attributes = options.ToUnobtrusiveHtmlAttributes();
+
+ // Assert
+ Assert.Equal(12, attributes.Count);
+ Assert.Equal("true", attributes["data-ajax"]);
+ Assert.Equal("confirm", attributes["data-ajax-confirm"]);
+ Assert.Equal("POST", attributes["data-ajax-method"]);
+ Assert.Equal("#loadingElement", attributes["data-ajax-loading"]);
+ Assert.Equal(450, attributes["data-ajax-loading-duration"]);
+ Assert.Equal("http://someurl.com", attributes["data-ajax-url"]);
+ Assert.Equal("#someId", attributes["data-ajax-update"]);
+ Assert.Equal("before", attributes["data-ajax-mode"]);
+ Assert.Equal("some_begin_function", attributes["data-ajax-begin"]);
+ Assert.Equal("some_complete_function", attributes["data-ajax-complete"]);
+ Assert.Equal("some_failure_function", attributes["data-ajax-failure"]);
+ Assert.Equal("some_success_function", attributes["data-ajax-success"]);
+ }
+
+ [Fact]
+ public void ToUnobtrusiveHtmlAttributesWithOnlyUpdateTargetId()
+ {
+ // Arrange
+ AjaxOptions options = new AjaxOptions { UpdateTargetId = "someId" };
+
+ // Act
+ var attributes = options.ToUnobtrusiveHtmlAttributes();
+
+ // Assert
+ Assert.Equal(3, attributes.Count);
+ Assert.Equal("true", attributes["data-ajax"]);
+ Assert.Equal("#someId", attributes["data-ajax-update"]);
+ Assert.Equal("replace", attributes["data-ajax-mode"]); // Only added when UpdateTargetId is set
+ }
+
+ [Fact]
+ public void ToUnobtrusiveHtmlAttributesWithUpdateTargetIdAndExplicitInsertionMode()
+ {
+ // Arrange
+ AjaxOptions options = new AjaxOptions
+ {
+ InsertionMode = InsertionMode.InsertAfter,
+ UpdateTargetId = "someId"
+ };
+
+ // Act
+ var attributes = options.ToUnobtrusiveHtmlAttributes();
+
+ // Assert
+ Assert.Equal(3, attributes.Count);
+ Assert.Equal("true", attributes["data-ajax"]);
+ Assert.Equal("#someId", attributes["data-ajax-update"]);
+ Assert.Equal("after", attributes["data-ajax-mode"]);
+ }
+
+ [Fact]
+ public void UpdateTargetIdProperty()
+ {
+ // Arrange
+ AjaxOptions options = new AjaxOptions();
+
+ // Act & Assert
+ MemberHelper.TestStringProperty(options, "UpdateTargetId", String.Empty);
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Async/Test/AsyncActionDescriptorTest.cs b/test/System.Web.Mvc.Test/Async/Test/AsyncActionDescriptorTest.cs
new file mode 100644
index 00000000..2bda6573
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Async/Test/AsyncActionDescriptorTest.cs
@@ -0,0 +1,51 @@
+using System.Collections.Generic;
+using Moq;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Mvc.Async.Test
+{
+ public class AsyncActionDescriptorTest
+ {
+ [Fact]
+ public void SynchronousExecuteThrows()
+ {
+ // Arrange
+ AsyncActionDescriptor actionDescriptor = new TestableAsyncActionDescriptor();
+
+ // Act & Assert
+ Assert.Throws<InvalidOperationException>(
+ () => actionDescriptor.Execute(new Mock<ControllerContext>().Object, parameters: null),
+ "The asynchronous action method 'testAction' cannot be executed synchronously."
+ );
+ }
+
+ private class TestableAsyncActionDescriptor : AsyncActionDescriptor
+ {
+ public override string ActionName
+ {
+ get { return "testAction"; }
+ }
+
+ public override ControllerDescriptor ControllerDescriptor
+ {
+ get { throw new NotImplementedException(); }
+ }
+
+ public override IAsyncResult BeginExecute(ControllerContext controllerContext, IDictionary<string, object> parameters, AsyncCallback callback, object state)
+ {
+ throw new NotImplementedException();
+ }
+
+ public override object EndExecute(IAsyncResult asyncResult)
+ {
+ throw new NotImplementedException();
+ }
+
+ public override ParameterDescriptor[] GetParameters()
+ {
+ throw new NotImplementedException();
+ }
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Async/Test/AsyncActionMethodSelectorTest.cs b/test/System.Web.Mvc.Test/Async/Test/AsyncActionMethodSelectorTest.cs
new file mode 100644
index 00000000..6a23adde
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Async/Test/AsyncActionMethodSelectorTest.cs
@@ -0,0 +1,377 @@
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection;
+using System.Threading.Tasks;
+using Moq;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Mvc.Async.Test
+{
+ public class AsyncActionMethodSelectorTest
+ {
+ [Fact]
+ public void AliasedMethodsProperty()
+ {
+ // Arrange
+ Type controllerType = typeof(MethodLocatorController);
+
+ // Act
+ AsyncActionMethodSelector selector = new AsyncActionMethodSelector(controllerType);
+
+ // Assert
+ Assert.Equal(3, selector.AliasedMethods.Length);
+
+ List<MethodInfo> sortedAliasedMethods = selector.AliasedMethods.OrderBy(methodInfo => methodInfo.Name).ToList();
+ Assert.Equal("Bar", sortedAliasedMethods[0].Name);
+ Assert.Equal("FooRenamed", sortedAliasedMethods[1].Name);
+ Assert.Equal("Renamed", sortedAliasedMethods[2].Name);
+ }
+
+ [Fact]
+ public void ControllerTypeProperty()
+ {
+ // Arrange
+ Type controllerType = typeof(MethodLocatorController);
+ AsyncActionMethodSelector selector = new AsyncActionMethodSelector(controllerType);
+
+ // Act & Assert
+ Assert.Same(controllerType, selector.ControllerType);
+ }
+
+ [Fact]
+ public void FindAction_DoesNotMatchAsyncMethod()
+ {
+ // Arrange
+ Type controllerType = typeof(MethodLocatorController);
+ AsyncActionMethodSelector selector = new AsyncActionMethodSelector(controllerType);
+
+ // Act
+ ActionDescriptorCreator creator = selector.FindAction(null, "EventPatternAsync");
+
+ // Assert
+ Assert.Null(creator);
+ }
+
+ [Fact]
+ public void FindAction_DoesNotMatchCompletedMethod()
+ {
+ // Arrange
+ Type controllerType = typeof(MethodLocatorController);
+ AsyncActionMethodSelector selector = new AsyncActionMethodSelector(controllerType);
+
+ // Act
+ ActionDescriptorCreator creator = selector.FindAction(null, "EventPatternCompleted");
+
+ // Assert
+ Assert.Null(creator);
+ }
+
+ [Fact]
+ public void FindAction_ReturnsMatchingMethodIfOneMethodMatches()
+ {
+ // Arrange
+ Type controllerType = typeof(SelectionAttributeController);
+ AsyncActionMethodSelector selector = new AsyncActionMethodSelector(controllerType);
+
+ // Act
+ ActionDescriptorCreator creator = selector.FindAction(null, "OneMatch");
+ ActionDescriptor actionDescriptor = creator("someName", new Mock<ControllerDescriptor>().Object);
+
+ // Assert
+ var castActionDescriptor = Assert.IsType<ReflectedActionDescriptor>(actionDescriptor);
+ Assert.Equal("OneMatch", castActionDescriptor.MethodInfo.Name);
+ Assert.Equal(typeof(string), castActionDescriptor.MethodInfo.GetParameters()[0].ParameterType);
+ }
+
+ [Fact]
+ public void FindAction_ReturnsMethodWithActionSelectionAttributeIfMultipleMethodsMatchRequest()
+ {
+ // DevDiv Bugs 212062: If multiple action methods match a request, we should match only the methods with an
+ // [ActionMethod] attribute since we assume those methods are more specific.
+
+ // Arrange
+ Type controllerType = typeof(SelectionAttributeController);
+ AsyncActionMethodSelector selector = new AsyncActionMethodSelector(controllerType);
+
+ // Act
+ ActionDescriptorCreator creator = selector.FindAction(null, "ShouldMatchMethodWithSelectionAttribute");
+ ActionDescriptor actionDescriptor = creator("someName", new Mock<ControllerDescriptor>().Object);
+
+ // Assert
+ var castActionDescriptor = Assert.IsType<ReflectedActionDescriptor>(actionDescriptor);
+ Assert.Equal("MethodHasSelectionAttribute1", castActionDescriptor.MethodInfo.Name);
+ }
+
+ [Fact]
+ public void FindAction_ReturnsNullIfNoMethodMatches()
+ {
+ // Arrange
+ Type controllerType = typeof(SelectionAttributeController);
+ AsyncActionMethodSelector selector = new AsyncActionMethodSelector(controllerType);
+
+ // Act
+ ActionDescriptorCreator creator = selector.FindAction(null, "ZeroMatch");
+
+ // Assert
+ Assert.Null(creator);
+ }
+
+ [Fact]
+ public void FindAction_ThrowsIfMultipleMethodsMatch()
+ {
+ // Arrange
+ Type controllerType = typeof(SelectionAttributeController);
+ AsyncActionMethodSelector selector = new AsyncActionMethodSelector(controllerType);
+
+ // Act & veriy
+ Assert.Throws<AmbiguousMatchException>(
+ delegate { selector.FindAction(null, "TwoMatch"); },
+ @"The current request for action 'TwoMatch' on controller type 'SelectionAttributeController' is ambiguous between the following action methods:
+Void TwoMatch2() on type System.Web.Mvc.Async.Test.AsyncActionMethodSelectorTest+SelectionAttributeController
+Void TwoMatch() on type System.Web.Mvc.Async.Test.AsyncActionMethodSelectorTest+SelectionAttributeController");
+ }
+
+ [Fact]
+ public void FindActionMethod_Asynchronous()
+ {
+ // Arrange
+ Type controllerType = typeof(MethodLocatorController);
+ AsyncActionMethodSelector selector = new AsyncActionMethodSelector(controllerType);
+
+ // Act
+ ActionDescriptorCreator creator = selector.FindAction(null, "EventPattern");
+ ActionDescriptor actionDescriptor = creator("someName", new Mock<ControllerDescriptor>().Object);
+
+ // Assert
+ var castActionDescriptor = Assert.IsType<ReflectedAsyncActionDescriptor>(actionDescriptor);
+ Assert.Equal("EventPatternAsync", castActionDescriptor.AsyncMethodInfo.Name);
+ Assert.Equal("EventPatternCompleted", castActionDescriptor.CompletedMethodInfo.Name);
+ }
+
+ [Fact]
+ public void FindActionMethod_Task()
+ {
+ // Arrange
+ Type controllerType = typeof(MethodLocatorController);
+ AsyncActionMethodSelector selector = new AsyncActionMethodSelector(controllerType);
+
+ // Act
+ ActionDescriptorCreator creator = selector.FindAction(null, "TaskPattern");
+ ActionDescriptor actionDescriptor = creator("someName", new Mock<ControllerDescriptor>().Object);
+
+ // Assert
+ var castActionDescriptor = Assert.IsType<TaskAsyncActionDescriptor>(actionDescriptor);
+ Assert.Equal("TaskPattern", castActionDescriptor.TaskMethodInfo.Name);
+ Assert.Equal(typeof(Task), castActionDescriptor.TaskMethodInfo.ReturnType);
+ }
+
+ [Fact]
+ public void FindActionMethod_GenericTask()
+ {
+ // Arrange
+ Type controllerType = typeof(MethodLocatorController);
+ AsyncActionMethodSelector selector = new AsyncActionMethodSelector(controllerType);
+
+ // Act
+ ActionDescriptorCreator creator = selector.FindAction(null, "GenericTaskPattern");
+ ActionDescriptor actionDescriptor = creator("someName", new Mock<ControllerDescriptor>().Object);
+
+ // Assert
+ var castActionDescriptor = Assert.IsType<TaskAsyncActionDescriptor>(actionDescriptor);
+ Assert.Equal("GenericTaskPattern", castActionDescriptor.TaskMethodInfo.Name);
+ Assert.Equal(typeof(Task<string>), castActionDescriptor.TaskMethodInfo.ReturnType);
+ }
+
+ [Fact]
+ public void FindActionMethod_Asynchronous_ThrowsIfCompletionMethodNotFound()
+ {
+ // Arrange
+ Type controllerType = typeof(MethodLocatorController);
+ AsyncActionMethodSelector selector = new AsyncActionMethodSelector(controllerType);
+
+ // Act & assert
+ Assert.Throws<InvalidOperationException>(
+ delegate { ActionDescriptorCreator creator = selector.FindAction(null, "EventPatternWithoutCompletionMethod"); },
+ @"Could not locate a method named 'EventPatternWithoutCompletionMethodCompleted' on controller type System.Web.Mvc.Async.Test.AsyncActionMethodSelectorTest+MethodLocatorController.");
+ }
+
+ [Fact]
+ public void FindActionMethod_Asynchronous_ThrowsIfMultipleCompletedMethodsMatched()
+ {
+ // Arrange
+ Type controllerType = typeof(MethodLocatorController);
+ AsyncActionMethodSelector selector = new AsyncActionMethodSelector(controllerType);
+
+ // Act & assert
+ Assert.Throws<AmbiguousMatchException>(
+ delegate { ActionDescriptorCreator creator = selector.FindAction(null, "EventPatternAmbiguous"); },
+ @"Lookup for method 'EventPatternAmbiguousCompleted' on controller type 'MethodLocatorController' failed because of an ambiguity between the following methods:
+Void EventPatternAmbiguousCompleted(Int32) on type System.Web.Mvc.Async.Test.AsyncActionMethodSelectorTest+MethodLocatorController
+Void EventPatternAmbiguousCompleted(System.String) on type System.Web.Mvc.Async.Test.AsyncActionMethodSelectorTest+MethodLocatorController");
+ }
+
+ [Fact]
+ public void NonAliasedMethodsProperty()
+ {
+ // Arrange
+ Type controllerType = typeof(MethodLocatorController);
+
+ // Act
+ AsyncActionMethodSelector selector = new AsyncActionMethodSelector(controllerType);
+
+ // Assert
+ Assert.Equal(6, selector.NonAliasedMethods.Count);
+
+ List<MethodInfo> sortedMethods = selector.NonAliasedMethods["foo"].OrderBy(methodInfo => methodInfo.GetParameters().Length).ToList();
+ Assert.Equal("Foo", sortedMethods[0].Name);
+ Assert.Empty(sortedMethods[0].GetParameters());
+ Assert.Equal("Foo", sortedMethods[1].Name);
+ Assert.Equal(typeof(string), sortedMethods[1].GetParameters()[0].ParameterType);
+
+ Assert.Equal(1, selector.NonAliasedMethods["EventPattern"].Count());
+ Assert.Equal("EventPatternAsync", selector.NonAliasedMethods["EventPattern"].First().Name);
+ Assert.Equal(1, selector.NonAliasedMethods["EventPatternAmbiguous"].Count());
+ Assert.Equal("EventPatternAmbiguousAsync", selector.NonAliasedMethods["EventPatternAmbiguous"].First().Name);
+ Assert.Equal(1, selector.NonAliasedMethods["EventPatternWithoutCompletionMethod"].Count());
+ Assert.Equal("EventPatternWithoutCompletionMethodAsync", selector.NonAliasedMethods["EventPatternWithoutCompletionMethod"].First().Name);
+
+ Assert.Equal(1, selector.NonAliasedMethods["TaskPattern"].Count());
+ Assert.Equal("TaskPattern", selector.NonAliasedMethods["TaskPattern"].First().Name);
+ Assert.Equal(1, selector.NonAliasedMethods["GenericTaskPattern"].Count());
+ Assert.Equal("GenericTaskPattern", selector.NonAliasedMethods["GenericTaskPattern"].First().Name);
+ }
+
+ private class MethodLocatorController : Controller
+ {
+ public void Foo()
+ {
+ }
+
+ public void Foo(string s)
+ {
+ }
+
+ [ActionName("Foo")]
+ public void FooRenamed()
+ {
+ }
+
+ [ActionName("Bar")]
+ public void Bar()
+ {
+ }
+
+ [ActionName("PrivateVoid")]
+ private void PrivateVoid()
+ {
+ }
+
+ protected void ProtectedVoidAction()
+ {
+ }
+
+ public static void StaticMethod()
+ {
+ }
+
+ public void EventPatternAsync()
+ {
+ }
+
+ public void EventPatternCompleted()
+ {
+ }
+
+ public void EventPatternWithoutCompletionMethodAsync()
+ {
+ }
+
+ public void EventPatternAmbiguousAsync()
+ {
+ }
+
+ public void EventPatternAmbiguousCompleted(int i)
+ {
+ }
+
+ public void EventPatternAmbiguousCompleted(string s)
+ {
+ }
+
+ public Task TaskPattern()
+ {
+ return Task.Factory.StartNew(() => "foo");
+ }
+
+ public Task<string> GenericTaskPattern()
+ {
+ return Task.Factory.StartNew(() => "foo");
+ }
+
+ [ActionName("RenamedCompleted")]
+ public void Renamed()
+ {
+ }
+
+ // ensure that methods inheriting from Controller or a base class are not matched
+ [ActionName("Blah")]
+ protected override void ExecuteCore()
+ {
+ throw new NotImplementedException();
+ }
+
+ public string StringProperty { get; set; }
+
+#pragma warning disable 0067
+ public event EventHandler<EventArgs> SomeEvent;
+#pragma warning restore 0067
+ }
+
+ private class SelectionAttributeController : Controller
+ {
+ [Match(false)]
+ public void OneMatch()
+ {
+ }
+
+ public void OneMatch(string s)
+ {
+ }
+
+ public void TwoMatch()
+ {
+ }
+
+ [ActionName("TwoMatch")]
+ public void TwoMatch2()
+ {
+ }
+
+ [Match(true), ActionName("ShouldMatchMethodWithSelectionAttribute")]
+ public void MethodHasSelectionAttribute1()
+ {
+ }
+
+ [ActionName("ShouldMatchMethodWithSelectionAttribute")]
+ public void MethodDoesNotHaveSelectionAttribute1()
+ {
+ }
+
+ private class MatchAttribute : ActionMethodSelectorAttribute
+ {
+ private bool _match;
+
+ public MatchAttribute(bool match)
+ {
+ _match = match;
+ }
+
+ public override bool IsValidForRequest(ControllerContext controllerContext, MethodInfo methodInfo)
+ {
+ return _match;
+ }
+ }
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Async/Test/AsyncControllerActionInvokerTest.cs b/test/System.Web.Mvc.Test/Async/Test/AsyncControllerActionInvokerTest.cs
new file mode 100644
index 00000000..7905eff0
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Async/Test/AsyncControllerActionInvokerTest.cs
@@ -0,0 +1,916 @@
+using System.Collections.Generic;
+using System.Threading;
+using Moq;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Mvc.Async.Test
+{
+ public class AsyncControllerActionInvokerTest
+ {
+ [Fact]
+ public void InvokeAction_ActionNotFound()
+ {
+ // Arrange
+ ControllerContext controllerContext = GetControllerContext();
+ AsyncControllerActionInvoker invoker = new AsyncControllerActionInvoker();
+
+ // Act
+ IAsyncResult asyncResult = invoker.BeginInvokeAction(controllerContext, "ActionNotFound", null, null);
+ bool retVal = invoker.EndInvokeAction(asyncResult);
+
+ // Assert
+ Assert.False(retVal);
+ }
+
+ [Fact]
+ public void InvokeAction_ActionThrowsException_Handled()
+ {
+ // Arrange
+ ControllerContext controllerContext = GetControllerContext();
+ AsyncControllerActionInvoker invoker = new AsyncControllerActionInvoker();
+
+ // Act & assert
+ IAsyncResult asyncResult = invoker.BeginInvokeAction(controllerContext, "ActionThrowsExceptionAndIsHandled", null, null);
+ Assert.Null(((TestController)controllerContext.Controller).Log); // Result filter shouldn't have executed yet
+
+ bool retVal = invoker.EndInvokeAction(asyncResult);
+ Assert.True(retVal);
+ Assert.Equal("From exception filter", ((TestController)controllerContext.Controller).Log);
+ }
+
+ [Fact]
+ public void InvokeAction_ActionThrowsException_NotHandled()
+ {
+ // Arrange
+ ControllerContext controllerContext = GetControllerContext();
+ AsyncControllerActionInvoker invoker = new AsyncControllerActionInvoker();
+
+ // Act & assert
+ Assert.Throws<Exception>(
+ delegate { invoker.BeginInvokeAction(controllerContext, "ActionThrowsExceptionAndIsNotHandled", null, null); },
+ @"Some exception text.");
+ }
+
+ [Fact]
+ public void InvokeAction_ActionThrowsException_ThreadAbort()
+ {
+ // Arrange
+ ControllerContext controllerContext = GetControllerContext();
+ AsyncControllerActionInvoker invoker = new AsyncControllerActionInvoker();
+
+ // Act & assert
+ Assert.Throws<ThreadAbortException>(
+ delegate { invoker.BeginInvokeAction(controllerContext, "ActionCallsThreadAbort", null, null); });
+ }
+
+ [Fact]
+ public void InvokeAction_AuthorizationFilterShortCircuits()
+ {
+ // Arrange
+ ControllerContext controllerContext = GetControllerContext();
+ AsyncControllerActionInvoker invoker = new AsyncControllerActionInvoker();
+
+ // Act
+ IAsyncResult asyncResult = invoker.BeginInvokeAction(controllerContext, "AuthorizationFilterShortCircuits", null, null);
+ bool retVal = invoker.EndInvokeAction(asyncResult);
+
+ // Assert
+ Assert.True(retVal);
+ Assert.Equal("From authorization filter", ((TestController)controllerContext.Controller).Log);
+ }
+
+ [Fact]
+ public void InvokeAction_NormalAction()
+ {
+ // Arrange
+ ControllerContext controllerContext = GetControllerContext();
+ AsyncControllerActionInvoker invoker = new AsyncControllerActionInvoker();
+
+ // Act
+ IAsyncResult asyncResult = invoker.BeginInvokeAction(controllerContext, "NormalAction", null, null);
+ bool retVal = invoker.EndInvokeAction(asyncResult);
+
+ // Assert
+ Assert.True(retVal);
+ Assert.Equal("From action", ((TestController)controllerContext.Controller).Log);
+ }
+
+ [Fact]
+ public void InvokeAction_OverrideFindAction()
+ {
+ // Arrange
+ ControllerContext controllerContext = GetControllerContext();
+ AsyncControllerActionInvoker invoker = new AsyncControllerActionInvokerWithCustomFindAction();
+
+ // Act
+ IAsyncResult asyncResult = invoker.BeginInvokeAction(controllerContext, actionName: "Non-ExistantAction", callback: null, state: null);
+ bool retVal = invoker.EndInvokeAction(asyncResult);
+
+ // Assert
+ Assert.True(retVal);
+ Assert.Equal("From action", ((TestController)controllerContext.Controller).Log);
+ }
+
+ [Fact]
+ public void InvokeAction_RequestValidationFails()
+ {
+ // Arrange
+ ControllerContext controllerContext = GetControllerContext(passesRequestValidation: false);
+ AsyncControllerActionInvoker invoker = new AsyncControllerActionInvoker();
+
+ // Act & assert
+ Assert.Throws<HttpRequestValidationException>(
+ delegate { invoker.BeginInvokeAction(controllerContext, "NormalAction", null, null); });
+ }
+
+ [Fact]
+ public void InvokeAction_ResultThrowsException_Handled()
+ {
+ // Arrange
+ ControllerContext controllerContext = GetControllerContext();
+ AsyncControllerActionInvoker invoker = new AsyncControllerActionInvoker();
+
+ // Act & assert
+ IAsyncResult asyncResult = invoker.BeginInvokeAction(controllerContext, "ResultThrowsExceptionAndIsHandled", null, null);
+ bool retVal = invoker.EndInvokeAction(asyncResult);
+
+ Assert.True(retVal);
+ Assert.Equal("From exception filter", ((TestController)controllerContext.Controller).Log);
+ }
+
+ [Fact]
+ public void InvokeAction_ResultThrowsException_NotHandled()
+ {
+ // Arrange
+ ControllerContext controllerContext = GetControllerContext();
+ AsyncControllerActionInvoker invoker = new AsyncControllerActionInvoker();
+
+ // Act & assert
+ IAsyncResult asyncResult = invoker.BeginInvokeAction(controllerContext, "ResultThrowsExceptionAndIsNotHandled", null, null);
+ Assert.Throws<Exception>(
+ delegate { invoker.EndInvokeAction(asyncResult); },
+ @"Some exception text.");
+ }
+
+ [Fact]
+ public void InvokeAction_ResultThrowsException_ThreadAbort()
+ {
+ // Arrange
+ ControllerContext controllerContext = GetControllerContext();
+ AsyncControllerActionInvoker invoker = new AsyncControllerActionInvoker();
+
+ // Act & assert
+ IAsyncResult asyncResult = invoker.BeginInvokeAction(controllerContext, "ResultCallsThreadAbort", null, null);
+ Assert.Throws<ThreadAbortException>(
+ delegate { invoker.EndInvokeAction(asyncResult); });
+ }
+
+ [Fact]
+ public void InvokeAction_ThrowsIfActionNameIsEmpty()
+ {
+ // Arrange
+ AsyncControllerActionInvoker invoker = new AsyncControllerActionInvoker();
+
+ // Act & assert
+ Assert.ThrowsArgumentNullOrEmpty(
+ delegate { invoker.BeginInvokeAction(new ControllerContext(), "", null, null); }, "actionName");
+ }
+
+ [Fact]
+ public void InvokeAction_ThrowsIfActionNameIsNull()
+ {
+ // Arrange
+ AsyncControllerActionInvoker invoker = new AsyncControllerActionInvoker();
+
+ // Act & assert
+ Assert.ThrowsArgumentNullOrEmpty(
+ delegate { invoker.BeginInvokeAction(new ControllerContext(), null, null, null); }, "actionName");
+ }
+
+ [Fact]
+ public void InvokeAction_ThrowsIfControllerContextIsNull()
+ {
+ // Arrange
+ AsyncControllerActionInvoker invoker = new AsyncControllerActionInvoker();
+
+ // Act & assert
+ Assert.ThrowsArgumentNull(
+ delegate { invoker.BeginInvokeAction(null, "someAction", null, null); }, "controllerContext");
+ }
+
+ [Fact]
+ public void InvokeActionMethod_AsynchronousDescriptor()
+ {
+ // Arrange
+ ControllerContext controllerContext = new ControllerContext();
+ Dictionary<string, object> parameters = new Dictionary<string, object>();
+ IAsyncResult innerAsyncResult = new MockAsyncResult();
+ ActionResult expectedResult = new ViewResult();
+
+ Mock<AsyncActionDescriptor> mockActionDescriptor = new Mock<AsyncActionDescriptor>();
+ mockActionDescriptor.Setup(d => d.BeginExecute(controllerContext, parameters, It.IsAny<AsyncCallback>(), It.IsAny<object>())).Returns(innerAsyncResult);
+ mockActionDescriptor.Setup(d => d.EndExecute(innerAsyncResult)).Returns(expectedResult);
+
+ AsyncControllerActionInvoker invoker = new AsyncControllerActionInvoker();
+
+ // Act
+ IAsyncResult asyncResult = invoker.BeginInvokeActionMethod(controllerContext, mockActionDescriptor.Object, parameters, null, null);
+ ActionResult returnedResult = invoker.EndInvokeActionMethod(asyncResult);
+
+ // Assert
+ Assert.Equal(expectedResult, returnedResult);
+ }
+
+ [Fact]
+ public void InvokeActionMethod_SynchronousDescriptor()
+ {
+ // Arrange
+ ControllerContext controllerContext = new ControllerContext();
+ Dictionary<string, object> parameters = new Dictionary<string, object>();
+ ActionResult expectedResult = new ViewResult();
+
+ Mock<ActionDescriptor> mockActionDescriptor = new Mock<ActionDescriptor>();
+ mockActionDescriptor.Setup(d => d.Execute(controllerContext, parameters)).Returns(expectedResult);
+
+ AsyncControllerActionInvoker invoker = new AsyncControllerActionInvoker();
+
+ // Act
+ IAsyncResult asyncResult = invoker.BeginInvokeActionMethod(controllerContext, mockActionDescriptor.Object, parameters, null, null);
+ ActionResult returnedResult = invoker.EndInvokeActionMethod(asyncResult);
+
+ // Assert
+ Assert.Equal(expectedResult, returnedResult);
+ }
+
+ [Fact]
+ public void InvokeActionMethodFilterAsynchronously_NextInChainThrowsOnActionExecutedException_Handled()
+ {
+ // Arrange
+ ViewResult expectedResult = new ViewResult();
+
+ bool nextInChainWasCalled = false;
+ bool onActionExecutedWasCalled = false;
+
+ ActionExecutingContext preContext = GetActionExecutingContext();
+ ActionFilterImpl actionFilter = new ActionFilterImpl()
+ {
+ OnActionExecutedImpl = filterContext =>
+ {
+ onActionExecutedWasCalled = true;
+ Assert.NotNull(filterContext.Exception);
+ filterContext.ExceptionHandled = true;
+ filterContext.Result = expectedResult;
+ }
+ };
+
+ // Act & assert pre-execution
+ Func<ActionExecutedContext> continuation = AsyncControllerActionInvoker.InvokeActionMethodFilterAsynchronously(
+ actionFilter, preContext,
+ () => () =>
+ {
+ nextInChainWasCalled = true;
+ throw new Exception("Some exception text.");
+ });
+
+ Assert.False(onActionExecutedWasCalled);
+
+ // Act & assert post-execution
+ ActionExecutedContext postContext = continuation();
+
+ Assert.True(nextInChainWasCalled);
+ Assert.True(onActionExecutedWasCalled);
+ Assert.Equal(expectedResult, postContext.Result);
+ }
+
+ [Fact]
+ public void InvokeActionMethodFilterAsynchronously_NextInChainThrowsOnActionExecutedException_NotHandled()
+ {
+ // Arrange
+ ViewResult expectedResult = new ViewResult();
+
+ bool onActionExecutedWasCalled = false;
+
+ ActionExecutingContext preContext = GetActionExecutingContext();
+ ActionFilterImpl actionFilter = new ActionFilterImpl()
+ {
+ OnActionExecutedImpl = filterContext => { onActionExecutedWasCalled = true; }
+ };
+
+ // Act & assert
+ Func<ActionExecutedContext> continuation = AsyncControllerActionInvoker.InvokeActionMethodFilterAsynchronously(actionFilter, preContext,
+ () => () => { throw new Exception("Some exception text."); });
+
+ Assert.Throws<Exception>(
+ delegate { continuation(); },
+ @"Some exception text.");
+
+ // Assert
+ Assert.True(onActionExecutedWasCalled);
+ }
+
+ [Fact]
+ public void InvokeActionMethodFilterAsynchronously_NextInChainThrowsOnActionExecutedException_ThreadAbort()
+ {
+ // Arrange
+ ViewResult expectedResult = new ViewResult();
+
+ bool onActionExecutedWasCalled = false;
+
+ ActionExecutingContext preContext = GetActionExecutingContext();
+ ActionFilterImpl actionFilter = new ActionFilterImpl()
+ {
+ OnActionExecutedImpl = filterContext =>
+ {
+ onActionExecutedWasCalled = true;
+ Thread.ResetAbort();
+ }
+ };
+
+ // Act & assert
+ Func<ActionExecutedContext> continuation = AsyncControllerActionInvoker.InvokeActionMethodFilterAsynchronously(
+ actionFilter, preContext,
+ () => () =>
+ {
+ Thread.CurrentThread.Abort();
+ return null;
+ });
+
+ Assert.Throws<ThreadAbortException>(
+ delegate { continuation(); });
+
+ // Assert
+ Assert.True(onActionExecutedWasCalled);
+ }
+
+ [Fact]
+ public void InvokeActionMethodFilterAsynchronously_NextInChainThrowsOnActionExecutingException_Handled()
+ {
+ // Arrange
+ ViewResult expectedResult = new ViewResult();
+
+ bool nextInChainWasCalled = false;
+ bool onActionExecutingWasCalled = false;
+ bool onActionExecutedWasCalled = false;
+
+ ActionExecutingContext preContext = GetActionExecutingContext();
+ ActionFilterImpl actionFilter = new ActionFilterImpl()
+ {
+ OnActionExecutingImpl = filterContext => { onActionExecutingWasCalled = true; },
+ OnActionExecutedImpl = filterContext =>
+ {
+ onActionExecutedWasCalled = true;
+ Assert.NotNull(filterContext.Exception);
+ filterContext.ExceptionHandled = true;
+ filterContext.Result = expectedResult;
+ }
+ };
+
+ // Act
+ Func<ActionExecutedContext> continuation = AsyncControllerActionInvoker.InvokeActionMethodFilterAsynchronously(
+ actionFilter, preContext,
+ () =>
+ {
+ nextInChainWasCalled = true;
+ throw new Exception("Some exception text.");
+ });
+
+ // Assert
+ Assert.True(nextInChainWasCalled);
+ Assert.True(onActionExecutingWasCalled);
+ Assert.True(onActionExecutedWasCalled);
+
+ ActionExecutedContext postContext = continuation();
+ Assert.Equal(expectedResult, postContext.Result);
+ }
+
+ [Fact]
+ public void InvokeActionMethodFilterAsynchronously_NextInChainThrowsOnActionExecutingException_NotHandled()
+ {
+ // Arrange
+ ViewResult expectedResult = new ViewResult();
+
+ bool nextInChainWasCalled = false;
+ bool onActionExecutingWasCalled = false;
+ bool onActionExecutedWasCalled = false;
+
+ ActionExecutingContext preContext = GetActionExecutingContext();
+ ActionFilterImpl actionFilter = new ActionFilterImpl()
+ {
+ OnActionExecutingImpl = filterContext => { onActionExecutingWasCalled = true; },
+ OnActionExecutedImpl = filterContext => { onActionExecutedWasCalled = true; }
+ };
+
+ // Act & assert
+ Assert.Throws<Exception>(
+ delegate
+ {
+ AsyncControllerActionInvoker.InvokeActionMethodFilterAsynchronously(
+ actionFilter, preContext,
+ () =>
+ {
+ nextInChainWasCalled = true;
+ throw new Exception("Some exception text.");
+ });
+ },
+ @"Some exception text.");
+
+ // Assert
+ Assert.True(nextInChainWasCalled);
+ Assert.True(onActionExecutingWasCalled);
+ Assert.True(onActionExecutedWasCalled);
+ }
+
+ [Fact]
+ public void InvokeActionMethodFilterAsynchronously_NextInChainThrowsOnActionExecutingException_ThreadAbort()
+ {
+ // Arrange
+ ViewResult expectedResult = new ViewResult();
+
+ bool nextInChainWasCalled = false;
+ bool onActionExecutingWasCalled = false;
+ bool onActionExecutedWasCalled = false;
+
+ ActionExecutingContext preContext = GetActionExecutingContext();
+ ActionFilterImpl actionFilter = new ActionFilterImpl()
+ {
+ OnActionExecutingImpl = filterContext => { onActionExecutingWasCalled = true; },
+ OnActionExecutedImpl = filterContext =>
+ {
+ onActionExecutedWasCalled = true;
+ Thread.ResetAbort();
+ }
+ };
+
+ // Act & assert
+ Assert.Throws<ThreadAbortException>(
+ delegate
+ {
+ AsyncControllerActionInvoker.InvokeActionMethodFilterAsynchronously(
+ actionFilter, preContext,
+ () =>
+ {
+ nextInChainWasCalled = true;
+ Thread.CurrentThread.Abort();
+ return null;
+ });
+ });
+
+ // Assert
+ Assert.True(nextInChainWasCalled);
+ Assert.True(onActionExecutingWasCalled);
+ Assert.True(onActionExecutedWasCalled);
+ }
+
+ [Fact]
+ public void InvokeActionMethodFilterAsynchronously_NormalExecutionNotCanceled()
+ {
+ // Arrange
+ bool nextInChainWasCalled = false;
+ bool onActionExecutingWasCalled = false;
+ bool onActionExecutedWasCalled = false;
+
+ ActionExecutingContext preContext = GetActionExecutingContext();
+ ActionFilterImpl actionFilter = new ActionFilterImpl()
+ {
+ OnActionExecutingImpl = _ => { onActionExecutingWasCalled = true; },
+ OnActionExecutedImpl = _ => { onActionExecutedWasCalled = true; }
+ };
+
+ // Act
+ Func<ActionExecutedContext> continuation = AsyncControllerActionInvoker.InvokeActionMethodFilterAsynchronously(
+ actionFilter, preContext,
+ () =>
+ {
+ nextInChainWasCalled = true;
+ return () => new ActionExecutedContext();
+ });
+
+ // Assert
+ Assert.True(nextInChainWasCalled);
+ Assert.True(onActionExecutingWasCalled);
+ Assert.False(onActionExecutedWasCalled);
+
+ continuation();
+ Assert.True(onActionExecutedWasCalled);
+ }
+
+ [Fact]
+ public void InvokeActionMethodFilterAsynchronously_OnActionExecutingSetsResult()
+ {
+ // Arrange
+ ViewResult expectedResult = new ViewResult();
+
+ bool nextInChainWasCalled = false;
+ bool onActionExecutingWasCalled = false;
+ bool onActionExecutedWasCalled = false;
+
+ ActionExecutingContext preContext = GetActionExecutingContext();
+ ActionFilterImpl actionFilter = new ActionFilterImpl()
+ {
+ OnActionExecutingImpl = filterContext =>
+ {
+ onActionExecutingWasCalled = true;
+ filterContext.Result = expectedResult;
+ },
+ OnActionExecutedImpl = _ => { onActionExecutedWasCalled = true; }
+ };
+
+ // Act
+ Func<ActionExecutedContext> continuation = AsyncControllerActionInvoker.InvokeActionMethodFilterAsynchronously(
+ actionFilter, preContext,
+ () =>
+ {
+ nextInChainWasCalled = true;
+ return () => new ActionExecutedContext();
+ });
+
+ // Assert
+ Assert.False(nextInChainWasCalled);
+ Assert.True(onActionExecutingWasCalled);
+ Assert.False(onActionExecutedWasCalled);
+
+ ActionExecutedContext postContext = continuation();
+ Assert.False(onActionExecutedWasCalled);
+ Assert.Equal(expectedResult, postContext.Result);
+ }
+
+ [Fact]
+ public void InvokeActionMethodWithFilters()
+ {
+ // Arrange
+ List<string> actionLog = new List<string>();
+ ControllerContext controllerContext = new ControllerContext();
+ Dictionary<string, object> parameters = new Dictionary<string, object>();
+ MockAsyncResult innerAsyncResult = new MockAsyncResult();
+ ActionResult actionResult = new ViewResult();
+
+ ActionFilterImpl filter1 = new ActionFilterImpl()
+ {
+ OnActionExecutingImpl = delegate(ActionExecutingContext filterContext) { actionLog.Add("OnActionExecuting1"); },
+ OnActionExecutedImpl = delegate(ActionExecutedContext filterContext) { actionLog.Add("OnActionExecuted1"); }
+ };
+ ActionFilterImpl filter2 = new ActionFilterImpl()
+ {
+ OnActionExecutingImpl = delegate(ActionExecutingContext filterContext) { actionLog.Add("OnActionExecuting2"); },
+ OnActionExecutedImpl = delegate(ActionExecutedContext filterContext) { actionLog.Add("OnActionExecuted2"); }
+ };
+
+ Mock<AsyncActionDescriptor> mockActionDescriptor = new Mock<AsyncActionDescriptor>();
+ mockActionDescriptor.Setup(d => d.BeginExecute(controllerContext, parameters, It.IsAny<AsyncCallback>(), It.IsAny<object>())).Returns(innerAsyncResult);
+ mockActionDescriptor.Setup(d => d.EndExecute(innerAsyncResult)).Returns(actionResult);
+
+ AsyncControllerActionInvoker invoker = new AsyncControllerActionInvoker();
+ IActionFilter[] filters = new IActionFilter[] { filter1, filter2 };
+
+ // Act
+ IAsyncResult outerAsyncResult = invoker.BeginInvokeActionMethodWithFilters(controllerContext, filters, mockActionDescriptor.Object, parameters, null, null);
+ ActionExecutedContext postContext = invoker.EndInvokeActionMethodWithFilters(outerAsyncResult);
+
+ // Assert
+ Assert.Equal(new[] { "OnActionExecuting1", "OnActionExecuting2", "OnActionExecuted2", "OnActionExecuted1" }, actionLog.ToArray());
+ Assert.Equal(actionResult, postContext.Result);
+ }
+
+ [Fact]
+ public void InvokeActionMethodWithFilters_ShortCircuited()
+ {
+ // Arrange
+ List<string> actionLog = new List<string>();
+ ControllerContext controllerContext = new ControllerContext();
+ Dictionary<string, object> parameters = new Dictionary<string, object>();
+ ActionResult actionResult = new ViewResult();
+
+ ActionFilterImpl filter1 = new ActionFilterImpl()
+ {
+ OnActionExecutingImpl = delegate(ActionExecutingContext filterContext) { actionLog.Add("OnActionExecuting1"); },
+ OnActionExecutedImpl = delegate(ActionExecutedContext filterContext) { actionLog.Add("OnActionExecuted1"); }
+ };
+ ActionFilterImpl filter2 = new ActionFilterImpl()
+ {
+ OnActionExecutingImpl = delegate(ActionExecutingContext filterContext)
+ {
+ actionLog.Add("OnActionExecuting2");
+ filterContext.Result = actionResult;
+ },
+ OnActionExecutedImpl = delegate(ActionExecutedContext filterContext) { actionLog.Add("OnActionExecuted2"); }
+ };
+
+ Mock<AsyncActionDescriptor> mockActionDescriptor = new Mock<AsyncActionDescriptor>();
+ mockActionDescriptor.Setup(d => d.BeginExecute(controllerContext, parameters, It.IsAny<AsyncCallback>(), It.IsAny<object>())).Throws(new Exception("I shouldn't have been called."));
+ mockActionDescriptor.Setup(d => d.EndExecute(It.IsAny<IAsyncResult>())).Throws(new Exception("I shouldn't have been called."));
+
+ AsyncControllerActionInvoker invoker = new AsyncControllerActionInvoker();
+ IActionFilter[] filters = new IActionFilter[] { filter1, filter2 };
+
+ // Act
+ IAsyncResult outerAsyncResult = invoker.BeginInvokeActionMethodWithFilters(controllerContext, filters, mockActionDescriptor.Object, parameters, null, null);
+ ActionExecutedContext postContext = invoker.EndInvokeActionMethodWithFilters(outerAsyncResult);
+
+ // Assert
+ Assert.Equal(new[] { "OnActionExecuting1", "OnActionExecuting2", "OnActionExecuted1" }, actionLog.ToArray());
+ Assert.Equal(actionResult, postContext.Result);
+ }
+
+ private static ActionExecutingContext GetActionExecutingContext()
+ {
+ return new ActionExecutingContext(new ControllerContext(), new Mock<ActionDescriptor>().Object, new Dictionary<string, object>());
+ }
+
+ private static ControllerContext GetControllerContext(bool passesRequestValidation = true)
+ {
+ Mock<HttpContextBase> mockHttpContext = new Mock<HttpContextBase>();
+ if (passesRequestValidation)
+ {
+#pragma warning disable 618
+ mockHttpContext.Setup(o => o.Request.ValidateInput()).AtMostOnce();
+#pragma warning restore 618
+ }
+ else
+ {
+ mockHttpContext.Setup(o => o.Request.ValidateInput()).Throws(new HttpRequestValidationException());
+ }
+
+ return new ControllerContext()
+ {
+ Controller = new TestController(),
+ HttpContext = mockHttpContext.Object
+ };
+ }
+
+ private class ActionFilterImpl : IActionFilter, IResultFilter
+ {
+ public Action<ActionExecutingContext> OnActionExecutingImpl { get; set; }
+
+ public void OnActionExecuting(ActionExecutingContext filterContext)
+ {
+ if (OnActionExecutingImpl != null)
+ {
+ OnActionExecutingImpl(filterContext);
+ }
+ }
+
+ public Action<ActionExecutedContext> OnActionExecutedImpl { get; set; }
+
+ public void OnActionExecuted(ActionExecutedContext filterContext)
+ {
+ if (OnActionExecutedImpl != null)
+ {
+ OnActionExecutedImpl(filterContext);
+ }
+ }
+
+ public Action<ResultExecutingContext> OnResultExecutingImpl { get; set; }
+
+ public void OnResultExecuting(ResultExecutingContext filterContext)
+ {
+ if (OnResultExecutingImpl != null)
+ {
+ OnResultExecutingImpl(filterContext);
+ }
+ }
+
+ public Action<ResultExecutedContext> OnResultExecutedImpl { get; set; }
+
+ public void OnResultExecuted(ResultExecutedContext filterContext)
+ {
+ if (OnResultExecutedImpl != null)
+ {
+ OnResultExecutedImpl(filterContext);
+ }
+ }
+ }
+
+ public class AsyncControllerActionInvokerHelper : AsyncControllerActionInvoker
+ {
+ public AsyncControllerActionInvokerHelper()
+ {
+ DescriptorCache = new ControllerDescriptorCache();
+ }
+
+ protected override ControllerDescriptor GetControllerDescriptor(ControllerContext controllerContext)
+ {
+ return PublicGetControllerDescriptor(controllerContext);
+ }
+
+ public virtual ControllerDescriptor PublicGetControllerDescriptor(ControllerContext controllerContext)
+ {
+ return base.GetControllerDescriptor(controllerContext);
+ }
+
+ protected override ExceptionContext InvokeExceptionFilters(ControllerContext controllerContext, IList<IExceptionFilter> filters, Exception exception)
+ {
+ return PublicInvokeExceptionFilters(controllerContext, filters, exception);
+ }
+
+ public virtual ExceptionContext PublicInvokeExceptionFilters(ControllerContext controllerContext, IList<IExceptionFilter> filters, Exception exception)
+ {
+ return base.InvokeExceptionFilters(controllerContext, filters, exception);
+ }
+
+ protected override void InvokeActionResult(ControllerContext controllerContext, ActionResult actionResult)
+ {
+ PublicInvokeActionResult(controllerContext, actionResult);
+ }
+
+ public virtual void PublicInvokeActionResult(ControllerContext controllerContext, ActionResult actionResult)
+ {
+ base.InvokeActionResult(controllerContext, actionResult);
+ }
+
+ protected override FilterInfo GetFilters(ControllerContext controllerContext, ActionDescriptor actionDescriptor)
+ {
+ return PublicGetFilters(controllerContext, actionDescriptor);
+ }
+
+ public virtual FilterInfo PublicGetFilters(ControllerContext controllerContext, ActionDescriptor actionDescriptor)
+ {
+ return base.GetFilters(controllerContext, actionDescriptor);
+ }
+
+ protected override AuthorizationContext InvokeAuthorizationFilters(ControllerContext controllerContext, IList<IAuthorizationFilter> filters, ActionDescriptor actionDescriptor)
+ {
+ return PublicInvokeAuthorizationFilters(controllerContext, filters, actionDescriptor);
+ }
+
+ public virtual AuthorizationContext PublicInvokeAuthorizationFilters(ControllerContext controllerContext, IList<IAuthorizationFilter> filters, ActionDescriptor actionDescriptor)
+ {
+ return base.InvokeAuthorizationFilters(controllerContext, filters, actionDescriptor);
+ }
+
+ protected override ActionDescriptor FindAction(ControllerContext controllerContext, ControllerDescriptor controllerDescriptor, string actionName)
+ {
+ return PublicFindAction(controllerContext, controllerDescriptor, actionName);
+ }
+
+ public virtual ActionDescriptor PublicFindAction(ControllerContext controllerContext, ControllerDescriptor controllerDescriptor, string actionName)
+ {
+ return base.FindAction(controllerContext, controllerDescriptor, actionName);
+ }
+
+ protected override IDictionary<string, object> GetParameterValues(ControllerContext controllerContext, ActionDescriptor actionDescriptor)
+ {
+ return PublicGetParameterValues(controllerContext, actionDescriptor);
+ }
+
+ public virtual IDictionary<string, object> PublicGetParameterValues(ControllerContext controllerContext, ActionDescriptor actionDescriptor)
+ {
+ return base.GetParameterValues(controllerContext, actionDescriptor);
+ }
+ }
+
+ public class AsyncControllerActionInvokerWithCustomFindAction : AsyncControllerActionInvoker
+ {
+ protected override ActionDescriptor FindAction(ControllerContext controllerContext, ControllerDescriptor controllerDescriptor, string actionName)
+ {
+ return base.FindAction(controllerContext, controllerDescriptor, "NormalAction");
+ }
+ }
+
+ [ResetThreadAbort]
+ private class TestController : AsyncController
+ {
+ public string Log;
+
+ public ActionResult ActionCallsThreadAbortAsync()
+ {
+ Thread.CurrentThread.Abort();
+ return null;
+ }
+
+ public ActionResult ActionCallsThreadAbortCompleted()
+ {
+ return null;
+ }
+
+ public ActionResult ResultCallsThreadAbort()
+ {
+ return new ActionResultWhichCallsThreadAbort();
+ }
+
+ public ActionResult NormalAction()
+ {
+ return new LoggingActionResult("From action");
+ }
+
+ [AuthorizationFilterReturnsResult]
+ public void AuthorizationFilterShortCircuits()
+ {
+ }
+
+ [CustomExceptionFilterHandlesError]
+ public void ActionThrowsExceptionAndIsHandledAsync()
+ {
+ throw new Exception("Some exception text.");
+ }
+
+ public void ActionThrowsExceptionAndIsHandledCompleted()
+ {
+ }
+
+ [CustomExceptionFilterDoesNotHandleError]
+ public void ActionThrowsExceptionAndIsNotHandledAsync()
+ {
+ throw new Exception("Some exception text.");
+ }
+
+ public void ActionThrowsExceptionAndIsNotHandledCompleted()
+ {
+ }
+
+ [CustomExceptionFilterHandlesError]
+ public ActionResult ResultThrowsExceptionAndIsHandled()
+ {
+ return new ActionResultWhichThrowsException();
+ }
+
+ [CustomExceptionFilterDoesNotHandleError]
+ public ActionResult ResultThrowsExceptionAndIsNotHandled()
+ {
+ return new ActionResultWhichThrowsException();
+ }
+
+ private class AuthorizationFilterReturnsResultAttribute : FilterAttribute, IAuthorizationFilter
+ {
+ public void OnAuthorization(AuthorizationContext filterContext)
+ {
+ filterContext.Result = new LoggingActionResult("From authorization filter");
+ }
+ }
+
+ private class CustomExceptionFilterDoesNotHandleErrorAttribute : FilterAttribute, IExceptionFilter
+ {
+ public void OnException(ExceptionContext filterContext)
+ {
+ }
+ }
+
+ private class CustomExceptionFilterHandlesErrorAttribute : FilterAttribute, IExceptionFilter
+ {
+ public void OnException(ExceptionContext filterContext)
+ {
+ filterContext.ExceptionHandled = true;
+ filterContext.Result = new LoggingActionResult("From exception filter");
+ }
+ }
+
+ private class ActionResultWhichCallsThreadAbort : ActionResult
+ {
+ public override void ExecuteResult(ControllerContext context)
+ {
+ Thread.CurrentThread.Abort();
+ }
+ }
+
+ private class ActionResultWhichThrowsException : ActionResult
+ {
+ public override void ExecuteResult(ControllerContext context)
+ {
+ throw new Exception("Some exception text.");
+ }
+ }
+ }
+
+ private class ResetThreadAbortAttribute : ActionFilterAttribute
+ {
+ public override void OnActionExecuted(ActionExecutedContext filterContext)
+ {
+ try
+ {
+ Thread.ResetAbort();
+ }
+ catch (ThreadStateException)
+ {
+ // thread wasn't being aborted
+ }
+ }
+
+ public override void OnResultExecuted(ResultExecutedContext filterContext)
+ {
+ try
+ {
+ Thread.ResetAbort();
+ }
+ catch (ThreadStateException)
+ {
+ // thread wasn't being aborted
+ }
+ }
+ }
+
+ private class LoggingActionResult : ActionResult
+ {
+ private readonly string _logText;
+
+ public LoggingActionResult(string logText)
+ {
+ _logText = logText;
+ }
+
+ public override void ExecuteResult(ControllerContext context)
+ {
+ ((TestController)context.Controller).Log = _logText;
+ }
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Async/Test/AsyncManagerTest.cs b/test/System.Web.Mvc.Test/Async/Test/AsyncManagerTest.cs
new file mode 100644
index 00000000..f57651a0
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Async/Test/AsyncManagerTest.cs
@@ -0,0 +1,113 @@
+using System.Threading;
+using Moq;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Mvc.Async.Test
+{
+ public class AsyncManagerTest
+ {
+ [Fact]
+ public void FinishEvent_ExplicitCallToFinishMethod()
+ {
+ // Arrange
+ AsyncManager helper = new AsyncManager();
+
+ bool delegateCalled = false;
+ helper.Finished += delegate { delegateCalled = true; };
+
+ // Act
+ helper.Finish();
+
+ // Assert
+ Assert.True(delegateCalled);
+ }
+
+ [Fact]
+ public void FinishEvent_LinkedToOutstandingOperationsCompletedEvent()
+ {
+ // Arrange
+ AsyncManager helper = new AsyncManager();
+
+ bool delegateCalled = false;
+ helper.Finished += delegate { delegateCalled = true; };
+
+ // Act
+ helper.OutstandingOperations.Increment();
+ helper.OutstandingOperations.Decrement();
+
+ // Assert
+ Assert.True(delegateCalled);
+ }
+
+ [Fact]
+ public void OutstandingOperationsProperty()
+ {
+ // Act
+ AsyncManager helper = new AsyncManager();
+
+ // Assert
+ Assert.NotNull(helper.OutstandingOperations);
+ }
+
+ [Fact]
+ public void ParametersProperty()
+ {
+ // Act
+ AsyncManager helper = new AsyncManager();
+
+ // Assert
+ Assert.NotNull(helper.Parameters);
+ }
+
+ [Fact]
+ public void Sync()
+ {
+ // Arrange
+ Mock<SynchronizationContext> mockSyncContext = new Mock<SynchronizationContext>();
+ mockSyncContext
+ .Setup(c => c.Send(It.IsAny<SendOrPostCallback>(), null))
+ .Callback(
+ delegate(SendOrPostCallback d, object state) { d(state); });
+
+ AsyncManager helper = new AsyncManager(mockSyncContext.Object);
+ bool wasCalled = false;
+
+ // Act
+ helper.Sync(() => { wasCalled = true; });
+
+ // Assert
+ Assert.True(wasCalled);
+ }
+
+ [Fact]
+ public void TimeoutProperty()
+ {
+ // Arrange
+ int setValue = 50;
+ AsyncManager helper = new AsyncManager();
+
+ // Act
+ int defaultTimeout = helper.Timeout;
+ helper.Timeout = setValue;
+ int newTimeout = helper.Timeout;
+
+ // Assert
+ Assert.Equal(45000, defaultTimeout);
+ Assert.Equal(setValue, newTimeout);
+ }
+
+ [Fact]
+ public void TimeoutPropertyThrowsIfDurationIsOutOfRange()
+ {
+ // Arrange
+ int timeout = -30;
+ AsyncManager helper = new AsyncManager();
+
+ // Act & assert
+ Assert.ThrowsArgumentOutOfRange(
+ delegate { helper.Timeout = timeout; }, "value",
+ @"The timeout value must be non-negative or Timeout.Infinite.");
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Async/Test/AsyncResultWrapperTest.cs b/test/System.Web.Mvc.Test/Async/Test/AsyncResultWrapperTest.cs
new file mode 100644
index 00000000..2323516d
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Async/Test/AsyncResultWrapperTest.cs
@@ -0,0 +1,228 @@
+using System.Threading;
+using Moq;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Mvc.Async.Test
+{
+ public class AsyncResultWrapperTest
+ {
+ [Fact]
+ public void Begin_AsynchronousCompletion()
+ {
+ // Arrange
+ AsyncCallback capturedCallback = null;
+ IAsyncResult resultGivenToCallback = null;
+ IAsyncResult innerResult = new MockAsyncResult();
+
+ // Act
+ IAsyncResult outerResult = AsyncResultWrapper.Begin(
+ ar => { resultGivenToCallback = ar; },
+ null,
+ (callback, state) =>
+ {
+ capturedCallback = callback;
+ return innerResult;
+ },
+ ar => { });
+
+ capturedCallback(innerResult);
+
+ // Assert
+ Assert.Equal(outerResult, resultGivenToCallback);
+ }
+
+ [Fact]
+ public void Begin_ReturnsAsyncResultWhichWrapsInnerResult()
+ {
+ // Arrange
+ IAsyncResult innerResult = new MockAsyncResult()
+ {
+ AsyncState = "inner state",
+ CompletedSynchronously = true,
+ IsCompleted = true
+ };
+
+ // Act
+ IAsyncResult outerResult = AsyncResultWrapper.Begin(
+ null, "outer state",
+ (callback, state) => innerResult,
+ ar => { });
+
+ // Assert
+ Assert.Equal(innerResult.AsyncState, outerResult.AsyncState);
+ Assert.Equal(innerResult.AsyncWaitHandle, outerResult.AsyncWaitHandle);
+ Assert.Equal(innerResult.CompletedSynchronously, outerResult.CompletedSynchronously);
+ Assert.Equal(innerResult.IsCompleted, outerResult.IsCompleted);
+ }
+
+ [Fact]
+ public void Begin_SynchronousCompletion()
+ {
+ // Arrange
+ IAsyncResult resultGivenToCallback = null;
+ IAsyncResult innerResult = new MockAsyncResult();
+
+ // Act
+ IAsyncResult outerResult = AsyncResultWrapper.Begin(
+ ar => { resultGivenToCallback = ar; },
+ null,
+ (callback, state) =>
+ {
+ callback(innerResult);
+ return innerResult;
+ },
+ ar => { });
+
+ // Assert
+ Assert.Equal(outerResult, resultGivenToCallback);
+ }
+
+ [Fact]
+ public void Begin_AsynchronousButAlreadyCompleted()
+ {
+ // Arrange
+ Mock<IAsyncResult> innerResultMock = new Mock<IAsyncResult>();
+ innerResultMock.Setup(ir => ir.CompletedSynchronously).Returns(false);
+ innerResultMock.Setup(ir => ir.IsCompleted).Returns(true);
+
+ // Act
+ IAsyncResult outerResult = AsyncResultWrapper.Begin(
+ null,
+ null,
+ (callback, state) =>
+ {
+ callback(innerResultMock.Object);
+ return innerResultMock.Object;
+ },
+ ar => { });
+
+ // Assert
+ Assert.True(outerResult.CompletedSynchronously);
+ }
+
+ [Fact]
+ public void BeginSynchronous_Action()
+ {
+ // Arrange
+ bool actionCalled = false;
+
+ // Act
+ IAsyncResult asyncResult = AsyncResultWrapper.BeginSynchronous(null, null, delegate { actionCalled = true; });
+ AsyncResultWrapper.End(asyncResult);
+
+ // Assert
+ Assert.True(actionCalled);
+ Assert.True(asyncResult.IsCompleted);
+ Assert.True(asyncResult.CompletedSynchronously);
+ }
+
+ [Fact]
+ public void BeginSynchronous_Func()
+ {
+ // Act
+ IAsyncResult asyncResult = AsyncResultWrapper.BeginSynchronous(null, null, () => 42);
+ int retVal = AsyncResultWrapper.End<int>(asyncResult);
+
+ // Assert
+ Assert.Equal(42, retVal);
+ Assert.True(asyncResult.IsCompleted);
+ Assert.True(asyncResult.CompletedSynchronously);
+ }
+
+ [Fact]
+ public void End_ExecutesStoredDelegateAndReturnsValue()
+ {
+ // Arrange
+ IAsyncResult asyncResult = AsyncResultWrapper.Begin(
+ null, null,
+ (callback, state) => new MockAsyncResult(),
+ ar => 42);
+
+ // Act
+ int returned = AsyncResultWrapper.End<int>(asyncResult);
+
+ // Assert
+ Assert.Equal(42, returned);
+ }
+
+ [Fact]
+ public void End_ThrowsIfAsyncResultIsIncorrectType()
+ {
+ // Arrange
+ IAsyncResult asyncResult = AsyncResultWrapper.Begin(
+ null, null,
+ (callback, state) => new MockAsyncResult(),
+ ar => { });
+
+ // Act & assert
+ Assert.Throws<ArgumentException>(
+ delegate { AsyncResultWrapper.End<int>(asyncResult); },
+ @"The provided IAsyncResult is not valid for this method.
+Parameter name: asyncResult");
+ }
+
+ [Fact]
+ public void End_ThrowsIfAsyncResultIsNull()
+ {
+ // Act & assert
+ Assert.ThrowsArgumentNull(
+ delegate { AsyncResultWrapper.End(null); }, "asyncResult");
+ }
+
+ [Fact]
+ public void End_ThrowsIfAsyncResultTagMismatch()
+ {
+ // Arrange
+ IAsyncResult asyncResult = AsyncResultWrapper.Begin(
+ null, null,
+ (callback, state) => new MockAsyncResult(),
+ ar => { },
+ "some tag");
+
+ // Act & assert
+ Assert.Throws<ArgumentException>(
+ delegate { AsyncResultWrapper.End(asyncResult, "some other tag"); },
+ @"The provided IAsyncResult is not valid for this method.
+Parameter name: asyncResult");
+ }
+
+ [Fact]
+ public void End_ThrowsIfCalledTwiceOnSameAsyncResult()
+ {
+ // Arrange
+ IAsyncResult asyncResult = AsyncResultWrapper.Begin(
+ null, null,
+ (callback, state) => new MockAsyncResult(),
+ ar => { });
+
+ // Act & assert
+ AsyncResultWrapper.End(asyncResult);
+ Assert.Throws<InvalidOperationException>(
+ delegate { AsyncResultWrapper.End(asyncResult); },
+ @"The provided IAsyncResult has already been consumed.");
+ }
+
+ [Fact]
+ public void TimedOut()
+ {
+ // Arrange
+ ManualResetEvent waitHandle = new ManualResetEvent(false /* initialState */);
+
+ AsyncCallback callback = ar => { waitHandle.Set(); };
+
+ // Act & assert
+ IAsyncResult asyncResult = AsyncResultWrapper.Begin(
+ callback, null,
+ (innerCallback, innerState) => new MockAsyncResult(),
+ ar => { Assert.True(false, "This callback should never execute since we timed out."); },
+ null, 0);
+
+ // wait for the timeout
+ waitHandle.WaitOne();
+
+ Assert.Throws<TimeoutException>(
+ delegate { AsyncResultWrapper.End(asyncResult); });
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Async/Test/AsyncUtilTest.cs b/test/System.Web.Mvc.Test/Async/Test/AsyncUtilTest.cs
new file mode 100644
index 00000000..b4675f9f
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Async/Test/AsyncUtilTest.cs
@@ -0,0 +1,98 @@
+using System.Threading;
+using Xunit;
+
+namespace System.Web.Mvc.Async.Test
+{
+ public class AsyncUtilTest
+ {
+ [Fact]
+ public void WrapCallbackForSynchronizedExecution_CallsSyncIfOperationCompletedAsynchronously()
+ {
+ // Arrange
+ MockAsyncResult asyncResult = new MockAsyncResult()
+ {
+ CompletedSynchronously = false,
+ IsCompleted = true
+ };
+
+ bool originalCallbackCalled = false;
+ AsyncCallback originalCallback = ar =>
+ {
+ Assert.Equal(asyncResult, ar);
+ originalCallbackCalled = true;
+ };
+
+ DummySynchronizationContext syncContext = new DummySynchronizationContext();
+
+ // Act
+ AsyncCallback retVal = AsyncUtil.WrapCallbackForSynchronizedExecution(originalCallback, syncContext);
+ retVal(asyncResult);
+
+ // Assert
+ Assert.True(originalCallbackCalled);
+ Assert.True(syncContext.SendCalled);
+ }
+
+ [Fact]
+ public void WrapCallbackForSynchronizedExecution_DoesNotCallSyncIfOperationCompletedSynchronously()
+ {
+ // Arrange
+ MockAsyncResult asyncResult = new MockAsyncResult()
+ {
+ CompletedSynchronously = true,
+ IsCompleted = true
+ };
+
+ bool originalCallbackCalled = false;
+ AsyncCallback originalCallback = ar =>
+ {
+ Assert.Equal(asyncResult, ar);
+ originalCallbackCalled = true;
+ };
+
+ DummySynchronizationContext syncContext = new DummySynchronizationContext();
+
+ // Act
+ AsyncCallback retVal = AsyncUtil.WrapCallbackForSynchronizedExecution(originalCallback, syncContext);
+ retVal(asyncResult);
+
+ // Assert
+ Assert.True(originalCallbackCalled);
+ Assert.False(syncContext.SendCalled);
+ }
+
+ [Fact]
+ public void WrapCallbackForSynchronizedExecution_ReturnsNullIfCallbackIsNull()
+ {
+ // Act
+ AsyncCallback retVal = AsyncUtil.WrapCallbackForSynchronizedExecution(null, new SynchronizationContext());
+
+ // Assert
+ Assert.Null(retVal);
+ }
+
+ [Fact]
+ public void WrapCallbackForSynchronizedExecution_ReturnsOriginalCallbackIfSyncContextIsNull()
+ {
+ // Arrange
+ AsyncCallback originalCallback = _ => { };
+
+ // Act
+ AsyncCallback retVal = AsyncUtil.WrapCallbackForSynchronizedExecution(originalCallback, null);
+
+ // Assert
+ Assert.Same(originalCallback, retVal);
+ }
+
+ private class DummySynchronizationContext : SynchronizationContext
+ {
+ public bool SendCalled { get; private set; }
+
+ public override void Send(SendOrPostCallback d, object state)
+ {
+ SendCalled = true;
+ base.Send(d, state);
+ }
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Async/Test/MockAsyncResult.cs b/test/System.Web.Mvc.Test/Async/Test/MockAsyncResult.cs
new file mode 100644
index 00000000..954e29ea
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Async/Test/MockAsyncResult.cs
@@ -0,0 +1,45 @@
+using System.Threading;
+
+namespace System.Web.Mvc.Async.Test
+{
+ public class MockAsyncResult : IAsyncResult
+ {
+ private volatile object _asyncState;
+ private volatile ManualResetEvent _asyncWaitHandle = new ManualResetEvent(false);
+ private volatile bool _completedSynchronously;
+ private volatile bool _isCompleted;
+
+ public object AsyncState
+ {
+ get { return _asyncState; }
+ set { _asyncState = value; }
+ }
+
+ public ManualResetEvent AsyncWaitHandle
+ {
+ get { return _asyncWaitHandle; }
+ set { _asyncWaitHandle = value; }
+ }
+
+ public bool CompletedSynchronously
+ {
+ get { return _completedSynchronously; }
+ set { _completedSynchronously = value; }
+ }
+
+ public bool IsCompleted
+ {
+ get { return _isCompleted; }
+ set { _isCompleted = value; }
+ }
+
+ #region IAsyncResult Members
+
+ WaitHandle IAsyncResult.AsyncWaitHandle
+ {
+ get { return _asyncWaitHandle; }
+ }
+
+ #endregion
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Async/Test/OperationCounterTest.cs b/test/System.Web.Mvc.Test/Async/Test/OperationCounterTest.cs
new file mode 100644
index 00000000..90827f9b
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Async/Test/OperationCounterTest.cs
@@ -0,0 +1,110 @@
+using Xunit;
+
+namespace System.Web.Mvc.Async.Test
+{
+ public class OperationCounterTest
+ {
+ [Fact]
+ public void CompletedEvent()
+ {
+ // Arrange
+ bool premature = true;
+ bool eventFired = false;
+ OperationCounter ops = new OperationCounter();
+ ops.Completed += (sender, eventArgs) =>
+ {
+ if (premature)
+ {
+ Assert.True(false, "Event fired too early!");
+ }
+ if (eventFired)
+ {
+ Assert.True(false, "Event fired multiple times.");
+ }
+
+ Assert.Equal(ops, sender);
+ Assert.Equal(eventArgs, EventArgs.Empty);
+ eventFired = true;
+ };
+
+ // Act & assert
+ ops.Increment(); // should not fire event (will throw exception)
+ premature = false;
+
+ ops.Decrement(); // should fire event
+ Assert.True(eventFired);
+
+ ops.Increment(); // should not fire event (will throw exception)
+ }
+
+ [Fact]
+ public void CountStartsAtZero()
+ {
+ // Arrange
+ OperationCounter ops = new OperationCounter();
+
+ // Act & assert
+ Assert.Equal(0, ops.Count);
+ }
+
+ [Fact]
+ public void DecrementWithIntegerArgument()
+ {
+ // Arrange
+ OperationCounter ops = new OperationCounter();
+
+ // Act
+ int returned = ops.Decrement(3);
+ int newCount = ops.Count;
+
+ // Assert
+ Assert.Equal(-3, returned);
+ Assert.Equal(-3, newCount);
+ }
+
+ [Fact]
+ public void DecrementWithNoArguments()
+ {
+ // Arrange
+ OperationCounter ops = new OperationCounter();
+
+ // Act
+ int returned = ops.Decrement();
+ int newCount = ops.Count;
+
+ // Assert
+ Assert.Equal(-1, returned);
+ Assert.Equal(-1, newCount);
+ }
+
+ [Fact]
+ public void IncrementWithIntegerArgument()
+ {
+ // Arrange
+ OperationCounter ops = new OperationCounter();
+
+ // Act
+ int returned = ops.Increment(3);
+ int newCount = ops.Count;
+
+ // Assert
+ Assert.Equal(3, returned);
+ Assert.Equal(3, newCount);
+ }
+
+ [Fact]
+ public void IncrementWithNoArguments()
+ {
+ // Arrange
+ OperationCounter ops = new OperationCounter();
+
+ // Act
+ int returned = ops.Increment();
+ int newCount = ops.Count;
+
+ // Assert
+ Assert.Equal(1, returned);
+ Assert.Equal(1, newCount);
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Async/Test/ReflectedAsyncActionDescriptorTest.cs b/test/System.Web.Mvc.Test/Async/Test/ReflectedAsyncActionDescriptorTest.cs
new file mode 100644
index 00000000..1420d47c
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Async/Test/ReflectedAsyncActionDescriptorTest.cs
@@ -0,0 +1,314 @@
+using System.Collections.Generic;
+using System.Globalization;
+using System.Linq;
+using System.Reflection;
+using Moq;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Mvc.Async.Test
+{
+ public class ReflectedAsyncActionDescriptorTest
+ {
+ private readonly MethodInfo _asyncMethod = typeof(ExecuteController).GetMethod("FooAsync");
+ private readonly MethodInfo _completedMethod = typeof(ExecuteController).GetMethod("FooCompleted");
+
+ [Fact]
+ public void Constructor_SetsProperties()
+ {
+ // Arrange
+ string actionName = "SomeAction";
+ ControllerDescriptor cd = new Mock<ControllerDescriptor>().Object;
+
+ // Act
+ ReflectedAsyncActionDescriptor ad = new ReflectedAsyncActionDescriptor(_asyncMethod, _completedMethod, actionName, cd);
+
+ // Assert
+ Assert.Equal(_asyncMethod, ad.AsyncMethodInfo);
+ Assert.Equal(_completedMethod, ad.CompletedMethodInfo);
+ Assert.Equal(actionName, ad.ActionName);
+ Assert.Equal(cd, ad.ControllerDescriptor);
+ }
+
+ [Fact]
+ public void Constructor_ThrowsIfActionNameIsEmpty()
+ {
+ // Arrange
+ ControllerDescriptor cd = new Mock<ControllerDescriptor>().Object;
+
+ // Act & assert
+ Assert.ThrowsArgumentNullOrEmpty(
+ delegate { new ReflectedAsyncActionDescriptor(_asyncMethod, _completedMethod, "", cd); }, "actionName");
+ }
+
+ [Fact]
+ public void Constructor_ThrowsIfActionNameIsNull()
+ {
+ // Arrange
+ ControllerDescriptor cd = new Mock<ControllerDescriptor>().Object;
+
+ // Act & assert
+ Assert.ThrowsArgumentNullOrEmpty(
+ delegate { new ReflectedAsyncActionDescriptor(_asyncMethod, _completedMethod, null, cd); }, "actionName");
+ }
+
+ [Fact]
+ public void Constructor_ThrowsIfAsyncMethodInfoIsInvalid()
+ {
+ // Arrange
+ ControllerDescriptor cd = new Mock<ControllerDescriptor>().Object;
+ MethodInfo getHashCodeMethod = typeof(object).GetMethod("GetHashCode");
+
+ // Act & assert
+ Assert.Throws<ArgumentException>(
+ delegate { new ReflectedAsyncActionDescriptor(getHashCodeMethod, _completedMethod, "SomeAction", cd); },
+ @"Cannot create a descriptor for instance method 'Int32 GetHashCode()' on type 'System.Object' because the type does not derive from ControllerBase.
+Parameter name: asyncMethodInfo");
+ }
+
+ [Fact]
+ public void Constructor_ThrowsIfAsyncMethodInfoIsNull()
+ {
+ // Arrange
+ ControllerDescriptor cd = new Mock<ControllerDescriptor>().Object;
+
+ // Act & assert
+ Assert.ThrowsArgumentNull(
+ delegate { new ReflectedAsyncActionDescriptor(null, _completedMethod, "SomeAction", cd); }, "asyncMethodInfo");
+ }
+
+ [Fact]
+ public void Constructor_ThrowsIfCompletedMethodInfoIsInvalid()
+ {
+ // Arrange
+ ControllerDescriptor cd = new Mock<ControllerDescriptor>().Object;
+ MethodInfo getHashCodeMethod = typeof(object).GetMethod("GetHashCode");
+
+ // Act & assert
+ Assert.Throws<ArgumentException>(
+ delegate { new ReflectedAsyncActionDescriptor(_asyncMethod, getHashCodeMethod, "SomeAction", cd); },
+ @"Cannot create a descriptor for instance method 'Int32 GetHashCode()' on type 'System.Object' because the type does not derive from ControllerBase.
+Parameter name: completedMethodInfo");
+ }
+
+ [Fact]
+ public void Constructor_ThrowsIfCompletedMethodInfoIsNull()
+ {
+ // Arrange
+ ControllerDescriptor cd = new Mock<ControllerDescriptor>().Object;
+
+ // Act & assert
+ Assert.ThrowsArgumentNull(
+ delegate { new ReflectedAsyncActionDescriptor(_asyncMethod, null, "SomeAction", cd); }, "completedMethodInfo");
+ }
+
+ [Fact]
+ public void Constructor_ThrowsIfControllerDescriptorIsNull()
+ {
+ // Act & assert
+ Assert.ThrowsArgumentNull(
+ delegate { new ReflectedAsyncActionDescriptor(_asyncMethod, _completedMethod, "SomeAction", null); }, "controllerDescriptor");
+ }
+
+ [Fact]
+ public void Execute()
+ {
+ // Arrange
+ Mock<ControllerContext> mockControllerContext = new Mock<ControllerContext>();
+ mockControllerContext.Setup(c => c.Controller).Returns(new ExecuteController());
+ ControllerContext controllerContext = mockControllerContext.Object;
+
+ Dictionary<string, object> parameters = new Dictionary<string, object>()
+ {
+ { "id1", 42 }
+ };
+
+ ReflectedAsyncActionDescriptor ad = GetActionDescriptor(_asyncMethod, _completedMethod);
+
+ SignalContainer<object> resultContainer = new SignalContainer<object>();
+ AsyncCallback callback = ar =>
+ {
+ object o = ad.EndExecute(ar);
+ resultContainer.Signal(o);
+ };
+
+ // Act
+ ad.BeginExecute(controllerContext, parameters, callback, null);
+ object retVal = resultContainer.Wait();
+
+ // Assert
+ Assert.Equal("Hello world: 42", retVal);
+ }
+
+ [Fact]
+ public void Execute_ThrowsIfControllerContextIsNull()
+ {
+ // Arrange
+ ReflectedAsyncActionDescriptor ad = GetActionDescriptor(_asyncMethod, _completedMethod);
+
+ // Act & assert
+ Assert.ThrowsArgumentNull(
+ delegate { ad.BeginExecute(null, new Dictionary<string, object>(), null, null); }, "controllerContext");
+ }
+
+ [Fact]
+ public void Execute_ThrowsIfControllerIsNotAsyncManagerContainer()
+ {
+ // Arrange
+ ReflectedAsyncActionDescriptor ad = GetActionDescriptor(_asyncMethod, _completedMethod);
+ ControllerContext controllerContext = new ControllerContext()
+ {
+ Controller = new RegularSyncController()
+ };
+
+ // Act & assert
+ Assert.Throws<InvalidOperationException>(
+ delegate { ad.BeginExecute(controllerContext, new Dictionary<string, object>(), null, null); },
+ @"The controller of type 'System.Web.Mvc.Async.Test.ReflectedAsyncActionDescriptorTest+RegularSyncController' must subclass AsyncController or implement the IAsyncManagerContainer interface.");
+ }
+
+ [Fact]
+ public void Execute_ThrowsIfParametersIsNull()
+ {
+ // Arrange
+ ReflectedAsyncActionDescriptor ad = GetActionDescriptor(_asyncMethod, _completedMethod);
+
+ // Act & assert
+ Assert.ThrowsArgumentNull(
+ delegate { ad.BeginExecute(new ControllerContext(), null, null, null); }, "parameters");
+ }
+
+ [Fact]
+ public void GetCustomAttributes()
+ {
+ // Arrange
+ ReflectedAsyncActionDescriptor ad = GetActionDescriptor(_asyncMethod, _completedMethod);
+
+ // Act
+ object[] attributes = ad.GetCustomAttributes(true /* inherit */);
+
+ // Assert
+ Assert.Single(attributes);
+ Assert.Equal(typeof(AuthorizeAttribute), attributes[0].GetType());
+ }
+
+ [Fact]
+ public void GetCustomAttributes_FilterByType()
+ {
+ // Shouldn't match attributes on the Completed() method, only the Async() method
+
+ // Arrange
+ ReflectedAsyncActionDescriptor ad = GetActionDescriptor(_asyncMethod, _completedMethod);
+
+ // Act
+ object[] attributes = ad.GetCustomAttributes(typeof(OutputCacheAttribute), true /* inherit */);
+
+ // Assert
+ Assert.Empty(attributes);
+ }
+
+ [Fact]
+ public void GetParameters()
+ {
+ // Arrange
+ ParameterInfo pInfo = _asyncMethod.GetParameters()[0];
+ ReflectedAsyncActionDescriptor ad = GetActionDescriptor(_asyncMethod, _completedMethod);
+
+ // Act
+ ParameterDescriptor[] pDescsFirstCall = ad.GetParameters();
+ ParameterDescriptor[] pDescsSecondCall = ad.GetParameters();
+
+ // Assert
+ Assert.NotSame(pDescsFirstCall, pDescsSecondCall);
+ Assert.Equal(pDescsFirstCall, pDescsSecondCall);
+ Assert.Single(pDescsFirstCall);
+
+ ReflectedParameterDescriptor pDesc = pDescsFirstCall[0] as ReflectedParameterDescriptor;
+
+ Assert.NotNull(pDesc);
+ Assert.Same(ad, pDesc.ActionDescriptor);
+ Assert.Same(pInfo, pDesc.ParameterInfo);
+ }
+
+ [Fact]
+ public void GetSelectors()
+ {
+ // Arrange
+ ControllerContext controllerContext = new Mock<ControllerContext>().Object;
+ Mock<MethodInfo> mockMethod = new Mock<MethodInfo>();
+
+ Mock<ActionMethodSelectorAttribute> mockAttr = new Mock<ActionMethodSelectorAttribute>();
+ mockAttr.Setup(attr => attr.IsValidForRequest(controllerContext, mockMethod.Object)).Returns(true).Verifiable();
+ mockMethod.Setup(m => m.GetCustomAttributes(typeof(ActionMethodSelectorAttribute), true)).Returns(new ActionMethodSelectorAttribute[] { mockAttr.Object });
+
+ ReflectedAsyncActionDescriptor ad = GetActionDescriptor(mockMethod.Object, _completedMethod);
+
+ // Act
+ ICollection<ActionSelector> selectors = ad.GetSelectors();
+ bool executedSuccessfully = selectors.All(s => s(controllerContext));
+
+ // Assert
+ Assert.Single(selectors);
+ Assert.True(executedSuccessfully);
+ mockAttr.Verify();
+ }
+
+ [Fact]
+ public void IsDefined()
+ {
+ // Arrange
+ ReflectedAsyncActionDescriptor ad = GetActionDescriptor(_asyncMethod, _completedMethod);
+
+ // Act
+ bool isDefined = ad.IsDefined(typeof(AuthorizeAttribute), true /* inherit */);
+
+ // Assert
+ Assert.True(isDefined);
+ }
+
+ private static ReflectedAsyncActionDescriptor GetActionDescriptor(MethodInfo asyncMethod, MethodInfo completedMethod)
+ {
+ return new ReflectedAsyncActionDescriptor(asyncMethod, completedMethod, "someName", new Mock<ControllerDescriptor>().Object, false /* validateMethod */)
+ {
+ DispatcherCache = new ActionMethodDispatcherCache()
+ };
+ }
+
+ private class ExecuteController : AsyncController
+ {
+ private Func<object, string> _func;
+
+ [Authorize]
+ public void FooAsync(int id1)
+ {
+ _func = o => Convert.ToString(o, CultureInfo.InvariantCulture) + id1.ToString(CultureInfo.InvariantCulture);
+ AsyncManager.Parameters["id2"] = "Hello world: ";
+ AsyncManager.Finish();
+ }
+
+ [OutputCache]
+ public string FooCompleted(string id2)
+ {
+ return _func(id2);
+ }
+
+ public string FooWithBool(bool id2)
+ {
+ return _func(id2);
+ }
+
+ public string FooWithException(Exception id2)
+ {
+ return _func(id2);
+ }
+ }
+
+ private class RegularSyncController : ControllerBase
+ {
+ protected override void ExecuteCore()
+ {
+ throw new NotImplementedException();
+ }
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Async/Test/ReflectedAsyncControllerDescriptorTest.cs b/test/System.Web.Mvc.Test/Async/Test/ReflectedAsyncControllerDescriptorTest.cs
new file mode 100644
index 00000000..13586447
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Async/Test/ReflectedAsyncControllerDescriptorTest.cs
@@ -0,0 +1,177 @@
+using System.Reflection;
+using Moq;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Mvc.Async.Test
+{
+ public class ReflectedAsyncControllerDescriptorTest
+ {
+ [Fact]
+ public void ConstructorSetsControllerTypeProperty()
+ {
+ // Arrange
+ Type controllerType = typeof(string);
+
+ // Act
+ ReflectedAsyncControllerDescriptor cd = new ReflectedAsyncControllerDescriptor(controllerType);
+
+ // Assert
+ Assert.Same(controllerType, cd.ControllerType);
+ }
+
+ [Fact]
+ public void ConstructorThrowsIfControllerTypeIsNull()
+ {
+ // Act & assert
+ Assert.ThrowsArgumentNull(
+ delegate { new ReflectedAsyncControllerDescriptor(null); }, "controllerType");
+ }
+
+ [Fact]
+ public void FindActionReturnsActionDescriptorIfFound()
+ {
+ // Arrange
+ Type controllerType = typeof(MyController);
+ MethodInfo asyncMethodInfo = controllerType.GetMethod("FooAsync");
+ MethodInfo completedMethodInfo = controllerType.GetMethod("FooCompleted");
+ ReflectedAsyncControllerDescriptor cd = new ReflectedAsyncControllerDescriptor(controllerType);
+
+ // Act
+ ActionDescriptor ad = cd.FindAction(new Mock<ControllerContext>().Object, "NewName");
+
+ // Assert
+ Assert.Equal("NewName", ad.ActionName);
+ var castAd = Assert.IsType<ReflectedAsyncActionDescriptor>(ad);
+
+ Assert.Same(asyncMethodInfo, castAd.AsyncMethodInfo);
+ Assert.Same(completedMethodInfo, castAd.CompletedMethodInfo);
+ Assert.Same(cd, ad.ControllerDescriptor);
+ }
+
+ [Fact]
+ public void FindActionReturnsNullIfNoActionFound()
+ {
+ // Arrange
+ Type controllerType = typeof(MyController);
+ ReflectedAsyncControllerDescriptor cd = new ReflectedAsyncControllerDescriptor(controllerType);
+
+ // Act
+ ActionDescriptor ad = cd.FindAction(new Mock<ControllerContext>().Object, "NonExistent");
+
+ // Assert
+ Assert.Null(ad);
+ }
+
+ [Fact]
+ public void FindActionThrowsIfActionNameIsEmpty()
+ {
+ // Arrange
+ Type controllerType = typeof(MyController);
+ ReflectedAsyncControllerDescriptor cd = new ReflectedAsyncControllerDescriptor(controllerType);
+
+ // Act & assert
+ Assert.ThrowsArgumentNullOrEmpty(
+ delegate { cd.FindAction(new Mock<ControllerContext>().Object, ""); }, "actionName");
+ }
+
+ [Fact]
+ public void FindActionThrowsIfActionNameIsNull()
+ {
+ // Arrange
+ Type controllerType = typeof(MyController);
+ ReflectedAsyncControllerDescriptor cd = new ReflectedAsyncControllerDescriptor(controllerType);
+
+ // Act & assert
+ Assert.ThrowsArgumentNullOrEmpty(
+ delegate { cd.FindAction(new Mock<ControllerContext>().Object, null); }, "actionName");
+ }
+
+ [Fact]
+ public void FindActionThrowsIfControllerContextIsNull()
+ {
+ // Arrange
+ Type controllerType = typeof(MyController);
+ ReflectedAsyncControllerDescriptor cd = new ReflectedAsyncControllerDescriptor(controllerType);
+
+ // Act & assert
+ Assert.ThrowsArgumentNull(
+ delegate { cd.FindAction(null, "someName"); }, "controllerContext");
+ }
+
+ [Fact]
+ public void GetCanonicalActionsReturnsEmptyArray()
+ {
+ // this method does nothing by default
+
+ // Arrange
+ Type controllerType = typeof(MyController);
+ ReflectedAsyncControllerDescriptor cd = new ReflectedAsyncControllerDescriptor(controllerType);
+
+ // Act
+ ActionDescriptor[] canonicalActions = cd.GetCanonicalActions();
+
+ // Assert
+ Assert.Empty(canonicalActions);
+ }
+
+ [Fact]
+ public void GetCustomAttributesCallsTypeGetCustomAttributes()
+ {
+ // Arrange
+ object[] expected = new object[0];
+ Mock<Type> mockType = new Mock<Type>();
+ mockType.Setup(t => t.GetCustomAttributes(true)).Returns(expected);
+ ReflectedAsyncControllerDescriptor cd = new ReflectedAsyncControllerDescriptor(mockType.Object);
+
+ // Act
+ object[] returned = cd.GetCustomAttributes(true);
+
+ // Assert
+ Assert.Same(expected, returned);
+ }
+
+ [Fact]
+ public void GetCustomAttributesWithAttributeTypeCallsTypeGetCustomAttributes()
+ {
+ // Arrange
+ object[] expected = new object[0];
+ Mock<Type> mockType = new Mock<Type>();
+ mockType.Setup(t => t.GetCustomAttributes(typeof(ObsoleteAttribute), true)).Returns(expected);
+ ReflectedAsyncControllerDescriptor cd = new ReflectedAsyncControllerDescriptor(mockType.Object);
+
+ // Act
+ object[] returned = cd.GetCustomAttributes(typeof(ObsoleteAttribute), true);
+
+ // Assert
+ Assert.Same(expected, returned);
+ }
+
+ [Fact]
+ public void IsDefinedCallsTypeIsDefined()
+ {
+ // Arrange
+ Mock<Type> mockType = new Mock<Type>();
+ mockType.Setup(t => t.IsDefined(typeof(ObsoleteAttribute), true)).Returns(true);
+ ReflectedAsyncControllerDescriptor cd = new ReflectedAsyncControllerDescriptor(mockType.Object);
+
+ // Act
+ bool isDefined = cd.IsDefined(typeof(ObsoleteAttribute), true);
+
+ // Assert
+ Assert.True(isDefined);
+ }
+
+ private class MyController : AsyncController
+ {
+ [ActionName("NewName")]
+ public void FooAsync()
+ {
+ }
+
+ public void FooCompleted()
+ {
+ }
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Async/Test/SignalContainer.cs b/test/System.Web.Mvc.Test/Async/Test/SignalContainer.cs
new file mode 100644
index 00000000..8e1f5125
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Async/Test/SignalContainer.cs
@@ -0,0 +1,22 @@
+using System.Threading;
+
+namespace System.Web.Mvc.Async.Test
+{
+ public sealed class SignalContainer<T>
+ {
+ private volatile object _item;
+ private readonly AutoResetEvent _waitHandle = new AutoResetEvent(false /* initialState */);
+
+ public void Signal(T item)
+ {
+ _item = item;
+ _waitHandle.Set();
+ }
+
+ public T Wait()
+ {
+ _waitHandle.WaitOne();
+ return (T)_item;
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Async/Test/SimpleAsyncResultTest.cs b/test/System.Web.Mvc.Test/Async/Test/SimpleAsyncResultTest.cs
new file mode 100644
index 00000000..0c71b598
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Async/Test/SimpleAsyncResultTest.cs
@@ -0,0 +1,101 @@
+using System.Threading;
+using Xunit;
+
+namespace System.Web.Mvc.Async.Test
+{
+ public class SimpleAsyncResultTest
+ {
+ [Fact]
+ public void AsyncStateProperty()
+ {
+ // Arrange
+ string expected = "Hello!";
+ SimpleAsyncResult asyncResult = new SimpleAsyncResult(expected);
+
+ // Act
+ object asyncState = asyncResult.AsyncState;
+
+ // Assert
+ Assert.Equal(expected, asyncState);
+ }
+
+ [Fact]
+ public void AsyncWaitHandleProperty()
+ {
+ // Arrange
+ SimpleAsyncResult asyncResult = new SimpleAsyncResult(null);
+
+ // Act
+ WaitHandle asyncWaitHandle = asyncResult.AsyncWaitHandle;
+
+ // Assert
+ Assert.Null(asyncWaitHandle);
+ }
+
+ [Fact]
+ public void CompletedSynchronouslyProperty()
+ {
+ // Arrange
+ SimpleAsyncResult asyncResult = new SimpleAsyncResult(null);
+
+ // Act
+ bool completedSynchronously = asyncResult.CompletedSynchronously;
+
+ // Assert
+ Assert.False(completedSynchronously);
+ }
+
+ [Fact]
+ public void IsCompletedProperty()
+ {
+ // Arrange
+ SimpleAsyncResult asyncResult = new SimpleAsyncResult(null);
+
+ // Act
+ bool isCompleted = asyncResult.IsCompleted;
+
+ // Assert
+ Assert.False(isCompleted);
+ }
+
+ [Fact]
+ public void MarkCompleted_AsynchronousCompletion()
+ {
+ // Arrange
+ SimpleAsyncResult asyncResult = new SimpleAsyncResult(null);
+
+ bool callbackWasCalled = false;
+ AsyncCallback callback = ar =>
+ {
+ callbackWasCalled = true;
+ Assert.Equal(asyncResult, ar);
+ Assert.True(ar.IsCompleted);
+ Assert.False(ar.CompletedSynchronously);
+ };
+
+ // Act & assert
+ asyncResult.MarkCompleted(false, callback);
+ Assert.True(callbackWasCalled);
+ }
+
+ [Fact]
+ public void MarkCompleted_SynchronousCompletion()
+ {
+ // Arrange
+ SimpleAsyncResult asyncResult = new SimpleAsyncResult(null);
+
+ bool callbackWasCalled = false;
+ AsyncCallback callback = ar =>
+ {
+ callbackWasCalled = true;
+ Assert.Equal(asyncResult, ar);
+ Assert.True(ar.IsCompleted);
+ Assert.True(ar.CompletedSynchronously);
+ };
+
+ // Act & assert
+ asyncResult.MarkCompleted(true, callback);
+ Assert.True(callbackWasCalled);
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Async/Test/SingleEntryGateTest.cs b/test/System.Web.Mvc.Test/Async/Test/SingleEntryGateTest.cs
new file mode 100644
index 00000000..2cfdf5c0
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Async/Test/SingleEntryGateTest.cs
@@ -0,0 +1,24 @@
+using Xunit;
+
+namespace System.Web.Mvc.Async.Test
+{
+ public class SingleEntryGateTest
+ {
+ [Fact]
+ public void TryEnterShouldBeTrueForFirstCallAndFalseForSubsequentCalls()
+ {
+ // Arrange
+ SingleEntryGate gate = new SingleEntryGate();
+
+ // Act
+ bool firstCall = gate.TryEnter();
+ bool secondCall = gate.TryEnter();
+ bool thirdCall = gate.TryEnter();
+
+ // Assert
+ Assert.True(firstCall);
+ Assert.False(secondCall);
+ Assert.False(thirdCall);
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Async/Test/SynchronizationContextUtilTest.cs b/test/System.Web.Mvc.Test/Async/Test/SynchronizationContextUtilTest.cs
new file mode 100644
index 00000000..0810a2a5
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Async/Test/SynchronizationContextUtilTest.cs
@@ -0,0 +1,89 @@
+using System.Threading;
+using Moq;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Mvc.Async.Test
+{
+ public class SynchronizationContextUtilTest
+ {
+ [Fact]
+ public void SyncWithAction()
+ {
+ // Arrange
+ bool actionWasCalled = false;
+ bool sendWasCalled = false;
+
+ Mock<SynchronizationContext> mockSyncContext = new Mock<SynchronizationContext>();
+ mockSyncContext
+ .Setup(sc => sc.Send(It.IsAny<SendOrPostCallback>(), null))
+ .Callback(
+ delegate(SendOrPostCallback d, object state)
+ {
+ sendWasCalled = true;
+ d(state);
+ });
+
+ // Act
+ SynchronizationContextUtil.Sync(mockSyncContext.Object, () => { actionWasCalled = true; });
+
+ // Assert
+ Assert.True(actionWasCalled);
+ Assert.True(sendWasCalled);
+ }
+
+ [Fact]
+ public void SyncWithActionCapturesException()
+ {
+ // Arrange
+ InvalidOperationException exception = new InvalidOperationException("Some exception text.");
+
+ Mock<SynchronizationContext> mockSyncContext = new Mock<SynchronizationContext>();
+ mockSyncContext
+ .Setup(sc => sc.Send(It.IsAny<SendOrPostCallback>(), null))
+ .Callback(
+ delegate(SendOrPostCallback d, object state)
+ {
+ try
+ {
+ d(state);
+ }
+ catch
+ {
+ // swallow exceptions, just like AspNetSynchronizationContext
+ }
+ });
+
+ // Act & assert
+ SynchronousOperationException thrownException = Assert.Throws<SynchronousOperationException>(
+ delegate { SynchronizationContextUtil.Sync(mockSyncContext.Object, () => { throw exception; }); },
+ @"An operation that crossed a synchronization context failed. See the inner exception for more information.");
+
+ Assert.Equal(exception, thrownException.InnerException);
+ }
+
+ [Fact]
+ public void SyncWithFunc()
+ {
+ // Arrange
+ bool sendWasCalled = false;
+
+ Mock<SynchronizationContext> mockSyncContext = new Mock<SynchronizationContext>();
+ mockSyncContext
+ .Setup(sc => sc.Send(It.IsAny<SendOrPostCallback>(), null))
+ .Callback(
+ delegate(SendOrPostCallback d, object state)
+ {
+ sendWasCalled = true;
+ d(state);
+ });
+
+ // Act
+ int retVal = SynchronizationContextUtil.Sync(mockSyncContext.Object, () => 42);
+
+ // Assert
+ Assert.Equal(42, retVal);
+ Assert.True(sendWasCalled);
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Async/Test/SynchronousOperationExceptionTest.cs b/test/System.Web.Mvc.Test/Async/Test/SynchronousOperationExceptionTest.cs
new file mode 100644
index 00000000..32570c22
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Async/Test/SynchronousOperationExceptionTest.cs
@@ -0,0 +1,62 @@
+using System.IO;
+using System.Runtime.Serialization.Formatters.Binary;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Mvc.Async.Test
+{
+ public class SynchronousOperationExceptionTest
+ {
+ [Fact]
+ public void ConstructorWithMessageAndInnerExceptionParameter()
+ {
+ // Arrange
+ Exception innerException = new Exception();
+
+ // Act
+ SynchronousOperationException ex = new SynchronousOperationException("the message", innerException);
+
+ // Assert
+ Assert.Equal("the message", ex.Message);
+ Assert.Equal(innerException, ex.InnerException);
+ }
+
+ [Fact]
+ public void ConstructorWithMessageParameter()
+ {
+ // Act
+ SynchronousOperationException ex = new SynchronousOperationException("the message");
+
+ // Assert
+ Assert.Equal("the message", ex.Message);
+ }
+
+ [Fact]
+ public void ConstructorWithoutParameters()
+ {
+ // Act & assert
+ Assert.Throws<SynchronousOperationException>(
+ delegate { throw new SynchronousOperationException(); });
+ }
+
+ [Fact]
+ public void TypeIsSerializable()
+ {
+ // Arrange
+ MemoryStream ms = new MemoryStream();
+ BinaryFormatter formatter = new BinaryFormatter();
+ SynchronousOperationException ex = new SynchronousOperationException("the message", new Exception("inner exception"));
+
+ // Act
+ formatter.Serialize(ms, ex);
+ ms.Position = 0;
+ SynchronousOperationException deserialized = formatter.Deserialize(ms) as SynchronousOperationException;
+
+ // Assert
+ Assert.NotNull(deserialized);
+ Assert.Equal("the message", deserialized.Message);
+ Assert.NotNull(deserialized.InnerException);
+ Assert.Equal("inner exception", deserialized.InnerException.Message);
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Async/Test/TaskAsyncActionDescriptorTest.cs b/test/System.Web.Mvc.Test/Async/Test/TaskAsyncActionDescriptorTest.cs
new file mode 100644
index 00000000..c2c83232
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Async/Test/TaskAsyncActionDescriptorTest.cs
@@ -0,0 +1,558 @@
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection;
+using System.Threading;
+using System.Threading.Tasks;
+using Moq;
+using Xunit;
+using Xunit.Sdk;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Mvc.Async.Test
+{
+ public class TaskAsyncActionDescriptorTest
+ {
+ private readonly MethodInfo _taskMethod = typeof(ExecuteController).GetMethod("SimpleTask");
+
+ [Fact]
+ public void Constructor_SetsProperties()
+ {
+ // Arrange
+ string actionName = "SomeAction";
+ ControllerDescriptor cd = new Mock<ControllerDescriptor>().Object;
+
+ // Act
+ TaskAsyncActionDescriptor ad = new TaskAsyncActionDescriptor(_taskMethod, actionName, cd);
+
+ // Assert
+ Assert.Equal(_taskMethod, ad.TaskMethodInfo);
+ Assert.Equal(actionName, ad.ActionName);
+ Assert.Equal(cd, ad.ControllerDescriptor);
+ }
+
+ [Fact]
+ public void Constructor_ThrowsIfActionNameIsEmpty()
+ {
+ // Arrange
+ ControllerDescriptor cd = new Mock<ControllerDescriptor>().Object;
+
+ // Act & assert
+ Assert.ThrowsArgumentNullOrEmpty(
+ delegate { new TaskAsyncActionDescriptor(_taskMethod, "", cd); }, "actionName");
+ }
+
+ [Fact]
+ public void Constructor_ThrowsIfActionNameIsNull()
+ {
+ // Arrange
+ ControllerDescriptor cd = new Mock<ControllerDescriptor>().Object;
+
+ // Act & assert
+ Assert.ThrowsArgumentNullOrEmpty(
+ delegate { new TaskAsyncActionDescriptor(_taskMethod, null, cd); }, "actionName");
+ }
+
+ [Fact]
+ public void Constructor_ThrowsIfTaskMethodInfoIsInvalid()
+ {
+ // Arrange
+ ControllerDescriptor cd = new Mock<ControllerDescriptor>().Object;
+ MethodInfo getHashCodeMethod = typeof(object).GetMethod("GetHashCode");
+
+ // Act & assert
+ Assert.Throws<ArgumentException>(
+ delegate { new TaskAsyncActionDescriptor(getHashCodeMethod, "SomeAction", cd); },
+ @"Cannot create a descriptor for instance method 'Int32 GetHashCode()' on type 'System.Object' because the type does not derive from ControllerBase.
+Parameter name: taskMethodInfo");
+ }
+
+ [Fact]
+ public void Constructor_ThrowsIfTaskMethodInfoIsNull()
+ {
+ // Arrange
+ ControllerDescriptor cd = new Mock<ControllerDescriptor>().Object;
+
+ // Act & assert
+ Assert.ThrowsArgumentNull(
+ delegate { new TaskAsyncActionDescriptor(null, "SomeAction", cd); }, "taskMethodInfo");
+ }
+
+ [Fact]
+ public void Constructor_ThrowsIfControllerDescriptorIsNull()
+ {
+ // Act & assert
+ Assert.ThrowsArgumentNull(
+ delegate { new TaskAsyncActionDescriptor(_taskMethod, "SomeAction", null); }, "controllerDescriptor");
+ }
+
+ [Fact]
+ public void ExecuteTask()
+ {
+ // Arrange
+ TaskAsyncActionDescriptor actionDescriptor = GetActionDescriptor(GetExecuteControllerMethodInfo("SimpleTask"));
+
+ Dictionary<string, object> parameters = new Dictionary<string, object>()
+ {
+ { "doWork", true }
+ };
+
+ ControllerContext controllerContext = GetControllerContext();
+
+ // Act
+ object retVal = ExecuteHelper(actionDescriptor, parameters, controllerContext);
+
+ // Assert
+ Assert.Null(retVal);
+ Assert.True((controllerContext.Controller as ExecuteController).WorkDone);
+ }
+
+ [Fact]
+ public void ExecuteTaskGeneric()
+ {
+ // Arrange
+ TaskAsyncActionDescriptor actionDescriptor = GetActionDescriptor(GetExecuteControllerMethodInfo("GenericTask"));
+
+ Dictionary<string, object> parameters = new Dictionary<string, object>()
+ {
+ { "taskId", "foo" }
+ };
+
+ // Act
+ object retVal = ExecuteHelper(actionDescriptor, parameters);
+
+ // Assert
+ Assert.Equal("foo", retVal);
+ }
+
+ [Fact]
+ public void ExecuteTaskPreservesStackTraceOnException()
+ {
+ // Arrange
+ TaskAsyncActionDescriptor actionDescriptor = GetActionDescriptor(GetExecuteControllerMethodInfo("SimpleTaskException"));
+
+ Dictionary<string, object> parameters = new Dictionary<string, object>()
+ {
+ { "doWork", true }
+ };
+
+ // Act
+ IAsyncResult result = actionDescriptor.BeginExecute(GetControllerContext(), parameters, null, null);
+
+ // Assert
+ InvalidOperationException ex = Assert.Throws<InvalidOperationException>(
+ () => actionDescriptor.EndExecute(result),
+ "Test exception from action"
+ );
+
+ Assert.True(ex.StackTrace.Contains("System.Web.Mvc.Async.Test.TaskAsyncActionDescriptorTest.ExecuteController."));
+ }
+
+ [Fact]
+ public void ExecuteTaskGenericPreservesStackTraceOnException()
+ {
+ // Arrange
+ TaskAsyncActionDescriptor actionDescriptor = GetActionDescriptor(GetExecuteControllerMethodInfo("GenericTaskException"));
+
+ Dictionary<string, object> parameters = new Dictionary<string, object>()
+ {
+ { "taskId", "foo" },
+ { "throwException", true }
+ };
+
+ // Act
+ IAsyncResult result = actionDescriptor.BeginExecute(GetControllerContext(), parameters, null, null);
+
+ // Assert
+ InvalidOperationException ex = Assert.Throws<InvalidOperationException>(
+ () => actionDescriptor.EndExecute(result),
+ "Test exception from action"
+ );
+
+ Assert.True(ex.StackTrace.Contains("System.Web.Mvc.Async.Test.TaskAsyncActionDescriptorTest.ExecuteController."));
+ }
+
+ [Fact]
+ public void ExecuteTaskOfPrivateT()
+ {
+ // Arrange
+ TaskAsyncActionDescriptor actionDescriptor = GetActionDescriptor(GetExecuteControllerMethodInfo("TaskOfPrivateT"));
+ ControllerContext controllerContext = GetControllerContext();
+
+ Dictionary<string, object> parameters = new Dictionary<string, object>();
+
+ // Act
+ object retVal = ExecuteHelper(actionDescriptor, parameters, controllerContext);
+
+ // Assert
+ Assert.Null(retVal);
+ Assert.True((controllerContext.Controller as ExecuteController).WorkDone);
+ }
+
+ [Fact]
+ public void ExecuteTaskPreservesState()
+ {
+ // Arrange
+ TaskAsyncActionDescriptor actionDescriptor = GetActionDescriptor(GetExecuteControllerMethodInfo("SimpleTask"));
+
+ Dictionary<string, object> parameters = new Dictionary<string, object>()
+ {
+ { "doWork", true }
+ };
+
+ ControllerContext controllerContext = GetControllerContext();
+
+ // Act
+ TaskWrapperAsyncResult result = (TaskWrapperAsyncResult)actionDescriptor.BeginExecute(GetControllerContext(), parameters, callback: null, state: "state");
+
+ // Assert
+ Assert.Equal("state", result.AsyncState);
+ }
+
+ [Fact]
+ public void ExecuteTaskWithNullParameterAndTimeout()
+ {
+ // Arrange
+ TaskAsyncActionDescriptor actionDescriptor = GetActionDescriptor(GetExecuteControllerMethodInfo("TaskTimeoutWithNullParam"));
+
+ Dictionary<string, object> token = new Dictionary<string, object>()
+ {
+ { "nullParam", null },
+ { "cancellationToken", new CancellationToken() }
+ };
+
+ // Act & assert
+ Assert.Throws<TimeoutException>(
+ () => actionDescriptor.EndExecute(actionDescriptor.BeginExecute(GetControllerContext(0), parameters: token, callback: null, state: null)),
+ "The operation has timed out."
+ );
+ }
+
+ [Fact]
+ public void ExecuteWithInfiniteTimeout()
+ {
+ // Arrange
+ TaskAsyncActionDescriptor actionDescriptor = GetActionDescriptor(GetExecuteControllerMethodInfo("TaskWithInfiniteTimeout"));
+ ControllerContext controllerContext = GetControllerContext(Timeout.Infinite);
+
+ Dictionary<string, object> parameters = new Dictionary<string, object>()
+ {
+ { "cancellationToken", new CancellationToken() }
+ };
+
+ // Act
+ object retVal = ExecuteHelper(actionDescriptor, parameters);
+
+ // Assert
+ Assert.Equal("Task Completed", retVal);
+ }
+
+ [Fact]
+ public void ExecuteTaskWithImmediateTimeout()
+ {
+ // Arrange
+ TaskAsyncActionDescriptor actionDescriptor = GetActionDescriptor(GetExecuteControllerMethodInfo("TaskTimeout"));
+
+ Dictionary<string, object> token = new Dictionary<string, object>()
+ {
+ { "cancellationToken", new CancellationToken() }
+ };
+
+ // Act & assert
+ Assert.Throws<TimeoutException>(
+ () => actionDescriptor.EndExecute(actionDescriptor.BeginExecute(GetControllerContext(0), parameters: token, callback: null, state: null)),
+ "The operation has timed out."
+ );
+ }
+
+ [Fact]
+ public void ExecuteTaskWithTimeout()
+ {
+ // Arrange
+ TaskAsyncActionDescriptor actionDescriptor = GetActionDescriptor(GetExecuteControllerMethodInfo("TaskTimeout"));
+
+ Dictionary<string, object> token = new Dictionary<string, object>()
+ {
+ { "cancellationToken", new CancellationToken() }
+ };
+
+ // Act & assert
+ Assert.Throws<TimeoutException>(
+ () => actionDescriptor.EndExecute(actionDescriptor.BeginExecute(GetControllerContext(2000), parameters: token, callback: null, state: null)),
+ "The operation has timed out."
+ );
+ }
+
+ [Fact]
+ public void SynchronousExecuteThrows()
+ {
+ // Arrange
+ TaskAsyncActionDescriptor actionDescriptor = GetActionDescriptor(GetExecuteControllerMethodInfo("SimpleTask"));
+
+ // Act & assert
+ Assert.Throws<InvalidOperationException>(
+ delegate { actionDescriptor.Execute(new ControllerContext(), new Dictionary<string, object>()); }, "The asynchronous action method 'someName' returns a Task, which cannot be executed synchronously.");
+ }
+
+ [Fact]
+ public void Execute_ThrowsIfControllerContextIsNull()
+ {
+ // Arrange
+ TaskAsyncActionDescriptor ad = GetActionDescriptor(_taskMethod);
+
+ // Act & assert
+ Assert.ThrowsArgumentNull(
+ delegate { ad.BeginExecute(null, new Dictionary<string, object>(), null, null); }, "controllerContext");
+ }
+
+ [Fact]
+ public void Execute_ThrowsIfControllerIsNotAsyncManagerContainer()
+ {
+ // Arrange
+ TaskAsyncActionDescriptor ad = GetActionDescriptor(_taskMethod);
+ ControllerContext controllerContext = new ControllerContext()
+ {
+ Controller = new RegularSyncController()
+ };
+
+ Dictionary<string, object> parameters = new Dictionary<string, object>()
+ {
+ { "doWork", true }
+ };
+
+ // Act & assert
+ Assert.Throws<InvalidOperationException>(
+ delegate { ad.BeginExecute(controllerContext, parameters, null, null); },
+ @"The controller of type 'System.Web.Mvc.Async.Test.TaskAsyncActionDescriptorTest+RegularSyncController' must subclass AsyncController or implement the IAsyncManagerContainer interface.");
+ }
+
+ [Fact]
+ public void Execute_ThrowsIfParametersIsNull()
+ {
+ // Arrange
+ TaskAsyncActionDescriptor ad = GetActionDescriptor(_taskMethod);
+
+ // Act & assert
+ Assert.ThrowsArgumentNull(
+ delegate { ad.BeginExecute(new ControllerContext(), null, null, null); }, "parameters");
+ }
+
+ [Fact]
+ public void GetCustomAttributesCallsMethodInfoGetCustomAttributes()
+ {
+ // Arrange
+ object[] expected = new object[0];
+ Mock<MethodInfo> mockMethod = new Mock<MethodInfo>();
+ mockMethod.Setup(mi => mi.GetCustomAttributes(true)).Returns(expected);
+ TaskAsyncActionDescriptor ad = new TaskAsyncActionDescriptor(mockMethod.Object, "someName", new Mock<ControllerDescriptor>().Object, validateMethod: false)
+ {
+ DispatcherCache = new ActionMethodDispatcherCache()
+ };
+
+ // Act
+ object[] returned = ad.GetCustomAttributes(true);
+
+ // Assert
+ Assert.Same(expected, returned);
+ }
+
+ [Fact]
+ public void GetCustomAttributesWithAttributeTypeCallsMethodInfoGetCustomAttributes()
+ {
+ // Arrange
+ object[] expected = new object[0];
+ Mock<MethodInfo> mockMethod = new Mock<MethodInfo>();
+ mockMethod.Setup(mi => mi.GetCustomAttributes(typeof(ObsoleteAttribute), true)).Returns(expected);
+ TaskAsyncActionDescriptor ad = new TaskAsyncActionDescriptor(mockMethod.Object, "someName", new Mock<ControllerDescriptor>().Object, validateMethod: false)
+ {
+ DispatcherCache = new ActionMethodDispatcherCache()
+ };
+
+ // Act
+ object[] returned = ad.GetCustomAttributes(typeof(ObsoleteAttribute), true);
+
+ // Assert
+ Assert.Same(expected, returned);
+ }
+
+ [Fact]
+ public void GetParameters()
+ {
+ // Arrange
+ ParameterInfo pInfo = _taskMethod.GetParameters()[0];
+ TaskAsyncActionDescriptor ad = GetActionDescriptor(_taskMethod);
+
+ // Act
+ ParameterDescriptor[] pDescsFirstCall = ad.GetParameters();
+ ParameterDescriptor[] pDescsSecondCall = ad.GetParameters();
+
+ // Assert
+ Assert.NotSame(pDescsFirstCall, pDescsSecondCall); // Should get a new array every time
+ Assert.Equal(pDescsFirstCall, pDescsSecondCall);
+ Assert.Single(pDescsFirstCall);
+
+ ReflectedParameterDescriptor pDesc = pDescsFirstCall[0] as ReflectedParameterDescriptor;
+
+ Assert.NotNull(pDesc);
+ Assert.Same(ad, pDesc.ActionDescriptor);
+ Assert.Same(pInfo, pDesc.ParameterInfo);
+ }
+
+ [Fact]
+ public void GetSelectors()
+ {
+ // Arrange
+ ControllerContext controllerContext = new Mock<ControllerContext>().Object;
+ Mock<MethodInfo> mockMethod = new Mock<MethodInfo>();
+
+ Mock<ActionMethodSelectorAttribute> mockAttr = new Mock<ActionMethodSelectorAttribute>();
+ mockAttr.Setup(attr => attr.IsValidForRequest(controllerContext, mockMethod.Object)).Returns(true).Verifiable();
+ mockMethod.Setup(m => m.GetCustomAttributes(typeof(ActionMethodSelectorAttribute), true)).Returns(new ActionMethodSelectorAttribute[] { mockAttr.Object });
+
+ TaskAsyncActionDescriptor ad = new TaskAsyncActionDescriptor(mockMethod.Object, "someName", new Mock<ControllerDescriptor>().Object, validateMethod: false)
+ {
+ DispatcherCache = new ActionMethodDispatcherCache()
+ };
+
+ // Act
+ ICollection<ActionSelector> selectors = ad.GetSelectors();
+ bool executedSuccessfully = selectors.All(s => s(controllerContext));
+
+ // Assert
+ Assert.Single(selectors);
+ Assert.True(executedSuccessfully);
+ mockAttr.Verify();
+ }
+
+ [Fact]
+ public void IsDefined()
+ {
+ // Arrange
+ TaskAsyncActionDescriptor ad = GetActionDescriptor(_taskMethod);
+
+ // Act
+ bool isDefined = ad.IsDefined(typeof(AuthorizeAttribute), inherit: true);
+
+ // Assert
+ Assert.True(isDefined);
+ }
+
+ public static object ExecuteHelper(TaskAsyncActionDescriptor actionDescriptor, Dictionary<string, object> parameters, ControllerContext controllerContext = null)
+ {
+ SignalContainer<object> resultContainer = new SignalContainer<object>();
+ AsyncCallback callback = ar =>
+ {
+ object o = actionDescriptor.EndExecute(ar);
+ resultContainer.Signal(o);
+ };
+
+ actionDescriptor.BeginExecute(controllerContext ?? GetControllerContext(), parameters, callback, state: null);
+ return resultContainer.Wait();
+ }
+
+ private static TaskAsyncActionDescriptor GetActionDescriptor(MethodInfo taskMethod)
+ {
+ return new TaskAsyncActionDescriptor(taskMethod, "someName", new Mock<ControllerDescriptor>().Object)
+ {
+ DispatcherCache = new ActionMethodDispatcherCache()
+ };
+ }
+
+ private static ControllerContext GetControllerContext(int timeout = 45 * 1000)
+ {
+ Mock<ControllerContext> mockControllerContext = new Mock<ControllerContext>();
+ ExecuteController controller = new ExecuteController();
+ controller.AsyncManager.Timeout = timeout;
+ mockControllerContext.Setup(c => c.Controller).Returns(controller);
+ return mockControllerContext.Object;
+ }
+
+ private static MethodInfo GetExecuteControllerMethodInfo(string methodName)
+ {
+ return typeof(ExecuteController).GetMethod(methodName);
+ }
+
+ private class ExecuteController : AsyncController
+ {
+ public bool WorkDone { get; set; }
+
+ public Task<ActionResult> ReturnedTask { get; set; }
+
+ public Task<string> GenericTask(string taskId)
+ {
+ return Task.Factory.StartNew(() => taskId);
+ }
+
+ public Task<string> GenericTaskException(string taskId, bool throwException)
+ {
+ return Task.Factory.StartNew(() =>
+ {
+ if (throwException)
+ {
+ ThrowException();
+ }
+ ;
+ return taskId;
+ });
+ }
+
+ private void ThrowException()
+ {
+ throw new InvalidOperationException("Test exception from action");
+ }
+
+ [Authorize]
+ public Task SimpleTask(bool doWork)
+ {
+ return Task.Factory.StartNew(() => { WorkDone = doWork; });
+ }
+
+ public Task SimpleTaskException(bool doWork)
+ {
+ return Task.Factory.StartNew(() => { ThrowException(); });
+ }
+
+ public Task<ActionResult> TaskTimeoutWithNullParam(Object nullParam, CancellationToken cancellationToken)
+ {
+ return TaskTimeout(cancellationToken);
+ }
+
+ public Task<string> TaskWithInfiniteTimeout(CancellationToken cancellationToken)
+ {
+ return Task.Factory.StartNew(() => "Task Completed");
+ }
+
+ public Task<ActionResult> TaskTimeout(CancellationToken cancellationToken)
+ {
+ TaskCompletionSource<ActionResult> completionSource = new TaskCompletionSource<ActionResult>();
+ cancellationToken.Register(() => completionSource.TrySetCanceled());
+ ReturnedTask = completionSource.Task;
+ return ReturnedTask;
+ }
+
+ public Task TaskOfPrivateT()
+ {
+ var completionSource = new TaskCompletionSource<PrivateObject>();
+ completionSource.SetResult(new PrivateObject());
+ WorkDone = true;
+ return completionSource.Task;
+ }
+
+ private class PrivateObject
+ {
+ public override string ToString()
+ {
+ return "Private Object";
+ }
+ }
+ }
+
+ // Controller is async, so derive from ControllerBase to get sync behavior.
+ private class RegularSyncController : ControllerBase
+ {
+ protected override void ExecuteCore()
+ {
+ throw new NotImplementedException();
+ }
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Async/Test/TaskWrapperAsyncResultTest.cs b/test/System.Web.Mvc.Test/Async/Test/TaskWrapperAsyncResultTest.cs
new file mode 100644
index 00000000..e0e74843
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Async/Test/TaskWrapperAsyncResultTest.cs
@@ -0,0 +1,62 @@
+using System.Threading;
+using System.Threading.Tasks;
+using Moq;
+using Xunit;
+
+namespace System.Web.Mvc.Async.Test
+{
+ public class TaskWrapperAsyncResultTest
+ {
+ [Fact]
+ public void PropertiesHaveCorrectValues()
+ {
+ // Arrange
+ Mock<MyTask> mockTask = new Mock<MyTask>();
+ WaitHandle waitHandle = new Mock<WaitHandle>().Object;
+
+ mockTask.Setup(o => o.AsyncState).Returns(10);
+ mockTask.Setup(o => o.AsyncWaitHandle).Returns(waitHandle);
+ mockTask.Setup(o => o.CompletedSynchronously).Returns(true);
+ mockTask.Setup(o => o.IsCompleted).Returns(true);
+
+ // Act
+ TaskWrapperAsyncResult taskWrapper = new TaskWrapperAsyncResult(mockTask.Object, asyncState: 20);
+
+ // Assert
+ Assert.Equal(20, taskWrapper.AsyncState);
+ Assert.Equal(waitHandle, taskWrapper.AsyncWaitHandle);
+ Assert.True(taskWrapper.CompletedSynchronously);
+ Assert.True(taskWrapper.IsCompleted);
+ Assert.Equal(mockTask.Object, taskWrapper.Task);
+ }
+
+ // Assists in mocking a Task by passing a dummy action to the Task constructor [which defers execution]
+ public class MyTask : Task, IAsyncResult
+ {
+ public MyTask()
+ : base(() => { })
+ {
+ }
+
+ public new virtual object AsyncState
+ {
+ get { throw new NotImplementedException(); }
+ }
+
+ public virtual WaitHandle AsyncWaitHandle
+ {
+ get { throw new NotImplementedException(); }
+ }
+
+ public virtual bool CompletedSynchronously
+ {
+ get { throw new NotImplementedException(); }
+ }
+
+ public new virtual bool IsCompleted
+ {
+ get { throw new NotImplementedException(); }
+ }
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Async/Test/TriggerListenerTest.cs b/test/System.Web.Mvc.Test/Async/Test/TriggerListenerTest.cs
new file mode 100644
index 00000000..63c48d4e
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Async/Test/TriggerListenerTest.cs
@@ -0,0 +1,30 @@
+using Xunit;
+
+namespace System.Web.Mvc.Async.Test
+{
+ public class TriggerListenerTest
+ {
+ [Fact]
+ public void PerformTest()
+ {
+ // Arrange
+ int count = 0;
+ TriggerListener listener = new TriggerListener();
+ Trigger trigger = listener.CreateTrigger();
+
+ // Act & assert (hasn't fired yet)
+ listener.SetContinuation(() => { count++; });
+ listener.Activate();
+ Assert.Equal(0, count);
+
+ // Act & assert (fire it, get the callback)
+ trigger.Fire();
+ Assert.Equal(1, count);
+
+ // Act & assert (fire again, but no callback since it already fired)
+ Trigger trigger2 = listener.CreateTrigger();
+ trigger2.Fire();
+ Assert.Equal(1, count);
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/ExpressionUtil/Test/BinaryExpressionFingerprintTest.cs b/test/System.Web.Mvc.Test/ExpressionUtil/Test/BinaryExpressionFingerprintTest.cs
new file mode 100644
index 00000000..519c5bf3
--- /dev/null
+++ b/test/System.Web.Mvc.Test/ExpressionUtil/Test/BinaryExpressionFingerprintTest.cs
@@ -0,0 +1,91 @@
+using System.Linq.Expressions;
+using System.Reflection;
+using Xunit;
+
+namespace System.Web.Mvc.ExpressionUtil.Test
+{
+ public class BinaryExpressionFingerprintTest
+ {
+ [Fact]
+ public void Properties()
+ {
+ // Arrange
+ ExpressionType expectedNodeType = ExpressionType.Add;
+ Type expectedType = typeof(DateTime);
+ MethodInfo expectedMethod = typeof(DateTime).GetMethod("op_Addition", new Type[] { typeof(DateTime), typeof(TimeSpan) });
+
+ // Act
+ BinaryExpressionFingerprint fingerprint = new BinaryExpressionFingerprint(expectedNodeType, expectedType, expectedMethod);
+
+ // Assert
+ Assert.Equal(expectedNodeType, fingerprint.NodeType);
+ Assert.Equal(expectedType, fingerprint.Type);
+ Assert.Equal(expectedMethod, fingerprint.Method);
+ }
+
+ [Fact]
+ public void Comparison_Equality()
+ {
+ // Arrange
+ ExpressionType nodeType = ExpressionType.Add;
+ Type type = typeof(DateTime);
+ MethodInfo method = typeof(DateTime).GetMethod("op_Addition", new Type[] { typeof(DateTime), typeof(TimeSpan) });
+
+ // Act
+ BinaryExpressionFingerprint fingerprint1 = new BinaryExpressionFingerprint(nodeType, type, method);
+ BinaryExpressionFingerprint fingerprint2 = new BinaryExpressionFingerprint(nodeType, type, method);
+
+ // Assert
+ Assert.Equal(fingerprint1, fingerprint2);
+ Assert.Equal(fingerprint1.GetHashCode(), fingerprint2.GetHashCode());
+ }
+
+ [Fact]
+ public void Comparison_Inequality_FingerprintType()
+ {
+ // Arrange
+ ExpressionType nodeType = ExpressionType.Add;
+ Type type = typeof(DateTime);
+ MethodInfo method = typeof(DateTime).GetMethod("op_Addition", new Type[] { typeof(DateTime), typeof(TimeSpan) });
+
+ // Act
+ BinaryExpressionFingerprint fingerprint1 = new BinaryExpressionFingerprint(nodeType, type, method);
+ DummyExpressionFingerprint fingerprint2 = new DummyExpressionFingerprint(nodeType, type);
+
+ // Assert
+ Assert.NotEqual<ExpressionFingerprint>(fingerprint1, fingerprint2);
+ }
+
+ [Fact]
+ public void Comparison_Inequality_Method()
+ {
+ // Arrange
+ ExpressionType nodeType = ExpressionType.Add;
+ Type type = typeof(DateTime);
+ MethodInfo method = typeof(DateTime).GetMethod("op_Addition", new Type[] { typeof(DateTime), typeof(TimeSpan) });
+
+ // Act
+ BinaryExpressionFingerprint fingerprint1 = new BinaryExpressionFingerprint(nodeType, type, method);
+ BinaryExpressionFingerprint fingerprint2 = new BinaryExpressionFingerprint(nodeType, type, null /* method */);
+
+ // Assert
+ Assert.NotEqual(fingerprint1, fingerprint2);
+ }
+
+ [Fact]
+ public void Comparison_Inequality_Type()
+ {
+ // Arrange
+ ExpressionType nodeType = ExpressionType.Add;
+ Type type = typeof(DateTime);
+ MethodInfo method = typeof(DateTime).GetMethod("op_Addition", new Type[] { typeof(DateTime), typeof(TimeSpan) });
+
+ // Act
+ BinaryExpressionFingerprint fingerprint1 = new BinaryExpressionFingerprint(nodeType, type, method);
+ BinaryExpressionFingerprint fingerprint2 = new BinaryExpressionFingerprint(nodeType, typeof(object), method);
+
+ // Assert
+ Assert.NotEqual(fingerprint1, fingerprint2);
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/ExpressionUtil/Test/CachedExpressionCompilerTest.cs b/test/System.Web.Mvc.Test/ExpressionUtil/Test/CachedExpressionCompilerTest.cs
new file mode 100644
index 00000000..b87a0642
--- /dev/null
+++ b/test/System.Web.Mvc.Test/ExpressionUtil/Test/CachedExpressionCompilerTest.cs
@@ -0,0 +1,141 @@
+using System.Linq.Expressions;
+using System.Reflection;
+using System.Text;
+using Xunit;
+
+namespace System.Web.Mvc.ExpressionUtil.Test
+{
+ public class CachedExpressionCompilerTest
+ {
+ private delegate Func<TIn, TOut> Compiler<TIn, TOut>(Expression<Func<TIn, TOut>> expr);
+
+ [Fact]
+ public void Compiler_CompileFromConstLookup()
+ {
+ // Arrange
+ Expression<Func<string, int>> expr = model => 42;
+ var compiler = GetCompilerMethod<string, int>("CompileFromConstLookup");
+
+ // Act
+ var func = compiler(expr);
+ int result = func("any model");
+
+ // Assert
+ Assert.Equal(42, result);
+ }
+
+ [Fact]
+ public void Compiler_CompileFromFingerprint()
+ {
+ // Arrange
+ Expression<Func<string, int>> expr = s => 20 * s.Length;
+ var compiler = GetCompilerMethod<string, int>("CompileFromFingerprint");
+
+ // Act
+ var func = compiler(expr);
+ int result = func("hello");
+
+ // Assert
+ Assert.Equal(100, result);
+ }
+
+ [Fact]
+ public void Compiler_CompileFromIdentityFunc()
+ {
+ // Arrange
+ Expression<Func<string, string>> expr = model => model;
+ var compiler = GetCompilerMethod<string, string>("CompileFromIdentityFunc");
+
+ // Act
+ var func = compiler(expr);
+ string result = func("hello");
+
+ // Assert
+ Assert.Equal("hello", result);
+ }
+
+ [Fact]
+ public void Compiler_CompileFromMemberAccess_CapturedLocal()
+ {
+ // Arrange
+ string capturedLocal = "goodbye";
+ Expression<Func<string, string>> expr = _ => capturedLocal;
+ var compiler = GetCompilerMethod<string, string>("CompileFromMemberAccess");
+
+ // Act
+ var func = compiler(expr);
+ string result = func("hello");
+
+ // Assert
+ Assert.Equal("goodbye", result);
+ }
+
+ [Fact]
+ public void Compiler_CompileFromMemberAccess_ParameterInstanceMember()
+ {
+ // Arrange
+ Expression<Func<string, int>> expr = s => s.Length;
+ var compiler = GetCompilerMethod<string, int>("CompileFromMemberAccess");
+
+ // Act
+ var func = compiler(expr);
+ int result = func("hello");
+
+ // Assert
+ Assert.Equal(5, result);
+ }
+
+ [Fact]
+ public void Compiler_CompileFromMemberAccess_StaticMember()
+ {
+ // Arrange
+ Expression<Func<string, string>> expr = _ => String.Empty;
+ var compiler = GetCompilerMethod<string, string>("CompileFromMemberAccess");
+
+ // Act
+ var func = compiler(expr);
+ string result = func("hello");
+
+ // Assert
+ Assert.Equal("", result);
+ }
+
+ [Fact]
+ public void Compiler_CompileSlow()
+ {
+ // Arrange
+ Expression<Func<string, string>> expr = s => new StringBuilder(s).ToString();
+ var compiler = GetCompilerMethod<string, string>("CompileSlow");
+
+ // Act
+ var func = compiler(expr);
+ string result = func("hello");
+
+ // Assert
+ Assert.Equal("hello", result);
+ }
+
+ [Fact]
+ public void Process()
+ {
+ // Arrange
+ Expression<Func<string, string>> expr = s => new StringBuilder(s).ToString();
+
+ // Act
+ var func = CachedExpressionCompiler.Process(expr);
+ string result = func("hello");
+
+ // Assert
+ Assert.Equal("hello", result);
+ }
+
+ // helper to create a delegate to a private method on the compiler
+ private static Compiler<TIn, TOut> GetCompilerMethod<TIn, TOut>(string methodName)
+ {
+ Type openCompilerType = typeof(CachedExpressionCompiler).GetNestedType("Compiler`2", BindingFlags.NonPublic);
+ Type closedCompilerType = openCompilerType.MakeGenericType(typeof(TIn), typeof(TOut));
+ MethodInfo targetMethod = closedCompilerType.GetMethod(methodName, BindingFlags.Static | BindingFlags.NonPublic);
+ return (Compiler<TIn, TOut>)Delegate.CreateDelegate(typeof(Compiler<TIn, TOut>), targetMethod);
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/ExpressionUtil/Test/ConditionalExpressionFingerprintTest.cs b/test/System.Web.Mvc.Test/ExpressionUtil/Test/ConditionalExpressionFingerprintTest.cs
new file mode 100644
index 00000000..27367e43
--- /dev/null
+++ b/test/System.Web.Mvc.Test/ExpressionUtil/Test/ConditionalExpressionFingerprintTest.cs
@@ -0,0 +1,69 @@
+using System.Linq.Expressions;
+using Xunit;
+
+namespace System.Web.Mvc.ExpressionUtil.Test
+{
+ public class ConditionalExpressionFingerprintTest
+ {
+ [Fact]
+ public void Properties()
+ {
+ // Arrange
+ ExpressionType expectedNodeType = ExpressionType.Conditional;
+ Type expectedType = typeof(object);
+
+ // Act
+ ConditionalExpressionFingerprint fingerprint = new ConditionalExpressionFingerprint(expectedNodeType, expectedType);
+
+ // Assert
+ Assert.Equal(expectedNodeType, fingerprint.NodeType);
+ Assert.Equal(expectedType, fingerprint.Type);
+ }
+
+ [Fact]
+ public void Comparison_Equality()
+ {
+ // Arrange
+ ExpressionType nodeType = ExpressionType.Conditional;
+ Type type = typeof(object);
+
+ // Act
+ ConditionalExpressionFingerprint fingerprint1 = new ConditionalExpressionFingerprint(nodeType, type);
+ ConditionalExpressionFingerprint fingerprint2 = new ConditionalExpressionFingerprint(nodeType, type);
+
+ // Assert
+ Assert.Equal(fingerprint1, fingerprint2);
+ Assert.Equal(fingerprint1.GetHashCode(), fingerprint2.GetHashCode());
+ }
+
+ [Fact]
+ public void Comparison_Inequality_FingerprintType()
+ {
+ // Arrange
+ ExpressionType nodeType = ExpressionType.Conditional;
+ Type type = typeof(object);
+
+ // Act
+ ConditionalExpressionFingerprint fingerprint1 = new ConditionalExpressionFingerprint(nodeType, type);
+ DummyExpressionFingerprint fingerprint2 = new DummyExpressionFingerprint(nodeType, type);
+
+ // Assert
+ Assert.NotEqual<ExpressionFingerprint>(fingerprint1, fingerprint2);
+ }
+
+ [Fact]
+ public void Comparison_Inequality_Type()
+ {
+ // Arrange
+ ExpressionType nodeType = ExpressionType.Conditional;
+ Type type = typeof(object);
+
+ // Act
+ ConditionalExpressionFingerprint fingerprint1 = new ConditionalExpressionFingerprint(nodeType, type);
+ ConditionalExpressionFingerprint fingerprint2 = new ConditionalExpressionFingerprint(nodeType, typeof(string));
+
+ // Assert
+ Assert.NotEqual(fingerprint1, fingerprint2);
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/ExpressionUtil/Test/ConstantExpressionFingerprintTest.cs b/test/System.Web.Mvc.Test/ExpressionUtil/Test/ConstantExpressionFingerprintTest.cs
new file mode 100644
index 00000000..d71f920e
--- /dev/null
+++ b/test/System.Web.Mvc.Test/ExpressionUtil/Test/ConstantExpressionFingerprintTest.cs
@@ -0,0 +1,69 @@
+using System.Linq.Expressions;
+using Xunit;
+
+namespace System.Web.Mvc.ExpressionUtil.Test
+{
+ public class ConstantExpressionFingerprintTest
+ {
+ [Fact]
+ public void Properties()
+ {
+ // Arrange
+ ExpressionType expectedNodeType = ExpressionType.Constant;
+ Type expectedType = typeof(object);
+
+ // Act
+ ConstantExpressionFingerprint fingerprint = new ConstantExpressionFingerprint(expectedNodeType, expectedType);
+
+ // Assert
+ Assert.Equal(expectedNodeType, fingerprint.NodeType);
+ Assert.Equal(expectedType, fingerprint.Type);
+ }
+
+ [Fact]
+ public void Comparison_Equality()
+ {
+ // Arrange
+ ExpressionType nodeType = ExpressionType.Constant;
+ Type type = typeof(object);
+
+ // Act
+ ConstantExpressionFingerprint fingerprint1 = new ConstantExpressionFingerprint(nodeType, type);
+ ConstantExpressionFingerprint fingerprint2 = new ConstantExpressionFingerprint(nodeType, type);
+
+ // Assert
+ Assert.Equal(fingerprint1, fingerprint2);
+ Assert.Equal(fingerprint1.GetHashCode(), fingerprint2.GetHashCode());
+ }
+
+ [Fact]
+ public void Comparison_Inequality_FingerprintType()
+ {
+ // Arrange
+ ExpressionType nodeType = ExpressionType.Constant;
+ Type type = typeof(object);
+
+ // Act
+ ConstantExpressionFingerprint fingerprint1 = new ConstantExpressionFingerprint(nodeType, type);
+ DummyExpressionFingerprint fingerprint2 = new DummyExpressionFingerprint(nodeType, type);
+
+ // Assert
+ Assert.NotEqual<ExpressionFingerprint>(fingerprint1, fingerprint2);
+ }
+
+ [Fact]
+ public void Comparison_Inequality_Type()
+ {
+ // Arrange
+ ExpressionType nodeType = ExpressionType.Constant;
+ Type type = typeof(object);
+
+ // Act
+ ConstantExpressionFingerprint fingerprint1 = new ConstantExpressionFingerprint(nodeType, type);
+ ConstantExpressionFingerprint fingerprint2 = new ConstantExpressionFingerprint(nodeType, typeof(string));
+
+ // Assert
+ Assert.NotEqual(fingerprint1, fingerprint2);
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/ExpressionUtil/Test/DefaultExpressionFingerprintTest.cs b/test/System.Web.Mvc.Test/ExpressionUtil/Test/DefaultExpressionFingerprintTest.cs
new file mode 100644
index 00000000..28bd3b1b
--- /dev/null
+++ b/test/System.Web.Mvc.Test/ExpressionUtil/Test/DefaultExpressionFingerprintTest.cs
@@ -0,0 +1,69 @@
+using System.Linq.Expressions;
+using Xunit;
+
+namespace System.Web.Mvc.ExpressionUtil.Test
+{
+ public class DefaultExpressionFingerprintTest
+ {
+ [Fact]
+ public void Properties()
+ {
+ // Arrange
+ ExpressionType expectedNodeType = ExpressionType.Default;
+ Type expectedType = typeof(object);
+
+ // Act
+ DefaultExpressionFingerprint fingerprint = new DefaultExpressionFingerprint(expectedNodeType, expectedType);
+
+ // Assert
+ Assert.Equal(expectedNodeType, fingerprint.NodeType);
+ Assert.Equal(expectedType, fingerprint.Type);
+ }
+
+ [Fact]
+ public void Comparison_Equality()
+ {
+ // Arrange
+ ExpressionType nodeType = ExpressionType.Default;
+ Type type = typeof(object);
+
+ // Act
+ DefaultExpressionFingerprint fingerprint1 = new DefaultExpressionFingerprint(nodeType, type);
+ DefaultExpressionFingerprint fingerprint2 = new DefaultExpressionFingerprint(nodeType, type);
+
+ // Assert
+ Assert.Equal(fingerprint1, fingerprint2);
+ Assert.Equal(fingerprint1.GetHashCode(), fingerprint2.GetHashCode());
+ }
+
+ [Fact]
+ public void Comparison_Inequality_FingerprintType()
+ {
+ // Arrange
+ ExpressionType nodeType = ExpressionType.Default;
+ Type type = typeof(object);
+
+ // Act
+ DefaultExpressionFingerprint fingerprint1 = new DefaultExpressionFingerprint(nodeType, type);
+ DummyExpressionFingerprint fingerprint2 = new DummyExpressionFingerprint(nodeType, type);
+
+ // Assert
+ Assert.NotEqual<ExpressionFingerprint>(fingerprint1, fingerprint2);
+ }
+
+ [Fact]
+ public void Comparison_Inequality_NodeType()
+ {
+ // Arrange
+ ExpressionType nodeType = ExpressionType.Default;
+ Type type = typeof(object);
+
+ // Act
+ DefaultExpressionFingerprint fingerprint1 = new DefaultExpressionFingerprint(nodeType, type);
+ DefaultExpressionFingerprint fingerprint2 = new DefaultExpressionFingerprint(nodeType, typeof(string));
+
+ // Assert
+ Assert.NotEqual(fingerprint1, fingerprint2);
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/ExpressionUtil/Test/DummyExpressionFingerprint.cs b/test/System.Web.Mvc.Test/ExpressionUtil/Test/DummyExpressionFingerprint.cs
new file mode 100644
index 00000000..0d7231ed
--- /dev/null
+++ b/test/System.Web.Mvc.Test/ExpressionUtil/Test/DummyExpressionFingerprint.cs
@@ -0,0 +1,13 @@
+using System.Linq.Expressions;
+
+namespace System.Web.Mvc.ExpressionUtil.Test
+{
+ // Represents an ExpressionFingerprint that is of the wrong type.
+ internal sealed class DummyExpressionFingerprint : ExpressionFingerprint
+ {
+ public DummyExpressionFingerprint(ExpressionType nodeType, Type type)
+ : base(nodeType, type)
+ {
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/ExpressionUtil/Test/ExpressionFingerprintTest.cs b/test/System.Web.Mvc.Test/ExpressionUtil/Test/ExpressionFingerprintTest.cs
new file mode 100644
index 00000000..2e2bbdf0
--- /dev/null
+++ b/test/System.Web.Mvc.Test/ExpressionUtil/Test/ExpressionFingerprintTest.cs
@@ -0,0 +1,42 @@
+using System.Linq.Expressions;
+using Xunit;
+
+namespace System.Web.Mvc.ExpressionUtil.Test
+{
+ public class ExpressionFingerprintTest
+ {
+ [Fact]
+ public void Comparison_Equality()
+ {
+ // Act
+ DummyExpressionFingerprint fingerprint1 = new DummyExpressionFingerprint(ExpressionType.Default, typeof(object));
+ DummyExpressionFingerprint fingerprint2 = new DummyExpressionFingerprint(ExpressionType.Default, typeof(object));
+
+ // Assert
+ Assert.Equal(fingerprint1, fingerprint2);
+ Assert.Equal(fingerprint1.GetHashCode(), fingerprint2.GetHashCode());
+ }
+
+ [Fact]
+ public void Comparison_Inequality_NodeType()
+ {
+ // Act
+ DummyExpressionFingerprint fingerprint1 = new DummyExpressionFingerprint(ExpressionType.Default, typeof(object));
+ DummyExpressionFingerprint fingerprint2 = new DummyExpressionFingerprint(ExpressionType.Parameter, typeof(object));
+
+ // Assert
+ Assert.NotEqual(fingerprint1, fingerprint2);
+ }
+
+ [Fact]
+ public void Comparison_Inequality_Type()
+ {
+ // Act
+ DummyExpressionFingerprint fingerprint1 = new DummyExpressionFingerprint(ExpressionType.Default, typeof(object));
+ DummyExpressionFingerprint fingerprint2 = new DummyExpressionFingerprint(ExpressionType.Default, typeof(string));
+
+ // Assert
+ Assert.NotEqual(fingerprint1, fingerprint2);
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/ExpressionUtil/Test/FingerprintingExpressionVisitorTest.cs b/test/System.Web.Mvc.Test/ExpressionUtil/Test/FingerprintingExpressionVisitorTest.cs
new file mode 100644
index 00000000..3e71de49
--- /dev/null
+++ b/test/System.Web.Mvc.Test/ExpressionUtil/Test/FingerprintingExpressionVisitorTest.cs
@@ -0,0 +1,301 @@
+using System.Collections.Generic;
+using System.Linq;
+using System.Linq.Expressions;
+using System.Reflection;
+using System.Text;
+using Xunit;
+
+namespace System.Web.Mvc.ExpressionUtil.Test
+{
+ public class FingerprintingExpressionVisitorTest
+ {
+ private const ExpressionFingerprint _nullFingerprint = null;
+
+ [Fact]
+ public void TypeOverridesAllMethods()
+ {
+ // Ensures that the FingerprintingExpressionVisitor type overrides all VisitXxx methods so that
+ // it can properly set the "I gave up" flag when it encounters an Expression it's not familiar
+ // with.
+
+ var methodsOnExpressionVisitorRequiringOverride = typeof(ExpressionVisitor).GetMethods(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance).Where(mi => mi.IsVirtual).Select(mi => mi.GetBaseDefinition()).Where(mi => mi.DeclaringType == typeof(ExpressionVisitor));
+ var methodsOnFingerprintingExpressionVisitor = typeof(FingerprintingExpressionVisitor).GetMethods(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance).Where(mi => mi.DeclaringType == typeof(FingerprintingExpressionVisitor));
+
+ var missingMethods = methodsOnExpressionVisitorRequiringOverride.Except(methodsOnFingerprintingExpressionVisitor.Select(mi => mi.GetBaseDefinition())).ToArray();
+ if (missingMethods.Length != 0)
+ {
+ StringBuilder sb = new StringBuilder("The following methods are declared on ExpressionVisitor and must be overridden on FingerprintingExpressionVisitor:");
+ foreach (MethodInfo method in missingMethods)
+ {
+ sb.AppendLine();
+ sb.Append(method);
+ }
+ Assert.True(false, sb.ToString());
+ }
+ }
+
+ [Fact]
+ public void Visit_Null()
+ {
+ // Arrange
+
+ // fingerprints as [ NULL ]
+ Expression expr = null;
+
+ // Act
+ List<object> capturedConstants;
+ ExpressionFingerprintChain fingerprint = FingerprintingExpressionVisitor.GetFingerprintChain(expr, out capturedConstants);
+
+ // Assert
+ Assert.Empty(capturedConstants);
+ AssertChainEquals(fingerprint, _nullFingerprint);
+ }
+
+ [Fact]
+ public void Visit_Unknown()
+ {
+ // Arrange
+
+ // if we fingerprinted ctors, would fingerprint as [ NEW(StringBuilder(int)):StringBuilder, PARAM(0):int ]
+ // but since we don't fingerprint ctors, should just return null (signaling failure)
+ Expression expr = (Expression<Func<int, StringBuilder>>)(capacity => new StringBuilder(capacity));
+
+ // Act
+ List<object> capturedConstants;
+ ExpressionFingerprintChain fingerprint = FingerprintingExpressionVisitor.GetFingerprintChain(expr, out capturedConstants);
+
+ // Assert
+ Assert.Null(fingerprint); // Can't fingerprint ctor
+ Assert.Null(capturedConstants); // Can't fingerprint ctor
+ }
+
+ [Fact]
+ public void VisitBinary()
+ {
+ // Arrange
+
+ // fingerprints as [ OP_GREATERTHAN:bool, CONST:int, CONST:int ]
+ Expression expr = Expression.MakeBinary(ExpressionType.GreaterThan, Expression.Constant(42), Expression.Constant(84));
+
+ // Act
+ List<object> capturedConstants;
+ ExpressionFingerprintChain fingerprint = FingerprintingExpressionVisitor.GetFingerprintChain(expr, out capturedConstants);
+
+ // Assert
+ Assert.Equal(new object[] { 42, 84 }, capturedConstants.ToArray());
+ AssertChainEquals(fingerprint,
+ new BinaryExpressionFingerprint(ExpressionType.GreaterThan, typeof(bool), null /* method */),
+ new ConstantExpressionFingerprint(ExpressionType.Constant, typeof(int)),
+ new ConstantExpressionFingerprint(ExpressionType.Constant, typeof(int)));
+ }
+
+ [Fact]
+ public void VisitConditional()
+ {
+ // Arrange
+
+ // fingerprints as [ CONDITIONAL:int, CONST:bool, CONST:int, CONST:int ]
+ Expression expr = Expression.Condition(Expression.Constant(true), Expression.Constant(42), Expression.Constant(84));
+
+ // Act
+ List<object> capturedConstants;
+ ExpressionFingerprintChain fingerprint = FingerprintingExpressionVisitor.GetFingerprintChain(expr, out capturedConstants);
+
+ // Assert
+ Assert.Equal(new object[] { true, 42, 84 }, capturedConstants.ToArray());
+ AssertChainEquals(fingerprint,
+ new ConditionalExpressionFingerprint(ExpressionType.Conditional, typeof(int)),
+ new ConstantExpressionFingerprint(ExpressionType.Constant, typeof(bool)),
+ new ConstantExpressionFingerprint(ExpressionType.Constant, typeof(int)),
+ new ConstantExpressionFingerprint(ExpressionType.Constant, typeof(int)));
+ }
+
+ [Fact]
+ public void VisitConstant()
+ {
+ // Arrange
+
+ // fingerprints as [ CONST:int ]
+ Expression expr = Expression.Constant(42);
+
+ // Act
+ List<object> capturedConstants;
+ ExpressionFingerprintChain fingerprint = FingerprintingExpressionVisitor.GetFingerprintChain(expr, out capturedConstants);
+
+ // Assert
+ Assert.Equal(new object[] { 42 }, capturedConstants.ToArray());
+ AssertChainEquals(fingerprint,
+ new ConstantExpressionFingerprint(ExpressionType.Constant, typeof(int)));
+ }
+
+ [Fact]
+ public void VisitDefault()
+ {
+ // Arrange
+
+ // fingerprints as [ DEFAULT:int ]
+ Expression expr = Expression.Default(typeof(int));
+
+ // Act
+ List<object> capturedConstants;
+ ExpressionFingerprintChain fingerprint = FingerprintingExpressionVisitor.GetFingerprintChain(expr, out capturedConstants);
+
+ // Assert
+ Assert.Empty(capturedConstants);
+ AssertChainEquals(fingerprint, new DefaultExpressionFingerprint(ExpressionType.Default, typeof(int)));
+ }
+
+ [Fact]
+ public void VisitIndex()
+ {
+ // Arrange
+
+ // fingerprints as [ INDEX:object, PARAM(0):object[], CONST:int ]
+ Expression expr = Expression.MakeIndex(Expression.Parameter(typeof(object[])), null /* indexer */, new Expression[] { Expression.Constant(42) });
+
+ // Act
+ List<object> capturedConstants;
+ ExpressionFingerprintChain fingerprint = FingerprintingExpressionVisitor.GetFingerprintChain(expr, out capturedConstants);
+
+ // Assert
+ Assert.Equal(new object[] { 42 }, capturedConstants.ToArray());
+ AssertChainEquals(fingerprint,
+ new IndexExpressionFingerprint(ExpressionType.Index, typeof(object), null /* indexer */),
+ new ParameterExpressionFingerprint(ExpressionType.Parameter, typeof(object[]), 0 /* parameterIndex */),
+ new ConstantExpressionFingerprint(ExpressionType.Constant, typeof(int)));
+ }
+
+ [Fact]
+ public void VisitLambda()
+ {
+ // Arrange
+
+ // fingerprints as [ LAMBDA:Func<string, int>, CONST:int, PARAM(0):string ]
+ Expression expr = (Expression<Func<string, int>>)(x => 42);
+
+ // Act
+ List<object> capturedConstants;
+ ExpressionFingerprintChain fingerprint = FingerprintingExpressionVisitor.GetFingerprintChain(expr, out capturedConstants);
+
+ // Assert
+ Assert.Equal(new object[] { 42 }, capturedConstants.ToArray());
+ AssertChainEquals(fingerprint,
+ new LambdaExpressionFingerprint(ExpressionType.Lambda, typeof(Func<string, int>)),
+ new ConstantExpressionFingerprint(ExpressionType.Constant, typeof(int)),
+ new ParameterExpressionFingerprint(ExpressionType.Parameter, typeof(string), 0 /* parameterIndex */));
+ }
+
+ [Fact]
+ public void VisitMember()
+ {
+ // Arrange
+
+ // fingerprints as [ MEMBER(String.Empty):string, NULL ]
+ Expression expr = Expression.Field(null, typeof(string), "Empty");
+
+ // Act
+ List<object> capturedConstants;
+ ExpressionFingerprintChain fingerprint = FingerprintingExpressionVisitor.GetFingerprintChain(expr, out capturedConstants);
+
+ // Assert
+ Assert.Empty(capturedConstants);
+ AssertChainEquals(fingerprint,
+ new MemberExpressionFingerprint(ExpressionType.MemberAccess, typeof(string), typeof(string).GetField("Empty")),
+ _nullFingerprint);
+ }
+
+ [Fact]
+ public void VisitMethodCall()
+ {
+ // Arrange
+
+ // fingerprints as [ CALL(GC.KeepAlive):void, NULL, PARAM(0):object ]
+ Expression expr = Expression.Call(typeof(GC).GetMethod("KeepAlive"), Expression.Parameter(typeof(object)));
+
+ // Act
+ List<object> capturedConstants;
+ ExpressionFingerprintChain fingerprint = FingerprintingExpressionVisitor.GetFingerprintChain(expr, out capturedConstants);
+
+ // Assert
+ Assert.Empty(capturedConstants);
+ AssertChainEquals(fingerprint,
+ new MethodCallExpressionFingerprint(ExpressionType.Call, typeof(void), typeof(GC).GetMethod("KeepAlive")),
+ _nullFingerprint,
+ new ParameterExpressionFingerprint(ExpressionType.Parameter, typeof(object), 0 /* parameterIndex */));
+ }
+
+ [Fact]
+ public void VisitParameter()
+ {
+ // Arrange
+
+ // fingerprints as [ LAMBDA:Func<int, int, int>, OP_ADD:int, OP_ADD:int, OP_ADD:int, PARAM(0):int, PARAM(0):int, PARAM(1):int, PARAM(0):int, PARAM(1):int, PARAM(0):int ]
+ // (note that the parameters are out of order since 'y' is used first, but this is ok due
+ // to preservation of alpha equivalence within the VisitParameter method.)
+ Expression expr = (Expression<Func<int, int, int>>)((x, y) => y + y + x + y);
+
+ // Act
+ List<object> capturedConstants;
+ ExpressionFingerprintChain fingerprint = FingerprintingExpressionVisitor.GetFingerprintChain(expr, out capturedConstants);
+
+ // Assert
+ Assert.Empty(capturedConstants);
+ AssertChainEquals(fingerprint,
+ new LambdaExpressionFingerprint(ExpressionType.Lambda, typeof(Func<int, int, int>)),
+ new BinaryExpressionFingerprint(ExpressionType.Add, typeof(int), null /* method */),
+ new BinaryExpressionFingerprint(ExpressionType.Add, typeof(int), null /* method */),
+ new BinaryExpressionFingerprint(ExpressionType.Add, typeof(int), null /* method */),
+ new ParameterExpressionFingerprint(ExpressionType.Parameter, typeof(int), 0 /* parameterIndex */),
+ new ParameterExpressionFingerprint(ExpressionType.Parameter, typeof(int), 0 /* parameterIndex */),
+ new ParameterExpressionFingerprint(ExpressionType.Parameter, typeof(int), 1 /* parameterIndex */),
+ new ParameterExpressionFingerprint(ExpressionType.Parameter, typeof(int), 0 /* parameterIndex */),
+ new ParameterExpressionFingerprint(ExpressionType.Parameter, typeof(int), 1 /* parameterIndex */),
+ new ParameterExpressionFingerprint(ExpressionType.Parameter, typeof(int), 0 /* parameterIndex */));
+ }
+
+ [Fact]
+ public void VisitTypeBinary()
+ {
+ // Arrange
+
+ // fingerprints as [ TYPEIS(DateTime):bool, CONST:string ]
+ Expression expr = Expression.TypeIs(Expression.Constant("hello"), typeof(DateTime));
+
+ // Act
+ List<object> capturedConstants;
+ ExpressionFingerprintChain fingerprint = FingerprintingExpressionVisitor.GetFingerprintChain(expr, out capturedConstants);
+
+ // Assert
+ Assert.Equal(new object[] { "hello" }, capturedConstants.ToArray());
+ AssertChainEquals(fingerprint,
+ new TypeBinaryExpressionFingerprint(ExpressionType.TypeIs, typeof(bool), typeof(DateTime)),
+ new ConstantExpressionFingerprint(ExpressionType.Constant, typeof(string)));
+ }
+
+ [Fact]
+ public void VisitUnary()
+ {
+ // Arrange
+
+ // fingerprints as [ OP_NOT:int, PARAM:int ]
+ Expression expr = Expression.Not(Expression.Parameter(typeof(int)));
+
+ // Act
+ List<object> capturedConstants;
+ ExpressionFingerprintChain fingerprint = FingerprintingExpressionVisitor.GetFingerprintChain(expr, out capturedConstants);
+
+ // Assert
+ Assert.Empty(capturedConstants);
+ AssertChainEquals(fingerprint,
+ new UnaryExpressionFingerprint(ExpressionType.Not, typeof(int), null /* method */),
+ new ParameterExpressionFingerprint(ExpressionType.Parameter, typeof(int), 0 /* parameterIndex */));
+ }
+
+ internal static void AssertChainEquals(ExpressionFingerprintChain fingerprintChain, params ExpressionFingerprint[] expectedElements)
+ {
+ ExpressionFingerprintChain newChain = new ExpressionFingerprintChain();
+ newChain.Elements.AddRange(expectedElements);
+ Assert.Equal(fingerprintChain, newChain);
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/ExpressionUtil/Test/HoistingExpressionVisitorTest.cs b/test/System.Web.Mvc.Test/ExpressionUtil/Test/HoistingExpressionVisitorTest.cs
new file mode 100644
index 00000000..ee87d224
--- /dev/null
+++ b/test/System.Web.Mvc.Test/ExpressionUtil/Test/HoistingExpressionVisitorTest.cs
@@ -0,0 +1,45 @@
+using System.Collections.Generic;
+using System.Linq.Expressions;
+using Xunit;
+
+namespace System.Web.Mvc.ExpressionUtil.Test
+{
+ public class HoistingExpressionVisitorTest
+ {
+ [Fact]
+ public void Hoist()
+ {
+ // Arrange
+ Expression<Func<string, int>> expr = s => 2 * s.Length + 1;
+
+ // Act
+ Expression<Hoisted<string, int>> hoisted = HoistingExpressionVisitor<string, int>.Hoist(expr);
+
+ // Assert
+ // new expression should be (s, capturedConstants) => (int)(capturedConstants[0]) * s.Length + (int)(capturedConstants[1])
+ // with fingerprint [ LAMBDA:Hoisted<string, int>, OP_ADD:int, OP_MULTIPLY:int, OP_CAST:int, INDEX(List<object>.get_Item):object, PARAM(0):List<object>, CONST:int, MEMBER(String.Length):int, PARAM(1):string, OP_CAST:int, INDEX(List<object>.get_Item):object, PARAM(0):List<object>, CONST:int, PARAM(1):string, PARAM(0):List<object> ]
+
+ List<object> capturedConstants;
+ ExpressionFingerprintChain fingerprint = FingerprintingExpressionVisitor.GetFingerprintChain(hoisted, out capturedConstants);
+
+ Assert.Equal(new object[] { 0, 1 }, capturedConstants.ToArray()); // these are constants from the hoisted expression (array indexes), not the original expression
+ FingerprintingExpressionVisitorTest.AssertChainEquals(
+ fingerprint,
+ new LambdaExpressionFingerprint(ExpressionType.Lambda, typeof(Hoisted<string, int>)),
+ new BinaryExpressionFingerprint(ExpressionType.Add, typeof(int), null /* method */),
+ new BinaryExpressionFingerprint(ExpressionType.Multiply, typeof(int), null /* method */),
+ new UnaryExpressionFingerprint(ExpressionType.Convert, typeof(int), null /* method */),
+ new IndexExpressionFingerprint(ExpressionType.Index, typeof(object), typeof(List<object>).GetProperty("Item")),
+ new ParameterExpressionFingerprint(ExpressionType.Parameter, typeof(List<object>), 0 /* parameterIndex */),
+ new ConstantExpressionFingerprint(ExpressionType.Constant, typeof(int)),
+ new MemberExpressionFingerprint(ExpressionType.MemberAccess, typeof(int), typeof(string).GetProperty("Length")),
+ new ParameterExpressionFingerprint(ExpressionType.Parameter, typeof(string), 1 /* parameterIndex */),
+ new UnaryExpressionFingerprint(ExpressionType.Convert, typeof(int), null /* method */),
+ new IndexExpressionFingerprint(ExpressionType.Index, typeof(object), typeof(List<object>).GetProperty("Item")),
+ new ParameterExpressionFingerprint(ExpressionType.Parameter, typeof(List<object>), 0 /* parameterIndex */),
+ new ConstantExpressionFingerprint(ExpressionType.Constant, typeof(int)),
+ new ParameterExpressionFingerprint(ExpressionType.Parameter, typeof(string), 1 /* parameterIndex */),
+ new ParameterExpressionFingerprint(ExpressionType.Parameter, typeof(List<object>), 0 /* parameterIndex */));
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/ExpressionUtil/Test/IndexExpressionFingerprintTest.cs b/test/System.Web.Mvc.Test/ExpressionUtil/Test/IndexExpressionFingerprintTest.cs
new file mode 100644
index 00000000..9005b7ab
--- /dev/null
+++ b/test/System.Web.Mvc.Test/ExpressionUtil/Test/IndexExpressionFingerprintTest.cs
@@ -0,0 +1,91 @@
+using System.Linq.Expressions;
+using System.Reflection;
+using Xunit;
+
+namespace System.Web.Mvc.ExpressionUtil.Test
+{
+ public class IndexExpressionFingerprintTest
+ {
+ [Fact]
+ public void Properties()
+ {
+ // Arrange
+ ExpressionType expectedNodeType = ExpressionType.Index;
+ Type expectedType = typeof(char);
+ PropertyInfo expectedIndexer = typeof(string).GetProperty("Chars");
+
+ // Act
+ IndexExpressionFingerprint fingerprint = new IndexExpressionFingerprint(expectedNodeType, expectedType, expectedIndexer);
+
+ // Assert
+ Assert.Equal(expectedNodeType, fingerprint.NodeType);
+ Assert.Equal(expectedType, fingerprint.Type);
+ Assert.Equal(expectedIndexer, fingerprint.Indexer);
+ }
+
+ [Fact]
+ public void Comparison_Equality()
+ {
+ // Arrange
+ ExpressionType nodeType = ExpressionType.Index;
+ Type type = typeof(char);
+ PropertyInfo indexer = typeof(string).GetProperty("Chars");
+
+ // Act
+ IndexExpressionFingerprint fingerprint1 = new IndexExpressionFingerprint(nodeType, type, indexer);
+ IndexExpressionFingerprint fingerprint2 = new IndexExpressionFingerprint(nodeType, type, indexer);
+
+ // Assert
+ Assert.Equal(fingerprint1, fingerprint2);
+ Assert.Equal(fingerprint1.GetHashCode(), fingerprint2.GetHashCode());
+ }
+
+ [Fact]
+ public void Comparison_Inequality_FingerprintType()
+ {
+ // Arrange
+ ExpressionType nodeType = ExpressionType.Index;
+ Type type = typeof(char);
+ PropertyInfo indexer = typeof(string).GetProperty("Chars");
+
+ // Act
+ IndexExpressionFingerprint fingerprint1 = new IndexExpressionFingerprint(nodeType, type, indexer);
+ DummyExpressionFingerprint fingerprint2 = new DummyExpressionFingerprint(nodeType, type);
+
+ // Assert
+ Assert.NotEqual<ExpressionFingerprint>(fingerprint1, fingerprint2);
+ }
+
+ [Fact]
+ public void Comparison_Inequality_Indexer()
+ {
+ // Arrange
+ ExpressionType nodeType = ExpressionType.Index;
+ Type type = typeof(char);
+ PropertyInfo indexer = typeof(string).GetProperty("Chars");
+
+ // Act
+ IndexExpressionFingerprint fingerprint1 = new IndexExpressionFingerprint(nodeType, type, indexer);
+ IndexExpressionFingerprint fingerprint2 = new IndexExpressionFingerprint(nodeType, type, null /* indexer */);
+
+ // Assert
+ Assert.NotEqual(fingerprint1, fingerprint2);
+ }
+
+ [Fact]
+ public void Comparison_Inequality_Type()
+ {
+ // Arrange
+ ExpressionType nodeType = ExpressionType.Index;
+ Type type = typeof(char);
+ PropertyInfo indexer = typeof(string).GetProperty("Chars");
+
+ // Act
+ IndexExpressionFingerprint fingerprint1 = new IndexExpressionFingerprint(nodeType, type, indexer);
+ IndexExpressionFingerprint fingerprint2 = new IndexExpressionFingerprint(nodeType, typeof(object), indexer);
+
+ // Assert
+ Assert.NotEqual(fingerprint1, fingerprint2);
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/ExpressionUtil/Test/LambdaExpressionFingerprintTest.cs b/test/System.Web.Mvc.Test/ExpressionUtil/Test/LambdaExpressionFingerprintTest.cs
new file mode 100644
index 00000000..3585da10
--- /dev/null
+++ b/test/System.Web.Mvc.Test/ExpressionUtil/Test/LambdaExpressionFingerprintTest.cs
@@ -0,0 +1,69 @@
+using System.Linq.Expressions;
+using Xunit;
+
+namespace System.Web.Mvc.ExpressionUtil.Test
+{
+ public class LambdaExpressionFingerprintTest
+ {
+ [Fact]
+ public void Properties()
+ {
+ // Arrange
+ ExpressionType expectedNodeType = ExpressionType.Lambda;
+ Type expectedType = typeof(Action<object>);
+
+ // Act
+ LambdaExpressionFingerprint fingerprint = new LambdaExpressionFingerprint(expectedNodeType, expectedType);
+
+ // Assert
+ Assert.Equal(expectedNodeType, fingerprint.NodeType);
+ Assert.Equal(expectedType, fingerprint.Type);
+ }
+
+ [Fact]
+ public void Comparison_Equality()
+ {
+ // Arrange
+ ExpressionType nodeType = ExpressionType.Lambda;
+ Type type = typeof(Action<object>);
+
+ // Act
+ LambdaExpressionFingerprint fingerprint1 = new LambdaExpressionFingerprint(nodeType, type);
+ LambdaExpressionFingerprint fingerprint2 = new LambdaExpressionFingerprint(nodeType, type);
+
+ // Assert
+ Assert.Equal(fingerprint1, fingerprint2);
+ Assert.Equal(fingerprint1.GetHashCode(), fingerprint2.GetHashCode());
+ }
+
+ [Fact]
+ public void Comparison_Inequality_FingerprintType()
+ {
+ // Arrange
+ ExpressionType nodeType = ExpressionType.Lambda;
+ Type type = typeof(Action<object>);
+
+ // Act
+ LambdaExpressionFingerprint fingerprint1 = new LambdaExpressionFingerprint(nodeType, type);
+ DummyExpressionFingerprint fingerprint2 = new DummyExpressionFingerprint(nodeType, type);
+
+ // Assert
+ Assert.NotEqual<ExpressionFingerprint>(fingerprint1, fingerprint2);
+ }
+
+ [Fact]
+ public void Comparison_Inequality_NodeType()
+ {
+ // Arrange
+ ExpressionType nodeType = ExpressionType.Lambda;
+ Type type = typeof(Action<object>);
+
+ // Act
+ LambdaExpressionFingerprint fingerprint1 = new LambdaExpressionFingerprint(nodeType, type);
+ LambdaExpressionFingerprint fingerprint2 = new LambdaExpressionFingerprint(nodeType, typeof(Action));
+
+ // Assert
+ Assert.NotEqual(fingerprint1, fingerprint2);
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/ExpressionUtil/Test/MemberExpressionFingerprintTest.cs b/test/System.Web.Mvc.Test/ExpressionUtil/Test/MemberExpressionFingerprintTest.cs
new file mode 100644
index 00000000..bc7e0acc
--- /dev/null
+++ b/test/System.Web.Mvc.Test/ExpressionUtil/Test/MemberExpressionFingerprintTest.cs
@@ -0,0 +1,91 @@
+using System.Linq.Expressions;
+using System.Reflection;
+using Xunit;
+
+namespace System.Web.Mvc.ExpressionUtil.Test
+{
+ public class MemberExpressionFingerprintTest
+ {
+ [Fact]
+ public void Properties()
+ {
+ // Arrange
+ ExpressionType expectedNodeType = ExpressionType.MemberAccess;
+ Type expectedType = typeof(int);
+ MemberInfo expectedMember = typeof(TimeSpan).GetProperty("Seconds");
+
+ // Act
+ MemberExpressionFingerprint fingerprint = new MemberExpressionFingerprint(expectedNodeType, expectedType, expectedMember);
+
+ // Assert
+ Assert.Equal(expectedNodeType, fingerprint.NodeType);
+ Assert.Equal(expectedType, fingerprint.Type);
+ Assert.Equal(expectedMember, fingerprint.Member);
+ }
+
+ [Fact]
+ public void Comparison_Equality()
+ {
+ // Arrange
+ ExpressionType nodeType = ExpressionType.MemberAccess;
+ Type type = typeof(int);
+ MemberInfo member = typeof(TimeSpan).GetProperty("Seconds");
+
+ // Act
+ MemberExpressionFingerprint fingerprint1 = new MemberExpressionFingerprint(nodeType, type, member);
+ MemberExpressionFingerprint fingerprint2 = new MemberExpressionFingerprint(nodeType, type, member);
+
+ // Assert
+ Assert.Equal(fingerprint1, fingerprint2);
+ Assert.Equal(fingerprint1.GetHashCode(), fingerprint2.GetHashCode());
+ }
+
+ [Fact]
+ public void Comparison_Inequality_FingerprintType()
+ {
+ // Arrange
+ ExpressionType nodeType = ExpressionType.MemberAccess;
+ Type type = typeof(int);
+ MemberInfo member = typeof(TimeSpan).GetProperty("Seconds");
+
+ // Act
+ MemberExpressionFingerprint fingerprint1 = new MemberExpressionFingerprint(nodeType, type, member);
+ DummyExpressionFingerprint fingerprint2 = new DummyExpressionFingerprint(nodeType, type);
+
+ // Assert
+ Assert.NotEqual<ExpressionFingerprint>(fingerprint1, fingerprint2);
+ }
+
+ [Fact]
+ public void Comparison_Inequality_Member()
+ {
+ // Arrange
+ ExpressionType nodeType = ExpressionType.MemberAccess;
+ Type type = typeof(int);
+ MemberInfo member = typeof(TimeSpan).GetProperty("Seconds");
+
+ // Act
+ MemberExpressionFingerprint fingerprint1 = new MemberExpressionFingerprint(nodeType, type, member);
+ MemberExpressionFingerprint fingerprint2 = new MemberExpressionFingerprint(nodeType, type, null /* member */);
+
+ // Assert
+ Assert.NotEqual(fingerprint1, fingerprint2);
+ }
+
+ [Fact]
+ public void Comparison_Inequality_Type()
+ {
+ // Arrange
+ ExpressionType nodeType = ExpressionType.MemberAccess;
+ Type type = typeof(int);
+ MemberInfo member = typeof(TimeSpan).GetProperty("Seconds");
+
+ // Act
+ MemberExpressionFingerprint fingerprint1 = new MemberExpressionFingerprint(nodeType, type, member);
+ MemberExpressionFingerprint fingerprint2 = new MemberExpressionFingerprint(nodeType, typeof(object), member);
+
+ // Assert
+ Assert.NotEqual(fingerprint1, fingerprint2);
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/ExpressionUtil/Test/MethodCallExpressionFingerprintTest.cs b/test/System.Web.Mvc.Test/ExpressionUtil/Test/MethodCallExpressionFingerprintTest.cs
new file mode 100644
index 00000000..a6243377
--- /dev/null
+++ b/test/System.Web.Mvc.Test/ExpressionUtil/Test/MethodCallExpressionFingerprintTest.cs
@@ -0,0 +1,91 @@
+using System.Linq.Expressions;
+using System.Reflection;
+using Xunit;
+
+namespace System.Web.Mvc.ExpressionUtil.Test
+{
+ public class MethodCallExpressionFingerprintTest
+ {
+ [Fact]
+ public void Properties()
+ {
+ // Arrange
+ ExpressionType expectedNodeType = ExpressionType.Call;
+ Type expectedType = typeof(string);
+ MethodInfo expectedMethod = typeof(string).GetMethod("Intern");
+
+ // Act
+ MethodCallExpressionFingerprint fingerprint = new MethodCallExpressionFingerprint(expectedNodeType, expectedType, expectedMethod);
+
+ // Assert
+ Assert.Equal(expectedNodeType, fingerprint.NodeType);
+ Assert.Equal(expectedType, fingerprint.Type);
+ Assert.Equal(expectedMethod, fingerprint.Method);
+ }
+
+ [Fact]
+ public void Comparison_Equality()
+ {
+ // Arrange
+ ExpressionType nodeType = ExpressionType.Call;
+ Type type = typeof(string);
+ MethodInfo method = typeof(string).GetMethod("Intern");
+
+ // Act
+ MethodCallExpressionFingerprint fingerprint1 = new MethodCallExpressionFingerprint(nodeType, type, method);
+ MethodCallExpressionFingerprint fingerprint2 = new MethodCallExpressionFingerprint(nodeType, type, method);
+
+ // Assert
+ Assert.Equal(fingerprint1, fingerprint2);
+ Assert.Equal(fingerprint1.GetHashCode(), fingerprint2.GetHashCode());
+ }
+
+ [Fact]
+ public void Comparison_Inequality_FingerprintType()
+ {
+ // Arrange
+ ExpressionType nodeType = ExpressionType.Call;
+ Type type = typeof(string);
+ MethodInfo method = typeof(string).GetMethod("Intern");
+
+ // Act
+ MethodCallExpressionFingerprint fingerprint1 = new MethodCallExpressionFingerprint(nodeType, type, method);
+ DummyExpressionFingerprint fingerprint2 = new DummyExpressionFingerprint(nodeType, type);
+
+ // Assert
+ Assert.NotEqual<ExpressionFingerprint>(fingerprint1, fingerprint2);
+ }
+
+ [Fact]
+ public void Comparison_Inequality_Method()
+ {
+ // Arrange
+ ExpressionType nodeType = ExpressionType.Call;
+ Type type = typeof(string);
+ MethodInfo method = typeof(string).GetMethod("Intern");
+
+ // Act
+ MethodCallExpressionFingerprint fingerprint1 = new MethodCallExpressionFingerprint(nodeType, type, method);
+ MethodCallExpressionFingerprint fingerprint2 = new MethodCallExpressionFingerprint(nodeType, type, null /* method */);
+
+ // Assert
+ Assert.NotEqual(fingerprint1, fingerprint2);
+ }
+
+ [Fact]
+ public void Comparison_Inequality_Type()
+ {
+ // Arrange
+ ExpressionType nodeType = ExpressionType.Call;
+ Type type = typeof(string);
+ MethodInfo method = typeof(string).GetMethod("Intern");
+
+ // Act
+ MethodCallExpressionFingerprint fingerprint1 = new MethodCallExpressionFingerprint(nodeType, type, method);
+ MethodCallExpressionFingerprint fingerprint2 = new MethodCallExpressionFingerprint(nodeType, typeof(object), method);
+
+ // Assert
+ Assert.NotEqual(fingerprint1, fingerprint2);
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/ExpressionUtil/Test/ParameterExpressionFingerprintTest.cs b/test/System.Web.Mvc.Test/ExpressionUtil/Test/ParameterExpressionFingerprintTest.cs
new file mode 100644
index 00000000..f3acb224
--- /dev/null
+++ b/test/System.Web.Mvc.Test/ExpressionUtil/Test/ParameterExpressionFingerprintTest.cs
@@ -0,0 +1,90 @@
+using System.Linq.Expressions;
+using Xunit;
+
+namespace System.Web.Mvc.ExpressionUtil.Test
+{
+ public class ParameterExpressionFingerprintTest
+ {
+ [Fact]
+ public void Properties()
+ {
+ // Arrange
+ ExpressionType expectedNodeType = ExpressionType.Parameter;
+ Type expectedType = typeof(object);
+ int expectedParameterIndex = 1;
+
+ // Act
+ ParameterExpressionFingerprint fingerprint = new ParameterExpressionFingerprint(expectedNodeType, expectedType, expectedParameterIndex);
+
+ // Assert
+ Assert.Equal(expectedNodeType, fingerprint.NodeType);
+ Assert.Equal(expectedType, fingerprint.Type);
+ Assert.Equal(expectedParameterIndex, fingerprint.ParameterIndex);
+ }
+
+ [Fact]
+ public void Comparison_Equality()
+ {
+ // Arrange
+ ExpressionType nodeType = ExpressionType.Parameter;
+ Type type = typeof(object);
+ int parameterIndex = 1;
+
+ // Act
+ ParameterExpressionFingerprint fingerprint1 = new ParameterExpressionFingerprint(nodeType, type, parameterIndex);
+ ParameterExpressionFingerprint fingerprint2 = new ParameterExpressionFingerprint(nodeType, type, parameterIndex);
+
+ // Assert
+ Assert.Equal(fingerprint1, fingerprint2);
+ Assert.Equal(fingerprint1.GetHashCode(), fingerprint2.GetHashCode());
+ }
+
+ [Fact]
+ public void Comparison_Inequality_FingerprintType()
+ {
+ // Arrange
+ ExpressionType nodeType = ExpressionType.Parameter;
+ Type type = typeof(object);
+ int parameterIndex = 1;
+
+ // Act
+ ParameterExpressionFingerprint fingerprint1 = new ParameterExpressionFingerprint(nodeType, type, parameterIndex);
+ DummyExpressionFingerprint fingerprint2 = new DummyExpressionFingerprint(nodeType, type);
+
+ // Assert
+ Assert.NotEqual<ExpressionFingerprint>(fingerprint1, fingerprint2);
+ }
+
+ [Fact]
+ public void Comparison_Inequality_Method()
+ {
+ // Arrange
+ ExpressionType nodeType = ExpressionType.Parameter;
+ Type type = typeof(object);
+ int parameterIndex = 1;
+
+ // Act
+ ParameterExpressionFingerprint fingerprint1 = new ParameterExpressionFingerprint(nodeType, type, parameterIndex);
+ ParameterExpressionFingerprint fingerprint2 = new ParameterExpressionFingerprint(nodeType, type, -1 /* parameterIndex */);
+
+ // Assert
+ Assert.NotEqual(fingerprint1, fingerprint2);
+ }
+
+ [Fact]
+ public void Comparison_Inequality_Type()
+ {
+ // Arrange
+ ExpressionType nodeType = ExpressionType.Parameter;
+ Type type = typeof(object);
+ int parameterIndex = 1;
+
+ // Act
+ ParameterExpressionFingerprint fingerprint1 = new ParameterExpressionFingerprint(nodeType, type, parameterIndex);
+ ParameterExpressionFingerprint fingerprint2 = new ParameterExpressionFingerprint(nodeType, typeof(string), parameterIndex);
+
+ // Assert
+ Assert.NotEqual(fingerprint1, fingerprint2);
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/ExpressionUtil/Test/TypeBinaryExpressionFingerprintTest.cs b/test/System.Web.Mvc.Test/ExpressionUtil/Test/TypeBinaryExpressionFingerprintTest.cs
new file mode 100644
index 00000000..024d3fa2
--- /dev/null
+++ b/test/System.Web.Mvc.Test/ExpressionUtil/Test/TypeBinaryExpressionFingerprintTest.cs
@@ -0,0 +1,90 @@
+using System.Linq.Expressions;
+using Xunit;
+
+namespace System.Web.Mvc.ExpressionUtil.Test
+{
+ public class TypeBinaryExpressionFingerprintTest
+ {
+ [Fact]
+ public void Properties()
+ {
+ // Arrange
+ ExpressionType expectedNodeType = ExpressionType.TypeIs;
+ Type expectedType = typeof(bool);
+ Type expectedTypeOperand = typeof(object);
+
+ // Act
+ TypeBinaryExpressionFingerprint fingerprint = new TypeBinaryExpressionFingerprint(expectedNodeType, expectedType, expectedTypeOperand);
+
+ // Assert
+ Assert.Equal(expectedNodeType, fingerprint.NodeType);
+ Assert.Equal(expectedType, fingerprint.Type);
+ Assert.Equal(expectedTypeOperand, fingerprint.TypeOperand);
+ }
+
+ [Fact]
+ public void Comparison_Equality()
+ {
+ // Arrange
+ ExpressionType nodeType = ExpressionType.TypeIs;
+ Type type = typeof(bool);
+ Type typeOperand = typeof(object);
+
+ // Act
+ TypeBinaryExpressionFingerprint fingerprint1 = new TypeBinaryExpressionFingerprint(nodeType, type, typeOperand);
+ TypeBinaryExpressionFingerprint fingerprint2 = new TypeBinaryExpressionFingerprint(nodeType, type, typeOperand);
+
+ // Assert
+ Assert.Equal(fingerprint1, fingerprint2);
+ Assert.Equal(fingerprint1.GetHashCode(), fingerprint2.GetHashCode());
+ }
+
+ [Fact]
+ public void Comparison_Inequality_FingerprintType()
+ {
+ // Arrange
+ ExpressionType nodeType = ExpressionType.TypeIs;
+ Type type = typeof(bool);
+ Type typeOperand = typeof(object);
+
+ // Act
+ TypeBinaryExpressionFingerprint fingerprint1 = new TypeBinaryExpressionFingerprint(nodeType, type, typeOperand);
+ DummyExpressionFingerprint fingerprint2 = new DummyExpressionFingerprint(nodeType, type);
+
+ // Assert
+ Assert.NotEqual<ExpressionFingerprint>(fingerprint1, fingerprint2);
+ }
+
+ [Fact]
+ public void Comparison_Inequality_TypeOperand()
+ {
+ // Arrange
+ ExpressionType nodeType = ExpressionType.TypeIs;
+ Type type = typeof(bool);
+ Type typeOperand = typeof(object);
+
+ // Act
+ TypeBinaryExpressionFingerprint fingerprint1 = new TypeBinaryExpressionFingerprint(nodeType, type, typeOperand);
+ TypeBinaryExpressionFingerprint fingerprint2 = new TypeBinaryExpressionFingerprint(nodeType, type, typeof(string) /* typeOperand */);
+
+ // Assert
+ Assert.NotEqual(fingerprint1, fingerprint2);
+ }
+
+ [Fact]
+ public void Comparison_Inequality_Type()
+ {
+ // Arrange
+ ExpressionType nodeType = ExpressionType.TypeIs;
+ Type type = typeof(bool);
+ Type typeOperand = typeof(object);
+
+ // Act
+ TypeBinaryExpressionFingerprint fingerprint1 = new TypeBinaryExpressionFingerprint(nodeType, type, typeOperand);
+ TypeBinaryExpressionFingerprint fingerprint2 = new TypeBinaryExpressionFingerprint(nodeType, typeof(object), typeOperand);
+
+ // Assert
+ Assert.NotEqual(fingerprint1, fingerprint2);
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/ExpressionUtil/Test/UnaryExpressionFingerprintTest.cs b/test/System.Web.Mvc.Test/ExpressionUtil/Test/UnaryExpressionFingerprintTest.cs
new file mode 100644
index 00000000..de6dc578
--- /dev/null
+++ b/test/System.Web.Mvc.Test/ExpressionUtil/Test/UnaryExpressionFingerprintTest.cs
@@ -0,0 +1,91 @@
+using System.Linq.Expressions;
+using System.Reflection;
+using Xunit;
+
+namespace System.Web.Mvc.ExpressionUtil.Test
+{
+ public class UnaryExpressionFingerprintTest
+ {
+ [Fact]
+ public void Properties()
+ {
+ // Arrange
+ ExpressionType expectedNodeType = ExpressionType.Not;
+ Type expectedType = typeof(int);
+ MethodInfo expectedMethod = typeof(object).GetMethod("GetHashCode");
+
+ // Act
+ UnaryExpressionFingerprint fingerprint = new UnaryExpressionFingerprint(expectedNodeType, expectedType, expectedMethod);
+
+ // Assert
+ Assert.Equal(expectedNodeType, fingerprint.NodeType);
+ Assert.Equal(expectedType, fingerprint.Type);
+ Assert.Equal(expectedMethod, fingerprint.Method);
+ }
+
+ [Fact]
+ public void Comparison_Equality()
+ {
+ // Arrange
+ ExpressionType nodeType = ExpressionType.Not;
+ Type type = typeof(int);
+ MethodInfo method = typeof(object).GetMethod("GetHashCode");
+
+ // Act
+ UnaryExpressionFingerprint fingerprint1 = new UnaryExpressionFingerprint(nodeType, type, method);
+ UnaryExpressionFingerprint fingerprint2 = new UnaryExpressionFingerprint(nodeType, type, method);
+
+ // Assert
+ Assert.Equal(fingerprint1, fingerprint2);
+ Assert.Equal(fingerprint1.GetHashCode(), fingerprint2.GetHashCode());
+ }
+
+ [Fact]
+ public void Comparison_Inequality_FingerprintType()
+ {
+ // Arrange
+ ExpressionType nodeType = ExpressionType.Not;
+ Type type = typeof(int);
+ MethodInfo method = typeof(object).GetMethod("GetHashCode");
+
+ // Act
+ UnaryExpressionFingerprint fingerprint1 = new UnaryExpressionFingerprint(nodeType, type, method);
+ DummyExpressionFingerprint fingerprint2 = new DummyExpressionFingerprint(nodeType, type);
+
+ // Assert
+ Assert.NotEqual<ExpressionFingerprint>(fingerprint1, fingerprint2);
+ }
+
+ [Fact]
+ public void Comparison_Inequality_Method()
+ {
+ // Arrange
+ ExpressionType nodeType = ExpressionType.Not;
+ Type type = typeof(int);
+ MethodInfo method = typeof(object).GetMethod("GetHashCode");
+
+ // Act
+ UnaryExpressionFingerprint fingerprint1 = new UnaryExpressionFingerprint(nodeType, type, method);
+ UnaryExpressionFingerprint fingerprint2 = new UnaryExpressionFingerprint(nodeType, type, null /* method */);
+
+ // Assert
+ Assert.NotEqual(fingerprint1, fingerprint2);
+ }
+
+ [Fact]
+ public void Comparison_Inequality_Type()
+ {
+ // Arrange
+ ExpressionType nodeType = ExpressionType.Not;
+ Type type = typeof(int);
+ MethodInfo method = typeof(object).GetMethod("GetHashCode");
+
+ // Act
+ UnaryExpressionFingerprint fingerprint1 = new UnaryExpressionFingerprint(nodeType, type, method);
+ UnaryExpressionFingerprint fingerprint2 = new UnaryExpressionFingerprint(nodeType, typeof(object), method);
+
+ // Assert
+ Assert.NotEqual(fingerprint1, fingerprint2);
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Html/Test/ChildActionExtensionsTest.cs b/test/System.Web.Mvc.Test/Html/Test/ChildActionExtensionsTest.cs
new file mode 100644
index 00000000..c800418e
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Html/Test/ChildActionExtensionsTest.cs
@@ -0,0 +1,256 @@
+using System.IO;
+using System.Web.Mvc.Properties;
+using System.Web.Routing;
+using Moq;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Mvc.Html.Test
+{
+ public class ChildActionExtensionsTest
+ {
+ Mock<HtmlHelper> htmlHelper;
+ Mock<HttpContextBase> httpContext;
+ Mock<RouteBase> route;
+ Mock<IViewDataContainer> viewDataContainer;
+
+ RouteData originalRouteData;
+ RouteCollection routes;
+ ViewContext viewContext;
+ VirtualPathData virtualPathData;
+
+ public ChildActionExtensionsTest()
+ {
+ route = new Mock<RouteBase>();
+ route.Setup(r => r.GetVirtualPath(It.IsAny<RequestContext>(), It.IsAny<RouteValueDictionary>()))
+ .Returns(() => virtualPathData);
+
+ virtualPathData = new VirtualPathData(route.Object, "~/VirtualPath");
+
+ routes = new RouteCollection();
+ routes.Add(route.Object);
+
+ originalRouteData = new RouteData();
+
+ string returnValue = "";
+ httpContext = new Mock<HttpContextBase>();
+ httpContext.Setup(hc => hc.Request.ApplicationPath).Returns("~");
+ httpContext.Setup(hc => hc.Response.ApplyAppPathModifier(It.IsAny<string>()))
+ .Callback<string>(s => returnValue = s)
+ .Returns(() => returnValue);
+ httpContext.Setup(hc => hc.Server.Execute(It.IsAny<IHttpHandler>(), It.IsAny<TextWriter>(), It.IsAny<bool>()));
+
+ viewContext = new ViewContext
+ {
+ RequestContext = new RequestContext(httpContext.Object, originalRouteData)
+ };
+
+ viewDataContainer = new Mock<IViewDataContainer>();
+
+ htmlHelper = new Mock<HtmlHelper>(viewContext, viewDataContainer.Object, routes);
+ }
+
+ [Fact]
+ public void GuardClauses()
+ {
+ // Act & Assert
+ Assert.ThrowsArgumentNull(
+ () => ChildActionExtensions.ActionHelper(null /* htmlHelper */, "abc", null /* controllerName */, null /* routeValues */, null /* textWriter */),
+ "htmlHelper"
+ );
+
+ Assert.ThrowsArgumentNullOrEmpty(
+ () => ChildActionExtensions.ActionHelper(htmlHelper.Object, null /* actionName */, null /* controllerName */, null /* routeValues */, null /* textWriter */),
+ "actionName"
+ );
+
+ Assert.ThrowsArgumentNullOrEmpty(
+ () => ChildActionExtensions.ActionHelper(htmlHelper.Object, String.Empty /* actionName */, null /* controllerName */, null /* routeValues */, null /* textWriter */),
+ "actionName"
+ );
+ }
+
+ [Fact]
+ public void ServerExecuteCalledWithWrappedChildActionMvcHandler()
+ {
+ // Arrange
+ IHttpHandler callbackHandler = null;
+ TextWriter callbackTextWriter = null;
+ bool callbackPreserveForm = false;
+ httpContext.Setup(hc => hc.Server.Execute(It.IsAny<IHttpHandler>(), It.IsAny<TextWriter>(), It.IsAny<bool>()))
+ .Callback<IHttpHandler, TextWriter, bool>(
+ (handler, textWriter, preserveForm) =>
+ {
+ callbackHandler = handler;
+ callbackTextWriter = textWriter;
+ callbackPreserveForm = preserveForm;
+ });
+ TextWriter stringWriter = new StringWriter();
+
+ // Act
+ ChildActionExtensions.ActionHelper(htmlHelper.Object, "actionName", null /* controllerName */, null /* routeValues */, stringWriter);
+
+ // Assert
+ Assert.NotNull(callbackHandler);
+ HttpHandlerUtil.ServerExecuteHttpHandlerWrapper wrapper = callbackHandler as HttpHandlerUtil.ServerExecuteHttpHandlerWrapper;
+ Assert.NotNull(wrapper);
+ Assert.NotNull(wrapper.InnerHandler);
+ ChildActionExtensions.ChildActionMvcHandler childHandler = wrapper.InnerHandler as ChildActionExtensions.ChildActionMvcHandler;
+ Assert.NotNull(childHandler);
+ Assert.Same(stringWriter, callbackTextWriter);
+ Assert.True(callbackPreserveForm);
+ }
+
+ [Fact]
+ public void RouteDataTokensIncludesParentActionViewContext()
+ {
+ // Arrange
+ MvcHandler mvcHandler = null;
+ httpContext.Setup(hc => hc.Server.Execute(It.IsAny<IHttpHandler>(), It.IsAny<TextWriter>(), It.IsAny<bool>()))
+ .Callback<IHttpHandler, TextWriter, bool>((handler, _, __) => mvcHandler = (MvcHandler)((HttpHandlerUtil.ServerExecuteHttpHandlerWrapper)handler).InnerHandler);
+
+ // Act
+ ChildActionExtensions.ActionHelper(htmlHelper.Object, "actionName", null /* controllerName */, null /* routeValues */, null /* textWriter */);
+
+ // Assert
+ Assert.Same(viewContext, mvcHandler.RequestContext.RouteData.DataTokens[ControllerContext.ParentActionViewContextToken]);
+ }
+
+ [Fact]
+ public void RouteValuesIncludeNewActionName()
+ {
+ // Arrange
+ MvcHandler mvcHandler = null;
+ httpContext.Setup(hc => hc.Server.Execute(It.IsAny<IHttpHandler>(), It.IsAny<TextWriter>(), It.IsAny<bool>()))
+ .Callback<IHttpHandler, TextWriter, bool>((handler, _, __) => mvcHandler = (MvcHandler)((HttpHandlerUtil.ServerExecuteHttpHandlerWrapper)handler).InnerHandler);
+
+ // Act
+ ChildActionExtensions.ActionHelper(htmlHelper.Object, "actionName", null /* controllerName */, null /* routeValues */, null /* textWriter */);
+
+ // Assert
+ RouteData routeData = mvcHandler.RequestContext.RouteData;
+ Assert.Equal("actionName", routeData.Values["action"]);
+ }
+
+ [Fact]
+ public void RouteValuesIncludeOldControllerNameWhenControllerNameIsNullOrEmpty()
+ {
+ // Arrange
+ originalRouteData.Values["controller"] = "oldController";
+ MvcHandler mvcHandler = null;
+ httpContext.Setup(hc => hc.Server.Execute(It.IsAny<IHttpHandler>(), It.IsAny<TextWriter>(), It.IsAny<bool>()))
+ .Callback<IHttpHandler, TextWriter, bool>((handler, _, __) => mvcHandler = (MvcHandler)((HttpHandlerUtil.ServerExecuteHttpHandlerWrapper)handler).InnerHandler);
+
+ // Act
+ ChildActionExtensions.ActionHelper(htmlHelper.Object, "actionName", null /* controllerName */, null /* routeValues */, null /* textWriter */);
+
+ // Assert
+ RouteData routeData = mvcHandler.RequestContext.RouteData;
+ Assert.Equal("oldController", routeData.Values["controller"]);
+ }
+
+ [Fact]
+ public void RouteValuesIncludeNewControllerNameWhenControllNameIsNotEmpty()
+ {
+ // Arrange
+ originalRouteData.Values["controller"] = "oldController";
+ MvcHandler mvcHandler = null;
+ httpContext.Setup(hc => hc.Server.Execute(It.IsAny<IHttpHandler>(), It.IsAny<TextWriter>(), It.IsAny<bool>()))
+ .Callback<IHttpHandler, TextWriter, bool>((handler, _, __) => mvcHandler = (MvcHandler)((HttpHandlerUtil.ServerExecuteHttpHandlerWrapper)handler).InnerHandler);
+
+ // Act
+ ChildActionExtensions.ActionHelper(htmlHelper.Object, "actionName", "newController", null /* routeValues */, null /* textWriter */);
+
+ // Assert
+ RouteData routeData = mvcHandler.RequestContext.RouteData;
+ Assert.Equal("newController", routeData.Values["controller"]);
+ }
+
+ [Fact]
+ public void PassedRouteValuesOverrideParentRequestRouteValues()
+ {
+ // Arrange
+ originalRouteData.Values["name1"] = "value1";
+ originalRouteData.Values["name2"] = "value2";
+ MvcHandler mvcHandler = null;
+ httpContext.Setup(hc => hc.Server.Execute(It.IsAny<IHttpHandler>(), It.IsAny<TextWriter>(), It.IsAny<bool>()))
+ .Callback<IHttpHandler, TextWriter, bool>((handler, _, __) => mvcHandler = (MvcHandler)((HttpHandlerUtil.ServerExecuteHttpHandlerWrapper)handler).InnerHandler);
+
+ // Act
+ ChildActionExtensions.ActionHelper(htmlHelper.Object, "actionName", null /* controllerName */, new RouteValueDictionary { { "name2", "newValue2" } }, null /* textWriter */);
+
+ // Assert
+ RouteData routeData = mvcHandler.RequestContext.RouteData;
+ Assert.Equal("value1", routeData.Values["name1"]);
+ Assert.Equal("newValue2", routeData.Values["name2"]);
+
+ Assert.Equal("newValue2", (routeData.Values[ChildActionValueProvider.ChildActionValuesKey] as DictionaryValueProvider<object>).GetValue("name2").RawValue);
+ }
+
+ [Fact]
+ public void NoChildActionValuesDictionaryCreatedIfNoRouteValuesPassed()
+ {
+ // Arrange
+ MvcHandler mvcHandler = null;
+ httpContext.Setup(hc => hc.Server.Execute(It.IsAny<IHttpHandler>(), It.IsAny<TextWriter>(), It.IsAny<bool>()))
+ .Callback<IHttpHandler, TextWriter, bool>((handler, _, __) => mvcHandler = (MvcHandler)((HttpHandlerUtil.ServerExecuteHttpHandlerWrapper)handler).InnerHandler);
+
+ // Act
+ ChildActionExtensions.ActionHelper(htmlHelper.Object, "actionName", null /* controllerName */, null, null /* textWriter */);
+
+ // Assert
+ RouteData routeData = mvcHandler.RequestContext.RouteData;
+ Assert.Null(routeData.Values[ChildActionValueProvider.ChildActionValuesKey]);
+ }
+
+ [Fact]
+ public void RouteValuesDoesNotIncludeExplicitlyPassedAreaName()
+ {
+ // Arrange
+ Route route = routes.MapRoute("my-area", "my-area");
+ route.DataTokens["area"] = "myArea";
+ MvcHandler mvcHandler = null;
+ httpContext.Setup(hc => hc.Server.Execute(It.IsAny<IHttpHandler>(), It.IsAny<TextWriter>(), It.IsAny<bool>()))
+ .Callback<IHttpHandler, TextWriter, bool>((handler, _, __) => mvcHandler = (MvcHandler)((HttpHandlerUtil.ServerExecuteHttpHandlerWrapper)handler).InnerHandler);
+
+ // Act
+ ChildActionExtensions.ActionHelper(htmlHelper.Object, "actionName", null /* controllerName */, new RouteValueDictionary { { "area", "myArea" } }, null /* textWriter */);
+
+ // Assert
+ RouteData routeData = mvcHandler.RequestContext.RouteData;
+ Assert.False(routeData.Values.ContainsKey("area"));
+ Assert.Null((routeData.Values[ChildActionValueProvider.ChildActionValuesKey] as DictionaryValueProvider<object>).GetValue("area"));
+ }
+
+ [Fact]
+ public void RouteValuesIncludeExplicitlyPassedAreaNameIfAreasNotInUse()
+ {
+ // Arrange
+ Route route = routes.MapRoute("my-area", "my-area");
+ MvcHandler mvcHandler = null;
+ httpContext.Setup(hc => hc.Server.Execute(It.IsAny<IHttpHandler>(), It.IsAny<TextWriter>(), It.IsAny<bool>()))
+ .Callback<IHttpHandler, TextWriter, bool>((handler, _, __) => mvcHandler = (MvcHandler)((HttpHandlerUtil.ServerExecuteHttpHandlerWrapper)handler).InnerHandler);
+
+ // Act
+ ChildActionExtensions.ActionHelper(htmlHelper.Object, "actionName", null /* controllerName */, new RouteValueDictionary { { "area", "myArea" } }, null /* textWriter */);
+
+ // Assert
+ RouteData routeData = mvcHandler.RequestContext.RouteData;
+ Assert.True(routeData.Values.ContainsKey("area"));
+ Assert.Equal("myArea", (routeData.Values[ChildActionValueProvider.ChildActionValuesKey] as DictionaryValueProvider<object>).GetValue("area").RawValue);
+ }
+
+ [Fact]
+ public void NoMatchingRouteThrows()
+ {
+ // Arrange
+ routes.Clear();
+
+ // Act & Assert
+ Assert.Throws<InvalidOperationException>(
+ () => ChildActionExtensions.ActionHelper(htmlHelper.Object, "actionName", null /* controllerName */, null /* routeValues */, null /* textWriter */),
+ MvcResources.Common_NoRouteMatched
+ );
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Html/Test/DefaultDisplayTemplatesTest.cs b/test/System.Web.Mvc.Test/Html/Test/DefaultDisplayTemplatesTest.cs
new file mode 100644
index 00000000..3d091c0e
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Html/Test/DefaultDisplayTemplatesTest.cs
@@ -0,0 +1,560 @@
+using System.Collections;
+using System.Collections.Generic;
+using System.Data.Objects.DataClasses;
+using System.IO;
+using System.Web.UI.WebControls;
+using Microsoft.Web.UnitTestUtil;
+using Moq;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Mvc.Html.Test
+{
+ public class DefaultDisplayTemplatesTest
+ {
+ // BooleanTemplate
+
+ [Fact]
+ public void BooleanTemplateTests()
+ {
+ // Boolean values
+
+ Assert.Equal(
+ @"<input checked=""checked"" class=""check-box"" disabled=""disabled"" type=""checkbox"" />",
+ DefaultDisplayTemplates.BooleanTemplate(MakeHtmlHelper<bool>(true)));
+
+ Assert.Equal(
+ @"<input class=""check-box"" disabled=""disabled"" type=""checkbox"" />",
+ DefaultDisplayTemplates.BooleanTemplate(MakeHtmlHelper<bool>(false)));
+
+ Assert.Equal(
+ @"<input class=""check-box"" disabled=""disabled"" type=""checkbox"" />",
+ DefaultDisplayTemplates.BooleanTemplate(MakeHtmlHelper<bool>(null)));
+
+ // Nullable<Boolean> values
+
+ Assert.Equal(
+ @"<select class=""tri-state list-box"" disabled=""disabled""><option value="""">Not Set</option><option selected=""selected"" value=""true"">True</option><option value=""false"">False</option></select>",
+ DefaultDisplayTemplates.BooleanTemplate(MakeHtmlHelper<Nullable<bool>>(true)));
+
+ Assert.Equal(
+ @"<select class=""tri-state list-box"" disabled=""disabled""><option value="""">Not Set</option><option value=""true"">True</option><option selected=""selected"" value=""false"">False</option></select>",
+ DefaultDisplayTemplates.BooleanTemplate(MakeHtmlHelper<Nullable<bool>>(false)));
+
+ Assert.Equal(
+ @"<select class=""tri-state list-box"" disabled=""disabled""><option selected=""selected"" value="""">Not Set</option><option value=""true"">True</option><option value=""false"">False</option></select>",
+ DefaultDisplayTemplates.BooleanTemplate(MakeHtmlHelper<Nullable<bool>>(null)));
+ }
+
+ // CollectionTemplate
+
+ private static string CollectionSpyCallback(HtmlHelper html, ModelMetadata metadata, string htmlFieldName, string templateName, DataBoundControlMode mode, object additionalViewData)
+ {
+ return String.Format(Environment.NewLine + "Model = {0}, ModelType = {1}, PropertyName = {2}, HtmlFieldName = {3}, TemplateName = {4}, Mode = {5}, TemplateInfo.HtmlFieldPrefix = {6}, AdditionalViewData = {7}",
+ metadata.Model ?? "(null)",
+ metadata.ModelType == null ? "(null)" : metadata.ModelType.FullName,
+ metadata.PropertyName ?? "(null)",
+ htmlFieldName == String.Empty ? "(empty)" : htmlFieldName ?? "(null)",
+ templateName ?? "(null)",
+ mode,
+ html.ViewContext.ViewData.TemplateInfo.HtmlFieldPrefix,
+ AnonymousObject.Inspect(additionalViewData));
+ }
+
+ [Fact]
+ public void CollectionTemplateWithNullModel()
+ {
+ // Arrange
+ HtmlHelper html = MakeHtmlHelper<object>(null);
+
+ // Act
+ string result = DefaultDisplayTemplates.CollectionTemplate(html, CollectionSpyCallback);
+
+ // Assert
+ Assert.Equal(String.Empty, result);
+ }
+
+ [Fact]
+ public void CollectionTemplateNonEnumerableModelThrows()
+ {
+ // Arrange
+ HtmlHelper html = MakeHtmlHelper<object>(new object());
+
+ // Act & Assert
+ Assert.Throws<InvalidOperationException>(
+ () => DefaultDisplayTemplates.CollectionTemplate(html, CollectionSpyCallback),
+ "The Collection template was used with an object of type 'System.Object', which does not implement System.IEnumerable."
+ );
+ }
+
+ [Fact]
+ public void CollectionTemplateWithSingleItemCollectionWithoutPrefix()
+ {
+ // Arrange
+ HtmlHelper html = MakeHtmlHelper<List<string>>(new List<string> { "foo" });
+ html.ViewContext.ViewData.TemplateInfo.HtmlFieldPrefix = null;
+
+ // Act
+ string result = DefaultDisplayTemplates.CollectionTemplate(html, CollectionSpyCallback);
+
+ // Assert
+ Assert.Equal(@"
+Model = foo, ModelType = System.String, PropertyName = (null), HtmlFieldName = [0], TemplateName = (null), Mode = ReadOnly, TemplateInfo.HtmlFieldPrefix = , AdditionalViewData = (null)",
+ result);
+ }
+
+ [Fact]
+ public void CollectionTemplateWithSingleItemCollectionWithPrefix()
+ {
+ // Arrange
+ HtmlHelper html = MakeHtmlHelper<List<string>>(new List<string> { "foo" });
+ html.ViewContext.ViewData.TemplateInfo.HtmlFieldPrefix = "ModelProperty";
+
+ // Act
+ string result = DefaultDisplayTemplates.CollectionTemplate(html, CollectionSpyCallback);
+
+ // Assert
+ Assert.Equal(@"
+Model = foo, ModelType = System.String, PropertyName = (null), HtmlFieldName = ModelProperty[0], TemplateName = (null), Mode = ReadOnly, TemplateInfo.HtmlFieldPrefix = , AdditionalViewData = (null)",
+ result);
+ }
+
+ [Fact]
+ public void CollectionTemplateWithMultiItemCollection()
+ {
+ // Arrange
+ HtmlHelper html = MakeHtmlHelper<List<string>>(new List<string> { "foo", "bar", "baz" });
+ html.ViewContext.ViewData.TemplateInfo.HtmlFieldPrefix = null;
+
+ // Act
+ string result = DefaultDisplayTemplates.CollectionTemplate(html, CollectionSpyCallback);
+
+ // Assert
+ Assert.Equal(@"
+Model = foo, ModelType = System.String, PropertyName = (null), HtmlFieldName = [0], TemplateName = (null), Mode = ReadOnly, TemplateInfo.HtmlFieldPrefix = , AdditionalViewData = (null)
+Model = bar, ModelType = System.String, PropertyName = (null), HtmlFieldName = [1], TemplateName = (null), Mode = ReadOnly, TemplateInfo.HtmlFieldPrefix = , AdditionalViewData = (null)
+Model = baz, ModelType = System.String, PropertyName = (null), HtmlFieldName = [2], TemplateName = (null), Mode = ReadOnly, TemplateInfo.HtmlFieldPrefix = , AdditionalViewData = (null)",
+ result);
+ }
+
+ [Fact]
+ public void CollectionTemplateNullITemInWeaklyTypedCollectionUsesModelTypeOfString()
+ {
+ // Arrange
+ HtmlHelper html = MakeHtmlHelper<ArrayList>(new ArrayList { null });
+ html.ViewContext.ViewData.TemplateInfo.HtmlFieldPrefix = null;
+
+ // Act
+ string result = DefaultDisplayTemplates.CollectionTemplate(html, CollectionSpyCallback);
+
+ // Assert
+ Assert.Equal(@"
+Model = (null), ModelType = System.String, PropertyName = (null), HtmlFieldName = [0], TemplateName = (null), Mode = ReadOnly, TemplateInfo.HtmlFieldPrefix = , AdditionalViewData = (null)",
+ result);
+ }
+
+ [Fact]
+ public void CollectionTemplateNullItemInStronglyTypedCollectionUsesModelTypeFromIEnumerable()
+ {
+ // Arrange
+ HtmlHelper html = MakeHtmlHelper<List<IHttpHandler>>(new List<IHttpHandler> { null });
+ html.ViewContext.ViewData.TemplateInfo.HtmlFieldPrefix = null;
+
+ // Act
+ string result = DefaultDisplayTemplates.CollectionTemplate(html, CollectionSpyCallback);
+
+ // Assert
+ Assert.Equal(@"
+Model = (null), ModelType = System.Web.IHttpHandler, PropertyName = (null), HtmlFieldName = [0], TemplateName = (null), Mode = ReadOnly, TemplateInfo.HtmlFieldPrefix = , AdditionalViewData = (null)",
+ result);
+ }
+
+ [Fact]
+ public void CollectionTemplateUsesRealObjectTypes()
+ {
+ // Arrange
+ HtmlHelper html = MakeHtmlHelper<List<Object>>(new List<Object> { 1, 2.3, "Hello World" });
+ html.ViewContext.ViewData.TemplateInfo.HtmlFieldPrefix = null;
+
+ // Act
+ string result = DefaultDisplayTemplates.CollectionTemplate(html, CollectionSpyCallback);
+
+ // Assert
+ Assert.Equal(@"
+Model = 1, ModelType = System.Int32, PropertyName = (null), HtmlFieldName = [0], TemplateName = (null), Mode = ReadOnly, TemplateInfo.HtmlFieldPrefix = , AdditionalViewData = (null)
+Model = 2.3, ModelType = System.Double, PropertyName = (null), HtmlFieldName = [1], TemplateName = (null), Mode = ReadOnly, TemplateInfo.HtmlFieldPrefix = , AdditionalViewData = (null)
+Model = Hello World, ModelType = System.String, PropertyName = (null), HtmlFieldName = [2], TemplateName = (null), Mode = ReadOnly, TemplateInfo.HtmlFieldPrefix = , AdditionalViewData = (null)",
+ result);
+ }
+
+ [Fact]
+ public void CollectionTemplateNullItemInCollectionOfNullableValueTypesDoesNotDiscardNullable()
+ {
+ // Arrange
+ HtmlHelper html = MakeHtmlHelper<List<int?>>(new List<int?> { 1, null, 2 });
+ html.ViewContext.ViewData.TemplateInfo.HtmlFieldPrefix = null;
+
+ // Act
+ string result = DefaultDisplayTemplates.CollectionTemplate(html, CollectionSpyCallback);
+
+ // Assert
+ Assert.Equal(@"
+Model = 1, ModelType = System.Nullable`1[[System.Int32, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]], PropertyName = (null), HtmlFieldName = [0], TemplateName = (null), Mode = ReadOnly, TemplateInfo.HtmlFieldPrefix = , AdditionalViewData = (null)
+Model = (null), ModelType = System.Nullable`1[[System.Int32, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]], PropertyName = (null), HtmlFieldName = [1], TemplateName = (null), Mode = ReadOnly, TemplateInfo.HtmlFieldPrefix = , AdditionalViewData = (null)
+Model = 2, ModelType = System.Nullable`1[[System.Int32, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]], PropertyName = (null), HtmlFieldName = [2], TemplateName = (null), Mode = ReadOnly, TemplateInfo.HtmlFieldPrefix = , AdditionalViewData = (null)",
+ result);
+ }
+
+ // DecimalTemplate
+
+ [Fact]
+ public void DecimalTemplateTests()
+ {
+ Assert.Equal(
+ @"12.35",
+ DefaultDisplayTemplates.DecimalTemplate(MakeHtmlHelper<decimal>(12.3456M)));
+
+ Assert.Equal(
+ @"Formatted Value",
+ DefaultDisplayTemplates.DecimalTemplate(MakeHtmlHelper<decimal>(12.3456M, "Formatted Value")));
+
+ Assert.Equal(
+ @"&lt;script&gt;alert(&#39;XSS!&#39;)&lt;/script&gt;",
+ DefaultDisplayTemplates.DecimalTemplate(MakeHtmlHelper<decimal>(12.3456M, "<script>alert('XSS!')</script>")));
+ }
+
+ // EmailAddressTemplate
+
+ [Fact]
+ public void EmailAddressTemplateTests()
+ {
+ Assert.Equal(
+ @"<a href=""mailto:foo@bar.com"">foo@bar.com</a>",
+ DefaultDisplayTemplates.EmailAddressTemplate(MakeHtmlHelper<string>("foo@bar.com")));
+
+ Assert.Equal(
+ @"<a href=""mailto:foo@bar.com"">The FooBar User</a>",
+ DefaultDisplayTemplates.EmailAddressTemplate(MakeHtmlHelper<string>("foo@bar.com", "The FooBar User")));
+
+ Assert.Equal(
+ @"<a href=""mailto:&lt;script>alert(&#39;XSS!&#39;)&lt;/script>"">&lt;script&gt;alert(&#39;XSS!&#39;)&lt;/script&gt;</a>",
+ DefaultDisplayTemplates.EmailAddressTemplate(MakeHtmlHelper<string>("<script>alert('XSS!')</script>")));
+
+ Assert.Equal(
+ @"<a href=""mailto:&lt;script>alert(&#39;XSS!&#39;)&lt;/script>"">&lt;b&gt;Encode me!&lt;/b&gt;</a>",
+ DefaultDisplayTemplates.EmailAddressTemplate(MakeHtmlHelper<string>("<script>alert('XSS!')</script>", "<b>Encode me!</b>")));
+ }
+
+ // HiddenInputTemplate
+
+ [Fact]
+ public void HiddenInputTemplateTests()
+ {
+ Assert.Equal(
+ @"Hidden Value",
+ DefaultDisplayTemplates.HiddenInputTemplate(MakeHtmlHelper<string>("Hidden Value")));
+
+ Assert.Equal(
+ @"&lt;b&gt;Encode me!&lt;/b&gt;",
+ DefaultDisplayTemplates.HiddenInputTemplate(MakeHtmlHelper<string>("<script>alert('XSS!')</script>", "<b>Encode me!</b>")));
+
+ var helperWithInvisibleHtml = MakeHtmlHelper<string>("<script>alert('XSS!')</script>", "<b>Encode me!</b>");
+ helperWithInvisibleHtml.ViewData.ModelMetadata.HideSurroundingHtml = true;
+ Assert.Equal(
+ String.Empty,
+ DefaultDisplayTemplates.HiddenInputTemplate(helperWithInvisibleHtml));
+ }
+
+ // HtmlTemplate
+
+ [Fact]
+ public void HtmlTemplateTests()
+ {
+ Assert.Equal(
+ @"Hello, world!",
+ DefaultDisplayTemplates.HtmlTemplate(MakeHtmlHelper<string>("", "Hello, world!")));
+
+ Assert.Equal(
+ @"<b>Hello, world!</b>",
+ DefaultDisplayTemplates.HtmlTemplate(MakeHtmlHelper<string>("", "<b>Hello, world!</b>")));
+ }
+
+ // ObjectTemplate
+
+ private static string SpyCallback(HtmlHelper html, ModelMetadata metadata, string htmlFieldName, string templateName, DataBoundControlMode mode, object additionalViewData)
+ {
+ return String.Format("Model = {0}, ModelType = {1}, PropertyName = {2}, HtmlFieldName = {3}, TemplateName = {4}, Mode = {5}, AdditionalViewData = {6}",
+ metadata.Model ?? "(null)",
+ metadata.ModelType == null ? "(null)" : metadata.ModelType.FullName,
+ metadata.PropertyName ?? "(null)",
+ htmlFieldName == String.Empty ? "(empty)" : htmlFieldName ?? "(null)",
+ templateName ?? "(null)",
+ mode,
+ AnonymousObject.Inspect(additionalViewData));
+ }
+
+ class ObjectTemplateModel
+ {
+ public ObjectTemplateModel()
+ {
+ ComplexInnerModel = new object();
+ }
+
+ public string Property1 { get; set; }
+ public string Property2 { get; set; }
+ public object ComplexInnerModel { get; set; }
+ }
+
+ [Fact]
+ public void ObjectTemplateDisplaysSimplePropertiesOnObjectByDefault()
+ {
+ string expected = @"<div class=""display-label"">Property1</div>
+<div class=""display-field"">Model = p1, ModelType = System.String, PropertyName = Property1, HtmlFieldName = Property1, TemplateName = (null), Mode = ReadOnly, AdditionalViewData = (null)</div>
+<div class=""display-label"">Property2</div>
+<div class=""display-field"">Model = (null), ModelType = System.String, PropertyName = Property2, HtmlFieldName = Property2, TemplateName = (null), Mode = ReadOnly, AdditionalViewData = (null)</div>
+";
+
+ // Arrange
+ ObjectTemplateModel model = new ObjectTemplateModel { Property1 = "p1", Property2 = null };
+ HtmlHelper html = MakeHtmlHelper<ObjectTemplateModel>(model);
+
+ // Act
+ string result = DefaultDisplayTemplates.ObjectTemplate(html, SpyCallback);
+
+ // Assert
+ Assert.Equal(expected, result);
+ }
+
+ [Fact]
+ public void ObjectTemplateWithDisplayNameMetadata()
+ {
+ string expected = @"<div class=""display-field"">Model = (null), ModelType = System.String, PropertyName = Property1, HtmlFieldName = Property1, TemplateName = (null), Mode = ReadOnly, AdditionalViewData = (null)</div>
+<div class=""display-label"">Custom display name</div>
+<div class=""display-field"">Model = (null), ModelType = System.String, PropertyName = Property2, HtmlFieldName = Property2, TemplateName = (null), Mode = ReadOnly, AdditionalViewData = (null)</div>
+";
+
+ // Arrange
+ ObjectTemplateModel model = new ObjectTemplateModel();
+ HtmlHelper html = MakeHtmlHelper<ObjectTemplateModel>(model);
+ Mock<ModelMetadataProvider> provider = new Mock<ModelMetadataProvider>();
+ Func<object> accessor = () => model;
+ Mock<ModelMetadata> metadata = new Mock<ModelMetadata>(provider.Object, null, accessor, typeof(ObjectTemplateModel), null);
+ ModelMetadata prop1Metadata = new ModelMetadata(provider.Object, typeof(ObjectTemplateModel), null, typeof(string), "Property1") { DisplayName = String.Empty };
+ ModelMetadata prop2Metadata = new ModelMetadata(provider.Object, typeof(ObjectTemplateModel), null, typeof(string), "Property2") { DisplayName = "Custom display name" };
+ html.ViewData.ModelMetadata = metadata.Object;
+ metadata.Setup(p => p.Properties).Returns(() => new[] { prop1Metadata, prop2Metadata });
+
+ // Act
+ string result = DefaultDisplayTemplates.ObjectTemplate(html, SpyCallback);
+
+ // Assert
+ Assert.Equal(expected, result);
+ }
+
+ [Fact]
+ public void ObjectTemplateWithShowForDisplayMetadata()
+ {
+ string expected = @"<div class=""display-label"">Property1</div>
+<div class=""display-field"">Model = (null), ModelType = System.String, PropertyName = Property1, HtmlFieldName = Property1, TemplateName = (null), Mode = ReadOnly, AdditionalViewData = (null)</div>
+";
+
+ // Arrange
+ ObjectTemplateModel model = new ObjectTemplateModel();
+ HtmlHelper html = MakeHtmlHelper<ObjectTemplateModel>(model);
+ Mock<ModelMetadataProvider> provider = new Mock<ModelMetadataProvider>();
+ Func<object> accessor = () => model;
+ Mock<ModelMetadata> metadata = new Mock<ModelMetadata>(provider.Object, null, accessor, typeof(ObjectTemplateModel), null);
+ ModelMetadata prop1Metadata = new ModelMetadata(provider.Object, typeof(ObjectTemplateModel), null, typeof(string), "Property1") { ShowForDisplay = true };
+ ModelMetadata prop2Metadata = new ModelMetadata(provider.Object, typeof(ObjectTemplateModel), null, typeof(string), "Property2") { ShowForDisplay = false };
+ html.ViewData.ModelMetadata = metadata.Object;
+ metadata.Setup(p => p.Properties).Returns(() => new[] { prop1Metadata, prop2Metadata });
+
+ // Act
+ string result = DefaultDisplayTemplates.ObjectTemplate(html, SpyCallback);
+
+ // Assert
+ Assert.Equal(expected, result);
+ }
+
+ [Fact]
+ public void ObjectTemplatePreventsRecursionOnModelValue()
+ {
+ string expected = @"<div class=""display-label"">Property2</div>
+<div class=""display-field"">Model = propValue2, ModelType = System.String, PropertyName = Property2, HtmlFieldName = Property2, TemplateName = (null), Mode = ReadOnly, AdditionalViewData = (null)</div>
+";
+
+ // Arrange
+ ObjectTemplateModel model = new ObjectTemplateModel();
+ HtmlHelper html = MakeHtmlHelper<ObjectTemplateModel>(model);
+ Mock<ModelMetadataProvider> provider = new Mock<ModelMetadataProvider>();
+ Func<object> accessor = () => model;
+ Mock<ModelMetadata> metadata = new Mock<ModelMetadata>(provider.Object, null, accessor, typeof(ObjectTemplateModel), null);
+ ModelMetadata prop1Metadata = new ModelMetadata(provider.Object, typeof(ObjectTemplateModel), () => "propValue1", typeof(string), "Property1");
+ ModelMetadata prop2Metadata = new ModelMetadata(provider.Object, typeof(ObjectTemplateModel), () => "propValue2", typeof(string), "Property2");
+ html.ViewData.ModelMetadata = metadata.Object;
+ metadata.Setup(p => p.Properties).Returns(() => new[] { prop1Metadata, prop2Metadata });
+ html.ViewData.TemplateInfo.VisitedObjects.Add("propValue1");
+
+ // Act
+ string result = DefaultDisplayTemplates.ObjectTemplate(html, SpyCallback);
+
+ // Assert
+ Assert.Equal(expected, result);
+ }
+
+ [Fact]
+ public void ObjectTemplatePreventsRecursionOnModelTypeForNullModelValues()
+ {
+ string expected = @"<div class=""display-label"">Property2</div>
+<div class=""display-field"">Model = propValue2, ModelType = System.String, PropertyName = Property2, HtmlFieldName = Property2, TemplateName = (null), Mode = ReadOnly, AdditionalViewData = (null)</div>
+";
+
+ // Arrange
+ ObjectTemplateModel model = new ObjectTemplateModel();
+ HtmlHelper html = MakeHtmlHelper<ObjectTemplateModel>(model);
+ Mock<ModelMetadataProvider> provider = new Mock<ModelMetadataProvider>();
+ Func<object> accessor = () => model;
+ Mock<ModelMetadata> metadata = new Mock<ModelMetadata>(provider.Object, null, accessor, typeof(ObjectTemplateModel), null);
+ ModelMetadata prop1Metadata = new ModelMetadata(provider.Object, typeof(ObjectTemplateModel), null, typeof(string), "Property1");
+ ModelMetadata prop2Metadata = new ModelMetadata(provider.Object, typeof(ObjectTemplateModel), () => "propValue2", typeof(string), "Property2");
+ html.ViewData.ModelMetadata = metadata.Object;
+ metadata.Setup(p => p.Properties).Returns(() => new[] { prop1Metadata, prop2Metadata });
+ html.ViewData.TemplateInfo.VisitedObjects.Add(typeof(string));
+
+ // Act
+ string result = DefaultDisplayTemplates.ObjectTemplate(html, SpyCallback);
+
+ // Assert
+ Assert.Equal(expected, result);
+ }
+
+ [Fact]
+ public void ObjectTemplateDisplaysNullDisplayTextWhenObjectIsNull()
+ {
+ // Arrange
+ HtmlHelper html = MakeHtmlHelper<ObjectTemplateModel>(null);
+ ModelMetadata metadata = ModelMetadataProviders.Current.GetMetadataForType(null, typeof(ObjectTemplateModel));
+ metadata.NullDisplayText = "(null value)";
+ html.ViewData.ModelMetadata = metadata;
+
+ // Act
+ string result = DefaultDisplayTemplates.ObjectTemplate(html, SpyCallback);
+
+ // Assert
+ Assert.Equal(metadata.NullDisplayText, result);
+ }
+
+ [Fact]
+ public void ObjectTemplateDisplaysSimpleDisplayTextWhenTemplateDepthGreaterThanOne()
+ {
+ // Arrange
+ ObjectTemplateModel model = new ObjectTemplateModel();
+ HtmlHelper html = MakeHtmlHelper<ObjectTemplateModel>(model);
+ ModelMetadata metadata = ModelMetadataProviders.Current.GetMetadataForType(() => model, typeof(ObjectTemplateModel));
+ metadata.SimpleDisplayText = "Simple Display Text";
+ html.ViewData.ModelMetadata = metadata;
+ html.ViewData.TemplateInfo.VisitedObjects.Add("foo");
+ html.ViewData.TemplateInfo.VisitedObjects.Add("bar");
+
+ // Act
+ string result = DefaultDisplayTemplates.ObjectTemplate(html, SpyCallback);
+
+ // Assert
+ Assert.Equal(metadata.SimpleDisplayText, result);
+ }
+
+ [Fact]
+ public void ObjectTemplateWithHiddenHtml()
+ {
+ string expected = @"Model = propValue1, ModelType = System.String, PropertyName = Property1, HtmlFieldName = Property1, TemplateName = (null), Mode = ReadOnly, AdditionalViewData = (null)";
+
+ // Arrange
+ ObjectTemplateModel model = new ObjectTemplateModel();
+ HtmlHelper html = MakeHtmlHelper<ObjectTemplateModel>(model);
+ Mock<ModelMetadataProvider> provider = new Mock<ModelMetadataProvider>();
+ Func<object> accessor = () => model;
+ Mock<ModelMetadata> metadata = new Mock<ModelMetadata>(provider.Object, null, accessor, typeof(ObjectTemplateModel), null);
+ ModelMetadata prop1Metadata = new ModelMetadata(provider.Object, typeof(ObjectTemplateModel), () => "propValue1", typeof(string), "Property1") { HideSurroundingHtml = true };
+ html.ViewData.ModelMetadata = metadata.Object;
+ metadata.Setup(p => p.Properties).Returns(() => new[] { prop1Metadata });
+
+ // Act
+ string result = DefaultDisplayTemplates.ObjectTemplate(html, SpyCallback);
+
+ // Assert
+ Assert.Equal(expected, result);
+ }
+
+ [Fact]
+ public void ObjectTemplateAllPropertiesFromEntityObjectAreHidden()
+ {
+ // Arrange
+ HtmlHelper html = MakeHtmlHelper<ObjectTemplateModel>(new MyEntityObject());
+
+ // Act
+ string result = DefaultDisplayTemplates.ObjectTemplate(html, SpyCallback);
+
+ // Assert
+ Assert.Equal(String.Empty, result);
+ }
+
+ private class MyEntityObject : EntityObject
+ {
+ }
+
+ // StringTemplate
+
+ [Fact]
+ public void StringTemplateTests()
+ {
+ Assert.Equal(
+ @"Hello, world!",
+ DefaultDisplayTemplates.StringTemplate(MakeHtmlHelper<string>("", "Hello, world!")));
+
+ Assert.Equal(
+ @"&lt;b&gt;Hello, world!&lt;/b&gt;",
+ DefaultDisplayTemplates.StringTemplate(MakeHtmlHelper<string>("", "<b>Hello, world!</b>")));
+ }
+
+ // UrlTemplate
+
+ [Fact]
+ public void UrlTemplateTests()
+ {
+ Assert.Equal(
+ @"<a href=""http://www.microsoft.com/testing.aspx?value1=foo&amp;value2=bar"">http://www.microsoft.com/testing.aspx?value1=foo&amp;value2=bar</a>",
+ DefaultDisplayTemplates.UrlTemplate(MakeHtmlHelper<string>("http://www.microsoft.com/testing.aspx?value1=foo&value2=bar")));
+
+ Assert.Equal(
+ @"<a href=""http://www.microsoft.com/testing.aspx?value1=foo&amp;value2=bar"">&lt;b&gt;Microsoft!&lt;/b&gt;</a>",
+ DefaultDisplayTemplates.UrlTemplate(MakeHtmlHelper<string>("http://www.microsoft.com/testing.aspx?value1=foo&value2=bar", "<b>Microsoft!</b>")));
+ }
+
+ // Helpers
+
+ private HtmlHelper MakeHtmlHelper<TModel>(object model)
+ {
+ return MakeHtmlHelper<TModel>(model, model);
+ }
+
+ private HtmlHelper MakeHtmlHelper<TModel>(object model, object formattedModelValue)
+ {
+ ViewDataDictionary viewData = new ViewDataDictionary(model);
+ viewData.TemplateInfo.HtmlFieldPrefix = "FieldPrefix";
+ viewData.TemplateInfo.FormattedModelValue = formattedModelValue;
+ viewData.ModelMetadata = new EmptyModelMetadataProvider().GetMetadataForType(() => model, typeof(TModel));
+
+ ViewContext viewContext = new ViewContext(new ControllerContext(), new DummyView(), viewData, new TempDataDictionary(), new StringWriter());
+
+ return new HtmlHelper(viewContext, new SimpleViewDataContainer(viewData));
+ }
+
+ private class DummyView : IView
+ {
+ public void Render(ViewContext viewContext, TextWriter writer)
+ {
+ throw new NotImplementedException();
+ }
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Html/Test/DefaultEditorTemplatesTest.cs b/test/System.Web.Mvc.Test/Html/Test/DefaultEditorTemplatesTest.cs
new file mode 100644
index 00000000..25049aca
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Html/Test/DefaultEditorTemplatesTest.cs
@@ -0,0 +1,595 @@
+using System.Collections;
+using System.Collections.Generic;
+using System.Data.Linq;
+using System.Data.Objects.DataClasses;
+using System.IO;
+using System.Web.UI.WebControls;
+using Microsoft.Web.UnitTestUtil;
+using Moq;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Mvc.Html.Test
+{
+ public class DefaultEditorTemplatesTest
+ {
+ // BooleanTemplate
+
+ [Fact]
+ public void BooleanTemplateTests()
+ {
+ // Boolean values
+
+ Assert.Equal(
+ @"<input checked=""checked"" class=""check-box"" id=""FieldPrefix"" name=""FieldPrefix"" type=""checkbox"" value=""true"" /><input name=""FieldPrefix"" type=""hidden"" value=""false"" />",
+ DefaultEditorTemplates.BooleanTemplate(MakeHtmlHelper<bool>(true)));
+
+ Assert.Equal(
+ @"<input class=""check-box"" id=""FieldPrefix"" name=""FieldPrefix"" type=""checkbox"" value=""true"" /><input name=""FieldPrefix"" type=""hidden"" value=""false"" />",
+ DefaultEditorTemplates.BooleanTemplate(MakeHtmlHelper<bool>(false)));
+
+ Assert.Equal(
+ @"<input class=""check-box"" id=""FieldPrefix"" name=""FieldPrefix"" type=""checkbox"" value=""true"" /><input name=""FieldPrefix"" type=""hidden"" value=""false"" />",
+ DefaultEditorTemplates.BooleanTemplate(MakeHtmlHelper<bool>(null)));
+
+ // Nullable<Boolean> values
+
+ Assert.Equal(
+ @"<select class=""list-box tri-state"" id=""FieldPrefix"" name=""FieldPrefix""><option value="""">Not Set</option>
+<option selected=""selected"" value=""true"">True</option>
+<option value=""false"">False</option>
+</select>",
+ DefaultEditorTemplates.BooleanTemplate(MakeHtmlHelper<Nullable<bool>>(true)));
+
+ Assert.Equal(
+ @"<select class=""list-box tri-state"" id=""FieldPrefix"" name=""FieldPrefix""><option value="""">Not Set</option>
+<option value=""true"">True</option>
+<option selected=""selected"" value=""false"">False</option>
+</select>",
+ DefaultEditorTemplates.BooleanTemplate(MakeHtmlHelper<Nullable<bool>>(false)));
+
+ Assert.Equal(
+ @"<select class=""list-box tri-state"" id=""FieldPrefix"" name=""FieldPrefix""><option selected=""selected"" value="""">Not Set</option>
+<option value=""true"">True</option>
+<option value=""false"">False</option>
+</select>",
+ DefaultEditorTemplates.BooleanTemplate(MakeHtmlHelper<Nullable<bool>>(null)));
+ }
+
+ // CollectionTemplate
+
+ private static string CollectionSpyCallback(HtmlHelper html, ModelMetadata metadata, string htmlFieldName, string templateName, DataBoundControlMode mode, object additionalViewData)
+ {
+ return String.Format(Environment.NewLine + "Model = {0}, ModelType = {1}, PropertyName = {2}, HtmlFieldName = {3}, TemplateName = {4}, Mode = {5}, TemplateInfo.HtmlFieldPrefix = {6}, AdditionalViewData = {7}",
+ metadata.Model ?? "(null)",
+ metadata.ModelType == null ? "(null)" : metadata.ModelType.FullName,
+ metadata.PropertyName ?? "(null)",
+ htmlFieldName == String.Empty ? "(empty)" : htmlFieldName ?? "(null)",
+ templateName ?? "(null)",
+ mode,
+ html.ViewContext.ViewData.TemplateInfo.HtmlFieldPrefix,
+ AnonymousObject.Inspect(additionalViewData));
+ }
+
+ [Fact]
+ public void CollectionTemplateWithNullModel()
+ {
+ // Arrange
+ HtmlHelper html = MakeHtmlHelper<object>(null);
+
+ // Act
+ string result = DefaultEditorTemplates.CollectionTemplate(html, CollectionSpyCallback);
+
+ // Assert
+ Assert.Equal(String.Empty, result);
+ }
+
+ [Fact]
+ public void CollectionTemplateNonEnumerableModelThrows()
+ {
+ // Arrange
+ HtmlHelper html = MakeHtmlHelper<object>(new object());
+
+ // Act & Assert
+ Assert.Throws<InvalidOperationException>(
+ () => DefaultEditorTemplates.CollectionTemplate(html, CollectionSpyCallback),
+ "The Collection template was used with an object of type 'System.Object', which does not implement System.IEnumerable."
+ );
+ }
+
+ [Fact]
+ public void CollectionTemplateWithSingleItemCollectionWithoutPrefix()
+ {
+ // Arrange
+ HtmlHelper html = MakeHtmlHelper<List<string>>(new List<string> { "foo" });
+ html.ViewContext.ViewData.TemplateInfo.HtmlFieldPrefix = null;
+
+ // Act
+ string result = DefaultEditorTemplates.CollectionTemplate(html, CollectionSpyCallback);
+
+ // Assert
+ Assert.Equal(@"
+Model = foo, ModelType = System.String, PropertyName = (null), HtmlFieldName = [0], TemplateName = (null), Mode = Edit, TemplateInfo.HtmlFieldPrefix = , AdditionalViewData = (null)",
+ result);
+ }
+
+ [Fact]
+ public void CollectionTemplateWithSingleItemCollectionWithPrefix()
+ {
+ // Arrange
+ HtmlHelper html = MakeHtmlHelper<List<string>>(new List<string> { "foo" });
+ html.ViewContext.ViewData.TemplateInfo.HtmlFieldPrefix = "ModelProperty";
+
+ // Act
+ string result = DefaultEditorTemplates.CollectionTemplate(html, CollectionSpyCallback);
+
+ // Assert
+ Assert.Equal(@"
+Model = foo, ModelType = System.String, PropertyName = (null), HtmlFieldName = ModelProperty[0], TemplateName = (null), Mode = Edit, TemplateInfo.HtmlFieldPrefix = , AdditionalViewData = (null)",
+ result);
+ }
+
+ [Fact]
+ public void CollectionTemplateWithMultiItemCollection()
+ {
+ // Arrange
+ HtmlHelper html = MakeHtmlHelper<List<string>>(new List<string> { "foo", "bar", "baz" });
+ html.ViewContext.ViewData.TemplateInfo.HtmlFieldPrefix = null;
+
+ // Act
+ string result = DefaultEditorTemplates.CollectionTemplate(html, CollectionSpyCallback);
+
+ // Assert
+ Assert.Equal(@"
+Model = foo, ModelType = System.String, PropertyName = (null), HtmlFieldName = [0], TemplateName = (null), Mode = Edit, TemplateInfo.HtmlFieldPrefix = , AdditionalViewData = (null)
+Model = bar, ModelType = System.String, PropertyName = (null), HtmlFieldName = [1], TemplateName = (null), Mode = Edit, TemplateInfo.HtmlFieldPrefix = , AdditionalViewData = (null)
+Model = baz, ModelType = System.String, PropertyName = (null), HtmlFieldName = [2], TemplateName = (null), Mode = Edit, TemplateInfo.HtmlFieldPrefix = , AdditionalViewData = (null)",
+ result);
+ }
+
+ [Fact]
+ public void CollectionTemplateNullITemInWeaklyTypedCollectionUsesModelTypeOfString()
+ {
+ // Arrange
+ HtmlHelper html = MakeHtmlHelper<ArrayList>(new ArrayList { null });
+ html.ViewContext.ViewData.TemplateInfo.HtmlFieldPrefix = null;
+
+ // Act
+ string result = DefaultEditorTemplates.CollectionTemplate(html, CollectionSpyCallback);
+
+ // Assert
+ Assert.Equal(@"
+Model = (null), ModelType = System.String, PropertyName = (null), HtmlFieldName = [0], TemplateName = (null), Mode = Edit, TemplateInfo.HtmlFieldPrefix = , AdditionalViewData = (null)",
+ result);
+ }
+
+ [Fact]
+ public void CollectionTemplateNullItemInStronglyTypedCollectionUsesModelTypeFromIEnumerable()
+ {
+ // Arrange
+ HtmlHelper html = MakeHtmlHelper<List<IHttpHandler>>(new List<IHttpHandler> { null });
+ html.ViewContext.ViewData.TemplateInfo.HtmlFieldPrefix = null;
+
+ // Act
+ string result = DefaultEditorTemplates.CollectionTemplate(html, CollectionSpyCallback);
+
+ // Assert
+ Assert.Equal(@"
+Model = (null), ModelType = System.Web.IHttpHandler, PropertyName = (null), HtmlFieldName = [0], TemplateName = (null), Mode = Edit, TemplateInfo.HtmlFieldPrefix = , AdditionalViewData = (null)",
+ result);
+ }
+
+ [Fact]
+ public void CollectionTemplateUsesRealObjectTypes()
+ {
+ // Arrange
+ HtmlHelper html = MakeHtmlHelper<List<Object>>(new List<Object> { 1, 2.3, "Hello World" });
+ html.ViewContext.ViewData.TemplateInfo.HtmlFieldPrefix = null;
+
+ // Act
+ string result = DefaultEditorTemplates.CollectionTemplate(html, CollectionSpyCallback);
+
+ // Assert
+ Assert.Equal(@"
+Model = 1, ModelType = System.Int32, PropertyName = (null), HtmlFieldName = [0], TemplateName = (null), Mode = Edit, TemplateInfo.HtmlFieldPrefix = , AdditionalViewData = (null)
+Model = 2.3, ModelType = System.Double, PropertyName = (null), HtmlFieldName = [1], TemplateName = (null), Mode = Edit, TemplateInfo.HtmlFieldPrefix = , AdditionalViewData = (null)
+Model = Hello World, ModelType = System.String, PropertyName = (null), HtmlFieldName = [2], TemplateName = (null), Mode = Edit, TemplateInfo.HtmlFieldPrefix = , AdditionalViewData = (null)",
+ result);
+ }
+
+ [Fact]
+ public void CollectionTemplateNullItemInCollectionOfNullableValueTypesDoesNotDiscardNullable()
+ {
+ // Arrange
+ HtmlHelper html = MakeHtmlHelper<List<int?>>(new List<int?> { 1, null, 2 });
+ html.ViewContext.ViewData.TemplateInfo.HtmlFieldPrefix = null;
+
+ // Act
+ string result = DefaultEditorTemplates.CollectionTemplate(html, CollectionSpyCallback);
+
+ // Assert
+ Assert.Equal(@"
+Model = 1, ModelType = System.Nullable`1[[System.Int32, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]], PropertyName = (null), HtmlFieldName = [0], TemplateName = (null), Mode = Edit, TemplateInfo.HtmlFieldPrefix = , AdditionalViewData = (null)
+Model = (null), ModelType = System.Nullable`1[[System.Int32, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]], PropertyName = (null), HtmlFieldName = [1], TemplateName = (null), Mode = Edit, TemplateInfo.HtmlFieldPrefix = , AdditionalViewData = (null)
+Model = 2, ModelType = System.Nullable`1[[System.Int32, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]], PropertyName = (null), HtmlFieldName = [2], TemplateName = (null), Mode = Edit, TemplateInfo.HtmlFieldPrefix = , AdditionalViewData = (null)",
+ result);
+ }
+
+ // DecimalTemplate
+
+ [Fact]
+ public void DecimalTemplateTests()
+ {
+ Assert.Equal(
+ @"<input class=""text-box single-line"" id=""FieldPrefix"" name=""FieldPrefix"" type=""text"" value=""12.35"" />",
+ DefaultEditorTemplates.DecimalTemplate(MakeHtmlHelper<decimal>(12.3456M)));
+
+ Assert.Equal(
+ @"<input class=""text-box single-line"" id=""FieldPrefix"" name=""FieldPrefix"" type=""text"" value=""Formatted Value"" />",
+ DefaultEditorTemplates.DecimalTemplate(MakeHtmlHelper<decimal>(12.3456M, "Formatted Value")));
+
+ Assert.Equal(
+ @"<input class=""text-box single-line"" id=""FieldPrefix"" name=""FieldPrefix"" type=""text"" value=""&lt;script>alert(&#39;XSS!&#39;)&lt;/script>"" />",
+ DefaultEditorTemplates.DecimalTemplate(MakeHtmlHelper<decimal>(12.3456M, "<script>alert('XSS!')</script>")));
+ }
+
+ // HiddenInputTemplate
+
+ [Fact]
+ public void HiddenInputTemplateTests()
+ {
+ Assert.Equal(
+ @"Hidden Value<input id=""FieldPrefix"" name=""FieldPrefix"" type=""hidden"" value=""Hidden Value"" />",
+ DefaultEditorTemplates.HiddenInputTemplate(MakeHtmlHelper<string>("Hidden Value")));
+
+ Assert.Equal(
+ @"&lt;script&gt;alert(&#39;XSS!&#39;)&lt;/script&gt;<input id=""FieldPrefix"" name=""FieldPrefix"" type=""hidden"" value=""&lt;script>alert(&#39;XSS!&#39;)&lt;/script>"" />",
+ DefaultEditorTemplates.HiddenInputTemplate(MakeHtmlHelper<string>("<script>alert('XSS!')</script>")));
+
+ var helperWithInvisibleHtml = MakeHtmlHelper<string>("<script>alert('XSS!')</script>", "<b>Encode me!</b>");
+ helperWithInvisibleHtml.ViewData.ModelMetadata.HideSurroundingHtml = true;
+ Assert.Equal(
+ @"<input id=""FieldPrefix"" name=""FieldPrefix"" type=""hidden"" value=""&lt;script>alert(&#39;XSS!&#39;)&lt;/script>"" />",
+ DefaultEditorTemplates.HiddenInputTemplate(helperWithInvisibleHtml));
+
+ byte[] byteValues = { 1, 2, 3, 4, 5 };
+
+ Assert.Equal(
+ @"&quot;AQIDBAU=&quot;<input id=""FieldPrefix"" name=""FieldPrefix"" type=""hidden"" value=""AQIDBAU="" />",
+ DefaultEditorTemplates.HiddenInputTemplate(MakeHtmlHelper<Binary>(new Binary(byteValues))));
+
+ Assert.Equal(
+ @"System.Byte[]<input id=""FieldPrefix"" name=""FieldPrefix"" type=""hidden"" value=""AQIDBAU="" />",
+ DefaultEditorTemplates.HiddenInputTemplate(MakeHtmlHelper<byte[]>(byteValues)));
+ }
+
+ // MultilineText
+
+ [Fact]
+ public void MultilineTextTemplateTests()
+ {
+ Assert.Equal(
+ @"<textarea class=""text-box multi-line"" id=""FieldPrefix"" name=""FieldPrefix"">
+Multiple
+Line
+Value!</textarea>",
+ DefaultEditorTemplates.MultilineTextTemplate(MakeHtmlHelper<string>("", @"Multiple
+Line
+Value!")));
+
+ Assert.Equal(
+ @"<textarea class=""text-box multi-line"" id=""FieldPrefix"" name=""FieldPrefix"">
+&lt;script&gt;alert(&#39;XSS!&#39;)&lt;/script&gt;</textarea>",
+ DefaultEditorTemplates.MultilineTextTemplate(MakeHtmlHelper<string>("", "<script>alert('XSS!')</script>")));
+ }
+
+ // ObjectTemplate
+
+ private static string SpyCallback(HtmlHelper html, ModelMetadata metadata, string htmlFieldName, string templateName, DataBoundControlMode mode, object additionalViewData)
+ {
+ return String.Format("Model = {0}, ModelType = {1}, PropertyName = {2}, HtmlFieldName = {3}, TemplateName = {4}, Mode = {5}, AdditionalViewData = {6}",
+ metadata.Model ?? "(null)",
+ metadata.ModelType == null ? "(null)" : metadata.ModelType.FullName,
+ metadata.PropertyName ?? "(null)",
+ htmlFieldName == String.Empty ? "(empty)" : htmlFieldName ?? "(null)",
+ templateName ?? "(null)",
+ mode,
+ AnonymousObject.Inspect(additionalViewData));
+ }
+
+ class ObjectTemplateModel
+ {
+ public ObjectTemplateModel()
+ {
+ ComplexInnerModel = new object();
+ }
+
+ public string Property1 { get; set; }
+ public string Property2 { get; set; }
+ public object ComplexInnerModel { get; set; }
+ }
+
+ [Fact]
+ public void ObjectTemplateEditsSimplePropertiesOnObjectByDefault()
+ {
+ string expected = @"<div class=""editor-label""><label for=""FieldPrefix_Property1"">Property1</label></div>
+<div class=""editor-field"">Model = p1, ModelType = System.String, PropertyName = Property1, HtmlFieldName = Property1, TemplateName = (null), Mode = Edit, AdditionalViewData = (null) </div>
+<div class=""editor-label""><label for=""FieldPrefix_Property2"">Property2</label></div>
+<div class=""editor-field"">Model = (null), ModelType = System.String, PropertyName = Property2, HtmlFieldName = Property2, TemplateName = (null), Mode = Edit, AdditionalViewData = (null) </div>
+";
+
+ // Arrange
+ ObjectTemplateModel model = new ObjectTemplateModel { Property1 = "p1", Property2 = null };
+ HtmlHelper html = MakeHtmlHelper<ObjectTemplateModel>(model);
+
+ // Act
+ string result = DefaultEditorTemplates.ObjectTemplate(html, SpyCallback);
+
+ // Assert
+ Assert.Equal(expected, result);
+ }
+
+ [Fact]
+ public void ObjectTemplateWithModelError()
+ {
+ string expected = @"<div class=""editor-label""><label for=""FieldPrefix_Property1"">Property1</label></div>
+<div class=""editor-field"">Model = p1, ModelType = System.String, PropertyName = Property1, HtmlFieldName = Property1, TemplateName = (null), Mode = Edit, AdditionalViewData = (null) <span class=""field-validation-error"">Error Message</span></div>
+<div class=""editor-label""><label for=""FieldPrefix_Property2"">Property2</label></div>
+<div class=""editor-field"">Model = (null), ModelType = System.String, PropertyName = Property2, HtmlFieldName = Property2, TemplateName = (null), Mode = Edit, AdditionalViewData = (null) </div>
+";
+
+ // Arrange
+ ObjectTemplateModel model = new ObjectTemplateModel { Property1 = "p1", Property2 = null };
+ HtmlHelper html = MakeHtmlHelper<ObjectTemplateModel>(model);
+ html.ViewData.ModelState.AddModelError("FieldPrefix.Property1", "Error Message");
+
+ // Act
+ string result = DefaultEditorTemplates.ObjectTemplate(html, SpyCallback);
+
+ // Assert
+ Assert.Equal(expected, result);
+ }
+
+ [Fact]
+ public void ObjectTemplateWithDisplayNameMetadata()
+ {
+ string expected = @"<div class=""editor-field"">Model = (null), ModelType = System.String, PropertyName = Property1, HtmlFieldName = Property1, TemplateName = (null), Mode = Edit, AdditionalViewData = (null) </div>
+<div class=""editor-label""><label for=""FieldPrefix_Property2"">Custom display name</label></div>
+<div class=""editor-field"">Model = (null), ModelType = System.String, PropertyName = Property2, HtmlFieldName = Property2, TemplateName = (null), Mode = Edit, AdditionalViewData = (null) </div>
+";
+
+ // Arrange
+ ObjectTemplateModel model = new ObjectTemplateModel();
+ HtmlHelper html = MakeHtmlHelper<ObjectTemplateModel>(model);
+ Mock<ModelMetadataProvider> provider = new Mock<ModelMetadataProvider>();
+ Func<object> accessor = () => model;
+ Mock<ModelMetadata> metadata = new Mock<ModelMetadata>(provider.Object, null, accessor, typeof(ObjectTemplateModel), null);
+ ModelMetadata prop1Metadata = new ModelMetadata(provider.Object, typeof(ObjectTemplateModel), null, typeof(string), "Property1") { DisplayName = String.Empty };
+ ModelMetadata prop2Metadata = new ModelMetadata(provider.Object, typeof(ObjectTemplateModel), null, typeof(string), "Property2") { DisplayName = "Custom display name" };
+ html.ViewData.ModelMetadata = metadata.Object;
+ metadata.Setup(p => p.Properties).Returns(() => new[] { prop1Metadata, prop2Metadata });
+
+ // Act
+ string result = DefaultEditorTemplates.ObjectTemplate(html, SpyCallback);
+
+ // Assert
+ Assert.Equal(expected, result);
+ }
+
+ [Fact]
+ public void ObjectTemplateWithShowForEditorMetadata()
+ {
+ string expected = @"<div class=""editor-label""><label for=""FieldPrefix_Property1"">Property1</label></div>
+<div class=""editor-field"">Model = (null), ModelType = System.String, PropertyName = Property1, HtmlFieldName = Property1, TemplateName = (null), Mode = Edit, AdditionalViewData = (null) </div>
+";
+
+ // Arrange
+ ObjectTemplateModel model = new ObjectTemplateModel();
+ HtmlHelper html = MakeHtmlHelper<ObjectTemplateModel>(model);
+ Mock<ModelMetadataProvider> provider = new Mock<ModelMetadataProvider>();
+ Func<object> accessor = () => model;
+ Mock<ModelMetadata> metadata = new Mock<ModelMetadata>(provider.Object, null, accessor, typeof(ObjectTemplateModel), null);
+ ModelMetadata prop1Metadata = new ModelMetadata(provider.Object, typeof(ObjectTemplateModel), null, typeof(string), "Property1") { ShowForEdit = true };
+ ModelMetadata prop2Metadata = new ModelMetadata(provider.Object, typeof(ObjectTemplateModel), null, typeof(string), "Property2") { ShowForEdit = false };
+ html.ViewData.ModelMetadata = metadata.Object;
+ metadata.Setup(p => p.Properties).Returns(() => new[] { prop1Metadata, prop2Metadata });
+
+ // Act
+ string result = DefaultEditorTemplates.ObjectTemplate(html, SpyCallback);
+
+ // Assert
+ Assert.Equal(expected, result);
+ }
+
+ [Fact]
+ public void ObjectTemplatePreventsRecursionOnModelValue()
+ {
+ string expected = @"<div class=""editor-label""><label for=""FieldPrefix_Property2"">Property2</label></div>
+<div class=""editor-field"">Model = propValue2, ModelType = System.String, PropertyName = Property2, HtmlFieldName = Property2, TemplateName = (null), Mode = Edit, AdditionalViewData = (null) </div>
+";
+
+ // Arrange
+ ObjectTemplateModel model = new ObjectTemplateModel();
+ HtmlHelper html = MakeHtmlHelper<ObjectTemplateModel>(model);
+ Mock<ModelMetadataProvider> provider = new Mock<ModelMetadataProvider>();
+ Func<object> accessor = () => model;
+ Mock<ModelMetadata> metadata = new Mock<ModelMetadata>(provider.Object, null, accessor, typeof(ObjectTemplateModel), null);
+ ModelMetadata prop1Metadata = new ModelMetadata(provider.Object, typeof(ObjectTemplateModel), () => "propValue1", typeof(string), "Property1");
+ ModelMetadata prop2Metadata = new ModelMetadata(provider.Object, typeof(ObjectTemplateModel), () => "propValue2", typeof(string), "Property2");
+ html.ViewData.ModelMetadata = metadata.Object;
+ metadata.Setup(p => p.Properties).Returns(() => new[] { prop1Metadata, prop2Metadata });
+ html.ViewData.TemplateInfo.VisitedObjects.Add("propValue1");
+
+ // Act
+ string result = DefaultEditorTemplates.ObjectTemplate(html, SpyCallback);
+
+ // Assert
+ Assert.Equal(expected, result);
+ }
+
+ [Fact]
+ public void ObjectTemplatePreventsRecursionOnModelTypeForNullModelValues()
+ {
+ string expected = @"<div class=""editor-label""><label for=""FieldPrefix_Property2"">Property2</label></div>
+<div class=""editor-field"">Model = propValue2, ModelType = System.String, PropertyName = Property2, HtmlFieldName = Property2, TemplateName = (null), Mode = Edit, AdditionalViewData = (null) </div>
+";
+
+ // Arrange
+ ObjectTemplateModel model = new ObjectTemplateModel();
+ HtmlHelper html = MakeHtmlHelper<ObjectTemplateModel>(model);
+ Mock<ModelMetadataProvider> provider = new Mock<ModelMetadataProvider>();
+ Func<object> accessor = () => model;
+ Mock<ModelMetadata> metadata = new Mock<ModelMetadata>(provider.Object, null, accessor, typeof(ObjectTemplateModel), null);
+ ModelMetadata prop1Metadata = new ModelMetadata(provider.Object, typeof(ObjectTemplateModel), null, typeof(string), "Property1");
+ ModelMetadata prop2Metadata = new ModelMetadata(provider.Object, typeof(ObjectTemplateModel), () => "propValue2", typeof(string), "Property2");
+ html.ViewData.ModelMetadata = metadata.Object;
+ metadata.Setup(p => p.Properties).Returns(() => new[] { prop1Metadata, prop2Metadata });
+ html.ViewData.TemplateInfo.VisitedObjects.Add(typeof(string));
+
+ // Act
+ string result = DefaultEditorTemplates.ObjectTemplate(html, SpyCallback);
+
+ // Assert
+ Assert.Equal(expected, result);
+ }
+
+ [Fact]
+ public void ObjectTemplateDisplaysNullDisplayTextWithNullModelAndTemplateDepthGreaterThanOne()
+ {
+ // Arrange
+ HtmlHelper html = MakeHtmlHelper<ObjectTemplateModel>(null);
+ ModelMetadata metadata = ModelMetadataProviders.Current.GetMetadataForType(null, typeof(ObjectTemplateModel));
+ metadata.NullDisplayText = "Null Display Text";
+ metadata.SimpleDisplayText = "Simple Display Text";
+ html.ViewData.ModelMetadata = metadata;
+ html.ViewData.TemplateInfo.VisitedObjects.Add("foo");
+ html.ViewData.TemplateInfo.VisitedObjects.Add("bar");
+
+ // Act
+ string result = DefaultEditorTemplates.ObjectTemplate(html, SpyCallback);
+
+ // Assert
+ Assert.Equal(metadata.NullDisplayText, result);
+ }
+
+ [Fact]
+ public void ObjectTemplateDisplaysSimpleDisplayTextWithNonNullModelTemplateDepthGreaterThanOne()
+ {
+ // Arrange
+ ObjectTemplateModel model = new ObjectTemplateModel();
+ HtmlHelper html = MakeHtmlHelper<ObjectTemplateModel>(model);
+ ModelMetadata metadata = ModelMetadataProviders.Current.GetMetadataForType(() => model, typeof(ObjectTemplateModel));
+ html.ViewData.ModelMetadata = metadata;
+ metadata.NullDisplayText = "Null Display Text";
+ metadata.SimpleDisplayText = "Simple Display Text";
+ html.ViewData.TemplateInfo.VisitedObjects.Add("foo");
+ html.ViewData.TemplateInfo.VisitedObjects.Add("bar");
+
+ // Act
+ string result = DefaultEditorTemplates.ObjectTemplate(html, SpyCallback);
+
+ // Assert
+ Assert.Equal(metadata.SimpleDisplayText, result);
+ }
+
+ // PasswordTemplate
+
+ [Fact]
+ public void PasswordTemplateTests()
+ {
+ Assert.Equal(
+ @"<input class=""text-box single-line password"" id=""FieldPrefix"" name=""FieldPrefix"" type=""password"" value=""Value"" />",
+ DefaultEditorTemplates.PasswordTemplate(MakeHtmlHelper<string>("Value")));
+
+ Assert.Equal(
+ @"<input class=""text-box single-line password"" id=""FieldPrefix"" name=""FieldPrefix"" type=""password"" value=""&lt;script>alert(&#39;XSS!&#39;)&lt;/script>"" />",
+ DefaultEditorTemplates.PasswordTemplate(MakeHtmlHelper<string>("<script>alert('XSS!')</script>")));
+ }
+
+ [Fact]
+ public void ObjectTemplateWithHiddenHtml()
+ {
+ string expected = @"Model = propValue1, ModelType = System.String, PropertyName = Property1, HtmlFieldName = Property1, TemplateName = (null), Mode = Edit, AdditionalViewData = (null)";
+
+ // Arrange
+ ObjectTemplateModel model = new ObjectTemplateModel();
+ HtmlHelper html = MakeHtmlHelper<ObjectTemplateModel>(model);
+ Mock<ModelMetadataProvider> provider = new Mock<ModelMetadataProvider>();
+ Func<object> accessor = () => model;
+ Mock<ModelMetadata> metadata = new Mock<ModelMetadata>(provider.Object, null, accessor, typeof(ObjectTemplateModel), null);
+ ModelMetadata prop1Metadata = new ModelMetadata(provider.Object, typeof(ObjectTemplateModel), () => "propValue1", typeof(string), "Property1") { HideSurroundingHtml = true };
+ html.ViewData.ModelMetadata = metadata.Object;
+ metadata.Setup(p => p.Properties).Returns(() => new[] { prop1Metadata });
+
+ // Act
+ string result = DefaultEditorTemplates.ObjectTemplate(html, SpyCallback);
+
+ // Assert
+ Assert.Equal(expected, result);
+ }
+
+ [Fact]
+ public void ObjectTemplateAllPropertiesFromEntityObjectAreHidden()
+ {
+ // Arrange
+ HtmlHelper html = MakeHtmlHelper<ObjectTemplateModel>(new MyEntityObject());
+
+ // Act
+ string result = DefaultEditorTemplates.ObjectTemplate(html, SpyCallback);
+
+ // Assert
+ Assert.Equal(String.Empty, result);
+ }
+
+ private class MyEntityObject : EntityObject
+ {
+ }
+
+ // StringTemplate
+
+ [Fact]
+ public void StringTemplateTests()
+ {
+ Assert.Equal(
+ @"<input class=""text-box single-line"" id=""FieldPrefix"" name=""FieldPrefix"" type=""text"" value=""Value"" />",
+ DefaultEditorTemplates.StringTemplate(MakeHtmlHelper<string>("Value")));
+
+ Assert.Equal(
+ @"<input class=""text-box single-line"" id=""FieldPrefix"" name=""FieldPrefix"" type=""text"" value=""&lt;script>alert(&#39;XSS!&#39;)&lt;/script>"" />",
+ DefaultEditorTemplates.StringTemplate(MakeHtmlHelper<string>("<script>alert('XSS!')</script>")));
+ }
+
+ // Helpers
+
+ private HtmlHelper MakeHtmlHelper<TModel>(object model)
+ {
+ return MakeHtmlHelper<TModel>(model, model);
+ }
+
+ private HtmlHelper MakeHtmlHelper<TModel>(object model, object formattedModelValue)
+ {
+ ViewDataDictionary viewData = new ViewDataDictionary(model);
+ viewData.TemplateInfo.HtmlFieldPrefix = "FieldPrefix";
+ viewData.TemplateInfo.FormattedModelValue = formattedModelValue;
+ viewData.ModelMetadata = new EmptyModelMetadataProvider().GetMetadataForType(() => model, typeof(TModel));
+
+ Mock<HttpContextBase> mockHttpContext = new Mock<HttpContextBase>();
+ mockHttpContext.Setup(o => o.Items).Returns(new Hashtable());
+
+ ViewContext viewContext = new ViewContext(new ControllerContext(), new DummyView(), viewData, new TempDataDictionary(), new StringWriter())
+ {
+ HttpContext = mockHttpContext.Object
+ };
+
+ return new HtmlHelper(viewContext, new SimpleViewDataContainer(viewData));
+ }
+
+ private class DummyView : IView
+ {
+ public void Render(ViewContext viewContext, TextWriter writer)
+ {
+ throw new NotImplementedException();
+ }
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Html/Test/DisplayNameExtensionsTest.cs b/test/System.Web.Mvc.Test/Html/Test/DisplayNameExtensionsTest.cs
new file mode 100644
index 00000000..dd2bbca8
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Html/Test/DisplayNameExtensionsTest.cs
@@ -0,0 +1,255 @@
+using System.Collections.Generic;
+using System.Linq.Expressions;
+using Microsoft.Web.UnitTestUtil;
+using Moq;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Mvc.Html.Test
+{
+ public class DisplayNameExtensionsTest
+ {
+ [Fact]
+ public void DisplayNameNullExpressionThrows()
+ {
+ // Act & Assert
+ Assert.ThrowsArgumentNull(
+ () => MvcHelper.GetHtmlHelper().Display(null),
+ "expression");
+ }
+
+ [Fact]
+ public void DisplayNameWithNoModelMetadataDisplayNameOverride()
+ {
+ // Act
+ MvcHtmlString result = MvcHelper.GetHtmlHelper().DisplayNameInternal("PropertyName", new MetadataHelper().MetadataProvider.Object);
+
+ // Assert
+ Assert.Equal("PropertyName", result.ToHtmlString());
+ }
+
+ [Fact]
+ public void DisplayNameUsesMetadataForDisplayText()
+ {
+ // Arrange
+ MetadataHelper metadataHelper = new MetadataHelper();
+ metadataHelper.Metadata.Setup(m => m.DisplayName).Returns("Custom display name from metadata");
+
+ // Act
+ MvcHtmlString result = MvcHelper.GetHtmlHelper().DisplayNameInternal("PropertyName", metadataHelper.MetadataProvider.Object);
+
+ // Assert
+ Assert.Equal("Custom display name from metadata", result.ToHtmlString());
+ }
+
+ private sealed class Model
+ {
+ public string PropertyName { get; set; }
+ }
+
+ [Fact]
+ public void DisplayNameConsultsMetadataProviderForMetadataAboutProperty()
+ {
+ // Arrange
+ Model model = new Model { PropertyName = "propertyValue" };
+
+ ViewDataDictionary viewData = new ViewDataDictionary();
+ Mock<ViewContext> viewContext = new Mock<ViewContext>();
+ viewContext.Setup(c => c.ViewData).Returns(viewData);
+
+ Mock<IViewDataContainer> viewDataContainer = new Mock<IViewDataContainer>();
+ viewDataContainer.Setup(c => c.ViewData).Returns(viewData);
+
+ HtmlHelper<Model> html = new HtmlHelper<Model>(viewContext.Object, viewDataContainer.Object);
+ viewData.Model = model;
+
+ MetadataHelper metadataHelper = new MetadataHelper();
+
+ metadataHelper.MetadataProvider.Setup(p => p.GetMetadataForProperty(It.IsAny<Func<object>>(), typeof(Model), "PropertyName"))
+ .Returns(metadataHelper.Metadata.Object)
+ .Verifiable();
+
+ // Act
+ html.DisplayNameInternal("PropertyName", metadataHelper.MetadataProvider.Object);
+
+ // Assert
+ metadataHelper.MetadataProvider.Verify();
+ }
+
+ [Fact]
+ public void DisplayNameUsesMetadataForPropertyName()
+ {
+ // Arrange
+ MetadataHelper metadataHelper = new MetadataHelper();
+
+ metadataHelper.Metadata = new Mock<ModelMetadata>(metadataHelper.MetadataProvider.Object, null, null, typeof(object), "Custom property name from metadata");
+ metadataHelper.MetadataProvider.Setup(p => p.GetMetadataForType(It.IsAny<Func<object>>(), It.IsAny<Type>()))
+ .Returns(metadataHelper.Metadata.Object);
+
+ // Act
+ MvcHtmlString result = MvcHelper.GetHtmlHelper().DisplayNameInternal("PropertyName", metadataHelper.MetadataProvider.Object);
+
+ // Assert
+ Assert.Equal("Custom property name from metadata", result.ToHtmlString());
+ }
+
+ [Fact]
+ public void DisplayNameForNullExpressionThrows()
+ {
+ // Act & Assert
+ Assert.ThrowsArgumentNull(
+ () => MvcHelper.GetHtmlHelper().DisplayNameFor((Expression<Func<Object, Object>>)null),
+ "expression");
+
+ Assert.ThrowsArgumentNull(
+ () => GetEnumerableHtmlHelper().DisplayNameFor((Expression<Func<Foo, Object>>)null),
+ "expression");
+ }
+
+ [Fact]
+ public void DisplayNameForNonMemberExpressionThrows()
+ {
+ // Act & Assert
+ Assert.Throws<InvalidOperationException>(
+ () => MvcHelper.GetHtmlHelper().DisplayNameFor(model => new { foo = "Bar" }),
+ "Templates can be used only with field access, property access, single-dimension array index, or single-parameter custom indexer expressions.");
+
+ Assert.Throws<InvalidOperationException>(
+ () => GetEnumerableHtmlHelper().DisplayNameFor((Expression<Func<IEnumerable<Foo>, object>>)(model => new { foo = "Bar" })),
+ "Templates can be used only with field access, property access, single-dimension array index, or single-parameter custom indexer expressions.");
+ }
+
+ [Fact]
+ public void DisplayNameForWithNoModelMetadataDisplayNameOverride()
+ {
+ // Arrange
+ string unknownKey = "this is a dummy parameter value";
+
+ // Act
+ MvcHtmlString result = MvcHelper.GetHtmlHelper().DisplayNameFor(model => unknownKey);
+ MvcHtmlString enumerableResult = GetEnumerableHtmlHelper().DisplayNameFor((Expression<Func<IEnumerable<Foo>, string>>)(model => unknownKey));
+
+ // Assert
+ Assert.Equal("unknownKey", result.ToHtmlString());
+ Assert.Equal("unknownKey", enumerableResult.ToHtmlString());
+ }
+
+ [Fact]
+ public void DisplayNameForUsesModelMetadata()
+ {
+ // Arrange
+ MetadataHelper metadataHelper = new MetadataHelper();
+
+ metadataHelper.Metadata.Setup(m => m.DisplayName).Returns("Custom display name from metadata");
+ string unknownKey = "this is a dummy parameter value";
+
+ // Act
+ MvcHtmlString result = MvcHelper.GetHtmlHelper().DisplayNameForInternal(model => unknownKey, metadataHelper.MetadataProvider.Object);
+ MvcHtmlString enumerableResult = GetEnumerableHtmlHelper().DisplayNameForInternal(model => model.Bar, metadataHelper.MetadataProvider.Object);
+
+ // Assert
+ Assert.Equal("Custom display name from metadata", result.ToHtmlString());
+ Assert.Equal("Custom display name from metadata", enumerableResult.ToHtmlString());
+ }
+
+ [Fact]
+ public void DisplayNameForEmptyDisplayNameReturnsEmptyName()
+ {
+ // Arrange
+ MetadataHelper metadataHelper = new MetadataHelper();
+
+ metadataHelper.Metadata.Setup(m => m.DisplayName).Returns(String.Empty);
+ string unknownKey = "this is a dummy parameter value";
+
+ // Act
+ MvcHtmlString result = MvcHelper.GetHtmlHelper().DisplayNameForInternal(model => unknownKey, metadataHelper.MetadataProvider.Object);
+ MvcHtmlString enumerableResult = GetEnumerableHtmlHelper().DisplayNameForInternal(model => model.Bar, metadataHelper.MetadataProvider.Object);
+
+ // Assert
+ Assert.Equal(String.Empty, result.ToHtmlString());
+ Assert.Equal(String.Empty, enumerableResult.ToHtmlString());
+ }
+
+ [Fact]
+ public void DisplayNameForModelUsesModelMetadata()
+ {
+ // Arrange
+ ViewDataDictionary viewData = new ViewDataDictionary();
+ Mock<ModelMetadata> metadata = new MetadataHelper().Metadata;
+ metadata.Setup(m => m.DisplayName).Returns("Custom display name from metadata");
+
+ viewData.ModelMetadata = metadata.Object;
+ viewData.TemplateInfo.HtmlFieldPrefix = "Prefix";
+
+ // Act
+ MvcHtmlString result = MvcHelper.GetHtmlHelper(viewData).DisplayNameForModel();
+
+ // Assert
+ Assert.Equal("Custom display name from metadata", result.ToHtmlString());
+ }
+
+ [Fact]
+ public void DisplayNameForWithNestedClass()
+ {
+ // Arrange
+ ViewDataDictionary viewData = new ViewDataDictionary();
+ Mock<ViewContext> viewContext = new Mock<ViewContext>();
+ viewContext.Setup(c => c.ViewData).Returns(viewData);
+
+ Mock<IViewDataContainer> viewDataContainer = new Mock<IViewDataContainer>();
+ viewDataContainer.Setup(c => c.ViewData).Returns(viewData);
+
+ HtmlHelper<NestedProduct> html = new HtmlHelper<NestedProduct>(viewContext.Object, viewDataContainer.Object);
+
+ // Act
+ MvcHtmlString result = html.DisplayNameForInternal(nested => nested.product.Id, new MetadataHelper().MetadataProvider.Object);
+
+ //Assert
+ Assert.Equal("Id", result.ToHtmlString());
+ }
+
+ private class Product
+ {
+ public int Id { get; set; }
+ }
+
+ private class Cart
+ {
+ public Product[] Products { get; set; }
+ }
+
+ private class NestedProduct
+ {
+ public Product product = new Product();
+ }
+
+ private sealed class Foo
+ {
+ public string Bar { get; set; }
+ }
+
+ private static HtmlHelper<IEnumerable<Foo>> GetEnumerableHtmlHelper()
+ {
+ return MvcHelper.GetHtmlHelper(new ViewDataDictionary<IEnumerable<Foo>>());
+ }
+
+ private sealed class MetadataHelper
+ {
+ public Mock<ModelMetadata> Metadata { get; set; }
+ public Mock<ModelMetadataProvider> MetadataProvider { get; set; }
+
+ public MetadataHelper()
+ {
+ MetadataProvider = new Mock<ModelMetadataProvider>();
+ Metadata = new Mock<ModelMetadata>(MetadataProvider.Object, null, null, typeof(object), null);
+
+ MetadataProvider.Setup(p => p.GetMetadataForProperties(It.IsAny<object>(), It.IsAny<Type>()))
+ .Returns(new ModelMetadata[0]);
+ MetadataProvider.Setup(p => p.GetMetadataForProperty(It.IsAny<Func<object>>(), It.IsAny<Type>(), It.IsAny<string>()))
+ .Returns(Metadata.Object);
+ MetadataProvider.Setup(p => p.GetMetadataForType(It.IsAny<Func<object>>(), It.IsAny<Type>()))
+ .Returns(Metadata.Object);
+ }
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Html/Test/FormExtensionsTest.cs b/test/System.Web.Mvc.Test/Html/Test/FormExtensionsTest.cs
new file mode 100644
index 00000000..c5bc9781
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Html/Test/FormExtensionsTest.cs
@@ -0,0 +1,423 @@
+using System.Collections;
+using System.Collections.Specialized;
+using System.IO;
+using System.Web.Routing;
+using Microsoft.Web.UnitTestUtil;
+using Moq;
+using Xunit;
+
+namespace System.Web.Mvc.Html.Test
+{
+ public class FormExtensionsTest
+ {
+ private static void BeginFormHelper(Func<HtmlHelper, MvcForm> beginForm, string expectedFormTag)
+ {
+ // Arrange
+ StringWriter writer;
+ HtmlHelper htmlHelper = GetFormHelper(out writer);
+
+ // Act
+ IDisposable formDisposable = beginForm(htmlHelper);
+ formDisposable.Dispose();
+
+ // Assert
+ Assert.Equal(expectedFormTag + "</form>", writer.ToString());
+ }
+
+ [Fact]
+ public void BeginFormParameterDictionaryMerging()
+ {
+ BeginFormHelper(
+ htmlHelper => htmlHelper.BeginForm("bar", "foo", FormMethod.Get, new RouteValueDictionary(new { method = "post" })),
+ @"<form action=""" + MvcHelper.AppPathModifier + @"/foo/bar"" method=""get"">");
+ }
+
+ [Fact]
+ public void BeginFormSetsAndRestoresToDefault()
+ {
+ // Arrange
+ StringWriter writer;
+ HtmlHelper htmlHelper = GetFormHelper(out writer);
+
+ htmlHelper.ViewContext.FormContext = null;
+ FormContext defaultFormContext = htmlHelper.ViewContext.FormContext;
+
+ // Act & assert - push
+ MvcForm theForm = htmlHelper.BeginForm();
+ Assert.NotNull(htmlHelper.ViewContext.FormContext);
+ Assert.NotEqual(defaultFormContext, htmlHelper.ViewContext.FormContext);
+
+ // Act & assert - pop
+ theForm.Dispose();
+ Assert.Equal(defaultFormContext, htmlHelper.ViewContext.FormContext);
+ Assert.Equal(@"<form action=""/some/path"" method=""post""></form>", writer.ToString());
+ }
+
+ [Fact]
+ public void BeginFormWithClientValidationEnabled()
+ {
+ // Arrange
+ StringWriter writer;
+ HtmlHelper htmlHelper = GetFormHelper(out writer);
+
+ htmlHelper.ViewContext.ClientValidationEnabled = true;
+ htmlHelper.ViewContext.FormContext = null;
+ FormContext defaultFormContext = htmlHelper.ViewContext.FormContext;
+
+ // Act & assert - push
+ MvcForm theForm = htmlHelper.BeginForm();
+ Assert.NotNull(htmlHelper.ViewContext.FormContext);
+ Assert.NotEqual(defaultFormContext, htmlHelper.ViewContext.FormContext);
+ Assert.Equal("form_id", htmlHelper.ViewContext.FormContext.FormId);
+
+ // Act & assert - pop
+ theForm.Dispose();
+ Assert.Equal(defaultFormContext, htmlHelper.ViewContext.FormContext);
+ Assert.Equal(@"<form action=""/some/path"" id=""form_id"" method=""post""></form><script type=""text/javascript"">
+//<![CDATA[
+if (!window.mvcClientValidationMetadata) { window.mvcClientValidationMetadata = []; }
+window.mvcClientValidationMetadata.push({""Fields"":[],""FormId"":""form_id"",""ReplaceValidationSummary"":false});
+//]]>
+</script>", writer.ToString());
+ }
+
+ [Fact]
+ public void BeginFormWithClientValidationAndUnobtrusiveJavaScriptEnabled()
+ {
+ // Arrange
+ StringWriter writer;
+ HtmlHelper htmlHelper = GetFormHelper(out writer);
+
+ htmlHelper.ViewContext.ClientValidationEnabled = true;
+ htmlHelper.ViewContext.UnobtrusiveJavaScriptEnabled = true;
+
+ // Act & assert - push
+ MvcForm theForm = htmlHelper.BeginForm();
+ Assert.Null(htmlHelper.ViewContext.FormContext.FormId);
+
+ // Act & assert - pop
+ theForm.Dispose();
+ Assert.Equal(@"<form action=""/some/path"" method=""post""></form>", writer.ToString());
+ }
+
+ [Fact]
+ public void BeginFormWithActionControllerInvalidFormMethodHtmlValues()
+ {
+ BeginFormHelper(
+ htmlHelper => htmlHelper.BeginForm("bar", "foo", (FormMethod)2, new RouteValueDictionary(new { baz = "baz" })),
+ @"<form action=""" + MvcHelper.AppPathModifier + @"/foo/bar"" baz=""baz"" method=""post"">");
+ }
+
+ [Fact]
+ public void BeginFormWithActionController()
+ {
+ BeginFormHelper(
+ htmlHelper => htmlHelper.BeginForm("bar", "foo"),
+ @"<form action=""" + MvcHelper.AppPathModifier + @"/foo/bar"" method=""post"">");
+ }
+
+ [Fact]
+ public void BeginFormWithActionControllerFormMethodHtmlDictionary()
+ {
+ BeginFormHelper(
+ htmlHelper => htmlHelper.BeginForm("bar", "foo", FormMethod.Get, new RouteValueDictionary(new { baz = "baz" })),
+ @"<form action=""" + MvcHelper.AppPathModifier + @"/foo/bar"" baz=""baz"" method=""get"">");
+ }
+
+ [Fact]
+ public void BeginFormWithActionControllerFormMethodHtmlValues()
+ {
+ BeginFormHelper(
+ htmlHelper => htmlHelper.BeginForm("bar", "foo", FormMethod.Get, new { baz = "baz" }),
+ @"<form action=""" + MvcHelper.AppPathModifier + @"/foo/bar"" baz=""baz"" method=""get"">");
+ }
+
+ [Fact]
+ public void BeginFormWithActionControllerFormMethodHtmlValuesWithUnderscores()
+ {
+ BeginFormHelper(
+ htmlHelper => htmlHelper.BeginForm("bar", "foo", FormMethod.Get, new { data_test = "value" }),
+ @"<form action=""" + MvcHelper.AppPathModifier + @"/foo/bar"" data-test=""value"" method=""get"">");
+ }
+
+ [Fact]
+ public void BeginFormWithActionControllerRouteDictionaryFormMethodHtmlDictionary()
+ {
+ BeginFormHelper(
+ htmlHelper => htmlHelper.BeginForm("bar", "foo", new RouteValueDictionary(new { id = "id" }), FormMethod.Get, new RouteValueDictionary(new { baz = "baz" })),
+ @"<form action=""" + MvcHelper.AppPathModifier + @"/foo/bar/id"" baz=""baz"" method=""get"">");
+ }
+
+ [Fact]
+ public void BeginFormWithActionControllerRouteValuesFormMethodHtmlValues()
+ {
+ BeginFormHelper(
+ htmlHelper => htmlHelper.BeginForm("bar", "foo", new { id = "id" }, FormMethod.Get, new { baz = "baz" }),
+ @"<form action=""" + MvcHelper.AppPathModifier + @"/foo/bar/id"" baz=""baz"" method=""get"">");
+ }
+
+ [Fact]
+ public void BeginFormWithActionControllerRouteValuesFormMethodHtmlValuesWithUnderscores()
+ {
+ BeginFormHelper(
+ htmlHelper => htmlHelper.BeginForm("bar", "foo", new { id = "id" }, FormMethod.Get, new { foo_baz = "baz" }),
+ @"<form action=""" + MvcHelper.AppPathModifier + @"/foo/bar/id"" foo-baz=""baz"" method=""get"">");
+ }
+
+ [Fact]
+ public void BeginFormWithActionControllerNullRouteValuesFormMethodNullHtmlValues()
+ {
+ BeginFormHelper(
+ htmlHelper => htmlHelper.BeginForm("bar", "foo", null, FormMethod.Get, null),
+ @"<form action=""" + MvcHelper.AppPathModifier + @"/foo/bar"" method=""get"">");
+ }
+
+ [Fact]
+ public void BeginFormWithRouteValues()
+ {
+ BeginFormHelper(
+ htmlHelper => htmlHelper.BeginForm(new { action = "someOtherAction", id = "id" }),
+ @"<form action=""" + MvcHelper.AppPathModifier + @"/home/someOtherAction/id"" method=""post"">");
+ }
+
+ [Fact]
+ public void BeginFormWithRouteDictionary()
+ {
+ BeginFormHelper(
+ htmlHelper => htmlHelper.BeginForm(new RouteValueDictionary { { "action", "someOtherAction" }, { "id", "id" } }),
+ @"<form action=""" + MvcHelper.AppPathModifier + @"/home/someOtherAction/id"" method=""post"">");
+ }
+
+ [Fact]
+ public void BeginFormWithActionControllerRouteValues()
+ {
+ BeginFormHelper(
+ htmlHelper => htmlHelper.BeginForm("myAction", "myController", new { id = "id", pageNum = "123" }),
+ @"<form action=""" + MvcHelper.AppPathModifier + @"/myController/myAction/id?pageNum=123"" method=""post"">");
+ }
+
+ [Fact]
+ public void BeginFormWithActionControllerRouteDictionary()
+ {
+ BeginFormHelper(
+ htmlHelper => htmlHelper.BeginForm("myAction", "myController", new RouteValueDictionary { { "pageNum", "123" }, { "id", "id" } }),
+ @"<form action=""" + MvcHelper.AppPathModifier + @"/myController/myAction/id?pageNum=123"" method=""post"">");
+ }
+
+ [Fact]
+ public void BeginFormWithActionControllerMethod()
+ {
+ BeginFormHelper(
+ htmlHelper => htmlHelper.BeginForm("myAction", "myController", FormMethod.Get),
+ @"<form action=""" + MvcHelper.AppPathModifier + @"/myController/myAction"" method=""get"">");
+ }
+
+ [Fact]
+ public void BeginFormWithActionControllerRouteValuesMethod()
+ {
+ BeginFormHelper(
+ htmlHelper => htmlHelper.BeginForm("myAction", "myController", new { id = "id", pageNum = "123" }, FormMethod.Get),
+ @"<form action=""" + MvcHelper.AppPathModifier + @"/myController/myAction/id?pageNum=123"" method=""get"">");
+ }
+
+ [Fact]
+ public void BeginFormWithActionControllerRouteDictionaryMethod()
+ {
+ BeginFormHelper(
+ htmlHelper => htmlHelper.BeginForm("myAction", "myController", new RouteValueDictionary { { "pageNum", "123" }, { "id", "id" } }, FormMethod.Get),
+ @"<form action=""" + MvcHelper.AppPathModifier + @"/myController/myAction/id?pageNum=123"" method=""get"">");
+ }
+
+ [Fact]
+ public void BeginFormWithNoParams()
+ {
+ BeginFormHelper(
+ htmlHelper => htmlHelper.BeginForm(),
+ @"<form action=""/some/path"" method=""post"">");
+ }
+
+ [Fact]
+ public void BeginRouteFormWithRouteNameInvalidFormMethodHtmlValues()
+ {
+ BeginFormHelper(
+ htmlHelper => htmlHelper.BeginRouteForm("namedroute", (FormMethod)2, new RouteValueDictionary(new { baz = "baz" })),
+ @"<form action=""" + MvcHelper.AppPathModifier + @"/named/home/oldaction"" baz=""baz"" method=""post"">");
+ }
+
+ [Fact]
+ public void BeginRouteFormWithRouteName()
+ {
+ BeginFormHelper(
+ htmlHelper => htmlHelper.BeginRouteForm("namedroute"),
+ @"<form action=""" + MvcHelper.AppPathModifier + @"/named/home/oldaction"" method=""post"">");
+ }
+
+ [Fact]
+ public void BeginRouteFormWithRouteNameFormMethodHtmlDictionary()
+ {
+ BeginFormHelper(
+ htmlHelper => htmlHelper.BeginRouteForm("namedroute", FormMethod.Get, new RouteValueDictionary(new { baz = "baz" })),
+ @"<form action=""" + MvcHelper.AppPathModifier + @"/named/home/oldaction"" baz=""baz"" method=""get"">");
+ }
+
+ [Fact]
+ public void BeginRouteFormWithRouteNameFormMethodHtmlValues()
+ {
+ BeginFormHelper(
+ htmlHelper => htmlHelper.BeginRouteForm("namedroute", FormMethod.Get, new { baz = "baz" }),
+ @"<form action=""" + MvcHelper.AppPathModifier + @"/named/home/oldaction"" baz=""baz"" method=""get"">");
+ }
+
+ [Fact]
+ public void BeginRouteFormWithRouteNameFormMethodHtmlValuesWithUnderscores()
+ {
+ BeginFormHelper(
+ htmlHelper => htmlHelper.BeginRouteForm("namedroute", FormMethod.Get, new { foo_baz = "baz" }),
+ @"<form action=""" + MvcHelper.AppPathModifier + @"/named/home/oldaction"" foo-baz=""baz"" method=""get"">");
+ }
+
+ [Fact]
+ public void BeginRouteFormWithRouteNameRouteDictionaryFormMethodHtmlDictionary()
+ {
+ BeginFormHelper(
+ htmlHelper => htmlHelper.BeginRouteForm("namedroute", new RouteValueDictionary(new { id = "id" }), FormMethod.Get, new RouteValueDictionary(new { baz = "baz" })),
+ @"<form action=""" + MvcHelper.AppPathModifier + @"/named/home/oldaction/id"" baz=""baz"" method=""get"">");
+ }
+
+ [Fact]
+ public void BeginRouteFormWithRouteNameRouteValuesFormMethodHtmlValues()
+ {
+ BeginFormHelper(
+ htmlHelper => htmlHelper.BeginRouteForm("namedroute", new { id = "id" }, FormMethod.Get, new { baz = "baz" }),
+ @"<form action=""" + MvcHelper.AppPathModifier + @"/named/home/oldaction/id"" baz=""baz"" method=""get"">");
+ }
+
+ [Fact]
+ public void BeginRouteFormWithRouteNameRouteValuesFormMethodHtmlValuesWithUnderscores()
+ {
+ BeginFormHelper(
+ htmlHelper => htmlHelper.BeginRouteForm("namedroute", new { id = "id" }, FormMethod.Get, new { foo_baz = "baz" }),
+ @"<form action=""" + MvcHelper.AppPathModifier + @"/named/home/oldaction/id"" foo-baz=""baz"" method=""get"">");
+ }
+
+ [Fact]
+ public void BeginRouteFormWithRouteNameNullRouteValuesFormMethodNullHtmlValues()
+ {
+ BeginFormHelper(
+ htmlHelper => htmlHelper.BeginRouteForm("namedroute", null, FormMethod.Get, null),
+ @"<form action=""" + MvcHelper.AppPathModifier + @"/named/home/oldaction"" method=""get"">");
+ }
+
+ [Fact]
+ public void BeginRouteFormWithRouteValues()
+ {
+ BeginFormHelper(
+ htmlHelper => htmlHelper.BeginRouteForm(new { action = "someOtherAction", id = "id" }),
+ @"<form action=""" + MvcHelper.AppPathModifier + @"/home/someOtherAction/id"" method=""post"">");
+ }
+
+ [Fact]
+ public void BeginRouteFormWithRouteDictionary()
+ {
+ BeginFormHelper(
+ htmlHelper => htmlHelper.BeginRouteForm(new RouteValueDictionary { { "action", "someOtherAction" }, { "id", "id" } }),
+ @"<form action=""" + MvcHelper.AppPathModifier + @"/home/someOtherAction/id"" method=""post"">");
+ }
+
+ [Fact]
+ public void BeginRouteFormWithRouteNameRouteValues()
+ {
+ BeginFormHelper(
+ htmlHelper => htmlHelper.BeginRouteForm("namedroute", new { id = "id", pageNum = "123" }),
+ @"<form action=""" + MvcHelper.AppPathModifier + @"/named/home/oldaction/id?pageNum=123"" method=""post"">");
+ }
+
+ [Fact]
+ public void BeginRouteFormWithActionControllerRouteDictionary()
+ {
+ BeginFormHelper(
+ htmlHelper => htmlHelper.BeginRouteForm("namedroute", new RouteValueDictionary { { "pageNum", "123" }, { "id", "id" } }),
+ @"<form action=""" + MvcHelper.AppPathModifier + @"/named/home/oldaction/id?pageNum=123"" method=""post"">");
+ }
+
+ [Fact]
+ public void BeginRouteFormCanUseNamedRouteWithoutSpecifyingDefaults()
+ {
+ // DevDiv 217072: Non-mvc specific helpers should not give default values for controller and action
+
+ BeginFormHelper(
+ htmlHelper =>
+ {
+ htmlHelper.RouteCollection.MapRoute("MyRouteName", "any/url", new { controller = "Charlie" });
+ return htmlHelper.BeginRouteForm("MyRouteName");
+ }, @"<form action=""" + MvcHelper.AppPathModifier + @"/any/url"" method=""post"">");
+ }
+
+ [Fact]
+ public void BeginRouteFormWithActionControllerMethod()
+ {
+ BeginFormHelper(
+ htmlHelper => htmlHelper.BeginRouteForm("namedroute", FormMethod.Get),
+ @"<form action=""" + MvcHelper.AppPathModifier + @"/named/home/oldaction"" method=""get"">");
+ }
+
+ [Fact]
+ public void BeginRouteFormWithActionControllerRouteValuesMethod()
+ {
+ BeginFormHelper(
+ htmlHelper => htmlHelper.BeginRouteForm("namedroute", new { id = "id", pageNum = "123" }, FormMethod.Get),
+ @"<form action=""" + MvcHelper.AppPathModifier + @"/named/home/oldaction/id?pageNum=123"" method=""get"">");
+ }
+
+ [Fact]
+ public void BeginRouteFormWithActionControllerRouteDictionaryMethod()
+ {
+ BeginFormHelper(
+ htmlHelper => htmlHelper.BeginRouteForm("namedroute", new RouteValueDictionary { { "pageNum", "123" }, { "id", "id" } }, FormMethod.Get),
+ @"<form action=""" + MvcHelper.AppPathModifier + @"/named/home/oldaction/id?pageNum=123"" method=""get"">");
+ }
+
+ [Fact]
+ public void EndFormWritesCloseTag()
+ {
+ // Arrange
+ StringWriter writer;
+ HtmlHelper htmlHelper = GetFormHelper(out writer);
+
+ // Act
+ htmlHelper.EndForm();
+
+ // Assert
+ Assert.Equal("</form>", writer.ToString());
+ }
+
+ private static HtmlHelper GetFormHelper(out StringWriter writer)
+ {
+ Mock<ViewContext> mockViewContext = new Mock<ViewContext>() { CallBase = true };
+ mockViewContext.Setup(c => c.HttpContext.Request.Url).Returns(new Uri("http://www.contoso.com/some/path"));
+ mockViewContext.Setup(c => c.HttpContext.Request.RawUrl).Returns("/some/path");
+ mockViewContext.Setup(c => c.HttpContext.Request.ApplicationPath).Returns("/");
+ mockViewContext.Setup(c => c.HttpContext.Request.Path).Returns("/");
+ mockViewContext.Setup(c => c.HttpContext.Request.ServerVariables).Returns((NameValueCollection)null);
+ mockViewContext.Setup(c => c.HttpContext.Response.Write(It.IsAny<string>())).Throws(new Exception("Should not be called"));
+ mockViewContext.Setup(c => c.HttpContext.Items).Returns(new Hashtable());
+
+ writer = new StringWriter();
+ mockViewContext.Setup(c => c.Writer).Returns(writer);
+
+ mockViewContext.Setup(c => c.HttpContext.Response.ApplyAppPathModifier(It.IsAny<string>())).Returns<string>(r => MvcHelper.AppPathModifier + r);
+
+ RouteCollection rt = new RouteCollection();
+ rt.Add(new Route("{controller}/{action}/{id}", null) { Defaults = new RouteValueDictionary(new { id = "defaultid" }) });
+ rt.Add("namedroute", new Route("named/{controller}/{action}/{id}", null) { Defaults = new RouteValueDictionary(new { id = "defaultid" }) });
+ RouteData rd = new RouteData();
+ rd.Values.Add("controller", "home");
+ rd.Values.Add("action", "oldaction");
+
+ mockViewContext.Setup(c => c.RouteData).Returns(rd);
+ HtmlHelper helper = new HtmlHelper(mockViewContext.Object, new Mock<IViewDataContainer>().Object, rt);
+ helper.ViewContext.FormIdGenerator = () => "form_id";
+ return helper;
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Html/Test/InputExtensionsTest.cs b/test/System.Web.Mvc.Test/Html/Test/InputExtensionsTest.cs
new file mode 100644
index 00000000..664ae26c
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Html/Test/InputExtensionsTest.cs
@@ -0,0 +1,2376 @@
+using System.ComponentModel.DataAnnotations;
+using System.Data.Linq;
+using System.Web.Mvc.Test;
+using System.Web.Routing;
+using Microsoft.Web.UnitTestUtil;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Mvc.Html.Test
+{
+ public class InputExtensionsTest
+ {
+ // CheckBox
+
+ [Fact]
+ public void CheckBoxDictionaryOverridesImplicitParameters()
+ {
+ // Arrange
+ HtmlHelper helper = MvcHelper.GetHtmlHelper(GetCheckBoxViewData());
+
+ // Act
+ MvcHtmlString html = helper.CheckBox("baz", new { @checked = "checked", value = "false" });
+
+ // Assert
+ Assert.Equal(@"<input checked=""checked"" id=""baz"" name=""baz"" type=""checkbox"" value=""false"" />" +
+ @"<input name=""baz"" type=""hidden"" value=""false"" />",
+ html.ToHtmlString());
+ }
+
+ [Fact]
+ public void CheckBoxExplicitParametersOverrideDictionary()
+ {
+ // Arrange
+ HtmlHelper helper = MvcHelper.GetHtmlHelper();
+
+ // Act
+ MvcHtmlString html = helper.CheckBox("foo", true /* isChecked */, new { @checked = "unchecked", value = "false" });
+
+ // Assert
+ Assert.Equal(@"<input checked=""checked"" id=""foo"" name=""foo"" type=""checkbox"" value=""false"" />" +
+ @"<input name=""foo"" type=""hidden"" value=""false"" />",
+ html.ToHtmlString());
+ }
+
+ [Fact]
+ public void CheckBoxShouldNotCopyAttributesForHidden()
+ {
+ // Arrange
+ HtmlHelper helper = MvcHelper.GetHtmlHelper();
+
+ // Act
+ MvcHtmlString html = helper.CheckBox("foo", true /* isChecked */, new { id = "myID" });
+
+ // Assert
+ Assert.Equal(@"<input checked=""checked"" id=""myID"" name=""foo"" type=""checkbox"" value=""true"" />" +
+ @"<input name=""foo"" type=""hidden"" value=""false"" />",
+ html.ToHtmlString());
+ }
+
+ [Fact]
+ public void CheckBoxWithEmptyNameThrows()
+ {
+ // Arrange
+ HtmlHelper helper = MvcHelper.GetHtmlHelper(GetCheckBoxViewData());
+
+ // Act & Assert
+ Assert.ThrowsArgumentNullOrEmpty(
+ delegate { helper.CheckBox(String.Empty); },
+ "name");
+ }
+
+ [Fact]
+ public void CheckBoxWithInvalidBooleanThrows()
+ {
+ // Arrange
+ HtmlHelper helper = MvcHelper.GetHtmlHelper(GetCheckBoxViewData());
+
+ // Act & Assert
+ Assert.Throws<FormatException>(
+ delegate { helper.CheckBox("bar"); },
+ "String was not recognized as a valid Boolean.");
+ }
+
+ [Fact]
+ public void CheckBoxCheckedWithOnlyName()
+ {
+ // Arrange
+ HtmlHelper helper = MvcHelper.GetHtmlHelper();
+
+ // Act
+ MvcHtmlString html = helper.CheckBox("foo", true /* isChecked */);
+
+ // Assert
+ Assert.Equal(@"<input checked=""checked"" id=""foo"" name=""foo"" type=""checkbox"" value=""true"" />" +
+ @"<input name=""foo"" type=""hidden"" value=""false"" />",
+ html.ToHtmlString());
+ }
+
+ [Fact]
+ public void CheckBoxShouldRespectModelStateAttemptedValue()
+ {
+ // Arrange
+ HtmlHelper helper = MvcHelper.GetHtmlHelper(GetCheckBoxViewData());
+ helper.ViewData.ModelState.SetModelValue("foo", HtmlHelperTest.GetValueProviderResult("false", "false"));
+
+ // Act
+ MvcHtmlString html = helper.CheckBox("foo");
+
+ // Assert
+ Assert.Equal(@"<input id=""foo"" name=""foo"" type=""checkbox"" value=""true"" />" +
+ @"<input name=""foo"" type=""hidden"" value=""false"" />",
+ html.ToHtmlString());
+ }
+
+ [Fact]
+ public void CheckBoxWithOnlyName()
+ {
+ // Arrange
+ HtmlHelper helper = MvcHelper.GetHtmlHelper(GetCheckBoxViewData());
+
+ // Act
+ MvcHtmlString html = helper.CheckBox("foo");
+
+ // Assert
+ Assert.Equal(@"<input checked=""checked"" id=""foo"" name=""foo"" type=""checkbox"" value=""true"" />" +
+ @"<input name=""foo"" type=""hidden"" value=""false"" />",
+ html.ToHtmlString());
+ }
+
+ [Fact]
+ public void CheckBoxWithOnlyName_Unobtrusive()
+ {
+ // Arrange
+ HtmlHelper helper = MvcHelper.GetHtmlHelper(GetCheckBoxViewData());
+ helper.ViewContext.ClientValidationEnabled = true;
+ helper.ViewContext.UnobtrusiveJavaScriptEnabled = true;
+ helper.ViewContext.FormContext = new FormContext();
+ helper.ClientValidationRuleFactory = (name, metadata) => new[] { new ModelClientValidationRule { ValidationType = "type", ErrorMessage = "error" } };
+
+ // Act
+ MvcHtmlString html = helper.CheckBox("foo");
+
+ // Assert
+ Assert.Equal(@"<input checked=""checked"" data-val=""true"" data-val-type=""error"" id=""foo"" name=""foo"" type=""checkbox"" value=""true"" />" +
+ @"<input name=""foo"" type=""hidden"" value=""false"" />",
+ html.ToHtmlString());
+ }
+
+ [Fact]
+ public void CheckBoxWithNameAndObjectAttribute()
+ {
+ // Arrange
+ HtmlHelper helper = MvcHelper.GetHtmlHelper(GetCheckBoxViewData());
+
+ // Act
+ MvcHtmlString html = helper.CheckBox("foo", _attributesObjectDictionary);
+
+ // Assert
+ Assert.Equal(@"<input baz=""BazObjValue"" checked=""checked"" id=""foo"" name=""foo"" type=""checkbox"" value=""true"" />" +
+ @"<input name=""foo"" type=""hidden"" value=""false"" />",
+ html.ToHtmlString());
+ }
+
+ [Fact]
+ public void CheckBoxWithNameAndObjectAttributeWithUnderscores()
+ {
+ // Arrange
+ HtmlHelper helper = MvcHelper.GetHtmlHelper(GetCheckBoxViewData());
+
+ // Act
+ MvcHtmlString html = helper.CheckBox("foo", _attributesObjectUnderscoresDictionary);
+
+ // Assert
+ Assert.Equal(@"<input checked=""checked"" foo-baz=""BazObjValue"" id=""foo"" name=""foo"" type=""checkbox"" value=""true"" />" +
+ @"<input name=""foo"" type=""hidden"" value=""false"" />",
+ html.ToHtmlString());
+ }
+
+ [Fact]
+ public void CheckBoxWithObjectAttribute()
+ {
+ // Arrange
+ HtmlHelper helper = MvcHelper.GetHtmlHelper();
+
+ // Act
+ MvcHtmlString html = helper.CheckBox("foo", false /* isChecked */, _attributesObjectDictionary);
+
+ // Assert
+ Assert.Equal(@"<input baz=""BazObjValue"" id=""foo"" name=""foo"" type=""checkbox"" value=""true"" />" +
+ @"<input name=""foo"" type=""hidden"" value=""false"" />",
+ html.ToHtmlString());
+ }
+
+ [Fact]
+ public void CheckBoxWithObjectAttributeWithUnderscores()
+ {
+ // Arrange
+ HtmlHelper helper = MvcHelper.GetHtmlHelper();
+
+ // Act
+ MvcHtmlString html = helper.CheckBox("foo", false /* isChecked */, _attributesObjectUnderscoresDictionary);
+
+ // Assert
+ Assert.Equal(@"<input foo-baz=""BazObjValue"" id=""foo"" name=""foo"" type=""checkbox"" value=""true"" />" +
+ @"<input name=""foo"" type=""hidden"" value=""false"" />",
+ html.ToHtmlString());
+ }
+
+ [Fact]
+ public void CheckBoxWithAttributeDictionary()
+ {
+ // Arrange
+ HtmlHelper helper = MvcHelper.GetHtmlHelper();
+
+ // Act
+ MvcHtmlString html = helper.CheckBox("foo", false /* isChecked */, _attributesDictionary);
+
+ // Assert
+ Assert.Equal(@"<input baz=""BazValue"" id=""foo"" name=""foo"" type=""checkbox"" value=""true"" />" +
+ @"<input name=""foo"" type=""hidden"" value=""false"" />",
+ html.ToHtmlString());
+ }
+
+ [Fact]
+ public void CheckBoxWithPrefix()
+ {
+ // Arrange
+ HtmlHelper helper = MvcHelper.GetHtmlHelper();
+ helper.ViewContext.ViewData.TemplateInfo.HtmlFieldPrefix = "MyPrefix";
+
+ // Act
+ MvcHtmlString html = helper.CheckBox("foo", false /* isChecked */, _attributesDictionary);
+
+ // Assert
+ Assert.Equal(@"<input baz=""BazValue"" id=""MyPrefix_foo"" name=""MyPrefix.foo"" type=""checkbox"" value=""true"" />" +
+ @"<input name=""MyPrefix.foo"" type=""hidden"" value=""false"" />",
+ html.ToHtmlString());
+ }
+
+ [Fact]
+ public void CheckBoxWithPrefixAndEmptyName()
+ {
+ // Arrange
+ HtmlHelper helper = MvcHelper.GetHtmlHelper();
+ helper.ViewContext.ViewData.TemplateInfo.HtmlFieldPrefix = "MyPrefix";
+
+ // Act
+ MvcHtmlString html = helper.CheckBox("", false /* isChecked */, _attributesDictionary);
+
+ // Assert
+ Assert.Equal(@"<input baz=""BazValue"" id=""MyPrefix"" name=""MyPrefix"" type=""checkbox"" value=""true"" />" +
+ @"<input name=""MyPrefix"" type=""hidden"" value=""false"" />",
+ html.ToHtmlString());
+ }
+
+ // CheckBoxFor
+
+ [Fact]
+ public void CheckBoxForWitNullExpressionThrows()
+ {
+ // Arrange
+ HtmlHelper<FooBarBazModel> helper = MvcHelper.GetHtmlHelper(GetCheckBoxViewData());
+
+ // Act & Assert
+ Assert.ThrowsArgumentNull(
+ () => helper.CheckBoxFor(null),
+ "expression");
+ }
+
+ [Fact]
+ public void CheckBoxForWithInvalidBooleanThrows()
+ {
+ // Arrange
+ HtmlHelper<FooBarBazModel> helper = MvcHelper.GetHtmlHelper(GetCheckBoxViewData());
+
+ // Act & Assert
+ Assert.Throws<FormatException>(
+ () => helper.CheckBoxFor(m => m.bar), // "bar" in ViewData isn't a valid boolean
+ "String was not recognized as a valid Boolean.");
+ }
+
+ [Fact]
+ public void CheckBoxForDictionaryOverridesImplicitParameters()
+ {
+ // Arrange
+ HtmlHelper<FooBarBazModel> helper = MvcHelper.GetHtmlHelper(GetCheckBoxViewData());
+
+ // Act
+ MvcHtmlString html = helper.CheckBoxFor(m => m.baz, new { @checked = "checked", value = "false" });
+
+ // Assert
+ Assert.Equal(@"<input checked=""checked"" id=""baz"" name=""baz"" type=""checkbox"" value=""false"" />" +
+ @"<input name=""baz"" type=""hidden"" value=""false"" />",
+ html.ToHtmlString());
+ }
+
+ [Fact]
+ public void CheckBoxForShouldNotCopyAttributesForHidden()
+ {
+ // Arrange
+ HtmlHelper<FooBarBazModel> helper = MvcHelper.GetHtmlHelper(GetCheckBoxViewData());
+
+ // Act
+ MvcHtmlString html = helper.CheckBoxFor(m => m.foo, new { id = "myID" });
+
+ // Assert
+ Assert.Equal(@"<input checked=""checked"" id=""myID"" name=""foo"" type=""checkbox"" value=""true"" />" +
+ @"<input name=""foo"" type=""hidden"" value=""false"" />",
+ html.ToHtmlString());
+ }
+
+ [Fact]
+ public void CheckBoxForCheckedWithOnlyName()
+ {
+ // Arrange
+ HtmlHelper<FooBarBazModel> helper = MvcHelper.GetHtmlHelper(GetCheckBoxViewData());
+
+ // Act
+ MvcHtmlString html = helper.CheckBoxFor(m => m.foo);
+
+ // Assert
+ Assert.Equal(@"<input checked=""checked"" id=""foo"" name=""foo"" type=""checkbox"" value=""true"" />" +
+ @"<input name=""foo"" type=""hidden"" value=""false"" />",
+ html.ToHtmlString());
+ }
+
+ [Fact]
+ public void CheckBoxForCheckedWithOnlyName_Unobtrusive()
+ {
+ // Arrange
+ HtmlHelper<FooBarBazModel> helper = MvcHelper.GetHtmlHelper(GetCheckBoxViewData());
+ helper.ViewContext.ClientValidationEnabled = true;
+ helper.ViewContext.UnobtrusiveJavaScriptEnabled = true;
+ helper.ViewContext.FormContext = new FormContext();
+ helper.ClientValidationRuleFactory = (name, metadata) => new[] { new ModelClientValidationRule { ValidationType = "type", ErrorMessage = "error" } };
+
+ // Act
+ MvcHtmlString html = helper.CheckBoxFor(m => m.foo);
+
+ // Assert
+ Assert.Equal(@"<input checked=""checked"" data-val=""true"" data-val-type=""error"" id=""foo"" name=""foo"" type=""checkbox"" value=""true"" />" +
+ @"<input name=""foo"" type=""hidden"" value=""false"" />",
+ html.ToHtmlString());
+ }
+
+ [Fact]
+ public void CheckBoxForShouldRespectModelStateAttemptedValue()
+ {
+ // Arrange
+ HtmlHelper<FooBarBazModel> helper = MvcHelper.GetHtmlHelper(GetCheckBoxViewData());
+ helper.ViewContext.ViewData.ModelState.SetModelValue("foo", HtmlHelperTest.GetValueProviderResult("false", "false"));
+
+ // Act
+ MvcHtmlString html = helper.CheckBoxFor(m => m.foo);
+
+ // Assert
+ Assert.Equal(@"<input id=""foo"" name=""foo"" type=""checkbox"" value=""true"" />" +
+ @"<input name=""foo"" type=""hidden"" value=""false"" />",
+ html.ToHtmlString());
+ }
+
+ [Fact]
+ public void CheckBoxForWithObjectAttribute()
+ {
+ // Arrange
+ HtmlHelper<FooBarBazModel> helper = MvcHelper.GetHtmlHelper(GetCheckBoxViewData());
+
+ // Act
+ MvcHtmlString html = helper.CheckBoxFor(m => m.foo, _attributesObjectDictionary);
+
+ // Assert
+ Assert.Equal(@"<input baz=""BazObjValue"" checked=""checked"" id=""foo"" name=""foo"" type=""checkbox"" value=""true"" />" +
+ @"<input name=""foo"" type=""hidden"" value=""false"" />",
+ html.ToHtmlString());
+ }
+
+ [Fact]
+ public void CheckBoxForWithObjectAttributeWithUnderscores()
+ {
+ // Arrange
+ HtmlHelper<FooBarBazModel> helper = MvcHelper.GetHtmlHelper(GetCheckBoxViewData());
+
+ // Act
+ MvcHtmlString html = helper.CheckBoxFor(m => m.foo, _attributesObjectUnderscoresDictionary);
+
+ // Assert
+ Assert.Equal(@"<input checked=""checked"" foo-baz=""BazObjValue"" id=""foo"" name=""foo"" type=""checkbox"" value=""true"" />" +
+ @"<input name=""foo"" type=""hidden"" value=""false"" />",
+ html.ToHtmlString());
+ }
+
+ [Fact]
+ public void CheckBoxForWithAttributeDictionary()
+ {
+ // Arrange
+ HtmlHelper<FooBarBazModel> helper = MvcHelper.GetHtmlHelper(GetCheckBoxViewData());
+
+ // Act
+ MvcHtmlString html = helper.CheckBoxFor(m => m.foo, _attributesDictionary);
+
+ // Assert
+ Assert.Equal(@"<input baz=""BazValue"" checked=""checked"" id=""foo"" name=""foo"" type=""checkbox"" value=""true"" />" +
+ @"<input name=""foo"" type=""hidden"" value=""false"" />",
+ html.ToHtmlString());
+ }
+
+ [Fact]
+ public void CheckBoxForWithPrefix()
+ {
+ // Arrange
+ HtmlHelper<FooBarBazModel> helper = MvcHelper.GetHtmlHelper(GetCheckBoxViewData());
+ helper.ViewContext.ViewData.TemplateInfo.HtmlFieldPrefix = "MyPrefix";
+
+ // Act
+ MvcHtmlString html = helper.CheckBoxFor(m => m.foo, _attributesDictionary);
+
+ // Assert
+ Assert.Equal(@"<input baz=""BazValue"" id=""MyPrefix_foo"" name=""MyPrefix.foo"" type=""checkbox"" value=""true"" />" +
+ @"<input name=""MyPrefix.foo"" type=""hidden"" value=""false"" />",
+ html.ToHtmlString());
+ }
+
+ // Culture tests
+
+ [Fact]
+ public void InputHelpersUseCurrentCultureToConvertValueParameter()
+ {
+ // Arrange
+ DateTime dt = new DateTime(1900, 1, 1, 0, 0, 0);
+ HtmlHelper helper = MvcHelper.GetHtmlHelper(new ViewDataDictionary { { "foo", dt } });
+
+ var tests = new[]
+ {
+ // Hidden(name)
+ new
+ {
+ Html = @"<input id=""foo"" name=""foo"" type=""hidden"" value=""1900/01/01 12:00:00 AM"" />",
+ Action = new Func<MvcHtmlString>(() => helper.Hidden("foo"))
+ },
+ // Hidden(name, value)
+ new
+ {
+ Html = @"<input id=""foo"" name=""foo"" type=""hidden"" value=""1900/01/01 12:00:00 AM"" />",
+ Action = new Func<MvcHtmlString>(() => helper.Hidden("foo", dt))
+ },
+ // Hidden(name, value, htmlAttributes)
+ new
+ {
+ Html = @"<input id=""foo"" name=""foo"" type=""hidden"" value=""1900/01/01 12:00:00 AM"" />",
+ Action = new Func<MvcHtmlString>(() => helper.Hidden("foo", dt, null))
+ },
+ // Hidden(name, value, htmlAttributes)
+ new
+ {
+ Html = @"<input id=""foo"" name=""foo"" type=""hidden"" value=""1900/01/01 12:00:00 AM"" />",
+ Action = new Func<MvcHtmlString>(() => helper.Hidden("foo", dt, new RouteValueDictionary()))
+ },
+ // RadioButton(name, value)
+ new
+ {
+ Html = @"<input checked=""checked"" id=""foo"" name=""foo"" type=""radio"" value=""1900/01/01 12:00:00 AM"" />",
+ Action = new Func<MvcHtmlString>(() => helper.RadioButton("foo", dt))
+ },
+ // RadioButton(name, value, isChecked)
+ new
+ {
+ Html = @"<input id=""foo"" name=""foo"" type=""radio"" value=""1900/01/01 12:00:00 AM"" />",
+ Action = new Func<MvcHtmlString>(() => helper.RadioButton("foo", dt, false))
+ },
+ // RadioButton(name, value, htmlAttributes)
+ new
+ {
+ Html = @"<input checked=""checked"" id=""foo"" name=""foo"" type=""radio"" value=""1900/01/01 12:00:00 AM"" />",
+ Action = new Func<MvcHtmlString>(() => helper.RadioButton("foo", dt, null))
+ },
+ // RadioButton(name, value)
+ new
+ {
+ Html = @"<input checked=""checked"" id=""foo"" name=""foo"" type=""radio"" value=""1900/01/01 12:00:00 AM"" />",
+ Action = new Func<MvcHtmlString>(() => helper.RadioButton("foo", dt, new RouteValueDictionary()))
+ },
+ // RadioButton(name, value, isChecked, htmlAttributes)
+ new
+ {
+ Html = @"<input id=""foo"" name=""foo"" type=""radio"" value=""1900/01/01 12:00:00 AM"" />",
+ Action = new Func<MvcHtmlString>(() => helper.RadioButton("foo", dt, false, null))
+ },
+ // RadioButton(name, value, isChecked, htmlAttributes)
+ new
+ {
+ Html = @"<input id=""foo"" name=""foo"" type=""radio"" value=""1900/01/01 12:00:00 AM"" />",
+ Action = new Func<MvcHtmlString>(() => helper.RadioButton("foo", dt, false, new RouteValueDictionary()))
+ },
+ // TextBox(name)
+ new
+ {
+ Html = @"<input id=""foo"" name=""foo"" type=""text"" value=""1900/01/01 12:00:00 AM"" />",
+ Action = new Func<MvcHtmlString>(() => helper.TextBox("foo"))
+ },
+ // TextBox(name, value)
+ new
+ {
+ Html = @"<input id=""foo"" name=""foo"" type=""text"" value=""1900/01/01 12:00:00 AM"" />",
+ Action = new Func<MvcHtmlString>(() => helper.TextBox("foo", dt))
+ },
+ // TextBox(name, value, hmtlAttributes)
+ new
+ {
+ Html = @"<input id=""foo"" name=""foo"" type=""text"" value=""1900/01/01 12:00:00 AM"" />",
+ Action = new Func<MvcHtmlString>(() => helper.TextBox("foo", dt, (object)null))
+ },
+ // TextBox(name, value, hmtlAttributes)
+ new
+ {
+ Html = @"<input id=""foo"" name=""foo"" type=""text"" value=""1900/01/01 12:00:00 AM"" />",
+ Action = new Func<MvcHtmlString>(() => helper.TextBox("foo", dt, new RouteValueDictionary()))
+ }
+ };
+
+ // Act && Assert
+ using (HtmlHelperTest.ReplaceCulture("en-ZA", "en-US"))
+ {
+ foreach (var test in tests)
+ {
+ Assert.Equal(test.Html, test.Action().ToHtmlString());
+ }
+ }
+ }
+
+ // Hidden
+
+ [Fact]
+ public void HiddenWithByteArrayValueRendersBase64EncodedValue()
+ {
+ // Arrange
+ HtmlHelper htmlHelper = MvcHelper.GetHtmlHelper();
+
+ // Act
+ MvcHtmlString result = htmlHelper.Hidden("ProductName", ByteArrayModelBinderTest.Base64TestBytes);
+
+ // Assert
+ Assert.Equal("<input id=\"ProductName\" name=\"ProductName\" type=\"hidden\" value=\"Fys1\" />", result.ToHtmlString());
+ }
+
+ [Fact]
+ public void HiddenWithBinaryArrayValueRendersBase64EncodedValue()
+ {
+ // Arrange
+ HtmlHelper htmlHelper = MvcHelper.GetHtmlHelper();
+
+ // Act
+ MvcHtmlString result = htmlHelper.Hidden("ProductName", new Binary(new byte[] { 23, 43, 53 }));
+
+ // Assert
+ Assert.Equal("<input id=\"ProductName\" name=\"ProductName\" type=\"hidden\" value=\"Fys1\" />", result.ToHtmlString());
+ }
+
+ [Fact]
+ public void HiddenWithEmptyNameThrows()
+ {
+ // Arrange
+ HtmlHelper helper = MvcHelper.GetHtmlHelper(GetHiddenViewData());
+
+ // Act & Assert
+ Assert.ThrowsArgumentNullOrEmpty(
+ delegate { helper.Hidden(String.Empty); },
+ "name");
+ }
+
+ [Fact]
+ public void HiddenWithExplicitValue()
+ {
+ // Arrange
+ HtmlHelper helper = MvcHelper.GetHtmlHelper(GetHiddenViewData());
+
+ // Act
+ MvcHtmlString html = helper.Hidden("foo", "DefaultFoo", null);
+
+ // Assert
+ Assert.Equal(@"<input id=""foo"" name=""foo"" type=""hidden"" value=""DefaultFoo"" />", html.ToHtmlString());
+ }
+
+ [Fact]
+ public void HiddenWithExplicitValueAndAttributesDictionary()
+ {
+ // Arrange
+ HtmlHelper helper = MvcHelper.GetHtmlHelper(GetHiddenViewData());
+
+ // Act
+ MvcHtmlString html = helper.Hidden("foo", "DefaultFoo", _attributesDictionary);
+
+ // Assert
+ Assert.Equal(@"<input baz=""BazValue"" id=""foo"" name=""foo"" type=""hidden"" value=""DefaultFoo"" />", html.ToHtmlString());
+ }
+
+ [Fact]
+ public void HiddenWithExplicitValueAndAttributesObject()
+ {
+ // Arrange
+ HtmlHelper helper = MvcHelper.GetHtmlHelper(GetHiddenViewData());
+
+ // Act
+ MvcHtmlString html = helper.Hidden("foo", "DefaultFoo", _attributesObjectDictionary);
+
+ // Assert
+ Assert.Equal(@"<input baz=""BazObjValue"" id=""foo"" name=""foo"" type=""hidden"" value=""DefaultFoo"" />", html.ToHtmlString());
+ }
+
+ [Fact]
+ public void HiddenWithExplicitValueAndAttributesObjectWithUnderscores()
+ {
+ // Arrange
+ HtmlHelper helper = MvcHelper.GetHtmlHelper(GetHiddenViewData());
+
+ // Act
+ MvcHtmlString html = helper.Hidden("foo", "DefaultFoo", _attributesObjectUnderscoresDictionary);
+
+ // Assert
+ Assert.Equal(@"<input foo-baz=""BazObjValue"" id=""foo"" name=""foo"" type=""hidden"" value=""DefaultFoo"" />", html.ToHtmlString());
+ }
+
+ [Fact]
+ public void HiddenWithExplicitValueNull()
+ {
+ // Arrange
+ HtmlHelper helper = MvcHelper.GetHtmlHelper(GetHiddenViewData());
+
+ // Act
+ MvcHtmlString html = helper.Hidden("foo", (string)null /* value */, (object)null /* htmlAttributes */);
+
+ // Assert
+ Assert.Equal(@"<input id=""foo"" name=""foo"" type=""hidden"" value=""ViewDataFoo"" />", html.ToHtmlString());
+ }
+
+ [Fact]
+ public void HiddenWithImplicitValue()
+ {
+ // Arrange
+ HtmlHelper helper = MvcHelper.GetHtmlHelper(GetHiddenViewData());
+
+ // Act
+ MvcHtmlString html = helper.Hidden("foo");
+
+ // Assert
+ Assert.Equal(@"<input id=""foo"" name=""foo"" type=""hidden"" value=""ViewDataFoo"" />", html.ToHtmlString());
+ }
+
+ [Fact]
+ public void HiddenWithImplicitValueAndAttributesDictionary()
+ {
+ // Arrange
+ HtmlHelper helper = MvcHelper.GetHtmlHelper(GetHiddenViewData());
+
+ // Act
+ MvcHtmlString html = helper.Hidden("foo", null, _attributesDictionary);
+
+ // Assert
+ Assert.Equal(@"<input baz=""BazValue"" id=""foo"" name=""foo"" type=""hidden"" value=""ViewDataFoo"" />", html.ToHtmlString());
+ }
+
+ [Fact]
+ public void HiddenWithImplicitValueAndAttributesDictionaryReturnsEmptyValueIfNotFound()
+ {
+ // Arrange
+ HtmlHelper helper = MvcHelper.GetHtmlHelper(GetHiddenViewData());
+
+ // Act
+ MvcHtmlString html = helper.Hidden("keyNotFound", null, _attributesDictionary);
+
+ // Assert
+ Assert.Equal(@"<input baz=""BazValue"" id=""keyNotFound"" name=""keyNotFound"" type=""hidden"" value="""" />", html.ToHtmlString());
+ }
+
+ [Fact]
+ public void HiddenWithImplicitValueAndAttributesObject()
+ {
+ // Arrange
+ HtmlHelper helper = MvcHelper.GetHtmlHelper(GetHiddenViewData());
+
+ // Act
+ MvcHtmlString html = helper.Hidden("foo", null, _attributesObjectDictionary);
+
+ // Assert
+ Assert.Equal(@"<input baz=""BazObjValue"" id=""foo"" name=""foo"" type=""hidden"" value=""ViewDataFoo"" />", html.ToHtmlString());
+ }
+
+ [Fact]
+ public void HiddenWithNameAndValue()
+ {
+ // Arrange
+ HtmlHelper helper = MvcHelper.GetHtmlHelper(GetHiddenViewData());
+
+ // Act
+ MvcHtmlString html = helper.Hidden("foo", "fooValue");
+
+ // Assert
+ Assert.Equal(@"<input id=""foo"" name=""foo"" type=""hidden"" value=""fooValue"" />", html.ToHtmlString());
+ }
+
+ [Fact]
+ public void HiddenWithPrefix()
+ {
+ // Arrange
+ HtmlHelper helper = MvcHelper.GetHtmlHelper(GetHiddenViewData());
+ helper.ViewContext.ViewData.TemplateInfo.HtmlFieldPrefix = "MyPrefix";
+
+ // Act
+ MvcHtmlString html = helper.Hidden("foo", "fooValue");
+
+ // Assert
+ Assert.Equal(@"<input id=""MyPrefix_foo"" name=""MyPrefix.foo"" type=""hidden"" value=""fooValue"" />", html.ToHtmlString());
+ }
+
+ [Fact]
+ public void HiddenWithPrefixAndEmptyName()
+ {
+ // Arrange
+ HtmlHelper helper = MvcHelper.GetHtmlHelper(GetHiddenViewData());
+ helper.ViewContext.ViewData.TemplateInfo.HtmlFieldPrefix = "MyPrefix";
+
+ // Act
+ MvcHtmlString html = helper.Hidden("", "fooValue");
+
+ // Assert
+ Assert.Equal(@"<input id=""MyPrefix"" name=""MyPrefix"" type=""hidden"" value=""fooValue"" />", html.ToHtmlString());
+ }
+
+ [Fact]
+ public void HiddenWithNullNameThrows()
+ {
+ // Arrange
+ HtmlHelper helper = MvcHelper.GetHtmlHelper(GetHiddenViewData());
+
+ // Act & Assert
+ Assert.ThrowsArgumentNullOrEmpty(
+ delegate { helper.Hidden(null /* name */); },
+ "name");
+ }
+
+ [Fact]
+ public void HiddenWithViewDataErrors()
+ {
+ // Arrange
+ HtmlHelper helper = MvcHelper.GetHtmlHelper(GetHiddenViewDataWithErrors());
+
+ // Act
+ MvcHtmlString html = helper.Hidden("foo", null, _attributesObjectDictionary);
+
+ // Assert
+ Assert.Equal(@"<input baz=""BazObjValue"" class=""input-validation-error"" id=""foo"" name=""foo"" type=""hidden"" value=""AttemptedValueFoo"" />", html.ToHtmlString());
+ }
+
+ [Fact]
+ public void HiddenWithViewDataErrorsAndCustomClass()
+ {
+ // Arrange
+ HtmlHelper helper = MvcHelper.GetHtmlHelper(GetHiddenViewDataWithErrors());
+
+ // Act
+ MvcHtmlString html = helper.Hidden("foo", null, new { @class = "foo-class" });
+
+ // Assert
+ Assert.Equal(@"<input class=""input-validation-error foo-class"" id=""foo"" name=""foo"" type=""hidden"" value=""AttemptedValueFoo"" />", html.ToHtmlString());
+ }
+
+ // HiddenFor
+
+ [Fact]
+ public void HiddenForWithNullExpressionThrows()
+ {
+ // Arrange
+ HtmlHelper<HiddenModel> helper = MvcHelper.GetHtmlHelper(GetHiddenViewData());
+
+ // Act & Assert
+ Assert.ThrowsArgumentNull(
+ () => helper.HiddenFor<HiddenModel, object>(null),
+ "expression"
+ );
+ }
+
+ [Fact]
+ public void HiddenForWithStringValue()
+ {
+ // Arrange
+ HtmlHelper<HiddenModel> helper = MvcHelper.GetHtmlHelper(GetHiddenViewData());
+ helper.ViewData.Model.foo = "DefaultFoo";
+
+ // Act
+ MvcHtmlString html = helper.HiddenFor(m => m.foo);
+
+ // Assert
+ Assert.Equal(@"<input id=""foo"" name=""foo"" type=""hidden"" value=""DefaultFoo"" />", html.ToHtmlString());
+ }
+
+ [Fact]
+ public void HiddenForWithByteArrayValueRendersBase64EncodedValue()
+ {
+ // Arrange
+ HtmlHelper<HiddenModel> helper = MvcHelper.GetHtmlHelper(GetHiddenViewData());
+ helper.ViewData.Model.bytes = ByteArrayModelBinderTest.Base64TestBytes;
+
+ // Act
+ MvcHtmlString result = helper.HiddenFor(m => m.bytes);
+
+ // Assert
+ Assert.Equal("<input id=\"bytes\" name=\"bytes\" type=\"hidden\" value=\"Fys1\" />", result.ToHtmlString());
+ }
+
+ [Fact]
+ public void HiddenForWithBinaryValueRendersBase64EncodedValue()
+ {
+ // Arrange
+ HtmlHelper<HiddenModel> helper = MvcHelper.GetHtmlHelper(GetHiddenViewData());
+ helper.ViewData.Model.binary = new Binary(new byte[] { 23, 43, 53 });
+
+ // Act
+ MvcHtmlString result = helper.HiddenFor(m => m.binary);
+
+ // Assert
+ Assert.Equal("<input id=\"binary\" name=\"binary\" type=\"hidden\" value=\"Fys1\" />", result.ToHtmlString());
+ }
+
+ [Fact]
+ public void HiddenForWithAttributesDictionary()
+ {
+ // Arrange
+ HtmlHelper<HiddenModel> helper = MvcHelper.GetHtmlHelper(GetHiddenViewData());
+ helper.ViewData.Model.foo = "DefaultFoo";
+
+ // Act
+ MvcHtmlString html = helper.HiddenFor(m => m.foo, _attributesDictionary);
+
+ // Assert
+ Assert.Equal(@"<input baz=""BazValue"" id=""foo"" name=""foo"" type=""hidden"" value=""DefaultFoo"" />", html.ToHtmlString());
+ }
+
+ [Fact]
+ public void HiddenForWithAttributesObject()
+ {
+ // Arrange
+ HtmlHelper<HiddenModel> helper = MvcHelper.GetHtmlHelper(GetHiddenViewData());
+ helper.ViewData.Model.foo = "DefaultFoo";
+
+ // Act
+ MvcHtmlString html = helper.HiddenFor(m => m.foo, _attributesObjectDictionary);
+
+ // Assert
+ Assert.Equal(@"<input baz=""BazObjValue"" id=""foo"" name=""foo"" type=""hidden"" value=""DefaultFoo"" />", html.ToHtmlString());
+ }
+
+ [Fact]
+ public void HiddenForWithAttributesObjectWithUnderscores()
+ {
+ // Arrange
+ HtmlHelper<HiddenModel> helper = MvcHelper.GetHtmlHelper(GetHiddenViewData());
+ helper.ViewData.Model.foo = "DefaultFoo";
+
+ // Act
+ MvcHtmlString html = helper.HiddenFor(m => m.foo, _attributesObjectUnderscoresDictionary);
+
+ // Assert
+ Assert.Equal(@"<input foo-baz=""BazObjValue"" id=""foo"" name=""foo"" type=""hidden"" value=""DefaultFoo"" />", html.ToHtmlString());
+ }
+
+ [Fact]
+ public void HiddenForWithPrefix()
+ {
+ // Arrange
+ HtmlHelper<HiddenModel> helper = MvcHelper.GetHtmlHelper(GetHiddenViewData());
+ helper.ViewData.Model.foo = "fooValue";
+ helper.ViewContext.ViewData.TemplateInfo.HtmlFieldPrefix = "MyPrefix";
+
+ // Act
+ MvcHtmlString html = helper.HiddenFor(m => m.foo);
+
+ // Assert
+ Assert.Equal(@"<input id=""MyPrefix_foo"" name=""MyPrefix.foo"" type=""hidden"" value=""fooValue"" />", html.ToHtmlString());
+ }
+
+ [Fact]
+ public void HiddenForWithPrefixAndEmptyName()
+ {
+ // Arrange
+ HtmlHelper<HiddenModel> helper = MvcHelper.GetHtmlHelper(GetHiddenViewData());
+ helper.ViewContext.ViewData.TemplateInfo.HtmlFieldPrefix = "MyPrefix";
+
+ // Act
+ MvcHtmlString html = helper.HiddenFor(m => m);
+
+ // Assert
+ Assert.Equal(@"<input id=""MyPrefix"" name=""MyPrefix"" type=""hidden"" value=""{ foo = (null) }"" />", html.ToHtmlString());
+ }
+
+ [Fact]
+ public void HiddenForWithViewDataErrors()
+ {
+ // Arrange
+ HtmlHelper<HiddenModel> helper = MvcHelper.GetHtmlHelper(GetHiddenViewDataWithErrors());
+
+ // Act
+ MvcHtmlString html = helper.HiddenFor(m => m.foo, _attributesObjectDictionary);
+
+ // Assert
+ Assert.Equal(@"<input baz=""BazObjValue"" class=""input-validation-error"" id=""foo"" name=""foo"" type=""hidden"" value=""AttemptedValueFoo"" />", html.ToHtmlString());
+ }
+
+ [Fact]
+ public void HiddenForWithViewDataErrorsAndCustomClass()
+ {
+ // Arrange
+ HtmlHelper<HiddenModel> helper = MvcHelper.GetHtmlHelper(GetHiddenViewDataWithErrors());
+
+ // Act
+ MvcHtmlString html = helper.HiddenFor(m => m.foo, new { @class = "foo-class" });
+
+ // Assert
+ Assert.Equal(@"<input class=""input-validation-error foo-class"" id=""foo"" name=""foo"" type=""hidden"" value=""AttemptedValueFoo"" />", html.ToHtmlString());
+ }
+
+ // Password
+
+ [Fact]
+ public void PasswordWithEmptyNameThrows()
+ {
+ // Arrange
+ HtmlHelper helper = MvcHelper.GetHtmlHelper(GetPasswordViewData());
+
+ // Act & Assert
+ Assert.ThrowsArgumentNullOrEmpty(
+ delegate { helper.Password(String.Empty); },
+ "name");
+ }
+
+ [Fact]
+ public void PasswordDictionaryOverridesImplicitParameters()
+ {
+ // Arrange
+ HtmlHelper helper = MvcHelper.GetHtmlHelper(GetPasswordViewData());
+
+ // Act
+ MvcHtmlString html = helper.Password("foo", "Some Value", new { type = "fooType" });
+
+ // Assert
+ Assert.Equal(@"<input id=""foo"" name=""foo"" type=""fooType"" value=""Some Value"" />", html.ToHtmlString());
+ }
+
+ [Fact]
+ public void PasswordExplicitParametersOverrideDictionary()
+ {
+ // Arrange
+ HtmlHelper helper = MvcHelper.GetHtmlHelper(GetPasswordViewData());
+
+ // Act
+ MvcHtmlString html = helper.Password("foo", "Some Value", new { value = "Another Value", name = "bar" });
+
+ // Assert
+ Assert.Equal(@"<input id=""foo"" name=""foo"" type=""password"" value=""Some Value"" />", html.ToHtmlString());
+ }
+
+ [Fact]
+ public void PasswordWithExplicitValue()
+ {
+ // Arrange
+ HtmlHelper helper = MvcHelper.GetHtmlHelper(GetPasswordViewData());
+
+ // Act
+ MvcHtmlString html = helper.Password("foo", "DefaultFoo", (object)null);
+
+ // Assert
+ Assert.Equal(@"<input id=""foo"" name=""foo"" type=""password"" value=""DefaultFoo"" />", html.ToHtmlString());
+ }
+
+ [Fact]
+ public void PasswordWithExplicitValue_Unobtrusive()
+ {
+ // Arrange
+ HtmlHelper helper = MvcHelper.GetHtmlHelper(GetPasswordViewData());
+ helper.ViewContext.ClientValidationEnabled = true;
+ helper.ViewContext.UnobtrusiveJavaScriptEnabled = true;
+ helper.ViewContext.FormContext = new FormContext();
+ helper.ClientValidationRuleFactory = (name, metadata) => new[] { new ModelClientValidationRule { ValidationType = "type", ErrorMessage = "error" } };
+
+ // Act
+ MvcHtmlString html = helper.Password("foo", "DefaultFoo", (object)null);
+
+ // Assert
+ Assert.Equal(@"<input data-val=""true"" data-val-type=""error"" id=""foo"" name=""foo"" type=""password"" value=""DefaultFoo"" />", html.ToHtmlString());
+ }
+
+ [Fact]
+ public void PasswordWithExplicitValueAndAttributesDictionary()
+ {
+ // Arrange
+ HtmlHelper helper = MvcHelper.GetHtmlHelper(GetPasswordViewData());
+
+ // Act
+ MvcHtmlString html = helper.Password("foo", "DefaultFoo", _attributesDictionary);
+
+ // Assert
+ Assert.Equal(@"<input baz=""BazValue"" id=""foo"" name=""foo"" type=""password"" value=""DefaultFoo"" />", html.ToHtmlString());
+ }
+
+ [Fact]
+ public void PasswordWithExplicitValueAndAttributesObject()
+ {
+ // Arrange
+ HtmlHelper helper = MvcHelper.GetHtmlHelper(GetPasswordViewData());
+
+ // Act
+ MvcHtmlString html = helper.Password("foo", "DefaultFoo", _attributesObjectDictionary);
+
+ // Assert
+ Assert.Equal(@"<input baz=""BazObjValue"" id=""foo"" name=""foo"" type=""password"" value=""DefaultFoo"" />", html.ToHtmlString());
+ }
+
+ [Fact]
+ public void PasswordWithExplicitValueNull()
+ {
+ // Arrange
+ HtmlHelper helper = MvcHelper.GetHtmlHelper(GetPasswordViewData());
+
+ // Act
+ MvcHtmlString html = helper.Password("foo", (string)null /* value */, (object)null);
+
+ // Assert
+ Assert.Equal(@"<input id=""foo"" name=""foo"" type=""password"" />", html.ToHtmlString());
+ }
+
+ [Fact]
+ public void PasswordWithImplicitValue()
+ {
+ // Arrange
+ HtmlHelper helper = MvcHelper.GetHtmlHelper(GetPasswordViewData());
+
+ // Act
+ MvcHtmlString html = helper.Password("foo");
+
+ // Assert
+ Assert.Equal(@"<input id=""foo"" name=""foo"" type=""password"" />", html.ToHtmlString());
+ }
+
+ [Fact]
+ public void PasswordWithImplicitValueAndAttributesDictionary()
+ {
+ // Arrange
+ HtmlHelper helper = MvcHelper.GetHtmlHelper(GetPasswordViewData());
+
+ // Act
+ MvcHtmlString html = helper.Password("foo", null, _attributesDictionary);
+
+ // Assert
+ Assert.Equal(@"<input baz=""BazValue"" id=""foo"" name=""foo"" type=""password"" />", html.ToHtmlString());
+ }
+
+ [Fact]
+ public void PasswordWithImplicitValueAndAttributesDictionaryReturnsEmptyValueIfNotFound()
+ {
+ // Arrange
+ HtmlHelper helper = MvcHelper.GetHtmlHelper(GetPasswordViewData());
+
+ // Act
+ MvcHtmlString html = helper.Password("keyNotFound", null, _attributesDictionary);
+
+ // Assert
+ Assert.Equal(@"<input baz=""BazValue"" id=""keyNotFound"" name=""keyNotFound"" type=""password"" />", html.ToHtmlString());
+ }
+
+ [Fact]
+ public void PasswordWithImplicitValueAndAttributesObject()
+ {
+ // Arrange
+ HtmlHelper helper = MvcHelper.GetHtmlHelper(GetPasswordViewData());
+
+ // Act
+ MvcHtmlString html = helper.Password("foo", null, _attributesObjectDictionary);
+
+ // Assert
+ Assert.Equal(@"<input baz=""BazObjValue"" id=""foo"" name=""foo"" type=""password"" />", html.ToHtmlString());
+ }
+
+ [Fact]
+ public void PasswordWithImplicitValueAndAttributesObjectWithUnderscores()
+ {
+ // Arrange
+ HtmlHelper helper = MvcHelper.GetHtmlHelper(GetPasswordViewData());
+
+ // Act
+ MvcHtmlString html = helper.Password("foo", null, _attributesObjectUnderscoresDictionary);
+
+ // Assert
+ Assert.Equal(@"<input foo-baz=""BazObjValue"" id=""foo"" name=""foo"" type=""password"" />", html.ToHtmlString());
+ }
+
+ [Fact]
+ public void PasswordWithNameAndValue()
+ {
+ // Arrange
+ HtmlHelper helper = MvcHelper.GetHtmlHelper(GetHiddenViewData());
+
+ // Act
+ MvcHtmlString html = helper.Password("foo", "fooValue");
+
+ // Assert
+ Assert.Equal(@"<input id=""foo"" name=""foo"" type=""password"" value=""fooValue"" />", html.ToHtmlString());
+ }
+
+ [Fact]
+ public void PasswordWithPrefix()
+ {
+ // Arrange
+ HtmlHelper helper = MvcHelper.GetHtmlHelper(GetHiddenViewData());
+ helper.ViewContext.ViewData.TemplateInfo.HtmlFieldPrefix = "MyPrefix";
+
+ // Act
+ MvcHtmlString html = helper.Password("foo", "fooValue");
+
+ // Assert
+ Assert.Equal(@"<input id=""MyPrefix_foo"" name=""MyPrefix.foo"" type=""password"" value=""fooValue"" />", html.ToHtmlString());
+ }
+
+ [Fact]
+ public void PasswordWithPrefixAndEmptyName()
+ {
+ // Arrange
+ HtmlHelper helper = MvcHelper.GetHtmlHelper(GetHiddenViewData());
+ helper.ViewContext.ViewData.TemplateInfo.HtmlFieldPrefix = "MyPrefix";
+
+ // Act
+ MvcHtmlString html = helper.Password("", "fooValue");
+
+ // Assert
+ Assert.Equal(@"<input id=""MyPrefix"" name=""MyPrefix"" type=""password"" value=""fooValue"" />", html.ToHtmlString());
+ }
+
+ [Fact]
+ public void PasswordWithNullNameThrows()
+ {
+ // Arrange
+ HtmlHelper helper = MvcHelper.GetHtmlHelper(GetPasswordViewData());
+
+ // Act & Assert
+ Assert.ThrowsArgumentNullOrEmpty(
+ delegate { helper.Password(null /* name */); },
+ "name");
+ }
+
+ [Fact]
+ public void PasswordWithViewDataErrors()
+ {
+ // Arrange
+ HtmlHelper helper = MvcHelper.GetHtmlHelper(GetPasswordViewDataWithErrors());
+
+ // Act
+ MvcHtmlString html = helper.Password("foo", null, _attributesObjectDictionary);
+
+ // Assert
+ Assert.Equal(@"<input baz=""BazObjValue"" class=""input-validation-error"" id=""foo"" name=""foo"" type=""password"" />", html.ToHtmlString());
+ }
+
+ [Fact]
+ public void PasswordWithViewDataErrorsAndCustomClass()
+ {
+ // Arrange
+ HtmlHelper helper = MvcHelper.GetHtmlHelper(GetPasswordViewDataWithErrors());
+
+ // Act
+ MvcHtmlString html = helper.Password("foo", null, new { @class = "foo-class" });
+
+ // Assert
+ Assert.Equal(@"<input class=""input-validation-error foo-class"" id=""foo"" name=""foo"" type=""password"" />", html.ToHtmlString());
+ }
+
+ // PasswordFor
+
+ [Fact]
+ public void PasswordForWithNullExpressionThrows()
+ {
+ // Arrange
+ HtmlHelper<FooModel> helper = MvcHelper.GetHtmlHelper(GetPasswordViewData());
+
+ // Act & Assert
+ Assert.ThrowsArgumentNull(
+ () => helper.PasswordFor<FooModel, object>(null),
+ "expression");
+ }
+
+ [Fact]
+ public void PasswordForDictionaryOverridesImplicitParameters()
+ {
+ // Arrange
+ HtmlHelper<FooModel> helper = MvcHelper.GetHtmlHelper(GetPasswordViewData());
+
+ // Act
+ MvcHtmlString html = helper.PasswordFor(m => m.foo, new { type = "fooType" });
+
+ // Assert
+ Assert.Equal(@"<input id=""foo"" name=""foo"" type=""fooType"" />", html.ToHtmlString());
+ }
+
+ [Fact]
+ public void PasswordForExpressionNameOverridesDictionary()
+ {
+ // Arrange
+ HtmlHelper<FooModel> helper = MvcHelper.GetHtmlHelper(GetPasswordViewData());
+
+ // Act
+ MvcHtmlString html = helper.PasswordFor(m => m.foo, new { name = "bar" });
+
+ // Assert
+ Assert.Equal(@"<input id=""foo"" name=""foo"" type=""password"" />", html.ToHtmlString());
+ }
+
+ [Fact]
+ public void PasswordForWithImplicitValue()
+ {
+ // Arrange
+ HtmlHelper<FooModel> helper = MvcHelper.GetHtmlHelper(GetPasswordViewData());
+
+ // Act
+ MvcHtmlString html = helper.PasswordFor(m => m.foo);
+
+ // Assert
+ Assert.Equal(@"<input id=""foo"" name=""foo"" type=""password"" />", html.ToHtmlString());
+ }
+
+ [Fact]
+ public void PasswordForWithImplicitValue_Unobtrusive()
+ {
+ // Arrange
+ HtmlHelper<FooModel> helper = MvcHelper.GetHtmlHelper(GetPasswordViewData());
+ helper.ViewContext.ClientValidationEnabled = true;
+ helper.ViewContext.UnobtrusiveJavaScriptEnabled = true;
+ helper.ViewContext.FormContext = new FormContext();
+ helper.ClientValidationRuleFactory = (name, metadata) => new[] { new ModelClientValidationRule { ValidationType = "type", ErrorMessage = "error" } };
+
+ // Act
+ MvcHtmlString html = helper.PasswordFor(m => m.foo);
+
+ // Assert
+ Assert.Equal(@"<input data-val=""true"" data-val-type=""error"" id=""foo"" name=""foo"" type=""password"" />", html.ToHtmlString());
+ }
+
+ [Fact]
+ public void PasswordForWithDeepValueWithNullModel_Unobtrusive()
+ { // Dev10 Bug #936192
+ // Arrange
+ HtmlHelper<DeepContainerModel> helper = MvcHelper.GetHtmlHelper(new ViewDataDictionary<DeepContainerModel>());
+ helper.ViewContext.ClientValidationEnabled = true;
+ helper.ViewContext.UnobtrusiveJavaScriptEnabled = true;
+ helper.ViewContext.FormContext = new FormContext();
+
+ // Act
+ MvcHtmlString html = helper.PasswordFor(m => m.contained.foo);
+
+ // Assert
+ Assert.Equal(@"<input data-val=""true"" data-val-required=""The foo field is required."" id=""contained_foo"" name=""contained.foo"" type=""password"" />", html.ToHtmlString());
+ }
+
+ [Fact]
+ public void PasswordForWithAttributesDictionary()
+ {
+ // Arrange
+ HtmlHelper<FooModel> helper = MvcHelper.GetHtmlHelper(GetPasswordViewData());
+
+ // Act
+ MvcHtmlString html = helper.PasswordFor(m => m.foo, _attributesDictionary);
+
+ // Assert
+ Assert.Equal(@"<input baz=""BazValue"" id=""foo"" name=""foo"" type=""password"" />", html.ToHtmlString());
+ }
+
+ [Fact]
+ public void PasswordForWithAttributesObject()
+ {
+ // Arrange
+ HtmlHelper<FooModel> helper = MvcHelper.GetHtmlHelper(GetPasswordViewData());
+
+ // Act
+ MvcHtmlString html = helper.PasswordFor(m => m.foo, _attributesObjectDictionary);
+
+ // Assert
+ Assert.Equal(@"<input baz=""BazObjValue"" id=""foo"" name=""foo"" type=""password"" />", html.ToHtmlString());
+ }
+
+ [Fact]
+ public void PasswordForWithAttributesObjectWithUnderscores()
+ {
+ // Arrange
+ HtmlHelper<FooModel> helper = MvcHelper.GetHtmlHelper(GetPasswordViewData());
+
+ // Act
+ MvcHtmlString html = helper.PasswordFor(m => m.foo, _attributesObjectUnderscoresDictionary);
+
+ // Assert
+ Assert.Equal(@"<input foo-baz=""BazObjValue"" id=""foo"" name=""foo"" type=""password"" />", html.ToHtmlString());
+ }
+
+ [Fact]
+ public void PasswordForWithPrefix()
+ {
+ // Arrange
+ HtmlHelper<FooModel> helper = MvcHelper.GetHtmlHelper(GetPasswordViewData());
+ helper.ViewContext.ViewData.TemplateInfo.HtmlFieldPrefix = "MyPrefix";
+
+ // Act
+ MvcHtmlString html = helper.PasswordFor(m => m.foo);
+
+ // Assert
+ Assert.Equal(@"<input id=""MyPrefix_foo"" name=""MyPrefix.foo"" type=""password"" />", html.ToHtmlString());
+ }
+
+ [Fact]
+ public void PasswordForWithViewDataErrors()
+ {
+ // Arrange
+ HtmlHelper<FooModel> helper = MvcHelper.GetHtmlHelper(GetPasswordViewDataWithErrors());
+
+ // Act
+ MvcHtmlString html = helper.PasswordFor(m => m.foo, _attributesObjectDictionary);
+
+ // Assert
+ Assert.Equal(@"<input baz=""BazObjValue"" class=""input-validation-error"" id=""foo"" name=""foo"" type=""password"" />", html.ToHtmlString());
+ }
+
+ [Fact]
+ public void PasswordForWithViewDataErrorsAndCustomClass()
+ {
+ // Arrange
+ HtmlHelper<FooModel> helper = MvcHelper.GetHtmlHelper(GetPasswordViewDataWithErrors());
+
+ // Act
+ MvcHtmlString html = helper.PasswordFor(m => m.foo, new { @class = "foo-class" });
+
+ // Assert
+ Assert.Equal(@"<input class=""input-validation-error foo-class"" id=""foo"" name=""foo"" type=""password"" />", html.ToHtmlString());
+ }
+
+ // RadioButton
+
+ [Fact]
+ public void RadioButtonDictionaryOverridesImplicitParameters()
+ {
+ // Arrange
+ HtmlHelper helper = MvcHelper.GetHtmlHelper(GetRadioButtonViewData());
+
+ // Act
+ MvcHtmlString html = helper.RadioButton("bar", "ViewDataBar", new { @checked = "chucked", value = "baz" });
+
+ // Assert
+ Assert.Equal(@"<input checked=""chucked"" id=""bar"" name=""bar"" type=""radio"" value=""ViewDataBar"" />", html.ToHtmlString());
+ }
+
+ [Fact]
+ public void RadioButtonExplicitParametersOverrideDictionary()
+ {
+ // Arrange
+ HtmlHelper helper = MvcHelper.GetHtmlHelper(GetRadioButtonViewData());
+
+ // Act
+ MvcHtmlString html = helper.RadioButton("bar", "ViewDataBar", false, new { @checked = "checked", value = "baz" });
+
+ // Assert
+ Assert.Equal(@"<input id=""bar"" name=""bar"" type=""radio"" value=""ViewDataBar"" />", html.ToHtmlString());
+ }
+
+ [Fact]
+ public void RadioButtonShouldRespectModelStateAttemptedValue()
+ {
+ // Arrange
+ HtmlHelper helper = MvcHelper.GetHtmlHelper(GetRadioButtonViewData());
+ helper.ViewData.ModelState.SetModelValue("foo", HtmlHelperTest.GetValueProviderResult("ModelStateFoo", "ModelStateFoo"));
+
+ // Act
+ MvcHtmlString html = helper.RadioButton("foo", "ModelStateFoo", false, new { @checked = "checked", value = "baz" });
+
+ // Assert
+ Assert.Equal(@"<input checked=""checked"" id=""foo"" name=""foo"" type=""radio"" value=""ModelStateFoo"" />", html.ToHtmlString());
+ }
+
+ [Fact]
+ public void RadioButtonValueParameterAlwaysRendered()
+ {
+ // Arrange
+ HtmlHelper helper = MvcHelper.GetHtmlHelper(GetRadioButtonViewData());
+
+ // Act
+ MvcHtmlString html = helper.RadioButton("foo", "ViewDataFoo");
+ MvcHtmlString html2 = helper.RadioButton("foo", "fooValue2");
+
+ // Assert
+ Assert.Equal(@"<input checked=""checked"" id=""foo"" name=""foo"" type=""radio"" value=""ViewDataFoo"" />", html.ToHtmlString());
+ Assert.Equal(@"<input id=""foo"" name=""foo"" type=""radio"" value=""fooValue2"" />", html2.ToHtmlString());
+ }
+
+ [Fact]
+ public void RadioButtonWithEmptyNameThrows()
+ {
+ // Arrange
+ HtmlHelper helper = MvcHelper.GetHtmlHelper();
+
+ // Act & Assert
+ Assert.ThrowsArgumentNullOrEmpty(
+ delegate { helper.RadioButton(String.Empty, "value"); },
+ "name");
+ }
+
+ [Fact]
+ public void RadioButtonWithNullValueThrows()
+ {
+ // Arrange
+ HtmlHelper helper = MvcHelper.GetHtmlHelper();
+
+ // Act & Assert
+ Assert.ThrowsArgumentNull(
+ delegate { helper.RadioButton("foo", null); },
+ "value");
+ }
+
+ [Fact]
+ public void RadioButtonWithNameAndValue()
+ {
+ // Arrange
+ HtmlHelper helper = MvcHelper.GetHtmlHelper(GetRadioButtonViewData());
+
+ // Act
+ MvcHtmlString html = helper.RadioButton("foo", "ViewDataFoo");
+
+ // Assert
+ Assert.Equal(@"<input checked=""checked"" id=""foo"" name=""foo"" type=""radio"" value=""ViewDataFoo"" />", html.ToHtmlString());
+ }
+
+ [Fact]
+ public void RadioButtonWithNameAndValue_Unobtrusive()
+ {
+ // Arrange
+ HtmlHelper helper = MvcHelper.GetHtmlHelper(GetRadioButtonViewData());
+ helper.ViewContext.ClientValidationEnabled = true;
+ helper.ViewContext.UnobtrusiveJavaScriptEnabled = true;
+ helper.ViewContext.FormContext = new FormContext();
+ helper.ClientValidationRuleFactory = (name, metadata) => new[] { new ModelClientValidationRule { ValidationType = "type", ErrorMessage = "error" } };
+
+ // Act
+ MvcHtmlString html = helper.RadioButton("foo", "ViewDataFoo");
+
+ // Assert
+ Assert.Equal(@"<input checked=""checked"" data-val=""true"" data-val-type=""error"" id=""foo"" name=""foo"" type=""radio"" value=""ViewDataFoo"" />", html.ToHtmlString());
+ }
+
+ [Fact]
+ public void RadioButtonWithPrefix()
+ {
+ // Arrange
+ HtmlHelper helper = MvcHelper.GetHtmlHelper(GetRadioButtonViewData());
+ helper.ViewContext.ViewData.TemplateInfo.HtmlFieldPrefix = "MyPrefix";
+
+ // Act
+ MvcHtmlString html = helper.RadioButton("foo", "ViewDataFoo");
+
+ // Assert
+ Assert.Equal(@"<input checked=""checked"" id=""MyPrefix_foo"" name=""MyPrefix.foo"" type=""radio"" value=""ViewDataFoo"" />", html.ToHtmlString());
+ }
+
+ [Fact]
+ public void RadioButtonWithPrefixAndEmptyName()
+ {
+ // Arrange
+ HtmlHelper helper = MvcHelper.GetHtmlHelper(GetRadioButtonViewData());
+ helper.ViewContext.ViewData.TemplateInfo.HtmlFieldPrefix = "MyPrefix";
+
+ // Act
+ MvcHtmlString html = helper.RadioButton("", "ViewDataFoo");
+
+ // Assert
+ Assert.Equal(@"<input id=""MyPrefix"" name=""MyPrefix"" type=""radio"" value=""ViewDataFoo"" />", html.ToHtmlString());
+ }
+
+ [Fact]
+ public void RadioButtonWithNameAndValueNotMatched()
+ {
+ // Arrange
+ HtmlHelper helper = MvcHelper.GetHtmlHelper(GetRadioButtonViewData());
+
+ // Act
+ MvcHtmlString html = helper.RadioButton("foo", "fooValue");
+
+ // Assert
+ Assert.Equal(@"<input id=""foo"" name=""foo"" type=""radio"" value=""fooValue"" />", html.ToHtmlString());
+ }
+
+ [Fact]
+ public void RadioButtonWithNameValueUnchecked()
+ {
+ // Arrange
+ HtmlHelper helper = MvcHelper.GetHtmlHelper(GetRadioButtonViewData());
+
+ // Act
+ MvcHtmlString html = helper.RadioButton("bar", "barValue", false /* isChecked */);
+
+ // Assert
+ Assert.Equal(@"<input id=""bar"" name=""bar"" type=""radio"" value=""barValue"" />", html.ToHtmlString());
+ }
+
+ [Fact]
+ public void RadioButtonWithNameValueChecked()
+ {
+ // Arrange
+ HtmlHelper helper = MvcHelper.GetHtmlHelper(GetRadioButtonViewData());
+
+ // Act
+ MvcHtmlString html = helper.RadioButton("bar", "barValue", true /* isChecked */);
+
+ // Assert
+ Assert.Equal(@"<input checked=""checked"" id=""bar"" name=""bar"" type=""radio"" value=""barValue"" />", html.ToHtmlString());
+ }
+
+ [Fact]
+ public void RadioButtonWithObjectAttribute()
+ {
+ // Arrange
+ HtmlHelper helper = MvcHelper.GetHtmlHelper(GetRadioButtonViewData());
+
+ // Act
+ MvcHtmlString html = helper.RadioButton("foo", "fooValue", _attributesObjectDictionary);
+
+ // Assert
+ Assert.Equal(@"<input baz=""BazObjValue"" id=""foo"" name=""foo"" type=""radio"" value=""fooValue"" />", html.ToHtmlString());
+ }
+
+ [Fact]
+ public void RadioButtonWithObjectAttributeWithUnderscores()
+ {
+ // Arrange
+ HtmlHelper helper = MvcHelper.GetHtmlHelper(GetRadioButtonViewData());
+
+ // Act
+ MvcHtmlString html = helper.RadioButton("foo", "fooValue", _attributesObjectUnderscoresDictionary);
+
+ // Assert
+ Assert.Equal(@"<input foo-baz=""BazObjValue"" id=""foo"" name=""foo"" type=""radio"" value=""fooValue"" />", html.ToHtmlString());
+ }
+
+ [Fact]
+ public void RadioButtonWithAttributeDictionary()
+ {
+ // Arrange
+ HtmlHelper helper = MvcHelper.GetHtmlHelper(GetRadioButtonViewData());
+
+ // Act
+ MvcHtmlString html = helper.RadioButton("bar", "barValue", _attributesDictionary);
+
+ // Assert
+ Assert.Equal(@"<input baz=""BazValue"" id=""bar"" name=""bar"" type=""radio"" value=""barValue"" />", html.ToHtmlString());
+ }
+
+ [Fact]
+ public void RadioButtonWithValueUnchecked()
+ {
+ // Arrange
+ HtmlHelper helper = MvcHelper.GetHtmlHelper(GetRadioButtonViewData());
+
+ // Act
+ MvcHtmlString html = helper.RadioButton("foo", "bar", false /* isChecked */);
+
+ // Assert
+ Assert.Equal(@"<input id=""foo"" name=""foo"" type=""radio"" value=""bar"" />", html.ToHtmlString());
+ }
+
+ [Fact]
+ public void RadioButtonWithValueAndObjectAttributeUnchecked()
+ {
+ // Arrange
+ HtmlHelper helper = MvcHelper.GetHtmlHelper(GetRadioButtonViewData());
+
+ // Act
+ MvcHtmlString html = helper.RadioButton("foo", "bar", false /* isChecked */, _attributesObjectDictionary);
+
+ // Assert
+ Assert.Equal(@"<input baz=""BazObjValue"" id=""foo"" name=""foo"" type=""radio"" value=""bar"" />", html.ToHtmlString());
+ }
+
+ [Fact]
+ public void RadioButtonWithValueAndObjectAttributeWithUnderscoresUnchecked()
+ {
+ // Arrange
+ HtmlHelper helper = MvcHelper.GetHtmlHelper(GetRadioButtonViewData());
+
+ // Act
+ MvcHtmlString html = helper.RadioButton("foo", "bar", false /* isChecked */, _attributesObjectUnderscoresDictionary);
+
+ // Assert
+ Assert.Equal(@"<input foo-baz=""BazObjValue"" id=""foo"" name=""foo"" type=""radio"" value=""bar"" />", html.ToHtmlString());
+ }
+
+ [Fact]
+ public void RadioButtonWithValueAndAttributeDictionaryUnchecked()
+ {
+ // Arrange
+ HtmlHelper helper = MvcHelper.GetHtmlHelper(GetRadioButtonViewData());
+
+ // Act
+ MvcHtmlString html = helper.RadioButton("foo", "bar", false /* isChecked */, _attributesDictionary);
+
+ // Assert
+ Assert.Equal(@"<input baz=""BazValue"" id=""foo"" name=""foo"" type=""radio"" value=""bar"" />", html.ToHtmlString());
+ }
+
+ // RadioButtonFor
+
+ [Fact]
+ public void RadioButtonForWithNullExpressionThrows()
+ {
+ // Arrange
+ HtmlHelper<FooBarModel> helper = MvcHelper.GetHtmlHelper(GetRadioButtonViewData());
+
+ // Act & Assert
+ Assert.ThrowsArgumentNull(
+ () => helper.RadioButtonFor<FooBarModel, object>(null, "value"),
+ "expression");
+ }
+
+ [Fact]
+ public void RadioButtonForWithNullValueThrows()
+ {
+ // Arrange
+ HtmlHelper<FooBarModel> helper = MvcHelper.GetHtmlHelper(GetRadioButtonViewData());
+
+ // Act & Assert
+ Assert.ThrowsArgumentNull(
+ () => helper.RadioButtonFor(m => m.foo, null),
+ "value");
+ }
+
+ [Fact]
+ public void RadioButtonForDictionaryOverridesImplicitParameters()
+ {
+ // Arrange
+ HtmlHelper<FooBarModel> helper = MvcHelper.GetHtmlHelper(GetRadioButtonViewData());
+
+ // Act
+ MvcHtmlString html = helper.RadioButtonFor(m => m.bar, "ViewDataBar", new { @checked = "chucked", value = "baz" });
+
+ // Assert
+ Assert.Equal(@"<input checked=""chucked"" id=""bar"" name=""bar"" type=""radio"" value=""ViewDataBar"" />", html.ToHtmlString());
+ }
+
+ [Fact]
+ public void RadioButtonForShouldRespectModelStateAttemptedValue()
+ {
+ // Arrange
+ HtmlHelper<FooBarModel> helper = MvcHelper.GetHtmlHelper(GetRadioButtonViewData());
+ helper.ViewData.ModelState.SetModelValue("foo", HtmlHelperTest.GetValueProviderResult("ModelStateFoo", "ModelStateFoo"));
+
+ // Act
+ MvcHtmlString html = helper.RadioButtonFor(m => m.foo, "ModelStateFoo", new { @checked = "checked", value = "baz" });
+
+ // Assert
+ Assert.Equal(@"<input checked=""checked"" id=""foo"" name=""foo"" type=""radio"" value=""ModelStateFoo"" />", html.ToHtmlString());
+ }
+
+ [Fact]
+ public void RadioButtonForValueParameterAlwaysRendered()
+ {
+ // Arrange
+ HtmlHelper<FooBarModel> helper = MvcHelper.GetHtmlHelper(GetRadioButtonViewData());
+
+ // Act & Assert
+ Assert.Equal(@"<input checked=""checked"" id=""foo"" name=""foo"" type=""radio"" value=""ViewDataFoo"" />",
+ helper.RadioButtonFor(m => m.foo, "ViewDataFoo").ToHtmlString());
+ Assert.Equal(@"<input id=""foo"" name=""foo"" type=""radio"" value=""fooValue2"" />",
+ helper.RadioButtonFor(m => m.foo, "fooValue2").ToHtmlString());
+ }
+
+ [Fact]
+ public void RadioButtonForWithNameAndValue()
+ {
+ // Arrange
+ HtmlHelper<FooBarModel> helper = MvcHelper.GetHtmlHelper(GetRadioButtonViewData());
+
+ // Act
+ MvcHtmlString html = helper.RadioButtonFor(m => m.foo, "ViewDataFoo");
+
+ // Assert
+ Assert.Equal(@"<input checked=""checked"" id=""foo"" name=""foo"" type=""radio"" value=""ViewDataFoo"" />", html.ToHtmlString());
+ }
+
+ [Fact]
+ public void RadioButtonForWithNameAndValue_Unobtrusive()
+ {
+ // Arrange
+ HtmlHelper<FooBarModel> helper = MvcHelper.GetHtmlHelper(GetRadioButtonViewData());
+ helper.ViewContext.ClientValidationEnabled = true;
+ helper.ViewContext.UnobtrusiveJavaScriptEnabled = true;
+ helper.ViewContext.FormContext = new FormContext();
+ helper.ClientValidationRuleFactory = (name, metadata) => new[] { new ModelClientValidationRule { ValidationType = "type", ErrorMessage = "error" } };
+
+ // Act
+ MvcHtmlString html = helper.RadioButtonFor(m => m.foo, "ViewDataFoo");
+
+ // Assert
+ Assert.Equal(@"<input checked=""checked"" data-val=""true"" data-val-type=""error"" id=""foo"" name=""foo"" type=""radio"" value=""ViewDataFoo"" />", html.ToHtmlString());
+ }
+
+ [Fact]
+ public void RadioButtonForWithPrefix()
+ {
+ // Arrange
+ HtmlHelper<FooBarModel> helper = MvcHelper.GetHtmlHelper(GetRadioButtonViewData());
+ helper.ViewContext.ViewData.TemplateInfo.HtmlFieldPrefix = "MyPrefix";
+
+ // Act
+ MvcHtmlString html = helper.RadioButtonFor(m => m.foo, "ViewDataFoo");
+
+ // Assert
+ Assert.Equal(@"<input id=""MyPrefix_foo"" name=""MyPrefix.foo"" type=""radio"" value=""ViewDataFoo"" />", html.ToHtmlString());
+ }
+
+ [Fact]
+ public void RadioButtonForWithNameAndValueNotMatched()
+ {
+ // Arrange
+ HtmlHelper<FooBarModel> helper = MvcHelper.GetHtmlHelper(GetRadioButtonViewData());
+
+ // Act
+ MvcHtmlString html = helper.RadioButtonFor(m => m.foo, "fooValue");
+
+ // Assert
+ Assert.Equal(@"<input id=""foo"" name=""foo"" type=""radio"" value=""fooValue"" />", html.ToHtmlString());
+ }
+
+ [Fact]
+ public void RadioButtonForWithObjectAttribute()
+ {
+ // Arrange
+ HtmlHelper<FooBarModel> helper = MvcHelper.GetHtmlHelper(GetRadioButtonViewData());
+
+ // Act
+ MvcHtmlString html = helper.RadioButtonFor(m => m.foo, "fooValue", _attributesObjectDictionary);
+
+ // Assert
+ Assert.Equal(@"<input baz=""BazObjValue"" id=""foo"" name=""foo"" type=""radio"" value=""fooValue"" />", html.ToHtmlString());
+ }
+
+ [Fact]
+ public void RadioButtonForWithObjectAttributeWithUnderscores()
+ {
+ // Arrange
+ HtmlHelper<FooBarModel> helper = MvcHelper.GetHtmlHelper(GetRadioButtonViewData());
+
+ // Act
+ MvcHtmlString html = helper.RadioButtonFor(m => m.foo, "fooValue", _attributesObjectUnderscoresDictionary);
+
+ // Assert
+ Assert.Equal(@"<input foo-baz=""BazObjValue"" id=""foo"" name=""foo"" type=""radio"" value=""fooValue"" />", html.ToHtmlString());
+ }
+
+ [Fact]
+ public void RadioButtonForWithAttributeDictionary()
+ {
+ // Arrange
+ HtmlHelper<FooBarModel> helper = MvcHelper.GetHtmlHelper(GetRadioButtonViewData());
+
+ // Act
+ MvcHtmlString html = helper.RadioButtonFor(m => m.bar, "barValue", _attributesDictionary);
+
+ // Assert
+ Assert.Equal(@"<input baz=""BazValue"" id=""bar"" name=""bar"" type=""radio"" value=""barValue"" />", html.ToHtmlString());
+ }
+
+ // TextBox
+
+ [Fact]
+ public void TextBoxDictionaryOverridesImplicitValues()
+ {
+ // Arrange
+ HtmlHelper helper = MvcHelper.GetHtmlHelper(GetTextBoxViewData());
+
+ // Act
+ MvcHtmlString html = helper.TextBox("foo", "DefaultFoo", new { type = "fooType" });
+
+ // Assert
+ Assert.Equal(@"<input id=""foo"" name=""foo"" type=""fooType"" value=""DefaultFoo"" />", html.ToHtmlString());
+ }
+
+ [Fact]
+ public void TextBoxExplicitParametersOverrideDictionaryValues()
+ {
+ // Arrange
+ HtmlHelper helper = MvcHelper.GetHtmlHelper(GetTextBoxViewData());
+
+ // Act
+ MvcHtmlString html = helper.TextBox("foo", "DefaultFoo", new { value = "Some other value" });
+
+ // Assert
+ Assert.Equal(@"<input id=""foo"" name=""foo"" type=""text"" value=""DefaultFoo"" />", html.ToHtmlString());
+ }
+
+ [Fact]
+ public void TextBoxWithDotReplacementForId()
+ {
+ // Arrange
+ HtmlHelper helper = MvcHelper.GetHtmlHelper(GetTextBoxViewData());
+
+ // Act
+ MvcHtmlString html = helper.TextBox("foo.bar.baz", null);
+
+ // Assert
+ Assert.Equal(@"<input id=""foo_bar_baz"" name=""foo.bar.baz"" type=""text"" value="""" />", html.ToHtmlString());
+ }
+
+ [Fact]
+ public void TextBoxWithEmptyNameThrows()
+ {
+ // Arrange
+ HtmlHelper helper = MvcHelper.GetHtmlHelper(GetTextBoxViewData());
+
+ // Act & Assert
+ Assert.ThrowsArgumentNullOrEmpty(
+ delegate { helper.TextBox(String.Empty); },
+ "name");
+ }
+
+ [Fact]
+ public void TextBoxWithExplicitValue()
+ {
+ // Arrange
+ HtmlHelper helper = MvcHelper.GetHtmlHelper(GetTextBoxViewData());
+
+ // Act
+ MvcHtmlString html = helper.TextBox("foo", "DefaultFoo", (object)null);
+
+ // Assert
+ Assert.Equal(@"<input id=""foo"" name=""foo"" type=""text"" value=""DefaultFoo"" />", html.ToHtmlString());
+ }
+
+ [Fact]
+ public void TextBoxWithExplicitValue_Unobtrusive()
+ {
+ // Arrange
+ HtmlHelper helper = MvcHelper.GetHtmlHelper(GetTextBoxViewData());
+ helper.ViewContext.ClientValidationEnabled = true;
+ helper.ViewContext.UnobtrusiveJavaScriptEnabled = true;
+ helper.ViewContext.FormContext = new FormContext();
+ helper.ClientValidationRuleFactory = (name, metadata) => new[] { new ModelClientValidationRule { ValidationType = "type", ErrorMessage = "error" } };
+
+ // Act
+ MvcHtmlString html = helper.TextBox("foo", "DefaultFoo", (object)null);
+
+ // Assert
+ Assert.Equal(@"<input data-val=""true"" data-val-type=""error"" id=""foo"" name=""foo"" type=""text"" value=""DefaultFoo"" />", html.ToHtmlString());
+ }
+
+ [Fact]
+ public void TextBoxWithExplicitValueAndAttributesDictionary()
+ {
+ // Arrange
+ HtmlHelper helper = MvcHelper.GetHtmlHelper(GetTextBoxViewData());
+
+ // Act
+ MvcHtmlString html = helper.TextBox("foo", "DefaultFoo", _attributesDictionary);
+
+ // Assert
+ Assert.Equal(@"<input baz=""BazValue"" id=""foo"" name=""foo"" type=""text"" value=""DefaultFoo"" />", html.ToHtmlString());
+ }
+
+ [Fact]
+ public void TextBoxWithExplicitValueAndAttributesObject()
+ {
+ // Arrange
+ HtmlHelper helper = MvcHelper.GetHtmlHelper(GetTextBoxViewData());
+
+ // Act
+ MvcHtmlString html = helper.TextBox("foo", "DefaultFoo", _attributesObjectDictionary);
+
+ // Assert
+ Assert.Equal(@"<input baz=""BazObjValue"" id=""foo"" name=""foo"" type=""text"" value=""DefaultFoo"" />", html.ToHtmlString());
+ }
+
+ [Fact]
+ public void TextBoxWithExplicitValueNull()
+ {
+ // Arrange
+ HtmlHelper helper = MvcHelper.GetHtmlHelper(GetTextBoxViewData());
+
+ // Act
+ MvcHtmlString html = helper.TextBox("foo", (string)null /* value */, (object)null);
+
+ // Assert
+ Assert.Equal(@"<input id=""foo"" name=""foo"" type=""text"" value=""ViewDataFoo"" />", html.ToHtmlString());
+ }
+
+ [Fact]
+ public void TextBoxWithImplicitValue()
+ {
+ // Arrange
+ HtmlHelper helper = MvcHelper.GetHtmlHelper(GetTextBoxViewData());
+
+ // Act
+ MvcHtmlString html = helper.TextBox("foo");
+
+ // Assert
+ Assert.Equal(@"<input id=""foo"" name=""foo"" type=""text"" value=""ViewDataFoo"" />", html.ToHtmlString());
+ }
+
+ [Fact]
+ public void TextBoxWithImplicitValueAndAttributesDictionary()
+ {
+ // Arrange
+ HtmlHelper helper = MvcHelper.GetHtmlHelper(GetTextBoxViewData());
+
+ // Act
+ MvcHtmlString html = helper.TextBox("foo", null, _attributesDictionary);
+
+ // Assert
+ Assert.Equal(@"<input baz=""BazValue"" id=""foo"" name=""foo"" type=""text"" value=""ViewDataFoo"" />", html.ToHtmlString());
+ }
+
+ [Fact]
+ public void TextBoxWithImplicitValueAndAttributesDictionaryReturnsEmptyValueIfNotFound()
+ {
+ // Arrange
+ HtmlHelper helper = MvcHelper.GetHtmlHelper(GetTextBoxViewData());
+
+ // Act
+ MvcHtmlString html = helper.TextBox("keyNotFound", null, _attributesDictionary);
+
+ // Assert
+ Assert.Equal(@"<input baz=""BazValue"" id=""keyNotFound"" name=""keyNotFound"" type=""text"" value="""" />", html.ToHtmlString());
+ }
+
+ [Fact]
+ public void TextBoxWithImplicitValueAndAttributesObject()
+ {
+ // Arrange
+ HtmlHelper helper = MvcHelper.GetHtmlHelper(GetTextBoxViewData());
+
+ // Act
+ MvcHtmlString html = helper.TextBox("foo", null, _attributesObjectDictionary);
+
+ // Assert
+ Assert.Equal(@"<input baz=""BazObjValue"" id=""foo"" name=""foo"" type=""text"" value=""ViewDataFoo"" />", html.ToHtmlString());
+ }
+
+ [Fact]
+ public void TextBoxWithImplicitValueAndAttributesObjectWithUnderscores()
+ {
+ // Arrange
+ HtmlHelper helper = MvcHelper.GetHtmlHelper(GetTextBoxViewData());
+
+ // Act
+ MvcHtmlString html = helper.TextBox("foo", null, _attributesObjectUnderscoresDictionary);
+
+ // Assert
+ Assert.Equal(@"<input foo-baz=""BazObjValue"" id=""foo"" name=""foo"" type=""text"" value=""ViewDataFoo"" />", html.ToHtmlString());
+ }
+
+ [Fact]
+ public void TextBoxWithNullNameThrows()
+ {
+ // Arrange
+ HtmlHelper helper = MvcHelper.GetHtmlHelper(GetTextBoxViewData());
+
+ // Act & Assert
+ Assert.ThrowsArgumentNullOrEmpty(
+ delegate { helper.TextBox(null /* name */); },
+ "name");
+ }
+
+ [Fact]
+ public void TextBoxWithNameAndValue()
+ {
+ // Arrange
+ HtmlHelper helper = MvcHelper.GetHtmlHelper(GetHiddenViewData());
+
+ // Act
+ MvcHtmlString html = helper.TextBox("foo", "fooValue");
+
+ // Assert
+ Assert.Equal(@"<input id=""foo"" name=""foo"" type=""text"" value=""fooValue"" />", html.ToHtmlString());
+ }
+
+ [Fact]
+ public void TextBoxWithPrefix()
+ {
+ // Arrange
+ HtmlHelper helper = MvcHelper.GetHtmlHelper(GetHiddenViewData());
+ helper.ViewContext.ViewData.TemplateInfo.HtmlFieldPrefix = "MyPrefix";
+
+ // Act
+ MvcHtmlString html = helper.TextBox("foo", "fooValue");
+
+ // Assert
+ Assert.Equal(@"<input id=""MyPrefix_foo"" name=""MyPrefix.foo"" type=""text"" value=""fooValue"" />", html.ToHtmlString());
+ }
+
+ [Fact]
+ public void TextBoxWithPrefixAndEmptyName()
+ {
+ // Arrange
+ HtmlHelper helper = MvcHelper.GetHtmlHelper(GetHiddenViewData());
+ helper.ViewContext.ViewData.TemplateInfo.HtmlFieldPrefix = "MyPrefix";
+
+ // Act
+ MvcHtmlString html = helper.TextBox("", "fooValue");
+
+ // Assert
+ Assert.Equal(@"<input id=""MyPrefix"" name=""MyPrefix"" type=""text"" value=""fooValue"" />", html.ToHtmlString());
+ }
+
+ [Fact]
+ public void TextBoxWithViewDataErrors()
+ {
+ // Arrange
+ HtmlHelper helper = MvcHelper.GetHtmlHelper(GetTextBoxViewDataWithErrors());
+
+ // Act
+ MvcHtmlString html = helper.TextBox("foo", null, _attributesObjectDictionary);
+
+ // Assert
+ Assert.Equal(@"<input baz=""BazObjValue"" class=""input-validation-error"" id=""foo"" name=""foo"" type=""text"" value=""AttemptedValueFoo"" />", html.ToHtmlString());
+ }
+
+ [Fact]
+ public void TextBoxWithViewDataErrorsAndCustomClass()
+ {
+ // Arrange
+ HtmlHelper helper = MvcHelper.GetHtmlHelper(GetTextBoxViewDataWithErrors());
+
+ // Act
+ MvcHtmlString html = helper.TextBox("foo", null, new { @class = "foo-class" });
+
+ // Assert
+ Assert.Equal(@"<input class=""input-validation-error foo-class"" id=""foo"" name=""foo"" type=""text"" value=""AttemptedValueFoo"" />", html.ToHtmlString());
+ }
+
+ // TextBoxFor
+
+ [Fact]
+ public void TextBoxForWithNullExpressionThrows()
+ {
+ // Arrange
+ HtmlHelper<FooBarModel> helper = MvcHelper.GetHtmlHelper(GetTextBoxViewData());
+
+ // Act & Assert
+ Assert.ThrowsArgumentNull(
+ () => helper.TextBoxFor<FooBarModel, object>(null /* expression */),
+ "expression"
+ );
+ }
+
+ [Fact]
+ public void TextBoxForWithSimpleExpression()
+ {
+ // Arrange
+ HtmlHelper<FooBarModel> helper = MvcHelper.GetHtmlHelper(GetTextBoxViewData());
+
+ // Act
+ MvcHtmlString html = helper.TextBoxFor(m => m.foo);
+
+ // Assert
+ Assert.Equal(@"<input id=""foo"" name=""foo"" type=""text"" value=""ViewItemFoo"" />", html.ToHtmlString());
+ }
+
+ [Fact]
+ public void TextBoxForWithSimpleExpression_Unobtrusive()
+ {
+ // Arrange
+ HtmlHelper<FooBarModel> helper = MvcHelper.GetHtmlHelper(GetTextBoxViewData());
+ helper.ViewContext.ClientValidationEnabled = true;
+ helper.ViewContext.UnobtrusiveJavaScriptEnabled = true;
+ helper.ViewContext.FormContext = new FormContext();
+ helper.ClientValidationRuleFactory = (name, metadata) => new[] { new ModelClientValidationRule { ValidationType = "type", ErrorMessage = "error" } };
+
+ // Act
+ MvcHtmlString html = helper.TextBoxFor(m => m.foo);
+
+ // Assert
+ Assert.Equal(@"<input data-val=""true"" data-val-type=""error"" id=""foo"" name=""foo"" type=""text"" value=""ViewItemFoo"" />", html.ToHtmlString());
+ }
+
+ [Fact]
+ public void TextBoxForWithAttributesDictionary()
+ {
+ // Arrange
+ HtmlHelper<FooBarModel> helper = MvcHelper.GetHtmlHelper(GetTextBoxViewData());
+
+ // Act
+ MvcHtmlString html = helper.TextBoxFor(m => m.foo, _attributesDictionary);
+
+ // Assert
+ Assert.Equal(@"<input baz=""BazValue"" id=""foo"" name=""foo"" type=""text"" value=""ViewItemFoo"" />", html.ToHtmlString());
+ }
+
+ [Fact]
+ public void TextBoxForWithAttributesObject()
+ {
+ // Arrange
+ HtmlHelper<FooBarModel> helper = MvcHelper.GetHtmlHelper(GetTextBoxViewData());
+
+ // Act
+ MvcHtmlString html = helper.TextBoxFor(m => m.foo, _attributesObjectDictionary);
+
+ // Assert
+ Assert.Equal(@"<input baz=""BazObjValue"" id=""foo"" name=""foo"" type=""text"" value=""ViewItemFoo"" />", html.ToHtmlString());
+ }
+
+ [Fact]
+ public void TextBoxForWithAttributesObjectWithUnderscores()
+ {
+ // Arrange
+ HtmlHelper<FooBarModel> helper = MvcHelper.GetHtmlHelper(GetTextBoxViewData());
+
+ // Act
+ MvcHtmlString html = helper.TextBoxFor(m => m.foo, _attributesObjectUnderscoresDictionary);
+
+ // Assert
+ Assert.Equal(@"<input foo-baz=""BazObjValue"" id=""foo"" name=""foo"" type=""text"" value=""ViewItemFoo"" />", html.ToHtmlString());
+ }
+
+ [Fact]
+ public void TextBoxForWithPrefix()
+ {
+ // Arrange
+ HtmlHelper<FooBarModel> helper = MvcHelper.GetHtmlHelper(GetTextBoxViewData());
+ helper.ViewContext.ViewData.TemplateInfo.HtmlFieldPrefix = "MyPrefix";
+
+ // Act
+ MvcHtmlString html = helper.TextBoxFor(m => m.foo);
+
+ // Assert
+ Assert.Equal(@"<input id=""MyPrefix_foo"" name=""MyPrefix.foo"" type=""text"" value=""ViewItemFoo"" />", html.ToHtmlString());
+ }
+
+ [Fact]
+ public void TextBoxForWithPrefixAndEmptyName()
+ {
+ // Arrange
+ HtmlHelper<FooBarModel> helper = MvcHelper.GetHtmlHelper(GetTextBoxViewData());
+ helper.ViewContext.ViewData.TemplateInfo.HtmlFieldPrefix = "MyPrefix";
+
+ // Act
+ MvcHtmlString html = helper.TextBoxFor(m => m);
+
+ // Assert
+ Assert.Equal(@"<input id=""MyPrefix"" name=""MyPrefix"" type=""text"" value=""{ foo = ViewItemFoo, bar = ViewItemBar }"" />", html.ToHtmlString());
+ }
+
+ [Fact]
+ public void TextBoxForWithErrors()
+ {
+ // Arrange
+ HtmlHelper<FooBarModel> helper = MvcHelper.GetHtmlHelper(GetTextBoxViewDataWithErrors());
+
+ // Act
+ MvcHtmlString html = helper.TextBoxFor(m => m.foo, _attributesObjectDictionary);
+
+ // Assert
+ Assert.Equal(@"<input baz=""BazObjValue"" class=""input-validation-error"" id=""foo"" name=""foo"" type=""text"" value=""AttemptedValueFoo"" />", html.ToHtmlString());
+ }
+
+ [Fact]
+ public void TextBoxForWithErrorsAndCustomClass()
+ {
+ // Arrange
+ HtmlHelper<FooBarModel> helper = MvcHelper.GetHtmlHelper(GetTextBoxViewDataWithErrors());
+
+ // Act
+ MvcHtmlString html = helper.TextBoxFor(m => m.foo, new { @class = "foo-class" });
+
+ // Assert
+ Assert.Equal(@"<input class=""input-validation-error foo-class"" id=""foo"" name=""foo"" type=""text"" value=""AttemptedValueFoo"" />", html.ToHtmlString());
+ }
+
+ [Fact]
+ public void TextBoxHelpersFormatValue()
+ {
+ // Arrange
+ DateTime dt = new DateTime(1900, 1, 1, 0, 0, 0);
+ HtmlHelper helper = MvcHelper.GetHtmlHelper(new ViewDataDictionary { { "viewDataDate", dt } });
+
+ ViewDataDictionary<DateModel> viewData = new ViewDataDictionary<DateModel>() { Model = new DateModel { date = dt } };
+ HtmlHelper<DateModel> dateModelhelper = MvcHelper.GetHtmlHelper(viewData);
+
+ var tests = new[]
+ {
+ // TextBox(name, value, format)
+ new
+ {
+ Html = @"<input id=""viewDataDate"" name=""viewDataDate"" type=""text"" value=""-1900/01/01 12:00:00 AM-"" />",
+ Action = new Func<MvcHtmlString>(() => helper.TextBox("viewDataDate", null, "-{0}-"))
+ },
+ // TextBox(name, value, format)
+ new
+ {
+ Html = @"<input id=""date"" name=""date"" type=""text"" value=""-1900/01/01 12:00:00 AM-"" />",
+ Action = new Func<MvcHtmlString>(() => helper.TextBox("date", dt, "-{0}-"))
+ },
+ // TextBox(name, value, format, hmtlAttributes)
+ new
+ {
+ Html = @"<input id=""date"" name=""date"" type=""text"" value=""-1900/01/01 12:00:00 AM-"" />",
+ Action = new Func<MvcHtmlString>(() => helper.TextBox("date", dt, "-{0}-", (object)null))
+ },
+ // TextBox(name, value, format, hmtlAttributes)
+ new
+ {
+ Html = @"<input id=""date"" name=""date"" type=""text"" value=""-1900/01/01 12:00:00 AM-"" />",
+ Action = new Func<MvcHtmlString>(() => helper.TextBox("date", dt, "-{0}-", new RouteValueDictionary()))
+ },
+ // TextBoxFor(expression, format)
+ new
+ {
+ Html = @"<input id=""date"" name=""date"" type=""text"" value=""-1900/01/01 12:00:00 AM-"" />",
+ Action = new Func<MvcHtmlString>(() => dateModelhelper.TextBoxFor(m => m.date, "-{0}-"))
+ },
+ // TextBoxFor(expression, format, hmtlAttributes)
+ new
+ {
+ Html = @"<input id=""date"" name=""date"" type=""text"" value=""-1900/01/01 12:00:00 AM-"" />",
+ Action = new Func<MvcHtmlString>(() => dateModelhelper.TextBoxFor(m => m.date, "-{0}-", (object)null))
+ },
+ // TextBoxFor(expression, format, hmtlAttributes)
+ new
+ {
+ Html = @"<input id=""date"" name=""date"" type=""text"" value=""-1900/01/01 12:00:00 AM-"" />",
+ Action = new Func<MvcHtmlString>(() => dateModelhelper.TextBoxFor(m => m.date, "-{0}-", new RouteValueDictionary()))
+ }
+ };
+
+ // Act && Assert
+ using (HtmlHelperTest.ReplaceCulture("en-ZA", "en-US"))
+ {
+ foreach (var test in tests)
+ {
+ Assert.Equal(test.Html, test.Action().ToHtmlString());
+ }
+ }
+ }
+
+ // MODELS
+ private class FooModel
+ {
+ public string foo { get; set; }
+
+ public override string ToString()
+ {
+ return String.Format("{{ foo = {0} }}", foo ?? "(null)");
+ }
+ }
+
+ private class FooBarModel : FooModel
+ {
+ public string bar { get; set; }
+
+ public override string ToString()
+ {
+ return String.Format("{{ foo = {0}, bar = {1} }}", foo ?? "(null)", bar ?? "(null)");
+ }
+ }
+
+ private class FooBarBazModel
+ {
+ public bool foo { get; set; }
+ public bool bar { get; set; }
+ public bool baz { get; set; }
+
+ public override string ToString()
+ {
+ return String.Format("{{ foo = {0}, bar = {1}, baz = {2} }}", foo, bar, baz);
+ }
+ }
+
+ private class ShallowModel
+ {
+ [Required]
+ public string foo { get; set; }
+ }
+
+ private class DeepContainerModel
+ {
+ public ShallowModel contained { get; set; }
+ }
+
+ private class HiddenModel : FooModel
+ {
+ public byte[] bytes { get; set; }
+ public Binary binary { get; set; }
+ }
+
+ private class DateModel
+ {
+ public DateTime date { get; set; }
+ }
+
+ // CHECKBOX
+ private static ViewDataDictionary<FooBarBazModel> GetCheckBoxViewData()
+ {
+ ViewDataDictionary<FooBarBazModel> viewData = new ViewDataDictionary<FooBarBazModel> { { "foo", true }, { "bar", "NotTrue" }, { "baz", false } };
+ return viewData;
+ }
+
+ // HIDDEN
+ private static ViewDataDictionary<HiddenModel> GetHiddenViewData()
+ {
+ return new ViewDataDictionary<HiddenModel>(new HiddenModel()) { { "foo", "ViewDataFoo" } };
+ }
+
+ private static ViewDataDictionary<HiddenModel> GetHiddenViewDataWithErrors()
+ {
+ ViewDataDictionary<HiddenModel> viewData = new ViewDataDictionary<HiddenModel> { { "foo", "ViewDataFoo" } };
+ viewData.Model = new HiddenModel();
+ ModelState modelStateFoo = new ModelState();
+ modelStateFoo.Errors.Add(new ModelError("foo error 1"));
+ modelStateFoo.Errors.Add(new ModelError("foo error 2"));
+ viewData.ModelState["foo"] = modelStateFoo;
+ modelStateFoo.Value = HtmlHelperTest.GetValueProviderResult("AttemptedValueFoo", "AttemptedValueFoo");
+
+ return viewData;
+ }
+
+ // PASSWORD
+ private static ViewDataDictionary<FooModel> GetPasswordViewData()
+ {
+ return new ViewDataDictionary<FooModel> { { "foo", "ViewDataFoo" } };
+ }
+
+ private static ViewDataDictionary<FooModel> GetPasswordViewDataWithErrors()
+ {
+ ViewDataDictionary<FooModel> viewData = new ViewDataDictionary<FooModel> { { "foo", "ViewDataFoo" } };
+ ModelState modelStateFoo = new ModelState();
+ modelStateFoo.Errors.Add(new ModelError("foo error 1"));
+ modelStateFoo.Errors.Add(new ModelError("foo error 2"));
+ viewData.ModelState["foo"] = modelStateFoo;
+ modelStateFoo.Value = HtmlHelperTest.GetValueProviderResult("AttemptedValueFoo", "AttemptedValueFoo");
+
+ return viewData;
+ }
+
+ // RADIO
+ private static ViewDataDictionary<FooBarModel> GetRadioButtonViewData()
+ {
+ ViewDataDictionary<FooBarModel> viewData = new ViewDataDictionary<FooBarModel> { { "foo", "ViewDataFoo" } };
+ viewData.Model = new FooBarModel { foo = "ViewItemFoo", bar = "ViewItemBar" };
+ ModelState modelState = new ModelState();
+ modelState.Value = HtmlHelperTest.GetValueProviderResult("ViewDataFoo", "ViewDataFoo");
+ viewData.ModelState["foo"] = modelState;
+
+ return viewData;
+ }
+
+ // TEXTBOX
+ private static readonly RouteValueDictionary _attributesDictionary = new RouteValueDictionary(new { baz = "BazValue" });
+ private static readonly object _attributesObjectDictionary = new { baz = "BazObjValue" };
+ private static readonly object _attributesObjectUnderscoresDictionary = new { foo_baz = "BazObjValue" };
+
+ private static ViewDataDictionary<FooBarModel> GetTextBoxViewData()
+ {
+ ViewDataDictionary<FooBarModel> viewData = new ViewDataDictionary<FooBarModel> { { "foo", "ViewDataFoo" } };
+ viewData.Model = new FooBarModel { foo = "ViewItemFoo", bar = "ViewItemBar" };
+
+ return viewData;
+ }
+
+ private static ViewDataDictionary<FooBarModel> GetTextBoxViewDataWithErrors()
+ {
+ ViewDataDictionary<FooBarModel> viewData = new ViewDataDictionary<FooBarModel> { { "foo", "ViewDataFoo" } };
+ viewData.Model = new FooBarModel { foo = "ViewItemFoo", bar = "ViewItemBar" };
+ ModelState modelStateFoo = new ModelState();
+ modelStateFoo.Errors.Add(new ModelError("foo error 1"));
+ modelStateFoo.Errors.Add(new ModelError("foo error 2"));
+ viewData.ModelState["foo"] = modelStateFoo;
+ modelStateFoo.Value = HtmlHelperTest.GetValueProviderResult(new string[] { "AttemptedValueFoo" }, "AttemptedValueFoo");
+
+ return viewData;
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Html/Test/LabelExtensionsTest.cs b/test/System.Web.Mvc.Test/Html/Test/LabelExtensionsTest.cs
new file mode 100644
index 00000000..fddfdd6e
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Html/Test/LabelExtensionsTest.cs
@@ -0,0 +1,516 @@
+using System.Collections.Generic;
+using System.Linq.Expressions;
+using Moq;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Mvc.Html.Test
+{
+ public class LabelExtensionsTest
+ {
+ Mock<ModelMetadataProvider> metadataProvider;
+ Mock<ModelMetadata> metadata;
+ ViewDataDictionary viewData;
+ Mock<ViewContext> viewContext;
+ Mock<IViewDataContainer> viewDataContainer;
+ HtmlHelper<object> html;
+
+ public LabelExtensionsTest()
+ {
+ metadataProvider = new Mock<ModelMetadataProvider>();
+ metadata = new Mock<ModelMetadata>(metadataProvider.Object, null, null, typeof(object), null);
+ viewData = new ViewDataDictionary();
+
+ viewContext = new Mock<ViewContext>();
+ viewContext.Setup(c => c.ViewData).Returns(viewData);
+
+ viewDataContainer = new Mock<IViewDataContainer>();
+ viewDataContainer.Setup(c => c.ViewData).Returns(viewData);
+
+ html = new HtmlHelper<object>(viewContext.Object, viewDataContainer.Object);
+
+ metadataProvider.Setup(p => p.GetMetadataForProperties(It.IsAny<object>(), It.IsAny<Type>()))
+ .Returns(new ModelMetadata[0]);
+ metadataProvider.Setup(p => p.GetMetadataForProperty(It.IsAny<Func<object>>(), It.IsAny<Type>(), It.IsAny<string>()))
+ .Returns(metadata.Object);
+ metadataProvider.Setup(p => p.GetMetadataForType(It.IsAny<Func<object>>(), It.IsAny<Type>()))
+ .Returns(metadata.Object);
+ }
+
+ // Label tests
+
+ [Fact]
+ public void LabelNullExpressionThrows()
+ {
+ // Act & Assert
+ Assert.ThrowsArgumentNull(
+ () => html.Label(null),
+ "expression");
+ }
+
+ [Fact]
+ public void LabelViewDataNotFound()
+ {
+ // Act
+ MvcHtmlString result = html.Label("PropertyName", null, null, metadataProvider.Object);
+
+ // Assert
+ Assert.Equal(@"<label for=""PropertyName"">PropertyName</label>", result.ToHtmlString());
+ }
+
+ [Fact]
+ public void LabelViewDataNull()
+ {
+ // Act
+ viewData["PropertyName"] = null;
+ MvcHtmlString result = html.Label("PropertyName", null, null, metadataProvider.Object);
+
+ // Assert
+ Assert.Equal(@"<label for=""PropertyName"">PropertyName</label>", result.ToHtmlString());
+ }
+
+ class Model
+ {
+ public string PropertyName { get; set; }
+ }
+
+ [Fact]
+ public void LabelViewDataFromPropertyGetsActualPropertyType()
+ {
+ // Arrange
+ Model model = new Model { PropertyName = "propertyValue" };
+ HtmlHelper<Model> html = new HtmlHelper<Model>(viewContext.Object, viewDataContainer.Object);
+ viewData.Model = model;
+ metadataProvider.Setup(p => p.GetMetadataForProperty(It.IsAny<Func<object>>(), typeof(Model), "PropertyName"))
+ .Returns(metadata.Object)
+ .Verifiable();
+
+ // Act
+ html.Label("PropertyName", null, null, metadataProvider.Object);
+
+ // Assert
+ metadataProvider.Verify();
+ }
+
+ [Fact]
+ public void LabelUsesTemplateInfoPrefix()
+ {
+ // Arrange
+ viewData.TemplateInfo.HtmlFieldPrefix = "Prefix";
+
+ // Act
+ MvcHtmlString result = html.Label("PropertyName", null, null, metadataProvider.Object);
+
+ // Assert
+ Assert.Equal(@"<label for=""Prefix_PropertyName"">PropertyName</label>", result.ToHtmlString());
+ }
+
+ [Fact]
+ public void LabelUsesLabelTextBeforeMetadata()
+ {
+ // Arrange
+ metadata = new Mock<ModelMetadata>(metadataProvider.Object, null, null, typeof(object), "Custom property name from metadata");
+ metadataProvider.Setup(p => p.GetMetadataForType(It.IsAny<Func<object>>(), It.IsAny<Type>()))
+ .Returns(metadata.Object);
+
+ //Act
+ MvcHtmlString result = html.Label("PropertyName", "Label Text", null, metadataProvider.Object);
+
+ // Assert
+ Assert.Equal(@"<label for=""PropertyName"">Label Text</label>", result.ToHtmlString());
+ }
+
+ [Fact]
+ public void LabelUsesMetadataForDisplayTextWhenLabelTextIsNull()
+ {
+ // Arrange
+ metadata.Setup(m => m.DisplayName).Returns("Custom display name from metadata");
+
+ // Act
+ MvcHtmlString result = html.Label("PropertyName", null, null, metadataProvider.Object);
+
+ // Assert
+ Assert.Equal(@"<label for=""PropertyName"">Custom display name from metadata</label>", result.ToHtmlString());
+ }
+
+ [Fact]
+ public void LabelUsesMetadataForPropertyNameWhenDisplayNameIsNull()
+ {
+ // Arrange
+ metadata = new Mock<ModelMetadata>(metadataProvider.Object, null, null, typeof(object), "Custom property name from metadata");
+ metadataProvider.Setup(p => p.GetMetadataForType(It.IsAny<Func<object>>(), It.IsAny<Type>()))
+ .Returns(metadata.Object);
+
+ // Act
+ MvcHtmlString result = html.Label("PropertyName", null, null, metadataProvider.Object);
+
+ // Assert
+ Assert.Equal(@"<label for=""PropertyName"">Custom property name from metadata</label>", result.ToHtmlString());
+ }
+
+ [Fact]
+ public void LabelEmptyDisplayNameReturnsEmptyLabelText()
+ {
+ // Arrange
+ metadata.Setup(m => m.DisplayName).Returns(String.Empty);
+
+ // Act
+ MvcHtmlString result = html.Label("PropertyName", null, null, metadataProvider.Object);
+
+ // Assert
+ Assert.Equal(String.Empty, result.ToHtmlString());
+ }
+
+ [Fact]
+ public void LabelWithAnonymousValues()
+ {
+ // Act
+ MvcHtmlString result = html.Label("PropertyName", null, new { @for = "attrFor" }, metadataProvider.Object);
+
+ // Assert
+ Assert.Equal(@"<label for=""attrFor"">PropertyName</label>", result.ToHtmlString());
+ }
+
+ [Fact]
+ public void LabelWithAnonymousValuesAndLabelText()
+ {
+ // Act
+ MvcHtmlString result = html.Label("PropertyName", "Label Text", new { @for = "attrFor" }, metadataProvider.Object);
+
+ // Assert
+ Assert.Equal(@"<label for=""attrFor"">Label Text</label>", result.ToHtmlString());
+ }
+
+ [Fact]
+ public void LabelWithTypedAttributes()
+ {
+ // Arrange
+ Dictionary<string, object> htmlAttributes = new Dictionary<string, object>
+ {
+ { "foo", "bar" },
+ { "quux", "baz" }
+ };
+
+ // Act
+ MvcHtmlString result = html.Label("PropertyName", null, htmlAttributes, metadataProvider.Object);
+
+ // Assert
+ Assert.Equal(@"<label foo=""bar"" for=""PropertyName"" quux=""baz"">PropertyName</label>", result.ToHtmlString());
+ }
+
+ [Fact]
+ public void LabelWithTypedAttributesAndLabelText()
+ {
+ // Arrange
+ Dictionary<string, object> htmlAttributes = new Dictionary<string, object>
+ {
+ { "foo", "bar" },
+ { "quux", "baz" }
+ };
+
+ // Act
+ MvcHtmlString result = html.Label("PropertyName", "Label Text", htmlAttributes, metadataProvider.Object);
+
+ // Assert
+ Assert.Equal(@"<label foo=""bar"" for=""PropertyName"" quux=""baz"">Label Text</label>", result.ToHtmlString());
+ }
+
+ // LabelFor tests
+
+ [Fact]
+ public void LabelForNullExpressionThrows()
+ {
+ // Act & Assert
+ Assert.ThrowsArgumentNull(
+ () => html.LabelFor((Expression<Func<Object, Object>>)null),
+ "expression");
+ }
+
+ [Fact]
+ public void LabelForNonMemberExpressionThrows()
+ {
+ // Act & Assert
+ Assert.Throws<InvalidOperationException>(
+ () => html.LabelFor(model => new { foo = "Bar" }, null, metadataProvider.Object),
+ "Templates can be used only with field access, property access, single-dimension array index, or single-parameter custom indexer expressions.");
+ }
+
+ [Fact]
+ public void LabelForViewDataNotFound()
+ {
+ // Arrange
+ string unknownKey = "this is a dummy parameter value";
+
+ // Act
+ MvcHtmlString result = html.LabelFor(model => unknownKey, null, null, metadataProvider.Object);
+
+ // Assert
+ Assert.Equal(@"<label for=""unknownKey"">unknownKey</label>", result.ToHtmlString());
+ }
+
+ [Fact]
+ public void LabelForUsesTemplateInfoPrefix()
+ {
+ // Arrange
+ viewData.TemplateInfo.HtmlFieldPrefix = "Prefix";
+ string unknownKey = "this is a dummy parameter value";
+
+ // Act
+ MvcHtmlString result = html.LabelFor(model => unknownKey, null, null, metadataProvider.Object);
+
+ // Assert
+ Assert.Equal(@"<label for=""Prefix_unknownKey"">unknownKey</label>", result.ToHtmlString());
+ }
+
+ [Fact]
+ public void LabelForUsesLabelTextBeforeModelMetadata()
+ {
+ // Arrange
+ metadata.Setup(m => m.DisplayName).Returns("Custom display name from metadata");
+ string unknownKey = "this is a dummy parameter value";
+
+ //Act
+ MvcHtmlString result = html.LabelFor(model => unknownKey, "Label Text", null, metadataProvider.Object);
+
+ // Assert
+ Assert.Equal(@"<label for=""unknownKey"">Label Text</label>", result.ToHtmlString());
+ }
+
+ [Fact]
+ public void LabelForUsesModelMetadata()
+ {
+ // Arrange
+ metadata.Setup(m => m.DisplayName).Returns("Custom display name from metadata");
+ string unknownKey = "this is a dummy parameter value";
+
+ // Act
+ MvcHtmlString result = html.LabelFor(model => unknownKey, null, null, metadataProvider.Object);
+
+ // Assert
+ Assert.Equal(@"<label for=""unknownKey"">Custom display name from metadata</label>", result.ToHtmlString());
+ }
+
+ [Fact]
+ public void LabelForEmptyDisplayNameReturnsEmptyLabelText()
+ {
+ // Arrange
+ metadata.Setup(m => m.DisplayName).Returns(String.Empty);
+ string unknownKey = "this is a dummy parameter value";
+
+ // Act
+ MvcHtmlString result = html.LabelFor(model => unknownKey, null, null, metadataProvider.Object);
+
+ // Assert
+ Assert.Equal(String.Empty, result.ToHtmlString());
+ }
+
+ [Fact]
+ public void LabelForWithAnonymousValues()
+ {
+ //Arrange
+ string unknownKey = "this is a dummy parameter value";
+
+ // Act
+ MvcHtmlString result = html.LabelFor(model => unknownKey, null, new { @for = "attrFor" }, metadataProvider.Object);
+
+ // Assert
+ Assert.Equal(@"<label for=""attrFor"">unknownKey</label>", result.ToHtmlString());
+ }
+
+ [Fact]
+ public void LabelForWithAnonymousValuesAndLabelText()
+ {
+ //Arrange
+ string unknownKey = "this is a dummy parameter value";
+
+ // Act
+ MvcHtmlString result = html.LabelFor(model => unknownKey, "Label Text", new { @for = "attrFor" }, metadataProvider.Object);
+
+ // Assert
+ Assert.Equal(@"<label for=""attrFor"">Label Text</label>", result.ToHtmlString());
+ }
+
+ [Fact]
+ public void LabelForWithTypedAttributes()
+ {
+ //Arrange
+ string unknownKey = "this is a dummy parameter value";
+
+ Dictionary<string, object> htmlAttributes = new Dictionary<string, object>
+ {
+ { "foo", "bar" },
+ { "quux", "baz" }
+ };
+
+ // Act
+ MvcHtmlString result = html.LabelFor(model => unknownKey, null, htmlAttributes, metadataProvider.Object);
+
+ // Assert
+ Assert.Equal(@"<label foo=""bar"" for=""unknownKey"" quux=""baz"">unknownKey</label>", result.ToHtmlString());
+ }
+
+ [Fact]
+ public void LabelForWithTypedAttributesAndLabelText()
+ {
+ //Arrange
+ string unknownKey = "this is a dummy parameter value";
+
+ Dictionary<string, object> htmlAttributes = new Dictionary<string, object>
+ {
+ { "foo", "bar" },
+ { "quux", "baz" }
+ };
+
+ // Act
+ MvcHtmlString result = html.LabelFor(model => unknownKey, "Label Text", htmlAttributes, metadataProvider.Object);
+
+ // Assert
+ Assert.Equal(@"<label foo=""bar"" for=""unknownKey"" quux=""baz"">Label Text</label>", result.ToHtmlString());
+ }
+
+ [Fact]
+ public void LabelForWithNestedClass()
+ { // Dev10 Bug #936323
+ // Arrange
+ HtmlHelper<NestedProduct> html = new HtmlHelper<NestedProduct>(viewContext.Object, viewDataContainer.Object);
+
+ // Act
+ MvcHtmlString result = html.LabelFor(nested => nested.product.Id, null, null, metadataProvider.Object);
+
+ //Assert
+ Assert.Equal(@"<label for=""product_Id"">Id</label>", result.ToHtmlString());
+ }
+
+ [Fact]
+ public void LabelForWithArrayExpression()
+ { // Dev10 Bug #905780
+ // Arrange
+ HtmlHelper<Cart> html = new HtmlHelper<Cart>(viewContext.Object, viewDataContainer.Object);
+
+ // Act
+ MvcHtmlString result = html.LabelFor(cart => cart.Products[0].Id, null, null, metadataProvider.Object);
+
+ // Assert
+ Assert.Equal(@"<label for=""Products_0__Id"">Id</label>", result.ToHtmlString());
+ }
+
+ private class Product
+ {
+ public int Id { get; set; }
+ }
+
+ private class Cart
+ {
+ public Product[] Products { get; set; }
+ }
+
+ private class NestedProduct
+ {
+ public Product product = new Product();
+ }
+
+ // LabelForModel tests
+
+ [Fact]
+ public void LabelForModelUsesLabelTextBeforeModelMetadata()
+ {
+ // Arrange
+ viewData.ModelMetadata = metadata.Object;
+ viewData.TemplateInfo.HtmlFieldPrefix = "Prefix";
+ metadata.Setup(m => m.DisplayName).Returns("Custom display name from metadata");
+
+ // Act
+ MvcHtmlString result = html.LabelForModel("Label Text");
+
+ // Assert
+ Assert.Equal(@"<label for=""Prefix"">Label Text</label>", result.ToHtmlString());
+ }
+
+ [Fact]
+ public void LabelForModelUsesModelMetadata()
+ {
+ // Arrange
+ viewData.ModelMetadata = metadata.Object;
+ viewData.TemplateInfo.HtmlFieldPrefix = "Prefix";
+ metadata.Setup(m => m.DisplayName).Returns("Custom display name from metadata");
+
+ // Act
+ MvcHtmlString result = html.LabelForModel();
+
+ // Assert
+ Assert.Equal(@"<label for=""Prefix"">Custom display name from metadata</label>", result.ToHtmlString());
+ }
+
+ [Fact]
+ public void LabelForModelWithAnonymousValues()
+ {
+ //Arrange
+ viewData.ModelMetadata = metadata.Object;
+ viewData.TemplateInfo.HtmlFieldPrefix = "Prefix";
+ metadata.Setup(m => m.DisplayName).Returns("Custom display name from metadata");
+
+ // Act
+ MvcHtmlString result = html.LabelForModel(new { @for = "attrFor" });
+
+ // Assert
+ Assert.Equal(@"<label for=""attrFor"">Custom display name from metadata</label>", result.ToHtmlString());
+ }
+
+ [Fact]
+ public void LabelForModelWithAnonymousValuesAndLabelText()
+ {
+ //Arrange
+ viewData.ModelMetadata = metadata.Object;
+ viewData.TemplateInfo.HtmlFieldPrefix = "Prefix";
+ metadata.Setup(m => m.DisplayName).Returns("Custom display name from metadata");
+
+ // Act
+ MvcHtmlString result = html.LabelForModel("Label Text", new { @for = "attrFor" });
+
+ // Assert
+ Assert.Equal(@"<label for=""attrFor"">Label Text</label>", result.ToHtmlString());
+ }
+
+ [Fact]
+ public void LabelForModelWithTypedAttributes()
+ {
+ //Arrange
+ viewData.ModelMetadata = metadata.Object;
+ viewData.TemplateInfo.HtmlFieldPrefix = "Prefix";
+ metadata.Setup(m => m.DisplayName).Returns("Custom display name from metadata");
+
+ Dictionary<string, object> htmlAttributes = new Dictionary<string, object>
+ {
+ { "foo", "bar" },
+ { "quux", "baz" }
+ };
+
+ // Act
+ MvcHtmlString result = html.LabelForModel(htmlAttributes);
+
+ // Assert
+ Assert.Equal(@"<label foo=""bar"" for=""Prefix"" quux=""baz"">Custom display name from metadata</label>", result.ToHtmlString());
+ }
+
+ [Fact]
+ public void LabelForModelWithTypedAttributesAndLabelText()
+ {
+ //Arrange
+ viewData.ModelMetadata = metadata.Object;
+ viewData.TemplateInfo.HtmlFieldPrefix = "Prefix";
+ metadata.Setup(m => m.DisplayName).Returns("Custom display name from metadata");
+
+ Dictionary<string, object> htmlAttributes = new Dictionary<string, object>
+ {
+ { "foo", "bar" },
+ { "quux", "baz" }
+ };
+
+ // Act
+ MvcHtmlString result = html.LabelForModel("Label Text", htmlAttributes);
+
+ // Assert
+ Assert.Equal(@"<label foo=""bar"" for=""Prefix"" quux=""baz"">Label Text</label>", result.ToHtmlString());
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Html/Test/LinkExtensionsTest.cs b/test/System.Web.Mvc.Test/Html/Test/LinkExtensionsTest.cs
new file mode 100644
index 00000000..eb182421
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Html/Test/LinkExtensionsTest.cs
@@ -0,0 +1,562 @@
+using System.Web.Routing;
+using Microsoft.Web.UnitTestUtil;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Mvc.Html.Test
+{
+ public class LinkExtensionsTest
+ {
+ private const string AppPathModifier = MvcHelper.AppPathModifier;
+
+ [Fact]
+ public void ActionLink()
+ {
+ // Arrange
+ HtmlHelper htmlHelper = MvcHelper.GetHtmlHelper();
+
+ // Act
+ MvcHtmlString html = htmlHelper.ActionLink("linktext", "newaction");
+
+ // Assert
+ Assert.Equal(@"<a href=""" + AppPathModifier + @"/app/home/newaction"">linktext</a>", html.ToHtmlString());
+ }
+
+ [Fact]
+ public void ActionLinkDictionaryOverridesImplicitValues()
+ {
+ // Arrange
+ HtmlHelper htmlHelper = MvcHelper.GetHtmlHelper();
+
+ // Act
+ MvcHtmlString html = htmlHelper.ActionLink("linktext", "newaction", null, new { href = "http://foo.com" });
+
+ // Assert
+ Assert.Equal(@"<a href=""http://foo.com"">linktext</a>", html.ToHtmlString());
+ }
+
+ [Fact]
+ public void ActionLinkExplictValuesOverrideDictionary()
+ {
+ // Arrange
+ HtmlHelper htmlHelper = MvcHelper.GetHtmlHelper();
+
+ // Act
+ MvcHtmlString html = htmlHelper.ActionLink("linktext", "explicitAction", new { action = "dictionaryAction" }, null);
+
+ // Assert
+ Assert.Equal(@"<a href=""" + AppPathModifier + @"/app/home/explicitAction"">linktext</a>", html.ToHtmlString());
+ }
+
+ [Fact(Skip = "External bug DevDiv 356125 -- does not work correctly on 4.5")]
+ public void ActionLinkParametersNeedEscaping()
+ {
+ // Arrange
+ HtmlHelper htmlHelper = MvcHelper.GetHtmlHelper();
+
+ // Act
+ MvcHtmlString html = htmlHelper.ActionLink("linktext<&>\"", "new action<&>\"");
+
+ // Assert
+ Assert.Equal(@"<a href=""" + AppPathModifier + @"/app/home/new%20action%3C%26%3E%22"">linktext&lt;&amp;&gt;&quot;</a>", html.ToHtmlString());
+ }
+
+ [Fact]
+ public void ActionLinkWithActionNameAndValueDictionary()
+ {
+ // Arrange
+ HtmlHelper htmlHelper = MvcHelper.GetHtmlHelper();
+
+ // Act
+ MvcHtmlString html = htmlHelper.ActionLink("linktext", "newaction", new RouteValueDictionary(new { controller = "home2" }));
+
+ // Assert
+ Assert.Equal(@"<a href=""" + AppPathModifier + @"/app/home2/newaction"">linktext</a>", html.ToHtmlString());
+ }
+
+ [Fact]
+ public void ActionLinkWithActionNameAndValueObject()
+ {
+ // Arrange
+ HtmlHelper htmlHelper = MvcHelper.GetHtmlHelper();
+
+ // Act
+ MvcHtmlString html = htmlHelper.ActionLink("linktext", "newaction", new { controller = "home2" });
+
+ // Assert
+ Assert.Equal(@"<a href=""" + AppPathModifier + @"/app/home2/newaction"">linktext</a>", html.ToHtmlString());
+ }
+
+ [Fact]
+ public void ActionLinkWithControllerName()
+ {
+ // Arrange
+ HtmlHelper htmlHelper = MvcHelper.GetHtmlHelper();
+
+ // Act
+ MvcHtmlString html = htmlHelper.ActionLink("linktext", "newaction", "home2");
+
+ // Assert
+ Assert.Equal(@"<a href=""" + AppPathModifier + @"/app/home2/newaction"">linktext</a>", html.ToHtmlString());
+ }
+
+ [Fact]
+ public void ActionLinkWithControllerNameAndDictionary()
+ {
+ // Arrange
+ HtmlHelper htmlHelper = MvcHelper.GetHtmlHelper();
+
+ // Act
+ MvcHtmlString html = htmlHelper.ActionLink("linktext", "newaction", "home2", new RouteValueDictionary(new { id = "someid" }), new RouteValueDictionary(new { baz = "baz" }));
+
+ // Assert
+ Assert.Equal(@"<a baz=""baz"" href=""" + AppPathModifier + @"/app/home2/newaction/someid"">linktext</a>", html.ToHtmlString());
+ }
+
+ [Fact]
+ public void ActionLinkWithControllerNameAndObjectProperties()
+ {
+ // Arrange
+ HtmlHelper htmlHelper = MvcHelper.GetHtmlHelper();
+
+ // Act
+ MvcHtmlString html = htmlHelper.ActionLink("linktext", "newaction", "home2", new { id = "someid" }, new { baz = "baz" });
+
+ // Assert
+ Assert.Equal(@"<a baz=""baz"" href=""" + AppPathModifier + @"/app/home2/newaction/someid"">linktext</a>", html.ToHtmlString());
+ }
+
+ [Fact]
+ public void ActionLinkWithControllerNameAndObjectPropertiesWithUnderscores()
+ {
+ // Arrange
+ HtmlHelper htmlHelper = MvcHelper.GetHtmlHelper();
+
+ // Act
+ MvcHtmlString html = htmlHelper.ActionLink("linktext", "newaction", "home2", new { id = "someid" }, new { foo_baz = "baz" });
+
+ // Assert
+ Assert.Equal(@"<a foo-baz=""baz"" href=""" + AppPathModifier + @"/app/home2/newaction/someid"">linktext</a>", html.ToHtmlString());
+ }
+
+ [Fact]
+ public void ActionLinkWithDictionary()
+ {
+ // Arrange
+ HtmlHelper htmlHelper = MvcHelper.GetHtmlHelper();
+
+ // Act
+ MvcHtmlString html = htmlHelper.ActionLink("linktext", "newaction", new RouteValueDictionary(new { Controller = "home2", id = "someid" }), new RouteValueDictionary(new { baz = "baz" }));
+
+ // Assert
+ Assert.Equal(@"<a baz=""baz"" href=""" + AppPathModifier + @"/app/home2/newaction/someid"">linktext</a>", html.ToHtmlString());
+ }
+
+ [Fact]
+ public void ActionLinkWithFragment()
+ {
+ // Arrange
+ HtmlHelper htmlHelper = MvcHelper.GetHtmlHelper();
+
+ // Act
+ MvcHtmlString html = htmlHelper.ActionLink("linktext", "newaction", "home2", "http", "foo.bar.com", "foo", new { id = "someid" }, new { baz = "baz" });
+
+ // Assert
+ Assert.Equal(@"<a baz=""baz"" href=""http://foo.bar.com" + AppPathModifier + @"/app/home2/newaction/someid#foo"">linktext</a>", html.ToHtmlString());
+ }
+
+ [Fact]
+ public void ActionLinkWithFragmentAndAttributesWithUnderscores()
+ {
+ // Arrange
+ HtmlHelper htmlHelper = MvcHelper.GetHtmlHelper();
+
+ // Act
+ MvcHtmlString html = htmlHelper.ActionLink("linktext", "newaction", "home2", "http", "foo.bar.com", "foo", new { id = "someid" }, new { foo_baz = "baz" });
+
+ // Assert
+ Assert.Equal(@"<a foo-baz=""baz"" href=""http://foo.bar.com" + AppPathModifier + @"/app/home2/newaction/someid#foo"">linktext</a>", html.ToHtmlString());
+ }
+
+ [Fact]
+ public void ActionLinkWithNullHostname()
+ {
+ // Arrange
+ HtmlHelper htmlHelper = MvcHelper.GetHtmlHelper();
+
+ // Act
+ MvcHtmlString html = htmlHelper.ActionLink("linktext", "newaction", "home2", "https", null /* hostName */, "foo", new { id = "someid" }, new { baz = "baz" });
+
+ // Assert
+ Assert.Equal(@"<a baz=""baz"" href=""https://localhost" + AppPathModifier + @"/app/home2/newaction/someid#foo"">linktext</a>", html.ToHtmlString());
+ }
+
+ [Fact]
+ public void ActionLinkWithNullProtocolAndFragment()
+ {
+ // Arrange
+ HtmlHelper htmlHelper = MvcHelper.GetHtmlHelper();
+
+ // Act
+ MvcHtmlString html = htmlHelper.ActionLink("linktext", "newaction", "home2", null /* protocol */, "foo.bar.com", null /* fragment */, new { id = "someid" }, new { baz = "baz" });
+
+ // Assert
+ Assert.Equal(@"<a baz=""baz"" href=""http://foo.bar.com" + AppPathModifier + @"/app/home2/newaction/someid"">linktext</a>", html.ToHtmlString());
+ }
+
+ [Fact]
+ public void ActionLinkWithNullProtocolNullHostNameAndNullFragment()
+ {
+ // Arrange
+ HtmlHelper htmlHelper = MvcHelper.GetHtmlHelper();
+
+ // Act
+ MvcHtmlString html = htmlHelper.ActionLink("linktext", "newaction", "home2", null /* protocol */, null /* hostName */, null /* fragment */, new { id = "someid" }, new { baz = "baz" });
+
+ // Assert
+ Assert.Equal(@"<a baz=""baz"" href=""" + AppPathModifier + @"/app/home2/newaction/someid"">linktext</a>", html.ToHtmlString());
+ }
+
+ [Fact]
+ public void ActionLinkWithObjectProperties()
+ {
+ // Arrange
+ HtmlHelper htmlHelper = MvcHelper.GetHtmlHelper();
+
+ // Act
+ MvcHtmlString html = htmlHelper.ActionLink("linktext", "newaction", new { Controller = "home2", id = "someid" }, new { baz = "baz" });
+
+ // Assert
+ Assert.Equal(@"<a baz=""baz"" href=""" + AppPathModifier + @"/app/home2/newaction/someid"">linktext</a>", html.ToHtmlString());
+ }
+
+ [Fact]
+ public void ActionLinkWithObjectPropertiesWithUnderscores()
+ {
+ // Arrange
+ HtmlHelper htmlHelper = MvcHelper.GetHtmlHelper();
+
+ // Act
+ MvcHtmlString html = htmlHelper.ActionLink("linktext", "newaction", new { Controller = "home2", id = "someid" }, new { foo_baz = "baz" });
+
+ // Assert
+ Assert.Equal(@"<a foo-baz=""baz"" href=""" + AppPathModifier + @"/app/home2/newaction/someid"">linktext</a>", html.ToHtmlString());
+ }
+
+ [Fact]
+ public void ActionLinkWithProtocol()
+ {
+ // Arrange
+ HtmlHelper htmlHelper = MvcHelper.GetHtmlHelper();
+
+ // Act
+ MvcHtmlString html = htmlHelper.ActionLink("linktext", "newaction", "home2", "https", "foo.bar.com", null /* fragment */, new { id = "someid" }, new { baz = "baz" });
+
+ // Assert
+ Assert.Equal(@"<a baz=""baz"" href=""https://foo.bar.com" + AppPathModifier + @"/app/home2/newaction/someid"">linktext</a>", html.ToHtmlString());
+ }
+
+ [Fact]
+ public void ActionLinkWithProtocolAndFragment()
+ {
+ // Arrange
+ HtmlHelper htmlHelper = MvcHelper.GetHtmlHelper();
+
+ // Act
+ MvcHtmlString html = htmlHelper.ActionLink("linktext", "newaction", "home2", "https", "foo.bar.com", "foo", new { id = "someid" }, new { baz = "baz" });
+
+ // Assert
+ Assert.Equal(@"<a baz=""baz"" href=""https://foo.bar.com" + AppPathModifier + @"/app/home2/newaction/someid#foo"">linktext</a>", html.ToHtmlString());
+ }
+
+ [Fact]
+ public void ActionLinkWithDefaultPort()
+ {
+ // Arrange
+ HtmlHelper htmlHelper = MvcHelper.GetHtmlHelper(Uri.UriSchemeHttps, -1);
+
+ // Act
+ MvcHtmlString html = htmlHelper.ActionLink("linktext", "newaction", "home2", "https", "foo.bar.com", "foo", new { id = "someid" }, new { baz = "baz" });
+
+ // Assert
+ Assert.Equal(@"<a baz=""baz"" href=""https://foo.bar.com" + AppPathModifier + @"/app/home2/newaction/someid#foo"">linktext</a>", html.ToHtmlString());
+ }
+
+ [Fact]
+ public void ActionLinkWithDifferentPortProtocols()
+ {
+ // Arrange
+ HtmlHelper htmlHelper = MvcHelper.GetHtmlHelper(Uri.UriSchemeHttp, -1);
+
+ // Act
+ MvcHtmlString html = htmlHelper.ActionLink("linktext", "newaction", "home2", "https", "foo.bar.com", "foo", new { id = "someid" }, new { baz = "baz" });
+
+ // Assert
+ Assert.Equal(@"<a baz=""baz"" href=""https://foo.bar.com" + AppPathModifier + @"/app/home2/newaction/someid#foo"">linktext</a>", html.ToHtmlString());
+ }
+
+ [Fact]
+ public void ActionLinkWithNonDefaultPortAndDifferentProtocol()
+ {
+ // Arrange
+ HtmlHelper htmlHelper = MvcHelper.GetHtmlHelper(Uri.UriSchemeHttp, 32768);
+
+ // Act
+ MvcHtmlString html = htmlHelper.ActionLink("linktext", "newaction", "home2", "https", "foo.bar.com", "foo", new { id = "someid" }, new { baz = "baz" });
+
+ // Assert
+ Assert.Equal(@"<a baz=""baz"" href=""https://foo.bar.com" + AppPathModifier + @"/app/home2/newaction/someid#foo"">linktext</a>", html.ToHtmlString());
+ }
+
+ [Fact]
+ public void ActionLinkWithNonDefaultPortAndSameProtocol()
+ {
+ // Arrange
+ HtmlHelper htmlHelper = MvcHelper.GetHtmlHelper(Uri.UriSchemeHttp, 32768);
+
+ // Act
+ MvcHtmlString html = htmlHelper.ActionLink("linktext", "newaction", "home2", "http", "foo.bar.com", "foo", new { id = "someid" }, new { baz = "baz" });
+
+ // Assert
+ Assert.Equal(@"<a baz=""baz"" href=""http://foo.bar.com:32768" + AppPathModifier + @"/app/home2/newaction/someid#foo"">linktext</a>", html.ToHtmlString());
+ }
+
+ [Fact]
+ public void LinkGenerationDoesNotChangeProvidedDictionary()
+ {
+ // Arrange
+ HtmlHelper htmlHelper = MvcHelper.GetHtmlHelper();
+ RouteValueDictionary valuesDictionary = new RouteValueDictionary();
+
+ // Act
+ htmlHelper.ActionLink("linkText", "actionName", valuesDictionary, new RouteValueDictionary());
+
+ // Assert
+ Assert.Empty(valuesDictionary);
+ Assert.False(valuesDictionary.ContainsKey("action"));
+ }
+
+ [Fact]
+ public void NullOrEmptyStringParameterThrows()
+ {
+ // Arrange
+ HtmlHelper htmlHelper = MvcHelper.GetHtmlHelper();
+ var tests = new[]
+ {
+ // ActionLink(string linkText, string actionName)
+ new { Parameter = "linkText", Action = new Action(() => htmlHelper.ActionLink(String.Empty, "actionName")) },
+ // ActionLink(string linkText, string actionName, object routeValues, object htmlAttributes)
+ new { Parameter = "linkText", Action = new Action(() => htmlHelper.ActionLink(String.Empty, "actionName", new Object(), null /* htmlAttributes */)) },
+ // ActionLink(string linkText, string actionName, RouteValueDictionary routeValues, IDictionary<string, object> htmlAttributes)
+ new { Parameter = "linkText", Action = new Action(() => htmlHelper.ActionLink(String.Empty, "actionName", new RouteValueDictionary(), new RouteValueDictionary())) },
+ // ActionLink(string linkText, string actionName, string controllerName)
+ new { Parameter = "linkText", Action = new Action(() => htmlHelper.ActionLink(String.Empty, "actionName", "controllerName")) },
+ // ActionLink(string linkText, string actionName, string controllerName, object routeValues, object htmlAttributes)
+ new { Parameter = "linkText", Action = new Action(() => htmlHelper.ActionLink(String.Empty, "actionName", "controllerName", new Object(), null /* htmlAttributes */)) },
+ // ActionLink(string linkText, string actionName, string controllerName, RouteValueDictionary routeValues, IDictionary<string, object> htmlAttributes)
+ new { Parameter = "linkText", Action = new Action(() => htmlHelper.ActionLink(String.Empty, "actionName", "controllerName", new RouteValueDictionary(), new RouteValueDictionary())) },
+ // ActionLink(string linkText, string actionName, string controllerName, string protocol, string hostName, string fragment, RouteValueDictionary routeValues, IDictionary<string, object> htmlAttributes)
+ new { Parameter = "linkText", Action = new Action(() => htmlHelper.ActionLink(String.Empty, "actionName", "controllerName", null, null, null, new RouteValueDictionary(), new RouteValueDictionary())) },
+ // RouteLink(string linkText, object routeValues, object htmlAttributes)
+ new { Parameter = "linkText", Action = new Action(() => htmlHelper.RouteLink(String.Empty, new Object(), null /* htmlAttributes */)) },
+ // RouteLink(string linkText, RouteValueDictionary routeValues, IDictionary<string, object> htmlAttributes)
+ new { Parameter = "linkText", Action = new Action(() => htmlHelper.RouteLink(String.Empty, new RouteValueDictionary(), new RouteValueDictionary())) },
+ // RouteLink(string linkText, string routeName, object routeValues)
+ new { Parameter = "linkText", Action = new Action(() => htmlHelper.RouteLink(String.Empty, "routeName", null /* routeValues */)) },
+ // RouteLink(string linkText, string routeName, RouteValueDictionary routeValues)
+ new { Parameter = "linkText", Action = new Action(() => htmlHelper.RouteLink(String.Empty, "routeName", new RouteValueDictionary() /* routeValues */)) },
+ // RouteLink(string linkText, string routeName)
+ new { Parameter = "linkText", Action = new Action(() => htmlHelper.RouteLink(String.Empty, (string)null /* routeName */)) },
+ // RouteLink(string linkText, object routeValues)
+ new { Parameter = "linkText", Action = new Action(() => htmlHelper.RouteLink(String.Empty, (object)null /* routeValues */)) },
+ // RouteLink(string linkText, RouteValueDictionary routeValues)
+ new { Parameter = "linkText", Action = new Action(() => htmlHelper.RouteLink(String.Empty, new RouteValueDictionary() /* routeValues */)) },
+ // RouteLink(string linkText, string routeName, object routeValues, object htmlAttributes)
+ new { Parameter = "linkText", Action = new Action(() => htmlHelper.RouteLink(String.Empty, "routeName", new Object(), null /* htmlAttributes */)) },
+ // RouteLink(string linkText, string routeName, RouteValueDictionary routeValues, IDictionary<string, object> htmlAttributes)
+ new { Parameter = "linkText", Action = new Action(() => htmlHelper.RouteLink(String.Empty, "routeName", new RouteValueDictionary(), new RouteValueDictionary())) },
+ // RouteLink(string linkText, string routeName, string protocol, string hostName, string fragment, RouteValueDictionary routeValues, IDictionary<string, object> htmlAttributes)
+ new { Parameter = "linkText", Action = new Action(() => htmlHelper.RouteLink(String.Empty, "routeName", null, null, null, new RouteValueDictionary(), new RouteValueDictionary())) },
+ };
+
+ // Act & Assert
+ foreach (var test in tests)
+ {
+ Assert.ThrowsArgumentNullOrEmpty(test.Action, test.Parameter);
+ }
+ }
+
+ [Fact]
+ public void RouteLinkCanUseNamedRouteWithoutSpecifyingDefaults()
+ {
+ // DevDiv 217072: Non-mvc specific helpers should not give default values for controller and action
+
+ // Arrange
+ HtmlHelper htmlHelper = MvcHelper.GetHtmlHelper();
+ htmlHelper.RouteCollection.MapRoute("MyRouteName", "any/url", new { controller = "Charlie" });
+
+ // Act
+ MvcHtmlString html = htmlHelper.RouteLink("linktext", "MyRouteName", null /* routeValues */);
+
+ // Assert
+ Assert.Equal(@"<a href=""" + AppPathModifier + @"/app/any/url"">linktext</a>", html.ToHtmlString());
+ }
+
+ [Fact]
+ public void RouteLinkWithDictionary()
+ {
+ // Arrange
+ HtmlHelper htmlHelper = MvcHelper.GetHtmlHelper();
+
+ // Act
+ MvcHtmlString html = htmlHelper.RouteLink("linktext", new RouteValueDictionary(new { Action = "newaction", Controller = "home2", id = "someid" }), new RouteValueDictionary(new { baz = "baz" }));
+
+ // Assert
+ Assert.Equal(@"<a baz=""baz"" href=""" + AppPathModifier + @"/app/home2/newaction/someid"">linktext</a>", html.ToHtmlString());
+ }
+
+ [Fact]
+ public void RouteLinkWithFragment()
+ {
+ // Arrange
+ HtmlHelper htmlHelper = MvcHelper.GetHtmlHelper();
+
+ // Act
+ MvcHtmlString html = htmlHelper.RouteLink("linktext", "namedroute", "http", "foo.bar.com", "foo", new { Action = "newaction", Controller = "home2", id = "someid" }, new { baz = "baz" });
+
+ // Assert
+ Assert.Equal(@"<a baz=""baz"" href=""http://foo.bar.com" + AppPathModifier + @"/app/named/home2/newaction/someid#foo"">linktext</a>", html.ToHtmlString());
+ }
+
+ [Fact]
+ public void RouteLinkWithFragmentAndAttributesWithUnderscores()
+ {
+ // Arrange
+ HtmlHelper htmlHelper = MvcHelper.GetHtmlHelper();
+
+ // Act
+ MvcHtmlString html = htmlHelper.RouteLink("linktext", "namedroute", "http", "foo.bar.com", "foo", new { Action = "newaction", Controller = "home2", id = "someid" }, new { foo_baz = "baz" });
+
+ // Assert
+ Assert.Equal(@"<a foo-baz=""baz"" href=""http://foo.bar.com" + AppPathModifier + @"/app/named/home2/newaction/someid#foo"">linktext</a>", html.ToHtmlString());
+ }
+
+ [Fact]
+ public void RouteLinkWithLinkTextAndRouteName()
+ {
+ // Arrange
+ HtmlHelper htmlHelper = MvcHelper.GetHtmlHelper();
+ htmlHelper.RouteCollection.MapRoute("MyRouteName", "any/url", new { controller = "Charlie" });
+
+ // Act
+ MvcHtmlString html = htmlHelper.RouteLink("linktext", "MyRouteName");
+
+ // Assert
+ Assert.Equal(@"<a href=""" + AppPathModifier + @"/app/any/url"">linktext</a>", html.ToHtmlString());
+ }
+
+ [Fact]
+ public void RouteLinkWithObjectProperties()
+ {
+ // Arrange
+ HtmlHelper htmlHelper = MvcHelper.GetHtmlHelper();
+
+ // Act
+ MvcHtmlString html = htmlHelper.RouteLink("linktext", new { Action = "newaction", Controller = "home2", id = "someid" }, new { baz = "baz" });
+
+ // Assert
+ Assert.Equal(@"<a baz=""baz"" href=""" + AppPathModifier + @"/app/home2/newaction/someid"">linktext</a>", html.ToHtmlString());
+ }
+
+ [Fact]
+ public void RouteLinkWithObjectPropertiesWithUnderscores()
+ {
+ // Arrange
+ HtmlHelper htmlHelper = MvcHelper.GetHtmlHelper();
+
+ // Act
+ MvcHtmlString html = htmlHelper.RouteLink("linktext", new { Action = "newaction", Controller = "home2", id = "someid" }, new { foo_baz = "baz" });
+
+ // Assert
+ Assert.Equal(@"<a foo-baz=""baz"" href=""" + AppPathModifier + @"/app/home2/newaction/someid"">linktext</a>", html.ToHtmlString());
+ }
+
+ [Fact]
+ public void RouteLinkWithProtocol()
+ {
+ // Arrange
+ HtmlHelper htmlHelper = MvcHelper.GetHtmlHelper();
+
+ // Act
+ MvcHtmlString html = htmlHelper.RouteLink("linktext", "namedroute", "https", "foo.bar.com", null /* fragment */, new { Action = "newaction", Controller = "home2", id = "someid" }, new { baz = "baz" });
+
+ // Assert
+ Assert.Equal(@"<a baz=""baz"" href=""https://foo.bar.com" + AppPathModifier + @"/app/named/home2/newaction/someid"">linktext</a>", html.ToHtmlString());
+ }
+
+ [Fact]
+ public void RouteLinkWithProtocolAndFragment()
+ {
+ // Arrange
+ HtmlHelper htmlHelper = MvcHelper.GetHtmlHelper();
+
+ // Act
+ MvcHtmlString html = htmlHelper.RouteLink("linktext", "namedroute", "https", "foo.bar.com", "foo", new { Action = "newaction", Controller = "home2", id = "someid" }, new { baz = "baz" });
+
+ // Assert
+ Assert.Equal(@"<a baz=""baz"" href=""https://foo.bar.com" + AppPathModifier + @"/app/named/home2/newaction/someid#foo"">linktext</a>", html.ToHtmlString());
+ }
+
+ [Fact]
+ public void RouteLinkWithRouteNameAndDefaults()
+ {
+ // Arrange
+ HtmlHelper htmlHelper = MvcHelper.GetHtmlHelper();
+
+ // Act
+ MvcHtmlString html = htmlHelper.RouteLink("linktext", "namedroute", new { Action = "newaction" });
+
+ // Assert
+ Assert.Equal(@"<a href=""" + AppPathModifier + @"/app/named/home/newaction"">linktext</a>", html.ToHtmlString());
+ }
+
+ [Fact]
+ public void RouteLinkWithRouteNameAndDictionary()
+ {
+ // Arrange
+ HtmlHelper htmlHelper = MvcHelper.GetHtmlHelper();
+
+ // Act
+ MvcHtmlString html = htmlHelper.RouteLink("linktext", "namedroute", new RouteValueDictionary(new { Action = "newaction", Controller = "home2", id = "someid" }), new RouteValueDictionary());
+
+ // Assert
+ Assert.Equal(@"<a href=""" + AppPathModifier + @"/app/named/home2/newaction/someid"">linktext</a>", html.ToHtmlString());
+ }
+
+ [Fact]
+ public void RouteLinkWithRouteNameAndObjectProperties()
+ {
+ // Arrange
+ HtmlHelper htmlHelper = MvcHelper.GetHtmlHelper();
+
+ // Act
+ MvcHtmlString html = htmlHelper.RouteLink("linktext", "namedroute", new { Action = "newaction", Controller = "home2", id = "someid" }, new { baz = "baz" });
+
+ // Assert
+ Assert.Equal(@"<a baz=""baz"" href=""" + AppPathModifier + @"/app/named/home2/newaction/someid"">linktext</a>", html.ToHtmlString());
+ }
+
+ [Fact]
+ public void RouteLinkWithRouteNameAndObjectPropertiesWithUnderscores()
+ {
+ // Arrange
+ HtmlHelper htmlHelper = MvcHelper.GetHtmlHelper();
+
+ // Act
+ MvcHtmlString html = htmlHelper.RouteLink("linktext", "namedroute", new { Action = "newaction", Controller = "home2", id = "someid" }, new { foo_baz = "baz" });
+
+ // Assert
+ Assert.Equal(@"<a foo-baz=""baz"" href=""" + AppPathModifier + @"/app/named/home2/newaction/someid"">linktext</a>", html.ToHtmlString());
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Html/Test/MvcFormTest.cs b/test/System.Web.Mvc.Test/Html/Test/MvcFormTest.cs
new file mode 100644
index 00000000..721b7fee
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Html/Test/MvcFormTest.cs
@@ -0,0 +1,97 @@
+using System.Collections;
+using System.IO;
+using Moq;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Mvc.Html.Test
+{
+ public class MvcFormTest
+ {
+ [Fact]
+ public void ConstructorWithNullViewContextThrows()
+ {
+ Assert.ThrowsArgumentNull(
+ delegate { new MvcForm((ViewContext)null); },
+ "viewContext");
+ }
+
+ [Fact]
+ public void DisposeRendersCloseFormTag()
+ {
+ // Arrange
+ StringWriter writer = new StringWriter();
+ ViewContext viewContext = GetViewContext(writer);
+
+ MvcForm form = new MvcForm(viewContext);
+
+ // Act
+ form.Dispose();
+
+ // Assert
+ Assert.Equal("</form>", writer.ToString());
+ }
+
+ [Fact]
+ public void EndFormRendersCloseFormTag()
+ {
+ // Arrange
+ StringWriter writer = new StringWriter();
+ ViewContext viewContext = GetViewContext(writer);
+
+ MvcForm form = new MvcForm(viewContext);
+
+ // Act
+ form.EndForm();
+
+ // Assert
+ Assert.Equal("</form>", writer.ToString());
+ }
+
+ [Fact]
+ public void DisposeTwiceRendersCloseFormTagOnce()
+ {
+ // Arrange
+ StringWriter writer = new StringWriter();
+ ViewContext viewContext = GetViewContext(writer);
+
+ MvcForm form = new MvcForm(viewContext);
+
+ // Act
+ form.Dispose();
+ form.Dispose();
+
+ // Assert
+ Assert.Equal("</form>", writer.ToString());
+ }
+
+ [Fact]
+ public void EndFormTwiceRendersCloseFormTagOnce()
+ {
+ // Arrange
+ StringWriter writer = new StringWriter();
+ ViewContext viewContext = GetViewContext(writer);
+
+ MvcForm form = new MvcForm(viewContext);
+
+ // Act
+ form.EndForm();
+ form.EndForm();
+
+ // Assert
+ Assert.Equal("</form>", writer.ToString());
+ }
+
+ private static ViewContext GetViewContext(TextWriter writer)
+ {
+ Mock<HttpContextBase> mockHttpContext = new Mock<HttpContextBase>();
+ mockHttpContext.Setup(o => o.Items).Returns(new Hashtable());
+
+ return new ViewContext()
+ {
+ HttpContext = mockHttpContext.Object,
+ Writer = writer
+ };
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Html/Test/NameExtensionsTest.cs b/test/System.Web.Mvc.Test/Html/Test/NameExtensionsTest.cs
new file mode 100644
index 00000000..55a4ba5b
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Html/Test/NameExtensionsTest.cs
@@ -0,0 +1,83 @@
+using Microsoft.Web.UnitTestUtil;
+using Xunit;
+
+namespace System.Web.Mvc.Html.Test
+{
+ public class NameExtensionsTest
+ {
+ [Fact]
+ public void NonStronglyTypedWithNoPrefix()
+ {
+ // Arrange
+ HtmlHelper html = MvcHelper.GetHtmlHelper(new ViewDataDictionary());
+
+ // Act & Assert
+ Assert.Equal("", html.IdForModel().ToHtmlString());
+ Assert.Equal("foo", html.Id("foo").ToHtmlString());
+ Assert.Equal("foo_bar", html.Id("foo.bar").ToHtmlString());
+ Assert.Equal(String.Empty, html.Id("<script>alert(\"XSS!\")</script>").ToHtmlString());
+
+ Assert.Equal("", html.NameForModel().ToHtmlString());
+ Assert.Equal("foo", html.Name("foo").ToHtmlString());
+ Assert.Equal("foo.bar", html.Name("foo.bar").ToHtmlString());
+ Assert.Equal("&lt;script>alert(&quot;XSS!&quot;)&lt;/script>", html.Name("<script>alert(\"XSS!\")</script>").ToHtmlString());
+ }
+
+ [Fact]
+ public void NonStronglyTypedWithPrefix()
+ {
+ // Arrange
+ HtmlHelper html = MvcHelper.GetHtmlHelper(new ViewDataDictionary());
+ html.ViewData.TemplateInfo.HtmlFieldPrefix = "prefix";
+
+ // Act & Assert
+ Assert.Equal("prefix", html.IdForModel().ToHtmlString());
+ Assert.Equal("prefix_foo", html.Id("foo").ToHtmlString());
+ Assert.Equal("prefix_foo_bar", html.Id("foo.bar").ToHtmlString());
+
+ Assert.Equal("prefix", html.NameForModel().ToHtmlString());
+ Assert.Equal("prefix.foo", html.Name("foo").ToHtmlString());
+ Assert.Equal("prefix.foo.bar", html.Name("foo.bar").ToHtmlString());
+ }
+
+ [Fact]
+ public void StronglyTypedWithNoPrefix()
+ {
+ // Arrange
+ HtmlHelper<OuterClass> html = MvcHelper.GetHtmlHelper(new ViewDataDictionary<OuterClass>());
+
+ // Act & Assert
+ Assert.Equal("IntValue", html.IdFor(m => m.IntValue).ToHtmlString());
+ Assert.Equal("Inner_StringValue", html.IdFor(m => m.Inner.StringValue).ToHtmlString());
+
+ Assert.Equal("IntValue", html.NameFor(m => m.IntValue).ToHtmlString());
+ Assert.Equal("Inner.StringValue", html.NameFor(m => m.Inner.StringValue).ToHtmlString());
+ }
+
+ [Fact]
+ public void StronglyTypedWithPrefix()
+ {
+ // Arrange
+ HtmlHelper<OuterClass> html = MvcHelper.GetHtmlHelper(new ViewDataDictionary<OuterClass>());
+ html.ViewData.TemplateInfo.HtmlFieldPrefix = "prefix";
+
+ // Act & Assert
+ Assert.Equal("prefix_IntValue", html.IdFor(m => m.IntValue).ToHtmlString());
+ Assert.Equal("prefix_Inner_StringValue", html.IdFor(m => m.Inner.StringValue).ToHtmlString());
+
+ Assert.Equal("prefix.IntValue", html.NameFor(m => m.IntValue).ToHtmlString());
+ Assert.Equal("prefix.Inner.StringValue", html.NameFor(m => m.Inner.StringValue).ToHtmlString());
+ }
+
+ private sealed class OuterClass
+ {
+ public InnerClass Inner { get; set; }
+ public int IntValue { get; set; }
+ }
+
+ private sealed class InnerClass
+ {
+ public string StringValue { get; set; }
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Html/Test/PartialExtensionsTest.cs b/test/System.Web.Mvc.Test/Html/Test/PartialExtensionsTest.cs
new file mode 100644
index 00000000..2380bd80
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Html/Test/PartialExtensionsTest.cs
@@ -0,0 +1,84 @@
+using System.IO;
+using Xunit;
+
+namespace System.Web.Mvc.Html.Test
+{
+ public class PartialExtensionsTest
+ {
+ [Fact]
+ public void PartialWithViewName()
+ {
+ // Arrange
+ RenderPartialExtensionsTest.SpyHtmlHelper helper = RenderPartialExtensionsTest.SpyHtmlHelper.Create();
+
+ // Act
+ MvcHtmlString result = helper.Partial("partial-view");
+
+ // Assert
+ Assert.Equal("partial-view", helper.RenderPartialInternal_PartialViewName);
+ Assert.Same(helper.ViewData, helper.RenderPartialInternal_ViewData);
+ Assert.Null(helper.RenderPartialInternal_Model);
+ Assert.IsType<StringWriter>(helper.RenderPartialInternal_Writer);
+ Assert.Same(ViewEngines.Engines, helper.RenderPartialInternal_ViewEngineCollection);
+ Assert.Equal("This is the result of the view", result.ToHtmlString());
+ }
+
+ [Fact]
+ public void PartialWithViewNameAndViewData()
+ {
+ // Arrange
+ RenderPartialExtensionsTest.SpyHtmlHelper helper = RenderPartialExtensionsTest.SpyHtmlHelper.Create();
+ ViewDataDictionary viewData = new ViewDataDictionary();
+
+ // Act
+ MvcHtmlString result = helper.Partial("partial-view", viewData);
+
+ // Assert
+ Assert.Equal("partial-view", helper.RenderPartialInternal_PartialViewName);
+ Assert.Same(viewData, helper.RenderPartialInternal_ViewData);
+ Assert.Null(helper.RenderPartialInternal_Model);
+ Assert.IsType<StringWriter>(helper.RenderPartialInternal_Writer);
+ Assert.Same(ViewEngines.Engines, helper.RenderPartialInternal_ViewEngineCollection);
+ Assert.Equal("This is the result of the view", result.ToHtmlString());
+ }
+
+ [Fact]
+ public void PartialWithViewNameAndModel()
+ {
+ // Arrange
+ RenderPartialExtensionsTest.SpyHtmlHelper helper = RenderPartialExtensionsTest.SpyHtmlHelper.Create();
+ object model = new object();
+
+ // Act
+ MvcHtmlString result = helper.Partial("partial-view", model);
+
+ // Assert
+ Assert.Equal("partial-view", helper.RenderPartialInternal_PartialViewName);
+ Assert.Same(helper.ViewData, helper.RenderPartialInternal_ViewData);
+ Assert.Same(model, helper.RenderPartialInternal_Model);
+ Assert.IsType<StringWriter>(helper.RenderPartialInternal_Writer);
+ Assert.Same(ViewEngines.Engines, helper.RenderPartialInternal_ViewEngineCollection);
+ Assert.Equal("This is the result of the view", result.ToHtmlString());
+ }
+
+ [Fact]
+ public void PartialWithViewNameAndModelAndViewData()
+ {
+ // Arrange
+ RenderPartialExtensionsTest.SpyHtmlHelper helper = RenderPartialExtensionsTest.SpyHtmlHelper.Create();
+ object model = new object();
+ ViewDataDictionary viewData = new ViewDataDictionary();
+
+ // Act
+ MvcHtmlString result = helper.Partial("partial-view", model, viewData);
+
+ // Assert
+ Assert.Equal("partial-view", helper.RenderPartialInternal_PartialViewName);
+ Assert.Same(viewData, helper.RenderPartialInternal_ViewData);
+ Assert.Same(model, helper.RenderPartialInternal_Model);
+ Assert.IsType<StringWriter>(helper.RenderPartialInternal_Writer);
+ Assert.Same(ViewEngines.Engines, helper.RenderPartialInternal_ViewEngineCollection);
+ Assert.Equal("This is the result of the view", result.ToHtmlString());
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Html/Test/RenderPartialExtensionsTest.cs b/test/System.Web.Mvc.Test/Html/Test/RenderPartialExtensionsTest.cs
new file mode 100644
index 00000000..4aba780e
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Html/Test/RenderPartialExtensionsTest.cs
@@ -0,0 +1,122 @@
+using System.IO;
+using Moq;
+using Xunit;
+
+namespace System.Web.Mvc.Html.Test
+{
+ public class RenderPartialExtensionsTest
+ {
+ [Fact]
+ public void RenderPartialWithViewName()
+ {
+ // Arrange
+ SpyHtmlHelper helper = SpyHtmlHelper.Create();
+
+ // Act
+ helper.RenderPartial("partial-view");
+
+ // Assert
+ Assert.Equal("partial-view", helper.RenderPartialInternal_PartialViewName);
+ Assert.Same(helper.ViewData, helper.RenderPartialInternal_ViewData);
+ Assert.Null(helper.RenderPartialInternal_Model);
+ Assert.Same(helper.ViewContext.Writer, helper.RenderPartialInternal_Writer);
+ Assert.Same(ViewEngines.Engines, helper.RenderPartialInternal_ViewEngineCollection);
+ }
+
+ [Fact]
+ public void RenderPartialWithViewNameAndViewData()
+ {
+ // Arrange
+ SpyHtmlHelper helper = SpyHtmlHelper.Create();
+ ViewDataDictionary viewData = new ViewDataDictionary();
+
+ // Act
+ helper.RenderPartial("partial-view", viewData);
+
+ // Assert
+ Assert.Equal("partial-view", helper.RenderPartialInternal_PartialViewName);
+ Assert.Same(viewData, helper.RenderPartialInternal_ViewData);
+ Assert.Null(helper.RenderPartialInternal_Model);
+ Assert.Same(helper.ViewContext.Writer, helper.RenderPartialInternal_Writer);
+ Assert.Same(ViewEngines.Engines, helper.RenderPartialInternal_ViewEngineCollection);
+ }
+
+ [Fact]
+ public void RenderPartialWithViewNameAndModel()
+ {
+ // Arrange
+ SpyHtmlHelper helper = SpyHtmlHelper.Create();
+ object model = new object();
+
+ // Act
+ helper.RenderPartial("partial-view", model);
+
+ // Assert
+ Assert.Equal("partial-view", helper.RenderPartialInternal_PartialViewName);
+ Assert.Same(helper.ViewData, helper.RenderPartialInternal_ViewData);
+ Assert.Same(model, helper.RenderPartialInternal_Model);
+ Assert.Same(helper.ViewContext.Writer, helper.RenderPartialInternal_Writer);
+ Assert.Same(ViewEngines.Engines, helper.RenderPartialInternal_ViewEngineCollection);
+ }
+
+ [Fact]
+ public void RenderPartialWithViewNameAndModelAndViewData()
+ {
+ // Arrange
+ SpyHtmlHelper helper = SpyHtmlHelper.Create();
+ object model = new object();
+ ViewDataDictionary viewData = new ViewDataDictionary();
+
+ // Act
+ helper.RenderPartial("partial-view", model, viewData);
+
+ // Assert
+ Assert.Equal("partial-view", helper.RenderPartialInternal_PartialViewName);
+ Assert.Same(viewData, helper.RenderPartialInternal_ViewData);
+ Assert.Same(model, helper.RenderPartialInternal_Model);
+ Assert.Same(helper.ViewContext.Writer, helper.RenderPartialInternal_Writer);
+ Assert.Same(ViewEngines.Engines, helper.RenderPartialInternal_ViewEngineCollection);
+ }
+
+ internal class SpyHtmlHelper : HtmlHelper
+ {
+ public string RenderPartialInternal_PartialViewName;
+ public ViewDataDictionary RenderPartialInternal_ViewData;
+ public object RenderPartialInternal_Model;
+ public TextWriter RenderPartialInternal_Writer;
+ public ViewEngineCollection RenderPartialInternal_ViewEngineCollection;
+
+ SpyHtmlHelper(ViewContext viewContext, IViewDataContainer viewDataContainer)
+ : base(viewContext, viewDataContainer)
+ {
+ }
+
+ public static SpyHtmlHelper Create()
+ {
+ ViewDataDictionary viewData = new ViewDataDictionary();
+
+ Mock<ViewContext> mockViewContext = new Mock<ViewContext>() { DefaultValue = DefaultValue.Mock };
+ mockViewContext.Setup(c => c.HttpContext.Response.Output).Throws(new Exception("Response.Output should never be called."));
+ mockViewContext.Setup(c => c.ViewData).Returns(viewData);
+ mockViewContext.Setup(c => c.Writer).Returns(new StringWriter());
+
+ Mock<IViewDataContainer> container = new Mock<IViewDataContainer>();
+ container.Setup(c => c.ViewData).Returns(viewData);
+
+ return new SpyHtmlHelper(mockViewContext.Object, container.Object);
+ }
+
+ internal override void RenderPartialInternal(string partialViewName, ViewDataDictionary viewData, object model,
+ TextWriter writer, ViewEngineCollection viewEngineCollection)
+ {
+ RenderPartialInternal_PartialViewName = partialViewName;
+ RenderPartialInternal_ViewData = viewData;
+ RenderPartialInternal_Model = model;
+ RenderPartialInternal_Writer = writer;
+ RenderPartialInternal_ViewEngineCollection = viewEngineCollection;
+
+ writer.Write("This is the result of the view");
+ }
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Html/Test/SelectExtensionsTest.cs b/test/System.Web.Mvc.Test/Html/Test/SelectExtensionsTest.cs
new file mode 100644
index 00000000..25454fb2
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Html/Test/SelectExtensionsTest.cs
@@ -0,0 +1,1857 @@
+using System.Collections;
+using System.Collections.Generic;
+using System.ComponentModel.DataAnnotations;
+using System.Globalization;
+using System.Linq;
+using System.Web.Mvc.Test;
+using Microsoft.Web.UnitTestUtil;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Mvc.Html.Test
+{
+ public class SelectExtensionsTest
+ {
+ private static readonly ViewDataDictionary<FooModel> _listBoxViewData = new ViewDataDictionary<FooModel> { { "foo", new[] { "Bravo" } } };
+ private static readonly ViewDataDictionary<FooModel> _dropDownListViewData = new ViewDataDictionary<FooModel> { { "foo", "Bravo" } };
+ private static readonly ViewDataDictionary<NonIEnumerableModel> _nonIEnumerableViewData = new ViewDataDictionary<NonIEnumerableModel> { { "foo", 1 } };
+
+ private static ViewDataDictionary GetViewDataWithSelectList()
+ {
+ ViewDataDictionary viewData = new ViewDataDictionary();
+ SelectList selectList = new SelectList(MultiSelectListTest.GetSampleAnonymousObjects(), "Letter", "FullWord", "C");
+ viewData["foo"] = selectList;
+ viewData["foo.bar"] = selectList;
+ return viewData;
+ }
+
+ // DropDownList
+
+ [Fact]
+ public void DropDownListUsesExplicitValueIfNotProvidedInViewData()
+ {
+ // Arrange
+ HtmlHelper helper = MvcHelper.GetHtmlHelper(new ViewDataDictionary());
+ SelectList selectList = new SelectList(MultiSelectListTest.GetSampleAnonymousObjects(), "Letter", "FullWord", "C");
+
+ // Act
+ MvcHtmlString html = helper.DropDownList("foo", selectList, (string)null /* optionLabel */);
+
+ // Assert
+ Assert.Equal(
+ @"<select id=""foo"" name=""foo""><option value=""A"">Alpha</option>
+<option value=""B"">Bravo</option>
+<option selected=""selected"" value=""C"">Charlie</option>
+</select>",
+ html.ToHtmlString());
+ }
+
+ [Fact]
+ public void DropDownListUsesExplicitValueIfNotProvidedInViewData_Unobtrusive()
+ {
+ // Arrange
+ HtmlHelper helper = MvcHelper.GetHtmlHelper(new ViewDataDictionary());
+ helper.ViewContext.ClientValidationEnabled = true;
+ helper.ViewContext.UnobtrusiveJavaScriptEnabled = true;
+ helper.ViewContext.FormContext = new FormContext();
+ helper.ClientValidationRuleFactory = (name, metadata) => new[] { new ModelClientValidationRule { ValidationType = "type", ErrorMessage = "error" } };
+ SelectList selectList = new SelectList(MultiSelectListTest.GetSampleAnonymousObjects(), "Letter", "FullWord", "C");
+
+ // Act
+ MvcHtmlString html = helper.DropDownList("foo", selectList, (string)null /* optionLabel */);
+
+ // Assert
+ Assert.Equal(
+ @"<select data-val=""true"" data-val-type=""error"" id=""foo"" name=""foo""><option value=""A"">Alpha</option>
+<option value=""B"">Bravo</option>
+<option selected=""selected"" value=""C"">Charlie</option>
+</select>",
+ html.ToHtmlString());
+ }
+
+ [Fact]
+ public void DropDownListUsesViewDataDefaultValue()
+ {
+ // Arrange
+ HtmlHelper helper = MvcHelper.GetHtmlHelper(_dropDownListViewData);
+ SelectList selectList = new SelectList(MultiSelectListTest.GetSampleStrings(), "Charlie");
+
+ // Act
+ MvcHtmlString html = helper.DropDownList("foo", selectList, (string)null /* optionLabel */);
+
+ // Assert
+ Assert.Equal(
+ @"<select id=""foo"" name=""foo""><option>Alpha</option>
+<option selected=""selected"">Bravo</option>
+<option>Charlie</option>
+</select>",
+ html.ToHtmlString());
+ }
+
+ [Fact]
+ public void DropDownListUsesViewDataDefaultValueNoOptionLabel()
+ {
+ // Arrange
+ HtmlHelper helper = MvcHelper.GetHtmlHelper(_dropDownListViewData);
+ SelectList selectList = new SelectList(MultiSelectListTest.GetSampleStrings(), "Charlie");
+
+ // Act
+ MvcHtmlString html = helper.DropDownList("foo", selectList);
+
+ // Assert
+ Assert.Equal(
+ @"<select id=""foo"" name=""foo""><option>Alpha</option>
+<option selected=""selected"">Bravo</option>
+<option>Charlie</option>
+</select>",
+ html.ToHtmlString());
+ }
+
+ [Fact]
+ public void DropDownListWithAttributesDictionary()
+ {
+ // Arrange
+ HtmlHelper helper = MvcHelper.GetHtmlHelper(new ViewDataDictionary());
+ SelectList selectList = new SelectList(MultiSelectListTest.GetSampleStrings());
+
+ // Act
+ MvcHtmlString html = helper.DropDownList("foo", selectList, null /* optionLabel */, HtmlHelperTest.AttributesDictionary);
+
+ // Assert
+ Assert.Equal(
+ @"<select baz=""BazValue"" id=""foo"" name=""foo""><option>Alpha</option>
+<option>Bravo</option>
+<option>Charlie</option>
+</select>",
+ html.ToHtmlString());
+ }
+
+ [Fact]
+ public void DropDownListWithEmptyNameThrows()
+ {
+ // Arrange
+ HtmlHelper helper = MvcHelper.GetHtmlHelper(new ViewDataDictionary());
+
+ // Act & Assert
+ Assert.ThrowsArgumentNullOrEmpty(
+ delegate { helper.DropDownList(String.Empty, (SelectList)null /* selectList */, (string)null /* optionLabel */); },
+ "name");
+ }
+
+ [Fact]
+ public void DropDownListWithErrors()
+ {
+ // Arrange
+ SelectList selectList = new SelectList(MultiSelectListTest.GetSampleStrings(), new[] { "Charlie" });
+ ViewDataDictionary viewData = GetViewDataWithErrors();
+ HtmlHelper helper = MvcHelper.GetHtmlHelper(viewData);
+
+ // Act
+ MvcHtmlString html = helper.DropDownList("foo", selectList, null /* optionLabel */, HtmlHelperTest.AttributesObjectDictionary);
+
+ // Assert
+ Assert.Equal(
+ @"<select baz=""BazObjValue"" class=""input-validation-error"" id=""foo"" name=""foo""><option>Alpha</option>
+<option selected=""selected"">Bravo</option>
+<option>Charlie</option>
+</select>",
+ html.ToHtmlString());
+ }
+
+ [Fact]
+ public void DropDownListWithErrorsAndCustomClass()
+ {
+ // Arrange
+ SelectList selectList = new SelectList(MultiSelectListTest.GetSampleStrings());
+ ViewDataDictionary viewData = GetViewDataWithErrors();
+ HtmlHelper helper = MvcHelper.GetHtmlHelper(viewData);
+
+ // Act
+ MvcHtmlString html = helper.DropDownList("foo", selectList, null /* optionLabel */, new { @class = "foo-class" });
+
+ // Assert
+ Assert.Equal(
+ @"<select class=""input-validation-error foo-class"" id=""foo"" name=""foo""><option>Alpha</option>
+<option selected=""selected"">Bravo</option>
+<option>Charlie</option>
+</select>",
+ html.ToHtmlString());
+ }
+
+ [Fact]
+ public void DropDownListWithNullNameThrows()
+ {
+ // Arrange
+ HtmlHelper helper = MvcHelper.GetHtmlHelper(new ViewDataDictionary());
+
+ // Act & Assert
+ Assert.ThrowsArgumentNullOrEmpty(
+ delegate { helper.DropDownList(null /* name */, (SelectList)null /* selectList */, (string)null /* optionLabel */); },
+ "name");
+ }
+
+ [Fact]
+ public void DropDownListWithNullSelectListUsesViewData()
+ {
+ // Arrange
+ HtmlHelper helper = MvcHelper.GetHtmlHelper();
+ helper.ViewData["foo"] = new MultiSelectList(MultiSelectListTest.GetSampleStrings(), new[] { "Charlie" });
+
+ // Act
+ MvcHtmlString html = helper.DropDownList("foo");
+
+ // Assert
+ Assert.Equal(
+ @"<select id=""foo"" name=""foo""><option>Alpha</option>
+<option>Bravo</option>
+<option selected=""selected"">Charlie</option>
+</select>",
+ html.ToHtmlString());
+ }
+
+ [Fact]
+ public void DropDownListWithObjectDictionary()
+ {
+ // Arrange
+ SelectList selectList = new SelectList(MultiSelectListTest.GetSampleStrings());
+ ViewDataDictionary viewData = new ViewDataDictionary();
+ HtmlHelper helper = MvcHelper.GetHtmlHelper(viewData);
+
+ // Act
+ MvcHtmlString html = helper.DropDownList("foo", selectList, null /* optionLabel */, HtmlHelperTest.AttributesObjectDictionary);
+
+ // Assert
+ Assert.Equal(
+ @"<select baz=""BazObjValue"" id=""foo"" name=""foo""><option>Alpha</option>
+<option>Bravo</option>
+<option>Charlie</option>
+</select>",
+ html.ToHtmlString());
+ }
+
+ [Fact]
+ public void DropDownListWithObjectDictionaryWithUnderscores()
+ {
+ // Arrange
+ SelectList selectList = new SelectList(MultiSelectListTest.GetSampleStrings());
+ ViewDataDictionary viewData = new ViewDataDictionary();
+ HtmlHelper helper = MvcHelper.GetHtmlHelper(viewData);
+
+ // Act
+ MvcHtmlString html = helper.DropDownList("foo", selectList, null /* optionLabel */, HtmlHelperTest.AttributesObjectUnderscoresDictionary);
+
+ // Assert
+ Assert.Equal(
+ @"<select foo-baz=""BazObjValue"" id=""foo"" name=""foo""><option>Alpha</option>
+<option>Bravo</option>
+<option>Charlie</option>
+</select>",
+ html.ToHtmlString());
+ }
+
+ [Fact]
+ public void DropDownListWithObjectDictionaryAndSelectList()
+ {
+ // Arrange
+ HtmlHelper helper = MvcHelper.GetHtmlHelper(new ViewDataDictionary());
+ SelectList selectList = new SelectList(MultiSelectListTest.GetSampleStrings());
+
+ // Act
+ MvcHtmlString html = helper.DropDownList("foo", selectList, null /* optionLabel */, HtmlHelperTest.AttributesObjectDictionary);
+
+ // Assert
+ Assert.Equal(
+ @"<select baz=""BazObjValue"" id=""foo"" name=""foo""><option>Alpha</option>
+<option>Bravo</option>
+<option>Charlie</option>
+</select>",
+ html.ToHtmlString());
+ }
+
+ [Fact]
+ public void DropDownListWithObjectDictionaryAndSelectListNoOptionLabel()
+ {
+ // Arrange
+ HtmlHelper helper = MvcHelper.GetHtmlHelper(new ViewDataDictionary());
+ SelectList selectList = new SelectList(MultiSelectListTest.GetSampleStrings());
+
+ // Act
+ MvcHtmlString html = helper.DropDownList("foo", selectList, HtmlHelperTest.AttributesObjectDictionary);
+
+ // Assert
+ Assert.Equal(
+ @"<select baz=""BazObjValue"" id=""foo"" name=""foo""><option>Alpha</option>
+<option>Bravo</option>
+<option>Charlie</option>
+</select>",
+ html.ToHtmlString());
+ }
+
+ [Fact]
+ public void DropDownListWithObjectDictionaryWithUnderscoresAndSelectListNoOptionLabel()
+ {
+ // Arrange
+ HtmlHelper helper = MvcHelper.GetHtmlHelper(new ViewDataDictionary());
+ SelectList selectList = new SelectList(MultiSelectListTest.GetSampleStrings());
+
+ // Act
+ MvcHtmlString html = helper.DropDownList("foo", selectList, HtmlHelperTest.AttributesObjectUnderscoresDictionary);
+
+ // Assert
+ Assert.Equal(
+ @"<select foo-baz=""BazObjValue"" id=""foo"" name=""foo""><option>Alpha</option>
+<option>Bravo</option>
+<option>Charlie</option>
+</select>",
+ html.ToHtmlString());
+ }
+
+ [Fact]
+ public void DropDownListWithObjectDictionaryAndEmptyOptionLabel()
+ {
+ // Arrange
+ HtmlHelper helper = MvcHelper.GetHtmlHelper(new ViewDataDictionary());
+ SelectList selectList = new SelectList(MultiSelectListTest.GetSampleStrings());
+
+ // Act
+ MvcHtmlString html = helper.DropDownList("foo", selectList, String.Empty /* optionLabel */, HtmlHelperTest.AttributesObjectDictionary);
+
+ // Assert
+ Assert.Equal(
+ @"<select baz=""BazObjValue"" id=""foo"" name=""foo""><option value=""""></option>
+<option>Alpha</option>
+<option>Bravo</option>
+<option>Charlie</option>
+</select>",
+ html.ToHtmlString());
+ }
+
+ [Fact]
+ public void DropDownListWithObjectDictionaryAndTitle()
+ {
+ // Arrange
+ HtmlHelper helper = MvcHelper.GetHtmlHelper(new ViewDataDictionary());
+ SelectList selectList = new SelectList(MultiSelectListTest.GetSampleStrings());
+
+ // Act
+ MvcHtmlString html = helper.DropDownList("foo", selectList, "[Select Something]", HtmlHelperTest.AttributesObjectDictionary);
+
+ // Assert
+ Assert.Equal(
+ @"<select baz=""BazObjValue"" id=""foo"" name=""foo""><option value="""">[Select Something]</option>
+<option>Alpha</option>
+<option>Bravo</option>
+<option>Charlie</option>
+</select>",
+ html.ToHtmlString());
+ }
+
+ [Fact]
+ public void DropDownListUsesViewDataSelectList()
+ {
+ // Arrange
+ HtmlHelper helper = MvcHelper.GetHtmlHelper(GetViewDataWithSelectList());
+
+ // Act
+ MvcHtmlString html = helper.DropDownList("foo", (string)null /* optionLabel */);
+
+ // Assert
+ Assert.Equal(
+ @"<select id=""foo"" name=""foo""><option value=""A"">Alpha</option>
+<option value=""B"">Bravo</option>
+<option selected=""selected"" value=""C"">Charlie</option>
+</select>",
+ html.ToHtmlString());
+ }
+
+ [Fact]
+ public void DropDownListUsesModelState()
+ {
+ // Arrange
+ SelectList selectList = new SelectList(MultiSelectListTest.GetSampleStrings());
+ ViewDataDictionary viewData = GetViewDataWithErrors();
+ viewData["foo"] = selectList;
+ HtmlHelper helper = MvcHelper.GetHtmlHelper(viewData);
+
+ // Act
+ MvcHtmlString html = helper.DropDownList("foo");
+
+ // Assert
+ Assert.Equal(
+ @"<select class=""input-validation-error"" id=""foo"" name=""foo""><option>Alpha</option>
+<option selected=""selected"">Bravo</option>
+<option>Charlie</option>
+</select>",
+ html.ToHtmlString());
+ }
+
+ [Fact]
+ public void DropDownListUsesViewDataSelectListNoOptionLabel()
+ {
+ // Arrange
+ HtmlHelper helper = MvcHelper.GetHtmlHelper(GetViewDataWithSelectList());
+
+ // Act
+ MvcHtmlString html = helper.DropDownList("foo");
+
+ // Assert
+ Assert.Equal(
+ @"<select id=""foo"" name=""foo""><option value=""A"">Alpha</option>
+<option value=""B"">Bravo</option>
+<option selected=""selected"" value=""C"">Charlie</option>
+</select>",
+ html.ToHtmlString());
+ }
+
+ [Fact]
+ public void DropDownListWithDotReplacementForId()
+ {
+ // Arrange
+ HtmlHelper helper = MvcHelper.GetHtmlHelper(GetViewDataWithSelectList());
+
+ // Act
+ MvcHtmlString html = helper.DropDownList("foo.bar");
+
+ // Assert
+ Assert.Equal(
+ @"<select id=""foo_bar"" name=""foo.bar""><option value=""A"">Alpha</option>
+<option value=""B"">Bravo</option>
+<option selected=""selected"" value=""C"">Charlie</option>
+</select>",
+ html.ToHtmlString());
+ }
+
+ [Fact]
+ public void DropDownListWithIEnumerableSelectListItem()
+ {
+ // Arrange
+ ViewDataDictionary vdd = new ViewDataDictionary { { "foo", MultiSelectListTest.GetSampleIEnumerableObjects() } };
+ HtmlHelper helper = MvcHelper.GetHtmlHelper(vdd);
+
+ // Act
+ MvcHtmlString html = helper.DropDownList("foo");
+
+ // Assert
+ Assert.Equal(
+ @"<select id=""foo"" name=""foo""><option value=""123456789"">John</option>
+<option value=""987654321"">Jane</option>
+<option selected=""selected"" value=""111111111"">Joe</option>
+</select>",
+ html.ToHtmlString());
+ }
+
+ [Fact]
+ public void DropDownListWithIEnumerableSelectListItemSelectsDefaultFromViewData()
+ {
+ // Arrange
+ ViewDataDictionary vdd = new ViewDataDictionary { { "foo", "123456789" } };
+ HtmlHelper helper = MvcHelper.GetHtmlHelper(vdd);
+
+ // Act
+ MvcHtmlString html = helper.DropDownList("foo", MultiSelectListTest.GetSampleIEnumerableObjects());
+
+ // Assert
+ Assert.Equal(
+ @"<select id=""foo"" name=""foo""><option selected=""selected"" value=""123456789"">John</option>
+<option value=""987654321"">Jane</option>
+<option value=""111111111"">Joe</option>
+</select>",
+ html.ToHtmlString());
+ }
+
+ [Fact]
+ public void DropDownListWithListOfSelectListItemSelectsDefaultFromViewData()
+ {
+ // Arrange
+ ViewDataDictionary vdd = new ViewDataDictionary { { "foo", "123456789" } };
+ HtmlHelper helper = MvcHelper.GetHtmlHelper(vdd);
+
+ // Act
+ MvcHtmlString html = helper.DropDownList("foo", MultiSelectListTest.GetSampleListObjects());
+
+ // Assert
+ Assert.Equal(
+ @"<select id=""foo"" name=""foo""><option selected=""selected"" value=""123456789"">John</option>
+<option value=""987654321"">Jane</option>
+<option value=""111111111"">Joe</option>
+</select>",
+ html.ToHtmlString());
+ }
+
+ [Fact]
+ public void DropDownListWithListOfSelectListItem()
+ {
+ // Arrange
+ ViewDataDictionary vdd = new ViewDataDictionary { { "foo", MultiSelectListTest.GetSampleListObjects() } };
+ HtmlHelper helper = MvcHelper.GetHtmlHelper(vdd);
+
+ // Act
+ MvcHtmlString html = helper.DropDownList("foo");
+
+ // Assert
+ Assert.Equal(
+ @"<select id=""foo"" name=""foo""><option value=""123456789"">John</option>
+<option value=""987654321"">Jane</option>
+<option selected=""selected"" value=""111111111"">Joe</option>
+</select>",
+ html.ToHtmlString());
+ }
+
+ [Fact]
+ public void DropDownListWithNullViewDataValueThrows()
+ {
+ // Arrange
+ HtmlHelper helper = MvcHelper.GetHtmlHelper(new ViewDataDictionary());
+
+ // Act
+ Assert.Throws<InvalidOperationException>(
+ delegate { helper.DropDownList("foo", (string)null /* optionLabel */); },
+ "There is no ViewData item of type 'IEnumerable<SelectListItem>' that has the key 'foo'.");
+ }
+
+ [Fact]
+ public void DropDownListWithWrongViewDataTypeValueThrows()
+ {
+ // Arrange
+ HtmlHelper helper = MvcHelper.GetHtmlHelper(new ViewDataDictionary { { "foo", 123 } });
+
+ // Act
+ Assert.Throws<InvalidOperationException>(
+ delegate { helper.DropDownList("foo", (string)null /* optionLabel */); },
+ "The ViewData item that has the key 'foo' is of type 'System.Int32' but must be of type 'IEnumerable<SelectListItem>'.");
+ }
+
+ [Fact]
+ public void DropDownListWithPrefix()
+ {
+ // Arrange
+ HtmlHelper helper = MvcHelper.GetHtmlHelper(new ViewDataDictionary());
+ SelectList selectList = new SelectList(MultiSelectListTest.GetSampleStrings());
+ helper.ViewContext.ViewData.TemplateInfo.HtmlFieldPrefix = "MyPrefix";
+
+ // Act
+ MvcHtmlString html = helper.DropDownList("foo", selectList, HtmlHelperTest.AttributesObjectDictionary);
+
+ // Assert
+ Assert.Equal(
+ @"<select baz=""BazObjValue"" id=""MyPrefix_foo"" name=""MyPrefix.foo""><option>Alpha</option>
+<option>Bravo</option>
+<option>Charlie</option>
+</select>",
+ html.ToHtmlString());
+ }
+
+ [Fact]
+ public void DropDownListWithPrefixAndEmptyName()
+ {
+ // Arrange
+ HtmlHelper helper = MvcHelper.GetHtmlHelper(new ViewDataDictionary());
+ SelectList selectList = new SelectList(MultiSelectListTest.GetSampleStrings());
+ helper.ViewContext.ViewData.TemplateInfo.HtmlFieldPrefix = "MyPrefix";
+
+ // Act
+ MvcHtmlString html = helper.DropDownList("", selectList, HtmlHelperTest.AttributesObjectDictionary);
+
+ // Assert
+ Assert.Equal(
+ @"<select baz=""BazObjValue"" id=""MyPrefix"" name=""MyPrefix""><option>Alpha</option>
+<option>Bravo</option>
+<option>Charlie</option>
+</select>",
+ html.ToHtmlString());
+ }
+
+ [Fact]
+ public void DropDownListWithPrefixAndNullSelectListUsesViewData()
+ {
+ // Arrange
+ HtmlHelper helper = MvcHelper.GetHtmlHelper();
+ helper.ViewData["foo"] = new MultiSelectList(MultiSelectListTest.GetSampleStrings(), new[] { "Charlie" });
+ helper.ViewContext.ViewData.TemplateInfo.HtmlFieldPrefix = "MyPrefix";
+
+ // Act
+ MvcHtmlString html = helper.DropDownList("foo");
+
+ // Assert
+ Assert.Equal(
+ @"<select id=""MyPrefix_foo"" name=""MyPrefix.foo""><option>Alpha</option>
+<option>Bravo</option>
+<option selected=""selected"">Charlie</option>
+</select>",
+ html.ToHtmlString());
+ }
+
+ // DropDownListFor
+
+ [Fact]
+ public void DropDownListForWithNullExpressionThrows()
+ {
+ // Arrange
+ HtmlHelper<object> helper = MvcHelper.GetHtmlHelper(new ViewDataDictionary<object>());
+ SelectList selectList = new SelectList(MultiSelectListTest.GetSampleAnonymousObjects(), "Letter", "FullWord", "C");
+
+ // Act & Assert
+ Assert.ThrowsArgumentNull(
+ () => helper.DropDownListFor<object, object>(null /* expression */, selectList),
+ "expression"
+ );
+ }
+
+ [Fact]
+ public void DropDownListForUsesExplicitValueIfNotProvidedInViewData()
+ {
+ // Arrange
+ HtmlHelper<FooModel> helper = MvcHelper.GetHtmlHelper(new ViewDataDictionary<FooModel>());
+ SelectList selectList = new SelectList(MultiSelectListTest.GetSampleAnonymousObjects(), "Letter", "FullWord", "C");
+
+ // Act
+ MvcHtmlString html = helper.DropDownListFor(m => m.foo, selectList, (string)null /* optionLabel */);
+
+ // Assert
+ Assert.Equal(
+ @"<select id=""foo"" name=""foo""><option value=""A"">Alpha</option>
+<option value=""B"">Bravo</option>
+<option selected=""selected"" value=""C"">Charlie</option>
+</select>",
+ html.ToHtmlString());
+ }
+
+ [Fact]
+ public void DropDownListForUsesExplicitValueIfNotProvidedInViewData_Unobtrusive()
+ {
+ // Arrange
+ HtmlHelper<FooModel> helper = MvcHelper.GetHtmlHelper(new ViewDataDictionary<FooModel>());
+ helper.ViewContext.ClientValidationEnabled = true;
+ helper.ViewContext.UnobtrusiveJavaScriptEnabled = true;
+ helper.ViewContext.FormContext = new FormContext();
+ helper.ClientValidationRuleFactory = (name, metadata) => new[] { new ModelClientValidationRule { ValidationType = "type", ErrorMessage = "error" } };
+ SelectList selectList = new SelectList(MultiSelectListTest.GetSampleAnonymousObjects(), "Letter", "FullWord", "C");
+
+ // Act
+ MvcHtmlString html = helper.DropDownListFor(m => m.foo, selectList, (string)null /* optionLabel */);
+
+ // Assert
+ Assert.Equal(
+ @"<select data-val=""true"" data-val-type=""error"" id=""foo"" name=""foo""><option value=""A"">Alpha</option>
+<option value=""B"">Bravo</option>
+<option selected=""selected"" value=""C"">Charlie</option>
+</select>",
+ html.ToHtmlString());
+ }
+
+ [Fact]
+ public void DropDownListForWithEnumerableModel_Unobtrusive()
+ {
+ // Arrange
+ HtmlHelper<IEnumerable<RequiredModel>> helper = MvcHelper.GetHtmlHelper(new ViewDataDictionary<IEnumerable<RequiredModel>>());
+ helper.ViewContext.ClientValidationEnabled = true;
+ helper.ViewContext.UnobtrusiveJavaScriptEnabled = true;
+ helper.ViewContext.FormContext = new FormContext();
+ helper.ViewContext.ViewData.TemplateInfo.HtmlFieldPrefix = "MyPrefix";
+
+ SelectList selectList = new SelectList(MultiSelectListTest.GetSampleAnonymousObjects(), "Letter", "FullWord", "C");
+
+ // Act
+ MvcHtmlString html = helper.DropDownListFor(m => m.ElementAt(0).foo, selectList);
+
+ // Assert
+ Assert.Equal(
+ @"<select data-val=""true"" data-val-required=""The foo field is required."" id=""MyPrefix_foo"" name=""MyPrefix.foo""><option value=""A"">Alpha</option>
+<option value=""B"">Bravo</option>
+<option selected=""selected"" value=""C"">Charlie</option>
+</select>",
+ html.ToHtmlString());
+ }
+
+ [Fact]
+ public void DropDownListForUsesViewDataDefaultValue()
+ {
+ // Arrange
+ HtmlHelper<FooModel> helper = MvcHelper.GetHtmlHelper(_dropDownListViewData);
+ SelectList selectList = new SelectList(MultiSelectListTest.GetSampleStrings(), "Charlie");
+
+ // Act
+ MvcHtmlString html = helper.DropDownListFor(m => m.foo, selectList, (string)null /* optionLabel */);
+
+ // Assert
+ Assert.Equal(
+ @"<select id=""foo"" name=""foo""><option>Alpha</option>
+<option selected=""selected"">Bravo</option>
+<option>Charlie</option>
+</select>",
+ html.ToHtmlString());
+ }
+
+ [Fact]
+ public void DropDownListForUsesViewDataDefaultValueNoOptionLabel()
+ {
+ // Arrange
+ HtmlHelper<FooModel> helper = MvcHelper.GetHtmlHelper(_dropDownListViewData);
+ SelectList selectList = new SelectList(MultiSelectListTest.GetSampleStrings(), "Charlie");
+
+ // Act
+ MvcHtmlString html = helper.DropDownListFor(m => m.foo, selectList);
+
+ // Assert
+ Assert.Equal(
+ @"<select id=""foo"" name=""foo""><option>Alpha</option>
+<option selected=""selected"">Bravo</option>
+<option>Charlie</option>
+</select>",
+ html.ToHtmlString());
+ }
+
+ [Fact]
+ public void DropDownListForWithAttributesDictionary()
+ {
+ // Arrange
+ HtmlHelper<FooModel> helper = MvcHelper.GetHtmlHelper(new ViewDataDictionary<FooModel>());
+ SelectList selectList = new SelectList(MultiSelectListTest.GetSampleStrings());
+
+ // Act
+ MvcHtmlString html = helper.DropDownListFor(m => m.foo, selectList, null /* optionLabel */, HtmlHelperTest.AttributesDictionary);
+
+ // Assert
+ Assert.Equal(
+ @"<select baz=""BazValue"" id=""foo"" name=""foo""><option>Alpha</option>
+<option>Bravo</option>
+<option>Charlie</option>
+</select>",
+ html.ToHtmlString());
+ }
+
+ [Fact]
+ public void DropDownListForWithErrors()
+ {
+ // Arrange
+ HtmlHelper<FooBarModel> helper = MvcHelper.GetHtmlHelper(GetViewDataWithErrors());
+ SelectList selectList = new SelectList(MultiSelectListTest.GetSampleStrings(), new[] { "Charlie" });
+
+ // Act
+ MvcHtmlString html = helper.DropDownListFor(m => m.foo, selectList, null /* optionLabel */, HtmlHelperTest.AttributesObjectDictionary);
+
+ // Assert
+ Assert.Equal(
+ @"<select baz=""BazObjValue"" class=""input-validation-error"" id=""foo"" name=""foo""><option>Alpha</option>
+<option selected=""selected"">Bravo</option>
+<option>Charlie</option>
+</select>",
+ html.ToHtmlString());
+ }
+
+ [Fact]
+ public void DropDownListForWithErrorsAndCustomClass()
+ {
+ // Arrange
+ HtmlHelper<FooBarModel> helper = MvcHelper.GetHtmlHelper(GetViewDataWithErrors());
+ SelectList selectList = new SelectList(MultiSelectListTest.GetSampleStrings());
+
+ // Act
+ MvcHtmlString html = helper.DropDownListFor(m => m.foo, selectList, null /* optionLabel */, new { @class = "foo-class" });
+
+ // Assert
+ Assert.Equal(
+ @"<select class=""input-validation-error foo-class"" id=""foo"" name=""foo""><option>Alpha</option>
+<option selected=""selected"">Bravo</option>
+<option>Charlie</option>
+</select>",
+ html.ToHtmlString());
+ }
+
+ [Fact]
+ public void DropDownListForWithObjectDictionary()
+ {
+ // Arrange
+ HtmlHelper<FooModel> helper = MvcHelper.GetHtmlHelper(new ViewDataDictionary<FooModel>());
+ SelectList selectList = new SelectList(MultiSelectListTest.GetSampleStrings());
+
+ // Act
+ MvcHtmlString html = helper.DropDownListFor(m => m.foo, selectList, null /* optionLabel */, HtmlHelperTest.AttributesObjectDictionary);
+
+ // Assert
+ Assert.Equal(
+ @"<select baz=""BazObjValue"" id=""foo"" name=""foo""><option>Alpha</option>
+<option>Bravo</option>
+<option>Charlie</option>
+</select>",
+ html.ToHtmlString());
+ }
+
+ [Fact]
+ public void DropDownListForWithObjectDictionaryWithUnderscores()
+ {
+ // Arrange
+ HtmlHelper<FooModel> helper = MvcHelper.GetHtmlHelper(new ViewDataDictionary<FooModel>());
+ SelectList selectList = new SelectList(MultiSelectListTest.GetSampleStrings());
+
+ // Act
+ MvcHtmlString html = helper.DropDownListFor(m => m.foo, selectList, null /* optionLabel */, HtmlHelperTest.AttributesObjectUnderscoresDictionary);
+
+ // Assert
+ Assert.Equal(
+ @"<select foo-baz=""BazObjValue"" id=""foo"" name=""foo""><option>Alpha</option>
+<option>Bravo</option>
+<option>Charlie</option>
+</select>",
+ html.ToHtmlString());
+ }
+
+ [Fact]
+ public void DropDownListForWithObjectDictionaryAndSelectListNoOptionLabel()
+ {
+ // Arrange
+ HtmlHelper<FooModel> helper = MvcHelper.GetHtmlHelper(new ViewDataDictionary<FooModel>());
+ SelectList selectList = new SelectList(MultiSelectListTest.GetSampleStrings());
+
+ // Act
+ MvcHtmlString html = helper.DropDownListFor(m => m.foo, selectList, HtmlHelperTest.AttributesObjectDictionary);
+
+ // Assert
+ Assert.Equal(
+ @"<select baz=""BazObjValue"" id=""foo"" name=""foo""><option>Alpha</option>
+<option>Bravo</option>
+<option>Charlie</option>
+</select>",
+ html.ToHtmlString());
+ }
+
+ [Fact]
+ public void DropDownListForWithObjectDictionaryWithUnderscoresAndSelectListNoOptionLabel()
+ {
+ // Arrange
+ HtmlHelper<FooModel> helper = MvcHelper.GetHtmlHelper(new ViewDataDictionary<FooModel>());
+ SelectList selectList = new SelectList(MultiSelectListTest.GetSampleStrings());
+
+ // Act
+ MvcHtmlString html = helper.DropDownListFor(m => m.foo, selectList, HtmlHelperTest.AttributesObjectUnderscoresDictionary);
+
+ // Assert
+ Assert.Equal(
+ @"<select foo-baz=""BazObjValue"" id=""foo"" name=""foo""><option>Alpha</option>
+<option>Bravo</option>
+<option>Charlie</option>
+</select>",
+ html.ToHtmlString());
+ }
+
+ [Fact]
+ public void DropDownListForWithObjectDictionaryAndEmptyOptionLabel()
+ {
+ // Arrange
+ HtmlHelper<FooModel> helper = MvcHelper.GetHtmlHelper(new ViewDataDictionary<FooModel>());
+ SelectList selectList = new SelectList(MultiSelectListTest.GetSampleStrings());
+
+ // Act
+ MvcHtmlString html = helper.DropDownListFor(m => m.foo, selectList, String.Empty /* optionLabel */, HtmlHelperTest.AttributesObjectDictionary);
+
+ // Assert
+ Assert.Equal(
+ @"<select baz=""BazObjValue"" id=""foo"" name=""foo""><option value=""""></option>
+<option>Alpha</option>
+<option>Bravo</option>
+<option>Charlie</option>
+</select>",
+ html.ToHtmlString());
+ }
+
+ [Fact]
+ public void DropDownListForWithObjectDictionaryAndTitle()
+ {
+ // Arrange
+ HtmlHelper<FooModel> helper = MvcHelper.GetHtmlHelper(new ViewDataDictionary<FooModel>());
+ SelectList selectList = new SelectList(MultiSelectListTest.GetSampleStrings());
+
+ // Act
+ MvcHtmlString html = helper.DropDownListFor(m => m.foo, selectList, "[Select Something]", HtmlHelperTest.AttributesObjectDictionary);
+
+ // Assert
+ Assert.Equal(
+ @"<select baz=""BazObjValue"" id=""foo"" name=""foo""><option value="""">[Select Something]</option>
+<option>Alpha</option>
+<option>Bravo</option>
+<option>Charlie</option>
+</select>",
+ html.ToHtmlString());
+ }
+
+ [Fact]
+ public void DropDownListForWithIEnumerableSelectListItemSelectsDefaultFromViewData()
+ {
+ // Arrange
+ ViewDataDictionary<FooModel> vdd = new ViewDataDictionary<FooModel> { { "foo", "123456789" } };
+ HtmlHelper<FooModel> helper = MvcHelper.GetHtmlHelper(vdd);
+
+ // Act
+ MvcHtmlString html = helper.DropDownListFor(m => m.foo, MultiSelectListTest.GetSampleIEnumerableObjects());
+
+ // Assert
+ Assert.Equal(
+ @"<select id=""foo"" name=""foo""><option selected=""selected"" value=""123456789"">John</option>
+<option value=""987654321"">Jane</option>
+<option value=""111111111"">Joe</option>
+</select>",
+ html.ToHtmlString());
+ }
+
+ [Fact]
+ public void DropDownListForWithListOfSelectListItemSelectsDefaultFromViewData()
+ {
+ // Arrange
+ ViewDataDictionary<FooModel> vdd = new ViewDataDictionary<FooModel> { { "foo", "123456789" } };
+ HtmlHelper<FooModel> helper = MvcHelper.GetHtmlHelper(vdd);
+
+ // Act
+ MvcHtmlString html = helper.DropDownListFor(m => m.foo, MultiSelectListTest.GetSampleListObjects());
+
+ // Assert
+ Assert.Equal(
+ @"<select id=""foo"" name=""foo""><option selected=""selected"" value=""123456789"">John</option>
+<option value=""987654321"">Jane</option>
+<option value=""111111111"">Joe</option>
+</select>",
+ html.ToHtmlString());
+ }
+
+ [Fact]
+ public void DropDownListForWithPrefix()
+ {
+ // Arrange
+ HtmlHelper<FooModel> helper = MvcHelper.GetHtmlHelper(new ViewDataDictionary<FooModel>());
+ SelectList selectList = new SelectList(MultiSelectListTest.GetSampleStrings());
+ helper.ViewContext.ViewData.TemplateInfo.HtmlFieldPrefix = "MyPrefix";
+
+ // Act
+ MvcHtmlString html = helper.DropDownListFor(m => m.foo, selectList, HtmlHelperTest.AttributesObjectDictionary);
+
+ // Assert
+ Assert.Equal(
+ @"<select baz=""BazObjValue"" id=""MyPrefix_foo"" name=""MyPrefix.foo""><option>Alpha</option>
+<option>Bravo</option>
+<option>Charlie</option>
+</select>",
+ html.ToHtmlString());
+ }
+
+ [Fact]
+ public void DropDownListForWithPrefixAndEmptyName()
+ {
+ // Arrange
+ HtmlHelper<FooModel> helper = MvcHelper.GetHtmlHelper(new ViewDataDictionary<FooModel>());
+ SelectList selectList = new SelectList(MultiSelectListTest.GetSampleStrings());
+ helper.ViewContext.ViewData.TemplateInfo.HtmlFieldPrefix = "MyPrefix";
+
+ // Act
+ MvcHtmlString html = helper.DropDownListFor(m => m, selectList, HtmlHelperTest.AttributesObjectDictionary);
+
+ // Assert
+ Assert.Equal(
+ @"<select baz=""BazObjValue"" id=""MyPrefix"" name=""MyPrefix""><option>Alpha</option>
+<option>Bravo</option>
+<option>Charlie</option>
+</select>",
+ html.ToHtmlString());
+ }
+
+ [Fact]
+ public void DropDownListForWithPrefixAndIEnumerableSelectListItemSelectsDefaultFromViewData()
+ {
+ // Arrange
+ ViewDataDictionary<FooModel> vdd = new ViewDataDictionary<FooModel> { { "foo", "123456789" } };
+ vdd.TemplateInfo.HtmlFieldPrefix = "MyPrefix";
+ HtmlHelper<FooModel> helper = MvcHelper.GetHtmlHelper(vdd);
+
+ // Act
+ MvcHtmlString html = helper.DropDownListFor(m => m.foo, MultiSelectListTest.GetSampleIEnumerableObjects());
+
+ // Assert
+ Assert.Equal(
+ @"<select id=""MyPrefix_foo"" name=""MyPrefix.foo""><option selected=""selected"" value=""123456789"">John</option>
+<option value=""987654321"">Jane</option>
+<option value=""111111111"">Joe</option>
+</select>",
+ html.ToHtmlString());
+ }
+
+ // ListBox
+
+ [Fact]
+ public void ListBoxUsesExplicitValueIfNotProvidedInViewData()
+ {
+ // Arrange
+ HtmlHelper helper = MvcHelper.GetHtmlHelper(new ViewDataDictionary());
+ MultiSelectList selectList = new MultiSelectList(MultiSelectListTest.GetSampleAnonymousObjects(), "Letter", "FullWord", new[] { "A", "C" });
+
+ // Act
+ MvcHtmlString html = helper.ListBox("foo", selectList);
+
+ // Assert
+ Assert.Equal(
+ @"<select id=""foo"" multiple=""multiple"" name=""foo""><option selected=""selected"" value=""A"">Alpha</option>
+<option value=""B"">Bravo</option>
+<option selected=""selected"" value=""C"">Charlie</option>
+</select>",
+ html.ToHtmlString());
+ }
+
+ [Fact]
+ public void ListBoxUsesExplicitValueIfNotProvidedInViewData_Unobtrusive()
+ {
+ // Arrange
+ HtmlHelper helper = MvcHelper.GetHtmlHelper(new ViewDataDictionary());
+ helper.ViewContext.ClientValidationEnabled = true;
+ helper.ViewContext.UnobtrusiveJavaScriptEnabled = true;
+ helper.ViewContext.FormContext = new FormContext();
+ helper.ClientValidationRuleFactory = (name, metadata) => new[] { new ModelClientValidationRule { ValidationType = "type", ErrorMessage = "error" } };
+ MultiSelectList selectList = new MultiSelectList(MultiSelectListTest.GetSampleAnonymousObjects(), "Letter", "FullWord", new[] { "A", "C" });
+
+ // Act
+ MvcHtmlString html = helper.ListBox("foo", selectList);
+
+ // Assert
+ Assert.Equal(
+ @"<select data-val=""true"" data-val-type=""error"" id=""foo"" multiple=""multiple"" name=""foo""><option selected=""selected"" value=""A"">Alpha</option>
+<option value=""B"">Bravo</option>
+<option selected=""selected"" value=""C"">Charlie</option>
+</select>",
+ html.ToHtmlString());
+ }
+
+ [Fact]
+ public void ListBoxUsesViewDataDefaultValue()
+ {
+ // Arrange
+ HtmlHelper helper = MvcHelper.GetHtmlHelper(_listBoxViewData);
+ MultiSelectList selectList = new MultiSelectList(MultiSelectListTest.GetSampleStrings(), new[] { "Charlie" });
+
+ // Act
+ MvcHtmlString html = helper.ListBox("foo", selectList);
+
+ // Assert
+ Assert.Equal(
+ @"<select id=""foo"" multiple=""multiple"" name=""foo""><option>Alpha</option>
+<option selected=""selected"">Bravo</option>
+<option>Charlie</option>
+</select>",
+ html.ToHtmlString());
+ }
+
+ [Fact]
+ public void ListBoxWithErrors()
+ {
+ // Arrange
+ ViewDataDictionary viewData = GetViewDataWithErrors();
+ HtmlHelper helper = MvcHelper.GetHtmlHelper(viewData);
+ MultiSelectList list = new MultiSelectList(MultiSelectListTest.GetSampleStrings(), new[] { "Charlie" });
+
+ // Act
+ MvcHtmlString html = helper.ListBox("foo", list);
+
+ // Assert
+ Assert.Equal(
+ @"<select class=""input-validation-error"" id=""foo"" multiple=""multiple"" name=""foo""><option>Alpha</option>
+<option selected=""selected"">Bravo</option>
+<option selected=""selected"">Charlie</option>
+</select>",
+ html.ToHtmlString());
+ }
+
+ [Fact]
+ public void ListBoxWithErrorsAndCustomClass()
+ {
+ // Arrange
+ ViewDataDictionary viewData = GetViewDataWithErrors();
+ HtmlHelper helper = MvcHelper.GetHtmlHelper(viewData);
+ MultiSelectList selectList = new MultiSelectList(MultiSelectListTest.GetSampleStrings(), new[] { "Charlie" });
+
+ // Act
+ MvcHtmlString html = helper.ListBox("foo", selectList, new { @class = "foo-class" });
+
+ // Assert
+ Assert.Equal(
+ @"<select class=""input-validation-error foo-class"" id=""foo"" multiple=""multiple"" name=""foo""><option>Alpha</option>
+<option selected=""selected"">Bravo</option>
+<option selected=""selected"">Charlie</option>
+</select>",
+ html.ToHtmlString());
+ }
+
+ [Fact]
+ public void ListBoxWithNameOnly()
+ {
+ // Arrange
+ HtmlHelper helper = MvcHelper.GetHtmlHelper();
+ helper.ViewData["foo"] = new MultiSelectList(MultiSelectListTest.GetSampleStrings(), new[] { "Charlie" });
+
+ // Act
+ MvcHtmlString html = helper.ListBox("foo");
+
+ // Assert
+ Assert.Equal(
+ @"<select id=""foo"" multiple=""multiple"" name=""foo""><option>Alpha</option>
+<option>Bravo</option>
+<option selected=""selected"">Charlie</option>
+</select>",
+ html.ToHtmlString());
+ }
+
+ [Fact]
+ public void ListBoxWithAttributesDictionary()
+ {
+ // Arrange
+ ViewDataDictionary viewData = new ViewDataDictionary();
+ MultiSelectList selectList = new MultiSelectList(MultiSelectListTest.GetSampleStrings());
+ //viewData["foo"] = selectList;
+ HtmlHelper helper = MvcHelper.GetHtmlHelper(viewData);
+
+ // Act
+ MvcHtmlString html = helper.ListBox("foo", selectList, HtmlHelperTest.AttributesDictionary);
+
+ // Assert
+ Assert.Equal(
+ @"<select baz=""BazValue"" id=""foo"" multiple=""multiple"" name=""foo""><option>Alpha</option>
+<option>Bravo</option>
+<option>Charlie</option>
+</select>",
+ html.ToHtmlString());
+ }
+
+ [Fact]
+ public void ListBoxWithAttributesDictionaryAndMultiSelectList()
+ {
+ // Arrange
+ HtmlHelper helper = MvcHelper.GetHtmlHelper(new ViewDataDictionary());
+ MultiSelectList selectList = new MultiSelectList(MultiSelectListTest.GetSampleStrings());
+
+ // Act
+ MvcHtmlString html = helper.ListBox("foo", selectList, HtmlHelperTest.AttributesDictionary);
+
+ // Assert
+ Assert.Equal(
+ @"<select baz=""BazValue"" id=""foo"" multiple=""multiple"" name=""foo""><option>Alpha</option>
+<option>Bravo</option>
+<option>Charlie</option>
+</select>",
+ html.ToHtmlString());
+ }
+
+ [Fact]
+ public void ListBoxWithAttributesDictionaryOverridesName()
+ {
+ // DevDiv Bugs #217602:
+ // SelectInternal() should override the user-provided 'name' attribute
+
+ // Arrange
+ ViewDataDictionary viewData = new ViewDataDictionary();
+ MultiSelectList selectList = new MultiSelectList(MultiSelectListTest.GetSampleStrings());
+ HtmlHelper helper = MvcHelper.GetHtmlHelper(viewData);
+
+ // Act
+ MvcHtmlString html = helper.ListBox("foo", selectList, new { myAttr = "myValue", name = "theName" });
+
+ // Assert
+ Assert.Equal(
+ @"<select id=""foo"" multiple=""multiple"" myAttr=""myValue"" name=""foo""><option>Alpha</option>
+<option>Bravo</option>
+<option>Charlie</option>
+</select>",
+ html.ToHtmlString());
+ }
+
+ [Fact]
+ public void ListBoxWithEmptyNameThrows()
+ {
+ // Arrange
+ HtmlHelper helper = MvcHelper.GetHtmlHelper(new ViewDataDictionary());
+
+ // Act & Assert
+ Assert.ThrowsArgumentNullOrEmpty(
+ delegate { helper.ListBox(String.Empty, (MultiSelectList)null /* selectList */); },
+ "name");
+ }
+
+ [Fact]
+ public void ListBoxWithNullNameThrows()
+ {
+ // Arrange
+ HtmlHelper helper = MvcHelper.GetHtmlHelper(new ViewDataDictionary());
+
+ // Act & Assert
+ Assert.ThrowsArgumentNullOrEmpty(
+ delegate { helper.ListBox(null /* name */, (MultiSelectList)null /* selectList */); },
+ "name");
+ }
+
+ [Fact]
+ public void ListBoxWithNullSelectListUsesViewData()
+ {
+ // Arrange
+ HtmlHelper helper = MvcHelper.GetHtmlHelper();
+ helper.ViewData["foo"] = new MultiSelectList(MultiSelectListTest.GetSampleStrings(), new[] { "Charlie" });
+
+ // Act
+ MvcHtmlString html = helper.ListBox("foo", null);
+
+ // Assert
+ Assert.Equal(
+ @"<select id=""foo"" multiple=""multiple"" name=""foo""><option>Alpha</option>
+<option>Bravo</option>
+<option selected=""selected"">Charlie</option>
+</select>",
+ html.ToHtmlString());
+ }
+
+ [Fact]
+ public void ListBoxWithObjectDictionary()
+ {
+ // Arrange
+ HtmlHelper helper = MvcHelper.GetHtmlHelper(new ViewDataDictionary());
+ MultiSelectList selectList = new MultiSelectList(MultiSelectListTest.GetSampleStrings());
+
+ // Act
+ MvcHtmlString html = helper.ListBox("foo", selectList, HtmlHelperTest.AttributesObjectDictionary);
+
+ // Assert
+ Assert.Equal(
+ @"<select baz=""BazObjValue"" id=""foo"" multiple=""multiple"" name=""foo""><option>Alpha</option>
+<option>Bravo</option>
+<option>Charlie</option>
+</select>",
+ html.ToHtmlString());
+ }
+
+ [Fact]
+ public void ListBoxWithObjectDictionaryWithUnderscores()
+ {
+ // Arrange
+ HtmlHelper helper = MvcHelper.GetHtmlHelper(new ViewDataDictionary());
+ MultiSelectList selectList = new MultiSelectList(MultiSelectListTest.GetSampleStrings());
+
+ // Act
+ MvcHtmlString html = helper.ListBox("foo", selectList, HtmlHelperTest.AttributesObjectUnderscoresDictionary);
+
+ // Assert
+ Assert.Equal(
+ @"<select foo-baz=""BazObjValue"" id=""foo"" multiple=""multiple"" name=""foo""><option>Alpha</option>
+<option>Bravo</option>
+<option>Charlie</option>
+</select>",
+ html.ToHtmlString());
+ }
+
+ [Fact]
+ public void ListBoxWithIEnumerableSelectListItem()
+ {
+ // Arrange
+ ViewDataDictionary vdd = new ViewDataDictionary { { "foo", MultiSelectListTest.GetSampleIEnumerableObjects() } };
+ HtmlHelper helper = MvcHelper.GetHtmlHelper(vdd);
+
+ // Act
+ MvcHtmlString html = helper.ListBox("foo");
+
+ // Assert
+ Assert.Equal(
+ @"<select id=""foo"" multiple=""multiple"" name=""foo""><option value=""123456789"">John</option>
+<option value=""987654321"">Jane</option>
+<option selected=""selected"" value=""111111111"">Joe</option>
+</select>",
+ html.ToHtmlString());
+ }
+
+ [Fact]
+ public void ListBoxThrowsWhenExpressionDoesNotEvaluateToIEnumerable()
+ {
+ // Arrange
+ ViewDataDictionary vdd = new ViewDataDictionary { { "foo", 123456789 } };
+ HtmlHelper helper = MvcHelper.GetHtmlHelper(vdd);
+
+ // Act & Assert
+ Assert.Throws<InvalidOperationException>(
+ () => helper.ListBox("foo", MultiSelectListTest.GetSampleIEnumerableObjects()),
+ @"The parameter 'expression' must evaluate to an IEnumerable when multiple selection is allowed."
+ );
+ }
+
+ [Fact]
+ public void ListBoxThrowsWhenExpressionEvaluatesToString()
+ {
+ // Arrange
+ ViewDataDictionary vdd = new ViewDataDictionary { { "foo", "123456789" } };
+ HtmlHelper helper = MvcHelper.GetHtmlHelper(vdd);
+
+ // Act & Assert
+ Assert.Throws<InvalidOperationException>(
+ () => helper.ListBox("foo", MultiSelectListTest.GetSampleIEnumerableObjects()),
+ @"The parameter 'expression' must evaluate to an IEnumerable when multiple selection is allowed."
+ );
+ }
+
+ [Fact]
+ public void ListBoxWithListOfSelectListItemSelectsDefaultFromViewData()
+ {
+ // Arrange
+ ViewDataDictionary vdd = new ViewDataDictionary { { "foo", new string[] { "123456789", "111111111" } } };
+ HtmlHelper helper = MvcHelper.GetHtmlHelper(vdd);
+
+ // Act
+ MvcHtmlString html = helper.ListBox("foo", MultiSelectListTest.GetSampleListObjects());
+
+ // Assert
+ Assert.Equal(
+ @"<select id=""foo"" multiple=""multiple"" name=""foo""><option selected=""selected"" value=""123456789"">John</option>
+<option value=""987654321"">Jane</option>
+<option selected=""selected"" value=""111111111"">Joe</option>
+</select>",
+ html.ToHtmlString());
+ }
+
+ [Fact]
+ public void ListBoxWithListOfSelectListItem()
+ {
+ // Arrange
+ ViewDataDictionary vdd = new ViewDataDictionary { { "foo", MultiSelectListTest.GetSampleListObjects() } };
+ HtmlHelper helper = MvcHelper.GetHtmlHelper(vdd);
+
+ // Act
+ MvcHtmlString html = helper.ListBox("foo");
+
+ // Assert
+ Assert.Equal(
+ @"<select id=""foo"" multiple=""multiple"" name=""foo""><option value=""123456789"">John</option>
+<option value=""987654321"">Jane</option>
+<option selected=""selected"" value=""111111111"">Joe</option>
+</select>",
+ html.ToHtmlString());
+ }
+
+ [Fact]
+ public void ListBoxWithPrefix()
+ {
+ // Arrange
+ HtmlHelper helper = MvcHelper.GetHtmlHelper(new ViewDataDictionary());
+ MultiSelectList selectList = new MultiSelectList(MultiSelectListTest.GetSampleStrings());
+ helper.ViewContext.ViewData.TemplateInfo.HtmlFieldPrefix = "MyPrefix";
+
+ // Act
+ MvcHtmlString html = helper.ListBox("foo", selectList, HtmlHelperTest.AttributesObjectDictionary);
+
+ // Assert
+ Assert.Equal(
+ @"<select baz=""BazObjValue"" id=""MyPrefix_foo"" multiple=""multiple"" name=""MyPrefix.foo""><option>Alpha</option>
+<option>Bravo</option>
+<option>Charlie</option>
+</select>",
+ html.ToHtmlString());
+ }
+
+ [Fact]
+ public void ListBoxWithPrefixAndEmptyName()
+ {
+ // Arrange
+ HtmlHelper helper = MvcHelper.GetHtmlHelper(new ViewDataDictionary());
+ MultiSelectList selectList = new MultiSelectList(MultiSelectListTest.GetSampleStrings());
+ helper.ViewContext.ViewData.TemplateInfo.HtmlFieldPrefix = "MyPrefix";
+
+ // Act
+ MvcHtmlString html = helper.ListBox("", selectList, HtmlHelperTest.AttributesObjectDictionary);
+
+ // Assert
+ Assert.Equal(
+ @"<select baz=""BazObjValue"" id=""MyPrefix"" multiple=""multiple"" name=""MyPrefix""><option>Alpha</option>
+<option>Bravo</option>
+<option>Charlie</option>
+</select>",
+ html.ToHtmlString());
+ }
+
+ [Fact]
+ public void ListBoxWithPrefixAndNullSelectListUsesViewData()
+ {
+ // Arrange
+ HtmlHelper helper = MvcHelper.GetHtmlHelper();
+ helper.ViewData["foo"] = new MultiSelectList(MultiSelectListTest.GetSampleStrings(), new[] { "Charlie" });
+ helper.ViewContext.ViewData.TemplateInfo.HtmlFieldPrefix = "MyPrefix";
+
+ // Act
+ MvcHtmlString html = helper.ListBox("foo");
+
+ // Assert
+ Assert.Equal(
+ @"<select id=""MyPrefix_foo"" multiple=""multiple"" name=""MyPrefix.foo""><option>Alpha</option>
+<option>Bravo</option>
+<option selected=""selected"">Charlie</option>
+</select>",
+ html.ToHtmlString());
+ }
+
+ // ListBoxFor
+
+ [Fact]
+ public void ListBoxForWithNullExpressionThrows()
+ {
+ // Arrange
+ HtmlHelper<FooModel> helper = MvcHelper.GetHtmlHelper(new ViewDataDictionary<FooModel>());
+
+ // Act & Assert
+ Assert.ThrowsArgumentNull(
+ () => helper.ListBoxFor<FooModel, object>(null, null),
+ "expression");
+ }
+
+ [Fact]
+ public void ListBoxForUsesExplicitValueIfNotProvidedInViewData()
+ {
+ // Arrange
+ HtmlHelper<FooModel> helper = MvcHelper.GetHtmlHelper(new ViewDataDictionary<FooModel>());
+ MultiSelectList selectList = new MultiSelectList(MultiSelectListTest.GetSampleAnonymousObjects(), "Letter", "FullWord", new[] { "A", "C" });
+
+ // Act
+ MvcHtmlString html = helper.ListBoxFor(m => m.foo, selectList);
+
+ // Assert
+ Assert.Equal(
+ @"<select id=""foo"" multiple=""multiple"" name=""foo""><option selected=""selected"" value=""A"">Alpha</option>
+<option value=""B"">Bravo</option>
+<option selected=""selected"" value=""C"">Charlie</option>
+</select>",
+ html.ToHtmlString());
+ }
+
+ [Fact]
+ public void ListBoxForUsesExplicitValueIfNotProvidedInViewData_Unobtrusive()
+ {
+ // Arrange
+ HtmlHelper<FooModel> helper = MvcHelper.GetHtmlHelper(new ViewDataDictionary<FooModel>());
+ helper.ViewContext.ClientValidationEnabled = true;
+ helper.ViewContext.UnobtrusiveJavaScriptEnabled = true;
+ helper.ViewContext.FormContext = new FormContext();
+ helper.ClientValidationRuleFactory = (name, metadata) => new[] { new ModelClientValidationRule { ValidationType = "type", ErrorMessage = "error" } };
+ MultiSelectList selectList = new MultiSelectList(MultiSelectListTest.GetSampleAnonymousObjects(), "Letter", "FullWord", new[] { "A", "C" });
+
+ // Act
+ MvcHtmlString html = helper.ListBoxFor(m => m.foo, selectList);
+
+ // Assert
+ Assert.Equal(
+ @"<select data-val=""true"" data-val-type=""error"" id=""foo"" multiple=""multiple"" name=""foo""><option selected=""selected"" value=""A"">Alpha</option>
+<option value=""B"">Bravo</option>
+<option selected=""selected"" value=""C"">Charlie</option>
+</select>",
+ html.ToHtmlString());
+ }
+
+ [Fact]
+ public void ListBoxForWithEnumerableModel_Unobtrusive()
+ {
+ // Arrange
+ HtmlHelper<IEnumerable<RequiredModel>> helper = MvcHelper.GetHtmlHelper(new ViewDataDictionary<IEnumerable<RequiredModel>>());
+ helper.ViewContext.ClientValidationEnabled = true;
+ helper.ViewContext.UnobtrusiveJavaScriptEnabled = true;
+ helper.ViewContext.FormContext = new FormContext();
+ helper.ViewContext.ViewData.TemplateInfo.HtmlFieldPrefix = "MyPrefix";
+
+ MultiSelectList selectList = new MultiSelectList(MultiSelectListTest.GetSampleAnonymousObjects(), "Letter", "FullWord", "C");
+
+ // Act
+ MvcHtmlString html = helper.ListBoxFor(m => m.ElementAt(0).foo, selectList);
+
+ // Assert
+ Assert.Equal(
+ @"<select data-val=""true"" data-val-required=""The foo field is required."" id=""MyPrefix_foo"" multiple=""multiple"" name=""MyPrefix.foo""><option value=""A"">Alpha</option>
+<option value=""B"">Bravo</option>
+<option selected=""selected"" value=""C"">Charlie</option>
+</select>",
+ html.ToHtmlString());
+ }
+
+ [Fact]
+ public void ListBoxForUsesViewDataDefaultValue()
+ {
+ // Arrange
+ HtmlHelper<FooModel> helper = MvcHelper.GetHtmlHelper(_listBoxViewData);
+ MultiSelectList selectList = new MultiSelectList(MultiSelectListTest.GetSampleStrings(), new[] { "Charlie" });
+
+ // Act
+ MvcHtmlString html = helper.ListBoxFor(m => m.foo, selectList);
+
+ // Assert
+ Assert.Equal(
+ @"<select id=""foo"" multiple=""multiple"" name=""foo""><option>Alpha</option>
+<option selected=""selected"">Bravo</option>
+<option>Charlie</option>
+</select>",
+ html.ToHtmlString());
+ }
+
+ [Fact]
+ public void ListBoxForWithErrors()
+ {
+ // Arrange
+ HtmlHelper<FooBarModel> helper = MvcHelper.GetHtmlHelper(GetViewDataWithErrors());
+ MultiSelectList list = new MultiSelectList(MultiSelectListTest.GetSampleStrings(), new[] { "Charlie" });
+
+ // Act
+ MvcHtmlString html = helper.ListBoxFor(m => m.foo, list);
+
+ // Assert
+ Assert.Equal(
+ @"<select class=""input-validation-error"" id=""foo"" multiple=""multiple"" name=""foo""><option>Alpha</option>
+<option selected=""selected"">Bravo</option>
+<option selected=""selected"">Charlie</option>
+</select>",
+ html.ToHtmlString());
+ }
+
+ [Fact]
+ public void ListBoxForWithErrorsAndCustomClass()
+ {
+ // Arrange
+ HtmlHelper<FooBarModel> helper = MvcHelper.GetHtmlHelper(GetViewDataWithErrors());
+ MultiSelectList selectList = new MultiSelectList(MultiSelectListTest.GetSampleStrings(), new[] { "Charlie" });
+
+ // Act
+ MvcHtmlString html = helper.ListBoxFor(m => m.foo, selectList, new { @class = "foo-class" });
+
+ // Assert
+ Assert.Equal(
+ @"<select class=""input-validation-error foo-class"" id=""foo"" multiple=""multiple"" name=""foo""><option>Alpha</option>
+<option selected=""selected"">Bravo</option>
+<option selected=""selected"">Charlie</option>
+</select>",
+ html.ToHtmlString());
+ }
+
+ [Fact]
+ public void ListBoxForWithAttributesDictionary()
+ {
+ // Arrange
+ MultiSelectList selectList = new MultiSelectList(MultiSelectListTest.GetSampleStrings());
+ HtmlHelper<FooModel> helper = MvcHelper.GetHtmlHelper(new ViewDataDictionary<FooModel>());
+
+ // Act
+ MvcHtmlString html = helper.ListBoxFor(m => m.foo, selectList, HtmlHelperTest.AttributesDictionary);
+
+ // Assert
+ Assert.Equal(
+ @"<select baz=""BazValue"" id=""foo"" multiple=""multiple"" name=""foo""><option>Alpha</option>
+<option>Bravo</option>
+<option>Charlie</option>
+</select>",
+ html.ToHtmlString());
+ }
+
+ [Fact]
+ public void ListBoxForWithAttributesDictionaryOverridesName()
+ {
+ // DevDiv Bugs #217602:
+ // SelectInternal() should override the user-provided 'name' attribute
+
+ // Arrange
+ MultiSelectList selectList = new MultiSelectList(MultiSelectListTest.GetSampleStrings());
+ HtmlHelper<FooModel> helper = MvcHelper.GetHtmlHelper(new ViewDataDictionary<FooModel>());
+
+ // Act
+ MvcHtmlString html = helper.ListBoxFor(m => m.foo, selectList, new { myAttr = "myValue", name = "theName" });
+
+ // Assert
+ Assert.Equal(
+ @"<select id=""foo"" multiple=""multiple"" myAttr=""myValue"" name=""foo""><option>Alpha</option>
+<option>Bravo</option>
+<option>Charlie</option>
+</select>",
+ html.ToHtmlString());
+ }
+
+ [Fact]
+ public void ListBoxForWithNullSelectListUsesViewData()
+ {
+ // Arrange
+ HtmlHelper<FooModel> helper = MvcHelper.GetHtmlHelper(new ViewDataDictionary<FooModel>());
+ helper.ViewContext.ViewData["foo"] = new MultiSelectList(MultiSelectListTest.GetSampleStrings(), new[] { "Charlie" });
+
+ // Act
+ MvcHtmlString html = helper.ListBoxFor(m => m.foo, null);
+
+ // Assert
+ Assert.Equal(
+ @"<select id=""foo"" multiple=""multiple"" name=""foo""><option>Alpha</option>
+<option>Bravo</option>
+<option selected=""selected"">Charlie</option>
+</select>",
+ html.ToHtmlString());
+ }
+
+ [Fact]
+ public void ListBoxForWithObjectDictionary()
+ {
+ // Arrange
+ HtmlHelper<FooModel> helper = MvcHelper.GetHtmlHelper(new ViewDataDictionary<FooModel>());
+ MultiSelectList selectList = new MultiSelectList(MultiSelectListTest.GetSampleStrings());
+
+ // Act
+ MvcHtmlString html = helper.ListBoxFor(m => m.foo, selectList, HtmlHelperTest.AttributesObjectDictionary);
+
+ // Assert
+ Assert.Equal(
+ @"<select baz=""BazObjValue"" id=""foo"" multiple=""multiple"" name=""foo""><option>Alpha</option>
+<option>Bravo</option>
+<option>Charlie</option>
+</select>",
+ html.ToHtmlString());
+ }
+
+ [Fact]
+ public void ListBoxForWithObjectDictionaryWithUnderscores()
+ {
+ // Arrange
+ HtmlHelper<FooModel> helper = MvcHelper.GetHtmlHelper(new ViewDataDictionary<FooModel>());
+ MultiSelectList selectList = new MultiSelectList(MultiSelectListTest.GetSampleStrings());
+
+ // Act
+ MvcHtmlString html = helper.ListBoxFor(m => m.foo, selectList, HtmlHelperTest.AttributesObjectUnderscoresDictionary);
+
+ // Assert
+ Assert.Equal(
+ @"<select foo-baz=""BazObjValue"" id=""foo"" multiple=""multiple"" name=""foo""><option>Alpha</option>
+<option>Bravo</option>
+<option>Charlie</option>
+</select>",
+ html.ToHtmlString());
+ }
+
+ [Fact]
+ public void ListBoxForWithIEnumerableSelectListItem()
+ {
+ // Arrange
+ ViewDataDictionary<FooModel> vdd = new ViewDataDictionary<FooModel> { { "foo", MultiSelectListTest.GetSampleIEnumerableObjects() } };
+ HtmlHelper<FooModel> helper = MvcHelper.GetHtmlHelper(vdd);
+
+ // Act
+ MvcHtmlString html = helper.ListBoxFor(m => m.foo, null);
+
+ // Assert
+ Assert.Equal(
+ @"<select id=""foo"" multiple=""multiple"" name=""foo""><option value=""123456789"">John</option>
+<option value=""987654321"">Jane</option>
+<option selected=""selected"" value=""111111111"">Joe</option>
+</select>",
+ html.ToHtmlString());
+ }
+
+ [Fact]
+ public void ListBoxForThrowsWhenExpressionDoesNotEvaluateToIEnumerable()
+ {
+ // Arrange
+ ViewDataDictionary<NonIEnumerableModel> vdd = new ViewDataDictionary<NonIEnumerableModel> { { "foo", 123456789 } };
+ HtmlHelper<NonIEnumerableModel> helper = MvcHelper.GetHtmlHelper(vdd);
+
+ // Act & Assert
+ Assert.Throws<InvalidOperationException>(
+ () => helper.ListBoxFor(m => m.foo, MultiSelectListTest.GetSampleIEnumerableObjects()),
+ @"The parameter 'expression' must evaluate to an IEnumerable when multiple selection is allowed."
+ );
+ }
+
+ [Fact]
+ public void ListBoxForThrowsWhenExpressionEvaluatesToString()
+ {
+ // Arrange
+ ViewDataDictionary<FooModel> vdd = new ViewDataDictionary<FooModel> { { "foo", "123456789" } };
+ HtmlHelper<FooModel> helper = MvcHelper.GetHtmlHelper(vdd);
+
+ // Act & Assert
+ Assert.Throws<InvalidOperationException>(
+ () => helper.ListBoxFor(m => m.foo, MultiSelectListTest.GetSampleIEnumerableObjects()),
+ @"The parameter 'expression' must evaluate to an IEnumerable when multiple selection is allowed."
+ );
+ }
+
+ [Fact]
+ public void ListBoxForWithListOfSelectListItemSelectsDefaultFromViewData()
+ {
+ // Arrange
+ ViewDataDictionary<FooModel> vdd = new ViewDataDictionary<FooModel> { { "foo", new string[] { "123456789", "111111111" } } };
+ HtmlHelper<FooModel> helper = MvcHelper.GetHtmlHelper(vdd);
+
+ // Act
+ MvcHtmlString html = helper.ListBoxFor(m => m.foo, MultiSelectListTest.GetSampleListObjects());
+
+ // Assert
+ Assert.Equal(
+ @"<select id=""foo"" multiple=""multiple"" name=""foo""><option selected=""selected"" value=""123456789"">John</option>
+<option value=""987654321"">Jane</option>
+<option selected=""selected"" value=""111111111"">Joe</option>
+</select>",
+ html.ToHtmlString());
+ }
+
+ [Fact]
+ public void ListBoxForWithListOfSelectListItem()
+ {
+ // Arrange
+ ViewDataDictionary<FooModel> vdd = new ViewDataDictionary<FooModel> { { "foo", MultiSelectListTest.GetSampleListObjects() } };
+ HtmlHelper<FooModel> helper = MvcHelper.GetHtmlHelper(vdd);
+
+ // Act
+ MvcHtmlString html = helper.ListBoxFor(m => m.foo, null);
+
+ // Assert
+ Assert.Equal(
+ @"<select id=""foo"" multiple=""multiple"" name=""foo""><option value=""123456789"">John</option>
+<option value=""987654321"">Jane</option>
+<option selected=""selected"" value=""111111111"">Joe</option>
+</select>",
+ html.ToHtmlString());
+ }
+
+ [Fact]
+ public void ListBoxForWithPrefix()
+ {
+ // Arrange
+ HtmlHelper<FooModel> helper = MvcHelper.GetHtmlHelper(new ViewDataDictionary<FooModel>());
+ MultiSelectList selectList = new MultiSelectList(MultiSelectListTest.GetSampleStrings());
+ helper.ViewContext.ViewData.TemplateInfo.HtmlFieldPrefix = "MyPrefix";
+
+ // Act
+ MvcHtmlString html = helper.ListBoxFor(m => m.foo, selectList, HtmlHelperTest.AttributesObjectDictionary);
+
+ // Assert
+ Assert.Equal(
+ @"<select baz=""BazObjValue"" id=""MyPrefix_foo"" multiple=""multiple"" name=""MyPrefix.foo""><option>Alpha</option>
+<option>Bravo</option>
+<option>Charlie</option>
+</select>",
+ html.ToHtmlString());
+ }
+
+ [Fact]
+ public void ListBoxForWithPrefixAndListOfSelectListItemSelectsDefaultFromViewData()
+ {
+ // Arrange
+ ViewDataDictionary<FooModel> vdd = new ViewDataDictionary<FooModel> { { "foo", new string[] { "123456789", "111111111" } } };
+ HtmlHelper<FooModel> helper = MvcHelper.GetHtmlHelper(vdd);
+ helper.ViewContext.ViewData.TemplateInfo.HtmlFieldPrefix = "MyPrefix";
+
+ // Act
+ MvcHtmlString html = helper.ListBoxFor(m => m.foo, MultiSelectListTest.GetSampleListObjects());
+
+ // Assert
+ Assert.Equal(
+ @"<select id=""MyPrefix_foo"" multiple=""multiple"" name=""MyPrefix.foo""><option selected=""selected"" value=""123456789"">John</option>
+<option value=""987654321"">Jane</option>
+<option selected=""selected"" value=""111111111"">Joe</option>
+</select>",
+ html.ToHtmlString());
+ }
+
+ // Culture tests
+
+ [Fact]
+ public void SelectHelpersUseCurrentCultureToConvertValues()
+ {
+ // Arrange
+ HtmlHelper defaultValueHelper = MvcHelper.GetHtmlHelper(new ViewDataDictionary
+ {
+ { "foo", new[] { new DateTime(1900, 1, 1, 0, 0, 1) } },
+ { "bar", new DateTime(1900, 1, 1, 0, 0, 1) }
+ });
+ HtmlHelper helper = MvcHelper.GetHtmlHelper();
+ SelectList selectList = new SelectList(GetSampleCultureAnonymousObjects(), "Date", "FullWord", new DateTime(1900, 1, 1, 0, 0, 0));
+
+ var tests = new[]
+ {
+ // DropDownList(name, selectList, optionLabel)
+ new
+ {
+ Html = @"<select id=""foo"" name=""foo""><option selected=""selected"" value=""1900/01/01 12:00:00 AM"">Alpha</option>
+<option value=""1900/01/01 12:00:01 AM"">Bravo</option>
+<option value=""1900/01/01 12:00:02 AM"">Charlie</option>
+</select>",
+ Action = new Func<MvcHtmlString>(() => helper.DropDownList("foo", selectList, (string)null))
+ },
+ // DropDownList(name, selectList, optionLabel) (With default value selected from ViewData)
+ new
+ {
+ Html = @"<select id=""bar"" name=""bar""><option value=""1900/01/01 12:00:00 AM"">Alpha</option>
+<option selected=""selected"" value=""1900/01/01 12:00:01 AM"">Bravo</option>
+<option value=""1900/01/01 12:00:02 AM"">Charlie</option>
+</select>",
+ Action = new Func<MvcHtmlString>(() => defaultValueHelper.DropDownList("bar", selectList, (string)null))
+ },
+ // ListBox(name, selectList)
+ new
+ {
+ Html = @"<select id=""foo"" multiple=""multiple"" name=""foo""><option selected=""selected"" value=""1900/01/01 12:00:00 AM"">Alpha</option>
+<option value=""1900/01/01 12:00:01 AM"">Bravo</option>
+<option value=""1900/01/01 12:00:02 AM"">Charlie</option>
+</select>",
+ Action = new Func<MvcHtmlString>(() => helper.ListBox("foo", selectList))
+ },
+ // ListBox(name, selectList) (With default value selected from ViewData)
+ new
+ {
+ Html = @"<select id=""foo"" multiple=""multiple"" name=""foo""><option value=""1900/01/01 12:00:00 AM"">Alpha</option>
+<option selected=""selected"" value=""1900/01/01 12:00:01 AM"">Bravo</option>
+<option value=""1900/01/01 12:00:02 AM"">Charlie</option>
+</select>",
+ Action = new Func<MvcHtmlString>(() => defaultValueHelper.ListBox("foo", selectList))
+ }
+ };
+
+ // Act && Assert
+ using (HtmlHelperTest.ReplaceCulture("en-ZA", "en-US"))
+ {
+ foreach (var test in tests)
+ {
+ Assert.Equal(test.Html, test.Action().ToHtmlString());
+ }
+ }
+ }
+
+ // Helpers
+
+ private class FooModel
+ {
+ public string foo { get; set; }
+ }
+
+ private class FooBarModel : FooModel
+ {
+ public string bar { get; set; }
+ }
+
+ private class NonIEnumerableModel
+ {
+ public int foo { get; set; }
+ }
+
+ private static ViewDataDictionary<FooBarModel> GetViewDataWithErrors()
+ {
+ ViewDataDictionary<FooBarModel> viewData = new ViewDataDictionary<FooBarModel> { { "foo", "ViewDataFoo" } };
+ viewData.Model = new FooBarModel { foo = "ViewItemFoo", bar = "ViewItemBar" };
+
+ ModelState modelStateFoo = new ModelState();
+ modelStateFoo.Errors.Add(new ModelError("foo error 1"));
+ modelStateFoo.Errors.Add(new ModelError("foo error 2"));
+ viewData.ModelState["foo"] = modelStateFoo;
+ modelStateFoo.Value = new ValueProviderResult(new string[] { "Bravo", "Charlie" }, "Bravo", CultureInfo.InvariantCulture);
+
+ return viewData;
+ }
+
+ internal static IEnumerable GetSampleCultureAnonymousObjects()
+ {
+ return new[]
+ {
+ new { Date = new DateTime(1900, 1, 1, 0, 0, 0), FullWord = "Alpha" },
+ new { Date = new DateTime(1900, 1, 1, 0, 0, 1), FullWord = "Bravo" },
+ new { Date = new DateTime(1900, 1, 1, 0, 0, 2), FullWord = "Charlie" }
+ };
+ }
+
+ private class RequiredModel
+ {
+ [Required]
+ public string foo { get; set; }
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Html/Test/TemplateHelpersTest.cs b/test/System.Web.Mvc.Test/Html/Test/TemplateHelpersTest.cs
new file mode 100644
index 00000000..e0a795d9
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Html/Test/TemplateHelpersTest.cs
@@ -0,0 +1,1158 @@
+using System.Collections;
+using System.Collections.Generic;
+using System.Globalization;
+using System.IO;
+using System.Linq;
+using System.Net;
+using System.Threading;
+using System.Web.Routing;
+using System.Web.UI.WebControls;
+using Microsoft.Web.UnitTestUtil;
+using Moq;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Mvc.Html.Test
+{
+ public class TemplateHelpersTest
+ {
+ // ExecuteTemplate
+
+ private class ExecuteTemplateModel
+ {
+ public string MyProperty { get; set; }
+ public Nullable<int> MyNullableProperty { get; set; }
+ }
+
+ [Fact]
+ public void ExecuteTemplateCallsGetViewNamesWithProvidedTemplateNameAndMetadataInformation()
+ {
+ using (new MockViewEngine())
+ {
+ // Arrange
+ HtmlHelper html = MakeHtmlHelper(new ExecuteTemplateModel());
+ ModelMetadata metadata = ModelMetadata.FromStringExpression("MyProperty", html.ViewData);
+ metadata.TemplateHint = "templateHint";
+ metadata.DataTypeName = "dataType";
+ string[] hints = null;
+
+ // Act
+ TemplateHelpers.ExecuteTemplate(
+ html, MakeViewData(html, metadata), "templateName", DataBoundControlMode.ReadOnly,
+ (_metadata, _hints) =>
+ {
+ hints = _hints;
+ return new[] { "String" };
+ },
+ TemplateHelpers.GetDefaultActions);
+
+ // Assert
+ Assert.NotNull(hints);
+ Assert.Equal(3, hints.Length);
+ Assert.Equal("templateName", hints[0]);
+ Assert.Equal("templateHint", hints[1]);
+ Assert.Equal("dataType", hints[2]);
+ }
+ }
+
+ [Fact]
+ public void ExecuteTemplateUsesViewFromViewEngineInReadOnlyMode()
+ {
+ using (MockViewEngine engine = new MockViewEngine())
+ {
+ // Arrange
+ HtmlHelper html = MakeHtmlHelper(new ExecuteTemplateModel { MyProperty = "Hello" });
+ ModelMetadata metadata = ModelMetadata.FromStringExpression("MyProperty", html.ViewData);
+ ViewContext callbackViewContext = null;
+ engine.Engine.Setup(e => e.FindPartialView(html.ViewContext, "DisplayTemplates/String", true))
+ .Returns(new ViewEngineResult(engine.View.Object, engine.Engine.Object))
+ .Verifiable();
+ engine.View.Setup(v => v.Render(It.IsAny<ViewContext>(), It.IsAny<TextWriter>()))
+ .Callback<ViewContext, TextWriter>((vc, tw) =>
+ {
+ callbackViewContext = vc;
+ tw.Write("View Text");
+ })
+ .Verifiable();
+ ViewDataDictionary viewData = MakeViewData(html, metadata);
+
+ // Act
+ string result = TemplateHelpers.ExecuteTemplate(
+ html, viewData, "templateName", DataBoundControlMode.ReadOnly,
+ delegate { return new[] { "String" }; },
+ TemplateHelpers.GetDefaultActions);
+
+ // Assert
+ engine.Engine.Verify();
+ engine.View.Verify();
+ Assert.Equal("View Text", result);
+ Assert.Same(engine.View.Object, callbackViewContext.View);
+ Assert.Same(viewData, callbackViewContext.ViewData);
+ Assert.Same(html.ViewContext.TempData, callbackViewContext.TempData);
+ TemplateHelpers.ActionCacheViewItem cacheItem = TemplateHelpers.GetActionCache(html)["DisplayTemplates/String"] as TemplateHelpers.ActionCacheViewItem;
+ Assert.NotNull(cacheItem);
+ Assert.Equal("DisplayTemplates/String", cacheItem.ViewName);
+ }
+ }
+
+ [Fact]
+ public void ExecuteTemplateUsesViewFromViewEngineInEditMode()
+ {
+ using (MockViewEngine engine = new MockViewEngine())
+ {
+ // Arrange
+ HtmlHelper html = MakeHtmlHelper(new ExecuteTemplateModel { MyProperty = "Hello" });
+ ModelMetadata metadata = ModelMetadata.FromStringExpression("MyProperty", html.ViewData);
+ ViewContext callbackViewContext = null;
+ engine.Engine.Setup(e => e.FindPartialView(html.ViewContext, "EditorTemplates/String", true))
+ .Returns(new ViewEngineResult(engine.View.Object, engine.Engine.Object))
+ .Verifiable();
+ engine.View.Setup(v => v.Render(It.IsAny<ViewContext>(), It.IsAny<TextWriter>()))
+ .Callback<ViewContext, TextWriter>((vc, tw) =>
+ {
+ callbackViewContext = vc;
+ tw.Write("View Text");
+ })
+ .Verifiable();
+ ViewDataDictionary viewData = MakeViewData(html, metadata);
+
+ // Act
+ string result = TemplateHelpers.ExecuteTemplate(
+ html, viewData, "templateName", DataBoundControlMode.Edit,
+ delegate { return new[] { "String" }; },
+ TemplateHelpers.GetDefaultActions);
+
+ // Assert
+ engine.Engine.Verify();
+ engine.View.Verify();
+ Assert.Equal("View Text", result);
+ Assert.Same(engine.View.Object, callbackViewContext.View);
+ Assert.Same(viewData, callbackViewContext.ViewData);
+ Assert.Same(html.ViewContext.TempData, callbackViewContext.TempData);
+ TemplateHelpers.ActionCacheViewItem cacheItem = TemplateHelpers.GetActionCache(html)["EditorTemplates/String"] as TemplateHelpers.ActionCacheViewItem;
+ Assert.NotNull(cacheItem);
+ Assert.Equal("EditorTemplates/String", cacheItem.ViewName);
+ }
+ }
+
+ [Fact]
+ public void ExecuteTemplateUsesViewFromDefaultActionsInReadOnlyMode()
+ {
+ using (MockViewEngine engine = new MockViewEngine())
+ {
+ // Arrange
+ HtmlHelper html = MakeHtmlHelper(new ExecuteTemplateModel { MyProperty = "Hello" });
+ ModelMetadata metadata = ModelMetadata.FromStringExpression("MyProperty", html.ViewData);
+ engine.Engine.Setup(e => e.FindPartialView(html.ViewContext, "DisplayTemplates/String", It.IsAny<bool>()))
+ .Returns(new ViewEngineResult(new string[0]))
+ .Verifiable();
+ ViewDataDictionary viewData = MakeViewData(html, metadata);
+
+ // Act
+ TemplateHelpers.ExecuteTemplate(
+ html, viewData, "templateName", DataBoundControlMode.ReadOnly,
+ delegate { return new[] { "String" }; },
+ TemplateHelpers.GetDefaultActions);
+
+ // Assert
+ engine.Engine.Verify();
+ TemplateHelpers.ActionCacheCodeItem cacheItem = TemplateHelpers.GetActionCache(html)["DisplayTemplates/String"] as TemplateHelpers.ActionCacheCodeItem;
+ Assert.NotNull(cacheItem);
+ Assert.Equal(DefaultDisplayTemplates.StringTemplate, cacheItem.Action);
+ }
+ }
+
+ [Fact]
+ public void ExecuteTemplateUsesViewFromDefaultActionsInEditMode()
+ {
+ using (MockViewEngine engine = new MockViewEngine())
+ {
+ // Arrange
+ HtmlHelper html = MakeHtmlHelper(new ExecuteTemplateModel { MyProperty = "Hello" });
+ ModelMetadata metadata = ModelMetadata.FromStringExpression("MyProperty", html.ViewData);
+ engine.Engine.Setup(e => e.FindPartialView(html.ViewContext, "EditorTemplates/String", It.IsAny<bool>()))
+ .Returns(new ViewEngineResult(new string[0]))
+ .Verifiable();
+ ViewDataDictionary viewData = MakeViewData(html, metadata);
+
+ // Act
+ TemplateHelpers.ExecuteTemplate(
+ html, viewData, "templateName", DataBoundControlMode.Edit,
+ delegate { return new[] { "String" }; },
+ TemplateHelpers.GetDefaultActions);
+
+ // Assert
+ engine.Engine.Verify();
+ TemplateHelpers.ActionCacheCodeItem cacheItem = TemplateHelpers.GetActionCache(html)["EditorTemplates/String"] as TemplateHelpers.ActionCacheCodeItem;
+ Assert.NotNull(cacheItem);
+ Assert.Equal(DefaultEditorTemplates.StringTemplate, cacheItem.Action);
+ }
+ }
+
+ [Fact]
+ public void ExecuteTemplatePrefersExistingActionCacheItem()
+ {
+ using (MockViewEngine engine = new MockViewEngine())
+ {
+ // Arrange
+ HtmlHelper html = MakeHtmlHelper(new ExecuteTemplateModel { MyProperty = "Hello" });
+ ModelMetadata metadata = ModelMetadata.FromStringExpression("MyProperty", html.ViewData);
+ ViewDataDictionary viewData = MakeViewData(html, metadata);
+ TemplateHelpers.GetActionCache(html).Add("EditorTemplates/String",
+ new TemplateHelpers.ActionCacheCodeItem { Action = _ => "Action Text" });
+
+ // Act
+ string result = TemplateHelpers.ExecuteTemplate(
+ html, viewData, "templateName", DataBoundControlMode.Edit,
+ delegate { return new[] { "String" }; },
+ TemplateHelpers.GetDefaultActions);
+
+ // Assert
+ engine.Engine.Verify();
+ engine.Engine.Verify(e => e.FindPartialView(It.IsAny<ControllerContext>(), It.IsAny<string>(), It.IsAny<bool>()), Times.Never());
+ Assert.Equal("Action Text", result);
+ }
+ }
+
+ [Fact]
+ public void ExecuteTemplateThrowsWhenNoTemplatesMatch()
+ {
+ using (new MockViewEngine())
+ {
+ // Arrange
+ HtmlHelper html = MakeHtmlHelper(new ExecuteTemplateModel { MyProperty = "Hello" });
+ ModelMetadata metadata = ModelMetadata.FromStringExpression("MyProperty", html.ViewData);
+ ViewDataDictionary viewData = MakeViewData(html, metadata);
+
+ // Act & Assert
+ Assert.Throws<InvalidOperationException>(
+ () => TemplateHelpers.ExecuteTemplate(html, viewData, "templateName", DataBoundControlMode.Edit, delegate { return new string[0]; }, TemplateHelpers.GetDefaultActions),
+ "Unable to locate an appropriate template for type System.String.");
+ }
+ }
+
+ [Fact]
+ public void ExecuteTemplateCreatesNewHtmlHelperWithCorrectViewDataForDefaultAction()
+ {
+ using (MockViewEngine engine = new MockViewEngine(false))
+ {
+ // Arrange
+ HtmlHelper html = MakeHtmlHelper(new ExecuteTemplateModel { MyProperty = "Hello" });
+ ModelMetadata metadata = ModelMetadata.FromStringExpression("MyProperty", html.ViewData);
+ ViewDataDictionary viewData = MakeViewData(html, metadata);
+ HtmlHelper passedHtmlHelper = null;
+
+ // Act
+ TemplateHelpers.ExecuteTemplate(
+ html, viewData, "templateName", DataBoundControlMode.Edit,
+ delegate { return new[] { "String" }; },
+ delegate
+ {
+ return new Dictionary<string, Func<HtmlHelper, string>>
+ {
+ {
+ "String", _htmlHelper =>
+ {
+ passedHtmlHelper = _htmlHelper;
+ return "content";
+ }
+ }
+ };
+ });
+
+ // Assert
+ Assert.NotNull(passedHtmlHelper);
+ Assert.Same(passedHtmlHelper.ViewData, passedHtmlHelper.ViewContext.ViewData);
+ Assert.NotSame(html.ViewData, passedHtmlHelper.ViewData);
+ }
+ }
+
+ [Fact]
+ public void ExecuteTemplateCreatesNewHtmlHelperWithCorrectViewDataForCachedAction()
+ {
+ using (MockViewEngine engine = new MockViewEngine())
+ {
+ // Arrange
+ HtmlHelper html = MakeHtmlHelper(new ExecuteTemplateModel { MyProperty = "Hello" });
+ ModelMetadata metadata = ModelMetadata.FromStringExpression("MyProperty", html.ViewData);
+ ViewDataDictionary viewData = MakeViewData(html, metadata);
+ HtmlHelper passedHtmlHelper = null;
+ TemplateHelpers.GetActionCache(html).Add(
+ "EditorTemplates/String",
+ new TemplateHelpers.ActionCacheCodeItem
+ {
+ Action = _htmlHelper =>
+ {
+ passedHtmlHelper = _htmlHelper;
+ return "content";
+ }
+ });
+
+ // Act
+ string result = TemplateHelpers.ExecuteTemplate(
+ html, viewData, "templateName", DataBoundControlMode.Edit,
+ delegate { return new[] { "String" }; },
+ TemplateHelpers.GetDefaultActions);
+
+ // Assert
+ Assert.NotNull(passedHtmlHelper);
+ Assert.Same(passedHtmlHelper.ViewData, passedHtmlHelper.ViewContext.ViewData);
+ Assert.NotSame(html.ViewData, passedHtmlHelper.ViewData);
+ }
+ }
+
+ // GetActionCache
+
+ [Fact]
+ public void CacheIsCreatedIfNotPresent()
+ {
+ // Arrange
+ Hashtable items = new Hashtable();
+ Mock<HttpContextBase> context = new Mock<HttpContextBase>();
+ context.Setup(c => c.Items).Returns(items);
+ Mock<ViewContext> viewContext = new Mock<ViewContext>();
+ viewContext.Setup(c => c.HttpContext).Returns(context.Object);
+ Mock<IViewDataContainer> viewDataContainer = new Mock<IViewDataContainer>();
+ HtmlHelper helper = new HtmlHelper(viewContext.Object, viewDataContainer.Object);
+
+ // Act
+ Dictionary<string, TemplateHelpers.ActionCacheItem> cache = TemplateHelpers.GetActionCache(helper);
+
+ // Assert
+ Assert.NotNull(cache);
+ Assert.Empty(cache);
+ Assert.Contains((object)cache, items.Values.OfType<object>());
+ }
+
+ [Fact]
+ public void CacheIsReusedIfPresent()
+ {
+ // Arrange
+ Hashtable items = new Hashtable();
+ Mock<HttpContextBase> context = new Mock<HttpContextBase>();
+ context.Setup(c => c.Items).Returns(items);
+ Mock<ViewContext> viewContext = new Mock<ViewContext>();
+ viewContext.Setup(c => c.HttpContext).Returns(context.Object);
+ Mock<IViewDataContainer> viewDataContainer = new Mock<IViewDataContainer>();
+ HtmlHelper helper = new HtmlHelper(viewContext.Object, viewDataContainer.Object);
+
+ // Act
+ Dictionary<string, TemplateHelpers.ActionCacheItem> cache1 = TemplateHelpers.GetActionCache(helper);
+ Dictionary<string, TemplateHelpers.ActionCacheItem> cache2 = TemplateHelpers.GetActionCache(helper);
+
+ // Assert
+ Assert.NotNull(cache1);
+ Assert.Same(cache1, cache2);
+ }
+
+ // GetViewNames
+
+ [Fact]
+ public void GetViewNamesFullOrderingOfBuiltInValueType()
+ {
+ // Arrange
+ ModelMetadata metadata = ModelMetadataProviders.Current.GetMetadataForType(null, typeof(double));
+
+ // Act
+ List<string> result = TemplateHelpers.GetViewNames(metadata, "UIHint", "DataType").ToList();
+
+ // Assert
+ Assert.Equal(4, result.Count);
+ Assert.Equal("UIHint", result[0]);
+ Assert.Equal("DataType", result[1]);
+ Assert.Equal("Double", result[2]);
+ Assert.Equal("String", result[3]);
+ }
+
+ [Fact]
+ public void GetViewNamesFullOrderingOfComplexType()
+ {
+ // Arrange
+ ModelMetadata metadata = ModelMetadataProviders.Current.GetMetadataForType(null, typeof(HttpWebRequest));
+
+ // Act
+ List<string> result = TemplateHelpers.GetViewNames(metadata, "UIHint", "DataType").ToList();
+
+ // Assert
+ Assert.Equal(6, result.Count);
+ Assert.Equal("UIHint", result[0]);
+ Assert.Equal("DataType", result[1]);
+ Assert.Equal("HttpWebRequest", result[2]);
+ Assert.Equal("WebRequest", result[3]);
+ Assert.Equal("MarshalByRefObject", result[4]);
+ Assert.Equal("Object", result[5]);
+ }
+
+ [Fact]
+ public void GetViewNamesFullOrderingOfInterface()
+ {
+ // Arrange
+ ModelMetadata metadata = ModelMetadataProviders.Current.GetMetadataForType(null, typeof(IDisposable));
+
+ // Act
+ List<string> result = TemplateHelpers.GetViewNames(metadata, "UIHint", "DataType").ToList();
+
+ // Assert
+ Assert.Equal(4, result.Count);
+ Assert.Equal("UIHint", result[0]);
+ Assert.Equal("DataType", result[1]);
+ Assert.Equal("IDisposable", result[2]);
+ Assert.Equal("Object", result[3]);
+ }
+
+ [Fact]
+ public void GetViewNamesFullOrderingOfComplexTypeThatImplementsIEnumerable()
+ {
+ // Arrange
+ ModelMetadata metadata = ModelMetadataProviders.Current.GetMetadataForType(null, typeof(List<int>));
+
+ // Act
+ List<string> result = TemplateHelpers.GetViewNames(metadata, "UIHint", "DataType").ToList();
+
+ // Assert
+ Assert.Equal(5, result.Count);
+ Assert.Equal("UIHint", result[0]);
+ Assert.Equal("DataType", result[1]);
+ Assert.Equal("List`1", result[2]);
+ Assert.Equal("Collection", result[3]);
+ Assert.Equal("Object", result[4]);
+ }
+
+ [Fact]
+ public void GetViewNamesFullOrderingOfInterfaceThatRequiresIEnumerable()
+ {
+ // Arrange
+ ModelMetadata metadata = ModelMetadataProviders.Current.GetMetadataForType(null, typeof(IList<int>));
+
+ // Act
+ List<string> result = TemplateHelpers.GetViewNames(metadata, "UIHint", "DataType").ToList();
+
+ // Assert
+ Assert.Equal(5, result.Count);
+ Assert.Equal("UIHint", result[0]);
+ Assert.Equal("DataType", result[1]);
+ Assert.Equal("IList`1", result[2]);
+ Assert.Equal("Collection", result[3]);
+ Assert.Equal("Object", result[4]);
+ }
+
+ [Fact]
+ public void GetViewNamesNullUIHintNotIncludedInList()
+ {
+ // Arrange
+ ModelMetadata metadata = ModelMetadataProviders.Current.GetMetadataForType(null, typeof(Object));
+
+ // Act
+ List<string> result = TemplateHelpers.GetViewNames(metadata, null, "DataType").ToList();
+
+ // Assert
+ Assert.Equal(2, result.Count);
+ Assert.Equal("DataType", result[0]);
+ Assert.Equal("Object", result[1]);
+ }
+
+ [Fact]
+ public void GetViewNamesNullDataTypeNotIncludedInList()
+ {
+ // Arrange
+ ModelMetadata metadata = ModelMetadataProviders.Current.GetMetadataForType(null, typeof(Object));
+
+ // Act
+ List<string> result = TemplateHelpers.GetViewNames(metadata, "UIHint", null).ToList();
+
+ // Assert
+ Assert.Equal(2, result.Count);
+ Assert.Equal("UIHint", result[0]);
+ Assert.Equal("Object", result[1]);
+ }
+
+ [Fact]
+ public void GetViewNamesConvertsNullableOfTIntoT()
+ {
+ // Arrange
+ ModelMetadata metadata = ModelMetadataProviders.Current.GetMetadataForType(null, typeof(Nullable<int>));
+
+ // Act
+ List<string> result = TemplateHelpers.GetViewNames(metadata, null, null).ToList();
+
+ // Assert
+ Assert.Equal(2, result.Count);
+ Assert.Equal("Int32", result[0]);
+ Assert.Equal("String", result[1]);
+ }
+
+ // Template
+
+ private class TemplateModel
+ {
+ public object MyProperty { get; set; }
+ }
+
+ [Fact]
+ public void TemplateNullExpressionThrows()
+ {
+ // Arrange
+ HtmlHelper<object> html = MakeHtmlHelper<object>(null);
+
+ // Act & Assert
+ Assert.ThrowsArgumentNull(
+ () => TemplateHelpers.Template(html, null, "templateName", "htmlFieldName", DataBoundControlMode.ReadOnly,
+ null /* additionalViewData */, TemplateHelperSpy),
+ "expression");
+ }
+
+ [Fact]
+ public void TemplateDataNotFound()
+ {
+ // Arrange
+ HtmlHelper<object> html = MakeHtmlHelper<object>(null);
+
+ // Act
+ string result = TemplateHelpers.Template(html, "UnknownObject", "templateName", null, DataBoundControlMode.ReadOnly,
+ null /* additionalViewData */, TemplateHelperSpy);
+
+ // Assert
+ Assert.Equal("Model = (null), ModelType = System.String, RealModelType = System.String, PropertyName = (null), HtmlFieldName = UnknownObject, TemplateName = templateName, Mode = ReadOnly, AdditionalViewData = (null)", result);
+ }
+
+ [Fact]
+ public void TemplateHtmlFieldNameReplacesExpression()
+ {
+ // Arrange
+ HtmlHelper<object> html = MakeHtmlHelper<object>(null);
+
+ // Act
+ string result = TemplateHelpers.Template(html, "UnknownObject", "templateName", "htmlFieldName", DataBoundControlMode.ReadOnly,
+ new { foo = "Bar" }, TemplateHelperSpy);
+
+ // Assert
+ Assert.Equal("Model = (null), ModelType = System.String, RealModelType = System.String, PropertyName = (null), HtmlFieldName = htmlFieldName, TemplateName = templateName, Mode = ReadOnly, AdditionalViewData = { foo: Bar }", result);
+ }
+
+ [Fact]
+ public void TemplateDataFoundInViewDataDictionaryHasNoPropertyName()
+ {
+ // Arrange
+ HtmlHelper<object> html = MakeHtmlHelper<object>(null);
+ html.ViewContext.ViewData["Key"] = 42;
+
+ // Act
+ string result = TemplateHelpers.Template(html, "Key", null, null, DataBoundControlMode.Edit, null /* additionalViewData */, TemplateHelperSpy);
+
+ // Assert
+ Assert.Equal("Model = 42, ModelType = System.Int32, RealModelType = System.Int32, PropertyName = (null), HtmlFieldName = Key, TemplateName = (null), Mode = Edit, AdditionalViewData = (null)", result);
+ }
+
+ [Fact]
+ public void TemplateDataFoundInViewDataDictionarySubPropertyHasPropertyName()
+ {
+ // Arrange
+ HtmlHelper<object> html = MakeHtmlHelper<object>(null);
+ html.ViewContext.ViewData["Key"] = new TemplateModel { MyProperty = "Hello!" };
+
+ // Act
+ string result = TemplateHelpers.Template(html, "Key.MyProperty", null, null, DataBoundControlMode.ReadOnly,
+ null /* additionalViewData */, TemplateHelperSpy);
+
+ // Assert
+ Assert.Equal("Model = Hello!, ModelType = System.Object, RealModelType = System.String, PropertyName = MyProperty, HtmlFieldName = Key.MyProperty, TemplateName = (null), Mode = ReadOnly, AdditionalViewData = (null)", result);
+ }
+
+ [Fact]
+ public void TemplateDataFoundInModelHasPropertyName()
+ {
+ // Arrange
+ HtmlHelper<TemplateModel> html = MakeHtmlHelper<TemplateModel>(new TemplateModel { MyProperty = "Hello!" });
+
+ // Act
+ string result = TemplateHelpers.Template(html, "MyProperty", null, null, DataBoundControlMode.ReadOnly,
+ null /* additionalViewData */, TemplateHelperSpy);
+
+ // Assert
+ Assert.Equal("Model = Hello!, ModelType = System.Object, RealModelType = System.String, PropertyName = MyProperty, HtmlFieldName = MyProperty, TemplateName = (null), Mode = ReadOnly, AdditionalViewData = (null)", result);
+ }
+
+ [Fact]
+ public void TemplateNullDataFoundInModelHasPropertyTypeInsteadOfActualModelType()
+ {
+ // Arrange
+ HtmlHelper<TemplateModel> html = MakeHtmlHelper<TemplateModel>(new TemplateModel());
+
+ // Act
+ string result = TemplateHelpers.Template(html, "MyProperty", null, null, DataBoundControlMode.ReadOnly,
+ null /* additionalViewData */, TemplateHelperSpy);
+
+ // Assert
+ Assert.Equal("Model = (null), ModelType = System.Object, RealModelType = System.Object, PropertyName = MyProperty, HtmlFieldName = MyProperty, TemplateName = (null), Mode = ReadOnly, AdditionalViewData = (null)", result);
+ }
+
+ // TemplateFor
+
+ private class TemplateForModel
+ {
+ public int MyField = 0;
+ public object MyProperty { get; set; }
+ public Nullable<int> MyNullableProperty { get; set; }
+ }
+
+ [Fact]
+ public void TemplateForNonUnsupportedExpressionTypeThrows()
+ {
+ // Arrange
+ HtmlHelper<TemplateForModel> html = MakeHtmlHelper<TemplateForModel>(null);
+
+ // Act & Assert
+ Assert.Throws<InvalidOperationException>(
+ () => TemplateHelpers.TemplateFor(html, model => new Object(), "templateName", "htmlFieldName", DataBoundControlMode.ReadOnly,
+ null /* additionalViewData */, TemplateHelperSpy),
+ "Templates can be used only with field access, property access, single-dimension array index, or single-parameter custom indexer expressions.");
+ }
+
+ [Fact]
+ public void TemplateForWithNonNullExpression()
+ {
+ // Arrange
+ HtmlHelper<TemplateForModel> html = MakeHtmlHelper(new TemplateForModel { MyProperty = "Hello!" });
+
+ // Act
+ string result = TemplateHelpers.TemplateFor(html, model => model.MyProperty, "templateName", null, DataBoundControlMode.ReadOnly,
+ new { foo = "Bar" }, TemplateHelperSpy);
+
+ // Assert
+ Assert.Equal("Model = Hello!, ModelType = System.Object, RealModelType = System.String, PropertyName = MyProperty, HtmlFieldName = MyProperty, TemplateName = templateName, Mode = ReadOnly, AdditionalViewData = { foo: Bar }", result);
+ }
+
+ [Fact]
+ public void TemplateForHtmlFieldNameReplacesExpression()
+ {
+ // Arrange
+ HtmlHelper<TemplateForModel> html = MakeHtmlHelper(new TemplateForModel { MyProperty = "Hello!" });
+
+ // Act
+ string result = TemplateHelpers.TemplateFor(html, model => model.MyProperty, "templateName", "htmlFieldName",
+ DataBoundControlMode.ReadOnly, null /* additionalViewData */, TemplateHelperSpy);
+
+ // Assert
+ Assert.Equal("Model = Hello!, ModelType = System.Object, RealModelType = System.String, PropertyName = MyProperty, HtmlFieldName = htmlFieldName, TemplateName = templateName, Mode = ReadOnly, AdditionalViewData = (null)", result);
+ }
+
+ [Fact]
+ public void TemplateForNullModelStillRetainsTypeInformation()
+ {
+ // Arrange
+ HtmlHelper<TemplateForModel> html = MakeHtmlHelper<TemplateForModel>(null);
+
+ // Act
+ string result = TemplateHelpers.TemplateFor(html, model => model.MyProperty, null, null, DataBoundControlMode.ReadOnly,
+ null /* additionalViewData */, TemplateHelperSpy);
+
+ // Assert
+ Assert.Equal("Model = (null), ModelType = System.Object, RealModelType = System.Object, PropertyName = MyProperty, HtmlFieldName = MyProperty, TemplateName = (null), Mode = ReadOnly, AdditionalViewData = (null)", result);
+ }
+
+ [Fact]
+ public void TemplateForNullPropertyStillRetainsTypeInformation()
+ {
+ // Arrange
+ HtmlHelper<TemplateForModel> html = MakeHtmlHelper(new TemplateForModel());
+
+ // Act
+ string result = TemplateHelpers.TemplateFor(html, model => model.MyProperty, null, null, DataBoundControlMode.ReadOnly,
+ null /* additionalViewData */, TemplateHelperSpy);
+
+ // Assert
+ Assert.Equal("Model = (null), ModelType = System.Object, RealModelType = System.Object, PropertyName = MyProperty, HtmlFieldName = MyProperty, TemplateName = (null), Mode = ReadOnly, AdditionalViewData = (null)", result);
+ }
+
+ [Fact]
+ public void TemplateForNullableValueTYpePropertyRetainsNullableValueTYpeForNullPropertyValue()
+ {
+ // Arrange
+ HtmlHelper<TemplateForModel> html = MakeHtmlHelper(new TemplateForModel());
+
+ // Act
+ string result = TemplateHelpers.TemplateFor(html, model => model.MyNullableProperty, null, null, DataBoundControlMode.ReadOnly,
+ null /* additionalViewData */, TemplateHelperSpy);
+
+ // Assert
+ Assert.Equal("Model = (null), ModelType = System.Nullable`1[[System.Int32, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]], RealModelType = System.Nullable`1[[System.Int32, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]], PropertyName = MyNullableProperty, HtmlFieldName = MyNullableProperty, TemplateName = (null), Mode = ReadOnly, AdditionalViewData = (null)", result);
+ }
+
+ [Fact]
+ public void TemplateForNullableValueTypePropertyRetainsNullableValueTypeForNonNullPropertyValue()
+ {
+ // Arrange
+ HtmlHelper<TemplateForModel> html = MakeHtmlHelper(new TemplateForModel { MyNullableProperty = 42 });
+
+ // Act
+ string result = TemplateHelpers.TemplateFor(html, model => model.MyNullableProperty, null, null, DataBoundControlMode.ReadOnly,
+ null /* additionalViewData */, TemplateHelperSpy);
+
+ // Assert
+ Assert.Equal("Model = 42, ModelType = System.Nullable`1[[System.Int32, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]], RealModelType = System.Nullable`1[[System.Int32, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]], PropertyName = MyNullableProperty, HtmlFieldName = MyNullableProperty, TemplateName = (null), Mode = ReadOnly, AdditionalViewData = (null)", result);
+ }
+
+ [Fact]
+ public void TemplateForWithParameterExpression()
+ {
+ // Arrange
+ HtmlHelper<TemplateForModel> html = MakeHtmlHelper(new TemplateForModel());
+
+ // Act
+ string result = TemplateHelpers.TemplateFor(html, model => model, null, null, DataBoundControlMode.ReadOnly,
+ null /* additionalViewData */, TemplateHelperSpy);
+
+ // Assert
+ Assert.Equal("Model = System.Web.Mvc.Html.Test.TemplateHelpersTest+TemplateForModel, ModelType = System.Web.Mvc.Html.Test.TemplateHelpersTest+TemplateForModel, RealModelType = System.Web.Mvc.Html.Test.TemplateHelpersTest+TemplateForModel, PropertyName = (null), HtmlFieldName = (empty), TemplateName = (null), Mode = ReadOnly, AdditionalViewData = (null)", result);
+ }
+
+ [Fact]
+ public void TemplateForWithFieldExpression()
+ {
+ // Arrange
+ HtmlHelper<TemplateForModel> html = MakeHtmlHelper(new TemplateForModel { MyField = 42 });
+
+ // Act
+ string result = TemplateHelpers.TemplateFor(html, model => model.MyField, null, null, DataBoundControlMode.ReadOnly,
+ null /* additionalViewData */, TemplateHelperSpy);
+
+ // Assert
+ Assert.Equal("Model = 42, ModelType = System.Int32, RealModelType = System.Int32, PropertyName = (null), HtmlFieldName = MyField, TemplateName = (null), Mode = ReadOnly, AdditionalViewData = (null)", result);
+ }
+
+ // TemplateHelper
+
+ private class TemplateHelperModel
+ {
+ public string MyProperty { get; set; }
+ }
+
+ [Fact]
+ public void TemplateHelperNonNullNonEmptyStringModel()
+ {
+ // Arrange
+ HtmlHelper html = MakeHtmlHelper(new TemplateHelperModel { MyProperty = "Hello" });
+ ModelMetadata metadata = ModelMetadata.FromStringExpression("MyProperty", html.ViewData);
+
+ // Act
+ string result = TemplateHelpers.TemplateHelper(html, metadata, "htmlFieldName", "templateName", DataBoundControlMode.ReadOnly,
+ null /* additionalViewData */, ExecuteTemplateSpy);
+
+ // Assert
+ Assert.Equal("Model = Hello, ModelType = System.String, RealModelType = System.String, PropertyName = MyProperty, FormattedModelValue = Hello, HtmlFieldPrefix = FieldPrefix.htmlFieldName, TemplateName = templateName, Mode = ReadOnly", result);
+ }
+
+ [Fact]
+ public void TemplateHelperEmptyStringModel()
+ {
+ // Arrange
+ HtmlHelper html = MakeHtmlHelper(new TemplateHelperModel { MyProperty = "" });
+ ModelMetadata metadata = ModelMetadata.FromStringExpression("MyProperty", html.ViewData);
+ metadata.ConvertEmptyStringToNull = false;
+
+ // Act
+ string result = TemplateHelpers.TemplateHelper(html, metadata, "htmlFieldName", "templateName", DataBoundControlMode.ReadOnly,
+ null /* additionalViewData */, ExecuteTemplateSpy);
+
+ // Assert
+ Assert.Equal("Model = , ModelType = System.String, RealModelType = System.String, PropertyName = MyProperty, FormattedModelValue = , HtmlFieldPrefix = FieldPrefix.htmlFieldName, TemplateName = templateName, Mode = ReadOnly", result);
+ }
+
+ [Fact]
+ public void TemplateHelperConvertsEmptyStringsToNull()
+ {
+ // Arrange
+ HtmlHelper html = MakeHtmlHelper(new TemplateHelperModel { MyProperty = "" });
+ ModelMetadata metadata = ModelMetadata.FromStringExpression("MyProperty", html.ViewData);
+ metadata.ConvertEmptyStringToNull = true;
+
+ // Act
+ string result = TemplateHelpers.TemplateHelper(html, metadata, "htmlFieldName", "templateName", DataBoundControlMode.ReadOnly,
+ null /* additionalViewData */, ExecuteTemplateSpy);
+
+ // Assert
+ Assert.Equal("Model = (null), ModelType = System.String, RealModelType = System.String, PropertyName = MyProperty, FormattedModelValue = , HtmlFieldPrefix = FieldPrefix.htmlFieldName, TemplateName = templateName, Mode = ReadOnly", result);
+ }
+
+ [Fact]
+ public void TemplateHelperConvertsNullModelsToNullDisplayTextInReadOnlyMode()
+ {
+ // Arrange
+ HtmlHelper html = MakeHtmlHelper(new TemplateHelperModel());
+ ModelMetadata metadata = ModelMetadata.FromStringExpression("MyProperty", html.ViewData);
+ metadata.NullDisplayText = "NullDisplayText";
+
+ // Act
+ string result = TemplateHelpers.TemplateHelper(html, metadata, "htmlFieldName", "templateName", DataBoundControlMode.ReadOnly,
+ null /* additionalViewData */, ExecuteTemplateSpy);
+
+ // Assert
+ Assert.Equal("Model = (null), ModelType = System.String, RealModelType = System.String, PropertyName = MyProperty, FormattedModelValue = NullDisplayText, HtmlFieldPrefix = FieldPrefix.htmlFieldName, TemplateName = templateName, Mode = ReadOnly", result);
+ }
+
+ [Fact]
+ public void TemplateHelperDoesNotConvertNullModelsToNullDisplayTextInEditMode()
+ {
+ // Arrange
+ HtmlHelper html = MakeHtmlHelper(new TemplateHelperModel());
+ ModelMetadata metadata = ModelMetadata.FromStringExpression("MyProperty", html.ViewData);
+ metadata.NullDisplayText = "NullDisplayText";
+
+ // Act
+ string result = TemplateHelpers.TemplateHelper(html, metadata, "htmlFieldName", "templateName", DataBoundControlMode.Edit,
+ null /* additionalViewData */, ExecuteTemplateSpy);
+
+ // Assert
+ Assert.Equal("Model = (null), ModelType = System.String, RealModelType = System.String, PropertyName = MyProperty, FormattedModelValue = , HtmlFieldPrefix = FieldPrefix.htmlFieldName, TemplateName = templateName, Mode = Edit", result);
+ }
+
+ [Fact]
+ public void TemplateHelperAppliesDisplayFormatStringInReadOnlyMode()
+ {
+ // Arrange
+ HtmlHelper html = MakeHtmlHelper(new TemplateHelperModel { MyProperty = "Hello" });
+ ModelMetadata metadata = ModelMetadata.FromStringExpression("MyProperty", html.ViewData);
+ metadata.DisplayFormatString = "{0} world!";
+
+ // Act
+ string result = TemplateHelpers.TemplateHelper(html, metadata, "htmlFieldName", "templateName", DataBoundControlMode.ReadOnly,
+ null /* additionalViewData */, ExecuteTemplateSpy);
+
+ // Assert
+ Assert.Equal("Model = Hello, ModelType = System.String, RealModelType = System.String, PropertyName = MyProperty, FormattedModelValue = Hello world!, HtmlFieldPrefix = FieldPrefix.htmlFieldName, TemplateName = templateName, Mode = ReadOnly", result);
+ }
+
+ [Fact]
+ public void TemplateHelperDoesNotApplyDisplayFormatStringInReadOnlyModeForNullModel()
+ {
+ // Arrange
+ HtmlHelper html = MakeHtmlHelper(new TemplateHelperModel());
+ ModelMetadata metadata = ModelMetadata.FromStringExpression("MyProperty", html.ViewData);
+ metadata.DisplayFormatString = "{0} world!";
+
+ // Act
+ string result = TemplateHelpers.TemplateHelper(html, metadata, "htmlFieldName", "templateName", DataBoundControlMode.ReadOnly,
+ null /* additionalViewData */, ExecuteTemplateSpy);
+
+ // Assert
+ Assert.Equal("Model = (null), ModelType = System.String, RealModelType = System.String, PropertyName = MyProperty, FormattedModelValue = , HtmlFieldPrefix = FieldPrefix.htmlFieldName, TemplateName = templateName, Mode = ReadOnly", result);
+ }
+
+ [Fact]
+ public void TemplateHelperAppliesEditFormatStringInEditMode()
+ {
+ HtmlHelper html = MakeHtmlHelper(new TemplateHelperModel { MyProperty = "Hello" });
+ ModelMetadata metadata = ModelMetadata.FromStringExpression("MyProperty", html.ViewData);
+ metadata.EditFormatString = "{0} world!";
+
+ // Act
+ string result = TemplateHelpers.TemplateHelper(html, metadata, "htmlFieldName", "templateName", DataBoundControlMode.Edit,
+ null /* additionalViewData */, ExecuteTemplateSpy);
+
+ // Assert
+ Assert.Equal("Model = Hello, ModelType = System.String, RealModelType = System.String, PropertyName = MyProperty, FormattedModelValue = Hello world!, HtmlFieldPrefix = FieldPrefix.htmlFieldName, TemplateName = templateName, Mode = Edit", result);
+ }
+
+ [Fact]
+ public void TemplateHelperDoesNotApplyEditFormatStringInEditModeForNullModel()
+ {
+ // Arrange
+ HtmlHelper html = MakeHtmlHelper(new TemplateHelperModel());
+ ModelMetadata metadata = ModelMetadata.FromStringExpression("MyProperty", html.ViewData);
+ metadata.EditFormatString = "{0} world!";
+
+ // Act
+ string result = TemplateHelpers.TemplateHelper(html, metadata, "htmlFieldName", "templateName", DataBoundControlMode.Edit,
+ null /* additionalViewData */, ExecuteTemplateSpy);
+
+ // Assert
+ Assert.Equal("Model = (null), ModelType = System.String, RealModelType = System.String, PropertyName = MyProperty, FormattedModelValue = , HtmlFieldPrefix = FieldPrefix.htmlFieldName, TemplateName = templateName, Mode = Edit", result);
+ }
+
+ [Fact]
+ public void TemplateHelperAddsNonNullModelToVisitedObjects()
+ { // DDB #224750
+ HtmlHelper html = MakeHtmlHelper(new TemplateHelperModel { MyProperty = "Hello" });
+ ModelMetadata metadata = ModelMetadata.FromStringExpression("MyProperty", html.ViewData);
+ ViewDataDictionary viewData = null;
+
+ // Act
+ TemplateHelpers.TemplateHelper(
+ html, metadata, "htmlFieldName", "templateName", DataBoundControlMode.ReadOnly, null /* additionalViewData */,
+ (_html, _viewData, _templateName, _mode, _getViews, _getDefaultActions) =>
+ {
+ viewData = _viewData;
+ return String.Empty;
+ });
+
+ // Assert
+ Assert.NotNull(viewData);
+ Assert.True(viewData.TemplateInfo.VisitedObjects.Contains("Hello"));
+ }
+
+ [Fact]
+ public void TemplateHelperAddsNullModelsTypeToVisitedObjects()
+ { // DDB #224750
+ HtmlHelper html = MakeHtmlHelper(new TemplateHelperModel());
+ ModelMetadata metadata = ModelMetadata.FromStringExpression("MyProperty", html.ViewData);
+ ViewDataDictionary viewData = null;
+
+ // Act
+ TemplateHelpers.TemplateHelper(
+ html, metadata, "htmlFieldName", "templateName", DataBoundControlMode.ReadOnly, null /* additionalViewData */,
+ (_html, _viewData, _templateName, _mode, _getViews, _getDefaultActions) =>
+ {
+ viewData = _viewData;
+ return String.Empty;
+ });
+
+ // Assert
+ Assert.NotNull(viewData);
+ Assert.True(viewData.TemplateInfo.VisitedObjects.Contains(typeof(string)));
+ }
+
+ [Fact]
+ public void TemplateHelperReturnsEmptyStringForAlreadyVisitedObject()
+ { // DDB #224750
+ HtmlHelper html = MakeHtmlHelper(new TemplateHelperModel { MyProperty = "Hello" });
+ ModelMetadata metadata = ModelMetadata.FromStringExpression("MyProperty", html.ViewData);
+ html.ViewData.TemplateInfo.VisitedObjects.Add("Hello");
+
+ // Act
+ string result = TemplateHelpers.TemplateHelper(html, metadata, "htmlFieldName", "templateName", DataBoundControlMode.ReadOnly,
+ null /* additionalViewData */);
+
+ // Assert
+ Assert.Equal(String.Empty, result);
+ }
+
+ [Fact]
+ public void TemplateHelperReturnsEmptyStringForAlreadyVisitedType()
+ { // DDB #224750
+ HtmlHelper html = MakeHtmlHelper(new TemplateHelperModel());
+ ModelMetadata metadata = ModelMetadata.FromStringExpression("MyProperty", html.ViewData);
+ html.ViewData.TemplateInfo.VisitedObjects.Add(typeof(string));
+
+ // Act
+ string result = TemplateHelpers.TemplateHelper(html, metadata, "htmlFieldName", "templateName", DataBoundControlMode.ReadOnly,
+ null /* additionalViewData */);
+
+ // Assert
+ Assert.Equal(String.Empty, result);
+ }
+
+ [Fact]
+ public void TemplateHelperPreservesSameInstanceOfModelMetadata()
+ { // DDB #225858
+ HtmlHelper html = MakeHtmlHelper(new TemplateHelperModel());
+ ModelMetadata metadata = ModelMetadata.FromStringExpression("MyProperty", html.ViewData);
+ ViewDataDictionary callbackViewData = null;
+
+ // Act
+ string result = TemplateHelpers.TemplateHelper(html, metadata, "htmlFieldName", "templateName", DataBoundControlMode.ReadOnly,
+ null /* additionalViewData */,
+ (_html, _viewData, _templateName, _mode, _getViewNames, _getDefaultActions) =>
+ {
+ callbackViewData = _viewData;
+ return String.Empty;
+ });
+
+ // Assert
+ Assert.NotNull(callbackViewData);
+ Assert.Same(metadata, callbackViewData.ModelMetadata);
+ }
+
+ [Fact]
+ public void TemplateHelperFormatsValuesUsingCurrentCulture()
+ {
+ CultureInfo existingCulture = Thread.CurrentThread.CurrentCulture;
+
+ try
+ {
+ // Arrange
+ Thread.CurrentThread.CurrentCulture = CultureInfo.GetCultureInfo("es-PR");
+ HtmlHelper html = MakeHtmlHelper(new { MyProperty = new DateTime(2009, 11, 18, 16, 12, 8, DateTimeKind.Utc) });
+ ModelMetadata metadata = ModelMetadata.FromStringExpression("MyProperty", html.ViewData);
+ metadata.DisplayFormatString = "{0:F}";
+
+ // Act
+ string result = TemplateHelpers.TemplateHelper(html, metadata, "htmlFieldName", "templateName", DataBoundControlMode.ReadOnly,
+ null /* additionalViewData */, ExecuteTemplateSpy);
+
+ // Assert
+ Assert.Equal("Model = 18/11/2009 04:12:08 p.m., ModelType = System.DateTime, RealModelType = System.DateTime, PropertyName = MyProperty, FormattedModelValue = miércoles, 18 de noviembre de 2009 04:12:08 p.m., HtmlFieldPrefix = FieldPrefix.htmlFieldName, TemplateName = templateName, Mode = ReadOnly", result);
+ }
+ finally
+ {
+ Thread.CurrentThread.CurrentCulture = existingCulture;
+ }
+ }
+
+ [Fact]
+ public void TemplateHelperPreservesExistingViewData()
+ {
+ HtmlHelper html = MakeHtmlHelper(new TemplateHelperModel());
+ html.ViewData["Foo"] = "Bar";
+ html.ViewData["Baz"] = 42;
+ ModelMetadata metadata = ModelMetadata.FromStringExpression("MyProperty", html.ViewData);
+ ViewDataDictionary callbackViewData = null;
+
+ // Act
+ string result = TemplateHelpers.TemplateHelper(html, metadata, "htmlFieldName", "templateName", DataBoundControlMode.ReadOnly,
+ null /* additionalViewData */,
+ (_html, _viewData, _templateName, _mode, _getViewNames, _getDefaultActions) =>
+ {
+ callbackViewData = _viewData;
+ return String.Empty;
+ });
+
+ // Assert
+ Assert.NotSame(html.ViewData, callbackViewData);
+ Assert.Equal(2, callbackViewData.Count);
+ Assert.Equal("Bar", callbackViewData["Foo"]);
+ Assert.Equal(42, callbackViewData["Baz"]);
+ }
+
+ [Fact]
+ public void TemplateHelperMergesAdditionalViewData()
+ {
+ HtmlHelper html = MakeHtmlHelper(new TemplateHelperModel());
+ html.ViewData["Foo"] = "Bar";
+ html.ViewData["Baz"] = 42;
+ ModelMetadata metadata = ModelMetadata.FromStringExpression("MyProperty", html.ViewData);
+ ViewDataDictionary callbackViewData = null;
+
+ // Act
+ string result = TemplateHelpers.TemplateHelper(html, metadata, "htmlFieldName", "templateName", DataBoundControlMode.ReadOnly,
+ new { foo = "New Foo", hello = "World!" },
+ (_html, _viewData, _templateName, _mode, _getViewNames, _getDefaultActions) =>
+ {
+ callbackViewData = _viewData;
+ return String.Empty;
+ });
+
+ // Assert
+ Assert.NotSame(html.ViewData, callbackViewData);
+ Assert.Equal(3, callbackViewData.Count);
+ Assert.Equal("New Foo", callbackViewData["Foo"]);
+ Assert.Equal(42, callbackViewData["Baz"]);
+ Assert.Equal("World!", callbackViewData["Hello"]);
+ }
+
+ // Helpers
+
+ private static string ExecuteTemplateSpy(HtmlHelper html, ViewDataDictionary viewData, string templateName, DataBoundControlMode mode,
+ TemplateHelpers.GetViewNamesDelegate getViewNames, TemplateHelpers.GetDefaultActionsDelegate getDefaultActions)
+ {
+ Assert.Same(viewData.Model, viewData.ModelMetadata.Model);
+ Assert.Equal<TemplateHelpers.GetViewNamesDelegate>(TemplateHelpers.GetViewNames, getViewNames);
+ Assert.Equal<TemplateHelpers.GetDefaultActionsDelegate>(TemplateHelpers.GetDefaultActions, getDefaultActions);
+
+ return String.Format("Model = {0}, ModelType = {1}, RealModelType = {2}, PropertyName = {3}, FormattedModelValue = {4}, HtmlFieldPrefix = {5}, TemplateName = {6}, Mode = {7}",
+ viewData.ModelMetadata.Model ?? "(null)",
+ viewData.ModelMetadata.ModelType == null ? "(null)" : viewData.ModelMetadata.ModelType.FullName,
+ viewData.ModelMetadata.RealModelType == null ? "(null)" : viewData.ModelMetadata.RealModelType.FullName,
+ viewData.ModelMetadata.PropertyName ?? "(null)",
+ viewData.TemplateInfo.FormattedModelValue ?? "(null)",
+ viewData.TemplateInfo.HtmlFieldPrefix == "" ? "(empty)" : viewData.TemplateInfo.HtmlFieldPrefix ?? "(null)",
+ templateName ?? "(null)",
+ mode);
+ }
+
+ private static string TemplateHelperSpy(HtmlHelper html, ModelMetadata metadata, string htmlFieldName, string templateName, DataBoundControlMode mode,
+ object additionalViewData)
+ {
+ return String.Format("Model = {0}, ModelType = {1}, RealModelType = {2}, PropertyName = {3}, HtmlFieldName = {4}, TemplateName = {5}, Mode = {6}, AdditionalViewData = {7}",
+ metadata.Model ?? "(null)",
+ metadata.ModelType == null ? "(null)" : metadata.ModelType.FullName,
+ metadata.RealModelType == null ? "(null)" : metadata.RealModelType.FullName,
+ metadata.PropertyName ?? "(null)",
+ htmlFieldName == String.Empty ? "(empty)" : htmlFieldName ?? "(null)",
+ templateName ?? "(null)",
+ mode,
+ AnonymousObject.Inspect(additionalViewData));
+ }
+
+ private HtmlHelper<TModel> MakeHtmlHelper<TModel>(TModel model)
+ {
+ return MakeHtmlHelper<TModel>(model, model);
+ }
+
+ private HtmlHelper<TModel> MakeHtmlHelper<TModel>(TModel model, object formattedModelValue)
+ {
+ ViewDataDictionary viewData = new ViewDataDictionary(model);
+ viewData.TemplateInfo.HtmlFieldPrefix = "FieldPrefix";
+ viewData.TemplateInfo.FormattedModelValue = formattedModelValue;
+ viewData.ModelMetadata = new EmptyModelMetadataProvider().GetMetadataForType(() => model, typeof(TModel));
+
+ Mock<HttpContextBase> httpContext = new Mock<HttpContextBase>();
+ httpContext.Setup(c => c.Items).Returns(new Hashtable());
+
+ Mock<ViewContext> viewContext = new Mock<ViewContext> { CallBase = true };
+ viewContext.Setup(c => c.View).Returns(new DummyView());
+ viewContext.Setup(c => c.ViewData).Returns(viewData);
+ viewContext.Setup(c => c.HttpContext).Returns(httpContext.Object);
+ viewContext.Setup(c => c.RouteData).Returns(new RouteData());
+ viewContext.Setup(c => c.TempData).Returns(new TempDataDictionary());
+ viewContext.Setup(c => c.Writer).Returns(TextWriter.Null);
+
+ return new HtmlHelper<TModel>(viewContext.Object, new SimpleViewDataContainer(viewData));
+ }
+
+ private ViewDataDictionary MakeViewData(HtmlHelper html, ModelMetadata metadata)
+ {
+ return new ViewDataDictionary(html.ViewDataContainer.ViewData)
+ {
+ Model = metadata.Model,
+ ModelMetadata = metadata,
+ TemplateInfo = new TemplateInfo
+ {
+ FormattedModelValue = metadata.Model,
+ HtmlFieldPrefix = "FieldPrefix",
+ }
+ };
+ }
+
+ private class DummyView : IView
+ {
+ public void Render(ViewContext viewContext, TextWriter writer)
+ {
+ throw new NotImplementedException();
+ }
+ }
+
+ private class MockViewEngine : IDisposable
+ {
+ List<IViewEngine> oldEngines;
+
+ public MockViewEngine(bool returnView = true)
+ {
+ oldEngines = ViewEngines.Engines.ToList();
+
+ View = new Mock<IView>();
+
+ Engine = new Mock<IViewEngine>();
+
+ Engine.Setup(e => e.FindPartialView(It.IsAny<ControllerContext>(), It.IsAny<string>(), It.IsAny<bool>()))
+ .Returns(returnView ? new ViewEngineResult(View.Object, Engine.Object) : new ViewEngineResult(new string[0]));
+
+ ViewEngines.Engines.Clear();
+ ViewEngines.Engines.Add(Engine.Object);
+ }
+
+ public void Dispose()
+ {
+ ViewEngines.Engines.Clear();
+
+ foreach (IViewEngine engine in oldEngines)
+ {
+ ViewEngines.Engines.Add(engine);
+ }
+ }
+
+ public Mock<IViewEngine> Engine { get; set; }
+
+ public Mock<IView> View { get; set; }
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Html/Test/TextAreaExtensionsTest.cs b/test/System.Web.Mvc.Test/Html/Test/TextAreaExtensionsTest.cs
new file mode 100644
index 00000000..dadf5e58
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Html/Test/TextAreaExtensionsTest.cs
@@ -0,0 +1,629 @@
+using System.Web.Mvc.Test;
+using System.Web.Routing;
+using Microsoft.Web.UnitTestUtil;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Mvc.Html.Test
+{
+ public class TextAreaExtensionsTest
+ {
+ private static readonly RouteValueDictionary _textAreaAttributesDictionary = new RouteValueDictionary(new { rows = "15", cols = "12" });
+ private static readonly object _textAreaAttributesObjectDictionary = new { rows = "15", cols = "12" };
+ private static readonly object _textAreaAttributesObjectUnderscoresDictionary = new { rows = "15", cols = "12", foo_bar = "baz" };
+
+ private class TextAreaModel
+ {
+ public string foo { get; set; }
+ public string bar { get; set; }
+ }
+
+ private static ViewDataDictionary<TextAreaModel> GetTextAreaViewData()
+ {
+ ViewDataDictionary<TextAreaModel> viewData = new ViewDataDictionary<TextAreaModel> { { "foo", "ViewDataFoo" } };
+ viewData.Model = new TextAreaModel { foo = "ViewItemFoo", bar = "ViewItemBar" };
+ return viewData;
+ }
+
+ private static ViewDataDictionary<TextAreaModel> GetTextAreaViewDataWithErrors()
+ {
+ ViewDataDictionary<TextAreaModel> viewData = new ViewDataDictionary<TextAreaModel> { { "foo", "ViewDataFoo" } };
+ viewData.Model = new TextAreaModel { foo = "ViewItemFoo", bar = "ViewItemBar" };
+
+ ModelState modelStateFoo = new ModelState();
+ modelStateFoo.Errors.Add(new ModelError("foo error 1"));
+ modelStateFoo.Errors.Add(new ModelError("foo error 2"));
+ viewData.ModelState["foo"] = modelStateFoo;
+ modelStateFoo.Value = HtmlHelperTest.GetValueProviderResult(new string[] { "AttemptedValueFoo" }, "AttemptedValueFoo");
+
+ return viewData;
+ }
+
+ // TextArea
+
+ [Fact]
+ public void TextAreaParameterDictionaryMerging()
+ {
+ // Arrange
+ HtmlHelper helper = MvcHelper.GetHtmlHelper();
+
+ // Act
+ MvcHtmlString html = helper.TextArea("foo", new { rows = "30" });
+
+ // Assert
+ Assert.Equal(@"<textarea cols=""20"" id=""foo"" name=""foo"" rows=""30"">
+</textarea>", html.ToHtmlString());
+ }
+
+ [Fact]
+ public void TextAreaParameterDictionaryMerging_Unobtrusive()
+ {
+ // Arrange
+ HtmlHelper helper = MvcHelper.GetHtmlHelper();
+ helper.ViewContext.ClientValidationEnabled = true;
+ helper.ViewContext.UnobtrusiveJavaScriptEnabled = true;
+ helper.ViewContext.FormContext = new FormContext();
+ helper.ClientValidationRuleFactory = (name, metadata) => new[] { new ModelClientValidationRule { ValidationType = "type", ErrorMessage = "error" } };
+
+ // Act
+ MvcHtmlString html = helper.TextArea("foo", new { rows = "30" });
+
+ // Assert
+ Assert.Equal(@"<textarea cols=""20"" data-val=""true"" data-val-type=""error"" id=""foo"" name=""foo"" rows=""30"">
+</textarea>", html.ToHtmlString());
+ }
+
+ [Fact]
+ public void TextAreaParameterDictionaryMergingExplicitParameters()
+ {
+ // Arrange
+ HtmlHelper helper = MvcHelper.GetHtmlHelper();
+
+ // Act
+ MvcHtmlString html = helper.TextArea("foo", "bar", 10, 25, new { rows = "30" });
+
+ // Assert
+ Assert.Equal(@"<textarea cols=""25"" id=""foo"" name=""foo"" rows=""10"">
+bar</textarea>", html.ToHtmlString());
+ }
+
+ [Fact]
+ public void TextAreaParameterDictionaryMergingExplicitParametersWithUnderscores()
+ {
+ // Arrange
+ HtmlHelper helper = MvcHelper.GetHtmlHelper();
+
+ // Act
+ MvcHtmlString html = helper.TextArea("foo", "bar", 10, 25, new { rows = "30", foo_bar = "baz" });
+
+ // Assert
+ Assert.Equal(@"<textarea cols=""25"" foo-bar=""baz"" id=""foo"" name=""foo"" rows=""10"">
+bar</textarea>", html.ToHtmlString());
+ }
+
+ [Fact]
+ public void TextAreaWithEmptyNameThrows()
+ {
+ // Arrange
+ HtmlHelper helper = MvcHelper.GetHtmlHelper();
+
+ // Act & Assert
+ Assert.ThrowsArgumentNullOrEmpty(
+ delegate { helper.TextArea(String.Empty); },
+ "name");
+ }
+
+ [Fact]
+ public void TextAreaWithOutOfRangeColsThrows()
+ {
+ // Arrange
+ HtmlHelper helper = MvcHelper.GetHtmlHelper();
+
+ // Act & Assert
+ Assert.ThrowsArgumentOutOfRange(
+ delegate { helper.TextArea("Foo", null /* value */, 0, -1, null /* htmlAttributes */); },
+ "columns",
+ @"The value must be greater than or equal to zero.");
+ }
+
+ [Fact]
+ public void TextAreaWithOutOfRangeRowsThrows()
+ {
+ // Arrange
+ HtmlHelper helper = MvcHelper.GetHtmlHelper();
+
+ // Act & Assert
+ Assert.ThrowsArgumentOutOfRange(
+ delegate { helper.TextArea("Foo", null /* value */, -1, 0, null /* htmlAttributes */); },
+ "rows",
+ @"The value must be greater than or equal to zero.");
+ }
+
+ [Fact]
+ public void TextAreaWithExplicitValue()
+ {
+ // Arrange
+ HtmlHelper helper = MvcHelper.GetHtmlHelper();
+
+ // Act
+ MvcHtmlString html = helper.TextArea("foo", "bar");
+
+ // Assert
+ Assert.Equal(@"<textarea cols=""20"" id=""foo"" name=""foo"" rows=""2"">
+bar</textarea>", html.ToHtmlString());
+ }
+
+ [Fact]
+ public void TextAreaWithDefaultAttributes()
+ {
+ // Arrange
+ HtmlHelper helper = MvcHelper.GetHtmlHelper(GetTextAreaViewData());
+
+ // Act
+ MvcHtmlString html = helper.TextArea("foo");
+
+ // Assert
+ Assert.Equal(@"<textarea cols=""20"" id=""foo"" name=""foo"" rows=""2"">
+ViewDataFoo</textarea>", html.ToHtmlString());
+ }
+
+ [Fact]
+ public void TextAreaWithZeroRowsAndColumns()
+ {
+ // Arrange
+ HtmlHelper helper = MvcHelper.GetHtmlHelper(GetTextAreaViewData());
+
+ // Act
+ MvcHtmlString html = helper.TextArea("foo", null, 0, 0, null);
+
+ // Assert
+ Assert.Equal(@"<textarea id=""foo"" name=""foo"">
+ViewDataFoo</textarea>", html.ToHtmlString());
+ }
+
+ [Fact]
+ public void TextAreaWithDotReplacementForId()
+ {
+ // Arrange
+ HtmlHelper helper = MvcHelper.GetHtmlHelper(GetTextAreaViewData());
+
+ // Act
+ MvcHtmlString html = helper.TextArea("foo.bar.baz");
+
+ // Assert
+ Assert.Equal(@"<textarea cols=""20"" id=""foo_bar_baz"" name=""foo.bar.baz"" rows=""2"">
+</textarea>", html.ToHtmlString());
+ }
+
+ [Fact]
+ public void TextAreaWithObjectAttributes()
+ {
+ // Arrange
+ HtmlHelper helper = MvcHelper.GetHtmlHelper(GetTextAreaViewData());
+
+ // Act
+ MvcHtmlString html = helper.TextArea("foo", _textAreaAttributesObjectDictionary);
+
+ // Assert
+ Assert.Equal(@"<textarea cols=""12"" id=""foo"" name=""foo"" rows=""15"">
+ViewDataFoo</textarea>", html.ToHtmlString());
+ }
+
+ [Fact]
+ public void TextAreaWithObjectAttributesWithUnderscores()
+ {
+ // Arrange
+ HtmlHelper helper = MvcHelper.GetHtmlHelper(GetTextAreaViewData());
+
+ // Act
+ MvcHtmlString html = helper.TextArea("foo", _textAreaAttributesObjectUnderscoresDictionary);
+
+ // Assert
+ Assert.Equal(@"<textarea cols=""12"" foo-bar=""baz"" id=""foo"" name=""foo"" rows=""15"">
+ViewDataFoo</textarea>", html.ToHtmlString());
+ }
+
+ [Fact]
+ public void TextAreaWithDictionaryAttributes()
+ {
+ // Arrange
+ HtmlHelper helper = MvcHelper.GetHtmlHelper(GetTextAreaViewData());
+
+ // Act
+ MvcHtmlString html = helper.TextArea("foo", _textAreaAttributesDictionary);
+
+ // Assert
+ Assert.Equal(@"<textarea cols=""12"" id=""foo"" name=""foo"" rows=""15"">
+ViewDataFoo</textarea>", html.ToHtmlString());
+ }
+
+ [Fact]
+ public void TextAreaWithExplicitValueAndObjectAttributes()
+ {
+ // Arrange
+ HtmlHelper helper = MvcHelper.GetHtmlHelper(GetTextAreaViewData());
+
+ // Act
+ MvcHtmlString html = helper.TextArea("foo", "Hello World", _textAreaAttributesObjectDictionary);
+
+ // Assert
+ Assert.Equal(@"<textarea cols=""12"" id=""foo"" name=""foo"" rows=""15"">
+Hello World</textarea>", html.ToHtmlString());
+ }
+
+ [Fact]
+ public void TextAreaWithExplicitValueAndObjectAttributesWithUnderscores()
+ {
+ // Arrange
+ HtmlHelper helper = MvcHelper.GetHtmlHelper(GetTextAreaViewData());
+
+ // Act
+ MvcHtmlString html = helper.TextArea("foo", "Hello World", _textAreaAttributesObjectUnderscoresDictionary);
+
+ // Assert
+ Assert.Equal(@"<textarea cols=""12"" foo-bar=""baz"" id=""foo"" name=""foo"" rows=""15"">
+Hello World</textarea>", html.ToHtmlString());
+ }
+
+ [Fact]
+ public void TextAreaWithExplicitValueAndDictionaryAttributes()
+ {
+ // Arrange
+ HtmlHelper helper = MvcHelper.GetHtmlHelper(GetTextAreaViewData());
+
+ // Act
+ MvcHtmlString html = helper.TextArea("foo", "<Hello World>", _textAreaAttributesDictionary);
+
+ // Assert
+ Assert.Equal(@"<textarea cols=""12"" id=""foo"" name=""foo"" rows=""15"">
+&lt;Hello World&gt;</textarea>", html.ToHtmlString());
+ }
+
+ [Fact]
+ public void TextAreaWithNoValueAndObjectAttributes()
+ {
+ // Arrange
+ HtmlHelper helper = MvcHelper.GetHtmlHelper(GetTextAreaViewData());
+
+ // Act
+ MvcHtmlString html = helper.TextArea("baz", _textAreaAttributesObjectDictionary);
+
+ // Assert
+ Assert.Equal(@"<textarea cols=""12"" id=""baz"" name=""baz"" rows=""15"">
+</textarea>", html.ToHtmlString());
+ }
+
+ [Fact]
+ public void TextAreaWithNullValue()
+ {
+ // Arrange
+ HtmlHelper helper = MvcHelper.GetHtmlHelper(GetTextAreaViewData());
+
+ // Act
+ MvcHtmlString html = helper.TextArea("foo", null, null);
+
+ // Assert
+ Assert.Equal(@"<textarea cols=""20"" id=""foo"" name=""foo"" rows=""2"">
+ViewDataFoo</textarea>", html.ToHtmlString());
+ }
+
+ [Fact]
+ public void TextAreaWithViewDataErrors()
+ {
+ // Arrange
+ HtmlHelper helper = MvcHelper.GetHtmlHelper(GetTextAreaViewDataWithErrors());
+
+ // Act
+ MvcHtmlString html = helper.TextArea("foo", _textAreaAttributesObjectDictionary);
+
+ // Assert
+ Assert.Equal(@"<textarea class=""input-validation-error"" cols=""12"" id=""foo"" name=""foo"" rows=""15"">
+AttemptedValueFoo</textarea>", html.ToHtmlString());
+ }
+
+ [Fact]
+ public void TextAreaWithViewDataErrorsAndCustomClass()
+ {
+ // Arrange
+ HtmlHelper helper = MvcHelper.GetHtmlHelper(GetTextAreaViewDataWithErrors());
+
+ // Act
+ MvcHtmlString html = helper.TextArea("foo", new { @class = "foo-class" });
+
+ // Assert
+ Assert.Equal(@"<textarea class=""input-validation-error foo-class"" cols=""20"" id=""foo"" name=""foo"" rows=""2"">
+AttemptedValueFoo</textarea>", html.ToHtmlString());
+ }
+
+ [Fact]
+ public void TextAreaWithPrefix()
+ {
+ // Arrange
+ HtmlHelper helper = MvcHelper.GetHtmlHelper();
+ helper.ViewContext.ViewData.TemplateInfo.HtmlFieldPrefix = "MyPrefix";
+
+ // Act
+ MvcHtmlString html = helper.TextArea("foo", "bar");
+
+ // Assert
+ Assert.Equal(@"<textarea cols=""20"" id=""MyPrefix_foo"" name=""MyPrefix.foo"" rows=""2"">
+bar</textarea>", html.ToHtmlString());
+ }
+
+ [Fact]
+ public void TextAreaWithPrefixAndEmptyName()
+ {
+ // Arrange
+ HtmlHelper helper = MvcHelper.GetHtmlHelper();
+ helper.ViewContext.ViewData.TemplateInfo.HtmlFieldPrefix = "MyPrefix";
+
+ // Act
+ MvcHtmlString html = helper.TextArea("", "bar");
+
+ // Assert
+ Assert.Equal(@"<textarea cols=""20"" id=""MyPrefix"" name=""MyPrefix"" rows=""2"">
+bar</textarea>", html.ToHtmlString());
+ }
+
+ // TextAreaFor
+
+ [Fact]
+ public void TextAreaForWithNullExpression()
+ {
+ // Arrange
+ HtmlHelper<TextAreaModel> helper = MvcHelper.GetHtmlHelper(GetTextAreaViewData());
+
+ // Act & Assert
+ Assert.ThrowsArgumentNull(
+ () => helper.TextAreaFor<TextAreaModel, object>(null),
+ "expression"
+ );
+ }
+
+ [Fact]
+ public void TextAreaForWithOutOfRangeColsThrows()
+ {
+ // Arrange
+ HtmlHelper<TextAreaModel> helper = MvcHelper.GetHtmlHelper(GetTextAreaViewData());
+
+ // Act & Assert
+ Assert.ThrowsArgumentOutOfRange(
+ () => helper.TextAreaFor(m => m.foo, 0, -1, null /* htmlAttributes */),
+ "columns",
+ "The value must be greater than or equal to zero."
+ );
+ }
+
+ [Fact]
+ public void TextAreaForWithOutOfRangeRowsThrows()
+ {
+ // Arrange
+ HtmlHelper<TextAreaModel> helper = MvcHelper.GetHtmlHelper(GetTextAreaViewData());
+
+ // Act & Assert
+ Assert.ThrowsArgumentOutOfRange(
+ () => helper.TextAreaFor(m => m.foo, -1, 0, null /* htmlAttributes */),
+ "rows",
+ "The value must be greater than or equal to zero."
+ );
+ }
+
+ [Fact]
+ public void TextAreaForParameterDictionaryMerging()
+ {
+ // Arrange
+ HtmlHelper<TextAreaModel> helper = MvcHelper.GetHtmlHelper(GetTextAreaViewData());
+
+ // Act
+ MvcHtmlString html = helper.TextAreaFor(m => m.foo, new { rows = "30" });
+
+ // Assert
+ Assert.Equal(@"<textarea cols=""20"" id=""foo"" name=""foo"" rows=""30"">
+ViewItemFoo</textarea>", html.ToHtmlString());
+ }
+
+ [Fact]
+ public void TextAreaForParameterDictionaryMerging_Unobtrusive()
+ {
+ // Arrange
+ HtmlHelper<TextAreaModel> helper = MvcHelper.GetHtmlHelper(GetTextAreaViewData());
+ helper.ViewContext.ClientValidationEnabled = true;
+ helper.ViewContext.UnobtrusiveJavaScriptEnabled = true;
+ helper.ViewContext.FormContext = new FormContext();
+ helper.ClientValidationRuleFactory = (name, metadata) => new[] { new ModelClientValidationRule { ValidationType = "type", ErrorMessage = "error" } };
+
+ // Act
+ MvcHtmlString html = helper.TextAreaFor(m => m.foo, new { rows = "30" });
+
+ // Assert
+ Assert.Equal(@"<textarea cols=""20"" data-val=""true"" data-val-type=""error"" id=""foo"" name=""foo"" rows=""30"">
+ViewItemFoo</textarea>", html.ToHtmlString());
+ }
+
+ [Fact]
+ public void TextAreaForWithDefaultAttributes()
+ {
+ // Arrange
+ HtmlHelper<TextAreaModel> helper = MvcHelper.GetHtmlHelper(GetTextAreaViewData());
+
+ // Act
+ MvcHtmlString html = helper.TextAreaFor(m => m.foo);
+
+ // Assert
+ Assert.Equal(@"<textarea cols=""20"" id=""foo"" name=""foo"" rows=""2"">
+ViewItemFoo</textarea>", html.ToHtmlString());
+ }
+
+ [Fact]
+ public void TextAreaForWithZeroRowsAndColumns()
+ {
+ // Arrange
+ HtmlHelper<TextAreaModel> helper = MvcHelper.GetHtmlHelper(GetTextAreaViewData());
+
+ // Act
+ MvcHtmlString html = helper.TextAreaFor(m => m.foo, 0, 0, null);
+
+ // Assert
+ Assert.Equal(@"<textarea id=""foo"" name=""foo"">
+ViewItemFoo</textarea>", html.ToHtmlString());
+ }
+
+ [Fact]
+ public void TextAreaForWithObjectAttributes()
+ {
+ // Arrange
+ HtmlHelper<TextAreaModel> helper = MvcHelper.GetHtmlHelper(GetTextAreaViewData());
+
+ // Act
+ MvcHtmlString html = helper.TextAreaFor(m => m.foo, _textAreaAttributesObjectDictionary);
+
+ // Assert
+ Assert.Equal(@"<textarea cols=""12"" id=""foo"" name=""foo"" rows=""15"">
+ViewItemFoo</textarea>", html.ToHtmlString());
+ }
+
+ [Fact]
+ public void TextAreaForWithObjectAttributesWithUnderscores()
+ {
+ // Arrange
+ HtmlHelper<TextAreaModel> helper = MvcHelper.GetHtmlHelper(GetTextAreaViewData());
+
+ // Act
+ MvcHtmlString html = helper.TextAreaFor(m => m.foo, _textAreaAttributesObjectUnderscoresDictionary);
+
+ // Assert
+ Assert.Equal(@"<textarea cols=""12"" foo-bar=""baz"" id=""foo"" name=""foo"" rows=""15"">
+ViewItemFoo</textarea>", html.ToHtmlString());
+ }
+
+ [Fact]
+ public void TextAreaForWithDictionaryAttributes()
+ {
+ // Arrange
+ HtmlHelper<TextAreaModel> helper = MvcHelper.GetHtmlHelper(GetTextAreaViewData());
+
+ // Act
+ MvcHtmlString html = helper.TextAreaFor(m => m.foo, _textAreaAttributesDictionary);
+
+ // Assert
+ Assert.Equal(@"<textarea cols=""12"" id=""foo"" name=""foo"" rows=""15"">
+ViewItemFoo</textarea>", html.ToHtmlString());
+ }
+
+ [Fact]
+ public void TextAreaForWithViewDataErrors()
+ {
+ // Arrange
+ HtmlHelper<TextAreaModel> helper = MvcHelper.GetHtmlHelper(GetTextAreaViewDataWithErrors());
+
+ // Act
+ MvcHtmlString html = helper.TextAreaFor(m => m.foo, _textAreaAttributesObjectDictionary);
+
+ // Assert
+ Assert.Equal(@"<textarea class=""input-validation-error"" cols=""12"" id=""foo"" name=""foo"" rows=""15"">
+AttemptedValueFoo</textarea>", html.ToHtmlString());
+ }
+
+ [Fact]
+ public void TextAreaForWithViewDataErrorsAndCustomClass()
+ {
+ // Arrange
+ HtmlHelper<TextAreaModel> helper = MvcHelper.GetHtmlHelper(GetTextAreaViewDataWithErrors());
+
+ // Act
+ MvcHtmlString html = helper.TextAreaFor(m => m.foo, new { @class = "foo-class" });
+
+ // Assert
+ Assert.Equal(@"<textarea class=""input-validation-error foo-class"" cols=""20"" id=""foo"" name=""foo"" rows=""2"">
+AttemptedValueFoo</textarea>", html.ToHtmlString());
+ }
+
+ [Fact]
+ public void TextAreaForWithPrefix()
+ {
+ // Arrange
+ HtmlHelper<TextAreaModel> helper = MvcHelper.GetHtmlHelper(GetTextAreaViewData());
+ helper.ViewContext.ViewData.TemplateInfo.HtmlFieldPrefix = "MyPrefix";
+
+ // Act
+ MvcHtmlString html = helper.TextAreaFor(m => m.foo);
+
+ // Assert
+ Assert.Equal(@"<textarea cols=""20"" id=""MyPrefix_foo"" name=""MyPrefix.foo"" rows=""2"">
+ViewItemFoo</textarea>", html.ToHtmlString());
+ }
+
+ [Fact]
+ public void TextAreaForWithPrefixAndEmptyName()
+ {
+ // Arrange
+ HtmlHelper<TextAreaModel> helper = MvcHelper.GetHtmlHelper(GetTextAreaViewData());
+ helper.ViewContext.ViewData.TemplateInfo.HtmlFieldPrefix = "MyPrefix";
+
+ // Act
+ MvcHtmlString html = helper.TextAreaFor(m => m);
+
+ // Assert
+ Assert.Equal(@"<textarea cols=""20"" id=""MyPrefix"" name=""MyPrefix"" rows=""2"">
+System.Web.Mvc.Html.Test.TextAreaExtensionsTest+TextAreaModel</textarea>", html.ToHtmlString());
+ }
+
+ [Fact]
+ public void TextAreaForParameterDictionaryMergingWithObjectValues()
+ {
+ // Arrange
+ HtmlHelper<TextAreaModel> helper = MvcHelper.GetHtmlHelper(GetTextAreaViewData());
+
+ // Act
+ MvcHtmlString html = helper.TextAreaFor(m => m.foo, 10, 25, new { rows = "30" });
+
+ // Assert
+ Assert.Equal(@"<textarea cols=""25"" id=""foo"" name=""foo"" rows=""10"">
+ViewItemFoo</textarea>", html.ToHtmlString());
+ }
+
+ [Fact]
+ public void TextAreaForParameterDictionaryMergingWithObjectValuesWithUnderscores()
+ {
+ // Arrange
+ HtmlHelper<TextAreaModel> helper = MvcHelper.GetHtmlHelper(GetTextAreaViewData());
+
+ // Act
+ MvcHtmlString html = helper.TextAreaFor(m => m.foo, 10, 25, new { rows = "30", foo_bar = "baz" });
+
+ // Assert
+ Assert.Equal(@"<textarea cols=""25"" foo-bar=""baz"" id=""foo"" name=""foo"" rows=""10"">
+ViewItemFoo</textarea>", html.ToHtmlString());
+ }
+
+ [Fact]
+ public void TextAreaForParameterDictionaryMergingWithDictionaryValues()
+ {
+ // Arrange
+ HtmlHelper<TextAreaModel> helper = MvcHelper.GetHtmlHelper(GetTextAreaViewData());
+
+ // Act
+ MvcHtmlString html = helper.TextAreaFor(m => m.foo, 10, 25, new RouteValueDictionary(new { rows = "30" }));
+
+ // Assert
+ Assert.Equal(@"<textarea cols=""25"" id=""foo"" name=""foo"" rows=""10"">
+ViewItemFoo</textarea>", html.ToHtmlString());
+ }
+
+ [Fact]
+ public void TextAreaHelperDoesNotEncodeInnerHtmlPrefix()
+ {
+ // Arrange
+ HtmlHelper helper = MvcHelper.GetHtmlHelper();
+ ModelMetadata metadata = ModelMetadata.FromStringExpression("foo", helper.ViewData);
+ metadata.Model = "<model>";
+
+ // Act
+ MvcHtmlString html = TextAreaExtensions.TextAreaHelper(helper, metadata, "testEncoding", rowsAndColumns: null,
+ htmlAttributes: null, innerHtmlPrefix: "<prefix>");
+
+ // Assert
+ Assert.Equal(@"<textarea id=""testEncoding"" name=""testEncoding""><prefix>&lt;model&gt;</textarea>", html.ToHtmlString());
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Html/Test/ValidationExtensionsTest.cs b/test/System.Web.Mvc.Test/Html/Test/ValidationExtensionsTest.cs
new file mode 100644
index 00000000..0683e60f
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Html/Test/ValidationExtensionsTest.cs
@@ -0,0 +1,1086 @@
+using System.ComponentModel.DataAnnotations;
+using System.Linq;
+using System.Web.Routing;
+using Microsoft.Web.UnitTestUtil;
+using Moq;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Mvc.Html.Test
+{
+ public class ValidationExtensionsTest
+ {
+ // Validate
+
+ [Fact]
+ public void Validate_AddsClientValidationMetadata()
+ {
+ var originalProviders = ModelValidatorProviders.Providers.ToArray();
+ ModelValidatorProviders.Providers.Clear();
+
+ try
+ {
+ // Arrange
+ HtmlHelper htmlHelper = MvcHelper.GetHtmlHelper(GetViewDataWithModelErrors());
+ FormContext formContext = new FormContext()
+ {
+ FormId = "form_id"
+ };
+ htmlHelper.ViewContext.ClientValidationEnabled = true;
+ htmlHelper.ViewContext.FormContext = formContext;
+
+ ModelClientValidationRule[] expectedValidationRules = new ModelClientValidationRule[]
+ {
+ new ModelClientValidationRule() { ValidationType = "ValidationRule1" },
+ new ModelClientValidationRule() { ValidationType = "ValidationRule2" }
+ };
+
+ Mock<ModelValidator> mockValidator = new Mock<ModelValidator>(ModelMetadata.FromStringExpression("", htmlHelper.ViewContext.ViewData), htmlHelper.ViewContext);
+ mockValidator.Setup(v => v.GetClientValidationRules())
+ .Returns(expectedValidationRules);
+ Mock<ModelValidatorProvider> mockValidatorProvider = new Mock<ModelValidatorProvider>();
+ mockValidatorProvider.Setup(vp => vp.GetValidators(It.IsAny<ModelMetadata>(), It.IsAny<ControllerContext>()))
+ .Returns(new[] { mockValidator.Object });
+ ModelValidatorProviders.Providers.Add(mockValidatorProvider.Object);
+
+ // Act
+ htmlHelper.Validate("baz");
+
+ // Assert
+ Assert.NotNull(formContext.GetValidationMetadataForField("baz"));
+ Assert.Equal(expectedValidationRules, formContext.FieldValidators["baz"].ValidationRules.ToArray());
+ }
+ finally
+ {
+ ModelValidatorProviders.Providers.Clear();
+ foreach (var provider in originalProviders)
+ {
+ ModelValidatorProviders.Providers.Add(provider);
+ }
+ }
+ }
+
+ [Fact]
+ public void Validate_DoesNothingIfClientValidationIsNotEnabled()
+ {
+ // Arrange
+ HtmlHelper<ValidationModel> htmlHelper = MvcHelper.GetHtmlHelper(GetViewDataWithModelErrors());
+ htmlHelper.ViewContext.FormContext = new FormContext();
+ htmlHelper.ViewContext.ClientValidationEnabled = false;
+
+ // Act
+ htmlHelper.Validate("foo");
+
+ // Assert
+ Assert.Empty(htmlHelper.ViewContext.FormContext.FieldValidators);
+ }
+
+ [Fact]
+ public void Validate_DoesNothingIfUnobtrusiveJavaScriptIsEnabled()
+ {
+ // Arrange
+ HtmlHelper<ValidationModel> htmlHelper = MvcHelper.GetHtmlHelper(GetViewDataWithModelErrors());
+ htmlHelper.ViewContext.FormContext = new FormContext();
+ htmlHelper.ViewContext.ClientValidationEnabled = true;
+ htmlHelper.ViewContext.UnobtrusiveJavaScriptEnabled = true;
+
+ // Act
+ htmlHelper.Validate("foo");
+
+ // Assert
+ Assert.Empty(htmlHelper.ViewContext.FormContext.FieldValidators);
+ }
+
+ [Fact]
+ public void Validate_ThrowsIfModelNameIsNull()
+ {
+ // Arrange
+ HtmlHelper<ValidationModel> htmlHelper = MvcHelper.GetHtmlHelper(GetViewDataWithModelErrors());
+
+ // Act & assert
+ Assert.ThrowsArgumentNull(
+ delegate { htmlHelper.Validate((string)null /* modelName */); }, "modelName");
+ }
+
+ [Fact]
+ public void ValidateFor_AddsClientValidationMetadata()
+ {
+ var originalProviders = ModelValidatorProviders.Providers.ToArray();
+ ModelValidatorProviders.Providers.Clear();
+
+ try
+ {
+ // Arrange
+ HtmlHelper<ValidationModel> htmlHelper = MvcHelper.GetHtmlHelper(GetViewDataWithModelErrors());
+ FormContext formContext = new FormContext()
+ {
+ FormId = "form_id"
+ };
+ htmlHelper.ViewContext.ClientValidationEnabled = true;
+ htmlHelper.ViewContext.FormContext = formContext;
+
+ ModelClientValidationRule[] expectedValidationRules = new ModelClientValidationRule[]
+ {
+ new ModelClientValidationRule() { ValidationType = "ValidationRule1" },
+ new ModelClientValidationRule() { ValidationType = "ValidationRule2" }
+ };
+
+ Mock<ModelValidator> mockValidator = new Mock<ModelValidator>(ModelMetadata.FromStringExpression("", htmlHelper.ViewContext.ViewData), htmlHelper.ViewContext);
+ mockValidator.Setup(v => v.GetClientValidationRules())
+ .Returns(expectedValidationRules);
+ Mock<ModelValidatorProvider> mockValidatorProvider = new Mock<ModelValidatorProvider>();
+ mockValidatorProvider.Setup(vp => vp.GetValidators(It.IsAny<ModelMetadata>(), It.IsAny<ControllerContext>()))
+ .Returns(new[] { mockValidator.Object });
+ ModelValidatorProviders.Providers.Add(mockValidatorProvider.Object);
+
+ // Act
+ htmlHelper.ValidateFor(m => m.baz);
+
+ // Assert
+ Assert.NotNull(formContext.GetValidationMetadataForField("baz"));
+ Assert.Equal(expectedValidationRules, formContext.FieldValidators["baz"].ValidationRules.ToArray());
+ }
+ finally
+ {
+ ModelValidatorProviders.Providers.Clear();
+ foreach (var provider in originalProviders)
+ {
+ ModelValidatorProviders.Providers.Add(provider);
+ }
+ }
+ }
+
+ // ValidationMessage
+
+ [Fact]
+ public void ValidationMessageAllowsEmptyModelName()
+ {
+ // Arrange
+ ViewDataDictionary vdd = new ViewDataDictionary();
+ vdd.ModelState.AddModelError("", "some error text");
+ HtmlHelper htmlHelper = MvcHelper.GetHtmlHelper(vdd);
+
+ // Act
+ MvcHtmlString html = htmlHelper.ValidationMessage("");
+
+ // Assert
+ Assert.Equal(@"<span class=""field-validation-error"">some error text</span>", html.ToHtmlString());
+ }
+
+ [Fact]
+ public void ValidationMessageReturnsNullForNullModelState()
+ {
+ // Arrange
+ HtmlHelper htmlHelper = MvcHelper.GetHtmlHelper(GetViewDataWithNullModelState());
+
+ // Act
+ MvcHtmlString html = htmlHelper.ValidationMessage("foo");
+
+ // Assert
+ Assert.Null(html);
+ }
+
+ [Fact]
+ public void ValidationMessageReturnsFirstErrorWithMessage()
+ {
+ // Arrange
+ HtmlHelper htmlHelper = MvcHelper.GetHtmlHelper(GetViewDataWithModelErrors());
+
+ // Act
+ MvcHtmlString html = htmlHelper.ValidationMessage("foo");
+
+ // Assert
+ Assert.Equal(@"<span class=""field-validation-error"">foo error &lt;1&gt;</span>", html.ToHtmlString());
+ }
+
+ [Fact]
+ public void ValidationMessageReturnsGenericMessageInsteadOfExceptionText()
+ {
+ // Arrange
+ HtmlHelper htmlHelper = MvcHelper.GetHtmlHelper(GetViewDataWithModelErrors());
+
+ // Act
+ MvcHtmlString html = htmlHelper.ValidationMessage("quux");
+
+ // Assert
+ Assert.Equal(@"<span class=""field-validation-error"">The value &#39;quuxValue&#39; is invalid.</span>", html.ToHtmlString());
+ }
+
+ [Fact]
+ public void ValidationMessageReturnsNullForInvalidName()
+ {
+ // Arrange
+ HtmlHelper htmlHelper = MvcHelper.GetHtmlHelper(GetViewDataWithModelErrors());
+
+ // Act
+ MvcHtmlString html = htmlHelper.ValidationMessage("boo");
+
+ // Assert
+ Assert.Null(html);
+ }
+
+ [Fact]
+ public void ValidationMessageReturnsWithObjectAttributes()
+ {
+ // Arrange
+ HtmlHelper htmlHelper = MvcHelper.GetHtmlHelper(GetViewDataWithModelErrors());
+
+ // Act
+ MvcHtmlString html = htmlHelper.ValidationMessage("foo", new { bar = "bar" });
+
+ // Assert
+ Assert.Equal(@"<span bar=""bar"" class=""field-validation-error"">foo error &lt;1&gt;</span>", html.ToHtmlString());
+ }
+
+ [Fact]
+ public void ValidationMessageReturnsWithObjectAttributesWithUnderscores()
+ {
+ // Arrange
+ HtmlHelper htmlHelper = MvcHelper.GetHtmlHelper(GetViewDataWithModelErrors());
+
+ // Act
+ MvcHtmlString html = htmlHelper.ValidationMessage("foo", new { foo_bar = "bar" });
+
+ // Assert
+ Assert.Equal(@"<span class=""field-validation-error"" foo-bar=""bar"">foo error &lt;1&gt;</span>", html.ToHtmlString());
+ }
+
+ [Fact]
+ public void ValidationMessageReturnsWithCustomMessage()
+ {
+ // Arrange
+ HtmlHelper htmlHelper = MvcHelper.GetHtmlHelper(GetViewDataWithModelErrors());
+
+ // Act
+ MvcHtmlString html = htmlHelper.ValidationMessage("foo", "bar error");
+
+ // Assert
+ Assert.Equal(@"<span class=""field-validation-error"">bar error</span>", html.ToHtmlString());
+ }
+
+ [Fact]
+ public void ValidationMessageReturnsWithCustomMessageAndObjectAttributes()
+ {
+ // Arrange
+ HtmlHelper htmlHelper = MvcHelper.GetHtmlHelper(GetViewDataWithModelErrors());
+
+ // Act
+ MvcHtmlString html = htmlHelper.ValidationMessage("foo", "bar error", new { baz = "baz" });
+
+ // Assert
+ Assert.Equal(@"<span baz=""baz"" class=""field-validation-error"">bar error</span>", html.ToHtmlString());
+ }
+
+ [Fact]
+ public void ValidationMessageReturnsWithCustomMessageAndObjectAttributesWithUnderscores()
+ {
+ // Arrange
+ HtmlHelper htmlHelper = MvcHelper.GetHtmlHelper(GetViewDataWithModelErrors());
+
+ // Act
+ MvcHtmlString html = htmlHelper.ValidationMessage("foo", "bar error", new { foo_baz = "baz" });
+
+ // Assert
+ Assert.Equal(@"<span class=""field-validation-error"" foo-baz=""baz"">bar error</span>", html.ToHtmlString());
+ }
+
+ [Fact]
+ public void ValidationMessageThrowsIfModelNameIsNull()
+ {
+ // Arrange
+ HtmlHelper htmlHelper = MvcHelper.GetHtmlHelper();
+
+ // Act & Assert
+ Assert.ThrowsArgumentNull(
+ delegate { htmlHelper.ValidationMessage(null); }, "modelName");
+ }
+
+ [Fact]
+ public void ValidationMessageWithClientValidation_DefaultMessage_Valid()
+ {
+ var originalProviders = ModelValidatorProviders.Providers.ToArray();
+ ModelValidatorProviders.Providers.Clear();
+
+ try
+ {
+ // Arrange
+ HtmlHelper htmlHelper = MvcHelper.GetHtmlHelper(GetViewDataWithModelErrors());
+ FormContext formContext = new FormContext();
+ htmlHelper.ViewContext.ClientValidationEnabled = true;
+ htmlHelper.ViewContext.FormContext = formContext;
+
+ ModelClientValidationRule[] expectedValidationRules = new ModelClientValidationRule[]
+ {
+ new ModelClientValidationRule() { ValidationType = "ValidationRule1" },
+ new ModelClientValidationRule() { ValidationType = "ValidationRule2" }
+ };
+
+ Mock<ModelValidator> mockValidator = new Mock<ModelValidator>(ModelMetadata.FromStringExpression("", htmlHelper.ViewContext.ViewData), htmlHelper.ViewContext);
+ mockValidator.Setup(v => v.GetClientValidationRules())
+ .Returns(expectedValidationRules);
+ Mock<ModelValidatorProvider> mockValidatorProvider = new Mock<ModelValidatorProvider>();
+ mockValidatorProvider.Setup(vp => vp.GetValidators(It.IsAny<ModelMetadata>(), It.IsAny<ControllerContext>()))
+ .Returns(new[] { mockValidator.Object });
+ ModelValidatorProviders.Providers.Add(mockValidatorProvider.Object);
+
+ // Act
+ MvcHtmlString html = htmlHelper.ValidationMessage("baz"); // 'baz' is valid
+
+ // Assert
+ Assert.Equal(@"<span class=""field-validation-valid"" id=""baz_validationMessage""></span>", html.ToHtmlString());
+ Assert.NotNull(formContext.GetValidationMetadataForField("baz"));
+ Assert.Equal("baz_validationMessage", formContext.FieldValidators["baz"].ValidationMessageId);
+ Assert.True(formContext.FieldValidators["baz"].ReplaceValidationMessageContents);
+ Assert.Equal(expectedValidationRules, formContext.FieldValidators["baz"].ValidationRules.ToArray());
+ }
+ finally
+ {
+ ModelValidatorProviders.Providers.Clear();
+ foreach (var provider in originalProviders)
+ {
+ ModelValidatorProviders.Providers.Add(provider);
+ }
+ }
+ }
+
+ [Fact]
+ public void ValidationMessageWithClientValidation_DefaultMessage_Valid_Unobtrusive()
+ {
+ var originalProviders = ModelValidatorProviders.Providers.ToArray();
+ ModelValidatorProviders.Providers.Clear();
+
+ try
+ {
+ // Arrange
+ HtmlHelper htmlHelper = MvcHelper.GetHtmlHelper(GetViewDataWithModelErrors());
+ FormContext formContext = new FormContext();
+ htmlHelper.ViewContext.ClientValidationEnabled = true;
+ htmlHelper.ViewContext.UnobtrusiveJavaScriptEnabled = true;
+ htmlHelper.ViewContext.FormContext = formContext;
+
+ ModelClientValidationRule[] expectedValidationRules = new ModelClientValidationRule[]
+ {
+ new ModelClientValidationRule() { ValidationType = "ValidationRule1" },
+ new ModelClientValidationRule() { ValidationType = "ValidationRule2" }
+ };
+
+ Mock<ModelValidator> mockValidator = new Mock<ModelValidator>(ModelMetadata.FromStringExpression("", htmlHelper.ViewContext.ViewData), htmlHelper.ViewContext);
+ mockValidator.Setup(v => v.GetClientValidationRules())
+ .Returns(expectedValidationRules);
+ Mock<ModelValidatorProvider> mockValidatorProvider = new Mock<ModelValidatorProvider>();
+ mockValidatorProvider.Setup(vp => vp.GetValidators(It.IsAny<ModelMetadata>(), It.IsAny<ControllerContext>()))
+ .Returns(new[] { mockValidator.Object });
+ ModelValidatorProviders.Providers.Add(mockValidatorProvider.Object);
+
+ // Act
+ MvcHtmlString html = htmlHelper.ValidationMessage("baz"); // 'baz' is valid
+
+ // Assert
+ Assert.Equal(@"<span class=""field-validation-valid"" data-valmsg-for=""baz"" data-valmsg-replace=""true""></span>", html.ToHtmlString());
+ }
+ finally
+ {
+ ModelValidatorProviders.Providers.Clear();
+ foreach (var provider in originalProviders)
+ {
+ ModelValidatorProviders.Providers.Add(provider);
+ }
+ }
+ }
+
+ [Fact]
+ public void ValidationMessageWithClientValidation_ExplicitMessage_Valid()
+ {
+ var originalProviders = ModelValidatorProviders.Providers.ToArray();
+ ModelValidatorProviders.Providers.Clear();
+
+ try
+ {
+ // Arrange
+ HtmlHelper htmlHelper = MvcHelper.GetHtmlHelper(GetViewDataWithModelErrors());
+ FormContext formContext = new FormContext();
+ htmlHelper.ViewContext.ClientValidationEnabled = true;
+ htmlHelper.ViewContext.FormContext = formContext;
+
+ ModelClientValidationRule[] expectedValidationRules = new ModelClientValidationRule[]
+ {
+ new ModelClientValidationRule() { ValidationType = "ValidationRule1" },
+ new ModelClientValidationRule() { ValidationType = "ValidationRule2" }
+ };
+
+ Mock<ModelValidator> mockValidator = new Mock<ModelValidator>(ModelMetadata.FromStringExpression("", htmlHelper.ViewContext.ViewData), htmlHelper.ViewContext);
+ mockValidator.Setup(v => v.GetClientValidationRules())
+ .Returns(expectedValidationRules);
+ Mock<ModelValidatorProvider> mockValidatorProvider = new Mock<ModelValidatorProvider>();
+ mockValidatorProvider.Setup(vp => vp.GetValidators(It.IsAny<ModelMetadata>(), It.IsAny<ControllerContext>()))
+ .Returns(new[] { mockValidator.Object });
+ ModelValidatorProviders.Providers.Add(mockValidatorProvider.Object);
+
+ // Act
+ MvcHtmlString html = htmlHelper.ValidationMessage("baz", "some explicit message"); // 'baz' is valid
+
+ // Assert
+ Assert.Equal(@"<span class=""field-validation-valid"" id=""baz_validationMessage"">some explicit message</span>", html.ToHtmlString());
+ Assert.NotNull(formContext.GetValidationMetadataForField("baz"));
+ Assert.Equal("baz_validationMessage", formContext.FieldValidators["baz"].ValidationMessageId);
+ Assert.False(formContext.FieldValidators["baz"].ReplaceValidationMessageContents);
+ Assert.Equal(expectedValidationRules, formContext.FieldValidators["baz"].ValidationRules.ToArray());
+ }
+ finally
+ {
+ ModelValidatorProviders.Providers.Clear();
+ foreach (var provider in originalProviders)
+ {
+ ModelValidatorProviders.Providers.Add(provider);
+ }
+ }
+ }
+
+ [Fact]
+ public void ValidationMessageWithClientValidation_ExplicitMessage_Valid_Unobtrusive()
+ {
+ var originalProviders = ModelValidatorProviders.Providers.ToArray();
+ ModelValidatorProviders.Providers.Clear();
+
+ try
+ {
+ // Arrange
+ HtmlHelper htmlHelper = MvcHelper.GetHtmlHelper(GetViewDataWithModelErrors());
+ FormContext formContext = new FormContext();
+ htmlHelper.ViewContext.ClientValidationEnabled = true;
+ htmlHelper.ViewContext.UnobtrusiveJavaScriptEnabled = true;
+ htmlHelper.ViewContext.FormContext = formContext;
+
+ ModelClientValidationRule[] expectedValidationRules = new ModelClientValidationRule[]
+ {
+ new ModelClientValidationRule() { ValidationType = "ValidationRule1" },
+ new ModelClientValidationRule() { ValidationType = "ValidationRule2" }
+ };
+
+ Mock<ModelValidator> mockValidator = new Mock<ModelValidator>(ModelMetadata.FromStringExpression("", htmlHelper.ViewContext.ViewData), htmlHelper.ViewContext);
+ mockValidator.Setup(v => v.GetClientValidationRules())
+ .Returns(expectedValidationRules);
+ Mock<ModelValidatorProvider> mockValidatorProvider = new Mock<ModelValidatorProvider>();
+ mockValidatorProvider.Setup(vp => vp.GetValidators(It.IsAny<ModelMetadata>(), It.IsAny<ControllerContext>()))
+ .Returns(new[] { mockValidator.Object });
+ ModelValidatorProviders.Providers.Add(mockValidatorProvider.Object);
+
+ // Act
+ MvcHtmlString html = htmlHelper.ValidationMessage("baz", "some explicit message"); // 'baz' is valid
+
+ // Assert
+ Assert.Equal(@"<span class=""field-validation-valid"" data-valmsg-for=""baz"" data-valmsg-replace=""false"">some explicit message</span>", html.ToHtmlString());
+ }
+ finally
+ {
+ ModelValidatorProviders.Providers.Clear();
+ foreach (var provider in originalProviders)
+ {
+ ModelValidatorProviders.Providers.Add(provider);
+ }
+ }
+ }
+
+ [Fact]
+ public void ValidationMessageWithModelStateAndNoErrors()
+ {
+ // Arrange
+ HtmlHelper htmlHelper = MvcHelper.GetHtmlHelper(GetViewDataWithModelErrors());
+
+ // Act
+ MvcHtmlString html = htmlHelper.ValidationMessage("baz");
+
+ // Assert
+ Assert.Null(html);
+ }
+
+ // ValidationMessageFor
+
+ [Fact]
+ public void ValidationMessageForThrowsIfExpressionIsNull()
+ {
+ // Arrange
+ HtmlHelper<object> htmlHelper = MvcHelper.GetHtmlHelper();
+
+ // Act & Assert
+ Assert.ThrowsArgumentNull(
+ () => htmlHelper.ValidationMessageFor<object, object>(null),
+ "expression"
+ );
+ }
+
+ [Fact]
+ public void ValidationMessageForReturnsNullIfModelStateIsNull()
+ {
+ // Arrange
+ HtmlHelper<ValidationModel> htmlHelper = MvcHelper.GetHtmlHelper(GetViewDataWithNullModelState());
+
+ // Act
+ MvcHtmlString html = htmlHelper.ValidationMessageFor(m => m.foo);
+
+ // Assert
+ Assert.Null(html);
+ }
+
+ [Fact]
+ public void ValidationMessageForReturnsFirstErrorWithErrorMessage()
+ {
+ // Arrange
+ HtmlHelper<ValidationModel> htmlHelper = MvcHelper.GetHtmlHelper(GetViewDataWithModelErrors());
+
+ // Act
+ MvcHtmlString html = htmlHelper.ValidationMessageFor(m => m.foo);
+
+ // Assert
+ Assert.Equal(@"<span class=""field-validation-error"">foo error &lt;1&gt;</span>", html.ToHtmlString());
+ }
+
+ [Fact]
+ public void ValidationMessageForReturnsGenericMessageInsteadOfExceptionText()
+ {
+ // Arrange
+ HtmlHelper<ValidationModel> htmlHelper = MvcHelper.GetHtmlHelper(GetViewDataWithModelErrors());
+
+ // Act
+ MvcHtmlString html = htmlHelper.ValidationMessageFor(m => m.quux);
+
+ // Assert
+ Assert.Equal(@"<span class=""field-validation-error"">The value &#39;quuxValue&#39; is invalid.</span>", html.ToHtmlString());
+ }
+
+ [Fact]
+ public void ValidationMessageForReturnsWithObjectAttributes()
+ {
+ // Arrange
+ HtmlHelper<ValidationModel> htmlHelper = MvcHelper.GetHtmlHelper(GetViewDataWithModelErrors());
+
+ // Act
+ MvcHtmlString html = htmlHelper.ValidationMessageFor(m => m.foo, null /* validationMessage */, new { bar = "bar" });
+
+ // Assert
+ Assert.Equal(@"<span bar=""bar"" class=""field-validation-error"">foo error &lt;1&gt;</span>", html.ToHtmlString());
+ }
+
+ [Fact]
+ public void ValidationMessageForReturnsWithObjectAttributesWithUnderscores()
+ {
+ // Arrange
+ HtmlHelper<ValidationModel> htmlHelper = MvcHelper.GetHtmlHelper(GetViewDataWithModelErrors());
+
+ // Act
+ MvcHtmlString html = htmlHelper.ValidationMessageFor(m => m.foo, null /* validationMessage */, new { foo_bar = "bar" });
+
+ // Assert
+ Assert.Equal(@"<span class=""field-validation-error"" foo-bar=""bar"">foo error &lt;1&gt;</span>", html.ToHtmlString());
+ }
+
+ [Fact]
+ public void ValidationMessageForReturnsWithCustomMessage()
+ {
+ // Arrange
+ HtmlHelper<ValidationModel> htmlHelper = MvcHelper.GetHtmlHelper(GetViewDataWithModelErrors());
+
+ // Act
+ MvcHtmlString html = htmlHelper.ValidationMessageFor(m => m.foo, "bar error");
+
+ // Assert
+ Assert.Equal(@"<span class=""field-validation-error"">bar error</span>", html.ToHtmlString());
+ }
+
+ [Fact]
+ public void ValidationMessageForReturnsWithCustomMessageAndObjectAttributes()
+ {
+ // Arrange
+ HtmlHelper<ValidationModel> htmlHelper = MvcHelper.GetHtmlHelper(GetViewDataWithModelErrors());
+
+ // Act
+ MvcHtmlString html = htmlHelper.ValidationMessageFor(m => m.foo, "bar error", new { baz = "baz" });
+
+ // Assert
+ Assert.Equal(@"<span baz=""baz"" class=""field-validation-error"">bar error</span>", html.ToHtmlString());
+ }
+
+ [Fact]
+ public void ValidationMessageForWithClientValidation()
+ {
+ var originalProviders = ModelValidatorProviders.Providers.ToArray();
+ ModelValidatorProviders.Providers.Clear();
+
+ try
+ {
+ // Arrange
+ HtmlHelper<ValidationModel> htmlHelper = MvcHelper.GetHtmlHelper(GetViewDataWithModelErrors());
+ FormContext formContext = new FormContext();
+ htmlHelper.ViewContext.ClientValidationEnabled = true;
+ htmlHelper.ViewContext.FormContext = formContext;
+
+ ModelClientValidationRule[] expectedValidationRules = new ModelClientValidationRule[]
+ {
+ new ModelClientValidationRule() { ValidationType = "ValidationRule1" },
+ new ModelClientValidationRule() { ValidationType = "ValidationRule2" }
+ };
+
+ Mock<ModelValidator> mockValidator = new Mock<ModelValidator>(ModelMetadata.FromStringExpression("", htmlHelper.ViewContext.ViewData), htmlHelper.ViewContext);
+ mockValidator.Setup(v => v.GetClientValidationRules())
+ .Returns(expectedValidationRules);
+ Mock<ModelValidatorProvider> mockValidatorProvider = new Mock<ModelValidatorProvider>();
+ mockValidatorProvider.Setup(vp => vp.GetValidators(It.IsAny<ModelMetadata>(), It.IsAny<ControllerContext>()))
+ .Returns(new[] { mockValidator.Object });
+ ModelValidatorProviders.Providers.Add(mockValidatorProvider.Object);
+
+ // Act
+ MvcHtmlString html = htmlHelper.ValidationMessageFor(m => m.baz);
+
+ // Assert
+ Assert.Equal(@"<span class=""field-validation-valid"" id=""baz_validationMessage""></span>", html.ToHtmlString());
+ Assert.NotNull(formContext.GetValidationMetadataForField("baz"));
+ Assert.Equal("baz_validationMessage", formContext.FieldValidators["baz"].ValidationMessageId);
+ Assert.Equal(expectedValidationRules, formContext.FieldValidators["baz"].ValidationRules.ToArray());
+ }
+ finally
+ {
+ ModelValidatorProviders.Providers.Clear();
+ foreach (var provider in originalProviders)
+ {
+ ModelValidatorProviders.Providers.Add(provider);
+ }
+ }
+ }
+
+ [Fact]
+ public void ValidationMessageForWithClientValidation_Unobtrusive()
+ {
+ var originalProviders = ModelValidatorProviders.Providers.ToArray();
+ ModelValidatorProviders.Providers.Clear();
+
+ try
+ {
+ // Arrange
+ HtmlHelper<ValidationModel> htmlHelper = MvcHelper.GetHtmlHelper(GetViewDataWithModelErrors());
+ FormContext formContext = new FormContext();
+ htmlHelper.ViewContext.ClientValidationEnabled = true;
+ htmlHelper.ViewContext.UnobtrusiveJavaScriptEnabled = true;
+ htmlHelper.ViewContext.FormContext = formContext;
+
+ ModelClientValidationRule[] expectedValidationRules = new ModelClientValidationRule[]
+ {
+ new ModelClientValidationRule() { ValidationType = "ValidationRule1" },
+ new ModelClientValidationRule() { ValidationType = "ValidationRule2" }
+ };
+
+ Mock<ModelValidator> mockValidator = new Mock<ModelValidator>(ModelMetadata.FromStringExpression("", htmlHelper.ViewContext.ViewData), htmlHelper.ViewContext);
+ mockValidator.Setup(v => v.GetClientValidationRules())
+ .Returns(expectedValidationRules);
+ Mock<ModelValidatorProvider> mockValidatorProvider = new Mock<ModelValidatorProvider>();
+ mockValidatorProvider.Setup(vp => vp.GetValidators(It.IsAny<ModelMetadata>(), It.IsAny<ControllerContext>()))
+ .Returns(new[] { mockValidator.Object });
+ ModelValidatorProviders.Providers.Add(mockValidatorProvider.Object);
+
+ // Act
+ MvcHtmlString html = htmlHelper.ValidationMessageFor(m => m.baz);
+
+ // Assert
+ Assert.Equal(@"<span class=""field-validation-valid"" data-valmsg-for=""baz"" data-valmsg-replace=""true""></span>", html.ToHtmlString());
+ }
+ finally
+ {
+ ModelValidatorProviders.Providers.Clear();
+ foreach (var provider in originalProviders)
+ {
+ ModelValidatorProviders.Providers.Add(provider);
+ }
+ }
+ }
+
+ [Fact]
+ public void ValidationMessageForWithModelStateAndNoErrors()
+ {
+ // Arrange
+ HtmlHelper<ValidationModel> htmlHelper = MvcHelper.GetHtmlHelper(GetViewDataWithModelErrors());
+
+ // Act
+ MvcHtmlString html = htmlHelper.ValidationMessageFor(m => m.baz);
+
+ // Assert
+ Assert.Null(html);
+ }
+
+ // ValidationSummary
+
+ [Fact]
+ public void ValidationSummary()
+ {
+ // Arrange
+ HtmlHelper htmlHelper = MvcHelper.GetHtmlHelper(GetViewDataWithModelErrors());
+
+ // Act
+ MvcHtmlString html = htmlHelper.ValidationSummary();
+
+ // Assert
+ Assert.Equal(@"<div class=""validation-summary-errors""><ul><li>foo error &lt;1&gt;</li>
+<li>foo error 2</li>
+<li>bar error &lt;1&gt;</li>
+<li>bar error 2</li>
+</ul></div>"
+ , html.ToHtmlString());
+ }
+
+ [Fact]
+ public void ValidationSummaryAddsIdIfClientValidationEnabled()
+ {
+ // Arrange
+ HtmlHelper htmlHelper = MvcHelper.GetHtmlHelper(GetViewDataWithModelErrors());
+ htmlHelper.ViewContext.FormContext = new FormContext();
+ htmlHelper.ViewContext.ClientValidationEnabled = true;
+
+ // Act
+ MvcHtmlString html = htmlHelper.ValidationSummary();
+
+ // Assert
+ Assert.Equal(@"<div class=""validation-summary-errors"" id=""validationSummary""><ul><li>foo error &lt;1&gt;</li>
+<li>foo error 2</li>
+<li>bar error &lt;1&gt;</li>
+<li>bar error 2</li>
+</ul></div>"
+ , html.ToHtmlString());
+ Assert.Equal("validationSummary", htmlHelper.ViewContext.FormContext.ValidationSummaryId);
+ }
+
+ [Fact]
+ public void ValidationSummaryDoesNotAddIdIfUnobtrusiveJavaScriptEnabled()
+ {
+ // Arrange
+ HtmlHelper htmlHelper = MvcHelper.GetHtmlHelper(GetViewDataWithModelErrors());
+ htmlHelper.ViewContext.FormContext = new FormContext();
+ htmlHelper.ViewContext.ClientValidationEnabled = true;
+ htmlHelper.ViewContext.UnobtrusiveJavaScriptEnabled = true;
+
+ // Act
+ MvcHtmlString html = htmlHelper.ValidationSummary();
+
+ // Assert
+ Assert.Equal(@"<div class=""validation-summary-errors"" data-valmsg-summary=""true""><ul><li>foo error &lt;1&gt;</li>
+<li>foo error 2</li>
+<li>bar error &lt;1&gt;</li>
+<li>bar error 2</li>
+</ul></div>", html.ToHtmlString());
+ }
+
+ [Fact]
+ public void ValidationSummaryWithDictionary()
+ {
+ // Arrange
+ HtmlHelper htmlHelper = MvcHelper.GetHtmlHelper(GetViewDataWithModelErrors());
+ RouteValueDictionary htmlAttributes = new RouteValueDictionary();
+ htmlAttributes["class"] = "my-class";
+
+ // Act
+ MvcHtmlString html = htmlHelper.ValidationSummary(null /* message */, htmlAttributes);
+
+ // Assert
+ Assert.Equal(@"<div class=""validation-summary-errors my-class""><ul><li>foo error &lt;1&gt;</li>
+<li>foo error 2</li>
+<li>bar error &lt;1&gt;</li>
+<li>bar error 2</li>
+</ul></div>"
+ , html.ToHtmlString());
+ }
+
+ [Fact]
+ public void ValidationSummaryWithDictionaryAndMessage()
+ {
+ // Arrange
+ HtmlHelper htmlHelper = MvcHelper.GetHtmlHelper(GetViewDataWithModelErrors());
+ RouteValueDictionary htmlAttributes = new RouteValueDictionary();
+ htmlAttributes["class"] = "my-class";
+
+ // Act
+ MvcHtmlString html = htmlHelper.ValidationSummary("This is my message.", htmlAttributes);
+
+ // Assert
+ Assert.Equal(@"<div class=""validation-summary-errors my-class""><span>This is my message.</span>
+<ul><li>foo error &lt;1&gt;</li>
+<li>foo error 2</li>
+<li>bar error &lt;1&gt;</li>
+<li>bar error 2</li>
+</ul></div>"
+ , html.ToHtmlString());
+ }
+
+ [Fact]
+ public void ValidationSummaryWithNoErrors_ReturnsNullIfClientValidationDisabled()
+ {
+ // Arrange
+ HtmlHelper htmlHelper = MvcHelper.GetHtmlHelper(new ViewDataDictionary());
+
+ // Act
+ MvcHtmlString html = htmlHelper.ValidationSummary();
+
+ // Assert
+ Assert.Null(html);
+ }
+
+ [Fact]
+ public void ValidationSummaryWithNoErrors_EmptyUlIfClientValidationEnabled()
+ {
+ // Arrange
+ HtmlHelper htmlHelper = MvcHelper.GetHtmlHelper(new ViewDataDictionary());
+ htmlHelper.ViewContext.ClientValidationEnabled = true;
+ htmlHelper.ViewContext.FormContext = new FormContext();
+
+ // Act
+ MvcHtmlString html = htmlHelper.ValidationSummary();
+
+ // Assert
+ Assert.Equal(@"<div class=""validation-summary-valid"" id=""validationSummary""><ul><li style=""display:none""></li>
+</ul></div>"
+ , html.ToHtmlString());
+ }
+
+ [Fact]
+ public void ValidationSummaryWithObjectAttributes()
+ {
+ // Arrange
+ HtmlHelper htmlHelper = MvcHelper.GetHtmlHelper(GetViewDataWithModelErrors());
+
+ // Act
+ MvcHtmlString html = htmlHelper.ValidationSummary(null /* message */, new { baz = "baz" });
+
+ // Assert
+ Assert.Equal(@"<div baz=""baz"" class=""validation-summary-errors""><ul><li>foo error &lt;1&gt;</li>
+<li>foo error 2</li>
+<li>bar error &lt;1&gt;</li>
+<li>bar error 2</li>
+</ul></div>"
+ , html.ToHtmlString());
+ }
+
+ [Fact]
+ public void ValidationSummaryWithObjectAttributesWithUnderscores()
+ {
+ // Arrange
+ HtmlHelper htmlHelper = MvcHelper.GetHtmlHelper(GetViewDataWithModelErrors());
+
+ // Act
+ MvcHtmlString html = htmlHelper.ValidationSummary(null /* message */, new { foo_baz = "baz" });
+
+ // Assert
+ Assert.Equal(@"<div class=""validation-summary-errors"" foo-baz=""baz""><ul><li>foo error &lt;1&gt;</li>
+<li>foo error 2</li>
+<li>bar error &lt;1&gt;</li>
+<li>bar error 2</li>
+</ul></div>"
+ , html.ToHtmlString());
+ }
+
+ [Fact]
+ public void ValidationSummaryWithObjectAttributesAndMessage()
+ {
+ // Arrange
+ HtmlHelper htmlHelper = MvcHelper.GetHtmlHelper(GetViewDataWithModelErrors());
+
+ // Act
+ MvcHtmlString html = htmlHelper.ValidationSummary("This is my message.", new { baz = "baz" });
+
+ // Assert
+ Assert.Equal(@"<div baz=""baz"" class=""validation-summary-errors""><span>This is my message.</span>
+<ul><li>foo error &lt;1&gt;</li>
+<li>foo error 2</li>
+<li>bar error &lt;1&gt;</li>
+<li>bar error 2</li>
+</ul></div>"
+ , html.ToHtmlString());
+ }
+
+ [Fact]
+ public void ValidationSummaryWithNoModelErrors()
+ {
+ // Arrange
+ HtmlHelper htmlHelper = MvcHelper.GetHtmlHelper(GetViewDataWithModelErrors());
+
+ // Act
+ MvcHtmlString html = htmlHelper.ValidationSummary(true /* excludePropertyErrors */, "This is my message.");
+
+ // Assert
+ Assert.Equal(@"<div class=""validation-summary-errors""><span>This is my message.</span>
+<ul><li style=""display:none""></li>
+</ul></div>"
+ , html.ToHtmlString());
+ }
+
+ [Fact]
+ public void ValidationSummaryWithOnlyModelErrors()
+ {
+ // Arrange
+ HtmlHelper htmlHelper = MvcHelper.GetHtmlHelper(GetViewDataWithModelAndPropertyErrors());
+
+ // Act
+ MvcHtmlString html = htmlHelper.ValidationSummary(true /* excludePropertyErrors */, "This is my message.");
+
+ // Assert
+ Assert.Equal(@"<div class=""validation-summary-errors""><span>This is my message.</span>
+<ul><li>Something is wrong.</li>
+<li>Something else is also wrong.</li>
+</ul></div>"
+ , html.ToHtmlString());
+ }
+
+ [Fact]
+ public void ValidationSummaryWithOnlyModelErrorsAndPrefix()
+ {
+ // Arrange
+ HtmlHelper htmlHelper = MvcHelper.GetHtmlHelper(GetViewDataWithModelErrors("MyPrefix"));
+
+ // Act
+ MvcHtmlString html = htmlHelper.ValidationSummary(true /* excludePropertyErrors */, "This is my message.");
+
+ // Assert
+ Assert.Equal(@"<div class=""validation-summary-errors""><span>This is my message.</span>
+<ul><li style=""display:none""></li>
+</ul></div>"
+ , html.ToHtmlString());
+ }
+
+ [Fact]
+ public void ValidationMessageWithPrefix()
+ {
+ // Arrange
+ HtmlHelper htmlHelper = MvcHelper.GetHtmlHelper(GetViewDataWithModelErrors("MyPrefix"));
+
+ // Act
+ MvcHtmlString html = htmlHelper.ValidationMessage("foo");
+
+ // Assert
+ Assert.Equal(@"<span class=""field-validation-error"">foo error &lt;1&gt;</span>", html.ToHtmlString());
+ }
+
+ [Fact]
+ public void ValidationErrorOrdering()
+ {
+ // Arrange
+ HtmlHelper htmlHelper = MvcHelper.GetHtmlHelper(GetViewDataWithModelWithDisplayOrderErrors());
+
+ // Act
+ MvcHtmlString html = htmlHelper.ValidationSummary();
+
+ // Assert
+ Assert.Equal(@"<div class=""validation-summary-errors""><ul><li>Error 1</li>
+<li>Error 2</li>
+<li>Error 3</li>
+<li>Error 4</li>
+</ul></div>", html.ToHtmlString());
+
+ }
+
+ private class ValidationModel
+ {
+ public string foo { get; set; }
+ public string bar { get; set; }
+ public string baz { get; set; }
+ public string quux { get; set; }
+ }
+
+ public class ModelWithOrdering
+ {
+ [Required]
+ [Display(Order = 2)]
+ public int Second { get; set; }
+
+ [Required]
+ [Display(Order = 1)]
+ public string First { get; set; }
+
+ [Required]
+ [Display(Order = 4)]
+ public string Fourth { get; set; }
+
+ [Required]
+ [Display(Order = 3)]
+ public string Third { get; set; }
+ }
+
+ private static ViewDataDictionary<ValidationModel> GetViewDataWithNullModelState()
+ {
+ ViewDataDictionary<ValidationModel> viewData = new ViewDataDictionary<ValidationModel>();
+ viewData.ModelState["foo"] = null;
+ return viewData;
+ }
+
+ private static ViewDataDictionary<ValidationModel> GetViewDataWithModelErrors()
+ {
+ ViewDataDictionary<ValidationModel> viewData = new ViewDataDictionary<ValidationModel>();
+ ModelState modelStateFoo = new ModelState();
+ ModelState modelStateBar = new ModelState();
+ ModelState modelStateBaz = new ModelState();
+
+ modelStateFoo.Errors.Add(new ModelError(new InvalidOperationException("foo error from exception")));
+ modelStateFoo.Errors.Add(new ModelError("foo error <1>"));
+ modelStateFoo.Errors.Add(new ModelError("foo error 2"));
+ modelStateBar.Errors.Add(new ModelError("bar error <1>"));
+ modelStateBar.Errors.Add(new ModelError("bar error 2"));
+
+ viewData.ModelState["foo"] = modelStateFoo;
+ viewData.ModelState["bar"] = modelStateBar;
+ viewData.ModelState["baz"] = modelStateBaz;
+
+ viewData.ModelState.SetModelValue("quux", new ValueProviderResult(null, "quuxValue", null));
+ viewData.ModelState.AddModelError("quux", new InvalidOperationException("Some error text."));
+ return viewData;
+ }
+
+ private static ViewDataDictionary<ValidationModel> GetViewDataWithModelAndPropertyErrors()
+ {
+ ViewDataDictionary<ValidationModel> viewData = new ViewDataDictionary<ValidationModel>();
+ ModelState modelStateFoo = new ModelState();
+ ModelState modelStateBar = new ModelState();
+ ModelState modelStateBaz = new ModelState();
+ modelStateFoo.Errors.Add(new ModelError("foo error <1>"));
+ modelStateFoo.Errors.Add(new ModelError("foo error 2"));
+ modelStateBar.Errors.Add(new ModelError("bar error <1>"));
+ modelStateBar.Errors.Add(new ModelError("bar error 2"));
+ viewData.ModelState["foo"] = modelStateFoo;
+ viewData.ModelState["bar"] = modelStateBar;
+ viewData.ModelState["baz"] = modelStateBaz;
+ viewData.ModelState.SetModelValue("quux", new ValueProviderResult(null, "quuxValue", null));
+ viewData.ModelState.AddModelError("quux", new InvalidOperationException("Some error text."));
+ viewData.ModelState.AddModelError(String.Empty, "Something is wrong.");
+ viewData.ModelState.AddModelError(String.Empty, "Something else is also wrong.");
+ return viewData;
+ }
+
+ private static ViewDataDictionary<ValidationModel> GetViewDataWithModelErrors(string prefix)
+ {
+ ViewDataDictionary<ValidationModel> viewData = new ViewDataDictionary<ValidationModel>();
+ viewData.TemplateInfo.HtmlFieldPrefix = prefix;
+ ModelState modelStateFoo = new ModelState();
+ ModelState modelStateBar = new ModelState();
+ ModelState modelStateBaz = new ModelState();
+ modelStateFoo.Errors.Add(new ModelError("foo error <1>"));
+ modelStateFoo.Errors.Add(new ModelError("foo error 2"));
+ modelStateBar.Errors.Add(new ModelError("bar error <1>"));
+ modelStateBar.Errors.Add(new ModelError("bar error 2"));
+ viewData.ModelState[prefix + ".foo"] = modelStateFoo;
+ viewData.ModelState[prefix + ".bar"] = modelStateBar;
+ viewData.ModelState[prefix + ".baz"] = modelStateBaz;
+ viewData.ModelState.SetModelValue(prefix + ".quux", new ValueProviderResult(null, "quuxValue", null));
+ viewData.ModelState.AddModelError(prefix + ".quux", new InvalidOperationException("Some error text."));
+ return viewData;
+ }
+
+
+ private static ViewDataDictionary<ModelWithOrdering> GetViewDataWithModelWithDisplayOrderErrors()
+ {
+ ViewDataDictionary<ModelWithOrdering> viewData = new ViewDataDictionary<ModelWithOrdering>();
+
+ var model = new ModelWithOrdering();
+
+ // Error names for each property on ModelWithOrdering.
+ viewData.ModelState.AddModelError("First", "Error 1");
+ viewData.ModelState.AddModelError("Second", "Error 2");
+ viewData.ModelState.AddModelError("Third", "Error 3");
+ viewData.ModelState.AddModelError("Fourth", "Error 4");
+
+ return viewData;
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Html/Test/ValueExtensionsTest.cs b/test/System.Web.Mvc.Test/Html/Test/ValueExtensionsTest.cs
new file mode 100644
index 00000000..6262f479
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Html/Test/ValueExtensionsTest.cs
@@ -0,0 +1,164 @@
+using System.Web.Mvc.Test;
+using Microsoft.Web.UnitTestUtil;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Mvc.Html.Test
+{
+ public class ValueExtensionsTest
+ {
+ // Value
+
+ [Fact]
+ public void ValueWithNullNameThrows()
+ {
+ // Arrange
+ HtmlHelper<FooBarModel> helper = MvcHelper.GetHtmlHelper(GetValueViewData());
+
+ // Act & Assert
+ Assert.ThrowsArgumentNull(
+ () => helper.Value(name: null),
+ "name"
+ );
+ }
+
+ [Fact]
+ public void ValueGetsValueFromViewData()
+ {
+ // Arrange
+ HtmlHelper<FooBarModel> helper = MvcHelper.GetHtmlHelper(GetValueViewData());
+
+ // Act
+ MvcHtmlString html = helper.Value("foo");
+
+ // Assert
+ Assert.Equal("ViewDataFoo", html.ToHtmlString());
+ }
+
+ // ValueFor
+
+ [Fact]
+ public void ValueForWithNullExpressionThrows()
+ {
+ // Arrange
+ HtmlHelper<FooBarModel> helper = MvcHelper.GetHtmlHelper(GetValueViewData());
+
+ // Act & Assert
+ Assert.ThrowsArgumentNull(
+ () => helper.ValueFor<FooBarModel, object>(expression: null),
+ "expression"
+ );
+ }
+
+ [Fact]
+ public void ValueForGetsExpressionValueFromViewDataModel()
+ {
+ // Arrange
+ HtmlHelper<FooBarModel> helper = MvcHelper.GetHtmlHelper(GetValueViewData());
+
+ // Act
+ MvcHtmlString html = helper.ValueFor(m => m.foo);
+
+ // Assert
+ Assert.Equal("ViewItemFoo", html.ToHtmlString());
+ }
+
+ // All Value Helpers including ValueForModel
+
+ [Fact]
+ public void ValueHelpersWithErrorsGetValueFromModelState()
+ {
+ // Arrange
+ ViewDataDictionary<FooBarModel> viewDataWithErrors = new ViewDataDictionary<FooBarModel> { { "foo", "ViewDataFoo" } };
+ viewDataWithErrors.Model = new FooBarModel() { foo = "ViewItemFoo", bar = "ViewItemBar" };
+ viewDataWithErrors.TemplateInfo.HtmlFieldPrefix = "FieldPrefix";
+
+ ModelState modelStateFoo = new ModelState();
+ modelStateFoo.Value = HtmlHelperTest.GetValueProviderResult(new string[] { "AttemptedValueFoo" }, "AttemptedValueFoo");
+ viewDataWithErrors.ModelState["FieldPrefix.foo"] = modelStateFoo;
+
+ ModelState modelStateFooBar = new ModelState();
+ modelStateFooBar.Value = HtmlHelperTest.GetValueProviderResult(new string[] { "AttemptedValueFooBar" }, "AttemptedValueFooBar");
+ viewDataWithErrors.ModelState["FieldPrefix"] = modelStateFooBar;
+
+ HtmlHelper<FooBarModel> helper = MvcHelper.GetHtmlHelper(viewDataWithErrors);
+
+ // Act & Assert
+ Assert.Equal("AttemptedValueFoo", helper.Value("foo").ToHtmlString());
+ Assert.Equal("AttemptedValueFoo", helper.ValueFor(m => m.foo).ToHtmlString());
+ Assert.Equal("AttemptedValueFooBar", helper.ValueForModel().ToHtmlString());
+ }
+
+ [Fact]
+ public void ValueHelpersWithEmptyNameConvertModelValueUsingCurrentCulture()
+ {
+ // Arrange
+ HtmlHelper<FooBarModel> helper = MvcHelper.GetHtmlHelper(GetValueViewData());
+ string expectedModelValue = "{ foo = ViewItemFoo, bar = 1900/01/01 12:00:00 AM }";
+
+ // Act & Assert
+ using (HtmlHelperTest.ReplaceCulture("en-ZA", "en-US"))
+ {
+ Assert.Equal(expectedModelValue, helper.Value(name: String.Empty).ToHtmlString());
+ Assert.Equal(expectedModelValue, helper.ValueFor(m => m).ToHtmlString());
+ Assert.Equal(expectedModelValue, helper.ValueForModel().ToHtmlString());
+ }
+ }
+
+ [Fact]
+ public void ValueHelpersFormatValue()
+ {
+ // Arrange
+ HtmlHelper<FooBarModel> helper = MvcHelper.GetHtmlHelper(GetValueViewData());
+ string expectedModelValue = "-{ foo = ViewItemFoo, bar = 1900/01/01 12:00:00 AM }-";
+ string expectedBarValue = "-1900/01/01 12:00:00 AM-";
+
+ // Act & Assert
+ using (HtmlHelperTest.ReplaceCulture("en-ZA", "en-US"))
+ {
+ Assert.Equal(expectedModelValue, helper.ValueForModel("-{0}-").ToHtmlString());
+ Assert.Equal(expectedBarValue, helper.Value("bar", "-{0}-").ToHtmlString());
+ Assert.Equal(expectedBarValue, helper.ValueFor(m => m.bar, "-{0}-").ToHtmlString());
+ }
+ }
+
+ [Fact]
+ public void ValueHelpersEncodeValue()
+ {
+ // Arrange
+ ViewDataDictionary<FooBarModel> viewData = new ViewDataDictionary<FooBarModel> { { "foo", @"ViewDataFoo <"">" } };
+ viewData.Model = new FooBarModel { foo = @"ViewItemFoo <"">" };
+
+ ModelState modelStateFoo = new ModelState();
+ modelStateFoo.Value = HtmlHelperTest.GetValueProviderResult(new string[] { @"AttemptedValueBar <"">" }, @"AttemptedValueBar <"">");
+ viewData.ModelState["bar"] = modelStateFoo;
+
+ HtmlHelper<FooBarModel> helper = MvcHelper.GetHtmlHelper(viewData);
+
+ // Act & Assert
+ Assert.Equal("&lt;{ foo = ViewItemFoo &lt;&quot;>, bar = (null) }", helper.ValueForModel("<{0}").ToHtmlString());
+ Assert.Equal("&lt;ViewDataFoo &lt;&quot;>", helper.Value("foo", "<{0}").ToHtmlString());
+ Assert.Equal("&lt;ViewItemFoo &lt;&quot;>", helper.ValueFor(m => m.foo, "<{0}").ToHtmlString());
+ Assert.Equal("AttemptedValueBar &lt;&quot;>", helper.ValueFor(m => m.bar).ToHtmlString());
+ }
+
+ private sealed class FooBarModel
+ {
+ public string foo { get; set; }
+ public object bar { get; set; }
+
+ public override string ToString()
+ {
+ return String.Format("{{ foo = {0}, bar = {1} }}", foo ?? "(null)", bar ?? "(null)");
+ }
+ }
+
+ private static ViewDataDictionary<FooBarModel> GetValueViewData()
+ {
+ ViewDataDictionary<FooBarModel> viewData = new ViewDataDictionary<FooBarModel> { { "foo", "ViewDataFoo" } };
+ viewData.Model = new FooBarModel { foo = "ViewItemFoo", bar = new DateTime(1900, 1, 1, 0, 0, 0) };
+
+ return viewData;
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Properties/AssemblyInfo.cs b/test/System.Web.Mvc.Test/Properties/AssemblyInfo.cs
new file mode 100644
index 00000000..8c114539
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Properties/AssemblyInfo.cs
@@ -0,0 +1,3 @@
+using System;
+
+[assembly: CLSCompliant(true)]
diff --git a/test/System.Web.Mvc.Test/Razor/Test/MvcCSharpRazorCodeGeneratorTest.cs b/test/System.Web.Mvc.Test/Razor/Test/MvcCSharpRazorCodeGeneratorTest.cs
new file mode 100644
index 00000000..0991c16b
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Razor/Test/MvcCSharpRazorCodeGeneratorTest.cs
@@ -0,0 +1,69 @@
+using System.Collections.Generic;
+using System.Web.Razor;
+using Moq;
+using Xunit;
+
+namespace System.Web.Mvc.Razor.Test
+{
+ public class MvcCSharpRazorCodeGeneratorTest
+ {
+ [Fact]
+ public void Constructor()
+ {
+ // Arrange
+ Mock<RazorEngineHost> mockHost = new Mock<RazorEngineHost>();
+
+ // Act
+ var generator = new MvcCSharpRazorCodeGenerator("FooClass", "Root.Namespace", "SomeSourceFile.cshtml", mockHost.Object);
+
+ // Assert
+ Assert.Equal("FooClass", generator.ClassName);
+ Assert.Equal("Root.Namespace", generator.RootNamespaceName);
+ Assert.Equal("SomeSourceFile.cshtml", generator.SourceFileName);
+ Assert.Same(mockHost.Object, generator.Host);
+ }
+
+ [Fact]
+ public void Constructor_DoesNotSetBaseTypeForNonMvcHost()
+ {
+ // Arrange
+ Mock<RazorEngineHost> mockHost = new Mock<RazorEngineHost>();
+ mockHost.SetupGet(h => h.NamespaceImports).Returns(new HashSet<string>());
+
+ // Act
+ var generator = new MvcCSharpRazorCodeGenerator("FooClass", "Root.Namespace", "SomeSourceFile.cshtml", mockHost.Object);
+
+ // Assert
+ Assert.Equal(0, generator.Context.GeneratedClass.BaseTypes.Count);
+ }
+
+ [Fact]
+ public void Constructor_DoesNotSetBaseTypeForSpecialPage()
+ {
+ // Arrange
+ Mock<MvcWebPageRazorHost> mockHost = new Mock<MvcWebPageRazorHost>("_viewStart.cshtml", "_viewStart.cshtml");
+ mockHost.SetupGet(h => h.NamespaceImports).Returns(new HashSet<string>());
+
+ // Act
+ var generator = new MvcCSharpRazorCodeGenerator("FooClass", "Root.Namespace", "_viewStart.cshtml", mockHost.Object);
+
+ // Assert
+ Assert.Equal(0, generator.Context.GeneratedClass.BaseTypes.Count);
+ }
+
+ [Fact]
+ public void Constructor_SetsBaseTypeForRegularPage()
+ {
+ // Arrange
+ Mock<MvcWebPageRazorHost> mockHost = new Mock<MvcWebPageRazorHost>("SomeSourceFile.cshtml", "SomeSourceFile.cshtml") { CallBase = true };
+ mockHost.SetupGet(h => h.NamespaceImports).Returns(new HashSet<string>());
+
+ // Act
+ var generator = new MvcCSharpRazorCodeGenerator("FooClass", "Root.Namespace", "SomeSourceFile.cshtml", mockHost.Object);
+
+ // Assert
+ Assert.Equal(1, generator.Context.GeneratedClass.BaseTypes.Count);
+ Assert.Equal("System.Web.Mvc.WebViewPage<dynamic>", generator.Context.GeneratedClass.BaseTypes[0].BaseType);
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Razor/Test/MvcCSharpRazorCodeParserTest.cs b/test/System.Web.Mvc.Test/Razor/Test/MvcCSharpRazorCodeParserTest.cs
new file mode 100644
index 00000000..280e1a50
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Razor/Test/MvcCSharpRazorCodeParserTest.cs
@@ -0,0 +1,284 @@
+using System.Collections.Generic;
+using System.Linq;
+using System.Web.Razor;
+using System.Web.Razor.Generator;
+using System.Web.Razor.Parser;
+using System.Web.Razor.Parser.SyntaxTree;
+using System.Web.Razor.Test.Framework;
+using System.Web.Razor.Text;
+using Xunit;
+
+namespace System.Web.Mvc.Razor.Test
+{
+ public class MvcCSharpRazorCodeParserTest
+ {
+ [Fact]
+ public void Constructor_AddsModelKeyword()
+ {
+ var parser = new TestMvcCSharpRazorCodeParser();
+
+ Assert.True(parser.HasDirective("model"));
+ }
+
+ [Fact]
+ public void ParseModelKeyword_HandlesSingleInstance()
+ {
+ // Arrange + Act
+ var document = "@model Foo";
+ var spans = ParseDocument(document);
+
+ // Assert
+ var factory = SpanFactory.CreateCsHtml();
+ var expectedSpans = new Span[]
+ {
+ factory.EmptyHtml(),
+ factory.CodeTransition(SyntaxConstants.TransitionString)
+ .Accepts(AcceptedCharacters.None),
+ factory.MetaCode("model ")
+ .Accepts(AcceptedCharacters.None),
+ factory.Code(" Foo")
+ .As(new SetModelTypeCodeGenerator("Foo", "{0}<{1}>"))
+ };
+ Assert.Equal(expectedSpans, spans.ToArray());
+ }
+
+ [Fact]
+ public void ParseModelKeyword_HandlesNullableTypes()
+ {
+ // Arrange + Act
+ var document = "@model Foo?\r\nBar";
+ var spans = ParseDocument(document);
+
+ // Assert
+ var factory = SpanFactory.CreateCsHtml();
+ var expectedSpans = new Span[]
+ {
+ factory.EmptyHtml(),
+ factory.CodeTransition(SyntaxConstants.TransitionString)
+ .Accepts(AcceptedCharacters.None),
+ factory.MetaCode("model ")
+ .Accepts(AcceptedCharacters.None),
+ factory.Code("Foo?\r\n")
+ .As(new SetModelTypeCodeGenerator("Foo?", "{0}<{1}>")),
+ factory.Markup("Bar")
+ .With(new MarkupCodeGenerator())
+ };
+ Assert.Equal(expectedSpans, spans.ToArray());
+ }
+
+ [Fact]
+ public void ParseModelKeyword_HandlesArrays()
+ {
+ // Arrange + Act
+ var document = "@model Foo[[]][]\r\nBar";
+ var spans = ParseDocument(document);
+
+ // Assert
+ var factory = SpanFactory.CreateCsHtml();
+ var expectedSpans = new Span[]
+ {
+ factory.EmptyHtml(),
+ factory.CodeTransition(SyntaxConstants.TransitionString)
+ .Accepts(AcceptedCharacters.None),
+ factory.MetaCode("model ")
+ .Accepts(AcceptedCharacters.None),
+ factory.Code("Foo[[]][]\r\n")
+ .As(new SetModelTypeCodeGenerator("Foo[[]][]", "{0}<{1}>")),
+ factory.Markup("Bar")
+ .With(new MarkupCodeGenerator())
+ };
+ Assert.Equal(expectedSpans, spans.ToArray());
+ }
+
+ [Fact]
+ public void ParseModelKeyword_HandlesVSTemplateSyntax()
+ {
+ // Arrange + Act
+ var document = "@model $rootnamespace$.MyModel";
+ var spans = ParseDocument(document);
+
+ // Assert
+ var factory = SpanFactory.CreateCsHtml();
+ var expectedSpans = new Span[]
+ {
+ factory.EmptyHtml(),
+ factory.CodeTransition(SyntaxConstants.TransitionString)
+ .Accepts(AcceptedCharacters.None),
+ factory.MetaCode("model ")
+ .Accepts(AcceptedCharacters.None),
+ factory.Code("$rootnamespace$.MyModel")
+ .As(new SetModelTypeCodeGenerator("$rootnamespace$.MyModel", "{0}<{1}>"))
+ };
+ Assert.Equal(expectedSpans, spans.ToArray());
+ }
+
+ [Fact]
+ public void ParseModelKeyword_ErrorOnMissingModelType()
+ {
+ // Arrange + Act
+ List<RazorError> errors = new List<RazorError>();
+ var document = "@model ";
+ var spans = ParseDocument(document, errors);
+
+ // Assert
+ var factory = SpanFactory.CreateCsHtml();
+ var expectedSpans = new Span[]
+ {
+ factory.EmptyHtml(),
+ factory.CodeTransition(SyntaxConstants.TransitionString)
+ .Accepts(AcceptedCharacters.None),
+ factory.MetaCode("model ")
+ .Accepts(AcceptedCharacters.None),
+ factory.Code(" ")
+ .As(new SetModelTypeCodeGenerator(String.Empty, "{0}<{1}>")),
+ };
+ var expectedErrors = new[]
+ {
+ new RazorError("The 'model' keyword must be followed by a type name on the same line.", new SourceLocation(9, 0, 9), 1)
+ };
+ Assert.Equal(expectedSpans, spans.ToArray());
+ Assert.Equal(expectedErrors, errors.ToArray());
+ }
+
+ [Fact]
+ public void ParseModelKeyword_ErrorOnMultipleModelStatements()
+ {
+ // Arrange + Act
+ List<RazorError> errors = new List<RazorError>();
+ var document =
+ @"@model Foo
+@model Bar";
+ var spans = ParseDocument(document, errors);
+
+ // Assert
+ var factory = SpanFactory.CreateCsHtml();
+ var expectedSpans = new Span[]
+ {
+ factory.EmptyHtml(),
+ factory.CodeTransition(SyntaxConstants.TransitionString)
+ .Accepts(AcceptedCharacters.None),
+ factory.MetaCode("model ")
+ .Accepts(AcceptedCharacters.None),
+ factory.Code("Foo\r\n")
+ .As(new SetModelTypeCodeGenerator("Foo", "{0}<{1}>")),
+ factory.CodeTransition(SyntaxConstants.TransitionString)
+ .Accepts(AcceptedCharacters.None),
+ factory.MetaCode("model ")
+ .Accepts(AcceptedCharacters.None),
+ factory.Code("Bar")
+ .As(new SetModelTypeCodeGenerator("Bar", "{0}<{1}>"))
+ };
+
+ var expectedErrors = new[]
+ {
+ new RazorError("Only one 'model' statement is allowed in a file.", new SourceLocation(18, 1, 6), 1)
+ };
+ expectedSpans.Zip(spans, (exp, span) => new { expected = exp, span = span }).ToList().ForEach(i => Assert.Equal(i.expected, i.span));
+ Assert.Equal(expectedSpans, spans.ToArray());
+ Assert.Equal(expectedErrors, errors.ToArray());
+ }
+
+ [Fact]
+ public void ParseModelKeyword_ErrorOnModelFollowedByInherits()
+ {
+ // Arrange + Act
+ List<RazorError> errors = new List<RazorError>();
+ var document =
+ @"@model Foo
+@inherits Bar";
+ var spans = ParseDocument(document, errors);
+
+ // Assert
+ var factory = SpanFactory.CreateCsHtml();
+ var expectedSpans = new Span[]
+ {
+ factory.EmptyHtml(),
+ factory.CodeTransition(SyntaxConstants.TransitionString)
+ .Accepts(AcceptedCharacters.None),
+ factory.MetaCode("model ")
+ .Accepts(AcceptedCharacters.None),
+ factory.Code("Foo\r\n")
+ .As(new SetModelTypeCodeGenerator("Foo", "{0}<{1}>")),
+ factory.CodeTransition(SyntaxConstants.TransitionString)
+ .Accepts(AcceptedCharacters.None),
+ factory.MetaCode("inherits ")
+ .Accepts(AcceptedCharacters.None),
+ factory.Code("Bar")
+ .As(new SetBaseTypeCodeGenerator("Bar"))
+ };
+
+ var expectedErrors = new[]
+ {
+ new RazorError("The 'inherits' keyword is not allowed when a 'model' keyword is used.", new SourceLocation(21, 1, 9), 1)
+ };
+ expectedSpans.Zip(spans, (exp, span) => new { expected = exp, span = span }).ToList().ForEach(i => Assert.Equal(i.expected, i.span));
+ Assert.Equal(expectedSpans, spans.ToArray());
+ Assert.Equal(expectedErrors, errors.ToArray());
+ }
+
+ [Fact]
+ public void ParseModelKeyword_ErrorOnInheritsFollowedByModel()
+ {
+ // Arrange + Act
+ List<RazorError> errors = new List<RazorError>();
+ var document =
+ @"@inherits Bar
+@model Foo";
+ var spans = ParseDocument(document, errors);
+
+ // Assert
+ var factory = SpanFactory.CreateCsHtml();
+ var expectedSpans = new Span[]
+ {
+ factory.EmptyHtml(),
+ factory.CodeTransition(SyntaxConstants.TransitionString)
+ .Accepts(AcceptedCharacters.None),
+ factory.MetaCode("inherits ")
+ .Accepts(AcceptedCharacters.None),
+ factory.Code("Bar\r\n")
+ .As(new SetBaseTypeCodeGenerator("Bar")),
+ factory.CodeTransition(SyntaxConstants.TransitionString)
+ .Accepts(AcceptedCharacters.None),
+ factory.MetaCode("model ")
+ .Accepts(AcceptedCharacters.None),
+ factory.Code("Foo")
+ .As(new SetModelTypeCodeGenerator("Foo", "{0}<{1}>"))
+ };
+
+ var expectedErrors = new[]
+ {
+ new RazorError("The 'inherits' keyword is not allowed when a 'model' keyword is used.", new SourceLocation(9, 0, 9), 1)
+ };
+ expectedSpans.Zip(spans, (exp, span) => new { expected = exp, span = span }).ToList().ForEach(i => Assert.Equal(i.expected, i.span));
+ Assert.Equal(expectedSpans, spans.ToArray());
+ Assert.Equal(expectedErrors, errors.ToArray());
+ }
+
+ private static List<Span> ParseDocument(string documentContents, IList<RazorError> errors = null)
+ {
+ errors = errors ?? new List<RazorError>();
+ var markupParser = new HtmlMarkupParser();
+ var codeParser = new TestMvcCSharpRazorCodeParser();
+ var context = new ParserContext(new SeekableTextReader(documentContents), codeParser, markupParser, markupParser);
+ codeParser.Context = context;
+ markupParser.Context = context;
+ markupParser.ParseDocument();
+
+ ParserResults results = context.CompleteParse();
+ foreach (RazorError error in results.ParserErrors)
+ {
+ errors.Add(error);
+ }
+ return results.Document.Flatten().ToList();
+ }
+
+ private sealed class TestMvcCSharpRazorCodeParser : MvcCSharpRazorCodeParser
+ {
+ public bool HasDirective(string directive)
+ {
+ Action handler;
+ return TryGetDirectiveHandler(directive, out handler);
+ }
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Razor/Test/MvcVBRazorCodeParserTest.cs b/test/System.Web.Mvc.Test/Razor/Test/MvcVBRazorCodeParserTest.cs
new file mode 100644
index 00000000..8826f0fe
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Razor/Test/MvcVBRazorCodeParserTest.cs
@@ -0,0 +1,301 @@
+using System.Collections.Generic;
+using System.Linq;
+using System.Web.Razor;
+using System.Web.Razor.Generator;
+using System.Web.Razor.Parser;
+using System.Web.Razor.Parser.SyntaxTree;
+using System.Web.Razor.Test.Framework;
+using System.Web.Razor.Text;
+using Xunit;
+
+namespace System.Web.Mvc.Razor.Test
+{
+ public class MvcVBRazorCodeParserTest
+ {
+ [Fact]
+ public void Constructor_AddsModelKeyword()
+ {
+ var parser = new MvcVBRazorCodeParser();
+
+ Assert.True(parser.IsDirectiveDefined(MvcVBRazorCodeParser.ModelTypeKeyword));
+ }
+
+ [Fact]
+ public void ParseModelKeyword_HandlesSingleInstance()
+ {
+ // Arrange + Act
+ var document = "@ModelType Foo";
+ var spans = ParseDocument(document);
+
+ // Assert
+ var factory = SpanFactory.CreateVbHtml();
+ var expectedSpans = new Span[]
+ {
+ factory.EmptyHtml(),
+ factory.CodeTransition(SyntaxConstants.TransitionString)
+ .Accepts(AcceptedCharacters.None),
+ factory.MetaCode("ModelType ")
+ .Accepts(AcceptedCharacters.None),
+ factory.Code("Foo")
+ .As(new SetModelTypeCodeGenerator("Foo", "{0}(Of {1})"))
+ };
+ Assert.Equal(expectedSpans, spans.ToArray());
+ }
+
+ [Fact]
+ public void ParseModelKeyword_HandlesNullableTypes()
+ {
+ // Arrange + Act
+ var document = "@ModelType Foo?\r\nBar";
+ var spans = ParseDocument(document);
+
+ // Assert
+ var factory = SpanFactory.CreateVbHtml();
+ var expectedSpans = new Span[]
+ {
+ factory.EmptyHtml(),
+ factory.CodeTransition(SyntaxConstants.TransitionString)
+ .Accepts(AcceptedCharacters.None),
+ factory.MetaCode("ModelType ")
+ .Accepts(AcceptedCharacters.None),
+ factory.Code("Foo?\r\n")
+ .As(new SetModelTypeCodeGenerator("Foo?", "{0}(Of {1})")),
+ factory.Markup("Bar")
+ };
+ Assert.Equal(expectedSpans, spans.ToArray());
+ }
+
+ [Fact]
+ public void ParseModelKeyword_HandlesArrays()
+ {
+ // Arrange + Act
+ var document = "@ModelType Foo(())()\r\nBar";
+ var spans = ParseDocument(document);
+
+ // Assert
+ var factory = SpanFactory.CreateVbHtml();
+ var expectedSpans = new Span[]
+ {
+ factory.EmptyHtml(),
+ factory.CodeTransition(SyntaxConstants.TransitionString)
+ .Accepts(AcceptedCharacters.None),
+ factory.MetaCode("ModelType ")
+ .Accepts(AcceptedCharacters.None),
+ factory.Code("Foo(())()\r\n")
+ .As(new SetModelTypeCodeGenerator("Foo(())()", "{0}(Of {1})")),
+ factory.Markup("Bar")
+ };
+ Assert.Equal(expectedSpans, spans.ToArray());
+ }
+
+ [Fact]
+ public void ParseModelKeyword_HandlesVSTemplateSyntax()
+ {
+ // Arrange + Act
+ var document = "@ModelType $rootnamespace$.MyModel";
+ var spans = ParseDocument(document);
+
+ // Assert
+ var factory = SpanFactory.CreateVbHtml();
+ var expectedSpans = new Span[]
+ {
+ factory.EmptyHtml(),
+ factory.CodeTransition(SyntaxConstants.TransitionString)
+ .Accepts(AcceptedCharacters.None),
+ factory.MetaCode("ModelType ")
+ .Accepts(AcceptedCharacters.None),
+ factory.Code("$rootnamespace$.MyModel")
+ .As(new SetModelTypeCodeGenerator("$rootnamespace$.MyModel", "{0}(Of {1})"))
+ };
+ Assert.Equal(expectedSpans, spans.ToArray());
+ }
+
+ [Fact]
+ public void ParseModelKeyword_ErrorOnMissingModelType()
+ {
+ // Arrange + Act
+ List<RazorError> errors = new List<RazorError>();
+ var document = "@ModelType ";
+ var spans = ParseDocument(document, errors);
+
+ // Assert
+ var factory = SpanFactory.CreateVbHtml();
+ var expectedSpans = new Span[]
+ {
+ factory.EmptyHtml(),
+ factory.CodeTransition(SyntaxConstants.TransitionString)
+ .Accepts(AcceptedCharacters.None),
+ factory.MetaCode("ModelType ")
+ .Accepts(AcceptedCharacters.None),
+ factory.EmptyVB()
+ .As(new SetModelTypeCodeGenerator(String.Empty, "{0}(Of {1})"))
+ .Accepts(AcceptedCharacters.Any)
+ };
+ var expectedErrors = new[]
+ {
+ new RazorError("The 'ModelType' keyword must be followed by a type name on the same line.", new SourceLocation(10, 0, 10), 1)
+ };
+ Assert.Equal(expectedSpans, spans.ToArray());
+ Assert.Equal(expectedErrors, errors.ToArray());
+ }
+
+ [Fact]
+ public void ParseModelKeyword_DoesNotAcceptNewlineIfInDesignTimeMode()
+ {
+ // Arrange + Act
+ List<RazorError> errors = new List<RazorError>();
+ var document = "@ModelType foo\r\n";
+ var spans = ParseDocument(document, errors, designTimeMode: true);
+
+ // Assert
+ var factory = SpanFactory.CreateVbHtml();
+ var expectedSpans = new Span[]
+ {
+ factory.EmptyHtml(),
+ factory.CodeTransition(SyntaxConstants.TransitionString)
+ .Accepts(AcceptedCharacters.None),
+ factory.MetaCode("ModelType ")
+ .Accepts(AcceptedCharacters.None),
+ factory.Code("foo")
+ .As(new SetModelTypeCodeGenerator("foo", "{0}(Of {1})"))
+ .Accepts(AcceptedCharacters.Any),
+ factory.Markup("\r\n")
+ };
+ Assert.Equal(expectedSpans, spans.ToArray());
+ Assert.Equal(0, errors.Count);
+ }
+
+ [Fact]
+ public void ParseModelKeyword_ErrorOnMultipleModelStatements()
+ {
+ // Arrange + Act
+ List<RazorError> errors = new List<RazorError>();
+ var document =
+ @"@ModelType Foo
+@ModelType Bar";
+ var spans = ParseDocument(document, errors);
+
+ // Assert
+ var factory = SpanFactory.CreateVbHtml();
+ var expectedSpans = new Span[]
+ {
+ factory.EmptyHtml(),
+ factory.CodeTransition(SyntaxConstants.TransitionString)
+ .Accepts(AcceptedCharacters.None),
+ factory.MetaCode("ModelType ")
+ .Accepts(AcceptedCharacters.None),
+ factory.Code("Foo\r\n")
+ .As(new SetModelTypeCodeGenerator("Foo", "{0}(Of {1})")),
+ factory.CodeTransition(SyntaxConstants.TransitionString)
+ .Accepts(AcceptedCharacters.None),
+ factory.MetaCode("ModelType ")
+ .Accepts(AcceptedCharacters.None),
+ factory.Code("Bar")
+ .As(new SetModelTypeCodeGenerator("Bar", "{0}(Of {1})"))
+ };
+
+ var expectedErrors = new[]
+ {
+ new RazorError("Only one 'ModelType' statement is allowed in a file.", new SourceLocation(26, 1, 10), 1)
+ };
+ expectedSpans.Zip(spans, (exp, span) => new { expected = exp, span = span }).ToList().ForEach(i => Assert.Equal(i.expected, i.span));
+ Assert.Equal(expectedSpans, spans.ToArray());
+ Assert.Equal(expectedErrors, errors.ToArray());
+ }
+
+ [Fact]
+ public void ParseModelKeyword_ErrorOnModelFollowedByInherits()
+ {
+ // Arrange + Act
+ List<RazorError> errors = new List<RazorError>();
+ var document =
+ @"@ModelType Foo
+@Inherits Bar";
+ var spans = ParseDocument(document, errors);
+
+ // Assert
+ var factory = SpanFactory.CreateVbHtml();
+ var expectedSpans = new Span[]
+ {
+ factory.EmptyHtml(),
+ factory.CodeTransition(SyntaxConstants.TransitionString)
+ .Accepts(AcceptedCharacters.None),
+ factory.MetaCode("ModelType ")
+ .Accepts(AcceptedCharacters.None),
+ factory.Code("Foo\r\n")
+ .As(new SetModelTypeCodeGenerator("Foo", "{0}(Of {1})")),
+ factory.CodeTransition(SyntaxConstants.TransitionString)
+ .Accepts(AcceptedCharacters.None),
+ factory.MetaCode("Inherits ")
+ .Accepts(AcceptedCharacters.None),
+ factory.Code("Bar")
+ .As(new SetBaseTypeCodeGenerator("Bar"))
+ };
+
+ var expectedErrors = new[]
+ {
+ new RazorError("The 'inherits' keyword is not allowed when a 'ModelType' keyword is used.", new SourceLocation(25, 1, 9), 1)
+ };
+ expectedSpans.Zip(spans, (exp, span) => new { expected = exp, span = span }).ToList().ForEach(i => Assert.Equal(i.expected, i.span));
+ Assert.Equal(expectedSpans, spans.ToArray());
+ Assert.Equal(expectedErrors, errors.ToArray());
+ }
+
+ [Fact]
+ public void ParseModelKeyword_ErrorOnInheritsFollowedByModel()
+ {
+ // Arrange + Act
+ List<RazorError> errors = new List<RazorError>();
+ var document =
+ @"@Inherits Bar
+@ModelType Foo";
+ var spans = ParseDocument(document, errors);
+
+ // Assert
+ var factory = SpanFactory.CreateVbHtml();
+ var expectedSpans = new Span[]
+ {
+ factory.EmptyHtml(),
+ factory.CodeTransition(SyntaxConstants.TransitionString)
+ .Accepts(AcceptedCharacters.None),
+ factory.MetaCode("Inherits ")
+ .Accepts(AcceptedCharacters.None),
+ factory.Code("Bar\r\n")
+ .AsBaseType("Bar"),
+ factory.CodeTransition(SyntaxConstants.TransitionString)
+ .Accepts(AcceptedCharacters.None),
+ factory.MetaCode("ModelType ")
+ .Accepts(AcceptedCharacters.None),
+ factory.Code("Foo")
+ .As(new SetModelTypeCodeGenerator("Foo", "{0}(Of {1})"))
+ };
+
+ var expectedErrors = new[]
+ {
+ new RazorError("The 'inherits' keyword is not allowed when a 'ModelType' keyword is used.", new SourceLocation(9, 0, 9), 1)
+ };
+ expectedSpans.Zip(spans, (exp, span) => new { expected = exp, span = span }).ToList().ForEach(i => Assert.Equal(i.expected, i.span));
+ Assert.Equal(expectedSpans, spans.ToArray());
+ Assert.Equal(expectedErrors, errors.ToArray());
+ }
+
+ private static List<Span> ParseDocument(string documentContents, List<RazorError> errors = null, bool designTimeMode = false)
+ {
+ errors = errors ?? new List<RazorError>();
+ var markupParser = new HtmlMarkupParser();
+ var codeParser = new MvcVBRazorCodeParser();
+ var context = new ParserContext(new SeekableTextReader(documentContents), codeParser, markupParser, markupParser);
+ context.DesignTimeMode = designTimeMode;
+ codeParser.Context = context;
+ markupParser.Context = context;
+ markupParser.ParseDocument();
+
+ ParserResults results = context.CompleteParse();
+ foreach (RazorError error in results.ParserErrors)
+ {
+ errors.Add(error);
+ }
+ return results.Document.Flatten().ToList();
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Razor/Test/MvcWebPageRazorHostTest.cs b/test/System.Web.Mvc.Test/Razor/Test/MvcWebPageRazorHostTest.cs
new file mode 100644
index 00000000..9354cbe7
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Razor/Test/MvcWebPageRazorHostTest.cs
@@ -0,0 +1,107 @@
+using System.Web.Razor.Generator;
+using System.Web.Razor.Parser;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Mvc.Razor.Test
+{
+ public class MvcWebPageRazorHostTest
+ {
+ [Fact]
+ public void Constructor()
+ {
+ MvcWebPageRazorHost host = new MvcWebPageRazorHost("foo.cshtml", "bar");
+
+ Assert.Equal("foo.cshtml", host.VirtualPath);
+ Assert.Equal("bar", host.PhysicalPath);
+ Assert.Equal(typeof(WebViewPage).FullName, host.DefaultBaseClass);
+ }
+
+ [Fact]
+ public void ConstructorRemovesUnwantedNamespaceImports()
+ {
+ MvcWebPageRazorHost host = new MvcWebPageRazorHost("foo.cshtml", "bar");
+
+ Assert.False(host.NamespaceImports.Contains("System.Web.WebPages.Html"));
+
+ // Even though MVC no longer needs to remove the following two namespaces
+ // (because they are no longer imported by System.Web.WebPages), we want
+ // to make sure that they don't get introduced again by default.
+ Assert.False(host.NamespaceImports.Contains("WebMatrix.Data"));
+ Assert.False(host.NamespaceImports.Contains("WebMatrix.WebData"));
+ }
+
+#if VB_ENABLED
+ [Fact]
+ public void DecorateGodeGenerator_ReplacesVBCodeGeneratorWithMvcSpecificOne() {
+ // Arrange
+ MvcWebPageRazorHost host = new MvcWebPageRazorHost("foo.vbhtml", "bar");
+ var generator = new VBRazorCodeGenerator("someClass", "root.name", "foo.vbhtml", host);
+
+ // Act
+ var result = host.DecorateCodeGenerator(generator);
+
+ // Assert
+ Assert.IsType<MvcVBRazorCodeGenerator>(result);
+ Assert.Equal("someClass", result.ClassName);
+ Assert.Equal("root.name", result.RootNamespaceName);
+ Assert.Equal("foo.vbhtml", result.SourceFileName);
+ Assert.Same(host, result.Host);
+ }
+#endif
+
+ [Fact]
+ public void DecorateGodeGenerator_ReplacesCSharpCodeGeneratorWithMvcSpecificOne()
+ {
+ // Arrange
+ MvcWebPageRazorHost host = new MvcWebPageRazorHost("foo.cshtml", "bar");
+ var generator = new CSharpRazorCodeGenerator("someClass", "root.name", "foo.cshtml", host);
+
+ // Act
+ var result = host.DecorateCodeGenerator(generator);
+
+ // Assert
+ Assert.IsType<MvcCSharpRazorCodeGenerator>(result);
+ Assert.Equal("someClass", result.ClassName);
+ Assert.Equal("root.name", result.RootNamespaceName);
+ Assert.Equal("foo.cshtml", result.SourceFileName);
+ Assert.Same(host, result.Host);
+ }
+
+ [Fact]
+ public void DecorateCodeParser_ThrowsOnNull()
+ {
+ MvcWebPageRazorHost host = new MvcWebPageRazorHost("foo.cshtml", "bar");
+ Assert.ThrowsArgumentNull(delegate() { host.DecorateCodeParser(null); }, "incomingCodeParser");
+ }
+
+ [Fact]
+ public void DecorateCodeParser_ReplacesCSharpCodeParserWithMvcSpecificOne()
+ {
+ // Arrange
+ MvcWebPageRazorHost host = new MvcWebPageRazorHost("foo.cshtml", "bar");
+ var parser = new CSharpCodeParser();
+
+ // Act
+ var result = host.DecorateCodeParser(parser);
+
+ // Assert
+ Assert.IsType<MvcCSharpRazorCodeParser>(result);
+ }
+
+#if VB_ENABLED
+ [Fact]
+ public void DecorateCodeParser_ReplacesVBCodeParserWithMvcSpecificOne() {
+ // Arrange
+ MvcWebPageRazorHost host = new MvcWebPageRazorHost("foo.vbhtml", "bar");
+ var parser = new VBCodeParser();
+
+ // Act
+ var result = host.DecorateCodeParser(parser);
+
+ // Assert
+ Assert.IsType<MvcVBRazorCodeParser>(result);
+ }
+#endif
+ }
+}
diff --git a/test/System.Web.Mvc.Test/System.Web.Mvc.Test.csproj b/test/System.Web.Mvc.Test/System.Web.Mvc.Test.csproj
new file mode 100644
index 00000000..23692c17
--- /dev/null
+++ b/test/System.Web.Mvc.Test/System.Web.Mvc.Test.csproj
@@ -0,0 +1,356 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory),Runtime.sln))\tools\WebStack.settings.targets" />
+ <PropertyGroup>
+ <!-- Temporarily disable Obsolete Warnings as Errors -->
+ <WarningsNotAsErrors>618</WarningsNotAsErrors>
+ <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+ <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+ <ProductVersion>9.0.30729</ProductVersion>
+ <SchemaVersion>2.0</SchemaVersion>
+ <ProjectGuid>{8AC2A2E4-2F11-4D40-A887-62E2583A65E6}</ProjectGuid>
+ <OutputType>Library</OutputType>
+ <AppDesignerFolder>Properties</AppDesignerFolder>
+ <RootNamespace>System.Web</RootNamespace>
+ <AssemblyName>System.Web.Mvc.Test</AssemblyName>
+ <FileAlignment>512</FileAlignment>
+ <ProjectTypeGuids>{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
+ <ErrorReport>prompt</ErrorReport>
+ <WarningLevel>4</WarningLevel>
+ <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
+ <DebugSymbols>true</DebugSymbols>
+ <DebugType>full</DebugType>
+ <Optimize>false</Optimize>
+ <OutputPath>$(WebStackRootPath)\bin\Debug\Test\</OutputPath>
+ <DefineConstants>TRACE;DEBUG</DefineConstants>
+ <CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
+ <DebugType>pdbonly</DebugType>
+ <Optimize>true</Optimize>
+ <OutputPath>$(WebStackRootPath)\bin\Release\Test\</OutputPath>
+ <DefineConstants>TRACE</DefineConstants>
+ <CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)' == 'CodeCoverage' ">
+ <DebugSymbols>true</DebugSymbols>
+ <DebugType>full</DebugType>
+ <Optimize>false</Optimize>
+ <OutputPath>$(WebStackRootPath)\bin\CodeCoverage\Test\</OutputPath>
+ <DefineConstants>TRACE;DEBUG</DefineConstants>
+ <CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
+ </PropertyGroup>
+ <ItemGroup>
+ <Reference Include="Microsoft.CSharp" />
+ <Reference Include="Moq, Version=4.0.10827.0, Culture=neutral, PublicKeyToken=69f491c39445e920, processorArchitecture=MSIL">
+ <HintPath>..\..\packages\Moq.4.0.10827\lib\NET40\Moq.dll</HintPath>
+ </Reference>
+ <Reference Include="System" />
+ <Reference Include="System.ComponentModel.DataAnnotations" />
+ <Reference Include="System.Configuration" />
+ <Reference Include="System.Core" />
+ <Reference Include="System.Data.Entity" />
+ <Reference Include="System.Data.Linq" />
+ <Reference Include="System.Runtime.Caching" />
+ <Reference Include="System.Web" />
+ <Reference Include="System.Web.Abstractions" />
+ <Reference Include="System.Web.Routing" />
+ <Reference Include="xunit">
+ <HintPath>..\..\packages\xunit.1.9.0.1566\lib\xunit.dll</HintPath>
+ </Reference>
+ <Reference Include="xunit.extensions">
+ <HintPath>..\..\packages\xunit.extensions.1.9.0.1566\lib\xunit.extensions.dll</HintPath>
+ </Reference>
+ </ItemGroup>
+ <ItemGroup>
+ <Compile Include="Async\Test\TaskAsyncActionDescriptorTest.cs" />
+ <Compile Include="Async\Test\TaskWrapperAsyncResultTest.cs" />
+ <Compile Include="Html\Test\DisplayNameExtensionsTest.cs" />
+ <Compile Include="Html\Test\NameExtensionsTest.cs" />
+ <Compile Include="Html\Test\ValueExtensionsTest.cs" />
+ <Compile Include="Razor\Test\MvcCSharpRazorCodeGeneratorTest.cs" />
+ <Compile Include="Test\AjaxHelper`1Test.cs" />
+ <Compile Include="Test\CancellationTokenModelBinderTest.cs" />
+ <Compile Include="Test\CachedAssociatedMetadataProviderTest.cs" />
+ <Compile Include="Test\CachedDataAnnotationsModelMetadataProviderTest.cs" />
+ <Compile Include="Test\ChildActionValueProviderFactoryTest.cs" />
+ <Compile Include="ExpressionUtil\Test\BinaryExpressionFingerprintTest.cs" />
+ <Compile Include="ExpressionUtil\Test\CachedExpressionCompilerTest.cs" />
+ <Compile Include="ExpressionUtil\Test\ConditionalExpressionFingerprintTest.cs" />
+ <Compile Include="ExpressionUtil\Test\ConstantExpressionFingerprintTest.cs" />
+ <Compile Include="ExpressionUtil\Test\DefaultExpressionFingerprintTest.cs" />
+ <Compile Include="ExpressionUtil\Test\DummyExpressionFingerprint.cs" />
+ <Compile Include="ExpressionUtil\Test\ExpressionFingerprintTest.cs" />
+ <Compile Include="ExpressionUtil\Test\FingerprintingExpressionVisitorTest.cs" />
+ <Compile Include="ExpressionUtil\Test\HoistingExpressionVisitorTest.cs" />
+ <Compile Include="ExpressionUtil\Test\IndexExpressionFingerprintTest.cs" />
+ <Compile Include="ExpressionUtil\Test\LambdaExpressionFingerprintTest.cs" />
+ <Compile Include="ExpressionUtil\Test\MemberExpressionFingerprintTest.cs" />
+ <Compile Include="ExpressionUtil\Test\MethodCallExpressionFingerprintTest.cs" />
+ <Compile Include="ExpressionUtil\Test\ParameterExpressionFingerprintTest.cs" />
+ <Compile Include="ExpressionUtil\Test\TypeBinaryExpressionFingerprintTest.cs" />
+ <Compile Include="ExpressionUtil\Test\UnaryExpressionFingerprintTest.cs" />
+ <Compile Include="Test\AdditionalMetadataAttributeTest.cs" />
+ <Compile Include="Test\BuildManagerCompiledViewTest.cs" />
+ <Compile Include="Test\BuildManagerViewEngineTest.cs" />
+ <Compile Include="Test\CompareAttributeTest.cs" />
+ <Compile Include="Test\DataAnnotationsModelMetadataProviderTestBase.cs" />
+ <Compile Include="Test\DataTypeUtilTest.cs" />
+ <Compile Include="Test\FilterInfoTest.cs" />
+ <Compile Include="Test\AllowHtmlAttributeTest.cs" />
+ <Compile Include="Test\HtmlHelper`1Test.cs" />
+ <Compile Include="Test\MockableUnvalidatedRequestValues.cs" />
+ <Compile Include="Test\DescriptorUtilTest.cs" />
+ <Compile Include="Razor\Test\MvcVBRazorCodeParserTest.cs" />
+ <Compile Include="Test\ModelBinderProviderCollectionTest.cs" />
+ <Compile Include="Test\ModelBinderProvidersTest.cs" />
+ <Compile Include="Razor\Test\MvcWebPageRazorHostTest.cs" />
+ <Compile Include="Ajax\Test\AjaxExtensionsTest.cs" />
+ <Compile Include="Ajax\Test\AjaxOptionsTest.cs" />
+ <Compile Include="Async\Test\AsyncActionMethodSelectorTest.cs" />
+ <Compile Include="Async\Test\AsyncControllerActionInvokerTest.cs" />
+ <Compile Include="Async\Test\AsyncUtilTest.cs" />
+ <Compile Include="Async\Test\AsyncActionDescriptorTest.cs" />
+ <Compile Include="Test\AsyncControllerTest.cs" />
+ <Compile Include="Async\Test\SynchronousOperationExceptionTest.cs" />
+ <Compile Include="Async\Test\AsyncManagerTest.cs" />
+ <Compile Include="Async\Test\AsyncResultWrapperTest.cs" />
+ <Compile Include="Test\AsyncTimeoutAttributeTest.cs" />
+ <Compile Include="Test\ClientDataTypeModelValidatorProviderTest.cs" />
+ <Compile Include="Test\ControllerInstanceFilterProviderTest.cs" />
+ <Compile Include="Test\PreApplicationStartCodeTest.cs" />
+ <Compile Include="Test\RemoteAttributeTest.cs" />
+ <Compile Include="Test\UrlParameterTest.cs" />
+ <Compile Include="Test\UrlRewriterHelperTest.cs" />
+ <Compile Include="Test\ViewStartPageTest.cs" />
+ <Compile Include="Test\RazorViewEngineTest.cs" />
+ <Compile Include="Test\RazorViewTest.cs" />
+ <Compile Include="Test\DynamicViewDataDictionaryTest.cs" />
+ <Compile Include="Test\FilterAttributeFilterProviderTest.cs" />
+ <Compile Include="Test\FilterProviderCollectionTest.cs" />
+ <Compile Include="Test\FilterProvidersTest.cs" />
+ <Compile Include="Test\FilterTest.cs" />
+ <Compile Include="Test\GlobalFilterCollectionTest.cs" />
+ <Compile Include="Test\HttpFileCollectionValueProviderFactoryTest.cs" />
+ <Compile Include="Test\HttpNotFoundResultTest.cs" />
+ <Compile Include="Test\HttpStatusCodeResultTest.cs" />
+ <Compile Include="Test\JsonValueProviderFactoryTest.cs" />
+ <Compile Include="Razor\Test\MvcCSharpRazorCodeParserTest.cs" />
+ <Compile Include="Test\MultiServiceResolverTest.cs" />
+ <Compile Include="Test\DependencyResolverTest.cs" />
+ <Compile Include="Test\MvcWebRazorHostFactoryTest.cs" />
+ <Compile Include="Test\RangeAttributeAdapterTest.cs" />
+ <Compile Include="Test\RegularExpressionAttributeAdapterTest.cs" />
+ <Compile Include="Test\RequiredAttributeAdapterTest.cs" />
+ <Compile Include="Test\RouteDataValueProviderFactoryTest.cs" />
+ <Compile Include="Test\QueryStringValueProviderFactoryTest.cs" />
+ <Compile Include="Test\FormValueProviderFactoryTest.cs" />
+ <Compile Include="Test\SingleServiceResolverTest.cs" />
+ <Compile Include="Test\StringLengthAttributeAdapterTest.cs" />
+ <Compile Include="Test\ValidatableObjectAdapterTest.cs" />
+ <Compile Include="Test\ValueProviderFactoryCollectionTest.cs" />
+ <Compile Include="Test\TypeCacheUtilTest.cs" />
+ <Compile Include="Test\TypeCacheSerializerTest.cs" />
+ <Compile Include="Test\NoAsyncTimeoutAttributeTest.cs" />
+ <Compile Include="Async\Test\OperationCounterTest.cs" />
+ <Compile Include="Async\Test\ReflectedAsyncActionDescriptorTest.cs" />
+ <Compile Include="Async\Test\ReflectedAsyncControllerDescriptorTest.cs" />
+ <Compile Include="Async\Test\SignalContainer.cs" />
+ <Compile Include="Async\Test\TriggerListenerTest.cs" />
+ <Compile Include="Async\Test\MockAsyncResult.cs" />
+ <Compile Include="Async\Test\SimpleAsyncResultTest.cs" />
+ <Compile Include="Async\Test\SynchronizationContextUtilTest.cs" />
+ <Compile Include="Html\Test\ChildActionExtensionsTest.cs" />
+ <Compile Include="Html\Test\DefaultDisplayTemplatesTest.cs" />
+ <Compile Include="Html\Test\DefaultEditorTemplatesTest.cs" />
+ <Compile Include="Html\Test\FormExtensionsTest.cs" />
+ <Compile Include="Html\Test\InputExtensionsTest.cs" />
+ <Compile Include="Html\Test\LabelExtensionsTest.cs" />
+ <Compile Include="Html\Test\LinkExtensionsTest.cs" />
+ <Compile Include="Html\Test\MvcFormTest.cs" />
+ <Compile Include="Html\Test\PartialExtensionsTest.cs" />
+ <Compile Include="Html\Test\RenderPartialExtensionsTest.cs" />
+ <Compile Include="Html\Test\SelectExtensionsTest.cs" />
+ <Compile Include="Html\Test\TemplateHelpersTest.cs" />
+ <Compile Include="Html\Test\TextAreaExtensionsTest.cs" />
+ <Compile Include="Html\Test\ValidationExtensionsTest.cs" />
+ <Compile Include="Test\ActionExecutedContextTest.cs" />
+ <Compile Include="Test\ActionExecutingContextTest.cs" />
+ <Compile Include="Test\AssociatedValidatorProviderTest.cs" />
+ <Compile Include="Test\BindAttributeTest.cs" />
+ <Compile Include="Test\AssociatedMetadataProviderTest.cs" />
+ <Compile Include="Test\ByteArrayModelBinderTest.cs" />
+ <Compile Include="Test\ChildActionOnlyAttributeTest.cs" />
+ <Compile Include="Test\ControllerBaseTest.cs" />
+ <Compile Include="Test\ActionMethodSelectorTest.cs" />
+ <Compile Include="Test\ActionNameAttributeTest.cs" />
+ <Compile Include="Test\AcceptVerbsAttributeTest.cs" />
+ <Compile Include="Test\ControllerContextTest.cs" />
+ <Compile Include="Test\AuthorizationContextTest.cs" />
+ <Compile Include="Test\ParameterInfoUtilTest.cs" />
+ <Compile Include="Test\ModelValidationResultTest.cs" />
+ <Compile Include="Test\HttpHandlerUtilTest.cs" />
+ <Compile Include="Test\ValueProviderFactoriesTest.cs" />
+ <Compile Include="Test\ValueProviderCollectionTest.cs" />
+ <Compile Include="Test\HttpFileCollectionValueProviderTest.cs" />
+ <Compile Include="Test\DictionaryValueProviderTest.cs" />
+ <Compile Include="Test\NameValueCollectionValueProviderTest.cs" />
+ <Compile Include="Test\ValueProviderUtilTest.cs" />
+ <Compile Include="Test\DataErrorInfoModelValidatorProviderTest.cs" />
+ <Compile Include="Test\ModelValidatorProviderCollectionTest.cs" />
+ <Compile Include="Test\HttpVerbAttributeHelper.cs" />
+ <Compile Include="Test\HttpDeleteAttributeTest.cs" />
+ <Compile Include="Test\HttpPutAttributeTest.cs" />
+ <Compile Include="Test\HttpGetAttributeTest.cs" />
+ <Compile Include="Test\DataAnnotationsModelValidatorTest.cs" />
+ <Compile Include="Test\EmptyModelValidatorProviderTest.cs" />
+ <Compile Include="Test\ModelValidatorProvidersTest.cs" />
+ <Compile Include="Test\ModelValidatorTest.cs" />
+ <Compile Include="Test\MvcHtmlStringTest.cs" />
+ <Compile Include="Test\DataAnnotationsModelValidatorProviderTest.cs" />
+ <Compile Include="Test\ExpressionHelperTest.cs" />
+ <Compile Include="Test\FormContextTest.cs" />
+ <Compile Include="Test\FieldValidationMetadataTest.cs" />
+ <Compile Include="Test\ModelClientValidationRuleTest.cs" />
+ <Compile Include="Test\RequireHttpsAttributeTest.cs" />
+ <Compile Include="Test\HttpRequestExtensionsTest.cs" />
+ <Compile Include="Test\DataAnnotationsModelMetadataProviderTest.cs" />
+ <Compile Include="Test\ModelMetadataProvidersTest.cs" />
+ <Compile Include="Test\ModelMetadataTest.cs" />
+ <Compile Include="Test\AreaHelpersTest.cs" />
+ <Compile Include="Test\AreaRegistrationContextTest.cs" />
+ <Compile Include="Test\AreaRegistrationTest.cs" />
+ <Compile Include="Async\Test\SingleEntryGateTest.cs" />
+ <Compile Include="Test\HttpPostAttributeTest.cs" />
+ <Compile Include="Test\LinqBinaryModelBinderTest.cs" />
+ <Compile Include="Test\MockHelpers.cs" />
+ <Compile Include="Test\PathHelpersTest.cs" />
+ <Compile Include="Test\ExceptionContextTest.cs" />
+ <Compile Include="Test\ModelBindingContextTest.cs" />
+ <Compile Include="Test\ResultExecutedContextTest.cs" />
+ <Compile Include="Test\ResultExecutingContextTest.cs" />
+ <Compile Include="Test\ValidateAntiForgeryTokenAttributeTest.cs" />
+ <Compile Include="Test\DictionaryHelpersTest.cs" />
+ <Compile Include="Test\AjaxRequestExtensionsTest.cs" />
+ <Compile Include="Test\JavaScriptResultTest.cs" />
+ <Compile Include="Test\ModelBinderDictionaryTest.cs" />
+ <Compile Include="Test\DefaultViewLocationCacheTest.cs" />
+ <Compile Include="Test\FormCollectionTest.cs" />
+ <Compile Include="Test\HttpPostedFileBaseModelBinderTest.cs" />
+ <Compile Include="Test\ValidateInputAttributeTest.cs" />
+ <Compile Include="Test\FileContentResultTest.cs" />
+ <Compile Include="Test\FilePathResultTest.cs" />
+ <Compile Include="Test\FileResultTest.cs" />
+ <Compile Include="Test\FileStreamResultTest.cs" />
+ <Compile Include="Test\ControllerDescriptorTest.cs" />
+ <Compile Include="Test\ControllerDescriptorCacheTest.cs" />
+ <Compile Include="Test\ReaderWriterCacheTest.cs" />
+ <Compile Include="Test\ReflectedParameterBindingInfoTest.cs" />
+ <Compile Include="Test\ParameterBindingInfoTest.cs" />
+ <Compile Include="Test\ReflectedControllerDescriptorTest.cs" />
+ <Compile Include="Test\ActionDescriptorTest.cs" />
+ <Compile Include="Test\ReflectedActionDescriptorTest.cs" />
+ <Compile Include="Test\ReflectedParameterDescriptorTest.cs" />
+ <Compile Include="Test\ParameterDescriptorTest.cs" />
+ <Compile Include="Test\DefaultModelBinderTest.cs" />
+ <Compile Include="Test\ValueProviderDictionaryTest.cs" />
+ <Compile Include="Test\ValueProviderResultTest.cs" />
+ <Compile Include="Test\ModelBinderAttributeTest.cs" />
+ <Compile Include="Test\ModelBindersTest.cs" />
+ <Compile Include="Test\ViewContextTest.cs" />
+ <Compile Include="Test\ViewDataInfoTest.cs" />
+ <Compile Include="Test\ViewEngineResultTest.cs" />
+ <Compile Include="Test\NonActionAttributeTest.cs" />
+ <Compile Include="Test\ModelStateDictionaryTest.cs" />
+ <Compile Include="Test\ModelStateTest.cs" />
+ <Compile Include="Test\ModelErrorCollectionTest.cs" />
+ <Compile Include="Test\ModelErrorTest.cs" />
+ <Compile Include="Test\AjaxHelperTest.cs" />
+ <Compile Include="Test\AuthorizeAttributeTest.cs" />
+ <Compile Include="Test\ControllerActionInvokerTest.cs" />
+ <Compile Include="Test\ActionFilterAttributeTest.cs" />
+ <Compile Include="Test\ControllerBuilderTest.cs" />
+ <Compile Include="Test\ControllerTest.cs" />
+ <Compile Include="Test\ContentResultTest.cs" />
+ <Compile Include="Test\ActionMethodDispatcherTest.cs" />
+ <Compile Include="Test\ActionMethodDispatcherCacheTest.cs" />
+ <Compile Include="Test\PartialViewResultTest.cs" />
+ <Compile Include="Test\ViewEngineCollectionTest.cs" />
+ <Compile Include="Test\ViewMasterPageControlBuilderTest.cs" />
+ <Compile Include="Test\ViewPageControlBuilderTest.cs" />
+ <Compile Include="Test\ViewTypeParserFilterTest.cs" />
+ <Compile Include="Test\ViewResultBaseTest.cs" />
+ <Compile Include="Test\ViewUserControlControlBuilderTest.cs" />
+ <Compile Include="Test\VirtualPathProviderViewEngineTest.cs" />
+ <Compile Include="Test\WebFormViewTest.cs" />
+ <Compile Include="Test\MvcHttpHandlerTest.cs" />
+ <Compile Include="Test\HandleErrorAttributeTest.cs" />
+ <Compile Include="Test\HandleErrorInfoTest.cs" />
+ <Compile Include="Test\HttpUnauthorizedResultTest.cs" />
+ <Compile Include="Test\SessionStateTempDataProviderTest.cs" />
+ <Compile Include="Test\OutputCacheAttributeTest.cs" />
+ <Compile Include="Test\JsonResultTest.cs" />
+ <Compile Include="Test\NameValueCollectionExtensionsTest.cs" />
+ <Compile Include="Test\MockBuildManager.cs" />
+ <Compile Include="Test\DefaultControllerFactoryTest.cs" />
+ <Compile Include="Test\HtmlHelperTest.cs" />
+ <Compile Include="Test\MultiSelectListTest.cs" />
+ <Compile Include="Test\MvcHandlerTest.cs" />
+ <Compile Include="Test\MvcRouteHandlerTest.cs" />
+ <Compile Include="Test\MvcTestHelper.cs" />
+ <Compile Include="Test\RedirectResultTest.cs" />
+ <Compile Include="Test\RedirectToRouteResultTest.cs" />
+ <Compile Include="Test\RouteCollectionExtensionsTest.cs" />
+ <Compile Include="Test\SelectListTest.cs" />
+ <Compile Include="Test\TempDataDictionaryTest.cs" />
+ <Compile Include="Test\TypeHelpersTest.cs" />
+ <Compile Include="Test\UrlHelperTest.cs" />
+ <Compile Include="Test\ViewDataDictionaryTest.cs" />
+ <Compile Include="Test\ViewEnginesTest.cs" />
+ <Compile Include="Test\ViewMasterPageTest.cs" />
+ <Compile Include="Test\ViewPageTest.cs" />
+ <Compile Include="Test\ViewResultTest.cs" />
+ <Compile Include="Test\ViewUserControlTest.cs" />
+ <Compile Include="Test\WebFormViewEngineTest.cs" />
+ <Compile Include="Util\AnonymousObject.cs" />
+ <Compile Include="Util\DictionaryHelper.cs" />
+ <Compile Include="Util\HttpContextHelpers.cs" />
+ <Compile Include="Util\MvcHelper.cs" />
+ <Compile Include="Util\Resolver.cs" />
+ <Compile Include="Properties\AssemblyInfo.cs" />
+ <Compile Include="Util\SimpleValueProvider.cs" />
+ <Compile Include="Util\SimpleViewDataContainer.cs" />
+ </ItemGroup>
+ <ItemGroup>
+ <ProjectReference Include="..\..\src\System.Web.Mvc\System.Web.Mvc.csproj">
+ <Project>{3D3FFD8A-624D-4E9B-954B-E1C105507975}</Project>
+ <Name>System.Web.Mvc</Name>
+ </ProjectReference>
+ <ProjectReference Include="..\..\src\System.Web.Razor\System.Web.Razor.csproj">
+ <Project>{8F18041B-9410-4C36-A9C5-067813DF5F31}</Project>
+ <Name>System.Web.Razor</Name>
+ </ProjectReference>
+ <ProjectReference Include="..\..\src\System.Web.WebPages.Razor\System.Web.WebPages.Razor.csproj">
+ <Project>{0939B11A-FE4E-4BA1-8AD6-D97741EE314F}</Project>
+ <Name>System.Web.WebPages.Razor</Name>
+ </ProjectReference>
+ <ProjectReference Include="..\..\src\System.Web.WebPages\System.Web.WebPages.csproj">
+ <Project>{76EFA9C5-8D7E-4FDF-B710-E20F8B6B00D2}</Project>
+ <Name>System.Web.WebPages</Name>
+ </ProjectReference>
+ <ProjectReference Include="..\Microsoft.TestCommon\Microsoft.TestCommon.csproj">
+ <Project>{FCCC4CB7-BAF7-4A57-9F89-E5766FE536C0}</Project>
+ <Name>Microsoft.TestCommon</Name>
+ </ProjectReference>
+ <ProjectReference Include="..\System.Web.Razor.Test\System.Web.Razor.Test.csproj">
+ <Project>{0BB62A1D-E6B5-49FA-9E3C-6AF679A66DFE}</Project>
+ <Name>System.Web.Razor.Test</Name>
+ </ProjectReference>
+ </ItemGroup>
+ <ItemGroup>
+ <None Include="packages.config" />
+ </ItemGroup>
+ <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
+</Project> \ No newline at end of file
diff --git a/test/System.Web.Mvc.Test/Test/AcceptVerbsAttributeTest.cs b/test/System.Web.Mvc.Test/Test/AcceptVerbsAttributeTest.cs
new file mode 100644
index 00000000..483befba
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Test/AcceptVerbsAttributeTest.cs
@@ -0,0 +1,214 @@
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Collections.Specialized;
+using System.Linq;
+using Moq;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Mvc.Test
+{
+ public class AcceptVerbsAttributeTest
+ {
+ private const string _invalidEnumFormatString = @"The enum '{0}' did not produce the correct array.
+Expected: {1}
+Actual: {2}";
+
+ [Fact]
+ public void ConstructorThrowsIfVerbsIsEmpty()
+ {
+ // Act & Assert
+ Assert.ThrowsArgumentNullOrEmpty(
+ delegate { new AcceptVerbsAttribute(new string[0]); }, "verbs");
+ }
+
+ [Fact]
+ public void ConstructorThrowsIfVerbsIsNull()
+ {
+ // Act & Assert
+ Assert.ThrowsArgumentNullOrEmpty(
+ delegate { new AcceptVerbsAttribute((string[])null); }, "verbs");
+ }
+
+ [Fact]
+ public void EnumToArray()
+ {
+ // Arrange
+ IDictionary<string, HttpVerbs> enumValues = EnumToDictionary<HttpVerbs>();
+ var allCombinations = EnumerableToCombinations(enumValues);
+
+ // Act & assert
+ foreach (var combination in allCombinations)
+ {
+ // generate all the names + values in this combination
+ List<string> aggrNames = new List<string>();
+ HttpVerbs aggrValues = (HttpVerbs)0;
+ foreach (var entry in combination)
+ {
+ aggrNames.Add(entry.Key);
+ aggrValues |= entry.Value;
+ }
+
+ // get the resulting array
+ string[] array = AcceptVerbsAttribute.EnumToArray(aggrValues);
+ var aggrNamesOrdered = aggrNames.OrderBy(name => name, StringComparer.OrdinalIgnoreCase);
+ var arrayOrdered = array.OrderBy(name => name, StringComparer.OrdinalIgnoreCase);
+ bool match = aggrNamesOrdered.SequenceEqual(arrayOrdered, StringComparer.OrdinalIgnoreCase);
+
+ if (!match)
+ {
+ string message = String.Format(_invalidEnumFormatString, aggrValues,
+ aggrNames.Aggregate((a, b) => a + ", " + b),
+ array.Aggregate((a, b) => a + ", " + b));
+ Assert.True(false, message);
+ }
+ }
+ }
+
+ [Fact]
+ public void IsValidForRequestReturnsFalseIfHttpVerbIsNotInVerbsCollection()
+ {
+ // Arrange
+ AcceptVerbsAttribute attr = new AcceptVerbsAttribute("get", "post");
+ ControllerContext context = GetControllerContextWithHttpVerb("HEAD");
+
+ // Act
+ bool result = attr.IsValidForRequest(context, null);
+
+ // Assert
+ Assert.False(result);
+ }
+
+ [Fact]
+ public void IsValidForRequestReturnsTrueIfHttpVerbIsInVerbsCollection()
+ {
+ // Arrange
+ AcceptVerbsAttribute attr = new AcceptVerbsAttribute("get", "post");
+ ControllerContext context = GetControllerContextWithHttpVerb("POST");
+
+ // Act
+ bool result = attr.IsValidForRequest(context, null);
+
+ // Assert
+ Assert.True(result);
+ }
+
+ [Fact]
+ public void IsValidForRequestReturnsTrueIfHttpVerbIsOverridden()
+ {
+ // Arrange
+ AcceptVerbsAttribute attr = new AcceptVerbsAttribute("put");
+ ControllerContext context = GetControllerContextWithHttpVerb("POST", "PUT", null, null);
+
+ // Act
+ bool result = attr.IsValidForRequest(context, null);
+
+ // Assert
+ Assert.True(result);
+ }
+
+ [Fact]
+ public void IsValidForRequestThrowsIfControllerContextIsNull()
+ {
+ // Arrange
+ AcceptVerbsAttribute attr = new AcceptVerbsAttribute("get", "post");
+
+ // Act & Assert
+ Assert.ThrowsArgumentNull(
+ delegate { attr.IsValidForRequest(null, null); }, "controllerContext");
+ }
+
+ [Fact]
+ public void VerbsPropertyFromEnumConstructor()
+ {
+ // Arrange
+ AcceptVerbsAttribute attr = new AcceptVerbsAttribute(HttpVerbs.Get | HttpVerbs.Post);
+
+ // Act
+ ReadOnlyCollection<string> collection = attr.Verbs as ReadOnlyCollection<string>;
+
+ // Assert
+ Assert.NotNull(collection);
+ Assert.Equal(2, collection.Count);
+ Assert.Equal("GET", collection[0]);
+ Assert.Equal("POST", collection[1]);
+ }
+
+ [Fact]
+ public void VerbsPropertyFromStringArrayConstructor()
+ {
+ // Arrange
+ AcceptVerbsAttribute attr = new AcceptVerbsAttribute("get", "post");
+
+ // Act
+ ReadOnlyCollection<string> collection = attr.Verbs as ReadOnlyCollection<string>;
+
+ // Assert
+ Assert.NotNull(collection);
+ Assert.Equal(2, collection.Count);
+ Assert.Equal("get", collection[0]);
+ Assert.Equal("post", collection[1]);
+ }
+
+ internal static ControllerContext GetControllerContextWithHttpVerb(string httpRequestVerb)
+ {
+ return GetControllerContextWithHttpVerb(httpRequestVerb, null, null, null);
+ }
+
+ internal static ControllerContext GetControllerContextWithHttpVerb(string httpRequestVerb, string httpHeaderVerb, string httpFormVerb, string httpQueryStringVerb)
+ {
+ Mock<ControllerContext> mockControllerContext = new Mock<ControllerContext>();
+ mockControllerContext.Setup(c => c.HttpContext.Request.HttpMethod).Returns(httpRequestVerb);
+
+ NameValueCollection headers = new NameValueCollection();
+ if (!String.IsNullOrEmpty(httpHeaderVerb))
+ {
+ headers.Add(HttpRequestExtensions.XHttpMethodOverrideKey, httpHeaderVerb);
+ }
+ mockControllerContext.Setup(c => c.HttpContext.Request.Headers).Returns(headers);
+
+ NameValueCollection form = new NameValueCollection();
+ if (!String.IsNullOrEmpty(httpFormVerb))
+ {
+ form.Add(HttpRequestExtensions.XHttpMethodOverrideKey, httpFormVerb);
+ }
+ mockControllerContext.Setup(c => c.HttpContext.Request.Form).Returns(form);
+
+ NameValueCollection queryString = new NameValueCollection();
+ if (!String.IsNullOrEmpty(httpQueryStringVerb))
+ {
+ queryString.Add(HttpRequestExtensions.XHttpMethodOverrideKey, httpQueryStringVerb);
+ }
+ mockControllerContext.Setup(c => c.HttpContext.Request.QueryString).Returns(queryString);
+
+ return mockControllerContext.Object;
+ }
+
+ private static IDictionary<string, TEnum> EnumToDictionary<TEnum>()
+ {
+ // Arrange
+ var values = Enum.GetValues(typeof(TEnum)).Cast<TEnum>();
+ return values.ToDictionary(value => Enum.GetName(typeof(TEnum), value), value => value);
+ }
+
+ private static IEnumerable<ICollection<T>> EnumerableToCombinations<T>(IEnumerable<T> elements)
+ {
+ List<T> allElements = elements.ToList();
+
+ int maxCount = 1 << allElements.Count;
+ for (int idxCombination = 0; idxCombination < maxCount; idxCombination++)
+ {
+ List<T> thisCollection = new List<T>();
+ for (int idxBit = 0; idxBit < 32; idxBit++)
+ {
+ bool bitActive = (((uint)idxCombination >> idxBit) & 1) != 0;
+ if (bitActive)
+ {
+ thisCollection.Add(allElements[idxBit]);
+ }
+ }
+ yield return thisCollection;
+ }
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Test/ActionDescriptorTest.cs b/test/System.Web.Mvc.Test/Test/ActionDescriptorTest.cs
new file mode 100644
index 00000000..d1abfbd6
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Test/ActionDescriptorTest.cs
@@ -0,0 +1,280 @@
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Linq;
+using System.Reflection;
+using Moq;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Mvc.Test
+{
+ public class ActionDescriptorTest
+ {
+ [Fact]
+ public void ExtractParameterOrDefaultFromDictionary_ReturnsDefaultParameterValueIfMismatch()
+ {
+ // Arrange
+ Dictionary<string, object> dictionary = new Dictionary<string, object>()
+ {
+ { "stringParameterWithDefaultValue", 42 }
+ };
+
+ // Act
+ object value = ActionDescriptor.ExtractParameterOrDefaultFromDictionary(ParameterExtractionController.StringParameterWithDefaultValue, dictionary);
+
+ // Assert
+ Assert.Equal("hello", value);
+ }
+
+ [Fact]
+ public void ExtractParameterOrDefaultFromDictionary_ReturnsDefaultTypeValueIfNoMatchAndNoDefaultParameterValue()
+ {
+ // Arrange
+ Dictionary<string, object> dictionary = new Dictionary<string, object>();
+
+ // Act
+ object value = ActionDescriptor.ExtractParameterOrDefaultFromDictionary(ParameterExtractionController.IntParameter, dictionary);
+
+ // Assert
+ Assert.Equal(0, value);
+ }
+
+ [Fact]
+ public void ExtractParameterOrDefaultFromDictionary_ReturnsDictionaryValueIfTypeMatch()
+ {
+ // Arrange
+ Dictionary<string, object> dictionary = new Dictionary<string, object>()
+ {
+ { "stringParameterNoDefaultValue", "someValue" }
+ };
+
+ // Act
+ object value = ActionDescriptor.ExtractParameterOrDefaultFromDictionary(ParameterExtractionController.StringParameterNoDefaultValue, dictionary);
+
+ // Assert
+ Assert.Equal("someValue", value);
+ }
+
+ [Fact]
+ public void GetCustomAttributesReturnsEmptyArrayOfAttributeType()
+ {
+ // Arrange
+ ActionDescriptor ad = GetActionDescriptor();
+
+ // Act
+ ObsoleteAttribute[] attrs = (ObsoleteAttribute[])ad.GetCustomAttributes(typeof(ObsoleteAttribute), true);
+
+ // Assert
+ Assert.Empty(attrs);
+ }
+
+ [Fact]
+ public void GetCustomAttributesThrowsIfAttributeTypeIsNull()
+ {
+ // Arrange
+ ActionDescriptor ad = GetActionDescriptor();
+
+ // Act & assert
+ Assert.ThrowsArgumentNull(
+ delegate { ad.GetCustomAttributes(null /* attributeType */, true); }, "attributeType");
+ }
+
+ [Fact]
+ public void GetCustomAttributesWithoutAttributeTypeCallsGetCustomAttributesWithAttributeType()
+ {
+ // Arrange
+ object[] expected = new object[0];
+ Mock<ActionDescriptor> mockDescriptor = new Mock<ActionDescriptor>() { CallBase = true };
+ mockDescriptor.Setup(d => d.GetCustomAttributes(typeof(object), true)).Returns(expected);
+ ActionDescriptor ad = mockDescriptor.Object;
+
+ // Act
+ object[] returned = ad.GetCustomAttributes(true /* inherit */);
+
+ // Assert
+ Assert.Same(expected, returned);
+ }
+
+ [Fact]
+ public void GetFilterAttributes_CallsGetCustomAttributes()
+ {
+ // Arrange
+ var mockDescriptor = new Mock<ActionDescriptor>() { CallBase = true };
+ mockDescriptor.Setup(d => d.GetCustomAttributes(typeof(FilterAttribute), true)).Returns(new object[] { new Mock<FilterAttribute>().Object }).Verifiable();
+
+ // Act
+ var result = mockDescriptor.Object.GetFilterAttributes(true).ToList();
+
+ // Assert
+ mockDescriptor.Verify();
+ Assert.Single(result);
+ }
+
+ [Fact]
+ public void GetSelectorsReturnsEmptyCollection()
+ {
+ // Arrange
+ ActionDescriptor ad = GetActionDescriptor();
+
+ // Act
+ ICollection<ActionSelector> selectors = ad.GetSelectors();
+
+ // Assert
+ Assert.IsType<ActionSelector[]>(selectors);
+ Assert.Empty(selectors);
+ }
+
+ [Fact]
+ public void IsDefinedReturnsFalse()
+ {
+ // Arrange
+ ActionDescriptor ad = GetActionDescriptor();
+
+ // Act
+ bool isDefined = ad.IsDefined(typeof(object), true);
+
+ // Assert
+ Assert.False(isDefined);
+ }
+
+ [Fact]
+ public void IsDefinedThrowsIfAttributeTypeIsNull()
+ {
+ // Arrange
+ ActionDescriptor ad = GetActionDescriptor();
+
+ // Act & assert
+ Assert.ThrowsArgumentNull(
+ delegate { ad.IsDefined(null /* attributeType */, true); }, "attributeType");
+ }
+
+ [Fact]
+ public void UniqueId_SameTypeControllerDescriptorAndActionName_SameID()
+ {
+ // Arrange
+ var controllerDescriptor = new Mock<ControllerDescriptor>().Object;
+
+ var descriptor1 = new Mock<ActionDescriptor> { CallBase = true };
+ descriptor1.SetupGet(d => d.ControllerDescriptor).Returns(controllerDescriptor);
+ descriptor1.SetupGet(d => d.ActionName).Returns("Action1");
+
+ var descriptor2 = new Mock<ActionDescriptor> { CallBase = true };
+ descriptor2.SetupGet(d => d.ControllerDescriptor).Returns(controllerDescriptor);
+ descriptor2.SetupGet(d => d.ActionName).Returns("Action1");
+
+ // Act
+ var id1 = descriptor1.Object.UniqueId;
+ var id2 = descriptor2.Object.UniqueId;
+
+ // Assert
+ Assert.Equal(id1, id2);
+ }
+
+ [Fact]
+ public void UniqueId_VariesWithActionName()
+ {
+ // Arrange
+ var controllerDescriptor = new Mock<ControllerDescriptor>().Object;
+
+ var descriptor1 = new Mock<ActionDescriptor> { CallBase = true };
+ descriptor1.SetupGet(d => d.ControllerDescriptor).Returns(controllerDescriptor);
+ descriptor1.SetupGet(d => d.ActionName).Returns("Action1");
+
+ var descriptor2 = new Mock<ActionDescriptor> { CallBase = true };
+ descriptor2.SetupGet(d => d.ControllerDescriptor).Returns(controllerDescriptor);
+ descriptor2.SetupGet(d => d.ActionName).Returns("Action2");
+
+ // Act
+ var id1 = descriptor1.Object.UniqueId;
+ var id2 = descriptor2.Object.UniqueId;
+
+ // Assert
+ Assert.NotEqual(id1, id2);
+ }
+
+ [Fact]
+ public void UniqueId_VariesWithControllerDescriptorsUniqueId()
+ {
+ // Arrange
+ var controllerDescriptor1 = new Mock<ControllerDescriptor>();
+ controllerDescriptor1.SetupGet(cd => cd.UniqueId).Returns("1");
+ var descriptor1 = new Mock<ActionDescriptor> { CallBase = true };
+ descriptor1.SetupGet(d => d.ControllerDescriptor).Returns(controllerDescriptor1.Object);
+ descriptor1.SetupGet(d => d.ActionName).Returns("Action1");
+
+ var controllerDescriptor2 = new Mock<ControllerDescriptor>();
+ controllerDescriptor2.SetupGet(cd => cd.UniqueId).Returns("2");
+ var descriptor2 = new Mock<ActionDescriptor> { CallBase = true };
+ descriptor2.SetupGet(d => d.ControllerDescriptor).Returns(controllerDescriptor2.Object);
+ descriptor2.SetupGet(d => d.ActionName).Returns("Action1");
+
+ // Act
+ var id1 = descriptor1.Object.UniqueId;
+ var id2 = descriptor2.Object.UniqueId;
+
+ // Assert
+ Assert.NotEqual(id1, id2);
+ }
+
+ [Fact]
+ public void UniqueId_VariesWithActionDescriptorType()
+ {
+ // Arrange
+ var descriptor1 = new BaseDescriptor();
+ var descriptor2 = new DerivedDescriptor();
+
+ // Act
+ var id1 = descriptor1.UniqueId;
+ var id2 = descriptor2.UniqueId;
+
+ // Assert
+ Assert.NotEqual(id1, id2);
+ }
+
+ class BaseDescriptor : ActionDescriptor
+ {
+ static ControllerDescriptor controllerDescriptor = new Mock<ControllerDescriptor>().Object;
+
+ public override string ActionName
+ {
+ get { return "ActionName"; }
+ }
+
+ public override ControllerDescriptor ControllerDescriptor
+ {
+ get { return controllerDescriptor; }
+ }
+
+ public override object Execute(ControllerContext controllerContext, IDictionary<string, object> parameters)
+ {
+ throw new NotImplementedException();
+ }
+
+ public override ParameterDescriptor[] GetParameters()
+ {
+ throw new NotImplementedException();
+ }
+ }
+
+ class DerivedDescriptor : BaseDescriptor
+ {
+ }
+
+ private static ActionDescriptor GetActionDescriptor()
+ {
+ Mock<ActionDescriptor> mockDescriptor = new Mock<ActionDescriptor>() { CallBase = true };
+ return mockDescriptor.Object;
+ }
+
+ private class ParameterExtractionController : Controller
+ {
+ public static readonly ParameterInfo IntParameter = typeof(ParameterExtractionController).GetMethod("SomeMethod").GetParameters()[0];
+ public static readonly ParameterInfo StringParameterNoDefaultValue = typeof(ParameterExtractionController).GetMethod("SomeMethod").GetParameters()[1];
+ public static readonly ParameterInfo StringParameterWithDefaultValue = typeof(ParameterExtractionController).GetMethod("SomeMethod").GetParameters()[2];
+
+ public void SomeMethod(int intParameter, string stringParameterNoDefaultValue, [DefaultValue("hello")] string stringParameterWithDefaultValue)
+ {
+ }
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Test/ActionExecutedContextTest.cs b/test/System.Web.Mvc.Test/Test/ActionExecutedContextTest.cs
new file mode 100644
index 00000000..9f98ad4c
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Test/ActionExecutedContextTest.cs
@@ -0,0 +1,52 @@
+using System.Web.TestUtil;
+using Moq;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Mvc.Test
+{
+ public class ActionExecutedContextTest
+ {
+ [Fact]
+ public void ConstructorThrowsIfActionDescriptorIsNull()
+ {
+ // Arrange
+ ControllerContext controllerContext = new Mock<ControllerContext>().Object;
+ ActionDescriptor actionDescriptor = null;
+ bool canceled = true;
+ Exception exception = null;
+
+ // Act & assert
+ Assert.ThrowsArgumentNull(
+ delegate { new ActionExecutedContext(controllerContext, actionDescriptor, canceled, exception); }, "actionDescriptor");
+ }
+
+ [Fact]
+ public void PropertiesAreSetByConstructor()
+ {
+ // Arrange
+ ControllerContext controllerContext = new Mock<ControllerContext>().Object;
+ ActionDescriptor actionDescriptor = new Mock<ActionDescriptor>().Object;
+ bool canceled = true;
+ Exception exception = new Exception();
+
+ // Act
+ ActionExecutedContext actionExecutedContext = new ActionExecutedContext(controllerContext, actionDescriptor, canceled, exception);
+
+ // Assert
+ Assert.Equal(actionDescriptor, actionExecutedContext.ActionDescriptor);
+ Assert.Equal(canceled, actionExecutedContext.Canceled);
+ Assert.Equal(exception, actionExecutedContext.Exception);
+ }
+
+ [Fact]
+ public void ResultProperty()
+ {
+ // Arrange
+ ActionExecutedContext actionExecutedContext = new Mock<ActionExecutedContext>().Object;
+
+ // Act & assert
+ MemberHelper.TestPropertyWithDefaultInstance(actionExecutedContext, "Result", new ViewResult(), EmptyResult.Instance);
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Test/ActionExecutingContextTest.cs b/test/System.Web.Mvc.Test/Test/ActionExecutingContextTest.cs
new file mode 100644
index 00000000..db992447
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Test/ActionExecutingContextTest.cs
@@ -0,0 +1,52 @@
+using System.Collections.Generic;
+using Moq;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Mvc.Test
+{
+ public class ActionExecutingContextTest
+ {
+ [Fact]
+ public void ConstructorThrowsIfActionDescriptorIsNull()
+ {
+ // Arrange
+ ControllerContext controllerContext = new Mock<ControllerContext>().Object;
+ ActionDescriptor actionDescriptor = null;
+ Dictionary<string, object> actionParameters = new Dictionary<string, object>();
+
+ // Act & assert
+ Assert.ThrowsArgumentNull(
+ delegate { new ActionExecutingContext(controllerContext, actionDescriptor, actionParameters); }, "actionDescriptor");
+ }
+
+ [Fact]
+ public void ConstructorThrowsIfActionParametersIsNull()
+ {
+ // Arrange
+ ControllerContext controllerContext = new Mock<ControllerContext>().Object;
+ ActionDescriptor actionDescriptor = new Mock<ActionDescriptor>().Object;
+ Dictionary<string, object> actionParameters = null;
+
+ // Act & assert
+ Assert.ThrowsArgumentNull(
+ delegate { new ActionExecutingContext(controllerContext, actionDescriptor, actionParameters); }, "actionParameters");
+ }
+
+ [Fact]
+ public void PropertiesAreSetByConstructor()
+ {
+ // Arrange
+ ControllerContext controllerContext = new Mock<ControllerContext>().Object;
+ ActionDescriptor actionDescriptor = new Mock<ActionDescriptor>().Object;
+ Dictionary<string, object> actionParameters = new Dictionary<string, object>();
+
+ // Act
+ ActionExecutingContext actionExecutingContext = new ActionExecutingContext(controllerContext, actionDescriptor, actionParameters);
+
+ // Assert
+ Assert.Equal(actionDescriptor, actionExecutingContext.ActionDescriptor);
+ Assert.Equal(actionParameters, actionExecutingContext.ActionParameters);
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Test/ActionFilterAttributeTest.cs b/test/System.Web.Mvc.Test/Test/ActionFilterAttributeTest.cs
new file mode 100644
index 00000000..2d19fd13
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Test/ActionFilterAttributeTest.cs
@@ -0,0 +1,42 @@
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Mvc.Test
+{
+ public class ActionFilterAttributeTest
+ {
+ [Fact]
+ public void DefaultOrderIsNegativeOne()
+ {
+ // Act
+ var attr = new EmptyActionFilterAttribute();
+
+ // Assert
+ Assert.Equal(-1, attr.Order);
+ }
+
+ [Fact]
+ public void OrderIsSetCorrectly()
+ {
+ // Act
+ var attr = new EmptyActionFilterAttribute() { Order = 98052 };
+
+ // Assert
+ Assert.Equal(98052, attr.Order);
+ }
+
+ [Fact]
+ public void SpecifyingInvalidOrderThrows()
+ {
+ // Act & Assert
+ Assert.ThrowsArgumentOutOfRange(
+ delegate { new EmptyActionFilterAttribute() { Order = -2 }; },
+ "value",
+ "Order must be greater than or equal to -1.");
+ }
+
+ private class EmptyActionFilterAttribute : ActionFilterAttribute
+ {
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Test/ActionMethodDispatcherCacheTest.cs b/test/System.Web.Mvc.Test/Test/ActionMethodDispatcherCacheTest.cs
new file mode 100644
index 00000000..d46c8b4d
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Test/ActionMethodDispatcherCacheTest.cs
@@ -0,0 +1,24 @@
+using System.Reflection;
+using Xunit;
+
+namespace System.Web.Mvc.Test
+{
+ public class ActionMethodDispatcherCacheTest
+ {
+ [Fact]
+ public void GetDispatcher()
+ {
+ // Arrange
+ MethodInfo methodInfo = typeof(object).GetMethod("ToString");
+ ActionMethodDispatcherCache cache = new ActionMethodDispatcherCache();
+
+ // Act
+ ActionMethodDispatcher dispatcher1 = cache.GetDispatcher(methodInfo);
+ ActionMethodDispatcher dispatcher2 = cache.GetDispatcher(methodInfo);
+
+ // Assert
+ Assert.Same(methodInfo, dispatcher1.MethodInfo);
+ Assert.Same(dispatcher1, dispatcher2);
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Test/ActionMethodDispatcherTest.cs b/test/System.Web.Mvc.Test/Test/ActionMethodDispatcherTest.cs
new file mode 100644
index 00000000..74242f1c
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Test/ActionMethodDispatcherTest.cs
@@ -0,0 +1,126 @@
+using System.Reflection;
+using Xunit;
+
+namespace System.Web.Mvc.Test
+{
+ public class ActionMethodDispatcherTest
+ {
+ [Fact]
+ public void ExecuteWithNormalActionMethod()
+ {
+ // Arrange
+ DispatcherController controller = new DispatcherController();
+ object[] parameters = new object[] { 5, "some string", new DateTime(2001, 1, 1) };
+ MethodInfo methodInfo = typeof(DispatcherController).GetMethod("NormalAction");
+ ActionMethodDispatcher dispatcher = new ActionMethodDispatcher(methodInfo);
+
+ // Act
+ object returnValue = dispatcher.Execute(controller, parameters);
+
+ // Assert
+ var stringResult = Assert.IsType<string>(returnValue);
+ Assert.Equal("Hello from NormalAction!", stringResult);
+
+ Assert.Equal(5, controller._i);
+ Assert.Equal("some string", controller._s);
+ Assert.Equal(new DateTime(2001, 1, 1), controller._dt);
+ }
+
+ [Fact]
+ public void ExecuteWithParameterlessActionMethod()
+ {
+ // Arrange
+ DispatcherController controller = new DispatcherController();
+ object[] parameters = new object[0];
+ MethodInfo methodInfo = typeof(DispatcherController).GetMethod("ParameterlessAction");
+ ActionMethodDispatcher dispatcher = new ActionMethodDispatcher(methodInfo);
+
+ // Act
+ object returnValue = dispatcher.Execute(controller, parameters);
+
+ // Assert
+ var intResult = Assert.IsType<int>(returnValue);
+ Assert.Equal(53, intResult);
+ }
+
+ [Fact]
+ public void ExecuteWithStaticActionMethod()
+ {
+ // Arrange
+ DispatcherController controller = new DispatcherController();
+ object[] parameters = new object[0];
+ MethodInfo methodInfo = typeof(DispatcherController).GetMethod("StaticAction");
+ ActionMethodDispatcher dispatcher = new ActionMethodDispatcher(methodInfo);
+
+ // Act
+ object returnValue = dispatcher.Execute(controller, parameters);
+
+ // Assert
+ var intResult = Assert.IsType<int>(returnValue);
+ Assert.Equal(89, intResult);
+ }
+
+ [Fact]
+ public void ExecuteWithVoidActionMethod()
+ {
+ // Arrange
+ DispatcherController controller = new DispatcherController();
+ object[] parameters = new object[] { 5, "some string", new DateTime(2001, 1, 1) };
+ MethodInfo methodInfo = typeof(DispatcherController).GetMethod("VoidAction");
+ ActionMethodDispatcher dispatcher = new ActionMethodDispatcher(methodInfo);
+
+ // Act
+ object returnValue = dispatcher.Execute(controller, parameters);
+
+ // Assert
+ Assert.Null(returnValue);
+ Assert.Equal(5, controller._i);
+ Assert.Equal("some string", controller._s);
+ Assert.Equal(new DateTime(2001, 1, 1), controller._dt);
+ }
+
+ [Fact]
+ public void MethodInfoProperty()
+ {
+ // Arrange
+ MethodInfo original = typeof(object).GetMethod("ToString");
+ ActionMethodDispatcher dispatcher = new ActionMethodDispatcher(original);
+
+ // Act
+ MethodInfo returned = dispatcher.MethodInfo;
+
+ // Assert
+ Assert.Same(original, returned);
+ }
+
+ private class DispatcherController : Controller
+ {
+ public int _i;
+ public string _s;
+ public DateTime _dt;
+
+ public object NormalAction(int i, string s, DateTime dt)
+ {
+ VoidAction(i, s, dt);
+ return "Hello from NormalAction!";
+ }
+
+ public int ParameterlessAction()
+ {
+ return 53;
+ }
+
+ public void VoidAction(int i, string s, DateTime dt)
+ {
+ _i = i;
+ _s = s;
+ _dt = dt;
+ }
+
+ public static int StaticAction()
+ {
+ return 89;
+ }
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Test/ActionMethodSelectorTest.cs b/test/System.Web.Mvc.Test/Test/ActionMethodSelectorTest.cs
new file mode 100644
index 00000000..3f988766
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Test/ActionMethodSelectorTest.cs
@@ -0,0 +1,212 @@
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Mvc.Test
+{
+ public class ActionMethodSelectorTest
+ {
+ [Fact]
+ public void AliasedMethodsProperty()
+ {
+ // Arrange
+ Type controllerType = typeof(MethodLocatorController);
+
+ // Act
+ ActionMethodSelector selector = new ActionMethodSelector(controllerType);
+
+ // Assert
+ Assert.Equal(2, selector.AliasedMethods.Length);
+
+ List<MethodInfo> sortedAliasedMethods = selector.AliasedMethods.OrderBy(methodInfo => methodInfo.Name).ToList();
+ Assert.Equal("Bar", sortedAliasedMethods[0].Name);
+ Assert.Equal("FooRenamed", sortedAliasedMethods[1].Name);
+ }
+
+ [Fact]
+ public void ControllerTypeProperty()
+ {
+ // Arrange
+ Type controllerType = typeof(MethodLocatorController);
+ ActionMethodSelector selector = new ActionMethodSelector(controllerType);
+
+ // Act & Assert
+ Assert.Same(controllerType, selector.ControllerType);
+ }
+
+ [Fact]
+ public void FindActionMethodReturnsMatchingMethodIfOneMethodMatches()
+ {
+ // Arrange
+ Type controllerType = typeof(SelectionAttributeController);
+ ActionMethodSelector selector = new ActionMethodSelector(controllerType);
+
+ // Act
+ MethodInfo matchedMethod = selector.FindActionMethod(null, "OneMatch");
+
+ // Assert
+ Assert.Equal("OneMatch", matchedMethod.Name);
+ Assert.Equal(typeof(string), matchedMethod.GetParameters()[0].ParameterType);
+ }
+
+ [Fact]
+ public void FindActionMethodReturnsMethodWithActionSelectionAttributeIfMultipleMethodsMatchRequest()
+ {
+ // DevDiv Bugs 212062: If multiple action methods match a request, we should match only the methods with an
+ // [ActionMethod] attribute since we assume those methods are more specific.
+
+ // Arrange
+ Type controllerType = typeof(SelectionAttributeController);
+ ActionMethodSelector selector = new ActionMethodSelector(controllerType);
+
+ // Act
+ MethodInfo matchedMethod = selector.FindActionMethod(null, "ShouldMatchMethodWithSelectionAttribute");
+
+ // Assert
+ Assert.Equal("MethodHasSelectionAttribute1", matchedMethod.Name);
+ }
+
+ [Fact]
+ public void FindActionMethodReturnsNullIfNoMethodMatches()
+ {
+ // Arrange
+ Type controllerType = typeof(SelectionAttributeController);
+ ActionMethodSelector selector = new ActionMethodSelector(controllerType);
+
+ // Act
+ MethodInfo matchedMethod = selector.FindActionMethod(null, "ZeroMatch");
+
+ // Assert
+ Assert.Null(matchedMethod);
+ }
+
+ [Fact]
+ public void FindActionMethodThrowsIfMultipleMethodsMatch()
+ {
+ // Arrange
+ Type controllerType = typeof(SelectionAttributeController);
+ ActionMethodSelector selector = new ActionMethodSelector(controllerType);
+
+ // Act & veriy
+ Assert.Throws<AmbiguousMatchException>(
+ delegate { selector.FindActionMethod(null, "TwoMatch"); },
+ @"The current request for action 'TwoMatch' on controller type 'SelectionAttributeController' is ambiguous between the following action methods:
+Void TwoMatch2() on type System.Web.Mvc.Test.ActionMethodSelectorTest+SelectionAttributeController
+Void TwoMatch() on type System.Web.Mvc.Test.ActionMethodSelectorTest+SelectionAttributeController");
+ }
+
+ [Fact]
+ public void NonAliasedMethodsProperty()
+ {
+ // Arrange
+ Type controllerType = typeof(MethodLocatorController);
+
+ // Act
+ ActionMethodSelector selector = new ActionMethodSelector(controllerType);
+
+ // Assert
+ Assert.Single(selector.NonAliasedMethods);
+
+ List<MethodInfo> sortedMethods = selector.NonAliasedMethods["foo"].OrderBy(methodInfo => methodInfo.GetParameters().Length).ToList();
+ Assert.Equal("Foo", sortedMethods[0].Name);
+ Assert.Empty(sortedMethods[0].GetParameters());
+ Assert.Equal("Foo", sortedMethods[1].Name);
+ Assert.Equal(typeof(string), sortedMethods[1].GetParameters()[0].ParameterType);
+ }
+
+ private class MethodLocatorController : Controller
+ {
+ public void Foo()
+ {
+ }
+
+ public void Foo(string s)
+ {
+ }
+
+ [ActionName("Foo")]
+ public void FooRenamed()
+ {
+ }
+
+ [ActionName("Bar")]
+ public void Bar()
+ {
+ }
+
+ [ActionName("PrivateVoid")]
+ private void PrivateVoid()
+ {
+ }
+
+ protected void ProtectedVoidAction()
+ {
+ }
+
+ public static void StaticMethod()
+ {
+ }
+
+ // ensure that methods inheriting from Controller or a base class are not matched
+ [ActionName("Blah")]
+ protected override void ExecuteCore()
+ {
+ throw new NotImplementedException();
+ }
+
+ public string StringProperty { get; set; }
+
+#pragma warning disable 0067
+ public event EventHandler<EventArgs> SomeEvent;
+#pragma warning restore 0067
+ }
+
+ private class SelectionAttributeController : Controller
+ {
+ [Match(false)]
+ public void OneMatch()
+ {
+ }
+
+ public void OneMatch(string s)
+ {
+ }
+
+ public void TwoMatch()
+ {
+ }
+
+ [ActionName("TwoMatch")]
+ public void TwoMatch2()
+ {
+ }
+
+ [Match(true), ActionName("ShouldMatchMethodWithSelectionAttribute")]
+ public void MethodHasSelectionAttribute1()
+ {
+ }
+
+ [ActionName("ShouldMatchMethodWithSelectionAttribute")]
+ public void MethodDoesNotHaveSelectionAttribute1()
+ {
+ }
+
+ private class MatchAttribute : ActionMethodSelectorAttribute
+ {
+ private bool _match;
+
+ public MatchAttribute(bool match)
+ {
+ _match = match;
+ }
+
+ public override bool IsValidForRequest(ControllerContext controllerContext, MethodInfo methodInfo)
+ {
+ return _match;
+ }
+ }
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Test/ActionNameAttributeTest.cs b/test/System.Web.Mvc.Test/Test/ActionNameAttributeTest.cs
new file mode 100644
index 00000000..1c394163
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Test/ActionNameAttributeTest.cs
@@ -0,0 +1,60 @@
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Mvc.Test
+{
+ public class ActionNameAttributeTest
+ {
+ [Fact]
+ public void ConstructorThrowsIfNameIsEmpty()
+ {
+ // Act & Assert
+ Assert.ThrowsArgumentNullOrEmpty(
+ delegate { new ActionNameAttribute(String.Empty); }, "name");
+ }
+
+ [Fact]
+ public void ConstructorThrowsIfNameIsNull()
+ {
+ // Act & Assert
+ Assert.ThrowsArgumentNullOrEmpty(
+ delegate { new ActionNameAttribute(null); }, "name");
+ }
+
+ [Fact]
+ public void IsValidForRequestReturnsFalseIfGivenNameDoesNotMatch()
+ {
+ // Arrange
+ ActionNameAttribute attr = new ActionNameAttribute("Bar");
+
+ // Act
+ bool returned = attr.IsValidName(null, "foo", null);
+
+ // Assert
+ Assert.False(returned);
+ }
+
+ [Fact]
+ public void IsValidForRequestReturnsTrueIfGivenNameMatches()
+ {
+ // Arrange
+ ActionNameAttribute attr = new ActionNameAttribute("Bar");
+
+ // Act
+ bool returned = attr.IsValidName(null, "bar", null);
+
+ // Assert
+ Assert.True(returned);
+ }
+
+ [Fact]
+ public void NameProperty()
+ {
+ // Arrange
+ ActionNameAttribute attr = new ActionNameAttribute("someName");
+
+ // Act & Assert
+ Assert.Equal("someName", attr.Name);
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Test/AdditionalMetadataAttributeTest.cs b/test/System.Web.Mvc.Test/Test/AdditionalMetadataAttributeTest.cs
new file mode 100644
index 00000000..2f67dee1
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Test/AdditionalMetadataAttributeTest.cs
@@ -0,0 +1,73 @@
+using Moq;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Mvc.Test
+{
+ public class AdditionalMetadataAttributeTest
+ {
+ [Fact]
+ public void GuardClauses()
+ {
+ // Act & assert
+ Assert.ThrowsArgumentNull(
+ () => new AdditionalMetadataAttribute(null, new object()),
+ "name");
+
+ AdditionalMetadataAttribute attr = new AdditionalMetadataAttribute("key", null);
+ Assert.ThrowsArgumentNull(
+ () => attr.OnMetadataCreated(null),
+ "metadata");
+ }
+
+ [Fact]
+ public void OnMetaDataCreatedSetsAdditionalValue()
+ {
+ // Arrange
+ string name = "name";
+ object value = new object();
+
+ ModelMetadata modelMetadata = new ModelMetadata(new Mock<ModelMetadataProvider>().Object, null, null, typeof(object), null);
+ AdditionalMetadataAttribute attr = new AdditionalMetadataAttribute(name, value);
+
+ // Act
+ attr.OnMetadataCreated(modelMetadata);
+
+ // Assert
+ Assert.Equal(modelMetadata.AdditionalValues[name], value);
+ Assert.Equal(attr.Name, name);
+ Assert.Equal(attr.Value, value);
+ }
+
+ [Fact]
+ public void MultipleAttributesCanSetValuesOnMetadata()
+ {
+ // Arrange
+ string name1 = "name1";
+ string name2 = "name2";
+
+ object value1 = new object();
+ object value2 = new object();
+ object value3 = new object();
+
+ ModelMetadata modelMetadata = new ModelMetadata(new Mock<ModelMetadataProvider>().Object, null, null, typeof(object), null);
+ AdditionalMetadataAttribute attr1 = new AdditionalMetadataAttribute(name1, value1);
+ AdditionalMetadataAttribute attr2 = new AdditionalMetadataAttribute(name2, value2);
+ AdditionalMetadataAttribute attr3 = new AdditionalMetadataAttribute(name1, value3);
+
+ // Act
+ attr1.OnMetadataCreated(modelMetadata);
+ attr2.OnMetadataCreated(modelMetadata);
+ attr3.OnMetadataCreated(modelMetadata);
+
+ // Assert
+ Assert.Equal(2, modelMetadata.AdditionalValues.Count);
+ Assert.Equal(modelMetadata.AdditionalValues[name1], value3);
+ Assert.Equal(modelMetadata.AdditionalValues[name2], value2);
+
+ Assert.NotEqual(attr1.TypeId, attr2.TypeId);
+ Assert.NotEqual(attr2.TypeId, attr3.TypeId);
+ Assert.NotEqual(attr3.TypeId, attr1.TypeId);
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Test/AjaxHelperTest.cs b/test/System.Web.Mvc.Test/Test/AjaxHelperTest.cs
new file mode 100644
index 00000000..3cfa0b36
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Test/AjaxHelperTest.cs
@@ -0,0 +1,236 @@
+using System.Web.Routing;
+using Moq;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Mvc.Test
+{
+ public class AjaxHelperTest
+ {
+ [Fact]
+ public void ConstructorWithNullViewContextThrows()
+ {
+ // Assert
+ Assert.ThrowsArgumentNull(
+ delegate { AjaxHelper ajaxHelper = new AjaxHelper(null, new Mock<IViewDataContainer>().Object); },
+ "viewContext");
+ }
+
+ [Fact]
+ public void ConstructorWithNullViewDataContainerThrows()
+ {
+ // Assert
+ Assert.ThrowsArgumentNull(
+ delegate { AjaxHelper ajaxHelper = new AjaxHelper(new Mock<ViewContext>().Object, null); },
+ "viewDataContainer");
+ }
+
+ [Fact]
+ public void ConstructorSetsProperties1()
+ {
+ // Arrange
+ ViewContext viewContext = new Mock<ViewContext>().Object;
+ IViewDataContainer vdc = new Mock<IViewDataContainer>().Object;
+
+ // Act
+ AjaxHelper ajaxHelper = new AjaxHelper(viewContext, vdc);
+
+ // Assert
+ Assert.Equal(viewContext, ajaxHelper.ViewContext);
+ Assert.Equal(vdc, ajaxHelper.ViewDataContainer);
+ Assert.Equal(RouteTable.Routes, ajaxHelper.RouteCollection);
+ }
+
+ [Fact]
+ public void ConstructorSetsProperties2()
+ {
+ // Arrange
+ ViewContext viewContext = new Mock<ViewContext>().Object;
+ IViewDataContainer vdc = new Mock<IViewDataContainer>().Object;
+ RouteCollection rc = new RouteCollection();
+
+ // Act
+ AjaxHelper ajaxHelper = new AjaxHelper(viewContext, vdc, rc);
+
+ // Assert
+ Assert.Equal(viewContext, ajaxHelper.ViewContext);
+ Assert.Equal(vdc, ajaxHelper.ViewDataContainer);
+ Assert.Equal(rc, ajaxHelper.RouteCollection);
+ }
+
+ [Fact]
+ public void GenericHelperConstructorSetsProperties1()
+ {
+ // Arrange
+ ViewContext viewContext = new Mock<ViewContext>().Object;
+ ViewDataDictionary<Controller> vdd = new ViewDataDictionary<Controller>(new Mock<Controller>().Object);
+ Mock<IViewDataContainer> vdc = new Mock<IViewDataContainer>();
+ vdc.Setup(v => v.ViewData).Returns(vdd);
+
+ // Act
+ AjaxHelper<Controller> ajaxHelper = new AjaxHelper<Controller>(viewContext, vdc.Object);
+
+ // Assert
+ Assert.Equal(viewContext, ajaxHelper.ViewContext);
+ Assert.Equal(vdc.Object, ajaxHelper.ViewDataContainer);
+ Assert.Equal(RouteTable.Routes, ajaxHelper.RouteCollection);
+ Assert.Equal(vdd.Model, ajaxHelper.ViewData.Model);
+ }
+
+ [Fact]
+ public void GenericHelperConstructorSetsProperties2()
+ {
+ // Arrange
+ ViewContext viewContext = new Mock<ViewContext>().Object;
+ ViewDataDictionary<Controller> vdd = new ViewDataDictionary<Controller>(new Mock<Controller>().Object);
+ Mock<IViewDataContainer> vdc = new Mock<IViewDataContainer>();
+ vdc.Setup(v => v.ViewData).Returns(vdd);
+ RouteCollection rc = new RouteCollection();
+
+ // Act
+ AjaxHelper<Controller> ajaxHelper = new AjaxHelper<Controller>(viewContext, vdc.Object, rc);
+
+ // Assert
+ Assert.Equal(viewContext, ajaxHelper.ViewContext);
+ Assert.Equal(vdc.Object, ajaxHelper.ViewDataContainer);
+ Assert.Equal(rc, ajaxHelper.RouteCollection);
+ Assert.Equal(vdd.Model, ajaxHelper.ViewData.Model);
+ }
+
+ [Fact]
+ public void GlobalizationScriptPathPropertyDefault()
+ {
+ try
+ {
+ // Act
+ AjaxHelper.GlobalizationScriptPath = null;
+
+ // Assert
+ Assert.Equal("~/Scripts/Globalization", AjaxHelper.GlobalizationScriptPath);
+ }
+ finally
+ {
+ AjaxHelper.GlobalizationScriptPath = null;
+ }
+ }
+
+ [Fact]
+ public void GlobalizationScriptPathPropertySet()
+ {
+ try
+ {
+ // Act
+ AjaxHelper.GlobalizationScriptPath = "/Foo/Bar";
+
+ // Assert
+ Assert.Equal("/Foo/Bar", AjaxHelper.GlobalizationScriptPath);
+ }
+ finally
+ {
+ AjaxHelper.GlobalizationScriptPath = null;
+ }
+ }
+
+ [Fact]
+ public void JavaScriptStringEncodeReturnsEmptyStringIfMessageIsEmpty()
+ {
+ // Arrange
+ AjaxHelper ajaxHelper = GetAjaxHelper();
+
+ // Act
+ string encoded = ajaxHelper.JavaScriptStringEncode(String.Empty);
+
+ // Assert
+ Assert.Equal(String.Empty, encoded);
+ }
+
+ [Fact]
+ public void JavaScriptStringEncodeReturnsEncodedMessage()
+ {
+ // Arrange
+ string message = "I said, \"Hello, world!\"\nHow are you?";
+ AjaxHelper ajaxHelper = GetAjaxHelper();
+
+ // Act
+ string encoded = ajaxHelper.JavaScriptStringEncode(message);
+
+ // Assert
+ Assert.Equal(@"I said, \""Hello, world!\""\nHow are you?", encoded);
+ }
+
+ [Fact]
+ public void JavaScriptStringEncodeReturnsNullIfMessageIsNull()
+ {
+ // Arrange
+ AjaxHelper ajaxHelper = GetAjaxHelper();
+
+ // Act
+ string encoded = ajaxHelper.JavaScriptStringEncode(null /* message */);
+
+ // Assert
+ Assert.Null(encoded);
+ }
+
+ [Fact]
+ public void ViewBagProperty_ReflectsViewData()
+ {
+ // Arrange
+ ViewDataDictionary viewDataDictionary = new ViewDataDictionary() { { "A", 1 } };
+ Mock<IViewDataContainer> viewDataContainer = new Mock<IViewDataContainer>();
+ viewDataContainer.Setup(container => container.ViewData).Returns(viewDataDictionary);
+
+ // Act
+ AjaxHelper ajaxHelper = new AjaxHelper(new Mock<ViewContext>().Object, viewDataContainer.Object);
+
+ // Assert
+ Assert.Equal(1, ajaxHelper.ViewBag.A);
+ }
+
+ [Fact]
+ public void ViewBagProperty_ReflectsNewViewDataContainerInstance()
+ {
+ // Arrange
+ ViewDataDictionary viewDataDictionary = new ViewDataDictionary() { { "A", 1 } };
+ Mock<IViewDataContainer> viewDataContainer = new Mock<IViewDataContainer>();
+ viewDataContainer.Setup(container => container.ViewData).Returns(viewDataDictionary);
+
+ ViewDataDictionary otherViewDataDictionary = new ViewDataDictionary() { { "A", 2 } };
+ Mock<IViewDataContainer> otherViewDataContainer = new Mock<IViewDataContainer>();
+ otherViewDataContainer.Setup(container => container.ViewData).Returns(otherViewDataDictionary);
+
+ AjaxHelper ajaxHelper = new AjaxHelper(new Mock<ViewContext>().Object, viewDataContainer.Object, new RouteCollection());
+
+ // Act
+ ajaxHelper.ViewDataContainer = otherViewDataContainer.Object;
+
+ // Assert
+ Assert.Equal(2, ajaxHelper.ViewBag.A);
+ }
+
+ [Fact]
+ public void ViewBag_PropagatesChangesToViewData()
+ {
+ // Arrange
+ ViewDataDictionary viewDataDictionary = new ViewDataDictionary() { { "A", 1 } };
+ Mock<IViewDataContainer> viewDataContainer = new Mock<IViewDataContainer>();
+ viewDataContainer.Setup(container => container.ViewData).Returns(viewDataDictionary);
+
+ AjaxHelper ajaxHelper = new AjaxHelper(new Mock<ViewContext>().Object, viewDataContainer.Object, new RouteCollection());
+
+ // Act
+ ajaxHelper.ViewBag.A = "foo";
+ ajaxHelper.ViewBag.B = 2;
+
+ // Assert
+ Assert.Equal("foo", ajaxHelper.ViewData["A"]);
+ Assert.Equal(2, ajaxHelper.ViewData["B"]);
+ }
+
+ private static AjaxHelper GetAjaxHelper()
+ {
+ ViewContext viewContext = new Mock<ViewContext>().Object;
+ IViewDataContainer viewDataContainer = new Mock<IViewDataContainer>().Object;
+ return new AjaxHelper(viewContext, viewDataContainer);
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Test/AjaxHelper`1Test.cs b/test/System.Web.Mvc.Test/Test/AjaxHelper`1Test.cs
new file mode 100644
index 00000000..41209a71
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Test/AjaxHelper`1Test.cs
@@ -0,0 +1,41 @@
+using Moq;
+using Xunit;
+
+namespace System.Web.Mvc.Test
+{
+ public class AjaxHelper_1Test
+ {
+ [Fact]
+ public void ViewBagAndViewDataStayInSync()
+ {
+ // Arrange
+ Mock<IViewDataContainer> viewDataContainer = new Mock<IViewDataContainer>();
+ ViewDataDictionary viewDataDictionary = new ViewDataDictionary() { { "A", 1 } };
+ viewDataContainer.Setup(container => container.ViewData).Returns(viewDataDictionary);
+
+ // Act
+ AjaxHelper<object> ajaxHelper = new AjaxHelper<object>(new Mock<ViewContext>().Object, viewDataContainer.Object);
+ ajaxHelper.ViewData["B"] = 2;
+ ajaxHelper.ViewBag.C = 3;
+
+ // Assert
+
+ // Original ViewData should not be modified by redfined ViewData and ViewBag
+ AjaxHelper nonGenericAjaxHelper = ajaxHelper;
+ Assert.Single(nonGenericAjaxHelper.ViewData.Keys);
+ Assert.Equal(1, nonGenericAjaxHelper.ViewData["A"]);
+ Assert.Equal(1, nonGenericAjaxHelper.ViewBag.A);
+
+ // Redefined ViewData and ViewBag should be in sync
+ Assert.Equal(3, ajaxHelper.ViewData.Keys.Count);
+
+ Assert.Equal(1, ajaxHelper.ViewData["A"]);
+ Assert.Equal(2, ajaxHelper.ViewData["B"]);
+ Assert.Equal(3, ajaxHelper.ViewData["C"]);
+
+ Assert.Equal(1, ajaxHelper.ViewBag.A);
+ Assert.Equal(2, ajaxHelper.ViewBag.B);
+ Assert.Equal(3, ajaxHelper.ViewBag.C);
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Test/AjaxRequestExtensionsTest.cs b/test/System.Web.Mvc.Test/Test/AjaxRequestExtensionsTest.cs
new file mode 100644
index 00000000..fbd3455b
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Test/AjaxRequestExtensionsTest.cs
@@ -0,0 +1,70 @@
+using System.Collections.Specialized;
+using Moq;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Mvc.Test
+{
+ public class AjaxRequestExtensionsTest
+ {
+ [Fact]
+ public void IsAjaxRequestWithNullRequestThrows()
+ {
+ // Act & Assert
+ Assert.ThrowsArgumentNull(
+ delegate { AjaxRequestExtensions.IsAjaxRequest(null); }, "request");
+ }
+
+ [Fact]
+ public void IsAjaxRequestWithKeyIsTrue()
+ {
+ // Arrange
+ Mock<HttpRequestBase> mockRequest = new Mock<HttpRequestBase>();
+ mockRequest.Setup(r => r["X-Requested-With"]).Returns("XMLHttpRequest").Verifiable();
+ HttpRequestBase request = mockRequest.Object;
+
+ // Act
+ bool retVal = AjaxRequestExtensions.IsAjaxRequest(request);
+
+ // Assert
+ Assert.True(retVal);
+ mockRequest.Verify();
+ }
+
+ [Fact]
+ public void IsAjaxRequestWithoutKeyOrHeaderIsFalse()
+ {
+ // Arrange
+ Mock<HttpRequestBase> mockRequest = new Mock<HttpRequestBase>();
+ NameValueCollection headerCollection = new NameValueCollection();
+ mockRequest.Setup(r => r.Headers).Returns(headerCollection).Verifiable();
+ mockRequest.Setup(r => r["X-Requested-With"]).Returns((string)null).Verifiable();
+ HttpRequestBase request = mockRequest.Object;
+
+ // Act
+ bool retVal = AjaxRequestExtensions.IsAjaxRequest(request);
+
+ // Assert
+ Assert.False(retVal);
+ mockRequest.Verify();
+ }
+
+ [Fact]
+ public void IsAjaxRequestReturnsTrueIfHeaderSet()
+ {
+ // Arrange
+ Mock<HttpRequestBase> mockRequest = new Mock<HttpRequestBase>();
+ NameValueCollection headerCollection = new NameValueCollection();
+ headerCollection["X-Requested-With"] = "XMLHttpRequest";
+ mockRequest.Setup(r => r.Headers).Returns(headerCollection).Verifiable();
+ HttpRequestBase request = mockRequest.Object;
+
+ // Act
+ bool retVal = AjaxRequestExtensions.IsAjaxRequest(request);
+
+ // Assert
+ Assert.True(retVal);
+ mockRequest.Verify();
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Test/AllowHtmlAttributeTest.cs b/test/System.Web.Mvc.Test/Test/AllowHtmlAttributeTest.cs
new file mode 100644
index 00000000..959326a2
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Test/AllowHtmlAttributeTest.cs
@@ -0,0 +1,37 @@
+using Moq;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Mvc.Test
+{
+ public class AllowHtmlAttributeTest
+ {
+ [Fact]
+ public void OnMetadataCreated_ThrowsIfMetadataIsNull()
+ {
+ // Arrange
+ AllowHtmlAttribute attr = new AllowHtmlAttribute();
+
+ // Act & assert
+ Assert.ThrowsArgumentNull(
+ delegate { attr.OnMetadataCreated(null); }, "metadata");
+ }
+
+ [Fact]
+ public void OnMetadataCreated()
+ {
+ // Arrange
+ ModelMetadata modelMetadata = new ModelMetadata(new Mock<ModelMetadataProvider>().Object, null, null, typeof(object), "SomeProperty");
+ AllowHtmlAttribute attr = new AllowHtmlAttribute();
+
+ // Act
+ bool originalValue = modelMetadata.RequestValidationEnabled;
+ attr.OnMetadataCreated(modelMetadata);
+ bool newValue = modelMetadata.RequestValidationEnabled;
+
+ // Assert
+ Assert.True(originalValue);
+ Assert.False(newValue);
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Test/AreaHelpersTest.cs b/test/System.Web.Mvc.Test/Test/AreaHelpersTest.cs
new file mode 100644
index 00000000..ef342afb
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Test/AreaHelpersTest.cs
@@ -0,0 +1,97 @@
+using System.Web.Routing;
+using Xunit;
+
+namespace System.Web.Mvc.Test
+{
+ public class AreaHelpersTest
+ {
+ [Fact]
+ public void GetAreaNameFromAreaRouteCollectionRoute()
+ {
+ // Arrange
+ RouteCollection routes = new RouteCollection();
+ AreaRegistrationContext context = new AreaRegistrationContext("area_name", routes);
+ Route route = context.MapRoute(null, "the_url");
+
+ // Act
+ string areaName = AreaHelpers.GetAreaName(route);
+
+ // Assert
+ Assert.Equal("area_name", areaName);
+ }
+
+ [Fact]
+ public void GetAreaNameFromIAreaAssociatedItem()
+ {
+ // Arrange
+ CustomRouteWithArea route = new CustomRouteWithArea();
+
+ // Act
+ string areaName = AreaHelpers.GetAreaName(route);
+
+ // Assert
+ Assert.Equal("area_name", areaName);
+ }
+
+ [Fact]
+ public void GetAreaNameFromRouteData()
+ {
+ // Arrange
+ RouteData routeData = new RouteData();
+ routeData.DataTokens["area"] = "area_name";
+
+ // Act
+ string areaName = AreaHelpers.GetAreaName(routeData);
+
+ // Assert
+ Assert.Equal("area_name", areaName);
+ }
+
+ [Fact]
+ public void GetAreaNameFromRouteDataFallsBackToRoute()
+ {
+ // Arrange
+ RouteCollection routes = new RouteCollection();
+ AreaRegistrationContext context = new AreaRegistrationContext("area_name", routes);
+ Route route = context.MapRoute(null, "the_url");
+ RouteData routeData = new RouteData(route, new MvcRouteHandler());
+
+ // Act
+ string areaName = AreaHelpers.GetAreaName(routeData);
+
+ // Assert
+ Assert.Equal("area_name", areaName);
+ }
+
+ [Fact]
+ public void GetAreaNameReturnsNullIfRouteNotAreaAware()
+ {
+ // Arrange
+ Route route = new Route("the_url", new MvcRouteHandler());
+
+ // Act
+ string areaName = AreaHelpers.GetAreaName(route);
+
+ // Assert
+ Assert.Null(areaName);
+ }
+
+ private class CustomRouteWithArea : RouteBase, IRouteWithArea
+ {
+ public string Area
+ {
+ get { return "area_name"; }
+ }
+
+ public override RouteData GetRouteData(HttpContextBase httpContext)
+ {
+ throw new NotImplementedException();
+ }
+
+ public override VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary values)
+ {
+ throw new NotImplementedException();
+ }
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Test/AreaRegistrationContextTest.cs b/test/System.Web.Mvc.Test/Test/AreaRegistrationContextTest.cs
new file mode 100644
index 00000000..f9e3e7e9
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Test/AreaRegistrationContextTest.cs
@@ -0,0 +1,138 @@
+using System.Collections.Generic;
+using System.Web.Routing;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Mvc.Test
+{
+ public class AreaRegistrationContextTest
+ {
+ [Fact]
+ public void ConstructorSetsProperties()
+ {
+ // Arrange
+ string areaName = "the_area";
+ RouteCollection routes = new RouteCollection();
+
+ // Act
+ AreaRegistrationContext context = new AreaRegistrationContext(areaName, routes);
+
+ // Assert
+ Assert.Equal(areaName, context.AreaName);
+ Assert.Same(routes, context.Routes);
+ }
+
+ [Fact]
+ public void ConstructorThrowsIfAreaNameIsEmpty()
+ {
+ // Act & assert
+ Assert.ThrowsArgumentNullOrEmpty(
+ delegate { new AreaRegistrationContext("", new RouteCollection()); }, "areaName");
+ }
+
+ [Fact]
+ public void ConstructorThrowsIfAreaNameIsNull()
+ {
+ // Act & assert
+ Assert.ThrowsArgumentNullOrEmpty(
+ delegate { new AreaRegistrationContext(null, new RouteCollection()); }, "areaName");
+ }
+
+ [Fact]
+ public void ConstructorThrowsIfRoutesIsNull()
+ {
+ // Act & assert
+ Assert.ThrowsArgumentNull(
+ delegate { new AreaRegistrationContext("the_area", null); }, "routes");
+ }
+
+ [Fact]
+ public void MapRouteWithEmptyStringNamespaces()
+ {
+ // Arrange
+ string[] implicitNamespaces = new string[] { "implicit_1", "implicit_2" };
+ string[] explicitNamespaces = new string[0];
+
+ RouteCollection routes = new RouteCollection();
+ AreaRegistrationContext context = new AreaRegistrationContext("the_area", routes);
+ ReplaceCollectionContents(context.Namespaces, implicitNamespaces);
+
+ // Act
+ Route route = context.MapRoute("the_name", "the_url", explicitNamespaces);
+
+ // Assert
+ Assert.Equal(route, routes["the_name"]);
+ Assert.Equal("the_area", route.DataTokens["area"]);
+ Assert.Equal(true, route.DataTokens["UseNamespaceFallback"]);
+ Assert.Null(route.DataTokens["namespaces"]);
+ }
+
+ [Fact]
+ public void MapRouteWithExplicitNamespaces()
+ {
+ // Arrange
+ string[] implicitNamespaces = new string[] { "implicit_1", "implicit_2" };
+ string[] explicitNamespaces = new string[] { "explicit_1", "explicit_2" };
+
+ RouteCollection routes = new RouteCollection();
+ AreaRegistrationContext context = new AreaRegistrationContext("the_area", routes);
+ ReplaceCollectionContents(context.Namespaces, implicitNamespaces);
+
+ // Act
+ Route route = context.MapRoute("the_name", "the_url", explicitNamespaces);
+
+ // Assert
+ Assert.Equal(route, routes["the_name"]);
+ Assert.Equal("the_area", route.DataTokens["area"]);
+ Assert.Equal(false, route.DataTokens["UseNamespaceFallback"]);
+ Assert.Equal(explicitNamespaces, (string[])route.DataTokens["namespaces"]);
+ }
+
+ [Fact]
+ public void MapRouteWithImplicitNamespaces()
+ {
+ // Arrange
+ string[] implicitNamespaces = new string[] { "implicit_1", "implicit_2" };
+ string[] explicitNamespaces = new string[] { "explicit_1", "explicit_2" };
+
+ RouteCollection routes = new RouteCollection();
+ AreaRegistrationContext context = new AreaRegistrationContext("the_area", routes);
+ ReplaceCollectionContents(context.Namespaces, implicitNamespaces);
+
+ // Act
+ Route route = context.MapRoute("the_name", "the_url");
+
+ // Assert
+ Assert.Equal(route, routes["the_name"]);
+ Assert.Equal("the_area", route.DataTokens["area"]);
+ Assert.Equal(false, route.DataTokens["UseNamespaceFallback"]);
+ Assert.Equal(implicitNamespaces, (string[])route.DataTokens["namespaces"]);
+ }
+
+ [Fact]
+ public void MapRouteWithoutNamespaces()
+ {
+ // Arrange
+ RouteCollection routes = new RouteCollection();
+ AreaRegistrationContext context = new AreaRegistrationContext("the_area", routes);
+
+ // Act
+ Route route = context.MapRoute("the_name", "the_url");
+
+ // Assert
+ Assert.Equal(route, routes["the_name"]);
+ Assert.Equal("the_area", route.DataTokens["area"]);
+ Assert.Null(route.DataTokens["namespaces"]);
+ Assert.Equal(true, route.DataTokens["UseNamespaceFallback"]);
+ }
+
+ private static void ReplaceCollectionContents(ICollection<string> collectionToReplace, IEnumerable<string> newContents)
+ {
+ collectionToReplace.Clear();
+ foreach (string item in newContents)
+ {
+ collectionToReplace.Add(item);
+ }
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Test/AreaRegistrationTest.cs b/test/System.Web.Mvc.Test/Test/AreaRegistrationTest.cs
new file mode 100644
index 00000000..2bdd879b
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Test/AreaRegistrationTest.cs
@@ -0,0 +1,99 @@
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection;
+using System.Web.Routing;
+using Xunit;
+
+namespace System.Web.Mvc.Test
+{
+ public class AreaRegistrationTest
+ {
+ [Fact]
+ public void CreateContextAndRegister()
+ {
+ // Arrange
+ string[] expectedNamespaces = new string[] { "System.Web.Mvc.Test.*" };
+
+ RouteCollection routes = new RouteCollection();
+ MyAreaRegistration registration = new MyAreaRegistration();
+
+ // Act
+ registration.CreateContextAndRegister(routes, "some state");
+
+ // Assert
+ Assert.Equal(expectedNamespaces, registration.Namespaces);
+ Assert.Equal("some state", registration.State);
+ }
+
+ [Fact]
+ public void RegisterAllAreas()
+ {
+ // Arrange
+ string[] expectedLoadedAreas = new string[] { "AreaRegistrationTest_AreaRegistration" };
+ AnnotatedRouteCollection routes = new AnnotatedRouteCollection();
+ MockBuildManager buildManager = new MockBuildManager(new Assembly[] { typeof(AreaRegistrationTest).Assembly });
+
+ // Act
+ AreaRegistration.RegisterAllAreas(routes, buildManager, null);
+
+ // Assert
+ Assert.Equal(expectedLoadedAreas, routes._areasLoaded.ToArray());
+ }
+
+ private class MyAreaRegistration : AreaRegistration
+ {
+ public string[] Namespaces;
+ public object State;
+
+ public override string AreaName
+ {
+ get { return "my_area"; }
+ }
+
+ public override void RegisterArea(AreaRegistrationContext context)
+ {
+ Namespaces = context.Namespaces.ToArray();
+ State = context.State;
+ }
+ }
+ }
+
+ [CLSCompliant(false)]
+ public class AnnotatedRouteCollection : RouteCollection
+ {
+ public List<string> _areasLoaded = new List<string>();
+ }
+
+ public abstract class AreaRegistrationTest_AbstractAreaRegistration : AreaRegistration
+ {
+ public override string AreaName
+ {
+ get { return "the_area"; }
+ }
+
+ public override void RegisterArea(AreaRegistrationContext context)
+ {
+ ((AnnotatedRouteCollection)context.Routes)._areasLoaded.Add("AreaRegistrationTest_AbstractAreaRegistration");
+ }
+ }
+
+ public class AreaRegistrationTest_AreaRegistration : AreaRegistrationTest_AbstractAreaRegistration
+ {
+ public override void RegisterArea(AreaRegistrationContext context)
+ {
+ ((AnnotatedRouteCollection)context.Routes)._areasLoaded.Add("AreaRegistrationTest_AreaRegistration");
+ }
+ }
+
+ public class AreaRegistrationTest_NoConstructorAreaRegistration : AreaRegistrationTest_AreaRegistration
+ {
+ private AreaRegistrationTest_NoConstructorAreaRegistration()
+ {
+ }
+
+ public override void RegisterArea(AreaRegistrationContext context)
+ {
+ ((AnnotatedRouteCollection)context.Routes)._areasLoaded.Add("AreaRegistrationTest_NoConstructorAreaRegistration");
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Test/AssociatedMetadataProviderTest.cs b/test/System.Web.Mvc.Test/Test/AssociatedMetadataProviderTest.cs
new file mode 100644
index 00000000..6e0d0f58
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Test/AssociatedMetadataProviderTest.cs
@@ -0,0 +1,346 @@
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.ComponentModel.DataAnnotations;
+using System.Linq;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Mvc.Test
+{
+ public class AssociatedMetadataProviderTest
+ {
+ // FilterAttributes
+
+ [Fact]
+ public void ReadOnlyAttributeIsFilteredOffWhenContainerTypeIsViewPage()
+ {
+ // Arrange
+ TestableAssociatedMetadataProvider provider = new TestableAssociatedMetadataProvider();
+
+ // Act
+ provider.GetMetadataForProperty(() => null, typeof(ViewPage<PropertyModel>), "Model");
+
+ // Assert
+ CreateMetadataParams parms = provider.CreateMetadataLog.Single();
+ Assert.False(parms.Attributes.Any(a => a is ReadOnlyAttribute));
+ }
+
+ [Fact]
+ public void ReadOnlyAttributeIsFilteredOffWhenContainerTypeIsViewUserControl()
+ {
+ // Arrange
+ TestableAssociatedMetadataProvider provider = new TestableAssociatedMetadataProvider();
+
+ // Act
+ provider.GetMetadataForProperty(() => null, typeof(ViewUserControl<PropertyModel>), "Model");
+
+ // Assert
+ CreateMetadataParams parms = provider.CreateMetadataLog.Single();
+ Assert.False(parms.Attributes.Any(a => a is ReadOnlyAttribute));
+ }
+
+ [Fact]
+ public void ReadOnlyAttributeIsPreservedForReadOnlyModelProperties()
+ {
+ // Arrange
+ TestableAssociatedMetadataProvider provider = new TestableAssociatedMetadataProvider();
+
+ // Act
+ provider.GetMetadataForProperty(() => null, typeof(ModelWithReadOnlyProperty), "ReadOnlyProperty");
+
+ // Assert
+ CreateMetadataParams parms = provider.CreateMetadataLog.Single();
+ Assert.True(parms.Attributes.Any(a => a is ReadOnlyAttribute));
+ }
+
+ // GetMetadataForProperties
+
+ [Fact]
+ public void GetMetadataForPropertiesNullContainerTypeThrows()
+ {
+ // Arrange
+ TestableAssociatedMetadataProvider provider = new TestableAssociatedMetadataProvider();
+
+ // Act & Assert
+ Assert.ThrowsArgumentNull(
+ () => provider.GetMetadataForProperties(new Object(), null),
+ "containerType");
+ }
+
+ [Fact]
+ public void GetMetadataForPropertiesCreatesMetadataForAllPropertiesOnModelWithPropertyValues()
+ {
+ // Arrange
+ PropertyModel model = new PropertyModel { LocalAttributes = 42, MetadataAttributes = "hello", MixedAttributes = 21.12 };
+ TestableAssociatedMetadataProvider provider = new TestableAssociatedMetadataProvider();
+
+ // Act
+ provider.GetMetadataForProperties(model, typeof(PropertyModel)).ToList(); // Call ToList() to force the lazy evaluation to evaluate
+
+ // Assert
+ CreateMetadataParams local =
+ provider.CreateMetadataLog.Single(m => m.ContainerType == typeof(PropertyModel) &&
+ m.PropertyName == "LocalAttributes");
+ Assert.Equal(typeof(int), local.ModelType);
+ Assert.Equal(42, local.Model);
+ Assert.True(local.Attributes.Any(a => a is RequiredAttribute));
+
+ CreateMetadataParams metadata =
+ provider.CreateMetadataLog.Single(m => m.ContainerType == typeof(PropertyModel) &&
+ m.PropertyName == "MetadataAttributes");
+ Assert.Equal(typeof(string), metadata.ModelType);
+ Assert.Equal("hello", metadata.Model);
+ Assert.True(metadata.Attributes.Any(a => a is RangeAttribute));
+
+ CreateMetadataParams mixed =
+ provider.CreateMetadataLog.Single(m => m.ContainerType == typeof(PropertyModel) &&
+ m.PropertyName == "MixedAttributes");
+ Assert.Equal(typeof(double), mixed.ModelType);
+ Assert.Equal(21.12, mixed.Model);
+ Assert.True(mixed.Attributes.Any(a => a is RequiredAttribute));
+ Assert.True(mixed.Attributes.Any(a => a is RangeAttribute));
+ }
+
+ [Fact]
+ public void GetMetadataForPropertyWithNullContainerReturnsMetadataWithNullValuesForProperties()
+ {
+ // Arrange
+ TestableAssociatedMetadataProvider provider = new TestableAssociatedMetadataProvider();
+
+ // Act
+ provider.GetMetadataForProperties(null, typeof(PropertyModel)).ToList(); // Call ToList() to force the lazy evaluation to evaluate
+
+ // Assert
+ Assert.True(provider.CreateMetadataLog.Any());
+ foreach (var parms in provider.CreateMetadataLog)
+ {
+ Assert.Null(parms.Model);
+ }
+ }
+
+ // GetMetadataForProperty
+
+ [Fact]
+ public void GetMetadataForPropertyNullContainerTypeThrows()
+ {
+ // Arrange
+ TestableAssociatedMetadataProvider provider = new TestableAssociatedMetadataProvider();
+
+ // Act & Assert
+ Assert.ThrowsArgumentNull(
+ () => provider.GetMetadataForProperty(null /* model */, null /* containerType */, "propertyName"),
+ "containerType");
+ }
+
+ [Fact]
+ public void GetMetadataForPropertyNullOrEmptyPropertyNameThrows()
+ {
+ // Arrange
+ TestableAssociatedMetadataProvider provider = new TestableAssociatedMetadataProvider();
+
+ // Act & Assert
+ Assert.ThrowsArgumentNullOrEmpty(
+ () => provider.GetMetadataForProperty(null /* model */, typeof(object), null /* propertyName */),
+ "propertyName");
+ Assert.ThrowsArgumentNullOrEmpty(
+ () => provider.GetMetadataForProperty(null, typeof(object), String.Empty),
+ "propertyName");
+ }
+
+ [Fact]
+ public void GetMetadataForPropertyInvalidPropertyNameThrows()
+ {
+ // Arrange
+ TestableAssociatedMetadataProvider provider = new TestableAssociatedMetadataProvider();
+
+ // Act & Assert
+ Assert.Throws<ArgumentException>(
+ () => provider.GetMetadataForProperty(null, typeof(object), "BadPropertyName"),
+ "The property System.Object.BadPropertyName could not be found.");
+ }
+
+ [Fact]
+ public void GetMetadataForPropertyWithLocalAttributes()
+ {
+ // Arrange
+ TestableAssociatedMetadataProvider provider = new TestableAssociatedMetadataProvider();
+ ModelMetadata metadata = new ModelMetadata(provider, typeof(PropertyModel), null, typeof(int), "LocalAttributes");
+ provider.CreateMetadataReturnValue = metadata;
+
+ // Act
+ ModelMetadata result = provider.GetMetadataForProperty(null, typeof(PropertyModel), "LocalAttributes");
+
+ // Assert
+ Assert.Same(metadata, result);
+ Assert.True(provider.CreateMetadataLog.Single().Attributes.Any(a => a is RequiredAttribute));
+ }
+
+ [Fact]
+ public void GetMetadataForPropertyWithMetadataAttributes()
+ {
+ // Arrange
+ TestableAssociatedMetadataProvider provider = new TestableAssociatedMetadataProvider();
+ ModelMetadata metadata = new ModelMetadata(provider, typeof(PropertyModel), null, typeof(string), "MetadataAttributes");
+ provider.CreateMetadataReturnValue = metadata;
+
+ // Act
+ ModelMetadata result = provider.GetMetadataForProperty(null, typeof(PropertyModel), "MetadataAttributes");
+
+ // Assert
+ Assert.Same(metadata, result);
+ CreateMetadataParams parms = provider.CreateMetadataLog.Single(p => p.PropertyName == "MetadataAttributes");
+ Assert.True(parms.Attributes.Any(a => a is RangeAttribute));
+ }
+
+ [Fact]
+ public void GetMetadataForPropertyWithMixedAttributes()
+ {
+ // Arrange
+ TestableAssociatedMetadataProvider provider = new TestableAssociatedMetadataProvider();
+ ModelMetadata metadata = new ModelMetadata(provider, typeof(PropertyModel), null, typeof(double), "MixedAttributes");
+ provider.CreateMetadataReturnValue = metadata;
+
+ // Act
+ ModelMetadata result = provider.GetMetadataForProperty(null, typeof(PropertyModel), "MixedAttributes");
+
+ // Assert
+ Assert.Same(metadata, result);
+ CreateMetadataParams parms = provider.CreateMetadataLog.Single(p => p.PropertyName == "MixedAttributes");
+ Assert.True(parms.Attributes.Any(a => a is RequiredAttribute));
+ Assert.True(parms.Attributes.Any(a => a is RangeAttribute));
+ }
+
+ // GetMetadataForType
+
+ [Fact]
+ public void GetMetadataForTypeNullModelTypeThrows()
+ {
+ // Arrange
+ TestableAssociatedMetadataProvider provider = new TestableAssociatedMetadataProvider();
+
+ // Act & Assert
+ Assert.ThrowsArgumentNull(
+ () => provider.GetMetadataForType(() => new Object(), null),
+ "modelType");
+ }
+
+ [Fact]
+ public void GetMetadataForTypeIncludesAttributesOnType()
+ {
+ TestableAssociatedMetadataProvider provider = new TestableAssociatedMetadataProvider();
+ ModelMetadata metadata = new ModelMetadata(provider, null, null, typeof(TypeModel), null);
+ provider.CreateMetadataReturnValue = metadata;
+
+ // Act
+ ModelMetadata result = provider.GetMetadataForType(null, typeof(TypeModel));
+
+ // Assert
+ Assert.Same(metadata, result);
+ CreateMetadataParams parms = provider.CreateMetadataLog.Single(p => p.ModelType == typeof(TypeModel));
+ Assert.True(parms.Attributes.Any(a => a is ReadOnlyAttribute));
+ }
+
+ [AdditionalMetadata("ClassName", "ClassValue")]
+ class ClassWithAdditionalMetadata
+ {
+ [AdditionalMetadata("PropertyName", "PropertyValue")]
+ public int MyProperty { get; set; }
+ }
+
+ [Fact]
+ public void MetadataAwareAttributeCanModifyTypeMetadata()
+ {
+ // Arrange
+ TestableAssociatedMetadataProvider provider = new TestableAssociatedMetadataProvider();
+ provider.CreateMetadataReturnValue = new ModelMetadata(provider, null, null, typeof(ClassWithAdditionalMetadata), null);
+
+ // Act
+ ModelMetadata metadata = provider.GetMetadataForType(null, typeof(ClassWithAdditionalMetadata));
+
+ // Assert
+ var kvp = metadata.AdditionalValues.Single();
+ Assert.Equal("ClassName", kvp.Key);
+ Assert.Equal("ClassValue", kvp.Value);
+ }
+
+ [Fact]
+ public void MetadataAwareAttributeCanModifyPropertyMetadata()
+ {
+ // Arrange
+ TestableAssociatedMetadataProvider provider = new TestableAssociatedMetadataProvider();
+ provider.CreateMetadataReturnValue = new ModelMetadata(provider, typeof(ClassWithAdditionalMetadata), null, typeof(int), "MyProperty");
+
+ // Act
+ ModelMetadata metadata = provider.GetMetadataForProperty(null, typeof(ClassWithAdditionalMetadata), "MyProperty");
+
+ // Assert
+ var kvp = metadata.AdditionalValues.Single();
+ Assert.Equal("PropertyName", kvp.Key);
+ Assert.Equal("PropertyValue", kvp.Value);
+ }
+
+ // Helpers
+
+ [MetadataType(typeof(Metadata))]
+ private class PropertyModel
+ {
+ [Required]
+ public int LocalAttributes { get; set; }
+
+ public string MetadataAttributes { get; set; }
+
+ [Required]
+ public double MixedAttributes { get; set; }
+
+ private class Metadata
+ {
+ [Range(10, 100)]
+ public object MetadataAttributes { get; set; }
+
+ [Range(10, 100)]
+ public object MixedAttributes { get; set; }
+ }
+ }
+
+ private class ModelWithReadOnlyProperty
+ {
+ public int ReadOnlyProperty { get; private set; }
+ }
+
+ [ReadOnly(true)]
+ private class TypeModel
+ {
+ }
+
+ class TestableAssociatedMetadataProvider : AssociatedMetadataProvider
+ {
+ public List<CreateMetadataParams> CreateMetadataLog = new List<CreateMetadataParams>();
+ public ModelMetadata CreateMetadataReturnValue = null;
+
+ protected override ModelMetadata CreateMetadata(IEnumerable<Attribute> attributes, Type containerType,
+ Func<object> modelAccessor, Type modelType,
+ string propertyName)
+ {
+ CreateMetadataLog.Add(new CreateMetadataParams
+ {
+ Attributes = attributes,
+ ContainerType = containerType,
+ Model = modelAccessor == null ? null : modelAccessor(),
+ ModelType = modelType,
+ PropertyName = propertyName
+ });
+
+ return CreateMetadataReturnValue;
+ }
+ }
+
+ class CreateMetadataParams
+ {
+ public IEnumerable<Attribute> Attributes { get; set; }
+ public Type ContainerType { get; set; }
+ public object Model { get; set; }
+ public Type ModelType { get; set; }
+ public string PropertyName { get; set; }
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Test/AssociatedValidatorProviderTest.cs b/test/System.Web.Mvc.Test/Test/AssociatedValidatorProviderTest.cs
new file mode 100644
index 00000000..f687f543
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Test/AssociatedValidatorProviderTest.cs
@@ -0,0 +1,124 @@
+using System.Collections.Generic;
+using System.ComponentModel.DataAnnotations;
+using System.Linq;
+using Moq;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Mvc.Test
+{
+ public class AssociatedValidatorProviderTest
+ {
+ [Fact]
+ public void GetValidatorsGuardClauses()
+ {
+ // Arrange
+ ModelMetadata metadata = ModelMetadataProviders.Current.GetMetadataForType(null, typeof(object));
+ Mock<AssociatedValidatorProvider> provider = new Mock<AssociatedValidatorProvider> { CallBase = true };
+
+ // Act & Assert
+ Assert.ThrowsArgumentNull(
+ () => provider.Object.GetValidators(null, new ControllerContext()),
+ "metadata");
+ Assert.ThrowsArgumentNull(
+ () => provider.Object.GetValidators(metadata, null),
+ "context");
+ }
+
+ [Fact]
+ public void GetValidatorsForPropertyWithLocalAttributes()
+ {
+ // Arrange
+ IEnumerable<Attribute> callbackAttributes = null;
+ ControllerContext context = new ControllerContext();
+ ModelMetadata metadata = ModelMetadataProviders.Current.GetMetadataForProperty(null, typeof(PropertyModel), "LocalAttributes");
+ Mock<TestableAssociatedValidatorProvider> provider = new Mock<TestableAssociatedValidatorProvider> { CallBase = true };
+ provider.Setup(p => p.AbstractGetValidators(metadata, context, It.IsAny<IEnumerable<Attribute>>()))
+ .Callback<ModelMetadata, ControllerContext, IEnumerable<Attribute>>((m, c, attributes) => callbackAttributes = attributes)
+ .Returns(() => null)
+ .Verifiable();
+
+ // Act
+ provider.Object.GetValidators(metadata, context);
+
+ // Assert
+ provider.Verify();
+ Assert.True(callbackAttributes.Any(a => a is RequiredAttribute));
+ }
+
+ [Fact]
+ public void GetValidatorsForPropertyWithMetadataAttributes()
+ {
+ // Arrange
+ IEnumerable<Attribute> callbackAttributes = null;
+ ControllerContext context = new ControllerContext();
+ ModelMetadata metadata = ModelMetadataProviders.Current.GetMetadataForProperty(null, typeof(PropertyModel), "MetadataAttributes");
+ Mock<TestableAssociatedValidatorProvider> provider = new Mock<TestableAssociatedValidatorProvider> { CallBase = true };
+ provider.Setup(p => p.AbstractGetValidators(metadata, context, It.IsAny<IEnumerable<Attribute>>()))
+ .Callback<ModelMetadata, ControllerContext, IEnumerable<Attribute>>((m, c, attributes) => callbackAttributes = attributes)
+ .Returns(() => null)
+ .Verifiable();
+
+ // Act
+ provider.Object.GetValidators(metadata, context);
+
+ // Assert
+ provider.Verify();
+ Assert.True(callbackAttributes.Any(a => a is RangeAttribute));
+ }
+
+ [Fact]
+ public void GetValidatorsForPropertyWithMixedAttributes()
+ {
+ // Arrange
+ IEnumerable<Attribute> callbackAttributes = null;
+ ControllerContext context = new ControllerContext();
+ ModelMetadata metadata = ModelMetadataProviders.Current.GetMetadataForProperty(null, typeof(PropertyModel), "MixedAttributes");
+ Mock<TestableAssociatedValidatorProvider> provider = new Mock<TestableAssociatedValidatorProvider> { CallBase = true };
+ provider.Setup(p => p.AbstractGetValidators(metadata, context, It.IsAny<IEnumerable<Attribute>>()))
+ .Callback<ModelMetadata, ControllerContext, IEnumerable<Attribute>>((m, c, attributes) => callbackAttributes = attributes)
+ .Returns(() => null)
+ .Verifiable();
+
+ // Act
+ provider.Object.GetValidators(metadata, context);
+
+ // Assert
+ provider.Verify();
+ Assert.True(callbackAttributes.Any(a => a is RangeAttribute));
+ Assert.True(callbackAttributes.Any(a => a is RequiredAttribute));
+ }
+
+ [MetadataType(typeof(Metadata))]
+ private class PropertyModel
+ {
+ [Required]
+ public int LocalAttributes { get; set; }
+
+ public string MetadataAttributes { get; set; }
+
+ [Required]
+ public double MixedAttributes { get; set; }
+
+ private class Metadata
+ {
+ [Range(10, 100)]
+ public object MetadataAttributes { get; set; }
+
+ [Range(10, 100)]
+ public object MixedAttributes { get; set; }
+ }
+ }
+
+ public abstract class TestableAssociatedValidatorProvider : AssociatedValidatorProvider
+ {
+ protected override IEnumerable<ModelValidator> GetValidators(ModelMetadata metadata, ControllerContext context, IEnumerable<Attribute> attributes)
+ {
+ return AbstractGetValidators(metadata, context, attributes);
+ }
+
+ // Hoist access
+ public abstract IEnumerable<ModelValidator> AbstractGetValidators(ModelMetadata metadata, ControllerContext context, IEnumerable<Attribute> attributes);
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Test/AsyncControllerTest.cs b/test/System.Web.Mvc.Test/Test/AsyncControllerTest.cs
new file mode 100644
index 00000000..50805c85
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Test/AsyncControllerTest.cs
@@ -0,0 +1,263 @@
+using System.Collections.Generic;
+using System.Web.Mvc.Async;
+using System.Web.Mvc.Async.Test;
+using System.Web.Routing;
+using Moq;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Mvc.Test
+{
+ public class AsyncControllerTest
+ {
+ [Fact]
+ public void ActionInvokerProperty()
+ {
+ // Arrange
+ EmptyController controller = new EmptyController();
+
+ // Act
+ IActionInvoker invoker = controller.ActionInvoker;
+
+ // Assert
+ Assert.IsType<AsyncControllerActionInvoker>(invoker);
+ }
+
+ [Fact]
+ public void AsyncManagerProperty()
+ {
+ // Arrange
+ EmptyController controller = new EmptyController();
+
+ // Act
+ AsyncManager asyncManager = controller.AsyncManager;
+
+ // Assert
+ Assert.NotNull(asyncManager);
+ }
+
+ [Fact]
+ public void Execute_ThrowsIfCalledMoreThanOnce()
+ {
+ // Arrange
+ IAsyncController controller = new EmptyController();
+ RequestContext requestContext = GetRequestContext("SomeAction");
+
+ // Act & assert
+ controller.BeginExecute(requestContext, null, null);
+ Assert.Throws<InvalidOperationException>(
+ delegate { controller.BeginExecute(requestContext, null, null); },
+ @"A single instance of controller 'System.Web.Mvc.Test.AsyncControllerTest+EmptyController' cannot be used to handle multiple requests. If a custom controller factory is in use, make sure that it creates a new instance of the controller for each request.");
+ }
+
+ [Fact]
+ public void Execute_ThrowsIfRequestContextIsNull()
+ {
+ // Arrange
+ IAsyncController controller = new EmptyController();
+
+ // Act & assert
+ Assert.ThrowsArgumentNull(
+ delegate { controller.BeginExecute(null, null, null); }, "requestContext");
+ }
+
+ [Fact]
+ public void ExecuteCore_Asynchronous_ActionFound()
+ {
+ // Arrange
+ MockAsyncResult innerAsyncResult = new MockAsyncResult();
+
+ Mock<IAsyncActionInvoker> mockActionInvoker = new Mock<IAsyncActionInvoker>();
+ mockActionInvoker.Setup(o => o.BeginInvokeAction(It.IsAny<ControllerContext>(), "SomeAction", It.IsAny<AsyncCallback>(), It.IsAny<object>())).Returns(innerAsyncResult);
+ mockActionInvoker.Setup(o => o.EndInvokeAction(innerAsyncResult)).Returns(true);
+
+ RequestContext requestContext = GetRequestContext("SomeAction");
+ EmptyController controller = new EmptyController()
+ {
+ ActionInvoker = mockActionInvoker.Object
+ };
+
+ // Act & assert
+ IAsyncResult outerAsyncResult = ((IAsyncController)controller).BeginExecute(requestContext, null, null);
+ Assert.False(controller.TempDataSaved);
+
+ ((IAsyncController)controller).EndExecute(outerAsyncResult);
+ Assert.True(controller.TempDataSaved);
+ Assert.False(controller.HandleUnknownActionCalled);
+ }
+
+ [Fact]
+ public void ExecuteCore_Asynchronous_ActionNotFound()
+ {
+ // Arrange
+ MockAsyncResult innerAsyncResult = new MockAsyncResult();
+
+ Mock<IAsyncActionInvoker> mockActionInvoker = new Mock<IAsyncActionInvoker>();
+ mockActionInvoker.Setup(o => o.BeginInvokeAction(It.IsAny<ControllerContext>(), "SomeAction", It.IsAny<AsyncCallback>(), It.IsAny<object>())).Returns(innerAsyncResult);
+ mockActionInvoker.Setup(o => o.EndInvokeAction(innerAsyncResult)).Returns(false);
+
+ RequestContext requestContext = GetRequestContext("SomeAction");
+ EmptyController controller = new EmptyController()
+ {
+ ActionInvoker = mockActionInvoker.Object
+ };
+
+ // Act & assert
+ IAsyncResult outerAsyncResult = ((IAsyncController)controller).BeginExecute(requestContext, null, null);
+ Assert.False(controller.TempDataSaved);
+
+ ((IAsyncController)controller).EndExecute(outerAsyncResult);
+ Assert.True(controller.TempDataSaved);
+ Assert.True(controller.HandleUnknownActionCalled);
+ }
+
+ [Fact]
+ public void ExecuteCore_Synchronous_ActionFound()
+ {
+ // Arrange
+ MockAsyncResult innerAsyncResult = new MockAsyncResult();
+
+ Mock<IActionInvoker> mockActionInvoker = new Mock<IActionInvoker>();
+ mockActionInvoker.Setup(o => o.InvokeAction(It.IsAny<ControllerContext>(), "SomeAction")).Returns(true);
+
+ RequestContext requestContext = GetRequestContext("SomeAction");
+ EmptyController controller = new EmptyController()
+ {
+ ActionInvoker = mockActionInvoker.Object
+ };
+
+ // Act & assert
+ IAsyncResult outerAsyncResult = ((IAsyncController)controller).BeginExecute(requestContext, null, null);
+ Assert.False(controller.TempDataSaved);
+
+ ((IAsyncController)controller).EndExecute(outerAsyncResult);
+ Assert.True(controller.TempDataSaved);
+ Assert.False(controller.HandleUnknownActionCalled);
+ }
+
+ [Fact]
+ public void ExecuteCore_Synchronous_ActionNotFound()
+ {
+ // Arrange
+ MockAsyncResult innerAsyncResult = new MockAsyncResult();
+
+ Mock<IActionInvoker> mockActionInvoker = new Mock<IActionInvoker>();
+ mockActionInvoker.Setup(o => o.InvokeAction(It.IsAny<ControllerContext>(), "SomeAction")).Returns(false);
+
+ RequestContext requestContext = GetRequestContext("SomeAction");
+ EmptyController controller = new EmptyController()
+ {
+ ActionInvoker = mockActionInvoker.Object
+ };
+
+ // Act & assert
+ IAsyncResult outerAsyncResult = ((IAsyncController)controller).BeginExecute(requestContext, null, null);
+ Assert.False(controller.TempDataSaved);
+
+ ((IAsyncController)controller).EndExecute(outerAsyncResult);
+ Assert.True(controller.TempDataSaved);
+ Assert.True(controller.HandleUnknownActionCalled);
+ }
+
+ [Fact]
+ public void ExecuteCore_SavesTempDataOnException()
+ {
+ // Arrange
+ Mock<IAsyncActionInvoker> mockActionInvoker = new Mock<IAsyncActionInvoker>();
+ mockActionInvoker
+ .Setup(o => o.BeginInvokeAction(It.IsAny<ControllerContext>(), "SomeAction", It.IsAny<AsyncCallback>(), It.IsAny<object>()))
+ .Throws(new Exception("Some exception text."));
+
+ RequestContext requestContext = GetRequestContext("SomeAction");
+ EmptyController controller = new EmptyController()
+ {
+ ActionInvoker = mockActionInvoker.Object
+ };
+
+ // Act & assert
+ Assert.Throws<Exception>(
+ delegate { ((IAsyncController)controller).BeginExecute(requestContext, null, null); },
+ @"Some exception text.");
+ Assert.True(controller.TempDataSaved);
+ }
+
+ [Fact]
+ public void CreateActionInvokerCallsIntoResolverInstance()
+ {
+ // Controller uses an IDependencyResolver to create an IActionInvoker.
+ var controller = new EmptyController();
+ Mock<IDependencyResolver> resolverMock = new Mock<IDependencyResolver>();
+ Mock<IAsyncActionInvoker> actionInvokerMock = new Mock<IAsyncActionInvoker>();
+ resolverMock.Setup(r => r.GetService(typeof(IAsyncActionInvoker))).Returns(actionInvokerMock.Object);
+ controller.Resolver = resolverMock.Object;
+
+ var ai = controller.CreateActionInvoker();
+
+ resolverMock.Verify(r => r.GetService(typeof(IAsyncActionInvoker)), Times.Once());
+ Assert.Same(actionInvokerMock.Object, ai);
+ }
+
+ [Fact]
+ public void CreateActionInvokerCallsIntoResolverInstanceAndCreatesANewOneIfNecessary()
+ {
+ // If IDependencyResolver is set, but empty, falls back and still creates.
+ var controller = new EmptyController();
+ Mock<IDependencyResolver> resolverMock = new Mock<IDependencyResolver>();
+ resolverMock.Setup(r => r.GetService(typeof(IAsyncActionInvoker))).Returns(null);
+ resolverMock.Setup(r => r.GetService(typeof(IActionInvoker))).Returns(null);
+ controller.Resolver = resolverMock.Object;
+
+ var ai = controller.CreateActionInvoker();
+
+ resolverMock.Verify(r => r.GetService(typeof(IAsyncActionInvoker)), Times.Once());
+ resolverMock.Verify(r => r.GetService(typeof(IActionInvoker)), Times.Once());
+ Assert.NotNull(ai);
+ }
+
+
+ private static RequestContext GetRequestContext(string actionName)
+ {
+ Mock<HttpContextBase> mockHttpContext = new Mock<HttpContextBase>();
+ RouteData routeData = new RouteData();
+ routeData.Values["action"] = actionName;
+
+ return new RequestContext(mockHttpContext.Object, routeData);
+ }
+
+ private class EmptyController : AsyncController
+ {
+ public bool TempDataSaved;
+ public bool HandleUnknownActionCalled;
+
+ protected override ITempDataProvider CreateTempDataProvider()
+ {
+ return new DummyTempDataProvider();
+ }
+
+ protected override void HandleUnknownAction(string actionName)
+ {
+ HandleUnknownActionCalled = true;
+ }
+
+ // Test can expose protected method as public.
+ public new IActionInvoker CreateActionInvoker()
+ {
+ return base.CreateActionInvoker();
+ }
+
+
+ private class DummyTempDataProvider : ITempDataProvider
+ {
+ public IDictionary<string, object> LoadTempData(ControllerContext controllerContext)
+ {
+ return new TempDataDictionary();
+ }
+
+ public void SaveTempData(ControllerContext controllerContext, IDictionary<string, object> values)
+ {
+ ((EmptyController)controllerContext.Controller).TempDataSaved = true;
+ }
+ }
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Test/AsyncTimeoutAttributeTest.cs b/test/System.Web.Mvc.Test/Test/AsyncTimeoutAttributeTest.cs
new file mode 100644
index 00000000..581bbf1e
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Test/AsyncTimeoutAttributeTest.cs
@@ -0,0 +1,87 @@
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Mvc.Test
+{
+ public class AsyncTimeoutAttributeTest
+ {
+ [Fact]
+ public void ConstructorThrowsIfDurationIsOutOfRange()
+ {
+ // Act & assert
+ Assert.ThrowsArgumentOutOfRange(() => new AsyncTimeoutAttribute(-1000), "duration",
+ @"The timeout value must be non-negative or Timeout.Infinite.");
+ }
+
+ [Fact]
+ public void DurationProperty()
+ {
+ // Act
+ AsyncTimeoutAttribute attr = new AsyncTimeoutAttribute(45);
+
+ // Assert
+ Assert.Equal(45, attr.Duration);
+ }
+
+ [Fact]
+ public void OnActionExecutingSetsTimeoutPropertyOnController()
+ {
+ // Arrange
+ AsyncTimeoutAttribute attr = new AsyncTimeoutAttribute(45);
+
+ MyAsyncController controller = new MyAsyncController();
+ controller.AsyncManager.Timeout = 0;
+
+ ActionExecutingContext filterContext = new ActionExecutingContext()
+ {
+ Controller = controller
+ };
+
+ // Act
+ attr.OnActionExecuting(filterContext);
+
+ // Assert
+ Assert.Equal(45, controller.AsyncManager.Timeout);
+ }
+
+ [Fact]
+ public void OnActionExecutingThrowsIfControllerIsNotAsyncManagerContainer()
+ {
+ // Arrange
+ AsyncTimeoutAttribute attr = new AsyncTimeoutAttribute(45);
+
+ ActionExecutingContext filterContext = new ActionExecutingContext()
+ {
+ Controller = new MyController()
+ };
+
+ // Act & assert
+ Assert.Throws<InvalidOperationException>(
+ delegate { attr.OnActionExecuting(filterContext); },
+ @"The controller of type 'System.Web.Mvc.Test.AsyncTimeoutAttributeTest+MyController' must subclass AsyncController or implement the IAsyncManagerContainer interface.");
+ }
+
+ [Fact]
+ public void OnActionExecutingThrowsIfFilterContextIsNull()
+ {
+ // Arrange
+ AsyncTimeoutAttribute attr = new AsyncTimeoutAttribute(45);
+
+ // Act & assert
+ Assert.ThrowsArgumentNull(
+ delegate { attr.OnActionExecuting(null); }, "filterContext");
+ }
+
+ private class MyController : ControllerBase
+ {
+ protected override void ExecuteCore()
+ {
+ throw new NotImplementedException();
+ }
+ }
+
+ private class MyAsyncController : AsyncController
+ {
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Test/AuthorizationContextTest.cs b/test/System.Web.Mvc.Test/Test/AuthorizationContextTest.cs
new file mode 100644
index 00000000..3f51a25d
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Test/AuthorizationContextTest.cs
@@ -0,0 +1,35 @@
+using Moq;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Mvc.Test
+{
+ public class AuthorizationContextTest
+ {
+ [Fact]
+ public void ConstructorThrowsIfActionDescriptorIsNull()
+ {
+ // Arrange
+ ControllerContext controllerContext = new Mock<ControllerContext>().Object;
+ ActionDescriptor actionDescriptor = null;
+
+ // Act & assert
+ Assert.ThrowsArgumentNull(
+ delegate { new AuthorizationContext(controllerContext, actionDescriptor); }, "actionDescriptor");
+ }
+
+ [Fact]
+ public void PropertiesAreSetByConstructor()
+ {
+ // Arrange
+ ControllerContext controllerContext = new Mock<ControllerContext>().Object;
+ ActionDescriptor actionDescriptor = new Mock<ActionDescriptor>().Object;
+
+ // Act
+ AuthorizationContext authorizationContext = new AuthorizationContext(controllerContext, actionDescriptor);
+
+ // Assert
+ Assert.Equal(actionDescriptor, authorizationContext.ActionDescriptor);
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Test/AuthorizeAttributeTest.cs b/test/System.Web.Mvc.Test/Test/AuthorizeAttributeTest.cs
new file mode 100644
index 00000000..1605b773
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Test/AuthorizeAttributeTest.cs
@@ -0,0 +1,362 @@
+using System.Collections;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Linq;
+using System.Reflection;
+using System.Security.Principal;
+using System.Web.TestUtil;
+using Moq;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Mvc.Test
+{
+ public class AuthorizeAttributeTest
+ {
+ [Fact]
+ public void AuthorizeAttributeReturnsUniqueTypeIDs()
+ {
+ // Arrange
+ AuthorizeAttribute attr1 = new AuthorizeAttribute();
+ AuthorizeAttribute attr2 = new AuthorizeAttribute();
+
+ // Assert
+ Assert.NotEqual(attr1.TypeId, attr2.TypeId);
+ }
+
+ [Authorize(Roles = "foo")]
+ [Authorize(Roles = "bar")]
+ private class ClassWithMultipleAuthorizeAttributes
+ {
+ }
+
+ [Fact]
+ public void CanRetrieveMultipleAuthorizeAttributesFromOneClass()
+ {
+ // Arrange
+ ClassWithMultipleAuthorizeAttributes @class = new ClassWithMultipleAuthorizeAttributes();
+
+ // Act
+ IEnumerable<AuthorizeAttribute> attributes = TypeDescriptor.GetAttributes(@class).OfType<AuthorizeAttribute>();
+
+ // Assert
+ Assert.Equal(2, attributes.Count());
+ Assert.True(attributes.Any(a => a.Roles == "foo"));
+ Assert.True(attributes.Any(a => a.Roles == "bar"));
+ }
+
+ [Fact]
+ public void AuthorizeCoreReturnsFalseIfNameDoesNotMatch()
+ {
+ // Arrange
+ AuthorizeAttributeHelper helper = new AuthorizeAttributeHelper() { Users = "SomeName" };
+
+ Mock<HttpContextBase> mockHttpContext = new Mock<HttpContextBase>();
+ mockHttpContext.Setup(c => c.User.Identity.IsAuthenticated).Returns(true);
+ mockHttpContext.Setup(c => c.User.Identity.Name).Returns("SomeOtherName");
+
+ // Act
+ bool retVal = helper.PublicAuthorizeCore(mockHttpContext.Object);
+
+ // Assert
+ Assert.False(retVal);
+ }
+
+ [Fact]
+ public void AuthorizeCoreReturnsFalseIfRoleDoesNotMatch()
+ {
+ // Arrange
+ AuthorizeAttributeHelper helper = new AuthorizeAttributeHelper() { Roles = "SomeRole" };
+
+ Mock<HttpContextBase> mockHttpContext = new Mock<HttpContextBase>();
+ mockHttpContext.Setup(c => c.User.Identity.IsAuthenticated).Returns(true);
+ mockHttpContext.Setup(c => c.User.IsInRole("SomeRole")).Returns(false).Verifiable();
+
+ // Act
+ bool retVal = helper.PublicAuthorizeCore(mockHttpContext.Object);
+
+ // Assert
+ Assert.False(retVal);
+ mockHttpContext.Verify();
+ }
+
+ [Fact]
+ public void AuthorizeCoreReturnsFalseIfUserIsUnauthenticated()
+ {
+ // Arrange
+ AuthorizeAttributeHelper helper = new AuthorizeAttributeHelper();
+
+ Mock<HttpContextBase> mockHttpContext = new Mock<HttpContextBase>();
+ mockHttpContext.Setup(c => c.User.Identity.IsAuthenticated).Returns(false);
+
+ // Act
+ bool retVal = helper.PublicAuthorizeCore(mockHttpContext.Object);
+
+ // Assert
+ Assert.False(retVal);
+ }
+
+ [Fact]
+ public void AuthorizeCoreReturnsTrueIfUserIsAuthenticatedAndNamesOrRolesSpecified()
+ {
+ // Arrange
+ AuthorizeAttributeHelper helper = new AuthorizeAttributeHelper() { Users = "SomeUser, SomeOtherUser", Roles = "SomeRole, SomeOtherRole" };
+
+ Mock<HttpContextBase> mockHttpContext = new Mock<HttpContextBase>();
+ mockHttpContext.Setup(c => c.User.Identity.IsAuthenticated).Returns(true);
+ mockHttpContext.Setup(c => c.User.Identity.Name).Returns("SomeUser");
+ mockHttpContext.Setup(c => c.User.IsInRole("SomeRole")).Returns(false).Verifiable();
+ mockHttpContext.Setup(c => c.User.IsInRole("SomeOtherRole")).Returns(true).Verifiable();
+
+ // Act
+ bool retVal = helper.PublicAuthorizeCore(mockHttpContext.Object);
+
+ // Assert
+ Assert.True(retVal);
+ mockHttpContext.Verify();
+ }
+
+ [Fact]
+ public void AuthorizeCoreReturnsTrueIfUserIsAuthenticatedAndNoNamesOrRolesSpecified()
+ {
+ // Arrange
+ AuthorizeAttributeHelper helper = new AuthorizeAttributeHelper();
+
+ Mock<HttpContextBase> mockHttpContext = new Mock<HttpContextBase>();
+ mockHttpContext.Setup(c => c.User.Identity.IsAuthenticated).Returns(true);
+
+ // Act
+ bool retVal = helper.PublicAuthorizeCore(mockHttpContext.Object);
+
+ // Assert
+ Assert.True(retVal);
+ }
+
+ [Fact]
+ public void AuthorizeCoreThrowsIfHttpContextIsNull()
+ {
+ // Arrange
+ AuthorizeAttributeHelper helper = new AuthorizeAttributeHelper();
+
+ // Act & assert
+ Assert.ThrowsArgumentNull(
+ delegate { helper.PublicAuthorizeCore((HttpContextBase)null); }, "httpContext");
+ }
+
+ [Fact]
+ public void OnAuthorizationCallsHandleUnauthorizedRequestIfUserUnauthorized()
+ {
+ // Arrange
+ CustomFailAuthorizeAttribute attr = new CustomFailAuthorizeAttribute();
+
+ Mock<AuthorizationContext> mockAuthContext = new Mock<AuthorizationContext>();
+ mockAuthContext.Setup(c => c.HttpContext.User.Identity.IsAuthenticated).Returns(false);
+ mockAuthContext.Setup(c => c.HttpContext.Items).Returns(new Hashtable());
+ mockAuthContext.Setup(c => c.ActionDescriptor.ControllerDescriptor.IsDefined(typeof(AllowAnonymousAttribute), true)).Returns(false);
+ AuthorizationContext authContext = mockAuthContext.Object;
+
+ // Act
+ attr.OnAuthorization(authContext);
+
+ // Assert
+ Assert.Equal(CustomFailAuthorizeAttribute.ExpectedResult, authContext.Result);
+ }
+
+ [Fact]
+ public void OnAuthorizationFailedSetsHttpUnauthorizedResultIfUserUnauthorized()
+ {
+ // Arrange
+ Mock<AuthorizeAttributeHelper> mockHelper = new Mock<AuthorizeAttributeHelper>() { CallBase = true };
+ mockHelper.Setup(h => h.PublicAuthorizeCore(It.IsAny<HttpContextBase>())).Returns(false);
+ AuthorizeAttributeHelper helper = mockHelper.Object;
+
+ AuthorizationContext filterContext = new Mock<AuthorizationContext>() { DefaultValue = DefaultValue.Mock }.Object;
+
+ // Act
+ helper.OnAuthorization(filterContext);
+
+ // Assert
+ Assert.IsType<HttpUnauthorizedResult>(filterContext.Result);
+ }
+
+ [Fact]
+ public void OnAuthorizationHooksCacheValidationIfUserAuthorized()
+ {
+ // Arrange
+ Mock<AuthorizeAttributeHelper> mockHelper = new Mock<AuthorizeAttributeHelper>() { CallBase = true };
+ mockHelper.Setup(h => h.PublicAuthorizeCore(It.IsAny<HttpContextBase>())).Returns(true);
+ AuthorizeAttributeHelper helper = mockHelper.Object;
+
+ MethodInfo callbackMethod = typeof(AuthorizeAttribute).GetMethod("CacheValidateHandler", BindingFlags.Instance | BindingFlags.NonPublic);
+ Mock<AuthorizationContext> mockFilterContext = new Mock<AuthorizationContext>();
+ mockFilterContext.Setup(c => c.HttpContext.Response.Cache.SetProxyMaxAge(new TimeSpan(0))).Verifiable();
+ mockFilterContext.Setup(c => c.HttpContext.Items).Returns(new Hashtable());
+ mockFilterContext
+ .Setup(c => c.HttpContext.Response.Cache.AddValidationCallback(It.IsAny<HttpCacheValidateHandler>(), null /* data */))
+ .Callback(
+ delegate(HttpCacheValidateHandler handler, object data)
+ {
+ Assert.Equal(helper, handler.Target);
+ Assert.Equal(callbackMethod, handler.Method);
+ })
+ .Verifiable();
+ mockFilterContext.Setup(c => c.ActionDescriptor.ControllerDescriptor.IsDefined(typeof(AllowAnonymousAttribute), true)).Returns(false);
+ AuthorizationContext filterContext = mockFilterContext.Object;
+
+ // Act
+ helper.OnAuthorization(filterContext);
+
+ // Assert
+ mockFilterContext.Verify();
+ }
+
+ [Fact]
+ public void OnAuthorizationThrowsIfFilterContextIsNull()
+ {
+ // Arrange
+ AuthorizeAttribute attr = new AuthorizeAttribute();
+
+ // Act & assert
+ Assert.ThrowsArgumentNull(
+ delegate { attr.OnAuthorization(null); }, "filterContext");
+ }
+
+ [Fact]
+ public void OnAuthorizationReturnsWithNoResultIfAllowAnonymousAttributeIsDefinedOnAction()
+ {
+ // Arrange
+ Mock<AuthorizeAttributeHelper> mockHelper = new Mock<AuthorizeAttributeHelper>() { CallBase = true };
+ AuthorizeAttributeHelper helper = mockHelper.Object;
+
+ Mock<AuthorizationContext> mockFilterContext = new Mock<AuthorizationContext>();
+ mockFilterContext.Setup(c => c.HttpContext.Items).Returns(new Hashtable());
+ mockFilterContext.Setup(c => c.ActionDescriptor.IsDefined(typeof(AllowAnonymousAttribute), true)).Returns(true);
+
+ // Act
+ helper.OnAuthorization(mockFilterContext.Object);
+
+ // Assert
+ Assert.Null(mockFilterContext.Object.Result);
+ mockHelper.Verify(h => h.PublicAuthorizeCore(It.IsAny<HttpContextBase>()), Times.Never());
+ }
+
+ [Fact]
+ public void OnAuthorizationReturnsWithNoResultIfAllowAnonymousAttributeIsDefinedOnController()
+ {
+ // Arrange
+ Mock<AuthorizeAttributeHelper> mockHelper = new Mock<AuthorizeAttributeHelper>() { CallBase = true };
+ AuthorizeAttributeHelper helper = mockHelper.Object;
+
+ Mock<AuthorizationContext> mockFilterContext = new Mock<AuthorizationContext>();
+ mockFilterContext.Setup(c => c.HttpContext.Items).Returns(new Hashtable());
+ mockFilterContext.Setup(c => c.ActionDescriptor.ControllerDescriptor.IsDefined(typeof(AllowAnonymousAttribute), true)).Returns(true);
+
+ // Act
+ helper.OnAuthorization(mockFilterContext.Object);
+
+ // Assert
+ Assert.Null(mockFilterContext.Object.Result);
+ mockHelper.Verify(h => h.PublicAuthorizeCore(It.IsAny<HttpContextBase>()), Times.Never());
+ }
+
+ [Fact]
+ public void OnCacheAuthorizationReturnsIgnoreRequestIfUserIsUnauthorized()
+ {
+ // Arrange
+ Mock<AuthorizeAttributeHelper> mockHelper = new Mock<AuthorizeAttributeHelper>() { CallBase = true };
+ mockHelper.Setup(h => h.PublicAuthorizeCore(It.IsAny<HttpContextBase>())).Returns(false);
+ AuthorizeAttributeHelper helper = mockHelper.Object;
+
+ Mock<HttpContextBase> mockHttpContext = new Mock<HttpContextBase>();
+ mockHttpContext.Setup(c => c.User).Returns(new Mock<IPrincipal>().Object);
+
+ // Act
+ HttpValidationStatus validationStatus = helper.PublicOnCacheAuthorization(mockHttpContext.Object);
+
+ // Assert
+ Assert.Equal(HttpValidationStatus.IgnoreThisRequest, validationStatus);
+ }
+
+ [Fact]
+ public void OnCacheAuthorizationReturnsValidIfUserIsAuthorized()
+ {
+ // Arrange
+ Mock<AuthorizeAttributeHelper> mockHelper = new Mock<AuthorizeAttributeHelper>() { CallBase = true };
+ mockHelper.Setup(h => h.PublicAuthorizeCore(It.IsAny<HttpContextBase>())).Returns(true);
+ AuthorizeAttributeHelper helper = mockHelper.Object;
+
+ Mock<HttpContextBase> mockHttpContext = new Mock<HttpContextBase>();
+ mockHttpContext.Setup(c => c.User).Returns(new Mock<IPrincipal>().Object);
+
+ // Act
+ HttpValidationStatus validationStatus = helper.PublicOnCacheAuthorization(mockHttpContext.Object);
+
+ // Assert
+ Assert.Equal(HttpValidationStatus.Valid, validationStatus);
+ }
+
+ [Fact]
+ public void OnCacheAuthorizationThrowsIfHttpContextIsNull()
+ {
+ // Arrange
+ AuthorizeAttributeHelper helper = new AuthorizeAttributeHelper();
+
+ // Act & assert
+ Assert.ThrowsArgumentNull(
+ delegate { helper.PublicOnCacheAuthorization(null); }, "httpContext");
+ }
+
+ [Fact]
+ public void RolesProperty()
+ {
+ // Arrange
+ AuthorizeAttribute attr = new AuthorizeAttribute();
+
+ // Act & assert
+ MemberHelper.TestStringProperty(attr, "Roles", String.Empty);
+ }
+
+ [Fact]
+ public void UsersProperty()
+ {
+ // Arrange
+ AuthorizeAttribute attr = new AuthorizeAttribute();
+
+ // Act & assert
+ MemberHelper.TestStringProperty(attr, "Users", String.Empty);
+ }
+
+ public class AuthorizeAttributeHelper : AuthorizeAttribute
+ {
+ public virtual bool PublicAuthorizeCore(HttpContextBase httpContext)
+ {
+ return base.AuthorizeCore(httpContext);
+ }
+
+ protected override bool AuthorizeCore(HttpContextBase httpContext)
+ {
+ return PublicAuthorizeCore(httpContext);
+ }
+
+ public virtual HttpValidationStatus PublicOnCacheAuthorization(HttpContextBase httpContext)
+ {
+ return base.OnCacheAuthorization(httpContext);
+ }
+
+ protected override HttpValidationStatus OnCacheAuthorization(HttpContextBase httpContext)
+ {
+ return PublicOnCacheAuthorization(httpContext);
+ }
+ }
+
+ public class CustomFailAuthorizeAttribute : AuthorizeAttribute
+ {
+ public static readonly ActionResult ExpectedResult = new ContentResult();
+
+ protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
+ {
+ filterContext.Result = ExpectedResult;
+ }
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Test/BindAttributeTest.cs b/test/System.Web.Mvc.Test/Test/BindAttributeTest.cs
new file mode 100644
index 00000000..625e2513
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Test/BindAttributeTest.cs
@@ -0,0 +1,96 @@
+using Xunit;
+
+namespace System.Web.Mvc.Test
+{
+ public class BindAttributeTest
+ {
+ [Fact]
+ public void PrefixProperty()
+ {
+ // Arrange
+ BindAttribute attr = new BindAttribute { Prefix = "somePrefix" };
+
+ // Act & assert
+ Assert.Equal("somePrefix", attr.Prefix);
+ }
+
+ [Fact]
+ public void PrefixPropertyDefaultsToNull()
+ {
+ // Arrange
+ BindAttribute attr = new BindAttribute();
+
+ // Act & assert
+ Assert.Null(attr.Prefix);
+ }
+
+ [Fact]
+ public void IncludePropertyDefaultsToEmptyString()
+ {
+ // Arrange
+ BindAttribute attr = new BindAttribute { Include = null };
+
+ // Act & assert
+ Assert.Equal(String.Empty, attr.Include);
+ }
+
+ [Fact]
+ public void ExcludePropertyDefaultsToEmptyString()
+ {
+ // Arrange
+ BindAttribute attr = new BindAttribute { Exclude = null };
+
+ // Act & assert
+ Assert.Equal(String.Empty, attr.Exclude);
+ }
+
+ [Fact]
+ public void IsPropertyAllowedReturnsFalseForBlacklistedPropertiesIfBindPropertiesIsExclude()
+ {
+ // Setup
+ BindAttribute attr = new BindAttribute { Exclude = "FOO,BAZ" };
+
+ // Act & assert
+ Assert.False(attr.IsPropertyAllowed("foo"));
+ Assert.True(attr.IsPropertyAllowed("bar"));
+ Assert.False(attr.IsPropertyAllowed("baz"));
+ }
+
+ [Fact]
+ public void IsPropertyAllowedReturnsTrueAlwaysIfBindPropertiesIsAll()
+ {
+ // Setup
+ BindAttribute attr = new BindAttribute();
+
+ // Act & assert
+ Assert.True(attr.IsPropertyAllowed("foo"));
+ Assert.True(attr.IsPropertyAllowed("bar"));
+ Assert.True(attr.IsPropertyAllowed("baz"));
+ }
+
+ [Fact]
+ public void IsPropertyAllowedReturnsTrueForWhitelistedPropertiesIfBindPropertiesIsInclude()
+ {
+ // Setup
+ BindAttribute attr = new BindAttribute { Include = "FOO,BAR" };
+
+ // Act & assert
+ Assert.True(attr.IsPropertyAllowed("foo"));
+ Assert.True(attr.IsPropertyAllowed("bar"));
+ Assert.False(attr.IsPropertyAllowed("baz"));
+ }
+
+ [Fact]
+ public void IsPropertyAllowedReturnsFalseForBlacklistOverridingWhitelistedProperties()
+ {
+ // Setup
+ BindAttribute attr = new BindAttribute { Include = "FOO,BAR", Exclude = "bar,QUx" };
+
+ // Act & assert
+ Assert.True(attr.IsPropertyAllowed("foo"));
+ Assert.False(attr.IsPropertyAllowed("bar"));
+ Assert.False(attr.IsPropertyAllowed("baz"));
+ Assert.False(attr.IsPropertyAllowed("qux"));
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Test/BuildManagerCompiledViewTest.cs b/test/System.Web.Mvc.Test/Test/BuildManagerCompiledViewTest.cs
new file mode 100644
index 00000000..8e0278fb
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Test/BuildManagerCompiledViewTest.cs
@@ -0,0 +1,145 @@
+using System.IO;
+using Moq;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Mvc.Test
+{
+ public class CompiledTypeViewTest
+ {
+ [Fact]
+ public void GuardClauses()
+ {
+ // Act & Assert
+ Assert.ThrowsArgumentNullOrEmpty(
+ () => new TestableBuildManagerCompiledView(new ControllerContext(), String.Empty),
+ "viewPath"
+ );
+
+ // Act & Assert
+ Assert.ThrowsArgumentNullOrEmpty(
+ () => new TestableBuildManagerCompiledView(new ControllerContext(), null),
+ "viewPath"
+ );
+
+ // Act & Assert
+ Assert.ThrowsArgumentNull(
+ () => new TestableBuildManagerCompiledView(null, "view path"),
+ "controllerContext"
+ );
+ }
+
+ [Fact]
+ public void RenderWithNullContextThrows()
+ {
+ // Arrange
+ TestableBuildManagerCompiledView view = new TestableBuildManagerCompiledView(new ControllerContext(), "~/view");
+
+ // Act & Assert
+ Assert.ThrowsArgumentNull(
+ () => view.Render(null, new Mock<TextWriter>().Object),
+ "viewContext"
+ );
+ }
+
+ [Fact]
+ public void RenderWithNullViewInstanceThrows()
+ {
+ // Arrange
+ ViewContext context = new Mock<ViewContext>().Object;
+ MockBuildManager buildManager = new MockBuildManager("view path", compiledType: null);
+ TestableBuildManagerCompiledView view = new TestableBuildManagerCompiledView(new ControllerContext(), "view path");
+ view.BuildManager = buildManager;
+
+ // Act & Assert
+ Assert.Throws<InvalidOperationException>(
+ () => view.Render(context, new Mock<TextWriter>().Object),
+ "The view found at 'view path' was not created."
+ );
+ }
+
+ [Fact]
+ public void ViewPathProperty()
+ {
+ // Act
+ BuildManagerCompiledView view = new TestableBuildManagerCompiledView(new ControllerContext(), "view path");
+
+ // Assert
+ Assert.Equal("view path", view.ViewPath);
+ }
+
+ [Fact]
+ public void ViewCreationConsultsSetActivator()
+ {
+ // Arrange
+ object viewInstance = new object();
+ Mock<IViewPageActivator> activator = new Mock<IViewPageActivator>(MockBehavior.Strict);
+ ControllerContext controllerContext = new ControllerContext();
+ activator.Setup(a => a.Create(controllerContext, typeof(object))).Returns(viewInstance).Verifiable();
+ MockBuildManager buildManager = new MockBuildManager("view path", typeof(object));
+ BuildManagerCompiledView view = new TestableBuildManagerCompiledView(controllerContext, "view path", activator.Object) { BuildManager = buildManager };
+
+ // Act
+ view.Render(new Mock<ViewContext>().Object, new Mock<TextWriter>().Object);
+
+ // Assert
+ activator.Verify();
+ }
+
+ [Fact]
+ public void ViewCreationDelegatesToDependencyResolverWhenActivatorIsNull()
+ {
+ // Arrange
+ var viewInstance = new object();
+ var controllerContext = new ControllerContext();
+ var buildManager = new MockBuildManager("view path", typeof(object));
+ var dependencyResolver = new Mock<IDependencyResolver>(MockBehavior.Strict);
+ dependencyResolver.Setup(dr => dr.GetService(typeof(object))).Returns(viewInstance).Verifiable();
+ var view = new TestableBuildManagerCompiledView(controllerContext, "view path", dependencyResolver: dependencyResolver.Object) { BuildManager = buildManager };
+
+ // Act
+ view.Render(new Mock<ViewContext>().Object, new Mock<TextWriter>().Object);
+
+ // Assert
+ dependencyResolver.Verify();
+ }
+
+ [Fact]
+ public void ViewCreationDelegatesToActivatorCreateInstanceWhenDependencyResolverReturnsNull()
+ {
+ // Arrange
+ var controllerContext = new ControllerContext();
+ var buildManager = new MockBuildManager("view path", typeof(NoParameterlessCtor));
+ var dependencyResolver = new Mock<IDependencyResolver>();
+ var view = new TestableBuildManagerCompiledView(controllerContext, "view path", dependencyResolver: dependencyResolver.Object) { BuildManager = buildManager };
+
+ // Act
+ MissingMethodException ex = Assert.Throws<MissingMethodException>( // Depend on the fact that Activator.CreateInstance cannot create an object without a parameterless ctor
+ () => view.Render(new Mock<ViewContext>().Object, new Mock<TextWriter>().Object)
+ );
+
+ // Assert
+ Assert.Contains("System.Activator.CreateInstance(", ex.StackTrace);
+ }
+
+ private class NoParameterlessCtor
+ {
+ public NoParameterlessCtor(int x)
+ {
+ }
+ }
+
+ private sealed class TestableBuildManagerCompiledView : BuildManagerCompiledView
+ {
+ public TestableBuildManagerCompiledView(ControllerContext controllerContext, string viewPath, IViewPageActivator viewPageActivator = null, IDependencyResolver dependencyResolver = null)
+ : base(controllerContext, viewPath, viewPageActivator, dependencyResolver)
+ {
+ }
+
+ protected override void RenderView(ViewContext viewContext, TextWriter writer, object instance)
+ {
+ return;
+ }
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Test/BuildManagerViewEngineTest.cs b/test/System.Web.Mvc.Test/Test/BuildManagerViewEngineTest.cs
new file mode 100644
index 00000000..59640cc0
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Test/BuildManagerViewEngineTest.cs
@@ -0,0 +1,182 @@
+using Moq;
+using Xunit;
+
+namespace System.Web.Mvc.Test
+{
+ public class BuildManagerViewEngineTest
+ {
+ [Fact]
+ public void BuildManagerProperty()
+ {
+ // Arrange
+ var engine = new TestableBuildManagerViewEngine();
+ var buildManagerMock = new MockBuildManager(expectedVirtualPath: null, compiledType: null);
+
+ // Act
+ engine.BuildManager = buildManagerMock;
+
+ // Assert
+ Assert.Same(engine.BuildManager, buildManagerMock);
+ }
+
+ [Fact]
+ public void FileExistsReturnsTrueForExistingPath()
+ {
+ // Arrange
+ var engine = new TestableBuildManagerViewEngine();
+ var buildManagerMock = new MockBuildManager("some path", typeof(object));
+ engine.BuildManager = buildManagerMock;
+
+ // Act
+ bool result = engine.FileExists("some path");
+
+ // Assert
+ Assert.True(result);
+ }
+
+ [Fact]
+ public void FileExistsReturnsFalseWhenBuildManagerFileExistsReturnsFalse()
+ {
+ // Arrange
+ var engine = new TestableBuildManagerViewEngine();
+ var buildManagerMock = new MockBuildManager("some path", false);
+ engine.BuildManager = buildManagerMock;
+
+ // Act
+ bool result = engine.FileExists("some path");
+
+ // Assert
+ Assert.False(result);
+ }
+
+ [Fact]
+ public void ViewPageActivatorConsultsSetActivatorResolver()
+ {
+ // Arrange
+ Mock<IViewPageActivator> activator = new Mock<IViewPageActivator>();
+
+ // Act
+ TestableBuildManagerViewEngine engine = new TestableBuildManagerViewEngine(activator.Object);
+
+ //Assert
+ Assert.Equal(activator.Object, engine.ViewPageActivator);
+ }
+
+ [Fact]
+ public void ViewPageActivatorDelegatesToActivatorResolver()
+ {
+ // Arrange
+ var activator = new Mock<IViewPageActivator>();
+ var activatorResolver = new Resolver<IViewPageActivator> { Current = activator.Object };
+
+ // Act
+ TestableBuildManagerViewEngine engine = new TestableBuildManagerViewEngine(activatorResolver: activatorResolver);
+
+ // Assert
+ Assert.Equal(activator.Object, engine.ViewPageActivator);
+ }
+
+ [Fact]
+ public void ViewPageActivatorDelegatesToDependencyResolverWhenActivatorResolverIsNull()
+ {
+ // Arrange
+ var viewInstance = new object();
+ var controllerContext = new ControllerContext();
+ var buildManager = new MockBuildManager("view path", typeof(object));
+ var dependencyResolver = new Mock<IDependencyResolver>(MockBehavior.Strict);
+ dependencyResolver.Setup(dr => dr.GetService(typeof(object))).Returns(viewInstance).Verifiable();
+
+ // Act
+ TestableBuildManagerViewEngine engine = new TestableBuildManagerViewEngine(dependencyResolver: dependencyResolver.Object);
+ engine.ViewPageActivator.Create(controllerContext, typeof(object));
+
+ // Assert
+ dependencyResolver.Verify();
+ }
+
+ [Fact]
+ public void ViewPageActivatorDelegatesToActivatorCreateInstanceWhenDependencyResolverReturnsNull()
+ {
+ // Arrange
+ var controllerContext = new ControllerContext();
+ var buildManager = new MockBuildManager("view path", typeof(NoParameterlessCtor));
+ var dependencyResolver = new Mock<IDependencyResolver>();
+
+ var engine = new TestableBuildManagerViewEngine(dependencyResolver: dependencyResolver.Object);
+
+ // Act
+ MissingMethodException ex = Assert.Throws<MissingMethodException>( // Depend on the fact that Activator.CreateInstance cannot create an object without a parameterless ctor
+ () => engine.ViewPageActivator.Create(controllerContext, typeof(NoParameterlessCtor))
+ );
+
+ // Assert
+ Assert.Contains("System.Activator.CreateInstance(", ex.StackTrace);
+ }
+
+ [Fact]
+ public void ActivatorResolverAndDependencyResolverAreNeverCalledWhenViewPageActivatorIsPassedInContstructor()
+ {
+ // Arrange
+ var controllerContext = new ControllerContext();
+ var expectedController = new Goodcontroller();
+
+ Mock<IViewPageActivator> activator = new Mock<IViewPageActivator>();
+
+ var resolverActivator = new Mock<IViewPageActivator>(MockBehavior.Strict);
+ var activatorResolver = new Resolver<IViewPageActivator> { Current = resolverActivator.Object };
+
+ var dependencyResolver = new Mock<IDependencyResolver>(MockBehavior.Strict);
+
+ //Act
+ var engine = new TestableBuildManagerViewEngine(activator.Object, activatorResolver, dependencyResolver.Object);
+
+ //Assert
+ Assert.Same(activator.Object, engine.ViewPageActivator);
+ }
+
+ private class NoParameterlessCtor
+ {
+ public NoParameterlessCtor(int x)
+ {
+ }
+ }
+
+ private class TestableBuildManagerViewEngine : BuildManagerViewEngine
+ {
+ public TestableBuildManagerViewEngine()
+ : base()
+ {
+ }
+
+ public TestableBuildManagerViewEngine(IViewPageActivator viewPageActivator)
+ : base(viewPageActivator)
+ {
+ }
+
+ public TestableBuildManagerViewEngine(IViewPageActivator viewPageActivator = null, IResolver<IViewPageActivator> activatorResolver = null, IDependencyResolver dependencyResolver = null)
+ : base(viewPageActivator, activatorResolver, dependencyResolver)
+ {
+ }
+
+ public new IViewPageActivator ViewPageActivator
+ {
+ get { return base.ViewPageActivator; }
+ }
+
+ protected override IView CreatePartialView(ControllerContext controllerContext, string partialPath)
+ {
+ throw new NotImplementedException();
+ }
+
+ protected override IView CreateView(ControllerContext controllerContext, string viewPath, string masterPath)
+ {
+ throw new NotImplementedException();
+ }
+
+ public bool FileExists(string virtualPath)
+ {
+ return base.FileExists(null, virtualPath);
+ }
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Test/ByteArrayModelBinderTest.cs b/test/System.Web.Mvc.Test/Test/ByteArrayModelBinderTest.cs
new file mode 100644
index 00000000..2aaf6b53
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Test/ByteArrayModelBinderTest.cs
@@ -0,0 +1,121 @@
+using Microsoft.Web.UnitTestUtil;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Mvc.Test
+{
+ public class ByteArrayModelBinderTest
+ {
+ internal const string Base64TestString = "Fys1";
+ internal static readonly byte[] Base64TestBytes = new byte[] { 23, 43, 53 };
+
+ [Fact]
+ public void BindModelWithNonExistentValueReturnsNull()
+ {
+ // Arrange
+ SimpleValueProvider valueProvider = new SimpleValueProvider()
+ {
+ { "foo", null }
+ };
+
+ ModelBindingContext bindingContext = new ModelBindingContext()
+ {
+ ModelName = "foo",
+ ValueProvider = valueProvider
+ };
+
+ ByteArrayModelBinder binder = new ByteArrayModelBinder();
+
+ // Act
+ object binderResult = binder.BindModel(null, bindingContext);
+
+ // Assert
+ Assert.Null(binderResult);
+ }
+
+ [Fact]
+ public void BinderWithEmptyStringValueReturnsNull()
+ {
+ // Arrange
+ SimpleValueProvider valueProvider = new SimpleValueProvider()
+ {
+ { "foo", "" }
+ };
+
+ ModelBindingContext bindingContext = new ModelBindingContext()
+ {
+ ModelName = "foo",
+ ValueProvider = valueProvider
+ };
+
+ ByteArrayModelBinder binder = new ByteArrayModelBinder();
+
+ // Act
+ object binderResult = binder.BindModel(null, bindingContext);
+
+ // Assert
+ Assert.Null(binderResult);
+ }
+
+ [Fact]
+ public void BindModelThrowsIfBindingContextIsNull()
+ {
+ // Arrange
+ ByteArrayModelBinder binder = new ByteArrayModelBinder();
+
+ // Act & assert
+ Assert.ThrowsArgumentNull(
+ delegate { binder.BindModel(null, null); }, "bindingContext");
+ }
+
+ [Fact]
+ public void BindModelWithBase64QuotedValueReturnsByteArray()
+ {
+ // Arrange
+ string base64Value = Base64TestString;
+ SimpleValueProvider valueProvider = new SimpleValueProvider()
+ {
+ { "foo", "\"" + base64Value + "\"" }
+ };
+
+ ModelBindingContext bindingContext = new ModelBindingContext()
+ {
+ ModelName = "foo",
+ ValueProvider = valueProvider
+ };
+
+ ByteArrayModelBinder binder = new ByteArrayModelBinder();
+
+ // Act
+ byte[] boundValue = binder.BindModel(null, bindingContext) as byte[];
+
+ // Assert
+ Assert.Equal(Base64TestBytes, boundValue);
+ }
+
+ [Fact]
+ public void BindModelWithBase64UnquotedValueReturnsByteArray()
+ {
+ // Arrange
+ string base64Value = Base64TestString;
+ SimpleValueProvider valueProvider = new SimpleValueProvider()
+ {
+ { "foo", base64Value }
+ };
+
+ ModelBindingContext bindingContext = new ModelBindingContext()
+ {
+ ModelName = "foo",
+ ValueProvider = valueProvider
+ };
+
+ ByteArrayModelBinder binder = new ByteArrayModelBinder();
+
+ // Act
+ byte[] boundValue = binder.BindModel(null, bindingContext) as byte[];
+
+ // Assert
+ Assert.Equal(Base64TestBytes, boundValue);
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Test/CachedAssociatedMetadataProviderTest.cs b/test/System.Web.Mvc.Test/Test/CachedAssociatedMetadataProviderTest.cs
new file mode 100644
index 00000000..38388b3d
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Test/CachedAssociatedMetadataProviderTest.cs
@@ -0,0 +1,275 @@
+using System.Collections.Generic;
+using System.Linq;
+using System.Runtime.Caching;
+using Moq;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Mvc.Test
+{
+ public class CachedAssociatedMetadataProviderTest
+ {
+ [Fact]
+ public void GetMetadataForPropertyInvalidPropertyNameThrows()
+ {
+ // Arrange
+ MockableCachedAssociatedMetadataProvider provider = new MockableCachedAssociatedMetadataProvider();
+
+ // Act & Assert
+ Assert.Throws<ArgumentException>(
+ () => provider.GetMetadataForProperty(null, typeof(object), "BadPropertyName"),
+ "The property System.Object.BadPropertyName could not be found.");
+ }
+
+ [Fact]
+ public void GetCacheKey_ResultsForTypesDoNotCollide()
+ {
+ // Arrange
+ var provider = new MockableCachedAssociatedMetadataProvider();
+ var keys = new List<string>();
+
+ // Act
+ keys.Add(provider.GetCacheKey(typeof(string)));
+ keys.Add(provider.GetCacheKey(typeof(int)));
+ keys.Add(provider.GetCacheKey(typeof(Nullable<int>)));
+ keys.Add(provider.GetCacheKey(typeof(Nullable<bool>)));
+ keys.Add(provider.GetCacheKey(typeof(List<string>)));
+ keys.Add(provider.GetCacheKey(typeof(List<bool>)));
+
+ // Assert
+ Assert.Equal(keys.Distinct().Count(), keys.Count);
+ }
+
+ [Fact]
+ public void GetCacheKey_ResultsForTypesAndPropertiesDoNotCollide()
+ {
+ // Arrange
+ var provider = new MockableCachedAssociatedMetadataProvider();
+ var keys = new List<string>();
+
+ // Act
+ keys.Add(provider.GetCacheKey(typeof(string), "Foo"));
+ keys.Add(provider.GetCacheKey(typeof(string), "Bar"));
+ keys.Add(provider.GetCacheKey(typeof(int), "Foo"));
+ keys.Add(provider.GetCacheKey(typeof(Nullable<int>), "Foo"));
+ keys.Add(provider.GetCacheKey(typeof(Nullable<bool>), "Foo"));
+ keys.Add(provider.GetCacheKey(typeof(List<string>), "Count"));
+ keys.Add(provider.GetCacheKey(typeof(List<bool>), "Count"));
+ keys.Add(provider.GetCacheKey(typeof(Foo), "BarBaz"));
+ keys.Add(provider.GetCacheKey(typeof(FooBar), "Baz"));
+
+ // Assert
+ Assert.Equal(keys.Distinct().Count(), keys.Count);
+ }
+
+ private class Foo
+ {
+ }
+
+ private class FooBar
+ {
+ }
+
+ // GetMetadataForProperty
+
+ [Fact]
+ public void GetMetadataForPropertyCreatesPrototypeMetadataAndAddsItToCache()
+ {
+ // Arrange
+ var provider = new Mock<MockableCachedAssociatedMetadataProvider> { CallBase = true };
+
+ // Act
+ provider.Object.GetMetadataForProperty(() => 3, typeof(string), "Length");
+
+ // Assert
+ provider.Verify(p => p.CreateMetadataPrototypeImpl(It.IsAny<IEnumerable<Attribute>>(),
+ typeof(string) /* containerType */,
+ typeof(int) /* modelType */,
+ "Length" /* propertyName */));
+ provider.Object.Cache.Verify(c => c.Add(provider.Object.GetCacheKey(typeof(string), "Length"),
+ provider.Object.PrototypeMetadata,
+ provider.Object.CacheItemPolicy, null));
+ }
+
+ [Fact]
+ public void GetMetadataForPropertyCreatesRealMetadataFromPrototype()
+ {
+ // Arrange
+ Func<object> accessor = () => 3;
+ var provider = new Mock<MockableCachedAssociatedMetadataProvider> { CallBase = true };
+
+ // Act
+ provider.Object.GetMetadataForProperty(accessor, typeof(string), "Length");
+
+ // Assert
+ provider.Verify(p => p.CreateMetadataFromPrototypeImpl(provider.Object.PrototypeMetadata, accessor));
+ }
+
+ [Fact]
+ public void MetaDataAwareAttributesForPropertyAreAppliedToMetadata()
+ {
+ // Arrange
+ MemoryCache memoryCache = new MemoryCache("testCache");
+ MockableCachedAssociatedMetadataProvider provider = new MockableCachedAssociatedMetadataProvider(memoryCache);
+
+ // Act
+ ModelMetadata metadata = provider.GetMetadataForProperty(null, typeof(ClassWithMetaDataAwareAttributes), "PropertyWithAdditionalValue");
+
+ // Assert
+ Assert.True(metadata.AdditionalValues["baz"].Equals("biz"));
+ }
+
+ [Fact]
+ public void GetMetadataForPropertyTwiceOnlyCreatesAndCachesPrototypeOnce()
+ {
+ // Arrange
+ Func<object> accessor = () => 3;
+ var provider = new Mock<MockableCachedAssociatedMetadataProvider> { CallBase = true };
+
+ // Act
+ provider.Object.GetMetadataForProperty(accessor, typeof(string), "Length");
+ provider.Object.GetMetadataForProperty(accessor, typeof(string), "Length");
+
+ // Assert
+ provider.Verify(p => p.CreateMetadataPrototypeImpl(It.IsAny<IEnumerable<Attribute>>(),
+ typeof(string) /* containerType */,
+ typeof(int) /* modelType */,
+ "Length" /* propertyName */),
+ Times.Once());
+
+ provider.Verify(p => p.CreateMetadataFromPrototypeImpl(provider.Object.PrototypeMetadata, accessor),
+ Times.Exactly(2));
+
+ provider.Object.Cache.Verify(c => c.Add(provider.Object.GetCacheKey(typeof(string), "Length"),
+ provider.Object.PrototypeMetadata,
+ provider.Object.CacheItemPolicy, null),
+ Times.Once());
+ }
+
+ // GetMetadataForType
+
+ [Fact]
+ public void GetMetadataForTypeCreatesPrototypeMetadataAndAddsItToCache()
+ {
+ // Arrange
+ var provider = new Mock<MockableCachedAssociatedMetadataProvider> { CallBase = true };
+
+ // Act
+ provider.Object.GetMetadataForType(() => "foo", typeof(string));
+
+ // Assert
+ provider.Verify(p => p.CreateMetadataPrototypeImpl(It.IsAny<IEnumerable<Attribute>>(),
+ null /* containerType */,
+ typeof(string) /* modelType */,
+ null /* propertyName */));
+ provider.Object.Cache.Verify(c => c.Add(provider.Object.GetCacheKey(typeof(string), null),
+ provider.Object.PrototypeMetadata,
+ provider.Object.CacheItemPolicy, null));
+ }
+
+ [Fact]
+ public void GetMetadataForTypeCreatesRealMetadataFromPrototype()
+ {
+ // Arrange
+ Func<object> accessor = () => "foo";
+ var provider = new Mock<MockableCachedAssociatedMetadataProvider> { CallBase = true };
+
+ // Act
+ provider.Object.GetMetadataForType(accessor, typeof(string));
+
+ // Assert
+ provider.Verify(p => p.CreateMetadataFromPrototypeImpl(provider.Object.PrototypeMetadata, accessor));
+ }
+
+ [Fact]
+ public void MetaDataAwareAttributesForTypeAreAppliedToMetadata()
+ {
+ // Arrange
+ MemoryCache memoryCache = new MemoryCache("testCache");
+ MockableCachedAssociatedMetadataProvider provider = new MockableCachedAssociatedMetadataProvider(memoryCache);
+
+ // Act
+ ModelMetadata metadata = provider.GetMetadataForType(null, typeof(ClassWithMetaDataAwareAttributes));
+
+ // Assert
+ Assert.True(metadata.AdditionalValues["foo"].Equals("bar"));
+ }
+
+ [Fact]
+ public void GetMetadataForTypeTwiceOnlyCreatesAndCachesPrototypeOnce()
+ {
+ // Arrange
+ Func<object> accessor = () => "foo";
+ var provider = new Mock<MockableCachedAssociatedMetadataProvider> { CallBase = true };
+
+ // Act
+ provider.Object.GetMetadataForType(accessor, typeof(string));
+ provider.Object.GetMetadataForType(accessor, typeof(string));
+
+ // Assert
+ provider.Verify(p => p.CreateMetadataPrototypeImpl(It.IsAny<IEnumerable<Attribute>>(),
+ null /* containerType */,
+ typeof(string) /* modelType */,
+ null /* propertyName */),
+ Times.Once());
+
+ provider.Verify(p => p.CreateMetadataFromPrototypeImpl(provider.Object.PrototypeMetadata, accessor),
+ Times.Exactly(2));
+
+ provider.Object.Cache.Verify(c => c.Add(provider.Object.GetCacheKey(typeof(string), null),
+ provider.Object.PrototypeMetadata,
+ provider.Object.CacheItemPolicy, null),
+ Times.Once());
+ }
+
+ // Helpers
+
+ public class MockableCachedAssociatedMetadataProvider : CachedAssociatedMetadataProvider<ModelMetadata>
+ {
+ public Mock<MemoryCache> Cache;
+ public ModelMetadata PrototypeMetadata;
+ public ModelMetadata RealMetadata;
+
+ public MockableCachedAssociatedMetadataProvider()
+ : this(null)
+ {
+ }
+
+ public MockableCachedAssociatedMetadataProvider(MemoryCache memoryCache = null)
+ {
+ Cache = new Mock<MemoryCache>("MockMemoryCache", null) { CallBase = true };
+ PrototypeMetadata = new ModelMetadata(this, null, null, typeof(string), null);
+ RealMetadata = new ModelMetadata(this, null, null, typeof(string), null);
+
+ PrototypeCache = memoryCache ?? Cache.Object;
+ }
+
+ public virtual ModelMetadata CreateMetadataPrototypeImpl(IEnumerable<Attribute> attributes, Type containerType, Type modelType, string propertyName)
+ {
+ return PrototypeMetadata;
+ }
+
+ public virtual ModelMetadata CreateMetadataFromPrototypeImpl(ModelMetadata prototype, Func<object> modelAccessor)
+ {
+ return RealMetadata;
+ }
+
+ protected override ModelMetadata CreateMetadataPrototype(IEnumerable<Attribute> attributes, Type containerType, Type modelType, string propertyName)
+ {
+ return CreateMetadataPrototypeImpl(attributes, containerType, modelType, propertyName);
+ }
+
+ protected override ModelMetadata CreateMetadataFromPrototype(ModelMetadata prototype, Func<object> modelAccessor)
+ {
+ return CreateMetadataFromPrototypeImpl(prototype, modelAccessor);
+ }
+ }
+
+ [AdditionalMetadata("foo", "bar")]
+ private class ClassWithMetaDataAwareAttributes
+ {
+ [AdditionalMetadata("baz", "biz")]
+ public string PropertyWithAdditionalValue { get; set; }
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Test/CachedDataAnnotationsModelMetadataProviderTest.cs b/test/System.Web.Mvc.Test/Test/CachedDataAnnotationsModelMetadataProviderTest.cs
new file mode 100644
index 00000000..0de0b039
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Test/CachedDataAnnotationsModelMetadataProviderTest.cs
@@ -0,0 +1,10 @@
+namespace System.Web.Mvc.Test
+{
+ public class CachedDataAnnotationsModelMetadataProviderTest : DataAnnotationsModelMetadataProviderTestBase
+ {
+ protected override AssociatedMetadataProvider MakeProvider()
+ {
+ return new CachedDataAnnotationsModelMetadataProvider();
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Test/CancellationTokenModelBinderTest.cs b/test/System.Web.Mvc.Test/Test/CancellationTokenModelBinderTest.cs
new file mode 100644
index 00000000..52ee5649
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Test/CancellationTokenModelBinderTest.cs
@@ -0,0 +1,21 @@
+using System.Threading;
+using Xunit;
+
+namespace System.Web.Mvc.Test
+{
+ public class CancellationTokenModelBinderTest
+ {
+ [Fact]
+ public void BinderReturnsDefaultCancellationToken()
+ {
+ // Arrange
+ CancellationTokenModelBinder binder = new CancellationTokenModelBinder();
+
+ // Act
+ object binderResult = binder.BindModel(controllerContext: null, bindingContext: null);
+
+ // Assert
+ Assert.Equal(default(CancellationToken), binderResult);
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Test/ChildActionOnlyAttributeTest.cs b/test/System.Web.Mvc.Test/Test/ChildActionOnlyAttributeTest.cs
new file mode 100644
index 00000000..1df26168
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Test/ChildActionOnlyAttributeTest.cs
@@ -0,0 +1,52 @@
+using Moq;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Mvc.Test
+{
+ public class ChildActionOnlyAttributeTest
+ {
+ [Fact]
+ public void GuardClause()
+ {
+ // Arrange
+ ChildActionOnlyAttribute attr = new ChildActionOnlyAttribute();
+
+ // Act & Assert
+ Assert.ThrowsArgumentNull(
+ () => attr.OnAuthorization(null /* filterContext */),
+ "filterContext"
+ );
+ }
+
+ [Fact]
+ public void DoesNothingForChildRequest()
+ {
+ // Arrange
+ ChildActionOnlyAttribute attr = new ChildActionOnlyAttribute();
+ Mock<AuthorizationContext> context = new Mock<AuthorizationContext>();
+ context.Setup(c => c.IsChildAction).Returns(true);
+
+ // Act
+ attr.OnAuthorization(context.Object);
+
+ // Assert
+ Assert.Null(context.Object.Result);
+ }
+
+ [Fact]
+ public void ThrowsIfNotChildRequest()
+ {
+ // Arrange
+ ChildActionOnlyAttribute attr = new ChildActionOnlyAttribute();
+ Mock<AuthorizationContext> context = new Mock<AuthorizationContext>();
+ context.Setup(c => c.IsChildAction).Returns(false);
+ context.Setup(c => c.ActionDescriptor.ActionName).Returns("some name");
+
+ // Act & assert
+ Assert.Throws<InvalidOperationException>(
+ delegate { attr.OnAuthorization(context.Object); },
+ @"The action 'some name' is accessible only by a child request.");
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Test/ChildActionValueProviderFactoryTest.cs b/test/System.Web.Mvc.Test/Test/ChildActionValueProviderFactoryTest.cs
new file mode 100644
index 00000000..2ea59353
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Test/ChildActionValueProviderFactoryTest.cs
@@ -0,0 +1,93 @@
+using System.Globalization;
+using System.Web.Routing;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Mvc.Test
+{
+ public class ExpiicitRouteDataValueProviderFactoryTest
+ {
+ [Fact]
+ public void GetValueProviderReturnsChildActionValue()
+ {
+ // Arrange
+ ChildActionValueProviderFactory factory = new ChildActionValueProviderFactory();
+
+ ControllerContext controllerContext = new ControllerContext();
+ controllerContext.RouteData = new RouteData();
+
+ string conflictingKey = "conflictingKey";
+
+ controllerContext.RouteData.Values["conflictingKey"] = 43;
+
+ DictionaryValueProvider<object> explictValueDictionary = new DictionaryValueProvider<object>(new RouteValueDictionary { { conflictingKey, 42 } }, CultureInfo.InvariantCulture);
+ controllerContext.RouteData.Values[ChildActionValueProvider.ChildActionValuesKey] = explictValueDictionary;
+
+ // Act
+ IValueProvider valueProvider = factory.GetValueProvider(controllerContext);
+
+ // Assert
+ Assert.Equal(typeof(ChildActionValueProvider), valueProvider.GetType());
+ ValueProviderResult vpResult = valueProvider.GetValue(conflictingKey);
+
+ Assert.NotNull(vpResult);
+ Assert.Equal(42, vpResult.RawValue);
+ Assert.Equal("42", vpResult.AttemptedValue);
+ Assert.Equal(CultureInfo.InvariantCulture, vpResult.Culture);
+ }
+
+ [Fact]
+ public void GetValueProviderReturnsNullIfNoChildActionDictionary()
+ {
+ // Arrange
+ ChildActionValueProviderFactory factory = new ChildActionValueProviderFactory();
+
+ ControllerContext controllerContext = new ControllerContext();
+ controllerContext.RouteData = new RouteData();
+ controllerContext.RouteData.Values["forty-two"] = 42;
+
+ // Act
+ IValueProvider valueProvider = factory.GetValueProvider(controllerContext);
+
+ // Assert
+ Assert.Equal(typeof(ChildActionValueProvider), valueProvider.GetType());
+ ValueProviderResult vpResult = valueProvider.GetValue("forty-two");
+
+ Assert.Null(vpResult);
+ }
+
+ [Fact]
+ public void GetValueProviderReturnsNullIfKeyIsNotInChildActionDictionary()
+ {
+ // Arrange
+ ChildActionValueProviderFactory factory = new ChildActionValueProviderFactory();
+
+ ControllerContext controllerContext = new ControllerContext();
+ controllerContext.RouteData = new RouteData();
+ controllerContext.RouteData.Values["forty-two"] = 42;
+
+ DictionaryValueProvider<object> explictValueDictionary = new DictionaryValueProvider<object>(new RouteValueDictionary { { "forty-three", 42 } }, CultureInfo.CurrentUICulture);
+ controllerContext.RouteData.Values[ChildActionValueProvider.ChildActionValuesKey] = explictValueDictionary;
+
+ // Act
+ IValueProvider valueProvider = factory.GetValueProvider(controllerContext);
+
+ // Assert
+ Assert.Equal(typeof(ChildActionValueProvider), valueProvider.GetType());
+ ValueProviderResult vpResult = valueProvider.GetValue("forty-two");
+
+ Assert.Null(vpResult);
+ }
+
+ [Fact]
+ public void GetValueProvider_ThrowsIfControllerContextIsNull()
+ {
+ // Arrange
+ RouteDataValueProviderFactory factory = new RouteDataValueProviderFactory();
+
+ // Act & assert
+ Assert.ThrowsArgumentNull(
+ delegate { factory.GetValueProvider(null); }, "controllerContext");
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Test/ClientDataTypeModelValidatorProviderTest.cs b/test/System.Web.Mvc.Test/Test/ClientDataTypeModelValidatorProviderTest.cs
new file mode 100644
index 00000000..524bfa77
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Test/ClientDataTypeModelValidatorProviderTest.cs
@@ -0,0 +1,215 @@
+using System.Collections.Generic;
+using System.Linq;
+using Xunit;
+using Xunit.Extensions;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Mvc.Test
+{
+ [CLSCompliant(false)]
+ public class ClientDataTypeModelValidatorProviderTest
+ {
+ private static readonly EmptyModelMetadataProvider _metadataProvider = new EmptyModelMetadataProvider();
+ private static readonly ClientDataTypeModelValidatorProvider _validatorProvider = new ClientDataTypeModelValidatorProvider();
+
+ private bool ReturnsValidator<TValidator>(string propertyName)
+ {
+ ModelMetadata metadata = _metadataProvider.GetMetadataForProperty(null, typeof(SampleModel), propertyName);
+ IEnumerable<ModelValidator> validators = _validatorProvider.GetValidators(metadata, new ControllerContext());
+ return validators.Any(v => v is TValidator);
+ }
+
+ [Theory]
+ [InlineData("Byte"), InlineData("SByte"), InlineData("Int16"), InlineData("UInt16")]
+ [InlineData("Int32"), InlineData("UInt32"), InlineData("Int64"), InlineData("UInt64")]
+ [InlineData("Single"), InlineData("Double"), InlineData("Decimal"), InlineData("NullableInt32")]
+ public void GetValidators_NumericValidatorTypes(string propertyName)
+ {
+ // Act & assert
+ Assert.True(ReturnsValidator<ClientDataTypeModelValidatorProvider.NumericModelValidator>(propertyName));
+ }
+
+ [Theory]
+ [InlineData("String"), InlineData("Object"), InlineData("DateTime"), InlineData("NullableDateTime")]
+ public void GetValidators_NonNumericValidatorTypes(string propertyName)
+ {
+ // Act & assert
+ Assert.False(ReturnsValidator<ClientDataTypeModelValidatorProvider.NumericModelValidator>(propertyName));
+ }
+
+ [Theory]
+ [InlineData("DateTime"), InlineData("NullableDateTime")]
+ public void GetValidators_DateTimeValidatorTypes(string propertyName)
+ {
+ // Act & assert
+ Assert.True(ReturnsValidator<ClientDataTypeModelValidatorProvider.DateModelValidator>(propertyName));
+ }
+
+ [Theory]
+ [InlineData("Int32"), InlineData("NullableInt32"), InlineData("String"), InlineData("Object")]
+ public void GetValidators_NonDateTimeValidatorTypes(string propertyName)
+ {
+ // Act & assert
+ Assert.False(ReturnsValidator<ClientDataTypeModelValidatorProvider.DateModelValidator>(propertyName));
+ }
+
+ [Fact]
+ public void GuardClauses()
+ {
+ // Arrange
+ ModelMetadata metadata = _metadataProvider.GetMetadataForType(null, typeof(SampleModel));
+
+ // Act & assert
+ Assert.ThrowsArgumentNullOrEmpty(
+ () => new ClientDataTypeModelValidatorProvider.ClientModelValidator(metadata, new ControllerContext(), "testValidationType", errorMessage: null),
+ "errorMessage");
+
+ Assert.ThrowsArgumentNullOrEmpty(
+ () => new ClientDataTypeModelValidatorProvider.ClientModelValidator(metadata, new ControllerContext(), "testValidationType", errorMessage: String.Empty),
+ "errorMessage");
+
+ Assert.ThrowsArgumentNullOrEmpty(
+ () => new ClientDataTypeModelValidatorProvider.ClientModelValidator(metadata, new ControllerContext(), validationType: null, errorMessage: "testErrorMessage"),
+ "validationType");
+
+ Assert.ThrowsArgumentNullOrEmpty(
+ () => new ClientDataTypeModelValidatorProvider.ClientModelValidator(metadata, new ControllerContext(), validationType: String.Empty, errorMessage: "testErrorMessage"),
+ "validationType");
+ }
+
+ [Fact]
+ public void GetValidators_ThrowsIfContextIsNull()
+ {
+ // Arrange
+ ModelMetadata metadata = _metadataProvider.GetMetadataForType(null, typeof(SampleModel));
+
+ // Act & assert
+ Assert.ThrowsArgumentNull(
+ () => _validatorProvider.GetValidators(metadata, null),
+ "context");
+ }
+
+ [Fact]
+ public void GetValidators_ThrowsIfMetadataIsNull()
+ {
+ // Act & assert
+ Assert.ThrowsArgumentNull(
+ () => _validatorProvider.GetValidators(null, new ControllerContext()),
+ "metadata");
+ }
+
+ [Fact]
+ public void NumericValidator_GetClientValidationRules()
+ {
+ // Arrange
+ ModelMetadata metadata = _metadataProvider.GetMetadataForProperty(null, typeof(SampleModel), "Int32");
+ var validator = new ClientDataTypeModelValidatorProvider.NumericModelValidator(metadata, new ControllerContext());
+
+ // Act
+ ModelClientValidationRule[] rules = validator.GetClientValidationRules().ToArray();
+
+ // Assert
+ ModelClientValidationRule rule = Assert.Single(rules);
+ Assert.Equal("number", rule.ValidationType);
+ Assert.Empty(rule.ValidationParameters);
+ Assert.Equal("The field Int32 must be a number.", rule.ErrorMessage);
+ }
+
+ [Fact]
+ public void DateValidator_GetClientValidationRules()
+ {
+ // Arrange
+ ModelMetadata metadata = _metadataProvider.GetMetadataForProperty(null, typeof(SampleModel), "DateTime");
+ var validator = new ClientDataTypeModelValidatorProvider.DateModelValidator(metadata, new ControllerContext());
+
+ // Act
+ ModelClientValidationRule[] rules = validator.GetClientValidationRules().ToArray();
+
+ // Assert
+ ModelClientValidationRule rule = Assert.Single(rules);
+ Assert.Equal("date", rule.ValidationType);
+ Assert.Empty(rule.ValidationParameters);
+ Assert.Equal("The field DateTime must be a date.", rule.ErrorMessage);
+ }
+
+ [Fact]
+ public void ClientModelValidator_Validate_DoesNotReadPropertyValue()
+ {
+ // Arrange
+ ObservableModel model = new ObservableModel();
+ ModelMetadata metadata = _metadataProvider.GetMetadataForProperty(() => model.TheProperty, typeof(ObservableModel), "TheProperty");
+ ControllerContext controllerContext = new ControllerContext();
+
+ // Act
+ ModelValidator[] validators = new ClientDataTypeModelValidatorProvider().GetValidators(metadata, controllerContext).ToArray();
+ ModelValidationResult[] results = validators.SelectMany(o => o.Validate(model)).ToArray();
+
+ // Assert
+ Assert.Equal(new Type[] { typeof(ClientDataTypeModelValidatorProvider.NumericModelValidator) }, Array.ConvertAll(validators, o => o.GetType()));
+ Assert.Empty(results);
+ Assert.False(model.PropertyWasRead());
+ }
+
+ [Fact]
+ public void ClientModelValidator_Validate_ReturnsEmptyCollection()
+ {
+ // Arrange
+ ModelMetadata metadata = _metadataProvider.GetMetadataForType(null, typeof(object));
+ var validator = new ClientDataTypeModelValidatorProvider.ClientModelValidator(metadata, new ControllerContext(), "testValidationType", "testErrorMessage");
+
+ // Act
+ IEnumerable<ModelValidationResult> result = validator.Validate(null);
+
+ // Assert
+ Assert.Empty(result);
+ }
+
+ private class SampleModel
+ {
+ // these should have 'numeric' validators associated with them
+ public byte Byte { get; set; }
+ public sbyte SByte { get; set; }
+ public short Int16 { get; set; }
+ public ushort UInt16 { get; set; }
+ public int Int32 { get; set; }
+ public uint UInt32 { get; set; }
+ public long Int64 { get; set; }
+ public ulong UInt64 { get; set; }
+ public float Single { get; set; }
+ public double Double { get; set; }
+ public decimal Decimal { get; set; }
+
+ // this should also have a 'numeric' validator
+ public int? NullableInt32 { get; set; }
+
+ // this should have a 'date' validator associated with it
+ public DateTime DateTime { get; set; }
+
+ // this should also have a 'date' validator associated with it
+ public DateTime? NullableDateTime { get; set; }
+
+ // these shouldn't have any validators
+ public string String { get; set; }
+ public object Object { get; set; }
+ }
+
+ private class ObservableModel
+ {
+ private bool _propertyWasRead;
+
+ public int TheProperty
+ {
+ get
+ {
+ _propertyWasRead = true;
+ return 42;
+ }
+ }
+
+ public bool PropertyWasRead()
+ {
+ return _propertyWasRead;
+ }
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Test/CompareAttributeTest.cs b/test/System.Web.Mvc.Test/Test/CompareAttributeTest.cs
new file mode 100644
index 00000000..1a3a76e8
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Test/CompareAttributeTest.cs
@@ -0,0 +1,198 @@
+using System.Collections.Generic;
+using System.ComponentModel.DataAnnotations;
+using Moq;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Mvc.Test
+{
+ public class CompareAttributeTest
+ {
+ [Fact]
+ public void GuardClauses()
+ {
+ //Act & Assert
+ Assert.ThrowsArgumentNull(
+ delegate { new CompareAttribute(null); }, "otherProperty");
+
+ Assert.ThrowsArgumentNullOrEmpty(
+ delegate { CompareAttribute.FormatPropertyForClientValidation(null); }, "property");
+ }
+
+ [Fact]
+ public void FormatPropertyForClientValidationPrependsStarDot()
+ {
+ string prepended = CompareAttribute.FormatPropertyForClientValidation("test");
+ Assert.Equal(prepended, "*.test");
+ }
+
+ [Fact]
+ public void ValidateDoesNotThrowWhenComparedObjectsAreEqual()
+ {
+ object otherObject = new CompareObject("test");
+ CompareObject currentObject = new CompareObject("test");
+ ValidationContext testContext = new ValidationContext(otherObject, null, null);
+
+ CompareAttribute attr = new CompareAttribute("CompareProperty");
+ attr.Validate(currentObject.CompareProperty, testContext);
+ }
+
+ [Fact]
+ public void ValidateThrowsWhenComparedObjectsAreNotEqual()
+ {
+ CompareObject currentObject = new CompareObject("a");
+ object otherObject = new CompareObject("b");
+
+ ValidationContext testContext = new ValidationContext(otherObject, null, null);
+ testContext.DisplayName = "CurrentProperty";
+
+ CompareAttribute attr = new CompareAttribute("CompareProperty");
+ Assert.Throws<ValidationException>(
+ delegate { attr.Validate(currentObject.CompareProperty, testContext); }, "'CurrentProperty' and 'CompareProperty' do not match.");
+ }
+
+ [Fact]
+ public void ValidateThrowsWithOtherPropertyDisplayName()
+ {
+ CompareObject currentObject = new CompareObject("a");
+ object otherObject = new CompareObject("b");
+
+ ValidationContext testContext = new ValidationContext(otherObject, null, null);
+ testContext.DisplayName = "CurrentProperty";
+
+ CompareAttribute attr = new CompareAttribute("ComparePropertyWithDisplayName");
+ Assert.Throws<ValidationException>(
+ delegate { attr.Validate(currentObject.CompareProperty, testContext); }, "'CurrentProperty' and 'DisplayName' do not match.");
+ }
+
+ [Fact]
+ public void ValidateUsesSetDisplayName()
+ {
+ CompareObject currentObject = new CompareObject("a");
+ object otherObject = new CompareObject("b");
+
+ ValidationContext testContext = new ValidationContext(otherObject, null, null);
+ testContext.DisplayName = "CurrentProperty";
+
+ CompareAttribute attr = new CompareAttribute("ComparePropertyWithDisplayName");
+ attr.OtherPropertyDisplayName = "SetDisplayName";
+
+ Assert.Throws<ValidationException>(
+ delegate { attr.Validate(currentObject.CompareProperty, testContext); }, "'CurrentProperty' and 'SetDisplayName' do not match.");
+ }
+
+ [Fact]
+ public void ValidateThrowsWhenPropertyNameIsUnknown()
+ {
+ CompareObject currentObject = new CompareObject("a");
+ object otherObject = new CompareObject("b");
+
+ ValidationContext testContext = new ValidationContext(otherObject, null, null);
+ testContext.DisplayName = "CurrentProperty";
+
+ CompareAttribute attr = new CompareAttribute("UnknownPropertyName");
+ Assert.Throws<ValidationException>(
+ () => attr.Validate(currentObject.CompareProperty, testContext),
+ "Could not find a property named UnknownPropertyName."
+ );
+ }
+
+ [Fact]
+ public void GetClientValidationRulesReturnsModelClientValidationEqualToRule()
+ {
+ Mock<ModelMetadataProvider> provider = new Mock<ModelMetadataProvider>();
+ Mock<ModelMetadata> metadata = new Mock<ModelMetadata>(provider.Object, null, null, typeof(string), null);
+ metadata.Setup(m => m.DisplayName).Returns("CurrentProperty");
+
+ CompareAttribute attr = new CompareAttribute("CompareProperty");
+ List<ModelClientValidationRule> ruleList = new List<ModelClientValidationRule>(attr.GetClientValidationRules(metadata.Object, null));
+
+ Assert.Equal(ruleList.Count, 1);
+
+ ModelClientValidationEqualToRule actualRule = ruleList[0] as ModelClientValidationEqualToRule;
+
+ Assert.Equal("'CurrentProperty' and 'CompareProperty' do not match.", actualRule.ErrorMessage);
+ Assert.Equal("equalto", actualRule.ValidationType);
+ Assert.Equal("*.CompareProperty", actualRule.ValidationParameters["other"]);
+ }
+
+ [Fact]
+ public void ModelClientValidationEqualToRuleErrorMessageUsesOtherPropertyDisplayName()
+ {
+ Mock<ModelMetadataProvider> provider = new Mock<ModelMetadataProvider>();
+ ModelMetadata metadata = new ModelMetadata(provider.Object, typeof(CompareObject), null, typeof(string), null);
+ metadata.DisplayName = "CurrentProperty";
+
+ CompareAttribute attr = new CompareAttribute("ComparePropertyWithDisplayName");
+ List<ModelClientValidationRule> ruleList = new List<ModelClientValidationRule>(attr.GetClientValidationRules(metadata, null));
+
+ Assert.Equal(ruleList.Count, 1);
+
+ ModelClientValidationEqualToRule actualRule = ruleList[0] as ModelClientValidationEqualToRule;
+
+ Assert.Equal("'CurrentProperty' and 'DisplayName' do not match.", actualRule.ErrorMessage);
+ Assert.Equal("equalto", actualRule.ValidationType);
+ Assert.Equal("*.ComparePropertyWithDisplayName", actualRule.ValidationParameters["other"]);
+ }
+
+ [Fact]
+ public void ModelClientValidationEqualToRuleUsesSetDisplayName()
+ {
+ Mock<ModelMetadataProvider> provider = new Mock<ModelMetadataProvider>();
+ ModelMetadata metadata = new ModelMetadata(provider.Object, typeof(CompareObject), null, typeof(string), null);
+ metadata.DisplayName = "CurrentProperty";
+
+ CompareAttribute attr = new CompareAttribute("ComparePropertyWithDisplayName");
+ attr.OtherPropertyDisplayName = "SetDisplayName";
+
+ List<ModelClientValidationRule> ruleList = new List<ModelClientValidationRule>(attr.GetClientValidationRules(metadata, null));
+ Assert.Equal(ruleList.Count, 1);
+ ModelClientValidationEqualToRule actualRule = ruleList[0] as ModelClientValidationEqualToRule;
+
+ Assert.Equal("'CurrentProperty' and 'SetDisplayName' do not match.", actualRule.ErrorMessage);
+ }
+
+ [Fact]
+ public void CompareAttributeCanBeDerivedFromAndOverrideIsValid()
+ {
+ object otherObject = new CompareObject("a");
+ CompareObject currentObject = new CompareObject("b");
+ ValidationContext testContext = new ValidationContext(otherObject, null, null);
+
+ DerivedCompareAttribute attr = new DerivedCompareAttribute("CompareProperty");
+ attr.Validate(currentObject.CompareProperty, testContext);
+ }
+
+ private class DerivedCompareAttribute : CompareAttribute
+ {
+ public DerivedCompareAttribute(string otherProperty)
+ : base(otherProperty)
+ {
+ }
+
+ public override bool IsValid(object value)
+ {
+ return false;
+ }
+
+ protected override ValidationResult IsValid(object value, ValidationContext context)
+ {
+ return null;
+ }
+ }
+
+ private class CompareObject
+ {
+ public string CompareProperty { get; set; }
+
+ [Display(Name = "DisplayName")]
+ public string ComparePropertyWithDisplayName { get; set; }
+
+ public CompareObject(string otherValue)
+ {
+ CompareProperty = otherValue;
+ ComparePropertyWithDisplayName = otherValue;
+ }
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Test/ContentResultTest.cs b/test/System.Web.Mvc.Test/Test/ContentResultTest.cs
new file mode 100644
index 00000000..888c16aa
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Test/ContentResultTest.cs
@@ -0,0 +1,158 @@
+using System.Text;
+using Moq;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Mvc.Test
+{
+ public class ContentResultTest
+ {
+ [Fact]
+ public void AllPropertiesDefaultToNull()
+ {
+ // Act
+ ContentResult result = new ContentResult();
+
+ // Assert
+ Assert.Null(result.Content);
+ Assert.Null(result.ContentEncoding);
+ Assert.Null(result.ContentType);
+ }
+
+ [Fact]
+ public void EmptyContentTypeIsNotOutput()
+ {
+ // Arrange
+ string content = "Some content.";
+ Encoding contentEncoding = Encoding.UTF8;
+
+ // Arrange expectations
+ Mock<ControllerContext> mockControllerContext = new Mock<ControllerContext>(MockBehavior.Strict);
+ mockControllerContext.SetupSet(c => c.HttpContext.Response.ContentEncoding = contentEncoding).Verifiable();
+ mockControllerContext.Setup(c => c.HttpContext.Response.Write(content)).Verifiable();
+
+ ContentResult result = new ContentResult
+ {
+ Content = content,
+ ContentType = String.Empty,
+ ContentEncoding = contentEncoding
+ };
+
+ // Act
+ result.ExecuteResult(mockControllerContext.Object);
+
+ // Assert
+ mockControllerContext.Verify();
+ }
+
+ [Fact]
+ public void ExecuteResult()
+ {
+ // Arrange
+ string content = "Some content.";
+ string contentType = "Some content type.";
+ Encoding contentEncoding = Encoding.UTF8;
+
+ // Arrange expectations
+ Mock<ControllerContext> mockControllerContext = new Mock<ControllerContext>(MockBehavior.Strict);
+ mockControllerContext.SetupSet(c => c.HttpContext.Response.ContentType = contentType).Verifiable();
+ mockControllerContext.SetupSet(c => c.HttpContext.Response.ContentEncoding = contentEncoding).Verifiable();
+ mockControllerContext.Setup(c => c.HttpContext.Response.Write(content)).Verifiable();
+
+ ContentResult result = new ContentResult
+ {
+ Content = content,
+ ContentType = contentType,
+ ContentEncoding = contentEncoding
+ };
+
+ // Act
+ result.ExecuteResult(mockControllerContext.Object);
+
+ // Assert
+ mockControllerContext.Verify();
+ }
+
+ [Fact]
+ public void ExecuteResultWithNullContextThrows()
+ {
+ Assert.ThrowsArgumentNull(
+ delegate { new ContentResult().ExecuteResult(null /* context */); }, "context");
+ }
+
+ [Fact]
+ public void NullContentIsNotOutput()
+ {
+ // Arrange
+ string contentType = "Some content type.";
+ Encoding contentEncoding = Encoding.UTF8;
+
+ // Arrange expectations
+ Mock<ControllerContext> mockControllerContext = new Mock<ControllerContext>();
+ mockControllerContext.SetupSet(c => c.HttpContext.Response.ContentType = contentType).Verifiable();
+ mockControllerContext.SetupSet(c => c.HttpContext.Response.ContentEncoding = contentEncoding).Verifiable();
+
+ ContentResult result = new ContentResult
+ {
+ ContentType = contentType,
+ ContentEncoding = contentEncoding
+ };
+
+ // Act
+ result.ExecuteResult(mockControllerContext.Object);
+
+ // Assert
+ mockControllerContext.Verify();
+ }
+
+ [Fact]
+ public void NullContentEncodingIsNotOutput()
+ {
+ // Arrange
+ string content = "Some content.";
+ string contentType = "Some content type.";
+
+ // Arrange expectations
+ Mock<ControllerContext> mockControllerContext = new Mock<ControllerContext>(MockBehavior.Strict);
+ mockControllerContext.SetupSet(c => c.HttpContext.Response.ContentType = contentType).Verifiable();
+ mockControllerContext.Setup(c => c.HttpContext.Response.Write(content)).Verifiable();
+
+ ContentResult result = new ContentResult
+ {
+ Content = content,
+ ContentType = contentType,
+ };
+
+ // Act
+ result.ExecuteResult(mockControllerContext.Object);
+
+ // Assert
+ mockControllerContext.Verify();
+ }
+
+ [Fact]
+ public void NullContentTypeIsNotOutput()
+ {
+ // Arrange
+ string content = "Some content.";
+ Encoding contentEncoding = Encoding.UTF8;
+
+ // Arrange expectations
+ Mock<ControllerContext> mockControllerContext = new Mock<ControllerContext>(MockBehavior.Strict);
+ mockControllerContext.SetupSet(c => c.HttpContext.Response.ContentEncoding = contentEncoding).Verifiable();
+ mockControllerContext.Setup(c => c.HttpContext.Response.Write(content)).Verifiable();
+
+ ContentResult result = new ContentResult
+ {
+ Content = content,
+ ContentEncoding = contentEncoding
+ };
+
+ // Act
+ result.ExecuteResult(mockControllerContext.Object);
+
+ // Assert
+ mockControllerContext.Verify();
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Test/ControllerActionInvokerTest.cs b/test/System.Web.Mvc.Test/Test/ControllerActionInvokerTest.cs
new file mode 100644
index 00000000..5327961a
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Test/ControllerActionInvokerTest.cs
@@ -0,0 +1,2427 @@
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Globalization;
+using System.Linq;
+using System.Reflection;
+using System.Threading;
+using Microsoft.Web.UnitTestUtil;
+using Moq;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Mvc.Test
+{
+ [CLSCompliant(false)]
+ public class ControllerActionInvokerTest
+ {
+ [Fact]
+ public void CreateActionResultWithActionResultParameterReturnsParameterUnchanged()
+ {
+ // Arrange
+ ControllerActionInvokerHelper invoker = new ControllerActionInvokerHelper();
+ ActionResult originalResult = new JsonResult();
+
+ // Act
+ ActionResult returnedActionResult = invoker.PublicCreateActionResult(null, null, originalResult);
+
+ // Assert
+ Assert.Same(originalResult, returnedActionResult);
+ }
+
+ [Fact]
+ public void CreateActionResultWithNullParameterReturnsEmptyResult()
+ {
+ // Arrange
+ ControllerActionInvokerHelper invoker = new ControllerActionInvokerHelper();
+
+ // Act
+ ActionResult returnedActionResult = invoker.PublicCreateActionResult(null, null, null);
+
+ // Assert
+ Assert.IsType<EmptyResult>(returnedActionResult);
+ }
+
+ [Fact]
+ public void CreateActionResultWithObjectParameterReturnsContentResult()
+ {
+ // Arrange
+ ControllerActionInvokerHelper invoker = new ControllerActionInvokerHelper();
+ object originalReturnValue = new CultureReflector();
+
+ // Act
+ ActionResult returnedActionResult = invoker.PublicCreateActionResult(null, null, originalReturnValue);
+
+ // Assert
+ ContentResult contentResult = Assert.IsType<ContentResult>(returnedActionResult);
+ Assert.Equal("ivl", contentResult.Content);
+ }
+
+ [Fact]
+ public void FindAction()
+ {
+ // Arrange
+ EmptyController controller = new EmptyController();
+ ControllerContext controllerContext = GetControllerContext(controller);
+ ControllerActionInvokerHelper helper = new ControllerActionInvokerHelper();
+
+ ActionDescriptor expectedAd = new Mock<ActionDescriptor>().Object;
+ Mock<ControllerDescriptor> mockCd = new Mock<ControllerDescriptor>();
+ mockCd.Setup(cd => cd.FindAction(controllerContext, "someAction")).Returns(expectedAd);
+
+ // Act
+ ActionDescriptor returnedAd = helper.PublicFindAction(controllerContext, mockCd.Object, "someAction");
+
+ // Assert
+ Assert.Equal(expectedAd, returnedAd);
+ }
+
+ [Fact]
+ public void FindActionDoesNotMatchConstructor()
+ {
+ // FindActionMethod() shouldn't match special-named methods like type constructors.
+
+ // Arrange
+ Controller controller = new FindMethodController();
+ ControllerContext context = GetControllerContext(controller);
+ ControllerDescriptor cd = new ReflectedControllerDescriptor(typeof(FindMethodController));
+
+ ControllerActionInvokerHelper helper = new ControllerActionInvokerHelper();
+
+ // Act
+ ActionDescriptor ad = helper.PublicFindAction(context, cd, ".ctor");
+ ActionDescriptor ad2 = helper.PublicFindAction(context, cd, "FindMethodController");
+
+ // Assert
+ Assert.Null(ad);
+ Assert.Null(ad2);
+ }
+
+ [Fact]
+ public void FindActionDoesNotMatchEvent()
+ {
+ // FindActionMethod() should skip methods that aren't publicly visible.
+
+ // Arrange
+ Controller controller = new FindMethodController();
+ ControllerContext context = GetControllerContext(controller);
+ ControllerDescriptor cd = new ReflectedControllerDescriptor(typeof(FindMethodController));
+
+ ControllerActionInvokerHelper helper = new ControllerActionInvokerHelper();
+
+ // Act
+ ActionDescriptor ad = helper.PublicFindAction(context, cd, "add_Event");
+
+ // Assert
+ Assert.Null(ad);
+ }
+
+ [Fact]
+ public void FindActionDoesNotMatchInternalMethod()
+ {
+ // FindActionMethod() should skip methods that aren't publicly visible.
+
+ // Arrange
+ Controller controller = new FindMethodController();
+ ControllerContext context = GetControllerContext(controller);
+ ControllerDescriptor cd = new ReflectedControllerDescriptor(typeof(FindMethodController));
+
+ ControllerActionInvokerHelper helper = new ControllerActionInvokerHelper();
+
+ // Act
+ ActionDescriptor ad = helper.PublicFindAction(context, cd, "InternalMethod");
+
+ // Assert
+ Assert.Null(ad);
+ }
+
+ [Fact]
+ public void FindActionDoesNotMatchMethodsDefinedOnControllerType()
+ {
+ // FindActionMethod() shouldn't match methods originally defined on the Controller type, e.g. Dispose().
+
+ // Arrange
+ Controller controller = new BlankController();
+ ControllerDescriptor cd = new ReflectedControllerDescriptor(typeof(BlankController));
+ ControllerContext context = GetControllerContext(controller);
+ ControllerActionInvokerHelper helper = new ControllerActionInvokerHelper();
+ var methods = typeof(Controller).GetMethods(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
+
+ // Act & Assert
+ foreach (var method in methods)
+ {
+ bool wasFound = true;
+ try
+ {
+ ActionDescriptor ad = helper.PublicFindAction(context, cd, method.Name);
+ wasFound = (ad != null);
+ }
+ finally
+ {
+ Assert.False(wasFound, "FindAction() should return false for methods defined on the Controller class: " + method);
+ }
+ }
+ }
+
+ [Fact]
+ public void FindActionDoesNotMatchMethodsDefinedOnObjectType()
+ {
+ // FindActionMethod() shouldn't match methods originally defined on the Object type, e.g. ToString().
+
+ // Arrange
+ Controller controller = new FindMethodController();
+ ControllerContext context = GetControllerContext(controller);
+ ControllerDescriptor cd = new ReflectedControllerDescriptor(typeof(FindMethodController));
+
+ ControllerActionInvokerHelper helper = new ControllerActionInvokerHelper();
+
+ // Act
+ ActionDescriptor ad = helper.PublicFindAction(context, cd, "ToString");
+
+ // Assert
+ Assert.Null(ad);
+ }
+
+ [Fact]
+ public void FindActionDoesNotMatchNonActionMethod()
+ {
+ // FindActionMethod() should respect the [NonAction] attribute.
+
+ // Arrange
+ Controller controller = new FindMethodController();
+ ControllerContext context = GetControllerContext(controller);
+ ControllerDescriptor cd = new ReflectedControllerDescriptor(typeof(FindMethodController));
+
+ ControllerActionInvokerHelper helper = new ControllerActionInvokerHelper();
+
+ // Act
+ ActionDescriptor ad = helper.PublicFindAction(context, cd, "NonActionMethod");
+
+ // Assert
+ Assert.Null(ad);
+ }
+
+ [Fact]
+ public void FindActionDoesNotMatchOverriddenNonActionMethod()
+ {
+ // FindActionMethod() should trace the method's inheritance chain looking for the [NonAction] attribute.
+
+ // Arrange
+ Controller controller = new DerivedFindMethodController();
+ ControllerContext context = GetControllerContext(controller);
+ ControllerDescriptor cd = new ReflectedControllerDescriptor(typeof(DerivedFindMethodController));
+
+ ControllerActionInvokerHelper helper = new ControllerActionInvokerHelper();
+
+ // Act
+ ActionDescriptor ad = helper.PublicFindAction(context, cd, "InternalMethod");
+
+ // Assert
+ Assert.Null(ad);
+ }
+
+ [Fact]
+ public void FindActionDoesNotMatchPrivateMethod()
+ {
+ // FindActionMethod() should skip methods that aren't publicly visible.
+
+ // Arrange
+ Controller controller = new FindMethodController();
+ ControllerContext context = GetControllerContext(controller);
+ ControllerDescriptor cd = new ReflectedControllerDescriptor(typeof(FindMethodController));
+
+ ControllerActionInvokerHelper helper = new ControllerActionInvokerHelper();
+
+ // Act
+ ActionDescriptor ad = helper.PublicFindAction(context, cd, "PrivateMethod");
+
+ // Assert
+ Assert.Null(ad);
+ }
+
+ [Fact]
+ public void FindActionDoesNotMatchProperty()
+ {
+ // FindActionMethod() shouldn't match special-named methods like property getters.
+
+ // Arrange
+ Controller controller = new FindMethodController();
+ ControllerContext context = GetControllerContext(controller);
+ ControllerDescriptor cd = new ReflectedControllerDescriptor(typeof(FindMethodController));
+
+ ControllerActionInvokerHelper helper = new ControllerActionInvokerHelper();
+
+ // Act
+ ActionDescriptor ad = helper.PublicFindAction(context, cd, "get_Property");
+
+ // Assert
+ Assert.Null(ad);
+ }
+
+ [Fact]
+ public void FindActionDoesNotMatchProtectedMethod()
+ {
+ // FindActionMethod() should skip methods that aren't publicly visible.
+
+ // Arrange
+ Controller controller = new FindMethodController();
+ ControllerContext context = GetControllerContext(controller);
+ ControllerDescriptor cd = new ReflectedControllerDescriptor(typeof(FindMethodController));
+
+ ControllerActionInvokerHelper helper = new ControllerActionInvokerHelper();
+
+ // Act
+ ActionDescriptor ad = helper.PublicFindAction(context, cd, "ProtectedMethod");
+
+ // Assert
+ Assert.Null(ad);
+ }
+
+ [Fact]
+ public void FindActionIsCaseInsensitive()
+ {
+ // Arrange
+ Controller controller = new FindMethodController();
+ ControllerContext context = GetControllerContext(controller);
+ ControllerDescriptor cd = new ReflectedControllerDescriptor(typeof(FindMethodController));
+ MethodInfo expectedMethodInfo = typeof(FindMethodController).GetMethod("ValidActionMethod");
+
+ ControllerActionInvokerHelper helper = new ControllerActionInvokerHelper();
+
+ // Act
+ ActionDescriptor ad1 = helper.PublicFindAction(context, cd, "validactionmethod");
+ ActionDescriptor ad2 = helper.PublicFindAction(context, cd, "VALIDACTIONMETHOD");
+
+ // Assert
+ ReflectedActionDescriptor rad1 = Assert.IsType<ReflectedActionDescriptor>(ad1);
+ Assert.Same(expectedMethodInfo, rad1.MethodInfo);
+ ReflectedActionDescriptor rad2 = Assert.IsType<ReflectedActionDescriptor>(ad2);
+ Assert.Same(expectedMethodInfo, rad2.MethodInfo);
+ }
+
+ [Fact]
+ public void FindActionMatchesActionMethodWithClosedGenerics()
+ {
+ // FindActionMethod() should work with generic methods as long as there are no open types.
+
+ // Arrange
+ Controller controller = new GenericFindMethodController<int>();
+ ControllerContext context = GetControllerContext(controller);
+ ControllerDescriptor cd = new ReflectedControllerDescriptor(typeof(GenericFindMethodController<int>));
+ MethodInfo expectedMethodInfo = typeof(GenericFindMethodController<int>).GetMethod("ClosedGenericMethod");
+
+ ControllerActionInvokerHelper helper = new ControllerActionInvokerHelper();
+
+ // Act
+ ActionDescriptor ad = helper.PublicFindAction(context, cd, "ClosedGenericMethod");
+
+ // Assert
+ ReflectedActionDescriptor rad = Assert.IsType<ReflectedActionDescriptor>(ad);
+ Assert.Same(expectedMethodInfo, rad.MethodInfo);
+ }
+
+ [Fact]
+ public void FindActionMatchesNewActionMethodsHidingNonActionMethods()
+ {
+ // FindActionMethod() should stop looking for [NonAction] in the method's inheritance chain when it sees
+ // that a method in a derived class hides the a method in the base class.
+
+ // Arrange
+ Controller controller = new DerivedFindMethodController();
+ ControllerContext context = GetControllerContext(controller);
+ ControllerDescriptor cd = new ReflectedControllerDescriptor(typeof(DerivedFindMethodController));
+ MethodInfo expectedMethodInfo = typeof(DerivedFindMethodController).GetMethod("DerivedIsActionMethod");
+
+ ControllerActionInvokerHelper helper = new ControllerActionInvokerHelper();
+
+ // Act
+ ActionDescriptor ad = helper.PublicFindAction(context, cd, "DerivedIsActionMethod");
+
+ // Assert
+ ReflectedActionDescriptor rad = Assert.IsType<ReflectedActionDescriptor>(ad);
+ Assert.Same(expectedMethodInfo, rad.MethodInfo);
+ }
+
+ [Fact]
+ public void GetControllerDescriptor()
+ {
+ // Arrange
+ EmptyController controller = new EmptyController();
+ ControllerContext controllerContext = GetControllerContext(controller);
+ ControllerActionInvokerHelper helper = new ControllerActionInvokerHelper();
+
+ // Act
+ ControllerDescriptor cd = helper.PublicGetControllerDescriptor(controllerContext);
+
+ // Assert
+ Assert.IsType<ReflectedControllerDescriptor>(cd);
+ Assert.Equal(typeof(EmptyController), cd.ControllerType);
+ }
+
+ [Fact]
+ public void GetFiltersSplitsFilterObjectsIntoFilterInfo()
+ {
+ // Arrange
+ IActionFilter actionFilter = new Mock<IActionFilter>().Object;
+ IResultFilter resultFilter = new Mock<IResultFilter>().Object;
+ IAuthorizationFilter authFilter = new Mock<IAuthorizationFilter>().Object;
+ IExceptionFilter exFilter = new Mock<IExceptionFilter>().Object;
+ object noneOfTheAbove = new object();
+ ControllerActionInvokerHelper invoker = new ControllerActionInvokerHelper(actionFilter, authFilter, exFilter, resultFilter, noneOfTheAbove);
+ ControllerContext context = new ControllerContext();
+ ActionDescriptor descriptor = new Mock<ActionDescriptor>().Object;
+
+ // Act
+ FilterInfo result = invoker.PublicGetFilters(context, descriptor);
+
+ // Assert
+ Assert.Same(actionFilter, result.ActionFilters.Single());
+ Assert.Same(authFilter, result.AuthorizationFilters.Single());
+ Assert.Same(exFilter, result.ExceptionFilters.Single());
+ Assert.Same(resultFilter, result.ResultFilters.Single());
+ }
+
+ [Fact]
+ public void GetParameterValueAllowsAllSubpropertiesIfBindAttributeNotSpecified()
+ {
+ // Arrange
+ CustomConverterController controller = new CustomConverterController();
+ ControllerContext controllerContext = GetControllerContext(controller);
+ ControllerActionInvokerHelper helper = new ControllerActionInvokerHelper();
+
+ ParameterInfo paramWithoutBindAttribute = typeof(CustomConverterController).GetMethod("ParameterWithoutBindAttribute").GetParameters()[0];
+ ReflectedParameterDescriptor pd = new ReflectedParameterDescriptor(paramWithoutBindAttribute, new Mock<ActionDescriptor>().Object);
+
+ // Act
+ object valueWithoutBindAttribute = helper.PublicGetParameterValue(controllerContext, pd);
+
+ // Assert
+ Assert.Equal("foo=True&bar=True", valueWithoutBindAttribute);
+ }
+
+ [Fact]
+ public void GetParameterValueResolvesConvertersInCorrectOrderOfPrecedence()
+ {
+ // Order of precedence:
+ // 1. Attributes on the parameter itself
+ // 2. Query the global converter provider
+
+ // Arrange
+ CustomConverterController controller = new CustomConverterController();
+ Dictionary<string, object> values = new Dictionary<string, object> { { "foo", "fooValue" } };
+ ControllerContext controllerContext = GetControllerContext(controller, values);
+ controller.ControllerContext = controllerContext;
+ ControllerActionInvokerHelper helper = new ControllerActionInvokerHelper();
+
+ ParameterInfo paramWithOneConverter = typeof(CustomConverterController).GetMethod("ParameterHasOneConverter").GetParameters()[0];
+ ReflectedParameterDescriptor pdOneConverter = new ReflectedParameterDescriptor(paramWithOneConverter, new Mock<ActionDescriptor>().Object);
+ ParameterInfo paramWithNoConverters = typeof(CustomConverterController).GetMethod("ParameterHasNoConverters").GetParameters()[0];
+ ReflectedParameterDescriptor pdNoConverters = new ReflectedParameterDescriptor(paramWithNoConverters, new Mock<ActionDescriptor>().Object);
+
+ // Act
+ object valueWithOneConverter = helper.PublicGetParameterValue(controllerContext, pdOneConverter);
+ object valueWithNoConverters = helper.PublicGetParameterValue(controllerContext, pdNoConverters);
+
+ // Assert
+ Assert.Equal("foo_String", valueWithOneConverter);
+ Assert.Equal("fooValue", valueWithNoConverters);
+ }
+
+ [Fact]
+ public void GetParameterValueRespectsBindAttribute()
+ {
+ // Arrange
+ CustomConverterController controller = new CustomConverterController();
+ ControllerContext controllerContext = GetControllerContext(controller);
+ ControllerActionInvokerHelper helper = new ControllerActionInvokerHelper();
+
+ ParameterInfo paramWithBindAttribute = typeof(CustomConverterController).GetMethod("ParameterHasBindAttribute").GetParameters()[0];
+ ReflectedParameterDescriptor pd = new ReflectedParameterDescriptor(paramWithBindAttribute, new Mock<ActionDescriptor>().Object);
+
+ // Act
+ object valueWithBindAttribute = helper.PublicGetParameterValue(controllerContext, pd);
+
+ // Assert
+ Assert.Equal("foo=True&bar=False", valueWithBindAttribute);
+ }
+
+ [Fact]
+ public void GetParameterValueRespectsBindAttributePrefix()
+ {
+ // Arrange
+ CustomConverterController controller = new CustomConverterController();
+ Dictionary<string, object> values = new Dictionary<string, object> { { "foo", "fooValue" }, { "bar", "barValue" } };
+ ControllerContext controllerContext = GetControllerContext(controller, values);
+ controller.ControllerContext = controllerContext;
+
+ ControllerActionInvokerHelper helper = new ControllerActionInvokerHelper();
+
+ ParameterInfo paramWithFieldPrefix = typeof(CustomConverterController).GetMethod("ParameterHasFieldPrefix").GetParameters()[0];
+ ReflectedParameterDescriptor pd = new ReflectedParameterDescriptor(paramWithFieldPrefix, new Mock<ActionDescriptor>().Object);
+
+ // Act
+ object parameterValue = helper.PublicGetParameterValue(controllerContext, pd);
+
+ // Assert
+ Assert.Equal("barValue", parameterValue);
+ }
+
+ [Fact]
+ public void GetParameterValueRespectsBindAttributePrefixOnComplexType()
+ {
+ // Arrange
+ CustomConverterController controller = new CustomConverterController();
+ Dictionary<string, object> values = new Dictionary<string, object> { { "intprop", "123" }, { "stringprop", "hello" } };
+ ControllerContext controllerContext = GetControllerContext(controller, values);
+ controller.ControllerContext = controllerContext;
+
+ ControllerActionInvokerHelper helper = new ControllerActionInvokerHelper();
+
+ ParameterInfo paramWithFieldPrefix = typeof(CustomConverterController).GetMethod("ParameterHasPrefixAndComplexType").GetParameters()[0];
+ ReflectedParameterDescriptor pd = new ReflectedParameterDescriptor(paramWithFieldPrefix, new Mock<ActionDescriptor>().Object);
+
+ // Act
+ MySimpleModel parameterValue = helper.PublicGetParameterValue(controllerContext, pd) as MySimpleModel;
+
+ // Assert
+ Assert.Null(parameterValue);
+ }
+
+ [Fact]
+ public void GetParameterValueRespectsBindAttributeNullPrefix()
+ {
+ // Arrange
+ CustomConverterController controller = new CustomConverterController();
+ Dictionary<string, object> values = new Dictionary<string, object> { { "foo", "fooValue" }, { "bar", "barValue" } };
+ ControllerContext controllerContext = GetControllerContext(controller, values);
+ controller.ControllerContext = controllerContext;
+
+ ControllerActionInvokerHelper helper = new ControllerActionInvokerHelper();
+
+ ParameterInfo paramWithFieldPrefix = typeof(CustomConverterController).GetMethod("ParameterHasNullFieldPrefix").GetParameters()[0];
+ ReflectedParameterDescriptor pd = new ReflectedParameterDescriptor(paramWithFieldPrefix, new Mock<ActionDescriptor>().Object);
+
+ // Act
+ object parameterValue = helper.PublicGetParameterValue(controllerContext, pd);
+
+ // Assert
+ Assert.Equal("fooValue", parameterValue);
+ }
+
+ [Fact]
+ public void GetParameterValueRespectsBindAttributeNullPrefixOnComplexType()
+ {
+ // Arrange
+ CustomConverterController controller = new CustomConverterController();
+ Dictionary<string, object> values = new Dictionary<string, object> { { "intprop", "123" }, { "stringprop", "hello" } };
+ ControllerContext controllerContext = GetControllerContext(controller, values);
+ controller.ControllerContext = controllerContext;
+
+ ControllerActionInvokerHelper helper = new ControllerActionInvokerHelper();
+
+ ParameterInfo paramWithFieldPrefix = typeof(CustomConverterController).GetMethod("ParameterHasNoPrefixAndComplexType").GetParameters()[0];
+ ReflectedParameterDescriptor pd = new ReflectedParameterDescriptor(paramWithFieldPrefix, new Mock<ActionDescriptor>().Object);
+
+ // Act
+ MySimpleModel parameterValue = helper.PublicGetParameterValue(controllerContext, pd) as MySimpleModel;
+
+ // Assert
+ Assert.NotNull(parameterValue);
+ Assert.Equal(123, parameterValue.IntProp);
+ Assert.Equal("hello", parameterValue.StringProp);
+ }
+
+ [Fact]
+ public void GetParameterValueRespectsBindAttributeEmptyPrefix()
+ {
+ // Arrange
+ CustomConverterController controller = new CustomConverterController();
+ Dictionary<string, object> values = new Dictionary<string, object> { { "foo", "fooValue" }, { "bar", "barValue" }, { "intprop", "123" }, { "stringprop", "hello" } };
+ ControllerContext controllerContext = GetControllerContext(controller, values);
+ controller.ControllerContext = controllerContext;
+
+ ControllerActionInvokerHelper helper = new ControllerActionInvokerHelper();
+
+ ParameterInfo paramWithFieldPrefix = typeof(CustomConverterController).GetMethod("ParameterHasEmptyFieldPrefix").GetParameters()[0];
+ ReflectedParameterDescriptor pd = new ReflectedParameterDescriptor(paramWithFieldPrefix, new Mock<ActionDescriptor>().Object);
+
+ // Act
+ MySimpleModel parameterValue = helper.PublicGetParameterValue(controllerContext, pd) as MySimpleModel;
+
+ // Assert
+ Assert.NotNull(parameterValue);
+ Assert.Equal(123, parameterValue.IntProp);
+ Assert.Equal("hello", parameterValue.StringProp);
+ }
+
+ [Fact]
+ public void GetParameterValueRespectsDefaultValueAttribute()
+ {
+ // Arrange
+ CustomConverterController controller = new CustomConverterController();
+ ControllerContext controllerContext = GetControllerContext(controller);
+ ControllerActionInvokerHelper helper = new ControllerActionInvokerHelper();
+ controller.ValueProvider = new SimpleValueProvider();
+
+ ParameterInfo paramWithDefaultValueAttribute = typeof(CustomConverterController).GetMethod("ParameterHasDefaultValueAttribute").GetParameters()[0];
+ ReflectedParameterDescriptor pd = new ReflectedParameterDescriptor(paramWithDefaultValueAttribute, new Mock<ActionDescriptor>().Object);
+
+ // Act
+ object valueWithDefaultValueAttribute = helper.PublicGetParameterValue(controllerContext, pd);
+
+ // Assert
+ Assert.Equal(42, valueWithDefaultValueAttribute);
+ }
+
+ [Fact]
+ public void GetParameterValueReturnsNullIfCannotConvertNonRequiredParameter()
+ {
+ // Arrange
+ Dictionary<string, object> dict = new Dictionary<string, object>()
+ {
+ { "id", DateTime.Now } // cannot convert DateTime to Nullable<int>
+ };
+ var controller = new ParameterTestingController();
+ ControllerContext context = GetControllerContext(controller, dict);
+ controller.ControllerContext = context;
+
+ ControllerActionInvokerHelper helper = new ControllerActionInvokerHelper();
+ MethodInfo mi = typeof(ParameterTestingController).GetMethod("TakesNullableInt");
+ ParameterInfo[] pis = mi.GetParameters();
+ ReflectedParameterDescriptor pd = new ReflectedParameterDescriptor(pis[0], new Mock<ActionDescriptor>().Object);
+
+ // Act
+ object oValue = helper.PublicGetParameterValue(context, pd);
+
+ // Assert
+ Assert.Null(oValue);
+ }
+
+ [Fact]
+ public void GetParameterValueReturnsNullIfNullableTypeValueNotFound()
+ {
+ // Arrange
+ var controller = new ParameterTestingController();
+ ControllerContext context = GetControllerContext(controller);
+ controller.ControllerContext = context;
+ controller.ValueProvider = new SimpleValueProvider();
+
+ ControllerActionInvokerHelper helper = new ControllerActionInvokerHelper();
+ MethodInfo mi = typeof(ParameterTestingController).GetMethod("TakesNullableInt");
+ ParameterInfo[] pis = mi.GetParameters();
+ ReflectedParameterDescriptor pd = new ReflectedParameterDescriptor(pis[0], new Mock<ActionDescriptor>().Object);
+
+ // Act
+ object oValue = helper.PublicGetParameterValue(context, pd);
+
+ // Assert
+ Assert.Null(oValue);
+ }
+
+ [Fact]
+ public void GetParameterValueReturnsNullIfReferenceTypeValueNotFound()
+ {
+ // Arrange
+ var controller = new ParameterTestingController();
+ ControllerContext context = GetControllerContext(controller);
+ controller.ControllerContext = context;
+ controller.ValueProvider = new SimpleValueProvider();
+
+ ControllerActionInvokerHelper helper = new ControllerActionInvokerHelper();
+ MethodInfo mi = typeof(ParameterTestingController).GetMethod("Foo");
+ ParameterInfo[] pis = mi.GetParameters();
+ ReflectedParameterDescriptor pd = new ReflectedParameterDescriptor(pis[0], new Mock<ActionDescriptor>().Object);
+
+ // Act
+ object oValue = helper.PublicGetParameterValue(context, pd);
+
+ // Assert
+ Assert.Null(oValue);
+ }
+
+ [Fact]
+ public void GetParameterValuesCallsGetParameterValue()
+ {
+ // Arrange
+ ControllerBase controller = new ParameterTestingController();
+ IDictionary<string, object> dict = new Dictionary<string, object>();
+ ControllerContext context = GetControllerContext(controller);
+ MethodInfo mi = typeof(ParameterTestingController).GetMethod("Foo");
+ ReflectedActionDescriptor ad = new ReflectedActionDescriptor(mi, "Foo", new Mock<ControllerDescriptor>().Object);
+ ParameterDescriptor[] pds = ad.GetParameters();
+
+ Mock<ControllerActionInvokerHelper> mockHelper = new Mock<ControllerActionInvokerHelper>() { CallBase = true };
+ mockHelper.Setup(h => h.PublicGetParameterValue(context, pds[0])).Returns("Myfoo").Verifiable();
+ mockHelper.Setup(h => h.PublicGetParameterValue(context, pds[1])).Returns("Mybar").Verifiable();
+ mockHelper.Setup(h => h.PublicGetParameterValue(context, pds[2])).Returns("Mybaz").Verifiable();
+ ControllerActionInvokerHelper helper = mockHelper.Object;
+
+ // Act
+ IDictionary<string, object> parameters = helper.PublicGetParameterValues(context, ad);
+
+ // Assert
+ Assert.Equal(3, parameters.Count);
+ Assert.Equal("Myfoo", parameters["foo"]);
+ Assert.Equal("Mybar", parameters["bar"]);
+ Assert.Equal("Mybaz", parameters["baz"]);
+ mockHelper.Verify();
+ }
+
+ [Fact]
+ public void GetParameterValuesReturnsEmptyDictionaryForParameterlessMethod()
+ {
+ // Arrange
+ var controller = new ParameterTestingController();
+ ControllerContext context = GetControllerContext(controller);
+ ControllerActionInvokerHelper helper = new ControllerActionInvokerHelper();
+ MethodInfo mi = typeof(ParameterTestingController).GetMethod("Parameterless");
+ ReflectedActionDescriptor ad = new ReflectedActionDescriptor(mi, "Parameterless", new Mock<ControllerDescriptor>().Object);
+
+ // Act
+ IDictionary<string, object> parameters = helper.PublicGetParameterValues(context, ad);
+
+ // Assert
+ Assert.Empty(parameters);
+ }
+
+ [Fact]
+ public void GetParameterValuesReturnsValuesForParametersInOrder()
+ {
+ // We need to hook into GetParameterValue() to make sure that GetParameterValues() is calling it.
+
+ // Arrange
+ var controller = new ParameterTestingController();
+ Dictionary<string, object> dict = new Dictionary<string, object>()
+ {
+ { "foo", "MyFoo" },
+ { "bar", "MyBar" },
+ { "baz", "MyBaz" }
+ };
+ ControllerContext context = GetControllerContext(controller, dict);
+ controller.ControllerContext = context;
+
+ ControllerActionInvokerHelper helper = new ControllerActionInvokerHelper();
+ MethodInfo mi = typeof(ParameterTestingController).GetMethod("Foo");
+ ReflectedActionDescriptor ad = new ReflectedActionDescriptor(mi, "Foo", new Mock<ControllerDescriptor>().Object);
+
+ // Act
+ IDictionary<string, object> parameters = helper.PublicGetParameterValues(context, ad);
+
+ // Assert
+ Assert.Equal(3, parameters.Count);
+ Assert.Equal("MyFoo", parameters["foo"]);
+ Assert.Equal("MyBar", parameters["bar"]);
+ Assert.Equal("MyBaz", parameters["baz"]);
+ }
+
+ [Fact]
+ public void GetParameterValueUsesControllerValueProviderAsValueProvider()
+ {
+ // Arrange
+ Dictionary<string, object> values = new Dictionary<string, object>()
+ {
+ { "foo", "fooValue" }
+ };
+
+ CustomConverterController controller = new CustomConverterController();
+ ControllerContext controllerContext = GetControllerContext(controller, values);
+ ControllerActionInvokerHelper helper = new ControllerActionInvokerHelper();
+
+ ParameterInfo parameter = typeof(CustomConverterController).GetMethod("ParameterHasNoConverters").GetParameters()[0];
+ ReflectedParameterDescriptor pd = new ReflectedParameterDescriptor(parameter, new Mock<ActionDescriptor>().Object);
+
+ // Act
+ object parameterValue = helper.PublicGetParameterValue(controllerContext, pd);
+
+ // Assert
+ Assert.Equal("fooValue", parameterValue);
+ }
+
+ [Fact]
+ public void InvokeAction()
+ {
+ // Arrange
+ ControllerBase controller = new Mock<ControllerBase>().Object;
+
+ ControllerContext context = GetControllerContext(controller);
+ ControllerDescriptor cd = new Mock<ControllerDescriptor>().Object;
+ ActionDescriptor ad = new Mock<ActionDescriptor>().Object;
+ FilterInfo filterInfo = new FilterInfo();
+
+ IDictionary<string, object> parameters = new Dictionary<string, object>();
+ MethodInfo methodInfo = typeof(object).GetMethod("ToString");
+ ActionResult actionResult = new EmptyResult();
+ ActionExecutedContext postContext = new ActionExecutedContext(context, ad, false /* canceled */, null /* exception */)
+ {
+ Result = actionResult
+ };
+ AuthorizationContext authContext = new AuthorizationContext();
+
+ Mock<ControllerActionInvokerHelper> mockHelper = new Mock<ControllerActionInvokerHelper>() { CallBase = true };
+ mockHelper.Setup(h => h.PublicGetControllerDescriptor(context)).Returns(cd).Verifiable();
+ mockHelper.Setup(h => h.PublicFindAction(context, cd, "SomeMethod")).Returns(ad).Verifiable();
+ mockHelper.Setup(h => h.PublicGetFilters(context, ad)).Returns(filterInfo).Verifiable();
+ mockHelper.Setup(h => h.PublicInvokeAuthorizationFilters(context, filterInfo.AuthorizationFilters, ad)).Returns(authContext).Verifiable();
+ mockHelper.Setup(h => h.PublicGetParameterValues(context, ad)).Returns(parameters).Verifiable();
+ mockHelper.Setup(h => h.PublicInvokeActionMethodWithFilters(context, filterInfo.ActionFilters, ad, parameters)).Returns(postContext).Verifiable();
+ mockHelper.Setup(h => h.PublicInvokeActionResultWithFilters(context, filterInfo.ResultFilters, actionResult)).Returns((ResultExecutedContext)null).Verifiable();
+ ControllerActionInvokerHelper helper = mockHelper.Object;
+
+ // Act
+ bool retVal = helper.InvokeAction(context, "SomeMethod");
+ Assert.True(retVal);
+ mockHelper.Verify();
+ }
+
+ [Fact]
+ public void InvokeActionCallsValidateRequestIfAsked()
+ {
+ // Arrange
+ ControllerBase controller = new Mock<ControllerBase>().Object;
+ controller.ValidateRequest = true;
+ bool validateInputWasCalled = false;
+
+ ControllerContext context = GetControllerContext(controller, null, validateInputCallback: () => { validateInputWasCalled = true; });
+ ControllerDescriptor cd = new Mock<ControllerDescriptor>().Object;
+ ActionDescriptor ad = new Mock<ActionDescriptor>().Object;
+ FilterInfo filterInfo = new FilterInfo();
+ AuthorizationContext authContext = new AuthorizationContext();
+
+ Mock<ControllerActionInvokerHelper> mockHelper = new Mock<ControllerActionInvokerHelper>();
+ mockHelper.CallBase = true;
+ mockHelper.Setup(h => h.PublicGetControllerDescriptor(context)).Returns(cd).Verifiable();
+ mockHelper.Setup(h => h.PublicFindAction(context, cd, "SomeMethod")).Returns(ad).Verifiable();
+ mockHelper.Setup(h => h.PublicGetFilters(context, ad)).Returns(filterInfo).Verifiable();
+ mockHelper.Setup(h => h.PublicInvokeAuthorizationFilters(context, filterInfo.AuthorizationFilters, ad)).Returns(authContext).Verifiable();
+ ControllerActionInvokerHelper helper = mockHelper.Object;
+
+ // Act
+ helper.InvokeAction(context, "SomeMethod");
+
+ // Assert
+ Assert.True(validateInputWasCalled);
+ mockHelper.Verify();
+ }
+
+ [Fact]
+ public void InvokeActionDoesNotCallValidateRequestForChildActions()
+ {
+ // Arrange
+ ControllerBase controller = new Mock<ControllerBase>().Object;
+ controller.ValidateRequest = true;
+
+ ControllerContext context = GetControllerContext(controller, null);
+ Mock.Get<ControllerContext>(context).SetupGet(c => c.IsChildAction).Returns(true);
+ ControllerDescriptor cd = new Mock<ControllerDescriptor>().Object;
+ ActionDescriptor ad = new Mock<ActionDescriptor>().Object;
+ FilterInfo filterInfo = new FilterInfo();
+ AuthorizationContext authContext = new AuthorizationContext();
+
+ Mock<ControllerActionInvokerHelper> mockHelper = new Mock<ControllerActionInvokerHelper>();
+ mockHelper.CallBase = true;
+ mockHelper.Setup(h => h.PublicGetControllerDescriptor(context)).Returns(cd).Verifiable();
+ mockHelper.Setup(h => h.PublicFindAction(context, cd, "SomeMethod")).Returns(ad).Verifiable();
+ mockHelper.Setup(h => h.PublicGetFilters(context, ad)).Returns(filterInfo).Verifiable();
+ mockHelper.Setup(h => h.PublicInvokeAuthorizationFilters(context, filterInfo.AuthorizationFilters, ad)).Returns(authContext).Verifiable();
+ ControllerActionInvokerHelper helper = mockHelper.Object;
+
+ // Act
+ helper.InvokeAction(context, "SomeMethod"); // No exception thrown
+
+ // Assert
+ mockHelper.Verify();
+ }
+
+ [Fact]
+ public void InvokeActionMethodFilterWhereContinuationThrowsExceptionAndIsHandled()
+ {
+ // Arrange
+ List<string> actions = new List<string>();
+ MethodInfo mi = typeof(object).GetMethod("ToString");
+ Dictionary<string, object> parameters = new Dictionary<string, object>();
+ Exception exception = new Exception();
+ ActionDescriptor action = new Mock<ActionDescriptor>().Object;
+
+ ActionFilterImpl filter = new ActionFilterImpl()
+ {
+ OnActionExecutingImpl = delegate(ActionExecutingContext filterContext) { actions.Add("OnActionExecuting"); },
+ OnActionExecutedImpl = delegate(ActionExecutedContext filterContext)
+ {
+ actions.Add("OnActionExecuted");
+ Assert.Same(exception, filterContext.Exception);
+ Assert.Same(action, filterContext.ActionDescriptor);
+ Assert.False(filterContext.ExceptionHandled);
+ filterContext.ExceptionHandled = true;
+ }
+ };
+ Func<ActionExecutedContext> continuation = delegate
+ {
+ actions.Add("Continuation");
+ throw exception;
+ };
+
+ ActionExecutingContext context = new ActionExecutingContext(GetControllerContext(new EmptyController()), action, parameters);
+
+ // Act
+ ActionExecutedContext result = ControllerActionInvoker.InvokeActionMethodFilter(filter, context, continuation);
+
+ // Assert
+ Assert.Equal(3, actions.Count);
+ Assert.Equal("OnActionExecuting", actions[0]);
+ Assert.Equal("Continuation", actions[1]);
+ Assert.Equal("OnActionExecuted", actions[2]);
+ Assert.Same(exception, result.Exception);
+ Assert.Same(action, result.ActionDescriptor);
+ Assert.True(result.ExceptionHandled);
+ }
+
+ [Fact]
+ public void InvokeActionMethodFilterWhereContinuationThrowsExceptionAndIsNotHandled()
+ {
+ // Arrange
+ List<string> actions = new List<string>();
+ Dictionary<string, object> parameters = new Dictionary<string, object>();
+ ActionDescriptor action = new Mock<ActionDescriptor>().Object;
+
+ ActionFilterImpl filter = new ActionFilterImpl()
+ {
+ OnActionExecutingImpl = delegate(ActionExecutingContext filterContext) { actions.Add("OnActionExecuting"); },
+ OnActionExecutedImpl = delegate(ActionExecutedContext filterContext)
+ {
+ Assert.NotNull(filterContext.Exception);
+ Assert.Equal("Some exception message.", filterContext.Exception.Message);
+ Assert.Same(action, filterContext.ActionDescriptor);
+ actions.Add("OnActionExecuted");
+ }
+ };
+ Func<ActionExecutedContext> continuation = delegate
+ {
+ actions.Add("Continuation");
+ throw new Exception("Some exception message.");
+ };
+
+ ActionExecutingContext context = new ActionExecutingContext(GetControllerContext(new EmptyController()), action, parameters);
+
+ // Act & Assert
+ Assert.Throws<Exception>(
+ delegate { ControllerActionInvoker.InvokeActionMethodFilter(filter, context, continuation); },
+ "Some exception message.");
+ Assert.Equal(3, actions.Count);
+ Assert.Equal("OnActionExecuting", actions[0]);
+ Assert.Equal("Continuation", actions[1]);
+ Assert.Equal("OnActionExecuted", actions[2]);
+ }
+
+ [Fact]
+ public void InvokeActionMethodFilterWhereContinuationThrowsThreadAbortException()
+ {
+ // Arrange
+ List<string> actions = new List<string>();
+ ActionResult actionResult = new EmptyResult();
+ ActionDescriptor action = new Mock<ActionDescriptor>().Object;
+
+ ActionFilterImpl filter = new ActionFilterImpl()
+ {
+ OnActionExecutingImpl = delegate(ActionExecutingContext filterContext) { actions.Add("OnActionExecuting"); },
+ OnActionExecutedImpl = delegate(ActionExecutedContext filterContext)
+ {
+ Thread.ResetAbort();
+ actions.Add("OnActionExecuted");
+ Assert.Null(filterContext.Exception);
+ Assert.False(filterContext.ExceptionHandled);
+ Assert.Same(action, filterContext.ActionDescriptor);
+ }
+ };
+ Func<ActionExecutedContext> continuation = delegate
+ {
+ actions.Add("Continuation");
+ Thread.CurrentThread.Abort();
+ return null;
+ };
+
+ ActionExecutingContext context = new ActionExecutingContext(new Mock<ControllerContext>().Object, action, new Dictionary<string, object>());
+
+ // Act & Assert
+ Assert.Throws<ThreadAbortException>(
+ delegate { ControllerActionInvoker.InvokeActionMethodFilter(filter, context, continuation); },
+ "Thread was being aborted.");
+ Assert.Equal(3, actions.Count);
+ Assert.Equal("OnActionExecuting", actions[0]);
+ Assert.Equal("Continuation", actions[1]);
+ Assert.Equal("OnActionExecuted", actions[2]);
+ }
+
+ [Fact]
+ public void InvokeActionMethodFilterWhereOnActionExecutingCancels()
+ {
+ // Arrange
+ bool wasCalled = false;
+ ActionDescriptor ad = new Mock<ActionDescriptor>().Object;
+ Dictionary<string, object> parameters = new Dictionary<string, object>();
+
+ ActionResult actionResult = new EmptyResult();
+ ActionDescriptor action = new Mock<ActionDescriptor>().Object;
+
+ ActionFilterImpl filter = new ActionFilterImpl()
+ {
+ OnActionExecutingImpl = delegate(ActionExecutingContext filterContext)
+ {
+ Assert.False(wasCalled);
+ wasCalled = true;
+ filterContext.Result = actionResult;
+ },
+ };
+ Func<ActionExecutedContext> continuation = delegate
+ {
+ Assert.True(false, "The continuation should not be called.");
+ return null;
+ };
+
+ ActionExecutingContext context = new ActionExecutingContext(GetControllerContext(new EmptyController()), action, parameters);
+
+ // Act
+ ActionExecutedContext result = ControllerActionInvoker.InvokeActionMethodFilter(filter, context, continuation);
+
+ // Assert
+ Assert.True(wasCalled);
+ Assert.Null(result.Exception);
+ Assert.True(result.Canceled);
+ Assert.Same(actionResult, result.Result);
+ Assert.Same(action, result.ActionDescriptor);
+ }
+
+ [Fact]
+ public void InvokeActionMethodFilterWithNormalControlFlow()
+ {
+ // Arrange
+ List<string> actions = new List<string>();
+ Dictionary<string, object> parameters = new Dictionary<string, object>();
+ ActionDescriptor action = new Mock<ActionDescriptor>().Object;
+
+ ActionExecutingContext preContext = new ActionExecutingContext(GetControllerContext(new EmptyController()), action, parameters);
+ Mock<ActionExecutedContext> mockPostContext = new Mock<ActionExecutedContext>();
+
+ ActionFilterImpl filter = new ActionFilterImpl()
+ {
+ OnActionExecutingImpl = delegate(ActionExecutingContext filterContext)
+ {
+ Assert.Same(parameters, filterContext.ActionParameters);
+ Assert.Null(filterContext.Result);
+ actions.Add("OnActionExecuting");
+ },
+ OnActionExecutedImpl = delegate(ActionExecutedContext filterContext)
+ {
+ Assert.Equal(mockPostContext.Object, filterContext);
+ actions.Add("OnActionExecuted");
+ }
+ };
+ Func<ActionExecutedContext> continuation = delegate
+ {
+ actions.Add("Continuation");
+ return mockPostContext.Object;
+ };
+
+ // Act
+ ActionExecutedContext result = ControllerActionInvoker.InvokeActionMethodFilter(filter, preContext, continuation);
+
+ // Assert
+ Assert.Equal(3, actions.Count);
+ Assert.Equal("OnActionExecuting", actions[0]);
+ Assert.Equal("Continuation", actions[1]);
+ Assert.Equal("OnActionExecuted", actions[2]);
+ Assert.Same(result, mockPostContext.Object);
+ }
+
+ [Fact]
+ public void InvokeActionInvokesExceptionFiltersAndExecutesResultIfExceptionHandled()
+ {
+ // Arrange
+ ControllerBase controller = new Mock<ControllerBase>().Object;
+
+ ControllerContext context = GetControllerContext(controller);
+ ControllerDescriptor cd = new Mock<ControllerDescriptor>().Object;
+ ActionDescriptor ad = new Mock<ActionDescriptor>().Object;
+ FilterInfo filterInfo = new FilterInfo();
+
+ Exception exception = new Exception();
+ ActionResult actionResult = new EmptyResult();
+ ExceptionContext exContext = new ExceptionContext(context, exception)
+ {
+ ExceptionHandled = true,
+ Result = actionResult
+ };
+
+ Mock<ControllerActionInvokerHelper> mockHelper = new Mock<ControllerActionInvokerHelper>() { CallBase = true };
+ mockHelper.Setup(h => h.PublicGetControllerDescriptor(context)).Returns(cd).Verifiable();
+ mockHelper.Setup(h => h.PublicFindAction(context, cd, "SomeMethod")).Returns(ad).Verifiable();
+ mockHelper.Setup(h => h.PublicGetFilters(context, ad)).Returns(filterInfo).Verifiable();
+ mockHelper.Setup(h => h.PublicInvokeAuthorizationFilters(context, filterInfo.AuthorizationFilters, ad)).Throws(exception).Verifiable();
+ mockHelper.Setup(h => h.PublicInvokeExceptionFilters(context, filterInfo.ExceptionFilters, exception)).Returns(exContext).Verifiable();
+ mockHelper.Setup(h => h.PublicInvokeActionResult(context, actionResult)).Verifiable();
+ ControllerActionInvokerHelper helper = mockHelper.Object;
+
+ // Act
+ bool retVal = helper.InvokeAction(context, "SomeMethod");
+ Assert.True(retVal);
+ mockHelper.Verify();
+ }
+
+ [Fact]
+ public void InvokeActionInvokesExceptionFiltersAndRethrowsExceptionIfNotHandled()
+ {
+ // Arrange
+ ControllerBase controller = new Mock<ControllerBase>().Object;
+
+ ControllerContext context = GetControllerContext(controller);
+ ControllerDescriptor cd = new Mock<ControllerDescriptor>().Object;
+ ActionDescriptor ad = new Mock<ActionDescriptor>().Object;
+ FilterInfo filterInfo = new FilterInfo();
+
+ Exception exception = new Exception();
+ ExceptionContext exContext = new ExceptionContext(context, exception);
+
+ Mock<ControllerActionInvokerHelper> mockHelper = new Mock<ControllerActionInvokerHelper>() { CallBase = true };
+ mockHelper.Setup(h => h.PublicGetControllerDescriptor(context)).Returns(cd).Verifiable();
+ mockHelper.Setup(h => h.PublicFindAction(context, cd, "SomeMethod")).Returns(ad).Verifiable();
+ mockHelper.Setup(h => h.PublicGetFilters(context, ad)).Returns(filterInfo).Verifiable();
+ mockHelper.Setup(h => h.PublicInvokeAuthorizationFilters(context, filterInfo.AuthorizationFilters, ad)).Throws(exception).Verifiable();
+ mockHelper.Setup(h => h.PublicInvokeExceptionFilters(context, filterInfo.ExceptionFilters, exception)).Returns(exContext).Verifiable();
+ mockHelper.Setup(h => h.PublicInvokeActionResult(context, It.IsAny<ActionResult>())).Callback(delegate { Assert.True(false, "InvokeActionResult() shouldn't be called if the exception was unhandled by filters."); });
+ ControllerActionInvokerHelper helper = mockHelper.Object;
+
+ // Act
+ Exception thrownException = Assert.Throws<Exception>(
+ delegate { helper.InvokeAction(context, "SomeMethod"); });
+
+ // Assert
+ Assert.Same(exception, thrownException);
+ mockHelper.Verify();
+ }
+
+ [Fact]
+ public void InvokeActionInvokesResultIfAuthorizationFilterReturnsResult()
+ {
+ // Arrange
+ ControllerBase controller = new Mock<ControllerBase>().Object;
+
+ ControllerContext context = GetControllerContext(controller);
+ ControllerDescriptor cd = new Mock<ControllerDescriptor>().Object;
+ ActionDescriptor ad = new Mock<ActionDescriptor>().Object;
+ FilterInfo filterInfo = new FilterInfo();
+
+ ActionResult actionResult = new EmptyResult();
+ ActionExecutedContext postContext = new ActionExecutedContext(context, ad, false /* canceled */, null /* exception */)
+ {
+ Result = actionResult
+ };
+ AuthorizationContext authContext = new AuthorizationContext() { Result = actionResult };
+
+ Mock<ControllerActionInvokerHelper> mockHelper = new Mock<ControllerActionInvokerHelper>() { CallBase = true };
+ mockHelper.Setup(h => h.PublicGetControllerDescriptor(context)).Returns(cd).Verifiable();
+ mockHelper.Setup(h => h.PublicFindAction(context, cd, "SomeMethod")).Returns(ad).Verifiable();
+ mockHelper.Setup(h => h.PublicGetFilters(context, ad)).Returns(filterInfo).Verifiable();
+ mockHelper.Setup(h => h.PublicInvokeAuthorizationFilters(context, filterInfo.AuthorizationFilters, ad)).Returns(authContext).Verifiable();
+ mockHelper.Setup(h => h.PublicInvokeActionResult(context, actionResult)).Verifiable();
+ ControllerActionInvokerHelper helper = mockHelper.Object;
+
+ // Act
+ bool retVal = helper.InvokeAction(context, "SomeMethod");
+ Assert.True(retVal);
+ mockHelper.Verify();
+ }
+
+ [Fact]
+ public void InvokeActionMethod()
+ {
+ // Arrange
+ EmptyController controller = new EmptyController();
+ ControllerContext controllerContext = GetControllerContext(controller);
+ Dictionary<string, object> parameters = new Dictionary<string, object>();
+
+ ActionResult expectedResult = new Mock<ActionResult>().Object;
+
+ Mock<ActionDescriptor> mockAd = new Mock<ActionDescriptor>();
+ mockAd.Setup(ad => ad.Execute(controllerContext, parameters)).Returns("hello world");
+
+ Mock<ControllerActionInvokerHelper> mockHelper = new Mock<ControllerActionInvokerHelper>() { CallBase = true };
+ mockHelper.Setup(h => h.PublicCreateActionResult(controllerContext, mockAd.Object, "hello world")).Returns(expectedResult);
+ ControllerActionInvokerHelper helper = mockHelper.Object;
+
+ // Act
+ ActionResult returnedResult = helper.PublicInvokeActionMethod(controllerContext, mockAd.Object, parameters);
+
+ // Assert
+ Assert.Same(expectedResult, returnedResult);
+ }
+
+ [Fact]
+ public void InvokeActionMethodWithFiltersOrdersFiltersCorrectly()
+ {
+ // Arrange
+ List<string> actions = new List<string>();
+ Dictionary<string, object> parameters = new Dictionary<string, object>();
+ ActionResult actionResult = new EmptyResult();
+
+ ActionFilterImpl filter1 = new ActionFilterImpl()
+ {
+ OnActionExecutingImpl = delegate(ActionExecutingContext filterContext) { actions.Add("OnActionExecuting1"); },
+ OnActionExecutedImpl = delegate(ActionExecutedContext filterContext) { actions.Add("OnActionExecuted1"); }
+ };
+ ActionFilterImpl filter2 = new ActionFilterImpl()
+ {
+ OnActionExecutingImpl = delegate(ActionExecutingContext filterContext) { actions.Add("OnActionExecuting2"); },
+ OnActionExecutedImpl = delegate(ActionExecutedContext filterContext) { actions.Add("OnActionExecuted2"); }
+ };
+ Func<ActionResult> continuation = delegate
+ {
+ actions.Add("Continuation");
+ return new EmptyResult();
+ };
+ ControllerBase controller = new ContinuationController(continuation);
+ ControllerContext context = GetControllerContext(controller);
+ ActionDescriptor actionDescriptor = new ReflectedActionDescriptor(ContinuationController.GoMethod, "someName", new Mock<ControllerDescriptor>().Object);
+ ControllerActionInvokerHelper helper = new ControllerActionInvokerHelper();
+ List<IActionFilter> filters = new List<IActionFilter>() { filter1, filter2 };
+
+ // Act
+ helper.PublicInvokeActionMethodWithFilters(context, filters, actionDescriptor, parameters);
+
+ // Assert
+ Assert.Equal(5, actions.Count);
+ Assert.Equal("OnActionExecuting1", actions[0]);
+ Assert.Equal("OnActionExecuting2", actions[1]);
+ Assert.Equal("Continuation", actions[2]);
+ Assert.Equal("OnActionExecuted2", actions[3]);
+ Assert.Equal("OnActionExecuted1", actions[4]);
+ }
+
+ [Fact]
+ public void InvokeActionMethodWithFiltersPassesArgumentsCorrectly()
+ {
+ // Arrange
+ bool wasCalled = false;
+ MethodInfo mi = ContinuationController.GoMethod;
+ Dictionary<string, object> parameters = new Dictionary<string, object>();
+ ActionResult actionResult = new EmptyResult();
+ ActionFilterImpl filter = new ActionFilterImpl()
+ {
+ OnActionExecutingImpl = delegate(ActionExecutingContext filterContext)
+ {
+ Assert.Same(parameters, filterContext.ActionParameters);
+ Assert.False(wasCalled);
+ wasCalled = true;
+ filterContext.Result = actionResult;
+ }
+ };
+ Func<ActionResult> continuation = delegate
+ {
+ Assert.True(false, "Continuation should not be called.");
+ return null;
+ };
+ ControllerBase controller = new ContinuationController(continuation);
+ ControllerContext context = GetControllerContext(controller);
+ ControllerActionInvokerHelper helper = new ControllerActionInvokerHelper();
+ ActionDescriptor actionDescriptor = new ReflectedActionDescriptor(ContinuationController.GoMethod, "someName", new Mock<ControllerDescriptor>().Object);
+ List<IActionFilter> filters = new List<IActionFilter>() { filter };
+
+ // Act
+ ActionExecutedContext result = helper.PublicInvokeActionMethodWithFilters(context, filters, actionDescriptor, parameters);
+
+ // Assert
+ Assert.True(wasCalled);
+ Assert.Null(result.Exception);
+ Assert.False(result.ExceptionHandled);
+ Assert.Same(actionResult, result.Result);
+ Assert.Same(actionDescriptor, result.ActionDescriptor);
+ }
+
+ [Fact]
+ public void InvokeActionPropagatesThreadAbortException()
+ {
+ // Arrange
+ ControllerBase controller = new Mock<ControllerBase>().Object;
+
+ ControllerContext context = GetControllerContext(controller);
+ ControllerDescriptor cd = new Mock<ControllerDescriptor>().Object;
+ ActionDescriptor ad = new Mock<ActionDescriptor>().Object;
+ FilterInfo filterInfo = new FilterInfo();
+
+ ActionResult actionResult = new EmptyResult();
+ ActionExecutedContext postContext = new ActionExecutedContext(context, ad, false /* canceled */, null /* exception */)
+ {
+ Result = actionResult
+ };
+ AuthorizationContext authContext = new AuthorizationContext() { Result = actionResult };
+
+ Mock<ControllerActionInvokerHelper> mockHelper = new Mock<ControllerActionInvokerHelper>() { CallBase = true };
+ mockHelper.Setup(h => h.PublicGetControllerDescriptor(context)).Returns(cd).Verifiable();
+ mockHelper.Setup(h => h.PublicFindAction(context, cd, "SomeMethod")).Returns(ad).Verifiable();
+ mockHelper.Setup(h => h.PublicGetFilters(context, ad)).Returns(filterInfo).Verifiable();
+ mockHelper
+ .Setup(h => h.PublicInvokeAuthorizationFilters(context, filterInfo.AuthorizationFilters, ad))
+ .Returns(
+ delegate(ControllerContext cc, IList<IAuthorizationFilter> f, ActionDescriptor a)
+ {
+ Thread.CurrentThread.Abort();
+ return null;
+ });
+ ControllerActionInvokerHelper helper = mockHelper.Object;
+
+ bool wasAborted = false;
+
+ // Act
+ try
+ {
+ helper.InvokeAction(context, "SomeMethod");
+ }
+ catch (ThreadAbortException)
+ {
+ wasAborted = true;
+ Thread.ResetAbort();
+ }
+
+ // Assert
+ Assert.True(wasAborted);
+ mockHelper.Verify();
+ }
+
+ [Fact]
+ public void InvokeActionResultWithFiltersPassesSameContextObjectToInnerFilters()
+ {
+ // Arrange
+ ControllerBase controller = new Mock<ControllerBase>().Object;
+ ControllerContext context = GetControllerContext(controller);
+
+ ResultExecutingContext storedContext = null;
+ ActionResult result = new EmptyResult();
+ List<IResultFilter> filters = new List<IResultFilter>()
+ {
+ new ActionFilterImpl()
+ {
+ OnResultExecutingImpl = delegate(ResultExecutingContext ctx) { storedContext = ctx; },
+ OnResultExecutedImpl = delegate { }
+ },
+ new ActionFilterImpl()
+ {
+ OnResultExecutingImpl = delegate(ResultExecutingContext ctx) { Assert.Same(storedContext, ctx); },
+ OnResultExecutedImpl = delegate { }
+ },
+ };
+ ControllerActionInvokerHelper helper = new ControllerActionInvokerHelper();
+
+ // Act
+ ResultExecutedContext postContext = helper.PublicInvokeActionResultWithFilters(context, filters, result);
+
+ // Assert
+ Assert.Same(result, postContext.Result);
+ }
+
+ [Fact]
+ public void InvokeActionReturnsFalseIfMethodNotFound()
+ {
+ // Arrange
+ var controller = new BlankController();
+ ControllerContext context = GetControllerContext(controller);
+ ControllerActionInvoker invoker = new ControllerActionInvoker();
+
+ // Act
+ bool retVal = invoker.InvokeAction(context, "foo");
+
+ // Assert
+ Assert.False(retVal);
+ }
+
+ [Fact]
+ public void InvokeActionThrowsIfControllerContextIsNull()
+ {
+ // Arrange
+ ControllerActionInvoker invoker = new ControllerActionInvoker();
+
+ // Act & Assert
+ Assert.ThrowsArgumentNull(
+ delegate { invoker.InvokeAction(null, "actionName"); }, "controllerContext");
+ }
+
+ [Fact]
+ public void InvokeActionWithEmptyActionNameThrows()
+ {
+ // Arrange
+ var controller = new BasicMethodInvokeController();
+ ControllerContext context = GetControllerContext(controller);
+ ControllerActionInvoker invoker = new ControllerActionInvoker();
+
+ // Act & Assert
+ Assert.ThrowsArgumentNullOrEmpty(
+ delegate { invoker.InvokeAction(context, String.Empty); },
+ "actionName");
+ }
+
+ [Fact]
+ public void InvokeActionWithNullActionNameThrows()
+ {
+ // Arrange
+ var controller = new BasicMethodInvokeController();
+ ControllerContext context = GetControllerContext(controller);
+ ControllerActionInvoker invoker = new ControllerActionInvoker();
+
+ // Act & Assert
+ Assert.ThrowsArgumentNullOrEmpty(
+ delegate { invoker.InvokeAction(context, null /* actionName */); },
+ "actionName");
+ }
+
+ [Fact]
+ public void InvokeActionWithResultExceptionInvokesExceptionFiltersAndExecutesResultIfExceptionHandled()
+ {
+ // Arrange
+ ControllerBase controller = new Mock<ControllerBase>().Object;
+
+ ControllerContext context = GetControllerContext(controller);
+ ControllerDescriptor cd = new Mock<ControllerDescriptor>().Object;
+ ActionDescriptor ad = new Mock<ActionDescriptor>().Object;
+ IDictionary<string, object> parameters = new Dictionary<string, object>();
+ FilterInfo filterInfo = new FilterInfo();
+ AuthorizationContext authContext = new AuthorizationContext();
+
+ Exception exception = new Exception();
+ ActionResult actionResult = new EmptyResult();
+ ActionExecutedContext postContext = new ActionExecutedContext(context, ad, false /* canceled */, null /* exception */)
+ {
+ Result = actionResult
+ };
+ ExceptionContext exContext = new ExceptionContext(context, exception)
+ {
+ ExceptionHandled = true,
+ Result = actionResult
+ };
+
+ Mock<ControllerActionInvokerHelper> mockHelper = new Mock<ControllerActionInvokerHelper>() { CallBase = true };
+ mockHelper.Setup(h => h.PublicGetControllerDescriptor(context)).Returns(cd).Verifiable();
+ mockHelper.Setup(h => h.PublicFindAction(context, cd, "SomeMethod")).Returns(ad).Verifiable();
+ mockHelper.Setup(h => h.PublicGetFilters(context, ad)).Returns(filterInfo).Verifiable();
+ mockHelper.Setup(h => h.PublicInvokeAuthorizationFilters(context, filterInfo.AuthorizationFilters, ad)).Returns(authContext).Verifiable();
+ mockHelper.Setup(h => h.PublicGetParameterValues(context, ad)).Returns(parameters).Verifiable();
+ mockHelper.Setup(h => h.PublicInvokeActionMethodWithFilters(context, filterInfo.ActionFilters, ad, parameters)).Returns(postContext).Verifiable();
+ mockHelper.Setup(h => h.PublicInvokeActionResultWithFilters(context, filterInfo.ResultFilters, actionResult)).Throws(exception).Verifiable();
+ mockHelper.Setup(h => h.PublicInvokeExceptionFilters(context, filterInfo.ExceptionFilters, exception)).Returns(exContext).Verifiable();
+ mockHelper.Setup(h => h.PublicInvokeActionResult(context, actionResult)).Verifiable();
+ ControllerActionInvokerHelper helper = mockHelper.Object;
+
+ // Act
+ bool retVal = helper.InvokeAction(context, "SomeMethod");
+ Assert.True(retVal, "InvokeAction() should return True on success.");
+ mockHelper.Verify();
+ }
+
+ [Fact]
+ public void InvokeAuthorizationFilters()
+ {
+ // Arrange
+ ControllerBase controller = new Mock<ControllerBase>().Object;
+ ActionDescriptor ad = new Mock<ActionDescriptor>().Object;
+ ControllerContext controllerContext = GetControllerContext(controller);
+ ControllerActionInvokerHelper helper = new ControllerActionInvokerHelper();
+
+ List<AuthorizationFilterHelper> callQueue = new List<AuthorizationFilterHelper>();
+ AuthorizationFilterHelper filter1 = new AuthorizationFilterHelper(callQueue);
+ AuthorizationFilterHelper filter2 = new AuthorizationFilterHelper(callQueue);
+ IAuthorizationFilter[] filters = new IAuthorizationFilter[] { filter1, filter2 };
+
+ // Act
+ AuthorizationContext postContext = helper.PublicInvokeAuthorizationFilters(controllerContext, filters, ad);
+
+ // Assert
+ Assert.Equal(ad, postContext.ActionDescriptor);
+ Assert.Equal(2, callQueue.Count);
+ Assert.Same(filter1, callQueue[0]);
+ Assert.Same(filter2, callQueue[1]);
+ }
+
+ [Fact]
+ public void InvokeAuthorizationFiltersStopsExecutingIfResultProvided()
+ {
+ // Arrange
+ ControllerBase controller = new Mock<ControllerBase>().Object;
+ ActionDescriptor ad = new Mock<ActionDescriptor>().Object;
+ ControllerContext controllerContext = GetControllerContext(controller);
+ ControllerActionInvokerHelper helper = new ControllerActionInvokerHelper();
+ ActionResult result = new EmptyResult();
+
+ List<AuthorizationFilterHelper> callQueue = new List<AuthorizationFilterHelper>();
+ AuthorizationFilterHelper filter1 = new AuthorizationFilterHelper(callQueue) { ShortCircuitResult = result };
+ AuthorizationFilterHelper filter2 = new AuthorizationFilterHelper(callQueue);
+ IAuthorizationFilter[] filters = new IAuthorizationFilter[] { filter1, filter2 };
+
+ // Act
+ AuthorizationContext postContext = helper.PublicInvokeAuthorizationFilters(controllerContext, filters, ad);
+
+ // Assert
+ Assert.Equal(ad, postContext.ActionDescriptor);
+ Assert.Same(result, postContext.Result);
+ Assert.Single(callQueue);
+ Assert.Same(filter1, callQueue[0]);
+ }
+
+ [Fact]
+ public void InvokeExceptionFilters()
+ {
+ // Arrange
+ ControllerBase controller = new Mock<ControllerBase>().Object;
+ Exception exception = new Exception();
+ ControllerContext controllerContext = GetControllerContext(controller);
+ ControllerActionInvokerHelper helper = new ControllerActionInvokerHelper();
+
+ List<ExceptionFilterHelper> callQueue = new List<ExceptionFilterHelper>();
+ ExceptionFilterHelper filter1 = new ExceptionFilterHelper(callQueue);
+ ExceptionFilterHelper filter2 = new ExceptionFilterHelper(callQueue);
+ IExceptionFilter[] filters = new IExceptionFilter[] { filter1, filter2 };
+
+ // Act
+ ExceptionContext postContext = helper.PublicInvokeExceptionFilters(controllerContext, filters, exception);
+
+ // Assert
+ Assert.Same(exception, postContext.Exception);
+ Assert.False(postContext.ExceptionHandled);
+ Assert.Same(filter1.ContextPassed, filter2.ContextPassed);
+ Assert.Equal(2, callQueue.Count);
+ Assert.Same(filter2, callQueue[0]); // Exception filters are executed in reverse order
+ Assert.Same(filter1, callQueue[1]);
+ }
+
+ [Fact]
+ public void InvokeExceptionFiltersContinuesExecutingIfExceptionHandled()
+ {
+ // Arrange
+ ControllerBase controller = new Mock<ControllerBase>().Object;
+ Exception exception = new Exception();
+ ControllerContext controllerContext = GetControllerContext(controller);
+ ControllerActionInvokerHelper helper = new ControllerActionInvokerHelper();
+
+ List<ExceptionFilterHelper> callQueue = new List<ExceptionFilterHelper>();
+ ExceptionFilterHelper filter1 = new ExceptionFilterHelper(callQueue) { ShouldHandleException = true };
+ ExceptionFilterHelper filter2 = new ExceptionFilterHelper(callQueue);
+ IExceptionFilter[] filters = new IExceptionFilter[] { filter1, filter2 };
+
+ // Act
+ ExceptionContext postContext = helper.PublicInvokeExceptionFilters(controllerContext, filters, exception);
+
+ // Assert
+ Assert.Same(exception, postContext.Exception);
+ Assert.True(postContext.ExceptionHandled);
+ Assert.Same(filter1.ContextPassed, filter2.ContextPassed);
+ Assert.Equal(2, callQueue.Count);
+ Assert.Same(filter2, callQueue[0]); // Exception filters are executed in reverse order
+ Assert.Same(filter1, callQueue[1]);
+ }
+
+ [Fact]
+ public void InvokeResultFiltersOrdersFiltersCorrectly()
+ {
+ // Arrange
+ List<string> actions = new List<string>();
+ ActionFilterImpl filter1 = new ActionFilterImpl()
+ {
+ OnResultExecutingImpl = delegate(ResultExecutingContext filterContext) { actions.Add("OnResultExecuting1"); },
+ OnResultExecutedImpl = delegate(ResultExecutedContext filterContext) { actions.Add("OnResultExecuted1"); }
+ };
+ ActionFilterImpl filter2 = new ActionFilterImpl()
+ {
+ OnResultExecutingImpl = delegate(ResultExecutingContext filterContext) { actions.Add("OnResultExecuting2"); },
+ OnResultExecutedImpl = delegate(ResultExecutedContext filterContext) { actions.Add("OnResultExecuted2"); }
+ };
+ Action continuation = delegate { actions.Add("Continuation"); };
+ ActionResult actionResult = new ContinuationResult(continuation);
+ ControllerBase controller = new Mock<ControllerBase>().Object;
+ ControllerContext context = GetControllerContext(controller);
+ ControllerActionInvokerHelper helper = new ControllerActionInvokerHelper();
+ List<IResultFilter> filters = new List<IResultFilter>() { filter1, filter2 };
+
+ // Act
+ helper.PublicInvokeActionResultWithFilters(context, filters, actionResult);
+
+ // Assert
+ Assert.Equal(5, actions.Count);
+ Assert.Equal("OnResultExecuting1", actions[0]);
+ Assert.Equal("OnResultExecuting2", actions[1]);
+ Assert.Equal("Continuation", actions[2]);
+ Assert.Equal("OnResultExecuted2", actions[3]);
+ Assert.Equal("OnResultExecuted1", actions[4]);
+ }
+
+ [Fact]
+ public void InvokeResultFiltersPassesArgumentsCorrectly()
+ {
+ // Arrange
+ bool wasCalled = false;
+ Action continuation = delegate { Assert.True(false, "Continuation should not be called."); };
+ ActionResult actionResult = new ContinuationResult(continuation);
+ ControllerBase controller = new Mock<ControllerBase>().Object;
+ ControllerContext context = GetControllerContext(controller);
+ ControllerActionInvokerHelper helper = new ControllerActionInvokerHelper();
+ ActionFilterImpl filter = new ActionFilterImpl()
+ {
+ OnResultExecutingImpl = delegate(ResultExecutingContext filterContext)
+ {
+ Assert.Same(actionResult, filterContext.Result);
+ Assert.False(wasCalled);
+ wasCalled = true;
+ filterContext.Cancel = true;
+ }
+ };
+
+ List<IResultFilter> filters = new List<IResultFilter>() { filter };
+
+ // Act
+ ResultExecutedContext result = helper.PublicInvokeActionResultWithFilters(context, filters, actionResult);
+
+ // Assert
+ Assert.True(wasCalled);
+ Assert.Null(result.Exception);
+ Assert.False(result.ExceptionHandled);
+ Assert.Same(actionResult, result.Result);
+ }
+
+ [Fact]
+ public void InvokeResultFilterWhereContinuationThrowsExceptionAndIsHandled()
+ {
+ // Arrange
+ List<string> actions = new List<string>();
+ ActionResult actionResult = new EmptyResult();
+ Exception exception = new Exception();
+ ActionFilterImpl filter = new ActionFilterImpl()
+ {
+ OnResultExecutingImpl = delegate(ResultExecutingContext filterContext) { actions.Add("OnResultExecuting"); },
+ OnResultExecutedImpl = delegate(ResultExecutedContext filterContext)
+ {
+ actions.Add("OnResultExecuted");
+ Assert.Same(actionResult, filterContext.Result);
+ Assert.Same(exception, filterContext.Exception);
+ Assert.False(filterContext.ExceptionHandled);
+ filterContext.ExceptionHandled = true;
+ }
+ };
+ Func<ResultExecutedContext> continuation = delegate
+ {
+ actions.Add("Continuation");
+ throw exception;
+ };
+
+ Mock<ResultExecutingContext> mockResultExecutingContext = new Mock<ResultExecutingContext>() { DefaultValue = DefaultValue.Mock };
+ mockResultExecutingContext.Setup(c => c.Result).Returns(actionResult);
+
+ // Act
+ ResultExecutedContext result = ControllerActionInvoker.InvokeActionResultFilter(filter, mockResultExecutingContext.Object, continuation);
+
+ // Assert
+ Assert.Equal(3, actions.Count);
+ Assert.Equal("OnResultExecuting", actions[0]);
+ Assert.Equal("Continuation", actions[1]);
+ Assert.Equal("OnResultExecuted", actions[2]);
+ Assert.Same(exception, result.Exception);
+ Assert.True(result.ExceptionHandled);
+ Assert.Same(actionResult, result.Result);
+ }
+
+ [Fact]
+ public void InvokeResultFilterWhereContinuationThrowsExceptionAndIsNotHandled()
+ {
+ // Arrange
+ List<string> actions = new List<string>();
+ ActionFilterImpl filter = new ActionFilterImpl()
+ {
+ OnResultExecutingImpl = delegate(ResultExecutingContext filterContext) { actions.Add("OnResultExecuting"); },
+ OnResultExecutedImpl = delegate(ResultExecutedContext filterContext) { actions.Add("OnResultExecuted"); }
+ };
+ Func<ResultExecutedContext> continuation = delegate
+ {
+ actions.Add("Continuation");
+ throw new Exception("Some exception message.");
+ };
+
+ // Act & Assert
+ Assert.Throws<Exception>(
+ delegate { ControllerActionInvoker.InvokeActionResultFilter(filter, new Mock<ResultExecutingContext>() { DefaultValue = DefaultValue.Mock }.Object, continuation); },
+ "Some exception message.");
+ Assert.Equal(3, actions.Count);
+ Assert.Equal("OnResultExecuting", actions[0]);
+ Assert.Equal("Continuation", actions[1]);
+ Assert.Equal("OnResultExecuted", actions[2]);
+ }
+
+ [Fact]
+ public void InvokeResultFilterWhereContinuationThrowsThreadAbortException()
+ {
+ // Arrange
+ List<string> actions = new List<string>();
+ ActionResult actionResult = new EmptyResult();
+
+ Mock<ResultExecutingContext> mockPreContext = new Mock<ResultExecutingContext>() { DefaultValue = DefaultValue.Mock };
+ mockPreContext.Setup(c => c.Result).Returns(actionResult);
+
+ ActionFilterImpl filter = new ActionFilterImpl()
+ {
+ OnResultExecutingImpl = delegate(ResultExecutingContext filterContext) { actions.Add("OnResultExecuting"); },
+ OnResultExecutedImpl = delegate(ResultExecutedContext filterContext)
+ {
+ Thread.ResetAbort();
+ actions.Add("OnResultExecuted");
+ Assert.Same(actionResult, filterContext.Result);
+ Assert.Null(filterContext.Exception);
+ Assert.False(filterContext.ExceptionHandled);
+ }
+ };
+ Func<ResultExecutedContext> continuation = delegate
+ {
+ actions.Add("Continuation");
+ Thread.CurrentThread.Abort();
+ return null;
+ };
+
+ // Act & Assert
+ Assert.Throws<ThreadAbortException>(
+ delegate { ControllerActionInvoker.InvokeActionResultFilter(filter, mockPreContext.Object, continuation); },
+ "Thread was being aborted.");
+ Assert.Equal(3, actions.Count);
+ Assert.Equal("OnResultExecuting", actions[0]);
+ Assert.Equal("Continuation", actions[1]);
+ Assert.Equal("OnResultExecuted", actions[2]);
+ }
+
+ [Fact]
+ public void InvokeResultFilterWhereOnResultExecutingCancels()
+ {
+ // Arrange
+ bool wasCalled = false;
+ MethodInfo mi = typeof(object).GetMethod("ToString");
+ object[] paramValues = new object[0];
+ ActionResult actionResult = new EmptyResult();
+ ActionFilterImpl filter = new ActionFilterImpl()
+ {
+ OnResultExecutingImpl = delegate(ResultExecutingContext filterContext)
+ {
+ Assert.False(wasCalled);
+ wasCalled = true;
+ filterContext.Cancel = true;
+ },
+ };
+ Func<ResultExecutedContext> continuation = delegate
+ {
+ Assert.True(false, "The continuation should not be called.");
+ return null;
+ };
+
+ Mock<ResultExecutingContext> mockResultExecutingContext = new Mock<ResultExecutingContext>() { DefaultValue = DefaultValue.Mock };
+ mockResultExecutingContext.Setup(c => c.Result).Returns(actionResult);
+
+ // Act
+ ResultExecutedContext result = ControllerActionInvoker.InvokeActionResultFilter(filter, mockResultExecutingContext.Object, continuation);
+
+ // Assert
+ Assert.True(wasCalled);
+ Assert.Null(result.Exception);
+ Assert.True(result.Canceled);
+ Assert.Same(actionResult, result.Result);
+ }
+
+ [Fact]
+ public void InvokeResultFilterWithNormalControlFlow()
+ {
+ // Arrange
+ List<string> actions = new List<string>();
+ ActionResult actionResult = new EmptyResult();
+
+ Mock<ResultExecutedContext> mockPostContext = new Mock<ResultExecutedContext>();
+ mockPostContext.Setup(c => c.Result).Returns(actionResult);
+
+ ActionFilterImpl filter = new ActionFilterImpl()
+ {
+ OnResultExecutingImpl = delegate(ResultExecutingContext filterContext)
+ {
+ Assert.Same(actionResult, filterContext.Result);
+ Assert.False(filterContext.Cancel);
+ actions.Add("OnResultExecuting");
+ },
+ OnResultExecutedImpl = delegate(ResultExecutedContext filterContext)
+ {
+ Assert.Equal(mockPostContext.Object, filterContext);
+ actions.Add("OnResultExecuted");
+ }
+ };
+ Func<ResultExecutedContext> continuation = delegate
+ {
+ actions.Add("Continuation");
+ return mockPostContext.Object;
+ };
+
+ Mock<ResultExecutingContext> mockResultExecutingContext = new Mock<ResultExecutingContext>();
+ mockResultExecutingContext.Setup(c => c.Result).Returns(actionResult);
+
+ // Act
+ ResultExecutedContext result = ControllerActionInvoker.InvokeActionResultFilter(filter, mockResultExecutingContext.Object, continuation);
+
+ // Assert
+ Assert.Equal(3, actions.Count);
+ Assert.Equal("OnResultExecuting", actions[0]);
+ Assert.Equal("Continuation", actions[1]);
+ Assert.Equal("OnResultExecuted", actions[2]);
+ Assert.Same(result, mockPostContext.Object);
+ }
+
+ [Fact]
+ public void InvokeMethodCallsOverriddenCreateActionResult()
+ {
+ // Arrange
+ CustomResultInvokerController controller = new CustomResultInvokerController();
+ ControllerContext context = GetControllerContext(controller);
+ CustomResultInvoker helper = new CustomResultInvoker();
+ MethodInfo mi = typeof(CustomResultInvokerController).GetMethod("ReturnCustomResult");
+ ReflectedActionDescriptor ad = new ReflectedActionDescriptor(mi, "ReturnCustomResult", new Mock<ControllerDescriptor>().Object);
+ IDictionary<string, object> parameters = new Dictionary<string, object>();
+
+ // Act
+ ActionResult actionResult = helper.PublicInvokeActionMethod(context, ad, parameters);
+
+ // Assert (arg got passed to method + back correctly)
+ CustomResult customResult = Assert.IsType<CustomResult>(actionResult);
+ Assert.Equal("abc123", customResult.ReturnValue);
+ }
+
+ private static ControllerContext GetControllerContext(ControllerBase controller)
+ {
+ return GetControllerContext(controller, null);
+ }
+
+ private static ControllerContext GetControllerContext(ControllerBase controller, IDictionary<string, object> values, Action validateInputCallback = null)
+ {
+ SimpleValueProvider valueProvider = new SimpleValueProvider();
+ controller.ValueProvider = valueProvider;
+ if (values != null)
+ {
+ foreach (var entry in values)
+ {
+ valueProvider[entry.Key] = entry.Value;
+ }
+ }
+
+ Mock<ControllerContext> mockControllerContext = new Mock<ControllerContext>() { DefaultValue = DefaultValue.Mock };
+
+ mockControllerContext.Setup(c => c.HttpContext.Request.ValidateInput()).Callback(() =>
+ {
+ if (!controller.ValidateRequest)
+ {
+ Assert.True(false, "ValidateRequest() should not be called if the controller opted out.");
+ }
+ if (validateInputCallback != null)
+ {
+ // signal to caller that ValidateInput was called
+ validateInputCallback();
+ }
+ });
+
+ mockControllerContext.Setup(c => c.HttpContext.Session).Returns((HttpSessionStateBase)null);
+ mockControllerContext.Setup(c => c.Controller).Returns(controller);
+ return mockControllerContext.Object;
+ }
+
+ private class EmptyActionFilterAttribute : ActionFilterAttribute
+ {
+ }
+
+ private abstract class KeyedFilterAttribute : FilterAttribute
+ {
+ public string Key { get; set; }
+ }
+
+ private class KeyedAuthorizationFilterAttribute : KeyedFilterAttribute, IAuthorizationFilter
+ {
+ public void OnAuthorization(AuthorizationContext filterContext)
+ {
+ throw new NotImplementedException();
+ }
+ }
+
+ [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = true, AllowMultiple = true)]
+ private class KeyedActionFilterAttribute : KeyedFilterAttribute, IActionFilter, IResultFilter
+ {
+ public void OnActionExecuting(ActionExecutingContext filterContext)
+ {
+ throw new NotImplementedException();
+ }
+
+ public void OnActionExecuted(ActionExecutedContext filterContext)
+ {
+ throw new NotImplementedException();
+ }
+
+ public void OnResultExecuting(ResultExecutingContext filterContext)
+ {
+ throw new NotImplementedException();
+ }
+
+ public void OnResultExecuted(ResultExecutedContext filterContext)
+ {
+ throw new NotImplementedException();
+ }
+ }
+
+ private class ActionFilterImpl : IActionFilter, IResultFilter
+ {
+ public Action<ActionExecutingContext> OnActionExecutingImpl { get; set; }
+
+ public void OnActionExecuting(ActionExecutingContext filterContext)
+ {
+ OnActionExecutingImpl(filterContext);
+ }
+
+ public Action<ActionExecutedContext> OnActionExecutedImpl { get; set; }
+
+ public void OnActionExecuted(ActionExecutedContext filterContext)
+ {
+ OnActionExecutedImpl(filterContext);
+ }
+
+ public Action<ResultExecutingContext> OnResultExecutingImpl { get; set; }
+
+ public void OnResultExecuting(ResultExecutingContext filterContext)
+ {
+ OnResultExecutingImpl(filterContext);
+ }
+
+ public Action<ResultExecutedContext> OnResultExecutedImpl { get; set; }
+
+ public void OnResultExecuted(ResultExecutedContext filterContext)
+ {
+ OnResultExecutedImpl(filterContext);
+ }
+ }
+
+ [KeyedActionFilter(Key = "BaseClass", Order = 0)]
+ [KeyedAuthorizationFilter(Key = "BaseClass", Order = 0)]
+ private class GetMemberChainController : Controller
+ {
+ [KeyedActionFilter(Key = "BaseMethod", Order = 0)]
+ [KeyedAuthorizationFilter(Key = "BaseMethod", Order = 0)]
+ public virtual void SomeVirtual()
+ {
+ }
+ }
+
+ [KeyedActionFilter(Key = "DerivedClass", Order = 1)]
+ private class GetMemberChainDerivedController : GetMemberChainController
+ {
+ }
+
+ [KeyedActionFilter(Key = "SubderivedClass", Order = 2)]
+ private class GetMemberChainSubderivedController : GetMemberChainDerivedController
+ {
+ [KeyedActionFilter(Key = "SubderivedMethod", Order = 2)]
+ public override void SomeVirtual()
+ {
+ }
+ }
+
+ // This controller serves only to test vanilla method invocation - nothing exciting here
+ private class BasicMethodInvokeController : Controller
+ {
+ public ActionResult ReturnsRenderView(object viewItem)
+ {
+ return View("ReturnsRenderView", viewItem);
+ }
+ }
+
+ private class BlankController : Controller
+ {
+ }
+
+ private sealed class CustomResult : ActionResult
+ {
+ public object ReturnValue { get; set; }
+
+ public override void ExecuteResult(ControllerContext context)
+ {
+ throw new NotImplementedException();
+ }
+ }
+
+ private sealed class CustomResultInvokerController : Controller
+ {
+ public object ReturnCustomResult()
+ {
+ return "abc123";
+ }
+ }
+
+ private sealed class CustomResultInvoker : ControllerActionInvokerHelper
+ {
+ protected override ActionResult CreateActionResult(ControllerContext controllerContext, ActionDescriptor actionDescriptor, object actionReturnValue)
+ {
+ return new CustomResult
+ {
+ ReturnValue = actionReturnValue
+ };
+ }
+ }
+
+ private class ContinuationController : Controller
+ {
+ private Func<ActionResult> _continuation;
+
+ public ContinuationController(Func<ActionResult> continuation)
+ {
+ _continuation = continuation;
+ }
+
+ public ActionResult Go()
+ {
+ return _continuation();
+ }
+
+ public static MethodInfo GoMethod
+ {
+ get { return typeof(ContinuationController).GetMethod("Go"); }
+ }
+ }
+
+ private class ContinuationResult : ActionResult
+ {
+ private Action _continuation;
+
+ public ContinuationResult(Action continuation)
+ {
+ _continuation = continuation;
+ }
+
+ public override void ExecuteResult(ControllerContext context)
+ {
+ _continuation();
+ }
+ }
+
+ private class EmptyController : Controller
+ {
+ }
+
+ // This controller serves to test the default action method matching mechanism
+ private class FindMethodController : Controller
+ {
+ public ActionResult ValidActionMethod()
+ {
+ return null;
+ }
+
+ [NonAction]
+ public virtual ActionResult NonActionMethod()
+ {
+ return null;
+ }
+
+ [NonAction]
+ public ActionResult DerivedIsActionMethod()
+ {
+ return null;
+ }
+
+ public ActionResult MethodOverloaded()
+ {
+ return null;
+ }
+
+ public ActionResult MethodOverloaded(string s)
+ {
+ return null;
+ }
+
+ public void WrongReturnType()
+ {
+ }
+
+ protected ActionResult ProtectedMethod()
+ {
+ return null;
+ }
+
+ private ActionResult PrivateMethod()
+ {
+ return null;
+ }
+
+ internal ActionResult InternalMethod()
+ {
+ return null;
+ }
+
+ public override string ToString()
+ {
+ // originally defined on Object
+ return base.ToString();
+ }
+
+ public ActionResult Property
+ {
+ get { return null; }
+ }
+
+#pragma warning disable 0067
+ // CS0067: Event declared but never used. We use reflection to access this member.
+ public event EventHandler Event;
+#pragma warning restore 0067
+ }
+
+ private class DerivedFindMethodController : FindMethodController
+ {
+ public override ActionResult NonActionMethod()
+ {
+ return base.NonActionMethod();
+ }
+
+ // FindActionMethod() should accept this as a valid method since [NonAction] doesn't appear
+ // in its inheritance chain.
+ public new ActionResult DerivedIsActionMethod()
+ {
+ return base.DerivedIsActionMethod();
+ }
+ }
+
+ // Similar to FindMethodController, but tests generics support specifically
+ private class GenericFindMethodController<T> : Controller
+ {
+ public ActionResult ClosedGenericMethod(T t)
+ {
+ return null;
+ }
+
+ public ActionResult OpenGenericMethod<U>(U t)
+ {
+ return null;
+ }
+ }
+
+ // Allows for testing parameter conversions, etc.
+ private class ParameterTestingController : Controller
+ {
+ public ParameterTestingController()
+ {
+ Values = new Dictionary<string, object>();
+ }
+
+ public IDictionary<string, object> Values { get; private set; }
+
+ public void Foo(string foo, string bar, string baz)
+ {
+ Values["foo"] = foo;
+ Values["bar"] = bar;
+ Values["baz"] = baz;
+ }
+
+ public void HasOutParam(out string foo)
+ {
+ foo = null;
+ }
+
+ public void HasRefParam(ref string foo)
+ {
+ }
+
+ public void Parameterless()
+ {
+ }
+
+ public void TakesInt(int id)
+ {
+ Values["id"] = id;
+ }
+
+ public ActionResult TakesNullableInt(int? id)
+ {
+ Values["id"] = id;
+ return null;
+ }
+
+ public void TakesString(string id)
+ {
+ }
+
+ public void TakesDateTime(DateTime id)
+ {
+ }
+ }
+
+ // Provides access to the protected members of ControllerActionInvoker
+ public class ControllerActionInvokerHelper : ControllerActionInvoker
+ {
+ public ControllerActionInvokerHelper()
+ {
+ // set instance caches to prevent modifying global test application state
+ DescriptorCache = new ControllerDescriptorCache();
+ }
+
+ public ControllerActionInvokerHelper(params object[] filters)
+ : base(filters)
+ {
+ // set instance caches to prevent modifying global test application state
+ DescriptorCache = new ControllerDescriptorCache();
+ }
+
+ public virtual ActionResult PublicCreateActionResult(ControllerContext controllerContext, ActionDescriptor actionDescriptor, object actionReturnValue)
+ {
+ return base.CreateActionResult(controllerContext, actionDescriptor, actionReturnValue);
+ }
+
+ protected override ActionResult CreateActionResult(ControllerContext controllerContext, ActionDescriptor actionDescriptor, object actionReturnValue)
+ {
+ return PublicCreateActionResult(controllerContext, actionDescriptor, actionReturnValue);
+ }
+
+ public virtual ActionDescriptor PublicFindAction(ControllerContext controllerContext, ControllerDescriptor controllerDescriptor, string actionName)
+ {
+ return base.FindAction(controllerContext, controllerDescriptor, actionName);
+ }
+
+ protected override ActionDescriptor FindAction(ControllerContext controllerContext, ControllerDescriptor controllerDescriptor, string actionName)
+ {
+ return PublicFindAction(controllerContext, controllerDescriptor, actionName);
+ }
+
+ public virtual ControllerDescriptor PublicGetControllerDescriptor(ControllerContext controllerContext)
+ {
+ return base.GetControllerDescriptor(controllerContext);
+ }
+
+ protected override ControllerDescriptor GetControllerDescriptor(ControllerContext controllerContext)
+ {
+ return PublicGetControllerDescriptor(controllerContext);
+ }
+
+ public virtual FilterInfo PublicGetFilters(ControllerContext controllerContext, ActionDescriptor actionDescriptor)
+ {
+ return base.GetFilters(controllerContext, actionDescriptor);
+ }
+
+ protected override FilterInfo GetFilters(ControllerContext controllerContext, ActionDescriptor actionDescriptor)
+ {
+ return PublicGetFilters(controllerContext, actionDescriptor);
+ }
+
+ public virtual object PublicGetParameterValue(ControllerContext controllerContext, ParameterDescriptor parameterDescriptor)
+ {
+ return base.GetParameterValue(controllerContext, parameterDescriptor);
+ }
+
+ protected override object GetParameterValue(ControllerContext controllerContext, ParameterDescriptor parameterDescriptor)
+ {
+ return PublicGetParameterValue(controllerContext, parameterDescriptor);
+ }
+
+ public virtual IDictionary<string, object> PublicGetParameterValues(ControllerContext controllerContext, ActionDescriptor actionDescriptor)
+ {
+ return base.GetParameterValues(controllerContext, actionDescriptor);
+ }
+
+ protected override IDictionary<string, object> GetParameterValues(ControllerContext controllerContext, ActionDescriptor actionDescriptor)
+ {
+ return PublicGetParameterValues(controllerContext, actionDescriptor);
+ }
+
+ public virtual ActionResult PublicInvokeActionMethod(ControllerContext controllerContext, ActionDescriptor actionDescriptor, IDictionary<string, object> parameters)
+ {
+ return base.InvokeActionMethod(controllerContext, actionDescriptor, parameters);
+ }
+
+ protected override ActionResult InvokeActionMethod(ControllerContext controllerContext, ActionDescriptor actionDescriptor, IDictionary<string, object> parameters)
+ {
+ return PublicInvokeActionMethod(controllerContext, actionDescriptor, parameters);
+ }
+
+ public virtual ActionExecutedContext PublicInvokeActionMethodWithFilters(ControllerContext controllerContext, IList<IActionFilter> filters, ActionDescriptor actionDescriptor, IDictionary<string, object> parameters)
+ {
+ return base.InvokeActionMethodWithFilters(controllerContext, filters, actionDescriptor, parameters);
+ }
+
+ protected override ActionExecutedContext InvokeActionMethodWithFilters(ControllerContext controllerContext, IList<IActionFilter> filters, ActionDescriptor actionDescriptor, IDictionary<string, object> parameters)
+ {
+ return PublicInvokeActionMethodWithFilters(controllerContext, filters, actionDescriptor, parameters);
+ }
+
+ public virtual void PublicInvokeActionResult(ControllerContext controllerContext, ActionResult actionResult)
+ {
+ base.InvokeActionResult(controllerContext, actionResult);
+ }
+
+ protected override void InvokeActionResult(ControllerContext controllerContext, ActionResult actionResult)
+ {
+ PublicInvokeActionResult(controllerContext, actionResult);
+ }
+
+ public virtual ResultExecutedContext PublicInvokeActionResultWithFilters(ControllerContext controllerContext, IList<IResultFilter> filters, ActionResult actionResult)
+ {
+ return base.InvokeActionResultWithFilters(controllerContext, filters, actionResult);
+ }
+
+ protected override ResultExecutedContext InvokeActionResultWithFilters(ControllerContext controllerContext, IList<IResultFilter> filters, ActionResult actionResult)
+ {
+ return PublicInvokeActionResultWithFilters(controllerContext, filters, actionResult);
+ }
+
+ public virtual AuthorizationContext PublicInvokeAuthorizationFilters(ControllerContext controllerContext, IList<IAuthorizationFilter> filters, ActionDescriptor actionDescriptor)
+ {
+ return base.InvokeAuthorizationFilters(controllerContext, filters, actionDescriptor);
+ }
+
+ protected override AuthorizationContext InvokeAuthorizationFilters(ControllerContext controllerContext, IList<IAuthorizationFilter> filters, ActionDescriptor actionDescriptor)
+ {
+ return PublicInvokeAuthorizationFilters(controllerContext, filters, actionDescriptor);
+ }
+
+ public virtual ExceptionContext PublicInvokeExceptionFilters(ControllerContext controllerContext, IList<IExceptionFilter> filters, Exception exception)
+ {
+ return base.InvokeExceptionFilters(controllerContext, filters, exception);
+ }
+
+ protected override ExceptionContext InvokeExceptionFilters(ControllerContext controllerContext, IList<IExceptionFilter> filters, Exception exception)
+ {
+ return PublicInvokeExceptionFilters(controllerContext, filters, exception);
+ }
+ }
+
+ public class AuthorizationFilterHelper : IAuthorizationFilter
+ {
+ private IList<AuthorizationFilterHelper> _callQueue;
+ public ActionResult ShortCircuitResult;
+
+ public AuthorizationFilterHelper(IList<AuthorizationFilterHelper> callQueue)
+ {
+ _callQueue = callQueue;
+ }
+
+ public void OnAuthorization(AuthorizationContext filterContext)
+ {
+ _callQueue.Add(this);
+ if (ShortCircuitResult != null)
+ {
+ filterContext.Result = ShortCircuitResult;
+ }
+ }
+ }
+
+ public class ExceptionFilterHelper : IExceptionFilter
+ {
+ private IList<ExceptionFilterHelper> _callQueue;
+ public bool ShouldHandleException;
+ public ExceptionContext ContextPassed;
+
+ public ExceptionFilterHelper(IList<ExceptionFilterHelper> callQueue)
+ {
+ _callQueue = callQueue;
+ }
+
+ public void OnException(ExceptionContext filterContext)
+ {
+ _callQueue.Add(this);
+ if (ShouldHandleException)
+ {
+ filterContext.ExceptionHandled = true;
+ }
+ ContextPassed = filterContext;
+ }
+ }
+
+ private class CustomConverterController : Controller
+ {
+ public void ParameterWithoutBindAttribute([PredicateReflector] string someParam)
+ {
+ }
+
+ public void ParameterHasBindAttribute([Bind(Include = "foo"), PredicateReflector] string someParam)
+ {
+ }
+
+ public void ParameterHasDefaultValueAttribute([DefaultValue(42)] int foo)
+ {
+ }
+
+ public void ParameterHasFieldPrefix([Bind(Prefix = "bar")] string foo)
+ {
+ }
+
+ public void ParameterHasNullFieldPrefix([Bind(Include = "whatever")] string foo)
+ {
+ }
+
+ public void ParameterHasEmptyFieldPrefix([Bind(Prefix = "")] MySimpleModel foo)
+ {
+ }
+
+ public void ParameterHasNoPrefixAndComplexType(MySimpleModel foo)
+ {
+ }
+
+ public void ParameterHasPrefixAndComplexType([Bind(Prefix = "badprefix")] MySimpleModel foo)
+ {
+ }
+
+ public void ParameterHasNoConverters(string foo)
+ {
+ }
+
+ public void ParameterHasOneConverter([MyCustomConverter] string foo)
+ {
+ }
+
+ public void ParameterHasTwoConverters([MyCustomConverter, MyCustomConverter] string foo)
+ {
+ }
+ }
+
+ public class MySimpleModel
+ {
+ public int IntProp { get; set; }
+ public string StringProp { get; set; }
+ }
+
+ [AttributeUsage(AttributeTargets.Parameter, AllowMultiple = true, Inherited = false)]
+ private class PredicateReflectorAttribute : CustomModelBinderAttribute
+ {
+ public override IModelBinder GetBinder()
+ {
+ return new MyConverter();
+ }
+
+ private class MyConverter : IModelBinder
+ {
+ public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
+ {
+ string s = String.Format("foo={0}&bar={1}", bindingContext.PropertyFilter("foo"), bindingContext.PropertyFilter("bar"));
+ return s;
+ }
+ }
+ }
+
+ [AttributeUsage(AttributeTargets.Parameter, AllowMultiple = true, Inherited = false)]
+ private class MyCustomConverterAttribute : CustomModelBinderAttribute
+ {
+ public override IModelBinder GetBinder()
+ {
+ return new MyConverter();
+ }
+
+ private class MyConverter : IModelBinder
+ {
+ public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
+ {
+ string s = bindingContext.ModelName + "_" + bindingContext.ModelType.Name;
+ return s;
+ }
+ }
+ }
+
+
+ // helper class for making sure that we're performing culture-invariant string conversions
+ public class CultureReflector : IFormattable
+ {
+ string IFormattable.ToString(string format, IFormatProvider formatProvider)
+ {
+ CultureInfo cInfo = (CultureInfo)formatProvider;
+ return cInfo.ThreeLetterISOLanguageName;
+ }
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Test/ControllerBaseTest.cs b/test/System.Web.Mvc.Test/Test/ControllerBaseTest.cs
new file mode 100644
index 00000000..98ac2df7
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Test/ControllerBaseTest.cs
@@ -0,0 +1,234 @@
+using System.Linq;
+using System.Web.Routing;
+using System.Web.TestUtil;
+using Microsoft.Web.UnitTestUtil;
+using Moq;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Mvc.Test
+{
+ public class ControllerBaseTest
+ {
+ [Fact]
+ public void ExecuteCallsControllerBaseExecute()
+ {
+ // Arrange
+ RequestContext requestContext = new RequestContext(HttpContextHelpers.GetMockHttpContext().Object, new RouteData());
+
+ Mock<ControllerBaseHelper> mockController = new Mock<ControllerBaseHelper>() { CallBase = true };
+ mockController.Setup(c => c.PublicInitialize(requestContext)).Verifiable();
+ mockController.Setup(c => c.PublicExecuteCore()).Verifiable();
+ IController controller = mockController.Object;
+
+ // Act
+ controller.Execute(requestContext);
+
+ // Assert
+ mockController.Verify();
+ }
+
+ [Fact]
+ public void ExecuteThrowsIfCalledTwice()
+ {
+ // Arrange
+ EmptyControllerBase controller = new EmptyControllerBase();
+ RequestContext requestContext = new RequestContext(HttpContextHelpers.GetMockHttpContext().Object, new RouteData());
+
+ // Act
+ ((IController)controller).Execute(requestContext); // first call
+ Assert.Throws<InvalidOperationException>(
+ delegate
+ {
+ ((IController)controller).Execute(requestContext); // second call
+ },
+ @"A single instance of controller 'System.Web.Mvc.Test.ControllerBaseTest+EmptyControllerBase' cannot be used to handle multiple requests. If a custom controller factory is in use, make sure that it creates a new instance of the controller for each request.");
+
+ // Assert
+ Assert.Equal(1, controller.NumTimesExecuteCoreCalled);
+ }
+
+ [Fact]
+ public void ExecuteThrowsIfRequestContextIsNull()
+ {
+ // Arrange
+ IController controller = new ControllerBaseHelper();
+
+ // Act & Assert
+ Assert.ThrowsArgumentNull(
+ delegate { controller.Execute(null); }, "requestContext");
+ }
+
+ [Fact]
+ public void ExecuteThrowsIfRequestContextHttpContextIsNull()
+ {
+ //Arrange
+ IController controller = new ControllerBaseHelper();
+
+ //Act & Assert
+ Assert.Throws<ArgumentException>(
+ delegate { controller.Execute(new Mock<RequestContext>().Object); }, "Cannot execute Controller with a null HttpContext.\r\nParameter name: requestContext");
+ }
+
+ [Fact]
+ public void InitializeSetsControllerContext()
+ {
+ // Arrange
+ ControllerBaseHelper helper = new ControllerBaseHelper();
+ RequestContext requestContext = new RequestContext(new Mock<HttpContextBase>().Object, new RouteData());
+
+ // Act
+ helper.PublicInitialize(requestContext);
+
+ // Assert
+ Assert.Same(requestContext.HttpContext, helper.ControllerContext.HttpContext);
+ Assert.Same(requestContext.RouteData, helper.ControllerContext.RouteData);
+ Assert.Same(helper, helper.ControllerContext.Controller);
+ }
+
+ [Fact]
+ public void TempDataProperty()
+ {
+ // Arrange
+ ControllerBase controller = new ControllerBaseHelper();
+
+ // Act & Assert
+ MemberHelper.TestPropertyWithDefaultInstance(controller, "TempData", new TempDataDictionary());
+ }
+
+ [Fact]
+ public void TempDataReturnsParentTempDataWhenInChildRequest()
+ {
+ // Arrange
+ TempDataDictionary tempData = new TempDataDictionary();
+ ViewContext viewContext = new ViewContext { TempData = tempData };
+ RouteData routeData = new RouteData();
+ routeData.DataTokens[ControllerContext.ParentActionViewContextToken] = viewContext;
+ RequestContext requestContext = new RequestContext(new Mock<HttpContextBase>().Object, routeData);
+ ControllerBaseHelper controller = new ControllerBaseHelper();
+ controller.PublicInitialize(requestContext);
+
+ // Act
+ TempDataDictionary result = controller.TempData;
+
+ // Assert
+ Assert.Same(result, tempData);
+ }
+
+ [Fact]
+ public void ValidateRequestProperty()
+ {
+ // Arrange
+ ControllerBase controller = new ControllerBaseHelper();
+
+ // Act & assert
+ MemberHelper.TestBooleanProperty(controller, "ValidateRequest", true /* initialValue */, false /* testDefaultValue */);
+ }
+
+ [Fact]
+ public void ValueProviderProperty()
+ {
+ // Arrange
+ ControllerBase controller = new ControllerBaseHelper();
+ IValueProvider valueProvider = new SimpleValueProvider();
+
+ // Act & assert
+ ValueProviderFactory[] originalFactories = ValueProviderFactories.Factories.ToArray();
+ try
+ {
+ ValueProviderFactories.Factories.Clear();
+ MemberHelper.TestPropertyWithDefaultInstance(controller, "ValueProvider", valueProvider);
+ }
+ finally
+ {
+ foreach (ValueProviderFactory factory in originalFactories)
+ {
+ ValueProviderFactories.Factories.Add(factory);
+ }
+ }
+ }
+
+ [Fact]
+ public void ViewDataProperty()
+ {
+ // Arrange
+ ControllerBase controller = new ControllerBaseHelper();
+
+ // Act & Assert
+ MemberHelper.TestPropertyWithDefaultInstance(controller, "ViewData", new ViewDataDictionary());
+ }
+
+ [Fact]
+ public void ViewBagProperty_ReflectsViewData()
+ {
+ // Arrange
+ ControllerBase controller = new ControllerBaseHelper();
+ controller.ViewData["A"] = 1;
+
+ // Act & Assert
+ Assert.NotNull(controller.ViewBag);
+ Assert.Equal(1, controller.ViewBag.A);
+ }
+
+ [Fact]
+ public void ViewBagProperty_ReflectsNewViewDataInstance()
+ {
+ // Arrange
+ ControllerBase controller = new ControllerBaseHelper();
+ controller.ViewData["A"] = 1;
+ controller.ViewData = new ViewDataDictionary() { { "A", "bar" } };
+
+ // Act & Assert
+ Assert.Equal("bar", controller.ViewBag.A);
+ }
+
+ [Fact]
+ public void ViewBag_PropagatesChangesToViewData()
+ {
+ // Arrange
+ ControllerBase controller = new ControllerBaseHelper();
+ controller.ViewData["A"] = 1;
+
+ // Act
+ controller.ViewBag.A = "foo";
+ controller.ViewBag.B = 2;
+
+ // Assert
+ Assert.Equal("foo", controller.ViewData["A"]);
+ Assert.Equal(2, controller.ViewData["B"]);
+ }
+
+ public class ControllerBaseHelper : ControllerBase
+ {
+ protected override void Initialize(RequestContext requestContext)
+ {
+ PublicInitialize(requestContext);
+ }
+
+ public virtual void PublicInitialize(RequestContext requestContext)
+ {
+ base.Initialize(requestContext);
+ }
+
+ protected override void ExecuteCore()
+ {
+ PublicExecuteCore();
+ }
+
+ public virtual void PublicExecuteCore()
+ {
+ throw new NotImplementedException();
+ }
+ }
+
+ private class EmptyControllerBase : ControllerBase
+ {
+ public int NumTimesExecuteCoreCalled = 0;
+
+ protected override void ExecuteCore()
+ {
+ NumTimesExecuteCoreCalled++;
+ }
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Test/ControllerBuilderTest.cs b/test/System.Web.Mvc.Test/Test/ControllerBuilderTest.cs
new file mode 100644
index 00000000..f3c41197
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Test/ControllerBuilderTest.cs
@@ -0,0 +1,274 @@
+using System.Web.Routing;
+using System.Web.SessionState;
+using Moq;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Mvc.Test
+{
+ public class ControllerBuilderTest
+ {
+ [Fact]
+ public void ControllerBuilderReturnsDefaultControllerBuilderByDefault()
+ {
+ // Arrange
+ ControllerBuilder cb = new ControllerBuilder();
+
+ // Act
+ IControllerFactory cf = cb.GetControllerFactory();
+
+ // Assert
+ Assert.IsType<DefaultControllerFactory>(cf);
+ }
+
+ [Fact]
+ public void CreateControllerWithFactoryThatCannotBeCreatedThrows()
+ {
+ // Arrange
+ ControllerBuilder cb = new ControllerBuilder();
+ cb.SetControllerFactory(typeof(ControllerFactoryThrowsFromConstructor));
+
+ // Act
+ Assert.Throws<InvalidOperationException>(
+ delegate
+ {
+ RequestContext reqContext = new RequestContext(new Mock<HttpContextBase>().Object, new RouteData());
+ reqContext.RouteData.Values["controller"] = "foo";
+ MvcHandlerWithNoVersionHeader handler = new MvcHandlerWithNoVersionHeader(reqContext)
+ {
+ ControllerBuilder = cb
+ };
+ handler.ProcessRequest(reqContext.HttpContext);
+ },
+ "An error occurred when trying to create the IControllerFactory 'System.Web.Mvc.Test.ControllerBuilderTest+ControllerFactoryThrowsFromConstructor'. Make sure that the controller factory has a public parameterless constructor.");
+ }
+
+ [Fact]
+ public void CreateControllerWithFactoryThatReturnsNullThrows()
+ {
+ // Arrange
+ ControllerBuilder cb = new ControllerBuilder();
+ cb.SetControllerFactory(typeof(ControllerFactoryReturnsNull));
+
+ // Act
+ Assert.Throws<InvalidOperationException>(
+ delegate
+ {
+ RequestContext reqContext = new RequestContext(new Mock<HttpContextBase>().Object, new RouteData());
+ reqContext.RouteData.Values["controller"] = "boo";
+ MvcHandlerWithNoVersionHeader handler = new MvcHandlerWithNoVersionHeader(reqContext)
+ {
+ ControllerBuilder = cb
+ };
+ handler.ProcessRequest(reqContext.HttpContext);
+ },
+ "The IControllerFactory 'System.Web.Mvc.Test.ControllerBuilderTest+ControllerFactoryReturnsNull' did not return a controller for the name 'boo'.");
+ }
+
+ [Fact]
+ public void CreateControllerWithFactoryThatThrowsDoesNothingSpecial()
+ {
+ // Arrange
+ ControllerBuilder cb = new ControllerBuilder();
+ cb.SetControllerFactory(typeof(ControllerFactoryThrows));
+
+ // Act
+ Assert.Throws<Exception>(
+ delegate
+ {
+ RequestContext reqContext = new RequestContext(new Mock<HttpContextBase>().Object, new RouteData());
+ reqContext.RouteData.Values["controller"] = "foo";
+ MvcHandlerWithNoVersionHeader handler = new MvcHandlerWithNoVersionHeader(reqContext)
+ {
+ ControllerBuilder = cb
+ };
+ handler.ProcessRequest(reqContext.HttpContext);
+ },
+ "ControllerFactoryThrows");
+ }
+
+ [Fact]
+ public void CreateControllerWithFactoryInstanceReturnsInstance()
+ {
+ // Arrange
+ ControllerBuilder cb = new ControllerBuilder();
+ DefaultControllerFactory factory = new DefaultControllerFactory();
+ cb.SetControllerFactory(factory);
+
+ // Act
+ IControllerFactory cf = cb.GetControllerFactory();
+
+ // Assert
+ Assert.Same(factory, cf);
+ }
+
+ [Fact]
+ public void CreateControllerWithFactoryTypeReturnsValidType()
+ {
+ // Arrange
+ ControllerBuilder cb = new ControllerBuilder();
+ cb.SetControllerFactory(typeof(MockControllerFactory));
+
+ // Act
+ IControllerFactory cf = cb.GetControllerFactory();
+
+ // Assert
+ Assert.IsType<MockControllerFactory>(cf);
+ }
+
+ [Fact]
+ public void SetControllerFactoryInstanceWithNullThrows()
+ {
+ ControllerBuilder cb = new ControllerBuilder();
+ Assert.ThrowsArgumentNull(
+ delegate { cb.SetControllerFactory((IControllerFactory)null); },
+ "controllerFactory");
+ }
+
+ [Fact]
+ public void SetControllerFactoryTypeWithNullThrows()
+ {
+ ControllerBuilder cb = new ControllerBuilder();
+ Assert.ThrowsArgumentNull(
+ delegate { cb.SetControllerFactory((Type)null); },
+ "controllerFactoryType");
+ }
+
+ [Fact]
+ public void SetControllerFactoryTypeWithNonFactoryTypeThrows()
+ {
+ ControllerBuilder cb = new ControllerBuilder();
+ Assert.Throws<ArgumentException>(
+ delegate { cb.SetControllerFactory(typeof(int)); },
+ "The controller factory type 'System.Int32' must implement the IControllerFactory interface.\r\nParameter name: controllerFactoryType");
+ }
+
+ [Fact]
+ public void DefaultControllerFactoryIsDefaultControllerFactory()
+ {
+ // Arrange
+ ControllerBuilder builder = new ControllerBuilder();
+
+ // Act
+ IControllerFactory returnedControllerFactory = builder.GetControllerFactory();
+
+ //Assert
+ Assert.Equal(typeof(DefaultControllerFactory), returnedControllerFactory.GetType());
+ }
+
+ [Fact]
+ public void SettingControllerFactoryReturnsSetFactory()
+ {
+ // Arrange
+ ControllerBuilder builder = new ControllerBuilder();
+ Mock<IControllerFactory> setFactory = new Mock<IControllerFactory>();
+
+ // Act
+ builder.SetControllerFactory(setFactory.Object);
+
+ // Assert
+ Assert.Same(setFactory.Object, builder.GetControllerFactory());
+ }
+
+ [Fact]
+ public void ControllerBuilderGetControllerFactoryDelegatesToResolver()
+ {
+ //Arrange
+ Mock<IControllerFactory> factory = new Mock<IControllerFactory>();
+ Resolver<IControllerFactory> resolver = new Resolver<IControllerFactory> { Current = factory.Object };
+ ControllerBuilder builder = new ControllerBuilder(resolver);
+
+ //Act
+ IControllerFactory result = builder.GetControllerFactory();
+
+ //Assert
+ Assert.Same(factory.Object, result);
+ }
+
+ public class ControllerFactoryThrowsFromConstructor : IControllerFactory
+ {
+ public ControllerFactoryThrowsFromConstructor()
+ {
+ throw new Exception("ControllerFactoryThrowsFromConstructor");
+ }
+
+ public IController CreateController(RequestContext context, string controllerName)
+ {
+ return null;
+ }
+
+ public SessionStateBehavior GetControllerSessionBehavior(RequestContext requestContext, string controllerName)
+ {
+ return SessionStateBehavior.Default;
+ }
+
+ public void ReleaseController(IController controller)
+ {
+ }
+ }
+
+ public class ControllerFactoryReturnsNull : IControllerFactory
+ {
+ public IController CreateController(RequestContext context, string controllerName)
+ {
+ return null;
+ }
+
+ public SessionStateBehavior GetControllerSessionBehavior(RequestContext requestContext, string controllerName)
+ {
+ return SessionStateBehavior.Default;
+ }
+
+ public void ReleaseController(IController controller)
+ {
+ }
+ }
+
+ public class ControllerFactoryThrows : IControllerFactory
+ {
+ public IController CreateController(RequestContext context, string controllerName)
+ {
+ throw new Exception("ControllerFactoryThrows");
+ }
+
+ public SessionStateBehavior GetControllerSessionBehavior(RequestContext requestContext, string controllerName)
+ {
+ return SessionStateBehavior.Default;
+ }
+
+ public void ReleaseController(IController controller)
+ {
+ }
+ }
+
+ public class MockControllerFactory : IControllerFactory
+ {
+ public IController CreateController(RequestContext context, string controllerName)
+ {
+ throw new NotImplementedException();
+ }
+
+ public SessionStateBehavior GetControllerSessionBehavior(RequestContext requestContext, string controllerName)
+ {
+ return SessionStateBehavior.Default;
+ }
+
+ public void ReleaseController(IController controller)
+ {
+ }
+ }
+
+ private sealed class MvcHandlerWithNoVersionHeader : MvcHandler
+ {
+ public MvcHandlerWithNoVersionHeader(RequestContext requestContext)
+ : base(requestContext)
+ {
+ }
+
+ protected internal override void AddVersionHeader(HttpContextBase httpContext)
+ {
+ // Don't try to set the version header for the unit tests
+ }
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Test/ControllerContextTest.cs b/test/System.Web.Mvc.Test/Test/ControllerContextTest.cs
new file mode 100644
index 00000000..5ce8ea0c
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Test/ControllerContextTest.cs
@@ -0,0 +1,296 @@
+using System.Collections;
+using System.Web.Routing;
+using System.Web.TestUtil;
+using System.Web.WebPages;
+using Moq;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Mvc.Test
+{
+ public class ControllerContextTest
+ {
+ [Fact]
+ public void ConstructorThrowsIfControllerIsNull()
+ {
+ // Arrange
+ RequestContext requestContext = new RequestContext(new Mock<HttpContextBase>().Object, new RouteData());
+ Controller controller = null;
+
+ // Act & assert
+ Assert.ThrowsArgumentNull(
+ delegate { new ControllerContext(requestContext, controller); }, "controller");
+ }
+
+ [Fact]
+ public void ConstructorThrowsIfRequestContextIsNull()
+ {
+ // Arrange
+ RequestContext requestContext = null;
+ Controller controller = new Mock<Controller>().Object;
+
+ // Act & assert
+ Assert.ThrowsArgumentNull(
+ delegate { new ControllerContext(requestContext, controller); }, "requestContext");
+ }
+
+ [Fact]
+ public void ConstructorWithHttpContextAndRouteData()
+ {
+ // Arrange
+ HttpContextBase httpContext = new Mock<HttpContextBase>().Object;
+ RouteData routeData = new RouteData();
+ Controller controller = new Mock<Controller>().Object;
+
+ // Act
+ ControllerContext controllerContext = new ControllerContext(httpContext, routeData, controller);
+
+ // Assert
+ Assert.Equal(httpContext, controllerContext.HttpContext);
+ Assert.Equal(routeData, controllerContext.RouteData);
+ Assert.Equal(controller, controllerContext.Controller);
+ }
+
+ [Fact]
+ public void ControllerProperty()
+ {
+ // Arrange
+ HttpContextBase httpContext = new Mock<HttpContextBase>().Object;
+ RouteData routeData = new RouteData();
+ Controller controller = new Mock<Controller>().Object;
+
+ // Act
+ ControllerContext controllerContext = new ControllerContext(httpContext, routeData, controller);
+
+ // Assert
+ Assert.Equal(controller, controllerContext.Controller);
+ }
+
+ [Fact]
+ public void CopyConstructorSetsProperties()
+ {
+ // Arrange
+ Mock<HttpContextBase> httpContext = new Mock<HttpContextBase>();
+ httpContext.Setup(c => c.Items).Returns(new Hashtable());
+
+ RequestContext requestContext = new RequestContext(httpContext.Object, new RouteData());
+ Controller controller = new Mock<Controller>().Object;
+ var displayMode = new DefaultDisplayMode("test");
+
+ ControllerContext innerControllerContext = new ControllerContext(requestContext, controller);
+ innerControllerContext.DisplayMode = displayMode;
+
+ // Act
+ ControllerContext outerControllerContext = new SubclassedControllerContext(innerControllerContext);
+
+ // Assert
+ Assert.Equal(requestContext, outerControllerContext.RequestContext);
+ Assert.Equal(controller, outerControllerContext.Controller);
+
+ // We don't actually set DisplayMode but verify it is identical to the inner controller context.
+ Assert.Equal(displayMode, outerControllerContext.DisplayMode);
+ }
+
+ [Fact]
+ public void DisplayModeDelegatesToHttpContext()
+ {
+ // Arrange
+ IDisplayMode testDisplayMode = new DefaultDisplayMode("test");
+ Mock<HttpContextBase> httpContext = new Mock<HttpContextBase>();
+ httpContext.Setup(c => c.Items).Returns(new Hashtable());
+ Controller controller = new Mock<Controller>().Object;
+ ControllerContext controllerContext = new ControllerContext(httpContext.Object, new RouteData(), controller);
+ ControllerContext controllerContextWithIdenticalContext = new ControllerContext(httpContext.Object, new RouteData(), controller);
+
+ // Act
+ controllerContext.DisplayMode = testDisplayMode;
+
+ // Assert
+ Assert.Same(testDisplayMode, controllerContext.DisplayMode);
+ Assert.Same(testDisplayMode, controllerContextWithIdenticalContext.DisplayMode);
+ }
+
+ [Fact]
+ public void CopyConstructorThrowsIfControllerContextIsNull()
+ {
+ // Act & assert
+ Assert.ThrowsArgumentNull(
+ delegate { new SubclassedControllerContext(null); }, "controllerContext");
+ }
+
+ [Fact]
+ public void HttpContextPropertyGetSetBehavior()
+ {
+ // Arrange
+ HttpContextBase httpContext = new Mock<HttpContextBase>().Object;
+ ControllerContext controllerContext = new ControllerContext();
+
+ // Act & assert
+ MemberHelper.TestPropertyValue(controllerContext, "HttpContext", httpContext);
+ }
+
+ [Fact]
+ public void HttpContextPropertyReturnsEmptyHttpContextIfRequestContextNotPresent()
+ {
+ // Arrange
+ ControllerContext controllerContext = new ControllerContext();
+
+ // Act
+ HttpContextBase httpContext = controllerContext.HttpContext;
+ HttpContextBase httpContext2 = controllerContext.HttpContext;
+
+ // Assert
+ Assert.NotNull(httpContext);
+ Assert.Equal(httpContext, httpContext2);
+ }
+
+ [Fact]
+ public void HttpContextPropertyReturnsRequestContextHttpContextIfPresent()
+ {
+ // Arrange
+ HttpContextBase httpContext = new Mock<HttpContextBase>().Object;
+ RouteData routeData = new RouteData();
+ RequestContext requestContext = new RequestContext(httpContext, routeData);
+ Controller controller = new Mock<Controller>().Object;
+
+ // Act
+ ControllerContext controllerContext = new ControllerContext(requestContext, controller);
+
+ // Assert
+ Assert.Equal(httpContext, controllerContext.HttpContext);
+ }
+
+ [Fact]
+ public void RequestContextPropertyCreatesDummyHttpContextAndRouteDataIfNecessary()
+ {
+ // Arrange
+ Mock<ControllerContext> mockControllerContext = new Mock<ControllerContext>();
+ mockControllerContext.Setup(c => c.HttpContext).Returns((HttpContextBase)null);
+ mockControllerContext.Setup(c => c.RouteData).Returns((RouteData)null);
+ ControllerContext controllerContext = mockControllerContext.Object;
+
+ // Act
+ RequestContext requestContext = controllerContext.RequestContext;
+ RequestContext requestContext2 = controllerContext.RequestContext;
+
+ // Assert
+ Assert.Equal(requestContext, requestContext2);
+ Assert.NotNull(requestContext.HttpContext);
+ Assert.NotNull(requestContext.RouteData);
+ }
+
+ [Fact]
+ public void RequestContextPropertyUsesExistingHttpContextAndRouteData()
+ {
+ // Arrange
+ HttpContextBase httpContext = new Mock<HttpContextBase>().Object;
+ RouteData routeData = new RouteData();
+
+ Mock<ControllerContext> mockControllerContext = new Mock<ControllerContext>();
+ mockControllerContext.Setup(c => c.HttpContext).Returns(httpContext);
+ mockControllerContext.Setup(c => c.RouteData).Returns(routeData);
+ ControllerContext controllerContext = mockControllerContext.Object;
+
+ // Act
+ RequestContext requestContext = controllerContext.RequestContext;
+ RequestContext requestContext2 = controllerContext.RequestContext;
+
+ // Assert
+ Assert.Equal(requestContext, requestContext2);
+ Assert.Equal(httpContext, requestContext.HttpContext);
+ Assert.Equal(routeData, requestContext.RouteData);
+ }
+
+ [Fact]
+ public void RouteDataPropertyGetSetBehavior()
+ {
+ // Arrange
+ RouteData routeData = new RouteData();
+ ControllerContext controllerContext = new ControllerContext();
+
+ // Act & assert
+ MemberHelper.TestPropertyValue(controllerContext, "RouteData", routeData);
+ }
+
+ [Fact]
+ public void RouteDataPropertyReturnsEmptyRouteDataIfRequestContextNotPresent()
+ {
+ // Arrange
+ ControllerContext controllerContext = new ControllerContext();
+
+ // Act
+ RouteData routeData = controllerContext.RouteData;
+ RouteData routeData2 = controllerContext.RouteData;
+
+ // Assert
+ Assert.Equal(routeData, routeData2);
+ Assert.Empty(routeData.Values);
+ }
+
+ [Fact]
+ public void RouteDataPropertyReturnsRequestContextRouteDataIfPresent()
+ {
+ // Arrange
+ HttpContextBase httpContext = new Mock<HttpContextBase>().Object;
+ RouteData routeData = new RouteData();
+ RequestContext requestContext = new RequestContext(httpContext, routeData);
+ Controller controller = new Mock<Controller>().Object;
+
+ // Act
+ ControllerContext controllerContext = new ControllerContext(requestContext, controller);
+
+ // Assert
+ Assert.Equal(routeData, controllerContext.RouteData);
+ }
+
+ [Fact]
+ public void IsChildActionReturnsFalseByDefault()
+ {
+ // Arrange
+ HttpContextBase httpContext = new Mock<HttpContextBase>().Object;
+ RouteData routeData = new RouteData();
+ RequestContext requestContext = new RequestContext(httpContext, routeData);
+ Controller controller = new Mock<Controller>().Object;
+ ControllerContext controllerContext = new ControllerContext(requestContext, controller);
+
+ // Act
+ bool result = controllerContext.IsChildAction;
+
+ // Assert
+ Assert.False(result);
+ }
+
+ [Fact]
+ public void IsChildActionReturnsTrueWhenRouteDataTokenIsSet()
+ {
+ // Arrange
+ HttpContextBase httpContext = new Mock<HttpContextBase>().Object;
+ ViewContext viewContext = new ViewContext();
+ RouteData routeData = new RouteData();
+ routeData.DataTokens[ControllerContext.ParentActionViewContextToken] = viewContext;
+ RequestContext requestContext = new RequestContext(httpContext, routeData);
+ Controller controller = new Mock<Controller>().Object;
+ ControllerContext controllerContext = new ControllerContext(requestContext, controller);
+
+ // Act
+ bool result = controllerContext.IsChildAction;
+
+ // Assert
+ Assert.True(result);
+ Assert.Same(viewContext, controllerContext.ParentActionViewContext);
+ }
+
+ public static ControllerContext CreateEmptyContext()
+ {
+ return new ControllerContext(new Mock<HttpContextBase>().Object, new RouteData(), new Mock<Controller>().Object);
+ }
+
+ private class SubclassedControllerContext : ControllerContext
+ {
+ public SubclassedControllerContext(ControllerContext controllerContext)
+ : base(controllerContext)
+ {
+ }
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Test/ControllerDescriptorCacheTest.cs b/test/System.Web.Mvc.Test/Test/ControllerDescriptorCacheTest.cs
new file mode 100644
index 00000000..6f700a20
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Test/ControllerDescriptorCacheTest.cs
@@ -0,0 +1,23 @@
+using Xunit;
+
+namespace System.Web.Mvc.Test
+{
+ public class ControllerDescriptorCacheTest
+ {
+ [Fact]
+ public void GetDescriptor()
+ {
+ // Arrange
+ Type controllerType = typeof(object);
+ ControllerDescriptorCache cache = new ControllerDescriptorCache();
+
+ // Act
+ ControllerDescriptor descriptor1 = cache.GetDescriptor(controllerType, () => new ReflectedControllerDescriptor(controllerType));
+ ControllerDescriptor descriptor2 = cache.GetDescriptor(controllerType, () => new ReflectedControllerDescriptor(controllerType));
+
+ // Assert
+ Assert.Same(controllerType, descriptor1.ControllerType);
+ Assert.Same(descriptor1, descriptor2);
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Test/ControllerDescriptorTest.cs b/test/System.Web.Mvc.Test/Test/ControllerDescriptorTest.cs
new file mode 100644
index 00000000..334ffcbe
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Test/ControllerDescriptorTest.cs
@@ -0,0 +1,129 @@
+using System.Linq;
+using Moq;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Mvc.Test
+{
+ public class ControllerDescriptorTest
+ {
+ [Fact]
+ public void ControllerNamePropertyReturnsControllerTypeName()
+ {
+ // Arrange
+ ControllerDescriptor cd = GetControllerDescriptor(typeof(object));
+
+ // Act
+ string name = cd.ControllerName;
+
+ // Assert
+ Assert.Equal("Object", name);
+ }
+
+ [Fact]
+ public void ControllerNamePropertyReturnsControllerTypeNameWithoutControllerSuffix()
+ {
+ // Arrange
+ Mock<Type> mockType = new Mock<Type>();
+ mockType.Setup(t => t.Name).Returns("somecontroller");
+ ControllerDescriptor cd = GetControllerDescriptor(mockType.Object);
+
+ // Act
+ string name = cd.ControllerName;
+
+ // Assert
+ Assert.Equal("some", name);
+ }
+
+ [Fact]
+ public void GetCustomAttributesReturnsEmptyArrayOfAttributeType()
+ {
+ // Arrange
+ ControllerDescriptor cd = GetControllerDescriptor();
+
+ // Act
+ ObsoleteAttribute[] attrs = (ObsoleteAttribute[])cd.GetCustomAttributes(typeof(ObsoleteAttribute), true);
+
+ // Assert
+ Assert.Empty(attrs);
+ }
+
+ [Fact]
+ public void GetCustomAttributesThrowsIfAttributeTypeIsNull()
+ {
+ // Arrange
+ ControllerDescriptor cd = GetControllerDescriptor();
+
+ // Act & assert
+ Assert.ThrowsArgumentNull(
+ delegate { cd.GetCustomAttributes(null /* attributeType */, true); }, "attributeType");
+ }
+
+ [Fact]
+ public void GetCustomAttributesWithoutAttributeTypeCallsGetCustomAttributesWithAttributeType()
+ {
+ // Arrange
+ object[] expected = new object[0];
+ Mock<ControllerDescriptor> mockDescriptor = new Mock<ControllerDescriptor>() { CallBase = true };
+ mockDescriptor.Setup(d => d.GetCustomAttributes(typeof(object), true)).Returns(expected);
+ ControllerDescriptor cd = mockDescriptor.Object;
+
+ // Act
+ object[] returned = cd.GetCustomAttributes(true /* inherit */);
+
+ // Assert
+ Assert.Same(expected, returned);
+ }
+
+ [Fact]
+ public void GetFilterAttributes_CallsGetCustomAttributes()
+ {
+ // Arrange
+ var mockDescriptor = new Mock<ControllerDescriptor>() { CallBase = true };
+ mockDescriptor.Setup(d => d.GetCustomAttributes(typeof(FilterAttribute), true)).Returns(new object[] { new Mock<FilterAttribute>().Object }).Verifiable();
+
+ // Act
+ var result = mockDescriptor.Object.GetFilterAttributes(true).ToList();
+
+ // Assert
+ mockDescriptor.Verify();
+ Assert.Single(result);
+ }
+
+ [Fact]
+ public void IsDefinedReturnsFalse()
+ {
+ // Arrange
+ ControllerDescriptor cd = GetControllerDescriptor();
+
+ // Act
+ bool isDefined = cd.IsDefined(typeof(object), true);
+
+ // Assert
+ Assert.False(isDefined);
+ }
+
+ [Fact]
+ public void IsDefinedThrowsIfAttributeTypeIsNull()
+ {
+ // Arrange
+ ControllerDescriptor cd = GetControllerDescriptor();
+
+ // Act & assert
+ Assert.ThrowsArgumentNull(
+ delegate { cd.IsDefined(null /* attributeType */, true); }, "attributeType");
+ }
+
+ private static ControllerDescriptor GetControllerDescriptor()
+ {
+ return GetControllerDescriptor(null);
+ }
+
+ private static ControllerDescriptor GetControllerDescriptor(Type controllerType)
+ {
+ Mock<ControllerDescriptor> mockDescriptor = new Mock<ControllerDescriptor>() { CallBase = true };
+ mockDescriptor.Setup(d => d.ControllerType).Returns(controllerType);
+ return mockDescriptor.Object;
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Test/ControllerInstanceFilterProviderTest.cs b/test/System.Web.Mvc.Test/Test/ControllerInstanceFilterProviderTest.cs
new file mode 100644
index 00000000..81f9685c
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Test/ControllerInstanceFilterProviderTest.cs
@@ -0,0 +1,44 @@
+using System.Collections.Generic;
+using System.Linq;
+using Moq;
+using Xunit;
+
+namespace System.Web.Mvc.Test
+{
+ public class ControllerInstanceFilterProviderTest
+ {
+ [Fact]
+ public void GetFiltersWithNullControllerReturnsEmptyCollection()
+ {
+ // Arrange
+ var context = new ControllerContext();
+ var descriptor = new Mock<ActionDescriptor>().Object;
+ var provider = new ControllerInstanceFilterProvider();
+
+ // Act
+ IEnumerable<Filter> result = provider.GetFilters(context, descriptor);
+
+ // Assert
+ Assert.Empty(result);
+ }
+
+ [Fact]
+ public void GetFiltersWithControllerReturnsWrappedController()
+ {
+ // Arrange
+ var controller = new Mock<ControllerBase>().Object;
+ var context = new ControllerContext { Controller = controller };
+ var descriptor = new Mock<ActionDescriptor>().Object;
+ var provider = new ControllerInstanceFilterProvider();
+
+ // Act
+ IEnumerable<Filter> result = provider.GetFilters(context, descriptor);
+
+ // Assert
+ Filter filter = result.Single();
+ Assert.Same(controller, filter.Instance);
+ Assert.Equal(Int32.MinValue, filter.Order);
+ Assert.Equal(FilterScope.First, filter.Scope);
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Test/ControllerTest.cs b/test/System.Web.Mvc.Test/Test/ControllerTest.cs
new file mode 100644
index 00000000..cc748821
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Test/ControllerTest.cs
@@ -0,0 +1,2047 @@
+using System.Collections;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.ComponentModel.DataAnnotations;
+using System.IO;
+using System.Reflection;
+using System.Security.Principal;
+using System.Text;
+using System.Web.Mvc.Async;
+using System.Web.Profile;
+using System.Web.Routing;
+using System.Web.TestUtil;
+using Microsoft.Web.UnitTestUtil;
+using Moq;
+using Moq.Protected;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Mvc.Test
+{
+ public class ControllerTest
+ {
+ [Fact]
+ public void ActionInvokerProperty()
+ {
+ // Arrange
+ Controller controller = new EmptyController();
+
+ // Act & Assert
+ MemberHelper.TestPropertyWithDefaultInstance(controller, "ActionInvoker", new ControllerActionInvoker());
+ }
+
+ [Fact]
+ public void ContentWithContentString()
+ {
+ // Arrange
+ Controller controller = new EmptyController();
+ string content = "Some content";
+
+ // Act
+ ContentResult result = controller.Content(content);
+
+ // Assert
+ Assert.Equal(content, result.Content);
+ }
+
+ [Fact]
+ public void ContentWithContentStringAndContentType()
+ {
+ // Arrange
+ Controller controller = new EmptyController();
+ string content = "Some content";
+ string contentType = "Some content type";
+
+ // Act
+ ContentResult result = controller.Content(content, contentType);
+
+ // Assert
+ Assert.Equal(content, result.Content);
+ Assert.Equal(contentType, result.ContentType);
+ }
+
+ [Fact]
+ public void ContentWithContentStringAndContentTypeAndEncoding()
+ {
+ // Arrange
+ Controller controller = new EmptyController();
+ string content = "Some content";
+ string contentType = "Some content type";
+ Encoding contentEncoding = Encoding.UTF8;
+
+ // Act
+ ContentResult result = controller.Content(content, contentType, contentEncoding);
+
+ // Assert
+ Assert.Equal(content, result.Content);
+ Assert.Equal(contentType, result.ContentType);
+ Assert.Same(contentEncoding, result.ContentEncoding);
+ }
+
+ [Fact]
+ public void ContextProperty()
+ {
+ var controller = new EmptyController();
+ MemberHelper.TestPropertyValue(controller, "ControllerContext", new Mock<ControllerContext>().Object);
+ }
+
+ [Fact]
+ public void HttpContextProperty()
+ {
+ var c = new EmptyController();
+ Assert.Null(c.HttpContext);
+
+ Mock<HttpContextBase> mockHttpContext = new Mock<HttpContextBase>();
+
+ Mock<ControllerContext> mockControllerContext = new Mock<ControllerContext>();
+ mockControllerContext.Setup(cc => cc.Controller).Returns(c);
+ mockControllerContext.Setup(cc => cc.HttpContext).Returns(mockHttpContext.Object);
+
+ c.ControllerContext = mockControllerContext.Object;
+ Assert.Equal(mockHttpContext.Object, c.HttpContext);
+ }
+
+ [Fact]
+ public void HttpNotFound()
+ {
+ // Arrange
+ var c = new EmptyController();
+
+ // Act
+ HttpNotFoundResult result = c.HttpNotFound();
+
+ // Assert
+ Assert.NotNull(result);
+ Assert.Null(result.StatusDescription);
+ Assert.Equal(404, result.StatusCode);
+ }
+
+ [Fact]
+ public void HttpNotFoundWithNullStatusDescription()
+ {
+ // Arrange
+ var c = new EmptyController();
+
+ // Act
+ HttpNotFoundResult result = c.HttpNotFound(statusDescription: null);
+
+ // Assert
+ Assert.NotNull(result);
+ Assert.Null(result.StatusDescription);
+ Assert.Equal(404, result.StatusCode);
+ }
+
+ [Fact]
+ public void HttpNotFoundWithStatusDescription()
+ {
+ // Arrange
+ var c = new EmptyController();
+
+ // Act
+ HttpNotFoundResult result = c.HttpNotFound(statusDescription: "I lost it");
+
+ // Assert
+ Assert.NotNull(result);
+ Assert.Equal("I lost it", result.StatusDescription);
+ Assert.Equal(404, result.StatusCode);
+ }
+
+ [Fact]
+ public void ModelStateProperty()
+ {
+ // Arrange
+ Controller controller = new EmptyController();
+
+ // Act & assert
+ Assert.Same(controller.ViewData.ModelState, controller.ModelState);
+ }
+
+ [Fact]
+ public void ProfileProperty()
+ {
+ var c = new EmptyController();
+ Assert.Null(c.Profile);
+
+ Mock<ProfileBase> mockProfile = new Mock<ProfileBase>();
+
+ Mock<ControllerContext> mockControllerContext = new Mock<ControllerContext>();
+ mockControllerContext.Setup(cc => cc.Controller).Returns(c);
+ mockControllerContext.Setup(cc => cc.HttpContext.Profile).Returns(mockProfile.Object);
+
+ c.ControllerContext = mockControllerContext.Object;
+ Assert.Equal(mockProfile.Object, c.Profile);
+ }
+
+ [Fact]
+ public void RequestProperty()
+ {
+ var c = new EmptyController();
+ Assert.Null(c.Request);
+
+ Mock<HttpRequestBase> mockHttpRequest = new Mock<HttpRequestBase>();
+
+ Mock<ControllerContext> mockControllerContext = new Mock<ControllerContext>();
+ mockControllerContext.Setup(cc => cc.Controller).Returns(c);
+ mockControllerContext.Setup(cc => cc.HttpContext.Request).Returns(mockHttpRequest.Object);
+
+ c.ControllerContext = mockControllerContext.Object;
+ Assert.Equal(mockHttpRequest.Object, c.Request);
+ }
+
+ [Fact]
+ public void ResponseProperty()
+ {
+ var c = new EmptyController();
+ Assert.Null(c.Request);
+
+ Mock<HttpResponseBase> mockHttpResponse = new Mock<HttpResponseBase>();
+
+ Mock<ControllerContext> mockControllerContext = new Mock<ControllerContext>();
+ mockControllerContext.Setup(cc => cc.Controller).Returns(c);
+ mockControllerContext.Setup(cc => cc.HttpContext.Response).Returns(mockHttpResponse.Object);
+
+ c.ControllerContext = mockControllerContext.Object;
+ Assert.Equal(mockHttpResponse.Object, c.Response);
+ }
+
+ [Fact]
+ public void ServerProperty()
+ {
+ var c = new EmptyController();
+ Assert.Null(c.Request);
+
+ Mock<HttpServerUtilityBase> mockServerUtility = new Mock<HttpServerUtilityBase>();
+
+ Mock<ControllerContext> mockControllerContext = new Mock<ControllerContext>();
+ mockControllerContext.Setup(cc => cc.Controller).Returns(c);
+ mockControllerContext.Setup(cc => cc.HttpContext.Server).Returns(mockServerUtility.Object);
+
+ c.ControllerContext = mockControllerContext.Object;
+ Assert.Equal(mockServerUtility.Object, c.Server);
+ }
+
+ [Fact]
+ public void SessionProperty()
+ {
+ var c = new EmptyController();
+ Assert.Null(c.Request);
+
+ Mock<HttpSessionStateBase> mockSessionState = new Mock<HttpSessionStateBase>();
+
+ Mock<ControllerContext> mockControllerContext = new Mock<ControllerContext>();
+ mockControllerContext.Setup(cc => cc.Controller).Returns(c);
+ mockControllerContext.Setup(cc => cc.HttpContext.Session).Returns(mockSessionState.Object);
+
+ c.ControllerContext = mockControllerContext.Object;
+ Assert.Same(mockSessionState.Object, c.Session);
+ }
+
+ [Fact]
+ public void UrlProperty()
+ {
+ // Arrange
+ EmptyController controller = new EmptyController();
+ RequestContext requestContext = new RequestContext(new Mock<HttpContextBase>().Object, new RouteData());
+
+ // Act
+ controller.PublicInitialize(requestContext);
+
+ // Assert
+ Assert.NotNull(controller.Url);
+ }
+
+ [Fact]
+ public void UserProperty()
+ {
+ var c = new EmptyController();
+ Assert.Null(c.Request);
+
+ Mock<IPrincipal> mockUser = new Mock<IPrincipal>();
+
+ Mock<ControllerContext> mockControllerContext = new Mock<ControllerContext>();
+ mockControllerContext.Setup(cc => cc.Controller).Returns(c);
+ mockControllerContext.Setup(cc => cc.HttpContext.User).Returns(mockUser.Object);
+
+ c.ControllerContext = mockControllerContext.Object;
+ Assert.Equal(mockUser.Object, c.User);
+ }
+
+ [Fact]
+ public void RouteDataProperty()
+ {
+ var c = new EmptyController();
+ Assert.Null(c.Request);
+
+ RouteData rd = new RouteData();
+
+ Mock<ControllerContext> mockControllerContext = new Mock<ControllerContext>();
+ mockControllerContext.Setup(cc => cc.Controller).Returns(c);
+ mockControllerContext.Setup(cc => cc.RouteData).Returns(rd);
+
+ c.ControllerContext = mockControllerContext.Object;
+ Assert.Equal(rd, c.RouteData);
+ }
+
+ [Fact]
+ public void ControllerMethodsDoNotHaveNonActionAttribute()
+ {
+ var methods = typeof(Controller).GetMethods(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
+ foreach (var method in methods)
+ {
+ var attrs = method.GetCustomAttributes(typeof(NonActionAttribute), true /* inherit */);
+ Assert.True(attrs.Length == 0, "Methods on the Controller class should not be marked [NonAction]: " + method);
+ }
+ }
+
+ [Fact]
+ public void DisposeCallsProtectedDisposingMethod()
+ {
+ // Arrange
+ Mock<Controller> mockController = new Mock<Controller>();
+ mockController.Protected().Setup("Dispose", true).Verifiable();
+ Controller controller = mockController.Object;
+
+ // Act
+ controller.Dispose();
+
+ // Assert
+ mockController.Verify();
+ }
+
+ [Fact]
+ public void ExecuteWithUnknownAction()
+ {
+ // Arrange
+ UnknownActionController controller = new UnknownActionController();
+ // We need a provider since Controller.Execute is called
+ controller.TempDataProvider = new EmptyTempDataProvider();
+ ControllerContext context = GetControllerContext("Foo");
+
+ Mock<IActionInvoker> mockInvoker = new Mock<IActionInvoker>();
+ mockInvoker.Setup(o => o.InvokeAction(context, "Foo")).Returns(false);
+ controller.ActionInvoker = mockInvoker.Object;
+
+ // Act
+ ((IController)controller).Execute(context.RequestContext);
+
+ // Assert
+ Assert.True(controller.WasCalled);
+ }
+
+ [Fact]
+ public void FileWithContents()
+ {
+ // Arrange
+ EmptyController controller = new EmptyController();
+ byte[] fileContents = new byte[0];
+
+ // Act
+ FileContentResult result = controller.File(fileContents, "someContentType");
+
+ // Assert
+ Assert.NotNull(result);
+ Assert.Same(fileContents, result.FileContents);
+ Assert.Equal("someContentType", result.ContentType);
+ Assert.Equal(String.Empty, result.FileDownloadName);
+ }
+
+ [Fact]
+ public void FileWithContentsAndFileDownloadName()
+ {
+ // Arrange
+ EmptyController controller = new EmptyController();
+ byte[] fileContents = new byte[0];
+
+ // Act
+ FileContentResult result = controller.File(fileContents, "someContentType", "someDownloadName");
+
+ // Assert
+ Assert.NotNull(result);
+ Assert.Same(fileContents, result.FileContents);
+ Assert.Equal("someContentType", result.ContentType);
+ Assert.Equal("someDownloadName", result.FileDownloadName);
+ }
+
+ [Fact]
+ public void FileWithPath()
+ {
+ // Arrange
+ EmptyController controller = new EmptyController();
+
+ // Act
+ FilePathResult result = controller.File("somePath", "someContentType");
+
+ // Assert
+ Assert.NotNull(result);
+ Assert.Equal("somePath", result.FileName);
+ Assert.Equal("someContentType", result.ContentType);
+ Assert.Equal(String.Empty, result.FileDownloadName);
+ }
+
+ [Fact]
+ public void FileWithPathAndFileDownloadName()
+ {
+ // Arrange
+ EmptyController controller = new EmptyController();
+
+ // Act
+ FilePathResult result = controller.File("somePath", "someContentType", "someDownloadName");
+
+ // Assert
+ Assert.NotNull(result);
+ Assert.Equal("somePath", result.FileName);
+ Assert.Equal("someContentType", result.ContentType);
+ Assert.Equal("someDownloadName", result.FileDownloadName);
+ }
+
+ [Fact]
+ public void FileWithStream()
+ {
+ // Arrange
+ EmptyController controller = new EmptyController();
+ Stream fileStream = Stream.Null;
+
+ // Act
+ FileStreamResult result = controller.File(fileStream, "someContentType");
+
+ // Assert
+ Assert.NotNull(result);
+ Assert.Same(fileStream, result.FileStream);
+ Assert.Equal("someContentType", result.ContentType);
+ Assert.Equal(String.Empty, result.FileDownloadName);
+ }
+
+ [Fact]
+ public void FileWithStreamAndFileDownloadName()
+ {
+ // Arrange
+ EmptyController controller = new EmptyController();
+ Stream fileStream = Stream.Null;
+
+ // Act
+ FileStreamResult result = controller.File(fileStream, "someContentType", "someDownloadName");
+
+ // Assert
+ Assert.NotNull(result);
+ Assert.Same(fileStream, result.FileStream);
+ Assert.Equal("someContentType", result.ContentType);
+ Assert.Equal("someDownloadName", result.FileDownloadName);
+ }
+
+ [Fact]
+ public void HandleUnknownActionThrows()
+ {
+ var controller = new EmptyController();
+ Assert.Throws<HttpException>(
+ delegate { controller.HandleUnknownAction("UnknownAction"); },
+ "A public action method 'UnknownAction' was not found on controller 'System.Web.Mvc.Test.ControllerTest+EmptyController'.");
+ }
+
+ [Fact]
+ public void JavaScript()
+ {
+ // Arrange
+ Controller controller = GetEmptyController();
+ string script = "alert('foo');";
+
+ // Act
+ JavaScriptResult result = controller.JavaScript(script);
+
+ // Assert
+ Assert.Equal(script, result.Script);
+ }
+
+ [Fact]
+ public void PartialView()
+ {
+ // Arrange
+ Controller controller = GetEmptyController();
+
+ // Act
+ PartialViewResult result = controller.PartialView();
+
+ // Assert
+ Assert.Same(controller.TempData, result.TempData);
+ Assert.Same(controller.ViewData, result.ViewData);
+ Assert.Same(ViewEngines.Engines, result.ViewEngineCollection);
+ }
+
+ [Fact]
+ public void PartialView_Model()
+ {
+ // Arrange
+ Controller controller = GetEmptyController();
+ object model = new object();
+
+ // Act
+ PartialViewResult result = controller.PartialView(model);
+
+ // Assert
+ Assert.Same(model, result.ViewData.Model);
+ Assert.Same(controller.TempData, result.TempData);
+ Assert.Same(controller.ViewData, result.ViewData);
+ }
+
+ [Fact]
+ public void PartialView_ViewName()
+ {
+ // Arrange
+ Controller controller = GetEmptyController();
+
+ // Act
+ PartialViewResult result = controller.PartialView("Some partial view");
+
+ // Assert
+ Assert.Equal("Some partial view", result.ViewName);
+ Assert.Same(controller.TempData, result.TempData);
+ Assert.Same(controller.ViewData, result.ViewData);
+ }
+
+ [Fact]
+ public void PartialView_ViewName_Model()
+ {
+ // Arrange
+ Controller controller = GetEmptyController();
+ object model = new object();
+
+ // Act
+ PartialViewResult result = controller.PartialView("Some partial view", model);
+
+ // Assert
+ Assert.Equal("Some partial view", result.ViewName);
+ Assert.Same(model, result.ViewData.Model);
+ Assert.Same(controller.TempData, result.TempData);
+ Assert.Same(controller.ViewData, result.ViewData);
+ }
+
+ [Fact]
+ public void PartialView_ViewEngineCollection()
+ {
+ // Arrange
+ Controller controller = GetEmptyController();
+ ViewEngineCollection viewEngines = new ViewEngineCollection();
+ controller.ViewEngineCollection = viewEngines;
+
+ // Act
+ PartialViewResult result = controller.PartialView();
+
+ // Assert
+ Assert.Same(viewEngines, result.ViewEngineCollection);
+ }
+
+ [Fact]
+ public void RedirectToActionClonesRouteValueDictionary()
+ {
+ // The RedirectToAction() method should clone the provided dictionary, then operate on the clone.
+ // The original dictionary should remain unmodified throughout the helper's execution.
+
+ // Arrange
+ Controller controller = GetEmptyController();
+ RouteValueDictionary values = new RouteValueDictionary(new { Action = "SomeAction", Controller = "SomeController" });
+
+ // Act
+ controller.RedirectToAction("SomeOtherAction", "SomeOtherController", values);
+
+ // Assert
+ Assert.Equal(2, values.Count);
+ Assert.Equal("SomeAction", values["action"]);
+ Assert.Equal("SomeController", values["controller"]);
+ }
+
+ [Fact]
+ public void RedirectToActionOverwritesActionDictionaryKey()
+ {
+ // Arrange
+ Controller controller = GetEmptyController();
+ object values = new { Action = "SomeAction" };
+
+ // Act
+ RedirectToRouteResult result = controller.RedirectToAction("SomeOtherAction", values);
+ RouteValueDictionary newValues = result.RouteValues;
+
+ // Assert
+ Assert.Equal("SomeOtherAction", newValues["action"]);
+ }
+
+ [Fact]
+ public void RedirectToActionOverwritesControllerDictionaryKeyIfSpecified()
+ {
+ // Arrange
+ Controller controller = GetEmptyController();
+ object values = new { Action = "SomeAction", Controller = "SomeController" };
+
+ // Act
+ RedirectToRouteResult result = controller.RedirectToAction("SomeOtherAction", "SomeOtherController", values);
+ RouteValueDictionary newValues = result.RouteValues;
+
+ // Assert
+ Assert.Equal("SomeOtherController", newValues["controller"]);
+ }
+
+ [Fact]
+ public void RedirectToActionPreservesControllerDictionaryKeyIfNotSpecified()
+ {
+ // Arrange
+ Controller controller = GetEmptyController();
+ object values = new { Controller = "SomeController" };
+
+ // Act
+ RedirectToRouteResult result = controller.RedirectToAction("SomeOtherAction", values);
+ RouteValueDictionary newValues = result.RouteValues;
+
+ // Assert
+ Assert.Equal("SomeController", newValues["controller"]);
+ }
+
+ [Fact]
+ public void RedirectToActionWithActionName()
+ {
+ // Arrange
+ Controller controller = GetEmptyController();
+
+ // Act
+ RedirectToRouteResult result = controller.RedirectToAction("SomeOtherAction");
+
+ // Assert
+ Assert.Equal("", result.RouteName);
+ Assert.Equal("SomeOtherAction", result.RouteValues["action"]);
+ Assert.False(result.Permanent);
+ }
+
+ [Fact]
+ public void RedirectToActionWithActionNameAndControllerName()
+ {
+ // Arrange
+ Controller controller = GetEmptyController();
+
+ // Act
+ RedirectToRouteResult result = controller.RedirectToAction("SomeOtherAction", "SomeOtherController");
+
+ // Assert
+ Assert.Equal("", result.RouteName);
+ Assert.Equal("SomeOtherAction", result.RouteValues["action"]);
+ Assert.Equal("SomeOtherController", result.RouteValues["controller"]);
+ Assert.False(result.Permanent);
+ }
+
+ [Fact]
+ public void RedirectToActionWithActionNameAndControllerNameAndValuesDictionary()
+ {
+ // Arrange
+ Controller controller = GetEmptyController();
+ RouteValueDictionary values = new RouteValueDictionary(new { Foo = "SomeFoo" });
+
+ // Act
+ RedirectToRouteResult result = controller.RedirectToAction("SomeOtherAction", "SomeOtherController", values);
+
+ // Assert
+ Assert.Equal("", result.RouteName);
+ Assert.Equal("SomeOtherAction", result.RouteValues["action"]);
+ Assert.Equal("SomeOtherController", result.RouteValues["controller"]);
+ Assert.Equal("SomeFoo", result.RouteValues["foo"]);
+ Assert.False(result.Permanent);
+ }
+
+ [Fact]
+ public void RedirectToActionWithActionNameAndControllerNameAndValuesObject()
+ {
+ // Arrange
+ Controller controller = GetEmptyController();
+ object values = new { Foo = "SomeFoo" };
+
+ // Act
+ RedirectToRouteResult result = controller.RedirectToAction("SomeOtherAction", "SomeOtherController", values);
+
+ // Assert
+ Assert.Equal("", result.RouteName);
+ Assert.Equal("SomeOtherAction", result.RouteValues["action"]);
+ Assert.Equal("SomeOtherController", result.RouteValues["controller"]);
+ Assert.Equal("SomeFoo", result.RouteValues["foo"]);
+ Assert.False(result.Permanent);
+ }
+
+ [Fact]
+ public void RedirectToActionSelectsCurrentControllerByDefault()
+ {
+ // Arrange
+ TestRouteController controller = new TestRouteController();
+ controller.ControllerContext = GetControllerContext("SomeAction", "TestRoute");
+
+ // Act
+ RedirectToRouteResult route = controller.Index() as RedirectToRouteResult;
+
+ // Assert
+ Assert.Equal("SomeAction", route.RouteValues["action"]);
+ Assert.Equal("TestRoute", route.RouteValues["controller"]);
+ }
+
+ [Fact]
+ public void RedirectToActionDictionaryOverridesDefaultControllerName()
+ {
+ // Arrange
+ TestRouteController controller = new TestRouteController();
+ object values = new { controller = "SomeOtherController" };
+ controller.ControllerContext = GetControllerContext("SomeAction", "TestRoute");
+
+ // Act
+ RedirectToRouteResult route = controller.RedirectToAction("SomeOtherAction", values);
+
+ // Assert
+ Assert.Equal("SomeOtherAction", route.RouteValues["action"]);
+ Assert.Equal("SomeOtherController", route.RouteValues["controller"]);
+ }
+
+ [Fact]
+ public void RedirectToActionSimpleOverridesCallLegacyMethod()
+ {
+ // The simple overrides need to call RedirectToAction(string, string, RouteValueDictionary) to maintain backwards compat
+
+ // Arrange
+ int invocationCount = 0;
+ Mock<Controller> controllerMock = new Mock<Controller>();
+ controllerMock.Setup(c => c.RedirectToAction(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<RouteValueDictionary>())).Callback(() => { invocationCount++; });
+
+ Controller controller = controllerMock.Object;
+
+ // Act
+ controller.RedirectToAction("SomeAction");
+ controller.RedirectToAction("SomeAction", (object)null);
+ controller.RedirectToAction("SomeAction", (RouteValueDictionary)null);
+ controller.RedirectToAction("SomeAction", "SomeController");
+ controller.RedirectToAction("SomeAction", "SomeController", (object)null);
+
+ // Assert
+ Assert.Equal(5, invocationCount);
+ }
+
+ [Fact]
+ public void RedirectToActionWithActionNameAndValuesDictionary()
+ {
+ // Arrange
+ Controller controller = GetEmptyController();
+ RouteValueDictionary values = new RouteValueDictionary(new { Foo = "SomeFoo" });
+
+ // Act
+ RedirectToRouteResult result = controller.RedirectToAction("SomeOtherAction", values);
+
+ // Assert
+ Assert.Equal("", result.RouteName);
+ Assert.Equal("SomeOtherAction", result.RouteValues["action"]);
+ Assert.Equal("SomeFoo", result.RouteValues["foo"]);
+ Assert.False(result.Permanent);
+ }
+
+ [Fact]
+ public void RedirectToActionWithActionNameAndValuesObject()
+ {
+ // Arrange
+ Controller controller = GetEmptyController();
+ object values = new { Foo = "SomeFoo" };
+
+ // Act
+ RedirectToRouteResult result = controller.RedirectToAction("SomeOtherAction", values);
+
+ // Assert
+ Assert.Equal("", result.RouteName);
+ Assert.Equal("SomeOtherAction", result.RouteValues["action"]);
+ Assert.Equal("SomeFoo", result.RouteValues["foo"]);
+ Assert.False(result.Permanent);
+ }
+
+ [Fact]
+ public void RedirectToActionWithNullRouteValueDictionary()
+ {
+ // Arrange
+ Controller controller = GetEmptyController();
+
+ // Act
+ RedirectToRouteResult result = controller.RedirectToAction("SomeOtherAction", (RouteValueDictionary)null);
+ RouteValueDictionary newValues = result.RouteValues;
+
+ // Assert
+ Assert.Single(newValues);
+ Assert.Equal("SomeOtherAction", newValues["action"]);
+ Assert.False(result.Permanent);
+ }
+
+ [Fact]
+ public void RedirectToActionPermanent()
+ {
+ // Arrange
+ Controller controller = GetEmptyController();
+
+ // Act
+ RedirectToRouteResult result = controller.RedirectToActionPermanent("SomeOtherAction");
+
+ // Assert
+ Assert.True(result.Permanent);
+ Assert.Equal("SomeOtherAction", result.RouteValues["action"]);
+ Assert.Null(result.RouteValues["controller"]);
+ }
+
+ [Fact]
+ public void RedirectToActionPermanentWithObjectDictionary()
+ {
+ // Arrange
+ Controller controller = GetEmptyController();
+
+ // Act
+ RedirectToRouteResult result = controller.RedirectToActionPermanent("SomeOtherAction", controllerName: "SomeController", routeValues: new { foo = "bar" });
+
+ // Assert
+ Assert.True(result.Permanent);
+ Assert.Equal("SomeOtherAction", result.RouteValues["action"]);
+ Assert.Equal("bar", result.RouteValues["foo"]);
+ Assert.Equal("SomeController", result.RouteValues["controller"]);
+ }
+
+ [Fact]
+ public void RedirectToActionPermanentWithRouteValueDictionary()
+ {
+ // Arrange
+ Controller controller = GetEmptyController();
+
+ // Act
+ RedirectToRouteResult result = controller.RedirectToActionPermanent("SomeOtherAction", routeValues: new RouteValueDictionary(new { foo = "bar" }));
+
+ // Assert
+ Assert.True(result.Permanent);
+ Assert.Equal("SomeOtherAction", result.RouteValues["action"]);
+ Assert.Equal("bar", result.RouteValues["foo"]);
+ }
+
+ [Fact]
+ public void RedirectToRouteSimpleOverridesCallLegacyMethod()
+ {
+ // The simple overrides need to call RedirectToRoute(string, RouteValueDictionary) to maintain backwards compat
+
+ // Arrange
+ int invocationCount = 0;
+ Mock<Controller> controllerMock = new Mock<Controller>();
+ controllerMock.Setup(c => c.RedirectToRoute(It.IsAny<string>(), It.IsAny<RouteValueDictionary>())).Callback(() => { invocationCount++; });
+
+ Controller controller = controllerMock.Object;
+
+ // Act
+ controller.RedirectToRoute("SomeRoute");
+ controller.RedirectToRoute("SomeRoute", (object)null);
+ controller.RedirectToRoute((object)null);
+ controller.RedirectToRoute((RouteValueDictionary)null);
+
+ // Assert
+ Assert.Equal(4, invocationCount);
+ }
+
+ [Fact]
+ public void RedirectToRouteWithNullRouteValueDictionary()
+ {
+ // Arrange
+ Controller controller = GetEmptyController();
+
+ // Act
+ RedirectToRouteResult result = controller.RedirectToRoute((RouteValueDictionary)null);
+
+ // Assert
+ Assert.Empty(result.RouteValues);
+ Assert.False(result.Permanent);
+ }
+
+ [Fact]
+ public void RedirectToRouteWithObjectDictionary()
+ {
+ // Arrange
+ Controller controller = GetEmptyController();
+ var values = new { Foo = "MyFoo" };
+
+ // Act
+ RedirectToRouteResult result = controller.RedirectToRoute(values);
+
+ // Assert
+ Assert.Single(result.RouteValues);
+ Assert.Equal("MyFoo", result.RouteValues["Foo"]);
+ Assert.False(result.Permanent);
+ }
+
+ [Fact]
+ public void RedirectToRouteWithRouteValueDictionary()
+ {
+ // Arrange
+ Controller controller = GetEmptyController();
+ RouteValueDictionary values = new RouteValueDictionary() { { "Foo", "MyFoo" } };
+
+ // Act
+ RedirectToRouteResult result = controller.RedirectToRoute(values);
+
+ // Assert
+ Assert.Single(result.RouteValues);
+ Assert.Equal("MyFoo", result.RouteValues["Foo"]);
+ Assert.NotSame(values, result.RouteValues);
+ Assert.False(result.Permanent);
+ }
+
+ [Fact]
+ public void RedirectToRouteWithName()
+ {
+ // Arrange
+ Controller controller = GetEmptyController();
+
+ // Act
+ RedirectToRouteResult result = controller.RedirectToRoute("foo");
+
+ // Assert
+ Assert.Empty(result.RouteValues);
+ Assert.Equal("foo", result.RouteName);
+ Assert.False(result.Permanent);
+ }
+
+ [Fact]
+ public void RedirectToRouteWithNameAndNullRouteValueDictionary()
+ {
+ // Arrange
+ Controller controller = GetEmptyController();
+
+ // Act
+ RedirectToRouteResult result = controller.RedirectToRoute("foo", (RouteValueDictionary)null);
+
+ // Assert
+ Assert.Empty(result.RouteValues);
+ Assert.Equal("foo", result.RouteName);
+ Assert.False(result.Permanent);
+ }
+
+ [Fact]
+ public void RedirectToRouteWithNullNameAndNullRouteValueDictionary()
+ {
+ // Arrange
+ Controller controller = GetEmptyController();
+
+ // Act
+ RedirectToRouteResult result = controller.RedirectToRoute(null, (RouteValueDictionary)null);
+
+ // Assert
+ Assert.Empty(result.RouteValues);
+ Assert.Equal(String.Empty, result.RouteName);
+ Assert.False(result.Permanent);
+ }
+
+ [Fact]
+ public void RedirectToRouteWithNameAndObjectDictionary()
+ {
+ // Arrange
+ Controller controller = GetEmptyController();
+ var values = new { Foo = "MyFoo" };
+
+ // Act
+ RedirectToRouteResult result = controller.RedirectToRoute("foo", values);
+
+ // Assert
+ Assert.Single(result.RouteValues);
+ Assert.Equal("MyFoo", result.RouteValues["Foo"]);
+ Assert.Equal("foo", result.RouteName);
+ Assert.False(result.Permanent);
+ }
+
+ [Fact]
+ public void RedirectToRouteWithNameAndRouteValueDictionary()
+ {
+ // Arrange
+ Controller controller = GetEmptyController();
+ RouteValueDictionary values = new RouteValueDictionary() { { "Foo", "MyFoo" } };
+
+ // Act
+ RedirectToRouteResult result = controller.RedirectToRoute("foo", values);
+
+ // Assert
+ Assert.Single(result.RouteValues);
+ Assert.Equal("MyFoo", result.RouteValues["Foo"]);
+ Assert.NotSame(values, result.RouteValues);
+ Assert.Equal("foo", result.RouteName);
+ Assert.False(result.Permanent);
+ }
+
+ [Fact]
+ public void RedirectToRoutePermanentWithObjectDictionary()
+ {
+ // Arrange
+ Controller controller = GetEmptyController();
+
+ // Act
+ RedirectToRouteResult result = controller.RedirectToRoutePermanent(routeValues: new { Foo = "Bar" });
+
+ // Assert
+ Assert.True(result.Permanent);
+ Assert.Equal("Bar", result.RouteValues["Foo"]);
+ }
+
+ [Fact]
+ public void RedirectToRoutePermanentWithRouteValueDictionary()
+ {
+ // Arrange
+ Controller controller = GetEmptyController();
+
+ // Act
+ RedirectToRouteResult result = controller.RedirectToRoutePermanent(routeValues: new RouteValueDictionary(new { Foo = "Bar" }));
+
+ // Assert
+ Assert.True(result.Permanent);
+ Assert.Equal("Bar", result.RouteValues["Foo"]);
+ }
+
+ [Fact]
+ public void RedirectReturnsCorrectActionResult()
+ {
+ // Arrange
+ Controller controller = GetEmptyController();
+
+ // Act & Assert
+ var result = controller.Redirect("http://www.contoso.com/");
+
+ // Assert
+ Assert.Equal("http://www.contoso.com/", result.Url);
+ Assert.False(result.Permanent);
+ }
+
+ [Fact]
+ public void RedirectPermanentReturnsCorrectActionResult()
+ {
+ // Arrange
+ Controller controller = GetEmptyController();
+
+ // Act & Assert
+ var result = controller.RedirectPermanent("http://www.contoso.com/");
+
+ // Assert
+ Assert.Equal("http://www.contoso.com/", result.Url);
+ Assert.True(result.Permanent);
+ }
+
+ [Fact]
+ public void RedirectWithEmptyUrlThrows()
+ {
+ // Arrange
+ Controller controller = GetEmptyController();
+
+ // Act & Assert
+ Assert.ThrowsArgumentNullOrEmpty(
+ delegate { controller.Redirect(String.Empty); },
+ "url");
+ }
+
+ [Fact]
+ public void RedirectPermanentWithEmptyUrlThrows()
+ {
+ // Arrange
+ Controller controller = GetEmptyController();
+
+ // Act & Assert
+ Assert.ThrowsArgumentNullOrEmpty(
+ delegate { controller.RedirectPermanent(String.Empty); },
+ "url");
+ }
+
+ [Fact]
+ public void RedirectWithNullUrlThrows()
+ {
+ // Arrange
+ Controller controller = GetEmptyController();
+
+ // Act & Assert
+ Assert.ThrowsArgumentNullOrEmpty(
+ delegate { controller.Redirect(url: null); },
+ "url");
+ }
+
+ [Fact]
+ public void RedirectPermanentWithNullUrlThrows()
+ {
+ // Arrange
+ Controller controller = GetEmptyController();
+
+ // Act & Assert
+ Assert.ThrowsArgumentNullOrEmpty(
+ delegate { controller.RedirectPermanent(url: null); },
+ "url");
+ }
+
+ [Fact]
+ public void View()
+ {
+ // Arrange
+ Controller controller = GetEmptyController();
+
+ // Act
+ ViewResult result = controller.View();
+
+ // Assert
+ Assert.Same(controller.ViewData, result.ViewData);
+ Assert.Same(controller.TempData, result.TempData);
+ Assert.Same(ViewEngines.Engines, result.ViewEngineCollection);
+ }
+
+ [Fact]
+ public void View_Model()
+ {
+ // Arrange
+ Controller controller = GetEmptyController();
+ object viewItem = new object();
+
+ // Act
+ ViewResult result = controller.View(viewItem);
+
+ // Assert
+ Assert.Same(viewItem, result.ViewData.Model);
+ Assert.Same(controller.TempData, result.TempData);
+ }
+
+ [Fact]
+ public void View_ViewName()
+ {
+ // Arrange
+ Controller controller = GetEmptyController();
+
+ // Act
+ ViewResult result = controller.View("Foo");
+
+ // Assert
+ Assert.Equal("Foo", result.ViewName);
+ Assert.Same(controller.ViewData, result.ViewData);
+ Assert.Same(controller.TempData, result.TempData);
+ }
+
+ [Fact]
+ public void View_ViewName_Model()
+ {
+ // Arrange
+ Controller controller = GetEmptyController();
+ object viewItem = new object();
+
+ // Act
+ ViewResult result = controller.View("Foo", viewItem);
+
+ // Assert
+ Assert.Equal("Foo", result.ViewName);
+ Assert.Same(viewItem, result.ViewData.Model);
+ Assert.Same(controller.TempData, result.TempData);
+ }
+
+ [Fact]
+ public void View_ViewName_MasterViewName()
+ {
+ // Arrange
+ Controller controller = GetEmptyController();
+
+ // Act
+ ViewResult result = controller.View("Foo", "Bar");
+
+ // Assert
+ Assert.Equal("Foo", result.ViewName);
+ Assert.Equal("Bar", result.MasterName);
+ Assert.Same(controller.ViewData, result.ViewData);
+ Assert.Same(controller.TempData, result.TempData);
+ }
+
+ [Fact]
+ public void View_ViewName_MasterViewName_Model()
+ {
+ // Arrange
+ Controller controller = GetEmptyController();
+ object viewItem = new object();
+
+ // Act
+ ViewResult result = controller.View("Foo", "Bar", viewItem);
+
+ // Assert
+ Assert.Equal("Foo", result.ViewName);
+ Assert.Equal("Bar", result.MasterName);
+ Assert.Same(viewItem, result.ViewData.Model);
+ Assert.Same(controller.TempData, result.TempData);
+ }
+
+ [Fact]
+ public void View_View()
+ {
+ // Arrange
+ Controller controller = GetEmptyController();
+ IView view = new Mock<IView>().Object;
+
+ // Act
+ ViewResult result = controller.View(view);
+
+ // Assert
+ Assert.Same(result.View, view);
+ Assert.Same(controller.ViewData, result.ViewData);
+ Assert.Same(controller.TempData, result.TempData);
+ }
+
+ [Fact]
+ public void View_View_Model()
+ {
+ // Arrange
+ Controller controller = GetEmptyController();
+ IView view = new Mock<IView>().Object;
+ object model = new object();
+
+ // Act
+ ViewResult result = controller.View(view, model);
+
+ // Assert
+ Assert.Same(result.View, view);
+ Assert.Same(controller.ViewData, result.ViewData);
+ Assert.Same(controller.TempData, result.TempData);
+ Assert.Same(model, result.ViewData.Model);
+ }
+
+ [Fact]
+ public void View_ViewEngineCollection()
+ {
+ // Arrange
+ Controller controller = GetEmptyController();
+ ViewEngineCollection viewEngines = new ViewEngineCollection();
+ controller.ViewEngineCollection = viewEngines;
+
+ // Act
+ ViewResult result = controller.View();
+
+ // Assert
+ Assert.Same(viewEngines, result.ViewEngineCollection);
+ }
+
+ internal static void AddRequestParams(Mock<HttpRequestBase> requestMock, object paramValues)
+ {
+ PropertyDescriptorCollection props = TypeDescriptor.GetProperties(paramValues);
+ foreach (PropertyDescriptor prop in props)
+ {
+ requestMock.Setup(o => o[It.Is<string>(item => String.Equals(prop.Name, item, StringComparison.OrdinalIgnoreCase))]).Returns((string)prop.GetValue(paramValues));
+ }
+ }
+
+ [Fact]
+ public void TempDataGreetUserWithNoUserIDRedirects()
+ {
+ // Arrange
+ TempDataHomeController tempDataHomeController = new TempDataHomeController();
+
+ // Act
+ RedirectToRouteResult result = tempDataHomeController.GreetUser() as RedirectToRouteResult;
+ RouteValueDictionary values = result.RouteValues;
+
+ // Assert
+ Assert.True(values.ContainsKey("action"));
+ Assert.Equal("ErrorPage", values["action"]);
+ Assert.Empty(tempDataHomeController.TempData);
+ }
+
+ [Fact]
+ public void TempDataGreetUserWithUserIDCopiesToViewDataAndRenders()
+ {
+ // Arrange
+ TempDataHomeController tempDataHomeController = new TempDataHomeController();
+ tempDataHomeController.TempData["UserID"] = "TestUserID";
+
+ // Act
+ ViewResult result = tempDataHomeController.GreetUser() as ViewResult;
+ ViewDataDictionary viewData = tempDataHomeController.ViewData;
+
+ // Assert
+ Assert.Equal("GreetUser", result.ViewName);
+ Assert.NotNull(viewData);
+ Assert.True(viewData.ContainsKey("NewUserID"));
+ Assert.Equal("TestUserID", viewData["NewUserID"]);
+ }
+
+ [Fact]
+ public void TempDataIndexSavesUserIDAndRedirects()
+ {
+ // Arrange
+ TempDataHomeController tempDataHomeController = new TempDataHomeController();
+
+ // Act
+ RedirectToRouteResult result = tempDataHomeController.Index() as RedirectToRouteResult;
+ RouteValueDictionary values = result.RouteValues;
+
+ // Assert
+ Assert.True(values.ContainsKey("action"));
+ Assert.Equal("GreetUser", values["action"]);
+
+ Assert.True(tempDataHomeController.TempData.ContainsKey("UserID"));
+ Assert.Equal("user123", tempDataHomeController.TempData["UserID"]);
+ }
+
+ [Fact]
+ public void TempDataSavedWhenControllerThrows()
+ {
+ // Arrange
+ BrokenController controller = new BrokenController() { ValidateRequest = false };
+ Mock<HttpContextBase> mockContext = HttpContextHelpers.GetMockHttpContext();
+ HttpSessionStateBase session = GetEmptySession();
+ mockContext.Setup(o => o.Session).Returns(session);
+ RouteData rd = new RouteData();
+ rd.Values.Add("action", "Crash");
+ controller.ControllerContext = new ControllerContext(mockContext.Object, rd, controller);
+
+ // Assert
+ Assert.Throws<InvalidOperationException>(
+ delegate { ((IController)controller).Execute(controller.ControllerContext.RequestContext); });
+ Assert.NotEqual(mockContext.Object.Session[SessionStateTempDataProvider.TempDataSessionStateKey], null);
+ TempDataDictionary tempData = new TempDataDictionary();
+ tempData.Load(controller.ControllerContext, controller.TempDataProvider);
+ Assert.Equal(tempData["Key1"], "Value1");
+ }
+
+ [Fact]
+ public void TempDataMovedToPreviousTempDataInDestinationController()
+ {
+ // Arrange
+ Mock<Controller> mockController = new Mock<Controller>() { CallBase = true };
+ Mock<HttpContextBase> mockContext = new Mock<HttpContextBase>();
+ HttpSessionStateBase session = GetEmptySession();
+ mockContext.Setup(o => o.Session).Returns(session);
+ mockController.Object.ControllerContext = new ControllerContext(mockContext.Object, new RouteData(), mockController.Object);
+
+ // Act
+ mockController.Object.TempData.Add("Key", "Value");
+ mockController.Object.TempData.Save(mockController.Object.ControllerContext, mockController.Object.TempDataProvider);
+
+ // Assert
+ Assert.True(mockController.Object.TempData.ContainsKey("Key"));
+ Assert.True(mockController.Object.TempData.ContainsValue("Value"));
+
+ // Instantiate "destination" controller with the same session state and see that it gets the temp data
+ Mock<Controller> mockDestinationController = new Mock<Controller>() { CallBase = true };
+ Mock<HttpContextBase> mockDestinationContext = new Mock<HttpContextBase>();
+ mockDestinationContext.Setup(o => o.Session).Returns(session);
+ mockDestinationController.Object.ControllerContext = new ControllerContext(mockDestinationContext.Object, new RouteData(), mockDestinationController.Object);
+ mockDestinationController.Object.TempData.Load(mockDestinationController.Object.ControllerContext, mockDestinationController.Object.TempDataProvider);
+
+ // Assert
+ Assert.True(mockDestinationController.Object.TempData.ContainsKey("Key"));
+
+ // Act
+ mockDestinationController.Object.TempData["NewKey"] = "NewValue";
+ Assert.True(mockDestinationController.Object.TempData.ContainsKey("NewKey"));
+ mockDestinationController.Object.TempData.Save(mockDestinationController.Object.ControllerContext, mockDestinationController.Object.TempDataProvider);
+
+ // Instantiate "second destination" controller with the same session state and see that it gets the temp data
+ Mock<Controller> mockSecondDestinationController = new Mock<Controller>() { CallBase = true };
+ Mock<HttpContextBase> mockSecondDestinationContext = new Mock<HttpContextBase>();
+ mockSecondDestinationContext.Setup(o => o.Session).Returns(session);
+ mockSecondDestinationController.Object.ControllerContext = new ControllerContext(mockSecondDestinationContext.Object, new RouteData(), mockSecondDestinationController.Object);
+ mockSecondDestinationController.Object.TempData.Load(mockSecondDestinationController.Object.ControllerContext, mockSecondDestinationController.Object.TempDataProvider);
+
+ // Assert
+ Assert.True(mockSecondDestinationController.Object.TempData.ContainsKey("Key"));
+ Assert.True(mockSecondDestinationController.Object.TempData.ContainsKey("NewKey"));
+ }
+
+ [Fact]
+ public void TempDataRemovesKeyWhenRead()
+ {
+ // Arrange
+ Mock<Controller> mockController = new Mock<Controller>() { CallBase = true };
+ Mock<HttpContextBase> mockContext = new Mock<HttpContextBase>();
+ HttpSessionStateBase session = GetEmptySession();
+ mockContext.Setup(o => o.Session).Returns(session);
+ mockController.Object.ControllerContext = new ControllerContext(mockContext.Object, new RouteData(), mockController.Object);
+
+ // Act
+ mockController.Object.TempData.Add("Key", "Value");
+ mockController.Object.TempData.Save(mockController.Object.ControllerContext, mockController.Object.TempDataProvider);
+
+ // Assert
+ Assert.True(mockController.Object.TempData.ContainsKey("Key"));
+ Assert.True(mockController.Object.TempData.ContainsValue("Value"));
+
+ // Instantiate "destination" controller with the same session state and see that it gets the temp data
+ Mock<Controller> mockDestinationController = new Mock<Controller>() { CallBase = true };
+ Mock<HttpContextBase> mockDestinationContext = new Mock<HttpContextBase>();
+ mockDestinationContext.Setup(o => o.Session).Returns(session);
+ mockDestinationController.Object.ControllerContext = new ControllerContext(mockDestinationContext.Object, new RouteData(), mockDestinationController.Object);
+ mockDestinationController.Object.TempData.Load(mockDestinationController.Object.ControllerContext, mockDestinationController.Object.TempDataProvider);
+
+ // Assert
+ Assert.True(mockDestinationController.Object.TempData.ContainsKey("Key"));
+
+ // Act
+ object value = mockDestinationController.Object.TempData["Key"];
+ mockDestinationController.Object.TempData.Save(mockDestinationController.Object.ControllerContext, mockDestinationController.Object.TempDataProvider);
+
+ // Instantiate "second destination" controller with the same session state and see that it gets the temp data
+ Mock<Controller> mockSecondDestinationController = new Mock<Controller>() { CallBase = true };
+ Mock<HttpContextBase> mockSecondDestinationContext = new Mock<HttpContextBase>();
+ mockSecondDestinationContext.Setup(o => o.Session).Returns(session);
+ mockSecondDestinationController.Object.ControllerContext = new ControllerContext(mockSecondDestinationContext.Object, new RouteData(), mockSecondDestinationController.Object);
+ mockSecondDestinationController.Object.TempData.Load(mockSecondDestinationController.Object.ControllerContext, mockSecondDestinationController.Object.TempDataProvider);
+
+ // Assert
+ Assert.False(mockSecondDestinationController.Object.TempData.ContainsKey("Key"));
+ }
+
+ [Fact]
+ public void TempDataValidForSingleControllerWhenSessionStateDisabled()
+ {
+ // Arrange
+ Mock<Controller> mockController = new Mock<Controller>();
+ Mock<HttpContextBase> mockContext = new Mock<HttpContextBase>();
+ HttpSessionStateBase session = null;
+ mockContext.Setup(o => o.Session).Returns(session);
+ mockController.Object.ControllerContext = new ControllerContext(mockContext.Object, new RouteData(), mockController.Object);
+ mockController.Object.TempData = new TempDataDictionary();
+
+ // Act
+ mockController.Object.TempData["Key"] = "Value";
+
+ // Assert
+ Assert.True(mockController.Object.TempData.ContainsKey("Key"));
+ }
+
+ [Fact]
+ public void TryUpdateModelCallsModelBinderForModel()
+ {
+ // Arrange
+ MyModel myModel = new MyModelSubclassed();
+ IValueProvider valueProvider = new SimpleValueProvider();
+
+ Controller controller = new EmptyController();
+ controller.ControllerContext = GetControllerContext("someAction");
+
+ // Act
+ bool returned = controller.TryUpdateModel(myModel, "somePrefix", new[] { "prop1", "prop2" }, null, valueProvider);
+
+ // Assert
+ Assert.True(returned);
+ Assert.Equal(valueProvider, myModel.BindingContext.ValueProvider);
+ Assert.Equal("somePrefix", myModel.BindingContext.ModelName);
+ Assert.Equal(controller.ModelState, myModel.BindingContext.ModelState);
+ Assert.Equal(typeof(MyModel), myModel.BindingContext.ModelType);
+ Assert.True(myModel.BindingContext.PropertyFilter("prop1"));
+ Assert.True(myModel.BindingContext.PropertyFilter("prop2"));
+ Assert.False(myModel.BindingContext.PropertyFilter("prop3"));
+ }
+
+ [Fact]
+ public void TryUpdateModelReturnsFalseIfModelStateInvalid()
+ {
+ // Arrange
+ MyModel myModel = new MyModelSubclassed();
+
+ Controller controller = new EmptyController();
+ controller.ModelState.AddModelError("key", "some exception message");
+
+ // Act
+ bool returned = controller.TryUpdateModel(myModel, new SimpleValueProvider());
+
+ // Assert
+ Assert.False(returned);
+ }
+
+ [Fact]
+ public void TryUpdateModelSuppliesControllerValueProviderIfNoValueProviderSpecified()
+ {
+ // Arrange
+ MyModel myModel = new MyModelSubclassed();
+ IValueProvider valueProvider = new SimpleValueProvider();
+
+ Controller controller = new EmptyController();
+ controller.ValueProvider = valueProvider;
+
+ // Act
+ bool returned = controller.TryUpdateModel(myModel, "somePrefix", new[] { "prop1", "prop2" });
+
+ // Assert
+ Assert.True(returned);
+ Assert.Equal(valueProvider, myModel.BindingContext.ValueProvider);
+ }
+
+ [Fact]
+ public void TryUpdateModelSuppliesEmptyModelNameIfNoPrefixSpecified()
+ {
+ // Arrange
+ MyModel myModel = new MyModelSubclassed();
+ Controller controller = new EmptyController();
+
+ // Act
+ bool returned = controller.TryUpdateModel(myModel, new[] { "prop1", "prop2" }, new SimpleValueProvider());
+
+ // Assert
+ Assert.True(returned);
+ Assert.Equal(String.Empty, myModel.BindingContext.ModelName);
+ }
+
+ [Fact]
+ public void TryUpdateModelThrowsIfModelIsNull()
+ {
+ // Arrange
+ Controller controller = new EmptyController();
+
+ // Act & Assert
+ Assert.ThrowsArgumentNull(
+ delegate { controller.TryUpdateModel<object>(null, new SimpleValueProvider()); }, "model");
+ }
+
+ [Fact]
+ public void TryUpdateModelThrowsIfValueProviderIsNull()
+ {
+ // Arrange
+ Controller controller = new EmptyController();
+
+ // Act & Assert
+ Assert.ThrowsArgumentNull(
+ delegate { controller.TryUpdateModel(new object(), null, null, null, null); }, "valueProvider");
+ }
+
+ [Fact]
+ public void UpdateModelReturnsIfModelStateValid()
+ {
+ // Arrange
+ MyModel myModel = new MyModelSubclassed();
+ Controller controller = new EmptyController();
+
+ // Act
+ controller.UpdateModel(myModel, new SimpleValueProvider());
+
+ // Assert
+ // nothing to do - if we got here, the test passed
+ }
+
+ [Fact]
+ public void TryUpdateModelWithoutBindPropertiesImpliesAllPropertiesAreUpdateable()
+ {
+ // Arrange
+ MyModel myModel = new MyModelSubclassed();
+ Controller controller = new EmptyController();
+
+ // Act
+ bool returned = controller.TryUpdateModel(myModel, "somePrefix", new SimpleValueProvider());
+
+ // Assert
+ Assert.True(returned);
+ Assert.True(myModel.BindingContext.PropertyFilter("prop1"));
+ Assert.True(myModel.BindingContext.PropertyFilter("prop2"));
+ Assert.True(myModel.BindingContext.PropertyFilter("prop3"));
+ }
+
+ [Fact]
+ public void UpdateModelSuppliesControllerValueProviderIfNoValueProviderSpecified()
+ {
+ // Arrange
+ MyModel myModel = new MyModelSubclassed();
+ IValueProvider valueProvider = new SimpleValueProvider();
+
+ Controller controller = new EmptyController() { ValueProvider = valueProvider };
+
+ // Act
+ controller.UpdateModel(myModel, "somePrefix", new[] { "prop1", "prop2" });
+
+ // Assert
+ Assert.Equal(valueProvider, myModel.BindingContext.ValueProvider);
+ }
+
+ [Fact]
+ public void UpdateModelThrowsIfModelStateInvalid()
+ {
+ // Arrange
+ MyModel myModel = new MyModelSubclassed();
+
+ Controller controller = new EmptyController();
+ controller.ModelState.AddModelError("key", "some exception message");
+
+ // Act & assert
+ Assert.Throws<InvalidOperationException>(
+ delegate { controller.UpdateModel(myModel, new SimpleValueProvider()); },
+ "The model of type 'System.Web.Mvc.Test.ControllerTest+MyModel' could not be updated.");
+ }
+
+ [Fact]
+ public void UpdateModelWithoutBindPropertiesImpliesAllPropertiesAreUpdateable()
+ {
+ // Arrange
+ MyModel myModel = new MyModelSubclassed();
+ Controller controller = new EmptyController();
+
+ // Act
+ controller.UpdateModel(myModel, "somePrefix", new SimpleValueProvider());
+
+ // Assert
+ Assert.True(myModel.BindingContext.PropertyFilter("prop1"));
+ Assert.True(myModel.BindingContext.PropertyFilter("prop2"));
+ Assert.True(myModel.BindingContext.PropertyFilter("prop3"));
+ }
+
+ [Fact]
+ public void Json()
+ {
+ // Arrange
+ MyModel model = new MyModel();
+ Controller controller = new EmptyController();
+
+ // Act
+ JsonResult result = controller.Json(model);
+
+ // Assert
+ Assert.Same(model, result.Data);
+ Assert.Null(result.ContentType);
+ Assert.Null(result.ContentEncoding);
+ Assert.Equal(JsonRequestBehavior.DenyGet, result.JsonRequestBehavior);
+ }
+
+ [Fact]
+ public void JsonWithContentType()
+ {
+ // Arrange
+ MyModel model = new MyModel();
+ Controller controller = new EmptyController();
+
+ // Act
+ JsonResult result = controller.Json(model, "text/xml");
+
+ // Assert
+ Assert.Same(model, result.Data);
+ Assert.Equal("text/xml", result.ContentType);
+ Assert.Null(result.ContentEncoding);
+ Assert.Equal(JsonRequestBehavior.DenyGet, result.JsonRequestBehavior);
+ }
+
+ [Fact]
+ public void JsonWithContentTypeAndEncoding()
+ {
+ // Arrange
+ MyModel model = new MyModel();
+ Controller controller = new EmptyController();
+
+ // Act
+ JsonResult result = controller.Json(model, "text/xml", Encoding.UTF32);
+
+ // Assert
+ Assert.Same(model, result.Data);
+ Assert.Equal("text/xml", result.ContentType);
+ Assert.Equal(Encoding.UTF32, result.ContentEncoding);
+ Assert.Equal(JsonRequestBehavior.DenyGet, result.JsonRequestBehavior);
+ }
+
+ [Fact]
+ public void JsonWithBehavior()
+ {
+ // Arrange
+ MyModel model = new MyModel();
+ Controller controller = new EmptyController();
+
+ // Act
+ JsonResult result = controller.Json(model, JsonRequestBehavior.AllowGet);
+
+ // Assert
+ Assert.Same(model, result.Data);
+ Assert.Null(result.ContentType);
+ Assert.Null(result.ContentEncoding);
+ Assert.Equal(JsonRequestBehavior.AllowGet, result.JsonRequestBehavior);
+ }
+
+ [Fact]
+ public void JsonWithContentTypeAndBehavior()
+ {
+ // Arrange
+ MyModel model = new MyModel();
+ Controller controller = new EmptyController();
+
+ // Act
+ JsonResult result = controller.Json(model, "text/xml", JsonRequestBehavior.AllowGet);
+
+ // Assert
+ Assert.Same(model, result.Data);
+ Assert.Equal("text/xml", result.ContentType);
+ Assert.Null(result.ContentEncoding);
+ Assert.Equal(JsonRequestBehavior.AllowGet, result.JsonRequestBehavior);
+ }
+
+ [Fact]
+ public void JsonWithContentTypeAndEncodingAndBehavior()
+ {
+ // Arrange
+ MyModel model = new MyModel();
+ Controller controller = new EmptyController();
+
+ // Act
+ JsonResult result = controller.Json(model, "text/xml", Encoding.UTF32, JsonRequestBehavior.AllowGet);
+
+ // Assert
+ Assert.Same(model, result.Data);
+ Assert.Equal("text/xml", result.ContentType);
+ Assert.Equal(Encoding.UTF32, result.ContentEncoding);
+ Assert.Equal(JsonRequestBehavior.AllowGet, result.JsonRequestBehavior);
+ }
+
+ [Fact]
+ public void ExecuteDoesNotCallTempDataLoadOrSave()
+ {
+ // Arrange
+ TempDataDictionary tempData = new TempDataDictionary();
+ ViewContext viewContext = new ViewContext { TempData = tempData };
+ RouteData routeData = new RouteData();
+ routeData.DataTokens[ControllerContext.ParentActionViewContextToken] = viewContext;
+ routeData.Values["action"] = "SimpleAction";
+ RequestContext requestContext = new RequestContext(HttpContextHelpers.GetMockHttpContext().Object, routeData);
+ // Strict == no default implementations == calls to Load & Save are not allowed
+ Mock<ITempDataProvider> tempDataProvider = new Mock<ITempDataProvider>(MockBehavior.Strict);
+ SimpleController controller = new SimpleController();
+ controller.ValidateRequest = false;
+
+ // Act
+ ((IController)controller).Execute(requestContext);
+
+ // Assert
+ tempDataProvider.Verify();
+ }
+
+ // Model validation
+
+ [Fact]
+ public void TryValidateModelGuardClauses()
+ {
+ // Arrange
+ Controller controller = new SimpleController();
+
+ // Act & Assert
+ Assert.ThrowsArgumentNull(
+ () => controller.TryValidateModel(null),
+ "model");
+ }
+
+ [Fact]
+ public void TryValidateModelWithValidModel()
+ {
+ // Arrange
+ Controller controller = new SimpleController();
+ TryValidateModelModel model = new TryValidateModelModel { IntegerProperty = 15 };
+
+ // Act
+ bool result = controller.TryValidateModel(model);
+
+ // Assert
+ Assert.True(result);
+ Assert.True(controller.ModelState.IsValid);
+ }
+
+ [Fact]
+ public void TryValidateModelWithInvalidModel()
+ {
+ // Arrange
+ Controller controller = new SimpleController();
+ TryValidateModelModel model = new TryValidateModelModel { IntegerProperty = 5 };
+
+ // Act
+ bool result = controller.TryValidateModel(model, "Prefix");
+
+ // Assert
+ Assert.False(result);
+ Assert.Equal("Out of range!", controller.ModelState["Prefix.IntegerProperty"].Errors[0].ErrorMessage);
+ }
+
+ [Fact]
+ public void ValidateModelGuardClauses()
+ {
+ // Arrange
+ Controller controller = new SimpleController();
+
+ // Act & Assert
+ Assert.ThrowsArgumentNull(
+ () => controller.ValidateModel(null),
+ "model");
+ }
+
+ [Fact]
+ public void ValidateModelWithValidModel()
+ {
+ // Arrange
+ Controller controller = new SimpleController();
+ TryValidateModelModel model = new TryValidateModelModel { IntegerProperty = 15 };
+
+ // Act
+ controller.ValidateModel(model);
+
+ // Assert
+ Assert.True(controller.ModelState.IsValid);
+ }
+
+ [Fact]
+ public void ValidateModelWithInvalidModel()
+ {
+ // Arrange
+ Controller controller = new SimpleController();
+ TryValidateModelModel model = new TryValidateModelModel { IntegerProperty = 5 };
+
+ // Act & Assert
+ Assert.Throws<InvalidOperationException>(
+ () => controller.ValidateModel(model, "Prefix"),
+ "The model of type '" + model.GetType().FullName + "' is not valid.");
+
+ Assert.Equal("Out of range!", controller.ModelState["Prefix.IntegerProperty"].Errors[0].ErrorMessage);
+ }
+
+ [Fact]
+ public void ValidateControllerUsesCachedResolver()
+ {
+ var controller = new EmptyController();
+
+ var resolver = controller.Resolver;
+
+ Assert.Equal(DependencyResolver.CurrentCache.GetType(), resolver.GetType());
+ }
+
+
+ [Fact]
+ public void CreateActionInvokerCallsIntoResolverInstance()
+ {
+ // Controller uses an IDependencyResolver to create an IActionInvoker.
+ var controller = new EmptyController();
+ Mock<IDependencyResolver> resolverMock = new Mock<IDependencyResolver>();
+ Mock<IAsyncActionInvoker> actionInvokerMock = new Mock<IAsyncActionInvoker>();
+ resolverMock.Setup(r => r.GetService(typeof(IAsyncActionInvoker))).Returns(actionInvokerMock.Object);
+ controller.Resolver = resolverMock.Object;
+
+ var ai = controller.CreateActionInvoker();
+
+ resolverMock.Verify(r => r.GetService(typeof(IAsyncActionInvoker)), Times.Once());
+ Assert.Same(actionInvokerMock.Object, ai);
+ }
+
+ [Fact]
+ public void CreateActionInvokerCallsIntoResolverInstanceAndCreatesANewOneIfNecessary()
+ {
+ // If IDependencyResolver is set, but empty, falls back and still creates.
+ var controller = new EmptyController();
+ Mock<IDependencyResolver> resolverMock = new Mock<IDependencyResolver>();
+ resolverMock.Setup(r => r.GetService(typeof(IAsyncActionInvoker))).Returns(null);
+ controller.Resolver = resolverMock.Object;
+
+ IActionInvoker ai = controller.CreateActionInvoker();
+
+ resolverMock.Verify(r => r.GetService(typeof(IAsyncActionInvoker)), Times.Once());
+ Assert.NotNull(ai);
+ }
+
+ [Fact]
+ public void CreateTempProviderWithResolver()
+ {
+ // Controller uses an IDependencyResolver to create an IActionInvoker.
+ var controller = new EmptyController();
+ Mock<IDependencyResolver> resolverMock = new Mock<IDependencyResolver>();
+ Mock<ITempDataProvider> tempMock = new Mock<ITempDataProvider>();
+ resolverMock.Setup(r => r.GetService(typeof(ITempDataProvider))).Returns(tempMock.Object);
+ controller.Resolver = resolverMock.Object;
+
+ ITempDataProvider temp = controller.CreateTempDataProvider();
+
+ resolverMock.Verify(r => r.GetService(typeof(ITempDataProvider)), Times.Once());
+ Assert.Same(tempMock.Object, temp);
+ }
+
+ private class TryValidateModelModel
+ {
+ [Range(10, 20, ErrorMessage = "Out of range!")]
+ public int IntegerProperty { get; set; }
+ }
+
+ // Helpers
+
+ private class SimpleController : Controller
+ {
+ public SimpleController()
+ {
+ ControllerContext = new ControllerContext { Controller = this };
+ }
+
+ public void SimpleAction()
+ {
+ }
+ }
+
+ private static ControllerContext GetControllerContext(string actionName)
+ {
+ RouteData rd = new RouteData();
+ rd.Values["action"] = actionName;
+
+ Mock<HttpContextBase> mockHttpContext = HttpContextHelpers.GetMockHttpContext();
+ mockHttpContext.Setup(c => c.Session).Returns((HttpSessionStateBase)null);
+
+ return new ControllerContext(mockHttpContext.Object, rd, new Mock<Controller>().Object);
+ }
+
+ private static ControllerContext GetControllerContext(string actionName, string controllerName)
+ {
+ RouteData rd = new RouteData();
+ rd.Values["action"] = actionName;
+ rd.Values["controller"] = controllerName;
+
+ Mock<HttpContextBase> mockHttpContext = new Mock<HttpContextBase>();
+ mockHttpContext.Setup(c => c.Session).Returns((HttpSessionStateBase)null);
+
+ return new ControllerContext(mockHttpContext.Object, rd, new Mock<Controller>().Object);
+ }
+
+ private static Controller GetEmptyController()
+ {
+ ControllerContext context = GetControllerContext("Foo");
+ var controller = new EmptyController()
+ {
+ ControllerContext = context,
+ RouteCollection = new RouteCollection(),
+ TempData = new TempDataDictionary(),
+ TempDataProvider = new SessionStateTempDataProvider()
+ };
+ return controller;
+ }
+
+ private static HttpSessionStateBase GetEmptySession()
+ {
+ HttpSessionStateMock mockSession = new HttpSessionStateMock();
+ return mockSession;
+ }
+
+ private sealed class HttpSessionStateMock : HttpSessionStateBase
+ {
+ private Hashtable _sessionData = new Hashtable(StringComparer.OrdinalIgnoreCase);
+
+ public override void Remove(string name)
+ {
+ Assert.Equal<string>(SessionStateTempDataProvider.TempDataSessionStateKey, name);
+ _sessionData.Remove(name);
+ }
+
+ public override object this[string name]
+ {
+ get
+ {
+ Assert.Equal<string>(SessionStateTempDataProvider.TempDataSessionStateKey, name);
+ return _sessionData[name];
+ }
+ set
+ {
+ Assert.Equal<string>(SessionStateTempDataProvider.TempDataSessionStateKey, name);
+ _sessionData[name] = value;
+ }
+ }
+ }
+
+ public class Person
+ {
+ public string Name { get; set; }
+ public int Age { get; set; }
+ }
+
+ private class EmptyController : Controller
+ {
+ public new void HandleUnknownAction(string actionName)
+ {
+ base.HandleUnknownAction(actionName);
+ }
+
+ public void PublicInitialize(RequestContext requestContext)
+ {
+ base.Initialize(requestContext);
+ }
+
+ // Test can expose protected method as public.
+ public new IActionInvoker CreateActionInvoker()
+ {
+ return base.CreateActionInvoker();
+ }
+
+ public new ITempDataProvider CreateTempDataProvider()
+ {
+ return base.CreateTempDataProvider();
+ }
+ }
+
+
+ private sealed class UnknownActionController : Controller
+ {
+ public bool WasCalled;
+
+ protected override void HandleUnknownAction(string actionName)
+ {
+ WasCalled = true;
+ }
+ }
+
+ private sealed class TempDataHomeController : Controller
+ {
+ public ActionResult Index()
+ {
+ // Save UserID into TempData and redirect to greeting page
+ TempData["UserID"] = "user123";
+ return RedirectToAction("GreetUser");
+ }
+
+ public ActionResult GreetUser()
+ {
+ // Check that the UserID is present. If it's not
+ // there, redirect to error page. If it is, show
+ // the greet user view.
+ if (!TempData.ContainsKey("UserID"))
+ {
+ return RedirectToAction("ErrorPage");
+ }
+ ViewData["NewUserID"] = TempData["UserID"];
+ return View("GreetUser");
+ }
+ }
+
+ public class BrokenController : Controller
+ {
+ public BrokenController()
+ {
+ ActionInvoker = new ControllerActionInvoker()
+ {
+ DescriptorCache = new ControllerDescriptorCache()
+ };
+ }
+
+ public ActionResult Crash()
+ {
+ TempData["Key1"] = "Value1";
+ throw new InvalidOperationException("Crashing....");
+ }
+ }
+
+ private sealed class TestRouteController : Controller
+ {
+ public ActionResult Index()
+ {
+ return RedirectToAction("SomeAction");
+ }
+ }
+
+ [ModelBinder(typeof(MyModelBinder))]
+ private class MyModel
+ {
+ public ControllerContext ControllerContext;
+ public ModelBindingContext BindingContext;
+ }
+
+ private class MyModelSubclassed : MyModel
+ {
+ }
+
+ private class MyModelBinder : IModelBinder
+ {
+ public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
+ {
+ MyModel myModel = (MyModel)bindingContext.Model;
+ myModel.ControllerContext = controllerContext;
+ myModel.BindingContext = bindingContext;
+ return myModel;
+ }
+ }
+ }
+
+ internal class EmptyTempDataProvider : ITempDataProvider
+ {
+ public void SaveTempData(ControllerContext controllerContext, IDictionary<string, object> values)
+ {
+ }
+
+ public IDictionary<string, object> LoadTempData(ControllerContext controllerContext)
+ {
+ return new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Test/DataAnnotationsModelMetadataProviderTest.cs b/test/System.Web.Mvc.Test/Test/DataAnnotationsModelMetadataProviderTest.cs
new file mode 100644
index 00000000..f7c4f69c
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Test/DataAnnotationsModelMetadataProviderTest.cs
@@ -0,0 +1,10 @@
+namespace System.Web.Mvc.Test
+{
+ public class DataAnnotationsModelMetadataProviderTest : DataAnnotationsModelMetadataProviderTestBase
+ {
+ protected override AssociatedMetadataProvider MakeProvider()
+ {
+ return new DataAnnotationsModelMetadataProvider();
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Test/DataAnnotationsModelMetadataProviderTestBase.cs b/test/System.Web.Mvc.Test/Test/DataAnnotationsModelMetadataProviderTestBase.cs
new file mode 100644
index 00000000..90958788
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Test/DataAnnotationsModelMetadataProviderTestBase.cs
@@ -0,0 +1,614 @@
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.ComponentModel.DataAnnotations;
+using System.Linq;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Mvc.Test
+{
+ public abstract class DataAnnotationsModelMetadataProviderTestBase
+ {
+ protected abstract AssociatedMetadataProvider MakeProvider();
+
+ [Fact]
+ public void GetMetadataForPropertiesSetTypesAndPropertyNames()
+ {
+ // Arrange
+ var provider = MakeProvider();
+
+ // Act
+ IEnumerable<ModelMetadata> result = provider.GetMetadataForProperties("foo", typeof(string));
+
+ // Assert
+ Assert.True(result.Any(m => m.ModelType == typeof(int)
+ && m.PropertyName == "Length"
+ && (int)m.Model == 3));
+ }
+
+ [Fact]
+ public void GetMetadataForPropertySetsTypeAndPropertyName()
+ {
+ // Arrange
+ var provider = MakeProvider();
+
+ // Act
+ ModelMetadata result = provider.GetMetadataForProperty(null, typeof(string), "Length");
+
+ // Assert
+ Assert.Equal(typeof(int), result.ModelType);
+ Assert.Equal("Length", result.PropertyName);
+ }
+
+ [Fact]
+ public void GetMetadataForTypeSetsTypeWithNullPropertyName()
+ {
+ // Arrange
+ var provider = MakeProvider();
+
+ // Act
+ ModelMetadata result = provider.GetMetadataForType(null, typeof(string));
+
+ // Assert
+ Assert.Equal(typeof(string), result.ModelType);
+ Assert.Null(result.PropertyName);
+ }
+
+ // [HiddenInput] tests
+
+ class HiddenModel
+ {
+ public int NoAttribute { get; set; }
+
+ [HiddenInput]
+ public int DefaultHidden { get; set; }
+
+ [HiddenInput(DisplayValue = false)]
+ public int HiddenWithDisplayValueFalse { get; set; }
+
+ [HiddenInput]
+ [UIHint("CustomUIHint")]
+ public int HiddenAndUIHint { get; set; }
+ }
+
+ [Fact]
+ public void HiddenAttributeSetsTemplateHintAndHideSurroundingHtml()
+ {
+ // Arrange
+ var provider = MakeProvider();
+
+ // Act & Assert
+ ModelMetadata noAttributeMetadata = provider.GetMetadataForProperty(null, typeof(HiddenModel), "NoAttribute");
+ Assert.Null(noAttributeMetadata.TemplateHint);
+ Assert.False(noAttributeMetadata.HideSurroundingHtml);
+
+ ModelMetadata defaultHiddenMetadata = provider.GetMetadataForProperty(null, typeof(HiddenModel), "DefaultHidden");
+ Assert.Equal("HiddenInput", defaultHiddenMetadata.TemplateHint);
+ Assert.False(defaultHiddenMetadata.HideSurroundingHtml);
+
+ ModelMetadata hiddenWithDisplayValueFalseMetadata = provider.GetMetadataForProperty(null, typeof(HiddenModel), "HiddenWithDisplayValueFalse");
+ Assert.Equal("HiddenInput", hiddenWithDisplayValueFalseMetadata.TemplateHint);
+ Assert.True(hiddenWithDisplayValueFalseMetadata.HideSurroundingHtml);
+
+ // [UIHint] overrides the template hint from [Hidden]
+ Assert.Equal("CustomUIHint", provider.GetMetadataForProperty(null, typeof(HiddenModel), "HiddenAndUIHint").TemplateHint);
+ }
+
+ // [UIHint] tests
+
+ class UIHintModel
+ {
+ public int NoAttribute { get; set; }
+
+ [UIHint("MyCustomTemplate")]
+ public int DefaultUIHint { get; set; }
+
+ [UIHint("MyMvcTemplate", "MVC")]
+ public int MvcUIHint { get; set; }
+
+ [UIHint("MyWebFormsTemplate", "WebForms")]
+ public int NoMvcUIHint { get; set; }
+
+ [UIHint("MyDefaultTemplate")]
+ [UIHint("MyWebFormsTemplate", "WebForms")]
+ [UIHint("MyMvcTemplate", "MVC")]
+ public int MultipleUIHint { get; set; }
+ }
+
+ [Fact]
+ public void UIHintAttributeSetsTemplateHint()
+ {
+ // Arrange
+ var provider = MakeProvider();
+
+ // Act & Assert
+ Assert.Null(provider.GetMetadataForProperty(null, typeof(UIHintModel), "NoAttribute").TemplateHint);
+ Assert.Equal("MyCustomTemplate", provider.GetMetadataForProperty(null, typeof(UIHintModel), "DefaultUIHint").TemplateHint);
+ Assert.Equal("MyMvcTemplate", provider.GetMetadataForProperty(null, typeof(UIHintModel), "MvcUIHint").TemplateHint);
+ Assert.Null(provider.GetMetadataForProperty(null, typeof(UIHintModel), "NoMvcUIHint").TemplateHint);
+
+ Assert.Equal("MyMvcTemplate", provider.GetMetadataForProperty(null, typeof(UIHintModel), "MultipleUIHint").TemplateHint);
+ }
+
+ // [DataType] tests
+
+ class DataTypeModel
+ {
+ public int NoAttribute { get; set; }
+
+ [DataType(DataType.EmailAddress)]
+ public int EmailAddressProperty { get; set; }
+
+ [DataType("CustomDataType")]
+ public int CustomDataTypeProperty { get; set; }
+ }
+
+ [Fact]
+ public void DataTypeAttributeSetsDataTypeName()
+ {
+ // Arrange
+ var provider = MakeProvider();
+
+ // Act & Assert
+ Assert.Null(provider.GetMetadataForProperty(null, typeof(DataTypeModel), "NoAttribute").DataTypeName);
+ Assert.Equal("EmailAddress", provider.GetMetadataForProperty(null, typeof(DataTypeModel), "EmailAddressProperty").DataTypeName);
+ Assert.Equal("CustomDataType", provider.GetMetadataForProperty(null, typeof(DataTypeModel), "CustomDataTypeProperty").DataTypeName);
+ }
+
+ // [ReadOnly] & [Editable] tests
+
+ class ReadOnlyModel
+ {
+ public int NoAttributes { get; set; }
+
+ [ReadOnly(true)]
+ public int ReadOnlyAttribute { get; set; }
+
+ [Editable(false)]
+ public int EditableAttribute { get; set; }
+
+ [ReadOnly(true)]
+ [Editable(true)]
+ public int BothAttributes { get; set; }
+
+ // Editable trumps ReadOnly
+ }
+
+ [Fact]
+ public void ReadOnlyTests()
+ {
+ // Arrange
+ var provider = MakeProvider();
+
+ // Act & Assert
+ Assert.False(provider.GetMetadataForProperty(null, typeof(ReadOnlyModel), "NoAttributes").IsReadOnly);
+ Assert.True(provider.GetMetadataForProperty(null, typeof(ReadOnlyModel), "ReadOnlyAttribute").IsReadOnly);
+ Assert.True(provider.GetMetadataForProperty(null, typeof(ReadOnlyModel), "EditableAttribute").IsReadOnly);
+ Assert.False(provider.GetMetadataForProperty(null, typeof(ReadOnlyModel), "BothAttributes").IsReadOnly);
+ }
+
+ // [DisplayFormat] tests
+
+ class DisplayFormatModel
+ {
+ public int NoAttribute { get; set; }
+
+ [DisplayFormat(NullDisplayText = "(null value)")]
+ public int NullDisplayText { get; set; }
+
+ [DisplayFormat(DataFormatString = "Data {0} format")]
+ public int DisplayFormatString { get; set; }
+
+ [DisplayFormat(DataFormatString = "Data {0} format", ApplyFormatInEditMode = true)]
+ public int DisplayAndEditFormatString { get; set; }
+
+ [DisplayFormat(ConvertEmptyStringToNull = true)]
+ public int ConvertEmptyStringToNullTrue { get; set; }
+
+ [DisplayFormat(ConvertEmptyStringToNull = false)]
+ public int ConvertEmptyStringToNullFalse { get; set; }
+
+ [DataType(DataType.Currency)]
+ public int DataTypeWithoutDisplayFormatOverride { get; set; }
+
+ [DataType(DataType.Currency)]
+ [DisplayFormat(DataFormatString = "format override")]
+ public int DataTypeWithDisplayFormatOverride { get; set; }
+
+ [DisplayFormat(HtmlEncode = true)]
+ public int HtmlEncodeTrue { get; set; }
+
+ [DisplayFormat(HtmlEncode = false)]
+ public int HtmlEncodeFalse { get; set; }
+
+ [DataType(DataType.Currency)]
+ [DisplayFormat(HtmlEncode = false)]
+ public int HtmlEncodeFalseWithDataType { get; set; }
+
+ // DataType trumps DisplayFormat.HtmlEncode
+ }
+
+ [Fact]
+ public void DisplayFormatAttributetSetsNullDisplayText()
+ {
+ // Arrange
+ var provider = MakeProvider();
+
+ // Act & Assert
+ Assert.Null(provider.GetMetadataForProperty(null, typeof(DisplayFormatModel), "NoAttribute").NullDisplayText);
+ Assert.Equal("(null value)", provider.GetMetadataForProperty(null, typeof(DisplayFormatModel), "NullDisplayText").NullDisplayText);
+ }
+
+ [Fact]
+ public void DisplayFormatAttributeSetsDisplayFormatString()
+ {
+ // Arrange
+ var provider = MakeProvider();
+
+ // Act & Assert
+ Assert.Null(provider.GetMetadataForProperty(null, typeof(DisplayFormatModel), "NoAttribute").DisplayFormatString);
+ Assert.Equal("Data {0} format", provider.GetMetadataForProperty(null, typeof(DisplayFormatModel), "DisplayFormatString").DisplayFormatString);
+ Assert.Equal("Data {0} format", provider.GetMetadataForProperty(null, typeof(DisplayFormatModel), "DisplayAndEditFormatString").DisplayFormatString);
+ }
+
+ [Fact]
+ public void DisplayFormatAttributeSetEditFormatString()
+ {
+ // Arrange
+ var provider = MakeProvider();
+
+ // Act & Assert
+ Assert.Null(provider.GetMetadataForProperty(null, typeof(DisplayFormatModel), "NoAttribute").EditFormatString);
+ Assert.Null(provider.GetMetadataForProperty(null, typeof(DisplayFormatModel), "DisplayFormatString").EditFormatString);
+ Assert.Equal("Data {0} format", provider.GetMetadataForProperty(null, typeof(DisplayFormatModel), "DisplayAndEditFormatString").EditFormatString);
+ }
+
+ [Fact]
+ public void DisplayFormatAttributeSetsConvertEmptyStringToNull()
+ {
+ // Arrange
+ var provider = MakeProvider();
+
+ // Act & Assert
+ Assert.True(provider.GetMetadataForProperty(null, typeof(DisplayFormatModel), "NoAttribute").ConvertEmptyStringToNull);
+ Assert.True(provider.GetMetadataForProperty(null, typeof(DisplayFormatModel), "ConvertEmptyStringToNullTrue").ConvertEmptyStringToNull);
+ Assert.False(provider.GetMetadataForProperty(null, typeof(DisplayFormatModel), "ConvertEmptyStringToNullFalse").ConvertEmptyStringToNull);
+ }
+
+ [Fact]
+ public void DataTypeWithoutDisplayFormatOverrideUsesDataTypesDisplayFormat()
+ {
+ // Arrange
+ var provider = MakeProvider();
+
+ // Act
+ string result = provider.GetMetadataForProperty(null, typeof(DisplayFormatModel), "DataTypeWithoutDisplayFormatOverride").DisplayFormatString;
+
+ // Assert
+ Assert.Equal("{0:C}", result); // Currency's default format string
+ }
+
+ [Fact]
+ public void DataTypeWithDisplayFormatOverrideUsesDisplayFormatOverride()
+ {
+ // Arrange
+ var provider = MakeProvider();
+
+ // Act
+ string result = provider.GetMetadataForProperty(null, typeof(DisplayFormatModel), "DataTypeWithDisplayFormatOverride").DisplayFormatString;
+
+ // Assert
+ Assert.Equal("format override", result);
+ }
+
+ [Fact]
+ public void DataTypeInfluencedByDisplayFormatAttributeHtmlEncode()
+ {
+ // Arrange
+ var provider = MakeProvider();
+
+ // Act & Assert
+ Assert.Null(provider.GetMetadataForProperty(null, typeof(DisplayFormatModel), "NoAttribute").DataTypeName);
+ Assert.Null(provider.GetMetadataForProperty(null, typeof(DisplayFormatModel), "HtmlEncodeTrue").DataTypeName);
+ Assert.Equal("Html", provider.GetMetadataForProperty(null, typeof(DisplayFormatModel), "HtmlEncodeFalse").DataTypeName);
+ Assert.Equal("Currency", provider.GetMetadataForProperty(null, typeof(DisplayFormatModel), "HtmlEncodeFalseWithDataType").DataTypeName);
+ }
+
+ // [ScaffoldColumn] tests
+
+ class ScaffoldColumnModel
+ {
+ public int NoAttribute { get; set; }
+
+ [ScaffoldColumn(true)]
+ public int ScaffoldColumnTrue { get; set; }
+
+ [ScaffoldColumn(false)]
+ public int ScaffoldColumnFalse { get; set; }
+ }
+
+ [Fact]
+ public void ScaffoldColumnAttributeSetsShowForDisplay()
+ {
+ // Arrange
+ var provider = MakeProvider();
+
+ // Act & Assert
+ Assert.True(provider.GetMetadataForProperty(null, typeof(ScaffoldColumnModel), "NoAttribute").ShowForDisplay);
+ Assert.True(provider.GetMetadataForProperty(null, typeof(ScaffoldColumnModel), "ScaffoldColumnTrue").ShowForDisplay);
+ Assert.False(provider.GetMetadataForProperty(null, typeof(ScaffoldColumnModel), "ScaffoldColumnFalse").ShowForDisplay);
+ }
+
+ [Fact]
+ public void ScaffoldColumnAttributeSetsShowForEdit()
+ {
+ // Arrange
+ var provider = MakeProvider();
+
+ // Act & Assert
+ Assert.True(provider.GetMetadataForProperty(null, typeof(ScaffoldColumnModel), "NoAttribute").ShowForEdit);
+ Assert.True(provider.GetMetadataForProperty(null, typeof(ScaffoldColumnModel), "ScaffoldColumnTrue").ShowForEdit);
+ Assert.False(provider.GetMetadataForProperty(null, typeof(ScaffoldColumnModel), "ScaffoldColumnFalse").ShowForEdit);
+ }
+
+ // [DisplayColumn] tests
+
+ [DisplayColumn("NoPropertyWithThisName")]
+ class UnknownDisplayColumnModel
+ {
+ }
+
+ [Fact]
+ public void SimpleDisplayNameWithUnknownDisplayColumnThrows()
+ {
+ // Arrange
+ var provider = MakeProvider();
+
+ // Act & Assert
+ Assert.Throws<InvalidOperationException>(
+ () => provider.GetMetadataForType(() => new UnknownDisplayColumnModel(), typeof(UnknownDisplayColumnModel)).SimpleDisplayText,
+ typeof(UnknownDisplayColumnModel).FullName + " has a DisplayColumn attribute for NoPropertyWithThisName, but property NoPropertyWithThisName does not exist.");
+ }
+
+ [DisplayColumn("WriteOnlyProperty")]
+ class WriteOnlyDisplayColumnModel
+ {
+ public int WriteOnlyProperty
+ {
+ set { }
+ }
+ }
+
+ [DisplayColumn("PrivateReadPublicWriteProperty")]
+ class PrivateReadPublicWriteDisplayColumnModel
+ {
+ public int PrivateReadPublicWriteProperty { private get; set; }
+ }
+
+ [Fact]
+ public void SimpleDisplayTextForTypeWithWriteOnlyDisplayColumnThrows()
+ {
+ // Arrange
+ var provider = MakeProvider();
+
+ // Act & Assert
+ Assert.Throws<InvalidOperationException>(
+ () => provider.GetMetadataForType(() => new WriteOnlyDisplayColumnModel(), typeof(WriteOnlyDisplayColumnModel)).SimpleDisplayText,
+ typeof(WriteOnlyDisplayColumnModel).FullName + " has a DisplayColumn attribute for WriteOnlyProperty, but property WriteOnlyProperty does not have a public getter.");
+
+ Assert.Throws<InvalidOperationException>(
+ () => provider.GetMetadataForType(() => new PrivateReadPublicWriteDisplayColumnModel(), typeof(PrivateReadPublicWriteDisplayColumnModel)).SimpleDisplayText,
+ typeof(PrivateReadPublicWriteDisplayColumnModel).FullName + " has a DisplayColumn attribute for PrivateReadPublicWriteProperty, but property PrivateReadPublicWriteProperty does not have a public getter.");
+ }
+
+ [DisplayColumn("DisplayColumnProperty")]
+ class SimpleDisplayTextAttributeModel
+ {
+ public int FirstProperty
+ {
+ get { return 42; }
+ }
+
+ [ScaffoldColumn(false)]
+ public string DisplayColumnProperty { get; set; }
+ }
+
+ class SimpleDisplayTextAttributeModelContainer
+ {
+ [DisplayFormat(NullDisplayText = "This is the null display text")]
+ public SimpleDisplayTextAttributeModel Inner { get; set; }
+ }
+
+ [Fact]
+ public void SimpleDisplayTextForNonNullClassWithNonNullDisplayColumnValue()
+ {
+ // Arrange
+ string expected = "Custom property display value";
+ var provider = MakeProvider();
+ var model = new SimpleDisplayTextAttributeModel { DisplayColumnProperty = expected };
+ var metadata = provider.GetMetadataForType(() => model, typeof(SimpleDisplayTextAttributeModel));
+
+ // Act
+ string result = metadata.SimpleDisplayText;
+
+ // Assert
+ Assert.Equal(expected, result);
+ }
+
+ [Fact]
+ public void SimpleDisplayTextForNullClassRevertsToDefaultBehavior()
+ {
+ // Arrange
+ var provider = MakeProvider();
+ var metadata = provider.GetMetadataForProperty(null, typeof(SimpleDisplayTextAttributeModelContainer), "Inner");
+
+ // Act
+ string result = metadata.SimpleDisplayText;
+
+ // Assert
+ Assert.Equal("This is the null display text", result);
+ }
+
+ [Fact]
+ public void SimpleDisplayTextForNonNullClassWithNullDisplayColumnValueRevertsToDefaultBehavior()
+ {
+ // Arrange
+ var provider = MakeProvider();
+ var model = new SimpleDisplayTextAttributeModel();
+ var metadata = provider.GetMetadataForType(() => model, typeof(SimpleDisplayTextAttributeModel));
+
+ // Act
+ string result = metadata.SimpleDisplayText;
+
+ // Assert
+ Assert.Equal("42", result); // Falls back to the default logic of first property value
+ }
+
+ // [Required] tests
+
+ class IsRequiredModel
+ {
+ public int NonNullableWithout { get; set; }
+
+ public string NullableWithout { get; set; }
+
+ [Required]
+ public string NullableWith { get; set; }
+ }
+
+ [Fact]
+ public void IsRequiredTests()
+ {
+ // Arrange
+ var provider = MakeProvider();
+
+ // Act & Assert
+ Assert.True(provider.GetMetadataForProperty(null, typeof(IsRequiredModel), "NonNullableWithout").IsRequired);
+ Assert.False(provider.GetMetadataForProperty(null, typeof(IsRequiredModel), "NullableWithout").IsRequired);
+ Assert.True(provider.GetMetadataForProperty(null, typeof(IsRequiredModel), "NullableWith").IsRequired);
+ }
+
+ // [Display] & [DisplayName] tests
+
+ class DisplayModel
+ {
+ public int NoAttribute { get; set; }
+
+ // Description
+
+ [Display]
+ public int DescriptionNotSet { get; set; }
+
+ [Display(Description = "Description text")]
+ public int DescriptionSet { get; set; }
+
+ // DisplayName
+
+ [DisplayName("Value from DisplayName")]
+ public int DisplayNameAttributeNoDisplayAttribute { get; set; }
+
+ [Display]
+ public int DisplayAttributeNameNotSet { get; set; }
+
+ [Display(Name = "Non empty name")]
+ public int DisplayAttributeNonEmptyName { get; set; }
+
+ [Display]
+ [DisplayName("Value from DisplayName")]
+ public int BothAttributesNameNotSet { get; set; }
+
+ [Display(Name = "Value from Display")]
+ [DisplayName("Value from DisplayName")]
+ public int BothAttributes { get; set; }
+
+ // Display trumps DisplayName
+
+ // Order
+
+ [Display]
+ public int OrderNotSet { get; set; }
+
+ [Display(Order = 2112)]
+ public int OrderSet { get; set; }
+
+ // ShortDisplayName
+
+ [Display]
+ public int ShortNameNotSet { get; set; }
+
+ [Display(ShortName = "Short name")]
+ public int ShortNameSet { get; set; }
+
+ // Watermark
+
+ [Display]
+ public int PromptNotSet { get; set; }
+
+ [Display(Prompt = "Enter stuff here")]
+ public int PromptSet { get; set; }
+ }
+
+ [Fact]
+ public void DescriptionTests()
+ {
+ // Arrange
+ var provider = MakeProvider();
+
+ // Act & Assert
+ Assert.Null(provider.GetMetadataForProperty(null, typeof(DisplayModel), "NoAttribute").Description);
+ Assert.Null(provider.GetMetadataForProperty(null, typeof(DisplayModel), "DescriptionNotSet").Description);
+ Assert.Equal("Description text", provider.GetMetadataForProperty(null, typeof(DisplayModel), "DescriptionSet").Description);
+ }
+
+ [Fact]
+ public void DisplayNameTests()
+ {
+ // Arrange
+ var provider = MakeProvider();
+
+ // Act & Assert
+ Assert.Null(provider.GetMetadataForProperty(null, typeof(DisplayModel), "NoAttribute").DisplayName);
+ Assert.Equal("Value from DisplayName", provider.GetMetadataForProperty(null, typeof(DisplayModel), "DisplayNameAttributeNoDisplayAttribute").DisplayName);
+ Assert.Null(provider.GetMetadataForProperty(null, typeof(DisplayModel), "DisplayAttributeNameNotSet").DisplayName);
+ Assert.Equal("Non empty name", provider.GetMetadataForProperty(null, typeof(DisplayModel), "DisplayAttributeNonEmptyName").DisplayName);
+ Assert.Equal("Value from DisplayName", provider.GetMetadataForProperty(null, typeof(DisplayModel), "BothAttributesNameNotSet").DisplayName);
+ Assert.Equal("Value from Display", provider.GetMetadataForProperty(null, typeof(DisplayModel), "BothAttributes").DisplayName);
+ }
+
+ [Fact]
+ public void OrderTests()
+ {
+ // Arrange
+ var provider = MakeProvider();
+
+ // Act & Assert
+ Assert.Equal(10000, provider.GetMetadataForProperty(null, typeof(DisplayModel), "NoAttribute").Order);
+ Assert.Equal(10000, provider.GetMetadataForProperty(null, typeof(DisplayModel), "OrderNotSet").Order);
+ Assert.Equal(2112, provider.GetMetadataForProperty(null, typeof(DisplayModel), "OrderSet").Order);
+ }
+
+ [Fact]
+ public void ShortDisplayNameTests()
+ {
+ // Arrange
+ var provider = MakeProvider();
+
+ // Act & Assert
+ Assert.Null(provider.GetMetadataForProperty(null, typeof(DisplayModel), "NoAttribute").ShortDisplayName);
+ Assert.Null(provider.GetMetadataForProperty(null, typeof(DisplayModel), "ShortNameNotSet").ShortDisplayName);
+ Assert.Equal("Short name", provider.GetMetadataForProperty(null, typeof(DisplayModel), "ShortNameSet").ShortDisplayName);
+ }
+
+ [Fact]
+ public void WatermarkTests()
+ {
+ // Arrange
+ var provider = MakeProvider();
+
+ // Act & Assert
+ Assert.Null(provider.GetMetadataForProperty(null, typeof(DisplayModel), "NoAttribute").Watermark);
+ Assert.Null(provider.GetMetadataForProperty(null, typeof(DisplayModel), "PromptNotSet").Watermark);
+ Assert.Equal("Enter stuff here", provider.GetMetadataForProperty(null, typeof(DisplayModel), "PromptSet").Watermark);
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Test/DataAnnotationsModelValidatorProviderTest.cs b/test/System.Web.Mvc.Test/Test/DataAnnotationsModelValidatorProviderTest.cs
new file mode 100644
index 00000000..ab8c0342
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Test/DataAnnotationsModelValidatorProviderTest.cs
@@ -0,0 +1,725 @@
+using System.Collections.Generic;
+using System.ComponentModel.DataAnnotations;
+using System.Linq;
+using Moq;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Mvc.Test
+{
+ public class DataAnnotationsModelValidatorProviderTest
+ {
+ // Validation attribute adapter registration
+
+ private class MyValidationAttribute : ValidationAttribute
+ {
+ public override bool IsValid(object value)
+ {
+ throw new NotImplementedException();
+ }
+ }
+
+ private class MyValidationAttributeAdapter : DataAnnotationsModelValidator<MyValidationAttribute>
+ {
+ public MyValidationAttributeAdapter(ModelMetadata metadata, ControllerContext context, MyValidationAttribute attribute)
+ : base(metadata, context, attribute)
+ {
+ }
+ }
+
+ private class MyValidationAttributeAdapterBadCtor : ModelValidator
+ {
+ public MyValidationAttributeAdapterBadCtor(ModelMetadata metadata, ControllerContext context)
+ : base(metadata, context)
+ {
+ }
+
+ public override IEnumerable<ModelValidationResult> Validate(object container)
+ {
+ throw new NotImplementedException();
+ }
+ }
+
+ private class MyDefaultValidationAttributeAdapter : DataAnnotationsModelValidator
+ {
+ public MyDefaultValidationAttributeAdapter(ModelMetadata metadata, ControllerContext context, ValidationAttribute attribute)
+ : base(metadata, context, attribute)
+ {
+ }
+ }
+
+ [MyValidation]
+ private class MyValidatedClass
+ {
+ }
+
+ [Fact]
+ public void RegisterAdapter()
+ {
+ var oldFactories = DataAnnotationsModelValidatorProvider.AttributeFactories;
+
+ try
+ {
+ // Arrange
+ DataAnnotationsModelValidatorProvider.AttributeFactories = new Dictionary<Type, DataAnnotationsModelValidationFactory>();
+
+ // Act
+ DataAnnotationsModelValidatorProvider.RegisterAdapter(typeof(MyValidationAttribute), typeof(MyValidationAttributeAdapter));
+
+ // Assert
+ var type = DataAnnotationsModelValidatorProvider.AttributeFactories.Keys.Single();
+ Assert.Equal(typeof(MyValidationAttribute), type);
+
+ var factory = DataAnnotationsModelValidatorProvider.AttributeFactories.Values.Single();
+ var metadata = ModelMetadataProviders.Current.GetMetadataForType(() => null, typeof(object));
+ var context = new ControllerContext();
+ var attribute = new MyValidationAttribute();
+ var validator = factory(metadata, context, attribute);
+ Assert.IsType<MyValidationAttributeAdapter>(validator);
+ }
+ finally
+ {
+ DataAnnotationsModelValidatorProvider.AttributeFactories = oldFactories;
+ }
+ }
+
+ [Fact]
+ public void RegisterAdapterGuardClauses()
+ {
+ // Attribute type cannot be null
+ Assert.ThrowsArgumentNull(
+ () => DataAnnotationsModelValidatorProvider.RegisterAdapter(null, typeof(MyValidationAttributeAdapter)),
+ "attributeType");
+
+ // Adapter type cannot be null
+ Assert.ThrowsArgumentNull(
+ () => DataAnnotationsModelValidatorProvider.RegisterAdapter(typeof(MyValidationAttribute), null),
+ "adapterType");
+
+ // Validation attribute must derive from ValidationAttribute
+ Assert.Throws<ArgumentException>(
+ () => DataAnnotationsModelValidatorProvider.RegisterAdapter(typeof(object), typeof(MyValidationAttributeAdapter)),
+ "The type System.Object must derive from System.ComponentModel.DataAnnotations.ValidationAttribute\r\nParameter name: attributeType");
+
+ // Adapter must derive from ModelValidator
+ Assert.Throws<ArgumentException>(
+ () => DataAnnotationsModelValidatorProvider.RegisterAdapter(typeof(MyValidationAttribute), typeof(object)),
+ "The type System.Object must derive from System.Web.Mvc.ModelValidator\r\nParameter name: adapterType");
+
+ // Adapter must have the expected constructor
+ Assert.Throws<ArgumentException>(
+ () => DataAnnotationsModelValidatorProvider.RegisterAdapter(typeof(MyValidationAttribute), typeof(MyValidationAttributeAdapterBadCtor)),
+ "The type System.Web.Mvc.Test.DataAnnotationsModelValidatorProviderTest+MyValidationAttributeAdapterBadCtor must have a public constructor which accepts three parameters of types System.Web.Mvc.ModelMetadata, System.Web.Mvc.ControllerContext, and System.Web.Mvc.Test.DataAnnotationsModelValidatorProviderTest+MyValidationAttribute\r\nParameter name: adapterType");
+ }
+
+ [Fact]
+ public void RegisterAdapterFactory()
+ {
+ var oldFactories = DataAnnotationsModelValidatorProvider.AttributeFactories;
+
+ try
+ {
+ // Arrange
+ DataAnnotationsModelValidatorProvider.AttributeFactories = new Dictionary<Type, DataAnnotationsModelValidationFactory>();
+ DataAnnotationsModelValidationFactory factory = delegate { return null; };
+
+ // Act
+ DataAnnotationsModelValidatorProvider.RegisterAdapterFactory(typeof(MyValidationAttribute), factory);
+
+ // Assert
+ var type = DataAnnotationsModelValidatorProvider.AttributeFactories.Keys.Single();
+ Assert.Equal(typeof(MyValidationAttribute), type);
+ Assert.Same(factory, DataAnnotationsModelValidatorProvider.AttributeFactories.Values.Single());
+ }
+ finally
+ {
+ DataAnnotationsModelValidatorProvider.AttributeFactories = oldFactories;
+ }
+ }
+
+ [Fact]
+ public void RegisterAdapterFactoryGuardClauses()
+ {
+ DataAnnotationsModelValidationFactory factory = (metadata, context, attribute) => null;
+
+ // Attribute type cannot be null
+ Assert.ThrowsArgumentNull(
+ () => DataAnnotationsModelValidatorProvider.RegisterAdapterFactory(null, factory),
+ "attributeType");
+
+ // Factory cannot be null
+ Assert.ThrowsArgumentNull(
+ () => DataAnnotationsModelValidatorProvider.RegisterAdapterFactory(typeof(MyValidationAttribute), null),
+ "factory");
+
+ // Validation attribute must derive from ValidationAttribute
+ Assert.Throws<ArgumentException>(
+ () => DataAnnotationsModelValidatorProvider.RegisterAdapterFactory(typeof(object), factory),
+ "The type System.Object must derive from System.ComponentModel.DataAnnotations.ValidationAttribute\r\nParameter name: attributeType");
+ }
+
+ [Fact]
+ public void RegisterDefaultAdapter()
+ {
+ var oldFactory = DataAnnotationsModelValidatorProvider.DefaultAttributeFactory;
+
+ try
+ {
+ // Arrange
+ var metadata = ModelMetadataProviders.Current.GetMetadataForType(() => null, typeof(MyValidatedClass));
+ var context = new ControllerContext();
+ DataAnnotationsModelValidatorProvider.RegisterDefaultAdapter(typeof(MyDefaultValidationAttributeAdapter));
+
+ // Act
+ var result = new DataAnnotationsModelValidatorProvider().GetValidators(metadata, context).Single();
+
+ // Assert
+ Assert.IsType<MyDefaultValidationAttributeAdapter>(result);
+ }
+ finally
+ {
+ DataAnnotationsModelValidatorProvider.DefaultAttributeFactory = oldFactory;
+ }
+ }
+
+ [Fact]
+ public void RegisterDefaultAdapterGuardClauses()
+ {
+ // Adapter type cannot be null
+ Assert.ThrowsArgumentNull(
+ () => DataAnnotationsModelValidatorProvider.RegisterDefaultAdapter(null),
+ "adapterType");
+
+ // Adapter must derive from ModelValidator
+ Assert.Throws<ArgumentException>(
+ () => DataAnnotationsModelValidatorProvider.RegisterDefaultAdapter(typeof(object)),
+ "The type System.Object must derive from System.Web.Mvc.ModelValidator\r\nParameter name: adapterType");
+
+ // Adapter must have the expected constructor
+ Assert.Throws<ArgumentException>(
+ () => DataAnnotationsModelValidatorProvider.RegisterDefaultAdapter(typeof(MyValidationAttributeAdapterBadCtor)),
+ "The type System.Web.Mvc.Test.DataAnnotationsModelValidatorProviderTest+MyValidationAttributeAdapterBadCtor must have a public constructor which accepts three parameters of types System.Web.Mvc.ModelMetadata, System.Web.Mvc.ControllerContext, and System.ComponentModel.DataAnnotations.ValidationAttribute\r\nParameter name: adapterType");
+ }
+
+ [Fact]
+ public void RegisterDefaultAdapterFactory()
+ {
+ var oldFactory = DataAnnotationsModelValidatorProvider.DefaultAttributeFactory;
+
+ try
+ {
+ // Arrange
+ var metadata = ModelMetadataProviders.Current.GetMetadataForType(() => null, typeof(MyValidatedClass));
+ var context = new ControllerContext();
+ ModelValidator validator = new Mock<ModelValidator>(metadata, context).Object;
+ DataAnnotationsModelValidationFactory factory = delegate { return validator; };
+ DataAnnotationsModelValidatorProvider.RegisterDefaultAdapterFactory(factory);
+
+ // Act
+ var result = new DataAnnotationsModelValidatorProvider().GetValidators(metadata, context).Single();
+
+ // Assert
+ Assert.Same(validator, result);
+ }
+ finally
+ {
+ DataAnnotationsModelValidatorProvider.DefaultAttributeFactory = oldFactory;
+ }
+ }
+
+ [Fact]
+ public void RegisterDefaultAdapterFactoryGuardClauses()
+ {
+ // Factory cannot be null
+ Assert.ThrowsArgumentNull(
+ () => DataAnnotationsModelValidatorProvider.RegisterDefaultAdapterFactory(null),
+ "factory");
+ }
+
+ // IValidatableObject adapter registration
+
+ private class MyValidatableAdapter : ModelValidator
+ {
+ public MyValidatableAdapter(ModelMetadata metadata, ControllerContext context)
+ : base(metadata, context)
+ {
+ }
+
+ public override IEnumerable<ModelValidationResult> Validate(object container)
+ {
+ throw new NotImplementedException();
+ }
+ }
+
+ private class MyValidatableAdapterBadCtor : ModelValidator
+ {
+ public MyValidatableAdapterBadCtor(ModelMetadata metadata, ControllerContext context, int unused)
+ : base(metadata, context)
+ {
+ }
+
+ public override IEnumerable<ModelValidationResult> Validate(object container)
+ {
+ throw new NotImplementedException();
+ }
+ }
+
+ private class MyValidatableClass : IValidatableObject
+ {
+ public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
+ {
+ throw new NotImplementedException();
+ }
+ }
+
+ [Fact]
+ public void RegisterValidatableObjectAdapter()
+ {
+ var oldFactories = DataAnnotationsModelValidatorProvider.ValidatableFactories;
+
+ try
+ {
+ // Arrange
+ DataAnnotationsModelValidatorProvider.ValidatableFactories = new Dictionary<Type, DataAnnotationsValidatableObjectAdapterFactory>();
+ IValidatableObject validatable = new Mock<IValidatableObject>().Object;
+
+ // Act
+ DataAnnotationsModelValidatorProvider.RegisterValidatableObjectAdapter(validatable.GetType(), typeof(MyValidatableAdapter));
+
+ // Assert
+ var type = DataAnnotationsModelValidatorProvider.ValidatableFactories.Keys.Single();
+ Assert.Equal(validatable.GetType(), type);
+
+ var factory = DataAnnotationsModelValidatorProvider.ValidatableFactories.Values.Single();
+ var metadata = ModelMetadataProviders.Current.GetMetadataForType(() => null, typeof(object));
+ var context = new ControllerContext();
+ var validator = factory(metadata, context);
+ Assert.IsType<MyValidatableAdapter>(validator);
+ }
+ finally
+ {
+ DataAnnotationsModelValidatorProvider.ValidatableFactories = oldFactories;
+ }
+ }
+
+ [Fact]
+ public void RegisterValidatableObjectAdapterGuardClauses()
+ {
+ // Attribute type cannot be null
+ Assert.ThrowsArgumentNull(
+ () => DataAnnotationsModelValidatorProvider.RegisterValidatableObjectAdapter(null, typeof(MyValidatableAdapter)),
+ "modelType");
+
+ // Adapter type cannot be null
+ Assert.ThrowsArgumentNull(
+ () => DataAnnotationsModelValidatorProvider.RegisterValidatableObjectAdapter(typeof(MyValidatableClass), null),
+ "adapterType");
+
+ // Validation attribute must derive from ValidationAttribute
+ Assert.Throws<ArgumentException>(
+ () => DataAnnotationsModelValidatorProvider.RegisterValidatableObjectAdapter(typeof(object), typeof(MyValidatableAdapter)),
+ "The type System.Object must derive from System.ComponentModel.DataAnnotations.IValidatableObject\r\nParameter name: modelType");
+
+ // Adapter must derive from ModelValidator
+ Assert.Throws<ArgumentException>(
+ () => DataAnnotationsModelValidatorProvider.RegisterValidatableObjectAdapter(typeof(MyValidatableClass), typeof(object)),
+ "The type System.Object must derive from System.Web.Mvc.ModelValidator\r\nParameter name: adapterType");
+
+ // Adapter must have the expected constructor
+ Assert.Throws<ArgumentException>(
+ () => DataAnnotationsModelValidatorProvider.RegisterValidatableObjectAdapter(typeof(MyValidatableClass), typeof(MyValidatableAdapterBadCtor)),
+ "The type System.Web.Mvc.Test.DataAnnotationsModelValidatorProviderTest+MyValidatableAdapterBadCtor must have a public constructor which accepts two parameters of types System.Web.Mvc.ModelMetadata and System.Web.Mvc.ControllerContext.\r\nParameter name: adapterType");
+ }
+
+ [Fact]
+ public void RegisterValidatableObjectAdapterFactory()
+ {
+ var oldFactories = DataAnnotationsModelValidatorProvider.ValidatableFactories;
+
+ try
+ {
+ // Arrange
+ DataAnnotationsModelValidatorProvider.ValidatableFactories = new Dictionary<Type, DataAnnotationsValidatableObjectAdapterFactory>();
+ DataAnnotationsValidatableObjectAdapterFactory factory = delegate { return null; };
+
+ // Act
+ DataAnnotationsModelValidatorProvider.RegisterValidatableObjectAdapterFactory(typeof(MyValidatableClass), factory);
+
+ // Assert
+ var type = DataAnnotationsModelValidatorProvider.ValidatableFactories.Keys.Single();
+ Assert.Equal(typeof(MyValidatableClass), type);
+ Assert.Same(factory, DataAnnotationsModelValidatorProvider.ValidatableFactories.Values.Single());
+ }
+ finally
+ {
+ DataAnnotationsModelValidatorProvider.ValidatableFactories = oldFactories;
+ }
+ }
+
+ [Fact]
+ public void RegisterValidatableObjectAdapterFactoryGuardClauses()
+ {
+ DataAnnotationsValidatableObjectAdapterFactory factory = (metadata, context) => null;
+
+ // Attribute type cannot be null
+ Assert.ThrowsArgumentNull(
+ () => DataAnnotationsModelValidatorProvider.RegisterValidatableObjectAdapterFactory(null, factory),
+ "modelType");
+
+ // Factory cannot be null
+ Assert.ThrowsArgumentNull(
+ () => DataAnnotationsModelValidatorProvider.RegisterValidatableObjectAdapterFactory(typeof(MyValidatableClass), null),
+ "factory");
+
+ // Validation attribute must derive from ValidationAttribute
+ Assert.Throws<ArgumentException>(
+ () => DataAnnotationsModelValidatorProvider.RegisterValidatableObjectAdapterFactory(typeof(object), factory),
+ "The type System.Object must derive from System.ComponentModel.DataAnnotations.IValidatableObject\r\nParameter name: modelType");
+ }
+
+ [Fact]
+ public void RegisterDefaultValidatableObjectAdapter()
+ {
+ var oldFactory = DataAnnotationsModelValidatorProvider.DefaultValidatableFactory;
+
+ try
+ {
+ // Arrange
+ var metadata = ModelMetadataProviders.Current.GetMetadataForType(() => null, typeof(MyValidatableClass));
+ var context = new ControllerContext();
+ DataAnnotationsModelValidatorProvider.RegisterDefaultValidatableObjectAdapter(typeof(MyValidatableAdapter));
+
+ // Act
+ var result = new DataAnnotationsModelValidatorProvider().GetValidators(metadata, context).Single();
+
+ // Assert
+ Assert.IsType<MyValidatableAdapter>(result);
+ }
+ finally
+ {
+ DataAnnotationsModelValidatorProvider.DefaultValidatableFactory = oldFactory;
+ }
+ }
+
+ [Fact]
+ public void RegisterDefaultValidatableObjectAdapterGuardClauses()
+ {
+ // Adapter type cannot be null
+ Assert.ThrowsArgumentNull(
+ () => DataAnnotationsModelValidatorProvider.RegisterDefaultValidatableObjectAdapter(null),
+ "adapterType");
+
+ // Adapter must derive from ModelValidator
+ Assert.Throws<ArgumentException>(
+ () => DataAnnotationsModelValidatorProvider.RegisterDefaultValidatableObjectAdapter(typeof(object)),
+ "The type System.Object must derive from System.Web.Mvc.ModelValidator\r\nParameter name: adapterType");
+
+ // Adapter must have the expected constructor
+ Assert.Throws<ArgumentException>(
+ () => DataAnnotationsModelValidatorProvider.RegisterDefaultValidatableObjectAdapter(typeof(MyValidatableAdapterBadCtor)),
+ "The type System.Web.Mvc.Test.DataAnnotationsModelValidatorProviderTest+MyValidatableAdapterBadCtor must have a public constructor which accepts two parameters of types System.Web.Mvc.ModelMetadata and System.Web.Mvc.ControllerContext.\r\nParameter name: adapterType");
+ }
+
+ [Fact]
+ public void RegisterDefaultValidatableObjectAdapterFactory()
+ {
+ var oldFactory = DataAnnotationsModelValidatorProvider.DefaultValidatableFactory;
+
+ try
+ {
+ // Arrange
+ var metadata = ModelMetadataProviders.Current.GetMetadataForType(() => null, typeof(MyValidatableClass));
+ var context = new ControllerContext();
+ ModelValidator validator = new Mock<ModelValidator>(metadata, context).Object;
+ DataAnnotationsValidatableObjectAdapterFactory factory = delegate { return validator; };
+ DataAnnotationsModelValidatorProvider.RegisterDefaultValidatableObjectAdapterFactory(factory);
+
+ // Act
+ var result = new DataAnnotationsModelValidatorProvider().GetValidators(metadata, context).Single();
+
+ // Assert
+ Assert.Same(validator, result);
+ }
+ finally
+ {
+ DataAnnotationsModelValidatorProvider.DefaultValidatableFactory = oldFactory;
+ }
+ }
+
+ [Fact]
+ public void RegisterDefaultValidatableObjectAdapterFactoryGuardClauses()
+ {
+ // Factory cannot be null
+ Assert.ThrowsArgumentNull(
+ () => DataAnnotationsModelValidatorProvider.RegisterDefaultValidatableObjectAdapterFactory(null),
+ "factory");
+ }
+
+ // Pre-configured adapters
+
+ [Fact]
+ public void AdapterForRangeAttributeRegistered()
+ {
+ // Arrange
+ var metadata = ModelMetadataProviders.Current.GetMetadataForType(() => null, typeof(object));
+ var context = new ControllerContext();
+ var adapters = DataAnnotationsModelValidatorProvider.AttributeFactories;
+ var adapterFactory = adapters.Single(kvp => kvp.Key == typeof(RangeAttribute)).Value;
+
+ // Act
+ var adapter = adapterFactory(metadata, context, new RangeAttribute(1, 100));
+
+ // Assert
+ Assert.IsType<RangeAttributeAdapter>(adapter);
+ }
+
+ [Fact]
+ public void AdapterForRegularExpressionAttributeRegistered()
+ {
+ // Arrange
+ var metadata = ModelMetadataProviders.Current.GetMetadataForType(() => null, typeof(object));
+ var context = new ControllerContext();
+ var adapters = DataAnnotationsModelValidatorProvider.AttributeFactories;
+ var adapterFactory = adapters.Single(kvp => kvp.Key == typeof(RegularExpressionAttribute)).Value;
+
+ // Act
+ var adapter = adapterFactory(metadata, context, new RegularExpressionAttribute("abc"));
+
+ // Assert
+ Assert.IsType<RegularExpressionAttributeAdapter>(adapter);
+ }
+
+ [Fact]
+ public void AdapterForRequiredAttributeRegistered()
+ {
+ // Arrange
+ var metadata = ModelMetadataProviders.Current.GetMetadataForType(() => null, typeof(object));
+ var context = new ControllerContext();
+ var adapters = DataAnnotationsModelValidatorProvider.AttributeFactories;
+ var adapterFactory = adapters.Single(kvp => kvp.Key == typeof(RequiredAttribute)).Value;
+
+ // Act
+ var adapter = adapterFactory(metadata, context, new RequiredAttribute());
+
+ // Assert
+ Assert.IsType<RequiredAttributeAdapter>(adapter);
+ }
+
+ [Fact]
+ public void AdapterForStringLengthAttributeRegistered()
+ {
+ // Arrange
+ var metadata = ModelMetadataProviders.Current.GetMetadataForType(() => null, typeof(object));
+ var context = new ControllerContext();
+ var adapters = DataAnnotationsModelValidatorProvider.AttributeFactories;
+ var adapterFactory = adapters.Single(kvp => kvp.Key == typeof(StringLengthAttribute)).Value;
+
+ // Act
+ var adapter = adapterFactory(metadata, context, new StringLengthAttribute(6));
+
+ // Assert
+ Assert.IsType<StringLengthAttributeAdapter>(adapter);
+ }
+
+ // Default adapter factory for unknown attribute type
+
+ [Fact]
+ public void UnknownValidationAttributeGetsDefaultAdapter()
+ {
+ // Arrange
+ var provider = new DataAnnotationsModelValidatorProvider();
+ var context = new ControllerContext();
+ var metadata = ModelMetadataProviders.Current.GetMetadataForType(() => null, typeof(DummyClassWithDummyValidationAttribute));
+
+ // Act
+ IEnumerable<ModelValidator> validators = provider.GetValidators(metadata, context);
+
+ // Assert
+ var validator = validators.Single();
+ Assert.IsType<DataAnnotationsModelValidator>(validator);
+ }
+
+ private class DummyValidationAttribute : ValidationAttribute
+ {
+ }
+
+ [DummyValidation]
+ private class DummyClassWithDummyValidationAttribute
+ {
+ }
+
+ // Default IValidatableObject adapter factory
+
+ [Fact]
+ public void IValidatableObjectGetsAValidator()
+ {
+ // Arrange
+ var provider = new DataAnnotationsModelValidatorProvider();
+ var mockValidatable = new Mock<IValidatableObject>();
+ var context = new ControllerContext();
+ var metadata = ModelMetadataProviders.Current.GetMetadataForType(() => null, mockValidatable.Object.GetType());
+
+ // Act
+ IEnumerable<ModelValidator> validators = provider.GetValidators(metadata, context);
+
+ // Assert
+ Assert.Single(validators);
+ }
+
+ // Implicit [Required] attribute
+
+ [Fact]
+ public void ReferenceTypesDontGetImplicitRequiredAttribute()
+ {
+ // Arrange
+ var provider = new DataAnnotationsModelValidatorProvider();
+ var context = new ControllerContext();
+ var metadata = ModelMetadataProviders.Current.GetMetadataForType(() => null, typeof(string));
+
+ // Act
+ IEnumerable<ModelValidator> validators = provider.GetValidators(metadata, context);
+
+ // Assert
+ Assert.Empty(validators);
+ }
+
+ [Fact]
+ public void NonNullableValueTypesGetImplicitRequiredAttribute()
+ {
+ // Arrange
+ var provider = new DataAnnotationsModelValidatorProvider();
+ var context = new ControllerContext();
+ var metadata = ModelMetadataProviders.Current.GetMetadataForProperty(() => null, typeof(DummyRequiredAttributeHelperClass), "WithoutAttribute");
+
+ // Act
+ IEnumerable<ModelValidator> validators = provider.GetValidators(metadata, context);
+
+ // Assert
+ ModelValidator validator = validators.Single();
+ ModelClientValidationRule rule = validator.GetClientValidationRules().Single();
+ Assert.IsType<ModelClientValidationRequiredRule>(rule);
+ }
+
+ [Fact]
+ public void NonNullableValueTypesWithExplicitRequiredAttributeDoesntGetImplictRequiredAttribute()
+ {
+ // Arrange
+ var provider = new DataAnnotationsModelValidatorProvider();
+ var context = new ControllerContext();
+ var metadata = ModelMetadataProviders.Current.GetMetadataForProperty(() => null, typeof(DummyRequiredAttributeHelperClass), "WithAttribute");
+
+ // Act
+ IEnumerable<ModelValidator> validators = provider.GetValidators(metadata, context);
+
+ // Assert
+ ModelValidator validator = validators.Single();
+ ModelClientValidationRule rule = validator.GetClientValidationRules().Single();
+ Assert.IsType<ModelClientValidationRequiredRule>(rule);
+ Assert.Equal("Custom Required Message", rule.ErrorMessage);
+ }
+
+ [Fact]
+ public void NonNullableValueTypeDoesntGetImplicitRequiredAttributeWhenFlagIsOff()
+ {
+ DataAnnotationsModelValidatorProvider.AddImplicitRequiredAttributeForValueTypes = false;
+
+ try
+ {
+ // Arrange
+ var provider = new DataAnnotationsModelValidatorProvider();
+ var context = new ControllerContext();
+ var metadata = ModelMetadataProviders.Current.GetMetadataForProperty(() => null, typeof(DummyRequiredAttributeHelperClass), "WithoutAttribute");
+
+ // Act
+ IEnumerable<ModelValidator> validators = provider.GetValidators(metadata, context);
+
+ // Assert
+ Assert.Empty(validators);
+ }
+ finally
+ {
+ DataAnnotationsModelValidatorProvider.AddImplicitRequiredAttributeForValueTypes = true;
+ }
+ }
+
+ private class DummyRequiredAttributeHelperClass
+ {
+ [Required(ErrorMessage = "Custom Required Message")]
+ public int WithAttribute { get; set; }
+
+ public int WithoutAttribute { get; set; }
+ }
+
+ // Integration with metadata system
+
+ [Fact]
+ public void DoesNotReadPropertyValue()
+ {
+ // Arrange
+ ObservableModel model = new ObservableModel();
+ ModelMetadata metadata = new DataAnnotationsModelMetadataProvider().GetMetadataForProperty(() => model.TheProperty, typeof(ObservableModel), "TheProperty");
+ ControllerContext controllerContext = new ControllerContext();
+
+ // Act
+ ModelValidator[] validators = new DataAnnotationsModelValidatorProvider().GetValidators(metadata, controllerContext).ToArray();
+ ModelValidationResult[] results = validators.SelectMany(o => o.Validate(model)).ToArray();
+
+ // Assert
+ Assert.Empty(validators);
+ Assert.False(model.PropertyWasRead());
+ }
+
+ private class ObservableModel
+ {
+ private bool _propertyWasRead;
+
+ public string TheProperty
+ {
+ get
+ {
+ _propertyWasRead = true;
+ return "Hello";
+ }
+ }
+
+ public bool PropertyWasRead()
+ {
+ return _propertyWasRead;
+ }
+ }
+
+ private class BaseModel
+ {
+ public virtual string MyProperty { get; set; }
+ }
+
+ private class DerivedModel : BaseModel
+ {
+ [StringLength(10)]
+ public override string MyProperty
+ {
+ get { return base.MyProperty; }
+ set { base.MyProperty = value; }
+ }
+ }
+
+ [Fact]
+ public void GetValidatorsReturnsClientValidatorForDerivedTypeAppliedAgainstBaseTypeViaFromLambdaExpression()
+ { // Dev10 Bug #868619
+ // Arrange
+ var provider = new DataAnnotationsModelValidatorProvider();
+ var context = new ControllerContext();
+ var viewdata = new ViewDataDictionary<DerivedModel>();
+ var metadata = ModelMetadata.FromLambdaExpression(m => m.MyProperty, viewdata); // Bug is in FromLambdaExpression
+
+ // Act
+ IEnumerable<ModelValidator> validators = provider.GetValidators(metadata, context);
+
+ // Assert
+ ModelValidator validator = validators.Single();
+ ModelClientValidationRule clientRule = validator.GetClientValidationRules().Single();
+ Assert.IsType<ModelClientValidationStringLengthRule>(clientRule);
+ Assert.Equal(10, clientRule.ValidationParameters["max"]);
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Test/DataAnnotationsModelValidatorTest.cs b/test/System.Web.Mvc.Test/Test/DataAnnotationsModelValidatorTest.cs
new file mode 100644
index 00000000..092fa2b1
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Test/DataAnnotationsModelValidatorTest.cs
@@ -0,0 +1,158 @@
+using System.Collections.Generic;
+using System.ComponentModel.DataAnnotations;
+using System.Linq;
+using Moq;
+using Moq.Protected;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Mvc.Test
+{
+ public class DataAnnotationsModelValidatorTest
+ {
+ [Fact]
+ public void ConstructorGuards()
+ {
+ // Arrange
+ ModelMetadata metadata = ModelMetadataProviders.Current.GetMetadataForType(null, typeof(object));
+ ControllerContext context = new ControllerContext();
+ RequiredAttribute attribute = new RequiredAttribute();
+
+ // Act & Assert
+ Assert.ThrowsArgumentNull(
+ () => new DataAnnotationsModelValidator(null, context, attribute),
+ "metadata");
+ Assert.ThrowsArgumentNull(
+ () => new DataAnnotationsModelValidator(metadata, null, attribute),
+ "controllerContext");
+ Assert.ThrowsArgumentNull(
+ () => new DataAnnotationsModelValidator(metadata, context, null),
+ "attribute");
+ }
+
+ [Fact]
+ public void ValuesSet()
+ {
+ // Arrange
+ ModelMetadata metadata = ModelMetadataProviders.Current.GetMetadataForProperty(() => 15, typeof(string), "Length");
+ ControllerContext context = new ControllerContext();
+ RequiredAttribute attribute = new RequiredAttribute();
+
+ // Act
+ DataAnnotationsModelValidator validator = new DataAnnotationsModelValidator(metadata, context, attribute);
+
+ // Assert
+ Assert.Same(attribute, validator.Attribute);
+ Assert.Equal(attribute.FormatErrorMessage("Length"), validator.ErrorMessage);
+ }
+
+ [Fact]
+ public void NoClientRulesByDefault()
+ {
+ // Arrange
+ ModelMetadata metadata = ModelMetadataProviders.Current.GetMetadataForProperty(() => 15, typeof(string), "Length");
+ ControllerContext context = new ControllerContext();
+ RequiredAttribute attribute = new RequiredAttribute();
+
+ // Act
+ DataAnnotationsModelValidator validator = new DataAnnotationsModelValidator(metadata, context, attribute);
+
+ // Assert
+ Assert.Empty(validator.GetClientValidationRules());
+ }
+
+ [Fact]
+ public void ValidateWithIsValidTrue()
+ {
+ // Arrange
+ ModelMetadata metadata = ModelMetadataProviders.Current.GetMetadataForProperty(() => 15, typeof(string), "Length");
+ ControllerContext context = new ControllerContext();
+ Mock<ValidationAttribute> attribute = new Mock<ValidationAttribute> { CallBase = true };
+ attribute.Setup(a => a.IsValid(metadata.Model)).Returns(true);
+ DataAnnotationsModelValidator validator = new DataAnnotationsModelValidator(metadata, context, attribute.Object);
+
+ // Act
+ IEnumerable<ModelValidationResult> result = validator.Validate(null);
+
+ // Assert
+ Assert.Empty(result);
+ }
+
+ [Fact]
+ public void ValidateWithIsValidFalse()
+ {
+ // Arrange
+ ModelMetadata metadata = ModelMetadataProviders.Current.GetMetadataForProperty(() => 15, typeof(string), "Length");
+ ControllerContext context = new ControllerContext();
+ Mock<ValidationAttribute> attribute = new Mock<ValidationAttribute> { CallBase = true };
+ attribute.Setup(a => a.IsValid(metadata.Model)).Returns(false);
+ DataAnnotationsModelValidator validator = new DataAnnotationsModelValidator(metadata, context, attribute.Object);
+
+ // Act
+ IEnumerable<ModelValidationResult> result = validator.Validate(null);
+
+ // Assert
+ var validationResult = result.Single();
+ Assert.Equal("", validationResult.MemberName);
+ Assert.Equal(attribute.Object.FormatErrorMessage("Length"), validationResult.Message);
+ }
+
+ [Fact]
+ public void ValidatateWithValidationResultSuccess()
+ {
+ // Arrange
+ ModelMetadata metadata = ModelMetadataProviders.Current.GetMetadataForProperty(() => 15, typeof(string), "Length");
+ ControllerContext context = new ControllerContext();
+ Mock<ValidationAttribute> attribute = new Mock<ValidationAttribute> { CallBase = true };
+ attribute.Protected()
+ .Setup<ValidationResult>("IsValid", ItExpr.IsAny<object>(), ItExpr.IsAny<ValidationContext>())
+ .Returns(ValidationResult.Success);
+ DataAnnotationsModelValidator validator = new DataAnnotationsModelValidator(metadata, context, attribute.Object);
+
+ // Act
+ IEnumerable<ModelValidationResult> result = validator.Validate(null);
+
+ // Assert
+ Assert.Empty(result);
+ }
+
+ [Fact]
+ public void IsRequiredTests()
+ {
+ // Arrange
+ ModelMetadata metadata = ModelMetadataProviders.Current.GetMetadataForProperty(() => 15, typeof(string), "Length");
+ ControllerContext context = new ControllerContext();
+
+ // Act & Assert
+ Assert.False(new DataAnnotationsModelValidator(metadata, context, new RangeAttribute(10, 20)).IsRequired);
+ Assert.True(new DataAnnotationsModelValidator(metadata, context, new RequiredAttribute()).IsRequired);
+ Assert.True(new DataAnnotationsModelValidator(metadata, context, new DerivedRequiredAttribute()).IsRequired);
+ }
+
+ class DerivedRequiredAttribute : RequiredAttribute
+ {
+ }
+
+ [Fact]
+ public void AttributeWithIClientValidatableGetsClientValidationRules()
+ {
+ // Arrange
+ var expected = new ModelClientValidationStringLengthRule("Error", 1, 10);
+ var context = new ControllerContext();
+ var metadata = ModelMetadataProviders.Current.GetMetadataForType(() => null, typeof(string));
+ var attribute = new Mock<ValidationAttribute> { CallBase = true };
+ attribute.As<IClientValidatable>()
+ .Setup(cv => cv.GetClientValidationRules(metadata, context))
+ .Returns(new[] { expected })
+ .Verifiable();
+ var validator = new DataAnnotationsModelValidator(metadata, context, attribute.Object);
+
+ // Act
+ ModelClientValidationRule actual = validator.GetClientValidationRules().Single();
+
+ // Assert
+ attribute.Verify();
+ Assert.Same(expected, actual);
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Test/DataErrorInfoModelValidatorProviderTest.cs b/test/System.Web.Mvc.Test/Test/DataErrorInfoModelValidatorProviderTest.cs
new file mode 100644
index 00000000..b65ca64e
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Test/DataErrorInfoModelValidatorProviderTest.cs
@@ -0,0 +1,287 @@
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Linq;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Mvc.Test
+{
+ public class DataErrorInfoModelValidatorProviderTest
+ {
+ private static readonly EmptyModelMetadataProvider _metadataProvider = new EmptyModelMetadataProvider();
+
+ [Fact]
+ public void GetValidatorsReturnsEmptyCollectionIfTypeNotIDataErrorInfo()
+ {
+ // Arrange
+ DataErrorInfoModelValidatorProvider validatorProvider = new DataErrorInfoModelValidatorProvider();
+ object model = new object();
+ ModelMetadata metadata = _metadataProvider.GetMetadataForType(() => model, typeof(object));
+
+ // Act
+ ModelValidator[] validators = validatorProvider.GetValidators(metadata, new ControllerContext()).ToArray();
+
+ // Assert
+ Assert.Empty(validators);
+ }
+
+ [Fact]
+ public void GetValidatorsReturnsValidatorForIDataErrorInfoProperty()
+ {
+ // Arrange
+ DataErrorInfoModelValidatorProvider validatorProvider = new DataErrorInfoModelValidatorProvider();
+ DataErrorInfo1 model = new DataErrorInfo1();
+ ModelMetadata metadata = _metadataProvider.GetMetadataForProperty(() => model, typeof(DataErrorInfo1), "SomeStringProperty");
+ Type[] expectedTypes = new Type[]
+ {
+ typeof(DataErrorInfoModelValidatorProvider.DataErrorInfoPropertyModelValidator)
+ };
+
+ // Act
+ Type[] actualTypes = validatorProvider.GetValidators(metadata, new ControllerContext()).Select(v => v.GetType()).ToArray();
+
+ // Assert
+ Assert.Equal(expectedTypes, actualTypes);
+ }
+
+ [Fact]
+ public void GetValidatorsReturnsValidatorForIDataErrorInfoRootType()
+ {
+ // Arrange
+ DataErrorInfoModelValidatorProvider validatorProvider = new DataErrorInfoModelValidatorProvider();
+ DataErrorInfo1 model = new DataErrorInfo1();
+ ModelMetadata metadata = _metadataProvider.GetMetadataForType(() => model, typeof(DataErrorInfo1));
+ Type[] expectedTypes = new Type[]
+ {
+ typeof(DataErrorInfoModelValidatorProvider.DataErrorInfoClassModelValidator)
+ };
+
+ // Act
+ Type[] actualTypes = validatorProvider.GetValidators(metadata, new ControllerContext()).Select(v => v.GetType()).ToArray();
+
+ // Assert
+ Assert.Equal(expectedTypes, actualTypes);
+ }
+
+ [Fact]
+ public void GetValidatorsThrowsIfContextIsNull()
+ {
+ // Arrange
+ DataErrorInfoModelValidatorProvider validatorProvider = new DataErrorInfoModelValidatorProvider();
+ ModelMetadata metadata = _metadataProvider.GetMetadataForType(null, typeof(DataErrorInfo1));
+
+ // Act & assert
+ Assert.ThrowsArgumentNull(
+ delegate { validatorProvider.GetValidators(metadata, null); }, "context");
+ }
+
+ [Fact]
+ public void GetValidatorsThrowsIfMetadataIsNull()
+ {
+ // Arrange
+ DataErrorInfoModelValidatorProvider validatorProvider = new DataErrorInfoModelValidatorProvider();
+
+ // Act & assert
+ Assert.ThrowsArgumentNull(
+ delegate { validatorProvider.GetValidators(null, new ControllerContext()); }, "metadata");
+ }
+
+ [Fact]
+ public void ClassValidator_Validate_IDataErrorInfoModelWithError()
+ {
+ // Arrange
+ DataErrorInfo1 model = new DataErrorInfo1()
+ {
+ Error = "This is an error message."
+ };
+ ModelMetadata metadata = _metadataProvider.GetMetadataForType(() => model, typeof(DataErrorInfo1));
+
+ var validator = new DataErrorInfoModelValidatorProvider.DataErrorInfoClassModelValidator(metadata, new ControllerContext());
+
+ // Act
+ ModelValidationResult[] result = validator.Validate(null).ToArray();
+
+ // Assert
+ ModelValidationResult modelValidationResult = Assert.Single(result);
+ Assert.Equal("This is an error message.", modelValidationResult.Message);
+ }
+
+ [Fact]
+ public void ClassValidator_Validate_IDataErrorInfoModelWithNoErrorReturnsEmptyResults()
+ {
+ // Arrange
+ DataErrorInfo1 model = new DataErrorInfo1();
+ ModelMetadata metadata = _metadataProvider.GetMetadataForType(() => model, typeof(DataErrorInfo1));
+
+ var validator = new DataErrorInfoModelValidatorProvider.DataErrorInfoClassModelValidator(metadata, new ControllerContext());
+
+ // Act
+ ModelValidationResult[] result = validator.Validate(null).ToArray();
+
+ // Assert
+ Assert.Empty(result);
+ }
+
+ [Fact]
+ public void ClassValidator_Validate_NonIDataErrorInfoModelReturnsEmptyResults()
+ {
+ // Arrange
+ object model = new object();
+ ModelMetadata metadata = _metadataProvider.GetMetadataForType(() => model, typeof(object));
+
+ var validator = new DataErrorInfoModelValidatorProvider.DataErrorInfoClassModelValidator(metadata, new ControllerContext());
+
+ // Act
+ ModelValidationResult[] result = validator.Validate(null).ToArray();
+
+ // Assert
+ Assert.Empty(result);
+ }
+
+ [Fact]
+ public void PropertyValidator_Validate_IDataErrorInfoSkipsErrorProperty()
+ {
+ // Arrange
+ DataErrorInfo1 container = new DataErrorInfo1();
+ container["Error"] = "This should never be shown.";
+ ModelMetadata metadata = _metadataProvider.GetMetadataForProperty(() => container, typeof(DataErrorInfo1), "Error");
+
+ var validator = new DataErrorInfoModelValidatorProvider.DataErrorInfoPropertyModelValidator(metadata, new ControllerContext());
+
+ // Act
+ ModelValidationResult[] result = validator.Validate(container).ToArray();
+
+ // Assert
+ Assert.Empty(result);
+ }
+
+ [Fact]
+ public void PropertyValidator_Validate_DoesNotReadPropertyValue()
+ {
+ // Arrange
+ ObservableModel model = new ObservableModel();
+ ModelMetadata metadata = _metadataProvider.GetMetadataForProperty(() => model.TheProperty, typeof(ObservableModel), "TheProperty");
+ ControllerContext controllerContext = new ControllerContext();
+
+ // Act
+ ModelValidator[] validators = new DataErrorInfoModelValidatorProvider().GetValidators(metadata, controllerContext).ToArray();
+ ModelValidationResult[] results = validators.SelectMany(o => o.Validate(model)).ToArray();
+
+ // Assert
+ Assert.Equal(new[] { typeof(DataErrorInfoModelValidatorProvider.DataErrorInfoPropertyModelValidator) }, Array.ConvertAll(validators, o => o.GetType()));
+ Assert.Equal(new[] { "TheProperty" }, model.GetColumnNamesPassed().ToArray());
+ Assert.Empty(results);
+ Assert.False(model.PropertyWasRead());
+ }
+
+ [Fact]
+ public void PropertyValidator_Validate_IDataErrorInfoContainerWithError()
+ {
+ // Arrange
+ DataErrorInfo1 container = new DataErrorInfo1();
+ container["SomeStringProperty"] = "This is an error message.";
+ ModelMetadata metadata = _metadataProvider.GetMetadataForProperty(() => container, typeof(DataErrorInfo1), "SomeStringProperty");
+
+ var validator = new DataErrorInfoModelValidatorProvider.DataErrorInfoPropertyModelValidator(metadata, new ControllerContext());
+
+ // Act
+ ModelValidationResult[] result = validator.Validate(container).ToArray();
+
+ // Assert
+ ModelValidationResult modelValidationResult = Assert.Single(result);
+ Assert.Equal("This is an error message.", modelValidationResult.Message);
+ }
+
+ [Fact]
+ public void PropertyValidator_Validate_IDataErrorInfoContainerWithNoErrorReturnsEmptyResults()
+ {
+ // Arrange
+ DataErrorInfo1 container = new DataErrorInfo1();
+ ModelMetadata metadata = _metadataProvider.GetMetadataForProperty(() => container, typeof(DataErrorInfo1), "SomeStringProperty");
+
+ var validator = new DataErrorInfoModelValidatorProvider.DataErrorInfoPropertyModelValidator(metadata, new ControllerContext());
+
+ // Act
+ ModelValidationResult[] result = validator.Validate(container).ToArray();
+
+ // Assert
+ Assert.Empty(result);
+ }
+
+ [Fact]
+ public void PropertyValidator_Validate_NonIDataErrorInfoContainerReturnsEmptyResults()
+ {
+ // Arrange
+ DataErrorInfo1 container = new DataErrorInfo1();
+ container["SomeStringProperty"] = "This is an error message.";
+ ModelMetadata metadata = _metadataProvider.GetMetadataForProperty(() => container, typeof(DataErrorInfo1), "SomeStringProperty");
+
+ var validator = new DataErrorInfoModelValidatorProvider.DataErrorInfoPropertyModelValidator(metadata, new ControllerContext());
+
+ // Act
+ ModelValidationResult[] result = validator.Validate(new object()).ToArray();
+
+ // Assert
+ Assert.Empty(result);
+ }
+
+ private class DataErrorInfo1 : IDataErrorInfo
+ {
+ private readonly Dictionary<string, string> _errors = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
+
+ public string SomeStringProperty { get; set; }
+
+ public string Error { get; set; }
+
+ public string this[string columnName]
+ {
+ get
+ {
+ string outVal;
+ _errors.TryGetValue(columnName, out outVal);
+ return outVal;
+ }
+ set { _errors[columnName] = value; }
+ }
+ }
+
+ private class ObservableModel : IDataErrorInfo
+ {
+ private bool _propertyWasRead;
+ private readonly List<string> _columnNamesPassed = new List<string>();
+
+ public int TheProperty
+ {
+ get
+ {
+ _propertyWasRead = true;
+ return 42;
+ }
+ }
+
+ public bool PropertyWasRead()
+ {
+ return _propertyWasRead;
+ }
+
+ public string Error
+ {
+ get { throw new NotImplementedException(); }
+ }
+
+ public string this[string columnName]
+ {
+ get
+ {
+ _columnNamesPassed.Add(columnName);
+ return null;
+ }
+ }
+
+ public List<string> GetColumnNamesPassed()
+ {
+ return _columnNamesPassed;
+ }
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Test/DataTypeUtilTest.cs b/test/System.Web.Mvc.Test/Test/DataTypeUtilTest.cs
new file mode 100644
index 00000000..a468d540
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Test/DataTypeUtilTest.cs
@@ -0,0 +1,75 @@
+using System.ComponentModel.DataAnnotations;
+using Moq;
+using Xunit;
+
+namespace System.Web.Mvc.Test
+{
+ public class DataTypeUtilTest
+ {
+ private class DerivedDataTypeAttribute : DataTypeAttribute
+ {
+ public DerivedDataTypeAttribute(DataType dataType)
+ : base(dataType)
+ {
+ }
+
+ public override string GetDataTypeName()
+ {
+ return "DerivedTypeName";
+ }
+ }
+
+ [Fact]
+ public void VirtualDataTypeNameCallsAttributeGetDataTypeName()
+ {
+ // Arrange
+ DataTypeAttribute derivedAttr = new DerivedDataTypeAttribute(DataType.Html);
+ string expectedTypeName = derivedAttr.GetDataTypeName();
+
+ // Act
+ string actualTypeName = DataTypeUtil.ToDataTypeName(derivedAttr);
+
+ // Assert
+ Assert.Equal(expectedTypeName, actualTypeName);
+ }
+
+ [Fact]
+ public void DataTypeAttributeDoesNotCallAttributeGetDataTypeName()
+ {
+ // Arrange
+ Func<DataTypeAttribute, Boolean> isDataTypeAttribute = t => (t as DataTypeAttribute) != null;
+
+ foreach (DataType dataTypeValue in Enum.GetValues(typeof(DataType)))
+ {
+ if (dataTypeValue != DataType.Custom)
+ {
+ Mock<DataTypeAttribute> dataType = new Mock<DataTypeAttribute>(dataTypeValue);
+
+ // Act
+ string actualTypeName = DataTypeUtil.ToDataTypeName(dataType.Object, dta => dta as DataTypeAttribute != null);
+
+ // Assert
+ Assert.Equal(dataTypeValue.ToString(), actualTypeName);
+ dataType.Verify(dt => dt.GetDataTypeName(), Times.Never());
+ }
+ }
+ }
+
+ [Fact]
+ public void CustomDataTypeNameCallsAttributeGetDataTypeName()
+ {
+ // Arrange
+ Func<DataTypeAttribute, Boolean> isDataTypeAttribute = t => (t as DataTypeAttribute) != null;
+
+ Mock<DataTypeAttribute> customDataType = new Mock<DataTypeAttribute>(DataType.Custom);
+ customDataType.Setup(c => c.GetDataTypeName()).Returns("CustomTypeName").Verifiable();
+
+ // Act
+ string actualTypeName = DataTypeUtil.ToDataTypeName(customDataType.Object);
+
+ // Assert
+ customDataType.Verify(c => c.GetDataTypeName(), Times.Once());
+ Assert.Equal("CustomTypeName", actualTypeName);
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Test/DefaultControllerFactoryTest.cs b/test/System.Web.Mvc.Test/Test/DefaultControllerFactoryTest.cs
new file mode 100644
index 00000000..b95ebb42
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Test/DefaultControllerFactoryTest.cs
@@ -0,0 +1,766 @@
+using System.Reflection;
+using System.Web.Routing;
+using System.Web.SessionState;
+using Moq;
+using Xunit;
+using Xunit.Extensions;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Mvc.Test
+{
+ [CLSCompliant(false)]
+ public class DefaultControllerFactoryTest
+ {
+ static DefaultControllerFactoryTest()
+ {
+ MvcTestHelper.CreateMvcAssemblies();
+ }
+
+ [Fact]
+ public void CreateAmbiguousControllerException_RouteWithoutUrl()
+ {
+ // Arrange
+ RouteBase route = new Mock<RouteBase>().Object;
+
+ Type[] matchingTypes = new Type[]
+ {
+ typeof(object),
+ typeof(string)
+ };
+
+ // Act
+ InvalidOperationException exception = DefaultControllerFactory.CreateAmbiguousControllerException(route, "Foo", matchingTypes);
+
+ // Assert
+ Assert.Equal(@"Multiple types were found that match the controller named 'Foo'. This can happen if the route that services this request does not specify namespaces to search for a controller that matches the request. If this is the case, register this route by calling an overload of the 'MapRoute' method that takes a 'namespaces' parameter.
+
+The request for 'Foo' has found the following matching controllers:
+System.Object
+System.String", exception.Message);
+ }
+
+ [Fact]
+ public void CreateAmbiguousControllerException_RouteWithUrl()
+ {
+ // Arrange
+ RouteBase route = new Route("{controller}/blah", new Mock<IRouteHandler>().Object);
+
+ Type[] matchingTypes = new Type[]
+ {
+ typeof(object),
+ typeof(string)
+ };
+
+ // Act
+ InvalidOperationException exception = DefaultControllerFactory.CreateAmbiguousControllerException(route, "Foo", matchingTypes);
+
+ // Assert
+ Assert.Equal(@"Multiple types were found that match the controller named 'Foo'. This can happen if the route that services this request ('{controller}/blah') does not specify namespaces to search for a controller that matches the request. If this is the case, register this route by calling an overload of the 'MapRoute' method that takes a 'namespaces' parameter.
+
+The request for 'Foo' has found the following matching controllers:
+System.Object
+System.String", exception.Message);
+ }
+
+ [Fact]
+ public void CreateControllerWithNullContextThrows()
+ {
+ // Arrange
+ DefaultControllerFactory factory = new DefaultControllerFactory();
+
+ // Act
+ Assert.ThrowsArgumentNull(
+ delegate
+ {
+ ((IControllerFactory)factory).CreateController(
+ null,
+ "foo");
+ },
+ "requestContext");
+ }
+
+ [Fact]
+ public void CreateControllerWithEmptyControllerNameThrows()
+ {
+ // Arrange
+ DefaultControllerFactory factory = new DefaultControllerFactory();
+
+ // Act
+ Assert.Throws<ArgumentException>(
+ delegate
+ {
+ ((IControllerFactory)factory).CreateController(
+ new RequestContext(new Mock<HttpContextBase>().Object, new RouteData()),
+ String.Empty);
+ },
+ "Value cannot be null or empty.\r\nParameter name: controllerName");
+ }
+
+ [Fact]
+ public void CreateControllerReturnsControllerInstance()
+ {
+ // Arrange
+ RequestContext requestContext = new RequestContext(new Mock<HttpContextBase>().Object, new RouteData());
+ Mock<DefaultControllerFactory> factoryMock = new Mock<DefaultControllerFactory>();
+ factoryMock.CallBase = true;
+ factoryMock.Setup(o => o.GetControllerType(requestContext, "moo")).Returns(typeof(DummyController));
+
+ // Act
+ IController controller = ((IControllerFactory)factoryMock.Object).CreateController(requestContext, "moo");
+
+ // Assert
+ Assert.IsType<DummyController>(controller);
+ }
+
+ [Fact]
+ public void CreateControllerCanReturnNull()
+ {
+ // Arrange
+ RequestContext requestContext = new RequestContext(new Mock<HttpContextBase>().Object, new RouteData());
+ Mock<DefaultControllerFactory> factoryMock = new Mock<DefaultControllerFactory>();
+ factoryMock.Setup(o => o.GetControllerType(requestContext, "moo")).Returns(typeof(DummyController));
+ factoryMock.Setup(o => o.GetControllerInstance(requestContext, typeof(DummyController))).Returns((ControllerBase)null);
+
+ // Act
+ IController controller = ((IControllerFactory)factoryMock.Object).CreateController(requestContext, "moo");
+
+ // Assert
+ Assert.Null(controller);
+ }
+
+ [Fact]
+ public void DisposeControllerFactoryWithDisposableController()
+ {
+ // Arrange
+ IControllerFactory factory = new DefaultControllerFactory();
+ Mock<ControllerBase> mockController = new Mock<ControllerBase>();
+ Mock<IDisposable> mockDisposable = mockController.As<IDisposable>();
+ mockDisposable.Setup(d => d.Dispose()).Verifiable();
+
+ // Act
+ factory.ReleaseController(mockController.Object);
+
+ // Assert
+ mockDisposable.Verify();
+ }
+
+ [Fact]
+ public void GetControllerInstanceThrowsIfControllerTypeIsNull()
+ {
+ // Arrange
+ Mock<HttpContextBase> contextMock = new Mock<HttpContextBase>();
+ Mock<HttpRequestBase> requestMock = new Mock<HttpRequestBase>();
+ contextMock.Setup(o => o.Request).Returns(requestMock.Object);
+ requestMock.Setup(o => o.Path).Returns("somepath");
+ RequestContext requestContext = new RequestContext(contextMock.Object, new RouteData());
+ Mock<DefaultControllerFactory> factoryMock = new Mock<DefaultControllerFactory> { CallBase = true };
+ factoryMock.Setup(o => o.GetControllerType(requestContext, "moo")).Returns((Type)null);
+
+ // Act
+ Assert.ThrowsHttpException(
+ delegate { ((IControllerFactory)factoryMock.Object).CreateController(requestContext, "moo"); },
+ "The controller for path 'somepath' was not found or does not implement IController.",
+ 404);
+ }
+
+ [Fact]
+ public void GetControllerInstanceThrowsIfControllerTypeIsNotControllerBase()
+ {
+ // Arrange
+ RequestContext requestContext = new RequestContext(new Mock<HttpContextBase>().Object, new RouteData());
+ DefaultControllerFactory factory = new DefaultControllerFactory();
+
+ // Act
+ Assert.Throws<ArgumentException>(
+ delegate { factory.GetControllerInstance(requestContext, typeof(int)); },
+ "The controller type 'System.Int32' must implement IController.\r\nParameter name: controllerType");
+ }
+
+ [Fact]
+ public void GetControllerInstanceWithBadConstructorThrows()
+ {
+ // Arrange
+ Mock<HttpContextBase> contextMock = new Mock<HttpContextBase>();
+ RequestContext requestContext = new RequestContext(contextMock.Object, new RouteData());
+ Mock<DefaultControllerFactory> factoryMock = new Mock<DefaultControllerFactory>();
+ factoryMock.CallBase = true;
+ factoryMock.Setup(o => o.GetControllerType(requestContext, "moo")).Returns(typeof(DummyControllerThrows));
+
+ // Act
+ Exception ex = Assert.Throws<InvalidOperationException>(
+ delegate { ((IControllerFactory)factoryMock.Object).CreateController(requestContext, "moo"); },
+ "An error occurred when trying to create a controller of type 'System.Web.Mvc.Test.DefaultControllerFactoryTest+DummyControllerThrows'. Make sure that the controller has a parameterless public constructor.");
+
+ Assert.Equal("constructor", ex.InnerException.InnerException.Message);
+ }
+
+ [Fact]
+ public void GetControllerSessionBehaviorGuardClauses()
+ {
+ // Arrange
+ RequestContext requestContext = new RequestContext(new Mock<HttpContextBase>().Object, new RouteData());
+ IControllerFactory factory = new DefaultControllerFactory();
+
+ // Act & Assert
+ Assert.ThrowsArgumentNull(
+ () => factory.GetControllerSessionBehavior(null, "controllerName"),
+ "requestContext"
+ );
+ Assert.ThrowsArgumentNullOrEmpty(
+ () => factory.GetControllerSessionBehavior(requestContext, null),
+ "controllerName"
+ );
+ Assert.ThrowsArgumentNullOrEmpty(
+ () => factory.GetControllerSessionBehavior(requestContext, ""),
+ "controllerName"
+ );
+ }
+
+ [Fact]
+ public void GetControllerSessionBehaviorReturnsDefaultForNullControllerType()
+ {
+ // Arrange
+ var factory = new DefaultControllerFactory();
+
+ // Act
+ SessionStateBehavior result = factory.GetControllerSessionBehavior(null, null);
+
+ // Assert
+ Assert.Equal(SessionStateBehavior.Default, result);
+ }
+
+ [Fact]
+ public void GetControllerSessionBehaviorReturnsDefaultForControllerWithoutAttribute()
+ {
+ // Arrange
+ var factory = new DefaultControllerFactory();
+
+ // Act
+ SessionStateBehavior result = factory.GetControllerSessionBehavior(null, typeof(object));
+
+ // Assert
+ Assert.Equal(SessionStateBehavior.Default, result);
+ }
+
+ [Fact]
+ public void GetControllerSessionBehaviorReturnsAttributeValueFromController()
+ {
+ // Arrange
+ var factory = new DefaultControllerFactory();
+
+ // Act
+ SessionStateBehavior result = factory.GetControllerSessionBehavior(null, typeof(MyReadOnlyController));
+
+ // Assert
+ Assert.Equal(SessionStateBehavior.ReadOnly, result);
+ }
+
+ [SessionState(SessionStateBehavior.ReadOnly)]
+ class MyReadOnlyController
+ {
+ }
+
+ [Fact]
+ public void GetControllerTypeWithEmptyControllerNameThrows()
+ {
+ // Arrange
+ RequestContext requestContext = new RequestContext(new Mock<HttpContextBase>().Object, new RouteData());
+ DefaultControllerFactory factory = new DefaultControllerFactory();
+
+ // Act
+ Assert.Throws<ArgumentException>(
+ delegate { factory.GetControllerType(requestContext, String.Empty); },
+ "Value cannot be null or empty.\r\nParameter name: controllerName");
+ }
+
+ [Fact]
+ public void GetControllerTypeForNoAssemblies()
+ {
+ // Arrange
+ RequestContext requestContext = new RequestContext(new Mock<HttpContextBase>().Object, new RouteData());
+ DefaultControllerFactory factory = new DefaultControllerFactory();
+ MockBuildManager buildManagerMock = new MockBuildManager(new Assembly[] { });
+ ControllerTypeCache controllerTypeCache = new ControllerTypeCache();
+
+ factory.BuildManager = buildManagerMock;
+ factory.ControllerTypeCache = controllerTypeCache;
+
+ // Act
+ Type controllerType = factory.GetControllerType(requestContext, "sometype");
+
+ // Assert
+ Assert.Null(controllerType);
+ Assert.Equal(0, controllerTypeCache.Count);
+ }
+
+ [Fact]
+ public void GetControllerTypeForOneAssembly()
+ {
+ // Arrange
+ RequestContext requestContext = new RequestContext(new Mock<HttpContextBase>().Object, new RouteData());
+ DefaultControllerFactory factory = GetDefaultControllerFactory("ns1a.ns1b", "ns2a.ns2b");
+ MockBuildManager buildManagerMock = new MockBuildManager(new Assembly[] { Assembly.Load("MvcAssembly1") });
+ ControllerTypeCache controllerTypeCache = new ControllerTypeCache();
+
+ factory.BuildManager = buildManagerMock;
+ factory.ControllerTypeCache = controllerTypeCache;
+
+ // Act
+ Type c1Type = factory.GetControllerType(requestContext, "C1");
+ Type c2Type = factory.GetControllerType(requestContext, "c2");
+
+ // Assert
+ Assembly asm1 = Assembly.Load("MvcAssembly1");
+ Type verifiedC1 = asm1.GetType("NS1a.NS1b.C1Controller");
+ Type verifiedC2 = asm1.GetType("NS2a.NS2b.C2Controller");
+ Assert.Equal(verifiedC1, c1Type);
+ Assert.Equal(verifiedC2, c2Type);
+ Assert.Equal(2, controllerTypeCache.Count);
+ }
+
+ [Fact]
+ public void GetControllerTypeForManyAssemblies()
+ {
+ // Arrange
+ RequestContext requestContext = new RequestContext(new Mock<HttpContextBase>().Object, new RouteData());
+ DefaultControllerFactory factory = GetDefaultControllerFactory("ns1a.ns1b", "ns2a.ns2b", "ns3a.ns3b", "ns4a.ns4b");
+ MockBuildManager buildManagerMock = new MockBuildManager(new Assembly[] { Assembly.Load("MvcAssembly1"), Assembly.Load("MvcAssembly2") });
+ ControllerTypeCache controllerTypeCache = new ControllerTypeCache();
+
+ factory.BuildManager = buildManagerMock;
+ factory.ControllerTypeCache = controllerTypeCache;
+
+ // Act
+ Type c1Type = factory.GetControllerType(requestContext, "C1");
+ Type c2Type = factory.GetControllerType(requestContext, "C2");
+ Type c3Type = factory.GetControllerType(requestContext, "c3"); // lower case
+ Type c4Type = factory.GetControllerType(requestContext, "c4"); // lower case
+
+ // Assert
+ Assembly asm1 = Assembly.Load("MvcAssembly1");
+ Type verifiedC1 = asm1.GetType("NS1a.NS1b.C1Controller");
+ Type verifiedC2 = asm1.GetType("NS2a.NS2b.C2Controller");
+ Assembly asm2 = Assembly.Load("MvcAssembly2");
+ Type verifiedC3 = asm2.GetType("NS3a.NS3b.C3Controller");
+ Type verifiedC4 = asm2.GetType("NS4a.NS4b.C4Controller");
+ Assert.NotNull(verifiedC1);
+ Assert.NotNull(verifiedC2);
+ Assert.NotNull(verifiedC3);
+ Assert.NotNull(verifiedC4);
+ Assert.Equal(verifiedC1, c1Type);
+ Assert.Equal(verifiedC2, c2Type);
+ Assert.Equal(verifiedC3, c3Type);
+ Assert.Equal(verifiedC4, c4Type);
+ Assert.Equal(4, controllerTypeCache.Count);
+ }
+
+ [Fact]
+ public void GetControllerTypeDoesNotThrowIfSameControllerMatchedMultipleNamespaces()
+ {
+ // both namespaces "ns3a" and "ns3a.ns3b" will match a controller type, but it's actually
+ // the same type. in this case, we shouldn't throw.
+
+ // Arrange
+ RequestContext requestContext = GetRequestContextWithNamespaces("ns3a", "ns3a.ns3b");
+ requestContext.RouteData.DataTokens["UseNamespaceFallback"] = false;
+ DefaultControllerFactory factory = GetDefaultControllerFactory("ns1a.ns1b", "ns2a.ns2b");
+ MockBuildManager buildManagerMock = new MockBuildManager(new Assembly[] { Assembly.Load("MvcAssembly3") });
+ ControllerTypeCache controllerTypeCache = new ControllerTypeCache();
+
+ factory.BuildManager = buildManagerMock;
+ factory.ControllerTypeCache = controllerTypeCache;
+
+ // Act
+ Type c1Type = factory.GetControllerType(requestContext, "C1");
+
+ // Assert
+ Assembly asm3 = Assembly.Load("MvcAssembly3");
+ Type verifiedC1 = asm3.GetType("NS3a.NS3b.C1Controller");
+ Assert.NotNull(verifiedC1);
+ Assert.Equal(verifiedC1, c1Type);
+ }
+
+ [Fact]
+ public void GetControllerTypeForAssembliesWithSameTypeNamesInDifferentNamespaces()
+ {
+ // Arrange
+ RequestContext requestContext = new RequestContext(new Mock<HttpContextBase>().Object, new RouteData());
+ DefaultControllerFactory factory = GetDefaultControllerFactory("ns1a.ns1b", "ns2a.ns2b");
+ MockBuildManager buildManagerMock = new MockBuildManager(new Assembly[] { Assembly.Load("MvcAssembly1"), Assembly.Load("MvcAssembly3") });
+ ControllerTypeCache controllerTypeCache = new ControllerTypeCache();
+
+ factory.BuildManager = buildManagerMock;
+ factory.ControllerTypeCache = controllerTypeCache;
+
+ // Act
+ Type c1Type = factory.GetControllerType(requestContext, "C1");
+ Type c2Type = factory.GetControllerType(requestContext, "C2");
+
+ // Assert
+ Assembly asm1 = Assembly.Load("MvcAssembly1");
+ Type verifiedC1 = asm1.GetType("NS1a.NS1b.C1Controller");
+ Type verifiedC2 = asm1.GetType("NS2a.NS2b.C2Controller");
+ Assert.NotNull(verifiedC1);
+ Assert.NotNull(verifiedC2);
+ Assert.Equal(verifiedC1, c1Type);
+ Assert.Equal(verifiedC2, c2Type);
+ Assert.Equal(4, controllerTypeCache.Count);
+ }
+
+ [Fact]
+ public void GetControllerTypeForAssembliesWithSameTypeNamesInDifferentNamespacesThrowsIfAmbiguous()
+ {
+ // Arrange
+ RequestContext requestContext = new RequestContext(new Mock<HttpContextBase>().Object, new RouteData());
+ DefaultControllerFactory factory = GetDefaultControllerFactory("ns1a.ns1b", "ns3a.ns3b");
+ MockBuildManager buildManagerMock = new MockBuildManager(new Assembly[] { Assembly.Load("MvcAssembly1"), Assembly.Load("MvcAssembly3") });
+ ControllerTypeCache controllerTypeCache = new ControllerTypeCache();
+
+ factory.BuildManager = buildManagerMock;
+ factory.ControllerTypeCache = controllerTypeCache;
+
+ // Act
+ Assert.Throws<InvalidOperationException>(
+ delegate { factory.GetControllerType(requestContext, "C1"); },
+ @"Multiple types were found that match the controller named 'C1'. This can happen if the route that services this request does not specify namespaces to search for a controller that matches the request. If this is the case, register this route by calling an overload of the 'MapRoute' method that takes a 'namespaces' parameter.
+
+The request for 'C1' has found the following matching controllers:
+NS1a.NS1b.C1Controller
+NS3a.NS3b.C1Controller");
+
+ // Assert
+ Assert.Equal(4, controllerTypeCache.Count);
+ }
+
+ [Fact]
+ public void GetControllerTypeForAssembliesWithSameTypeNamesInSameNamespaceThrows()
+ {
+ // Arrange
+ RequestContext requestContext = new RequestContext(new Mock<HttpContextBase>().Object, new RouteData());
+ DefaultControllerFactory factory = GetDefaultControllerFactory("ns1a.ns1b");
+ MockBuildManager buildManagerMock = new MockBuildManager(new Assembly[] { Assembly.Load("MvcAssembly1"), Assembly.Load("MvcAssembly4") });
+ ControllerTypeCache controllerTypeCache = new ControllerTypeCache();
+
+ factory.BuildManager = buildManagerMock;
+ factory.ControllerTypeCache = controllerTypeCache;
+
+ // Act
+ Assert.Throws<InvalidOperationException>(
+ delegate { factory.GetControllerType(requestContext, "C1"); },
+ @"Multiple types were found that match the controller named 'C1'. This can happen if the route that services this request does not specify namespaces to search for a controller that matches the request. If this is the case, register this route by calling an overload of the 'MapRoute' method that takes a 'namespaces' parameter.
+
+The request for 'C1' has found the following matching controllers:
+NS1a.NS1b.C1Controller
+NS1a.NS1b.C1Controller");
+
+ // Assert
+ Assert.Equal(4, controllerTypeCache.Count);
+ }
+
+ [Fact]
+ public void GetControllerTypeSearchesAllNamespacesAsLastResort()
+ {
+ // Arrange
+ RequestContext requestContext = GetRequestContextWithNamespaces("ns3a.ns3b");
+ DefaultControllerFactory factory = GetDefaultControllerFactory("ns1a.ns1b");
+ MockBuildManager buildManagerMock = new MockBuildManager(new Assembly[] { Assembly.Load("MvcAssembly1") });
+ ControllerTypeCache controllerTypeCache = new ControllerTypeCache();
+
+ factory.BuildManager = buildManagerMock;
+ factory.ControllerTypeCache = controllerTypeCache;
+
+ // Act
+ Type c2Type = factory.GetControllerType(requestContext, "C2");
+
+ // Assert
+ Assembly asm1 = Assembly.Load("MvcAssembly1");
+ Type verifiedC2 = asm1.GetType("NS2a.NS2b.C2Controller");
+ Assert.NotNull(verifiedC2);
+ Assert.Equal(verifiedC2, c2Type);
+ Assert.Equal(2, controllerTypeCache.Count);
+ }
+
+ [Fact]
+ public void GetControllerTypeSearchesOnlyRouteDefinedNamespacesIfRequested()
+ {
+ // Arrange
+ RequestContext requestContext = GetRequestContextWithNamespaces("ns3a.ns3b");
+ requestContext.RouteData.DataTokens["UseNamespaceFallback"] = false;
+ DefaultControllerFactory factory = GetDefaultControllerFactory("ns1a.ns1b", "ns2a.ns2b");
+ MockBuildManager buildManagerMock = new MockBuildManager(new Assembly[] { Assembly.Load("MvcAssembly1"), Assembly.Load("MvcAssembly3") });
+ ControllerTypeCache controllerTypeCache = new ControllerTypeCache();
+
+ factory.BuildManager = buildManagerMock;
+ factory.ControllerTypeCache = controllerTypeCache;
+
+ // Act
+ Type c1Type = factory.GetControllerType(requestContext, "C1");
+ Type c2Type = factory.GetControllerType(requestContext, "C2");
+
+ // Assert
+ Assembly asm3 = Assembly.Load("MvcAssembly3");
+ Type verifiedC1 = asm3.GetType("NS3a.NS3b.C1Controller");
+ Assert.NotNull(verifiedC1);
+ Assert.Equal(verifiedC1, c1Type);
+ Assert.Null(c2Type);
+ }
+
+ [Fact]
+ public void GetControllerTypeSearchesRouteDefinedNamespacesBeforeApplicationDefinedNamespaces()
+ {
+ // Arrange
+ RequestContext requestContext = GetRequestContextWithNamespaces("ns3a.ns3b");
+ DefaultControllerFactory factory = GetDefaultControllerFactory("ns1a.ns1b", "ns2a.ns2b");
+ MockBuildManager buildManagerMock = new MockBuildManager(new Assembly[] { Assembly.Load("MvcAssembly1"), Assembly.Load("MvcAssembly3") });
+ ControllerTypeCache controllerTypeCache = new ControllerTypeCache();
+
+ factory.BuildManager = buildManagerMock;
+ factory.ControllerTypeCache = controllerTypeCache;
+
+ // Act
+ Type c1Type = factory.GetControllerType(requestContext, "C1");
+ Type c2Type = factory.GetControllerType(requestContext, "C2");
+
+ // Assert
+ Assembly asm1 = Assembly.Load("MvcAssembly1");
+ Type verifiedC2 = asm1.GetType("NS2a.NS2b.C2Controller");
+ Assembly asm3 = Assembly.Load("MvcAssembly3");
+ Type verifiedC1 = asm3.GetType("NS3a.NS3b.C1Controller");
+ Assert.NotNull(verifiedC1);
+ Assert.NotNull(verifiedC2);
+ Assert.Equal(verifiedC1, c1Type);
+ Assert.Equal(verifiedC2, c2Type);
+ Assert.Equal(4, controllerTypeCache.Count);
+ }
+
+ [Fact]
+ public void GetControllerTypeThatDoesntExist()
+ {
+ // Arrange
+ RequestContext requestContext = new RequestContext(new Mock<HttpContextBase>().Object, new RouteData());
+ DefaultControllerFactory factory = GetDefaultControllerFactory("ns1a.ns1b", "ns2a.ns2b", "ns3a.ns3b", "ns4a.ns4b");
+ MockBuildManager buildManagerMock = new MockBuildManager(new Assembly[] { Assembly.Load("MvcAssembly1"), Assembly.Load("MvcAssembly2"), Assembly.Load("MvcAssembly3"), Assembly.Load("MvcAssembly4") });
+ ControllerTypeCache controllerTypeCache = new ControllerTypeCache();
+
+ factory.BuildManager = buildManagerMock;
+ factory.ControllerTypeCache = controllerTypeCache;
+
+ // Act
+ Type randomType1 = factory.GetControllerType(requestContext, "Cx");
+ Type randomType2 = factory.GetControllerType(requestContext, "Cy");
+ Type randomType3 = factory.GetControllerType(requestContext, "Foo.Bar");
+ Type randomType4 = factory.GetControllerType(requestContext, "C1Controller");
+
+ // Assert
+ Assert.Null(randomType1);
+ Assert.Null(randomType2);
+ Assert.Null(randomType3);
+ Assert.Null(randomType4);
+ Assert.Equal(8, controllerTypeCache.Count);
+ }
+
+ [Fact]
+ public void IsControllerType()
+ {
+ // Act
+ bool isController1 = ControllerTypeCache.IsControllerType(null);
+ bool isController2 = ControllerTypeCache.IsControllerType(typeof(NonPublicController));
+ bool isController3 = ControllerTypeCache.IsControllerType(typeof(MisspelledKontroller));
+ bool isController4 = ControllerTypeCache.IsControllerType(typeof(AbstractController));
+ bool isController5 = ControllerTypeCache.IsControllerType(typeof(NonIControllerController));
+ bool isController6 = ControllerTypeCache.IsControllerType(typeof(Goodcontroller));
+
+ // Assert
+ Assert.False(isController1);
+ Assert.False(isController2);
+ Assert.False(isController3);
+ Assert.False(isController4);
+ Assert.False(isController5);
+ Assert.True(isController6);
+ }
+
+ [Theory]
+ [InlineData(null, false)]
+ [InlineData("", true)]
+ [InlineData("Dummy", false)]
+ [InlineData("Dummy.*", true)]
+ [InlineData("Dummy.Controller.*", false)]
+ [InlineData("Dummy.Controllers", true)]
+ [InlineData("Dummy.Controllers.*", true)]
+ [InlineData("Dummy.Controllers*", false)]
+ public void IsNamespaceMatch(string testNamespace, bool expectedResult)
+ {
+ // Act & Assert
+ Assert.Equal(expectedResult, ControllerTypeCache.IsNamespaceMatch(testNamespace, "Dummy.Controllers"));
+ }
+
+ [Fact]
+ public void GetControllerInstanceConsultsSetControllerActivator()
+ {
+ //Arrange
+ Mock<IControllerActivator> activator = new Mock<IControllerActivator>();
+ DefaultControllerFactory factory = new DefaultControllerFactory(activator.Object);
+ RequestContext context = new RequestContext();
+
+ //Act
+ factory.GetControllerInstance(context, typeof(Goodcontroller));
+
+ //Assert
+ activator.Verify(l => l.Create(context, typeof(Goodcontroller)));
+ }
+
+ [Fact]
+ public void GetControllerDelegatesToActivatorResolver()
+ {
+ //Arrange
+ var context = new RequestContext();
+ var expectedController = new Goodcontroller();
+ var resolverActivator = new Mock<IControllerActivator>();
+ resolverActivator.Setup(a => a.Create(context, typeof(Goodcontroller))).Returns(expectedController);
+ var activatorResolver = new Resolver<IControllerActivator> { Current = resolverActivator.Object };
+ var factory = new DefaultControllerFactory(null, activatorResolver, null);
+
+ //Act
+ IController returnedController = factory.GetControllerInstance(context, typeof(Goodcontroller));
+
+ //Assert
+ Assert.Same(returnedController, expectedController);
+ }
+
+ [Fact]
+ public void GetControllerDelegatesToDependencyResolveWhenActivatorResolverIsNull()
+ {
+ // Arrange
+ var context = new RequestContext();
+ var expectedController = new Goodcontroller();
+ var dependencyResolver = new Mock<IDependencyResolver>(MockBehavior.Strict);
+ dependencyResolver.Setup(dr => dr.GetService(typeof(Goodcontroller))).Returns(expectedController);
+ var factory = new DefaultControllerFactory(null, null, dependencyResolver.Object);
+
+ // Act
+ IController returnedController = factory.GetControllerInstance(context, typeof(Goodcontroller));
+
+ // Assert
+ Assert.Same(returnedController, expectedController);
+ }
+
+ [Fact]
+ public void GetControllerDelegatesToActivatorCreateInstanceWhenDependencyResolverReturnsNull()
+ {
+ // Arrange
+ var context = new RequestContext();
+ var dependencyResolver = new Mock<IDependencyResolver>();
+ var factory = new DefaultControllerFactory(null, null, dependencyResolver.Object);
+
+ // Act & Assert
+ Assert.Throws<InvalidOperationException>(
+ () => factory.GetControllerInstance(context, typeof(NoParameterlessCtor)),
+ "An error occurred when trying to create a controller of type 'System.Web.Mvc.Test.DefaultControllerFactoryTest+NoParameterlessCtor'. Make sure that the controller has a parameterless public constructor."
+ );
+ }
+
+ [Fact]
+ public void ActivatorResolverAndDependencyResolverAreNeverCalledWhenControllerActivatorIsPassedInConstructor()
+ {
+ // Arrange
+ var context = new RequestContext();
+ var expectedController = new Goodcontroller();
+
+ Mock<IControllerActivator> activator = new Mock<IControllerActivator>();
+ activator.Setup(a => a.Create(context, typeof(Goodcontroller))).Returns(expectedController);
+
+ var resolverActivator = new Mock<IControllerActivator>(MockBehavior.Strict);
+ var activatorResolver = new Resolver<IControllerActivator> { Current = resolverActivator.Object };
+
+ var dependencyResolver = new Mock<IDependencyResolver>(MockBehavior.Strict);
+
+ //Act
+ var factory = new DefaultControllerFactory(activator.Object, activatorResolver, dependencyResolver.Object);
+ IController returnedController = factory.GetControllerInstance(context, typeof(Goodcontroller));
+
+ //Assert
+ Assert.Same(returnedController, expectedController);
+ }
+
+ class NoParameterlessCtor : IController
+ {
+ public NoParameterlessCtor(int x)
+ {
+ }
+
+ public void Execute(RequestContext requestContext)
+ {
+ }
+ }
+
+ private static DefaultControllerFactory GetDefaultControllerFactory(params string[] namespaces)
+ {
+ ControllerBuilder builder = new ControllerBuilder();
+ builder.DefaultNamespaces.UnionWith(namespaces);
+ return new DefaultControllerFactory() { ControllerBuilder = builder };
+ }
+
+ private static RequestContext GetRequestContextWithNamespaces(params string[] namespaces)
+ {
+ RouteData routeData = new RouteData();
+ routeData.DataTokens["namespaces"] = namespaces;
+ Mock<HttpContextBase> mockHttpContext = new Mock<HttpContextBase>();
+ RequestContext requestContext = new RequestContext(mockHttpContext.Object, routeData);
+ return requestContext;
+ }
+
+ private sealed class DummyController : ControllerBase
+ {
+ protected override void ExecuteCore()
+ {
+ throw new NotImplementedException();
+ }
+ }
+
+ private sealed class DummyControllerThrows : IController
+ {
+ public DummyControllerThrows()
+ {
+ throw new Exception("constructor");
+ }
+
+ #region IController Members
+
+ void IController.Execute(RequestContext requestContext)
+ {
+ throw new NotImplementedException();
+ }
+
+ #endregion
+ }
+
+ public interface IDisposableController : IController, IDisposable
+ {
+ }
+ }
+
+ // BAD: type isn't public
+ internal class NonPublicController : Controller
+ {
+ }
+
+ // BAD: type doesn't end with 'Controller'
+ public class MisspelledKontroller : Controller
+ {
+ }
+
+ // BAD: type is abstract
+ public abstract class AbstractController : Controller
+ {
+ }
+
+ // BAD: type doesn't implement IController
+ public class NonIControllerController
+ {
+ }
+
+ // GOOD: 'Controller' suffix should be case-insensitive
+ public class Goodcontroller : Controller
+ {
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Test/DefaultModelBinderTest.cs b/test/System.Web.Mvc.Test/Test/DefaultModelBinderTest.cs
new file mode 100644
index 00000000..d4c4ddc6
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Test/DefaultModelBinderTest.cs
@@ -0,0 +1,2904 @@
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.ComponentModel.DataAnnotations;
+using System.Globalization;
+using System.IO;
+using System.Linq;
+using Microsoft.Web.UnitTestUtil;
+using Moq;
+using Moq.Protected;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Mvc.Test
+{
+ [CLSCompliant(false)]
+ public class DefaultModelBinderTest
+ {
+ [Fact]
+ public void BindComplexElementalModelReturnsIfOnModelUpdatingReturnsFalse()
+ {
+ // Arrange
+ ControllerContext controllerContext = new Mock<ControllerContext>().Object;
+
+ MyModel model = new MyModel() { ReadWriteProperty = 3 };
+ ModelBindingContext bindingContext = new ModelBindingContext()
+ {
+ ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(() => model, model.GetType()),
+ };
+
+ Mock<DefaultModelBinderHelper> mockHelper = new Mock<DefaultModelBinderHelper>() { CallBase = true };
+ mockHelper.Setup(b => b.PublicOnModelUpdating(controllerContext, It.IsAny<ModelBindingContext>())).Returns(false);
+ DefaultModelBinderHelper helper = mockHelper.Object;
+
+ // Act
+ helper.BindComplexElementalModel(controllerContext, bindingContext, model);
+
+ // Assert
+ Assert.Equal(3, model.ReadWriteProperty);
+ mockHelper.Verify();
+ mockHelper.Verify(b => b.PublicGetModelProperties(controllerContext, It.IsAny<ModelBindingContext>()), Times.Never());
+ mockHelper.Verify(b => b.PublicBindProperty(controllerContext, It.IsAny<ModelBindingContext>(), It.IsAny<PropertyDescriptor>()), Times.Never());
+ }
+
+ [Fact]
+ public void BindComplexModelCanBindArrays()
+ {
+ // Arrange
+ ControllerContext controllerContext = new Mock<ControllerContext>().Object;
+
+ ModelBindingContext bindingContext = new ModelBindingContext()
+ {
+ ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(null, typeof(int[])),
+ ModelName = "foo",
+ PropertyFilter = _ => false,
+ ValueProvider = new SimpleValueProvider()
+ {
+ { "foo[0]", null },
+ { "foo[1]", null },
+ { "foo[2]", null }
+ }
+ };
+
+ Mock<IModelBinder> mockInnerBinder = new Mock<IModelBinder>();
+ mockInnerBinder
+ .Setup(b => b.BindModel(It.IsAny<ControllerContext>(), It.IsAny<ModelBindingContext>()))
+ .Returns(
+ delegate(ControllerContext cc, ModelBindingContext bc)
+ {
+ Assert.Equal(controllerContext, cc);
+ Assert.Equal(typeof(int), bc.ModelType);
+ Assert.Equal(bindingContext.ModelState, bc.ModelState);
+ Assert.Equal(bindingContext.PropertyFilter, bc.PropertyFilter);
+ Assert.Equal(bindingContext.ValueProvider, bc.ValueProvider);
+ return Int32.Parse(bc.ModelName.Substring(4, 1), CultureInfo.InvariantCulture);
+ });
+
+ DefaultModelBinder binder = new DefaultModelBinder()
+ {
+ Binders = new ModelBinderDictionary()
+ {
+ { typeof(int), mockInnerBinder.Object }
+ }
+ };
+
+ // Act
+ object newModel = binder.BindComplexModel(controllerContext, bindingContext);
+
+ // Assert
+ var newIntArray = Assert.IsType<int[]>(newModel);
+ Assert.Equal(new[] { 0, 1, 2 }, newIntArray);
+ }
+
+ [Fact]
+ public void BindComplexModelCanBindCollections()
+ {
+ // Arrange
+ ControllerContext controllerContext = new Mock<ControllerContext>().Object;
+
+ ModelBindingContext bindingContext = new ModelBindingContext()
+ {
+ ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(null, typeof(IList<int>)),
+ ModelName = "foo",
+ PropertyFilter = _ => false,
+ ValueProvider = new SimpleValueProvider()
+ {
+ { "foo[0]", null },
+ { "foo[1]", null },
+ { "foo[2]", null }
+ }
+ };
+
+ Mock<IModelBinder> mockInnerBinder = new Mock<IModelBinder>();
+ mockInnerBinder
+ .Setup(b => b.BindModel(It.IsAny<ControllerContext>(), It.IsAny<ModelBindingContext>()))
+ .Returns(
+ delegate(ControllerContext cc, ModelBindingContext bc)
+ {
+ Assert.Equal(controllerContext, cc);
+ Assert.Equal(typeof(int), bc.ModelType);
+ Assert.Equal(bindingContext.ModelState, bc.ModelState);
+ Assert.Equal(bindingContext.PropertyFilter, bc.PropertyFilter);
+ Assert.Equal(bindingContext.ValueProvider, bc.ValueProvider);
+ return Int32.Parse(bc.ModelName.Substring(4, 1), CultureInfo.InvariantCulture);
+ });
+
+ DefaultModelBinder binder = new DefaultModelBinder()
+ {
+ Binders = new ModelBinderDictionary()
+ {
+ { typeof(int), mockInnerBinder.Object }
+ }
+ };
+
+ // Act
+ object newModel = binder.BindComplexModel(controllerContext, bindingContext);
+
+ // Assert
+ var modelAsList = Assert.IsAssignableFrom<IList<int>>(newModel);
+ Assert.Equal(new[] { 0, 1, 2 }, modelAsList.ToArray());
+ }
+
+ [Fact]
+ public void BindComplexModelCanBindDictionariesWithDotsNotation()
+ {
+ // Arrange
+ ControllerContext controllerContext = new Mock<ControllerContext>().Object;
+
+ ModelBindingContext bindingContext = new ModelBindingContext()
+ {
+ ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(null, typeof(IDictionary<string, CountryState>)),
+ ModelName = "countries",
+ PropertyFilter = _ => true,
+ ValueProvider = new DictionaryValueProvider<object>(new Dictionary<string, object>()
+ {
+ { "countries.CA.Name", "Canada" },
+ { "countries.CA.States[0]", "Québec" },
+ { "countries.CA.States[1]", "British Columbia" },
+ { "countries.US.Name", "United States" },
+ { "countries.US.States[0]", "Washington" },
+ { "countries.US.States[1]", "Oregon" }
+ }, CultureInfo.CurrentCulture)
+ };
+
+ DefaultModelBinder binder = new DefaultModelBinder();
+
+ // Act
+ object newModel = binder.BindComplexModel(controllerContext, bindingContext);
+
+ // Assert
+ var modelAsDictionary = Assert.IsAssignableFrom<IDictionary<string, CountryState>>(newModel);
+ Assert.Equal(2, modelAsDictionary.Count);
+ Assert.Equal("Canada", modelAsDictionary["CA"].Name);
+ Assert.Equal("United States", modelAsDictionary["US"].Name);
+ Assert.Equal(2, modelAsDictionary["CA"].States.Count());
+ Assert.True(modelAsDictionary["CA"].States.Contains("Québec"));
+ Assert.True(modelAsDictionary["CA"].States.Contains("British Columbia"));
+ Assert.Equal(2, modelAsDictionary["US"].States.Count());
+ Assert.True(modelAsDictionary["US"].States.Contains("Washington"));
+ Assert.True(modelAsDictionary["US"].States.Contains("Oregon"));
+ }
+
+ [Fact]
+ public void BindComplexModelCanBindDictionariesWithBracketsNotation()
+ {
+ // Arrange
+ ControllerContext controllerContext = new Mock<ControllerContext>().Object;
+
+ ModelBindingContext bindingContext = new ModelBindingContext()
+ {
+ ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(null, typeof(IDictionary<string, CountryState>)),
+ ModelName = "countries",
+ PropertyFilter = _ => true,
+ ValueProvider = new DictionaryValueProvider<object>(new Dictionary<string, object>()
+ {
+ { "countries[CA].Name", "Canada" },
+ { "countries[CA].States[0]", "Québec" },
+ { "countries[CA].States[1]", "British Columbia" },
+ { "countries[US].Name", "United States" },
+ { "countries[US].States[0]", "Washington" },
+ { "countries[US].States[1]", "Oregon" }
+ }, CultureInfo.CurrentCulture)
+ };
+
+ DefaultModelBinder binder = new DefaultModelBinder();
+
+ // Act
+ object newModel = binder.BindComplexModel(controllerContext, bindingContext);
+
+ // Assert
+ var modelAsDictionary = Assert.IsAssignableFrom<IDictionary<string, CountryState>>(newModel);
+ Assert.Equal(2, modelAsDictionary.Count);
+ Assert.Equal("Canada", modelAsDictionary["CA"].Name);
+ Assert.Equal("United States", modelAsDictionary["US"].Name);
+ Assert.Equal(2, modelAsDictionary["CA"].States.Count());
+ Assert.True(modelAsDictionary["CA"].States.Contains("Québec"));
+ Assert.True(modelAsDictionary["CA"].States.Contains("British Columbia"));
+ Assert.Equal(2, modelAsDictionary["US"].States.Count());
+ Assert.True(modelAsDictionary["US"].States.Contains("Washington"));
+ Assert.True(modelAsDictionary["US"].States.Contains("Oregon"));
+ }
+
+ [Fact]
+ public void BindComplexModelCanBindDictionariesWithBracketsAndDotsNotation()
+ {
+ // Arrange
+ ControllerContext controllerContext = new Mock<ControllerContext>().Object;
+
+ ModelBindingContext bindingContext = new ModelBindingContext()
+ {
+ ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(null, typeof(IDictionary<string, CountryState>)),
+ ModelName = "countries",
+ PropertyFilter = _ => true,
+ ValueProvider = new DictionaryValueProvider<object>(new Dictionary<string, object>()
+ {
+ { "countries[CA].Name", "Canada" },
+ { "countries[CA].States[0]", "Québec" },
+ { "countries.CA.States[1]", "British Columbia" },
+ { "countries.US.Name", "United States" },
+ { "countries.US.States[0]", "Washington" },
+ { "countries.US.States[1]", "Oregon" }
+ }, CultureInfo.CurrentCulture)
+ };
+
+ DefaultModelBinder binder = new DefaultModelBinder();
+
+ // Act
+ object newModel = binder.BindComplexModel(controllerContext, bindingContext);
+
+ // Assert
+ var modelAsDictionary = Assert.IsAssignableFrom<IDictionary<string, CountryState>>(newModel);
+ Assert.Equal(2, modelAsDictionary.Count);
+ Assert.Equal("Canada", modelAsDictionary["CA"].Name);
+ Assert.Equal("United States", modelAsDictionary["US"].Name);
+ Assert.Equal(1, modelAsDictionary["CA"].States.Count());
+ Assert.True(modelAsDictionary["CA"].States.Contains("Québec"));
+
+ // We do not accept double notation for a same entry, so we can't find that state.
+ Assert.False(modelAsDictionary["CA"].States.Contains("British Columbia"));
+ Assert.Equal(2, modelAsDictionary["US"].States.Count());
+ Assert.True(modelAsDictionary["US"].States.Contains("Washington"));
+ Assert.True(modelAsDictionary["US"].States.Contains("Oregon"));
+ }
+
+ [Fact]
+ public void BindComplexModelCanBindDictionaries()
+ {
+ // Arrange
+ ControllerContext controllerContext = new Mock<ControllerContext>().Object;
+
+ ModelBindingContext bindingContext = new ModelBindingContext()
+ {
+ ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(null, typeof(IDictionary<int, string>)),
+ ModelName = "foo",
+ PropertyFilter = _ => false,
+ ValueProvider = new SimpleValueProvider()
+ {
+ { "foo[0].key", null }, { "foo[0].value", null },
+ { "foo[1].key", null }, { "foo[1].value", null },
+ { "foo[2].key", null }, { "foo[2].value", null }
+ }
+ };
+
+ Mock<IModelBinder> mockIntBinder = new Mock<IModelBinder>();
+ mockIntBinder
+ .Setup(b => b.BindModel(It.IsAny<ControllerContext>(), It.IsAny<ModelBindingContext>()))
+ .Returns(
+ delegate(ControllerContext cc, ModelBindingContext bc)
+ {
+ Assert.Equal(controllerContext, cc);
+ Assert.Equal(typeof(int), bc.ModelType);
+ Assert.Equal(bindingContext.ModelState, bc.ModelState);
+ Assert.Equal(new ModelBindingContext().PropertyFilter, bc.PropertyFilter);
+ Assert.Equal(bindingContext.ValueProvider, bc.ValueProvider);
+ return Int32.Parse(bc.ModelName.Substring(4, 1), CultureInfo.InvariantCulture) + 10;
+ });
+
+ Mock<IModelBinder> mockStringBinder = new Mock<IModelBinder>();
+ mockStringBinder
+ .Setup(b => b.BindModel(It.IsAny<ControllerContext>(), It.IsAny<ModelBindingContext>()))
+ .Returns(
+ delegate(ControllerContext cc, ModelBindingContext bc)
+ {
+ Assert.Equal(controllerContext, cc);
+ Assert.Equal(typeof(string), bc.ModelType);
+ Assert.Equal(bindingContext.ModelState, bc.ModelState);
+ Assert.Equal(bindingContext.PropertyFilter, bc.PropertyFilter);
+ Assert.Equal(bindingContext.ValueProvider, bc.ValueProvider);
+ return (Int32.Parse(bc.ModelName.Substring(4, 1), CultureInfo.InvariantCulture) + 10) + "Value";
+ });
+
+ DefaultModelBinder binder = new DefaultModelBinder()
+ {
+ Binders = new ModelBinderDictionary()
+ {
+ { typeof(int), mockIntBinder.Object },
+ { typeof(string), mockStringBinder.Object }
+ }
+ };
+
+ // Act
+ object newModel = binder.BindComplexModel(controllerContext, bindingContext);
+
+ // Assert
+ var modelAsDictionary = Assert.IsAssignableFrom<IDictionary<int, string>>(newModel);
+ Assert.Equal(3, modelAsDictionary.Count);
+ Assert.Equal("10Value", modelAsDictionary[10]);
+ Assert.Equal("11Value", modelAsDictionary[11]);
+ Assert.Equal("12Value", modelAsDictionary[12]);
+ }
+
+ [Fact]
+ public void BindComplexModelCanBindObjects()
+ {
+ // Arrange
+ ControllerContext controllerContext = new Mock<ControllerContext>().Object;
+
+ ModelWithoutBindAttribute model = new ModelWithoutBindAttribute()
+ {
+ Foo = "FooPreValue",
+ Bar = "BarPreValue",
+ Baz = "BazPreValue",
+ };
+ ModelBindingContext bindingContext = new ModelBindingContext()
+ {
+ ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(() => model, model.GetType()),
+ ValueProvider = new SimpleValueProvider() { { "Foo", null }, { "Bar", null } }
+ };
+
+ Mock<IModelBinder> mockInnerBinder = new Mock<IModelBinder>();
+ mockInnerBinder
+ .Setup(b => b.BindModel(It.IsAny<ControllerContext>(), It.IsAny<ModelBindingContext>()))
+ .Returns(
+ delegate(ControllerContext cc, ModelBindingContext bc)
+ {
+ Assert.Equal(controllerContext, cc);
+ Assert.Equal(bindingContext.ValueProvider, bc.ValueProvider);
+ return bc.ModelName + "PostValue";
+ });
+
+ DefaultModelBinder binder = new DefaultModelBinder()
+ {
+ Binders = new ModelBinderDictionary()
+ {
+ { typeof(string), mockInnerBinder.Object }
+ }
+ };
+
+ // Act
+ object updatedModel = binder.BindComplexModel(controllerContext, bindingContext);
+
+ // Assert
+ Assert.Same(model, updatedModel);
+ Assert.Equal("FooPostValue", model.Foo);
+ Assert.Equal("BarPostValue", model.Bar);
+ Assert.Equal("BazPreValue", model.Baz);
+ }
+
+ [Fact]
+ public void BindComplexModelReturnsNullArrayIfNoValuesProvided()
+ {
+ // Arrange
+ ModelBindingContext bindingContext = new ModelBindingContext()
+ {
+ ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(null, typeof(int[])),
+ ModelName = "foo",
+ ValueProvider = new SimpleValueProvider() { { "foo", null } }
+ };
+
+ Mock<IModelBinder> mockInnerBinder = new Mock<IModelBinder>();
+ mockInnerBinder
+ .Setup(b => b.BindModel(It.IsAny<ControllerContext>(), It.IsAny<ModelBindingContext>()))
+ .Returns(
+ delegate(ControllerContext cc, ModelBindingContext bc) { return Int32.Parse(bc.ModelName.Substring(4, 1), CultureInfo.InvariantCulture); });
+
+ DefaultModelBinder binder = new DefaultModelBinder()
+ {
+ Binders = new ModelBinderDictionary()
+ {
+ { typeof(int), mockInnerBinder.Object }
+ }
+ };
+
+ // Act
+ object newModel = binder.BindComplexModel(null, bindingContext);
+
+ // Assert
+ Assert.Null(newModel);
+ }
+
+ [Fact]
+ public void BindComplexModelWhereModelTypeContainsBindAttribute()
+ {
+ // Arrange
+ ControllerContext controllerContext = new Mock<ControllerContext>().Object;
+
+ ModelWithBindAttribute model = new ModelWithBindAttribute()
+ {
+ Foo = "FooPreValue",
+ Bar = "BarPreValue",
+ Baz = "BazPreValue",
+ };
+ ModelBindingContext bindingContext = new ModelBindingContext()
+ {
+ ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(() => model, model.GetType()),
+ ValueProvider = new SimpleValueProvider() { { "Foo", null }, { "Bar", null } }
+ };
+
+ Mock<IModelBinder> mockInnerBinder = new Mock<IModelBinder>();
+ mockInnerBinder
+ .Setup(b => b.BindModel(It.IsAny<ControllerContext>(), It.IsAny<ModelBindingContext>()))
+ .Returns(
+ delegate(ControllerContext cc, ModelBindingContext bc)
+ {
+ Assert.Equal(controllerContext, cc);
+ Assert.Equal(bindingContext.ValueProvider, bc.ValueProvider);
+ return bc.ModelName + "PostValue";
+ });
+
+ DefaultModelBinder binder = new DefaultModelBinder()
+ {
+ Binders = new ModelBinderDictionary()
+ {
+ { typeof(string), mockInnerBinder.Object }
+ }
+ };
+
+ // Act
+ binder.BindComplexModel(controllerContext, bindingContext);
+
+ // Assert
+ Assert.Equal("FooPreValue", model.Foo);
+ Assert.Equal("BarPostValue", model.Bar);
+ Assert.Equal("BazPreValue", model.Baz);
+ }
+
+ [Fact]
+ public void BindComplexModelWhereModelTypeDoesNotContainBindAttribute()
+ {
+ // Arrange
+ ControllerContext controllerContext = new Mock<ControllerContext>().Object;
+
+ ModelWithoutBindAttribute model = new ModelWithoutBindAttribute()
+ {
+ Foo = "FooPreValue",
+ Bar = "BarPreValue",
+ Baz = "BazPreValue",
+ };
+ ModelBindingContext bindingContext = new ModelBindingContext()
+ {
+ ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(() => model, model.GetType()),
+ ValueProvider = new SimpleValueProvider() { { "Foo", null }, { "Bar", null } }
+ };
+
+ Mock<IModelBinder> mockInnerBinder = new Mock<IModelBinder>();
+ mockInnerBinder
+ .Setup(b => b.BindModel(It.IsAny<ControllerContext>(), It.IsAny<ModelBindingContext>()))
+ .Returns(
+ delegate(ControllerContext cc, ModelBindingContext bc)
+ {
+ Assert.Equal(controllerContext, cc);
+ Assert.Equal(bindingContext.ValueProvider, bc.ValueProvider);
+ return bc.ModelName + "PostValue";
+ });
+
+ DefaultModelBinder binder = new DefaultModelBinder()
+ {
+ Binders = new ModelBinderDictionary()
+ {
+ { typeof(string), mockInnerBinder.Object }
+ }
+ };
+
+ // Act
+ binder.BindComplexModel(controllerContext, bindingContext);
+
+ // Assert
+ Assert.Equal("FooPostValue", model.Foo);
+ Assert.Equal("BarPostValue", model.Bar);
+ Assert.Equal("BazPreValue", model.Baz);
+ }
+
+ // BindModel tests
+
+ [Fact]
+ public void BindModelCanBindObjects()
+ {
+ // Arrange
+ ControllerContext controllerContext = new Mock<ControllerContext>().Object;
+
+ ModelWithoutBindAttribute model = new ModelWithoutBindAttribute()
+ {
+ Foo = "FooPreValue",
+ Bar = "BarPreValue",
+ Baz = "BazPreValue",
+ };
+ ModelBindingContext bindingContext = new ModelBindingContext()
+ {
+ ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(() => model, model.GetType()),
+ ValueProvider = new SimpleValueProvider() { { "Foo", null }, { "Bar", null } }
+ };
+
+ Mock<IModelBinder> mockInnerBinder = new Mock<IModelBinder>();
+ mockInnerBinder
+ .Setup(b => b.BindModel(It.IsAny<ControllerContext>(), It.IsAny<ModelBindingContext>()))
+ .Returns(
+ delegate(ControllerContext cc, ModelBindingContext bc)
+ {
+ Assert.Equal(controllerContext, cc);
+ Assert.Equal(bindingContext.ValueProvider, bc.ValueProvider);
+ return bc.ModelName + "PostValue";
+ });
+
+ DefaultModelBinder binder = new DefaultModelBinder()
+ {
+ Binders = new ModelBinderDictionary()
+ {
+ { typeof(string), mockInnerBinder.Object }
+ }
+ };
+
+ // Act
+ object updatedModel = binder.BindModel(controllerContext, bindingContext);
+
+ // Assert
+ Assert.Same(model, updatedModel);
+ Assert.Equal("FooPostValue", model.Foo);
+ Assert.Equal("BarPostValue", model.Bar);
+ Assert.Equal("BazPreValue", model.Baz);
+ }
+
+ [Fact]
+ public void BindModelCanBindSimpleTypes()
+ {
+ // Arrange
+ ModelBindingContext bindingContext = new ModelBindingContext()
+ {
+ ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(null, typeof(int)),
+ ModelName = "foo",
+ ValueProvider = new SimpleValueProvider()
+ {
+ { "foo", "42" }
+ }
+ };
+
+ DefaultModelBinder binder = new DefaultModelBinder();
+
+ // Act
+ object updatedModel = binder.BindModel(new ControllerContext(), bindingContext);
+
+ // Assert
+ Assert.Equal(42, updatedModel);
+ }
+
+ [Fact]
+ public void BindModel_PerformsValidationByDefault()
+ {
+ // Arrange
+ ModelMetadata metadata = new DataAnnotationsModelMetadataProvider().GetMetadataForType(null, typeof(string));
+
+ ControllerContext controllerContext = new ControllerContext();
+ controllerContext.Controller = new SimpleController();
+
+ ModelBindingContext bindingContext = new ModelBindingContext()
+ {
+ ModelMetadata = metadata,
+ ModelName = "foo",
+ ValueProvider = new CustomUnvalidatedValueProvider()
+ };
+
+ DefaultModelBinder binder = new DefaultModelBinder();
+
+ // Act
+ object updatedModel = binder.BindModel(controllerContext, bindingContext);
+
+ // Assert
+ Assert.Equal("fooValidated", updatedModel);
+ }
+
+ [Fact]
+ public void BindModel_SkipsValidationIfControllerOptsOut()
+ {
+ // Arrange
+ ModelMetadata metadata = new DataAnnotationsModelMetadataProvider().GetMetadataForType(null, typeof(string));
+
+ ControllerContext controllerContext = new ControllerContext();
+ controllerContext.Controller = new SimpleController();
+ controllerContext.Controller.ValidateRequest = false;
+
+ ModelBindingContext bindingContext = new ModelBindingContext()
+ {
+ ModelMetadata = metadata,
+ ModelName = "foo",
+ ValueProvider = new CustomUnvalidatedValueProvider()
+ };
+
+ DefaultModelBinder binder = new DefaultModelBinder();
+
+ // Act
+ object updatedModel = binder.BindModel(controllerContext, bindingContext);
+
+ // Assert
+ Assert.Equal("fooUnvalidated", updatedModel);
+ }
+
+ [Fact]
+ public void BindModel_SkipsValidationIfModelOptsOut()
+ {
+ // Arrange
+ ModelMetadata metadata = new DataAnnotationsModelMetadataProvider().GetMetadataForType(null, typeof(string));
+ metadata.RequestValidationEnabled = false;
+
+ ControllerContext controllerContext = new ControllerContext();
+ controllerContext.Controller = new SimpleController();
+
+ ModelBindingContext bindingContext = new ModelBindingContext()
+ {
+ ModelMetadata = metadata,
+ ModelName = "foo",
+ ValueProvider = new CustomUnvalidatedValueProvider()
+ };
+
+ DefaultModelBinder binder = new DefaultModelBinder();
+
+ // Act
+ object updatedModel = binder.BindModel(controllerContext, bindingContext);
+
+ // Assert
+ Assert.Equal("fooUnvalidated", updatedModel);
+ }
+
+ [Fact]
+ public void BindModelReturnsNullIfKeyNotFound()
+ {
+ // Arrange
+ ModelBindingContext bindingContext = new ModelBindingContext()
+ {
+ ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(null, typeof(int)),
+ ModelName = "foo",
+ ValueProvider = new SimpleValueProvider()
+ };
+
+ DefaultModelBinder binder = new DefaultModelBinder();
+
+ // Act
+ object returnedModel = binder.BindModel(new ControllerContext(), bindingContext);
+
+ // Assert
+ Assert.Null(returnedModel);
+ }
+
+ [Fact]
+ public void BindModelThrowsIfBindingContextIsNull()
+ {
+ // Arrange
+ DefaultModelBinder binder = new DefaultModelBinder();
+
+ // Act & assert
+ Assert.ThrowsArgumentNull(
+ delegate { binder.BindModel(new ControllerContext(), null); }, "bindingContext");
+ }
+
+ [Fact]
+ public void BindModelValuesCanBeOverridden()
+ {
+ // Arrange
+ ModelBindingContext bindingContext = new ModelBindingContext()
+ {
+ ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(() => new ModelWithoutBindAttribute(), typeof(ModelWithoutBindAttribute)),
+ ModelName = "",
+ ValueProvider = new SimpleValueProvider()
+ {
+ { "foo", "FooPostValue" },
+ { "bar", "BarPostValue" },
+ { "baz", "BazPostValue" }
+ }
+ };
+ Mock<DefaultModelBinder> binder = new Mock<DefaultModelBinder> { CallBase = true };
+ binder.Protected().Setup<object>("GetPropertyValue",
+ ItExpr.IsAny<ControllerContext>(), ItExpr.IsAny<ModelBindingContext>(),
+ ItExpr.IsAny<PropertyDescriptor>(), ItExpr.IsAny<IModelBinder>())
+ .Returns("Hello, world!");
+
+ // Act
+ ModelWithoutBindAttribute model = (ModelWithoutBindAttribute)binder.Object.BindModel(new ControllerContext(), bindingContext);
+
+ // Assert
+ Assert.Equal("Hello, world!", model.Bar);
+ Assert.Equal("Hello, world!", model.Baz);
+ Assert.Equal("Hello, world!", model.Foo);
+ }
+
+ [Fact]
+ public void BindModelWithTypeConversionErrorUpdatesModelStateMessage()
+ {
+ // Arrange
+ ModelBindingContext bindingContext = new ModelBindingContext
+ {
+ ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(() => new PropertyTestingModel(), typeof(PropertyTestingModel)),
+ ModelName = "",
+ ValueProvider = new SimpleValueProvider()
+ {
+ { "IntReadWrite", "foo" }
+ },
+ };
+ DefaultModelBinder binder = new DefaultModelBinder();
+
+ // Act
+ PropertyTestingModel model = (PropertyTestingModel)binder.BindModel(new ControllerContext(), bindingContext);
+
+ // Assert
+ ModelState modelState = bindingContext.ModelState["IntReadWrite"];
+ Assert.NotNull(modelState);
+ Assert.Single(modelState.Errors);
+ Assert.Equal("The value 'foo' is not valid for IntReadWrite.", modelState.Errors[0].ErrorMessage);
+ }
+
+ [Fact]
+ public void BindModelWithPrefix()
+ {
+ // Arrange
+ ModelWithoutBindAttribute model = new ModelWithoutBindAttribute()
+ {
+ Foo = "FooPreValue",
+ Bar = "BarPreValue",
+ Baz = "BazPreValue",
+ };
+ ModelBindingContext bindingContext = new ModelBindingContext()
+ {
+ ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(() => model, model.GetType()),
+ ModelName = "prefix",
+ ValueProvider = new SimpleValueProvider()
+ {
+ { "prefix.foo", "FooPostValue" },
+ { "prefix.bar", "BarPostValue" }
+ }
+ };
+
+ DefaultModelBinder binder = new DefaultModelBinder();
+
+ // Act
+ object updatedModel = binder.BindModel(new ControllerContext(), bindingContext);
+
+ // Assert
+ Assert.Same(model, updatedModel);
+ Assert.Equal("FooPostValue", model.Foo);
+ Assert.Equal("BarPostValue", model.Bar);
+ Assert.Equal("BazPreValue", model.Baz);
+ }
+
+ [Fact]
+ public void BindModelWithPrefixAndFallback()
+ {
+ // Arrange
+ ModelWithoutBindAttribute model = new ModelWithoutBindAttribute()
+ {
+ Foo = "FooPreValue",
+ Bar = "BarPreValue",
+ Baz = "BazPreValue",
+ };
+ ModelBindingContext bindingContext = new ModelBindingContext()
+ {
+ FallbackToEmptyPrefix = true,
+ ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(() => model, model.GetType()),
+ ModelName = "prefix",
+ ValueProvider = new SimpleValueProvider()
+ {
+ { "foo", "FooPostValue" },
+ { "bar", "BarPostValue" }
+ }
+ };
+
+ DefaultModelBinder binder = new DefaultModelBinder();
+
+ // Act
+ object updatedModel = binder.BindModel(new ControllerContext(), bindingContext);
+
+ // Assert
+ Assert.Same(model, updatedModel);
+ Assert.Equal("FooPostValue", model.Foo);
+ Assert.Equal("BarPostValue", model.Bar);
+ Assert.Equal("BazPreValue", model.Baz);
+ }
+
+ [Fact]
+ public void BindModelWithPrefixReturnsNullIfFallbackNotSpecifiedAndValueProviderContainsNoEntries()
+ {
+ // Arrange
+ ModelWithoutBindAttribute model = new ModelWithoutBindAttribute()
+ {
+ Foo = "FooPreValue",
+ Bar = "BarPreValue",
+ Baz = "BazPreValue",
+ };
+ ModelBindingContext bindingContext = new ModelBindingContext()
+ {
+ ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(() => model, model.GetType()),
+ ModelName = "prefix",
+ ValueProvider = new SimpleValueProvider()
+ {
+ { "foo", "FooPostValue" },
+ { "bar", "BarPostValue" }
+ }
+ };
+
+ DefaultModelBinder binder = new DefaultModelBinder();
+
+ // Act
+ object updatedModel = binder.BindModel(new ControllerContext(), bindingContext);
+
+ // Assert
+ Assert.Null(updatedModel);
+ }
+
+ [Fact]
+ public void BindModelReturnsNullIfSimpleTypeNotFound()
+ {
+ // DevDiv 216165: ModelBinders should not try and instantiate simple types
+
+ // Arrange
+ ModelBindingContext bindingContext = new ModelBindingContext()
+ {
+ ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(null, typeof(string)),
+ ModelName = "prefix",
+ ValueProvider = new SimpleValueProvider()
+ {
+ { "prefix.foo", "foo" },
+ { "prefix.bar", "bar" }
+ }
+ };
+
+ DefaultModelBinder binder = new DefaultModelBinder();
+
+ // Act
+ object updatedModel = binder.BindModel(new ControllerContext(), bindingContext);
+
+ // Assert
+ Assert.Null(updatedModel);
+ }
+
+ // BindProperty tests
+
+ [Fact]
+ public void BindPropertyCanUpdateComplexReadOnlyProperties()
+ {
+ // Arrange
+ // the Customer type contains a single read-only Address property
+ Customer model = new Customer();
+ ModelBindingContext bindingContext = new ModelBindingContext()
+ {
+ ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(() => model, model.GetType()),
+ ValueProvider = new SimpleValueProvider() { { "Address", null } }
+ };
+
+ Mock<IModelBinder> mockInnerBinder = new Mock<IModelBinder>();
+ mockInnerBinder
+ .Setup(b => b.BindModel(It.IsAny<ControllerContext>(), It.IsAny<ModelBindingContext>()))
+ .Returns(
+ delegate(ControllerContext cc, ModelBindingContext bc)
+ {
+ Address address = (Address)bc.Model;
+ address.Street = "1 Microsoft Way";
+ address.Zip = "98052";
+ return address;
+ });
+
+ PropertyDescriptor pd = TypeDescriptor.GetProperties(model)["Address"];
+ DefaultModelBinderHelper helper = new DefaultModelBinderHelper()
+ {
+ Binders = new ModelBinderDictionary()
+ {
+ { typeof(Address), mockInnerBinder.Object }
+ }
+ };
+
+ // Act
+ helper.PublicBindProperty(new ControllerContext(), bindingContext, pd);
+
+ // Assert
+ Assert.Equal("1 Microsoft Way", model.Address.Street);
+ Assert.Equal("98052", model.Address.Zip);
+ }
+
+ [Fact]
+ public void BindPropertyDoesNothingIfValueProviderContainsNoEntryForProperty()
+ {
+ // Arrange
+ MyModel2 model = new MyModel2() { IntReadWrite = 3 };
+ ModelBindingContext bindingContext = new ModelBindingContext()
+ {
+ ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(() => model, model.GetType()),
+ ValueProvider = new SimpleValueProvider()
+ };
+
+ PropertyDescriptor pd = TypeDescriptor.GetProperties(model)["IntReadWrite"];
+ DefaultModelBinderHelper helper = new DefaultModelBinderHelper();
+
+ // Act
+ helper.PublicBindProperty(new ControllerContext(), bindingContext, pd);
+
+ // Assert
+ Assert.Equal(3, model.IntReadWrite);
+ }
+
+ [Fact]
+ public void BindProperty()
+ {
+ // Arrange
+ ControllerContext controllerContext = new Mock<ControllerContext>().Object;
+ MyModel2 model = new MyModel2() { IntReadWrite = 3 };
+ ModelBindingContext bindingContext = new ModelBindingContext()
+ {
+ ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(() => model, model.GetType()),
+ ValueProvider = new SimpleValueProvider()
+ {
+ { "IntReadWrite", "42" }
+ }
+ };
+
+ PropertyDescriptor pd = TypeDescriptor.GetProperties(model)["IntReadWrite"];
+
+ Mock<DefaultModelBinderHelper> mockHelper = new Mock<DefaultModelBinderHelper>() { CallBase = true };
+ mockHelper.Setup(b => b.PublicOnPropertyValidating(controllerContext, bindingContext, pd, 42)).Returns(true).Verifiable();
+ mockHelper.Setup(b => b.PublicSetProperty(controllerContext, bindingContext, pd, 42)).Verifiable();
+ mockHelper.Setup(b => b.PublicOnPropertyValidated(controllerContext, bindingContext, pd, 42)).Verifiable();
+ DefaultModelBinderHelper helper = mockHelper.Object;
+
+ // Act
+ helper.PublicBindProperty(controllerContext, bindingContext, pd);
+
+ // Assert
+ mockHelper.Verify();
+ }
+
+ [Fact]
+ public void BindPropertyReturnsIfOnPropertyValidatingReturnsFalse()
+ {
+ // Arrange
+ ControllerContext controllerContext = new Mock<ControllerContext>().Object;
+ MyModel2 model = new MyModel2() { IntReadWrite = 3 };
+ ModelBindingContext bindingContext = new ModelBindingContext()
+ {
+ ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(() => model, model.GetType()),
+ ValueProvider = new SimpleValueProvider()
+ {
+ { "IntReadWrite", "42" }
+ }
+ };
+
+ PropertyDescriptor pd = TypeDescriptor.GetProperties(model)["IntReadWrite"];
+
+ Mock<DefaultModelBinderHelper> mockHelper = new Mock<DefaultModelBinderHelper>() { CallBase = true };
+ mockHelper.Setup(b => b.PublicOnPropertyValidating(controllerContext, bindingContext, pd, 42)).Returns(false);
+ DefaultModelBinderHelper helper = mockHelper.Object;
+
+ // Act
+ helper.PublicBindProperty(controllerContext, bindingContext, pd);
+
+ // Assert
+ Assert.Equal(3, model.IntReadWrite);
+ mockHelper.Verify();
+ mockHelper.Verify(b => b.PublicSetProperty(controllerContext, bindingContext, pd, 42), Times.Never());
+ mockHelper.Verify(b => b.PublicOnPropertyValidated(controllerContext, bindingContext, pd, 42), Times.Never());
+ }
+
+ [Fact]
+ public void BindPropertySetsPropertyToNullIfUserLeftTextEntryFieldBlankForOptionalValue()
+ {
+ // Arrange
+ MyModel2 model = new MyModel2() { NullableIntReadWrite = 8 };
+ ModelBindingContext bindingContext = new ModelBindingContext()
+ {
+ ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(() => model, model.GetType()),
+ ValueProvider = new SimpleValueProvider() { { "NullableIntReadWrite", null } }
+ };
+
+ Mock<IModelBinder> mockInnerBinder = new Mock<IModelBinder>();
+ mockInnerBinder.Setup(b => b.BindModel(new ControllerContext(), It.IsAny<ModelBindingContext>())).Returns((object)null);
+
+ PropertyDescriptor pd = TypeDescriptor.GetProperties(model)["NullableIntReadWrite"];
+ DefaultModelBinderHelper helper = new DefaultModelBinderHelper()
+ {
+ Binders = new ModelBinderDictionary()
+ {
+ { typeof(int?), mockInnerBinder.Object }
+ }
+ };
+
+ // Act
+ helper.PublicBindProperty(new ControllerContext(), bindingContext, pd);
+
+ // Assert
+ Assert.Empty(bindingContext.ModelState);
+ Assert.Null(model.NullableIntReadWrite);
+ }
+
+ [Fact]
+ public void BindPropertyUpdatesPropertyOnFailureIfInnerBinderReturnsNonNullObject()
+ {
+ // Arrange
+ MyModel2 model = new MyModel2() { IntReadWriteNonNegative = 8 };
+ ModelBindingContext bindingContext = new ModelBindingContext()
+ {
+ ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(() => model, model.GetType()),
+ ValueProvider = new SimpleValueProvider() { { "IntReadWriteNonNegative", null } }
+ };
+
+ Mock<IModelBinder> mockInnerBinder = new Mock<IModelBinder>();
+ mockInnerBinder
+ .Setup(b => b.BindModel(It.IsAny<ControllerContext>(), It.IsAny<ModelBindingContext>()))
+ .Returns(
+ delegate(ControllerContext cc, ModelBindingContext bc)
+ {
+ bc.ModelState.AddModelError("IntReadWriteNonNegative", "Some error text.");
+ return 4;
+ });
+
+ PropertyDescriptor pd = TypeDescriptor.GetProperties(model)["IntReadWriteNonNegative"];
+ DefaultModelBinderHelper helper = new DefaultModelBinderHelper()
+ {
+ Binders = new ModelBinderDictionary()
+ {
+ { typeof(int), mockInnerBinder.Object }
+ }
+ };
+
+ // Act
+ helper.PublicBindProperty(new ControllerContext(), bindingContext, pd);
+
+ // Assert
+ Assert.Equal(false, bindingContext.ModelState.IsValidField("IntReadWriteNonNegative"));
+ var error = Assert.Single(bindingContext.ModelState["IntReadWriteNonNegative"].Errors);
+ Assert.Equal("Some error text.", error.ErrorMessage);
+ Assert.Equal(4, model.IntReadWriteNonNegative);
+ }
+
+ [Fact]
+ public void BindPropertyUpdatesPropertyOnSuccess()
+ {
+ // Arrange
+ // Effectively, this is just testing updating a single property named "IntReadWrite"
+ ControllerContext controllerContext = new Mock<ControllerContext>().Object;
+
+ MyModel2 model = new MyModel2() { IntReadWrite = 3 };
+ ModelBindingContext bindingContext = new ModelBindingContext()
+ {
+ ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(() => model, model.GetType()),
+ ModelName = "foo",
+ ModelState = new ModelStateDictionary() { { "blah", new ModelState() } },
+ ValueProvider = new SimpleValueProvider() { { "foo.IntReadWrite", null } }
+ };
+
+ Mock<IModelBinder> mockInnerBinder = new Mock<IModelBinder>();
+ mockInnerBinder
+ .Setup(b => b.BindModel(It.IsAny<ControllerContext>(), It.IsAny<ModelBindingContext>()))
+ .Returns(
+ delegate(ControllerContext cc, ModelBindingContext bc)
+ {
+ Assert.Equal(controllerContext, cc);
+ Assert.Equal(3, bc.Model);
+ Assert.Equal(typeof(int), bc.ModelType);
+ Assert.Equal("foo.IntReadWrite", bc.ModelName);
+ Assert.Equal(new ModelBindingContext().PropertyFilter, bc.PropertyFilter);
+ Assert.Equal(bindingContext.ModelState, bc.ModelState);
+ Assert.Equal(bindingContext.ValueProvider, bc.ValueProvider);
+ return 4;
+ });
+
+ PropertyDescriptor pd = TypeDescriptor.GetProperties(model)["IntReadWrite"];
+ DefaultModelBinderHelper helper = new DefaultModelBinderHelper()
+ {
+ Binders = new ModelBinderDictionary()
+ {
+ { typeof(int), mockInnerBinder.Object }
+ }
+ };
+
+ // Act
+ helper.PublicBindProperty(controllerContext, bindingContext, pd);
+
+ // Assert
+ Assert.Equal(4, model.IntReadWrite);
+ }
+
+ // BindSimpleModel tests
+
+ [Fact]
+ public void BindSimpleModelCanReturnArrayTypes()
+ {
+ // Arrange
+ ValueProviderResult result = new ValueProviderResult(42, null, null);
+ ModelBindingContext bindingContext = new ModelBindingContext()
+ {
+ ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(null, typeof(int[])),
+ ModelName = "foo",
+ };
+
+ DefaultModelBinder binder = new DefaultModelBinder();
+
+ // Act
+ object returnedValue = binder.BindSimpleModel(null, bindingContext, result);
+
+ // Assert
+ var returnedValueAsIntArray = Assert.IsType<int[]>(returnedValue);
+ Assert.Single(returnedValueAsIntArray);
+ Assert.Equal(42, returnedValueAsIntArray[0]);
+ }
+
+ [Fact]
+ public void BindSimpleModelCanReturnCollectionTypes()
+ {
+ // Arrange
+ ValueProviderResult result = new ValueProviderResult(new string[] { "42", "82" }, null, null);
+ ModelBindingContext bindingContext = new ModelBindingContext()
+ {
+ ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(null, typeof(IEnumerable<int>)),
+ ModelName = "foo",
+ };
+
+ DefaultModelBinder binder = new DefaultModelBinder();
+
+ // Act
+ object returnedValue = binder.BindSimpleModel(null, bindingContext, result);
+
+ // Assert
+ var returnedValueAsList = Assert.IsAssignableFrom<IEnumerable<int>>(returnedValue).ToList();
+ Assert.Equal(2, returnedValueAsList.Count);
+ Assert.Equal(42, returnedValueAsList[0]);
+ Assert.Equal(82, returnedValueAsList[1]);
+ }
+
+ [Fact]
+ public void BindSimpleModelCanReturnElementalTypes()
+ {
+ // Arrange
+ ValueProviderResult result = new ValueProviderResult("42", null, null);
+ ModelBindingContext bindingContext = new ModelBindingContext()
+ {
+ ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(null, typeof(int)),
+ ModelName = "foo",
+ };
+
+ DefaultModelBinder binder = new DefaultModelBinder();
+
+ // Act
+ object returnedValue = binder.BindSimpleModel(null, bindingContext, result);
+
+ // Assert
+ Assert.Equal(42, returnedValue);
+ }
+
+ [Fact]
+ public void BindSimpleModelCanReturnStrings()
+ {
+ // Arrange
+ ValueProviderResult result = new ValueProviderResult(new object[] { "42" }, null, null);
+ ModelBindingContext bindingContext = new ModelBindingContext()
+ {
+ ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(null, typeof(string)),
+ ModelName = "foo",
+ };
+
+ DefaultModelBinder binder = new DefaultModelBinder();
+
+ // Act
+ object returnedValue = binder.BindSimpleModel(null, bindingContext, result);
+
+ // Assert
+ Assert.Equal("42", returnedValue);
+ }
+
+ [Fact]
+ public void BindSimpleModelChecksValueProviderResultRawValueType()
+ {
+ // Arrange
+ ValueProviderResult result = new ValueProviderResult(new MemoryStream(), null, null);
+ ModelBindingContext bindingContext = new ModelBindingContext()
+ {
+ ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(null, typeof(Stream)),
+ ModelName = "foo",
+ };
+
+ DefaultModelBinder binder = new DefaultModelBinder();
+
+ // Act
+ object returnedValue = binder.BindSimpleModel(null, bindingContext, result);
+
+ // Assert
+ Assert.Equal(result, bindingContext.ModelState["foo"].Value);
+ Assert.Same(result.RawValue, returnedValue);
+ }
+
+ [Fact]
+ public void BindSimpleModelPropagatesErrorsOnFailure()
+ {
+ // Arrange
+ ValueProviderResult result = new ValueProviderResult("invalid", null, null);
+ ModelBindingContext bindingContext = new ModelBindingContext()
+ {
+ ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(null, typeof(int)),
+ ModelName = "foo",
+ };
+
+ DefaultModelBinder binder = new DefaultModelBinder();
+
+ // Act
+ object returnedValue = binder.BindSimpleModel(null, bindingContext, result);
+
+ // Assert
+ Assert.False(bindingContext.ModelState.IsValidField("foo"));
+ Assert.IsType<InvalidOperationException>(bindingContext.ModelState["foo"].Errors[0].Exception);
+ Assert.Equal("The parameter conversion from type 'System.String' to type 'System.Int32' failed. See the inner exception for more information.", bindingContext.ModelState["foo"].Errors[0].Exception.Message);
+ Assert.Null(returnedValue);
+ }
+
+ [Fact]
+ public void CreateComplexElementalModelBindingContext_ReadsBindAttributeFromBuddyClass()
+ {
+ // Arrange
+ ModelBindingContext originalBindingContext = new ModelBindingContext()
+ {
+ ModelMetadata = new EmptyModelMetadataProvider().GetMetadataForType(null, typeof(CreateComplexElementalModelBindingContext_ReadsBindAttributeFromBuddyClass_Model)),
+ ModelName = "someName",
+ ValueProvider = new SimpleValueProvider()
+ };
+
+ DefaultModelBinder binder = new DefaultModelBinder();
+
+ // Act
+ ModelBindingContext newBindingContext = binder.CreateComplexElementalModelBindingContext(new ControllerContext(), originalBindingContext, null);
+
+ // Assert
+ Assert.True(newBindingContext.PropertyFilter("foo"));
+ Assert.False(newBindingContext.PropertyFilter("bar"));
+ }
+
+ [MetadataType(typeof(CreateComplexElementalModelBindingContext_ReadsBindAttributeFromBuddyClass_Model_BuddyClass))]
+ private class CreateComplexElementalModelBindingContext_ReadsBindAttributeFromBuddyClass_Model
+ {
+ [Bind(Include = "foo")]
+ private class CreateComplexElementalModelBindingContext_ReadsBindAttributeFromBuddyClass_Model_BuddyClass
+ {
+ }
+ }
+
+ [Fact]
+ public void CreateInstanceCreatesModelInstance()
+ {
+ // Arrange
+ DefaultModelBinderHelper helper = new DefaultModelBinderHelper();
+
+ // Act
+ object modelObj = helper.PublicCreateModel(null, null, typeof(Guid));
+
+ // Assert
+ Assert.Equal(Guid.Empty, modelObj);
+ }
+
+ [Fact]
+ public void CreateInstanceCreatesModelInstanceForGenericICollection()
+ {
+ // Arrange
+ DefaultModelBinderHelper helper = new DefaultModelBinderHelper();
+
+ // Act
+ object modelObj = helper.PublicCreateModel(null, null, typeof(ICollection<Guid>));
+
+ // Assert
+ Assert.IsAssignableFrom<ICollection<Guid>>(modelObj);
+ }
+
+ [Fact]
+ public void CreateInstanceCreatesModelInstanceForGenericIDictionary()
+ {
+ // Arrange
+ DefaultModelBinderHelper helper = new DefaultModelBinderHelper();
+
+ // Act
+ object modelObj = helper.PublicCreateModel(null, null, typeof(IDictionary<string, Guid>));
+
+ // Assert
+ Assert.IsAssignableFrom<IDictionary<string, Guid>>(modelObj);
+ }
+
+ [Fact]
+ public void CreateInstanceCreatesModelInstanceForGenericIEnumerable()
+ {
+ // Arrange
+ DefaultModelBinderHelper helper = new DefaultModelBinderHelper();
+
+ // Act
+ object modelObj = helper.PublicCreateModel(null, null, typeof(IEnumerable<Guid>));
+
+ // Assert
+ Assert.IsAssignableFrom<ICollection<Guid>>(modelObj);
+ }
+
+ [Fact]
+ public void CreateInstanceCreatesModelInstanceForGenericIList()
+ {
+ // Arrange
+ DefaultModelBinderHelper helper = new DefaultModelBinderHelper();
+
+ // Act
+ object modelObj = helper.PublicCreateModel(null, null, typeof(IList<Guid>));
+
+ // Assert
+ Assert.IsAssignableFrom<IList<Guid>>(modelObj);
+ }
+
+ [Fact]
+ public void CreateSubIndexNameReturnsPrefixPlusIndex()
+ {
+ // Arrange
+ DefaultModelBinderHelper helper = new DefaultModelBinderHelper();
+
+ // Act
+ string newName = helper.PublicCreateSubIndexName("somePrefix", 2);
+
+ // Assert
+ Assert.Equal("somePrefix[2]", newName);
+ }
+
+ [Fact]
+ public void CreateSubPropertyNameReturnsPrefixPlusPropertyName()
+ {
+ // Arrange
+ DefaultModelBinderHelper helper = new DefaultModelBinderHelper();
+
+ // Act
+ string newName = helper.PublicCreateSubPropertyName("somePrefix", "someProperty");
+
+ // Assert
+ Assert.Equal("somePrefix.someProperty", newName);
+ }
+
+ [Fact]
+ public void CreateSubPropertyNameReturnsPropertyNameIfPrefixIsEmpty()
+ {
+ // Arrange
+ DefaultModelBinderHelper helper = new DefaultModelBinderHelper();
+
+ // Act
+ string newName = helper.PublicCreateSubPropertyName(String.Empty, "someProperty");
+
+ // Assert
+ Assert.Equal("someProperty", newName);
+ }
+
+ [Fact]
+ public void CreateSubPropertyNameReturnsPropertyNameIfPrefixIsNull()
+ {
+ // Arrange
+ DefaultModelBinderHelper helper = new DefaultModelBinderHelper();
+
+ // Act
+ string newName = helper.PublicCreateSubPropertyName(null, "someProperty");
+
+ // Assert
+ Assert.Equal("someProperty", newName);
+ }
+
+ [Fact]
+ public void GetFilteredModelPropertiesFiltersNonUpdateableProperties()
+ {
+ // Arrange
+ ModelBindingContext bindingContext = new ModelBindingContext()
+ {
+ ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(null, typeof(PropertyTestingModel)),
+ PropertyFilter = new BindAttribute() { Exclude = "Blacklisted" }.IsPropertyAllowed
+ };
+
+ DefaultModelBinderHelper helper = new DefaultModelBinderHelper();
+
+ // Act
+ PropertyDescriptorCollection properties = new PropertyDescriptorCollection(helper.PublicGetFilteredModelProperties(null, bindingContext).ToArray());
+
+ // Assert
+ Assert.NotNull(properties["StringReadWrite"]);
+ Assert.Null(properties["StringReadOnly"]);
+ Assert.NotNull(properties["IntReadWrite"]);
+ Assert.Null(properties["IntReadOnly"]);
+ Assert.NotNull(properties["ArrayReadWrite"]);
+ Assert.Null(properties["ArrayReadOnly"]);
+ Assert.NotNull(properties["AddressReadWrite"]);
+ Assert.NotNull(properties["AddressReadOnly"]);
+ Assert.NotNull(properties["Whitelisted"]);
+ Assert.Null(properties["Blacklisted"]);
+ Assert.Equal(6, properties.Count);
+ }
+
+ [Fact]
+ public void GetModelPropertiesReturnsUnfilteredPropertyList()
+ {
+ // Arrange
+ ModelBindingContext bindingContext = new ModelBindingContext()
+ {
+ ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(null, typeof(PropertyTestingModel)),
+ PropertyFilter = new BindAttribute() { Exclude = "Blacklisted" }.IsPropertyAllowed
+ };
+
+ DefaultModelBinderHelper helper = new DefaultModelBinderHelper();
+
+ // Act
+ PropertyDescriptorCollection properties = helper.PublicGetModelProperties(null, bindingContext);
+
+ // Assert
+ Assert.NotNull(properties["StringReadWrite"]);
+ Assert.NotNull(properties["StringReadOnly"]);
+ Assert.NotNull(properties["IntReadWrite"]);
+ Assert.NotNull(properties["IntReadOnly"]);
+ Assert.NotNull(properties["ArrayReadWrite"]);
+ Assert.NotNull(properties["ArrayReadOnly"]);
+ Assert.NotNull(properties["AddressReadWrite"]);
+ Assert.NotNull(properties["AddressReadOnly"]);
+ Assert.NotNull(properties["Whitelisted"]);
+ Assert.NotNull(properties["Blacklisted"]);
+ Assert.Equal(10, properties.Count);
+ }
+
+ [Fact]
+ public void IsModelValidWithNullBindingContextThrows()
+ {
+ // Act & Assert
+ Assert.ThrowsArgumentNull(
+ () => DefaultModelBinderHelper.PublicIsModelValid(null),
+ "bindingContext");
+ }
+
+ [Fact]
+ public void IsModelValidReturnsModelStateIsValidWhenModelNameIsEmpty()
+ {
+ // Arrange
+ ModelBindingContext contextWithNoErrors = new ModelBindingContext { ModelName = "" };
+ ModelBindingContext contextWithErrors = new ModelBindingContext { ModelName = "" };
+ contextWithErrors.ModelState.AddModelError("foo", "bar");
+
+ // Act & Assert
+ Assert.True(DefaultModelBinderHelper.PublicIsModelValid(contextWithNoErrors));
+ Assert.False(DefaultModelBinderHelper.PublicIsModelValid(contextWithErrors));
+ }
+
+ [Fact]
+ public void IsModelValidReturnsValidityOfSubModelStateWhenModelNameIsNotEmpty()
+ {
+ // Arrange
+ ModelBindingContext contextWithNoErrors = new ModelBindingContext { ModelName = "foo" };
+ ModelBindingContext contextWithErrors = new ModelBindingContext { ModelName = "foo" };
+ contextWithErrors.ModelState.AddModelError("foo.bar", "baz");
+ ModelBindingContext contextWithUnrelatedErrors = new ModelBindingContext { ModelName = "foo" };
+ contextWithUnrelatedErrors.ModelState.AddModelError("biff", "baz");
+
+ // Act & Assert
+ Assert.True(DefaultModelBinderHelper.PublicIsModelValid(contextWithNoErrors));
+ Assert.False(DefaultModelBinderHelper.PublicIsModelValid(contextWithErrors));
+ Assert.True(DefaultModelBinderHelper.PublicIsModelValid(contextWithUnrelatedErrors));
+ }
+
+ [Fact]
+ public void OnModelUpdatingReturnsTrue()
+ {
+ // By default, this method does nothing, so we just want to make sure it returns true
+
+ // Arrange
+ DefaultModelBinderHelper helper = new DefaultModelBinderHelper();
+
+ // Act
+ bool returned = helper.PublicOnModelUpdating(null, null);
+
+ // Arrange
+ Assert.True(returned);
+ }
+
+ // OnModelUpdated tests
+
+ [Fact]
+ public void OnModelUpdatedCalledWhenOnModelUpdatingReturnsTrue()
+ {
+ // Arrange
+ ModelBindingContext bindingContext = new ModelBindingContext()
+ {
+ ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(() => new ModelWithoutBindAttribute(), typeof(ModelWithoutBindAttribute)),
+ ModelName = "",
+ ValueProvider = new SimpleValueProvider()
+ };
+ Mock<DefaultModelBinder> binder = new Mock<DefaultModelBinder> { CallBase = true };
+ binder.Protected().Setup<bool>("OnModelUpdating",
+ ItExpr.IsAny<ControllerContext>(), ItExpr.IsAny<ModelBindingContext>())
+ .Returns(true);
+ binder.Protected().Setup("OnModelUpdated",
+ ItExpr.IsAny<ControllerContext>(), ItExpr.IsAny<ModelBindingContext>())
+ .Verifiable();
+
+ // Act
+ binder.Object.BindModel(new ControllerContext(), bindingContext);
+
+ // Assert
+ binder.Verify();
+ }
+
+ [Fact]
+ public void OnModelUpdatedNotCalledWhenOnModelUpdatingReturnsFalse()
+ {
+ // Arrange
+ ModelBindingContext bindingContext = new ModelBindingContext()
+ {
+ ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(() => new ModelWithoutBindAttribute(), typeof(ModelWithoutBindAttribute)),
+ ModelName = "",
+ ValueProvider = new SimpleValueProvider()
+ };
+ Mock<DefaultModelBinder> binder = new Mock<DefaultModelBinder> { CallBase = true };
+ binder.Protected().Setup<bool>("OnModelUpdating",
+ ItExpr.IsAny<ControllerContext>(), ItExpr.IsAny<ModelBindingContext>())
+ .Returns(false);
+
+ // Act
+ binder.Object.BindModel(new ControllerContext(), bindingContext);
+
+ // Assert
+ binder.Verify();
+ binder.Protected().Verify("OnModelUpdated", Times.Never(),
+ ItExpr.IsAny<ControllerContext>(), ItExpr.IsAny<ModelBindingContext>());
+ }
+
+ [Fact]
+ public void OnModelUpdatedDoesntAddNewMessagesWhenMessagesAlreadyExist()
+ {
+ // Arrange
+ var binder = new TestableDefaultModelBinder<SetPropertyModel>();
+ binder.Context.ModelState.AddModelError(BASE_MODEL_NAME + ".NonNullableStringWithAttribute", "Some pre-existing error");
+
+ // Act
+ binder.OnModelUpdated();
+
+ // Assert
+ var modelState = binder.Context.ModelState[BASE_MODEL_NAME + ".NonNullableStringWithAttribute"];
+ var error = Assert.Single(modelState.Errors);
+ Assert.Equal("Some pre-existing error", error.ErrorMessage);
+ }
+
+ [Fact]
+ public void OnPropertyValidatingNotCalledOnPropertiesWithErrors()
+ {
+ // Arrange
+ ModelWithoutBindAttribute model = new ModelWithoutBindAttribute();
+ ModelBindingContext bindingContext = new ModelBindingContext
+ {
+ ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(() => model, model.GetType()),
+ ModelName = "",
+ ValueProvider = new SimpleValueProvider()
+ {
+ { "foo", "foo" }
+ },
+ };
+ bindingContext.ModelState.AddModelError("foo", "Pre-existing error");
+ Mock<DefaultModelBinder> binder = new Mock<DefaultModelBinder> { CallBase = true };
+
+ // Act
+ binder.Object.BindModel(new ControllerContext(), bindingContext);
+
+ // Assert
+ binder.Verify();
+ binder.Protected().Verify("OnPropertyValidating", Times.Never(),
+ ItExpr.IsAny<ControllerContext>(), ItExpr.IsAny<ModelBindingContext>(),
+ ItExpr.IsAny<PropertyDescriptor>(), ItExpr.IsAny<object>());
+ }
+
+ public class ExtraValueModel
+ {
+ public int RequiredValue { get; set; }
+ }
+
+ [Fact]
+ public void ExtraValueRequiredMessageNotAddedForAlreadyInvalidProperty()
+ {
+ // Arrange
+ DefaultModelBinder binder = new DefaultModelBinder();
+ ModelBindingContext bindingContext = new ModelBindingContext()
+ {
+ ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(null, typeof(MyModel)),
+ ModelName = "theModel",
+ ValueProvider = new SimpleValueProvider()
+ };
+ bindingContext.ModelState.AddModelError("theModel.ReadWriteProperty", "Existing Error Message");
+
+ // Act
+ binder.BindModel(new ControllerContext(), bindingContext);
+
+ // Assert
+ ModelState modelState = bindingContext.ModelState["theModel.ReadWriteProperty"];
+ var error = Assert.Single(modelState.Errors);
+ Assert.Equal("Existing Error Message", error.ErrorMessage);
+ }
+
+ [Fact]
+ public void OnPropertyValidatingReturnsTrueOnSuccess()
+ {
+ // Arrange
+ ModelBindingContext bindingContext = new ModelBindingContext()
+ {
+ ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(null, typeof(MyModel)),
+ ModelName = "theModel"
+ };
+
+ PropertyDescriptor property = TypeDescriptor.GetProperties(typeof(MyModel))["ReadWriteProperty"];
+ DefaultModelBinderHelper helper = new DefaultModelBinderHelper();
+ bindingContext.PropertyMetadata["ReadWriteProperty"].Model = 42;
+
+ // Act
+ bool returned = helper.PublicOnPropertyValidating(new ControllerContext(), bindingContext, property, 42);
+
+ // Assert
+ Assert.True(returned);
+ Assert.Empty(bindingContext.ModelState);
+ }
+
+ [Fact]
+ public void UpdateCollectionCreatesDefaultEntriesForInvalidElements()
+ {
+ // Arrange
+ List<int> model = new List<int>() { 4, 5, 6, 7, 8 };
+ ModelBindingContext bindingContext = new ModelBindingContext()
+ {
+ ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(() => model, model.GetType()),
+ ModelName = "foo",
+ ValueProvider = new SimpleValueProvider()
+ {
+ { "foo[0]", null },
+ { "foo[1]", null },
+ { "foo[2]", null }
+ }
+ };
+
+ Mock<IModelBinder> mockInnerBinder = new Mock<IModelBinder>();
+ mockInnerBinder
+ .Setup(b => b.BindModel(It.IsAny<ControllerContext>(), It.IsAny<ModelBindingContext>()))
+ .Returns(
+ delegate(ControllerContext cc, ModelBindingContext bc)
+ {
+ int fooIdx = Int32.Parse(bc.ModelName.Substring(4, 1), CultureInfo.InvariantCulture);
+ return (fooIdx == 1) ? (object)null : fooIdx;
+ });
+
+ DefaultModelBinder binder = new DefaultModelBinder()
+ {
+ Binders = new ModelBinderDictionary()
+ {
+ { typeof(int), mockInnerBinder.Object }
+ }
+ };
+
+ // Act
+ object updatedModel = binder.UpdateCollection(null, bindingContext, typeof(int));
+
+ // Assert
+ Assert.Equal(3, model.Count);
+ Assert.Equal(false, bindingContext.ModelState.IsValidField("foo[1]"));
+ Assert.Equal("A value is required.", bindingContext.ModelState["foo[1]"].Errors[0].ErrorMessage);
+ Assert.Equal(0, model[0]);
+ Assert.Equal(0, model[1]);
+ Assert.Equal(2, model[2]);
+ }
+
+ [Fact]
+ public void UpdateCollectionReturnsModifiedCollectionOnSuccess_ExplicitIndex()
+ {
+ // Arrange
+ ControllerContext controllerContext = new Mock<ControllerContext>().Object;
+
+ List<int> model = new List<int>() { 4, 5, 6, 7, 8 };
+ ModelBindingContext bindingContext = new ModelBindingContext()
+ {
+ ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(() => model, model.GetType()),
+ ModelName = "foo",
+ PropertyFilter = _ => false,
+ ValueProvider = new SimpleValueProvider()
+ {
+ { "foo.index", new string[] { "alpha", "bravo", "charlie" } }, // 'bravo' will be skipped
+ { "foo[alpha]", "10" },
+ { "foo[charlie]", "30" }
+ }
+ };
+
+ DefaultModelBinder binder = new DefaultModelBinder();
+
+ // Act
+ object updatedModel = binder.UpdateCollection(controllerContext, bindingContext, typeof(int));
+
+ // Assert
+ Assert.Same(model, updatedModel);
+ Assert.Equal(2, model.Count);
+ Assert.Equal(10, model[0]);
+ Assert.Equal(30, model[1]);
+ }
+
+ [Fact]
+ public void UpdateCollectionReturnsModifiedCollectionOnSuccess_ZeroBased()
+ {
+ // Arrange
+ ControllerContext controllerContext = new Mock<ControllerContext>().Object;
+
+ List<int> model = new List<int>() { 4, 5, 6, 7, 8 };
+ ModelBindingContext bindingContext = new ModelBindingContext()
+ {
+ ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(() => model, model.GetType()),
+ ModelName = "foo",
+ PropertyFilter = _ => false,
+ ValueProvider = new SimpleValueProvider()
+ {
+ { "foo[0]", null },
+ { "foo[1]", null },
+ { "foo[2]", null }
+ }
+ };
+
+ Mock<IModelBinder> mockInnerBinder = new Mock<IModelBinder>();
+ mockInnerBinder
+ .Setup(b => b.BindModel(It.IsAny<ControllerContext>(), It.IsAny<ModelBindingContext>()))
+ .Returns(
+ delegate(ControllerContext cc, ModelBindingContext bc)
+ {
+ Assert.Equal(controllerContext, cc);
+ Assert.Equal(typeof(int), bc.ModelType);
+ Assert.Equal(bindingContext.ModelState, bc.ModelState);
+ Assert.Equal(bindingContext.PropertyFilter, bc.PropertyFilter);
+ Assert.Equal(bindingContext.ValueProvider, bc.ValueProvider);
+ return Int32.Parse(bc.ModelName.Substring(4, 1), CultureInfo.InvariantCulture);
+ });
+
+ DefaultModelBinder binder = new DefaultModelBinder()
+ {
+ Binders = new ModelBinderDictionary()
+ {
+ { typeof(int), mockInnerBinder.Object }
+ }
+ };
+
+ // Act
+ object updatedModel = binder.UpdateCollection(controllerContext, bindingContext, typeof(int));
+
+ // Assert
+ Assert.Same(model, updatedModel);
+ Assert.Equal(3, model.Count);
+ Assert.Equal(0, model[0]);
+ Assert.Equal(1, model[1]);
+ Assert.Equal(2, model[2]);
+ }
+
+ [Fact]
+ public void UpdateCollectionReturnsNullIfZeroIndexNotFound()
+ {
+ // Arrange
+ ModelBindingContext bindingContext = new ModelBindingContext()
+ {
+ ValueProvider = new SimpleValueProvider()
+ };
+ DefaultModelBinder binder = new DefaultModelBinder();
+
+ // Act
+ object updatedModel = binder.UpdateCollection(null, bindingContext, typeof(object));
+
+ // Assert
+ Assert.Null(updatedModel);
+ }
+
+ [Fact]
+ public void UpdateDictionaryCreatesDefaultEntriesForInvalidValues()
+ {
+ // Arrange
+ Dictionary<string, int> model = new Dictionary<string, int>
+ {
+ { "one", 1 },
+ { "two", 2 }
+ };
+ ModelBindingContext bindingContext = new ModelBindingContext()
+ {
+ ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(() => model, model.GetType()),
+ ModelName = "foo",
+ ValueProvider = new SimpleValueProvider()
+ {
+ { "foo[0].key", null }, { "foo[0].value", null },
+ { "foo[1].key", null }, { "foo[1].value", null },
+ { "foo[2].key", null }, { "foo[2].value", null }
+ }
+ };
+
+ Mock<IModelBinder> mockStringBinder = new Mock<IModelBinder>();
+ mockStringBinder
+ .Setup(b => b.BindModel(It.IsAny<ControllerContext>(), It.IsAny<ModelBindingContext>()))
+ .Returns(
+ delegate(ControllerContext cc, ModelBindingContext bc) { return (Int32.Parse(bc.ModelName.Substring(4, 1), CultureInfo.InvariantCulture) + 10) + "Value"; });
+
+ Mock<IModelBinder> mockIntBinder = new Mock<IModelBinder>();
+ mockIntBinder
+ .Setup(b => b.BindModel(It.IsAny<ControllerContext>(), It.IsAny<ModelBindingContext>()))
+ .Returns(
+ delegate(ControllerContext cc, ModelBindingContext bc)
+ {
+ int fooIdx = Int32.Parse(bc.ModelName.Substring(4, 1), CultureInfo.InvariantCulture);
+ return (fooIdx == 1) ? (object)null : fooIdx;
+ });
+
+ DefaultModelBinder binder = new DefaultModelBinder()
+ {
+ Binders = new ModelBinderDictionary()
+ {
+ { typeof(string), mockStringBinder.Object },
+ { typeof(int), mockIntBinder.Object }
+ }
+ };
+
+ // Act
+ object updatedModel = binder.UpdateDictionary(null, bindingContext, typeof(string), typeof(int));
+
+ // Assert
+ Assert.Equal(3, model.Count);
+ Assert.False(bindingContext.ModelState.IsValidField("foo[1].value"));
+ Assert.Equal("A value is required.", bindingContext.ModelState["foo[1].value"].Errors[0].ErrorMessage);
+ Assert.Equal(0, model["10Value"]);
+ Assert.Equal(0, model["11Value"]);
+ Assert.Equal(2, model["12Value"]);
+ }
+
+ [Fact]
+ public void UpdateDictionaryReturnsModifiedDictionaryOnSuccess_ExplicitIndex()
+ {
+ // Arrange
+ ControllerContext controllerContext = new Mock<ControllerContext>().Object;
+
+ Dictionary<int, string> model = new Dictionary<int, string>
+ {
+ { 1, "one" },
+ { 2, "two" }
+ };
+ ModelBindingContext bindingContext = new ModelBindingContext()
+ {
+ ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(() => model, model.GetType()),
+ ModelName = "foo",
+ PropertyFilter = _ => false,
+ ValueProvider = new SimpleValueProvider()
+ {
+ { "foo.index", new string[] { "alpha", "bravo", "charlie" } }, // 'bravo' will be skipped
+ { "foo[alpha].key", "10" }, { "foo[alpha].value", "ten" },
+ { "foo[charlie].key", "30" }, { "foo[charlie].value", "thirty" }
+ }
+ };
+
+ DefaultModelBinder binder = new DefaultModelBinder();
+
+ // Act
+ object updatedModel = binder.UpdateDictionary(controllerContext, bindingContext, typeof(int), typeof(string));
+
+ // Assert
+ Assert.Same(model, updatedModel);
+ Assert.Equal(2, model.Count);
+ Assert.Equal("ten", model[10]);
+ Assert.Equal("thirty", model[30]);
+ }
+
+ [Fact]
+ public void UpdateDictionaryReturnsModifiedDictionaryOnSuccess_ZeroBased()
+ {
+ // Arrange
+ ControllerContext controllerContext = new Mock<ControllerContext>().Object;
+
+ Dictionary<int, string> model = new Dictionary<int, string>
+ {
+ { 1, "one" },
+ { 2, "two" }
+ };
+ ModelBindingContext bindingContext = new ModelBindingContext()
+ {
+ ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(() => model, model.GetType()),
+ ModelName = "foo",
+ PropertyFilter = _ => false,
+ ValueProvider = new SimpleValueProvider()
+ {
+ { "foo[0].key", null }, { "foo[0].value", null },
+ { "foo[1].key", null }, { "foo[1].value", null },
+ { "foo[2].key", null }, { "foo[2].value", null }
+ }
+ };
+
+ Mock<IModelBinder> mockIntBinder = new Mock<IModelBinder>();
+ mockIntBinder
+ .Setup(b => b.BindModel(It.IsAny<ControllerContext>(), It.IsAny<ModelBindingContext>()))
+ .Returns(
+ delegate(ControllerContext cc, ModelBindingContext bc)
+ {
+ Assert.Equal(controllerContext, cc);
+ Assert.Equal(typeof(int), bc.ModelType);
+ Assert.Equal(bindingContext.ModelState, bc.ModelState);
+ Assert.Equal(new ModelBindingContext().PropertyFilter, bc.PropertyFilter);
+ Assert.Equal(bindingContext.ValueProvider, bc.ValueProvider);
+ return Int32.Parse(bc.ModelName.Substring(4, 1), CultureInfo.InvariantCulture) + 10;
+ });
+
+ Mock<IModelBinder> mockStringBinder = new Mock<IModelBinder>();
+ mockStringBinder
+ .Setup(b => b.BindModel(It.IsAny<ControllerContext>(), It.IsAny<ModelBindingContext>()))
+ .Returns(
+ delegate(ControllerContext cc, ModelBindingContext bc)
+ {
+ Assert.Equal(controllerContext, cc);
+ Assert.Equal(typeof(string), bc.ModelType);
+ Assert.Equal(bindingContext.ModelState, bc.ModelState);
+ Assert.Equal(bindingContext.PropertyFilter, bc.PropertyFilter);
+ Assert.Equal(bindingContext.ValueProvider, bc.ValueProvider);
+ return (Int32.Parse(bc.ModelName.Substring(4, 1), CultureInfo.InvariantCulture) + 10) + "Value";
+ });
+
+ DefaultModelBinder binder = new DefaultModelBinder()
+ {
+ Binders = new ModelBinderDictionary()
+ {
+ { typeof(int), mockIntBinder.Object },
+ { typeof(string), mockStringBinder.Object }
+ }
+ };
+
+ // Act
+ object updatedModel = binder.UpdateDictionary(controllerContext, bindingContext, typeof(int), typeof(string));
+
+ // Assert
+ Assert.Same(model, updatedModel);
+ Assert.Equal(3, model.Count);
+ Assert.Equal("10Value", model[10]);
+ Assert.Equal("11Value", model[11]);
+ Assert.Equal("12Value", model[12]);
+ }
+
+ [Fact]
+ public void UpdateDictionaryReturnsNullIfNoValidElementsFound()
+ {
+ // Arrange
+ ModelBindingContext bindingContext = new ModelBindingContext()
+ {
+ ValueProvider = new SimpleValueProvider()
+ };
+ DefaultModelBinder binder = new DefaultModelBinder();
+
+ // Act
+ object updatedModel = binder.UpdateDictionary(null, bindingContext, typeof(object), typeof(object));
+
+ // Assert
+ Assert.Null(updatedModel);
+ }
+
+ [Fact]
+ public void UpdateDictionarySkipsInvalidKeys()
+ {
+ // Arrange
+ Dictionary<int, string> model = new Dictionary<int, string>
+ {
+ { 1, "one" },
+ { 2, "two" }
+ };
+ ModelBindingContext bindingContext = new ModelBindingContext()
+ {
+ ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(() => model, model.GetType()),
+ ModelName = "foo",
+ ValueProvider = new SimpleValueProvider()
+ {
+ { "foo[0].key", null }, { "foo[0].value", null },
+ { "foo[1].key", null }, { "foo[1].value", null },
+ { "foo[2].key", null }, { "foo[2].value", null }
+ }
+ };
+
+ Mock<IModelBinder> mockIntBinder = new Mock<IModelBinder>();
+ mockIntBinder
+ .Setup(b => b.BindModel(It.IsAny<ControllerContext>(), It.IsAny<ModelBindingContext>()))
+ .Returns(
+ delegate(ControllerContext cc, ModelBindingContext bc)
+ {
+ int fooIdx = Int32.Parse(bc.ModelName.Substring(4, 1), CultureInfo.InvariantCulture);
+ return (fooIdx == 1) ? (object)null : fooIdx;
+ });
+
+ Mock<IModelBinder> mockStringBinder = new Mock<IModelBinder>();
+ mockStringBinder
+ .Setup(b => b.BindModel(It.IsAny<ControllerContext>(), It.IsAny<ModelBindingContext>()))
+ .Returns(
+ delegate(ControllerContext cc, ModelBindingContext bc) { return (Int32.Parse(bc.ModelName.Substring(4, 1), CultureInfo.InvariantCulture) + 10) + "Value"; });
+
+ DefaultModelBinder binder = new DefaultModelBinder()
+ {
+ Binders = new ModelBinderDictionary()
+ {
+ { typeof(int), mockIntBinder.Object },
+ { typeof(string), mockStringBinder.Object }
+ }
+ };
+
+ // Act
+ object updatedModel = binder.UpdateDictionary(null, bindingContext, typeof(int), typeof(string));
+
+ // Assert
+ Assert.Equal(2, model.Count);
+ Assert.False(bindingContext.ModelState.IsValidField("foo[1].key"));
+ Assert.Equal("A value is required.", bindingContext.ModelState["foo[1].key"].Errors[0].ErrorMessage);
+ Assert.Equal("10Value", model[0]);
+ Assert.Equal("12Value", model[2]);
+ }
+
+ [ModelBinder(typeof(DefaultModelBinder))]
+ private class MyModel
+ {
+ public int ReadOnlyProperty
+ {
+ get { return 4; }
+ }
+
+ public int ReadWriteProperty { get; set; }
+ public int ReadWriteProperty2 { get; set; }
+ }
+
+ private class MyClassWithoutConverter
+ {
+ }
+
+ [Bind(Exclude = "Alpha,Echo")]
+ private class MyOtherModel
+ {
+ public string Alpha { get; set; }
+ public string Bravo { get; set; }
+ public string Charlie { get; set; }
+ public string Delta { get; set; }
+ public string Echo { get; set; }
+ public string Foxtrot { get; set; }
+ }
+
+ public class Customer
+ {
+ private Address _address = new Address();
+
+ public Address Address
+ {
+ get { return _address; }
+ }
+ }
+
+ public class Address
+ {
+ public string Street { get; set; }
+ public string Zip { get; set; }
+ }
+
+ public class IntegerContainer
+ {
+ public int Integer { get; set; }
+ public int? NullableInteger { get; set; }
+ }
+
+ [TypeConverter(typeof(CultureAwareConverter))]
+ public class StringContainer
+ {
+ public StringContainer(string value)
+ {
+ Value = value;
+ }
+
+ public string Value { get; private set; }
+ }
+
+ private class CultureAwareConverter : TypeConverter
+ {
+ public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
+ {
+ return (sourceType == typeof(string));
+ }
+
+ public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
+ {
+ return (destinationType == typeof(string));
+ }
+
+ public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
+ {
+ string stringValue = value as string;
+ if (stringValue == null || stringValue.Length < 3)
+ {
+ throw new Exception("Value must have at least 3 characters.");
+ }
+ return new StringContainer(AppendCultureName(stringValue, culture));
+ }
+
+ public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType)
+ {
+ StringContainer container = value as StringContainer;
+ if (container.Value == null || container.Value.Length < 3)
+ {
+ throw new Exception("Value must have at least 3 characters.");
+ }
+
+ return AppendCultureName(container.Value, culture);
+ }
+
+ private static string AppendCultureName(string value, CultureInfo culture)
+ {
+ string cultureName = (!String.IsNullOrEmpty(culture.Name)) ? culture.Name : culture.ThreeLetterWindowsLanguageName;
+ return value + " (" + cultureName + ")";
+ }
+ }
+
+ [ModelBinder(typeof(MyStringModelBinder))]
+ private class MyStringModel
+ {
+ public string Value { get; set; }
+ }
+
+ private class MyStringModelBinder : IModelBinder
+ {
+ public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
+ {
+ MyStringModel castModel = bindingContext.Model as MyStringModel;
+ if (castModel != null)
+ {
+ castModel.Value += "_Update";
+ }
+ else
+ {
+ castModel = new MyStringModel() { Value = bindingContext.ModelName + "_Create" };
+ }
+ return castModel;
+ }
+ }
+
+ private class CustomUnvalidatedValueProvider : IUnvalidatedValueProvider
+ {
+ public ValueProviderResult GetValue(string key, bool skipValidation)
+ {
+ string newValue = key + ((skipValidation) ? "Unvalidated" : "Validated");
+ return new ValueProviderResult(newValue, newValue, null);
+ }
+
+ public bool ContainsPrefix(string prefix)
+ {
+ return true;
+ }
+
+ public ValueProviderResult GetValue(string key)
+ {
+ return GetValue(key, skipValidation: false);
+ }
+ }
+
+ private class SimpleController : Controller
+ {
+ }
+
+ public class DefaultModelBinderHelper : DefaultModelBinder
+ {
+ public virtual void PublicBindProperty(ControllerContext controllerContext, ModelBindingContext bindingContext, PropertyDescriptor property)
+ {
+ base.BindProperty(controllerContext, bindingContext, property);
+ }
+
+ protected override void BindProperty(ControllerContext controllerContext, ModelBindingContext bindingContext, PropertyDescriptor property)
+ {
+ PublicBindProperty(controllerContext, bindingContext, property);
+ }
+
+ public virtual object PublicCreateModel(ControllerContext controllerContext, ModelBindingContext bindingContext, Type modelType)
+ {
+ return base.CreateModel(controllerContext, bindingContext, modelType);
+ }
+
+ protected override object CreateModel(ControllerContext controllerContext, ModelBindingContext bindingContext, Type modelType)
+ {
+ return PublicCreateModel(controllerContext, bindingContext, modelType);
+ }
+
+ public virtual IEnumerable<PropertyDescriptor> PublicGetFilteredModelProperties(ControllerContext controllerContext, ModelBindingContext bindingContext)
+ {
+ return base.GetFilteredModelProperties(controllerContext, bindingContext);
+ }
+
+ public virtual PropertyDescriptorCollection PublicGetModelProperties(ControllerContext controllerContext, ModelBindingContext bindingContext)
+ {
+ return base.GetModelProperties(controllerContext, bindingContext);
+ }
+
+ protected override PropertyDescriptorCollection GetModelProperties(ControllerContext controllerContext, ModelBindingContext bindingContext)
+ {
+ return PublicGetModelProperties(controllerContext, bindingContext);
+ }
+
+ public string PublicCreateSubIndexName(string prefix, int indexName)
+ {
+ return CreateSubIndexName(prefix, indexName);
+ }
+
+ public string PublicCreateSubPropertyName(string prefix, string propertyName)
+ {
+ return CreateSubPropertyName(prefix, propertyName);
+ }
+
+ public static bool PublicIsModelValid(ModelBindingContext bindingContext)
+ {
+ return IsModelValid(bindingContext);
+ }
+
+ public virtual bool PublicOnModelUpdating(ControllerContext controllerContext, ModelBindingContext bindingContext)
+ {
+ return base.OnModelUpdating(controllerContext, bindingContext);
+ }
+
+ protected override bool OnModelUpdating(ControllerContext controllerContext, ModelBindingContext bindingContext)
+ {
+ return PublicOnModelUpdating(controllerContext, bindingContext);
+ }
+
+ public virtual void PublicOnModelUpdated(ControllerContext controllerContext, ModelBindingContext bindingContext)
+ {
+ base.OnModelUpdated(controllerContext, bindingContext);
+ }
+
+ protected override void OnModelUpdated(ControllerContext controllerContext, ModelBindingContext bindingContext)
+ {
+ PublicOnModelUpdated(controllerContext, bindingContext);
+ }
+
+ public virtual bool PublicOnPropertyValidating(ControllerContext controllerContext, ModelBindingContext bindingContext, PropertyDescriptor property, object value)
+ {
+ return base.OnPropertyValidating(controllerContext, bindingContext, property, value);
+ }
+
+ protected override bool OnPropertyValidating(ControllerContext controllerContext, ModelBindingContext bindingContext, PropertyDescriptor property, object value)
+ {
+ return PublicOnPropertyValidating(controllerContext, bindingContext, property, value);
+ }
+
+ public virtual void PublicOnPropertyValidated(ControllerContext controllerContext, ModelBindingContext bindingContext, PropertyDescriptor property, object value)
+ {
+ base.OnPropertyValidated(controllerContext, bindingContext, property, value);
+ }
+
+ protected override void OnPropertyValidated(ControllerContext controllerContext, ModelBindingContext bindingContext, PropertyDescriptor property, object value)
+ {
+ PublicOnPropertyValidated(controllerContext, bindingContext, property, value);
+ }
+
+ public virtual void PublicSetProperty(ControllerContext controllerContext, ModelBindingContext bindingContext, PropertyDescriptor property, object value)
+ {
+ base.SetProperty(controllerContext, bindingContext, property, value);
+ }
+
+ protected override void SetProperty(ControllerContext controllerContext, ModelBindingContext bindingContext, PropertyDescriptor property, object value)
+ {
+ PublicSetProperty(controllerContext, bindingContext, property, value);
+ }
+ }
+
+ private class MyModel2
+ {
+ private int _intReadWriteNonNegative;
+
+ public int IntReadOnly
+ {
+ get { return 4; }
+ }
+
+ public int IntReadWrite { get; set; }
+
+ public int IntReadWriteNonNegative
+ {
+ get { return _intReadWriteNonNegative; }
+ set
+ {
+ if (value < 0)
+ {
+ throw new ArgumentOutOfRangeException("value", "Value must be non-negative.");
+ }
+ _intReadWriteNonNegative = value;
+ }
+ }
+
+ public int? NullableIntReadWrite { get; set; }
+ }
+
+ [Bind(Exclude = "Foo")]
+ private class ModelWithBindAttribute : ModelWithoutBindAttribute
+ {
+ }
+
+ private class ModelWithoutBindAttribute
+ {
+ public string Foo { get; set; }
+ public string Bar { get; set; }
+ public string Baz { get; set; }
+ }
+
+ private class PropertyTestingModel
+ {
+ public string StringReadWrite { get; set; }
+ public string StringReadOnly { get; private set; }
+ public int IntReadWrite { get; set; }
+ public int IntReadOnly { get; private set; }
+ public object[] ArrayReadWrite { get; set; }
+ public object[] ArrayReadOnly { get; private set; }
+ public Address AddressReadWrite { get; set; }
+ public Address AddressReadOnly { get; private set; }
+ public string Whitelisted { get; set; }
+ public string Blacklisted { get; set; }
+ }
+
+ // --------------------------------------------------------------------------------
+ // DataAnnotations tests
+
+ public const string BASE_MODEL_NAME = "BaseModelName";
+
+ // GetModelProperties tests
+
+ [MetadataType(typeof(Metadata))]
+ class GetModelPropertiesModel
+ {
+ [Required]
+ public int LocalAttributes { get; set; }
+
+ public int MetadataAttributes { get; set; }
+
+ [Required]
+ public int MixedAttributes { get; set; }
+
+ class Metadata
+ {
+ [Range(10, 100)]
+ public int MetadataAttributes { get; set; }
+
+ [Range(10, 100)]
+ public int MixedAttributes { get; set; }
+ }
+ }
+
+ [Fact]
+ public void GetModelPropertiesWithLocalAttributes()
+ {
+ // Arrange
+ TestableDefaultModelBinder<GetModelPropertiesModel> modelBinder = new TestableDefaultModelBinder<GetModelPropertiesModel>();
+
+ // Act
+ PropertyDescriptor property = modelBinder.GetModelProperties()
+ .Cast<PropertyDescriptor>()
+ .Where(pd => pd.Name == "LocalAttributes")
+ .Single();
+
+ // Assert
+ Assert.True(property.Attributes.Cast<Attribute>().Any(a => a is RequiredAttribute));
+ }
+
+ [Fact]
+ public void GetModelPropertiesWithMetadataAttributes()
+ {
+ // Arrange
+ TestableDefaultModelBinder<GetModelPropertiesModel> modelBinder = new TestableDefaultModelBinder<GetModelPropertiesModel>();
+
+ // Act
+ PropertyDescriptor property = modelBinder.GetModelProperties()
+ .Cast<PropertyDescriptor>()
+ .Where(pd => pd.Name == "MetadataAttributes")
+ .Single();
+
+ // Assert
+ Assert.True(property.Attributes.Cast<Attribute>().Any(a => a is RangeAttribute));
+ }
+
+ [Fact]
+ public void GetModelPropertiesWithMixedAttributes()
+ {
+ // Arrange
+ TestableDefaultModelBinder<GetModelPropertiesModel> modelBinder = new TestableDefaultModelBinder<GetModelPropertiesModel>();
+
+ // Act
+ PropertyDescriptor property = modelBinder.GetModelProperties()
+ .Cast<PropertyDescriptor>()
+ .Where(pd => pd.Name == "MixedAttributes")
+ .Single();
+
+ // Assert
+ Assert.True(property.Attributes.Cast<Attribute>().Any(a => a is RequiredAttribute));
+ Assert.True(property.Attributes.Cast<Attribute>().Any(a => a is RangeAttribute));
+ }
+
+ // GetPropertyValue tests
+
+ class GetPropertyValueModel
+ {
+ public string NoAttribute { get; set; }
+
+ [DisplayFormat(ConvertEmptyStringToNull = false)]
+ public string AttributeWithoutConversion { get; set; }
+
+ [DisplayFormat(ConvertEmptyStringToNull = true)]
+ public string AttributeWithConversion { get; set; }
+ }
+
+ [Fact]
+ public void GetPropertyValueWithNoAttributeConvertsEmptyStringToNull()
+ {
+ // Arrange
+ TestableDefaultModelBinder<GetPropertyValueModel> binder = new TestableDefaultModelBinder<GetPropertyValueModel>();
+ binder.Context.ModelMetadata = binder.Context.PropertyMetadata["NoAttribute"];
+
+ // Act
+ object result = binder.GetPropertyValue("NoAttribute", String.Empty);
+
+ // Assert
+ Assert.Null(result);
+ }
+
+ [Fact]
+ public void GetPropertyValueWithFalseAttributeDoesNotConvertEmptyStringToNull()
+ {
+ // Arrange
+ TestableDefaultModelBinder<GetPropertyValueModel> binder = new TestableDefaultModelBinder<GetPropertyValueModel>();
+ binder.Context.ModelMetadata = binder.Context.PropertyMetadata["AttributeWithoutConversion"];
+
+ // Act
+ object result = binder.GetPropertyValue("AttributeWithoutConversion", String.Empty);
+
+ // Assert
+ Assert.Equal(String.Empty, result);
+ }
+
+ [Fact]
+ public void GetPropertyValueWithTrueAttributeConvertsEmptyStringToNull()
+ {
+ // Arrange
+ TestableDefaultModelBinder<GetPropertyValueModel> binder = new TestableDefaultModelBinder<GetPropertyValueModel>();
+ binder.Context.ModelMetadata = binder.Context.PropertyMetadata["AttributeWithConversion"];
+
+ // Act
+ object result = binder.GetPropertyValue("AttributeWithConversion", String.Empty);
+
+ // Assert
+ Assert.Null(result);
+ }
+
+ // OnModelUpdated tests
+
+ [Fact]
+ public void OnModelUpdatedPassesNullContainerToValidate()
+ {
+ Mock<ModelValidatorProvider> provider = null;
+
+ try
+ {
+ // Arrange
+ ModelMetadata metadata = ModelMetadataProviders.Current.GetMetadataForType(null, typeof(object));
+ ControllerContext context = new ControllerContext();
+ Mock<ModelValidator> validator = new Mock<ModelValidator>(metadata, context);
+ provider = new Mock<ModelValidatorProvider>();
+ provider.Setup(p => p.GetValidators(It.IsAny<ModelMetadata>(), It.IsAny<ControllerContext>()))
+ .Returns(new ModelValidator[] { validator.Object });
+ ModelValidatorProviders.Providers.Add(provider.Object);
+ object model = new object();
+ TestableDefaultModelBinder<object> modelBinder = new TestableDefaultModelBinder<object>(model);
+
+ // Act
+ modelBinder.OnModelUpdated();
+
+ // Assert
+ validator.Verify(v => v.Validate(null));
+ }
+ finally
+ {
+ if (provider != null)
+ {
+ ModelValidatorProviders.Providers.Remove(provider.Object);
+ }
+ }
+ }
+
+ public class MinMaxValidationAttribute : ValidationAttribute
+ {
+ public MinMaxValidationAttribute()
+ : base("Minimum must be less than or equal to Maximum")
+ {
+ }
+
+ public override bool IsValid(object value)
+ {
+ OnModelUpdatedModelMultipleParameters model = (OnModelUpdatedModelMultipleParameters)value;
+ return model.Minimum <= model.Maximum;
+ }
+ }
+
+ [MinMaxValidation]
+ public class OnModelUpdatedModelMultipleParameters
+ {
+ public int Minimum { get; set; }
+ public int Maximum { get; set; }
+ }
+
+ [Fact]
+ public void OnModelUpdatedWithValidationAttributeMultipleParameters()
+ {
+ // Arrange
+ OnModelUpdatedModelMultipleParameters model = new OnModelUpdatedModelMultipleParameters { Minimum = 250, Maximum = 100 };
+ TestableDefaultModelBinder<OnModelUpdatedModelMultipleParameters> modelBinder = new TestableDefaultModelBinder<OnModelUpdatedModelMultipleParameters>(model);
+
+ // Act
+ modelBinder.OnModelUpdated();
+
+ // Assert
+ Assert.Single(modelBinder.ModelState);
+ ModelState stateModel = modelBinder.ModelState[BASE_MODEL_NAME];
+ Assert.NotNull(stateModel);
+ Assert.Equal("Minimum must be less than or equal to Maximum", stateModel.Errors.Single().ErrorMessage);
+ }
+
+ [Fact]
+ public void OnModelUpdatedWithInvalidPropertyValidationWillNotRunEntityLevelValidation()
+ {
+ // Arrange
+ OnModelUpdatedModelMultipleParameters model = new OnModelUpdatedModelMultipleParameters { Minimum = 250, Maximum = 100 };
+ TestableDefaultModelBinder<OnModelUpdatedModelMultipleParameters> modelBinder = new TestableDefaultModelBinder<OnModelUpdatedModelMultipleParameters>(model);
+ modelBinder.ModelState.AddModelError(BASE_MODEL_NAME + ".Minimum", "The minimum value was invalid.");
+
+ // Act
+ modelBinder.OnModelUpdated();
+
+ // Assert
+ Assert.Null(modelBinder.ModelState[BASE_MODEL_NAME]);
+ }
+
+ public class AlwaysInvalidAttribute : ValidationAttribute
+ {
+ public AlwaysInvalidAttribute()
+ {
+ }
+
+ public AlwaysInvalidAttribute(string message)
+ : base(message)
+ {
+ }
+
+ public override bool IsValid(object value)
+ {
+ return false;
+ }
+ }
+
+ [AlwaysInvalid("The object just isn't right")]
+ public class OnModelUpdatedModelNoParameters
+ {
+ }
+
+ [Fact]
+ public void OnModelUpdatedWithValidationAttributeNoParameters()
+ {
+ // Arrange
+ TestableDefaultModelBinder<OnModelUpdatedModelNoParameters> modelBinder = new TestableDefaultModelBinder<OnModelUpdatedModelNoParameters>();
+
+ // Act
+ modelBinder.OnModelUpdated();
+
+ // Assert
+ Assert.Single(modelBinder.ModelState);
+ ModelState stateModel = modelBinder.ModelState[BASE_MODEL_NAME];
+ Assert.NotNull(stateModel);
+ Assert.Equal("The object just isn't right", stateModel.Errors.Single().ErrorMessage);
+ }
+
+ [AlwaysInvalid]
+ public class OnModelUpdatedModelNoValidationResult
+ {
+ }
+
+ [Fact]
+ public void OnModelUpdatedWithValidationAttributeNoValidationMessage()
+ {
+ // Arrange
+ TestableDefaultModelBinder<OnModelUpdatedModelNoValidationResult> modelBinder = new TestableDefaultModelBinder<OnModelUpdatedModelNoValidationResult>();
+
+ // Act
+ modelBinder.OnModelUpdated();
+
+ // Assert
+ Assert.Single(modelBinder.ModelState);
+ ModelState stateModel = modelBinder.ModelState[BASE_MODEL_NAME];
+ Assert.NotNull(stateModel);
+ Assert.Equal("The field OnModelUpdatedModelNoValidationResult is invalid.", stateModel.Errors.Single().ErrorMessage);
+ }
+
+ [Fact]
+ public void OnModelUpdatedDoesNotPlaceErrorMessagesInModelStateWhenSubPropertiesHaveErrors()
+ {
+ // Arrange
+ TestableDefaultModelBinder<OnModelUpdatedModelNoValidationResult> modelBinder = new TestableDefaultModelBinder<OnModelUpdatedModelNoValidationResult>();
+ modelBinder.ModelState.AddModelError("Foo.Bar", "Foo.Bar is invalid");
+ modelBinder.Context.ModelName = "Foo";
+
+ // Act
+ modelBinder.OnModelUpdated();
+
+ // Assert
+ Assert.Null(modelBinder.ModelState["Foo"]);
+ }
+
+ // OnPropertyValidating tests
+
+ class OnPropertyValidatingModel
+ {
+ public string NotValidated { get; set; }
+
+ [Range(10, 65)]
+ public int RangedInteger { get; set; }
+
+ [Required(ErrorMessage = "Custom Required Message")]
+ public int RequiredInteger { get; set; }
+ }
+
+ [Fact]
+ public void OnPropertyValidatingWithoutValidationAttribute()
+ {
+ // Arrange
+ TestableDefaultModelBinder<OnPropertyValidatingModel> modelBinder = new TestableDefaultModelBinder<OnPropertyValidatingModel>();
+
+ // Act
+ modelBinder.OnPropertyValidating("NotValidated", 42);
+
+ // Assert
+ Assert.Empty(modelBinder.ModelState);
+ }
+
+ [Fact]
+ public void OnPropertyValidatingWithValidationAttributePassing()
+ {
+ // Arrange
+ TestableDefaultModelBinder<OnPropertyValidatingModel> modelBinder = new TestableDefaultModelBinder<OnPropertyValidatingModel>();
+ modelBinder.Context.PropertyMetadata["RangedInteger"].Model = 42;
+
+ // Act
+ bool result = modelBinder.OnPropertyValidating("RangedInteger", 42);
+
+ // Assert
+ Assert.True(result);
+ Assert.Empty(modelBinder.ModelState);
+ }
+
+ // SetProperty tests
+
+ [Fact]
+ public void SetPropertyWithRequiredOnValueTypeOnlyResultsInSingleMessage()
+ { // DDB #225150
+ // Arrange
+ TestableDefaultModelBinder<OnPropertyValidatingModel> modelBinder = new TestableDefaultModelBinder<OnPropertyValidatingModel>();
+ modelBinder.Context.ModelMetadata.Model = new OnPropertyValidatingModel();
+
+ // Act
+ modelBinder.SetProperty("RequiredInteger", null);
+
+ // Assert
+ ModelState modelState = modelBinder.ModelState[BASE_MODEL_NAME + ".RequiredInteger"];
+ ModelError modelStateError = modelState.Errors.Single();
+ Assert.Equal("Custom Required Message", modelStateError.ErrorMessage);
+ }
+
+ [Fact]
+ public void SetPropertyAddsDefaultMessageForNonBindableNonNullableValueTypes()
+ {
+ DataAnnotationsModelValidatorProvider.AddImplicitRequiredAttributeForValueTypes = false;
+
+ try
+ {
+ // Arrange
+ TestableDefaultModelBinder<List<String>> modelBinder = new TestableDefaultModelBinder<List<String>>();
+ modelBinder.Context.ModelMetadata.Model = null;
+
+ // Act
+ modelBinder.SetProperty("Count", null);
+
+ // Assert
+ ModelState modelState = modelBinder.ModelState[BASE_MODEL_NAME + ".Count"];
+ ModelError modelStateError = modelState.Errors.Single();
+ Assert.Equal("A value is required.", modelStateError.ErrorMessage);
+ }
+ finally
+ {
+ DataAnnotationsModelValidatorProvider.AddImplicitRequiredAttributeForValueTypes = true;
+ }
+ }
+
+ [Fact]
+ public void SetPropertyCreatesValueRequiredErrorIfNecessary()
+ {
+ // Arrange
+ ModelBindingContext bindingContext = new ModelBindingContext()
+ {
+ ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(() => new MyModel(), typeof(MyModel)),
+ ModelName = "theModel",
+ };
+
+ PropertyDescriptor property = TypeDescriptor.GetProperties(typeof(MyModel))["ReadWriteProperty"];
+ DefaultModelBinderHelper helper = new DefaultModelBinderHelper();
+
+ // Act
+ helper.PublicSetProperty(new ControllerContext(), bindingContext, property, null);
+
+ // Assert
+ Assert.Equal("The ReadWriteProperty field is required.", bindingContext.ModelState["theModel.ReadWriteProperty"].Errors[0].ErrorMessage);
+ }
+
+ [Fact]
+ public void SetPropertyWithThrowingSetter()
+ {
+ // Arrange
+ TestableDefaultModelBinder<SetPropertyModel> binder = new TestableDefaultModelBinder<SetPropertyModel>();
+
+ // Act
+ binder.SetProperty("NonNullableString", null);
+
+ // Assert
+ ModelState modelState = binder.Context.ModelState[BASE_MODEL_NAME + ".NonNullableString"];
+ Assert.Single(modelState.Errors);
+ Assert.IsType<ArgumentNullException>(modelState.Errors[0].Exception);
+ }
+
+ [Fact]
+ public void SetPropertyWithNullValueAndThrowingSetterWithRequiredAttribute()
+ { // DDB #227809
+ // Arrange
+ TestableDefaultModelBinder<SetPropertyModel> binder = new TestableDefaultModelBinder<SetPropertyModel>();
+
+ // Act
+ binder.SetProperty("NonNullableStringWithAttribute", null);
+
+ // Assert
+ ModelState modelState = binder.Context.ModelState[BASE_MODEL_NAME + ".NonNullableStringWithAttribute"];
+ var error = Assert.Single(modelState.Errors);
+ Assert.Equal("My custom required message", error.ErrorMessage);
+ }
+
+ [Fact]
+ public void SetPropertyDoesNothingIfPropertyIsReadOnly()
+ {
+ // Arrange
+ MyModel model = new MyModel();
+ ModelBindingContext bindingContext = new ModelBindingContext()
+ {
+ ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(() => model, model.GetType()),
+ ModelName = "theModel"
+ };
+
+ PropertyDescriptor property = TypeDescriptor.GetProperties(model)["ReadOnlyProperty"];
+ DefaultModelBinderHelper helper = new DefaultModelBinderHelper();
+
+ // Act
+ helper.PublicSetProperty(new ControllerContext(), bindingContext, property, 42);
+
+ // Assert
+ Assert.Empty(bindingContext.ModelState);
+ }
+
+ [Fact]
+ public void SetPropertySuccess()
+ {
+ // Arrange
+ TestableDefaultModelBinder<SetPropertyModel> binder = new TestableDefaultModelBinder<SetPropertyModel>();
+
+ // Act
+ binder.SetProperty("NullableString", "The new value");
+
+ // Assert
+ Assert.Equal("The new value", ((SetPropertyModel)binder.Context.Model).NullableString);
+ Assert.Empty(binder.Context.ModelState);
+ }
+
+ class SetPropertyModel
+ {
+ public string NullableString { get; set; }
+
+ public string NonNullableString
+ {
+ get { return null; }
+ set
+ {
+ if (value == null)
+ {
+ throw new ArgumentNullException("value");
+ }
+ }
+ }
+
+ [Required(ErrorMessage = "My custom required message")]
+ public string NonNullableStringWithAttribute
+ {
+ get { return null; }
+ set
+ {
+ if (value == null)
+ {
+ throw new ArgumentNullException("value");
+ }
+ }
+ }
+ }
+
+ // Helper methods
+
+ static PropertyDescriptor GetProperty<T>(string propertyName)
+ {
+ return TypeDescriptor.GetProperties(typeof(T))
+ .Cast<PropertyDescriptor>()
+ .Where(p => p.Name == propertyName)
+ .Single();
+ }
+
+ static ICustomTypeDescriptor GetType<T>()
+ {
+ return TypeDescriptor.GetProvider(typeof(T)).GetTypeDescriptor(typeof(T));
+ }
+
+ // Helper classes
+
+ class TestableDefaultModelBinder<TModel> : DefaultModelBinder
+ where TModel : new()
+ {
+ public TestableDefaultModelBinder()
+ : this(new TModel())
+ {
+ }
+
+ public TestableDefaultModelBinder(TModel model)
+ {
+ ModelState = new ModelStateDictionary();
+
+ Context = new ModelBindingContext();
+ Context.ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(() => model, typeof(TModel));
+ Context.ModelName = BASE_MODEL_NAME;
+ Context.ModelState = ModelState;
+ }
+
+ public ModelBindingContext Context { get; private set; }
+
+ public ModelStateDictionary ModelState { get; set; }
+
+ public void BindProperty(string propertyName)
+ {
+ base.BindProperty(new ControllerContext(), Context, GetProperty<TModel>(propertyName));
+ }
+
+ public PropertyDescriptorCollection GetModelProperties()
+ {
+ return base.GetModelProperties(new ControllerContext(), Context);
+ }
+
+ public object GetPropertyValue(string propertyName, object existingValue)
+ {
+ Mock<IModelBinder> mockModelBinder = new Mock<IModelBinder>();
+ mockModelBinder.Setup(b => b.BindModel(It.IsAny<ControllerContext>(), Context)).Returns(existingValue);
+ return base.GetPropertyValue(new ControllerContext(), Context, GetProperty<TModel>(propertyName), mockModelBinder.Object);
+ }
+
+ public bool OnPropertyValidating(string propertyName, object value)
+ {
+ return base.OnPropertyValidating(new ControllerContext(), Context, GetProperty<TModel>(propertyName), value);
+ }
+
+ public void OnPropertyValidated(string propertyName, object value)
+ {
+ base.OnPropertyValidated(new ControllerContext(), Context, GetProperty<TModel>(propertyName), value);
+ }
+
+ public void OnModelUpdated()
+ {
+ base.OnModelUpdated(new ControllerContext(), Context);
+ }
+
+ public void SetProperty(string propertyName, object value)
+ {
+ base.SetProperty(new ControllerContext(), Context, GetProperty<TModel>(propertyName), value);
+ }
+ }
+
+ private class CountryState
+ {
+ public string Name { get; set; }
+
+ public IEnumerable<string> States { get; set; }
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Test/DefaultViewLocationCacheTest.cs b/test/System.Web.Mvc.Test/Test/DefaultViewLocationCacheTest.cs
new file mode 100644
index 00000000..90fbf950
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Test/DefaultViewLocationCacheTest.cs
@@ -0,0 +1,73 @@
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Mvc.Test
+{
+ public class DefaultViewLocationCacheTest
+ {
+ [Fact]
+ public void TimeSpanProperty()
+ {
+ // Arrange
+ TimeSpan timeSpan = new TimeSpan(0, 20, 0);
+ DefaultViewLocationCache viewCache = new DefaultViewLocationCache(timeSpan);
+
+ // Assert
+ Assert.Equal(timeSpan.Ticks, viewCache.TimeSpan.Ticks);
+ }
+
+ [Fact]
+ public void ConstructorAssignsDefaultTimeSpan()
+ {
+ // Arrange
+ DefaultViewLocationCache viewLocationCache = new DefaultViewLocationCache();
+ TimeSpan timeSpan = new TimeSpan(0, 15, 0);
+
+ // Assert
+ Assert.Equal(timeSpan.Ticks, viewLocationCache.TimeSpan.Ticks);
+ }
+
+ [Fact]
+ public void ConstructorWithNegativeTimeSpanThrows()
+ {
+ // Assert
+ Assert.Throws<InvalidOperationException>(
+ delegate { new DefaultViewLocationCache(new TimeSpan(-1, 0, 0)); },
+ "The number of ticks for the TimeSpan value must be greater than or equal to 0.");
+ }
+
+ [Fact]
+ public void GetViewLocationThrowsWithNullHttpContext()
+ {
+ // Arrange
+ DefaultViewLocationCache viewLocationCache = new DefaultViewLocationCache();
+
+ // Act & Assert
+ Assert.ThrowsArgumentNull(
+ delegate { string viewLocation = viewLocationCache.GetViewLocation(null /* httpContext */, "foo"); },
+ "httpContext");
+ }
+
+ [Fact]
+ public void InsertViewLocationThrowsWithNullHttpContext()
+ {
+ // Arrange
+ DefaultViewLocationCache viewLocationCache = new DefaultViewLocationCache();
+
+ // Act & Assert
+ Assert.ThrowsArgumentNull(
+ delegate { viewLocationCache.InsertViewLocation(null /* httpContext */, "foo", "fooPath"); },
+ "httpContext");
+ }
+
+ [Fact]
+ public void NullViewLocationCacheReturnsNullLocations()
+ {
+ // Act
+ DefaultViewLocationCache.Null.InsertViewLocation(null /* httpContext */, "foo", "fooPath");
+
+ // Assert
+ Assert.Equal(null, DefaultViewLocationCache.Null.GetViewLocation(null /* httpContext */, "foo"));
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Test/DependencyResolverTest.cs b/test/System.Web.Mvc.Test/Test/DependencyResolverTest.cs
new file mode 100644
index 00000000..ebc65e9d
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Test/DependencyResolverTest.cs
@@ -0,0 +1,241 @@
+using System.Collections.Generic;
+using Moq;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Mvc.Test
+{
+ public class DependencyResolverTest
+ {
+ [Fact]
+ public void GuardClauses()
+ {
+ // Arrange
+ var resolver = new DependencyResolver();
+
+ // Act & Assert
+ Assert.ThrowsArgumentNull(
+ () => resolver.InnerSetResolver((IDependencyResolver)null),
+ "resolver"
+ );
+ Assert.ThrowsArgumentNull(
+ () => resolver.InnerSetResolver((object)null),
+ "commonServiceLocator"
+ );
+ Assert.ThrowsArgumentNull(
+ () => resolver.InnerSetResolver(null, type => null),
+ "getService"
+ );
+ Assert.ThrowsArgumentNull(
+ () => resolver.InnerSetResolver(type => null, null),
+ "getServices"
+ );
+ }
+
+ [Fact]
+ public void DefaultServiceLocatorBehaviorTests()
+ {
+ // Arrange
+ var resolver = new DependencyResolver();
+
+ // Act & Assert
+ Assert.NotNull(resolver.InnerCurrent.GetService<object>()); // Concrete type
+ Assert.Null(resolver.InnerCurrent.GetService<ModelMetadataProvider>()); // Abstract type
+ Assert.Null(resolver.InnerCurrent.GetService<IDisposable>()); // Interface
+ Assert.Null(resolver.InnerCurrent.GetService(typeof(List<>))); // Open generic
+ }
+
+ [Fact]
+ public void DefaultServiceLocatorResolvesNewInstances()
+ {
+ // Arrange
+ var resolver = new DependencyResolver();
+
+ // Act
+ object obj1 = resolver.InnerCurrent.GetService<object>();
+ object obj2 = resolver.InnerCurrent.GetService<object>();
+
+ // Assert
+ Assert.NotSame(obj1, obj2);
+ }
+
+ public class MockableResolver
+ {
+ public virtual object Get(Type type)
+ {
+ throw new NotImplementedException();
+ }
+
+ public virtual IEnumerable<object> GetAll(Type type)
+ {
+ throw new NotImplementedException();
+ }
+ }
+
+ [Fact]
+ public void ResolverPassesCallsToDelegateBasedResolver()
+ {
+ // Arrange
+ var resolver = new DependencyResolver();
+ var mockResolver = new Mock<MockableResolver>();
+ resolver.InnerSetResolver(mockResolver.Object.Get, mockResolver.Object.GetAll);
+
+ // Act & Assert
+ resolver.InnerCurrent.GetService(typeof(object));
+ mockResolver.Verify(r => r.Get(typeof(object)));
+
+ resolver.InnerCurrent.GetServices(typeof(string));
+ mockResolver.Verify(r => r.GetAll(typeof(string)));
+ }
+
+ public class MockableCommonServiceLocator
+ {
+ public virtual object GetInstance(Type type)
+ {
+ throw new NotImplementedException();
+ }
+
+ public virtual IEnumerable<object> GetAllInstances(Type type)
+ {
+ throw new NotImplementedException();
+ }
+ }
+
+ [Fact]
+ public void ResolverPassesCallsToICommonServiceLocator()
+ {
+ // Arrange
+ var resolver = new DependencyResolver();
+ var mockResolver = new Mock<MockableCommonServiceLocator>();
+ resolver.InnerSetResolver(mockResolver.Object);
+
+ // Act & Assert
+ resolver.InnerCurrent.GetService(typeof(object));
+ mockResolver.Verify(r => r.GetInstance(typeof(object)));
+
+ resolver.InnerCurrent.GetServices(typeof(string));
+ mockResolver.Verify(r => r.GetAllInstances(typeof(string)));
+ }
+
+ class MissingGetInstance
+ {
+ public IEnumerable<object> GetAllInstances(Type type)
+ {
+ return null;
+ }
+ }
+
+ class MissingGetAllInstances
+ {
+ public object GetInstance(Type type)
+ {
+ return null;
+ }
+ }
+
+ class GetInstanceHasWrongSignature
+ {
+ public string GetInstance(Type type)
+ {
+ return null;
+ }
+
+ public IEnumerable<object> GetAllInstances(Type type)
+ {
+ return null;
+ }
+ }
+
+ class GetAllInstancesHasWrongSignature
+ {
+ public object GetInstance(Type type)
+ {
+ return null;
+ }
+
+ public IEnumerable<string> GetAllInstances(Type type)
+ {
+ return null;
+ }
+ }
+
+ [Fact]
+ public void ValidationOfCommonServiceLocatorTests()
+ {
+ // Arrange
+ var resolver = new DependencyResolver();
+
+ // Act & Assert
+ Assert.Throws<ArgumentException>(
+ () => resolver.InnerSetResolver(new MissingGetInstance()),
+ @"The type System.Web.Mvc.Test.DependencyResolverTest+MissingGetInstance does not appear to implement Microsoft.Practices.ServiceLocation.IServiceLocator.
+Parameter name: commonServiceLocator"
+ );
+ Assert.Throws<ArgumentException>(
+ () => resolver.InnerSetResolver(new MissingGetAllInstances()),
+ @"The type System.Web.Mvc.Test.DependencyResolverTest+MissingGetAllInstances does not appear to implement Microsoft.Practices.ServiceLocation.IServiceLocator.
+Parameter name: commonServiceLocator"
+ );
+ Assert.Throws<ArgumentException>(
+ () => resolver.InnerSetResolver(new GetInstanceHasWrongSignature()),
+ @"The type System.Web.Mvc.Test.DependencyResolverTest+GetInstanceHasWrongSignature does not appear to implement Microsoft.Practices.ServiceLocation.IServiceLocator.
+Parameter name: commonServiceLocator"
+ );
+ Assert.Throws<ArgumentException>(
+ () => resolver.InnerSetResolver(new GetAllInstancesHasWrongSignature()),
+ @"The type System.Web.Mvc.Test.DependencyResolverTest+GetAllInstancesHasWrongSignature does not appear to implement Microsoft.Practices.ServiceLocation.IServiceLocator.
+Parameter name: commonServiceLocator"
+ );
+ }
+
+
+
+ [Fact]
+ public void DependencyResolverCache()
+ {
+ // Verify that when we ask for an interface twice, it only queries the underlying resolver once.
+ var resolver = new DependencyResolver();
+
+ Mock<IDependencyResolver> resolverMock = new Mock<IDependencyResolver>();
+ resolverMock.Setup(r => r.GetService(typeof(object))).Returns(() => new object());
+ resolverMock.Setup(r => r.GetService(typeof(int))).Returns(15);
+
+ resolver.InnerSetResolver(resolverMock.Object);
+
+ object result1 = resolver.InnerCurrentCache.GetService(typeof(object)); // 1st call
+ object otherResult = resolver.InnerCurrentCache.GetService(typeof(int)); // 2nd call
+ object result2 = resolver.InnerCurrentCache.GetService(typeof(object)); // Cached result from 1st call
+
+
+ resolverMock.Verify(r => r.GetService(typeof(object)), Times.Once());
+ resolverMock.Verify(r => r.GetService(typeof(int)), Times.Once());
+ Assert.Same(result1, result2);
+ Assert.Equal(15, otherResult);
+ }
+
+ [Fact]
+ public void ClearDependencyResolverCache()
+ {
+ // Verify that when we ask for an interface twice, it only queries the underlying resolver once.
+ var resolver = new DependencyResolver();
+
+ Mock<IDependencyResolver> resolverMock = new Mock<IDependencyResolver>();
+ resolverMock.Setup(r => r.GetService(typeof(object))).Returns(() => new object());
+ resolverMock.Setup(r => r.GetService(typeof(int))).Returns(15);
+
+ resolver.InnerSetResolver(resolverMock.Object);
+
+ object result1 = resolver.InnerCurrentCache.GetService(typeof(object)); // 1st call
+ object otherResult = resolver.InnerCurrentCache.GetService(typeof(int)); // 2nd call
+ resolver.InnerSetResolver(resolverMock.Object); // This will clear the cache
+ object result2 = resolver.InnerCurrentCache.GetService(typeof(object)); // 3rd call
+
+
+ resolverMock.Verify(r => r.GetService(typeof(object)), Times.Exactly(2));
+ resolverMock.Verify(r => r.GetService(typeof(int)), Times.Once());
+ Assert.NotSame(result1, result2);
+ Assert.Equal(15, otherResult);
+ }
+
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Test/DescriptorUtilTest.cs b/test/System.Web.Mvc.Test/Test/DescriptorUtilTest.cs
new file mode 100644
index 00000000..22a155a4
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Test/DescriptorUtilTest.cs
@@ -0,0 +1,55 @@
+using Xunit;
+
+namespace System.Web.Mvc.Test
+{
+ public class DescriptorUtilTest
+ {
+ [Fact]
+ public void CreateUniqueId_FromIUniquelyIdentifiable()
+ {
+ // Arrange
+ CustomUniquelyIdentifiable custom = new CustomUniquelyIdentifiable("hello-world");
+
+ // Act
+ string retVal = DescriptorUtil.CreateUniqueId(custom);
+
+ // Assert
+ Assert.Equal("[11]hello-world", retVal);
+ }
+
+ [Fact]
+ public void CreateUniqueId_FromMemberInfo()
+ {
+ // Arrange
+ string moduleVersionId = typeof(DescriptorUtilTest).Module.ModuleVersionId.ToString();
+ string metadataToken = typeof(DescriptorUtilTest).MetadataToken.ToString();
+ string expected = String.Format("[{0}]{1}[{2}]{3}", moduleVersionId.Length, moduleVersionId, metadataToken.Length, metadataToken);
+
+ // Act
+ string retVal = DescriptorUtil.CreateUniqueId(typeof(DescriptorUtilTest));
+
+ // Assert
+ Assert.Equal(expected, retVal);
+ }
+
+ [Fact]
+ public void CreateUniqueId_FromSimpleTypes()
+ {
+ // Act
+ string retVal = DescriptorUtil.CreateUniqueId("foo", null, 12345);
+
+ // Assert
+ Assert.Equal("[3]foo[-1][5]12345", retVal);
+ }
+
+ private sealed class CustomUniquelyIdentifiable : IUniquelyIdentifiable
+ {
+ public CustomUniquelyIdentifiable(string uniqueId)
+ {
+ UniqueId = uniqueId;
+ }
+
+ public string UniqueId { get; private set; }
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Test/DictionaryHelpersTest.cs b/test/System.Web.Mvc.Test/Test/DictionaryHelpersTest.cs
new file mode 100644
index 00000000..6df4c367
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Test/DictionaryHelpersTest.cs
@@ -0,0 +1,89 @@
+using System.Collections.Generic;
+using System.Linq;
+using Xunit;
+
+namespace System.Web.Mvc.Test
+{
+ public class DictionaryHelpersTest
+ {
+ [Fact]
+ public void DoesAnyKeyHavePrefixFailure()
+ {
+ // Arrange
+ Dictionary<string, object> dict = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase)
+ {
+ { "FOOBAR", 42 }
+ };
+
+ // Act
+ bool wasPrefixFound = DictionaryHelpers.DoesAnyKeyHavePrefix(dict, "foo");
+
+ // Assert
+ Assert.False(wasPrefixFound);
+ }
+
+ [Fact]
+ public void DoesAnyKeyHavePrefixSuccess()
+ {
+ // Arrange
+ Dictionary<string, object> dict = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase)
+ {
+ { "FOO.BAR", 42 }
+ };
+
+ // Act
+ bool wasPrefixFound = DictionaryHelpers.DoesAnyKeyHavePrefix(dict, "foo");
+
+ // Assert
+ Assert.True(wasPrefixFound);
+ }
+
+ [Fact]
+ public void FindKeysWithPrefix()
+ {
+ // Arrange
+ Dictionary<string, string> dict = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
+ {
+ { "FOO", "fooValue" },
+ { "FOOBAR", "foobarValue" },
+ { "FOO.BAR", "foo.barValue" },
+ { "FOO[0]", "foo[0]Value" },
+ { "BAR", "barValue" }
+ };
+
+ // Act
+ var matchingEntries = DictionaryHelpers.FindKeysWithPrefix(dict, "foo");
+
+ // Assert
+ var matchingEntriesList = matchingEntries.OrderBy(entry => entry.Key).ToList();
+ Assert.Equal(3, matchingEntriesList.Count);
+ Assert.Equal("foo", matchingEntriesList[0].Key);
+ Assert.Equal("fooValue", matchingEntriesList[0].Value);
+ Assert.Equal("FOO.BAR", matchingEntriesList[1].Key);
+ Assert.Equal("foo.barValue", matchingEntriesList[1].Value);
+ Assert.Equal("FOO[0]", matchingEntriesList[2].Key);
+ Assert.Equal("foo[0]Value", matchingEntriesList[2].Value);
+ }
+
+ [Fact]
+ public void GetOrDefaultMissing()
+ {
+ Dictionary<string, int> dict = new Dictionary<string, int>();
+ int @default = 15;
+
+ int value = dict.GetOrDefault("two", @default);
+
+ Assert.Equal(15, @default);
+ }
+
+ [Fact]
+ public void GetOrDefaultPresent()
+ {
+ Dictionary<string, int> dict = new Dictionary<string, int>() { { "one", 1 } };
+
+ int value = dict.GetOrDefault("one", -999);
+
+ Assert.Equal(1, 1);
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Test/DictionaryValueProviderTest.cs b/test/System.Web.Mvc.Test/Test/DictionaryValueProviderTest.cs
new file mode 100644
index 00000000..9ebf55df
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Test/DictionaryValueProviderTest.cs
@@ -0,0 +1,102 @@
+using System.Collections.Generic;
+using System.Globalization;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Mvc.Test
+{
+ public class DictionaryValueProviderTest
+ {
+ private static readonly Dictionary<string, object> _backingStore = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase)
+ {
+ { "forty.two", 42 },
+ { "nineteen.eighty.four", new DateTime(1984, 1, 1) }
+ };
+
+ [Fact]
+ public void Constructor_ThrowsIfDictionaryIsNull()
+ {
+ // Act & assert
+ Assert.ThrowsArgumentNull(
+ delegate { new DictionaryValueProvider<object>(null, CultureInfo.InvariantCulture); }, "dictionary");
+ }
+
+ [Fact]
+ public void ContainsPrefix()
+ {
+ // Arrange
+ DictionaryValueProvider<object> valueProvider = new DictionaryValueProvider<object>(_backingStore, null);
+
+ // Act
+ bool result = valueProvider.ContainsPrefix("forty");
+
+ // Assert
+ Assert.True(result);
+ }
+
+ [Fact]
+ public void ContainsPrefix_DoesNotContainEmptyPrefixIfBackingStoreIsEmpty()
+ {
+ // Arrange
+ DictionaryValueProvider<object> valueProvider = new DictionaryValueProvider<object>(new Dictionary<string, object>(), null);
+
+ // Act
+ bool result = valueProvider.ContainsPrefix("");
+
+ // Assert
+ Assert.False(result);
+ }
+
+ [Fact]
+ public void ContainsPrefix_ThrowsIfPrefixIsNull()
+ {
+ // Arrange
+ DictionaryValueProvider<object> valueProvider = new DictionaryValueProvider<object>(_backingStore, null);
+
+ // Act & assert
+ Assert.ThrowsArgumentNull(
+ delegate { valueProvider.ContainsPrefix(null); }, "prefix");
+ }
+
+ [Fact]
+ public void GetValue()
+ {
+ // Arrange
+ CultureInfo culture = CultureInfo.GetCultureInfo("fr-FR");
+ DictionaryValueProvider<object> valueProvider = new DictionaryValueProvider<object>(_backingStore, culture);
+
+ // Act
+ ValueProviderResult vpResult = valueProvider.GetValue("nineteen.eighty.four");
+
+ // Assert
+ Assert.NotNull(vpResult);
+ Assert.Equal(new DateTime(1984, 1, 1), vpResult.RawValue);
+ Assert.Equal("01/01/1984 00:00:00", vpResult.AttemptedValue);
+ Assert.Equal(culture, vpResult.Culture);
+ }
+
+ [Fact]
+ public void GetValue_ReturnsNullIfKeyNotFound()
+ {
+ // Arrange
+ DictionaryValueProvider<object> valueProvider = new DictionaryValueProvider<object>(_backingStore, null);
+
+ // Act
+ ValueProviderResult vpResult = valueProvider.GetValue("nineteen.eighty");
+
+ // Assert
+ Assert.Null(vpResult);
+ }
+
+ [Fact]
+ public void GetValue_ThrowsIfKeyIsNull()
+ {
+ // Arrange
+ DictionaryValueProvider<object> valueProvider = new DictionaryValueProvider<object>(_backingStore, null);
+
+ // Act & assert
+ Assert.ThrowsArgumentNull(
+ delegate { valueProvider.GetValue(null); }, "key");
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Test/DynamicViewDataDictionaryTest.cs b/test/System.Web.Mvc.Test/Test/DynamicViewDataDictionaryTest.cs
new file mode 100644
index 00000000..55430a0f
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Test/DynamicViewDataDictionaryTest.cs
@@ -0,0 +1,214 @@
+using System.Collections.Generic;
+using System.Dynamic;
+using System.Linq;
+using Moq;
+using Xunit;
+
+namespace System.Web.Mvc.Test
+{
+ public class DynamicViewDataDictionaryTest
+ {
+ [Fact]
+ public void Get_OnExistingProperty_ReturnsValue()
+ {
+ // Arrange
+ ViewDataDictionary vd = new ViewDataDictionary()
+ {
+ { "Prop", "Value" }
+ };
+ dynamic dynamicVD = new DynamicViewDataDictionary(() => vd);
+
+ // Act
+ object value = dynamicVD.Prop;
+
+ // Assert
+ Assert.IsType<string>(value);
+ Assert.Equal("Value", value);
+ }
+
+ [Fact]
+ public void Get_OnNonExistentProperty_ReturnsNull()
+ {
+ // Arrange
+ ViewDataDictionary vd = new ViewDataDictionary();
+ dynamic dynamicVD = new DynamicViewDataDictionary(() => vd);
+
+ // Act
+ object value = dynamicVD.Prop;
+
+ // Assert
+ Assert.Null(value);
+ }
+
+ [Fact]
+ public void Set_OnExistingProperty_OverridesValue()
+ {
+ // Arrange
+ ViewDataDictionary vd = new ViewDataDictionary()
+ {
+ { "Prop", "Value" }
+ };
+ dynamic dynamicVD = new DynamicViewDataDictionary(() => vd);
+
+ // Act
+ dynamicVD.Prop = "NewValue";
+
+ // Assert
+ Assert.Equal("NewValue", dynamicVD.Prop);
+ Assert.Equal("NewValue", vd["Prop"]);
+ }
+
+ [Fact]
+ public void Set_OnNonExistentProperty_SetsValue()
+ {
+ // Arrange
+ ViewDataDictionary vd = new ViewDataDictionary();
+ dynamic dynamicVD = new DynamicViewDataDictionary(() => vd);
+
+ // Act
+ dynamicVD.Prop = "NewValue";
+
+ // Assert
+ Assert.Equal("NewValue", dynamicVD.Prop);
+ Assert.Equal("NewValue", vd["Prop"]);
+ }
+
+ [Fact]
+ public void TryGetMember_OnExistingProperty_ReturnsValueAndSucceeds()
+ {
+ // Arrange
+ object result = null;
+ ViewDataDictionary vd = new ViewDataDictionary()
+ {
+ { "Prop", "Value" }
+ };
+ DynamicViewDataDictionary dynamicVD = new DynamicViewDataDictionary(() => vd);
+ Mock<GetMemberBinder> binderMock = new Mock<GetMemberBinder>("Prop", /* ignoreCase */ false);
+
+ // Act
+ bool success = dynamicVD.TryGetMember(binderMock.Object, out result);
+
+ // Assert
+ Assert.True(success);
+ Assert.Equal("Value", result);
+ }
+
+ [Fact]
+ public void TryGetMember_OnNonExistentProperty_ReturnsNullAndSucceeds()
+ {
+ // Arrange
+ object result = null;
+ ViewDataDictionary vd = new ViewDataDictionary();
+ DynamicViewDataDictionary dynamicVD = new DynamicViewDataDictionary(() => vd);
+ Mock<GetMemberBinder> binderMock = new Mock<GetMemberBinder>("Prop", /* ignoreCase */ false);
+
+ // Act
+ bool success = dynamicVD.TryGetMember(binderMock.Object, out result);
+
+ // Assert
+ Assert.True(success);
+ Assert.Null(result);
+ }
+
+ [Fact]
+ public void TrySetMember_OnExistingProperty_OverridesValueAndSucceeds()
+ {
+ // Arrange
+ ViewDataDictionary vd = new ViewDataDictionary()
+ {
+ { "Prop", "Value" }
+ };
+ DynamicViewDataDictionary dynamicVD = new DynamicViewDataDictionary(() => vd);
+ Mock<SetMemberBinder> binderMock = new Mock<SetMemberBinder>("Prop", /* ignoreCase */ false);
+
+ // Act
+ bool success = dynamicVD.TrySetMember(binderMock.Object, "NewValue");
+
+ // Assert
+ Assert.True(success);
+ Assert.Equal("NewValue", ((dynamic)dynamicVD).Prop);
+ Assert.Equal("NewValue", vd["Prop"]);
+ }
+
+ [Fact]
+ public void TrySetMember_OnNonExistentProperty_SetsValueAndSucceeds()
+ {
+ // Arrange
+ ViewDataDictionary vd = new ViewDataDictionary();
+ DynamicViewDataDictionary dynamicVD = new DynamicViewDataDictionary(() => vd);
+ Mock<SetMemberBinder> binderMock = new Mock<SetMemberBinder>("Prop", /* ignoreCase */ false);
+
+ // Act
+ bool success = dynamicVD.TrySetMember(binderMock.Object, "NewValue");
+
+ // Assert
+ Assert.True(success);
+ Assert.Equal("NewValue", ((dynamic)dynamicVD).Prop);
+ Assert.Equal("NewValue", vd["Prop"]);
+ }
+
+ [Fact]
+ public void GetDynamicMemberNames_ReturnsEmptyListForEmptyViewDataDictionary()
+ {
+ // Arrange
+ ViewDataDictionary vd = new ViewDataDictionary();
+ DynamicViewDataDictionary dvd = new DynamicViewDataDictionary(() => vd);
+
+ // Act
+ IEnumerable<string> result = dvd.GetDynamicMemberNames();
+
+ // Assert
+ Assert.Empty(result);
+ }
+
+ [Fact]
+ public void GetDynamicMemberNames_ReturnsKeyNamesForFilledViewDataDictionary()
+ {
+ // Arrange
+ ViewDataDictionary vd = new ViewDataDictionary()
+ {
+ { "Prop1", 1 },
+ { "Prop2", 2 }
+ };
+ DynamicViewDataDictionary dvd = new DynamicViewDataDictionary(() => vd);
+
+ // Act
+ var result = dvd.GetDynamicMemberNames();
+
+ // Assert
+ Assert.Equal(new[] { "Prop1", "Prop2" }, result.OrderBy(s => s).ToArray());
+ }
+
+ [Fact]
+ public void GetDynamicMemberNames_ReflectsChangesToUnderlyingViewDataDictionary()
+ {
+ // Arrange
+ ViewDataDictionary vd = new ViewDataDictionary();
+ vd["OldProp"] = 123;
+ DynamicViewDataDictionary dvd = new DynamicViewDataDictionary(() => vd);
+ vd["NewProp"] = "somevalue";
+
+ // Act
+ var result = dvd.GetDynamicMemberNames();
+
+ // Assert
+ Assert.Equal(new[] { "NewProp", "OldProp" }, result.OrderBy(s => s).ToArray());
+ }
+
+ [Fact]
+ public void GetDynamicMemberNames_ReflectsChangesToDynamicObject()
+ {
+ // Arrange
+ ViewDataDictionary vd = new ViewDataDictionary();
+ vd["OldProp"] = 123;
+ DynamicViewDataDictionary dvd = new DynamicViewDataDictionary(() => vd);
+ ((dynamic)dvd).NewProp = "foo";
+
+ // Act
+ var result = dvd.GetDynamicMemberNames();
+
+ // Assert
+ Assert.Equal(new[] { "NewProp", "OldProp" }, result.OrderBy(s => s).ToArray());
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Test/EmptyModelValidatorProviderTest.cs b/test/System.Web.Mvc.Test/Test/EmptyModelValidatorProviderTest.cs
new file mode 100644
index 00000000..d03598ae
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Test/EmptyModelValidatorProviderTest.cs
@@ -0,0 +1,21 @@
+using System.Collections.Generic;
+using Xunit;
+
+namespace System.Web.Mvc.Test
+{
+ public class EmptyModelValidatorProviderTest
+ {
+ [Fact]
+ public void ReturnsNoValidators()
+ {
+ // Arrange
+ EmptyModelValidatorProvider provider = new EmptyModelValidatorProvider();
+
+ // Act
+ IEnumerable<ModelValidator> result = provider.GetValidators(null, null);
+
+ // Assert
+ Assert.Empty(result);
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Test/ExceptionContextTest.cs b/test/System.Web.Mvc.Test/Test/ExceptionContextTest.cs
new file mode 100644
index 00000000..79222697
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Test/ExceptionContextTest.cs
@@ -0,0 +1,46 @@
+using System.Web.TestUtil;
+using Moq;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Mvc.Test
+{
+ public class ExceptionContextTest
+ {
+ [Fact]
+ public void ConstructorThrowsIfExceptionIsNull()
+ {
+ // Arrange
+ ControllerContext controllerContext = new Mock<ControllerContext>().Object;
+ Exception exception = null;
+
+ // Act & assert
+ Assert.ThrowsArgumentNull(
+ delegate { new ExceptionContext(controllerContext, exception); }, "exception");
+ }
+
+ [Fact]
+ public void ExceptionProperty()
+ {
+ // Arrange
+ ControllerContext controllerContext = new Mock<ControllerContext>().Object;
+ Exception exception = new Exception();
+
+ // Act
+ ExceptionContext exceptionContext = new ExceptionContext(controllerContext, exception);
+
+ // Assert
+ Assert.Equal(exception, exceptionContext.Exception);
+ }
+
+ [Fact]
+ public void ResultProperty()
+ {
+ // Arrange
+ ExceptionContext exceptionContext = new Mock<ExceptionContext>().Object;
+
+ // Act & assert
+ MemberHelper.TestPropertyWithDefaultInstance(exceptionContext, "Result", new ViewResult(), EmptyResult.Instance);
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Test/ExpressionHelperTest.cs b/test/System.Web.Mvc.Test/Test/ExpressionHelperTest.cs
new file mode 100644
index 00000000..a51aa27f
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Test/ExpressionHelperTest.cs
@@ -0,0 +1,120 @@
+using System.Linq.Expressions;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Mvc.Test
+{
+ public class ExpressionHelperTest
+ {
+ [Fact]
+ public void StringBasedExpressionTests()
+ {
+ ViewDataDictionary vdd = new ViewDataDictionary();
+
+ // Uses the given expression as the expression text
+ Assert.Equal("?", ExpressionHelper.GetExpressionText("?"));
+
+ // Exactly "Model" (case-insensitive) is turned into empty string
+ Assert.Equal(String.Empty, ExpressionHelper.GetExpressionText("Model"));
+ Assert.Equal(String.Empty, ExpressionHelper.GetExpressionText("mOdEl"));
+
+ // Beginning with "Model" is untouched
+ Assert.Equal("Model.Foo", ExpressionHelper.GetExpressionText("Model.Foo"));
+ }
+
+ [Fact]
+ public void LambdaBasedExpressionTextTests()
+ {
+ // "Model" at the front of the expression is excluded (case insensitively)
+ DummyContactModel Model = null;
+ Assert.Equal(String.Empty, ExpressionHelper.GetExpressionText(Lambda<object, DummyContactModel>(m => Model)));
+ Assert.Equal("FirstName", ExpressionHelper.GetExpressionText(Lambda<object, string>(m => Model.FirstName)));
+
+ DummyContactModel mOdeL = null;
+ Assert.Equal(String.Empty, ExpressionHelper.GetExpressionText(Lambda<object, DummyContactModel>(m => mOdeL)));
+ Assert.Equal("FirstName", ExpressionHelper.GetExpressionText(Lambda<object, string>(m => mOdeL.FirstName)));
+
+ // Model property of model is passed through
+ Assert.Equal("Model", ExpressionHelper.GetExpressionText(Lambda<DummyModelContainer, DummyContactModel>(m => m.Model)));
+
+ // "Model" in the middle of the expression is not excluded
+ DummyModelContainer container = null;
+ Assert.Equal("container.Model", ExpressionHelper.GetExpressionText(Lambda<object, DummyContactModel>(m => container.Model)));
+ Assert.Equal("container.Model.FirstName", ExpressionHelper.GetExpressionText(Lambda<object, string>(m => container.Model.FirstName)));
+
+ // The parameter is excluded
+ Assert.Equal(String.Empty, ExpressionHelper.GetExpressionText(Lambda<DummyContactModel, DummyContactModel>(m => m)));
+ Assert.Equal("FirstName", ExpressionHelper.GetExpressionText(Lambda<DummyContactModel, string>(m => m.FirstName)));
+
+ // Integer indexer is included and properly computed from captured values
+ int x = 2;
+ Assert.Equal("container.Model[42].Length", ExpressionHelper.GetExpressionText(Lambda<object, int>(m => container.Model[x * 21].Length)));
+ Assert.Equal("[42]", ExpressionHelper.GetExpressionText(Lambda<int[], int>(m => m[x * 21])));
+
+ // String indexer is included and properly computed from captured values
+ string y = "Hello world";
+ Assert.Equal("container.Model[Hello].Length", ExpressionHelper.GetExpressionText(Lambda<object, int>(m => container.Model[y.Substring(0, 5)].Length)));
+
+ // Back to back indexer is included
+ Assert.Equal("container.Model[1024][2]", ExpressionHelper.GetExpressionText(Lambda<object, char>(m => container.Model[x * 512][x])));
+
+ // Multi-parameter indexer is excluded
+ Assert.Equal("Length", ExpressionHelper.GetExpressionText(Lambda<object, int>(m => container.Model[42, "Hello World"].Length)));
+
+ // Single array indexer is included
+ Assert.Equal("container.Model.Array[1024]", ExpressionHelper.GetExpressionText(Lambda<object, int>(m => container.Model.Array[x * 512])));
+
+ // Double array indexer is excluded
+ Assert.Equal("", ExpressionHelper.GetExpressionText(Lambda<object, int>(m => container.Model.DoubleArray[1, 2])));
+
+ // Non-indexer method call is excluded
+ Assert.Equal("Length", ExpressionHelper.GetExpressionText(Lambda<object, int>(m => container.Model.Method().Length)));
+
+ // Lambda expression which involves indexer which references lambda parameter throws
+ Assert.Throws<InvalidOperationException>(
+ () => ExpressionHelper.GetExpressionText(Lambda<string, char>(s => s[s.Length - 4])),
+ "The expression compiler was unable to evaluate the indexer expression '(s.Length - 4)' because it references the model parameter 's' which is unavailable.");
+ }
+
+ // Helpers
+
+ private LambdaExpression Lambda<T1, T2>(Expression<Func<T1, T2>> expression)
+ {
+ return expression;
+ }
+
+ class DummyContactModel
+ {
+ public string FirstName { get; set; }
+
+ public string this[int index]
+ {
+ get { return index.ToString(); }
+ }
+
+ public string this[string index]
+ {
+ get { return index; }
+ }
+
+ public string this[int index, string index2]
+ {
+ get { return index2; }
+ }
+
+ public int[] Array { get; set; }
+
+ public int[,] DoubleArray { get; set; }
+
+ public string Method()
+ {
+ return String.Empty;
+ }
+ }
+
+ class DummyModelContainer
+ {
+ public DummyContactModel Model { get; set; }
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Test/FieldValidationMetadataTest.cs b/test/System.Web.Mvc.Test/Test/FieldValidationMetadataTest.cs
new file mode 100644
index 00000000..bd79d04f
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Test/FieldValidationMetadataTest.cs
@@ -0,0 +1,33 @@
+using System.Collections.Generic;
+using System.Web.TestUtil;
+using Xunit;
+
+namespace System.Web.Mvc.Test
+{
+ public class FieldValidationMetadataTest
+ {
+ [Fact]
+ public void FieldNameProperty()
+ {
+ // Arrange
+ FieldValidationMetadata metadata = new FieldValidationMetadata();
+
+ // Act & assert
+ MemberHelper.TestStringProperty(metadata, "FieldName", String.Empty);
+ }
+
+ [Fact]
+ public void ValidationRulesProperty()
+ {
+ // Arrange
+ FieldValidationMetadata metadata = new FieldValidationMetadata();
+
+ // Act
+ ICollection<ModelClientValidationRule> validationRules = metadata.ValidationRules;
+
+ // Assert
+ Assert.NotNull(validationRules);
+ Assert.Empty(validationRules);
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Test/FileContentResultTest.cs b/test/System.Web.Mvc.Test/Test/FileContentResultTest.cs
new file mode 100644
index 00000000..74f9f7a0
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Test/FileContentResultTest.cs
@@ -0,0 +1,65 @@
+using System.IO;
+using Moq;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Mvc.Test
+{
+ public class FileContentResultTest
+ {
+ [Fact]
+ public void ConstructorSetsFileContentsProperty()
+ {
+ // Arrange
+ byte[] fileContents = new byte[0];
+
+ // Act
+ FileContentResult result = new FileContentResult(fileContents, "contentType");
+
+ // Assert
+ Assert.Same(fileContents, result.FileContents);
+ }
+
+ [Fact]
+ public void ConstructorThrowsIfFileContentsIsNull()
+ {
+ // Act & assert
+ Assert.ThrowsArgumentNull(
+ delegate { new FileContentResult(null, "contentType"); }, "fileContents");
+ }
+
+ [Fact]
+ public void WriteFileCopiesBufferToOutputStream()
+ {
+ // Arrange
+ byte[] buffer = new byte[] { 1, 2, 3, 4, 5 };
+
+ Mock<Stream> mockOutputStream = new Mock<Stream>();
+ mockOutputStream.Setup(s => s.Write(buffer, 0, buffer.Length)).Verifiable();
+ Mock<HttpResponseBase> mockResponse = new Mock<HttpResponseBase>();
+ mockResponse.Setup(r => r.OutputStream).Returns(mockOutputStream.Object);
+
+ FileContentResultHelper helper = new FileContentResultHelper(buffer, "application/octet-stream");
+
+ // Act
+ helper.PublicWriteFile(mockResponse.Object);
+
+ // Assert
+ mockOutputStream.Verify();
+ mockResponse.Verify();
+ }
+
+ private class FileContentResultHelper : FileContentResult
+ {
+ public FileContentResultHelper(byte[] fileContents, string contentType)
+ : base(fileContents, contentType)
+ {
+ }
+
+ public void PublicWriteFile(HttpResponseBase response)
+ {
+ WriteFile(response);
+ }
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Test/FilePathResultTest.cs b/test/System.Web.Mvc.Test/Test/FilePathResultTest.cs
new file mode 100644
index 00000000..e7fb4f01
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Test/FilePathResultTest.cs
@@ -0,0 +1,64 @@
+using Moq;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Mvc.Test
+{
+ public class FilePathResultTest
+ {
+ [Fact]
+ public void ConstructorSetsFileNameProperty()
+ {
+ // Act
+ FilePathResult result = new FilePathResult("someFile", "contentType");
+
+ // Assert
+ Assert.Equal("someFile", result.FileName);
+ }
+
+ [Fact]
+ public void ConstructorThrowsIfFileNameIsEmpty()
+ {
+ // Act & assert
+ Assert.ThrowsArgumentNullOrEmpty(
+ delegate { new FilePathResult(String.Empty, "contentType"); }, "fileName");
+ }
+
+ [Fact]
+ public void ConstructorThrowsIfFileNameIsNull()
+ {
+ // Act & assert
+ Assert.ThrowsArgumentNullOrEmpty(
+ delegate { new FilePathResult(null, "contentType"); }, "fileName");
+ }
+
+ [Fact]
+ public void WriteFileTransmitsFileToOutputStream()
+ {
+ // Arrange
+ Mock<HttpResponseBase> mockResponse = new Mock<HttpResponseBase>();
+ mockResponse.Setup(r => r.TransmitFile("someFile")).Verifiable();
+
+ FilePathResultHelper helper = new FilePathResultHelper("someFile", "application/octet-stream");
+
+ // Act
+ helper.PublicWriteFile(mockResponse.Object);
+
+ // Assert
+ mockResponse.Verify();
+ }
+
+ private class FilePathResultHelper : FilePathResult
+ {
+ public FilePathResultHelper(string fileName, string contentType)
+ : base(fileName, contentType)
+ {
+ }
+
+ public void PublicWriteFile(HttpResponseBase response)
+ {
+ WriteFile(response);
+ }
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Test/FileResultTest.cs b/test/System.Web.Mvc.Test/Test/FileResultTest.cs
new file mode 100644
index 00000000..69cd8f12
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Test/FileResultTest.cs
@@ -0,0 +1,160 @@
+using System.Net.Mime;
+using System.Web.TestUtil;
+using Moq;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Mvc.Test
+{
+ public class FileResultTest
+ {
+ [Fact]
+ public void ConstructorSetsContentTypeProperty()
+ {
+ // Act
+ FileResult result = new EmptyFileResult("someContentType");
+
+ // Assert
+ Assert.Equal("someContentType", result.ContentType);
+ }
+
+ [Fact]
+ public void ConstructorThrowsIfContentTypeIsEmpty()
+ {
+ // Act & assert
+ Assert.ThrowsArgumentNullOrEmpty(
+ delegate { new EmptyFileResult(String.Empty); }, "contentType");
+ }
+
+ [Fact]
+ public void ConstructorThrowsIfContentTypeIsNull()
+ {
+ // Act & assert
+ Assert.ThrowsArgumentNullOrEmpty(
+ delegate { new EmptyFileResult(null); }, "contentType");
+ }
+
+ [Fact]
+ public void ContentDispositionHeaderIsEncodedCorrectly()
+ {
+ // See comment in FileResult.cs detailing how the FileDownloadName should be encoded.
+
+ // Arrange
+ Mock<ControllerContext> mockControllerContext = new Mock<ControllerContext>(MockBehavior.Strict);
+ mockControllerContext.SetupSet(c => c.HttpContext.Response.ContentType = "application/my-type").Verifiable();
+ mockControllerContext.Setup(c => c.HttpContext.Response.AddHeader("Content-Disposition", @"attachment; filename=""some\\file""")).Verifiable();
+
+ EmptyFileResult result = new EmptyFileResult("application/my-type")
+ {
+ FileDownloadName = @"some\file"
+ };
+
+ // Act
+ result.ExecuteResult(mockControllerContext.Object);
+
+ // Assert
+ Assert.True(result.WasWriteFileCalled);
+ mockControllerContext.Verify();
+ }
+
+ [Fact(Skip="Pending fix to DevDiv 356181 -- 4.5 changed ContentDisposition.ToString()")]
+ public void ContentDispositionHeaderIsEncodedCorrectlyForUnicodeCharacters()
+ {
+ // Arrange
+ Mock<ControllerContext> mockControllerContext = new Mock<ControllerContext>(MockBehavior.Strict);
+ mockControllerContext.SetupSet(c => c.HttpContext.Response.ContentType = "application/my-type").Verifiable();
+ mockControllerContext.Setup(c => c.HttpContext.Response.AddHeader("Content-Disposition", @"attachment; filename*=UTF-8''ABCXYZabcxyz012789!%40%23$%25%5E&%2A%28%29-%3D_+.:~%CE%94")).Verifiable();
+
+ EmptyFileResult result = new EmptyFileResult("application/my-type")
+ {
+ FileDownloadName = "ABCXYZabcxyz012789!@#$%^&*()-=_+.:~Δ"
+ };
+
+ // Act
+ result.ExecuteResult(mockControllerContext.Object);
+
+ // Assert
+ Assert.True(result.WasWriteFileCalled);
+ mockControllerContext.Verify();
+ }
+
+ [Fact]
+ public void ExecuteResultDoesNotSetContentDispositionIfNotSpecified()
+ {
+ // Arrange
+ Mock<ControllerContext> mockControllerContext = new Mock<ControllerContext>();
+ mockControllerContext.SetupSet(c => c.HttpContext.Response.ContentType = "application/my-type").Verifiable();
+
+ EmptyFileResult result = new EmptyFileResult("application/my-type");
+
+ // Act
+ result.ExecuteResult(mockControllerContext.Object);
+
+ // Assert
+ Assert.True(result.WasWriteFileCalled);
+ mockControllerContext.Verify();
+ }
+
+ [Fact]
+ public void ExecuteResultSetsContentDispositionIfSpecified()
+ {
+ // Arrange
+ Mock<ControllerContext> mockControllerContext = new Mock<ControllerContext>(MockBehavior.Strict);
+ mockControllerContext.SetupSet(c => c.HttpContext.Response.ContentType = "application/my-type").Verifiable();
+ mockControllerContext.Setup(c => c.HttpContext.Response.AddHeader("Content-Disposition", "attachment; filename=filename.ext")).Verifiable();
+
+ EmptyFileResult result = new EmptyFileResult("application/my-type")
+ {
+ FileDownloadName = "filename.ext"
+ };
+
+ // Act
+ result.ExecuteResult(mockControllerContext.Object);
+
+ // Assert
+ Assert.True(result.WasWriteFileCalled);
+ mockControllerContext.Verify();
+ }
+
+ [Fact]
+ public void ExecuteResultThrowsIfContextIsNull()
+ {
+ // Arrange
+ FileResult result = new EmptyFileResult();
+
+ // Act & assert
+ Assert.ThrowsArgumentNull(
+ delegate { result.ExecuteResult(null); }, "context");
+ }
+
+ [Fact]
+ public void FileDownloadNameProperty()
+ {
+ // Arrange
+ FileResult result = new EmptyFileResult();
+
+ // Act & assert
+ MemberHelper.TestStringProperty(result, "FileDownloadName", String.Empty);
+ }
+
+ private class EmptyFileResult : FileResult
+ {
+ public bool WasWriteFileCalled;
+
+ public EmptyFileResult()
+ : this(MediaTypeNames.Application.Octet)
+ {
+ }
+
+ public EmptyFileResult(string contentType)
+ : base(contentType)
+ {
+ }
+
+ protected override void WriteFile(HttpResponseBase response)
+ {
+ WasWriteFileCalled = true;
+ }
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Test/FileStreamResultTest.cs b/test/System.Web.Mvc.Test/Test/FileStreamResultTest.cs
new file mode 100644
index 00000000..2f560d42
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Test/FileStreamResultTest.cs
@@ -0,0 +1,77 @@
+using System.IO;
+using System.Linq;
+using Moq;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Mvc.Test
+{
+ public class FileStreamResultTest
+ {
+ private static readonly Random _random = new Random();
+
+ [Fact]
+ public void ConstructorSetsFileStreamProperty()
+ {
+ // Arrange
+ Stream stream = new MemoryStream();
+
+ // Act
+ FileStreamResult result = new FileStreamResult(stream, "contentType");
+
+ // Assert
+ Assert.Same(stream, result.FileStream);
+ }
+
+ [Fact]
+ public void ConstructorThrowsIfFileStreamIsNull()
+ {
+ // Act & assert
+ Assert.ThrowsArgumentNull(
+ delegate { new FileStreamResult(null, "contentType"); }, "fileStream");
+ }
+
+ [Fact]
+ public void WriteFileCopiesProvidedStreamToOutputStream()
+ {
+ // Arrange
+ int byteLen = 0x1234;
+ byte[] originalBytes = GetRandomByteArray(byteLen);
+ MemoryStream originalStream = new MemoryStream(originalBytes);
+ MemoryStream outStream = new MemoryStream();
+
+ Mock<HttpResponseBase> mockResponse = new Mock<HttpResponseBase>();
+ mockResponse.Setup(r => r.OutputStream).Returns(outStream);
+
+ FileStreamResultHelper helper = new FileStreamResultHelper(originalStream, "application/octet-stream");
+
+ // Act
+ helper.PublicWriteFile(mockResponse.Object);
+
+ // Assert
+ byte[] outBytes = outStream.ToArray();
+ Assert.True(originalBytes.SequenceEqual(outBytes));
+ mockResponse.Verify();
+ }
+
+ private static byte[] GetRandomByteArray(int length)
+ {
+ byte[] bytes = new byte[length];
+ _random.NextBytes(bytes);
+ return bytes;
+ }
+
+ private class FileStreamResultHelper : FileStreamResult
+ {
+ public FileStreamResultHelper(Stream fileStream, string contentType)
+ : base(fileStream, contentType)
+ {
+ }
+
+ public void PublicWriteFile(HttpResponseBase response)
+ {
+ WriteFile(response);
+ }
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Test/FilterAttributeFilterProviderTest.cs b/test/System.Web.Mvc.Test/Test/FilterAttributeFilterProviderTest.cs
new file mode 100644
index 00000000..b482d128
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Test/FilterAttributeFilterProviderTest.cs
@@ -0,0 +1,155 @@
+using System.Collections.Generic;
+using System.Linq;
+using Moq;
+using Xunit;
+
+namespace System.Web.Mvc.Test
+{
+ public class FilterAttributeFilterProviderTest
+ {
+ [Fact]
+ public void GetFilters_WithNullController_ReturnsEmptyList()
+ {
+ // Arrange
+ var context = new ControllerContext();
+ var descriptor = new Mock<ActionDescriptor>().Object;
+ var provider = new FilterAttributeFilterProvider();
+
+ // Act
+ IEnumerable<Filter> result = provider.GetFilters(context, descriptor);
+
+ // Assert
+ Assert.Empty(result);
+ }
+
+ [MyFilter(Order = 2112)]
+ private class ControllerWithTypeAttribute : Controller
+ {
+ }
+
+ [Fact]
+ public void GetFilters_IncludesAttributesOnControllerType()
+ {
+ // Arrange
+ var context = new ControllerContext { Controller = new ControllerWithTypeAttribute() };
+ var controllerDescriptorMock = new Mock<ControllerDescriptor>();
+ controllerDescriptorMock.Setup(cd => cd.GetFilterAttributes(It.IsAny<bool>()))
+ .Returns(new FilterAttribute[] { new MyFilterAttribute { Order = 2112 } });
+ var actionDescriptorMock = new Mock<ActionDescriptor>();
+ actionDescriptorMock.Setup(ad => ad.ControllerDescriptor).Returns(controllerDescriptorMock.Object);
+ var provider = new FilterAttributeFilterProvider();
+
+ // Act
+ Filter filter = provider.GetFilters(context, actionDescriptorMock.Object).Single();
+
+ // Assert
+ MyFilterAttribute attrib = filter.Instance as MyFilterAttribute;
+ Assert.NotNull(attrib);
+ Assert.Equal(FilterScope.Controller, filter.Scope);
+ Assert.Equal(2112, filter.Order);
+ }
+
+ private class ControllerWithActionAttribute : Controller
+ {
+ [MyFilter(Order = 1234)]
+ public ActionResult MyActionMethod()
+ {
+ return null;
+ }
+ }
+
+ [Fact]
+ public void GetFilters_IncludesAttributesOnActionMethod()
+ {
+ // Arrange
+ var context = new ControllerContext { Controller = new ControllerWithActionAttribute() };
+ var controllerDescriptor = new ReflectedControllerDescriptor(context.Controller.GetType());
+ var action = context.Controller.GetType().GetMethod("MyActionMethod");
+ var actionDescriptor = new ReflectedActionDescriptor(action, "MyActionMethod", controllerDescriptor);
+ var provider = new FilterAttributeFilterProvider();
+
+ // Act
+ Filter filter = provider.GetFilters(context, actionDescriptor).Single();
+
+ // Assert
+ MyFilterAttribute attrib = filter.Instance as MyFilterAttribute;
+ Assert.NotNull(attrib);
+ Assert.Equal(FilterScope.Action, filter.Scope);
+ Assert.Equal(1234, filter.Order);
+ }
+
+ private abstract class BaseController : Controller
+ {
+ public ActionResult MyActionMethod()
+ {
+ return null;
+ }
+ }
+
+ [MyFilter]
+ private class DerivedController : BaseController
+ {
+ }
+
+ [Fact]
+ public void GetFilters_IncludesTypeAttributesFromDerivedTypeWhenMethodIsOnBaseClass()
+ { // DDB #208062
+ // Arrange
+ var context = new ControllerContext { Controller = new DerivedController() };
+ var controllerDescriptor = new ReflectedControllerDescriptor(context.Controller.GetType());
+ var action = context.Controller.GetType().GetMethod("MyActionMethod");
+ var actionDescriptor = new ReflectedActionDescriptor(action, "MyActionMethod", controllerDescriptor);
+ var provider = new FilterAttributeFilterProvider();
+
+ // Act
+ IEnumerable<Filter> filters = provider.GetFilters(context, actionDescriptor);
+
+ // Assert
+ Assert.NotNull(filters.Select(f => f.Instance).Cast<MyFilterAttribute>().Single());
+ }
+
+ private class MyFilterAttribute : FilterAttribute
+ {
+ }
+
+ [Fact]
+ public void GetFilters_RetrievesCachedAttributesByDefault()
+ {
+ // Arrange
+ var provider = new FilterAttributeFilterProvider();
+ var context = new ControllerContext { Controller = new DerivedController() };
+ var controllerDescriptorMock = new Mock<ControllerDescriptor>();
+ controllerDescriptorMock.Setup(cd => cd.GetFilterAttributes(true)).Returns(Enumerable.Empty<FilterAttribute>()).Verifiable();
+ var actionDescriptorMock = new Mock<ActionDescriptor>();
+ actionDescriptorMock.Setup(ad => ad.GetFilterAttributes(true)).Returns(Enumerable.Empty<FilterAttribute>()).Verifiable();
+ actionDescriptorMock.Setup(ad => ad.ControllerDescriptor).Returns(controllerDescriptorMock.Object);
+
+ // Act
+ var result = provider.GetFilters(context, actionDescriptorMock.Object);
+
+ // Assert
+ controllerDescriptorMock.Verify();
+ actionDescriptorMock.Verify();
+ }
+
+ [Fact]
+ public void GetFilters_RetrievesNonCachedAttributesWhenConfiguredNotTo()
+ {
+ // Arrange
+ var provider = new FilterAttributeFilterProvider(false);
+ var context = new ControllerContext { Controller = new DerivedController() };
+ var controllerDescriptorMock = new Mock<ControllerDescriptor>();
+ controllerDescriptorMock.Setup(cd => cd.GetFilterAttributes(false)).Returns(Enumerable.Empty<FilterAttribute>()).Verifiable();
+ var actionDescriptorMock = new Mock<ActionDescriptor>();
+ actionDescriptorMock.Setup(ad => ad.GetFilterAttributes(false)).Returns(Enumerable.Empty<FilterAttribute>()).Verifiable();
+ actionDescriptorMock.Setup(ad => ad.ControllerDescriptor).Returns(controllerDescriptorMock.Object);
+
+ // Act
+ var result = provider.GetFilters(context, actionDescriptorMock.Object);
+
+ // Assert
+ controllerDescriptorMock.Verify();
+ actionDescriptorMock.Verify();
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Test/FilterInfoTest.cs b/test/System.Web.Mvc.Test/Test/FilterInfoTest.cs
new file mode 100644
index 00000000..d5286d27
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Test/FilterInfoTest.cs
@@ -0,0 +1,69 @@
+using System.Collections.Generic;
+using System.Linq;
+using Moq;
+using Xunit;
+
+namespace System.Web.Mvc.Test
+{
+ public class FilterInfoTest
+ {
+ [Fact]
+ public void Constructor_Default()
+ {
+ // Arrange + Act
+ FilterInfo filterInfo = new FilterInfo();
+
+ // Assert
+ Assert.Empty(filterInfo.ActionFilters);
+ Assert.Empty(filterInfo.AuthorizationFilters);
+ Assert.Empty(filterInfo.ExceptionFilters);
+ Assert.Empty(filterInfo.ResultFilters);
+ }
+
+ [Fact]
+ public void Constructor_PopulatesFilterCollections()
+ {
+ // Arrange
+ Mock<IActionFilter> actionFilterMock = new Mock<IActionFilter>();
+ Mock<IAuthorizationFilter> authorizationFilterMock = new Mock<IAuthorizationFilter>();
+ Mock<IExceptionFilter> exceptionFilterMock = new Mock<IExceptionFilter>();
+ Mock<IResultFilter> resultFilterMock = new Mock<IResultFilter>();
+
+ List<Filter> filters = new List<Filter>()
+ {
+ CreateFilter(actionFilterMock),
+ CreateFilter(authorizationFilterMock),
+ CreateFilter(exceptionFilterMock),
+ CreateFilter(resultFilterMock),
+ };
+
+ // Act
+ FilterInfo filterInfo = new FilterInfo(filters);
+
+ // Assert
+ Assert.Equal(actionFilterMock.Object, filterInfo.ActionFilters.SingleOrDefault());
+ Assert.Equal(authorizationFilterMock.Object, filterInfo.AuthorizationFilters.SingleOrDefault());
+ Assert.Equal(exceptionFilterMock.Object, filterInfo.ExceptionFilters.SingleOrDefault());
+ Assert.Equal(resultFilterMock.Object, filterInfo.ResultFilters.SingleOrDefault());
+ }
+
+ [Fact]
+ public void Constructor_IteratesOverFiltersOnlyOnce()
+ {
+ // Arrange
+ var filtersMock = new Mock<IEnumerable<Filter>>();
+ filtersMock.Setup(f => f.GetEnumerator()).Returns(new List<Filter>().GetEnumerator());
+
+ // Act
+ FilterInfo filterInfo = new FilterInfo(filtersMock.Object);
+
+ // Assert
+ filtersMock.Verify(f => f.GetEnumerator(), Times.Once());
+ }
+
+ private static Filter CreateFilter(Mock instanceMock)
+ {
+ return new Filter(instanceMock.Object, FilterScope.Global, null);
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Test/FilterProviderCollectionTest.cs b/test/System.Web.Mvc.Test/Test/FilterProviderCollectionTest.cs
new file mode 100644
index 00000000..46b8d117
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Test/FilterProviderCollectionTest.cs
@@ -0,0 +1,202 @@
+using System.Collections.Generic;
+using System.Linq;
+using Moq;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Mvc.Test
+{
+ public class FilterProviderCollectionTest
+ {
+ [Fact]
+ public void GuardClauses()
+ {
+ // Arrange
+ var context = new ControllerContext();
+ var descriptor = new Mock<ActionDescriptor>().Object;
+ var collection = new FilterProviderCollection();
+
+ // Act & Assert
+ Assert.ThrowsArgumentNull(
+ () => collection.GetFilters(null, descriptor),
+ "controllerContext"
+ );
+ Assert.ThrowsArgumentNull(
+ () => collection.GetFilters(context, null),
+ "actionDescriptor"
+ );
+ }
+
+ [Fact]
+ public void GetFiltersUsesRegisteredProviders()
+ {
+ // Arrange
+ var context = new ControllerContext();
+ var descriptor = new Mock<ActionDescriptor>().Object;
+ var filter = new Filter(new Object(), FilterScope.Action, null);
+ var provider = new Mock<IFilterProvider>(MockBehavior.Strict);
+ var collection = new FilterProviderCollection(new[] { provider.Object });
+ provider.Setup(p => p.GetFilters(context, descriptor)).Returns(new[] { filter });
+
+ // Act
+ IEnumerable<Filter> result = collection.GetFilters(context, descriptor);
+
+ // Assert
+ Assert.Same(filter, result.Single());
+ }
+
+ [Fact]
+ public void GetFiltersDelegatesToResolver()
+ {
+ // Arrange
+ var context = new ControllerContext();
+ var descriptor = new Mock<ActionDescriptor>().Object;
+ var filter = new Filter(new Object(), FilterScope.Action, null);
+ var provider = new Mock<IFilterProvider>(MockBehavior.Strict);
+ var resolver = new Resolver<IEnumerable<IFilterProvider>> { Current = new[] { provider.Object } };
+ var collection = new FilterProviderCollection(resolver);
+
+ provider.Setup(p => p.GetFilters(context, descriptor)).Returns(new[] { filter });
+
+ // Act
+ IEnumerable<Filter> result = collection.GetFilters(context, descriptor);
+
+ // Assert
+ Assert.Same(filter, result.Single());
+ }
+
+ [Fact]
+ public void GetFiltersSortsFiltersByOrderFirstThenScope()
+ {
+ // Arrange
+ var context = new ControllerContext();
+ var descriptor = new Mock<ActionDescriptor>().Object;
+ var actionFilter = new Filter(new Object(), FilterScope.Action, null);
+ var controllerFilter = new Filter(new Object(), FilterScope.Controller, null);
+ var globalFilter = new Filter(new Object(), FilterScope.Global, null);
+ var earlyActionFilter = new Filter(new Object(), FilterScope.Action, -100);
+ var lateGlobalFilter = new Filter(new Object(), FilterScope.Global, 100);
+ var provider = new Mock<IFilterProvider>(MockBehavior.Strict);
+ var collection = new FilterProviderCollection(new[] { provider.Object });
+ provider.Setup(p => p.GetFilters(context, descriptor))
+ .Returns(new[] { actionFilter, controllerFilter, globalFilter, earlyActionFilter, lateGlobalFilter });
+
+ // Act
+ Filter[] result = collection.GetFilters(context, descriptor).ToArray();
+
+ // Assert
+ Assert.Equal(5, result.Length);
+ Assert.Same(earlyActionFilter, result[0]);
+ Assert.Same(globalFilter, result[1]);
+ Assert.Same(controllerFilter, result[2]);
+ Assert.Same(actionFilter, result[3]);
+ Assert.Same(lateGlobalFilter, result[4]);
+ }
+
+ [AttributeUsage(AttributeTargets.All, AllowMultiple = false)]
+ private class AllowMultipleFalseAttribute : FilterAttribute
+ {
+ }
+
+ [Fact]
+ public void GetFiltersIncludesLastFilterOnlyWithAttributeUsageAllowMultipleFalse()
+ { // DDB #222988
+ // Arrange
+ var context = new ControllerContext();
+ var descriptor = new Mock<ActionDescriptor>().Object;
+ var globalFilter = new Filter(new AllowMultipleFalseAttribute(), FilterScope.Global, null);
+ var controllerFilter = new Filter(new AllowMultipleFalseAttribute(), FilterScope.Controller, null);
+ var actionFilter = new Filter(new AllowMultipleFalseAttribute(), FilterScope.Action, null);
+ var provider = new Mock<IFilterProvider>(MockBehavior.Strict);
+ var collection = new FilterProviderCollection(new[] { provider.Object });
+ provider.Setup(p => p.GetFilters(context, descriptor))
+ .Returns(new[] { controllerFilter, actionFilter, globalFilter });
+
+ // Act
+ IEnumerable<Filter> result = collection.GetFilters(context, descriptor);
+
+ // Assert
+ Assert.Same(actionFilter, result.Single());
+ }
+
+ [AttributeUsage(AttributeTargets.All, AllowMultiple = true)]
+ private class AllowMultipleTrueAttribute : FilterAttribute
+ {
+ }
+
+ [Fact]
+ public void GetFiltersIncludesAllFiltersWithAttributeUsageAllowMultipleTrue()
+ { // DDB #222988
+ // Arrange
+ var context = new ControllerContext();
+ var descriptor = new Mock<ActionDescriptor>().Object;
+ var globalFilter = new Filter(new AllowMultipleTrueAttribute(), FilterScope.Global, null);
+ var controllerFilter = new Filter(new AllowMultipleTrueAttribute(), FilterScope.Controller, null);
+ var actionFilter = new Filter(new AllowMultipleTrueAttribute(), FilterScope.Action, null);
+ var provider = new Mock<IFilterProvider>(MockBehavior.Strict);
+ var collection = new FilterProviderCollection(new[] { provider.Object });
+ provider.Setup(p => p.GetFilters(context, descriptor))
+ .Returns(new[] { controllerFilter, actionFilter, globalFilter });
+
+ // Act
+ List<Filter> result = collection.GetFilters(context, descriptor).ToList();
+
+ // Assert
+ Assert.Same(globalFilter, result[0]);
+ Assert.Same(controllerFilter, result[1]);
+ Assert.Same(actionFilter, result[2]);
+ }
+
+ private class AllowMultipleCustomFilter : MvcFilter
+ {
+ public AllowMultipleCustomFilter(bool allowMultiple)
+ : base(allowMultiple, -1)
+ {
+ }
+ }
+
+ [Fact]
+ public void GetFiltersIncludesLastFilterOnlyWithCustomFilterAllowMultipleFalse()
+ { // DDB #222988
+ // Arrange
+ var context = new ControllerContext();
+ var descriptor = new Mock<ActionDescriptor>().Object;
+ var globalFilter = new Filter(new AllowMultipleCustomFilter(false), FilterScope.Global, null);
+ var controllerFilter = new Filter(new AllowMultipleCustomFilter(false), FilterScope.Controller, null);
+ var actionFilter = new Filter(new AllowMultipleCustomFilter(false), FilterScope.Action, null);
+ var provider = new Mock<IFilterProvider>(MockBehavior.Strict);
+ var collection = new FilterProviderCollection(new[] { provider.Object });
+ provider.Setup(p => p.GetFilters(context, descriptor))
+ .Returns(new[] { controllerFilter, actionFilter, globalFilter });
+
+ // Act
+ IEnumerable<Filter> result = collection.GetFilters(context, descriptor);
+
+ // Assert
+ Assert.Same(actionFilter, result.Single());
+ }
+
+ [Fact]
+ public void GetFiltersIncludesAllFiltersWithCustomFilterAllowMultipleTrue()
+ { // DDB #222988
+ // Arrange
+ var context = new ControllerContext();
+ var descriptor = new Mock<ActionDescriptor>().Object;
+ var globalFilter = new Filter(new AllowMultipleCustomFilter(true), FilterScope.Global, null);
+ var controllerFilter = new Filter(new AllowMultipleCustomFilter(true), FilterScope.Controller, null);
+ var actionFilter = new Filter(new AllowMultipleCustomFilter(true), FilterScope.Action, null);
+ var provider = new Mock<IFilterProvider>(MockBehavior.Strict);
+ var collection = new FilterProviderCollection(new[] { provider.Object });
+ provider.Setup(p => p.GetFilters(context, descriptor))
+ .Returns(new[] { controllerFilter, actionFilter, globalFilter });
+
+ // Act
+ List<Filter> result = collection.GetFilters(context, descriptor).ToList();
+
+ // Assert
+ Assert.Same(globalFilter, result[0]);
+ Assert.Same(controllerFilter, result[1]);
+ Assert.Same(actionFilter, result[2]);
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Test/FilterProvidersTest.cs b/test/System.Web.Mvc.Test/Test/FilterProvidersTest.cs
new file mode 100644
index 00000000..cfff049a
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Test/FilterProvidersTest.cs
@@ -0,0 +1,17 @@
+using System.Linq;
+using Xunit;
+
+namespace System.Web.Mvc.Test
+{
+ public class FilterProvidersTest
+ {
+ [Fact]
+ public void DefaultFilterProviders()
+ {
+ // Assert
+ Assert.NotNull(FilterProviders.Providers.Single(fp => fp is GlobalFilterCollection));
+ Assert.NotNull(FilterProviders.Providers.Single(fp => fp is FilterAttributeFilterProvider));
+ Assert.NotNull(FilterProviders.Providers.Single(fp => fp is ControllerInstanceFilterProvider));
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Test/FilterTest.cs b/test/System.Web.Mvc.Test/Test/FilterTest.cs
new file mode 100644
index 00000000..50c5a60e
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Test/FilterTest.cs
@@ -0,0 +1,66 @@
+using Moq;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Mvc.Test
+{
+ public class FilterTest
+ {
+ [Fact]
+ public void GuardClause()
+ {
+ // Act & Assert
+ Assert.ThrowsArgumentNull(
+ () => new Filter(null, FilterScope.Action, null),
+ "instance"
+ );
+ }
+
+ [Fact]
+ public void FilterDoesNotImplementIOrderedFilter()
+ {
+ // Arrange
+ var filterInstance = new object();
+
+ // Act
+ var filter = new Filter(filterInstance, FilterScope.Action, null);
+
+ // Assert
+ Assert.Same(filterInstance, filter.Instance);
+ Assert.Equal(FilterScope.Action, filter.Scope);
+ Assert.Equal(Filter.DefaultOrder, filter.Order);
+ }
+
+ [Fact]
+ public void FilterImplementsIOrderedFilter()
+ {
+ // Arrange
+ var filterInstance = new Mock<IMvcFilter>();
+ filterInstance.SetupGet(f => f.Order).Returns(42);
+
+ // Act
+ var filter = new Filter(filterInstance.Object, FilterScope.Controller, null);
+
+ // Assert
+ Assert.Same(filterInstance.Object, filter.Instance);
+ Assert.Equal(FilterScope.Controller, filter.Scope);
+ Assert.Equal(42, filter.Order);
+ }
+
+ [Fact]
+ public void ExplicitOrderOverridesIOrderedFilter()
+ {
+ // Arrange
+ var filterInstance = new Mock<IMvcFilter>();
+ filterInstance.SetupGet(f => f.Order).Returns(42);
+
+ // Act
+ var filter = new Filter(filterInstance.Object, FilterScope.Controller, 2112);
+
+ // Assert
+ Assert.Same(filterInstance.Object, filter.Instance);
+ Assert.Equal(FilterScope.Controller, filter.Scope);
+ Assert.Equal(2112, filter.Order);
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Test/FormCollectionTest.cs b/test/System.Web.Mvc.Test/Test/FormCollectionTest.cs
new file mode 100644
index 00000000..5c8a45ff
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Test/FormCollectionTest.cs
@@ -0,0 +1,180 @@
+using System.Collections.Specialized;
+using System.Globalization;
+using Moq;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Mvc.Test
+{
+ public class FormCollectionTest
+ {
+ [Fact]
+ public void ConstructorCopiesProvidedCollection()
+ {
+ // Arrange
+ NameValueCollection nvc = new NameValueCollection()
+ {
+ { "foo", "fooValue" },
+ { "bar", "barValue" }
+ };
+
+ // Act
+ FormCollection formCollection = new FormCollection(nvc);
+
+ // Assert
+ Assert.Equal(2, formCollection.Count);
+ Assert.Equal("fooValue", formCollection["foo"]);
+ Assert.Equal("barValue", formCollection["bar"]);
+ }
+
+ [Fact]
+ public void ConstructorThrowsIfCollectionIsNull()
+ {
+ // Act & Assert
+ Assert.ThrowsArgumentNull(
+ delegate { new FormCollection(null); }, "collection");
+ }
+
+ [Fact]
+ public void ConstructorUsesValidatedValuesWhenControllerIsNull()
+ {
+ // Arrange
+ var values = new NameValueCollection()
+ {
+ { "foo", "fooValue" },
+ { "bar", "barValue" }
+ };
+
+ // Act
+ var result = new FormCollection(controller: null,
+ validatedValuesThunk: () => values,
+ unvalidatedValuesThunk: () => { throw new NotImplementedException(); });
+
+ // Assert
+ Assert.Equal(2, result.Count);
+ Assert.Equal("fooValue", result["foo"]);
+ Assert.Equal("barValue", result["bar"]);
+ }
+
+ [Fact]
+ public void ConstructorUsesValidatedValuesWhenControllerValidateRequestIsTrue()
+ {
+ // Arrange
+ var values = new NameValueCollection()
+ {
+ { "foo", "fooValue" },
+ { "bar", "barValue" }
+ };
+ var controller = new Mock<ControllerBase>().Object;
+ controller.ValidateRequest = true;
+
+ // Act
+ var result = new FormCollection(controller,
+ validatedValuesThunk: () => values,
+ unvalidatedValuesThunk: () => { throw new NotImplementedException(); });
+
+ // Assert
+ Assert.Equal(2, result.Count);
+ Assert.Equal("fooValue", result["foo"]);
+ Assert.Equal("barValue", result["bar"]);
+ }
+
+ [Fact]
+ public void ConstructorUsesUnvalidatedValuesWhenControllerValidateRequestIsFalse()
+ {
+ // Arrange
+ var values = new NameValueCollection()
+ {
+ { "foo", "fooValue" },
+ { "bar", "barValue" }
+ };
+ var controller = new Mock<ControllerBase>().Object;
+ controller.ValidateRequest = false;
+
+ // Act
+ var result = new FormCollection(controller,
+ validatedValuesThunk: () => { throw new NotImplementedException(); },
+ unvalidatedValuesThunk: () => values);
+
+ // Assert
+ Assert.Equal(2, result.Count);
+ Assert.Equal("fooValue", result["foo"]);
+ Assert.Equal("barValue", result["bar"]);
+ }
+
+ [Fact]
+ public void CustomBinderBindModelReturnsFormCollection()
+ {
+ // Arrange
+ NameValueCollection nvc = new NameValueCollection() { { "foo", "fooValue" }, { "bar", "barValue" } };
+ IModelBinder binder = ModelBinders.Binders.GetBinder(typeof(FormCollection));
+
+ Mock<ControllerContext> mockControllerContext = new Mock<ControllerContext>();
+ mockControllerContext.Setup(c => c.HttpContext.Request.Form).Returns(nvc);
+
+ // Act
+ FormCollection formCollection = (FormCollection)binder.BindModel(mockControllerContext.Object, null);
+
+ // Assert
+ Assert.NotNull(formCollection);
+ Assert.Equal(2, formCollection.Count);
+ Assert.Equal("fooValue", nvc["foo"]);
+ Assert.Equal("barValue", nvc["bar"]);
+ }
+
+ [Fact]
+ public void CustomBinderBindModelThrowsIfControllerContextIsNull()
+ {
+ // Arrange
+ IModelBinder binder = ModelBinders.Binders.GetBinder(typeof(FormCollection));
+
+ // Act & assert
+ Assert.ThrowsArgumentNull(
+ delegate { binder.BindModel(null, null); }, "controllerContext");
+ }
+
+ [Fact]
+ public void GetValue_ThrowsIfNameIsNull()
+ {
+ // Arrange
+ FormCollection formCollection = new FormCollection();
+
+ // Act & assert
+ Assert.ThrowsArgumentNull(
+ delegate { formCollection.GetValue(null); }, "name");
+ }
+
+ [Fact]
+ public void GetValue_KeyDoesNotExist_ReturnsNull()
+ {
+ // Arrange
+ FormCollection formCollection = new FormCollection();
+
+ // Act
+ ValueProviderResult vpResult = formCollection.GetValue("");
+
+ // Assert
+ Assert.Null(vpResult);
+ }
+
+ [Fact]
+ public void GetValue_KeyExists_ReturnsResult()
+ {
+ // Arrange
+ FormCollection formCollection = new FormCollection()
+ {
+ { "foo", "1" },
+ { "foo", "2" }
+ };
+
+ // Act
+ ValueProviderResult vpResult = formCollection.GetValue("foo");
+
+ // Assert
+ Assert.NotNull(vpResult);
+ Assert.Equal(new[] { "1", "2" }, (string[])vpResult.RawValue);
+ Assert.Equal("1,2", vpResult.AttemptedValue);
+ Assert.Equal(CultureInfo.CurrentCulture, vpResult.Culture);
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Test/FormContextTest.cs b/test/System.Web.Mvc.Test/Test/FormContextTest.cs
new file mode 100644
index 00000000..4551d967
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Test/FormContextTest.cs
@@ -0,0 +1,172 @@
+using System.Collections.Generic;
+using System.Web.TestUtil;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Mvc.Test
+{
+ public class FormContextTest
+ {
+ [Fact]
+ public void FieldValidatorsProperty()
+ {
+ // Arrange
+ FormContext context = new FormContext();
+
+ // Act
+ IDictionary<String, FieldValidationMetadata> fieldValidators = context.FieldValidators;
+
+ // Assert
+ Assert.NotNull(fieldValidators);
+ Assert.Empty(fieldValidators);
+ }
+
+ [Fact]
+ public void ReplaceValidationSummaryProperty()
+ {
+ // Arrange
+ FormContext context = new FormContext();
+
+ // Act & Assert
+ MemberHelper.TestBooleanProperty(context, "ReplaceValidationSummary", false, false);
+ }
+
+ [Fact]
+ public void GetJsonValidationMetadata_NoValidationSummary()
+ {
+ // Arrange
+ FormContext context = new FormContext() { FormId = "theFormId" };
+
+ ModelClientValidationRule rule = new ModelClientValidationRule() { ValidationType = "ValidationType1", ErrorMessage = "Error Message" };
+ rule.ValidationParameters["theParam"] = new { FirstName = "John", LastName = "Doe", Age = 32 };
+ FieldValidationMetadata metadata = new FieldValidationMetadata() { FieldName = "theFieldName", ValidationMessageId = "theFieldName_ValidationMessage" };
+ metadata.ValidationRules.Add(rule);
+ context.FieldValidators["theFieldName"] = metadata;
+
+ // Act
+ string jsonMetadata = context.GetJsonValidationMetadata();
+
+ // Assert
+ string expected = @"{""Fields"":[{""FieldName"":""theFieldName"",""ReplaceValidationMessageContents"":false,""ValidationMessageId"":""theFieldName_ValidationMessage"",""ValidationRules"":[{""ErrorMessage"":""Error Message"",""ValidationParameters"":{""theParam"":{""FirstName"":""John"",""LastName"":""Doe"",""Age"":32}},""ValidationType"":""ValidationType1""}]}],""FormId"":""theFormId"",""ReplaceValidationSummary"":false}";
+ Assert.Equal(expected, jsonMetadata);
+ }
+
+ [Fact]
+ public void GetJsonValidationMetadata_ValidationSummary()
+ {
+ // Arrange
+ FormContext context = new FormContext() { FormId = "theFormId", ValidationSummaryId = "validationSummary" };
+
+ ModelClientValidationRule rule = new ModelClientValidationRule() { ValidationType = "ValidationType1", ErrorMessage = "Error Message" };
+ rule.ValidationParameters["theParam"] = new { FirstName = "John", LastName = "Doe", Age = 32 };
+ FieldValidationMetadata metadata = new FieldValidationMetadata() { FieldName = "theFieldName", ValidationMessageId = "theFieldName_ValidationMessage" };
+ metadata.ValidationRules.Add(rule);
+ context.FieldValidators["theFieldName"] = metadata;
+
+ // Act
+ string jsonMetadata = context.GetJsonValidationMetadata();
+
+ // Assert
+ string expected = @"{""Fields"":[{""FieldName"":""theFieldName"",""ReplaceValidationMessageContents"":false,""ValidationMessageId"":""theFieldName_ValidationMessage"",""ValidationRules"":[{""ErrorMessage"":""Error Message"",""ValidationParameters"":{""theParam"":{""FirstName"":""John"",""LastName"":""Doe"",""Age"":32}},""ValidationType"":""ValidationType1""}]}],""FormId"":""theFormId"",""ReplaceValidationSummary"":false,""ValidationSummaryId"":""validationSummary""}";
+ Assert.Equal(expected, jsonMetadata);
+ }
+
+ [Fact]
+ public void GetValidationMetadataForField_Create_CreatesNewMetadataIfNotFound()
+ {
+ // Arrange
+ FormContext context = new FormContext();
+
+ // Act
+ FieldValidationMetadata result = context.GetValidationMetadataForField("fieldName", true /* createIfNotFound */);
+
+ // Assert
+ Assert.NotNull(result);
+ Assert.Equal("fieldName", result.FieldName);
+
+ Assert.Single(context.FieldValidators);
+ Assert.Equal(result, context.FieldValidators["fieldName"]);
+ }
+
+ [Fact]
+ public void GetValidationMetadataForField_NoCreate_ReturnsMetadataIfFound()
+ {
+ // Arrange
+ FormContext context = new FormContext();
+ FieldValidationMetadata metadata = new FieldValidationMetadata();
+ context.FieldValidators["fieldName"] = metadata;
+
+ // Act
+ FieldValidationMetadata result = context.GetValidationMetadataForField("fieldName");
+
+ // Assert
+ Assert.NotNull(result);
+ Assert.Equal(metadata, result);
+ }
+
+ [Fact]
+ public void GetValidationMetadataForField_NoCreate_ReturnsNullIfNotFound()
+ {
+ // Arrange
+ FormContext context = new FormContext();
+
+ // Act
+ FieldValidationMetadata result = context.GetValidationMetadataForField("fieldName");
+
+ // Assert
+ Assert.Null(result);
+ }
+
+ [Fact]
+ public void GetValidationMetadataForFieldThrowsIfFieldNameIsEmpty()
+ {
+ // Arrange
+ FormContext context = new FormContext();
+
+ // Act & assert
+ Assert.ThrowsArgumentNullOrEmpty(
+ delegate { context.GetValidationMetadataForField(String.Empty); }, "fieldName");
+ }
+
+ [Fact]
+ public void GetValidationMetadataForFieldThrowsIfFieldNameIsNull()
+ {
+ // Arrange
+ FormContext context = new FormContext();
+
+ // Act & assert
+ Assert.ThrowsArgumentNullOrEmpty(
+ delegate { context.GetValidationMetadataForField(null); }, "fieldName");
+ }
+
+ // RenderedField
+
+ [Fact]
+ public void RenderedFieldIsFalseByDefault()
+ {
+ // Arrange
+ var context = new FormContext();
+
+ // Act
+ bool result = context.RenderedField(Guid.NewGuid().ToString());
+
+ // Assert
+ Assert.False(result);
+ }
+
+ [Fact]
+ public void CanSetRenderedFieldToBeTrue()
+ {
+ // Arrange
+ var context = new FormContext();
+ var name = Guid.NewGuid().ToString();
+ context.RenderedField(name, true);
+
+ // Act
+ bool result = context.RenderedField(name);
+
+ // Assert
+ Assert.True(result);
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Test/FormValueProviderFactoryTest.cs b/test/System.Web.Mvc.Test/Test/FormValueProviderFactoryTest.cs
new file mode 100644
index 00000000..1bbe2ca0
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Test/FormValueProviderFactoryTest.cs
@@ -0,0 +1,77 @@
+using System.Collections.Specialized;
+using System.Globalization;
+using Moq;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Mvc.Test
+{
+ public class FormValueProviderFactoryTest
+ {
+ private static readonly NameValueCollection _backingStore = new NameValueCollection()
+ {
+ { "foo", "fooValue" }
+ };
+
+ private static readonly NameValueCollection _unvalidatedBackingStore = new NameValueCollection()
+ {
+ { "foo", "fooUnvalidated" }
+ };
+
+ [Fact]
+ public void GetValueProvider()
+ {
+ // Arrange
+ Mock<MockableUnvalidatedRequestValues> mockUnvalidatedValues = new Mock<MockableUnvalidatedRequestValues>();
+ FormValueProviderFactory factory = new FormValueProviderFactory(_ => mockUnvalidatedValues.Object);
+
+ Mock<ControllerContext> mockControllerContext = new Mock<ControllerContext>();
+ mockControllerContext.Setup(o => o.HttpContext.Request.Form).Returns(_backingStore);
+
+ // Act
+ IValueProvider valueProvider = factory.GetValueProvider(mockControllerContext.Object);
+
+ // Assert
+ Assert.Equal(typeof(FormValueProvider), valueProvider.GetType());
+ ValueProviderResult vpResult = valueProvider.GetValue("foo");
+
+ Assert.NotNull(vpResult);
+ Assert.Equal("fooValue", vpResult.AttemptedValue);
+ Assert.Equal(CultureInfo.CurrentCulture, vpResult.Culture);
+ }
+
+ [Fact]
+ public void GetValueProvider_GetValue_SkipValidation()
+ {
+ // Arrange
+ Mock<MockableUnvalidatedRequestValues> mockUnvalidatedValues = new Mock<MockableUnvalidatedRequestValues>();
+ mockUnvalidatedValues.Setup(o => o.Form).Returns(_unvalidatedBackingStore);
+ FormValueProviderFactory factory = new FormValueProviderFactory(_ => mockUnvalidatedValues.Object);
+
+ Mock<ControllerContext> mockControllerContext = new Mock<ControllerContext>();
+ mockControllerContext.Setup(o => o.HttpContext.Request.Form).Returns(_backingStore);
+
+ // Act
+ IUnvalidatedValueProvider valueProvider = (IUnvalidatedValueProvider)factory.GetValueProvider(mockControllerContext.Object);
+
+ // Assert
+ Assert.Equal(typeof(FormValueProvider), valueProvider.GetType());
+ ValueProviderResult vpResult = valueProvider.GetValue("foo", skipValidation: true);
+
+ Assert.NotNull(vpResult);
+ Assert.Equal("fooUnvalidated", vpResult.AttemptedValue);
+ Assert.Equal(CultureInfo.CurrentCulture, vpResult.Culture);
+ }
+
+ [Fact]
+ public void GetValueProvider_ThrowsIfControllerContextIsNull()
+ {
+ // Arrange
+ FormValueProviderFactory factory = new FormValueProviderFactory();
+
+ // Act & assert
+ Assert.ThrowsArgumentNull(
+ delegate { factory.GetValueProvider(null); }, "controllerContext");
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Test/GlobalFilterCollectionTest.cs b/test/System.Web.Mvc.Test/Test/GlobalFilterCollectionTest.cs
new file mode 100644
index 00000000..44d2455c
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Test/GlobalFilterCollectionTest.cs
@@ -0,0 +1,92 @@
+using System.Collections.Generic;
+using Moq;
+using Xunit;
+
+namespace System.Web.Mvc.Test
+{
+ public class GlobalFilterCollectionTest
+ {
+ [Fact]
+ public void AddPlacesFilterInGlobalScope()
+ {
+ // Arrange
+ var filterInstance = new object();
+ var collection = new GlobalFilterCollection();
+
+ // Act
+ collection.Add(filterInstance);
+
+ // Assert
+ Filter filter = Assert.Single(collection);
+ Assert.Same(filterInstance, filter.Instance);
+ Assert.Equal(FilterScope.Global, filter.Scope);
+ Assert.Equal(-1, filter.Order);
+ }
+
+ [Fact]
+ public void AddWithOrderPlacesFilterInGlobalScope()
+ {
+ // Arrange
+ var filterInstance = new object();
+ var collection = new GlobalFilterCollection();
+
+ // Act
+ collection.Add(filterInstance, 42);
+
+ // Assert
+ Filter filter = Assert.Single(collection);
+ Assert.Same(filterInstance, filter.Instance);
+ Assert.Equal(FilterScope.Global, filter.Scope);
+ Assert.Equal(42, filter.Order);
+ }
+
+ [Fact]
+ public void ContainsFindsFilterByInstance()
+ {
+ // Arrange
+ var filterInstance = new object();
+ var collection = new GlobalFilterCollection();
+ collection.Add(filterInstance);
+
+ // Act
+ bool result = collection.Contains(filterInstance);
+
+ // Assert
+ Assert.True(result);
+ }
+
+ [Fact]
+ public void RemoveDeletesFilterByInstance()
+ {
+ // Arrange
+ var filterInstance = new object();
+ var collection = new GlobalFilterCollection();
+ collection.Add(filterInstance);
+
+ // Act
+ collection.Remove(filterInstance);
+
+ // Assert
+ Assert.Empty(collection);
+ }
+
+ [Fact]
+ public void CollectionIsIFilterProviderWhichReturnsAllFilters()
+ {
+ // Arrange
+ var context = new ControllerContext();
+ var descriptor = new Mock<ActionDescriptor>().Object;
+ var filterInstance = new object();
+ var collection = new GlobalFilterCollection();
+ collection.Add(filterInstance);
+ var provider = (IFilterProvider)collection;
+
+ // Act
+ IEnumerable<Filter> result = provider.GetFilters(context, descriptor);
+
+ // Assert
+ Filter filter = Assert.Single(result);
+ Assert.Same(filterInstance, filter.Instance);
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Test/HandleErrorAttributeTest.cs b/test/System.Web.Mvc.Test/Test/HandleErrorAttributeTest.cs
new file mode 100644
index 00000000..59ee5638
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Test/HandleErrorAttributeTest.cs
@@ -0,0 +1,251 @@
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Linq;
+using System.Web.Routing;
+using System.Web.TestUtil;
+using Moq;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Mvc.Test
+{
+ public class HandleErrorAttributeTest
+ {
+ [Fact]
+ public void HandleErrorAttributeReturnsUniqueTypeIDs()
+ {
+ // Arrange
+ HandleErrorAttribute attr1 = new HandleErrorAttribute();
+ HandleErrorAttribute attr2 = new HandleErrorAttribute();
+
+ // Assert
+ Assert.NotEqual(attr1.TypeId, attr2.TypeId);
+ }
+
+ [HandleError(View = "foo")]
+ [HandleError(View = "bar")]
+ private class ClassWithMultipleHandleErrorAttributes
+ {
+ }
+
+ [Fact]
+ public void CanRetrieveMultipleAuthorizeAttributesFromOneClass()
+ {
+ // Arrange
+ ClassWithMultipleHandleErrorAttributes @class = new ClassWithMultipleHandleErrorAttributes();
+
+ // Act
+ IEnumerable<HandleErrorAttribute> attributes = TypeDescriptor.GetAttributes(@class).OfType<HandleErrorAttribute>();
+
+ // Assert
+ Assert.Equal(2, attributes.Count());
+ Assert.True(attributes.Any(a => a.View == "foo"));
+ Assert.True(attributes.Any(a => a.View == "bar"));
+ }
+
+ [Fact]
+ public void ExceptionTypeProperty()
+ {
+ // Arrange
+ HandleErrorAttribute attr = new HandleErrorAttribute();
+
+ // Act
+ Type origType = attr.ExceptionType;
+ attr.ExceptionType = typeof(SystemException);
+ Type newType = attr.ExceptionType;
+
+ // Assert
+ Assert.Equal(typeof(Exception), origType);
+ Assert.Equal(typeof(SystemException), attr.ExceptionType);
+ }
+
+ [Fact]
+ public void ExceptionTypePropertyWithNonExceptionTypeThrows()
+ {
+ // Arrange
+ HandleErrorAttribute attr = new HandleErrorAttribute();
+
+ // Act & Assert
+ Assert.Throws<ArgumentException>(
+ delegate { attr.ExceptionType = typeof(string); },
+ "The type 'System.String' does not inherit from Exception.");
+ }
+
+ [Fact]
+ public void ExceptionTypePropertyWithNullValueThrows()
+ {
+ // Arrange
+ HandleErrorAttribute attr = new HandleErrorAttribute();
+
+ // Act & Assert
+ Assert.ThrowsArgumentNull(
+ delegate { attr.ExceptionType = null; }, "value");
+ }
+
+ [Fact]
+ public void MasterProperty()
+ {
+ // Arrange
+ HandleErrorAttribute attr = new HandleErrorAttribute();
+
+ // Act & Assert
+ MemberHelper.TestStringProperty(attr, "Master", String.Empty);
+ }
+
+ [Fact]
+ public void OnException()
+ {
+ // Arrange
+ HandleErrorAttribute attr = new HandleErrorAttribute()
+ {
+ View = "SomeView",
+ Master = "SomeMaster",
+ ExceptionType = typeof(ArgumentException)
+ };
+ Exception exception = new ArgumentNullException();
+
+ Mock<HttpContextBase> mockHttpContext = new Mock<HttpContextBase>(MockBehavior.Strict);
+ mockHttpContext.Setup(c => c.IsCustomErrorEnabled).Returns(true);
+ mockHttpContext.Setup(c => c.Session).Returns((HttpSessionStateBase)null);
+ mockHttpContext.Setup(c => c.Response.Clear()).Verifiable();
+ mockHttpContext.SetupSet(c => c.Response.StatusCode = 500).Verifiable();
+ mockHttpContext.SetupSet(c => c.Response.TrySkipIisCustomErrors = true).Verifiable();
+
+ TempDataDictionary tempData = new TempDataDictionary();
+ IViewEngine viewEngine = new Mock<IViewEngine>().Object;
+ Controller controller = new Mock<Controller>().Object;
+ controller.TempData = tempData;
+
+ ExceptionContext context = GetExceptionContext(mockHttpContext.Object, controller, exception);
+
+ // Exception
+ attr.OnException(context);
+
+ // Assert
+ mockHttpContext.Verify();
+ ViewResult viewResult = context.Result as ViewResult;
+ Assert.NotNull(viewResult);
+ Assert.Equal(tempData, viewResult.TempData);
+ Assert.Equal("SomeView", viewResult.ViewName);
+ Assert.Equal("SomeMaster", viewResult.MasterName);
+
+ HandleErrorInfo viewData = viewResult.ViewData.Model as HandleErrorInfo;
+ Assert.NotNull(viewData);
+ Assert.Same(exception, viewData.Exception);
+ Assert.Equal("SomeController", viewData.ControllerName);
+ Assert.Equal("SomeAction", viewData.ActionName);
+ }
+
+ [Fact]
+ public void OnExceptionWithCustomErrorsDisabledDoesNothing()
+ {
+ // Arrange
+ HandleErrorAttribute attr = new HandleErrorAttribute();
+ ActionResult result = new EmptyResult();
+ ExceptionContext context = GetExceptionContext(GetHttpContext(false), null, new Exception());
+ context.Result = result;
+
+ // Exception
+ attr.OnException(context);
+
+ // Assert
+ Assert.Same(result, context.Result);
+ }
+
+ [Fact]
+ public void OnExceptionWithExceptionHandledDoesNothing()
+ {
+ // Arrange
+ HandleErrorAttribute attr = new HandleErrorAttribute();
+ ActionResult result = new EmptyResult();
+ ExceptionContext context = GetExceptionContext(GetHttpContext(), null, new Exception());
+ context.Result = result;
+ context.ExceptionHandled = true;
+
+ // Exception
+ attr.OnException(context);
+
+ // Assert
+ Assert.Same(result, context.Result);
+ }
+
+ [Fact]
+ public void OnExceptionWithNon500ExceptionDoesNothing()
+ {
+ // Arrange
+ HandleErrorAttribute attr = new HandleErrorAttribute();
+ ActionResult result = new EmptyResult();
+ ExceptionContext context = GetExceptionContext(GetHttpContext(), null, new HttpException(404, "Some Exception"));
+ context.Result = result;
+
+ // Exception
+ attr.OnException(context);
+
+ // Assert
+ Assert.Same(result, context.Result);
+ }
+
+ [Fact]
+ public void OnExceptionWithNullFilterContextThrows()
+ {
+ // Arrange
+ HandleErrorAttribute attr = new HandleErrorAttribute();
+
+ // Act & Assert
+ Assert.ThrowsArgumentNull(
+ delegate { attr.OnException(null /* filterContext */); }, "filterContext");
+ }
+
+ [Fact]
+ public void OnExceptionWithWrongExceptionTypeDoesNothing()
+ {
+ // Arrange
+ HandleErrorAttribute attr = new HandleErrorAttribute() { ExceptionType = typeof(ArgumentException) };
+ ActionResult result = new EmptyResult();
+ ExceptionContext context = GetExceptionContext(GetHttpContext(), null, new InvalidCastException());
+ context.Result = result;
+
+ // Exception
+ attr.OnException(context);
+
+ // Assert
+ Assert.Same(result, context.Result);
+ }
+
+ [Fact]
+ public void ViewProperty()
+ {
+ // Arrange
+ HandleErrorAttribute attr = new HandleErrorAttribute();
+
+ // Act & Assert
+ MemberHelper.TestStringProperty(attr, "View", "Error", nullAndEmptyReturnValue: "Error");
+ }
+
+ private static ExceptionContext GetExceptionContext(HttpContextBase httpContext, ControllerBase controller, Exception exception)
+ {
+ RouteData rd = new RouteData();
+ rd.Values["controller"] = "SomeController";
+ rd.Values["action"] = "SomeAction";
+
+ Mock<ExceptionContext> mockExceptionContext = new Mock<ExceptionContext>();
+ mockExceptionContext.Setup(c => c.Controller).Returns(controller);
+ mockExceptionContext.Setup(c => c.Exception).Returns(exception);
+ mockExceptionContext.Setup(c => c.RouteData).Returns(rd);
+ mockExceptionContext.Setup(c => c.HttpContext).Returns(httpContext);
+ return mockExceptionContext.Object;
+ }
+
+ private static HttpContextBase GetHttpContext()
+ {
+ return GetHttpContext(true);
+ }
+
+ private static HttpContextBase GetHttpContext(bool isCustomErrorEnabled)
+ {
+ Mock<HttpContextBase> mockContext = new Mock<HttpContextBase>(MockBehavior.Strict);
+ mockContext.Setup(c => c.IsCustomErrorEnabled).Returns(isCustomErrorEnabled);
+ return mockContext.Object;
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Test/HandleErrorInfoTest.cs b/test/System.Web.Mvc.Test/Test/HandleErrorInfoTest.cs
new file mode 100644
index 00000000..e68b7d94
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Test/HandleErrorInfoTest.cs
@@ -0,0 +1,76 @@
+using Moq;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Mvc.Test
+{
+ public class HandleErrorInfoTest
+ {
+ [Fact]
+ public void ConstructorSetsProperties()
+ {
+ // Arrange
+ Exception exception = new Exception();
+ string controller = "SomeController";
+ string action = "SomeAction";
+
+ // Act
+ HandleErrorInfo viewData = new HandleErrorInfo(exception, controller, action);
+
+ // Assert
+ Assert.Same(exception, viewData.Exception);
+ Assert.Equal(controller, viewData.ControllerName);
+ Assert.Equal(action, viewData.ActionName);
+ }
+
+ [Fact]
+ public void ConstructorWithEmptyActionThrows()
+ {
+ Assert.ThrowsArgumentNullOrEmpty(
+ delegate { new HandleErrorInfo(new Exception(), "SomeController", String.Empty); }, "actionName");
+ }
+
+ [Fact]
+ public void ConstructorWithEmptyControllerThrows()
+ {
+ Assert.ThrowsArgumentNullOrEmpty(
+ delegate { new HandleErrorInfo(new Exception(), String.Empty, "SomeAction"); }, "controllerName");
+ }
+
+ [Fact]
+ public void ConstructorWithNullActionThrows()
+ {
+ Assert.ThrowsArgumentNullOrEmpty(
+ delegate { new HandleErrorInfo(new Exception(), "SomeController", null /* action */); }, "actionName");
+ }
+
+ [Fact]
+ public void ConstructorWithNullControllerThrows()
+ {
+ Assert.ThrowsArgumentNullOrEmpty(
+ delegate { new HandleErrorInfo(new Exception(), null /* controller */, "SomeAction"); }, "controllerName");
+ }
+
+ [Fact]
+ public void ConstructorWithNullExceptionThrows()
+ {
+ Assert.ThrowsArgumentNull(
+ delegate { new HandleErrorInfo(null /* exception */, "SomeController", "SomeAction"); }, "exception");
+ }
+
+ [Fact]
+ public void ErrorHandlingDoesNotFireIfCalledInChildAction()
+ {
+ // Arrange
+ HandleErrorAttribute attr = new HandleErrorAttribute();
+ Mock<ExceptionContext> context = new Mock<ExceptionContext>();
+ context.Setup(c => c.IsChildAction).Returns(true);
+
+ // Act
+ attr.OnException(context.Object);
+
+ // Assert
+ Assert.IsType<EmptyResult>(context.Object.Result);
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Test/HtmlHelperTest.cs b/test/System.Web.Mvc.Test/Test/HtmlHelperTest.cs
new file mode 100644
index 00000000..1e577dc8
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Test/HtmlHelperTest.cs
@@ -0,0 +1,1172 @@
+using System.Collections.Generic;
+using System.ComponentModel.DataAnnotations;
+using System.Globalization;
+using System.IO;
+using System.Linq;
+using System.Threading;
+using System.Web.Routing;
+using Microsoft.Web.UnitTestUtil;
+using Moq;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Mvc.Test
+{
+ public class HtmlHelperTest
+ {
+ public static readonly RouteValueDictionary AttributesDictionary = new RouteValueDictionary(new { baz = "BazValue" });
+ public static readonly object AttributesObjectDictionary = new { baz = "BazObjValue" };
+ public static readonly object AttributesObjectUnderscoresDictionary = new { foo_baz = "BazObjValue" };
+
+ // Constructor
+
+ [Fact]
+ public void ConstructorGuardClauses()
+ {
+ // Arrange
+ var viewContext = new Mock<ViewContext>().Object;
+ var viewDataContainer = MvcHelper.GetViewDataContainer(null);
+
+ // Act & Assert
+ Assert.ThrowsArgumentNull(
+ () => new HtmlHelper(null, viewDataContainer),
+ "viewContext"
+ );
+ Assert.ThrowsArgumentNull(
+ () => new HtmlHelper(viewContext, null),
+ "viewDataContainer"
+ );
+ Assert.ThrowsArgumentNull(
+ () => new HtmlHelper(viewContext, viewDataContainer, null),
+ "routeCollection"
+ );
+ }
+
+ [Fact]
+ public void PropertiesAreSet()
+ {
+ // Arrange
+ var viewContext = new Mock<ViewContext>().Object;
+ var viewData = new ViewDataDictionary<String>("The Model");
+ var routes = new RouteCollection();
+ var mockViewDataContainer = new Mock<IViewDataContainer>();
+ mockViewDataContainer.Setup(vdc => vdc.ViewData).Returns(viewData);
+
+ // Act
+ var htmlHelper = new HtmlHelper(viewContext, mockViewDataContainer.Object, routes);
+
+ // Assert
+ Assert.Equal(viewContext, htmlHelper.ViewContext);
+ Assert.Equal(mockViewDataContainer.Object, htmlHelper.ViewDataContainer);
+ Assert.Equal(routes, htmlHelper.RouteCollection);
+ Assert.Equal(viewData.Model, htmlHelper.ViewData.Model);
+ }
+
+ [Fact]
+ public void DefaultRouteCollectionIsRouteTableRoutes()
+ {
+ // Arrange
+ var viewContext = new Mock<ViewContext>().Object;
+ var viewDataContainer = new Mock<IViewDataContainer>().Object;
+
+ // Act
+ var htmlHelper = new HtmlHelper(viewContext, viewDataContainer);
+
+ // Assert
+ Assert.Equal(RouteTable.Routes, htmlHelper.RouteCollection);
+ }
+
+ // AnonymousObjectToHtmlAttributes tests
+
+ [Fact]
+ public void ConvertsUnderscoresInNamesToDashes()
+ {
+ // Arrange
+ var attributes = new { foo = "Bar", baz_bif = "pow_wow" };
+
+ // Act
+ RouteValueDictionary result = HtmlHelper.AnonymousObjectToHtmlAttributes(attributes);
+
+ // Assert
+ Assert.Equal(2, result.Count);
+ Assert.Equal("Bar", result["foo"]);
+ Assert.Equal("pow_wow", result["baz-bif"]);
+ }
+
+ // AttributeEncode
+
+ [Fact]
+ public void AttributeEncodeObject()
+ {
+ // Arrange
+ HtmlHelper htmlHelper = MvcHelper.GetHtmlHelper();
+
+ // Act
+ string encodedHtml = htmlHelper.AttributeEncode((object)@"<"">");
+
+ // Assert
+ Assert.Equal(encodedHtml, "&lt;&quot;>");
+ }
+
+ [Fact]
+ public void AttributeEncodeObjectNull()
+ {
+ // Arrange
+ HtmlHelper htmlHelper = MvcHelper.GetHtmlHelper();
+
+ // Act
+ string encodedHtml = htmlHelper.AttributeEncode((object)null);
+
+ // Assert
+ Assert.Equal("", encodedHtml);
+ }
+
+ [Fact]
+ public void AttributeEncodeString()
+ {
+ // Arrange
+ HtmlHelper htmlHelper = MvcHelper.GetHtmlHelper();
+
+ // Act
+ string encodedHtml = htmlHelper.AttributeEncode(@"<"">");
+
+ // Assert
+ Assert.Equal(encodedHtml, "&lt;&quot;>");
+ }
+
+ [Fact]
+ public void AttributeEncodeStringNull()
+ {
+ // Arrange
+ HtmlHelper htmlHelper = MvcHelper.GetHtmlHelper();
+
+ // Act
+ string encodedHtml = htmlHelper.AttributeEncode((string)null);
+
+ // Assert
+ Assert.Equal("", encodedHtml);
+ }
+
+ // EnableClientValidation
+
+ [Fact]
+ public void EnableClientValidation()
+ {
+ // Arrange
+ var mockViewContext = new Mock<ViewContext>();
+ var viewDataContainer = new Mock<IViewDataContainer>().Object;
+ var htmlHelper = new HtmlHelper(mockViewContext.Object, viewDataContainer);
+
+ // Act
+ htmlHelper.EnableClientValidation();
+
+ // Act & assert
+ mockViewContext.VerifySet(vc => vc.ClientValidationEnabled = true);
+ }
+
+ // EnableUnobtrusiveJavaScript
+
+ [Fact]
+ public void EnableUnobtrusiveJavaScript()
+ {
+ // Arrange
+ var mockViewContext = new Mock<ViewContext>();
+ var viewDataContainer = new Mock<IViewDataContainer>().Object;
+ var htmlHelper = new HtmlHelper(mockViewContext.Object, viewDataContainer);
+
+ // Act
+ htmlHelper.EnableUnobtrusiveJavaScript();
+
+ // Act & assert
+ mockViewContext.VerifySet(vc => vc.UnobtrusiveJavaScriptEnabled = true);
+ }
+
+ // Encode
+
+ [Fact]
+ public void EncodeObject()
+ {
+ // Arrange
+ HtmlHelper htmlHelper = MvcHelper.GetHtmlHelper();
+
+ // Act
+ string encodedHtml = htmlHelper.Encode((object)"<br />");
+
+ // Assert
+ Assert.Equal(encodedHtml, "&lt;br /&gt;");
+ }
+
+ [Fact]
+ public void EncodeObjectNull()
+ {
+ // Arrange
+ HtmlHelper htmlHelper = MvcHelper.GetHtmlHelper();
+
+ // Act
+ string encodedHtml = htmlHelper.Encode((object)null);
+
+ // Assert
+ Assert.Equal("", encodedHtml);
+ }
+
+ [Fact]
+ public void EncodeString()
+ {
+ // Arrange
+ HtmlHelper htmlHelper = MvcHelper.GetHtmlHelper();
+
+ // Act
+ string encodedHtml = htmlHelper.Encode("<br />");
+
+ // Assert
+ Assert.Equal(encodedHtml, "&lt;br /&gt;");
+ }
+
+ [Fact]
+ public void EncodeStringNull()
+ {
+ // Arrange
+ HtmlHelper htmlHelper = MvcHelper.GetHtmlHelper();
+
+ // Act
+ string encodedHtml = htmlHelper.Encode((string)null);
+
+ // Assert
+ Assert.Equal("", encodedHtml);
+ }
+
+ // GetModelStateValue
+
+ [Fact]
+ public void GetModelStateValueReturnsNullIfModelStateHasNoValue()
+ {
+ // Arrange
+ ViewDataDictionary vdd = new ViewDataDictionary();
+ vdd.ModelState.AddModelError("foo", "some error text"); // didn't call SetModelValue()
+
+ HtmlHelper helper = new HtmlHelper(new ViewContext(), new SimpleViewDataContainer(vdd));
+
+ // Act
+ object retVal = helper.GetModelStateValue("foo", typeof(object));
+
+ // Assert
+ Assert.Null(retVal);
+ }
+
+ [Fact]
+ public void GetModelStateValueReturnsNullIfModelStateKeyNotPresent()
+ {
+ // Arrange
+ ViewDataDictionary vdd = new ViewDataDictionary();
+ HtmlHelper helper = new HtmlHelper(new ViewContext(), new SimpleViewDataContainer(vdd));
+
+ // Act
+ object retVal = helper.GetModelStateValue("key_not_present", typeof(object));
+
+ // Assert
+ Assert.Null(retVal);
+ }
+
+ // GenerateIdFromName
+
+ [Fact]
+ public void GenerateIdFromNameTests()
+ {
+ // Guard clauses
+ Assert.ThrowsArgumentNull(
+ () => HtmlHelper.GenerateIdFromName(null),
+ "name"
+ );
+ Assert.ThrowsArgumentNull(
+ () => HtmlHelper.GenerateIdFromName(null, "?"),
+ "name"
+ );
+ Assert.ThrowsArgumentNull(
+ () => HtmlHelper.GenerateIdFromName("?", null),
+ "idAttributeDotReplacement"
+ );
+
+ // Default replacement tests
+ Assert.Equal("", HtmlHelper.GenerateIdFromName(""));
+ Assert.Equal("Foo", HtmlHelper.GenerateIdFromName("Foo"));
+ Assert.Equal("Foo_Bar", HtmlHelper.GenerateIdFromName("Foo.Bar"));
+ Assert.Equal("Foo_Bar_Baz", HtmlHelper.GenerateIdFromName("Foo.Bar.Baz"));
+ Assert.Null(HtmlHelper.GenerateIdFromName("1Foo"));
+ Assert.Equal("Foo_0_", HtmlHelper.GenerateIdFromName("Foo[0]"));
+
+ // Custom replacement tests
+ Assert.Equal("", HtmlHelper.GenerateIdFromName("", "?"));
+ Assert.Equal("Foo", HtmlHelper.GenerateIdFromName("Foo", "?"));
+ Assert.Equal("Foo?Bar", HtmlHelper.GenerateIdFromName("Foo.Bar", "?"));
+ Assert.Equal("Foo?Bar?Baz", HtmlHelper.GenerateIdFromName("Foo.Bar.Baz", "?"));
+ Assert.Equal("FooBarBaz", HtmlHelper.GenerateIdFromName("Foo.Bar.Baz", ""));
+ Assert.Null(HtmlHelper.GenerateIdFromName("1Foo", "?"));
+ Assert.Equal("Foo?0?", HtmlHelper.GenerateIdFromName("Foo[0]", "?"));
+ }
+
+ // RenderPartialInternal
+
+ [Fact]
+ public void NullPartialViewNameThrows()
+ {
+ // Arrange
+ TestableHtmlHelper helper = TestableHtmlHelper.Create();
+ ViewDataDictionary viewData = new ViewDataDictionary();
+
+ // Act & Assert
+ Assert.ThrowsArgumentNullOrEmpty(
+ () => helper.RenderPartialInternal(null /* partialViewName */, null /* viewData */, null /* model */, TextWriter.Null),
+ "partialViewName");
+ }
+
+ [Fact]
+ public void EmptyPartialViewNameThrows()
+ {
+ // Arrange
+ TestableHtmlHelper helper = TestableHtmlHelper.Create();
+ ViewDataDictionary viewData = new ViewDataDictionary();
+
+ // Act & Assert
+ Assert.ThrowsArgumentNullOrEmpty(
+ () => helper.RenderPartialInternal(String.Empty /* partialViewName */, null /* viewData */, null /* model */, TextWriter.Null),
+ "partialViewName");
+ }
+
+ [Fact]
+ public void EngineLookupSuccessCallsRender()
+ {
+ // Arrange
+ TestableHtmlHelper helper = TestableHtmlHelper.Create();
+ TextWriter writer = helper.ViewContext.Writer;
+ Mock<IViewEngine> engine = new Mock<IViewEngine>(MockBehavior.Strict);
+ Mock<IView> view = new Mock<IView>(MockBehavior.Strict);
+ engine
+ .Setup(e => e.FindPartialView(It.IsAny<ControllerContext>(), "partial-view", It.IsAny<bool>()))
+ .Returns(new ViewEngineResult(view.Object, engine.Object))
+ .Verifiable();
+ view
+ .Setup(v => v.Render(It.IsAny<ViewContext>(), writer))
+ .Callback<ViewContext, TextWriter>(
+ (viewContext, _) =>
+ {
+ Assert.Same(helper.ViewContext.View, viewContext.View);
+ Assert.Same(helper.ViewContext.TempData, viewContext.TempData);
+ })
+ .Verifiable();
+
+ // Act
+ helper.RenderPartialInternal("partial-view", null /* viewData */, null /* model */, writer, engine.Object);
+
+ // Assert
+ engine.Verify();
+ view.Verify();
+ }
+
+ [Fact]
+ public void EngineLookupFailureThrows()
+ {
+ // Arrange
+ TestableHtmlHelper helper = TestableHtmlHelper.Create();
+ Mock<IViewEngine> engine = new Mock<IViewEngine>(MockBehavior.Strict);
+ engine
+ .Setup(e => e.FindPartialView(It.IsAny<ControllerContext>(), "partial-view", It.IsAny<bool>()))
+ .Returns(new ViewEngineResult(new[] { "location1", "location2" }))
+ .Verifiable();
+
+ // Act & Assert
+ Assert.Throws<InvalidOperationException>(
+ () => helper.RenderPartialInternal("partial-view", null /* viewData */, null /* model */, TextWriter.Null, engine.Object),
+ @"The partial view 'partial-view' was not found or no view engine supports the searched locations. The following locations were searched:
+location1
+location2");
+
+ engine.Verify();
+ }
+
+ [Fact]
+ public void RenderPartialInternalWithNullModelAndNullViewData()
+ {
+ // Arrange
+ object model = new object();
+ TestableHtmlHelper helper = TestableHtmlHelper.Create();
+ helper.ViewData["Foo"] = "Bar";
+ helper.ViewData.Model = model;
+ Mock<IViewEngine> engine = new Mock<IViewEngine>(MockBehavior.Strict);
+ Mock<IView> view = new Mock<IView>(MockBehavior.Strict);
+ engine
+ .Setup(e => e.FindPartialView(It.IsAny<ControllerContext>(), "partial-view", It.IsAny<bool>()))
+ .Returns(new ViewEngineResult(view.Object, engine.Object))
+ .Verifiable();
+ view
+ .Setup(v => v.Render(It.IsAny<ViewContext>(), TextWriter.Null))
+ .Callback<ViewContext, TextWriter>(
+ (viewContext, writer) =>
+ {
+ Assert.NotSame(helper.ViewData, viewContext.ViewData); // New view data instance
+ Assert.Equal("Bar", viewContext.ViewData["Foo"]); // Copy of the existing view data
+ Assert.Same(model, viewContext.ViewData.Model); // Keep existing model
+ })
+ .Verifiable();
+
+ // Act
+ helper.RenderPartialInternal("partial-view", null /* viewData */, null /* model */, TextWriter.Null, engine.Object);
+
+ // Assert
+ engine.Verify();
+ view.Verify();
+ }
+
+ [Fact]
+ public void RenderPartialInternalWithNonNullModelAndNullViewData()
+ {
+ // Arrange
+ object model = new object();
+ object newModel = new object();
+ TestableHtmlHelper helper = TestableHtmlHelper.Create();
+ helper.ViewData["Foo"] = "Bar";
+ helper.ViewData.Model = model;
+ Mock<IViewEngine> engine = new Mock<IViewEngine>(MockBehavior.Strict);
+ Mock<IView> view = new Mock<IView>(MockBehavior.Strict);
+ engine
+ .Setup(e => e.FindPartialView(It.IsAny<ControllerContext>(), "partial-view", It.IsAny<bool>()))
+ .Returns(new ViewEngineResult(view.Object, engine.Object))
+ .Verifiable();
+ view
+ .Setup(v => v.Render(It.IsAny<ViewContext>(), TextWriter.Null))
+ .Callback<ViewContext, TextWriter>(
+ (viewContext, writer) =>
+ {
+ Assert.NotSame(helper.ViewData, viewContext.ViewData); // New view data instance
+ Assert.Empty(viewContext.ViewData); // Empty (not copied)
+ Assert.Same(newModel, viewContext.ViewData.Model); // New model
+ })
+ .Verifiable();
+
+ // Act
+ helper.RenderPartialInternal("partial-view", null /* viewData */, newModel, TextWriter.Null, engine.Object);
+
+ // Assert
+ engine.Verify();
+ view.Verify();
+ }
+
+ [Fact]
+ public void RenderPartialInternalWithNullModelAndNonNullViewData()
+ {
+ // Arrange
+ object model = new object();
+ object vddModel = new object();
+ ViewDataDictionary vdd = new ViewDataDictionary();
+ vdd["Baz"] = "Biff";
+ vdd.Model = vddModel;
+ TestableHtmlHelper helper = TestableHtmlHelper.Create();
+ helper.ViewData["Foo"] = "Bar";
+ helper.ViewData.Model = model;
+ Mock<IViewEngine> engine = new Mock<IViewEngine>(MockBehavior.Strict);
+ Mock<IView> view = new Mock<IView>(MockBehavior.Strict);
+ engine
+ .Setup(e => e.FindPartialView(It.IsAny<ControllerContext>(), "partial-view", It.IsAny<bool>()))
+ .Returns(new ViewEngineResult(view.Object, engine.Object))
+ .Verifiable();
+ view
+ .Setup(v => v.Render(It.IsAny<ViewContext>(), TextWriter.Null))
+ .Callback<ViewContext, TextWriter>(
+ (viewContext, writer) =>
+ {
+ Assert.NotSame(helper.ViewData, viewContext.ViewData); // New view data instance
+ Assert.Single(viewContext.ViewData); // Copy of the passed view data, not original view data
+ Assert.Equal("Biff", viewContext.ViewData["Baz"]);
+ Assert.Same(vddModel, viewContext.ViewData.Model); // Keep model from passed view data, not original view data
+ })
+ .Verifiable();
+
+ // Act
+ helper.RenderPartialInternal("partial-view", vdd, null /* model */, TextWriter.Null, engine.Object);
+
+ // Assert
+ engine.Verify();
+ view.Verify();
+ }
+
+ [Fact]
+ public void RenderPartialInternalWithNonNullModelAndNonNullViewData()
+ {
+ // Arrange
+ object model = new object();
+ object vddModel = new object();
+ object newModel = new object();
+ ViewDataDictionary vdd = new ViewDataDictionary();
+ vdd["Baz"] = "Biff";
+ vdd.Model = vddModel;
+ TestableHtmlHelper helper = TestableHtmlHelper.Create();
+ helper.ViewData["Foo"] = "Bar";
+ helper.ViewData.Model = model;
+ Mock<IViewEngine> engine = new Mock<IViewEngine>(MockBehavior.Strict);
+ Mock<IView> view = new Mock<IView>(MockBehavior.Strict);
+ engine
+ .Setup(e => e.FindPartialView(It.IsAny<ControllerContext>(), "partial-view", It.IsAny<bool>()))
+ .Returns(new ViewEngineResult(view.Object, engine.Object))
+ .Verifiable();
+ view
+ .Setup(v => v.Render(It.IsAny<ViewContext>(), TextWriter.Null))
+ .Callback<ViewContext, TextWriter>(
+ (viewContext, writer) =>
+ {
+ Assert.NotSame(helper.ViewData, viewContext.ViewData); // New view data instance
+ Assert.Single(viewContext.ViewData); // Copy of the passed view data, not original view data
+ Assert.Equal("Biff", viewContext.ViewData["Baz"]);
+ Assert.Same(newModel, viewContext.ViewData.Model); // New model
+ })
+ .Verifiable();
+
+ // Act
+ helper.RenderPartialInternal("partial-view", vdd, newModel, TextWriter.Null, engine.Object);
+
+ // Assert
+ engine.Verify();
+ view.Verify();
+ }
+
+ // HttpMethodOverride
+
+ [Fact]
+ public void HttpMethodOverrideGuardClauses()
+ {
+ // Arrange
+ var viewContext = new Mock<ViewContext>().Object;
+ var viewDataContainer = MvcHelper.GetViewDataContainer(null);
+ var htmlHelper = new HtmlHelper(viewContext, viewDataContainer);
+
+ // Act & Assert
+ Assert.ThrowsArgumentNullOrEmpty(
+ () => htmlHelper.HttpMethodOverride(null),
+ "httpMethod"
+ );
+ Assert.Throws<ArgumentException>(
+ () => htmlHelper.HttpMethodOverride((HttpVerbs)10000),
+ @"The specified HttpVerbs value is not supported. The supported values are Delete, Head, and Put.
+Parameter name: httpVerb"
+ );
+ Assert.Throws<ArgumentException>(
+ () => htmlHelper.HttpMethodOverride(HttpVerbs.Get),
+ @"The specified HttpVerbs value is not supported. The supported values are Delete, Head, and Put.
+Parameter name: httpVerb"
+ );
+ Assert.Throws<ArgumentException>(
+ () => htmlHelper.HttpMethodOverride(HttpVerbs.Post),
+ @"The specified HttpVerbs value is not supported. The supported values are Delete, Head, and Put.
+Parameter name: httpVerb"
+ );
+ Assert.Throws<ArgumentException>(
+ () => htmlHelper.HttpMethodOverride("gEt"),
+ @"The GET and POST HTTP methods are not supported.
+Parameter name: httpMethod"
+ );
+ Assert.Throws<ArgumentException>(
+ () => htmlHelper.HttpMethodOverride("pOsT"),
+ @"The GET and POST HTTP methods are not supported.
+Parameter name: httpMethod"
+ );
+ }
+
+ [Fact]
+ public void HttpMethodOverrideWithMethodRendersHiddenField()
+ {
+ // Arrange
+ var viewContext = new Mock<ViewContext>().Object;
+ var viewDataContainer = MvcHelper.GetViewDataContainer(null);
+ var htmlHelper = new HtmlHelper(viewContext, viewDataContainer);
+
+ // Act
+ MvcHtmlString hiddenField = htmlHelper.HttpMethodOverride("PUT");
+
+ // Assert
+ Assert.Equal(@"<input name=""X-HTTP-Method-Override"" type=""hidden"" value=""PUT"" />", hiddenField.ToHtmlString());
+ }
+
+ [Fact]
+ public void HttpMethodOverrideWithVerbRendersHiddenField()
+ {
+ // Arrange
+ var viewContext = new Mock<ViewContext>().Object;
+ var viewDataContainer = MvcHelper.GetViewDataContainer(null);
+ var htmlHelper = new HtmlHelper(viewContext, viewDataContainer);
+
+ // Act
+ MvcHtmlString hiddenField = htmlHelper.HttpMethodOverride(HttpVerbs.Delete);
+
+ // Assert
+ Assert.Equal(@"<input name=""X-HTTP-Method-Override"" type=""hidden"" value=""DELETE"" />", hiddenField.ToHtmlString());
+ }
+
+ [Fact]
+ public void ViewBagProperty_ReflectsViewData()
+ {
+ // Arrange
+ Mock<IViewDataContainer> viewDataContainer = new Mock<IViewDataContainer>();
+ ViewDataDictionary viewDataDictionary = new ViewDataDictionary() { { "A", 1 } };
+ viewDataContainer.Setup(container => container.ViewData).Returns(viewDataDictionary);
+
+ // Act
+ HtmlHelper htmlHelper = new HtmlHelper(new Mock<ViewContext>().Object, viewDataContainer.Object);
+
+ // Assert
+ Assert.Equal(1, htmlHelper.ViewBag.A);
+ }
+
+ [Fact]
+ public void ViewBagProperty_ReflectsNewViewDataContainerInstance()
+ {
+ // Arrange
+ ViewDataDictionary viewDataDictionary = new ViewDataDictionary() { { "A", 1 } };
+ Mock<IViewDataContainer> viewDataContainer = new Mock<IViewDataContainer>();
+ viewDataContainer.Setup(container => container.ViewData).Returns(viewDataDictionary);
+
+ ViewDataDictionary otherViewDataDictionary = new ViewDataDictionary() { { "A", 2 } };
+ Mock<IViewDataContainer> otherViewDataContainer = new Mock<IViewDataContainer>();
+ otherViewDataContainer.Setup(container => container.ViewData).Returns(otherViewDataDictionary);
+
+ HtmlHelper htmlHelper = new HtmlHelper(new Mock<ViewContext>().Object, viewDataContainer.Object, new RouteCollection());
+
+ // Act
+ htmlHelper.ViewDataContainer = otherViewDataContainer.Object;
+
+ // Assert
+ Assert.Equal(2, htmlHelper.ViewBag.A);
+ }
+
+ [Fact]
+ public void ViewBag_PropagatesChangesToViewData()
+ {
+ // Arrange
+ ViewDataDictionary viewDataDictionary = new ViewDataDictionary() { { "A", 1 } };
+ Mock<IViewDataContainer> viewDataContainer = new Mock<IViewDataContainer>();
+ viewDataContainer.Setup(container => container.ViewData).Returns(viewDataDictionary);
+
+ HtmlHelper htmlHelper = new HtmlHelper(new Mock<ViewContext>().Object, viewDataContainer.Object, new RouteCollection());
+
+ // Act
+ htmlHelper.ViewBag.A = "foo";
+ htmlHelper.ViewBag.B = 2;
+
+ // Assert
+ Assert.Equal("foo", htmlHelper.ViewData["A"]);
+ Assert.Equal(2, htmlHelper.ViewData["B"]);
+ }
+
+ // Unobtrusive validation attributes
+
+ [Fact]
+ public void GetUnobtrusiveValidationAttributesReturnsEmptySetWhenClientValidationIsNotEnabled()
+ {
+ // Arrange
+ var formContext = new FormContext();
+ formContext.RenderedField("foobar", true);
+ var viewContext = new Mock<ViewContext>();
+ viewContext.SetupGet(vc => vc.FormContext).Returns(formContext);
+ var viewDataContainer = MvcHelper.GetViewDataContainer(new ViewDataDictionary());
+ var htmlHelper = new HtmlHelper(viewContext.Object, viewDataContainer);
+
+ // Act
+ IDictionary<string, object> result = htmlHelper.GetUnobtrusiveValidationAttributes("foobar");
+
+ // Assert
+ Assert.Empty(result);
+ }
+
+ [Fact]
+ public void GetUnobtrusiveValidationAttributesReturnsEmptySetWhenUnobtrusiveJavaScriptIsNotEnabled()
+ {
+ // Arrange
+ var formContext = new FormContext();
+ formContext.RenderedField("foobar", true);
+ var viewContext = new Mock<ViewContext>();
+ viewContext.SetupGet(vc => vc.FormContext).Returns(formContext);
+ viewContext.SetupGet(vc => vc.ClientValidationEnabled).Returns(true);
+ var viewDataContainer = MvcHelper.GetViewDataContainer(new ViewDataDictionary());
+ var htmlHelper = new HtmlHelper(viewContext.Object, viewDataContainer);
+
+ // Act
+ IDictionary<string, object> result = htmlHelper.GetUnobtrusiveValidationAttributes("foobar");
+
+ // Assert
+ Assert.Empty(result);
+ }
+
+ [Fact]
+ public void GetUnobtrusiveValidationAttributesReturnsEmptySetWhenFieldHasAlreadyBeenRendered()
+ {
+ // Arrange
+ var formContext = new FormContext();
+ formContext.RenderedField("foobar", true);
+ var viewContext = new Mock<ViewContext>();
+ viewContext.SetupGet(vc => vc.FormContext).Returns(formContext);
+ viewContext.SetupGet(vc => vc.ClientValidationEnabled).Returns(true);
+ viewContext.SetupGet(vc => vc.UnobtrusiveJavaScriptEnabled).Returns(true);
+ var viewDataContainer = MvcHelper.GetViewDataContainer(new ViewDataDictionary());
+ var htmlHelper = new HtmlHelper(viewContext.Object, viewDataContainer);
+
+ // Act
+ IDictionary<string, object> result = htmlHelper.GetUnobtrusiveValidationAttributes("foobar");
+
+ // Assert
+ Assert.Empty(result);
+ }
+
+ [Fact]
+ public void GetUnobtrusiveValidationAttributesReturnsEmptySetAndSetsFieldAsRenderedForFieldWithNoClientRules()
+ {
+ // Arrange
+ var formContext = new FormContext();
+ var viewContext = new Mock<ViewContext>();
+ viewContext.SetupGet(vc => vc.FormContext).Returns(formContext);
+ viewContext.SetupGet(vc => vc.ClientValidationEnabled).Returns(true);
+ viewContext.SetupGet(vc => vc.UnobtrusiveJavaScriptEnabled).Returns(true);
+ var viewDataContainer = MvcHelper.GetViewDataContainer(new ViewDataDictionary());
+ var htmlHelper = new HtmlHelper(viewContext.Object, viewDataContainer);
+ htmlHelper.ClientValidationRuleFactory = delegate { return Enumerable.Empty<ModelClientValidationRule>(); };
+
+ // Act
+ IDictionary<string, object> result = htmlHelper.GetUnobtrusiveValidationAttributes("foobar");
+
+ // Assert
+ Assert.Empty(result);
+ Assert.True(formContext.RenderedField("foobar"));
+ }
+
+ [Fact]
+ public void GetUnobtrusiveValidationAttributesIncludesDataValTrueWithNonEmptyClientRuleList()
+ {
+ // Arrange
+ var formContext = new FormContext();
+ var viewContext = new Mock<ViewContext>();
+ viewContext.SetupGet(vc => vc.FormContext).Returns(formContext);
+ viewContext.SetupGet(vc => vc.ClientValidationEnabled).Returns(true);
+ viewContext.SetupGet(vc => vc.UnobtrusiveJavaScriptEnabled).Returns(true);
+ var viewDataContainer = MvcHelper.GetViewDataContainer(new ViewDataDictionary());
+ var htmlHelper = new HtmlHelper(viewContext.Object, viewDataContainer);
+ htmlHelper.ClientValidationRuleFactory = delegate { return new[] { new ModelClientValidationRule { ValidationType = "type" } }; };
+
+ // Act
+ IDictionary<string, object> result = htmlHelper.GetUnobtrusiveValidationAttributes("foobar");
+
+ // Assert
+ Assert.Equal("true", result["data-val"]);
+ }
+
+ [Fact]
+ public void GetUnobtrusiveValidationAttributesWithEmptyMessage()
+ {
+ // Arrange
+ var formContext = new FormContext();
+ var viewContext = new Mock<ViewContext>();
+ viewContext.SetupGet(vc => vc.FormContext).Returns(formContext);
+ viewContext.SetupGet(vc => vc.ClientValidationEnabled).Returns(true);
+ viewContext.SetupGet(vc => vc.UnobtrusiveJavaScriptEnabled).Returns(true);
+ var viewDataContainer = MvcHelper.GetViewDataContainer(new ViewDataDictionary());
+ var htmlHelper = new HtmlHelper(viewContext.Object, viewDataContainer);
+ htmlHelper.ClientValidationRuleFactory = delegate { return new[] { new ModelClientValidationRule { ValidationType = "type" } }; };
+
+ // Act
+ IDictionary<string, object> result = htmlHelper.GetUnobtrusiveValidationAttributes("foobar");
+
+ // Assert
+ Assert.Equal("", result["data-val-type"]);
+ }
+
+ [Fact]
+ public void GetUnobtrusiveValidationAttributesMessageIsHtmlEncoded()
+ {
+ // Arrange
+ var formContext = new FormContext();
+ var viewContext = new Mock<ViewContext>();
+ viewContext.SetupGet(vc => vc.FormContext).Returns(formContext);
+ viewContext.SetupGet(vc => vc.ClientValidationEnabled).Returns(true);
+ viewContext.SetupGet(vc => vc.UnobtrusiveJavaScriptEnabled).Returns(true);
+ var viewDataContainer = MvcHelper.GetViewDataContainer(new ViewDataDictionary());
+ var htmlHelper = new HtmlHelper(viewContext.Object, viewDataContainer);
+ htmlHelper.ClientValidationRuleFactory = delegate { return new[] { new ModelClientValidationRule { ValidationType = "type", ErrorMessage = "<script>alert('xss')</script>" } }; };
+
+ // Act
+ IDictionary<string, object> result = htmlHelper.GetUnobtrusiveValidationAttributes("foobar");
+
+ // Assert
+ Assert.Equal("&lt;script&gt;alert(&#39;xss&#39;)&lt;/script&gt;", result["data-val-type"]);
+ }
+
+ [Fact]
+ public void GetUnobtrusiveValidationAttributesWithMessageAndParameters()
+ {
+ // Arrange
+ var formContext = new FormContext();
+ var viewContext = new Mock<ViewContext>();
+ viewContext.SetupGet(vc => vc.FormContext).Returns(formContext);
+ viewContext.SetupGet(vc => vc.ClientValidationEnabled).Returns(true);
+ viewContext.SetupGet(vc => vc.UnobtrusiveJavaScriptEnabled).Returns(true);
+ var viewDataContainer = MvcHelper.GetViewDataContainer(new ViewDataDictionary());
+ var htmlHelper = new HtmlHelper(viewContext.Object, viewDataContainer);
+ htmlHelper.ClientValidationRuleFactory = delegate
+ {
+ ModelClientValidationRule rule = new ModelClientValidationRule { ValidationType = "type", ErrorMessage = "error" };
+ rule.ValidationParameters["foo"] = "bar";
+ rule.ValidationParameters["baz"] = "biff";
+ return new[] { rule };
+ };
+
+ // Act
+ IDictionary<string, object> result = htmlHelper.GetUnobtrusiveValidationAttributes("foobar");
+
+ // Assert
+ Assert.Equal("error", result["data-val-type"]);
+ Assert.Equal("bar", result["data-val-type-foo"]);
+ Assert.Equal("biff", result["data-val-type-baz"]);
+ }
+
+ [Fact]
+ public void GetUnobtrusiveValidationAttributesWithTwoClientRules()
+ {
+ // Arrange
+ var formContext = new FormContext();
+ var viewContext = new Mock<ViewContext>();
+ viewContext.SetupGet(vc => vc.FormContext).Returns(formContext);
+ viewContext.SetupGet(vc => vc.ClientValidationEnabled).Returns(true);
+ viewContext.SetupGet(vc => vc.UnobtrusiveJavaScriptEnabled).Returns(true);
+ var viewDataContainer = MvcHelper.GetViewDataContainer(new ViewDataDictionary());
+ var htmlHelper = new HtmlHelper(viewContext.Object, viewDataContainer);
+ htmlHelper.ClientValidationRuleFactory = delegate
+ {
+ ModelClientValidationRule rule1 = new ModelClientValidationRule { ValidationType = "type", ErrorMessage = "error" };
+ rule1.ValidationParameters["foo"] = "bar";
+ rule1.ValidationParameters["baz"] = "biff";
+ ModelClientValidationRule rule2 = new ModelClientValidationRule { ValidationType = "othertype", ErrorMessage = "othererror" };
+ rule2.ValidationParameters["true3"] = "false4";
+ return new[] { rule1, rule2 };
+ };
+
+ // Act
+ IDictionary<string, object> result = htmlHelper.GetUnobtrusiveValidationAttributes("foobar");
+
+ // Assert
+ Assert.Equal("error", result["data-val-type"]);
+ Assert.Equal("bar", result["data-val-type-foo"]);
+ Assert.Equal("biff", result["data-val-type-baz"]);
+ Assert.Equal("othererror", result["data-val-othertype"]);
+ Assert.Equal("false4", result["data-val-othertype-true3"]);
+ }
+
+ [Fact]
+ public void GetUnobtrusiveValidationAttributesUsesShortNameForModelMetadataLookup()
+ {
+ // Arrange
+ string passedName = null;
+ var formContext = new FormContext();
+ var viewContext = new Mock<ViewContext>();
+ var viewData = new ViewDataDictionary();
+ viewContext.SetupGet(vc => vc.FormContext).Returns(formContext);
+ viewContext.SetupGet(vc => vc.ClientValidationEnabled).Returns(true);
+ viewContext.SetupGet(vc => vc.UnobtrusiveJavaScriptEnabled).Returns(true);
+ viewData.TemplateInfo.HtmlFieldPrefix = "Prefix";
+ var viewDataContainer = MvcHelper.GetViewDataContainer(viewData);
+ var htmlHelper = new HtmlHelper(viewContext.Object, viewDataContainer);
+ htmlHelper.ClientValidationRuleFactory = (name, _) =>
+ {
+ passedName = name;
+ return Enumerable.Empty<ModelClientValidationRule>();
+ };
+
+ // Act
+ htmlHelper.GetUnobtrusiveValidationAttributes("foobar");
+
+ // Assert
+ Assert.Equal("foobar", passedName);
+ }
+
+ [Fact]
+ public void GetUnobtrusiveValidationAttributeUsesViewDataForModelMetadataLookup()
+ {
+ // Arrange
+ var formContext = new FormContext();
+ var viewContext = new Mock<ViewContext>();
+ var viewData = new ViewDataDictionary<MyModel>();
+ viewContext.SetupGet(vc => vc.FormContext).Returns(formContext);
+ viewContext.SetupGet(vc => vc.ClientValidationEnabled).Returns(true);
+ viewContext.SetupGet(vc => vc.UnobtrusiveJavaScriptEnabled).Returns(true);
+ viewData.TemplateInfo.HtmlFieldPrefix = "Prefix";
+ var viewDataContainer = MvcHelper.GetViewDataContainer(viewData);
+ var htmlHelper = new HtmlHelper(viewContext.Object, viewDataContainer);
+
+ // Act
+ IDictionary<string, object> result = htmlHelper.GetUnobtrusiveValidationAttributes("MyProperty");
+
+ // Assert
+ Assert.Equal(2, result.Count);
+ Assert.Equal("true", result["data-val"]);
+ Assert.Equal("My required message", result["data-val-required"]);
+ }
+
+ class MyModel
+ {
+ [Required(ErrorMessage = "My required message")]
+ public object MyProperty { get; set; }
+ }
+
+ [Fact]
+ public void GetUnobtrusiveValidationAttributesMarksRenderedFieldsWithFullName()
+ {
+ // Arrange
+ var formContext = new FormContext();
+ var viewContext = new Mock<ViewContext>();
+ var viewData = new ViewDataDictionary();
+ viewContext.SetupGet(vc => vc.FormContext).Returns(formContext);
+ viewContext.SetupGet(vc => vc.ClientValidationEnabled).Returns(true);
+ viewContext.SetupGet(vc => vc.UnobtrusiveJavaScriptEnabled).Returns(true);
+ viewData.TemplateInfo.HtmlFieldPrefix = "Prefix";
+ var viewDataContainer = MvcHelper.GetViewDataContainer(viewData);
+ var htmlHelper = new HtmlHelper(viewContext.Object, viewDataContainer);
+
+ // Act
+ htmlHelper.GetUnobtrusiveValidationAttributes("foobar");
+
+ // Assert
+ Assert.False(formContext.RenderedField("foobar"));
+ Assert.True(formContext.RenderedField("Prefix.foobar"));
+ }
+
+ [Fact]
+ public void GetUnobtrusiveValidationAttributesGuardClauses()
+ {
+ // Arrange
+ var formContext = new FormContext();
+ var viewContext = new Mock<ViewContext>();
+ viewContext.SetupGet(vc => vc.FormContext).Returns(formContext);
+ viewContext.SetupGet(vc => vc.ClientValidationEnabled).Returns(true);
+ viewContext.SetupGet(vc => vc.UnobtrusiveJavaScriptEnabled).Returns(true);
+ var viewDataContainer = MvcHelper.GetViewDataContainer(new ViewDataDictionary());
+ var htmlHelper = new HtmlHelper(viewContext.Object, viewDataContainer);
+
+ // Act & Assert
+ AssertBadClientValidationRule(htmlHelper, "Validation type names in unobtrusive client validation rules cannot be empty. Client rule type: System.Web.Mvc.ModelClientValidationRule", new ModelClientValidationRule());
+ AssertBadClientValidationRule(htmlHelper, "Validation type names in unobtrusive client validation rules must consist of only lowercase letters. Invalid name: \"OnlyLowerCase\", client rule type: System.Web.Mvc.ModelClientValidationRule", new ModelClientValidationRule { ValidationType = "OnlyLowerCase" });
+ AssertBadClientValidationRule(htmlHelper, "Validation type names in unobtrusive client validation rules must consist of only lowercase letters. Invalid name: \"nonumb3rs\", client rule type: System.Web.Mvc.ModelClientValidationRule", new ModelClientValidationRule { ValidationType = "nonumb3rs" });
+ AssertBadClientValidationRule(htmlHelper, "Validation type names in unobtrusive client validation rules must be unique. The following validation type was seen more than once: rule", new ModelClientValidationRule { ValidationType = "rule" }, new ModelClientValidationRule { ValidationType = "rule" });
+
+ var emptyParamName = new ModelClientValidationRule { ValidationType = "type" };
+ emptyParamName.ValidationParameters[""] = "foo";
+ AssertBadClientValidationRule(htmlHelper, "Validation parameter names in unobtrusive client validation rules cannot be empty. Client rule type: System.Web.Mvc.ModelClientValidationRule", emptyParamName);
+
+ var paramNameMixedCase = new ModelClientValidationRule { ValidationType = "type" };
+ paramNameMixedCase.ValidationParameters["MixedCase"] = "foo";
+ AssertBadClientValidationRule(htmlHelper, "Validation parameter names in unobtrusive client validation rules must start with a lowercase letter and consist of only lowercase letters or digits. Validation parameter name: MixedCase, client rule type: System.Web.Mvc.ModelClientValidationRule", paramNameMixedCase);
+
+ var paramNameStartsWithNumber = new ModelClientValidationRule { ValidationType = "type" };
+ paramNameStartsWithNumber.ValidationParameters["2112"] = "foo";
+ AssertBadClientValidationRule(htmlHelper, "Validation parameter names in unobtrusive client validation rules must start with a lowercase letter and consist of only lowercase letters or digits. Validation parameter name: 2112, client rule type: System.Web.Mvc.ModelClientValidationRule", paramNameStartsWithNumber);
+ }
+
+ [Fact]
+ public void RawReturnsWrapperMarkup()
+ {
+ // Arrange
+ var viewContext = new Mock<ViewContext>().Object;
+ var viewDataContainer = new Mock<IViewDataContainer>().Object;
+ var htmlHelper = new HtmlHelper(viewContext, viewDataContainer);
+ string markup = "<b>bold</b>";
+
+ // Act
+ IHtmlString markupHtml = htmlHelper.Raw(markup);
+
+ // Assert
+ Assert.Equal("<b>bold</b>", markupHtml.ToString());
+ Assert.Equal("<b>bold</b>", markupHtml.ToHtmlString());
+ }
+
+ [Fact]
+ public void RawAllowsNullValue()
+ {
+ // Arrange
+ var viewContext = new Mock<ViewContext>().Object;
+ var viewDataContainer = new Mock<IViewDataContainer>().Object;
+ var htmlHelper = new HtmlHelper(viewContext, viewDataContainer);
+
+ // Act
+ IHtmlString markupHtml = htmlHelper.Raw(null);
+
+ // Assert
+ Assert.Equal(null, markupHtml.ToString());
+ Assert.Equal(null, markupHtml.ToHtmlString());
+ }
+
+ [Fact]
+ public void RawAllowsNullObjectValue()
+ {
+ // Arrange
+ var viewContext = new Mock<ViewContext>().Object;
+ var viewDataContainer = new Mock<IViewDataContainer>().Object;
+ var htmlHelper = new HtmlHelper(viewContext, viewDataContainer);
+
+ // Act
+ IHtmlString markupHtml = htmlHelper.Raw((object)null);
+
+ // Assert
+ Assert.Equal(null, markupHtml.ToString());
+ Assert.Equal(null, markupHtml.ToHtmlString());
+ }
+
+ [Fact]
+ public void RawAllowsEmptyValue()
+ {
+ // Arrange
+ var viewContext = new Mock<ViewContext>().Object;
+ var viewDataContainer = new Mock<IViewDataContainer>().Object;
+ var htmlHelper = new HtmlHelper(viewContext, viewDataContainer);
+
+ // Act
+ IHtmlString markupHtml = htmlHelper.Raw("");
+
+ // Assert
+ Assert.Equal("", markupHtml.ToString());
+ Assert.Equal("", markupHtml.ToHtmlString());
+ }
+
+ [Fact]
+ public void RawReturnsWrapperMarkupOfObject()
+ {
+ // Arrange
+ var viewContext = new Mock<ViewContext>().Object;
+ var viewDataContainer = new Mock<IViewDataContainer>().Object;
+ var htmlHelper = new HtmlHelper(viewContext, viewDataContainer);
+ ObjectWithWrapperMarkup obj = new ObjectWithWrapperMarkup();
+
+ // Act
+ IHtmlString markupHtml = htmlHelper.Raw(obj);
+
+ // Assert
+ Assert.Equal("<b>boldFromObject</b>", markupHtml.ToString());
+ Assert.Equal("<b>boldFromObject</b>", markupHtml.ToHtmlString());
+ }
+
+ [Fact]
+ public void EvalStringAndFormatValueWithNullValueReturnsEmptyString()
+ {
+ // Arrange
+ var htmlHelper = MvcHelper.GetHtmlHelper(new ViewDataDictionary());
+
+ // Act & Assert
+ Assert.Equal(String.Empty, htmlHelper.FormatValue(null, "-{0}-"));
+ Assert.Equal(String.Empty, htmlHelper.EvalString("nonExistant"));
+ Assert.Equal(String.Empty, htmlHelper.EvalString("nonExistant", "-{0}-"));
+ }
+
+ [Fact]
+ public void EvalStringAndFormatValueUseCurrentCulture()
+ {
+ // Arrange
+ DateTime dt = new DateTime(1900, 1, 1, 0, 0, 0);
+ HtmlHelper htmlHelper = MvcHelper.GetHtmlHelper(new ViewDataDictionary() { { "date", dt } });
+ string expectedFormattedDate = "-1900/01/01 12:00:00 AM-";
+
+ // Act && Assert
+ using (ReplaceCulture("en-ZA", "en-US"))
+ {
+ Assert.Equal(expectedFormattedDate, htmlHelper.FormatValue(dt, "-{0}-"));
+ Assert.Equal(expectedFormattedDate, htmlHelper.EvalString("date", "-{0}-"));
+ }
+ }
+
+ [Fact]
+ public void EvalStringAndFormatValueWithEmptyFormatConvertsValueToString()
+ {
+ // Arrange
+ DateTime dt = new DateTime(1900, 1, 1, 0, 0, 0);
+ HtmlHelper htmlHelper = MvcHelper.GetHtmlHelper(new ViewDataDictionary() { { "date", dt } });
+ string expectedUnformattedDate = "1900/01/01 12:00:00 AM";
+
+ // Act && Assert
+ using (ReplaceCulture("en-ZA", "en-US"))
+ {
+ Assert.Equal(expectedUnformattedDate, htmlHelper.FormatValue(dt, String.Empty));
+ Assert.Equal(expectedUnformattedDate, htmlHelper.EvalString("date", String.Empty));
+ Assert.Equal(expectedUnformattedDate, htmlHelper.EvalString("date"));
+ }
+ }
+
+ private class ObjectWithWrapperMarkup
+ {
+ public override string ToString()
+ {
+ return "<b>boldFromObject</b>";
+ }
+ }
+
+ // Helpers
+
+ private static void AssertBadClientValidationRule(HtmlHelper htmlHelper, string expectedMessage, params ModelClientValidationRule[] rules)
+ {
+ htmlHelper.ClientValidationRuleFactory = delegate { return rules; };
+ Assert.Throws<InvalidOperationException>(
+ () => htmlHelper.GetUnobtrusiveValidationAttributes(Guid.NewGuid().ToString()),
+ expectedMessage
+ );
+ }
+
+ internal static ValueProviderResult GetValueProviderResult(object rawValue, string attemptedValue)
+ {
+ return new ValueProviderResult(rawValue, attemptedValue, CultureInfo.InvariantCulture);
+ }
+
+ internal static IDisposable ReplaceCulture(string currentCulture, string currentUICulture)
+ {
+ CultureInfo newCulture = CultureInfo.GetCultureInfo(currentCulture);
+ CultureInfo newUICulture = CultureInfo.GetCultureInfo(currentUICulture);
+ CultureInfo originalCulture = Thread.CurrentThread.CurrentCulture;
+ CultureInfo originalUICulture = Thread.CurrentThread.CurrentUICulture;
+ Thread.CurrentThread.CurrentCulture = newCulture;
+ Thread.CurrentThread.CurrentUICulture = newUICulture;
+ return new CultureReplacement { OriginalCulture = originalCulture, OriginalUICulture = originalUICulture };
+ }
+
+ private class CultureReplacement : IDisposable
+ {
+ public CultureInfo OriginalCulture;
+ public CultureInfo OriginalUICulture;
+
+ public void Dispose()
+ {
+ Thread.CurrentThread.CurrentCulture = OriginalCulture;
+ Thread.CurrentThread.CurrentUICulture = OriginalUICulture;
+ }
+ }
+
+ private class TestableHtmlHelper : HtmlHelper
+ {
+ TestableHtmlHelper(ViewContext viewContext, IViewDataContainer viewDataContainer)
+ : base(viewContext, viewDataContainer)
+ {
+ }
+
+ public static TestableHtmlHelper Create()
+ {
+ ViewDataDictionary viewData = new ViewDataDictionary();
+
+ Mock<ViewContext> mockViewContext = new Mock<ViewContext>() { DefaultValue = DefaultValue.Mock };
+ mockViewContext.Setup(c => c.HttpContext.Response.Output).Throws(new Exception("Response.Output should never be called."));
+ mockViewContext.Setup(c => c.ViewData).Returns(viewData);
+ mockViewContext.Setup(c => c.Writer).Returns(new StringWriter());
+
+ Mock<IViewDataContainer> container = new Mock<IViewDataContainer>();
+ container.Setup(c => c.ViewData).Returns(viewData);
+
+ return new TestableHtmlHelper(mockViewContext.Object, container.Object);
+ }
+
+ public void RenderPartialInternal(string partialViewName,
+ ViewDataDictionary viewData,
+ object model,
+ TextWriter writer,
+ params IViewEngine[] engines)
+ {
+ base.RenderPartialInternal(partialViewName, viewData, model, writer, new ViewEngineCollection(engines));
+ }
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Test/HtmlHelper`1Test.cs b/test/System.Web.Mvc.Test/Test/HtmlHelper`1Test.cs
new file mode 100644
index 00000000..101a5dbb
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Test/HtmlHelper`1Test.cs
@@ -0,0 +1,40 @@
+using Moq;
+using Xunit;
+
+namespace System.Web.Mvc.Test
+{
+ public class HtmlHelper_1Test
+ {
+ [Fact]
+ public void StronglyTypedViewBagAndStronglyTypedViewDataStayInSync()
+ {
+ // Arrange
+ Mock<IViewDataContainer> viewDataContainer = new Mock<IViewDataContainer>();
+ ViewDataDictionary viewDataDictionary = new ViewDataDictionary() { { "A", 1 } };
+ viewDataContainer.Setup(container => container.ViewData).Returns(viewDataDictionary);
+
+ // Act
+ HtmlHelper<object> htmlHelper = new HtmlHelper<object>(new Mock<ViewContext>().Object, viewDataContainer.Object);
+ htmlHelper.ViewData["B"] = 2;
+ htmlHelper.ViewBag.C = 3;
+
+ // Assert
+
+ // Original ViewData should not be modified by redfined ViewData and ViewBag
+ Assert.Single((htmlHelper as HtmlHelper).ViewData.Keys);
+ Assert.Equal(1, (htmlHelper as HtmlHelper).ViewData["A"]);
+ Assert.Equal(1, (htmlHelper as HtmlHelper).ViewBag.A);
+
+ // Redefined ViewData and ViewBag should be in sync
+ Assert.Equal(3, htmlHelper.ViewData.Keys.Count);
+
+ Assert.Equal(1, htmlHelper.ViewData["A"]);
+ Assert.Equal(2, htmlHelper.ViewData["B"]);
+ Assert.Equal(3, htmlHelper.ViewData["C"]);
+
+ Assert.Equal(1, htmlHelper.ViewBag.A);
+ Assert.Equal(2, htmlHelper.ViewBag.B);
+ Assert.Equal(3, htmlHelper.ViewBag.C);
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Test/HttpDeleteAttributeTest.cs b/test/System.Web.Mvc.Test/Test/HttpDeleteAttributeTest.cs
new file mode 100644
index 00000000..f52eda78
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Test/HttpDeleteAttributeTest.cs
@@ -0,0 +1,25 @@
+using Xunit;
+
+namespace System.Web.Mvc.Test
+{
+ public class HttpDeleteAttributeTest
+ {
+ [Fact]
+ public void IsValidForRequestReturnsFalseIfHttpVerbIsNotPost()
+ {
+ HttpVerbAttributeHelper.TestHttpVerbAttributeWithInvalidVerb<HttpDeleteAttribute>("POST");
+ }
+
+ [Fact]
+ public void IsValidForRequestReturnsTrueIfHttpVerbIsPost()
+ {
+ HttpVerbAttributeHelper.TestHttpVerbAttributeWithValidVerb<HttpDeleteAttribute>("DELETE");
+ }
+
+ [Fact]
+ public void IsValidForRequestThrowsIfControllerContextIsNull()
+ {
+ HttpVerbAttributeHelper.TestHttpVerbAttributeNullControllerContext<HttpDeleteAttribute>();
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Test/HttpFileCollectionValueProviderFactoryTest.cs b/test/System.Web.Mvc.Test/Test/HttpFileCollectionValueProviderFactoryTest.cs
new file mode 100644
index 00000000..36ba8ade
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Test/HttpFileCollectionValueProviderFactoryTest.cs
@@ -0,0 +1,36 @@
+using Moq;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Mvc.Test
+{
+ public class HttpFileCollectionValueProviderFactoryTest
+ {
+ [Fact]
+ public void GetValueProvider()
+ {
+ // Arrange
+ HttpFileCollectionValueProviderFactory factory = new HttpFileCollectionValueProviderFactory();
+
+ Mock<ControllerContext> mockControllerContext = new Mock<ControllerContext>();
+ mockControllerContext.Setup(o => o.HttpContext.Request.Files.Count).Returns(0);
+
+ // Act
+ IValueProvider valueProvider = factory.GetValueProvider(mockControllerContext.Object);
+
+ // Assert
+ Assert.IsType<HttpFileCollectionValueProvider>(valueProvider);
+ }
+
+ [Fact]
+ public void GetValueProvider_ThrowsIfControllerContextIsNull()
+ {
+ // Arrange
+ HttpFileCollectionValueProviderFactory factory = new HttpFileCollectionValueProviderFactory();
+
+ // Act & assert
+ Assert.ThrowsArgumentNull(
+ delegate { factory.GetValueProvider(null); }, "controllerContext");
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Test/HttpFileCollectionValueProviderTest.cs b/test/System.Web.Mvc.Test/Test/HttpFileCollectionValueProviderTest.cs
new file mode 100644
index 00000000..dcb0a7f0
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Test/HttpFileCollectionValueProviderTest.cs
@@ -0,0 +1,147 @@
+using System.Collections.Generic;
+using System.Globalization;
+using System.Linq;
+using Moq;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Mvc.Test
+{
+ public class HttpFileCollectionValueProviderTest
+ {
+ private static readonly KeyValuePair<string, HttpPostedFileBase>[] _allFiles = new KeyValuePair<string, HttpPostedFileBase>[]
+ {
+ new KeyValuePair<string, HttpPostedFileBase>("foo", new MockHttpPostedFile(42, "fooFile1")),
+ new KeyValuePair<string, HttpPostedFileBase>("foo", null),
+ new KeyValuePair<string, HttpPostedFileBase>("foo", new MockHttpPostedFile(0, "") /* empty */),
+ new KeyValuePair<string, HttpPostedFileBase>("foo", new MockHttpPostedFile(100, "fooFile2")),
+ new KeyValuePair<string, HttpPostedFileBase>("bar.baz", new MockHttpPostedFile(200, "barBazFile"))
+ };
+
+ [Fact]
+ public void ContainsPrefix()
+ {
+ // Arrange
+ HttpFileCollectionValueProvider valueProvider = GetValueProvider();
+
+ // Act
+ bool result = valueProvider.ContainsPrefix("bar");
+
+ // Assert
+ Assert.True(result);
+ }
+
+ [Fact]
+ public void ContainsPrefix_DoesNotContainEmptyPrefixIfBackingStoreIsEmpty()
+ {
+ // Arrange
+ HttpFileCollectionValueProvider valueProvider = GetEmptyValueProvider();
+
+ // Act
+ bool result = valueProvider.ContainsPrefix("");
+
+ // Assert
+ Assert.False(result);
+ }
+
+ [Fact]
+ public void ContainsPrefix_ThrowsIfPrefixIsNull()
+ {
+ // Arrange
+ HttpFileCollectionValueProvider valueProvider = GetValueProvider();
+
+ // Act & assert
+ Assert.ThrowsArgumentNull(
+ delegate { valueProvider.ContainsPrefix(null); }, "prefix");
+ }
+
+ [Fact]
+ public void GetValue()
+ {
+ // Arrange
+ HttpFileCollectionValueProvider valueProvider = GetValueProvider();
+
+ // Act
+ ValueProviderResult vpResult = valueProvider.GetValue("foo");
+
+ // Assert
+ Assert.NotNull(vpResult);
+
+ HttpPostedFileBase[] expectedRawValues = (from el in _allFiles
+ where el.Key == "foo"
+ let file = el.Value
+ let hasContent = (file != null && file.ContentLength > 0 && !String.IsNullOrEmpty(file.FileName))
+ select (hasContent) ? file : null).ToArray();
+ Assert.Equal(expectedRawValues, (HttpPostedFileBase[])vpResult.RawValue);
+ Assert.Equal("System.Web.HttpPostedFileBase[]", vpResult.AttemptedValue);
+ Assert.Equal(CultureInfo.InvariantCulture, vpResult.Culture);
+ }
+
+ [Fact]
+ public void GetValue_ReturnsNullIfKeyNotFound()
+ {
+ // Arrange
+ HttpFileCollectionValueProvider valueProvider = GetValueProvider();
+
+ // Act
+ ValueProviderResult vpResult = valueProvider.GetValue("bar");
+
+ // Assert
+ Assert.Null(vpResult);
+ }
+
+ [Fact]
+ public void GetValue_ThrowsIfKeyIsNull()
+ {
+ // Arrange
+ HttpFileCollectionValueProvider valueProvider = GetValueProvider();
+
+ // Act & assert
+ Assert.ThrowsArgumentNull(
+ delegate { valueProvider.GetValue(null); }, "key");
+ }
+
+ private static HttpFileCollectionValueProvider GetEmptyValueProvider()
+ {
+ Mock<ControllerContext> mockControllerContext = new Mock<ControllerContext>();
+ mockControllerContext.Setup(o => o.HttpContext.Request.Files.Count).Returns(0);
+ return new HttpFileCollectionValueProvider(mockControllerContext.Object);
+ }
+
+ private static HttpFileCollectionValueProvider GetValueProvider()
+ {
+ Mock<ControllerContext> mockControllerContext = new Mock<ControllerContext>();
+ mockControllerContext.Setup(o => o.HttpContext.Request.Files.Count).Returns(_allFiles.Length);
+ mockControllerContext.Setup(o => o.HttpContext.Request.Files.AllKeys).Returns(_allFiles.Select(f => f.Key).ToArray());
+ for (int i = 0; i < _allFiles.Length; i++)
+ {
+ int j = i;
+ mockControllerContext.Setup(o => o.HttpContext.Request.Files[j]).Returns(_allFiles[j].Value);
+ }
+
+ return new HttpFileCollectionValueProvider(mockControllerContext.Object);
+ }
+
+ private sealed class MockHttpPostedFile : HttpPostedFileBase
+ {
+ private readonly int _contentLength;
+ private readonly string _fileName;
+
+ public MockHttpPostedFile(int contentLength, string fileName)
+ {
+ _contentLength = contentLength;
+ _fileName = fileName;
+ }
+
+ public override int ContentLength
+ {
+ get { return _contentLength; }
+ }
+
+ public override string FileName
+ {
+ get { return _fileName; }
+ }
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Test/HttpGetAttributeTest.cs b/test/System.Web.Mvc.Test/Test/HttpGetAttributeTest.cs
new file mode 100644
index 00000000..cb4e09a0
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Test/HttpGetAttributeTest.cs
@@ -0,0 +1,25 @@
+using Xunit;
+
+namespace System.Web.Mvc.Test
+{
+ public class HttpGetAttributeTest
+ {
+ [Fact]
+ public void IsValidForRequestReturnsFalseIfHttpVerbIsNotPost()
+ {
+ HttpVerbAttributeHelper.TestHttpVerbAttributeWithInvalidVerb<HttpGetAttribute>("DELETE");
+ }
+
+ [Fact]
+ public void IsValidForRequestReturnsTrueIfHttpVerbIsPost()
+ {
+ HttpVerbAttributeHelper.TestHttpVerbAttributeWithValidVerb<HttpGetAttribute>("GET");
+ }
+
+ [Fact]
+ public void IsValidForRequestThrowsIfControllerContextIsNull()
+ {
+ HttpVerbAttributeHelper.TestHttpVerbAttributeNullControllerContext<HttpGetAttribute>();
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Test/HttpHandlerUtilTest.cs b/test/System.Web.Mvc.Test/Test/HttpHandlerUtilTest.cs
new file mode 100644
index 00000000..fdb16d51
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Test/HttpHandlerUtilTest.cs
@@ -0,0 +1,153 @@
+using System.IO;
+using System.Web.Hosting;
+using Moq;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Mvc.Test
+{
+ public class HttpHandlerUtilTest
+ {
+ [Fact]
+ public void WrapForServerExecute_BeginProcessRequest_DelegatesCorrectly()
+ {
+ // Arrange
+ IAsyncResult expectedResult = new Mock<IAsyncResult>().Object;
+ AsyncCallback cb = delegate { };
+
+ HttpContext httpContext = GetHttpContext();
+ Mock<IHttpAsyncHandler> mockHttpHandler = new Mock<IHttpAsyncHandler>();
+ mockHttpHandler.Setup(o => o.BeginProcessRequest(httpContext, cb, "extraData")).Returns(expectedResult);
+
+ IHttpAsyncHandler wrapper = (IHttpAsyncHandler)HttpHandlerUtil.WrapForServerExecute(mockHttpHandler.Object);
+
+ // Act
+ IAsyncResult actualResult = wrapper.BeginProcessRequest(httpContext, cb, "extraData");
+
+ // Assert
+ Assert.Equal(expectedResult, actualResult);
+ }
+
+ [Fact]
+ public void WrapForServerExecute_EndProcessRequest_DelegatesCorrectly()
+ {
+ // Arrange
+ IAsyncResult asyncResult = new Mock<IAsyncResult>().Object;
+
+ HttpContext httpContext = GetHttpContext();
+ Mock<IHttpAsyncHandler> mockHttpHandler = new Mock<IHttpAsyncHandler>();
+ mockHttpHandler.Setup(o => o.EndProcessRequest(asyncResult)).Verifiable();
+
+ IHttpAsyncHandler wrapper = (IHttpAsyncHandler)HttpHandlerUtil.WrapForServerExecute(mockHttpHandler.Object);
+
+ // Act
+ wrapper.EndProcessRequest(asyncResult);
+
+ // Assert
+ mockHttpHandler.Verify();
+ }
+
+ [Fact]
+ public void WrapForServerExecute_ProcessRequest_DelegatesCorrectly()
+ {
+ // Arrange
+ HttpContext httpContext = GetHttpContext();
+ Mock<IHttpHandler> mockHttpHandler = new Mock<IHttpHandler>();
+ mockHttpHandler.Setup(o => o.ProcessRequest(httpContext)).Verifiable();
+
+ IHttpHandler wrapper = HttpHandlerUtil.WrapForServerExecute(mockHttpHandler.Object);
+
+ // Act
+ wrapper.ProcessRequest(httpContext);
+
+ // Assert
+ mockHttpHandler.Verify();
+ }
+
+ [Fact]
+ public void WrapForServerExecute_ProcessRequest_PropagatesExceptionsIfNotHttpException()
+ {
+ // Arrange
+ HttpContext httpContext = GetHttpContext();
+ Mock<IHttpHandler> mockHttpHandler = new Mock<IHttpHandler>();
+ mockHttpHandler.Setup(o => o.ProcessRequest(httpContext)).Throws(new InvalidOperationException("Some exception."));
+
+ IHttpHandler wrapper = HttpHandlerUtil.WrapForServerExecute(mockHttpHandler.Object);
+
+ // Act & assert
+ Assert.Throws<InvalidOperationException>(
+ delegate { wrapper.ProcessRequest(httpContext); },
+ @"Some exception.");
+ }
+
+ [Fact]
+ public void WrapForServerExecute_ProcessRequest_PropagatesHttpExceptionIfStatusCode500()
+ {
+ // Arrange
+ HttpContext httpContext = GetHttpContext();
+ Mock<IHttpHandler> mockHttpHandler = new Mock<IHttpHandler>();
+ mockHttpHandler.Setup(o => o.ProcessRequest(httpContext)).Throws(new HttpException(500, "Some exception."));
+
+ IHttpHandler wrapper = HttpHandlerUtil.WrapForServerExecute(mockHttpHandler.Object);
+
+ // Act & assert
+ Assert.ThrowsHttpException(
+ delegate { wrapper.ProcessRequest(httpContext); },
+ @"Some exception.",
+ 500);
+ }
+
+ [Fact]
+ public void WrapForServerExecute_ProcessRequest_WrapsHttpExceptionIfStatusCodeNot500()
+ {
+ // Arrange
+ HttpContext httpContext = GetHttpContext();
+ Mock<IHttpHandler> mockHttpHandler = new Mock<IHttpHandler>();
+ mockHttpHandler.Setup(o => o.ProcessRequest(httpContext)).Throws(new HttpException(404, "Some exception."));
+
+ IHttpHandler wrapper = HttpHandlerUtil.WrapForServerExecute(mockHttpHandler.Object);
+
+ // Act & assert
+ HttpException outerException = Assert.ThrowsHttpException(
+ delegate { wrapper.ProcessRequest(httpContext); },
+ @"Execution of the child request failed. Please examine the InnerException for more information.",
+ 500);
+
+ HttpException innerException = outerException.InnerException as HttpException;
+ Assert.NotNull(innerException);
+ Assert.Equal(404, innerException.GetHttpCode());
+ Assert.Equal("Some exception.", innerException.Message);
+ }
+
+ [Fact]
+ public void WrapForServerExecute_ReturnsIHttpAsyncHandler()
+ {
+ // Arrange
+ IHttpAsyncHandler httpHandler = new Mock<IHttpAsyncHandler>().Object;
+
+ // Act
+ IHttpHandler wrapper = HttpHandlerUtil.WrapForServerExecute(httpHandler);
+
+ // Assert
+ Assert.True(wrapper is IHttpAsyncHandler);
+ }
+
+ [Fact]
+ public void WrapForServerExecute_ReturnsIHttpHandler()
+ {
+ // Arrange
+ IHttpHandler httpHandler = new Mock<IHttpHandler>().Object;
+
+ // Act
+ IHttpHandler wrapper = HttpHandlerUtil.WrapForServerExecute(httpHandler);
+
+ // Assert
+ Assert.False(wrapper is IHttpAsyncHandler);
+ }
+
+ private static HttpContext GetHttpContext()
+ {
+ return new HttpContext(new SimpleWorkerRequest("/", "/", "Page", "Query", TextWriter.Null));
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Test/HttpNotFoundResultTest.cs b/test/System.Web.Mvc.Test/Test/HttpNotFoundResultTest.cs
new file mode 100644
index 00000000..0e9007e3
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Test/HttpNotFoundResultTest.cs
@@ -0,0 +1,31 @@
+using Moq;
+using Xunit;
+
+namespace System.Web.Mvc.Test
+{
+ public class HttpNotFoundResultTest
+ {
+ [Fact]
+ public void ExecuteResult()
+ {
+ // Arrange
+ Mock<ControllerContext> mockControllerContext = new Mock<ControllerContext>();
+ mockControllerContext.SetupSet(c => c.HttpContext.Response.StatusCode = 404).Verifiable();
+ mockControllerContext.SetupSet(c => c.HttpContext.Response.StatusDescription = "Some description").Verifiable();
+
+ HttpNotFoundResult result = new HttpNotFoundResult("Some description");
+
+ // Act
+ result.ExecuteResult(mockControllerContext.Object);
+
+ // Assert
+ mockControllerContext.Verify();
+ }
+
+ [Fact]
+ public void StatusCode()
+ {
+ Assert.Equal(404, new HttpNotFoundResult().StatusCode);
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Test/HttpPostAttributeTest.cs b/test/System.Web.Mvc.Test/Test/HttpPostAttributeTest.cs
new file mode 100644
index 00000000..9c9fd4f0
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Test/HttpPostAttributeTest.cs
@@ -0,0 +1,25 @@
+using Xunit;
+
+namespace System.Web.Mvc.Test
+{
+ public class HttpPostAttributeTest
+ {
+ [Fact]
+ public void IsValidForRequestReturnsFalseIfHttpVerbIsNotPost()
+ {
+ HttpVerbAttributeHelper.TestHttpVerbAttributeWithInvalidVerb<HttpPostAttribute>("DELETE");
+ }
+
+ [Fact]
+ public void IsValidForRequestReturnsTrueIfHttpVerbIsPost()
+ {
+ HttpVerbAttributeHelper.TestHttpVerbAttributeWithValidVerb<HttpPostAttribute>("POST");
+ }
+
+ [Fact]
+ public void IsValidForRequestThrowsIfControllerContextIsNull()
+ {
+ HttpVerbAttributeHelper.TestHttpVerbAttributeNullControllerContext<HttpPostAttribute>();
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Test/HttpPostedFileBaseModelBinderTest.cs b/test/System.Web.Mvc.Test/Test/HttpPostedFileBaseModelBinderTest.cs
new file mode 100644
index 00000000..9bb1ff81
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Test/HttpPostedFileBaseModelBinderTest.cs
@@ -0,0 +1,90 @@
+using Moq;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Mvc.Test
+{
+ public class HttpPostedFileBaseModelBinderTest
+ {
+ [Fact]
+ public void BindModelReturnsEmptyResultIfEmptyFileInputElementInPost()
+ {
+ // Arrange
+ Mock<HttpPostedFileBase> mockFile = new Mock<HttpPostedFileBase>();
+ mockFile.Setup(f => f.ContentLength).Returns(0);
+ mockFile.Setup(f => f.FileName).Returns(String.Empty);
+ Mock<ControllerContext> mockControllerContext = new Mock<ControllerContext>();
+ mockControllerContext.SetupGet(c => c.HttpContext.Request.Files["fileName"]).Returns(mockFile.Object);
+
+ HttpPostedFileBaseModelBinder binder = new HttpPostedFileBaseModelBinder();
+ ModelBindingContext bindingContext = new ModelBindingContext() { ModelName = "fileName" };
+
+ // Act
+ object result = binder.BindModel(mockControllerContext.Object, bindingContext);
+
+ // Assert
+ Assert.Null(result);
+ }
+
+ [Fact]
+ public void BindModelReturnsNullIfNoFileInputElementInPost()
+ {
+ // Arrange
+ Mock<ControllerContext> mockControllerContext = new Mock<ControllerContext>();
+ mockControllerContext.Setup(c => c.HttpContext.Request.Files["fileName"]).Returns((HttpPostedFileBase)null);
+
+ HttpPostedFileBaseModelBinder binder = new HttpPostedFileBaseModelBinder();
+ ModelBindingContext bindingContext = new ModelBindingContext() { ModelName = "fileName" };
+
+ // Act
+ object result = binder.BindModel(mockControllerContext.Object, bindingContext);
+
+ // Assert
+ Assert.Null(result);
+ }
+
+ [Fact]
+ public void BindModelReturnsResultIfFileFound()
+ {
+ // Arrange
+ Mock<HttpPostedFileBase> mockFile = new Mock<HttpPostedFileBase>();
+ mockFile.Setup(f => f.ContentLength).Returns(1234);
+ mockFile.Setup(f => f.FileName).Returns("somefile");
+ Mock<ControllerContext> mockControllerContext = new Mock<ControllerContext>();
+ mockControllerContext.SetupGet(c => c.HttpContext.Request.Files["fileName"]).Returns(mockFile.Object);
+
+ HttpPostedFileBaseModelBinder binder = new HttpPostedFileBaseModelBinder();
+ ModelBindingContext bindingContext = new ModelBindingContext() { ModelName = "fileName" };
+
+ // Act
+ object result = binder.BindModel(mockControllerContext.Object, bindingContext);
+
+ // Assert
+ Assert.NotNull(result);
+ Assert.Same(mockFile.Object, result);
+ }
+
+ [Fact]
+ public void BindModelThrowsIfBindingContextIsNull()
+ {
+ // Arrange
+ ControllerContext controllerContext = new Mock<ControllerContext>().Object;
+ HttpPostedFileBaseModelBinder binder = new HttpPostedFileBaseModelBinder();
+
+ // Act & assert
+ Assert.ThrowsArgumentNull(
+ delegate { binder.BindModel(controllerContext, null); }, "bindingContext");
+ }
+
+ [Fact]
+ public void BindModelThrowsIfControllerContextIsNull()
+ {
+ // Arrange
+ HttpPostedFileBaseModelBinder binder = new HttpPostedFileBaseModelBinder();
+
+ // Act & assert
+ Assert.ThrowsArgumentNull(
+ delegate { binder.BindModel(null, null); }, "controllerContext");
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Test/HttpPutAttributeTest.cs b/test/System.Web.Mvc.Test/Test/HttpPutAttributeTest.cs
new file mode 100644
index 00000000..f7894e9e
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Test/HttpPutAttributeTest.cs
@@ -0,0 +1,25 @@
+using Xunit;
+
+namespace System.Web.Mvc.Test
+{
+ public class HttpPutAttributeTest
+ {
+ [Fact]
+ public void IsValidForRequestReturnsFalseIfHttpVerbIsNotPost()
+ {
+ HttpVerbAttributeHelper.TestHttpVerbAttributeWithInvalidVerb<HttpPutAttribute>("GET");
+ }
+
+ [Fact]
+ public void IsValidForRequestReturnsTrueIfHttpVerbIsPost()
+ {
+ HttpVerbAttributeHelper.TestHttpVerbAttributeWithValidVerb<HttpPutAttribute>("PUT");
+ }
+
+ [Fact]
+ public void IsValidForRequestThrowsIfControllerContextIsNull()
+ {
+ HttpVerbAttributeHelper.TestHttpVerbAttributeNullControllerContext<HttpPutAttribute>();
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Test/HttpRequestExtensionsTest.cs b/test/System.Web.Mvc.Test/Test/HttpRequestExtensionsTest.cs
new file mode 100644
index 00000000..4ffbf9a5
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Test/HttpRequestExtensionsTest.cs
@@ -0,0 +1,50 @@
+using Xunit;
+using Xunit.Extensions;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Mvc.Test
+{
+ [CLSCompliant(false)]
+ public class HttpRequestExtensionsTest
+ {
+ [Fact]
+ public void GetHttpMethodOverrideWithNullRequestThrows()
+ {
+ // Act & Assert
+ Assert.ThrowsArgumentNull(
+ () => HttpRequestExtensions.GetHttpMethodOverride(null),
+ "request"
+ );
+ }
+
+ [Theory]
+ [InlineData("GET", "PUT", null, null, "GET")] // Cannot override GET with header PUT
+ [InlineData("GET", null, "PUT", null, "GET")] // Cannot override GET with form PUT
+ [InlineData("GET", null, null, "PUT", "GET")] // Cannot override GET with query string PUT
+ [InlineData("PUT", "GET", null, null, "PUT")] // Cannot override PUT with GET
+ [InlineData("PUT", "POST", null, null, "PUT")] // Cannot override PUT with POST
+ [InlineData("POST", "GET", null, null, "POST")] // Cannot override POST with GET
+ [InlineData("POST", "POST", null, null, "POST")] // Cannot override POST with POST
+ [InlineData("POST", "PUT", null, null, "PUT")] // Can override POST with header PUT
+ [InlineData("POST", null, "PUT", null, "PUT")] // Can override POST with form PUT
+ [InlineData("POST", null, null, "PUT", "PUT")] // Can override POST with query string PUT
+ [InlineData("POST", "PUT", "BOGUS", null, "PUT")] // Header override wins over form override
+ [InlineData("POST", "PUT", null, "BOGUS", "PUT")] // Header override wins over query string override
+ [InlineData("POST", null, "PUT", "BOGUS", "PUT")] // Form override wins over query string override
+ public void TestHttpMethodOverride(string httpRequestVerb,
+ string httpHeaderVerb,
+ string httpFormVerb,
+ string httpQueryStringVerb,
+ string expectedMethod)
+ {
+ // Arrange
+ ControllerContext context = AcceptVerbsAttributeTest.GetControllerContextWithHttpVerb(httpRequestVerb, httpHeaderVerb, httpFormVerb, httpQueryStringVerb);
+
+ // Act
+ string methodOverride = context.RequestContext.HttpContext.Request.GetHttpMethodOverride();
+
+ // Assert
+ Assert.Equal(expectedMethod, methodOverride);
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Test/HttpStatusCodeResultTest.cs b/test/System.Web.Mvc.Test/Test/HttpStatusCodeResultTest.cs
new file mode 100644
index 00000000..c099044b
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Test/HttpStatusCodeResultTest.cs
@@ -0,0 +1,96 @@
+using System.Net;
+using Moq;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Mvc.Test
+{
+ public class HttpStatusCodeResultTest
+ {
+ [Fact]
+ public void ExecuteResult()
+ {
+ // Arrange
+ Mock<ControllerContext> mockControllerContext = new Mock<ControllerContext>();
+ mockControllerContext.SetupSet(c => c.HttpContext.Response.StatusCode = 666).Verifiable();
+
+ HttpStatusCodeResult result = new HttpStatusCodeResult(666);
+
+ // Act
+ result.ExecuteResult(mockControllerContext.Object);
+
+ // Assert
+ mockControllerContext.Verify();
+ }
+
+ [Fact]
+ public void ExecuteResultWithDescription()
+ {
+ // Arrange
+ Mock<ControllerContext> mockControllerContext = new Mock<ControllerContext>();
+ mockControllerContext.SetupSet(c => c.HttpContext.Response.StatusCode = 666).Verifiable();
+ mockControllerContext.SetupSet(c => c.HttpContext.Response.StatusDescription = "Foo Bar").Verifiable();
+ HttpStatusCodeResult result = new HttpStatusCodeResult(666, "Foo Bar");
+
+ // Act
+ result.ExecuteResult(mockControllerContext.Object);
+
+ // Assert
+ mockControllerContext.Verify();
+ }
+
+ [Fact]
+ public void ExecuteResultWithNullContextThrows()
+ {
+ // Act and Assert
+ Assert.ThrowsArgumentNull(delegate { new HttpStatusCodeResult(1).ExecuteResult(context: null); }, "context");
+ }
+
+ [Fact]
+ public void StatusCode()
+ {
+ // Assert
+ Assert.Equal(123, new HttpStatusCodeResult(123).StatusCode);
+ Assert.Equal(234, new HttpStatusCodeResult(234, "foobar").StatusCode);
+ }
+
+ [Fact]
+ public void StatusDescription()
+ {
+ // Assert
+ Assert.Null(new HttpStatusCodeResult(123).StatusDescription);
+ Assert.Equal("foobar", new HttpStatusCodeResult(234, "foobar").StatusDescription);
+ }
+
+ [Fact]
+ public void HttpStatusCodeAndStatusDescription()
+ {
+ // Arrange
+ int unusedStatusCode = 306;
+
+ // Act
+ HttpStatusCodeResult result = new HttpStatusCodeResult(HttpStatusCode.Unused, "foobar");
+
+ // Assert
+ Assert.Equal(unusedStatusCode, result.StatusCode);
+ Assert.Equal("foobar", result.StatusDescription);
+ }
+
+ [Fact]
+ public void ExecuteResultWithHttpStatusCode()
+ {
+ // Arrange
+ int unusedStatusCode = 306;
+ Mock<ControllerContext> mockControllerContext = new Mock<ControllerContext>();
+ mockControllerContext.SetupSet(c => c.HttpContext.Response.StatusCode = unusedStatusCode).Verifiable();
+
+ HttpStatusCodeResult result = new HttpStatusCodeResult(HttpStatusCode.Unused);
+
+ // Act
+ result.ExecuteResult(mockControllerContext.Object);
+
+ // Assert
+ mockControllerContext.Verify();
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Test/HttpUnauthorizedResultTest.cs b/test/System.Web.Mvc.Test/Test/HttpUnauthorizedResultTest.cs
new file mode 100644
index 00000000..70cba3ee
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Test/HttpUnauthorizedResultTest.cs
@@ -0,0 +1,31 @@
+using Moq;
+using Xunit;
+
+namespace System.Web.Mvc.Test
+{
+ public class HttpUnauthorizedResultTest
+ {
+ [Fact]
+ public void ExecuteResult()
+ {
+ // Arrange
+ Mock<ControllerContext> mockControllerContext = new Mock<ControllerContext>();
+ mockControllerContext.SetupSet(c => c.HttpContext.Response.StatusCode = 401).Verifiable();
+ mockControllerContext.SetupSet(c => c.HttpContext.Response.StatusDescription = "Some description").Verifiable();
+
+ HttpUnauthorizedResult result = new HttpUnauthorizedResult("Some description");
+
+ // Act
+ result.ExecuteResult(mockControllerContext.Object);
+
+ // Assert
+ mockControllerContext.Verify();
+ }
+
+ [Fact]
+ public void StatusCode()
+ {
+ Assert.Equal(401, new HttpUnauthorizedResult().StatusCode);
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Test/HttpVerbAttributeHelper.cs b/test/System.Web.Mvc.Test/Test/HttpVerbAttributeHelper.cs
new file mode 100644
index 00000000..3436db1c
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Test/HttpVerbAttributeHelper.cs
@@ -0,0 +1,46 @@
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Mvc.Test
+{
+ internal static class HttpVerbAttributeHelper
+ {
+ internal static void TestHttpVerbAttributeNullControllerContext<THttpVerb>()
+ where THttpVerb : ActionMethodSelectorAttribute, new()
+ {
+ // Arrange
+ ActionMethodSelectorAttribute attribute = new THttpVerb();
+
+ // Act & Assert
+ Assert.ThrowsArgumentNull(
+ delegate { attribute.IsValidForRequest(null, null); }, "controllerContext");
+ }
+
+ internal static void TestHttpVerbAttributeWithValidVerb<THttpVerb>(string validVerb)
+ where THttpVerb : ActionMethodSelectorAttribute, new()
+ {
+ // Arrange
+ ActionMethodSelectorAttribute attribute = new THttpVerb();
+ ControllerContext context = AcceptVerbsAttributeTest.GetControllerContextWithHttpVerb(validVerb);
+
+ // Act
+ bool result = attribute.IsValidForRequest(context, null);
+
+ // Assert
+ Assert.True(result);
+ }
+
+ internal static void TestHttpVerbAttributeWithInvalidVerb<THttpVerb>(string invalidVerb)
+ where THttpVerb : ActionMethodSelectorAttribute, new()
+ {
+ // Arrange
+ ActionMethodSelectorAttribute attribute = new THttpVerb();
+ ControllerContext context = AcceptVerbsAttributeTest.GetControllerContextWithHttpVerb(invalidVerb);
+
+ // Act
+ bool result = attribute.IsValidForRequest(context, null);
+
+ // Assert
+ Assert.False(result);
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Test/JavaScriptResultTest.cs b/test/System.Web.Mvc.Test/Test/JavaScriptResultTest.cs
new file mode 100644
index 00000000..8902b9f2
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Test/JavaScriptResultTest.cs
@@ -0,0 +1,69 @@
+using Moq;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Mvc.Test
+{
+ public class JavaScriptResultTest
+ {
+ [Fact]
+ public void AllPropertiesDefaultToNull()
+ {
+ // Act
+ JavaScriptResult result = new JavaScriptResult();
+
+ // Assert
+ Assert.Null(result.Script);
+ }
+
+ [Fact]
+ public void ExecuteResult()
+ {
+ // Arrange
+ string script = "alert('foo');";
+ string contentType = "application/x-javascript";
+
+ // Arrange expectations
+ Mock<ControllerContext> mockControllerContext = new Mock<ControllerContext>(MockBehavior.Strict);
+ mockControllerContext.SetupSet(c => c.HttpContext.Response.ContentType = contentType).Verifiable();
+ mockControllerContext.Setup(c => c.HttpContext.Response.Write(script)).Verifiable();
+
+ JavaScriptResult result = new JavaScriptResult
+ {
+ Script = script
+ };
+
+ // Act
+ result.ExecuteResult(mockControllerContext.Object);
+
+ // Assert
+ mockControllerContext.Verify();
+ }
+
+ [Fact]
+ public void ExecuteResultWithNullContextThrows()
+ {
+ Assert.ThrowsArgumentNull(
+ delegate { new JavaScriptResult().ExecuteResult(null /* context */); }, "context");
+ }
+
+ [Fact]
+ public void NullScriptIsNotOutput()
+ {
+ // Arrange
+ string contentType = "application/x-javascript";
+
+ // Arrange expectations
+ Mock<ControllerContext> mockControllerContext = new Mock<ControllerContext>();
+ mockControllerContext.SetupSet(c => c.HttpContext.Response.ContentType = contentType).Verifiable();
+
+ JavaScriptResult result = new JavaScriptResult();
+
+ // Act
+ result.ExecuteResult(mockControllerContext.Object);
+
+ // Assert
+ mockControllerContext.Verify();
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Test/JsonResultTest.cs b/test/System.Web.Mvc.Test/Test/JsonResultTest.cs
new file mode 100644
index 00000000..de43d1fc
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Test/JsonResultTest.cs
@@ -0,0 +1,292 @@
+using System.Text;
+using Moq;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Mvc.Test
+{
+ public class JsonResultTest
+ {
+ private static readonly object _jsonData = new object[] { 1, 2, "three", "four" };
+ private static readonly string _jsonSerializedData = "[1,2,\"three\",\"four\"]";
+
+ [Fact]
+ public void PropertyDefaults()
+ {
+ // Act
+ JsonResult result = new JsonResult();
+
+ // Assert
+ Assert.Null(result.Data);
+ Assert.Null(result.ContentEncoding);
+ Assert.Null(result.ContentType);
+ Assert.Null(result.MaxJsonLength);
+ Assert.Null(result.RecursionLimit);
+ Assert.Equal(JsonRequestBehavior.DenyGet, result.JsonRequestBehavior);
+ }
+
+ [Fact]
+ public void EmptyContentTypeRendersDefault()
+ {
+ // Arrange
+ object data = _jsonData;
+ Encoding contentEncoding = Encoding.UTF8;
+
+ // Arrange expectations
+ Mock<ControllerContext> mockControllerContext = new Mock<ControllerContext>(MockBehavior.Strict);
+ mockControllerContext.SetupGet(c => c.HttpContext.Request.HttpMethod).Returns("POST").Verifiable();
+ mockControllerContext.SetupSet(c => c.HttpContext.Response.ContentType = "application/json").Verifiable();
+ mockControllerContext.SetupSet(c => c.HttpContext.Response.ContentEncoding = contentEncoding).Verifiable();
+ mockControllerContext.Setup(c => c.HttpContext.Response.Write(_jsonSerializedData)).Verifiable();
+
+ JsonResult result = new JsonResult
+ {
+ Data = data,
+ ContentType = String.Empty,
+ ContentEncoding = contentEncoding
+ };
+
+ // Act
+ result.ExecuteResult(mockControllerContext.Object);
+
+ // Assert
+ mockControllerContext.Verify();
+ }
+
+ [Fact]
+ public void ExecuteResult()
+ {
+ // Arrange
+ object data = _jsonData;
+ string contentType = "Some content type.";
+ Encoding contentEncoding = Encoding.UTF8;
+
+ // Arrange expectations
+ Mock<ControllerContext> mockControllerContext = new Mock<ControllerContext>(MockBehavior.Strict);
+ mockControllerContext.SetupGet(c => c.HttpContext.Request.HttpMethod).Returns("POST").Verifiable();
+ mockControllerContext.SetupSet(c => c.HttpContext.Response.ContentType = contentType).Verifiable();
+ mockControllerContext.SetupSet(c => c.HttpContext.Response.ContentEncoding = contentEncoding).Verifiable();
+ mockControllerContext.Setup(c => c.HttpContext.Response.Write(_jsonSerializedData)).Verifiable();
+
+ JsonResult result = new JsonResult
+ {
+ Data = data,
+ ContentType = contentType,
+ ContentEncoding = contentEncoding
+ };
+
+ // Act
+ result.ExecuteResult(mockControllerContext.Object);
+
+ // Assert
+ mockControllerContext.Verify();
+ }
+
+ [Fact]
+ public void ExecuteResultWithNullContextThrows()
+ {
+ Assert.ThrowsArgumentNull(
+ delegate { new JsonResult().ExecuteResult(null /* context */); }, "context");
+ }
+
+ [Fact]
+ public void NullContentIsNotOutput()
+ {
+ // Arrange
+ string contentType = "Some content type.";
+ Encoding contentEncoding = Encoding.UTF8;
+
+ // Arrange expectations
+ Mock<ControllerContext> mockControllerContext = new Mock<ControllerContext>();
+ mockControllerContext.SetupGet(c => c.HttpContext.Request.HttpMethod).Returns("POST").Verifiable();
+ mockControllerContext.SetupSet(c => c.HttpContext.Response.ContentType = contentType).Verifiable();
+ mockControllerContext.SetupSet(c => c.HttpContext.Response.ContentEncoding = contentEncoding).Verifiable();
+
+ JsonResult result = new JsonResult
+ {
+ ContentType = contentType,
+ ContentEncoding = contentEncoding
+ };
+
+ // Act
+ result.ExecuteResult(mockControllerContext.Object);
+
+ // Assert
+ mockControllerContext.Verify();
+ }
+
+ [Fact]
+ public void NullContentEncodingIsNotOutput()
+ {
+ // Arrange
+ object data = _jsonData;
+ string contentType = "Some content type.";
+
+ // Arrange expectations
+ Mock<ControllerContext> mockControllerContext = new Mock<ControllerContext>(MockBehavior.Strict);
+ mockControllerContext.SetupGet(c => c.HttpContext.Request.HttpMethod).Returns("POST").Verifiable();
+ mockControllerContext.SetupSet(c => c.HttpContext.Response.ContentType = contentType).Verifiable();
+ mockControllerContext.Setup(c => c.HttpContext.Response.Write(_jsonSerializedData)).Verifiable();
+
+ JsonResult result = new JsonResult
+ {
+ Data = data,
+ ContentType = contentType,
+ };
+
+ // Act
+ result.ExecuteResult(mockControllerContext.Object);
+
+ // Assert
+ mockControllerContext.Verify();
+ }
+
+ [Fact]
+ public void NullContentTypeRendersDefault()
+ {
+ // Arrange
+ object data = _jsonData;
+ Encoding contentEncoding = Encoding.UTF8;
+
+ // Arrange expectations
+ Mock<ControllerContext> mockControllerContext = new Mock<ControllerContext>(MockBehavior.Strict);
+ mockControllerContext.SetupGet(c => c.HttpContext.Request.HttpMethod).Returns("POST").Verifiable();
+ mockControllerContext.SetupSet(c => c.HttpContext.Response.ContentType = "application/json").Verifiable();
+ mockControllerContext.SetupSet(c => c.HttpContext.Response.ContentEncoding = contentEncoding).Verifiable();
+ mockControllerContext.Setup(c => c.HttpContext.Response.Write(_jsonSerializedData)).Verifiable();
+
+ JsonResult result = new JsonResult
+ {
+ Data = data,
+ ContentEncoding = contentEncoding
+ };
+
+ // Act
+ result.ExecuteResult(mockControllerContext.Object);
+
+ // Assert
+ mockControllerContext.Verify();
+ }
+
+ [Fact]
+ public void NullMaxJsonLengthDefaultIsUsed()
+ {
+ // Arrange
+ string data = new String('1', 2100000);
+
+ Mock<ControllerContext> mockControllerContext = new Mock<ControllerContext>();
+ mockControllerContext.SetupGet(c => c.HttpContext.Request.HttpMethod).Returns("POST").Verifiable();
+ mockControllerContext.SetupSet(c => c.HttpContext.Response.ContentType = "application/json").Verifiable();
+
+ JsonResult result = new JsonResult
+ {
+ Data = data
+ };
+
+ // Act & Assert
+ Assert.Throws<InvalidOperationException>(
+ () => result.ExecuteResult(mockControllerContext.Object),
+ "Error during serialization or deserialization using the JSON JavaScriptSerializer. The length of the string exceeds the value set on the maxJsonLength property.");
+ }
+
+ [Fact]
+ public void MaxJsonLengthIsPassedToSerializer()
+ {
+ // Arrange
+ string data = new String('1', 2100000);
+ string jsonData = "\"" + data + "\"";
+
+ Mock<ControllerContext> mockControllerContext = new Mock<ControllerContext>();
+ mockControllerContext.SetupGet(c => c.HttpContext.Request.HttpMethod).Returns("POST").Verifiable();
+ mockControllerContext.SetupSet(c => c.HttpContext.Response.ContentType = "application/json").Verifiable();
+ mockControllerContext.Setup(c => c.HttpContext.Response.Write(jsonData)).Verifiable();
+
+ JsonResult result = new JsonResult
+ {
+ Data = data,
+ MaxJsonLength = 2200000
+ };
+
+ // Act
+ result.ExecuteResult(mockControllerContext.Object);
+
+ // Assert
+ mockControllerContext.Verify();
+ }
+
+ [Fact]
+ public void RecursionLimitIsPassedToSerilizer()
+ {
+ // Arrange
+ Tuple<string, Tuple<string, Tuple<string, string>>> data =
+ new Tuple<string, Tuple<string, Tuple<string, string>>>("key1",
+ new Tuple<string, Tuple<string, string>>("key2",
+ new Tuple<string, string>("key3", "value")
+ )
+ );
+ string jsonData = "{\"Item1\":\"key1\",\"Item2\":{\"Item1\":\"key2\",\"Item2\":{\"Item1\":\"key3\",\"Item2\":\"value\"}}}";
+
+ Mock<ControllerContext> mockControllerContext = new Mock<ControllerContext>();
+ mockControllerContext.SetupGet(c => c.HttpContext.Request.HttpMethod).Returns("POST").Verifiable();
+ mockControllerContext.SetupSet(c => c.HttpContext.Response.ContentType = "application/json").Verifiable();
+ mockControllerContext.Setup(c => c.HttpContext.Response.Write(jsonData)).Verifiable();
+
+ JsonResult result = new JsonResult
+ {
+ Data = data,
+ RecursionLimit = 2
+ };
+
+ // Act & Assert
+ Assert.Throws<ArgumentException>(
+ () => result.ExecuteResult(mockControllerContext.Object),
+ "RecursionLimit exceeded.");
+ }
+
+ [Fact]
+ public void NullRecursionLimitDefaultIsUsed()
+ {
+ // Arrange
+ Tuple<string, Tuple<string, Tuple<string, string>>> data =
+ new Tuple<string, Tuple<string, Tuple<string, string>>>("key1",
+ new Tuple<string, Tuple<string, string>>("key2",
+ new Tuple<string, string>("key3", "value")
+ )
+ );
+ string jsonData = "{\"Item1\":\"key1\",\"Item2\":{\"Item1\":\"key2\",\"Item2\":{\"Item1\":\"key3\",\"Item2\":\"value\"}}}";
+
+ Mock<ControllerContext> mockControllerContext = new Mock<ControllerContext>();
+ mockControllerContext.SetupGet(c => c.HttpContext.Request.HttpMethod).Returns("POST").Verifiable();
+ mockControllerContext.SetupSet(c => c.HttpContext.Response.ContentType = "application/json").Verifiable();
+ mockControllerContext.Setup(c => c.HttpContext.Response.Write(jsonData)).Verifiable();
+
+ JsonResult result = new JsonResult
+ {
+ Data = data
+ };
+
+ // Act
+ result.ExecuteResult(mockControllerContext.Object);
+
+ // Assert
+ mockControllerContext.Verify();
+ }
+
+ [Fact]
+ public void GetRequestBlocked()
+ {
+ // Arrange expectations
+ Mock<ControllerContext> mockControllerContext = new Mock<ControllerContext>(MockBehavior.Strict);
+ mockControllerContext.SetupGet(c => c.HttpContext.Request.HttpMethod).Returns("GET").Verifiable();
+
+ JsonResult result = new JsonResult();
+
+ // Act & Assert
+ Assert.Throws<InvalidOperationException>(
+ () => result.ExecuteResult(mockControllerContext.Object),
+ "This request has been blocked because sensitive information could be disclosed to third party web sites when this is used in a GET request. To allow GET requests, set JsonRequestBehavior to AllowGet.");
+
+ mockControllerContext.Verify();
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Test/JsonValueProviderFactoryTest.cs b/test/System.Web.Mvc.Test/Test/JsonValueProviderFactoryTest.cs
new file mode 100644
index 00000000..69a51679
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Test/JsonValueProviderFactoryTest.cs
@@ -0,0 +1,152 @@
+using System.Globalization;
+using System.IO;
+using System.Text;
+using Moq;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Mvc.Test
+{
+ public class JsonValueProviderFactoryTest
+ {
+ [Fact]
+ public void GetValueProvider_NullControllerContext_ThrowsException()
+ {
+ JsonValueProviderFactory factory = new JsonValueProviderFactory();
+
+ Assert.ThrowsArgumentNull(delegate() { factory.GetValueProvider(controllerContext: null); }, "controllerContext");
+ }
+
+ [Fact]
+ public void GetValueProvider_SimpleArrayJsonObject()
+ {
+ const string jsonString = @"
+[ ""abc"", null, ""foobar"" ]
+";
+ ControllerContext cc = GetJsonEnabledControllerContext(jsonString);
+ JsonValueProviderFactory factory = new JsonValueProviderFactory();
+
+ // Act & assert
+ IValueProvider valueProvider = factory.GetValueProvider(cc);
+ Assert.True(valueProvider.ContainsPrefix("[0]"));
+ Assert.True(valueProvider.ContainsPrefix("[2]"));
+ Assert.False(valueProvider.ContainsPrefix("[3]"));
+
+ ValueProviderResult vpResult1 = valueProvider.GetValue("[0]");
+ Assert.Equal("abc", vpResult1.AttemptedValue);
+ Assert.Equal(CultureInfo.CurrentCulture, vpResult1.Culture);
+
+ // null values should exist in the backing store as actual entries
+ ValueProviderResult vpResult2 = valueProvider.GetValue("[1]");
+ Assert.NotNull(vpResult2);
+ Assert.Null(vpResult2.RawValue);
+ }
+
+ [Fact]
+ public void GetValueProvider_SimpleDictionaryJsonObject()
+ {
+ const string jsonString = @"
+{ ""FirstName"":""John"",
+ ""LastName"": ""Doe""
+}";
+
+ ControllerContext cc = GetJsonEnabledControllerContext(jsonString);
+ JsonValueProviderFactory factory = new JsonValueProviderFactory();
+
+ // Act & assert
+ IValueProvider valueProvider = factory.GetValueProvider(cc);
+ Assert.True(valueProvider.ContainsPrefix("firstname"));
+
+ ValueProviderResult vpResult1 = valueProvider.GetValue("firstname");
+ Assert.Equal("John", vpResult1.AttemptedValue);
+ Assert.Equal(CultureInfo.CurrentCulture, vpResult1.Culture);
+ }
+
+ [Fact]
+ public void GetValueProvider_ComplexJsonObject()
+ {
+ // Arrange
+ const string jsonString = @"
+[
+ {
+ ""BillingAddress"": {
+ ""Street"": ""1 Microsoft Way"",
+ ""City"": ""Redmond"",
+ ""State"": ""WA"",
+ ""ZIP"": 98052 },
+ ""ShippingAddress"": {
+ ""Street"": ""123 Anywhere Ln"",
+ ""City"": ""Anytown"",
+ ""State"": ""ZZ"",
+ ""ZIP"": 99999 }
+ },
+ {
+ ""Enchiladas"": [ ""Delicious"", ""Nutritious""]
+ }
+]
+";
+
+ ControllerContext cc = GetJsonEnabledControllerContext(jsonString);
+ JsonValueProviderFactory factory = new JsonValueProviderFactory();
+
+ // Act & assert
+ IValueProvider valueProvider = factory.GetValueProvider(cc);
+ Assert.NotNull(valueProvider);
+
+ Assert.True(valueProvider.ContainsPrefix("[0].billingaddress"));
+ Assert.Null(valueProvider.GetValue("[0].billingaddress"));
+
+ Assert.True(valueProvider.ContainsPrefix("[0].billingaddress.street"));
+ Assert.NotNull(valueProvider.GetValue("[0].billingaddress.street"));
+
+ ValueProviderResult vpResult1 = valueProvider.GetValue("[1].enchiladas[0]");
+ Assert.NotNull(vpResult1);
+ Assert.Equal("Delicious", vpResult1.AttemptedValue);
+ Assert.Equal(CultureInfo.CurrentCulture, vpResult1.Culture);
+ }
+
+ [Fact]
+ public void GetValueProvider_NoJsonBody_ReturnsNull()
+ {
+ // Arrange
+ Mock<ControllerContext> mockControllerContext = new Mock<ControllerContext>();
+ mockControllerContext.Setup(o => o.HttpContext.Request.ContentType).Returns("application/json");
+ mockControllerContext.Setup(o => o.HttpContext.Request.InputStream).Returns(new MemoryStream());
+
+ JsonValueProviderFactory factory = new JsonValueProviderFactory();
+
+ // Act
+ IValueProvider valueProvider = factory.GetValueProvider(mockControllerContext.Object);
+
+ // Assert
+ Assert.Null(valueProvider);
+ }
+
+ [Fact]
+ public void GetValueProvider_NotJsonRequest_ReturnsNull()
+ {
+ // Arrange
+ Mock<ControllerContext> mockControllerContext = new Mock<ControllerContext>();
+ mockControllerContext.Setup(o => o.HttpContext.Request.ContentType).Returns("not JSON");
+
+ JsonValueProviderFactory factory = new JsonValueProviderFactory();
+
+ // Act
+ IValueProvider valueProvider = factory.GetValueProvider(mockControllerContext.Object);
+
+ // Assert
+ Assert.Null(valueProvider);
+ }
+
+ private static ControllerContext GetJsonEnabledControllerContext(string jsonString)
+ {
+ byte[] jsonBytes = Encoding.UTF8.GetBytes(jsonString);
+ MemoryStream jsonStream = new MemoryStream(jsonBytes);
+
+ Mock<ControllerContext> mockControllerContext = new Mock<ControllerContext>();
+ mockControllerContext.Setup(o => o.HttpContext.Request.ContentType).Returns("application/json");
+ mockControllerContext.Setup(o => o.HttpContext.Request.InputStream).Returns(jsonStream);
+ return mockControllerContext.Object;
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Test/LinqBinaryModelBinderTest.cs b/test/System.Web.Mvc.Test/Test/LinqBinaryModelBinderTest.cs
new file mode 100644
index 00000000..70dd4651
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Test/LinqBinaryModelBinderTest.cs
@@ -0,0 +1,120 @@
+using System.Data.Linq;
+using Microsoft.Web.UnitTestUtil;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Mvc.Test
+{
+ public class LinqBinaryModelBinderTest
+ {
+ [Fact]
+ public void BindModelWithNonExistentValueReturnsNull()
+ {
+ // Arrange
+ SimpleValueProvider valueProvider = new SimpleValueProvider()
+ {
+ { "foo", null }
+ };
+
+ ModelBindingContext bindingContext = new ModelBindingContext()
+ {
+ ModelName = "foo",
+ ValueProvider = valueProvider
+ };
+
+ LinqBinaryModelBinder binder = new LinqBinaryModelBinder();
+
+ // Act
+ object binderResult = binder.BindModel(null, bindingContext);
+
+ // Assert
+ Assert.Null(binderResult);
+ }
+
+ [Fact]
+ public void BinderWithEmptyStringValueReturnsNull()
+ {
+ // Arrange
+ SimpleValueProvider valueProvider = new SimpleValueProvider()
+ {
+ { "foo", "" }
+ };
+
+ ModelBindingContext bindingContext = new ModelBindingContext()
+ {
+ ModelName = "foo",
+ ValueProvider = valueProvider
+ };
+
+ LinqBinaryModelBinder binder = new LinqBinaryModelBinder();
+
+ // Act
+ object binderResult = binder.BindModel(null, bindingContext);
+
+ // Assert
+ Assert.Null(binderResult);
+ }
+
+ [Fact]
+ public void BindModelThrowsIfBindingContextIsNull()
+ {
+ // Arrange
+ LinqBinaryModelBinder binder = new LinqBinaryModelBinder();
+
+ // Act & assert
+ Assert.ThrowsArgumentNull(
+ delegate { binder.BindModel(null, null); }, "bindingContext");
+ }
+
+ [Fact]
+ public void BindModelWithBase64QuotedValueReturnsBinary()
+ {
+ // Arrange
+ string base64Value = ByteArrayModelBinderTest.Base64TestString;
+
+ SimpleValueProvider valueProvider = new SimpleValueProvider()
+ {
+ { "foo", "\"" + base64Value + "\"" }
+ };
+
+ ModelBindingContext bindingContext = new ModelBindingContext()
+ {
+ ModelName = "foo",
+ ValueProvider = valueProvider
+ };
+
+ LinqBinaryModelBinder binder = new LinqBinaryModelBinder();
+
+ // Act
+ Binary boundValue = binder.BindModel(null, bindingContext) as Binary;
+
+ // Assert
+ Assert.Equal(ByteArrayModelBinderTest.Base64TestBytes, boundValue);
+ }
+
+ [Fact]
+ public void BindModelWithBase64UnquotedValueReturnsBinary()
+ {
+ // Arrange
+ string base64Value = ByteArrayModelBinderTest.Base64TestString;
+ SimpleValueProvider valueProvider = new SimpleValueProvider()
+ {
+ { "foo", base64Value }
+ };
+
+ ModelBindingContext bindingContext = new ModelBindingContext()
+ {
+ ModelName = "foo",
+ ValueProvider = valueProvider
+ };
+
+ LinqBinaryModelBinder binder = new LinqBinaryModelBinder();
+
+ // Act
+ Binary boundValue = binder.BindModel(null, bindingContext) as Binary;
+
+ // Assert
+ Assert.Equal(ByteArrayModelBinderTest.Base64TestBytes, boundValue);
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Test/MockBuildManager.cs b/test/System.Web.Mvc.Test/Test/MockBuildManager.cs
new file mode 100644
index 00000000..a5f6732c
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Test/MockBuildManager.cs
@@ -0,0 +1,80 @@
+using System.Collections;
+using System.Collections.Generic;
+using System.IO;
+using System.Reflection;
+
+namespace System.Web.Mvc.Test
+{
+ // Custom mock IBuildManager since the mock framework doesn't support mocking internal types
+ public class MockBuildManager : IBuildManager
+ {
+ private Assembly[] _referencedAssemblies;
+
+ private Type _compiledType;
+ private string _expectedVirtualPath;
+ private bool _fileExists = true;
+
+ public readonly Dictionary<string, Stream> CachedFileStore = new Dictionary<string, Stream>(StringComparer.OrdinalIgnoreCase);
+
+ public MockBuildManager()
+ : this(new Assembly[] { typeof(MockBuildManager).Assembly })
+ {
+ }
+
+ public MockBuildManager(Assembly[] referencedAssemblies)
+ {
+ _referencedAssemblies = referencedAssemblies;
+ }
+
+ public MockBuildManager(string expectedVirtualPath, bool fileExists)
+ {
+ _expectedVirtualPath = expectedVirtualPath;
+ _fileExists = fileExists;
+ }
+
+ public MockBuildManager(string expectedVirtualPath, Type compiledType)
+ {
+ _expectedVirtualPath = expectedVirtualPath;
+ _compiledType = compiledType;
+ }
+
+ bool IBuildManager.FileExists(string virtualPath)
+ {
+ if (_expectedVirtualPath == virtualPath)
+ {
+ return _fileExists;
+ }
+
+ throw new InvalidOperationException("Unexpected call to IBuildManager.FileExists()");
+ }
+
+ public Type GetCompiledType(string virtualPath)
+ {
+ if (_expectedVirtualPath == virtualPath)
+ {
+ return _compiledType;
+ }
+
+ throw new InvalidOperationException("Unexpected call to IBuildManager.GetCompiledType()");
+ }
+
+ ICollection IBuildManager.GetReferencedAssemblies()
+ {
+ return _referencedAssemblies;
+ }
+
+ Stream IBuildManager.ReadCachedFile(string fileName)
+ {
+ Stream stream;
+ CachedFileStore.TryGetValue(fileName, out stream);
+ return stream;
+ }
+
+ Stream IBuildManager.CreateCachedFile(string fileName)
+ {
+ MemoryStream stream = new MemoryStream();
+ CachedFileStore[fileName] = stream;
+ return stream;
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Test/MockHelpers.cs b/test/System.Web.Mvc.Test/Test/MockHelpers.cs
new file mode 100644
index 00000000..307601e3
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Test/MockHelpers.cs
@@ -0,0 +1,13 @@
+using Moq;
+using Moq.Language.Flow;
+
+namespace System.Web.Mvc.Test
+{
+ public static class MockHelpers
+ {
+ public static ISetup<HttpContextBase> ExpectMvcVersionResponseHeader(this Mock<HttpContextBase> mock)
+ {
+ return mock.Setup(r => r.Response.AppendHeader(MvcHandler.MvcVersionHeaderName, "4.0"));
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Test/MockableUnvalidatedRequestValues.cs b/test/System.Web.Mvc.Test/Test/MockableUnvalidatedRequestValues.cs
new file mode 100644
index 00000000..8de57b53
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Test/MockableUnvalidatedRequestValues.cs
@@ -0,0 +1,11 @@
+using System.Collections.Specialized;
+
+namespace System.Web.Mvc.Test
+{
+ public abstract class MockableUnvalidatedRequestValues : IUnvalidatedRequestValues
+ {
+ public abstract NameValueCollection Form { get; }
+ public abstract NameValueCollection QueryString { get; }
+ public abstract string this[string key] { get; }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Test/ModelBinderAttributeTest.cs b/test/System.Web.Mvc.Test/Test/ModelBinderAttributeTest.cs
new file mode 100644
index 00000000..3bfeef21
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Test/ModelBinderAttributeTest.cs
@@ -0,0 +1,85 @@
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Mvc.Test
+{
+ public class ModelBinderAttributeTest
+ {
+ [Fact]
+ public void ConstructorWithInvalidBinderTypeThrows()
+ {
+ // Arrange
+ Type badType = typeof(string);
+
+ // Act & Assert
+ Assert.Throws<ArgumentException>(
+ delegate { new ModelBinderAttribute(badType); },
+ "The type 'System.String' does not implement the IModelBinder interface.\r\nParameter name: binderType");
+ }
+
+ [Fact]
+ public void ConstructorWithNullBinderTypeThrows()
+ {
+ // Act & Assert
+ Assert.ThrowsArgumentNull(
+ delegate { new ModelBinderAttribute(null); }, "binderType");
+ }
+
+ [Fact]
+ public void BinderTypeProperty()
+ {
+ // Arrange
+ Type binderType = typeof(GoodConverter);
+ ModelBinderAttribute attr = new ModelBinderAttribute(binderType);
+
+ // Act & Assert
+ Assert.Same(binderType, attr.BinderType);
+ }
+
+ [Fact]
+ public void GetBinder()
+ {
+ // Arrange
+ ModelBinderAttribute attr = new ModelBinderAttribute(typeof(GoodConverter));
+
+ // Act
+ IModelBinder binder = attr.GetBinder();
+
+ // Assert
+ Assert.IsType<GoodConverter>(binder);
+ }
+
+ [Fact]
+ public void GetBinderWithBadConstructorThrows()
+ {
+ // Arrange
+ ModelBinderAttribute attr = new ModelBinderAttribute(typeof(BadConverter));
+
+ // Act & Assert
+ Assert.Throws<InvalidOperationException>(
+ delegate { attr.GetBinder(); },
+ "An error occurred when trying to create the IModelBinder 'System.Web.Mvc.Test.ModelBinderAttributeTest+BadConverter'. Make sure that the binder has a public parameterless constructor.");
+ }
+
+ private class GoodConverter : IModelBinder
+ {
+ public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
+ {
+ throw new NotImplementedException();
+ }
+ }
+
+ private class BadConverter : IModelBinder
+ {
+ // no public parameterless constructor
+ public BadConverter(string s)
+ {
+ }
+
+ public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
+ {
+ throw new NotImplementedException();
+ }
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Test/ModelBinderDictionaryTest.cs b/test/System.Web.Mvc.Test/Test/ModelBinderDictionaryTest.cs
new file mode 100644
index 00000000..3a4e57fa
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Test/ModelBinderDictionaryTest.cs
@@ -0,0 +1,191 @@
+using System.Web.TestUtil;
+using Moq;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Mvc.Test
+{
+ public class ModelBinderDictionaryTest
+ {
+ [Fact]
+ public void DefaultBinderIsInstanceOfDefaultModelBinder()
+ {
+ // Arrange
+ ModelBinderDictionary binders = new ModelBinderDictionary();
+
+ // Act
+ IModelBinder defaultBinder = binders.DefaultBinder;
+
+ // Assert
+ Assert.IsType<DefaultModelBinder>(defaultBinder);
+ }
+
+ [Fact]
+ public void DefaultBinderProperty()
+ {
+ // Arrange
+ ModelBinderDictionary binders = new ModelBinderDictionary();
+ IModelBinder binder = new Mock<IModelBinder>().Object;
+
+ // Act & assert
+ MemberHelper.TestPropertyWithDefaultInstance(binders, "DefaultBinder", binder);
+ }
+
+ [Fact]
+ public void DictionaryInterface()
+ {
+ // Arrange
+ DictionaryHelper<Type, IModelBinder> helper = new DictionaryHelper<Type, IModelBinder>()
+ {
+ Creator = () => new ModelBinderDictionary(),
+ SampleKeys = new Type[] { typeof(object), typeof(string), typeof(int), typeof(long), typeof(long) },
+ SampleValues = new IModelBinder[] { new DefaultModelBinder(), new DefaultModelBinder(), new DefaultModelBinder(), new DefaultModelBinder(), new DefaultModelBinder() },
+ ThrowOnKeyNotFound = false
+ };
+
+ // Act & assert
+ helper.Execute();
+ }
+
+ [Fact]
+ public void GetBinderConsultsProviders()
+ {
+ // Arrange
+ Type modelType = typeof(string);
+ IModelBinder expectedBinderFromProvider = new Mock<IModelBinder>().Object;
+
+ Mock<IModelBinderProvider> locatedProvider = new Mock<IModelBinderProvider>();
+ locatedProvider.Setup(p => p.GetBinder(modelType))
+ .Returns(expectedBinderFromProvider);
+
+ Mock<IModelBinderProvider> secondProvider = new Mock<IModelBinderProvider>();
+
+ ModelBinderProviderCollection providers = new ModelBinderProviderCollection(new IModelBinderProvider[] { locatedProvider.Object, secondProvider.Object });
+ ModelBinderDictionary binders = new ModelBinderDictionary(providers);
+
+ // Act
+ IModelBinder returnedBinder = binders.GetBinder(modelType);
+
+ // Assert
+ Assert.Same(expectedBinderFromProvider, returnedBinder);
+ }
+
+ [Fact]
+ public void GetBinderDoesNotReturnDefaultBinderIfAskedNotTo()
+ {
+ // Proper order of precedence:
+ // 1. Binder registered in the global table
+ // 2. Binder attribute defined on the type
+ // 3. <null>
+
+ // Arrange
+ IModelBinder registeredFirstBinder = new Mock<IModelBinder>().Object;
+ ModelBinderDictionary binders = new ModelBinderDictionary()
+ {
+ { typeof(MyFirstConvertibleType), registeredFirstBinder }
+ };
+
+ // Act
+ IModelBinder binder1 = binders.GetBinder(typeof(MyFirstConvertibleType), false /* fallbackToDefault */);
+ IModelBinder binder2 = binders.GetBinder(typeof(MySecondConvertibleType), false /* fallbackToDefault */);
+ IModelBinder binder3 = binders.GetBinder(typeof(object), false /* fallbackToDefault */);
+
+ // Assert
+ Assert.Same(registeredFirstBinder, binder1);
+ Assert.IsType<MySecondBinder>(binder2);
+ Assert.Null(binder3);
+ }
+
+ [Fact]
+ public void GetBinderResolvesBindersWithCorrectPrecedence()
+ {
+ // Proper order of precedence:
+ // 1. Binder registered in the global table
+ // 2. Binder attribute defined on the type
+ // 3. Default binder
+
+ // Arrange
+ IModelBinder registeredFirstBinder = new Mock<IModelBinder>().Object;
+ ModelBinderDictionary binders = new ModelBinderDictionary()
+ {
+ { typeof(MyFirstConvertibleType), registeredFirstBinder }
+ };
+
+ IModelBinder defaultBinder = new Mock<IModelBinder>().Object;
+ binders.DefaultBinder = defaultBinder;
+
+ // Act
+ IModelBinder binder1 = binders.GetBinder(typeof(MyFirstConvertibleType));
+ IModelBinder binder2 = binders.GetBinder(typeof(MySecondConvertibleType));
+ IModelBinder binder3 = binders.GetBinder(typeof(object));
+
+ // Assert
+ Assert.Same(registeredFirstBinder, binder1);
+ Assert.IsType<MySecondBinder>(binder2);
+ Assert.Same(defaultBinder, binder3);
+ }
+
+ [Fact]
+ public void GetBinderThrowsIfModelTypeContainsMultipleAttributes()
+ {
+ // Arrange
+ ModelBinderDictionary binders = new ModelBinderDictionary();
+
+ // Act & Assert
+ Assert.Throws<InvalidOperationException>(
+ delegate { binders.GetBinder(typeof(ConvertibleTypeWithSeveralBinders), true /* fallbackToDefault */); },
+ "The type 'System.Web.Mvc.Test.ModelBinderDictionaryTest+ConvertibleTypeWithSeveralBinders' contains multiple attributes that inherit from CustomModelBinderAttribute.");
+ }
+
+ [Fact]
+ public void GetBinderThrowsIfModelTypeIsNull()
+ {
+ // Arrange
+ ModelBinderDictionary binders = new ModelBinderDictionary();
+
+ // Act & Assert
+ Assert.ThrowsArgumentNull(
+ delegate { binders.GetBinder(null); }, "modelType");
+ }
+
+ [ModelBinder(typeof(MyFirstBinder))]
+ private class MyFirstConvertibleType
+ {
+ }
+
+ private class MyFirstBinder : IModelBinder
+ {
+ public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
+ {
+ throw new NotImplementedException();
+ }
+ }
+
+ [ModelBinder(typeof(MySecondBinder))]
+ private class MySecondConvertibleType
+ {
+ }
+
+ private class MySecondBinder : IModelBinder
+ {
+ public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
+ {
+ throw new NotImplementedException();
+ }
+ }
+
+ [ModelBinder(typeof(MySecondBinder))]
+ [MySubclassedBinder]
+ private class ConvertibleTypeWithSeveralBinders
+ {
+ }
+
+ private class MySubclassedBinderAttribute : CustomModelBinderAttribute
+ {
+ public override IModelBinder GetBinder()
+ {
+ throw new NotImplementedException();
+ }
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Test/ModelBinderProviderCollectionTest.cs b/test/System.Web.Mvc.Test/Test/ModelBinderProviderCollectionTest.cs
new file mode 100644
index 00000000..99fb73d9
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Test/ModelBinderProviderCollectionTest.cs
@@ -0,0 +1,113 @@
+using System.Collections.Generic;
+using Moq;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Mvc.Test
+{
+ public class ModelBinderProviderCollectionTest
+ {
+ [Fact]
+ public void GuardClause()
+ {
+ // Arrange
+ var collection = new ModelBinderProviderCollection();
+
+ // Act & Assert
+ Assert.ThrowsArgumentNull(
+ () => collection.GetBinder(null),
+ "modelType"
+ );
+ }
+
+ [Fact]
+ public void GetBinderUsesRegisteredProviders()
+ {
+ // Arrange
+ var testType = typeof(string);
+ var expectedBinder = new Mock<IModelBinder>().Object;
+
+ var provider = new Mock<IModelBinderProvider>(MockBehavior.Strict);
+ provider.Setup(p => p.GetBinder(testType)).Returns(expectedBinder);
+ var collection = new ModelBinderProviderCollection(new[] { provider.Object });
+
+ // Act
+ IModelBinder returnedBinder = collection.GetBinder(testType);
+
+ // Assert
+ Assert.Same(expectedBinder, returnedBinder);
+ }
+
+ [Fact]
+ public void GetBinderReturnsValueFromFirstSuccessfulBinderProvider()
+ {
+ // Arrange
+ var testType = typeof(string);
+ IModelBinder nullModelBinder = null;
+ IModelBinder expectedBinder = new Mock<IModelBinder>().Object;
+ IModelBinder secondMatchingBinder = new Mock<IModelBinder>().Object;
+
+ var provider1 = new Mock<IModelBinderProvider>();
+ provider1.Setup(p => p.GetBinder(testType)).Returns(nullModelBinder);
+
+ var provider2 = new Mock<IModelBinderProvider>(MockBehavior.Strict);
+ provider2.Setup(p => p.GetBinder(testType)).Returns(expectedBinder);
+
+ var provider3 = new Mock<IModelBinderProvider>(MockBehavior.Strict);
+ provider3.Setup(p => p.GetBinder(testType)).Returns(secondMatchingBinder);
+
+ var collection = new ModelBinderProviderCollection(new[] { provider1.Object, provider2.Object, provider3.Object });
+
+ // Act
+ IModelBinder returnedBinder = collection.GetBinder(testType);
+
+ // Assert
+ Assert.Same(expectedBinder, returnedBinder);
+ }
+
+ [Fact]
+ public void GetBinderReturnsNullWhenNoSuccessfulBinderProviders()
+ {
+ // Arrange
+ var testType = typeof(string);
+ IModelBinder nullModelBinder = null;
+
+ var provider1 = new Mock<IModelBinderProvider>();
+ provider1.Setup(p => p.GetBinder(testType)).Returns(nullModelBinder);
+
+ var provider2 = new Mock<IModelBinderProvider>(MockBehavior.Strict);
+ provider2.Setup(p => p.GetBinder(testType)).Returns(nullModelBinder);
+
+ var collection = new ModelBinderProviderCollection(new[] { provider1.Object, provider2.Object });
+
+ // Act
+ IModelBinder returnedBinder = collection.GetBinder(testType);
+
+ // Assert
+ Assert.Null(returnedBinder);
+ }
+
+ [Fact]
+ public void GetBinderDelegatesToResolver()
+ {
+ // Arrange
+ Type modelType = typeof(string);
+ IModelBinder expectedBinder = new Mock<IModelBinder>().Object;
+
+ Mock<IModelBinderProvider> locatedProvider = new Mock<IModelBinderProvider>();
+ locatedProvider.Setup(p => p.GetBinder(modelType))
+ .Returns(expectedBinder);
+
+ Mock<IModelBinderProvider> secondProvider = new Mock<IModelBinderProvider>();
+ Resolver<IEnumerable<IModelBinderProvider>> resolver = new Resolver<IEnumerable<IModelBinderProvider>> { Current = new IModelBinderProvider[] { locatedProvider.Object, secondProvider.Object } };
+
+ ModelBinderProviderCollection providers = new ModelBinderProviderCollection(resolver);
+
+ // Act
+ IModelBinder returnedBinder = providers.GetBinder(modelType);
+
+ // Assert
+ Assert.Same(expectedBinder, returnedBinder);
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Test/ModelBinderProvidersTest.cs b/test/System.Web.Mvc.Test/Test/ModelBinderProvidersTest.cs
new file mode 100644
index 00000000..18ebdd53
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Test/ModelBinderProvidersTest.cs
@@ -0,0 +1,18 @@
+using System.Linq;
+using Xunit;
+
+namespace System.Web.Mvc.Test
+{
+ public class ModelBinderProvidersTest
+ {
+ [Fact]
+ public void CollectionDefaults()
+ {
+ // Act
+ Type[] actualTypes = ModelBinderProviders.BinderProviders.Select(b => b.GetType()).ToArray();
+
+ // Assert
+ Assert.Equal(Enumerable.Empty<Type>(), actualTypes);
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Test/ModelBindersTest.cs b/test/System.Web.Mvc.Test/Test/ModelBindersTest.cs
new file mode 100644
index 00000000..b2d31388
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Test/ModelBindersTest.cs
@@ -0,0 +1,65 @@
+using System.ComponentModel.DataAnnotations;
+using System.Data.Linq;
+using System.Threading;
+using Xunit;
+
+namespace System.Web.Mvc.Test
+{
+ public class ModelBindersTest
+ {
+ [Fact]
+ public void BindersPropertyIsNotNull()
+ {
+ // Arrange & Act
+ ModelBinderDictionary binders = ModelBinders.Binders;
+
+ // Assert
+ Assert.NotNull(binders);
+ }
+
+ [Fact]
+ public void DefaultModelBinders()
+ {
+ // Act
+ ModelBinderDictionary binders = ModelBinders.Binders;
+
+ // Assert
+ Assert.Equal(4, binders.Count);
+ Assert.True(binders.ContainsKey(typeof(byte[])));
+ Assert.IsType<ByteArrayModelBinder>(binders[typeof(byte[])]);
+ Assert.True(binders.ContainsKey(typeof(HttpPostedFileBase)));
+ Assert.IsType<HttpPostedFileBaseModelBinder>(binders[typeof(HttpPostedFileBase)]);
+ Assert.True(binders.ContainsKey(typeof(Binary)));
+ Assert.IsType<LinqBinaryModelBinder>(binders[typeof(Binary)]);
+ Assert.True(binders.ContainsKey(typeof(CancellationToken)));
+ Assert.IsType<CancellationTokenModelBinder>(binders[typeof(CancellationToken)]);
+ }
+
+ [Fact]
+ public void GetBindersFromAttributes_ReadsModelBinderAttributeFromBuddyClass()
+ {
+ // Act
+ IModelBinder binder = ModelBinders.GetBinderFromAttributes(typeof(SampleModel), null);
+
+ // Assert
+ Assert.IsType<SampleModelBinder>(binder);
+ }
+
+ [MetadataType(typeof(SampleModel_Buddy))]
+ private class SampleModel
+ {
+ [ModelBinder(typeof(SampleModelBinder))]
+ private class SampleModel_Buddy
+ {
+ }
+ }
+
+ private class SampleModelBinder : IModelBinder
+ {
+ public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
+ {
+ throw new NotImplementedException();
+ }
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Test/ModelBindingContextTest.cs b/test/System.Web.Mvc.Test/Test/ModelBindingContextTest.cs
new file mode 100644
index 00000000..d5e32e25
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Test/ModelBindingContextTest.cs
@@ -0,0 +1,122 @@
+using System.Web.TestUtil;
+using Microsoft.Web.UnitTestUtil;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Mvc.Test
+{
+ public class ModelBindingContextTest
+ {
+ [Fact]
+ public void CopyConstructor()
+ {
+ // Arrange
+ ModelBindingContext originalBindingContext = new ModelBindingContext()
+ {
+ FallbackToEmptyPrefix = true,
+ ModelMetadata = new EmptyModelMetadataProvider().GetMetadataForType(null, typeof(object)),
+ ModelName = "theName",
+ ModelState = new ModelStateDictionary(),
+ PropertyFilter = _ => false,
+ ValueProvider = new SimpleValueProvider()
+ };
+
+ // Act
+ ModelBindingContext newBindingContext = new ModelBindingContext(originalBindingContext);
+
+ // Assert
+ Assert.False(newBindingContext.FallbackToEmptyPrefix);
+ Assert.Null(newBindingContext.ModelMetadata);
+ Assert.Equal("", newBindingContext.ModelName);
+ Assert.Equal(originalBindingContext.ModelState, newBindingContext.ModelState);
+ Assert.True(newBindingContext.PropertyFilter("foo"));
+ Assert.Equal(originalBindingContext.ValueProvider, newBindingContext.ValueProvider);
+ }
+
+ [Fact]
+ public void ModelNameProperty()
+ {
+ // Arrange
+ ModelBindingContext bindingContext = new ModelBindingContext();
+
+ // Act & assert
+ MemberHelper.TestStringProperty(bindingContext, "ModelName", String.Empty);
+ }
+
+ [Fact]
+ public void ModelStateProperty()
+ {
+ // Arrange
+ ModelBindingContext bindingContext = new ModelBindingContext();
+ ModelStateDictionary modelState = new ModelStateDictionary();
+
+ // Act & assert
+ MemberHelper.TestPropertyWithDefaultInstance(bindingContext, "ModelState", modelState);
+ }
+
+ [Fact]
+ public void PropertyFilterPropertyDefaultInstanceReturnsTrueForAnyInput()
+ {
+ // Arrange
+ ModelBindingContext bindingContext = new ModelBindingContext();
+
+ // Act
+ Predicate<string> propertyFilter = bindingContext.PropertyFilter;
+
+ // Assert
+ // We can't test all inputs, but at least this gives us high confidence that we ignore the parameter by default
+ Assert.True(propertyFilter(null));
+ Assert.True(propertyFilter(String.Empty));
+ Assert.True(propertyFilter("Foo"));
+ }
+
+ [Fact]
+ public void PropertyFilterPropertyReturnsDefaultInstance()
+ {
+ // Arrange
+ ModelBindingContext bindingContext = new ModelBindingContext();
+ Predicate<string> propertyFilter = _ => true;
+
+ // Act & assert
+ MemberHelper.TestPropertyWithDefaultInstance(bindingContext, "PropertyFilter", propertyFilter);
+ }
+
+ [Fact]
+ public void ModelAndModelTypeAreFedFromModelMetadata()
+ {
+ // Act
+ ModelBindingContext bindingContext = new ModelBindingContext
+ {
+ ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(() => 42, typeof(int))
+ };
+
+ // Assert
+ Assert.Equal(42, bindingContext.Model);
+ Assert.Equal(typeof(int), bindingContext.ModelType);
+ }
+
+ [Fact]
+ public void ModelIsNotSettable()
+ {
+ // Arrange
+ ModelBindingContext bindingContext = new ModelBindingContext();
+
+ // Act & Assert
+ Assert.Throws<InvalidOperationException>(
+ () => bindingContext.Model = "foo",
+ "This property setter is obsolete, because its value is derived from ModelMetadata.Model now.");
+ }
+
+ [Fact]
+ public void ModelTypeIsNotSettable()
+ {
+ // Arrange
+ ModelBindingContext bindingContext = new ModelBindingContext();
+
+ // Act & Assert
+ Assert.Throws<InvalidOperationException>(
+ () => bindingContext.ModelType = typeof(string),
+ "This property setter is obsolete, because its value is derived from ModelMetadata.Model now.");
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Test/ModelClientValidationRuleTest.cs b/test/System.Web.Mvc.Test/Test/ModelClientValidationRuleTest.cs
new file mode 100644
index 00000000..40d21dd2
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Test/ModelClientValidationRuleTest.cs
@@ -0,0 +1,33 @@
+using System.Collections.Generic;
+using System.Web.TestUtil;
+using Xunit;
+
+namespace System.Web.Mvc.Test
+{
+ public class ModelClientValidationRuleTest
+ {
+ [Fact]
+ public void ValidationParametersProperty()
+ {
+ // Arrange
+ ModelClientValidationRule rule = new ModelClientValidationRule();
+
+ // Act
+ IDictionary<string, object> parameters = rule.ValidationParameters;
+
+ // Assert
+ Assert.NotNull(parameters);
+ Assert.Empty(parameters);
+ }
+
+ [Fact]
+ public void ValidationTypeProperty()
+ {
+ // Arrange
+ ModelClientValidationRule rule = new ModelClientValidationRule();
+
+ // Act & assert
+ MemberHelper.TestStringProperty(rule, "ValidationType", String.Empty);
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Test/ModelErrorCollectionTest.cs b/test/System.Web.Mvc.Test/Test/ModelErrorCollectionTest.cs
new file mode 100644
index 00000000..dbc7afdb
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Test/ModelErrorCollectionTest.cs
@@ -0,0 +1,37 @@
+using Xunit;
+
+namespace System.Web.Mvc.Test
+{
+ public class ModelErrorCollectionTest
+ {
+ [Fact]
+ public void AddWithExceptionArgument()
+ {
+ // Arrange
+ ModelErrorCollection collection = new ModelErrorCollection();
+ Exception ex = new Exception("some message");
+
+ // Act
+ collection.Add(ex);
+
+ // Assert
+ ModelError modelError = Assert.Single(collection);
+ Assert.Same(ex, modelError.Exception);
+ }
+
+ [Fact]
+ public void AddWithStringArgument()
+ {
+ // Arrange
+ ModelErrorCollection collection = new ModelErrorCollection();
+
+ // Act
+ collection.Add("some message");
+
+ // Assert
+ ModelError modelError = Assert.Single(collection);
+ Assert.Equal("some message", modelError.ErrorMessage);
+ Assert.Null(modelError.Exception);
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Test/ModelErrorTest.cs b/test/System.Web.Mvc.Test/Test/ModelErrorTest.cs
new file mode 100644
index 00000000..a2048008
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Test/ModelErrorTest.cs
@@ -0,0 +1,65 @@
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Mvc.Test
+{
+ public class ModelErrorTest
+ {
+ [Fact]
+ public void ConstructorThrowsIfExceptionIsNull()
+ {
+ // Act & Assert
+ Assert.ThrowsArgumentNull(
+ delegate { new ModelError((Exception)null); }, "exception");
+ }
+
+ [Fact]
+ public void ConstructorWithExceptionAndStringArguments()
+ {
+ // Arrange
+ Exception ex = new Exception("some message");
+
+ // Act
+ ModelError modelError = new ModelError(ex, "some other message");
+
+ // Assert
+ Assert.Equal("some other message", modelError.ErrorMessage);
+ Assert.Same(ex, modelError.Exception);
+ }
+
+ [Fact]
+ public void ConstructorWithExceptionArgument()
+ {
+ // Arrange
+ Exception ex = new Exception("some message");
+
+ // Act
+ ModelError modelError = new ModelError(ex);
+
+ // Assert
+ Assert.Equal(String.Empty, modelError.ErrorMessage);
+ Assert.Same(ex, modelError.Exception);
+ }
+
+ [Fact]
+ public void ConstructorWithNullStringArgumentCreatesEmptyStringErrorMessage()
+ {
+ // Act
+ ModelError modelError = new ModelError((string)null);
+
+ // Assert
+ Assert.Equal(String.Empty, modelError.ErrorMessage);
+ }
+
+ [Fact]
+ public void ConstructorWithStringArgument()
+ {
+ // Act
+ ModelError modelError = new ModelError("some message");
+
+ // Assert
+ Assert.Equal("some message", modelError.ErrorMessage);
+ Assert.Null(modelError.Exception);
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Test/ModelMetadataProvidersTest.cs b/test/System.Web.Mvc.Test/Test/ModelMetadataProvidersTest.cs
new file mode 100644
index 00000000..a0f6509d
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Test/ModelMetadataProvidersTest.cs
@@ -0,0 +1,68 @@
+using Moq;
+using Xunit;
+
+namespace System.Web.Mvc.Test
+{
+ public class ModelMetadataProvidersTest
+ {
+ [Fact]
+ public void DefaultModelMetadataProviderIsCachedDataAnnotations()
+ {
+ // Arrange
+ ModelMetadataProviders providers = new ModelMetadataProviders();
+
+ // Act
+ ModelMetadataProvider provider = providers.CurrentInternal;
+
+ // Assert
+ Assert.IsType<CachedDataAnnotationsModelMetadataProvider>(provider);
+ }
+
+ [Fact]
+ public void SettingModelMetadataProviderReturnsSetProvider()
+ {
+ // Arrange
+ ModelMetadataProviders providers = new ModelMetadataProviders();
+ Mock<ModelMetadataProvider> provider = new Mock<ModelMetadataProvider>();
+
+ // Act
+ providers.CurrentInternal = provider.Object;
+
+ // Assert
+ Assert.Same(provider.Object, providers.CurrentInternal);
+ }
+
+ [Fact]
+ public void SettingNullModelMetadataProviderUsesEmptyModelMetadataProvider()
+ {
+ // Arrange
+ ModelMetadataProviders providers = new ModelMetadataProviders();
+
+ // Act
+ providers.CurrentInternal = null;
+
+ // Assert
+ Assert.IsType<EmptyModelMetadataProvider>(providers.CurrentInternal);
+ }
+
+ [Fact]
+ public void ModelMetadataProvidersCurrentDelegatesToResolver()
+ {
+ // Arrange
+ Mock<ModelMetadataProvider> provider = new Mock<ModelMetadataProvider>();
+ Resolver<ModelMetadataProvider> resolver = new Resolver<ModelMetadataProvider> { Current = provider.Object };
+ ModelMetadataProviders providers = new ModelMetadataProviders(resolver);
+
+ // Act
+ ModelMetadataProvider result = providers.CurrentInternal;
+
+ // Assert
+ Assert.Same(provider.Object, result);
+ }
+
+ private class Resolver<T> : IResolver<T>
+ {
+ public T Current { get; set; }
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Test/ModelMetadataTest.cs b/test/System.Web.Mvc.Test/Test/ModelMetadataTest.cs
new file mode 100644
index 00000000..78244edd
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Test/ModelMetadataTest.cs
@@ -0,0 +1,892 @@
+using System.Collections.Generic;
+using System.ComponentModel.DataAnnotations;
+using System.Linq;
+using System.Linq.Expressions;
+using Moq;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Mvc.Test
+{
+ public class ModelMetadataTest
+ {
+ // Guard clauses
+
+ [Fact]
+ public void NullProviderThrows()
+ {
+ // Act & Assert
+ Assert.ThrowsArgumentNull(
+ () => new ModelMetadata(null /* provider */, null /* containerType */, null /* model */, typeof(object), null /* propertyName */),
+ "provider");
+ }
+
+ [Fact]
+ public void NullTypeThrows()
+ {
+ // Arrange
+ Mock<ModelMetadataProvider> provider = new Mock<ModelMetadataProvider>();
+
+ // Act & Assert
+ Assert.ThrowsArgumentNull(
+ () => new ModelMetadata(provider.Object, null /* containerType */, null /* model */, null /* modelType */, null /* propertyName */),
+ "modelType");
+ }
+
+ // Constructor
+
+ [Fact]
+ public void DefaultValues()
+ {
+ // Arrange
+ Mock<ModelMetadataProvider> provider = new Mock<ModelMetadataProvider>();
+
+ // Act
+ ModelMetadata metadata = new ModelMetadata(provider.Object, typeof(Exception), () => "model", typeof(string), "propertyName");
+
+ // Assert
+ Assert.Equal(typeof(Exception), metadata.ContainerType);
+ Assert.True(metadata.ConvertEmptyStringToNull);
+ Assert.Null(metadata.DataTypeName);
+ Assert.Null(metadata.Description);
+ Assert.Null(metadata.DisplayFormatString);
+ Assert.Null(metadata.DisplayName);
+ Assert.Null(metadata.EditFormatString);
+ Assert.False(metadata.HideSurroundingHtml);
+ Assert.Equal("model", metadata.Model);
+ Assert.Equal(typeof(string), metadata.ModelType);
+ Assert.Null(metadata.NullDisplayText);
+ Assert.Equal(10000, metadata.Order);
+ Assert.Equal("propertyName", metadata.PropertyName);
+ Assert.False(metadata.IsReadOnly);
+ Assert.True(metadata.RequestValidationEnabled);
+ Assert.Null(metadata.ShortDisplayName);
+ Assert.True(metadata.ShowForDisplay);
+ Assert.True(metadata.ShowForEdit);
+ Assert.Null(metadata.TemplateHint);
+ Assert.Null(metadata.Watermark);
+ }
+
+ // IsComplexType
+
+ struct IsComplexTypeModel
+ {
+ }
+
+ [Fact]
+ public void IsComplexTypeTests()
+ {
+ // Arrange
+ Mock<ModelMetadataProvider> provider = new Mock<ModelMetadataProvider>();
+
+ // Act & Assert
+ Assert.True(new ModelMetadata(provider.Object, null, null, typeof(Object), null).IsComplexType);
+ Assert.False(new ModelMetadata(provider.Object, null, null, typeof(string), null).IsComplexType);
+ Assert.True(new ModelMetadata(provider.Object, null, null, typeof(IDisposable), null).IsComplexType);
+ Assert.False(new ModelMetadata(provider.Object, null, null, typeof(Nullable<int>), null).IsComplexType);
+ Assert.False(new ModelMetadata(provider.Object, null, null, typeof(int), null).IsComplexType);
+ Assert.True(new ModelMetadata(provider.Object, null, null, typeof(IsComplexTypeModel), null).IsComplexType);
+ Assert.True(new ModelMetadata(provider.Object, null, null, typeof(Nullable<IsComplexTypeModel>), null).IsComplexType);
+ }
+
+ // IsNullableValueType
+
+ [Fact]
+ public void IsNullableValueTypeTests()
+ {
+ // Arrange
+ Mock<ModelMetadataProvider> provider = new Mock<ModelMetadataProvider>();
+
+ // Act & Assert
+ Assert.False(new ModelMetadata(provider.Object, null, null, typeof(string), null).IsNullableValueType);
+ Assert.False(new ModelMetadata(provider.Object, null, null, typeof(IDisposable), null).IsNullableValueType);
+ Assert.True(new ModelMetadata(provider.Object, null, null, typeof(Nullable<int>), null).IsNullableValueType);
+ Assert.False(new ModelMetadata(provider.Object, null, null, typeof(int), null).IsNullableValueType);
+ }
+
+ // IsRequired
+
+ [Fact]
+ public void IsRequiredTests()
+ {
+ // Arrange
+ Mock<ModelMetadataProvider> provider = new Mock<ModelMetadataProvider>();
+
+ // Act & Assert
+ Assert.False(new ModelMetadata(provider.Object, null, null, typeof(string), null).IsRequired); // Reference type not required
+ Assert.False(new ModelMetadata(provider.Object, null, null, typeof(IDisposable), null).IsRequired); // Interface not required
+ Assert.False(new ModelMetadata(provider.Object, null, null, typeof(Nullable<int>), null).IsRequired); // Nullable value type not required
+ Assert.True(new ModelMetadata(provider.Object, null, null, typeof(int), null).IsRequired); // Value type required
+ Assert.True(new ModelMetadata(provider.Object, null, null, typeof(DayOfWeek), null).IsRequired); // Enum (implicit value type) is required
+ }
+
+ // Properties
+
+ [Fact]
+ public void PropertiesCallsProvider()
+ {
+ // Arrange
+ Type modelType = typeof(string);
+ List<ModelMetadata> propertyMetadata = new List<ModelMetadata>();
+ Mock<ModelMetadataProvider> provider = new Mock<ModelMetadataProvider>();
+ ModelMetadata metadata = new ModelMetadata(provider.Object, null, null, modelType, null);
+ provider.Setup(p => p.GetMetadataForProperties(null, modelType))
+ .Returns(propertyMetadata)
+ .Verifiable();
+
+ // Act
+ IEnumerable<ModelMetadata> result = metadata.Properties;
+
+ // Assert
+ Assert.Equal(propertyMetadata, result.ToList());
+ provider.Verify();
+ }
+
+ [Fact]
+ public void PropertiesUsesRealModelTypeRatherThanPassedModelType()
+ {
+ // Arrange
+ string model = "String Value";
+ Expression<Func<object, object>> accessor = _ => model;
+ ModelMetadata metadata = ModelMetadata.FromLambdaExpression(accessor, new ViewDataDictionary<object>());
+
+ // Act
+ IEnumerable<ModelMetadata> result = metadata.Properties;
+
+ // Assert
+ Assert.Equal("Length", result.Single().PropertyName);
+ }
+
+ [Fact]
+ public void PropertiesAreSortedByOrder()
+ {
+ // Arrange
+ Type modelType = typeof(string);
+ Mock<ModelMetadataProvider> provider = new Mock<ModelMetadataProvider>();
+ List<ModelMetadata> propertyMetadata = new List<ModelMetadata>
+ {
+ new ModelMetadata(provider.Object, null, () => 1, typeof(int), null) { Order = 20 },
+ new ModelMetadata(provider.Object, null, () => 2, typeof(int), null) { Order = 30 },
+ new ModelMetadata(provider.Object, null, () => 3, typeof(int), null) { Order = 10 },
+ };
+ ModelMetadata metadata = new ModelMetadata(provider.Object, null, null, modelType, null);
+ provider.Setup(p => p.GetMetadataForProperties(null, modelType))
+ .Returns(propertyMetadata)
+ .Verifiable();
+
+ // Act
+ List<ModelMetadata> result = metadata.Properties.ToList();
+
+ // Assert
+ Assert.Equal(3, result.Count);
+ Assert.Equal(3, result[0].Model);
+ Assert.Equal(1, result[1].Model);
+ Assert.Equal(2, result[2].Model);
+ }
+
+ [Fact]
+ public void PropertiesListGetsResetWhenModelGetsReset()
+ { // Dev10 Bug #923263
+ // Arrange
+ var provider = new DataAnnotationsModelMetadataProvider();
+ var metadata = new ModelMetadata(provider, null, () => new Class1(), typeof(Class1), null);
+
+ // Act
+ ModelMetadata[] originalProps = metadata.Properties.ToArray();
+ metadata.Model = new Class2();
+ ModelMetadata[] newProps = metadata.Properties.ToArray();
+
+ // Assert
+ ModelMetadata originalProp = Assert.Single(originalProps);
+ Assert.Equal(typeof(string), originalProp.ModelType);
+ Assert.Equal("Prop1", originalProp.PropertyName);
+ ModelMetadata newProp = Assert.Single(newProps);
+ Assert.Equal(typeof(int), newProp.ModelType);
+ Assert.Equal("Prop2", newProp.PropertyName);
+ }
+
+ class Class1
+ {
+ public string Prop1 { get; set; }
+ }
+
+ class Class2
+ {
+ public int Prop2 { get; set; }
+ }
+
+ // SimpleDisplayText
+
+ [Fact]
+ public void SimpleDisplayTextReturnsNullDisplayTextForNullModel()
+ {
+ // Arrange
+ string nullText = "(null)";
+ Mock<ModelMetadataProvider> provider = new Mock<ModelMetadataProvider>();
+ ModelMetadata metadata = new ModelMetadata(provider.Object, null, null, typeof(object), null) { NullDisplayText = nullText };
+
+ // Act
+ string result = metadata.SimpleDisplayText;
+
+ // Assert
+ Assert.Equal(nullText, result);
+ }
+
+ private class SimpleDisplayTextModelWithToString
+ {
+ public override string ToString()
+ {
+ return "Custom ToString Value";
+ }
+ }
+
+ [Fact]
+ public void SimpleDisplayTextReturnsToStringValueWhenOverridden()
+ {
+ // Arrange
+ SimpleDisplayTextModelWithToString model = new SimpleDisplayTextModelWithToString();
+ EmptyModelMetadataProvider provider = new EmptyModelMetadataProvider();
+ ModelMetadata metadata = new ModelMetadata(provider, null, () => model, typeof(SimpleDisplayTextModelWithToString), null);
+
+ // Act
+ string result = metadata.SimpleDisplayText;
+
+ // Assert
+ Assert.Equal(model.ToString(), result);
+ }
+
+ private class SimpleDisplayTextModelWithoutToString
+ {
+ public string FirstProperty { get; set; }
+
+ public int SecondProperty { get; set; }
+ }
+
+ [Fact]
+ public void SimpleDisplayTextReturnsFirstPropertyValueForNonNullModel()
+ {
+ // Arrange
+ SimpleDisplayTextModelWithoutToString model = new SimpleDisplayTextModelWithoutToString
+ {
+ FirstProperty = "First Property Value"
+ };
+ EmptyModelMetadataProvider provider = new EmptyModelMetadataProvider();
+ ModelMetadata metadata = new ModelMetadata(provider, null, () => model, typeof(SimpleDisplayTextModelWithoutToString), null);
+
+ // Act
+ string result = metadata.SimpleDisplayText;
+
+ // Assert
+ Assert.Equal(model.FirstProperty, result);
+ }
+
+ [Fact]
+ public void SimpleDisplayTextReturnsFirstPropertyNullDisplayTextForNonNullModelWithNullDisplayColumnPropertyValue()
+ {
+ // Arrange
+ SimpleDisplayTextModelWithoutToString model = new SimpleDisplayTextModelWithoutToString();
+ EmptyModelMetadataProvider propertyProvider = new EmptyModelMetadataProvider();
+ ModelMetadata propertyMetadata = propertyProvider.GetMetadataForProperty(() => model.FirstProperty, typeof(SimpleDisplayTextModelWithoutToString), "FirstProperty");
+ propertyMetadata.NullDisplayText = "Null Display Text";
+ Mock<ModelMetadataProvider> provider = new Mock<ModelMetadataProvider>();
+ provider.Setup(p => p.GetMetadataForProperties(model, typeof(SimpleDisplayTextModelWithoutToString)))
+ .Returns(new[] { propertyMetadata });
+ ModelMetadata metadata = new ModelMetadata(provider.Object, null, () => model, typeof(SimpleDisplayTextModelWithoutToString), null);
+
+ // Act
+ string result = metadata.SimpleDisplayText;
+
+ // Assert
+ Assert.Equal(propertyMetadata.NullDisplayText, result);
+ }
+
+ private class SimpleDisplayTextModelWithNoProperties
+ {
+ }
+
+ [Fact]
+ public void SimpleDisplayTextReturnsEmptyStringForNonNullModelWithNoVisibleProperties()
+ {
+ // Arrange
+ SimpleDisplayTextModelWithNoProperties model = new SimpleDisplayTextModelWithNoProperties();
+ EmptyModelMetadataProvider provider = new EmptyModelMetadataProvider();
+ ModelMetadata metadata = new ModelMetadata(provider, null, () => model, typeof(SimpleDisplayTextModelWithNoProperties), null);
+
+ // Act
+ string result = metadata.SimpleDisplayText;
+
+ // Assert
+ Assert.Equal(String.Empty, result);
+ }
+
+ private class ObjectWithToStringOverride
+ {
+ private string _toStringValue;
+
+ public ObjectWithToStringOverride(string toStringValue)
+ {
+ _toStringValue = toStringValue;
+ }
+
+ public override string ToString()
+ {
+ return _toStringValue;
+ }
+ }
+
+ [Fact]
+ public void SimpleDisplayTextReturnsToStringOfModelForNonNullModel()
+ {
+ // Arrange
+ string toStringText = "text from ToString()";
+ ObjectWithToStringOverride model = new ObjectWithToStringOverride(toStringText);
+ Mock<ModelMetadataProvider> provider = new Mock<ModelMetadataProvider>();
+ ModelMetadata metadata = new ModelMetadata(provider.Object, null, () => model, typeof(ObjectWithToStringOverride), null);
+
+ // Act
+ string result = metadata.SimpleDisplayText;
+
+ // Assert
+ Assert.Equal(toStringText, result);
+ }
+
+ [Fact]
+ public void SimpleDisplayTextReturnsEmptyStringForNonNullModelWithToStringNull()
+ {
+ // Arrange
+ string toStringText = null;
+ ObjectWithToStringOverride model = new ObjectWithToStringOverride(toStringText);
+ Mock<ModelMetadataProvider> provider = new Mock<ModelMetadataProvider>();
+ ModelMetadata metadata = new ModelMetadata(provider.Object, null, () => model, typeof(ObjectWithToStringOverride), null);
+
+ // Act
+ string result = metadata.SimpleDisplayText;
+
+ // Assert
+ Assert.Equal(String.Empty, result);
+ }
+
+ // FromStringExpression()
+
+ [Fact]
+ public void FromStringExpressionGuardClauses()
+ {
+ // Null expression throws
+ Assert.ThrowsArgumentNull(
+ () => ModelMetadata.FromStringExpression(null, new ViewDataDictionary()),
+ "expression");
+
+ // Null view data dictionary throws
+ Assert.ThrowsArgumentNull(
+ () => ModelMetadata.FromStringExpression("expression", null),
+ "viewData");
+ }
+
+ [Fact]
+ public void FromStringExpressionEmptyExpressionReturnsExistingModelMetadata()
+ {
+ // Arrange
+ Mock<ModelMetadataProvider> provider = new Mock<ModelMetadataProvider>();
+ ModelMetadata metadata = new ModelMetadata(provider.Object, null, null, typeof(object), null);
+ ViewDataDictionary viewData = new ViewDataDictionary();
+ viewData.ModelMetadata = metadata;
+
+ // Act
+ ModelMetadata result = ModelMetadata.FromStringExpression(String.Empty, viewData, provider.Object);
+
+ // Assert
+ Assert.Same(metadata, result);
+ }
+
+ [Fact]
+ public void FromStringExpressionItemNotFoundInViewData()
+ {
+ // Arrange
+ Mock<ModelMetadataProvider> provider = new Mock<ModelMetadataProvider>();
+ ViewDataDictionary viewData = new ViewDataDictionary();
+ provider.Setup(p => p.GetMetadataForType(It.IsAny<Func<object>>(), It.IsAny<Type>()))
+ .Callback<Func<object>, Type>((accessor, type) =>
+ {
+ Assert.Null(accessor);
+ Assert.Equal(typeof(string), type); // Don't know the type, must fall back on string
+ })
+ .Returns(() => null)
+ .Verifiable();
+
+ // Act
+ ModelMetadata.FromStringExpression("UnknownObject", viewData, provider.Object);
+
+ // Assert
+ provider.Verify();
+ }
+
+ [Fact]
+ public void FromStringExpressionNullItemFoundAtRootOfViewData()
+ {
+ // Arrange
+ Mock<ModelMetadataProvider> provider = new Mock<ModelMetadataProvider>();
+ ViewDataDictionary viewData = new ViewDataDictionary();
+ viewData["Object"] = null;
+ provider.Setup(p => p.GetMetadataForType(It.IsAny<Func<object>>(), It.IsAny<Type>()))
+ .Callback<Func<object>, Type>((accessor, type) =>
+ {
+ Assert.Null(accessor());
+ Assert.Equal(typeof(string), type); // Don't know the type, must fall back on string
+ })
+ .Returns(() => null)
+ .Verifiable();
+
+ // Act
+ ModelMetadata.FromStringExpression("Object", viewData, provider.Object);
+
+ // Assert
+ provider.Verify();
+ }
+
+ [Fact]
+ public void FromStringExpressionNonNullItemFoundAtRootOfViewData()
+ {
+ // Arrange
+ Mock<ModelMetadataProvider> provider = new Mock<ModelMetadataProvider>();
+ object model = new object();
+ ViewDataDictionary viewData = new ViewDataDictionary();
+ viewData["Object"] = model;
+ provider.Setup(p => p.GetMetadataForType(It.IsAny<Func<object>>(), It.IsAny<Type>()))
+ .Callback<Func<object>, Type>((accessor, type) =>
+ {
+ Assert.Same(model, accessor());
+ Assert.Equal(typeof(object), type);
+ })
+ .Returns(() => null)
+ .Verifiable();
+
+ // Act
+ ModelMetadata.FromStringExpression("Object", viewData, provider.Object);
+
+ // Assert
+ provider.Verify();
+ }
+
+ [Fact]
+ public void FromStringExpressionNullItemFoundOnPropertyOfItemInViewData()
+ {
+ // Arrange
+ Mock<ModelMetadataProvider> provider = new Mock<ModelMetadataProvider>();
+ DummyModelContainer model = new DummyModelContainer();
+ ViewDataDictionary viewData = new ViewDataDictionary();
+ viewData["Object"] = model;
+ provider.Setup(p => p.GetMetadataForProperty(It.IsAny<Func<object>>(), It.IsAny<Type>(), It.IsAny<string>()))
+ .Callback<Func<object>, Type, string>((accessor, type, propertyName) =>
+ {
+ Assert.Null(accessor());
+ Assert.Equal(typeof(DummyModelContainer), type);
+ Assert.Equal("Model", propertyName);
+ })
+ .Returns(() => null)
+ .Verifiable();
+
+ // Act
+ ModelMetadata.FromStringExpression("Object.Model", viewData, provider.Object);
+
+ // Assert
+ provider.Verify();
+ }
+
+ [Fact]
+ public void FromStringExpressionNonNullItemFoundOnPropertyOfItemInViewData()
+ {
+ // Arrange
+ Mock<ModelMetadataProvider> provider = new Mock<ModelMetadataProvider>();
+ DummyModelContainer model = new DummyModelContainer { Model = new DummyContactModel() };
+ ViewDataDictionary viewData = new ViewDataDictionary();
+ viewData["Object"] = model;
+ provider.Setup(p => p.GetMetadataForProperty(It.IsAny<Func<object>>(), It.IsAny<Type>(), It.IsAny<string>()))
+ .Callback<Func<object>, Type, string>((accessor, type, propertyName) =>
+ {
+ Assert.Same(model.Model, accessor());
+ Assert.Equal(typeof(DummyModelContainer), type);
+ Assert.Equal("Model", propertyName);
+ })
+ .Returns(() => null)
+ .Verifiable();
+
+ // Act
+ ModelMetadata.FromStringExpression("Object.Model", viewData, provider.Object);
+
+ // Assert
+ provider.Verify();
+ }
+
+ [Fact]
+ public void FromStringExpressionWithNullModelButValidModelMetadataShouldReturnProperPropertyMetadata()
+ {
+ // Arrange
+ ViewDataDictionary viewData = new ViewDataDictionary();
+ viewData.ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(null, typeof(DummyContactModel));
+
+ // Act
+ ModelMetadata result = ModelMetadata.FromStringExpression("NullableIntValue", viewData);
+
+ // Assert
+ Assert.Null(result.Model);
+ Assert.Equal(typeof(Nullable<int>), result.ModelType);
+ Assert.Equal("NullableIntValue", result.PropertyName);
+ Assert.Equal(typeof(DummyContactModel), result.ContainerType);
+ }
+
+ [Fact]
+ public void FromStringExpressionValueInModelProperty()
+ {
+ // Arrange
+ DummyContactModel model = new DummyContactModel { FirstName = "John" };
+ ViewDataDictionary viewData = new ViewDataDictionary(model);
+
+ // Act
+ ModelMetadata metadata = ModelMetadata.FromStringExpression("FirstName", viewData);
+
+ // Assert
+ Assert.Equal("John", metadata.Model);
+ }
+
+ [Fact]
+ public void FromStringExpressionValueInViewDataOverridesValueFromModelProperty()
+ {
+ // Arrange
+ DummyContactModel model = new DummyContactModel { FirstName = "John" };
+ ViewDataDictionary viewData = new ViewDataDictionary(model);
+ viewData["FirstName"] = "Jim";
+
+ // Act
+ ModelMetadata metadata = ModelMetadata.FromStringExpression("FirstName", viewData);
+
+ // Assert
+ Assert.Equal("Jim", metadata.Model);
+ }
+
+ // FromLambdaExpression()
+
+ [Fact]
+ public void FromLambdaExpressionGuardClauseTests()
+ {
+ // Null expression throws
+ Assert.ThrowsArgumentNull(
+ () => ModelMetadata.FromLambdaExpression<string, object>(null, new ViewDataDictionary<string>()),
+ "expression");
+
+ // Null view data throws
+ Assert.ThrowsArgumentNull(
+ () => ModelMetadata.FromLambdaExpression<string, object>(m => m, null),
+ "viewData");
+
+ // Unsupported expression type throws
+ Assert.Throws<InvalidOperationException>(
+ () => ModelMetadata.FromLambdaExpression<string, object>(m => new Object(), new ViewDataDictionary<string>()),
+ "Templates can be used only with field access, property access, single-dimension array index, or single-parameter custom indexer expressions.");
+ }
+
+ [Fact]
+ public void FromLambdaExpressionModelIdentityExpressionReturnsExistingModelMetadata()
+ {
+ // Arrange
+ Mock<ModelMetadataProvider> provider = new Mock<ModelMetadataProvider>();
+ ModelMetadata metadata = new ModelMetadata(provider.Object, null, null, typeof(object), null);
+ ViewDataDictionary<object> viewData = new ViewDataDictionary<object>();
+ viewData.ModelMetadata = metadata;
+
+ // Act
+ ModelMetadata result = ModelMetadata.FromLambdaExpression<object, object>(m => m, viewData, provider.Object);
+
+ // Assert
+ Assert.Same(metadata, result);
+ }
+
+ [Fact]
+ public void FromLambdaExpressionPropertyExpressionFromParameter()
+ {
+ // Arrange
+ Mock<ModelMetadataProvider> provider = new Mock<ModelMetadataProvider>();
+ DummyContactModel model = new DummyContactModel { FirstName = "Test" };
+ ViewDataDictionary<DummyContactModel> viewData = new ViewDataDictionary<DummyContactModel>(model);
+ provider.Setup(p => p.GetMetadataForProperty(It.IsAny<Func<object>>(), It.IsAny<Type>(), It.IsAny<string>()))
+ .Callback<Func<object>, Type, string>((accessor, type, propertyName) =>
+ {
+ Assert.Equal("Test", accessor());
+ Assert.Equal(typeof(DummyContactModel), type);
+ Assert.Equal("FirstName", propertyName);
+ })
+ .Returns(() => null)
+ .Verifiable();
+
+ // Act
+ ModelMetadata.FromLambdaExpression<DummyContactModel, string>(m => m.FirstName, viewData, provider.Object);
+
+ // Assert
+ provider.Verify();
+ }
+
+ [Fact]
+ public void FromLambdaExpressionPropertyExpressionFromClosureValue()
+ {
+ // Arrange
+ Mock<ModelMetadataProvider> provider = new Mock<ModelMetadataProvider>();
+ DummyContactModel model = new DummyContactModel { FirstName = "Test" };
+ ViewDataDictionary<object> viewData = new ViewDataDictionary<object>();
+ provider.Setup(p => p.GetMetadataForProperty(It.IsAny<Func<object>>(), It.IsAny<Type>(), It.IsAny<string>()))
+ .Callback<Func<object>, Type, string>((accessor, type, propertyName) =>
+ {
+ Assert.Equal("Test", accessor());
+ Assert.Equal(typeof(DummyContactModel), type);
+ Assert.Equal("FirstName", propertyName);
+ })
+ .Returns(() => null)
+ .Verifiable();
+
+ // Act
+ ModelMetadata.FromLambdaExpression<object, string>(m => model.FirstName, viewData, provider.Object);
+
+ // Assert
+ provider.Verify();
+ }
+
+ [Fact]
+ public void FromLambdaExpressionFieldExpressionFromParameter()
+ {
+ // Arrange
+ Mock<ModelMetadataProvider> provider = new Mock<ModelMetadataProvider>();
+ DummyContactModel model = new DummyContactModel { IntField = 42 };
+ ViewDataDictionary<DummyContactModel> viewData = new ViewDataDictionary<DummyContactModel>(model);
+ provider.Setup(p => p.GetMetadataForType(It.IsAny<Func<object>>(), It.IsAny<Type>()))
+ .Callback<Func<object>, Type>((accessor, type) =>
+ {
+ Assert.Equal(42, accessor());
+ Assert.Equal(typeof(int), type);
+ })
+ .Returns(() => null)
+ .Verifiable();
+
+ // Act
+ ModelMetadata.FromLambdaExpression<DummyContactModel, int>(m => m.IntField, viewData, provider.Object);
+
+ // Assert
+ provider.Verify();
+ }
+
+ [Fact]
+ public void FromLambdaExpressionFieldExpressionFromFieldOfClosureValue()
+ {
+ // Arrange
+ Mock<ModelMetadataProvider> provider = new Mock<ModelMetadataProvider>();
+ DummyContactModel model = new DummyContactModel { IntField = 42 };
+ ViewDataDictionary<object> viewData = new ViewDataDictionary<object>();
+ provider.Setup(p => p.GetMetadataForType(It.IsAny<Func<object>>(), It.IsAny<Type>()))
+ .Callback<Func<object>, Type>((accessor, type) =>
+ {
+ Assert.Equal(42, accessor());
+ Assert.Equal(typeof(int), type);
+ })
+ .Returns(() => null)
+ .Verifiable();
+
+ // Act
+ ModelMetadata.FromLambdaExpression<object, int>(m => model.IntField, viewData, provider.Object);
+
+ // Assert
+ provider.Verify();
+ }
+
+ [Fact]
+ public void FromLambdaExpressionFieldExpressionFromClosureValue()
+ {
+ // Arrange
+ Mock<ModelMetadataProvider> provider = new Mock<ModelMetadataProvider>();
+ DummyContactModel model = new DummyContactModel();
+ ViewDataDictionary<object> viewData = new ViewDataDictionary<object>();
+ provider.Setup(p => p.GetMetadataForType(It.IsAny<Func<object>>(), It.IsAny<Type>()))
+ .Callback<Func<object>, Type>((accessor, type) =>
+ {
+ Assert.Same(model, accessor());
+ Assert.Equal(typeof(DummyContactModel), type);
+ })
+ .Returns(() => null)
+ .Verifiable();
+
+ // Act
+ ModelMetadata.FromLambdaExpression<object, DummyContactModel>(m => model, viewData, provider.Object);
+
+ // Assert
+ provider.Verify();
+ }
+
+ [Fact]
+ public void FromLambdaExpressionSingleParameterClassIndexer()
+ {
+ // Arrange
+ Mock<ModelMetadataProvider> provider = new Mock<ModelMetadataProvider>();
+ DummyContactModel model = new DummyContactModel();
+ ViewDataDictionary<DummyContactModel> viewData = new ViewDataDictionary<DummyContactModel>(model);
+ provider.Setup(p => p.GetMetadataForType(It.IsAny<Func<object>>(), It.IsAny<Type>()))
+ .Callback<Func<object>, Type>((accessor, type) =>
+ {
+ Assert.Equal("Indexed into 42", accessor());
+ Assert.Equal(typeof(string), type);
+ })
+ .Returns(() => null)
+ .Verifiable();
+
+ // Act
+ ModelMetadata.FromLambdaExpression<DummyContactModel, string>(m => m[42], viewData, provider.Object);
+
+ // Assert
+ provider.Verify();
+ }
+
+ [Fact]
+ public void FromLambdaExpressionSingleDimensionArrayIndex()
+ {
+ // Arrange
+ Mock<ModelMetadataProvider> provider = new Mock<ModelMetadataProvider>();
+ DummyContactModel model = new DummyContactModel { Array = new[] { 4, 8, 15, 16, 23, 42 } };
+ ViewDataDictionary<DummyContactModel> viewData = new ViewDataDictionary<DummyContactModel>(model);
+ provider.Setup(p => p.GetMetadataForType(It.IsAny<Func<object>>(), It.IsAny<Type>()))
+ .Callback<Func<object>, Type>((accessor, type) =>
+ {
+ Assert.Equal(16, accessor());
+ Assert.Equal(typeof(int), type);
+ })
+ .Returns(() => null)
+ .Verifiable();
+
+ // Act
+ ModelMetadata.FromLambdaExpression<DummyContactModel, int>(m => m.Array[3], viewData, provider.Object);
+
+ // Assert
+ provider.Verify();
+ }
+
+ [Fact]
+ public void FromLambdaExpressionNullReferenceExceptionsInPropertyExpressionPreserveAllExpressionInformation()
+ {
+ // Arrange
+ Mock<ModelMetadataProvider> provider = new Mock<ModelMetadataProvider>();
+ ViewDataDictionary<DummyContactModel> viewData = new ViewDataDictionary<DummyContactModel>();
+ provider.Setup(p => p.GetMetadataForProperty(It.IsAny<Func<object>>(), It.IsAny<Type>(), It.IsAny<string>()))
+ .Callback<Func<object>, Type, string>((accessor, type, propertyName) =>
+ {
+ Assert.Null(accessor());
+ Assert.Equal(typeof(DummyContactModel), type);
+ Assert.Equal("FirstName", propertyName);
+ })
+ .Returns(() => null)
+ .Verifiable();
+
+ // Act
+ ModelMetadata.FromLambdaExpression<DummyContactModel, string>(m => m.FirstName, viewData, provider.Object);
+
+ // Assert
+ provider.Verify();
+ }
+
+ [Fact]
+ public void FromLambdaExpressionSetsContainerTypeToDerivedMostType()
+ { // Dev10 Bug #868619
+ // Arrange
+ Mock<ModelMetadataProvider> provider = new Mock<ModelMetadataProvider>();
+ ViewDataDictionary<DerivedModel> viewData = new ViewDataDictionary<DerivedModel>();
+ provider.Setup(p => p.GetMetadataForProperty(It.IsAny<Func<object>>(), It.IsAny<Type>(), It.IsAny<string>()))
+ .Callback<Func<object>, Type, string>((accessor, type, propertyName) =>
+ {
+ Assert.Null(accessor());
+ Assert.Equal(typeof(DerivedModel), type);
+ Assert.Equal("MyProperty", propertyName);
+ })
+ .Returns(() => null)
+ .Verifiable();
+
+ // Act
+ ModelMetadata.FromLambdaExpression<DerivedModel, string>(m => m.MyProperty, viewData, provider.Object);
+
+ // Assert
+ provider.Verify();
+ }
+
+ private class BaseModel
+ {
+ public virtual string MyProperty { get; set; }
+ }
+
+ private class DerivedModel : BaseModel
+ {
+ [Required]
+ public override string MyProperty
+ {
+ get { return base.MyProperty; }
+ set { base.MyProperty = value; }
+ }
+ }
+
+ // GetDisplayName()
+
+ [Fact]
+ public void ReturnsDisplayNameWhenSet()
+ {
+ // Arrange
+ Mock<ModelMetadataProvider> provider = new Mock<ModelMetadataProvider>();
+ ModelMetadata metadata = new ModelMetadata(provider.Object, null, null, typeof(object), "PropertyName") { DisplayName = "Display Name" };
+
+ // Act
+ string result = metadata.GetDisplayName();
+
+ // Assert
+ Assert.Equal("Display Name", result);
+ }
+
+ [Fact]
+ public void ReturnsPropertyNameWhenSetAndDisplayNameIsNull()
+ {
+ // Arrange
+ Mock<ModelMetadataProvider> provider = new Mock<ModelMetadataProvider>();
+ ModelMetadata metadata = new ModelMetadata(provider.Object, null, null, typeof(object), "PropertyName");
+
+ // Act
+ string result = metadata.GetDisplayName();
+
+ // Assert
+ Assert.Equal("PropertyName", result);
+ }
+
+ [Fact]
+ public void ReturnsTypeNameWhenPropertyNameAndDisplayNameAreNull()
+ {
+ // Arrange
+ Mock<ModelMetadataProvider> provider = new Mock<ModelMetadataProvider>();
+ ModelMetadata metadata = new ModelMetadata(provider.Object, null, null, typeof(object), null);
+
+ // Act
+ string result = metadata.GetDisplayName();
+
+ // Assert
+ Assert.Equal("Object", result);
+ }
+
+ // Helpers
+
+ private class DummyContactModel
+ {
+ public int IntField = 0;
+ public string FirstName { get; set; }
+ public string LastName { get; set; }
+ public Nullable<int> NullableIntValue { get; set; }
+ public int[] Array { get; set; }
+
+ public string this[int index]
+ {
+ get { return "Indexed into " + index; }
+ }
+ }
+
+ private class DummyModelContainer
+ {
+ public DummyContactModel Model { get; set; }
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Test/ModelStateDictionaryTest.cs b/test/System.Web.Mvc.Test/Test/ModelStateDictionaryTest.cs
new file mode 100644
index 00000000..deb9f014
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Test/ModelStateDictionaryTest.cs
@@ -0,0 +1,309 @@
+using System.Collections.Generic;
+using System.Globalization;
+using System.IO;
+using System.Runtime.Serialization.Formatters.Binary;
+using System.Web.TestUtil;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Mvc.Test
+{
+ public class ModelStateDictionaryTest
+ {
+ [Fact]
+ public void AddModelErrorCreatesModelStateIfNotPresent()
+ {
+ // Arrange
+ ModelStateDictionary dictionary = new ModelStateDictionary();
+
+ // Act
+ dictionary.AddModelError("some key", "some error");
+
+ // Assert
+ KeyValuePair<string, ModelState> kvp = Assert.Single(dictionary);
+ Assert.Equal("some key", kvp.Key);
+ ModelError error = Assert.Single(kvp.Value.Errors);
+ Assert.Equal("some error", error.ErrorMessage);
+ }
+
+ [Fact]
+ public void AddModelErrorThrowsIfKeyIsNull()
+ {
+ // Arrange
+ ModelStateDictionary dictionary = new ModelStateDictionary();
+
+ // Act & Assert
+ Assert.ThrowsArgumentNull(
+ delegate { dictionary.AddModelError(null, (string)null); }, "key");
+ }
+
+ [Fact]
+ public void AddModelErrorUsesExistingModelStateIfPresent()
+ {
+ // Arrange
+ ModelStateDictionary dictionary = new ModelStateDictionary();
+ dictionary.AddModelError("some key", "some error");
+ Exception ex = new Exception();
+
+ // Act
+ dictionary.AddModelError("some key", ex);
+
+ // Assert
+ KeyValuePair<string, ModelState> kvp = Assert.Single(dictionary);
+ Assert.Equal("some key", kvp.Key);
+
+ Assert.Equal(2, kvp.Value.Errors.Count);
+ Assert.Equal("some error", kvp.Value.Errors[0].ErrorMessage);
+ Assert.Same(ex, kvp.Value.Errors[1].Exception);
+ }
+
+ [Fact]
+ public void ConstructorThrowsIfDictionaryIsNull()
+ {
+ // Act & assert
+ Assert.ThrowsArgumentNull(
+ delegate { new ModelStateDictionary((ModelStateDictionary)null); }, "dictionary");
+ }
+
+ [Fact]
+ public void ConstructorWithDictionaryParameter()
+ {
+ // Arrange
+ ModelStateDictionary oldDictionary = new ModelStateDictionary()
+ {
+ { "foo", new ModelState() { Value = HtmlHelperTest.GetValueProviderResult("bar", "bar") } }
+ };
+
+ // Act
+ ModelStateDictionary newDictionary = new ModelStateDictionary(oldDictionary);
+
+ // Assert
+ Assert.Single(newDictionary);
+ Assert.Equal("bar", newDictionary["foo"].Value.ConvertTo(typeof(string)));
+ }
+
+ [Fact]
+ public void DictionaryInterface()
+ {
+ // Arrange
+ DictionaryHelper<string, ModelState> helper = new DictionaryHelper<string, ModelState>()
+ {
+ Creator = () => new ModelStateDictionary(),
+ Comparer = StringComparer.OrdinalIgnoreCase,
+ SampleKeys = new string[] { "foo", "bar", "baz", "quux", "QUUX" },
+ SampleValues = new ModelState[] { new ModelState(), new ModelState(), new ModelState(), new ModelState(), new ModelState() },
+ ThrowOnKeyNotFound = false
+ };
+
+ // Act & assert
+ helper.Execute();
+ }
+
+ [Fact]
+ public void DictionaryIsSerializable()
+ {
+ // Arrange
+ MemoryStream stream = new MemoryStream();
+ BinaryFormatter formatter = new BinaryFormatter();
+
+ ModelStateDictionary originalDict = new ModelStateDictionary();
+ originalDict.AddModelError("foo", new InvalidOperationException("Some invalid operation."));
+ originalDict.AddModelError("foo", new InvalidOperationException("Some other invalid operation."));
+ originalDict.AddModelError("bar", "Some exception text.");
+ originalDict.SetModelValue("baz", new ValueProviderResult("rawValue", "attemptedValue", CultureInfo.GetCultureInfo("fr-FR")));
+
+ // Act
+ formatter.Serialize(stream, originalDict);
+ stream.Position = 0;
+ ModelStateDictionary deserializedDict = formatter.Deserialize(stream) as ModelStateDictionary;
+
+ // Assert
+ Assert.NotNull(deserializedDict);
+ Assert.Equal(3, deserializedDict.Count);
+
+ ModelState foo = deserializedDict["FOO"];
+ Assert.IsType<InvalidOperationException>(foo.Errors[0].Exception);
+ Assert.Equal("Some invalid operation.", foo.Errors[0].Exception.Message);
+ Assert.IsType<InvalidOperationException>(foo.Errors[1].Exception);
+ Assert.Equal("Some other invalid operation.", foo.Errors[1].Exception.Message);
+
+ ModelState bar = deserializedDict["BAR"];
+ Assert.Equal("Some exception text.", bar.Errors[0].ErrorMessage);
+
+ ModelState baz = deserializedDict["BAZ"];
+ Assert.Equal("rawValue", baz.Value.RawValue);
+ Assert.Equal("attemptedValue", baz.Value.AttemptedValue);
+ Assert.Equal(CultureInfo.GetCultureInfo("fr-FR"), baz.Value.Culture);
+ }
+
+ [Fact]
+ public void IsValidFieldReturnsFalseIfDictionaryDoesNotContainKey()
+ {
+ // Arrange
+ ModelStateDictionary msd = new ModelStateDictionary();
+
+ // Act
+ bool isValid = msd.IsValidField("foo");
+
+ // Assert
+ Assert.True(isValid);
+ }
+
+ [Fact]
+ public void IsValidFieldReturnsFalseIfKeyChildContainsErrors()
+ {
+ // Arrange
+ ModelStateDictionary msd = new ModelStateDictionary();
+ msd.AddModelError("foo.bar", "error text");
+
+ // Act
+ bool isValid = msd.IsValidField("foo");
+
+ // Assert
+ Assert.False(isValid);
+ }
+
+ [Fact]
+ public void IsValidFieldReturnsFalseIfKeyContainsErrors()
+ {
+ // Arrange
+ ModelStateDictionary msd = new ModelStateDictionary();
+ msd.AddModelError("foo", "error text");
+
+ // Act
+ bool isValid = msd.IsValidField("foo");
+
+ // Assert
+ Assert.False(isValid);
+ }
+
+ [Fact]
+ public void IsValidFieldReturnsTrueIfModelStateDoesNotContainErrors()
+ {
+ // Arrange
+ ModelStateDictionary msd = new ModelStateDictionary()
+ {
+ { "foo", new ModelState() { Value = new ValueProviderResult(null, null, null) } }
+ };
+
+ // Act
+ bool isValid = msd.IsValidField("foo");
+
+ // Assert
+ Assert.True(isValid);
+ }
+
+ [Fact]
+ public void IsValidFieldThrowsIfKeyIsNull()
+ {
+ // Arrange
+ ModelStateDictionary msd = new ModelStateDictionary();
+
+ // Act & assert
+ Assert.ThrowsArgumentNull(
+ delegate { msd.IsValidField(null); }, "key");
+ }
+
+ [Fact]
+ public void IsValidPropertyReturnsFalseIfErrors()
+ {
+ // Arrange
+ ModelState errorState = new ModelState() { Value = HtmlHelperTest.GetValueProviderResult("quux", "quux") };
+ errorState.Errors.Add("some error");
+ ModelStateDictionary dictionary = new ModelStateDictionary()
+ {
+ { "foo", new ModelState() { Value = HtmlHelperTest.GetValueProviderResult("bar", "bar") } },
+ { "baz", errorState }
+ };
+
+ // Act
+ bool isValid = dictionary.IsValid;
+
+ // Assert
+ Assert.False(isValid);
+ }
+
+ [Fact]
+ public void IsValidPropertyReturnsTrueIfNoErrors()
+ {
+ // Arrange
+ ModelStateDictionary dictionary = new ModelStateDictionary()
+ {
+ { "foo", new ModelState() { Value = HtmlHelperTest.GetValueProviderResult("bar", "bar") } },
+ { "baz", new ModelState() { Value = HtmlHelperTest.GetValueProviderResult("quux", "bar") } }
+ };
+
+ // Act
+ bool isValid = dictionary.IsValid;
+
+ // Assert
+ Assert.True(isValid);
+ }
+
+ [Fact]
+ public void MergeCopiesDictionaryEntries()
+ {
+ // Arrange
+ ModelStateDictionary fooDict = new ModelStateDictionary() { { "foo", new ModelState() } };
+ ModelStateDictionary barDict = new ModelStateDictionary() { { "bar", new ModelState() } };
+
+ // Act
+ fooDict.Merge(barDict);
+
+ // Assert
+ Assert.Equal(2, fooDict.Count);
+ Assert.Equal(barDict["bar"], fooDict["bar"]);
+ }
+
+ [Fact]
+ public void MergeDoesNothingIfParameterIsNull()
+ {
+ // Arrange
+ ModelStateDictionary fooDict = new ModelStateDictionary() { { "foo", new ModelState() } };
+
+ // Act
+ fooDict.Merge(null);
+
+ // Assert
+ Assert.Single(fooDict);
+ Assert.True(fooDict.ContainsKey("foo"));
+ }
+
+ [Fact]
+ public void SetAttemptedValueCreatesModelStateIfNotPresent()
+ {
+ // Arrange
+ ModelStateDictionary dictionary = new ModelStateDictionary();
+
+ // Act
+ dictionary.SetModelValue("some key", HtmlHelperTest.GetValueProviderResult("some value", "some value"));
+
+ // Assert
+ Assert.Single(dictionary);
+ ModelState modelState = dictionary["some key"];
+
+ Assert.Empty(modelState.Errors);
+ Assert.Equal("some value", modelState.Value.ConvertTo(typeof(string)));
+ }
+
+ [Fact]
+ public void SetAttemptedValueUsesExistingModelStateIfPresent()
+ {
+ // Arrange
+ ModelStateDictionary dictionary = new ModelStateDictionary();
+ dictionary.AddModelError("some key", "some error");
+ Exception ex = new Exception();
+
+ // Act
+ dictionary.SetModelValue("some key", HtmlHelperTest.GetValueProviderResult("some value", "some value"));
+
+ // Assert
+ Assert.Single(dictionary);
+ ModelState modelState = dictionary["some key"];
+
+ Assert.Single(modelState.Errors);
+ Assert.Equal("some error", modelState.Errors[0].ErrorMessage);
+ Assert.Equal("some value", modelState.Value.ConvertTo(typeof(string)));
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Test/ModelStateTest.cs b/test/System.Web.Mvc.Test/Test/ModelStateTest.cs
new file mode 100644
index 00000000..d3e1df95
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Test/ModelStateTest.cs
@@ -0,0 +1,17 @@
+using Xunit;
+
+namespace System.Web.Mvc.Test
+{
+ public class ModelStateTest
+ {
+ [Fact]
+ public void ErrorsProperty()
+ {
+ // Arrange
+ ModelState modelState = new ModelState();
+
+ // Act & Assert
+ Assert.NotNull(modelState.Errors);
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Test/ModelValidationResultTest.cs b/test/System.Web.Mvc.Test/Test/ModelValidationResultTest.cs
new file mode 100644
index 00000000..18a51cd8
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Test/ModelValidationResultTest.cs
@@ -0,0 +1,28 @@
+using System.Web.TestUtil;
+using Xunit;
+
+namespace System.Web.Mvc.Test
+{
+ public class ModelValidationResultTest
+ {
+ [Fact]
+ public void MemberNameProperty()
+ {
+ // Arrange
+ ModelValidationResult result = new ModelValidationResult();
+
+ // Act & assert
+ MemberHelper.TestStringProperty(result, "MemberName", String.Empty);
+ }
+
+ [Fact]
+ public void MessageProperty()
+ {
+ // Arrange
+ ModelValidationResult result = new ModelValidationResult();
+
+ // Act & assert
+ MemberHelper.TestStringProperty(result, "Message", String.Empty);
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Test/ModelValidatorProviderCollectionTest.cs b/test/System.Web.Mvc.Test/Test/ModelValidatorProviderCollectionTest.cs
new file mode 100644
index 00000000..e9a1d58b
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Test/ModelValidatorProviderCollectionTest.cs
@@ -0,0 +1,185 @@
+using System.Collections.Generic;
+using System.Linq;
+using Moq;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Mvc.Test
+{
+ public class ModelValidatorProviderCollectionTest
+ {
+ [Fact]
+ public void ListWrappingConstructor()
+ {
+ // Arrange
+ List<ModelValidatorProvider> list = new List<ModelValidatorProvider>()
+ {
+ new Mock<ModelValidatorProvider>().Object, new Mock<ModelValidatorProvider>().Object
+ };
+
+ // Act
+ ModelValidatorProviderCollection collection = new ModelValidatorProviderCollection(list);
+
+ // Assert
+ Assert.Equal(list, collection.ToList());
+ }
+
+ [Fact]
+ public void ListWrappingConstructorThrowsIfListIsNull()
+ {
+ // Act & Assert
+ Assert.ThrowsArgumentNull(
+ delegate { new ModelValidatorProviderCollection((IList<ModelValidatorProvider>)null); },
+ "list");
+ }
+
+ [Fact]
+ public void DefaultConstructor()
+ {
+ // Act
+ ModelValidatorProviderCollection collection = new ModelValidatorProviderCollection();
+
+ // Assert
+ Assert.Empty(collection);
+ }
+
+ [Fact]
+ public void AddNullModelValidatorProviderThrows()
+ {
+ // Arrange
+ ModelValidatorProviderCollection collection = new ModelValidatorProviderCollection();
+
+ // Act & Assert
+ Assert.ThrowsArgumentNull(
+ delegate { collection.Add(null); },
+ "item");
+ }
+
+ [Fact]
+ public void SetItem()
+ {
+ // Arrange
+ ModelValidatorProviderCollection collection = new ModelValidatorProviderCollection();
+ collection.Add(new Mock<ModelValidatorProvider>().Object);
+
+ ModelValidatorProvider newProvider = new Mock<ModelValidatorProvider>().Object;
+
+ // Act
+ collection[0] = newProvider;
+
+ // Assert
+ ModelValidatorProvider provider = Assert.Single(collection);
+ Assert.Equal(newProvider, provider);
+ }
+
+ [Fact]
+ public void SetNullModelValidatorProviderThrows()
+ {
+ // Arrange
+ ModelValidatorProviderCollection collection = new ModelValidatorProviderCollection();
+ collection.Add(new Mock<ModelValidatorProvider>().Object);
+
+ // Act & Assert
+ Assert.ThrowsArgumentNull(
+ delegate { collection[0] = null; },
+ "item");
+ }
+
+ [Fact]
+ public void GetValidators()
+ {
+ // Arrange
+ ModelMetadata metadata = GetMetadata();
+ ControllerContext controllerContext = new ControllerContext();
+
+ ModelValidator[] allValidators = new ModelValidator[]
+ {
+ new SimpleModelValidator(),
+ new SimpleModelValidator(),
+ new SimpleModelValidator(),
+ new SimpleModelValidator(),
+ new SimpleModelValidator()
+ };
+
+ Mock<ModelValidatorProvider> provider1 = new Mock<ModelValidatorProvider>();
+ provider1.Setup(p => p.GetValidators(metadata, controllerContext)).Returns(new ModelValidator[]
+ {
+ allValidators[0], allValidators[1]
+ });
+
+ Mock<ModelValidatorProvider> provider2 = new Mock<ModelValidatorProvider>();
+ provider2.Setup(p => p.GetValidators(metadata, controllerContext)).Returns(new ModelValidator[]
+ {
+ allValidators[2], allValidators[3], allValidators[4]
+ });
+
+ ModelValidatorProviderCollection collection = new ModelValidatorProviderCollection();
+ collection.Add(provider1.Object);
+ collection.Add(provider2.Object);
+
+ // Act
+ IEnumerable<ModelValidator> returnedValidators = collection.GetValidators(metadata, controllerContext);
+
+ // Assert
+ Assert.Equal(allValidators, returnedValidators.ToArray());
+ }
+
+ [Fact]
+ public void GetValidatorsDelegatesToResolver()
+ {
+ // Arrange
+ ModelValidator[] allValidators = new ModelValidator[]
+ {
+ new SimpleModelValidator(),
+ new SimpleModelValidator(),
+ new SimpleModelValidator(),
+ new SimpleModelValidator()
+ };
+
+ ModelMetadata metadata = GetMetadata();
+ ControllerContext controllerContext = new ControllerContext();
+
+ Mock<ModelValidatorProvider> resolverProvider1 = new Mock<ModelValidatorProvider>();
+ resolverProvider1.Setup(p => p.GetValidators(metadata, controllerContext)).Returns(new ModelValidator[]
+ {
+ allValidators[0], allValidators[1]
+ });
+
+ Mock<ModelValidatorProvider> resolverprovider2 = new Mock<ModelValidatorProvider>();
+ resolverprovider2.Setup(p => p.GetValidators(metadata, controllerContext)).Returns(new ModelValidator[]
+ {
+ allValidators[2], allValidators[3]
+ });
+
+ Resolver<IEnumerable<ModelValidatorProvider>> resolver = new Resolver<IEnumerable<ModelValidatorProvider>>();
+ resolver.Current = new ModelValidatorProvider[] { resolverProvider1.Object, resolverprovider2.Object };
+
+ ModelValidatorProviderCollection collection = new ModelValidatorProviderCollection(resolver);
+
+ // Act
+ IEnumerable<ModelValidator> returnedValidators = collection.GetValidators(metadata, controllerContext);
+
+ // Assert
+ Assert.Equal(allValidators, returnedValidators.ToArray());
+ }
+
+ private static ModelMetadata GetMetadata()
+ {
+ ModelMetadataProvider provider = new EmptyModelMetadataProvider();
+ return provider.GetMetadataForType(null, typeof(object));
+ }
+
+ private sealed class SimpleModelValidator : ModelValidator
+ {
+ public SimpleModelValidator()
+ : base(GetMetadata(), new ControllerContext())
+ {
+ }
+
+ public override IEnumerable<ModelValidationResult> Validate(object container)
+ {
+ throw new NotImplementedException();
+ }
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Test/ModelValidatorProvidersTest.cs b/test/System.Web.Mvc.Test/Test/ModelValidatorProvidersTest.cs
new file mode 100644
index 00000000..175ac7bb
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Test/ModelValidatorProvidersTest.cs
@@ -0,0 +1,26 @@
+using System.Linq;
+using Xunit;
+
+namespace System.Web.Mvc.Test
+{
+ public class ModelValidatorProvidersTest
+ {
+ [Fact]
+ public void CollectionDefaults()
+ {
+ // Arrange
+ Type[] expectedTypes = new Type[]
+ {
+ typeof(DataAnnotationsModelValidatorProvider),
+ typeof(DataErrorInfoModelValidatorProvider),
+ typeof(ClientDataTypeModelValidatorProvider)
+ };
+
+ // Act
+ Type[] actualTypes = ModelValidatorProviders.Providers.Select(p => p.GetType()).ToArray();
+
+ // Assert
+ Assert.Equal(expectedTypes, actualTypes);
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Test/ModelValidatorTest.cs b/test/System.Web.Mvc.Test/Test/ModelValidatorTest.cs
new file mode 100644
index 00000000..663de14e
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Test/ModelValidatorTest.cs
@@ -0,0 +1,233 @@
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Linq;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Mvc.Test
+{
+ public class ModelValidatorTest
+ {
+ [Fact]
+ public void ConstructorGuards()
+ {
+ // Arrange
+ ModelMetadata metadata = ModelMetadataProviders.Current.GetMetadataForType(null, typeof(object));
+ ControllerContext context = new ControllerContext();
+
+ // Act & Assert
+ Assert.ThrowsArgumentNull(
+ () => new TestableModelValidator(null, context),
+ "metadata");
+ Assert.ThrowsArgumentNull(
+ () => new TestableModelValidator(metadata, null),
+ "controllerContext");
+ }
+
+ [Fact]
+ public void ValuesSet()
+ {
+ // Arrange
+ ModelMetadata metadata = ModelMetadataProviders.Current.GetMetadataForProperty(() => 15, typeof(string), "Length");
+ ControllerContext context = new ControllerContext();
+
+ // Act
+ TestableModelValidator validator = new TestableModelValidator(metadata, context);
+
+ // Assert
+ Assert.Same(context, validator.ControllerContext);
+ Assert.Same(metadata, validator.Metadata);
+ }
+
+ [Fact]
+ public void NoClientRulesByDefault()
+ {
+ // Arrange
+ ModelMetadata metadata = ModelMetadataProviders.Current.GetMetadataForProperty(() => 15, typeof(string), "Length");
+ ControllerContext context = new ControllerContext();
+
+ // Act
+ TestableModelValidator validator = new TestableModelValidator(metadata, context);
+
+ // Assert
+ Assert.Empty(validator.GetClientValidationRules());
+ }
+
+ [Fact]
+ public void IsRequiredFalseByDefault()
+ {
+ // Arrange
+ ModelMetadata metadata = ModelMetadataProviders.Current.GetMetadataForProperty(() => 15, typeof(string), "Length");
+ ControllerContext context = new ControllerContext();
+
+ // Act
+ TestableModelValidator validator = new TestableModelValidator(metadata, context);
+
+ // Assert
+ Assert.False(validator.IsRequired);
+ }
+
+ [Fact]
+ public void GetModelValidator_DoesNotReadPropertyValues()
+ {
+ ModelValidatorProvider[] originalProviders = ModelValidatorProviders.Providers.ToArray();
+ try
+ {
+ // Arrange
+ ModelValidatorProviders.Providers.Clear();
+ ModelValidatorProviders.Providers.Add(new ObservableModelValidatorProvider());
+
+ ObservableModel model = new ObservableModel();
+ ModelMetadata metadata = new EmptyModelMetadataProvider().GetMetadataForType(() => model, typeof(ObservableModel));
+ ControllerContext controllerContext = new ControllerContext();
+
+ // Act
+ ModelValidator validator = ModelValidator.GetModelValidator(metadata, controllerContext);
+ ModelValidationResult[] results = validator.Validate(model).ToArray();
+
+ // Assert
+ Assert.False(model.PropertyWasRead());
+ }
+ finally
+ {
+ ModelValidatorProviders.Providers.Clear();
+ foreach (ModelValidatorProvider provider in originalProviders)
+ {
+ ModelValidatorProviders.Providers.Add(provider);
+ }
+ }
+ }
+
+ private class ObservableModelValidatorProvider : ModelValidatorProvider
+ {
+ public override IEnumerable<ModelValidator> GetValidators(ModelMetadata metadata, ControllerContext context)
+ {
+ return new ModelValidator[] { new ObservableModelValidator(metadata, context) };
+ }
+
+ private class ObservableModelValidator : ModelValidator
+ {
+ public ObservableModelValidator(ModelMetadata metadata, ControllerContext controllerContext)
+ : base(metadata, controllerContext)
+ {
+ }
+
+ public override IEnumerable<ModelValidationResult> Validate(object container)
+ {
+ return Enumerable.Empty<ModelValidationResult>();
+ }
+ }
+ }
+
+ private class ObservableModel
+ {
+ private bool _propertyWasRead;
+
+ public int TheProperty
+ {
+ get
+ {
+ _propertyWasRead = true;
+ return 42;
+ }
+ }
+
+ public bool PropertyWasRead()
+ {
+ return _propertyWasRead;
+ }
+ }
+
+ [Fact]
+ public void GetModelValidatorWithTypeLevelValidator()
+ {
+ // Arrange
+ ControllerContext context = new ControllerContext();
+ DataErrorInfo1 model = new DataErrorInfo1 { Error = "Some Type Error" };
+ ModelMetadata metadata = ModelMetadataProviders.Current.GetMetadataForType(() => model, model.GetType());
+ ModelValidator validator = ModelValidator.GetModelValidator(metadata, context);
+
+ // Act
+ ModelValidationResult result = validator.Validate(null).Single();
+
+ // Assert
+ Assert.Equal(String.Empty, result.MemberName);
+ Assert.Equal("Some Type Error", result.Message);
+ }
+
+ [Fact]
+ public void GetModelValidatorWithPropertyLevelValidator()
+ {
+ // Arrange
+ ControllerContext context = new ControllerContext();
+ DataErrorInfo1 model = new DataErrorInfo1();
+ model["SomeStringProperty"] = "Some Property Error";
+ ModelMetadata metadata = ModelMetadataProviders.Current.GetMetadataForType(() => model, model.GetType());
+ ModelValidator validator = ModelValidator.GetModelValidator(metadata, context);
+
+ // Act
+ ModelValidationResult result = validator.Validate(null).Single();
+
+ // Assert
+ Assert.Equal("SomeStringProperty", result.MemberName);
+ Assert.Equal("Some Property Error", result.Message);
+ }
+
+ [Fact]
+ public void GetModelValidatorWithFailedPropertyValidatorsPreventsTypeValidatorFromRunning()
+ {
+ // Arrange
+ ControllerContext context = new ControllerContext();
+ DataErrorInfo1 model = new DataErrorInfo1 { Error = "Some Type Error" };
+ model["SomeStringProperty"] = "Some Property Error";
+ model["SomeOtherStringProperty"] = "Some Other Property Error";
+ ModelMetadata metadata = ModelMetadataProviders.Current.GetMetadataForType(() => model, model.GetType());
+ ModelValidator validator = ModelValidator.GetModelValidator(metadata, context);
+
+ // Act
+ List<ModelValidationResult> result = validator.Validate(null).ToList();
+
+ // Assert
+ Assert.Equal(2, result.Count);
+ Assert.Equal("SomeStringProperty", result[0].MemberName);
+ Assert.Equal("Some Property Error", result[0].Message);
+ Assert.Equal("SomeOtherStringProperty", result[1].MemberName);
+ Assert.Equal("Some Other Property Error", result[1].Message);
+ }
+
+ private class TestableModelValidator : ModelValidator
+ {
+ public TestableModelValidator(ModelMetadata metadata, ControllerContext context)
+ : base(metadata, context)
+ {
+ }
+
+ public override IEnumerable<ModelValidationResult> Validate(object container)
+ {
+ throw new NotImplementedException();
+ }
+ }
+
+ private class DataErrorInfo1 : IDataErrorInfo
+ {
+ private readonly Dictionary<string, string> _errors = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
+
+ public string SomeStringProperty { get; set; }
+
+ public string SomeOtherStringProperty { get; set; }
+
+ public string Error { get; set; }
+
+ public string this[string columnName]
+ {
+ get
+ {
+ string outVal;
+ _errors.TryGetValue(columnName, out outVal);
+ return outVal;
+ }
+ set { _errors[columnName] = value; }
+ }
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Test/MultiSelectListTest.cs b/test/System.Web.Mvc.Test/Test/MultiSelectListTest.cs
new file mode 100644
index 00000000..13173cf4
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Test/MultiSelectListTest.cs
@@ -0,0 +1,307 @@
+using System.Collections;
+using System.Collections.Generic;
+using System.Linq;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Mvc.Test
+{
+ public class MultiSelectListTest
+ {
+ [Fact]
+ public void Constructor1SetsProperties()
+ {
+ // Arrange
+ IEnumerable items = new object[0];
+
+ // Act
+ MultiSelectList multiSelect = new MultiSelectList(items);
+
+ // Assert
+ Assert.Same(items, multiSelect.Items);
+ Assert.Null(multiSelect.DataValueField);
+ Assert.Null(multiSelect.DataTextField);
+ Assert.Null(multiSelect.SelectedValues);
+ }
+
+ [Fact]
+ public void Constructor2SetsProperties()
+ {
+ // Arrange
+ IEnumerable items = new object[0];
+ IEnumerable selectedValues = new object[0];
+
+ // Act
+ MultiSelectList multiSelect = new MultiSelectList(items, selectedValues);
+
+ // Assert
+ Assert.Same(items, multiSelect.Items);
+ Assert.Null(multiSelect.DataValueField);
+ Assert.Null(multiSelect.DataTextField);
+ Assert.Same(selectedValues, multiSelect.SelectedValues);
+ }
+
+ [Fact]
+ public void Constructor3SetsProperties()
+ {
+ // Arrange
+ IEnumerable items = new object[0];
+
+ // Act
+ MultiSelectList multiSelect = new MultiSelectList(items, "SomeValueField", "SomeTextField");
+
+ // Assert
+ Assert.Same(items, multiSelect.Items);
+ Assert.Equal("SomeValueField", multiSelect.DataValueField);
+ Assert.Equal("SomeTextField", multiSelect.DataTextField);
+ Assert.Null(multiSelect.SelectedValues);
+ }
+
+ [Fact]
+ public void Constructor4SetsProperties()
+ {
+ // Arrange
+ IEnumerable items = new object[0];
+ IEnumerable selectedValues = new object[0];
+
+ // Act
+ MultiSelectList multiSelect = new MultiSelectList(items, "SomeValueField", "SomeTextField", selectedValues);
+
+ // Assert
+ Assert.Same(items, multiSelect.Items);
+ Assert.Equal("SomeValueField", multiSelect.DataValueField);
+ Assert.Equal("SomeTextField", multiSelect.DataTextField);
+ Assert.Same(selectedValues, multiSelect.SelectedValues);
+ }
+
+ [Fact]
+ public void ConstructorWithNullItemsThrows()
+ {
+ Assert.ThrowsArgumentNull(
+ delegate { new MultiSelectList(null /* items */, "dataValueField", "dataTextField", null /* selectedValues */); }, "items");
+ }
+
+ [Fact]
+ public void GetListItemsThrowsOnBindingFailure()
+ {
+ // Arrange
+ MultiSelectList multiSelect = new MultiSelectList(GetSampleFieldObjects(),
+ "Text", "Value", new string[] { "A", "C", "T" });
+
+ // Assert
+ Assert.ThrowsHttpException(
+ delegate { IList<SelectListItem> listItems = multiSelect.GetListItems(); }, "DataBinding: 'System.Web.Mvc.Test.MultiSelectListTest+Item' does not contain a property with the name 'Text'.", 500);
+ }
+
+ [Fact]
+ public void GetListItemsWithoutValueField()
+ {
+ // Arrange
+ MultiSelectList multiSelect = new MultiSelectList(GetSampleStrings());
+
+ // Act
+ IList<SelectListItem> listItems = multiSelect.GetListItems();
+
+ // Assert
+ Assert.Equal(3, listItems.Count);
+ Assert.Null(listItems[0].Value);
+ Assert.Equal("Alpha", listItems[0].Text);
+ Assert.False(listItems[0].Selected);
+ Assert.Null(listItems[1].Value);
+ Assert.Equal("Bravo", listItems[1].Text);
+ Assert.False(listItems[1].Selected);
+ Assert.Null(listItems[2].Value);
+ Assert.Equal("Charlie", listItems[2].Text);
+ Assert.False(listItems[2].Selected);
+ }
+
+ [Fact]
+ public void GetListItemsWithoutValueFieldWithSelections()
+ {
+ // Arrange
+ MultiSelectList multiSelect = new MultiSelectList(GetSampleStrings(), new string[] { "Alpha", "Charlie", "Tango" });
+
+ // Act
+ IList<SelectListItem> listItems = multiSelect.GetListItems();
+
+ // Assert
+ Assert.Equal(3, listItems.Count);
+ Assert.Null(listItems[0].Value);
+ Assert.Equal("Alpha", listItems[0].Text);
+ Assert.True(listItems[0].Selected);
+ Assert.Null(listItems[1].Value);
+ Assert.Equal("Bravo", listItems[1].Text);
+ Assert.False(listItems[1].Selected);
+ Assert.Null(listItems[2].Value);
+ Assert.Equal("Charlie", listItems[2].Text);
+ Assert.True(listItems[2].Selected);
+ }
+
+ [Fact]
+ public void GetListItemsWithValueField()
+ {
+ // Arrange
+ MultiSelectList multiSelect = new MultiSelectList(GetSampleAnonymousObjects(), "Letter", "FullWord");
+
+ // Act
+ IList<SelectListItem> listItems = multiSelect.GetListItems();
+
+ // Assert
+ Assert.Equal(3, listItems.Count);
+ Assert.Equal("A", listItems[0].Value);
+ Assert.Equal("Alpha", listItems[0].Text);
+ Assert.False(listItems[0].Selected);
+ Assert.Equal("B", listItems[1].Value);
+ Assert.Equal("Bravo", listItems[1].Text);
+ Assert.False(listItems[1].Selected);
+ Assert.Equal("C", listItems[2].Value);
+ Assert.Equal("Charlie", listItems[2].Text);
+ Assert.False(listItems[2].Selected);
+ }
+
+ [Fact]
+ public void GetListItemsWithValueFieldWithSelections()
+ {
+ // Arrange
+ MultiSelectList multiSelect = new MultiSelectList(GetSampleAnonymousObjects(),
+ "Letter", "FullWord", new string[] { "A", "C", "T" });
+
+ // Act
+ IList<SelectListItem> listItems = multiSelect.GetListItems();
+
+ // Assert
+ Assert.Equal(3, listItems.Count);
+ Assert.Equal("A", listItems[0].Value);
+ Assert.Equal("Alpha", listItems[0].Text);
+ Assert.True(listItems[0].Selected);
+ Assert.Equal("B", listItems[1].Value);
+ Assert.Equal("Bravo", listItems[1].Text);
+ Assert.False(listItems[1].Selected);
+ Assert.Equal("C", listItems[2].Value);
+ Assert.Equal("Charlie", listItems[2].Text);
+ Assert.True(listItems[2].Selected);
+ }
+
+ [Fact]
+ public void IEnumerableWithAnonymousObjectsAndTextValueFields()
+ {
+ // Arrange
+ MultiSelectList multiSelect = new MultiSelectList(GetSampleAnonymousObjects(),
+ "Letter", "FullWord", new string[] { "A", "C", "T" });
+
+ // Act
+ IEnumerator enumerator = multiSelect.GetEnumerator();
+ enumerator.MoveNext();
+ SelectListItem firstItem = enumerator.Current as SelectListItem;
+ SelectListItem lastItem = null;
+
+ while (enumerator.MoveNext())
+ {
+ lastItem = enumerator.Current as SelectListItem;
+ }
+
+ // Assert
+ Assert.True(firstItem != null);
+ Assert.Equal("Alpha", firstItem.Text);
+ Assert.Equal("A", firstItem.Value);
+ Assert.True(firstItem.Selected);
+
+ Assert.True(lastItem != null);
+ Assert.Equal("Charlie", lastItem.Text);
+ Assert.Equal("C", lastItem.Value);
+ Assert.True(lastItem.Selected);
+ }
+
+ internal static IEnumerable GetSampleAnonymousObjects()
+ {
+ return new[]
+ {
+ new { Letter = 'A', FullWord = "Alpha" },
+ new { Letter = 'B', FullWord = "Bravo" },
+ new { Letter = 'C', FullWord = "Charlie" }
+ };
+ }
+
+ internal static IEnumerable GetSampleFieldObjects()
+ {
+ return new[]
+ {
+ new Item { Text = "A", Value = "Alpha" },
+ new Item { Text = "B", Value = "Bravo" },
+ new Item { Text = "C", Value = "Charlie" }
+ };
+ }
+
+ internal static List<SelectListItem> GetSampleListObjects()
+ {
+ List<SelectListItem> list = new List<SelectListItem>();
+ string selectedSSN = "111111111";
+
+ foreach (Person person in GetSamplePeople())
+ {
+ list.Add(new SelectListItem
+ {
+ Text = person.FirstName,
+ Value = person.SSN,
+ Selected = String.Equals(person.SSN, selectedSSN)
+ });
+ }
+ return list;
+ }
+
+ internal static IEnumerable<SelectListItem> GetSampleIEnumerableObjects()
+ {
+ Person[] people = GetSamplePeople();
+
+ string selectedSSN = "111111111";
+ IEnumerable<SelectListItem> list = from person in people
+ select new SelectListItem
+ {
+ Text = person.FirstName,
+ Value = person.SSN,
+ Selected = String.Equals(person.SSN, selectedSSN)
+ };
+ return list;
+ }
+
+ internal static IEnumerable GetSampleStrings()
+ {
+ return new string[] { "Alpha", "Bravo", "Charlie" };
+ }
+
+ internal static Person[] GetSamplePeople()
+ {
+ return new Person[]
+ {
+ new Person
+ {
+ FirstName = "John",
+ SSN = "123456789"
+ },
+ new Person
+ {
+ FirstName = "Jane",
+ SSN = "987654321"
+ },
+ new Person
+ {
+ FirstName = "Joe",
+ SSN = "111111111"
+ }
+ };
+ }
+
+ internal class Item
+ {
+ public string Text;
+ public string Value;
+ }
+
+ internal class Person
+ {
+ public string FirstName { get; set; }
+
+ public string SSN { get; set; }
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Test/MultiServiceResolverTest.cs b/test/System.Web.Mvc.Test/Test/MultiServiceResolverTest.cs
new file mode 100644
index 00000000..22084ddb
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Test/MultiServiceResolverTest.cs
@@ -0,0 +1,136 @@
+using System.Collections.Generic;
+using System.Linq;
+using Moq;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Mvc.Test
+{
+ public class MultiServiceResolverTest
+ {
+ [Fact]
+ public void ConstructorWithNullThunkArgumentThrows()
+ {
+ // Act & Assert
+ Assert.ThrowsArgumentNull(
+ delegate { new MultiServiceResolver<TestProvider>(null); },
+ "itemsThunk");
+ }
+
+ [Fact]
+ public void CurrentPrependsFromResolver()
+ {
+ // Arrange
+ IEnumerable<TestProvider> providersFromServiceLocation = GetProvidersFromService();
+ IEnumerable<TestProvider> providersFromItemsThunk = GetProvidersFromItemsThunk();
+ IEnumerable<TestProvider> expectedProviders = providersFromServiceLocation.Concat(providersFromItemsThunk);
+
+ Mock<IDependencyResolver> resolver = new Mock<IDependencyResolver>();
+ resolver.Setup(r => r.GetServices(typeof(TestProvider)))
+ .Returns(providersFromServiceLocation);
+
+ MultiServiceResolver<TestProvider> multiResolver = new MultiServiceResolver<TestProvider>(() => providersFromItemsThunk, resolver.Object);
+
+ // Act
+ IEnumerable<TestProvider> returnedProviders = multiResolver.Current;
+
+ // Assert
+ Assert.Equal(expectedProviders.ToList(), returnedProviders.ToList());
+ }
+
+ [Fact]
+ public void CurrentCachesResolverResult()
+ {
+ // Arrange
+ IEnumerable<TestProvider> providersFromServiceLocation = GetProvidersFromService();
+ IEnumerable<TestProvider> providersFromItemsThunk = GetProvidersFromItemsThunk();
+ IEnumerable<TestProvider> expectedProviders = providersFromServiceLocation.Concat(providersFromItemsThunk);
+
+ Mock<IDependencyResolver> resolver = new Mock<IDependencyResolver>();
+ resolver.Setup(r => r.GetServices(typeof(TestProvider)))
+ .Returns(providersFromServiceLocation);
+
+ MultiServiceResolver<TestProvider> multiResolver = new MultiServiceResolver<TestProvider>(() => providersFromItemsThunk, resolver.Object);
+
+ // Act
+ IEnumerable<TestProvider> returnedProviders = multiResolver.Current;
+ IEnumerable<TestProvider> cachedProviders = multiResolver.Current;
+
+ // Assert
+ Assert.Equal(expectedProviders.ToList(), returnedProviders.ToList());
+ Assert.Equal(expectedProviders.ToList(), cachedProviders.ToList());
+ resolver.Verify(r => r.GetServices(typeof(TestProvider)), Times.Exactly(1));
+ }
+
+ [Fact]
+ public void CurrentReturnsCurrentItemsWhenResolverReturnsNoInstances()
+ {
+ // Arrange
+ IEnumerable<TestProvider> providersFromItemsThunk = GetProvidersFromItemsThunk();
+ MultiServiceResolver<TestProvider> resolver = new MultiServiceResolver<TestProvider>(() => providersFromItemsThunk);
+
+ // Act
+ IEnumerable<TestProvider> returnedProviders = resolver.Current;
+
+ // Assert
+ Assert.Equal(providersFromItemsThunk.ToList(), returnedProviders.ToList());
+ }
+
+ [Fact]
+ public void CurrentDoesNotQueryResolverAfterNoInstancesAreReturned()
+ {
+ // Arrange
+ IEnumerable<TestProvider> providersFromItemsThunk = GetProvidersFromItemsThunk();
+ Mock<IDependencyResolver> resolver = new Mock<IDependencyResolver>();
+ resolver.Setup(r => r.GetServices(typeof(TestProvider)))
+ .Returns(new TestProvider[0]);
+ MultiServiceResolver<TestProvider> multiResolver = new MultiServiceResolver<TestProvider>(() => providersFromItemsThunk, resolver.Object);
+
+ // Act
+ IEnumerable<TestProvider> returnedProviders = multiResolver.Current;
+ IEnumerable<TestProvider> cachedProviders = multiResolver.Current;
+
+ // Assert
+ Assert.Equal(providersFromItemsThunk.ToList(), returnedProviders.ToList());
+ Assert.Equal(providersFromItemsThunk.ToList(), cachedProviders.ToList());
+ resolver.Verify(r => r.GetServices(typeof(TestProvider)), Times.Exactly(1));
+ }
+
+ [Fact]
+ public void CurrentPropagatesExceptionWhenResolverThrowsNonActivationException()
+ {
+ // Arrange
+ Mock<IDependencyResolver> resolver = new Mock<IDependencyResolver>(MockBehavior.Strict);
+ MultiServiceResolver<TestProvider> multiResolver = new MultiServiceResolver<TestProvider>(() => null, resolver.Object);
+
+ // Act & Assert
+ Assert.Throws<MockException>(
+ () => multiResolver.Current,
+ @"IDependencyResolver.GetServices(System.Web.Mvc.Test.MultiServiceResolverTest+TestProvider) invocation failed with mock behavior Strict.
+All invocations on the mock must have a corresponding setup."
+ );
+ }
+
+ private class TestProvider
+ {
+ }
+
+ private IEnumerable<TestProvider> GetProvidersFromService()
+ {
+ return new TestProvider[]
+ {
+ new TestProvider(),
+ new TestProvider()
+ };
+ }
+
+ private IEnumerable<TestProvider> GetProvidersFromItemsThunk()
+ {
+ return new TestProvider[]
+ {
+ new TestProvider(),
+ new TestProvider()
+ };
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Test/MvcHandlerTest.cs b/test/System.Web.Mvc.Test/Test/MvcHandlerTest.cs
new file mode 100644
index 00000000..7b874fb4
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Test/MvcHandlerTest.cs
@@ -0,0 +1,360 @@
+using System.Web.Mvc.Async;
+using System.Web.Mvc.Async.Test;
+using System.Web.Routing;
+using System.Web.SessionState;
+using Moq;
+using Moq.Protected;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Mvc.Test
+{
+ public class MvcHandlerTest
+ {
+ [Fact]
+ public void ConstructorWithNullRequestContextThrows()
+ {
+ Assert.ThrowsArgumentNull(
+ delegate { new MvcHandler(null); },
+ "requestContext");
+ }
+
+ [Fact]
+ public void ProcessRequestWithRouteWithoutControllerThrows()
+ {
+ // Arrange
+ Mock<HttpContextBase> contextMock = new Mock<HttpContextBase>();
+ contextMock.ExpectMvcVersionResponseHeader().Verifiable();
+ RouteData rd = new RouteData();
+ MvcHandler mvcHandler = new MvcHandler(new RequestContext(contextMock.Object, rd));
+
+ // Act
+ Assert.Throws<InvalidOperationException>(
+ delegate { mvcHandler.ProcessRequest(contextMock.Object); },
+ "The RouteData must contain an item named 'controller' with a non-empty string value.");
+
+ // Assert
+ contextMock.Verify();
+ }
+
+ [Fact]
+ public void ProcessRequestAddsServerHeaderCallsExecute()
+ {
+ // Arrange
+ Mock<HttpContextBase> contextMock = new Mock<HttpContextBase>();
+ contextMock.ExpectMvcVersionResponseHeader().Verifiable();
+
+ RouteData rd = new RouteData();
+ rd.Values.Add("controller", "foo");
+ RequestContext requestContext = new RequestContext(contextMock.Object, rd);
+ MvcHandler mvcHandler = new MvcHandler(requestContext);
+
+ Mock<ControllerBase> controllerMock = new Mock<ControllerBase>();
+ controllerMock.Protected().Setup("Execute", requestContext).Verifiable();
+
+ ControllerBuilder cb = new ControllerBuilder();
+ Mock<IControllerFactory> controllerFactoryMock = new Mock<IControllerFactory>();
+ controllerFactoryMock.Setup(o => o.CreateController(requestContext, "foo")).Returns(controllerMock.Object);
+ controllerFactoryMock.Setup(o => o.ReleaseController(controllerMock.Object));
+ cb.SetControllerFactory(controllerFactoryMock.Object);
+ mvcHandler.ControllerBuilder = cb;
+
+ // Act
+ mvcHandler.ProcessRequest(contextMock.Object);
+
+ // Assert
+ contextMock.Verify();
+ controllerMock.Verify();
+ }
+
+ [Fact]
+ public void ProcessRequestRemovesOptionalParametersFromRouteValueDictionary()
+ {
+ // Arrange
+ Mock<HttpContextBase> contextMock = new Mock<HttpContextBase>();
+ contextMock.ExpectMvcVersionResponseHeader();
+
+ RouteData rd = new RouteData();
+ rd.Values.Add("controller", "foo");
+ rd.Values.Add("optional", UrlParameter.Optional);
+ RequestContext requestContext = new RequestContext(contextMock.Object, rd);
+ MvcHandler mvcHandler = new MvcHandler(requestContext);
+
+ Mock<ControllerBase> controllerMock = new Mock<ControllerBase>();
+ controllerMock.Protected().Setup("Execute", requestContext).Verifiable();
+
+ ControllerBuilder cb = new ControllerBuilder();
+ Mock<IControllerFactory> controllerFactoryMock = new Mock<IControllerFactory>();
+ controllerFactoryMock.Setup(o => o.CreateController(requestContext, "foo")).Returns(controllerMock.Object);
+ controllerFactoryMock.Setup(o => o.ReleaseController(controllerMock.Object));
+ cb.SetControllerFactory(controllerFactoryMock.Object);
+ mvcHandler.ControllerBuilder = cb;
+
+ // Act
+ mvcHandler.ProcessRequest(contextMock.Object);
+
+ // Assert
+ controllerMock.Verify();
+ Assert.False(rd.Values.ContainsKey("optional"));
+ }
+
+ [Fact]
+ public void ProcessRequestWithDisabledServerHeaderOnlyCallsExecute()
+ {
+ bool oldResponseHeaderValue = MvcHandler.DisableMvcResponseHeader;
+ try
+ {
+ // Arrange
+ MvcHandler.DisableMvcResponseHeader = true;
+ Mock<HttpContextBase> contextMock = new Mock<HttpContextBase>();
+
+ RouteData rd = new RouteData();
+ rd.Values.Add("controller", "foo");
+ RequestContext requestContext = new RequestContext(contextMock.Object, rd);
+ MvcHandler mvcHandler = new MvcHandler(requestContext);
+
+ Mock<ControllerBase> controllerMock = new Mock<ControllerBase>();
+ controllerMock.Protected().Setup("Execute", requestContext).Verifiable();
+
+ ControllerBuilder cb = new ControllerBuilder();
+ Mock<IControllerFactory> controllerFactoryMock = new Mock<IControllerFactory>();
+ controllerFactoryMock.Setup(o => o.CreateController(requestContext, "foo")).Returns(controllerMock.Object);
+ controllerFactoryMock.Setup(o => o.ReleaseController(controllerMock.Object));
+ cb.SetControllerFactory(controllerFactoryMock.Object);
+ mvcHandler.ControllerBuilder = cb;
+
+ // Act
+ mvcHandler.ProcessRequest(contextMock.Object);
+
+ // Assert
+ controllerMock.Verify();
+ }
+ finally
+ {
+ MvcHandler.DisableMvcResponseHeader = oldResponseHeaderValue;
+ }
+ }
+
+ [Fact]
+ public void ProcessRequestDisposesControllerIfExecuteDoesNotThrowException()
+ {
+ // Arrange
+ Mock<ControllerBase> mockController = new Mock<ControllerBase>();
+ mockController.As<IDisposable>(); // so that Verify can be called on Dispose later
+ mockController.Protected().Setup("Execute", ItExpr.IsAny<RequestContext>()).Verifiable();
+
+ ControllerBuilder builder = new ControllerBuilder();
+ builder.SetControllerFactory(new SimpleControllerFactory(mockController.Object));
+
+ Mock<HttpContextBase> contextMock = new Mock<HttpContextBase>();
+ contextMock.ExpectMvcVersionResponseHeader().Verifiable();
+ RequestContext requestContext = new RequestContext(contextMock.Object, new RouteData());
+ requestContext.RouteData.Values["controller"] = "fooController";
+ MvcHandler handler = new MvcHandler(requestContext)
+ {
+ ControllerBuilder = builder
+ };
+
+ // Act
+ handler.ProcessRequest(requestContext.HttpContext);
+
+ // Assert
+ mockController.Verify();
+ contextMock.Verify();
+ mockController.As<IDisposable>().Verify(d => d.Dispose(), Times.AtMostOnce());
+ }
+
+ [Fact]
+ public void ProcessRequestDisposesControllerIfExecuteThrowsException()
+ {
+ // Arrange
+ Mock<ControllerBase> mockController = new Mock<ControllerBase>(MockBehavior.Strict);
+ mockController.As<IDisposable>().Setup(d => d.Dispose()); // so that Verify can be called on Dispose later
+ mockController.Protected().Setup("Execute", ItExpr.IsAny<RequestContext>()).Throws(new Exception("some exception"));
+
+ ControllerBuilder builder = new ControllerBuilder();
+ builder.SetControllerFactory(new SimpleControllerFactory(mockController.Object));
+
+ Mock<HttpContextBase> contextMock = new Mock<HttpContextBase>();
+ contextMock.ExpectMvcVersionResponseHeader().Verifiable();
+ RequestContext requestContext = new RequestContext(contextMock.Object, new RouteData());
+ requestContext.RouteData.Values["controller"] = "fooController";
+ MvcHandler handler = new MvcHandler(requestContext)
+ {
+ ControllerBuilder = builder
+ };
+
+ // Act
+ Assert.Throws<Exception>(
+ delegate { handler.ProcessRequest(requestContext.HttpContext); },
+ "some exception");
+
+ // Assert
+ mockController.Verify();
+ contextMock.Verify();
+ mockController.As<IDisposable>().Verify(d => d.Dispose(), Times.AtMostOnce());
+ }
+
+ [Fact]
+ public void ProcessRequestAsync_AsyncController_DisposesControllerOnException()
+ {
+ // Arrange
+ Mock<IAsyncController> mockController = new Mock<IAsyncController>();
+ mockController.Setup(o => o.BeginExecute(It.IsAny<RequestContext>(), It.IsAny<AsyncCallback>(), It.IsAny<object>())).Throws(new Exception("Some exception text."));
+ mockController.As<IDisposable>().Setup(o => o.Dispose()).Verifiable();
+
+ MvcHandler handler = GetMvcHandler(mockController.Object);
+
+ // Act & assert
+ Assert.Throws<Exception>(
+ delegate { handler.BeginProcessRequest(handler.RequestContext.HttpContext, null, null); },
+ @"Some exception text.");
+
+ mockController.Verify();
+ }
+
+ [Fact]
+ public void ProcessRequestAsync_AsyncController_NormalExecution()
+ {
+ // Arrange
+ MockAsyncResult innerAsyncResult = new MockAsyncResult();
+ bool disposeWasCalled = false;
+
+ Mock<IAsyncController> mockController = new Mock<IAsyncController>();
+ mockController.Setup(o => o.BeginExecute(It.IsAny<RequestContext>(), It.IsAny<AsyncCallback>(), It.IsAny<object>())).Returns(innerAsyncResult);
+ mockController.As<IDisposable>().Setup(o => o.Dispose()).Callback(delegate { disposeWasCalled = true; });
+
+ MvcHandler handler = GetMvcHandler(mockController.Object);
+
+ // Act & assert
+ IAsyncResult outerAsyncResult = handler.BeginProcessRequest(handler.RequestContext.HttpContext, null, null);
+ Assert.False(disposeWasCalled);
+
+ handler.EndProcessRequest(outerAsyncResult);
+ Assert.True(disposeWasCalled);
+ mockController.Verify(o => o.EndExecute(innerAsyncResult), Times.AtMostOnce());
+ }
+
+ [Fact]
+ public void ProcessRequestAsync_SyncController_NormalExecution()
+ {
+ // Arrange
+ bool executeWasCalled = false;
+ bool disposeWasCalled = false;
+
+ Mock<IController> mockController = new Mock<IController>();
+ mockController.Setup(o => o.Execute(It.IsAny<RequestContext>())).Callback(delegate { executeWasCalled = true; });
+ mockController.As<IDisposable>().Setup(o => o.Dispose()).Callback(delegate { disposeWasCalled = true; });
+
+ MvcHandler handler = GetMvcHandler(mockController.Object);
+
+ // Act & assert
+ IAsyncResult outerAsyncResult = handler.BeginProcessRequest(handler.RequestContext.HttpContext, null, null);
+ Assert.False(executeWasCalled);
+ Assert.False(disposeWasCalled);
+
+ handler.EndProcessRequest(outerAsyncResult);
+ Assert.True(executeWasCalled);
+ Assert.True(disposeWasCalled);
+ }
+
+ // Test that execute is called on a user controller that derives from Controller
+ [Fact]
+ public void ProcessRequestAsync_SyncController_NormalExecution2()
+ {
+ // Arrange
+ MyCustomerController controller = new MyCustomerController();
+
+ MvcHandler handler = GetMvcHandler(controller);
+ handler.RequestContext.RouteData.Values["action"] = "Widget";
+
+ // Act
+ IAsyncResult outerAsyncResult = handler.BeginProcessRequest(handler.RequestContext.HttpContext, null, null);
+ handler.EndProcessRequest(outerAsyncResult);
+
+ // Assert
+ Assert.Equal(1, controller.Called);
+ }
+
+ private class EmptyActionInvoker : IActionInvoker
+ {
+ public bool InvokeAction(ControllerContext controllerContext, string actionName)
+ {
+ return true; // action handled.
+ }
+ }
+
+ // Class that captures how an end-user would override and use a Controller
+ private class MyCustomerController : Controller
+ {
+ public int Called { get; set; }
+
+ protected override void ExecuteCore()
+ {
+ this.Called++;
+ }
+
+ protected override IActionInvoker CreateActionInvoker()
+ {
+ // The default action invoker relies on too much state (like having an HttpRequestContext), so stub it out.
+ return new EmptyActionInvoker();
+ }
+
+ // Workaround.
+ protected override bool DisableAsyncSupport
+ {
+ get
+ {
+ return true; // ensure ExecuteCore still gets called.
+ }
+ }
+ }
+
+ private static MvcHandler GetMvcHandler(IController controller)
+ {
+ Mock<HttpContextBase> mockHttpContext = new Mock<HttpContextBase>();
+ mockHttpContext.Setup(o => o.Response.AddHeader("X-AspNetMvc-Version", "2.0"));
+
+ RouteData routeData = new RouteData();
+ routeData.Values["controller"] = "SomeController";
+ RequestContext requestContext = new RequestContext(mockHttpContext.Object, routeData);
+
+ ControllerBuilder controllerBuilder = new ControllerBuilder();
+ controllerBuilder.SetControllerFactory(new SimpleControllerFactory(controller));
+
+ return new MvcHandler(requestContext)
+ {
+ ControllerBuilder = controllerBuilder
+ };
+ }
+
+ private class SimpleControllerFactory : IControllerFactory
+ {
+ private IController _instance;
+
+ public SimpleControllerFactory(IController instance)
+ {
+ _instance = instance;
+ }
+
+ public IController CreateController(RequestContext context, string controllerName)
+ {
+ return _instance;
+ }
+
+ public SessionStateBehavior GetControllerSessionBehavior(RequestContext requestContext, string controllerName)
+ {
+ return SessionStateBehavior.Default;
+ }
+
+ public void ReleaseController(IController controller)
+ {
+ IDisposable disposable = controller as IDisposable;
+ if (disposable != null)
+ {
+ disposable.Dispose();
+ }
+ }
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Test/MvcHtmlStringTest.cs b/test/System.Web.Mvc.Test/Test/MvcHtmlStringTest.cs
new file mode 100644
index 00000000..519d0e50
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Test/MvcHtmlStringTest.cs
@@ -0,0 +1,62 @@
+using Xunit;
+
+namespace System.Web.Mvc.Test
+{
+ public class MvcHtmlStringTest
+ {
+ // IsNullOrEmpty
+
+ [Fact]
+ public void IsNullOrEmptyTests()
+ {
+ // Act & Assert
+ Assert.True(MvcHtmlString.IsNullOrEmpty(null));
+ Assert.True(MvcHtmlString.IsNullOrEmpty(MvcHtmlString.Empty));
+ Assert.True(MvcHtmlString.IsNullOrEmpty(MvcHtmlString.Create("")));
+ Assert.False(MvcHtmlString.IsNullOrEmpty(MvcHtmlString.Create(" ")));
+ }
+
+ // ToHtmlString
+
+ [Fact]
+ public void ToHtmlStringReturnsOriginalString()
+ {
+ // Arrange
+ MvcHtmlString htmlString = MvcHtmlString.Create("some value");
+
+ // Act
+ string retVal = htmlString.ToHtmlString();
+
+ // Assert
+ Assert.Equal("some value", retVal);
+ }
+
+ // ToString
+
+ [Fact]
+ public void ToStringReturnsOriginalString()
+ {
+ // Arrange
+ MvcHtmlString htmlString = MvcHtmlString.Create("some value");
+
+ // Act
+ string retVal = htmlString.ToString();
+
+ // Assert
+ Assert.Equal("some value", retVal);
+ }
+
+ [Fact]
+ public void ToStringReturnsEmptyStringIfOriginalStringWasNull()
+ {
+ // Arrange
+ MvcHtmlString htmlString = MvcHtmlString.Create(null);
+
+ // Act
+ string retVal = htmlString.ToString();
+
+ // Assert
+ Assert.Equal("", retVal);
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Test/MvcHttpHandlerTest.cs b/test/System.Web.Mvc.Test/Test/MvcHttpHandlerTest.cs
new file mode 100644
index 00000000..14ccc5f0
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Test/MvcHttpHandlerTest.cs
@@ -0,0 +1,63 @@
+using Moq;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Mvc.Test
+{
+ public class MvcHttpHandlerTest
+ {
+ [Fact]
+ public void ConstructorDoesNothing()
+ {
+ new MvcHttpHandler();
+ }
+
+ [Fact]
+ public void VerifyAndProcessRequestWithNullHandlerThrows()
+ {
+ // Arrange
+ PublicMvcHttpHandler handler = new PublicMvcHttpHandler();
+
+ // Act
+ Assert.ThrowsArgumentNull(
+ delegate { handler.PublicVerifyAndProcessRequest(null, null); },
+ "httpHandler");
+ }
+
+ [Fact]
+ public void ProcessRequestCallsExecute()
+ {
+ // Arrange
+ PublicMvcHttpHandler handler = new PublicMvcHttpHandler();
+ Mock<IHttpHandler> mockTargetHandler = new Mock<IHttpHandler>();
+ mockTargetHandler.Setup(h => h.ProcessRequest(It.IsAny<HttpContext>())).Verifiable();
+
+ // Act
+ handler.PublicVerifyAndProcessRequest(mockTargetHandler.Object, null);
+
+ // Assert
+ mockTargetHandler.Verify();
+ }
+
+ private sealed class DummyHttpHandler : IHttpHandler
+ {
+ bool IHttpHandler.IsReusable
+ {
+ get { throw new NotImplementedException(); }
+ }
+
+ void IHttpHandler.ProcessRequest(HttpContext context)
+ {
+ throw new NotImplementedException();
+ }
+ }
+
+ private sealed class PublicMvcHttpHandler : MvcHttpHandler
+ {
+ public void PublicVerifyAndProcessRequest(IHttpHandler httpHandler, HttpContextBase httpContext)
+ {
+ base.VerifyAndProcessRequest(httpHandler, httpContext);
+ }
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Test/MvcRouteHandlerTest.cs b/test/System.Web.Mvc.Test/Test/MvcRouteHandlerTest.cs
new file mode 100644
index 00000000..c5d9d8a8
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Test/MvcRouteHandlerTest.cs
@@ -0,0 +1,71 @@
+using System.Web.Routing;
+using System.Web.SessionState;
+using Moq;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Mvc.Test
+{
+ public class MvcRouteHandlerTest
+ {
+ [Fact]
+ public void GetHttpHandlerReturnsMvcHandlerWithRouteData()
+ {
+ // Arrange
+ var routeData = new RouteData();
+ routeData.Values["controller"] = "controllerName";
+ var context = new RequestContext(new Mock<HttpContextBase>().Object, routeData);
+ var controllerFactory = new Mock<IControllerFactory>();
+ controllerFactory.Setup(f => f.GetControllerSessionBehavior(context, "controllerName"))
+ .Returns(SessionStateBehavior.Default)
+ .Verifiable();
+ IRouteHandler rh = new MvcRouteHandler(controllerFactory.Object);
+
+ // Act
+ IHttpHandler httpHandler = rh.GetHttpHandler(context);
+
+ // Assert
+ MvcHandler h = httpHandler as MvcHandler;
+ Assert.NotNull(h);
+ Assert.Equal(context, h.RequestContext);
+ }
+
+ [Fact]
+ public void GetHttpHandlerAsksControllerFactoryForSessionBehaviorOfController()
+ {
+ // Arrange
+ var httpContext = new Mock<HttpContextBase>();
+ var routeData = new RouteData();
+ routeData.Values["controller"] = "controllerName";
+ var requestContext = new RequestContext(httpContext.Object, routeData);
+ var controllerFactory = new Mock<IControllerFactory>();
+ controllerFactory.Setup(f => f.GetControllerSessionBehavior(requestContext, "controllerName"))
+ .Returns(SessionStateBehavior.ReadOnly)
+ .Verifiable();
+ IRouteHandler routeHandler = new MvcRouteHandler(controllerFactory.Object);
+
+ // Act
+ routeHandler.GetHttpHandler(requestContext);
+
+ // Assert
+ controllerFactory.Verify();
+ httpContext.Verify(c => c.SetSessionStateBehavior(SessionStateBehavior.ReadOnly));
+ }
+
+ [Fact]
+ public void GetHttpHandlerThrowsIfTheRouteValuesDoesNotIncludeAControllerName()
+ {
+ // Arrange
+ var httpContext = new Mock<HttpContextBase>();
+ var routeData = new RouteData();
+ var requestContext = new RequestContext(httpContext.Object, routeData);
+ IRouteHandler routeHandler = new MvcRouteHandler();
+
+ // Act & Assert
+ Assert.Throws<InvalidOperationException>(
+ () => routeHandler.GetHttpHandler(requestContext),
+ "The matched route does not include a 'controller' route value, which is required."
+ );
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Test/MvcTestHelper.cs b/test/System.Web.Mvc.Test/Test/MvcTestHelper.cs
new file mode 100644
index 00000000..ff714aaa
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Test/MvcTestHelper.cs
@@ -0,0 +1,102 @@
+using System.Reflection;
+using System.Reflection.Emit;
+
+namespace System.Web.Mvc.Test
+{
+ internal static class MvcTestHelper
+ {
+ private static bool _mvcAssembliesCreated;
+
+ public static void CreateMvcAssemblies()
+ {
+ // Only create MVC assemblies once per appdomain. This method is called from the static ctor of several
+ // test classes.
+ if (_mvcAssembliesCreated)
+ {
+ return;
+ }
+
+ CreateMvcTestAssembly1();
+ CreateMvcTestAssembly2();
+ CreateMvcTestAssembly3();
+ CreateMvcTestAssembly4();
+
+ _mvcAssembliesCreated = true;
+ }
+
+ private static void CreateMvcTestAssembly1()
+ {
+ AssemblyBuilder assemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(
+ new AssemblyName("MvcAssembly1"),
+ AssemblyBuilderAccess.Save);
+ ModuleBuilder moduleBuilder = assemblyBuilder.DefineDynamicModule(
+ "MvcAssembly1", "MvcAssembly1.dll");
+
+ CreateController(moduleBuilder, "NS1a.NS1b.C1Controller");
+ CreateController(moduleBuilder, "NS2a.NS2b.C2Controller");
+
+ assemblyBuilder.Save("MvcAssembly1.dll");
+ }
+
+ private static void CreateMvcTestAssembly2()
+ {
+ AssemblyBuilder assemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(
+ new AssemblyName("MvcAssembly2"),
+ AssemblyBuilderAccess.Save);
+ ModuleBuilder moduleBuilder = assemblyBuilder.DefineDynamicModule(
+ "MvcAssembly2", "MvcAssembly2.dll");
+
+ CreateController(moduleBuilder, "NS3a.NS3b.C3Controller");
+ CreateController(moduleBuilder, "NS4a.NS4b.C4Controller");
+
+ assemblyBuilder.Save("MvcAssembly2.dll");
+ }
+
+ private static void CreateMvcTestAssembly3()
+ {
+ AssemblyBuilder assemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(
+ new AssemblyName("MvcAssembly3"),
+ AssemblyBuilderAccess.Save);
+ ModuleBuilder moduleBuilder = assemblyBuilder.DefineDynamicModule(
+ "MvcAssembly3", "MvcAssembly3.dll");
+
+ // Type names (but not namespaces) are the same as those in TestAssembly1
+ CreateController(moduleBuilder, "NS3a.NS3b.C1Controller");
+ CreateController(moduleBuilder, "NS4a.NS4b.C2Controller");
+
+ assemblyBuilder.Save("MvcAssembly3.dll");
+ }
+
+ private static void CreateMvcTestAssembly4()
+ {
+ AssemblyBuilder assemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(
+ new AssemblyName("MvcAssembly4"),
+ AssemblyBuilderAccess.Save);
+ ModuleBuilder moduleBuilder = assemblyBuilder.DefineDynamicModule(
+ "MvcAssembly4", "MvcAssembly4.dll");
+
+ // Namespaces and type names are the same as those in TestAssembly1
+ CreateController(moduleBuilder, "NS1a.NS1b.C1Controller");
+ CreateController(moduleBuilder, "NS2a.NS2b.C2Controller");
+
+ assemblyBuilder.Save("MvcAssembly4.dll");
+ }
+
+ private static void CreateController(ModuleBuilder moduleBuilder, string typeName)
+ {
+ //namespace {namespace} {
+ // public class {typename} : ControllerBase {
+ // protected virtual void ExecuteCore() {
+ // return;
+ // }
+ // }
+ //}
+
+ TypeBuilder controllerTypeBuilder = moduleBuilder.DefineType(typeName, TypeAttributes.Class | TypeAttributes.Public, typeof(ControllerBase));
+ MethodBuilder executeMethodBuilder = controllerTypeBuilder.DefineMethod("ExecuteCore", MethodAttributes.Family | MethodAttributes.Virtual, typeof(void), Type.EmptyTypes);
+ executeMethodBuilder.GetILGenerator().Emit(OpCodes.Ret);
+ controllerTypeBuilder.DefineMethodOverride(executeMethodBuilder, typeof(ControllerBase).GetMethod("ExecuteCore", BindingFlags.Instance | BindingFlags.NonPublic));
+ controllerTypeBuilder.CreateType();
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Test/MvcWebRazorHostFactoryTest.cs b/test/System.Web.Mvc.Test/Test/MvcWebRazorHostFactoryTest.cs
new file mode 100644
index 00000000..0d23f365
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Test/MvcWebRazorHostFactoryTest.cs
@@ -0,0 +1,56 @@
+using System.Web.Mvc.Razor;
+using System.Web.WebPages.Razor;
+using Xunit;
+
+namespace System.Web.Mvc.Test
+{
+ public class MvcWebRazorHostFactoryTest
+ {
+ [Fact]
+ public void Constructor()
+ {
+ new MvcWebRazorHostFactory();
+
+ // All is cool
+ }
+
+ [Fact]
+ public void CreateHost_ReplacesRegularHostWithMvcSpecificOne()
+ {
+ // Arrange
+ MvcWebRazorHostFactory factory = new MvcWebRazorHostFactory();
+
+ // Act
+ WebPageRazorHost result = factory.CreateHost("foo.cshtml", null);
+
+ // Assert
+ Assert.IsType<MvcWebPageRazorHost>(result);
+ }
+
+ [Fact]
+ public void CreateHost_DoesNotChangeAppStartFileHost()
+ {
+ // Arrange
+ MvcWebRazorHostFactory factory = new MvcWebRazorHostFactory();
+
+ // Act
+ WebPageRazorHost result = factory.CreateHost("_appstart.cshtml", null);
+
+ // Assert
+ Assert.IsNotType<MvcWebPageRazorHost>(result);
+ }
+
+ [Fact]
+ public void CreateHost_DoesNotChangePageStartFileHost()
+ {
+ // Arrange
+ MvcWebRazorHostFactory factory = new MvcWebRazorHostFactory();
+
+ // Act
+ WebPageRazorHost result = factory.CreateHost("_pagestart.cshtml", null);
+
+ // Assert
+ Assert.IsNotType<MvcWebPageRazorHost>(result);
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Test/NameValueCollectionExtensionsTest.cs b/test/System.Web.Mvc.Test/Test/NameValueCollectionExtensionsTest.cs
new file mode 100644
index 00000000..cff63b64
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Test/NameValueCollectionExtensionsTest.cs
@@ -0,0 +1,76 @@
+using System.Collections.Generic;
+using System.Collections.Specialized;
+using System.Web.Routing;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Mvc.Test
+{
+ public class NameValueCollectionExtensionsTest
+ {
+ [Fact]
+ public void CopyTo()
+ {
+ // Arrange
+ NameValueCollection collection = GetCollection();
+ IDictionary<string, object> dictionary = GetDictionary();
+
+ // Act
+ collection.CopyTo(dictionary);
+
+ // Assert
+ Assert.Equal(3, dictionary.Count);
+ Assert.Equal("FooDictionary", dictionary["foo"]);
+ Assert.Equal("BarDictionary", dictionary["bar"]);
+ Assert.Equal("BazCollection", dictionary["baz"]);
+ }
+
+ public void CopyToReplaceExisting()
+ {
+ // Arrange
+ NameValueCollection collection = GetCollection();
+ IDictionary<string, object> dictionary = GetDictionary();
+
+ // Act
+ collection.CopyTo(dictionary, true /* replaceExisting */);
+
+ // Assert
+ Assert.Equal(3, dictionary.Count);
+ Assert.Equal("FooCollection", dictionary["foo"]);
+ Assert.Equal("BarDictionary", dictionary["bar"]);
+ Assert.Equal("BazCollection", dictionary["baz"]);
+ }
+
+ [Fact]
+ public void CopyToWithNullCollectionThrows()
+ {
+ Assert.ThrowsArgumentNull(
+ delegate { NameValueCollectionExtensions.CopyTo(null /* collection */, null /* destination */); }, "collection");
+ }
+
+ [Fact]
+ public void CopyToWithNullDestinationThrows()
+ {
+ // Arrange
+ NameValueCollection collection = GetCollection();
+
+ // Act & Assert
+ Assert.ThrowsArgumentNull(
+ delegate { collection.CopyTo(null /* destination */); }, "destination");
+ }
+
+ private static NameValueCollection GetCollection()
+ {
+ return new NameValueCollection
+ {
+ { "Foo", "FooCollection" },
+ { "Baz", "BazCollection" }
+ };
+ }
+
+ private static IDictionary<string, object> GetDictionary()
+ {
+ return new RouteValueDictionary(new { Foo = "FooDictionary", Bar = "BarDictionary" });
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Test/NameValueCollectionValueProviderTest.cs b/test/System.Web.Mvc.Test/Test/NameValueCollectionValueProviderTest.cs
new file mode 100644
index 00000000..49f5aaf9
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Test/NameValueCollectionValueProviderTest.cs
@@ -0,0 +1,143 @@
+using System.Collections.Specialized;
+using System.Globalization;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Mvc.Test
+{
+ public class NameValueCollectionValueProviderTest
+ {
+ private static readonly NameValueCollection _backingStore = new NameValueCollection()
+ {
+ { "foo", "fooValue1" },
+ { "foo", "fooValue2" },
+ { "bar.baz", "someOtherValue" }
+ };
+
+ [Fact]
+ public void Constructor_ThrowsIfCollectionIsNull()
+ {
+ // Act & assert
+ Assert.ThrowsArgumentNull(
+ delegate { new NameValueCollectionValueProvider(null, CultureInfo.InvariantCulture); }, "collection");
+ }
+
+ [Fact]
+ public void ContainsPrefix()
+ {
+ // Arrange
+ NameValueCollectionValueProvider valueProvider = new NameValueCollectionValueProvider(_backingStore, null);
+
+ // Act
+ bool result = valueProvider.ContainsPrefix("bar");
+
+ // Assert
+ Assert.True(result);
+ }
+
+ [Fact]
+ public void ContainsPrefix_DoesNotContainEmptyPrefixIfBackingStoreIsEmpty()
+ {
+ // Arrange
+ NameValueCollectionValueProvider valueProvider = new NameValueCollectionValueProvider(new NameValueCollection(), null);
+
+ // Act
+ bool result = valueProvider.ContainsPrefix("");
+
+ // Assert
+ Assert.False(result);
+ }
+
+ [Fact]
+ public void ContainsPrefix_ThrowsIfPrefixIsNull()
+ {
+ // Arrange
+ NameValueCollectionValueProvider valueProvider = new NameValueCollectionValueProvider(_backingStore, null);
+
+ // Act & assert
+ Assert.ThrowsArgumentNull(
+ delegate { valueProvider.ContainsPrefix(null); }, "prefix");
+ }
+
+ [Fact]
+ public void GetValue()
+ {
+ // Arrange
+ CultureInfo culture = CultureInfo.GetCultureInfo("fr-FR");
+ NameValueCollectionValueProvider valueProvider = new NameValueCollectionValueProvider(_backingStore, culture);
+
+ // Act
+ ValueProviderResult vpResult = valueProvider.GetValue("foo");
+
+ // Assert
+ Assert.NotNull(vpResult);
+ Assert.Equal(_backingStore.GetValues("foo"), (string[])vpResult.RawValue);
+ Assert.Equal("fooValue1,fooValue2", vpResult.AttemptedValue);
+ Assert.Equal(culture, vpResult.Culture);
+ }
+
+ [Fact]
+ public void GetValue_NonValidating()
+ {
+ // Arrange
+ NameValueCollection unvalidatedCollection = new NameValueCollection()
+ {
+ { "foo", "fooValue3" },
+ { "foo", "fooValue4" }
+ };
+
+ CultureInfo culture = CultureInfo.GetCultureInfo("fr-FR");
+ NameValueCollectionValueProvider valueProvider = new NameValueCollectionValueProvider(_backingStore, unvalidatedCollection, culture);
+
+ // Act
+ ValueProviderResult vpResult = valueProvider.GetValue("foo", skipValidation: true);
+
+ // Assert
+ Assert.NotNull(vpResult);
+ Assert.Equal(new[] { "fooValue3", "fooValue4" }, (string[])vpResult.RawValue);
+ Assert.Equal("fooValue3,fooValue4", vpResult.AttemptedValue);
+ Assert.Equal(culture, vpResult.Culture);
+ }
+
+ [Fact]
+ public void GetValue_NonValidating_NoUnvalidatedCollectionSpecified_UsesDefaultCollectionValue()
+ {
+ // Arrange
+ CultureInfo culture = CultureInfo.GetCultureInfo("fr-FR");
+ NameValueCollectionValueProvider valueProvider = new NameValueCollectionValueProvider(_backingStore, null, culture);
+
+ // Act
+ ValueProviderResult vpResult = valueProvider.GetValue("foo", skipValidation: true);
+
+ // Assert
+ Assert.NotNull(vpResult);
+ Assert.Equal(_backingStore.GetValues("foo"), (string[])vpResult.RawValue);
+ Assert.Equal("fooValue1,fooValue2", vpResult.AttemptedValue);
+ Assert.Equal(culture, vpResult.Culture);
+ }
+
+ [Fact]
+ public void GetValue_ReturnsNullIfKeyNotFound()
+ {
+ // Arrange
+ NameValueCollectionValueProvider valueProvider = new NameValueCollectionValueProvider(_backingStore, null);
+
+ // Act
+ ValueProviderResult vpResult = valueProvider.GetValue("bar");
+
+ // Assert
+ Assert.Null(vpResult);
+ }
+
+ [Fact]
+ public void GetValue_ThrowsIfKeyIsNull()
+ {
+ // Arrange
+ NameValueCollectionValueProvider valueProvider = new NameValueCollectionValueProvider(_backingStore, null);
+
+ // Act & assert
+ Assert.ThrowsArgumentNull(
+ delegate { valueProvider.GetValue(null); }, "key");
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Test/NoAsyncTimeoutAttributeTest.cs b/test/System.Web.Mvc.Test/Test/NoAsyncTimeoutAttributeTest.cs
new file mode 100644
index 00000000..aaaace5d
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Test/NoAsyncTimeoutAttributeTest.cs
@@ -0,0 +1,18 @@
+using System.Threading;
+using Xunit;
+
+namespace System.Web.Mvc.Test
+{
+ public class NoAsyncTimeoutAttributeTest
+ {
+ [Fact]
+ public void DurationPropertyIsZero()
+ {
+ // Act
+ AsyncTimeoutAttribute attr = new NoAsyncTimeoutAttribute();
+
+ // Assert
+ Assert.Equal(Timeout.Infinite, attr.Duration);
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Test/NonActionAttributeTest.cs b/test/System.Web.Mvc.Test/Test/NonActionAttributeTest.cs
new file mode 100644
index 00000000..60f55cf2
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Test/NonActionAttributeTest.cs
@@ -0,0 +1,17 @@
+using Xunit;
+
+namespace System.Web.Mvc.Test
+{
+ public class NonActionAttributeTest
+ {
+ [Fact]
+ public void InValidActionForRequestReturnsFalse()
+ {
+ // Arrange
+ NonActionAttribute attr = new NonActionAttribute();
+
+ // Act & Assert
+ Assert.False(attr.IsValidForRequest(null, null));
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Test/OutputCacheAttributeTest.cs b/test/System.Web.Mvc.Test/Test/OutputCacheAttributeTest.cs
new file mode 100644
index 00000000..32c8479c
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Test/OutputCacheAttributeTest.cs
@@ -0,0 +1,279 @@
+using System.Collections.Generic;
+using System.Web.TestUtil;
+using System.Web.UI;
+using Moq;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Mvc.Test
+{
+ public class OutputCacheAttributeTest
+ {
+ [Fact]
+ public void CacheProfileProperty()
+ {
+ // Arrange
+ OutputCacheAttribute attr = new OutputCacheAttribute();
+
+ // Act & assert
+ MemberHelper.TestStringProperty(attr, "CacheProfile", String.Empty);
+ }
+
+ [Fact]
+ public void CacheSettingsProperty()
+ {
+ // Arrange
+ OutputCacheAttribute attr = new OutputCacheAttribute()
+ {
+ CacheProfile = "SomeProfile",
+ Duration = 50,
+ Location = OutputCacheLocation.Downstream,
+ NoStore = true,
+ SqlDependency = "SomeSqlDependency",
+ VaryByContentEncoding = "SomeContentEncoding",
+ VaryByCustom = "SomeCustom",
+ VaryByHeader = "SomeHeader",
+ VaryByParam = "SomeParam",
+ };
+
+ // Act
+ OutputCacheParameters cacheSettings = attr.CacheSettings;
+
+ // Assert
+ Assert.Equal("SomeProfile", cacheSettings.CacheProfile);
+ Assert.Equal(50, cacheSettings.Duration);
+ Assert.Equal(OutputCacheLocation.Downstream, cacheSettings.Location);
+ Assert.Equal(true, cacheSettings.NoStore);
+ Assert.Equal("SomeSqlDependency", cacheSettings.SqlDependency);
+ Assert.Equal("SomeContentEncoding", cacheSettings.VaryByContentEncoding);
+ Assert.Equal("SomeCustom", cacheSettings.VaryByCustom);
+ Assert.Equal("SomeHeader", cacheSettings.VaryByHeader);
+ Assert.Equal("SomeParam", cacheSettings.VaryByParam);
+ }
+
+ [Fact]
+ public void DurationProperty()
+ {
+ // Arrange
+ OutputCacheAttribute attr = new OutputCacheAttribute();
+
+ // Act & assert
+ MemberHelper.TestInt32Property(attr, "Duration", 10, 20);
+ }
+
+ [Fact]
+ public void LocationProperty()
+ {
+ // Arrange
+ OutputCacheAttribute attr = new OutputCacheAttribute();
+
+ // Act & assert
+ MemberHelper.TestPropertyValue(attr, "Location", OutputCacheLocation.ServerAndClient);
+ }
+
+ [Fact]
+ public void NoStoreProperty()
+ {
+ // Arrange
+ OutputCacheAttribute attr = new OutputCacheAttribute();
+
+ // Act & assert
+ MemberHelper.TestBooleanProperty(attr, "NoStore", false /* initialValue */, false /* testDefaultValue */);
+ }
+
+ [Fact]
+ public void OnResultExecutingThrowsIfFilterContextIsNull()
+ {
+ // Arrange
+ OutputCacheAttribute attr = new OutputCacheAttribute();
+
+ // Act & assert
+ Assert.ThrowsArgumentNull(
+ delegate { attr.OnResultExecuting(null); }, "filterContext");
+ }
+
+ [Fact]
+ public void SqlDependencyProperty()
+ {
+ // Arrange
+ OutputCacheAttribute attr = new OutputCacheAttribute();
+
+ // Act & assert
+ MemberHelper.TestStringProperty(attr, "SqlDependency", String.Empty);
+ }
+
+ [Fact]
+ public void VaryByContentEncodingProperty()
+ {
+ // Arrange
+ OutputCacheAttribute attr = new OutputCacheAttribute();
+
+ // Act & assert
+ MemberHelper.TestStringProperty(attr, "VaryByContentEncoding", String.Empty);
+ }
+
+ [Fact]
+ public void VaryByCustomProperty()
+ {
+ // Arrange
+ OutputCacheAttribute attr = new OutputCacheAttribute();
+
+ // Act & assert
+ MemberHelper.TestStringProperty(attr, "VaryByCustom", String.Empty);
+ }
+
+ [Fact]
+ public void VaryByHeaderProperty()
+ {
+ // Arrange
+ OutputCacheAttribute attr = new OutputCacheAttribute();
+
+ // Act & assert
+ MemberHelper.TestStringProperty(attr, "VaryByHeader", String.Empty);
+ }
+
+ [Fact]
+ public void VaryByParamProperty()
+ {
+ // Arrange
+ OutputCacheAttribute attr = new OutputCacheAttribute();
+
+ // Act & assert
+ MemberHelper.TestStringProperty(attr, "VaryByParam", "*");
+ }
+
+ [Fact]
+ public void OutputCacheDoesNotExecuteIfInChildAction()
+ {
+ // Arrange
+ OutputCacheAttribute attr = new OutputCacheAttribute();
+ Mock<ResultExecutingContext> context = new Mock<ResultExecutingContext>();
+ context.Setup(c => c.IsChildAction).Returns(true);
+
+ // Act
+ attr.OnResultExecuting(context.Object);
+
+ // Assert
+ context.Verify();
+ context.Verify(c => c.Result, Times.Never());
+ }
+
+ // GetChildActionUniqueId
+
+ [Fact]
+ public void GetChildActionUniqueId_ReturnsRepeatableValueForIdenticalContext()
+ {
+ // Arrange
+ var attr = new OutputCacheAttribute();
+ var context = new MockActionExecutingContext();
+
+ // Act
+ string result1 = attr.GetChildActionUniqueId(context.Object);
+ string result2 = attr.GetChildActionUniqueId(context.Object);
+
+ // Assert
+ Assert.Equal(result1, result2);
+ }
+
+ [Fact]
+ public void GetChildActionUniqueId_VariesByActionDescriptorsUniqueId()
+ {
+ // Arrange
+ var attr = new OutputCacheAttribute();
+ var context1 = new MockActionExecutingContext();
+ context1.Setup(c => c.ActionDescriptor.UniqueId).Returns("1");
+ var context2 = new MockActionExecutingContext();
+ context2.Setup(c => c.ActionDescriptor.UniqueId).Returns("2");
+
+ // Act
+ string result1 = attr.GetChildActionUniqueId(context1.Object);
+ string result2 = attr.GetChildActionUniqueId(context2.Object);
+
+ // Assert
+ Assert.NotEqual(result1, result2);
+ }
+
+ [Fact]
+ public void GetChildActionUniqueId_VariesByCustom()
+ {
+ // Arrange
+ var attr = new OutputCacheAttribute { VaryByCustom = "foo" };
+ var context1 = new MockActionExecutingContext();
+ context1.Setup(c => c.HttpContext.ApplicationInstance.GetVaryByCustomString(It.IsAny<HttpContext>(), "foo")).Returns("1");
+ var context2 = new MockActionExecutingContext();
+ context2.Setup(c => c.HttpContext.ApplicationInstance.GetVaryByCustomString(It.IsAny<HttpContext>(), "foo")).Returns("2");
+
+ // Act
+ string result1 = attr.GetChildActionUniqueId(context1.Object);
+ string result2 = attr.GetChildActionUniqueId(context2.Object);
+
+ // Assert
+ Assert.NotEqual(result1, result2);
+ }
+
+ [Fact]
+ public void GetChildActionUniqueId_VariesByActionParameters_AllParametersByDefault()
+ {
+ // Arrange
+ var attr = new OutputCacheAttribute();
+ var context1 = new MockActionExecutingContext();
+ context1.ActionParameters["foo"] = "1";
+ var context2 = new MockActionExecutingContext();
+ context2.ActionParameters["foo"] = "2";
+
+ // Act
+ string result1 = attr.GetChildActionUniqueId(context1.Object);
+ string result2 = attr.GetChildActionUniqueId(context2.Object);
+
+ // Assert
+ Assert.NotEqual(result1, result2);
+ }
+
+ [Fact]
+ public void GetChildActionUniqueId_DoesNotVaryByActionParametersWhenVaryByParamIsNone()
+ {
+ // Arrange
+ var attr = new OutputCacheAttribute { VaryByParam = "none" };
+ var context1 = new MockActionExecutingContext();
+ context1.ActionParameters["foo"] = "1";
+ var context2 = new MockActionExecutingContext();
+ context2.ActionParameters["foo"] = "2";
+
+ // Act
+ string result1 = attr.GetChildActionUniqueId(context1.Object);
+ string result2 = attr.GetChildActionUniqueId(context2.Object);
+
+ // Assert
+ Assert.Equal(result1, result2);
+ }
+
+ [Fact]
+ public void GetChildActionUniqueId_VariesByActionParameters_OnlyVariesByTheGivenParameters()
+ {
+ // Arrange
+ var attr = new OutputCacheAttribute { VaryByParam = "bar" };
+ var context1 = new MockActionExecutingContext();
+ context1.ActionParameters["foo"] = "1";
+ var context2 = new MockActionExecutingContext();
+ context2.ActionParameters["foo"] = "2";
+
+ // Act
+ string result1 = attr.GetChildActionUniqueId(context1.Object);
+ string result2 = attr.GetChildActionUniqueId(context2.Object);
+
+ // Assert
+ Assert.Equal(result1, result2);
+ }
+
+ class MockActionExecutingContext : Mock<ActionExecutingContext>
+ {
+ public Dictionary<string, object> ActionParameters = new Dictionary<string, object>();
+
+ public MockActionExecutingContext()
+ {
+ Setup(c => c.ActionDescriptor.UniqueId).Returns("abc123");
+ Setup(c => c.ActionParameters).Returns(() => ActionParameters);
+ }
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Test/ParameterBindingInfoTest.cs b/test/System.Web.Mvc.Test/Test/ParameterBindingInfoTest.cs
new file mode 100644
index 00000000..0f6b6b61
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Test/ParameterBindingInfoTest.cs
@@ -0,0 +1,60 @@
+using System.Collections.Generic;
+using Xunit;
+
+namespace System.Web.Mvc.Test
+{
+ public class ParameterBindingInfoTest
+ {
+ [Fact]
+ public void BinderProperty()
+ {
+ // Arrange
+ ParameterBindingInfo bindingInfo = new ParameterBindingInfoHelper();
+
+ // Act & assert
+ Assert.Null(bindingInfo.Binder);
+ }
+
+ [Fact]
+ public void ExcludeProperty()
+ {
+ // Arrange
+ ParameterBindingInfo bindingInfo = new ParameterBindingInfoHelper();
+
+ // Act
+ ICollection<string> exclude = bindingInfo.Exclude;
+
+ // Assert
+ Assert.NotNull(exclude);
+ Assert.Empty(exclude);
+ }
+
+ [Fact]
+ public void IncludeProperty()
+ {
+ // Arrange
+ ParameterBindingInfo bindingInfo = new ParameterBindingInfoHelper();
+
+ // Act
+ ICollection<string> include = bindingInfo.Include;
+
+ // Assert
+ Assert.NotNull(include);
+ Assert.Empty(include);
+ }
+
+ [Fact]
+ public void PrefixProperty()
+ {
+ // Arrange
+ ParameterBindingInfo bindingInfo = new ParameterBindingInfoHelper();
+
+ // Act & assert
+ Assert.Null(bindingInfo.Prefix);
+ }
+
+ private class ParameterBindingInfoHelper : ParameterBindingInfo
+ {
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Test/ParameterDescriptorTest.cs b/test/System.Web.Mvc.Test/Test/ParameterDescriptorTest.cs
new file mode 100644
index 00000000..a537b9d7
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Test/ParameterDescriptorTest.cs
@@ -0,0 +1,114 @@
+using System.Reflection;
+using Moq;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Mvc.Test
+{
+ public class ParameterDescriptorTest
+ {
+ [Fact]
+ public void BindingInfoProperty()
+ {
+ // Arrange
+ ParameterDescriptor pd = GetParameterDescriptor(typeof(object), "someName");
+
+ // Act
+ ParameterBindingInfo bindingInfo = pd.BindingInfo;
+
+ // Assert
+ Assert.IsType(typeof(ParameterDescriptor).GetNestedType("EmptyParameterBindingInfo", BindingFlags.NonPublic),
+ bindingInfo);
+ }
+
+ [Fact]
+ public void DefaultValuePropertyDefaultsToNull()
+ {
+ // Arrange
+ ParameterDescriptor pd = GetParameterDescriptor();
+
+ // Act
+ object defaultValue = pd.DefaultValue;
+
+ // Assert
+ Assert.Null(defaultValue);
+ }
+
+ [Fact]
+ public void GetCustomAttributesReturnsEmptyArrayOfAttributeType()
+ {
+ // Arrange
+ ParameterDescriptor pd = GetParameterDescriptor();
+
+ // Act
+ ObsoleteAttribute[] attrs = (ObsoleteAttribute[])pd.GetCustomAttributes(typeof(ObsoleteAttribute), true);
+
+ // Assert
+ Assert.Empty(attrs);
+ }
+
+ [Fact]
+ public void GetCustomAttributesThrowsIfAttributeTypeIsNull()
+ {
+ // Arrange
+ ParameterDescriptor pd = GetParameterDescriptor();
+
+ // Act & assert
+ Assert.ThrowsArgumentNull(
+ delegate { pd.GetCustomAttributes(null /* attributeType */, true); }, "attributeType");
+ }
+
+ [Fact]
+ public void GetCustomAttributesWithoutAttributeTypeCallsGetCustomAttributesWithAttributeType()
+ {
+ // Arrange
+ object[] expected = new object[0];
+ Mock<ParameterDescriptor> mockDescriptor = new Mock<ParameterDescriptor>() { CallBase = true };
+ mockDescriptor.Setup(d => d.GetCustomAttributes(typeof(object), true)).Returns(expected);
+ ParameterDescriptor pd = mockDescriptor.Object;
+
+ // Act
+ object[] returned = pd.GetCustomAttributes(true /* inherit */);
+
+ // Assert
+ Assert.Same(expected, returned);
+ }
+
+ [Fact]
+ public void IsDefinedReturnsFalse()
+ {
+ // Arrange
+ ParameterDescriptor pd = GetParameterDescriptor();
+
+ // Act
+ bool isDefined = pd.IsDefined(typeof(object), true);
+
+ // Assert
+ Assert.False(isDefined);
+ }
+
+ [Fact]
+ public void IsDefinedThrowsIfAttributeTypeIsNull()
+ {
+ // Arrange
+ ParameterDescriptor pd = GetParameterDescriptor();
+
+ // Act & assert
+ Assert.ThrowsArgumentNull(
+ delegate { pd.IsDefined(null /* attributeType */, true); }, "attributeType");
+ }
+
+ private static ParameterDescriptor GetParameterDescriptor()
+ {
+ return GetParameterDescriptor(typeof(object), "someName");
+ }
+
+ private static ParameterDescriptor GetParameterDescriptor(Type type, string name)
+ {
+ Mock<ParameterDescriptor> mockDescriptor = new Mock<ParameterDescriptor>() { CallBase = true };
+ mockDescriptor.Setup(d => d.ParameterType).Returns(type);
+ mockDescriptor.Setup(d => d.ParameterName).Returns(name);
+ return mockDescriptor.Object;
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Test/ParameterInfoUtilTest.cs b/test/System.Web.Mvc.Test/Test/ParameterInfoUtilTest.cs
new file mode 100644
index 00000000..2a406a6f
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Test/ParameterInfoUtilTest.cs
@@ -0,0 +1,182 @@
+using System.ComponentModel;
+using System.Linq;
+using System.Reflection;
+using Moq;
+using Xunit;
+
+namespace System.Web.Mvc.Test
+{
+ public class ParameterInfoUtilTest
+ {
+ [Fact]
+ public void TryGetDefaultValue_FirstChecksDefaultValue()
+ {
+ // Arrange
+ Mock<ParameterInfo> mockPInfo = new Mock<ParameterInfo>() { DefaultValue = DefaultValue.Mock };
+ mockPInfo.Setup(p => p.DefaultValue).Returns(42);
+ mockPInfo.Setup(p => p.Name).Returns("someParameter");
+
+ // Act
+ object defaultValue;
+ bool retVal = ParameterInfoUtil.TryGetDefaultValue(mockPInfo.Object, out defaultValue);
+
+ // Assert
+ Assert.True(retVal);
+ Assert.Equal(42, defaultValue);
+ }
+
+ [Fact]
+ public void TryGetDefaultValue_SecondChecksDefaultValueAttribute()
+ {
+ // Arrange
+ ParameterInfo pInfo = typeof(MyController).GetMethod("DefaultValues").GetParameters()[1]; // hasDefaultValue
+
+ // Act
+ object defaultValue;
+ bool retVal = ParameterInfoUtil.TryGetDefaultValue(pInfo, out defaultValue);
+
+ // Assert
+ Assert.True(retVal);
+ Assert.Equal("someValue", defaultValue);
+ }
+
+ [Fact]
+ public void TryGetDefaultValue_RespectsNullDefaultValue()
+ {
+ // Arrange
+ Mock<ParameterInfo> mockPInfo = new Mock<ParameterInfo>() { DefaultValue = DefaultValue.Mock };
+ mockPInfo.Setup(p => p.DefaultValue).Returns(null);
+ mockPInfo.Setup(p => p.Name).Returns("someParameter");
+ mockPInfo
+ .Setup(p => p.GetCustomAttributes(typeof(DefaultValueAttribute[]), false))
+ .Returns(new DefaultValueAttribute[] { new DefaultValueAttribute(42) });
+
+ // Act
+ object defaultValue;
+ bool retVal = ParameterInfoUtil.TryGetDefaultValue(mockPInfo.Object, out defaultValue);
+
+ // Assert
+ Assert.True(retVal);
+ Assert.Null(defaultValue);
+ }
+
+ [Fact]
+ public void TryGetDefaultValue_ReturnsFalseIfNoDefaultValue()
+ {
+ // Arrange
+ ParameterInfo pInfo = typeof(MyController).GetMethod("DefaultValues").GetParameters()[0]; // noDefaultValue
+
+ // Act
+ object defaultValue;
+ bool retVal = ParameterInfoUtil.TryGetDefaultValue(pInfo, out defaultValue);
+
+ // Assert
+ Assert.False(retVal);
+ Assert.Equal(default(object), defaultValue);
+ }
+
+ [Fact]
+ public void TryGetDefaultValue_DefaultValueAttributeParameters()
+ {
+ DefaultValueAttributeHelper<bool>(true, "boolParam");
+ DefaultValueAttributeHelper<byte>(42, "byteParam");
+ DefaultValueAttributeHelper<char>('a', "charParam");
+ DefaultValueAttributeHelper<double>(1.0, "doubleParam");
+ DefaultValueAttributeHelper<MyEnum>(MyEnum.All, "enumParam");
+ DefaultValueAttributeHelper<float>((float)1.0, "floatParam");
+ DefaultValueAttributeHelper<int>(42, "intParam");
+ DefaultValueAttributeHelper<long>(42, "longParam");
+ DefaultValueAttributeHelper<object>(null, "objectParam");
+ DefaultValueAttributeHelper<short>(42, "shortParam");
+ DefaultValueAttributeHelper<string>("abc", "stringParam");
+ DefaultValueAttributeHelper<DateTime>(new DateTime(2010, 09, 27), "customParam");
+ }
+
+ [Fact]
+ public void TryGetDefaultValue_OptionalParameters()
+ {
+ OptionalParamHelper<bool>(true, "boolParam");
+ OptionalParamHelper<byte>(42, "byteParam");
+ OptionalParamHelper<char>('a', "charParam");
+ OptionalParamHelper<double>(1.0, "doubleParam");
+ OptionalParamHelper<MyEnum>(MyEnum.All, "enumParam");
+ OptionalParamHelper<float>((float)1.0, "floatParam");
+ OptionalParamHelper<int>(42, "intParam");
+ OptionalParamHelper<long>(42, "longParam");
+ OptionalParamHelper<object>(null, "objectParam");
+ OptionalParamHelper<short>(42, "shortParam");
+ OptionalParamHelper<string>("abc", "stringParam");
+ }
+
+ private static void DefaultValueAttributeHelper<TParam>(TParam expectedValue, string paramName)
+ {
+ ParameterTestHelper<TParam>(expectedValue, paramName, "AttributeDefaultValues");
+ }
+
+ private static void OptionalParamHelper<TParam>(TParam expectedValue, string paramName)
+ {
+ ParameterTestHelper<TParam>(expectedValue, paramName, "OptionalParamDefaultValues");
+ }
+
+ private static void ParameterTestHelper<TParam>(TParam expectedValue, string paramName, string actionMethodName)
+ {
+ ParameterInfo pInfo = typeof(MyController).GetMethod(actionMethodName).GetParameters().Single(p => p.Name == paramName);
+ object returnValueObject;
+ bool result = ParameterInfoUtil.TryGetDefaultValue(pInfo, out returnValueObject);
+
+ Assert.True(result);
+ if (expectedValue != null)
+ {
+ Assert.IsType<TParam>(returnValueObject);
+ }
+ TParam returnValue = (TParam)returnValueObject;
+ Assert.Equal<TParam>(expectedValue, returnValue);
+ }
+
+ private class MyController : Controller
+ {
+ public void DefaultValues(string noDefaultValue, [DefaultValue("someValue")] string hasDefaultValue)
+ {
+ }
+
+ public void AttributeDefaultValues(
+ [DefaultValue(true)] bool boolParam,
+ [DefaultValue((byte)42)] byte byteParam,
+ [DefaultValue('a')] char charParam,
+ [DefaultValue(typeof(DateTime), "2010-09-27")] object customParam,
+ [DefaultValue((double)1.0)] double doubleParam,
+ [DefaultValue(MyEnum.All)] MyEnum enumParam,
+ [DefaultValue((float)1.0)] float floatParam,
+ [DefaultValue(42)] int intParam,
+ [DefaultValue((long)42)] long longParam,
+ [DefaultValue(null)] object objectParam,
+ [DefaultValue((short)42)] short shortParam,
+ [DefaultValue("abc")] string stringParam
+ )
+ {
+ }
+
+ public void OptionalParamDefaultValues(
+ bool boolParam = true,
+ byte byteParam = 42,
+ char charParam = 'a',
+ double doubleParam = 1.0,
+ MyEnum enumParam = MyEnum.All,
+ float floatParam = (float)1.0,
+ int intParam = 42,
+ long longParam = 42,
+ object objectParam = null,
+ short shortParam = 42,
+ string stringParam = "abc"
+ )
+ {
+ }
+ }
+
+ private enum MyEnum
+ {
+ None = 0,
+ All = 1
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Test/PartialViewResultTest.cs b/test/System.Web.Mvc.Test/Test/PartialViewResultTest.cs
new file mode 100644
index 00000000..561a0bd9
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Test/PartialViewResultTest.cs
@@ -0,0 +1,197 @@
+using System.Collections.Generic;
+using System.IO;
+using System.Web.Routing;
+using Moq;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Mvc.Test
+{
+ public class PartialViewResultTest
+ {
+ private const string _viewName = "My cool partial view.";
+
+ [Fact]
+ public void EmptyViewNameUsesActionNameAsViewName()
+ {
+ // Arrange
+ ControllerBase controller = new Mock<ControllerBase>().Object;
+ HttpContextBase httpContext = CreateHttpContext();
+ RouteData routeData = new RouteData();
+ routeData.Values["action"] = _viewName;
+ ControllerContext context = new ControllerContext(httpContext, routeData, controller);
+ Mock<IViewEngine> viewEngine = new Mock<IViewEngine>(MockBehavior.Strict);
+ Mock<IView> view = new Mock<IView>(MockBehavior.Strict);
+ List<IViewEngine> viewEngines = new List<IViewEngine>();
+ viewEngines.Add(viewEngine.Object);
+ Mock<ViewEngineCollection> viewEngineCollection = new Mock<ViewEngineCollection>(MockBehavior.Strict, viewEngines);
+ PartialViewResult result = new PartialViewResultHelper { ViewEngineCollection = viewEngineCollection.Object };
+ viewEngine
+ .Setup(e => e.FindPartialView(It.IsAny<ControllerContext>(), _viewName, It.IsAny<bool>()))
+ .Callback<ControllerContext, string, bool>(
+ (controllerContext, viewName, useCache) =>
+ {
+ Assert.Same(httpContext, controllerContext.HttpContext);
+ Assert.Same(routeData, controllerContext.RouteData);
+ })
+ .Returns(new ViewEngineResult(view.Object, viewEngine.Object));
+ viewEngineCollection
+ .Setup(e => e.FindPartialView(It.IsAny<ControllerContext>(), _viewName))
+ .Returns(new ViewEngineResult(view.Object, viewEngine.Object));
+ view
+ .Setup(o => o.Render(It.IsAny<ViewContext>(), httpContext.Response.Output))
+ .Callback<ViewContext, TextWriter>(
+ (viewContext, writer) =>
+ {
+ Assert.Same(view.Object, viewContext.View);
+ Assert.Same(result.ViewData, viewContext.ViewData);
+ Assert.Same(result.TempData, viewContext.TempData);
+ Assert.Same(controller, viewContext.Controller);
+ });
+ viewEngine
+ .Setup(e => e.ReleaseView(context, It.IsAny<IView>()))
+ .Callback<ControllerContext, IView>(
+ (controllerContext, releasedView) => { Assert.Same(releasedView, view.Object); });
+
+ // Act
+ result.ExecuteResult(context);
+
+ // Assert
+ viewEngine.Verify();
+ viewEngineCollection.Verify();
+ view.Verify();
+ }
+
+ [Fact]
+ public void EngineLookupFailureThrows()
+ {
+ // Arrange
+ ControllerBase controller = new Mock<ControllerBase>().Object;
+ HttpContextBase httpContext = CreateHttpContext();
+ RouteData routeData = new RouteData();
+ routeData.Values["action"] = _viewName;
+ ControllerContext context = new ControllerContext(httpContext, routeData, controller);
+ Mock<IViewEngine> viewEngine = new Mock<IViewEngine>(MockBehavior.Strict);
+ List<IViewEngine> viewEngines = new List<IViewEngine>();
+ viewEngines.Add(viewEngine.Object);
+ Mock<ViewEngineCollection> viewEngineCollection = new Mock<ViewEngineCollection>(MockBehavior.Strict, viewEngines);
+ PartialViewResult result = new PartialViewResultHelper { ViewEngineCollection = viewEngineCollection.Object };
+ viewEngineCollection
+ .Setup(e => e.FindPartialView(It.IsAny<ControllerContext>(), _viewName))
+ .Returns(new ViewEngineResult(new[] { "location1", "location2" }));
+ viewEngine
+ .Setup(e => e.FindPartialView(It.IsAny<ControllerContext>(), _viewName, It.IsAny<bool>()))
+ .Callback<ControllerContext, string, bool>(
+ (controllerContext, viewName, useCache) =>
+ {
+ Assert.Same(httpContext, controllerContext.HttpContext);
+ Assert.Same(routeData, controllerContext.RouteData);
+ })
+ .Returns(new ViewEngineResult(new[] { "location1", "location2" }));
+
+ // Act & Assert
+ Assert.Throws<InvalidOperationException>(
+ () => result.ExecuteResult(context),
+ @"The partial view '" + _viewName + @"' was not found or no view engine supports the searched locations. The following locations were searched:
+location1
+location2");
+
+ viewEngine.Verify();
+ viewEngineCollection.Verify();
+ }
+
+ [Fact]
+ public void EngineLookupSuccessRendersView()
+ {
+ // Arrange
+ ControllerBase controller = new Mock<ControllerBase>().Object;
+ HttpContextBase httpContext = CreateHttpContext();
+ RouteData routeData = new RouteData();
+ ControllerContext context = new ControllerContext(httpContext, routeData, controller);
+ Mock<IViewEngine> viewEngine = new Mock<IViewEngine>(MockBehavior.Strict);
+ List<IViewEngine> viewEngines = new List<IViewEngine>();
+ viewEngines.Add(viewEngine.Object);
+ Mock<ViewEngineCollection> viewEngineCollection = new Mock<ViewEngineCollection>(MockBehavior.Strict, viewEngines);
+ Mock<IView> view = new Mock<IView>(MockBehavior.Strict);
+ PartialViewResult result = new PartialViewResultHelper { ViewEngineCollection = viewEngineCollection.Object, ViewName = _viewName };
+ view
+ .Setup(o => o.Render(It.IsAny<ViewContext>(), httpContext.Response.Output))
+ .Callback<ViewContext, TextWriter>(
+ (viewContext, writer) =>
+ {
+ Assert.Same(view.Object, viewContext.View);
+ Assert.Same(result.ViewData, viewContext.ViewData);
+ Assert.Same(result.TempData, viewContext.TempData);
+ Assert.Same(controller, viewContext.Controller);
+ });
+ viewEngineCollection
+ .Setup(e => e.FindPartialView(It.IsAny<ControllerContext>(), _viewName))
+ .Returns(new ViewEngineResult(view.Object, viewEngine.Object));
+ viewEngine
+ .Setup(e => e.FindPartialView(It.IsAny<ControllerContext>(), _viewName, It.IsAny<bool>()))
+ .Callback<ControllerContext, string, bool>(
+ (controllerContext, viewName, useCache) =>
+ {
+ Assert.Same(httpContext, controllerContext.HttpContext);
+ Assert.Same(routeData, controllerContext.RouteData);
+ })
+ .Returns(new ViewEngineResult(view.Object, viewEngine.Object));
+ viewEngine
+ .Setup(e => e.ReleaseView(context, It.IsAny<IView>()))
+ .Callback<ControllerContext, IView>(
+ (controllerContext, releasedView) => { Assert.Same(releasedView, view.Object); });
+
+ // Act
+ result.ExecuteResult(context);
+
+ // Assert
+ viewEngineCollection.Verify();
+ viewEngine.Verify();
+ view.Verify();
+ }
+
+ [Fact]
+ public void ExecuteResultWithExplicitViewObject()
+ {
+ // Arrange
+ ControllerBase controller = new Mock<ControllerBase>().Object;
+ HttpContextBase httpContext = CreateHttpContext();
+ RouteData routeData = new RouteData();
+ routeData.Values["action"] = _viewName;
+ ControllerContext context = new ControllerContext(httpContext, routeData, controller);
+ Mock<IView> view = new Mock<IView>(MockBehavior.Strict);
+ PartialViewResult result = new PartialViewResultHelper { View = view.Object };
+ view
+ .Setup(o => o.Render(It.IsAny<ViewContext>(), httpContext.Response.Output))
+ .Callback<ViewContext, TextWriter>(
+ (viewContext, writer) =>
+ {
+ Assert.Same(view.Object, viewContext.View);
+ Assert.Same(result.ViewData, viewContext.ViewData);
+ Assert.Same(result.TempData, viewContext.TempData);
+ Assert.Same(controller, viewContext.Controller);
+ });
+
+ // Act
+ result.ExecuteResult(context);
+
+ // Assert
+ view.Verify();
+ }
+
+ private static HttpContextBase CreateHttpContext()
+ {
+ Mock<HttpContextBase> mockHttpContext = new Mock<HttpContextBase>();
+ mockHttpContext.Setup(c => c.Response.Output).Returns(TextWriter.Null);
+ return mockHttpContext.Object;
+ }
+
+ private class PartialViewResultHelper : PartialViewResult
+ {
+ public PartialViewResultHelper()
+ {
+ ViewEngineCollection = new ViewEngineCollection(new IViewEngine[] { new WebFormViewEngine() });
+ }
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Test/PathHelpersTest.cs b/test/System.Web.Mvc.Test/Test/PathHelpersTest.cs
new file mode 100644
index 00000000..da773723
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Test/PathHelpersTest.cs
@@ -0,0 +1,247 @@
+using System.Collections.Specialized;
+using Moq;
+using Xunit;
+
+namespace System.Web.Mvc.Test
+{
+ public class PathHelpersTest
+ {
+ [Fact]
+ public void GenerateClientUrlWithAbsoluteContentPathAndRewritingDisabled()
+ {
+ // Arrange
+ Mock<HttpContextBase> mockHttpContext = GetMockHttpContext(includeRewriterServerVar: false);
+
+ // Act
+ string returnedUrl = PathHelpers.GenerateClientUrl(mockHttpContext.Object, "should remain unchanged");
+
+ // Assert
+ Assert.Equal("should remain unchanged", returnedUrl);
+ }
+
+ [Fact]
+ public void GenerateClientUrlWithAbsoluteContentPathAndRewritingEnabled()
+ {
+ PathHelpers.ResetUrlRewriterHelper(); // Reset the "is URL rewriting enabled?" cache
+
+ // Arrange
+ Mock<HttpContextBase> mockHttpContext = GetMockHttpContext(includeRewriterServerVar: true);
+ mockHttpContext.Setup(c => c.Request.RawUrl).Returns("/quux/foo/bar/baz");
+ mockHttpContext.Setup(c => c.Request.Path).Returns("/myapp/foo/bar/baz");
+
+ // Act
+ string returnedUrl = PathHelpers.GenerateClientUrl(mockHttpContext.Object, "/myapp/some/absolute/path?alpha=bravo");
+
+ // Assert
+ Assert.Equal("/quux/some/absolute/path?alpha=bravo", returnedUrl);
+ }
+
+ [Fact]
+ public void GenerateClientUrlWithAppRelativeContentPathAndRewritingDisabled()
+ {
+ // Arrange
+ Mock<HttpContextBase> mockHttpContext = GetMockHttpContext(includeRewriterServerVar: false);
+
+ // Act
+ string returnedUrl = PathHelpers.GenerateClientUrl(mockHttpContext.Object, "~/foo/bar?alpha=bravo");
+
+ // Assert
+ Assert.Equal("/myapp/(S(session))/foo/bar?alpha=bravo", returnedUrl);
+ }
+
+ [Fact]
+ public void GenerateClientUrlWithAppRelativeContentPathAndRewritingEnabled()
+ {
+ PathHelpers.ResetUrlRewriterHelper(); // Reset the "is URL rewriting enabled?" cache
+
+ // Arrange
+ Mock<HttpContextBase> mockHttpContext = GetMockHttpContext(includeRewriterServerVar: true);
+ mockHttpContext.Setup(c => c.Request.RawUrl).Returns("/quux/foo/baz");
+ mockHttpContext.Setup(c => c.Request.Path).Returns("/myapp/foo/baz");
+
+ // Act
+ string returnedUrl = PathHelpers.GenerateClientUrl(mockHttpContext.Object, "~/foo/bar?alpha=bravo");
+
+ // Assert
+ Assert.Equal("/quux/foo/bar?alpha=bravo", returnedUrl);
+ }
+
+ [Fact]
+ public void GenerateClientUrlWithEmptyContentPathReturnsEmptyString()
+ {
+ // Act
+ string returnedUrl = PathHelpers.GenerateClientUrl(null, "");
+
+ // Assert
+ Assert.Equal("", returnedUrl);
+ }
+
+ [Fact]
+ public void GenerateClientUrlWithNullContentPathReturnsNull()
+ {
+ // Act
+ string returnedUrl = PathHelpers.GenerateClientUrl(null, null);
+
+ // Assert
+ Assert.Null(returnedUrl);
+ }
+
+ [Fact]
+ public void GenerateClientUrlWithOnlyQueryStringForContentPathReturnsOriginalContentPath()
+ {
+ // Act
+ string returnedUrl = PathHelpers.GenerateClientUrl(null, "?foo=bar");
+
+ // Assert
+ Assert.Equal("?foo=bar", returnedUrl);
+ }
+
+ [Fact]
+ public void MakeAbsoluteFromDirectoryToParent()
+ {
+ // Act
+ string returnedUrl = PathHelpers.MakeAbsolute("/Account/Register", "../Account");
+
+ // Assert
+ Assert.Equal("/Account", returnedUrl);
+ }
+
+ [Fact]
+ public void MakeAbsoluteFromDirectoryToSelf()
+ {
+ // Act
+ string returnedUrl = PathHelpers.MakeAbsolute("/foo/", "./");
+
+ // Assert
+ Assert.Equal("/foo/", returnedUrl);
+ }
+
+ [Fact]
+ public void MakeAbsoluteFromFileToFile()
+ {
+ // Act
+ string returnedUrl = PathHelpers.MakeAbsolute("/foo", "bar");
+
+ // Assert
+ Assert.Equal("/bar", returnedUrl);
+ }
+
+ [Fact]
+ public void MakeAbsoluteFromFileWithQueryToFile()
+ {
+ // Act
+ string returnedUrl = PathHelpers.MakeAbsolute("/foo/bar?alpha=bravo", "baz");
+
+ // Assert
+ Assert.Equal("/foo/baz", returnedUrl);
+ }
+
+ [Fact]
+ public void MakeAbsoluteFromRootToSelf()
+ {
+ // Act
+ string returnedUrl = PathHelpers.MakeAbsolute("/", "./");
+
+ // Assert
+ Assert.Equal("/", returnedUrl);
+ }
+
+ [Fact]
+ public void MakeRelativeFromFileToDirectory()
+ {
+ // Act
+ string returnedUrl = PathHelpers.MakeRelative("/foo/bar", "/foo/");
+
+ // Assert
+ Assert.Equal("./", returnedUrl);
+ }
+
+ [Fact]
+ public void MakeRelativeFromFileToDirectoryWithQueryString()
+ {
+ // Act
+ string returnedUrl = PathHelpers.MakeRelative("/foo/bar", "/foo/?alpha=bravo");
+
+ // Assert
+ Assert.Equal("./?alpha=bravo", returnedUrl);
+ }
+
+ [Fact]
+ public void MakeRelativeFromFileToFile()
+ {
+ // Act
+ string returnedUrl = PathHelpers.MakeRelative("/foo/bar", "/baz/quux");
+
+ // Assert
+ Assert.Equal("../baz/quux", returnedUrl);
+ }
+
+ [Fact]
+ public void MakeRelativeFromFileToFileWithQuery()
+ {
+ // Act
+ string returnedUrl = PathHelpers.MakeRelative("/foo/bar", "/baz/quux?alpha=bravo");
+
+ // Assert
+ Assert.Equal("../baz/quux?alpha=bravo", returnedUrl);
+ }
+
+ [Fact]
+ public void MakeRelativeFromFileWithQueryToFileWithQuery()
+ {
+ // Act
+ string returnedUrl = PathHelpers.MakeRelative("/foo/bar?charlie=delta", "/baz/quux?alpha=bravo");
+
+ // Assert
+ Assert.Equal("../baz/quux?alpha=bravo", returnedUrl);
+ }
+
+ [Fact]
+ public void MakeRelativeFromRootToRoot()
+ {
+ // Act
+ string returnedUrl = PathHelpers.MakeRelative("/", "/");
+
+ // Assert
+ Assert.Equal("./", returnedUrl);
+ }
+
+ [Fact]
+ public void MakeRelativeFromRootToRootWithQueryString()
+ {
+ // Act
+ string returnedUrl = PathHelpers.MakeRelative("/", "/?foo=bar");
+
+ // Assert
+ Assert.Equal("./?foo=bar", returnedUrl);
+ }
+
+ internal static Mock<HttpContextBase> GetMockHttpContext(bool includeRewriterServerVar)
+ {
+ Mock<HttpContextBase> mockContext = new Mock<HttpContextBase>();
+
+ NameValueCollection serverVars = new NameValueCollection();
+ serverVars["IIS_UrlRewriteModule"] = "I'm on!";
+ mockContext.Setup(c => c.Request.ServerVariables).Returns(serverVars);
+ mockContext.Setup(c => c.Request.ApplicationPath).Returns("/myapp");
+
+ if (includeRewriterServerVar)
+ {
+ serverVars["IIS_WasUrlRewritten"] = "Got rewritten!";
+ mockContext
+ .Setup(c => c.Response.ApplyAppPathModifier(It.IsAny<string>()))
+ .Returns(
+ delegate(string input) { return input; });
+ }
+ else
+ {
+ mockContext
+ .Setup(c => c.Response.ApplyAppPathModifier(It.IsAny<string>()))
+ .Returns(
+ delegate(string input) { return "/myapp/(S(session))" + input.Substring("/myapp".Length); });
+ }
+
+ return mockContext;
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Test/PreApplicationStartCodeTest.cs b/test/System.Web.Mvc.Test/Test/PreApplicationStartCodeTest.cs
new file mode 100644
index 00000000..d71f42e7
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Test/PreApplicationStartCodeTest.cs
@@ -0,0 +1,26 @@
+using System.Linq;
+using System.Reflection;
+using System.Web.WebPages.TestUtils;
+using Xunit;
+
+namespace System.Web.Mvc.Test
+{
+ public class PreApplicationStartCodeTest
+ {
+ [Fact]
+ public void PreApplicationStartCodeIsNotBrowsableTest()
+ {
+ PreAppStartTestHelper.TestPreAppStartClass(typeof(PreApplicationStartCode));
+ }
+
+ [Fact]
+ public void PreApplicationStartMethodAttributeTest()
+ {
+ Assembly assembly = typeof(Controller).Assembly;
+ object[] attributes = assembly.GetCustomAttributes(typeof(PreApplicationStartMethodAttribute), true);
+ var preAppStartMethodAttribute = Assert.Single(attributes.Cast<PreApplicationStartMethodAttribute>());
+ Type preAppStartMethodType = preAppStartMethodAttribute.Type;
+ Assert.Equal(typeof(PreApplicationStartCode), preAppStartMethodType);
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Test/QueryStringValueProviderFactoryTest.cs b/test/System.Web.Mvc.Test/Test/QueryStringValueProviderFactoryTest.cs
new file mode 100644
index 00000000..34072f1e
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Test/QueryStringValueProviderFactoryTest.cs
@@ -0,0 +1,77 @@
+using System.Collections.Specialized;
+using System.Globalization;
+using Moq;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Mvc.Test
+{
+ public class QueryStringValueProviderFactoryTest
+ {
+ private static readonly NameValueCollection _backingStore = new NameValueCollection()
+ {
+ { "foo", "fooValue" }
+ };
+
+ private static readonly NameValueCollection _unvalidatedBackingStore = new NameValueCollection()
+ {
+ { "foo", "fooUnvalidated" }
+ };
+
+ [Fact]
+ public void GetValueProvider()
+ {
+ // Arrange
+ Mock<MockableUnvalidatedRequestValues> mockUnvalidatedValues = new Mock<MockableUnvalidatedRequestValues>();
+ QueryStringValueProviderFactory factory = new QueryStringValueProviderFactory(_ => mockUnvalidatedValues.Object);
+
+ Mock<ControllerContext> mockControllerContext = new Mock<ControllerContext>();
+ mockControllerContext.Setup(o => o.HttpContext.Request.QueryString).Returns(_backingStore);
+
+ // Act
+ IValueProvider valueProvider = factory.GetValueProvider(mockControllerContext.Object);
+
+ // Assert
+ Assert.Equal(typeof(QueryStringValueProvider), valueProvider.GetType());
+ ValueProviderResult vpResult = valueProvider.GetValue("foo");
+
+ Assert.NotNull(vpResult);
+ Assert.Equal("fooValue", vpResult.AttemptedValue);
+ Assert.Equal(CultureInfo.InvariantCulture, vpResult.Culture);
+ }
+
+ [Fact]
+ public void GetValueProvider_GetValue_SkipValidation()
+ {
+ // Arrange
+ Mock<MockableUnvalidatedRequestValues> mockUnvalidatedValues = new Mock<MockableUnvalidatedRequestValues>();
+ mockUnvalidatedValues.Setup(o => o.QueryString).Returns(_unvalidatedBackingStore);
+ QueryStringValueProviderFactory factory = new QueryStringValueProviderFactory(_ => mockUnvalidatedValues.Object);
+
+ Mock<ControllerContext> mockControllerContext = new Mock<ControllerContext>();
+ mockControllerContext.Setup(o => o.HttpContext.Request.QueryString).Returns(_backingStore);
+
+ // Act
+ IUnvalidatedValueProvider valueProvider = (IUnvalidatedValueProvider)factory.GetValueProvider(mockControllerContext.Object);
+
+ // Assert
+ Assert.Equal(typeof(QueryStringValueProvider), valueProvider.GetType());
+ ValueProviderResult vpResult = valueProvider.GetValue("foo", skipValidation: true);
+
+ Assert.NotNull(vpResult);
+ Assert.Equal("fooUnvalidated", vpResult.AttemptedValue);
+ Assert.Equal(CultureInfo.InvariantCulture, vpResult.Culture);
+ }
+
+ [Fact]
+ public void GetValueProvider_ThrowsIfControllerContextIsNull()
+ {
+ // Arrange
+ QueryStringValueProviderFactory factory = new QueryStringValueProviderFactory();
+
+ // Act & assert
+ Assert.ThrowsArgumentNull(
+ delegate { factory.GetValueProvider(null); }, "controllerContext");
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Test/RangeAttributeAdapterTest.cs b/test/System.Web.Mvc.Test/Test/RangeAttributeAdapterTest.cs
new file mode 100644
index 00000000..bdcbfa3b
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Test/RangeAttributeAdapterTest.cs
@@ -0,0 +1,32 @@
+using System.ComponentModel.DataAnnotations;
+using System.Linq;
+using Xunit;
+
+namespace System.Web.Mvc.Test
+{
+ public class RangeAttributeAdapterTest
+ {
+ [Fact]
+ public void ClientRulesWithRangeAttribute()
+ {
+ // Arrange
+ var metadata = ModelMetadataProviders.Current.GetMetadataForProperty(() => null, typeof(string), "Length");
+ var context = new ControllerContext();
+ var attribute = new RangeAttribute(typeof(decimal), "0", "100");
+ var adapter = new RangeAttributeAdapter(metadata, context, attribute);
+
+ // Act
+ var rules = adapter.GetClientValidationRules()
+ .OrderBy(r => r.ValidationType)
+ .ToArray();
+
+ // Assert
+ ModelClientValidationRule rule = Assert.Single(rules);
+ Assert.Equal("range", rule.ValidationType);
+ Assert.Equal(2, rule.ValidationParameters.Count);
+ Assert.Equal(0m, rule.ValidationParameters["min"]);
+ Assert.Equal(100m, rule.ValidationParameters["max"]);
+ Assert.Equal(@"The field Length must be between 0 and 100.", rule.ErrorMessage);
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Test/RazorViewEngineTest.cs b/test/System.Web.Mvc.Test/Test/RazorViewEngineTest.cs
new file mode 100644
index 00000000..1ec5c498
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Test/RazorViewEngineTest.cs
@@ -0,0 +1,253 @@
+using System.Linq;
+using Moq;
+using Xunit;
+
+namespace System.Web.Mvc.Test
+{
+ public class RazorViewEngineTest
+ {
+ [Fact]
+ public void AreaMasterLocationFormats()
+ {
+ // Arrange
+ string[] expected = new[]
+ {
+ "~/Areas/{2}/Views/{1}/{0}.cshtml",
+ "~/Areas/{2}/Views/{1}/{0}.vbhtml",
+ "~/Areas/{2}/Views/Shared/{0}.cshtml",
+ "~/Areas/{2}/Views/Shared/{0}.vbhtml"
+ };
+
+ // Act
+ RazorViewEngine viewEngine = new RazorViewEngine();
+
+ // Assert
+ Assert.Equal(expected, viewEngine.AreaMasterLocationFormats);
+ }
+
+ [Fact]
+ public void AreaPartialViewLocationFormats()
+ {
+ // Arrange
+ string[] expected = new[]
+ {
+ "~/Areas/{2}/Views/{1}/{0}.cshtml",
+ "~/Areas/{2}/Views/{1}/{0}.vbhtml",
+ "~/Areas/{2}/Views/Shared/{0}.cshtml",
+ "~/Areas/{2}/Views/Shared/{0}.vbhtml"
+ };
+
+ // Act
+ RazorViewEngine viewEngine = new RazorViewEngine();
+
+ // Assert
+ Assert.Equal(expected, viewEngine.AreaPartialViewLocationFormats);
+ }
+
+ [Fact]
+ public void AreaViewLocationFormats()
+ {
+ // Arrange
+ string[] expected = new[]
+ {
+ "~/Areas/{2}/Views/{1}/{0}.cshtml",
+ "~/Areas/{2}/Views/{1}/{0}.vbhtml",
+ "~/Areas/{2}/Views/Shared/{0}.cshtml",
+ "~/Areas/{2}/Views/Shared/{0}.vbhtml"
+ };
+
+ // Act
+ RazorViewEngine viewEngine = new RazorViewEngine();
+
+ // Assert
+ Assert.Equal(expected, viewEngine.AreaViewLocationFormats);
+ }
+
+ [Fact]
+ public void RazorViewEngineSetsViewPageActivator()
+ {
+ // Arrange
+ Mock<IViewPageActivator> viewPageActivator = new Mock<IViewPageActivator>();
+ TestableRazorViewEngine viewEngine = new TestableRazorViewEngine(viewPageActivator.Object);
+
+ //Act & Assert
+ Assert.Equal(viewPageActivator.Object, viewEngine.ViewPageActivator);
+ }
+
+ [Fact]
+ public void CreatePartialView_PassesViewPageActivator()
+ {
+ // Arrange
+ Mock<IViewPageActivator> viewPageActivator = new Mock<IViewPageActivator>();
+ TestableRazorViewEngine viewEngine = new TestableRazorViewEngine(viewPageActivator.Object);
+
+ // Act
+ RazorView result = (RazorView)viewEngine.CreatePartialView("partial path");
+
+ // Assert
+ Assert.Equal(viewEngine.ViewPageActivator, result.ViewPageActivator);
+ }
+
+ [Fact]
+ public void CreateView_PassesViewPageActivator()
+ {
+ // Arrange
+ Mock<IViewPageActivator> viewPageActivator = new Mock<IViewPageActivator>();
+ TestableRazorViewEngine viewEngine = new TestableRazorViewEngine(viewPageActivator.Object);
+
+ // Act
+ RazorView result = (RazorView)viewEngine.CreateView("partial path", "master path");
+
+ // Assert
+ Assert.Equal(viewEngine.ViewPageActivator, result.ViewPageActivator);
+ }
+
+ [Fact]
+ public void CreatePartialView_ReturnsRazorView()
+ {
+ // Arrange
+ TestableRazorViewEngine viewEngine = new TestableRazorViewEngine();
+
+ // Act
+ RazorView result = (RazorView)viewEngine.CreatePartialView("partial path");
+
+ // Assert
+ Assert.Equal("partial path", result.ViewPath);
+ Assert.Equal(String.Empty, result.LayoutPath);
+ Assert.False(result.RunViewStartPages);
+ }
+
+ [Fact]
+ public void CreateView_ReturnsRazorView()
+ {
+ // Arrange
+ TestableRazorViewEngine viewEngine = new TestableRazorViewEngine()
+ {
+ FileExtensions = new[] { "cshtml", "vbhtml", "razor" }
+ };
+
+ // Act
+ RazorView result = (RazorView)viewEngine.CreateView("partial path", "master path");
+
+ // Assert
+ Assert.Equal("partial path", result.ViewPath);
+ Assert.Equal("master path", result.LayoutPath);
+ Assert.Equal(new[] { "cshtml", "vbhtml", "razor" }, result.ViewStartFileExtensions.ToArray());
+ Assert.True(result.RunViewStartPages);
+ }
+
+ [Fact]
+ public void FileExtensionsProperty()
+ {
+ // Arrange
+ string[] expected = new[]
+ {
+ "cshtml",
+ "vbhtml",
+ };
+
+ // Act
+ RazorViewEngine viewEngine = new RazorViewEngine();
+
+ // Assert
+ Assert.Equal(expected, viewEngine.FileExtensions);
+ }
+
+ [Fact]
+ public void MasterLocationFormats()
+ {
+ // Arrange
+ string[] expected = new[]
+ {
+ "~/Views/{1}/{0}.cshtml",
+ "~/Views/{1}/{0}.vbhtml",
+ "~/Views/Shared/{0}.cshtml",
+ "~/Views/Shared/{0}.vbhtml"
+ };
+
+ // Act
+ RazorViewEngine viewEngine = new RazorViewEngine();
+
+ // Assert
+ Assert.Equal(expected, viewEngine.MasterLocationFormats);
+ }
+
+ [Fact]
+ public void PartialViewLocationFormats()
+ {
+ // Arrange
+ string[] expected = new[]
+ {
+ "~/Views/{1}/{0}.cshtml",
+ "~/Views/{1}/{0}.vbhtml",
+ "~/Views/Shared/{0}.cshtml",
+ "~/Views/Shared/{0}.vbhtml"
+ };
+
+ // Act
+ RazorViewEngine viewEngine = new RazorViewEngine();
+
+ // Assert
+ Assert.Equal(expected, viewEngine.PartialViewLocationFormats);
+ }
+
+ [Fact]
+ public void ViewLocationFormats()
+ {
+ // Arrange
+ string[] expected = new[]
+ {
+ "~/Views/{1}/{0}.cshtml",
+ "~/Views/{1}/{0}.vbhtml",
+ "~/Views/Shared/{0}.cshtml",
+ "~/Views/Shared/{0}.vbhtml"
+ };
+
+ // Act
+ RazorViewEngine viewEngine = new RazorViewEngine();
+
+ // Assert
+ Assert.Equal(expected, viewEngine.ViewLocationFormats);
+ }
+
+ [Fact]
+ public void ViewStartFileName()
+ {
+ Assert.Equal("_ViewStart", RazorViewEngine.ViewStartFileName);
+ }
+
+ private sealed class TestableRazorViewEngine : RazorViewEngine
+ {
+ public TestableRazorViewEngine()
+ : base()
+ {
+ }
+
+ public TestableRazorViewEngine(IViewPageActivator viewPageActivator)
+ : base(viewPageActivator)
+ {
+ }
+
+ public new IViewPageActivator ViewPageActivator
+ {
+ get { return base.ViewPageActivator; }
+ }
+
+ public IView CreatePartialView(string partialPath)
+ {
+ return base.CreatePartialView(new ControllerContext(), partialPath);
+ }
+
+ public IView CreateView(string viewPath, string masterPath)
+ {
+ return base.CreateView(new ControllerContext(), viewPath, masterPath);
+ }
+
+ // This method should remain overridable in derived view engines
+ protected override bool FileExists(ControllerContext controllerContext, string virtualPath)
+ {
+ return base.FileExists(controllerContext, virtualPath);
+ }
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Test/RazorViewTest.cs b/test/System.Web.Mvc.Test/Test/RazorViewTest.cs
new file mode 100644
index 00000000..fa644a9c
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Test/RazorViewTest.cs
@@ -0,0 +1,248 @@
+using System.Collections;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Web.WebPages;
+using Moq;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Mvc.Test
+{
+ public class RazorViewTest
+ {
+ [Fact]
+ public void Constructor_RunViewStartPagesParam()
+ {
+ var context = new ControllerContext();
+ Assert.True(new RazorView(context, "~/view", "~/master", runViewStartPages: true, viewStartFileExtensions: null).RunViewStartPages);
+ Assert.False(new RazorView(context, "~/view", "~/master", runViewStartPages: false, viewStartFileExtensions: null).RunViewStartPages);
+ Assert.True(new RazorView(context, "~/view", "~/master", runViewStartPages: true, viewStartFileExtensions: null, viewPageActivator: new Mock<IViewPageActivator>().Object).RunViewStartPages);
+ Assert.False(new RazorView(context, "~/view", "~/master", runViewStartPages: false, viewStartFileExtensions: null, viewPageActivator: new Mock<IViewPageActivator>().Object).RunViewStartPages);
+ }
+
+ [Fact]
+ public void ConstructorWithEmptyViewPathThrows()
+ {
+ // Act & Assert
+ Assert.ThrowsArgumentNullOrEmpty(
+ () => new RazorView(new ControllerContext(), String.Empty, "~/master", false, Enumerable.Empty<string>()),
+ "viewPath"
+ );
+ }
+
+ [Fact]
+ public void ConstructorWithNullViewPathThrows()
+ {
+ // Act & Assert
+ Assert.ThrowsArgumentNullOrEmpty(
+ () => new RazorView(new ControllerContext(), null, "~/master", false, Enumerable.Empty<string>()),
+ "viewPath"
+ );
+ }
+
+ [Fact]
+ public void ConstructorWithNullControllerContextThrows()
+ {
+ // Act & Assert
+ Assert.ThrowsArgumentNull(
+ () => new RazorView(null, "view path", "~/master", false, Enumerable.Empty<string>()),
+ "controllerContext"
+ );
+ }
+
+ [Fact]
+ public void LayoutPathProperty()
+ {
+ //Arrange
+ ControllerContext controllerContext = new ControllerContext();
+
+ // Act
+ RazorView view = new RazorView(new ControllerContext(), "view path", "master path", false, Enumerable.Empty<string>());
+
+ // Assert
+ Assert.Equal("master path", view.LayoutPath);
+ }
+
+ [Fact]
+ public void LayoutPathPropertyReturnsEmptyStringIfNullLayoutSpecified()
+ {
+ // Act
+ RazorView view = new RazorView(new ControllerContext(), "view path", null, false, Enumerable.Empty<string>());
+
+ // Assert
+ Assert.Equal(String.Empty, view.LayoutPath);
+ }
+
+ [Fact]
+ public void LayoutPathPropertyReturnsEmptyStringIfLayoutNotSpecified()
+ {
+ // Act
+ RazorView view = new RazorView(new ControllerContext(), "view path", null, false, Enumerable.Empty<string>());
+
+ // Assert
+ Assert.Equal(String.Empty, view.LayoutPath);
+ }
+
+ [Fact]
+ public void RenderWithNullWriterThrows()
+ {
+ // Arrange
+ RazorView view = new RazorView(new ControllerContext(), "~/viewPath", null, false, Enumerable.Empty<string>());
+ Mock<ViewContext> viewContextMock = new Mock<ViewContext>();
+
+ MockBuildManager buildManager = new MockBuildManager("~/viewPath", typeof(object));
+ view.BuildManager = buildManager;
+
+ // Act & Assert
+ Assert.ThrowsArgumentNull(
+ () => view.Render(viewContextMock.Object, null),
+ "writer"
+ );
+ }
+
+ [Fact]
+ public void RenderWithUnsupportedTypeThrows()
+ {
+ // Arrange
+ ViewContext context = new Mock<ViewContext>().Object;
+ MockBuildManager buildManagerMock = new MockBuildManager("view path", typeof(object));
+ RazorView view = new RazorView(new ControllerContext(), "view path", null, false, Enumerable.Empty<string>());
+ view.BuildManager = buildManagerMock;
+
+ // Act & Assert
+ Assert.Throws<InvalidOperationException>(
+ () => view.Render(context, new Mock<TextWriter>().Object),
+ "The view at 'view path' must derive from WebViewPage, or WebViewPage<TModel>."
+ );
+ }
+
+ [Fact]
+ public void RenderWithViewPageAndNoStartPageLookupRendersView()
+ {
+ // Arrange
+ StubWebViewPage viewPage = new StubWebViewPage();
+ Mock<ViewContext> viewContextMock = new Mock<ViewContext>();
+ viewContextMock.Setup(vc => vc.HttpContext.Items).Returns(new Dictionary<object, object>());
+ viewContextMock.Setup(vc => vc.HttpContext.Request.IsLocal).Returns(false);
+ MockBuildManager buildManager = new MockBuildManager("~/viewPath", typeof(object));
+ Mock<IViewPageActivator> activator = new Mock<IViewPageActivator>(MockBehavior.Strict);
+ ControllerContext controllerContext = new ControllerContext();
+ activator.Setup(l => l.Create(controllerContext, typeof(object))).Returns(viewPage);
+ RazorView view = new RazorView(controllerContext, "~/viewPath", null, false, Enumerable.Empty<string>(), activator.Object);
+ view.StartPageLookup = (WebPageRenderingBase p, string n, IEnumerable<string> e) =>
+ {
+ Assert.True(false, "ViewStart page lookup should not be called");
+ return null;
+ };
+ view.BuildManager = buildManager;
+
+ // Act
+ view.Render(viewContextMock.Object, new Mock<TextWriter>().Object);
+
+ // Assert
+ Assert.Null(viewPage.Layout);
+ Assert.Equal("", viewPage.OverridenLayoutPath);
+ Assert.Same(viewContextMock.Object, viewPage.ViewContext);
+ Assert.Equal("~/viewPath", viewPage.VirtualPath);
+ }
+
+ [Fact]
+ public void RenderWithViewPageAndStartPageLookupExecutesStartPage()
+ {
+ // Arrange
+ StubWebViewPage viewPage = new StubWebViewPage();
+ Mock<ViewContext> viewContextMock = new Mock<ViewContext>();
+ viewContextMock.Setup(vc => vc.HttpContext.Items).Returns(new Dictionary<object, object>());
+ MockBuildManager buildManager = new MockBuildManager("~/viewPath", typeof(object));
+ Mock<IViewPageActivator> activator = new Mock<IViewPageActivator>(MockBehavior.Strict);
+ ControllerContext controllerContext = new ControllerContext();
+ activator.Setup(l => l.Create(controllerContext, typeof(object))).Returns(viewPage);
+ RazorView view = new RazorView(controllerContext, "~/viewPath", null, true, new[] { "cshtml" }, activator.Object);
+ Mock<ViewStartPage> startPage = new Mock<ViewStartPage>();
+ startPage.Setup(sp => sp.ExecutePageHierarchy()).Verifiable();
+ view.StartPageLookup = (WebPageRenderingBase page, string fileName, IEnumerable<string> extensions) =>
+ {
+ Assert.Equal(viewPage, page);
+ Assert.Equal("_ViewStart", fileName);
+ Assert.Equal(new[] { "cshtml" }, extensions.ToArray());
+ return startPage.Object;
+ };
+ view.BuildManager = buildManager;
+
+ // Act
+ view.Render(viewContextMock.Object, new Mock<TextWriter>().Object);
+
+ // Assert
+ startPage.Verify(sp => sp.ExecutePageHierarchy(), Times.Once());
+ }
+
+ // TODO: This throws in WebPages and needs to be tracked down.
+ [Fact]
+ public void RenderWithViewPageAndLayoutPageRendersView()
+ {
+ // Arrange
+ StubWebViewPage viewPage = new StubWebViewPage();
+ Mock<ViewContext> viewContext = new Mock<ViewContext>();
+ Mock<HttpContextBase> httpContext = new Mock<HttpContextBase>();
+ Mock<HttpRequestBase> httpRequest = new Mock<HttpRequestBase>();
+
+ httpRequest.SetupGet(r => r.IsLocal).Returns(false);
+ httpRequest.SetupGet(r => r.Browser.IsMobileDevice).Returns(false);
+ httpRequest.SetupGet(r => r.Cookies).Returns(new HttpCookieCollection());
+
+ httpContext.SetupGet(c => c.Request).Returns(httpRequest.Object);
+ httpContext.SetupGet(c => c.Response.Cookies).Returns(new HttpCookieCollection());
+ httpContext.SetupGet(c => c.Items).Returns(new Hashtable());
+
+ viewContext.SetupGet(v => v.HttpContext).Returns(httpContext.Object);
+
+ MockBuildManager buildManager = new MockBuildManager("~/viewPath", typeof(object));
+ Mock<IViewPageActivator> activator = new Mock<IViewPageActivator>(MockBehavior.Strict);
+
+ Mock<WebPage> layoutPage = new Mock<WebPage> { CallBase = true };
+ layoutPage.Setup(c => c.Execute()).Callback(() => layoutPage.Object.RenderBody());
+ Mock<IVirtualPathFactory> virtualPathFactory = new Mock<IVirtualPathFactory>(MockBehavior.Strict);
+ virtualPathFactory.Setup(f => f.Exists("~/layoutPath")).Returns(true);
+ virtualPathFactory.Setup(f => f.CreateInstance("~/layoutPath")).Returns(layoutPage.Object);
+ ControllerContext controllerContext = new ControllerContext();
+ activator.Setup(l => l.Create(controllerContext, typeof(object))).Returns(viewPage);
+ RazorView view = new RazorView(controllerContext, "~/viewPath", "~/layoutPath", false, Enumerable.Empty<string>(), activator.Object);
+ view.BuildManager = buildManager;
+ view.VirtualPathFactory = virtualPathFactory.Object;
+ view.DisplayModeProvider = DisplayModeProvider.Instance;
+
+ // Act
+ view.Render(viewContext.Object, TextWriter.Null);
+
+ // Assert
+ Assert.Equal("~/layoutPath", viewPage.Layout);
+ Assert.Equal("~/layoutPath", viewPage.OverridenLayoutPath);
+ Assert.Same(viewContext.Object, viewPage.ViewContext);
+ Assert.Equal("~/viewPath", viewPage.VirtualPath);
+ }
+
+ public class StubWebViewPage : WebViewPage
+ {
+ public bool InitHelpersCalled;
+ public string ResultLayoutPage;
+ public string ResultOverridenLayoutPath;
+ public ViewContext ResultViewContext;
+ public string ResultVirtualPath;
+
+ public override void Execute()
+ {
+ ResultLayoutPage = Layout;
+ ResultOverridenLayoutPath = OverridenLayoutPath;
+ ResultViewContext = ViewContext;
+ ResultVirtualPath = VirtualPath;
+ }
+
+ public override void InitHelpers()
+ {
+ base.InitHelpers();
+ InitHelpersCalled = true;
+ }
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Test/ReaderWriterCacheTest.cs b/test/System.Web.Mvc.Test/Test/ReaderWriterCacheTest.cs
new file mode 100644
index 00000000..58625fdc
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Test/ReaderWriterCacheTest.cs
@@ -0,0 +1,76 @@
+using System.Collections.Generic;
+using Xunit;
+
+namespace System.Web.Mvc.Test
+{
+ public class ReaderWriterCacheTest
+ {
+ [Fact]
+ public void PublicFetchOrCreateItemCreatesItemIfNotAlreadyInCache()
+ {
+ // Arrange
+ ReaderWriterCacheHelper<int, string> helper = new ReaderWriterCacheHelper<int, string>();
+ Dictionary<int, string> cache = helper.PublicCache;
+
+ // Act
+ string item = helper.PublicFetchOrCreateItem(42, () => "new");
+
+ // Assert
+ Assert.Equal("new", cache[42]);
+ Assert.Equal("new", item);
+ }
+
+ [Fact]
+ public void PublicFetchOrCreateItemReturnsExistingItemIfFound()
+ {
+ // Arrange
+ ReaderWriterCacheHelper<int, string> helper = new ReaderWriterCacheHelper<int, string>();
+ Dictionary<int, string> cache = helper.PublicCache;
+ helper.PublicCache[42] = "original";
+
+ // Act
+ string item = helper.PublicFetchOrCreateItem(42, () => "new");
+
+ // Assert
+ Assert.Equal("original", cache[42]);
+ Assert.Equal("original", item);
+ }
+
+ [Fact]
+ public void PublicFetchOrCreateItemReturnsFirstItemIfTwoThreadsUpdateCacheSimultaneously()
+ {
+ // Arrange
+ ReaderWriterCacheHelper<int, string> helper = new ReaderWriterCacheHelper<int, string>();
+ Dictionary<int, string> cache = helper.PublicCache;
+ Func<string> creator = delegate()
+ {
+ // fake a second thread coming along when we weren't looking
+ string firstItem = helper.PublicFetchOrCreateItem(42, () => "original");
+
+ Assert.Equal("original", cache[42]);
+ Assert.Equal("original", firstItem);
+ return "new";
+ };
+
+ // Act
+ string secondItem = helper.PublicFetchOrCreateItem(42, creator);
+
+ // Assert
+ Assert.Equal("original", cache[42]);
+ Assert.Equal("original", secondItem);
+ }
+
+ private class ReaderWriterCacheHelper<TKey, TValue> : ReaderWriterCache<TKey, TValue>
+ {
+ public Dictionary<TKey, TValue> PublicCache
+ {
+ get { return Cache; }
+ }
+
+ public TValue PublicFetchOrCreateItem(TKey key, Func<TValue> creator)
+ {
+ return FetchOrCreateItem(key, creator);
+ }
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Test/RedirectResultTest.cs b/test/System.Web.Mvc.Test/Test/RedirectResultTest.cs
new file mode 100644
index 00000000..2882bc38
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Test/RedirectResultTest.cs
@@ -0,0 +1,125 @@
+using System.Web.Mvc.Properties;
+using System.Web.Routing;
+using Moq;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Mvc.Test
+{
+ public class RedirectResultTest
+ {
+ private static string _baseUrl = "http://www.contoso.com/";
+
+ [Fact]
+ public void ConstructorSetsUrl()
+ {
+ // Act
+ var result = new RedirectResult(_baseUrl);
+
+ // Assert
+ Assert.Same(_baseUrl, result.Url);
+ Assert.False(result.Permanent);
+ }
+
+ [Fact]
+ public void ConstructorSetsUrlAndPermanent()
+ {
+ // Act
+ var result = new RedirectResult(_baseUrl, permanent: true);
+
+ // Assert
+ Assert.Same(_baseUrl, result.Url);
+ Assert.True(result.Permanent);
+ }
+
+ [Fact]
+ public void ConstructorWithEmptyUrlThrows()
+ {
+ // Act & Assert
+ Assert.ThrowsArgumentNullOrEmpty(
+ delegate { new RedirectResult(String.Empty); },
+ "url");
+
+ Assert.ThrowsArgumentNullOrEmpty(
+ delegate { new RedirectResult(String.Empty, true); },
+ "url");
+ }
+
+ [Fact]
+ public void ConstructorWithNullUrlThrows()
+ {
+ // Act & Assert
+ Assert.ThrowsArgumentNullOrEmpty(
+ delegate { new RedirectResult(url: null); },
+ "url");
+
+ Assert.ThrowsArgumentNullOrEmpty(
+ delegate { new RedirectResult(url: null, permanent: true); },
+ "url");
+ }
+
+ [Fact]
+ public void ExecuteResultCallsResponseRedirect()
+ {
+ // Arrange
+ Mock<HttpResponseBase> mockResponse = new Mock<HttpResponseBase>();
+ mockResponse.Setup(o => o.Redirect(_baseUrl, false /* endResponse */)).Verifiable();
+ Mock<HttpContextBase> mockContext = new Mock<HttpContextBase>();
+ mockContext.Setup(o => o.Response).Returns(mockResponse.Object);
+ ControllerContext context = new ControllerContext(mockContext.Object, new RouteData(), new Mock<ControllerBase>().Object);
+ var result = new RedirectResult(_baseUrl);
+
+ // Act
+ result.ExecuteResult(context);
+
+ // Assert
+ mockResponse.Verify();
+ }
+
+ [Fact]
+ public void ExecuteResultWithPermanentCallsResponseRedirectPermanent()
+ {
+ // Arrange
+ Mock<HttpResponseBase> mockResponse = new Mock<HttpResponseBase>();
+ mockResponse.Setup(o => o.RedirectPermanent(_baseUrl, false /* endResponse */)).Verifiable();
+ Mock<HttpContextBase> mockContext = new Mock<HttpContextBase>();
+ mockContext.Setup(o => o.Response).Returns(mockResponse.Object);
+ ControllerContext context = new ControllerContext(mockContext.Object, new RouteData(), new Mock<ControllerBase>().Object);
+ var result = new RedirectResult(_baseUrl, permanent: true);
+
+ // Act
+ result.ExecuteResult(context);
+
+ // Assert
+ mockResponse.Verify();
+ }
+
+ [Fact]
+ public void ExecuteResultWithNullControllerContextThrows()
+ {
+ // Arrange
+ var result = new RedirectResult(_baseUrl);
+
+ // Act & Assert
+ Assert.ThrowsArgumentNull(
+ delegate { result.ExecuteResult(null /* context */); },
+ "context");
+ }
+
+ [Fact]
+ public void RedirectInChildActionThrows()
+ {
+ // Arrange
+ RouteData routeData = new RouteData();
+ routeData.DataTokens[ControllerContext.ParentActionViewContextToken] = new ViewContext();
+ ControllerContext context = new ControllerContext(new Mock<HttpContextBase>().Object, routeData, new Mock<ControllerBase>().Object);
+ RedirectResult result = new RedirectResult(_baseUrl);
+
+ // Act & Assert
+ Assert.Throws<InvalidOperationException>(
+ () => result.ExecuteResult(context),
+ MvcResources.RedirectAction_CannotRedirectInChildAction
+ );
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Test/RedirectToRouteResultTest.cs b/test/System.Web.Mvc.Test/Test/RedirectToRouteResultTest.cs
new file mode 100644
index 00000000..eb097c7d
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Test/RedirectToRouteResultTest.cs
@@ -0,0 +1,209 @@
+using System.Web.Routing;
+using Moq;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Mvc.Test
+{
+ public class RedirectToRouteResultTest
+ {
+ [Fact]
+ public void ConstructorWithNullValuesDictionary()
+ {
+ // Act
+ var result = new RedirectToRouteResult(routeValues: null);
+
+ // Assert
+ Assert.NotNull(result.RouteValues);
+ Assert.Empty(result.RouteValues);
+ Assert.Equal(String.Empty, result.RouteName);
+ Assert.False(result.Permanent);
+ }
+
+ [Fact]
+ public void ConstructorSetsValuesDictionary()
+ {
+ // Arrange
+ RouteValueDictionary dict = new RouteValueDictionary();
+
+ // Act
+ var result = new RedirectToRouteResult(dict);
+
+ // Assert
+ Assert.Same(dict, result.RouteValues);
+ Assert.Equal(String.Empty, result.RouteName);
+ Assert.False(result.Permanent);
+ }
+
+ [Fact]
+ public void ConstructorSetsValuesDictionaryAndEmptyName()
+ {
+ // Arrange
+ RouteValueDictionary dict = new RouteValueDictionary();
+
+ // Act
+ var result = new RedirectToRouteResult(null, dict);
+
+ // Assert
+ Assert.Same(dict, result.RouteValues);
+ Assert.Equal(String.Empty, result.RouteName);
+ Assert.False(result.Permanent);
+ }
+
+ [Fact]
+ public void ConstructorSetsValuesDictionaryAndName()
+ {
+ // Arrange
+ RouteValueDictionary dict = new RouteValueDictionary();
+
+ // Act
+ var result = new RedirectToRouteResult("foo", dict);
+
+ // Assert
+ Assert.Same(dict, result.RouteValues);
+ Assert.Equal("foo", result.RouteName);
+ Assert.False(result.Permanent);
+ }
+
+ [Fact]
+ public void ConstructorSetsPermanent()
+ {
+ // Act
+ var result = new RedirectToRouteResult(null, null, true);
+
+ // Assert
+ Assert.True(result.Permanent);
+ }
+
+ [Fact]
+ public void ExecuteResultCallsResponseRedirect()
+ {
+ // Arrange
+ Mock<Controller> mockController = new Mock<Controller>();
+ Mock<ControllerContext> mockControllerContext = new Mock<ControllerContext>();
+ mockControllerContext.Setup(c => c.HttpContext.Request.ApplicationPath).Returns("/somepath");
+ mockControllerContext.Setup(c => c.HttpContext.Response.ApplyAppPathModifier(It.IsAny<string>())).Returns((string s) => s);
+ mockControllerContext.Setup(c => c.HttpContext.Response.Redirect("/somepath/c/a/i", false)).Verifiable();
+ mockControllerContext.Setup(c => c.Controller).Returns(mockController.Object);
+
+ var values = new { Controller = "c", Action = "a", Id = "i" };
+ RedirectToRouteResult result = new RedirectToRouteResult(new RouteValueDictionary(values))
+ {
+ Routes = new RouteCollection() { new Route("{controller}/{action}/{id}", null) },
+ };
+
+ // Act
+ result.ExecuteResult(mockControllerContext.Object);
+
+ // Assert
+ mockControllerContext.Verify();
+ }
+
+ [Fact]
+ public void ExecuteResultWithPermanentCallsResponseRedirectPermanent()
+ {
+ // Arrange
+ Mock<Controller> mockController = new Mock<Controller>();
+ Mock<ControllerContext> mockControllerContext = new Mock<ControllerContext>();
+ mockControllerContext.Setup(c => c.HttpContext.Request.ApplicationPath).Returns("/somepath");
+ mockControllerContext.Setup(c => c.HttpContext.Response.ApplyAppPathModifier(It.IsAny<string>())).Returns((string s) => s);
+ mockControllerContext.Setup(c => c.HttpContext.Response.RedirectPermanent("/somepath/c/a/i", false)).Verifiable();
+ mockControllerContext.Setup(c => c.Controller).Returns(mockController.Object);
+
+ var values = new { Controller = "c", Action = "a", Id = "i" };
+ RedirectToRouteResult result = new RedirectToRouteResult(null, new RouteValueDictionary(values), permanent: true)
+ {
+ Routes = new RouteCollection() { new Route("{controller}/{action}/{id}", null) },
+ };
+
+ // Act
+ result.ExecuteResult(mockControllerContext.Object);
+
+ // Assert
+ mockControllerContext.Verify();
+ }
+
+ [Fact]
+ public void ExecuteResultPreservesTempData()
+ {
+ // Arrange
+ TempDataDictionary tempData = new TempDataDictionary();
+ tempData["Foo"] = "Foo";
+ tempData["Bar"] = "Bar";
+ Mock<Controller> mockController = new Mock<Controller>() { CallBase = true };
+ mockController.Object.TempData = tempData;
+ Mock<ControllerContext> mockControllerContext = new Mock<ControllerContext>();
+ mockControllerContext.Setup(c => c.HttpContext.Request.ApplicationPath).Returns("/somepath");
+ mockControllerContext.Setup(c => c.HttpContext.Response.ApplyAppPathModifier(It.IsAny<string>())).Returns((string s) => s);
+ mockControllerContext.Setup(c => c.HttpContext.Response.Redirect("/somepath/c/a/i", false)).Verifiable();
+ mockControllerContext.Setup(c => c.Controller).Returns(mockController.Object);
+
+ var values = new { Controller = "c", Action = "a", Id = "i" };
+ RedirectToRouteResult result = new RedirectToRouteResult(new RouteValueDictionary(values))
+ {
+ Routes = new RouteCollection() { new Route("{controller}/{action}/{id}", null) },
+ };
+
+ // Act
+ object value = tempData["Foo"];
+ result.ExecuteResult(mockControllerContext.Object);
+ mockController.Object.TempData.Save(mockControllerContext.Object, new Mock<ITempDataProvider>().Object);
+
+ // Assert
+ Assert.True(tempData.ContainsKey("Foo"));
+ Assert.True(tempData.ContainsKey("Bar"));
+ }
+
+ [Fact]
+ public void ExecuteResultThrowsIfVirtualPathDataIsNull()
+ {
+ // Arrange
+ var result = new RedirectToRouteResult(null)
+ {
+ Routes = new RouteCollection()
+ };
+
+ // Act & Assert
+ Assert.Throws<InvalidOperationException>(
+ delegate { result.ExecuteResult(ControllerContextTest.CreateEmptyContext()); },
+ "No route in the route table matches the supplied values.");
+ }
+
+ [Fact]
+ public void ExecuteResultWithNullControllerContextThrows()
+ {
+ // Arrange
+ var result = new RedirectToRouteResult(null);
+
+ // Act & Assert
+ Assert.ThrowsArgumentNull(
+ delegate { result.ExecuteResult(null /* context */); },
+ "context");
+ }
+
+ [Fact]
+ public void RoutesPropertyDefaultsToGlobalRouteTable()
+ {
+ // Act
+ var result = new RedirectToRouteResult(new RouteValueDictionary());
+
+ // Assert
+ Assert.Same(RouteTable.Routes, result.Routes);
+ }
+
+ [Fact]
+ public void RedirectInChildActionThrows()
+ {
+ // Arrange
+ RouteData routeData = new RouteData();
+ routeData.DataTokens[ControllerContext.ParentActionViewContextToken] = new ViewContext();
+ ControllerContext context = new ControllerContext(new Mock<HttpContextBase>().Object, routeData, new Mock<ControllerBase>().Object);
+ RedirectToRouteResult result = new RedirectToRouteResult(new RouteValueDictionary());
+
+ // Act & Assert
+ Assert.Throws<InvalidOperationException>(
+ () => result.ExecuteResult(context),
+ "Child actions are not allowed to perform redirect actions.");
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Test/ReflectedActionDescriptorTest.cs b/test/System.Web.Mvc.Test/Test/ReflectedActionDescriptorTest.cs
new file mode 100644
index 00000000..eaf418f4
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Test/ReflectedActionDescriptorTest.cs
@@ -0,0 +1,491 @@
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection;
+using Moq;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Mvc.Test
+{
+ public class ReflectedActionDescriptorTest
+ {
+ private static readonly MethodInfo _int32EqualsIntMethod = typeof(int).GetMethod("Equals", new Type[] { typeof(int) });
+
+ [Fact]
+ public void ConstructorSetsActionNameProperty()
+ {
+ // Arrange
+ string name = "someName";
+
+ // Act
+ ReflectedActionDescriptor ad = new ReflectedActionDescriptor(new Mock<MethodInfo>().Object, "someName", new Mock<ControllerDescriptor>().Object, false /* validateMethod */);
+
+ // Assert
+ Assert.Equal(name, ad.ActionName);
+ }
+
+ [Fact]
+ public void ConstructorSetsControllerDescriptorProperty()
+ {
+ // Arrange
+ ControllerDescriptor cd = new Mock<ControllerDescriptor>().Object;
+
+ // Act
+ ReflectedActionDescriptor ad = new ReflectedActionDescriptor(new Mock<MethodInfo>().Object, "someName", cd, false /* validateMethod */);
+
+ // Assert
+ Assert.Same(cd, ad.ControllerDescriptor);
+ }
+
+ [Fact]
+ public void ConstructorSetsMethodInfoProperty()
+ {
+ // Arrange
+ MethodInfo methodInfo = new Mock<MethodInfo>().Object;
+
+ // Act
+ ReflectedActionDescriptor ad = new ReflectedActionDescriptor(methodInfo, "someName", new Mock<ControllerDescriptor>().Object, false /* validateMethod */);
+
+ // Assert
+ Assert.Same(methodInfo, ad.MethodInfo);
+ }
+
+ [Fact]
+ public void ConstructorThrowsIfActionNameIsEmpty()
+ {
+ // Act & assert
+ Assert.ThrowsArgumentNullOrEmpty(
+ delegate { new ReflectedActionDescriptor(new Mock<MethodInfo>().Object, "", new Mock<ControllerDescriptor>().Object); }, "actionName");
+ }
+
+ [Fact]
+ public void ConstructorThrowsIfActionNameIsNull()
+ {
+ // Act & assert
+ Assert.ThrowsArgumentNullOrEmpty(
+ delegate { new ReflectedActionDescriptor(new Mock<MethodInfo>().Object, null, new Mock<ControllerDescriptor>().Object); }, "actionName");
+ }
+
+ [Fact]
+ public void ConstructorThrowsIfControllerDescriptorIsNull()
+ {
+ // Act & assert
+ Assert.ThrowsArgumentNull(
+ delegate { new ReflectedActionDescriptor(new Mock<MethodInfo>().Object, "someName", null); }, "controllerDescriptor");
+ }
+
+ [Fact]
+ public void ConstructorThrowsIfMethodInfoHasRefParameters()
+ {
+ // Arrange
+ MethodInfo methodInfo = typeof(MyController).GetMethod("MethodHasRefParameter");
+
+ // Act & assert
+ Assert.Throws<ArgumentException>(
+ delegate { new ReflectedActionDescriptor(methodInfo, "someName", new Mock<ControllerDescriptor>().Object); },
+ @"Cannot call action method 'Void MethodHasRefParameter(Int32 ByRef)' on controller 'System.Web.Mvc.Test.ReflectedActionDescriptorTest+MyController' because the parameter 'Int32& i' is passed by reference.
+Parameter name: methodInfo");
+ }
+
+ [Fact]
+ public void ConstructorThrowsIfMethodInfoHasOutParameters()
+ {
+ // Arrange
+ MethodInfo methodInfo = typeof(MyController).GetMethod("MethodHasOutParameter");
+
+ // Act & assert
+ Assert.Throws<ArgumentException>(
+ delegate { new ReflectedActionDescriptor(methodInfo, "someName", new Mock<ControllerDescriptor>().Object); },
+ @"Cannot call action method 'Void MethodHasOutParameter(Int32 ByRef)' on controller 'System.Web.Mvc.Test.ReflectedActionDescriptorTest+MyController' because the parameter 'Int32& i' is passed by reference.
+Parameter name: methodInfo");
+ }
+
+ [Fact]
+ public void ConstructorThrowsIfMethodInfoIsInstanceMethodOnNonControllerBaseType()
+ {
+ // Arrange
+ MethodInfo methodInfo = typeof(string).GetMethod("Clone");
+
+ // Act & assert
+ Assert.Throws<ArgumentException>(
+ delegate { new ReflectedActionDescriptor(methodInfo, "someName", new Mock<ControllerDescriptor>().Object); },
+ @"Cannot create a descriptor for instance method 'System.Object Clone()' on type 'System.String' because the type does not derive from ControllerBase.
+Parameter name: methodInfo");
+ }
+
+ [Fact]
+ public void ConstructorThrowsIfMethodIsStatic()
+ {
+ // Arrange
+ MethodInfo methodInfo = typeof(MyController).GetMethod("StaticMethod");
+
+ // Act & assert
+ Assert.Throws<ArgumentException>(
+ delegate { new ReflectedActionDescriptor(methodInfo, "someName", new Mock<ControllerDescriptor>().Object); },
+ @"Cannot call action method 'Void StaticMethod()' on controller 'System.Web.Mvc.Test.ReflectedActionDescriptorTest+MyController' because the action method is a static method.
+Parameter name: methodInfo");
+ }
+
+ [Fact]
+ public void ConstructorThrowsIfMethodInfoIsNull()
+ {
+ // Act & assert
+ Assert.ThrowsArgumentNull(
+ delegate { new ReflectedActionDescriptor(null, "someName", new Mock<ControllerDescriptor>().Object); }, "methodInfo");
+ }
+
+ [Fact]
+ public void ConstructorThrowsIfMethodInfoIsOpenGenericType()
+ {
+ // Arrange
+ MethodInfo methodInfo = typeof(MyController).GetMethod("OpenGenericMethod");
+
+ // Act & assert
+ Assert.Throws<ArgumentException>(
+ delegate { new ReflectedActionDescriptor(methodInfo, "someName", new Mock<ControllerDescriptor>().Object); },
+ @"Cannot call action method 'Void OpenGenericMethod[T]()' on controller 'System.Web.Mvc.Test.ReflectedActionDescriptorTest+MyController' because the action method is a generic method.
+Parameter name: methodInfo");
+ }
+
+ [Fact]
+ public void ExecuteCallsMethodInfoOnSuccess()
+ {
+ // Arrange
+ Mock<ControllerContext> mockControllerContext = new Mock<ControllerContext>();
+ mockControllerContext.Setup(c => c.Controller).Returns(new ConcatController());
+ Dictionary<string, object> parameters = new Dictionary<string, object>()
+ {
+ { "a", "hello " },
+ { "b", "world" }
+ };
+
+ ReflectedActionDescriptor ad = GetActionDescriptor(typeof(ConcatController).GetMethod("Concat"));
+
+ // Act
+ object result = ad.Execute(mockControllerContext.Object, parameters);
+
+ // Assert
+ Assert.Equal("hello world", result);
+ }
+
+ [Fact]
+ public void ExecuteThrowsIfControllerContextIsNull()
+ {
+ // Arrange
+ ReflectedActionDescriptor ad = GetActionDescriptor();
+
+ // Act & assert
+ Assert.ThrowsArgumentNull(
+ delegate { ad.Execute(null, new Dictionary<string, object>()); }, "controllerContext");
+ }
+
+ [Fact]
+ public void ExecuteThrowsIfParametersContainsNullForNonNullableParameter()
+ {
+ // Arrange
+ ReflectedActionDescriptor ad = GetActionDescriptor(_int32EqualsIntMethod);
+ Dictionary<string, object> parameters = new Dictionary<string, object>() { { "obj", null } };
+
+ // Act & assert
+ Assert.Throws<ArgumentException>(
+ delegate { ad.Execute(new Mock<ControllerContext>().Object, parameters); },
+ @"The parameters dictionary contains a null entry for parameter 'obj' of non-nullable type 'System.Int32' for method 'Boolean Equals(Int32)' in 'System.Int32'. An optional parameter must be a reference type, a nullable type, or be declared as an optional parameter.
+Parameter name: parameters");
+ }
+
+ [Fact]
+ public void ExecuteThrowsIfParametersContainsValueOfWrongTypeForParameter()
+ {
+ // Arrange
+ ReflectedActionDescriptor ad = GetActionDescriptor(_int32EqualsIntMethod);
+ Dictionary<string, object> parameters = new Dictionary<string, object>() { { "obj", "notAnInteger" } };
+
+ // Act & assert
+ Assert.Throws<ArgumentException>(
+ delegate { ad.Execute(new Mock<ControllerContext>().Object, parameters); },
+ @"The parameters dictionary contains an invalid entry for parameter 'obj' for method 'Boolean Equals(Int32)' in 'System.Int32'. The dictionary contains a value of type 'System.String', but the parameter requires a value of type 'System.Int32'.
+Parameter name: parameters");
+ }
+
+ [Fact]
+ public void ExecuteThrowsIfParametersIsMissingAValue()
+ {
+ // Arrange
+ ReflectedActionDescriptor ad = GetActionDescriptor(_int32EqualsIntMethod);
+ Dictionary<string, object> parameters = new Dictionary<string, object>();
+
+ // Act & assert
+ Assert.Throws<ArgumentException>(
+ delegate { ad.Execute(new Mock<ControllerContext>().Object, parameters); },
+ @"The parameters dictionary does not contain an entry for parameter 'obj' of type 'System.Int32' for method 'Boolean Equals(Int32)' in 'System.Int32'. The dictionary must contain an entry for each parameter, including parameters that have null values.
+Parameter name: parameters");
+ }
+
+ [Fact]
+ public void ExecuteThrowsIfParametersIsNull()
+ {
+ // Arrange
+ ReflectedActionDescriptor ad = GetActionDescriptor();
+
+ // Act & assert
+ Assert.ThrowsArgumentNull(
+ delegate { ad.Execute(new Mock<ControllerContext>().Object, null); }, "parameters");
+ }
+
+ [Fact]
+ public void GetCustomAttributesCallsMethodInfoGetCustomAttributes()
+ {
+ // Arrange
+ object[] expected = new object[0];
+ Mock<MethodInfo> mockMethod = new Mock<MethodInfo>();
+ mockMethod.Setup(mi => mi.GetCustomAttributes(true)).Returns(expected);
+ ReflectedActionDescriptor ad = GetActionDescriptor(mockMethod.Object);
+
+ // Act
+ object[] returned = ad.GetCustomAttributes(true);
+
+ // Assert
+ Assert.Same(expected, returned);
+ }
+
+ [Fact]
+ public void GetCustomAttributesWithAttributeTypeCallsMethodInfoGetCustomAttributes()
+ {
+ // Arrange
+ object[] expected = new object[0];
+ Mock<MethodInfo> mockMethod = new Mock<MethodInfo>();
+ mockMethod.Setup(mi => mi.GetCustomAttributes(typeof(ObsoleteAttribute), true)).Returns(expected);
+ ReflectedActionDescriptor ad = GetActionDescriptor(mockMethod.Object);
+
+ // Act
+ object[] returned = ad.GetCustomAttributes(typeof(ObsoleteAttribute), true);
+
+ // Assert
+ Assert.Same(expected, returned);
+ }
+
+ [Fact]
+ public void GetParametersWrapsParameterInfos()
+ {
+ // Arrange
+ ParameterInfo pInfo0 = typeof(ConcatController).GetMethod("Concat").GetParameters()[0];
+ ParameterInfo pInfo1 = typeof(ConcatController).GetMethod("Concat").GetParameters()[1];
+ ReflectedActionDescriptor ad = GetActionDescriptor(typeof(ConcatController).GetMethod("Concat"));
+
+ // Act
+ ParameterDescriptor[] pDescsFirstCall = ad.GetParameters();
+ ParameterDescriptor[] pDescsSecondCall = ad.GetParameters();
+
+ // Assert
+ Assert.NotSame(pDescsFirstCall, pDescsSecondCall);
+ Assert.True(pDescsFirstCall.SequenceEqual(pDescsSecondCall));
+ Assert.Equal(2, pDescsFirstCall.Length);
+
+ ReflectedParameterDescriptor pDesc0 = pDescsFirstCall[0] as ReflectedParameterDescriptor;
+ ReflectedParameterDescriptor pDesc1 = pDescsFirstCall[1] as ReflectedParameterDescriptor;
+
+ Assert.NotNull(pDesc0);
+ Assert.Same(ad, pDesc0.ActionDescriptor);
+ Assert.Same(pInfo0, pDesc0.ParameterInfo);
+ Assert.NotNull(pDesc1);
+ Assert.Same(ad, pDesc1.ActionDescriptor);
+ Assert.Same(pInfo1, pDesc1.ParameterInfo);
+ }
+
+ [Fact]
+ public void GetSelectorsWrapsSelectorAttributes()
+ {
+ // Arrange
+ ControllerContext controllerContext = new Mock<ControllerContext>().Object;
+ Mock<MethodInfo> mockMethod = new Mock<MethodInfo>();
+
+ Mock<ActionMethodSelectorAttribute> mockAttr = new Mock<ActionMethodSelectorAttribute>();
+ mockAttr.Setup(attr => attr.IsValidForRequest(controllerContext, mockMethod.Object)).Returns(true).Verifiable();
+ mockMethod.Setup(m => m.GetCustomAttributes(typeof(ActionMethodSelectorAttribute), true)).Returns(new ActionMethodSelectorAttribute[] { mockAttr.Object });
+
+ ReflectedActionDescriptor ad = GetActionDescriptor(mockMethod.Object);
+
+ // Act
+ ICollection<ActionSelector> selectors = ad.GetSelectors();
+ bool executedSuccessfully = selectors.All(s => s(controllerContext));
+
+ // Assert
+ Assert.Single(selectors);
+ Assert.True(executedSuccessfully);
+ mockAttr.Verify();
+ }
+
+ [Fact]
+ public void IsDefinedCallsMethodInfoIsDefined()
+ {
+ // Arrange
+ Mock<MethodInfo> mockMethod = new Mock<MethodInfo>();
+ mockMethod.Setup(mi => mi.IsDefined(typeof(ObsoleteAttribute), true)).Returns(true);
+ ReflectedActionDescriptor ad = GetActionDescriptor(mockMethod.Object);
+
+ // Act
+ bool isDefined = ad.IsDefined(typeof(ObsoleteAttribute), true);
+
+ // Assert
+ Assert.True(isDefined);
+ }
+
+ [Fact]
+ public void TryCreateDescriptorReturnsDescriptorOnSuccess()
+ {
+ // Arrange
+ MethodInfo methodInfo = typeof(MyController).GetMethod("GoodActionMethod");
+ ControllerDescriptor cd = new Mock<ControllerDescriptor>().Object;
+
+ // Act
+ ReflectedActionDescriptor ad = ReflectedActionDescriptor.TryCreateDescriptor(methodInfo, "someName", cd);
+
+ // Assert
+ Assert.NotNull(ad);
+ Assert.Same(methodInfo, ad.MethodInfo);
+ Assert.Equal("someName", ad.ActionName);
+ Assert.Same(cd, ad.ControllerDescriptor);
+ }
+
+ [Fact]
+ public void TryCreateDescriptorReturnsNullOnFailure()
+ {
+ // Arrange
+ MethodInfo methodInfo = typeof(MyController).GetMethod("OpenGenericMethod");
+ ControllerDescriptor cd = new Mock<ControllerDescriptor>().Object;
+
+ // Act
+ ReflectedActionDescriptor ad = ReflectedActionDescriptor.TryCreateDescriptor(methodInfo, "someName", cd);
+
+ // Assert
+ Assert.Null(ad);
+ }
+
+ private static ReflectedActionDescriptor GetActionDescriptor()
+ {
+ return GetActionDescriptor(new Mock<MethodInfo>().Object);
+ }
+
+ private static ReflectedActionDescriptor GetActionDescriptor(MethodInfo methodInfo)
+ {
+ return new ReflectedActionDescriptor(methodInfo, "someName", new Mock<ControllerDescriptor>().Object, false /* validateMethod */)
+ {
+ DispatcherCache = new ActionMethodDispatcherCache()
+ };
+ }
+
+ private class ConcatController : Controller
+ {
+ public string Concat(string a, string b)
+ {
+ return a + b;
+ }
+ }
+
+ [OutputCache(VaryByParam = "Class")]
+ private class OverriddenAttributeController : Controller
+ {
+ [OutputCache(VaryByParam = "Method")]
+ public void SomeMethod()
+ {
+ }
+ }
+
+ [KeyedActionFilter(Key = "BaseClass", Order = 0)]
+ [KeyedAuthorizationFilter(Key = "BaseClass", Order = 0)]
+ [KeyedExceptionFilter(Key = "BaseClass", Order = 0)]
+ private class GetMemberChainController : Controller
+ {
+ [KeyedActionFilter(Key = "BaseMethod", Order = 0)]
+ [KeyedAuthorizationFilter(Key = "BaseMethod", Order = 0)]
+ public virtual void SomeVirtual()
+ {
+ }
+ }
+
+ [KeyedActionFilter(Key = "DerivedClass", Order = 1)]
+ private class GetMemberChainDerivedController : GetMemberChainController
+ {
+ }
+
+ [KeyedActionFilter(Key = "SubderivedClass", Order = 2)]
+ private class GetMemberChainSubderivedController : GetMemberChainDerivedController
+ {
+ [KeyedActionFilter(Key = "SubderivedMethod", Order = 2)]
+ public override void SomeVirtual()
+ {
+ }
+ }
+
+ private abstract class KeyedFilterAttribute : FilterAttribute
+ {
+ public string Key { get; set; }
+ }
+
+ [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = true, AllowMultiple = true)]
+ private class KeyedAuthorizationFilterAttribute : KeyedFilterAttribute, IAuthorizationFilter
+ {
+ public void OnAuthorization(AuthorizationContext filterContext)
+ {
+ throw new NotImplementedException();
+ }
+ }
+
+ [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = true, AllowMultiple = true)]
+ private class KeyedExceptionFilterAttribute : KeyedFilterAttribute, IExceptionFilter
+ {
+ public void OnException(ExceptionContext filterContext)
+ {
+ throw new NotImplementedException();
+ }
+ }
+
+ [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = true, AllowMultiple = true)]
+ private class KeyedActionFilterAttribute : KeyedFilterAttribute, IActionFilter, IResultFilter
+ {
+ public void OnActionExecuting(ActionExecutingContext filterContext)
+ {
+ throw new NotImplementedException();
+ }
+
+ public void OnActionExecuted(ActionExecutedContext filterContext)
+ {
+ throw new NotImplementedException();
+ }
+
+ public void OnResultExecuting(ResultExecutingContext filterContext)
+ {
+ throw new NotImplementedException();
+ }
+
+ public void OnResultExecuted(ResultExecutedContext filterContext)
+ {
+ throw new NotImplementedException();
+ }
+ }
+
+ private class MyController : Controller
+ {
+ public void GoodActionMethod()
+ {
+ }
+
+ public static void StaticMethod()
+ {
+ }
+
+ public void OpenGenericMethod<T>()
+ {
+ }
+
+ public void MethodHasOutParameter(out int i)
+ {
+ i = 0;
+ }
+
+ public void MethodHasRefParameter(ref int i)
+ {
+ }
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Test/ReflectedControllerDescriptorTest.cs b/test/System.Web.Mvc.Test/Test/ReflectedControllerDescriptorTest.cs
new file mode 100644
index 00000000..5f5ca11f
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Test/ReflectedControllerDescriptorTest.cs
@@ -0,0 +1,198 @@
+using System.Linq;
+using System.Reflection;
+using Moq;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Mvc.Test
+{
+ public class ReflectedControllerDescriptorTest
+ {
+ [Fact]
+ public void ConstructorSetsControllerTypeProperty()
+ {
+ // Arrange
+ Type controllerType = typeof(string);
+
+ // Act
+ ReflectedControllerDescriptor cd = new ReflectedControllerDescriptor(controllerType);
+
+ // Assert
+ Assert.Same(controllerType, cd.ControllerType);
+ }
+
+ [Fact]
+ public void ConstructorThrowsIfControllerTypeIsNull()
+ {
+ // Act & assert
+ Assert.ThrowsArgumentNull(
+ delegate { new ReflectedControllerDescriptor(null); }, "controllerType");
+ }
+
+ [Fact]
+ public void FindActionReturnsActionDescriptorIfFound()
+ {
+ // Arrange
+ Type controllerType = typeof(MyController);
+ MethodInfo targetMethod = controllerType.GetMethod("AliasedMethod");
+ ReflectedControllerDescriptor cd = new ReflectedControllerDescriptor(controllerType);
+
+ // Act
+ ActionDescriptor ad = cd.FindAction(new Mock<ControllerContext>().Object, "NewName");
+
+ // Assert
+ Assert.Equal("NewName", ad.ActionName);
+ Assert.IsType<ReflectedActionDescriptor>(ad);
+ Assert.Same(targetMethod, ((ReflectedActionDescriptor)ad).MethodInfo);
+ Assert.Same(cd, ad.ControllerDescriptor);
+ }
+
+ [Fact]
+ public void FindActionReturnsNullIfNoActionFound()
+ {
+ // Arrange
+ Type controllerType = typeof(MyController);
+ ReflectedControllerDescriptor cd = new ReflectedControllerDescriptor(controllerType);
+
+ // Act
+ ActionDescriptor ad = cd.FindAction(new Mock<ControllerContext>().Object, "NonExistent");
+
+ // Assert
+ Assert.Null(ad);
+ }
+
+ [Fact]
+ public void FindActionThrowsIfActionNameIsEmpty()
+ {
+ // Arrange
+ Type controllerType = typeof(MyController);
+ ReflectedControllerDescriptor cd = new ReflectedControllerDescriptor(controllerType);
+
+ // Act & assert
+ Assert.ThrowsArgumentNullOrEmpty(
+ delegate { cd.FindAction(new Mock<ControllerContext>().Object, ""); }, "actionName");
+ }
+
+ [Fact]
+ public void FindActionThrowsIfActionNameIsNull()
+ {
+ // Arrange
+ Type controllerType = typeof(MyController);
+ ReflectedControllerDescriptor cd = new ReflectedControllerDescriptor(controllerType);
+
+ // Act & assert
+ Assert.ThrowsArgumentNullOrEmpty(
+ delegate { cd.FindAction(new Mock<ControllerContext>().Object, null); }, "actionName");
+ }
+
+ [Fact]
+ public void FindActionThrowsIfControllerContextIsNull()
+ {
+ // Arrange
+ Type controllerType = typeof(MyController);
+ ReflectedControllerDescriptor cd = new ReflectedControllerDescriptor(controllerType);
+
+ // Act & assert
+ Assert.ThrowsArgumentNull(
+ delegate { cd.FindAction(null, "someName"); }, "controllerContext");
+ }
+
+ [Fact]
+ public void GetCanonicalActionsWrapsMethodInfos()
+ {
+ // Arrange
+ Type controllerType = typeof(MyController);
+ MethodInfo mInfo0 = controllerType.GetMethod("AliasedMethod");
+ MethodInfo mInfo1 = controllerType.GetMethod("NonAliasedMethod");
+ ReflectedControllerDescriptor cd = new ReflectedControllerDescriptor(controllerType);
+
+ // Act
+ ActionDescriptor[] aDescsFirstCall = cd.GetCanonicalActions();
+ ActionDescriptor[] aDescsSecondCall = cd.GetCanonicalActions();
+
+ // Assert
+ Assert.NotSame(aDescsFirstCall, aDescsSecondCall);
+ Assert.True(aDescsFirstCall.SequenceEqual(aDescsSecondCall));
+ Assert.Equal(2, aDescsFirstCall.Length);
+
+ ReflectedActionDescriptor aDesc0 = aDescsFirstCall[0] as ReflectedActionDescriptor;
+ ReflectedActionDescriptor aDesc1 = aDescsFirstCall[1] as ReflectedActionDescriptor;
+
+ Assert.NotNull(aDesc0);
+ Assert.Equal("AliasedMethod", aDesc0.ActionName);
+ Assert.Same(mInfo0, aDesc0.MethodInfo);
+ Assert.Same(cd, aDesc0.ControllerDescriptor);
+ Assert.NotNull(aDesc1);
+ Assert.Equal("NonAliasedMethod", aDesc1.ActionName);
+ Assert.Same(mInfo1, aDesc1.MethodInfo);
+ Assert.Same(cd, aDesc1.ControllerDescriptor);
+ }
+
+ [Fact]
+ public void GetCustomAttributesCallsTypeGetCustomAttributes()
+ {
+ // Arrange
+ object[] expected = new object[0];
+ Mock<Type> mockType = new Mock<Type>();
+ mockType.Setup(t => t.GetCustomAttributes(true)).Returns(expected);
+ ReflectedControllerDescriptor cd = new ReflectedControllerDescriptor(mockType.Object);
+
+ // Act
+ object[] returned = cd.GetCustomAttributes(true);
+
+ // Assert
+ Assert.Same(expected, returned);
+ }
+
+ [Fact]
+ public void GetCustomAttributesWithAttributeTypeCallsTypeGetCustomAttributes()
+ {
+ // Arrange
+ object[] expected = new object[0];
+ Mock<Type> mockType = new Mock<Type>();
+ mockType.Setup(t => t.GetCustomAttributes(typeof(ObsoleteAttribute), true)).Returns(expected);
+ ReflectedControllerDescriptor cd = new ReflectedControllerDescriptor(mockType.Object);
+
+ // Act
+ object[] returned = cd.GetCustomAttributes(typeof(ObsoleteAttribute), true);
+
+ // Assert
+ Assert.Same(expected, returned);
+ }
+
+ [Fact]
+ public void IsDefinedCallsTypeIsDefined()
+ {
+ // Arrange
+ Mock<Type> mockType = new Mock<Type>();
+ mockType.Setup(t => t.IsDefined(typeof(ObsoleteAttribute), true)).Returns(true);
+ ReflectedControllerDescriptor cd = new ReflectedControllerDescriptor(mockType.Object);
+
+ // Act
+ bool isDefined = cd.IsDefined(typeof(ObsoleteAttribute), true);
+
+ // Assert
+ Assert.True(isDefined);
+ }
+
+ private class MyController : Controller
+ {
+ [ActionName("NewName")]
+ public void AliasedMethod()
+ {
+ }
+
+ public void NonAliasedMethod()
+ {
+ }
+
+ public void GenericMethod<T>()
+ {
+ }
+
+ private void PrivateMethod()
+ {
+ }
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Test/ReflectedParameterBindingInfoTest.cs b/test/System.Web.Mvc.Test/Test/ReflectedParameterBindingInfoTest.cs
new file mode 100644
index 00000000..f64238bf
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Test/ReflectedParameterBindingInfoTest.cs
@@ -0,0 +1,172 @@
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Linq;
+using System.Reflection;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Mvc.Test
+{
+ public class ReflectedParameterBindingInfoTest
+ {
+ [Fact]
+ public void BinderProperty()
+ {
+ // Arrange
+ ParameterInfo pInfo = typeof(MyController).GetMethod("ParameterHasSingleModelBinderAttribute").GetParameters()[0];
+ ReflectedParameterBindingInfo bindingInfo = new ReflectedParameterBindingInfo(pInfo);
+
+ // Act
+ IModelBinder binder = bindingInfo.Binder;
+
+ // Assert
+ Assert.IsType<MyModelBinder>(binder);
+ }
+
+ [Fact]
+ public void BinderPropertyThrowsIfMultipleBinderAttributesFound()
+ {
+ // Arrange
+ ParameterInfo pInfo = typeof(MyController).GetMethod("ParameterHasMultipleModelBinderAttributes").GetParameters()[0];
+ ReflectedParameterBindingInfo bindingInfo = new ReflectedParameterBindingInfo(pInfo);
+
+ // Act & assert
+ Assert.Throws<InvalidOperationException>(
+ delegate { IModelBinder binder = bindingInfo.Binder; },
+ "The parameter 'p1' on method 'Void ParameterHasMultipleModelBinderAttributes(System.Object)' contains multiple attributes that inherit from CustomModelBinderAttribute.");
+ }
+
+ [Fact]
+ public void ExcludeProperty()
+ {
+ // Arrange
+ ParameterInfo pInfo = typeof(MyController).GetMethod("ParameterHasBindAttribute").GetParameters()[0];
+ ReflectedParameterBindingInfo bindingInfo = new ReflectedParameterBindingInfo(pInfo);
+
+ // Act
+ ICollection<string> excludes = bindingInfo.Exclude;
+
+ // Assert
+ Assert.IsType<ReadOnlyCollection<string>>(excludes);
+
+ string[] excludesArray = excludes.ToArray();
+ Assert.Equal(2, excludesArray.Length);
+ Assert.Equal("excl_a", excludesArray[0]);
+ Assert.Equal("excl_b", excludesArray[1]);
+ }
+
+ [Fact]
+ public void ExcludePropertyReturnsEmptyArrayIfNoBindAttributeSpecified()
+ {
+ // Arrange
+ ParameterInfo pInfo = typeof(MyController).GetMethod("ParameterHasNoBindAttributes").GetParameters()[0];
+ ReflectedParameterBindingInfo bindingInfo = new ReflectedParameterBindingInfo(pInfo);
+
+ // Act
+ ICollection<string> excludes = bindingInfo.Exclude;
+
+ // Assert
+ Assert.NotNull(excludes);
+ Assert.Empty(excludes);
+ }
+
+ [Fact]
+ public void IncludeProperty()
+ {
+ // Arrange
+ ParameterInfo pInfo = typeof(MyController).GetMethod("ParameterHasBindAttribute").GetParameters()[0];
+ ReflectedParameterBindingInfo bindingInfo = new ReflectedParameterBindingInfo(pInfo);
+
+ // Act
+ ICollection<string> includes = bindingInfo.Include;
+
+ // Assert
+ Assert.IsType<ReadOnlyCollection<string>>(includes);
+
+ string[] includesArray = includes.ToArray();
+ Assert.Equal(2, includesArray.Length);
+ Assert.Equal("incl_a", includesArray[0]);
+ Assert.Equal("incl_b", includesArray[1]);
+ }
+
+ [Fact]
+ public void IncludePropertyReturnsEmptyArrayIfNoBindAttributeSpecified()
+ {
+ // Arrange
+ ParameterInfo pInfo = typeof(MyController).GetMethod("ParameterHasNoBindAttributes").GetParameters()[0];
+ ReflectedParameterBindingInfo bindingInfo = new ReflectedParameterBindingInfo(pInfo);
+
+ // Act
+ ICollection<string> includes = bindingInfo.Include;
+
+ // Assert
+ Assert.NotNull(includes);
+ Assert.Empty(includes);
+ }
+
+ [Fact]
+ public void PrefixProperty()
+ {
+ // Arrange
+ ParameterInfo pInfo = typeof(MyController).GetMethod("ParameterHasBindAttribute").GetParameters()[0];
+ ReflectedParameterBindingInfo bindingInfo = new ReflectedParameterBindingInfo(pInfo);
+
+ // Act
+ string prefix = bindingInfo.Prefix;
+
+ // Assert
+ Assert.Equal("some prefix", prefix);
+ }
+
+ [Fact]
+ public void PrefixPropertyReturnsNullIfNoBindAttributeSpecified()
+ {
+ // Arrange
+ ParameterInfo pInfo = typeof(MyController).GetMethod("ParameterHasNoBindAttributes").GetParameters()[0];
+ ReflectedParameterBindingInfo bindingInfo = new ReflectedParameterBindingInfo(pInfo);
+
+ // Act
+ string prefix = bindingInfo.Prefix;
+
+ // Assert
+ Assert.Null(prefix);
+ }
+
+ private class MyController : Controller
+ {
+ public void ParameterHasBindAttribute(
+ [Bind(Prefix = "some prefix", Include = "incl_a, incl_b", Exclude = "excl_a, excl_b")] object p1)
+ {
+ }
+
+ public void ParameterHasNoBindAttributes(object p1)
+ {
+ }
+
+ public void ParameterHasSingleModelBinderAttribute([ModelBinder(typeof(MyModelBinder))] object p1)
+ {
+ }
+
+ public void ParameterHasMultipleModelBinderAttributes([MyCustomModelBinder, MyCustomModelBinder] object p1)
+ {
+ }
+ }
+
+ [AttributeUsage(AttributeTargets.Parameter, AllowMultiple = true, Inherited = true)]
+ private class MyCustomModelBinderAttribute : CustomModelBinderAttribute
+ {
+ public override IModelBinder GetBinder()
+ {
+ throw new NotImplementedException();
+ }
+ }
+
+ private class MyModelBinder : IModelBinder
+ {
+ public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
+ {
+ throw new NotImplementedException();
+ }
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Test/ReflectedParameterDescriptorTest.cs b/test/System.Web.Mvc.Test/Test/ReflectedParameterDescriptorTest.cs
new file mode 100644
index 00000000..ec949366
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Test/ReflectedParameterDescriptorTest.cs
@@ -0,0 +1,159 @@
+using System.ComponentModel;
+using System.Reflection;
+using Moq;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Mvc.Test
+{
+ public class ReflectedParameterDescriptorTest
+ {
+ [Fact]
+ public void ConstructorSetsActionDescriptorProperty()
+ {
+ // Arrange
+ ParameterInfo pInfo = typeof(MyController).GetMethod("Foo").GetParameters()[0];
+ ActionDescriptor ad = new Mock<ActionDescriptor>().Object;
+
+ // Act
+ ReflectedParameterDescriptor pd = new ReflectedParameterDescriptor(pInfo, ad);
+
+ // Assert
+ Assert.Same(ad, pd.ActionDescriptor);
+ }
+
+ [Fact]
+ public void ConstructorSetsParameterInfo()
+ {
+ // Arrange
+ ParameterInfo pInfo = typeof(MyController).GetMethod("Foo").GetParameters()[0];
+
+ // Act
+ ReflectedParameterDescriptor pd = new ReflectedParameterDescriptor(pInfo, new Mock<ActionDescriptor>().Object);
+
+ // Assert
+ Assert.Same(pInfo, pd.ParameterInfo);
+ }
+
+ [Fact]
+ public void ConstructorThrowsIfActionDescriptorIsNull()
+ {
+ // Act & assert
+ Assert.ThrowsArgumentNull(
+ delegate { new ReflectedParameterDescriptor(new Mock<ParameterInfo>().Object, null); }, "actionDescriptor");
+ }
+
+ [Fact]
+ public void ConstructorThrowsIfParameterInfoIsNull()
+ {
+ // Act & assert
+ Assert.ThrowsArgumentNull(
+ delegate { new ReflectedParameterDescriptor(null, new Mock<ActionDescriptor>().Object); }, "parameterInfo");
+ }
+
+ [Fact]
+ public void DefaultValuePropertyDefaultsToNull()
+ {
+ // Arrange
+ ParameterInfo pInfo = typeof(MyController).GetMethod("DefaultValues").GetParameters()[0]; // noDefaultValue
+
+ // Act
+ ReflectedParameterDescriptor pd = GetParameterDescriptor(pInfo);
+
+ // Assert
+ Assert.Null(pd.DefaultValue);
+ }
+
+ [Fact]
+ public void GetCustomAttributesCallsParameterInfoGetCustomAttributes()
+ {
+ // Arrange
+ object[] expected = new object[0];
+ Mock<ParameterInfo> mockParameter = new Mock<ParameterInfo>();
+ mockParameter.Setup(pi => pi.Member).Returns(new Mock<MemberInfo>().Object);
+ mockParameter.Setup(pi => pi.GetCustomAttributes(true)).Returns(expected);
+ ReflectedParameterDescriptor pd = GetParameterDescriptor(mockParameter.Object);
+
+ // Act
+ object[] returned = pd.GetCustomAttributes(true);
+
+ // Assert
+ Assert.Same(expected, returned);
+ }
+
+ [Fact]
+ public void GetCustomAttributesWithAttributeTypeCallsParameterInfoGetCustomAttributes()
+ {
+ // Arrange
+ object[] expected = new object[0];
+ Mock<ParameterInfo> mockParameter = new Mock<ParameterInfo>();
+ mockParameter.Setup(pi => pi.Member).Returns(new Mock<MemberInfo>().Object);
+ mockParameter.Setup(pi => pi.GetCustomAttributes(typeof(ObsoleteAttribute), true)).Returns(expected);
+ ReflectedParameterDescriptor pd = GetParameterDescriptor(mockParameter.Object);
+
+ // Act
+ object[] returned = pd.GetCustomAttributes(typeof(ObsoleteAttribute), true);
+
+ // Assert
+ Assert.Same(expected, returned);
+ }
+
+ [Fact]
+ public void IsDefinedCallsParameterInfoIsDefined()
+ {
+ // Arrange
+ Mock<ParameterInfo> mockParameter = new Mock<ParameterInfo>();
+ mockParameter.Setup(pi => pi.Member).Returns(new Mock<MemberInfo>().Object);
+ mockParameter.Setup(pi => pi.IsDefined(typeof(ObsoleteAttribute), true)).Returns(true);
+ ReflectedParameterDescriptor pd = GetParameterDescriptor(mockParameter.Object);
+
+ // Act
+ bool isDefined = pd.IsDefined(typeof(ObsoleteAttribute), true);
+
+ // Assert
+ Assert.True(isDefined);
+ }
+
+ [Fact]
+ public void ParameterNameProperty()
+ {
+ // Arrange
+ ParameterInfo pInfo = typeof(MyController).GetMethod("Foo").GetParameters()[0];
+
+ // Act
+ ReflectedParameterDescriptor pd = GetParameterDescriptor(pInfo);
+
+ // Assert
+ Assert.Equal("s1", pd.ParameterName);
+ }
+
+ [Fact]
+ public void ParameterTypeProperty()
+ {
+ // Arrange
+ ParameterInfo pInfo = typeof(MyController).GetMethod("Foo").GetParameters()[0];
+
+ // Act
+ ReflectedParameterDescriptor pd = GetParameterDescriptor(pInfo);
+
+ // Assert
+ Assert.Equal(typeof(string), pd.ParameterType);
+ }
+
+ private static ReflectedParameterDescriptor GetParameterDescriptor(ParameterInfo parameterInfo)
+ {
+ return new ReflectedParameterDescriptor(parameterInfo, new Mock<ActionDescriptor>().Object);
+ }
+
+ private class MyController : Controller
+ {
+ public void Foo(string s1)
+ {
+ }
+
+ public void DefaultValues(string noDefaultValue, [DefaultValue("someValue")] string hasDefaultValue)
+ {
+ }
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Test/RegularExpressionAttributeAdapterTest.cs b/test/System.Web.Mvc.Test/Test/RegularExpressionAttributeAdapterTest.cs
new file mode 100644
index 00000000..cfba012e
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Test/RegularExpressionAttributeAdapterTest.cs
@@ -0,0 +1,31 @@
+using System.ComponentModel.DataAnnotations;
+using System.Linq;
+using Xunit;
+
+namespace System.Web.Mvc.Test
+{
+ public class RegularExpressionAttributeAdapterTest
+ {
+ [Fact]
+ public void ClientRulesWithRegexAttribute()
+ {
+ // Arrange
+ var metadata = ModelMetadataProviders.Current.GetMetadataForProperty(() => null, typeof(string), "Length");
+ var context = new ControllerContext();
+ var attribute = new RegularExpressionAttribute("the_pattern");
+ var adapter = new RegularExpressionAttributeAdapter(metadata, context, attribute);
+
+ // Act
+ var rules = adapter.GetClientValidationRules()
+ .OrderBy(r => r.ValidationType)
+ .ToArray();
+
+ // Assert
+ ModelClientValidationRule rule = Assert.Single(rules);
+ Assert.Equal("regex", rule.ValidationType);
+ Assert.Single(rule.ValidationParameters);
+ Assert.Equal("the_pattern", rule.ValidationParameters["pattern"]);
+ Assert.Equal(@"The field Length must match the regular expression 'the_pattern'.", rule.ErrorMessage);
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Test/RemoteAttributeTest.cs b/test/System.Web.Mvc.Test/Test/RemoteAttributeTest.cs
new file mode 100644
index 00000000..019066d2
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Test/RemoteAttributeTest.cs
@@ -0,0 +1,172 @@
+using System.Collections.Generic;
+using System.Linq;
+using System.Web.Routing;
+using Moq;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Mvc.Test
+{
+ public class RemoteAttributeTest
+ {
+ // Good route name, bad route name
+ // Controller + Action
+
+ [Fact]
+ public void GuardClauses()
+ {
+ // Act & Assert
+ Assert.ThrowsArgumentNullOrEmpty(
+ () => new RemoteAttribute(null, "controller"),
+ "action");
+ Assert.ThrowsArgumentNullOrEmpty(
+ () => new RemoteAttribute("action", null),
+ "controller");
+ Assert.ThrowsArgumentNullOrEmpty(
+ () => new RemoteAttribute(null),
+ "routeName");
+ Assert.ThrowsArgumentNullOrEmpty(
+ () => RemoteAttribute.FormatPropertyForClientValidation(String.Empty),
+ "property");
+ Assert.ThrowsArgumentNullOrEmpty(
+ () => new RemoteAttribute("foo").FormatAdditionalFieldsForClientValidation(String.Empty),
+ "property");
+ }
+
+ [Fact]
+ public void IsValidAlwaysReturnsTrue()
+ {
+ // Act & Assert
+ Assert.True(new RemoteAttribute("RouteName", "ParameterName").IsValid(null));
+ Assert.True(new RemoteAttribute("ActionName", "ControllerName", "ParameterName").IsValid(null));
+ }
+
+ [Fact]
+ public void BadRouteNameThrows()
+ {
+ // Arrange
+ ControllerContext context = new ControllerContext();
+ ModelMetadata metadata = ModelMetadataProviders.Current.GetMetadataForType(null, typeof(object));
+ TestableRemoteAttribute attribute = new TestableRemoteAttribute("RouteName");
+
+ // Act & Assert
+ Assert.Throws<ArgumentException>(
+ () => new List<ModelClientValidationRule>(attribute.GetClientValidationRules(metadata, context)),
+ "A route named 'RouteName' could not be found in the route collection.\r\nParameter name: name");
+ }
+
+ [Fact]
+ public void NoRouteWithActionControllerThrows()
+ {
+ // Arrange
+ ControllerContext context = new ControllerContext();
+ ModelMetadata metadata = ModelMetadataProviders.Current.GetMetadataForProperty(null, typeof(string), "Length");
+ TestableRemoteAttribute attribute = new TestableRemoteAttribute("Action", "Controller");
+
+ // Act & Assert
+ Assert.Throws<InvalidOperationException>(
+ () => new List<ModelClientValidationRule>(attribute.GetClientValidationRules(metadata, context)),
+ "No url for remote validation could be found.");
+ }
+
+ [Fact]
+ public void GoodRouteNameReturnsCorrectClientData()
+ {
+ // Arrange
+ string url = null;
+ ModelMetadata metadata = ModelMetadataProviders.Current.GetMetadataForProperty(null, typeof(string), "Length");
+ TestableRemoteAttribute attribute = new TestableRemoteAttribute("RouteName");
+ attribute.RouteTable.Add("RouteName", new Route("my/url", new MvcRouteHandler()));
+
+ // Act
+ ModelClientValidationRule rule = attribute.GetClientValidationRules(metadata, GetMockControllerContext(url)).Single();
+
+ // Assert
+ Assert.Equal("remote", rule.ValidationType);
+ Assert.Equal("'Length' is invalid.", rule.ErrorMessage);
+ Assert.Equal(2, rule.ValidationParameters.Count);
+ Assert.Equal("/my/url", rule.ValidationParameters["url"]);
+ }
+
+ [Fact]
+ public void ActionControllerReturnsCorrectClientDataWithoutNamedParameters()
+ {
+ // Arrange
+ string url = null;
+
+ ModelMetadata metadata = ModelMetadataProviders.Current.GetMetadataForProperty(null, typeof(string), "Length");
+ TestableRemoteAttribute attribute = new TestableRemoteAttribute("Action", "Controller");
+ attribute.RouteTable.Add(new Route("{controller}/{action}", new MvcRouteHandler()));
+
+ // Act
+ ModelClientValidationRule rule = attribute.GetClientValidationRules(metadata, GetMockControllerContext(url)).Single();
+
+ // Assert
+ Assert.Equal("remote", rule.ValidationType);
+ Assert.Equal("'Length' is invalid.", rule.ErrorMessage);
+ Assert.Equal(2, rule.ValidationParameters.Count);
+ Assert.Equal("/Controller/Action", rule.ValidationParameters["url"]);
+ Assert.Equal("*.Length", rule.ValidationParameters["additionalfields"]);
+ Assert.Throws<KeyNotFoundException>(
+ () => rule.ValidationParameters["type"],
+ "The given key was not present in the dictionary.");
+ }
+
+ [Fact]
+ public void ActionControllerReturnsCorrectClientDataWithNamedParameters()
+ {
+ // Arrange
+ string url = null;
+
+ ModelMetadata metadata = ModelMetadataProviders.Current.GetMetadataForProperty(null, typeof(string), "Length");
+ TestableRemoteAttribute attribute = new TestableRemoteAttribute("Action", "Controller");
+ attribute.HttpMethod = "POST";
+ attribute.AdditionalFields = "Password,ConfirmPassword";
+
+ attribute.RouteTable.Add(new Route("{controller}/{action}", new MvcRouteHandler()));
+
+ // Act
+ ModelClientValidationRule rule = attribute.GetClientValidationRules(metadata, GetMockControllerContext(url)).Single();
+
+ // Assert
+ Assert.Equal("remote", rule.ValidationType);
+ Assert.Equal("'Length' is invalid.", rule.ErrorMessage);
+ Assert.Equal(3, rule.ValidationParameters.Count);
+ Assert.Equal("/Controller/Action", rule.ValidationParameters["url"]);
+ Assert.Equal("*.Length,*.Password,*.ConfirmPassword", rule.ValidationParameters["additionalfields"]);
+ Assert.Equal("POST", rule.ValidationParameters["type"]);
+ }
+
+ private ControllerContext GetMockControllerContext(string url)
+ {
+ Mock<ControllerContext> context = new Mock<ControllerContext>();
+ context.Setup(c => c.HttpContext.Request.ApplicationPath)
+ .Returns("/");
+ context.Setup(c => c.HttpContext.Response.ApplyAppPathModifier(It.IsAny<string>()))
+ .Callback<string>(vpath => url = vpath)
+ .Returns(() => url);
+
+ return context.Object;
+ }
+
+ private class TestableRemoteAttribute : RemoteAttribute
+ {
+ public RouteCollection RouteTable = new RouteCollection();
+
+ public TestableRemoteAttribute(string action, string controller)
+ : base(action, controller)
+ {
+ }
+
+ public TestableRemoteAttribute(string routeName)
+ : base(routeName)
+ {
+ }
+
+ protected override RouteCollection Routes
+ {
+ get { return RouteTable; }
+ }
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Test/RequireHttpsAttributeTest.cs b/test/System.Web.Mvc.Test/Test/RequireHttpsAttributeTest.cs
new file mode 100644
index 00000000..38d623ac
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Test/RequireHttpsAttributeTest.cs
@@ -0,0 +1,106 @@
+using Moq;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Mvc.Test
+{
+ public class RequireHttpsAttributeTest
+ {
+ [Fact]
+ public void HandleNonHttpsRequestExtensibility()
+ {
+ // Arrange
+ Mock<AuthorizationContext> mockAuthContext = new Mock<AuthorizationContext>();
+ mockAuthContext.Setup(c => c.HttpContext.Request.IsSecureConnection).Returns(false);
+ AuthorizationContext authContext = mockAuthContext.Object;
+
+ RequireHttpsAttribute attr = new MyRequireHttpsAttribute();
+
+ // Act
+ attr.OnAuthorization(authContext);
+ ContentResult result = authContext.Result as ContentResult;
+
+ // Assert
+ Assert.NotNull(result);
+ Assert.Equal("Custom HandleNonHttpsRequest", result.Content);
+ }
+
+ [Fact]
+ public void OnAuthorizationDoesNothingIfRequestIsSecure()
+ {
+ // Arrange
+ Mock<AuthorizationContext> mockAuthContext = new Mock<AuthorizationContext>();
+ mockAuthContext.Setup(c => c.HttpContext.Request.IsSecureConnection).Returns(true);
+ AuthorizationContext authContext = mockAuthContext.Object;
+
+ ViewResult result = new ViewResult();
+ authContext.Result = result;
+
+ RequireHttpsAttribute attr = new RequireHttpsAttribute();
+
+ // Act
+ attr.OnAuthorization(authContext);
+
+ // Assert
+ Assert.Same(result, authContext.Result);
+ }
+
+ [Fact]
+ public void OnAuthorizationRedirectsIfRequestIsNotSecureAndMethodIsGet()
+ {
+ // Arrange
+ Mock<AuthorizationContext> mockAuthContext = new Mock<AuthorizationContext>();
+ mockAuthContext.Setup(c => c.HttpContext.Request.HttpMethod).Returns("get");
+ mockAuthContext.Setup(c => c.HttpContext.Request.IsSecureConnection).Returns(false);
+ mockAuthContext.Setup(c => c.HttpContext.Request.RawUrl).Returns("/alpha/bravo/charlie?q=quux");
+ mockAuthContext.Setup(c => c.HttpContext.Request.Url).Returns(new Uri("http://www.example.com:8080/foo/bar/baz"));
+ AuthorizationContext authContext = mockAuthContext.Object;
+
+ RequireHttpsAttribute attr = new RequireHttpsAttribute();
+
+ // Act
+ attr.OnAuthorization(authContext);
+ RedirectResult result = authContext.Result as RedirectResult;
+
+ // Assert
+ Assert.NotNull(result);
+ Assert.Equal("https://www.example.com/alpha/bravo/charlie?q=quux", result.Url);
+ }
+
+ [Fact]
+ public void OnAuthorizationThrowsIfFilterContextIsNull()
+ {
+ // Arrange
+ RequireHttpsAttribute attr = new RequireHttpsAttribute();
+
+ // Act & assert
+ Assert.ThrowsArgumentNull(
+ delegate { attr.OnAuthorization(null); }, "filterContext");
+ }
+
+ [Fact]
+ public void OnAuthorizationThrowsIfRequestIsNotSecureAndMethodIsNotGet()
+ {
+ // Arrange
+ Mock<AuthorizationContext> mockAuthContext = new Mock<AuthorizationContext>();
+ mockAuthContext.Setup(c => c.HttpContext.Request.HttpMethod).Returns("post");
+ mockAuthContext.Setup(c => c.HttpContext.Request.IsSecureConnection).Returns(false);
+ AuthorizationContext authContext = mockAuthContext.Object;
+
+ RequireHttpsAttribute attr = new RequireHttpsAttribute();
+
+ // Act & assert
+ Assert.Throws<InvalidOperationException>(
+ delegate { attr.OnAuthorization(authContext); },
+ @"The requested resource can only be accessed via SSL.");
+ }
+
+ private class MyRequireHttpsAttribute : RequireHttpsAttribute
+ {
+ protected override void HandleNonHttpsRequest(AuthorizationContext filterContext)
+ {
+ filterContext.Result = new ContentResult() { Content = "Custom HandleNonHttpsRequest" };
+ }
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Test/RequiredAttributeAdapterTest.cs b/test/System.Web.Mvc.Test/Test/RequiredAttributeAdapterTest.cs
new file mode 100644
index 00000000..62c041eb
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Test/RequiredAttributeAdapterTest.cs
@@ -0,0 +1,30 @@
+using System.ComponentModel.DataAnnotations;
+using System.Linq;
+using Xunit;
+
+namespace System.Web.Mvc.Test
+{
+ public class RequiredAttributeAdapterTest
+ {
+ [Fact]
+ public void ClientRulesWithRequiredAttribute()
+ {
+ // Arrange
+ var metadata = ModelMetadataProviders.Current.GetMetadataForProperty(() => null, typeof(string), "Length");
+ var context = new ControllerContext();
+ var attribute = new RequiredAttribute();
+ var adapter = new RequiredAttributeAdapter(metadata, context, attribute);
+
+ // Act
+ var rules = adapter.GetClientValidationRules()
+ .OrderBy(r => r.ValidationType)
+ .ToArray();
+
+ // Assert
+ ModelClientValidationRule rule = Assert.Single(rules);
+ Assert.Equal("required", rule.ValidationType);
+ Assert.Empty(rule.ValidationParameters);
+ Assert.Equal(@"The Length field is required.", rule.ErrorMessage);
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Test/ResultExecutedContextTest.cs b/test/System.Web.Mvc.Test/Test/ResultExecutedContextTest.cs
new file mode 100644
index 00000000..7852b7eb
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Test/ResultExecutedContextTest.cs
@@ -0,0 +1,55 @@
+using Moq;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Mvc.Test
+{
+ public class ResultExecutedContextTest
+ {
+ [Fact]
+ public void ConstructorThrowsIfControllerDescriptorIsNull()
+ {
+ // Arrange
+ ControllerContext controllerContext = null;
+ ActionResult result = new ViewResult();
+ bool canceled = true;
+ Exception exception = null;
+
+ // Act & assert
+ Assert.ThrowsArgumentNull(
+ delegate { new ResultExecutedContext(controllerContext, result, canceled, exception); }, "controllerContext");
+ }
+
+ [Fact]
+ public void ConstructorThrowsIfResultIsNull()
+ {
+ // Arrange
+ ControllerContext controllerContext = new Mock<ControllerContext>().Object;
+ ActionResult result = null;
+ bool canceled = true;
+ Exception exception = null;
+
+ // Act & assert
+ Assert.ThrowsArgumentNull(
+ delegate { new ResultExecutedContext(controllerContext, result, canceled, exception); }, "result");
+ }
+
+ [Fact]
+ public void PropertiesAreSetByConstructor()
+ {
+ // Arrange
+ ControllerContext controllerContext = new Mock<ControllerContext>().Object;
+ ActionResult result = new ViewResult();
+ bool canceled = true;
+ Exception exception = new Exception();
+
+ // Act
+ ResultExecutedContext resultExecutedContext = new ResultExecutedContext(controllerContext, result, canceled, exception);
+
+ // Assert
+ Assert.Equal(result, resultExecutedContext.Result);
+ Assert.Equal(canceled, resultExecutedContext.Canceled);
+ Assert.Equal(exception, resultExecutedContext.Exception);
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Test/ResultExecutingContextTest.cs b/test/System.Web.Mvc.Test/Test/ResultExecutingContextTest.cs
new file mode 100644
index 00000000..2881e2c0
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Test/ResultExecutingContextTest.cs
@@ -0,0 +1,47 @@
+using Moq;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Mvc.Test
+{
+ public class ResultExecutingContextTest
+ {
+ [Fact]
+ public void ConstructorThrowsIfControllerContextIsNull()
+ {
+ // Arrange
+ ControllerContext controllerContext = null;
+ ActionResult result = new ViewResult();
+
+ // Act & assert
+ Assert.ThrowsArgumentNull(
+ delegate { new ResultExecutingContext(controllerContext, result); }, "controllerContext");
+ }
+
+ [Fact]
+ public void ConstructorThrowsIfResultIsNull()
+ {
+ // Arrange
+ ControllerContext controllerContext = new Mock<ControllerContext>().Object;
+ ActionResult result = null;
+
+ // Act & assert
+ Assert.ThrowsArgumentNull(
+ delegate { new ResultExecutingContext(controllerContext, result); }, "result");
+ }
+
+ [Fact]
+ public void ResultProperty()
+ {
+ // Arrange
+ ControllerContext controllerContext = new Mock<ControllerContext>().Object;
+ ActionResult result = new ViewResult();
+
+ // Act
+ ResultExecutingContext resultExecutingContext = new ResultExecutingContext(controllerContext, result);
+
+ // Assert
+ Assert.Equal(result, resultExecutingContext.Result);
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Test/RouteCollectionExtensionsTest.cs b/test/System.Web.Mvc.Test/Test/RouteCollectionExtensionsTest.cs
new file mode 100644
index 00000000..300de646
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Test/RouteCollectionExtensionsTest.cs
@@ -0,0 +1,388 @@
+using System.Linq;
+using System.Web.Routing;
+using Moq;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Mvc.Test
+{
+ public class RouteCollectionExtensionsTest
+ {
+ private static string[] _nameSpaces = new string[] { "nsA.nsB.nsC", "ns1.ns2.ns3" };
+
+ [Fact]
+ public void GetVirtualPathForAreaDoesNotStripAreaTokenIfAreasNotInUse()
+ {
+ // Arrange
+ RouteCollection routes = new RouteCollection();
+ routes.MapRoute(
+ "Default",
+ "no-area/{controller}/{action}/{id}",
+ new { controller = "Home", action = "Index", id = "" }
+ );
+
+ RequestContext requestContext = GetRequestContext(null);
+ RouteValueDictionary values = new RouteValueDictionary()
+ {
+ { "controller", "home" },
+ { "action", "about" },
+ { "area", "some-area" }
+ };
+
+ // Act
+ VirtualPathData vpd = routes.GetVirtualPathForArea(requestContext, values);
+
+ // Assert
+ Assert.NotNull(vpd);
+ Assert.Equal(routes["Default"], vpd.Route);
+
+ // note presence of 'area' query string parameter; RVD should not be modified if areas not in use
+ Assert.Equal("/app/no-area/home/about?area=some-area", vpd.VirtualPath);
+ }
+
+ [Fact]
+ public void GetVirtualPathForAreaForwardsCallIfRouteNameSpecified()
+ {
+ // Arrange
+ RouteCollection routes = GetRouteCollection();
+ RequestContext requestContext = GetRequestContext(null);
+ RouteValueDictionary values = new RouteValueDictionary()
+ {
+ { "controller", "home" },
+ { "action", "index" },
+ { "area", "some-area" }
+ };
+
+ // Act
+ VirtualPathData vpd = routes.GetVirtualPathForArea(requestContext, "admin_default", values);
+
+ // Assert
+ Assert.NotNull(vpd);
+ Assert.Equal(routes["admin_default"], vpd.Route);
+
+ // note presence of 'area' query string parameter; RVD should not be modified if route name was provided
+ Assert.Equal("/app/admin-area?area=some-area", vpd.VirtualPath);
+ }
+
+ [Fact]
+ public void GetVirtualPathForAreaThrowsIfRoutesIsNull()
+ {
+ // Act & assert
+ Assert.ThrowsArgumentNull(
+ delegate { RouteCollectionExtensions.GetVirtualPathForArea(null, null, null); }, "routes");
+ }
+
+ [Fact]
+ public void GetVirtualPathForAreaWillJumpBetweenAreasExplicitly()
+ {
+ // Arrange
+ RouteCollection routes = GetRouteCollection();
+ RequestContext requestContext = GetRequestContext(null);
+ RouteValueDictionary values = new RouteValueDictionary()
+ {
+ { "controller", "home" },
+ { "action", "tenmostrecent" },
+ { "tag", "some-tag" },
+ { "area", "blog" }
+ };
+
+ // Act
+ VirtualPathData vpd = routes.GetVirtualPathForArea(requestContext, values);
+
+ // Assert
+ Assert.NotNull(vpd);
+ Assert.Equal(routes["blog_whatsnew"], vpd.Route);
+ Assert.Equal("/app/whats-new/some-tag", vpd.VirtualPath);
+ }
+
+ [Fact]
+ public void GetVirtualPathForAreaWillNotJumpBetweenAreasImplicitly()
+ {
+ // Arrange
+ RouteCollection routes = GetRouteCollection();
+ RequestContext requestContext = GetRequestContext("admin");
+ RouteValueDictionary values = new RouteValueDictionary()
+ {
+ { "controller", "home" },
+ { "action", "tenmostrecent" },
+ { "tag", "some-tag" }
+ };
+
+ // Act
+ VirtualPathData vpd = routes.GetVirtualPathForArea(requestContext, values);
+
+ // Assert
+ Assert.NotNull(vpd);
+ Assert.Equal(routes["admin_default"], vpd.Route);
+ Assert.Equal("/app/admin-area/home/tenmostrecent?tag=some-tag", vpd.VirtualPath);
+ }
+
+ [Fact]
+ public void MapRoute3()
+ {
+ // Arrange
+ RouteCollection routes = new RouteCollection();
+
+ // Act
+ routes.MapRoute("RouteName", "SomeUrl");
+
+ // Assert
+ Route route = Assert.Single(routes.Cast<Route>());
+ Assert.NotNull(route);
+ Assert.Same(route, routes["RouteName"]);
+ Assert.Equal("SomeUrl", route.Url);
+ Assert.IsType<MvcRouteHandler>(route.RouteHandler);
+ Assert.Empty(route.Defaults);
+ Assert.Empty(route.Constraints);
+ Assert.Empty(route.DataTokens);
+ }
+
+ [Fact]
+ public void MapRoute3WithNameSpaces()
+ {
+ // Arrange
+ RouteCollection routes = new RouteCollection();
+ //string[] namespaces = new string[] { "nsA.nsB.nsC", "ns1.ns2.ns3" };
+
+ // Act
+ routes.MapRoute("RouteName", "SomeUrl", _nameSpaces);
+
+ // Assert
+ Route route = Assert.Single(routes.Cast<Route>());
+ Assert.NotNull(route);
+ Assert.NotNull(route.DataTokens);
+ Assert.NotNull(route.DataTokens["Namespaces"]);
+ string[] routeNameSpaces = route.DataTokens["Namespaces"] as string[];
+ Assert.Equal(routeNameSpaces.Length, 2);
+ Assert.Same(route, routes["RouteName"]);
+ Assert.Same(routeNameSpaces, _nameSpaces);
+ Assert.Equal("SomeUrl", route.Url);
+ Assert.IsType<MvcRouteHandler>(route.RouteHandler);
+ Assert.Empty(route.Defaults);
+ Assert.Empty(route.Constraints);
+ }
+
+ [Fact]
+ public void MapRoute3WithEmptyNameSpaces()
+ {
+ // Arrange
+ RouteCollection routes = new RouteCollection();
+
+ // Act
+ routes.MapRoute("RouteName", "SomeUrl", new string[] { });
+
+ // Assert
+ Route route = Assert.Single(routes.Cast<Route>());
+ Assert.NotNull(route);
+ Assert.Same(route, routes["RouteName"]);
+ Assert.Equal("SomeUrl", route.Url);
+ Assert.IsType<MvcRouteHandler>(route.RouteHandler);
+ Assert.Empty(route.Defaults);
+ Assert.Empty(route.Constraints);
+ Assert.Empty(route.DataTokens);
+ }
+
+ [Fact]
+ public void MapRoute4()
+ {
+ // Arrange
+ RouteCollection routes = new RouteCollection();
+ var defaults = new { Foo = "DefaultFoo" };
+
+ // Act
+ routes.MapRoute("RouteName", "SomeUrl", defaults);
+
+ // Assert
+ Route route = Assert.Single(routes.Cast<Route>());
+ Assert.NotNull(route);
+ Assert.Same(route, routes["RouteName"]);
+ Assert.Equal("SomeUrl", route.Url);
+ Assert.IsType<MvcRouteHandler>(route.RouteHandler);
+ Assert.Equal("DefaultFoo", route.Defaults["Foo"]);
+ Assert.Empty(route.Constraints);
+ Assert.Empty(route.DataTokens);
+ }
+
+ [Fact]
+ public void MapRoute4WithNameSpaces()
+ {
+ // Arrange
+ RouteCollection routes = new RouteCollection();
+ var defaults = new { Foo = "DefaultFoo" };
+
+ // Act
+ routes.MapRoute("RouteName", "SomeUrl", defaults, _nameSpaces);
+
+ // Assert
+ Route route = Assert.Single(routes.Cast<Route>());
+ Assert.NotNull(route);
+ Assert.NotNull(route.DataTokens);
+ Assert.NotNull(route.DataTokens["Namespaces"]);
+ string[] routeNameSpaces = route.DataTokens["Namespaces"] as string[];
+ Assert.Equal(routeNameSpaces.Length, 2);
+ Assert.Same(route, routes["RouteName"]);
+ Assert.Same(routeNameSpaces, _nameSpaces);
+ Assert.Equal("SomeUrl", route.Url);
+ Assert.IsType<MvcRouteHandler>(route.RouteHandler);
+ Assert.Equal("DefaultFoo", route.Defaults["Foo"]);
+ Assert.Empty(route.Constraints);
+ }
+
+ [Fact]
+ public void MapRoute5()
+ {
+ // Arrange
+ RouteCollection routes = new RouteCollection();
+ var defaults = new { Foo = "DefaultFoo" };
+ var constraints = new { Foo = "ConstraintFoo" };
+
+ // Act
+ routes.MapRoute("RouteName", "SomeUrl", defaults, constraints);
+
+ // Assert
+ Route route = Assert.Single(routes.Cast<Route>());
+ Assert.NotNull(route);
+ Assert.Same(route, routes["RouteName"]);
+ Assert.Equal("SomeUrl", route.Url);
+ Assert.IsType<MvcRouteHandler>(route.RouteHandler);
+ Assert.Equal("DefaultFoo", route.Defaults["Foo"]);
+ Assert.Equal("ConstraintFoo", route.Constraints["Foo"]);
+ Assert.Empty(route.DataTokens);
+ }
+
+ [Fact]
+ public void MapRoute5WithNullRouteCollectionThrows()
+ {
+ Assert.ThrowsArgumentNull(
+ delegate { RouteCollectionExtensions.MapRoute(null, null, null, null, null); },
+ "routes");
+ }
+
+ [Fact]
+ public void MapRoute5WithNullUrlThrows()
+ {
+ // Arrange
+ RouteCollection routes = new RouteCollection();
+
+ // Act & Assert
+ Assert.ThrowsArgumentNull(
+ delegate { routes.MapRoute(null, null /* url */, null, null); },
+ "url");
+ }
+
+ [Fact]
+ public void IgnoreRoute1WithNullRouteCollectionThrows()
+ {
+ Assert.ThrowsArgumentNull(
+ delegate { RouteCollectionExtensions.IgnoreRoute(null, "foo"); },
+ "routes");
+ }
+
+ [Fact]
+ public void IgnoreRoute1WithNullUrlThrows()
+ {
+ // Arrange
+ RouteCollection routes = new RouteCollection();
+
+ // Act & Assert
+ Assert.ThrowsArgumentNull(
+ delegate { routes.IgnoreRoute(null); },
+ "url");
+ }
+
+ [Fact]
+ public void IgnoreRoute3()
+ {
+ // Arrange
+ RouteCollection routes = new RouteCollection();
+
+ // Act
+ routes.IgnoreRoute("SomeUrl");
+
+ // Assert
+ Route route = Assert.Single(routes.Cast<Route>());
+ Assert.NotNull(route);
+ Assert.Equal("SomeUrl", route.Url);
+ Assert.IsType<StopRoutingHandler>(route.RouteHandler);
+ Assert.Null(route.Defaults);
+ Assert.Empty(route.Constraints);
+ }
+
+ [Fact]
+ public void IgnoreRoute4()
+ {
+ // Arrange
+ RouteCollection routes = new RouteCollection();
+ var constraints = new { Foo = "DefaultFoo" };
+
+ // Act
+ routes.IgnoreRoute("SomeUrl", constraints);
+
+ // Assert
+ Route route = Assert.Single(routes.Cast<Route>());
+ Assert.NotNull(route);
+ Assert.Equal("SomeUrl", route.Url);
+ Assert.IsType<StopRoutingHandler>(route.RouteHandler);
+ Assert.Null(route.Defaults);
+ Assert.Single(route.Constraints);
+ Assert.Equal("DefaultFoo", route.Constraints["Foo"]);
+ }
+
+ [Fact]
+ public void IgnoreRouteInternalNeverMatchesUrlGeneration()
+ {
+ // Arrange
+ RouteCollection routes = new RouteCollection();
+ routes.IgnoreRoute("SomeUrl");
+ Route route = routes[0] as Route;
+
+ // Act
+ VirtualPathData vpd = route.GetVirtualPath(new RequestContext(new Mock<HttpContextBase>().Object, new RouteData()), null);
+
+ // Assert
+ Assert.Null(vpd);
+ }
+
+ private static RequestContext GetRequestContext(string currentAreaName)
+ {
+ Mock<HttpContextBase> mockHttpContext = new Mock<HttpContextBase>();
+ mockHttpContext.Setup(c => c.Request.ApplicationPath).Returns("/app");
+ mockHttpContext.Setup(c => c.Response.ApplyAppPathModifier(It.IsAny<string>())).Returns<string>(virtualPath => virtualPath);
+
+ RouteData routeData = new RouteData();
+ routeData.DataTokens["area"] = currentAreaName;
+ return new RequestContext(mockHttpContext.Object, routeData);
+ }
+
+ private static RouteCollection GetRouteCollection()
+ {
+ RouteCollection routes = new RouteCollection();
+ routes.MapRoute(
+ "Default",
+ "no-area/{controller}/{action}/{id}",
+ new { controller = "Home", action = "Index", id = "" }
+ );
+
+ AreaRegistrationContext blogContext = new AreaRegistrationContext("blog", routes);
+ blogContext.MapRoute(
+ "Blog_WhatsNew",
+ "whats-new/{tag}",
+ new { controller = "Home", action = "TenMostRecent", tag = "" }
+ );
+ blogContext.MapRoute(
+ "Blog_Default",
+ "blog-area/{controller}/{action}/{id}",
+ new { controller = "Home", action = "Index", id = "" }
+ );
+
+ AreaRegistrationContext adminContext = new AreaRegistrationContext("admin", routes);
+ adminContext.MapRoute(
+ "Admin_Default",
+ "admin-area/{controller}/{action}/{id}",
+ new { controller = "Home", action = "Index", id = "" }
+ );
+
+ return routes;
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Test/RouteDataValueProviderFactoryTest.cs b/test/System.Web.Mvc.Test/Test/RouteDataValueProviderFactoryTest.cs
new file mode 100644
index 00000000..dc66d1eb
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Test/RouteDataValueProviderFactoryTest.cs
@@ -0,0 +1,44 @@
+using System.Globalization;
+using System.Web.Routing;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Mvc.Test
+{
+ public class RouteDataValueProviderFactoryTest
+ {
+ [Fact]
+ public void GetValueProvider()
+ {
+ // Arrange
+ RouteDataValueProviderFactory factory = new RouteDataValueProviderFactory();
+
+ ControllerContext controllerContext = new ControllerContext();
+ controllerContext.RouteData = new RouteData();
+ controllerContext.RouteData.Values["forty-two"] = 42;
+
+ // Act
+ IValueProvider valueProvider = factory.GetValueProvider(controllerContext);
+
+ // Assert
+ Assert.IsType<RouteDataValueProvider>(valueProvider);
+ ValueProviderResult vpResult = valueProvider.GetValue("forty-two");
+
+ Assert.NotNull(vpResult);
+ Assert.Equal(42, vpResult.RawValue);
+ Assert.Equal("42", vpResult.AttemptedValue);
+ Assert.Equal(CultureInfo.InvariantCulture, vpResult.Culture);
+ }
+
+ [Fact]
+ public void GetValueProvider_ThrowsIfControllerContextIsNull()
+ {
+ // Arrange
+ RouteDataValueProviderFactory factory = new RouteDataValueProviderFactory();
+
+ // Act & assert
+ Assert.ThrowsArgumentNull(
+ delegate { factory.GetValueProvider(null); }, "controllerContext");
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Test/SelectListTest.cs b/test/System.Web.Mvc.Test/Test/SelectListTest.cs
new file mode 100644
index 00000000..1691291c
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Test/SelectListTest.cs
@@ -0,0 +1,84 @@
+using System.Collections;
+using System.Collections.Generic;
+using System.Linq;
+using Xunit;
+
+namespace System.Web.Mvc.Test
+{
+ public class SelectListTest
+ {
+ [Fact]
+ public void Constructor1SetsProperties()
+ {
+ // Arrange
+ IEnumerable items = new object[0];
+
+ // Act
+ SelectList selectList = new SelectList(items);
+
+ // Assert
+ Assert.Same(items, selectList.Items);
+ Assert.Null(selectList.DataValueField);
+ Assert.Null(selectList.DataTextField);
+ Assert.Null(selectList.SelectedValues);
+ Assert.Null(selectList.SelectedValue);
+ }
+
+ [Fact]
+ public void Constructor2SetsProperties()
+ {
+ // Arrange
+ IEnumerable items = new object[0];
+ object selectedValue = new object();
+
+ // Act
+ SelectList selectList = new SelectList(items, selectedValue);
+ List<object> selectedValues = selectList.SelectedValues.Cast<object>().ToList();
+
+ // Assert
+ Assert.Same(items, selectList.Items);
+ Assert.Null(selectList.DataValueField);
+ Assert.Null(selectList.DataTextField);
+ Assert.Same(selectedValue, selectList.SelectedValue);
+ Assert.Single(selectedValues);
+ Assert.Same(selectedValue, selectedValues[0]);
+ }
+
+ [Fact]
+ public void Constructor3SetsProperties()
+ {
+ // Arrange
+ IEnumerable items = new object[0];
+
+ // Act
+ SelectList selectList = new SelectList(items, "SomeValueField", "SomeTextField");
+
+ // Assert
+ Assert.Same(items, selectList.Items);
+ Assert.Equal("SomeValueField", selectList.DataValueField);
+ Assert.Equal("SomeTextField", selectList.DataTextField);
+ Assert.Null(selectList.SelectedValues);
+ Assert.Null(selectList.SelectedValue);
+ }
+
+ [Fact]
+ public void Constructor4SetsProperties()
+ {
+ // Arrange
+ IEnumerable items = new object[0];
+ object selectedValue = new object();
+
+ // Act
+ SelectList selectList = new SelectList(items, "SomeValueField", "SomeTextField", selectedValue);
+ List<object> selectedValues = selectList.SelectedValues.Cast<object>().ToList();
+
+ // Assert
+ Assert.Same(items, selectList.Items);
+ Assert.Equal("SomeValueField", selectList.DataValueField);
+ Assert.Equal("SomeTextField", selectList.DataTextField);
+ Assert.Same(selectedValue, selectList.SelectedValue);
+ Assert.Single(selectedValues);
+ Assert.Same(selectedValue, selectedValues[0]);
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Test/SessionStateTempDataProviderTest.cs b/test/System.Web.Mvc.Test/Test/SessionStateTempDataProviderTest.cs
new file mode 100644
index 00000000..252ff007
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Test/SessionStateTempDataProviderTest.cs
@@ -0,0 +1,166 @@
+using System.Collections.Generic;
+using Moq;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Mvc.Test
+{
+ public class SessionStateTempDataProviderTest
+ {
+ [Fact]
+ public void Load_NullSession_ReturnsEmptyDictionary()
+ {
+ // Arrange
+ SessionStateTempDataProvider testProvider = new SessionStateTempDataProvider();
+
+ // Act
+ IDictionary<string, object> tempDataDictionary = testProvider.LoadTempData(GetControllerContext());
+
+ // Assert
+ Assert.Empty(tempDataDictionary);
+ }
+
+ [Fact]
+ public void Load_NonNullSession_NoSessionData_ReturnsEmptyDictionary()
+ {
+ // Arrange
+ SessionStateTempDataProvider testProvider = new SessionStateTempDataProvider();
+ Mock<ControllerContext> mockControllerContext = new Mock<ControllerContext>();
+ Mock<HttpSessionStateBase> mockSessionStateBase = new Mock<HttpSessionStateBase>();
+ mockControllerContext.Setup(c => c.HttpContext.Session).Returns(mockSessionStateBase.Object);
+
+ // Act
+ IDictionary<string, object> result = testProvider.LoadTempData(mockControllerContext.Object);
+
+ // Assert
+ Assert.Empty(result);
+ }
+
+ [Fact]
+ public void Load_NonNullSession_IncorrectSessionDataType_ReturnsEmptyDictionary()
+ {
+ // Arrange
+ SessionStateTempDataProvider testProvider = new SessionStateTempDataProvider();
+ Mock<ControllerContext> mockControllerContext = new Mock<ControllerContext>();
+ Mock<HttpSessionStateBase> mockSessionStateBase = new Mock<HttpSessionStateBase>();
+ mockControllerContext.Setup(c => c.HttpContext.Session).Returns(mockSessionStateBase.Object);
+ mockSessionStateBase.Setup(ssb => ssb[SessionStateTempDataProvider.TempDataSessionStateKey]).Returns(42);
+
+ // Act
+ IDictionary<string, object> result = testProvider.LoadTempData(mockControllerContext.Object);
+
+ // Assert
+ Assert.Empty(result);
+ }
+
+ [Fact]
+ public void Load_NonNullSession_CorrectSessionDataType_ReturnsSessionData()
+ {
+ // Arrange
+ SessionStateTempDataProvider testProvider = new SessionStateTempDataProvider();
+ Dictionary<string, object> tempData = new Dictionary<string, object> { { "foo", "bar" } };
+ Mock<ControllerContext> mockControllerContext = new Mock<ControllerContext>();
+ Mock<HttpSessionStateBase> mockSessionStateBase = new Mock<HttpSessionStateBase>();
+ mockControllerContext.Setup(c => c.HttpContext.Session).Returns(mockSessionStateBase.Object);
+ mockSessionStateBase.Setup(ssb => ssb[SessionStateTempDataProvider.TempDataSessionStateKey]).Returns(tempData);
+
+ // Act
+ var result = testProvider.LoadTempData(mockControllerContext.Object);
+
+ // Assert
+ Assert.Same(tempData, result);
+ }
+
+ [Fact]
+ public void Save_NullSession_NullDictionary_DoesNotThrow()
+ {
+ // Arrange
+ SessionStateTempDataProvider testProvider = new SessionStateTempDataProvider();
+
+ // Act
+ testProvider.SaveTempData(GetControllerContext(), null);
+ }
+
+ [Fact]
+ public void Save_NullSession_EmptyDictionary_DoesNotThrow()
+ {
+ // Arrange
+ SessionStateTempDataProvider testProvider = new SessionStateTempDataProvider();
+
+ // Act
+ testProvider.SaveTempData(GetControllerContext(), new Dictionary<string, object>());
+ }
+
+ [Fact]
+ public void Save_NullSession_NonEmptyDictionary_Throws()
+ {
+ // Arrange
+ SessionStateTempDataProvider testProvider = new SessionStateTempDataProvider();
+
+ // Act & Assert
+ Assert.Throws<InvalidOperationException>(
+ delegate { testProvider.SaveTempData(GetControllerContext(), new Dictionary<string, object> { { "foo", "bar" } }); },
+ "The SessionStateTempDataProvider class requires session state to be enabled.");
+ }
+
+ [Fact]
+ public void Save_NonNullSession_TempDataIsDirty_AssignsTempDataDictionaryIntoSession()
+ {
+ // Arrange
+ SessionStateTempDataProvider testProvider = new SessionStateTempDataProvider();
+ Dictionary<string, object> tempData = new Dictionary<string, object> { { "foo", "bar" } };
+ Mock<ControllerContext> mockControllerContext = new Mock<ControllerContext>();
+ Mock<HttpSessionStateBase> mockSessionStateBase = new Mock<HttpSessionStateBase>();
+ mockControllerContext.Setup(c => c.HttpContext.Session).Returns(mockSessionStateBase.Object);
+ mockSessionStateBase.SetupSet(ssb => ssb[SessionStateTempDataProvider.TempDataSessionStateKey] = tempData);
+
+ // Act
+ testProvider.SaveTempData(mockControllerContext.Object, tempData);
+
+ // Assert
+ mockSessionStateBase.VerifyAll();
+ }
+
+ [Fact]
+ public void Save_NonNullSession_TempDataIsNotDirty_KeyDoesNotExistInSession_SessionRemainsUntouched()
+ {
+ // Arrange
+ SessionStateTempDataProvider testProvider = new SessionStateTempDataProvider();
+ Dictionary<string, object> tempData = new Dictionary<string, object>();
+
+ Mock<ControllerContext> mockControllerContext = new Mock<ControllerContext>(MockBehavior.Strict);
+ mockControllerContext.Setup(o => o.HttpContext.Session[SessionStateTempDataProvider.TempDataSessionStateKey]).Returns(null);
+
+ // Act
+ testProvider.SaveTempData(mockControllerContext.Object, tempData);
+
+ // Assert
+ mockControllerContext.Verify();
+ }
+
+ [Fact]
+ public void Save_NonNullSession_TempDataIsNotDirty_KeyExistsInSession_KeyRemovedFromSession()
+ {
+ // Arrange
+ SessionStateTempDataProvider testProvider = new SessionStateTempDataProvider();
+ Dictionary<string, object> tempData = new Dictionary<string, object>();
+
+ Mock<ControllerContext> mockControllerContext = new Mock<ControllerContext>(MockBehavior.Strict);
+ mockControllerContext.Setup(o => o.HttpContext.Session[SessionStateTempDataProvider.TempDataSessionStateKey]).Returns(new object());
+ mockControllerContext.Setup(o => o.HttpContext.Session.Remove(SessionStateTempDataProvider.TempDataSessionStateKey)).Verifiable();
+
+ // Act
+ testProvider.SaveTempData(mockControllerContext.Object, tempData);
+
+ // Assert
+ mockControllerContext.Verify();
+ }
+
+ private static ControllerContext GetControllerContext()
+ {
+ Mock<ControllerContext> mockControllerContext = new Mock<ControllerContext>();
+ mockControllerContext.Setup(c => c.HttpContext.Session).Returns((HttpSessionStateBase)null);
+ return mockControllerContext.Object;
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Test/SingleServiceResolverTest.cs b/test/System.Web.Mvc.Test/Test/SingleServiceResolverTest.cs
new file mode 100644
index 00000000..6c8b6aed
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Test/SingleServiceResolverTest.cs
@@ -0,0 +1,163 @@
+using Moq;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Mvc.Test
+{
+ public class SingleServiceResolverTest
+ {
+ [Fact]
+ public void ConstructorWithNullThunkArgumentThrows()
+ {
+ // Act & Assert
+ Assert.ThrowsArgumentNull(
+ delegate { new SingleServiceResolver<TestProvider>(null, null, "TestProvider.Current"); },
+ "currentValueThunk");
+
+ Assert.ThrowsArgumentNull(
+ delegate { new SingleServiceResolver<TestProvider>(null, null, "TestProvider.Current"); },
+ "currentValueThunk");
+
+ Assert.ThrowsArgumentNull(
+ delegate { new SingleServiceResolver<TestProvider>(() => null, null, "TestProvider.Current"); },
+ "defaultValue");
+ }
+
+ [Fact]
+ public void CurrentConsultsResolver()
+ {
+ // Arrange
+ TestProvider providerFromDefaultValue = new TestProvider();
+ TestProvider providerFromServiceLocation = new TestProvider();
+
+ Mock<IDependencyResolver> resolver = new Mock<IDependencyResolver>();
+ resolver.Setup(r => r.GetService(typeof(TestProvider)))
+ .Returns(providerFromServiceLocation);
+
+ SingleServiceResolver<TestProvider> singleResolver = new SingleServiceResolver<TestProvider>(() => null, providerFromDefaultValue, resolver.Object, "TestProvider.Current");
+
+ // Act
+ TestProvider returnedProvider = singleResolver.Current;
+
+ // Assert
+ Assert.Equal(providerFromServiceLocation, returnedProvider);
+ }
+
+ [Fact]
+ public void CurrentReturnsCurrentProviderNotDefaultIfSet()
+ {
+ // Arrange
+ TestProvider providerFromDefaultValue = new TestProvider();
+ TestProvider providerFromCurrentValueThunk = null;
+ Mock<IDependencyResolver> resolver = new Mock<IDependencyResolver>();
+ SingleServiceResolver<TestProvider> singleResolver = new SingleServiceResolver<TestProvider>(() => providerFromCurrentValueThunk, providerFromDefaultValue, resolver.Object, "TestProvider.Current");
+
+ // Act
+ providerFromCurrentValueThunk = new TestProvider();
+ TestProvider returnedProvider = singleResolver.Current;
+
+ // Assert
+ Assert.Equal(providerFromCurrentValueThunk, returnedProvider);
+ resolver.Verify(r => r.GetService(typeof(TestProvider)));
+ }
+
+ [Fact]
+ public void CurrentCachesResolverResult()
+ {
+ // Arrange
+ TestProvider providerFromDefaultValue = new TestProvider();
+ TestProvider providerFromServiceLocation = new TestProvider();
+
+ Mock<IDependencyResolver> resolver = new Mock<IDependencyResolver>();
+ resolver.Setup(r => r.GetService(typeof(TestProvider)))
+ .Returns(providerFromServiceLocation);
+
+ SingleServiceResolver<TestProvider> singleResolver = new SingleServiceResolver<TestProvider>(() => null, providerFromDefaultValue, resolver.Object, "TestProvider.Current");
+
+ // Act
+ TestProvider returnedProvider = singleResolver.Current;
+ TestProvider cachedProvider = singleResolver.Current;
+
+ // Assert
+ Assert.Equal(providerFromServiceLocation, returnedProvider);
+ Assert.Equal(providerFromServiceLocation, cachedProvider);
+ resolver.Verify(r => r.GetService(typeof(TestProvider)), Times.Exactly(1));
+ }
+
+ [Fact]
+ public void CurrentDoesNotQueryResolverAfterReceivingNull()
+ {
+ // Arrange
+ TestProvider providerFromDefaultValue = new TestProvider();
+ TestProvider providerFromCurrentValueThunk = new TestProvider();
+ Mock<IDependencyResolver> resolver = new Mock<IDependencyResolver>();
+ SingleServiceResolver<TestProvider> singleResolver = new SingleServiceResolver<TestProvider>(() => providerFromCurrentValueThunk, providerFromDefaultValue, resolver.Object, "TestProvider.Current");
+
+ // Act
+ TestProvider returnedProvider = singleResolver.Current;
+ TestProvider cachedProvider = singleResolver.Current;
+
+ // Assert
+ Assert.Equal(providerFromCurrentValueThunk, returnedProvider);
+ Assert.Equal(providerFromCurrentValueThunk, cachedProvider);
+ resolver.Verify(r => r.GetService(typeof(TestProvider)), Times.Exactly(1));
+ }
+
+ [Fact]
+ public void CurrentReturnsDefaultIfCurrentNotSet()
+ {
+ //Arrange
+ TestProvider providerFromDefaultValue = new TestProvider();
+ Mock<IDependencyResolver> resolver = new Mock<IDependencyResolver>();
+ SingleServiceResolver<TestProvider> singleResolver = new SingleServiceResolver<TestProvider>(() => null, providerFromDefaultValue, resolver.Object, "TestProvider.Current");
+
+ //Act
+ TestProvider returnedProvider = singleResolver.Current;
+
+ // Assert
+ Assert.Equal(returnedProvider, providerFromDefaultValue);
+ resolver.Verify(l => l.GetService(typeof(TestProvider)));
+ }
+
+ [Fact]
+ public void CurrentThrowsIfCurrentSetThroughServiceAndSetter()
+ {
+ // Arrange
+ TestProvider providerFromCurrentValueThunk = new TestProvider();
+ TestProvider providerFromServiceLocation = new TestProvider();
+ TestProvider providerFromDefaultValue = new TestProvider();
+ Mock<IDependencyResolver> resolver = new Mock<IDependencyResolver>();
+
+ resolver.Setup(r => r.GetService(typeof(TestProvider)))
+ .Returns(providerFromServiceLocation);
+
+ SingleServiceResolver<TestProvider> singleResolver = new SingleServiceResolver<TestProvider>(() => providerFromCurrentValueThunk, providerFromDefaultValue, resolver.Object, "TestProvider.Current");
+
+ //Act & assert
+ Assert.Throws<InvalidOperationException>(
+ () => singleResolver.Current,
+ "An instance of TestProvider was found in the resolver as well as a custom registered provider in TestProvider.Current. Please set only one or the other."
+ );
+ }
+
+ [Fact]
+ public void CurrentPropagatesExceptionWhenResolverThrowsNonActivationException()
+ {
+ // Arrange
+ TestProvider providerFromDefaultValue = new TestProvider();
+ Mock<IDependencyResolver> resolver = new Mock<IDependencyResolver>(MockBehavior.Strict);
+ SingleServiceResolver<TestProvider> singleResolver = new SingleServiceResolver<TestProvider>(() => null, providerFromDefaultValue, resolver.Object, "TestProvider.Current");
+
+ // Act & Assert
+ Assert.Throws<MockException>(
+ () => singleResolver.Current,
+ @"IDependencyResolver.GetService(System.Web.Mvc.Test.SingleServiceResolverTest+TestProvider) invocation failed with mock behavior Strict.
+All invocations on the mock must have a corresponding setup."
+ );
+ }
+
+ private class TestProvider
+ {
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Test/StringLengthAttributeAdapterTest.cs b/test/System.Web.Mvc.Test/Test/StringLengthAttributeAdapterTest.cs
new file mode 100644
index 00000000..027eab5c
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Test/StringLengthAttributeAdapterTest.cs
@@ -0,0 +1,32 @@
+using System.ComponentModel.DataAnnotations;
+using System.Linq;
+using Xunit;
+
+namespace System.Web.Mvc.Test
+{
+ public class StringLengthAttributeAdapterTest
+ {
+ [Fact]
+ public void ClientRulesWithStringLengthAttribute()
+ {
+ // Arrange
+ var metadata = ModelMetadataProviders.Current.GetMetadataForProperty(() => null, typeof(string), "Length");
+ var context = new ControllerContext();
+ var attribute = new StringLengthAttribute(10) { MinimumLength = 3 };
+ var adapter = new StringLengthAttributeAdapter(metadata, context, attribute);
+
+ // Act
+ var rules = adapter.GetClientValidationRules()
+ .OrderBy(r => r.ValidationType)
+ .ToArray();
+
+ // Assert
+ ModelClientValidationRule rule = Assert.Single(rules);
+ Assert.Equal("length", rule.ValidationType);
+ Assert.Equal(2, rule.ValidationParameters.Count);
+ Assert.Equal(3, rule.ValidationParameters["min"]);
+ Assert.Equal(10, rule.ValidationParameters["max"]);
+ Assert.Equal("The field Length must be a string with a minimum length of 3 and a maximum length of 10.", rule.ErrorMessage);
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Test/TempDataDictionaryTest.cs b/test/System.Web.Mvc.Test/Test/TempDataDictionaryTest.cs
new file mode 100644
index 00000000..78a6404c
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Test/TempDataDictionaryTest.cs
@@ -0,0 +1,340 @@
+using System.Collections;
+using System.Collections.Generic;
+using Moq;
+using Xunit;
+
+namespace System.Web.Mvc.Test
+{
+ public class TempDataDictionaryTest
+ {
+ [Fact]
+ public void CompareIsOrdinalIgnoreCase()
+ {
+ // Arrange
+ TempDataDictionary tempData = new TempDataDictionary();
+ object item = new object();
+
+ // Act
+ tempData["Foo"] = item;
+ object value = tempData["FOO"];
+
+ // Assert
+ Assert.Same(item, value);
+ }
+
+ [Fact]
+ public void EnumeratingDictionaryMarksValuesForDeletion()
+ {
+ // Arrange
+ NullTempDataProvider provider = new NullTempDataProvider();
+ TempDataDictionary tempData = new TempDataDictionary();
+ Mock<ControllerContext> controllerContext = new Mock<ControllerContext>();
+ tempData["Foo"] = "Foo";
+ tempData["Bar"] = "Bar";
+
+ // Act
+ IEnumerator<KeyValuePair<string, object>> enumerator = tempData.GetEnumerator();
+ while (enumerator.MoveNext())
+ {
+ object value = enumerator.Current;
+ }
+ tempData.Save(controllerContext.Object, provider);
+
+ // Assert
+ Assert.False(tempData.ContainsKey("Foo"));
+ Assert.False(tempData.ContainsKey("Bar"));
+ }
+
+ [Fact]
+ public void EnumeratingTempDataAsIEnmerableMarksValuesForDeletion()
+ {
+ // Arrange
+ NullTempDataProvider provider = new NullTempDataProvider();
+ TempDataDictionary tempData = new TempDataDictionary();
+ Mock<ControllerContext> controllerContext = new Mock<ControllerContext>();
+ tempData["Foo"] = "Foo";
+ tempData["Bar"] = "Bar";
+
+ // Act
+ IEnumerator enumerator = ((IEnumerable)tempData).GetEnumerator();
+ while (enumerator.MoveNext())
+ {
+ object value = enumerator.Current;
+ }
+ tempData.Save(controllerContext.Object, provider);
+
+ // Assert
+ Assert.False(tempData.ContainsKey("Foo"));
+ Assert.False(tempData.ContainsKey("Bar"));
+ }
+
+ [Fact]
+ public void KeepRetainsAllKeysWhenSavingDictionary()
+ {
+ // Arrange
+ NullTempDataProvider provider = new NullTempDataProvider();
+ TempDataDictionary tempData = new TempDataDictionary();
+ Mock<ControllerContext> controllerContext = new Mock<ControllerContext>();
+ controllerContext.Setup(c => c.HttpContext.Request).Returns(new Mock<HttpRequestBase>().Object);
+ tempData["Foo"] = "Foo";
+ tempData["Bar"] = "Bar";
+
+ // Act
+ tempData.Keep();
+ tempData.Save(controllerContext.Object, provider);
+
+ // Assert
+ Assert.True(tempData.ContainsKey("Foo"));
+ Assert.True(tempData.ContainsKey("Bar"));
+ }
+
+ [Fact]
+ public void KeepRetainsSpecificKeysWhenSavingDictionary()
+ {
+ // Arrange
+ NullTempDataProvider provider = new NullTempDataProvider();
+ TempDataDictionary tempData = new TempDataDictionary();
+ Mock<ControllerContext> controllerContext = new Mock<ControllerContext>();
+ controllerContext.Setup(c => c.HttpContext.Request).Returns(new Mock<HttpRequestBase>().Object);
+ tempData["Foo"] = "Foo";
+ tempData["Bar"] = "Bar";
+
+ // Act
+ tempData.Keep("Foo");
+ object value = tempData["Bar"];
+ tempData.Save(controllerContext.Object, provider);
+
+ // Assert
+ Assert.True(tempData.ContainsKey("Foo"));
+ Assert.False(tempData.ContainsKey("Bar"));
+ }
+
+ [Fact]
+ public void LoadAndSaveAreCaseInsensitive()
+ {
+ // Arrange
+ Dictionary<string, object> data = new Dictionary<string, object>();
+ data["Foo"] = "Foo";
+ data["Bar"] = "Bar";
+ TestTempDataProvider provider = new TestTempDataProvider(data);
+ Mock<ControllerContext> controllerContext = new Mock<ControllerContext>();
+ TempDataDictionary tempData = new TempDataDictionary();
+
+ // Act
+ tempData.Load(controllerContext.Object, provider);
+ object value = tempData["FOO"];
+ tempData.Save(controllerContext.Object, provider);
+
+ // Assert
+ Assert.False(tempData.ContainsKey("foo"));
+ Assert.True(tempData.ContainsKey("bar"));
+ }
+
+ [Fact]
+ public void PeekDoesNotMarkKeyAsRead()
+ {
+ // Arrange
+ NullTempDataProvider provider = new NullTempDataProvider();
+ TempDataDictionary tempData = new TempDataDictionary();
+ Mock<ControllerContext> controllerContext = new Mock<ControllerContext>();
+ tempData["Bar"] = "barValue";
+
+ // Act
+ object value = tempData.Peek("bar");
+ tempData.Save(controllerContext.Object, provider);
+
+ // Assert
+ Assert.Equal("barValue", value);
+ Assert.True(tempData.ContainsKey("Bar"));
+ }
+
+ [Fact]
+ public void RemovalOfKeysAreCaseInsensitive()
+ {
+ NullTempDataProvider provider = new NullTempDataProvider();
+ TempDataDictionary tempData = new TempDataDictionary();
+ Mock<ControllerContext> controllerContext = new Mock<ControllerContext>();
+ object fooValue;
+ tempData["Foo"] = "Foo";
+ tempData["Bar"] = "Bar";
+
+ // Act
+ tempData.TryGetValue("foo", out fooValue);
+ object barValue = tempData["bar"];
+ tempData.Save(controllerContext.Object, provider);
+
+ // Assert
+ Assert.False(tempData.ContainsKey("Foo"));
+ Assert.False(tempData.ContainsKey("Boo"));
+ }
+
+ [Fact]
+ public void SaveRetainsAllKeys()
+ {
+ // Arrange
+ NullTempDataProvider provider = new NullTempDataProvider();
+ TempDataDictionary tempData = new TempDataDictionary();
+ Mock<ControllerContext> controllerContext = new Mock<ControllerContext>();
+ tempData["Foo"] = "Foo";
+ tempData["Bar"] = "Bar";
+
+ // Act
+ tempData.Save(controllerContext.Object, provider);
+
+ // Assert
+ Assert.True(tempData.ContainsKey("Foo"));
+ Assert.True(tempData.ContainsKey("Bar"));
+ }
+
+ [Fact]
+ public void SaveRemovesKeysThatWereRead()
+ {
+ // Arrange
+ NullTempDataProvider provider = new NullTempDataProvider();
+ TempDataDictionary tempData = new TempDataDictionary();
+ Mock<ControllerContext> controllerContext = new Mock<ControllerContext>();
+ tempData["Foo"] = "Foo";
+ tempData["Bar"] = "Bar";
+
+ // Act
+ object value = tempData["Foo"];
+ tempData.Save(controllerContext.Object, provider);
+
+ // Assert
+ Assert.False(tempData.ContainsKey("Foo"));
+ Assert.True(tempData.ContainsKey("Bar"));
+ }
+
+ [Fact]
+ public void TempDataIsADictionary()
+ {
+ // Arrange
+ TempDataDictionary tempData = new TempDataDictionary();
+
+ // Act
+ tempData["Key1"] = "Value1";
+ tempData.Add("Key2", "Value2");
+ ((ICollection<KeyValuePair<string, object>>)tempData).Add(new KeyValuePair<string, object>("Key3", "Value3"));
+
+ // Assert (IDictionary)
+ Assert.Equal(3, tempData.Count);
+ Assert.True(tempData.Remove("Key1"));
+ Assert.False(tempData.Remove("Key4"));
+ Assert.True(tempData.ContainsValue("Value2"));
+ Assert.False(tempData.ContainsValue("Value1"));
+ Assert.Null(tempData["Key6"]);
+
+ IEnumerator tempDataEnumerator = tempData.GetEnumerator();
+ tempDataEnumerator.Reset();
+ while (tempDataEnumerator.MoveNext())
+ {
+ KeyValuePair<string, object> pair = (KeyValuePair<string, object>)tempDataEnumerator.Current;
+ Assert.True(((ICollection<KeyValuePair<string, object>>)tempData).Contains(pair));
+ }
+
+ // Assert (ICollection)
+ foreach (string key in tempData.Keys)
+ {
+ Assert.True(((ICollection<KeyValuePair<string, object>>)tempData).Contains(new KeyValuePair<string, object>(key, tempData[key])));
+ }
+
+ foreach (string value in tempData.Values)
+ {
+ Assert.True(tempData.ContainsValue(value));
+ }
+
+ foreach (string key in ((IDictionary<string, object>)tempData).Keys)
+ {
+ Assert.True(tempData.ContainsKey(key));
+ }
+
+ foreach (string value in ((IDictionary<string, object>)tempData).Values)
+ {
+ Assert.True(tempData.ContainsValue(value));
+ }
+
+ KeyValuePair<string, object>[] keyValuePairArray = new KeyValuePair<string, object>[tempData.Count];
+ ((ICollection<KeyValuePair<string, object>>)tempData).CopyTo(keyValuePairArray, 0);
+
+ Assert.False(((ICollection<KeyValuePair<string, object>>)tempData).IsReadOnly);
+
+ Assert.False(((ICollection<KeyValuePair<string, object>>)tempData).Remove(new KeyValuePair<string, object>("Key5", "Value5")));
+
+ IEnumerator<KeyValuePair<string, object>> keyValuePairEnumerator = ((ICollection<KeyValuePair<string, object>>)tempData).GetEnumerator();
+ keyValuePairEnumerator.Reset();
+ while (keyValuePairEnumerator.MoveNext())
+ {
+ KeyValuePair<string, object> pair = keyValuePairEnumerator.Current;
+ Assert.True(((ICollection<KeyValuePair<string, object>>)tempData).Contains(pair));
+ }
+
+ // Act
+ tempData.Clear();
+
+ // Assert
+ Assert.Empty(tempData);
+ }
+
+ [Fact]
+ public void TempDataDictionaryCreatesEmptyDictionaryIfProviderReturnsNull()
+ {
+ // Arrange
+ TempDataDictionary tempDataDictionary = new TempDataDictionary();
+ NullTempDataProvider provider = new NullTempDataProvider();
+
+ // Act
+ tempDataDictionary.Load(null /* controllerContext */, provider);
+
+ // Assert
+ Assert.Empty(tempDataDictionary);
+ }
+
+ [Fact]
+ public void TryGetValueMarksKeyForDeletion()
+ {
+ NullTempDataProvider provider = new NullTempDataProvider();
+ TempDataDictionary tempData = new TempDataDictionary();
+ Mock<ControllerContext> controllerContext = new Mock<ControllerContext>();
+ object value;
+ tempData["Foo"] = "Foo";
+
+ // Act
+ tempData.TryGetValue("Foo", out value);
+ tempData.Save(controllerContext.Object, provider);
+
+ // Assert
+ Assert.False(tempData.ContainsKey("Foo"));
+ }
+
+ internal class NullTempDataProvider : ITempDataProvider
+ {
+ public void SaveTempData(ControllerContext controllerContext, IDictionary<string, object> values)
+ {
+ }
+
+ public IDictionary<string, object> LoadTempData(ControllerContext controllerContext)
+ {
+ return null;
+ }
+ }
+
+ internal class TestTempDataProvider : ITempDataProvider
+ {
+ private IDictionary<string, object> _data;
+
+ public TestTempDataProvider(IDictionary<string, object> data)
+ {
+ _data = data;
+ }
+
+ public void SaveTempData(ControllerContext controllerContext, IDictionary<string, object> values)
+ {
+ }
+
+ public IDictionary<string, object> LoadTempData(ControllerContext controllerContext)
+ {
+ return _data;
+ }
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Test/TypeCacheSerializerTest.cs b/test/System.Web.Mvc.Test/Test/TypeCacheSerializerTest.cs
new file mode 100644
index 00000000..d7447021
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Test/TypeCacheSerializerTest.cs
@@ -0,0 +1,165 @@
+using System.Collections.Generic;
+using System.IO;
+using Xunit;
+
+namespace System.Web.Mvc.Test
+{
+ public class TypeCacheSerializerTest
+ {
+ private const string _expectedDeserializationFormat = @"<?xml version=""1.0"" encoding=""utf-16""?>
+<!--This file is automatically generated. Please do not modify the contents of this file.-->
+<typeCache lastModified=""__IGNORED__"" mvcVersionId=""{0}"">
+ <assembly name=""{1}"">
+ <module versionId=""{2}"">
+ <type>System.String</type>
+ <type>System.Object</type>
+ </module>
+ </assembly>
+</typeCache>";
+
+ private static readonly string _mscorlibAsmFullName = typeof(object).Assembly.FullName;
+
+ [Fact]
+ public void DeserializeTypes_ReturnsNullIfModuleVersionIdDoesNotMatch()
+ {
+ // Arrange
+ string expected = String.Format(_expectedDeserializationFormat,
+ GetMvidForType(typeof(TypeCacheSerializer)) /* mvcVersionId */,
+ _mscorlibAsmFullName /* assembly.name */,
+ Guid.Empty /* module.versionId */
+ );
+
+ TypeCacheSerializer serializer = new TypeCacheSerializer();
+ StringReader input = new StringReader(expected);
+
+ // Act
+ List<Type> deserializedTypes = serializer.DeserializeTypes(input);
+
+ // Assert
+ Assert.Null(deserializedTypes);
+ }
+
+ [Fact]
+ public void DeserializeTypes_ReturnsNullIfMvcVersionIdDoesNotMatch()
+ {
+ // Arrange
+ string expected = String.Format(_expectedDeserializationFormat,
+ Guid.Empty /* mvcVersionId */,
+ _mscorlibAsmFullName /* assembly.name */,
+ GetMvidForType(typeof(object)) /* module.versionId */
+ );
+
+ TypeCacheSerializer serializer = new TypeCacheSerializer();
+ StringReader input = new StringReader(expected);
+
+ // Act
+ List<Type> deserializedTypes = serializer.DeserializeTypes(input);
+
+ // Assert
+ Assert.Null(deserializedTypes);
+ }
+
+ [Fact]
+ public void DeserializeTypes_ReturnsNullIfTypeNotFound()
+ {
+ string expectedFormat = @"<?xml version=""1.0"" encoding=""utf-16""?>
+<!--This file is automatically generated. Please do not modify the contents of this file.-->
+<typeCache lastModified=""__IGNORED__"" mvcVersionId=""{0}"">
+ <assembly name=""{1}"">
+ <module versionId=""{2}"">
+ <type>System.String</type>
+ <type>This.Type.Does.Not.Exist</type>
+ </module>
+ </assembly>
+</typeCache>";
+
+ // Arrange
+ string expected = String.Format(expectedFormat,
+ GetMvidForType(typeof(TypeCacheSerializer)) /* mvcVersionId */,
+ _mscorlibAsmFullName /* assembly.name */,
+ GetMvidForType(typeof(object)) /* module.versionId */
+ );
+
+ TypeCacheSerializer serializer = new TypeCacheSerializer();
+ StringReader input = new StringReader(expected);
+
+ // Act
+ List<Type> deserializedTypes = serializer.DeserializeTypes(input);
+
+ // Assert
+ Assert.Null(deserializedTypes);
+ }
+
+ [Fact]
+ public void DeserializeTypes_Success()
+ {
+ // Arrange
+ string expected = String.Format(_expectedDeserializationFormat,
+ GetMvidForType(typeof(TypeCacheSerializer)) /* mvcVersionId */,
+ _mscorlibAsmFullName /* assembly.name */,
+ GetMvidForType(typeof(object)) /* module.versionId */
+ );
+
+ TypeCacheSerializer serializer = new TypeCacheSerializer();
+ StringReader input = new StringReader(expected);
+
+ Type[] expectedTypes = new Type[]
+ {
+ typeof(string),
+ typeof(object)
+ };
+
+ // Act
+ List<Type> deserializedTypes = serializer.DeserializeTypes(input);
+
+ // Assert
+ Assert.Equal(expectedTypes, deserializedTypes.ToArray());
+ }
+
+ [Fact]
+ public void SerializeTypes()
+ {
+ // Arrange
+ DateTime expectedDate = new DateTime(2001, 1, 1, 0, 0, 0, DateTimeKind.Utc); // Jan 1, 2001 midnight UTC
+ string expectedFormat = @"<?xml version=""1.0"" encoding=""utf-16""?>
+<!--This file is automatically generated. Please do not modify the contents of this file.-->
+<typeCache lastModified=""{0}"" mvcVersionId=""{1}"">
+ <assembly name=""{2}"">
+ <module versionId=""{3}"">
+ <type>System.String</type>
+ <type>System.Object</type>
+ </module>
+ </assembly>
+</typeCache>";
+ string expected = String.Format(expectedFormat,
+ expectedDate /* lastModified */,
+ GetMvidForType(typeof(TypeCacheSerializer)) /* mvcVersionId */,
+ _mscorlibAsmFullName /* assembly.name */,
+ GetMvidForType(typeof(object)) /* module.versionId */
+ );
+
+ Type[] typesToSerialize = new Type[]
+ {
+ typeof(string),
+ typeof(object)
+ };
+
+ TypeCacheSerializer serializer = new TypeCacheSerializer();
+ serializer.CurrentDateOverride = expectedDate;
+
+ StringWriter output = new StringWriter();
+
+ // Act
+ serializer.SerializeTypes(typesToSerialize, output);
+ string outputString = output.ToString();
+
+ // Assert
+ Assert.Equal(expected, outputString);
+ }
+
+ private static Guid GetMvidForType(Type type)
+ {
+ return type.Module.ModuleVersionId;
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Test/TypeCacheUtilTest.cs b/test/System.Web.Mvc.Test/Test/TypeCacheUtilTest.cs
new file mode 100644
index 00000000..7d60f20b
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Test/TypeCacheUtilTest.cs
@@ -0,0 +1,149 @@
+using System.Collections.Generic;
+using System.IO;
+using Xunit;
+
+namespace System.Web.Mvc.Test
+{
+ public class TypeCacheUtilTest
+ {
+ [Fact]
+ public void GetFilteredTypesFromAssemblies_FallThrough()
+ {
+ // Arrange
+ Type[] expectedTypes = new Type[]
+ {
+ typeof(TypeCacheValidFoo),
+ typeof(TypeCacheValidBar)
+ };
+
+ string cacheName = "testCache";
+ MockBuildManager buildManager = new MockBuildManager();
+ Predicate<Type> predicate = type => type.IsDefined(typeof(TypeCacheMarkerAttribute), true);
+
+ // Act
+ List<Type> returnedTypes = TypeCacheUtil.GetFilteredTypesFromAssemblies(cacheName, predicate, buildManager);
+
+ // Assert
+ Assert.Equal(expectedTypes, returnedTypes.ToArray());
+
+ MemoryStream cachedStream = buildManager.CachedFileStore[cacheName] as MemoryStream;
+ Assert.NotNull(cachedStream);
+ Assert.NotEqual(0, cachedStream.ToArray().Length);
+ }
+
+ [Fact]
+ public void SaveToCache_ReadFromCache_ReturnsNullIfTypesAreInvalid()
+ {
+ //
+ // SAVING
+ //
+
+ // Arrange
+ Type[] expectedTypes = new Type[]
+ {
+ typeof(object),
+ typeof(string)
+ };
+
+ TypeCacheSerializer serializer = new TypeCacheSerializer();
+ string cacheName = "testCache";
+ MockBuildManager buildManager = new MockBuildManager();
+
+ // Act
+ TypeCacheUtil.SaveTypesToCache(cacheName, expectedTypes, buildManager, serializer);
+
+ // Assert
+ MemoryStream writeStream = buildManager.CachedFileStore[cacheName] as MemoryStream;
+ Assert.NotNull(writeStream);
+
+ byte[] streamContents = writeStream.ToArray();
+ Assert.NotEqual(0, streamContents.Length);
+
+ //
+ // READING
+ //
+
+ // Arrange
+ MemoryStream readStream = new MemoryStream(streamContents);
+ buildManager.CachedFileStore[cacheName] = readStream;
+
+ // Act
+ List<Type> returnedTypes = TypeCacheUtil.ReadTypesFromCache(cacheName, _ => false /* all types are invalid */, buildManager, serializer);
+
+ // Assert
+ Assert.Null(returnedTypes);
+ }
+
+ [Fact]
+ public void SaveToCache_ReadFromCache_Success()
+ {
+ //
+ // SAVING
+ //
+
+ // Arrange
+ Type[] expectedTypes = new Type[]
+ {
+ typeof(object),
+ typeof(string)
+ };
+
+ TypeCacheSerializer serializer = new TypeCacheSerializer();
+ string cacheName = "testCache";
+ MockBuildManager buildManager = new MockBuildManager();
+
+ // Act
+ TypeCacheUtil.SaveTypesToCache(cacheName, expectedTypes, buildManager, serializer);
+
+ // Assert
+ MemoryStream writeStream = buildManager.CachedFileStore[cacheName] as MemoryStream;
+ Assert.NotNull(writeStream);
+
+ byte[] streamContents = writeStream.ToArray();
+ Assert.NotEqual(0, streamContents.Length);
+
+ //
+ // READING
+ //
+
+ // Arrange
+ MemoryStream readStream = new MemoryStream(streamContents);
+ buildManager.CachedFileStore[cacheName] = readStream;
+
+ // Act
+ List<Type> returnedTypes = TypeCacheUtil.ReadTypesFromCache(cacheName, _ => true /* all types are valid */, buildManager, serializer);
+
+ // Assert
+ Assert.Equal(expectedTypes, returnedTypes.ToArray());
+ }
+ }
+
+ public class TypeCacheMarkerAttribute : Attribute
+ {
+ }
+
+ [TypeCacheMarker]
+ public class TypeCacheValidFoo
+ {
+ }
+
+ [TypeCacheMarker]
+ public class TypeCacheValidBar
+ {
+ }
+
+ [TypeCacheMarker]
+ internal class TypeCacheInvalidInternal
+ {
+ }
+
+ [TypeCacheMarker]
+ public abstract class TypeCacheInvalidAbstract
+ {
+ }
+
+ [TypeCacheMarker]
+ public struct TypeCacheInvalidStruct
+ {
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Test/TypeHelpersTest.cs b/test/System.Web.Mvc.Test/Test/TypeHelpersTest.cs
new file mode 100644
index 00000000..9815c9d5
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Test/TypeHelpersTest.cs
@@ -0,0 +1,242 @@
+using System.Collections;
+using System.Collections.Generic;
+using Xunit;
+
+namespace System.Web.Mvc.Test
+{
+ public class TypeHelpersTest
+ {
+ [Fact]
+ public void CreateDelegateBindsInstanceMethod()
+ {
+ // Act
+ string s = "Hello, world!";
+ Func<string, bool> endsWith = TypeHelpers.CreateDelegate<Func<string, bool>>(TypeHelpers.MsCorLibAssembly, "System.String", "EndsWith", s);
+
+ // Assert
+ Assert.NotNull(endsWith);
+ Assert.True(endsWith("world!"));
+ }
+
+ [Fact]
+ public void CreateDelegateBindsStaticMethod()
+ {
+ // Act
+ Func<object, object, string> concat = TypeHelpers.CreateDelegate<Func<object, object, string>>(TypeHelpers.MsCorLibAssembly, "System.String", "Concat", null);
+
+ // Assert
+ Assert.NotNull(concat);
+ Assert.Equal("45", concat(4, 5));
+ }
+
+ [Fact]
+ public void CreateDelegateReturnsNullIfTypeDoesNotExist()
+ {
+ // Act
+ Action d = TypeHelpers.CreateDelegate<Action>(TypeHelpers.MsCorLibAssembly, "System.xyz.TypeDoesNotExist", "SomeMethod", null);
+
+ // Assert
+ Assert.Null(d);
+ }
+
+ [Fact]
+ public void CreateDelegateReturnsNullIfMethodDoesNotExist()
+ {
+ // Act
+ Action d = TypeHelpers.CreateDelegate<Action>(TypeHelpers.MsCorLibAssembly, "System.String", "MethodDoesNotExist", null);
+
+ // Assert
+ Assert.Null(d);
+ }
+
+ [Fact]
+ public void CreateTryGetValueDelegateReturnsNullForNonDictionaries()
+ {
+ // Arrange
+ object notDictionary = "Hello, world";
+
+ // Act
+ TryGetValueDelegate d = TypeHelpers.CreateTryGetValueDelegate(notDictionary.GetType());
+
+ // Assert
+ Assert.Null(d);
+ }
+
+ [Fact]
+ public void CreateTryGetValueDelegateWrapsGenericObjectDictionaries()
+ {
+ // Arrange
+ object dictionary = new Dictionary<object, int>()
+ {
+ { "theKey", 42 }
+ };
+
+ // Act
+ TryGetValueDelegate d = TypeHelpers.CreateTryGetValueDelegate(dictionary.GetType());
+
+ object value;
+ bool found = d(dictionary, "theKey", out value);
+
+ // Assert
+ Assert.True(found);
+ Assert.Equal(42, value);
+ }
+
+ [Fact]
+ public void CreateTryGetValueDelegateWrapsGenericStringDictionaries()
+ {
+ // Arrange
+ object dictionary = new Dictionary<string, int>()
+ {
+ { "theKey", 42 }
+ };
+
+ // Act
+ TryGetValueDelegate d = TypeHelpers.CreateTryGetValueDelegate(dictionary.GetType());
+
+ object value;
+ bool found = d(dictionary, "theKey", out value);
+
+ // Assert
+ Assert.True(found);
+ Assert.Equal(42, value);
+ }
+
+ [Fact]
+ public void CreateTryGetValueDelegateWrapsNonGenericDictionaries()
+ {
+ // Arrange
+ object dictionary = new Hashtable()
+ {
+ { "foo", 42 }
+ };
+
+ // Act
+ TryGetValueDelegate d = TypeHelpers.CreateTryGetValueDelegate(dictionary.GetType());
+
+ object fooValue;
+ bool fooFound = d(dictionary, "foo", out fooValue);
+
+ object barValue;
+ bool barFound = d(dictionary, "bar", out barValue);
+
+ // Assert
+ Assert.True(fooFound);
+ Assert.Equal(42, fooValue);
+ Assert.False(barFound);
+ Assert.Null(barValue);
+ }
+
+ [Fact]
+ public void GetDefaultValue_NullableValueType()
+ {
+ // Act
+ object defaultValue = TypeHelpers.GetDefaultValue(typeof(int?));
+
+ // Assert
+ Assert.Equal(default(int?), defaultValue);
+ }
+
+ [Fact]
+ public void GetDefaultValue_ReferenceType()
+ {
+ // Act
+ object defaultValue = TypeHelpers.GetDefaultValue(typeof(object));
+
+ // Assert
+ Assert.Equal(default(object), defaultValue);
+ }
+
+ [Fact]
+ public void GetDefaultValue_ValueType()
+ {
+ // Act
+ object defaultValue = TypeHelpers.GetDefaultValue(typeof(int));
+
+ // Assert
+ Assert.Equal(default(int), defaultValue);
+ }
+
+ [Fact]
+ public void IsCompatibleObjectReturnsTrueIfTypeIsNotNullableAndValueIsNull()
+ {
+ // Act
+ bool retVal = TypeHelpers.IsCompatibleObject<int>(null);
+
+ // Assert
+ Assert.False(retVal);
+ }
+
+ [Fact]
+ public void IsCompatibleObjectReturnsFalseIfValueIsIncorrectType()
+ {
+ // Arrange
+ object value = new string[] { "Hello", "world" };
+
+ // Act
+ bool retVal = TypeHelpers.IsCompatibleObject<int>(value);
+
+ // Assert
+ Assert.False(retVal);
+ }
+
+ [Fact]
+ public void IsCompatibleObjectReturnsTrueIfTypeIsNullableAndValueIsNull()
+ {
+ // Act
+ bool retVal = TypeHelpers.IsCompatibleObject<int?>(null);
+
+ // Assert
+ Assert.True(retVal);
+ }
+
+ [Fact]
+ public void IsCompatibleObjectReturnsTrueIfValueIsOfCorrectType()
+ {
+ // Arrange
+ object value = new string[] { "Hello", "world" };
+
+ // Act
+ bool retVal = TypeHelpers.IsCompatibleObject<IEnumerable<string>>(value);
+
+ // Assert
+ Assert.True(retVal);
+ }
+
+ [Fact]
+ public void TypeAllowsNullValueReturnsFalseForNonNullableGenericValueType()
+ {
+ Assert.False(TypeHelpers.TypeAllowsNullValue(typeof(KeyValuePair<int, string>)));
+ }
+
+ [Fact]
+ public void TypeAllowsNullValueReturnsFalseForNonNullableGenericValueTypeDefinition()
+ {
+ Assert.False(TypeHelpers.TypeAllowsNullValue(typeof(KeyValuePair<,>)));
+ }
+
+ [Fact]
+ public void TypeAllowsNullValueReturnsFalseForNonNullableValueType()
+ {
+ Assert.False(TypeHelpers.TypeAllowsNullValue(typeof(int)));
+ }
+
+ [Fact]
+ public void TypeAllowsNullValueReturnsTrueForInterfaceType()
+ {
+ Assert.True(TypeHelpers.TypeAllowsNullValue(typeof(IDisposable)));
+ }
+
+ [Fact]
+ public void TypeAllowsNullValueReturnsTrueForNullableType()
+ {
+ Assert.True(TypeHelpers.TypeAllowsNullValue(typeof(int?)));
+ }
+
+ [Fact]
+ public void TypeAllowsNullValueReturnsTrueForReferenceType()
+ {
+ Assert.True(TypeHelpers.TypeAllowsNullValue(typeof(object)));
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Test/UrlHelperTest.cs b/test/System.Web.Mvc.Test/Test/UrlHelperTest.cs
new file mode 100644
index 00000000..b93e1e70
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Test/UrlHelperTest.cs
@@ -0,0 +1,694 @@
+using System.Web.Routing;
+using Microsoft.Web.UnitTestUtil;
+using Moq;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Mvc.Test
+{
+ public class UrlHelperTest
+ {
+ [Fact]
+ public void IsLocalUrl_RejectsNull()
+ {
+ UrlHelper helper = GetUrlHelperForIsLocalUrl();
+
+ Assert.False(helper.IsLocalUrl(null));
+ }
+
+ [Fact]
+ public void IsLocalUrl_RejectsEmptyString()
+ {
+ UrlHelper helper = GetUrlHelperForIsLocalUrl();
+
+ Assert.False(helper.IsLocalUrl(String.Empty));
+ Assert.False(helper.IsLocalUrl(" "));
+ }
+
+ [Fact]
+ public void IsLocalUrl_AcceptsRootedUrls()
+ {
+ UrlHelper helper = GetUrlHelperForIsLocalUrl();
+ Assert.True(helper.IsLocalUrl("/fooo"));
+ Assert.True(helper.IsLocalUrl("/www.hackerz.com"));
+ Assert.True(helper.IsLocalUrl("/"));
+ }
+
+ [Fact]
+ public void IsLocalUrl_AcceptsAppRelativeUrls()
+ {
+ UrlHelper helper = GetUrlHelperForIsLocalUrl();
+ Assert.True(helper.IsLocalUrl("~/"));
+ Assert.True(helper.IsLocalUrl("~/foobar.html"));
+ }
+
+ [Fact]
+ public void IsLocalUrl_RejectsRelativeUrls()
+ {
+ UrlHelper helper = GetUrlHelperForIsLocalUrl();
+ Assert.False(helper.IsLocalUrl("foobar.html"));
+ Assert.False(helper.IsLocalUrl("../foobar.html"));
+ Assert.False(helper.IsLocalUrl("fold/foobar.html"));
+ }
+
+ [Fact]
+ public void IsLocalUrl_RejectValidButUnsafeRelativeUrls()
+ {
+ UrlHelper helper = GetUrlHelperForIsLocalUrl();
+
+ Assert.False(helper.IsLocalUrl("http:/foobar.html"));
+ Assert.False(helper.IsLocalUrl("hTtP:foobar.html"));
+ Assert.False(helper.IsLocalUrl("http:/www.hackerz.com"));
+ Assert.False(helper.IsLocalUrl("HtTpS:/www.hackerz.com"));
+ }
+
+ [Fact]
+ public void IsLocalUrl_RejectsUrlsOnTheSameHost()
+ {
+ UrlHelper helper = GetUrlHelperForIsLocalUrl();
+
+ Assert.False(helper.IsLocalUrl("http://www.mysite.com/appDir/foobar.html"));
+ Assert.False(helper.IsLocalUrl("http://WWW.MYSITE.COM"));
+ }
+
+ [Fact]
+ public void IsLocalUrl_RejectsUrlsOnLocalHost()
+ {
+ UrlHelper helper = GetUrlHelperForIsLocalUrl();
+
+ Assert.False(helper.IsLocalUrl("http://localhost/foobar.html"));
+ Assert.False(helper.IsLocalUrl("http://127.0.0.1/foobar.html"));
+ }
+
+ [Fact]
+ public void IsLocalUrl_RejectsUrlsOnTheSameHostButDifferentScheme()
+ {
+ UrlHelper helper = GetUrlHelperForIsLocalUrl();
+
+ Assert.False(helper.IsLocalUrl("https://www.mysite.com/"));
+ }
+
+ [Fact]
+ public void IsLocalUrl_RejectsUrlsOnDifferentHost()
+ {
+ UrlHelper helper = GetUrlHelperForIsLocalUrl();
+
+ Assert.False(helper.IsLocalUrl("http://www.hackerz.com"));
+ Assert.False(helper.IsLocalUrl("https://www.hackerz.com"));
+ Assert.False(helper.IsLocalUrl("hTtP://www.hackerz.com"));
+ Assert.False(helper.IsLocalUrl("HtTpS://www.hackerz.com"));
+ }
+
+ [Fact]
+ public void IsLocalUrl_RejectsUrlsWithTooManySchemeSeparatorCharacters()
+ {
+ UrlHelper helper = GetUrlHelperForIsLocalUrl();
+
+ Assert.False(helper.IsLocalUrl("http://///www.hackerz.com/foobar.html"));
+ Assert.False(helper.IsLocalUrl("https://///www.hackerz.com/foobar.html"));
+ Assert.False(helper.IsLocalUrl("HtTpS://///www.hackerz.com/foobar.html"));
+
+ Assert.False(helper.IsLocalUrl("http:///www.hackerz.com/foobar.html"));
+ Assert.False(helper.IsLocalUrl("http:////www.hackerz.com/foobar.html"));
+ Assert.False(helper.IsLocalUrl("http://///www.hackerz.com/foobar.html"));
+ }
+
+ [Fact]
+ public void IsLocalUrl_RejectsUrlsWithMissingSchemeName()
+ {
+ UrlHelper helper = GetUrlHelperForIsLocalUrl();
+
+ Assert.False(helper.IsLocalUrl("//www.hackerz.com"));
+ Assert.False(helper.IsLocalUrl("//www.hackerz.com/foobar.html"));
+ Assert.False(helper.IsLocalUrl("///www.hackerz.com"));
+ Assert.False(helper.IsLocalUrl("//////www.hackerz.com"));
+ }
+
+ [Fact]
+ public void IsLocalUrl_RejectsInvalidUrls()
+ {
+ UrlHelper helper = GetUrlHelperForIsLocalUrl();
+
+ Assert.False(helper.IsLocalUrl(@"http:\\www.hackerz.com"));
+ Assert.False(helper.IsLocalUrl(@"http:\\www.hackerz.com\"));
+ }
+
+ [Fact]
+ public void RequestContextProperty()
+ {
+ // Arrange
+ RequestContext requestContext = new RequestContext(new Mock<HttpContextBase>().Object, new RouteData());
+ UrlHelper urlHelper = new UrlHelper(requestContext);
+
+ // Assert
+ Assert.Equal(requestContext, urlHelper.RequestContext);
+ }
+
+ [Fact]
+ public void ConstructorWithNullRequestContextThrows()
+ {
+ // Assert
+ Assert.ThrowsArgumentNull(
+ delegate { new UrlHelper(null); },
+ "requestContext");
+ }
+
+ [Fact]
+ public void ConstructorWithNullRouteCollectionThrows()
+ {
+ // Assert
+ Assert.ThrowsArgumentNull(
+ delegate { new UrlHelper(GetRequestContext(), null); },
+ "routeCollection");
+ }
+
+ [Fact]
+ public void Action()
+ {
+ // Arrange
+ UrlHelper urlHelper = GetUrlHelper();
+
+ // Act
+ string url = urlHelper.Action("newaction");
+
+ // Assert
+ Assert.Equal(MvcHelper.AppPathModifier + "/app/home/newaction", url);
+ }
+
+ [Fact]
+ public void ActionWithControllerName()
+ {
+ // Arrange
+ UrlHelper urlHelper = GetUrlHelper();
+
+ // Act
+ string url = urlHelper.Action("newaction", "home2");
+
+ // Assert
+ Assert.Equal(MvcHelper.AppPathModifier + "/app/home2/newaction", url);
+ }
+
+ [Fact]
+ public void ActionWithControllerNameAndDictionary()
+ {
+ // Arrange
+ UrlHelper urlHelper = GetUrlHelper();
+
+ // Act
+ string url = urlHelper.Action("newaction", "home2", new RouteValueDictionary(new { id = "someid" }));
+
+ // Assert
+ Assert.Equal(MvcHelper.AppPathModifier + "/app/home2/newaction/someid", url);
+ }
+
+ [Fact]
+ public void ActionWithControllerNameAndObjectProperties()
+ {
+ // Arrange
+ UrlHelper urlHelper = GetUrlHelper();
+
+ // Act
+ string url = urlHelper.Action("newaction", "home2", new { id = "someid" });
+
+ // Assert
+ Assert.Equal(MvcHelper.AppPathModifier + "/app/home2/newaction/someid", url);
+ }
+
+ [Fact]
+ public void ActionWithDictionary()
+ {
+ // Arrange
+ UrlHelper urlHelper = GetUrlHelper();
+
+ // Act
+ string url = urlHelper.Action("newaction", new RouteValueDictionary(new { Controller = "home2", id = "someid" }));
+
+ // Assert
+ Assert.Equal(MvcHelper.AppPathModifier + "/app/home2/newaction/someid", url);
+ }
+
+ [Fact]
+ public void ActionWithNullActionName()
+ {
+ // Arrange
+ UrlHelper urlHelper = GetUrlHelper();
+
+ // Act
+ string url = urlHelper.Action(null);
+
+ // Assert
+ Assert.Equal(MvcHelper.AppPathModifier + "/app/home/oldaction", url);
+ }
+
+ [Fact]
+ public void ActionWithNullProtocol()
+ {
+ // Arrange
+ UrlHelper urlHelper = GetUrlHelper();
+
+ // Act
+ string url = urlHelper.Action("newaction", "home2", new { id = "someid" }, null /* protocol */);
+
+ // Assert
+ Assert.Equal(MvcHelper.AppPathModifier + "/app/home2/newaction/someid", url);
+ }
+
+ [Fact]
+ public void ActionParameterOverridesObjectProperties()
+ {
+ // Arrange
+ UrlHelper urlHelper = GetUrlHelper();
+
+ // Act
+ string url = urlHelper.Action("newaction", new { Action = "action" });
+
+ // Assert
+ Assert.Equal(MvcHelper.AppPathModifier + "/app/home/newaction", url);
+ }
+
+ [Fact]
+ public void ActionWithObjectProperties()
+ {
+ // Arrange
+ UrlHelper urlHelper = GetUrlHelper();
+
+ // Act
+ string url = urlHelper.Action("newaction", new { Controller = "home2", id = "someid" });
+
+ // Assert
+ Assert.Equal(MvcHelper.AppPathModifier + "/app/home2/newaction/someid", url);
+ }
+
+ [Fact]
+ public void ActionWithProtocol()
+ {
+ // Arrange
+ UrlHelper urlHelper = GetUrlHelper();
+
+ // Act
+ string url = urlHelper.Action("newaction", "home2", new { id = "someid" }, "https");
+
+ // Assert
+ Assert.Equal("https://localhost" + MvcHelper.AppPathModifier + "/app/home2/newaction/someid", url);
+ }
+
+ [Fact]
+ public void ContentWithAbsolutePath()
+ {
+ // Arrange
+ UrlHelper urlHelper = GetUrlHelper();
+
+ // Act
+ string url = urlHelper.Content("/Content/Image.jpg");
+
+ // Assert
+ Assert.Equal("/Content/Image.jpg", url);
+ }
+
+ [Fact]
+ public void ContentWithAppRelativePath()
+ {
+ // Arrange
+ UrlHelper urlHelper = GetUrlHelper();
+
+ // Act
+ string url = urlHelper.Content("~/Content/Image.jpg");
+
+ // Assert
+ Assert.Equal(MvcHelper.AppPathModifier + "/app/Content/Image.jpg", url);
+ }
+
+ [Fact]
+ public void ContentWithEmptyPathThrows()
+ {
+ // Arrange
+ UrlHelper urlHelper = GetUrlHelper();
+
+ // Act & Assert
+ Assert.ThrowsArgumentNullOrEmpty(
+ delegate() { urlHelper.Content(String.Empty); },
+ "contentPath");
+ }
+
+ [Fact]
+ public void ContentWithRelativePath()
+ {
+ // Arrange
+ UrlHelper urlHelper = GetUrlHelper();
+
+ // Act
+ string url = urlHelper.Content("Content/Image.jpg");
+
+ // Assert
+ Assert.Equal("Content/Image.jpg", url);
+ }
+
+ [Fact]
+ public void ContentWithExternalUrl()
+ {
+ // Arrange
+ UrlHelper urlHelper = GetUrlHelper();
+
+ // Act
+ string url = urlHelper.Content("http://www.asp.net/App_Themes/Standard/i/logo.png");
+
+ // Assert
+ Assert.Equal("http://www.asp.net/App_Themes/Standard/i/logo.png", url);
+ }
+
+ [Fact]
+ public void Encode()
+ {
+ // Arrange
+ UrlHelper urlHelper = GetUrlHelper();
+
+ // Act
+ string encodedUrl = urlHelper.Encode(@"SomeUrl /+\");
+
+ // Assert
+ Assert.Equal(encodedUrl, "SomeUrl+%2f%2b%5c");
+ }
+
+ [Fact]
+ public void GenerateContentUrlWithNullContentPathThrows()
+ {
+ // Act & Assert
+ Assert.ThrowsArgumentNullOrEmpty(
+ delegate() { UrlHelper.GenerateContentUrl(null, null); },
+ "contentPath");
+ }
+
+ [Fact]
+ public void GenerateContentUrlWithNullContextThrows()
+ {
+ // Act & Assert
+ Assert.ThrowsArgumentNull(
+ delegate() { UrlHelper.GenerateContentUrl("Content/foo.png", null); },
+ "httpContext");
+ }
+
+ [Fact]
+ public void GenerateUrlWithNullRequestContextThrows()
+ {
+ // Act & Assert
+ Assert.ThrowsArgumentNull(
+ delegate() { UrlHelper.GenerateUrl(null /* routeName */, null /* actionName */, null /* controllerName */, null /* routeValues */, new RouteCollection(), null /* requestContext */, false); },
+ "requestContext");
+ }
+
+ [Fact]
+ public void GenerateUrlWithNullRouteCollectionThrows()
+ {
+ // Act & Assert
+ Assert.ThrowsArgumentNull(
+ delegate() { UrlHelper.GenerateUrl(null /* routeName */, null /* actionName */, null /* controllerName */, null /* routeValues */, null /* routeCollection */, null /* requestContext */, false); },
+ "routeCollection");
+ }
+
+ [Fact]
+ public void GenerateUrlWithEmptyCollectionsReturnsNull()
+ {
+ // Arrange
+ RequestContext requestContext = GetRequestContext();
+
+ // Act
+ string url = UrlHelper.GenerateUrl(null, null, null, null, new RouteCollection(), requestContext, true);
+
+ // Assert
+ Assert.Null(url);
+ }
+
+ [Fact]
+ public void GenerateUrlWithAction()
+ {
+ // Arrange
+ RequestContext requestContext = GetRequestContext(GetRouteData());
+
+ // Act
+ string url = UrlHelper.GenerateUrl(null, "newaction", null, null, GetRouteCollection(), requestContext, true);
+
+ // Assert
+ Assert.Equal(MvcHelper.AppPathModifier + "/app/home/newaction", url);
+ }
+
+ [Fact]
+ public void GenerateUrlWithActionAndController()
+ {
+ // Arrange
+ RequestContext requestContext = GetRequestContext(GetRouteData());
+
+ // Act
+ string url = UrlHelper.GenerateUrl(null, "newaction", "newcontroller", null, GetRouteCollection(), requestContext, true);
+
+ // Assert
+ Assert.Equal(MvcHelper.AppPathModifier + "/app/newcontroller/newaction", url);
+ }
+
+ [Fact]
+ public void GenerateUrlWithImplicitValues()
+ {
+ // Arrange
+ RequestContext requestContext = GetRequestContext(GetRouteData());
+
+ // Act
+ string url = UrlHelper.GenerateUrl(null, null, null, null, GetRouteCollection(), requestContext, true);
+
+ // Assert
+ Assert.Equal(MvcHelper.AppPathModifier + "/app/home/oldaction", url);
+ }
+
+ [Fact]
+ public void RouteUrlCanUseNamedRouteWithoutSpecifyingDefaults()
+ {
+ // DevDiv 217072: Non-mvc specific helpers should not give default values for controller and action
+
+ // Arrange
+ UrlHelper urlHelper = GetUrlHelper();
+ urlHelper.RouteCollection.MapRoute("MyRouteName", "any/url", new { controller = "Charlie" });
+
+ // Act
+ string result = urlHelper.RouteUrl("MyRouteName");
+
+ // Assert
+ Assert.Equal(MvcHelper.AppPathModifier + "/app/any/url", result);
+ }
+
+ [Fact]
+ public void RouteUrlWithDictionary()
+ {
+ // Arrange
+ UrlHelper urlHelper = GetUrlHelper();
+
+ // Act
+ string url = urlHelper.RouteUrl(new RouteValueDictionary(new { Action = "newaction", Controller = "home2", id = "someid" }));
+
+ // Assert
+ Assert.Equal(MvcHelper.AppPathModifier + "/app/home2/newaction/someid", url);
+ }
+
+ [Fact]
+ public void RouteUrlWithEmptyHostName()
+ {
+ // Arrange
+ UrlHelper urlHelper = GetUrlHelper();
+
+ // Act
+ string url = urlHelper.RouteUrl("namedroute", new RouteValueDictionary(new { Action = "newaction", Controller = "home2", id = "someid" }), "http", String.Empty /* hostName */);
+
+ // Assert
+ Assert.Equal("http://localhost" + MvcHelper.AppPathModifier + "/app/named/home2/newaction/someid", url);
+ }
+
+ [Fact]
+ public void RouteUrlWithEmptyProtocol()
+ {
+ // Arrange
+ UrlHelper urlHelper = GetUrlHelper();
+
+ // Act
+ string url = urlHelper.RouteUrl("namedroute", new RouteValueDictionary(new { Action = "newaction", Controller = "home2", id = "someid" }), String.Empty /* protocol */, "foo.bar.com");
+
+ // Assert
+ Assert.Equal("http://foo.bar.com" + MvcHelper.AppPathModifier + "/app/named/home2/newaction/someid", url);
+ }
+
+ [Fact]
+ public void RouteUrlWithNullProtocol()
+ {
+ // Arrange
+ UrlHelper urlHelper = GetUrlHelper();
+
+ // Act
+ string url = urlHelper.RouteUrl("namedroute", new RouteValueDictionary(new { Action = "newaction", Controller = "home2", id = "someid" }), null /* protocol */, "foo.bar.com");
+
+ // Assert
+ Assert.Equal("http://foo.bar.com" + MvcHelper.AppPathModifier + "/app/named/home2/newaction/someid", url);
+ }
+
+ [Fact]
+ public void RouteUrlWithNullProtocolAndNullHostName()
+ {
+ // Arrange
+ UrlHelper urlHelper = GetUrlHelper();
+
+ // Act
+ string url = urlHelper.RouteUrl("namedroute", new RouteValueDictionary(new { Action = "newaction", Controller = "home2", id = "someid" }), null /* protocol */, null /* hostName */);
+
+ // Assert
+ Assert.Equal(MvcHelper.AppPathModifier + "/app/named/home2/newaction/someid", url);
+ }
+
+ [Fact]
+ public void RouteUrlWithObjectProperties()
+ {
+ // Arrange
+ UrlHelper urlHelper = GetUrlHelper();
+
+ // Act
+ string url = urlHelper.RouteUrl(new { Action = "newaction", Controller = "home2", id = "someid" });
+
+ // Assert
+ Assert.Equal(MvcHelper.AppPathModifier + "/app/home2/newaction/someid", url);
+ }
+
+ [Fact]
+ public void RouteUrlWithProtocol()
+ {
+ // Arrange
+ UrlHelper urlHelper = GetUrlHelper();
+
+ // Act
+ string url = urlHelper.RouteUrl("namedroute", new { Action = "newaction", Controller = "home2", id = "someid" }, "https");
+
+ // Assert
+ Assert.Equal("https://localhost" + MvcHelper.AppPathModifier + "/app/named/home2/newaction/someid", url);
+ }
+
+ [Fact]
+ public void RouteUrlWithRouteNameAndDefaults()
+ {
+ // Arrange
+ UrlHelper urlHelper = GetUrlHelper();
+
+ // Act
+ string url = urlHelper.RouteUrl("namedroute");
+
+ // Assert
+ Assert.Equal(MvcHelper.AppPathModifier + "/app/named/home/oldaction", url);
+ }
+
+ [Fact]
+ public void RouteUrlWithRouteNameAndDictionary()
+ {
+ // Arrange
+ UrlHelper urlHelper = GetUrlHelper();
+
+ // Act
+ string url = urlHelper.RouteUrl("namedroute", new RouteValueDictionary(new { Action = "newaction", Controller = "home2", id = "someid" }));
+
+ // Assert
+ Assert.Equal(MvcHelper.AppPathModifier + "/app/named/home2/newaction/someid", url);
+ }
+
+ [Fact]
+ public void RouteUrlWithRouteNameAndObjectProperties()
+ {
+ // Arrange
+ UrlHelper urlHelper = GetUrlHelper();
+
+ // Act
+ string url = urlHelper.RouteUrl("namedroute", new { Action = "newaction", Controller = "home2", id = "someid" });
+
+ // Assert
+ Assert.Equal(MvcHelper.AppPathModifier + "/app/named/home2/newaction/someid", url);
+ }
+
+ [Fact]
+ public void UrlGenerationDoesNotChangeProvidedDictionary()
+ {
+ // Arrange
+ UrlHelper urlHelper = GetUrlHelper();
+ RouteValueDictionary valuesDictionary = new RouteValueDictionary();
+
+ // Act
+ urlHelper.Action("actionName", valuesDictionary);
+
+ // Assert
+ Assert.Empty(valuesDictionary);
+ Assert.False(valuesDictionary.ContainsKey("action"));
+ }
+
+ [Fact]
+ public void UrlGenerationReturnsNullWhenSubsequentSegmentHasValue()
+ { // Dev10 Bug #924729
+ // Arrange
+ RouteCollection routes = new RouteCollection();
+ routes.MapRoute("SampleRoute", "testing/{a}/{b}/{c}",
+ new
+ {
+ controller = "controller",
+ action = "action",
+ b = UrlParameter.Optional,
+ c = UrlParameter.Optional
+ });
+
+ UrlHelper helper = GetUrlHelper(routeCollection: routes);
+
+ // Act
+ string url = helper.Action("action", "controller", new { a = 42, c = 2112 });
+
+ // Assert
+ Assert.Null(url);
+ }
+
+ private static RequestContext GetRequestContext()
+ {
+ HttpContextBase httpcontext = MvcHelper.GetHttpContext("/app/", null, null);
+ RouteData rd = new RouteData();
+ return new RequestContext(httpcontext, rd);
+ }
+
+ private static RequestContext GetRequestContext(RouteData routeData)
+ {
+ HttpContextBase httpcontext = MvcHelper.GetHttpContext("/app/", null, null);
+ return new RequestContext(httpcontext, routeData);
+ }
+
+ private static RouteCollection GetRouteCollection()
+ {
+ RouteCollection rt = new RouteCollection();
+ rt.Add(new Route("{controller}/{action}/{id}", null) { Defaults = new RouteValueDictionary(new { id = "defaultid" }) });
+ rt.Add("namedroute", new Route("named/{controller}/{action}/{id}", null) { Defaults = new RouteValueDictionary(new { id = "defaultid" }) });
+ return rt;
+ }
+
+ private static RouteData GetRouteData()
+ {
+ RouteData rd = new RouteData();
+ rd.Values.Add("controller", "home");
+ rd.Values.Add("action", "oldaction");
+ return rd;
+ }
+
+ private static UrlHelper GetUrlHelper()
+ {
+ return GetUrlHelper(GetRouteData(), GetRouteCollection());
+ }
+
+ private static UrlHelper GetUrlHelper(RouteData routeData = null, RouteCollection routeCollection = null)
+ {
+ HttpContextBase httpcontext = MvcHelper.GetHttpContext("/app/", null, null);
+ UrlHelper urlHelper = new UrlHelper(new RequestContext(httpcontext, routeData ?? new RouteData()), routeCollection ?? new RouteCollection());
+ return urlHelper;
+ }
+
+ private static UrlHelper GetUrlHelperForIsLocalUrl()
+ {
+ Mock<HttpContextBase> contextMock = new Mock<HttpContextBase>();
+ contextMock.SetupGet(context => context.Request.Url).Returns(new Uri("http://www.mysite.com/"));
+ RequestContext requestContext = new RequestContext(contextMock.Object, new RouteData());
+ UrlHelper helper = new UrlHelper(requestContext);
+ return helper;
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Test/UrlParameterTest.cs b/test/System.Web.Mvc.Test/Test/UrlParameterTest.cs
new file mode 100644
index 00000000..89f8e8df
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Test/UrlParameterTest.cs
@@ -0,0 +1,14 @@
+using Xunit;
+
+namespace System.Web.Mvc.Test
+{
+ public class UrlParameterTest
+ {
+ [Fact]
+ public void UrlParameterOptionalToStringReturnsEmptyString()
+ {
+ // Act & Assert
+ Assert.Empty(UrlParameter.Optional.ToString());
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Test/UrlRewriterHelperTest.cs b/test/System.Web.Mvc.Test/Test/UrlRewriterHelperTest.cs
new file mode 100644
index 00000000..8c8f8740
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Test/UrlRewriterHelperTest.cs
@@ -0,0 +1,83 @@
+using System.Collections.Specialized;
+using Moq;
+using Xunit;
+
+namespace System.Web.Mvc.Test
+{
+ public class UrlRewriterHelperTest
+ {
+ private const string _urlWasRewrittenServerVar = "IIS_WasUrlRewritten";
+ private const string _urlRewriterEnabledServerVar = "IIS_UrlRewriteModule";
+
+ [Fact]
+ public void WasRequestRewritten_FalseIfUrlRewriterIsTurnedOff()
+ {
+ // Arrange
+ UrlRewriterHelper helper = new UrlRewriterHelper();
+ Mock<HttpContextBase> requestMock = new Mock<HttpContextBase>();
+ requestMock.Setup(c => c.Request.ServerVariables.Get(_urlRewriterEnabledServerVar)).Returns((string)null).Verifiable();
+
+ // Act
+ bool result = helper.WasRequestRewritten(requestMock.Object);
+
+ // Assert
+ Assert.False(result);
+ requestMock.Verify();
+ requestMock.Verify(c => c.Request.ServerVariables.Get(_urlWasRewrittenServerVar), Times.Never());
+ }
+
+ [Fact]
+ public void WasRequestRewritten_FalseIfUrlRewriterIsTurnedOnButRequestWasNotRewritten()
+ {
+ // Arrange
+ UrlRewriterHelper helper = new UrlRewriterHelper();
+ Mock<HttpContextBase> requestMock = new Mock<HttpContextBase>();
+ requestMock.Setup(c => c.Request.ServerVariables.Get(_urlRewriterEnabledServerVar)).Returns("yes").Verifiable();
+ requestMock.Setup(c => c.Request.ServerVariables.Get(_urlWasRewrittenServerVar)).Returns((string)null).Verifiable();
+
+ // Act
+ bool result = helper.WasRequestRewritten(requestMock.Object);
+
+ // Assert
+ Assert.False(result);
+ requestMock.Verify();
+ }
+
+ [Fact]
+ public void WasRequestRewritten_TrueIfUrlRewriterIsTurnedOnAndRequestWasRewritten()
+ {
+ // Arrange
+ UrlRewriterHelper helper = new UrlRewriterHelper();
+ Mock<HttpContextBase> requestMock = new Mock<HttpContextBase>();
+ requestMock.Setup(c => c.Request.ServerVariables.Get(_urlRewriterEnabledServerVar)).Returns("yes").Verifiable();
+ requestMock.Setup(c => c.Request.ServerVariables.Get(_urlWasRewrittenServerVar)).Returns("yes").Verifiable();
+
+ // Act
+ bool result = helper.WasRequestRewritten(requestMock.Object);
+
+ // Assert
+ Assert.True(result);
+ requestMock.Verify();
+ }
+
+ [Fact]
+ public void WasRequestRewritten_ChecksIfUrlRewriterIsTurnedOnOnlyOnce()
+ {
+ // Arrange
+ UrlRewriterHelper helper = new UrlRewriterHelper();
+ Mock<HttpContextBase> request1Mock = new Mock<HttpContextBase>();
+ request1Mock.Setup(c => c.Request.ServerVariables).Returns(new NameValueCollection());
+ Mock<HttpContextBase> request2Mock = new Mock<HttpContextBase>();
+
+ // Act
+ bool result1 = helper.WasRequestRewritten(request1Mock.Object);
+ bool result2 = helper.WasRequestRewritten(request2Mock.Object);
+
+ // Assert
+ request1Mock.Verify(c => c.Request.ServerVariables, Times.Once());
+ request2Mock.Verify(c => c.Request.ServerVariables, Times.Never());
+ Assert.False(result1);
+ Assert.False(result2);
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Test/ValidatableObjectAdapterTest.cs b/test/System.Web.Mvc.Test/Test/ValidatableObjectAdapterTest.cs
new file mode 100644
index 00000000..4941de2f
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Test/ValidatableObjectAdapterTest.cs
@@ -0,0 +1,172 @@
+using System.Collections.Generic;
+using System.ComponentModel.DataAnnotations;
+using System.Linq;
+using Moq;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Mvc.Test
+{
+ public class ValidatableObjectAdapterTest
+ {
+ // IValidatableObject support
+
+ [Fact]
+ public void NonIValidatableObjectInsideMetadataThrows()
+ {
+ // Arrange
+ var context = new ControllerContext();
+ var validatable = new Mock<IValidatableObject>();
+ var metadata = ModelMetadataProviders.Current.GetMetadataForType(() => 42, typeof(IValidatableObject));
+ var validator = new ValidatableObjectAdapter(metadata, context);
+
+ // Act & Assert
+ Assert.Throws<InvalidOperationException>(
+ () => validator.Validate(null),
+ "The model object inside the metadata claimed to be compatible with System.ComponentModel.DataAnnotations.IValidatableObject, but was actually System.Int32.");
+ }
+
+ [Fact]
+ public void IValidatableObjectGetsAProperlyPopulatedValidationContext()
+ {
+ // Arrange
+ var context = new ControllerContext();
+ var validatable = new Mock<IValidatableObject>();
+ var metadata = ModelMetadataProviders.Current.GetMetadataForType(() => validatable.Object, validatable.Object.GetType());
+ var validator = new ValidatableObjectAdapter(metadata, context);
+ ValidationContext validationContext = null;
+ validatable.Setup(vo => vo.Validate(It.IsAny<ValidationContext>()))
+ .Callback<ValidationContext>(vc => validationContext = vc)
+ .Returns(Enumerable.Empty<ValidationResult>())
+ .Verifiable();
+
+ // Act
+ validator.Validate(null);
+
+ // Assert
+ validatable.Verify();
+ Assert.Same(validatable.Object, validationContext.ObjectInstance);
+ Assert.Null(validationContext.MemberName);
+ }
+
+ [Fact]
+ public void IValidatableObjectWithNoErrors()
+ {
+ // Arrange
+ var context = new ControllerContext();
+ var validatable = new Mock<IValidatableObject>();
+ var metadata = ModelMetadataProviders.Current.GetMetadataForType(() => validatable.Object, validatable.Object.GetType());
+ var validator = new ValidatableObjectAdapter(metadata, context);
+ validatable.Setup(vo => vo.Validate(It.IsAny<ValidationContext>()))
+ .Returns(Enumerable.Empty<ValidationResult>());
+
+ // Act
+ IEnumerable<ModelValidationResult> results = validator.Validate(null);
+
+ // Assert
+ Assert.Empty(results);
+ }
+
+ [Fact]
+ public void IValidatableObjectWithModelLevelError()
+ {
+ // Arrange
+ var context = new ControllerContext();
+ var validatable = new Mock<IValidatableObject>();
+ var metadata = ModelMetadataProviders.Current.GetMetadataForType(() => validatable.Object, validatable.Object.GetType());
+ var validator = new ValidatableObjectAdapter(metadata, context);
+ validatable.Setup(vo => vo.Validate(It.IsAny<ValidationContext>()))
+ .Returns(new ValidationResult[] { new ValidationResult("Error Message") });
+
+ // Act
+ ModelValidationResult result = validator.Validate(null).Single();
+
+ // Assert
+ Assert.Equal("Error Message", result.Message);
+ Assert.Equal(String.Empty, result.MemberName);
+ }
+
+ [Fact]
+ public void IValidatableObjectWithMultipleModelLevelErrors()
+ {
+ // Arrange
+ var context = new ControllerContext();
+ var validatable = new Mock<IValidatableObject>();
+ var metadata = ModelMetadataProviders.Current.GetMetadataForType(() => validatable.Object, validatable.Object.GetType());
+ var validator = new ValidatableObjectAdapter(metadata, context);
+ validatable.Setup(vo => vo.Validate(It.IsAny<ValidationContext>()))
+ .Returns(new ValidationResult[]
+ {
+ new ValidationResult("Error Message 1"),
+ new ValidationResult("Error Message 2")
+ });
+
+ // Act
+ ModelValidationResult[] results = validator.Validate(null).ToArray();
+
+ // Assert
+ Assert.Equal(2, results.Length);
+ Assert.Equal("Error Message 1", results[0].Message);
+ Assert.Equal("Error Message 2", results[1].Message);
+ }
+
+ [Fact]
+ public void IValidatableObjectWithMultiPropertyValidationFailure()
+ {
+ // Arrange
+ var context = new ControllerContext();
+ var validatable = new Mock<IValidatableObject>();
+ var metadata = ModelMetadataProviders.Current.GetMetadataForType(() => validatable.Object, validatable.Object.GetType());
+ var validator = new ValidatableObjectAdapter(metadata, context);
+ validatable.Setup(vo => vo.Validate(It.IsAny<ValidationContext>()))
+ .Returns(new[] { new ValidationResult("Error Message", new[] { "Property1", "Property2" }) })
+ .Verifiable();
+
+ // Act
+ ModelValidationResult[] results = validator.Validate(null).ToArray();
+
+ // Assert
+ validatable.Verify();
+ Assert.Equal(2, results.Length);
+ Assert.Equal("Error Message", results[0].Message);
+ Assert.Equal("Property1", results[0].MemberName);
+ Assert.Equal("Error Message", results[1].Message);
+ Assert.Equal("Property2", results[1].MemberName);
+ }
+
+ [Fact]
+ public void IValidatableObjectWhichIsNullReturnsNoErrors()
+ {
+ // Arrange
+ var context = new ControllerContext();
+ var metadata = ModelMetadataProviders.Current.GetMetadataForType(() => null, typeof(IValidatableObject));
+ var validator = new ValidatableObjectAdapter(metadata, context);
+
+ // Act
+ IEnumerable<ModelValidationResult> results = validator.Validate(null);
+
+ // Assert
+ Assert.Empty(results);
+ }
+
+ [Fact]
+ public void IValidatableObjectWhichReturnsValidationResultSuccessReturnsNoErrors()
+ {
+ // Arrange
+ var context = new ControllerContext();
+ var validatable = new Mock<IValidatableObject>();
+ var metadata = ModelMetadataProviders.Current.GetMetadataForType(() => validatable.Object, validatable.Object.GetType());
+ var validator = new ValidatableObjectAdapter(metadata, context);
+ validatable.Setup(vo => vo.Validate(It.IsAny<ValidationContext>()))
+ .Returns(new[] { ValidationResult.Success })
+ .Verifiable();
+
+ // Act
+ ModelValidationResult[] results = validator.Validate(null).ToArray();
+
+ // Assert
+ validatable.Verify();
+ Assert.Empty(results);
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Test/ValidateAntiForgeryTokenAttributeTest.cs b/test/System.Web.Mvc.Test/Test/ValidateAntiForgeryTokenAttributeTest.cs
new file mode 100644
index 00000000..0b9464cf
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Test/ValidateAntiForgeryTokenAttributeTest.cs
@@ -0,0 +1,68 @@
+using System.Web.Helpers;
+using System.Web.TestUtil;
+using Moq;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Mvc.Test
+{
+ public class ValidateAntiForgeryTokenAttributeTest
+ {
+ [Fact]
+ public void OnAuthorization_ThrowsIfFilterContextIsNull()
+ {
+ // Arrange
+ ValidateAntiForgeryTokenAttribute attribute = new ValidateAntiForgeryTokenAttribute();
+
+ // Act & Assert
+ Assert.ThrowsArgumentNull(
+ delegate { attribute.OnAuthorization(null); }, "filterContext");
+ }
+
+ [Fact]
+ public void OnAuthorization_ForwardsAttributes()
+ {
+ // Arrange
+ HttpContextBase context = new Mock<HttpContextBase>().Object;
+ Mock<AuthorizationContext> authorizationContextMock = new Mock<AuthorizationContext>();
+ authorizationContextMock.SetupGet(ac => ac.HttpContext).Returns(context);
+ bool validateCalled = false;
+ Action<HttpContextBase, string> validateMethod = (c, s) =>
+ {
+ Assert.Same(context, c);
+ Assert.Equal("some salt", s);
+ validateCalled = true;
+ };
+ ValidateAntiForgeryTokenAttribute attribute = new ValidateAntiForgeryTokenAttribute(validateMethod)
+ {
+ Salt = "some salt"
+ };
+
+ // Act
+ attribute.OnAuthorization(authorizationContextMock.Object);
+
+ // Assert
+ Assert.True(validateCalled);
+ }
+
+ [Fact]
+ public void SaltProperty()
+ {
+ // Arrange
+ ValidateAntiForgeryTokenAttribute attribute = new ValidateAntiForgeryTokenAttribute();
+
+ // Act & Assert
+ MemberHelper.TestStringProperty(attribute, "Salt", String.Empty);
+ }
+
+ [Fact]
+ public void ValidateThunk_DefaultsToAntiForgeryMethod()
+ {
+ // Arrange
+ ValidateAntiForgeryTokenAttribute attribute = new ValidateAntiForgeryTokenAttribute();
+
+ // Act & Assert
+ Assert.Equal(AntiForgery.Validate, attribute.ValidateAction);
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Test/ValidateInputAttributeTest.cs b/test/System.Web.Mvc.Test/Test/ValidateInputAttributeTest.cs
new file mode 100644
index 00000000..0f94efc6
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Test/ValidateInputAttributeTest.cs
@@ -0,0 +1,73 @@
+using Moq;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Mvc.Test
+{
+ public class ValidateInputAttributeTest
+ {
+ [Fact]
+ public void EnableValidationProperty()
+ {
+ // Act
+ ValidateInputAttribute attrTrue = new ValidateInputAttribute(true);
+ ValidateInputAttribute attrFalse = new ValidateInputAttribute(false);
+
+ // Assert
+ Assert.True(attrTrue.EnableValidation);
+ Assert.False(attrFalse.EnableValidation);
+ }
+
+ [Fact]
+ public void OnAuthorizationSetsControllerValidateRequestToFalse()
+ {
+ // Arrange
+ Controller controller = new EmptyController() { ValidateRequest = true };
+ ValidateInputAttribute attr = new ValidateInputAttribute(enableValidation: false);
+ AuthorizationContext authContext = GetAuthorizationContext(controller);
+
+ // Act
+ attr.OnAuthorization(authContext);
+
+ // Assert
+ Assert.False(controller.ValidateRequest);
+ }
+
+ [Fact]
+ public void OnAuthorizationSetsControllerValidateRequestToTrue()
+ {
+ // Arrange
+ Controller controller = new EmptyController() { ValidateRequest = false };
+ ValidateInputAttribute attr = new ValidateInputAttribute(enableValidation: true);
+ AuthorizationContext authContext = GetAuthorizationContext(controller);
+
+ // Act
+ attr.OnAuthorization(authContext);
+
+ // Assert
+ Assert.True(controller.ValidateRequest);
+ }
+
+ [Fact]
+ public void OnAuthorizationThrowsIfFilterContextIsNull()
+ {
+ // Arrange
+ ValidateInputAttribute attr = new ValidateInputAttribute(true);
+
+ // Act & assert
+ Assert.ThrowsArgumentNull(
+ delegate { attr.OnAuthorization(null); }, "filterContext");
+ }
+
+ private static AuthorizationContext GetAuthorizationContext(ControllerBase controller)
+ {
+ Mock<AuthorizationContext> mockAuthContext = new Mock<AuthorizationContext>();
+ mockAuthContext.Setup(c => c.Controller).Returns(controller);
+ return mockAuthContext.Object;
+ }
+
+ private class EmptyController : Controller
+ {
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Test/ValueProviderCollectionTest.cs b/test/System.Web.Mvc.Test/Test/ValueProviderCollectionTest.cs
new file mode 100644
index 00000000..950722fa
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Test/ValueProviderCollectionTest.cs
@@ -0,0 +1,246 @@
+using System.Collections.Generic;
+using System.Linq;
+using Moq;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Mvc.Test
+{
+ public class ValueProviderCollectionTest
+ {
+ [Fact]
+ public void ListWrappingConstructor()
+ {
+ // Arrange
+ List<IValueProvider> list = new List<IValueProvider>()
+ {
+ new Mock<IValueProvider>().Object, new Mock<IValueProvider>().Object
+ };
+
+ // Act
+ ValueProviderCollection collection = new ValueProviderCollection(list);
+
+ // Assert
+ Assert.Equal(list, collection.ToList());
+ }
+
+ [Fact]
+ public void ListWrappingConstructorThrowsIfListIsNull()
+ {
+ // Act & Assert
+ Assert.ThrowsArgumentNull(
+ delegate { new ValueProviderCollection(null); },
+ "list");
+ }
+
+ [Fact]
+ public void DefaultConstructor()
+ {
+ // Act
+ ValueProviderCollection collection = new ValueProviderCollection();
+
+ // Assert
+ Assert.Empty(collection);
+ }
+
+ [Fact]
+ public void AddNullValueProviderThrows()
+ {
+ // Arrange
+ ValueProviderCollection collection = new ValueProviderCollection();
+
+ // Act & Assert
+ Assert.ThrowsArgumentNull(
+ delegate { collection.Add(null); },
+ "item");
+ }
+
+ [Fact]
+ public void SetItem()
+ {
+ // Arrange
+ ValueProviderCollection collection = new ValueProviderCollection();
+ collection.Add(new Mock<IValueProvider>().Object);
+
+ IValueProvider newProvider = new Mock<IValueProvider>().Object;
+
+ // Act
+ collection[0] = newProvider;
+
+ // Assert
+ IValueProvider provider = Assert.Single(collection);
+ Assert.Equal(newProvider, provider);
+ }
+
+ [Fact]
+ public void SetNullValueProviderThrows()
+ {
+ // Arrange
+ ValueProviderCollection collection = new ValueProviderCollection();
+ collection.Add(new Mock<IValueProvider>().Object);
+
+ // Act & Assert
+ Assert.ThrowsArgumentNull(
+ delegate { collection[0] = null; },
+ "item");
+ }
+
+ [Fact]
+ public void ContainsPrefix()
+ {
+ // Arrange
+ string prefix = "somePrefix";
+
+ Mock<IValueProvider> mockProvider1 = new Mock<IValueProvider>();
+ mockProvider1.Setup(p => p.ContainsPrefix(prefix)).Returns(false);
+ Mock<IValueProvider> mockProvider2 = new Mock<IValueProvider>();
+ mockProvider2.Setup(p => p.ContainsPrefix(prefix)).Returns(true);
+ Mock<IValueProvider> mockProvider3 = new Mock<IValueProvider>();
+ mockProvider3.Setup(p => p.ContainsPrefix(prefix)).Returns(false);
+
+ ValueProviderCollection collection = new ValueProviderCollection()
+ {
+ mockProvider1.Object, mockProvider2.Object, mockProvider3.Object
+ };
+
+ // Act
+ bool retVal = collection.ContainsPrefix(prefix);
+
+ // Assert
+ Assert.True(retVal);
+ }
+
+ [Fact]
+ public void GetValue()
+ {
+ // Arrange
+ string key = "someKey";
+
+ Mock<IValueProvider> mockProvider1 = new Mock<IValueProvider>();
+ mockProvider1.Setup(p => p.GetValue(key)).Returns((ValueProviderResult)null);
+ Mock<IValueProvider> mockProvider2 = new Mock<IValueProvider>();
+ mockProvider2.Setup(p => p.GetValue(key)).Returns(new ValueProviderResult("2", "2", null));
+ Mock<IValueProvider> mockProvider3 = new Mock<IValueProvider>();
+ mockProvider3.Setup(p => p.GetValue(key)).Returns(new ValueProviderResult("3", "3", null));
+
+ ValueProviderCollection collection = new ValueProviderCollection()
+ {
+ mockProvider1.Object, mockProvider2.Object, mockProvider3.Object
+ };
+
+ // Act
+ ValueProviderResult result = collection.GetValue(key);
+
+ // Assert
+ Assert.NotNull(result);
+ Assert.Equal(2, result.ConvertTo(typeof(int)));
+ }
+
+ [Fact]
+ public void GetValueFromProvider_NormalProvider_DoNotSkipValidation()
+ {
+ // Arrange
+ ValueProviderResult expectedResult = new ValueProviderResult("Success", "Success", null);
+
+ Mock<IValueProvider> mockProvider = new Mock<IValueProvider>();
+ mockProvider.Setup(o => o.GetValue("key")).Returns(expectedResult);
+
+ // Act
+ ValueProviderResult actualResult = ValueProviderCollection.GetValueFromProvider(mockProvider.Object, "key", skipValidation: false);
+
+ // Assert
+ Assert.Equal(expectedResult, actualResult);
+ }
+
+ [Fact]
+ public void GetValueFromProvider_NormalProvider_SkipValidation()
+ {
+ // Arrange
+ ValueProviderResult expectedResult = new ValueProviderResult("Success", "Success", null);
+
+ Mock<IValueProvider> mockProvider = new Mock<IValueProvider>();
+ mockProvider.Setup(o => o.GetValue("key")).Returns(expectedResult);
+
+ // Act
+ ValueProviderResult actualResult = ValueProviderCollection.GetValueFromProvider(mockProvider.Object, "key", skipValidation: true);
+
+ // Assert
+ Assert.Equal(expectedResult, actualResult);
+ }
+
+ [Fact]
+ public void GetValueFromProvider_UnvalidatedProvider_DoNotSkipValidation()
+ {
+ // Arrange
+ ValueProviderResult expectedResult = new ValueProviderResult("Success", "Success", null);
+
+ Mock<IUnvalidatedValueProvider> mockProvider = new Mock<IUnvalidatedValueProvider>();
+ mockProvider.Setup(o => o.GetValue("key", false)).Returns(expectedResult);
+
+ // Act
+ ValueProviderResult actualResult = ValueProviderCollection.GetValueFromProvider(mockProvider.Object, "key", skipValidation: false);
+
+ // Assert
+ Assert.Equal(expectedResult, actualResult);
+ }
+
+ [Fact]
+ public void GetValueFromProvider_UnvalidatedProvider_SkipValidation()
+ {
+ // Arrange
+ ValueProviderResult expectedResult = new ValueProviderResult("Success", "Success", null);
+
+ Mock<IUnvalidatedValueProvider> mockProvider = new Mock<IUnvalidatedValueProvider>();
+ mockProvider.Setup(o => o.GetValue("key", true)).Returns(expectedResult);
+
+ // Act
+ ValueProviderResult actualResult = ValueProviderCollection.GetValueFromProvider(mockProvider.Object, "key", skipValidation: true);
+
+ // Assert
+ Assert.Equal(expectedResult, actualResult);
+ }
+
+ [Fact]
+ public void GetValueFromProvider_EnumeratedProvider_GoodPrefix()
+ {
+ // Arrange
+ IDictionary<string, string> expectedResult = new Dictionary<string, string>()
+ {
+ { "random", "random.hello" }
+ };
+
+ Mock<IEnumerableValueProvider> mockProvider = new Mock<IEnumerableValueProvider>();
+ mockProvider.Setup(o => o.GetKeysFromPrefix("prefix")).Returns(expectedResult);
+
+ ValueProviderCollection providerCollection = new ValueProviderCollection(new List<IValueProvider>() { mockProvider.Object });
+
+ // Act
+ IDictionary<string, string> actualResult = providerCollection.GetKeysFromPrefix("prefix");
+
+ // Assert
+ Assert.Equal(expectedResult, actualResult);
+ }
+
+ [Fact]
+ public void GetValueFromProvider_EnumeratedProvider_NotFound_DoNotReturnNull()
+ {
+ // Arrange
+ IDictionary<string, string> expectedResult = new Dictionary<string, string>()
+ {
+ { "random", "random.hello" }
+ };
+
+ Mock<IEnumerableValueProvider> mockProvider = new Mock<IEnumerableValueProvider>();
+ mockProvider.Setup(o => o.GetKeysFromPrefix("notfound")).Returns(expectedResult);
+
+ ValueProviderCollection providerCollection = new ValueProviderCollection(new List<IValueProvider>() { mockProvider.Object });
+
+ // Act
+ IDictionary<string, string> actualResult = providerCollection.GetKeysFromPrefix("prefix");
+
+ // Assert
+ Assert.NotNull(actualResult);
+ Assert.Empty(actualResult);
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Test/ValueProviderDictionaryTest.cs b/test/System.Web.Mvc.Test/Test/ValueProviderDictionaryTest.cs
new file mode 100644
index 00000000..7bc74b78
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Test/ValueProviderDictionaryTest.cs
@@ -0,0 +1,204 @@
+using System.Collections.Specialized;
+using System.Globalization;
+using System.Threading;
+using System.Web.Routing;
+using System.Web.TestUtil;
+using Moq;
+using Xunit;
+
+#pragma warning disable 0618 // ValueProviderDictionary is now obsolete
+
+namespace System.Web.Mvc.Test
+{
+ public class ValueProviderDictionaryTest
+ {
+ [Fact]
+ public void ConstructorCreatesEmptyDictionaryIfControllerContextIsNull()
+ {
+ // Act
+ ValueProviderDictionary dict = new ValueProviderDictionary(null);
+
+ // Assert
+ Assert.Empty(dict);
+ }
+
+ [Fact]
+ public void ControllerContextProperty()
+ {
+ // Arrange
+ ControllerContext expected = GetControllerContext();
+ ValueProviderDictionary dict = new ValueProviderDictionary(expected);
+
+ // Act
+ ControllerContext returned = dict.ControllerContext;
+
+ // Assert
+ Assert.Equal(expected, returned);
+ }
+
+ [Fact]
+ public void DictionaryInterface()
+ {
+ // Arrange
+ DictionaryHelper<string, ValueProviderResult> helper = new DictionaryHelper<string, ValueProviderResult>()
+ {
+ Creator = () => new ValueProviderDictionary(null),
+ Comparer = StringComparer.OrdinalIgnoreCase,
+ SampleKeys = new string[] { "foo", "bar", "baz", "quux", "QUUX" },
+ SampleValues = new ValueProviderResult[]
+ {
+ new ValueProviderResult(null, null, null),
+ new ValueProviderResult(null, null, null),
+ new ValueProviderResult(null, null, null),
+ new ValueProviderResult(null, null, null),
+ new ValueProviderResult(null, null, null)
+ },
+ ThrowOnKeyNotFound = false
+ };
+
+ // Act & assert
+ helper.Execute();
+ }
+
+ [Fact]
+ public void AddWithRawValueUsesInvariantCulture()
+ {
+ // Arrange
+ ValueProviderDictionary dict = new ValueProviderDictionary(null);
+
+ // Act
+ dict.Add("foo", 42);
+
+ // Assert
+ ValueProviderResult vpResult = dict["foo"];
+ Assert.Equal("42", vpResult.AttemptedValue);
+ Assert.Equal(42, vpResult.RawValue);
+ Assert.Equal(CultureInfo.InvariantCulture, vpResult.Culture);
+ }
+
+ [Fact]
+ public void NullAndEmptyKeysAreIgnored()
+ {
+ // DevDiv Bugs #216667: Exception thrown when querystring contains name without value
+
+ // Arrange
+ ValueProviderDictionary dict = GetAndPopulateDictionary();
+
+ // Act
+ bool emptyKeyFound = dict.ContainsKey(String.Empty);
+
+ // Assert
+ Assert.False(emptyKeyFound);
+ }
+
+ [Fact]
+ public void ValueFromForm()
+ {
+ // Arrange
+ ValueProviderDictionary dict;
+
+ // Act
+ using (ReplaceCurrentCulture("fr-FR"))
+ {
+ dict = GetAndPopulateDictionary();
+ }
+ ValueProviderResult result = dict["foo"];
+
+ // Assert
+ Assert.NotNull(result);
+ Assert.Equal("fooFromForm", result.AttemptedValue);
+ string[] stringValue = Assert.IsType<string[]>(result.RawValue);
+ Assert.Single(stringValue);
+ Assert.Equal("fooFromForm", stringValue[0]);
+ Assert.Equal(CultureInfo.GetCultureInfo("fr-FR"), result.Culture);
+ }
+
+ [Fact]
+ public void ValueFromQueryString()
+ {
+ // Arrange
+ ValueProviderDictionary dict;
+
+ // Act
+ using (ReplaceCurrentCulture("fr-FR"))
+ {
+ dict = GetAndPopulateDictionary();
+ }
+ ValueProviderResult result = dict["baz"];
+
+ // Assert
+ Assert.NotNull(result);
+ Assert.Equal("bazFromQueryString", result.AttemptedValue);
+ string[] stringValue = Assert.IsType<string[]>(result.RawValue);
+ Assert.Single(stringValue);
+ Assert.Equal("bazFromQueryString", stringValue[0]);
+ Assert.Equal(CultureInfo.InvariantCulture, result.Culture);
+ }
+
+ public void ValueFromRoute()
+ {
+ // Arrange
+ ValueProviderDictionary dict;
+
+ // Act
+ using (ReplaceCurrentCulture("fr-FR"))
+ {
+ dict = GetAndPopulateDictionary();
+ }
+ ValueProviderResult result = dict["bar"];
+
+ // Assert
+ Assert.NotNull(result);
+ Assert.Equal("barFromRoute", result.AttemptedValue);
+ Assert.Equal("barFromRoute", result.RawValue);
+ Assert.Equal(CultureInfo.InvariantCulture, result.Culture);
+ }
+
+ private static ValueProviderDictionary GetAndPopulateDictionary()
+ {
+ return new ValueProviderDictionary(GetControllerContext());
+ }
+
+ private static ControllerContext GetControllerContext()
+ {
+ NameValueCollection form = new NameValueCollection() { { "foo", "fooFromForm" } };
+
+ RouteData rd = new RouteData();
+ rd.Values["foo"] = "fooFromRoute";
+ rd.Values["bar"] = "barFromRoute";
+
+ NameValueCollection queryString = new NameValueCollection()
+ {
+ { "foo", "fooFromQueryString" },
+ { "bar", "barFromQueryString" },
+ { "baz", "bazFromQueryString" },
+ { null, "nullValue" },
+ { "", "emptyStringValue" }
+ };
+
+ Mock<ControllerContext> mockControllerContext = new Mock<ControllerContext>();
+ mockControllerContext.Setup(c => c.HttpContext.Request.Form).Returns(form);
+ mockControllerContext.Setup(c => c.HttpContext.Request.QueryString).Returns(queryString);
+ mockControllerContext.Setup(c => c.RouteData).Returns(rd);
+ return mockControllerContext.Object;
+ }
+
+ public static IDisposable ReplaceCurrentCulture(string culture)
+ {
+ CultureInfo newCulture = CultureInfo.GetCultureInfo(culture);
+ CultureInfo originalCulture = Thread.CurrentThread.CurrentCulture;
+ Thread.CurrentThread.CurrentCulture = newCulture;
+ return new CultureReplacement { OriginalCulture = originalCulture };
+ }
+
+ private class CultureReplacement : IDisposable
+ {
+ public CultureInfo OriginalCulture;
+
+ public void Dispose()
+ {
+ Thread.CurrentThread.CurrentCulture = OriginalCulture;
+ }
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Test/ValueProviderFactoriesTest.cs b/test/System.Web.Mvc.Test/Test/ValueProviderFactoriesTest.cs
new file mode 100644
index 00000000..a4a2ac56
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Test/ValueProviderFactoriesTest.cs
@@ -0,0 +1,29 @@
+using System.Linq;
+using Xunit;
+
+namespace System.Web.Mvc.Test
+{
+ public class ValueProviderFactoriesTest
+ {
+ [Fact]
+ public void CollectionDefaults()
+ {
+ // Arrange
+ Type[] expectedTypes = new[]
+ {
+ typeof(ChildActionValueProviderFactory),
+ typeof(FormValueProviderFactory),
+ typeof(JsonValueProviderFactory),
+ typeof(RouteDataValueProviderFactory),
+ typeof(QueryStringValueProviderFactory),
+ typeof(HttpFileCollectionValueProviderFactory),
+ };
+
+ // Act
+ Type[] actualTypes = ValueProviderFactories.Factories.Select(p => p.GetType()).ToArray();
+
+ // Assert
+ Assert.Equal(expectedTypes, actualTypes);
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Test/ValueProviderFactoryCollectionTest.cs b/test/System.Web.Mvc.Test/Test/ValueProviderFactoryCollectionTest.cs
new file mode 100644
index 00000000..eedba8c0
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Test/ValueProviderFactoryCollectionTest.cs
@@ -0,0 +1,143 @@
+using System.Collections.Generic;
+using System.Linq;
+using Moq;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Mvc.Test
+{
+ public class ValueProviderFactoryCollectionTest
+ {
+ [Fact]
+ public void ListWrappingConstructor()
+ {
+ // Arrange
+ List<ValueProviderFactory> list = new List<ValueProviderFactory>()
+ {
+ new FormValueProviderFactory()
+ };
+
+ // Act
+ ValueProviderFactoryCollection collection = new ValueProviderFactoryCollection(list);
+
+ // Assert
+ Assert.Equal(list, collection.ToList());
+ }
+
+ [Fact]
+ public void ListWrappingConstructorThrowsIfListIsNull()
+ {
+ // Act & Assert
+ Assert.ThrowsArgumentNull(
+ delegate { new ValueProviderFactoryCollection(null, null); },
+ "list");
+ }
+
+ [Fact]
+ public void DefaultConstructor()
+ {
+ // Act
+ ValueProviderFactoryCollection collection = new ValueProviderFactoryCollection();
+
+ // Assert
+ Assert.Empty(collection);
+ }
+
+ [Fact]
+ public void AddNullValueProviderFactoryThrows()
+ {
+ // Arrange
+ ValueProviderFactoryCollection collection = new ValueProviderFactoryCollection();
+
+ // Act & Assert
+ Assert.ThrowsArgumentNull(
+ delegate { collection.Add(null); },
+ "item");
+ }
+
+ [Fact]
+ public void GetValueProvider()
+ {
+ // Arrange
+ ControllerContext controllerContext = new ControllerContext();
+ IValueProvider[] expectedValueProviders = new[]
+ {
+ new Mock<IValueProvider>().Object,
+ new Mock<IValueProvider>().Object
+ };
+
+ Mock<ValueProviderFactory> mockFactory1 = new Mock<ValueProviderFactory>();
+ mockFactory1.Setup(o => o.GetValueProvider(controllerContext)).Returns(expectedValueProviders[0]);
+ Mock<ValueProviderFactory> mockFactory2 = new Mock<ValueProviderFactory>();
+ mockFactory2.Setup(o => o.GetValueProvider(controllerContext)).Returns(expectedValueProviders[1]);
+
+ ValueProviderFactoryCollection factories = new ValueProviderFactoryCollection()
+ {
+ mockFactory1.Object,
+ mockFactory2.Object
+ };
+
+ // Act
+ ValueProviderCollection valueProviders = (ValueProviderCollection)factories.GetValueProvider(controllerContext);
+
+ // Assert
+ Assert.Equal(expectedValueProviders, valueProviders.ToArray());
+ }
+
+ [Fact]
+ public void GetValueProviderDelegatesToResolver()
+ {
+ //Arrange
+ ControllerContext controllerContext = new ControllerContext();
+ IValueProvider[] expectedValueProviders = new[]
+ {
+ new Mock<IValueProvider>().Object,
+ new Mock<IValueProvider>().Object
+ };
+
+ Mock<ValueProviderFactory> mockFactory1 = new Mock<ValueProviderFactory>();
+ mockFactory1.Setup(o => o.GetValueProvider(controllerContext)).Returns(expectedValueProviders[0]);
+ Mock<ValueProviderFactory> mockFactory2 = new Mock<ValueProviderFactory>();
+ mockFactory2.Setup(o => o.GetValueProvider(controllerContext)).Returns(expectedValueProviders[1]);
+
+ Resolver<IEnumerable<ValueProviderFactory>> resolver = new Resolver<IEnumerable<ValueProviderFactory>> { Current = new[] { mockFactory1.Object, mockFactory2.Object } };
+ ValueProviderFactoryCollection factories = new ValueProviderFactoryCollection(resolver);
+
+ // Act
+ ValueProviderCollection valueProviders = (ValueProviderCollection)factories.GetValueProvider(controllerContext);
+
+ // Assert
+ Assert.Equal(expectedValueProviders, valueProviders.ToArray());
+ }
+
+ [Fact]
+ public void SetItem()
+ {
+ // Arrange
+ ValueProviderFactoryCollection collection = new ValueProviderFactoryCollection();
+ collection.Add(new Mock<ValueProviderFactory>().Object);
+
+ ValueProviderFactory newFactory = new Mock<ValueProviderFactory>().Object;
+
+ // Act
+ collection[0] = newFactory;
+
+ // Assert
+ Assert.Single(collection);
+ Assert.Equal(newFactory, collection[0]);
+ }
+
+ [Fact]
+ public void SetNullValueProviderFactoryThrows()
+ {
+ // Arrange
+ ValueProviderFactoryCollection collection = new ValueProviderFactoryCollection();
+ collection.Add(new Mock<ValueProviderFactory>().Object);
+
+ // Act & Assert
+ Assert.ThrowsArgumentNull(
+ delegate { collection[0] = null; },
+ "item");
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Test/ValueProviderResultTest.cs b/test/System.Web.Mvc.Test/Test/ValueProviderResultTest.cs
new file mode 100644
index 00000000..cae550d8
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Test/ValueProviderResultTest.cs
@@ -0,0 +1,398 @@
+using System.Globalization;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Mvc.Test
+{
+ public class ValueProviderResultTest
+ {
+ [Fact]
+ public void ConstructorSetsProperties()
+ {
+ // Arrange
+ object rawValue = new object();
+ string attemptedValue = "some string";
+ CultureInfo culture = CultureInfo.GetCultureInfo("fr-FR");
+
+ // Act
+ ValueProviderResult result = new ValueProviderResult(rawValue, attemptedValue, culture);
+
+ // Assert
+ Assert.Same(rawValue, result.RawValue);
+ Assert.Same(attemptedValue, result.AttemptedValue);
+ Assert.Same(culture, result.Culture);
+ }
+
+ [Fact]
+ public void ConvertToCanConvertArraysToArrays()
+ {
+ // Arrange
+ ValueProviderResult vpr = new ValueProviderResult(new int[] { 1, 20, 42 }, "", CultureInfo.InvariantCulture);
+
+ // Act
+ string[] converted = (string[])vpr.ConvertTo(typeof(string[]));
+
+ // Assert
+ Assert.NotNull(converted);
+ Assert.Equal(3, converted.Length);
+ Assert.Equal("1", converted[0]);
+ Assert.Equal("20", converted[1]);
+ Assert.Equal("42", converted[2]);
+ }
+
+ [Fact]
+ public void ConvertToCanConvertArraysToSingleElements()
+ {
+ // Arrange
+ ValueProviderResult vpr = new ValueProviderResult(new int[] { 1, 20, 42 }, "", CultureInfo.InvariantCulture);
+
+ // Act
+ string converted = (string)vpr.ConvertTo(typeof(string));
+
+ // Assert
+ Assert.Equal("1", converted);
+ }
+
+ [Fact]
+ public void ConvertToCanConvertSingleElementsToArrays()
+ {
+ // Arrange
+ ValueProviderResult vpr = new ValueProviderResult(42, "", CultureInfo.InvariantCulture);
+
+ // Act
+ string[] converted = (string[])vpr.ConvertTo(typeof(string[]));
+
+ // Assert
+ Assert.NotNull(converted);
+ Assert.Single(converted);
+ Assert.Equal("42", converted[0]);
+ }
+
+ [Fact]
+ public void ConvertToCanConvertSingleElementsToSingleElements()
+ {
+ // Arrange
+ ValueProviderResult vpr = new ValueProviderResult(42, "", CultureInfo.InvariantCulture);
+
+ // Act
+ string converted = (string)vpr.ConvertTo(typeof(string));
+
+ // Assert
+ Assert.NotNull(converted);
+ Assert.Equal("42", converted);
+ }
+
+ [Fact]
+ public void ConvertToChecksTypeConverterCanConvertFrom()
+ {
+ // Arrange
+ object original = "someValue";
+ ValueProviderResult vpr = new ValueProviderResult(original, null, CultureInfo.GetCultureInfo("fr-FR"));
+
+ // Act
+ DefaultModelBinderTest.StringContainer returned = (DefaultModelBinderTest.StringContainer)vpr.ConvertTo(typeof(DefaultModelBinderTest.StringContainer));
+
+ // Assert
+ Assert.Equal(returned.Value, "someValue (fr-FR)");
+ }
+
+ [Fact]
+ public void ConvertToChecksTypeConverterCanConvertTo()
+ {
+ // Arrange
+ object original = new DefaultModelBinderTest.StringContainer("someValue");
+ ValueProviderResult vpr = new ValueProviderResult(original, "", CultureInfo.GetCultureInfo("en-US"));
+
+ // Act
+ string returned = (string)vpr.ConvertTo(typeof(string));
+
+ // Assert
+ Assert.Equal(returned, "someValue (en-US)");
+ }
+
+ [Fact]
+ public void ConvertToReturnsNullIfArrayElementValueIsNull()
+ {
+ // Arrange
+ ValueProviderResult vpr = new ValueProviderResult(new string[] { null }, null, CultureInfo.InvariantCulture);
+
+ // Act
+ object outValue = vpr.ConvertTo(typeof(int));
+
+ // Assert
+ Assert.Null(outValue);
+ }
+
+ [Fact]
+ public void ConvertToReturnsNullIfTryingToConvertEmptyArrayToSingleElement()
+ {
+ // Arrange
+ ValueProviderResult vpr = new ValueProviderResult(new int[0], "", CultureInfo.InvariantCulture);
+
+ // Act
+ object outValue = vpr.ConvertTo(typeof(int));
+
+ // Assert
+ Assert.Null(outValue);
+ }
+
+ [Fact]
+ public void ConvertToReturnsNullIfValueIsEmptyString()
+ {
+ // Arrange
+ ValueProviderResult vpr = new ValueProviderResult("", null, CultureInfo.InvariantCulture);
+
+ // Act
+ object outValue = vpr.ConvertTo(typeof(int));
+
+ // Assert
+ Assert.Null(outValue);
+ }
+
+ [Fact]
+ public void ConvertToReturnsNullIfTrimmedValueIsEmptyString()
+ {
+ // Arrange
+ ValueProviderResult vpr = new ValueProviderResult(" \t \r\n ", null, CultureInfo.InvariantCulture);
+
+ // Act
+ object outValue = vpr.ConvertTo(typeof(int));
+
+ // Assert
+ Assert.Null(outValue);
+ }
+
+ [Fact]
+ public void ConvertToReturnsNullIfValueIsNull()
+ {
+ // Arrange
+ ValueProviderResult vpr = new ValueProviderResult(null /* rawValue */, null /* attemptedValue */, CultureInfo.InvariantCulture);
+
+ // Act
+ object outValue = vpr.ConvertTo(typeof(int[]));
+
+ // Assert
+ Assert.Null(outValue);
+ }
+
+ [Fact]
+ public void ConvertToReturnsValueIfArrayElementIsIntegerAndDestinationTypeIsEnum()
+ {
+ // Arrange
+ ValueProviderResult vpr = new ValueProviderResult(new object[] { 1 }, null, CultureInfo.InvariantCulture);
+
+ // Act
+ object outValue = vpr.ConvertTo(typeof(MyEnum));
+
+ // Assert
+ Assert.Equal(outValue, MyEnum.Value1);
+ }
+
+ [Fact]
+ public void ConvertToReturnsValueIfArrayElementIsStringValueAndDestinationTypeIsEnum()
+ {
+ // Arrange
+ ValueProviderResult vpr = new ValueProviderResult(new object[] { "1" }, null, CultureInfo.InvariantCulture);
+
+ // Act
+ object outValue = vpr.ConvertTo(typeof(MyEnum));
+
+ // Assert
+ Assert.Equal(outValue, MyEnum.Value1);
+ }
+
+ [Fact]
+ public void ConvertToReturnsValueIfArrayElementIsStringKeyAndDestinationTypeIsEnum()
+ {
+ // Arrange
+ ValueProviderResult vpr = new ValueProviderResult(new object[] { "Value1" }, null, CultureInfo.InvariantCulture);
+
+ // Act
+ object outValue = vpr.ConvertTo(typeof(MyEnum));
+
+ // Assert
+ Assert.Equal(outValue, MyEnum.Value1);
+ }
+
+ [Fact]
+ public void ConvertToReturnsValueIfElementIsStringAndDestionationIsNullableInteger()
+ {
+ // Arrange
+ ValueProviderResult vpr = new ValueProviderResult("12", null, CultureInfo.InvariantCulture);
+
+ // Act
+ object outValue = vpr.ConvertTo(typeof(int?));
+
+ // Assert
+ Assert.Equal(12, outValue);
+ }
+
+ [Fact]
+ public void ConvertToReturnsValueIfElementIsStringAndDestionationIsNullableDouble()
+ {
+ // Arrange
+ ValueProviderResult vpr = new ValueProviderResult("12.5", null, CultureInfo.InvariantCulture);
+
+ // Act
+ object outValue = vpr.ConvertTo(typeof(double?));
+
+ // Assert
+ Assert.Equal(12.5, outValue);
+ }
+
+ [Fact]
+ public void ConvertToReturnsValueIfElementIsDecimalAndDestionationIsNullableInteger()
+ {
+ // Arrange
+ ValueProviderResult vpr = new ValueProviderResult(12M, null, CultureInfo.InvariantCulture);
+
+ // Act
+ object outValue = vpr.ConvertTo(typeof(int?));
+
+ // Assert
+ Assert.Equal(12, outValue);
+ }
+
+ [Fact]
+ public void ConvertToReturnsValueIfElementIsDecimalAndDestionationIsNullableDouble()
+ {
+ // Arrange
+ ValueProviderResult vpr = new ValueProviderResult(12.5M, null, CultureInfo.InvariantCulture);
+
+ // Act
+ object outValue = vpr.ConvertTo(typeof(double?));
+
+ // Assert
+ Assert.Equal(12.5, outValue);
+ }
+
+ [Fact]
+ public void ConvertToReturnsValueIfElementIsDecimalDoubleAndDestionationIsNullableInteger()
+ {
+ // Arrange
+ ValueProviderResult vpr = new ValueProviderResult(12.5M, null, CultureInfo.InvariantCulture);
+
+ // Act
+ object outValue = vpr.ConvertTo(typeof(int?));
+
+ // Assert
+ Assert.Equal(12, outValue);
+ }
+
+ [Fact]
+ public void ConvertToReturnsValueIfElementIsDecimalDoubleAndDestionationIsNullableLong()
+ {
+ // Arrange
+ ValueProviderResult vpr = new ValueProviderResult(12.5M, null, CultureInfo.InvariantCulture);
+
+ // Act
+ object outValue = vpr.ConvertTo(typeof(long?));
+
+ // Assert
+ Assert.Equal(12L, outValue);
+ }
+
+ [Fact]
+ public void ConvertToReturnsValueIfArrayElementInstanceOfDestinationType()
+ {
+ // Arrange
+ ValueProviderResult vpr = new ValueProviderResult(new object[] { "some string" }, null, CultureInfo.InvariantCulture);
+
+ // Act
+ object outValue = vpr.ConvertTo(typeof(string));
+
+ // Assert
+ Assert.Equal("some string", outValue);
+ }
+
+ [Fact]
+ public void ConvertToReturnsValueIfInstanceOfDestinationType()
+ {
+ // Arrange
+ string[] original = new string[] { "some string" };
+ ValueProviderResult vpr = new ValueProviderResult(original, null, CultureInfo.InvariantCulture);
+
+ // Act
+ object outValue = vpr.ConvertTo(typeof(string[]));
+
+ // Assert
+ Assert.Same(original, outValue);
+ }
+
+ [Fact]
+ public void ConvertToThrowsIfConverterThrows()
+ {
+ // Arrange
+ ValueProviderResult vpr = new ValueProviderResult("x", null, CultureInfo.InvariantCulture);
+ Type destinationType = typeof(DefaultModelBinderTest.StringContainer);
+
+ // Act & Assert
+ // Will throw since the custom converter assumes the first 5 characters to be digits
+ InvalidOperationException exception = Assert.Throws<InvalidOperationException>(
+ delegate { vpr.ConvertTo(destinationType); },
+ "The parameter conversion from type 'System.String' to type 'System.Web.Mvc.Test.DefaultModelBinderTest+StringContainer' failed. See the inner exception for more information.");
+
+ Exception innerException = exception.InnerException;
+ Assert.Equal("Value must have at least 3 characters.", innerException.Message);
+ }
+
+ [Fact]
+ public void ConvertToThrowsIfNoConverterExists()
+ {
+ // Arrange
+ ValueProviderResult vpr = new ValueProviderResult("x", null, CultureInfo.InvariantCulture);
+ Type destinationType = typeof(MyClassWithoutConverter);
+
+ // Act & Assert
+ Assert.Throws<InvalidOperationException>(
+ delegate { vpr.ConvertTo(destinationType); },
+ "The parameter conversion from type 'System.String' to type 'System.Web.Mvc.Test.ValueProviderResultTest+MyClassWithoutConverter' failed because no type converter can convert between these types.");
+ }
+
+ [Fact]
+ public void ConvertToThrowsIfTypeIsNull()
+ {
+ // Arrange
+ ValueProviderResult vpr = new ValueProviderResult("x", null, CultureInfo.InvariantCulture);
+
+ // Act & Assert
+ Assert.ThrowsArgumentNull(
+ delegate { vpr.ConvertTo(null); }, "type");
+ }
+
+ [Fact]
+ public void ConvertToUsesProvidedCulture()
+ {
+ // Arrange
+ object original = "someValue";
+ CultureInfo gbCulture = CultureInfo.GetCultureInfo("en-GB");
+ ValueProviderResult vpr = new ValueProviderResult(original, null, CultureInfo.GetCultureInfo("fr-FR"));
+
+ // Act
+ DefaultModelBinderTest.StringContainer returned = (DefaultModelBinderTest.StringContainer)vpr.ConvertTo(typeof(DefaultModelBinderTest.StringContainer), gbCulture);
+
+ // Assert
+ Assert.Equal(returned.Value, "someValue (en-GB)");
+ }
+
+ [Fact]
+ public void CulturePropertyDefaultsToInvariantCulture()
+ {
+ // Arrange
+ ValueProviderResult result = new ValueProviderResult(null, null, null);
+
+ // Act & assert
+ Assert.Same(CultureInfo.InvariantCulture, result.Culture);
+ }
+
+ private class MyClassWithoutConverter
+ {
+ }
+
+ private enum MyEnum
+ {
+ Value0 = 0,
+ Value1 = 1
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Test/ValueProviderUtilTest.cs b/test/System.Web.Mvc.Test/Test/ValueProviderUtilTest.cs
new file mode 100644
index 00000000..9ceec6b5
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Test/ValueProviderUtilTest.cs
@@ -0,0 +1,185 @@
+using System.Collections.Generic;
+using System.Linq;
+using Xunit;
+
+namespace System.Web.Mvc.Test
+{
+ public class ValueProviderUtilTest
+ {
+ [Fact]
+ public void CollectionContainsPrefix_EmptyCollectionReturnsFalse()
+ {
+ // Arrange
+ string[] collection = new string[0];
+
+ // Act
+ bool retVal = ValueProviderUtil.CollectionContainsPrefix(collection, "");
+
+ // Assert
+ Assert.False(retVal);
+ }
+
+ [Fact]
+ public void CollectionContainsPrefix_ExactMatch()
+ {
+ // Arrange
+ string[] collection = new string[] { "Hello" };
+
+ // Act
+ bool retVal = ValueProviderUtil.CollectionContainsPrefix(collection, "Hello");
+
+ // Assert
+ Assert.True(retVal);
+ }
+
+ [Fact]
+ public void CollectionContainsPrefix_MatchIsCaseInsensitive()
+ {
+ // Arrange
+ string[] collection = new string[] { "Hello" };
+
+ // Act
+ bool retVal = ValueProviderUtil.CollectionContainsPrefix(collection, "hello");
+
+ // Assert
+ Assert.True(retVal);
+ }
+
+ [Fact]
+ public void CollectionContainsPrefix_MatchIsNotSimpleSubstringMatch()
+ {
+ // Arrange
+ string[] collection = new string[] { "Hello" };
+
+ // Act
+ bool retVal = ValueProviderUtil.CollectionContainsPrefix(collection, "He");
+
+ // Assert
+ Assert.False(retVal);
+ }
+
+ [Fact]
+ public void CollectionContainsPrefix_NonEmptyCollectionReturnsTrueIfPrefixIsEmptyString()
+ {
+ // Arrange
+ string[] collection = new string[] { "Hello" };
+
+ // Act
+ bool retVal = ValueProviderUtil.CollectionContainsPrefix(collection, "");
+
+ // Assert
+ Assert.True(retVal);
+ }
+
+ [Fact]
+ public void CollectionContainsPrefix_PrefixBoundaries()
+ {
+ // Arrange
+ string[] collection = new string[] { "Hello.There[0]" };
+
+ // Act
+ bool retVal1 = ValueProviderUtil.CollectionContainsPrefix(collection, "hello");
+ bool retVal2 = ValueProviderUtil.CollectionContainsPrefix(collection, "hello.there");
+
+ // Assert
+ Assert.True(retVal1);
+ Assert.True(retVal2);
+ }
+
+ [Fact]
+ public void GetPrefixes()
+ {
+ // Arrange
+ string key = "foo.bar[baz].quux";
+ string[] expected = new string[]
+ {
+ "foo.bar[baz].quux",
+ "foo.bar[baz]",
+ "foo.bar",
+ "foo"
+ };
+
+ // Act
+ string[] result = ValueProviderUtil.GetPrefixes(key).ToArray();
+
+ // Assert
+ Assert.Equal(expected, result);
+ }
+
+ public void GetKeysFromPrefixWithDotsNotation()
+ {
+ // Arrange
+ IList<string> collection = new List<string>()
+ {
+ "foo.bar", "something.other", "foo.baz", "foot.hello", "fo.nothing", "foo"
+ };
+ string prefix = "foo";
+
+ // Act
+ IDictionary<string, string> result = ValueProviderUtil.GetKeysFromPrefix(collection, prefix);
+
+ // Assert
+ Assert.Equal(2, result.Count());
+ Assert.True(result.ContainsKey("bar"));
+ Assert.True(result.ContainsKey("baz"));
+ Assert.Equal("foo.bar", result["bar"]);
+ Assert.Equal("foo.baz", result["baz"]);
+ }
+
+ public void GetKeysFromPrefixWithBracketsNotation()
+ {
+ // Arrange
+ IList<string> collection = new List<string>()
+ {
+ "foo[bar]", "something[other]", "foo[baz]", "foot[hello]", "fo[nothing]", "foo"
+ };
+ string prefix = "foo";
+
+ // Act
+ IDictionary<string, string> result = ValueProviderUtil.GetKeysFromPrefix(collection, prefix);
+
+ // Assert
+ Assert.Equal(2, result.Count());
+ Assert.True(result.ContainsKey("bar"));
+ Assert.True(result.ContainsKey("baz"));
+ Assert.Equal("foo[bar]", result["bar"]);
+ Assert.Equal("foo[baz]", result["baz"]);
+ }
+
+ public void GetKeysFromPrefixWithDotsAndBracketsNotation()
+ {
+ // Arrange
+ IList<string> collection = new List<string>()
+ {
+ "foo[bar]", "something[other]", "foo.baz", "foot[hello]", "fo[nothing]", "foo"
+ };
+ string prefix = "foo";
+
+ // Act
+ IDictionary<string, string> result = ValueProviderUtil.GetKeysFromPrefix(collection, prefix);
+
+ // Assert
+ Assert.Equal(2, result.Count());
+ Assert.True(result.ContainsKey("bar"));
+ Assert.True(result.ContainsKey("baz"));
+ Assert.Equal("foo[bar]", result["bar"]);
+ Assert.Equal("foo.baz", result["baz"]);
+ }
+
+ public void GetKeysFromPrefixWithPrefixNotFound()
+ {
+ // Arrange
+ IList<string> collection = new List<string>()
+ {
+ "foo[bar]", "something[other]", "foo.baz", "foot[hello]", "fo[nothing]", "foo"
+ };
+ string prefix = "notfound";
+
+ // Act
+ IDictionary<string, string> result = ValueProviderUtil.GetKeysFromPrefix(collection, prefix);
+
+ // Assert
+ Assert.Empty(result);
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Test/ViewContextTest.cs b/test/System.Web.Mvc.Test/Test/ViewContextTest.cs
new file mode 100644
index 00000000..b8230fb2
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Test/ViewContextTest.cs
@@ -0,0 +1,182 @@
+using System.Collections;
+using System.Collections.Generic;
+using System.IO;
+using Moq;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Mvc.Test
+{
+ public class ViewContextTest
+ {
+ [Fact]
+ public void GuardClauses()
+ {
+ // Arrange
+ var controllerContext = new Mock<ControllerContext>().Object;
+ var view = new Mock<IView>().Object;
+ var viewData = new ViewDataDictionary();
+ var tempData = new TempDataDictionary();
+ var writer = new StringWriter();
+
+ // Act & Assert
+ Assert.ThrowsArgumentNull(
+ () => new ViewContext(null, view, viewData, tempData, writer),
+ "controllerContext"
+ );
+ Assert.ThrowsArgumentNull(
+ () => new ViewContext(controllerContext, null, viewData, tempData, writer),
+ "view"
+ );
+ Assert.ThrowsArgumentNull(
+ () => new ViewContext(controllerContext, view, null, tempData, writer),
+ "viewData"
+ );
+ Assert.ThrowsArgumentNull(
+ () => new ViewContext(controllerContext, view, viewData, null, writer),
+ "tempData"
+ );
+ Assert.ThrowsArgumentNull(
+ () => new ViewContext(controllerContext, view, viewData, tempData, null),
+ "writer"
+ );
+ }
+
+ [Fact]
+ public void FormIdGeneratorProperty()
+ {
+ // Arrange
+ var mockHttpContext = new Mock<HttpContextBase>();
+ mockHttpContext.Setup(o => o.Items).Returns(new Hashtable());
+ var viewContext = new ViewContext
+ {
+ HttpContext = mockHttpContext.Object
+ };
+
+ // Act
+ string form0Name = viewContext.FormIdGenerator();
+ string form1Name = viewContext.FormIdGenerator();
+ string form2Name = viewContext.FormIdGenerator();
+
+ // Assert
+ Assert.Equal("form0", form0Name);
+ Assert.Equal("form1", form1Name);
+ Assert.Equal("form2", form2Name);
+ }
+
+ [Fact]
+ public void PropertiesAreSet()
+ {
+ // Arrange
+ var mockControllerContext = new Mock<ControllerContext>();
+ mockControllerContext.Setup(o => o.HttpContext.Items).Returns(new Hashtable());
+ var view = new Mock<IView>().Object;
+ var viewData = new ViewDataDictionary();
+ var tempData = new TempDataDictionary();
+ var writer = new StringWriter();
+
+ // Act
+ ViewContext viewContext = new ViewContext(mockControllerContext.Object, view, viewData, tempData, writer);
+
+ // Setting FormContext to null will return the default one later
+ viewContext.FormContext = null;
+
+ // Assert
+ Assert.Equal(view, viewContext.View);
+ Assert.Equal(viewData, viewContext.ViewData);
+ Assert.Equal(tempData, viewContext.TempData);
+ Assert.Equal(writer, viewContext.Writer);
+ Assert.False(viewContext.UnobtrusiveJavaScriptEnabled); // Unobtrusive JavaScript should be off by default
+ Assert.NotNull(viewContext.FormContext); // We get the default FormContext
+ }
+
+ [Fact]
+ public void ViewContextUsesScopeThunkForInstanceClientValidationFlag()
+ {
+ // Arrange
+ var scope = new Dictionary<object, object>();
+ var httpContext = new Mock<HttpContextBase>();
+ var viewContext = new ViewContext { ScopeThunk = () => scope, HttpContext = httpContext.Object };
+ httpContext.Setup(c => c.Items).Returns(new Hashtable());
+
+ // Act & Assert
+ Assert.False(viewContext.ClientValidationEnabled);
+ viewContext.ClientValidationEnabled = true;
+ Assert.True(viewContext.ClientValidationEnabled);
+ Assert.Equal(true, scope[ViewContext.ClientValidationKeyName]);
+ viewContext.ClientValidationEnabled = false;
+ Assert.False(viewContext.ClientValidationEnabled);
+ Assert.Equal(false, scope[ViewContext.ClientValidationKeyName]);
+ }
+
+ [Fact]
+ public void ViewContextUsesScopeThunkForInstanceUnobstrusiveJavaScriptFlag()
+ {
+ // Arrange
+ var scope = new Dictionary<object, object>();
+ var httpContext = new Mock<HttpContextBase>();
+ var viewContext = new ViewContext { ScopeThunk = () => scope, HttpContext = httpContext.Object };
+ httpContext.Setup(c => c.Items).Returns(new Hashtable());
+
+ // Act & Assert
+ Assert.False(viewContext.UnobtrusiveJavaScriptEnabled);
+ viewContext.UnobtrusiveJavaScriptEnabled = true;
+ Assert.True(viewContext.UnobtrusiveJavaScriptEnabled);
+ Assert.Equal(true, scope[ViewContext.UnobtrusiveJavaScriptKeyName]);
+ viewContext.UnobtrusiveJavaScriptEnabled = false;
+ Assert.False(viewContext.UnobtrusiveJavaScriptEnabled);
+ Assert.Equal(false, scope[ViewContext.UnobtrusiveJavaScriptKeyName]);
+ }
+
+ [Fact]
+ public void ViewBagProperty_ReflectsViewData()
+ {
+ // Arrange
+ var mockControllerContext = new Mock<ControllerContext>();
+ var view = new Mock<IView>().Object;
+ var viewData = new ViewDataDictionary() { { "A", 1 } };
+
+ // Act
+ ViewContext viewContext = new ViewContext(mockControllerContext.Object, view, viewData, new TempDataDictionary(), new StringWriter());
+
+ // Assert
+ Assert.Equal(1, viewContext.ViewBag.A);
+ }
+
+ [Fact]
+ public void ViewBagProperty_ReflectsNewViewDataInstance()
+ {
+ // Arrange
+ var mockControllerContext = new Mock<ControllerContext>();
+ var view = new Mock<IView>().Object;
+ var viewData = new ViewDataDictionary() { { "A", 1 } };
+
+ ViewContext viewContext = new ViewContext(mockControllerContext.Object, view, viewData, new TempDataDictionary(), new StringWriter());
+
+ // Act
+ viewContext.ViewData = new ViewDataDictionary() { { "A", "bar" } };
+
+ // Assert
+ Assert.Equal("bar", viewContext.ViewBag.A);
+ }
+
+ [Fact]
+ public void ViewBag_PropagatesChangesToViewData()
+ {
+ // Arrange
+ var mockControllerContext = new Mock<ControllerContext>();
+ var view = new Mock<IView>().Object;
+ var viewData = new ViewDataDictionary() { { "A", 1 } };
+
+ ViewContext viewContext = new ViewContext(mockControllerContext.Object, view, viewData, new TempDataDictionary(), new StringWriter());
+
+ // Act
+ viewContext.ViewBag.A = "foo";
+ viewContext.ViewBag.B = 2;
+
+ // Assert
+ Assert.Equal("foo", viewContext.ViewData["A"]);
+ Assert.Equal(2, viewContext.ViewData["B"]);
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Test/ViewDataDictionaryTest.cs b/test/System.Web.Mvc.Test/Test/ViewDataDictionaryTest.cs
new file mode 100644
index 00000000..3321af13
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Test/ViewDataDictionaryTest.cs
@@ -0,0 +1,536 @@
+using System.Collections;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Web.TestUtil;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Mvc.Test
+{
+ public class ViewDataDictionaryTest
+ {
+ [Fact]
+ public void ConstructorThrowsIfDictionaryIsNull()
+ {
+ // Act & assert
+ Assert.ThrowsArgumentNull(
+ delegate { new ViewDataDictionary((ViewDataDictionary)null); }, "dictionary");
+ }
+
+ [Fact]
+ public void ConstructorWithViewDataDictionaryCopiesModelAndModelState()
+ {
+ // Arrange
+ ViewDataDictionary originalVdd = new ViewDataDictionary();
+ object model = new object();
+ originalVdd.Model = model;
+ originalVdd["foo"] = "bar";
+ originalVdd.ModelState.AddModelError("key", "error");
+
+ // Act
+ ViewDataDictionary newVdd = new ViewDataDictionary(originalVdd);
+
+ // Assert
+ Assert.Equal(model, newVdd.Model);
+ Assert.True(newVdd.ModelState.ContainsKey("key"));
+ Assert.Equal("error", newVdd.ModelState["key"].Errors[0].ErrorMessage);
+ Assert.Equal("bar", newVdd["foo"]);
+ }
+
+ [Fact]
+ public void DictionaryInterface()
+ {
+ // Arrange
+ DictionaryHelper<string, object> helper = new DictionaryHelper<string, object>()
+ {
+ Creator = () => new ViewDataDictionary(),
+ Comparer = StringComparer.OrdinalIgnoreCase,
+ SampleKeys = new string[] { "foo", "bar", "baz", "quux", "QUUX" },
+ SampleValues = new object[] { 42, "string value", new DateTime(2001, 1, 1), new object(), 32m },
+ ThrowOnKeyNotFound = false
+ };
+
+ // Act & assert
+ helper.Execute();
+ }
+
+ [Fact]
+ public void EvalReturnsSimplePropertyValue()
+ {
+ var obj = new { Foo = "Bar" };
+ ViewDataDictionary vdd = new ViewDataDictionary(obj);
+
+ Assert.Equal("Bar", vdd.Eval("Foo"));
+ }
+
+ [Fact]
+ public void EvalWithModelAndDictionaryPropertyEvaluatesDictionaryValue()
+ {
+ var obj = new { Foo = new Dictionary<string, object> { { "Bar", "Baz" } } };
+ ViewDataDictionary vdd = new ViewDataDictionary(obj);
+
+ Assert.Equal("Baz", vdd.Eval("Foo.Bar"));
+ }
+
+ [Fact]
+ public void EvalEvaluatesDictionaryThenModel()
+ {
+ var obj = new { Foo = "NotBar" };
+ ViewDataDictionary vdd = new ViewDataDictionary(obj);
+ vdd.Add("Foo", "Bar");
+
+ Assert.Equal("Bar", vdd.Eval("Foo"));
+ }
+
+ [Fact]
+ public void EvalReturnsValueOfCompoundExpressionByFollowingObjectPath()
+ {
+ var obj = new { Foo = new { Bar = "Baz" } };
+ ViewDataDictionary vdd = new ViewDataDictionary(obj);
+
+ Assert.Equal("Baz", vdd.Eval("Foo.Bar"));
+ }
+
+ [Fact]
+ public void EvalReturnsNullIfExpressionDoesNotMatch()
+ {
+ var obj = new { Foo = new { Biz = "Baz" } };
+ ViewDataDictionary vdd = new ViewDataDictionary(obj);
+
+ Assert.Equal(null, vdd.Eval("Foo.Bar"));
+ }
+
+ [Fact]
+ public void EvalReturnsValueJustAdded()
+ {
+ ViewDataDictionary vdd = new ViewDataDictionary();
+ vdd.Add("Foo", "Blah");
+
+ Assert.Equal("Blah", vdd.Eval("Foo"));
+ }
+
+ [Fact]
+ public void EvalWithCompoundExpressionReturnsIndexedValue()
+ {
+ ViewDataDictionary vdd = new ViewDataDictionary();
+ vdd.Add("Foo.Bar", "Baz");
+
+ Assert.Equal("Baz", vdd.Eval("Foo.Bar"));
+ }
+
+ [Fact]
+ public void EvalWithCompoundExpressionReturnsPropertyOfAddedObject()
+ {
+ ViewDataDictionary vdd = new ViewDataDictionary();
+ vdd.Add("Foo", new { Bar = "Baz" });
+
+ Assert.Equal("Baz", vdd.Eval("Foo.Bar"));
+ }
+
+ [Fact]
+ public void EvalWithCompoundIndexExpressionReturnsEval()
+ {
+ ViewDataDictionary vdd = new ViewDataDictionary();
+ vdd.Add("Foo.Bar", new { Baz = "Quux" });
+
+ Assert.Equal("Quux", vdd.Eval("Foo.Bar.Baz"));
+ }
+
+ [Fact]
+ public void EvalWithCompoundIndexAndCompoundExpressionReturnsValue()
+ {
+ ViewDataDictionary vdd = new ViewDataDictionary();
+ vdd.Add("Foo.Bar", new { Baz = new { Blah = "Quux" } });
+
+ Assert.Equal("Quux", vdd.Eval("Foo.Bar.Baz.Blah"));
+ }
+
+ /// <summary>
+ /// Make sure that dict["foo.bar"] gets chosen before dict["foo"]["bar"]
+ /// </summary>
+ [Fact]
+ public void EvalChoosesValueInDictionaryOverOtherValue()
+ {
+ ViewDataDictionary vdd = new ViewDataDictionary();
+ vdd.Add("Foo", new { Bar = "Not Baz" });
+ vdd.Add("Foo.Bar", "Baz");
+
+ Assert.Equal("Baz", vdd.Eval("Foo.Bar"));
+ }
+
+ /// <summary>
+ /// Make sure that dict["foo.bar"]["baz"] gets chosen before dict["foo"]["bar"]["baz"]
+ /// </summary>
+ [Fact]
+ public void EvalChoosesCompoundValueInDictionaryOverOtherValues()
+ {
+ ViewDataDictionary vdd = new ViewDataDictionary();
+ vdd.Add("Foo", new { Bar = new { Baz = "Not Quux" } });
+ vdd.Add("Foo.Bar", new { Baz = "Quux" });
+
+ Assert.Equal("Quux", vdd.Eval("Foo.Bar.Baz"));
+ }
+
+ /// <summary>
+ /// Make sure that dict["foo.bar"]["baz"] gets chosen before dict["foo"]["bar.baz"]
+ /// </summary>
+ [Fact]
+ public void EvalChoosesCompoundValueInDictionaryOverOtherValuesWithCompoundProperty()
+ {
+ ViewDataDictionary vdd = new ViewDataDictionary();
+ vdd.Add("Foo", new Person());
+ vdd.Add("Foo.Bar", new { Baz = "Quux" });
+
+ Assert.Equal("Quux", vdd.Eval("Foo.Bar.Baz"));
+ }
+
+ [Fact]
+ public void EvalThrowsIfExpressionIsEmpty()
+ {
+ // Arrange
+ ViewDataDictionary vdd = new ViewDataDictionary();
+
+ // Act & Assert
+ Assert.ThrowsArgumentNullOrEmpty(
+ delegate { vdd.Eval(String.Empty); }, "expression");
+ }
+
+ [Fact]
+ public void EvalThrowsIfExpressionIsNull()
+ {
+ // Arrange
+ ViewDataDictionary vdd = new ViewDataDictionary();
+
+ // Act & Assert
+ Assert.ThrowsArgumentNullOrEmpty(
+ delegate { vdd.Eval(null); }, "expression");
+ }
+
+ [Fact]
+ public void EvalWithCompoundExpressionAndDictionarySubExpressionChoosesDictionaryValue()
+ {
+ ViewDataDictionary vdd = new ViewDataDictionary();
+ vdd.Add("Foo", new Dictionary<string, object> { { "Bar", "Baz" } });
+
+ Assert.Equal("Baz", vdd.Eval("Foo.Bar"));
+ }
+
+ [Fact]
+ public void EvalWithDictionaryAndNoMatchReturnsNull()
+ {
+ ViewDataDictionary vdd = new ViewDataDictionary();
+ vdd.Add("Foo", new Dictionary<string, object> { { "NotBar", "Baz" } });
+
+ object result = vdd.Eval("Foo.Bar");
+ Assert.Null(result);
+ }
+
+ [Fact]
+ public void EvalWithNestedDictionariesEvalCorrectly()
+ {
+ ViewDataDictionary vdd = new ViewDataDictionary();
+ vdd.Add("Foo", new Dictionary<string, object> { { "Bar", new Hashtable { { "Baz", "Quux" } } } });
+
+ Assert.Equal("Quux", vdd.Eval("Foo.Bar.Baz"));
+ }
+
+ [Fact]
+ public void EvalFormatWithNullValueReturnsEmptyString()
+ {
+ // Arrange
+ ViewDataDictionary vdd = new ViewDataDictionary();
+
+ // Act
+ string formattedValue = vdd.Eval("foo", "for{0}mat");
+
+ // Assert
+ Assert.Equal(String.Empty, formattedValue);
+ }
+
+ [Fact]
+ public void EvalFormatWithEmptyFormatReturnsViewData()
+ {
+ // Arrange
+ ViewDataDictionary vdd = new ViewDataDictionary();
+ vdd["foo"] = "value";
+
+ // Act
+ string formattedValue = vdd.Eval("foo", "");
+
+ // Assert
+ Assert.Equal("value", formattedValue);
+ }
+
+ [Fact]
+ public void EvalFormatWithFormatReturnsFormattedViewData()
+ {
+ // Arrange
+ ViewDataDictionary vdd = new ViewDataDictionary();
+ vdd["foo"] = "value";
+
+ // Act
+ string formattedValue = vdd.Eval("foo", "for{0}mat");
+
+ // Assert
+ Assert.Equal("forvaluemat", formattedValue);
+ }
+
+ [Fact]
+ public void EvalPropertyNamedModel()
+ {
+ // Arrange
+ ViewDataDictionary vdd = new ViewDataDictionary();
+ vdd["Title"] = "Home Page";
+ vdd["Message"] = "Welcome to ASP.NET MVC!";
+ vdd.Model = new TheQueryStringParam
+ {
+ Name = "The Name",
+ Value = "The Value",
+ Model = "The Model",
+ };
+
+ // Act
+ object o = vdd.Eval("Model");
+
+ // Assert
+ Assert.Equal("The Model", o);
+ }
+
+ [Fact]
+ public void EvalSubPropertyNamedValueInModel()
+ {
+ // Arrange
+ ViewDataDictionary vdd = new ViewDataDictionary();
+ vdd["Title"] = "Home Page";
+ vdd["Message"] = "Welcome to ASP.NET MVC!";
+ vdd.Model = new TheQueryStringParam
+ {
+ Name = "The Name",
+ Value = "The Value",
+ Model = "The Model",
+ };
+
+ // Act
+ object o = vdd.Eval("Value");
+
+ // Assert
+ Assert.Equal("The Value", o);
+ }
+
+ [Fact]
+ public void GetViewDataInfoFromDictionary()
+ {
+ // Arrange
+ ViewDataDictionary fooVdd = new ViewDataDictionary()
+ {
+ { "Bar", "barValue" }
+ };
+ ViewDataDictionary vdd = new ViewDataDictionary()
+ {
+ { "Foo", fooVdd }
+ };
+
+ // Act
+ ViewDataInfo info = vdd.GetViewDataInfo("foo.bar");
+
+ // Assert
+ Assert.NotNull(info);
+ Assert.Equal(fooVdd, info.Container);
+ Assert.Equal("barValue", info.Value);
+ }
+
+ [Fact]
+ public void GetViewDataInfoFromDictionaryWithMissingEntry()
+ {
+ // Arrange
+ ViewDataDictionary vdd = new ViewDataDictionary();
+
+ // Act
+ ViewDataInfo info = vdd.GetViewDataInfo("foo");
+
+ // Assert
+ Assert.Null(info);
+ }
+
+ [Fact]
+ public void GetViewDataInfoFromDictionaryWithNullEntry()
+ {
+ // Arrange
+ ViewDataDictionary vdd = new ViewDataDictionary()
+ {
+ { "Foo", null }
+ };
+
+ // Act
+ ViewDataInfo info = vdd.GetViewDataInfo("foo");
+
+ // Assert
+ Assert.NotNull(info);
+ Assert.Equal(vdd, info.Container);
+ Assert.Null(info.Value);
+ }
+
+ [Fact]
+ public void GetViewDataInfoFromModel()
+ {
+ // Arrange
+ object model = new { foo = "fooValue" };
+ ViewDataDictionary vdd = new ViewDataDictionary(model);
+
+ PropertyDescriptor propDesc = TypeDescriptor.GetProperties(model).Find("foo", true /* ignoreCase */);
+
+ // Act
+ ViewDataInfo info = vdd.GetViewDataInfo("foo");
+
+ // Assert
+ Assert.NotNull(info);
+ Assert.Equal(model, info.Container);
+ Assert.Equal(propDesc, info.PropertyDescriptor);
+ Assert.Equal("fooValue", info.Value);
+ }
+
+ [Fact]
+ public void FabricatesModelMetadataFromModelWhenModelMetadataHasNotBeenSet()
+ {
+ // Arrange
+ object model = new { foo = "fooValue", bar = "barValue" };
+ ViewDataDictionary vdd = new ViewDataDictionary(model);
+
+ // Act
+ ModelMetadata metadata = vdd.ModelMetadata;
+
+ // Assert
+ Assert.NotNull(metadata);
+ Assert.Equal(model.GetType(), metadata.ModelType);
+ }
+
+ [Fact]
+ public void ReturnsExistingModelMetadata()
+ {
+ // Arrange
+ object model = new { foo = "fooValue", bar = "barValue" };
+ ModelMetadata originalMetadata = ModelMetadataProviders.Current.GetMetadataForType(() => model, model.GetType());
+ ViewDataDictionary vdd = new ViewDataDictionary(model) { ModelMetadata = originalMetadata };
+
+ // Act
+ ModelMetadata metadata = vdd.ModelMetadata;
+
+ // Assert
+ Assert.Same(originalMetadata, metadata);
+ }
+
+ [Fact]
+ public void ModelMetadataIsNullIfModelMetadataHasNotBeenSetAndModelIsNull()
+ {
+ // Arrange
+ ViewDataDictionary vdd = new ViewDataDictionary();
+
+ // Act
+ ModelMetadata metadata = vdd.ModelMetadata;
+
+ // Assert
+ Assert.Null(metadata);
+ }
+
+ [Fact]
+ public void ModelMetadataCanBeFabricatedWithNullModelAndGenericViewDataDictionary()
+ {
+ // Arrange
+ ViewDataDictionary vdd = new ViewDataDictionary<Exception>();
+
+ // Act
+ ModelMetadata metadata = vdd.ModelMetadata;
+
+ // Assert
+ Assert.NotNull(metadata);
+ Assert.Equal(typeof(Exception), metadata.ModelType);
+ }
+
+ [Fact]
+ public void ModelSetterThrowsIfValueIsNullAndModelTypeIsNonNullable()
+ {
+ // Arrange
+ ViewDataDictionary vdd = new ViewDataDictionary<int>();
+
+ // Act & assert
+ Assert.Throws<InvalidOperationException>(
+ delegate { vdd.Model = null; },
+ @"The model item passed into the dictionary is null, but this dictionary requires a non-null model item of type 'System.Int32'.");
+ }
+
+ [Fact]
+ public void ChangingModelReplacesModelMetadata()
+ {
+ // Arrange
+ ViewDataDictionary vdd = new ViewDataDictionary(new Object());
+ ModelMetadata originalMetadata = vdd.ModelMetadata;
+
+ // Act
+ vdd.Model = "New Model";
+
+ // Assert
+ Assert.NotSame(originalMetadata, vdd.ModelMetadata);
+ }
+
+ public class TheQueryStringParam
+ {
+ public string Name { get; set; }
+ public string Value { get; set; }
+ public string Model { get; set; }
+ }
+
+ public class Person : CustomTypeDescriptor
+ {
+ public override PropertyDescriptorCollection GetProperties()
+ {
+ return new PropertyDescriptorCollection(new PersonPropertyDescriptor[] { new PersonPropertyDescriptor() });
+ }
+ }
+
+ public class PersonPropertyDescriptor : PropertyDescriptor
+ {
+ public PersonPropertyDescriptor()
+ : base("Bar.Baz", null)
+ {
+ }
+
+ public override object GetValue(object component)
+ {
+ return "Quux";
+ }
+
+ public override bool CanResetValue(object component)
+ {
+ return false;
+ }
+
+ public override Type ComponentType
+ {
+ get { return typeof(Person); }
+ }
+
+ public override bool IsReadOnly
+ {
+ get { return false; }
+ }
+
+ public override Type PropertyType
+ {
+ get { return typeof(string); }
+ }
+
+ public override void ResetValue(object component)
+ {
+ }
+
+ public override void SetValue(object component, object value)
+ {
+ }
+
+ public override bool ShouldSerializeValue(object component)
+ {
+ return true;
+ }
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Test/ViewDataInfoTest.cs b/test/System.Web.Mvc.Test/Test/ViewDataInfoTest.cs
new file mode 100644
index 00000000..f2293550
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Test/ViewDataInfoTest.cs
@@ -0,0 +1,64 @@
+using Xunit;
+
+namespace System.Web.Mvc.Test
+{
+ public class ViewDataInfoTest
+ {
+ [Fact]
+ public void ViewDataInfoDoesNotCallAccessorUntilValuePropertyAccessed()
+ {
+ // Arrange
+ bool called = false;
+ ViewDataInfo vdi = new ViewDataInfo(() =>
+ {
+ called = true;
+ return 21;
+ });
+
+ // Act & Assert
+ Assert.False(called);
+ object result = vdi.Value;
+ Assert.True(called);
+ Assert.Equal(21, result);
+ }
+
+ [Fact]
+ public void AccessorIsOnlyCalledOnce()
+ {
+ // Arrange
+ int callCount = 0;
+ ViewDataInfo vdi = new ViewDataInfo(() =>
+ {
+ ++callCount;
+ return null;
+ });
+
+ // Act & Assert
+ Assert.Equal(0, callCount);
+ object unused;
+ unused = vdi.Value;
+ unused = vdi.Value;
+ unused = vdi.Value;
+ Assert.Equal(1, callCount);
+ }
+
+ [Fact]
+ public void SettingExplicitValueOverridesAccessorMethod()
+ {
+ // Arrange
+ bool called = false;
+ ViewDataInfo vdi = new ViewDataInfo(() =>
+ {
+ called = true;
+ return null;
+ });
+
+ // Act & Assert
+ Assert.False(called);
+ vdi.Value = 42;
+ object result = vdi.Value;
+ Assert.False(called);
+ Assert.Equal(42, result);
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Test/ViewEngineCollectionTest.cs b/test/System.Web.Mvc.Test/Test/ViewEngineCollectionTest.cs
new file mode 100644
index 00000000..331e37f7
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Test/ViewEngineCollectionTest.cs
@@ -0,0 +1,631 @@
+using System.Collections.Generic;
+using System.Linq;
+using Moq;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Mvc.Test
+{
+ public class ViewEngineCollectionTest
+ {
+ [Fact]
+ public void ListWrappingConstructor()
+ {
+ // Arrange
+ List<IViewEngine> list = new List<IViewEngine>() { new Mock<IViewEngine>().Object, new Mock<IViewEngine>().Object };
+
+ // Act
+ ViewEngineCollection collection = new ViewEngineCollection(list);
+
+ // Assert
+ Assert.Equal(2, collection.Count);
+ Assert.Same(list[0], collection[0]);
+ Assert.Same(list[1], collection[1]);
+ }
+
+ [Fact]
+ public void ListWrappingConstructorThrowsIfListIsNull()
+ {
+ // Act & Assert
+ Assert.ThrowsArgumentNull(
+ delegate { new ViewEngineCollection((IList<IViewEngine>)null); },
+ "list");
+ }
+
+ [Fact]
+ public void DefaultConstructor()
+ {
+ // Act
+ ViewEngineCollection collection = new ViewEngineCollection();
+
+ // Assert
+ Assert.Empty(collection);
+ }
+
+ [Fact]
+ public void AddNullViewEngineThrows()
+ {
+ // Arrange
+ ViewEngineCollection collection = new ViewEngineCollection();
+
+ // Act & Assert
+ Assert.ThrowsArgumentNull(
+ delegate { collection.Add(null); },
+ "item");
+ }
+
+ [Fact]
+ public void SetNullViewEngineThrows()
+ {
+ // Arrange
+ ViewEngineCollection collection = new ViewEngineCollection();
+ collection.Add(new Mock<IViewEngine>().Object);
+
+ // Act & Assert
+ Assert.ThrowsArgumentNull(
+ delegate { collection[0] = null; },
+ "item");
+ }
+
+ [Fact]
+ public void FindPartialViewAggregatesAllSearchedLocationsIfAllEnginesFail()
+ {
+ // Arrange
+ ControllerContext context = new Mock<ControllerContext>().Object;
+ ViewEngineCollection viewEngineCollection = new ViewEngineCollection();
+ Mock<IViewEngine> engine1 = new Mock<IViewEngine>();
+ ViewEngineResult engine1Result = new ViewEngineResult(new[] { "location1", "location2" });
+ engine1.Setup(e => e.FindPartialView(context, "partial", It.IsAny<bool>())).Returns(engine1Result);
+ Mock<IViewEngine> engine2 = new Mock<IViewEngine>();
+ ViewEngineResult engine2Result = new ViewEngineResult(new[] { "location3", "location4" });
+ engine2.Setup(e => e.FindPartialView(context, "partial", It.IsAny<bool>())).Returns(engine2Result);
+ viewEngineCollection.Add(engine1.Object);
+ viewEngineCollection.Add(engine2.Object);
+
+ // Act
+ ViewEngineResult result = viewEngineCollection.FindPartialView(context, "partial");
+
+ // Assert
+ Assert.Null(result.View);
+ Assert.Equal(4, result.SearchedLocations.Count());
+ Assert.True(result.SearchedLocations.Contains("location1"));
+ Assert.True(result.SearchedLocations.Contains("location2"));
+ Assert.True(result.SearchedLocations.Contains("location3"));
+ Assert.True(result.SearchedLocations.Contains("location4"));
+ }
+
+ [Fact]
+ public void FindPartialViewFailureWithOneEngine()
+ {
+ // Arrange
+ ControllerContext context = new Mock<ControllerContext>().Object;
+ ViewEngineCollection collection = new ViewEngineCollection();
+ Mock<IViewEngine> engine = new Mock<IViewEngine>();
+ ViewEngineResult engineResult = new ViewEngineResult(new[] { "location1", "location2" });
+ engine.Setup(e => e.FindPartialView(context, "partial", It.IsAny<bool>())).Returns(engineResult);
+ collection.Add(engine.Object);
+
+ // Act
+ ViewEngineResult result = collection.FindPartialView(context, "partial");
+
+ // Assert
+ Assert.Null(result.View);
+ Assert.Equal(2, result.SearchedLocations.Count());
+ Assert.True(result.SearchedLocations.Contains("location1"));
+ Assert.True(result.SearchedLocations.Contains("location2"));
+ }
+
+ [Fact]
+ public void FindPartialViewLooksAtCacheFirst()
+ {
+ // Arrange
+ ControllerContext context = new Mock<ControllerContext>().Object;
+ Mock<IViewEngine> engine = new Mock<IViewEngine>();
+ ViewEngineResult engineResult = new ViewEngineResult(new Mock<IView>().Object, engine.Object);
+ engine.Setup(e => e.FindPartialView(context, "partial", true)).Returns(engineResult);
+ ViewEngineCollection collection = new ViewEngineCollection()
+ {
+ engine.Object,
+ };
+
+ // Act
+ ViewEngineResult result = collection.FindPartialView(context, "partial");
+
+ // Assert
+ Assert.Same(engineResult, result);
+ engine.Verify(e => e.FindPartialView(context, "partial", true), Times.Once());
+ engine.Verify(e => e.FindPartialView(context, "partial", false), Times.Never());
+ }
+
+ [Fact]
+ public void FindPartialViewLooksAtLocatorIfCacheEmpty()
+ {
+ // Arrange
+ ControllerContext context = new Mock<ControllerContext>().Object;
+ Mock<IViewEngine> engine = new Mock<IViewEngine>();
+ ViewEngineResult engineResult = new ViewEngineResult(new Mock<IView>().Object, engine.Object);
+ engine.Setup(e => e.FindPartialView(context, "partial", true)).Returns(new ViewEngineResult(new[] { "path" }));
+ engine.Setup(e => e.FindPartialView(context, "partial", false)).Returns(engineResult);
+ ViewEngineCollection collection = new ViewEngineCollection()
+ {
+ engine.Object,
+ };
+
+ // Act
+ ViewEngineResult result = collection.FindPartialView(context, "partial");
+
+ // Assert
+ Assert.Same(engineResult, result);
+ engine.Verify(e => e.FindPartialView(context, "partial", true), Times.Once());
+ engine.Verify(e => e.FindPartialView(context, "partial", false), Times.Once());
+ }
+
+ [Fact]
+ public void FindPartialViewIgnoresSearchLocationsFromCache()
+ {
+ // Arrange
+ ControllerContext context = new Mock<ControllerContext>().Object;
+ Mock<IViewEngine> engine = new Mock<IViewEngine>();
+ engine.Setup(e => e.FindPartialView(context, "partial", true)).Returns(new ViewEngineResult(new[] { "cachePath" }));
+ engine.Setup(e => e.FindPartialView(context, "partial", false)).Returns(new ViewEngineResult(new[] { "locatorPath" }));
+ ViewEngineCollection collection = new ViewEngineCollection()
+ {
+ engine.Object,
+ };
+
+ // Act
+ ViewEngineResult result = collection.FindPartialView(context, "partial");
+
+ // Assert
+ string searchedLocation = Assert.Single(result.SearchedLocations);
+ Assert.Equal("locatorPath", searchedLocation);
+ engine.Verify(e => e.FindPartialView(context, "partial", true), Times.Once());
+ engine.Verify(e => e.FindPartialView(context, "partial", false), Times.Once());
+ }
+
+ [Fact]
+ public void FindPartialViewIteratesThroughCollectionUntilFindsSuccessfulEngine()
+ {
+ // Arrange
+ ControllerContext context = new Mock<ControllerContext>().Object;
+ ViewEngineCollection collection = new ViewEngineCollection();
+ Mock<IViewEngine> engine1 = new Mock<IViewEngine>();
+ ViewEngineResult engine1Result = new ViewEngineResult(new[] { "location1", "location2" });
+ engine1.Setup(e => e.FindPartialView(context, "partial", It.IsAny<bool>())).Returns(engine1Result);
+ Mock<IViewEngine> engine2 = new Mock<IViewEngine>();
+ ViewEngineResult engine2Result = new ViewEngineResult(new Mock<IView>().Object, engine2.Object);
+ engine2.Setup(e => e.FindPartialView(context, "partial", It.IsAny<bool>())).Returns(engine2Result);
+ collection.Add(engine1.Object);
+ collection.Add(engine2.Object);
+
+ // Act
+ ViewEngineResult result = collection.FindPartialView(context, "partial");
+
+ // Assert
+ Assert.Same(engine2Result, result);
+ }
+
+ [Fact]
+ public void FindPartialViewRemovesDuplicateSearchedLocationsFromMultipleEngines()
+ {
+ // Arrange
+ ControllerContext context = new Mock<ControllerContext>().Object;
+ Mock<IViewEngine> engine1 = new Mock<IViewEngine>();
+ ViewEngineResult engine1Result = new ViewEngineResult(new[] { "repeatLocation", "location1" });
+ engine1.Setup(e => e.FindPartialView(context, "partial", It.IsAny<bool>())).Returns(engine1Result);
+ Mock<IViewEngine> engine2 = new Mock<IViewEngine>();
+ ViewEngineResult engine2Result = new ViewEngineResult(new[] { "location2", "repeatLocation" });
+ engine2.Setup(e => e.FindPartialView(context, "partial", It.IsAny<bool>())).Returns(engine2Result);
+ ViewEngineCollection viewEngineCollection = new ViewEngineCollection()
+ {
+ engine1.Object,
+ engine2.Object,
+ };
+
+ // Act
+ ViewEngineResult result = viewEngineCollection.FindPartialView(context, "partial");
+
+ // Assert
+ var expectedLocations = new[] { "repeatLocation", "location1", "location2" };
+ Assert.Null(result.View);
+ Assert.Equal(expectedLocations, result.SearchedLocations.ToArray());
+ }
+
+ [Fact]
+ public void FindPartialViewReturnsNoViewAndEmptySearchedLocationsIfCollectionEmpty()
+ {
+ // Arrange
+ ControllerContext context = new Mock<ControllerContext>().Object;
+ ViewEngineCollection collection = new ViewEngineCollection();
+
+ // Act
+ ViewEngineResult result = collection.FindPartialView(context, "partial");
+
+ // Assert
+ Assert.Null(result.View);
+ Assert.Empty(result.SearchedLocations);
+ }
+
+ [Fact]
+ public void FindPartialViewReturnsValueFromFirstSuccessfulEngine()
+ {
+ // Arrange
+ ControllerContext context = new Mock<ControllerContext>().Object;
+ ViewEngineCollection collection = new ViewEngineCollection();
+ Mock<IViewEngine> engine1 = new Mock<IViewEngine>();
+ ViewEngineResult engine1Result = new ViewEngineResult(new Mock<IView>().Object, engine1.Object);
+ engine1.Setup(e => e.FindPartialView(context, "partial", It.IsAny<bool>())).Returns(engine1Result);
+ Mock<IViewEngine> engine2 = new Mock<IViewEngine>();
+ ViewEngineResult engine2Result = new ViewEngineResult(new Mock<IView>().Object, engine2.Object);
+ engine2.Setup(e => e.FindPartialView(context, "partial", It.IsAny<bool>())).Returns(engine2Result);
+ collection.Add(engine1.Object);
+ collection.Add(engine2.Object);
+
+ // Act
+ ViewEngineResult result = collection.FindPartialView(context, "partial");
+
+ // Assert
+ Assert.Same(engine1Result, result);
+ }
+
+ [Fact]
+ public void FindPartialViewSuccessWithOneEngine()
+ {
+ // Arrange
+ ControllerContext context = new Mock<ControllerContext>().Object;
+ ViewEngineCollection collection = new ViewEngineCollection();
+ Mock<IViewEngine> engine = new Mock<IViewEngine>();
+ ViewEngineResult engineResult = new ViewEngineResult(new Mock<IView>().Object, engine.Object);
+ engine.Setup(e => e.FindPartialView(context, "partial", It.IsAny<bool>())).Returns(engineResult);
+ collection.Add(engine.Object);
+
+ // Act
+ ViewEngineResult result = collection.FindPartialView(context, "partial");
+
+ // Assert
+ Assert.Same(engineResult, result);
+ }
+
+ [Fact]
+ public void FindPartialViewThrowsIfPartialViewNameIsEmpty()
+ {
+ // Arrange
+ ControllerContext context = new Mock<ControllerContext>().Object;
+ ViewEngineCollection collection = new ViewEngineCollection();
+
+ // Act & Assert
+ Assert.ThrowsArgumentNullOrEmpty(
+ () => collection.FindPartialView(context, ""),
+ "partialViewName");
+ }
+
+ [Fact]
+ public void FindPartialViewThrowsIfPartialViewNameIsNull()
+ {
+ // Arrange
+ ControllerContext context = new Mock<ControllerContext>().Object;
+ ViewEngineCollection collection = new ViewEngineCollection();
+
+ // Act & Assert
+ Assert.ThrowsArgumentNullOrEmpty(
+ () => collection.FindPartialView(context, null),
+ "partialViewName");
+ }
+
+ [Fact]
+ public void FindPartialViewThrowsIfControllerContextIsNull()
+ {
+ // Arrange
+ ViewEngineCollection collection = new ViewEngineCollection();
+
+ // Act & Assert
+ Assert.ThrowsArgumentNull(
+ () => collection.FindPartialView(null, "partial"),
+ "controllerContext");
+ }
+
+ [Fact]
+ public void FindViewAggregatesAllSearchedLocationsIfAllEnginesFail()
+ {
+ // Arrange
+ ControllerContext context = new Mock<ControllerContext>().Object;
+ ViewEngineCollection collection = new ViewEngineCollection();
+ Mock<IViewEngine> engine1 = new Mock<IViewEngine>();
+ ViewEngineResult engine1Result = new ViewEngineResult(new[] { "location1", "location2" });
+ engine1.Setup(e => e.FindView(context, "view", "master", It.IsAny<bool>())).Returns(engine1Result);
+ Mock<IViewEngine> engine2 = new Mock<IViewEngine>();
+ ViewEngineResult engine2Result = new ViewEngineResult(new[] { "location3", "location4" });
+ engine2.Setup(e => e.FindView(context, "view", "master", It.IsAny<bool>())).Returns(engine2Result);
+ collection.Add(engine1.Object);
+ collection.Add(engine2.Object);
+
+ // Act
+ ViewEngineResult result = collection.FindView(context, "view", "master");
+
+ // Assert
+ Assert.Null(result.View);
+ Assert.Equal(4, result.SearchedLocations.Count());
+ Assert.True(result.SearchedLocations.Contains("location1"));
+ Assert.True(result.SearchedLocations.Contains("location2"));
+ Assert.True(result.SearchedLocations.Contains("location3"));
+ Assert.True(result.SearchedLocations.Contains("location4"));
+ }
+
+ [Fact]
+ public void FindViewFailureWithOneEngine()
+ {
+ // Arrange
+ ControllerContext context = new Mock<ControllerContext>().Object;
+ ViewEngineCollection collection = new ViewEngineCollection();
+ Mock<IViewEngine> engine = new Mock<IViewEngine>();
+ ViewEngineResult engineResult = new ViewEngineResult(new[] { "location1", "location2" });
+ engine.Setup(e => e.FindView(context, "view", "master", It.IsAny<bool>())).Returns(engineResult);
+ collection.Add(engine.Object);
+
+ // Act
+ ViewEngineResult result = collection.FindView(context, "view", "master");
+
+ // Assert
+ Assert.Null(result.View);
+ Assert.Equal(2, result.SearchedLocations.Count());
+ Assert.True(result.SearchedLocations.Contains("location1"));
+ Assert.True(result.SearchedLocations.Contains("location2"));
+ }
+
+ [Fact]
+ public void FindViewLooksAtCacheFirst()
+ {
+ // Arrange
+ ControllerContext context = new Mock<ControllerContext>().Object;
+ Mock<IViewEngine> engine = new Mock<IViewEngine>();
+ ViewEngineResult engineResult = new ViewEngineResult(new Mock<IView>().Object, engine.Object);
+ engine.Setup(e => e.FindView(context, "view", "master", true)).Returns(engineResult);
+ ViewEngineCollection collection = new ViewEngineCollection()
+ {
+ engine.Object,
+ };
+
+ // Act
+ ViewEngineResult result = collection.FindView(context, "view", "master");
+
+ // Assert
+ Assert.Same(engineResult, result);
+ engine.Verify(e => e.FindView(context, "view", "master", true), Times.Once());
+ engine.Verify(e => e.FindView(context, "view", "master", false), Times.Never());
+ }
+
+ [Fact]
+ public void FindViewLooksAtLocatorIfCacheEmpty()
+ {
+ // Arrange
+ ControllerContext context = new Mock<ControllerContext>().Object;
+ Mock<IViewEngine> engine = new Mock<IViewEngine>();
+ ViewEngineResult engineResult = new ViewEngineResult(new Mock<IView>().Object, engine.Object);
+ engine.Setup(e => e.FindView(context, "view", "master", true)).Returns(new ViewEngineResult(new[] { "path" }));
+ engine.Setup(e => e.FindView(context, "view", "master", false)).Returns(engineResult);
+ ViewEngineCollection collection = new ViewEngineCollection()
+ {
+ engine.Object,
+ };
+
+ // Act
+ ViewEngineResult result = collection.FindView(context, "view", "master");
+
+ // Assert
+ Assert.Same(engineResult, result);
+ engine.Verify(e => e.FindView(context, "view", "master", true), Times.Once());
+ engine.Verify(e => e.FindView(context, "view", "master", false), Times.Once());
+ }
+
+ [Fact]
+ public void FindViewIgnoresSearchLocationsFromCache()
+ {
+ // Arrange
+ ControllerContext context = new Mock<ControllerContext>().Object;
+ Mock<IViewEngine> engine = new Mock<IViewEngine>();
+ engine.Setup(e => e.FindView(context, "view", "master", true)).Returns(new ViewEngineResult(new[] { "cachePath" }));
+ engine.Setup(e => e.FindView(context, "view", "master", false)).Returns(new ViewEngineResult(new[] { "locatorPath" }));
+ ViewEngineCollection collection = new ViewEngineCollection()
+ {
+ engine.Object,
+ };
+
+ // Act
+ ViewEngineResult result = collection.FindView(context, "view", "master");
+
+ // Assert
+ string searchedLocation = Assert.Single(result.SearchedLocations);
+ Assert.Equal("locatorPath", searchedLocation);
+ engine.Verify(e => e.FindView(context, "view", "master", true), Times.Once());
+ engine.Verify(e => e.FindView(context, "view", "master", false), Times.Once());
+ }
+
+ [Fact]
+ public void FindViewIteratesThroughCollectionUntilFindsSuccessfulEngine()
+ {
+ // Arrange
+ ControllerContext context = new Mock<ControllerContext>().Object;
+ ViewEngineCollection collection = new ViewEngineCollection();
+ Mock<IViewEngine> engine1 = new Mock<IViewEngine>();
+ ViewEngineResult engine1Result = new ViewEngineResult(new[] { "location1", "location2" });
+ engine1.Setup(e => e.FindView(context, "view", "master", It.IsAny<bool>())).Returns(engine1Result);
+ Mock<IViewEngine> engine2 = new Mock<IViewEngine>();
+ ViewEngineResult engine2Result = new ViewEngineResult(new Mock<IView>().Object, engine2.Object);
+ engine2.Setup(e => e.FindView(context, "view", "master", It.IsAny<bool>())).Returns(engine2Result);
+ collection.Add(engine1.Object);
+ collection.Add(engine2.Object);
+
+ // Act
+ ViewEngineResult result = collection.FindView(context, "view", "master");
+
+ // Assert
+ Assert.Same(engine2Result, result);
+ }
+
+ [Fact]
+ public void FindViewRemovesDuplicateSearchedLocationsFromMultipleEngines()
+ {
+ // Arrange
+ ControllerContext context = new Mock<ControllerContext>().Object;
+ Mock<IViewEngine> engine1 = new Mock<IViewEngine>();
+ ViewEngineResult engine1Result = new ViewEngineResult(new[] { "repeatLocation", "location1" });
+ engine1.Setup(e => e.FindView(context, "view", "master", It.IsAny<bool>())).Returns(engine1Result);
+ Mock<IViewEngine> engine2 = new Mock<IViewEngine>();
+ ViewEngineResult engine2Result = new ViewEngineResult(new[] { "location2", "repeatLocation" });
+ engine2.Setup(e => e.FindView(context, "view", "master", It.IsAny<bool>())).Returns(engine2Result);
+ ViewEngineCollection collection = new ViewEngineCollection()
+ {
+ engine1.Object,
+ engine2.Object,
+ };
+
+ // Act
+ ViewEngineResult result = collection.FindView(context, "view", "master");
+
+ // Assert
+ Assert.Null(result.View);
+ var expectedLocations = new[] { "repeatLocation", "location1", "location2" };
+ Assert.Equal(expectedLocations, result.SearchedLocations.ToArray());
+ }
+
+ [Fact]
+ public void FindViewReturnsNoViewAndEmptySearchedLocationsIfCollectionEmpty()
+ {
+ // Arrange
+ ControllerContext context = new Mock<ControllerContext>().Object;
+ ViewEngineCollection collection = new ViewEngineCollection();
+
+ // Act
+ ViewEngineResult result = collection.FindView(context, "view", null);
+
+ // Assert
+ Assert.Null(result.View);
+ Assert.Empty(result.SearchedLocations);
+ }
+
+ [Fact]
+ public void FindViewReturnsValueFromFirstSuccessfulEngine()
+ {
+ // Arrange
+ ControllerContext context = new Mock<ControllerContext>().Object;
+ ViewEngineCollection collection = new ViewEngineCollection();
+ Mock<IViewEngine> engine1 = new Mock<IViewEngine>();
+ ViewEngineResult engine1Result = new ViewEngineResult(new Mock<IView>().Object, engine1.Object);
+ engine1.Setup(e => e.FindView(context, "view", "master", It.IsAny<bool>())).Returns(engine1Result);
+ Mock<IViewEngine> engine2 = new Mock<IViewEngine>();
+ ViewEngineResult engine2Result = new ViewEngineResult(new Mock<IView>().Object, engine2.Object);
+ engine2.Setup(e => e.FindView(context, "view", "master", It.IsAny<bool>())).Returns(engine2Result);
+ collection.Add(engine1.Object);
+ collection.Add(engine2.Object);
+
+ // Act
+ ViewEngineResult result = collection.FindView(context, "view", "master");
+
+ // Assert
+ Assert.Same(engine1Result, result);
+ }
+
+ [Fact]
+ public void FindViewSuccessWithOneEngine()
+ {
+ // Arrange
+ ControllerContext context = new Mock<ControllerContext>().Object;
+ ViewEngineCollection collection = new ViewEngineCollection();
+ Mock<IViewEngine> engine = new Mock<IViewEngine>();
+ ViewEngineResult engineResult = new ViewEngineResult(new Mock<IView>().Object, engine.Object);
+ engine.Setup(e => e.FindView(context, "view", "master", It.IsAny<bool>())).Returns(engineResult);
+ collection.Add(engine.Object);
+
+ // Act
+ ViewEngineResult result = collection.FindView(context, "view", "master");
+
+ // Assert
+ Assert.Same(engineResult, result);
+ }
+
+ [Fact]
+ public void FindViewThrowsIfControllerContextIsNull()
+ {
+ // Arrange
+ ViewEngineCollection collection = new ViewEngineCollection();
+
+ // Act & Assert
+ Assert.ThrowsArgumentNull(
+ () => collection.FindView(null, "view", null),
+ "controllerContext"
+ );
+ }
+
+ [Fact]
+ public void FindViewThrowsIfViewNameIsEmpty()
+ {
+ // Arrange
+ ControllerContext context = new Mock<ControllerContext>().Object;
+ ViewEngineCollection collection = new ViewEngineCollection();
+
+ // Act & Assert
+ Assert.ThrowsArgumentNullOrEmpty(
+ () => collection.FindView(context, "", null),
+ "viewName"
+ );
+ }
+
+ [Fact]
+ public void FindViewThrowsIfViewNameIsNull()
+ {
+ // Arrange
+ ControllerContext context = new Mock<ControllerContext>().Object;
+ ViewEngineCollection collection = new ViewEngineCollection();
+
+ // Act & Assert
+ Assert.ThrowsArgumentNullOrEmpty(
+ () => collection.FindView(context, null, null),
+ "viewName"
+ );
+ }
+
+ [Fact]
+ public void FindViewDelegatesToResolver()
+ {
+ // Arrange
+ Mock<IView> view = new Mock<IView>();
+ ControllerContext context = new ControllerContext();
+ Mock<IViewEngine> locatedEngine = new Mock<IViewEngine>();
+ ViewEngineResult engineResult = new ViewEngineResult(view.Object, locatedEngine.Object);
+ locatedEngine.Setup(e => e.FindView(context, "ViewName", "MasterName", true))
+ .Returns(engineResult);
+ Mock<IViewEngine> secondEngine = new Mock<IViewEngine>();
+ Resolver<IEnumerable<IViewEngine>> resolver = new Resolver<IEnumerable<IViewEngine>> { Current = new IViewEngine[] { locatedEngine.Object, secondEngine.Object } };
+ ViewEngineCollection engines = new ViewEngineCollection(resolver);
+
+ // Act
+ ViewEngineResult result = engines.FindView(context, "ViewName", "MasterName");
+
+ // Assert
+ Assert.Same(engineResult, result);
+ secondEngine.Verify(e => e.FindView(context, "ViewName", "MasterName", It.IsAny<bool>()), Times.Never());
+ }
+
+ [Fact]
+ public void FindPartialViewDelegatesToResolver()
+ {
+ // Arrange
+ Mock<IView> view = new Mock<IView>();
+ ControllerContext context = new ControllerContext();
+ Mock<IViewEngine> locatedEngine = new Mock<IViewEngine>();
+ ViewEngineResult engineResult = new ViewEngineResult(view.Object, locatedEngine.Object);
+ locatedEngine.Setup(e => e.FindPartialView(context, "ViewName", true))
+ .Returns(engineResult);
+ Mock<IViewEngine> secondEngine = new Mock<IViewEngine>();
+ Resolver<IEnumerable<IViewEngine>> resolver = new Resolver<IEnumerable<IViewEngine>> { Current = new IViewEngine[] { locatedEngine.Object, secondEngine.Object } };
+ ViewEngineCollection engines = new ViewEngineCollection(resolver);
+
+ // Act
+ ViewEngineResult result = engines.FindPartialView(context, "ViewName");
+
+ // Assert
+ Assert.Same(engineResult, result);
+ secondEngine.Verify(e => e.FindPartialView(context, "ViewName", It.IsAny<bool>()), Times.Never());
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Test/ViewEngineResultTest.cs b/test/System.Web.Mvc.Test/Test/ViewEngineResultTest.cs
new file mode 100644
index 00000000..9791a683
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Test/ViewEngineResultTest.cs
@@ -0,0 +1,80 @@
+using Moq;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Mvc.Test
+{
+ public class ViewEngineResultTest
+ {
+ [Fact]
+ public void ConstructorThrowsIfSearchedLocationsIsNull()
+ {
+ // Arrange
+ string[] searchedLocations = null;
+
+ // Act & Assert
+ Assert.ThrowsArgumentNull(
+ delegate { new ViewEngineResult(searchedLocations); }, "searchedLocations");
+ }
+
+ [Fact]
+ public void ConstructorThrowsIfViewIsNull()
+ {
+ // Arrange
+ IView view = null;
+
+ // Act & Assert
+ Assert.ThrowsArgumentNull(
+ delegate { new ViewEngineResult(view, null); }, "view");
+ }
+
+ [Fact]
+ public void ConstructorThrowsIfViewEngineIsNull()
+ {
+ // Arrange
+ IView view = new Mock<IView>().Object;
+
+ // Act & Assert
+ Assert.ThrowsArgumentNull(
+ delegate { new ViewEngineResult(view, null); }, "viewEngine");
+ }
+
+ [Fact]
+ public void SearchedLocationsProperty()
+ {
+ // Arrange
+ string[] searchedLocations = new string[0];
+ ViewEngineResult result = new ViewEngineResult(searchedLocations);
+
+ // Act & Assert
+ Assert.Same(searchedLocations, result.SearchedLocations);
+ Assert.Null(result.View);
+ }
+
+ [Fact]
+ public void ViewProperty()
+ {
+ // Arrange
+ IView view = new Mock<IView>().Object;
+ IViewEngine viewEngine = new Mock<IViewEngine>().Object;
+ ViewEngineResult result = new ViewEngineResult(view, viewEngine);
+
+ // Act & Assert
+ Assert.Same(view, result.View);
+ Assert.Null(result.SearchedLocations);
+ }
+
+ [Fact]
+ public void ViewEngineProperty()
+ {
+ // Arrange
+ IView view = new Mock<IView>().Object;
+ IViewEngine viewEngine = new Mock<IViewEngine>().Object;
+ ViewEngineResult result = new ViewEngineResult(view, viewEngine);
+
+ // Act & Assert
+ Assert.Same(viewEngine, result.ViewEngine);
+ Assert.Null(result.SearchedLocations);
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Test/ViewEnginesTest.cs b/test/System.Web.Mvc.Test/Test/ViewEnginesTest.cs
new file mode 100644
index 00000000..966d3b43
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Test/ViewEnginesTest.cs
@@ -0,0 +1,19 @@
+using Xunit;
+
+namespace System.Web.Mvc.Test
+{
+ public class ViewEnginesTest
+ {
+ [Fact]
+ public void EnginesProperty()
+ {
+ // Act
+ ViewEngineCollection collection = ViewEngines.Engines;
+
+ // Assert
+ Assert.Equal(2, collection.Count);
+ Assert.IsType<WebFormViewEngine>(collection[0]);
+ Assert.IsType<RazorViewEngine>(collection[1]);
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Test/ViewMasterPageControlBuilderTest.cs b/test/System.Web.Mvc.Test/Test/ViewMasterPageControlBuilderTest.cs
new file mode 100644
index 00000000..c55d22d1
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Test/ViewMasterPageControlBuilderTest.cs
@@ -0,0 +1,39 @@
+using System.CodeDom;
+using System.Linq;
+using Xunit;
+
+namespace System.Web.Mvc.Test
+{
+ public class ViewMasterPageControlBuilderTest
+ {
+ [Fact]
+ public void BuilderWithoutInheritsDoesNothing()
+ {
+ // Arrange
+ var builder = new ViewMasterPageControlBuilder();
+ var derivedType = new CodeTypeDeclaration();
+ derivedType.BaseTypes.Add("basetype");
+
+ // Act
+ builder.ProcessGeneratedCode(null, null, derivedType, null, null);
+
+ // Assert
+ Assert.Equal("basetype", derivedType.BaseTypes.Cast<CodeTypeReference>().Single().BaseType);
+ }
+
+ [Fact]
+ public void BuilderWithInheritsSetsBaseType()
+ {
+ // Arrange
+ var builder = new ViewMasterPageControlBuilder { Inherits = "inheritedtype" };
+ var derivedType = new CodeTypeDeclaration();
+ derivedType.BaseTypes.Add("basetype");
+
+ // Act
+ builder.ProcessGeneratedCode(null, null, derivedType, null, null);
+
+ // Assert
+ Assert.Equal("inheritedtype", derivedType.BaseTypes.Cast<CodeTypeReference>().Single().BaseType);
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Test/ViewMasterPageTest.cs b/test/System.Web.Mvc.Test/Test/ViewMasterPageTest.cs
new file mode 100644
index 00000000..cfa0b3d6
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Test/ViewMasterPageTest.cs
@@ -0,0 +1,245 @@
+using System.IO;
+using System.Web.Routing;
+using System.Web.UI;
+using Moq;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Mvc.Test
+{
+ public class ViewMasterPageTest
+ {
+ [Fact]
+ public void GetModelFromViewPage()
+ {
+ // Arrange
+ ViewMasterPage vmp = new ViewMasterPage();
+ ViewPage vp = new ViewPage();
+ vmp.Page = vp;
+ object model = new object();
+ vp.ViewData = new ViewDataDictionary(model);
+
+ // Assert
+ Assert.Equal(model, vmp.Model);
+ }
+
+ [Fact]
+ public void GetModelFromViewPageStronglyTyped()
+ {
+ // Arrange
+ ViewMasterPage<FooModel> vmp = new ViewMasterPage<FooModel>();
+ ViewPage vp = new ViewPage();
+ vmp.Page = vp;
+ FooModel model = new FooModel();
+ vp.ViewData = new ViewDataDictionary(model);
+
+ // Assert
+ Assert.Equal(model, vmp.Model);
+ }
+
+ [Fact]
+ public void GetViewDataFromViewPage()
+ {
+ // Arrange
+ ViewMasterPage vmp = new ViewMasterPage();
+ ViewPage vp = new ViewPage();
+ vmp.Page = vp;
+ vp.ViewData = new ViewDataDictionary { { "a", "123" }, { "b", "456" } };
+
+ // Assert
+ Assert.Equal("123", vmp.ViewData.Eval("a"));
+ Assert.Equal("456", vmp.ViewData.Eval("b"));
+ }
+
+ [Fact]
+ public void GetViewItemFromViewPageTViewData()
+ {
+ // Arrange
+ MockViewMasterPageDummyViewData vmp = new MockViewMasterPageDummyViewData();
+ MockViewPageDummyViewData vp = new MockViewPageDummyViewData();
+ vmp.Page = vp;
+ vp.ViewData.Model = new DummyViewData { MyInt = 123, MyString = "abc" };
+
+ // Assert
+ Assert.Equal(123, vmp.ViewData.Model.MyInt);
+ Assert.Equal("abc", vmp.ViewData.Model.MyString);
+ }
+
+ [Fact]
+ public void GetWriterFromViewPage()
+ {
+ // Arrange
+ bool triggered = false;
+ HtmlTextWriter writer = new HtmlTextWriter(TextWriter.Null);
+ ViewMasterPage vmp = new ViewMasterPage();
+ MockViewPage vp = new MockViewPage();
+ vp.RenderCallback = delegate()
+ {
+ triggered = true;
+ Assert.Equal(writer, vmp.Writer);
+ };
+ vmp.Page = vp;
+
+ // Act & Assert
+ Assert.Null(vmp.Writer);
+ vp.RenderControl(writer);
+ Assert.Null(vmp.Writer);
+ Assert.True(triggered);
+ }
+
+ [Fact]
+ public void GetViewDataFromPageThrows()
+ {
+ // Arrange
+ ViewMasterPage vmp = new ViewMasterPage();
+ vmp.Page = new Page();
+
+ // Assert
+ Assert.Throws<InvalidOperationException>(
+ delegate { object foo = vmp.ViewData; },
+ "A ViewMasterPage can be used only with content pages that derive from ViewPage or ViewPage<TModel>.");
+ }
+
+ [Fact]
+ public void GetViewItemFromWrongGenericViewPageType()
+ {
+ // Arrange
+ MockViewMasterPageDummyViewData vmp = new MockViewMasterPageDummyViewData();
+ MockViewPageBogusViewData vp = new MockViewPageBogusViewData();
+ vmp.Page = vp;
+ vp.ViewData.Model = new SelectListItem();
+
+ // Assert
+ Assert.Throws<InvalidOperationException>(
+ delegate { object foo = vmp.ViewData.Model; },
+ "The model item passed into the dictionary is of type 'System.Web.Mvc.SelectListItem', but this dictionary requires a model item of type 'System.Web.Mvc.Test.ViewMasterPageTest+DummyViewData'.");
+ }
+
+ [Fact]
+ public void GetViewDataFromNullPageThrows()
+ {
+ // Arrange
+ MockViewMasterPageDummyViewData vmp = new MockViewMasterPageDummyViewData();
+
+ // Assert
+ Assert.Throws<InvalidOperationException>(
+ delegate { object foo = vmp.ViewData; },
+ "A ViewMasterPage can be used only with content pages that derive from ViewPage or ViewPage<TModel>.");
+ }
+
+ [Fact]
+ public void GetViewDataFromRegularPageThrows()
+ {
+ // Arrange
+ MockViewMasterPageDummyViewData vmp = new MockViewMasterPageDummyViewData();
+ vmp.Page = new Page();
+
+ // Assert
+ Assert.Throws<InvalidOperationException>(
+ delegate { object foo = vmp.ViewData; },
+ "A ViewMasterPage can be used only with content pages that derive from ViewPage or ViewPage<TModel>.");
+ }
+
+ [Fact]
+ public void GetHtmlHelperFromViewPage()
+ {
+ // Arrange
+ ViewMasterPage vmp = new ViewMasterPage();
+ ViewPage vp = new ViewPage();
+ vmp.Page = vp;
+ ViewContext vc = new Mock<ViewContext>().Object;
+
+ HtmlHelper<object> htmlHelper = new HtmlHelper<object>(vc, vp);
+ vp.Html = htmlHelper;
+
+ // Assert
+ Assert.Equal(vmp.Html, htmlHelper);
+ }
+
+ [Fact]
+ public void GetUrlHelperFromViewPage()
+ {
+ // Arrange
+ ViewMasterPage vmp = new ViewMasterPage();
+ ViewPage vp = new ViewPage();
+ vmp.Page = vp;
+ RequestContext rc = new RequestContext(new Mock<HttpContextBase>().Object, new RouteData());
+ UrlHelper urlHelper = new UrlHelper(rc);
+ vp.Url = urlHelper;
+
+ // Assert
+ Assert.Equal(vmp.Url, urlHelper);
+ }
+
+ [Fact]
+ public void ViewBagProperty_ReflectsViewData()
+ {
+ // Arrange
+ ViewPage page = new ViewPage();
+ ViewMasterPage masterPage = new ViewMasterPage();
+ masterPage.Page = page;
+ masterPage.ViewData["A"] = 1;
+
+ // Act & Assert
+ Assert.NotNull(masterPage.ViewBag);
+ Assert.Equal(1, masterPage.ViewBag.A);
+ }
+
+ [Fact]
+ public void ViewBagProperty_PropagatesChangesToViewData()
+ {
+ // Arrange
+ ViewPage page = new ViewPage();
+ ViewMasterPage masterPage = new ViewMasterPage();
+ masterPage.Page = page;
+ masterPage.ViewData["A"] = 1;
+
+ // Act
+ masterPage.ViewBag.A = "foo";
+ masterPage.ViewBag.B = 2;
+
+ // Assert
+ Assert.Equal("foo", masterPage.ViewData["A"]);
+ Assert.Equal(2, masterPage.ViewData["B"]);
+ }
+
+ // Master page types
+ private sealed class MockViewMasterPageDummyViewData : ViewMasterPage<DummyViewData>
+ {
+ }
+
+ // View data types
+ private sealed class DummyViewData
+ {
+ public int MyInt { get; set; }
+ public string MyString { get; set; }
+ }
+
+ // Page types
+ private sealed class MockViewPageBogusViewData : ViewPage<SelectListItem>
+ {
+ }
+
+ private sealed class MockViewPageDummyViewData : ViewPage<DummyViewData>
+ {
+ }
+
+ private sealed class MockViewPage : ViewPage
+ {
+ public Action RenderCallback { get; set; }
+
+ protected override void RenderChildren(HtmlTextWriter writer)
+ {
+ if (RenderCallback != null)
+ {
+ RenderCallback();
+ }
+ base.RenderChildren(writer);
+ }
+ }
+
+ private sealed class FooModel
+ {
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Test/ViewPageControlBuilderTest.cs b/test/System.Web.Mvc.Test/Test/ViewPageControlBuilderTest.cs
new file mode 100644
index 00000000..1ae597a1
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Test/ViewPageControlBuilderTest.cs
@@ -0,0 +1,39 @@
+using System.CodeDom;
+using System.Linq;
+using Xunit;
+
+namespace System.Web.Mvc.Test
+{
+ public class ViewPageControlBuilderTest
+ {
+ [Fact]
+ public void BuilderWithoutInheritsDoesNothing()
+ {
+ // Arrange
+ var builder = new ViewPageControlBuilder();
+ var derivedType = new CodeTypeDeclaration();
+ derivedType.BaseTypes.Add("basetype");
+
+ // Act
+ builder.ProcessGeneratedCode(null, null, derivedType, null, null);
+
+ // Assert
+ Assert.Equal("basetype", derivedType.BaseTypes.Cast<CodeTypeReference>().Single().BaseType);
+ }
+
+ [Fact]
+ public void BuilderWithInheritsSetsBaseType()
+ {
+ // Arrange
+ var builder = new ViewPageControlBuilder { Inherits = "inheritedtype" };
+ var derivedType = new CodeTypeDeclaration();
+ derivedType.BaseTypes.Add("basetype");
+
+ // Act
+ builder.ProcessGeneratedCode(null, null, derivedType, null, null);
+
+ // Assert
+ Assert.Equal("inheritedtype", derivedType.BaseTypes.Cast<CodeTypeReference>().Single().BaseType);
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Test/ViewPageTest.cs b/test/System.Web.Mvc.Test/Test/ViewPageTest.cs
new file mode 100644
index 00000000..f7ae263f
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Test/ViewPageTest.cs
@@ -0,0 +1,262 @@
+using System.IO;
+using System.Web.UI;
+using Moq;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Mvc.Test
+{
+ public class ViewPageTest
+ {
+ [Fact]
+ public void ModelProperty()
+ {
+ // Arrange
+ object model = new object();
+ ViewDataDictionary viewData = new ViewDataDictionary(model);
+ ViewPage viewPage = new ViewPage();
+ viewPage.ViewData = viewData;
+
+ // Act
+ object viewPageModel = viewPage.Model;
+
+ // Assert
+ Assert.Equal(model, viewPageModel);
+ Assert.Equal(model, viewPage.ViewData.Model);
+ }
+
+ [Fact]
+ public void ModelPropertyStronglyTypedViewPage()
+ {
+ // Arrange
+ FooModel model = new FooModel();
+ ViewDataDictionary<FooModel> viewData = new ViewDataDictionary<FooModel>(model);
+ ViewPage<FooModel> viewPage = new ViewPage<FooModel>();
+ viewPage.ViewData = viewData;
+
+ // Act
+ object viewPageModelObject = ((ViewPage)viewPage).Model;
+ FooModel viewPageModelPerson = viewPage.Model;
+
+ // Assert
+ Assert.Equal(model, viewPageModelObject);
+ Assert.Equal(model, viewPageModelPerson);
+ }
+
+ [Fact]
+ public void SetViewItemOnBaseClassPropagatesToDerivedClass()
+ {
+ // Arrange
+ ViewPage<object> vpInt = new ViewPage<object>();
+ ViewPage vp = vpInt;
+ object o = new object();
+
+ // Act
+ vp.ViewData.Model = o;
+
+ // Assert
+ Assert.Equal(o, vpInt.ViewData.Model);
+ Assert.Equal(o, vp.ViewData.Model);
+ }
+
+ [Fact]
+ public void SetViewItemOnDerivedClassPropagatesToBaseClass()
+ {
+ // Arrange
+ ViewPage<object> vpInt = new ViewPage<object>();
+ ViewPage vp = vpInt;
+ object o = new object();
+
+ // Act
+ vpInt.ViewData.Model = o;
+
+ // Assert
+ Assert.Equal(o, vpInt.ViewData.Model);
+ Assert.Equal(o, vp.ViewData.Model);
+ }
+
+ [Fact]
+ public void SetViewItemToWrongTypeThrows()
+ {
+ // Arrange
+ ViewPage vp = new ViewPage<string>();
+
+ // Act & Assert
+ Assert.Throws<InvalidOperationException>(
+ delegate { vp.ViewData.Model = 50; },
+ "The model item passed into the dictionary is of type 'System.Int32', but this dictionary requires a model item of type 'System.String'.");
+ }
+
+ [Fact]
+ public void RenderInitsHelpersAndSetsID()
+ {
+ // Arrange
+ ViewPageWithNoProcessRequest viewPage = new ViewPageWithNoProcessRequest();
+ TextWriter writer = new StringWriter();
+
+ Mock<ViewContext> mockViewContext = new Mock<ViewContext>();
+ mockViewContext.Setup(c => c.Writer).Returns(writer);
+ mockViewContext.Setup(c => c.HttpContext.Response.Output).Returns(TextWriter.Null);
+ mockViewContext.Setup(c => c.HttpContext.Server.Execute(It.IsAny<IHttpHandler>(), It.IsAny<TextWriter>(), true))
+ .Callback<IHttpHandler, TextWriter, bool>((_h, _w, _pf) =>
+ {
+ ViewPage.SwitchWriter switchWriter = _w as ViewPage.SwitchWriter;
+ Assert.NotNull(switchWriter);
+ Assert.Same(writer, switchWriter.InnerWriter);
+ })
+ .Verifiable();
+
+ // Act
+ viewPage.RenderView(mockViewContext.Object);
+
+ // Assert
+ mockViewContext.Verify();
+ Assert.NotNull(viewPage.Ajax);
+ Assert.NotNull(viewPage.Html);
+ Assert.NotNull(viewPage.Url);
+ }
+
+ [Fact]
+ public void GenericPageRenderInitsHelpersAndSetsID()
+ {
+ // Arrange
+ Mock<ViewContext> mockViewContext = new Mock<ViewContext>();
+ mockViewContext.Setup(c => c.Writer).Returns(new StringWriter());
+ mockViewContext.Setup(c => c.HttpContext.Response.Output).Returns(TextWriter.Null);
+ mockViewContext.Setup(c => c.HttpContext.Server).Returns(new Mock<HttpServerUtilityBase>().Object);
+
+ ViewPageWithNoProcessRequest<Controller> viewPage = new ViewPageWithNoProcessRequest<Controller>();
+
+ // Act
+ viewPage.RenderView(mockViewContext.Object);
+
+ // Assert
+ Assert.NotNull(viewPage.Ajax);
+ Assert.NotNull(viewPage.Html);
+ Assert.NotNull(viewPage.Url);
+ Assert.NotNull(((ViewPage)viewPage).Html);
+ Assert.NotNull(((ViewPage)viewPage).Url);
+ }
+
+ private static void WriterSetCorrectlyInternal(bool throwException)
+ {
+ // Arrange
+ bool triggered = false;
+ HtmlTextWriter writer = new HtmlTextWriter(TextWriter.Null);
+ MockViewPage vp = new MockViewPage();
+ vp.RenderCallback = delegate()
+ {
+ triggered = true;
+ Assert.Equal(writer, vp.Writer);
+ if (throwException)
+ {
+ throw new CallbackException();
+ }
+ };
+
+ // Act & Assert
+ Assert.Null(vp.Writer);
+ try
+ {
+ vp.RenderControl(writer);
+ }
+ catch (CallbackException)
+ {
+ }
+ Assert.Null(vp.Writer);
+ Assert.True(triggered);
+ }
+
+ [Fact]
+ public void WriterSetCorrectly()
+ {
+ WriterSetCorrectlyInternal(false /* throwException */);
+ }
+
+ [Fact]
+ public void WriterSetCorrectlyThrowException()
+ {
+ WriterSetCorrectlyInternal(true /* throwException */);
+ }
+
+ private sealed class ViewPageWithNoProcessRequest : ViewPage
+ {
+ public override void ProcessRequest(HttpContext context)
+ {
+ }
+ }
+
+ private sealed class ViewPageWithNoProcessRequest<TModel> : ViewPage<TModel>
+ {
+ public override void ProcessRequest(HttpContext context)
+ {
+ }
+ }
+
+ [Fact]
+ public void ViewBagProperty_ReflectsViewData()
+ {
+ // Arrange
+ ViewPage page = new ViewPage();
+ page.ViewData["A"] = 1;
+
+ // Act & Assert
+ Assert.NotNull(page.ViewBag);
+ Assert.Equal(1, page.ViewBag.A);
+ }
+
+ [Fact]
+ public void ViewBagProperty_ReflectsNewViewDataInstance()
+ {
+ // Arrange
+ ViewPage page = new ViewPage();
+ page.ViewData["A"] = 1;
+ page.ViewData = new ViewDataDictionary() { { "A", "bar" } };
+
+ // Act & Assert
+ Assert.Equal("bar", page.ViewBag.A);
+ }
+
+ [Fact]
+ public void ViewBagProperty_PropagatesChangesToViewData()
+ {
+ // Arrange
+ ViewPage page = new ViewPage();
+ page.ViewData["A"] = 1;
+
+ // Act
+ page.ViewBag.A = "foo";
+ page.ViewBag.B = 2;
+
+ // Assert
+ Assert.Equal("foo", page.ViewData["A"]);
+ Assert.Equal(2, page.ViewData["B"]);
+ }
+
+ private sealed class MockViewPage : ViewPage
+ {
+ public MockViewPage()
+ {
+ }
+
+ public Action RenderCallback { get; set; }
+
+ protected override void RenderChildren(HtmlTextWriter writer)
+ {
+ if (RenderCallback != null)
+ {
+ RenderCallback();
+ }
+ base.RenderChildren(writer);
+ }
+ }
+
+ private sealed class FooModel
+ {
+ }
+
+ private sealed class CallbackException : Exception
+ {
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Test/ViewResultBaseTest.cs b/test/System.Web.Mvc.Test/Test/ViewResultBaseTest.cs
new file mode 100644
index 00000000..3dc3bedf
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Test/ViewResultBaseTest.cs
@@ -0,0 +1,72 @@
+using System.Web.TestUtil;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Mvc.Test
+{
+ public class ViewResultBaseTest
+ {
+ [Fact]
+ public void ExecuteResultWithNullControllerContextThrows()
+ {
+ // Arrange
+ ViewResultBaseHelper result = new ViewResultBaseHelper();
+
+ // Act & Assert
+ Assert.ThrowsArgumentNull(
+ () => result.ExecuteResult(null),
+ "context");
+ }
+
+ [Fact]
+ public void TempDataProperty()
+ {
+ // Arrange
+ TempDataDictionary newDict = new TempDataDictionary();
+ ViewResultBaseHelper result = new ViewResultBaseHelper();
+
+ // Act & Assert
+ MemberHelper.TestPropertyWithDefaultInstance(result, "TempData", newDict);
+ }
+
+ [Fact]
+ public void ViewDataProperty()
+ {
+ // Arrange
+ ViewDataDictionary newDict = new ViewDataDictionary();
+ ViewResultBaseHelper result = new ViewResultBaseHelper();
+
+ // Act & Assert
+ MemberHelper.TestPropertyWithDefaultInstance(result, "ViewData", newDict);
+ }
+
+ [Fact]
+ public void ViewEngineCollectionProperty()
+ {
+ // Arrange
+ ViewEngineCollection viewEngineCollection = new ViewEngineCollection();
+ ViewResultBaseHelper result = new ViewResultBaseHelper();
+
+ // Act & Assert
+ MemberHelper.TestPropertyWithDefaultInstance(result, "ViewEngineCollection", viewEngineCollection);
+ }
+
+ [Fact]
+ public void ViewNameProperty()
+ {
+ // Arrange
+ ViewResultBaseHelper result = new ViewResultBaseHelper();
+
+ // Act & Assert
+ MemberHelper.TestStringProperty(result, "ViewName", String.Empty);
+ }
+
+ public class ViewResultBaseHelper : ViewResultBase
+ {
+ protected override ViewEngineResult FindView(ControllerContext context)
+ {
+ throw new NotImplementedException();
+ }
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Test/ViewResultTest.cs b/test/System.Web.Mvc.Test/Test/ViewResultTest.cs
new file mode 100644
index 00000000..e78a366c
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Test/ViewResultTest.cs
@@ -0,0 +1,222 @@
+using System.Collections.Generic;
+using System.IO;
+using System.Web.Routing;
+using System.Web.TestUtil;
+using Moq;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Mvc.Test
+{
+ public class ViewResultTest
+ {
+ private const string _viewName = "My cool view.";
+ private const string _masterName = "My cool master.";
+
+ [Fact]
+ public void EmptyViewNameUsesActionNameAsViewName()
+ {
+ // Arrange
+ ControllerBase controller = new Mock<ControllerBase>().Object;
+ HttpContextBase httpContext = CreateHttpContext();
+ RouteData routeData = new RouteData();
+ routeData.Values["action"] = _viewName;
+ ControllerContext context = new ControllerContext(httpContext, routeData, controller);
+ Mock<IViewEngine> viewEngine = new Mock<IViewEngine>(MockBehavior.Strict);
+ Mock<IView> view = new Mock<IView>(MockBehavior.Strict);
+ List<IViewEngine> viewEngines = new List<IViewEngine>();
+ viewEngines.Add(viewEngine.Object);
+ Mock<ViewEngineCollection> viewEngineCollection = new Mock<ViewEngineCollection>(MockBehavior.Strict, viewEngines);
+ ViewResult result = new ViewResultHelper { ViewEngineCollection = viewEngineCollection.Object };
+ viewEngineCollection
+ .Setup(e => e.FindView(It.IsAny<ControllerContext>(), _viewName, _masterName))
+ .Returns(new ViewEngineResult(view.Object, viewEngine.Object));
+ viewEngine
+ .Setup(e => e.FindView(It.IsAny<ControllerContext>(), _viewName, _masterName, It.IsAny<bool>()))
+ .Callback<ControllerContext, string, string, bool>(
+ (controllerContext, viewName, masterName, useCache) =>
+ {
+ Assert.Same(httpContext, controllerContext.HttpContext);
+ Assert.Same(routeData, controllerContext.RouteData);
+ })
+ .Returns(new ViewEngineResult(view.Object, viewEngine.Object));
+ view
+ .Setup(o => o.Render(It.IsAny<ViewContext>(), httpContext.Response.Output))
+ .Callback<ViewContext, TextWriter>(
+ (viewContext, writer) =>
+ {
+ Assert.Same(view.Object, viewContext.View);
+ Assert.Same(result.ViewData, viewContext.ViewData);
+ Assert.Same(result.TempData, viewContext.TempData);
+ Assert.Same(controller, viewContext.Controller);
+ });
+ viewEngine
+ .Setup(e => e.ReleaseView(context, It.IsAny<IView>()))
+ .Callback<ControllerContext, IView>(
+ (controllerContext, releasedView) => { Assert.Same(releasedView, view.Object); });
+
+ // Act
+ result.ExecuteResult(context);
+
+ // Assert
+ viewEngine.Verify();
+ viewEngineCollection.Verify();
+ view.Verify();
+ }
+
+ [Fact]
+ public void EngineLookupFailureThrows()
+ {
+ // Arrange
+ ControllerBase controller = new Mock<ControllerBase>().Object;
+ HttpContextBase httpContext = CreateHttpContext();
+ RouteData routeData = new RouteData();
+ routeData.Values["action"] = _viewName;
+ ControllerContext context = new ControllerContext(httpContext, routeData, controller);
+ Mock<IViewEngine> viewEngine = new Mock<IViewEngine>(MockBehavior.Strict);
+ List<IViewEngine> viewEngines = new List<IViewEngine>();
+ viewEngines.Add(viewEngine.Object);
+ Mock<ViewEngineCollection> viewEngineCollection = new Mock<ViewEngineCollection>(MockBehavior.Strict, viewEngines);
+ ViewResult result = new ViewResultHelper { ViewEngineCollection = viewEngineCollection.Object };
+ viewEngineCollection
+ .Setup(e => e.FindView(It.IsAny<ControllerContext>(), _viewName, _masterName))
+ .Returns(new ViewEngineResult(new[] { "location1", "location2" }));
+ viewEngine
+ .Setup(e => e.FindView(It.IsAny<ControllerContext>(), _viewName, _masterName, It.IsAny<bool>()))
+ .Callback<ControllerContext, string, string, bool>(
+ (controllerContext, viewName, masterName, useCache) =>
+ {
+ Assert.Same(httpContext, controllerContext.HttpContext);
+ Assert.Same(routeData, controllerContext.RouteData);
+ })
+ .Returns(new ViewEngineResult(new[] { "location1", "location2" }));
+
+ // Act & Assert
+ Assert.Throws<InvalidOperationException>(
+ () => result.ExecuteResult(context),
+ @"The view '" + _viewName + @"' or its master was not found or no view engine supports the searched locations. The following locations were searched:
+location1
+location2");
+
+ viewEngine.Verify();
+ viewEngineCollection.Verify();
+ }
+
+ [Fact]
+ public void EngineLookupSuccessRendersView()
+ {
+ // Arrange
+ ControllerBase controller = new Mock<ControllerBase>().Object;
+ HttpContextBase httpContext = CreateHttpContext();
+ RouteData routeData = new RouteData();
+ ControllerContext context = new ControllerContext(httpContext, routeData, controller);
+ Mock<IViewEngine> viewEngine = new Mock<IViewEngine>(MockBehavior.Strict);
+ Mock<IView> view = new Mock<IView>(MockBehavior.Strict);
+ List<IViewEngine> viewEngines = new List<IViewEngine>();
+ viewEngines.Add(viewEngine.Object);
+ Mock<ViewEngineCollection> viewEngineCollection = new Mock<ViewEngineCollection>(MockBehavior.Strict, viewEngines);
+ ViewResult result = new ViewResultHelper { ViewName = _viewName, ViewEngineCollection = viewEngineCollection.Object };
+ view
+ .Setup(o => o.Render(It.IsAny<ViewContext>(), httpContext.Response.Output))
+ .Callback<ViewContext, TextWriter>(
+ (viewContext, writer) =>
+ {
+ Assert.Same(view.Object, viewContext.View);
+ Assert.Same(result.ViewData, viewContext.ViewData);
+ Assert.Same(result.TempData, viewContext.TempData);
+ Assert.Same(controller, viewContext.Controller);
+ });
+ viewEngineCollection
+ .Setup(e => e.FindView(It.IsAny<ControllerContext>(), _viewName, _masterName))
+ .Returns(new ViewEngineResult(view.Object, viewEngine.Object));
+ viewEngine
+ .Setup(e => e.FindView(It.IsAny<ControllerContext>(), _viewName, _masterName, It.IsAny<bool>()))
+ .Callback<ControllerContext, string, string, bool>(
+ (controllerContext, viewName, masterName, useCache) =>
+ {
+ Assert.Same(httpContext, controllerContext.HttpContext);
+ Assert.Same(routeData, controllerContext.RouteData);
+ })
+ .Returns(new ViewEngineResult(view.Object, viewEngine.Object));
+ viewEngine
+ .Setup(e => e.ReleaseView(context, It.IsAny<IView>()))
+ .Callback<ControllerContext, IView>(
+ (controllerContext, releasedView) => { Assert.Same(releasedView, view.Object); });
+
+ // Act
+ result.ExecuteResult(context);
+
+ // Assert
+ viewEngine.Verify();
+ viewEngineCollection.Verify();
+ view.Verify();
+ }
+
+ [Fact]
+ public void ExecuteResultWithExplicitViewObject()
+ {
+ // Arrange
+ ControllerBase controller = new Mock<ControllerBase>().Object;
+ HttpContextBase httpContext = CreateHttpContext();
+ RouteData routeData = new RouteData();
+ routeData.Values["action"] = _viewName;
+ ControllerContext context = new ControllerContext(httpContext, routeData, controller);
+ Mock<IView> view = new Mock<IView>(MockBehavior.Strict);
+ ViewResult result = new ViewResultHelper { View = view.Object };
+ view
+ .Setup(o => o.Render(It.IsAny<ViewContext>(), httpContext.Response.Output))
+ .Callback<ViewContext, TextWriter>(
+ (viewContext, writer) =>
+ {
+ Assert.Same(view.Object, viewContext.View);
+ Assert.Same(result.ViewData, viewContext.ViewData);
+ Assert.Same(result.TempData, viewContext.TempData);
+ Assert.Same(controller, viewContext.Controller);
+ });
+
+ // Act
+ result.ExecuteResult(context);
+
+ // Assert
+ view.Verify();
+ }
+
+ [Fact]
+ public void ExecuteResultWithNullControllerContextThrows()
+ {
+ // Arrange
+ ViewResult result = new ViewResultHelper();
+
+ // Act & Assert
+ Assert.ThrowsArgumentNull(
+ () => result.ExecuteResult(null),
+ "context");
+ }
+
+ [Fact]
+ public void MasterNameProperty()
+ {
+ // Arrange
+ ViewResult result = new ViewResult();
+
+ // Act & Assert
+ MemberHelper.TestStringProperty(result, "MasterName", String.Empty);
+ }
+
+ private static HttpContextBase CreateHttpContext()
+ {
+ Mock<HttpContextBase> mockHttpContext = new Mock<HttpContextBase>();
+ mockHttpContext.Setup(c => c.Response.Output).Returns(TextWriter.Null);
+ return mockHttpContext.Object;
+ }
+
+ private class ViewResultHelper : ViewResult
+ {
+ public ViewResultHelper()
+ {
+ ViewEngineCollection = new ViewEngineCollection(new IViewEngine[] { new WebFormViewEngine() });
+ MasterName = _masterName;
+ }
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Test/ViewStartPageTest.cs b/test/System.Web.Mvc.Test/Test/ViewStartPageTest.cs
new file mode 100644
index 00000000..7bcdf8a0
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Test/ViewStartPageTest.cs
@@ -0,0 +1,110 @@
+using System.Web.Routing;
+using System.Web.WebPages;
+using Moq;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Mvc.Test
+{
+ public class ViewStartPageTest
+ {
+ [Fact]
+ public void Html_DelegatesToChildPage()
+ {
+ // Arrange
+ MockViewStartPage viewStart = new MockViewStartPage();
+ var viewPage = new Mock<WebViewPage>() { CallBase = true };
+ var helper = new HtmlHelper<object>(new ViewContext() { ViewData = new ViewDataDictionary() }, viewPage.Object, new RouteCollection());
+ viewPage.Object.Html = helper;
+ viewStart.ChildPage = viewPage.Object;
+
+ // Act
+ var result = viewStart.Html;
+
+ // Assert
+ Assert.Same(helper, result);
+ }
+
+ [Fact]
+ public void Url_DelegatesToChildPage()
+ {
+ // Arrange
+ MockViewStartPage viewStart = new MockViewStartPage();
+ var viewPage = new Mock<WebViewPage>() { CallBase = true };
+ var helper = new UrlHelper(new RequestContext());
+ viewPage.Object.Url = helper;
+ viewStart.ChildPage = viewPage.Object;
+
+ // Act
+ var result = viewStart.Url;
+
+ // Assert
+ Assert.Same(helper, result);
+ }
+
+ [Fact]
+ public void ViewContext_DelegatesToChildPage()
+ {
+ // Arrange
+ MockViewStartPage viewStart = new MockViewStartPage();
+ var viewPage = new Mock<WebViewPage>() { CallBase = true };
+ var viewContext = new ViewContext();
+ viewPage.Object.ViewContext = viewContext;
+ viewStart.ChildPage = viewPage.Object;
+
+ // Act
+ var result = viewStart.ViewContext;
+
+ // Assert
+ Assert.Same(viewContext, result);
+ }
+
+ [Fact]
+ public void ViewStartPageChild_ThrowsOnNonMvcChildPage()
+ {
+ // Arrange
+ MockViewStartPage viewStart = new MockViewStartPage();
+ viewStart.ChildPage = new Mock<WebPage>().Object;
+
+ // Act + Assert
+ Assert.Throws<InvalidOperationException>(delegate() { var c = viewStart.ViewStartPageChild; }, "A ViewStartPage can be used only with with a page that derives from WebViewPage or another ViewStartPage.");
+ }
+
+ [Fact]
+ public void ViewStartPageChild_WorksWithWebViewPage()
+ {
+ // Arrange
+ MockViewStartPage viewStart = new MockViewStartPage();
+ var viewPage = new Mock<WebViewPage>();
+ viewStart.ChildPage = viewPage.Object;
+
+ // Act
+ var result = viewStart.ViewStartPageChild;
+
+ // Assert
+ Assert.Same(viewPage.Object, result);
+ }
+
+ [Fact]
+ public void ViewStartPageChild_WorksWithAnotherRazorStartPage()
+ {
+ // Arrange
+ MockViewStartPage viewStart = new MockViewStartPage();
+ var anotherViewStart = new Mock<ViewStartPage>();
+ viewStart.ChildPage = anotherViewStart.Object;
+
+ // Act
+ var result = viewStart.ViewStartPageChild;
+
+ // Assert
+ Assert.Same(anotherViewStart.Object, result);
+ }
+
+ class MockViewStartPage : ViewStartPage
+ {
+ public override void Execute()
+ {
+ }
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Test/ViewTypeParserFilterTest.cs b/test/System.Web.Mvc.Test/Test/ViewTypeParserFilterTest.cs
new file mode 100644
index 00000000..92c3c03e
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Test/ViewTypeParserFilterTest.cs
@@ -0,0 +1,208 @@
+using System.Collections.Generic;
+using System.Web.UI;
+using Xunit;
+
+namespace System.Web.Mvc.Test
+{
+ public class ViewTypeParserFilterTest
+ {
+ // Non-generic directives
+
+ [Fact]
+ public void NonGenericPageDirectiveDoesNotChangeInheritsDirective()
+ {
+ var filter = new ViewTypeParserFilter();
+ var attributes = new Dictionary<string, string> { { "inherits", "foobar" } };
+ var builder = new MvcBuilder();
+
+ filter.PreprocessDirective("page", attributes);
+ filter.ParseComplete(builder);
+
+ Assert.Equal("foobar", attributes["inherits"]);
+ Assert.Null(builder.Inherits);
+ }
+
+ [Fact]
+ public void NonGenericControlDirectiveDoesNotChangeInheritsDirective()
+ {
+ var filter = new ViewTypeParserFilter();
+ var attributes = new Dictionary<string, string> { { "inherits", "foobar" } };
+ var builder = new MvcBuilder();
+
+ filter.PreprocessDirective("control", attributes);
+ filter.ParseComplete(builder);
+
+ Assert.Equal("foobar", attributes["inherits"]);
+ Assert.Null(builder.Inherits);
+ }
+
+ [Fact]
+ public void NonGenericMasterDirectiveDoesNotChangeInheritsDirective()
+ {
+ var filter = new ViewTypeParserFilter();
+ var attributes = new Dictionary<string, string> { { "inherits", "foobar" } };
+ var builder = new MvcBuilder();
+
+ filter.PreprocessDirective("master", attributes);
+ filter.ParseComplete(builder);
+
+ Assert.Equal("foobar", attributes["inherits"]);
+ Assert.Null(builder.Inherits);
+ }
+
+ // C#-style generic directives
+
+ [Fact]
+ public void CSGenericUnknownDirectiveDoesNotChangeInheritsDirective()
+ {
+ var filter = new ViewTypeParserFilter();
+ var attributes = new Dictionary<string, string> { { "inherits", "foobar<baz>" } };
+ var builder = new MvcBuilder();
+
+ filter.PreprocessDirective("unknown", attributes);
+ filter.ParseComplete(builder);
+
+ Assert.Equal("foobar<baz>", attributes["inherits"]);
+ Assert.Null(builder.Inherits);
+ }
+
+ [Fact]
+ public void CSGenericPageDirectiveChangesInheritsDirective()
+ {
+ var filter = new ViewTypeParserFilter();
+ var attributes = new Dictionary<string, string> { { "inherits", "foobar<baz>" } };
+ var builder = new MvcBuilder();
+
+ filter.PreprocessDirective("page", attributes);
+ filter.ParseComplete(builder);
+
+ Assert.Equal(typeof(ViewPage).FullName, attributes["inherits"]);
+ Assert.Equal("foobar<baz>", builder.Inherits);
+ }
+
+ [Fact]
+ public void CSGenericControlDirectiveChangesInheritsDirective()
+ {
+ var filter = new ViewTypeParserFilter();
+ var attributes = new Dictionary<string, string> { { "inherits", "foobar<baz>" } };
+ var builder = new MvcBuilder();
+
+ filter.PreprocessDirective("control", attributes);
+ filter.ParseComplete(builder);
+
+ Assert.Equal(typeof(ViewUserControl).FullName, attributes["inherits"]);
+ Assert.Equal("foobar<baz>", builder.Inherits);
+ }
+
+ [Fact]
+ public void CSGenericMasterDirectiveChangesInheritsDirective()
+ {
+ var filter = new ViewTypeParserFilter();
+ var attributes = new Dictionary<string, string> { { "inherits", "foobar<baz>" } };
+ var builder = new MvcBuilder();
+
+ filter.PreprocessDirective("master", attributes);
+ filter.ParseComplete(builder);
+
+ Assert.Equal(typeof(ViewMasterPage).FullName, attributes["inherits"]);
+ Assert.Equal("foobar<baz>", builder.Inherits);
+ }
+
+ [Fact]
+ public void CSDirectivesAfterPageDirectiveProperlyPreserveInheritsDirective()
+ {
+ var filter = new ViewTypeParserFilter();
+ var pageAttributes = new Dictionary<string, string> { { "inherits", "foobar<baz>" } };
+ var importAttributes = new Dictionary<string, string> { { "inherits", "dummyvalue<baz>" } };
+ var builder = new MvcBuilder();
+
+ filter.PreprocessDirective("page", pageAttributes);
+ filter.PreprocessDirective("import", importAttributes);
+ filter.ParseComplete(builder);
+
+ Assert.Equal(typeof(ViewPage).FullName, pageAttributes["inherits"]);
+ Assert.Equal("foobar<baz>", builder.Inherits);
+ }
+
+ // VB.NET-style generic directives
+
+ [Fact]
+ public void VBGenericUnknownDirectiveDoesNotChangeInheritsDirective()
+ {
+ var filter = new ViewTypeParserFilter();
+ var attributes = new Dictionary<string, string> { { "inherits", "foobar(of baz)" } };
+ var builder = new MvcBuilder();
+
+ filter.PreprocessDirective("unknown", attributes);
+ filter.ParseComplete(builder);
+
+ Assert.Equal("foobar(of baz)", attributes["inherits"]);
+ Assert.Null(builder.Inherits);
+ }
+
+ [Fact]
+ public void VBGenericPageDirectiveChangesInheritsDirective()
+ {
+ var filter = new ViewTypeParserFilter();
+ var attributes = new Dictionary<string, string> { { "inherits", "foobar(of baz)" } };
+ var builder = new MvcBuilder();
+
+ filter.PreprocessDirective("page", attributes);
+ filter.ParseComplete(builder);
+
+ Assert.Equal(typeof(ViewPage).FullName, attributes["inherits"]);
+ Assert.Equal("foobar(of baz)", builder.Inherits);
+ }
+
+ [Fact]
+ public void VBGenericControlDirectiveChangesInheritsDirective()
+ {
+ var filter = new ViewTypeParserFilter();
+ var attributes = new Dictionary<string, string> { { "inherits", "foobar(of baz)" } };
+ var builder = new MvcBuilder();
+
+ filter.PreprocessDirective("control", attributes);
+ filter.ParseComplete(builder);
+
+ Assert.Equal(typeof(ViewUserControl).FullName, attributes["inherits"]);
+ Assert.Equal("foobar(of baz)", builder.Inherits);
+ }
+
+ [Fact]
+ public void VBGenericMasterDirectiveChangesInheritsDirective()
+ {
+ var filter = new ViewTypeParserFilter();
+ var attributes = new Dictionary<string, string> { { "inherits", "foobar(of baz)" } };
+ var builder = new MvcBuilder();
+
+ filter.PreprocessDirective("master", attributes);
+ filter.ParseComplete(builder);
+
+ Assert.Equal(typeof(ViewMasterPage).FullName, attributes["inherits"]);
+ Assert.Equal("foobar(of baz)", builder.Inherits);
+ }
+
+ [Fact]
+ public void VBDirectivesAfterPageDirectiveProperlyPreserveInheritsDirective()
+ {
+ var filter = new ViewTypeParserFilter();
+ var pageAttributes = new Dictionary<string, string> { { "inherits", "foobar(of baz)" } };
+ var importAttributes = new Dictionary<string, string> { { "inherits", "dummyvalue(of baz)" } };
+ var builder = new MvcBuilder();
+
+ filter.PreprocessDirective("page", pageAttributes);
+ filter.PreprocessDirective("import", importAttributes);
+ filter.ParseComplete(builder);
+
+ Assert.Equal(typeof(ViewPage).FullName, pageAttributes["inherits"]);
+ Assert.Equal("foobar(of baz)", builder.Inherits);
+ }
+
+ // Helpers
+
+ private class MvcBuilder : RootBuilder, IMvcControlBuilder
+ {
+ public string Inherits { get; set; }
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Test/ViewUserControlControlBuilderTest.cs b/test/System.Web.Mvc.Test/Test/ViewUserControlControlBuilderTest.cs
new file mode 100644
index 00000000..94fbcf39
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Test/ViewUserControlControlBuilderTest.cs
@@ -0,0 +1,39 @@
+using System.CodeDom;
+using System.Linq;
+using Xunit;
+
+namespace System.Web.Mvc.Test
+{
+ public class ViewUserControlControlBuilderTest
+ {
+ [Fact]
+ public void BuilderWithoutInheritsDoesNothing()
+ {
+ // Arrange
+ var builder = new ViewUserControlControlBuilder();
+ var derivedType = new CodeTypeDeclaration();
+ derivedType.BaseTypes.Add("basetype");
+
+ // Act
+ builder.ProcessGeneratedCode(null, null, derivedType, null, null);
+
+ // Assert
+ Assert.Equal("basetype", derivedType.BaseTypes.Cast<CodeTypeReference>().Single().BaseType);
+ }
+
+ [Fact]
+ public void BuilderWithInheritsSetsBaseType()
+ {
+ // Arrange
+ var builder = new ViewUserControlControlBuilder { Inherits = "inheritedtype" };
+ var derivedType = new CodeTypeDeclaration();
+ derivedType.BaseTypes.Add("basetype");
+
+ // Act
+ builder.ProcessGeneratedCode(null, null, derivedType, null, null);
+
+ // Assert
+ Assert.Equal("inheritedtype", derivedType.BaseTypes.Cast<CodeTypeReference>().Single().BaseType);
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Test/ViewUserControlTest.cs b/test/System.Web.Mvc.Test/Test/ViewUserControlTest.cs
new file mode 100644
index 00000000..37e8bdd1
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Test/ViewUserControlTest.cs
@@ -0,0 +1,538 @@
+using System.IO;
+using System.Web.Routing;
+using System.Web.TestUtil;
+using System.Web.UI;
+using Moq;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Mvc.Test
+{
+ public class ViewUserControlTest
+ {
+ [Fact]
+ public void ModelProperty()
+ {
+ // Arrange
+ object model = new object();
+ ViewDataDictionary viewData = new ViewDataDictionary(model);
+ ViewUserControl viewUserControl = new ViewUserControl();
+ viewUserControl.ViewData = viewData;
+
+ // Act
+ object viewPageModel = viewUserControl.Model;
+
+ // Assert
+ Assert.Equal(model, viewPageModel);
+ Assert.Equal(model, viewUserControl.ViewData.Model);
+ }
+
+ [Fact]
+ public void ModelPropertyStronglyTyped()
+ {
+ // Arrange
+ FooModel model = new FooModel();
+ ViewDataDictionary<FooModel> viewData = new ViewDataDictionary<FooModel>(model);
+ ViewUserControl<FooModel> viewUserControl = new ViewUserControl<FooModel>();
+ viewUserControl.ViewData = viewData;
+
+ // Act
+ object viewPageModelObject = ((ViewUserControl)viewUserControl).Model;
+ FooModel viewPageModelPerson = viewUserControl.Model;
+
+ // Assert
+ Assert.Equal(model, viewPageModelObject);
+ Assert.Equal(model, viewPageModelPerson);
+ }
+
+ [Fact]
+ public void RenderViewAndRestoreContentType()
+ {
+ // Arrange
+ Mock<ViewContext> mockViewContext = new Mock<ViewContext>();
+ mockViewContext.SetupProperty(c => c.HttpContext.Response.ContentType);
+ ViewContext vc = mockViewContext.Object;
+
+ Mock<ViewPage> mockViewPage = new Mock<ViewPage>();
+ mockViewPage.Setup(vp => vp.RenderView(vc)).Callback(() => vc.HttpContext.Response.ContentType = "newContentType");
+
+ // Act
+ vc.HttpContext.Response.ContentType = "oldContentType";
+ ViewUserControl.RenderViewAndRestoreContentType(mockViewPage.Object, vc);
+ string postContentType = vc.HttpContext.Response.ContentType;
+
+ // Assert
+ Assert.Equal("oldContentType", postContentType);
+ }
+
+ [Fact]
+ public void SetViewItem()
+ {
+ // Arrange
+ ViewUserControl vuc = new ViewUserControl();
+ object viewItem = new object();
+ vuc.ViewData = new ViewDataDictionary(viewItem);
+
+ // Act
+ vuc.ViewData.Model = viewItem;
+ object newViewItem = vuc.ViewData.Model;
+
+ // Assert
+ Assert.Same(viewItem, newViewItem);
+ }
+
+ [Fact]
+ public void SetViewItemOnBaseClassPropagatesToDerivedClass()
+ {
+ // Arrange
+ ViewUserControl<object> vucInt = new ViewUserControl<object>();
+ ViewUserControl vuc = vucInt;
+ vuc.ViewData = new ViewDataDictionary();
+ object o = new object();
+
+ // Act
+ vuc.ViewData.Model = o;
+
+ // Assert
+ Assert.Equal(o, vucInt.ViewData.Model);
+ Assert.Equal(o, vuc.ViewData.Model);
+ }
+
+ [Fact]
+ public void SetViewItemOnDerivedClassPropagatesToBaseClass()
+ {
+ // Arrange
+ ViewUserControl<object> vucInt = new ViewUserControl<object>();
+ ViewUserControl vuc = vucInt;
+ vucInt.ViewData = new ViewDataDictionary<object>();
+ object o = new object();
+
+ // Act
+ vucInt.ViewData.Model = o;
+
+ // Assert
+ Assert.Equal(o, vucInt.ViewData.Model);
+ Assert.Equal(o, vuc.ViewData.Model);
+ }
+
+ [Fact]
+ public void SetViewItemToWrongTypeThrows()
+ {
+ // Arrange
+ ViewUserControl<string> vucString = new ViewUserControl<string>();
+ vucString.ViewData = new ViewDataDictionary<string>();
+ ViewUserControl vuc = vucString;
+
+ // Act & Assert
+ Assert.Throws<InvalidOperationException>(
+ delegate { vuc.ViewData.Model = 50; },
+ "The model item passed into the dictionary is of type 'System.Int32', but this dictionary requires a model item of type 'System.String'.");
+ }
+
+ [Fact]
+ public void GetViewDataWhenNoPageSetThrows()
+ {
+ ViewUserControl vuc = new ViewUserControl();
+ vuc.AppRelativeVirtualPath = "~/Foo.ascx";
+
+ Assert.Throws<InvalidOperationException>(
+ delegate { var foo = vuc.ViewData["Foo"]; },
+ "The ViewUserControl '~/Foo.ascx' cannot find an IViewDataContainer object. The ViewUserControl must be inside a ViewPage, a ViewMasterPage, or another ViewUserControl.");
+ }
+
+ [Fact]
+ public void GetViewDataWhenRegularPageSetThrows()
+ {
+ Page p = new Page();
+ p.Controls.Add(new Control());
+ ViewUserControl vuc = new ViewUserControl();
+ p.Controls[0].Controls.Add(vuc);
+ vuc.AppRelativeVirtualPath = "~/Foo.ascx";
+
+ Assert.Throws<InvalidOperationException>(
+ delegate { var foo = vuc.ViewData["Foo"]; },
+ "The ViewUserControl '~/Foo.ascx' cannot find an IViewDataContainer object. The ViewUserControl must be inside a ViewPage, a ViewMasterPage, or another ViewUserControl.");
+ }
+
+ [Fact]
+ public void GetViewDataFromViewPage()
+ {
+ // Arrange
+ ViewPage p = new ViewPage();
+ p.Controls.Add(new Control());
+ ViewUserControl vuc = new ViewUserControl();
+ p.Controls[0].Controls.Add(vuc);
+ p.ViewData = new ViewDataDictionary { { "FirstName", "Joe" }, { "LastName", "Schmoe" } };
+
+ // Act
+ object firstName = vuc.ViewData.Eval("FirstName");
+ object lastName = vuc.ViewData.Eval("LastName");
+
+ // Assert
+ Assert.Equal("Joe", firstName);
+ Assert.Equal("Schmoe", lastName);
+ }
+
+ [Fact]
+ public void GetViewDataFromViewPageWithViewDataKeyPointingToObject()
+ {
+ // Arrange
+ ViewDataDictionary vdd = new ViewDataDictionary()
+ {
+ { "Foo", "FooParent" },
+ { "Bar", "BarParent" },
+ { "Child", new object() }
+ };
+
+ ViewPage p = new ViewPage();
+ p.Controls.Add(new Control());
+ ViewUserControl vuc = new ViewUserControl() { ViewDataKey = "Child" };
+ p.Controls[0].Controls.Add(vuc);
+ p.ViewData = vdd;
+
+ // Act
+ object oFoo = vuc.ViewData.Eval("Foo");
+ object oBar = vuc.ViewData.Eval("Bar");
+
+ // Assert
+ Assert.Equal(vdd["Child"], vuc.ViewData.Model);
+ Assert.Equal("FooParent", oFoo);
+ Assert.Equal("BarParent", oBar);
+ }
+
+ [Fact]
+ public void GetViewDataFromViewPageWithViewDataKeyPointingToViewDataDictionary()
+ {
+ // Arrange
+ ViewDataDictionary vdd = new ViewDataDictionary()
+ {
+ { "Foo", "FooParent" },
+ { "Bar", "BarParent" },
+ {
+ "Child",
+ new ViewDataDictionary()
+ {
+ { "Foo", "FooChild" },
+ { "Bar", "BarChild" }
+ }
+ }
+ };
+
+ ViewPage p = new ViewPage();
+ p.Controls.Add(new Control());
+ ViewUserControl vuc = new ViewUserControl() { ViewDataKey = "Child" };
+ p.Controls[0].Controls.Add(vuc);
+ p.ViewData = vdd;
+
+ // Act
+ object oFoo = vuc.ViewData.Eval("Foo");
+ object oBar = vuc.ViewData.Eval("Bar");
+
+ // Assert
+ Assert.Equal(vdd["Child"], vuc.ViewData);
+ Assert.Equal("FooChild", oFoo);
+ Assert.Equal("BarChild", oBar);
+ }
+
+ [Fact]
+ public void GetViewDataFromViewUserControl()
+ {
+ // Arrange
+ ViewPage p = new ViewPage();
+ p.Controls.Add(new Control());
+ ViewUserControl outerVuc = new ViewUserControl();
+ p.Controls[0].Controls.Add(outerVuc);
+ outerVuc.Controls.Add(new Control());
+ ViewUserControl vuc = new ViewUserControl();
+ outerVuc.Controls[0].Controls.Add(vuc);
+
+ p.ViewData = new ViewDataDictionary { { "FirstName", "Joe" }, { "LastName", "Schmoe" } };
+
+ // Act
+ object firstName = vuc.ViewData.Eval("FirstName");
+ object lastName = vuc.ViewData.Eval("LastName");
+
+ // Assert
+ Assert.Equal("Joe", firstName);
+ Assert.Equal("Schmoe", lastName);
+ }
+
+ [Fact]
+ public void GetViewDataFromViewUserControlWithViewDataKeyOnInnerControl()
+ {
+ // Arrange
+ ViewPage p = new ViewPage();
+ p.Controls.Add(new Control());
+ ViewUserControl outerVuc = new ViewUserControl();
+ p.Controls[0].Controls.Add(outerVuc);
+ outerVuc.Controls.Add(new Control());
+ ViewUserControl vuc = new ViewUserControl() { ViewDataKey = "SubData" };
+ outerVuc.Controls[0].Controls.Add(vuc);
+
+ p.ViewData = new ViewDataDictionary { { "FirstName", "Joe" }, { "LastName", "Schmoe" } };
+ p.ViewData["SubData"] = new ViewDataDictionary { { "FirstName", "SubJoe" }, { "LastName", "SubSchmoe" } };
+
+ // Act
+ object firstName = vuc.ViewData.Eval("FirstName");
+ object lastName = vuc.ViewData.Eval("LastName");
+
+ // Assert
+ Assert.Equal("SubJoe", firstName);
+ Assert.Equal("SubSchmoe", lastName);
+ }
+
+ [Fact]
+ public void GetViewDataFromViewUserControlWithViewDataKeyOnOuterControl()
+ {
+ // Arrange
+ ViewPage p = new ViewPage();
+ p.Controls.Add(new Control());
+ ViewUserControl outerVuc = new ViewUserControl() { ViewDataKey = "SubData" };
+ p.Controls[0].Controls.Add(outerVuc);
+ outerVuc.Controls.Add(new Control());
+ ViewUserControl vuc = new ViewUserControl();
+ outerVuc.Controls[0].Controls.Add(vuc);
+
+ p.ViewData = new ViewDataDictionary { { "FirstName", "Joe" }, { "LastName", "Schmoe" } };
+ p.ViewData["SubData"] = new ViewDataDictionary { { "FirstName", "SubJoe" }, { "LastName", "SubSchmoe" } };
+
+ // Act
+ object firstName = vuc.ViewData.Eval("FirstName");
+ object lastName = vuc.ViewData.Eval("LastName");
+
+ // Assert
+ Assert.Equal("SubJoe", firstName);
+ Assert.Equal("SubSchmoe", lastName);
+ }
+
+ [Fact]
+ public void ViewDataKeyProperty()
+ {
+ MemberHelper.TestStringProperty(new ViewUserControl(), "ViewDataKey", String.Empty, testDefaultValueAttribute: true);
+ }
+
+ [Fact]
+ public void GetWrongGenericViewItemTypeThrows()
+ {
+ // Arrange
+ ViewPage p = new ViewPage();
+ p.ViewData = new ViewDataDictionary();
+ p.ViewData["Foo"] = new DummyViewData { MyInt = 123, MyString = "Whatever" };
+
+ MockViewUserControl<MyViewData> vuc = new MockViewUserControl<MyViewData>() { ViewDataKey = "FOO" };
+ vuc.AppRelativeVirtualPath = "~/Foo.aspx";
+ p.Controls.Add(new Control());
+ p.Controls[0].Controls.Add(vuc);
+
+ // Act
+ Assert.Throws<InvalidOperationException>(
+ delegate { var foo = vuc.ViewData.Model.IntProp; },
+ @"The model item passed into the dictionary is of type 'System.Web.Mvc.Test.ViewUserControlTest+DummyViewData', but this dictionary requires a model item of type 'System.Web.Mvc.Test.ViewUserControlTest+MyViewData'.");
+ }
+
+ [Fact]
+ public void GetGenericViewItemType()
+ {
+ // Arrange
+ ViewPage p = new ViewPage();
+ p.Controls.Add(new Control());
+ MockViewUserControl<MyViewData> vuc = new MockViewUserControl<MyViewData>() { ViewDataKey = "FOO" };
+ p.Controls[0].Controls.Add(vuc);
+ p.ViewData = new ViewDataDictionary();
+ p.ViewData["Foo"] = new MyViewData { IntProp = 123, StringProp = "miao" };
+
+ // Act
+ int intProp = vuc.ViewData.Model.IntProp;
+ string stringProp = vuc.ViewData.Model.StringProp;
+
+ // Assert
+ Assert.Equal(123, intProp);
+ Assert.Equal("miao", stringProp);
+ }
+
+ [Fact]
+ public void GetHtmlHelperFromViewPage()
+ {
+ // Arrange
+ ViewUserControl vuc = new ViewUserControl();
+ ViewPage containerPage = new ViewPage();
+ containerPage.Controls.Add(vuc);
+ ViewContext vc = new Mock<ViewContext>().Object;
+ vuc.ViewContext = vc;
+
+ // Act
+ HtmlHelper htmlHelper = vuc.Html;
+
+ // Assert
+ Assert.Equal(vc, htmlHelper.ViewContext);
+ Assert.Equal(vuc, htmlHelper.ViewDataContainer);
+ }
+
+ [Fact]
+ public void GetHtmlHelperFromRegularPage()
+ {
+ // Arrange
+ ViewUserControl vuc = new ViewUserControl();
+ Page containerPage = new Page();
+ containerPage.Controls.Add(vuc);
+
+ // Assert
+ Assert.Throws<InvalidOperationException>(
+ delegate { HtmlHelper foo = vuc.Html; },
+ "A ViewUserControl can be used only in pages that derive from ViewPage or ViewPage<TModel>.");
+ }
+
+ [Fact]
+ public void GetUrlHelperFromViewPage()
+ {
+ // Arrange
+ ViewUserControl vuc = new ViewUserControl();
+ ViewPage containerPage = new ViewPage();
+ containerPage.Controls.Add(vuc);
+ RequestContext rc = new RequestContext(new Mock<HttpContextBase>().Object, new RouteData());
+ UrlHelper urlHelper = new UrlHelper(rc);
+ containerPage.Url = urlHelper;
+
+ // Assert
+ Assert.Equal(vuc.Url, urlHelper);
+ }
+
+ [Fact]
+ public void GetUrlHelperFromRegularPage()
+ {
+ // Arrange
+ ViewUserControl vuc = new ViewUserControl();
+ Page containerPage = new Page();
+ containerPage.Controls.Add(vuc);
+
+ // Assert
+ Assert.Throws<InvalidOperationException>(
+ delegate { UrlHelper foo = vuc.Url; },
+ "A ViewUserControl can be used only in pages that derive from ViewPage or ViewPage<TModel>.");
+ }
+
+ [Fact]
+ public void GetWriterFromViewPage()
+ {
+ // Arrange
+ MockViewUserControl vuc = new MockViewUserControl();
+ MockViewUserControlContainerPage containerPage = new MockViewUserControlContainerPage(vuc);
+ bool triggered = false;
+ HtmlTextWriter writer = new HtmlTextWriter(TextWriter.Null);
+ containerPage.RenderCallback = delegate()
+ {
+ triggered = true;
+ Assert.Equal(writer, vuc.Writer);
+ };
+
+ // Act & Assert
+ Assert.Null(vuc.Writer);
+ containerPage.RenderControl(writer);
+ Assert.Null(vuc.Writer);
+ Assert.True(triggered);
+ }
+
+ [Fact]
+ public void GetWriterFromRegularPageThrows()
+ {
+ // Arrange
+ MockViewUserControl vuc = new MockViewUserControl();
+ Page containerPage = new Page();
+ containerPage.Controls.Add(vuc);
+
+ // Act
+ Assert.Throws<InvalidOperationException>(
+ delegate { HtmlTextWriter writer = vuc.Writer; },
+ "A ViewUserControl can be used only in pages that derive from ViewPage or ViewPage<TModel>.");
+ }
+
+ [Fact]
+ public void ViewBagProperty_ReflectsViewData()
+ {
+ // Arrange
+ ViewPage containerPage = new ViewPage();
+ ViewUserControl userControl = new ViewUserControl();
+ containerPage.Controls.Add(userControl);
+ userControl.ViewData["A"] = 1;
+
+ // Act & Assert
+ Assert.NotNull(userControl.ViewBag);
+ Assert.Equal(1, userControl.ViewBag.A);
+ }
+
+ [Fact]
+ public void ViewBagProperty_ReflectsNewViewDataInstance()
+ {
+ // Arrange
+ ViewPage containerPage = new ViewPage();
+ ViewUserControl userControl = new ViewUserControl();
+ containerPage.Controls.Add(userControl);
+ userControl.ViewData["A"] = 1;
+ userControl.ViewData = new ViewDataDictionary() { { "A", "bar" } };
+
+ // Act & Assert
+ Assert.Equal("bar", userControl.ViewBag.A);
+ }
+
+ [Fact]
+ public void ViewBagProperty_PropagatesChangesToViewData()
+ {
+ // Arrange
+ ViewPage containerPage = new ViewPage();
+ ViewUserControl userControl = new ViewUserControl();
+ containerPage.Controls.Add(userControl);
+ userControl.ViewData["A"] = 1;
+
+ // Act
+ userControl.ViewBag.A = "foo";
+ userControl.ViewBag.B = 2;
+
+ // Assert
+ Assert.Equal("foo", userControl.ViewData["A"]);
+ Assert.Equal(2, userControl.ViewData["B"]);
+ }
+
+ private sealed class DummyViewData
+ {
+ public int MyInt { get; set; }
+ public string MyString { get; set; }
+ }
+
+ private sealed class MockViewUserControlContainerPage : ViewPage
+ {
+ public Action RenderCallback { get; set; }
+
+ public MockViewUserControlContainerPage(ViewUserControl userControl)
+ {
+ Controls.Add(userControl);
+ }
+
+ protected override void RenderChildren(HtmlTextWriter writer)
+ {
+ if (RenderCallback != null)
+ {
+ RenderCallback();
+ }
+ base.RenderChildren(writer);
+ }
+ }
+
+ private sealed class MockViewUserControl : ViewUserControl
+ {
+ }
+
+ private sealed class MockViewUserControl<TViewData> : ViewUserControl<TViewData>
+ {
+ }
+
+ private sealed class MyViewData
+ {
+ public int IntProp { get; set; }
+ public string StringProp { get; set; }
+ }
+
+ private sealed class FooModel
+ {
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Test/VirtualPathProviderViewEngineTest.cs b/test/System.Web.Mvc.Test/Test/VirtualPathProviderViewEngineTest.cs
new file mode 100644
index 00000000..555988df
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Test/VirtualPathProviderViewEngineTest.cs
@@ -0,0 +1,1412 @@
+using System.Collections;
+using System.IO;
+using System.Linq;
+using System.Web.Hosting;
+using System.Web.Routing;
+using System.Web.WebPages;
+using Moq;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Mvc.Test
+{
+ public class VirtualPathProviderViewEngineTest
+ {
+ [Fact]
+ public void FindView_NullControllerContext_Throws()
+ {
+ // Arrange
+ TestableVirtualPathProviderViewEngine engine = new TestableVirtualPathProviderViewEngine();
+
+ // Act & Assert
+ Assert.ThrowsArgumentNull(
+ () => engine.FindView(null, "view name", null, false),
+ "controllerContext"
+ );
+ }
+
+ [Fact]
+ public void FindView_NullViewName_Throws()
+ {
+ // Arrange
+ ControllerContext context = CreateContext();
+ TestableVirtualPathProviderViewEngine engine = new TestableVirtualPathProviderViewEngine();
+
+ // Act & Assert
+ Assert.ThrowsArgumentNullOrEmpty(
+ () => engine.FindView(context, null, null, false),
+ "viewName"
+ );
+ }
+
+ [Fact]
+ public void FindView_EmptyViewName_Throws()
+ {
+ // Arrange
+ ControllerContext context = CreateContext();
+ TestableVirtualPathProviderViewEngine engine = new TestableVirtualPathProviderViewEngine();
+
+ // Act & Assert
+ Assert.ThrowsArgumentNullOrEmpty(
+ () => engine.FindView(context, "", null, false),
+ "viewName"
+ );
+ }
+
+ [Fact]
+ public void FindView_ControllerNameNotInRequestContext_Throws()
+ {
+ // Arrange
+ TestableVirtualPathProviderViewEngine engine = new TestableVirtualPathProviderViewEngine();
+ ControllerContext context = CreateContext();
+ context.RouteData.Values.Remove("controller");
+
+ // Act & Assert
+ Assert.Throws<InvalidOperationException>(
+ () => engine.FindView(context, "viewName", null, false),
+ "The RouteData must contain an item named 'controller' with a non-empty string value."
+ );
+ }
+
+ [Fact]
+ public void FindView_EmptyViewLocations_Throws()
+ {
+ // Arrange
+ ControllerContext context = CreateContext();
+ TestableVirtualPathProviderViewEngine engine = new TestableVirtualPathProviderViewEngine();
+ engine.ClearViewLocations();
+
+ // Act & Assert
+ Assert.Throws<InvalidOperationException>(
+ () => engine.FindView(context, "viewName", null, false),
+ "The property 'ViewLocationFormats' cannot be null or empty."
+ );
+ }
+
+ [Fact]
+ public void FindView_ViewDoesNotExistAndNoMaster_ReturnsSearchedLocationsResult()
+ {
+ // Arrange
+ ControllerContext context = CreateContext();
+ TestableVirtualPathProviderViewEngine engine = new TestableVirtualPathProviderViewEngine();
+ engine.MockPathProvider
+ .Setup(vpp => vpp.FileExists("~/vpath/controllerName/viewName.view"))
+ .Returns(false)
+ .Verifiable();
+
+ // Act
+ ViewEngineResult result = engine.FindView(context, "viewName", null, false);
+
+ // Assert
+ Assert.Null(result.View);
+ Assert.Single(result.SearchedLocations);
+ Assert.True(result.SearchedLocations.Contains("~/vpath/controllerName/viewName.view"));
+ engine.MockPathProvider.Verify();
+ }
+
+ [Fact]
+ public void FindView_VirtualPathViewDoesNotExistAndNoMaster_ReturnsSearchedLocationsResult()
+ {
+ // Arrange
+ ControllerContext context = CreateContext();
+ TestableVirtualPathProviderViewEngine engine = new TestableVirtualPathProviderViewEngine();
+ engine.MockPathProvider
+ .Setup(vpp => vpp.FileExists("~/foo/bar.view"))
+ .Returns(false)
+ .Verifiable();
+ engine.MockCache
+ .Setup(c => c.InsertViewLocation(It.IsAny<HttpContextBase>(), It.IsAny<string>(), ""))
+ .Verifiable();
+
+ // Act
+ ViewEngineResult result = engine.FindView(context, "~/foo/bar.view", null, false);
+
+ // Assert
+ Assert.Null(result.View);
+ Assert.Single(result.SearchedLocations);
+ Assert.True(result.SearchedLocations.Contains("~/foo/bar.view"));
+ engine.MockPathProvider.Verify();
+ engine.MockCache.Verify();
+ }
+
+ [Fact]
+ public void FindView_VirtualPathViewNotSupportedAndNoMaster_ReturnsSearchedLocationsResult()
+ {
+ // Arrange
+ ControllerContext context = CreateContext();
+ TestableVirtualPathProviderViewEngine engine = new TestableVirtualPathProviderViewEngine();
+ engine.MockCache
+ .Setup(c => c.InsertViewLocation(It.IsAny<HttpContextBase>(), It.IsAny<string>(), ""))
+ .Verifiable();
+
+ // Act
+ ViewEngineResult result = engine.FindView(context, "~/foo/bar.unsupported", null, false);
+
+ // Assert
+ Assert.Null(result.View);
+ Assert.Single(result.SearchedLocations);
+ Assert.True(result.SearchedLocations.Contains("~/foo/bar.unsupported"));
+ engine.MockPathProvider.Verify(vpp => vpp.FileExists("~/foo/bar.unsupported"), Times.Never());
+ engine.MockCache.Verify();
+ }
+
+ [Fact]
+ public void FindView_AbsolutePathViewDoesNotExistAndNoMaster_ReturnsSearchedLocationsResult()
+ {
+ // Arrange
+ ControllerContext context = CreateContext();
+ TestableVirtualPathProviderViewEngine engine = new TestableVirtualPathProviderViewEngine();
+ engine.MockPathProvider
+ .Setup(vpp => vpp.FileExists("/foo/bar.view"))
+ .Returns(false)
+ .Verifiable();
+ engine.MockCache
+ .Setup(c => c.InsertViewLocation(It.IsAny<HttpContextBase>(), It.IsAny<string>(), ""))
+ .Verifiable();
+
+ // Act
+ ViewEngineResult result = engine.FindView(context, "/foo/bar.view", null, false);
+
+ // Assert
+ Assert.Null(result.View);
+ Assert.Single(result.SearchedLocations);
+ Assert.True(result.SearchedLocations.Contains("/foo/bar.view"));
+ engine.MockPathProvider.Verify();
+ engine.MockCache.Verify();
+ }
+
+ [Fact]
+ public void FindView_AbsolutePathViewNotSupportedAndNoMaster_ReturnsSearchedLocationsResult()
+ {
+ // Arrange
+ ControllerContext context = CreateContext();
+ TestableVirtualPathProviderViewEngine engine = new TestableVirtualPathProviderViewEngine();
+ engine.MockCache
+ .Setup(c => c.InsertViewLocation(It.IsAny<HttpContextBase>(), It.IsAny<string>(), ""))
+ .Verifiable();
+
+ // Act
+ ViewEngineResult result = engine.FindView(context, "/foo/bar.unsupported", null, false);
+
+ // Assert
+ Assert.Null(result.View);
+ Assert.Single(result.SearchedLocations);
+ Assert.True(result.SearchedLocations.Contains("/foo/bar.unsupported"));
+ engine.MockPathProvider.Verify(vpp => vpp.FileExists("/foo/bar.unsupported"), Times.Never());
+ engine.MockCache.Verify();
+ }
+
+ [Fact]
+ public void FindView_ViewExistsAndNoMaster_ReturnsView()
+ {
+ // Arrange
+ ControllerContext context = CreateContext();
+ TestableVirtualPathProviderViewEngine engine = new TestableVirtualPathProviderViewEngine();
+ engine.ClearMasterLocations(); // If master is not provided, master locations can be empty
+ engine.MockPathProvider
+ .Setup(vpp => vpp.FileExists("~/vpath/controllerName/viewName.view"))
+ .Returns(true)
+ .Verifiable();
+ engine.MockPathProvider
+ .Setup(vpp => vpp.FileExists("~/vpath/controllerName/viewName.Mobile.view"))
+ .Returns(false)
+ .Verifiable();
+ engine.MockCache
+ .Setup(c => c.InsertViewLocation(It.IsAny<HttpContextBase>(), It.IsAny<string>(), "~/vpath/controllerName/viewName.view"))
+ .Verifiable();
+
+ // Act
+ ViewEngineResult result = engine.FindView(context, "viewName", null, false);
+
+ // Assert
+ Assert.Same(engine.CreateViewResult, result.View);
+ Assert.Null(result.SearchedLocations);
+ Assert.Same(context, engine.CreateViewControllerContext);
+ Assert.Equal("~/vpath/controllerName/viewName.view", engine.CreateViewViewPath);
+ Assert.Equal(String.Empty, engine.CreateViewMasterPath);
+ engine.MockPathProvider.Verify();
+ engine.MockCache.Verify();
+ }
+
+ [Fact]
+ public void FindView_VirtualPathViewExistsAndNoMaster_ReturnsView()
+ {
+ // Arrange
+ ControllerContext context = CreateContext();
+ TestableVirtualPathProviderViewEngine engine = new TestableVirtualPathProviderViewEngine();
+ engine.ClearMasterLocations();
+ engine.MockPathProvider
+ .Setup(vpp => vpp.FileExists("~/foo/bar.view"))
+ .Returns(true)
+ .Verifiable();
+ engine.MockCache
+ .Setup(c => c.InsertViewLocation(It.IsAny<HttpContextBase>(), It.IsAny<string>(), "~/foo/bar.view"))
+ .Verifiable();
+
+ // Act
+ ViewEngineResult result = engine.FindView(context, "~/foo/bar.view", null, false);
+
+ // Assert
+ Assert.Same(engine.CreateViewResult, result.View);
+ Assert.Null(result.SearchedLocations);
+ Assert.Same(context, engine.CreateViewControllerContext);
+ Assert.Equal("~/foo/bar.view", engine.CreateViewViewPath);
+ Assert.Equal(String.Empty, engine.CreateViewMasterPath);
+ engine.MockPathProvider.Verify();
+ engine.MockCache.Verify();
+ }
+
+ [Fact]
+ public void FindView_VirtualPathViewExistsAndNoMaster_Legacy_ReturnsView()
+ {
+ // Arrange
+ ControllerContext context = CreateContext();
+ TestableVirtualPathProviderViewEngine engine = new TestableVirtualPathProviderViewEngine()
+ {
+ FileExtensions = null, // Set FileExtensions to null to simulate View Engines that do not set this property
+ };
+ engine.ClearMasterLocations();
+ engine.MockPathProvider
+ .Setup(vpp => vpp.FileExists("~/foo/bar.unsupported"))
+ .Returns(true)
+ .Verifiable();
+ engine.MockCache
+ .Setup(c => c.InsertViewLocation(It.IsAny<HttpContextBase>(), It.IsAny<string>(), "~/foo/bar.unsupported"))
+ .Verifiable();
+
+ // Act
+ ViewEngineResult result = engine.FindView(context, "~/foo/bar.unsupported", null, false);
+
+ // Assert
+ Assert.Same(engine.CreateViewResult, result.View);
+ Assert.Null(result.SearchedLocations);
+ Assert.Same(context, engine.CreateViewControllerContext);
+ Assert.Equal("~/foo/bar.unsupported", engine.CreateViewViewPath);
+ Assert.Equal(String.Empty, engine.CreateViewMasterPath);
+ engine.MockPathProvider.Verify();
+ engine.MockCache.Verify();
+ }
+
+ [Fact]
+ public void FindView_AbsolutePathViewExistsAndNoMaster_ReturnsView()
+ {
+ // Arrange
+ ControllerContext context = CreateContext();
+ TestableVirtualPathProviderViewEngine engine = new TestableVirtualPathProviderViewEngine();
+ engine.ClearMasterLocations();
+ engine.MockPathProvider
+ .Setup(vpp => vpp.FileExists("/foo/bar.view"))
+ .Returns(true)
+ .Verifiable();
+ engine.MockCache
+ .Setup(c => c.InsertViewLocation(It.IsAny<HttpContextBase>(), It.IsAny<string>(), "/foo/bar.view"))
+ .Verifiable();
+
+ // Act
+ ViewEngineResult result = engine.FindView(context, "/foo/bar.view", null, false);
+
+ // Assert
+ Assert.Same(engine.CreateViewResult, result.View);
+ Assert.Null(result.SearchedLocations);
+ Assert.Same(context, engine.CreateViewControllerContext);
+ Assert.Equal("/foo/bar.view", engine.CreateViewViewPath);
+ Assert.Equal(String.Empty, engine.CreateViewMasterPath);
+ engine.MockPathProvider.Verify();
+ engine.MockCache.Verify();
+ }
+
+ [Fact]
+ public void FindView_AbsolutePathViewExistsAndNoMaster_Legacy_ReturnsView()
+ {
+ // Arrange
+ ControllerContext context = CreateContext();
+ TestableVirtualPathProviderViewEngine engine = new TestableVirtualPathProviderViewEngine()
+ {
+ FileExtensions = null, // Set FileExtensions to null to simulate View Engines that do not set this property
+ };
+ engine.ClearMasterLocations();
+ engine.MockPathProvider
+ .Setup(vpp => vpp.FileExists("/foo/bar.unsupported"))
+ .Returns(true)
+ .Verifiable();
+ engine.MockCache
+ .Setup(c => c.InsertViewLocation(It.IsAny<HttpContextBase>(), It.IsAny<string>(), "/foo/bar.unsupported"))
+ .Verifiable();
+
+ // Act
+ ViewEngineResult result = engine.FindView(context, "/foo/bar.unsupported", null, false);
+
+ // Assert
+ Assert.Same(engine.CreateViewResult, result.View);
+ Assert.Null(result.SearchedLocations);
+ Assert.Same(context, engine.CreateViewControllerContext);
+ Assert.Equal("/foo/bar.unsupported", engine.CreateViewViewPath);
+ Assert.Equal(String.Empty, engine.CreateViewMasterPath);
+ engine.MockPathProvider.Verify();
+ engine.MockCache.Verify();
+ }
+
+ [Fact]
+ public void FindView_ViewExistsAndMasterNameProvidedButEmptyMasterLocations_Throws()
+ {
+ // Arrange
+ ControllerContext context = CreateContext();
+ TestableVirtualPathProviderViewEngine engine = new TestableVirtualPathProviderViewEngine();
+ engine.ClearMasterLocations();
+ engine.MockPathProvider
+ .Setup(vpp => vpp.FileExists("~/vpath/controllerName/viewName.view"))
+ .Returns(true)
+ .Verifiable();
+ engine.MockCache
+ .Setup(c => c.InsertViewLocation(It.IsAny<HttpContextBase>(), It.IsAny<string>(), "~/vpath/controllerName/viewName.view"))
+ .Verifiable();
+ engine.MockPathProvider
+ .Setup(vpp => vpp.FileExists("~/vpath/controllerName/viewName.Mobile.view"))
+ .Returns(false)
+ .Verifiable();
+
+ // Act & Assert
+ Assert.Throws<InvalidOperationException>(
+ () => engine.FindView(context, "viewName", "masterName", false),
+ "The property 'MasterLocationFormats' cannot be null or empty."
+ );
+ engine.MockPathProvider.Verify();
+ engine.MockCache.Verify();
+ }
+
+ [Fact]
+ public void FindView_ViewDoesNotExistAndMasterDoesNotExist_ReturnsSearchedLocationsResult()
+ {
+ // Arrange
+ ControllerContext context = CreateContext();
+ TestableVirtualPathProviderViewEngine engine = new TestableVirtualPathProviderViewEngine();
+ engine.MockPathProvider
+ .Setup(vpp => vpp.FileExists("~/vpath/controllerName/viewName.view"))
+ .Returns(false)
+ .Verifiable();
+ engine.MockPathProvider
+ .Setup(vpp => vpp.FileExists("~/vpath/controllerName/masterName.master"))
+ .Returns(false)
+ .Verifiable();
+
+ // Act
+ ViewEngineResult result = engine.FindView(context, "viewName", "masterName", false);
+
+ // Assert
+ Assert.Null(result.View);
+ Assert.Equal(2, result.SearchedLocations.Count()); // Both view and master locations
+ Assert.True(result.SearchedLocations.Contains("~/vpath/controllerName/viewName.view"));
+ Assert.True(result.SearchedLocations.Contains("~/vpath/controllerName/masterName.master"));
+ engine.MockPathProvider.Verify();
+ }
+
+ [Fact]
+ public void FindView_ViewExistsButMasterDoesNotExist_ReturnsSearchedLocationsResult()
+ {
+ // Arrange
+ ControllerContext context = CreateContext();
+ TestableVirtualPathProviderViewEngine engine = new TestableVirtualPathProviderViewEngine();
+ engine.MockPathProvider
+ .Setup(vpp => vpp.FileExists("~/vpath/controllerName/viewName.view"))
+ .Returns(true)
+ .Verifiable();
+ engine.MockPathProvider
+ .Setup(vpp => vpp.FileExists("~/vpath/controllerName/viewName.Mobile.view"))
+ .Returns(false)
+ .Verifiable();
+ engine.MockCache
+ .Setup(c => c.InsertViewLocation(It.IsAny<HttpContextBase>(), It.IsAny<string>(), "~/vpath/controllerName/viewName.view"))
+ .Verifiable();
+ engine.MockPathProvider
+ .Setup(vpp => vpp.FileExists("~/vpath/controllerName/masterName.master"))
+ .Returns(false)
+ .Verifiable();
+
+ // Act
+ ViewEngineResult result = engine.FindView(context, "viewName", "masterName", false);
+
+ // Assert
+ Assert.Null(result.View);
+ Assert.Single(result.SearchedLocations); // View was found, not included in 'searched locations'
+ Assert.True(result.SearchedLocations.Contains("~/vpath/controllerName/masterName.master"));
+ engine.MockPathProvider.Verify();
+ engine.MockCache.Verify();
+ }
+
+ [Fact]
+ public void FindView_MasterInAreaDoesNotExist_ReturnsSearchedLocationsResult()
+ {
+ // Arrange
+ ControllerContext context = CreateContext();
+ context.RouteData.DataTokens["area"] = "areaName";
+
+ TestableVirtualPathProviderViewEngine engine = new TestableVirtualPathProviderViewEngine();
+ engine.MockPathProvider
+ .Setup(vpp => vpp.FileExists("~/vpath/areaName/controllerName/viewName.view"))
+ .Returns(true)
+ .Verifiable();
+ engine.MockCache
+ .Setup(c => c.InsertViewLocation(It.IsAny<HttpContextBase>(), It.IsAny<string>(), "~/vpath/areaName/controllerName/viewName.view"))
+ .Verifiable();
+ engine.MockPathProvider
+ .Setup(vpp => vpp.FileExists("~/vpath/areaName/controllerName/viewName.Mobile.view"))
+ .Returns(false)
+ .Verifiable();
+ engine.MockPathProvider
+ .Setup(vpp => vpp.FileExists("~/vpath/areaName/controllerName/masterName.master"))
+ .Returns(false)
+ .Verifiable();
+ engine.MockPathProvider
+ .Setup(vpp => vpp.FileExists("~/vpath/controllerName/masterName.master"))
+ .Returns(false)
+ .Verifiable();
+
+ // Act
+ ViewEngineResult result = engine.FindView(context, "viewName", "masterName", false);
+
+ // Assert
+ Assert.Null(result.View);
+ Assert.Equal(2, result.SearchedLocations.Count()); // View was found, not included in 'searched locations'
+ Assert.True(result.SearchedLocations.Contains("~/vpath/areaName/controllerName/masterName.master"));
+ Assert.True(result.SearchedLocations.Contains("~/vpath/controllerName/masterName.master"));
+ engine.MockPathProvider.Verify();
+ engine.MockCache.Verify();
+ }
+
+ [Fact]
+ public void FindView_ViewExistsAndMasterExists_ReturnsView()
+ {
+ // Arrange
+ ControllerContext context = CreateContext();
+ TestableVirtualPathProviderViewEngine engine = new TestableVirtualPathProviderViewEngine();
+ engine.MockPathProvider
+ .Setup(vpp => vpp.FileExists("~/vpath/controllerName/viewName.view"))
+ .Returns(true)
+ .Verifiable();
+ engine.MockCache
+ .Setup(c => c.InsertViewLocation(It.IsAny<HttpContextBase>(), It.IsAny<string>(), "~/vpath/controllerName/viewName.view"))
+ .Verifiable();
+ engine.MockPathProvider
+ .Setup(vpp => vpp.FileExists("~/vpath/controllerName/viewName.Mobile.view"))
+ .Returns(false)
+ .Verifiable();
+ engine.MockPathProvider
+ .Setup(vpp => vpp.FileExists("~/vpath/controllerName/masterName.master"))
+ .Returns(true)
+ .Verifiable();
+ engine.MockCache
+ .Setup(c => c.InsertViewLocation(It.IsAny<HttpContextBase>(), It.IsAny<string>(), "~/vpath/controllerName/masterName.master"))
+ .Verifiable();
+ engine.MockPathProvider
+ .Setup(vpp => vpp.FileExists("~/vpath/controllerName/masterName.Mobile.master"))
+ .Returns(false)
+ .Verifiable();
+
+ // Act
+ ViewEngineResult result = engine.FindView(context, "viewName", "masterName", false);
+
+ // Assert
+ Assert.Same(engine.CreateViewResult, result.View);
+ Assert.Null(result.SearchedLocations);
+ Assert.Same(context, engine.CreateViewControllerContext);
+ Assert.Equal("~/vpath/controllerName/viewName.view", engine.CreateViewViewPath);
+ Assert.Equal("~/vpath/controllerName/masterName.master", engine.CreateViewMasterPath);
+ engine.MockPathProvider.Verify();
+ engine.MockCache.Verify();
+ }
+
+ [Fact]
+ public void FindView_ViewInAreaExistsAndMasterExists_ReturnsView()
+ {
+ // Arrange
+ ControllerContext context = CreateContext();
+ context.RouteData.DataTokens["area"] = "areaName";
+
+ TestableVirtualPathProviderViewEngine engine = new TestableVirtualPathProviderViewEngine();
+ engine.MockPathProvider
+ .Setup(vpp => vpp.FileExists("~/vpath/areaName/controllerName/viewName.view"))
+ .Returns(true)
+ .Verifiable();
+ engine.MockCache
+ .Setup(c => c.InsertViewLocation(It.IsAny<HttpContextBase>(), It.IsAny<string>(), "~/vpath/areaName/controllerName/viewName.view"))
+ .Verifiable();
+ engine.MockPathProvider
+ .Setup(vpp => vpp.FileExists("~/vpath/areaName/controllerName/viewName.Mobile.view"))
+ .Returns(false)
+ .Verifiable();
+ engine.MockPathProvider
+ .Setup(vpp => vpp.FileExists("~/vpath/areaName/controllerName/masterName.master"))
+ .Returns(false)
+ .Verifiable();
+ engine.MockPathProvider
+ .Setup(vpp => vpp.FileExists("~/vpath/controllerName/masterName.master"))
+ .Returns(true)
+ .Verifiable();
+ engine.MockCache
+ .Setup(c => c.InsertViewLocation(It.IsAny<HttpContextBase>(), It.IsAny<string>(), "~/vpath/controllerName/masterName.master"))
+ .Verifiable();
+ engine.MockPathProvider
+ .Setup(vpp => vpp.FileExists("~/vpath/controllerName/masterName.Mobile.master"))
+ .Returns(false)
+ .Verifiable();
+
+ // Act
+ ViewEngineResult result = engine.FindView(context, "viewName", "masterName", false);
+
+ // Assert
+ Assert.Same(engine.CreateViewResult, result.View);
+ Assert.Null(result.SearchedLocations);
+ Assert.Same(context, engine.CreateViewControllerContext);
+ Assert.Equal("~/vpath/areaName/controllerName/viewName.view", engine.CreateViewViewPath);
+ Assert.Equal("~/vpath/controllerName/masterName.master", engine.CreateViewMasterPath);
+ engine.MockPathProvider.Verify();
+ engine.MockCache.Verify();
+ }
+
+ [Fact]
+ public void FindView_ViewInAreaExistsAndMasterExists_ReturnsView_Mobile()
+ {
+ // Arrange
+ ControllerContext context = CreateContext(isMobileDevice: true);
+ context.RouteData.DataTokens["area"] = "areaName";
+
+ TestableVirtualPathProviderViewEngine engine = new TestableVirtualPathProviderViewEngine();
+ engine.MockPathProvider
+ .Setup(vpp => vpp.FileExists("~/vpath/areaName/controllerName/viewName.view"))
+ .Returns(true)
+ .Verifiable();
+ engine.MockCache
+ .Setup(c => c.InsertViewLocation(It.IsAny<HttpContextBase>(), It.IsAny<string>(), "~/vpath/areaName/controllerName/viewName.view"))
+ .Verifiable();
+
+ engine.MockPathProvider
+ .Setup(vpp => vpp.FileExists("~/vpath/areaName/controllerName/viewName.Mobile.view"))
+ .Returns(true)
+ .Verifiable();
+ engine.MockCache
+ .Setup(c => c.InsertViewLocation(It.IsAny<HttpContextBase>(), It.IsAny<string>(), "~/vpath/areaName/controllerName/viewName.Mobile.view"))
+ .Verifiable();
+
+ engine.MockPathProvider
+ .Setup(vpp => vpp.FileExists("~/vpath/areaName/controllerName/masterName.master"))
+ .Returns(false)
+ .Verifiable();
+ engine.MockPathProvider
+ .Setup(vpp => vpp.FileExists("~/vpath/areaName/controllerName/masterName.Mobile.master"))
+ .Returns(false)
+ .Verifiable();
+
+ engine.MockPathProvider
+ .Setup(vpp => vpp.FileExists("~/vpath/controllerName/masterName.master"))
+ .Returns(true)
+ .Verifiable();
+ engine.MockCache
+ .Setup(c => c.InsertViewLocation(It.IsAny<HttpContextBase>(), It.IsAny<string>(), "~/vpath/controllerName/masterName.master"))
+ .Verifiable();
+
+ engine.MockPathProvider
+ .Setup(vpp => vpp.FileExists("~/vpath/controllerName/masterName.Mobile.master"))
+ .Returns(true)
+ .Verifiable();
+ engine.MockCache
+ .Setup(c => c.InsertViewLocation(It.IsAny<HttpContextBase>(), It.IsAny<string>(), "~/vpath/controllerName/masterName.Mobile.master"))
+ .Verifiable();
+
+ // Act
+ ViewEngineResult result = engine.FindView(context, "viewName", "masterName", false);
+
+ // Assert
+ Assert.Same(engine.CreateViewResult, result.View);
+ Assert.Null(result.SearchedLocations);
+ Assert.Same(context, engine.CreateViewControllerContext);
+ Assert.Equal("~/vpath/areaName/controllerName/viewName.Mobile.view", engine.CreateViewViewPath);
+ Assert.Equal("~/vpath/controllerName/masterName.Mobile.master", engine.CreateViewMasterPath);
+ engine.MockPathProvider.Verify();
+ engine.MockCache.Verify();
+ }
+
+ [Fact]
+ public void FindPartialView_NullControllerContext_Throws()
+ {
+ // Arrange
+ TestableVirtualPathProviderViewEngine engine = new TestableVirtualPathProviderViewEngine();
+
+ // Act & Assert
+ Assert.ThrowsArgumentNull(
+ () => engine.FindPartialView(null, "view name", false),
+ "controllerContext"
+ );
+ }
+
+ [Fact]
+ public void FindPartialView_NullPartialViewName_Throws()
+ {
+ // Arrange
+ ControllerContext context = CreateContext();
+ TestableVirtualPathProviderViewEngine engine = new TestableVirtualPathProviderViewEngine();
+
+ // Act & Assert
+ Assert.ThrowsArgumentNullOrEmpty(
+ () => engine.FindPartialView(context, null, false),
+ "partialViewName"
+ );
+ }
+
+ [Fact]
+ public void FindPartialView_EmptyPartialViewName_Throws()
+ {
+ // Arrange
+ ControllerContext context = CreateContext();
+ TestableVirtualPathProviderViewEngine engine = new TestableVirtualPathProviderViewEngine();
+
+ // Act & Assert
+ Assert.ThrowsArgumentNullOrEmpty(
+ () => engine.FindPartialView(context, "", false),
+ "partialViewName"
+ );
+ }
+
+ [Fact]
+ public void FindPartialView_ControllerNameNotInRequestContext_Throws()
+ {
+ // Arrange
+ TestableVirtualPathProviderViewEngine engine = new TestableVirtualPathProviderViewEngine();
+ ControllerContext context = CreateContext();
+ context.RouteData.Values.Remove("controller");
+
+ // Act & Assert
+ Assert.Throws<InvalidOperationException>(
+ () => engine.FindPartialView(context, "partialName", false),
+ "The RouteData must contain an item named 'controller' with a non-empty string value."
+ );
+ }
+
+ [Fact]
+ public void FindPartialView_EmptyPartialViewLocations_Throws()
+ {
+ // Arrange
+ ControllerContext context = CreateContext();
+ TestableVirtualPathProviderViewEngine engine = new TestableVirtualPathProviderViewEngine();
+ engine.ClearPartialViewLocations();
+
+ // Act & Assert
+ Assert.Throws<InvalidOperationException>(
+ () => engine.FindPartialView(context, "partialName", false),
+ "The property 'PartialViewLocationFormats' cannot be null or empty."
+ );
+ }
+
+ [Fact]
+ public void FindPartialView_ViewDoesNotExist_ReturnsSearchLocationsResult()
+ {
+ // Arrange
+ ControllerContext context = CreateContext();
+ TestableVirtualPathProviderViewEngine engine = new TestableVirtualPathProviderViewEngine();
+ engine.MockPathProvider
+ .Setup(vpp => vpp.FileExists("~/vpath/controllerName/partialName.partial"))
+ .Returns(false)
+ .Verifiable();
+
+ // Act
+ ViewEngineResult result = engine.FindPartialView(context, "partialName", false);
+
+ // Assert
+ Assert.Null(result.View);
+ Assert.Single(result.SearchedLocations);
+ Assert.True(result.SearchedLocations.Contains("~/vpath/controllerName/partialName.partial"));
+ engine.MockPathProvider.Verify();
+ }
+
+ [Fact]
+ public void FindPartialView_VirtualPathViewExists_Legacy_ReturnsView()
+ {
+ // Arrange
+ ControllerContext context = CreateContext();
+ TestableVirtualPathProviderViewEngine engine = new TestableVirtualPathProviderViewEngine()
+ {
+ FileExtensions = null, // Set FileExtensions to null to simulate View Engines that do not set this property
+ };
+ engine.MockPathProvider
+ .Setup(vpp => vpp.FileExists("~/foo/bar.unsupported"))
+ .Returns(true)
+ .Verifiable();
+ engine.MockCache
+ .Setup(c => c.InsertViewLocation(It.IsAny<HttpContextBase>(), It.IsAny<string>(), "~/foo/bar.unsupported"))
+ .Verifiable();
+
+ // Act
+ ViewEngineResult result = engine.FindPartialView(context, "~/foo/bar.unsupported", false);
+
+ // Assert
+ Assert.Same(engine.CreatePartialViewResult, result.View);
+ Assert.Null(result.SearchedLocations);
+ Assert.Same(context, engine.CreatePartialViewControllerContext);
+ Assert.Equal("~/foo/bar.unsupported", engine.CreatePartialViewPartialPath);
+ engine.MockPathProvider.Verify();
+ engine.MockCache.Verify();
+ }
+
+ [Fact]
+ public void FindPartialView_VirtualPathViewDoesNotExist_ReturnsSearchedLocationsResult()
+ {
+ // Arrange
+ ControllerContext context = CreateContext();
+ TestableVirtualPathProviderViewEngine engine = new TestableVirtualPathProviderViewEngine();
+ engine.MockPathProvider
+ .Setup(vpp => vpp.FileExists("~/foo/bar.partial"))
+ .Returns(false)
+ .Verifiable();
+ engine.MockCache
+ .Setup(c => c.InsertViewLocation(It.IsAny<HttpContextBase>(), It.IsAny<string>(), ""))
+ .Verifiable();
+
+ // Act
+ ViewEngineResult result = engine.FindPartialView(context, "~/foo/bar.partial", false);
+
+ // Assert
+ Assert.Null(result.View);
+ Assert.Single(result.SearchedLocations);
+ Assert.True(result.SearchedLocations.Contains("~/foo/bar.partial"));
+ engine.MockPathProvider.Verify();
+ engine.MockCache.Verify();
+ }
+
+ [Fact]
+ public void FindPartialView_VirtualPathViewNotSupported_ReturnsSearchedLocationsResult()
+ {
+ // Arrange
+ ControllerContext context = CreateContext();
+ TestableVirtualPathProviderViewEngine engine = new TestableVirtualPathProviderViewEngine();
+ engine.MockCache
+ .Setup(c => c.InsertViewLocation(It.IsAny<HttpContextBase>(), It.IsAny<string>(), ""))
+ .Verifiable();
+
+ // Act
+ ViewEngineResult result = engine.FindPartialView(context, "~/foo/bar.unsupported", false);
+
+ // Assert
+ Assert.Null(result.View);
+ Assert.Single(result.SearchedLocations);
+ Assert.True(result.SearchedLocations.Contains("~/foo/bar.unsupported"));
+ engine.MockPathProvider.Verify(vpp => vpp.FileExists("~/foo/bar.unsupported"), Times.Never());
+ engine.MockCache.Verify();
+ }
+
+ [Fact]
+ public void FindPartialView_AbsolutePathViewDoesNotExist_ReturnsSearchedLocationsResult()
+ {
+ // Arrange
+ ControllerContext context = CreateContext();
+ TestableVirtualPathProviderViewEngine engine = new TestableVirtualPathProviderViewEngine();
+ engine.MockPathProvider
+ .Setup(vpp => vpp.FileExists("/foo/bar.partial"))
+ .Returns(false)
+ .Verifiable();
+ engine.MockCache
+ .Setup(c => c.InsertViewLocation(It.IsAny<HttpContextBase>(), It.IsAny<string>(), ""))
+ .Verifiable();
+
+ // Act
+ ViewEngineResult result = engine.FindPartialView(context, "/foo/bar.partial", false);
+
+ // Assert
+ Assert.Null(result.View);
+ Assert.Single(result.SearchedLocations);
+ Assert.True(result.SearchedLocations.Contains("/foo/bar.partial"));
+ engine.MockPathProvider.Verify();
+ engine.MockCache.Verify();
+ }
+
+ [Fact]
+ public void FindPartialView_AbsolutePathViewNotSupported_ReturnsSearchedLocationsResult()
+ {
+ // Arrange
+ ControllerContext context = CreateContext();
+ TestableVirtualPathProviderViewEngine engine = new TestableVirtualPathProviderViewEngine();
+ engine.MockCache
+ .Setup(c => c.InsertViewLocation(It.IsAny<HttpContextBase>(), It.IsAny<string>(), ""))
+ .Verifiable();
+
+ // Act
+ ViewEngineResult result = engine.FindPartialView(context, "/foo/bar.unsupported", false);
+
+ // Assert
+ Assert.Null(result.View);
+ Assert.Single(result.SearchedLocations);
+ Assert.True(result.SearchedLocations.Contains("/foo/bar.unsupported"));
+ engine.MockPathProvider.Verify<bool>(vpp => vpp.FileExists("/foo/bar.unsupported"), Times.Never());
+ engine.MockCache.Verify();
+ }
+
+ [Fact]
+ public void FindPartialView_AbsolutePathViewExists_Legacy_ReturnsView()
+ {
+ // Arrange
+ ControllerContext context = CreateContext();
+ TestableVirtualPathProviderViewEngine engine = new TestableVirtualPathProviderViewEngine()
+ {
+ FileExtensions = null, // Set FileExtensions to null to simulate View Engines that do not set this property
+ };
+ engine.MockPathProvider
+ .Setup(vpp => vpp.FileExists("/foo/bar.unsupported"))
+ .Returns(true)
+ .Verifiable();
+ engine.MockCache
+ .Setup(c => c.InsertViewLocation(It.IsAny<HttpContextBase>(), It.IsAny<string>(), "/foo/bar.unsupported"))
+ .Verifiable();
+
+ // Act
+ ViewEngineResult result = engine.FindPartialView(context, "/foo/bar.unsupported", false);
+
+ // Assert
+ Assert.Same(engine.CreatePartialViewResult, result.View);
+ Assert.Null(result.SearchedLocations);
+ Assert.Same(context, engine.CreatePartialViewControllerContext);
+ Assert.Equal("/foo/bar.unsupported", engine.CreatePartialViewPartialPath);
+ engine.MockPathProvider.Verify();
+ engine.MockCache.Verify();
+ }
+
+ [Fact]
+ public void FindPartialView_ViewExists_ReturnsView()
+ {
+ // Arrange
+ ControllerContext context = CreateContext();
+ TestableVirtualPathProviderViewEngine engine = new TestableVirtualPathProviderViewEngine();
+ engine.MockPathProvider
+ .Setup(vpp => vpp.FileExists("~/vpath/controllerName/partialName.partial"))
+ .Returns(true)
+ .Verifiable();
+ engine.MockCache
+ .Setup(c => c.InsertViewLocation(It.IsAny<HttpContextBase>(), It.IsAny<string>(), "~/vpath/controllerName/partialName.partial"))
+ .Verifiable();
+ engine.MockPathProvider
+ .Setup(vpp => vpp.FileExists("~/vpath/controllerName/partialName.Mobile.partial"))
+ .Returns(false)
+ .Verifiable();
+
+ // Act
+ ViewEngineResult result = engine.FindPartialView(context, "partialName", false);
+
+ // Assert
+ Assert.Same(engine.CreatePartialViewResult, result.View);
+ Assert.Null(result.SearchedLocations);
+ Assert.Same(context, engine.CreatePartialViewControllerContext);
+ Assert.Equal("~/vpath/controllerName/partialName.partial", engine.CreatePartialViewPartialPath);
+ engine.MockPathProvider.Verify();
+ engine.MockCache.Verify();
+ }
+
+ [Fact]
+ public void FindPartialView_VirtualPathViewExists_ReturnsView()
+ {
+ // Arrange
+ ControllerContext context = CreateContext();
+ TestableVirtualPathProviderViewEngine engine = new TestableVirtualPathProviderViewEngine();
+ engine.MockPathProvider
+ .Setup(vpp => vpp.FileExists("~/foo/bar.partial"))
+ .Returns(true)
+ .Verifiable();
+ engine.MockCache
+ .Setup(c => c.InsertViewLocation(It.IsAny<HttpContextBase>(), It.IsAny<string>(), "~/foo/bar.partial"))
+ .Verifiable();
+
+ // Act
+ ViewEngineResult result = engine.FindPartialView(context, "~/foo/bar.partial", false);
+
+ // Assert
+ Assert.Same(engine.CreatePartialViewResult, result.View);
+ Assert.Null(result.SearchedLocations);
+ Assert.Same(context, engine.CreatePartialViewControllerContext);
+ Assert.Equal("~/foo/bar.partial", engine.CreatePartialViewPartialPath);
+ engine.MockPathProvider.Verify();
+ engine.MockCache.Verify();
+ }
+
+ [Fact]
+ public void FindPartialView_AbsolutePathViewExists_ReturnsView()
+ {
+ // Arrange
+ ControllerContext context = CreateContext();
+ TestableVirtualPathProviderViewEngine engine = new TestableVirtualPathProviderViewEngine();
+ engine.MockPathProvider
+ .Setup(vpp => vpp.FileExists("/foo/bar.partial"))
+ .Returns(true)
+ .Verifiable();
+ engine.MockCache
+ .Setup(c => c.InsertViewLocation(It.IsAny<HttpContextBase>(), It.IsAny<string>(), "/foo/bar.partial"))
+ .Verifiable();
+
+ // Act
+ ViewEngineResult result = engine.FindPartialView(context, "/foo/bar.partial", false);
+
+ // Assert
+ Assert.Same(engine.CreatePartialViewResult, result.View);
+ Assert.Null(result.SearchedLocations);
+ Assert.Same(context, engine.CreatePartialViewControllerContext);
+ Assert.Equal("/foo/bar.partial", engine.CreatePartialViewPartialPath);
+ engine.MockPathProvider.Verify();
+ engine.MockCache.Verify();
+ }
+
+ [Fact]
+ public void FileExtensions()
+ {
+ // Arrange + Assert
+ Assert.Null(new Mock<VirtualPathProviderViewEngine>().Object.FileExtensions);
+ }
+
+ [Fact]
+ public void GetExtensionThunk()
+ {
+ // Arrange and Assert
+ Assert.Equal(VirtualPathUtility.GetExtension, new Mock<VirtualPathProviderViewEngine>().Object.GetExtensionThunk);
+ }
+
+ [Fact]
+ public void DisplayModeSetOncePerRequest()
+ {
+ // Arrange
+ RouteData routeData = new RouteData();
+ routeData.Values["controller"] = "controllerName";
+ routeData.Values["action"] = "actionName";
+
+ Mock<ControllerContext> mockControllerContext = new Mock<ControllerContext>();
+ mockControllerContext.Setup(c => c.HttpContext.Request.Browser.IsMobileDevice).Returns(true);
+ mockControllerContext.Setup(c => c.HttpContext.Request.Cookies).Returns(new HttpCookieCollection());
+ mockControllerContext.Setup(c => c.HttpContext.Response.Cookies).Returns(new HttpCookieCollection());
+ mockControllerContext.Setup(c => c.HttpContext.Items).Returns(new Hashtable());
+ mockControllerContext.Setup(c => c.RouteData).Returns(routeData);
+
+ ControllerContext context = mockControllerContext.Object;
+ TestableVirtualPathProviderViewEngine engine = new TestableVirtualPathProviderViewEngine();
+
+ engine.MockPathProvider
+ .Setup(vpp => vpp.FileExists("~/vpath/controllerName/viewName.view"))
+ .Returns(true)
+ .Verifiable();
+ engine.MockCache
+ .Setup(c => c.InsertViewLocation(It.IsAny<HttpContextBase>(), It.IsAny<string>(), "~/vpath/controllerName/viewName.view"))
+ .Verifiable();
+ engine.MockPathProvider
+ .Setup(vpp => vpp.FileExists("~/vpath/controllerName/viewName.Mobile.view"))
+ .Returns(false)
+ .Verifiable();
+
+ engine.MockPathProvider
+ .Setup(vpp => vpp.FileExists("~/vpath/controllerName/partialName.partial"))
+ .Returns(false)
+ .Verifiable();
+ engine.MockPathProvider
+ .Setup(vpp => vpp.FileExists("~/vpath/controllerName/partialName.Mobile.partial"))
+ .Returns(true)
+ .Verifiable();
+ engine.MockCache
+ .Setup(c => c.InsertViewLocation(It.IsAny<HttpContextBase>(), It.IsAny<string>(), "~/vpath/controllerName/partialName.Mobile.partial"))
+ .Callback<HttpContextBase, string, string>((httpContext, key, virtualPath) =>
+ {
+ engine.MockCache
+ .Setup(c => c.GetViewLocation(It.IsAny<HttpContextBase>(), key))
+ .Returns("~/vpath/controllerName/partialName.Mobile.partial")
+ .Verifiable();
+ })
+ .Verifiable();
+
+ // Act
+ ViewEngineResult viewResult = engine.FindView(context, "viewName", masterName: null, useCache: false);
+
+ // Mobile display mode will be used to locate the view with and without the cache.
+ // In neither case should this set the DisplayModeId to Mobile because it has already been set.
+ ViewEngineResult partialResult = engine.FindPartialView(context, "partialName", useCache: false);
+ ViewEngineResult cachedPartialResult = engine.FindPartialView(context, "partialName", useCache: true);
+
+ // Assert
+ engine.MockPathProvider.Verify();
+ engine.MockCache.Verify();
+ Assert.Same(engine.CreateViewResult, viewResult.View);
+ Assert.Same(engine.CreatePartialViewResult, partialResult.View);
+ Assert.Same(engine.CreatePartialViewResult, cachedPartialResult.View);
+
+ Assert.Equal(DisplayModeProvider.DefaultDisplayModeId, context.DisplayMode.DisplayModeId);
+ }
+
+ // The core caching scenarios are covered in the FindView/FindPartialView tests. These
+ // extra tests deal with the cache itself, rather than specifics around finding views.
+
+ private const string MASTER_VIRTUAL = "~/vpath/controllerName/name.master";
+ private const string PARTIAL_VIRTUAL = "~/vpath/controllerName/name.partial";
+ private const string VIEW_VIRTUAL = "~/vpath/controllerName/name.view";
+ private const string MOBILE_VIEW_VIRTUAL = "~/vpath/controllerName/name.Mobile.view";
+
+ [Fact]
+ public void UsesDifferentKeysForViewMasterAndPartial()
+ {
+ string keyMaster = null;
+ string keyPartial = null;
+ string keyView = null;
+
+ // Arrange
+ ControllerContext context = CreateContext();
+ TestableVirtualPathProviderViewEngine engine = new TestableVirtualPathProviderViewEngine();
+ engine.MockPathProvider
+ .Setup(vpp => vpp.FileExists(VIEW_VIRTUAL))
+ .Returns(true)
+ .Verifiable();
+ engine.MockPathProvider
+ .Setup(vpp => vpp.FileExists(MOBILE_VIEW_VIRTUAL))
+ .Returns(false)
+ .Verifiable();
+ engine.MockPathProvider
+ .Setup(vpp => vpp.FileExists(MASTER_VIRTUAL))
+ .Returns(true)
+ .Verifiable();
+ engine.MockPathProvider
+ .Setup(vpp => vpp.FileExists("~/vpath/controllerName/name.Mobile.master"))
+ .Returns(false)
+ .Verifiable();
+ engine.MockPathProvider
+ .Setup(vpp => vpp.FileExists(PARTIAL_VIRTUAL))
+ .Returns(true)
+ .Verifiable();
+ engine.MockPathProvider
+ .Setup(vpp => vpp.FileExists("~/vpath/controllerName/name.Mobile.partial"))
+ .Returns(false)
+ .Verifiable();
+ engine.MockCache
+ .Setup(c => c.InsertViewLocation(It.IsAny<HttpContextBase>(), It.IsAny<string>(), VIEW_VIRTUAL))
+ .Callback<HttpContextBase, string, string>((httpContext, key, path) => keyView = key)
+ .Verifiable();
+ engine.MockCache
+ .Setup(c => c.InsertViewLocation(It.IsAny<HttpContextBase>(), It.IsAny<string>(), MASTER_VIRTUAL))
+ .Callback<HttpContextBase, string, string>((httpContext, key, path) => keyMaster = key)
+ .Verifiable();
+ engine.MockCache
+ .Setup(c => c.InsertViewLocation(It.IsAny<HttpContextBase>(), It.IsAny<string>(), PARTIAL_VIRTUAL))
+ .Callback<HttpContextBase, string, string>((httpContext, key, path) => keyPartial = key)
+ .Verifiable();
+
+ // Act
+ engine.FindView(context, "name", "name", false);
+ engine.FindPartialView(context, "name", false);
+
+ // Assert
+ Assert.NotNull(keyMaster);
+ Assert.NotNull(keyPartial);
+ Assert.NotNull(keyView);
+ Assert.NotEqual(keyMaster, keyPartial);
+ Assert.NotEqual(keyMaster, keyView);
+ Assert.NotEqual(keyPartial, keyView);
+ engine.MockPathProvider.Verify();
+ engine.MockCache.Verify();
+ engine.MockPathProvider
+ .Verify(vpp => vpp.FileExists(VIEW_VIRTUAL), Times.AtMostOnce());
+ engine.MockPathProvider
+ .Verify(vpp => vpp.FileExists(MASTER_VIRTUAL), Times.AtMostOnce());
+ engine.MockPathProvider
+ .Verify(vpp => vpp.FileExists(PARTIAL_VIRTUAL), Times.AtMostOnce());
+ engine.MockCache
+ .Verify(c => c.InsertViewLocation(It.IsAny<HttpContextBase>(), It.IsAny<string>(), VIEW_VIRTUAL), Times.AtMostOnce());
+ engine.MockCache
+ .Verify(c => c.InsertViewLocation(It.IsAny<HttpContextBase>(), It.IsAny<string>(), MASTER_VIRTUAL), Times.AtMostOnce());
+ engine.MockCache
+ .Verify(c => c.InsertViewLocation(It.IsAny<HttpContextBase>(), It.IsAny<string>(), PARTIAL_VIRTUAL), Times.AtMostOnce());
+ }
+
+ // This tests the protocol involved with two calls to FindView for the same view name
+ // where the request succeeds. The calls happen in this order:
+ //
+ // FindView("view")
+ // Cache.GetViewLocation(key for "view") -> returns null (not found)
+ // VirtualPathProvider.FileExists(virtual path for "view") -> returns true
+ // Cache.InsertViewLocation(key for "view", virtual path for "view")
+ // FindView("view")
+ // Cache.GetViewLocation(key for "view") -> returns virtual path for "view"
+ //
+ // The mocking code is written as it is because we don't want to make any assumptions
+ // about the format of the cache key. So we intercept the first call to Cache.GetViewLocation and
+ // take the key they gave us to set up the rest of the mock expectations.
+ // The ViewCollection class will typically place to successive calls to FindView and FindPartialView and
+ // set the useCache parameter to true/false respectively. To simulate this, both calls to FindView are executed
+ // with useCache set to true. This mimics the behavior of always going to the cache first and after finding a
+ // view, ensuring that subsequent calls from the cache are successful.
+
+ [Fact]
+ public void ValueInCacheBypassesVirtualPathProvider()
+ {
+ // Arrange
+ string cacheKey = null;
+ ControllerContext context = CreateContext();
+ ControllerContext mobileContext = CreateContext(isMobileDevice: true);
+
+ TestableVirtualPathProviderViewEngine engine = new TestableVirtualPathProviderViewEngine();
+
+ engine.MockPathProvider // It wasn't found, so they call vpp.FileExists
+ .Setup(vpp => vpp.FileExists(VIEW_VIRTUAL))
+ .Returns(true)
+ .Verifiable();
+ engine.MockPathProvider
+ .Setup(vpp => vpp.FileExists(MOBILE_VIEW_VIRTUAL))
+ .Returns(false)
+ .Verifiable();
+ engine.MockCache // Then they set the value into the cache
+ .Setup(c => c.InsertViewLocation(It.IsAny<HttpContextBase>(), It.IsAny<string>(), VIEW_VIRTUAL))
+ .Callback<HttpContextBase, string, string>((httpContext, key, virtualPath) =>
+ {
+ cacheKey = key;
+ engine.MockCache // Second time through, we give them a cache hit
+ .Setup(c => c.GetViewLocation(It.IsAny<HttpContextBase>(), key))
+ .Returns(VIEW_VIRTUAL)
+ .Verifiable();
+ })
+ .Verifiable();
+
+ // Act
+ engine.FindView(context, "name", null, false); // Call it once with false to seed the cache
+ engine.FindView(context, "name", null, true); // Call it once with true to check the cache
+
+ // Assert
+ engine.MockPathProvider.Verify();
+ engine.MockCache.Verify();
+
+ engine.MockPathProvider.Verify(vpp => vpp.FileExists(VIEW_VIRTUAL), Times.AtMostOnce());
+ engine.MockCache.Verify(c => c.InsertViewLocation(It.IsAny<HttpContextBase>(), It.IsAny<string>(), VIEW_VIRTUAL), Times.AtMostOnce());
+ engine.MockCache.Verify(c => c.GetViewLocation(It.IsAny<HttpContextBase>(), cacheKey), Times.AtMostOnce());
+
+ // We seed the cache with all possible display modes but since the mobile view does not exist we don't insert it into the cache.
+ engine.MockPathProvider.Verify(vpp => vpp.FileExists(MOBILE_VIEW_VIRTUAL), Times.Exactly(1));
+ engine.MockCache.Verify(c => c.InsertViewLocation(It.IsAny<HttpContextBase>(), It.IsAny<string>(), MOBILE_VIEW_VIRTUAL), Times.Never());
+ engine.MockCache.Verify(c => c.GetViewLocation(It.IsAny<HttpContextBase>(), VirtualPathProviderViewEngine.AppendDisplayModeToCacheKey(cacheKey, DisplayModeProvider.MobileDisplayModeId)), Times.Never());
+ }
+
+ [Fact]
+ public void ValueInCacheBypassesVirtualPathProviderForAllAvailableDisplayModesForContext()
+ {
+ // Arrange
+ string cacheKey = null;
+ string mobileCacheKey = null;
+
+ ControllerContext mobileContext = CreateContext(isMobileDevice: true);
+ ControllerContext desktopContext = CreateContext(isMobileDevice: false);
+
+ TestableVirtualPathProviderViewEngine engine = new TestableVirtualPathProviderViewEngine();
+
+ engine.MockPathProvider
+ .Setup(vpp => vpp.FileExists(VIEW_VIRTUAL))
+ .Returns(true)
+ .Verifiable();
+ engine.MockPathProvider
+ .Setup(vpp => vpp.FileExists(MOBILE_VIEW_VIRTUAL))
+ .Returns(true)
+ .Verifiable();
+
+ engine.MockCache
+ .Setup(c => c.InsertViewLocation(It.IsAny<HttpContextBase>(), It.IsAny<string>(), VIEW_VIRTUAL))
+ .Callback<HttpContextBase, string, string>((httpContext, key, virtualPath) =>
+ {
+ cacheKey = key;
+ engine.MockCache
+ .Setup(c => c.GetViewLocation(It.IsAny<HttpContextBase>(), key))
+ .Returns(MOBILE_VIEW_VIRTUAL)
+ .Verifiable();
+ })
+ .Verifiable();
+ engine.MockCache
+ .Setup(c => c.InsertViewLocation(It.IsAny<HttpContextBase>(), It.IsAny<string>(), MOBILE_VIEW_VIRTUAL))
+ .Callback<HttpContextBase, string, string>((httpContext, key, virtualPath) =>
+ {
+ mobileCacheKey = key;
+ engine.MockCache
+ .Setup(c => c.GetViewLocation(It.IsAny<HttpContextBase>(), key))
+ .Returns(MOBILE_VIEW_VIRTUAL)
+ .Verifiable();
+ })
+ .Verifiable();
+
+ // Act
+ engine.FindView(mobileContext, "name", null, false);
+ engine.FindView(mobileContext, "name", null, true);
+
+ // Assert
+ engine.MockPathProvider.Verify();
+
+ // DefaultDisplayMode with Mobile substitution is cached and hit on the second call to FindView
+ engine.MockPathProvider.Verify(vpp => vpp.FileExists(MOBILE_VIEW_VIRTUAL), Times.AtMostOnce());
+ engine.MockCache.Verify(c => c.InsertViewLocation(It.IsAny<HttpContextBase>(), It.IsAny<string>(), MOBILE_VIEW_VIRTUAL), Times.AtMostOnce());
+ engine.MockCache.Verify(c => c.GetViewLocation(It.IsAny<HttpContextBase>(), VirtualPathProviderViewEngine.AppendDisplayModeToCacheKey(cacheKey, DisplayModeProvider.MobileDisplayModeId)), Times.AtMostOnce());
+
+ engine.MockPathProvider.Verify(vpp => vpp.FileExists(VIEW_VIRTUAL), Times.AtMostOnce());
+ engine.MockCache.Verify(c => c.InsertViewLocation(It.IsAny<HttpContextBase>(), It.IsAny<string>(), VIEW_VIRTUAL), Times.Exactly(1));
+
+ Assert.NotEqual(cacheKey, mobileCacheKey);
+
+ // Act
+ engine.FindView(desktopContext, "name", null, true);
+
+ // Assert
+ engine.MockCache.Verify();
+
+ // The first call to FindView without a mobile browser results in a cache hit
+ engine.MockPathProvider.Verify(vpp => vpp.FileExists(VIEW_VIRTUAL), Times.AtMostOnce());
+ engine.MockCache.Verify(c => c.InsertViewLocation(It.IsAny<HttpContextBase>(), It.IsAny<string>(), VIEW_VIRTUAL), Times.Exactly(1));
+ engine.MockCache.Verify(c => c.GetViewLocation(It.IsAny<HttpContextBase>(), cacheKey), Times.Exactly(1));
+ }
+
+ [Fact]
+ public void NoValueInCacheButFileExistsReturnsNullIfUsingCache()
+ {
+ // Arrange
+ ControllerContext mobileContext = CreateContext(isMobileDevice: true);
+ TestableVirtualPathProviderViewEngine engine = new TestableVirtualPathProviderViewEngine();
+
+ engine.MockPathProvider
+ .Setup(vpp => vpp.FileExists(MOBILE_VIEW_VIRTUAL))
+ .Returns(true);
+ engine.MockPathProvider
+ .Setup(vpp => vpp.FileExists(VIEW_VIRTUAL))
+ .Returns(true);
+
+ engine.MockCache
+ .Setup(c => c.GetViewLocation(It.IsAny<HttpContextBase>(), It.IsAny<string>()))
+ .Returns((string)null)
+ .Verifiable();
+ engine.MockCache
+ .Setup(c => c.InsertViewLocation(It.IsAny<HttpContextBase>(), It.IsAny<string>(), It.IsAny<string>()));
+
+ // Act
+ IView viewNotInCacheResult = engine.FindView(mobileContext, "name", masterName: null, useCache: true).View;
+
+ // Assert
+ Assert.Null(viewNotInCacheResult);
+
+ // On a cache miss we should never check the file system. FindView will be called on a second pass
+ // without using the cache.
+ engine.MockPathProvider.Verify(vpp => vpp.FileExists(MOBILE_VIEW_VIRTUAL), Times.Never());
+ engine.MockPathProvider.Verify(vpp => vpp.FileExists(VIEW_VIRTUAL), Times.Never());
+
+ // Act & Assert
+
+ //At this point the view on disk should be found and cached.
+ Assert.NotNull(engine.FindView(mobileContext, "name", masterName: null, useCache: false).View);
+ }
+
+ [Fact]
+ public void ReleaseViewCallsDispose()
+ {
+ // Arrange
+ TestableVirtualPathProviderViewEngine engine = new TestableVirtualPathProviderViewEngine();
+ ControllerContext context = CreateContext();
+ IView view = engine.CreateViewResult;
+
+ // Act
+ engine.ReleaseView(context, view);
+
+ // Assert
+ Assert.True(((TestView)view).Disposed);
+ }
+
+ private static ControllerContext CreateContext(bool isMobileDevice = false)
+ {
+ RouteData routeData = new RouteData();
+ routeData.Values["controller"] = "controllerName";
+ routeData.Values["action"] = "actionName";
+
+ Mock<ControllerContext> mockControllerContext = new Mock<ControllerContext>();
+ mockControllerContext.Setup(c => c.HttpContext.Request.Browser.IsMobileDevice).Returns(isMobileDevice);
+ mockControllerContext.Setup(c => c.HttpContext.Items).Returns(new Hashtable());
+ mockControllerContext.Setup(c => c.RouteData).Returns(routeData);
+
+ mockControllerContext.Setup(c => c.HttpContext.Response.Cookies).Returns(new HttpCookieCollection());
+ mockControllerContext.Setup(c => c.HttpContext.Request.Cookies).Returns(new HttpCookieCollection());
+
+ return mockControllerContext.Object;
+ }
+
+ private class TestView : IView, IDisposable
+ {
+ public bool Disposed { get; set; }
+
+ void IDisposable.Dispose()
+ {
+ Disposed = true;
+ }
+
+ void IView.Render(ViewContext viewContext, TextWriter writer)
+ {
+ }
+ }
+
+ private class TestableVirtualPathProviderViewEngine : VirtualPathProviderViewEngine
+ {
+ public IView CreatePartialViewResult = new Mock<IView>().Object;
+ public string CreatePartialViewPartialPath;
+ public ControllerContext CreatePartialViewControllerContext;
+
+ //public IView CreateViewResult = new Mock<IView>().Object;
+ public IView CreateViewResult = new TestView();
+ public string CreateViewMasterPath;
+ public ControllerContext CreateViewControllerContext;
+ public string CreateViewViewPath;
+
+ public Mock<IViewLocationCache> MockCache = new Mock<IViewLocationCache>(MockBehavior.Strict);
+ public Mock<VirtualPathProvider> MockPathProvider = new Mock<VirtualPathProvider>(MockBehavior.Strict);
+
+ public TestableVirtualPathProviderViewEngine()
+ {
+ MasterLocationFormats = new[] { "~/vpath/{1}/{0}.master" };
+ ViewLocationFormats = new[] { "~/vpath/{1}/{0}.view" };
+ PartialViewLocationFormats = new[] { "~/vpath/{1}/{0}.partial" };
+ AreaMasterLocationFormats = new[] { "~/vpath/{2}/{1}/{0}.master" };
+ AreaViewLocationFormats = new[] { "~/vpath/{2}/{1}/{0}.view" };
+ AreaPartialViewLocationFormats = new[] { "~/vpath/{2}/{1}/{0}.partial" };
+ FileExtensions = new[] { "view", "partial", "master" };
+
+ ViewLocationCache = MockCache.Object;
+ VirtualPathProvider = MockPathProvider.Object;
+
+ MockCache
+ .Setup(c => c.GetViewLocation(It.IsAny<HttpContextBase>(), It.IsAny<string>()))
+ .Returns((string)null);
+
+ GetExtensionThunk = GetExtension;
+ }
+
+ public void ClearViewLocations()
+ {
+ ViewLocationFormats = new string[0];
+ }
+
+ public void ClearMasterLocations()
+ {
+ MasterLocationFormats = new string[0];
+ }
+
+ public void ClearPartialViewLocations()
+ {
+ PartialViewLocationFormats = new string[0];
+ }
+
+ protected override IView CreatePartialView(ControllerContext controllerContext, string partialPath)
+ {
+ CreatePartialViewControllerContext = controllerContext;
+ CreatePartialViewPartialPath = partialPath;
+
+ return CreatePartialViewResult;
+ }
+
+ protected override IView CreateView(ControllerContext controllerContext, string viewPath, string masterPath)
+ {
+ CreateViewControllerContext = controllerContext;
+ CreateViewViewPath = viewPath;
+ CreateViewMasterPath = masterPath;
+
+ return CreateViewResult;
+ }
+
+ private static string GetExtension(string virtualPath)
+ {
+ var extension = virtualPath.Substring(virtualPath.LastIndexOf('.'));
+ return extension;
+ }
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Test/WebFormViewEngineTest.cs b/test/System.Web.Mvc.Test/Test/WebFormViewEngineTest.cs
new file mode 100644
index 00000000..8b14bdbd
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Test/WebFormViewEngineTest.cs
@@ -0,0 +1,244 @@
+using System.Web.Hosting;
+using Moq;
+using Xunit;
+
+namespace System.Web.Mvc.Test
+{
+ public class WebFormViewEngineTest
+ {
+ [Fact]
+ public void CreatePartialViewCreatesWebFormView()
+ {
+ // Arrange
+ TestableWebFormViewEngine engine = new TestableWebFormViewEngine();
+
+ // Act
+ WebFormView result = (WebFormView)engine.CreatePartialView("partial path");
+
+ // Assert
+ Assert.Equal("partial path", result.ViewPath);
+ Assert.Equal(String.Empty, result.MasterPath);
+ }
+
+ [Fact]
+ public void CreateViewCreatesWebFormView()
+ {
+ // Arrange
+ TestableWebFormViewEngine engine = new TestableWebFormViewEngine();
+
+ // Act
+ WebFormView result = (WebFormView)engine.CreateView("view path", "master path");
+
+ // Assert
+ Assert.Equal("view path", result.ViewPath);
+ Assert.Equal("master path", result.MasterPath);
+ }
+
+ [Fact]
+ public void WebFormViewEngineSetsViewPageActivator()
+ {
+ // Arrange
+ Mock<IViewPageActivator> viewPageActivator = new Mock<IViewPageActivator>();
+ TestableWebFormViewEngine viewEngine = new TestableWebFormViewEngine(viewPageActivator.Object);
+
+ //Act & Assert
+ Assert.Equal(viewPageActivator.Object, viewEngine.ViewPageActivator);
+ }
+
+ [Fact]
+ public void CreatePartialView_PassesViewPageActivator()
+ {
+ // Arrange
+ Mock<IViewPageActivator> viewPageActivator = new Mock<IViewPageActivator>();
+ TestableWebFormViewEngine viewEngine = new TestableWebFormViewEngine(viewPageActivator.Object);
+
+ // Act
+ WebFormView result = (WebFormView)viewEngine.CreatePartialView("partial path");
+
+ // Assert
+ Assert.Equal(viewEngine.ViewPageActivator, result.ViewPageActivator);
+ }
+
+ [Fact]
+ public void CreateView_PassesViewPageActivator()
+ {
+ // Arrange
+ Mock<IViewPageActivator> viewPageActivator = new Mock<IViewPageActivator>();
+ TestableWebFormViewEngine viewEngine = new TestableWebFormViewEngine(viewPageActivator.Object);
+
+ // Act
+ WebFormView result = (WebFormView)viewEngine.CreateView("partial path", "master path");
+
+ // Assert
+ Assert.Equal(viewEngine.ViewPageActivator, result.ViewPageActivator);
+ }
+
+ [Fact]
+ public void MasterLocationFormatsProperty()
+ {
+ // Arrange
+ string[] expected = new string[]
+ {
+ "~/Views/{1}/{0}.master",
+ "~/Views/Shared/{0}.master"
+ };
+
+ // Act
+ TestableWebFormViewEngine engine = new TestableWebFormViewEngine();
+
+ // Assert
+ Assert.Equal(expected, engine.MasterLocationFormats);
+ }
+
+ [Fact]
+ public void AreaMasterLocationFormatsProperty()
+ {
+ // Arrange
+ string[] expected = new string[]
+ {
+ "~/Areas/{2}/Views/{1}/{0}.master",
+ "~/Areas/{2}/Views/Shared/{0}.master",
+ };
+
+ // Act
+ TestableWebFormViewEngine engine = new TestableWebFormViewEngine();
+
+ // Assert
+ Assert.Equal(expected, engine.AreaMasterLocationFormats);
+ }
+
+ [Fact]
+ public void PartialViewLocationFormatsProperty()
+ {
+ // Arrange
+ string[] expected = new string[]
+ {
+ "~/Views/{1}/{0}.aspx",
+ "~/Views/{1}/{0}.ascx",
+ "~/Views/Shared/{0}.aspx",
+ "~/Views/Shared/{0}.ascx"
+ };
+
+ // Act
+ TestableWebFormViewEngine engine = new TestableWebFormViewEngine();
+
+ // Assert
+ Assert.Equal(expected, engine.PartialViewLocationFormats);
+ }
+
+ [Fact]
+ public void AreaPartialViewLocationFormatsProperty()
+ {
+ // Arrange
+ string[] expected = new string[]
+ {
+ "~/Areas/{2}/Views/{1}/{0}.aspx",
+ "~/Areas/{2}/Views/{1}/{0}.ascx",
+ "~/Areas/{2}/Views/Shared/{0}.aspx",
+ "~/Areas/{2}/Views/Shared/{0}.ascx",
+ };
+
+ // Act
+ TestableWebFormViewEngine engine = new TestableWebFormViewEngine();
+
+ // Assert
+ Assert.Equal(expected, engine.AreaPartialViewLocationFormats);
+ }
+
+ [Fact]
+ public void ViewLocationFormatsProperty()
+ {
+ // Arrange
+ string[] expected = new string[]
+ {
+ "~/Views/{1}/{0}.aspx",
+ "~/Views/{1}/{0}.ascx",
+ "~/Views/Shared/{0}.aspx",
+ "~/Views/Shared/{0}.ascx"
+ };
+
+ // Act
+ TestableWebFormViewEngine engine = new TestableWebFormViewEngine();
+
+ // Assert
+ Assert.Equal(expected, engine.ViewLocationFormats);
+ }
+
+ [Fact]
+ public void AreaViewLocationFormatsProperty()
+ {
+ // Arrange
+ string[] expected = new string[]
+ {
+ "~/Areas/{2}/Views/{1}/{0}.aspx",
+ "~/Areas/{2}/Views/{1}/{0}.ascx",
+ "~/Areas/{2}/Views/Shared/{0}.aspx",
+ "~/Areas/{2}/Views/Shared/{0}.ascx",
+ };
+
+ // Act
+ TestableWebFormViewEngine engine = new TestableWebFormViewEngine();
+
+ // Assert
+ Assert.Equal(expected, engine.AreaViewLocationFormats);
+ }
+
+ [Fact]
+ public void FileExtensionsProperty()
+ {
+ // Arrange
+ string[] expected = new string[]
+ {
+ "aspx",
+ "ascx",
+ "master",
+ };
+
+ // Act
+ TestableWebFormViewEngine engine = new TestableWebFormViewEngine();
+
+ // Assert
+ Assert.Equal(expected, engine.FileExtensions);
+ }
+
+ private sealed class TestableWebFormViewEngine : WebFormViewEngine
+ {
+ public TestableWebFormViewEngine()
+ : base()
+ {
+ }
+
+ public TestableWebFormViewEngine(IViewPageActivator viewPageActivator)
+ : base(viewPageActivator)
+ {
+ }
+
+ public new IViewPageActivator ViewPageActivator
+ {
+ get { return base.ViewPageActivator; }
+ }
+
+ public new VirtualPathProvider VirtualPathProvider
+ {
+ get { return base.VirtualPathProvider; }
+ set { base.VirtualPathProvider = value; }
+ }
+
+ public IView CreatePartialView(string partialPath)
+ {
+ return base.CreatePartialView(new ControllerContext(), partialPath);
+ }
+
+ public IView CreateView(string viewPath, string masterPath)
+ {
+ return base.CreateView(new ControllerContext(), viewPath, masterPath);
+ }
+
+ // This method should remain overridable in derived view engines
+ protected override bool FileExists(ControllerContext controllerContext, string virtualPath)
+ {
+ return base.FileExists(controllerContext, virtualPath);
+ }
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Test/WebFormViewTest.cs b/test/System.Web.Mvc.Test/Test/WebFormViewTest.cs
new file mode 100644
index 00000000..83e9cb96
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Test/WebFormViewTest.cs
@@ -0,0 +1,164 @@
+using Moq;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Mvc.Test
+{
+ public class WebFormViewTest
+ {
+ [Fact]
+ public void GuardClauses()
+ {
+ // Act & Assert
+ Assert.ThrowsArgumentNullOrEmpty(
+ () => new WebFormView(new ControllerContext(), String.Empty, "~/master"),
+ "viewPath"
+ );
+
+ // Act & Assert
+ Assert.ThrowsArgumentNullOrEmpty(
+ () => new WebFormView(new ControllerContext(), null, "~/master"),
+ "viewPath"
+ );
+
+ // Act & Assert
+ Assert.ThrowsArgumentNull(
+ () => new WebFormView(null, "view path", "~/master"),
+ "controllerContext"
+ );
+ }
+
+ [Fact]
+ public void MasterPathProperty()
+ {
+ // Act
+ WebFormView view = new WebFormView(new ControllerContext(), "view path", "master path");
+
+ // Assert
+ Assert.Equal("master path", view.MasterPath);
+ }
+
+ [Fact]
+ public void MasterPathPropertyReturnsEmptyStringIfMasterNotSpecified()
+ {
+ // Act
+ WebFormView view = new WebFormView(new ControllerContext(), "view path", null);
+
+ // Assert
+ Assert.Equal(String.Empty, view.MasterPath);
+ }
+
+ [Fact]
+ public void RenderWithUnsupportedTypeThrows()
+ {
+ // Arrange
+ ViewContext context = new Mock<ViewContext>().Object;
+ MockBuildManager buildManagerMock = new MockBuildManager("view path", typeof(int));
+ WebFormView view = new WebFormView(new ControllerContext(), "view path", null);
+ view.BuildManager = buildManagerMock;
+
+ // Act & Assert
+ Assert.Throws<InvalidOperationException>(
+ () => view.Render(context, null),
+ "The view at 'view path' must derive from ViewPage, ViewPage<TModel>, ViewUserControl, or ViewUserControl<TModel>."
+ );
+ }
+
+ [Fact]
+ public void RenderWithViewPageAndMasterRendersView()
+ {
+ // Arrange
+ ViewContext context = new Mock<ViewContext>().Object;
+ MockBuildManager buildManager = new MockBuildManager("view path", typeof(object));
+ Mock<IViewPageActivator> activator = new Mock<IViewPageActivator>(MockBehavior.Strict);
+ ControllerContext controllerContext = new ControllerContext();
+ StubViewPage viewPage = new StubViewPage();
+ activator.Setup(l => l.Create(controllerContext, typeof(object))).Returns(viewPage);
+ WebFormView view = new WebFormView(controllerContext, "view path", "master path", activator.Object);
+ view.BuildManager = buildManager;
+
+ // Act
+ view.Render(context, null);
+
+ // Assert
+ Assert.Equal(context, viewPage.ResultViewContext);
+ Assert.Equal("master path", viewPage.MasterLocation);
+ }
+
+ [Fact]
+ public void RenderWithViewPageRendersView()
+ {
+ // Arrange
+ ViewContext context = new Mock<ViewContext>().Object;
+ MockBuildManager buildManager = new MockBuildManager("view path", typeof(object));
+ Mock<IViewPageActivator> activator = new Mock<IViewPageActivator>(MockBehavior.Strict);
+ ControllerContext controllerContext = new ControllerContext();
+ StubViewPage viewPage = new StubViewPage();
+ activator.Setup(l => l.Create(controllerContext, typeof(object))).Returns(viewPage);
+ WebFormView view = new WebFormView(controllerContext, "view path", null, activator.Object);
+ view.BuildManager = buildManager;
+
+ // Act
+ view.Render(context, null);
+
+ // Assert
+ Assert.Equal(context, viewPage.ResultViewContext);
+ Assert.Equal(String.Empty, viewPage.MasterLocation);
+ }
+
+ [Fact]
+ public void RenderWithViewUserControlAndMasterThrows()
+ {
+ // Arrange
+ ViewContext context = new Mock<ViewContext>().Object;
+ MockBuildManager buildManagerMock = new MockBuildManager("view path", typeof(StubViewUserControl));
+ WebFormView view = new WebFormView(new ControllerContext(), "view path", "master path");
+ view.BuildManager = buildManagerMock;
+
+ // Act & Assert
+ Assert.Throws<InvalidOperationException>(
+ () => view.Render(context, null),
+ "A master name cannot be specified when the view is a ViewUserControl."
+ );
+ }
+
+ [Fact]
+ public void RenderWithViewUserControlRendersView()
+ {
+ // Arrange
+ ViewContext context = new Mock<ViewContext>().Object;
+ MockBuildManager buildManager = new MockBuildManager("view path", typeof(object));
+ Mock<IViewPageActivator> activator = new Mock<IViewPageActivator>(MockBehavior.Strict);
+ ControllerContext controllerContext = new ControllerContext();
+ StubViewUserControl viewUserControl = new StubViewUserControl();
+ activator.Setup(l => l.Create(controllerContext, typeof(object))).Returns(viewUserControl);
+ WebFormView view = new WebFormView(controllerContext, "view path", null, activator.Object) { BuildManager = buildManager };
+
+ // Act
+ view.Render(context, null);
+
+ // Assert
+ Assert.Equal(context, viewUserControl.ResultViewContext);
+ }
+
+ public sealed class StubViewPage : ViewPage
+ {
+ public ViewContext ResultViewContext;
+
+ public override void RenderView(ViewContext viewContext)
+ {
+ ResultViewContext = viewContext;
+ }
+ }
+
+ public sealed class StubViewUserControl : ViewUserControl
+ {
+ public ViewContext ResultViewContext;
+
+ public override void RenderView(ViewContext viewContext)
+ {
+ ResultViewContext = viewContext;
+ }
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Util/AnonymousObject.cs b/test/System.Web.Mvc.Test/Util/AnonymousObject.cs
new file mode 100644
index 00000000..ec99ac82
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Util/AnonymousObject.cs
@@ -0,0 +1,29 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace Microsoft.Web.UnitTestUtil
+{
+ public static class AnonymousObject
+ {
+ public static string Inspect(object obj)
+ {
+ if (obj == null)
+ {
+ return "(null)";
+ }
+
+ object[] args = Enumerable.Empty<Object>().ToArray();
+ IEnumerable<string> values = obj.GetType()
+ .GetProperties()
+ .Select(prop => String.Format("{0}: {1}", prop.Name, prop.GetValue(obj, args)));
+
+ if (!values.Any())
+ {
+ return "(no properties)";
+ }
+
+ return "{ " + values.Aggregate((left, right) => left + ", " + right) + " }";
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Util/DictionaryHelper.cs b/test/System.Web.Mvc.Test/Util/DictionaryHelper.cs
new file mode 100644
index 00000000..253f2292
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Util/DictionaryHelper.cs
@@ -0,0 +1,517 @@
+using System.Collections;
+using System.Collections.Generic;
+using System.Linq;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.TestUtil
+{
+ public class DictionaryHelper<TKey, TValue>
+ {
+ public IEqualityComparer<TKey> Comparer { get; set; }
+
+ public Func<IDictionary<TKey, TValue>> Creator { get; set; }
+
+ public IList<TKey> SampleKeys { get; set; }
+
+ public IList<TValue> SampleValues { get; set; }
+
+ public bool SkipItemPropertyTest { get; set; }
+
+ public bool ThrowOnKeyNotFound { get; set; }
+
+ public void Execute()
+ {
+ ValidateProperties();
+
+ Executor executor = new Executor()
+ {
+ Comparer = Comparer,
+ Creator = Creator,
+ ThrowOnKeyNotFound = ThrowOnKeyNotFound,
+ Values = SampleValues.ToArray()
+ };
+ SeparateKeys(out executor.ExcludedKey, out executor.ConflictingKeys, out executor.NonConflictingKeys);
+
+ executor.TestAdd1();
+ executor.TestAdd1ThrowsArgumentExceptionIfKeyAlreadyInDictionary();
+ executor.TestAdd2();
+ executor.TestClear();
+ executor.TestContains();
+ executor.TestContainsKey();
+ executor.TestCopyTo();
+ executor.TestCountProperty();
+ executor.TestGetEnumerator();
+ executor.TestGetEnumeratorGeneric();
+ executor.TestIsReadOnlyProperty();
+
+ if (!SkipItemPropertyTest)
+ {
+ executor.TestItemProperty();
+ }
+
+ executor.TestKeysProperty();
+ executor.TestRemove1();
+ executor.TestRemove2();
+ executor.TestTryGetValue();
+ executor.TestValuesProperty();
+ }
+
+ private void SeparateKeys(out TKey excludedKey, out TKey[] conflictingKeys, out TKey[] nonConflictingKeys)
+ {
+ List<TKey> nonConflictingKeyList = new List<TKey>();
+ TKey[] newConflictingKeys = null;
+
+ var keyLookup = SampleKeys.ToLookup(key => key, Comparer);
+ foreach (var entry in keyLookup)
+ {
+ if (entry.Count() == 1)
+ {
+ // not a conflict
+ nonConflictingKeyList.AddRange(entry);
+ }
+ else
+ {
+ // conflict
+ newConflictingKeys = entry.ToArray();
+ }
+ }
+
+ excludedKey = nonConflictingKeyList[nonConflictingKeyList.Count - 1];
+ nonConflictingKeyList.RemoveAt(nonConflictingKeyList.Count - 1);
+ conflictingKeys = newConflictingKeys;
+ nonConflictingKeys = nonConflictingKeyList.ToArray();
+ }
+
+ private void ValidateProperties()
+ {
+ if (Creator == null)
+ {
+ throw new InvalidOperationException("The Creator property must not be null.");
+ }
+ if (SampleKeys == null || SampleKeys.Count < 4)
+ {
+ throw new InvalidOperationException("The SampleKeys property must contain at least 4 elements.");
+ }
+ if (SampleValues == null || SampleValues.Count != SampleKeys.Count)
+ {
+ throw new InvalidOperationException("The SampleValues property must contain as many elements as the SampleKeys property.");
+ }
+
+ HashSet<TKey> keys = new HashSet<TKey>(SampleKeys, Comparer);
+ if (keys.Count != SampleKeys.Count - 1)
+ {
+ throw new InvalidOperationException("The SampleKeys property must contain exactly one colliding keypair using the given comparer.");
+ }
+ }
+
+ private class Executor
+ {
+ public IEqualityComparer<TKey> Comparer;
+ public Func<IDictionary<TKey, TValue>> Creator;
+ public TKey ExcludedKey;
+ public TKey[] ConflictingKeys;
+ public TKey[] NonConflictingKeys;
+ public bool ThrowOnKeyNotFound;
+ public TValue[] Values;
+
+ private IEnumerable<KeyValuePair<TKey, TValue>> MakeKeyValuePairs()
+ {
+ return MakeKeyValuePairs(false /* includeConflictingKeys */);
+ }
+
+ private IEnumerable<KeyValuePair<TKey, TValue>> MakeKeyValuePairs(bool includeConflictingKeys)
+ {
+ for (int i = 0; i < NonConflictingKeys.Length; i++)
+ {
+ TKey key = NonConflictingKeys[i];
+ TValue value = Values[i];
+ yield return new KeyValuePair<TKey, TValue>(key, value);
+ }
+ if (includeConflictingKeys)
+ {
+ for (int i = 0; i < 2; i++)
+ {
+ TKey key = ConflictingKeys[i];
+ TValue value = Values[NonConflictingKeys.Length + i];
+ yield return new KeyValuePair<TKey, TValue>(key, value);
+ }
+ }
+ }
+
+ public void TestAdd1()
+ {
+ // Arrange
+ Dictionary<TKey, TValue> controlDictionary = new Dictionary<TKey, TValue>(Comparer);
+ IDictionary<TKey, TValue> testDictionary = Creator();
+
+ // Act
+ foreach (var entry in MakeKeyValuePairs())
+ {
+ controlDictionary.Add(entry.Key, entry.Value);
+ testDictionary.Add(entry.Key, entry.Value);
+ }
+
+ // Assert
+ VerifyDictionaryEntriesEqual(controlDictionary, testDictionary);
+ }
+
+ public void TestAdd1ThrowsArgumentExceptionIfKeyAlreadyInDictionary()
+ {
+ // Arrange
+ IDictionary<TKey, TValue> testDictionary = Creator();
+
+ // Act & assert
+ var pairs = MakeKeyValuePairs(true /* includeConflictingKeys */).Skip(NonConflictingKeys.Length).ToArray();
+ testDictionary.Add(pairs[0].Key, pairs[1].Value);
+
+ Assert.Throws<ArgumentException>(
+ delegate { testDictionary.Add(pairs[1].Key, pairs[1].Value); },
+ "An item with the same key has already been added."
+ );
+ }
+
+ public void TestAdd2()
+ {
+ // Arrange
+ Dictionary<TKey, TValue> controlDictionary = new Dictionary<TKey, TValue>(Comparer);
+ IDictionary<TKey, TValue> testDictionary = Creator();
+
+ // Act
+ foreach (var entry in MakeKeyValuePairs())
+ {
+ ((IDictionary<TKey, TValue>)controlDictionary).Add(entry);
+ testDictionary.Add(entry);
+ }
+
+ // Assert
+ VerifyDictionaryEntriesEqual(controlDictionary, testDictionary);
+ }
+
+ public void TestClear()
+ {
+ // Arrange
+ IDictionary<TKey, TValue> testDictionary = Creator();
+
+ // Act
+ foreach (var entry in MakeKeyValuePairs())
+ {
+ testDictionary.Add(entry);
+ }
+ testDictionary.Clear();
+
+ // Assert
+ Assert.Empty(testDictionary);
+ }
+
+ public void TestCountProperty()
+ {
+ // Arrange
+ Dictionary<TKey, TValue> controlDictionary = new Dictionary<TKey, TValue>(Comparer);
+ IDictionary<TKey, TValue> testDictionary = Creator();
+
+ // Act & assert
+ foreach (var entry in MakeKeyValuePairs())
+ {
+ controlDictionary.Add(entry.Key, entry.Value);
+ testDictionary.Add(entry.Key, entry.Value);
+ Assert.Equal(controlDictionary.Count, testDictionary.Count);
+ }
+ }
+
+ public void TestContains()
+ {
+ // Arrange
+ IDictionary<TKey, TValue> testDictionary = Creator();
+
+ // Act
+ foreach (var entry in MakeKeyValuePairs())
+ {
+ testDictionary.Add(entry);
+ }
+
+ // Assert
+ var shouldBeFound = MakeKeyValuePairs().First();
+ var shouldNotBeFound = new KeyValuePair<TKey, TValue>(ExcludedKey, Values[Values.Length - 1]);
+ Assert.True(testDictionary.Contains(shouldBeFound), String.Format("Test dictionary should have contained entry for KVP '{0}'.", shouldBeFound));
+ Assert.False(testDictionary.Contains(shouldNotBeFound), String.Format("Test dictionary should not have contained entry for KVP '{0}'.", shouldNotBeFound));
+ }
+
+ public void TestContainsKey()
+ {
+ // Arrange
+ IDictionary<TKey, TValue> testDictionary = Creator();
+
+ // Act
+ foreach (var entry in MakeKeyValuePairs())
+ {
+ testDictionary.Add(entry);
+ }
+
+ // Assert
+ Assert.True(testDictionary.ContainsKey(NonConflictingKeys[0]), String.Format("Test dictionary should have contained entry for key '{0}'.", NonConflictingKeys[0]));
+ Assert.False(testDictionary.ContainsKey(ExcludedKey), String.Format("Test dictionary should not have contained entry for key '{0}'.", ExcludedKey));
+ }
+
+ public void TestCopyTo()
+ {
+ // Arrange
+ IDictionary<TKey, TValue> controlDictionary = new Dictionary<TKey, TValue>(Comparer);
+ IDictionary<TKey, TValue> testDictionary = Creator();
+
+ foreach (var entry in MakeKeyValuePairs())
+ {
+ controlDictionary.Add(entry.Key, entry.Value);
+ testDictionary.Add(entry.Key, entry.Value);
+ }
+ KeyValuePair<TKey, TValue>[] testKvps = new KeyValuePair<TKey, TValue>[testDictionary.Count + 2];
+
+ // Act
+ testDictionary.CopyTo(testKvps, 2);
+
+ // Assert
+ for (int i = 0; i < 2; i++)
+ {
+ var defaultValue = default(KeyValuePair<TKey, TValue>);
+ var entry = testKvps[i];
+ Assert.Equal(defaultValue, entry);
+ }
+ for (int i = 2; i < testKvps.Length; i++)
+ {
+ var entry = testKvps[i];
+ Assert.True(controlDictionary.Contains(entry), String.Format("The value '{0}' wasn't present in the control dictionary.", entry));
+ controlDictionary.Remove(entry);
+ }
+
+ Assert.Empty(controlDictionary);
+ }
+
+ public void TestGetEnumerator()
+ {
+ // Arrange
+ Dictionary<TKey, TValue> controlDictionary = new Dictionary<TKey, TValue>(Comparer);
+ IDictionary<TKey, TValue> testDictionary = Creator();
+
+ foreach (var entry in MakeKeyValuePairs())
+ {
+ controlDictionary.Add(entry.Key, entry.Value);
+ testDictionary.Add(entry.Key, entry.Value);
+ }
+
+ IEnumerable testDictionaryAsEnumerable = (IEnumerable)testDictionary;
+
+ // Act
+ Dictionary<TKey, TValue> newTestDictionary = new Dictionary<TKey, TValue>(Comparer);
+ foreach (object entry in testDictionaryAsEnumerable)
+ {
+ var kvp = Assert.IsType<KeyValuePair<TKey, TValue>>(entry);
+ newTestDictionary.Add(kvp.Key, kvp.Value);
+ }
+
+ // Assert
+ VerifyDictionaryEntriesEqual(controlDictionary, newTestDictionary);
+ }
+
+ public void TestGetEnumeratorGeneric()
+ {
+ // Arrange
+ Dictionary<TKey, TValue> controlDictionary = new Dictionary<TKey, TValue>(Comparer);
+ IDictionary<TKey, TValue> testDictionary = Creator();
+
+ foreach (var entry in MakeKeyValuePairs())
+ {
+ controlDictionary.Add(entry.Key, entry.Value);
+ testDictionary.Add(entry.Key, entry.Value);
+ }
+
+ // Act & assert
+ VerifyDictionaryEntriesEqual(controlDictionary, testDictionary);
+ }
+
+ public void TestIsReadOnlyProperty()
+ {
+ // Arrange
+ IDictionary<TKey, TValue> testDictionary = Creator();
+
+ // Act & assert
+ Assert.False(testDictionary.IsReadOnly, "Dictionary should not be read only.");
+ }
+
+ public void TestItemProperty()
+ {
+ // Arrange
+ Dictionary<TKey, TValue> controlDictionary = new Dictionary<TKey, TValue>(Comparer);
+ IDictionary<TKey, TValue> testDictionary = Creator();
+
+ var shouldBeFound = MakeKeyValuePairs().First();
+ var shouldNotBeFound = new KeyValuePair<TKey, TValue>(ExcludedKey, Values[Values.Length - 1]);
+
+ // Act & assert
+ foreach (var entry in MakeKeyValuePairs())
+ {
+ controlDictionary.Add(entry.Key, entry.Value);
+ testDictionary[entry.Key] = entry.Value;
+ }
+ VerifyDictionaryEntriesEqual(controlDictionary, testDictionary);
+
+ TValue value = testDictionary[shouldBeFound.Key];
+ Assert.Equal(shouldBeFound.Value, value);
+
+ if (ThrowOnKeyNotFound)
+ {
+ Assert.Throws<KeyNotFoundException>(
+ delegate { TValue valueNotFound = testDictionary[shouldNotBeFound.Key]; }, allowDerivedExceptions: true);
+ }
+ else
+ {
+ TValue valueNotFound = testDictionary[shouldNotBeFound.Key];
+ Assert.Equal(default(TValue), valueNotFound); // Should not throw
+ }
+ }
+
+ public void TestKeysProperty()
+ {
+ // Arrange
+ Dictionary<TKey, TValue> controlDictionary = new Dictionary<TKey, TValue>(Comparer);
+ IDictionary<TKey, TValue> testDictionary = Creator();
+
+ foreach (var entry in MakeKeyValuePairs())
+ {
+ controlDictionary.Add(entry.Key, entry.Value);
+ testDictionary.Add(entry.Key, entry.Value);
+ }
+
+ // Act
+ HashSet<TKey> controlKeys = new HashSet<TKey>(controlDictionary.Keys, Comparer);
+ HashSet<TKey> testKeys = new HashSet<TKey>(testDictionary.Keys, Comparer);
+
+ // Assert
+ Assert.True(controlKeys.SetEquals(testKeys), "Control dictionary and test dictionary key sets were not equal.");
+ }
+
+ public void TestRemove1()
+ {
+ // Arrange
+ Dictionary<TKey, TValue> controlDictionary = new Dictionary<TKey, TValue>(Comparer);
+ IDictionary<TKey, TValue> testDictionary = Creator();
+
+ foreach (var entry in MakeKeyValuePairs())
+ {
+ controlDictionary.Add(entry.Key, entry.Value);
+ testDictionary.Add(entry.Key, entry.Value);
+ }
+
+ // Act
+ bool removalSuccess = testDictionary.Remove(NonConflictingKeys[0]);
+ bool removalFailure = testDictionary.Remove(ExcludedKey);
+
+ // Assert
+ Assert.True(removalSuccess, "Remove() should return true if the key was removed.");
+ Assert.False(removalFailure, "Remove() should return false if the key was not removed.");
+
+ controlDictionary.Remove(NonConflictingKeys[0]);
+ VerifyDictionaryEntriesEqual(controlDictionary, testDictionary);
+ }
+
+ public void TestRemove2()
+ {
+ // Arrange
+ Dictionary<TKey, TValue> controlDictionary = new Dictionary<TKey, TValue>(Comparer);
+ IDictionary<TKey, TValue> testDictionary = Creator();
+
+ foreach (var entry in MakeKeyValuePairs())
+ {
+ ((IDictionary<TKey, TValue>)controlDictionary).Add(entry);
+ testDictionary.Add(entry);
+ }
+
+ // Act
+ var shouldBeFound = MakeKeyValuePairs().First();
+ var shouldNotBeFound = new KeyValuePair<TKey, TValue>(ExcludedKey, Values[Values.Length - 1]);
+ bool removalSuccess = testDictionary.Remove(shouldBeFound);
+ bool removalFailure = testDictionary.Remove(shouldNotBeFound);
+
+ // Assert
+ Assert.True(removalSuccess, "Remove() should return true if the key was removed.");
+ Assert.False(removalFailure, "Remove() should return false if the key was not removed.");
+
+ ((IDictionary<TKey, TValue>)controlDictionary).Remove(shouldBeFound);
+ VerifyDictionaryEntriesEqual(controlDictionary, testDictionary);
+ }
+
+ public void TestTryGetValue()
+ {
+ // Arrange
+ Dictionary<TKey, TValue> controlDictionary = new Dictionary<TKey, TValue>(Comparer);
+ IDictionary<TKey, TValue> testDictionary = Creator();
+
+ foreach (var entry in MakeKeyValuePairs())
+ {
+ controlDictionary.Add(entry.Key, entry.Value);
+ testDictionary.Add(entry.Key, entry.Value);
+ }
+
+ var shouldBeFound = MakeKeyValuePairs().First();
+ var shouldNotBeFound = new KeyValuePair<TKey, TValue>(ExcludedKey, Values[Values.Length - 1]);
+
+ // Act
+ TValue value1;
+ bool returned1 = testDictionary.TryGetValue(shouldBeFound.Key, out value1);
+ TValue value2;
+ bool returned2 = testDictionary.TryGetValue(shouldNotBeFound.Key, out value2);
+
+ // Assert
+ Assert.True(returned1, String.Format("The entry '{0}' should have been found.", shouldBeFound));
+ Assert.Equal(shouldBeFound.Value, value1);
+ Assert.False(returned2, String.Format("The entry '{0}' should not have been found.", shouldNotBeFound));
+ Assert.Equal(default(TValue), value2);
+ }
+
+ public void TestValuesProperty()
+ {
+ // Arrange
+ Dictionary<TKey, TValue> controlDictionary = new Dictionary<TKey, TValue>(Comparer);
+ IDictionary<TKey, TValue> testDictionary = Creator();
+
+ foreach (var entry in MakeKeyValuePairs())
+ {
+ controlDictionary.Add(entry.Key, entry.Value);
+ testDictionary.Add(entry.Key, entry.Value);
+ }
+
+ // Act
+ List<TValue> controlValues = controlDictionary.Values.ToList();
+ List<TValue> testValues = testDictionary.Values.ToList();
+
+ // Assert
+ foreach (var entry in controlValues)
+ {
+ Assert.True(testValues.Contains(entry), String.Format("Test dictionary did not contain value '{0}'.", entry));
+ }
+ foreach (var entry in testValues)
+ {
+ Assert.True(controlValues.Contains(entry), String.Format("Control dictionary did not contain value '{0}'.", entry));
+ }
+ }
+
+ private void VerifyDictionaryEntriesEqual(Dictionary<TKey, TValue> controlDictionary, IDictionary<TKey, TValue> testDictionary)
+ {
+ Assert.Equal(controlDictionary.Count, testDictionary.Count);
+
+ Dictionary<TKey, TValue> clonedControlDictionary = new Dictionary<TKey, TValue>(controlDictionary, controlDictionary.Comparer);
+
+ foreach (var entry in testDictionary)
+ {
+ var key = entry.Key;
+ Assert.True(clonedControlDictionary.ContainsKey(entry.Key), String.Format("Control dictionary did not contain key '{0}'.", key));
+ clonedControlDictionary.Remove(key);
+ }
+
+ foreach (var entry in clonedControlDictionary)
+ {
+ var key = entry.Key;
+ Assert.True(false, String.Format("Test dictionary did not contain key '{0}'.", key));
+ }
+ }
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Util/HttpContextHelpers.cs b/test/System.Web.Mvc.Test/Util/HttpContextHelpers.cs
new file mode 100644
index 00000000..de2778a2
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Util/HttpContextHelpers.cs
@@ -0,0 +1,15 @@
+using System.Collections.Generic;
+using Moq;
+
+namespace System.Web.Mvc.Test
+{
+ public static class HttpContextHelpers
+ {
+ public static Mock<HttpContextBase> GetMockHttpContext()
+ {
+ Mock<HttpContextBase> mockContext = new Mock<HttpContextBase>();
+ mockContext.Setup(m => m.Items).Returns(new Dictionary<object, object>());
+ return mockContext;
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Util/MvcHelper.cs b/test/System.Web.Mvc.Test/Util/MvcHelper.cs
new file mode 100644
index 00000000..db87dd35
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Util/MvcHelper.cs
@@ -0,0 +1,174 @@
+using System;
+using System.Collections;
+using System.IO;
+using System.Web;
+using System.Web.Mvc;
+using System.Web.Routing;
+using Moq;
+
+namespace Microsoft.Web.UnitTestUtil
+{
+ public static class MvcHelper
+ {
+ public const string AppPathModifier = "/$(SESSION)";
+
+ public static HtmlHelper<object> GetHtmlHelper()
+ {
+ HttpContextBase httpcontext = GetHttpContext("/app/", null, null);
+ RouteCollection rt = new RouteCollection();
+ rt.Add(new Route("{controller}/{action}/{id}", null) { Defaults = new RouteValueDictionary(new { id = "defaultid" }) });
+ rt.Add("namedroute", new Route("named/{controller}/{action}/{id}", null) { Defaults = new RouteValueDictionary(new { id = "defaultid" }) });
+ RouteData rd = new RouteData();
+ rd.Values.Add("controller", "home");
+ rd.Values.Add("action", "oldaction");
+
+ ViewDataDictionary vdd = new ViewDataDictionary();
+
+ ViewContext viewContext = new ViewContext()
+ {
+ HttpContext = httpcontext,
+ RouteData = rd,
+ ViewData = vdd
+ };
+ Mock<IViewDataContainer> mockVdc = new Mock<IViewDataContainer>();
+ mockVdc.Setup(vdc => vdc.ViewData).Returns(vdd);
+
+ HtmlHelper<object> htmlHelper = new HtmlHelper<object>(viewContext, mockVdc.Object, rt);
+ return htmlHelper;
+ }
+
+ public static HtmlHelper GetHtmlHelper(string protocol, int port)
+ {
+ HttpContextBase httpcontext = GetHttpContext("/app/", null, null, protocol, port);
+ RouteCollection rt = new RouteCollection();
+ rt.Add(new Route("{controller}/{action}/{id}", null) { Defaults = new RouteValueDictionary(new { id = "defaultid" }) });
+ rt.Add("namedroute", new Route("named/{controller}/{action}/{id}", null) { Defaults = new RouteValueDictionary(new { id = "defaultid" }) });
+ RouteData rd = new RouteData();
+ rd.Values.Add("controller", "home");
+ rd.Values.Add("action", "oldaction");
+
+ ViewDataDictionary vdd = new ViewDataDictionary();
+
+ Mock<ViewContext> mockViewContext = new Mock<ViewContext>();
+ mockViewContext.Setup(c => c.HttpContext).Returns(httpcontext);
+ mockViewContext.Setup(c => c.RouteData).Returns(rd);
+ mockViewContext.Setup(c => c.ViewData).Returns(vdd);
+ Mock<IViewDataContainer> mockVdc = new Mock<IViewDataContainer>();
+ mockVdc.Setup(vdc => vdc.ViewData).Returns(vdd);
+
+ HtmlHelper htmlHelper = new HtmlHelper(mockViewContext.Object, mockVdc.Object, rt);
+ return htmlHelper;
+ }
+
+ public static HtmlHelper GetHtmlHelper(ViewDataDictionary viewData)
+ {
+ Mock<ViewContext> mockViewContext = new Mock<ViewContext>() { CallBase = true };
+ mockViewContext.Setup(c => c.ViewData).Returns(viewData);
+ mockViewContext.Setup(c => c.HttpContext.Items).Returns(new Hashtable());
+ IViewDataContainer container = GetViewDataContainer(viewData);
+ return new HtmlHelper(mockViewContext.Object, container);
+ }
+
+ public static HtmlHelper<TModel> GetHtmlHelper<TModel>(ViewDataDictionary<TModel> viewData)
+ {
+ Mock<ViewContext> mockViewContext = new Mock<ViewContext>() { CallBase = true };
+ mockViewContext.Setup(c => c.ViewData).Returns(viewData);
+ mockViewContext.Setup(c => c.HttpContext.Items).Returns(new Hashtable());
+ IViewDataContainer container = GetViewDataContainer(viewData);
+ return new HtmlHelper<TModel>(mockViewContext.Object, container);
+ }
+
+ public static HtmlHelper GetHtmlHelperWithPath(ViewDataDictionary viewData)
+ {
+ return GetHtmlHelperWithPath(viewData, "/");
+ }
+
+ public static HtmlHelper GetHtmlHelperWithPath(ViewDataDictionary viewData, string appPath)
+ {
+ ViewContext viewContext = GetViewContextWithPath(appPath, viewData);
+ Mock<IViewDataContainer> mockContainer = new Mock<IViewDataContainer>();
+ mockContainer.Setup(c => c.ViewData).Returns(viewData);
+ IViewDataContainer container = mockContainer.Object;
+ return new HtmlHelper(viewContext, container, new RouteCollection());
+ }
+
+ public static HtmlHelper<TModel> GetHtmlHelperWithPath<TModel>(ViewDataDictionary<TModel> viewData, string appPath)
+ {
+ ViewContext viewContext = GetViewContextWithPath(appPath, viewData);
+ Mock<IViewDataContainer> mockContainer = new Mock<IViewDataContainer>();
+ mockContainer.Setup(c => c.ViewData).Returns(viewData);
+ IViewDataContainer container = mockContainer.Object;
+ return new HtmlHelper<TModel>(viewContext, container, new RouteCollection());
+ }
+
+ public static HtmlHelper<TModel> GetHtmlHelperWithPath<TModel>(ViewDataDictionary<TModel> viewData)
+ {
+ return GetHtmlHelperWithPath(viewData, "/");
+ }
+
+ public static HttpContextBase GetHttpContext(string appPath, string requestPath, string httpMethod, string protocol, int port)
+ {
+ Mock<HttpContextBase> mockHttpContext = new Mock<HttpContextBase>();
+
+ if (!String.IsNullOrEmpty(appPath))
+ {
+ mockHttpContext.Setup(o => o.Request.ApplicationPath).Returns(appPath);
+ }
+ if (!String.IsNullOrEmpty(requestPath))
+ {
+ mockHttpContext.Setup(o => o.Request.AppRelativeCurrentExecutionFilePath).Returns(requestPath);
+ }
+
+ Uri uri;
+
+ if (port >= 0)
+ {
+ uri = new Uri(protocol + "://localhost" + ":" + Convert.ToString(port));
+ }
+ else
+ {
+ uri = new Uri(protocol + "://localhost");
+ }
+ mockHttpContext.Setup(o => o.Request.Url).Returns(uri);
+
+ mockHttpContext.Setup(o => o.Request.PathInfo).Returns(String.Empty);
+ if (!String.IsNullOrEmpty(httpMethod))
+ {
+ mockHttpContext.Setup(o => o.Request.HttpMethod).Returns(httpMethod);
+ }
+
+ mockHttpContext.Setup(o => o.Session).Returns((HttpSessionStateBase)null);
+ mockHttpContext.Setup(o => o.Response.ApplyAppPathModifier(It.IsAny<string>())).Returns<string>(r => AppPathModifier + r);
+ mockHttpContext.Setup(o => o.Items).Returns(new Hashtable());
+ return mockHttpContext.Object;
+ }
+
+ public static HttpContextBase GetHttpContext(string appPath, string requestPath, string httpMethod)
+ {
+ return GetHttpContext(appPath, requestPath, httpMethod, Uri.UriSchemeHttp.ToString(), -1);
+ }
+
+ public static ViewContext GetViewContextWithPath(string appPath, ViewDataDictionary viewData)
+ {
+ HttpContextBase httpContext = GetHttpContext(appPath, "/request", "GET");
+
+ Mock<ViewContext> mockViewContext = new Mock<ViewContext>() { DefaultValue = DefaultValue.Mock };
+ mockViewContext.Setup(c => c.HttpContext).Returns(httpContext);
+ mockViewContext.Setup(c => c.ViewData).Returns(viewData);
+ mockViewContext.Setup(c => c.Writer).Returns(new StringWriter());
+ return mockViewContext.Object;
+ }
+
+ public static ViewContext GetViewContextWithPath(ViewDataDictionary viewData)
+ {
+ return GetViewContextWithPath("/", viewData);
+ }
+
+ public static IViewDataContainer GetViewDataContainer(ViewDataDictionary viewData)
+ {
+ Mock<IViewDataContainer> mockContainer = new Mock<IViewDataContainer>();
+ mockContainer.Setup(c => c.ViewData).Returns(viewData);
+ return mockContainer.Object;
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Util/Resolver.cs b/test/System.Web.Mvc.Test/Util/Resolver.cs
new file mode 100644
index 00000000..f83ec1bc
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Util/Resolver.cs
@@ -0,0 +1,7 @@
+namespace System.Web.Mvc.Test
+{
+ public class Resolver<T> : IResolver<T>
+ {
+ public T Current { get; set; }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Util/SimpleValueProvider.cs b/test/System.Web.Mvc.Test/Util/SimpleValueProvider.cs
new file mode 100644
index 00000000..c25be79c
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Util/SimpleValueProvider.cs
@@ -0,0 +1,73 @@
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Web.Mvc;
+
+namespace Microsoft.Web.UnitTestUtil
+{
+ // just a simple value provider used for unit testing
+
+ public sealed class SimpleValueProvider : Dictionary<string, object>, IValueProvider
+ {
+ private readonly CultureInfo _culture;
+
+ public SimpleValueProvider()
+ : this(null)
+ {
+ }
+
+ public SimpleValueProvider(CultureInfo culture)
+ : base(StringComparer.OrdinalIgnoreCase)
+ {
+ _culture = culture ?? CultureInfo.InvariantCulture;
+ }
+
+ // copied from ValueProviderUtil
+ public bool ContainsPrefix(string prefix)
+ {
+ foreach (string key in Keys)
+ {
+ if (key != null)
+ {
+ if (prefix.Length == 0)
+ {
+ return true; // shortcut - non-null key matches empty prefix
+ }
+
+ if (key.StartsWith(prefix, StringComparison.OrdinalIgnoreCase))
+ {
+ if (key.Length == prefix.Length)
+ {
+ return true; // exact match
+ }
+ else
+ {
+ switch (key[prefix.Length])
+ {
+ case '.': // known separator characters
+ case '[':
+ return true;
+ }
+ }
+ }
+ }
+ }
+
+ return false; // nothing found
+ }
+
+ public ValueProviderResult GetValue(string key)
+ {
+ object rawValue;
+ if (TryGetValue(key, out rawValue))
+ {
+ return new ValueProviderResult(rawValue, Convert.ToString(rawValue, _culture), _culture);
+ }
+ else
+ {
+ // value not found
+ return null;
+ }
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Util/SimpleViewDataContainer.cs b/test/System.Web.Mvc.Test/Util/SimpleViewDataContainer.cs
new file mode 100644
index 00000000..304e6d8f
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Util/SimpleViewDataContainer.cs
@@ -0,0 +1,14 @@
+using System.Web.Mvc;
+
+namespace Microsoft.Web.UnitTestUtil
+{
+ public class SimpleViewDataContainer : IViewDataContainer
+ {
+ public SimpleViewDataContainer(ViewDataDictionary viewData)
+ {
+ ViewData = viewData;
+ }
+
+ public ViewDataDictionary ViewData { get; set; }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/packages.config b/test/System.Web.Mvc.Test/packages.config
new file mode 100644
index 00000000..d5aa6401
--- /dev/null
+++ b/test/System.Web.Mvc.Test/packages.config
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<packages>
+ <package id="Moq" version="4.0.10827" />
+ <package id="xunit" version="1.9.0.1566" />
+ <package id="xunit.extensions" version="1.9.0.1566" />
+</packages> \ No newline at end of file
diff --git a/test/System.Web.Razor.Test/CSharpRazorCodeLanguageTest.cs b/test/System.Web.Razor.Test/CSharpRazorCodeLanguageTest.cs
new file mode 100644
index 00000000..587eb6f7
--- /dev/null
+++ b/test/System.Web.Razor.Test/CSharpRazorCodeLanguageTest.cs
@@ -0,0 +1,53 @@
+using System.Web.Razor.Generator;
+using System.Web.Razor.Parser;
+using Microsoft.CSharp;
+using Xunit;
+
+namespace System.Web.Razor.Test
+{
+ public class CSharpRazorCodeLanguageTest
+ {
+ [Fact]
+ public void CreateCodeParserReturnsNewCSharpCodeParser()
+ {
+ // Arrange
+ RazorCodeLanguage service = new CSharpRazorCodeLanguage();
+
+ // Act
+ ParserBase parser = service.CreateCodeParser();
+
+ // Assert
+ Assert.NotNull(parser);
+ Assert.IsType<CSharpCodeParser>(parser);
+ }
+
+ [Fact]
+ public void CreateCodeGeneratorParserListenerReturnsNewCSharpCodeGeneratorParserListener()
+ {
+ // Arrange
+ RazorCodeLanguage service = new CSharpRazorCodeLanguage();
+
+ // Act
+ RazorEngineHost host = new RazorEngineHost(service);
+ RazorCodeGenerator generator = service.CreateCodeGenerator("Foo", "Bar", "Baz", host);
+
+ // Assert
+ Assert.NotNull(generator);
+ Assert.IsType<CSharpRazorCodeGenerator>(generator);
+ Assert.Equal("Foo", generator.ClassName);
+ Assert.Equal("Bar", generator.RootNamespaceName);
+ Assert.Equal("Baz", generator.SourceFileName);
+ Assert.Same(host, generator.Host);
+ }
+
+ [Fact]
+ public void CodeDomProviderTypeReturnsVBCodeProvider()
+ {
+ // Arrange
+ RazorCodeLanguage service = new CSharpRazorCodeLanguage();
+
+ // Assert
+ Assert.Equal(typeof(CSharpCodeProvider), service.CodeDomProviderType);
+ }
+ }
+}
diff --git a/test/System.Web.Razor.Test/CodeCompileUnitExtensions.cs b/test/System.Web.Razor.Test/CodeCompileUnitExtensions.cs
new file mode 100644
index 00000000..49f4a59f
--- /dev/null
+++ b/test/System.Web.Razor.Test/CodeCompileUnitExtensions.cs
@@ -0,0 +1,22 @@
+using System.CodeDom;
+using System.CodeDom.Compiler;
+using System.IO;
+using System.Text;
+
+namespace System.Web.Razor.Test
+{
+ internal static class CodeCompileUnitExtensions
+ {
+ public static string GenerateCode<T>(this CodeCompileUnit ccu) where T : CodeDomProvider, new()
+ {
+ StringBuilder output = new StringBuilder();
+ using (StringWriter writer = new StringWriter(output))
+ {
+ T provider = new T();
+ provider.GenerateCodeFromCompileUnit(ccu, writer, new CodeGeneratorOptions() { IndentString = " " });
+ }
+
+ return output.ToString();
+ }
+ }
+}
diff --git a/test/System.Web.Razor.Test/Editor/RazorEditorParserTest.cs b/test/System.Web.Razor.Test/Editor/RazorEditorParserTest.cs
new file mode 100644
index 00000000..c7dccca5
--- /dev/null
+++ b/test/System.Web.Razor.Test/Editor/RazorEditorParserTest.cs
@@ -0,0 +1,216 @@
+using System.Threading;
+using System.Web.Razor.Editor;
+using System.Web.Razor.Parser;
+using System.Web.Razor.Parser.SyntaxTree;
+using System.Web.Razor.Resources;
+using System.Web.Razor.Test.Framework;
+using System.Web.Razor.Test.Utils;
+using System.Web.Razor.Text;
+using System.Web.WebPages.TestUtils;
+using Microsoft.CSharp;
+using Moq;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Razor.Test.Editor
+{
+ public class RazorEditorParserTest
+ {
+ private static readonly TestFile SimpleCSHTMLDocument = TestFile.Create("DesignTime.Simple.cshtml");
+ private static readonly TestFile SimpleCSHTMLDocumentGenerated = TestFile.Create("DesignTime.Simple.txt");
+ private const string TestLinePragmaFileName = "C:\\This\\Path\\Is\\Just\\For\\Line\\Pragmas.cshtml";
+
+ [Fact]
+ public void ConstructorRequiresNonNullHost()
+ {
+ Assert.ThrowsArgumentNull(() => new RazorEditorParser(null, TestLinePragmaFileName),
+ "host");
+ }
+
+ [Fact]
+ public void ConstructorRequiresNonNullPhysicalPath()
+ {
+ Assert.ThrowsArgumentNullOrEmptyString(() => new RazorEditorParser(CreateHost(), null),
+ "sourceFileName");
+ }
+
+ [Fact]
+ public void ConstructorRequiresNonEmptyPhysicalPath()
+ {
+ Assert.ThrowsArgumentNullOrEmptyString(() => new RazorEditorParser(CreateHost(), String.Empty),
+ "sourceFileName");
+ }
+
+ [Fact]
+ public void TreesAreDifferentReturnsTrueIfTreeStructureIsDifferent()
+ {
+ var factory = SpanFactory.CreateCsHtml();
+ Block original = new MarkupBlock(
+ factory.Markup("<p>"),
+ new ExpressionBlock(
+ factory.CodeTransition()),
+ factory.Markup("</p>"));
+ Block modified = new MarkupBlock(
+ factory.Markup("<p>"),
+ new ExpressionBlock(
+ factory.CodeTransition("@"),
+ factory.Code("f")
+ .AsImplicitExpression(CSharpCodeParser.DefaultKeywords, acceptTrailingDot: false)),
+ factory.Markup("</p>"));
+ ITextBuffer oldBuffer = new StringTextBuffer("<p>@</p>");
+ ITextBuffer newBuffer = new StringTextBuffer("<p>@f</p>");
+ Assert.True(RazorEditorParser.TreesAreDifferent(
+ original, modified, new[] {
+ new TextChange(position: 4, oldLength: 0, oldBuffer: oldBuffer, newLength: 1, newBuffer: newBuffer)
+ }));
+ }
+
+ [Fact]
+ public void TreesAreDifferentReturnsFalseIfTreeStructureIsSame()
+ {
+ var factory = SpanFactory.CreateCsHtml();
+ Block original = new MarkupBlock(
+ factory.Markup("<p>"),
+ new ExpressionBlock(
+ factory.CodeTransition(),
+ factory.Code("f")
+ .AsImplicitExpression(CSharpCodeParser.DefaultKeywords, acceptTrailingDot: false)),
+ factory.Markup("</p>"));
+ factory.Reset();
+ Block modified = new MarkupBlock(
+ factory.Markup("<p>"),
+ new ExpressionBlock(
+ factory.CodeTransition(),
+ factory.Code("foo")
+ .AsImplicitExpression(CSharpCodeParser.DefaultKeywords, acceptTrailingDot: false)),
+ factory.Markup("</p>"));
+ original.LinkNodes();
+ modified.LinkNodes();
+ ITextBuffer oldBuffer = new StringTextBuffer("<p>@f</p>");
+ ITextBuffer newBuffer = new StringTextBuffer("<p>@foo</p>");
+ Assert.False(RazorEditorParser.TreesAreDifferent(
+ original, modified, new[] {
+ new TextChange(position: 5, oldLength: 0, oldBuffer: oldBuffer, newLength: 2, newBuffer: newBuffer)
+ }));
+ }
+
+ [Fact]
+ public void CheckForStructureChangesRequiresNonNullBufferInChange()
+ {
+ TextChange change = new TextChange();
+ Assert.ThrowsArgument(
+ () => new RazorEditorParser(
+ CreateHost(),
+ "C:\\Foo.cshtml").CheckForStructureChanges(change),
+ "change",
+ String.Format(RazorResources.Structure_Member_CannotBeNull, "Buffer", "TextChange"));
+ }
+
+ private static RazorEngineHost CreateHost()
+ {
+ return new RazorEngineHost(new CSharpRazorCodeLanguage()) { DesignTimeMode = true };
+ }
+
+ [Fact]
+ public void CheckForStructureChangesStartsReparseAndFiresDocumentParseCompletedEventIfNoAdditionalChangesQueued()
+ {
+ // Arrange
+ using (RazorEditorParser parser = CreateClientParser())
+ {
+ StringTextBuffer input = new StringTextBuffer(SimpleCSHTMLDocument.ReadAllText());
+
+ DocumentParseCompleteEventArgs capturedArgs = null;
+ ManualResetEventSlim parseComplete = new ManualResetEventSlim(false);
+
+ parser.DocumentParseComplete += (sender, args) =>
+ {
+ capturedArgs = args;
+ parseComplete.Set();
+ };
+
+ // Act
+ parser.CheckForStructureChanges(new TextChange(0, 0, new StringTextBuffer(String.Empty), input.Length, input));
+
+ // Assert
+ MiscUtils.DoWithTimeoutIfNotDebugging(parseComplete.Wait);
+ Assert.Equal(
+ SimpleCSHTMLDocumentGenerated.ReadAllText(),
+ MiscUtils.StripRuntimeVersion(
+ capturedArgs.GeneratorResults.GeneratedCode.GenerateCode<CSharpCodeProvider>()
+ ));
+ }
+ }
+
+ [Fact]
+ public void CheckForStructureChangesStartsFullReparseIfChangeOverlapsMultipleSpans()
+ {
+ // Arrange
+ RazorEditorParser parser = new RazorEditorParser(CreateHost(), TestLinePragmaFileName);
+ ITextBuffer original = new StringTextBuffer("Foo @bar Baz");
+ ITextBuffer changed = new StringTextBuffer("Foo @bap Daz");
+ TextChange change = new TextChange(7, 3, original, 3, changed);
+
+ ManualResetEventSlim parseComplete = new ManualResetEventSlim();
+ int parseCount = 0;
+ parser.DocumentParseComplete += (sender, args) =>
+ {
+ Interlocked.Increment(ref parseCount);
+ parseComplete.Set();
+ };
+
+ Assert.Equal(PartialParseResult.Rejected, parser.CheckForStructureChanges(new TextChange(0, 0, new StringTextBuffer(String.Empty), 12, original)));
+ MiscUtils.DoWithTimeoutIfNotDebugging(parseComplete.Wait); // Wait for the parse to finish
+ parseComplete.Reset();
+
+ // Act
+ PartialParseResult result = parser.CheckForStructureChanges(change);
+
+ // Assert
+ Assert.Equal(PartialParseResult.Rejected, result);
+ MiscUtils.DoWithTimeoutIfNotDebugging(parseComplete.Wait);
+ Assert.Equal(2, parseCount);
+ }
+
+ private static void SetupMockWorker(Mock<RazorEditorParser> parser, ManualResetEventSlim running)
+ {
+ SetUpMockWorker(parser, running, null);
+ }
+
+ private static void SetUpMockWorker(Mock<RazorEditorParser> parser, ManualResetEventSlim running, Action<int> backgroundThreadIdReceiver)
+ {
+ parser.Setup(p => p.CreateBackgroundTask(It.IsAny<RazorEngineHost>(), It.IsAny<string>(), It.IsAny<TextChange>()))
+ .Returns<RazorEngineHost, string, TextChange>((host, fileName, change) =>
+ {
+ var task = new Mock<BackgroundParseTask>(new RazorTemplateEngine(host), fileName, change) { CallBase = true };
+ task.Setup(t => t.Run(It.IsAny<CancellationToken>()))
+ .Callback<CancellationToken>((token) =>
+ {
+ if (backgroundThreadIdReceiver != null)
+ {
+ backgroundThreadIdReceiver(Thread.CurrentThread.ManagedThreadId);
+ }
+ running.Set();
+ while (!token.IsCancellationRequested)
+ {
+ }
+ });
+ return task.Object;
+ });
+ }
+
+ private TextChange CreateDummyChange()
+ {
+ return new TextChange(0, 0, new StringTextBuffer(String.Empty), 3, new StringTextBuffer("foo"));
+ }
+
+ private static Mock<RazorEditorParser> CreateMockParser()
+ {
+ return new Mock<RazorEditorParser>(CreateHost(), TestLinePragmaFileName) { CallBase = true };
+ }
+
+ private static RazorEditorParser CreateClientParser()
+ {
+ return new RazorEditorParser(CreateHost(), TestLinePragmaFileName);
+ }
+ }
+}
diff --git a/test/System.Web.Razor.Test/Framework/BlockExtensions.cs b/test/System.Web.Razor.Test/Framework/BlockExtensions.cs
new file mode 100644
index 00000000..2effe832
--- /dev/null
+++ b/test/System.Web.Razor.Test/Framework/BlockExtensions.cs
@@ -0,0 +1,27 @@
+using System.Web.Razor.Parser.SyntaxTree;
+
+namespace System.Web.Razor.Test.Framework
+{
+ public static class BlockExtensions
+ {
+ public static void LinkNodes(this Block self)
+ {
+ Span first = null;
+ Span previous = null;
+ foreach (Span span in self.Flatten())
+ {
+ if (first == null)
+ {
+ first = span;
+ }
+ span.Previous = previous;
+
+ if (previous != null)
+ {
+ previous.Next = span;
+ }
+ previous = span;
+ }
+ }
+ }
+}
diff --git a/test/System.Web.Razor.Test/Framework/BlockTypes.cs b/test/System.Web.Razor.Test/Framework/BlockTypes.cs
new file mode 100644
index 00000000..c0cde64f
--- /dev/null
+++ b/test/System.Web.Razor.Test/Framework/BlockTypes.cs
@@ -0,0 +1,233 @@
+using System.Collections.Generic;
+using System.Web.Razor.Generator;
+using System.Web.Razor.Parser.SyntaxTree;
+
+namespace System.Web.Razor.Test.Framework
+{
+ // The product code doesn't need this, but having subclasses for the block types makes tests much cleaner :)
+
+ public class StatementBlock : Block
+ {
+ private const BlockType ThisBlockType = BlockType.Statement;
+
+ public StatementBlock(IBlockCodeGenerator codeGenerator, IEnumerable<SyntaxTreeNode> children)
+ : base(ThisBlockType, children, codeGenerator)
+ {
+ }
+
+ public StatementBlock(IBlockCodeGenerator codeGenerator, params SyntaxTreeNode[] children)
+ : this(codeGenerator, (IEnumerable<SyntaxTreeNode>)children)
+ {
+ }
+
+ public StatementBlock(params SyntaxTreeNode[] children)
+ : this(BlockCodeGenerator.Null, children)
+ {
+ }
+
+ public StatementBlock(IEnumerable<SyntaxTreeNode> children)
+ : this(BlockCodeGenerator.Null, children)
+ {
+ }
+ }
+
+ public class DirectiveBlock : Block
+ {
+ private const BlockType ThisBlockType = BlockType.Directive;
+
+ public DirectiveBlock(IBlockCodeGenerator codeGenerator, IEnumerable<SyntaxTreeNode> children)
+ : base(ThisBlockType, children, codeGenerator)
+ {
+ }
+
+ public DirectiveBlock(IBlockCodeGenerator codeGenerator, params SyntaxTreeNode[] children)
+ : this(codeGenerator, (IEnumerable<SyntaxTreeNode>)children)
+ {
+ }
+
+ public DirectiveBlock(params SyntaxTreeNode[] children)
+ : this(BlockCodeGenerator.Null, children)
+ {
+ }
+
+ public DirectiveBlock(IEnumerable<SyntaxTreeNode> children)
+ : this(BlockCodeGenerator.Null, children)
+ {
+ }
+ }
+
+ public class FunctionsBlock : Block
+ {
+ private const BlockType ThisBlockType = BlockType.Functions;
+
+ public FunctionsBlock(IBlockCodeGenerator codeGenerator, IEnumerable<SyntaxTreeNode> children)
+ : base(ThisBlockType, children, codeGenerator)
+ {
+ }
+
+ public FunctionsBlock(IBlockCodeGenerator codeGenerator, params SyntaxTreeNode[] children)
+ : this(codeGenerator, (IEnumerable<SyntaxTreeNode>)children)
+ {
+ }
+
+ public FunctionsBlock(params SyntaxTreeNode[] children)
+ : this(BlockCodeGenerator.Null, children)
+ {
+ }
+
+ public FunctionsBlock(IEnumerable<SyntaxTreeNode> children)
+ : this(BlockCodeGenerator.Null, children)
+ {
+ }
+ }
+
+ public class ExpressionBlock : Block
+ {
+ private const BlockType ThisBlockType = BlockType.Expression;
+
+ public ExpressionBlock(IBlockCodeGenerator codeGenerator, IEnumerable<SyntaxTreeNode> children)
+ : base(ThisBlockType, children, codeGenerator)
+ {
+ }
+
+ public ExpressionBlock(IBlockCodeGenerator codeGenerator, params SyntaxTreeNode[] children)
+ : this(codeGenerator, (IEnumerable<SyntaxTreeNode>)children)
+ {
+ }
+
+ public ExpressionBlock(params SyntaxTreeNode[] children)
+ : this(new ExpressionCodeGenerator(), children)
+ {
+ }
+
+ public ExpressionBlock(IEnumerable<SyntaxTreeNode> children)
+ : this(new ExpressionCodeGenerator(), children)
+ {
+ }
+ }
+
+ public class HelperBlock : Block
+ {
+ private const BlockType ThisBlockType = BlockType.Helper;
+
+ public HelperBlock(IBlockCodeGenerator codeGenerator, IEnumerable<SyntaxTreeNode> children)
+ : base(ThisBlockType, children, codeGenerator)
+ {
+ }
+
+ public HelperBlock(IBlockCodeGenerator codeGenerator, params SyntaxTreeNode[] children)
+ : this(codeGenerator, (IEnumerable<SyntaxTreeNode>)children)
+ {
+ }
+
+ public HelperBlock(params SyntaxTreeNode[] children)
+ : this(BlockCodeGenerator.Null, children)
+ {
+ }
+
+ public HelperBlock(IEnumerable<SyntaxTreeNode> children)
+ : this(BlockCodeGenerator.Null, children)
+ {
+ }
+ }
+
+ public class MarkupBlock : Block
+ {
+ private const BlockType ThisBlockType = BlockType.Markup;
+
+ public MarkupBlock(IBlockCodeGenerator codeGenerator, IEnumerable<SyntaxTreeNode> children)
+ : base(ThisBlockType, children, codeGenerator)
+ {
+ }
+
+ public MarkupBlock(IBlockCodeGenerator codeGenerator, params SyntaxTreeNode[] children)
+ : this(codeGenerator, (IEnumerable<SyntaxTreeNode>)children)
+ {
+ }
+
+ public MarkupBlock(params SyntaxTreeNode[] children)
+ : this(BlockCodeGenerator.Null, children)
+ {
+ }
+
+ public MarkupBlock(IEnumerable<SyntaxTreeNode> children)
+ : this(BlockCodeGenerator.Null, children)
+ {
+ }
+ }
+
+ public class SectionBlock : Block
+ {
+ private const BlockType ThisBlockType = BlockType.Section;
+
+ public SectionBlock(IBlockCodeGenerator codeGenerator, IEnumerable<SyntaxTreeNode> children)
+ : base(ThisBlockType, children, codeGenerator)
+ {
+ }
+
+ public SectionBlock(IBlockCodeGenerator codeGenerator, params SyntaxTreeNode[] children)
+ : this(codeGenerator, (IEnumerable<SyntaxTreeNode>)children)
+ {
+ }
+
+ public SectionBlock(params SyntaxTreeNode[] children)
+ : this(BlockCodeGenerator.Null, children)
+ {
+ }
+
+ public SectionBlock(IEnumerable<SyntaxTreeNode> children)
+ : this(BlockCodeGenerator.Null, children)
+ {
+ }
+ }
+
+ public class TemplateBlock : Block
+ {
+ private const BlockType ThisBlockType = BlockType.Template;
+
+ public TemplateBlock(IBlockCodeGenerator codeGenerator, IEnumerable<SyntaxTreeNode> children)
+ : base(ThisBlockType, children, codeGenerator)
+ {
+ }
+
+ public TemplateBlock(IBlockCodeGenerator codeGenerator, params SyntaxTreeNode[] children)
+ : this(codeGenerator, (IEnumerable<SyntaxTreeNode>)children)
+ {
+ }
+
+ public TemplateBlock(params SyntaxTreeNode[] children)
+ : this(new TemplateBlockCodeGenerator(), children)
+ {
+ }
+
+ public TemplateBlock(IEnumerable<SyntaxTreeNode> children)
+ : this(new TemplateBlockCodeGenerator(), children)
+ {
+ }
+ }
+
+ public class CommentBlock : Block
+ {
+ private const BlockType ThisBlockType = BlockType.Comment;
+
+ public CommentBlock(IBlockCodeGenerator codeGenerator, IEnumerable<SyntaxTreeNode> children)
+ : base(ThisBlockType, children, codeGenerator)
+ {
+ }
+
+ public CommentBlock(IBlockCodeGenerator codeGenerator, params SyntaxTreeNode[] children)
+ : this(codeGenerator, (IEnumerable<SyntaxTreeNode>)children)
+ {
+ }
+
+ public CommentBlock(params SyntaxTreeNode[] children)
+ : this(new RazorCommentCodeGenerator(), children)
+ {
+ }
+
+ public CommentBlock(IEnumerable<SyntaxTreeNode> children)
+ : this(new RazorCommentCodeGenerator(), children)
+ {
+ }
+ }
+}
diff --git a/test/System.Web.Razor.Test/Framework/CodeParserTestBase.cs b/test/System.Web.Razor.Test/Framework/CodeParserTestBase.cs
new file mode 100644
index 00000000..42dd4934
--- /dev/null
+++ b/test/System.Web.Razor.Test/Framework/CodeParserTestBase.cs
@@ -0,0 +1,74 @@
+using System.Collections.Generic;
+using System.Web.Razor.Parser;
+using System.Web.Razor.Parser.SyntaxTree;
+
+namespace System.Web.Razor.Test.Framework
+{
+ public abstract class CodeParserTestBase : ParserTestBase
+ {
+ protected abstract ISet<string> KeywordSet { get; }
+
+ protected override ParserBase SelectActiveParser(ParserBase codeParser, ParserBase markupParser)
+ {
+ return codeParser;
+ }
+
+ protected void ImplicitExpressionTest(string input, params RazorError[] errors)
+ {
+ ImplicitExpressionTest(input, AcceptedCharacters.NonWhiteSpace, errors);
+ }
+
+ protected void ImplicitExpressionTest(string input, AcceptedCharacters acceptedCharacters, params RazorError[] errors)
+ {
+ ImplicitExpressionTest(input, input, acceptedCharacters, errors);
+ }
+
+ protected void ImplicitExpressionTest(string input, string expected, params RazorError[] errors)
+ {
+ ImplicitExpressionTest(input, expected, AcceptedCharacters.NonWhiteSpace, errors);
+ }
+
+ protected override void SingleSpanBlockTest(string document, BlockType blockType, SpanKind spanType, AcceptedCharacters acceptedCharacters = AcceptedCharacters.Any)
+ {
+ SingleSpanBlockTest(document, blockType, spanType, acceptedCharacters, expectedError: null);
+ }
+
+ protected override void SingleSpanBlockTest(string document, string spanContent, BlockType blockType, SpanKind spanType, AcceptedCharacters acceptedCharacters = AcceptedCharacters.Any)
+ {
+ SingleSpanBlockTest(document, spanContent, blockType, spanType, acceptedCharacters, expectedErrors: null);
+ }
+
+ protected override void SingleSpanBlockTest(string document, BlockType blockType, SpanKind spanType, params RazorError[] expectedError)
+ {
+ SingleSpanBlockTest(document, document, blockType, spanType, expectedError);
+ }
+
+ protected override void SingleSpanBlockTest(string document, string spanContent, BlockType blockType, SpanKind spanType, params RazorError[] expectedErrors)
+ {
+ SingleSpanBlockTest(document, spanContent, blockType, spanType, AcceptedCharacters.Any, expectedErrors ?? new RazorError[0]);
+ }
+
+ protected override void SingleSpanBlockTest(string document, BlockType blockType, SpanKind spanType, AcceptedCharacters acceptedCharacters, params RazorError[] expectedError)
+ {
+ SingleSpanBlockTest(document, document, blockType, spanType, acceptedCharacters, expectedError);
+ }
+
+ protected override void SingleSpanBlockTest(string document, string spanContent, BlockType blockType, SpanKind spanType, AcceptedCharacters acceptedCharacters, params RazorError[] expectedErrors)
+ {
+ Block b = CreateSimpleBlockAndSpan(spanContent, blockType, spanType, acceptedCharacters);
+ ParseBlockTest(document, b, expectedErrors ?? new RazorError[0]);
+ }
+
+ protected void ImplicitExpressionTest(string input, string expected, AcceptedCharacters acceptedCharacters, params RazorError[] errors)
+ {
+ var factory = CreateSpanFactory();
+ ParseBlockTest(SyntaxConstants.TransitionString + input,
+ new ExpressionBlock(
+ factory.CodeTransition(),
+ factory.Code(expected)
+ .AsImplicitExpression(KeywordSet)
+ .Accepts(acceptedCharacters)),
+ errors);
+ }
+ }
+}
diff --git a/test/System.Web.Razor.Test/Framework/CsHtmlCodeParserTestBase.cs b/test/System.Web.Razor.Test/Framework/CsHtmlCodeParserTestBase.cs
new file mode 100644
index 00000000..db9ff8bf
--- /dev/null
+++ b/test/System.Web.Razor.Test/Framework/CsHtmlCodeParserTestBase.cs
@@ -0,0 +1,28 @@
+using System.Collections.Generic;
+using System.Web.Razor.Parser;
+
+namespace System.Web.Razor.Test.Framework
+{
+ public abstract class CsHtmlCodeParserTestBase : CodeParserTestBase
+ {
+ protected override ISet<string> KeywordSet
+ {
+ get { return CSharpCodeParser.DefaultKeywords; }
+ }
+
+ protected override SpanFactory CreateSpanFactory()
+ {
+ return SpanFactory.CreateCsHtml();
+ }
+
+ public override ParserBase CreateMarkupParser()
+ {
+ return new HtmlMarkupParser();
+ }
+
+ public override ParserBase CreateCodeParser()
+ {
+ return new CSharpCodeParser();
+ }
+ }
+}
diff --git a/test/System.Web.Razor.Test/Framework/CsHtmlMarkupParserTestBase.cs b/test/System.Web.Razor.Test/Framework/CsHtmlMarkupParserTestBase.cs
new file mode 100644
index 00000000..181abf9c
--- /dev/null
+++ b/test/System.Web.Razor.Test/Framework/CsHtmlMarkupParserTestBase.cs
@@ -0,0 +1,28 @@
+using System.Collections.Generic;
+using System.Web.Razor.Parser;
+
+namespace System.Web.Razor.Test.Framework
+{
+ public abstract class CsHtmlMarkupParserTestBase : MarkupParserTestBase
+ {
+ protected override ISet<string> KeywordSet
+ {
+ get { return CSharpCodeParser.DefaultKeywords; }
+ }
+
+ protected override SpanFactory CreateSpanFactory()
+ {
+ return SpanFactory.CreateCsHtml();
+ }
+
+ public override ParserBase CreateMarkupParser()
+ {
+ return new HtmlMarkupParser();
+ }
+
+ public override ParserBase CreateCodeParser()
+ {
+ return new CSharpCodeParser();
+ }
+ }
+}
diff --git a/test/System.Web.Razor.Test/Framework/ErrorCollector.cs b/test/System.Web.Razor.Test/Framework/ErrorCollector.cs
new file mode 100644
index 00000000..64a8e461
--- /dev/null
+++ b/test/System.Web.Razor.Test/Framework/ErrorCollector.cs
@@ -0,0 +1,54 @@
+using System.Text;
+using System.Web.Razor.Utils;
+
+namespace System.Web.Razor.Test.Framework
+{
+ public class ErrorCollector
+ {
+ private StringBuilder _message = new StringBuilder();
+ private int _indent = 0;
+
+ public bool Success { get; private set; }
+
+ public string Message
+ {
+ get { return _message.ToString(); }
+ }
+
+ public ErrorCollector()
+ {
+ Success = true;
+ }
+
+ public void AddError(string msg, params object[] args)
+ {
+ Append("F", msg, args);
+ Success = false;
+ }
+
+ public void AddMessage(string msg, params object[] args)
+ {
+ Append("P", msg, args);
+ }
+
+ public IDisposable Indent()
+ {
+ _indent++;
+ return new DisposableAction(Unindent);
+ }
+
+ public void Unindent()
+ {
+ _indent--;
+ }
+
+ private void Append(string prefix, string msg, object[] args)
+ {
+ _message.Append(prefix);
+ _message.Append(":");
+ _message.Append(new String('\t', _indent));
+ _message.AppendFormat(msg, args);
+ _message.AppendLine();
+ }
+ }
+}
diff --git a/test/System.Web.Razor.Test/Framework/MarkupParserTestBase.cs b/test/System.Web.Razor.Test/Framework/MarkupParserTestBase.cs
new file mode 100644
index 00000000..5149cdec
--- /dev/null
+++ b/test/System.Web.Razor.Test/Framework/MarkupParserTestBase.cs
@@ -0,0 +1,59 @@
+using System.Web.Razor.Parser;
+using System.Web.Razor.Parser.SyntaxTree;
+
+namespace System.Web.Razor.Test.Framework
+{
+ public abstract class MarkupParserTestBase : CodeParserTestBase
+ {
+ protected override ParserBase SelectActiveParser(ParserBase codeParser, ParserBase markupParser)
+ {
+ return markupParser;
+ }
+
+ protected virtual void SingleSpanDocumentTest(string document, BlockType blockType, SpanKind spanType)
+ {
+ Block b = CreateSimpleBlockAndSpan(document, blockType, spanType);
+ ParseDocumentTest(document, b);
+ }
+
+ protected virtual void ParseDocumentTest(string document)
+ {
+ ParseDocumentTest(document, null, false);
+ }
+
+ protected virtual void ParseDocumentTest(string document, Block expectedRoot)
+ {
+ ParseDocumentTest(document, expectedRoot, false, null);
+ }
+
+ protected virtual void ParseDocumentTest(string document, Block expectedRoot, params RazorError[] expectedErrors)
+ {
+ ParseDocumentTest(document, expectedRoot, false, expectedErrors);
+ }
+
+ protected virtual void ParseDocumentTest(string document, bool designTimeParser)
+ {
+ ParseDocumentTest(document, null, designTimeParser);
+ }
+
+ protected virtual void ParseDocumentTest(string document, Block expectedRoot, bool designTimeParser)
+ {
+ ParseDocumentTest(document, expectedRoot, designTimeParser, null);
+ }
+
+ protected virtual void ParseDocumentTest(string document, Block expectedRoot, bool designTimeParser, params RazorError[] expectedErrors)
+ {
+ RunParseTest(document, parser => parser.ParseDocument, expectedRoot, expectedErrors, designTimeParser);
+ }
+
+ protected virtual ParserResults ParseDocument(string document)
+ {
+ return ParseDocument(document, designTimeParser: false);
+ }
+
+ protected virtual ParserResults ParseDocument(string document, bool designTimeParser)
+ {
+ return RunParse(document, parser => parser.ParseDocument, designTimeParser);
+ }
+ }
+}
diff --git a/test/System.Web.Razor.Test/Framework/ParserTestBase.cs b/test/System.Web.Razor.Test/Framework/ParserTestBase.cs
new file mode 100644
index 00000000..58f5dccb
--- /dev/null
+++ b/test/System.Web.Razor.Test/Framework/ParserTestBase.cs
@@ -0,0 +1,381 @@
+//#define PARSER_TRACE
+
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Linq;
+using System.Text;
+using System.Web.Razor.Generator;
+using System.Web.Razor.Parser;
+using System.Web.Razor.Parser.SyntaxTree;
+using System.Web.Razor.Text;
+using Xunit;
+
+namespace System.Web.Razor.Test.Framework
+{
+ public abstract class ParserTestBase
+ {
+ protected static Block IgnoreOutput = new IgnoreOutputBlock();
+
+ public SpanFactory Factory { get; private set; }
+
+ protected ParserTestBase()
+ {
+ Factory = CreateSpanFactory();
+ }
+
+ public abstract ParserBase CreateMarkupParser();
+ public abstract ParserBase CreateCodeParser();
+
+ protected abstract ParserBase SelectActiveParser(ParserBase codeParser, ParserBase markupParser);
+
+ public virtual ParserContext CreateParserContext(ITextDocument input, ParserBase codeParser, ParserBase markupParser)
+ {
+ return new ParserContext(input, codeParser, markupParser, SelectActiveParser(codeParser, markupParser));
+ }
+
+ protected abstract SpanFactory CreateSpanFactory();
+
+ protected virtual void ParseBlockTest(string document)
+ {
+ ParseBlockTest(document, null, false, new RazorError[0]);
+ }
+
+ protected virtual void ParseBlockTest(string document, bool designTimeParser)
+ {
+ ParseBlockTest(document, null, designTimeParser, new RazorError[0]);
+ }
+
+ protected virtual void ParseBlockTest(string document, params RazorError[] expectedErrors)
+ {
+ ParseBlockTest(document, false, expectedErrors);
+ }
+
+ protected virtual void ParseBlockTest(string document, bool designTimeParser, params RazorError[] expectedErrors)
+ {
+ ParseBlockTest(document, null, designTimeParser, expectedErrors);
+ }
+
+ protected virtual void ParseBlockTest(string document, Block expectedRoot)
+ {
+ ParseBlockTest(document, expectedRoot, false, null);
+ }
+
+ protected virtual void ParseBlockTest(string document, Block expectedRoot, bool designTimeParser)
+ {
+ ParseBlockTest(document, expectedRoot, designTimeParser, null);
+ }
+
+ protected virtual void ParseBlockTest(string document, Block expectedRoot, params RazorError[] expectedErrors)
+ {
+ ParseBlockTest(document, expectedRoot, false, expectedErrors);
+ }
+
+ protected virtual void ParseBlockTest(string document, Block expectedRoot, bool designTimeParser, params RazorError[] expectedErrors)
+ {
+ RunParseTest(document, parser => parser.ParseBlock, expectedRoot, (expectedErrors ?? new RazorError[0]).ToList(), designTimeParser);
+ }
+
+ protected virtual void SingleSpanBlockTest(string document, BlockType blockType, SpanKind spanType, AcceptedCharacters acceptedCharacters = AcceptedCharacters.Any)
+ {
+ SingleSpanBlockTest(document, blockType, spanType, acceptedCharacters, expectedError: null);
+ }
+
+ protected virtual void SingleSpanBlockTest(string document, string spanContent, BlockType blockType, SpanKind spanType, AcceptedCharacters acceptedCharacters = AcceptedCharacters.Any)
+ {
+ SingleSpanBlockTest(document, spanContent, blockType, spanType, acceptedCharacters, expectedErrors: null);
+ }
+
+ protected virtual void SingleSpanBlockTest(string document, BlockType blockType, SpanKind spanType, params RazorError[] expectedError)
+ {
+ SingleSpanBlockTest(document, document, blockType, spanType, expectedError);
+ }
+
+ protected virtual void SingleSpanBlockTest(string document, string spanContent, BlockType blockType, SpanKind spanType, params RazorError[] expectedErrors)
+ {
+ SingleSpanBlockTest(document, spanContent, blockType, spanType, AcceptedCharacters.Any, expectedErrors ?? new RazorError[0]);
+ }
+
+ protected virtual void SingleSpanBlockTest(string document, BlockType blockType, SpanKind spanType, AcceptedCharacters acceptedCharacters, params RazorError[] expectedError)
+ {
+ SingleSpanBlockTest(document, document, blockType, spanType, acceptedCharacters, expectedError);
+ }
+
+ protected virtual void SingleSpanBlockTest(string document, string spanContent, BlockType blockType, SpanKind spanType, AcceptedCharacters acceptedCharacters, params RazorError[] expectedErrors)
+ {
+ BlockBuilder builder = new BlockBuilder();
+ builder.Type = blockType;
+ ParseBlockTest(
+ document,
+ ConfigureAndAddSpanToBlock(
+ builder,
+ Factory.Span(spanType, spanContent, spanType == SpanKind.Markup)
+ .Accepts(acceptedCharacters)),
+ expectedErrors ?? new RazorError[0]);
+ }
+
+ protected virtual ParserResults RunParse(string document, Func<ParserBase, Action> parserActionSelector, bool designTimeParser)
+ {
+ // Create the source
+ ParserResults results = null;
+ using (SeekableTextReader reader = new SeekableTextReader(document))
+ {
+ try
+ {
+ ParserBase codeParser = CreateCodeParser();
+ ParserBase markupParser = CreateMarkupParser();
+ ParserContext context = CreateParserContext(reader, codeParser, markupParser);
+ context.DesignTimeMode = designTimeParser;
+
+ codeParser.Context = context;
+ markupParser.Context = context;
+
+ // Run the parser
+ parserActionSelector(context.ActiveParser)();
+ results = context.CompleteParse();
+ }
+ finally
+ {
+ if (results != null && results.Document != null)
+ {
+ WriteTraceLine(String.Empty);
+ WriteTraceLine("Actual Parse Tree:");
+ WriteNode(0, results.Document);
+ }
+ }
+ }
+ return results;
+ }
+
+ protected virtual void RunParseTest(string document, Func<ParserBase, Action> parserActionSelector, Block expectedRoot, IList<RazorError> expectedErrors, bool designTimeParser)
+ {
+ // Create the source
+ ParserResults results = RunParse(document, parserActionSelector, designTimeParser);
+
+ // Evaluate the results
+ if (!ReferenceEquals(expectedRoot, IgnoreOutput))
+ {
+ EvaluateResults(results, expectedRoot, expectedErrors);
+ }
+ }
+
+ [Conditional("PARSER_TRACE")]
+ private void WriteNode(int indent, SyntaxTreeNode node)
+ {
+ string content = node.ToString().Replace("\r", "\\r")
+ .Replace("\n", "\\n")
+ .Replace("{", "{{")
+ .Replace("}", "}}");
+ if (indent > 0)
+ {
+ content = new String('.', indent * 2) + content;
+ }
+ WriteTraceLine(content);
+ Block block = node as Block;
+ if (block != null)
+ {
+ foreach (SyntaxTreeNode child in block.Children)
+ {
+ WriteNode(indent + 1, child);
+ }
+ }
+ }
+
+ public static void EvaluateResults(ParserResults results, Block expectedRoot)
+ {
+ EvaluateResults(results, expectedRoot, null);
+ }
+
+ public static void EvaluateResults(ParserResults results, Block expectedRoot, IList<RazorError> expectedErrors)
+ {
+ EvaluateParseTree(results.Document, expectedRoot);
+ EvaluateRazorErrors(results.ParserErrors, expectedErrors);
+ }
+
+ public static void EvaluateParseTree(Block actualRoot, Block expectedRoot)
+ {
+ // Evaluate the result
+ ErrorCollector collector = new ErrorCollector();
+
+ // Link all the nodes
+ expectedRoot.LinkNodes();
+
+ if (expectedRoot == null)
+ {
+ Assert.Null(actualRoot);
+ }
+ else
+ {
+ Assert.NotNull(actualRoot);
+ EvaluateSyntaxTreeNode(collector, actualRoot, expectedRoot);
+ if (collector.Success)
+ {
+ WriteTraceLine("Parse Tree Validation Succeeded:\r\n{0}", collector.Message);
+ }
+ else
+ {
+ Assert.True(false, String.Format("\r\n{0}", collector.Message));
+ }
+ }
+ }
+
+ private static void EvaluateSyntaxTreeNode(ErrorCollector collector, SyntaxTreeNode actual, SyntaxTreeNode expected)
+ {
+ if (actual == null)
+ {
+ AddNullActualError(collector, actual, expected);
+ }
+
+ if (actual.IsBlock != expected.IsBlock)
+ {
+ AddMismatchError(collector, actual, expected);
+ }
+ else
+ {
+ if (expected.IsBlock)
+ {
+ EvaluateBlock(collector, (Block)actual, (Block)expected);
+ }
+ else
+ {
+ EvaluateSpan(collector, (Span)actual, (Span)expected);
+ }
+ }
+ }
+
+ private static void EvaluateSpan(ErrorCollector collector, Span actual, Span expected)
+ {
+ if (!Equals(expected, actual))
+ {
+ AddMismatchError(collector, actual, expected);
+ }
+ else
+ {
+ AddPassedMessage(collector, expected);
+ }
+ }
+
+ private static void EvaluateBlock(ErrorCollector collector, Block actual, Block expected)
+ {
+ if (actual.Type != expected.Type || !expected.CodeGenerator.Equals(actual.CodeGenerator))
+ {
+ AddMismatchError(collector, actual, expected);
+ }
+ else
+ {
+ AddPassedMessage(collector, expected);
+ using (collector.Indent())
+ {
+ IEnumerator<SyntaxTreeNode> expectedNodes = expected.Children.GetEnumerator();
+ IEnumerator<SyntaxTreeNode> actualNodes = actual.Children.GetEnumerator();
+ while (expectedNodes.MoveNext())
+ {
+ if (!actualNodes.MoveNext())
+ {
+ collector.AddError("{0} - FAILED :: No more elements at this node", expectedNodes.Current);
+ }
+ else
+ {
+ EvaluateSyntaxTreeNode(collector, actualNodes.Current, expectedNodes.Current);
+ }
+ }
+ while (actualNodes.MoveNext())
+ {
+ collector.AddError("End of Node - FAILED :: Found Node: {0}", actualNodes.Current);
+ }
+ }
+ }
+ }
+
+ private static void AddPassedMessage(ErrorCollector collector, SyntaxTreeNode expected)
+ {
+ collector.AddMessage("{0} - PASSED", expected);
+ }
+
+ private static void AddMismatchError(ErrorCollector collector, SyntaxTreeNode actual, SyntaxTreeNode expected)
+ {
+ collector.AddError("{0} - FAILED :: Actual: {1}", expected, actual);
+ }
+
+ private static void AddNullActualError(ErrorCollector collector, SyntaxTreeNode actual, SyntaxTreeNode expected)
+ {
+ collector.AddError("{0} - FAILED :: Actual: << Null >>", expected);
+ }
+
+ public static void EvaluateRazorErrors(IList<RazorError> actualErrors, IList<RazorError> expectedErrors)
+ {
+ // Evaluate the errors
+ if (expectedErrors == null || expectedErrors.Count == 0)
+ {
+ Assert.True(actualErrors.Count == 0,
+ String.Format("Expected that no errors would be raised, but the following errors were:\r\n{0}", FormatErrors(actualErrors)));
+ }
+ else
+ {
+ Assert.True(expectedErrors.Count == actualErrors.Count,
+ String.Format("Expected that {0} errors would be raised, but {1} errors were.\r\nExpected Errors: \r\n{2}\r\nActual Errors: \r\n{3}",
+ expectedErrors.Count,
+ actualErrors.Count,
+ FormatErrors(expectedErrors),
+ FormatErrors(actualErrors)));
+ Assert.Equal(expectedErrors.ToArray(), actualErrors.ToArray());
+ }
+ WriteTraceLine("Expected Errors were raised:\r\n{0}", FormatErrors(expectedErrors));
+ }
+
+ public static string FormatErrors(IList<RazorError> errors)
+ {
+ if (errors == null)
+ {
+ return "\t<< No Errors >>";
+ }
+
+ StringBuilder builder = new StringBuilder();
+ foreach (RazorError err in errors)
+ {
+ builder.AppendFormat("\t{0}", err);
+ builder.AppendLine();
+ }
+ return builder.ToString();
+ }
+
+ [Conditional("PARSER_TRACE")]
+ private static void WriteTraceLine(string format, params object[] args)
+ {
+ Trace.WriteLine(String.Format(format, args));
+ }
+
+ protected virtual Block CreateSimpleBlockAndSpan(string spanContent, BlockType blockType, SpanKind spanType, AcceptedCharacters acceptedCharacters = AcceptedCharacters.Any)
+ {
+ SpanConstructor span = Factory.Span(spanType, spanContent, spanType == SpanKind.Markup).Accepts(acceptedCharacters);
+ BlockBuilder b = new BlockBuilder()
+ {
+ Type = blockType
+ };
+ return ConfigureAndAddSpanToBlock(b, span);
+ }
+
+ protected virtual Block ConfigureAndAddSpanToBlock(BlockBuilder block, SpanConstructor span)
+ {
+ switch (block.Type)
+ {
+ case BlockType.Markup:
+ span.With(new MarkupCodeGenerator());
+ break;
+ case BlockType.Statement:
+ span.With(new StatementCodeGenerator());
+ break;
+ case BlockType.Expression:
+ block.CodeGenerator = new ExpressionCodeGenerator();
+ span.With(new ExpressionCodeGenerator());
+ break;
+ }
+ block.Children.Add(span);
+ return block.Build();
+ }
+
+ private class IgnoreOutputBlock : Block
+ {
+ public IgnoreOutputBlock() : base(BlockType.Template, Enumerable.Empty<SyntaxTreeNode>(), null) { }
+ }
+ }
+}
diff --git a/test/System.Web.Razor.Test/Framework/RawTextSymbol.cs b/test/System.Web.Razor.Test/Framework/RawTextSymbol.cs
new file mode 100644
index 00000000..abb02fd2
--- /dev/null
+++ b/test/System.Web.Razor.Test/Framework/RawTextSymbol.cs
@@ -0,0 +1,71 @@
+using System.Globalization;
+using System.Web.Razor.Parser.SyntaxTree;
+using System.Web.Razor.Text;
+using System.Web.Razor.Tokenizer.Symbols;
+using Microsoft.Internal.Web.Utils;
+
+namespace System.Web.Razor.Test.Framework
+{
+ internal class RawTextSymbol : ISymbol
+ {
+ public SourceLocation Start { get; private set; }
+ public string Content { get; private set; }
+
+ public RawTextSymbol(SourceLocation start, string content)
+ {
+ if (content == null)
+ {
+ throw new ArgumentNullException("content");
+ }
+
+ Start = start;
+ Content = content;
+ }
+
+ public override bool Equals(object obj)
+ {
+ RawTextSymbol other = obj as RawTextSymbol;
+ return Equals(Start, other.Start) && Equals(Content, other.Content);
+ }
+
+ internal bool EquivalentTo(ISymbol sym)
+ {
+ return Equals(Start, sym.Start) && Equals(Content, sym.Content);
+ }
+
+ public override int GetHashCode()
+ {
+ return HashCodeCombiner.Start()
+ .Add(Start)
+ .Add(Content)
+ .CombinedHash;
+ }
+
+ public void OffsetStart(SourceLocation documentStart)
+ {
+ Start = documentStart + Start;
+ }
+
+ public void ChangeStart(SourceLocation newStart)
+ {
+ Start = newStart;
+ }
+
+ public override string ToString()
+ {
+ return String.Format(CultureInfo.InvariantCulture, "{0} RAW - [{1}]", Start, Content);
+ }
+
+ internal void CalculateStart(Span prev)
+ {
+ if (prev == null)
+ {
+ Start = SourceLocation.Zero;
+ }
+ else
+ {
+ Start = new SourceLocationTracker(prev.Start).UpdateLocation(prev.Content).CurrentLocation;
+ }
+ }
+ }
+}
diff --git a/test/System.Web.Razor.Test/Framework/TestSpanBuilder.cs b/test/System.Web.Razor.Test/Framework/TestSpanBuilder.cs
new file mode 100644
index 00000000..27a4776d
--- /dev/null
+++ b/test/System.Web.Razor.Test/Framework/TestSpanBuilder.cs
@@ -0,0 +1,405 @@
+using System.Collections.Generic;
+using System.Linq;
+using System.Web.Razor.Editor;
+using System.Web.Razor.Generator;
+using System.Web.Razor.Parser;
+using System.Web.Razor.Parser.SyntaxTree;
+using System.Web.Razor.Text;
+using System.Web.Razor.Tokenizer;
+using System.Web.Razor.Tokenizer.Symbols;
+
+namespace System.Web.Razor.Test.Framework
+{
+ public static class SpanFactoryExtensions
+ {
+ public static UnclassifiedCodeSpanConstructor EmptyCSharp(this SpanFactory self)
+ {
+ return new UnclassifiedCodeSpanConstructor(
+ self.Span(SpanKind.Code, new CSharpSymbol(self.LocationTracker.CurrentLocation, String.Empty, CSharpSymbolType.Unknown)));
+ }
+
+ public static UnclassifiedCodeSpanConstructor EmptyVB(this SpanFactory self)
+ {
+ return new UnclassifiedCodeSpanConstructor(
+ self.Span(SpanKind.Code, new VBSymbol(self.LocationTracker.CurrentLocation, String.Empty, VBSymbolType.Unknown)));
+ }
+
+ public static SpanConstructor EmptyHtml(this SpanFactory self)
+ {
+ return self.Span(SpanKind.Markup, new HtmlSymbol(self.LocationTracker.CurrentLocation, String.Empty, HtmlSymbolType.Unknown))
+ .With(new MarkupCodeGenerator());
+ }
+
+ public static UnclassifiedCodeSpanConstructor Code(this SpanFactory self, string content)
+ {
+ return new UnclassifiedCodeSpanConstructor(
+ self.Span(SpanKind.Code, content, markup: false));
+ }
+
+ public static SpanConstructor CodeTransition(this SpanFactory self)
+ {
+ return self.Span(SpanKind.Transition, SyntaxConstants.TransitionString, markup: false).Accepts(AcceptedCharacters.None);
+ }
+
+ public static SpanConstructor CodeTransition(this SpanFactory self, string content)
+ {
+ return self.Span(SpanKind.Transition, content, markup: false).Accepts(AcceptedCharacters.None);
+ }
+
+ public static SpanConstructor CodeTransition(this SpanFactory self, CSharpSymbolType type)
+ {
+ return self.Span(SpanKind.Transition, SyntaxConstants.TransitionString, type).Accepts(AcceptedCharacters.None);
+ }
+
+ public static SpanConstructor CodeTransition(this SpanFactory self, string content, CSharpSymbolType type)
+ {
+ return self.Span(SpanKind.Transition, content, type).Accepts(AcceptedCharacters.None);
+ }
+
+ public static SpanConstructor CodeTransition(this SpanFactory self, VBSymbolType type)
+ {
+ return self.Span(SpanKind.Transition, SyntaxConstants.TransitionString, type).Accepts(AcceptedCharacters.None);
+ }
+
+ public static SpanConstructor CodeTransition(this SpanFactory self, string content, VBSymbolType type)
+ {
+ return self.Span(SpanKind.Transition, content, type).Accepts(AcceptedCharacters.None);
+ }
+
+ public static SpanConstructor MarkupTransition(this SpanFactory self)
+ {
+ return self.Span(SpanKind.Transition, SyntaxConstants.TransitionString, markup: true).Accepts(AcceptedCharacters.None);
+ }
+
+ public static SpanConstructor MarkupTransition(this SpanFactory self, string content)
+ {
+ return self.Span(SpanKind.Transition, content, markup: true).Accepts(AcceptedCharacters.None);
+ }
+
+ public static SpanConstructor MarkupTransition(this SpanFactory self, HtmlSymbolType type)
+ {
+ return self.Span(SpanKind.Transition, SyntaxConstants.TransitionString, type).Accepts(AcceptedCharacters.None);
+ }
+
+ public static SpanConstructor MarkupTransition(this SpanFactory self, string content, HtmlSymbolType type)
+ {
+ return self.Span(SpanKind.Transition, content, type).Accepts(AcceptedCharacters.None);
+ }
+
+ public static SpanConstructor MetaCode(this SpanFactory self, string content)
+ {
+ return self.Span(SpanKind.MetaCode, content, markup: false);
+ }
+
+ public static SpanConstructor MetaCode(this SpanFactory self, string content, CSharpSymbolType type)
+ {
+ return self.Span(SpanKind.MetaCode, content, type);
+ }
+
+ public static SpanConstructor MetaCode(this SpanFactory self, string content, VBSymbolType type)
+ {
+ return self.Span(SpanKind.MetaCode, content, type);
+ }
+
+ public static SpanConstructor MetaMarkup(this SpanFactory self, string content)
+ {
+ return self.Span(SpanKind.MetaCode, content, markup: true);
+ }
+
+ public static SpanConstructor MetaMarkup(this SpanFactory self, string content, HtmlSymbolType type)
+ {
+ return self.Span(SpanKind.MetaCode, content, type);
+ }
+
+ public static SpanConstructor Comment(this SpanFactory self, string content, CSharpSymbolType type)
+ {
+ return self.Span(SpanKind.Comment, content, type);
+ }
+
+ public static SpanConstructor Comment(this SpanFactory self, string content, VBSymbolType type)
+ {
+ return self.Span(SpanKind.Comment, content, type);
+ }
+
+ public static SpanConstructor Comment(this SpanFactory self, string content, HtmlSymbolType type)
+ {
+ return self.Span(SpanKind.Comment, content, type);
+ }
+
+ public static SpanConstructor Markup(this SpanFactory self, string content)
+ {
+ return self.Span(SpanKind.Markup, content, markup: true).With(new MarkupCodeGenerator());
+ }
+
+ public static SpanConstructor Markup(this SpanFactory self, params string[] content)
+ {
+ return self.Span(SpanKind.Markup, content, markup: true).With(new MarkupCodeGenerator());
+ }
+
+ public static SourceLocation GetLocationAndAdvance(this SourceLocationTracker self, string content)
+ {
+ SourceLocation ret = self.CurrentLocation;
+ self.UpdateLocation(content);
+ return ret;
+ }
+ }
+
+ public class SpanFactory
+ {
+ public Func<ITextDocument, ITokenizer> MarkupTokenizerFactory { get; set; }
+ public Func<ITextDocument, ITokenizer> CodeTokenizerFactory { get; set; }
+ public SourceLocationTracker LocationTracker { get; private set; }
+
+ public static SpanFactory CreateCsHtml()
+ {
+ return new SpanFactory()
+ {
+ MarkupTokenizerFactory = doc => new HtmlTokenizer(doc),
+ CodeTokenizerFactory = doc => new CSharpTokenizer(doc)
+ };
+ }
+
+ public static SpanFactory CreateVbHtml()
+ {
+ return new SpanFactory()
+ {
+ MarkupTokenizerFactory = doc => new HtmlTokenizer(doc),
+ CodeTokenizerFactory = doc => new VBTokenizer(doc)
+ };
+ }
+
+ public SpanFactory()
+ {
+ LocationTracker = new SourceLocationTracker();
+ }
+
+ public SpanConstructor Span(SpanKind kind, string content, CSharpSymbolType type)
+ {
+ return CreateSymbolSpan(kind, content, st => new CSharpSymbol(st, content, type));
+ }
+
+ public SpanConstructor Span(SpanKind kind, string content, VBSymbolType type)
+ {
+ return CreateSymbolSpan(kind, content, st => new VBSymbol(st, content, type));
+ }
+
+ public SpanConstructor Span(SpanKind kind, string content, HtmlSymbolType type)
+ {
+ return CreateSymbolSpan(kind, content, st => new HtmlSymbol(st, content, type));
+ }
+
+ public SpanConstructor Span(SpanKind kind, string content, bool markup)
+ {
+ return new SpanConstructor(kind, Tokenize(new[] { content }, markup));
+ }
+
+ public SpanConstructor Span(SpanKind kind, string[] content, bool markup)
+ {
+ return new SpanConstructor(kind, Tokenize(content, markup));
+ }
+
+ public SpanConstructor Span(SpanKind kind, params ISymbol[] symbols)
+ {
+ return new SpanConstructor(kind, symbols);
+ }
+
+ private SpanConstructor CreateSymbolSpan(SpanKind kind, string content, Func<SourceLocation, ISymbol> ctor)
+ {
+ SourceLocation start = LocationTracker.CurrentLocation;
+ LocationTracker.UpdateLocation(content);
+ return new SpanConstructor(kind, new[] { ctor(start) });
+ }
+
+ public void Reset()
+ {
+ LocationTracker.CurrentLocation = SourceLocation.Zero;
+ }
+
+ private IEnumerable<ISymbol> Tokenize(IEnumerable<string> contentFragments, bool markup)
+ {
+ return contentFragments.SelectMany(fragment => Tokenize(fragment, markup));
+ }
+
+ private IEnumerable<ISymbol> Tokenize(string content, bool markup)
+ {
+ ITokenizer tok = MakeTokenizer(markup, new SeekableTextReader(content));
+ ISymbol sym;
+ ISymbol last = null;
+ while ((sym = tok.NextSymbol()) != null)
+ {
+ OffsetStart(sym, LocationTracker.CurrentLocation);
+ last = sym;
+ yield return sym;
+ }
+ LocationTracker.UpdateLocation(content);
+ }
+
+ private ITokenizer MakeTokenizer(bool markup, SeekableTextReader seekableTextReader)
+ {
+ if (markup)
+ {
+ return MarkupTokenizerFactory(seekableTextReader);
+ }
+ else
+ {
+ return CodeTokenizerFactory(seekableTextReader);
+ }
+ }
+
+ private void OffsetStart(ISymbol sym, SourceLocation sourceLocation)
+ {
+ sym.OffsetStart(sourceLocation);
+ }
+ }
+
+ public static class SpanConstructorExtensions
+ {
+ public static SpanConstructor Accepts(this SpanConstructor self, AcceptedCharacters accepted)
+ {
+ return self.With(eh => eh.AcceptedCharacters = accepted);
+ }
+
+ public static SpanConstructor AutoCompleteWith(this SpanConstructor self, string autoCompleteString)
+ {
+ return AutoCompleteWith(self, autoCompleteString, atEndOfSpan: false);
+ }
+
+ public static SpanConstructor AutoCompleteWith(this SpanConstructor self, string autoCompleteString, bool atEndOfSpan)
+ {
+ return self.With(new AutoCompleteEditHandler(SpanConstructor.TestTokenizer) { AutoCompleteString = autoCompleteString, AutoCompleteAtEndOfSpan = atEndOfSpan });
+ }
+
+ public static SpanConstructor WithEditorHints(this SpanConstructor self, EditorHints hints)
+ {
+ return self.With(eh => eh.EditorHints = hints);
+ }
+ }
+
+ public class UnclassifiedCodeSpanConstructor
+ {
+ SpanConstructor _self;
+
+ public UnclassifiedCodeSpanConstructor(SpanConstructor self)
+ {
+ _self = self;
+ }
+
+ public SpanConstructor AsMetaCode()
+ {
+ _self.Builder.Kind = SpanKind.MetaCode;
+ return _self;
+ }
+
+ public SpanConstructor AsStatement()
+ {
+ return _self.With(new StatementCodeGenerator());
+ }
+
+ public SpanConstructor AsExpression()
+ {
+ return _self.With(new ExpressionCodeGenerator());
+ }
+
+ public SpanConstructor AsImplicitExpression(ISet<string> keywords)
+ {
+ return AsImplicitExpression(keywords, acceptTrailingDot: false);
+ }
+
+ public SpanConstructor AsImplicitExpression(ISet<string> keywords, bool acceptTrailingDot)
+ {
+ return _self.With(new ImplicitExpressionEditHandler(SpanConstructor.TestTokenizer, keywords, acceptTrailingDot))
+ .With(new ExpressionCodeGenerator());
+ }
+
+ public SpanConstructor AsFunctionsBody()
+ {
+ return _self.With(new TypeMemberCodeGenerator());
+ }
+
+ public SpanConstructor AsNamespaceImport(string ns, int namespaceKeywordLength)
+ {
+ return _self.With(new AddImportCodeGenerator(ns, namespaceKeywordLength));
+ }
+
+ public SpanConstructor Hidden()
+ {
+ return _self.With(SpanCodeGenerator.Null);
+ }
+
+ public SpanConstructor AsBaseType(string baseType)
+ {
+ return _self.With(new SetBaseTypeCodeGenerator(baseType));
+ }
+
+ public SpanConstructor AsRazorDirectiveAttribute(string key, string value)
+ {
+ return _self.With(new RazorDirectiveAttributeCodeGenerator(key, value));
+ }
+
+ public SpanConstructor As(ISpanCodeGenerator codeGenerator)
+ {
+ return _self.With(codeGenerator);
+ }
+ }
+
+ public class SpanConstructor
+ {
+ public SpanBuilder Builder { get; private set; }
+
+ internal static IEnumerable<ISymbol> TestTokenizer(string str)
+ {
+ yield return new RawTextSymbol(SourceLocation.Zero, str);
+ }
+
+ public SpanConstructor(SpanKind kind, IEnumerable<ISymbol> symbols)
+ {
+ Builder = new SpanBuilder();
+ Builder.Kind = kind;
+ Builder.EditHandler = SpanEditHandler.CreateDefault(TestTokenizer);
+ foreach (ISymbol sym in symbols)
+ {
+ Builder.Accept(sym);
+ }
+ }
+
+ private Span Build()
+ {
+ return Builder.Build();
+ }
+
+ public SpanConstructor With(ISpanCodeGenerator generator)
+ {
+ Builder.CodeGenerator = generator;
+ return this;
+ }
+
+ public SpanConstructor With(SpanEditHandler handler)
+ {
+ Builder.EditHandler = handler;
+ return this;
+ }
+
+ public SpanConstructor With(Action<ISpanCodeGenerator> generatorConfigurer)
+ {
+ generatorConfigurer(Builder.CodeGenerator);
+ return this;
+ }
+
+ public SpanConstructor With(Action<SpanEditHandler> handlerConfigurer)
+ {
+ handlerConfigurer(Builder.EditHandler);
+ return this;
+ }
+
+ public static implicit operator Span(SpanConstructor self)
+ {
+ return self.Build();
+ }
+
+ public SpanConstructor Hidden()
+ {
+ Builder.CodeGenerator = SpanCodeGenerator.Null;
+ return this;
+ }
+ }
+}
diff --git a/test/System.Web.Razor.Test/Framework/VBHtmlCodeParserTestBase.cs b/test/System.Web.Razor.Test/Framework/VBHtmlCodeParserTestBase.cs
new file mode 100644
index 00000000..65336ab7
--- /dev/null
+++ b/test/System.Web.Razor.Test/Framework/VBHtmlCodeParserTestBase.cs
@@ -0,0 +1,28 @@
+using System.Collections.Generic;
+using System.Web.Razor.Parser;
+
+namespace System.Web.Razor.Test.Framework
+{
+ public abstract class VBHtmlCodeParserTestBase : CodeParserTestBase
+ {
+ protected override ISet<string> KeywordSet
+ {
+ get { return VBCodeParser.DefaultKeywords; }
+ }
+
+ protected override SpanFactory CreateSpanFactory()
+ {
+ return SpanFactory.CreateVbHtml();
+ }
+
+ public override ParserBase CreateMarkupParser()
+ {
+ return new HtmlMarkupParser();
+ }
+
+ public override ParserBase CreateCodeParser()
+ {
+ return new VBCodeParser();
+ }
+ }
+}
diff --git a/test/System.Web.Razor.Test/Framework/VBHtmlMarkupParserTestBase.cs b/test/System.Web.Razor.Test/Framework/VBHtmlMarkupParserTestBase.cs
new file mode 100644
index 00000000..a336f048
--- /dev/null
+++ b/test/System.Web.Razor.Test/Framework/VBHtmlMarkupParserTestBase.cs
@@ -0,0 +1,28 @@
+using System.Collections.Generic;
+using System.Web.Razor.Parser;
+
+namespace System.Web.Razor.Test.Framework
+{
+ public abstract class VBHtmlMarkupParserTestBase : MarkupParserTestBase
+ {
+ protected override ISet<string> KeywordSet
+ {
+ get { return VBCodeParser.DefaultKeywords; }
+ }
+
+ protected override SpanFactory CreateSpanFactory()
+ {
+ return SpanFactory.CreateVbHtml();
+ }
+
+ public override ParserBase CreateMarkupParser()
+ {
+ return new HtmlMarkupParser();
+ }
+
+ public override ParserBase CreateCodeParser()
+ {
+ return new VBCodeParser();
+ }
+ }
+}
diff --git a/test/System.Web.Razor.Test/Generator/CSharpRazorCodeGeneratorTest.cs b/test/System.Web.Razor.Test/Generator/CSharpRazorCodeGeneratorTest.cs
new file mode 100644
index 00000000..996aee4c
--- /dev/null
+++ b/test/System.Web.Razor.Test/Generator/CSharpRazorCodeGeneratorTest.cs
@@ -0,0 +1,290 @@
+using System.Collections.Generic;
+using System.Web.Razor.Generator;
+using System.Web.WebPages.TestUtils;
+using Xunit;
+using Xunit.Extensions;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Razor.Test.Generator
+{
+ public class CSharpRazorCodeGeneratorTest : RazorCodeGeneratorTest<CSharpRazorCodeLanguage>
+ {
+ protected override string FileExtension
+ {
+ get { return "cshtml"; }
+ }
+
+ protected override string LanguageName
+ {
+ get { return "CS"; }
+ }
+
+ protected override string BaselineExtension
+ {
+ get { return "cs"; }
+ }
+
+ private const string TestPhysicalPath = @"C:\Bar.cshtml";
+ private const string TestVirtualPath = "~/Foo/Bar.cshtml";
+
+ [Fact]
+ public void ConstructorRequiresNonNullClassName()
+ {
+ Assert.ThrowsArgumentNullOrEmptyString(() => new CSharpRazorCodeGenerator(null, TestRootNamespaceName, TestPhysicalPath, CreateHost()), "className");
+ }
+
+ [Fact]
+ public void ConstructorRequiresNonEmptyClassName()
+ {
+ Assert.ThrowsArgumentNullOrEmptyString(() => new CSharpRazorCodeGenerator(String.Empty, TestRootNamespaceName, TestPhysicalPath, CreateHost()), "className");
+ }
+
+ [Fact]
+ public void ConstructorRequiresNonNullRootNamespaceName()
+ {
+ Assert.ThrowsArgumentNull(() => new CSharpRazorCodeGenerator("Foo", null, TestPhysicalPath, CreateHost()), "rootNamespaceName");
+ }
+
+ [Fact]
+ public void ConstructorAllowsEmptyRootNamespaceName()
+ {
+ new CSharpRazorCodeGenerator("Foo", String.Empty, TestPhysicalPath, CreateHost());
+ }
+
+ [Fact]
+ public void ConstructorRequiresNonNullHost()
+ {
+ Assert.ThrowsArgumentNull(() => new CSharpRazorCodeGenerator("Foo", TestRootNamespaceName, TestPhysicalPath, null), "host");
+ }
+
+ [Theory]
+ [InlineData("NestedCodeBlocks")]
+ [InlineData("CodeBlock")]
+ [InlineData("ExplicitExpression")]
+ [InlineData("MarkupInCodeBlock")]
+ [InlineData("Blocks")]
+ [InlineData("ImplicitExpression")]
+ [InlineData("Imports")]
+ [InlineData("ExpressionsInCode")]
+ [InlineData("FunctionsBlock")]
+ [InlineData("Templates")]
+ [InlineData("Sections")]
+ [InlineData("RazorComments")]
+ [InlineData("Helpers")]
+ [InlineData("HelpersMissingCloseParen")]
+ [InlineData("HelpersMissingOpenBrace")]
+ [InlineData("HelpersMissingOpenParen")]
+ [InlineData("NestedHelpers")]
+ [InlineData("InlineBlocks")]
+ [InlineData("NestedHelpers")]
+ [InlineData("LayoutDirective")]
+ [InlineData("ConditionalAttributes")]
+ [InlineData("ResolveUrl")]
+ public void CSharpCodeGeneratorCorrectlyGeneratesRunTimeCode(string testType)
+ {
+ RunTest(testType);
+ }
+
+ //// To regenerate individual baselines, uncomment this and set the appropriate test name in the Inline Data.
+ //// Please comment out again after regenerating.
+ //// TODO: Remove this when we go to a Source Control system that doesn't lock files, thus requiring we unlock them to regenerate them :(
+ //[Theory]
+ //[InlineData("ConditionalAttributes")]
+ //public void CSharpCodeGeneratorCorrectlyGeneratesRunTimeCode2(string testType)
+ //{
+ // RunTest(testType);
+ //}
+
+ [Fact]
+ public void CSharpCodeGeneratorCorrectlyGeneratesMappingsForRazorCommentsAtDesignTime()
+ {
+ RunTest("RazorComments", "RazorComments.DesignTime", designTimeMode: true, expectedDesignTimePragmas: new List<GeneratedCodeMapping>()
+ {
+ /* 01 */ new GeneratedCodeMapping(4, 3, 3, 6),
+ /* 02 */ new GeneratedCodeMapping(5, 40, 39, 22),
+ /* 03 */ new GeneratedCodeMapping(6, 50, 49, 58),
+ /* 04 */ new GeneratedCodeMapping(12, 3, 3, 24),
+ /* 05 */ new GeneratedCodeMapping(13, 46, 46, 3),
+ /* 06 */ new GeneratedCodeMapping(15, 3, 7, 1),
+ /* 07 */ new GeneratedCodeMapping(15, 8, 8, 1)
+ });
+ }
+
+ [Fact]
+ public void CSharpCodeGeneratorCorrectlyGeneratesImportStatementsAtDesignTime()
+ {
+ RunTest("Imports", "Imports.DesignTime", designTimeMode: true, expectedDesignTimePragmas: new List<GeneratedCodeMapping>()
+ {
+ /* 01 */ new GeneratedCodeMapping(1, 2, 1, 15),
+ /* 02 */ new GeneratedCodeMapping(2, 2, 1, 32),
+ /* 03 */ new GeneratedCodeMapping(3, 2, 1, 12),
+ /* 04 */ new GeneratedCodeMapping(5, 30, 30, 21),
+ /* 05 */ new GeneratedCodeMapping(6, 36, 36, 20),
+ });
+ }
+
+ [Fact]
+ public void CSharpCodeGeneratorCorrectlyGeneratesFunctionsBlocksAtDesignTime()
+ {
+ RunTest("FunctionsBlock", "FunctionsBlock.DesignTime", designTimeMode: true, expectedDesignTimePragmas: new List<GeneratedCodeMapping>()
+ {
+ /* 01 */ new GeneratedCodeMapping(1, 13, 13, 4),
+ /* 02 */ new GeneratedCodeMapping(5, 13, 13, 104),
+ /* 03 */ new GeneratedCodeMapping(12, 26, 26, 11)
+ });
+ }
+
+ [Fact]
+ public void CSharpCodeGeneratorCorrectlyGeneratesHiddenSpansWithinCode()
+ {
+ RunTest("HiddenSpansInCode", designTimeMode: true, expectedDesignTimePragmas: new List<GeneratedCodeMapping>
+ {
+ /* 01 */ new GeneratedCodeMapping(1, 3, 3, 6),
+ /* 02 */ new GeneratedCodeMapping(2, 6, 6, 5)
+ });
+ }
+
+ [Fact]
+ public void CSharpCodeGeneratorGeneratesCodeWithParserErrorsInDesignTimeMode()
+ {
+ RunTest("ParserError", designTimeMode: true, expectedDesignTimePragmas: new List<GeneratedCodeMapping>()
+ {
+ /* 01 */ new GeneratedCodeMapping(1, 3, 3, 31)
+ });
+ }
+
+ [Fact]
+ public void CSharpCodeGeneratorCorrectlyGeneratesInheritsAtRuntime()
+ {
+ RunTest("Inherits", baselineName: "Inherits.Runtime");
+ }
+
+ [Fact]
+ public void CSharpCodeGeneratorCorrectlyGeneratesInheritsAtDesigntime()
+ {
+ RunTest("Inherits", baselineName: "Inherits.Designtime", designTimeMode: true, expectedDesignTimePragmas: new List<GeneratedCodeMapping>()
+ {
+ /* 01 */ new GeneratedCodeMapping(1, 2, 7, 5),
+ /* 02 */ new GeneratedCodeMapping(3, 11, 11, 25),
+ });
+ }
+
+ [Fact]
+ public void CSharpCodeGeneratorCorrectlyGeneratesDesignTimePragmasForUnfinishedExpressionsInCode()
+ {
+ RunTest("UnfinishedExpressionInCode", designTimeMode: true, expectedDesignTimePragmas: new List<GeneratedCodeMapping>()
+ {
+ /* 01 */ new GeneratedCodeMapping(1, 3, 3, 2),
+ /* 02 */ new GeneratedCodeMapping(2, 2, 7, 9),
+ /* 03 */ new GeneratedCodeMapping(2, 11, 11, 2)
+ });
+ }
+
+ [Fact]
+ public void CSharpCodeGeneratorCorrectlyGeneratesDesignTimePragmasMarkupAndExpressions()
+ {
+ RunTest("DesignTime", designTimeMode: true, expectedDesignTimePragmas: new List<GeneratedCodeMapping>()
+ {
+ /* 01 */ new GeneratedCodeMapping(2, 14, 13, 36),
+ /* 02 */ new GeneratedCodeMapping(3, 23, 23, 1),
+ /* 03 */ new GeneratedCodeMapping(3, 28, 28, 15),
+ /* 04 */ new GeneratedCodeMapping(8, 3, 7, 12),
+ /* 05 */ new GeneratedCodeMapping(9, 2, 7, 4),
+ /* 06 */ new GeneratedCodeMapping(9, 15, 15, 3),
+ /* 07 */ new GeneratedCodeMapping(9, 26, 26, 1),
+ /* 08 */ new GeneratedCodeMapping(14, 6, 7, 3),
+ /* 09 */ new GeneratedCodeMapping(17, 9, 24, 7),
+ /* 10 */ new GeneratedCodeMapping(17, 16, 16, 26),
+ /* 11 */ new GeneratedCodeMapping(19, 19, 19, 9),
+ /* 12 */ new GeneratedCodeMapping(21, 1, 1, 1)
+ });
+ }
+
+ [Fact]
+ public void CSharpCodeGeneratorCorrectlyGeneratesDesignTimePragmasForImplicitExpressionStartedAtEOF()
+ {
+ RunTest("ImplicitExpressionAtEOF", designTimeMode: true, expectedDesignTimePragmas: new List<GeneratedCodeMapping>()
+ {
+ /* 01 */ new GeneratedCodeMapping(3, 2, 7, 0)
+ });
+ }
+
+ [Fact]
+ public void CSharpCodeGeneratorCorrectlyGeneratesDesignTimePragmasForExplicitExpressionStartedAtEOF()
+ {
+ RunTest("ExplicitExpressionAtEOF", designTimeMode: true, expectedDesignTimePragmas: new List<GeneratedCodeMapping>()
+ {
+ /* 01 */ new GeneratedCodeMapping(3, 3, 7, 0)
+ });
+ }
+
+ [Fact]
+ public void CSharpCodeGeneratorCorrectlyGeneratesDesignTimePragmasForCodeBlockStartedAtEOF()
+ {
+ RunTest("CodeBlockAtEOF", designTimeMode: true, expectedDesignTimePragmas: new List<GeneratedCodeMapping>()
+ {
+ /* 01 */ new GeneratedCodeMapping(1, 3, 3, 0)
+ });
+ }
+
+ [Fact]
+ public void CSharpCodeGeneratorCorrectlyGeneratesDesignTimePragmasForEmptyImplicitExpression()
+ {
+ RunTest("EmptyImplicitExpression", designTimeMode: true, expectedDesignTimePragmas: new List<GeneratedCodeMapping>()
+ {
+ /* 01 */ new GeneratedCodeMapping(3, 2, 7, 0)
+ });
+ }
+
+ [Fact]
+ public void CSharpCodeGeneratorCorrectlyGeneratesDesignTimePragmasForEmptyImplicitExpressionInCode()
+ {
+ RunTest("EmptyImplicitExpressionInCode", designTimeMode: true, expectedDesignTimePragmas: new List<GeneratedCodeMapping>()
+ {
+ /* 01 */ new GeneratedCodeMapping(1, 3, 3, 6),
+ /* 02 */ new GeneratedCodeMapping(2, 6, 7, 0),
+ /* 03 */ new GeneratedCodeMapping(2, 6, 6, 2)
+ });
+ }
+
+ [Fact]
+ public void CSharpCodeGeneratorCorrectlyGeneratesDesignTimePragmasForEmptyExplicitExpression()
+ {
+ RunTest("EmptyExplicitExpression", designTimeMode: true, expectedDesignTimePragmas: new List<GeneratedCodeMapping>()
+ {
+ /* 01 */ new GeneratedCodeMapping(3, 3, 7, 0)
+ });
+ }
+
+ [Fact]
+ public void CSharpCodeGeneratorCorrectlyGeneratesDesignTimePragmasForEmptyCodeBlock()
+ {
+ RunTest("EmptyCodeBlock", designTimeMode: true, expectedDesignTimePragmas: new List<GeneratedCodeMapping>()
+ {
+ /* 01 */ new GeneratedCodeMapping(3, 3, 3, 0)
+ });
+ }
+
+ [Fact]
+ public void CSharpCodeGeneratorDoesNotRenderLinePragmasIfGenerateLinePragmasIsSetToFalse()
+ {
+ RunTest("NoLinePragmas", generatePragmas: false);
+ }
+
+ [Fact]
+ public void CSharpCodeGeneratorRendersHelpersBlockCorrectlyWhenInstanceHelperRequested()
+ {
+ RunTest("Helpers", baselineName: "Helpers.Instance", hostConfig: h => h.StaticHelpers = false);
+ }
+
+ [Fact]
+ public void CSharpCodeGeneratorCorrectlyInstrumentsRazorCodeWhenInstrumentationRequested()
+ {
+ RunTest("Instrumented", hostConfig: host =>
+ {
+ host.EnableInstrumentation = true;
+ host.InstrumentedSourceFilePath = String.Format("~/{0}.cshtml", host.DefaultClassName);
+ });
+ }
+ }
+}
diff --git a/test/System.Web.Razor.Test/Generator/GeneratedCodeMappingTest.cs b/test/System.Web.Razor.Test/Generator/GeneratedCodeMappingTest.cs
new file mode 100644
index 00000000..b0abdef4
--- /dev/null
+++ b/test/System.Web.Razor.Test/Generator/GeneratedCodeMappingTest.cs
@@ -0,0 +1,63 @@
+using System.Web.Razor.Generator;
+using Xunit;
+
+namespace System.Web.Razor.Test.Generator
+{
+ public class GeneratedCodeMappingTest
+ {
+ [Fact]
+ public void GeneratedCodeMappingsAreEqualIfDataIsEqual()
+ {
+ GeneratedCodeMapping left = new GeneratedCodeMapping(12, 34, 56, 78);
+ GeneratedCodeMapping right = new GeneratedCodeMapping(12, 34, 56, 78);
+ Assert.True(left == right);
+ Assert.True(left.Equals(right));
+ Assert.True(right.Equals(left));
+ Assert.True(Equals(left, right));
+ }
+
+ [Fact]
+ public void GeneratedCodeMappingsAreNotEqualIfCodeLengthIsNotEqual()
+ {
+ GeneratedCodeMapping left = new GeneratedCodeMapping(12, 34, 56, 87);
+ GeneratedCodeMapping right = new GeneratedCodeMapping(12, 34, 56, 78);
+ Assert.False(left == right);
+ Assert.False(left.Equals(right));
+ Assert.False(right.Equals(left));
+ Assert.False(Equals(left, right));
+ }
+
+ [Fact]
+ public void GeneratedCodeMappingsAreNotEqualIfStartGeneratedColumnIsNotEqual()
+ {
+ GeneratedCodeMapping left = new GeneratedCodeMapping(12, 34, 56, 87);
+ GeneratedCodeMapping right = new GeneratedCodeMapping(12, 34, 65, 87);
+ Assert.False(left == right);
+ Assert.False(left.Equals(right));
+ Assert.False(right.Equals(left));
+ Assert.False(Equals(left, right));
+ }
+
+ [Fact]
+ public void GeneratedCodeMappingsAreNotEqualIfStartColumnIsNotEqual()
+ {
+ GeneratedCodeMapping left = new GeneratedCodeMapping(12, 34, 56, 87);
+ GeneratedCodeMapping right = new GeneratedCodeMapping(12, 43, 56, 87);
+ Assert.False(left == right);
+ Assert.False(left.Equals(right));
+ Assert.False(right.Equals(left));
+ Assert.False(Equals(left, right));
+ }
+
+ [Fact]
+ public void GeneratedCodeMappingsAreNotEqualIfStartLineIsNotEqual()
+ {
+ GeneratedCodeMapping left = new GeneratedCodeMapping(12, 34, 56, 87);
+ GeneratedCodeMapping right = new GeneratedCodeMapping(21, 34, 56, 87);
+ Assert.False(left == right);
+ Assert.False(left.Equals(right));
+ Assert.False(right.Equals(left));
+ Assert.False(Equals(left, right));
+ }
+ }
+} \ No newline at end of file
diff --git a/test/System.Web.Razor.Test/Generator/RazorCodeGeneratorTest.cs b/test/System.Web.Razor.Test/Generator/RazorCodeGeneratorTest.cs
new file mode 100644
index 00000000..0a130d95
--- /dev/null
+++ b/test/System.Web.Razor.Test/Generator/RazorCodeGeneratorTest.cs
@@ -0,0 +1,146 @@
+//#define GENERATE_BASELINES
+
+using System.CodeDom;
+using System.CodeDom.Compiler;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Web.Razor.Generator;
+using System.Web.Razor.Test.Utils;
+using System.Web.WebPages.TestUtils;
+using Xunit;
+
+namespace System.Web.Razor.Test.Generator
+{
+ public abstract class RazorCodeGeneratorTest<TLanguage>
+ where TLanguage : RazorCodeLanguage, new()
+ {
+ protected static readonly string TestRootNamespaceName = "TestOutput";
+
+ protected abstract string FileExtension { get; }
+ protected abstract string LanguageName { get; }
+ protected abstract string BaselineExtension { get; }
+
+ protected RazorEngineHost CreateHost()
+ {
+ return new RazorEngineHost(new TLanguage());
+ }
+
+ protected void RunTest(string name, string baselineName = null, bool generatePragmas = true, bool designTimeMode = false, IList<GeneratedCodeMapping> expectedDesignTimePragmas = null, Action<RazorEngineHost> hostConfig = null)
+ {
+ // Load the test files
+ if (baselineName == null)
+ {
+ baselineName = name;
+ }
+ string source = TestFile.Create(String.Format("CodeGenerator.{1}.Source.{0}.{2}", name, LanguageName, FileExtension)).ReadAllText();
+ string expectedOutput = TestFile.Create(String.Format("CodeGenerator.{1}.Output.{0}.{2}", baselineName, LanguageName, BaselineExtension)).ReadAllText();
+
+ // Set up the host and engine
+ RazorEngineHost host = CreateHost();
+ host.NamespaceImports.Add("System");
+ host.DesignTimeMode = designTimeMode;
+ host.StaticHelpers = true;
+ host.DefaultClassName = name;
+
+ // Add support for templates, etc.
+ host.GeneratedClassContext = new GeneratedClassContext(GeneratedClassContext.DefaultExecuteMethodName,
+ GeneratedClassContext.DefaultWriteMethodName,
+ GeneratedClassContext.DefaultWriteLiteralMethodName,
+ "WriteTo",
+ "WriteLiteralTo",
+ "Template",
+ "DefineSection",
+ "BeginContext",
+ "EndContext")
+ {
+ LayoutPropertyName = "Layout",
+ ResolveUrlMethodName = "Href"
+ };
+ if (hostConfig != null)
+ {
+ hostConfig(host);
+ }
+
+ RazorTemplateEngine engine = new RazorTemplateEngine(host);
+
+ // Generate code for the file
+ GeneratorResults results = null;
+ using (StringTextBuffer buffer = new StringTextBuffer(source))
+ {
+ results = engine.GenerateCode(buffer, className: name, rootNamespace: TestRootNamespaceName, sourceFileName: generatePragmas ? String.Format("{0}.{1}", name, FileExtension) : null);
+ }
+
+ // Generate code
+ CodeCompileUnit ccu = results.GeneratedCode;
+ CodeDomProvider codeProvider = (CodeDomProvider)Activator.CreateInstance(host.CodeLanguage.CodeDomProviderType);
+
+ CodeGeneratorOptions options = new CodeGeneratorOptions();
+
+ // Both run-time and design-time use these settings. See:
+ // * $/Dev10/pu/SP_WebTools/venus/html/Razor/Impl/RazorCodeGenerator.cs:204
+ // * $/Dev10/Releases/RTMRel/ndp/fx/src/xsp/System/Web/Compilation/BuildManagerHost.cs:373
+ options.BlankLinesBetweenMembers = false;
+ options.IndentString = String.Empty;
+
+ StringBuilder output = new StringBuilder();
+ using (StringWriter writer = new StringWriter(output))
+ {
+ codeProvider.GenerateCodeFromCompileUnit(ccu, writer, options);
+ }
+
+ WriteBaseline(String.Format(@"test\System.Web.Razor.Test\TestFiles\CodeGenerator\{0}\Output\{1}.{2}", LanguageName, baselineName, BaselineExtension), MiscUtils.StripRuntimeVersion(output.ToString()));
+
+ // Verify code against baseline
+#if !GENERATE_BASELINES
+ Assert.Equal(expectedOutput, MiscUtils.StripRuntimeVersion(output.ToString()));
+#endif
+
+ // Verify design-time pragmas
+ if (designTimeMode)
+ {
+ Assert.True(expectedDesignTimePragmas != null || results.DesignTimeLineMappings == null || results.DesignTimeLineMappings.Count == 0);
+ Assert.True(expectedDesignTimePragmas == null || (results.DesignTimeLineMappings != null && results.DesignTimeLineMappings.Count > 0));
+ if (expectedDesignTimePragmas != null)
+ {
+ Assert.Equal(
+ expectedDesignTimePragmas.ToArray(),
+ results.DesignTimeLineMappings
+ .OrderBy(p => p.Key)
+ .Select(p => p.Value)
+ .ToArray());
+ }
+ }
+ }
+
+ [Conditional("GENERATE_BASELINES")]
+ private void WriteBaseline(string baselineFile, string output)
+ {
+ string root = RecursiveFind("Runtime.sln", Path.GetFullPath("."));
+ string baselinePath = Path.Combine(root, baselineFile);
+
+ // Update baseline
+ // IMPORTANT! Replace this path with the local path on your machine to the baseline files!
+ if (File.Exists(baselinePath))
+ {
+ File.Delete(baselinePath);
+ }
+ File.WriteAllText(baselinePath, output.ToString());
+ }
+
+ private string RecursiveFind(string path, string start)
+ {
+ string test = Path.Combine(start, path);
+ if (File.Exists(test))
+ {
+ return start;
+ }
+ else
+ {
+ return RecursiveFind(path, new DirectoryInfo(start).Parent.FullName);
+ }
+ }
+ }
+}
diff --git a/test/System.Web.Razor.Test/Generator/VBRazorCodeGeneratorTest.cs b/test/System.Web.Razor.Test/Generator/VBRazorCodeGeneratorTest.cs
new file mode 100644
index 00000000..debdd8d3
--- /dev/null
+++ b/test/System.Web.Razor.Test/Generator/VBRazorCodeGeneratorTest.cs
@@ -0,0 +1,279 @@
+using System.Collections.Generic;
+using System.Web.Razor.Generator;
+using System.Web.WebPages.TestUtils;
+using Xunit;
+using Xunit.Extensions;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Razor.Test.Generator
+{
+ public class VBRazorCodeGeneratorTest : RazorCodeGeneratorTest<VBRazorCodeLanguage>
+ {
+ private const string TestPhysicalPath = @"C:\Bar.vbhtml";
+ private const string TestVirtualPath = "~/Foo/Bar.vbhtml";
+
+ protected override string FileExtension
+ {
+ get { return "vbhtml"; }
+ }
+
+ protected override string LanguageName
+ {
+ get { return "VB"; }
+ }
+
+ protected override string BaselineExtension
+ {
+ get { return "vb"; }
+ }
+
+ [Fact]
+ public void ConstructorRequiresNonNullClassName()
+ {
+ Assert.ThrowsArgumentNullOrEmptyString(() => new VBRazorCodeGenerator(null, TestRootNamespaceName, TestPhysicalPath, CreateHost()), "className");
+ }
+
+ [Fact]
+ public void ConstructorRequiresNonEmptyClassName()
+ {
+ Assert.ThrowsArgumentNullOrEmptyString(() => new VBRazorCodeGenerator(String.Empty, TestRootNamespaceName, TestPhysicalPath, CreateHost()), "className");
+ }
+
+ [Fact]
+ public void ConstructorRequiresNonNullRootNamespaceName()
+ {
+ Assert.ThrowsArgumentNull(() => new VBRazorCodeGenerator("Foo", null, TestPhysicalPath, CreateHost()), "rootNamespaceName");
+ }
+
+ [Fact]
+ public void ConstructorAllowsEmptyRootNamespaceName()
+ {
+ new VBRazorCodeGenerator("Foo", String.Empty, TestPhysicalPath, CreateHost());
+ }
+
+ [Fact]
+ public void ConstructorRequiresNonNullHost()
+ {
+ Assert.ThrowsArgumentNull(() => new VBRazorCodeGenerator("Foo", TestRootNamespaceName, TestPhysicalPath, null), "host");
+ }
+
+ [Theory]
+ [InlineData("NestedCodeBlocks")]
+ [InlineData("NestedCodeBlocks")]
+ [InlineData("CodeBlock")]
+ [InlineData("ExplicitExpression")]
+ [InlineData("MarkupInCodeBlock")]
+ [InlineData("Blocks")]
+ [InlineData("ImplicitExpression")]
+ [InlineData("Imports")]
+ [InlineData("ExpressionsInCode")]
+ [InlineData("FunctionsBlock")]
+ [InlineData("Options")]
+ [InlineData("Templates")]
+ [InlineData("RazorComments")]
+ [InlineData("Sections")]
+ [InlineData("Helpers")]
+ [InlineData("HelpersMissingCloseParen")]
+ [InlineData("HelpersMissingOpenParen")]
+ [InlineData("NestedHelpers")]
+ [InlineData("LayoutDirective")]
+ [InlineData("ConditionalAttributes")]
+ [InlineData("ResolveUrl")]
+ public void VBCodeGeneratorCorrectlyGeneratesRunTimeCode(string testName)
+ {
+ RunTest(testName);
+ }
+
+ //// To regenerate individual baselines, uncomment this and set the appropriate test name in the Inline Data.
+ //// Please comment out again after regenerating.
+ //// TODO: Remove this when we go to a Source Control system that doesn't lock files, thus requiring we unlock them to regenerate them :(
+ //[Theory]
+ //[InlineData("RazorComments")]
+ //public void VBCodeGeneratorCorrectlyGeneratesRunTimeCode2(string testType)
+ //{
+ // RunTest(testType);
+ //}
+
+ [Fact]
+ public void VBCodeGeneratorCorrectlyGeneratesMappingsForRazorCommentsAtDesignTime()
+ {
+ // (4, 6) -> (?, 6) [6]
+ // ( 5, 40) -> (?, 39) [2]
+ // ( 8, 6) -> (?, 6) [33]
+ // ( 9, 46) -> (?, 46) [3]
+ // ( 12, 3) -> (?, 7) [3]
+ // ( 12, 8) -> (?, 8) [1]
+ RunTest("RazorComments", "RazorComments.DesignTime", designTimeMode: true, expectedDesignTimePragmas: new List<GeneratedCodeMapping>()
+ {
+ /* 01 */ new GeneratedCodeMapping(4, 6, 6, 6),
+ /* 02 */ new GeneratedCodeMapping(5, 40, 39, 2),
+ /* 03 */ new GeneratedCodeMapping(8, 6, 6, 33),
+ /* 04 */ new GeneratedCodeMapping(9, 46, 46, 3),
+ /* 05 */ new GeneratedCodeMapping(12, 3, 7, 1),
+ /* 06 */ new GeneratedCodeMapping(12, 8, 8, 1)
+ });
+ }
+
+ [Fact]
+ public void VBCodeGeneratorCorrectlyGeneratesHelperMissingNameAtDesignTime()
+ {
+ RunTest("HelpersMissingName", designTimeMode: true);
+ }
+
+ [Fact]
+ public void VBCodeGeneratorCorrectlyGeneratesImportStatementsAtDesignTimeButCannotWrapPragmasAroundImportStatement()
+ {
+ RunTest("Imports", "Imports.DesignTime", designTimeMode: true, expectedDesignTimePragmas: new List<GeneratedCodeMapping>()
+ {
+ /* 01 */ new GeneratedCodeMapping(1, 2, 1, 19),
+ /* 02 */ new GeneratedCodeMapping(2, 2, 1, 36),
+ /* 03 */ new GeneratedCodeMapping(3, 2, 1, 16),
+ /* 04 */ new GeneratedCodeMapping(5, 30, 30, 22),
+ /* 05 */ new GeneratedCodeMapping(6, 36, 36, 21),
+ });
+ }
+
+ [Fact]
+ public void VBCodeGeneratorCorrectlyGeneratesFunctionsBlocksAtDesignTime()
+ {
+ RunTest("FunctionsBlock", "FunctionsBlock.DesignTime", designTimeMode: true, expectedDesignTimePragmas: new List<GeneratedCodeMapping>()
+ {
+ /* 01 */ new GeneratedCodeMapping(1, 11, 11, 4),
+ /* 02 */ new GeneratedCodeMapping(5, 11, 11, 129),
+ /* 03 */ new GeneratedCodeMapping(12, 26, 26, 11)
+ });
+ }
+
+ [Fact]
+ public void VBCodeGeneratorGeneratesCodeWithParserErrorsInDesignTimeMode()
+ {
+ RunTest("ParserError", designTimeMode: true, expectedDesignTimePragmas: new List<GeneratedCodeMapping>()
+ {
+ /* 01 */ new GeneratedCodeMapping(1, 6, 6, 16)
+ });
+ }
+
+ [Fact]
+ public void VBCodeGeneratorCorrectlyGeneratesInheritsAtRuntime()
+ {
+ RunTest("Inherits", baselineName: "Inherits.Runtime");
+ }
+
+ [Fact]
+ public void VBCodeGeneratorCorrectlyGeneratesInheritsAtDesigntime()
+ {
+ RunTest("Inherits", baselineName: "Inherits.Designtime", designTimeMode: true, expectedDesignTimePragmas: new List<GeneratedCodeMapping>()
+ {
+ /* 01 */ new GeneratedCodeMapping(1, 11, 25, 27)
+ });
+ }
+
+ [Fact]
+ public void VBCodeGeneratorCorrectlyGeneratesDesignTimePragmasForUnfinishedExpressionsInCode()
+ {
+ RunTest("UnfinishedExpressionInCode", designTimeMode: true, expectedDesignTimePragmas: new List<GeneratedCodeMapping>()
+ {
+ /* 01 */ new GeneratedCodeMapping(1, 6, 6, 2),
+ /* 02 */ new GeneratedCodeMapping(2, 2, 7, 9),
+ /* 03 */ new GeneratedCodeMapping(2, 11, 11, 2)
+ });
+ }
+
+ [Fact]
+ public void VBCodeGeneratorCorrectlyGeneratesDesignTimePragmasMarkupAndExpressions()
+ {
+ RunTest("DesignTime", designTimeMode: true, expectedDesignTimePragmas: new List<GeneratedCodeMapping>()
+ {
+ /* 01 */ new GeneratedCodeMapping(2, 14, 13, 17),
+ /* 02 */ new GeneratedCodeMapping(3, 20, 20, 1),
+ /* 03 */ new GeneratedCodeMapping(3, 25, 25, 20),
+ /* 04 */ new GeneratedCodeMapping(8, 3, 7, 12),
+ /* 05 */ new GeneratedCodeMapping(9, 2, 7, 4),
+ /* 06 */ new GeneratedCodeMapping(9, 16, 16, 3),
+ /* 07 */ new GeneratedCodeMapping(9, 27, 27, 1),
+ /* 08 */ new GeneratedCodeMapping(14, 6, 7, 3),
+ /* 09 */ new GeneratedCodeMapping(17, 9, 24, 5),
+ /* 10 */ new GeneratedCodeMapping(17, 14, 14, 28),
+ /* 11 */ new GeneratedCodeMapping(19, 20, 20, 14)
+ });
+ }
+
+ [Fact]
+ public void VBCodeGeneratorCorrectlyGeneratesDesignTimePragmasForImplicitExpressionStartedAtEOF()
+ {
+ RunTest("ImplicitExpressionAtEOF", designTimeMode: true, expectedDesignTimePragmas: new List<GeneratedCodeMapping>()
+ {
+ /* 01 */ new GeneratedCodeMapping(3, 2, 7, 0)
+ });
+ }
+
+ [Fact]
+ public void VBCodeGeneratorCorrectlyGeneratesDesignTimePragmasForExplicitExpressionStartedAtEOF()
+ {
+ RunTest("ExplicitExpressionAtEOF", designTimeMode: true, expectedDesignTimePragmas: new List<GeneratedCodeMapping>()
+ {
+ /* 01 */ new GeneratedCodeMapping(3, 3, 7, 0)
+ });
+ }
+
+ [Fact]
+ public void VBCodeGeneratorCorrectlyGeneratesDesignTimePragmasForCodeBlockStartedAtEOF()
+ {
+ RunTest("CodeBlockAtEOF", designTimeMode: true, expectedDesignTimePragmas: new List<GeneratedCodeMapping>()
+ {
+ /* 01 */ new GeneratedCodeMapping(3, 6, 6, 0)
+ });
+ }
+
+ [Fact]
+ public void VBCodeGeneratorCorrectlyGeneratesDesignTimePragmasForEmptyImplicitExpression()
+ {
+ RunTest("EmptyImplicitExpression", designTimeMode: true, expectedDesignTimePragmas: new List<GeneratedCodeMapping>()
+ {
+ /* 01 */ new GeneratedCodeMapping(3, 2, 7, 0)
+ });
+ }
+
+ [Fact]
+ public void VBCodeGeneratorCorrectlyGeneratesDesignTimePragmasForEmptyImplicitExpressionInCode()
+ {
+ RunTest("EmptyImplicitExpressionInCode", designTimeMode: true, expectedDesignTimePragmas: new List<GeneratedCodeMapping>()
+ {
+ /* 01 */ new GeneratedCodeMapping(1, 6, 6, 6),
+ /* 02 */ new GeneratedCodeMapping(2, 6, 7, 0),
+ /* 03 */ new GeneratedCodeMapping(2, 6, 6, 2)
+ });
+ }
+
+ [Fact]
+ public void VBCodeGeneratorCorrectlyGeneratesDesignTimePragmasForEmptyExplicitExpression()
+ {
+ RunTest("EmptyExplicitExpression", designTimeMode: true, expectedDesignTimePragmas: new List<GeneratedCodeMapping>()
+ {
+ /* 01 */ new GeneratedCodeMapping(3, 3, 7, 0)
+ });
+ }
+
+ [Fact]
+ public void VBCodeGeneratorDoesNotRenderLinePragmasIfGenerateLinePragmasIsSetToFalse()
+ {
+ RunTest("NoLinePragmas", generatePragmas: false);
+ }
+
+ [Fact]
+ public void VBCodeGeneratorRendersHelpersBlockCorrectlyWhenInstanceHelperRequested()
+ {
+ RunTest("Helpers", baselineName: "Helpers.Instance", hostConfig: h => h.StaticHelpers = false);
+ }
+
+ [Fact]
+ public void VBCodeGeneratorCorrectlyInstrumentsRazorCodeWhenInstrumentationRequested()
+ {
+ RunTest("Instrumented", hostConfig: host =>
+ {
+ host.EnableInstrumentation = true;
+ host.InstrumentedSourceFilePath = String.Format("~/{0}.vbhtml", host.DefaultClassName);
+ });
+ }
+ }
+}
diff --git a/test/System.Web.Razor.Test/Parser/BlockTest.cs b/test/System.Web.Razor.Test/Parser/BlockTest.cs
new file mode 100644
index 00000000..92561fa0
--- /dev/null
+++ b/test/System.Web.Razor.Test/Parser/BlockTest.cs
@@ -0,0 +1,122 @@
+using System.Linq;
+using System.Web.Razor.Generator;
+using System.Web.Razor.Parser.SyntaxTree;
+using System.Web.Razor.Test.Framework;
+using System.Web.Razor.Text;
+using System.Web.WebPages.TestUtils;
+using Xunit;
+
+namespace System.Web.Razor.Test.Parser
+{
+ public class BlockTest
+ {
+ [Fact]
+ public void ConstructorWithBlockBuilderSetsParent()
+ {
+ // Arrange
+ BlockBuilder builder = new BlockBuilder() { Type = BlockType.Comment };
+ Span span = new SpanBuilder() { Kind = SpanKind.Code }.Build();
+ builder.Children.Add(span);
+
+ // Act
+ Block block = builder.Build();
+
+ // Assert
+ Assert.Same(block, span.Parent);
+ }
+
+ [Fact]
+ public void ConstructorCopiesBasicValuesFromBlockBuilder()
+ {
+ // Arrange
+ BlockBuilder builder = new BlockBuilder()
+ {
+ Name = "Foo",
+ Type = BlockType.Helper
+ };
+
+ // Act
+ Block actual = builder.Build();
+
+ // Assert
+ Assert.Equal("Foo", actual.Name);
+ Assert.Equal(BlockType.Helper, actual.Type);
+ }
+
+ [Fact]
+ public void ConstructorTransfersInstanceOfCodeGeneratorFromBlockBuilder()
+ {
+ // Arrange
+ IBlockCodeGenerator expected = new ExpressionCodeGenerator();
+ BlockBuilder builder = new BlockBuilder()
+ {
+ Type = BlockType.Helper,
+ CodeGenerator = expected
+ };
+
+ // Act
+ Block actual = builder.Build();
+
+ // Assert
+ Assert.Same(expected, actual.CodeGenerator);
+ }
+
+ [Fact]
+ public void ConstructorTransfersChildrenFromBlockBuilder()
+ {
+ // Arrange
+ Span expected = new SpanBuilder() { Kind = SpanKind.Code }.Build();
+ BlockBuilder builder = new BlockBuilder()
+ {
+ Type = BlockType.Functions
+ };
+ builder.Children.Add(expected);
+
+ // Act
+ Block block = builder.Build();
+
+ // Assert
+ Assert.Same(expected, block.Children.Single());
+ }
+
+ [Fact]
+ public void LocateOwnerReturnsNullIfNoSpanReturnsTrueForOwnsSpan()
+ {
+ // Arrange
+ var factory = SpanFactory.CreateCsHtml();
+ Block block = new MarkupBlock(
+ factory.Markup("Foo "),
+ new StatementBlock(
+ factory.CodeTransition(),
+ factory.Code("bar").AsStatement()),
+ factory.Markup(" Baz"));
+ TextChange change = new TextChange(128, 1, new StringTextBuffer("Foo @bar Baz"), 1, new StringTextBuffer("Foo @bor Baz"));
+
+ // Act
+ Span actual = block.LocateOwner(change);
+
+ // Assert
+ Assert.Null(actual);
+ }
+
+ [Fact]
+ public void LocateOwnerReturnsNullIfChangeCrossesMultipleSpans()
+ {
+ // Arrange
+ var factory = SpanFactory.CreateCsHtml();
+ Block block = new MarkupBlock(
+ factory.Markup("Foo "),
+ new StatementBlock(
+ factory.CodeTransition(),
+ factory.Code("bar").AsStatement()),
+ factory.Markup(" Baz"));
+ TextChange change = new TextChange(4, 10, new StringTextBuffer("Foo @bar Baz"), 10, new StringTextBuffer("Foo @bor Baz"));
+
+ // Act
+ Span actual = block.LocateOwner(change);
+
+ // Assert
+ Assert.Null(actual);
+ }
+ }
+}
diff --git a/test/System.Web.Razor.Test/Parser/CSharp/CSharpAutoCompleteTest.cs b/test/System.Web.Razor.Test/Parser/CSharp/CSharpAutoCompleteTest.cs
new file mode 100644
index 00000000..ed10c56f
--- /dev/null
+++ b/test/System.Web.Razor.Test/Parser/CSharp/CSharpAutoCompleteTest.cs
@@ -0,0 +1,172 @@
+using System.Web.Razor.Generator;
+using System.Web.Razor.Parser;
+using System.Web.Razor.Parser.SyntaxTree;
+using System.Web.Razor.Resources;
+using System.Web.Razor.Test.Framework;
+using System.Web.Razor.Text;
+using System.Web.Razor.Tokenizer.Symbols;
+using Xunit;
+
+namespace System.Web.Razor.Test.Parser.CSharp
+{
+ public class CSharpAutoCompleteTest : CsHtmlCodeParserTestBase
+ {
+ [Fact]
+ public void FunctionsDirectiveAutoCompleteAtEOF()
+ {
+ ParseBlockTest("@functions{",
+ new FunctionsBlock(
+ Factory.CodeTransition("@")
+ .Accepts(AcceptedCharacters.None),
+ Factory.MetaCode("functions{")
+ .Accepts(AcceptedCharacters.None),
+ Factory.EmptyCSharp()
+ .AsFunctionsBody()
+ .With(new AutoCompleteEditHandler(CSharpLanguageCharacteristics.Instance.TokenizeString)
+ {
+ AutoCompleteString = "}"
+ })),
+ new RazorError(String.Format(RazorResources.ParseError_Expected_EndOfBlock_Before_EOF, "functions", "}", "{"),
+ 1, 0, 1));
+ }
+
+ [Fact]
+ public void HelperDirectiveAutoCompleteAtEOF()
+ {
+ ParseBlockTest("@helper Strong(string value) {",
+ new HelperBlock(new HelperCodeGenerator(new LocationTagged<string>("Strong(string value) {", 8, 0, 8), headerComplete: true),
+ Factory.CodeTransition(),
+ Factory.MetaCode("helper ")
+ .Accepts(AcceptedCharacters.None),
+ Factory.Code("Strong(string value) {")
+ .Hidden()
+ .Accepts(AcceptedCharacters.None),
+ new StatementBlock(
+ Factory.EmptyCSharp()
+ .AsStatement()
+ .With(new AutoCompleteEditHandler(CSharpLanguageCharacteristics.Instance.TokenizeString) { AutoCompleteString = "}" })
+ )
+ ),
+ new RazorError(String.Format(RazorResources.ParseError_Expected_EndOfBlock_Before_EOF, "helper", "}", "{"),
+ 1, 0, 1));
+ }
+
+ [Fact]
+ public void SectionDirectiveAutoCompleteAtEOF()
+ {
+ ParseBlockTest("@section Header {",
+ new SectionBlock(new SectionCodeGenerator("Header"),
+ Factory.CodeTransition(),
+ Factory.MetaCode("section Header {")
+ .AutoCompleteWith("}", atEndOfSpan: true)
+ .Accepts(AcceptedCharacters.Any),
+ new MarkupBlock()),
+ new RazorError(
+ String.Format(RazorResources.ParseError_Expected_X, "}"),
+ 17, 0, 17));
+ }
+
+ [Fact]
+ public void VerbatimBlockAutoCompleteAtEOF()
+ {
+ ParseBlockTest("@{",
+ new StatementBlock(
+ Factory.CodeTransition(),
+ Factory.MetaCode("{").Accepts(AcceptedCharacters.None),
+ Factory.EmptyCSharp()
+ .AsStatement()
+ .With(new AutoCompleteEditHandler(CSharpLanguageCharacteristics.Instance.TokenizeString) { AutoCompleteString = "}" })
+ ),
+ new RazorError(String.Format(RazorResources.ParseError_Expected_EndOfBlock_Before_EOF, RazorResources.BlockName_Code, "}", "{"),
+ 1, 0, 1));
+ }
+
+ [Fact]
+ public void FunctionsDirectiveAutoCompleteAtStartOfFile()
+ {
+ ParseBlockTest(@"@functions{
+foo",
+ new FunctionsBlock(
+ Factory.CodeTransition("@")
+ .Accepts(AcceptedCharacters.None),
+ Factory.MetaCode("functions{")
+ .Accepts(AcceptedCharacters.None),
+ Factory.Code("\r\nfoo")
+ .AsFunctionsBody()
+ .With(new AutoCompleteEditHandler(CSharpLanguageCharacteristics.Instance.TokenizeString)
+ {
+ AutoCompleteString = "}"
+ })),
+ new RazorError(String.Format(RazorResources.ParseError_Expected_EndOfBlock_Before_EOF, "functions", "}", "{"),
+ 1, 0, 1));
+ }
+
+ [Fact]
+ public void HelperDirectiveAutoCompleteAtStartOfFile()
+ {
+ ParseBlockTest(@"@helper Strong(string value) {
+<p></p>",
+ new HelperBlock(new HelperCodeGenerator(new LocationTagged<string>("Strong(string value) {", 8, 0, 8), headerComplete: true),
+ Factory.CodeTransition(),
+ Factory.MetaCode("helper ")
+ .Accepts(AcceptedCharacters.None),
+ Factory.Code("Strong(string value) {")
+ .Hidden()
+ .Accepts(AcceptedCharacters.None),
+ new StatementBlock(
+ Factory.Code("\r\n")
+ .AsStatement()
+ .With(new AutoCompleteEditHandler(CSharpLanguageCharacteristics.Instance.TokenizeString) { AutoCompleteString = "}" }),
+ new MarkupBlock(
+ Factory.Markup(@"<p></p>")
+ .With(new MarkupCodeGenerator())
+ .Accepts(AcceptedCharacters.None)
+ ),
+ Factory.Span(SpanKind.Code, new CSharpSymbol(Factory.LocationTracker.CurrentLocation, String.Empty, CSharpSymbolType.Unknown))
+ .With(new StatementCodeGenerator())
+ )
+ ),
+ new RazorError(String.Format(RazorResources.ParseError_Expected_EndOfBlock_Before_EOF, "helper", "}", "{"),
+ 1, 0, 1));
+ }
+
+ [Fact]
+ public void SectionDirectiveAutoCompleteAtStartOfFile()
+ {
+ ParseBlockTest(@"@section Header {
+<p>Foo</p>",
+ new SectionBlock(new SectionCodeGenerator("Header"),
+ Factory.CodeTransition(),
+ Factory.MetaCode("section Header {")
+ .AutoCompleteWith("}", atEndOfSpan: true)
+ .Accepts(AcceptedCharacters.Any),
+ new MarkupBlock(
+ Factory.Markup("\r\n<p>Foo</p>"))),
+ new RazorError(String.Format(RazorResources.ParseError_Expected_X, "}"),
+ 29, 1, 10));
+ }
+
+ [Fact]
+ public void VerbatimBlockAutoCompleteAtStartOfFile()
+ {
+ ParseBlockTest(@"@{
+<p></p>",
+ new StatementBlock(
+ Factory.CodeTransition(),
+ Factory.MetaCode("{").Accepts(AcceptedCharacters.None),
+ Factory.Code("\r\n")
+ .AsStatement()
+ .With(new AutoCompleteEditHandler(CSharpLanguageCharacteristics.Instance.TokenizeString) { AutoCompleteString = "}" }),
+ new MarkupBlock(
+ Factory.Markup(@"<p></p>")
+ .With(new MarkupCodeGenerator())
+ .Accepts(AcceptedCharacters.None)
+ ),
+ Factory.Span(SpanKind.Code, new CSharpSymbol(Factory.LocationTracker.CurrentLocation, String.Empty, CSharpSymbolType.Unknown))
+ .With(new StatementCodeGenerator())
+ ),
+ new RazorError(String.Format(RazorResources.ParseError_Expected_EndOfBlock_Before_EOF, RazorResources.BlockName_Code, "}", "{"),
+ 1, 0, 1));
+ }
+ }
+}
diff --git a/test/System.Web.Razor.Test/Parser/CSharp/CSharpBlockTest.cs b/test/System.Web.Razor.Test/Parser/CSharp/CSharpBlockTest.cs
new file mode 100644
index 00000000..addf3951
--- /dev/null
+++ b/test/System.Web.Razor.Test/Parser/CSharp/CSharpBlockTest.cs
@@ -0,0 +1,731 @@
+using System.Web.Razor.Generator;
+using System.Web.Razor.Parser;
+using System.Web.Razor.Parser.SyntaxTree;
+using System.Web.Razor.Resources;
+using System.Web.Razor.Test.Framework;
+using System.Web.Razor.Text;
+using System.Web.Razor.Tokenizer.Symbols;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Razor.Test.Parser.CSharp
+{
+ public class CSharpBlockTest : CsHtmlCodeParserTestBase
+ {
+ [Fact]
+ public void ParseBlockMethodThrowsArgNullExceptionOnNullContext()
+ {
+ // Arrange
+ CSharpCodeParser parser = new CSharpCodeParser();
+
+ // Act and Assert
+ Assert.Throws<InvalidOperationException>(() => parser.ParseBlock(), RazorResources.Parser_Context_Not_Set);
+ }
+
+ [Fact]
+ public void BalancingBracketsIgnoresStringLiteralCharactersAndBracketsInsideSingleLineComments()
+ {
+ SingleSpanBlockTest(@"if(foo) {
+ // bar } "" baz '
+ zoop();
+}", BlockType.Statement, SpanKind.Code);
+ }
+
+ [Fact]
+ public void NestedCodeBlockWithAtCausesError()
+ {
+ ParseBlockTest("if (true) { @if(false) { } }",
+ new StatementBlock(
+ Factory.Code("if (true) { ").AsStatement(),
+ new StatementBlock(
+ Factory.CodeTransition(),
+ Factory.Code("if(false) { }").AsStatement()
+ ),
+ Factory.Code(" }").AsStatement()),
+ new RazorError(
+ String.Format(RazorResources.ParseError_Unexpected_Keyword_After_At,
+ "if"),
+ new SourceLocation(13, 0, 13)));
+ }
+
+ [Fact]
+ public void BalancingBracketsIgnoresStringLiteralCharactersAndBracketsInsideBlockComments()
+ {
+ SingleSpanBlockTest(
+ @"if(foo) {
+ /* bar } "" */ ' baz } '
+ zoop();
+}", BlockType.Statement, SpanKind.Code);
+ }
+
+ [Fact]
+ public void ParseBlockSkipsParenthesisedExpressionAndThenBalancesBracesIfFirstIdentifierIsForKeyword()
+ {
+ SingleSpanBlockTest("for(int i = 0; i < 10; new Foo { Bar = \"baz\" }) { Debug.WriteLine(@\"foo } bar\"); }", BlockType.Statement, SpanKind.Code, acceptedCharacters: AcceptedCharacters.None);
+ }
+
+ [Fact]
+ public void ParseBlockSkipsParenthesisedExpressionAndThenBalancesBracesIfFirstIdentifierIsForeachKeyword()
+ {
+ SingleSpanBlockTest("foreach(int i = 0; i < 10; new Foo { Bar = \"baz\" }) { Debug.WriteLine(@\"foo } bar\"); }", BlockType.Statement, SpanKind.Code, acceptedCharacters: AcceptedCharacters.None);
+ }
+
+ [Fact]
+ public void ParseBlockSkipsParenthesisedExpressionAndThenBalancesBracesIfFirstIdentifierIsWhileKeyword()
+ {
+ SingleSpanBlockTest("while(int i = 0; i < 10; new Foo { Bar = \"baz\" }) { Debug.WriteLine(@\"foo } bar\"); }", BlockType.Statement, SpanKind.Code, acceptedCharacters: AcceptedCharacters.None);
+ }
+
+ [Fact]
+ public void ParseBlockSkipsParenthesisedExpressionAndThenBalancesBracesIfFirstIdentifierIsUsingKeywordFollowedByParen()
+ {
+ SingleSpanBlockTest("using(int i = 0; i < 10; new Foo { Bar = \"baz\" }) { Debug.WriteLine(@\"foo } bar\"); }", BlockType.Statement, SpanKind.Code, acceptedCharacters: AcceptedCharacters.None);
+ }
+
+ [Fact]
+ public void ParseBlockSupportsUsingsNestedWithinOtherBlocks()
+ {
+ SingleSpanBlockTest("if(foo) { using(int i = 0; i < 10; new Foo { Bar = \"baz\" }) { Debug.WriteLine(@\"foo } bar\"); } }", BlockType.Statement, SpanKind.Code);
+ }
+
+ [Fact]
+ public void ParseBlockSkipsParenthesisedExpressionAndThenBalancesBracesIfFirstIdentifierIsIfKeywordWithNoElseBranches()
+ {
+ SingleSpanBlockTest("if(int i = 0; i < 10; new Foo { Bar = \"baz\" }) { Debug.WriteLine(@\"foo } bar\"); }", BlockType.Statement, SpanKind.Code);
+ }
+
+ [Fact]
+ public void ParseBlockAllowsEmptyBlockStatement()
+ {
+ SingleSpanBlockTest("if(false) { }", BlockType.Statement, SpanKind.Code);
+ }
+
+ [Fact]
+ public void ParseBlockTerminatesParenBalancingAtEOF()
+ {
+ ImplicitExpressionTest("Html.En(code()", "Html.En(code()",
+ AcceptedCharacters.Any,
+ new RazorError(
+ String.Format(RazorResources.ParseError_Expected_CloseBracket_Before_EOF,
+ "(", ")"),
+ new SourceLocation(8, 0, 8)));
+ }
+
+ [Fact]
+ public void ParseBlockSupportsBlockCommentBetweenIfAndElseClause()
+ {
+ SingleSpanBlockTest("if(foo) { bar(); } /* Foo */ /* Bar */ else { baz(); }", BlockType.Statement, SpanKind.Code, acceptedCharacters: AcceptedCharacters.None);
+ }
+
+ [Fact]
+ public void ParseBlockSupportsRazorCommentBetweenIfAndElseClause()
+ {
+ RunRazorCommentBetweenClausesTest("if(foo) { bar(); } ", " else { baz(); }", acceptedCharacters: AcceptedCharacters.None);
+ }
+
+ [Fact]
+ public void ParseBlockSupportsBlockCommentBetweenElseIfAndElseClause()
+ {
+ SingleSpanBlockTest("if(foo) { bar(); } else if(bar) { baz(); } /* Foo */ /* Bar */ else { biz(); }", BlockType.Statement, SpanKind.Code, acceptedCharacters: AcceptedCharacters.None);
+ }
+
+ [Fact]
+ public void ParseBlockSupportsRazorCommentBetweenElseIfAndElseClause()
+ {
+ RunRazorCommentBetweenClausesTest("if(foo) { bar(); } else if(bar) { baz(); } ", " else { baz(); }", acceptedCharacters: AcceptedCharacters.None);
+ }
+
+ [Fact]
+ public void ParseBlockSupportsBlockCommentBetweenIfAndElseIfClause()
+ {
+ SingleSpanBlockTest("if(foo) { bar(); } /* Foo */ /* Bar */ else if(bar) { baz(); }", BlockType.Statement, SpanKind.Code);
+ }
+
+ [Fact]
+ public void ParseBlockSupportsRazorCommentBetweenIfAndElseIfClause()
+ {
+ RunRazorCommentBetweenClausesTest("if(foo) { bar(); } ", " else if(bar) { baz(); }");
+ }
+
+ [Fact]
+ public void ParseBlockSupportsLineCommentBetweenIfAndElseClause()
+ {
+ SingleSpanBlockTest(@"if(foo) { bar(); }
+// Foo
+// Bar
+else { baz(); }", BlockType.Statement, SpanKind.Code, acceptedCharacters: AcceptedCharacters.None);
+ }
+
+ [Fact]
+ public void ParseBlockSupportsLineCommentBetweenElseIfAndElseClause()
+ {
+ SingleSpanBlockTest(@"if(foo) { bar(); } else if(bar) { baz(); }
+// Foo
+// Bar
+else { biz(); }", BlockType.Statement, SpanKind.Code, acceptedCharacters: AcceptedCharacters.None);
+ }
+
+ [Fact]
+ public void ParseBlockSupportsLineCommentBetweenIfAndElseIfClause()
+ {
+ SingleSpanBlockTest(@"if(foo) { bar(); }
+// Foo
+// Bar
+else if(bar) { baz(); }", BlockType.Statement, SpanKind.Code);
+ }
+
+ [Fact]
+ public void ParseBlockParsesElseIfBranchesOfIfStatement()
+ {
+ const string ifStatement = @"if(int i = 0; i < 10; new Foo { Bar = ""baz"" }) {
+ Debug.WriteLine(@""foo } bar"");
+}";
+ const string elseIfBranch = @" else if(int i = 0; i < 10; new Foo { Bar = ""baz"" }) {
+ Debug.WriteLine(@""bar } baz"");
+}";
+ const string document = ifStatement + elseIfBranch;
+
+ SingleSpanBlockTest(document, BlockType.Statement, SpanKind.Code);
+ }
+
+ [Fact]
+ public void ParseBlockParsesMultipleElseIfBranchesOfIfStatement()
+ {
+ const string ifStatement = @"if(int i = 0; i < 10; new Foo { Bar = ""baz"" }) {
+ Debug.WriteLine(@""foo } bar"");
+}";
+ const string elseIfBranch = @" else if(int i = 0; i < 10; new Foo { Bar = ""baz"" }) {
+ Debug.WriteLine(@""bar } baz"");
+}";
+ const string document = ifStatement + elseIfBranch + elseIfBranch + elseIfBranch + elseIfBranch;
+ SingleSpanBlockTest(document, BlockType.Statement, SpanKind.Code);
+ }
+
+ [Fact]
+ public void ParseBlockParsesMultipleElseIfBranchesOfIfStatementFollowedByOneElseBranch()
+ {
+ const string ifStatement = @"if(int i = 0; i < 10; new Foo { Bar = ""baz"" }) {
+ Debug.WriteLine(@""foo } bar"");
+}";
+ const string elseIfBranch = @" else if(int i = 0; i < 10; new Foo { Bar = ""baz"" }) {
+ Debug.WriteLine(@""bar } baz"");
+}";
+ const string elseBranch = @" else { Debug.WriteLine(@""bar } baz""); }";
+ const string document = ifStatement + elseIfBranch + elseIfBranch + elseBranch;
+
+ SingleSpanBlockTest(document, BlockType.Statement, SpanKind.Code, acceptedCharacters: AcceptedCharacters.None);
+ }
+
+ [Fact]
+ public void ParseBlockStopsParsingCodeAfterElseBranch()
+ {
+ const string ifStatement = @"if(int i = 0; i < 10; new Foo { Bar = ""baz"" }) {
+ Debug.WriteLine(@""foo } bar"");
+}";
+ const string elseIfBranch = @" else if(int i = 0; i < 10; new Foo { Bar = ""baz"" }) {
+ Debug.WriteLine(@""bar } baz"");
+}";
+ const string elseBranch = @" else { Debug.WriteLine(@""bar } baz""); }";
+ const string document = ifStatement + elseIfBranch + elseBranch + elseIfBranch;
+ const string expected = ifStatement + elseIfBranch + elseBranch;
+
+ ParseBlockTest(document, new StatementBlock(Factory.Code(expected).AsStatement().Accepts(AcceptedCharacters.None)));
+ }
+
+ [Fact]
+ public void ParseBlockStopsParsingIfIfStatementNotFollowedByElse()
+ {
+ const string document = @"if(int i = 0; i < 10; new Foo { Bar = ""baz"" }) {
+ Debug.WriteLine(@""foo } bar"");
+}";
+
+ SingleSpanBlockTest(document, BlockType.Statement, SpanKind.Code);
+ }
+
+ [Fact]
+ public void ParseBlockAcceptsElseIfWithNoCondition()
+ {
+ // We don't want to be a full C# parser - If the else if is missing it's condition, the C# compiler can handle that, we have all the info we need to keep parsing
+ const string ifBranch = @"if(int i = 0; i < 10; new Foo { Bar = ""baz"" }) {
+ Debug.WriteLine(@""foo } bar"");
+}";
+ const string elseIfBranch = @" else if { foo(); }";
+ const string document = ifBranch + elseIfBranch;
+
+ SingleSpanBlockTest(document, BlockType.Statement, SpanKind.Code);
+ }
+
+ [Fact]
+ public void ParseBlockCorrectlyParsesDoWhileBlock()
+ {
+ SingleSpanBlockTest("do { var foo = bar; } while(foo != bar);", BlockType.Statement, SpanKind.Code, acceptedCharacters: AcceptedCharacters.None);
+ }
+
+ [Fact]
+ public void ParseBlockCorrectlyParsesDoWhileBlockMissingSemicolon()
+ {
+ SingleSpanBlockTest("do { var foo = bar; } while(foo != bar)", BlockType.Statement, SpanKind.Code);
+ }
+
+ [Fact]
+ public void ParseBlockCorrectlyParsesDoWhileBlockMissingWhileCondition()
+ {
+ SingleSpanBlockTest("do { var foo = bar; } while", BlockType.Statement, SpanKind.Code);
+ }
+
+ [Fact]
+ public void ParseBlockCorrectlyParsesDoWhileBlockMissingWhileConditionWithSemicolon()
+ {
+ SingleSpanBlockTest("do { var foo = bar; } while;", BlockType.Statement, SpanKind.Code, acceptedCharacters: AcceptedCharacters.None);
+ }
+
+ [Fact]
+ public void ParseBlockCorrectlyParsesDoWhileBlockMissingWhileClauseEntirely()
+ {
+ SingleSpanBlockTest("do { var foo = bar; } narf;", "do { var foo = bar; }", BlockType.Statement, SpanKind.Code);
+ }
+
+ [Fact]
+ public void ParseBlockSupportsBlockCommentBetweenDoAndWhileClause()
+ {
+ SingleSpanBlockTest("do { var foo = bar; } /* Foo */ /* Bar */ while(true);", BlockType.Statement, SpanKind.Code, acceptedCharacters: AcceptedCharacters.None);
+ }
+
+ [Fact]
+ public void ParseBlockSupportsLineCommentBetweenDoAndWhileClause()
+ {
+ SingleSpanBlockTest(@"do { var foo = bar; }
+// Foo
+// Bar
+while(true);", BlockType.Statement, SpanKind.Code, acceptedCharacters: AcceptedCharacters.None);
+ }
+
+ [Fact]
+ public void ParseBlockSupportsRazorCommentBetweenDoAndWhileClause()
+ {
+ RunRazorCommentBetweenClausesTest("do { var foo = bar; } ", " while(true);", acceptedCharacters: AcceptedCharacters.None);
+ }
+
+ [Fact]
+ public void ParseBlockCorrectlyParsesMarkupInDoWhileBlock()
+ {
+ ParseBlockTest("@do { var foo = bar; <p>Foo</p> foo++; } while (foo<bar>);",
+ new StatementBlock(
+ Factory.CodeTransition(),
+ Factory.Code("do { var foo = bar;").AsStatement(),
+ new MarkupBlock(
+ Factory.Markup(" <p>Foo</p> ").Accepts(AcceptedCharacters.None)
+ ),
+ Factory.Code("foo++; } while (foo<bar>);").AsStatement().Accepts(AcceptedCharacters.None)
+ ));
+ }
+
+ [Fact]
+ public void ParseBlockSkipsParenthesisedExpressionAndThenBalancesBracesIfFirstIdentifierIsSwitchKeyword()
+ {
+ SingleSpanBlockTest(@"switch(foo) {
+ case 0:
+ break;
+ case 1:
+ {
+ break;
+ }
+ case 2:
+ return;
+ default:
+ return;
+}", BlockType.Statement, SpanKind.Code, acceptedCharacters: AcceptedCharacters.None);
+ }
+
+ [Fact]
+ public void ParseBlockSkipsParenthesisedExpressionAndThenBalancesBracesIfFirstIdentifierIsLockKeyword()
+ {
+ SingleSpanBlockTest("lock(foo) { Debug.WriteLine(@\"foo } bar\"); }", BlockType.Statement, SpanKind.Code, acceptedCharacters: AcceptedCharacters.None);
+ }
+
+ [Fact]
+ public void ParseBlockHasErrorsIfNamespaceImportMissingSemicolon()
+ {
+ NamespaceImportTest("using Foo.Bar.Baz", " Foo.Bar.Baz", acceptedCharacters: AcceptedCharacters.NonWhiteSpace | AcceptedCharacters.WhiteSpace, location: new SourceLocation(17, 0, 17));
+ }
+
+ [Fact]
+ public void ParseBlockHasErrorsIfNamespaceAliasMissingSemicolon()
+ {
+ NamespaceImportTest("using Foo.Bar.Baz = FooBarBaz", " Foo.Bar.Baz = FooBarBaz", acceptedCharacters: AcceptedCharacters.NonWhiteSpace | AcceptedCharacters.WhiteSpace, location: new SourceLocation(29, 0, 29));
+ }
+
+ [Fact]
+ public void ParseBlockParsesNamespaceImportWithSemicolonForUsingKeywordIfIsInValidFormat()
+ {
+ NamespaceImportTest("using Foo.Bar.Baz;", " Foo.Bar.Baz", AcceptedCharacters.NonWhiteSpace | AcceptedCharacters.WhiteSpace);
+ }
+
+ [Fact]
+ public void ParseBlockDoesntCaptureWhitespaceAfterUsing()
+ {
+ ParseBlockTest("using Foo ",
+ new DirectiveBlock(
+ Factory.Code("using Foo")
+ .AsNamespaceImport(" Foo", CSharpCodeParser.UsingKeywordLength)
+ .Accepts(AcceptedCharacters.NonWhiteSpace | AcceptedCharacters.WhiteSpace)));
+ }
+
+ [Fact]
+ public void ParseBlockParsesNamespaceAliasWithSemicolonForUsingKeywordIfIsInValidFormat()
+ {
+ NamespaceImportTest("using FooBarBaz = FooBarBaz;", " FooBarBaz = FooBarBaz", AcceptedCharacters.NonWhiteSpace | AcceptedCharacters.WhiteSpace);
+ }
+
+ [Fact]
+ public void ParseBlockTerminatesUsingKeywordAtEOFAndOutputsFileCodeBlock()
+ {
+ SingleSpanBlockTest("using ", BlockType.Statement, SpanKind.Code);
+ }
+
+ [Fact]
+ public void ParseBlockTerminatesSingleLineCommentAtEndOfFile()
+ {
+ const string document = "foreach(var f in Foo) { // foo bar baz";
+ SingleSpanBlockTest(document, document, BlockType.Statement, SpanKind.Code,
+ new RazorError(String.Format(RazorResources.ParseError_Expected_EndOfBlock_Before_EOF, "foreach", '}', '{'), SourceLocation.Zero));
+ }
+
+ [Fact]
+ public void ParseBlockTerminatesBlockCommentAtEndOfFile()
+ {
+ const string document = "foreach(var f in Foo) { /* foo bar baz";
+ SingleSpanBlockTest(document, document, BlockType.Statement, SpanKind.Code,
+ new RazorError(String.Format(RazorResources.ParseError_BlockComment_Not_Terminated), 24, 0, 24),
+ new RazorError(String.Format(RazorResources.ParseError_Expected_EndOfBlock_Before_EOF, "foreach", '}', '{'), SourceLocation.Zero));
+ }
+
+ [Fact]
+ public void ParseBlockTerminatesSingleSlashAtEndOfFile()
+ {
+ const string document = "foreach(var f in Foo) { / foo bar baz";
+ SingleSpanBlockTest(document, document, BlockType.Statement, SpanKind.Code,
+ new RazorError(String.Format(RazorResources.ParseError_Expected_EndOfBlock_Before_EOF, "foreach", '}', '{'), SourceLocation.Zero));
+ }
+
+ [Fact]
+ public void ParseBlockSupportsBlockCommentBetweenTryAndFinallyClause()
+ {
+ SingleSpanBlockTest("try { bar(); } /* Foo */ /* Bar */ finally { baz(); }", BlockType.Statement, SpanKind.Code, acceptedCharacters: AcceptedCharacters.None);
+ }
+
+ [Fact]
+ public void ParseBlockSupportsRazorCommentBetweenTryAndFinallyClause()
+ {
+ RunRazorCommentBetweenClausesTest("try { bar(); } ", " finally { biz(); }", acceptedCharacters: AcceptedCharacters.None);
+ }
+
+ [Fact]
+ public void ParseBlockSupportsBlockCommentBetweenCatchAndFinallyClause()
+ {
+ SingleSpanBlockTest("try { bar(); } catch(bar) { baz(); } /* Foo */ /* Bar */ finally { biz(); }", BlockType.Statement, SpanKind.Code, acceptedCharacters: AcceptedCharacters.None);
+ }
+
+ [Fact]
+ public void ParseBlockSupportsRazorCommentBetweenCatchAndFinallyClause()
+ {
+ RunRazorCommentBetweenClausesTest("try { bar(); } catch(bar) { baz(); } ", " finally { biz(); }", acceptedCharacters: AcceptedCharacters.None);
+ }
+
+ [Fact]
+ public void ParseBlockSupportsBlockCommentBetweenTryAndCatchClause()
+ {
+ SingleSpanBlockTest("try { bar(); } /* Foo */ /* Bar */ catch(bar) { baz(); }", BlockType.Statement, SpanKind.Code);
+ }
+
+ [Fact]
+ public void ParseBlockSupportsRazorCommentBetweenTryAndCatchClause()
+ {
+ RunRazorCommentBetweenClausesTest("try { bar(); }", " catch(bar) { baz(); }");
+ }
+
+ [Fact]
+ public void ParseBlockSupportsLineCommentBetweenTryAndFinallyClause()
+ {
+ SingleSpanBlockTest(@"try { bar(); }
+// Foo
+// Bar
+finally { baz(); }", BlockType.Statement, SpanKind.Code, acceptedCharacters: AcceptedCharacters.None);
+ }
+
+ [Fact]
+ public void ParseBlockSupportsLineCommentBetweenCatchAndFinallyClause()
+ {
+ SingleSpanBlockTest(@"try { bar(); } catch(bar) { baz(); }
+// Foo
+// Bar
+finally { biz(); }", BlockType.Statement, SpanKind.Code, acceptedCharacters: AcceptedCharacters.None);
+ }
+
+ [Fact]
+ public void ParseBlockSupportsLineCommentBetweenTryAndCatchClause()
+ {
+ SingleSpanBlockTest(@"try { bar(); }
+// Foo
+// Bar
+catch(bar) { baz(); }", BlockType.Statement, SpanKind.Code);
+ }
+
+ [Fact]
+ public void ParseBlockSupportsTryStatementWithNoAdditionalClauses()
+ {
+ SingleSpanBlockTest("try { var foo = new { } }", BlockType.Statement, SpanKind.Code);
+ }
+
+ [Fact]
+ public void ParseBlockSupportsMarkupWithinTryClause()
+ {
+ RunSimpleWrappedMarkupTest("try {", " <p>Foo</p> ", "}");
+ }
+
+ [Fact]
+ public void ParseBlockSupportsTryStatementWithOneCatchClause()
+ {
+ SingleSpanBlockTest("try { var foo = new { } } catch(Foo Bar Baz) { var foo = new { } }", BlockType.Statement, SpanKind.Code);
+ }
+
+ [Fact]
+ public void ParseBlockSupportsMarkupWithinCatchClause()
+ {
+ RunSimpleWrappedMarkupTest("try { var foo = new { } } catch(Foo Bar Baz) {", " <p>Foo</p> ", "}");
+ }
+
+ [Fact]
+ public void ParseBlockSupportsTryStatementWithMultipleCatchClause()
+ {
+ SingleSpanBlockTest("try { var foo = new { } } catch(Foo Bar Baz) { var foo = new { } } catch(Foo Bar Baz) { var foo = new { } } catch(Foo Bar Baz) { var foo = new { } }", BlockType.Statement, SpanKind.Code);
+ }
+
+ [Fact]
+ public void ParseBlockSupportsExceptionLessCatchClauses()
+ {
+ SingleSpanBlockTest("try { var foo = new { } } catch { var foo = new { } }", BlockType.Statement, SpanKind.Code);
+ }
+
+ [Fact]
+ public void ParseBlockSupportsMarkupWithinAdditionalCatchClauses()
+ {
+ RunSimpleWrappedMarkupTest("try { var foo = new { } } catch(Foo Bar Baz) { var foo = new { } } catch(Foo Bar Baz) { var foo = new { } } catch(Foo Bar Baz) {", " <p>Foo</p> ", "}");
+ }
+
+ [Fact]
+ public void ParseBlockSupportsTryStatementWithFinallyClause()
+ {
+ SingleSpanBlockTest("try { var foo = new { } } finally { var foo = new { } }", BlockType.Statement, SpanKind.Code, acceptedCharacters: AcceptedCharacters.None);
+ }
+
+ [Fact]
+ public void ParseBlockSupportsMarkupWithinFinallyClause()
+ {
+ RunSimpleWrappedMarkupTest("try { var foo = new { } } finally {", " <p>Foo</p> ", "}", acceptedCharacters: AcceptedCharacters.None);
+ }
+
+ [Fact]
+ public void ParseBlockStopsParsingCatchClausesAfterFinallyBlock()
+ {
+ string expectedContent = "try { var foo = new { } } finally { var foo = new { } }";
+ SingleSpanBlockTest(expectedContent + " catch(Foo Bar Baz) { }", expectedContent, BlockType.Statement, SpanKind.Code, acceptedCharacters: AcceptedCharacters.None);
+ }
+
+ [Fact]
+ public void ParseBlockDoesNotAllowMultipleFinallyBlocks()
+ {
+ string expectedContent = "try { var foo = new { } } finally { var foo = new { } }";
+ SingleSpanBlockTest(expectedContent + " finally { }", expectedContent, BlockType.Statement, SpanKind.Code, acceptedCharacters: AcceptedCharacters.None);
+ }
+
+ [Fact]
+ public void ParseBlockAcceptsTrailingDotIntoImplicitExpressionWhenEmbeddedInCode()
+ {
+ // Arrange
+ ParseBlockTest(@"if(foo) { @foo. }",
+ new StatementBlock(
+ Factory.Code("if(foo) { ").AsStatement(),
+ new ExpressionBlock(
+ Factory.CodeTransition(),
+ Factory.Code("foo.")
+ .AsImplicitExpression(CSharpCodeParser.DefaultKeywords, acceptTrailingDot: true)
+ .Accepts(AcceptedCharacters.NonWhiteSpace)
+ ),
+ Factory.Code(" }").AsStatement()
+ ));
+ }
+
+ [Fact]
+ public void ParseBlockParsesExpressionOnSwitchCharacterFollowedByOpenParen()
+ {
+ // Arrange
+ ParseBlockTest(@"if(foo) { @(foo + bar) }",
+ new StatementBlock(
+ Factory.Code("if(foo) { ").AsStatement(),
+ new ExpressionBlock(
+ Factory.CodeTransition(),
+ Factory.MetaCode("(").Accepts(AcceptedCharacters.None),
+ Factory.Code("foo + bar").AsExpression(),
+ Factory.MetaCode(")").Accepts(AcceptedCharacters.None)
+ ),
+ Factory.Code(" }").AsStatement()
+ ));
+ }
+
+ [Fact]
+ public void ParseBlockParsesExpressionOnSwitchCharacterFollowedByIdentifierStart()
+ {
+ // Arrange
+ ParseBlockTest(@"if(foo) { @foo[4].bar() }",
+ new StatementBlock(
+ Factory.Code("if(foo) { ").AsStatement(),
+ new ExpressionBlock(
+ Factory.CodeTransition(),
+ Factory.Code("foo[4].bar()")
+ .AsImplicitExpression(CSharpCodeParser.DefaultKeywords, acceptTrailingDot: true)
+ .Accepts(AcceptedCharacters.NonWhiteSpace)
+ ),
+ Factory.Code(" }").AsStatement()
+ ));
+ }
+
+ [Fact]
+ public void ParseBlockTreatsDoubleAtSignAsEscapeSequenceIfAtStatementStart()
+ {
+ // Arrange
+ ParseBlockTest(@"if(foo) { @@class.Foo() }",
+ new StatementBlock(
+ Factory.Code("if(foo) { ").AsStatement(),
+ Factory.Code("@").Hidden(),
+ Factory.Code("@class.Foo() }").AsStatement()
+ ));
+ }
+
+ [Fact]
+ public void ParseBlockTreatsAtSignsAfterFirstPairAsPartOfCSharpStatement()
+ {
+ // Arrange
+ ParseBlockTest(@"if(foo) { @@@@class.Foo() }",
+ new StatementBlock(
+ Factory.Code("if(foo) { ").AsStatement(),
+ Factory.Code("@").Hidden(),
+ Factory.Code("@@@class.Foo() }").AsStatement()
+ ));
+ }
+
+ [Fact]
+ public void ParseBlockDoesNotParseMarkupStatementOrExpressionOnSwitchCharacterNotFollowedByOpenAngleOrColon()
+ {
+ // Arrange
+ ParseBlockTest("if(foo) { @\"Foo\".ToString(); }",
+ new StatementBlock(
+ Factory.Code("if(foo) { @\"Foo\".ToString(); }").AsStatement()));
+ }
+
+ [Fact]
+ public void ParsersCanNestRecursively()
+ {
+ // Arrange
+ ParseBlockTest(@"foreach(var c in db.Categories) {
+ <div>
+ <h1>@c.Name</h1>
+ <ul>
+ @foreach(var p in c.Products) {
+ <li><a href=""@Html.ActionUrl(""Products"", ""Detail"", new { id = p.Id })"">@p.Name</a></li>
+ }
+ </ul>
+ </div>
+ }",
+ new StatementBlock(
+ Factory.Code("foreach(var c in db.Categories) {\r\n").AsStatement(),
+ new MarkupBlock(
+ Factory.Markup(" <div>\r\n <h1>"),
+ new ExpressionBlock(
+ Factory.CodeTransition(),
+ Factory.Code("c.Name")
+ .AsImplicitExpression(CSharpCodeParser.DefaultKeywords)
+ .Accepts(AcceptedCharacters.NonWhiteSpace)),
+ Factory.Markup("</h1>\r\n <ul>\r\n"),
+ new StatementBlock(
+ Factory.Code(@" ").AsStatement(),
+ Factory.CodeTransition(),
+ Factory.Code("foreach(var p in c.Products) {\r\n").AsStatement(),
+ new MarkupBlock(
+ Factory.Markup(" <li><a"),
+ new MarkupBlock(new AttributeBlockCodeGenerator("href", new LocationTagged<string>(" href=\"", 193, 5, 30), new LocationTagged<string>("\"", 256, 5, 93)),
+ Factory.Markup(" href=\"").With(SpanCodeGenerator.Null),
+ new MarkupBlock(new DynamicAttributeBlockCodeGenerator(new LocationTagged<string>(String.Empty, 200, 5, 37), 200, 5, 37),
+ new ExpressionBlock(
+ Factory.CodeTransition(),
+ Factory.Code("Html.ActionUrl(\"Products\", \"Detail\", new { id = p.Id })")
+ .AsImplicitExpression(CSharpCodeParser.DefaultKeywords)
+ .Accepts(AcceptedCharacters.NonWhiteSpace))),
+ Factory.Markup("\"").With(SpanCodeGenerator.Null)),
+ Factory.Markup(">"),
+ new ExpressionBlock(
+ Factory.CodeTransition(),
+ Factory.Code("p.Name")
+ .AsImplicitExpression(CSharpCodeParser.DefaultKeywords)
+ .Accepts(AcceptedCharacters.NonWhiteSpace)),
+ Factory.Markup("</a></li>\r\n").Accepts(AcceptedCharacters.None)),
+ Factory.Code(" }\r\n").AsStatement().Accepts(AcceptedCharacters.None)),
+ Factory.Markup(" </ul>\r\n </div>\r\n")
+ .Accepts(AcceptedCharacters.None)),
+ Factory.Code(" }").AsStatement().Accepts(AcceptedCharacters.None)));
+ }
+
+ private void RunRazorCommentBetweenClausesTest(string preComment, string postComment, AcceptedCharacters acceptedCharacters = AcceptedCharacters.Any)
+ {
+ ParseBlockTest(preComment + "@* Foo *@ @* Bar *@" + postComment,
+ new StatementBlock(
+ Factory.Code(preComment).AsStatement(),
+ new CommentBlock(
+ Factory.CodeTransition(CSharpSymbolType.RazorCommentTransition),
+ Factory.MetaCode("*", CSharpSymbolType.RazorCommentStar).Accepts(AcceptedCharacters.None),
+ Factory.Comment(" Foo ", CSharpSymbolType.RazorComment),
+ Factory.MetaCode("*", CSharpSymbolType.RazorCommentStar).Accepts(AcceptedCharacters.None),
+ Factory.CodeTransition(CSharpSymbolType.RazorCommentTransition)
+ ),
+ Factory.Code(" ").AsStatement(),
+ new CommentBlock(
+ Factory.CodeTransition(CSharpSymbolType.RazorCommentTransition),
+ Factory.MetaCode("*", CSharpSymbolType.RazorCommentStar).Accepts(AcceptedCharacters.None),
+ Factory.Comment(" Bar ", CSharpSymbolType.RazorComment),
+ Factory.MetaCode("*", CSharpSymbolType.RazorCommentStar).Accepts(AcceptedCharacters.None),
+ Factory.CodeTransition(CSharpSymbolType.RazorCommentTransition)
+ ),
+ Factory.Code(postComment).AsStatement().Accepts(acceptedCharacters)));
+ }
+
+ private void RunSimpleWrappedMarkupTest(string prefix, string markup, string suffix, AcceptedCharacters acceptedCharacters = AcceptedCharacters.Any)
+ {
+ ParseBlockTest(prefix + markup + suffix,
+ new StatementBlock(
+ Factory.Code(prefix).AsStatement(),
+ new MarkupBlock(
+ Factory.Markup(markup).Accepts(AcceptedCharacters.None)
+ ),
+ Factory.Code(suffix).AsStatement().Accepts(acceptedCharacters)
+ ));
+ }
+
+ private void NamespaceImportTest(string content, string expectedNS, AcceptedCharacters acceptedCharacters = AcceptedCharacters.None, string errorMessage = null, SourceLocation? location = null)
+ {
+ var errors = new RazorError[0];
+ if (!String.IsNullOrEmpty(errorMessage) && location.HasValue)
+ {
+ errors = new RazorError[]
+ {
+ new RazorError(errorMessage, location.Value)
+ };
+ }
+ ParseBlockTest(content,
+ new DirectiveBlock(
+ Factory.Code(content)
+ .AsNamespaceImport(expectedNS, CSharpCodeParser.UsingKeywordLength)
+ .Accepts(acceptedCharacters)),
+ errors);
+ }
+ }
+}
diff --git a/test/System.Web.Razor.Test/Parser/CSharp/CSharpDirectivesTest.cs b/test/System.Web.Razor.Test/Parser/CSharp/CSharpDirectivesTest.cs
new file mode 100644
index 00000000..00bdb167
--- /dev/null
+++ b/test/System.Web.Razor.Test/Parser/CSharp/CSharpDirectivesTest.cs
@@ -0,0 +1,161 @@
+using System.Web.Razor.Generator;
+using System.Web.Razor.Parser;
+using System.Web.Razor.Parser.SyntaxTree;
+using System.Web.Razor.Test.Framework;
+using System.Web.Razor.Text;
+using Xunit;
+
+namespace System.Web.Razor.Test.Parser.CSharp
+{
+ public class CSharpDirectivesTest : CsHtmlCodeParserTestBase
+ {
+ [Fact]
+ public void InheritsDirective()
+ {
+ ParseBlockTest("@inherits System.Web.WebPages.WebPage",
+ new DirectiveBlock(
+ Factory.CodeTransition(),
+ Factory.MetaCode(SyntaxConstants.CSharp.InheritsKeyword + " ")
+ .Accepts(AcceptedCharacters.None),
+ Factory.Code("System.Web.WebPages.WebPage")
+ .AsBaseType("System.Web.WebPages.WebPage")));
+ }
+
+ [Fact]
+ public void InheritsDirectiveSupportsArrays()
+ {
+ ParseBlockTest("@inherits string[[]][]",
+ new DirectiveBlock(
+ Factory.CodeTransition(),
+ Factory.MetaCode(SyntaxConstants.CSharp.InheritsKeyword + " ")
+ .Accepts(AcceptedCharacters.None),
+ Factory.Code("string[[]][]")
+ .AsBaseType("string[[]][]")));
+ }
+
+ [Fact]
+ public void InheritsDirectiveSupportsNestedGenerics()
+ {
+ ParseBlockTest("@inherits System.Web.Mvc.WebViewPage<IEnumerable<MvcApplication2.Models.RegisterModel>>",
+ new DirectiveBlock(
+ Factory.CodeTransition(),
+ Factory.MetaCode(SyntaxConstants.CSharp.InheritsKeyword + " ")
+ .Accepts(AcceptedCharacters.None),
+ Factory.Code("System.Web.Mvc.WebViewPage<IEnumerable<MvcApplication2.Models.RegisterModel>>")
+ .AsBaseType("System.Web.Mvc.WebViewPage<IEnumerable<MvcApplication2.Models.RegisterModel>>")));
+ }
+
+ [Fact]
+ public void InheritsDirectiveSupportsTypeKeywords()
+ {
+ ParseBlockTest("@inherits string",
+ new DirectiveBlock(
+ Factory.CodeTransition(),
+ Factory.MetaCode(SyntaxConstants.CSharp.InheritsKeyword + " ")
+ .Accepts(AcceptedCharacters.None),
+ Factory.Code("string")
+ .AsBaseType("string")));
+ }
+
+ [Fact]
+ public void InheritsDirectiveSupportsVSTemplateTokens()
+ {
+ ParseBlockTest("@inherits $rootnamespace$.MyBase",
+ new DirectiveBlock(
+ Factory.CodeTransition(),
+ Factory.MetaCode(SyntaxConstants.CSharp.InheritsKeyword + " ")
+ .Accepts(AcceptedCharacters.None),
+ Factory.Code("$rootnamespace$.MyBase")
+ .AsBaseType("$rootnamespace$.MyBase")));
+ }
+
+ [Fact]
+ public void SessionStateDirectiveWorks()
+ {
+ ParseBlockTest("@sessionstate InProc",
+ new DirectiveBlock(
+ Factory.CodeTransition(),
+ Factory.MetaCode(SyntaxConstants.CSharp.SessionStateKeyword + " ")
+ .Accepts(AcceptedCharacters.None),
+ Factory.Code("InProc")
+ .AsRazorDirectiveAttribute("sessionstate", "InProc")
+ ));
+ }
+
+ [Fact]
+ public void SessionStateDirectiveParsesInvalidSessionValue()
+ {
+ ParseBlockTest("@sessionstate Blah",
+ new DirectiveBlock(
+ Factory.CodeTransition(),
+ Factory.MetaCode(SyntaxConstants.CSharp.SessionStateKeyword + " ")
+ .Accepts(AcceptedCharacters.None),
+ Factory.Code("Blah")
+ .AsRazorDirectiveAttribute("sessionstate", "Blah")
+ ));
+ }
+
+ [Fact]
+ public void FunctionsDirective()
+ {
+ ParseBlockTest("@functions { foo(); bar(); }",
+ new FunctionsBlock(
+ Factory.CodeTransition(),
+ Factory.MetaCode(SyntaxConstants.CSharp.FunctionsKeyword + " {")
+ .Accepts(AcceptedCharacters.None),
+ Factory.Code(" foo(); bar(); ")
+ .AsFunctionsBody(),
+ Factory.MetaCode("}")
+ .Accepts(AcceptedCharacters.None)));
+ }
+
+ [Fact]
+ public void EmptyFunctionsDirective()
+ {
+ ParseBlockTest("@functions { }",
+ new FunctionsBlock(
+ Factory.CodeTransition(),
+ Factory.MetaCode(SyntaxConstants.CSharp.FunctionsKeyword + " {")
+ .Accepts(AcceptedCharacters.None),
+ Factory.Code(" ")
+ .AsFunctionsBody(),
+ Factory.MetaCode("}")
+ .Accepts(AcceptedCharacters.None)));
+ }
+
+ [Fact]
+ public void SectionDirective()
+ {
+ ParseBlockTest("@section Header { <p>F{o}o</p> }",
+ new SectionBlock(new SectionCodeGenerator("Header"),
+ Factory.CodeTransition(),
+ Factory.MetaCode("section Header {")
+ .AutoCompleteWith(null, atEndOfSpan: true)
+ .Accepts(AcceptedCharacters.Any),
+ new MarkupBlock(
+ Factory.Markup(" <p>F", "{", "o", "}", "o", "</p> ")),
+ Factory.MetaCode("}")
+ .Accepts(AcceptedCharacters.None)));
+ }
+
+ [Fact]
+ public void HelperDirective()
+ {
+ ParseBlockTest("@helper Strong(string value) { foo(); }",
+ new HelperBlock(new HelperCodeGenerator(new LocationTagged<string>("Strong(string value) {", new SourceLocation(8, 0, 8)), headerComplete: true),
+ Factory.CodeTransition(),
+ Factory.MetaCode("helper ")
+ .Accepts(AcceptedCharacters.None),
+ Factory.Code("Strong(string value) {")
+ .Hidden()
+ .Accepts(AcceptedCharacters.None),
+ new StatementBlock(
+ Factory.Code(" foo(); ")
+ .AsStatement()
+ .With(new StatementCodeGenerator())),
+ Factory.Code("}")
+ .Hidden()
+ .Accepts(AcceptedCharacters.None)));
+ }
+ }
+}
diff --git a/test/System.Web.Razor.Test/Parser/CSharp/CSharpErrorTest.cs b/test/System.Web.Razor.Test/Parser/CSharp/CSharpErrorTest.cs
new file mode 100644
index 00000000..37fd73b6
--- /dev/null
+++ b/test/System.Web.Razor.Test/Parser/CSharp/CSharpErrorTest.cs
@@ -0,0 +1,604 @@
+using System.Web.Razor.Parser;
+using System.Web.Razor.Parser.SyntaxTree;
+using System.Web.Razor.Resources;
+using System.Web.Razor.Test.Framework;
+using System.Web.Razor.Text;
+using Xunit;
+
+namespace System.Web.Razor.Test.Parser.CSharp
+{
+ public class CSharpErrorTest : CsHtmlCodeParserTestBase
+ {
+ [Fact]
+ public void ParseBlockHandlesQuotesAfterTransition()
+ {
+ ParseBlockTest("@\"",
+ new ExpressionBlock(
+ Factory.CodeTransition(),
+ Factory.EmptyCSharp()
+ .AsImplicitExpression(KeywordSet)
+ .Accepts(AcceptedCharacters.NonWhiteSpace)
+ ),
+ new RazorError(
+ String.Format(RazorResources.ParseError_Unexpected_Character_At_Start_Of_CodeBlock_CS, '"'),
+ 1, 0, 1));
+ }
+
+ [Fact]
+ public void ParseBlockCapturesWhitespaceToEndOfLineInInvalidUsingStatementAndTreatsAsFileCode()
+ {
+ ParseBlockTest(@"using
+
+",
+ new StatementBlock(
+ Factory.Code("using \r\n").AsStatement()
+ ));
+ }
+
+ [Fact]
+ public void ParseBlockMethodOutputsOpenCurlyAsCodeSpanIfEofFoundAfterOpenCurlyBrace()
+ {
+ ParseBlockTest("{",
+ new StatementBlock(
+ Factory.MetaCode("{").Accepts(AcceptedCharacters.None),
+ Factory.EmptyCSharp()
+ .AsStatement()
+ .With(new AutoCompleteEditHandler(CSharpLanguageCharacteristics.Instance.TokenizeString) { AutoCompleteString = "}" })
+ ),
+ new RazorError(
+ String.Format(RazorResources.ParseError_Expected_EndOfBlock_Before_EOF,
+ RazorResources.BlockName_Code,
+ "}", "{"),
+ SourceLocation.Zero));
+ }
+
+ [Fact]
+ public void ParseBlockMethodOutputsZeroLengthCodeSpanIfStatementBlockEmpty()
+ {
+ ParseBlockTest("{}",
+ new StatementBlock(
+ Factory.MetaCode("{").Accepts(AcceptedCharacters.None),
+ Factory.EmptyCSharp().AsStatement(),
+ Factory.MetaCode("}").Accepts(AcceptedCharacters.None)
+ ));
+ }
+
+ [Fact]
+ public void ParseBlockMethodProducesErrorIfNewlineFollowsTransition()
+ {
+ ParseBlockTest(@"@
+",
+ new ExpressionBlock(
+ Factory.CodeTransition(),
+ Factory.EmptyCSharp()
+ .AsImplicitExpression(CSharpCodeParser.DefaultKeywords)
+ .Accepts(AcceptedCharacters.NonWhiteSpace)),
+ new RazorError(RazorResources.ParseError_Unexpected_WhiteSpace_At_Start_Of_CodeBlock_CS, new SourceLocation(1, 0, 1)));
+ }
+
+ [Fact]
+ public void ParseBlockMethodProducesErrorIfWhitespaceBetweenTransitionAndBlockStartInEmbeddedExpression()
+ {
+ ParseBlockTest(@"{
+ @ {}
+}",
+ new StatementBlock(
+ Factory.MetaCode("{").Accepts(AcceptedCharacters.None),
+ Factory.Code("\r\n ").AsStatement(),
+ new ExpressionBlock(
+ Factory.CodeTransition(),
+ Factory.EmptyCSharp()
+ .AsImplicitExpression(CSharpCodeParser.DefaultKeywords, acceptTrailingDot: true)
+ .Accepts(AcceptedCharacters.NonWhiteSpace)),
+ Factory.Code(" {}\r\n").AsStatement(),
+ Factory.MetaCode("}").Accepts(AcceptedCharacters.None)
+ ),
+ new RazorError(RazorResources.ParseError_Unexpected_WhiteSpace_At_Start_Of_CodeBlock_CS, 8, 1, 5));
+ }
+
+ [Fact]
+ public void ParseBlockMethodProducesErrorIfEOFAfterTransitionInEmbeddedExpression()
+ {
+ ParseBlockTest(@"{
+ @",
+ new StatementBlock(
+ Factory.MetaCode("{").Accepts(AcceptedCharacters.None),
+ Factory.Code("\r\n ").AsStatement(),
+ new ExpressionBlock(
+ Factory.CodeTransition(),
+ Factory.EmptyCSharp()
+ .AsImplicitExpression(CSharpCodeParser.DefaultKeywords, acceptTrailingDot: true)
+ .Accepts(AcceptedCharacters.NonWhiteSpace)),
+ Factory.EmptyCSharp().AsStatement()
+ ),
+ new RazorError(RazorResources.ParseError_Unexpected_EndOfFile_At_Start_Of_CodeBlock, 8, 1, 5),
+ new RazorError(
+ String.Format(RazorResources.ParseError_Expected_EndOfBlock_Before_EOF, RazorResources.BlockName_Code, "}", "{"),
+ SourceLocation.Zero));
+ }
+
+ [Fact]
+ public void ParseBlockMethodParsesNothingIfFirstCharacterIsNotIdentifierStartOrParenOrBrace()
+ {
+ ParseBlockTest("@!!!",
+ new ExpressionBlock(
+ Factory.CodeTransition(),
+ Factory.EmptyCSharp()
+ .AsImplicitExpression(CSharpCodeParser.DefaultKeywords)
+ .Accepts(AcceptedCharacters.NonWhiteSpace)),
+ new RazorError(
+ String.Format(RazorResources.ParseError_Unexpected_Character_At_Start_Of_CodeBlock_CS, "!"),
+ 1, 0, 1));
+ }
+
+ [Fact]
+ public void ParseBlockShouldReportErrorAndTerminateAtEOFIfIfParenInExplicitExpressionUnclosed()
+ {
+ ParseBlockTest(@"(foo bar
+baz",
+ new ExpressionBlock(
+ Factory.MetaCode("(").Accepts(AcceptedCharacters.None),
+ Factory.Code("foo bar\r\nbaz").AsExpression()
+ ),
+ new RazorError(
+ String.Format(RazorResources.ParseError_Expected_EndOfBlock_Before_EOF,
+ RazorResources.BlockName_ExplicitExpression, ')', '('),
+ new SourceLocation(0, 0, 0)));
+ }
+
+ [Fact]
+ public void ParseBlockShouldReportErrorAndTerminateAtMarkupIfIfParenInExplicitExpressionUnclosed()
+ {
+ ParseBlockTest(@"(foo bar
+<html>
+baz
+</html",
+ new ExpressionBlock(
+ Factory.MetaCode("(").Accepts(AcceptedCharacters.None),
+ Factory.Code("foo bar\r\n").AsExpression()
+ ),
+ new RazorError(
+ String.Format(RazorResources.ParseError_Expected_EndOfBlock_Before_EOF,
+ RazorResources.BlockName_ExplicitExpression, ')', '('),
+ new SourceLocation(0, 0, 0)));
+ }
+
+ [Fact]
+ public void ParseBlockCorrectlyHandlesInCorrectTransitionsIfImplicitExpressionParensUnclosed()
+ {
+ ParseBlockTest(@"Href(
+<h1>@Html.Foo(Bar);</h1>
+",
+ new ExpressionBlock(
+ Factory.Code("Href(\r\n")
+ .AsImplicitExpression(CSharpCodeParser.DefaultKeywords)
+ ),
+ new RazorError(
+ String.Format(RazorResources.ParseError_Expected_CloseBracket_Before_EOF,
+ "(", ")"),
+ new SourceLocation(4, 0, 4)));
+ }
+
+ [Fact]
+ // Test for fix to Dev10 884975 - Incorrect Error Messaging
+ public void ParseBlockShouldReportErrorAndTerminateAtEOFIfParenInImplicitExpressionUnclosed()
+ {
+ ParseBlockTest(@"Foo(Bar(Baz)
+Biz
+Boz",
+ new ExpressionBlock(
+ Factory.Code("Foo(Bar(Baz)\r\nBiz\r\nBoz")
+ .AsImplicitExpression(CSharpCodeParser.DefaultKeywords)
+ ),
+ new RazorError(String.Format(RazorResources.ParseError_Expected_CloseBracket_Before_EOF,
+ "(", ")"),
+ new SourceLocation(3, 0, 3)));
+ }
+
+ [Fact]
+ // Test for fix to Dev10 884975 - Incorrect Error Messaging
+ public void ParseBlockShouldReportErrorAndTerminateAtMarkupIfParenInImplicitExpressionUnclosed()
+ {
+ ParseBlockTest(@"Foo(Bar(Baz)
+Biz
+<html>
+Boz
+</html>",
+ new ExpressionBlock(
+ Factory.Code("Foo(Bar(Baz)\r\nBiz\r\n")
+ .AsImplicitExpression(CSharpCodeParser.DefaultKeywords)
+ ),
+ new RazorError(String.Format(RazorResources.ParseError_Expected_CloseBracket_Before_EOF,
+ "(", ")"),
+ new SourceLocation(3, 0, 3)));
+ }
+
+ [Fact]
+ // Test for fix to Dev10 884975 - Incorrect Error Messaging
+ public void ParseBlockShouldReportErrorAndTerminateAtEOFIfBracketInImplicitExpressionUnclosed()
+ {
+ ParseBlockTest(@"Foo[Bar[Baz]
+Biz
+Boz",
+ new ExpressionBlock(
+ Factory.Code("Foo[Bar[Baz]\r\nBiz\r\nBoz")
+ .AsImplicitExpression(CSharpCodeParser.DefaultKeywords)
+ ),
+ new RazorError(
+ String.Format(RazorResources.ParseError_Expected_CloseBracket_Before_EOF,
+ "[", "]"),
+ new SourceLocation(3, 0, 3)));
+ }
+
+ [Fact]
+ // Test for fix to Dev10 884975 - Incorrect Error Messaging
+ public void ParseBlockShouldReportErrorAndTerminateAtMarkupIfBracketInImplicitExpressionUnclosed()
+ {
+ ParseBlockTest(@"Foo[Bar[Baz]
+Biz
+<b>
+Boz
+</b>",
+ new ExpressionBlock(
+ Factory.Code("Foo[Bar[Baz]\r\nBiz\r\n")
+ .AsImplicitExpression(CSharpCodeParser.DefaultKeywords)
+ ),
+ new RazorError(
+ String.Format(RazorResources.ParseError_Expected_CloseBracket_Before_EOF,
+ "[", "]"),
+ new SourceLocation(3, 0, 3)));
+ }
+
+ // Simple EOF handling errors:
+ [Fact]
+ public void ParseBlockReportsErrorIfExplicitCodeBlockUnterminatedAtEOF()
+ {
+ ParseBlockTest("{ var foo = bar; if(foo != null) { bar(); } ",
+ new StatementBlock(
+ Factory.MetaCode("{").Accepts(AcceptedCharacters.None),
+ Factory.Code(" var foo = bar; if(foo != null) { bar(); } ").AsStatement()
+ ),
+ new RazorError(
+ String.Format(RazorResources.ParseError_Expected_EndOfBlock_Before_EOF,
+ RazorResources.BlockName_Code, '}', '{'),
+ SourceLocation.Zero));
+ }
+
+ [Fact]
+ public void ParseBlockReportsErrorIfClassBlockUnterminatedAtEOF()
+ {
+ ParseBlockTest("functions { var foo = bar; if(foo != null) { bar(); } ",
+ new FunctionsBlock(
+ Factory.MetaCode("functions {").Accepts(AcceptedCharacters.None),
+ Factory.Code(" var foo = bar; if(foo != null) { bar(); } ").AsFunctionsBody()
+ ),
+ new RazorError(
+ String.Format(RazorResources.ParseError_Expected_EndOfBlock_Before_EOF,
+ "functions", '}', '{'),
+ SourceLocation.Zero));
+ }
+
+ [Fact]
+ public void ParseBlockReportsErrorIfIfBlockUnterminatedAtEOF()
+ {
+ RunUnterminatedSimpleKeywordBlock("if");
+ }
+
+ [Fact]
+ public void ParseBlockReportsErrorIfElseBlockUnterminatedAtEOF()
+ {
+ ParseBlockTest("if(foo) { baz(); } else { var foo = bar; if(foo != null) { bar(); } ",
+ new StatementBlock(
+ Factory.Code("if(foo) { baz(); } else { var foo = bar; if(foo != null) { bar(); } ").AsStatement()
+ ),
+ new RazorError(
+ String.Format(RazorResources.ParseError_Expected_EndOfBlock_Before_EOF,
+ "else", '}', '{'),
+ new SourceLocation(19, 0, 19)));
+ }
+
+ [Fact]
+ public void ParseBlockReportsErrorIfElseIfBlockUnterminatedAtEOF()
+ {
+ ParseBlockTest("if(foo) { baz(); } else if { var foo = bar; if(foo != null) { bar(); } ",
+ new StatementBlock(
+ Factory.Code("if(foo) { baz(); } else if { var foo = bar; if(foo != null) { bar(); } ").AsStatement()
+ ),
+ new RazorError(
+ String.Format(RazorResources.ParseError_Expected_EndOfBlock_Before_EOF,
+ "else if", '}', '{'),
+ new SourceLocation(19, 0, 19)));
+ }
+
+ [Fact]
+ public void ParseBlockReportsErrorIfDoBlockUnterminatedAtEOF()
+ {
+ ParseBlockTest("do { var foo = bar; if(foo != null) { bar(); } ",
+ new StatementBlock(
+ Factory.Code("do { var foo = bar; if(foo != null) { bar(); } ").AsStatement()
+ ),
+ new RazorError(
+ String.Format(RazorResources.ParseError_Expected_EndOfBlock_Before_EOF,
+ "do", '}', '{'),
+ SourceLocation.Zero));
+ }
+
+ [Fact]
+ public void ParseBlockReportsErrorIfTryBlockUnterminatedAtEOF()
+ {
+ ParseBlockTest("try { var foo = bar; if(foo != null) { bar(); } ",
+ new StatementBlock(
+ Factory.Code("try { var foo = bar; if(foo != null) { bar(); } ").AsStatement()
+ ),
+ new RazorError(
+ String.Format(RazorResources.ParseError_Expected_EndOfBlock_Before_EOF,
+ "try", '}', '{'),
+ SourceLocation.Zero));
+ }
+
+ [Fact]
+ public void ParseBlockReportsErrorIfCatchBlockUnterminatedAtEOF()
+ {
+ ParseBlockTest("try { baz(); } catch(Foo) { var foo = bar; if(foo != null) { bar(); } ",
+ new StatementBlock(
+ Factory.Code("try { baz(); } catch(Foo) { var foo = bar; if(foo != null) { bar(); } ").AsStatement()
+ ),
+ new RazorError(
+ String.Format(RazorResources.ParseError_Expected_EndOfBlock_Before_EOF,
+ "catch", '}', '{'),
+ new SourceLocation(15, 0, 15)));
+ }
+
+ [Fact]
+ public void ParseBlockReportsErrorIfFinallyBlockUnterminatedAtEOF()
+ {
+ ParseBlockTest("try { baz(); } finally { var foo = bar; if(foo != null) { bar(); } ",
+ new StatementBlock(
+ Factory.Code("try { baz(); } finally { var foo = bar; if(foo != null) { bar(); } ").AsStatement()
+ ),
+ new RazorError(
+ String.Format(RazorResources.ParseError_Expected_EndOfBlock_Before_EOF,
+ "finally", '}', '{'),
+ new SourceLocation(15, 0, 15)));
+ }
+
+ [Fact]
+ public void ParseBlockReportsErrorIfForBlockUnterminatedAtEOF()
+ {
+ RunUnterminatedSimpleKeywordBlock("for");
+ }
+
+ [Fact]
+ public void ParseBlockReportsErrorIfForeachBlockUnterminatedAtEOF()
+ {
+ RunUnterminatedSimpleKeywordBlock("foreach");
+ }
+
+ [Fact]
+ public void ParseBlockReportsErrorIfWhileBlockUnterminatedAtEOF()
+ {
+ RunUnterminatedSimpleKeywordBlock("while");
+ }
+
+ [Fact]
+ public void ParseBlockReportsErrorIfSwitchBlockUnterminatedAtEOF()
+ {
+ RunUnterminatedSimpleKeywordBlock("switch");
+ }
+
+ [Fact]
+ public void ParseBlockReportsErrorIfLockBlockUnterminatedAtEOF()
+ {
+ RunUnterminatedSimpleKeywordBlock("lock");
+ }
+
+ [Fact]
+ public void ParseBlockReportsErrorIfUsingBlockUnterminatedAtEOF()
+ {
+ RunUnterminatedSimpleKeywordBlock("using");
+ }
+
+ [Fact]
+ public void ParseBlockRequiresControlFlowStatementsToHaveBraces()
+ {
+ string expectedMessage = String.Format(RazorResources.ParseError_SingleLine_ControlFlowStatements_Not_Allowed, "{", "<");
+ ParseBlockTest("if(foo) <p>Bar</p> else if(bar) <p>Baz</p> else <p>Boz</p>",
+ new StatementBlock(
+ Factory.Code("if(foo) ").AsStatement(),
+ new MarkupBlock(
+ Factory.Markup("<p>Bar</p> ").Accepts(AcceptedCharacters.None)
+ ),
+ Factory.Code("else if(bar) ").AsStatement(),
+ new MarkupBlock(
+ Factory.Markup("<p>Baz</p> ").Accepts(AcceptedCharacters.None)
+ ),
+ Factory.Code("else ").AsStatement(),
+ new MarkupBlock(
+ Factory.Markup("<p>Boz</p>").Accepts(AcceptedCharacters.None)
+ ),
+ Factory.EmptyCSharp().AsStatement()
+ ),
+ new RazorError(expectedMessage, 8, 0, 8),
+ new RazorError(expectedMessage, 32, 0, 32),
+ new RazorError(expectedMessage, 48, 0, 48));
+ }
+
+ [Fact]
+ public void ParseBlockIncludesUnexpectedCharacterInSingleStatementControlFlowStatementError()
+ {
+ ParseBlockTest("if(foo)) { var bar = foo; }",
+ new StatementBlock(
+ Factory.Code("if(foo)) { var bar = foo; }").AsStatement()
+ ),
+ new RazorError(
+ String.Format(RazorResources.ParseError_SingleLine_ControlFlowStatements_Not_Allowed,
+ "{", ")"),
+ new SourceLocation(7, 0, 7)));
+ }
+
+ [Fact]
+ public void ParseBlockOutputsErrorIfAtSignFollowedByLessThanSignAtStatementStart()
+ {
+ ParseBlockTest("if(foo) { @<p>Bar</p> }",
+ new StatementBlock(
+ Factory.Code("if(foo) {").AsStatement(),
+ new MarkupBlock(
+ Factory.Markup(" "),
+ Factory.MarkupTransition(),
+ Factory.Markup("<p>Bar</p> ").Accepts(AcceptedCharacters.None)
+ ),
+ Factory.Code("}").AsStatement()
+ ),
+ new RazorError(
+ RazorResources.ParseError_AtInCode_Must_Be_Followed_By_Colon_Paren_Or_Identifier_Start,
+ 10, 0, 10));
+ }
+
+ [Fact]
+ public void ParseBlockTerminatesIfBlockAtEOLWhenRecoveringFromMissingCloseParen()
+ {
+ ParseBlockTest(@"if(foo bar
+baz",
+ new StatementBlock(
+ Factory.Code("if(foo bar\r\n").AsStatement()
+ ),
+ new RazorError(
+ String.Format(RazorResources.ParseError_Expected_CloseBracket_Before_EOF,
+ "(", ")"),
+ new SourceLocation(2, 0, 2)));
+ }
+
+ [Fact]
+ public void ParseBlockTerminatesForeachBlockAtEOLWhenRecoveringFromMissingCloseParen()
+ {
+ ParseBlockTest(@"foreach(foo bar
+baz",
+ new StatementBlock(
+ Factory.Code("foreach(foo bar\r\n").AsStatement()
+ ),
+ new RazorError(
+ String.Format(RazorResources.ParseError_Expected_CloseBracket_Before_EOF,
+ "(", ")"),
+ new SourceLocation(7, 0, 7)));
+ }
+
+ [Fact]
+ public void ParseBlockTerminatesWhileClauseInDoStatementAtEOLWhenRecoveringFromMissingCloseParen()
+ {
+ ParseBlockTest(@"do { } while(foo bar
+baz",
+ new StatementBlock(
+ Factory.Code("do { } while(foo bar\r\n").AsStatement()
+ ),
+ new RazorError(
+ String.Format(RazorResources.ParseError_Expected_CloseBracket_Before_EOF,
+ "(", ")"),
+ new SourceLocation(12, 0, 12)));
+ }
+
+ [Fact]
+ public void ParseBlockTerminatesUsingBlockAtEOLWhenRecoveringFromMissingCloseParen()
+ {
+ ParseBlockTest(@"using(foo bar
+baz",
+ new StatementBlock(
+ Factory.Code("using(foo bar\r\n").AsStatement()
+ ),
+ new RazorError(
+ String.Format(RazorResources.ParseError_Expected_CloseBracket_Before_EOF,
+ "(", ")"),
+ new SourceLocation(5, 0, 5)));
+ }
+
+ [Fact]
+ public void ParseBlockResumesIfStatementAfterOpenParen()
+ {
+ ParseBlockTest(@"if(
+else { <p>Foo</p> }",
+ new StatementBlock(
+ Factory.Code("if(\r\nelse {").AsStatement(),
+ new MarkupBlock(
+ Factory.Markup(" <p>Foo</p> ").Accepts(AcceptedCharacters.None)
+ ),
+ Factory.Code("}").AsStatement().Accepts(AcceptedCharacters.None)
+ ),
+ new RazorError(
+ String.Format(RazorResources.ParseError_Expected_CloseBracket_Before_EOF,
+ "(", ")"),
+ new SourceLocation(2, 0, 2)));
+ }
+
+ [Fact]
+ public void ParseBlockTerminatesNormalCSharpStringsAtEOLIfEndQuoteMissing()
+ {
+ SingleSpanBlockTest(@"if(foo) {
+ var p = ""foo bar baz
+;
+}",
+ BlockType.Statement, SpanKind.Code,
+ new RazorError(RazorResources.ParseError_Unterminated_String_Literal, 23, 1, 12));
+ }
+
+ [Fact]
+ public void ParseBlockTerminatesNormalStringAtEndOfFile()
+ {
+ SingleSpanBlockTest("if(foo) { var foo = \"blah blah blah blah blah", BlockType.Statement, SpanKind.Code,
+ new RazorError(RazorResources.ParseError_Unterminated_String_Literal, 20, 0, 20),
+ new RazorError(String.Format(RazorResources.ParseError_Expected_EndOfBlock_Before_EOF, "if", '}', '{'), SourceLocation.Zero));
+ }
+
+ [Fact]
+ public void ParseBlockTerminatesVerbatimStringAtEndOfFile()
+ {
+ SingleSpanBlockTest(@"if(foo) { var foo = @""blah
+blah;
+<p>Foo</p>
+blah
+blah",
+ BlockType.Statement, SpanKind.Code,
+ new RazorError(RazorResources.ParseError_Unterminated_String_Literal, 20, 0, 20),
+ new RazorError(String.Format(RazorResources.ParseError_Expected_EndOfBlock_Before_EOF, "if", '}', '{'), SourceLocation.Zero));
+ }
+
+ [Fact]
+ public void ParseBlockCorrectlyParsesMarkupIncorrectyAssumedToBeWithinAStatement()
+ {
+ ParseBlockTest(@"if(foo) {
+ var foo = ""foo bar baz
+ <p>Foo is @foo</p>
+}",
+ new StatementBlock(
+ Factory.Code("if(foo) {\r\n var foo = \"foo bar baz\r\n ").AsStatement(),
+ new MarkupBlock(
+ Factory.Markup("<p>Foo is "),
+ new ExpressionBlock(
+ Factory.CodeTransition(),
+ Factory.Code("foo")
+ .AsImplicitExpression(CSharpCodeParser.DefaultKeywords)
+ .Accepts(AcceptedCharacters.NonWhiteSpace)),
+ Factory.Markup("</p>\r\n").Accepts(AcceptedCharacters.None)),
+ Factory.Code("}").AsStatement()
+ ),
+ new RazorError(
+ RazorResources.ParseError_Unterminated_String_Literal,
+ 25, 1, 14));
+ }
+
+ [Fact]
+ public void ParseBlockCorrectlyParsesAtSignInDelimitedBlock()
+ {
+ ParseBlockTest("(Request[\"description\"] ?? @photo.Description)",
+ new ExpressionBlock(
+ Factory.MetaCode("(").Accepts(AcceptedCharacters.None),
+ Factory.Code("Request[\"description\"] ?? @photo.Description").AsExpression(),
+ Factory.MetaCode(")").Accepts(AcceptedCharacters.None)
+ ));
+ }
+
+ private void RunUnterminatedSimpleKeywordBlock(string keyword)
+ {
+ SingleSpanBlockTest(keyword + " (foo) { var foo = bar; if(foo != null) { bar(); } ", BlockType.Statement, SpanKind.Code,
+ new RazorError(String.Format(RazorResources.ParseError_Expected_EndOfBlock_Before_EOF, keyword, '}', '{'), SourceLocation.Zero));
+ }
+ }
+}
diff --git a/test/System.Web.Razor.Test/Parser/CSharp/CSharpExplicitExpressionTest.cs b/test/System.Web.Razor.Test/Parser/CSharp/CSharpExplicitExpressionTest.cs
new file mode 100644
index 00000000..24156f30
--- /dev/null
+++ b/test/System.Web.Razor.Test/Parser/CSharp/CSharpExplicitExpressionTest.cs
@@ -0,0 +1,139 @@
+using System.Web.Razor.Parser.SyntaxTree;
+using System.Web.Razor.Resources;
+using System.Web.Razor.Test.Framework;
+using System.Web.Razor.Text;
+using Xunit;
+
+namespace System.Web.Razor.Test.Parser.CSharp
+{
+ public class CSharpExplicitExpressionTest : CsHtmlCodeParserTestBase
+ {
+ [Fact]
+ public void ParseBlockShouldOutputZeroLengthCodeSpanIfExplicitExpressionIsEmpty()
+ {
+ ParseBlockTest("@()",
+ new ExpressionBlock(
+ Factory.CodeTransition(),
+ Factory.MetaCode("(").Accepts(AcceptedCharacters.None),
+ Factory.EmptyCSharp().AsExpression(),
+ Factory.MetaCode(")").Accepts(AcceptedCharacters.None)
+ ));
+ }
+
+ [Fact]
+ public void ParseBlockShouldOutputZeroLengthCodeSpanIfEOFOccursAfterStartOfExplicitExpression()
+ {
+ ParseBlockTest("@(",
+ new ExpressionBlock(
+ Factory.CodeTransition(),
+ Factory.MetaCode("(").Accepts(AcceptedCharacters.None),
+ Factory.EmptyCSharp().AsExpression()
+ ),
+ new RazorError(
+ String.Format(RazorResources.ParseError_Expected_EndOfBlock_Before_EOF,
+ RazorResources.BlockName_ExplicitExpression,
+ ")", "("),
+ new SourceLocation(1, 0, 1)));
+ }
+
+ [Fact]
+ public void ParseBlockShouldAcceptEscapedQuoteInNonVerbatimStrings()
+ {
+ ParseBlockTest("@(\"\\\"\")",
+ new ExpressionBlock(
+ Factory.CodeTransition(),
+ Factory.MetaCode("(").Accepts(AcceptedCharacters.None),
+ Factory.Code("\"\\\"\"").AsExpression(),
+ Factory.MetaCode(")").Accepts(AcceptedCharacters.None)
+ ));
+ }
+
+ [Fact]
+ public void ParseBlockShouldAcceptEscapedQuoteInVerbatimStrings()
+ {
+ ParseBlockTest("@(@\"\"\"\")",
+ new ExpressionBlock(
+ Factory.CodeTransition(),
+ Factory.MetaCode("(").Accepts(AcceptedCharacters.None),
+ Factory.Code("@\"\"\"\"").AsExpression(),
+ Factory.MetaCode(")").Accepts(AcceptedCharacters.None)
+ ));
+ }
+
+ [Fact]
+ public void ParseBlockShouldAcceptMultipleRepeatedEscapedQuoteInVerbatimStrings()
+ {
+ ParseBlockTest("@(@\"\"\"\"\"\")",
+ new ExpressionBlock(
+ Factory.CodeTransition(),
+ Factory.MetaCode("(").Accepts(AcceptedCharacters.None),
+ Factory.Code("@\"\"\"\"\"\"").AsExpression(),
+ Factory.MetaCode(")").Accepts(AcceptedCharacters.None)
+ ));
+ }
+
+ [Fact]
+ public void ParseBlockShouldAcceptMultiLineVerbatimStrings()
+ {
+ ParseBlockTest(@"@(@""
+Foo
+Bar
+Baz
+"")",
+ new ExpressionBlock(
+ Factory.CodeTransition(),
+ Factory.MetaCode("(").Accepts(AcceptedCharacters.None),
+ Factory.Code("@\"\r\nFoo\r\nBar\r\nBaz\r\n\"").AsExpression(),
+ Factory.MetaCode(")").Accepts(AcceptedCharacters.None)
+ ));
+ }
+
+ [Fact]
+ public void ParseBlockShouldAcceptMultipleEscapedQuotesInNonVerbatimStrings()
+ {
+ ParseBlockTest("@(\"\\\"hello, world\\\"\")",
+ new ExpressionBlock(
+ Factory.CodeTransition(),
+ Factory.MetaCode("(").Accepts(AcceptedCharacters.None),
+ Factory.Code("\"\\\"hello, world\\\"\"").AsExpression(),
+ Factory.MetaCode(")").Accepts(AcceptedCharacters.None)
+ ));
+ }
+
+ [Fact]
+ public void ParseBlockShouldAcceptMultipleEscapedQuotesInVerbatimStrings()
+ {
+ ParseBlockTest("@(@\"\"\"hello, world\"\"\")",
+ new ExpressionBlock(
+ Factory.CodeTransition(),
+ Factory.MetaCode("(").Accepts(AcceptedCharacters.None),
+ Factory.Code("@\"\"\"hello, world\"\"\"").AsExpression(),
+ Factory.MetaCode(")").Accepts(AcceptedCharacters.None)
+ ));
+ }
+
+ [Fact]
+ public void ParseBlockShouldAcceptConsecutiveEscapedQuotesInNonVerbatimStrings()
+ {
+ ParseBlockTest("@(\"\\\"\\\"\")",
+ new ExpressionBlock(
+ Factory.CodeTransition(),
+ Factory.MetaCode("(").Accepts(AcceptedCharacters.None),
+ Factory.Code("\"\\\"\\\"\"").AsExpression(),
+ Factory.MetaCode(")").Accepts(AcceptedCharacters.None)
+ ));
+ }
+
+ [Fact]
+ public void ParseBlockShouldAcceptConsecutiveEscapedQuotesInVerbatimStrings()
+ {
+ ParseBlockTest("@(@\"\"\"\"\"\")",
+ new ExpressionBlock(
+ Factory.CodeTransition(),
+ Factory.MetaCode("(").Accepts(AcceptedCharacters.None),
+ Factory.Code("@\"\"\"\"\"\"").AsExpression(),
+ Factory.MetaCode(")").Accepts(AcceptedCharacters.None)
+ ));
+ }
+ }
+}
diff --git a/test/System.Web.Razor.Test/Parser/CSharp/CSharpHelperTest.cs b/test/System.Web.Razor.Test/Parser/CSharp/CSharpHelperTest.cs
new file mode 100644
index 00000000..0bb99afa
--- /dev/null
+++ b/test/System.Web.Razor.Test/Parser/CSharp/CSharpHelperTest.cs
@@ -0,0 +1,345 @@
+using System.Web.Razor.Generator;
+using System.Web.Razor.Parser;
+using System.Web.Razor.Parser.SyntaxTree;
+using System.Web.Razor.Resources;
+using System.Web.Razor.Test.Framework;
+using System.Web.Razor.Text;
+using Xunit;
+
+namespace System.Web.Razor.Test.Parser.CSharp
+{
+ public class CSharpHelperTest : CsHtmlMarkupParserTestBase
+ {
+ [Fact]
+ public void ParseHelperCorrectlyParsesHelperWithNoSpaceInBody()
+ {
+ ParseDocumentTest("@helper Foo(){@Bar()}",
+ new MarkupBlock(
+ Factory.EmptyHtml(),
+ new HelperBlock(new HelperCodeGenerator(new LocationTagged<string>("Foo(){", 8, 0, 8), headerComplete: true),
+ Factory.CodeTransition(),
+ Factory.MetaCode("helper ").Accepts(AcceptedCharacters.None),
+ Factory.Code("Foo(){").Hidden().Accepts(AcceptedCharacters.None),
+ new StatementBlock(
+ Factory.EmptyCSharp().AsStatement(),
+ new ExpressionBlock(
+ Factory.CodeTransition(),
+ Factory.Code("Bar()")
+ .AsImplicitExpression(CSharpCodeParser.DefaultKeywords, acceptTrailingDot: true)
+ .Accepts(AcceptedCharacters.NonWhiteSpace)),
+ Factory.EmptyCSharp().AsStatement()),
+ Factory.Code("}").Hidden().Accepts(AcceptedCharacters.None)),
+ Factory.EmptyHtml()));
+ }
+
+ [Fact]
+ public void ParseHelperCorrectlyParsesIncompleteHelperPreceedingCodeBlock()
+ {
+ ParseDocumentTest(@"@helper
+@{}",
+ new MarkupBlock(
+ Factory.EmptyHtml(),
+ new HelperBlock(
+ Factory.CodeTransition(),
+ Factory.MetaCode("helper")),
+ Factory.Markup("\r\n"),
+ new StatementBlock(
+ Factory.CodeTransition(),
+ Factory.MetaCode("{").Accepts(AcceptedCharacters.None),
+ Factory.EmptyCSharp().AsStatement(),
+ Factory.MetaCode("}").Accepts(AcceptedCharacters.None)),
+ Factory.EmptyHtml()),
+ new RazorError(
+ String.Format(
+ RazorResources.ParseError_Unexpected_Character_At_Helper_Name_Start,
+ RazorResources.ErrorComponent_Newline),
+ 7, 0, 7));
+ }
+
+ [Fact]
+ public void ParseHelperRequiresSpaceBeforeSignature()
+ {
+ ParseDocumentTest("@helper{",
+ new MarkupBlock(
+ Factory.EmptyHtml(),
+ new HelperBlock(
+ Factory.CodeTransition(),
+ Factory.MetaCode("helper")),
+ Factory.Markup("{")),
+ new RazorError(
+ String.Format(
+ RazorResources.ParseError_Unexpected_Character_At_Helper_Name_Start,
+ String.Format(RazorResources.ErrorComponent_Character, "{")),
+ 7, 0, 7));
+ }
+
+ [Fact]
+ public void ParseHelperOutputsErrorButContinuesIfLParenFoundAfterHelperKeyword()
+ {
+ ParseDocumentTest("@helper () {",
+ new MarkupBlock(
+ Factory.EmptyHtml(),
+ new HelperBlock(new HelperCodeGenerator(new LocationTagged<string>("() {", 8, 0, 8), headerComplete: true),
+ Factory.CodeTransition(),
+ Factory.MetaCode("helper ").Accepts(AcceptedCharacters.None),
+ Factory.Code("() {").Hidden().Accepts(AcceptedCharacters.None),
+ new StatementBlock(
+ Factory.EmptyCSharp()
+ .AsStatement()
+ .AutoCompleteWith("}")))),
+ new RazorError(
+ String.Format(
+ RazorResources.ParseError_Unexpected_Character_At_Helper_Name_Start,
+ String.Format(RazorResources.ErrorComponent_Character, "(")),
+ 8, 0, 8),
+ new RazorError(
+ String.Format(
+ RazorResources.ParseError_Expected_EndOfBlock_Before_EOF,
+ "helper", "}", "{"),
+ 1, 0, 1));
+ }
+
+ [Fact]
+ public void ParseHelperStatementOutputsMarkerHelperHeaderSpanOnceKeywordComplete()
+ {
+ ParseDocumentTest("@helper ",
+ new MarkupBlock(
+ Factory.EmptyHtml(),
+ new HelperBlock(new HelperCodeGenerator(new LocationTagged<string>(String.Empty, 8, 0, 8), headerComplete: false),
+ Factory.CodeTransition(),
+ Factory.MetaCode("helper ").Accepts(AcceptedCharacters.None),
+ Factory.EmptyCSharp().Hidden())),
+ new RazorError(
+ String.Format(
+ RazorResources.ParseError_Unexpected_Character_At_Helper_Name_Start,
+ RazorResources.ErrorComponent_EndOfFile),
+ 8, 0, 8));
+ }
+
+ [Fact]
+ public void ParseHelperStatementMarksHelperSpanAsCanGrowIfMissingTrailingSpace()
+ {
+ ParseDocumentTest("@helper",
+ new MarkupBlock(
+ Factory.EmptyHtml(),
+ new HelperBlock(
+ Factory.CodeTransition(),
+ Factory.MetaCode("helper").Accepts(AcceptedCharacters.Any))),
+ new RazorError(
+ String.Format(
+ RazorResources.ParseError_Unexpected_Character_At_Helper_Name_Start,
+ RazorResources.ErrorComponent_EndOfFile),
+ 7, 0, 7));
+ }
+
+ [Fact]
+ public void ParseHelperStatementCapturesWhitespaceToEndOfLineIfHelperStatementMissingName()
+ {
+ ParseDocumentTest(@"@helper
+ ",
+ new MarkupBlock(
+ Factory.EmptyHtml(),
+ new HelperBlock(new HelperCodeGenerator(new LocationTagged<string>(" ", 8, 0, 8), headerComplete: false),
+ Factory.CodeTransition(),
+ Factory.MetaCode("helper ").Accepts(AcceptedCharacters.None),
+ Factory.Code(" \r\n").Hidden()),
+ Factory.Markup(@" ")),
+ new RazorError(
+ String.Format(
+ RazorResources.ParseError_Unexpected_Character_At_Helper_Name_Start,
+ RazorResources.ErrorComponent_Newline),
+ 30, 0, 30));
+ }
+
+ [Fact]
+ public void ParseHelperStatementCapturesWhitespaceToEndOfLineIfHelperStatementMissingOpenParen()
+ {
+ ParseDocumentTest(@"@helper Foo
+ ",
+ new MarkupBlock(
+ Factory.EmptyHtml(),
+ new HelperBlock(new HelperCodeGenerator(new LocationTagged<string>("Foo ", 8, 0, 8), headerComplete: false),
+ Factory.CodeTransition(),
+ Factory.MetaCode("helper ").Accepts(AcceptedCharacters.None),
+ Factory.Code("Foo \r\n").Hidden()),
+ Factory.Markup(" ")),
+ new RazorError(
+ String.Format(RazorResources.ParseError_MissingCharAfterHelperName, "("),
+ 15, 0, 15));
+ }
+
+ [Fact]
+ public void ParseHelperStatementCapturesAllContentToEndOfFileIfHelperStatementMissingCloseParenInParameterList()
+ {
+ ParseDocumentTest(@"@helper Foo(Foo Bar
+Biz
+Boz",
+ new MarkupBlock(
+ Factory.EmptyHtml(),
+ new HelperBlock(new HelperCodeGenerator(new LocationTagged<string>("Foo(Foo Bar\r\nBiz\r\nBoz", 8, 0, 8), headerComplete: false),
+ Factory.CodeTransition(),
+ Factory.MetaCode("helper ").Accepts(AcceptedCharacters.None),
+ Factory.Code("Foo(Foo Bar\r\nBiz\r\nBoz").Hidden())),
+ new RazorError(
+ RazorResources.ParseError_UnterminatedHelperParameterList,
+ 11, 0, 11));
+ }
+
+ [Fact]
+ public void ParseHelperStatementCapturesWhitespaceToEndOfLineIfHelperStatementMissingOpenBraceAfterParameterList()
+ {
+ ParseDocumentTest(@"@helper Foo(string foo)
+",
+ new MarkupBlock(
+ Factory.EmptyHtml(),
+ new HelperBlock(new HelperCodeGenerator(new LocationTagged<string>("Foo(string foo) ", 8, 0, 8), headerComplete: false),
+ Factory.CodeTransition(),
+ Factory.MetaCode("helper ").Accepts(AcceptedCharacters.None),
+ Factory.Code("Foo(string foo) \r\n").Hidden())),
+ new RazorError(
+ String.Format(RazorResources.ParseError_MissingCharAfterHelperParameters, "{"),
+ 29, 1, 0));
+ }
+
+ [Fact]
+ public void ParseHelperStatementContinuesParsingHelperUntilEOF()
+ {
+ ParseDocumentTest(@"@helper Foo(string foo) {
+ <p>Foo</p>",
+ new MarkupBlock(
+ Factory.EmptyHtml(),
+ new HelperBlock(new HelperCodeGenerator(new LocationTagged<string>("Foo(string foo) {", 8, 0, 8), headerComplete: true),
+ Factory.CodeTransition(),
+ Factory.MetaCode("helper ").Accepts(AcceptedCharacters.None),
+ Factory.Code(@"Foo(string foo) {").Hidden().Accepts(AcceptedCharacters.None),
+ new StatementBlock(
+ Factory.Code(" \r\n")
+ .AsStatement()
+ .AutoCompleteWith("}"),
+ new MarkupBlock(
+ Factory.Markup(" <p>Foo</p>").Accepts(AcceptedCharacters.None)),
+ Factory.EmptyCSharp().AsStatement()))),
+ new RazorError(
+ String.Format(
+ RazorResources.ParseError_Expected_EndOfBlock_Before_EOF,
+ "helper", "}", "{"),
+ 1, 0, 1));
+ }
+
+ [Fact]
+ public void ParseHelperStatementCorrectlyParsesHelperWithEmbeddedCode()
+ {
+ ParseDocumentTest(@"@helper Foo(string foo) {
+ <p>@foo</p>
+}",
+ new MarkupBlock(
+ Factory.EmptyHtml(),
+ new HelperBlock(new HelperCodeGenerator(new LocationTagged<string>("Foo(string foo) {", 8, 0, 8), headerComplete: true),
+ Factory.CodeTransition(),
+ Factory.MetaCode("helper ").Accepts(AcceptedCharacters.None),
+ Factory.Code(@"Foo(string foo) {").Hidden().Accepts(AcceptedCharacters.None),
+ new StatementBlock(
+ Factory.Code(" \r\n").AsStatement(),
+ new MarkupBlock(
+ Factory.Markup(" <p>"),
+ new ExpressionBlock(
+ Factory.CodeTransition(),
+ Factory.Code("foo")
+ .AsImplicitExpression(CSharpCodeParser.DefaultKeywords)
+ .Accepts(AcceptedCharacters.NonWhiteSpace)),
+ Factory.Markup("</p>\r\n").Accepts(AcceptedCharacters.None)),
+ Factory.EmptyCSharp().AsStatement()),
+ Factory.Code("}").Hidden().Accepts(AcceptedCharacters.None)),
+ Factory.EmptyHtml()));
+ }
+
+ [Fact]
+ public void ParseHelperStatementCorrectlyParsesHelperWithNewlinesBetweenCloseParenAndOpenBrace()
+ {
+ ParseDocumentTest(@"@helper Foo(string foo)
+
+
+
+{
+ <p>@foo</p>
+}",
+ new MarkupBlock(
+ Factory.EmptyHtml(),
+ new HelperBlock(new HelperCodeGenerator(new LocationTagged<string>("Foo(string foo)\r\n\r\n\r\n\r\n{", 8, 0, 8), headerComplete: true),
+ Factory.CodeTransition(),
+ Factory.MetaCode("helper ").Accepts(AcceptedCharacters.None),
+ Factory.Code("Foo(string foo)\r\n\r\n\r\n\r\n{").Hidden().Accepts(AcceptedCharacters.None),
+ new StatementBlock(
+ Factory.Code(" \r\n").AsStatement(),
+ new MarkupBlock(
+ Factory.Markup(@" <p>"),
+ new ExpressionBlock(
+ Factory.CodeTransition(),
+ Factory.Code("foo")
+ .AsImplicitExpression(CSharpCodeParser.DefaultKeywords)
+ .Accepts(AcceptedCharacters.NonWhiteSpace)),
+ Factory.Markup("</p>\r\n").Accepts(AcceptedCharacters.None)),
+ Factory.EmptyCSharp().AsStatement()),
+ Factory.Code("}").Hidden().Accepts(AcceptedCharacters.None)),
+ Factory.EmptyHtml()));
+ }
+
+ [Fact]
+ public void ParseHelperStatementGivesWhitespaceAfterOpenBraceToMarkupInDesignMode()
+ {
+ ParseDocumentTest(@"@helper Foo(string foo) {
+ ",
+ new MarkupBlock(
+ Factory.EmptyHtml(),
+ new HelperBlock(new HelperCodeGenerator(new LocationTagged<string>("Foo(string foo) {", 8, 0, 8), headerComplete: true),
+ Factory.CodeTransition(),
+ Factory.MetaCode("helper ").Accepts(AcceptedCharacters.None),
+ Factory.Code(@"Foo(string foo) {").Hidden().Accepts(AcceptedCharacters.None),
+ new StatementBlock(
+ Factory.Code(" \r\n ")
+ .AsStatement()
+ .AutoCompleteWith("}")))),
+ designTimeParser: true,
+ expectedErrors: new[]
+ {
+ new RazorError(
+ String.Format(
+ RazorResources.ParseError_Expected_EndOfBlock_Before_EOF,
+ "helper", "}", "{"),
+ new SourceLocation(1, 0, 1))
+ });
+ }
+
+ [Fact]
+ public void ParseHelperAcceptsNestedHelpersButOutputsError()
+ {
+ ParseDocumentTest(@"@helper Foo(string foo) {
+ @helper Bar(string baz) {
+ }
+}",
+ new MarkupBlock(
+ Factory.EmptyHtml(),
+ new HelperBlock(new HelperCodeGenerator(new LocationTagged<string>("Foo(string foo) {", 8, 0, 8), headerComplete: true),
+ Factory.CodeTransition(),
+ Factory.MetaCode("helper ").Accepts(AcceptedCharacters.None),
+ Factory.Code(@"Foo(string foo) {").Hidden().Accepts(AcceptedCharacters.None),
+ new StatementBlock(
+ Factory.Code("\r\n ").AsStatement(),
+ new HelperBlock(new HelperCodeGenerator(new LocationTagged<string>("Bar(string baz) {", 39, 1, 12), headerComplete: true),
+ Factory.CodeTransition(),
+ Factory.MetaCode("helper ").Accepts(AcceptedCharacters.None),
+ Factory.Code(@"Bar(string baz) {").Hidden().Accepts(AcceptedCharacters.None),
+ new StatementBlock(
+ Factory.Code("\r\n ").AsStatement()),
+ Factory.Code("}").Hidden().Accepts(AcceptedCharacters.None)),
+ Factory.Code("\r\n").AsStatement()),
+ Factory.Code("}").Hidden().Accepts(AcceptedCharacters.None)),
+ Factory.EmptyHtml()),
+ designTimeParser: true,
+ expectedErrors: new[]
+ {
+ new RazorError(RazorResources.ParseError_Helpers_Cannot_Be_Nested, 38, 1, 11)
+ });
+ }
+ }
+}
diff --git a/test/System.Web.Razor.Test/Parser/CSharp/CSharpImplicitExpressionTest.cs b/test/System.Web.Razor.Test/Parser/CSharp/CSharpImplicitExpressionTest.cs
new file mode 100644
index 00000000..44a375c1
--- /dev/null
+++ b/test/System.Web.Razor.Test/Parser/CSharp/CSharpImplicitExpressionTest.cs
@@ -0,0 +1,201 @@
+using System.Web.Razor.Parser;
+using System.Web.Razor.Parser.SyntaxTree;
+using System.Web.Razor.Resources;
+using System.Web.Razor.Test.Framework;
+using System.Web.Razor.Text;
+using Xunit;
+
+namespace System.Web.Razor.Test.Parser.CSharp
+{
+ public class CSharpImplicitExpressionTest : CsHtmlCodeParserTestBase
+ {
+ private const string TestExtraKeyword = "model";
+
+ public override ParserBase CreateCodeParser()
+ {
+ return new CSharpCodeParser();
+ }
+
+ [Fact]
+ public void NestedImplicitExpression()
+ {
+ ParseBlockTest("if (true) { @foo }",
+ new StatementBlock(
+ Factory.Code("if (true) { ").AsStatement(),
+ new ExpressionBlock(
+ Factory.CodeTransition(),
+ Factory.Code("foo")
+ .AsImplicitExpression(CSharpCodeParser.DefaultKeywords, acceptTrailingDot: true)
+ .Accepts(AcceptedCharacters.NonWhiteSpace)),
+ Factory.Code(" }").AsStatement()));
+ }
+
+ [Fact]
+ public void ParseBlockAcceptsNonEnglishCharactersThatAreValidIdentifiers()
+ {
+ ImplicitExpressionTest("हळूँजद॔.", "हळूँजद॔");
+ }
+
+ [Fact]
+ public void ParseBlockOutputsZeroLengthCodeSpanIfInvalidCharacterFollowsTransition()
+ {
+ ParseBlockTest("@/",
+ new ExpressionBlock(
+ Factory.CodeTransition(),
+ Factory.EmptyCSharp()
+ .AsImplicitExpression(KeywordSet)
+ .Accepts(AcceptedCharacters.NonWhiteSpace)),
+ new RazorError(
+ String.Format(RazorResources.ParseError_Unexpected_Character_At_Start_Of_CodeBlock_CS, "/"),
+ new SourceLocation(1, 0, 1)));
+ }
+
+ [Fact]
+ public void ParseBlockOutputsZeroLengthCodeSpanIfEOFOccursAfterTransition()
+ {
+ ParseBlockTest("@",
+ new ExpressionBlock(
+ Factory.CodeTransition(),
+ Factory.EmptyCSharp()
+ .AsImplicitExpression(KeywordSet)
+ .Accepts(AcceptedCharacters.NonWhiteSpace)),
+ new RazorError(
+ RazorResources.ParseError_Unexpected_EndOfFile_At_Start_Of_CodeBlock,
+ new SourceLocation(1, 0, 1)));
+ }
+
+ [Fact]
+ public void ParseBlockSupportsSlashesWithinComplexImplicitExpressions()
+ {
+ ImplicitExpressionTest("DataGridColumn.Template(\"Years of Service\", e => (int)Math.Round((DateTime.Now - dt).TotalDays / 365))");
+ }
+
+ [Fact]
+ public void ParseBlockMethodParsesSingleIdentifierAsImplicitExpression()
+ {
+ ImplicitExpressionTest("foo");
+ }
+
+ [Fact]
+ public void ParseBlockMethodDoesNotAcceptSemicolonIfExpressionTerminatedByWhitespace()
+ {
+ ImplicitExpressionTest("foo ;", "foo");
+ }
+
+ [Fact]
+ public void ParseBlockMethodIgnoresSemicolonAtEndOfSimpleImplicitExpression()
+ {
+ RunTrailingSemicolonTest("foo");
+ }
+
+ [Fact]
+ public void ParseBlockMethodParsesDottedIdentifiersAsImplicitExpression()
+ {
+ ImplicitExpressionTest("foo.bar.baz");
+ }
+
+ [Fact]
+ public void ParseBlockMethodIgnoresSemicolonAtEndOfDottedIdentifiers()
+ {
+ RunTrailingSemicolonTest("foo.bar.baz");
+ }
+
+ [Fact]
+ public void ParseBlockMethodDoesNotIncludeDotAtEOFInImplicitExpression()
+ {
+ ImplicitExpressionTest("foo.bar.", "foo.bar");
+ }
+
+ [Fact]
+ public void ParseBlockMethodDoesNotIncludeDotFollowedByInvalidIdentifierCharacterInImplicitExpression()
+ {
+ ImplicitExpressionTest("foo.bar.0", "foo.bar");
+ ImplicitExpressionTest("foo.bar.</p>", "foo.bar");
+ }
+
+ [Fact]
+ public void ParseBlockMethodDoesNotIncludeSemicolonAfterDot()
+ {
+ ImplicitExpressionTest("foo.bar.;", "foo.bar");
+ }
+
+ [Fact]
+ public void ParseBlockMethodTerminatesAfterIdentifierUnlessFollowedByDotOrParenInImplicitExpression()
+ {
+ ImplicitExpressionTest("foo.bar</p>", "foo.bar");
+ }
+
+ [Fact]
+ public void ParseBlockProperlyParsesParenthesesAndBalancesThemInImplicitExpression()
+ {
+ ImplicitExpressionTest(@"foo().bar(""bi\""z"", 4)(""chained method; call"").baz(@""bo""""z"", '\'', () => { return 4; }, (4+5+new { foo = bar[4] }))");
+ }
+
+ [Fact]
+ public void ParseBlockProperlyParsesBracketsAndBalancesThemInImplicitExpression()
+ {
+ ImplicitExpressionTest(@"foo.bar[4 * (8 + 7)][""fo\""o""].baz");
+ }
+
+ [Fact]
+ public void ParseBlockTerminatesImplicitExpressionAtHtmlEndTag()
+ {
+ ImplicitExpressionTest("foo().bar.baz</p>zoop", "foo().bar.baz");
+ }
+
+ [Fact]
+ public void ParseBlockTerminatesImplicitExpressionAtHtmlStartTag()
+ {
+ ImplicitExpressionTest("foo().bar.baz<p>zoop", "foo().bar.baz");
+ }
+
+ [Fact]
+ public void ParseBlockTerminatesImplicitExpressionBeforeDotIfDotNotFollowedByIdentifierStartCharacter()
+ {
+ ImplicitExpressionTest("foo().bar.baz.42", "foo().bar.baz");
+ }
+
+ [Fact]
+ public void ParseBlockStopsBalancingParenthesesAtEOF()
+ {
+ ImplicitExpressionTest("foo(()", "foo(()",
+ acceptedCharacters: AcceptedCharacters.Any,
+ errors: new RazorError(String.Format(RazorResources.ParseError_Expected_CloseBracket_Before_EOF, "(", ")"), new SourceLocation(4, 0, 4)));
+ }
+
+ [Fact]
+ public void ParseBlockTerminatesImplicitExpressionIfCloseParenFollowedByAnyWhiteSpace()
+ {
+ ImplicitExpressionTest("foo.bar() (baz)", "foo.bar()");
+ }
+
+ [Fact]
+ public void ParseBlockTerminatesImplicitExpressionIfIdentifierFollowedByAnyWhiteSpace()
+ {
+ ImplicitExpressionTest("foo .bar() (baz)", "foo");
+ }
+
+ [Fact]
+ public void ParseBlockTerminatesImplicitExpressionAtLastValidPointIfDotFollowedByWhitespace()
+ {
+ ImplicitExpressionTest("foo. bar() (baz)", "foo");
+ }
+
+ [Fact]
+ public void ParseBlockOutputExpressionIfModuleTokenNotFollowedByBrace()
+ {
+ ImplicitExpressionTest("module.foo()");
+ }
+
+ private void RunTrailingSemicolonTest(string expr)
+ {
+ ParseBlockTest(SyntaxConstants.TransitionString + expr + ";",
+ new ExpressionBlock(
+ Factory.CodeTransition(),
+ Factory.Code(expr)
+ .AsImplicitExpression(KeywordSet)
+ .Accepts(AcceptedCharacters.NonWhiteSpace)
+ ));
+ }
+ }
+}
diff --git a/test/System.Web.Razor.Test/Parser/CSharp/CSharpLayoutDirectiveTest.cs b/test/System.Web.Razor.Test/Parser/CSharp/CSharpLayoutDirectiveTest.cs
new file mode 100644
index 00000000..ad444764
--- /dev/null
+++ b/test/System.Web.Razor.Test/Parser/CSharp/CSharpLayoutDirectiveTest.cs
@@ -0,0 +1,84 @@
+using System.Web.Razor.Editor;
+using System.Web.Razor.Generator;
+using System.Web.Razor.Parser;
+using System.Web.Razor.Parser.SyntaxTree;
+using System.Web.Razor.Test.Framework;
+using Xunit;
+using Xunit.Extensions;
+
+namespace System.Web.Razor.Test.Parser.CSharp
+{
+ public class CSharpLayoutDirectiveTest : CsHtmlCodeParserTestBase
+ {
+ [Theory]
+ [InlineData("Layout")]
+ [InlineData("LAYOUT")]
+ [InlineData("layOut")]
+ [InlineData("LayOut")]
+ private void LayoutKeywordIsCaseSensitive(string word)
+ {
+ ParseBlockTest(word,
+ new ExpressionBlock(
+ Factory.Code(word)
+ .AsImplicitExpression(CSharpCodeParser.DefaultKeywords)
+ .Accepts(AcceptedCharacters.NonWhiteSpace)
+ ));
+ }
+
+ [Fact]
+ public void LayoutDirectiveAcceptsAllTextToEndOfLine()
+ {
+ ParseBlockTest(@"@layout Foo Bar Baz",
+ new DirectiveBlock(
+ Factory.CodeTransition(),
+ Factory.MetaCode("layout ").Accepts(AcceptedCharacters.None),
+ Factory.MetaCode("Foo Bar Baz")
+ .With(new SetLayoutCodeGenerator("Foo Bar Baz"))
+ .WithEditorHints(EditorHints.VirtualPath | EditorHints.LayoutPage)
+ )
+ );
+ }
+
+ [Fact]
+ public void LayoutDirectiveAcceptsAnyIfNoWhitespaceFollowingLayoutKeyword()
+ {
+ ParseBlockTest(@"@layout",
+ new DirectiveBlock(
+ Factory.CodeTransition(),
+ Factory.MetaCode("layout")
+ )
+ );
+ }
+
+ [Fact]
+ public void LayoutDirectiveOutputsMarkerSpanIfAnyWhitespaceAfterLayoutKeyword()
+ {
+ ParseBlockTest(@"@layout ",
+ new DirectiveBlock(
+ Factory.CodeTransition(),
+ Factory.MetaCode("layout ").Accepts(AcceptedCharacters.None),
+ Factory.EmptyCSharp()
+ .AsMetaCode()
+ .With(new SetLayoutCodeGenerator(String.Empty))
+ .WithEditorHints(EditorHints.VirtualPath | EditorHints.LayoutPage)
+ )
+ );
+ }
+
+ [Fact]
+ public void LayoutDirectiveAcceptsTrailingNewlineButDoesNotIncludeItInLayoutPath()
+ {
+ ParseBlockTest(@"@layout Foo
+",
+ new DirectiveBlock(
+ Factory.CodeTransition(),
+ Factory.MetaCode("layout ").Accepts(AcceptedCharacters.None),
+ Factory.MetaCode("Foo\r\n")
+ .With(new SetLayoutCodeGenerator("Foo"))
+ .Accepts(AcceptedCharacters.None)
+ .WithEditorHints(EditorHints.VirtualPath | EditorHints.LayoutPage)
+ )
+ );
+ }
+ }
+}
diff --git a/test/System.Web.Razor.Test/Parser/CSharp/CSharpNestedStatementsTest.cs b/test/System.Web.Razor.Test/Parser/CSharp/CSharpNestedStatementsTest.cs
new file mode 100644
index 00000000..7e8f2bba
--- /dev/null
+++ b/test/System.Web.Razor.Test/Parser/CSharp/CSharpNestedStatementsTest.cs
@@ -0,0 +1,100 @@
+using System.Web.Razor.Generator;
+using System.Web.Razor.Parser;
+using System.Web.Razor.Parser.SyntaxTree;
+using System.Web.Razor.Test.Framework;
+using Xunit;
+
+namespace System.Web.Razor.Test.Parser.CSharp
+{
+ public class CSharpNestedStatementsTest : CsHtmlCodeParserTestBase
+ {
+ [Fact]
+ public void NestedSimpleStatement()
+ {
+ ParseBlockTest("@while(true) { foo(); }",
+ new StatementBlock(
+ Factory.CodeTransition(),
+ Factory.Code("while(true) { foo(); }")
+ .AsStatement()
+ .Accepts(AcceptedCharacters.None)));
+ }
+
+ [Fact]
+ public void NestedKeywordStatement()
+ {
+ ParseBlockTest("@while(true) { for(int i = 0; i < 10; i++) { foo(); } }",
+ new StatementBlock(
+ Factory.CodeTransition(),
+ Factory.Code("while(true) { for(int i = 0; i < 10; i++) { foo(); } }")
+ .AsStatement()
+ .Accepts(AcceptedCharacters.None)));
+ }
+
+ [Fact]
+ public void NestedCodeBlock()
+ {
+ ParseBlockTest("@while(true) { { { { foo(); } } } }",
+ new StatementBlock(
+ Factory.CodeTransition(),
+ Factory.Code("while(true) { { { { foo(); } } } }")
+ .AsStatement()
+ .Accepts(AcceptedCharacters.None)));
+ }
+
+ [Fact]
+ public void NestedImplicitExpression()
+ {
+ ParseBlockTest("@while(true) { @foo }",
+ new StatementBlock(
+ Factory.CodeTransition(),
+ Factory.Code("while(true) { ")
+ .AsStatement(),
+ new ExpressionBlock(
+ Factory.CodeTransition(),
+ Factory.Code("foo")
+ .AsImplicitExpression(CSharpCodeParser.DefaultKeywords, acceptTrailingDot: true)
+ .Accepts(AcceptedCharacters.NonWhiteSpace)),
+ Factory.Code(" }")
+ .AsStatement()
+ .Accepts(AcceptedCharacters.None)));
+ }
+
+ [Fact]
+ public void NestedExplicitExpression()
+ {
+ ParseBlockTest("@while(true) { @(foo) }",
+ new StatementBlock(
+ Factory.CodeTransition(),
+ Factory.Code("while(true) { ")
+ .AsStatement(),
+ new ExpressionBlock(
+ Factory.CodeTransition(),
+ Factory.MetaCode("(")
+ .Accepts(AcceptedCharacters.None),
+ Factory.Code("foo")
+ .AsExpression(),
+ Factory.MetaCode(")")
+ .Accepts(AcceptedCharacters.None)),
+ Factory.Code(" }")
+ .AsStatement()
+ .Accepts(AcceptedCharacters.None)));
+ }
+
+ [Fact]
+ public void NestedMarkupBlock()
+ {
+ ParseBlockTest("@while(true) { <p>Hello</p> }",
+ new StatementBlock(
+ Factory.CodeTransition(),
+ Factory.Code("while(true) {")
+ .AsStatement(),
+ new MarkupBlock(
+ Factory.Markup(" <p>Hello</p> ")
+ .With(new MarkupCodeGenerator())
+ .Accepts(AcceptedCharacters.None)),
+ Factory.Code("}")
+ .AsStatement()
+ .Accepts(AcceptedCharacters.None)));
+ }
+ }
+}
diff --git a/test/System.Web.Razor.Test/Parser/CSharp/CSharpRazorCommentsTest.cs b/test/System.Web.Razor.Test/Parser/CSharp/CSharpRazorCommentsTest.cs
new file mode 100644
index 00000000..7942c9e6
--- /dev/null
+++ b/test/System.Web.Razor.Test/Parser/CSharp/CSharpRazorCommentsTest.cs
@@ -0,0 +1,172 @@
+using System.Web.Razor.Parser;
+using System.Web.Razor.Parser.SyntaxTree;
+using System.Web.Razor.Resources;
+using System.Web.Razor.Test.Framework;
+using System.Web.Razor.Tokenizer.Symbols;
+using Xunit;
+
+namespace System.Web.Razor.Test.Parser.CSharp
+{
+ public class CSharpRazorCommentsTest : CsHtmlMarkupParserTestBase
+ {
+ [Fact]
+ public void UnterminatedRazorComment()
+ {
+ ParseDocumentTest("@*",
+ new MarkupBlock(
+ Factory.EmptyHtml(),
+ new CommentBlock(
+ Factory.MarkupTransition(HtmlSymbolType.RazorCommentTransition)
+ .Accepts(AcceptedCharacters.None),
+ Factory.MetaMarkup("*", HtmlSymbolType.RazorCommentStar)
+ .Accepts(AcceptedCharacters.None),
+ Factory.Span(SpanKind.Comment, new HtmlSymbol(
+ Factory.LocationTracker.CurrentLocation,
+ String.Empty,
+ HtmlSymbolType.Unknown))
+ .Accepts(AcceptedCharacters.Any))),
+ new RazorError(RazorResources.ParseError_RazorComment_Not_Terminated, 0, 0, 0));
+ }
+
+ [Fact]
+ public void EmptyRazorComment()
+ {
+ ParseDocumentTest("@**@",
+ new MarkupBlock(
+ Factory.EmptyHtml(),
+ new CommentBlock(
+ Factory.MarkupTransition(HtmlSymbolType.RazorCommentTransition)
+ .Accepts(AcceptedCharacters.None),
+ Factory.MetaMarkup("*", HtmlSymbolType.RazorCommentStar)
+ .Accepts(AcceptedCharacters.None),
+ Factory.Span(SpanKind.Comment, new HtmlSymbol(
+ Factory.LocationTracker.CurrentLocation,
+ String.Empty,
+ HtmlSymbolType.Unknown))
+ .Accepts(AcceptedCharacters.Any),
+ Factory.MetaMarkup("*", HtmlSymbolType.RazorCommentStar)
+ .Accepts(AcceptedCharacters.None),
+ Factory.MarkupTransition(HtmlSymbolType.RazorCommentTransition)
+ .Accepts(AcceptedCharacters.None)),
+ Factory.EmptyHtml()));
+ }
+
+ [Fact]
+ public void RazorCommentInImplicitExpressionMethodCall()
+ {
+ ParseDocumentTest(@"@foo(
+@**@
+",
+ new MarkupBlock(
+ Factory.EmptyHtml(),
+ new ExpressionBlock(
+ Factory.CodeTransition(),
+ Factory.Code("foo(\r\n")
+ .AsImplicitExpression(CSharpCodeParser.DefaultKeywords),
+ new CommentBlock(
+ Factory.CodeTransition(CSharpSymbolType.RazorCommentTransition)
+ .Accepts(AcceptedCharacters.None),
+ Factory.MetaCode("*", CSharpSymbolType.RazorCommentStar)
+ .Accepts(AcceptedCharacters.None),
+ Factory.Span(SpanKind.Comment, new CSharpSymbol(
+ Factory.LocationTracker.CurrentLocation,
+ String.Empty,
+ CSharpSymbolType.Unknown))
+ .Accepts(AcceptedCharacters.Any),
+ Factory.MetaCode("*", CSharpSymbolType.RazorCommentStar)
+ .Accepts(AcceptedCharacters.None),
+ Factory.CodeTransition(CSharpSymbolType.RazorCommentTransition)
+ .Accepts(AcceptedCharacters.None)),
+ Factory.Code("\r\n")
+ .AsImplicitExpression(CSharpCodeParser.DefaultKeywords))),
+ new RazorError(
+ String.Format(RazorResources.ParseError_Expected_CloseBracket_Before_EOF, "(", ")"),
+ 4, 0, 4));
+ }
+
+ [Fact]
+ public void UnterminatedRazorCommentInImplicitExpressionMethodCall()
+ {
+ ParseDocumentTest("@foo(@*",
+ new MarkupBlock(
+ Factory.EmptyHtml(),
+ new ExpressionBlock(
+ Factory.CodeTransition(),
+ Factory.Code("foo(")
+ .AsImplicitExpression(CSharpCodeParser.DefaultKeywords),
+ new CommentBlock(
+ Factory.CodeTransition(CSharpSymbolType.RazorCommentTransition)
+ .Accepts(AcceptedCharacters.None),
+ Factory.MetaCode("*", CSharpSymbolType.RazorCommentStar)
+ .Accepts(AcceptedCharacters.None),
+ Factory.Span(SpanKind.Comment, new CSharpSymbol(
+ Factory.LocationTracker.CurrentLocation,
+ String.Empty,
+ CSharpSymbolType.Unknown))
+ .Accepts(AcceptedCharacters.Any)))),
+ new RazorError(RazorResources.ParseError_RazorComment_Not_Terminated, 5, 0, 5),
+ new RazorError(String.Format(RazorResources.ParseError_Expected_CloseBracket_Before_EOF, "(", ")"), 4, 0, 4));
+ }
+
+ [Fact]
+ public void RazorCommentInVerbatimBlock()
+ {
+ ParseDocumentTest(@"@{
+ <text
+ @**@
+}",
+ new MarkupBlock(
+ Factory.EmptyHtml(),
+ new StatementBlock(
+ Factory.CodeTransition(),
+ Factory.MetaCode("{").Accepts(AcceptedCharacters.None),
+ Factory.Code("\r\n").AsStatement(),
+ new MarkupBlock(
+ Factory.Markup(" "),
+ Factory.MarkupTransition("<text").Accepts(AcceptedCharacters.Any),
+ Factory.Markup("\r\n "),
+ new CommentBlock(
+ Factory.MarkupTransition(HtmlSymbolType.RazorCommentTransition)
+ .Accepts(AcceptedCharacters.None),
+ Factory.MetaMarkup("*", HtmlSymbolType.RazorCommentStar)
+ .Accepts(AcceptedCharacters.None),
+ Factory.Span(SpanKind.Comment, new HtmlSymbol(
+ Factory.LocationTracker.CurrentLocation,
+ String.Empty,
+ HtmlSymbolType.Unknown))
+ .Accepts(AcceptedCharacters.Any),
+ Factory.MetaMarkup("*", HtmlSymbolType.RazorCommentStar)
+ .Accepts(AcceptedCharacters.None),
+ Factory.MarkupTransition(HtmlSymbolType.RazorCommentTransition)
+ .Accepts(AcceptedCharacters.None)),
+ Factory.Markup("\r\n}")))),
+ new RazorError(RazorResources.ParseError_TextTagCannotContainAttributes, 8, 1, 4),
+ new RazorError(String.Format(RazorResources.ParseError_MissingEndTag, "text"), 8, 1, 4),
+ new RazorError(String.Format(RazorResources.ParseError_Expected_EndOfBlock_Before_EOF, RazorResources.BlockName_Code, "}", "{"), 1, 0, 1));
+ }
+
+ [Fact]
+ public void UnterminatedRazorCommentInVerbatimBlock()
+ {
+ ParseDocumentTest("@{@*",
+ new MarkupBlock(
+ Factory.EmptyHtml(),
+ new StatementBlock(
+ Factory.CodeTransition(),
+ Factory.MetaCode("{").Accepts(AcceptedCharacters.None),
+ Factory.EmptyCSharp()
+ .AsStatement(),
+ new CommentBlock(
+ Factory.CodeTransition(CSharpSymbolType.RazorCommentTransition)
+ .Accepts(AcceptedCharacters.None),
+ Factory.MetaCode("*", CSharpSymbolType.RazorCommentStar)
+ .Accepts(AcceptedCharacters.None),
+ Factory.Span(SpanKind.Comment, new CSharpSymbol(Factory.LocationTracker.CurrentLocation,
+ String.Empty,
+ CSharpSymbolType.Unknown))
+ .Accepts(AcceptedCharacters.Any)))),
+ new RazorError(RazorResources.ParseError_RazorComment_Not_Terminated, 2, 0, 2),
+ new RazorError(String.Format(RazorResources.ParseError_Expected_EndOfBlock_Before_EOF, RazorResources.BlockName_Code, "}", "{"), 1, 0, 1));
+ }
+ }
+}
diff --git a/test/System.Web.Razor.Test/Parser/CSharp/CSharpReservedWordsTest.cs b/test/System.Web.Razor.Test/Parser/CSharp/CSharpReservedWordsTest.cs
new file mode 100644
index 00000000..cc6bfdde
--- /dev/null
+++ b/test/System.Web.Razor.Test/Parser/CSharp/CSharpReservedWordsTest.cs
@@ -0,0 +1,41 @@
+using System.Web.Razor.Parser;
+using System.Web.Razor.Parser.SyntaxTree;
+using System.Web.Razor.Resources;
+using System.Web.Razor.Test.Framework;
+using System.Web.Razor.Text;
+using Xunit.Extensions;
+
+namespace System.Web.Razor.Test.Parser.CSharp
+{
+ public class CSharpReservedWordsTest : CsHtmlCodeParserTestBase
+ {
+ [Theory]
+ [InlineData("namespace")]
+ [InlineData("class")]
+ public void ReservedWords(string word)
+ {
+ ParseBlockTest(word,
+ new DirectiveBlock(
+ Factory.MetaCode(word).Accepts(AcceptedCharacters.None)
+ ),
+ new RazorError(String.Format(RazorResources.ParseError_ReservedWord, word), SourceLocation.Zero));
+ }
+
+ [Theory]
+ [InlineData("Namespace")]
+ [InlineData("Class")]
+ [InlineData("NAMESPACE")]
+ [InlineData("CLASS")]
+ [InlineData("nameSpace")]
+ [InlineData("NameSpace")]
+ private void ReservedWordsAreCaseSensitive(string word)
+ {
+ ParseBlockTest(word,
+ new ExpressionBlock(
+ Factory.Code(word)
+ .AsImplicitExpression(CSharpCodeParser.DefaultKeywords)
+ .Accepts(AcceptedCharacters.NonWhiteSpace)
+ ));
+ }
+ }
+}
diff --git a/test/System.Web.Razor.Test/Parser/CSharp/CSharpSectionTest.cs b/test/System.Web.Razor.Test/Parser/CSharp/CSharpSectionTest.cs
new file mode 100644
index 00000000..4239354c
--- /dev/null
+++ b/test/System.Web.Razor.Test/Parser/CSharp/CSharpSectionTest.cs
@@ -0,0 +1,298 @@
+using System.Web.Razor.Generator;
+using System.Web.Razor.Parser;
+using System.Web.Razor.Parser.SyntaxTree;
+using System.Web.Razor.Resources;
+using System.Web.Razor.Test.Framework;
+using Xunit;
+
+namespace System.Web.Razor.Test.Parser.CSharp
+{
+ public class CSharpSectionTest : CsHtmlMarkupParserTestBase
+ {
+ [Fact]
+ public void ParseSectionBlockCapturesNewlineImmediatelyFollowing()
+ {
+ ParseDocumentTest(@"@section
+",
+ new MarkupBlock(
+ Factory.EmptyHtml(),
+ new SectionBlock(new SectionCodeGenerator(String.Empty),
+ Factory.CodeTransition(),
+ Factory.MetaCode("section\r\n"))),
+ new RazorError(
+ String.Format(
+ RazorResources.ParseError_Unexpected_Character_At_Section_Name_Start,
+ RazorResources.ErrorComponent_EndOfFile),
+ 10, 1, 0));
+ }
+
+ [Fact]
+ public void ParseSectionBlockCapturesWhitespaceToEndOfLineInSectionStatementMissingOpenBrace()
+ {
+ ParseDocumentTest(@"@section Foo
+ ",
+ new MarkupBlock(
+ Factory.EmptyHtml(),
+ new SectionBlock(new SectionCodeGenerator("Foo"),
+ Factory.CodeTransition(),
+ Factory.MetaCode("section Foo \r\n")),
+ Factory.Markup(@" ")),
+ new RazorError(RazorResources.ParseError_MissingOpenBraceAfterSection, 12, 0, 12));
+ }
+
+ [Fact]
+ public void ParseSectionBlockCapturesWhitespaceToEndOfLineInSectionStatementMissingName()
+ {
+ ParseDocumentTest(@"@section
+ ",
+ new MarkupBlock(
+ Factory.EmptyHtml(),
+ new SectionBlock(new SectionCodeGenerator(String.Empty),
+ Factory.CodeTransition(),
+ Factory.MetaCode("section \r\n")),
+ Factory.Markup(@" ")),
+ new RazorError(
+ String.Format(
+ RazorResources.ParseError_Unexpected_Character_At_Section_Name_Start,
+ RazorResources.ErrorComponent_EndOfFile),
+ 23, 1, 4));
+ }
+
+ [Fact]
+ public void ParseSectionBlockIgnoresSectionUnlessAllLowerCase()
+ {
+ ParseDocumentTest("@Section foo",
+ new MarkupBlock(
+ Factory.EmptyHtml(),
+ new ExpressionBlock(
+ Factory.CodeTransition(),
+ Factory.Code("Section")
+ .AsImplicitExpression(CSharpCodeParser.DefaultKeywords)
+ .Accepts(AcceptedCharacters.NonWhiteSpace)),
+ Factory.Markup(" foo")));
+ }
+
+ [Fact]
+ public void ParseSectionBlockReportsErrorAndTerminatesSectionBlockIfKeywordNotFollowedByIdentifierStartCharacter()
+ {
+ ParseDocumentTest("@section 9 { <p>Foo</p> }",
+ new MarkupBlock(
+ Factory.EmptyHtml(),
+ new SectionBlock(new SectionCodeGenerator(String.Empty),
+ Factory.CodeTransition(),
+ Factory.MetaCode("section ")),
+ Factory.Markup("9 { <p>Foo</p> }")),
+ new RazorError(
+ String.Format(
+ RazorResources.ParseError_Unexpected_Character_At_Section_Name_Start,
+ String.Format(RazorResources.ErrorComponent_Character, "9")),
+ 9, 0, 9));
+ }
+
+ [Fact]
+ public void ParseSectionBlockReportsErrorAndTerminatesSectionBlockIfNameNotFollowedByOpenBrace()
+ {
+ ParseDocumentTest("@section foo-bar { <p>Foo</p> }",
+ new MarkupBlock(
+ Factory.EmptyHtml(),
+ new SectionBlock(new SectionCodeGenerator("foo"),
+ Factory.CodeTransition(),
+ Factory.MetaCode("section foo")),
+ Factory.Markup("-bar { <p>Foo</p> }")),
+ new RazorError(RazorResources.ParseError_MissingOpenBraceAfterSection, 12, 0, 12));
+ }
+
+ [Fact]
+ public void ParserOutputsErrorOnNestedSections()
+ {
+ ParseDocumentTest("@section foo { @section bar { <p>Foo</p> } }",
+ new MarkupBlock(
+ Factory.EmptyHtml(),
+ new SectionBlock(new SectionCodeGenerator("foo"),
+ Factory.CodeTransition(),
+ Factory.MetaCode("section foo {")
+ .AutoCompleteWith(null, atEndOfSpan: true),
+ new MarkupBlock(
+ Factory.Markup(" "),
+ new SectionBlock(new SectionCodeGenerator("bar"),
+ Factory.CodeTransition(),
+ Factory.MetaCode("section bar {")
+ .AutoCompleteWith(null, atEndOfSpan: true),
+ new MarkupBlock(
+ Factory.Markup(" <p>Foo</p> ")),
+ Factory.MetaCode("}").Accepts(AcceptedCharacters.None)),
+ Factory.Markup(" ")),
+ Factory.MetaCode("}").Accepts(AcceptedCharacters.None)),
+ Factory.EmptyHtml()),
+ new RazorError(
+ String.Format(
+ RazorResources.ParseError_Sections_Cannot_Be_Nested,
+ RazorResources.SectionExample_CS),
+ 23, 0, 23));
+ }
+
+ [Fact]
+ public void ParseSectionBlockHandlesEOFAfterOpenBrace()
+ {
+ ParseDocumentTest("@section foo {",
+ new MarkupBlock(
+ Factory.EmptyHtml(),
+ new SectionBlock(new SectionCodeGenerator("foo"),
+ Factory.CodeTransition(),
+ Factory.MetaCode("section foo {")
+ .AutoCompleteWith("}", atEndOfSpan: true),
+ new MarkupBlock())),
+ new RazorError(
+ String.Format(RazorResources.ParseError_Expected_X, "}"),
+ 14, 0, 14));
+ }
+
+ [Fact]
+ public void ParseSectionBlockHandlesUnterminatedSection()
+ {
+ ParseDocumentTest("@section foo { <p>Foo{}</p>",
+ new MarkupBlock(
+ Factory.EmptyHtml(),
+ new SectionBlock(new SectionCodeGenerator("foo"),
+ Factory.CodeTransition(),
+ Factory.MetaCode("section foo {")
+ .AutoCompleteWith("}", atEndOfSpan: true),
+ new MarkupBlock(
+ // Need to provide the markup span as fragments, since the parser will split the {} into separate symbols.
+ Factory.Markup(" <p>Foo", "{", "}", "</p>")))),
+ new RazorError(
+ String.Format(RazorResources.ParseError_Expected_X, "}"),
+ 27, 0, 27));
+ }
+
+ [Fact]
+ public void ParseSectionBlockReportsErrorAndAcceptsWhitespaceToEndOfLineIfSectionNotFollowedByOpenBrace()
+ {
+ ParseDocumentTest(@"@section foo
+",
+ new MarkupBlock(
+ Factory.EmptyHtml(),
+ new SectionBlock(new SectionCodeGenerator("foo"),
+ Factory.CodeTransition(),
+ Factory.MetaCode("section foo \r\n"))),
+ new RazorError(RazorResources.ParseError_MissingOpenBraceAfterSection, 12, 0, 12));
+ }
+
+ [Fact]
+ public void ParseSectionBlockAcceptsOpenBraceMultipleLinesBelowSectionName()
+ {
+ ParseDocumentTest(@"@section foo
+
+
+
+
+
+{
+<p>Foo</p>
+}",
+ new MarkupBlock(
+ Factory.EmptyHtml(),
+ new SectionBlock(new SectionCodeGenerator("foo"),
+ Factory.CodeTransition(),
+ Factory.MetaCode("section foo \r\n\r\n\r\n\r\n\r\n\r\n{")
+ .AutoCompleteWith(null, atEndOfSpan: true),
+ new MarkupBlock(
+ Factory.Markup("\r\n<p>Foo</p>\r\n")),
+ Factory.MetaCode("}").Accepts(AcceptedCharacters.None)),
+ Factory.EmptyHtml()));
+ }
+
+ [Fact]
+ public void ParseSectionBlockParsesNamedSectionCorrectly()
+ {
+ ParseDocumentTest("@section foo { <p>Foo</p> }",
+ new MarkupBlock(
+ Factory.EmptyHtml(),
+ new SectionBlock(new SectionCodeGenerator("foo"),
+ Factory.CodeTransition(),
+ Factory.MetaCode("section foo {")
+ .AutoCompleteWith(null, atEndOfSpan: true),
+ new MarkupBlock(
+ Factory.Markup(" <p>Foo</p> ")),
+ Factory.MetaCode("}").Accepts(AcceptedCharacters.None)),
+ Factory.EmptyHtml()));
+ }
+
+ [Fact]
+ public void ParseSectionBlockDoesNotRequireSpaceBetweenSectionNameAndOpenBrace()
+ {
+ ParseDocumentTest("@section foo{ <p>Foo</p> }",
+ new MarkupBlock(
+ Factory.EmptyHtml(),
+ new SectionBlock(new SectionCodeGenerator("foo"),
+ Factory.CodeTransition(),
+ Factory.MetaCode("section foo{")
+ .AutoCompleteWith(null, atEndOfSpan: true),
+ new MarkupBlock(
+ Factory.Markup(" <p>Foo</p> ")),
+ Factory.MetaCode("}").Accepts(AcceptedCharacters.None)),
+ Factory.EmptyHtml()));
+ }
+
+ [Fact]
+ public void ParseSectionBlockBalancesBraces()
+ {
+ ParseDocumentTest("@section foo { <script>(function foo() { return 1; })();</script> }",
+ new MarkupBlock(
+ Factory.EmptyHtml(),
+ new SectionBlock(new SectionCodeGenerator("foo"),
+ Factory.CodeTransition(),
+ Factory.MetaCode("section foo {")
+ .AutoCompleteWith(null, atEndOfSpan: true),
+ new MarkupBlock(
+ Factory.Markup(" <script>(function foo() ", "{", " return 1; ", "}", ")();</script> ")),
+ Factory.MetaCode("}").Accepts(AcceptedCharacters.None)),
+ Factory.EmptyHtml()));
+ }
+
+ [Fact]
+ public void ParseSectionBlockAllowsBracesInCSharpExpression()
+ {
+ ParseDocumentTest("@section foo { I really want to render a close brace, so here I go: @(\"}\") }",
+ new MarkupBlock(
+ Factory.EmptyHtml(),
+ new SectionBlock(new SectionCodeGenerator("foo"),
+ Factory.CodeTransition(),
+ Factory.MetaCode("section foo {")
+ .AutoCompleteWith(null, atEndOfSpan: true),
+ new MarkupBlock(
+ Factory.Markup(" I really want to render a close brace, so here I go: "),
+ new ExpressionBlock(
+ Factory.CodeTransition(),
+ Factory.MetaCode("(").Accepts(AcceptedCharacters.None),
+ Factory.Code("\"}\"").AsExpression(),
+ Factory.MetaCode(")").Accepts(AcceptedCharacters.None)),
+ Factory.Markup(" ")),
+ Factory.MetaCode("}").Accepts(AcceptedCharacters.None)),
+ Factory.EmptyHtml()));
+ }
+
+ [Fact]
+ public void SectionIsCorrectlyTerminatedWhenCloseBraceImmediatelyFollowsCodeBlock()
+ {
+ ParseDocumentTest(@"@section Foo {
+@if(true) {
+}
+}",
+ new MarkupBlock(
+ Factory.EmptyHtml(),
+ new SectionBlock(new SectionCodeGenerator("Foo"),
+ Factory.CodeTransition(),
+ Factory.MetaCode("section Foo {")
+ .AutoCompleteWith(null, atEndOfSpan: true),
+ new MarkupBlock(
+ Factory.Markup("\r\n"),
+ new StatementBlock(
+ Factory.CodeTransition(),
+ Factory.Code("if(true) {\r\n}\r\n").AsStatement()
+ )),
+ Factory.MetaCode("}").Accepts(AcceptedCharacters.None)),
+ Factory.EmptyHtml()));
+ }
+ }
+}
diff --git a/test/System.Web.Razor.Test/Parser/CSharp/CSharpSpecialBlockTest.cs b/test/System.Web.Razor.Test/Parser/CSharp/CSharpSpecialBlockTest.cs
new file mode 100644
index 00000000..1f10433e
--- /dev/null
+++ b/test/System.Web.Razor.Test/Parser/CSharp/CSharpSpecialBlockTest.cs
@@ -0,0 +1,195 @@
+using System.Web.Razor.Parser;
+using System.Web.Razor.Parser.SyntaxTree;
+using System.Web.Razor.Resources;
+using System.Web.Razor.Test.Framework;
+using System.Web.Razor.Text;
+using Xunit;
+
+namespace System.Web.Razor.Test.Parser.CSharp
+{
+ public class CSharpSpecialBlockTest : CsHtmlCodeParserTestBase
+ {
+ [Fact]
+ public void ParseInheritsStatementMarksInheritsSpanAsCanGrowIfMissingTrailingSpace()
+ {
+ ParseBlockTest("inherits",
+ new DirectiveBlock(
+ Factory.MetaCode("inherits").Accepts(AcceptedCharacters.Any)
+ ),
+ new RazorError(
+ RazorResources.ParseError_InheritsKeyword_Must_Be_Followed_By_TypeName,
+ new SourceLocation(8, 0, 8)));
+ }
+
+ [Fact]
+ public void InheritsBlockAcceptsMultipleGenericArguments()
+ {
+ ParseBlockTest("inherits Foo.Bar<Biz<Qux>, string, int>.Baz",
+ new DirectiveBlock(
+ Factory.MetaCode("inherits ").Accepts(AcceptedCharacters.None),
+ Factory.Code("Foo.Bar<Biz<Qux>, string, int>.Baz")
+ .AsBaseType("Foo.Bar<Biz<Qux>, string, int>.Baz")
+ ));
+ }
+
+ [Fact]
+ public void InheritsBlockOutputsErrorIfInheritsNotFollowedByTypeButAcceptsEntireLineAsCode()
+ {
+ ParseBlockTest(@"inherits
+foo",
+ new DirectiveBlock(
+ Factory.MetaCode("inherits ").Accepts(AcceptedCharacters.None),
+ Factory.Code(" \r\n")
+ .AsBaseType(String.Empty)
+ ),
+ new RazorError(RazorResources.ParseError_InheritsKeyword_Must_Be_Followed_By_TypeName, 24, 0, 24));
+ }
+
+ [Fact]
+ public void NamespaceImportInsideCodeBlockCausesError()
+ {
+ ParseBlockTest("{ using Foo.Bar.Baz; var foo = bar; }",
+ new StatementBlock(
+ Factory.MetaCode("{").Accepts(AcceptedCharacters.None),
+ Factory.Code(" using Foo.Bar.Baz; var foo = bar; ").AsStatement(),
+ Factory.MetaCode("}").Accepts(AcceptedCharacters.None)
+ ),
+ new RazorError(
+ RazorResources.ParseError_NamespaceImportAndTypeAlias_Cannot_Exist_Within_CodeBlock,
+ new SourceLocation(2, 0, 2)));
+ }
+
+ [Fact]
+ public void TypeAliasInsideCodeBlockIsNotHandledSpecially()
+ {
+ ParseBlockTest("{ using Foo = Bar.Baz; var foo = bar; }",
+ new StatementBlock(
+ Factory.MetaCode("{").Accepts(AcceptedCharacters.None),
+ Factory.Code(" using Foo = Bar.Baz; var foo = bar; ").AsStatement(),
+ Factory.MetaCode("}").Accepts(AcceptedCharacters.None)
+ ),
+ new RazorError(
+ RazorResources.ParseError_NamespaceImportAndTypeAlias_Cannot_Exist_Within_CodeBlock,
+ new SourceLocation(2, 0, 2)));
+ }
+
+ [Fact]
+ public void Plan9FunctionsKeywordInsideCodeBlockIsNotHandledSpecially()
+ {
+ ParseBlockTest("{ functions Foo; }",
+ new StatementBlock(
+ Factory.MetaCode("{").Accepts(AcceptedCharacters.None),
+ Factory.Code(" functions Foo; ").AsStatement(),
+ Factory.MetaCode("}").Accepts(AcceptedCharacters.None)
+ ));
+ }
+
+ [Fact]
+ public void NonKeywordStatementInCodeBlockIsHandledCorrectly()
+ {
+ ParseBlockTest(@"{
+ List<dynamic> photos = gallery.Photo.ToList();
+}",
+ new StatementBlock(
+ Factory.MetaCode("{").Accepts(AcceptedCharacters.None),
+ Factory.Code("\r\n List<dynamic> photos = gallery.Photo.ToList();\r\n").AsStatement(),
+ Factory.MetaCode("}").Accepts(AcceptedCharacters.None)
+ ));
+ }
+
+ [Fact]
+ public void ParseBlockBalancesBracesOutsideStringsIfFirstCharacterIsBraceAndReturnsSpanOfTypeCode()
+ {
+ // Arrange
+ const string code = "foo\"b}ar\" if(condition) { String.Format(\"{0}\"); } ";
+
+ // Act/Assert
+ ParseBlockTest("{" + code + "}",
+ new StatementBlock(
+ Factory.MetaCode("{").Accepts(AcceptedCharacters.None),
+ Factory.Code(code).AsStatement(),
+ Factory.MetaCode("}").Accepts(AcceptedCharacters.None)
+ ));
+ }
+
+ [Fact]
+ public void ParseBlockBalancesParensOutsideStringsIfFirstCharacterIsParenAndReturnsSpanOfTypeExpression()
+ {
+ // Arrange
+ const string code = "foo\"b)ar\" if(condition) { String.Format(\"{0}\"); } ";
+
+ // Act/Assert
+ ParseBlockTest("(" + code + ")",
+ new ExpressionBlock(
+ Factory.MetaCode("(").Accepts(AcceptedCharacters.None),
+ Factory.Code(code).AsExpression(),
+ Factory.MetaCode(")").Accepts(AcceptedCharacters.None)
+ ));
+ }
+
+ [Fact]
+ public void ParseBlockBalancesBracesAndOutputsContentAsClassLevelCodeSpanIfFirstIdentifierIsFunctionsKeyword()
+ {
+ const string code = " foo(); \"bar}baz\" ";
+ ParseBlockTest("functions {" + code + "} zoop",
+ new FunctionsBlock(
+ Factory.MetaCode("functions {").Accepts(AcceptedCharacters.None),
+ Factory.Code(code).AsFunctionsBody(),
+ Factory.MetaCode("}").Accepts(AcceptedCharacters.None)
+ ));
+ }
+
+ [Fact]
+ public void ParseBlockDoesNoErrorRecoveryForFunctionsBlock()
+ {
+ ParseBlockTest("functions { { { { { } zoop",
+ new FunctionsBlock(
+ Factory.MetaCode("functions {").Accepts(AcceptedCharacters.None),
+ Factory.Code(" { { { { } zoop").AsFunctionsBody()
+ ),
+ new RazorError(
+ String.Format(RazorResources.ParseError_Expected_EndOfBlock_Before_EOF, "functions", "}", "{"),
+ SourceLocation.Zero));
+ }
+
+ [Fact]
+ public void ParseBlockIgnoresFunctionsUnlessAllLowerCase()
+ {
+ ParseBlockTest("Functions { foo() }",
+ new ExpressionBlock(
+ Factory.Code("Functions")
+ .AsImplicitExpression(CSharpCodeParser.DefaultKeywords)
+ .Accepts(AcceptedCharacters.NonWhiteSpace)));
+ }
+
+ [Fact]
+ public void ParseBlockIgnoresSingleSlashAtStart()
+ {
+ ParseBlockTest("@/ foo",
+ new ExpressionBlock(
+ Factory.CodeTransition(),
+ Factory.EmptyCSharp()
+ .AsImplicitExpression(CSharpCodeParser.DefaultKeywords)
+ .Accepts(AcceptedCharacters.NonWhiteSpace)),
+ new RazorError(
+ String.Format(RazorResources.ParseError_Unexpected_Character_At_Start_Of_CodeBlock_CS, "/"),
+ 1, 0, 1));
+ }
+
+ [Fact]
+ public void ParseBlockTerminatesSingleLineCommentAtEndOfLine()
+ {
+ ParseBlockTest(@"if(!false) {
+ // Foo
+ <p>A real tag!</p>
+}",
+ new StatementBlock(
+ Factory.Code("if(!false) {\r\n // Foo\r\n").AsStatement(),
+ new MarkupBlock(
+ Factory.Markup(" <p>A real tag!</p>\r\n")
+ .Accepts(AcceptedCharacters.None)),
+ Factory.Code("}").AsStatement()
+ ));
+ }
+ }
+}
diff --git a/test/System.Web.Razor.Test/Parser/CSharp/CSharpStatementTest.cs b/test/System.Web.Razor.Test/Parser/CSharp/CSharpStatementTest.cs
new file mode 100644
index 00000000..fb2629f7
--- /dev/null
+++ b/test/System.Web.Razor.Test/Parser/CSharp/CSharpStatementTest.cs
@@ -0,0 +1,208 @@
+using System.Web.Razor.Generator;
+using System.Web.Razor.Parser;
+using System.Web.Razor.Parser.SyntaxTree;
+using System.Web.Razor.Test.Framework;
+using Xunit;
+
+namespace System.Web.Razor.Test.Parser.CSharp
+{
+ // Basic Tests for C# Statements:
+ // * Basic case for each statement
+ // * Basic case for ALL clauses
+
+ // This class DOES NOT contain
+ // * Error cases
+ // * Tests for various types of nested statements
+ // * Comment tests
+
+ public class CSharpStatementTest : CsHtmlCodeParserTestBase
+ {
+ [Fact]
+ public void ForStatement()
+ {
+ ParseBlockTest("@for(int i = 0; i++; i < length) { foo(); }",
+ new StatementBlock(
+ Factory.CodeTransition(),
+ Factory.Code("for(int i = 0; i++; i < length) { foo(); }")
+ .AsStatement()
+ .Accepts(AcceptedCharacters.None)
+ ));
+ }
+
+ [Fact]
+ public void ForEachStatement()
+ {
+ ParseBlockTest("@foreach(var foo in bar) { foo(); }",
+ new StatementBlock(
+ Factory.CodeTransition(),
+ Factory.Code("foreach(var foo in bar) { foo(); }")
+ .AsStatement()
+ .Accepts(AcceptedCharacters.None)
+ ));
+ }
+
+ [Fact]
+ public void WhileStatement()
+ {
+ ParseBlockTest("@while(true) { foo(); }",
+ new StatementBlock(
+ Factory.CodeTransition(),
+ Factory.Code("while(true) { foo(); }")
+ .AsStatement()
+ .Accepts(AcceptedCharacters.None)
+ ));
+ }
+
+ [Fact]
+ public void SwitchStatement()
+ {
+ ParseBlockTest("@switch(foo) { foo(); }",
+ new StatementBlock(
+ Factory.CodeTransition(),
+ Factory.Code("switch(foo) { foo(); }")
+ .AsStatement()
+ .Accepts(AcceptedCharacters.None)
+ ));
+ }
+
+ [Fact]
+ public void LockStatement()
+ {
+ ParseBlockTest("@lock(baz) { foo(); }",
+ new StatementBlock(
+ Factory.CodeTransition(),
+ Factory.Code("lock(baz) { foo(); }")
+ .AsStatement()
+ .Accepts(AcceptedCharacters.None)
+ ));
+ }
+
+ [Fact]
+ public void IfStatement()
+ {
+ ParseBlockTest("@if(true) { foo(); }",
+ new StatementBlock(
+ Factory.CodeTransition(),
+ Factory.Code("if(true) { foo(); }")
+ .AsStatement()
+ ));
+ }
+
+ [Fact]
+ public void ElseIfClause()
+ {
+ ParseBlockTest("@if(true) { foo(); } else if(false) { foo(); } else if(!false) { foo(); }",
+ new StatementBlock(
+ Factory.CodeTransition(),
+ Factory.Code("if(true) { foo(); } else if(false) { foo(); } else if(!false) { foo(); }")
+ .AsStatement()
+ ));
+ }
+
+ [Fact]
+ public void ElseClause()
+ {
+ ParseBlockTest("@if(true) { foo(); } else { foo(); }",
+ new StatementBlock(
+ Factory.CodeTransition(),
+ Factory.Code("if(true) { foo(); } else { foo(); }")
+ .AsStatement()
+ .Accepts(AcceptedCharacters.None)
+ ));
+ }
+
+ [Fact]
+ public void TryStatement()
+ {
+ ParseBlockTest("@try { foo(); }",
+ new StatementBlock(
+ Factory.CodeTransition(),
+ Factory.Code("try { foo(); }")
+ .AsStatement()
+ ));
+ }
+
+ [Fact]
+ public void CatchClause()
+ {
+ ParseBlockTest("@try { foo(); } catch(IOException ioex) { handleIO(); } catch(Exception ex) { handleOther(); }",
+ new StatementBlock(
+ Factory.CodeTransition(),
+ Factory.Code("try { foo(); } catch(IOException ioex) { handleIO(); } catch(Exception ex) { handleOther(); }")
+ .AsStatement()
+ ));
+ }
+
+ [Fact]
+ public void FinallyClause()
+ {
+ ParseBlockTest("@try { foo(); } finally { Dispose(); }",
+ new StatementBlock(
+ Factory.CodeTransition(),
+ Factory.Code("try { foo(); } finally { Dispose(); }")
+ .AsStatement()
+ .Accepts(AcceptedCharacters.None)
+ ));
+ }
+
+ [Fact]
+ public void UsingStatement()
+ {
+ ParseBlockTest("@using(var foo = new Foo()) { foo.Bar(); }",
+ new StatementBlock(
+ Factory.CodeTransition(),
+ Factory.Code("using(var foo = new Foo()) { foo.Bar(); }")
+ .AsStatement()
+ .Accepts(AcceptedCharacters.None)
+ ));
+ }
+
+ [Fact]
+ public void UsingTypeAlias()
+ {
+ ParseBlockTest("@using StringDictionary = System.Collections.Generic.Dictionary<string, string>",
+ new DirectiveBlock(
+ Factory.CodeTransition(),
+ Factory.Code("using StringDictionary = System.Collections.Generic.Dictionary<string, string>")
+ .AsNamespaceImport(" StringDictionary = System.Collections.Generic.Dictionary<string, string>", 5)
+ .Accepts(AcceptedCharacters.AnyExceptNewline)
+ ));
+ }
+
+ [Fact]
+ public void UsingNamespaceImport()
+ {
+ ParseBlockTest("@using System.Text.Encoding.ASCIIEncoding",
+ new DirectiveBlock(
+ Factory.CodeTransition(),
+ Factory.Code("using System.Text.Encoding.ASCIIEncoding")
+ .AsNamespaceImport(" System.Text.Encoding.ASCIIEncoding", 5)
+ .Accepts(AcceptedCharacters.AnyExceptNewline)
+ ));
+ }
+
+ [Fact]
+ public void DoStatement()
+ {
+ ParseBlockTest("@do { foo(); } while(true);",
+ new StatementBlock(
+ Factory.CodeTransition(),
+ Factory.Code("do { foo(); } while(true);")
+ .AsStatement()
+ .Accepts(AcceptedCharacters.None)
+ ));
+ }
+
+ [Fact]
+ public void NonBlockKeywordTreatedAsImplicitExpression()
+ {
+ ParseBlockTest("@is foo",
+ new ExpressionBlock(new ExpressionCodeGenerator(),
+ Factory.CodeTransition(),
+ Factory.Code("is")
+ .AsImplicitExpression(CSharpCodeParser.DefaultKeywords)
+ .Accepts(AcceptedCharacters.NonWhiteSpace)
+ ));
+ }
+ }
+}
diff --git a/test/System.Web.Razor.Test/Parser/CSharp/CSharpTemplateTest.cs b/test/System.Web.Razor.Test/Parser/CSharp/CSharpTemplateTest.cs
new file mode 100644
index 00000000..4b797d3e
--- /dev/null
+++ b/test/System.Web.Razor.Test/Parser/CSharp/CSharpTemplateTest.cs
@@ -0,0 +1,258 @@
+using System.Web.Razor.Editor;
+using System.Web.Razor.Parser;
+using System.Web.Razor.Parser.SyntaxTree;
+using System.Web.Razor.Resources;
+using System.Web.Razor.Test.Framework;
+using System.Web.Razor.Text;
+using System.Web.Razor.Tokenizer.Symbols;
+using Xunit;
+
+namespace System.Web.Razor.Test.Parser.CSharp
+{
+ public class CSharpTemplateTest : CsHtmlCodeParserTestBase
+ {
+ private const string TestTemplateCode = " @<p>Foo #@item</p>";
+
+ private TemplateBlock TestTemplate()
+ {
+ return new TemplateBlock(
+ new MarkupBlock(
+ Factory.MarkupTransition(),
+ Factory.Markup("<p>Foo #"),
+ new ExpressionBlock(
+ Factory.CodeTransition(),
+ Factory.Code("item")
+ .AsImplicitExpression(CSharpCodeParser.DefaultKeywords)
+ .Accepts(AcceptedCharacters.NonWhiteSpace)
+ ),
+ Factory.Markup("</p>").Accepts(AcceptedCharacters.None)
+ )
+ );
+ }
+
+ private const string TestNestedTemplateCode = " @<p>Foo #@Html.Repeat(10, @<p>@item</p>)</p>";
+
+ private TemplateBlock TestNestedTemplate()
+ {
+ return new TemplateBlock(
+ new MarkupBlock(
+ Factory.MarkupTransition(),
+ Factory.Markup("<p>Foo #"),
+ new ExpressionBlock(
+ Factory.CodeTransition(),
+ Factory.Code("Html.Repeat(10, ")
+ .AsImplicitExpression(CSharpCodeParser.DefaultKeywords),
+ new TemplateBlock(
+ new MarkupBlock(
+ Factory.MarkupTransition(),
+ Factory.Markup("<p>"),
+ new ExpressionBlock(
+ Factory.CodeTransition(),
+ Factory.Code("item")
+ .AsImplicitExpression(CSharpCodeParser.DefaultKeywords)
+ .Accepts(AcceptedCharacters.NonWhiteSpace)
+ ),
+ Factory.Markup("</p>").Accepts(AcceptedCharacters.None)
+ )
+ ),
+ Factory.Code(")")
+ .AsImplicitExpression(CSharpCodeParser.DefaultKeywords)
+ .Accepts(AcceptedCharacters.NonWhiteSpace)
+ ),
+ Factory.Markup("</p>").Accepts(AcceptedCharacters.None)
+ )
+ );
+ }
+
+ [Fact]
+ public void ParseBlockHandlesSingleLineTemplate()
+ {
+ ParseBlockTest(@"{ var foo = @: bar
+; }",
+ new StatementBlock(
+ Factory.MetaCode("{").Accepts(AcceptedCharacters.None),
+ Factory.Code(" var foo = ").AsStatement(),
+ new TemplateBlock(
+ new MarkupBlock(
+ Factory.MarkupTransition(),
+ Factory.MetaMarkup(":", HtmlSymbolType.Colon),
+ Factory.Markup(" bar\r\n")
+ .With(new SingleLineMarkupEditHandler(CSharpLanguageCharacteristics.Instance.TokenizeString))
+ .Accepts(AcceptedCharacters.None)
+ )
+ ),
+ Factory.Code("; ").AsStatement(),
+ Factory.MetaCode("}").Accepts(AcceptedCharacters.None)
+ ));
+ }
+
+ [Fact]
+ public void ParseBlockHandlesSingleLineImmediatelyFollowingStatementChar()
+ {
+ ParseBlockTest(@"{i@: bar
+}",
+ new StatementBlock(
+ Factory.MetaCode("{").Accepts(AcceptedCharacters.None),
+ Factory.Code("i").AsStatement(),
+ new TemplateBlock(
+ new MarkupBlock(
+ Factory.MarkupTransition(),
+ Factory.MetaMarkup(":", HtmlSymbolType.Colon),
+ Factory.Markup(" bar\r\n")
+ .With(new SingleLineMarkupEditHandler(CSharpLanguageCharacteristics.Instance.TokenizeString))
+ .Accepts(AcceptedCharacters.None)
+ )
+ ),
+ Factory.EmptyCSharp().AsStatement(),
+ Factory.MetaCode("}").Accepts(AcceptedCharacters.None)
+ ));
+ }
+
+ [Fact]
+ public void ParseBlockHandlesSimpleTemplateInExplicitExpressionParens()
+ {
+ ParseBlockTest("(Html.Repeat(10," + TestTemplateCode + "))",
+ new ExpressionBlock(
+ Factory.MetaCode("(").Accepts(AcceptedCharacters.None),
+ Factory.Code("Html.Repeat(10, ").AsExpression(),
+ TestTemplate(),
+ Factory.Code(")").AsExpression(),
+ Factory.MetaCode(")").Accepts(AcceptedCharacters.None)
+ ));
+ }
+
+ [Fact]
+ public void ParseBlockHandlesSimpleTemplateInImplicitExpressionParens()
+ {
+ ParseBlockTest("Html.Repeat(10," + TestTemplateCode + ")",
+ new ExpressionBlock(
+ Factory.Code("Html.Repeat(10, ")
+ .AsImplicitExpression(CSharpCodeParser.DefaultKeywords),
+ TestTemplate(),
+ Factory.Code(")")
+ .AsImplicitExpression(CSharpCodeParser.DefaultKeywords)
+ .Accepts(AcceptedCharacters.NonWhiteSpace)
+ ));
+ }
+
+ [Fact]
+ public void ParseBlockHandlesTwoTemplatesInImplicitExpressionParens()
+ {
+ ParseBlockTest("Html.Repeat(10," + TestTemplateCode + "," + TestTemplateCode + ")",
+ new ExpressionBlock(
+ Factory.Code("Html.Repeat(10, ")
+ .AsImplicitExpression(CSharpCodeParser.DefaultKeywords),
+ TestTemplate(),
+ Factory.Code(", ")
+ .AsImplicitExpression(CSharpCodeParser.DefaultKeywords),
+ TestTemplate(),
+ Factory.Code(")")
+ .AsImplicitExpression(CSharpCodeParser.DefaultKeywords).Accepts(AcceptedCharacters.NonWhiteSpace)
+ ));
+ }
+
+ [Fact]
+ public void ParseBlockProducesErrorButCorrectlyParsesNestedTemplateInImplicitExpressionParens()
+ {
+ ParseBlockTest("Html.Repeat(10," + TestNestedTemplateCode + ")",
+ new ExpressionBlock(
+ Factory.Code("Html.Repeat(10, ")
+ .AsImplicitExpression(CSharpCodeParser.DefaultKeywords),
+ TestNestedTemplate(),
+ Factory.Code(")")
+ .AsImplicitExpression(CSharpCodeParser.DefaultKeywords)
+ .Accepts(AcceptedCharacters.NonWhiteSpace)
+ ),
+ GetNestedTemplateError(42));
+ }
+
+ [Fact]
+ public void ParseBlockHandlesSimpleTemplateInStatementWithinCodeBlock()
+ {
+ ParseBlockTest("foreach(foo in Bar) { Html.ExecuteTemplate(foo," + TestTemplateCode + "); }",
+ new StatementBlock(
+ Factory.Code("foreach(foo in Bar) { Html.ExecuteTemplate(foo, ").AsStatement(),
+ TestTemplate(),
+ Factory.Code("); }")
+ .AsStatement()
+ .Accepts(AcceptedCharacters.None)
+ ));
+ }
+
+ [Fact]
+ public void ParseBlockHandlesTwoTemplatesInStatementWithinCodeBlock()
+ {
+ ParseBlockTest("foreach(foo in Bar) { Html.ExecuteTemplate(foo," + TestTemplateCode + "," + TestTemplateCode + "); }",
+ new StatementBlock(
+ Factory.Code("foreach(foo in Bar) { Html.ExecuteTemplate(foo, ").AsStatement(),
+ TestTemplate(),
+ Factory.Code(", ").AsStatement(),
+ TestTemplate(),
+ Factory.Code("); }")
+ .AsStatement()
+ .Accepts(AcceptedCharacters.None)
+ ));
+ }
+
+ [Fact]
+ public void ParseBlockProducesErrorButCorrectlyParsesNestedTemplateInStatementWithinCodeBlock()
+ {
+ ParseBlockTest("foreach(foo in Bar) { Html.ExecuteTemplate(foo," + TestNestedTemplateCode + "); }",
+ new StatementBlock(
+ Factory.Code("foreach(foo in Bar) { Html.ExecuteTemplate(foo, ").AsStatement(),
+ TestNestedTemplate(),
+ Factory.Code("); }")
+ .AsStatement()
+ .Accepts(AcceptedCharacters.None)
+ ),
+ GetNestedTemplateError(74));
+ }
+
+ [Fact]
+ public void ParseBlockHandlesSimpleTemplateInStatementWithinStatementBlock()
+ {
+ ParseBlockTest("{ var foo = bar; Html.ExecuteTemplate(foo," + TestTemplateCode + "); }",
+ new StatementBlock(
+ Factory.MetaCode("{").Accepts(AcceptedCharacters.None),
+ Factory.Code(" var foo = bar; Html.ExecuteTemplate(foo, ").AsStatement(),
+ TestTemplate(),
+ Factory.Code("); ").AsStatement(),
+ Factory.MetaCode("}").Accepts(AcceptedCharacters.None)
+ ));
+ }
+
+ [Fact]
+ public void ParseBlockHandlessTwoTemplatesInStatementWithinStatementBlock()
+ {
+ ParseBlockTest("{ var foo = bar; Html.ExecuteTemplate(foo," + TestTemplateCode + "," + TestTemplateCode + "); }",
+ new StatementBlock(
+ Factory.MetaCode("{").Accepts(AcceptedCharacters.None),
+ Factory.Code(" var foo = bar; Html.ExecuteTemplate(foo, ").AsStatement(),
+ TestTemplate(),
+ Factory.Code(", ").AsStatement(),
+ TestTemplate(),
+ Factory.Code("); ").AsStatement(),
+ Factory.MetaCode("}").Accepts(AcceptedCharacters.None)
+ ));
+ }
+
+ [Fact]
+ public void ParseBlockProducesErrorButCorrectlyParsesNestedTemplateInStatementWithinStatementBlock()
+ {
+ ParseBlockTest("{ var foo = bar; Html.ExecuteTemplate(foo," + TestNestedTemplateCode + "); }",
+ new StatementBlock(
+ Factory.MetaCode("{").Accepts(AcceptedCharacters.None),
+ Factory.Code(" var foo = bar; Html.ExecuteTemplate(foo, ").AsStatement(),
+ TestNestedTemplate(),
+ Factory.Code("); ").AsStatement(),
+ Factory.MetaCode("}").Accepts(AcceptedCharacters.None)
+ ),
+ GetNestedTemplateError(69));
+ }
+
+ private static RazorError GetNestedTemplateError(int characterIndex)
+ {
+ return new RazorError(RazorResources.ParseError_InlineMarkup_Blocks_Cannot_Be_Nested, new SourceLocation(characterIndex, 0, characterIndex));
+ }
+ }
+}
diff --git a/test/System.Web.Razor.Test/Parser/CSharp/CSharpToMarkupSwitchTest.cs b/test/System.Web.Razor.Test/Parser/CSharp/CSharpToMarkupSwitchTest.cs
new file mode 100644
index 00000000..c84ff737
--- /dev/null
+++ b/test/System.Web.Razor.Test/Parser/CSharp/CSharpToMarkupSwitchTest.cs
@@ -0,0 +1,491 @@
+using System.Web.Razor.Editor;
+using System.Web.Razor.Parser;
+using System.Web.Razor.Parser.SyntaxTree;
+using System.Web.Razor.Resources;
+using System.Web.Razor.Test.Framework;
+using System.Web.Razor.Tokenizer.Symbols;
+using Xunit;
+
+namespace System.Web.Razor.Test.Parser.CSharp
+{
+ public class CSharpToMarkupSwitchTest : CsHtmlCodeParserTestBase
+ {
+ [Fact]
+ public void SingleAngleBracketDoesNotCauseSwitchIfOuterBlockIsTerminated()
+ {
+ ParseBlockTest("{ List< }",
+ new StatementBlock(
+ Factory.MetaCode("{").Accepts(AcceptedCharacters.None),
+ Factory.Code(" List< ").AsStatement(),
+ Factory.MetaCode("}").Accepts(AcceptedCharacters.None)));
+ }
+
+ [Fact]
+ public void ParseBlockGivesSpacesToCodeOnAtTagTemplateTransitionInDesignTimeMode()
+ {
+ ParseBlockTest(@"Foo( @<p>Foo</p> )",
+ new ExpressionBlock(
+ Factory.Code(@"Foo( ")
+ .AsImplicitExpression(CSharpCodeParser.DefaultKeywords)
+ .Accepts(AcceptedCharacters.Any),
+ new TemplateBlock(
+ new MarkupBlock(
+ Factory.MarkupTransition(),
+ Factory.Markup("<p>Foo</p>").Accepts(AcceptedCharacters.None)
+ )
+ ),
+ Factory.Code(@" )")
+ .AsImplicitExpression(CSharpCodeParser.DefaultKeywords)
+ .Accepts(AcceptedCharacters.NonWhiteSpace)
+ ), designTimeParser: true);
+ }
+
+ [Fact]
+ public void ParseBlockGivesSpacesToCodeOnAtColonTemplateTransitionInDesignTimeMode()
+ {
+ ParseBlockTest(@"Foo(
+@:<p>Foo</p>
+)",
+ new ExpressionBlock(
+ Factory.Code("Foo( \r\n").AsImplicitExpression(CSharpCodeParser.DefaultKeywords),
+ new TemplateBlock(
+ new MarkupBlock(
+ Factory.MarkupTransition(),
+ Factory.MetaMarkup(":", HtmlSymbolType.Colon),
+ Factory.Markup("<p>Foo</p> \r\n")
+ .With(new SingleLineMarkupEditHandler(CSharpLanguageCharacteristics.Instance.TokenizeString, AcceptedCharacters.None))
+ )
+ ),
+ Factory.Code(@")")
+ .AsImplicitExpression(CSharpCodeParser.DefaultKeywords)
+ .Accepts(AcceptedCharacters.NonWhiteSpace)
+ ), designTimeParser: true);
+ }
+
+ [Fact]
+ public void ParseBlockGivesSpacesToCodeOnTagTransitionInDesignTimeMode()
+ {
+ ParseBlockTest(@"{
+ <p>Foo</p>
+}",
+ new StatementBlock(
+ Factory.MetaCode("{").Accepts(AcceptedCharacters.None),
+ Factory.Code("\r\n ").AsStatement(),
+ new MarkupBlock(
+ Factory.Markup("<p>Foo</p>").Accepts(AcceptedCharacters.None)
+ ),
+ Factory.Code(" \r\n").AsStatement(),
+ Factory.MetaCode("}").Accepts(AcceptedCharacters.None)
+ ), designTimeParser: true);
+ }
+
+ [Fact]
+ public void ParseBlockGivesSpacesToCodeOnInvalidAtTagTransitionInDesignTimeMode()
+ {
+ ParseBlockTest(@"{
+ @<p>Foo</p>
+}",
+ new StatementBlock(
+ Factory.MetaCode("{").Accepts(AcceptedCharacters.None),
+ Factory.Code("\r\n ").AsStatement(),
+ new MarkupBlock(
+ Factory.MarkupTransition(),
+ Factory.Markup("<p>Foo</p>").Accepts(AcceptedCharacters.None)
+ ),
+ Factory.Code(" \r\n").AsStatement(),
+ Factory.MetaCode("}").Accepts(AcceptedCharacters.None)
+ ), true,
+ new RazorError(RazorResources.ParseError_AtInCode_Must_Be_Followed_By_Colon_Paren_Or_Identifier_Start, 7, 1, 4));
+ }
+
+ [Fact]
+ public void ParseBlockGivesSpacesToCodeOnAtColonTransitionInDesignTimeMode()
+ {
+ ParseBlockTest(@"{
+ @:<p>Foo</p>
+}",
+ new StatementBlock(
+ Factory.MetaCode("{").Accepts(AcceptedCharacters.None),
+ Factory.Code("\r\n ").AsStatement(),
+ new MarkupBlock(
+ Factory.MarkupTransition(),
+ Factory.MetaMarkup(":", HtmlSymbolType.Colon),
+ Factory.Markup("<p>Foo</p> \r\n")
+ .With(new SingleLineMarkupEditHandler(CSharpLanguageCharacteristics.Instance.TokenizeString, AcceptedCharacters.None))
+ ),
+ Factory.EmptyCSharp().AsStatement(),
+ Factory.MetaCode("}").Accepts(AcceptedCharacters.None)
+ ), designTimeParser: true);
+ }
+
+ [Fact]
+ public void ParseBlockShouldSupportSingleLineMarkupContainingStatementBlock()
+ {
+ ParseBlockTest(@"Repeat(10,
+ @: @{}
+)",
+ new ExpressionBlock(
+ Factory.Code("Repeat(10,\r\n ")
+ .AsImplicitExpression(CSharpCodeParser.DefaultKeywords),
+ new TemplateBlock(
+ new MarkupBlock(
+ Factory.MarkupTransition(),
+ Factory.MetaMarkup(":", HtmlSymbolType.Colon),
+ Factory.Markup(" ")
+ .With(new SingleLineMarkupEditHandler(CSharpLanguageCharacteristics.Instance.TokenizeString)),
+ new StatementBlock(
+ Factory.CodeTransition(),
+ Factory.MetaCode("{").Accepts(AcceptedCharacters.None),
+ Factory.EmptyCSharp().AsStatement(),
+ Factory.MetaCode("}").Accepts(AcceptedCharacters.None)
+ ),
+ Factory.Markup("\r\n")
+ .With(new SingleLineMarkupEditHandler(CSharpLanguageCharacteristics.Instance.TokenizeString, AcceptedCharacters.None))
+ )
+ ),
+ Factory.Code(")")
+ .AsImplicitExpression(CSharpCodeParser.DefaultKeywords)
+ .Accepts(AcceptedCharacters.NonWhiteSpace)
+ ));
+ }
+
+ [Fact]
+ public void ParseBlockShouldSupportMarkupWithoutPreceedingWhitespace()
+ {
+ ParseBlockTest(@"foreach(var file in files){
+
+
+@:Baz
+<br/>
+<a>Foo</a>
+@:Bar
+}",
+ new StatementBlock(
+ Factory.Code("foreach(var file in files){\r\n\r\n\r\n").AsStatement(),
+ new MarkupBlock(
+ Factory.MarkupTransition(),
+ Factory.MetaMarkup(":", HtmlSymbolType.Colon),
+ Factory.Markup("Baz\r\n")
+ .With(new SingleLineMarkupEditHandler(CSharpLanguageCharacteristics.Instance.TokenizeString, AcceptedCharacters.None))
+ ),
+ new MarkupBlock(
+ Factory.Markup("<br/>\r\n")
+ .Accepts(AcceptedCharacters.None)
+ ),
+ new MarkupBlock(
+ Factory.Markup("<a>Foo</a>\r\n")
+ .Accepts(AcceptedCharacters.None)
+ ),
+ new MarkupBlock(
+ Factory.MarkupTransition(),
+ Factory.MetaMarkup(":", HtmlSymbolType.Colon),
+ Factory.Markup("Bar\r\n")
+ .With(new SingleLineMarkupEditHandler(CSharpLanguageCharacteristics.Instance.TokenizeString, AcceptedCharacters.None))
+ ),
+ Factory.Code("}").AsStatement().Accepts(AcceptedCharacters.None)
+ ));
+ }
+
+ [Fact]
+ public void ParseBlockGivesAllWhitespaceOnSameLineExcludingPreceedingNewlineButIncludingTrailingNewLineToMarkup()
+ {
+ ParseBlockTest(@"if(foo) {
+ var foo = ""After this statement there are 10 spaces"";
+ <p>
+ Foo
+ @bar
+ </p>
+ @:Hello!
+ var biz = boz;
+}",
+ new StatementBlock(
+ Factory.Code("if(foo) {\r\n var foo = \"After this statement there are 10 spaces\"; \r\n").AsStatement(),
+ new MarkupBlock(
+ Factory.Markup(" <p>\r\n Foo\r\n"),
+ new ExpressionBlock(
+ Factory.Code(" ").AsStatement(),
+ Factory.CodeTransition(),
+ Factory.Code(@"bar").AsImplicitExpression(CSharpCodeParser.DefaultKeywords).Accepts(AcceptedCharacters.NonWhiteSpace)
+ ),
+ Factory.Markup("\r\n </p>\r\n").Accepts(AcceptedCharacters.None)
+ ),
+ new MarkupBlock(
+ Factory.Markup(@" "),
+ Factory.MarkupTransition(),
+ Factory.MetaMarkup(":", HtmlSymbolType.Colon),
+ Factory.Markup("Hello!\r\n").With(new SingleLineMarkupEditHandler(CSharpLanguageCharacteristics.Instance.TokenizeString, AcceptedCharacters.None))
+ ),
+ Factory.Code(" var biz = boz;\r\n}").AsStatement()));
+ }
+
+ [Fact]
+ public void ParseBlockAllowsMarkupInIfBodyWithBraces()
+ {
+ ParseBlockTest("if(foo) { <p>Bar</p> } else if(bar) { <p>Baz</p> } else { <p>Boz</p> }",
+ new StatementBlock(
+ Factory.Code("if(foo) {").AsStatement(),
+ new MarkupBlock(
+ Factory.Markup(" <p>Bar</p> ").Accepts(AcceptedCharacters.None)
+ ),
+ Factory.Code("} else if(bar) {").AsStatement(),
+ new MarkupBlock(
+ Factory.Markup(" <p>Baz</p> ").Accepts(AcceptedCharacters.None)
+ ),
+ Factory.Code("} else {").AsStatement(),
+ new MarkupBlock(
+ Factory.Markup(" <p>Boz</p> ").Accepts(AcceptedCharacters.None)
+ ),
+ Factory.Code("}").AsStatement().Accepts(AcceptedCharacters.None)
+ ));
+ }
+
+ [Fact]
+ public void ParseBlockAllowsMarkupInIfBodyWithBracesWithinCodeBlock()
+ {
+ ParseBlockTest("{ if(foo) { <p>Bar</p> } else if(bar) { <p>Baz</p> } else { <p>Boz</p> } }",
+ new StatementBlock(
+ Factory.MetaCode("{").Accepts(AcceptedCharacters.None),
+ Factory.Code(" if(foo) {").AsStatement(),
+ new MarkupBlock(
+ Factory.Markup(" <p>Bar</p> ").Accepts(AcceptedCharacters.None)
+ ),
+ Factory.Code("} else if(bar) {").AsStatement(),
+ new MarkupBlock(
+ Factory.Markup(" <p>Baz</p> ").Accepts(AcceptedCharacters.None)
+ ),
+ Factory.Code("} else {").AsStatement(),
+ new MarkupBlock(
+ Factory.Markup(" <p>Boz</p> ").Accepts(AcceptedCharacters.None)
+ ),
+ Factory.Code("} ").AsStatement(),
+ Factory.MetaCode("}").Accepts(AcceptedCharacters.None)
+ ));
+ }
+
+ [Fact]
+ public void ParseBlockSupportsMarkupInCaseAndDefaultBranchesOfSwitch()
+ {
+ // Arrange
+ ParseBlockTest(@"switch(foo) {
+ case 0:
+ <p>Foo</p>
+ break;
+ case 1:
+ <p>Bar</p>
+ return;
+ case 2:
+ {
+ <p>Baz</p>
+ <p>Boz</p>
+ }
+ default:
+ <p>Biz</p>
+}",
+ new StatementBlock(
+ Factory.Code("switch(foo) {\r\n case 0:\r\n").AsStatement(),
+ new MarkupBlock(
+ Factory.Markup(" <p>Foo</p>\r\n").Accepts(AcceptedCharacters.None)
+ ),
+ Factory.Code(" break;\r\n case 1:\r\n").AsStatement(),
+ new MarkupBlock(
+ Factory.Markup(" <p>Bar</p>\r\n").Accepts(AcceptedCharacters.None)
+ ),
+ Factory.Code(" return;\r\n case 2:\r\n {\r\n").AsStatement(),
+ new MarkupBlock(
+ Factory.Markup(" <p>Baz</p>\r\n").Accepts(AcceptedCharacters.None)
+ ),
+ new MarkupBlock(
+ Factory.Markup(" <p>Boz</p>\r\n").Accepts(AcceptedCharacters.None)
+ ),
+ Factory.Code(" }\r\n default:\r\n").AsStatement(),
+ new MarkupBlock(
+ Factory.Markup(" <p>Biz</p>\r\n").Accepts(AcceptedCharacters.None)
+ ),
+ Factory.Code("}").AsStatement().Accepts(AcceptedCharacters.None)));
+ }
+
+ [Fact]
+ public void ParseBlockSupportsMarkupInCaseAndDefaultBranchesOfSwitchInCodeBlock()
+ {
+ // Arrange
+ ParseBlockTest(@"{ switch(foo) {
+ case 0:
+ <p>Foo</p>
+ break;
+ case 1:
+ <p>Bar</p>
+ return;
+ case 2:
+ {
+ <p>Baz</p>
+ <p>Boz</p>
+ }
+ default:
+ <p>Biz</p>
+} }",
+ new StatementBlock(
+ Factory.MetaCode("{").Accepts(AcceptedCharacters.None),
+ Factory.Code(" switch(foo) {\r\n case 0:\r\n").AsStatement(),
+ new MarkupBlock(
+ Factory.Markup(" <p>Foo</p>\r\n").Accepts(AcceptedCharacters.None)
+ ),
+ Factory.Code(" break;\r\n case 1:\r\n").AsStatement(),
+ new MarkupBlock(
+ Factory.Markup(" <p>Bar</p>\r\n").Accepts(AcceptedCharacters.None)
+ ),
+ Factory.Code(" return;\r\n case 2:\r\n {\r\n").AsStatement(),
+ new MarkupBlock(
+ Factory.Markup(" <p>Baz</p>\r\n").Accepts(AcceptedCharacters.None)
+ ),
+ new MarkupBlock(
+ Factory.Markup(" <p>Boz</p>\r\n").Accepts(AcceptedCharacters.None)
+ ),
+ Factory.Code(" }\r\n default:\r\n").AsStatement(),
+ new MarkupBlock(
+ Factory.Markup(" <p>Biz</p>\r\n").Accepts(AcceptedCharacters.None)
+ ),
+ Factory.Code("} ").AsStatement(),
+ Factory.MetaCode("}").Accepts(AcceptedCharacters.None)));
+ }
+
+ [Fact]
+ public void ParseBlockParsesMarkupStatementOnOpenAngleBracket()
+ {
+ ParseBlockTest("for(int i = 0; i < 10; i++) { <p>Foo</p> }",
+ new StatementBlock(
+ Factory.Code("for(int i = 0; i < 10; i++) {").AsStatement(),
+ new MarkupBlock(
+ Factory.Markup(" <p>Foo</p> ").Accepts(AcceptedCharacters.None)
+ ),
+ Factory.Code("}").AsStatement().Accepts(AcceptedCharacters.None)
+ ));
+ }
+
+ [Fact]
+ public void ParseBlockParsesMarkupStatementOnOpenAngleBracketInCodeBlock()
+ {
+ ParseBlockTest("{ for(int i = 0; i < 10; i++) { <p>Foo</p> } }",
+ new StatementBlock(
+ Factory.MetaCode("{").Accepts(AcceptedCharacters.None),
+ Factory.Code(" for(int i = 0; i < 10; i++) {").AsStatement(),
+ new MarkupBlock(
+ Factory.Markup(" <p>Foo</p> ").Accepts(AcceptedCharacters.None)
+ ),
+ Factory.Code("} ").AsStatement(),
+ Factory.MetaCode("}").Accepts(AcceptedCharacters.None)));
+ }
+
+ [Fact]
+ public void ParseBlockParsesMarkupStatementOnSwitchCharacterFollowedByColon()
+ {
+ // Arrange
+ ParseBlockTest(@"if(foo) { @:Bar
+} zoop",
+ new StatementBlock(
+ Factory.Code("if(foo) {").AsStatement(),
+ new MarkupBlock(
+ Factory.Markup(" "),
+ Factory.MarkupTransition(),
+ Factory.MetaMarkup(":", HtmlSymbolType.Colon),
+ Factory.Markup("Bar\r\n").With(new SingleLineMarkupEditHandler(CSharpLanguageCharacteristics.Instance.TokenizeString, AcceptedCharacters.None))
+ ),
+ Factory.Code("}").AsStatement()));
+ }
+
+ [Fact]
+ public void ParseBlockParsesMarkupStatementOnSwitchCharacterFollowedByColonInCodeBlock()
+ {
+ // Arrange
+ ParseBlockTest(@"{ if(foo) { @:Bar
+} } zoop",
+ new StatementBlock(
+ Factory.MetaCode("{").Accepts(AcceptedCharacters.None),
+ Factory.Code(" if(foo) {").AsStatement(),
+ new MarkupBlock(
+ Factory.Markup(" "),
+ Factory.MarkupTransition(),
+ Factory.MetaMarkup(":", HtmlSymbolType.Colon),
+ Factory.Markup("Bar\r\n").Accepts(AcceptedCharacters.None)
+ ),
+ Factory.Code("} ").AsStatement(),
+ Factory.MetaCode("}").Accepts(AcceptedCharacters.None)));
+ }
+
+ [Fact]
+ public void ParseBlockCorrectlyReturnsFromMarkupBlockWithPseudoTag()
+ {
+ ParseBlockTest(@"if (i > 0) { <text>;</text> }",
+ new StatementBlock(
+ Factory.Code(@"if (i > 0) {").AsStatement(),
+ new MarkupBlock(
+ Factory.Markup(" "),
+ Factory.MarkupTransition("<text>").Accepts(AcceptedCharacters.None),
+ Factory.Markup(";"),
+ Factory.MarkupTransition("</text>").Accepts(AcceptedCharacters.None),
+ Factory.Markup(" ").Accepts(AcceptedCharacters.None)
+ ),
+ Factory.Code(@"}").AsStatement()));
+ }
+
+ [Fact]
+ public void ParseBlockCorrectlyReturnsFromMarkupBlockWithPseudoTagInCodeBlock()
+ {
+ ParseBlockTest(@"{ if (i > 0) { <text>;</text> } }",
+ new StatementBlock(
+ Factory.MetaCode("{").Accepts(AcceptedCharacters.None),
+ Factory.Code(@" if (i > 0) {").AsStatement(),
+ new MarkupBlock(
+ Factory.Markup(" "),
+ Factory.MarkupTransition("<text>").Accepts(AcceptedCharacters.None),
+ Factory.Markup(";"),
+ Factory.MarkupTransition("</text>").Accepts(AcceptedCharacters.None),
+ Factory.Markup(" ").Accepts(AcceptedCharacters.None)
+ ),
+ Factory.Code(@"} ").AsStatement(),
+ Factory.MetaCode("}").Accepts(AcceptedCharacters.None)));
+ }
+
+ [Fact]
+ public void ParseBlockSupportsAllKindsOfImplicitMarkupInCodeBlock()
+ {
+ ParseBlockTest(@"{
+ if(true) {
+ @:Single Line Markup
+ }
+ foreach (var p in Enumerable.Range(1, 10)) {
+ <text>The number is @p</text>
+ }
+ if(!false) {
+ <p>A real tag!</p>
+ }
+}",
+ new StatementBlock(
+ Factory.MetaCode("{").Accepts(AcceptedCharacters.None),
+ Factory.Code("\r\n if(true) {\r\n").AsStatement(),
+ new MarkupBlock(
+ Factory.Markup(" "),
+ Factory.MarkupTransition(),
+ Factory.MetaMarkup(":", HtmlSymbolType.Colon),
+ Factory.Markup("Single Line Markup\r\n").With(new SingleLineMarkupEditHandler(CSharpLanguageCharacteristics.Instance.TokenizeString, AcceptedCharacters.None))
+ ),
+ Factory.Code(" }\r\n foreach (var p in Enumerable.Range(1, 10)) {\r\n").AsStatement(),
+ new MarkupBlock(
+ Factory.Markup(@" "),
+ Factory.MarkupTransition("<text>").Accepts(AcceptedCharacters.None),
+ Factory.Markup("The number is "),
+ new ExpressionBlock(
+ Factory.CodeTransition(),
+ Factory.Code("p").AsImplicitExpression(CSharpCodeParser.DefaultKeywords).Accepts(AcceptedCharacters.NonWhiteSpace)
+ ),
+ Factory.MarkupTransition("</text>").Accepts(AcceptedCharacters.None),
+ Factory.Markup("\r\n").Accepts(AcceptedCharacters.None)
+ ),
+ Factory.Code(" }\r\n if(!false) {\r\n").AsStatement(),
+ new MarkupBlock(
+ Factory.Markup(" <p>A real tag!</p>\r\n").Accepts(AcceptedCharacters.None)
+ ),
+ Factory.Code(" }\r\n").AsStatement(),
+ Factory.MetaCode("}").Accepts(AcceptedCharacters.None)));
+ }
+ }
+}
diff --git a/test/System.Web.Razor.Test/Parser/CSharp/CSharpVerbatimBlockTest.cs b/test/System.Web.Razor.Test/Parser/CSharp/CSharpVerbatimBlockTest.cs
new file mode 100644
index 00000000..dcc20cba
--- /dev/null
+++ b/test/System.Web.Razor.Test/Parser/CSharp/CSharpVerbatimBlockTest.cs
@@ -0,0 +1,118 @@
+using System.Web.Razor.Parser.SyntaxTree;
+using System.Web.Razor.Resources;
+using System.Web.Razor.Test.Framework;
+using System.Web.Razor.Text;
+using Xunit;
+
+namespace System.Web.Razor.Test.Parser.CSharp
+{
+ public class CSharpVerbatimBlockTest : CsHtmlCodeParserTestBase
+ {
+ private const string TestExtraKeyword = "model";
+
+ [Fact]
+ public void VerbatimBlock()
+ {
+ ParseBlockTest("@{ foo(); }",
+ new StatementBlock(
+ Factory.CodeTransition(),
+ Factory.MetaCode("{")
+ .Accepts(AcceptedCharacters.None),
+ Factory.Code(" foo(); ")
+ .AsStatement(),
+ Factory.MetaCode("}")
+ .Accepts(AcceptedCharacters.None)
+ ));
+ }
+
+ [Fact]
+ public void InnerImplicitExpressionWithOnlySingleAtOutputsZeroLengthCodeSpan()
+ {
+ ParseBlockTest(@"{@}",
+ new StatementBlock(
+ Factory.MetaCode("{").Accepts(AcceptedCharacters.None),
+ Factory.EmptyCSharp().AsStatement(),
+ new ExpressionBlock(
+ Factory.CodeTransition(),
+ Factory.EmptyCSharp().AsImplicitExpression(KeywordSet, acceptTrailingDot: true).Accepts(AcceptedCharacters.NonWhiteSpace)
+ ),
+ Factory.EmptyCSharp().AsStatement(),
+ Factory.MetaCode("}").Accepts(AcceptedCharacters.None)),
+ designTimeParser: true,
+ expectedErrors: new[]
+ {
+ new RazorError(String.Format(RazorResources.ParseError_Unexpected_Character_At_Start_Of_CodeBlock_CS, "}"), new SourceLocation(2, 0, 2))
+ });
+ }
+
+ [Fact]
+ public void InnerImplicitExpressionDoesNotAcceptDotAfterAt()
+ {
+ ParseBlockTest(@"{@.}",
+ new StatementBlock(
+ Factory.MetaCode("{").Accepts(AcceptedCharacters.None),
+ Factory.EmptyCSharp().AsStatement(),
+ new ExpressionBlock(
+ Factory.CodeTransition(),
+ Factory.EmptyCSharp().AsImplicitExpression(KeywordSet, acceptTrailingDot: true).Accepts(AcceptedCharacters.NonWhiteSpace)
+ ),
+ Factory.Code(".").AsStatement(),
+ Factory.MetaCode("}").Accepts(AcceptedCharacters.None)),
+ designTimeParser: true,
+ expectedErrors: new[]
+ {
+ new RazorError(String.Format(RazorResources.ParseError_Unexpected_Character_At_Start_Of_CodeBlock_CS, "."), new SourceLocation(2, 0, 2))
+ });
+ }
+
+ [Fact]
+ public void InnerImplicitExpressionWithOnlySingleAtAcceptsSingleSpaceOrNewlineAtDesignTime()
+ {
+ ParseBlockTest(@"{
+ @
+}",
+ new StatementBlock(
+ Factory.MetaCode("{").Accepts(AcceptedCharacters.None),
+ Factory.Code("\r\n ").AsStatement(),
+ new ExpressionBlock(
+ Factory.CodeTransition(),
+ Factory.EmptyCSharp().AsImplicitExpression(KeywordSet, acceptTrailingDot: true).Accepts(AcceptedCharacters.NonWhiteSpace)
+ ),
+ Factory.Code("\r\n").AsStatement(),
+ Factory.MetaCode("}").Accepts(AcceptedCharacters.None)),
+ /* designTimeParser */ true,
+ new RazorError(RazorResources.ParseError_Unexpected_WhiteSpace_At_Start_Of_CodeBlock_CS, 8, 1, 5));
+ }
+
+ [Fact]
+ public void InnerImplicitExpressionDoesNotAcceptTrailingNewlineInRunTimeMode()
+ {
+ ParseBlockTest(@"{@foo.
+}",
+ new StatementBlock(
+ Factory.MetaCode("{").Accepts(AcceptedCharacters.None),
+ Factory.EmptyCSharp().AsStatement(),
+ new ExpressionBlock(
+ Factory.CodeTransition(),
+ Factory.Code(@"foo.").AsImplicitExpression(KeywordSet, acceptTrailingDot: true).Accepts(AcceptedCharacters.NonWhiteSpace)),
+ Factory.Code("\r\n").AsStatement(),
+ Factory.MetaCode("}").Accepts(AcceptedCharacters.None)));
+ }
+
+ [Fact]
+ public void InnerImplicitExpressionAcceptsTrailingNewlineInDesignTimeMode()
+ {
+ ParseBlockTest(@"{@foo.
+}",
+ new StatementBlock(
+ Factory.MetaCode("{").Accepts(AcceptedCharacters.None),
+ Factory.EmptyCSharp().AsStatement(),
+ new ExpressionBlock(
+ Factory.CodeTransition(),
+ Factory.Code(@"foo.").AsImplicitExpression(KeywordSet, acceptTrailingDot: true).Accepts(AcceptedCharacters.NonWhiteSpace)),
+ Factory.Code("\r\n").AsStatement(),
+ Factory.MetaCode("}").Accepts(AcceptedCharacters.None)),
+ designTimeParser: true);
+ }
+ }
+}
diff --git a/test/System.Web.Razor.Test/Parser/CSharp/CSharpWhitespaceHandlingTest.cs b/test/System.Web.Razor.Test/Parser/CSharp/CSharpWhitespaceHandlingTest.cs
new file mode 100644
index 00000000..92a5c2b9
--- /dev/null
+++ b/test/System.Web.Razor.Test/Parser/CSharp/CSharpWhitespaceHandlingTest.cs
@@ -0,0 +1,30 @@
+using System.Web.Razor.Parser.SyntaxTree;
+using System.Web.Razor.Test.Framework;
+using System.Web.Razor.Tokenizer.Symbols;
+using Xunit;
+
+namespace System.Web.Razor.Test.Parser.CSharp
+{
+ public class CSharpWhitespaceHandlingTest : CsHtmlMarkupParserTestBase
+ {
+ [Fact]
+ public void StatementBlockDoesNotAcceptTrailingNewlineIfNewlinesAreSignificantToAncestor()
+ {
+ ParseBlockTest(@"@: @if (true) { }
+}",
+ new MarkupBlock(
+ Factory.MarkupTransition()
+ .Accepts(AcceptedCharacters.None),
+ Factory.MetaMarkup(":", HtmlSymbolType.Colon),
+ Factory.Markup(" "),
+ new StatementBlock(
+ Factory.CodeTransition()
+ .Accepts(AcceptedCharacters.None),
+ Factory.Code("if (true) { }")
+ .AsStatement()
+ ),
+ Factory.Markup("\r\n")
+ .Accepts(AcceptedCharacters.None)));
+ }
+ }
+}
diff --git a/test/System.Web.Razor.Test/Parser/CSharp/CsHtmlDocumentTest.cs b/test/System.Web.Razor.Test/Parser/CSharp/CsHtmlDocumentTest.cs
new file mode 100644
index 00000000..b7c17d70
--- /dev/null
+++ b/test/System.Web.Razor.Test/Parser/CSharp/CsHtmlDocumentTest.cs
@@ -0,0 +1,286 @@
+using System.Web.Razor.Parser;
+using System.Web.Razor.Parser.SyntaxTree;
+using System.Web.Razor.Resources;
+using System.Web.Razor.Test.Framework;
+using System.Web.Razor.Text;
+using System.Web.Razor.Tokenizer.Symbols;
+using Xunit;
+
+namespace System.Web.Razor.Test.Parser.CSharp
+{
+ public class CsHtmlDocumentTest : CsHtmlMarkupParserTestBase
+ {
+ [Fact]
+ public void UnterminatedBlockCommentCausesRazorError()
+ {
+ ParseDocumentTest(@"@* Foo Bar",
+ new MarkupBlock(
+ Factory.EmptyHtml(),
+ new CommentBlock(
+ Factory.MarkupTransition(HtmlSymbolType.RazorCommentTransition),
+ Factory.MetaMarkup("*", HtmlSymbolType.RazorCommentStar).Accepts(AcceptedCharacters.None),
+ Factory.Comment(" Foo Bar", HtmlSymbolType.RazorComment)
+ )
+ ),
+ new RazorError(RazorResources.ParseError_RazorComment_Not_Terminated, SourceLocation.Zero));
+ }
+
+ [Fact]
+ public void BlockCommentInMarkupDocumentIsHandledCorrectly()
+ {
+ ParseDocumentTest(@"<ul>
+ @* This is a block comment </ul> *@ foo",
+ new MarkupBlock(
+ Factory.Markup("<ul>\r\n "),
+ new CommentBlock(
+ Factory.MarkupTransition(HtmlSymbolType.RazorCommentTransition),
+ Factory.MetaMarkup("*", HtmlSymbolType.RazorCommentStar).Accepts(AcceptedCharacters.None),
+ Factory.Comment(" This is a block comment </ul> ", HtmlSymbolType.RazorComment),
+ Factory.MetaMarkup("*", HtmlSymbolType.RazorCommentStar).Accepts(AcceptedCharacters.None),
+ Factory.MarkupTransition(HtmlSymbolType.RazorCommentTransition)
+ ),
+ Factory.Markup(" foo")
+ ));
+ }
+
+ [Fact]
+ public void BlockCommentInMarkupBlockIsHandledCorrectly()
+ {
+ ParseBlockTest(@"<ul>
+ @* This is a block comment </ul> *@ foo </ul>",
+ new MarkupBlock(
+ Factory.Markup("<ul>\r\n "),
+ new CommentBlock(
+ Factory.MarkupTransition(HtmlSymbolType.RazorCommentTransition),
+ Factory.MetaMarkup("*", HtmlSymbolType.RazorCommentStar).Accepts(AcceptedCharacters.None),
+ Factory.Comment(" This is a block comment </ul> ", HtmlSymbolType.RazorComment),
+ Factory.MetaMarkup("*", HtmlSymbolType.RazorCommentStar).Accepts(AcceptedCharacters.None),
+ Factory.MarkupTransition(HtmlSymbolType.RazorCommentTransition)
+ ),
+ Factory.Markup(" foo </ul>").Accepts(AcceptedCharacters.None)
+ ));
+ }
+
+ [Fact]
+ public void BlockCommentAtStatementStartInCodeBlockIsHandledCorrectly()
+ {
+ ParseDocumentTest(@"@if(Request.IsAuthenticated) {
+ @* User is logged in! } *@
+ Write(""Hello friend!"");
+}",
+ new MarkupBlock(
+ Factory.EmptyHtml(),
+ new StatementBlock(
+ Factory.CodeTransition(),
+ Factory.Code("if(Request.IsAuthenticated) {\r\n ").AsStatement(),
+ new CommentBlock(
+ Factory.CodeTransition(CSharpSymbolType.RazorCommentTransition),
+ Factory.MetaCode("*", CSharpSymbolType.RazorCommentStar).Accepts(AcceptedCharacters.None),
+ Factory.Comment(" User is logged in! } ", CSharpSymbolType.RazorComment),
+ Factory.MetaCode("*", CSharpSymbolType.RazorCommentStar).Accepts(AcceptedCharacters.None),
+ Factory.CodeTransition(CSharpSymbolType.RazorCommentTransition)
+ ),
+ Factory.Code("\r\n Write(\"Hello friend!\");\r\n}").AsStatement())));
+ }
+
+ [Fact]
+ public void BlockCommentInStatementInCodeBlockIsHandledCorrectly()
+ {
+ ParseDocumentTest(@"@if(Request.IsAuthenticated) {
+ var foo = @* User is logged in! ; *@;
+ Write(""Hello friend!"");
+}",
+ new MarkupBlock(
+ Factory.EmptyHtml(),
+ new StatementBlock(
+ Factory.CodeTransition(),
+ Factory.Code("if(Request.IsAuthenticated) {\r\n var foo = ").AsStatement(),
+ new CommentBlock(
+ Factory.CodeTransition(CSharpSymbolType.RazorCommentTransition),
+ Factory.MetaCode("*", CSharpSymbolType.RazorCommentStar).Accepts(AcceptedCharacters.None),
+ Factory.Comment(" User is logged in! ; ", CSharpSymbolType.RazorComment),
+ Factory.MetaCode("*", CSharpSymbolType.RazorCommentStar).Accepts(AcceptedCharacters.None),
+ Factory.CodeTransition(CSharpSymbolType.RazorCommentTransition)
+ ),
+ Factory.Code(";\r\n Write(\"Hello friend!\");\r\n}").AsStatement())));
+ }
+
+ [Fact]
+ public void BlockCommentInStringIsIgnored()
+ {
+ ParseDocumentTest(@"@if(Request.IsAuthenticated) {
+ var foo = ""@* User is logged in! ; *@"";
+ Write(""Hello friend!"");
+}",
+ new MarkupBlock(
+ Factory.EmptyHtml(),
+ new StatementBlock(
+ Factory.CodeTransition(),
+ Factory.Code(@"if(Request.IsAuthenticated) {
+ var foo = ""@* User is logged in! ; *@"";
+ Write(""Hello friend!"");
+}").AsStatement())));
+ }
+
+ [Fact]
+ public void BlockCommentInCSharpBlockCommentIsIgnored()
+ {
+ ParseDocumentTest(@"@if(Request.IsAuthenticated) {
+ var foo = /*@* User is logged in! */ *@ */;
+ Write(""Hello friend!"");
+}",
+ new MarkupBlock(
+ Factory.EmptyHtml(),
+ new StatementBlock(
+ Factory.CodeTransition(),
+ Factory.Code(@"if(Request.IsAuthenticated) {
+ var foo = /*@* User is logged in! */ *@ */;
+ Write(""Hello friend!"");
+}").AsStatement())));
+ }
+
+ [Fact]
+ public void BlockCommentInCSharpLineCommentIsIgnored()
+ {
+ ParseDocumentTest(@"@if(Request.IsAuthenticated) {
+ var foo = //@* User is logged in! */ *@;
+ Write(""Hello friend!"");
+}",
+ new MarkupBlock(
+ Factory.EmptyHtml(),
+ new StatementBlock(
+ Factory.CodeTransition(),
+ Factory.Code(@"if(Request.IsAuthenticated) {
+ var foo = //@* User is logged in! */ *@;
+ Write(""Hello friend!"");
+}").AsStatement())));
+ }
+
+ [Fact]
+ public void BlockCommentInImplicitExpressionIsHandledCorrectly()
+ {
+ ParseDocumentTest(@"@Html.Foo@*bar*@",
+ new MarkupBlock(
+ Factory.EmptyHtml(),
+ new ExpressionBlock(
+ Factory.CodeTransition(),
+ Factory.Code("Html.Foo").AsImplicitExpression(CSharpCodeParser.DefaultKeywords).Accepts(AcceptedCharacters.NonWhiteSpace)
+ ),
+ Factory.EmptyHtml(),
+ new CommentBlock(
+ Factory.MarkupTransition(HtmlSymbolType.RazorCommentTransition),
+ Factory.MetaMarkup("*", HtmlSymbolType.RazorCommentStar).Accepts(AcceptedCharacters.None),
+ Factory.Comment("bar", HtmlSymbolType.RazorComment),
+ Factory.MetaMarkup("*", HtmlSymbolType.RazorCommentStar).Accepts(AcceptedCharacters.None),
+ Factory.MarkupTransition(HtmlSymbolType.RazorCommentTransition)
+ ),
+ Factory.EmptyHtml()));
+ }
+
+ [Fact]
+ public void BlockCommentAfterDotOfImplicitExpressionIsHandledCorrectly()
+ {
+ ParseDocumentTest(@"@Html.@*bar*@",
+ new MarkupBlock(
+ Factory.EmptyHtml(),
+ new ExpressionBlock(
+ Factory.CodeTransition(),
+ Factory.Code(@"Html").AsImplicitExpression(CSharpCodeParser.DefaultKeywords).Accepts(AcceptedCharacters.NonWhiteSpace)
+ ),
+ Factory.Markup("."),
+ new CommentBlock(
+ Factory.MarkupTransition(HtmlSymbolType.RazorCommentTransition),
+ Factory.MetaMarkup("*", HtmlSymbolType.RazorCommentStar).Accepts(AcceptedCharacters.None),
+ Factory.Comment("bar", HtmlSymbolType.RazorComment),
+ Factory.MetaMarkup("*", HtmlSymbolType.RazorCommentStar).Accepts(AcceptedCharacters.None),
+ Factory.MarkupTransition(HtmlSymbolType.RazorCommentTransition)
+ ),
+ Factory.EmptyHtml()));
+ }
+
+ [Fact]
+ public void BlockCommentInParensOfImplicitExpressionIsHandledCorrectly()
+ {
+ ParseDocumentTest(@"@Html.Foo(@*bar*@ 4)",
+ new MarkupBlock(
+ Factory.EmptyHtml(),
+ new ExpressionBlock(
+ Factory.CodeTransition(),
+ Factory.Code(@"Html.Foo(").AsImplicitExpression(CSharpCodeParser.DefaultKeywords).Accepts(AcceptedCharacters.Any),
+ new CommentBlock(
+ Factory.CodeTransition(CSharpSymbolType.RazorCommentTransition),
+ Factory.MetaCode("*", CSharpSymbolType.RazorCommentStar).Accepts(AcceptedCharacters.None),
+ Factory.Comment("bar", CSharpSymbolType.RazorComment),
+ Factory.MetaCode("*", CSharpSymbolType.RazorCommentStar).Accepts(AcceptedCharacters.None),
+ Factory.CodeTransition(CSharpSymbolType.RazorCommentTransition)
+ ),
+ Factory.Code(" 4)").AsImplicitExpression(CSharpCodeParser.DefaultKeywords).Accepts(AcceptedCharacters.NonWhiteSpace)
+ ),
+ Factory.EmptyHtml()));
+ }
+
+ [Fact]
+ public void BlockCommentInBracketsOfImplicitExpressionIsHandledCorrectly()
+ {
+ ParseDocumentTest(@"@Html.Foo[@*bar*@ 4]",
+ new MarkupBlock(
+ Factory.EmptyHtml(),
+ new ExpressionBlock(
+ Factory.CodeTransition(),
+ Factory.Code(@"Html.Foo[").AsImplicitExpression(CSharpCodeParser.DefaultKeywords).Accepts(AcceptedCharacters.Any),
+ new CommentBlock(
+ Factory.CodeTransition(CSharpSymbolType.RazorCommentTransition),
+ Factory.MetaCode("*", CSharpSymbolType.RazorCommentStar).Accepts(AcceptedCharacters.None),
+ Factory.Comment("bar", CSharpSymbolType.RazorComment),
+ Factory.MetaCode("*", CSharpSymbolType.RazorCommentStar).Accepts(AcceptedCharacters.None),
+ Factory.CodeTransition(CSharpSymbolType.RazorCommentTransition)
+ ),
+ Factory.Code(" 4]").AsImplicitExpression(CSharpCodeParser.DefaultKeywords).Accepts(AcceptedCharacters.NonWhiteSpace)
+ ),
+ Factory.EmptyHtml()));
+ }
+
+ [Fact]
+ public void BlockCommentInParensOfConditionIsHandledCorrectly()
+ {
+ ParseDocumentTest(@"@if(@*bar*@) {}",
+ new MarkupBlock(
+ Factory.EmptyHtml(),
+ new StatementBlock(
+ Factory.CodeTransition(),
+ Factory.Code(@"if(").AsStatement(),
+ new CommentBlock(
+ Factory.CodeTransition(CSharpSymbolType.RazorCommentTransition),
+ Factory.MetaCode("*", CSharpSymbolType.RazorCommentStar).Accepts(AcceptedCharacters.None),
+ Factory.Comment("bar", CSharpSymbolType.RazorComment),
+ Factory.MetaCode("*", CSharpSymbolType.RazorCommentStar).Accepts(AcceptedCharacters.None),
+ Factory.CodeTransition(CSharpSymbolType.RazorCommentTransition)
+ ),
+ Factory.Code(") {}").AsStatement()
+ )));
+ }
+
+ [Fact]
+ public void BlockCommentInExplicitExpressionIsHandledCorrectly()
+ {
+ ParseDocumentTest(@"@(1 + @*bar*@ 1)",
+ new MarkupBlock(
+ Factory.EmptyHtml(),
+ new ExpressionBlock(
+ Factory.CodeTransition(),
+ Factory.MetaCode("(").Accepts(AcceptedCharacters.None),
+ Factory.Code(@"1 + ").AsExpression(),
+ new CommentBlock(
+ Factory.CodeTransition(CSharpSymbolType.RazorCommentTransition),
+ Factory.MetaCode("*", CSharpSymbolType.RazorCommentStar).Accepts(AcceptedCharacters.None),
+ Factory.Comment("bar", CSharpSymbolType.RazorComment),
+ Factory.MetaCode("*", CSharpSymbolType.RazorCommentStar).Accepts(AcceptedCharacters.None),
+ Factory.CodeTransition(CSharpSymbolType.RazorCommentTransition)
+ ),
+ Factory.Code(" 1").AsExpression(),
+ Factory.MetaCode(")").Accepts(AcceptedCharacters.None)
+ ),
+ Factory.EmptyHtml()));
+ }
+ }
+}
diff --git a/test/System.Web.Razor.Test/Parser/CallbackParserListenerTest.cs b/test/System.Web.Razor.Test/Parser/CallbackParserListenerTest.cs
new file mode 100644
index 00000000..499a84ef
--- /dev/null
+++ b/test/System.Web.Razor.Test/Parser/CallbackParserListenerTest.cs
@@ -0,0 +1,162 @@
+using System.Threading;
+using System.Web.Razor.Parser;
+using System.Web.Razor.Parser.SyntaxTree;
+using System.Web.Razor.Test.Framework;
+using System.Web.Razor.Text;
+using Moq;
+using Xunit;
+
+namespace System.Web.Razor.Test.Parser
+{
+ public class CallbackParserListenerTest
+ {
+ [Fact]
+ public void ListenerConstructedWithSpanCallbackCallsCallbackOnEndSpan()
+ {
+ RunOnEndSpanTest(callback => new CallbackVisitor(callback));
+ }
+
+ [Fact]
+ public void ListenerConstructedWithSpanCallbackDoesNotThrowOnStartBlockEndBlockOrError()
+ {
+ // Arrange
+ Action<Span> spanCallback = _ => { };
+ CallbackVisitor listener = new CallbackVisitor(spanCallback);
+
+ // Act/Assert
+ listener.VisitStartBlock(new FunctionsBlock());
+ listener.VisitError(new RazorError("Error", SourceLocation.Zero));
+ listener.VisitEndBlock(new FunctionsBlock());
+ }
+
+ [Fact]
+ public void ListenerConstructedWithSpanAndErrorCallbackCallsCallbackOnEndSpan()
+ {
+ RunOnEndSpanTest(spanCallback => new CallbackVisitor(spanCallback, _ => { }));
+ }
+
+ [Fact]
+ public void ListenerConstructedWithSpanAndErrorCallbackCallsCallbackOnError()
+ {
+ RunOnErrorTest(errorCallback => new CallbackVisitor(_ => { }, errorCallback));
+ }
+
+ [Fact]
+ public void ListenerConstructedWithAllCallbacksCallsCallbackOnEndSpan()
+ {
+ RunOnEndSpanTest(spanCallback => new CallbackVisitor(spanCallback, _ => { }, _ => { }, _ => { }));
+ }
+
+ [Fact]
+ public void ListenerConstructedWithAllCallbacksCallsCallbackOnError()
+ {
+ RunOnErrorTest(errorCallback => new CallbackVisitor(_ => { }, errorCallback, _ => { }, _ => { }));
+ }
+
+ [Fact]
+ public void ListenerConstructedWithAllCallbacksCallsCallbackOnStartBlock()
+ {
+ RunOnStartBlockTest(startBlockCallback => new CallbackVisitor(_ => { }, _ => { }, startBlockCallback, _ => { }));
+ }
+
+ [Fact]
+ public void ListenerConstructedWithAllCallbacksCallsCallbackOnEndBlock()
+ {
+ RunOnEndBlockTest(endBlockCallback => new CallbackVisitor(_ => { }, _ => { }, _ => { }, endBlockCallback));
+ }
+
+ [Fact]
+ public void ListenerCallsOnEndSpanCallbackUsingSynchronizationContextIfSpecified()
+ {
+ RunSyncContextTest(new SpanBuilder().Build(),
+ spanCallback => new CallbackVisitor(spanCallback, _ => { }, _ => { }, _ => { }),
+ (listener, expected) => listener.VisitSpan(expected));
+ }
+
+ [Fact]
+ public void ListenerCallsOnStartBlockCallbackUsingSynchronizationContextIfSpecified()
+ {
+ RunSyncContextTest(BlockType.Template,
+ startBlockCallback => new CallbackVisitor(_ => { }, _ => { }, startBlockCallback, _ => { }),
+ (listener, expected) => listener.VisitStartBlock(new BlockBuilder() { Type = expected }.Build()));
+ }
+
+ [Fact]
+ public void ListenerCallsOnEndBlockCallbackUsingSynchronizationContextIfSpecified()
+ {
+ RunSyncContextTest(BlockType.Template,
+ endBlockCallback => new CallbackVisitor(_ => { }, _ => { }, _ => { }, endBlockCallback),
+ (listener, expected) => listener.VisitEndBlock(new BlockBuilder() { Type = expected }.Build()));
+ }
+
+ [Fact]
+ public void ListenerCallsOnErrorCallbackUsingSynchronizationContextIfSpecified()
+ {
+ RunSyncContextTest(new RazorError("Bar", 42, 42, 42),
+ errorCallback => new CallbackVisitor(_ => { }, errorCallback, _ => { }, _ => { }),
+ (listener, expected) => listener.VisitError(expected));
+ }
+
+ private static void RunSyncContextTest<T>(T expected, Func<Action<T>, CallbackVisitor> ctor, Action<CallbackVisitor, T> call)
+ {
+ // Arrange
+ Mock<SynchronizationContext> mockContext = new Mock<SynchronizationContext>();
+ mockContext.Setup(c => c.Post(It.IsAny<SendOrPostCallback>(), It.IsAny<object>()))
+ .Callback<SendOrPostCallback, object>((callback, state) => { callback(expected); });
+
+ // Act/Assert
+ RunCallbackTest<T>(default(T), callback =>
+ {
+ CallbackVisitor listener = ctor(callback);
+ listener.SynchronizationContext = mockContext.Object;
+ return listener;
+ }, call, (original, actual) =>
+ {
+ Assert.NotEqual(original, actual);
+ Assert.Equal(expected, actual);
+ });
+ }
+
+ private static void RunOnStartBlockTest(Func<Action<BlockType>, CallbackVisitor> ctor, Action<BlockType, BlockType> verifyResults = null)
+ {
+ RunCallbackTest(BlockType.Markup, ctor, (listener, expected) => listener.VisitStartBlock(new BlockBuilder() { Type = expected }.Build()), verifyResults);
+ }
+
+ private static void RunOnEndBlockTest(Func<Action<BlockType>, CallbackVisitor> ctor, Action<BlockType, BlockType> verifyResults = null)
+ {
+ RunCallbackTest(BlockType.Markup, ctor, (listener, expected) => listener.VisitEndBlock(new BlockBuilder() { Type = expected }.Build()), verifyResults);
+ }
+
+ private static void RunOnErrorTest(Func<Action<RazorError>, CallbackVisitor> ctor, Action<RazorError, RazorError> verifyResults = null)
+ {
+ RunCallbackTest(new RazorError("Foo", SourceLocation.Zero), ctor, (listener, expected) => listener.VisitError(expected), verifyResults);
+ }
+
+ private static void RunOnEndSpanTest(Func<Action<Span>, CallbackVisitor> ctor, Action<Span, Span> verifyResults = null)
+ {
+ RunCallbackTest(new SpanBuilder().Build(), ctor, (listener, expected) => listener.VisitSpan(expected), verifyResults);
+ }
+
+ private static void RunCallbackTest<T>(T expected, Func<Action<T>, CallbackVisitor> ctor, Action<CallbackVisitor, T> call, Action<T, T> verifyResults = null)
+ {
+ // Arrange
+ object actual = null;
+ Action<T> callback = t => actual = t;
+
+ CallbackVisitor listener = ctor(callback);
+
+ // Act
+ call(listener, expected);
+
+ // Assert
+ if (verifyResults == null)
+ {
+ Assert.Equal(expected, actual);
+ }
+ else
+ {
+ verifyResults(expected, (T)actual);
+ }
+ }
+ }
+}
diff --git a/test/System.Web.Razor.Test/Parser/Html/HtmlAttributeTest.cs b/test/System.Web.Razor.Test/Parser/Html/HtmlAttributeTest.cs
new file mode 100644
index 00000000..65fac9e5
--- /dev/null
+++ b/test/System.Web.Razor.Test/Parser/Html/HtmlAttributeTest.cs
@@ -0,0 +1,268 @@
+using System.Web.Razor.Editor;
+using System.Web.Razor.Generator;
+using System.Web.Razor.Parser;
+using System.Web.Razor.Parser.SyntaxTree;
+using System.Web.Razor.Test.Framework;
+using System.Web.Razor.Text;
+using Xunit;
+
+namespace System.Web.Razor.Test.Parser.Html
+{
+ public class HtmlAttributeTest : CsHtmlMarkupParserTestBase
+ {
+ [Fact]
+ public void SimpleLiteralAttribute()
+ {
+ ParseBlockTest("<a href='Foo' />",
+ new MarkupBlock(
+ Factory.Markup("<a"),
+ new MarkupBlock(new AttributeBlockCodeGenerator(name: "href", prefix: new LocationTagged<string>(" href='", 2, 0, 2), suffix: new LocationTagged<string>("'", 12, 0, 12)),
+ Factory.Markup(" href='").With(SpanCodeGenerator.Null),
+ Factory.Markup("Foo").With(new LiteralAttributeCodeGenerator(prefix: new LocationTagged<string>(String.Empty, 9, 0, 9), value: new LocationTagged<string>("Foo", 9, 0, 9))),
+ Factory.Markup("'").With(SpanCodeGenerator.Null)),
+ Factory.Markup(" />").Accepts(AcceptedCharacters.None)));
+ }
+
+ [Fact]
+ public void MultiPartLiteralAttribute()
+ {
+ ParseBlockTest("<a href='Foo Bar Baz' />",
+ new MarkupBlock(
+ Factory.Markup("<a"),
+ new MarkupBlock(new AttributeBlockCodeGenerator(name: "href", prefix: new LocationTagged<string>(" href='", 2, 0, 2), suffix: new LocationTagged<string>("'", 20, 0, 20)),
+ Factory.Markup(" href='").With(SpanCodeGenerator.Null),
+ Factory.Markup("Foo").With(new LiteralAttributeCodeGenerator(prefix: new LocationTagged<string>(String.Empty, 9, 0, 9), value: new LocationTagged<string>("Foo", 9, 0, 9))),
+ Factory.Markup(" Bar").With(new LiteralAttributeCodeGenerator(prefix: new LocationTagged<string>(" ", 12, 0, 12), value: new LocationTagged<string>("Bar", 13, 0, 13))),
+ Factory.Markup(" Baz").With(new LiteralAttributeCodeGenerator(prefix: new LocationTagged<string>(" ", 16, 0, 16), value: new LocationTagged<string>("Baz", 17, 0, 17))),
+ Factory.Markup("'").With(SpanCodeGenerator.Null)),
+ Factory.Markup(" />").Accepts(AcceptedCharacters.None)));
+ }
+
+ [Fact]
+ public void DoubleQuotedLiteralAttribute()
+ {
+ ParseBlockTest("<a href=\"Foo Bar Baz\" />",
+ new MarkupBlock(
+ Factory.Markup("<a"),
+ new MarkupBlock(new AttributeBlockCodeGenerator(name: "href", prefix: new LocationTagged<string>(" href=\"", 2, 0, 2), suffix: new LocationTagged<string>("\"", 20, 0, 20)),
+ Factory.Markup(" href=\"").With(SpanCodeGenerator.Null),
+ Factory.Markup("Foo").With(new LiteralAttributeCodeGenerator(prefix: new LocationTagged<string>(String.Empty, 9, 0, 9), value: new LocationTagged<string>("Foo", 9, 0, 9))),
+ Factory.Markup(" Bar").With(new LiteralAttributeCodeGenerator(prefix: new LocationTagged<string>(" ", 12, 0, 12), value: new LocationTagged<string>("Bar", 13, 0, 13))),
+ Factory.Markup(" Baz").With(new LiteralAttributeCodeGenerator(prefix: new LocationTagged<string>(" ", 16, 0, 16), value: new LocationTagged<string>("Baz", 17, 0, 17))),
+ Factory.Markup("\"").With(SpanCodeGenerator.Null)),
+ Factory.Markup(" />").Accepts(AcceptedCharacters.None)));
+ }
+
+ [Fact]
+ public void UnquotedLiteralAttribute()
+ {
+ ParseBlockTest("<a href=Foo Bar Baz />",
+ new MarkupBlock(
+ Factory.Markup("<a"),
+ new MarkupBlock(new AttributeBlockCodeGenerator(name: "href", prefix: new LocationTagged<string>(" href=", 2, 0, 2), suffix: new LocationTagged<string>(String.Empty, 11, 0, 11)),
+ Factory.Markup(" href=").With(SpanCodeGenerator.Null),
+ Factory.Markup("Foo").With(new LiteralAttributeCodeGenerator(prefix: new LocationTagged<string>(String.Empty, 8, 0, 8), value: new LocationTagged<string>("Foo", 8, 0, 8)))),
+ Factory.Markup(" Bar Baz />").Accepts(AcceptedCharacters.None)));
+ }
+
+ [Fact]
+ public void SimpleExpressionAttribute()
+ {
+ ParseBlockTest("<a href='@foo' />",
+ new MarkupBlock(
+ Factory.Markup("<a"),
+ new MarkupBlock(new AttributeBlockCodeGenerator(name: "href", prefix: new LocationTagged<string>(" href='", 2, 0, 2), suffix: new LocationTagged<string>("'", 13, 0, 13)),
+ Factory.Markup(" href='").With(SpanCodeGenerator.Null),
+ new MarkupBlock(new DynamicAttributeBlockCodeGenerator(new LocationTagged<string>(String.Empty, 9, 0, 9), 9, 0, 9),
+ new ExpressionBlock(
+ Factory.CodeTransition(),
+ Factory.Code("foo")
+ .AsImplicitExpression(CSharpCodeParser.DefaultKeywords)
+ .Accepts(AcceptedCharacters.NonWhiteSpace))),
+ Factory.Markup("'").With(SpanCodeGenerator.Null)),
+ Factory.Markup(" />").Accepts(AcceptedCharacters.None)));
+ }
+
+ [Fact]
+ public void MultiValueExpressionAttribute()
+ {
+ ParseBlockTest("<a href='@foo bar @baz' />",
+ new MarkupBlock(
+ Factory.Markup("<a"),
+ new MarkupBlock(new AttributeBlockCodeGenerator(name: "href", prefix: new LocationTagged<string>(" href='", 2, 0, 2), suffix: new LocationTagged<string>("'", 22, 0, 22)),
+ Factory.Markup(" href='").With(SpanCodeGenerator.Null),
+ new MarkupBlock(new DynamicAttributeBlockCodeGenerator(new LocationTagged<string>(String.Empty, 9, 0, 9), 9, 0, 9),
+ new ExpressionBlock(
+ Factory.CodeTransition(),
+ Factory.Code("foo")
+ .AsImplicitExpression(CSharpCodeParser.DefaultKeywords)
+ .Accepts(AcceptedCharacters.NonWhiteSpace))),
+ Factory.Markup(" bar").With(new LiteralAttributeCodeGenerator(new LocationTagged<string>(" ", 13, 0, 13), new LocationTagged<string>("bar", 14, 0, 14))),
+ new MarkupBlock(new DynamicAttributeBlockCodeGenerator(new LocationTagged<string>(" ", 17, 0, 17), 18, 0, 18),
+ Factory.Markup(" ").With(SpanCodeGenerator.Null),
+ new ExpressionBlock(
+ Factory.CodeTransition(),
+ Factory.Code("baz")
+ .AsImplicitExpression(CSharpCodeParser.DefaultKeywords)
+ .Accepts(AcceptedCharacters.NonWhiteSpace))),
+ Factory.Markup("'").With(SpanCodeGenerator.Null)),
+ Factory.Markup(" />").Accepts(AcceptedCharacters.None)));
+ }
+
+ [Fact]
+ public void VirtualPathAttributesWorkWithConditionalAttributes()
+ {
+ ParseBlockTest("<a href='@foo ~/Foo/Bar' />",
+ new MarkupBlock(
+ Factory.Markup("<a"),
+ new MarkupBlock(new AttributeBlockCodeGenerator(name: "href", prefix: new LocationTagged<string>(" href='", 2, 0, 2), suffix: new LocationTagged<string>("'", 23, 0, 23)),
+ Factory.Markup(" href='").With(SpanCodeGenerator.Null),
+ new MarkupBlock(new DynamicAttributeBlockCodeGenerator(new LocationTagged<string>(String.Empty, 9, 0, 9), 9, 0, 9),
+ new ExpressionBlock(
+ Factory.CodeTransition(),
+ Factory.Code("foo")
+ .AsImplicitExpression(CSharpCodeParser.DefaultKeywords)
+ .Accepts(AcceptedCharacters.NonWhiteSpace))),
+ Factory.Markup(" ~/Foo/Bar")
+ .WithEditorHints(EditorHints.VirtualPath)
+ .With(new LiteralAttributeCodeGenerator(
+ new LocationTagged<string>(" ", 13, 0, 13),
+ new LocationTagged<SpanCodeGenerator>(new ResolveUrlCodeGenerator(), 14, 0, 14))),
+ Factory.Markup("'").With(SpanCodeGenerator.Null)),
+ Factory.Markup(" />").Accepts(AcceptedCharacters.None)));
+ }
+
+ [Fact]
+ public void UnquotedAttributeWithCodeWithSpacesInBlock()
+ {
+ ParseBlockTest("<input value=@foo />",
+ new MarkupBlock(
+ Factory.Markup("<input"),
+ new MarkupBlock(new AttributeBlockCodeGenerator(name: "value", prefix: new LocationTagged<string>(" value=", 6, 0, 6), suffix: new LocationTagged<string>(String.Empty, 17, 0, 17)),
+ Factory.Markup(" value=").With(SpanCodeGenerator.Null),
+ new MarkupBlock(new DynamicAttributeBlockCodeGenerator(new LocationTagged<string>(String.Empty, 13, 0, 13), 13, 0, 13),
+ new ExpressionBlock(
+ Factory.CodeTransition(),
+ Factory.Code("foo")
+ .AsImplicitExpression(CSharpCodeParser.DefaultKeywords)
+ .Accepts(AcceptedCharacters.NonWhiteSpace)))),
+ Factory.Markup(" />").Accepts(AcceptedCharacters.None)));
+ }
+
+ [Fact]
+ public void UnquotedAttributeWithCodeWithSpacesInDocument()
+ {
+ ParseDocumentTest("<input value=@foo />",
+ new MarkupBlock(
+ Factory.Markup("<input"),
+ new MarkupBlock(new AttributeBlockCodeGenerator(name: "value", prefix: new LocationTagged<string>(" value=", 6, 0, 6), suffix: new LocationTagged<string>(String.Empty, 17, 0, 17)),
+ Factory.Markup(" value=").With(SpanCodeGenerator.Null),
+ new MarkupBlock(new DynamicAttributeBlockCodeGenerator(new LocationTagged<string>(String.Empty, 13, 0, 13), 13, 0, 13),
+ new ExpressionBlock(
+ Factory.CodeTransition(),
+ Factory.Code("foo")
+ .AsImplicitExpression(CSharpCodeParser.DefaultKeywords)
+ .Accepts(AcceptedCharacters.NonWhiteSpace)))),
+ Factory.Markup(" />")));
+ }
+
+ [Fact]
+ public void ConditionalAttributeCollapserDoesNotRemoveUrlAttributeValues()
+ {
+ // Act
+ ParserResults results = ParseDocument("<a href='~/Foo/Bar' />");
+ Block rewritten = new ConditionalAttributeCollapser(new HtmlMarkupParser().BuildSpan).Rewrite(results.Document);
+ rewritten = new MarkupCollapser(new HtmlMarkupParser().BuildSpan).Rewrite(rewritten);
+
+ // Assert
+ Assert.Equal(0, results.ParserErrors.Count);
+ EvaluateParseTree(rewritten,
+ new MarkupBlock(
+ Factory.Markup("<a"),
+ new MarkupBlock(new AttributeBlockCodeGenerator(name: "href", prefix: new LocationTagged<string>(" href='", 2, 0, 2), suffix: new LocationTagged<string>("'", 18, 0, 18)),
+ Factory.Markup(" href='").With(SpanCodeGenerator.Null),
+ Factory.Markup("~/Foo/Bar")
+ .WithEditorHints(EditorHints.VirtualPath)
+ .With(new LiteralAttributeCodeGenerator(
+ new LocationTagged<string>(String.Empty, 9, 0, 9),
+ new LocationTagged<SpanCodeGenerator>(new ResolveUrlCodeGenerator(), 9, 0, 9))),
+ Factory.Markup("'").With(SpanCodeGenerator.Null)),
+ Factory.Markup(" />")));
+ }
+
+ [Fact]
+ public void ConditionalAttributesDoNotCreateExtraDataForEntirelyLiteralAttribute()
+ {
+ // Arrange
+ const string code =
+ #region Big Block o' code
+ @"<div class=""sidebar"">
+ <h1>Title</h1>
+ <p>
+ As the author, you can <a href=""/Photo/Edit/photoId"">edit</a>
+ or <a href=""/Photo/Remove/photoId"">remove</a> this photo.
+ </p>
+ <dl>
+ <dt class=""description"">Description</dt>
+ <dd class=""description"">
+ The uploader did not provide a description for this photo.
+ </dd>
+ <dt class=""uploaded-by"">Uploaded by</dt>
+ <dd class=""uploaded-by""><a href=""/User/View/user.UserId"">user.DisplayName</a></dd>
+ <dt class=""upload-date"">Upload date</dt>
+ <dd class=""upload-date"">photo.UploadDate</dd>
+ <dt class=""part-of-gallery"">Gallery</dt>
+ <dd><a href=""/View/gallery.Id"" title=""View gallery.Name gallery"">gallery.Name</a></dd>
+ <dt class=""tags"">Tags</dt>
+ <dd class=""tags"">
+ <ul class=""tags"">
+ <li>This photo has no tags.</li>
+ </ul>
+ <a href=""/Photo/EditTags/photoId"">edit tags</a>
+ </dd>
+ </dl>
+
+ <p>
+ <a class=""download"" href=""/Photo/Full/photoId"" title=""Download: (photo.FileTitle + photo.FileExtension)"">Download full photo</a> ((photo.FileSize / 1024) KB)
+ </p>
+</div>
+<div class=""main"">
+ <img class=""large-photo"" alt=""photo.FileTitle"" src=""/Photo/Thumbnail"" />
+ <h2>Nobody has commented on this photo</h2>
+ <ol class=""comments"">
+ <li>
+ <h3 class=""comment-header"">
+ <a href=""/User/View/comment.UserId"" title=""View comment.DisplayName's profile"">comment.DisplayName</a> commented at comment.CommentDate:
+ </h3>
+ <p class=""comment-body"">comment.CommentText</p>
+ </li>
+ </ol>
+
+ <form method=""post"" action="""">
+ <fieldset id=""addComment"">
+ <legend>Post new comment</legend>
+ <ol>
+ <li>
+ <label for=""newComment"">Comment</label>
+ <textarea id=""newComment"" name=""newComment"" title=""Your comment"" rows=""6"" cols=""70""></textarea>
+ </li>
+ </ol>
+ <p class=""form-actions"">
+ <input type=""submit"" title=""Add comment"" value=""Add comment"" />
+ </p>
+ </fieldset>
+ </form>
+</div>";
+ #endregion
+
+ // Act
+ ParserResults results = ParseDocument(code);
+ Block rewritten = new ConditionalAttributeCollapser(new HtmlMarkupParser().BuildSpan).Rewrite(results.Document);
+ rewritten = new MarkupCollapser(new HtmlMarkupParser().BuildSpan).Rewrite(rewritten);
+
+ // Assert
+ Assert.Equal(0, results.ParserErrors.Count);
+ EvaluateParseTree(rewritten, new MarkupBlock(Factory.Markup(code)));
+ }
+ }
+}
diff --git a/test/System.Web.Razor.Test/Parser/Html/HtmlBlockTest.cs b/test/System.Web.Razor.Test/Parser/Html/HtmlBlockTest.cs
new file mode 100644
index 00000000..d69b367c
--- /dev/null
+++ b/test/System.Web.Razor.Test/Parser/Html/HtmlBlockTest.cs
@@ -0,0 +1,388 @@
+using System.Web.Razor.Editor;
+using System.Web.Razor.Generator;
+using System.Web.Razor.Parser;
+using System.Web.Razor.Parser.SyntaxTree;
+using System.Web.Razor.Resources;
+using System.Web.Razor.Test.Framework;
+using System.Web.Razor.Text;
+using System.Web.Razor.Tokenizer.Symbols;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Razor.Test.Parser.Html
+{
+ public class HtmlBlockTest : CsHtmlMarkupParserTestBase
+ {
+ [Fact]
+ public void ParseBlockMethodThrowsArgNullExceptionOnNullContext()
+ {
+ // Arrange
+ HtmlMarkupParser parser = new HtmlMarkupParser();
+
+ // Act and Assert
+ Assert.Throws<InvalidOperationException>(() => parser.ParseBlock(), RazorResources.Parser_Context_Not_Set);
+ }
+
+ [Fact]
+ public void ParseBlockHandlesOpenAngleAtEof()
+ {
+ ParseDocumentTest(@"@{
+<",
+ new MarkupBlock(
+ Factory.EmptyHtml(),
+ new StatementBlock(
+ Factory.CodeTransition(),
+ Factory.MetaCode("{").Accepts(AcceptedCharacters.None),
+ Factory.Code("\r\n").AsStatement(),
+ new MarkupBlock(
+ Factory.Markup("<")))),
+ new RazorError(
+ String.Format(RazorResources.ParseError_Expected_EndOfBlock_Before_EOF, RazorResources.BlockName_Code, "}", "{"),
+ 1, 0, 1));
+ }
+
+ [Fact]
+ public void ParseBlockHandlesOpenAngleWithProperTagFollowingIt()
+ {
+ ParseDocumentTest(@"@{
+<
+</html>",
+ new MarkupBlock(
+ Factory.EmptyHtml(),
+ new StatementBlock(
+ Factory.CodeTransition(),
+ Factory.MetaCode("{").Accepts(AcceptedCharacters.None),
+ Factory.Code("\r\n").AsStatement(),
+ new MarkupBlock(
+ Factory.Markup("<\r\n")
+ ),
+ new MarkupBlock(
+ Factory.Markup(@"</html>").Accepts(AcceptedCharacters.None)
+ ),
+ Factory.EmptyCSharp().AsStatement()
+ )
+ ),
+ designTimeParser: true,
+ expectedErrors: new[]
+ {
+ new RazorError(String.Format(RazorResources.ParseError_UnexpectedEndTag, "html"), 7, 2, 0),
+ new RazorError(String.Format(RazorResources.ParseError_Expected_EndOfBlock_Before_EOF, "code", "}", "{"), 1, 0, 1)
+ });
+ }
+
+ [Fact]
+ public void TagWithoutCloseAngleDoesNotTerminateBlock()
+ {
+ ParseBlockTest(@"<
+ ",
+ new MarkupBlock(
+ Factory.Markup("< \r\n ")),
+ designTimeParser: true,
+ expectedErrors: new RazorError(String.Format(RazorResources.ParseError_UnfinishedTag, String.Empty), 0, 0, 0));
+ }
+
+ [Fact]
+ public void ParseBlockAllowsStartAndEndTagsToDifferInCase()
+ {
+ SingleSpanBlockTest("<li><p>Foo</P></lI>", BlockType.Markup, SpanKind.Markup, acceptedCharacters: AcceptedCharacters.None);
+ }
+
+ [Fact]
+ public void ParseBlockReadsToEndOfLineIfFirstCharacterAfterTransitionIsColon()
+ {
+ ParseBlockTest(@"@:<li>Foo Bar Baz
+bork",
+ new MarkupBlock(
+ Factory.MarkupTransition(),
+ Factory.MetaMarkup(":", HtmlSymbolType.Colon),
+ Factory.Markup("<li>Foo Bar Baz\r\n")
+ .With(new SingleLineMarkupEditHandler(CSharpLanguageCharacteristics.Instance.TokenizeString, AcceptedCharacters.None))
+ ));
+ }
+
+ [Fact]
+ public void ParseBlockStopsParsingSingleLineBlockAtEOFIfNoEOLReached()
+ {
+ ParseBlockTest("@:foo bar",
+ new MarkupBlock(
+ Factory.MarkupTransition(),
+ Factory.MetaMarkup(":", HtmlSymbolType.Colon),
+ Factory.Markup(@"foo bar")
+ .With(new SingleLineMarkupEditHandler(CSharpLanguageCharacteristics.Instance.TokenizeString))
+ ));
+ }
+
+ [Fact]
+ public void ParseBlockTreatsTwoAtSignsAsEscapeSequence()
+ {
+ HtmlParserTestUtils.RunSingleAtEscapeTest(ParseBlockTest);
+ }
+
+ [Fact]
+ public void ParseBlockTreatsPairsOfAtSignsAsEscapeSequence()
+ {
+ HtmlParserTestUtils.RunMultiAtEscapeTest(ParseBlockTest);
+ }
+
+ [Fact]
+ public void ParseBlockStopsAtMatchingCloseTagToStartTag()
+ {
+ SingleSpanBlockTest("<a><b></b></a><c></c>", "<a><b></b></a>", BlockType.Markup, SpanKind.Markup, acceptedCharacters: AcceptedCharacters.None);
+ }
+
+ [Fact]
+ public void ParseBlockParsesUntilMatchingEndTagIfFirstNonWhitespaceCharacterIsStartTag()
+ {
+ SingleSpanBlockTest("<baz><boz><biz></biz></boz></baz>", BlockType.Markup, SpanKind.Markup, acceptedCharacters: AcceptedCharacters.None);
+ }
+
+ [Fact]
+ public void ParseBlockAllowsUnclosedTagsAsLongAsItCanRecoverToAnExpectedEndTag()
+ {
+ SingleSpanBlockTest("<foo><bar><baz></foo>", BlockType.Markup, SpanKind.Markup, acceptedCharacters: AcceptedCharacters.None);
+ }
+
+ [Fact]
+ public void ParseBlockWithSelfClosingTagJustEmitsTag()
+ {
+ SingleSpanBlockTest("<foo />", BlockType.Markup, SpanKind.Markup, acceptedCharacters: AcceptedCharacters.None);
+ }
+
+ [Fact]
+ public void ParseBlockCanHandleSelfClosingTagsWithinBlock()
+ {
+ SingleSpanBlockTest("<foo><bar /></foo>", BlockType.Markup, SpanKind.Markup, acceptedCharacters: AcceptedCharacters.None);
+ }
+
+ [Fact]
+ public void ParseBlockSupportsTagsWithAttributes()
+ {
+ ParseBlockTest("<foo bar=\"baz\"><biz><boz zoop=zork/></biz></foo>",
+ new MarkupBlock(
+ Factory.Markup("<foo"),
+ new MarkupBlock(new AttributeBlockCodeGenerator("bar", new LocationTagged<string>(" bar=\"", 4, 0, 4), new LocationTagged<string>("\"", 13, 0, 13)),
+ Factory.Markup(" bar=\"").With(SpanCodeGenerator.Null),
+ Factory.Markup("baz").With(new LiteralAttributeCodeGenerator(new LocationTagged<string>(String.Empty, 10, 0, 10), new LocationTagged<string>("baz", 10, 0, 10))),
+ Factory.Markup("\"").With(SpanCodeGenerator.Null)),
+ Factory.Markup("><biz><boz"),
+ new MarkupBlock(new AttributeBlockCodeGenerator("zoop", new LocationTagged<string>(" zoop=", 24, 0, 24), new LocationTagged<string>(String.Empty, 34, 0, 34)),
+ Factory.Markup(" zoop=").With(SpanCodeGenerator.Null),
+ Factory.Markup("zork").With(new LiteralAttributeCodeGenerator(new LocationTagged<string>(String.Empty, 30, 0, 30), new LocationTagged<string>("zork", 30, 0, 30)))),
+ Factory.Markup("/></biz></foo>").Accepts(AcceptedCharacters.None)));
+ }
+
+ [Fact]
+ public void ParseBlockAllowsCloseAngleBracketInAttributeValueIfDoubleQuoted()
+ {
+ ParseBlockTest("<foo><bar baz=\">\" /></foo>",
+ new MarkupBlock(
+ Factory.Markup("<foo><bar"),
+ new MarkupBlock(new AttributeBlockCodeGenerator("baz", new LocationTagged<string>(" baz=\"", 9, 0, 9), new LocationTagged<string>("\"", 16, 0, 16)),
+ Factory.Markup(" baz=\"").With(SpanCodeGenerator.Null),
+ Factory.Markup(">").With(new LiteralAttributeCodeGenerator(new LocationTagged<string>(String.Empty, 15, 0, 15), new LocationTagged<string>(">", 15, 0, 15))),
+ Factory.Markup("\"").With(SpanCodeGenerator.Null)),
+ Factory.Markup(" /></foo>").Accepts(AcceptedCharacters.None)));
+ }
+
+ [Fact]
+ public void ParseBlockAllowsCloseAngleBracketInAttributeValueIfSingleQuoted()
+ {
+ ParseBlockTest("<foo><bar baz=\'>\' /></foo>",
+ new MarkupBlock(
+ Factory.Markup("<foo><bar"),
+ new MarkupBlock(new AttributeBlockCodeGenerator("baz", new LocationTagged<string>(" baz='", 9, 0, 9), new LocationTagged<string>("'", 16, 0, 16)),
+ Factory.Markup(" baz='").With(SpanCodeGenerator.Null),
+ Factory.Markup(">").With(new LiteralAttributeCodeGenerator(new LocationTagged<string>(String.Empty, 15, 0, 15), new LocationTagged<string>(">", 15, 0, 15))),
+ Factory.Markup("'").With(SpanCodeGenerator.Null)),
+ Factory.Markup(" /></foo>").Accepts(AcceptedCharacters.None)));
+ }
+
+ [Fact]
+ public void ParseBlockAllowsSlashInAttributeValueIfDoubleQuoted()
+ {
+ ParseBlockTest("<foo><bar baz=\"/\"></bar></foo>",
+ new MarkupBlock(
+ Factory.Markup("<foo><bar"),
+ new MarkupBlock(new AttributeBlockCodeGenerator("baz", new LocationTagged<string>(" baz=\"", 9, 0, 9), new LocationTagged<string>("\"", 16, 0, 16)),
+ Factory.Markup(" baz=\"").With(SpanCodeGenerator.Null),
+ Factory.Markup("/").With(new LiteralAttributeCodeGenerator(new LocationTagged<string>(String.Empty, 15, 0, 15), new LocationTagged<string>("/", 15, 0, 15))),
+ Factory.Markup("\"").With(SpanCodeGenerator.Null)),
+ Factory.Markup("></bar></foo>").Accepts(AcceptedCharacters.None)));
+ }
+
+ [Fact]
+ public void ParseBlockAllowsSlashInAttributeValueIfSingleQuoted()
+ {
+ ParseBlockTest("<foo><bar baz=\'/\'></bar></foo>",
+ new MarkupBlock(
+ Factory.Markup("<foo><bar"),
+ new MarkupBlock(new AttributeBlockCodeGenerator("baz", new LocationTagged<string>(" baz='", 9, 0, 9), new LocationTagged<string>("'", 16, 0, 16)),
+ Factory.Markup(" baz='").With(SpanCodeGenerator.Null),
+ Factory.Markup("/").With(new LiteralAttributeCodeGenerator(new LocationTagged<string>(String.Empty, 15, 0, 15), new LocationTagged<string>("/", 15, 0, 15))),
+ Factory.Markup("'").With(SpanCodeGenerator.Null)),
+ Factory.Markup("></bar></foo>").Accepts(AcceptedCharacters.None)));
+ }
+
+ [Fact]
+ public void ParseBlockTerminatesAtEOF()
+ {
+ SingleSpanBlockTest("<foo>", "<foo>", BlockType.Markup, SpanKind.Markup,
+ new RazorError(String.Format(RazorResources.ParseError_MissingEndTag, "foo"), new SourceLocation(0, 0, 0)));
+ }
+
+ [Fact]
+ public void ParseBlockSupportsCommentAsBlock()
+ {
+ SingleSpanBlockTest("<!-- foo -->", BlockType.Markup, SpanKind.Markup, acceptedCharacters: AcceptedCharacters.None);
+ }
+
+ [Fact]
+ public void ParseBlockSupportsCommentWithinBlock()
+ {
+ SingleSpanBlockTest("<foo>bar<!-- zoop -->baz</foo>", BlockType.Markup, SpanKind.Markup, acceptedCharacters: AcceptedCharacters.None);
+ }
+
+ [Fact]
+ public void ParseBlockProperlyBalancesCommentStartAndEndTags()
+ {
+ SingleSpanBlockTest("<!--<foo></bar>-->", BlockType.Markup, SpanKind.Markup, acceptedCharacters: AcceptedCharacters.None);
+ }
+
+ [Fact]
+ public void ParseBlockTerminatesAtEOFWhenParsingComment()
+ {
+ SingleSpanBlockTest("<!--<foo>", "<!--<foo>", BlockType.Markup, SpanKind.Markup);
+ }
+
+ [Fact]
+ public void ParseBlockOnlyTerminatesCommentOnFullEndSequence()
+ {
+ SingleSpanBlockTest("<!--<foo>--</bar>-->", BlockType.Markup, SpanKind.Markup, acceptedCharacters: AcceptedCharacters.None);
+ }
+
+ [Fact]
+ public void ParseBlockTerminatesCommentAtFirstOccurrenceOfEndSequence()
+ {
+ SingleSpanBlockTest("<foo><!--<foo></bar-->--></foo>", BlockType.Markup, SpanKind.Markup, acceptedCharacters: AcceptedCharacters.None);
+ }
+
+ [Fact]
+ public void ParseBlockTreatsMalformedTagsAsContent()
+ {
+ SingleSpanBlockTest(
+ "<foo></!-- bar --></foo>",
+ "<foo></!-- bar -->",
+ BlockType.Markup,
+ SpanKind.Markup,
+ AcceptedCharacters.None,
+ new RazorError(String.Format(RazorResources.ParseError_MissingEndTag, "foo"), 0, 0, 0));
+ }
+
+
+ [Fact]
+ public void ParseBlockParsesSGMLDeclarationAsEmptyTag()
+ {
+ SingleSpanBlockTest("<foo><!DOCTYPE foo bar baz></foo>", BlockType.Markup, SpanKind.Markup, acceptedCharacters: AcceptedCharacters.None);
+ }
+
+ [Fact]
+ public void ParseBlockTerminatesSGMLDeclarationAtFirstCloseAngle()
+ {
+ SingleSpanBlockTest("<foo><!DOCTYPE foo bar> baz></foo>", BlockType.Markup, SpanKind.Markup, acceptedCharacters: AcceptedCharacters.None);
+ }
+
+ [Fact]
+ public void ParseBlockParsesXMLProcessingInstructionAsEmptyTag()
+ {
+ SingleSpanBlockTest("<foo><?xml foo bar baz?></foo>", BlockType.Markup, SpanKind.Markup, acceptedCharacters: AcceptedCharacters.None);
+ }
+
+ [Fact]
+ public void ParseBlockTerminatesXMLProcessingInstructionAtQuestionMarkCloseAnglePair()
+ {
+ SingleSpanBlockTest("<foo><?xml foo bar?> baz</foo>", BlockType.Markup, SpanKind.Markup, acceptedCharacters: AcceptedCharacters.None);
+ }
+
+ [Fact]
+ public void ParseBlockDoesNotTerminateXMLProcessingInstructionAtCloseAngleUnlessPreceededByQuestionMark()
+ {
+ SingleSpanBlockTest("<foo><?xml foo bar> baz?></foo>", BlockType.Markup, SpanKind.Markup, acceptedCharacters: AcceptedCharacters.None);
+ }
+
+ [Fact]
+ public void ParseBlockSupportsScriptTagsWithLessThanSignsInThem()
+ {
+ SingleSpanBlockTest(@"<script>if(foo<bar) { alert(""baz"");)</script>", BlockType.Markup, SpanKind.Markup, acceptedCharacters: AcceptedCharacters.None);
+ }
+
+ [Fact]
+ public void ParseBlockSupportsScriptTagsWithSpacedLessThanSignsInThem()
+ {
+ SingleSpanBlockTest(@"<script>if(foo < bar) { alert(""baz"");)</script>", BlockType.Markup, SpanKind.Markup, acceptedCharacters: AcceptedCharacters.None);
+ }
+
+ [Fact]
+ public void ParseBlockAcceptsEmptyTextTag()
+ {
+ ParseBlockTest("<text/>",
+ new MarkupBlock(
+ Factory.MarkupTransition("<text/>")
+ ));
+ }
+
+ [Fact]
+ public void ParseBlockAcceptsTextTagAsOuterTagButDoesNotRender()
+ {
+ ParseBlockTest("<text>Foo Bar <foo> Baz</text> zoop",
+ new MarkupBlock(
+ Factory.MarkupTransition("<text>"),
+ Factory.Markup("Foo Bar <foo> Baz"),
+ Factory.MarkupTransition("</text>"),
+ Factory.Markup(" ").Accepts(AcceptedCharacters.None)
+ ));
+ }
+
+ [Fact]
+ public void ParseBlockRendersLiteralTextTagIfDoubled()
+ {
+ ParseBlockTest("<text><text>Foo Bar <foo> Baz</text></text> zoop",
+ new MarkupBlock(
+ Factory.MarkupTransition("<text>"),
+ Factory.Markup("<text>Foo Bar <foo> Baz</text>"),
+ Factory.MarkupTransition("</text>"),
+ Factory.Markup(" ").Accepts(AcceptedCharacters.None)
+ ));
+ }
+
+ [Fact]
+ public void ParseBlockDoesNotConsiderPsuedoTagWithinMarkupBlock()
+ {
+ ParseBlockTest("<foo><text><bar></bar></foo>",
+ new MarkupBlock(
+ Factory.Markup("<foo><text><bar></bar></foo>").Accepts(AcceptedCharacters.None)
+ ));
+ }
+
+ [Fact]
+ public void ParseBlockStopsParsingMidEmptyTagIfEOFReached()
+ {
+ ParseBlockTest("<br/",
+ new MarkupBlock(
+ Factory.Markup("<br/")
+ ),
+ new RazorError(String.Format(RazorResources.ParseError_UnfinishedTag, "br"), SourceLocation.Zero));
+ }
+
+ [Fact]
+ public void ParseBlockCorrectlyHandlesSingleLineOfMarkupWithEmbeddedStatement()
+ {
+ ParseBlockTest("<div>Foo @if(true) {} Bar</div>",
+ new MarkupBlock(
+ Factory.Markup("<div>Foo "),
+ new StatementBlock(
+ Factory.CodeTransition(),
+ Factory.Code("if(true) {}").AsStatement()),
+ Factory.Markup(" Bar</div>").Accepts(AcceptedCharacters.None)));
+ }
+ }
+}
diff --git a/test/System.Web.Razor.Test/Parser/Html/HtmlDocumentTest.cs b/test/System.Web.Razor.Test/Parser/Html/HtmlDocumentTest.cs
new file mode 100644
index 00000000..58a18819
--- /dev/null
+++ b/test/System.Web.Razor.Test/Parser/Html/HtmlDocumentTest.cs
@@ -0,0 +1,214 @@
+using System.Web.Razor.Generator;
+using System.Web.Razor.Parser;
+using System.Web.Razor.Parser.SyntaxTree;
+using System.Web.Razor.Resources;
+using System.Web.Razor.Test.Framework;
+using System.Web.Razor.Text;
+using System.Web.WebPages.TestUtils;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Razor.Test.Parser.Html
+{
+ public class HtmlDocumentTest : CsHtmlMarkupParserTestBase
+ {
+ private static readonly TestFile Nested1000 = TestFile.Create("nested-1000.html");
+
+ [Fact]
+ public void ParseDocumentMethodThrowsArgNullExceptionOnNullContext()
+ {
+ // Arrange
+ HtmlMarkupParser parser = new HtmlMarkupParser();
+
+ // Act and Assert
+ Assert.Throws<InvalidOperationException>(() => parser.ParseDocument(), RazorResources.Parser_Context_Not_Set);
+ }
+
+ [Fact]
+ public void ParseSectionMethodThrowsArgNullExceptionOnNullContext()
+ {
+ // Arrange
+ HtmlMarkupParser parser = new HtmlMarkupParser();
+
+ // Act and Assert
+ Assert.Throws<InvalidOperationException>(() => parser.ParseSection(null, true), RazorResources.Parser_Context_Not_Set);
+ }
+
+ [Fact]
+ public void ParseDocumentOutputsEmptyBlockWithEmptyMarkupSpanIfContentIsEmptyString()
+ {
+ ParseDocumentTest(String.Empty, new MarkupBlock(Factory.EmptyHtml()));
+ }
+
+ [Fact]
+ public void ParseDocumentOutputsWhitespaceOnlyContentAsSingleWhitespaceMarkupSpan()
+ {
+ SingleSpanDocumentTest(" ", BlockType.Markup, SpanKind.Markup);
+ }
+
+ [Fact]
+ public void ParseDocumentAcceptsSwapTokenAtEndOfFileAndOutputsZeroLengthCodeSpan()
+ {
+ ParseDocumentTest("@",
+ new MarkupBlock(
+ Factory.EmptyHtml(),
+ new ExpressionBlock(
+ Factory.CodeTransition(),
+ Factory.EmptyCSharp()
+ .AsImplicitExpression(CSharpCodeParser.DefaultKeywords)
+ .Accepts(AcceptedCharacters.NonWhiteSpace)),
+ Factory.EmptyHtml()),
+ new RazorError(RazorResources.ParseError_Unexpected_EndOfFile_At_Start_Of_CodeBlock, 1, 0, 1));
+ }
+
+ [Fact]
+ public void ParseDocumentCorrectlyHandlesSingleLineOfMarkupWithEmbeddedStatement()
+ {
+ ParseDocumentTest("<div>Foo @if(true) {} Bar</div>",
+ new MarkupBlock(
+ Factory.Markup("<div>Foo "),
+ new StatementBlock(
+ Factory.CodeTransition(),
+ Factory.Code("if(true) {}").AsStatement()),
+ Factory.Markup(" Bar</div>")));
+ }
+
+ [Fact]
+ public void ParseDocumentWithinSectionDoesNotCreateDocumentLevelSpan()
+ {
+ ParseDocumentTest(@"@section Foo {
+ <html></html>
+}",
+ new MarkupBlock(
+ Factory.EmptyHtml(),
+ new SectionBlock(new SectionCodeGenerator("Foo"),
+ Factory.CodeTransition(),
+ Factory.MetaCode("section Foo {")
+ .AutoCompleteWith(null, atEndOfSpan: true),
+ new MarkupBlock(
+ Factory.Markup("\r\n <html></html>\r\n")),
+ Factory.MetaCode("}").Accepts(AcceptedCharacters.None)),
+ Factory.EmptyHtml()));
+ }
+
+ [Fact]
+ public void ParseDocumentParsesWholeContentAsOneSpanIfNoSwapCharacterEncountered()
+ {
+ SingleSpanDocumentTest("foo <bar>baz</bar>", BlockType.Markup, SpanKind.Markup);
+ }
+
+ [Fact]
+ public void ParseDocumentHandsParsingOverToCodeParserWhenAtSignEncounteredAndEmitsOutput()
+ {
+ ParseDocumentTest("foo @bar baz",
+ new MarkupBlock(
+ Factory.Markup("foo "),
+ new ExpressionBlock(
+ Factory.CodeTransition(),
+ Factory.Code("bar")
+ .AsImplicitExpression(CSharpCodeParser.DefaultKeywords)
+ .Accepts(AcceptedCharacters.NonWhiteSpace)),
+ Factory.Markup(" baz")));
+ }
+
+ [Fact]
+ public void ParseDocumentEmitsAtSignAsMarkupIfAtEndOfFile()
+ {
+ ParseDocumentTest("foo @",
+ new MarkupBlock(
+ Factory.Markup("foo "),
+ new ExpressionBlock(
+ Factory.CodeTransition(),
+ Factory.EmptyCSharp()
+ .AsImplicitExpression(CSharpCodeParser.DefaultKeywords)
+ .Accepts(AcceptedCharacters.NonWhiteSpace)),
+ Factory.EmptyHtml()),
+ new RazorError(RazorResources.ParseError_Unexpected_EndOfFile_At_Start_Of_CodeBlock, 5, 0, 5));
+ }
+
+ [Fact]
+ public void ParseDocumentEmitsCodeBlockIfFirstCharacterIsSwapCharacter()
+ {
+ ParseDocumentTest("@bar",
+ new MarkupBlock(
+ Factory.EmptyHtml(),
+ new ExpressionBlock(
+ Factory.CodeTransition(),
+ Factory.Code("bar")
+ .AsImplicitExpression(CSharpCodeParser.DefaultKeywords)
+ .Accepts(AcceptedCharacters.NonWhiteSpace)),
+ Factory.EmptyHtml()));
+ }
+
+ [Fact]
+ public void ParseDocumentDoesNotSwitchToCodeOnEmailAddressInText()
+ {
+ SingleSpanDocumentTest("<foo>anurse@microsoft.com</foo>", BlockType.Markup, SpanKind.Markup);
+ }
+
+ [Fact]
+ public void ParseDocumentDoesNotSwitchToCodeOnEmailAddressInAttribute()
+ {
+ ParseDocumentTest("<a href=\"mailto:anurse@microsoft.com\">Email me</a>",
+ new MarkupBlock(
+ Factory.Markup("<a"),
+ new MarkupBlock(new AttributeBlockCodeGenerator("href", new LocationTagged<string>(" href=\"", 2, 0, 2), new LocationTagged<string>("\"", 36, 0, 36)),
+ Factory.Markup(" href=\"").With(SpanCodeGenerator.Null),
+ Factory.Markup("mailto:anurse@microsoft.com")
+ .With(new LiteralAttributeCodeGenerator(new LocationTagged<string>(String.Empty, 9, 0, 9), new LocationTagged<string>("mailto:anurse@microsoft.com", 9, 0, 9))),
+ Factory.Markup("\"").With(SpanCodeGenerator.Null)),
+ Factory.Markup(">Email me</a>")));
+ }
+
+ [Fact]
+ public void ParseDocumentDoesNotReturnErrorOnMismatchedTags()
+ {
+ SingleSpanDocumentTest("Foo <div><p></p></p> Baz", BlockType.Markup, SpanKind.Markup);
+ }
+
+ [Fact]
+ public void ParseDocumentReturnsOneMarkupSegmentIfNoCodeBlocksEncountered()
+ {
+ SingleSpanDocumentTest("Foo <p>Baz<!--Foo-->Bar<!-F> Qux", BlockType.Markup, SpanKind.Markup);
+ }
+
+ [Fact]
+ public void ParseDocumentRendersTextPseudoTagAsMarkup()
+ {
+ SingleSpanDocumentTest("Foo <text>Foo</text>", BlockType.Markup, SpanKind.Markup);
+ }
+
+ [Fact]
+ public void ParseDocumentAcceptsEndTagWithNoMatchingStartTag()
+ {
+ SingleSpanDocumentTest("Foo </div> Bar", BlockType.Markup, SpanKind.Markup);
+ }
+
+ [Fact]
+ public void ParseDocumentNoLongerSupportsDollarOpenBraceCombination()
+ {
+ ParseDocumentTest("<foo>${bar}</foo>",
+ new MarkupBlock(
+ Factory.Markup("<foo>${bar}</foo>")));
+ }
+
+ [Fact]
+ public void ParseDocumentTreatsTwoAtSignsAsEscapeSequence()
+ {
+ HtmlParserTestUtils.RunSingleAtEscapeTest(ParseDocumentTest, lastSpanAcceptedCharacters: AcceptedCharacters.Any);
+ }
+
+ [Fact]
+ public void ParseDocumentTreatsPairsOfAtSignsAsEscapeSequence()
+ {
+ HtmlParserTestUtils.RunMultiAtEscapeTest(ParseDocumentTest, lastSpanAcceptedCharacters: AcceptedCharacters.Any);
+ }
+
+ [Fact]
+ public void ParseBlockCanParse1000NestedElements()
+ {
+ string content = Nested1000.ReadAllText();
+ SingleSpanDocumentTest(content, BlockType.Markup, SpanKind.Markup);
+ }
+ }
+}
diff --git a/test/System.Web.Razor.Test/Parser/Html/HtmlErrorTest.cs b/test/System.Web.Razor.Test/Parser/Html/HtmlErrorTest.cs
new file mode 100644
index 00000000..191a3f92
--- /dev/null
+++ b/test/System.Web.Razor.Test/Parser/Html/HtmlErrorTest.cs
@@ -0,0 +1,87 @@
+using System.Web.Razor.Generator;
+using System.Web.Razor.Parser.SyntaxTree;
+using System.Web.Razor.Resources;
+using System.Web.Razor.Test.Framework;
+using System.Web.Razor.Text;
+using Xunit;
+
+namespace System.Web.Razor.Test.Parser.Html
+{
+ public class HtmlErrorTest : CsHtmlMarkupParserTestBase
+ {
+ [Fact]
+ public void ParseBlockAllowsInvalidTagNamesAsLongAsParserCanIdentifyEndTag()
+ {
+ SingleSpanBlockTest("<1-foo+bar>foo</1-foo+bar>", BlockType.Markup, SpanKind.Markup, acceptedCharacters: AcceptedCharacters.None);
+ }
+
+ [Fact]
+ public void ParseBlockThrowsErrorIfStartTextTagContainsTextAfterName()
+ {
+ ParseBlockTest("<text foo bar></text>",
+ new MarkupBlock(
+ Factory.MarkupTransition("<text").Accepts(AcceptedCharacters.Any),
+ Factory.Markup(" foo bar>"),
+ Factory.MarkupTransition("</text>")),
+ new RazorError(RazorResources.ParseError_TextTagCannotContainAttributes, SourceLocation.Zero));
+ }
+
+ [Fact]
+ public void ParseBlockThrowsErrorIfEndTextTagContainsTextAfterName()
+ {
+ ParseBlockTest("<text></text foo bar>",
+ new MarkupBlock(
+ Factory.MarkupTransition("<text>"),
+ Factory.MarkupTransition("</text").Accepts(AcceptedCharacters.Any),
+ Factory.Markup(" ")),
+ new RazorError(RazorResources.ParseError_TextTagCannotContainAttributes, 6, 0, 6));
+ }
+
+ [Fact]
+ public void ParseBlockThrowsExceptionIfBlockDoesNotStartWithTag()
+ {
+ ParseBlockTest("foo bar <baz>",
+ new MarkupBlock(),
+ new RazorError(RazorResources.ParseError_MarkupBlock_Must_Start_With_Tag, SourceLocation.Zero));
+ }
+
+ [Fact]
+ public void ParseBlockStartingWithEndTagProducesRazorErrorThenOutputsMarkupSegmentAndEndsBlock()
+ {
+ ParseBlockTest("</foo> bar baz",
+ new MarkupBlock(
+ Factory.Markup("</foo> ").Accepts(AcceptedCharacters.None)),
+ new RazorError(String.Format(RazorResources.ParseError_UnexpectedEndTag, "foo"), SourceLocation.Zero));
+ }
+
+ [Fact]
+ public void ParseBlockWithUnclosedTopLevelTagThrowsMissingEndTagParserExceptionOnOutermostUnclosedTag()
+ {
+ ParseBlockTest("<p><foo></bar>",
+ new MarkupBlock(
+ Factory.Markup("<p><foo></bar>").Accepts(AcceptedCharacters.None)),
+ new RazorError(String.Format(RazorResources.ParseError_MissingEndTag, "p"), new SourceLocation(0, 0, 0)));
+ }
+
+ [Fact]
+ public void ParseBlockWithUnclosedTagAtEOFThrowsMissingEndTagException()
+ {
+ ParseBlockTest("<foo>blah blah blah blah blah",
+ new MarkupBlock(
+ Factory.Markup("<foo>blah blah blah blah blah")),
+ new RazorError(String.Format(RazorResources.ParseError_MissingEndTag, "foo"), new SourceLocation(0, 0, 0)));
+ }
+
+ [Fact]
+ public void ParseBlockWithUnfinishedTagAtEOFThrowsIncompleteTagException()
+ {
+ ParseBlockTest("<foo bar=baz",
+ new MarkupBlock(
+ Factory.Markup("<foo"),
+ new MarkupBlock(new AttributeBlockCodeGenerator("bar", new LocationTagged<string>(" bar=", 4, 0, 4), new LocationTagged<string>(String.Empty, 12, 0, 12)),
+ Factory.Markup(" bar=").With(SpanCodeGenerator.Null),
+ Factory.Markup("baz").With(new LiteralAttributeCodeGenerator(new LocationTagged<string>(String.Empty, 9, 0, 9), new LocationTagged<string>("baz", 9, 0, 9))))),
+ new RazorError(String.Format(RazorResources.ParseError_UnfinishedTag, "foo"), new SourceLocation(0, 0, 0)));
+ }
+ }
+}
diff --git a/test/System.Web.Razor.Test/Parser/Html/HtmlParserTestUtils.cs b/test/System.Web.Razor.Test/Parser/Html/HtmlParserTestUtils.cs
new file mode 100644
index 00000000..595651fb
--- /dev/null
+++ b/test/System.Web.Razor.Test/Parser/Html/HtmlParserTestUtils.cs
@@ -0,0 +1,37 @@
+using System.Web.Razor.Parser;
+using System.Web.Razor.Parser.SyntaxTree;
+using System.Web.Razor.Test.Framework;
+
+namespace System.Web.Razor.Test.Parser.Html
+{
+ internal class HtmlParserTestUtils
+ {
+ public static void RunSingleAtEscapeTest(Action<string, Block> testMethod, AcceptedCharacters lastSpanAcceptedCharacters = AcceptedCharacters.None)
+ {
+ var factory = SpanFactory.CreateCsHtml();
+ testMethod("<foo>@@bar</foo>",
+ new MarkupBlock(
+ factory.Markup("<foo>"),
+ factory.Markup("@").Hidden(),
+ factory.Markup("@bar</foo>").Accepts(lastSpanAcceptedCharacters)));
+ }
+
+ public static void RunMultiAtEscapeTest(Action<string, Block> testMethod, AcceptedCharacters lastSpanAcceptedCharacters = AcceptedCharacters.None)
+ {
+ var factory = SpanFactory.CreateCsHtml();
+ testMethod("<foo>@@@@@bar</foo>",
+ new MarkupBlock(
+ factory.Markup("<foo>"),
+ factory.Markup("@").Hidden(),
+ factory.Markup("@"),
+ factory.Markup("@").Hidden(),
+ factory.Markup("@"),
+ new ExpressionBlock(
+ factory.CodeTransition(),
+ factory.Code("bar")
+ .AsImplicitExpression(CSharpCodeParser.DefaultKeywords)
+ .Accepts(AcceptedCharacters.NonWhiteSpace)),
+ factory.Markup("</foo>").Accepts(lastSpanAcceptedCharacters)));
+ }
+ }
+}
diff --git a/test/System.Web.Razor.Test/Parser/Html/HtmlTagsTest.cs b/test/System.Web.Razor.Test/Parser/Html/HtmlTagsTest.cs
new file mode 100644
index 00000000..465666e1
--- /dev/null
+++ b/test/System.Web.Razor.Test/Parser/Html/HtmlTagsTest.cs
@@ -0,0 +1,150 @@
+using System.Collections.Generic;
+using System.Web.Razor.Parser.SyntaxTree;
+using System.Web.Razor.Resources;
+using System.Web.Razor.Test.Framework;
+using Xunit;
+using Xunit.Extensions;
+
+namespace System.Web.Razor.Test.Parser.Html
+{
+ public class HtmlTagsTest : CsHtmlMarkupParserTestBase
+ {
+ public static IEnumerable<string[]> VoidElementNames
+ {
+ get
+ {
+ yield return new[] { "area" };
+ yield return new[] { "base" };
+ yield return new[] { "br" };
+ yield return new[] { "col" };
+ yield return new[] { "command" };
+ yield return new[] { "embed" };
+ yield return new[] { "hr" };
+ yield return new[] { "img" };
+ yield return new[] { "input" };
+ yield return new[] { "keygen" };
+ yield return new[] { "link" };
+ yield return new[] { "meta" };
+ yield return new[] { "param" };
+ yield return new[] { "source" };
+ yield return new[] { "track" };
+ yield return new[] { "wbr" };
+ }
+ }
+
+ [Fact]
+ public void EmptyTagNestsLikeNormalTag()
+ {
+ ParseBlockTest("<p></> Bar",
+ new MarkupBlock(
+ Factory.Markup("<p></> ").Accepts(AcceptedCharacters.None)),
+ new RazorError(String.Format(RazorResources.ParseError_MissingEndTag, "p"), 0, 0, 0));
+ }
+
+ [Fact]
+ public void EmptyTag()
+ {
+ ParseBlockTest("<></> Bar",
+ new MarkupBlock(
+ Factory.Markup("<></> ").Accepts(AcceptedCharacters.None)));
+ }
+
+ [Fact]
+ public void CommentTag()
+ {
+ ParseBlockTest("<!--Foo--> Bar",
+ new MarkupBlock(
+ Factory.Markup("<!--Foo--> ").Accepts(AcceptedCharacters.None)));
+ }
+
+ [Fact]
+ public void DocTypeTag()
+ {
+ ParseBlockTest("<!DOCTYPE html> foo",
+ new MarkupBlock(
+ Factory.Markup("<!DOCTYPE html> ").Accepts(AcceptedCharacters.None)));
+ }
+
+ [Fact]
+ public void ProcessingInstructionTag()
+ {
+ ParseBlockTest("<?xml version=\"1.0\" ?> foo",
+ new MarkupBlock(
+ Factory.Markup("<?xml version=\"1.0\" ?> ").Accepts(AcceptedCharacters.None)));
+ }
+
+ [Fact]
+ public void ElementTags()
+ {
+ ParseBlockTest("<p>Foo</p> Bar",
+ new MarkupBlock(
+ Factory.Markup("<p>Foo</p> ").Accepts(AcceptedCharacters.None)));
+ }
+
+ [Fact]
+ public void TextTags()
+ {
+ ParseBlockTest("<text>Foo</text>}",
+ new MarkupBlock(
+ Factory.MarkupTransition("<text>"),
+ Factory.Markup("Foo"),
+ Factory.MarkupTransition("</text>")));
+ }
+
+ [Fact]
+ public void CDataTag()
+ {
+ ParseBlockTest("<![CDATA[Foo]]> Bar",
+ new MarkupBlock(
+ Factory.Markup("<![CDATA[Foo]]> ").Accepts(AcceptedCharacters.None)));
+ }
+
+ [Fact]
+ public void ScriptTag()
+ {
+ ParseDocumentTest("<script>foo < bar && quantity.toString() !== orderQty.val()</script>",
+ new MarkupBlock(
+ Factory.Markup("<script>foo < bar && quantity.toString() !== orderQty.val()</script>")));
+ }
+
+ [Theory]
+ [PropertyData("VoidElementNames")]
+ public void VoidElementFollowedByContent(string tagName)
+ {
+ ParseBlockTest("<" + tagName + ">foo",
+ new MarkupBlock(
+ Factory.Markup("<" + tagName + ">")
+ .Accepts(AcceptedCharacters.None)));
+ }
+
+ [Theory]
+ [PropertyData("VoidElementNames")]
+ public void VoidElementFollowedByOtherTag(string tagName)
+ {
+ ParseBlockTest("<" + tagName + "><other>foo",
+ new MarkupBlock(
+ Factory.Markup("<" + tagName + ">")
+ .Accepts(AcceptedCharacters.None)));
+ }
+
+ [Theory]
+ [PropertyData("VoidElementNames")]
+ public void VoidElementFollowedByCloseTag(string tagName)
+ {
+ ParseBlockTest("<" + tagName + "> </" + tagName + ">foo",
+ new MarkupBlock(
+ Factory.Markup("<" + tagName + "> </" + tagName + ">")
+ .Accepts(AcceptedCharacters.None)));
+ }
+
+ [Theory]
+ [PropertyData("VoidElementNames")]
+ public void IncompleteVoidElementEndTag(string tagName)
+ {
+ ParseBlockTest("<" + tagName + "></" + tagName,
+ new MarkupBlock(
+ Factory.Markup("<" + tagName + "></" + tagName)
+ .Accepts(AcceptedCharacters.Any)));
+ }
+ }
+}
diff --git a/test/System.Web.Razor.Test/Parser/Html/HtmlToCodeSwitchTest.cs b/test/System.Web.Razor.Test/Parser/Html/HtmlToCodeSwitchTest.cs
new file mode 100644
index 00000000..67275318
--- /dev/null
+++ b/test/System.Web.Razor.Test/Parser/Html/HtmlToCodeSwitchTest.cs
@@ -0,0 +1,222 @@
+using System.Web.Razor.Editor;
+using System.Web.Razor.Generator;
+using System.Web.Razor.Parser;
+using System.Web.Razor.Parser.SyntaxTree;
+using System.Web.Razor.Test.Framework;
+using System.Web.Razor.Text;
+using System.Web.Razor.Tokenizer.Symbols;
+using Xunit;
+
+namespace System.Web.Razor.Test.Parser.Html
+{
+ public class HtmlToCodeSwitchTest : CsHtmlMarkupParserTestBase
+ {
+ [Fact]
+ public void ParseBlockSwitchesWhenCharacterBeforeSwapIsNonAlphanumeric()
+ {
+ ParseBlockTest("<p>foo#@i</p>",
+ new MarkupBlock(
+ Factory.Markup("<p>foo#"),
+ new ExpressionBlock(
+ Factory.CodeTransition(),
+ Factory.Code("i").AsImplicitExpression(CSharpCodeParser.DefaultKeywords).Accepts(AcceptedCharacters.NonWhiteSpace)),
+ Factory.Markup("</p>").Accepts(AcceptedCharacters.None)));
+ }
+
+ [Fact]
+ public void ParseBlockSwitchesToCodeWhenSwapCharacterEncounteredMidTag()
+ {
+ ParseBlockTest("<foo @bar />",
+ new MarkupBlock(
+ Factory.Markup("<foo "),
+ new ExpressionBlock(
+ Factory.CodeTransition(),
+ Factory.Code("bar")
+ .AsImplicitExpression(CSharpCodeParser.DefaultKeywords)
+ .Accepts(AcceptedCharacters.NonWhiteSpace)),
+ Factory.Markup(" />").Accepts(AcceptedCharacters.None)));
+ }
+
+ [Fact]
+ public void ParseBlockSwitchesToCodeWhenSwapCharacterEncounteredInAttributeValue()
+ {
+ ParseBlockTest("<foo bar=\"@baz\" />",
+ new MarkupBlock(
+ Factory.Markup("<foo"),
+ new MarkupBlock(new AttributeBlockCodeGenerator("bar", new LocationTagged<string>(" bar=\"", 4, 0, 4), new LocationTagged<string>("\"", 14, 0, 14)),
+ Factory.Markup(" bar=\"").With(SpanCodeGenerator.Null),
+ new MarkupBlock(new DynamicAttributeBlockCodeGenerator(new LocationTagged<string>(String.Empty, 10, 0, 10), 10, 0, 10),
+ new ExpressionBlock(
+ Factory.CodeTransition(),
+ Factory.Code("baz")
+ .AsImplicitExpression(CSharpCodeParser.DefaultKeywords)
+ .Accepts(AcceptedCharacters.NonWhiteSpace))),
+ Factory.Markup("\"").With(SpanCodeGenerator.Null)),
+ Factory.Markup(" />").Accepts(AcceptedCharacters.None)));
+ }
+
+ [Fact]
+ public void ParseBlockSwitchesToCodeWhenSwapCharacterEncounteredInTagContent()
+ {
+ ParseBlockTest("<foo>@bar<baz>@boz</baz></foo>",
+ new MarkupBlock(
+ Factory.Markup("<foo>"),
+ new ExpressionBlock(
+ Factory.CodeTransition(),
+ Factory.Code("bar")
+ .AsImplicitExpression(CSharpCodeParser.DefaultKeywords)
+ .Accepts(AcceptedCharacters.NonWhiteSpace)),
+ Factory.Markup("<baz>"),
+ new ExpressionBlock(
+ Factory.CodeTransition(),
+ Factory.Code("boz")
+ .AsImplicitExpression(CSharpCodeParser.DefaultKeywords)
+ .Accepts(AcceptedCharacters.NonWhiteSpace)),
+ Factory.Markup("</baz></foo>").Accepts(AcceptedCharacters.None)));
+ }
+
+ [Fact]
+ public void ParseBlockParsesCodeWithinSingleLineMarkup()
+ {
+ ParseBlockTest(@"@:<li>Foo @Bar Baz
+bork",
+ new MarkupBlock(
+ Factory.MarkupTransition(),
+ Factory.MetaMarkup(":", HtmlSymbolType.Colon),
+ Factory.Markup("<li>Foo ").With(new SingleLineMarkupEditHandler(CSharpLanguageCharacteristics.Instance.TokenizeString)),
+ new ExpressionBlock(
+ Factory.CodeTransition(),
+ Factory.Code("Bar")
+ .AsImplicitExpression(CSharpCodeParser.DefaultKeywords)
+ .Accepts(AcceptedCharacters.NonWhiteSpace)),
+ Factory.Markup(" Baz\r\n")
+ .With(new SingleLineMarkupEditHandler(CSharpLanguageCharacteristics.Instance.TokenizeString, AcceptedCharacters.None))));
+ }
+
+ [Fact]
+ public void ParseBlockSupportsCodeWithinComment()
+ {
+ ParseBlockTest("<foo><!-- @foo --></foo>",
+ new MarkupBlock(
+ Factory.Markup("<foo><!-- "),
+ new ExpressionBlock(
+ Factory.CodeTransition(),
+ Factory.Code("foo")
+ .AsImplicitExpression(CSharpCodeParser.DefaultKeywords)
+ .Accepts(AcceptedCharacters.NonWhiteSpace)),
+ Factory.Markup(" --></foo>").Accepts(AcceptedCharacters.None)));
+ }
+
+ [Fact]
+ public void ParseBlockSupportsCodeWithinSGMLDeclaration()
+ {
+ ParseBlockTest("<foo><!DOCTYPE foo @bar baz></foo>",
+ new MarkupBlock(
+ Factory.Markup("<foo><!DOCTYPE foo "),
+ new ExpressionBlock(
+ Factory.CodeTransition(),
+ Factory.Code("bar")
+ .AsImplicitExpression(CSharpCodeParser.DefaultKeywords)
+ .Accepts(AcceptedCharacters.NonWhiteSpace)),
+ Factory.Markup(" baz></foo>").Accepts(AcceptedCharacters.None)));
+ }
+
+ [Fact]
+ public void ParseBlockSupportsCodeWithinCDataDeclaration()
+ {
+ ParseBlockTest("<foo><![CDATA[ foo @bar baz]]></foo>",
+ new MarkupBlock(
+ Factory.Markup("<foo><![CDATA[ foo "),
+ new ExpressionBlock(
+ Factory.CodeTransition(),
+ Factory.Code("bar")
+ .AsImplicitExpression(CSharpCodeParser.DefaultKeywords)
+ .Accepts(AcceptedCharacters.NonWhiteSpace)),
+ Factory.Markup(" baz]]></foo>").Accepts(AcceptedCharacters.None)));
+ }
+
+ [Fact]
+ public void ParseBlockSupportsCodeWithinXMLProcessingInstruction()
+ {
+ ParseBlockTest("<foo><?xml foo @bar baz?></foo>",
+ new MarkupBlock(
+ Factory.Markup("<foo><?xml foo "),
+ new ExpressionBlock(
+ Factory.CodeTransition(),
+ Factory.Code("bar")
+ .AsImplicitExpression(CSharpCodeParser.DefaultKeywords)
+ .Accepts(AcceptedCharacters.NonWhiteSpace)),
+ Factory.Markup(" baz?></foo>").Accepts(AcceptedCharacters.None)));
+ }
+
+ [Fact]
+ public void ParseBlockDoesNotSwitchToCodeOnEmailAddressInText()
+ {
+ SingleSpanBlockTest("<foo>anurse@microsoft.com</foo>", BlockType.Markup, SpanKind.Markup, acceptedCharacters: AcceptedCharacters.None);
+ }
+
+ [Fact]
+ public void ParseBlockDoesNotSwitchToCodeOnEmailAddressInAttribute()
+ {
+ ParseBlockTest("<a href=\"mailto:anurse@microsoft.com\">Email me</a>",
+ new MarkupBlock(
+ Factory.Markup("<a"),
+ new MarkupBlock(new AttributeBlockCodeGenerator("href", new LocationTagged<string>(" href=\"", 2, 0, 2), new LocationTagged<string>("\"", 36, 0, 36)),
+ Factory.Markup(" href=\"").With(SpanCodeGenerator.Null),
+ Factory.Markup("mailto:anurse@microsoft.com")
+ .With(new LiteralAttributeCodeGenerator(new LocationTagged<string>(String.Empty, 9, 0, 9), new LocationTagged<string>("mailto:anurse@microsoft.com", 9, 0, 9))),
+ Factory.Markup("\"").With(SpanCodeGenerator.Null)),
+ Factory.Markup(">Email me</a>").Accepts(AcceptedCharacters.None)));
+ }
+
+ [Fact]
+ public void ParseBlockGivesWhitespacePreceedingAtToCodeIfThereIsNoMarkupOnThatLine()
+ {
+ ParseBlockTest(@" <ul>
+ @foreach(var p in Products) {
+ <li>Product: @p.Name</li>
+ }
+ </ul>",
+ new MarkupBlock(
+ Factory.Markup(" <ul>\r\n"),
+ new StatementBlock(
+ Factory.Code(" ").AsStatement(),
+ Factory.CodeTransition(),
+ Factory.Code("foreach(var p in Products) {\r\n").AsStatement(),
+ new MarkupBlock(
+ Factory.Markup(" <li>Product: "),
+ new ExpressionBlock(
+ Factory.CodeTransition(),
+ Factory.Code("p.Name")
+ .AsImplicitExpression(CSharpCodeParser.DefaultKeywords)
+ .Accepts(AcceptedCharacters.NonWhiteSpace)),
+ Factory.Markup("</li>\r\n").Accepts(AcceptedCharacters.None)),
+ Factory.Code(" }\r\n").AsStatement().Accepts(AcceptedCharacters.None)),
+ Factory.Markup(" </ul>").Accepts(AcceptedCharacters.None)));
+ }
+
+ [Fact]
+ public void CSharpCodeParserDoesNotAcceptLeadingOrTrailingWhitespaceInDesignMode()
+ {
+ ParseBlockTest(@" <ul>
+ @foreach(var p in Products) {
+ <li>Product: @p.Name</li>
+ }
+ </ul>",
+ new MarkupBlock(
+ Factory.Markup(" <ul>\r\n "),
+ new StatementBlock(
+ Factory.CodeTransition(),
+ Factory.Code("foreach(var p in Products) {\r\n ").AsStatement(),
+ new MarkupBlock(
+ Factory.Markup("<li>Product: "),
+ new ExpressionBlock(
+ Factory.CodeTransition(),
+ Factory.Code("p.Name").AsImplicitExpression(CSharpCodeParser.DefaultKeywords).Accepts(AcceptedCharacters.NonWhiteSpace)),
+ Factory.Markup("</li>").Accepts(AcceptedCharacters.None)),
+ Factory.Code("\r\n }").AsStatement().Accepts(AcceptedCharacters.None)),
+ Factory.Markup("\r\n </ul>").Accepts(AcceptedCharacters.None)),
+ designTimeParser: true);
+ }
+ }
+}
diff --git a/test/System.Web.Razor.Test/Parser/Html/HtmlUrlAttributeTest.cs b/test/System.Web.Razor.Test/Parser/Html/HtmlUrlAttributeTest.cs
new file mode 100644
index 00000000..ad5fe9fa
--- /dev/null
+++ b/test/System.Web.Razor.Test/Parser/Html/HtmlUrlAttributeTest.cs
@@ -0,0 +1,270 @@
+using System.Web.Razor.Editor;
+using System.Web.Razor.Generator;
+using System.Web.Razor.Parser;
+using System.Web.Razor.Parser.SyntaxTree;
+using System.Web.Razor.Test.Framework;
+using System.Web.Razor.Text;
+using Xunit;
+
+namespace System.Web.Razor.Test.Parser.Html
+{
+ public class HtmlUrlAttributeTest : CsHtmlMarkupParserTestBase
+ {
+ [Fact]
+ public void SimpleUrlInAttributeInMarkupBlock()
+ {
+ ParseBlockTest("<a href='~/Foo/Bar/Baz' />",
+ new MarkupBlock(
+ Factory.Markup("<a"),
+ new MarkupBlock(new AttributeBlockCodeGenerator("href", new LocationTagged<string>(" href='", 2, 0, 2), new LocationTagged<string>("'", 22, 0, 22)),
+ Factory.Markup(" href='").With(SpanCodeGenerator.Null),
+ Factory.Markup("~/Foo/Bar/Baz")
+ .WithEditorHints(EditorHints.VirtualPath)
+ .With(new LiteralAttributeCodeGenerator(
+ new LocationTagged<string>(String.Empty, 9, 0, 9),
+ new LocationTagged<SpanCodeGenerator>(new ResolveUrlCodeGenerator(), 9, 0, 9))),
+ Factory.Markup("'").With(SpanCodeGenerator.Null)),
+ Factory.Markup(" />").Accepts(AcceptedCharacters.None)));
+ }
+
+ [Fact]
+ public void SimpleUrlInAttributeInMarkupDocument()
+ {
+ ParseDocumentTest("<a href='~/Foo/Bar/Baz' />",
+ new MarkupBlock(
+ Factory.Markup("<a"),
+ new MarkupBlock(new AttributeBlockCodeGenerator("href", new LocationTagged<string>(" href='", 2, 0, 2), new LocationTagged<string>("'", 22, 0, 22)),
+ Factory.Markup(" href='").With(SpanCodeGenerator.Null),
+ Factory.Markup("~/Foo/Bar/Baz")
+ .WithEditorHints(EditorHints.VirtualPath)
+ .With(new LiteralAttributeCodeGenerator(
+ new LocationTagged<string>(String.Empty, 9, 0, 9),
+ new LocationTagged<SpanCodeGenerator>(new ResolveUrlCodeGenerator(), 9, 0, 9))),
+ Factory.Markup("'").With(SpanCodeGenerator.Null)),
+ Factory.Markup(" />")));
+ }
+
+ [Fact]
+ public void SimpleUrlInAttributeInMarkupSection()
+ {
+ ParseDocumentTest("@section Foo { <a href='~/Foo/Bar/Baz' /> }",
+ new MarkupBlock(
+ Factory.EmptyHtml(),
+ new SectionBlock(new SectionCodeGenerator("Foo"),
+ Factory.CodeTransition(),
+ Factory.MetaCode("section Foo {")
+ .AutoCompleteWith(null, atEndOfSpan: true)
+ .Accepts(AcceptedCharacters.Any),
+ new MarkupBlock(
+ Factory.Markup(" <a"),
+ new MarkupBlock(new AttributeBlockCodeGenerator("href", new LocationTagged<string>(" href='", 17, 0, 17), new LocationTagged<string>("'", 37, 0, 37)),
+ Factory.Markup(" href='").With(SpanCodeGenerator.Null),
+ Factory.Markup("~/Foo/Bar/Baz")
+ .WithEditorHints(EditorHints.VirtualPath)
+ .With(new LiteralAttributeCodeGenerator(
+ new LocationTagged<string>(String.Empty, 24, 0, 24),
+ new LocationTagged<SpanCodeGenerator>(new ResolveUrlCodeGenerator(), 24, 0, 24))),
+ Factory.Markup("'").With(SpanCodeGenerator.Null)),
+ Factory.Markup(" /> ")),
+ Factory.MetaCode("}").Accepts(AcceptedCharacters.None)),
+ Factory.EmptyHtml()));
+ }
+
+ [Fact]
+ public void UrlWithExpressionsInAttributeInMarkupBlock()
+ {
+ ParseBlockTest("<a href='~/Foo/@id/Baz' />",
+ new MarkupBlock(
+ Factory.Markup("<a"),
+ new MarkupBlock(new AttributeBlockCodeGenerator("href", new LocationTagged<string>(" href='", 2, 0, 2), new LocationTagged<string>("'", 22, 0, 22)),
+ Factory.Markup(" href='").With(SpanCodeGenerator.Null),
+ Factory.Markup("~/Foo/")
+ .WithEditorHints(EditorHints.VirtualPath)
+ .With(new LiteralAttributeCodeGenerator(
+ new LocationTagged<string>(String.Empty, 9, 0, 9),
+ new LocationTagged<SpanCodeGenerator>(new ResolveUrlCodeGenerator(), 9, 0, 9))),
+ new MarkupBlock(new DynamicAttributeBlockCodeGenerator(new LocationTagged<string>(String.Empty, 15, 0, 15), 15, 0, 15),
+ new ExpressionBlock(
+ Factory.CodeTransition().Accepts(AcceptedCharacters.None),
+ Factory.Code("id")
+ .AsImplicitExpression(CSharpCodeParser.DefaultKeywords)
+ .Accepts(AcceptedCharacters.NonWhiteSpace))),
+ Factory.Markup("/Baz")
+ .With(new LiteralAttributeCodeGenerator(new LocationTagged<string>(String.Empty, 18, 0, 18), new LocationTagged<string>("/Baz", 18, 0, 18))),
+ Factory.Markup("'").With(SpanCodeGenerator.Null)),
+ Factory.Markup(" />").Accepts(AcceptedCharacters.None)));
+ }
+
+ [Fact]
+ public void UrlWithExpressionsInAttributeInMarkupDocument()
+ {
+ ParseDocumentTest("<a href='~/Foo/@id/Baz' />",
+ new MarkupBlock(
+ Factory.Markup("<a"),
+ new MarkupBlock(new AttributeBlockCodeGenerator("href", new LocationTagged<string>(" href='", 2, 0, 2), new LocationTagged<string>("'", 22, 0, 22)),
+ Factory.Markup(" href='").With(SpanCodeGenerator.Null),
+ Factory.Markup("~/Foo/")
+ .WithEditorHints(EditorHints.VirtualPath)
+ .With(new LiteralAttributeCodeGenerator(
+ new LocationTagged<string>(String.Empty, 9, 0, 9),
+ new LocationTagged<SpanCodeGenerator>(new ResolveUrlCodeGenerator(), 9, 0, 9))),
+ new MarkupBlock(new DynamicAttributeBlockCodeGenerator(new LocationTagged<string>(String.Empty, 15, 0, 15), 15, 0, 15),
+ new ExpressionBlock(
+ Factory.CodeTransition().Accepts(AcceptedCharacters.None),
+ Factory.Code("id")
+ .AsImplicitExpression(CSharpCodeParser.DefaultKeywords)
+ .Accepts(AcceptedCharacters.NonWhiteSpace))),
+ Factory.Markup("/Baz")
+ .With(new LiteralAttributeCodeGenerator(new LocationTagged<string>(String.Empty, 18, 0, 18), new LocationTagged<string>("/Baz", 18, 0, 18))),
+ Factory.Markup("'").With(SpanCodeGenerator.Null)),
+ Factory.Markup(" />")));
+ }
+
+ [Fact]
+ public void UrlWithExpressionsInAttributeInMarkupSection()
+ {
+ ParseDocumentTest("@section Foo { <a href='~/Foo/@id/Baz' /> }",
+ new MarkupBlock(
+ Factory.EmptyHtml(),
+ new SectionBlock(new SectionCodeGenerator("Foo"),
+ Factory.CodeTransition(),
+ Factory.MetaCode("section Foo {")
+ .AutoCompleteWith(null, atEndOfSpan: true),
+ new MarkupBlock(
+ Factory.Markup(" <a"),
+ new MarkupBlock(new AttributeBlockCodeGenerator("href", new LocationTagged<string>(" href='", 17, 0, 17), new LocationTagged<string>("'", 37, 0, 37)),
+ Factory.Markup(" href='").With(SpanCodeGenerator.Null),
+ Factory.Markup("~/Foo/")
+ .WithEditorHints(EditorHints.VirtualPath)
+ .With(new LiteralAttributeCodeGenerator(
+ new LocationTagged<string>(String.Empty, 24, 0, 24),
+ new LocationTagged<SpanCodeGenerator>(new ResolveUrlCodeGenerator(), 24, 0, 24))),
+ new MarkupBlock(new DynamicAttributeBlockCodeGenerator(new LocationTagged<string>(String.Empty, 30, 0, 30), 30, 0, 30),
+ new ExpressionBlock(
+ Factory.CodeTransition().Accepts(AcceptedCharacters.None),
+ Factory.Code("id")
+ .AsImplicitExpression(CSharpCodeParser.DefaultKeywords)
+ .Accepts(AcceptedCharacters.NonWhiteSpace))),
+ Factory.Markup("/Baz")
+ .With(new LiteralAttributeCodeGenerator(new LocationTagged<string>(String.Empty, 33, 0, 33), new LocationTagged<string>("/Baz", 33, 0, 33))),
+ Factory.Markup("'").With(SpanCodeGenerator.Null)),
+ Factory.Markup(" /> ")),
+ Factory.MetaCode("}").Accepts(AcceptedCharacters.None)),
+ Factory.EmptyHtml()));
+ }
+
+ [Fact]
+ public void UrlWithComplexCharactersInAttributeInMarkupBlock()
+ {
+ ParseBlockTest("<a href='~/Foo+Bar:Baz(Biz),Boz' />",
+ new MarkupBlock(
+ Factory.Markup("<a"),
+ new MarkupBlock(new AttributeBlockCodeGenerator("href", new LocationTagged<string>(" href='", 2, 0, 2), new LocationTagged<string>("'", 31, 0, 31)),
+ Factory.Markup(" href='").With(SpanCodeGenerator.Null),
+ Factory.Markup("~/Foo+Bar:Baz(Biz),Boz")
+ .WithEditorHints(EditorHints.VirtualPath)
+ .With(new LiteralAttributeCodeGenerator(
+ new LocationTagged<string>(String.Empty, 9, 0, 9),
+ new LocationTagged<SpanCodeGenerator>(new ResolveUrlCodeGenerator(), 9, 0, 9))),
+ Factory.Markup("'").With(SpanCodeGenerator.Null)),
+ Factory.Markup(" />").Accepts(AcceptedCharacters.None)));
+ }
+
+ [Fact]
+ public void UrlWithComplexCharactersInAttributeInMarkupDocument()
+ {
+ ParseDocumentTest("<a href='~/Foo+Bar:Baz(Biz),Boz' />",
+ new MarkupBlock(
+ Factory.Markup("<a"),
+ new MarkupBlock(new AttributeBlockCodeGenerator("href", new LocationTagged<string>(" href='", 2, 0, 2), new LocationTagged<string>("'", 31, 0, 31)),
+ Factory.Markup(" href='").With(SpanCodeGenerator.Null),
+ Factory.Markup("~/Foo+Bar:Baz(Biz),Boz")
+ .WithEditorHints(EditorHints.VirtualPath)
+ .With(new LiteralAttributeCodeGenerator(
+ new LocationTagged<string>(String.Empty, 9, 0, 9),
+ new LocationTagged<SpanCodeGenerator>(new ResolveUrlCodeGenerator(), 9, 0, 9))),
+ Factory.Markup("'").With(SpanCodeGenerator.Null)),
+ Factory.Markup(" />")));
+ }
+
+ [Fact]
+ public void UrlInUnquotedAttributeValueInMarkupBlock()
+ {
+ ParseBlockTest("<a href=~/Foo+Bar:Baz(Biz),Boz/@id/Boz />",
+ new MarkupBlock(
+ Factory.Markup("<a"),
+ new MarkupBlock(new AttributeBlockCodeGenerator("href", new LocationTagged<string>(" href=", 2, 0, 2), new LocationTagged<string>(String.Empty, 38, 0, 38)),
+ Factory.Markup(" href=").With(SpanCodeGenerator.Null),
+ Factory.Markup("~/Foo+Bar:Baz(Biz),Boz/")
+ .WithEditorHints(EditorHints.VirtualPath)
+ .With(new LiteralAttributeCodeGenerator(
+ new LocationTagged<string>(String.Empty, 8, 0, 8),
+ new LocationTagged<SpanCodeGenerator>(new ResolveUrlCodeGenerator(), 8, 0, 8))),
+ new MarkupBlock(new DynamicAttributeBlockCodeGenerator(new LocationTagged<string>(String.Empty, 31, 0, 31), 31, 0, 31),
+ new ExpressionBlock(
+ Factory.CodeTransition()
+ .Accepts(AcceptedCharacters.None),
+ Factory.Code("id")
+ .AsImplicitExpression(CSharpCodeParser.DefaultKeywords)
+ .Accepts(AcceptedCharacters.NonWhiteSpace))),
+ Factory.Markup("/Boz").With(new LiteralAttributeCodeGenerator(new LocationTagged<string>(String.Empty, 34, 0, 34), new LocationTagged<string>("/Boz", 34, 0, 34)))),
+ Factory.Markup(" />").Accepts(AcceptedCharacters.None)));
+ }
+
+ [Fact]
+ public void UrlInUnquotedAttributeValueInMarkupDocument()
+ {
+ ParseDocumentTest("<a href=~/Foo+Bar:Baz(Biz),Boz/@id/Boz />",
+ new MarkupBlock(
+ Factory.Markup("<a"),
+ new MarkupBlock(new AttributeBlockCodeGenerator("href", new LocationTagged<string>(" href=", 2, 0, 2), new LocationTagged<string>(String.Empty, 38, 0, 38)),
+ Factory.Markup(" href=").With(SpanCodeGenerator.Null),
+ Factory.Markup("~/Foo+Bar:Baz(Biz),Boz/")
+ .WithEditorHints(EditorHints.VirtualPath)
+ .With(new LiteralAttributeCodeGenerator(
+ new LocationTagged<string>(String.Empty, 8, 0, 8),
+ new LocationTagged<SpanCodeGenerator>(new ResolveUrlCodeGenerator(), 8, 0, 8))),
+ new MarkupBlock(new DynamicAttributeBlockCodeGenerator(new LocationTagged<string>(String.Empty, 31, 0, 31), 31, 0, 31),
+ new ExpressionBlock(
+ Factory.CodeTransition()
+ .Accepts(AcceptedCharacters.None),
+ Factory.Code("id")
+ .AsImplicitExpression(CSharpCodeParser.DefaultKeywords)
+ .Accepts(AcceptedCharacters.NonWhiteSpace))),
+ Factory.Markup("/Boz").With(new LiteralAttributeCodeGenerator(new LocationTagged<string>(String.Empty, 34, 0, 34), new LocationTagged<string>("/Boz", 34, 0, 34)))),
+ Factory.Markup(" />")));
+ }
+
+ [Fact]
+ public void UrlInUnquotedAttributeValueInMarkupSection()
+ {
+ ParseDocumentTest("@section Foo { <a href=~/Foo+Bar:Baz(Biz),Boz/@id/Boz /> }",
+ new MarkupBlock(
+ Factory.EmptyHtml(),
+ new SectionBlock(new SectionCodeGenerator("Foo"),
+ Factory.CodeTransition(),
+ Factory.MetaCode("section Foo {")
+ .AutoCompleteWith(null, atEndOfSpan: true),
+ new MarkupBlock(
+ Factory.Markup(" <a"),
+ new MarkupBlock(new AttributeBlockCodeGenerator("href", new LocationTagged<string>(" href=", 17, 0, 17), new LocationTagged<string>(String.Empty, 53, 0, 53)),
+ Factory.Markup(" href=").With(SpanCodeGenerator.Null),
+ Factory.Markup("~/Foo+Bar:Baz(Biz),Boz/")
+ .WithEditorHints(EditorHints.VirtualPath)
+ .With(new LiteralAttributeCodeGenerator(
+ new LocationTagged<string>(String.Empty, 23, 0, 23),
+ new LocationTagged<SpanCodeGenerator>(new ResolveUrlCodeGenerator(), 23, 0, 23))),
+ new MarkupBlock(new DynamicAttributeBlockCodeGenerator(new LocationTagged<string>(String.Empty, 46, 0, 46), 46, 0, 46),
+ new ExpressionBlock(
+ Factory.CodeTransition()
+ .Accepts(AcceptedCharacters.None),
+ Factory.Code("id")
+ .AsImplicitExpression(CSharpCodeParser.DefaultKeywords)
+ .Accepts(AcceptedCharacters.NonWhiteSpace))),
+ Factory.Markup("/Boz").With(new LiteralAttributeCodeGenerator(new LocationTagged<string>(String.Empty, 49, 0, 49), new LocationTagged<string>("/Boz", 49, 0, 49)))),
+ Factory.Markup(" /> ")),
+ Factory.MetaCode("}").Accepts(AcceptedCharacters.None)),
+ Factory.EmptyHtml()));
+ }
+ }
+}
diff --git a/test/System.Web.Razor.Test/Parser/Old/CsHtmlDocumentTest.cs b/test/System.Web.Razor.Test/Parser/Old/CsHtmlDocumentTest.cs
new file mode 100644
index 00000000..7480d2ba
--- /dev/null
+++ b/test/System.Web.Razor.Test/Parser/Old/CsHtmlDocumentTest.cs
@@ -0,0 +1,267 @@
+using System.Web.Razor.Parser;
+using System.Web.Razor.Resources;
+using System.Web.Razor.Test.Framework;
+using System.Web.Razor.Text;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using System.Web.Razor.Parser.SyntaxTree;
+
+namespace System.Web.Razor.Test.Parser.CSharp {
+ [TestClass]
+ public class CsHtmlDocumentTest : CsHtmlMarkupParserTestBase {
+ [TestMethod]
+ public void UnterminatedBlockCommentCausesRazorError() {
+ ParseDocumentTest(@"@* Foo Bar",
+ new MarkupBlock(
+ new MarkupSpan(String.Empty),
+ new CommentBlock(
+ new TransitionSpan(RazorParser.TransitionString, hidden: false, acceptedCharacters: AcceptedCharacters.None),
+ new MetaCodeSpan("*", hidden: false, acceptedCharacters: AcceptedCharacters.None),
+ new CommentSpan(" Foo Bar")
+ )
+ ),
+ new RazorError(RazorResources.ParseError_RazorComment_Not_Terminated, SourceLocation.Zero));
+ }
+
+ [TestMethod]
+ public void BlockCommentInMarkupDocumentIsHandledCorrectly() {
+ ParseDocumentTest(@"<ul>
+ @* This is a block comment </ul> *@ foo",
+ new MarkupBlock(
+ new MarkupSpan(@"<ul>
+ "),
+ new CommentBlock(
+ new TransitionSpan(RazorParser.TransitionString, hidden: false, acceptedCharacters: AcceptedCharacters.None),
+ new MetaCodeSpan("*", hidden: false, acceptedCharacters: AcceptedCharacters.None),
+ new CommentSpan(" This is a block comment </ul> "),
+ new MetaCodeSpan("*", hidden: false, acceptedCharacters: AcceptedCharacters.None),
+ new TransitionSpan(RazorParser.TransitionString, hidden: false, acceptedCharacters: AcceptedCharacters.None)
+ ),
+ new MarkupSpan(" foo")
+ ));
+ }
+
+ [TestMethod]
+ public void BlockCommentInMarkupBlockIsHandledCorrectly() {
+ ParseBlockTest(@"<ul>
+ @* This is a block comment </ul> *@ foo </ul>",
+ new MarkupBlock(
+ new MarkupSpan(@"<ul>
+ "),
+ new CommentBlock(
+ new TransitionSpan(RazorParser.TransitionString, hidden: false, acceptedCharacters: AcceptedCharacters.None),
+ new MetaCodeSpan("*", hidden: false, acceptedCharacters: AcceptedCharacters.None),
+ new CommentSpan(" This is a block comment </ul> "),
+ new MetaCodeSpan("*", hidden: false, acceptedCharacters: AcceptedCharacters.None),
+ new TransitionSpan(RazorParser.TransitionString, hidden: false, acceptedCharacters: AcceptedCharacters.None)
+ ),
+ new MarkupSpan(" foo </ul>", hidden: false, acceptedCharacters: AcceptedCharacters.None)
+ ));
+ }
+
+ [TestMethod]
+ public void BlockCommentAtStatementStartInCodeBlockIsHandledCorrectly() {
+ ParseDocumentTest(@"@if(Request.IsAuthenticated) {
+ @* User is logged in! } *@
+ Write(""Hello friend!"");
+}",
+ new MarkupBlock(
+ new StatementBlock(
+ new TransitionSpan(RazorParser.TransitionString, hidden: false, acceptedCharacters: AcceptedCharacters.None),
+ new CodeSpan(@"if(Request.IsAuthenticated) {
+ "),
+ new CommentBlock(
+ new TransitionSpan(RazorParser.TransitionString, hidden: false, acceptedCharacters: AcceptedCharacters.None),
+ new MetaCodeSpan("*", hidden: false, acceptedCharacters: AcceptedCharacters.None),
+ new CommentSpan(" User is logged in! } "),
+ new MetaCodeSpan("*", hidden: false, acceptedCharacters: AcceptedCharacters.None),
+ new TransitionSpan(RazorParser.TransitionString, hidden: false, acceptedCharacters: AcceptedCharacters.None)
+ ),
+ new CodeSpan(@"
+ Write(""Hello friend!"");
+}"))));
+ }
+
+ [TestMethod]
+ public void BlockCommentInStatementInCodeBlockIsHandledCorrectly() {
+ ParseDocumentTest(@"@if(Request.IsAuthenticated) {
+ var foo = @* User is logged in! ; *@;
+ Write(""Hello friend!"");
+}",
+ new MarkupBlock(
+ new StatementBlock(
+ new TransitionSpan(RazorParser.TransitionString, hidden: false, acceptedCharacters: AcceptedCharacters.None),
+ new CodeSpan(@"if(Request.IsAuthenticated) {
+ var foo = "),
+ new CommentBlock(
+ new TransitionSpan(RazorParser.TransitionString, hidden: false, acceptedCharacters: AcceptedCharacters.None),
+ new MetaCodeSpan("*", hidden: false, acceptedCharacters: AcceptedCharacters.None),
+ new CommentSpan(" User is logged in! ; "),
+ new MetaCodeSpan("*", hidden: false, acceptedCharacters: AcceptedCharacters.None),
+ new TransitionSpan(RazorParser.TransitionString, hidden: false, acceptedCharacters: AcceptedCharacters.None)
+ ),
+ new CodeSpan(@";
+ Write(""Hello friend!"");
+}"))));
+ }
+
+ [TestMethod]
+ public void BlockCommentInStringIsIgnored() {
+ ParseDocumentTest(@"@if(Request.IsAuthenticated) {
+ var foo = ""@* User is logged in! ; *@"";
+ Write(""Hello friend!"");
+}",
+ new MarkupBlock(
+ new StatementBlock(
+ new TransitionSpan(RazorParser.TransitionString, hidden: false, acceptedCharacters: AcceptedCharacters.None),
+ new CodeSpan(@"if(Request.IsAuthenticated) {
+ var foo = ""@* User is logged in! ; *@"";
+ Write(""Hello friend!"");
+}"))));
+ }
+
+ [TestMethod]
+ public void BlockCommentInCSharpBlockCommentIsIgnored() {
+ ParseDocumentTest(@"@if(Request.IsAuthenticated) {
+ var foo = /*@* User is logged in! */ *@ */;
+ Write(""Hello friend!"");
+}",
+ new MarkupBlock(
+ new StatementBlock(
+ new TransitionSpan(RazorParser.TransitionString, hidden: false, acceptedCharacters: AcceptedCharacters.None),
+ new CodeSpan(@"if(Request.IsAuthenticated) {
+ var foo = /*@* User is logged in! */ *@ */;
+ Write(""Hello friend!"");
+}"))));
+ }
+
+ [TestMethod]
+ public void BlockCommentInCSharpLineCommentIsIgnored() {
+ ParseDocumentTest(@"@if(Request.IsAuthenticated) {
+ var foo = //@* User is logged in! */ *@;
+ Write(""Hello friend!"");
+}",
+ new MarkupBlock(
+ new StatementBlock(
+ new TransitionSpan(RazorParser.TransitionString, hidden: false, acceptedCharacters: AcceptedCharacters.None),
+ new CodeSpan(@"if(Request.IsAuthenticated) {
+ var foo = //@* User is logged in! */ *@;
+ Write(""Hello friend!"");
+}"))));
+ }
+
+ [TestMethod]
+ public void BlockCommentInImplicitExpressionIsHandledCorrectly() {
+ ParseDocumentTest(@"@Html.Foo@*bar*@",
+ new MarkupBlock(
+ new ExpressionBlock(
+ new TransitionSpan(RazorParser.TransitionString, hidden: false, acceptedCharacters: AcceptedCharacters.None),
+ new ImplicitExpressionSpan(@"Html.Foo", CSharpCodeParser.DefaultKeywords, acceptTrailingDot: false, acceptedCharacters: AcceptedCharacters.NonWhiteSpace)
+ ),
+ new MarkupSpan(String.Empty),
+ new CommentBlock(
+ new TransitionSpan(RazorParser.TransitionString, hidden: false, acceptedCharacters: AcceptedCharacters.None),
+ new MetaCodeSpan("*", hidden: false, acceptedCharacters: AcceptedCharacters.None),
+ new CommentSpan("bar"),
+ new MetaCodeSpan("*", hidden: false, acceptedCharacters: AcceptedCharacters.None),
+ new TransitionSpan(RazorParser.TransitionString, hidden: false, acceptedCharacters: AcceptedCharacters.None)
+ ),
+ new MarkupSpan(String.Empty)));
+ }
+
+ [TestMethod]
+ public void BlockCommentAfterDotOfImplicitExpressionIsHandledCorrectly() {
+ ParseDocumentTest(@"@Html.@*bar*@",
+ new MarkupBlock(
+ new ExpressionBlock(
+ new TransitionSpan(RazorParser.TransitionString, hidden: false, acceptedCharacters: AcceptedCharacters.None),
+ new ImplicitExpressionSpan(@"Html", CSharpCodeParser.DefaultKeywords, acceptTrailingDot: false, acceptedCharacters: AcceptedCharacters.NonWhiteSpace)
+ ),
+ new MarkupSpan("."),
+ new CommentBlock(
+ new TransitionSpan(RazorParser.TransitionString, hidden: false, acceptedCharacters: AcceptedCharacters.None),
+ new MetaCodeSpan("*", hidden: false, acceptedCharacters: AcceptedCharacters.None),
+ new CommentSpan("bar"),
+ new MetaCodeSpan("*", hidden: false, acceptedCharacters: AcceptedCharacters.None),
+ new TransitionSpan(RazorParser.TransitionString, hidden: false, acceptedCharacters: AcceptedCharacters.None)
+ ),
+ new MarkupSpan(String.Empty)));
+ }
+
+ [TestMethod]
+ public void BlockCommentInParensOfImplicitExpressionIsHandledCorrectly() {
+ ParseDocumentTest(@"@Html.Foo(@*bar*@ 4)",
+ new MarkupBlock(
+ new ExpressionBlock(
+ new TransitionSpan(RazorParser.TransitionString, hidden: false, acceptedCharacters: AcceptedCharacters.None),
+ new ImplicitExpressionSpan(@"Html.Foo(", CSharpCodeParser.DefaultKeywords, acceptTrailingDot: false, acceptedCharacters: AcceptedCharacters.Any),
+ new CommentBlock(
+ new TransitionSpan(RazorParser.TransitionString, hidden: false, acceptedCharacters: AcceptedCharacters.None),
+ new MetaCodeSpan("*", hidden: false, acceptedCharacters: AcceptedCharacters.None),
+ new CommentSpan("bar"),
+ new MetaCodeSpan("*", hidden: false, acceptedCharacters: AcceptedCharacters.None),
+ new TransitionSpan(RazorParser.TransitionString, hidden: false, acceptedCharacters: AcceptedCharacters.None)
+ ),
+ new ImplicitExpressionSpan(" 4)", CSharpCodeParser.DefaultKeywords, acceptTrailingDot: false, acceptedCharacters: AcceptedCharacters.NonWhiteSpace)
+ ),
+ new MarkupSpan(String.Empty)));
+ }
+
+ [TestMethod]
+ public void BlockCommentInBracketsOfImplicitExpressionIsHandledCorrectly() {
+ ParseDocumentTest(@"@Html.Foo[@*bar*@ 4]",
+ new MarkupBlock(
+ new ExpressionBlock(
+ new TransitionSpan(RazorParser.TransitionString, hidden: false, acceptedCharacters: AcceptedCharacters.None),
+ new ImplicitExpressionSpan(@"Html.Foo[", CSharpCodeParser.DefaultKeywords, acceptTrailingDot: false, acceptedCharacters: AcceptedCharacters.Any),
+ new CommentBlock(
+ new TransitionSpan(RazorParser.TransitionString, hidden: false, acceptedCharacters: AcceptedCharacters.None),
+ new MetaCodeSpan("*", hidden: false, acceptedCharacters: AcceptedCharacters.None),
+ new CommentSpan("bar"),
+ new MetaCodeSpan("*", hidden: false, acceptedCharacters: AcceptedCharacters.None),
+ new TransitionSpan(RazorParser.TransitionString, hidden: false, acceptedCharacters: AcceptedCharacters.None)
+ ),
+ new ImplicitExpressionSpan(" 4]", CSharpCodeParser.DefaultKeywords, acceptTrailingDot: false, acceptedCharacters: AcceptedCharacters.NonWhiteSpace)
+ ),
+ new MarkupSpan(String.Empty)));
+ }
+
+ [TestMethod]
+ public void BlockCommentInParensOfConditionIsHandledCorrectly() {
+ ParseDocumentTest(@"@if(@*bar*@) {}",
+ new MarkupBlock(
+ new StatementBlock(
+ new TransitionSpan(RazorParser.TransitionString, hidden: false, acceptedCharacters: AcceptedCharacters.None),
+ new CodeSpan(@"if("),
+ new CommentBlock(
+ new TransitionSpan(RazorParser.TransitionString, hidden: false, acceptedCharacters: AcceptedCharacters.None),
+ new MetaCodeSpan("*", hidden: false, acceptedCharacters: AcceptedCharacters.None),
+ new CommentSpan("bar"),
+ new MetaCodeSpan("*", hidden: false, acceptedCharacters: AcceptedCharacters.None),
+ new TransitionSpan(RazorParser.TransitionString, hidden: false, acceptedCharacters: AcceptedCharacters.None)
+ ),
+ new CodeSpan(") {}")
+ )));
+ }
+
+ [TestMethod]
+ public void BlockCommentInExplicitExpressionIsHandledCorrectly() {
+ ParseDocumentTest(@"@(1 + @*bar*@ 1)",
+ new MarkupBlock(
+ new ExpressionBlock(
+ new TransitionSpan(RazorParser.TransitionString, hidden: false, acceptedCharacters: AcceptedCharacters.None),
+ new MetaCodeSpan("(", hidden: false, acceptedCharacters: AcceptedCharacters.None),
+ new CodeSpan(@"1 + "),
+ new CommentBlock(
+ new TransitionSpan(RazorParser.TransitionString, hidden: false, acceptedCharacters: AcceptedCharacters.None),
+ new MetaCodeSpan("*", hidden: false, acceptedCharacters: AcceptedCharacters.None),
+ new CommentSpan("bar"),
+ new MetaCodeSpan("*", hidden: false, acceptedCharacters: AcceptedCharacters.None),
+ new TransitionSpan(RazorParser.TransitionString, hidden: false, acceptedCharacters: AcceptedCharacters.None)
+ ),
+ new CodeSpan(" 1"),
+ new MetaCodeSpan(")", hidden: false, acceptedCharacters: AcceptedCharacters.None)
+ ),
+ new MarkupSpan(String.Empty)));
+ }
+ }
+}
diff --git a/test/System.Web.Razor.Test/Parser/ParserContextTest.cs b/test/System.Web.Razor.Test/Parser/ParserContextTest.cs
new file mode 100644
index 00000000..bd7e5ab6
--- /dev/null
+++ b/test/System.Web.Razor.Test/Parser/ParserContextTest.cs
@@ -0,0 +1,240 @@
+using System.IO;
+using System.Web.Razor.Parser;
+using System.Web.Razor.Parser.SyntaxTree;
+using System.Web.Razor.Resources;
+using System.Web.Razor.Test.Framework;
+using System.Web.Razor.Text;
+using System.Web.Razor.Tokenizer.Symbols;
+using Moq;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Razor.Test.Parser
+{
+ public class ParserContextTest
+ {
+ [Fact]
+ public void ConstructorRequiresNonNullSource()
+ {
+ var codeParser = new CSharpCodeParser();
+ Assert.ThrowsArgumentNull(() => new ParserContext(null, codeParser, new HtmlMarkupParser(), codeParser), "source");
+ }
+
+ [Fact]
+ public void ConstructorRequiresNonNullCodeParser()
+ {
+ var codeParser = new CSharpCodeParser();
+ Assert.ThrowsArgumentNull(() => new ParserContext(new SeekableTextReader(TextReader.Null), null, new HtmlMarkupParser(), codeParser), "codeParser");
+ }
+
+ [Fact]
+ public void ConstructorRequiresNonNullMarkupParser()
+ {
+ var codeParser = new CSharpCodeParser();
+ Assert.ThrowsArgumentNull(() => new ParserContext(new SeekableTextReader(TextReader.Null), codeParser, null, codeParser), "markupParser");
+ }
+
+ [Fact]
+ public void ConstructorRequiresNonNullActiveParser()
+ {
+ Assert.ThrowsArgumentNull(() => new ParserContext(new SeekableTextReader(TextReader.Null), new CSharpCodeParser(), new HtmlMarkupParser(), null), "activeParser");
+ }
+
+ [Fact]
+ public void ConstructorThrowsIfActiveParserIsNotCodeOrMarkupParser()
+ {
+ Assert.ThrowsArgument(() => new ParserContext(new SeekableTextReader(TextReader.Null), new CSharpCodeParser(), new HtmlMarkupParser(), new CSharpCodeParser()),
+ "activeParser",
+ RazorResources.ActiveParser_Must_Be_Code_Or_Markup_Parser);
+ }
+
+ [Fact]
+ public void ConstructorAcceptsActiveParserIfIsSameAsEitherCodeOrMarkupParser()
+ {
+ var codeParser = new CSharpCodeParser();
+ var markupParser = new HtmlMarkupParser();
+ new ParserContext(new SeekableTextReader(TextReader.Null), codeParser, markupParser, codeParser);
+ new ParserContext(new SeekableTextReader(TextReader.Null), codeParser, markupParser, markupParser);
+ }
+
+ [Fact]
+ public void ConstructorInitializesProperties()
+ {
+ // Arrange
+ SeekableTextReader expectedBuffer = new SeekableTextReader(TextReader.Null);
+ CSharpCodeParser expectedCodeParser = new CSharpCodeParser();
+ HtmlMarkupParser expectedMarkupParser = new HtmlMarkupParser();
+
+ // Act
+ ParserContext context = new ParserContext(expectedBuffer, expectedCodeParser, expectedMarkupParser, expectedCodeParser);
+
+ // Assert
+ Assert.NotNull(context.Source);
+ Assert.Same(expectedCodeParser, context.CodeParser);
+ Assert.Same(expectedMarkupParser, context.MarkupParser);
+ Assert.Same(expectedCodeParser, context.ActiveParser);
+ }
+
+ [Fact]
+ public void CurrentCharacterReturnsCurrentCharacterInTextBuffer()
+ {
+ // Arrange
+ ParserContext context = SetupTestContext("bar", b => b.Read());
+
+ // Act
+ char actual = context.CurrentCharacter;
+
+ // Assert
+ Assert.Equal('a', actual);
+ }
+
+ [Fact]
+ public void CurrentCharacterReturnsNulCharacterIfTextBufferAtEOF()
+ {
+ // Arrange
+ ParserContext context = SetupTestContext("bar", b => b.ReadToEnd());
+
+ // Act
+ char actual = context.CurrentCharacter;
+
+ // Assert
+ Assert.Equal('\0', actual);
+ }
+
+ [Fact]
+ public void EndOfFileReturnsFalseIfTextBufferNotAtEOF()
+ {
+ // Arrange
+ ParserContext context = SetupTestContext("bar");
+
+ // Act/Assert
+ Assert.False(context.EndOfFile);
+ }
+
+ [Fact]
+ public void EndOfFileReturnsTrueIfTextBufferAtEOF()
+ {
+ // Arrange
+ ParserContext context = SetupTestContext("bar", b => b.ReadToEnd());
+
+ // Act/Assert
+ Assert.True(context.EndOfFile);
+ }
+
+ [Fact]
+ public void StartBlockCreatesNewBlock()
+ {
+ // Arrange
+ ParserContext context = SetupTestContext("phoo");
+
+ // Act
+ context.StartBlock(BlockType.Expression);
+
+ // Assert
+ Assert.Equal(1, context.BlockStack.Count);
+ Assert.Equal(BlockType.Expression, context.BlockStack.Peek().Type);
+ }
+
+ [Fact]
+ public void EndBlockAddsCurrentBlockToParentBlock()
+ {
+ // Arrange
+ Mock<ParserVisitor> mockListener = new Mock<ParserVisitor>();
+ ParserContext context = SetupTestContext("phoo");
+
+ // Act
+ context.StartBlock(BlockType.Expression);
+ context.StartBlock(BlockType.Statement);
+ context.EndBlock();
+
+ // Assert
+ Assert.Equal(1, context.BlockStack.Count);
+ Assert.Equal(BlockType.Expression, context.BlockStack.Peek().Type);
+ Assert.Equal(1, context.BlockStack.Peek().Children.Count);
+ Assert.Equal(BlockType.Statement, ((Block)context.BlockStack.Peek().Children[0]).Type);
+ }
+
+ [Fact]
+ public void AddSpanAddsSpanToCurrentBlockBuilder()
+ {
+ // Arrange
+ var factory = SpanFactory.CreateCsHtml();
+ Mock<ParserVisitor> mockListener = new Mock<ParserVisitor>();
+ ParserContext context = SetupTestContext("phoo");
+
+ SpanBuilder builder = new SpanBuilder()
+ {
+ Kind = SpanKind.Code
+ };
+ builder.Accept(new CSharpSymbol(1, 0, 1, "foo", CSharpSymbolType.Identifier));
+ Span added = builder.Build();
+
+ using (context.StartBlock(BlockType.Functions))
+ {
+ context.AddSpan(added);
+ }
+
+ BlockBuilder expected = new BlockBuilder()
+ {
+ Type = BlockType.Functions,
+ };
+ expected.Children.Add(added);
+
+ // Assert
+ ParserTestBase.EvaluateResults(context.CompleteParse(), expected.Build());
+ }
+
+ [Fact]
+ public void SwitchActiveParserSetsMarkupParserAsActiveIfCodeParserCurrentlyActive()
+ {
+ // Arrange
+ var codeParser = new CSharpCodeParser();
+ var markupParser = new HtmlMarkupParser();
+ ParserContext context = SetupTestContext("barbazbiz", b => b.Read(), codeParser, markupParser, codeParser);
+ Assert.Same(codeParser, context.ActiveParser);
+
+ // Act
+ context.SwitchActiveParser();
+
+ // Assert
+ Assert.Same(markupParser, context.ActiveParser);
+ }
+
+ [Fact]
+ public void SwitchActiveParserSetsCodeParserAsActiveIfMarkupParserCurrentlyActive()
+ {
+ // Arrange
+ var codeParser = new CSharpCodeParser();
+ var markupParser = new HtmlMarkupParser();
+ ParserContext context = SetupTestContext("barbazbiz", b => b.Read(), codeParser, markupParser, markupParser);
+ Assert.Same(markupParser, context.ActiveParser);
+
+ // Act
+ context.SwitchActiveParser();
+
+ // Assert
+ Assert.Same(codeParser, context.ActiveParser);
+ }
+
+ private ParserContext SetupTestContext(string document)
+ {
+ var codeParser = new CSharpCodeParser();
+ var markupParser = new HtmlMarkupParser();
+ return SetupTestContext(document, b => { }, codeParser, markupParser, codeParser);
+ }
+
+ private ParserContext SetupTestContext(string document, Action<TextReader> positioningAction)
+ {
+ var codeParser = new CSharpCodeParser();
+ var markupParser = new HtmlMarkupParser();
+ return SetupTestContext(document, positioningAction, codeParser, markupParser, codeParser);
+ }
+
+ private ParserContext SetupTestContext(string document, Action<TextReader> positioningAction, ParserBase codeParser, ParserBase markupParser, ParserBase activeParser)
+ {
+ ParserContext context = new ParserContext(new SeekableTextReader(new StringReader(document)), codeParser, markupParser, activeParser);
+ positioningAction(context.Source);
+ return context;
+ }
+ }
+}
diff --git a/test/System.Web.Razor.Test/Parser/ParserVisitorExtensionsTest.cs b/test/System.Web.Razor.Test/Parser/ParserVisitorExtensionsTest.cs
new file mode 100644
index 00000000..903354ec
--- /dev/null
+++ b/test/System.Web.Razor.Test/Parser/ParserVisitorExtensionsTest.cs
@@ -0,0 +1,82 @@
+using System.Collections.Generic;
+using System.Web.Razor.Parser;
+using System.Web.Razor.Parser.SyntaxTree;
+using Moq;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Razor.Test.Parser
+{
+ public class ParserVisitorExtensionsTest
+ {
+ [Fact]
+ public void VisitThrowsOnNullVisitor()
+ {
+ ParserVisitor target = null;
+ ParserResults results = new ParserResults(new BlockBuilder() { Type = BlockType.Comment }.Build(), new List<RazorError>());
+
+ Assert.ThrowsArgumentNull(() => target.Visit(results), "self");
+ }
+
+ [Fact]
+ public void VisitThrowsOnNullResults()
+ {
+ ParserVisitor target = new Mock<ParserVisitor>().Object;
+ Assert.ThrowsArgumentNull(() => target.Visit(null), "result");
+ }
+
+ [Fact]
+ public void VisitSendsDocumentToVisitor()
+ {
+ // Arrange
+ Mock<ParserVisitor> targetMock = new Mock<ParserVisitor>();
+ Block root = new BlockBuilder() { Type = BlockType.Comment }.Build();
+ ParserResults results = new ParserResults(root, new List<RazorError>());
+
+ // Act
+ targetMock.Object.Visit(results);
+
+ // Assert
+ targetMock.Verify(v => v.VisitBlock(root));
+ }
+
+ [Fact]
+ public void VisitSendsErrorsToVisitor()
+ {
+ // Arrange
+ Mock<ParserVisitor> targetMock = new Mock<ParserVisitor>();
+ Block root = new BlockBuilder() { Type = BlockType.Comment }.Build();
+ List<RazorError> errors = new List<RazorError>() {
+ new RazorError("Foo", 1, 0, 1),
+ new RazorError("Bar", 2, 0, 2)
+ };
+ ParserResults results = new ParserResults(root, errors);
+
+ // Act
+ targetMock.Object.Visit(results);
+
+ // Assert
+ targetMock.Verify(v => v.VisitError(errors[0]));
+ targetMock.Verify(v => v.VisitError(errors[1]));
+ }
+
+ [Fact]
+ public void VisitCallsOnCompleteWhenAllNodesHaveBeenVisited()
+ {
+ // Arrange
+ Mock<ParserVisitor> targetMock = new Mock<ParserVisitor>();
+ Block root = new BlockBuilder() { Type = BlockType.Comment }.Build();
+ List<RazorError> errors = new List<RazorError>() {
+ new RazorError("Foo", 1, 0, 1),
+ new RazorError("Bar", 2, 0, 2)
+ };
+ ParserResults results = new ParserResults(root, errors);
+
+ // Act
+ targetMock.Object.Visit(results);
+
+ // Assert
+ targetMock.Verify(v => v.OnComplete());
+ }
+ }
+}
diff --git a/test/System.Web.Razor.Test/Parser/PartialParsing/CSharpPartialParsingTest.cs b/test/System.Web.Razor.Test/Parser/PartialParsing/CSharpPartialParsingTest.cs
new file mode 100644
index 00000000..b57dbd19
--- /dev/null
+++ b/test/System.Web.Razor.Test/Parser/PartialParsing/CSharpPartialParsingTest.cs
@@ -0,0 +1,400 @@
+using System.Web.Razor.Parser;
+using System.Web.Razor.Parser.SyntaxTree;
+using System.Web.Razor.Test.Framework;
+using System.Web.Razor.Text;
+using System.Web.WebPages.TestUtils;
+using Xunit;
+
+namespace System.Web.Razor.Test.Parser.PartialParsing
+{
+ public class CSharpPartialParsingTest : PartialParsingTestBase<CSharpRazorCodeLanguage>
+ {
+ [Fact]
+ public void ImplicitExpressionProvisionallyAcceptsDeleteOfIdentifierPartsIfDotRemains()
+ {
+ var factory = SpanFactory.CreateCsHtml();
+ StringTextBuffer changed = new StringTextBuffer("foo @User. baz");
+ StringTextBuffer old = new StringTextBuffer("foo @User.Name baz");
+ RunPartialParseTest(new TextChange(10, 4, old, 0, changed),
+ new MarkupBlock(
+ factory.Markup("foo "),
+ new ExpressionBlock(
+ factory.CodeTransition(),
+ factory.Code("User.").AsImplicitExpression(CSharpCodeParser.DefaultKeywords).Accepts(AcceptedCharacters.NonWhiteSpace)),
+ factory.Markup(" baz")),
+ additionalFlags: PartialParseResult.Provisional);
+ }
+
+ [Fact]
+ public void ImplicitExpressionAcceptsDeleteOfIdentifierPartsIfSomeOfIdentifierRemains()
+ {
+ var factory = SpanFactory.CreateCsHtml();
+ StringTextBuffer changed = new StringTextBuffer("foo @Us baz");
+ StringTextBuffer old = new StringTextBuffer("foo @User baz");
+ RunPartialParseTest(new TextChange(7, 2, old, 0, changed),
+ new MarkupBlock(
+ factory.Markup("foo "),
+ new ExpressionBlock(
+ factory.CodeTransition(),
+ factory.Code("Us").AsImplicitExpression(CSharpCodeParser.DefaultKeywords).Accepts(AcceptedCharacters.NonWhiteSpace)),
+ factory.Markup(" baz")));
+ }
+
+ [Fact]
+ public void ImplicitExpressionProvisionallyAcceptsMultipleInsertionIfItCausesIdentifierExpansionAndTrailingDot()
+ {
+ var factory = SpanFactory.CreateCsHtml();
+ StringTextBuffer changed = new StringTextBuffer("foo @User. baz");
+ StringTextBuffer old = new StringTextBuffer("foo @U baz");
+ RunPartialParseTest(new TextChange(6, 0, old, 4, changed),
+ new MarkupBlock(
+ factory.Markup("foo "),
+ new ExpressionBlock(
+ factory.CodeTransition(),
+ factory.Code("User.").AsImplicitExpression(CSharpCodeParser.DefaultKeywords).Accepts(AcceptedCharacters.NonWhiteSpace)),
+ factory.Markup(" baz")),
+ additionalFlags: PartialParseResult.Provisional);
+ }
+
+ [Fact]
+ public void ImplicitExpressionAcceptsMultipleInsertionIfItOnlyCausesIdentifierExpansion()
+ {
+ var factory = SpanFactory.CreateCsHtml();
+ StringTextBuffer changed = new StringTextBuffer("foo @barbiz baz");
+ StringTextBuffer old = new StringTextBuffer("foo @bar baz");
+ RunPartialParseTest(new TextChange(8, 0, old, 3, changed),
+ new MarkupBlock(
+ factory.Markup("foo "),
+ new ExpressionBlock(
+ factory.CodeTransition(),
+ factory.Code("barbiz").AsImplicitExpression(CSharpCodeParser.DefaultKeywords).Accepts(AcceptedCharacters.NonWhiteSpace)),
+ factory.Markup(" baz")));
+ }
+
+ [Fact]
+ public void ImplicitExpressionAcceptsIdentifierExpansionAtEndOfNonWhitespaceCharacters()
+ {
+ var factory = SpanFactory.CreateCsHtml();
+ StringTextBuffer changed = new StringTextBuffer(@"@{
+ @food
+}");
+ StringTextBuffer old = new StringTextBuffer(@"@{
+ @foo
+}");
+ RunPartialParseTest(new TextChange(12, 0, old, 1, changed),
+ new MarkupBlock(
+ factory.EmptyHtml(),
+ new StatementBlock(
+ factory.CodeTransition(),
+ factory.MetaCode("{").Accepts(AcceptedCharacters.None),
+ factory.Code("\r\n ").AsStatement(),
+ new ExpressionBlock(
+ factory.CodeTransition(),
+ factory.Code("food")
+ .AsImplicitExpression(CSharpCodeParser.DefaultKeywords, acceptTrailingDot: true)
+ .Accepts(AcceptedCharacters.NonWhiteSpace)),
+ factory.Code("\r\n").AsStatement(),
+ factory.MetaCode("}").Accepts(AcceptedCharacters.None)),
+ factory.EmptyHtml()));
+ }
+
+ [Fact]
+ public void ImplicitExpressionAcceptsIdentifierAfterDotAtEndOfNonWhitespaceCharacters()
+ {
+ var factory = SpanFactory.CreateCsHtml();
+ StringTextBuffer changed = new StringTextBuffer(@"@{
+ @foo.d
+}");
+ StringTextBuffer old = new StringTextBuffer(@"@{
+ @foo.
+}");
+ RunPartialParseTest(new TextChange(13, 0, old, 1, changed),
+ new MarkupBlock(
+ factory.EmptyHtml(),
+ new StatementBlock(
+ factory.CodeTransition(),
+ factory.MetaCode("{").Accepts(AcceptedCharacters.None),
+ factory.Code("\r\n ").AsStatement(),
+ new ExpressionBlock(
+ factory.CodeTransition(),
+ factory.Code("foo.d")
+ .AsImplicitExpression(CSharpCodeParser.DefaultKeywords, acceptTrailingDot: true)
+ .Accepts(AcceptedCharacters.NonWhiteSpace)),
+ factory.Code("\r\n").AsStatement(),
+ factory.MetaCode("}").Accepts(AcceptedCharacters.None)),
+ factory.EmptyHtml()));
+ }
+
+ [Fact]
+ public void ImplicitExpressionAcceptsDotAtEndOfNonWhitespaceCharacters()
+ {
+ var factory = SpanFactory.CreateCsHtml();
+ StringTextBuffer changed = new StringTextBuffer(@"@{
+ @foo.
+}");
+ StringTextBuffer old = new StringTextBuffer(@"@{
+ @foo
+}");
+ RunPartialParseTest(new TextChange(12, 0, old, 1, changed),
+ new MarkupBlock(
+ factory.EmptyHtml(),
+ new StatementBlock(
+ factory.CodeTransition(),
+ factory.MetaCode("{").Accepts(AcceptedCharacters.None),
+ factory.Code("\r\n ").AsStatement(),
+ new ExpressionBlock(
+ factory.CodeTransition(),
+ factory.Code(@"foo.")
+ .AsImplicitExpression(CSharpCodeParser.DefaultKeywords, acceptTrailingDot: true)
+ .Accepts(AcceptedCharacters.NonWhiteSpace)),
+ factory.Code("\r\n").AsStatement(),
+ factory.MetaCode("}").Accepts(AcceptedCharacters.None)),
+ factory.EmptyHtml()));
+ }
+
+ [Fact]
+ public void ImplicitExpressionRejectsChangeWhichWouldHaveBeenAcceptedIfLastChangeWasProvisionallyAcceptedOnDifferentSpan()
+ {
+ var factory = SpanFactory.CreateCsHtml();
+
+ // Arrange
+ TextChange dotTyped = new TextChange(8, 0, new StringTextBuffer("foo @foo @bar"), 1, new StringTextBuffer("foo @foo. @bar"));
+ TextChange charTyped = new TextChange(14, 0, new StringTextBuffer("foo @foo. @bar"), 1, new StringTextBuffer("foo @foo. @barb"));
+ TestParserManager manager = CreateParserManager();
+ manager.InitializeWithDocument(dotTyped.OldBuffer);
+
+ // Apply the dot change
+ Assert.Equal(PartialParseResult.Provisional | PartialParseResult.Accepted, manager.CheckForStructureChangesAndWait(dotTyped));
+
+ // Act (apply the identifier start char change)
+ PartialParseResult result = manager.CheckForStructureChangesAndWait(charTyped);
+
+ // Assert
+ Assert.Equal(PartialParseResult.Rejected, result);
+ Assert.False(manager.Parser.LastResultProvisional, "LastResultProvisional flag should have been cleared but it was not");
+ ParserTestBase.EvaluateParseTree(manager.Parser.CurrentParseTree,
+ new MarkupBlock(
+ factory.Markup("foo "),
+ new ExpressionBlock(
+ factory.CodeTransition(),
+ factory.Code("foo")
+ .AsImplicitExpression(CSharpCodeParser.DefaultKeywords)
+ .Accepts(AcceptedCharacters.NonWhiteSpace)),
+ factory.Markup(". "),
+ new ExpressionBlock(
+ factory.CodeTransition(),
+ factory.Code("barb")
+ .AsImplicitExpression(CSharpCodeParser.DefaultKeywords)
+ .Accepts(AcceptedCharacters.NonWhiteSpace)),
+ factory.EmptyHtml()));
+ }
+
+ [Fact]
+ public void ImplicitExpressionAcceptsIdentifierTypedAfterDotIfLastChangeWasProvisionalAcceptanceOfDot()
+ {
+ var factory = SpanFactory.CreateCsHtml();
+
+ // Arrange
+ TextChange dotTyped = new TextChange(8, 0, new StringTextBuffer("foo @foo bar"), 1, new StringTextBuffer("foo @foo. bar"));
+ TextChange charTyped = new TextChange(9, 0, new StringTextBuffer("foo @foo. bar"), 1, new StringTextBuffer("foo @foo.b bar"));
+ TestParserManager manager = CreateParserManager();
+ manager.InitializeWithDocument(dotTyped.OldBuffer);
+
+ // Apply the dot change
+ Assert.Equal(PartialParseResult.Provisional | PartialParseResult.Accepted, manager.CheckForStructureChangesAndWait(dotTyped));
+
+ // Act (apply the identifier start char change)
+ PartialParseResult result = manager.CheckForStructureChangesAndWait(charTyped);
+
+ // Assert
+ Assert.Equal(PartialParseResult.Accepted, result);
+ Assert.False(manager.Parser.LastResultProvisional, "LastResultProvisional flag should have been cleared but it was not");
+ ParserTestBase.EvaluateParseTree(manager.Parser.CurrentParseTree,
+ new MarkupBlock(
+ factory.Markup("foo "),
+ new ExpressionBlock(
+ factory.CodeTransition(),
+ factory.Code("foo.b")
+ .AsImplicitExpression(CSharpCodeParser.DefaultKeywords)
+ .Accepts(AcceptedCharacters.NonWhiteSpace)),
+ factory.Markup(" bar")));
+ }
+
+ [Fact]
+ public void ImplicitExpressionProvisionallyAcceptsDotAfterIdentifierInMarkup()
+ {
+ var factory = SpanFactory.CreateCsHtml();
+ StringTextBuffer changed = new StringTextBuffer("foo @foo. bar");
+ StringTextBuffer old = new StringTextBuffer("foo @foo bar");
+ RunPartialParseTest(new TextChange(8, 0, old, 1, changed),
+ new MarkupBlock(
+ factory.Markup("foo "),
+ new ExpressionBlock(
+ factory.CodeTransition(),
+ factory.Code("foo.")
+ .AsImplicitExpression(CSharpCodeParser.DefaultKeywords)
+ .Accepts(AcceptedCharacters.NonWhiteSpace)),
+ factory.Markup(" bar")),
+ additionalFlags: PartialParseResult.Provisional);
+ }
+
+ [Fact]
+ public void ImplicitExpressionAcceptsAdditionalIdentifierCharactersIfEndOfSpanIsIdentifier()
+ {
+ var factory = SpanFactory.CreateCsHtml();
+ StringTextBuffer changed = new StringTextBuffer("foo @foob bar");
+ StringTextBuffer old = new StringTextBuffer("foo @foo bar");
+ RunPartialParseTest(new TextChange(8, 0, old, 1, changed),
+ new MarkupBlock(
+ factory.Markup("foo "),
+ new ExpressionBlock(
+ factory.CodeTransition(),
+ factory.Code("foob")
+ .AsImplicitExpression(CSharpCodeParser.DefaultKeywords)
+ .Accepts(AcceptedCharacters.NonWhiteSpace)),
+ factory.Markup(" bar")));
+ }
+
+ [Fact]
+ public void ImplicitExpressionAcceptsAdditionalIdentifierStartCharactersIfEndOfSpanIsDot()
+ {
+ var factory = SpanFactory.CreateCsHtml();
+ StringTextBuffer changed = new StringTextBuffer("@{@foo.b}");
+ StringTextBuffer old = new StringTextBuffer("@{@foo.}");
+ RunPartialParseTest(new TextChange(7, 0, old, 1, changed),
+ new MarkupBlock(
+ factory.EmptyHtml(),
+ new StatementBlock(
+ factory.CodeTransition(),
+ factory.MetaCode("{").Accepts(AcceptedCharacters.None),
+ factory.EmptyCSharp().AsStatement(),
+ new ExpressionBlock(
+ factory.CodeTransition(),
+ factory.Code("foo.b")
+ .AsImplicitExpression(CSharpCodeParser.DefaultKeywords, acceptTrailingDot: true)
+ .Accepts(AcceptedCharacters.NonWhiteSpace)),
+ factory.EmptyCSharp().AsStatement(),
+ factory.MetaCode("}").Accepts(AcceptedCharacters.None)),
+ factory.EmptyHtml()));
+ }
+
+ [Fact]
+ public void ImplicitExpressionAcceptsDotIfTrailingDotsAreAllowed()
+ {
+ var factory = SpanFactory.CreateCsHtml();
+ StringTextBuffer changed = new StringTextBuffer("@{@foo.}");
+ StringTextBuffer old = new StringTextBuffer("@{@foo}");
+ RunPartialParseTest(new TextChange(6, 0, old, 1, changed),
+ new MarkupBlock(
+ factory.EmptyHtml(),
+ new StatementBlock(
+ factory.CodeTransition(),
+ factory.MetaCode("{").Accepts(AcceptedCharacters.None),
+ factory.EmptyCSharp().AsStatement(),
+ new ExpressionBlock(
+ factory.CodeTransition(),
+ factory.Code("foo.")
+ .AsImplicitExpression(CSharpCodeParser.DefaultKeywords, acceptTrailingDot: true)
+ .Accepts(AcceptedCharacters.NonWhiteSpace)),
+ factory.EmptyCSharp().AsStatement(),
+ factory.MetaCode("}").Accepts(AcceptedCharacters.None)),
+ factory.EmptyHtml()));
+ }
+
+ [Fact]
+ public void ImplicitExpressionCorrectlyTriggersReparseIfIfKeywordTyped()
+ {
+ RunTypeKeywordTest("if");
+ }
+
+ [Fact]
+ public void ImplicitExpressionCorrectlyTriggersReparseIfDoKeywordTyped()
+ {
+ RunTypeKeywordTest("do");
+ }
+
+ [Fact]
+ public void ImplicitExpressionCorrectlyTriggersReparseIfTryKeywordTyped()
+ {
+ RunTypeKeywordTest("try");
+ }
+
+ [Fact]
+ public void ImplicitExpressionCorrectlyTriggersReparseIfForKeywordTyped()
+ {
+ RunTypeKeywordTest("for");
+ }
+
+ [Fact]
+ public void ImplicitExpressionCorrectlyTriggersReparseIfForEachKeywordTyped()
+ {
+ RunTypeKeywordTest("foreach");
+ }
+
+ [Fact]
+ public void ImplicitExpressionCorrectlyTriggersReparseIfWhileKeywordTyped()
+ {
+ RunTypeKeywordTest("while");
+ }
+
+ [Fact]
+ public void ImplicitExpressionCorrectlyTriggersReparseIfSwitchKeywordTyped()
+ {
+ RunTypeKeywordTest("switch");
+ }
+
+ [Fact]
+ public void ImplicitExpressionCorrectlyTriggersReparseIfLockKeywordTyped()
+ {
+ RunTypeKeywordTest("lock");
+ }
+
+ [Fact]
+ public void ImplicitExpressionCorrectlyTriggersReparseIfUsingKeywordTyped()
+ {
+ RunTypeKeywordTest("using");
+ }
+
+ [Fact]
+ public void ImplicitExpressionCorrectlyTriggersReparseIfSectionKeywordTyped()
+ {
+ RunTypeKeywordTest("section");
+ }
+
+ [Fact]
+ public void ImplicitExpressionCorrectlyTriggersReparseIfInheritsKeywordTyped()
+ {
+ RunTypeKeywordTest("inherits");
+ }
+
+ [Fact]
+ public void ImplicitExpressionCorrectlyTriggersReparseIfHelperKeywordTyped()
+ {
+ RunTypeKeywordTest("helper");
+ }
+
+ [Fact]
+ public void ImplicitExpressionCorrectlyTriggersReparseIfFunctionsKeywordTyped()
+ {
+ RunTypeKeywordTest("functions");
+ }
+
+ [Fact]
+ public void ImplicitExpressionCorrectlyTriggersReparseIfNamespaceKeywordTyped()
+ {
+ RunTypeKeywordTest("namespace");
+ }
+
+ [Fact]
+ public void ImplicitExpressionCorrectlyTriggersReparseIfClassKeywordTyped()
+ {
+ RunTypeKeywordTest("class");
+ }
+
+ [Fact]
+ public void ImplicitExpressionCorrectlyTriggersReparseIfLayoutKeywordTyped()
+ {
+ RunTypeKeywordTest("layout");
+ }
+ }
+}
diff --git a/test/System.Web.Razor.Test/Parser/PartialParsing/PartialParsingTestBase.cs b/test/System.Web.Razor.Test/Parser/PartialParsing/PartialParsingTestBase.cs
new file mode 100644
index 00000000..34b63736
--- /dev/null
+++ b/test/System.Web.Razor.Test/Parser/PartialParsing/PartialParsingTestBase.cs
@@ -0,0 +1,111 @@
+using System.Threading;
+using System.Web.Razor.Generator;
+using System.Web.Razor.Parser.SyntaxTree;
+using System.Web.Razor.Test.Framework;
+using System.Web.Razor.Test.Utils;
+using System.Web.Razor.Text;
+using System.Web.WebPages.TestUtils;
+using Xunit;
+
+namespace System.Web.Razor.Test.Parser.PartialParsing
+{
+ public abstract class PartialParsingTestBase<TLanguage>
+ where TLanguage : RazorCodeLanguage, new()
+ {
+ private const string TestLinePragmaFileName = "C:\\This\\Path\\Is\\Just\\For\\Line\\Pragmas.cshtml";
+
+ protected static void RunFullReparseTest(TextChange change, PartialParseResult additionalFlags = (PartialParseResult)0)
+ {
+ // Arrange
+ TestParserManager manager = CreateParserManager();
+ manager.InitializeWithDocument(change.OldBuffer);
+
+ // Act
+ PartialParseResult result = manager.CheckForStructureChangesAndWait(change);
+
+ // Assert
+ Assert.Equal(PartialParseResult.Rejected | additionalFlags, result);
+ Assert.Equal(2, manager.ParseCount);
+ }
+
+ protected static void RunPartialParseTest(TextChange change, Block newTreeRoot, PartialParseResult additionalFlags = (PartialParseResult)0)
+ {
+ // Arrange
+ TestParserManager manager = CreateParserManager();
+ manager.InitializeWithDocument(change.OldBuffer);
+
+ // Act
+ PartialParseResult result = manager.CheckForStructureChangesAndWait(change);
+
+ // Assert
+ Assert.Equal(PartialParseResult.Accepted | additionalFlags, result);
+ Assert.Equal(1, manager.ParseCount);
+ ParserTestBase.EvaluateParseTree(manager.Parser.CurrentParseTree, newTreeRoot);
+ }
+
+ protected static TestParserManager CreateParserManager()
+ {
+ RazorEngineHost host = CreateHost();
+ RazorEditorParser parser = new RazorEditorParser(host, TestLinePragmaFileName);
+ return new TestParserManager(parser);
+ }
+
+ protected static RazorEngineHost CreateHost()
+ {
+ return new RazorEngineHost(new TLanguage())
+ {
+ GeneratedClassContext = new GeneratedClassContext("Execute", "Write", "WriteLiteral", "WriteTo", "WriteLiteralTo", "Template", "DefineSection"),
+ DesignTimeMode = true
+ };
+ }
+
+ protected static void RunTypeKeywordTest(string keyword)
+ {
+ string before = "@" + keyword.Substring(0, keyword.Length - 1);
+ string after = "@" + keyword;
+ StringTextBuffer changed = new StringTextBuffer(after);
+ StringTextBuffer old = new StringTextBuffer(before);
+ RunFullReparseTest(new TextChange(keyword.Length, 0, old, 1, changed), additionalFlags: PartialParseResult.SpanContextChanged);
+ }
+
+ protected class TestParserManager
+ {
+ public RazorEditorParser Parser;
+ public ManualResetEventSlim ParserComplete;
+ public int ParseCount;
+
+ public TestParserManager(RazorEditorParser parser)
+ {
+ ParserComplete = new ManualResetEventSlim();
+ ParseCount = 0;
+ Parser = parser;
+ parser.DocumentParseComplete += (sender, args) =>
+ {
+ Interlocked.Increment(ref ParseCount);
+ ParserComplete.Set();
+ };
+ }
+
+ public void InitializeWithDocument(ITextBuffer startDocument)
+ {
+ CheckForStructureChangesAndWait(new TextChange(0, 0, new StringTextBuffer(String.Empty), startDocument.Length, startDocument));
+ }
+
+ public PartialParseResult CheckForStructureChangesAndWait(TextChange change)
+ {
+ PartialParseResult result = Parser.CheckForStructureChanges(change);
+ if (result.HasFlag(PartialParseResult.Rejected))
+ {
+ WaitForParse();
+ }
+ return result;
+ }
+
+ public void WaitForParse()
+ {
+ MiscUtils.DoWithTimeoutIfNotDebugging(ParserComplete.Wait); // Wait for the parse to finish
+ ParserComplete.Reset();
+ }
+ }
+ }
+}
diff --git a/test/System.Web.Razor.Test/Parser/PartialParsing/VBPartialParsingTest.cs b/test/System.Web.Razor.Test/Parser/PartialParsing/VBPartialParsingTest.cs
new file mode 100644
index 00000000..7cf7738d
--- /dev/null
+++ b/test/System.Web.Razor.Test/Parser/PartialParsing/VBPartialParsingTest.cs
@@ -0,0 +1,372 @@
+using System.Web.Razor.Parser;
+using System.Web.Razor.Parser.SyntaxTree;
+using System.Web.Razor.Test.Framework;
+using System.Web.Razor.Text;
+using System.Web.WebPages.TestUtils;
+using Xunit;
+
+namespace System.Web.Razor.Test.Parser.PartialParsing
+{
+ public class VBPartialParsingTest : PartialParsingTestBase<VBRazorCodeLanguage>
+ {
+ [Fact]
+ public void ImplicitExpressionProvisionallyAcceptsDeleteOfIdentifierPartsIfDotRemains()
+ {
+ var factory = SpanFactory.CreateVbHtml();
+ StringTextBuffer changed = new StringTextBuffer("foo @User. baz");
+ StringTextBuffer old = new StringTextBuffer("foo @User.Name baz");
+ RunPartialParseTest(new TextChange(10, 4, old, 0, changed),
+ new MarkupBlock(
+ factory.Markup("foo "),
+ new ExpressionBlock(
+ factory.CodeTransition(),
+ factory.Code("User.")
+ .AsImplicitExpression(VBCodeParser.DefaultKeywords)
+ .Accepts(AcceptedCharacters.NonWhiteSpace)),
+ factory.Markup(" baz")),
+ additionalFlags: PartialParseResult.Provisional);
+ }
+
+ [Fact]
+ public void ImplicitExpressionAcceptsDeleteOfIdentifierPartsIfSomeOfIdentifierRemains()
+ {
+ var factory = SpanFactory.CreateVbHtml();
+ StringTextBuffer changed = new StringTextBuffer("foo @Us baz");
+ StringTextBuffer old = new StringTextBuffer("foo @User baz");
+ RunPartialParseTest(new TextChange(7, 2, old, 0, changed),
+ new MarkupBlock(
+ factory.Markup("foo "),
+ new ExpressionBlock(
+ factory.CodeTransition(),
+ factory.Code("Us")
+ .AsImplicitExpression(VBCodeParser.DefaultKeywords)
+ .Accepts(AcceptedCharacters.NonWhiteSpace)),
+ factory.Markup(" baz")));
+ }
+
+ [Fact]
+ public void ImplicitExpressionProvisionallyAcceptsMultipleInsertionIfItCausesIdentifierExpansionAndTrailingDot()
+ {
+ var factory = SpanFactory.CreateVbHtml();
+ StringTextBuffer changed = new StringTextBuffer("foo @User. baz");
+ StringTextBuffer old = new StringTextBuffer("foo @U baz");
+ RunPartialParseTest(new TextChange(6, 0, old, 4, changed),
+ new MarkupBlock(
+ factory.Markup("foo "),
+ new ExpressionBlock(
+ factory.CodeTransition(),
+ factory.Code("User.")
+ .AsImplicitExpression(VBCodeParser.DefaultKeywords)
+ .Accepts(AcceptedCharacters.NonWhiteSpace)),
+ factory.Markup(" baz")),
+ additionalFlags: PartialParseResult.Provisional);
+ }
+
+ [Fact]
+ public void ImplicitExpressionAcceptsMultipleInsertionIfItOnlyCausesIdentifierExpansion()
+ {
+ var factory = SpanFactory.CreateVbHtml();
+ StringTextBuffer changed = new StringTextBuffer("foo @barbiz baz");
+ StringTextBuffer old = new StringTextBuffer("foo @bar baz");
+ RunPartialParseTest(new TextChange(8, 0, old, 3, changed),
+ new MarkupBlock(
+ factory.Markup("foo "),
+ new ExpressionBlock(
+ factory.CodeTransition(),
+ factory.Code("barbiz")
+ .AsImplicitExpression(VBCodeParser.DefaultKeywords)
+ .Accepts(AcceptedCharacters.NonWhiteSpace)),
+ factory.Markup(" baz")));
+ }
+
+ [Fact]
+ public void ImplicitExpressionRejectsChangeWhichWouldHaveBeenAcceptedIfLastChangeWasProvisionallyAcceptedOnDifferentSpan()
+ {
+ var factory = SpanFactory.CreateVbHtml();
+
+ // Arrange
+ TextChange dotTyped = new TextChange(8, 0, new StringTextBuffer("foo @foo @bar"), 1, new StringTextBuffer("foo @foo. @bar"));
+ TextChange charTyped = new TextChange(14, 0, new StringTextBuffer("foo @foo. @barb"), 1, new StringTextBuffer("foo @foo. @barb"));
+ TestParserManager manager = CreateParserManager();
+ manager.InitializeWithDocument(dotTyped.OldBuffer);
+
+ // Apply the dot change
+ Assert.Equal(PartialParseResult.Provisional | PartialParseResult.Accepted, manager.CheckForStructureChangesAndWait(dotTyped));
+
+ // Act (apply the identifier start char change)
+ PartialParseResult result = manager.CheckForStructureChangesAndWait(charTyped);
+
+ // Assert
+ Assert.Equal(PartialParseResult.Rejected, result);
+ Assert.False(manager.Parser.LastResultProvisional, "LastResultProvisional flag should have been cleared but it was not");
+ ParserTestBase.EvaluateParseTree(manager.Parser.CurrentParseTree,
+ new MarkupBlock(
+ factory.Markup("foo "),
+ new ExpressionBlock(
+ factory.CodeTransition(),
+ factory.Code("foo")
+ .AsImplicitExpression(VBCodeParser.DefaultKeywords)
+ .Accepts(AcceptedCharacters.NonWhiteSpace)),
+ factory.Markup(". "),
+ new ExpressionBlock(
+ factory.CodeTransition(),
+ factory.Code("barb")
+ .AsImplicitExpression(VBCodeParser.DefaultKeywords)
+ .Accepts(AcceptedCharacters.NonWhiteSpace)),
+ factory.EmptyHtml()));
+ }
+
+ [Fact]
+ public void ImplicitExpressionAcceptsIdentifierTypedAfterDotIfLastChangeWasProvisionalAcceptanceOfDot()
+ {
+ var factory = SpanFactory.CreateVbHtml();
+
+ // Arrange
+ TextChange dotTyped = new TextChange(8, 0, new StringTextBuffer("foo @foo bar"), 1, new StringTextBuffer("foo @foo. bar"));
+ TextChange charTyped = new TextChange(9, 0, new StringTextBuffer("foo @foo. bar"), 1, new StringTextBuffer("foo @foo.b bar"));
+ TestParserManager manager = CreateParserManager();
+ manager.InitializeWithDocument(dotTyped.OldBuffer);
+
+ // Apply the dot change
+ Assert.Equal(PartialParseResult.Provisional | PartialParseResult.Accepted, manager.CheckForStructureChangesAndWait(dotTyped));
+
+ // Act (apply the identifier start char change)
+ PartialParseResult result = manager.CheckForStructureChangesAndWait(charTyped);
+
+ // Assert
+ Assert.Equal(PartialParseResult.Accepted, result);
+ Assert.False(manager.Parser.LastResultProvisional, "LastResultProvisional flag should have been cleared but it was not");
+ ParserTestBase.EvaluateParseTree(manager.Parser.CurrentParseTree,
+ new MarkupBlock(
+ factory.Markup("foo "),
+ new ExpressionBlock(
+ factory.CodeTransition(),
+ factory.Code("foo.b")
+ .AsImplicitExpression(VBCodeParser.DefaultKeywords)
+ .Accepts(AcceptedCharacters.NonWhiteSpace)),
+ factory.Markup(" bar")));
+ }
+ [Fact]
+ public void ImplicitExpressionAcceptsIdentifierExpansionAtEndOfNonWhitespaceCharacters()
+ {
+ var factory = SpanFactory.CreateVbHtml();
+ StringTextBuffer changed = new StringTextBuffer(@"@Code
+ @food
+End Code");
+ StringTextBuffer old = new StringTextBuffer(@"@Code
+ @foo
+End Code");
+ RunPartialParseTest(new TextChange(15, 0, old, 1, changed),
+ new MarkupBlock(
+ factory.EmptyHtml(),
+ new StatementBlock(
+ factory.CodeTransition(),
+ factory.MetaCode("Code")
+ .Accepts(AcceptedCharacters.None),
+ factory.Code("\r\n ").AsStatement(),
+ new ExpressionBlock(
+ factory.CodeTransition(),
+ factory.Code("food")
+ .AsImplicitExpression(VBCodeParser.DefaultKeywords, acceptTrailingDot: true)
+ .Accepts(AcceptedCharacters.NonWhiteSpace)),
+ factory.Code("\r\n").AsStatement(),
+ factory.MetaCode("End Code").Accepts(AcceptedCharacters.None)),
+ factory.EmptyHtml()));
+ }
+
+ [Fact]
+ public void ImplicitExpressionProvisionallyAcceptsDotAfterIdentifierInMarkup()
+ {
+ var factory = SpanFactory.CreateVbHtml();
+ StringTextBuffer changed = new StringTextBuffer("foo @foo. bar");
+ StringTextBuffer old = new StringTextBuffer("foo @foo bar");
+ RunPartialParseTest(new TextChange(8, 0, old, 1, changed),
+ new MarkupBlock(
+ factory.Markup("foo "),
+ new ExpressionBlock(
+ factory.CodeTransition(),
+ factory.Code("foo.")
+ .AsImplicitExpression(VBCodeParser.DefaultKeywords)
+ .Accepts(AcceptedCharacters.NonWhiteSpace)),
+ factory.Markup(" bar")),
+ additionalFlags: PartialParseResult.Provisional);
+ }
+
+ [Fact]
+ public void ImplicitExpressionAcceptsAdditionalIdentifierCharactersIfEndOfSpanIsIdentifier()
+ {
+ var factory = SpanFactory.CreateVbHtml();
+ StringTextBuffer changed = new StringTextBuffer("foo @foob baz");
+ StringTextBuffer old = new StringTextBuffer("foo @foo bar");
+ RunPartialParseTest(new TextChange(8, 0, old, 1, changed),
+ new MarkupBlock(
+ factory.Markup("foo "),
+ new ExpressionBlock(
+ factory.CodeTransition(),
+ factory.Code("foob")
+ .AsImplicitExpression(VBCodeParser.DefaultKeywords)
+ .Accepts(AcceptedCharacters.NonWhiteSpace)),
+ factory.Markup(" bar")));
+ }
+
+ [Fact]
+ public void ImplicitExpressionAcceptsAdditionalIdentifierStartCharactersIfEndOfSpanIsDot()
+ {
+ var factory = SpanFactory.CreateVbHtml();
+ StringTextBuffer changed = new StringTextBuffer("@Code @foo.b End Code");
+ StringTextBuffer old = new StringTextBuffer("@Code @foo. End Code");
+ RunPartialParseTest(new TextChange(11, 0, old, 1, changed),
+ new MarkupBlock(
+ factory.EmptyHtml(),
+ new StatementBlock(
+ factory.CodeTransition(),
+ factory.MetaCode("Code").Accepts(AcceptedCharacters.None),
+ factory.Code(" ").AsStatement(),
+ new ExpressionBlock(
+ factory.CodeTransition(),
+ factory.Code("foo.b")
+ .AsImplicitExpression(VBCodeParser.DefaultKeywords, acceptTrailingDot: true)
+ .Accepts(AcceptedCharacters.NonWhiteSpace)),
+ factory.Code(" ").AsStatement(),
+ factory.MetaCode("End Code").Accepts(AcceptedCharacters.None)),
+ factory.EmptyHtml()));
+ }
+
+ [Fact]
+ public void ImplicitExpressionAcceptsDotIfTrailingDotsAreAllowed()
+ {
+ var factory = SpanFactory.CreateVbHtml();
+ StringTextBuffer changed = new StringTextBuffer("@Code @foo. End Code");
+ StringTextBuffer old = new StringTextBuffer("@Code @foo End Code");
+ RunPartialParseTest(new TextChange(10, 0, old, 1, changed),
+ new MarkupBlock(
+ factory.EmptyHtml(),
+ new StatementBlock(
+ factory.CodeTransition(),
+ factory.MetaCode("Code").Accepts(AcceptedCharacters.None),
+ factory.Code(" ").AsStatement(),
+ new ExpressionBlock(
+ factory.CodeTransition(),
+ factory.Code("foo.")
+ .AsImplicitExpression(VBCodeParser.DefaultKeywords, acceptTrailingDot: true)
+ .Accepts(AcceptedCharacters.NonWhiteSpace)),
+ factory.Code(" ").AsStatement(),
+ factory.MetaCode("End Code").Accepts(AcceptedCharacters.None)),
+ factory.EmptyHtml()));
+ }
+
+ [Fact]
+ public void ImplicitExpressionCorrectlyTriggersReparseIfFunctionsKeywordTyped()
+ {
+ RunTypeKeywordTest("functions");
+ }
+
+ [Fact]
+ public void ImplicitExpressionCorrectlyTriggersReparseIfCodeKeywordTyped()
+ {
+ RunTypeKeywordTest("code");
+ }
+
+ [Fact]
+ public void ImplicitExpressionCorrectlyTriggersReparseIfSectionKeywordTyped()
+ {
+ RunTypeKeywordTest("section");
+ }
+
+ [Fact]
+ public void ImplicitExpressionCorrectlyTriggersReparseIfDoKeywordTyped()
+ {
+ RunTypeKeywordTest("do");
+ }
+
+ [Fact]
+ public void ImplicitExpressionCorrectlyTriggersReparseIfWhileKeywordTyped()
+ {
+ RunTypeKeywordTest("while");
+ }
+
+ [Fact]
+ public void ImplicitExpressionCorrectlyTriggersReparseIfIfKeywordTyped()
+ {
+ RunTypeKeywordTest("if");
+ }
+
+ [Fact]
+ public void ImplicitExpressionCorrectlyTriggersReparseIfSelectKeywordTyped()
+ {
+ RunTypeKeywordTest("select");
+ }
+
+ [Fact]
+ public void ImplicitExpressionCorrectlyTriggersReparseIfForKeywordTyped()
+ {
+ RunTypeKeywordTest("for");
+ }
+
+ [Fact]
+ public void ImplicitExpressionCorrectlyTriggersReparseIfTryKeywordTyped()
+ {
+ RunTypeKeywordTest("try");
+ }
+
+ [Fact]
+ public void ImplicitExpressionCorrectlyTriggersReparseIfWithKeywordTyped()
+ {
+ RunTypeKeywordTest("with");
+ }
+
+ [Fact]
+ public void ImplicitExpressionCorrectlyTriggersReparseIfSyncLockKeywordTyped()
+ {
+ RunTypeKeywordTest("synclock");
+ }
+
+ [Fact]
+ public void ImplicitExpressionCorrectlyTriggersReparseIfUsingKeywordTyped()
+ {
+ RunTypeKeywordTest("using");
+ }
+
+ [Fact]
+ public void ImplicitExpressionCorrectlyTriggersReparseIfImportsKeywordTyped()
+ {
+ RunTypeKeywordTest("imports");
+ }
+
+ [Fact]
+ public void ImplicitExpressionCorrectlyTriggersReparseIfInheritsKeywordTyped()
+ {
+ RunTypeKeywordTest("inherits");
+ }
+
+ [Fact]
+ public void ImplicitExpressionCorrectlyTriggersReparseIfOptionKeywordTyped()
+ {
+ RunTypeKeywordTest("option");
+ }
+
+ [Fact]
+ public void ImplicitExpressionCorrectlyTriggersReparseIfHelperKeywordTyped()
+ {
+ RunTypeKeywordTest("helper");
+ }
+
+ [Fact]
+ public void ImplicitExpressionCorrectlyTriggersReparseIfNamespaceKeywordTyped()
+ {
+ RunTypeKeywordTest("namespace");
+ }
+
+ [Fact]
+ public void ImplicitExpressionCorrectlyTriggersReparseIfClassKeywordTyped()
+ {
+ RunTypeKeywordTest("class");
+ }
+
+ [Fact]
+ public void ImplicitExpressionCorrectlyTriggersReparseIfLayoutKeywordTyped()
+ {
+ RunTypeKeywordTest("layout");
+ }
+ }
+} \ No newline at end of file
diff --git a/test/System.Web.Razor.Test/Parser/RazorParserTest.cs b/test/System.Web.Razor.Test/Parser/RazorParserTest.cs
new file mode 100644
index 00000000..69c0ff33
--- /dev/null
+++ b/test/System.Web.Razor.Test/Parser/RazorParserTest.cs
@@ -0,0 +1,134 @@
+using System.IO;
+using System.Web.Razor.Parser;
+using System.Web.Razor.Parser.SyntaxTree;
+using System.Web.Razor.Test.Framework;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Razor.Test.Parser
+{
+ public class RazorParserTest
+ {
+ [Fact]
+ public void ConstructorRequiresNonNullCodeParser()
+ {
+ Assert.ThrowsArgumentNull(() => new RazorParser(null, new HtmlMarkupParser()), "codeParser");
+ }
+
+ [Fact]
+ public void ConstructorRequiresNonNullMarkupParser()
+ {
+ Assert.ThrowsArgumentNull(() => new RazorParser(new CSharpCodeParser(), null), "markupParser");
+ }
+
+ [Fact]
+ public void ParseMethodCallsParseDocumentOnMarkupParserAndReturnsResults()
+ {
+ var factory = SpanFactory.CreateCsHtml();
+
+ // Arrange
+ RazorParser parser = new RazorParser(new CSharpCodeParser(), new HtmlMarkupParser());
+
+ // Act/Assert
+ ParserTestBase.EvaluateResults(parser.Parse(new StringReader("foo @bar baz")),
+ new MarkupBlock(
+ factory.Markup("foo "),
+ new ExpressionBlock(
+ factory.CodeTransition(),
+ factory.Code("bar")
+ .AsImplicitExpression(CSharpCodeParser.DefaultKeywords)
+ .Accepts(AcceptedCharacters.NonWhiteSpace)),
+ factory.Markup(" baz")));
+ }
+
+ [Fact]
+ public void ParseMethodUsesProvidedParserListenerIfSpecified()
+ {
+ var factory = SpanFactory.CreateCsHtml();
+
+ // Arrange
+ RazorParser parser = new RazorParser(new CSharpCodeParser(), new HtmlMarkupParser());
+
+ // Act
+ ParserResults results = parser.Parse(new StringReader("foo @bar baz"));
+
+ // Assert
+ ParserTestBase.EvaluateResults(results,
+ new MarkupBlock(
+ factory.Markup("foo "),
+ new ExpressionBlock(
+ factory.CodeTransition(),
+ factory.Code("bar")
+ .AsImplicitExpression(CSharpCodeParser.DefaultKeywords)
+ .Accepts(AcceptedCharacters.NonWhiteSpace)),
+ factory.Markup(" baz")));
+ }
+
+ [Fact]
+ public void ParseMethodSetsUpRunWithSpecifiedCodeParserMarkupParserAndListenerAndPassesToMarkupParser()
+ {
+ RunParseWithListenerTest((parser, reader) => parser.Parse(reader));
+ }
+
+ private static void RunParseWithListenerTest(Action<RazorParser, TextReader> parserAction)
+ {
+ // Arrange
+ ParserBase markupParser = new MockMarkupParser();
+ ParserBase codeParser = new CSharpCodeParser();
+ RazorParser parser = new RazorParser(codeParser, markupParser);
+ TextReader expectedReader = new StringReader("foo");
+
+ // Act
+ parserAction(parser, expectedReader);
+
+ // Assert
+ ParserContext actualContext = markupParser.Context;
+ Assert.NotNull(actualContext);
+ Assert.Same(markupParser, actualContext.MarkupParser);
+ Assert.Same(markupParser, actualContext.ActiveParser);
+ Assert.Same(codeParser, actualContext.CodeParser);
+ }
+
+ private class MockMarkupParser : ParserBase
+ {
+ public override bool IsMarkupParser
+ {
+ get
+ {
+ return true;
+ }
+ }
+
+ public override void ParseDocument()
+ {
+ using (Context.StartBlock(BlockType.Markup))
+ {
+ }
+ }
+
+ public override void ParseSection(Tuple<string, string> nestingSequences, bool caseSensitive = true)
+ {
+ using (Context.StartBlock(BlockType.Markup))
+ {
+ }
+ }
+
+ public override void ParseBlock()
+ {
+ using (Context.StartBlock(BlockType.Markup))
+ {
+ }
+ }
+
+ protected override ParserBase OtherParser
+ {
+ get { return Context.CodeParser; }
+ }
+
+ public override void BuildSpan(SpanBuilder span, Razor.Text.SourceLocation start, string content)
+ {
+ throw new NotImplementedException();
+ }
+ }
+ }
+}
diff --git a/test/System.Web.Razor.Test/Parser/VB/VBAutoCompleteTest.cs b/test/System.Web.Razor.Test/Parser/VB/VBAutoCompleteTest.cs
new file mode 100644
index 00000000..dc557bf2
--- /dev/null
+++ b/test/System.Web.Razor.Test/Parser/VB/VBAutoCompleteTest.cs
@@ -0,0 +1,153 @@
+using System.Web.Razor.Generator;
+using System.Web.Razor.Parser;
+using System.Web.Razor.Parser.SyntaxTree;
+using System.Web.Razor.Resources;
+using System.Web.Razor.Test.Framework;
+using System.Web.Razor.Text;
+using System.Web.Razor.Tokenizer.Symbols;
+using Xunit;
+
+namespace System.Web.Razor.Test.Parser.VB
+{
+ public class VBAutoCompleteTest : VBHtmlCodeParserTestBase
+ {
+ [Fact]
+ public void FunctionsDirective_AutoComplete_At_EOF()
+ {
+ ParseBlockTest("@Functions",
+ new FunctionsBlock(
+ Factory.CodeTransition("@")
+ .Accepts(AcceptedCharacters.None),
+ Factory.MetaCode("Functions")
+ .Accepts(AcceptedCharacters.None),
+ Factory.EmptyVB()
+ .AsFunctionsBody()
+ .With(new AutoCompleteEditHandler(CSharpLanguageCharacteristics.Instance.TokenizeString)
+ {
+ AutoCompleteString = SyntaxConstants.VB.EndFunctionsKeyword
+ })),
+ new RazorError(
+ String.Format(RazorResources.ParseError_BlockNotTerminated, "Functions", "End Functions"),
+ 1, 0, 1));
+ }
+
+ [Fact]
+ public void HelperDirective_AutoComplete_At_EOF()
+ {
+ ParseBlockTest("@Helper Strong(value As String)",
+ new HelperBlock(new HelperCodeGenerator(new LocationTagged<string>("Strong(value As String)", 8, 0, 8), headerComplete: true),
+ Factory.CodeTransition(),
+ Factory.MetaCode("Helper ")
+ .Accepts(AcceptedCharacters.None),
+ Factory.Code("Strong(value As String)")
+ .Hidden()
+ .Accepts(AcceptedCharacters.None)
+ .With(new AutoCompleteEditHandler(CSharpLanguageCharacteristics.Instance.TokenizeString) { AutoCompleteString = SyntaxConstants.VB.EndHelperKeyword }),
+ new StatementBlock()),
+ new RazorError(
+ String.Format(RazorResources.ParseError_BlockNotTerminated, "Helper", "End Helper"),
+ 1, 0, 1));
+ }
+
+ [Fact]
+ public void SectionDirective_AutoComplete_At_EOF()
+ {
+ ParseBlockTest("@Section Header",
+ new SectionBlock(new SectionCodeGenerator("Header"),
+ Factory.CodeTransition(),
+ Factory.MetaCode("Section Header")
+ .With(new AutoCompleteEditHandler(CSharpLanguageCharacteristics.Instance.TokenizeString) { AutoCompleteString = SyntaxConstants.VB.EndSectionKeyword }),
+ new MarkupBlock()),
+ new RazorError(
+ String.Format(RazorResources.ParseError_BlockNotTerminated, "Section", "End Section"),
+ 1, 0, 1));
+ }
+
+ [Fact]
+ public void VerbatimBlock_AutoComplete_At_EOF()
+ {
+ ParseBlockTest("@Code",
+ new StatementBlock(
+ Factory.CodeTransition(),
+ Factory.MetaCode("Code").Accepts(AcceptedCharacters.None),
+ Factory.Span(SpanKind.Code, new VBSymbol(5, 0, 5, String.Empty, VBSymbolType.Unknown))
+ .With(new StatementCodeGenerator())
+ .With(new AutoCompleteEditHandler(CSharpLanguageCharacteristics.Instance.TokenizeString) { AutoCompleteString = SyntaxConstants.VB.EndCodeKeyword })),
+ new RazorError(
+ String.Format(RazorResources.ParseError_BlockNotTerminated, "Code", "End Code"),
+ 1, 0, 1));
+ }
+
+ [Fact]
+ public void FunctionsDirective_AutoComplete_At_StartOfFile()
+ {
+ ParseBlockTest(@"@Functions
+foo",
+ new FunctionsBlock(
+ Factory.CodeTransition("@").Accepts(AcceptedCharacters.None),
+ Factory.MetaCode("Functions").Accepts(AcceptedCharacters.None),
+ Factory.Code("\r\nfoo")
+ .AsFunctionsBody()
+ .With(new AutoCompleteEditHandler(CSharpLanguageCharacteristics.Instance.TokenizeString)
+ {
+ AutoCompleteString = SyntaxConstants.VB.EndFunctionsKeyword
+ })),
+ new RazorError(
+ String.Format(RazorResources.ParseError_BlockNotTerminated, "Functions", "End Functions"),
+ 1, 0, 1));
+ }
+
+ [Fact]
+ public void HelperDirective_AutoComplete_At_StartOfFile()
+ {
+ ParseBlockTest(@"@Helper Strong(value As String)
+Foo",
+ new HelperBlock(new HelperCodeGenerator(new LocationTagged<string>("Strong(value As String)", 8, 0, 8), headerComplete: true),
+ Factory.CodeTransition(),
+ Factory.MetaCode("Helper ")
+ .Accepts(AcceptedCharacters.None),
+ Factory.Code("Strong(value As String)")
+ .Hidden()
+ .Accepts(AcceptedCharacters.None)
+ .With(new AutoCompleteEditHandler(CSharpLanguageCharacteristics.Instance.TokenizeString) { AutoCompleteString = SyntaxConstants.VB.EndHelperKeyword }),
+ new StatementBlock(
+ Factory.Code("\r\nFoo").AsStatement())),
+ new RazorError(
+ String.Format(RazorResources.ParseError_BlockNotTerminated, "Helper", "End Helper"),
+ 1, 0, 1));
+ }
+
+ [Fact]
+ public void SectionDirective_AutoComplete_At_StartOfFile()
+ {
+ ParseBlockTest(@"@Section Header
+Foo",
+ new SectionBlock(new SectionCodeGenerator("Header"),
+ Factory.CodeTransition(),
+ Factory.MetaCode("Section Header")
+ .With(new AutoCompleteEditHandler(CSharpLanguageCharacteristics.Instance.TokenizeString) { AutoCompleteString = SyntaxConstants.VB.EndSectionKeyword }),
+ new MarkupBlock(
+ Factory.Markup("\r\nFoo")
+ .With(new MarkupCodeGenerator()))),
+ new RazorError(
+ String.Format(RazorResources.ParseError_BlockNotTerminated, "Section", "End Section"),
+ 1, 0, 1));
+ }
+
+ [Fact]
+ public void VerbatimBlock_AutoComplete_At_StartOfFile()
+ {
+ ParseBlockTest(@"@Code
+Foo",
+ new StatementBlock(
+ Factory.CodeTransition(),
+ Factory.MetaCode("Code").Accepts(AcceptedCharacters.None),
+ Factory.Code("\r\nFoo")
+ .AsStatement()
+ .With(new AutoCompleteEditHandler(CSharpLanguageCharacteristics.Instance.TokenizeString) { AutoCompleteString = SyntaxConstants.VB.EndCodeKeyword })),
+ new RazorError(
+ String.Format(RazorResources.ParseError_BlockNotTerminated, "Code", "End Code"),
+ 1, 0, 1));
+ }
+ }
+}
diff --git a/test/System.Web.Razor.Test/Parser/VB/VBBlockTest.cs b/test/System.Web.Razor.Test/Parser/VB/VBBlockTest.cs
new file mode 100644
index 00000000..a346dce5
--- /dev/null
+++ b/test/System.Web.Razor.Test/Parser/VB/VBBlockTest.cs
@@ -0,0 +1,372 @@
+using System.Web.Razor.Parser;
+using System.Web.Razor.Parser.SyntaxTree;
+using System.Web.Razor.Resources;
+using System.Web.Razor.Test.Framework;
+using System.Web.Razor.Text;
+using Xunit;
+using Xunit.Extensions;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Razor.Test.Parser.VB
+{
+ public class VBBlockTest : VBHtmlCodeParserTestBase
+ {
+ [Fact]
+ public void ParseBlockMethodThrowsArgNullExceptionOnNullContext()
+ {
+ // Arrange
+ VBCodeParser parser = new VBCodeParser();
+
+ // Act and Assert
+ Assert.Throws<InvalidOperationException>(() => parser.ParseBlock(), RazorResources.Parser_Context_Not_Set);
+ }
+
+ [Fact]
+ public void ParseBlockAcceptsImplicitExpression()
+ {
+ ParseBlockTest(@"If True Then
+ @foo
+End If",
+ new StatementBlock(
+ Factory.Code("If True Then\r\n ").AsStatement(),
+ new ExpressionBlock(
+ Factory.CodeTransition(),
+ Factory.Code("foo")
+ .AsImplicitExpression(VBCodeParser.DefaultKeywords, acceptTrailingDot: true)
+ .Accepts(AcceptedCharacters.NonWhiteSpace)),
+ Factory.Code("\r\nEnd If")
+ .AsStatement()
+ .Accepts(AcceptedCharacters.None)));
+ }
+
+ [Fact]
+ public void ParseBlockAcceptsIfStatementWithinCodeBlockIfInDesignTimeMode()
+ {
+ ParseBlockTest(@"If True Then
+ @If True Then
+ End If
+End If",
+ new StatementBlock(
+ Factory.Code("If True Then\r\n ").AsStatement(),
+ new StatementBlock(
+ Factory.CodeTransition(),
+ Factory.Code("If True Then\r\n End If\r\n")
+ .AsStatement()
+ .Accepts(AcceptedCharacters.None)),
+ Factory.Code(@"End If")
+ .AsStatement()
+ .Accepts(AcceptedCharacters.None)));
+ }
+
+ [Fact]
+ public void ParseBlockSupportsSpacesInStrings()
+ {
+ ParseBlockTest(@"for each p in db.Query(""SELECT * FROM PRODUCTS"")
+ @<p>@p.Name</p>
+next",
+ new StatementBlock(
+ Factory.Code("for each p in db.Query(\"SELECT * FROM PRODUCTS\")\r\n")
+ .AsStatement(),
+ new MarkupBlock(
+ Factory.Markup(" "),
+ Factory.MarkupTransition(),
+ Factory.Markup("<p>"),
+ new ExpressionBlock(
+ Factory.CodeTransition(),
+ Factory.Code("p.Name")
+ .AsImplicitExpression(VBCodeParser.DefaultKeywords)
+ .Accepts(AcceptedCharacters.NonWhiteSpace)),
+ Factory.Markup("</p>\r\n").Accepts(AcceptedCharacters.None)),
+ Factory.Code("next")
+ .AsStatement()
+ .Accepts(AcceptedCharacters.WhiteSpace | AcceptedCharacters.NonWhiteSpace)));
+ }
+
+ [Fact]
+ public void ParseBlockSupportsSimpleCodeBlock()
+ {
+ ParseBlockTest(@"Code
+ If foo IsNot Nothing
+ Bar(foo)
+ End If
+End Code",
+ new StatementBlock(
+ Factory.MetaCode("Code").Accepts(AcceptedCharacters.None),
+ Factory.Code("\r\n If foo IsNot Nothing\r\n Bar(foo)\r\n End If\r\n")
+ .AsStatement(),
+ Factory.MetaCode("End Code").Accepts(AcceptedCharacters.None)));
+ }
+
+ [Fact]
+ public void ParseBlockRejectsNewlineBetweenEndAndCodeIfNotPrefixedWithUnderscore()
+ {
+ ParseBlockTest(@"Code
+ If foo IsNot Nothing
+ Bar(foo)
+ End If
+End
+Code",
+ new StatementBlock(
+ Factory.MetaCode("Code").Accepts(AcceptedCharacters.None),
+ Factory.Code("\r\n If foo IsNot Nothing\r\n Bar(foo)\r\n End If\r\nEnd\r\nCode")
+ .AsStatement()),
+ new RazorError(
+ String.Format(RazorResources.ParseError_BlockNotTerminated, "Code", "End Code"),
+ SourceLocation.Zero));
+ }
+
+ [Fact]
+ public void ParseBlockAcceptsNewlineBetweenEndAndCodeIfPrefixedWithUnderscore()
+ {
+ ParseBlockTest(@"Code
+ If foo IsNot Nothing
+ Bar(foo)
+ End If
+End _
+_
+ _
+Code",
+ new StatementBlock(
+ Factory.MetaCode("Code").Accepts(AcceptedCharacters.None),
+ Factory.Code("\r\n If foo IsNot Nothing\r\n Bar(foo)\r\n End If\r\n")
+ .AsStatement(),
+ Factory.MetaCode("End _\r\n_\r\n _\r\nCode").Accepts(AcceptedCharacters.None)));
+ }
+
+ [Fact]
+ public void ParseBlockSupportsSimpleFunctionsBlock()
+ {
+ ParseBlockTest(@"Functions
+ Public Sub Foo()
+ Bar()
+ End Sub
+
+ Private Function Bar() As Object
+ Return Nothing
+ End Function
+End Functions",
+ new FunctionsBlock(
+ Factory.MetaCode("Functions").Accepts(AcceptedCharacters.None),
+ Factory.Code("\r\n Public Sub Foo()\r\n Bar()\r\n End Sub\r\n\r\n Private Function Bar() As Object\r\n Return Nothing\r\n End Function\r\n")
+ .AsFunctionsBody(),
+ Factory.MetaCode("End Functions").Accepts(AcceptedCharacters.None)));
+ }
+
+ [Fact]
+ public void ParseBlockRejectsNewlineBetweenEndAndFunctionsIfNotPrefixedWithUnderscore()
+ {
+ ParseBlockTest(@"Functions
+ If foo IsNot Nothing
+ Bar(foo)
+ End If
+End
+Functions",
+ new FunctionsBlock(
+ Factory.MetaCode("Functions").Accepts(AcceptedCharacters.None),
+ Factory.Code("\r\n If foo IsNot Nothing\r\n Bar(foo)\r\n End If\r\nEnd\r\nFunctions")
+ .AsFunctionsBody()),
+ new RazorError(
+ String.Format(RazorResources.ParseError_BlockNotTerminated, "Functions", "End Functions"),
+ SourceLocation.Zero));
+ }
+
+ [Fact]
+ public void ParseBlockAcceptsNewlineBetweenEndAndFunctionsIfPrefixedWithUnderscore()
+ {
+ ParseBlockTest(@"Functions
+ If foo IsNot Nothing
+ Bar(foo)
+ End If
+End _
+_
+ _
+Functions",
+ new FunctionsBlock(
+ Factory.MetaCode("Functions").Accepts(AcceptedCharacters.None),
+ Factory.Code("\r\n If foo IsNot Nothing\r\n Bar(foo)\r\n End If\r\n")
+ .AsFunctionsBody(),
+ Factory.MetaCode("End _\r\n_\r\n _\r\nFunctions").Accepts(AcceptedCharacters.None)));
+ }
+
+ [Fact]
+ public void ParseBlockCorrectlyHandlesExtraEndsInEndCode()
+ {
+ ParseBlockTest(@"Code
+ Bar End
+End Code",
+ new StatementBlock(
+ Factory.MetaCode("Code").Accepts(AcceptedCharacters.None),
+ Factory.Code("\r\n Bar End\r\n").AsStatement(),
+ Factory.MetaCode("End Code").Accepts(AcceptedCharacters.None)));
+ }
+
+ [Fact]
+ public void ParseBlockCorrectlyHandlesExtraEndsInEndFunctions()
+ {
+ ParseBlockTest(@"Functions
+ Bar End
+End Functions",
+ new FunctionsBlock(
+ Factory.MetaCode("Functions").Accepts(AcceptedCharacters.None),
+ Factory.Code("\r\n Bar End\r\n").AsFunctionsBody().AutoCompleteWith(null, atEndOfSpan: false),
+ Factory.MetaCode("End Functions").Accepts(AcceptedCharacters.None)));
+ }
+
+ [Theory]
+ [InlineData("If", "End", "If")]
+ [InlineData("Try", "End", "Try")]
+ [InlineData("While", "End", "While")]
+ [InlineData("Using", "End", "Using")]
+ [InlineData("With", "End", "With")]
+ public void KeywordAllowsNewlinesIfPrefixedByUnderscore(string startKeyword, string endKeyword1, string endKeyword2)
+ {
+ string code = startKeyword + @"
+ ' In the block
+" + endKeyword1 + @" _
+_
+_
+_
+_
+_
+ " + endKeyword2 + @"
+";
+ ParseBlockTest(code + "foo bar baz",
+ new StatementBlock(
+ Factory.Code(code)
+ .AsStatement()
+ .Accepts(AcceptedCharacters.None)));
+ }
+
+ [Theory]
+ [InlineData("While", "EndWhile", "End While")]
+ [InlineData("If", "EndIf", "End If")]
+ [InlineData("Select", "EndSelect", "End Select")]
+ [InlineData("Try", "EndTry", "End Try")]
+ [InlineData("With", "EndWith", "End With")]
+ [InlineData("Using", "EndUsing", "End Using")]
+ public void EndTerminatedKeywordRequiresSpaceBetweenEndAndKeyword(string startKeyword, string wrongEndKeyword, string endKeyword)
+ {
+ string code = startKeyword + @"
+ ' This should not end the code
+ " + wrongEndKeyword + @"
+ ' But this should
+" + endKeyword;
+ ParseBlockTest(code,
+ new StatementBlock(
+ Factory.Code(code)
+ .AsStatement()
+ .Accepts(AcceptedCharacters.None)));
+ }
+
+ [Theory]
+ [InlineData("While", "End While", false)]
+ [InlineData("Do", "Loop", true)]
+ [InlineData("If", "End If", false)]
+ [InlineData("Select", "End Select", false)]
+ [InlineData("For", "Next", true)]
+ [InlineData("Try", "End Try", false)]
+ [InlineData("With", "End With", false)]
+ [InlineData("Using", "End Using", false)]
+ public void EndSequenceInString(string keyword, string endSequence, bool acceptToEndOfLine)
+ {
+ string code = keyword + @"
+ """ + endSequence + @"""
+" + endSequence + (acceptToEndOfLine ? @" foo bar baz" : "") + @"
+";
+ ParseBlockTest(code + "biz boz",
+ new StatementBlock(
+ Factory.Code(code).AsStatement().Accepts(GetAcceptedCharacters(acceptToEndOfLine))));
+ }
+
+ [Theory]
+ [InlineData("While", "End While", false)]
+ [InlineData("Do", "Loop", true)]
+ [InlineData("If", "End If", false)]
+ [InlineData("Select", "End Select", false)]
+ [InlineData("For", "Next", true)]
+ [InlineData("Try", "End Try", false)]
+ [InlineData("With", "End With", false)]
+ [InlineData("Using", "End Using", false)]
+ private void CommentedEndSequence(string keyword, string endSequence, bool acceptToEndOfLine)
+ {
+ string code = keyword + @"
+ '" + endSequence + @"
+" + endSequence + (acceptToEndOfLine ? @" foo bar baz" : "") + @"
+";
+ ParseBlockTest(code + "biz boz",
+ new StatementBlock(
+ Factory.Code(code).AsStatement().Accepts(GetAcceptedCharacters(acceptToEndOfLine))));
+ }
+
+ [Theory]
+ [InlineData("While", "End While", false)]
+ [InlineData("Do", "Loop", true)]
+ [InlineData("If", "End If", false)]
+ [InlineData("Select", "End Select", false)]
+ [InlineData("For", "Next", true)]
+ [InlineData("Try", "End Try", false)]
+ [InlineData("With", "End With", false)]
+ [InlineData("SyncLock", "End SyncLock", false)]
+ [InlineData("Using", "End Using", false)]
+ private void NestedKeywordBlock(string keyword, string endSequence, bool acceptToEndOfLine)
+ {
+ string code = keyword + @"
+ " + keyword + @"
+ Bar(foo)
+ " + endSequence + @"
+" + endSequence + (acceptToEndOfLine ? @" foo bar baz" : "") + @"
+";
+ ParseBlockTest(code + "biz boz",
+ new StatementBlock(
+ Factory.Code(code).AsStatement().Accepts(GetAcceptedCharacters(acceptToEndOfLine))));
+ }
+
+ [Theory]
+ [InlineData("While True", "End While", false)]
+ [InlineData("Do", "Loop", true)]
+ [InlineData("If foo IsNot Nothing", "End If", false)]
+ [InlineData("Select Case foo", "End Select", false)]
+ [InlineData("For Each p in Products", "Next", true)]
+ [InlineData("Try", "End Try", false)]
+ [InlineData("With", "End With", false)]
+ [InlineData("SyncLock", "End SyncLock", false)]
+ [InlineData("Using", "End Using", false)]
+ private void SimpleKeywordBlock(string keyword, string endSequence, bool acceptToEndOfLine)
+ {
+ string code = keyword + @"
+ Bar(foo)
+" + endSequence + (acceptToEndOfLine ? @" foo bar baz" : "") + @"
+";
+ ParseBlockTest(code + "biz boz",
+ new StatementBlock(
+ Factory.Code(code).AsStatement().Accepts(GetAcceptedCharacters(acceptToEndOfLine))));
+ }
+
+ [Theory]
+ [InlineData("While True", "Exit While", "End While", false)]
+ [InlineData("Do", "Exit Do", "Loop", true)]
+ [InlineData("For Each p in Products", "Exit For", "Next", true)]
+ [InlineData("While True", "Continue While", "End While", false)]
+ [InlineData("Do", "Continue Do", "Loop", true)]
+ [InlineData("For Each p in Products", "Continue For", "Next", true)]
+ private void KeywordWithExitOrContinue(string startKeyword, string exitKeyword, string endKeyword, bool acceptToEndOfLine)
+ {
+ string code = startKeyword + @"
+ ' This is before the exit
+ " + exitKeyword + @"
+ ' This is after the exit
+" + endKeyword + @"
+";
+ ParseBlockTest(code + "foo bar baz",
+ new StatementBlock(
+ Factory.Code(code).AsStatement().Accepts(GetAcceptedCharacters(acceptToEndOfLine))));
+ }
+
+ private AcceptedCharacters GetAcceptedCharacters(bool acceptToEndOfLine)
+ {
+ return acceptToEndOfLine ?
+ AcceptedCharacters.WhiteSpace | AcceptedCharacters.NonWhiteSpace :
+ AcceptedCharacters.None;
+ }
+ }
+}
diff --git a/test/System.Web.Razor.Test/Parser/VB/VBContinueStatementTest.cs b/test/System.Web.Razor.Test/Parser/VB/VBContinueStatementTest.cs
new file mode 100644
index 00000000..b342c8ee
--- /dev/null
+++ b/test/System.Web.Razor.Test/Parser/VB/VBContinueStatementTest.cs
@@ -0,0 +1,56 @@
+using System.Web.Razor.Parser;
+using System.Web.Razor.Parser.SyntaxTree;
+using System.Web.Razor.Test.Framework;
+using Xunit;
+
+namespace System.Web.Razor.Test.Parser.VB
+{
+ // VB Continue Statement: http://msdn.microsoft.com/en-us/library/801hyx6f.aspx
+ public class VBContinueStatementTest : VBHtmlCodeParserTestBase
+ {
+ [Fact]
+ public void VB_Do_Statement_With_Continue()
+ {
+ ParseBlockTest(@"@Do While True
+ Continue Do
+Loop
+' Not in the block!",
+ new StatementBlock(
+ Factory.CodeTransition(SyntaxConstants.TransitionString)
+ .Accepts(AcceptedCharacters.None),
+ Factory.Code("Do While True\r\n Continue Do\r\nLoop\r\n")
+ .AsStatement()
+ .Accepts(AcceptedCharacters.AnyExceptNewline)));
+ }
+
+ [Fact]
+ public void VB_For_Statement_With_Continue()
+ {
+ ParseBlockTest(@"@For i = 1 To 12
+ Continue For
+Next i
+' Not in the block!",
+ new StatementBlock(
+ Factory.CodeTransition(SyntaxConstants.TransitionString)
+ .Accepts(AcceptedCharacters.None),
+ Factory.Code("For i = 1 To 12\r\n Continue For\r\nNext i\r\n")
+ .AsStatement()
+ .Accepts(AcceptedCharacters.AnyExceptNewline)));
+ }
+
+ [Fact]
+ public void VB_While_Statement_With_Continue()
+ {
+ ParseBlockTest(@"@While True
+ Continue While
+End While
+' Not in the block!",
+ new StatementBlock(
+ Factory.CodeTransition(SyntaxConstants.TransitionString)
+ .Accepts(AcceptedCharacters.None),
+ Factory.Code("While True\r\n Continue While\r\nEnd While\r\n")
+ .AsStatement()
+ .Accepts(AcceptedCharacters.None)));
+ }
+ }
+}
diff --git a/test/System.Web.Razor.Test/Parser/VB/VBDirectiveTest.cs b/test/System.Web.Razor.Test/Parser/VB/VBDirectiveTest.cs
new file mode 100644
index 00000000..ad212625
--- /dev/null
+++ b/test/System.Web.Razor.Test/Parser/VB/VBDirectiveTest.cs
@@ -0,0 +1,127 @@
+using System.Web.Razor.Generator;
+using System.Web.Razor.Parser;
+using System.Web.Razor.Parser.SyntaxTree;
+using System.Web.Razor.Test.Framework;
+using System.Web.Razor.Text;
+using Xunit;
+
+namespace System.Web.Razor.Test.Parser.VB
+{
+ public class VBDirectiveTest : VBHtmlCodeParserTestBase
+ {
+ [Fact]
+ public void VB_Code_Directive()
+ {
+ ParseBlockTest(@"@Code
+ foo()
+End Code
+' Not part of the block",
+ new StatementBlock(
+ Factory.CodeTransition(SyntaxConstants.TransitionString)
+ .Accepts(AcceptedCharacters.None),
+ Factory.MetaCode("Code")
+ .Accepts(AcceptedCharacters.None),
+ Factory.Code("\r\n foo()\r\n")
+ .AsStatement()
+ .With(new AutoCompleteEditHandler(VBLanguageCharacteristics.Instance.TokenizeString)),
+ Factory.MetaCode("End Code")
+ .Accepts(AcceptedCharacters.None)));
+ }
+
+ [Fact]
+ public void VB_Functions_Directive()
+ {
+ ParseBlockTest(@"@Functions
+ Public Function Foo() As String
+ Return ""Foo""
+ End Function
+
+ Public Sub Bar()
+ End Sub
+End Functions
+' Not part of the block",
+ new FunctionsBlock(
+ Factory.CodeTransition(SyntaxConstants.TransitionString)
+ .Accepts(AcceptedCharacters.None),
+ Factory.MetaCode("Functions")
+ .Accepts(AcceptedCharacters.None),
+ Factory.Code("\r\n Public Function Foo() As String\r\n Return \"Foo\"\r\n End Function\r\n\r\n Public Sub Bar()\r\n End Sub\r\n")
+ .AsFunctionsBody(),
+ Factory.MetaCode("End Functions")
+ .Accepts(AcceptedCharacters.None)));
+ }
+
+ [Fact]
+ public void VB_Section_Directive()
+ {
+ ParseBlockTest(@"@Section Header
+ <p>Foo</p>
+End Section",
+ new SectionBlock(new SectionCodeGenerator("Header"),
+ Factory.CodeTransition(SyntaxConstants.TransitionString),
+ Factory.MetaCode(@"Section Header"),
+ new MarkupBlock(
+ Factory.Markup("\r\n <p>Foo</p>\r\n")),
+ Factory.MetaCode("End Section")
+ .Accepts(AcceptedCharacters.None)));
+ }
+
+ [Fact]
+ public void SessionStateDirectiveWorks()
+ {
+ ParseBlockTest(@"@SessionState InProc
+",
+ new DirectiveBlock(
+ Factory.CodeTransition(),
+ Factory.MetaCode("SessionState ")
+ .Accepts(AcceptedCharacters.None),
+ Factory.MetaCode("InProc\r\n")
+ .Accepts(AcceptedCharacters.None)
+ .With(new RazorDirectiveAttributeCodeGenerator("SessionState", "InProc"))
+ )
+ );
+ }
+
+ [Fact]
+ public void SessionStateDirectiveIsCaseInsensitive()
+ {
+ ParseBlockTest(@"@sessionstate disabled
+",
+ new DirectiveBlock(
+ Factory.CodeTransition(),
+ Factory.MetaCode("sessionstate ")
+ .Accepts(AcceptedCharacters.None),
+ Factory.MetaCode("disabled\r\n")
+ .Accepts(AcceptedCharacters.None)
+ .With(new RazorDirectiveAttributeCodeGenerator("SessionState", "disabled"))
+ )
+ );
+ }
+
+ [Fact]
+ public void VB_Helper_Directive()
+ {
+ ParseBlockTest(@"@Helper Strong(s as String)
+ s = s.ToUpperCase()
+ @<strong>s</strong>
+End Helper",
+ new HelperBlock(new HelperCodeGenerator(new LocationTagged<string>("Strong(s as String)", 8, 0, 8), headerComplete: true),
+ Factory.CodeTransition(SyntaxConstants.TransitionString),
+ Factory.MetaCode("Helper ")
+ .Accepts(AcceptedCharacters.None),
+ Factory.Code("Strong(s as String)").Hidden(),
+ new StatementBlock(
+ Factory.Code("\r\n s = s.ToUpperCase()\r\n")
+ .AsStatement(),
+ new MarkupBlock(
+ Factory.Markup(" "),
+ Factory.MarkupTransition(SyntaxConstants.TransitionString),
+ Factory.Markup("<strong>s</strong>\r\n")
+ .Accepts(AcceptedCharacters.None)),
+ Factory.EmptyVB()
+ .AsStatement(),
+ Factory.MetaCode("End Helper")
+ .Accepts(AcceptedCharacters.None))));
+ }
+ }
+}
diff --git a/test/System.Web.Razor.Test/Parser/VB/VBErrorTest.cs b/test/System.Web.Razor.Test/Parser/VB/VBErrorTest.cs
new file mode 100644
index 00000000..d0f1bdc7
--- /dev/null
+++ b/test/System.Web.Razor.Test/Parser/VB/VBErrorTest.cs
@@ -0,0 +1,172 @@
+using System.Web.Razor.Editor;
+using System.Web.Razor.Parser;
+using System.Web.Razor.Parser.SyntaxTree;
+using System.Web.Razor.Resources;
+using System.Web.Razor.Test.Framework;
+using System.Web.Razor.Text;
+using Xunit;
+using Xunit.Extensions;
+
+namespace System.Web.Razor.Test.Parser.VB
+{
+ public class VBErrorTest : VBHtmlCodeParserTestBase
+ {
+ [Fact]
+ public void ParserOutputsErrorAndRecoversToEndOfLineIfExplicitExpressionUnterminated()
+ {
+ ParseBlockTest(@"(foo
+bar",
+ new ExpressionBlock(
+ Factory.MetaCode("(").Accepts(AcceptedCharacters.None),
+ Factory.Code("foo").AsExpression()),
+ new RazorError(
+ String.Format(
+ RazorResources.ParseError_Expected_EndOfBlock_Before_EOF,
+ RazorResources.BlockName_ExplicitExpression,
+ ")", "("),
+ SourceLocation.Zero));
+ }
+
+ [Fact]
+ public void ParserOutputsZeroLengthCodeSpanIfEofReachedAfterStartOfExplicitExpression()
+ {
+ ParseBlockTest("(",
+ new ExpressionBlock(
+ Factory.MetaCode("(").Accepts(AcceptedCharacters.None),
+ Factory.EmptyVB().AsExpression()),
+ new RazorError(
+ String.Format(RazorResources.ParseError_Expected_EndOfBlock_Before_EOF, "explicit expression", ")", "("),
+ SourceLocation.Zero));
+ }
+
+ [Fact]
+ public void ParserOutputsZeroLengthCodeSpanIfEofReachedAfterAtSign()
+ {
+ ParseBlockTest(String.Empty,
+ new ExpressionBlock(
+ Factory.EmptyVB().AsImplicitExpression(KeywordSet).Accepts(AcceptedCharacters.NonWhiteSpace)),
+ new RazorError(
+ RazorResources.ParseError_Unexpected_EndOfFile_At_Start_Of_CodeBlock,
+ SourceLocation.Zero));
+ }
+
+ [Fact]
+ public void ParserOutputsZeroLengthCodeSpanIfOnlyWhitespaceFoundAfterAtSign()
+ {
+ ParseBlockTest(" ",
+ new ExpressionBlock(
+ Factory.EmptyVB().AsImplicitExpression(KeywordSet).Accepts(AcceptedCharacters.NonWhiteSpace)),
+ new RazorError(
+ RazorResources.ParseError_Unexpected_WhiteSpace_At_Start_Of_CodeBlock_VB,
+ SourceLocation.Zero));
+ }
+
+ [Fact]
+ public void ParserOutputsZeroLengthCodeSpanIfInvalidCharacterFoundAfterAtSign()
+ {
+ ParseBlockTest("!!!",
+ new ExpressionBlock(
+ Factory.EmptyVB().AsImplicitExpression(KeywordSet).Accepts(AcceptedCharacters.NonWhiteSpace)),
+ new RazorError(
+ String.Format(RazorResources.ParseError_Unexpected_Character_At_Start_Of_CodeBlock_VB, "!"),
+ SourceLocation.Zero));
+ }
+
+ [Theory]
+ [InlineData("Code", "End Code", true, true)]
+ [InlineData("Do", "Loop", false, false)]
+ [InlineData("While", "End While", false, false)]
+ [InlineData("If", "End If", false, false)]
+ [InlineData("Select Case", "End Select", false, false)]
+ [InlineData("For", "Next", false, false)]
+ [InlineData("Try", "End Try", false, false)]
+ [InlineData("With", "End With", false, false)]
+ [InlineData("Using", "End Using", false, false)]
+ public void EofBlock(string keyword, string expectedTerminator, bool autoComplete, bool keywordIsMetaCode)
+ {
+ EofBlockCore(keyword, expectedTerminator, autoComplete, BlockType.Statement, keywordIsMetaCode, c => c.AsStatement());
+ }
+
+ [Fact]
+ public void EofFunctionsBlock()
+ {
+ EofBlockCore("Functions", "End Functions", true, BlockType.Functions, true, c => c.AsFunctionsBody());
+ }
+
+ private void EofBlockCore(string keyword, string expectedTerminator, bool autoComplete, BlockType blockType, bool keywordIsMetaCode, Func<UnclassifiedCodeSpanConstructor, SpanConstructor> classifier)
+ {
+ BlockBuilder expected = new BlockBuilder();
+ expected.Type = blockType;
+ if (keywordIsMetaCode)
+ {
+ expected.Children.Add(Factory.MetaCode(keyword).Accepts(AcceptedCharacters.None));
+ expected.Children.Add(
+ classifier(Factory.EmptyVB())
+ .With((SpanEditHandler)(
+ autoComplete ?
+ new AutoCompleteEditHandler(CSharpLanguageCharacteristics.Instance.TokenizeString) { AutoCompleteString = expectedTerminator } :
+ SpanEditHandler.CreateDefault())));
+ }
+ else
+ {
+ expected.Children.Add(
+ classifier(Factory.Code(keyword))
+ .With((SpanEditHandler)(
+ autoComplete ?
+ new AutoCompleteEditHandler(CSharpLanguageCharacteristics.Instance.TokenizeString) { AutoCompleteString = expectedTerminator } :
+ SpanEditHandler.CreateDefault())));
+ }
+
+ ParseBlockTest(keyword,
+ expected.Build(),
+ new RazorError(
+ String.Format(RazorResources.ParseError_BlockNotTerminated, keyword, expectedTerminator),
+ SourceLocation.Zero));
+ }
+
+ [Theory]
+ [InlineData("Code", "End Code", true)]
+ [InlineData("Do", "Loop", false)]
+ [InlineData("While", "End While", false)]
+ [InlineData("If", "End If", false)]
+ [InlineData("Select Case", "End Select", false)]
+ [InlineData("For", "Next", false)]
+ [InlineData("Try", "End Try", false)]
+ [InlineData("With", "End With", false)]
+ [InlineData("Using", "End Using", false)]
+ public void UnterminatedBlock(string keyword, string expectedTerminator, bool keywordIsMetaCode)
+ {
+ UnterminatedBlockCore(keyword, expectedTerminator, BlockType.Statement, keywordIsMetaCode, c => c.AsStatement());
+ }
+
+ [Fact]
+ public void UnterminatedFunctionsBlock()
+ {
+ UnterminatedBlockCore("Functions", "End Functions", BlockType.Functions, true, c => c.AsFunctionsBody());
+ }
+
+ private void UnterminatedBlockCore(string keyword, string expectedTerminator, BlockType blockType, bool keywordIsMetaCode, Func<UnclassifiedCodeSpanConstructor, SpanConstructor> classifier)
+ {
+ const string blockBody = @"
+ ' This block is not correctly terminated!";
+
+ BlockBuilder expected = new BlockBuilder();
+ expected.Type = blockType;
+ if (keywordIsMetaCode)
+ {
+ expected.Children.Add(Factory.MetaCode(keyword).Accepts(AcceptedCharacters.None));
+ expected.Children.Add(classifier(Factory.Code(blockBody)));
+ }
+ else
+ {
+ expected.Children.Add(classifier(Factory.Code(keyword + blockBody)));
+ }
+
+ ParseBlockTest(keyword + blockBody,
+ expected.Build(),
+ new RazorError(
+ String.Format(RazorResources.ParseError_BlockNotTerminated, keyword, expectedTerminator),
+ SourceLocation.Zero));
+ }
+ }
+}
diff --git a/test/System.Web.Razor.Test/Parser/VB/VBExitStatementTest.cs b/test/System.Web.Razor.Test/Parser/VB/VBExitStatementTest.cs
new file mode 100644
index 00000000..c7968f58
--- /dev/null
+++ b/test/System.Web.Razor.Test/Parser/VB/VBExitStatementTest.cs
@@ -0,0 +1,94 @@
+using System.Web.Razor.Parser;
+using System.Web.Razor.Parser.SyntaxTree;
+using System.Web.Razor.Test.Framework;
+using Xunit;
+
+namespace System.Web.Razor.Test.Parser.VB
+{
+ // VB Exit Statement: http://msdn.microsoft.com/en-us/library/t2at9t47.aspx
+ public class VBExitStatementTest : VBHtmlCodeParserTestBase
+ {
+ [Fact]
+ public void VB_Do_Statement_With_Exit()
+ {
+ ParseBlockTest(@"@Do While True
+ Exit Do
+Loop
+' Not in the block!",
+ new StatementBlock(
+ Factory.CodeTransition(SyntaxConstants.TransitionString)
+ .Accepts(AcceptedCharacters.None),
+ Factory.Code("Do While True\r\n Exit Do\r\nLoop\r\n")
+ .AsStatement()
+ .Accepts(AcceptedCharacters.AnyExceptNewline)));
+ }
+
+ [Fact]
+ public void VB_For_Statement_With_Exit()
+ {
+ ParseBlockTest(@"@For i = 1 To 12
+ Exit For
+Next i
+' Not in the block!",
+ new StatementBlock(
+ Factory.CodeTransition(SyntaxConstants.TransitionString)
+ .Accepts(AcceptedCharacters.None),
+ Factory.Code("For i = 1 To 12\r\n Exit For\r\nNext i\r\n")
+ .AsStatement()
+ .Accepts(AcceptedCharacters.AnyExceptNewline)));
+ }
+
+ [Fact]
+ public void VB_Select_Statement_With_Exit()
+ {
+ ParseBlockTest(@"@Select Case Foo
+ Case 1
+ Exit Select
+ Case 2
+ Exit Select
+End Select
+' Not in the block!",
+ new StatementBlock(
+ Factory.CodeTransition(SyntaxConstants.TransitionString)
+ .Accepts(AcceptedCharacters.None),
+ Factory.Code("Select Case Foo\r\n Case 1\r\n Exit Select\r\n Case 2\r\n Exit Select\r\nEnd Select\r\n")
+ .AsStatement()
+ .Accepts(AcceptedCharacters.None)));
+ }
+
+ [Fact]
+ public void VB_Try_Statement_With_Exit()
+ {
+ ParseBlockTest(@"@Try
+ Foo()
+ Exit Try
+Catch Bar
+ Throw Bar
+Finally
+ Baz()
+End Try
+' Not in the block!",
+ new StatementBlock(
+ Factory.CodeTransition(SyntaxConstants.TransitionString)
+ .Accepts(AcceptedCharacters.None),
+ Factory.Code("Try\r\n Foo()\r\n Exit Try\r\nCatch Bar\r\n Throw Bar\r\nFinally\r\n Baz()\r\nEnd Try\r\n")
+ .AsStatement()
+ .Accepts(AcceptedCharacters.None)));
+ }
+
+ [Fact]
+ public void VB_While_Statement_With_Exit()
+ {
+ ParseBlockTest(@"@While True
+ Exit While
+End While
+' Not in the block!",
+ new StatementBlock(
+ Factory.CodeTransition(SyntaxConstants.TransitionString)
+ .Accepts(AcceptedCharacters.None),
+ Factory.Code("While True\r\n Exit While\r\nEnd While\r\n")
+ .AsStatement()
+ .Accepts(AcceptedCharacters.None)));
+ }
+ }
+}
diff --git a/test/System.Web.Razor.Test/Parser/VB/VBExplicitExpressionTest.cs b/test/System.Web.Razor.Test/Parser/VB/VBExplicitExpressionTest.cs
new file mode 100644
index 00000000..bd41e3e4
--- /dev/null
+++ b/test/System.Web.Razor.Test/Parser/VB/VBExplicitExpressionTest.cs
@@ -0,0 +1,20 @@
+using System.Web.Razor.Parser.SyntaxTree;
+using System.Web.Razor.Test.Framework;
+using Xunit;
+
+namespace System.Web.Razor.Test.Parser.VB
+{
+ public class VBExplicitExpressionTest : VBHtmlCodeParserTestBase
+ {
+ [Fact]
+ public void VB_Simple_ExplicitExpression()
+ {
+ ParseBlockTest("@(foo)",
+ new ExpressionBlock(
+ Factory.CodeTransition(),
+ Factory.MetaCode("(").Accepts(AcceptedCharacters.None),
+ Factory.Code("foo").AsExpression(),
+ Factory.MetaCode(")").Accepts(AcceptedCharacters.None)));
+ }
+ }
+}
diff --git a/test/System.Web.Razor.Test/Parser/VB/VBExpressionTest.cs b/test/System.Web.Razor.Test/Parser/VB/VBExpressionTest.cs
new file mode 100644
index 00000000..6b29123b
--- /dev/null
+++ b/test/System.Web.Razor.Test/Parser/VB/VBExpressionTest.cs
@@ -0,0 +1,109 @@
+using System.Web.Razor.Parser.SyntaxTree;
+using System.Web.Razor.Resources;
+using System.Web.Razor.Test.Framework;
+using System.Web.Razor.Text;
+using Xunit;
+using Xunit.Extensions;
+
+namespace System.Web.Razor.Test.Parser.VB
+{
+ public class VBExpressionTest : VBHtmlCodeParserTestBase
+ {
+ [Fact]
+ public void ParseBlockCorrectlyHandlesCodeBlockInBodyOfExplicitExpressionDueToUnclosedExpression()
+ {
+ ParseBlockTest(@"(
+@Code
+ Dim foo = bar
+End Code",
+ new ExpressionBlock(
+ Factory.MetaCode("(").Accepts(AcceptedCharacters.None),
+ Factory.EmptyVB().AsExpression()),
+ new RazorError(
+ String.Format(
+ RazorResources.ParseError_Expected_EndOfBlock_Before_EOF,
+ RazorResources.BlockName_ExplicitExpression,
+ ")", "("),
+ SourceLocation.Zero));
+ }
+
+ [Fact]
+ public void ParseBlockAcceptsNonEnglishCharactersThatAreValidIdentifiers()
+ {
+ ImplicitExpressionTest("हळूँजद॔.", "हळूँजद॔");
+ }
+
+ [Fact]
+ public void ParseBlockDoesNotTreatXmlAxisPropertyAsTransitionToMarkup()
+ {
+ SingleSpanBlockTest(
+ @"If foo Is Nothing Then
+ Dim bar As XElement
+ Dim foo = bar.<foo>
+End If",
+ BlockType.Statement,
+ SpanKind.Code,
+ acceptedCharacters: AcceptedCharacters.None);
+ }
+
+ [Fact]
+ public void ParseBlockDoesNotTreatXmlAttributePropertyAsTransitionToMarkup()
+ {
+ SingleSpanBlockTest(
+ @"If foo Is Nothing Then
+ Dim bar As XElement
+ Dim foo = bar.@foo
+End If",
+ BlockType.Statement,
+ SpanKind.Code,
+ acceptedCharacters: AcceptedCharacters.None);
+ }
+
+ [Fact]
+ public void ParseBlockSupportsSimpleImplicitExpression()
+ {
+ ImplicitExpressionTest("Foo");
+ }
+
+ [Fact]
+ public void ParseBlockSupportsImplicitExpressionWithDots()
+ {
+ ImplicitExpressionTest("Foo.Bar.Baz");
+ }
+
+ [Fact]
+ public void ParseBlockSupportsImplicitExpressionWithParens()
+ {
+ ImplicitExpressionTest("Foo().Bar().Baz()");
+ }
+
+ [Fact]
+ public void ParseBlockSupportsImplicitExpressionWithStuffInParens()
+ {
+ ImplicitExpressionTest("Foo().Bar(sdfkhj sdfksdfjs \")\" sjdfkjsdf).Baz()");
+ }
+
+ [Fact]
+ public void ParseBlockSupportsImplicitExpressionWithCommentInParens()
+ {
+ ImplicitExpressionTest("Foo().Bar(sdfkhj sdfksdfjs \")\" '))))))))\r\nsjdfkjsdf).Baz()");
+ }
+
+ [Theory]
+ [InlineData("Foo")]
+ [InlineData("Foo(Of String).Bar(1, 2, 3).Biz")]
+ [InlineData("Foo(Of String).Bar(\")\").Biz")]
+ [InlineData("Foo(Of String).Bar(\"Foo\"\"Bar)\"\"Baz\").Biz")]
+ [InlineData("\"foo\r\nbar")]
+ [InlineData("Foo.Bar. _\r\nREM )\r\nBaz()\r\n")]
+ [InlineData("Foo.Bar. _\r\n' )\r\nBaz()\r\n")]
+ public void ValidExplicitExpressions(string body)
+ {
+ ParseBlockTest("(" + body + ")",
+ new ExpressionBlock(
+ Factory.MetaCode("(").Accepts(AcceptedCharacters.None),
+ Factory.Code(body).AsExpression(),
+ Factory.MetaCode(")").Accepts(AcceptedCharacters.None)));
+ }
+ }
+}
diff --git a/test/System.Web.Razor.Test/Parser/VB/VBExpressionsInCodeTest.cs b/test/System.Web.Razor.Test/Parser/VB/VBExpressionsInCodeTest.cs
new file mode 100644
index 00000000..8be07bca
--- /dev/null
+++ b/test/System.Web.Razor.Test/Parser/VB/VBExpressionsInCodeTest.cs
@@ -0,0 +1,119 @@
+using System.Web.Razor.Parser;
+using System.Web.Razor.Parser.SyntaxTree;
+using System.Web.Razor.Resources;
+using System.Web.Razor.Test.Framework;
+using Xunit;
+using Xunit.Extensions;
+
+namespace System.Web.Razor.Test.Parser.VB
+{
+ public class VBExpressionsInCodeTest : VBHtmlCodeParserTestBase
+ {
+ [Fact]
+ public void InnerImplicitExpressionWithOnlySingleAtAcceptsSingleSpaceOrNewlineAtDesignTime()
+ {
+ ParseBlockTest(@"Code
+ @
+End Code",
+ new StatementBlock(
+ Factory.MetaCode("Code").Accepts(AcceptedCharacters.None),
+ Factory.Code("\r\n ").AsStatement(),
+ new ExpressionBlock(
+ Factory.CodeTransition(),
+ Factory.EmptyVB()
+ .AsImplicitExpression(VBCodeParser.DefaultKeywords, acceptTrailingDot: true)
+ .Accepts(AcceptedCharacters.NonWhiteSpace)),
+ Factory.Code("\r\n").AsStatement(),
+ Factory.MetaCode("End Code").Accepts(AcceptedCharacters.None)),
+ designTimeParser: true,
+ expectedErrors: new[]
+ {
+ new RazorError(RazorResources.ParseError_Unexpected_WhiteSpace_At_Start_Of_CodeBlock_VB, 11, 1, 5)
+ });
+ }
+
+ [Fact]
+ public void InnerImplicitExpressionDoesNotAcceptDotAfterAt()
+ {
+ ParseBlockTest(@"Code
+ @.
+End Code",
+ new StatementBlock(
+ Factory.MetaCode("Code").Accepts(AcceptedCharacters.None),
+ Factory.Code("\r\n ").AsStatement(),
+ new ExpressionBlock(
+ Factory.CodeTransition(),
+ Factory.EmptyVB()
+ .AsImplicitExpression(VBCodeParser.DefaultKeywords, acceptTrailingDot: true)
+ .Accepts(AcceptedCharacters.NonWhiteSpace)),
+ Factory.Code(".\r\n").AsStatement(),
+ Factory.MetaCode("End Code").Accepts(AcceptedCharacters.None)),
+ designTimeParser: true,
+ expectedErrors: new[]
+ {
+ new RazorError(
+ String.Format(RazorResources.ParseError_Unexpected_Character_At_Start_Of_CodeBlock_VB, "."),
+ 11, 1, 5)
+ });
+ }
+
+ [Theory]
+ [InlineData("Foo.Bar.", true)]
+ [InlineData("Foo", true)]
+ [InlineData("Foo.Bar.Baz", true)]
+ [InlineData("Foo().Bar().Baz()", true)]
+ [InlineData("Foo().Bar(sdfkhj sdfksdfjs \")\" sjdfkjsdf).Baz()", true)]
+ [InlineData("Foo().Bar(sdfkhj sdfksdfjs \")\" '))))))))\r\nsjdfkjsdf).Baz()", true)]
+ [InlineData("Foo", false)]
+ [InlineData("Foo(Of String).Bar(1, 2, 3).Biz", false)]
+ [InlineData("Foo(Of String).Bar(\")\").Biz", false)]
+ [InlineData("Foo(Of String).Bar(\"Foo\"\"Bar)\"\"Baz\").Biz", false)]
+ [InlineData("Foo.Bar. _\r\nREM )\r\nBaz()\r\n", false)]
+ [InlineData("Foo.Bar. _\r\n' )\r\nBaz()\r\n", false)]
+ public void ExpressionInCode(string expression, bool isImplicit)
+ {
+ ExpressionBlock expressionBlock;
+ if (isImplicit)
+ {
+ expressionBlock =
+ new ExpressionBlock(
+ Factory.CodeTransition(),
+ Factory.Code(expression)
+ .AsImplicitExpression(VBCodeParser.DefaultKeywords, acceptTrailingDot: true)
+ .Accepts(AcceptedCharacters.NonWhiteSpace));
+ }
+ else
+ {
+ expressionBlock =
+ new ExpressionBlock(
+ Factory.CodeTransition(),
+ Factory.MetaCode("(").Accepts(AcceptedCharacters.None),
+ Factory.Code(expression).AsExpression(),
+ Factory.MetaCode(")").Accepts(AcceptedCharacters.None));
+ }
+
+ string code;
+ if (isImplicit)
+ {
+ code = @"If foo IsNot Nothing Then
+ @" + expression + @"
+End If";
+ }
+ else
+ {
+ code = @"If foo IsNot Nothing Then
+ @(" + expression + @")
+End If";
+ }
+
+ ParseBlockTest(code,
+ new StatementBlock(
+ Factory.Code("If foo IsNot Nothing Then\r\n ")
+ .AsStatement(),
+ expressionBlock,
+ Factory.Code("\r\nEnd If")
+ .AsStatement()
+ .Accepts(AcceptedCharacters.None)));
+ }
+ }
+}
diff --git a/test/System.Web.Razor.Test/Parser/VB/VBHelperTest.cs b/test/System.Web.Razor.Test/Parser/VB/VBHelperTest.cs
new file mode 100644
index 00000000..9b0f501f
--- /dev/null
+++ b/test/System.Web.Razor.Test/Parser/VB/VBHelperTest.cs
@@ -0,0 +1,322 @@
+using System.Web.Razor.Generator;
+using System.Web.Razor.Parser;
+using System.Web.Razor.Parser.SyntaxTree;
+using System.Web.Razor.Resources;
+using System.Web.Razor.Test.Framework;
+using System.Web.Razor.Text;
+using Xunit;
+
+namespace System.Web.Razor.Test.Parser.VB
+{
+ public class VBHelperTest : VBHtmlMarkupParserTestBase
+ {
+ [Fact]
+ public void ParseHelperOutputsErrorButContinuesIfLParenFoundAfterHelperKeyword()
+ {
+ ParseDocumentTest("@Helper ()",
+ new MarkupBlock(
+ Factory.EmptyHtml(),
+ new HelperBlock(new HelperCodeGenerator(new LocationTagged<string>("()", 8, 0, 8), headerComplete: true),
+ Factory.CodeTransition(),
+ Factory.MetaCode("Helper ").Accepts(AcceptedCharacters.None),
+ Factory.Code("()").Hidden().AutoCompleteWith(SyntaxConstants.VB.EndHelperKeyword),
+ new StatementBlock())),
+ new RazorError(
+ String.Format(
+ RazorResources.ParseError_Unexpected_Character_At_Helper_Name_Start,
+ String.Format(RazorResources.ErrorComponent_Character, "(")),
+ 8, 0, 8),
+ new RazorError(
+ String.Format(RazorResources.ParseError_BlockNotTerminated, "Helper", "End Helper"),
+ 1, 0, 1));
+ }
+
+ [Fact]
+ public void ParseHelperStatementOutputsMarkerHelperHeaderSpanOnceKeywordComplete()
+ {
+ ParseDocumentTest("@Helper ",
+ new MarkupBlock(
+ Factory.EmptyHtml(),
+ new HelperBlock(new HelperCodeGenerator(new LocationTagged<string>(String.Empty, 8, 0, 8), headerComplete: false),
+ Factory.CodeTransition(),
+ Factory.MetaCode("Helper ").Accepts(AcceptedCharacters.None),
+ Factory.EmptyVB().Hidden())),
+ new RazorError(
+ String.Format(RazorResources.ParseError_Unexpected_Character_At_Helper_Name_Start, RazorResources.ErrorComponent_EndOfFile),
+ 8, 0, 8));
+ }
+
+ [Fact]
+ public void ParseHelperStatementMarksHelperSpanAsCanGrowIfMissingTrailingSpace()
+ {
+ ParseDocumentTest("@Helper",
+ new MarkupBlock(
+ Factory.EmptyHtml(),
+ new HelperBlock(
+ Factory.CodeTransition(),
+ Factory.MetaCode("Helper"))),
+ new RazorError(
+ String.Format(RazorResources.ParseError_Unexpected_Character_At_Helper_Name_Start, RazorResources.ErrorComponent_EndOfFile),
+ 7, 0, 7));
+ }
+
+ [Fact]
+ public void ParseHelperStatementTerminatesEarlyIfHeaderNotComplete()
+ {
+ ParseDocumentTest(@"@Helper
+@Helper",
+ new MarkupBlock(
+ Factory.EmptyHtml(),
+ new HelperBlock(
+ Factory.CodeTransition(),
+ Factory.MetaCode("Helper\r\n").Accepts(AcceptedCharacters.None),
+ Factory.EmptyVB().Hidden()),
+ new HelperBlock(
+ Factory.CodeTransition(),
+ Factory.MetaCode("Helper"))),
+ designTimeParser: true,
+ expectedErrors: new[]
+ {
+ new RazorError(
+ String.Format(
+ RazorResources.ParseError_Unexpected_Character_At_Helper_Name_Start,
+ String.Format(RazorResources.ErrorComponent_Character, "@")),
+ 9, 1, 0),
+ new RazorError(
+ String.Format(
+ RazorResources.ParseError_Unexpected_Character_At_Helper_Name_Start,
+ RazorResources.ErrorComponent_EndOfFile),
+ 16, 1, 7)
+ });
+ }
+
+ [Fact]
+ public void ParseHelperStatementTerminatesEarlyIfHeaderNotCompleteWithSpace()
+ {
+ ParseDocumentTest(@"@Helper @Helper",
+ new MarkupBlock(
+ Factory.EmptyHtml(),
+ new HelperBlock(new HelperCodeGenerator(new LocationTagged<string>(String.Empty, 8, 0, 8), headerComplete: false),
+ Factory.CodeTransition(),
+ Factory.MetaCode(@"Helper ").Accepts(AcceptedCharacters.None),
+ Factory.EmptyVB().Hidden()),
+ new HelperBlock(
+ Factory.CodeTransition(),
+ Factory.MetaCode("Helper").Accepts(AcceptedCharacters.Any))),
+ designTimeParser: true,
+ expectedErrors: new[]
+ {
+ new RazorError(
+ String.Format(
+ RazorResources.ParseError_Unexpected_Character_At_Helper_Name_Start,
+ String.Format(RazorResources.ErrorComponent_Character, "@")),
+ 8, 0, 8),
+ new RazorError(
+ String.Format(
+ RazorResources.ParseError_Unexpected_Character_At_Helper_Name_Start,
+ RazorResources.ErrorComponent_EndOfFile),
+ 15, 0, 15)
+ });
+ }
+
+ [Fact]
+ public void ParseHelperStatementAllowsDifferentlyCasedEndHelperKeyword()
+ {
+ ParseDocumentTest(@"@Helper Foo()
+end helper",
+ new MarkupBlock(
+ Factory.EmptyHtml(),
+ new HelperBlock(new HelperCodeGenerator(new LocationTagged<string>("Foo()", 8, 0, 8), headerComplete: true),
+ Factory.CodeTransition(),
+ Factory.MetaCode("Helper ").Accepts(AcceptedCharacters.None),
+ Factory.Code("Foo()").Hidden(),
+ new StatementBlock(
+ Factory.Code("\r\n").AsStatement(),
+ Factory.MetaCode("end helper").Accepts(AcceptedCharacters.None))),
+ Factory.EmptyHtml()));
+ }
+
+ [Fact]
+ public void ParseHelperStatementCapturesWhitespaceToEndOfLineIfHelperStatementMissingName()
+ {
+ ParseDocumentTest(@"@Helper
+ ",
+ new MarkupBlock(
+ Factory.EmptyHtml(),
+ new HelperBlock(new HelperCodeGenerator(new LocationTagged<string>(" ", 8, 0, 8), headerComplete: false),
+ Factory.CodeTransition(),
+ Factory.MetaCode("Helper ").Accepts(AcceptedCharacters.None),
+ Factory.Code(" ").Hidden()),
+ Factory.Markup("\r\n ")),
+ new RazorError(
+ String.Format(
+ RazorResources.ParseError_Unexpected_Character_At_Helper_Name_Start,
+ RazorResources.ErrorComponent_Newline),
+ 30, 0, 30));
+ }
+
+ [Fact]
+ public void ParseHelperStatementCapturesWhitespaceToEndOfLineIfHelperStatementMissingOpenParen()
+ {
+ ParseDocumentTest(@"@Helper Foo
+ ",
+ new MarkupBlock(
+ Factory.EmptyHtml(),
+ new HelperBlock(new HelperCodeGenerator(new LocationTagged<string>("Foo ", 8, 0, 8), headerComplete: false),
+ Factory.CodeTransition(),
+ Factory.MetaCode("Helper ").Accepts(AcceptedCharacters.None),
+ Factory.Code("Foo ").Hidden()),
+ Factory.Markup("\r\n ")),
+ new RazorError(
+ String.Format(RazorResources.ParseError_MissingCharAfterHelperName, "("),
+ 15, 0, 15));
+ }
+
+ [Fact]
+ public void ParseHelperStatementCapturesAllContentToEndOfFileIfHelperStatementMissingCloseParenInParameterList()
+ {
+ ParseDocumentTest(@"@Helper Foo(Foo Bar
+Biz
+Boz",
+ new MarkupBlock(
+ Factory.EmptyHtml(),
+ new HelperBlock(new HelperCodeGenerator(new LocationTagged<string>("Foo(Foo Bar\r\nBiz\r\nBoz", 8, 0, 8), headerComplete: false),
+ Factory.CodeTransition(),
+ Factory.MetaCode("Helper ").Accepts(AcceptedCharacters.None),
+ Factory.Code("Foo(Foo Bar\r\nBiz\r\nBoz").Hidden())),
+ new RazorError(RazorResources.ParseError_UnterminatedHelperParameterList, 11, 0, 11));
+ }
+
+ [Fact]
+ public void ParseHelperStatementCapturesWhitespaceToEndOfLineIfHelperStatementMissingOpenBraceAfterParameterList()
+ {
+ ParseDocumentTest(@"@Helper Foo(foo as String)
+",
+ new MarkupBlock(
+ Factory.EmptyHtml(),
+ new HelperBlock(new HelperCodeGenerator(new LocationTagged<string>("Foo(foo as String)", 8, 0, 8), headerComplete: true),
+ Factory.CodeTransition(),
+ Factory.MetaCode("Helper ").Accepts(AcceptedCharacters.None),
+ Factory.Code("Foo(foo as String)")
+ .Hidden()
+ .AutoCompleteWith(SyntaxConstants.VB.EndHelperKeyword),
+ new StatementBlock(
+ Factory.Code(" \r\n").AsStatement()))),
+ new RazorError(
+ String.Format(RazorResources.ParseError_BlockNotTerminated, "Helper", "End Helper"),
+ 1, 0, 1));
+ }
+
+ [Fact]
+ public void ParseHelperStatementContinuesParsingHelperUntilEOF()
+ {
+ ParseDocumentTest(@"@Helper Foo(foo as String)
+ @<p>Foo</p>",
+ new MarkupBlock(
+ Factory.EmptyHtml(),
+ new HelperBlock(new HelperCodeGenerator(new LocationTagged<string>("Foo(foo as String)", 8, 0, 8), headerComplete: true),
+ Factory.CodeTransition(),
+ Factory.MetaCode("Helper ").Accepts(AcceptedCharacters.None),
+ Factory.Code("Foo(foo as String)")
+ .Hidden()
+ .AutoCompleteWith(SyntaxConstants.VB.EndHelperKeyword),
+ new StatementBlock(
+ Factory.Code("\r\n").AsStatement(),
+ new MarkupBlock(
+ Factory.Markup(" "),
+ Factory.MarkupTransition(),
+ Factory.Markup("<p>Foo</p>").Accepts(AcceptedCharacters.None)),
+ Factory.EmptyVB().AsStatement()))),
+ new RazorError(
+ String.Format(RazorResources.ParseError_BlockNotTerminated, "Helper", "End Helper"),
+ 1, 0, 1));
+ }
+
+ [Fact]
+ public void ParseHelperStatementCorrectlyParsesHelperWithEmbeddedCode()
+ {
+ ParseDocumentTest(@"@Helper Foo(foo as String, bar as String)
+ @<p>@foo</p>
+End Helper",
+ new MarkupBlock(
+ Factory.EmptyHtml(),
+ new HelperBlock(new HelperCodeGenerator(new LocationTagged<string>("Foo(foo as String, bar as String)", 8, 0, 8), headerComplete: true),
+ Factory.CodeTransition(),
+ Factory.MetaCode("Helper ").Accepts(AcceptedCharacters.None),
+ Factory.Code("Foo(foo as String, bar as String)").Hidden(),
+ new StatementBlock(
+ Factory.Code("\r\n").AsStatement(),
+ new MarkupBlock(
+ Factory.Markup(" "),
+ Factory.MarkupTransition(),
+ Factory.Markup("<p>"),
+ new ExpressionBlock(
+ Factory.CodeTransition(),
+ Factory.Code("foo")
+ .AsImplicitExpression(VBCodeParser.DefaultKeywords)
+ .Accepts(AcceptedCharacters.NonWhiteSpace)),
+ Factory.Markup("</p>\r\n").Accepts(AcceptedCharacters.None)),
+ Factory.EmptyVB().AsStatement(),
+ Factory.MetaCode("End Helper").Accepts(AcceptedCharacters.None))),
+ Factory.EmptyHtml()));
+ }
+
+ [Fact]
+ public void ParseHelperStatementGivesWhitespaceAfterCloseParenToMarkup()
+ {
+ ParseDocumentTest(@"@Helper Foo(string foo)
+ ",
+ new MarkupBlock(
+ Factory.EmptyHtml(),
+ new HelperBlock(new HelperCodeGenerator(new LocationTagged<string>("Foo(string foo)", 8, 0, 8), headerComplete: true),
+ Factory.CodeTransition(),
+ Factory.MetaCode("Helper ").Accepts(AcceptedCharacters.None),
+ Factory.Code("Foo(string foo)")
+ .Hidden()
+ .AutoCompleteWith(SyntaxConstants.VB.EndHelperKeyword),
+ new StatementBlock(
+ Factory.Code(" \r\n ").AsStatement()))),
+ designTimeParser: true,
+ expectedErrors:
+ new RazorError(
+ String.Format(
+ RazorResources.ParseError_BlockNotTerminated,
+ "Helper", "End Helper"),
+ 1, 0, 1));
+ }
+
+ [Fact]
+ public void ParseHelperAcceptsNestedHelpersButOutputsError()
+ {
+ ParseDocumentTest(@"@Helper Foo(string foo)
+ @Helper Bar(string baz)
+ End Helper
+End Helper",
+ new MarkupBlock(
+ Factory.EmptyHtml(),
+ new HelperBlock(new HelperCodeGenerator(new LocationTagged<string>("Foo(string foo)", 8, 0, 8), headerComplete: true),
+ Factory.CodeTransition(),
+ Factory.MetaCode("Helper ").Accepts(AcceptedCharacters.None),
+ Factory.Code("Foo(string foo)").Hidden(),
+ new StatementBlock(
+ Factory.Code("\r\n ").AsStatement(),
+ new HelperBlock(new HelperCodeGenerator(new LocationTagged<string>("Bar(string baz)", 37, 1, 12), headerComplete: true),
+ Factory.CodeTransition(),
+ Factory.MetaCode("Helper ").Accepts(AcceptedCharacters.None),
+ Factory.Code("Bar(string baz)").Hidden(),
+ new StatementBlock(
+ Factory.Code("\r\n ").AsStatement(),
+ Factory.MetaCode("End Helper").Accepts(AcceptedCharacters.None))),
+ Factory.Code("\r\n").AsStatement(),
+ Factory.MetaCode("End Helper").Accepts(AcceptedCharacters.None))),
+ Factory.EmptyHtml()),
+ designTimeParser: true,
+ expectedErrors: new[]
+ {
+ new RazorError(
+ RazorResources.ParseError_Helpers_Cannot_Be_Nested,
+ 30, 1, 5)
+ });
+ }
+ }
+}
diff --git a/test/System.Web.Razor.Test/Parser/VB/VBHtmlDocumentTest.cs b/test/System.Web.Razor.Test/Parser/VB/VBHtmlDocumentTest.cs
new file mode 100644
index 00000000..8bf1a066
--- /dev/null
+++ b/test/System.Web.Razor.Test/Parser/VB/VBHtmlDocumentTest.cs
@@ -0,0 +1,248 @@
+using System.Web.Razor.Parser.SyntaxTree;
+using System.Web.Razor.Test.Framework;
+using System.Web.Razor.Tokenizer.Symbols;
+using Xunit;
+
+namespace System.Web.Razor.Test.Parser.VB
+{
+ public class VBHtmlDocumentTest : VBHtmlMarkupParserTestBase
+ {
+ [Fact]
+ public void BlockCommentInMarkupDocumentIsHandledCorrectly()
+ {
+ ParseDocumentTest(@"<ul>
+ @* This is a block comment </ul> *@ foo",
+ new MarkupBlock(
+ Factory.Markup("<ul>\r\n "),
+ new CommentBlock(
+ Factory.MarkupTransition(HtmlSymbolType.RazorCommentTransition),
+ Factory.MetaMarkup("*", HtmlSymbolType.RazorCommentStar).Accepts(AcceptedCharacters.None),
+ Factory.Comment(" This is a block comment </ul> ", HtmlSymbolType.RazorComment),
+ Factory.MetaMarkup("*", HtmlSymbolType.RazorCommentStar).Accepts(AcceptedCharacters.None),
+ Factory.MarkupTransition(HtmlSymbolType.RazorCommentTransition)),
+ Factory.Markup(" foo")));
+ }
+
+ [Fact]
+ public void BlockCommentInMarkupBlockIsHandledCorrectly()
+ {
+ ParseBlockTest(@"<ul>
+ @* This is a block comment </ul> *@ foo </ul>",
+ new MarkupBlock(
+ Factory.Markup("<ul>\r\n "),
+ new CommentBlock(
+ Factory.MarkupTransition(HtmlSymbolType.RazorCommentTransition),
+ Factory.MetaMarkup("*", HtmlSymbolType.RazorCommentStar).Accepts(AcceptedCharacters.None),
+ Factory.Comment(" This is a block comment </ul> ", HtmlSymbolType.RazorComment),
+ Factory.MetaMarkup("*", HtmlSymbolType.RazorCommentStar).Accepts(AcceptedCharacters.None),
+ Factory.MarkupTransition(HtmlSymbolType.RazorCommentTransition)),
+ Factory.Markup(" foo </ul>").Accepts(AcceptedCharacters.None)));
+ }
+
+ [Fact]
+ public void BlockCommentAtStatementStartInCodeBlockIsHandledCorrectly()
+ {
+ ParseDocumentTest(@"@If Request.IsAuthenticated Then
+ @* User is logged in! End If *@
+ Write(""Hello friend!"")
+End If",
+ new MarkupBlock(
+ Factory.EmptyHtml(),
+ new StatementBlock(
+ Factory.CodeTransition(),
+ Factory.Code("If Request.IsAuthenticated Then\r\n ").AsStatement(),
+ new CommentBlock(
+ Factory.CodeTransition(VBSymbolType.RazorCommentTransition),
+ Factory.MetaCode("*", VBSymbolType.RazorCommentStar).Accepts(AcceptedCharacters.None),
+ Factory.Comment(" User is logged in! End If ", VBSymbolType.RazorComment),
+ Factory.MetaCode("*", VBSymbolType.RazorCommentStar).Accepts(AcceptedCharacters.None),
+ Factory.CodeTransition(VBSymbolType.RazorCommentTransition)),
+ Factory.Code("\r\n Write(\"Hello friend!\")\r\nEnd If")
+ .AsStatement()
+ .Accepts(AcceptedCharacters.None)),
+ Factory.EmptyHtml()));
+ }
+
+ [Fact]
+ public void BlockCommentInStatementInCodeBlockIsHandledCorrectly()
+ {
+ ParseDocumentTest(@"@If Request.IsAuthenticated Then
+ Dim foo = @* User is logged in! End If *@ bar
+ Write(""Hello friend!"")
+End If",
+ new MarkupBlock(
+ Factory.EmptyHtml(),
+ new StatementBlock(
+ Factory.CodeTransition(),
+ Factory.Code("If Request.IsAuthenticated Then\r\n Dim foo = ").AsStatement(),
+ new CommentBlock(
+ Factory.CodeTransition(VBSymbolType.RazorCommentTransition),
+ Factory.MetaCode("*", VBSymbolType.RazorCommentStar).Accepts(AcceptedCharacters.None),
+ Factory.Comment(" User is logged in! End If ", VBSymbolType.RazorComment),
+ Factory.MetaCode("*", VBSymbolType.RazorCommentStar).Accepts(AcceptedCharacters.None),
+ Factory.CodeTransition(VBSymbolType.RazorCommentTransition)),
+ Factory.Code(" bar\r\n Write(\"Hello friend!\")\r\nEnd If")
+ .AsStatement()
+ .Accepts(AcceptedCharacters.None)),
+ Factory.EmptyHtml()));
+ }
+
+ [Fact]
+ public void BlockCommentInStringInCodeBlockIsIgnored()
+ {
+ ParseDocumentTest(@"@If Request.IsAuthenticated Then
+ Dim foo = ""@* User is logged in! End If *@ bar""
+ Write(""Hello friend!"")
+End If",
+ new MarkupBlock(
+ Factory.EmptyHtml(),
+ new StatementBlock(
+ Factory.CodeTransition(),
+ Factory.Code("If Request.IsAuthenticated Then\r\n Dim foo = \"@* User is logged in! End If *@ bar\"\r\n Write(\"Hello friend!\")\r\nEnd If")
+ .AsStatement()
+ .Accepts(AcceptedCharacters.None)),
+ Factory.EmptyHtml()));
+ }
+
+ [Fact]
+ public void BlockCommentInTickCommentInCodeBlockIsIgnored()
+ {
+ ParseDocumentTest(@"@If Request.IsAuthenticated Then
+ Dim foo = '@* User is logged in! End If *@ bar
+ Write(""Hello friend!"")
+End If",
+ new MarkupBlock(
+ Factory.EmptyHtml(),
+ new StatementBlock(
+ Factory.CodeTransition(),
+ Factory.Code("If Request.IsAuthenticated Then\r\n Dim foo = '@* User is logged in! End If *@ bar\r\n Write(\"Hello friend!\")\r\nEnd If")
+ .AsStatement()
+ .Accepts(AcceptedCharacters.None)),
+ Factory.EmptyHtml()));
+ }
+
+ [Fact]
+ public void BlockCommentInRemCommentInCodeBlockIsIgnored()
+ {
+ ParseDocumentTest(@"@If Request.IsAuthenticated Then
+ Dim foo = REM @* User is logged in! End If *@ bar
+ Write(""Hello friend!"")
+End If",
+ new MarkupBlock(
+ Factory.EmptyHtml(),
+ new StatementBlock(
+ Factory.CodeTransition(),
+ Factory.Code("If Request.IsAuthenticated Then\r\n Dim foo = REM @* User is logged in! End If *@ bar\r\n Write(\"Hello friend!\")\r\nEnd If")
+ .AsStatement()
+ .Accepts(AcceptedCharacters.None)),
+ Factory.EmptyHtml()));
+ }
+
+ [Fact]
+ public void BlockCommentInImplicitExpressionIsHandledCorrectly()
+ {
+ ParseDocumentTest("@Html.Foo@*bar*@",
+ new MarkupBlock(
+ Factory.EmptyHtml(),
+ new ExpressionBlock(
+ Factory.CodeTransition(),
+ Factory.Code("Html.Foo")
+ .AsImplicitExpression(KeywordSet)
+ .Accepts(AcceptedCharacters.NonWhiteSpace)),
+ Factory.EmptyHtml(),
+ new CommentBlock(
+ Factory.MarkupTransition(HtmlSymbolType.RazorCommentTransition),
+ Factory.MetaMarkup("*", HtmlSymbolType.RazorCommentStar).Accepts(AcceptedCharacters.None),
+ Factory.Comment("bar", HtmlSymbolType.RazorComment),
+ Factory.MetaMarkup("*", HtmlSymbolType.RazorCommentStar).Accepts(AcceptedCharacters.None),
+ Factory.MarkupTransition(HtmlSymbolType.RazorCommentTransition)),
+ Factory.EmptyHtml()));
+ }
+
+ [Fact]
+ public void BlockCommentAfterDotOfImplicitExpressionIsHandledCorrectly()
+ {
+ ParseDocumentTest("@Html.@*bar*@",
+ new MarkupBlock(
+ Factory.EmptyHtml(),
+ new ExpressionBlock(
+ Factory.CodeTransition(),
+ Factory.Code("Html")
+ .AsImplicitExpression(KeywordSet)
+ .Accepts(AcceptedCharacters.NonWhiteSpace)),
+ Factory.Markup("."),
+ new CommentBlock(
+ Factory.MarkupTransition(HtmlSymbolType.RazorCommentTransition),
+ Factory.MetaMarkup("*", HtmlSymbolType.RazorCommentStar).Accepts(AcceptedCharacters.None),
+ Factory.Comment("bar", HtmlSymbolType.RazorComment),
+ Factory.MetaMarkup("*", HtmlSymbolType.RazorCommentStar).Accepts(AcceptedCharacters.None),
+ Factory.MarkupTransition(HtmlSymbolType.RazorCommentTransition)),
+ Factory.EmptyHtml()));
+ }
+
+ [Fact]
+ public void BlockCommentInParensOfImplicitExpressionIsHandledCorrectly()
+ {
+ ParseDocumentTest("@Html.Foo(@*bar*@ 4)",
+ new MarkupBlock(
+ Factory.EmptyHtml(),
+ new ExpressionBlock(
+ Factory.CodeTransition(),
+ Factory.Code("Html.Foo(")
+ .AsImplicitExpression(KeywordSet)
+ .Accepts(AcceptedCharacters.Any),
+ new CommentBlock(
+ Factory.CodeTransition(VBSymbolType.RazorCommentTransition),
+ Factory.MetaCode("*", VBSymbolType.RazorCommentStar).Accepts(AcceptedCharacters.None),
+ Factory.Comment("bar", VBSymbolType.RazorComment),
+ Factory.MetaCode("*", VBSymbolType.RazorCommentStar).Accepts(AcceptedCharacters.None),
+ Factory.CodeTransition(VBSymbolType.RazorCommentTransition)),
+ Factory.Code(" 4)")
+ .AsImplicitExpression(KeywordSet)
+ .Accepts(AcceptedCharacters.NonWhiteSpace)),
+ Factory.EmptyHtml()));
+ }
+
+ [Fact]
+ public void BlockCommentInConditionIsHandledCorrectly()
+ {
+ ParseDocumentTest("@If @*bar*@ Then End If",
+ new MarkupBlock(
+ Factory.EmptyHtml(),
+ new StatementBlock(
+ Factory.CodeTransition(),
+ Factory.Code("If ").AsStatement(),
+ new CommentBlock(
+ Factory.CodeTransition(VBSymbolType.RazorCommentTransition),
+ Factory.MetaCode("*", VBSymbolType.RazorCommentStar).Accepts(AcceptedCharacters.None),
+ Factory.Comment("bar", VBSymbolType.RazorComment),
+ Factory.MetaCode("*", VBSymbolType.RazorCommentStar).Accepts(AcceptedCharacters.None),
+ Factory.CodeTransition(VBSymbolType.RazorCommentTransition)),
+ Factory.Code(" Then End If").AsStatement().Accepts(AcceptedCharacters.None)),
+ Factory.EmptyHtml()));
+ }
+
+ [Fact]
+ public void BlockCommentInExplicitExpressionIsHandledCorrectly()
+ {
+ ParseDocumentTest(@"@(1 + @*bar*@ 1)",
+ new MarkupBlock(
+ Factory.EmptyHtml(),
+ new ExpressionBlock(
+ Factory.CodeTransition(),
+ Factory.MetaCode("(").Accepts(AcceptedCharacters.None),
+ Factory.Code(@"1 + ").AsExpression(),
+ new CommentBlock(
+ Factory.CodeTransition(VBSymbolType.RazorCommentTransition),
+ Factory.MetaCode("*", VBSymbolType.RazorCommentStar).Accepts(AcceptedCharacters.None),
+ Factory.Comment("bar", VBSymbolType.RazorComment),
+ Factory.MetaCode("*", VBSymbolType.RazorCommentStar).Accepts(AcceptedCharacters.None),
+ Factory.CodeTransition(VBSymbolType.RazorCommentTransition)
+ ),
+ Factory.Code(" 1").AsExpression(),
+ Factory.MetaCode(")").Accepts(AcceptedCharacters.None)
+ ),
+ Factory.EmptyHtml()));
+ }
+ }
+}
diff --git a/test/System.Web.Razor.Test/Parser/VB/VBImplicitExpressionTest.cs b/test/System.Web.Razor.Test/Parser/VB/VBImplicitExpressionTest.cs
new file mode 100644
index 00000000..1ec7528d
--- /dev/null
+++ b/test/System.Web.Razor.Test/Parser/VB/VBImplicitExpressionTest.cs
@@ -0,0 +1,77 @@
+using System.Web.Razor.Parser;
+using System.Web.Razor.Parser.SyntaxTree;
+using System.Web.Razor.Test.Framework;
+using Xunit;
+
+namespace System.Web.Razor.Test.Parser.VB
+{
+ public class VBImplicitExpressionTest : VBHtmlCodeParserTestBase
+ {
+ [Fact]
+ public void VB_Simple_ImplicitExpression()
+ {
+ ParseBlockTest("@foo not-part-of-the-block",
+ new ExpressionBlock(
+ Factory.CodeTransition(),
+ Factory.Code("foo")
+ .AsImplicitExpression(VBCodeParser.DefaultKeywords)
+ .Accepts(AcceptedCharacters.NonWhiteSpace)));
+ }
+
+ [Fact]
+ public void VB_ImplicitExpression_With_Keyword_At_Start()
+ {
+ ParseBlockTest("@Partial",
+ new ExpressionBlock(
+ Factory.CodeTransition(),
+ Factory.Code("Partial")
+ .AsImplicitExpression(VBCodeParser.DefaultKeywords)
+ .Accepts(AcceptedCharacters.NonWhiteSpace)));
+ }
+
+ [Fact]
+ public void VB_ImplicitExpression_With_Keyword_In_Body()
+ {
+ ParseBlockTest("@Html.Partial",
+ new ExpressionBlock(
+ Factory.CodeTransition(),
+ Factory.Code("Html.Partial")
+ .AsImplicitExpression(VBCodeParser.DefaultKeywords)
+ .Accepts(AcceptedCharacters.NonWhiteSpace)));
+ }
+
+ [Fact]
+ public void VB_ImplicitExpression_With_MethodCallOrArrayIndex()
+ {
+ ParseBlockTest("@foo(42) not-part-of-the-block",
+ new ExpressionBlock(
+ Factory.CodeTransition(),
+ Factory.Code("foo(42)")
+ .AsImplicitExpression(VBCodeParser.DefaultKeywords)
+ .Accepts(AcceptedCharacters.NonWhiteSpace)));
+ }
+
+ [Fact]
+ public void VB_ImplicitExpression_Terminates_If_Trailing_Dot_Not_Followed_By_Valid_Token()
+ {
+ ParseBlockTest("@foo(42). ",
+ new ExpressionBlock(
+ Factory.CodeTransition(),
+ Factory.Code("foo(42)")
+ .AsImplicitExpression(VBCodeParser.DefaultKeywords)
+ .Accepts(AcceptedCharacters.NonWhiteSpace)));
+ }
+
+ [Fact]
+ public void VB_ImplicitExpression_Supports_Complex_Expressions()
+ {
+ ParseBlockTest("@foo(42).bar(Biz.Boz / 42 * 8)(1).Burf not part of the block",
+ new ExpressionBlock(
+ Factory.CodeTransition()
+ .Accepts(AcceptedCharacters.None),
+ Factory.Code("foo(42).bar(Biz.Boz / 42 * 8)(1).Burf")
+ .AsImplicitExpression(VBCodeParser.DefaultKeywords)
+ .Accepts(AcceptedCharacters.NonWhiteSpace)));
+ }
+ }
+}
diff --git a/test/System.Web.Razor.Test/Parser/VB/VBLayoutDirectiveTest.cs b/test/System.Web.Razor.Test/Parser/VB/VBLayoutDirectiveTest.cs
new file mode 100644
index 00000000..a1d58b86
--- /dev/null
+++ b/test/System.Web.Razor.Test/Parser/VB/VBLayoutDirectiveTest.cs
@@ -0,0 +1,86 @@
+using System.Web.Razor.Editor;
+using System.Web.Razor.Generator;
+using System.Web.Razor.Parser.SyntaxTree;
+using System.Web.Razor.Test.Framework;
+using Xunit;
+using Xunit.Extensions;
+
+namespace System.Web.Razor.Test.Parser.VB
+{
+ public class VBLayoutDirectiveTest : VBHtmlCodeParserTestBase
+ {
+ [Theory]
+ [InlineData("layout")]
+ [InlineData("Layout")]
+ [InlineData("LAYOUT")]
+ [InlineData("layOut")]
+ [InlineData("LayOut")]
+ [InlineData("LaYoUt")]
+ [InlineData("lAyOuT")]
+ public void LayoutDirectiveSupportsAnyCasingOfKeyword(string keyword)
+ {
+ ParseBlockTest("@" + keyword,
+ new DirectiveBlock(
+ Factory.CodeTransition(),
+ Factory.MetaCode(keyword)
+ )
+ );
+ }
+
+ [Fact]
+ public void LayoutDirectiveAcceptsAllTextToEndOfLine()
+ {
+ ParseBlockTest(@"@Layout Foo Bar Baz",
+ new DirectiveBlock(
+ Factory.CodeTransition(),
+ Factory.MetaCode("Layout ").Accepts(AcceptedCharacters.None),
+ Factory.MetaCode("Foo Bar Baz")
+ .With(new SetLayoutCodeGenerator("Foo Bar Baz"))
+ .WithEditorHints(EditorHints.VirtualPath | EditorHints.LayoutPage)
+ )
+ );
+ }
+
+ [Fact]
+ public void LayoutDirectiveAcceptsAnyIfNoWhitespaceFollowingLayoutKeyword()
+ {
+ ParseBlockTest(@"@Layout",
+ new DirectiveBlock(
+ Factory.CodeTransition(),
+ Factory.MetaCode("Layout")
+ )
+ );
+ }
+
+ [Fact]
+ public void LayoutDirectiveOutputsMarkerSpanIfAnyWhitespaceAfterLayoutKeyword()
+ {
+ ParseBlockTest(@"@Layout ",
+ new DirectiveBlock(
+ Factory.CodeTransition(),
+ Factory.MetaCode("Layout ").Accepts(AcceptedCharacters.None),
+ Factory.EmptyVB()
+ .AsMetaCode()
+ .With(new SetLayoutCodeGenerator(String.Empty))
+ .WithEditorHints(EditorHints.VirtualPath | EditorHints.LayoutPage)
+ )
+ );
+ }
+
+ [Fact]
+ public void LayoutDirectiveAcceptsTrailingNewlineButDoesNotIncludeItInLayoutPath()
+ {
+ ParseBlockTest(@"@Layout Foo
+",
+ new DirectiveBlock(
+ Factory.CodeTransition(),
+ Factory.MetaCode("Layout ").Accepts(AcceptedCharacters.None),
+ Factory.MetaCode("Foo\r\n")
+ .With(new SetLayoutCodeGenerator("Foo"))
+ .Accepts(AcceptedCharacters.None)
+ .WithEditorHints(EditorHints.VirtualPath | EditorHints.LayoutPage)
+ )
+ );
+ }
+ }
+}
diff --git a/test/System.Web.Razor.Test/Parser/VB/VBNestedStatementsTest.cs b/test/System.Web.Razor.Test/Parser/VB/VBNestedStatementsTest.cs
new file mode 100644
index 00000000..8d4f8fca
--- /dev/null
+++ b/test/System.Web.Razor.Test/Parser/VB/VBNestedStatementsTest.cs
@@ -0,0 +1,167 @@
+using System.Web.Razor.Parser.SyntaxTree;
+using System.Web.Razor.Test.Framework;
+using System.Web.Razor.Tokenizer.Symbols;
+using Xunit;
+
+namespace System.Web.Razor.Test.Parser.VB
+{
+ public class VBNestedStatementsTest : VBHtmlCodeParserTestBase
+ {
+ [Fact]
+ public void VB_Nested_If_Statement()
+ {
+ ParseBlockTest(@"@If True Then
+ If False Then
+ End If
+End If",
+ new StatementBlock(
+ Factory.CodeTransition(),
+ Factory.Code("If True Then\r\n If False Then\r\n End If\r\nEnd If")
+ .AsStatement()
+ .Accepts(AcceptedCharacters.None)));
+ }
+
+ [Fact]
+ public void VB_Nested_Do_Statement()
+ {
+ ParseBlockTest(@"@Do While True
+ Do
+ Loop Until False
+Loop",
+ new StatementBlock(
+ Factory.CodeTransition(),
+ Factory.Code("Do While True\r\n Do\r\n Loop Until False\r\nLoop")
+ .AsStatement()
+ .Accepts(AcceptedCharacters.AnyExceptNewline)));
+ }
+
+ [Fact]
+ public void VB_Nested_Markup_Statement_In_If()
+ {
+ ParseBlockTest(@"@If True Then
+ @<p>Tag</p>
+End If",
+ new StatementBlock(
+ Factory.CodeTransition(),
+ Factory.Code("If True Then\r\n")
+ .AsStatement(),
+ new MarkupBlock(
+ Factory.Markup(" "),
+ Factory.MarkupTransition(),
+ Factory.Markup("<p>Tag</p>\r\n")
+ .Accepts(AcceptedCharacters.None)),
+ Factory.Code("End If")
+ .AsStatement()
+ .Accepts(AcceptedCharacters.None)));
+ }
+
+ [Fact]
+ public void VB_Nested_Markup_Statement_In_Code()
+ {
+ ParseBlockTest(@"@Code
+ Foo()
+ @<p>Tag</p>
+ Bar()
+End Code",
+ new StatementBlock(
+ Factory.CodeTransition(),
+ Factory.MetaCode("Code")
+ .Accepts(AcceptedCharacters.None),
+ Factory.Code("\r\n Foo()\r\n")
+ .AsStatement(),
+ new MarkupBlock(
+ Factory.Markup(" "),
+ Factory.MarkupTransition(),
+ Factory.Markup("<p>Tag</p>\r\n")
+ .Accepts(AcceptedCharacters.None)),
+ Factory.Code(" Bar()\r\n")
+ .AsStatement(),
+ Factory.MetaCode("End Code")
+ .Accepts(AcceptedCharacters.None)));
+ }
+
+ [Fact]
+ public void VB_Nested_Markup_Statement_In_Do()
+ {
+ ParseBlockTest(@"@Do
+ @<p>Tag</p>
+Loop While True",
+ new StatementBlock(
+ Factory.CodeTransition(),
+ Factory.Code("Do\r\n")
+ .AsStatement(),
+ new MarkupBlock(
+ Factory.Markup(" "),
+ Factory.MarkupTransition(),
+ Factory.Markup("<p>Tag</p>\r\n")
+ .Accepts(AcceptedCharacters.None)),
+ Factory.Code("Loop While True")
+ .AsStatement()
+ .Accepts(AcceptedCharacters.AnyExceptNewline)));
+ }
+
+ [Fact]
+ public void VB_Nested_Single_Line_Markup_Statement_In_Do()
+ {
+ ParseBlockTest(@"@Do
+ @:<p>Tag
+Loop While True",
+ new StatementBlock(
+ Factory.CodeTransition(),
+ Factory.Code("Do\r\n")
+ .AsStatement(),
+ new MarkupBlock(
+ Factory.Markup(" "),
+ Factory.MarkupTransition(),
+ Factory.MetaMarkup(":", HtmlSymbolType.Colon),
+ Factory.Markup("<p>Tag\r\n")
+ .Accepts(AcceptedCharacters.None)),
+ Factory.Code("Loop While True")
+ .AsStatement()
+ .Accepts(AcceptedCharacters.AnyExceptNewline)));
+ }
+
+ [Fact]
+ public void VB_Nested_Implicit_Expression_In_If()
+ {
+ ParseBlockTest(@"@If True Then
+ @Foo.Bar
+End If",
+ new StatementBlock(
+ Factory.CodeTransition(),
+ Factory.Code("If True Then\r\n ")
+ .AsStatement(),
+ new ExpressionBlock(
+ Factory.CodeTransition(),
+ Factory.Code("Foo.Bar")
+ .AsExpression()
+ .Accepts(AcceptedCharacters.NonWhiteSpace)),
+ Factory.Code("\r\nEnd If")
+ .AsStatement()
+ .Accepts(AcceptedCharacters.None)));
+ }
+
+ [Fact]
+ public void VB_Nested_Explicit_Expression_In_If()
+ {
+ ParseBlockTest(@"@If True Then
+ @(Foo.Bar + 42)
+End If",
+ new StatementBlock(
+ Factory.CodeTransition(),
+ Factory.Code("If True Then\r\n ")
+ .AsStatement(),
+ new ExpressionBlock(
+ Factory.CodeTransition(),
+ Factory.MetaCode("(")
+ .Accepts(AcceptedCharacters.None),
+ Factory.Code("Foo.Bar + 42")
+ .AsExpression(),
+ Factory.MetaCode(")")
+ .Accepts(AcceptedCharacters.None)),
+ Factory.Code("\r\nEnd If")
+ .AsStatement()
+ .Accepts(AcceptedCharacters.None)));
+ }
+ }
+}
diff --git a/test/System.Web.Razor.Test/Parser/VB/VBRazorCommentsTest.cs b/test/System.Web.Razor.Test/Parser/VB/VBRazorCommentsTest.cs
new file mode 100644
index 00000000..e90c5a2d
--- /dev/null
+++ b/test/System.Web.Razor.Test/Parser/VB/VBRazorCommentsTest.cs
@@ -0,0 +1,172 @@
+using System.Web.Razor.Parser;
+using System.Web.Razor.Parser.SyntaxTree;
+using System.Web.Razor.Resources;
+using System.Web.Razor.Test.Framework;
+using System.Web.Razor.Tokenizer.Symbols;
+using Xunit;
+
+namespace System.Web.Razor.Test.Parser.VB
+{
+ public class VBRazorCommentsTest : VBHtmlMarkupParserTestBase
+ {
+ [Fact]
+ public void UnterminatedRazorComment()
+ {
+ ParseDocumentTest("@*",
+ new MarkupBlock(
+ Factory.EmptyHtml(),
+ new CommentBlock(
+ Factory.MarkupTransition(HtmlSymbolType.RazorCommentTransition)
+ .Accepts(AcceptedCharacters.None),
+ Factory.MetaMarkup("*", HtmlSymbolType.RazorCommentStar)
+ .Accepts(AcceptedCharacters.None),
+ Factory.Span(SpanKind.Comment, new HtmlSymbol(
+ Factory.LocationTracker.CurrentLocation,
+ String.Empty,
+ HtmlSymbolType.Unknown))
+ .Accepts(AcceptedCharacters.Any))),
+ new RazorError(RazorResources.ParseError_RazorComment_Not_Terminated, 0, 0, 0));
+ }
+
+ [Fact]
+ public void EmptyRazorComment()
+ {
+ ParseDocumentTest("@**@",
+ new MarkupBlock(
+ Factory.EmptyHtml(),
+ new CommentBlock(
+ Factory.MarkupTransition(HtmlSymbolType.RazorCommentTransition)
+ .Accepts(AcceptedCharacters.None),
+ Factory.MetaMarkup("*", HtmlSymbolType.RazorCommentStar)
+ .Accepts(AcceptedCharacters.None),
+ Factory.Span(SpanKind.Comment, new HtmlSymbol(
+ Factory.LocationTracker.CurrentLocation,
+ String.Empty,
+ HtmlSymbolType.Unknown))
+ .Accepts(AcceptedCharacters.Any),
+ Factory.MetaMarkup("*", HtmlSymbolType.RazorCommentStar)
+ .Accepts(AcceptedCharacters.None),
+ Factory.MarkupTransition(HtmlSymbolType.RazorCommentTransition)
+ .Accepts(AcceptedCharacters.None)),
+ Factory.EmptyHtml()));
+ }
+
+ [Fact]
+ public void RazorCommentInImplicitExpressionMethodCall()
+ {
+ ParseDocumentTest(@"@foo(@**@",
+ new MarkupBlock(
+ Factory.EmptyHtml(),
+ new ExpressionBlock(
+ Factory.CodeTransition(),
+ Factory.Code("foo(")
+ .AsImplicitExpression(VBCodeParser.DefaultKeywords),
+ new CommentBlock(
+ Factory.CodeTransition(VBSymbolType.RazorCommentTransition)
+ .Accepts(AcceptedCharacters.None),
+ Factory.MetaCode("*", VBSymbolType.RazorCommentStar)
+ .Accepts(AcceptedCharacters.None),
+ Factory.Span(SpanKind.Comment, new VBSymbol(
+ Factory.LocationTracker.CurrentLocation,
+ String.Empty,
+ VBSymbolType.Unknown))
+ .Accepts(AcceptedCharacters.Any),
+ Factory.MetaCode("*", VBSymbolType.RazorCommentStar)
+ .Accepts(AcceptedCharacters.None),
+ Factory.CodeTransition(VBSymbolType.RazorCommentTransition)
+ .Accepts(AcceptedCharacters.None)),
+ Factory.EmptyVB()
+ .AsImplicitExpression(VBCodeParser.DefaultKeywords))),
+ new RazorError(
+ String.Format(RazorResources.ParseError_Expected_CloseBracket_Before_EOF, "(", ")"),
+ 4, 0, 4));
+ }
+
+ [Fact]
+ public void UnterminatedRazorCommentInImplicitExpressionMethodCall()
+ {
+ ParseDocumentTest("@foo(@*",
+ new MarkupBlock(
+ Factory.EmptyHtml(),
+ new ExpressionBlock(
+ Factory.CodeTransition(),
+ Factory.Code("foo(")
+ .AsImplicitExpression(VBCodeParser.DefaultKeywords),
+ new CommentBlock(
+ Factory.CodeTransition(VBSymbolType.RazorCommentTransition)
+ .Accepts(AcceptedCharacters.None),
+ Factory.MetaCode("*", VBSymbolType.RazorCommentStar)
+ .Accepts(AcceptedCharacters.None),
+ Factory.Span(SpanKind.Comment, new VBSymbol(
+ Factory.LocationTracker.CurrentLocation,
+ String.Empty,
+ VBSymbolType.Unknown))
+ .Accepts(AcceptedCharacters.Any)))),
+ new RazorError(RazorResources.ParseError_RazorComment_Not_Terminated, 5, 0, 5),
+ new RazorError(String.Format(RazorResources.ParseError_Expected_CloseBracket_Before_EOF, "(", ")"), 4, 0, 4));
+ }
+
+ [Fact]
+ public void RazorCommentInVerbatimBlock()
+ {
+ ParseDocumentTest(@"@Code
+ @<text
+ @**@
+End Code",
+ new MarkupBlock(
+ Factory.EmptyHtml(),
+ new StatementBlock(
+ Factory.CodeTransition(),
+ Factory.MetaCode("Code").Accepts(AcceptedCharacters.None),
+ Factory.Code("\r\n").AsStatement(),
+ new MarkupBlock(
+ Factory.Markup(" "),
+ Factory.MarkupTransition("@"),
+ Factory.MarkupTransition("<text").Accepts(AcceptedCharacters.Any),
+ Factory.Markup("\r\n "),
+ new CommentBlock(
+ Factory.MarkupTransition(HtmlSymbolType.RazorCommentTransition)
+ .Accepts(AcceptedCharacters.None),
+ Factory.MetaMarkup("*", HtmlSymbolType.RazorCommentStar)
+ .Accepts(AcceptedCharacters.None),
+ Factory.Span(SpanKind.Comment, new HtmlSymbol(
+ Factory.LocationTracker.CurrentLocation,
+ String.Empty,
+ HtmlSymbolType.Unknown))
+ .Accepts(AcceptedCharacters.Any),
+ Factory.MetaMarkup("*", HtmlSymbolType.RazorCommentStar)
+ .Accepts(AcceptedCharacters.None),
+ Factory.MarkupTransition(HtmlSymbolType.RazorCommentTransition)
+ .Accepts(AcceptedCharacters.None)),
+ Factory.Markup("\r\nEnd Code")))),
+ new RazorError(RazorResources.ParseError_TextTagCannotContainAttributes, 12, 1, 5),
+ new RazorError(String.Format(RazorResources.ParseError_MissingEndTag, "text"), 12, 1, 5),
+ new RazorError(String.Format(RazorResources.ParseError_BlockNotTerminated, SyntaxConstants.VB.CodeKeyword, SyntaxConstants.VB.EndCodeKeyword), 1, 0, 1));
+ }
+
+ [Fact]
+ public void UnterminatedRazorCommentInVerbatimBlock()
+ {
+ ParseDocumentTest(@"@Code
+@*",
+ new MarkupBlock(
+ Factory.EmptyHtml(),
+ new StatementBlock(
+ Factory.CodeTransition(),
+ Factory.MetaCode("Code").Accepts(AcceptedCharacters.None),
+ Factory.Code("\r\n")
+ .AsStatement(),
+ new CommentBlock(
+ Factory.CodeTransition(VBSymbolType.RazorCommentTransition)
+ .Accepts(AcceptedCharacters.None),
+ Factory.MetaCode("*", VBSymbolType.RazorCommentStar)
+ .Accepts(AcceptedCharacters.None),
+ Factory.Span(SpanKind.Comment, new VBSymbol(Factory.LocationTracker.CurrentLocation,
+ String.Empty,
+ VBSymbolType.Unknown))
+ .Accepts(AcceptedCharacters.Any)))),
+ new RazorError(RazorResources.ParseError_RazorComment_Not_Terminated, 7, 1, 0),
+ new RazorError(String.Format(RazorResources.ParseError_BlockNotTerminated, SyntaxConstants.VB.CodeKeyword, SyntaxConstants.VB.EndCodeKeyword), 1, 0, 1));
+ }
+ }
+}
diff --git a/test/System.Web.Razor.Test/Parser/VB/VBReservedWordsTest.cs b/test/System.Web.Razor.Test/Parser/VB/VBReservedWordsTest.cs
new file mode 100644
index 00000000..fe0c1f87
--- /dev/null
+++ b/test/System.Web.Razor.Test/Parser/VB/VBReservedWordsTest.cs
@@ -0,0 +1,28 @@
+using System.Web.Razor.Parser.SyntaxTree;
+using System.Web.Razor.Resources;
+using System.Web.Razor.Test.Framework;
+using System.Web.Razor.Text;
+using Xunit.Extensions;
+
+namespace System.Web.Razor.Test.Parser.VB
+{
+ public class VBReservedWordsTest : VBHtmlCodeParserTestBase
+ {
+ [Theory]
+ [InlineData("Namespace")]
+ [InlineData("Class")]
+ [InlineData("NAMESPACE")]
+ [InlineData("CLASS")]
+ [InlineData("NameSpace")]
+ [InlineData("nameSpace")]
+ private void ReservedWords(string word)
+ {
+ ParseBlockTest(word,
+ new DirectiveBlock(
+ Factory.MetaCode(word).Accepts(AcceptedCharacters.None)),
+ new RazorError(
+ String.Format(RazorResources.ParseError_ReservedWord, word),
+ SourceLocation.Zero));
+ }
+ }
+}
diff --git a/test/System.Web.Razor.Test/Parser/VB/VBSectionTest.cs b/test/System.Web.Razor.Test/Parser/VB/VBSectionTest.cs
new file mode 100644
index 00000000..e0052527
--- /dev/null
+++ b/test/System.Web.Razor.Test/Parser/VB/VBSectionTest.cs
@@ -0,0 +1,225 @@
+using System.Web.Razor.Generator;
+using System.Web.Razor.Parser;
+using System.Web.Razor.Parser.SyntaxTree;
+using System.Web.Razor.Resources;
+using System.Web.Razor.Test.Framework;
+using Xunit;
+
+namespace System.Web.Razor.Test.Parser.VB
+{
+ public class VBSectionTest : VBHtmlMarkupParserTestBase
+ {
+ [Fact]
+ public void ParseSectionBlockCapturesNewlineImmediatelyFollowing()
+ {
+ ParseDocumentTest(@"@Section
+",
+ new MarkupBlock(
+ Factory.EmptyHtml(),
+ new SectionBlock(new SectionCodeGenerator(String.Empty),
+ Factory.CodeTransition(),
+ Factory.MetaCode("Section\r\n"),
+ new MarkupBlock())),
+ new RazorError(
+ String.Format(
+ RazorResources.ParseError_Unexpected_Character_At_Section_Name_Start,
+ RazorResources.ErrorComponent_EndOfFile),
+ 10, 1, 0),
+ new RazorError(
+ String.Format(
+ RazorResources.ParseError_BlockNotTerminated,
+ "Section", "End Section"),
+ 1, 0, 1));
+ }
+
+ [Fact]
+ public void ParseSectionRequiresNameBeOnSameLineAsSectionKeyword()
+ {
+ ParseDocumentTest(@"@Section
+Foo
+ <p>Body</p>
+End Section",
+ new MarkupBlock(
+ Factory.EmptyHtml(),
+ new SectionBlock(new SectionCodeGenerator(String.Empty),
+ Factory.CodeTransition(),
+ Factory.MetaCode("Section "),
+ new MarkupBlock(
+ Factory.Markup("\r\nFoo\r\n <p>Body</p>\r\n")),
+ Factory.MetaCode("End Section").Accepts(AcceptedCharacters.None)),
+ Factory.EmptyHtml()),
+ new RazorError(
+ String.Format(
+ RazorResources.ParseError_Unexpected_Character_At_Section_Name_Start,
+ RazorResources.ErrorComponent_Newline),
+ 9, 0, 9));
+ }
+
+ [Fact]
+ public void ParseSectionAllowsNameToBeOnDifferentLineAsSectionKeywordIfUnderscoresUsed()
+ {
+ ParseDocumentTest(@"@Section _
+_
+Foo
+ <p>Body</p>
+End Section",
+ new MarkupBlock(
+ Factory.EmptyHtml(),
+ new SectionBlock(new SectionCodeGenerator("Foo"),
+ Factory.CodeTransition(),
+ Factory.MetaCode("Section _\r\n_\r\nFoo"),
+ new MarkupBlock(
+ Factory.Markup("\r\n <p>Body</p>\r\n")),
+ Factory.MetaCode("End Section").Accepts(AcceptedCharacters.None)),
+ Factory.EmptyHtml()));
+ }
+
+ [Fact]
+ public void ParseSectionReportsErrorAndTerminatesSectionBlockIfKeywordNotFollowedByIdentifierStartCharacter()
+ {
+ ParseDocumentTest(@"@Section 9
+ <p>Foo</p>
+End Section",
+ new MarkupBlock(
+ Factory.EmptyHtml(),
+ new SectionBlock(new SectionCodeGenerator(String.Empty),
+ Factory.CodeTransition(),
+ Factory.MetaCode("Section "),
+ new MarkupBlock(
+ Factory.Markup("9\r\n <p>Foo</p>\r\n")),
+ Factory.MetaCode("End Section").Accepts(AcceptedCharacters.None)),
+ Factory.EmptyHtml()),
+ new RazorError(
+ String.Format(
+ RazorResources.ParseError_Unexpected_Character_At_Section_Name_Start,
+ String.Format(RazorResources.ErrorComponent_Character, "9")),
+ 9, 0, 9));
+ }
+
+ [Fact]
+ public void ParserOutputsErrorOnNestedSections()
+ {
+ ParseDocumentTest(@"@Section foo
+ @Section bar
+ <p>Foo</p>
+ End Section
+End Section",
+ new MarkupBlock(
+ Factory.EmptyHtml(),
+ new SectionBlock(new SectionCodeGenerator("foo"),
+ Factory.CodeTransition(),
+ Factory.MetaCode("Section foo"),
+ new MarkupBlock(
+ Factory.Markup("\r\n"),
+ new SectionBlock(new SectionCodeGenerator("bar"),
+ Factory.Code(" ").AsStatement(),
+ Factory.CodeTransition(),
+ Factory.MetaCode("Section bar"),
+ new MarkupBlock(
+ Factory.Markup("\r\n <p>Foo</p>\r\n ")),
+ Factory.MetaCode("End Section").Accepts(AcceptedCharacters.None)),
+ Factory.Markup("\r\n")),
+ Factory.MetaCode("End Section").Accepts(AcceptedCharacters.None)),
+ Factory.EmptyHtml()),
+ new RazorError(
+ String.Format(
+ RazorResources.ParseError_Sections_Cannot_Be_Nested,
+ RazorResources.SectionExample_VB),
+ 26, 1, 12));
+ }
+
+ [Fact]
+ public void ParseSectionHandlesEOFAfterIdentifier()
+ {
+ ParseDocumentTest("@Section foo",
+ new MarkupBlock(
+ Factory.EmptyHtml(),
+ new SectionBlock(new SectionCodeGenerator("foo"),
+ Factory.CodeTransition(),
+ Factory.MetaCode("Section foo")
+ .AutoCompleteWith(SyntaxConstants.VB.EndSectionKeyword),
+ new MarkupBlock())),
+ new RazorError(
+ String.Format(
+ RazorResources.ParseError_BlockNotTerminated,
+ "Section", "End Section"),
+ 1, 0, 1));
+ }
+
+ [Fact]
+ public void ParseSectionHandlesUnterminatedSection()
+ {
+ ParseDocumentTest(@"@Section foo
+ <p>Foo</p>",
+ new MarkupBlock(
+ Factory.EmptyHtml(),
+ new SectionBlock(new SectionCodeGenerator("foo"),
+ Factory.CodeTransition(),
+ Factory.MetaCode("Section foo")
+ .AutoCompleteWith(SyntaxConstants.VB.EndSectionKeyword),
+ new MarkupBlock(
+ Factory.Markup("\r\n <p>Foo</p>")))),
+ new RazorError(
+ String.Format(
+ RazorResources.ParseError_BlockNotTerminated,
+ "Section", "End Section"),
+ 1, 0, 1));
+ }
+
+ [Fact]
+ public void ParseDocumentParsesNamedSectionCorrectly()
+ {
+ ParseDocumentTest(@"@Section foo
+ <p>Foo</p>
+End Section",
+ new MarkupBlock(
+ Factory.EmptyHtml(),
+ new SectionBlock(new SectionCodeGenerator("foo"),
+ Factory.CodeTransition(),
+ Factory.MetaCode("Section foo"),
+ new MarkupBlock(
+ Factory.Markup("\r\n <p>Foo</p>\r\n")),
+ Factory.MetaCode("End Section").Accepts(AcceptedCharacters.None)),
+ Factory.EmptyHtml()));
+ }
+
+ [Fact]
+ public void ParseSectionTerminatesOnFirstEndSection()
+ {
+ ParseDocumentTest(@"@Section foo
+ <p>End Section</p>",
+ new MarkupBlock(
+ Factory.EmptyHtml(),
+ new SectionBlock(new SectionCodeGenerator("foo"),
+ Factory.CodeTransition(),
+ Factory.MetaCode("Section foo"),
+ new MarkupBlock(
+ Factory.Markup("\r\n <p>")),
+ Factory.MetaCode("End Section").Accepts(AcceptedCharacters.None)),
+ Factory.Markup("</p>")));
+ }
+
+ [Fact]
+ public void ParseSectionAllowsEndSectionInVBExpression()
+ {
+ ParseDocumentTest(@"@Section foo
+ I really want to render the word @(""End Section""), so this is how I do it
+End Section",
+ new MarkupBlock(
+ Factory.EmptyHtml(),
+ new SectionBlock(new SectionCodeGenerator("foo"),
+ Factory.CodeTransition(),
+ Factory.MetaCode("Section foo"),
+ new MarkupBlock(
+ Factory.Markup("\r\n I really want to render the word "),
+ new ExpressionBlock(
+ Factory.CodeTransition(),
+ Factory.MetaCode("(").Accepts(AcceptedCharacters.None),
+ Factory.Code("\"End Section\"").AsExpression(),
+ Factory.MetaCode(")").Accepts(AcceptedCharacters.None)),
+ Factory.Markup(", so this is how I do it\r\n")),
+ Factory.MetaCode("End Section").Accepts(AcceptedCharacters.None)),
+ Factory.EmptyHtml()));
+ }
+ }
+}
diff --git a/test/System.Web.Razor.Test/Parser/VB/VBSpecialKeywordsTest.cs b/test/System.Web.Razor.Test/Parser/VB/VBSpecialKeywordsTest.cs
new file mode 100644
index 00000000..9c4b17bd
--- /dev/null
+++ b/test/System.Web.Razor.Test/Parser/VB/VBSpecialKeywordsTest.cs
@@ -0,0 +1,169 @@
+using System.Web.Razor.Generator;
+using System.Web.Razor.Parser;
+using System.Web.Razor.Parser.SyntaxTree;
+using System.Web.Razor.Resources;
+using System.Web.Razor.Test.Framework;
+using Xunit;
+
+namespace System.Web.Razor.Test.Parser.VB
+{
+ public class VBSpecialKeywordsTest : VBHtmlCodeParserTestBase
+ {
+ [Fact]
+ public void ParseInheritsStatementMarksInheritsSpanAsCanGrowIfMissingTrailingSpace()
+ {
+ ParseBlockTest("inherits",
+ new DirectiveBlock(
+ Factory.MetaCode("inherits")),
+ new RazorError(
+ RazorResources.ParseError_InheritsKeyword_Must_Be_Followed_By_TypeName,
+ 8, 0, 8));
+ }
+
+ [Fact]
+ public void InheritsBlockAcceptsMultipleGenericArguments()
+ {
+ ParseBlockTest("inherits Foo.Bar(Of Biz(Of Qux), String, Integer).Baz",
+ new DirectiveBlock(
+ Factory.MetaCode("inherits ").Accepts(AcceptedCharacters.None),
+ Factory.Code("Foo.Bar(Of Biz(Of Qux), String, Integer).Baz")
+ .AsBaseType("Foo.Bar(Of Biz(Of Qux), String, Integer).Baz")));
+ }
+
+ [Fact]
+ public void InheritsDirectiveSupportsVSTemplateTokens()
+ {
+ ParseBlockTest("@Inherits $rootnamespace$.MyBase",
+ new DirectiveBlock(
+ Factory.CodeTransition(),
+ Factory.MetaCode("Inherits ")
+ .Accepts(AcceptedCharacters.None),
+ Factory.Code("$rootnamespace$.MyBase")
+ .AsBaseType("$rootnamespace$.MyBase")));
+ }
+
+ [Fact]
+ public void InheritsBlockOutputsErrorIfInheritsNotFollowedByTypeButAcceptsEntireLineAsCode()
+ {
+ ParseBlockTest(@"inherits
+foo",
+ new DirectiveBlock(
+ Factory.MetaCode("inherits ").Accepts(AcceptedCharacters.None),
+ Factory.Code("\r\n").AsBaseType(String.Empty)),
+ new RazorError(
+ RazorResources.ParseError_InheritsKeyword_Must_Be_Followed_By_TypeName,
+ 8, 0, 8));
+ }
+
+ [Fact]
+ public void ParseBlockShouldSupportNamespaceImports()
+ {
+ ParseBlockTest("Imports Foo.Bar.Baz.Biz.Boz",
+ new DirectiveBlock(
+ Factory.MetaCode("Imports Foo.Bar.Baz.Biz.Boz")
+ .With(new AddImportCodeGenerator(
+ ns: " Foo.Bar.Baz.Biz.Boz",
+ namespaceKeywordLength: SyntaxConstants.VB.ImportsKeywordLength))));
+ }
+
+ [Fact]
+ public void ParseBlockShowsErrorIfNamespaceNotOnSameLineAsImportsKeyword()
+ {
+ ParseBlockTest(@"Imports
+Foo",
+ new DirectiveBlock(
+ Factory.MetaCode("Imports\r\n")
+ .With(new AddImportCodeGenerator(
+ ns: "\r\n",
+ namespaceKeywordLength: SyntaxConstants.VB.ImportsKeywordLength))),
+ new RazorError(
+ RazorResources.ParseError_NamespaceOrTypeAliasExpected,
+ 7, 0, 7));
+ }
+
+ [Fact]
+ public void ParseBlockShowsErrorIfTypeBeingAliasedNotOnSameLineAsImportsKeyword()
+ {
+ ParseBlockTest(@"Imports Foo =
+System.Bar",
+ new DirectiveBlock(
+ Factory.MetaCode("Imports Foo =\r\n")
+ .With(new AddImportCodeGenerator(
+ ns: " Foo =\r\n",
+ namespaceKeywordLength: SyntaxConstants.VB.ImportsKeywordLength))));
+ }
+
+ [Fact]
+ public void ParseBlockShouldSupportTypeAliases()
+ {
+ ParseBlockTest("Imports Foo = Bar.Baz.Biz.Boz",
+ new DirectiveBlock(
+ Factory.MetaCode("Imports Foo = Bar.Baz.Biz.Boz")
+ .With(new AddImportCodeGenerator(
+ ns: " Foo = Bar.Baz.Biz.Boz",
+ namespaceKeywordLength: SyntaxConstants.VB.ImportsKeywordLength))));
+ }
+
+ [Fact]
+ public void ParseBlockThrowsErrorIfOptionIsNotFollowedByStrictOrExplicit()
+ {
+ ParseBlockTest("Option FizzBuzz On",
+ new DirectiveBlock(
+ Factory.MetaCode("Option FizzBuzz On")
+ .With(new SetVBOptionCodeGenerator(optionName: null, value: true))),
+ new RazorError(
+ String.Format(RazorResources.ParseError_UnknownOption, "FizzBuzz"),
+ 7, 0, 7));
+ }
+
+ [Fact]
+ public void ParseBlockThrowsErrorIfOptionStrictIsNotFollowedByOnOrOff()
+ {
+ ParseBlockTest("Option Strict Yes",
+ new DirectiveBlock(
+ Factory.MetaCode("Option Strict Yes")
+ .With(SetVBOptionCodeGenerator.Strict(true))),
+ new RazorError(
+ String.Format(
+ RazorResources.ParseError_InvalidOptionValue,
+ "Strict", "Yes"),
+ 14, 0, 14));
+ }
+
+ [Fact]
+ public void ParseBlockReadsToAfterOnKeywordIfOptionStrictBlock()
+ {
+ ParseBlockTest("Option Strict On Foo Bar Baz",
+ new DirectiveBlock(
+ Factory.MetaCode("Option Strict On")
+ .With(SetVBOptionCodeGenerator.Strict(true))));
+ }
+
+ [Fact]
+ public void ParseBlockReadsToAfterOffKeywordIfOptionStrictBlock()
+ {
+ ParseBlockTest("Option Strict Off Foo Bar Baz",
+ new DirectiveBlock(
+ Factory.MetaCode("Option Strict Off")
+ .With(SetVBOptionCodeGenerator.Strict(false))));
+ }
+
+ [Fact]
+ public void ParseBlockReadsToAfterOnKeywordIfOptionExplicitBlock()
+ {
+ ParseBlockTest("Option Explicit On Foo Bar Baz",
+ new DirectiveBlock(
+ Factory.MetaCode("Option Explicit On")
+ .With(SetVBOptionCodeGenerator.Explicit(true))));
+ }
+
+ [Fact]
+ public void ParseBlockReadsToAfterOffKeywordIfOptionExplicitBlock()
+ {
+ ParseBlockTest("Option Explicit Off Foo Bar Baz",
+ new DirectiveBlock(
+ Factory.MetaCode("Option Explicit Off")
+ .With(SetVBOptionCodeGenerator.Explicit(false))));
+ }
+ }
+}
diff --git a/test/System.Web.Razor.Test/Parser/VB/VBStatementTest.cs b/test/System.Web.Razor.Test/Parser/VB/VBStatementTest.cs
new file mode 100644
index 00000000..73423af5
--- /dev/null
+++ b/test/System.Web.Razor.Test/Parser/VB/VBStatementTest.cs
@@ -0,0 +1,218 @@
+using System.Web.Razor.Generator;
+using System.Web.Razor.Parser;
+using System.Web.Razor.Parser.SyntaxTree;
+using System.Web.Razor.Test.Framework;
+using Xunit;
+
+namespace System.Web.Razor.Test.Parser.VB
+{
+ public class VBStatementTest : VBHtmlCodeParserTestBase
+ {
+ [Fact]
+ public void VB_Inherits_Statement()
+ {
+ ParseBlockTest(@"@Inherits System.Foo.Bar(Of Baz)",
+ new DirectiveBlock(
+ Factory.CodeTransition(),
+ Factory.MetaCode("Inherits ").Accepts(AcceptedCharacters.None),
+ Factory.Code("System.Foo.Bar(Of Baz)")
+ .AsBaseType("System.Foo.Bar(Of Baz)")));
+ }
+
+ [Fact]
+ public void InheritsDirectiveSupportsArrays()
+ {
+ ParseBlockTest("@Inherits System.String(())()",
+ new DirectiveBlock(
+ Factory.CodeTransition(),
+ Factory.MetaCode("Inherits ")
+ .Accepts(AcceptedCharacters.None),
+ Factory.Code("System.String(())()")
+ .AsBaseType("System.String(())()")));
+ }
+
+ [Fact]
+ public void InheritsDirectiveSupportsNestedGenerics()
+ {
+ ParseBlockTest("@Inherits System.Web.Mvc.WebViewPage(Of IEnumerable(Of MvcApplication2.Models.RegisterModel))",
+ new DirectiveBlock(
+ Factory.CodeTransition(),
+ Factory.MetaCode("Inherits ")
+ .Accepts(AcceptedCharacters.None),
+ Factory.Code("System.Web.Mvc.WebViewPage(Of IEnumerable(Of MvcApplication2.Models.RegisterModel))")
+ .AsBaseType("System.Web.Mvc.WebViewPage(Of IEnumerable(Of MvcApplication2.Models.RegisterModel))")));
+ }
+
+ [Fact]
+ public void InheritsDirectiveSupportsTypeKeywords()
+ {
+ ParseBlockTest("@Inherits String",
+ new DirectiveBlock(
+ Factory.CodeTransition(),
+ Factory.MetaCode("Inherits ").Accepts(AcceptedCharacters.None),
+ Factory.Code("String").AsBaseType("String")));
+ }
+
+ [Fact]
+ public void VB_Option_Strict_Statement()
+ {
+ ParseBlockTest(@"@Option Strict Off",
+ new DirectiveBlock(
+ Factory.CodeTransition(),
+ Factory.MetaCode("Option Strict Off")
+ .With(SetVBOptionCodeGenerator.Strict(false))));
+ }
+
+ [Fact]
+ public void VB_Option_Explicit_Statement()
+ {
+ ParseBlockTest(@"@Option Explicit Off",
+ new DirectiveBlock(
+ Factory.CodeTransition(),
+ Factory.MetaCode("Option Explicit Off")
+ .With(SetVBOptionCodeGenerator.Explicit(false))));
+ }
+
+ [Fact]
+ public void VB_Imports_Statement()
+ {
+ ParseBlockTest("@Imports Biz = System.Foo.Bar(Of Boz.Baz(Of Qux))",
+ new DirectiveBlock(
+ Factory.CodeTransition(),
+ Factory.MetaCode("Imports Biz = System.Foo.Bar(Of Boz.Baz(Of Qux))")
+ .With(new AddImportCodeGenerator(
+ ns: " Biz = System.Foo.Bar(Of Boz.Baz(Of Qux))",
+ namespaceKeywordLength: SyntaxConstants.VB.ImportsKeywordLength))));
+ }
+
+ [Fact]
+ public void VB_Using_Statement()
+ {
+ ParseBlockTest(@"@Using foo as Bar
+ foo()
+End Using",
+ new StatementBlock(
+ Factory.CodeTransition(),
+ Factory.Code("Using foo as Bar\r\n foo()\r\nEnd Using")
+ .AsStatement()
+ .Accepts(AcceptedCharacters.None)));
+ }
+
+ [Fact]
+ public void VB_Do_Loop_Statement()
+ {
+ ParseBlockTest(@"@Do
+ foo()
+Loop While True",
+ new StatementBlock(
+ Factory.CodeTransition(),
+ Factory.Code("Do\r\n foo()\r\nLoop While True")
+ .AsStatement()
+ .Accepts(AcceptedCharacters.AnyExceptNewline)));
+ }
+
+ [Fact]
+ public void VB_While_Statement()
+ {
+ ParseBlockTest(@"@While True
+ foo()
+End While",
+ new StatementBlock(
+ Factory.CodeTransition(),
+ Factory.Code("While True\r\n foo()\r\nEnd While")
+ .AsStatement()
+ .Accepts(AcceptedCharacters.None)));
+ }
+
+ [Fact]
+ public void VB_If_Statement()
+ {
+ ParseBlockTest(@"@If True Then
+ foo()
+ElseIf False Then
+ bar()
+Else
+ baz()
+End If",
+ new StatementBlock(
+ Factory.CodeTransition(),
+ Factory.Code("If True Then\r\n foo()\r\nElseIf False Then\r\n bar()\r\nElse\r\n baz()\r\nEnd If")
+ .AsStatement()
+ .Accepts(AcceptedCharacters.None)));
+ }
+
+ [Fact]
+ public void VB_Select_Statement()
+ {
+ ParseBlockTest(@"@Select Case foo
+ Case 1
+ foo()
+ Case 2
+ bar()
+ Case Else
+ baz()
+End Select",
+ new StatementBlock(
+ Factory.CodeTransition(),
+ Factory.Code("Select Case foo\r\n Case 1\r\n foo()\r\n Case 2\r\n bar()\r\n Case Else\r\n baz()\r\nEnd Select")
+ .AsStatement()
+ .Accepts(AcceptedCharacters.None)));
+ }
+
+ [Fact]
+ public void VB_For_Statement()
+ {
+ ParseBlockTest(@"@For Each foo In bar
+ baz()
+Next",
+ new StatementBlock(
+ Factory.CodeTransition(),
+ Factory.Code("For Each foo In bar\r\n baz()\r\nNext")
+ .AsStatement()
+ .Accepts(AcceptedCharacters.AnyExceptNewline)));
+ }
+
+ [Fact]
+ public void VB_Try_Statement()
+ {
+ ParseBlockTest(@"@Try
+ foo()
+Catch ex as Exception
+ bar()
+Finally
+ baz()
+End Try",
+ new StatementBlock(
+ Factory.CodeTransition(),
+ Factory.Code("Try\r\n foo()\r\nCatch ex as Exception\r\n bar()\r\nFinally\r\n baz()\r\nEnd Try")
+ .AsStatement()
+ .Accepts(AcceptedCharacters.None)));
+ }
+
+ [Fact]
+ public void VB_With_Statement()
+ {
+ ParseBlockTest(@"@With foo
+ .bar()
+End With",
+ new StatementBlock(
+ Factory.CodeTransition(),
+ Factory.Code("With foo\r\n .bar()\r\nEnd With")
+ .AsStatement()
+ .Accepts(AcceptedCharacters.None)));
+ }
+
+ [Fact]
+ public void VB_SyncLock_Statement()
+ {
+ ParseBlockTest(@"@SyncLock foo
+ foo.bar()
+End SyncLock",
+ new StatementBlock(
+ Factory.CodeTransition(),
+ Factory.Code("SyncLock foo\r\n foo.bar()\r\nEnd SyncLock")
+ .AsStatement()
+ .Accepts(AcceptedCharacters.None)));
+ }
+ }
+}
diff --git a/test/System.Web.Razor.Test/Parser/VB/VBTemplateTest.cs b/test/System.Web.Razor.Test/Parser/VB/VBTemplateTest.cs
new file mode 100644
index 00000000..76992485
--- /dev/null
+++ b/test/System.Web.Razor.Test/Parser/VB/VBTemplateTest.cs
@@ -0,0 +1,211 @@
+using System.Web.Razor.Generator;
+using System.Web.Razor.Parser;
+using System.Web.Razor.Parser.SyntaxTree;
+using System.Web.Razor.Resources;
+using System.Web.Razor.Test.Framework;
+using Xunit;
+
+namespace System.Web.Razor.Test.Parser.VB
+{
+ public class VBTemplateTest : VBHtmlCodeParserTestBase
+ {
+ private const string TestTemplateCode = "@@<p>Foo #@item</p>";
+
+ private TemplateBlock TestTemplate()
+ {
+ return new TemplateBlock(new TemplateBlockCodeGenerator(),
+ new MarkupBlock(
+ Factory.MarkupTransition(),
+ Factory.MetaMarkup("@"),
+ Factory.Markup("<p>Foo #"),
+ new ExpressionBlock(
+ Factory.CodeTransition(),
+ Factory.Code("item")
+ .AsImplicitExpression(VBCodeParser.DefaultKeywords)
+ .Accepts(AcceptedCharacters.NonWhiteSpace)),
+ Factory.Markup("</p>").Accepts(AcceptedCharacters.None)));
+ }
+
+ private const string TestNestedTemplateCode = "@@<p>Foo #@Html.Repeat(10,@@<p>@item</p>)</p>";
+
+ private TemplateBlock TestNestedTemplate()
+ {
+ return new TemplateBlock(new TemplateBlockCodeGenerator(),
+ new MarkupBlock(
+ Factory.MarkupTransition(),
+ Factory.MetaMarkup("@"),
+ Factory.Markup("<p>Foo #"),
+ new ExpressionBlock(
+ Factory.CodeTransition(),
+ Factory.Code("Html.Repeat(10,")
+ .AsImplicitExpression(VBCodeParser.DefaultKeywords)
+ .Accepts(AcceptedCharacters.Any),
+ new TemplateBlock(new TemplateBlockCodeGenerator(),
+ new MarkupBlock(
+ Factory.MarkupTransition(),
+ Factory.MetaMarkup("@"),
+ Factory.Markup("<p>"),
+ new ExpressionBlock(
+ Factory.CodeTransition(),
+ Factory.Code("item")
+ .AsImplicitExpression(VBCodeParser.DefaultKeywords)
+ .Accepts(AcceptedCharacters.NonWhiteSpace)),
+ Factory.Markup("</p>").Accepts(AcceptedCharacters.None))),
+ Factory.Code(")")
+ .AsImplicitExpression(VBCodeParser.DefaultKeywords)
+ .Accepts(AcceptedCharacters.NonWhiteSpace)),
+ Factory.Markup("</p>").Accepts(AcceptedCharacters.None)));
+ }
+
+ [Fact]
+ public void ParseBlockHandlesSimpleAnonymousSectionInExplicitExpressionParens()
+ {
+ ParseBlockTest("(Html.Repeat(10," + TestTemplateCode + "))",
+ new ExpressionBlock(
+ Factory.MetaCode("(").Accepts(AcceptedCharacters.None),
+ Factory.Code("Html.Repeat(10,").AsExpression(),
+ TestTemplate(),
+ Factory.Code(")").AsExpression(),
+ Factory.MetaCode(")").Accepts(AcceptedCharacters.None)));
+ }
+
+ [Fact]
+ public void ParseBlockHandlesSimpleAnonymousSectionInImplicitExpressionParens()
+ {
+ ParseBlockTest("Html.Repeat(10," + TestTemplateCode + ")",
+ new ExpressionBlock(
+ Factory.Code("Html.Repeat(10,").AsImplicitExpression(KeywordSet),
+ TestTemplate(),
+ Factory.Code(")").AsImplicitExpression(KeywordSet).Accepts(AcceptedCharacters.NonWhiteSpace)));
+ }
+
+ [Fact]
+ public void ParseBlockHandlesTwoAnonymousSectionsInImplicitExpressionParens()
+ {
+ ParseBlockTest("Html.Repeat(10," + TestTemplateCode + "," + TestTemplateCode + ")",
+ new ExpressionBlock(
+ Factory.Code("Html.Repeat(10,").AsImplicitExpression(KeywordSet),
+ TestTemplate(),
+ Factory.Code(",").AsImplicitExpression(KeywordSet),
+ TestTemplate(),
+ Factory.Code(")").AsImplicitExpression(KeywordSet).Accepts(AcceptedCharacters.NonWhiteSpace)));
+ }
+
+ [Fact]
+ public void ParseBlockProducesErrorButCorrectlyParsesNestedAnonymousSectionInImplicitExpressionParens()
+ {
+ ParseBlockTest("Html.Repeat(10," + TestNestedTemplateCode + ")",
+ new ExpressionBlock(
+ Factory.Code("Html.Repeat(10,").AsImplicitExpression(KeywordSet),
+ TestNestedTemplate(),
+ Factory.Code(")").AsImplicitExpression(KeywordSet).Accepts(AcceptedCharacters.NonWhiteSpace)),
+ GetNestedSectionError(41, 0, 41));
+ }
+
+ [Fact]
+ public void ParseBlockHandlesSimpleAnonymousSectionInStatementWithinCodeBlock()
+ {
+ ParseBlockTest(@"For Each foo in Bar
+ Html.ExecuteTemplate(foo," + TestTemplateCode + @")
+Next foo",
+ new StatementBlock(
+ Factory.Code("For Each foo in Bar \r\n Html.ExecuteTemplate(foo,")
+ .AsStatement(),
+ TestTemplate(),
+ Factory.Code(")\r\nNext foo")
+ .AsStatement()
+ .Accepts(AcceptedCharacters.WhiteSpace | AcceptedCharacters.NonWhiteSpace)));
+ }
+
+ [Fact]
+ public void ParseBlockHandlesTwoAnonymousSectionsInStatementWithinCodeBlock()
+ {
+ ParseBlockTest(@"For Each foo in Bar
+ Html.ExecuteTemplate(foo," + TestTemplateCode + "," + TestTemplateCode + @")
+Next foo",
+ new StatementBlock(
+ Factory.Code("For Each foo in Bar \r\n Html.ExecuteTemplate(foo,")
+ .AsStatement(),
+ TestTemplate(),
+ Factory.Code(",").AsStatement(),
+ TestTemplate(),
+ Factory.Code(")\r\nNext foo")
+ .AsStatement()
+ .Accepts(AcceptedCharacters.WhiteSpace | AcceptedCharacters.NonWhiteSpace)));
+ }
+
+ [Fact]
+ public void ParseBlockProducesErrorButCorrectlyParsesNestedAnonymousSectionInStatementWithinCodeBlock()
+ {
+ ParseBlockTest(@"For Each foo in Bar
+ Html.ExecuteTemplate(foo," + TestNestedTemplateCode + @")
+Next foo",
+ new StatementBlock(
+ Factory.Code("For Each foo in Bar \r\n Html.ExecuteTemplate(foo,")
+ .AsStatement(),
+ TestNestedTemplate(),
+ Factory.Code(")\r\nNext foo")
+ .AsStatement()
+ .Accepts(AcceptedCharacters.WhiteSpace | AcceptedCharacters.NonWhiteSpace)),
+ GetNestedSectionError(77, 1, 55));
+ }
+
+ [Fact]
+ public void ParseBlockHandlesSimpleAnonymousSectionInStatementWithinStatementBlock()
+ {
+ ParseBlockTest(@"Code
+ Dim foo = bar
+ Html.ExecuteTemplate(foo," + TestTemplateCode + @")
+End Code",
+ new StatementBlock(
+ Factory.MetaCode("Code").Accepts(AcceptedCharacters.None),
+ Factory.Code(" \r\n Dim foo = bar\r\n Html.ExecuteTemplate(foo,")
+ .AsStatement(),
+ TestTemplate(),
+ Factory.Code(")\r\n").AsStatement(),
+ Factory.MetaCode("End Code").Accepts(AcceptedCharacters.None)));
+ }
+
+ [Fact]
+ public void ParseBlockHandlessTwoAnonymousSectionsInStatementWithinStatementBlock()
+ {
+ ParseBlockTest(@"Code
+ Dim foo = bar
+ Html.ExecuteTemplate(foo," + TestTemplateCode + "," + TestTemplateCode + @")
+End Code",
+ new StatementBlock(
+ Factory.MetaCode("Code").Accepts(AcceptedCharacters.None),
+ Factory.Code("\r\n Dim foo = bar\r\n Html.ExecuteTemplate(foo,")
+ .AsStatement(),
+ TestTemplate(),
+ Factory.Code(",").AsStatement(),
+ TestTemplate(),
+ Factory.Code(")\r\n").AsStatement(),
+ Factory.MetaCode("End Code").Accepts(AcceptedCharacters.None)));
+ }
+
+ [Fact]
+ public void ParseBlockProducesErrorButCorrectlyParsesNestedAnonymousSectionInStatementWithinStatementBlock()
+ {
+ ParseBlockTest(@"Code
+ Dim foo = bar
+ Html.ExecuteTemplate(foo," + TestNestedTemplateCode + @")
+End Code",
+ new StatementBlock(
+ Factory.MetaCode("Code").Accepts(AcceptedCharacters.None),
+ Factory.Code("\r\n Dim foo = bar\r\n Html.ExecuteTemplate(foo,")
+ .AsStatement(),
+ TestNestedTemplate(),
+ Factory.Code(")\r\n").AsStatement(),
+ Factory.MetaCode("End Code").Accepts(AcceptedCharacters.None)),
+ GetNestedSectionError(80, 2, 55));
+ }
+
+ private static RazorError GetNestedSectionError(int absoluteIndex, int lineIndex, int characterIndex)
+ {
+ return new RazorError(
+ RazorResources.ParseError_InlineMarkup_Blocks_Cannot_Be_Nested,
+ absoluteIndex, lineIndex, characterIndex);
+ }
+ }
+}
diff --git a/test/System.Web.Razor.Test/Parser/VB/VBToMarkupSwitchTest.cs b/test/System.Web.Razor.Test/Parser/VB/VBToMarkupSwitchTest.cs
new file mode 100644
index 00000000..b8568626
--- /dev/null
+++ b/test/System.Web.Razor.Test/Parser/VB/VBToMarkupSwitchTest.cs
@@ -0,0 +1,103 @@
+using System.Web.Razor.Editor;
+using System.Web.Razor.Parser;
+using System.Web.Razor.Parser.SyntaxTree;
+using System.Web.Razor.Test.Framework;
+using System.Web.Razor.Tokenizer.Symbols;
+using Xunit;
+using Xunit.Extensions;
+
+namespace System.Web.Razor.Test.Parser.VB
+{
+ public class VBToMarkupSwitchTest : VBHtmlCodeParserTestBase
+ {
+ [Fact]
+ public void ParseBlockSwitchesToMarkupWhenAtSignFollowedByLessThanInStatementBlock()
+ {
+ ParseBlockTest(@"Code
+ If True Then
+ @<p>It's True!</p>
+ End If
+End Code",
+ new StatementBlock(
+ Factory.MetaCode("Code").Accepts(AcceptedCharacters.None),
+ Factory.Code("\r\n If True Then\r\n").AsStatement(),
+ new MarkupBlock(
+ Factory.Markup(" "),
+ Factory.MarkupTransition(),
+ Factory.Markup("<p>It's True!</p>\r\n").Accepts(AcceptedCharacters.None)),
+ Factory.Code(" End If\r\n").AsStatement(),
+ Factory.MetaCode("End Code").Accepts(AcceptedCharacters.None)));
+ }
+
+ [Fact]
+ public void ParseBlockGivesWhiteSpacePreceedingMarkupBlockToCodeInDesignTimeMode()
+ {
+ ParseBlockTest(@"Code
+ @<p>Foo</p>
+End Code",
+ new StatementBlock(
+ Factory.MetaCode("Code").Accepts(AcceptedCharacters.None),
+ Factory.Code("\r\n ").AsStatement(),
+ new MarkupBlock(
+ Factory.MarkupTransition(),
+ Factory.Markup("<p>Foo</p>").Accepts(AcceptedCharacters.None)),
+ Factory.Code("\r\n").AsStatement(),
+ Factory.MetaCode("End Code").Accepts(AcceptedCharacters.None)),
+ designTimeParser: true);
+ }
+
+ [Theory]
+ [InlineData("While", "End While", AcceptedCharacters.None)]
+ [InlineData("If", "End If", AcceptedCharacters.None)]
+ [InlineData("Select", "End Select", AcceptedCharacters.None)]
+ [InlineData("For", "Next", AcceptedCharacters.WhiteSpace | AcceptedCharacters.NonWhiteSpace)]
+ [InlineData("Try", "End Try", AcceptedCharacters.None)]
+ [InlineData("With", "End With", AcceptedCharacters.None)]
+ [InlineData("Using", "End Using", AcceptedCharacters.None)]
+ public void SimpleMarkupSwitch(string keyword, string endSequence, AcceptedCharacters acceptedCharacters)
+ {
+ ParseBlockTest(keyword + @"
+ If True Then
+ @<p>It's True!</p>
+ End If
+" + endSequence,
+ new StatementBlock(
+ Factory.Code(keyword + "\r\n If True Then\r\n").AsStatement(),
+ new MarkupBlock(
+ Factory.Markup(" "),
+ Factory.MarkupTransition(),
+ Factory.Markup("<p>It's True!</p>\r\n").Accepts(AcceptedCharacters.None)),
+ Factory.Code(" End If\r\n" + endSequence).AsStatement().Accepts(acceptedCharacters)));
+ }
+
+ [Theory]
+ [InlineData("While", "End While", AcceptedCharacters.None)]
+ [InlineData("If", "End If", AcceptedCharacters.None)]
+ [InlineData("Select", "End Select", AcceptedCharacters.None)]
+ [InlineData("For", "Next", AcceptedCharacters.WhiteSpace | AcceptedCharacters.NonWhiteSpace)]
+ [InlineData("Try", "End Try", AcceptedCharacters.None)]
+ [InlineData("With", "End With", AcceptedCharacters.None)]
+ [InlineData("Using", "End Using", AcceptedCharacters.None)]
+ public void SingleLineMarkupSwitch(string keyword, string endSequence, AcceptedCharacters acceptedCharacters)
+ {
+ ParseBlockTest(keyword + @"
+ If True Then
+ @:<p>It's True!</p>
+ This is code!
+ End If
+" + endSequence,
+ new StatementBlock(
+ Factory.Code(keyword + "\r\n If True Then\r\n").AsStatement(),
+ new MarkupBlock(
+ Factory.Markup(" "),
+ Factory.MarkupTransition(),
+ Factory.MetaMarkup(":", HtmlSymbolType.Colon),
+ Factory.Markup("<p>It's True!</p>\r\n")
+ .With(new SingleLineMarkupEditHandler(CSharpLanguageCharacteristics.Instance.TokenizeString))
+ .Accepts(AcceptedCharacters.None)),
+ Factory.Code(" This is code!\r\n End If\r\n" + endSequence)
+ .AsStatement()
+ .Accepts(acceptedCharacters)));
+ }
+ }
+}
diff --git a/test/System.Web.Razor.Test/Parser/WhitespaceRewriterTest.cs b/test/System.Web.Razor.Test/Parser/WhitespaceRewriterTest.cs
new file mode 100644
index 00000000..146312a4
--- /dev/null
+++ b/test/System.Web.Razor.Test/Parser/WhitespaceRewriterTest.cs
@@ -0,0 +1,50 @@
+using System.Web.Razor.Parser;
+using System.Web.Razor.Parser.SyntaxTree;
+using System.Web.Razor.Test.Framework;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Razor.Test.Parser
+{
+ public class WhitespaceRewriterTest
+ {
+ [Fact]
+ public void Constructor_Requires_NonNull_SymbolConverter()
+ {
+ Assert.ThrowsArgumentNull(() => new WhiteSpaceRewriter(null), "markupSpanFactory");
+ }
+
+ [Fact]
+ public void Rewrite_Moves_Whitespace_Preceeding_ExpressionBlock_To_Parent_Block()
+ {
+ // Arrange
+ var factory = SpanFactory.CreateCsHtml();
+ Block start = new MarkupBlock(
+ factory.Markup("test"),
+ new ExpressionBlock(
+ factory.Code(" ").AsExpression(),
+ factory.CodeTransition(SyntaxConstants.TransitionString),
+ factory.Code("foo").AsExpression()
+ ),
+ factory.Markup("test")
+ );
+ WhiteSpaceRewriter rewriter = new WhiteSpaceRewriter(new HtmlMarkupParser().BuildSpan);
+
+ // Act
+ Block actual = rewriter.Rewrite(start);
+
+ factory.Reset();
+
+ // Assert
+ ParserTestBase.EvaluateParseTree(actual, new MarkupBlock(
+ factory.Markup("test"),
+ factory.Markup(" "),
+ new ExpressionBlock(
+ factory.CodeTransition(SyntaxConstants.TransitionString),
+ factory.Code("foo").AsExpression()
+ ),
+ factory.Markup("test")
+ ));
+ }
+ }
+}
diff --git a/test/System.Web.Razor.Test/Properties/AssemblyInfo.cs b/test/System.Web.Razor.Test/Properties/AssemblyInfo.cs
new file mode 100644
index 00000000..7256e213
--- /dev/null
+++ b/test/System.Web.Razor.Test/Properties/AssemblyInfo.cs
@@ -0,0 +1,34 @@
+using System.Reflection;
+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("System.Web.Razor.Test")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("Microsoft")]
+[assembly: AssemblyProduct("System.Web.Razor.Test")]
+[assembly: AssemblyCopyright("Copyright © Microsoft 2009")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Setting ComVisible to false makes the types in this assembly not visible
+// to COM componenets. If you need to access a type in this assembly from
+// COM, set the ComVisible attribute to true on that type.
+
+[assembly: ComVisible(false)]
+
+// 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 Revision and Build Numbers
+// by using the '*' as shown below:
+
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
diff --git a/test/System.Web.Razor.Test/RazorCodeLanguageTest.cs b/test/System.Web.Razor.Test/RazorCodeLanguageTest.cs
new file mode 100644
index 00000000..2dab4b84
--- /dev/null
+++ b/test/System.Web.Razor.Test/RazorCodeLanguageTest.cs
@@ -0,0 +1,47 @@
+using Xunit;
+
+namespace System.Web.Razor.Test
+{
+ public class RazorCodeLanguageTest
+ {
+ [Fact]
+ public void ServicesPropertyContainsEntriesForCSharpCodeLanguageService()
+ {
+ // Assert
+ Assert.Equal(2, RazorCodeLanguage.Languages.Count);
+ Assert.IsType<CSharpRazorCodeLanguage>(RazorCodeLanguage.Languages["cshtml"]);
+ Assert.IsType<VBRazorCodeLanguage>(RazorCodeLanguage.Languages["vbhtml"]);
+ }
+
+ [Fact]
+ public void GetServiceByExtensionReturnsEntryMatchingExtensionWithoutPreceedingDot()
+ {
+ Assert.IsType<CSharpRazorCodeLanguage>(RazorCodeLanguage.GetLanguageByExtension("cshtml"));
+ }
+
+ [Fact]
+ public void GetServiceByExtensionReturnsEntryMatchingExtensionWithPreceedingDot()
+ {
+ Assert.IsType<CSharpRazorCodeLanguage>(RazorCodeLanguage.GetLanguageByExtension(".cshtml"));
+ }
+
+ [Fact]
+ public void GetServiceByExtensionReturnsNullIfNoServiceForSpecifiedExtension()
+ {
+ Assert.Null(RazorCodeLanguage.GetLanguageByExtension("foobar"));
+ }
+
+ [Fact]
+ public void MultipleCallsToGetServiceWithSameExtensionReturnSameObject()
+ {
+ // Arrange
+ RazorCodeLanguage expected = RazorCodeLanguage.GetLanguageByExtension("cshtml");
+
+ // Act
+ RazorCodeLanguage actual = RazorCodeLanguage.GetLanguageByExtension("cshtml");
+
+ // Assert
+ Assert.Same(expected, actual);
+ }
+ }
+}
diff --git a/test/System.Web.Razor.Test/RazorDirectiveAttributeTest.cs b/test/System.Web.Razor.Test/RazorDirectiveAttributeTest.cs
new file mode 100644
index 00000000..d26ec878
--- /dev/null
+++ b/test/System.Web.Razor.Test/RazorDirectiveAttributeTest.cs
@@ -0,0 +1,79 @@
+using System.Linq;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Razor.Test
+{
+ public class RazorDirectiveAttributeTest
+ {
+ [Fact]
+ public void ConstructorThrowsIfNameIsNullOrEmpty()
+ {
+ // Act and Assert
+ Assert.ThrowsArgumentNullOrEmptyString(() => new RazorDirectiveAttribute(name: null, value: "blah"), "name");
+ Assert.ThrowsArgumentNullOrEmptyString(() => new RazorDirectiveAttribute(name: "", value: "blah"), "name");
+ }
+
+ [Fact]
+ public void EnsureRazorDirectiveProperties()
+ {
+ // Arrange
+ var attribute = (AttributeUsageAttribute)typeof(RazorDirectiveAttribute).GetCustomAttributes(typeof(AttributeUsageAttribute), inherit: false)
+ .SingleOrDefault();
+
+ // Assert
+ Assert.True(attribute.AllowMultiple);
+ Assert.True(attribute.ValidOn == AttributeTargets.Class);
+ Assert.True(attribute.Inherited);
+ }
+
+ [Fact]
+ public void EqualsAndGetHashCodeIgnoresCase()
+ {
+ // Arrange
+ var attribute1 = new RazorDirectiveAttribute("foo", "bar");
+ var attribute2 = new RazorDirectiveAttribute("fOo", "BAr");
+
+ // Act
+ var hashCode1 = attribute1.GetHashCode();
+ var hashCode2 = attribute2.GetHashCode();
+
+ // Assert
+ Assert.Equal(attribute1, attribute2);
+ Assert.Equal(hashCode1, hashCode2);
+ }
+
+ [Fact]
+ public void EqualsAndGetHashCodeDoNotThrowIfValueIsNullOrEmpty()
+ {
+ // Arrange
+ var attribute1 = new RazorDirectiveAttribute("foo", null);
+ var attribute2 = new RazorDirectiveAttribute("foo", "BAr");
+
+ // Act
+ bool result = attribute1.Equals(attribute2);
+ var hashCode = attribute1.GetHashCode();
+
+ // Assert
+ Assert.False(result);
+ // If we've got this far, GetHashCode did not throw
+ }
+
+ [Fact]
+ public void EqualsAndGetHashCodeReturnDifferentValuesForNullAndEmpty()
+ {
+ // Arrange
+ var attribute1 = new RazorDirectiveAttribute("foo", null);
+ var attribute2 = new RazorDirectiveAttribute("foo", "");
+
+ // Act
+ bool result = attribute1.Equals(attribute2);
+ var hashCode1 = attribute1.GetHashCode();
+ var hashCode2 = attribute2.GetHashCode();
+
+ // Assert
+ Assert.False(result);
+ Assert.NotEqual(hashCode1, hashCode2);
+ }
+ }
+}
diff --git a/test/System.Web.Razor.Test/RazorEngineHostTest.cs b/test/System.Web.Razor.Test/RazorEngineHostTest.cs
new file mode 100644
index 00000000..1385fec8
--- /dev/null
+++ b/test/System.Web.Razor.Test/RazorEngineHostTest.cs
@@ -0,0 +1,186 @@
+using System.CodeDom;
+using System.Web.Razor.Generator;
+using System.Web.Razor.Parser;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Razor.Test
+{
+ public class RazorEngineHostTest
+ {
+ [Fact]
+ public void ConstructorRequiresNonNullCodeLanguage()
+ {
+ Assert.ThrowsArgumentNull(() => new RazorEngineHost(null), "codeLanguage");
+ Assert.ThrowsArgumentNull(() => new RazorEngineHost(null, () => new HtmlMarkupParser()), "codeLanguage");
+ }
+
+ [Fact]
+ public void ConstructorRequiresNonNullMarkupParser()
+ {
+ Assert.ThrowsArgumentNull(() => new RazorEngineHost(new CSharpRazorCodeLanguage(), null), "markupParserFactory");
+ }
+
+ [Fact]
+ public void ConstructorWithCodeLanguageSetsPropertiesAppropriately()
+ {
+ // Arrange
+ RazorCodeLanguage language = new CSharpRazorCodeLanguage();
+
+ // Act
+ RazorEngineHost host = new RazorEngineHost(language);
+
+ // Assert
+ VerifyCommonDefaults(host);
+ Assert.Same(language, host.CodeLanguage);
+ Assert.IsType<HtmlMarkupParser>(host.CreateMarkupParser());
+ }
+
+ [Fact]
+ public void ConstructorWithCodeLanguageAndMarkupParserSetsPropertiesAppropriately()
+ {
+ // Arrange
+ RazorCodeLanguage language = new CSharpRazorCodeLanguage();
+ ParserBase expected = new HtmlMarkupParser();
+
+ // Act
+ RazorEngineHost host = new RazorEngineHost(language, () => expected);
+
+ // Assert
+ VerifyCommonDefaults(host);
+ Assert.Same(language, host.CodeLanguage);
+ Assert.Same(expected, host.CreateMarkupParser());
+ }
+
+ [Fact]
+ public void DecorateCodeParserRequiresNonNullCodeParser()
+ {
+ Assert.ThrowsArgumentNull(() => CreateHost().DecorateCodeParser(null), "incomingCodeParser");
+ }
+
+ [Fact]
+ public void DecorateMarkupParserRequiresNonNullMarkupParser()
+ {
+ Assert.ThrowsArgumentNull(() => CreateHost().DecorateMarkupParser(null), "incomingMarkupParser");
+ }
+
+ [Fact]
+ public void DecorateCodeGeneratorRequiresNonNullCodeGenerator()
+ {
+ Assert.ThrowsArgumentNull(() => CreateHost().DecorateCodeGenerator(null), "incomingCodeGenerator");
+ }
+
+ [Fact]
+ public void PostProcessGeneratedCodeRequiresNonNullCompileUnit()
+ {
+ Assert.ThrowsArgumentNull(() => CreateHost().PostProcessGeneratedCode(codeCompileUnit: null,
+ generatedNamespace: new CodeNamespace(),
+ generatedClass: new CodeTypeDeclaration(),
+ executeMethod: new CodeMemberMethod()),
+ "codeCompileUnit");
+ }
+
+ [Fact]
+ public void PostProcessGeneratedCodeRequiresNonNullGeneratedNamespace()
+ {
+ Assert.ThrowsArgumentNull(() => CreateHost().PostProcessGeneratedCode(codeCompileUnit: new CodeCompileUnit(),
+ generatedNamespace: null,
+ generatedClass: new CodeTypeDeclaration(),
+ executeMethod: new CodeMemberMethod()),
+ "generatedNamespace");
+ }
+
+ [Fact]
+ public void PostProcessGeneratedCodeRequiresNonNullGeneratedClass()
+ {
+ Assert.ThrowsArgumentNull(() => CreateHost().PostProcessGeneratedCode(codeCompileUnit: new CodeCompileUnit(),
+ generatedNamespace: new CodeNamespace(),
+ generatedClass: null,
+ executeMethod: new CodeMemberMethod()),
+ "generatedClass");
+ }
+
+ [Fact]
+ public void PostProcessGeneratedCodeRequiresNonNullExecuteMethod()
+ {
+ Assert.ThrowsArgumentNull(() => CreateHost().PostProcessGeneratedCode(codeCompileUnit: new CodeCompileUnit(),
+ generatedNamespace: new CodeNamespace(),
+ generatedClass: new CodeTypeDeclaration(),
+ executeMethod: null),
+ "executeMethod");
+ }
+
+ [Fact]
+ public void DecorateCodeParserDoesNotModifyIncomingParser()
+ {
+ // Arrange
+ ParserBase expected = new CSharpCodeParser();
+
+ // Act
+ ParserBase actual = CreateHost().DecorateCodeParser(expected);
+
+ // Assert
+ Assert.Same(expected, actual);
+ }
+
+ [Fact]
+ public void DecorateMarkupParserReturnsIncomingParser()
+ {
+ // Arrange
+ ParserBase expected = new HtmlMarkupParser();
+
+ // Act
+ ParserBase actual = CreateHost().DecorateMarkupParser(expected);
+
+ // Assert
+ Assert.Same(expected, actual);
+ }
+
+ [Fact]
+ public void DecorateCodeGeneratorReturnsIncomingCodeGenerator()
+ {
+ // Arrange
+ RazorCodeGenerator expected = new CSharpRazorCodeGenerator("Foo", "Bar", "Baz", CreateHost());
+
+ // Act
+ RazorCodeGenerator actual = CreateHost().DecorateCodeGenerator(expected);
+
+ // Assert
+ Assert.Same(expected, actual);
+ }
+
+ [Fact]
+ public void PostProcessGeneratedCodeDoesNotModifyCode()
+ {
+ // Arrange
+ CodeCompileUnit compileUnit = new CodeCompileUnit();
+ CodeNamespace ns = new CodeNamespace();
+ CodeTypeDeclaration typeDecl = new CodeTypeDeclaration();
+ CodeMemberMethod execMethod = new CodeMemberMethod();
+
+ // Act
+ CreateHost().PostProcessGeneratedCode(compileUnit, ns, typeDecl, execMethod);
+
+ // Assert
+ Assert.Empty(compileUnit.Namespaces);
+ Assert.Empty(ns.Imports);
+ Assert.Empty(ns.Types);
+ Assert.Empty(typeDecl.Members);
+ Assert.Empty(execMethod.Statements);
+ }
+
+ private static RazorEngineHost CreateHost()
+ {
+ return new RazorEngineHost(new CSharpRazorCodeLanguage());
+ }
+
+ private static void VerifyCommonDefaults(RazorEngineHost host)
+ {
+ Assert.Equal(GeneratedClassContext.Default, host.GeneratedClassContext);
+ Assert.Empty(host.NamespaceImports);
+ Assert.False(host.DesignTimeMode);
+ Assert.Equal(RazorEngineHost.InternalDefaultClassName, host.DefaultClassName);
+ Assert.Equal(RazorEngineHost.InternalDefaultNamespace, host.DefaultNamespace);
+ }
+ }
+}
diff --git a/test/System.Web.Razor.Test/RazorTemplateEngineTest.cs b/test/System.Web.Razor.Test/RazorTemplateEngineTest.cs
new file mode 100644
index 00000000..1f3d4fff
--- /dev/null
+++ b/test/System.Web.Razor.Test/RazorTemplateEngineTest.cs
@@ -0,0 +1,197 @@
+using System.IO;
+using System.Threading;
+using System.Web.Razor.Generator;
+using System.Web.Razor.Parser;
+using System.Web.Razor.Text;
+using System.Web.WebPages.TestUtils;
+using Moq;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Razor.Test
+{
+ public class RazorTemplateEngineTest
+ {
+ [Fact]
+ public void ConstructorRequiresNonNullHost()
+ {
+ Assert.ThrowsArgumentNull(() => new RazorTemplateEngine(null), "host");
+ }
+
+ [Fact]
+ public void ConstructorInitializesHost()
+ {
+ // Arrange
+ RazorEngineHost host = new RazorEngineHost(new CSharpRazorCodeLanguage());
+
+ // Act
+ RazorTemplateEngine engine = new RazorTemplateEngine(host);
+
+ // Assert
+ Assert.Same(host, engine.Host);
+ }
+
+ [Fact]
+ public void CreateParserMethodIsConstructedFromHost()
+ {
+ // Arrange
+ RazorEngineHost host = CreateHost();
+ RazorTemplateEngine engine = new RazorTemplateEngine(host);
+
+ // Act
+ RazorParser parser = engine.CreateParser();
+
+ // Assert
+ Assert.IsType<CSharpCodeParser>(parser.CodeParser);
+ Assert.IsType<HtmlMarkupParser>(parser.MarkupParser);
+ }
+
+ [Fact]
+ public void CreateParserMethodSetsParserContextToDesignTimeModeIfHostSetToDesignTimeMode()
+ {
+ // Arrange
+ RazorEngineHost host = CreateHost();
+ RazorTemplateEngine engine = new RazorTemplateEngine(host);
+ host.DesignTimeMode = true;
+
+ // Act
+ RazorParser parser = engine.CreateParser();
+
+ // Assert
+ Assert.True(parser.DesignTimeMode);
+ }
+
+ [Fact]
+ public void CreateParserMethodPassesParsersThroughDecoratorMethodsOnHost()
+ {
+ // Arrange
+ ParserBase expectedCode = new Mock<ParserBase>().Object;
+ ParserBase expectedMarkup = new Mock<ParserBase>().Object;
+
+ var mockHost = new Mock<RazorEngineHost>(new CSharpRazorCodeLanguage()) { CallBase = true };
+ mockHost.Setup(h => h.DecorateCodeParser(It.IsAny<CSharpCodeParser>()))
+ .Returns(expectedCode);
+ mockHost.Setup(h => h.DecorateMarkupParser(It.IsAny<HtmlMarkupParser>()))
+ .Returns(expectedMarkup);
+ RazorTemplateEngine engine = new RazorTemplateEngine(mockHost.Object);
+
+ // Act
+ RazorParser actual = engine.CreateParser();
+
+ // Assert
+ Assert.Equal(expectedCode, actual.CodeParser);
+ Assert.Equal(expectedMarkup, actual.MarkupParser);
+ }
+
+ [Fact]
+ public void CreateCodeGeneratorMethodPassesCodeGeneratorThroughDecorateMethodOnHost()
+ {
+ // Arrange
+ var mockHost = new Mock<RazorEngineHost>(new CSharpRazorCodeLanguage()) { CallBase = true };
+
+ RazorCodeGenerator expected = new Mock<RazorCodeGenerator>("Foo", "Bar", "Baz", mockHost.Object).Object;
+
+ mockHost.Setup(h => h.DecorateCodeGenerator(It.IsAny<CSharpRazorCodeGenerator>()))
+ .Returns(expected);
+ RazorTemplateEngine engine = new RazorTemplateEngine(mockHost.Object);
+
+ // Act
+ RazorCodeGenerator actual = engine.CreateCodeGenerator("Foo", "Bar", "Baz");
+
+ // Assert
+ Assert.Equal(expected, actual);
+ }
+
+ [Fact]
+ public void ParseTemplateCopiesTextReaderContentToSeekableTextReaderAndPassesToParseTemplateCore()
+ {
+ // Arrange
+ Mock<RazorTemplateEngine> mockEngine = new Mock<RazorTemplateEngine>(CreateHost());
+ TextReader reader = new StringReader("foo");
+ CancellationTokenSource source = new CancellationTokenSource();
+
+ // Act
+ mockEngine.Object.ParseTemplate(reader, cancelToken: source.Token);
+
+ // Assert
+ mockEngine.Verify(e => e.ParseTemplateCore(It.Is<SeekableTextReader>(l => l.ReadToEnd() == "foo"),
+ source.Token));
+ }
+
+ [Fact]
+ public void GenerateCodeCopiesTextReaderContentToSeekableTextReaderAndPassesToGenerateCodeCore()
+ {
+ // Arrange
+ Mock<RazorTemplateEngine> mockEngine = new Mock<RazorTemplateEngine>(CreateHost());
+ TextReader reader = new StringReader("foo");
+ CancellationTokenSource source = new CancellationTokenSource();
+ string className = "Foo";
+ string ns = "Bar";
+ string src = "Baz";
+
+ // Act
+ mockEngine.Object.GenerateCode(reader, className: className, rootNamespace: ns, sourceFileName: src, cancelToken: source.Token);
+
+ // Assert
+ mockEngine.Verify(e => e.GenerateCodeCore(It.Is<SeekableTextReader>(l => l.ReadToEnd() == "foo"),
+ className, ns, src, source.Token));
+ }
+
+ [Fact]
+ public void ParseTemplateOutputsResultsOfParsingProvidedTemplateSource()
+ {
+ // Arrange
+ RazorTemplateEngine engine = new RazorTemplateEngine(CreateHost());
+
+ // Act
+ ParserResults results = engine.ParseTemplate(new StringTextBuffer("foo @bar("));
+
+ // Assert
+ Assert.False(results.Success);
+ Assert.Single(results.ParserErrors);
+ Assert.NotNull(results.Document);
+ }
+
+ [Fact]
+ public void GenerateOutputsResultsOfParsingAndGeneration()
+ {
+ // Arrange
+ RazorTemplateEngine engine = new RazorTemplateEngine(CreateHost());
+
+ // Act
+ GeneratorResults results = engine.GenerateCode(new StringTextBuffer("foo @bar("));
+
+ // Assert
+ Assert.False(results.Success);
+ Assert.Single(results.ParserErrors);
+ Assert.NotNull(results.Document);
+ Assert.NotNull(results.GeneratedCode);
+ Assert.Null(results.DesignTimeLineMappings);
+ }
+
+ [Fact]
+ public void GenerateOutputsDesignTimeMappingsIfDesignTimeSetOnHost()
+ {
+ // Arrange
+ RazorTemplateEngine engine = new RazorTemplateEngine(CreateHost(designTime: true));
+
+ // Act
+ GeneratorResults results = engine.GenerateCode(new StringTextBuffer("foo @bar()"), className: null, rootNamespace: null, sourceFileName: "foo.cshtml");
+
+ // Assert
+ Assert.True(results.Success);
+ Assert.Empty(results.ParserErrors);
+ Assert.NotNull(results.Document);
+ Assert.NotNull(results.GeneratedCode);
+ Assert.NotNull(results.DesignTimeLineMappings);
+ }
+
+ private static RazorEngineHost CreateHost(bool designTime = false)
+ {
+ return new RazorEngineHost(new CSharpRazorCodeLanguage())
+ {
+ DesignTimeMode = designTime
+ };
+ }
+ }
+}
diff --git a/test/System.Web.Razor.Test/StringTextBuffer.cs b/test/System.Web.Razor.Test/StringTextBuffer.cs
new file mode 100644
index 00000000..d94ddfba
--- /dev/null
+++ b/test/System.Web.Razor.Test/StringTextBuffer.cs
@@ -0,0 +1,50 @@
+using System.Web.Razor.Text;
+
+namespace System.Web.WebPages.TestUtils
+{
+ public class StringTextBuffer : ITextBuffer, IDisposable
+ {
+ private string _buffer;
+ public bool Disposed { get; set; }
+
+ public StringTextBuffer(string buffer)
+ {
+ _buffer = buffer;
+ }
+
+ public int Length
+ {
+ get { return _buffer.Length; }
+ }
+
+ public int Position { get; set; }
+
+ public int Read()
+ {
+ if (Position >= _buffer.Length)
+ {
+ return -1;
+ }
+ return _buffer[Position++];
+ }
+
+ public int Peek()
+ {
+ if (Position >= _buffer.Length)
+ {
+ return -1;
+ }
+ return _buffer[Position];
+ }
+
+ public void Dispose()
+ {
+ Disposed = true;
+ }
+
+ public object VersionToken
+ {
+ get { return _buffer; }
+ }
+ }
+}
diff --git a/test/System.Web.Razor.Test/System.Web.Razor.Test.csproj b/test/System.Web.Razor.Test/System.Web.Razor.Test.csproj
new file mode 100644
index 00000000..f4dedfeb
--- /dev/null
+++ b/test/System.Web.Razor.Test/System.Web.Razor.Test.csproj
@@ -0,0 +1,464 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory),Runtime.sln))\tools\WebStack.settings.targets" />
+ <PropertyGroup>
+ <!-- Temporarily disable Obsolete Warnings as Errors -->
+ <WarningsNotAsErrors>618</WarningsNotAsErrors>
+ <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+ <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+ <ProductVersion>9.0.30729</ProductVersion>
+ <SchemaVersion>2.0</SchemaVersion>
+ <ProjectGuid>{0BB62A1D-E6B5-49FA-9E3C-6AF679A66DFE}</ProjectGuid>
+ <OutputType>Library</OutputType>
+ <AppDesignerFolder>Properties</AppDesignerFolder>
+ <RootNamespace>System.Web.Razor.Test</RootNamespace>
+ <AssemblyName>System.Web.Razor.Test</AssemblyName>
+ <FileAlignment>512</FileAlignment>
+ <ProjectTypeGuids>{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
+ <ErrorReport>prompt</ErrorReport>
+ <WarningLevel>4</WarningLevel>
+ <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
+ <DebugSymbols>true</DebugSymbols>
+ <DebugType>full</DebugType>
+ <Optimize>false</Optimize>
+ <OutputPath>$(WebStackRootPath)\bin\Debug\Test\</OutputPath>
+ <DefineConstants>TRACE;DEBUG</DefineConstants>
+ <CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
+ <NoWarn>0618</NoWarn>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
+ <DebugType>pdbonly</DebugType>
+ <Optimize>true</Optimize>
+ <OutputPath>$(WebStackRootPath)\bin\Release\Test\</OutputPath>
+ <DefineConstants>TRACE</DefineConstants>
+ <CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
+ <NoWarn>0618</NoWarn>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)' == 'CodeCoverage' ">
+ <DebugSymbols>true</DebugSymbols>
+ <DebugType>full</DebugType>
+ <Optimize>false</Optimize>
+ <OutputPath>$(WebStackRootPath)\bin\CodeCoverage\Test\</OutputPath>
+ <DefineConstants>TRACE;DEBUG</DefineConstants>
+ <CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
+ <NoWarn>0618</NoWarn>
+ </PropertyGroup>
+ <ItemGroup>
+ <Reference Include="Microsoft.CSharp" />
+ <Reference Include="Moq">
+ <HintPath>..\..\packages\Moq.4.0.10827\lib\NET40\Moq.dll</HintPath>
+ </Reference>
+ <Reference Include="System" />
+ <Reference Include="System.Core">
+ <RequiredTargetFramework>3.5</RequiredTargetFramework>
+ </Reference>
+ <Reference Include="xunit">
+ <HintPath>..\..\packages\xunit.1.9.0.1566\lib\xunit.dll</HintPath>
+ </Reference>
+ <Reference Include="xunit.extensions">
+ <HintPath>..\..\packages\xunit.extensions.1.9.0.1566\lib\xunit.extensions.dll</HintPath>
+ </Reference>
+ </ItemGroup>
+ <ItemGroup>
+ <Compile Include="CodeCompileUnitExtensions.cs" />
+ <Compile Include="Framework\BlockExtensions.cs" />
+ <Compile Include="Framework\BlockTypes.cs" />
+ <Compile Include="Framework\CodeParserTestBase.cs" />
+ <Compile Include="Framework\CsHtmlCodeParserTestBase.cs" />
+ <Compile Include="Framework\CsHtmlMarkupParserTestBase.cs" />
+ <Compile Include="Framework\ErrorCollector.cs" />
+ <Compile Include="Framework\MarkupParserTestBase.cs" />
+ <Compile Include="Framework\ParserTestBase.cs" />
+ <Compile Include="Framework\RawTextSymbol.cs" />
+ <Compile Include="Framework\TestSpanBuilder.cs" />
+ <Compile Include="Framework\VBHtmlCodeParserTestBase.cs" />
+ <Compile Include="Framework\VBHtmlMarkupParserTestBase.cs" />
+ <Compile Include="Generator\GeneratedCodeMappingTest.cs" />
+ <Compile Include="Parser\CSharp\CSharpAutoCompleteTest.cs" />
+ <Compile Include="Parser\CSharp\CSharpDirectivesTest.cs" />
+ <Compile Include="Parser\CSharp\CSharpLayoutDirectiveTest.cs" />
+ <Compile Include="Parser\CSharp\CSharpNestedStatementsTest.cs" />
+ <Compile Include="Parser\CSharp\CSharpRazorCommentsTest.cs" />
+ <Compile Include="Parser\CSharp\CSharpStatementTest.cs" />
+ <Compile Include="Parser\CSharp\CSharpTemplateTest.cs" />
+ <Compile Include="Parser\CSharp\CSharpToMarkupSwitchTest.cs">
+ <SubType>Code</SubType>
+ </Compile>
+ <Compile Include="Parser\CSharp\CSharpVerbatimBlockTest.cs">
+ <SubType>Code</SubType>
+ </Compile>
+ <Compile Include="Parser\CSharp\CSharpWhitespaceHandlingTest.cs" />
+ <Compile Include="Parser\CSharp\CsHtmlDocumentTest.cs">
+ <SubType>Code</SubType>
+ </Compile>
+ <Compile Include="Parser\Html\HtmlAttributeTest.cs" />
+ <Compile Include="Parser\Html\HtmlUrlAttributeTest.cs" />
+ <Compile Include="Parser\ParserVisitorExtensionsTest.cs" />
+ <Compile Include="Parser\VB\VBRazorCommentsTest.cs" />
+ <Compile Include="Parser\VB\VBLayoutDirectiveTest.cs" />
+ <Compile Include="Parser\VB\VBAutoCompleteTest.cs" />
+ <Compile Include="Parser\Html\HtmlTagsTest.cs" />
+ <Compile Include="Parser\VB\VBContinueStatementTest.cs" />
+ <Compile Include="Parser\VB\VBDirectiveTest.cs" />
+ <Compile Include="Parser\VB\VBExitStatementTest.cs" />
+ <Compile Include="Parser\VB\VBExplicitExpressionTest.cs" />
+ <Compile Include="Parser\VB\VBImplicitExpressionTest.cs" />
+ <Compile Include="Parser\VB\VBReservedWordsTest.cs" />
+ <Compile Include="Parser\CSharp\CSharpReservedWordsTest.cs" />
+ <Compile Include="Parser\PartialParsing\VBPartialParsingTest.cs" />
+ <Compile Include="Parser\PartialParsing\CSharpPartialParsingTest.cs" />
+ <Compile Include="Parser\VB\VBHelperTest.cs" />
+ <Compile Include="Parser\CSharp\CSharpHelperTest.cs" />
+ <Compile Include="Generator\RazorCodeGeneratorTest.cs" />
+ <Compile Include="Parser\PartialParsing\PartialParsingTestBase.cs" />
+ <Compile Include="Parser\VB\VBNestedStatementsTest.cs" />
+ <Compile Include="Parser\VB\VBStatementTest.cs" />
+ <Compile Include="Parser\WhitespaceRewriterTest.cs" />
+ <Compile Include="RazorCodeLanguageTest.cs" />
+ <Compile Include="Parser\VB\VBHtmlDocumentTest.cs" />
+ <Compile Include="Parser\VB\VBErrorTest.cs" />
+ <Compile Include="Parser\VB\VBSectionTest.cs" />
+ <Compile Include="Parser\VB\VBTemplateTest.cs" />
+ <Compile Include="Parser\VB\VBExpressionsInCodeTest.cs" />
+ <Compile Include="Parser\VB\VBSpecialKeywordsTest.cs" />
+ <Compile Include="Parser\VB\VBBlockTest.cs" />
+ <Compile Include="Parser\VB\VBToMarkupSwitchTest.cs" />
+ <Compile Include="Editor\RazorEditorParserTest.cs" />
+ <Compile Include="RazorDirectiveAttributeTest.cs" />
+ <Compile Include="RazorEngineHostTest.cs" />
+ <Compile Include="RazorTemplateEngineTest.cs" />
+ <EmbeddedResource Include="TestFiles\CodeGenerator\CS\Output\Blocks.cs" />
+ <EmbeddedResource Include="TestFiles\CodeGenerator\CS\Output\CodeBlock.cs" />
+ <EmbeddedResource Include="TestFiles\CodeGenerator\CS\Output\CodeBlockAtEOF.cs" />
+ <EmbeddedResource Include="TestFiles\CodeGenerator\CS\Output\Comments.cs" />
+ <EmbeddedResource Include="TestFiles\CodeGenerator\CS\Output\ConditionalAttributes.cs" />
+ <EmbeddedResource Include="TestFiles\CodeGenerator\CS\Output\DesignTime.cs" />
+ <EmbeddedResource Include="TestFiles\CodeGenerator\CS\Output\EmptyCodeBlock.cs" />
+ <EmbeddedResource Include="TestFiles\CodeGenerator\CS\Output\EmptyExplicitExpression.cs" />
+ <EmbeddedResource Include="TestFiles\CodeGenerator\CS\Output\EmptyImplicitExpression.cs" />
+ <EmbeddedResource Include="TestFiles\CodeGenerator\CS\Output\EmptyImplicitExpressionInCode.cs" />
+ <EmbeddedResource Include="TestFiles\CodeGenerator\CS\Output\ExplicitExpression.cs" />
+ <EmbeddedResource Include="TestFiles\CodeGenerator\CS\Output\ExplicitExpressionAtEOF.cs" />
+ <EmbeddedResource Include="TestFiles\CodeGenerator\CS\Output\ExpressionsInCode.cs" />
+ <EmbeddedResource Include="TestFiles\CodeGenerator\CS\Output\FunctionsBlock.cs" />
+ <EmbeddedResource Include="TestFiles\CodeGenerator\CS\Output\FunctionsBlock.DesignTime.cs" />
+ <EmbeddedResource Include="TestFiles\CodeGenerator\CS\Output\Helpers.cs" />
+ <EmbeddedResource Include="TestFiles\CodeGenerator\CS\Output\Helpers.Instance.cs" />
+ <EmbeddedResource Include="TestFiles\CodeGenerator\CS\Output\HelpersMissingCloseParen.cs" />
+ <EmbeddedResource Include="TestFiles\CodeGenerator\CS\Output\HelpersMissingName.cs" />
+ <EmbeddedResource Include="TestFiles\CodeGenerator\CS\Output\HelpersMissingOpenBrace.cs" />
+ <EmbeddedResource Include="TestFiles\CodeGenerator\CS\Output\HelpersMissingOpenParen.cs" />
+ <EmbeddedResource Include="TestFiles\CodeGenerator\CS\Output\HiddenSpansInCode.cs" />
+ <EmbeddedResource Include="TestFiles\CodeGenerator\CS\Output\ImplicitExpression.cs" />
+ <EmbeddedResource Include="TestFiles\CodeGenerator\CS\Output\ImplicitExpressionAtEOF.cs" />
+ <EmbeddedResource Include="TestFiles\CodeGenerator\CS\Output\Imports.cs" />
+ <EmbeddedResource Include="TestFiles\CodeGenerator\CS\Output\Imports.DesignTime.cs" />
+ <EmbeddedResource Include="TestFiles\CodeGenerator\CS\Output\Inherits.Designtime.cs" />
+ <EmbeddedResource Include="TestFiles\CodeGenerator\CS\Output\Inherits.Runtime.cs" />
+ <EmbeddedResource Include="TestFiles\CodeGenerator\CS\Output\InlineBlocks.cs" />
+ <EmbeddedResource Include="TestFiles\CodeGenerator\CS\Output\Instrumented.cs" />
+ <EmbeddedResource Include="TestFiles\CodeGenerator\CS\Output\LayoutDirective.cs" />
+ <EmbeddedResource Include="TestFiles\CodeGenerator\CS\Output\MarkupInCodeBlock.cs" />
+ <EmbeddedResource Include="TestFiles\CodeGenerator\CS\Output\NestedCodeBlocks.cs" />
+ <EmbeddedResource Include="TestFiles\CodeGenerator\CS\Output\NestedHelpers.cs" />
+ <EmbeddedResource Include="TestFiles\CodeGenerator\CS\Output\NoLinePragmas.cs" />
+ <EmbeddedResource Include="TestFiles\CodeGenerator\CS\Output\ParserError.cs" />
+ <EmbeddedResource Include="TestFiles\CodeGenerator\CS\Output\RazorComments.cs" />
+ <EmbeddedResource Include="TestFiles\CodeGenerator\CS\Output\ResolveUrl.cs" />
+ <EmbeddedResource Include="TestFiles\CodeGenerator\CS\Output\Sections.cs" />
+ <EmbeddedResource Include="TestFiles\CodeGenerator\CS\Output\Templates.cs" />
+ <EmbeddedResource Include="TestFiles\CodeGenerator\CS\Output\UnfinishedExpressionInCode.cs" />
+ <EmbeddedResource Include="TestFiles\CodeGenerator\CS\Output\RazorComments.DesignTime.cs" />
+ <Compile Include="StringTextBuffer.cs" />
+ <Compile Include="Text\LineTrackingStringBufferTest.cs" />
+ <Compile Include="Tokenizer\VBTokenizerLiteralTest.cs" />
+ <Compile Include="Tokenizer\VBTokenizerIdentifierTest.cs" />
+ <Compile Include="Tokenizer\VBTokenizerCommentTest.cs" />
+ <Compile Include="Tokenizer\VBTokenizerOperatorsTest.cs" />
+ <Compile Include="Tokenizer\VBTokenizerTestBase.cs" />
+ <Compile Include="Tokenizer\VBTokenizerTest.cs" />
+ <Compile Include="Tokenizer\CSharpTokenizerCommentTest.cs" />
+ <Compile Include="Tokenizer\CSharpTokenizerLiteralTest.cs" />
+ <Compile Include="Tokenizer\CSharpTokenizerIdentifierTest.cs" />
+ <Compile Include="Tokenizer\CSharpTokenizerOperatorsTest.cs" />
+ <Compile Include="Tokenizer\CSharpTokenizerTestBase.cs" />
+ <Compile Include="Tokenizer\CSharpTokenizerTest.cs" />
+ <Compile Include="Tokenizer\TokenizerLookaheadTest.cs" />
+ <Compile Include="Tokenizer\HtmlTokenizerTest.cs" />
+ <Compile Include="Tokenizer\HtmlTokenizerTestBase.cs" />
+ <Compile Include="Tokenizer\TokenizerTestBase.cs" />
+ <Compile Include="Utils\MiscUtils.cs" />
+ <Compile Include="Generator\VBRazorCodeGeneratorTest.cs" />
+ <Compile Include="Generator\CSharpRazorCodeGeneratorTest.cs" />
+ <Compile Include="CSharpRazorCodeLanguageTest.cs" />
+ <Compile Include="Parser\CallbackParserListenerTest.cs" />
+ <Compile Include="Parser\CSharp\CSharpExplicitExpressionTest.cs" />
+ <Compile Include="Parser\CSharp\CSharpSectionTest.cs" />
+ <Compile Include="Parser\BlockTest.cs" />
+ <Compile Include="Parser\VB\VBExpressionTest.cs" />
+ <Compile Include="Text\BufferingTextReaderTest.cs" />
+ <Compile Include="Parser\CSharp\CSharpErrorTest.cs" />
+ <Compile Include="Parser\CSharp\CSharpImplicitExpressionTest.cs" />
+ <Compile Include="Parser\CSharp\CSharpSpecialBlockTest.cs" />
+ <Compile Include="Parser\Html\HtmlBlockTest.cs" />
+ <Compile Include="Parser\CSharp\CSharpBlockTest.cs" />
+ <Compile Include="Text\LookaheadTextReaderTestBase.cs" />
+ <Compile Include="Text\SourceLocationTrackerTest.cs" />
+ <Compile Include="Text\TextBufferReaderTest.cs" />
+ <Compile Include="Utils\DisposableActionTest.cs" />
+ <Compile Include="Parser\Html\HtmlDocumentTest.cs" />
+ <Compile Include="Parser\ParserContextTest.cs" />
+ <Compile Include="Parser\Html\HtmlErrorTest.cs" />
+ <Compile Include="Parser\Html\HtmlParserTestUtils.cs" />
+ <Compile Include="Parser\Html\HtmlToCodeSwitchTest.cs" />
+ <Compile Include="Parser\RazorParserTest.cs" />
+ <Compile Include="Properties\AssemblyInfo.cs" />
+ <Compile Include="Text\SourceLocationTest.cs" />
+ <Compile Include="Utils\SpanAssert.cs">
+ <SubType>Code</SubType>
+ </Compile>
+ <Compile Include="VBRazorCodeLanguageTest.cs" />
+ <Compile Include="Text\TextChangeTest.cs" />
+ <Compile Include="Text\TextReaderExtensionsTest.cs" />
+ <Compile Include="Utils\EnumerableUtils.cs" />
+ <Compile Include="Utils\MiscAssert.cs" />
+ </ItemGroup>
+ <ItemGroup>
+ <ProjectReference Include="..\..\src\System.Web.Razor\System.Web.Razor.csproj">
+ <Project>{8F18041B-9410-4C36-A9C5-067813DF5F31}</Project>
+ <Name>System.Web.Razor</Name>
+ </ProjectReference>
+ <ProjectReference Include="..\Microsoft.TestCommon\Microsoft.TestCommon.csproj">
+ <Project>{FCCC4CB7-BAF7-4A57-9F89-E5766FE536C0}</Project>
+ <Name>Microsoft.TestCommon</Name>
+ </ProjectReference>
+ </ItemGroup>
+ <ItemGroup>
+ <EmbeddedResource Include="TestFiles\nested-1000.html" />
+ </ItemGroup>
+ <ItemGroup>
+ <Service Include="{508349B6-6B84-4DF5-91F0-309BEEBAD82D}" />
+ </ItemGroup>
+ <ItemGroup>
+ <EmbeddedResource Include="TestFiles\CodeGenerator\VB\Source\CodeBlock.vbhtml" />
+ </ItemGroup>
+ <ItemGroup>
+ <EmbeddedResource Include="TestFiles\CodeGenerator\VB\Source\ExplicitExpression.vbhtml" />
+ </ItemGroup>
+ <ItemGroup>
+ <EmbeddedResource Include="TestFiles\CodeGenerator\VB\Source\MarkupInCodeBlock.vbhtml" />
+ </ItemGroup>
+ <ItemGroup>
+ <EmbeddedResource Include="TestFiles\CodeGenerator\VB\Source\Blocks.vbhtml" />
+ </ItemGroup>
+ <ItemGroup>
+ <EmbeddedResource Include="TestFiles\CodeGenerator\VB\Source\ImplicitExpression.vbhtml" />
+ </ItemGroup>
+ <ItemGroup>
+ <EmbeddedResource Include="TestFiles\CodeGenerator\VB\Source\NoLinePragmas.vbhtml" />
+ </ItemGroup>
+ <ItemGroup>
+ <EmbeddedResource Include="TestFiles\CodeGenerator\VB\Source\Imports.vbhtml" />
+ </ItemGroup>
+ <ItemGroup>
+ <EmbeddedResource Include="TestFiles\CodeGenerator\VB\Source\ExpressionsInCode.vbhtml" />
+ </ItemGroup>
+ <ItemGroup>
+ <EmbeddedResource Include="TestFiles\CodeGenerator\VB\Source\FunctionsBlock.vbhtml" />
+ </ItemGroup>
+ <ItemGroup>
+ <EmbeddedResource Include="TestFiles\CodeGenerator\VB\Source\Options.vbhtml" />
+ </ItemGroup>
+ <ItemGroup>
+ <EmbeddedResource Include="TestFiles\CodeGenerator\VB\Source\Templates.vbhtml" />
+ </ItemGroup>
+ <ItemGroup>
+ <EmbeddedResource Include="TestFiles\CodeGenerator\VB\Source\Sections.vbhtml" />
+ </ItemGroup>
+ <ItemGroup>
+ <EmbeddedResource Include="TestFiles\CodeGenerator\VB\Source\DesignTime.vbhtml" />
+ </ItemGroup>
+ <ItemGroup>
+ <EmbeddedResource Include="TestFiles\CodeGenerator\CS\Source\Templates.cshtml" />
+ <EmbeddedResource Include="TestFiles\CodeGenerator\CS\Source\Blocks.cshtml" />
+ <EmbeddedResource Include="TestFiles\CodeGenerator\CS\Source\CodeBlock.cshtml" />
+ <EmbeddedResource Include="TestFiles\CodeGenerator\CS\Source\NoLinePragmas.cshtml" />
+ <EmbeddedResource Include="TestFiles\CodeGenerator\CS\Source\ExplicitExpression.cshtml" />
+ <EmbeddedResource Include="TestFiles\CodeGenerator\CS\Source\ImplicitExpression.cshtml" />
+ <EmbeddedResource Include="TestFiles\CodeGenerator\CS\Source\Imports.cshtml" />
+ <EmbeddedResource Include="TestFiles\CodeGenerator\CS\Source\MarkupInCodeBlock.cshtml" />
+ </ItemGroup>
+ <ItemGroup>
+ <EmbeddedResource Include="TestFiles\CodeGenerator\CS\Source\DesignTime.cshtml" />
+ </ItemGroup>
+ <ItemGroup>
+ <EmbeddedResource Include="TestFiles\CodeGenerator\CS\Source\ExpressionsInCode.cshtml" />
+ </ItemGroup>
+ <ItemGroup>
+ <EmbeddedResource Include="TestFiles\CodeGenerator\CS\Source\FunctionsBlock.cshtml" />
+ </ItemGroup>
+ <ItemGroup>
+ <EmbeddedResource Include="TestFiles\CodeGenerator\CS\Source\Sections.cshtml" />
+ </ItemGroup>
+ <ItemGroup>
+ <EmbeddedResource Include="TestFiles\CodeGenerator\CS\Source\Inherits.cshtml" />
+ <EmbeddedResource Include="TestFiles\CodeGenerator\VB\Source\Inherits.vbhtml" />
+ </ItemGroup>
+ <ItemGroup>
+ <EmbeddedResource Include="TestFiles\CodeGenerator\CS\Source\NestedHelpers.cshtml" />
+ <EmbeddedResource Include="TestFiles\CodeGenerator\VB\Source\NestedHelpers.vbhtml" />
+ <EmbeddedResource Include="TestFiles\CodeGenerator\CS\Source\Instrumented.cshtml" />
+ <EmbeddedResource Include="TestFiles\CodeGenerator\VB\Source\Instrumented.vbhtml" />
+ </ItemGroup>
+ <ItemGroup>
+ <EmbeddedResource Include="TestFiles\CodeGenerator\CS\Source\RazorComments.cshtml" />
+ </ItemGroup>
+ <ItemGroup>
+ <EmbeddedResource Include="TestFiles\CodeGenerator\VB\Source\RazorComments.vbhtml" />
+ </ItemGroup>
+ <ItemGroup>
+ <EmbeddedResource Include="TestFiles\CodeGenerator\CS\Source\ParserError.cshtml" />
+ </ItemGroup>
+ <ItemGroup>
+ <EmbeddedResource Include="TestFiles\CodeGenerator\VB\Source\ParserError.vbhtml" />
+ </ItemGroup>
+ <ItemGroup>
+ <EmbeddedResource Include="TestFiles\CodeGenerator\CS\Source\ImplicitExpressionAtEOF.cshtml" />
+ </ItemGroup>
+ <ItemGroup>
+ <EmbeddedResource Include="TestFiles\CodeGenerator\CS\Source\CodeBlockAtEOF.cshtml" />
+ </ItemGroup>
+ <ItemGroup>
+ <EmbeddedResource Include="TestFiles\CodeGenerator\CS\Source\ExplicitExpressionAtEOF.cshtml" />
+ </ItemGroup>
+ <ItemGroup>
+ <EmbeddedResource Include="TestFiles\CodeGenerator\CS\Source\EmptyCodeBlock.cshtml" />
+ </ItemGroup>
+ <ItemGroup>
+ <EmbeddedResource Include="TestFiles\CodeGenerator\CS\Source\EmptyExplicitExpression.cshtml" />
+ </ItemGroup>
+ <ItemGroup>
+ <EmbeddedResource Include="TestFiles\CodeGenerator\CS\Source\EmptyImplicitExpression.cshtml" />
+ </ItemGroup>
+ <ItemGroup>
+ <EmbeddedResource Include="TestFiles\CodeGenerator\VB\Source\CodeBlockAtEOF.vbhtml" />
+ </ItemGroup>
+ <ItemGroup>
+ <EmbeddedResource Include="TestFiles\CodeGenerator\VB\Source\EmptyExplicitExpression.vbhtml" />
+ </ItemGroup>
+ <ItemGroup>
+ <EmbeddedResource Include="TestFiles\CodeGenerator\VB\Source\EmptyImplicitExpression.vbhtml" />
+ </ItemGroup>
+ <ItemGroup>
+ <EmbeddedResource Include="TestFiles\CodeGenerator\VB\Source\ExplicitExpressionAtEOF.vbhtml" />
+ </ItemGroup>
+ <ItemGroup>
+ <EmbeddedResource Include="TestFiles\CodeGenerator\VB\Source\ImplicitExpressionAtEOF.vbhtml" />
+ </ItemGroup>
+ <ItemGroup>
+ <EmbeddedResource Include="TestFiles\CodeGenerator\CS\Source\UnfinishedExpressionInCode.cshtml" />
+ </ItemGroup>
+ <ItemGroup>
+ <EmbeddedResource Include="TestFiles\CodeGenerator\VB\Source\UnfinishedExpressionInCode.vbhtml" />
+ </ItemGroup>
+ <ItemGroup>
+ <EmbeddedResource Include="TestFiles\DesignTime\Simple.cshtml" />
+ </ItemGroup>
+ <ItemGroup>
+ <EmbeddedResource Include="TestFiles\DesignTime\Simple.txt" />
+ </ItemGroup>
+ <ItemGroup>
+ <EmbeddedResource Include="TestFiles\CodeGenerator\CS\Source\Helpers.cshtml" />
+ <EmbeddedResource Include="TestFiles\CodeGenerator\CS\Source\HelpersMissingCloseParen.cshtml" />
+ <EmbeddedResource Include="TestFiles\CodeGenerator\CS\Source\HelpersMissingName.cshtml" />
+ <EmbeddedResource Include="TestFiles\CodeGenerator\CS\Source\HelpersMissingOpenBrace.cshtml" />
+ </ItemGroup>
+ <ItemGroup>
+ <EmbeddedResource Include="TestFiles\CodeGenerator\CS\Source\HelpersMissingOpenParen.cshtml" />
+ </ItemGroup>
+ <ItemGroup>
+ <EmbeddedResource Include="TestFiles\CodeGenerator\VB\Source\HelpersMissingOpenParen.vbhtml" />
+ </ItemGroup>
+ <ItemGroup>
+ <EmbeddedResource Include="TestFiles\CodeGenerator\VB\Source\Helpers.vbhtml" />
+ </ItemGroup>
+ <ItemGroup>
+ <EmbeddedResource Include="TestFiles\CodeGenerator\VB\Source\HelpersMissingCloseParen.vbhtml" />
+ </ItemGroup>
+ <ItemGroup>
+ <EmbeddedResource Include="TestFiles\CodeGenerator\VB\Source\HelpersMissingName.vbhtml" />
+ </ItemGroup>
+ <ItemGroup>
+ <EmbeddedResource Include="TestFiles\CodeGenerator\CS\Source\NestedCodeBlocks.cshtml" />
+ </ItemGroup>
+ <ItemGroup>
+ <EmbeddedResource Include="TestFiles\CodeGenerator\VB\Source\NestedCodeBlocks.vbhtml" />
+ </ItemGroup>
+ <ItemGroup>
+ <EmbeddedResource Include="TestFiles\CodeGenerator\CS\Source\InlineBlocks.cshtml" />
+ </ItemGroup>
+ <ItemGroup>
+ <EmbeddedResource Include="TestFiles\CodeGenerator\CS\Source\EmptyImplicitExpressionInCode.cshtml" />
+ </ItemGroup>
+ <ItemGroup>
+ <EmbeddedResource Include="TestFiles\CodeGenerator\VB\Source\EmptyImplicitExpressionInCode.vbhtml" />
+ </ItemGroup>
+ <ItemGroup>
+ <EmbeddedResource Include="TestFiles\CodeGenerator\CS\Source\HiddenSpansInCode.cshtml" />
+ </ItemGroup>
+ <ItemGroup>
+ <None Include="packages.config" />
+ <EmbeddedResource Include="TestFiles\CodeGenerator\CS\Source\ResolveUrl.cshtml" />
+ <EmbeddedResource Include="TestFiles\CodeGenerator\VB\Source\ResolveUrl.vbhtml" />
+ <EmbeddedResource Include="TestFiles\CodeGenerator\CS\Source\LayoutDirective.cshtml" />
+ <EmbeddedResource Include="TestFiles\CodeGenerator\VB\Source\LayoutDirective.vbhtml" />
+ <EmbeddedResource Include="TestFiles\CodeGenerator\CS\Source\ConditionalAttributes.cshtml" />
+ <EmbeddedResource Include="TestFiles\CodeGenerator\VB\Source\ConditionalAttributes.vbhtml" />
+ </ItemGroup>
+ <ItemGroup>
+ <EmbeddedResource Include="TestFiles\CodeGenerator\VB\Output\Blocks.vb" />
+ <EmbeddedResource Include="TestFiles\CodeGenerator\VB\Output\CodeBlock.vb" />
+ <EmbeddedResource Include="TestFiles\CodeGenerator\VB\Output\CodeBlockAtEOF.vb" />
+ <EmbeddedResource Include="TestFiles\CodeGenerator\VB\Output\Comments.vb" />
+ <EmbeddedResource Include="TestFiles\CodeGenerator\VB\Output\ConditionalAttributes.vb" />
+ <EmbeddedResource Include="TestFiles\CodeGenerator\VB\Output\DesignTime.vb" />
+ <EmbeddedResource Include="TestFiles\CodeGenerator\VB\Output\EmptyExplicitExpression.vb" />
+ <EmbeddedResource Include="TestFiles\CodeGenerator\VB\Output\EmptyImplicitExpression.vb" />
+ <EmbeddedResource Include="TestFiles\CodeGenerator\VB\Output\EmptyImplicitExpressionInCode.vb" />
+ <EmbeddedResource Include="TestFiles\CodeGenerator\VB\Output\ExplicitExpression.vb" />
+ <EmbeddedResource Include="TestFiles\CodeGenerator\VB\Output\ExplicitExpressionAtEOF.vb" />
+ <EmbeddedResource Include="TestFiles\CodeGenerator\VB\Output\ExpressionsInCode.vb" />
+ <EmbeddedResource Include="TestFiles\CodeGenerator\VB\Output\FunctionsBlock.DesignTime.vb" />
+ <EmbeddedResource Include="TestFiles\CodeGenerator\VB\Output\FunctionsBlock.vb" />
+ <EmbeddedResource Include="TestFiles\CodeGenerator\VB\Output\Helpers.Instance.vb" />
+ <EmbeddedResource Include="TestFiles\CodeGenerator\VB\Output\Helpers.vb" />
+ <EmbeddedResource Include="TestFiles\CodeGenerator\VB\Output\HelpersMissingCloseParen.vb" />
+ <EmbeddedResource Include="TestFiles\CodeGenerator\VB\Output\HelpersMissingName.vb" />
+ <EmbeddedResource Include="TestFiles\CodeGenerator\VB\Output\HelpersMissingOpenParen.vb" />
+ <EmbeddedResource Include="TestFiles\CodeGenerator\VB\Output\ImplicitExpression.vb" />
+ <EmbeddedResource Include="TestFiles\CodeGenerator\VB\Output\ImplicitExpressionAtEOF.vb" />
+ <EmbeddedResource Include="TestFiles\CodeGenerator\VB\Output\Imports.DesignTime.vb" />
+ <EmbeddedResource Include="TestFiles\CodeGenerator\VB\Output\Imports.vb" />
+ <EmbeddedResource Include="TestFiles\CodeGenerator\VB\Output\Inherits.Designtime.vb" />
+ <EmbeddedResource Include="TestFiles\CodeGenerator\VB\Output\Inherits.Runtime.vb" />
+ <EmbeddedResource Include="TestFiles\CodeGenerator\VB\Output\Instrumented.vb" />
+ <EmbeddedResource Include="TestFiles\CodeGenerator\VB\Output\LayoutDirective.vb" />
+ <EmbeddedResource Include="TestFiles\CodeGenerator\VB\Output\MarkupInCodeBlock.vb" />
+ <EmbeddedResource Include="TestFiles\CodeGenerator\VB\Output\NestedCodeBlocks.vb" />
+ <EmbeddedResource Include="TestFiles\CodeGenerator\VB\Output\NestedHelpers.vb" />
+ <EmbeddedResource Include="TestFiles\CodeGenerator\VB\Output\NoLinePragmas.vb" />
+ <EmbeddedResource Include="TestFiles\CodeGenerator\VB\Output\Options.vb" />
+ <EmbeddedResource Include="TestFiles\CodeGenerator\VB\Output\ParserError.vb" />
+ <EmbeddedResource Include="TestFiles\CodeGenerator\VB\Output\RazorComments.vb" />
+ <EmbeddedResource Include="TestFiles\CodeGenerator\VB\Output\ResolveUrl.vb" />
+ <EmbeddedResource Include="TestFiles\CodeGenerator\VB\Output\Sections.vb" />
+ <EmbeddedResource Include="TestFiles\CodeGenerator\VB\Output\Templates.vb" />
+ <EmbeddedResource Include="TestFiles\CodeGenerator\VB\Output\UnfinishedExpressionInCode.vb" />
+ </ItemGroup>
+ <ItemGroup>
+ <EmbeddedResource Include="TestFiles\CodeGenerator\VB\Output\RazorComments.DesignTime.vb" />
+ </ItemGroup>
+ <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
+</Project> \ No newline at end of file
diff --git a/test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Output/Blocks.cs b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Output/Blocks.cs
new file mode 100644
index 00000000..f92286bf
--- /dev/null
+++ b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Output/Blocks.cs
@@ -0,0 +1,194 @@
+//------------------------------------------------------------------------------
+// <auto-generated>
+// This code was generated by a tool.
+// Runtime Version:N.N.NNNNN.N
+//
+// Changes to this file may cause incorrect behavior and will be lost if
+// the code is regenerated.
+// </auto-generated>
+//------------------------------------------------------------------------------
+
+namespace TestOutput {
+using System;
+
+public class Blocks {
+#line hidden
+public Blocks() {
+}
+public override void Execute() {
+
+#line 1 "Blocks.cshtml"
+
+ int i = 1;
+
+
+#line default
+#line hidden
+WriteLiteral("\r\n\r\n");
+
+
+#line 5 "Blocks.cshtml"
+ while(i <= 10) {
+
+
+#line default
+#line hidden
+WriteLiteral(" <p>Hello from C#, #");
+
+
+#line 6 "Blocks.cshtml"
+ Write(i);
+
+
+#line default
+#line hidden
+WriteLiteral("</p>\r\n");
+
+
+#line 7 "Blocks.cshtml"
+ i += 1;
+}
+
+
+#line default
+#line hidden
+WriteLiteral("\r\n");
+
+
+#line 10 "Blocks.cshtml"
+ if(i == 11) {
+
+
+#line default
+#line hidden
+WriteLiteral(" <p>We wrote 10 lines!</p>\r\n");
+
+
+#line 12 "Blocks.cshtml"
+}
+
+
+#line default
+#line hidden
+WriteLiteral("\r\n");
+
+
+#line 14 "Blocks.cshtml"
+ switch(i) {
+ case 11:
+
+
+#line default
+#line hidden
+WriteLiteral(" <p>No really, we wrote 10 lines!</p>\r\n");
+
+
+#line 17 "Blocks.cshtml"
+ break;
+ default:
+
+
+#line default
+#line hidden
+WriteLiteral(" <p>Actually, we didn\'t...</p>\r\n");
+
+
+#line 20 "Blocks.cshtml"
+ break;
+}
+
+
+#line default
+#line hidden
+WriteLiteral("\r\n");
+
+
+#line 23 "Blocks.cshtml"
+ for(int j = 1; j <= 10; j += 2) {
+
+
+#line default
+#line hidden
+WriteLiteral(" <p>Hello again from C#, #");
+
+
+#line 24 "Blocks.cshtml"
+ Write(j);
+
+
+#line default
+#line hidden
+WriteLiteral("</p>\r\n");
+
+
+#line 25 "Blocks.cshtml"
+}
+
+
+#line default
+#line hidden
+WriteLiteral("\r\n");
+
+
+#line 27 "Blocks.cshtml"
+ try {
+
+
+#line default
+#line hidden
+WriteLiteral(" <p>That time, we wrote 5 lines!</p>\r\n");
+
+
+#line 29 "Blocks.cshtml"
+} catch(Exception ex) {
+
+
+#line default
+#line hidden
+WriteLiteral(" <p>Oh no! An error occurred: ");
+
+
+#line 30 "Blocks.cshtml"
+ Write(ex.Message);
+
+
+#line default
+#line hidden
+WriteLiteral("</p>\r\n");
+
+
+#line 31 "Blocks.cshtml"
+}
+
+
+#line default
+#line hidden
+WriteLiteral("\r\n<p>i is now ");
+
+
+#line 33 "Blocks.cshtml"
+ Write(i);
+
+
+#line default
+#line hidden
+WriteLiteral("</p>\r\n\r\n");
+
+
+#line 35 "Blocks.cshtml"
+ lock(new object()) {
+
+
+#line default
+#line hidden
+WriteLiteral(" <p>This block is locked, for your security!</p>\r\n");
+
+
+#line 37 "Blocks.cshtml"
+}
+
+#line default
+#line hidden
+}
+}
+}
diff --git a/test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Output/CodeBlock.cs b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Output/CodeBlock.cs
new file mode 100644
index 00000000..a47b997e
--- /dev/null
+++ b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Output/CodeBlock.cs
@@ -0,0 +1,31 @@
+//------------------------------------------------------------------------------
+// <auto-generated>
+// This code was generated by a tool.
+// Runtime Version:N.N.NNNNN.N
+//
+// Changes to this file may cause incorrect behavior and will be lost if
+// the code is regenerated.
+// </auto-generated>
+//------------------------------------------------------------------------------
+
+namespace TestOutput {
+using System;
+
+public class CodeBlock {
+#line hidden
+public CodeBlock() {
+}
+public override void Execute() {
+
+#line 1 "CodeBlock.cshtml"
+
+ for(int i = 1; i <= 10; i++) {
+ Output.Write("<p>Hello from C#, #" + i.ToString() + "</p>");
+ }
+
+
+#line default
+#line hidden
+}
+}
+}
diff --git a/test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Output/CodeBlockAtEOF.cs b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Output/CodeBlockAtEOF.cs
new file mode 100644
index 00000000..1563d2e0
--- /dev/null
+++ b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Output/CodeBlockAtEOF.cs
@@ -0,0 +1,27 @@
+//------------------------------------------------------------------------------
+// <auto-generated>
+// This code was generated by a tool.
+// Runtime Version:N.N.NNNNN.N
+//
+// Changes to this file may cause incorrect behavior and will be lost if
+// the code is regenerated.
+// </auto-generated>
+//------------------------------------------------------------------------------
+
+namespace TestOutput {
+using System;
+
+public class CodeBlockAtEOF {
+#line hidden
+public CodeBlockAtEOF() {
+}
+public override void Execute() {
+
+#line 1 "CodeBlockAtEOF.cshtml"
+
+
+#line default
+#line hidden
+}
+}
+}
diff --git a/test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Output/Comments.cs b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Output/Comments.cs
new file mode 100644
index 00000000..61e70fcb
--- /dev/null
+++ b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Output/Comments.cs
@@ -0,0 +1,38 @@
+//------------------------------------------------------------------------------
+// <auto-generated>
+// This code was generated by a tool.
+// Runtime Version:N.N.NNNNN.N
+//
+// Changes to this file may cause incorrect behavior and will be lost if
+// the code is regenerated.
+// </auto-generated>
+//------------------------------------------------------------------------------
+
+namespace TestOutput {
+using System;
+
+public class Comments {
+public override void Execute() {
+
+
+#line 1 "Comments.cshtml"
+ //This is not going to be rendered
+
+
+#line default
+#line hidden
+WriteLiteral("<p>This is going to be rendered</p>\r\n");
+
+
+
+#line 3 "Comments.cshtml"
+ /* Neither is this
+ nor this
+ nor this */
+
+#line default
+#line hidden
+
+}
+}
+}
diff --git a/test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Output/ConditionalAttributes.cs b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Output/ConditionalAttributes.cs
new file mode 100644
index 00000000..90d8a9a5
--- /dev/null
+++ b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Output/ConditionalAttributes.cs
@@ -0,0 +1,197 @@
+//------------------------------------------------------------------------------
+// <auto-generated>
+// This code was generated by a tool.
+// Runtime Version:N.N.NNNNN.N
+//
+// Changes to this file may cause incorrect behavior and will be lost if
+// the code is regenerated.
+// </auto-generated>
+//------------------------------------------------------------------------------
+
+namespace TestOutput {
+using System;
+
+public class ConditionalAttributes {
+#line hidden
+public ConditionalAttributes() {
+}
+public override void Execute() {
+
+#line 1 "ConditionalAttributes.cshtml"
+
+ var ch = true;
+ var cls = "bar";
+
+
+#line default
+#line hidden
+WriteLiteral(" <a");
+
+WriteLiteral(" href=\"Foo\"");
+
+WriteLiteral(" />\r\n");
+
+WriteLiteral(" <p");
+
+WriteAttribute("class", Tuple.Create(" class=\"", 74), Tuple.Create("\"", 86)
+
+#line 5 "ConditionalAttributes.cshtml"
+, Tuple.Create(Tuple.Create("", 82), Tuple.Create<System.Object, System.Int32>(cls
+
+#line default
+#line hidden
+, 82), false)
+);
+
+WriteLiteral(" />\r\n");
+
+WriteLiteral(" <p");
+
+WriteAttribute("class", Tuple.Create(" class=\"", 98), Tuple.Create("\"", 114)
+, Tuple.Create(Tuple.Create("", 106), Tuple.Create("foo", 106), true)
+
+#line 6 "ConditionalAttributes.cshtml"
+, Tuple.Create(Tuple.Create(" ", 109), Tuple.Create<System.Object, System.Int32>(cls
+
+#line default
+#line hidden
+, 110), false)
+);
+
+WriteLiteral(" />\r\n");
+
+WriteLiteral(" <p");
+
+WriteAttribute("class", Tuple.Create(" class=\"", 126), Tuple.Create("\"", 142)
+
+#line 7 "ConditionalAttributes.cshtml"
+, Tuple.Create(Tuple.Create("", 134), Tuple.Create<System.Object, System.Int32>(cls
+
+#line default
+#line hidden
+, 134), false)
+, Tuple.Create(Tuple.Create(" ", 138), Tuple.Create("foo", 139), true)
+);
+
+WriteLiteral(" />\r\n");
+
+WriteLiteral(" <input");
+
+WriteLiteral(" type=\"checkbox\"");
+
+WriteAttribute("checked", Tuple.Create(" checked=\"", 174), Tuple.Create("\"", 187)
+
+#line 8 "ConditionalAttributes.cshtml"
+, Tuple.Create(Tuple.Create("", 184), Tuple.Create<System.Object, System.Int32>(ch
+
+#line default
+#line hidden
+, 184), false)
+);
+
+WriteLiteral(" />\r\n");
+
+WriteLiteral(" <input");
+
+WriteLiteral(" type=\"checkbox\"");
+
+WriteAttribute("checked", Tuple.Create(" checked=\"", 219), Tuple.Create("\"", 236)
+, Tuple.Create(Tuple.Create("", 229), Tuple.Create("foo", 229), true)
+
+#line 9 "ConditionalAttributes.cshtml"
+, Tuple.Create(Tuple.Create(" ", 232), Tuple.Create<System.Object, System.Int32>(ch
+
+#line default
+#line hidden
+, 233), false)
+);
+
+WriteLiteral(" />\r\n");
+
+WriteLiteral(" <p");
+
+WriteAttribute("class", Tuple.Create(" class=\"", 248), Tuple.Create("\"", 281)
+, Tuple.Create(Tuple.Create("", 256), Tuple.Create<System.Object, System.Int32>(new Template(__razor_attribute_value_writer => {
+
+
+#line 10 "ConditionalAttributes.cshtml"
+ if(cls != null) {
+
+#line default
+#line hidden
+
+#line 10 "ConditionalAttributes.cshtml"
+WriteTo(__razor_attribute_value_writer, cls);
+
+
+#line default
+#line hidden
+
+#line 10 "ConditionalAttributes.cshtml"
+ }
+
+#line default
+#line hidden
+}), 256), false)
+);
+
+WriteLiteral(" />\r\n");
+
+WriteLiteral(" <a");
+
+WriteAttribute("href", Tuple.Create(" href=\"", 293), Tuple.Create("\"", 305)
+, Tuple.Create(Tuple.Create("", 300), Tuple.Create<System.Object, System.Int32>(Href("~/Foo")
+, 300), false)
+);
+
+WriteLiteral(" />\r\n");
+
+WriteLiteral(" <script");
+
+WriteAttribute("src", Tuple.Create(" src=\"", 322), Tuple.Create("\"", 373)
+
+#line 12 "ConditionalAttributes.cshtml"
+, Tuple.Create(Tuple.Create("", 328), Tuple.Create<System.Object, System.Int32>(Url.Content("~/Scripts/jquery-1.6.2.min.js")
+
+#line default
+#line hidden
+, 328), false)
+);
+
+WriteLiteral(" type=\"text/javascript\"");
+
+WriteLiteral("></script>\r\n");
+
+WriteLiteral(" <script");
+
+WriteAttribute("src", Tuple.Create(" src=\"", 420), Tuple.Create("\"", 487)
+
+#line 13 "ConditionalAttributes.cshtml"
+, Tuple.Create(Tuple.Create("", 426), Tuple.Create<System.Object, System.Int32>(Url.Content("~/Scripts/modernizr-2.0.6-development-only.js")
+
+#line default
+#line hidden
+, 426), false)
+);
+
+WriteLiteral(" type=\"text/javascript\"");
+
+WriteLiteral("></script>\r\n");
+
+WriteLiteral(" <script");
+
+WriteLiteral(" src=\"http://ajax.aspnetcdn.com/ajax/jquery.ui/1.8.16/jquery-ui.min.js\"");
+
+WriteLiteral(" type=\"text/javascript\"");
+
+WriteLiteral("></script>\r\n");
+
+
+#line 15 "ConditionalAttributes.cshtml"
+
+
+#line default
+#line hidden
+}
+}
+}
diff --git a/test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Output/DesignTime.cs b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Output/DesignTime.cs
new file mode 100644
index 00000000..794a3661
--- /dev/null
+++ b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Output/DesignTime.cs
@@ -0,0 +1,108 @@
+//------------------------------------------------------------------------------
+// <auto-generated>
+// This code was generated by a tool.
+// Runtime Version:N.N.NNNNN.N
+//
+// Changes to this file may cause incorrect behavior and will be lost if
+// the code is regenerated.
+// </auto-generated>
+//------------------------------------------------------------------------------
+
+namespace TestOutput {
+using System;
+
+public class DesignTime {
+private static object @__o;
+#line hidden
+#line 9 "DesignTime.cshtml"
+public static Template Foo() {
+#line default
+#line hidden
+return new Template(__razor_helper_writer => {
+
+#line 10 "DesignTime.cshtml"
+
+ if(true) {
+
+#line default
+#line hidden
+
+#line 11 "DesignTime.cshtml"
+
+ }
+
+#line default
+#line hidden
+});
+
+#line 12 "DesignTime.cshtml"
+}
+#line default
+#line hidden
+
+public DesignTime() {
+}
+public override void Execute() {
+
+#line 1 "DesignTime.cshtml"
+ for(int i = 1; i <= 10; i++) {
+
+
+#line default
+#line hidden
+
+#line 2 "DesignTime.cshtml"
+ __o = i;
+
+
+#line default
+#line hidden
+
+#line 3 "DesignTime.cshtml"
+
+ }
+
+#line default
+#line hidden
+
+#line 4 "DesignTime.cshtml"
+__o = Foo(Bar.Baz);
+
+
+#line default
+#line hidden
+
+#line 5 "DesignTime.cshtml"
+__o = Foo(item => new Template(__razor_template_writer => {
+
+
+#line default
+#line hidden
+
+#line 6 "DesignTime.cshtml"
+ __o = baz;
+
+
+#line default
+#line hidden
+
+#line 7 "DesignTime.cshtml"
+ }));
+
+
+#line default
+#line hidden
+DefineSection("Footer", () => {
+
+
+#line 8 "DesignTime.cshtml"
+__o = bar;
+
+
+#line default
+#line hidden
+});
+
+}
+}
+}
diff --git a/test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Output/EmptyCodeBlock.cs b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Output/EmptyCodeBlock.cs
new file mode 100644
index 00000000..5aa97216
--- /dev/null
+++ b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Output/EmptyCodeBlock.cs
@@ -0,0 +1,27 @@
+//------------------------------------------------------------------------------
+// <auto-generated>
+// This code was generated by a tool.
+// Runtime Version:N.N.NNNNN.N
+//
+// Changes to this file may cause incorrect behavior and will be lost if
+// the code is regenerated.
+// </auto-generated>
+//------------------------------------------------------------------------------
+
+namespace TestOutput {
+using System;
+
+public class EmptyCodeBlock {
+#line hidden
+public EmptyCodeBlock() {
+}
+public override void Execute() {
+
+#line 1 "EmptyCodeBlock.cshtml"
+
+
+#line default
+#line hidden
+}
+}
+}
diff --git a/test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Output/EmptyExplicitExpression.cs b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Output/EmptyExplicitExpression.cs
new file mode 100644
index 00000000..bac2bc5d
--- /dev/null
+++ b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Output/EmptyExplicitExpression.cs
@@ -0,0 +1,29 @@
+//------------------------------------------------------------------------------
+// <auto-generated>
+// This code was generated by a tool.
+// Runtime Version:N.N.NNNNN.N
+//
+// Changes to this file may cause incorrect behavior and will be lost if
+// the code is regenerated.
+// </auto-generated>
+//------------------------------------------------------------------------------
+
+namespace TestOutput {
+using System;
+
+public class EmptyExplicitExpression {
+private static object @__o;
+#line hidden
+public EmptyExplicitExpression() {
+}
+public override void Execute() {
+
+#line 1 "EmptyExplicitExpression.cshtml"
+__o = ;
+
+
+#line default
+#line hidden
+}
+}
+}
diff --git a/test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Output/EmptyImplicitExpression.cs b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Output/EmptyImplicitExpression.cs
new file mode 100644
index 00000000..bf8cdefa
--- /dev/null
+++ b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Output/EmptyImplicitExpression.cs
@@ -0,0 +1,29 @@
+//------------------------------------------------------------------------------
+// <auto-generated>
+// This code was generated by a tool.
+// Runtime Version:N.N.NNNNN.N
+//
+// Changes to this file may cause incorrect behavior and will be lost if
+// the code is regenerated.
+// </auto-generated>
+//------------------------------------------------------------------------------
+
+namespace TestOutput {
+using System;
+
+public class EmptyImplicitExpression {
+private static object @__o;
+#line hidden
+public EmptyImplicitExpression() {
+}
+public override void Execute() {
+
+#line 1 "EmptyImplicitExpression.cshtml"
+__o = ;
+
+
+#line default
+#line hidden
+}
+}
+}
diff --git a/test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Output/EmptyImplicitExpressionInCode.cs b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Output/EmptyImplicitExpressionInCode.cs
new file mode 100644
index 00000000..3c8deb41
--- /dev/null
+++ b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Output/EmptyImplicitExpressionInCode.cs
@@ -0,0 +1,43 @@
+//------------------------------------------------------------------------------
+// <auto-generated>
+// This code was generated by a tool.
+// Runtime Version:N.N.NNNNN.N
+//
+// Changes to this file may cause incorrect behavior and will be lost if
+// the code is regenerated.
+// </auto-generated>
+//------------------------------------------------------------------------------
+
+namespace TestOutput {
+using System;
+
+public class EmptyImplicitExpressionInCode {
+private static object @__o;
+#line hidden
+public EmptyImplicitExpressionInCode() {
+}
+public override void Execute() {
+
+#line 1 "EmptyImplicitExpressionInCode.cshtml"
+
+
+
+#line default
+#line hidden
+
+#line 2 "EmptyImplicitExpressionInCode.cshtml"
+__o = ;
+
+
+#line default
+#line hidden
+
+#line 3 "EmptyImplicitExpressionInCode.cshtml"
+
+
+
+#line default
+#line hidden
+}
+}
+}
diff --git a/test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Output/ExplicitExpression.cs b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Output/ExplicitExpression.cs
new file mode 100644
index 00000000..95651766
--- /dev/null
+++ b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Output/ExplicitExpression.cs
@@ -0,0 +1,30 @@
+//------------------------------------------------------------------------------
+// <auto-generated>
+// This code was generated by a tool.
+// Runtime Version:N.N.NNNNN.N
+//
+// Changes to this file may cause incorrect behavior and will be lost if
+// the code is regenerated.
+// </auto-generated>
+//------------------------------------------------------------------------------
+
+namespace TestOutput {
+using System;
+
+public class ExplicitExpression {
+#line hidden
+public ExplicitExpression() {
+}
+public override void Execute() {
+WriteLiteral("1 + 1 = ");
+
+
+#line 1 "ExplicitExpression.cshtml"
+ Write(1+1);
+
+
+#line default
+#line hidden
+}
+}
+}
diff --git a/test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Output/ExplicitExpressionAtEOF.cs b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Output/ExplicitExpressionAtEOF.cs
new file mode 100644
index 00000000..941af4e0
--- /dev/null
+++ b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Output/ExplicitExpressionAtEOF.cs
@@ -0,0 +1,29 @@
+//------------------------------------------------------------------------------
+// <auto-generated>
+// This code was generated by a tool.
+// Runtime Version:N.N.NNNNN.N
+//
+// Changes to this file may cause incorrect behavior and will be lost if
+// the code is regenerated.
+// </auto-generated>
+//------------------------------------------------------------------------------
+
+namespace TestOutput {
+using System;
+
+public class ExplicitExpressionAtEOF {
+private static object @__o;
+#line hidden
+public ExplicitExpressionAtEOF() {
+}
+public override void Execute() {
+
+#line 1 "ExplicitExpressionAtEOF.cshtml"
+__o = ;
+
+
+#line default
+#line hidden
+}
+}
+}
diff --git a/test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Output/ExpressionsInCode.cs b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Output/ExpressionsInCode.cs
new file mode 100644
index 00000000..4171f401
--- /dev/null
+++ b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Output/ExpressionsInCode.cs
@@ -0,0 +1,89 @@
+//------------------------------------------------------------------------------
+// <auto-generated>
+// This code was generated by a tool.
+// Runtime Version:N.N.NNNNN.N
+//
+// Changes to this file may cause incorrect behavior and will be lost if
+// the code is regenerated.
+// </auto-generated>
+//------------------------------------------------------------------------------
+
+namespace TestOutput {
+using System;
+
+public class ExpressionsInCode {
+#line hidden
+public ExpressionsInCode() {
+}
+public override void Execute() {
+
+#line 1 "ExpressionsInCode.cshtml"
+
+ object foo = null;
+ string bar = "Foo";
+
+
+#line default
+#line hidden
+WriteLiteral("\r\n\r\n");
+
+
+#line 6 "ExpressionsInCode.cshtml"
+ if(foo != null) {
+
+
+#line default
+#line hidden
+
+#line 7 "ExpressionsInCode.cshtml"
+Write(foo);
+
+
+#line default
+#line hidden
+
+#line 7 "ExpressionsInCode.cshtml"
+
+} else {
+
+
+#line default
+#line hidden
+WriteLiteral(" <p>Foo is Null!</p>\r\n");
+
+
+#line 10 "ExpressionsInCode.cshtml"
+}
+
+
+#line default
+#line hidden
+WriteLiteral("\r\n<p>\r\n");
+
+
+#line 13 "ExpressionsInCode.cshtml"
+ if(!String.IsNullOrEmpty(bar)) {
+
+
+#line default
+#line hidden
+
+#line 14 "ExpressionsInCode.cshtml"
+Write(bar.Replace("F", "B"));
+
+
+#line default
+#line hidden
+
+#line 14 "ExpressionsInCode.cshtml"
+
+}
+
+
+#line default
+#line hidden
+WriteLiteral("</p>");
+
+}
+}
+}
diff --git a/test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Output/FunctionsBlock.DesignTime.cs b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Output/FunctionsBlock.DesignTime.cs
new file mode 100644
index 00000000..534039f8
--- /dev/null
+++ b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Output/FunctionsBlock.DesignTime.cs
@@ -0,0 +1,46 @@
+//------------------------------------------------------------------------------
+// <auto-generated>
+// This code was generated by a tool.
+// Runtime Version:N.N.NNNNN.N
+//
+// Changes to this file may cause incorrect behavior and will be lost if
+// the code is regenerated.
+// </auto-generated>
+//------------------------------------------------------------------------------
+
+namespace TestOutput {
+using System;
+
+public class FunctionsBlock {
+private static object @__o;
+#line hidden
+#line 1 "FunctionsBlock.cshtml"
+
+
+
+#line default
+#line hidden
+
+#line 2 "FunctionsBlock.cshtml"
+
+ Random _rand = new Random();
+ private int RandomInt() {
+ return _rand.Next();
+ }
+
+#line default
+#line hidden
+
+public FunctionsBlock() {
+}
+public override void Execute() {
+
+#line 3 "FunctionsBlock.cshtml"
+ __o = RandomInt();
+
+
+#line default
+#line hidden
+}
+}
+}
diff --git a/test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Output/FunctionsBlock.cs b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Output/FunctionsBlock.cs
new file mode 100644
index 00000000..0b9f2a37
--- /dev/null
+++ b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Output/FunctionsBlock.cs
@@ -0,0 +1,49 @@
+//------------------------------------------------------------------------------
+// <auto-generated>
+// This code was generated by a tool.
+// Runtime Version:N.N.NNNNN.N
+//
+// Changes to this file may cause incorrect behavior and will be lost if
+// the code is regenerated.
+// </auto-generated>
+//------------------------------------------------------------------------------
+
+namespace TestOutput {
+using System;
+
+public class FunctionsBlock {
+#line hidden
+#line 1 "FunctionsBlock.cshtml"
+
+
+
+#line default
+#line hidden
+
+#line 5 "FunctionsBlock.cshtml"
+
+ Random _rand = new Random();
+ private int RandomInt() {
+ return _rand.Next();
+ }
+
+#line default
+#line hidden
+
+public FunctionsBlock() {
+}
+public override void Execute() {
+WriteLiteral("\r\n");
+
+WriteLiteral("\r\nHere\'s a random number: ");
+
+
+#line 12 "FunctionsBlock.cshtml"
+ Write(RandomInt());
+
+
+#line default
+#line hidden
+}
+}
+}
diff --git a/test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Output/Helpers.Instance.cs b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Output/Helpers.Instance.cs
new file mode 100644
index 00000000..ebe067e4
--- /dev/null
+++ b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Output/Helpers.Instance.cs
@@ -0,0 +1,96 @@
+//------------------------------------------------------------------------------
+// <auto-generated>
+// This code was generated by a tool.
+// Runtime Version:N.N.NNNNN.N
+//
+// Changes to this file may cause incorrect behavior and will be lost if
+// the code is regenerated.
+// </auto-generated>
+//------------------------------------------------------------------------------
+
+namespace TestOutput {
+using System;
+
+public class Helpers {
+#line hidden
+#line 1 "Helpers.cshtml"
+public Template Bold(string s) {
+#line default
+#line hidden
+return new Template(__razor_helper_writer => {
+
+#line 1 "Helpers.cshtml"
+
+ s = s.ToUpper();
+
+#line default
+#line hidden
+WriteLiteralTo(__razor_helper_writer, " <strong>");
+
+#line 3 "Helpers.cshtml"
+WriteTo(__razor_helper_writer, s);
+
+#line default
+#line hidden
+WriteLiteralTo(__razor_helper_writer, "</strong>\r\n");
+
+#line 4 "Helpers.cshtml"
+
+#line default
+#line hidden
+});
+
+#line 4 "Helpers.cshtml"
+}
+#line default
+#line hidden
+
+#line 6 "Helpers.cshtml"
+public Template Italic(string s) {
+#line default
+#line hidden
+return new Template(__razor_helper_writer => {
+
+#line 6 "Helpers.cshtml"
+
+ s = s.ToUpper();
+
+#line default
+#line hidden
+WriteLiteralTo(__razor_helper_writer, " <em>");
+
+#line 8 "Helpers.cshtml"
+WriteTo(__razor_helper_writer, s);
+
+#line default
+#line hidden
+WriteLiteralTo(__razor_helper_writer, "</em>\r\n");
+
+#line 9 "Helpers.cshtml"
+
+#line default
+#line hidden
+});
+
+#line 9 "Helpers.cshtml"
+}
+#line default
+#line hidden
+
+public Helpers() {
+}
+public override void Execute() {
+WriteLiteral("\r\n");
+
+WriteLiteral("\r\n");
+
+
+#line 11 "Helpers.cshtml"
+Write(Bold("Hello"));
+
+
+#line default
+#line hidden
+}
+}
+}
diff --git a/test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Output/Helpers.cs b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Output/Helpers.cs
new file mode 100644
index 00000000..3afbaca2
--- /dev/null
+++ b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Output/Helpers.cs
@@ -0,0 +1,96 @@
+//------------------------------------------------------------------------------
+// <auto-generated>
+// This code was generated by a tool.
+// Runtime Version:N.N.NNNNN.N
+//
+// Changes to this file may cause incorrect behavior and will be lost if
+// the code is regenerated.
+// </auto-generated>
+//------------------------------------------------------------------------------
+
+namespace TestOutput {
+using System;
+
+public class Helpers {
+#line hidden
+#line 1 "Helpers.cshtml"
+public static Template Bold(string s) {
+#line default
+#line hidden
+return new Template(__razor_helper_writer => {
+
+#line 1 "Helpers.cshtml"
+
+ s = s.ToUpper();
+
+#line default
+#line hidden
+WriteLiteralTo(__razor_helper_writer, " <strong>");
+
+#line 3 "Helpers.cshtml"
+WriteTo(__razor_helper_writer, s);
+
+#line default
+#line hidden
+WriteLiteralTo(__razor_helper_writer, "</strong>\r\n");
+
+#line 4 "Helpers.cshtml"
+
+#line default
+#line hidden
+});
+
+#line 4 "Helpers.cshtml"
+}
+#line default
+#line hidden
+
+#line 6 "Helpers.cshtml"
+public static Template Italic(string s) {
+#line default
+#line hidden
+return new Template(__razor_helper_writer => {
+
+#line 6 "Helpers.cshtml"
+
+ s = s.ToUpper();
+
+#line default
+#line hidden
+WriteLiteralTo(__razor_helper_writer, " <em>");
+
+#line 8 "Helpers.cshtml"
+WriteTo(__razor_helper_writer, s);
+
+#line default
+#line hidden
+WriteLiteralTo(__razor_helper_writer, "</em>\r\n");
+
+#line 9 "Helpers.cshtml"
+
+#line default
+#line hidden
+});
+
+#line 9 "Helpers.cshtml"
+}
+#line default
+#line hidden
+
+public Helpers() {
+}
+public override void Execute() {
+WriteLiteral("\r\n");
+
+WriteLiteral("\r\n");
+
+
+#line 11 "Helpers.cshtml"
+Write(Bold("Hello"));
+
+
+#line default
+#line hidden
+}
+}
+}
diff --git a/test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Output/HelpersMissingCloseParen.cs b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Output/HelpersMissingCloseParen.cs
new file mode 100644
index 00000000..5e340f70
--- /dev/null
+++ b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Output/HelpersMissingCloseParen.cs
@@ -0,0 +1,61 @@
+//------------------------------------------------------------------------------
+// <auto-generated>
+// This code was generated by a tool.
+// Runtime Version:N.N.NNNNN.N
+//
+// Changes to this file may cause incorrect behavior and will be lost if
+// the code is regenerated.
+// </auto-generated>
+//------------------------------------------------------------------------------
+
+namespace TestOutput {
+using System;
+
+public class HelpersMissingCloseParen {
+#line hidden
+#line 1 "HelpersMissingCloseParen.cshtml"
+public static Template Bold(string s) {
+#line default
+#line hidden
+return new Template(__razor_helper_writer => {
+
+#line 1 "HelpersMissingCloseParen.cshtml"
+
+ s = s.ToUpper();
+
+#line default
+#line hidden
+WriteLiteralTo(__razor_helper_writer, " <strong>");
+
+#line 3 "HelpersMissingCloseParen.cshtml"
+WriteTo(__razor_helper_writer, s);
+
+#line default
+#line hidden
+WriteLiteralTo(__razor_helper_writer, "</strong>\r\n");
+
+#line 4 "HelpersMissingCloseParen.cshtml"
+
+#line default
+#line hidden
+});
+
+#line 4 "HelpersMissingCloseParen.cshtml"
+}
+#line default
+#line hidden
+
+#line 6 "HelpersMissingCloseParen.cshtml"
+public static Template Italic(string s
+@Bold("Hello")
+#line default
+#line hidden
+
+public HelpersMissingCloseParen() {
+}
+public override void Execute() {
+WriteLiteral("\r\n");
+
+}
+}
+}
diff --git a/test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Output/HelpersMissingName.cs b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Output/HelpersMissingName.cs
new file mode 100644
index 00000000..aa0ea146
--- /dev/null
+++ b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Output/HelpersMissingName.cs
@@ -0,0 +1,21 @@
+//------------------------------------------------------------------------------
+// <auto-generated>
+// This code was generated by a tool.
+// Runtime Version:N.N.NNNNN.N
+//
+// Changes to this file may cause incorrect behavior and will be lost if
+// the code is regenerated.
+// </auto-generated>
+//------------------------------------------------------------------------------
+
+namespace TestOutput {
+using System;
+
+public class HelpersMissingName {
+#line hidden
+public HelpersMissingName() {
+}
+public override void Execute() {
+}
+}
+}
diff --git a/test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Output/HelpersMissingOpenBrace.cs b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Output/HelpersMissingOpenBrace.cs
new file mode 100644
index 00000000..c464f666
--- /dev/null
+++ b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Output/HelpersMissingOpenBrace.cs
@@ -0,0 +1,67 @@
+//------------------------------------------------------------------------------
+// <auto-generated>
+// This code was generated by a tool.
+// Runtime Version:N.N.NNNNN.N
+//
+// Changes to this file may cause incorrect behavior and will be lost if
+// the code is regenerated.
+// </auto-generated>
+//------------------------------------------------------------------------------
+
+namespace TestOutput {
+using System;
+
+public class HelpersMissingOpenBrace {
+#line hidden
+#line 1 "HelpersMissingOpenBrace.cshtml"
+public static Template Bold(string s) {
+#line default
+#line hidden
+return new Template(__razor_helper_writer => {
+
+#line 1 "HelpersMissingOpenBrace.cshtml"
+
+ s = s.ToUpper();
+
+#line default
+#line hidden
+WriteLiteralTo(__razor_helper_writer, " <strong>");
+
+#line 3 "HelpersMissingOpenBrace.cshtml"
+WriteTo(__razor_helper_writer, s);
+
+#line default
+#line hidden
+WriteLiteralTo(__razor_helper_writer, "</strong>\r\n");
+
+#line 4 "HelpersMissingOpenBrace.cshtml"
+
+#line default
+#line hidden
+});
+
+#line 4 "HelpersMissingOpenBrace.cshtml"
+}
+#line default
+#line hidden
+
+#line 6 "HelpersMissingOpenBrace.cshtml"
+public static Template Italic(string s)
+#line default
+#line hidden
+
+public HelpersMissingOpenBrace() {
+}
+public override void Execute() {
+WriteLiteral("\r\n");
+
+
+#line 7 "HelpersMissingOpenBrace.cshtml"
+Write(Italic(s));
+
+
+#line default
+#line hidden
+}
+}
+}
diff --git a/test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Output/HelpersMissingOpenParen.cs b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Output/HelpersMissingOpenParen.cs
new file mode 100644
index 00000000..136d52c9
--- /dev/null
+++ b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Output/HelpersMissingOpenParen.cs
@@ -0,0 +1,67 @@
+//------------------------------------------------------------------------------
+// <auto-generated>
+// This code was generated by a tool.
+// Runtime Version:N.N.NNNNN.N
+//
+// Changes to this file may cause incorrect behavior and will be lost if
+// the code is regenerated.
+// </auto-generated>
+//------------------------------------------------------------------------------
+
+namespace TestOutput {
+using System;
+
+public class HelpersMissingOpenParen {
+#line hidden
+#line 1 "HelpersMissingOpenParen.cshtml"
+public static Template Bold(string s) {
+#line default
+#line hidden
+return new Template(__razor_helper_writer => {
+
+#line 1 "HelpersMissingOpenParen.cshtml"
+
+ s = s.ToUpper();
+
+#line default
+#line hidden
+WriteLiteralTo(__razor_helper_writer, " <strong>");
+
+#line 3 "HelpersMissingOpenParen.cshtml"
+WriteTo(__razor_helper_writer, s);
+
+#line default
+#line hidden
+WriteLiteralTo(__razor_helper_writer, "</strong>\r\n");
+
+#line 4 "HelpersMissingOpenParen.cshtml"
+
+#line default
+#line hidden
+});
+
+#line 4 "HelpersMissingOpenParen.cshtml"
+}
+#line default
+#line hidden
+
+#line 6 "HelpersMissingOpenParen.cshtml"
+public static Template Italic
+#line default
+#line hidden
+
+public HelpersMissingOpenParen() {
+}
+public override void Execute() {
+WriteLiteral("\r\n");
+
+
+#line 7 "HelpersMissingOpenParen.cshtml"
+Write(Bold("Hello"));
+
+
+#line default
+#line hidden
+}
+}
+}
diff --git a/test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Output/HiddenSpansInCode.cs b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Output/HiddenSpansInCode.cs
new file mode 100644
index 00000000..31c1da09
--- /dev/null
+++ b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Output/HiddenSpansInCode.cs
@@ -0,0 +1,35 @@
+//------------------------------------------------------------------------------
+// <auto-generated>
+// This code was generated by a tool.
+// Runtime Version:N.N.NNNNN.N
+//
+// Changes to this file may cause incorrect behavior and will be lost if
+// the code is regenerated.
+// </auto-generated>
+//------------------------------------------------------------------------------
+
+namespace TestOutput {
+using System;
+
+public class HiddenSpansInCode {
+#line hidden
+public HiddenSpansInCode() {
+}
+public override void Execute() {
+
+#line 1 "HiddenSpansInCode.cshtml"
+
+
+
+#line default
+#line hidden
+
+#line 2 "HiddenSpansInCode.cshtml"
+ @Da
+
+
+#line default
+#line hidden
+}
+}
+}
diff --git a/test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Output/ImplicitExpression.cs b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Output/ImplicitExpression.cs
new file mode 100644
index 00000000..6d987037
--- /dev/null
+++ b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Output/ImplicitExpression.cs
@@ -0,0 +1,45 @@
+//------------------------------------------------------------------------------
+// <auto-generated>
+// This code was generated by a tool.
+// Runtime Version:N.N.NNNNN.N
+//
+// Changes to this file may cause incorrect behavior and will be lost if
+// the code is regenerated.
+// </auto-generated>
+//------------------------------------------------------------------------------
+
+namespace TestOutput {
+using System;
+
+public class ImplicitExpression {
+#line hidden
+public ImplicitExpression() {
+}
+public override void Execute() {
+
+#line 1 "ImplicitExpression.cshtml"
+ for(int i = 1; i <= 10; i++) {
+
+
+#line default
+#line hidden
+WriteLiteral(" <p>This is item #");
+
+
+#line 2 "ImplicitExpression.cshtml"
+ Write(i);
+
+
+#line default
+#line hidden
+WriteLiteral("</p>\r\n");
+
+
+#line 3 "ImplicitExpression.cshtml"
+}
+
+#line default
+#line hidden
+}
+}
+}
diff --git a/test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Output/ImplicitExpressionAtEOF.cs b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Output/ImplicitExpressionAtEOF.cs
new file mode 100644
index 00000000..0eff69ae
--- /dev/null
+++ b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Output/ImplicitExpressionAtEOF.cs
@@ -0,0 +1,29 @@
+//------------------------------------------------------------------------------
+// <auto-generated>
+// This code was generated by a tool.
+// Runtime Version:N.N.NNNNN.N
+//
+// Changes to this file may cause incorrect behavior and will be lost if
+// the code is regenerated.
+// </auto-generated>
+//------------------------------------------------------------------------------
+
+namespace TestOutput {
+using System;
+
+public class ImplicitExpressionAtEOF {
+private static object @__o;
+#line hidden
+public ImplicitExpressionAtEOF() {
+}
+public override void Execute() {
+
+#line 1 "ImplicitExpressionAtEOF.cshtml"
+__o = ;
+
+
+#line default
+#line hidden
+}
+}
+}
diff --git a/test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Output/Imports.DesignTime.cs b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Output/Imports.DesignTime.cs
new file mode 100644
index 00000000..082da73b
--- /dev/null
+++ b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Output/Imports.DesignTime.cs
@@ -0,0 +1,53 @@
+//------------------------------------------------------------------------------
+// <auto-generated>
+// This code was generated by a tool.
+// Runtime Version:N.N.NNNNN.N
+//
+// Changes to this file may cause incorrect behavior and will be lost if
+// the code is regenerated.
+// </auto-generated>
+//------------------------------------------------------------------------------
+
+namespace TestOutput {
+
+#line 3 "Imports.cshtml"
+using System;
+
+#line default
+#line hidden
+
+#line 1 "Imports.cshtml"
+using System.IO;
+
+#line default
+#line hidden
+
+#line 2 "Imports.cshtml"
+using Foo = System.Text.Encoding;
+
+#line default
+#line hidden
+
+public class Imports {
+private static object @__o;
+#line hidden
+public Imports() {
+}
+public override void Execute() {
+
+#line 4 "Imports.cshtml"
+ __o = typeof(Path).FullName;
+
+
+#line default
+#line hidden
+
+#line 5 "Imports.cshtml"
+ __o = typeof(Foo).FullName;
+
+
+#line default
+#line hidden
+}
+}
+}
diff --git a/test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Output/Imports.cs b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Output/Imports.cs
new file mode 100644
index 00000000..58105a3b
--- /dev/null
+++ b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Output/Imports.cs
@@ -0,0 +1,58 @@
+//------------------------------------------------------------------------------
+// <auto-generated>
+// This code was generated by a tool.
+// Runtime Version:N.N.NNNNN.N
+//
+// Changes to this file may cause incorrect behavior and will be lost if
+// the code is regenerated.
+// </auto-generated>
+//------------------------------------------------------------------------------
+
+namespace TestOutput {
+
+#line 3 "Imports.cshtml"
+using System;
+
+#line default
+#line hidden
+
+#line 1 "Imports.cshtml"
+using System.IO;
+
+#line default
+#line hidden
+
+#line 2 "Imports.cshtml"
+using Foo = System.Text.Encoding;
+
+#line default
+#line hidden
+
+public class Imports {
+#line hidden
+public Imports() {
+}
+public override void Execute() {
+WriteLiteral("\r\n<p>Path\'s full type name is ");
+
+
+#line 5 "Imports.cshtml"
+ Write(typeof(Path).FullName);
+
+
+#line default
+#line hidden
+WriteLiteral("</p>\r\n<p>Foo\'s actual full type name is ");
+
+
+#line 6 "Imports.cshtml"
+ Write(typeof(Foo).FullName);
+
+
+#line default
+#line hidden
+WriteLiteral("</p>");
+
+}
+}
+}
diff --git a/test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Output/Inherits.Designtime.cs b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Output/Inherits.Designtime.cs
new file mode 100644
index 00000000..44af46d9
--- /dev/null
+++ b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Output/Inherits.Designtime.cs
@@ -0,0 +1,40 @@
+//------------------------------------------------------------------------------
+// <auto-generated>
+// This code was generated by a tool.
+// Runtime Version:N.N.NNNNN.N
+//
+// Changes to this file may cause incorrect behavior and will be lost if
+// the code is regenerated.
+// </auto-generated>
+//------------------------------------------------------------------------------
+
+namespace TestOutput {
+using System;
+
+public class Inherits : foo.bar<baz<biz>>.boz bar {
+private static object @__o;
+#line hidden
+public Inherits() {
+}
+private void @__RazorDesignTimeHelpers__() {
+#pragma warning disable 219
+
+#line 2 "Inherits.cshtml"
+ foo.bar<baz<biz>>.boz bar __inheritsHelper = null;
+
+
+#line default
+#line hidden
+#pragma warning restore 219
+}
+public override void Execute() {
+
+#line 1 "Inherits.cshtml"
+__o = foo();
+
+
+#line default
+#line hidden
+}
+}
+}
diff --git a/test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Output/Inherits.Runtime.cs b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Output/Inherits.Runtime.cs
new file mode 100644
index 00000000..77e3633e
--- /dev/null
+++ b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Output/Inherits.Runtime.cs
@@ -0,0 +1,30 @@
+//------------------------------------------------------------------------------
+// <auto-generated>
+// This code was generated by a tool.
+// Runtime Version:N.N.NNNNN.N
+//
+// Changes to this file may cause incorrect behavior and will be lost if
+// the code is regenerated.
+// </auto-generated>
+//------------------------------------------------------------------------------
+
+namespace TestOutput {
+using System;
+
+public class Inherits : foo.bar<baz<biz>>.boz bar {
+#line hidden
+public Inherits() {
+}
+public override void Execute() {
+
+#line 1 "Inherits.cshtml"
+Write(foo());
+
+
+#line default
+#line hidden
+WriteLiteral("\r\n\r\n");
+
+}
+}
+}
diff --git a/test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Output/InlineBlocks.cs b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Output/InlineBlocks.cs
new file mode 100644
index 00000000..3f24dcb0
--- /dev/null
+++ b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Output/InlineBlocks.cs
@@ -0,0 +1,72 @@
+//------------------------------------------------------------------------------
+// <auto-generated>
+// This code was generated by a tool.
+// Runtime Version:N.N.NNNNN.N
+//
+// Changes to this file may cause incorrect behavior and will be lost if
+// the code is regenerated.
+// </auto-generated>
+//------------------------------------------------------------------------------
+
+namespace TestOutput {
+using System;
+
+public class InlineBlocks {
+#line hidden
+#line 1 "InlineBlocks.cshtml"
+public static Template Link(string link) {
+#line default
+#line hidden
+return new Template(__razor_helper_writer => {
+
+#line 1 "InlineBlocks.cshtml"
+
+
+#line default
+#line hidden
+WriteLiteralTo(__razor_helper_writer, " <a");
+WriteAttributeTo(__razor_helper_writer, "href", Tuple.Create(" href=\"", 35), Tuple.Create("\"", 93), Tuple.Create(Tuple.Create("", 42), Tuple.Create<System.Object, System.Int32>(new Template(__razor_attribute_value_writer => {
+
+#line 2 "InlineBlocks.cshtml"
+ if(link != null) {
+#line default
+#line hidden
+
+#line 2 "InlineBlocks.cshtml"
+WriteTo(__razor_attribute_value_writer, link);
+
+#line default
+#line hidden
+
+#line 2 "InlineBlocks.cshtml"
+ } else {
+#line default
+#line hidden
+WriteLiteralTo(__razor_attribute_value_writer, " ");
+WriteLiteralTo(__razor_attribute_value_writer, "#");
+WriteLiteralTo(__razor_attribute_value_writer, " ");
+
+#line 2 "InlineBlocks.cshtml"
+ }
+#line default
+#line hidden
+}), 42), false));
+WriteLiteralTo(__razor_helper_writer, " />\r\n");
+
+#line 3 "InlineBlocks.cshtml"
+
+#line default
+#line hidden
+});
+
+#line 3 "InlineBlocks.cshtml"
+}
+#line default
+#line hidden
+
+public InlineBlocks() {
+}
+public override void Execute() {
+}
+}
+}
diff --git a/test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Output/Instrumented.cs b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Output/Instrumented.cs
new file mode 100644
index 00000000..c0a7cc97
--- /dev/null
+++ b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Output/Instrumented.cs
@@ -0,0 +1,348 @@
+//------------------------------------------------------------------------------
+// <auto-generated>
+// This code was generated by a tool.
+// Runtime Version:N.N.NNNNN.N
+//
+// Changes to this file may cause incorrect behavior and will be lost if
+// the code is regenerated.
+// </auto-generated>
+//------------------------------------------------------------------------------
+
+namespace TestOutput {
+using System;
+
+public class Instrumented {
+#line hidden
+#line 1 "Instrumented.cshtml"
+public static Template Strong(string s) {
+#line default
+#line hidden
+return new Template(__razor_helper_writer => {
+
+#line 1 "Instrumented.cshtml"
+
+
+#line default
+#line hidden
+BeginContext(__razor_helper_writer, "~/Instrumented.cshtml", 28, 12, true);
+WriteLiteralTo(__razor_helper_writer, " <strong>");
+EndContext(__razor_helper_writer, "~/Instrumented.cshtml", 28, 12, true);
+BeginContext(__razor_helper_writer, "~/Instrumented.cshtml", 41, 1, false);
+
+#line 2 "Instrumented.cshtml"
+WriteTo(__razor_helper_writer, s);
+
+#line default
+#line hidden
+EndContext(__razor_helper_writer, "~/Instrumented.cshtml", 41, 1, false);
+BeginContext(__razor_helper_writer, "~/Instrumented.cshtml", 42, 11, true);
+WriteLiteralTo(__razor_helper_writer, "</strong>\r\n");
+EndContext(__razor_helper_writer, "~/Instrumented.cshtml", 42, 11, true);
+
+#line 3 "Instrumented.cshtml"
+
+#line default
+#line hidden
+});
+
+#line 3 "Instrumented.cshtml"
+}
+#line default
+#line hidden
+
+public Instrumented() {
+}
+public override void Execute() {
+BeginContext("~/Instrumented.cshtml", 56, 2, true);
+
+WriteLiteral("\r\n");
+
+EndContext("~/Instrumented.cshtml", 56, 2, true);
+
+
+#line 5 "Instrumented.cshtml"
+
+ int i = 1;
+ var foo =
+
+#line default
+#line hidden
+item => new Template(__razor_template_writer => {
+
+BeginContext(__razor_template_writer, "~/Instrumented.cshtml", 93, 10, true);
+
+WriteLiteralTo(__razor_template_writer, "<p>Bar</p>");
+
+EndContext(__razor_template_writer, "~/Instrumented.cshtml", 93, 10, true);
+
+})
+
+#line 7 "Instrumented.cshtml"
+ ;
+
+
+#line default
+#line hidden
+BeginContext("~/Instrumented.cshtml", 106, 4, true);
+
+WriteLiteral(" ");
+
+EndContext("~/Instrumented.cshtml", 106, 4, true);
+
+BeginContext("~/Instrumented.cshtml", 112, 14, true);
+
+WriteLiteral("Hello, World\r\n");
+
+EndContext("~/Instrumented.cshtml", 112, 14, true);
+
+BeginContext("~/Instrumented.cshtml", 126, 25, true);
+
+WriteLiteral(" <p>Hello, World</p>\r\n");
+
+EndContext("~/Instrumented.cshtml", 126, 25, true);
+
+
+#line 10 "Instrumented.cshtml"
+
+
+#line default
+#line hidden
+BeginContext("~/Instrumented.cshtml", 152, 4, true);
+
+WriteLiteral("\r\n\r\n");
+
+EndContext("~/Instrumented.cshtml", 152, 4, true);
+
+
+#line 12 "Instrumented.cshtml"
+ while(i <= 10) {
+
+
+#line default
+#line hidden
+BeginContext("~/Instrumented.cshtml", 175, 23, true);
+
+WriteLiteral(" <p>Hello from C#, #");
+
+EndContext("~/Instrumented.cshtml", 175, 23, true);
+
+BeginContext("~/Instrumented.cshtml", 200, 1, false);
+
+
+#line 13 "Instrumented.cshtml"
+ Write(i);
+
+
+#line default
+#line hidden
+EndContext("~/Instrumented.cshtml", 200, 1, false);
+
+BeginContext("~/Instrumented.cshtml", 202, 6, true);
+
+WriteLiteral("</p>\r\n");
+
+EndContext("~/Instrumented.cshtml", 202, 6, true);
+
+
+#line 14 "Instrumented.cshtml"
+ i += 1;
+}
+
+
+#line default
+#line hidden
+BeginContext("~/Instrumented.cshtml", 224, 2, true);
+
+WriteLiteral("\r\n");
+
+EndContext("~/Instrumented.cshtml", 224, 2, true);
+
+
+#line 17 "Instrumented.cshtml"
+ if(i == 11) {
+
+
+#line default
+#line hidden
+BeginContext("~/Instrumented.cshtml", 242, 31, true);
+
+WriteLiteral(" <p>We wrote 10 lines!</p>\r\n");
+
+EndContext("~/Instrumented.cshtml", 242, 31, true);
+
+
+#line 19 "Instrumented.cshtml"
+}
+
+
+#line default
+#line hidden
+BeginContext("~/Instrumented.cshtml", 276, 2, true);
+
+WriteLiteral("\r\n");
+
+EndContext("~/Instrumented.cshtml", 276, 2, true);
+
+
+#line 21 "Instrumented.cshtml"
+ switch(i) {
+ case 11:
+
+
+#line default
+#line hidden
+BeginContext("~/Instrumented.cshtml", 306, 46, true);
+
+WriteLiteral(" <p>No really, we wrote 10 lines!</p>\r\n");
+
+EndContext("~/Instrumented.cshtml", 306, 46, true);
+
+
+#line 24 "Instrumented.cshtml"
+ break;
+ default:
+
+
+#line default
+#line hidden
+BeginContext("~/Instrumented.cshtml", 382, 39, true);
+
+WriteLiteral(" <p>Actually, we didn\'t...</p>\r\n");
+
+EndContext("~/Instrumented.cshtml", 382, 39, true);
+
+
+#line 27 "Instrumented.cshtml"
+ break;
+}
+
+
+#line default
+#line hidden
+BeginContext("~/Instrumented.cshtml", 440, 2, true);
+
+WriteLiteral("\r\n");
+
+EndContext("~/Instrumented.cshtml", 440, 2, true);
+
+
+#line 30 "Instrumented.cshtml"
+ for(int j = 1; j <= 10; j += 2) {
+
+
+#line default
+#line hidden
+BeginContext("~/Instrumented.cshtml", 478, 29, true);
+
+WriteLiteral(" <p>Hello again from C#, #");
+
+EndContext("~/Instrumented.cshtml", 478, 29, true);
+
+BeginContext("~/Instrumented.cshtml", 509, 1, false);
+
+
+#line 31 "Instrumented.cshtml"
+ Write(j);
+
+
+#line default
+#line hidden
+EndContext("~/Instrumented.cshtml", 509, 1, false);
+
+BeginContext("~/Instrumented.cshtml", 511, 6, true);
+
+WriteLiteral("</p>\r\n");
+
+EndContext("~/Instrumented.cshtml", 511, 6, true);
+
+
+#line 32 "Instrumented.cshtml"
+}
+
+
+#line default
+#line hidden
+BeginContext("~/Instrumented.cshtml", 520, 2, true);
+
+WriteLiteral("\r\n");
+
+EndContext("~/Instrumented.cshtml", 520, 2, true);
+
+
+#line 34 "Instrumented.cshtml"
+ try {
+
+
+#line default
+#line hidden
+BeginContext("~/Instrumented.cshtml", 530, 41, true);
+
+WriteLiteral(" <p>That time, we wrote 5 lines!</p>\r\n");
+
+EndContext("~/Instrumented.cshtml", 530, 41, true);
+
+
+#line 36 "Instrumented.cshtml"
+} catch(Exception ex) {
+
+
+#line default
+#line hidden
+BeginContext("~/Instrumented.cshtml", 596, 33, true);
+
+WriteLiteral(" <p>Oh no! An error occurred: ");
+
+EndContext("~/Instrumented.cshtml", 596, 33, true);
+
+BeginContext("~/Instrumented.cshtml", 631, 10, false);
+
+
+#line 37 "Instrumented.cshtml"
+ Write(ex.Message);
+
+
+#line default
+#line hidden
+EndContext("~/Instrumented.cshtml", 631, 10, false);
+
+BeginContext("~/Instrumented.cshtml", 642, 6, true);
+
+WriteLiteral("</p>\r\n");
+
+EndContext("~/Instrumented.cshtml", 642, 6, true);
+
+
+#line 38 "Instrumented.cshtml"
+}
+
+
+#line default
+#line hidden
+BeginContext("~/Instrumented.cshtml", 651, 2, true);
+
+WriteLiteral("\r\n");
+
+EndContext("~/Instrumented.cshtml", 651, 2, true);
+
+
+#line 40 "Instrumented.cshtml"
+ lock(new object()) {
+
+
+#line default
+#line hidden
+BeginContext("~/Instrumented.cshtml", 676, 53, true);
+
+WriteLiteral(" <p>This block is locked, for your security!</p>\r\n");
+
+EndContext("~/Instrumented.cshtml", 676, 53, true);
+
+
+#line 42 "Instrumented.cshtml"
+}
+
+#line default
+#line hidden
+}
+}
+}
diff --git a/test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Output/LayoutDirective.cs b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Output/LayoutDirective.cs
new file mode 100644
index 00000000..86c0e0f6
--- /dev/null
+++ b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Output/LayoutDirective.cs
@@ -0,0 +1,22 @@
+//------------------------------------------------------------------------------
+// <auto-generated>
+// This code was generated by a tool.
+// Runtime Version:N.N.NNNNN.N
+//
+// Changes to this file may cause incorrect behavior and will be lost if
+// the code is regenerated.
+// </auto-generated>
+//------------------------------------------------------------------------------
+
+namespace TestOutput {
+using System;
+
+public class LayoutDirective {
+#line hidden
+public LayoutDirective() {
+}
+public override void Execute() {
+Layout = "~/Foo/Bar/Baz";
+}
+}
+}
diff --git a/test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Output/MarkupInCodeBlock.cs b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Output/MarkupInCodeBlock.cs
new file mode 100644
index 00000000..14a1f096
--- /dev/null
+++ b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Output/MarkupInCodeBlock.cs
@@ -0,0 +1,49 @@
+//------------------------------------------------------------------------------
+// <auto-generated>
+// This code was generated by a tool.
+// Runtime Version:N.N.NNNNN.N
+//
+// Changes to this file may cause incorrect behavior and will be lost if
+// the code is regenerated.
+// </auto-generated>
+//------------------------------------------------------------------------------
+
+namespace TestOutput {
+using System;
+
+public class MarkupInCodeBlock {
+#line hidden
+public MarkupInCodeBlock() {
+}
+public override void Execute() {
+
+#line 1 "MarkupInCodeBlock.cshtml"
+
+ for(int i = 1; i <= 10; i++) {
+
+
+#line default
+#line hidden
+WriteLiteral(" <p>Hello from C#, #");
+
+
+#line 3 "MarkupInCodeBlock.cshtml"
+ Write(i.ToString());
+
+
+#line default
+#line hidden
+WriteLiteral("</p>\r\n");
+
+
+#line 4 "MarkupInCodeBlock.cshtml"
+ }
+
+
+#line default
+#line hidden
+WriteLiteral("\r\n");
+
+}
+}
+}
diff --git a/test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Output/NestedCodeBlocks.cs b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Output/NestedCodeBlocks.cs
new file mode 100644
index 00000000..424e26c4
--- /dev/null
+++ b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Output/NestedCodeBlocks.cs
@@ -0,0 +1,42 @@
+//------------------------------------------------------------------------------
+// <auto-generated>
+// This code was generated by a tool.
+// Runtime Version:N.N.NNNNN.N
+//
+// Changes to this file may cause incorrect behavior and will be lost if
+// the code is regenerated.
+// </auto-generated>
+//------------------------------------------------------------------------------
+
+namespace TestOutput {
+using System;
+
+public class NestedCodeBlocks {
+#line hidden
+public NestedCodeBlocks() {
+}
+public override void Execute() {
+
+#line 1 "NestedCodeBlocks.cshtml"
+ if(foo) {
+
+
+#line default
+#line hidden
+
+#line 2 "NestedCodeBlocks.cshtml"
+ if(bar) {
+ }
+
+#line default
+#line hidden
+
+#line 3 "NestedCodeBlocks.cshtml"
+
+}
+
+#line default
+#line hidden
+}
+}
+}
diff --git a/test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Output/NestedHelpers.cs b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Output/NestedHelpers.cs
new file mode 100644
index 00000000..76d02f53
--- /dev/null
+++ b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Output/NestedHelpers.cs
@@ -0,0 +1,100 @@
+//------------------------------------------------------------------------------
+// <auto-generated>
+// This code was generated by a tool.
+// Runtime Version:N.N.NNNNN.N
+//
+// Changes to this file may cause incorrect behavior and will be lost if
+// the code is regenerated.
+// </auto-generated>
+//------------------------------------------------------------------------------
+
+namespace TestOutput {
+using System;
+
+public class NestedHelpers {
+#line hidden
+#line 3 "NestedHelpers.cshtml"
+public static Template Bold(string s) {
+#line default
+#line hidden
+return new Template(__razor_helper_writer => {
+
+#line 3 "NestedHelpers.cshtml"
+
+ s = s.ToUpper();
+
+#line default
+#line hidden
+WriteLiteralTo(__razor_helper_writer, " <strong>");
+
+#line 5 "NestedHelpers.cshtml"
+WriteTo(__razor_helper_writer, s);
+
+#line default
+#line hidden
+WriteLiteralTo(__razor_helper_writer, "</strong>\r\n");
+
+#line 6 "NestedHelpers.cshtml"
+
+#line default
+#line hidden
+});
+
+#line 6 "NestedHelpers.cshtml"
+}
+#line default
+#line hidden
+
+#line 1 "NestedHelpers.cshtml"
+public static Template Italic(string s) {
+#line default
+#line hidden
+return new Template(__razor_helper_writer => {
+
+#line 1 "NestedHelpers.cshtml"
+
+ s = s.ToUpper();
+
+#line default
+#line hidden
+
+#line 6 "NestedHelpers.cshtml"
+
+
+#line default
+#line hidden
+WriteLiteralTo(__razor_helper_writer, " <em>");
+
+#line 7 "NestedHelpers.cshtml"
+WriteTo(__razor_helper_writer, Bold(s));
+
+#line default
+#line hidden
+WriteLiteralTo(__razor_helper_writer, "</em>\r\n");
+
+#line 8 "NestedHelpers.cshtml"
+
+#line default
+#line hidden
+});
+
+#line 8 "NestedHelpers.cshtml"
+}
+#line default
+#line hidden
+
+public NestedHelpers() {
+}
+public override void Execute() {
+WriteLiteral("\r\n");
+
+
+#line 10 "NestedHelpers.cshtml"
+Write(Italic("Hello"));
+
+
+#line default
+#line hidden
+}
+}
+}
diff --git a/test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Output/NoLinePragmas.cs b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Output/NoLinePragmas.cs
new file mode 100644
index 00000000..3fd500a5
--- /dev/null
+++ b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Output/NoLinePragmas.cs
@@ -0,0 +1,102 @@
+//------------------------------------------------------------------------------
+// <auto-generated>
+// This code was generated by a tool.
+// Runtime Version:N.N.NNNNN.N
+//
+// Changes to this file may cause incorrect behavior and will be lost if
+// the code is regenerated.
+// </auto-generated>
+//------------------------------------------------------------------------------
+
+namespace TestOutput {
+using System;
+
+public class NoLinePragmas {
+#line hidden
+public NoLinePragmas() {
+}
+public override void Execute() {
+
+ int i = 1;
+
+WriteLiteral("\r\n\r\n");
+
+ while(i <= 10) {
+
+WriteLiteral(" <p>Hello from C#, #");
+
+ Write(i);
+
+WriteLiteral("</p>\r\n");
+
+ i += 1;
+}
+
+WriteLiteral("\r\n");
+
+ if(i == 11) {
+
+WriteLiteral(" <p>We wrote 10 lines!</p>\r\n");
+
+}
+
+WriteLiteral("\r\n");
+
+ switch(i) {
+ case 11:
+
+WriteLiteral(" <p>No really, we wrote 10 lines!</p>\r\n");
+
+ break;
+ default:
+
+WriteLiteral(" <p>Actually, we didn\'t...</p>\r\n");
+
+ break;
+}
+
+WriteLiteral("\r\n");
+
+ for(int j = 1; j <= 10; j += 2) {
+
+WriteLiteral(" <p>Hello again from C#, #");
+
+ Write(j);
+
+WriteLiteral("</p>\r\n");
+
+}
+
+WriteLiteral("\r\n");
+
+ try {
+
+WriteLiteral(" <p>That time, we wrote 5 lines!</p>\r\n");
+
+} catch(Exception ex) {
+
+WriteLiteral(" <p>Oh no! An error occurred: ");
+
+ Write(ex.Message);
+
+WriteLiteral("</p>\r\n");
+
+}
+
+
+
+
+WriteLiteral("<p>i is now ");
+
+ Write(i);
+
+WriteLiteral("</p>\r\n\r\n");
+
+ lock(new object()) {
+
+WriteLiteral(" <p>This block is locked, for your security!</p>\r\n");
+
+}
+}
+}
+}
diff --git a/test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Output/ParserError.cs b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Output/ParserError.cs
new file mode 100644
index 00000000..79d25e62
--- /dev/null
+++ b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Output/ParserError.cs
@@ -0,0 +1,31 @@
+//------------------------------------------------------------------------------
+// <auto-generated>
+// This code was generated by a tool.
+// Runtime Version:N.N.NNNNN.N
+//
+// Changes to this file may cause incorrect behavior and will be lost if
+// the code is regenerated.
+// </auto-generated>
+//------------------------------------------------------------------------------
+
+namespace TestOutput {
+using System;
+
+public class ParserError {
+#line hidden
+public ParserError() {
+}
+public override void Execute() {
+
+#line 1 "ParserError.cshtml"
+
+/*
+int i =10;
+int j =20;
+}
+
+#line default
+#line hidden
+}
+}
+}
diff --git a/test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Output/RazorComments.DesignTime.cs b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Output/RazorComments.DesignTime.cs
new file mode 100644
index 00000000..dc732228
--- /dev/null
+++ b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Output/RazorComments.DesignTime.cs
@@ -0,0 +1,72 @@
+//------------------------------------------------------------------------------
+// <auto-generated>
+// This code was generated by a tool.
+// Runtime Version:N.N.NNNNN.N
+//
+// Changes to this file may cause incorrect behavior and will be lost if
+// the code is regenerated.
+// </auto-generated>
+//------------------------------------------------------------------------------
+
+namespace TestOutput {
+using System;
+
+public class RazorComments {
+private static object @__o;
+#line hidden
+public RazorComments() {
+}
+public override void Execute() {
+
+#line 1 "RazorComments.cshtml"
+
+
+
+#line default
+#line hidden
+
+#line 2 "RazorComments.cshtml"
+
+ Exception foo =
+
+#line default
+#line hidden
+
+#line 3 "RazorComments.cshtml"
+ null;
+ if(foo != null) {
+ throw foo;
+ }
+
+
+#line default
+#line hidden
+
+#line 4 "RazorComments.cshtml"
+ var bar = "@* bar *@";
+
+#line default
+#line hidden
+
+#line 5 "RazorComments.cshtml"
+ __o = bar;
+
+
+#line default
+#line hidden
+
+#line 6 "RazorComments.cshtml"
+__o = a
+
+#line default
+#line hidden
+
+#line 7 "RazorComments.cshtml"
+ b;
+
+
+#line default
+#line hidden
+}
+}
+}
diff --git a/test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Output/RazorComments.cs b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Output/RazorComments.cs
new file mode 100644
index 00000000..72d45d60
--- /dev/null
+++ b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Output/RazorComments.cs
@@ -0,0 +1,83 @@
+//------------------------------------------------------------------------------
+// <auto-generated>
+// This code was generated by a tool.
+// Runtime Version:N.N.NNNNN.N
+//
+// Changes to this file may cause incorrect behavior and will be lost if
+// the code is regenerated.
+// </auto-generated>
+//------------------------------------------------------------------------------
+
+namespace TestOutput {
+using System;
+
+public class RazorComments {
+#line hidden
+public RazorComments() {
+}
+public override void Execute() {
+WriteLiteral("\r\n<p>This should ");
+
+WriteLiteral(" be shown</p>\r\n\r\n");
+
+
+#line 4 "RazorComments.cshtml"
+
+
+
+#line default
+#line hidden
+
+#line 5 "RazorComments.cshtml"
+
+ Exception foo =
+
+#line default
+#line hidden
+
+#line 6 "RazorComments.cshtml"
+ null;
+ if(foo != null) {
+ throw foo;
+ }
+
+
+#line default
+#line hidden
+WriteLiteral("\r\n\r\n");
+
+
+#line 12 "RazorComments.cshtml"
+ var bar = "@* bar *@";
+
+#line default
+#line hidden
+WriteLiteral("\r\n<p>But this should show the comment syntax: ");
+
+
+#line 13 "RazorComments.cshtml"
+ Write(bar);
+
+
+#line default
+#line hidden
+WriteLiteral("</p>\r\n\r\n");
+
+
+#line 15 "RazorComments.cshtml"
+Write(a
+
+#line default
+#line hidden
+
+#line 15 "RazorComments.cshtml"
+ b);
+
+
+#line default
+#line hidden
+WriteLiteral("\r\n");
+
+}
+}
+}
diff --git a/test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Output/ResolveUrl.cs b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Output/ResolveUrl.cs
new file mode 100644
index 00000000..f99f1ec1
--- /dev/null
+++ b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Output/ResolveUrl.cs
@@ -0,0 +1,230 @@
+//------------------------------------------------------------------------------
+// <auto-generated>
+// This code was generated by a tool.
+// Runtime Version:N.N.NNNNN.N
+//
+// Changes to this file may cause incorrect behavior and will be lost if
+// the code is regenerated.
+// </auto-generated>
+//------------------------------------------------------------------------------
+
+namespace TestOutput {
+using System;
+
+public class ResolveUrl {
+#line hidden
+public ResolveUrl() {
+}
+public override void Execute() {
+WriteLiteral("<a");
+
+WriteAttribute("href", Tuple.Create(" href=\"", 2), Tuple.Create("\"", 14)
+, Tuple.Create(Tuple.Create("", 9), Tuple.Create<System.Object, System.Int32>(Href("~/Foo")
+, 9), false)
+);
+
+WriteLiteral(">Foo</a>\r\n<a");
+
+WriteAttribute("href", Tuple.Create(" href=\"", 27), Tuple.Create("\"", 56)
+, Tuple.Create(Tuple.Create("", 34), Tuple.Create<System.Object, System.Int32>(Href("~/Products/")
+, 34), false)
+
+#line 2 "ResolveUrl.cshtml"
+, Tuple.Create(Tuple.Create("", 45), Tuple.Create<System.Object, System.Int32>(product.id
+
+#line default
+#line hidden
+, 45), false)
+);
+
+WriteLiteral(">");
+
+
+#line 2 "ResolveUrl.cshtml"
+ Write(product.Name);
+
+
+#line default
+#line hidden
+WriteLiteral("</a>\r\n<a");
+
+WriteAttribute("href", Tuple.Create(" href=\"", 79), Tuple.Create("\"", 115)
+, Tuple.Create(Tuple.Create("", 86), Tuple.Create<System.Object, System.Int32>(Href("~/Products/")
+, 86), false)
+
+#line 3 "ResolveUrl.cshtml"
+, Tuple.Create(Tuple.Create("", 97), Tuple.Create<System.Object, System.Int32>(product.id
+
+#line default
+#line hidden
+, 97), false)
+, Tuple.Create(Tuple.Create("", 108), Tuple.Create("/Detail", 108), true)
+);
+
+WriteLiteral(">Details</a>\r\n<a");
+
+WriteAttribute("href", Tuple.Create(" href=\"", 132), Tuple.Create("\"", 187)
+, Tuple.Create(Tuple.Create("", 139), Tuple.Create<System.Object, System.Int32>(Href("~/A+Really(Crazy),Url.Is:This/")
+, 139), false)
+
+#line 4 "ResolveUrl.cshtml"
+, Tuple.Create(Tuple.Create("", 169), Tuple.Create<System.Object, System.Int32>(product.id
+
+#line default
+#line hidden
+, 169), false)
+, Tuple.Create(Tuple.Create("", 180), Tuple.Create("/Detail", 180), true)
+);
+
+WriteLiteral(">Crazy Url!</a>\r\n\r\n");
+
+
+#line 6 "ResolveUrl.cshtml"
+
+
+
+#line default
+#line hidden
+WriteLiteral(" ");
+
+WriteLiteral("\r\n <a");
+
+WriteAttribute("href", Tuple.Create(" href=\"", 233), Tuple.Create("\"", 245)
+, Tuple.Create(Tuple.Create("", 240), Tuple.Create<System.Object, System.Int32>(Href("~/Foo")
+, 240), false)
+);
+
+WriteLiteral(">Foo</a>\r\n <a");
+
+WriteAttribute("href", Tuple.Create(" href=\"", 266), Tuple.Create("\"", 295)
+, Tuple.Create(Tuple.Create("", 273), Tuple.Create<System.Object, System.Int32>(Href("~/Products/")
+, 273), false)
+
+#line 9 "ResolveUrl.cshtml"
+, Tuple.Create(Tuple.Create("", 284), Tuple.Create<System.Object, System.Int32>(product.id
+
+#line default
+#line hidden
+, 284), false)
+);
+
+WriteLiteral(">");
+
+
+#line 9 "ResolveUrl.cshtml"
+ Write(product.Name);
+
+
+#line default
+#line hidden
+WriteLiteral("</a>\r\n <a");
+
+WriteAttribute("href", Tuple.Create(" href=\"", 326), Tuple.Create("\"", 362)
+, Tuple.Create(Tuple.Create("", 333), Tuple.Create<System.Object, System.Int32>(Href("~/Products/")
+, 333), false)
+
+#line 10 "ResolveUrl.cshtml"
+, Tuple.Create(Tuple.Create("", 344), Tuple.Create<System.Object, System.Int32>(product.id
+
+#line default
+#line hidden
+, 344), false)
+, Tuple.Create(Tuple.Create("", 355), Tuple.Create("/Detail", 355), true)
+);
+
+WriteLiteral(">Details</a>\r\n <a");
+
+WriteAttribute("href", Tuple.Create(" href=\"", 387), Tuple.Create("\"", 442)
+, Tuple.Create(Tuple.Create("", 394), Tuple.Create<System.Object, System.Int32>(Href("~/A+Really(Crazy),Url.Is:This/")
+, 394), false)
+
+#line 11 "ResolveUrl.cshtml"
+, Tuple.Create(Tuple.Create("", 424), Tuple.Create<System.Object, System.Int32>(product.id
+
+#line default
+#line hidden
+, 424), false)
+, Tuple.Create(Tuple.Create("", 435), Tuple.Create("/Detail", 435), true)
+);
+
+WriteLiteral(">Crazy Url!</a>\r\n ");
+
+WriteLiteral("\r\n");
+
+
+#line 13 "ResolveUrl.cshtml"
+
+
+#line default
+#line hidden
+WriteLiteral("\r\n\r\n");
+
+DefineSection("Foo", () => {
+
+WriteLiteral("\r\n <a");
+
+WriteAttribute("href", Tuple.Create(" href=\"", 500), Tuple.Create("\"", 512)
+, Tuple.Create(Tuple.Create("", 507), Tuple.Create<System.Object, System.Int32>(Href("~/Foo")
+, 507), false)
+);
+
+WriteLiteral(">Foo</a>\r\n <a");
+
+WriteAttribute("href", Tuple.Create(" href=\"", 529), Tuple.Create("\"", 558)
+, Tuple.Create(Tuple.Create("", 536), Tuple.Create<System.Object, System.Int32>(Href("~/Products/")
+, 536), false)
+
+#line 17 "ResolveUrl.cshtml"
+, Tuple.Create(Tuple.Create("", 547), Tuple.Create<System.Object, System.Int32>(product.id
+
+#line default
+#line hidden
+, 547), false)
+);
+
+WriteLiteral(">");
+
+
+#line 17 "ResolveUrl.cshtml"
+ Write(product.Name);
+
+
+#line default
+#line hidden
+WriteLiteral("</a>\r\n <a");
+
+WriteAttribute("href", Tuple.Create(" href=\"", 585), Tuple.Create("\"", 621)
+, Tuple.Create(Tuple.Create("", 592), Tuple.Create<System.Object, System.Int32>(Href("~/Products/")
+, 592), false)
+
+#line 18 "ResolveUrl.cshtml"
+, Tuple.Create(Tuple.Create("", 603), Tuple.Create<System.Object, System.Int32>(product.id
+
+#line default
+#line hidden
+, 603), false)
+, Tuple.Create(Tuple.Create("", 614), Tuple.Create("/Detail", 614), true)
+);
+
+WriteLiteral(">Details</a>\r\n <a");
+
+WriteAttribute("href", Tuple.Create(" href=\"", 642), Tuple.Create("\"", 697)
+, Tuple.Create(Tuple.Create("", 649), Tuple.Create<System.Object, System.Int32>(Href("~/A+Really(Crazy),Url.Is:This/")
+, 649), false)
+
+#line 19 "ResolveUrl.cshtml"
+, Tuple.Create(Tuple.Create("", 679), Tuple.Create<System.Object, System.Int32>(product.id
+
+#line default
+#line hidden
+, 679), false)
+, Tuple.Create(Tuple.Create("", 690), Tuple.Create("/Detail", 690), true)
+);
+
+WriteLiteral(">Crazy Url!</a>\r\n");
+
+});
+
+}
+}
+}
diff --git a/test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Output/Sections.cs b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Output/Sections.cs
new file mode 100644
index 00000000..13f24b73
--- /dev/null
+++ b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Output/Sections.cs
@@ -0,0 +1,45 @@
+//------------------------------------------------------------------------------
+// <auto-generated>
+// This code was generated by a tool.
+// Runtime Version:N.N.NNNNN.N
+//
+// Changes to this file may cause incorrect behavior and will be lost if
+// the code is regenerated.
+// </auto-generated>
+//------------------------------------------------------------------------------
+
+namespace TestOutput {
+using System;
+
+public class Sections {
+#line hidden
+public Sections() {
+}
+public override void Execute() {
+
+#line 1 "Sections.cshtml"
+
+ Layout = "_SectionTestLayout.cshtml"
+
+
+#line default
+#line hidden
+WriteLiteral("\r\n\r\n<div>This is in the Body>\r\n\r\n");
+
+DefineSection("Section2", () => {
+
+WriteLiteral("\r\n <div>This is in Section 2</div>\r\n");
+
+});
+
+WriteLiteral("\r\n");
+
+DefineSection("Section1", () => {
+
+WriteLiteral("\r\n <div>This is in Section 1</div>\r\n");
+
+});
+
+}
+}
+}
diff --git a/test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Output/Templates.cs b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Output/Templates.cs
new file mode 100644
index 00000000..8131acf7
--- /dev/null
+++ b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Output/Templates.cs
@@ -0,0 +1,180 @@
+//------------------------------------------------------------------------------
+// <auto-generated>
+// This code was generated by a tool.
+// Runtime Version:N.N.NNNNN.N
+//
+// Changes to this file may cause incorrect behavior and will be lost if
+// the code is regenerated.
+// </auto-generated>
+//------------------------------------------------------------------------------
+
+namespace TestOutput {
+using System;
+
+public class Templates {
+#line hidden
+#line 1 "Templates.cshtml"
+
+ public HelperResult Repeat(int times, Func<int, object> template) {
+ return new HelperResult((writer) => {
+ for(int i = 0; i < times; i++) {
+ ((HelperResult)template(i)).WriteTo(writer);
+ }
+ });
+ }
+
+#line default
+#line hidden
+
+public Templates() {
+}
+public override void Execute() {
+WriteLiteral("\r\n");
+
+
+#line 11 "Templates.cshtml"
+
+ Func<dynamic, object> foo =
+
+#line default
+#line hidden
+item => new Template(__razor_template_writer => {
+
+WriteLiteralTo(__razor_template_writer, "This works ");
+
+
+#line 12 "Templates.cshtml"
+ WriteTo(__razor_template_writer, item);
+
+
+#line default
+#line hidden
+WriteLiteralTo(__razor_template_writer, "!");
+
+})
+
+#line 12 "Templates.cshtml"
+ ;
+
+
+#line default
+#line hidden
+
+#line 13 "Templates.cshtml"
+Write(foo(""));
+
+
+#line default
+#line hidden
+
+#line 13 "Templates.cshtml"
+
+
+
+#line default
+#line hidden
+WriteLiteral("\r\n\r\n<ul>\r\n");
+
+
+#line 17 "Templates.cshtml"
+Write(Repeat(10, item => new Template(__razor_template_writer => {
+
+
+#line default
+#line hidden
+WriteLiteralTo(__razor_template_writer, "<li>Item #");
+
+
+#line 17 "Templates.cshtml"
+WriteTo(__razor_template_writer, item);
+
+
+#line default
+#line hidden
+WriteLiteralTo(__razor_template_writer, "</li>");
+
+
+#line 17 "Templates.cshtml"
+ })));
+
+
+#line default
+#line hidden
+WriteLiteral("\r\n</ul>\r\n\r\n<p>\r\n");
+
+
+#line 21 "Templates.cshtml"
+Write(Repeat(10,
+ item => new Template(__razor_template_writer => {
+
+
+#line default
+#line hidden
+WriteLiteralTo(__razor_template_writer, " This is line#");
+
+
+#line 22 "Templates.cshtml"
+WriteTo(__razor_template_writer, item);
+
+
+#line default
+#line hidden
+WriteLiteralTo(__razor_template_writer, " of markup<br/>\r\n");
+
+
+#line 23 "Templates.cshtml"
+})));
+
+
+#line default
+#line hidden
+WriteLiteral("\r\n</p>\r\n\r\n<ul>\r\n");
+
+WriteLiteral(" ");
+
+
+#line 27 "Templates.cshtml"
+Write(Repeat(10, item => new Template(__razor_template_writer => {
+
+
+#line default
+#line hidden
+WriteLiteralTo(__razor_template_writer, "<li>\r\n Item #");
+
+
+#line 28 "Templates.cshtml"
+WriteTo(__razor_template_writer, item);
+
+
+#line default
+#line hidden
+WriteLiteralTo(__razor_template_writer, "\r\n");
+
+
+#line 29 "Templates.cshtml"
+
+
+#line default
+#line hidden
+
+#line 29 "Templates.cshtml"
+ var parent = item;
+
+#line default
+#line hidden
+WriteLiteralTo(__razor_template_writer, "\r\n <ul>\r\n <li>Child Items... ?</li>\r\n ");
+
+WriteLiteralTo(__razor_template_writer, "\r\n </ul>\r\n </li>");
+
+
+#line 34 "Templates.cshtml"
+ })));
+
+
+#line default
+#line hidden
+WriteLiteral("\r\n</ul> ");
+
+}
+}
+}
diff --git a/test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Output/UnfinishedExpressionInCode.cs b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Output/UnfinishedExpressionInCode.cs
new file mode 100644
index 00000000..2a469eea
--- /dev/null
+++ b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Output/UnfinishedExpressionInCode.cs
@@ -0,0 +1,43 @@
+//------------------------------------------------------------------------------
+// <auto-generated>
+// This code was generated by a tool.
+// Runtime Version:N.N.NNNNN.N
+//
+// Changes to this file may cause incorrect behavior and will be lost if
+// the code is regenerated.
+// </auto-generated>
+//------------------------------------------------------------------------------
+
+namespace TestOutput {
+using System;
+
+public class UnfinishedExpressionInCode {
+private static object @__o;
+#line hidden
+public UnfinishedExpressionInCode() {
+}
+public override void Execute() {
+
+#line 1 "UnfinishedExpressionInCode.cshtml"
+
+
+
+#line default
+#line hidden
+
+#line 2 "UnfinishedExpressionInCode.cshtml"
+__o = DateTime.;
+
+
+#line default
+#line hidden
+
+#line 3 "UnfinishedExpressionInCode.cshtml"
+
+
+
+#line default
+#line hidden
+}
+}
+}
diff --git a/test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Source/Blocks.cshtml b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Source/Blocks.cshtml
new file mode 100644
index 00000000..8d27de89
--- /dev/null
+++ b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Source/Blocks.cshtml
@@ -0,0 +1,37 @@
+@{
+ int i = 1;
+}
+
+@while(i <= 10) {
+ <p>Hello from C#, #@(i)</p>
+ i += 1;
+}
+
+@if(i == 11) {
+ <p>We wrote 10 lines!</p>
+}
+
+@switch(i) {
+ case 11:
+ <p>No really, we wrote 10 lines!</p>
+ break;
+ default:
+ <p>Actually, we didn't...</p>
+ break;
+}
+
+@for(int j = 1; j <= 10; j += 2) {
+ <p>Hello again from C#, #@(j)</p>
+}
+
+@try {
+ <p>That time, we wrote 5 lines!</p>
+} catch(Exception ex) {
+ <p>Oh no! An error occurred: @(ex.Message)</p>
+}
+
+<p>i is now @i</p>
+
+@lock(new object()) {
+ <p>This block is locked, for your security!</p>
+} \ No newline at end of file
diff --git a/test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Source/CodeBlock.cshtml b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Source/CodeBlock.cshtml
new file mode 100644
index 00000000..1c78883a
--- /dev/null
+++ b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Source/CodeBlock.cshtml
@@ -0,0 +1,5 @@
+@{
+ for(int i = 1; i <= 10; i++) {
+ Output.Write("<p>Hello from C#, #" + i.ToString() + "</p>");
+ }
+} \ No newline at end of file
diff --git a/test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Source/CodeBlockAtEOF.cshtml b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Source/CodeBlockAtEOF.cshtml
new file mode 100644
index 00000000..38417d48
--- /dev/null
+++ b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Source/CodeBlockAtEOF.cshtml
@@ -0,0 +1 @@
+@{ \ No newline at end of file
diff --git a/test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Source/ConditionalAttributes.cshtml b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Source/ConditionalAttributes.cshtml
new file mode 100644
index 00000000..be1a9c20
--- /dev/null
+++ b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Source/ConditionalAttributes.cshtml
@@ -0,0 +1,15 @@
+@{
+ var ch = true;
+ var cls = "bar";
+ <a href="Foo" />
+ <p class="@cls" />
+ <p class="foo @cls" />
+ <p class="@cls foo" />
+ <input type="checkbox" checked="@ch" />
+ <input type="checkbox" checked="foo @ch" />
+ <p class="@if(cls != null) { @cls }" />
+ <a href="~/Foo" />
+ <script src="@Url.Content("~/Scripts/jquery-1.6.2.min.js")" type="text/javascript"></script>
+ <script src="@Url.Content("~/Scripts/modernizr-2.0.6-development-only.js")" type="text/javascript"></script>
+ <script src="http://ajax.aspnetcdn.com/ajax/jquery.ui/1.8.16/jquery-ui.min.js" type="text/javascript"></script>
+} \ No newline at end of file
diff --git a/test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Source/DesignTime.cshtml b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Source/DesignTime.cshtml
new file mode 100644
index 00000000..581b164d
--- /dev/null
+++ b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Source/DesignTime.cshtml
@@ -0,0 +1,21 @@
+<div>
+ @for(int i = 1; i <= 10; i++) {
+ <p>This is item #@i</p>
+ }
+</div>
+
+<p>
+@(Foo(Bar.Baz))
+@Foo(@<p>Bar @baz Biz</p>)
+</p>
+
+@section Footer {
+ <p>Foo</p>
+ @bar
+}
+
+@helper Foo() {
+ if(true) {
+ <p>Foo</p>
+ }
+} \ No newline at end of file
diff --git a/test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Source/EmptyCodeBlock.cshtml b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Source/EmptyCodeBlock.cshtml
new file mode 100644
index 00000000..0366199c
--- /dev/null
+++ b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Source/EmptyCodeBlock.cshtml
@@ -0,0 +1,3 @@
+This is markup
+
+@{} \ No newline at end of file
diff --git a/test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Source/EmptyExplicitExpression.cshtml b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Source/EmptyExplicitExpression.cshtml
new file mode 100644
index 00000000..6790c7eb
--- /dev/null
+++ b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Source/EmptyExplicitExpression.cshtml
@@ -0,0 +1,3 @@
+This is markup
+
+@() \ No newline at end of file
diff --git a/test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Source/EmptyImplicitExpression.cshtml b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Source/EmptyImplicitExpression.cshtml
new file mode 100644
index 00000000..021306da
--- /dev/null
+++ b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Source/EmptyImplicitExpression.cshtml
@@ -0,0 +1,3 @@
+This is markup
+
+@! \ No newline at end of file
diff --git a/test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Source/EmptyImplicitExpressionInCode.cshtml b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Source/EmptyImplicitExpressionInCode.cshtml
new file mode 100644
index 00000000..a1db8cd6
--- /dev/null
+++ b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Source/EmptyImplicitExpressionInCode.cshtml
@@ -0,0 +1,3 @@
+@{
+ @
+} \ No newline at end of file
diff --git a/test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Source/ExplicitExpression.cshtml b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Source/ExplicitExpression.cshtml
new file mode 100644
index 00000000..10730f11
--- /dev/null
+++ b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Source/ExplicitExpression.cshtml
@@ -0,0 +1 @@
+1 + 1 = @(1+1) \ No newline at end of file
diff --git a/test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Source/ExplicitExpressionAtEOF.cshtml b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Source/ExplicitExpressionAtEOF.cshtml
new file mode 100644
index 00000000..a0fdfc9a
--- /dev/null
+++ b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Source/ExplicitExpressionAtEOF.cshtml
@@ -0,0 +1,3 @@
+This is markup
+
+@( \ No newline at end of file
diff --git a/test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Source/ExpressionsInCode.cshtml b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Source/ExpressionsInCode.cshtml
new file mode 100644
index 00000000..a4d4caa0
--- /dev/null
+++ b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Source/ExpressionsInCode.cshtml
@@ -0,0 +1,16 @@
+@{
+ object foo = null;
+ string bar = "Foo";
+}
+
+@if(foo != null) {
+ @foo
+} else {
+ <p>Foo is Null!</p>
+}
+
+<p>
+@if(!String.IsNullOrEmpty(bar)) {
+ @(bar.Replace("F", "B"))
+}
+</p> \ No newline at end of file
diff --git a/test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Source/FunctionsBlock.cshtml b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Source/FunctionsBlock.cshtml
new file mode 100644
index 00000000..5d06b372
--- /dev/null
+++ b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Source/FunctionsBlock.cshtml
@@ -0,0 +1,12 @@
+@functions {
+
+}
+
+@functions {
+ Random _rand = new Random();
+ private int RandomInt() {
+ return _rand.Next();
+ }
+}
+
+Here's a random number: @RandomInt() \ No newline at end of file
diff --git a/test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Source/Helpers.cshtml b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Source/Helpers.cshtml
new file mode 100644
index 00000000..7ad443fe
--- /dev/null
+++ b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Source/Helpers.cshtml
@@ -0,0 +1,11 @@
+@helper Bold(string s) {
+ s = s.ToUpper();
+ <strong>@s</strong>
+}
+
+@helper Italic(string s) {
+ s = s.ToUpper();
+ <em>@s</em>
+}
+
+@Bold("Hello") \ No newline at end of file
diff --git a/test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Source/HelpersMissingCloseParen.cshtml b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Source/HelpersMissingCloseParen.cshtml
new file mode 100644
index 00000000..e787dea2
--- /dev/null
+++ b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Source/HelpersMissingCloseParen.cshtml
@@ -0,0 +1,7 @@
+@helper Bold(string s) {
+ s = s.ToUpper();
+ <strong>@s</strong>
+}
+
+@helper Italic(string s
+@Bold("Hello") \ No newline at end of file
diff --git a/test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Source/HelpersMissingName.cshtml b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Source/HelpersMissingName.cshtml
new file mode 100644
index 00000000..504cbee6
--- /dev/null
+++ b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Source/HelpersMissingName.cshtml
@@ -0,0 +1 @@
+@helper \ No newline at end of file
diff --git a/test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Source/HelpersMissingOpenBrace.cshtml b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Source/HelpersMissingOpenBrace.cshtml
new file mode 100644
index 00000000..b9702e38
--- /dev/null
+++ b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Source/HelpersMissingOpenBrace.cshtml
@@ -0,0 +1,7 @@
+@helper Bold(string s) {
+ s = s.ToUpper();
+ <strong>@s</strong>
+}
+
+@helper Italic(string s)
+@Italic(s) \ No newline at end of file
diff --git a/test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Source/HelpersMissingOpenParen.cshtml b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Source/HelpersMissingOpenParen.cshtml
new file mode 100644
index 00000000..f3825f7e
--- /dev/null
+++ b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Source/HelpersMissingOpenParen.cshtml
@@ -0,0 +1,7 @@
+@helper Bold(string s) {
+ s = s.ToUpper();
+ <strong>@s</strong>
+}
+
+@helper Italic
+@Bold("Hello") \ No newline at end of file
diff --git a/test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Source/HiddenSpansInCode.cshtml b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Source/HiddenSpansInCode.cshtml
new file mode 100644
index 00000000..a6addbe9
--- /dev/null
+++ b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Source/HiddenSpansInCode.cshtml
@@ -0,0 +1,3 @@
+@{
+ @@Da
+} \ No newline at end of file
diff --git a/test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Source/ImplicitExpression.cshtml b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Source/ImplicitExpression.cshtml
new file mode 100644
index 00000000..dcce7fa5
--- /dev/null
+++ b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Source/ImplicitExpression.cshtml
@@ -0,0 +1,3 @@
+@for(int i = 1; i <= 10; i++) {
+ <p>This is item #@i</p>
+} \ No newline at end of file
diff --git a/test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Source/ImplicitExpressionAtEOF.cshtml b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Source/ImplicitExpressionAtEOF.cshtml
new file mode 100644
index 00000000..365d20e0
--- /dev/null
+++ b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Source/ImplicitExpressionAtEOF.cshtml
@@ -0,0 +1,3 @@
+This is markup
+
+@ \ No newline at end of file
diff --git a/test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Source/Imports.cshtml b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Source/Imports.cshtml
new file mode 100644
index 00000000..332e26f0
--- /dev/null
+++ b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Source/Imports.cshtml
@@ -0,0 +1,6 @@
+@using System.IO
+@using Foo = System.Text.Encoding
+@using System
+
+<p>Path's full type name is @typeof(Path).FullName</p>
+<p>Foo's actual full type name is @typeof(Foo).FullName</p> \ No newline at end of file
diff --git a/test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Source/Inherits.cshtml b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Source/Inherits.cshtml
new file mode 100644
index 00000000..b449937d
--- /dev/null
+++ b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Source/Inherits.cshtml
@@ -0,0 +1,3 @@
+@foo()
+
+@inherits foo.bar<baz<biz>>.boz bar
diff --git a/test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Source/InlineBlocks.cshtml b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Source/InlineBlocks.cshtml
new file mode 100644
index 00000000..0a8b3d8f
--- /dev/null
+++ b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Source/InlineBlocks.cshtml
@@ -0,0 +1,3 @@
+@helper Link(string link) {
+ <a href="@if(link != null) { @link } else { <text>#</text> }" />
+} \ No newline at end of file
diff --git a/test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Source/Instrumented.cshtml b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Source/Instrumented.cshtml
new file mode 100644
index 00000000..4d9b03eb
--- /dev/null
+++ b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Source/Instrumented.cshtml
@@ -0,0 +1,42 @@
+@helper Strong(string s) {
+ <strong>@s</strong>
+}
+
+@{
+ int i = 1;
+ var foo = @<p>Bar</p>;
+ @:Hello, World
+ <p>Hello, World</p>
+}
+
+@while(i <= 10) {
+ <p>Hello from C#, #@(i)</p>
+ i += 1;
+}
+
+@if(i == 11) {
+ <p>We wrote 10 lines!</p>
+}
+
+@switch(i) {
+ case 11:
+ <p>No really, we wrote 10 lines!</p>
+ break;
+ default:
+ <p>Actually, we didn't...</p>
+ break;
+}
+
+@for(int j = 1; j <= 10; j += 2) {
+ <p>Hello again from C#, #@(j)</p>
+}
+
+@try {
+ <p>That time, we wrote 5 lines!</p>
+} catch(Exception ex) {
+ <p>Oh no! An error occurred: @(ex.Message)</p>
+}
+
+@lock(new object()) {
+ <p>This block is locked, for your security!</p>
+} \ No newline at end of file
diff --git a/test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Source/LayoutDirective.cshtml b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Source/LayoutDirective.cshtml
new file mode 100644
index 00000000..155c550b
--- /dev/null
+++ b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Source/LayoutDirective.cshtml
@@ -0,0 +1 @@
+@layout ~/Foo/Bar/Baz \ No newline at end of file
diff --git a/test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Source/MarkupInCodeBlock.cshtml b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Source/MarkupInCodeBlock.cshtml
new file mode 100644
index 00000000..712ef428
--- /dev/null
+++ b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Source/MarkupInCodeBlock.cshtml
@@ -0,0 +1,5 @@
+@{
+ for(int i = 1; i <= 10; i++) {
+ <p>Hello from C#, #@(i.ToString())</p>
+ }
+}
diff --git a/test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Source/NestedCodeBlocks.cshtml b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Source/NestedCodeBlocks.cshtml
new file mode 100644
index 00000000..070875f5
--- /dev/null
+++ b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Source/NestedCodeBlocks.cshtml
@@ -0,0 +1,4 @@
+@if(foo) {
+ @if(bar) {
+ }
+} \ No newline at end of file
diff --git a/test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Source/NestedHelpers.cshtml b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Source/NestedHelpers.cshtml
new file mode 100644
index 00000000..63158ff0
--- /dev/null
+++ b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Source/NestedHelpers.cshtml
@@ -0,0 +1,10 @@
+@helper Italic(string s) {
+ s = s.ToUpper();
+ @helper Bold(string s) {
+ s = s.ToUpper();
+ <strong>@s</strong>
+ }
+ <em>@Bold(s)</em>
+}
+
+@Italic("Hello") \ No newline at end of file
diff --git a/test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Source/NoLinePragmas.cshtml b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Source/NoLinePragmas.cshtml
new file mode 100644
index 00000000..36e96c46
--- /dev/null
+++ b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Source/NoLinePragmas.cshtml
@@ -0,0 +1,38 @@
+@{
+ int i = 1;
+}
+
+@while(i <= 10) {
+ <p>Hello from C#, #@(i)</p>
+ i += 1;
+}
+
+@if(i == 11) {
+ <p>We wrote 10 lines!</p>
+}
+
+@switch(i) {
+ case 11:
+ <p>No really, we wrote 10 lines!</p>
+ break;
+ default:
+ <p>Actually, we didn't...</p>
+ break;
+}
+
+@for(int j = 1; j <= 10; j += 2) {
+ <p>Hello again from C#, #@(j)</p>
+}
+
+@try {
+ <p>That time, we wrote 5 lines!</p>
+} catch(Exception ex) {
+ <p>Oh no! An error occurred: @(ex.Message)</p>
+}
+
+@* With has no equivalent in C# *@
+<p>i is now @i</p>
+
+@lock(new object()) {
+ <p>This block is locked, for your security!</p>
+} \ No newline at end of file
diff --git a/test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Source/ParserError.cshtml b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Source/ParserError.cshtml
new file mode 100644
index 00000000..ab30e853
--- /dev/null
+++ b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Source/ParserError.cshtml
@@ -0,0 +1,5 @@
+@{
+/*
+int i =10;
+int j =20;
+} \ No newline at end of file
diff --git a/test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Source/RazorComments.cshtml b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Source/RazorComments.cshtml
new file mode 100644
index 00000000..bd4820f9
--- /dev/null
+++ b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Source/RazorComments.cshtml
@@ -0,0 +1,15 @@
+@*This is not going to be rendered*@
+<p>This should @* not *@ be shown</p>
+
+@{
+ @* throw new Exception("Oh no!") *@
+ Exception foo = @* new Exception("Oh no!") *@ null;
+ if(foo != null) {
+ throw foo;
+ }
+}
+
+@{ var bar = "@* bar *@"; }
+<p>But this should show the comment syntax: @bar</p>
+
+@(a@**@b)
diff --git a/test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Source/ResolveUrl.cshtml b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Source/ResolveUrl.cshtml
new file mode 100644
index 00000000..fc8c77cf
--- /dev/null
+++ b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Source/ResolveUrl.cshtml
@@ -0,0 +1,20 @@
+<a href="~/Foo">Foo</a>
+<a href="~/Products/@product.id">@product.Name</a>
+<a href="~/Products/@product.id/Detail">Details</a>
+<a href="~/A+Really(Crazy),Url.Is:This/@product.id/Detail">Crazy Url!</a>
+
+@{
+ <text>
+ <a href="~/Foo">Foo</a>
+ <a href="~/Products/@product.id">@product.Name</a>
+ <a href="~/Products/@product.id/Detail">Details</a>
+ <a href="~/A+Really(Crazy),Url.Is:This/@product.id/Detail">Crazy Url!</a>
+ </text>
+}
+
+@section Foo {
+ <a href="~/Foo">Foo</a>
+ <a href="~/Products/@product.id">@product.Name</a>
+ <a href="~/Products/@product.id/Detail">Details</a>
+ <a href="~/A+Really(Crazy),Url.Is:This/@product.id/Detail">Crazy Url!</a>
+} \ No newline at end of file
diff --git a/test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Source/Sections.cshtml b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Source/Sections.cshtml
new file mode 100644
index 00000000..1d8cbefe
--- /dev/null
+++ b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Source/Sections.cshtml
@@ -0,0 +1,13 @@
+@{
+ Layout = "_SectionTestLayout.cshtml"
+}
+
+<div>This is in the Body>
+
+@section Section2 {
+ <div>This is in Section 2</div>
+}
+
+@section Section1 {
+ <div>This is in Section 1</div>
+} \ No newline at end of file
diff --git a/test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Source/Templates.cshtml b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Source/Templates.cshtml
new file mode 100644
index 00000000..c9523f0b
--- /dev/null
+++ b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Source/Templates.cshtml
@@ -0,0 +1,35 @@
+@functions {
+ public HelperResult Repeat(int times, Func<int, object> template) {
+ return new HelperResult((writer) => {
+ for(int i = 0; i < times; i++) {
+ ((HelperResult)template(i)).WriteTo(writer);
+ }
+ });
+ }
+}
+
+@{
+ Func<dynamic, object> foo = @<text>This works @item!</text>;
+ @foo("")
+}
+
+<ul>
+@(Repeat(10, @<li>Item #@item</li>))
+</ul>
+
+<p>
+@Repeat(10,
+ @: This is line#@item of markup<br/>
+)
+</p>
+
+<ul>
+ @Repeat(10, @<li>
+ Item #@item
+ @{var parent = item;}
+ <ul>
+ <li>Child Items... ?</li>
+ @*Repeat(10, @<li>Item #@(parent).@item</li>)*@
+ </ul>
+ </li>)
+</ul> \ No newline at end of file
diff --git a/test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Source/UnfinishedExpressionInCode.cshtml b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Source/UnfinishedExpressionInCode.cshtml
new file mode 100644
index 00000000..bf1d7964
--- /dev/null
+++ b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/CS/Source/UnfinishedExpressionInCode.cshtml
@@ -0,0 +1,3 @@
+@{
+@DateTime.
+} \ No newline at end of file
diff --git a/test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Output/Blocks.vb b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Output/Blocks.vb
new file mode 100644
index 00000000..d0dc84d5
--- /dev/null
+++ b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Output/Blocks.vb
@@ -0,0 +1,255 @@
+'------------------------------------------------------------------------------
+' <auto-generated>
+' This code was generated by a tool.
+' Runtime Version:N.N.NNNNN.N
+'
+' Changes to this file may cause incorrect behavior and will be lost if
+' the code is regenerated.
+' </auto-generated>
+'------------------------------------------------------------------------------
+
+Option Strict Off
+Option Explicit On
+
+Imports System
+
+Namespace TestOutput
+Public Class Blocks
+Public Sub New()
+MyBase.New
+End Sub
+Public Overrides Sub Execute()
+
+#ExternalSource("Blocks.vbhtml",1)
+
+ Dim i as Integer = 1
+
+
+#End ExternalSource
+WriteLiteral(""&Global.Microsoft.VisualBasic.ChrW(13)&Global.Microsoft.VisualBasic.ChrW(10)&Global.Microsoft.VisualBasic.ChrW(13)&Global.Microsoft.VisualBasic.ChrW(10))
+
+
+#ExternalSource("Blocks.vbhtml",5)
+ While i <= 10
+
+
+#End ExternalSource
+WriteLiteral(" ")
+
+WriteLiteral("<p>Hello from VB.Net, #")
+
+
+#ExternalSource("Blocks.vbhtml",6)
+ Write(i)
+
+
+#End ExternalSource
+WriteLiteral("</p>"&Global.Microsoft.VisualBasic.ChrW(13)&Global.Microsoft.VisualBasic.ChrW(10))
+
+
+#ExternalSource("Blocks.vbhtml",7)
+ i += 1
+End While
+
+
+#End ExternalSource
+WriteLiteral(""&Global.Microsoft.VisualBasic.ChrW(13)&Global.Microsoft.VisualBasic.ChrW(10))
+
+
+#ExternalSource("Blocks.vbhtml",10)
+ If i = 11 Then
+
+
+#End ExternalSource
+WriteLiteral(" ")
+
+WriteLiteral("<p>We wrote 10 lines!</p>"&Global.Microsoft.VisualBasic.ChrW(13)&Global.Microsoft.VisualBasic.ChrW(10))
+
+
+#ExternalSource("Blocks.vbhtml",12)
+End If
+
+
+#End ExternalSource
+WriteLiteral(""&Global.Microsoft.VisualBasic.ChrW(13)&Global.Microsoft.VisualBasic.ChrW(10))
+
+
+#ExternalSource("Blocks.vbhtml",14)
+ Do
+
+
+#End ExternalSource
+WriteLiteral(" ")
+
+WriteLiteral("<p>Hello again: ")
+
+
+#ExternalSource("Blocks.vbhtml",15)
+ Write(i)
+
+
+#End ExternalSource
+WriteLiteral("</p>"&Global.Microsoft.VisualBasic.ChrW(13)&Global.Microsoft.VisualBasic.ChrW(10))
+
+
+#ExternalSource("Blocks.vbhtml",16)
+ i -= 1
+Loop While i > 0
+
+
+#End ExternalSource
+WriteLiteral(""&Global.Microsoft.VisualBasic.ChrW(13)&Global.Microsoft.VisualBasic.ChrW(10))
+
+
+#ExternalSource("Blocks.vbhtml",19)
+ Select Case i
+ Case 11
+
+
+#End ExternalSource
+WriteLiteral(" ")
+
+WriteLiteral("<p>No really, we wrote 10 lines!</p>"&Global.Microsoft.VisualBasic.ChrW(13)&Global.Microsoft.VisualBasic.ChrW(10))
+
+
+#ExternalSource("Blocks.vbhtml",22)
+ Case Else
+
+
+#End ExternalSource
+WriteLiteral(" ")
+
+WriteLiteral("<p>We wrote a bunch more lines too!</p>"&Global.Microsoft.VisualBasic.ChrW(13)&Global.Microsoft.VisualBasic.ChrW(10))
+
+
+#ExternalSource("Blocks.vbhtml",24)
+End Select
+
+
+#End ExternalSource
+WriteLiteral(""&Global.Microsoft.VisualBasic.ChrW(13)&Global.Microsoft.VisualBasic.ChrW(10))
+
+
+#ExternalSource("Blocks.vbhtml",26)
+ For j as Integer = 1 to 10 Step 2
+
+
+#End ExternalSource
+WriteLiteral(" ")
+
+WriteLiteral("<p>Hello again from VB.Net, #")
+
+
+#ExternalSource("Blocks.vbhtml",27)
+ Write(j)
+
+
+#End ExternalSource
+WriteLiteral("</p>"&Global.Microsoft.VisualBasic.ChrW(13)&Global.Microsoft.VisualBasic.ChrW(10))
+
+
+#ExternalSource("Blocks.vbhtml",28)
+Next j
+
+
+#End ExternalSource
+WriteLiteral(""&Global.Microsoft.VisualBasic.ChrW(13)&Global.Microsoft.VisualBasic.ChrW(10))
+
+
+#ExternalSource("Blocks.vbhtml",30)
+ Try
+
+
+#End ExternalSource
+WriteLiteral(" ")
+
+WriteLiteral("<p>That time, we wrote 5 lines!</p>"&Global.Microsoft.VisualBasic.ChrW(13)&Global.Microsoft.VisualBasic.ChrW(10))
+
+
+#ExternalSource("Blocks.vbhtml",32)
+Catch ex as Exception
+
+
+#End ExternalSource
+WriteLiteral(" ")
+
+WriteLiteral("<p>Oh no! An error occurred: ")
+
+
+#ExternalSource("Blocks.vbhtml",33)
+ Write(ex.Message)
+
+
+#End ExternalSource
+WriteLiteral("</p>"&Global.Microsoft.VisualBasic.ChrW(13)&Global.Microsoft.VisualBasic.ChrW(10))
+
+
+#ExternalSource("Blocks.vbhtml",34)
+End Try
+
+
+#End ExternalSource
+WriteLiteral(""&Global.Microsoft.VisualBasic.ChrW(13)&Global.Microsoft.VisualBasic.ChrW(10))
+
+
+#ExternalSource("Blocks.vbhtml",36)
+ With i
+
+
+#End ExternalSource
+WriteLiteral(" ")
+
+WriteLiteral("<p>i is now ")
+
+
+#ExternalSource("Blocks.vbhtml",37)
+ Write(.ToString())
+
+
+#End ExternalSource
+WriteLiteral("</p>"&Global.Microsoft.VisualBasic.ChrW(13)&Global.Microsoft.VisualBasic.ChrW(10))
+
+
+#ExternalSource("Blocks.vbhtml",38)
+End With
+
+
+#End ExternalSource
+WriteLiteral(""&Global.Microsoft.VisualBasic.ChrW(13)&Global.Microsoft.VisualBasic.ChrW(10))
+
+
+#ExternalSource("Blocks.vbhtml",40)
+ SyncLock New Object()
+
+
+#End ExternalSource
+WriteLiteral(" ")
+
+WriteLiteral("<p>This block is locked, for your security!</p>"&Global.Microsoft.VisualBasic.ChrW(13)&Global.Microsoft.VisualBasic.ChrW(10))
+
+
+#ExternalSource("Blocks.vbhtml",42)
+End SyncLock
+
+
+#End ExternalSource
+WriteLiteral(""&Global.Microsoft.VisualBasic.ChrW(13)&Global.Microsoft.VisualBasic.ChrW(10))
+
+
+#ExternalSource("Blocks.vbhtml",44)
+ Using New System.IO.MemoryStream()
+
+
+#End ExternalSource
+WriteLiteral(" ")
+
+WriteLiteral("<p>Some random memory stream will be disposed after rendering this block</p>"&Global.Microsoft.VisualBasic.ChrW(13)&Global.Microsoft.VisualBasic.ChrW(10))
+
+
+#ExternalSource("Blocks.vbhtml",46)
+End Using
+
+#End ExternalSource
+End Sub
+End Class
+End Namespace
diff --git a/test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Output/CodeBlock.vb b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Output/CodeBlock.vb
new file mode 100644
index 00000000..b77904b3
--- /dev/null
+++ b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Output/CodeBlock.vb
@@ -0,0 +1,31 @@
+'------------------------------------------------------------------------------
+' <auto-generated>
+' This code was generated by a tool.
+' Runtime Version:N.N.NNNNN.N
+'
+' Changes to this file may cause incorrect behavior and will be lost if
+' the code is regenerated.
+' </auto-generated>
+'------------------------------------------------------------------------------
+
+Option Strict Off
+Option Explicit On
+
+Imports System
+
+Namespace TestOutput
+Public Class CodeBlock
+Public Sub New()
+MyBase.New
+End Sub
+Public Overrides Sub Execute()
+
+#ExternalSource("CodeBlock.vbhtml",1)
+
+ Test()
+
+
+#End ExternalSource
+End Sub
+End Class
+End Namespace
diff --git a/test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Output/CodeBlockAtEOF.vb b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Output/CodeBlockAtEOF.vb
new file mode 100644
index 00000000..4550554d
--- /dev/null
+++ b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Output/CodeBlockAtEOF.vb
@@ -0,0 +1,29 @@
+'------------------------------------------------------------------------------
+' <auto-generated>
+' This code was generated by a tool.
+' Runtime Version:N.N.NNNNN.N
+'
+' Changes to this file may cause incorrect behavior and will be lost if
+' the code is regenerated.
+' </auto-generated>
+'------------------------------------------------------------------------------
+
+Option Strict Off
+Option Explicit On
+
+Imports System
+
+Namespace TestOutput
+Public Class CodeBlockAtEOF
+Public Sub New()
+MyBase.New
+End Sub
+Public Overrides Sub Execute()
+
+#ExternalSource("CodeBlockAtEOF.vbhtml",1)
+
+
+#End ExternalSource
+End Sub
+End Class
+End Namespace
diff --git a/test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Output/Comments.vb b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Output/Comments.vb
new file mode 100644
index 00000000..b6cabd32
--- /dev/null
+++ b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Output/Comments.vb
@@ -0,0 +1,51 @@
+'------------------------------------------------------------------------------
+' <auto-generated>
+' This code was generated by a tool.
+' Runtime Version:N.N.NNNNN.N
+'
+' Changes to this file may cause incorrect behavior and will be lost if
+' the code is regenerated.
+' </auto-generated>
+'------------------------------------------------------------------------------
+
+Option Strict Off
+Option Explicit On
+
+Imports System
+
+Namespace TestOutput
+Public Class Comments
+Public Overrides Sub Execute()
+
+
+#ExternalSource("Comments.vbhtml",1)
+ 'This is not going to be rendered
+
+
+#End ExternalSource
+WriteLiteral("<p>This is going to be rendered</p>"&Global.Microsoft.VisualBasic.ChrW(13)&Global.Microsoft.VisualBasic.ChrW(10))
+
+
+
+#ExternalSource("Comments.vbhtml",3)
+ REM Neither is this
+
+
+#End ExternalSource
+
+
+#ExternalSource("Comments.vbhtml",4)
+ rem nor this
+
+
+#End ExternalSource
+
+
+#ExternalSource("Comments.vbhtml",5)
+ rEm nor this
+
+#End ExternalSource
+
+End Sub
+End Class
+End Namespace
diff --git a/test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Output/ConditionalAttributes.vb b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Output/ConditionalAttributes.vb
new file mode 100644
index 00000000..6abcc142
--- /dev/null
+++ b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Output/ConditionalAttributes.vb
@@ -0,0 +1,146 @@
+'------------------------------------------------------------------------------
+' <auto-generated>
+' This code was generated by a tool.
+' Runtime Version:N.N.NNNNN.N
+'
+' Changes to this file may cause incorrect behavior and will be lost if
+' the code is regenerated.
+' </auto-generated>
+'------------------------------------------------------------------------------
+
+Option Strict Off
+Option Explicit On
+
+Imports System
+
+Namespace TestOutput
+Public Class ConditionalAttributes
+Public Sub New()
+MyBase.New
+End Sub
+Public Overrides Sub Execute()
+
+#ExternalSource("ConditionalAttributes.vbhtml",1)
+
+ Dim ch = True
+ Dim cls = "bar"
+
+
+#End ExternalSource
+WriteLiteral(" ")
+
+WriteLiteral("<a")
+
+WriteLiteral(" href=""Foo""")
+
+WriteLiteral(" />"&Global.Microsoft.VisualBasic.ChrW(13)&Global.Microsoft.VisualBasic.ChrW(10))
+
+WriteLiteral(" ")
+
+WriteLiteral("<p")
+
+WriteAttribute("class", Tuple.Create(" class=""", 77), Tuple.Create("""", 89) _
+, Tuple.Create(Tuple.Create("", 85), Tuple.Create(Of System.Object, System.Int32)(cls _
+, 85), False) _
+)
+
+WriteLiteral(" />"&Global.Microsoft.VisualBasic.ChrW(13)&Global.Microsoft.VisualBasic.ChrW(10))
+
+WriteLiteral(" ")
+
+WriteLiteral("<p")
+
+WriteAttribute("class", Tuple.Create(" class=""", 102), Tuple.Create("""", 118) _
+, Tuple.Create(Tuple.Create("", 110), Tuple.Create("foo", 110), True) _
+, Tuple.Create(Tuple.Create(" ", 113), Tuple.Create(Of System.Object, System.Int32)(cls _
+, 114), False) _
+)
+
+WriteLiteral(" />"&Global.Microsoft.VisualBasic.ChrW(13)&Global.Microsoft.VisualBasic.ChrW(10))
+
+WriteLiteral(" ")
+
+WriteLiteral("<p")
+
+WriteAttribute("class", Tuple.Create(" class=""", 131), Tuple.Create("""", 147) _
+, Tuple.Create(Tuple.Create("", 139), Tuple.Create(Of System.Object, System.Int32)(cls _
+, 139), False) _
+, Tuple.Create(Tuple.Create(" ", 143), Tuple.Create("foo", 144), True) _
+)
+
+WriteLiteral(" />"&Global.Microsoft.VisualBasic.ChrW(13)&Global.Microsoft.VisualBasic.ChrW(10))
+
+WriteLiteral(" ")
+
+WriteLiteral("<input")
+
+WriteLiteral(" type=""checkbox""")
+
+WriteAttribute("checked", Tuple.Create(" checked=""", 180), Tuple.Create("""", 193) _
+, Tuple.Create(Tuple.Create("", 190), Tuple.Create(Of System.Object, System.Int32)(ch _
+, 190), False) _
+)
+
+WriteLiteral(" />"&Global.Microsoft.VisualBasic.ChrW(13)&Global.Microsoft.VisualBasic.ChrW(10))
+
+WriteLiteral(" ")
+
+WriteLiteral("<input")
+
+WriteLiteral(" type=""checkbox""")
+
+WriteAttribute("checked", Tuple.Create(" checked=""", 226), Tuple.Create("""", 243) _
+, Tuple.Create(Tuple.Create("", 236), Tuple.Create("foo", 236), True) _
+, Tuple.Create(Tuple.Create(" ", 239), Tuple.Create(Of System.Object, System.Int32)(ch _
+, 240), False) _
+)
+
+WriteLiteral(" />"&Global.Microsoft.VisualBasic.ChrW(13)&Global.Microsoft.VisualBasic.ChrW(10))
+
+WriteLiteral(" ")
+
+WriteLiteral("<p")
+
+WriteAttribute("class", Tuple.Create(" class=""", 256), Tuple.Create("""", 302) _
+, Tuple.Create(Tuple.Create("", 264), Tuple.Create(Of System.Object, System.Int32)(New Template(Sub (__razor_attribute_value_writer)
+
+
+#ExternalSource("ConditionalAttributes.vbhtml",10)
+ If cls IsNot Nothing Then
+
+#End ExternalSource
+
+#ExternalSource("ConditionalAttributes.vbhtml",10)
+ WriteTo(__razor_attribute_value_writer, cls)
+
+
+#End ExternalSource
+
+#ExternalSource("ConditionalAttributes.vbhtml",10)
+ End If
+
+#End ExternalSource
+End Sub), 264), False) _
+)
+
+WriteLiteral(" />"&Global.Microsoft.VisualBasic.ChrW(13)&Global.Microsoft.VisualBasic.ChrW(10))
+
+WriteLiteral(" ")
+
+WriteLiteral("<a")
+
+WriteAttribute("href", Tuple.Create(" href=""", 315), Tuple.Create("""", 327) _
+, Tuple.Create(Tuple.Create("", 322), Tuple.Create(Of System.Object, System.Int32)(Href("~/Foo") _
+, 322), False) _
+)
+
+WriteLiteral(" />"&Global.Microsoft.VisualBasic.ChrW(13)&Global.Microsoft.VisualBasic.ChrW(10))
+
+
+#ExternalSource("ConditionalAttributes.vbhtml",12)
+
+
+#End ExternalSource
+End Sub
+End Class
+End Namespace
diff --git a/test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Output/DesignTime.vb b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Output/DesignTime.vb
new file mode 100644
index 00000000..8c2f832c
--- /dev/null
+++ b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Output/DesignTime.vb
@@ -0,0 +1,99 @@
+'------------------------------------------------------------------------------
+' <auto-generated>
+' This code was generated by a tool.
+' Runtime Version:N.N.NNNNN.N
+'
+' Changes to this file may cause incorrect behavior and will be lost if
+' the code is regenerated.
+' </auto-generated>
+'------------------------------------------------------------------------------
+
+Option Strict Off
+Option Explicit On
+
+Imports System
+
+Namespace TestOutput
+Public Class DesignTime
+Private Shared __o As Object
+
+#ExternalSource("DesignTime.vbhtml", 9)
+Public Shared Function Foo() As Template
+
+#End ExternalSource
+Return New Template(Sub (__razor_helper_writer)
+
+#ExternalSource("DesignTime.vbhtml", 10)
+
+ If True Then
+
+#End ExternalSource
+
+#ExternalSource("DesignTime.vbhtml", 11)
+
+ End If
+
+#End ExternalSource
+End Sub)
+End Function
+
+Public Sub New()
+MyBase.New
+End Sub
+Public Overrides Sub Execute()
+
+#ExternalSource("DesignTime.vbhtml",1)
+ For i = 1 to 10
+
+
+#End ExternalSource
+
+#ExternalSource("DesignTime.vbhtml",2)
+ __o = i
+
+
+#End ExternalSource
+
+#ExternalSource("DesignTime.vbhtml",3)
+
+ Next
+
+
+#End ExternalSource
+
+#ExternalSource("DesignTime.vbhtml",4)
+__o = Foo(Bar.Baz)
+
+
+#End ExternalSource
+
+#ExternalSource("DesignTime.vbhtml",5)
+__o = Foo(Function (item) New Template(Sub (__razor_template_writer)
+
+
+#End ExternalSource
+
+#ExternalSource("DesignTime.vbhtml",6)
+ __o = baz
+
+
+#End ExternalSource
+
+#ExternalSource("DesignTime.vbhtml",7)
+ End Sub))
+
+
+#End ExternalSource
+DefineSection("Footer", Sub ()
+
+
+#ExternalSource("DesignTime.vbhtml",8)
+__o = bar
+
+
+#End ExternalSource
+End Sub)
+
+End Sub
+End Class
+End Namespace
diff --git a/test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Output/EmptyExplicitExpression.vb b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Output/EmptyExplicitExpression.vb
new file mode 100644
index 00000000..ce8fc4c1
--- /dev/null
+++ b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Output/EmptyExplicitExpression.vb
@@ -0,0 +1,31 @@
+'------------------------------------------------------------------------------
+' <auto-generated>
+' This code was generated by a tool.
+' Runtime Version:N.N.NNNNN.N
+'
+' Changes to this file may cause incorrect behavior and will be lost if
+' the code is regenerated.
+' </auto-generated>
+'------------------------------------------------------------------------------
+
+Option Strict Off
+Option Explicit On
+
+Imports System
+
+Namespace TestOutput
+Public Class EmptyExplicitExpression
+Private Shared __o As Object
+Public Sub New()
+MyBase.New
+End Sub
+Public Overrides Sub Execute()
+
+#ExternalSource("EmptyExplicitExpression.vbhtml",1)
+__o =
+
+
+#End ExternalSource
+End Sub
+End Class
+End Namespace
diff --git a/test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Output/EmptyImplicitExpression.vb b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Output/EmptyImplicitExpression.vb
new file mode 100644
index 00000000..786c049d
--- /dev/null
+++ b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Output/EmptyImplicitExpression.vb
@@ -0,0 +1,31 @@
+'------------------------------------------------------------------------------
+' <auto-generated>
+' This code was generated by a tool.
+' Runtime Version:N.N.NNNNN.N
+'
+' Changes to this file may cause incorrect behavior and will be lost if
+' the code is regenerated.
+' </auto-generated>
+'------------------------------------------------------------------------------
+
+Option Strict Off
+Option Explicit On
+
+Imports System
+
+Namespace TestOutput
+Public Class EmptyImplicitExpression
+Private Shared __o As Object
+Public Sub New()
+MyBase.New
+End Sub
+Public Overrides Sub Execute()
+
+#ExternalSource("EmptyImplicitExpression.vbhtml",1)
+__o =
+
+
+#End ExternalSource
+End Sub
+End Class
+End Namespace
diff --git a/test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Output/EmptyImplicitExpressionInCode.vb b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Output/EmptyImplicitExpressionInCode.vb
new file mode 100644
index 00000000..9e4b2b1c
--- /dev/null
+++ b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Output/EmptyImplicitExpressionInCode.vb
@@ -0,0 +1,43 @@
+'------------------------------------------------------------------------------
+' <auto-generated>
+' This code was generated by a tool.
+' Runtime Version:N.N.NNNNN.N
+'
+' Changes to this file may cause incorrect behavior and will be lost if
+' the code is regenerated.
+' </auto-generated>
+'------------------------------------------------------------------------------
+
+Option Strict Off
+Option Explicit On
+
+Imports System
+
+Namespace TestOutput
+Public Class EmptyImplicitExpressionInCode
+Private Shared __o As Object
+Public Sub New()
+MyBase.New
+End Sub
+Public Overrides Sub Execute()
+
+#ExternalSource("EmptyImplicitExpressionInCode.vbhtml",1)
+
+
+
+#End ExternalSource
+
+#ExternalSource("EmptyImplicitExpressionInCode.vbhtml",2)
+__o =
+
+
+#End ExternalSource
+
+#ExternalSource("EmptyImplicitExpressionInCode.vbhtml",3)
+
+
+
+#End ExternalSource
+End Sub
+End Class
+End Namespace
diff --git a/test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Output/ExplicitExpression.vb b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Output/ExplicitExpression.vb
new file mode 100644
index 00000000..d2e7a9fd
--- /dev/null
+++ b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Output/ExplicitExpression.vb
@@ -0,0 +1,30 @@
+'------------------------------------------------------------------------------
+' <auto-generated>
+' This code was generated by a tool.
+' Runtime Version:N.N.NNNNN.N
+'
+' Changes to this file may cause incorrect behavior and will be lost if
+' the code is regenerated.
+' </auto-generated>
+'------------------------------------------------------------------------------
+
+Option Strict Off
+Option Explicit On
+
+Imports System
+
+Namespace TestOutput
+Public Class ExplicitExpression
+Public Sub New()
+MyBase.New
+End Sub
+Public Overrides Sub Execute()
+
+#ExternalSource("ExplicitExpression.vbhtml",1)
+Write(Foo(Bar.Baz))
+
+
+#End ExternalSource
+End Sub
+End Class
+End Namespace
diff --git a/test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Output/ExplicitExpressionAtEOF.vb b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Output/ExplicitExpressionAtEOF.vb
new file mode 100644
index 00000000..7fb5bd77
--- /dev/null
+++ b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Output/ExplicitExpressionAtEOF.vb
@@ -0,0 +1,31 @@
+'------------------------------------------------------------------------------
+' <auto-generated>
+' This code was generated by a tool.
+' Runtime Version:N.N.NNNNN.N
+'
+' Changes to this file may cause incorrect behavior and will be lost if
+' the code is regenerated.
+' </auto-generated>
+'------------------------------------------------------------------------------
+
+Option Strict Off
+Option Explicit On
+
+Imports System
+
+Namespace TestOutput
+Public Class ExplicitExpressionAtEOF
+Private Shared __o As Object
+Public Sub New()
+MyBase.New
+End Sub
+Public Overrides Sub Execute()
+
+#ExternalSource("ExplicitExpressionAtEOF.vbhtml",1)
+__o =
+
+
+#End ExternalSource
+End Sub
+End Class
+End Namespace
diff --git a/test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Output/ExpressionsInCode.vb b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Output/ExpressionsInCode.vb
new file mode 100644
index 00000000..bbbd62b2
--- /dev/null
+++ b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Output/ExpressionsInCode.vb
@@ -0,0 +1,86 @@
+'------------------------------------------------------------------------------
+' <auto-generated>
+' This code was generated by a tool.
+' Runtime Version:N.N.NNNNN.N
+'
+' Changes to this file may cause incorrect behavior and will be lost if
+' the code is regenerated.
+' </auto-generated>
+'------------------------------------------------------------------------------
+
+Option Strict Off
+Option Explicit On
+
+Imports System
+
+Namespace TestOutput
+Public Class ExpressionsInCode
+Public Sub New()
+MyBase.New
+End Sub
+Public Overrides Sub Execute()
+
+#ExternalSource("ExpressionsInCode.vbhtml",1)
+
+ Dim foo As Object = Nothing
+ Dim bar as String = "Foo"
+
+
+#End ExternalSource
+WriteLiteral(""&Global.Microsoft.VisualBasic.ChrW(13)&Global.Microsoft.VisualBasic.ChrW(10)&Global.Microsoft.VisualBasic.ChrW(13)&Global.Microsoft.VisualBasic.ChrW(10))
+
+
+#ExternalSource("ExpressionsInCode.vbhtml",6)
+ If foo IsNot Nothing Then
+
+
+#End ExternalSource
+
+#ExternalSource("ExpressionsInCode.vbhtml",7)
+Write(foo)
+
+
+#End ExternalSource
+
+#ExternalSource("ExpressionsInCode.vbhtml",7)
+
+Else
+
+
+#End ExternalSource
+WriteLiteral(" ")
+
+WriteLiteral("<p>Foo is Null!</p>"&Global.Microsoft.VisualBasic.ChrW(13)&Global.Microsoft.VisualBasic.ChrW(10))
+
+
+#ExternalSource("ExpressionsInCode.vbhtml",10)
+End If
+
+
+#End ExternalSource
+WriteLiteral(""&Global.Microsoft.VisualBasic.ChrW(13)&Global.Microsoft.VisualBasic.ChrW(10)&"<p>"&Global.Microsoft.VisualBasic.ChrW(13)&Global.Microsoft.VisualBasic.ChrW(10))
+
+
+#ExternalSource("ExpressionsInCode.vbhtml",13)
+ If Not String.IsNullOrEmpty(bar) Then
+
+
+#End ExternalSource
+
+#ExternalSource("ExpressionsInCode.vbhtml",14)
+Write(bar.Replace("F", "B"))
+
+
+#End ExternalSource
+
+#ExternalSource("ExpressionsInCode.vbhtml",14)
+
+End If
+
+
+#End ExternalSource
+WriteLiteral("</p>")
+
+End Sub
+End Class
+End Namespace
diff --git a/test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Output/FunctionsBlock.DesignTime.vb b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Output/FunctionsBlock.DesignTime.vb
new file mode 100644
index 00000000..3ade6739
--- /dev/null
+++ b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Output/FunctionsBlock.DesignTime.vb
@@ -0,0 +1,47 @@
+'------------------------------------------------------------------------------
+' <auto-generated>
+' This code was generated by a tool.
+' Runtime Version:N.N.NNNNN.N
+'
+' Changes to this file may cause incorrect behavior and will be lost if
+' the code is regenerated.
+' </auto-generated>
+'------------------------------------------------------------------------------
+
+Option Strict Off
+Option Explicit On
+
+Imports System
+
+Namespace TestOutput
+Public Class FunctionsBlock
+Private Shared __o As Object
+
+#ExternalSource("FunctionsBlock.vbhtml",1)
+
+
+
+#End ExternalSource
+
+#ExternalSource("FunctionsBlock.vbhtml",2)
+
+ Private _rand as New Random()
+ Private Function RandomInt() as Integer
+ Return _rand.Next()
+ End Function
+
+#End ExternalSource
+
+Public Sub New()
+MyBase.New
+End Sub
+Public Overrides Sub Execute()
+
+#ExternalSource("FunctionsBlock.vbhtml",3)
+ __o = RandomInt()
+
+
+#End ExternalSource
+End Sub
+End Class
+End Namespace
diff --git a/test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Output/FunctionsBlock.vb b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Output/FunctionsBlock.vb
new file mode 100644
index 00000000..b14fedc3
--- /dev/null
+++ b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Output/FunctionsBlock.vb
@@ -0,0 +1,50 @@
+'------------------------------------------------------------------------------
+' <auto-generated>
+' This code was generated by a tool.
+' Runtime Version:N.N.NNNNN.N
+'
+' Changes to this file may cause incorrect behavior and will be lost if
+' the code is regenerated.
+' </auto-generated>
+'------------------------------------------------------------------------------
+
+Option Strict Off
+Option Explicit On
+
+Imports System
+
+Namespace TestOutput
+Public Class FunctionsBlock
+
+#ExternalSource("FunctionsBlock.vbhtml",1)
+
+
+
+#End ExternalSource
+
+#ExternalSource("FunctionsBlock.vbhtml",5)
+
+ Private _rand as New Random()
+ Private Function RandomInt() as Integer
+ Return _rand.Next()
+ End Function
+
+#End ExternalSource
+
+Public Sub New()
+MyBase.New
+End Sub
+Public Overrides Sub Execute()
+WriteLiteral(""&Global.Microsoft.VisualBasic.ChrW(13)&Global.Microsoft.VisualBasic.ChrW(10)&Global.Microsoft.VisualBasic.ChrW(13)&Global.Microsoft.VisualBasic.ChrW(10))
+
+WriteLiteral(""&Global.Microsoft.VisualBasic.ChrW(13)&Global.Microsoft.VisualBasic.ChrW(10)&Global.Microsoft.VisualBasic.ChrW(13)&Global.Microsoft.VisualBasic.ChrW(10)&"Here's a random number: ")
+
+
+#ExternalSource("FunctionsBlock.vbhtml",12)
+ Write(RandomInt())
+
+
+#End ExternalSource
+End Sub
+End Class
+End Namespace
diff --git a/test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Output/Helpers.Instance.vb b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Output/Helpers.Instance.vb
new file mode 100644
index 00000000..5669e5e7
--- /dev/null
+++ b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Output/Helpers.Instance.vb
@@ -0,0 +1,87 @@
+'------------------------------------------------------------------------------
+' <auto-generated>
+' This code was generated by a tool.
+' Runtime Version:N.N.NNNNN.N
+'
+' Changes to this file may cause incorrect behavior and will be lost if
+' the code is regenerated.
+' </auto-generated>
+'------------------------------------------------------------------------------
+
+Option Strict Off
+Option Explicit On
+
+Imports System
+
+Namespace TestOutput
+Public Class Helpers
+
+#ExternalSource("Helpers.vbhtml", 1)
+Public Function Bold(s as String) As Template
+
+#End ExternalSource
+Return New Template(Sub (__razor_helper_writer)
+
+#ExternalSource("Helpers.vbhtml", 1)
+
+ s = s.ToUpper()
+
+#End ExternalSource
+WriteLiteralTo(__razor_helper_writer, " ")
+WriteLiteralTo(__razor_helper_writer, "<strong>")
+
+#ExternalSource("Helpers.vbhtml", 3)
+WriteTo(__razor_helper_writer, s)
+
+#End ExternalSource
+WriteLiteralTo(__razor_helper_writer, "</strong>"&Global.Microsoft.VisualBasic.ChrW(13)&Global.Microsoft.VisualBasic.ChrW(10))
+
+#ExternalSource("Helpers.vbhtml", 4)
+
+#End ExternalSource
+End Sub)
+End Function
+
+#ExternalSource("Helpers.vbhtml", 6)
+Public Function Italic(s as String) As Template
+
+#End ExternalSource
+Return New Template(Sub (__razor_helper_writer)
+
+#ExternalSource("Helpers.vbhtml", 6)
+
+ s = s.ToUpper()
+
+#End ExternalSource
+WriteLiteralTo(__razor_helper_writer, " ")
+WriteLiteralTo(__razor_helper_writer, "<em>")
+
+#ExternalSource("Helpers.vbhtml", 8)
+WriteTo(__razor_helper_writer, s)
+
+#End ExternalSource
+WriteLiteralTo(__razor_helper_writer, "</em>"&Global.Microsoft.VisualBasic.ChrW(13)&Global.Microsoft.VisualBasic.ChrW(10))
+
+#ExternalSource("Helpers.vbhtml", 9)
+
+#End ExternalSource
+End Sub)
+End Function
+
+Public Sub New()
+MyBase.New
+End Sub
+Public Overrides Sub Execute()
+WriteLiteral(""&Global.Microsoft.VisualBasic.ChrW(13)&Global.Microsoft.VisualBasic.ChrW(10)&Global.Microsoft.VisualBasic.ChrW(13)&Global.Microsoft.VisualBasic.ChrW(10))
+
+WriteLiteral(""&Global.Microsoft.VisualBasic.ChrW(13)&Global.Microsoft.VisualBasic.ChrW(10)&Global.Microsoft.VisualBasic.ChrW(13)&Global.Microsoft.VisualBasic.ChrW(10))
+
+
+#ExternalSource("Helpers.vbhtml",11)
+Write(Bold("Hello"))
+
+
+#End ExternalSource
+End Sub
+End Class
+End Namespace
diff --git a/test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Output/Helpers.vb b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Output/Helpers.vb
new file mode 100644
index 00000000..b8e2ec46
--- /dev/null
+++ b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Output/Helpers.vb
@@ -0,0 +1,87 @@
+'------------------------------------------------------------------------------
+' <auto-generated>
+' This code was generated by a tool.
+' Runtime Version:N.N.NNNNN.N
+'
+' Changes to this file may cause incorrect behavior and will be lost if
+' the code is regenerated.
+' </auto-generated>
+'------------------------------------------------------------------------------
+
+Option Strict Off
+Option Explicit On
+
+Imports System
+
+Namespace TestOutput
+Public Class Helpers
+
+#ExternalSource("Helpers.vbhtml", 1)
+Public Shared Function Bold(s as String) As Template
+
+#End ExternalSource
+Return New Template(Sub (__razor_helper_writer)
+
+#ExternalSource("Helpers.vbhtml", 1)
+
+ s = s.ToUpper()
+
+#End ExternalSource
+WriteLiteralTo(__razor_helper_writer, " ")
+WriteLiteralTo(__razor_helper_writer, "<strong>")
+
+#ExternalSource("Helpers.vbhtml", 3)
+WriteTo(__razor_helper_writer, s)
+
+#End ExternalSource
+WriteLiteralTo(__razor_helper_writer, "</strong>"&Global.Microsoft.VisualBasic.ChrW(13)&Global.Microsoft.VisualBasic.ChrW(10))
+
+#ExternalSource("Helpers.vbhtml", 4)
+
+#End ExternalSource
+End Sub)
+End Function
+
+#ExternalSource("Helpers.vbhtml", 6)
+Public Shared Function Italic(s as String) As Template
+
+#End ExternalSource
+Return New Template(Sub (__razor_helper_writer)
+
+#ExternalSource("Helpers.vbhtml", 6)
+
+ s = s.ToUpper()
+
+#End ExternalSource
+WriteLiteralTo(__razor_helper_writer, " ")
+WriteLiteralTo(__razor_helper_writer, "<em>")
+
+#ExternalSource("Helpers.vbhtml", 8)
+WriteTo(__razor_helper_writer, s)
+
+#End ExternalSource
+WriteLiteralTo(__razor_helper_writer, "</em>"&Global.Microsoft.VisualBasic.ChrW(13)&Global.Microsoft.VisualBasic.ChrW(10))
+
+#ExternalSource("Helpers.vbhtml", 9)
+
+#End ExternalSource
+End Sub)
+End Function
+
+Public Sub New()
+MyBase.New
+End Sub
+Public Overrides Sub Execute()
+WriteLiteral(""&Global.Microsoft.VisualBasic.ChrW(13)&Global.Microsoft.VisualBasic.ChrW(10)&Global.Microsoft.VisualBasic.ChrW(13)&Global.Microsoft.VisualBasic.ChrW(10))
+
+WriteLiteral(""&Global.Microsoft.VisualBasic.ChrW(13)&Global.Microsoft.VisualBasic.ChrW(10)&Global.Microsoft.VisualBasic.ChrW(13)&Global.Microsoft.VisualBasic.ChrW(10))
+
+
+#ExternalSource("Helpers.vbhtml",11)
+Write(Bold("Hello"))
+
+
+#End ExternalSource
+End Sub
+End Class
+End Namespace
diff --git a/test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Output/HelpersMissingCloseParen.vb b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Output/HelpersMissingCloseParen.vb
new file mode 100644
index 00000000..9a7337cb
--- /dev/null
+++ b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Output/HelpersMissingCloseParen.vb
@@ -0,0 +1,60 @@
+'------------------------------------------------------------------------------
+' <auto-generated>
+' This code was generated by a tool.
+' Runtime Version:N.N.NNNNN.N
+'
+' Changes to this file may cause incorrect behavior and will be lost if
+' the code is regenerated.
+' </auto-generated>
+'------------------------------------------------------------------------------
+
+Option Strict Off
+Option Explicit On
+
+Imports System
+
+Namespace TestOutput
+Public Class HelpersMissingCloseParen
+
+#ExternalSource("HelpersMissingCloseParen.vbhtml", 1)
+Public Shared Function Bold(s as String) As Template
+
+#End ExternalSource
+Return New Template(Sub (__razor_helper_writer)
+
+#ExternalSource("HelpersMissingCloseParen.vbhtml", 1)
+
+ s = s.ToUpper()
+
+#End ExternalSource
+WriteLiteralTo(__razor_helper_writer, " ")
+WriteLiteralTo(__razor_helper_writer, "<strong>")
+
+#ExternalSource("HelpersMissingCloseParen.vbhtml", 3)
+WriteTo(__razor_helper_writer, s)
+
+#End ExternalSource
+WriteLiteralTo(__razor_helper_writer, "</strong>"&Global.Microsoft.VisualBasic.ChrW(13)&Global.Microsoft.VisualBasic.ChrW(10))
+
+#ExternalSource("HelpersMissingCloseParen.vbhtml", 4)
+
+#End ExternalSource
+End Sub)
+End Function
+
+#ExternalSource("HelpersMissingCloseParen.vbhtml", 6)
+Public Shared Function Italic(s as String
+
+@Bold("Hello")
+#End ExternalSource
+End Function
+
+Public Sub New()
+MyBase.New
+End Sub
+Public Overrides Sub Execute()
+WriteLiteral(""&Global.Microsoft.VisualBasic.ChrW(13)&Global.Microsoft.VisualBasic.ChrW(10)&Global.Microsoft.VisualBasic.ChrW(13)&Global.Microsoft.VisualBasic.ChrW(10))
+
+End Sub
+End Class
+End Namespace
diff --git a/test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Output/HelpersMissingName.vb b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Output/HelpersMissingName.vb
new file mode 100644
index 00000000..79c3c00d
--- /dev/null
+++ b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Output/HelpersMissingName.vb
@@ -0,0 +1,24 @@
+'------------------------------------------------------------------------------
+' <auto-generated>
+' This code was generated by a tool.
+' Runtime Version:N.N.NNNNN.N
+'
+' Changes to this file may cause incorrect behavior and will be lost if
+' the code is regenerated.
+' </auto-generated>
+'------------------------------------------------------------------------------
+
+Option Strict Off
+Option Explicit On
+
+Imports System
+
+Namespace TestOutput
+Public Class HelpersMissingName
+Public Sub New()
+MyBase.New
+End Sub
+Public Overrides Sub Execute()
+End Sub
+End Class
+End Namespace
diff --git a/test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Output/HelpersMissingOpenParen.vb b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Output/HelpersMissingOpenParen.vb
new file mode 100644
index 00000000..62fedca0
--- /dev/null
+++ b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Output/HelpersMissingOpenParen.vb
@@ -0,0 +1,66 @@
+'------------------------------------------------------------------------------
+' <auto-generated>
+' This code was generated by a tool.
+' Runtime Version:N.N.NNNNN.N
+'
+' Changes to this file may cause incorrect behavior and will be lost if
+' the code is regenerated.
+' </auto-generated>
+'------------------------------------------------------------------------------
+
+Option Strict Off
+Option Explicit On
+
+Imports System
+
+Namespace TestOutput
+Public Class HelpersMissingOpenParen
+
+#ExternalSource("HelpersMissingOpenParen.vbhtml", 1)
+Public Shared Function Bold(s as String) As Template
+
+#End ExternalSource
+Return New Template(Sub (__razor_helper_writer)
+
+#ExternalSource("HelpersMissingOpenParen.vbhtml", 1)
+
+ s = s.ToUpper()
+
+#End ExternalSource
+WriteLiteralTo(__razor_helper_writer, " ")
+WriteLiteralTo(__razor_helper_writer, "<strong>")
+
+#ExternalSource("HelpersMissingOpenParen.vbhtml", 3)
+WriteTo(__razor_helper_writer, s)
+
+#End ExternalSource
+WriteLiteralTo(__razor_helper_writer, "</strong>"&Global.Microsoft.VisualBasic.ChrW(13)&Global.Microsoft.VisualBasic.ChrW(10))
+
+#ExternalSource("HelpersMissingOpenParen.vbhtml", 4)
+
+#End ExternalSource
+End Sub)
+End Function
+
+#ExternalSource("HelpersMissingOpenParen.vbhtml", 6)
+Public Shared Function Italic
+#End ExternalSource
+End Function
+
+Public Sub New()
+MyBase.New
+End Sub
+Public Overrides Sub Execute()
+WriteLiteral(""&Global.Microsoft.VisualBasic.ChrW(13)&Global.Microsoft.VisualBasic.ChrW(10)&Global.Microsoft.VisualBasic.ChrW(13)&Global.Microsoft.VisualBasic.ChrW(10))
+
+WriteLiteral(""&Global.Microsoft.VisualBasic.ChrW(13)&Global.Microsoft.VisualBasic.ChrW(10)&Global.Microsoft.VisualBasic.ChrW(13)&Global.Microsoft.VisualBasic.ChrW(10))
+
+
+#ExternalSource("HelpersMissingOpenParen.vbhtml",8)
+Write(Bold("Hello"))
+
+
+#End ExternalSource
+End Sub
+End Class
+End Namespace
diff --git a/test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Output/ImplicitExpression.vb b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Output/ImplicitExpression.vb
new file mode 100644
index 00000000..dd9e756f
--- /dev/null
+++ b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Output/ImplicitExpression.vb
@@ -0,0 +1,56 @@
+'------------------------------------------------------------------------------
+' <auto-generated>
+' This code was generated by a tool.
+' Runtime Version:N.N.NNNNN.N
+'
+' Changes to this file may cause incorrect behavior and will be lost if
+' the code is regenerated.
+' </auto-generated>
+'------------------------------------------------------------------------------
+
+Option Strict Off
+Option Explicit On
+
+Imports System
+
+Namespace TestOutput
+Public Class ImplicitExpression
+Public Sub New()
+MyBase.New
+End Sub
+Public Overrides Sub Execute()
+
+#ExternalSource("ImplicitExpression.vbhtml",1)
+ For i = 1 To 10
+
+
+#End ExternalSource
+WriteLiteral(" ")
+
+WriteLiteral("<p>This is item #")
+
+
+#ExternalSource("ImplicitExpression.vbhtml",2)
+ Write(i)
+
+
+#End ExternalSource
+WriteLiteral("</p>"&Global.Microsoft.VisualBasic.ChrW(13)&Global.Microsoft.VisualBasic.ChrW(10))
+
+
+#ExternalSource("ImplicitExpression.vbhtml",3)
+Next
+
+
+#End ExternalSource
+WriteLiteral(""&Global.Microsoft.VisualBasic.ChrW(13)&Global.Microsoft.VisualBasic.ChrW(10))
+
+
+#ExternalSource("ImplicitExpression.vbhtml",5)
+Write(SyntaxSampleHelpers.CodeForLink(Me))
+
+
+#End ExternalSource
+End Sub
+End Class
+End Namespace
diff --git a/test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Output/ImplicitExpressionAtEOF.vb b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Output/ImplicitExpressionAtEOF.vb
new file mode 100644
index 00000000..debff90d
--- /dev/null
+++ b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Output/ImplicitExpressionAtEOF.vb
@@ -0,0 +1,31 @@
+'------------------------------------------------------------------------------
+' <auto-generated>
+' This code was generated by a tool.
+' Runtime Version:N.N.NNNNN.N
+'
+' Changes to this file may cause incorrect behavior and will be lost if
+' the code is regenerated.
+' </auto-generated>
+'------------------------------------------------------------------------------
+
+Option Strict Off
+Option Explicit On
+
+Imports System
+
+Namespace TestOutput
+Public Class ImplicitExpressionAtEOF
+Private Shared __o As Object
+Public Sub New()
+MyBase.New
+End Sub
+Public Overrides Sub Execute()
+
+#ExternalSource("ImplicitExpressionAtEOF.vbhtml",1)
+__o =
+
+
+#End ExternalSource
+End Sub
+End Class
+End Namespace
diff --git a/test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Output/Imports.DesignTime.vb b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Output/Imports.DesignTime.vb
new file mode 100644
index 00000000..5868d5ab
--- /dev/null
+++ b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Output/Imports.DesignTime.vb
@@ -0,0 +1,41 @@
+'------------------------------------------------------------------------------
+' <auto-generated>
+' This code was generated by a tool.
+' Runtime Version:N.N.NNNNN.N
+'
+' Changes to this file may cause incorrect behavior and will be lost if
+' the code is regenerated.
+' </auto-generated>
+'------------------------------------------------------------------------------
+
+Option Strict Off
+Option Explicit On
+
+Imports Foo = System.Text.Encoding
+
+Imports System
+Imports System.IO
+
+
+Namespace TestOutput
+Public Class [Imports]
+Private Shared __o As Object
+Public Sub New()
+MyBase.New
+End Sub
+Public Overrides Sub Execute()
+
+#ExternalSource("Imports.vbhtml",4)
+ __o = GetType(Path).FullName
+
+
+#End ExternalSource
+
+#ExternalSource("Imports.vbhtml",5)
+ __o = GetType(Foo).FullName
+
+
+#End ExternalSource
+End Sub
+End Class
+End Namespace
diff --git a/test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Output/Imports.vb b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Output/Imports.vb
new file mode 100644
index 00000000..d35ce2ab
--- /dev/null
+++ b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Output/Imports.vb
@@ -0,0 +1,46 @@
+'------------------------------------------------------------------------------
+' <auto-generated>
+' This code was generated by a tool.
+' Runtime Version:N.N.NNNNN.N
+'
+' Changes to this file may cause incorrect behavior and will be lost if
+' the code is regenerated.
+' </auto-generated>
+'------------------------------------------------------------------------------
+
+Option Strict Off
+Option Explicit On
+
+Imports Foo = System.Text.Encoding
+
+Imports System
+Imports System.IO
+
+
+Namespace TestOutput
+Public Class [Imports]
+Public Sub New()
+MyBase.New
+End Sub
+Public Overrides Sub Execute()
+WriteLiteral(""&Global.Microsoft.VisualBasic.ChrW(13)&Global.Microsoft.VisualBasic.ChrW(10)&"<p>Path's full type name is ")
+
+
+#ExternalSource("Imports.vbhtml",5)
+ Write(GetType(Path).FullName)
+
+
+#End ExternalSource
+WriteLiteral("</p>"&Global.Microsoft.VisualBasic.ChrW(13)&Global.Microsoft.VisualBasic.ChrW(10)&"<p>Foo's actual full type name is ")
+
+
+#ExternalSource("Imports.vbhtml",6)
+ Write(GetType(Foo).FullName)
+
+
+#End ExternalSource
+WriteLiteral("</p>")
+
+End Sub
+End Class
+End Namespace
diff --git a/test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Output/Inherits.Designtime.vb b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Output/Inherits.Designtime.vb
new file mode 100644
index 00000000..fc62324b
--- /dev/null
+++ b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Output/Inherits.Designtime.vb
@@ -0,0 +1,35 @@
+'------------------------------------------------------------------------------
+' <auto-generated>
+' This code was generated by a tool.
+' Runtime Version:N.N.NNNNN.N
+'
+' Changes to this file may cause incorrect behavior and will be lost if
+' the code is regenerated.
+' </auto-generated>
+'------------------------------------------------------------------------------
+
+Option Strict Off
+Option Explicit On
+
+Imports System
+
+Namespace TestOutput
+Public Class [Inherits]
+Inherits System.Web.WebPages.WebPage
+Public Sub New()
+MyBase.New
+End Sub
+Private Sub __RazorDesignTimeHelpers__()
+
+
+#ExternalSource("Inherits.vbhtml",1)
+Dim __inheritsHelper As System.Web.WebPages.WebPage = Nothing
+
+
+#End ExternalSource
+
+End Sub
+Public Overrides Sub Execute()
+End Sub
+End Class
+End Namespace
diff --git a/test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Output/Inherits.Runtime.vb b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Output/Inherits.Runtime.vb
new file mode 100644
index 00000000..1395dac1
--- /dev/null
+++ b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Output/Inherits.Runtime.vb
@@ -0,0 +1,25 @@
+'------------------------------------------------------------------------------
+' <auto-generated>
+' This code was generated by a tool.
+' Runtime Version:N.N.NNNNN.N
+'
+' Changes to this file may cause incorrect behavior and will be lost if
+' the code is regenerated.
+' </auto-generated>
+'------------------------------------------------------------------------------
+
+Option Strict Off
+Option Explicit On
+
+Imports System
+
+Namespace TestOutput
+Public Class [Inherits]
+Inherits System.Web.WebPages.WebPage
+Public Sub New()
+MyBase.New
+End Sub
+Public Overrides Sub Execute()
+End Sub
+End Class
+End Namespace
diff --git a/test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Output/Instrumented.vb b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Output/Instrumented.vb
new file mode 100644
index 00000000..22eadaf0
--- /dev/null
+++ b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Output/Instrumented.vb
@@ -0,0 +1,497 @@
+'------------------------------------------------------------------------------
+' <auto-generated>
+' This code was generated by a tool.
+' Runtime Version:N.N.NNNNN.N
+'
+' Changes to this file may cause incorrect behavior and will be lost if
+' the code is regenerated.
+' </auto-generated>
+'------------------------------------------------------------------------------
+
+Option Strict Off
+Option Explicit On
+
+Imports System
+
+Namespace TestOutput
+Public Class Instrumented
+
+#ExternalSource("Instrumented.vbhtml", 1)
+Public Shared Function Strong(s As String) As Template
+
+#End ExternalSource
+Return New Template(Sub (__razor_helper_writer)
+
+#ExternalSource("Instrumented.vbhtml", 1)
+
+
+#End ExternalSource
+BeginContext(__razor_helper_writer, "~/Instrumented.vbhtml", 29, 4, true)
+WriteLiteralTo(__razor_helper_writer, " ")
+EndContext(__razor_helper_writer, "~/Instrumented.vbhtml", 29, 4, true)
+BeginContext(__razor_helper_writer, "~/Instrumented.vbhtml", 34, 8, true)
+WriteLiteralTo(__razor_helper_writer, "<strong>")
+EndContext(__razor_helper_writer, "~/Instrumented.vbhtml", 34, 8, true)
+BeginContext(__razor_helper_writer, "~/Instrumented.vbhtml", 43, 1, false)
+
+#ExternalSource("Instrumented.vbhtml", 2)
+WriteTo(__razor_helper_writer, s)
+
+#End ExternalSource
+EndContext(__razor_helper_writer, "~/Instrumented.vbhtml", 43, 1, false)
+BeginContext(__razor_helper_writer, "~/Instrumented.vbhtml", 44, 11, true)
+WriteLiteralTo(__razor_helper_writer, "</strong>"&Global.Microsoft.VisualBasic.ChrW(13)&Global.Microsoft.VisualBasic.ChrW(10))
+EndContext(__razor_helper_writer, "~/Instrumented.vbhtml", 44, 11, true)
+
+#ExternalSource("Instrumented.vbhtml", 3)
+
+#End ExternalSource
+End Sub)
+End Function
+
+Public Sub New()
+MyBase.New
+End Sub
+Public Overrides Sub Execute()
+BeginContext("~/Instrumented.vbhtml", 65, 4, true)
+
+WriteLiteral(""&Global.Microsoft.VisualBasic.ChrW(13)&Global.Microsoft.VisualBasic.ChrW(10)&Global.Microsoft.VisualBasic.ChrW(13)&Global.Microsoft.VisualBasic.ChrW(10))
+
+EndContext("~/Instrumented.vbhtml", 65, 4, true)
+
+
+#ExternalSource("Instrumented.vbhtml",5)
+
+ Dim i As Integer = 1
+ Dim foo =
+
+#End ExternalSource
+Function (item) New Template(Sub (__razor_template_writer)
+
+BeginContext(__razor_template_writer, "~/Instrumented.vbhtml", 118, 12, true)
+
+WriteLiteralTo(__razor_template_writer, "<p>Foo</p>"&Global.Microsoft.VisualBasic.ChrW(13)&Global.Microsoft.VisualBasic.ChrW(10))
+
+EndContext(__razor_template_writer, "~/Instrumented.vbhtml", 118, 12, true)
+
+BeginContext("~/Instrumented.vbhtml", 130, 4, true)
+
+WriteLiteral(" ")
+
+EndContext("~/Instrumented.vbhtml", 130, 4, true)
+
+BeginContext("~/Instrumented.vbhtml", 136, 15, true)
+
+WriteLiteral("Hello, World!"&Global.Microsoft.VisualBasic.ChrW(13)&Global.Microsoft.VisualBasic.ChrW(10))
+
+EndContext("~/Instrumented.vbhtml", 136, 15, true)
+
+BeginContext("~/Instrumented.vbhtml", 151, 4, true)
+
+WriteLiteral(" ")
+
+EndContext("~/Instrumented.vbhtml", 151, 4, true)
+
+BeginContext("~/Instrumented.vbhtml", 156, 22, true)
+
+WriteLiteral("<p>Hello, World!</p>"&Global.Microsoft.VisualBasic.ChrW(13)&Global.Microsoft.VisualBasic.ChrW(10))
+
+EndContext("~/Instrumented.vbhtml", 156, 22, true)
+
+End Sub)
+
+#ExternalSource("Instrumented.vbhtml",10)
+
+
+#End ExternalSource
+BeginContext("~/Instrumented.vbhtml", 186, 4, true)
+
+WriteLiteral(""&Global.Microsoft.VisualBasic.ChrW(13)&Global.Microsoft.VisualBasic.ChrW(10)&Global.Microsoft.VisualBasic.ChrW(13)&Global.Microsoft.VisualBasic.ChrW(10))
+
+EndContext("~/Instrumented.vbhtml", 186, 4, true)
+
+
+#ExternalSource("Instrumented.vbhtml",12)
+ While i <= 10
+
+
+#End ExternalSource
+BeginContext("~/Instrumented.vbhtml", 206, 4, true)
+
+WriteLiteral(" ")
+
+EndContext("~/Instrumented.vbhtml", 206, 4, true)
+
+BeginContext("~/Instrumented.vbhtml", 211, 23, true)
+
+WriteLiteral("<p>Hello from VB.Net, #")
+
+EndContext("~/Instrumented.vbhtml", 211, 23, true)
+
+BeginContext("~/Instrumented.vbhtml", 236, 1, false)
+
+
+#ExternalSource("Instrumented.vbhtml",13)
+ Write(i)
+
+
+#End ExternalSource
+EndContext("~/Instrumented.vbhtml", 236, 1, false)
+
+BeginContext("~/Instrumented.vbhtml", 238, 6, true)
+
+WriteLiteral("</p>"&Global.Microsoft.VisualBasic.ChrW(13)&Global.Microsoft.VisualBasic.ChrW(10))
+
+EndContext("~/Instrumented.vbhtml", 238, 6, true)
+
+
+#ExternalSource("Instrumented.vbhtml",14)
+ i += 1
+End While
+
+
+#End ExternalSource
+BeginContext("~/Instrumented.vbhtml", 267, 2, true)
+
+WriteLiteral(""&Global.Microsoft.VisualBasic.ChrW(13)&Global.Microsoft.VisualBasic.ChrW(10))
+
+EndContext("~/Instrumented.vbhtml", 267, 2, true)
+
+
+#ExternalSource("Instrumented.vbhtml",17)
+ If i = 11 Then
+
+
+#End ExternalSource
+BeginContext("~/Instrumented.vbhtml", 286, 4, true)
+
+WriteLiteral(" ")
+
+EndContext("~/Instrumented.vbhtml", 286, 4, true)
+
+BeginContext("~/Instrumented.vbhtml", 291, 27, true)
+
+WriteLiteral("<p>We wrote 10 lines!</p>"&Global.Microsoft.VisualBasic.ChrW(13)&Global.Microsoft.VisualBasic.ChrW(10))
+
+EndContext("~/Instrumented.vbhtml", 291, 27, true)
+
+
+#ExternalSource("Instrumented.vbhtml",19)
+End If
+
+
+#End ExternalSource
+BeginContext("~/Instrumented.vbhtml", 326, 2, true)
+
+WriteLiteral(""&Global.Microsoft.VisualBasic.ChrW(13)&Global.Microsoft.VisualBasic.ChrW(10))
+
+EndContext("~/Instrumented.vbhtml", 326, 2, true)
+
+
+#ExternalSource("Instrumented.vbhtml",21)
+ Do
+
+
+#End ExternalSource
+BeginContext("~/Instrumented.vbhtml", 333, 4, true)
+
+WriteLiteral(" ")
+
+EndContext("~/Instrumented.vbhtml", 333, 4, true)
+
+BeginContext("~/Instrumented.vbhtml", 338, 16, true)
+
+WriteLiteral("<p>Hello again: ")
+
+EndContext("~/Instrumented.vbhtml", 338, 16, true)
+
+BeginContext("~/Instrumented.vbhtml", 355, 1, false)
+
+
+#ExternalSource("Instrumented.vbhtml",22)
+ Write(i)
+
+
+#End ExternalSource
+EndContext("~/Instrumented.vbhtml", 355, 1, false)
+
+BeginContext("~/Instrumented.vbhtml", 356, 6, true)
+
+WriteLiteral("</p>"&Global.Microsoft.VisualBasic.ChrW(13)&Global.Microsoft.VisualBasic.ChrW(10))
+
+EndContext("~/Instrumented.vbhtml", 356, 6, true)
+
+
+#ExternalSource("Instrumented.vbhtml",23)
+ i -= 1
+Loop While i > 0
+
+
+#End ExternalSource
+BeginContext("~/Instrumented.vbhtml", 392, 2, true)
+
+WriteLiteral(""&Global.Microsoft.VisualBasic.ChrW(13)&Global.Microsoft.VisualBasic.ChrW(10))
+
+EndContext("~/Instrumented.vbhtml", 392, 2, true)
+
+
+#ExternalSource("Instrumented.vbhtml",26)
+ Select Case i
+ Case 11
+
+
+#End ExternalSource
+BeginContext("~/Instrumented.vbhtml", 423, 8, true)
+
+WriteLiteral(" ")
+
+EndContext("~/Instrumented.vbhtml", 423, 8, true)
+
+BeginContext("~/Instrumented.vbhtml", 432, 38, true)
+
+WriteLiteral("<p>No really, we wrote 10 lines!</p>"&Global.Microsoft.VisualBasic.ChrW(13)&Global.Microsoft.VisualBasic.ChrW(10))
+
+EndContext("~/Instrumented.vbhtml", 432, 38, true)
+
+
+#ExternalSource("Instrumented.vbhtml",29)
+ Case Else
+
+
+#End ExternalSource
+BeginContext("~/Instrumented.vbhtml", 485, 8, true)
+
+WriteLiteral(" ")
+
+EndContext("~/Instrumented.vbhtml", 485, 8, true)
+
+BeginContext("~/Instrumented.vbhtml", 494, 41, true)
+
+WriteLiteral("<p>We wrote a bunch more lines too!</p>"&Global.Microsoft.VisualBasic.ChrW(13)&Global.Microsoft.VisualBasic.ChrW(10))
+
+EndContext("~/Instrumented.vbhtml", 494, 41, true)
+
+
+#ExternalSource("Instrumented.vbhtml",31)
+End Select
+
+
+#End ExternalSource
+BeginContext("~/Instrumented.vbhtml", 547, 2, true)
+
+WriteLiteral(""&Global.Microsoft.VisualBasic.ChrW(13)&Global.Microsoft.VisualBasic.ChrW(10))
+
+EndContext("~/Instrumented.vbhtml", 547, 2, true)
+
+
+#ExternalSource("Instrumented.vbhtml",33)
+ For j as Integer = 1 to 10 Step 2
+
+
+#End ExternalSource
+BeginContext("~/Instrumented.vbhtml", 585, 4, true)
+
+WriteLiteral(" ")
+
+EndContext("~/Instrumented.vbhtml", 585, 4, true)
+
+BeginContext("~/Instrumented.vbhtml", 590, 29, true)
+
+WriteLiteral("<p>Hello again from VB.Net, #")
+
+EndContext("~/Instrumented.vbhtml", 590, 29, true)
+
+BeginContext("~/Instrumented.vbhtml", 621, 1, false)
+
+
+#ExternalSource("Instrumented.vbhtml",34)
+ Write(j)
+
+
+#End ExternalSource
+EndContext("~/Instrumented.vbhtml", 621, 1, false)
+
+BeginContext("~/Instrumented.vbhtml", 623, 6, true)
+
+WriteLiteral("</p>"&Global.Microsoft.VisualBasic.ChrW(13)&Global.Microsoft.VisualBasic.ChrW(10))
+
+EndContext("~/Instrumented.vbhtml", 623, 6, true)
+
+
+#ExternalSource("Instrumented.vbhtml",35)
+Next j
+
+
+#End ExternalSource
+BeginContext("~/Instrumented.vbhtml", 637, 2, true)
+
+WriteLiteral(""&Global.Microsoft.VisualBasic.ChrW(13)&Global.Microsoft.VisualBasic.ChrW(10))
+
+EndContext("~/Instrumented.vbhtml", 637, 2, true)
+
+
+#ExternalSource("Instrumented.vbhtml",37)
+ Try
+
+
+#End ExternalSource
+BeginContext("~/Instrumented.vbhtml", 645, 4, true)
+
+WriteLiteral(" ")
+
+EndContext("~/Instrumented.vbhtml", 645, 4, true)
+
+BeginContext("~/Instrumented.vbhtml", 650, 37, true)
+
+WriteLiteral("<p>That time, we wrote 5 lines!</p>"&Global.Microsoft.VisualBasic.ChrW(13)&Global.Microsoft.VisualBasic.ChrW(10))
+
+EndContext("~/Instrumented.vbhtml", 650, 37, true)
+
+
+#ExternalSource("Instrumented.vbhtml",39)
+Catch ex as Exception
+
+
+#End ExternalSource
+BeginContext("~/Instrumented.vbhtml", 710, 4, true)
+
+WriteLiteral(" ")
+
+EndContext("~/Instrumented.vbhtml", 710, 4, true)
+
+BeginContext("~/Instrumented.vbhtml", 715, 29, true)
+
+WriteLiteral("<p>Oh no! An error occurred: ")
+
+EndContext("~/Instrumented.vbhtml", 715, 29, true)
+
+BeginContext("~/Instrumented.vbhtml", 746, 10, false)
+
+
+#ExternalSource("Instrumented.vbhtml",40)
+ Write(ex.Message)
+
+
+#End ExternalSource
+EndContext("~/Instrumented.vbhtml", 746, 10, false)
+
+BeginContext("~/Instrumented.vbhtml", 757, 6, true)
+
+WriteLiteral("</p>"&Global.Microsoft.VisualBasic.ChrW(13)&Global.Microsoft.VisualBasic.ChrW(10))
+
+EndContext("~/Instrumented.vbhtml", 757, 6, true)
+
+
+#ExternalSource("Instrumented.vbhtml",41)
+End Try
+
+
+#End ExternalSource
+BeginContext("~/Instrumented.vbhtml", 772, 2, true)
+
+WriteLiteral(""&Global.Microsoft.VisualBasic.ChrW(13)&Global.Microsoft.VisualBasic.ChrW(10))
+
+EndContext("~/Instrumented.vbhtml", 772, 2, true)
+
+
+#ExternalSource("Instrumented.vbhtml",43)
+ With i
+
+
+#End ExternalSource
+BeginContext("~/Instrumented.vbhtml", 783, 4, true)
+
+WriteLiteral(" ")
+
+EndContext("~/Instrumented.vbhtml", 783, 4, true)
+
+BeginContext("~/Instrumented.vbhtml", 788, 12, true)
+
+WriteLiteral("<p>i is now ")
+
+EndContext("~/Instrumented.vbhtml", 788, 12, true)
+
+BeginContext("~/Instrumented.vbhtml", 802, 11, false)
+
+
+#ExternalSource("Instrumented.vbhtml",44)
+ Write(.ToString())
+
+
+#End ExternalSource
+EndContext("~/Instrumented.vbhtml", 802, 11, false)
+
+BeginContext("~/Instrumented.vbhtml", 814, 6, true)
+
+WriteLiteral("</p>"&Global.Microsoft.VisualBasic.ChrW(13)&Global.Microsoft.VisualBasic.ChrW(10))
+
+EndContext("~/Instrumented.vbhtml", 814, 6, true)
+
+
+#ExternalSource("Instrumented.vbhtml",45)
+End With
+
+
+#End ExternalSource
+BeginContext("~/Instrumented.vbhtml", 830, 2, true)
+
+WriteLiteral(""&Global.Microsoft.VisualBasic.ChrW(13)&Global.Microsoft.VisualBasic.ChrW(10))
+
+EndContext("~/Instrumented.vbhtml", 830, 2, true)
+
+
+#ExternalSource("Instrumented.vbhtml",47)
+ SyncLock New Object()
+
+
+#End ExternalSource
+BeginContext("~/Instrumented.vbhtml", 856, 4, true)
+
+WriteLiteral(" ")
+
+EndContext("~/Instrumented.vbhtml", 856, 4, true)
+
+BeginContext("~/Instrumented.vbhtml", 861, 49, true)
+
+WriteLiteral("<p>This block is locked, for your security!</p>"&Global.Microsoft.VisualBasic.ChrW(13)&Global.Microsoft.VisualBasic.ChrW(10))
+
+EndContext("~/Instrumented.vbhtml", 861, 49, true)
+
+
+#ExternalSource("Instrumented.vbhtml",49)
+End SyncLock
+
+
+#End ExternalSource
+BeginContext("~/Instrumented.vbhtml", 924, 2, true)
+
+WriteLiteral(""&Global.Microsoft.VisualBasic.ChrW(13)&Global.Microsoft.VisualBasic.ChrW(10))
+
+EndContext("~/Instrumented.vbhtml", 924, 2, true)
+
+
+#ExternalSource("Instrumented.vbhtml",51)
+ Using New System.IO.MemoryStream()
+
+
+#End ExternalSource
+BeginContext("~/Instrumented.vbhtml", 963, 4, true)
+
+WriteLiteral(" ")
+
+EndContext("~/Instrumented.vbhtml", 963, 4, true)
+
+BeginContext("~/Instrumented.vbhtml", 968, 78, true)
+
+WriteLiteral("<p>Some random memory stream will be disposed after rendering this block</p>"&Global.Microsoft.VisualBasic.ChrW(13)&Global.Microsoft.VisualBasic.ChrW(10))
+
+EndContext("~/Instrumented.vbhtml", 968, 78, true)
+
+
+#ExternalSource("Instrumented.vbhtml",53)
+End Using
+
+#End ExternalSource
+End Sub
+End Class
+End Namespace
diff --git a/test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Output/LayoutDirective.vb b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Output/LayoutDirective.vb
new file mode 100644
index 00000000..fb7bdbdd
--- /dev/null
+++ b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Output/LayoutDirective.vb
@@ -0,0 +1,25 @@
+'------------------------------------------------------------------------------
+' <auto-generated>
+' This code was generated by a tool.
+' Runtime Version:N.N.NNNNN.N
+'
+' Changes to this file may cause incorrect behavior and will be lost if
+' the code is regenerated.
+' </auto-generated>
+'------------------------------------------------------------------------------
+
+Option Strict Off
+Option Explicit On
+
+Imports System
+
+Namespace TestOutput
+Public Class LayoutDirective
+Public Sub New()
+MyBase.New
+End Sub
+Public Overrides Sub Execute()
+Layout = "~/Foo/Bar/Baz"
+End Sub
+End Class
+End Namespace
diff --git a/test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Output/MarkupInCodeBlock.vb b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Output/MarkupInCodeBlock.vb
new file mode 100644
index 00000000..c70fd0bf
--- /dev/null
+++ b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Output/MarkupInCodeBlock.vb
@@ -0,0 +1,51 @@
+'------------------------------------------------------------------------------
+' <auto-generated>
+' This code was generated by a tool.
+' Runtime Version:N.N.NNNNN.N
+'
+' Changes to this file may cause incorrect behavior and will be lost if
+' the code is regenerated.
+' </auto-generated>
+'------------------------------------------------------------------------------
+
+Option Strict Off
+Option Explicit On
+
+Imports System
+
+Namespace TestOutput
+Public Class MarkupInCodeBlock
+Public Sub New()
+MyBase.New
+End Sub
+Public Overrides Sub Execute()
+
+#ExternalSource("MarkupInCodeBlock.vbhtml",1)
+
+ For i = 1 To 10
+
+
+#End ExternalSource
+WriteLiteral(" ")
+
+WriteLiteral("<p>Hello from VB.Net, #")
+
+
+#ExternalSource("MarkupInCodeBlock.vbhtml",3)
+ Write(i.ToString())
+
+
+#End ExternalSource
+WriteLiteral("</p>"&Global.Microsoft.VisualBasic.ChrW(13)&Global.Microsoft.VisualBasic.ChrW(10))
+
+
+#ExternalSource("MarkupInCodeBlock.vbhtml",4)
+ Next i
+
+
+#End ExternalSource
+WriteLiteral(""&Global.Microsoft.VisualBasic.ChrW(13)&Global.Microsoft.VisualBasic.ChrW(10))
+
+End Sub
+End Class
+End Namespace
diff --git a/test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Output/NestedCodeBlocks.vb b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Output/NestedCodeBlocks.vb
new file mode 100644
index 00000000..1ea3d48b
--- /dev/null
+++ b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Output/NestedCodeBlocks.vb
@@ -0,0 +1,42 @@
+'------------------------------------------------------------------------------
+' <auto-generated>
+' This code was generated by a tool.
+' Runtime Version:N.N.NNNNN.N
+'
+' Changes to this file may cause incorrect behavior and will be lost if
+' the code is regenerated.
+' </auto-generated>
+'------------------------------------------------------------------------------
+
+Option Strict Off
+Option Explicit On
+
+Imports System
+
+Namespace TestOutput
+Public Class NestedCodeBlocks
+Public Sub New()
+MyBase.New
+End Sub
+Public Overrides Sub Execute()
+
+#ExternalSource("NestedCodeBlocks.vbhtml",1)
+ If True Then
+
+
+#End ExternalSource
+
+#ExternalSource("NestedCodeBlocks.vbhtml",2)
+ If True Then
+ End If
+
+
+#End ExternalSource
+
+#ExternalSource("NestedCodeBlocks.vbhtml",4)
+End If
+
+#End ExternalSource
+End Sub
+End Class
+End Namespace
diff --git a/test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Output/NestedHelpers.vb b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Output/NestedHelpers.vb
new file mode 100644
index 00000000..f6db0092
--- /dev/null
+++ b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Output/NestedHelpers.vb
@@ -0,0 +1,90 @@
+'------------------------------------------------------------------------------
+' <auto-generated>
+' This code was generated by a tool.
+' Runtime Version:N.N.NNNNN.N
+'
+' Changes to this file may cause incorrect behavior and will be lost if
+' the code is regenerated.
+' </auto-generated>
+'------------------------------------------------------------------------------
+
+Option Strict Off
+Option Explicit On
+
+Imports System
+
+Namespace TestOutput
+Public Class NestedHelpers
+
+#ExternalSource("NestedHelpers.vbhtml", 3)
+Public Shared Function Bold(s As String) As Template
+
+#End ExternalSource
+Return New Template(Sub (__razor_helper_writer)
+
+#ExternalSource("NestedHelpers.vbhtml", 3)
+
+ s = s.ToUpper()
+
+#End ExternalSource
+WriteLiteralTo(__razor_helper_writer, " ")
+WriteLiteralTo(__razor_helper_writer, "<strong>")
+
+#ExternalSource("NestedHelpers.vbhtml", 5)
+WriteTo(__razor_helper_writer, s)
+
+#End ExternalSource
+WriteLiteralTo(__razor_helper_writer, "</strong>"&Global.Microsoft.VisualBasic.ChrW(13)&Global.Microsoft.VisualBasic.ChrW(10))
+
+#ExternalSource("NestedHelpers.vbhtml", 6)
+
+#End ExternalSource
+End Sub)
+End Function
+
+#ExternalSource("NestedHelpers.vbhtml", 1)
+Public Shared Function Italic(s As String) As Template
+
+#End ExternalSource
+Return New Template(Sub (__razor_helper_writer)
+
+#ExternalSource("NestedHelpers.vbhtml", 1)
+
+ s = s.ToUpper()
+
+#End ExternalSource
+
+#ExternalSource("NestedHelpers.vbhtml", 6)
+
+
+#End ExternalSource
+WriteLiteralTo(__razor_helper_writer, " ")
+WriteLiteralTo(__razor_helper_writer, "<em>")
+
+#ExternalSource("NestedHelpers.vbhtml", 7)
+WriteTo(__razor_helper_writer, Bold(s))
+
+#End ExternalSource
+WriteLiteralTo(__razor_helper_writer, "</em>"&Global.Microsoft.VisualBasic.ChrW(13)&Global.Microsoft.VisualBasic.ChrW(10))
+
+#ExternalSource("NestedHelpers.vbhtml", 8)
+
+#End ExternalSource
+End Sub)
+End Function
+
+Public Sub New()
+MyBase.New
+End Sub
+Public Overrides Sub Execute()
+WriteLiteral(""&Global.Microsoft.VisualBasic.ChrW(13)&Global.Microsoft.VisualBasic.ChrW(10)&Global.Microsoft.VisualBasic.ChrW(13)&Global.Microsoft.VisualBasic.ChrW(10))
+
+
+#ExternalSource("NestedHelpers.vbhtml",10)
+Write(Italic("Hello"))
+
+
+#End ExternalSource
+End Sub
+End Class
+End Namespace
diff --git a/test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Output/NoLinePragmas.vb b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Output/NoLinePragmas.vb
new file mode 100644
index 00000000..fea68676
--- /dev/null
+++ b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Output/NoLinePragmas.vb
@@ -0,0 +1,143 @@
+'------------------------------------------------------------------------------
+' <auto-generated>
+' This code was generated by a tool.
+' Runtime Version:N.N.NNNNN.N
+'
+' Changes to this file may cause incorrect behavior and will be lost if
+' the code is regenerated.
+' </auto-generated>
+'------------------------------------------------------------------------------
+
+Option Strict Off
+Option Explicit On
+
+Imports System
+
+Namespace TestOutput
+Public Class NoLinePragmas
+Public Sub New()
+MyBase.New
+End Sub
+Public Overrides Sub Execute()
+
+ Dim i as Integer = 1
+
+WriteLiteral(""&Global.Microsoft.VisualBasic.ChrW(13)&Global.Microsoft.VisualBasic.ChrW(10)&Global.Microsoft.VisualBasic.ChrW(13)&Global.Microsoft.VisualBasic.ChrW(10))
+
+ While i <= 10
+
+WriteLiteral(" ")
+
+WriteLiteral("<p>Hello from VB.Net, #")
+
+ Write(i)
+
+WriteLiteral("</p>"&Global.Microsoft.VisualBasic.ChrW(13)&Global.Microsoft.VisualBasic.ChrW(10))
+
+ i += 1
+ 'End While
+End While
+
+WriteLiteral(""&Global.Microsoft.VisualBasic.ChrW(13)&Global.Microsoft.VisualBasic.ChrW(10))
+
+ If i = 11 Then
+
+WriteLiteral(" ")
+
+WriteLiteral("<p>We wrote 10 lines!</p>"&Global.Microsoft.VisualBasic.ChrW(13)&Global.Microsoft.VisualBasic.ChrW(10))
+
+ Dim s = "End If"
+End If
+
+WriteLiteral(""&Global.Microsoft.VisualBasic.ChrW(13)&Global.Microsoft.VisualBasic.ChrW(10))
+
+ Select Case i
+ Case 11
+
+WriteLiteral(" ")
+
+WriteLiteral("<p>No really, we wrote 10 lines!</p>"&Global.Microsoft.VisualBasic.ChrW(13)&Global.Microsoft.VisualBasic.ChrW(10))
+
+ Case Else
+
+WriteLiteral(" ")
+
+WriteLiteral("<p>Actually, we didn't...</p>"&Global.Microsoft.VisualBasic.ChrW(13)&Global.Microsoft.VisualBasic.ChrW(10))
+
+End Select
+
+WriteLiteral(""&Global.Microsoft.VisualBasic.ChrW(13)&Global.Microsoft.VisualBasic.ChrW(10))
+
+ For j as Integer = 1 to 10 Step 2
+
+WriteLiteral(" ")
+
+WriteLiteral("<p>Hello again from VB.Net, #")
+
+ Write(j)
+
+WriteLiteral("</p>"&Global.Microsoft.VisualBasic.ChrW(13)&Global.Microsoft.VisualBasic.ChrW(10))
+
+Next
+
+WriteLiteral(""&Global.Microsoft.VisualBasic.ChrW(13)&Global.Microsoft.VisualBasic.ChrW(10))
+
+ Try
+
+WriteLiteral(" ")
+
+WriteLiteral("<p>That time, we wrote 5 lines!</p>"&Global.Microsoft.VisualBasic.ChrW(13)&Global.Microsoft.VisualBasic.ChrW(10))
+
+Catch ex as Exception
+
+WriteLiteral(" ")
+
+WriteLiteral("<p>Oh no! An error occurred: ")
+
+ Write(ex.Message)
+
+WriteLiteral("</p>"&Global.Microsoft.VisualBasic.ChrW(13)&Global.Microsoft.VisualBasic.ChrW(10))
+
+End Try
+
+WriteLiteral(""&Global.Microsoft.VisualBasic.ChrW(13)&Global.Microsoft.VisualBasic.ChrW(10))
+
+ With i
+
+WriteLiteral(" ")
+
+WriteLiteral("<p>i is now ")
+
+ Write(.ToString())
+
+WriteLiteral("</p>"&Global.Microsoft.VisualBasic.ChrW(13)&Global.Microsoft.VisualBasic.ChrW(10))
+
+End With
+
+WriteLiteral(""&Global.Microsoft.VisualBasic.ChrW(13)&Global.Microsoft.VisualBasic.ChrW(10))
+
+ SyncLock New Object()
+
+WriteLiteral(" ")
+
+WriteLiteral("<p>This block is locked, for your security!</p>"&Global.Microsoft.VisualBasic.ChrW(13)&Global.Microsoft.VisualBasic.ChrW(10))
+
+End SyncLock
+
+WriteLiteral(""&Global.Microsoft.VisualBasic.ChrW(13)&Global.Microsoft.VisualBasic.ChrW(10))
+
+ Using New System.IO.MemoryStream()
+
+WriteLiteral(" ")
+
+WriteLiteral("<p>Some random memory stream will be disposed after rendering this block</p>"&Global.Microsoft.VisualBasic.ChrW(13)&Global.Microsoft.VisualBasic.ChrW(10))
+
+End Using
+
+WriteLiteral(""&Global.Microsoft.VisualBasic.ChrW(13)&Global.Microsoft.VisualBasic.ChrW(10))
+
+Write(SyntaxSampleHelpers.CodeForLink(Me))
+
+End Sub
+End Class
+End Namespace
diff --git a/test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Output/Options.vb b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Output/Options.vb
new file mode 100644
index 00000000..7f472d20
--- /dev/null
+++ b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Output/Options.vb
@@ -0,0 +1,28 @@
+'------------------------------------------------------------------------------
+' <auto-generated>
+' This code was generated by a tool.
+' Runtime Version:N.N.NNNNN.N
+'
+' Changes to this file may cause incorrect behavior and will be lost if
+' the code is regenerated.
+' </auto-generated>
+'------------------------------------------------------------------------------
+
+Option Strict On
+Option Explicit Off
+
+Imports System
+
+Namespace TestOutput
+Public Class Options
+Public Sub New()
+MyBase.New
+End Sub
+Public Overrides Sub Execute()
+WriteLiteral(""&Global.Microsoft.VisualBasic.ChrW(13)&Global.Microsoft.VisualBasic.ChrW(10))
+
+WriteLiteral(""&Global.Microsoft.VisualBasic.ChrW(13)&Global.Microsoft.VisualBasic.ChrW(10)&Global.Microsoft.VisualBasic.ChrW(13)&Global.Microsoft.VisualBasic.ChrW(10)&"Hello, World!")
+
+End Sub
+End Class
+End Namespace
diff --git a/test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Output/ParserError.vb b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Output/ParserError.vb
new file mode 100644
index 00000000..dc1c177b
--- /dev/null
+++ b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Output/ParserError.vb
@@ -0,0 +1,31 @@
+'------------------------------------------------------------------------------
+' <auto-generated>
+' This code was generated by a tool.
+' Runtime Version:N.N.NNNNN.N
+'
+' Changes to this file may cause incorrect behavior and will be lost if
+' the code is regenerated.
+' </auto-generated>
+'------------------------------------------------------------------------------
+
+Option Strict Off
+Option Explicit On
+
+Imports System
+
+Namespace TestOutput
+Public Class ParserError
+Public Sub New()
+MyBase.New
+End Sub
+Public Overrides Sub Execute()
+
+#ExternalSource("ParserError.vbhtml",1)
+
+Foo
+'End Code
+
+#End ExternalSource
+End Sub
+End Class
+End Namespace
diff --git a/test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Output/RazorComments.DesignTime.vb b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Output/RazorComments.DesignTime.vb
new file mode 100644
index 00000000..925fc5bb
--- /dev/null
+++ b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Output/RazorComments.DesignTime.vb
@@ -0,0 +1,59 @@
+'------------------------------------------------------------------------------
+' <auto-generated>
+' This code was generated by a tool.
+' Runtime Version:N.N.NNNNN.N
+'
+' Changes to this file may cause incorrect behavior and will be lost if
+' the code is regenerated.
+' </auto-generated>
+'------------------------------------------------------------------------------
+
+Option Strict Off
+Option Explicit On
+
+Imports System
+
+Namespace TestOutput
+Public Class RazorComments
+Private Shared __o As Object
+Public Sub New()
+MyBase.New
+End Sub
+Public Overrides Sub Execute()
+
+#ExternalSource("RazorComments.vbhtml",1)
+
+
+
+#End ExternalSource
+
+#ExternalSource("RazorComments.vbhtml",2)
+
+
+
+#End ExternalSource
+
+#ExternalSource("RazorComments.vbhtml",3)
+ Dim bar As String = "@* bar *@"
+
+#End ExternalSource
+
+#ExternalSource("RazorComments.vbhtml",4)
+ __o = bar
+
+
+#End ExternalSource
+
+#ExternalSource("RazorComments.vbhtml",5)
+__o = a _
+
+#End ExternalSource
+
+#ExternalSource("RazorComments.vbhtml",6)
+ b
+
+
+#End ExternalSource
+End Sub
+End Class
+End Namespace
diff --git a/test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Output/RazorComments.vb b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Output/RazorComments.vb
new file mode 100644
index 00000000..909c96fe
--- /dev/null
+++ b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Output/RazorComments.vb
@@ -0,0 +1,72 @@
+'------------------------------------------------------------------------------
+' <auto-generated>
+' This code was generated by a tool.
+' Runtime Version:N.N.NNNNN.N
+'
+' Changes to this file may cause incorrect behavior and will be lost if
+' the code is regenerated.
+' </auto-generated>
+'------------------------------------------------------------------------------
+
+Option Strict Off
+Option Explicit On
+
+Imports System
+
+Namespace TestOutput
+Public Class RazorComments
+Public Sub New()
+MyBase.New
+End Sub
+Public Overrides Sub Execute()
+WriteLiteral(""&Global.Microsoft.VisualBasic.ChrW(13)&Global.Microsoft.VisualBasic.ChrW(10)&"<p>This should ")
+
+WriteLiteral(" be shown</p>"&Global.Microsoft.VisualBasic.ChrW(13)&Global.Microsoft.VisualBasic.ChrW(10)&Global.Microsoft.VisualBasic.ChrW(13)&Global.Microsoft.VisualBasic.ChrW(10))
+
+
+#ExternalSource("RazorComments.vbhtml",4)
+
+
+
+#End ExternalSource
+
+#ExternalSource("RazorComments.vbhtml",5)
+
+
+
+#End ExternalSource
+WriteLiteral(""&Global.Microsoft.VisualBasic.ChrW(13)&Global.Microsoft.VisualBasic.ChrW(10)&Global.Microsoft.VisualBasic.ChrW(13)&Global.Microsoft.VisualBasic.ChrW(10))
+
+
+#ExternalSource("RazorComments.vbhtml",8)
+ Dim bar As String = "@* bar *@"
+
+#End ExternalSource
+WriteLiteral(""&Global.Microsoft.VisualBasic.ChrW(13)&Global.Microsoft.VisualBasic.ChrW(10)&"<p>But this should show the comment syntax: ")
+
+
+#ExternalSource("RazorComments.vbhtml",9)
+ Write(bar)
+
+
+#End ExternalSource
+WriteLiteral("</p>"&Global.Microsoft.VisualBasic.ChrW(13)&Global.Microsoft.VisualBasic.ChrW(10)&"<p>So should this: ")
+
+WriteLiteral("@* bar *")
+
+WriteLiteral("@</p>"&Global.Microsoft.VisualBasic.ChrW(13)&Global.Microsoft.VisualBasic.ChrW(10)&Global.Microsoft.VisualBasic.ChrW(13)&Global.Microsoft.VisualBasic.ChrW(10))
+
+
+#ExternalSource("RazorComments.vbhtml",12)
+Write(a _
+
+#End ExternalSource
+
+#ExternalSource("RazorComments.vbhtml",12)
+ b)
+
+
+#End ExternalSource
+End Sub
+End Class
+End Namespace
diff --git a/test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Output/ResolveUrl.vb b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Output/ResolveUrl.vb
new file mode 100644
index 00000000..6e876b1d
--- /dev/null
+++ b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Output/ResolveUrl.vb
@@ -0,0 +1,183 @@
+'------------------------------------------------------------------------------
+' <auto-generated>
+' This code was generated by a tool.
+' Runtime Version:N.N.NNNNN.N
+'
+' Changes to this file may cause incorrect behavior and will be lost if
+' the code is regenerated.
+' </auto-generated>
+'------------------------------------------------------------------------------
+
+Option Strict Off
+Option Explicit On
+
+Imports System
+
+Namespace TestOutput
+Public Class ResolveUrl
+Public Sub New()
+MyBase.New
+End Sub
+Public Overrides Sub Execute()
+WriteLiteral("<a")
+
+WriteAttribute("href", Tuple.Create(" href=""", 2), Tuple.Create("""", 14) _
+, Tuple.Create(Tuple.Create("", 9), Tuple.Create(Of System.Object, System.Int32)(Href("~/Foo") _
+, 9), False) _
+)
+
+WriteLiteral(">Foo</a>"&Global.Microsoft.VisualBasic.ChrW(13)&Global.Microsoft.VisualBasic.ChrW(10)&"<a")
+
+WriteAttribute("href", Tuple.Create(" href=""", 27), Tuple.Create("""", 56) _
+, Tuple.Create(Tuple.Create("", 34), Tuple.Create(Of System.Object, System.Int32)(Href("~/Products/") _
+, 34), False) _
+, Tuple.Create(Tuple.Create("", 45), Tuple.Create(Of System.Object, System.Int32)(product.id _
+, 45), False) _
+)
+
+WriteLiteral(">")
+
+
+#ExternalSource("ResolveUrl.vbhtml",2)
+ Write(product.Name)
+
+
+#End ExternalSource
+WriteLiteral("</a>"&Global.Microsoft.VisualBasic.ChrW(13)&Global.Microsoft.VisualBasic.ChrW(10)&"<a")
+
+WriteAttribute("href", Tuple.Create(" href=""", 79), Tuple.Create("""", 115) _
+, Tuple.Create(Tuple.Create("", 86), Tuple.Create(Of System.Object, System.Int32)(Href("~/Products/") _
+, 86), False) _
+, Tuple.Create(Tuple.Create("", 97), Tuple.Create(Of System.Object, System.Int32)(product.id _
+, 97), False) _
+, Tuple.Create(Tuple.Create("", 108), Tuple.Create("/Detail", 108), True) _
+)
+
+WriteLiteral(">Details</a>"&Global.Microsoft.VisualBasic.ChrW(13)&Global.Microsoft.VisualBasic.ChrW(10)&"<a")
+
+WriteAttribute("href", Tuple.Create(" href=""", 132), Tuple.Create("""", 187) _
+, Tuple.Create(Tuple.Create("", 139), Tuple.Create(Of System.Object, System.Int32)(Href("~/A+Really(Crazy),Url.Is:This/") _
+, 139), False) _
+, Tuple.Create(Tuple.Create("", 169), Tuple.Create(Of System.Object, System.Int32)(product.id _
+, 169), False) _
+, Tuple.Create(Tuple.Create("", 180), Tuple.Create("/Detail", 180), True) _
+)
+
+WriteLiteral(">Crazy Url!</a>"&Global.Microsoft.VisualBasic.ChrW(13)&Global.Microsoft.VisualBasic.ChrW(10)&Global.Microsoft.VisualBasic.ChrW(13)&Global.Microsoft.VisualBasic.ChrW(10))
+
+
+#ExternalSource("ResolveUrl.vbhtml",6)
+
+
+
+#End ExternalSource
+WriteLiteral(" ")
+
+WriteLiteral(""&Global.Microsoft.VisualBasic.ChrW(13)&Global.Microsoft.VisualBasic.ChrW(10)&" <a")
+
+WriteAttribute("href", Tuple.Create(" href=""", 237), Tuple.Create("""", 249) _
+, Tuple.Create(Tuple.Create("", 244), Tuple.Create(Of System.Object, System.Int32)(Href("~/Foo") _
+, 244), False) _
+)
+
+WriteLiteral(">Foo</a>"&Global.Microsoft.VisualBasic.ChrW(13)&Global.Microsoft.VisualBasic.ChrW(10)&" <a")
+
+WriteAttribute("href", Tuple.Create(" href=""", 270), Tuple.Create("""", 299) _
+, Tuple.Create(Tuple.Create("", 277), Tuple.Create(Of System.Object, System.Int32)(Href("~/Products/") _
+, 277), False) _
+, Tuple.Create(Tuple.Create("", 288), Tuple.Create(Of System.Object, System.Int32)(product.id _
+, 288), False) _
+)
+
+WriteLiteral(">")
+
+
+#ExternalSource("ResolveUrl.vbhtml",9)
+ Write(product.Name)
+
+
+#End ExternalSource
+WriteLiteral("</a>"&Global.Microsoft.VisualBasic.ChrW(13)&Global.Microsoft.VisualBasic.ChrW(10)&" <a")
+
+WriteAttribute("href", Tuple.Create(" href=""", 330), Tuple.Create("""", 366) _
+, Tuple.Create(Tuple.Create("", 337), Tuple.Create(Of System.Object, System.Int32)(Href("~/Products/") _
+, 337), False) _
+, Tuple.Create(Tuple.Create("", 348), Tuple.Create(Of System.Object, System.Int32)(product.id _
+, 348), False) _
+, Tuple.Create(Tuple.Create("", 359), Tuple.Create("/Detail", 359), True) _
+)
+
+WriteLiteral(">Details</a>"&Global.Microsoft.VisualBasic.ChrW(13)&Global.Microsoft.VisualBasic.ChrW(10)&" <a")
+
+WriteAttribute("href", Tuple.Create(" href=""", 391), Tuple.Create("""", 446) _
+, Tuple.Create(Tuple.Create("", 398), Tuple.Create(Of System.Object, System.Int32)(Href("~/A+Really(Crazy),Url.Is:This/") _
+, 398), False) _
+, Tuple.Create(Tuple.Create("", 428), Tuple.Create(Of System.Object, System.Int32)(product.id _
+, 428), False) _
+, Tuple.Create(Tuple.Create("", 439), Tuple.Create("/Detail", 439), True) _
+)
+
+WriteLiteral(">Crazy Url!</a>"&Global.Microsoft.VisualBasic.ChrW(13)&Global.Microsoft.VisualBasic.ChrW(10)&" ")
+
+WriteLiteral(""&Global.Microsoft.VisualBasic.ChrW(13)&Global.Microsoft.VisualBasic.ChrW(10))
+
+
+#ExternalSource("ResolveUrl.vbhtml",13)
+
+
+#End ExternalSource
+WriteLiteral(""&Global.Microsoft.VisualBasic.ChrW(13)&Global.Microsoft.VisualBasic.ChrW(10)&Global.Microsoft.VisualBasic.ChrW(13)&Global.Microsoft.VisualBasic.ChrW(10))
+
+DefineSection("Foo", Sub ()
+
+WriteLiteral(""&Global.Microsoft.VisualBasic.ChrW(13)&Global.Microsoft.VisualBasic.ChrW(10)&" <a")
+
+WriteAttribute("href", Tuple.Create(" href=""", 509), Tuple.Create("""", 521) _
+, Tuple.Create(Tuple.Create("", 516), Tuple.Create(Of System.Object, System.Int32)(Href("~/Foo") _
+, 516), False) _
+)
+
+WriteLiteral(">Foo</a>"&Global.Microsoft.VisualBasic.ChrW(13)&Global.Microsoft.VisualBasic.ChrW(10)&" <a")
+
+WriteAttribute("href", Tuple.Create(" href=""", 538), Tuple.Create("""", 567) _
+, Tuple.Create(Tuple.Create("", 545), Tuple.Create(Of System.Object, System.Int32)(Href("~/Products/") _
+, 545), False) _
+, Tuple.Create(Tuple.Create("", 556), Tuple.Create(Of System.Object, System.Int32)(product.id _
+, 556), False) _
+)
+
+WriteLiteral(">")
+
+
+#ExternalSource("ResolveUrl.vbhtml",17)
+ Write(product.Name)
+
+
+#End ExternalSource
+WriteLiteral("</a>"&Global.Microsoft.VisualBasic.ChrW(13)&Global.Microsoft.VisualBasic.ChrW(10)&" <a")
+
+WriteAttribute("href", Tuple.Create(" href=""", 594), Tuple.Create("""", 630) _
+, Tuple.Create(Tuple.Create("", 601), Tuple.Create(Of System.Object, System.Int32)(Href("~/Products/") _
+, 601), False) _
+, Tuple.Create(Tuple.Create("", 612), Tuple.Create(Of System.Object, System.Int32)(product.id _
+, 612), False) _
+, Tuple.Create(Tuple.Create("", 623), Tuple.Create("/Detail", 623), True) _
+)
+
+WriteLiteral(">Details</a>"&Global.Microsoft.VisualBasic.ChrW(13)&Global.Microsoft.VisualBasic.ChrW(10)&" <a")
+
+WriteAttribute("href", Tuple.Create(" href=""", 651), Tuple.Create("""", 706) _
+, Tuple.Create(Tuple.Create("", 658), Tuple.Create(Of System.Object, System.Int32)(Href("~/A+Really(Crazy),Url.Is:This/") _
+, 658), False) _
+, Tuple.Create(Tuple.Create("", 688), Tuple.Create(Of System.Object, System.Int32)(product.id _
+, 688), False) _
+, Tuple.Create(Tuple.Create("", 699), Tuple.Create("/Detail", 699), True) _
+)
+
+WriteLiteral(">Crazy Url!</a>"&Global.Microsoft.VisualBasic.ChrW(13)&Global.Microsoft.VisualBasic.ChrW(10))
+
+End Sub)
+
+End Sub
+End Class
+End Namespace
diff --git a/test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Output/Sections.vb b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Output/Sections.vb
new file mode 100644
index 00000000..8b9b34d0
--- /dev/null
+++ b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Output/Sections.vb
@@ -0,0 +1,47 @@
+'------------------------------------------------------------------------------
+' <auto-generated>
+' This code was generated by a tool.
+' Runtime Version:N.N.NNNNN.N
+'
+' Changes to this file may cause incorrect behavior and will be lost if
+' the code is regenerated.
+' </auto-generated>
+'------------------------------------------------------------------------------
+
+Option Strict Off
+Option Explicit On
+
+Imports System
+
+Namespace TestOutput
+Public Class Sections
+Public Sub New()
+MyBase.New
+End Sub
+Public Overrides Sub Execute()
+
+#ExternalSource("Sections.vbhtml",1)
+
+ Layout = "_SectionTestLayout.vbhtml"
+
+
+#End ExternalSource
+WriteLiteral(""&Global.Microsoft.VisualBasic.ChrW(13)&Global.Microsoft.VisualBasic.ChrW(10)&Global.Microsoft.VisualBasic.ChrW(13)&Global.Microsoft.VisualBasic.ChrW(10)&"<div>This is in the Body>"&Global.Microsoft.VisualBasic.ChrW(13)&Global.Microsoft.VisualBasic.ChrW(10)&Global.Microsoft.VisualBasic.ChrW(13)&Global.Microsoft.VisualBasic.ChrW(10))
+
+DefineSection("Section2", Sub ()
+
+WriteLiteral(""&Global.Microsoft.VisualBasic.ChrW(13)&Global.Microsoft.VisualBasic.ChrW(10)&" <div>This is in Section 2</div>"&Global.Microsoft.VisualBasic.ChrW(13)&Global.Microsoft.VisualBasic.ChrW(10))
+
+End Sub)
+
+WriteLiteral(""&Global.Microsoft.VisualBasic.ChrW(13)&Global.Microsoft.VisualBasic.ChrW(10)&Global.Microsoft.VisualBasic.ChrW(13)&Global.Microsoft.VisualBasic.ChrW(10))
+
+DefineSection("Section1", Sub ()
+
+WriteLiteral(""&Global.Microsoft.VisualBasic.ChrW(13)&Global.Microsoft.VisualBasic.ChrW(10)&" <div>This is in Section 1</div>"&Global.Microsoft.VisualBasic.ChrW(13)&Global.Microsoft.VisualBasic.ChrW(10))
+
+End Sub)
+
+End Sub
+End Class
+End Namespace
diff --git a/test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Output/Templates.vb b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Output/Templates.vb
new file mode 100644
index 00000000..8d4bcdd3
--- /dev/null
+++ b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Output/Templates.vb
@@ -0,0 +1,169 @@
+'------------------------------------------------------------------------------
+' <auto-generated>
+' This code was generated by a tool.
+' Runtime Version:N.N.NNNNN.N
+'
+' Changes to this file may cause incorrect behavior and will be lost if
+' the code is regenerated.
+' </auto-generated>
+'------------------------------------------------------------------------------
+
+Option Strict Off
+Option Explicit On
+
+Imports System
+Imports System.Web.Helpers
+
+
+Namespace TestOutput
+Public Class Templates
+
+#ExternalSource("Templates.vbhtml",3)
+
+ Public Function Repeat(times As Integer, template As Func(Of Integer, object)) As HelperResult
+ Return New HelperResult(Sub(writer)
+ For i = 0 to times
+ DirectCast(template(i), HelperResult).WriteTo(writer)
+ Next i
+ End Sub)
+ End Function
+
+#End ExternalSource
+
+Public Sub New()
+MyBase.New
+End Sub
+Public Overrides Sub Execute()
+WriteLiteral(""&Global.Microsoft.VisualBasic.ChrW(13)&Global.Microsoft.VisualBasic.ChrW(10))
+
+WriteLiteral(""&Global.Microsoft.VisualBasic.ChrW(13)&Global.Microsoft.VisualBasic.ChrW(10)&Global.Microsoft.VisualBasic.ChrW(13)&Global.Microsoft.VisualBasic.ChrW(10))
+
+
+#ExternalSource("Templates.vbhtml",13)
+
+ Dim foo As Func(Of Object, Object) =
+
+#End ExternalSource
+WriteLiteral(" ")
+
+WriteLiteral("This works ")
+
+
+#ExternalSource("Templates.vbhtml",14)
+ Write(item)
+
+
+#End ExternalSource
+WriteLiteral("!")
+
+WriteLiteral(""&Global.Microsoft.VisualBasic.ChrW(13)&Global.Microsoft.VisualBasic.ChrW(10))
+
+
+#ExternalSource("Templates.vbhtml",15)
+
+
+#End ExternalSource
+
+#ExternalSource("Templates.vbhtml",15)
+Write(foo("too"))
+
+
+#End ExternalSource
+
+#ExternalSource("Templates.vbhtml",15)
+
+
+
+#End ExternalSource
+WriteLiteral(""&Global.Microsoft.VisualBasic.ChrW(13)&Global.Microsoft.VisualBasic.ChrW(10)&Global.Microsoft.VisualBasic.ChrW(13)&Global.Microsoft.VisualBasic.ChrW(10)&"<ul>"&Global.Microsoft.VisualBasic.ChrW(13)&Global.Microsoft.VisualBasic.ChrW(10))
+
+
+#ExternalSource("Templates.vbhtml",19)
+Write(Repeat(10, Function (item) New Template(Sub (__razor_template_writer)
+
+
+#End ExternalSource
+WriteLiteralTo(__razor_template_writer, "<li>Item #")
+
+
+#ExternalSource("Templates.vbhtml",19)
+WriteTo(__razor_template_writer, item)
+
+
+#End ExternalSource
+WriteLiteralTo(__razor_template_writer, "</li>")
+
+
+#ExternalSource("Templates.vbhtml",19)
+ End Sub)))
+
+
+#End ExternalSource
+WriteLiteral(""&Global.Microsoft.VisualBasic.ChrW(13)&Global.Microsoft.VisualBasic.ChrW(10)&"</ul>"&Global.Microsoft.VisualBasic.ChrW(13)&Global.Microsoft.VisualBasic.ChrW(10)&Global.Microsoft.VisualBasic.ChrW(13)&Global.Microsoft.VisualBasic.ChrW(10)&"<p>"&Global.Microsoft.VisualBasic.ChrW(13)&Global.Microsoft.VisualBasic.ChrW(10))
+
+
+#ExternalSource("Templates.vbhtml",23)
+Write(Repeat(10,
+ Function (item) New Template(Sub (__razor_template_writer)
+
+
+#End ExternalSource
+WriteLiteralTo(__razor_template_writer, " This is line#")
+
+
+#ExternalSource("Templates.vbhtml",24)
+WriteTo(__razor_template_writer, item)
+
+
+#End ExternalSource
+WriteLiteralTo(__razor_template_writer, " of markup<br/>"&Global.Microsoft.VisualBasic.ChrW(13)&Global.Microsoft.VisualBasic.ChrW(10))
+
+
+#ExternalSource("Templates.vbhtml",25)
+End Sub)))
+
+
+#End ExternalSource
+WriteLiteral(""&Global.Microsoft.VisualBasic.ChrW(13)&Global.Microsoft.VisualBasic.ChrW(10)&"</p>"&Global.Microsoft.VisualBasic.ChrW(13)&Global.Microsoft.VisualBasic.ChrW(10)&Global.Microsoft.VisualBasic.ChrW(13)&Global.Microsoft.VisualBasic.ChrW(10)&"<ul>"&Global.Microsoft.VisualBasic.ChrW(13)&Global.Microsoft.VisualBasic.ChrW(10))
+
+WriteLiteral(" ")
+
+
+#ExternalSource("Templates.vbhtml",29)
+Write(Repeat(10, Function (item) New Template(Sub (__razor_template_writer)
+
+
+#End ExternalSource
+WriteLiteralTo(__razor_template_writer, "<li>"&Global.Microsoft.VisualBasic.ChrW(13)&Global.Microsoft.VisualBasic.ChrW(10)&" Item #")
+
+
+#ExternalSource("Templates.vbhtml",30)
+WriteTo(__razor_template_writer, item)
+
+
+#End ExternalSource
+WriteLiteralTo(__razor_template_writer, ""&Global.Microsoft.VisualBasic.ChrW(13)&Global.Microsoft.VisualBasic.ChrW(10))
+
+
+#ExternalSource("Templates.vbhtml",31)
+
+
+#End ExternalSource
+
+#ExternalSource("Templates.vbhtml",31)
+ Dim parent = item
+
+#End ExternalSource
+WriteLiteralTo(__razor_template_writer, ""&Global.Microsoft.VisualBasic.ChrW(13)&Global.Microsoft.VisualBasic.ChrW(10)&" <ul>"&Global.Microsoft.VisualBasic.ChrW(13)&Global.Microsoft.VisualBasic.ChrW(10)&" <li>Child Items... ?</li>"&Global.Microsoft.VisualBasic.ChrW(13)&Global.Microsoft.VisualBasic.ChrW(10)&" </ul>"&Global.Microsoft.VisualBasic.ChrW(13)&Global.Microsoft.VisualBasic.ChrW(10)&" </li>")
+
+
+#ExternalSource("Templates.vbhtml",35)
+ End Sub)))
+
+
+#End ExternalSource
+WriteLiteral(""&Global.Microsoft.VisualBasic.ChrW(13)&Global.Microsoft.VisualBasic.ChrW(10)&"</ul>")
+
+End Sub
+End Class
+End Namespace
diff --git a/test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Output/UnfinishedExpressionInCode.vb b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Output/UnfinishedExpressionInCode.vb
new file mode 100644
index 00000000..a2cccc0f
--- /dev/null
+++ b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Output/UnfinishedExpressionInCode.vb
@@ -0,0 +1,43 @@
+'------------------------------------------------------------------------------
+' <auto-generated>
+' This code was generated by a tool.
+' Runtime Version:N.N.NNNNN.N
+'
+' Changes to this file may cause incorrect behavior and will be lost if
+' the code is regenerated.
+' </auto-generated>
+'------------------------------------------------------------------------------
+
+Option Strict Off
+Option Explicit On
+
+Imports System
+
+Namespace TestOutput
+Public Class UnfinishedExpressionInCode
+Private Shared __o As Object
+Public Sub New()
+MyBase.New
+End Sub
+Public Overrides Sub Execute()
+
+#ExternalSource("UnfinishedExpressionInCode.vbhtml",1)
+
+
+
+#End ExternalSource
+
+#ExternalSource("UnfinishedExpressionInCode.vbhtml",2)
+__o = DateTime.
+
+
+#End ExternalSource
+
+#ExternalSource("UnfinishedExpressionInCode.vbhtml",3)
+
+
+
+#End ExternalSource
+End Sub
+End Class
+End Namespace
diff --git a/test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Source/Blocks.vbhtml b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Source/Blocks.vbhtml
new file mode 100644
index 00000000..e2e02a69
--- /dev/null
+++ b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Source/Blocks.vbhtml
@@ -0,0 +1,46 @@
+@Code
+ Dim i as Integer = 1
+End Code
+
+@While i <= 10
+ @<p>Hello from VB.Net, #@(i)</p>
+ i += 1
+End While
+
+@If i = 11 Then
+ @<p>We wrote 10 lines!</p>
+End If
+
+@Do
+ @<p>Hello again: @i</p>
+ i -= 1
+Loop While i > 0
+
+@Select Case i
+ Case 11
+ @<p>No really, we wrote 10 lines!</p>
+ Case Else
+ @<p>We wrote a bunch more lines too!</p>
+End Select
+
+@For j as Integer = 1 to 10 Step 2
+ @<p>Hello again from VB.Net, #@(j)</p>
+Next j
+
+@Try
+ @<p>That time, we wrote 5 lines!</p>
+Catch ex as Exception
+ @<p>Oh no! An error occurred: @(ex.Message)</p>
+End Try
+
+@With i
+ @<p>i is now @(.ToString())</p>
+End With
+
+@SyncLock New Object()
+ @<p>This block is locked, for your security!</p>
+End SyncLock
+
+@Using New System.IO.MemoryStream()
+ @<p>Some random memory stream will be disposed after rendering this block</p>
+End Using \ No newline at end of file
diff --git a/test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Source/CodeBlock.vbhtml b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Source/CodeBlock.vbhtml
new file mode 100644
index 00000000..c710eaf1
--- /dev/null
+++ b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Source/CodeBlock.vbhtml
@@ -0,0 +1,3 @@
+@Code
+ Test()
+End Code \ No newline at end of file
diff --git a/test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Source/CodeBlockAtEOF.vbhtml b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Source/CodeBlockAtEOF.vbhtml
new file mode 100644
index 00000000..011d09a1
--- /dev/null
+++ b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Source/CodeBlockAtEOF.vbhtml
@@ -0,0 +1,3 @@
+This is markup
+
+@Code \ No newline at end of file
diff --git a/test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Source/ConditionalAttributes.vbhtml b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Source/ConditionalAttributes.vbhtml
new file mode 100644
index 00000000..2e49d75f
--- /dev/null
+++ b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Source/ConditionalAttributes.vbhtml
@@ -0,0 +1,12 @@
+@Code
+ Dim ch = True
+ Dim cls = "bar"
+ @<a href="Foo" />
+ @<p class="@cls" />
+ @<p class="foo @cls" />
+ @<p class="@cls foo" />
+ @<input type="checkbox" checked="@ch" />
+ @<input type="checkbox" checked="foo @ch" />
+ @<p class="@If cls IsNot Nothing Then @cls End If" />
+ @<a href="~/Foo" />
+End Code \ No newline at end of file
diff --git a/test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Source/DesignTime.vbhtml b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Source/DesignTime.vbhtml
new file mode 100644
index 00000000..cfd33e71
--- /dev/null
+++ b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Source/DesignTime.vbhtml
@@ -0,0 +1,21 @@
+<div>
+ @For i = 1 to 10
+@<p>This is item #@i</p>
+ Next
+</div>
+
+<p>
+@(Foo(Bar.Baz))
+@Foo(@@<p>Bar @baz Biz</p>)
+</p>
+
+@Section Footer
+ <p>Foo</p>
+ @bar
+End Section
+
+@Helper Foo()
+ If True Then
+ @<p>Foo</p>
+ End If
+End Helper \ No newline at end of file
diff --git a/test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Source/EmptyExplicitExpression.vbhtml b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Source/EmptyExplicitExpression.vbhtml
new file mode 100644
index 00000000..6790c7eb
--- /dev/null
+++ b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Source/EmptyExplicitExpression.vbhtml
@@ -0,0 +1,3 @@
+This is markup
+
+@() \ No newline at end of file
diff --git a/test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Source/EmptyImplicitExpression.vbhtml b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Source/EmptyImplicitExpression.vbhtml
new file mode 100644
index 00000000..021306da
--- /dev/null
+++ b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Source/EmptyImplicitExpression.vbhtml
@@ -0,0 +1,3 @@
+This is markup
+
+@! \ No newline at end of file
diff --git a/test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Source/EmptyImplicitExpressionInCode.vbhtml b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Source/EmptyImplicitExpressionInCode.vbhtml
new file mode 100644
index 00000000..deb98d53
--- /dev/null
+++ b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Source/EmptyImplicitExpressionInCode.vbhtml
@@ -0,0 +1,3 @@
+@Code
+ @
+End Code \ No newline at end of file
diff --git a/test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Source/ExplicitExpression.vbhtml b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Source/ExplicitExpression.vbhtml
new file mode 100644
index 00000000..b65eee56
--- /dev/null
+++ b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Source/ExplicitExpression.vbhtml
@@ -0,0 +1 @@
+@(Foo(Bar.Baz)) \ No newline at end of file
diff --git a/test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Source/ExplicitExpressionAtEOF.vbhtml b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Source/ExplicitExpressionAtEOF.vbhtml
new file mode 100644
index 00000000..a0fdfc9a
--- /dev/null
+++ b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Source/ExplicitExpressionAtEOF.vbhtml
@@ -0,0 +1,3 @@
+This is markup
+
+@( \ No newline at end of file
diff --git a/test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Source/ExpressionsInCode.vbhtml b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Source/ExpressionsInCode.vbhtml
new file mode 100644
index 00000000..3416ee9a
--- /dev/null
+++ b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Source/ExpressionsInCode.vbhtml
@@ -0,0 +1,16 @@
+@Code
+ Dim foo As Object = Nothing
+ Dim bar as String = "Foo"
+End Code
+
+@If foo IsNot Nothing Then
+ @foo
+Else
+ @<p>Foo is Null!</p>
+End If
+
+<p>
+@If Not String.IsNullOrEmpty(bar) Then
+ @(bar.Replace("F", "B"))
+End If
+</p> \ No newline at end of file
diff --git a/test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Source/FunctionsBlock.vbhtml b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Source/FunctionsBlock.vbhtml
new file mode 100644
index 00000000..bcb78211
--- /dev/null
+++ b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Source/FunctionsBlock.vbhtml
@@ -0,0 +1,12 @@
+@Functions
+
+End Functions
+
+@Functions
+ Private _rand as New Random()
+ Private Function RandomInt() as Integer
+ Return _rand.Next()
+ End Function
+End Functions
+
+Here's a random number: @RandomInt() \ No newline at end of file
diff --git a/test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Source/Helpers.vbhtml b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Source/Helpers.vbhtml
new file mode 100644
index 00000000..6fead0cc
--- /dev/null
+++ b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Source/Helpers.vbhtml
@@ -0,0 +1,11 @@
+@Helper Bold(s as String)
+ s = s.ToUpper()
+ @<strong>@s</strong>
+End Helper
+
+@Helper Italic(s as String)
+ s = s.ToUpper()
+ @<em>@s</em>
+End Helper
+
+@Bold("Hello") \ No newline at end of file
diff --git a/test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Source/HelpersMissingCloseParen.vbhtml b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Source/HelpersMissingCloseParen.vbhtml
new file mode 100644
index 00000000..d81d24b2
--- /dev/null
+++ b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Source/HelpersMissingCloseParen.vbhtml
@@ -0,0 +1,8 @@
+@Helper Bold(s as String)
+ s = s.ToUpper()
+ @<strong>@s</strong>
+End Helper
+
+@Helper Italic(s as String
+
+@Bold("Hello") \ No newline at end of file
diff --git a/test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Source/HelpersMissingName.vbhtml b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Source/HelpersMissingName.vbhtml
new file mode 100644
index 00000000..36ec5a7e
--- /dev/null
+++ b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Source/HelpersMissingName.vbhtml
@@ -0,0 +1 @@
+@Helper \ No newline at end of file
diff --git a/test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Source/HelpersMissingOpenParen.vbhtml b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Source/HelpersMissingOpenParen.vbhtml
new file mode 100644
index 00000000..145bda68
--- /dev/null
+++ b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Source/HelpersMissingOpenParen.vbhtml
@@ -0,0 +1,8 @@
+@Helper Bold(s as String)
+ s = s.ToUpper()
+ @<strong>@s</strong>
+End Helper
+
+@Helper Italic
+
+@Bold("Hello") \ No newline at end of file
diff --git a/test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Source/ImplicitExpression.vbhtml b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Source/ImplicitExpression.vbhtml
new file mode 100644
index 00000000..fb6f5281
--- /dev/null
+++ b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Source/ImplicitExpression.vbhtml
@@ -0,0 +1,5 @@
+@For i = 1 To 10
+ @<p>This is item #@i</p>
+Next
+
+@SyntaxSampleHelpers.CodeForLink(Me) \ No newline at end of file
diff --git a/test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Source/ImplicitExpressionAtEOF.vbhtml b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Source/ImplicitExpressionAtEOF.vbhtml
new file mode 100644
index 00000000..365d20e0
--- /dev/null
+++ b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Source/ImplicitExpressionAtEOF.vbhtml
@@ -0,0 +1,3 @@
+This is markup
+
+@ \ No newline at end of file
diff --git a/test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Source/Imports.vbhtml b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Source/Imports.vbhtml
new file mode 100644
index 00000000..da3f0ec1
--- /dev/null
+++ b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Source/Imports.vbhtml
@@ -0,0 +1,6 @@
+@Imports System.IO
+@Imports Foo = System.Text.Encoding
+@Imports System
+
+<p>Path's full type name is @GetType(Path).FullName</p>
+<p>Foo's actual full type name is @GetType(Foo).FullName</p> \ No newline at end of file
diff --git a/test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Source/Inherits.vbhtml b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Source/Inherits.vbhtml
new file mode 100644
index 00000000..cc0a2d2a
--- /dev/null
+++ b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Source/Inherits.vbhtml
@@ -0,0 +1 @@
+@Inherits System.Web.WebPages.WebPage
diff --git a/test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Source/Instrumented.vbhtml b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Source/Instrumented.vbhtml
new file mode 100644
index 00000000..510aae4b
--- /dev/null
+++ b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Source/Instrumented.vbhtml
@@ -0,0 +1,53 @@
+@Helper Strong(s As String)
+ @<strong>@s</strong>
+End Helper
+
+@Code
+ Dim i As Integer = 1
+ Dim foo = @@<p>Foo</p>
+ @:Hello, World!
+ @<p>Hello, World!</p>
+End Code
+
+@While i <= 10
+ @<p>Hello from VB.Net, #@(i)</p>
+ i += 1
+End While
+
+@If i = 11 Then
+ @<p>We wrote 10 lines!</p>
+End If
+
+@Do
+ @<p>Hello again: @i</p>
+ i -= 1
+Loop While i > 0
+
+@Select Case i
+ Case 11
+ @<p>No really, we wrote 10 lines!</p>
+ Case Else
+ @<p>We wrote a bunch more lines too!</p>
+End Select
+
+@For j as Integer = 1 to 10 Step 2
+ @<p>Hello again from VB.Net, #@(j)</p>
+Next j
+
+@Try
+ @<p>That time, we wrote 5 lines!</p>
+Catch ex as Exception
+ @<p>Oh no! An error occurred: @(ex.Message)</p>
+End Try
+
+@With i
+ @<p>i is now @(.ToString())</p>
+End With
+
+@SyncLock New Object()
+ @<p>This block is locked, for your security!</p>
+End SyncLock
+
+@Using New System.IO.MemoryStream()
+ @<p>Some random memory stream will be disposed after rendering this block</p>
+End Using \ No newline at end of file
diff --git a/test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Source/LayoutDirective.vbhtml b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Source/LayoutDirective.vbhtml
new file mode 100644
index 00000000..58904f11
--- /dev/null
+++ b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Source/LayoutDirective.vbhtml
@@ -0,0 +1 @@
+@Layout ~/Foo/Bar/Baz \ No newline at end of file
diff --git a/test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Source/MarkupInCodeBlock.vbhtml b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Source/MarkupInCodeBlock.vbhtml
new file mode 100644
index 00000000..83289134
--- /dev/null
+++ b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Source/MarkupInCodeBlock.vbhtml
@@ -0,0 +1,5 @@
+@Code
+ For i = 1 To 10
+ @<p>Hello from VB.Net, #@(i.ToString())</p>
+ Next i
+End Code
diff --git a/test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Source/NestedCodeBlocks.vbhtml b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Source/NestedCodeBlocks.vbhtml
new file mode 100644
index 00000000..83f518ac
--- /dev/null
+++ b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Source/NestedCodeBlocks.vbhtml
@@ -0,0 +1,4 @@
+@If True Then
+ @If True Then
+ End If
+End If \ No newline at end of file
diff --git a/test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Source/NestedHelpers.vbhtml b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Source/NestedHelpers.vbhtml
new file mode 100644
index 00000000..4c52502b
--- /dev/null
+++ b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Source/NestedHelpers.vbhtml
@@ -0,0 +1,10 @@
+@Helper Italic(s As String)
+ s = s.ToUpper()
+ @Helper Bold(s As String)
+ s = s.ToUpper()
+ @<strong>@s</strong>
+ End Helper
+ @<em>@Bold(s)</em>
+End Helper
+
+@Italic("Hello") \ No newline at end of file
diff --git a/test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Source/NoLinePragmas.vbhtml b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Source/NoLinePragmas.vbhtml
new file mode 100644
index 00000000..4e9df788
--- /dev/null
+++ b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Source/NoLinePragmas.vbhtml
@@ -0,0 +1,45 @@
+@Code
+ Dim i as Integer = 1
+End Code
+
+@While i <= 10
+ @<p>Hello from VB.Net, #@(i)</p>
+ i += 1
+ 'End While
+End While
+
+@If i = 11 Then
+ @<p>We wrote 10 lines!</p>
+ Dim s = "End If"
+End If
+
+@Select Case i
+ Case 11
+ @<p>No really, we wrote 10 lines!</p>
+ Case Else
+ @<p>Actually, we didn't...</p>
+End Select
+
+@For j as Integer = 1 to 10 Step 2
+ @<p>Hello again from VB.Net, #@(j)</p>
+Next
+
+@Try
+ @<p>That time, we wrote 5 lines!</p>
+Catch ex as Exception
+ @<p>Oh no! An error occurred: @(ex.Message)</p>
+End Try
+
+@With i
+ @<p>i is now @(.ToString())</p>
+End With
+
+@SyncLock New Object()
+ @<p>This block is locked, for your security!</p>
+End SyncLock
+
+@Using New System.IO.MemoryStream()
+ @<p>Some random memory stream will be disposed after rendering this block</p>
+End Using
+
+@SyntaxSampleHelpers.CodeForLink(Me) \ No newline at end of file
diff --git a/test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Source/Options.vbhtml b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Source/Options.vbhtml
new file mode 100644
index 00000000..0cd49aa3
--- /dev/null
+++ b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Source/Options.vbhtml
@@ -0,0 +1,4 @@
+@Option Strict On
+@Option Explicit Off
+
+Hello, World! \ No newline at end of file
diff --git a/test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Source/ParserError.vbhtml b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Source/ParserError.vbhtml
new file mode 100644
index 00000000..2d2e4227
--- /dev/null
+++ b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Source/ParserError.vbhtml
@@ -0,0 +1,3 @@
+@Code
+Foo
+'End Code \ No newline at end of file
diff --git a/test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Source/RazorComments.vbhtml b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Source/RazorComments.vbhtml
new file mode 100644
index 00000000..7c638d63
--- /dev/null
+++ b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Source/RazorComments.vbhtml
@@ -0,0 +1,12 @@
+@*This is not going to be rendered*@
+<p>This should @* not *@ be shown</p>
+
+@Code
+ @* Throw new Exception("Oh no!") *@
+End Code
+
+@Code Dim bar As String = "@* bar *@" End Code
+<p>But this should show the comment syntax: @bar</p>
+<p>So should this: @@* bar *@@</p>
+
+@(a@**@b) \ No newline at end of file
diff --git a/test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Source/ResolveUrl.vbhtml b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Source/ResolveUrl.vbhtml
new file mode 100644
index 00000000..dc20f644
--- /dev/null
+++ b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Source/ResolveUrl.vbhtml
@@ -0,0 +1,20 @@
+<a href="~/Foo">Foo</a>
+<a href="~/Products/@product.id">@product.Name</a>
+<a href="~/Products/@product.id/Detail">Details</a>
+<a href="~/A+Really(Crazy),Url.Is:This/@product.id/Detail">Crazy Url!</a>
+
+@Code
+ @<text>
+ <a href="~/Foo">Foo</a>
+ <a href="~/Products/@product.id">@product.Name</a>
+ <a href="~/Products/@product.id/Detail">Details</a>
+ <a href="~/A+Really(Crazy),Url.Is:This/@product.id/Detail">Crazy Url!</a>
+ </text>
+End Code
+
+@Section Foo
+ <a href="~/Foo">Foo</a>
+ <a href="~/Products/@product.id">@product.Name</a>
+ <a href="~/Products/@product.id/Detail">Details</a>
+ <a href="~/A+Really(Crazy),Url.Is:This/@product.id/Detail">Crazy Url!</a>
+End Section \ No newline at end of file
diff --git a/test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Source/Sections.vbhtml b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Source/Sections.vbhtml
new file mode 100644
index 00000000..ad05f5cd
--- /dev/null
+++ b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Source/Sections.vbhtml
@@ -0,0 +1,13 @@
+@Code
+ Layout = "_SectionTestLayout.vbhtml"
+End Code
+
+<div>This is in the Body>
+
+@Section Section2
+ <div>This is in Section 2</div>
+End Section
+
+@Section Section1
+ <div>This is in Section 1</div>
+End Section \ No newline at end of file
diff --git a/test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Source/Templates.vbhtml b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Source/Templates.vbhtml
new file mode 100644
index 00000000..80e17703
--- /dev/null
+++ b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Source/Templates.vbhtml
@@ -0,0 +1,36 @@
+@Imports System.Web.Helpers
+
+@Functions
+ Public Function Repeat(times As Integer, template As Func(Of Integer, object)) As HelperResult
+ Return New HelperResult(Sub(writer)
+ For i = 0 to times
+ DirectCast(template(i), HelperResult).WriteTo(writer)
+ Next i
+ End Sub)
+ End Function
+End Functions
+
+@Code
+ Dim foo As Func(Of Object, Object) = @<text>This works @item!</text>
+ @foo("too")
+End Code
+
+<ul>
+@(Repeat(10, @@<li>Item #@item</li>))
+</ul>
+
+<p>
+@Repeat(10,
+ @@: This is line#@item of markup<br/>
+)
+</p>
+
+<ul>
+ @Repeat(10, @@<li>
+ Item #@item
+ @Code Dim parent = item End Code
+ <ul>
+ <li>Child Items... ?</li>
+ </ul>
+ </li>)
+</ul> \ No newline at end of file
diff --git a/test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Source/UnfinishedExpressionInCode.vbhtml b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Source/UnfinishedExpressionInCode.vbhtml
new file mode 100644
index 00000000..4b4acb3b
--- /dev/null
+++ b/test/System.Web.Razor.Test/TestFiles/CodeGenerator/VB/Source/UnfinishedExpressionInCode.vbhtml
@@ -0,0 +1,3 @@
+@Code
+@DateTime.
+End Code \ No newline at end of file
diff --git a/test/System.Web.Razor.Test/TestFiles/DesignTime/Simple.cshtml b/test/System.Web.Razor.Test/TestFiles/DesignTime/Simple.cshtml
new file mode 100644
index 00000000..b50db22f
--- /dev/null
+++ b/test/System.Web.Razor.Test/TestFiles/DesignTime/Simple.cshtml
@@ -0,0 +1,16 @@
+@{
+ string hello = "Hello, World";
+}
+
+<html>
+ <head>
+ <title>Simple Page</title>
+ </head>
+ <body>
+ <h1>Simple Page</h1>
+ <p>@hello</p>
+ <p>
+ @foreach(char c in hello) {@c}
+ </p>
+ </body>
+</html> \ No newline at end of file
diff --git a/test/System.Web.Razor.Test/TestFiles/DesignTime/Simple.txt b/test/System.Web.Razor.Test/TestFiles/DesignTime/Simple.txt
new file mode 100644
index 00000000..4eaa2321
--- /dev/null
+++ b/test/System.Web.Razor.Test/TestFiles/DesignTime/Simple.txt
@@ -0,0 +1,60 @@
+//------------------------------------------------------------------------------
+// <auto-generated>
+// This code was generated by a tool.
+// Runtime Version:N.N.NNNNN.N
+//
+// Changes to this file may cause incorrect behavior and will be lost if
+// the code is regenerated.
+// </auto-generated>
+//------------------------------------------------------------------------------
+
+namespace Razor {
+
+
+ public class @__CompiledTemplate {
+
+ private static object @__o;
+
+#line hidden
+
+ public @__CompiledTemplate() {
+ }
+
+ public override void Execute() {
+
+ #line 1 "C:\This\Path\Is\Just\For\Line\Pragmas.cshtml"
+
+ string hello = "Hello, World";
+
+
+ #line default
+ #line hidden
+
+ #line 2 "C:\This\Path\Is\Just\For\Line\Pragmas.cshtml"
+ __o = hello;
+
+
+ #line default
+ #line hidden
+
+ #line 3 "C:\This\Path\Is\Just\For\Line\Pragmas.cshtml"
+ foreach(char c in hello) {
+
+ #line default
+ #line hidden
+
+ #line 4 "C:\This\Path\Is\Just\For\Line\Pragmas.cshtml"
+ __o = c;
+
+
+ #line default
+ #line hidden
+
+ #line 5 "C:\This\Path\Is\Just\For\Line\Pragmas.cshtml"
+ }
+
+ #line default
+ #line hidden
+ }
+ }
+}
diff --git a/test/System.Web.Razor.Test/TestFiles/nested-1000.html b/test/System.Web.Razor.Test/TestFiles/nested-1000.html
new file mode 100644
index 00000000..3c35bdbc
--- /dev/null
+++ b/test/System.Web.Razor.Test/TestFiles/nested-1000.html
@@ -0,0 +1,2002 @@
+<outer>
+<elem-1>
+<elem-2>
+<elem-3>
+<elem-4>
+<elem-5>
+<elem-6>
+<elem-7>
+<elem-8>
+<elem-9>
+<elem-10>
+<elem-11>
+<elem-12>
+<elem-13>
+<elem-14>
+<elem-15>
+<elem-16>
+<elem-17>
+<elem-18>
+<elem-19>
+<elem-20>
+<elem-21>
+<elem-22>
+<elem-23>
+<elem-24>
+<elem-25>
+<elem-26>
+<elem-27>
+<elem-28>
+<elem-29>
+<elem-30>
+<elem-31>
+<elem-32>
+<elem-33>
+<elem-34>
+<elem-35>
+<elem-36>
+<elem-37>
+<elem-38>
+<elem-39>
+<elem-40>
+<elem-41>
+<elem-42>
+<elem-43>
+<elem-44>
+<elem-45>
+<elem-46>
+<elem-47>
+<elem-48>
+<elem-49>
+<elem-50>
+<elem-51>
+<elem-52>
+<elem-53>
+<elem-54>
+<elem-55>
+<elem-56>
+<elem-57>
+<elem-58>
+<elem-59>
+<elem-60>
+<elem-61>
+<elem-62>
+<elem-63>
+<elem-64>
+<elem-65>
+<elem-66>
+<elem-67>
+<elem-68>
+<elem-69>
+<elem-70>
+<elem-71>
+<elem-72>
+<elem-73>
+<elem-74>
+<elem-75>
+<elem-76>
+<elem-77>
+<elem-78>
+<elem-79>
+<elem-80>
+<elem-81>
+<elem-82>
+<elem-83>
+<elem-84>
+<elem-85>
+<elem-86>
+<elem-87>
+<elem-88>
+<elem-89>
+<elem-90>
+<elem-91>
+<elem-92>
+<elem-93>
+<elem-94>
+<elem-95>
+<elem-96>
+<elem-97>
+<elem-98>
+<elem-99>
+<elem-100>
+<elem-101>
+<elem-102>
+<elem-103>
+<elem-104>
+<elem-105>
+<elem-106>
+<elem-107>
+<elem-108>
+<elem-109>
+<elem-110>
+<elem-111>
+<elem-112>
+<elem-113>
+<elem-114>
+<elem-115>
+<elem-116>
+<elem-117>
+<elem-118>
+<elem-119>
+<elem-120>
+<elem-121>
+<elem-122>
+<elem-123>
+<elem-124>
+<elem-125>
+<elem-126>
+<elem-127>
+<elem-128>
+<elem-129>
+<elem-130>
+<elem-131>
+<elem-132>
+<elem-133>
+<elem-134>
+<elem-135>
+<elem-136>
+<elem-137>
+<elem-138>
+<elem-139>
+<elem-140>
+<elem-141>
+<elem-142>
+<elem-143>
+<elem-144>
+<elem-145>
+<elem-146>
+<elem-147>
+<elem-148>
+<elem-149>
+<elem-150>
+<elem-151>
+<elem-152>
+<elem-153>
+<elem-154>
+<elem-155>
+<elem-156>
+<elem-157>
+<elem-158>
+<elem-159>
+<elem-160>
+<elem-161>
+<elem-162>
+<elem-163>
+<elem-164>
+<elem-165>
+<elem-166>
+<elem-167>
+<elem-168>
+<elem-169>
+<elem-170>
+<elem-171>
+<elem-172>
+<elem-173>
+<elem-174>
+<elem-175>
+<elem-176>
+<elem-177>
+<elem-178>
+<elem-179>
+<elem-180>
+<elem-181>
+<elem-182>
+<elem-183>
+<elem-184>
+<elem-185>
+<elem-186>
+<elem-187>
+<elem-188>
+<elem-189>
+<elem-190>
+<elem-191>
+<elem-192>
+<elem-193>
+<elem-194>
+<elem-195>
+<elem-196>
+<elem-197>
+<elem-198>
+<elem-199>
+<elem-200>
+<elem-201>
+<elem-202>
+<elem-203>
+<elem-204>
+<elem-205>
+<elem-206>
+<elem-207>
+<elem-208>
+<elem-209>
+<elem-210>
+<elem-211>
+<elem-212>
+<elem-213>
+<elem-214>
+<elem-215>
+<elem-216>
+<elem-217>
+<elem-218>
+<elem-219>
+<elem-220>
+<elem-221>
+<elem-222>
+<elem-223>
+<elem-224>
+<elem-225>
+<elem-226>
+<elem-227>
+<elem-228>
+<elem-229>
+<elem-230>
+<elem-231>
+<elem-232>
+<elem-233>
+<elem-234>
+<elem-235>
+<elem-236>
+<elem-237>
+<elem-238>
+<elem-239>
+<elem-240>
+<elem-241>
+<elem-242>
+<elem-243>
+<elem-244>
+<elem-245>
+<elem-246>
+<elem-247>
+<elem-248>
+<elem-249>
+<elem-250>
+<elem-251>
+<elem-252>
+<elem-253>
+<elem-254>
+<elem-255>
+<elem-256>
+<elem-257>
+<elem-258>
+<elem-259>
+<elem-260>
+<elem-261>
+<elem-262>
+<elem-263>
+<elem-264>
+<elem-265>
+<elem-266>
+<elem-267>
+<elem-268>
+<elem-269>
+<elem-270>
+<elem-271>
+<elem-272>
+<elem-273>
+<elem-274>
+<elem-275>
+<elem-276>
+<elem-277>
+<elem-278>
+<elem-279>
+<elem-280>
+<elem-281>
+<elem-282>
+<elem-283>
+<elem-284>
+<elem-285>
+<elem-286>
+<elem-287>
+<elem-288>
+<elem-289>
+<elem-290>
+<elem-291>
+<elem-292>
+<elem-293>
+<elem-294>
+<elem-295>
+<elem-296>
+<elem-297>
+<elem-298>
+<elem-299>
+<elem-300>
+<elem-301>
+<elem-302>
+<elem-303>
+<elem-304>
+<elem-305>
+<elem-306>
+<elem-307>
+<elem-308>
+<elem-309>
+<elem-310>
+<elem-311>
+<elem-312>
+<elem-313>
+<elem-314>
+<elem-315>
+<elem-316>
+<elem-317>
+<elem-318>
+<elem-319>
+<elem-320>
+<elem-321>
+<elem-322>
+<elem-323>
+<elem-324>
+<elem-325>
+<elem-326>
+<elem-327>
+<elem-328>
+<elem-329>
+<elem-330>
+<elem-331>
+<elem-332>
+<elem-333>
+<elem-334>
+<elem-335>
+<elem-336>
+<elem-337>
+<elem-338>
+<elem-339>
+<elem-340>
+<elem-341>
+<elem-342>
+<elem-343>
+<elem-344>
+<elem-345>
+<elem-346>
+<elem-347>
+<elem-348>
+<elem-349>
+<elem-350>
+<elem-351>
+<elem-352>
+<elem-353>
+<elem-354>
+<elem-355>
+<elem-356>
+<elem-357>
+<elem-358>
+<elem-359>
+<elem-360>
+<elem-361>
+<elem-362>
+<elem-363>
+<elem-364>
+<elem-365>
+<elem-366>
+<elem-367>
+<elem-368>
+<elem-369>
+<elem-370>
+<elem-371>
+<elem-372>
+<elem-373>
+<elem-374>
+<elem-375>
+<elem-376>
+<elem-377>
+<elem-378>
+<elem-379>
+<elem-380>
+<elem-381>
+<elem-382>
+<elem-383>
+<elem-384>
+<elem-385>
+<elem-386>
+<elem-387>
+<elem-388>
+<elem-389>
+<elem-390>
+<elem-391>
+<elem-392>
+<elem-393>
+<elem-394>
+<elem-395>
+<elem-396>
+<elem-397>
+<elem-398>
+<elem-399>
+<elem-400>
+<elem-401>
+<elem-402>
+<elem-403>
+<elem-404>
+<elem-405>
+<elem-406>
+<elem-407>
+<elem-408>
+<elem-409>
+<elem-410>
+<elem-411>
+<elem-412>
+<elem-413>
+<elem-414>
+<elem-415>
+<elem-416>
+<elem-417>
+<elem-418>
+<elem-419>
+<elem-420>
+<elem-421>
+<elem-422>
+<elem-423>
+<elem-424>
+<elem-425>
+<elem-426>
+<elem-427>
+<elem-428>
+<elem-429>
+<elem-430>
+<elem-431>
+<elem-432>
+<elem-433>
+<elem-434>
+<elem-435>
+<elem-436>
+<elem-437>
+<elem-438>
+<elem-439>
+<elem-440>
+<elem-441>
+<elem-442>
+<elem-443>
+<elem-444>
+<elem-445>
+<elem-446>
+<elem-447>
+<elem-448>
+<elem-449>
+<elem-450>
+<elem-451>
+<elem-452>
+<elem-453>
+<elem-454>
+<elem-455>
+<elem-456>
+<elem-457>
+<elem-458>
+<elem-459>
+<elem-460>
+<elem-461>
+<elem-462>
+<elem-463>
+<elem-464>
+<elem-465>
+<elem-466>
+<elem-467>
+<elem-468>
+<elem-469>
+<elem-470>
+<elem-471>
+<elem-472>
+<elem-473>
+<elem-474>
+<elem-475>
+<elem-476>
+<elem-477>
+<elem-478>
+<elem-479>
+<elem-480>
+<elem-481>
+<elem-482>
+<elem-483>
+<elem-484>
+<elem-485>
+<elem-486>
+<elem-487>
+<elem-488>
+<elem-489>
+<elem-490>
+<elem-491>
+<elem-492>
+<elem-493>
+<elem-494>
+<elem-495>
+<elem-496>
+<elem-497>
+<elem-498>
+<elem-499>
+<elem-500>
+<elem-501>
+<elem-502>
+<elem-503>
+<elem-504>
+<elem-505>
+<elem-506>
+<elem-507>
+<elem-508>
+<elem-509>
+<elem-510>
+<elem-511>
+<elem-512>
+<elem-513>
+<elem-514>
+<elem-515>
+<elem-516>
+<elem-517>
+<elem-518>
+<elem-519>
+<elem-520>
+<elem-521>
+<elem-522>
+<elem-523>
+<elem-524>
+<elem-525>
+<elem-526>
+<elem-527>
+<elem-528>
+<elem-529>
+<elem-530>
+<elem-531>
+<elem-532>
+<elem-533>
+<elem-534>
+<elem-535>
+<elem-536>
+<elem-537>
+<elem-538>
+<elem-539>
+<elem-540>
+<elem-541>
+<elem-542>
+<elem-543>
+<elem-544>
+<elem-545>
+<elem-546>
+<elem-547>
+<elem-548>
+<elem-549>
+<elem-550>
+<elem-551>
+<elem-552>
+<elem-553>
+<elem-554>
+<elem-555>
+<elem-556>
+<elem-557>
+<elem-558>
+<elem-559>
+<elem-560>
+<elem-561>
+<elem-562>
+<elem-563>
+<elem-564>
+<elem-565>
+<elem-566>
+<elem-567>
+<elem-568>
+<elem-569>
+<elem-570>
+<elem-571>
+<elem-572>
+<elem-573>
+<elem-574>
+<elem-575>
+<elem-576>
+<elem-577>
+<elem-578>
+<elem-579>
+<elem-580>
+<elem-581>
+<elem-582>
+<elem-583>
+<elem-584>
+<elem-585>
+<elem-586>
+<elem-587>
+<elem-588>
+<elem-589>
+<elem-590>
+<elem-591>
+<elem-592>
+<elem-593>
+<elem-594>
+<elem-595>
+<elem-596>
+<elem-597>
+<elem-598>
+<elem-599>
+<elem-600>
+<elem-601>
+<elem-602>
+<elem-603>
+<elem-604>
+<elem-605>
+<elem-606>
+<elem-607>
+<elem-608>
+<elem-609>
+<elem-610>
+<elem-611>
+<elem-612>
+<elem-613>
+<elem-614>
+<elem-615>
+<elem-616>
+<elem-617>
+<elem-618>
+<elem-619>
+<elem-620>
+<elem-621>
+<elem-622>
+<elem-623>
+<elem-624>
+<elem-625>
+<elem-626>
+<elem-627>
+<elem-628>
+<elem-629>
+<elem-630>
+<elem-631>
+<elem-632>
+<elem-633>
+<elem-634>
+<elem-635>
+<elem-636>
+<elem-637>
+<elem-638>
+<elem-639>
+<elem-640>
+<elem-641>
+<elem-642>
+<elem-643>
+<elem-644>
+<elem-645>
+<elem-646>
+<elem-647>
+<elem-648>
+<elem-649>
+<elem-650>
+<elem-651>
+<elem-652>
+<elem-653>
+<elem-654>
+<elem-655>
+<elem-656>
+<elem-657>
+<elem-658>
+<elem-659>
+<elem-660>
+<elem-661>
+<elem-662>
+<elem-663>
+<elem-664>
+<elem-665>
+<elem-666>
+<elem-667>
+<elem-668>
+<elem-669>
+<elem-670>
+<elem-671>
+<elem-672>
+<elem-673>
+<elem-674>
+<elem-675>
+<elem-676>
+<elem-677>
+<elem-678>
+<elem-679>
+<elem-680>
+<elem-681>
+<elem-682>
+<elem-683>
+<elem-684>
+<elem-685>
+<elem-686>
+<elem-687>
+<elem-688>
+<elem-689>
+<elem-690>
+<elem-691>
+<elem-692>
+<elem-693>
+<elem-694>
+<elem-695>
+<elem-696>
+<elem-697>
+<elem-698>
+<elem-699>
+<elem-700>
+<elem-701>
+<elem-702>
+<elem-703>
+<elem-704>
+<elem-705>
+<elem-706>
+<elem-707>
+<elem-708>
+<elem-709>
+<elem-710>
+<elem-711>
+<elem-712>
+<elem-713>
+<elem-714>
+<elem-715>
+<elem-716>
+<elem-717>
+<elem-718>
+<elem-719>
+<elem-720>
+<elem-721>
+<elem-722>
+<elem-723>
+<elem-724>
+<elem-725>
+<elem-726>
+<elem-727>
+<elem-728>
+<elem-729>
+<elem-730>
+<elem-731>
+<elem-732>
+<elem-733>
+<elem-734>
+<elem-735>
+<elem-736>
+<elem-737>
+<elem-738>
+<elem-739>
+<elem-740>
+<elem-741>
+<elem-742>
+<elem-743>
+<elem-744>
+<elem-745>
+<elem-746>
+<elem-747>
+<elem-748>
+<elem-749>
+<elem-750>
+<elem-751>
+<elem-752>
+<elem-753>
+<elem-754>
+<elem-755>
+<elem-756>
+<elem-757>
+<elem-758>
+<elem-759>
+<elem-760>
+<elem-761>
+<elem-762>
+<elem-763>
+<elem-764>
+<elem-765>
+<elem-766>
+<elem-767>
+<elem-768>
+<elem-769>
+<elem-770>
+<elem-771>
+<elem-772>
+<elem-773>
+<elem-774>
+<elem-775>
+<elem-776>
+<elem-777>
+<elem-778>
+<elem-779>
+<elem-780>
+<elem-781>
+<elem-782>
+<elem-783>
+<elem-784>
+<elem-785>
+<elem-786>
+<elem-787>
+<elem-788>
+<elem-789>
+<elem-790>
+<elem-791>
+<elem-792>
+<elem-793>
+<elem-794>
+<elem-795>
+<elem-796>
+<elem-797>
+<elem-798>
+<elem-799>
+<elem-800>
+<elem-801>
+<elem-802>
+<elem-803>
+<elem-804>
+<elem-805>
+<elem-806>
+<elem-807>
+<elem-808>
+<elem-809>
+<elem-810>
+<elem-811>
+<elem-812>
+<elem-813>
+<elem-814>
+<elem-815>
+<elem-816>
+<elem-817>
+<elem-818>
+<elem-819>
+<elem-820>
+<elem-821>
+<elem-822>
+<elem-823>
+<elem-824>
+<elem-825>
+<elem-826>
+<elem-827>
+<elem-828>
+<elem-829>
+<elem-830>
+<elem-831>
+<elem-832>
+<elem-833>
+<elem-834>
+<elem-835>
+<elem-836>
+<elem-837>
+<elem-838>
+<elem-839>
+<elem-840>
+<elem-841>
+<elem-842>
+<elem-843>
+<elem-844>
+<elem-845>
+<elem-846>
+<elem-847>
+<elem-848>
+<elem-849>
+<elem-850>
+<elem-851>
+<elem-852>
+<elem-853>
+<elem-854>
+<elem-855>
+<elem-856>
+<elem-857>
+<elem-858>
+<elem-859>
+<elem-860>
+<elem-861>
+<elem-862>
+<elem-863>
+<elem-864>
+<elem-865>
+<elem-866>
+<elem-867>
+<elem-868>
+<elem-869>
+<elem-870>
+<elem-871>
+<elem-872>
+<elem-873>
+<elem-874>
+<elem-875>
+<elem-876>
+<elem-877>
+<elem-878>
+<elem-879>
+<elem-880>
+<elem-881>
+<elem-882>
+<elem-883>
+<elem-884>
+<elem-885>
+<elem-886>
+<elem-887>
+<elem-888>
+<elem-889>
+<elem-890>
+<elem-891>
+<elem-892>
+<elem-893>
+<elem-894>
+<elem-895>
+<elem-896>
+<elem-897>
+<elem-898>
+<elem-899>
+<elem-900>
+<elem-901>
+<elem-902>
+<elem-903>
+<elem-904>
+<elem-905>
+<elem-906>
+<elem-907>
+<elem-908>
+<elem-909>
+<elem-910>
+<elem-911>
+<elem-912>
+<elem-913>
+<elem-914>
+<elem-915>
+<elem-916>
+<elem-917>
+<elem-918>
+<elem-919>
+<elem-920>
+<elem-921>
+<elem-922>
+<elem-923>
+<elem-924>
+<elem-925>
+<elem-926>
+<elem-927>
+<elem-928>
+<elem-929>
+<elem-930>
+<elem-931>
+<elem-932>
+<elem-933>
+<elem-934>
+<elem-935>
+<elem-936>
+<elem-937>
+<elem-938>
+<elem-939>
+<elem-940>
+<elem-941>
+<elem-942>
+<elem-943>
+<elem-944>
+<elem-945>
+<elem-946>
+<elem-947>
+<elem-948>
+<elem-949>
+<elem-950>
+<elem-951>
+<elem-952>
+<elem-953>
+<elem-954>
+<elem-955>
+<elem-956>
+<elem-957>
+<elem-958>
+<elem-959>
+<elem-960>
+<elem-961>
+<elem-962>
+<elem-963>
+<elem-964>
+<elem-965>
+<elem-966>
+<elem-967>
+<elem-968>
+<elem-969>
+<elem-970>
+<elem-971>
+<elem-972>
+<elem-973>
+<elem-974>
+<elem-975>
+<elem-976>
+<elem-977>
+<elem-978>
+<elem-979>
+<elem-980>
+<elem-981>
+<elem-982>
+<elem-983>
+<elem-984>
+<elem-985>
+<elem-986>
+<elem-987>
+<elem-988>
+<elem-989>
+<elem-990>
+<elem-991>
+<elem-992>
+<elem-993>
+<elem-994>
+<elem-995>
+<elem-996>
+<elem-997>
+<elem-998>
+<elem-999>
+<elem-1000>
+</elem-1000>
+</elem-999>
+</elem-998>
+</elem-997>
+</elem-996>
+</elem-995>
+</elem-994>
+</elem-993>
+</elem-992>
+</elem-991>
+</elem-990>
+</elem-989>
+</elem-988>
+</elem-987>
+</elem-986>
+</elem-985>
+</elem-984>
+</elem-983>
+</elem-982>
+</elem-981>
+</elem-980>
+</elem-979>
+</elem-978>
+</elem-977>
+</elem-976>
+</elem-975>
+</elem-974>
+</elem-973>
+</elem-972>
+</elem-971>
+</elem-970>
+</elem-969>
+</elem-968>
+</elem-967>
+</elem-966>
+</elem-965>
+</elem-964>
+</elem-963>
+</elem-962>
+</elem-961>
+</elem-960>
+</elem-959>
+</elem-958>
+</elem-957>
+</elem-956>
+</elem-955>
+</elem-954>
+</elem-953>
+</elem-952>
+</elem-951>
+</elem-950>
+</elem-949>
+</elem-948>
+</elem-947>
+</elem-946>
+</elem-945>
+</elem-944>
+</elem-943>
+</elem-942>
+</elem-941>
+</elem-940>
+</elem-939>
+</elem-938>
+</elem-937>
+</elem-936>
+</elem-935>
+</elem-934>
+</elem-933>
+</elem-932>
+</elem-931>
+</elem-930>
+</elem-929>
+</elem-928>
+</elem-927>
+</elem-926>
+</elem-925>
+</elem-924>
+</elem-923>
+</elem-922>
+</elem-921>
+</elem-920>
+</elem-919>
+</elem-918>
+</elem-917>
+</elem-916>
+</elem-915>
+</elem-914>
+</elem-913>
+</elem-912>
+</elem-911>
+</elem-910>
+</elem-909>
+</elem-908>
+</elem-907>
+</elem-906>
+</elem-905>
+</elem-904>
+</elem-903>
+</elem-902>
+</elem-901>
+</elem-900>
+</elem-899>
+</elem-898>
+</elem-897>
+</elem-896>
+</elem-895>
+</elem-894>
+</elem-893>
+</elem-892>
+</elem-891>
+</elem-890>
+</elem-889>
+</elem-888>
+</elem-887>
+</elem-886>
+</elem-885>
+</elem-884>
+</elem-883>
+</elem-882>
+</elem-881>
+</elem-880>
+</elem-879>
+</elem-878>
+</elem-877>
+</elem-876>
+</elem-875>
+</elem-874>
+</elem-873>
+</elem-872>
+</elem-871>
+</elem-870>
+</elem-869>
+</elem-868>
+</elem-867>
+</elem-866>
+</elem-865>
+</elem-864>
+</elem-863>
+</elem-862>
+</elem-861>
+</elem-860>
+</elem-859>
+</elem-858>
+</elem-857>
+</elem-856>
+</elem-855>
+</elem-854>
+</elem-853>
+</elem-852>
+</elem-851>
+</elem-850>
+</elem-849>
+</elem-848>
+</elem-847>
+</elem-846>
+</elem-845>
+</elem-844>
+</elem-843>
+</elem-842>
+</elem-841>
+</elem-840>
+</elem-839>
+</elem-838>
+</elem-837>
+</elem-836>
+</elem-835>
+</elem-834>
+</elem-833>
+</elem-832>
+</elem-831>
+</elem-830>
+</elem-829>
+</elem-828>
+</elem-827>
+</elem-826>
+</elem-825>
+</elem-824>
+</elem-823>
+</elem-822>
+</elem-821>
+</elem-820>
+</elem-819>
+</elem-818>
+</elem-817>
+</elem-816>
+</elem-815>
+</elem-814>
+</elem-813>
+</elem-812>
+</elem-811>
+</elem-810>
+</elem-809>
+</elem-808>
+</elem-807>
+</elem-806>
+</elem-805>
+</elem-804>
+</elem-803>
+</elem-802>
+</elem-801>
+</elem-800>
+</elem-799>
+</elem-798>
+</elem-797>
+</elem-796>
+</elem-795>
+</elem-794>
+</elem-793>
+</elem-792>
+</elem-791>
+</elem-790>
+</elem-789>
+</elem-788>
+</elem-787>
+</elem-786>
+</elem-785>
+</elem-784>
+</elem-783>
+</elem-782>
+</elem-781>
+</elem-780>
+</elem-779>
+</elem-778>
+</elem-777>
+</elem-776>
+</elem-775>
+</elem-774>
+</elem-773>
+</elem-772>
+</elem-771>
+</elem-770>
+</elem-769>
+</elem-768>
+</elem-767>
+</elem-766>
+</elem-765>
+</elem-764>
+</elem-763>
+</elem-762>
+</elem-761>
+</elem-760>
+</elem-759>
+</elem-758>
+</elem-757>
+</elem-756>
+</elem-755>
+</elem-754>
+</elem-753>
+</elem-752>
+</elem-751>
+</elem-750>
+</elem-749>
+</elem-748>
+</elem-747>
+</elem-746>
+</elem-745>
+</elem-744>
+</elem-743>
+</elem-742>
+</elem-741>
+</elem-740>
+</elem-739>
+</elem-738>
+</elem-737>
+</elem-736>
+</elem-735>
+</elem-734>
+</elem-733>
+</elem-732>
+</elem-731>
+</elem-730>
+</elem-729>
+</elem-728>
+</elem-727>
+</elem-726>
+</elem-725>
+</elem-724>
+</elem-723>
+</elem-722>
+</elem-721>
+</elem-720>
+</elem-719>
+</elem-718>
+</elem-717>
+</elem-716>
+</elem-715>
+</elem-714>
+</elem-713>
+</elem-712>
+</elem-711>
+</elem-710>
+</elem-709>
+</elem-708>
+</elem-707>
+</elem-706>
+</elem-705>
+</elem-704>
+</elem-703>
+</elem-702>
+</elem-701>
+</elem-700>
+</elem-699>
+</elem-698>
+</elem-697>
+</elem-696>
+</elem-695>
+</elem-694>
+</elem-693>
+</elem-692>
+</elem-691>
+</elem-690>
+</elem-689>
+</elem-688>
+</elem-687>
+</elem-686>
+</elem-685>
+</elem-684>
+</elem-683>
+</elem-682>
+</elem-681>
+</elem-680>
+</elem-679>
+</elem-678>
+</elem-677>
+</elem-676>
+</elem-675>
+</elem-674>
+</elem-673>
+</elem-672>
+</elem-671>
+</elem-670>
+</elem-669>
+</elem-668>
+</elem-667>
+</elem-666>
+</elem-665>
+</elem-664>
+</elem-663>
+</elem-662>
+</elem-661>
+</elem-660>
+</elem-659>
+</elem-658>
+</elem-657>
+</elem-656>
+</elem-655>
+</elem-654>
+</elem-653>
+</elem-652>
+</elem-651>
+</elem-650>
+</elem-649>
+</elem-648>
+</elem-647>
+</elem-646>
+</elem-645>
+</elem-644>
+</elem-643>
+</elem-642>
+</elem-641>
+</elem-640>
+</elem-639>
+</elem-638>
+</elem-637>
+</elem-636>
+</elem-635>
+</elem-634>
+</elem-633>
+</elem-632>
+</elem-631>
+</elem-630>
+</elem-629>
+</elem-628>
+</elem-627>
+</elem-626>
+</elem-625>
+</elem-624>
+</elem-623>
+</elem-622>
+</elem-621>
+</elem-620>
+</elem-619>
+</elem-618>
+</elem-617>
+</elem-616>
+</elem-615>
+</elem-614>
+</elem-613>
+</elem-612>
+</elem-611>
+</elem-610>
+</elem-609>
+</elem-608>
+</elem-607>
+</elem-606>
+</elem-605>
+</elem-604>
+</elem-603>
+</elem-602>
+</elem-601>
+</elem-600>
+</elem-599>
+</elem-598>
+</elem-597>
+</elem-596>
+</elem-595>
+</elem-594>
+</elem-593>
+</elem-592>
+</elem-591>
+</elem-590>
+</elem-589>
+</elem-588>
+</elem-587>
+</elem-586>
+</elem-585>
+</elem-584>
+</elem-583>
+</elem-582>
+</elem-581>
+</elem-580>
+</elem-579>
+</elem-578>
+</elem-577>
+</elem-576>
+</elem-575>
+</elem-574>
+</elem-573>
+</elem-572>
+</elem-571>
+</elem-570>
+</elem-569>
+</elem-568>
+</elem-567>
+</elem-566>
+</elem-565>
+</elem-564>
+</elem-563>
+</elem-562>
+</elem-561>
+</elem-560>
+</elem-559>
+</elem-558>
+</elem-557>
+</elem-556>
+</elem-555>
+</elem-554>
+</elem-553>
+</elem-552>
+</elem-551>
+</elem-550>
+</elem-549>
+</elem-548>
+</elem-547>
+</elem-546>
+</elem-545>
+</elem-544>
+</elem-543>
+</elem-542>
+</elem-541>
+</elem-540>
+</elem-539>
+</elem-538>
+</elem-537>
+</elem-536>
+</elem-535>
+</elem-534>
+</elem-533>
+</elem-532>
+</elem-531>
+</elem-530>
+</elem-529>
+</elem-528>
+</elem-527>
+</elem-526>
+</elem-525>
+</elem-524>
+</elem-523>
+</elem-522>
+</elem-521>
+</elem-520>
+</elem-519>
+</elem-518>
+</elem-517>
+</elem-516>
+</elem-515>
+</elem-514>
+</elem-513>
+</elem-512>
+</elem-511>
+</elem-510>
+</elem-509>
+</elem-508>
+</elem-507>
+</elem-506>
+</elem-505>
+</elem-504>
+</elem-503>
+</elem-502>
+</elem-501>
+</elem-500>
+</elem-499>
+</elem-498>
+</elem-497>
+</elem-496>
+</elem-495>
+</elem-494>
+</elem-493>
+</elem-492>
+</elem-491>
+</elem-490>
+</elem-489>
+</elem-488>
+</elem-487>
+</elem-486>
+</elem-485>
+</elem-484>
+</elem-483>
+</elem-482>
+</elem-481>
+</elem-480>
+</elem-479>
+</elem-478>
+</elem-477>
+</elem-476>
+</elem-475>
+</elem-474>
+</elem-473>
+</elem-472>
+</elem-471>
+</elem-470>
+</elem-469>
+</elem-468>
+</elem-467>
+</elem-466>
+</elem-465>
+</elem-464>
+</elem-463>
+</elem-462>
+</elem-461>
+</elem-460>
+</elem-459>
+</elem-458>
+</elem-457>
+</elem-456>
+</elem-455>
+</elem-454>
+</elem-453>
+</elem-452>
+</elem-451>
+</elem-450>
+</elem-449>
+</elem-448>
+</elem-447>
+</elem-446>
+</elem-445>
+</elem-444>
+</elem-443>
+</elem-442>
+</elem-441>
+</elem-440>
+</elem-439>
+</elem-438>
+</elem-437>
+</elem-436>
+</elem-435>
+</elem-434>
+</elem-433>
+</elem-432>
+</elem-431>
+</elem-430>
+</elem-429>
+</elem-428>
+</elem-427>
+</elem-426>
+</elem-425>
+</elem-424>
+</elem-423>
+</elem-422>
+</elem-421>
+</elem-420>
+</elem-419>
+</elem-418>
+</elem-417>
+</elem-416>
+</elem-415>
+</elem-414>
+</elem-413>
+</elem-412>
+</elem-411>
+</elem-410>
+</elem-409>
+</elem-408>
+</elem-407>
+</elem-406>
+</elem-405>
+</elem-404>
+</elem-403>
+</elem-402>
+</elem-401>
+</elem-400>
+</elem-399>
+</elem-398>
+</elem-397>
+</elem-396>
+</elem-395>
+</elem-394>
+</elem-393>
+</elem-392>
+</elem-391>
+</elem-390>
+</elem-389>
+</elem-388>
+</elem-387>
+</elem-386>
+</elem-385>
+</elem-384>
+</elem-383>
+</elem-382>
+</elem-381>
+</elem-380>
+</elem-379>
+</elem-378>
+</elem-377>
+</elem-376>
+</elem-375>
+</elem-374>
+</elem-373>
+</elem-372>
+</elem-371>
+</elem-370>
+</elem-369>
+</elem-368>
+</elem-367>
+</elem-366>
+</elem-365>
+</elem-364>
+</elem-363>
+</elem-362>
+</elem-361>
+</elem-360>
+</elem-359>
+</elem-358>
+</elem-357>
+</elem-356>
+</elem-355>
+</elem-354>
+</elem-353>
+</elem-352>
+</elem-351>
+</elem-350>
+</elem-349>
+</elem-348>
+</elem-347>
+</elem-346>
+</elem-345>
+</elem-344>
+</elem-343>
+</elem-342>
+</elem-341>
+</elem-340>
+</elem-339>
+</elem-338>
+</elem-337>
+</elem-336>
+</elem-335>
+</elem-334>
+</elem-333>
+</elem-332>
+</elem-331>
+</elem-330>
+</elem-329>
+</elem-328>
+</elem-327>
+</elem-326>
+</elem-325>
+</elem-324>
+</elem-323>
+</elem-322>
+</elem-321>
+</elem-320>
+</elem-319>
+</elem-318>
+</elem-317>
+</elem-316>
+</elem-315>
+</elem-314>
+</elem-313>
+</elem-312>
+</elem-311>
+</elem-310>
+</elem-309>
+</elem-308>
+</elem-307>
+</elem-306>
+</elem-305>
+</elem-304>
+</elem-303>
+</elem-302>
+</elem-301>
+</elem-300>
+</elem-299>
+</elem-298>
+</elem-297>
+</elem-296>
+</elem-295>
+</elem-294>
+</elem-293>
+</elem-292>
+</elem-291>
+</elem-290>
+</elem-289>
+</elem-288>
+</elem-287>
+</elem-286>
+</elem-285>
+</elem-284>
+</elem-283>
+</elem-282>
+</elem-281>
+</elem-280>
+</elem-279>
+</elem-278>
+</elem-277>
+</elem-276>
+</elem-275>
+</elem-274>
+</elem-273>
+</elem-272>
+</elem-271>
+</elem-270>
+</elem-269>
+</elem-268>
+</elem-267>
+</elem-266>
+</elem-265>
+</elem-264>
+</elem-263>
+</elem-262>
+</elem-261>
+</elem-260>
+</elem-259>
+</elem-258>
+</elem-257>
+</elem-256>
+</elem-255>
+</elem-254>
+</elem-253>
+</elem-252>
+</elem-251>
+</elem-250>
+</elem-249>
+</elem-248>
+</elem-247>
+</elem-246>
+</elem-245>
+</elem-244>
+</elem-243>
+</elem-242>
+</elem-241>
+</elem-240>
+</elem-239>
+</elem-238>
+</elem-237>
+</elem-236>
+</elem-235>
+</elem-234>
+</elem-233>
+</elem-232>
+</elem-231>
+</elem-230>
+</elem-229>
+</elem-228>
+</elem-227>
+</elem-226>
+</elem-225>
+</elem-224>
+</elem-223>
+</elem-222>
+</elem-221>
+</elem-220>
+</elem-219>
+</elem-218>
+</elem-217>
+</elem-216>
+</elem-215>
+</elem-214>
+</elem-213>
+</elem-212>
+</elem-211>
+</elem-210>
+</elem-209>
+</elem-208>
+</elem-207>
+</elem-206>
+</elem-205>
+</elem-204>
+</elem-203>
+</elem-202>
+</elem-201>
+</elem-200>
+</elem-199>
+</elem-198>
+</elem-197>
+</elem-196>
+</elem-195>
+</elem-194>
+</elem-193>
+</elem-192>
+</elem-191>
+</elem-190>
+</elem-189>
+</elem-188>
+</elem-187>
+</elem-186>
+</elem-185>
+</elem-184>
+</elem-183>
+</elem-182>
+</elem-181>
+</elem-180>
+</elem-179>
+</elem-178>
+</elem-177>
+</elem-176>
+</elem-175>
+</elem-174>
+</elem-173>
+</elem-172>
+</elem-171>
+</elem-170>
+</elem-169>
+</elem-168>
+</elem-167>
+</elem-166>
+</elem-165>
+</elem-164>
+</elem-163>
+</elem-162>
+</elem-161>
+</elem-160>
+</elem-159>
+</elem-158>
+</elem-157>
+</elem-156>
+</elem-155>
+</elem-154>
+</elem-153>
+</elem-152>
+</elem-151>
+</elem-150>
+</elem-149>
+</elem-148>
+</elem-147>
+</elem-146>
+</elem-145>
+</elem-144>
+</elem-143>
+</elem-142>
+</elem-141>
+</elem-140>
+</elem-139>
+</elem-138>
+</elem-137>
+</elem-136>
+</elem-135>
+</elem-134>
+</elem-133>
+</elem-132>
+</elem-131>
+</elem-130>
+</elem-129>
+</elem-128>
+</elem-127>
+</elem-126>
+</elem-125>
+</elem-124>
+</elem-123>
+</elem-122>
+</elem-121>
+</elem-120>
+</elem-119>
+</elem-118>
+</elem-117>
+</elem-116>
+</elem-115>
+</elem-114>
+</elem-113>
+</elem-112>
+</elem-111>
+</elem-110>
+</elem-109>
+</elem-108>
+</elem-107>
+</elem-106>
+</elem-105>
+</elem-104>
+</elem-103>
+</elem-102>
+</elem-101>
+</elem-100>
+</elem-99>
+</elem-98>
+</elem-97>
+</elem-96>
+</elem-95>
+</elem-94>
+</elem-93>
+</elem-92>
+</elem-91>
+</elem-90>
+</elem-89>
+</elem-88>
+</elem-87>
+</elem-86>
+</elem-85>
+</elem-84>
+</elem-83>
+</elem-82>
+</elem-81>
+</elem-80>
+</elem-79>
+</elem-78>
+</elem-77>
+</elem-76>
+</elem-75>
+</elem-74>
+</elem-73>
+</elem-72>
+</elem-71>
+</elem-70>
+</elem-69>
+</elem-68>
+</elem-67>
+</elem-66>
+</elem-65>
+</elem-64>
+</elem-63>
+</elem-62>
+</elem-61>
+</elem-60>
+</elem-59>
+</elem-58>
+</elem-57>
+</elem-56>
+</elem-55>
+</elem-54>
+</elem-53>
+</elem-52>
+</elem-51>
+</elem-50>
+</elem-49>
+</elem-48>
+</elem-47>
+</elem-46>
+</elem-45>
+</elem-44>
+</elem-43>
+</elem-42>
+</elem-41>
+</elem-40>
+</elem-39>
+</elem-38>
+</elem-37>
+</elem-36>
+</elem-35>
+</elem-34>
+</elem-33>
+</elem-32>
+</elem-31>
+</elem-30>
+</elem-29>
+</elem-28>
+</elem-27>
+</elem-26>
+</elem-25>
+</elem-24>
+</elem-23>
+</elem-22>
+</elem-21>
+</elem-20>
+</elem-19>
+</elem-18>
+</elem-17>
+</elem-16>
+</elem-15>
+</elem-14>
+</elem-13>
+</elem-12>
+</elem-11>
+</elem-10>
+</elem-9>
+</elem-8>
+</elem-7>
+</elem-6>
+</elem-5>
+</elem-4>
+</elem-3>
+</elem-2>
+</elem-1>
+</outer> \ No newline at end of file
diff --git a/test/System.Web.Razor.Test/Text/BufferingTextReaderTest.cs b/test/System.Web.Razor.Test/Text/BufferingTextReaderTest.cs
new file mode 100644
index 00000000..b4b8c019
--- /dev/null
+++ b/test/System.Web.Razor.Test/Text/BufferingTextReaderTest.cs
@@ -0,0 +1,265 @@
+using System.IO;
+using System.Web.Razor.Text;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Razor.Test.Text
+{
+ public class BufferingTextReaderTest : LookaheadTextReaderTestBase
+ {
+ private const string TestString = "abcdefg";
+
+ private class DisposeTestMockTextReader : TextReader
+ {
+ public bool Disposed { get; set; }
+
+ protected override void Dispose(bool disposing)
+ {
+ base.Dispose(disposing);
+ Disposed = true;
+ }
+ }
+
+ protected override LookaheadTextReader CreateReader(string testString)
+ {
+ return new BufferingTextReader(new StringReader(testString));
+ }
+
+ [Fact]
+ public void ConstructorRequiresNonNullSourceReader()
+ {
+ Assert.ThrowsArgumentNull(() => new BufferingTextReader(null), "source");
+ }
+
+ [Fact]
+ public void PeekReturnsCurrentCharacterWithoutAdvancingPosition()
+ {
+ RunPeekTest("abc", peekAt: 2);
+ }
+
+ [Fact]
+ public void PeekReturnsNegativeOneAtEndOfSourceReader()
+ {
+ RunPeekTest("abc", peekAt: 3);
+ }
+
+ [Fact]
+ public void ReadReturnsCurrentCharacterAndAdvancesToNextCharacter()
+ {
+ RunReadTest("abc", readAt: 2);
+ }
+
+ [Fact]
+ public void EndingLookaheadReturnsReaderToPreviousLocation()
+ {
+ RunLookaheadTest("abcdefg", "abcb",
+ Read,
+ Lookahead(
+ Read,
+ Read),
+ Read);
+ }
+
+ [Fact]
+ public void MultipleLookaheadsCanBePerformed()
+ {
+ RunLookaheadTest("abcdefg", "abcbcdc",
+ Read,
+ Lookahead(
+ Read,
+ Read),
+ Read,
+ Lookahead(
+ Read,
+ Read),
+ Read);
+ }
+
+ [Fact]
+ public void LookaheadsCanBeNested()
+ {
+ RunLookaheadTest("abcdefg", "abcdefebc",
+ Read, // Appended: "a" Reader: "bcdefg"
+ Lookahead( // Reader: "bcdefg"
+ Read, // Appended: "b" Reader: "cdefg";
+ Read, // Appended: "c" Reader: "defg";
+ Read, // Appended: "d" Reader: "efg";
+ Lookahead( // Reader: "efg"
+ Read, // Appended: "e" Reader: "fg";
+ Read // Appended: "f" Reader: "g";
+ ), // Reader: "efg"
+ Read // Appended: "e" Reader: "fg";
+ ), // Reader: "bcdefg"
+ Read, // Appended: "b" Reader: "cdefg";
+ Read); // Appended: "c" Reader: "defg";
+ }
+
+ [Fact]
+ public void SourceLocationIsZeroWhenInitialized()
+ {
+ RunSourceLocationTest("abcdefg", SourceLocation.Zero, checkAt: 0);
+ }
+
+ [Fact]
+ public void CharacterAndAbsoluteIndicesIncreaseAsCharactersAreRead()
+ {
+ RunSourceLocationTest("abcdefg", new SourceLocation(4, 0, 4), checkAt: 4);
+ }
+
+ [Fact]
+ public void CharacterAndAbsoluteIndicesIncreaseAsSlashRInTwoCharacterNewlineIsRead()
+ {
+ RunSourceLocationTest("f\r\nb", new SourceLocation(2, 0, 2), checkAt: 2);
+ }
+
+ [Fact]
+ public void CharacterIndexResetsToZeroAndLineIndexIncrementsWhenSlashNInTwoCharacterNewlineIsRead()
+ {
+ RunSourceLocationTest("f\r\nb", new SourceLocation(3, 1, 0), checkAt: 3);
+ }
+
+ [Fact]
+ public void CharacterIndexResetsToZeroAndLineIndexIncrementsWhenSlashRInSingleCharacterNewlineIsRead()
+ {
+ RunSourceLocationTest("f\rb", new SourceLocation(2, 1, 0), checkAt: 2);
+ }
+
+ [Fact]
+ public void CharacterIndexResetsToZeroAndLineIndexIncrementsWhenSlashNInSingleCharacterNewlineIsRead()
+ {
+ RunSourceLocationTest("f\nb", new SourceLocation(2, 1, 0), checkAt: 2);
+ }
+
+ [Fact]
+ public void EndingLookaheadResetsRawCharacterAndLineIndexToValuesWhenLookaheadBegan()
+ {
+ RunEndLookaheadUpdatesSourceLocationTest();
+ }
+
+ [Fact]
+ public void OnceBufferingBeginsReadsCanContinuePastEndOfBuffer()
+ {
+ RunLookaheadTest("abcdefg", "abcbcdefg",
+ Read,
+ Lookahead(Read(2)),
+ Read(2),
+ ReadToEnd);
+ }
+
+ [Fact]
+ public void DisposeDisposesSourceReader()
+ {
+ RunDisposeTest(r => r.Dispose());
+ }
+
+ [Fact]
+ public void CloseDisposesSourceReader()
+ {
+ RunDisposeTest(r => r.Close());
+ }
+
+ [Fact]
+ public void ReadWithBufferSupportsLookahead()
+ {
+ RunBufferReadTest((reader, buffer, index, count) => reader.Read(buffer, index, count));
+ }
+
+ [Fact]
+ public void ReadBlockSupportsLookahead()
+ {
+ RunBufferReadTest((reader, buffer, index, count) => reader.ReadBlock(buffer, index, count));
+ }
+
+ [Fact]
+ public void ReadLineSupportsLookahead()
+ {
+ RunReadUntilTest(r => r.ReadLine(), expectedRaw: 8, expectedChar: 0, expectedLine: 2);
+ }
+
+ [Fact]
+ public void ReadToEndSupportsLookahead()
+ {
+ RunReadUntilTest(r => r.ReadToEnd(), expectedRaw: 11, expectedChar: 3, expectedLine: 2);
+ }
+
+ [Fact]
+ public void ReadLineMaintainsCorrectCharacterPosition()
+ {
+ RunSourceLocationTest("abc\r\ndef", new SourceLocation(5, 1, 0), r => r.ReadLine());
+ }
+
+ [Fact]
+ public void ReadToEndWorksAsInNormalTextReader()
+ {
+ RunReadToEndTest();
+ }
+
+ [Fact]
+ public void CancelBacktrackStopsNextEndLookaheadFromBacktracking()
+ {
+ RunLookaheadTest("abcdefg", "abcdefg",
+ Lookahead(
+ Read(2),
+ CancelBacktrack
+ ),
+ ReadToEnd);
+ }
+
+ [Fact]
+ public void CancelBacktrackThrowsInvalidOperationExceptionIfCalledOutsideOfLookahead()
+ {
+ RunCancelBacktrackOutsideLookaheadTest();
+ }
+
+ [Fact]
+ public void CancelBacktrackOnlyCancelsBacktrackingForInnermostNestedLookahead()
+ {
+ RunLookaheadTest("abcdefg", "abcdabcdefg",
+ Lookahead(
+ Read(2),
+ Lookahead(
+ Read,
+ CancelBacktrack
+ ),
+ Read
+ ),
+ ReadToEnd);
+ }
+
+ [Fact]
+ public void BacktrackBufferIsClearedWhenEndReachedAndNoCurrentLookaheads()
+ {
+ // Arrange
+ StringReader source = new StringReader(TestString);
+ BufferingTextReader reader = new BufferingTextReader(source);
+
+ reader.Read(); // Reader: "bcdefg"
+ using (reader.BeginLookahead())
+ {
+ reader.Read(); // Reader: "cdefg"
+ } // Reader: "bcdefg"
+ reader.Read(); // Reader: "cdefg"
+ Assert.NotNull(reader.Buffer); // Verify our assumption that the buffer still exists
+
+ // Act
+ reader.Read();
+
+ // Assert
+ Assert.False(reader.Buffering, "The buffer was not reset when the end was reached");
+ Assert.Equal(0, reader.Buffer.Length);
+ }
+
+ private static void RunDisposeTest(Action<LookaheadTextReader> triggerAction)
+ {
+ // Arrange
+ DisposeTestMockTextReader source = new DisposeTestMockTextReader();
+ LookaheadTextReader reader = new BufferingTextReader(source);
+
+ // Act
+ triggerAction(reader);
+
+ // Assert
+ Assert.True(source.Disposed);
+ }
+ }
+}
diff --git a/test/System.Web.Razor.Test/Text/LineTrackingStringBufferTest.cs b/test/System.Web.Razor.Test/Text/LineTrackingStringBufferTest.cs
new file mode 100644
index 00000000..52cffb7f
--- /dev/null
+++ b/test/System.Web.Razor.Test/Text/LineTrackingStringBufferTest.cs
@@ -0,0 +1,25 @@
+using System.Web.Razor.Text;
+using Xunit;
+
+namespace System.Web.Razor.Test.Text
+{
+ public class LineTrackingStringBufferTest
+ {
+ [Fact]
+ public void CtorInitializesProperties()
+ {
+ LineTrackingStringBuffer buffer = new LineTrackingStringBuffer();
+ Assert.Equal(0, buffer.Length);
+ }
+
+ [Fact]
+ public void CharAtCorrectlyReturnsLocation()
+ {
+ LineTrackingStringBuffer buffer = new LineTrackingStringBuffer();
+ buffer.Append("foo\rbar\nbaz\r\nbiz");
+ LineTrackingStringBuffer.CharacterReference chr = buffer.CharAt(14);
+ Assert.Equal('i', chr.Character);
+ Assert.Equal(new SourceLocation(14, 3, 1), chr.Location);
+ }
+ }
+}
diff --git a/test/System.Web.Razor.Test/Text/LookaheadTextReaderTestBase.cs b/test/System.Web.Razor.Test/Text/LookaheadTextReaderTestBase.cs
new file mode 100644
index 00000000..fbcc8e29
--- /dev/null
+++ b/test/System.Web.Razor.Test/Text/LookaheadTextReaderTestBase.cs
@@ -0,0 +1,252 @@
+using System.Text;
+using System.Web.Razor.Resources;
+using System.Web.Razor.Text;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Razor.Test.Text
+{
+ public abstract class LookaheadTextReaderTestBase
+ {
+ protected abstract LookaheadTextReader CreateReader(string testString);
+
+ protected void RunPeekTest(string input, int peekAt = 0)
+ {
+ RunPeekOrReadTest(input, peekAt, false);
+ }
+
+ protected void RunReadTest(string input, int readAt = 0)
+ {
+ RunPeekOrReadTest(input, readAt, true);
+ }
+
+ protected void RunSourceLocationTest(string input, SourceLocation expected, int checkAt = 0)
+ {
+ RunSourceLocationTest(input, expected, r => AdvanceReader(checkAt, r));
+ }
+
+ protected void RunSourceLocationTest(string input, SourceLocation expected, Action<LookaheadTextReader> readerAction)
+ {
+ // Arrange
+ LookaheadTextReader reader = CreateReader(input);
+ readerAction(reader);
+
+ // Act
+ SourceLocation actual = reader.CurrentLocation;
+
+ // Assert
+ Assert.Equal(expected, actual);
+ }
+
+ protected void RunEndLookaheadUpdatesSourceLocationTest()
+ {
+ SourceLocation? expectedLocation = null;
+ SourceLocation? actualLocation = null;
+
+ RunLookaheadTest("abc\r\ndef\r\nghi", null,
+ Read(6),
+ CaptureSourceLocation(s => expectedLocation = s),
+ Lookahead(Read(6)),
+ CaptureSourceLocation(s => actualLocation = s));
+ // Assert
+ Assert.Equal(expectedLocation.Value.AbsoluteIndex, actualLocation.Value.AbsoluteIndex);
+ Assert.Equal(expectedLocation.Value.CharacterIndex, actualLocation.Value.CharacterIndex);
+ Assert.Equal(expectedLocation.Value.LineIndex, actualLocation.Value.LineIndex);
+ }
+
+ protected void RunReadToEndTest()
+ {
+ // Arrange
+ LookaheadTextReader reader = CreateReader("abcdefg");
+
+ // Act
+ string str = reader.ReadToEnd();
+
+ // Assert
+ Assert.Equal("abcdefg", str);
+ }
+
+ protected void RunCancelBacktrackOutsideLookaheadTest()
+ {
+ // Arrange
+ LookaheadTextReader reader = CreateReader("abcdefg");
+
+ // Act and Assert
+ Assert.Throws<InvalidOperationException>(() => reader.CancelBacktrack(), RazorResources.CancelBacktrack_Must_Be_Called_Within_Lookahead);
+ }
+
+ protected Action<StringBuilder, LookaheadTextReader> CaptureSourceLocation(Action<SourceLocation> capture)
+ {
+ return (_, reader) => { capture(reader.CurrentLocation); };
+ }
+
+ protected Action<StringBuilder, LookaheadTextReader> Read(int count)
+ {
+ return (builder, reader) =>
+ {
+ for (int i = 0; i < count; i++)
+ {
+ Read(builder, reader);
+ }
+ };
+ }
+
+ protected void Read(StringBuilder builder, LookaheadTextReader reader)
+ {
+ builder.Append((char)reader.Read());
+ }
+
+ protected void ReadToEnd(StringBuilder builder, LookaheadTextReader reader)
+ {
+ builder.Append(reader.ReadToEnd());
+ }
+
+ protected void CancelBacktrack(StringBuilder builder, LookaheadTextReader reader)
+ {
+ reader.CancelBacktrack();
+ }
+
+ protected Action<StringBuilder, LookaheadTextReader> Lookahead(params Action<StringBuilder, LookaheadTextReader>[] readerCommands)
+ {
+ return (builder, reader) =>
+ {
+ using (reader.BeginLookahead())
+ {
+ RunAll(readerCommands, builder, reader);
+ }
+ };
+ }
+
+ protected void RunLookaheadTest(string input, string expected, params Action<StringBuilder, LookaheadTextReader>[] readerCommands)
+ {
+ // Arrange
+ StringBuilder builder = new StringBuilder();
+ using (LookaheadTextReader reader = CreateReader(input))
+ {
+ RunAll(readerCommands, builder, reader);
+ }
+
+ if (expected != null)
+ {
+ Assert.Equal(expected, builder.ToString());
+ }
+ }
+
+ protected void RunReadUntilTest(Func<LookaheadTextReader, string> readMethod, int expectedRaw, int expectedChar, int expectedLine)
+ {
+ // Arrange
+ LookaheadTextReader reader = CreateReader("a\r\nbcd\r\nefg");
+
+ reader.Read(); // Reader: "\r\nbcd\r\nefg"
+ reader.Read(); // Reader: "\nbcd\r\nefg"
+ reader.Read(); // Reader: "bcd\r\nefg"
+
+ // Act
+ string read = null;
+ SourceLocation actualLocation;
+ using (reader.BeginLookahead())
+ {
+ read = readMethod(reader);
+ actualLocation = reader.CurrentLocation;
+ }
+
+ // Assert
+ Assert.Equal(3, reader.CurrentLocation.AbsoluteIndex);
+ Assert.Equal(0, reader.CurrentLocation.CharacterIndex);
+ Assert.Equal(1, reader.CurrentLocation.LineIndex);
+ Assert.Equal(expectedRaw, actualLocation.AbsoluteIndex);
+ Assert.Equal(expectedChar, actualLocation.CharacterIndex);
+ Assert.Equal(expectedLine, actualLocation.LineIndex);
+ Assert.Equal('b', reader.Peek());
+ Assert.Equal(read, readMethod(reader));
+ }
+
+ protected void RunBufferReadTest(Func<LookaheadTextReader, char[], int, int, int> readMethod)
+ {
+ // Arrange
+ LookaheadTextReader reader = CreateReader("abcdefg");
+
+ reader.Read(); // Reader: "bcdefg"
+
+ // Act
+ char[] buffer = new char[4];
+ int read = -1;
+ SourceLocation actualLocation;
+ using (reader.BeginLookahead())
+ {
+ read = readMethod(reader, buffer, 0, 4);
+ actualLocation = reader.CurrentLocation;
+ }
+
+ // Assert
+ Assert.Equal("bcde", new String(buffer));
+ Assert.Equal(4, read);
+ Assert.Equal(5, actualLocation.AbsoluteIndex);
+ Assert.Equal(5, actualLocation.CharacterIndex);
+ Assert.Equal(0, actualLocation.LineIndex);
+ Assert.Equal(1, reader.CurrentLocation.CharacterIndex);
+ Assert.Equal(0, reader.CurrentLocation.LineIndex);
+ Assert.Equal('b', reader.Peek());
+ }
+
+ private static void RunAll(Action<StringBuilder, LookaheadTextReader>[] readerCommands, StringBuilder builder, LookaheadTextReader reader)
+ {
+ foreach (Action<StringBuilder, LookaheadTextReader> readerCommand in readerCommands)
+ {
+ readerCommand(builder, reader);
+ }
+ }
+
+ private void RunPeekOrReadTest(string input, int offset, bool isRead)
+ {
+ using (LookaheadTextReader reader = CreateReader(input))
+ {
+ AdvanceReader(offset, reader);
+
+ // Act
+ int? actual = null;
+ if (isRead)
+ {
+ actual = reader.Read();
+ }
+ else
+ {
+ actual = reader.Peek();
+ }
+
+ Assert.NotNull(actual);
+
+ // Asserts
+ AssertReaderValueCorrect(actual.Value, input, offset, "Peek");
+
+ if (isRead)
+ {
+ AssertReaderValueCorrect(reader.Peek(), input, offset + 1, "Read");
+ }
+ else
+ {
+ Assert.Equal(actual, reader.Peek());
+ }
+ }
+ }
+
+ private static void AdvanceReader(int offset, LookaheadTextReader reader)
+ {
+ for (int i = 0; i < offset; i++)
+ {
+ reader.Read();
+ }
+ }
+
+ private void AssertReaderValueCorrect(int actual, string input, int expectedOffset, string methodName)
+ {
+ if (expectedOffset < input.Length)
+ {
+ Assert.Equal(input[expectedOffset], actual);
+ }
+ else
+ {
+ Assert.Equal(-1, actual);
+ }
+ }
+ }
+}
diff --git a/test/System.Web.Razor.Test/Text/SourceLocationTest.cs b/test/System.Web.Razor.Test/Text/SourceLocationTest.cs
new file mode 100644
index 00000000..54e907b8
--- /dev/null
+++ b/test/System.Web.Razor.Test/Text/SourceLocationTest.cs
@@ -0,0 +1,20 @@
+using System.Web.Razor.Text;
+using Xunit;
+
+namespace System.Web.Razor.Test.Text
+{
+ public class SourceLocationTest
+ {
+ [Fact]
+ public void ConstructorWithLineAndCharacterIndexSetsAssociatedProperties()
+ {
+ // Act
+ SourceLocation loc = new SourceLocation(0, 42, 24);
+
+ // Assert
+ Assert.Equal(0, loc.AbsoluteIndex);
+ Assert.Equal(42, loc.LineIndex);
+ Assert.Equal(24, loc.CharacterIndex);
+ }
+ }
+}
diff --git a/test/System.Web.Razor.Test/Text/SourceLocationTrackerTest.cs b/test/System.Web.Razor.Test/Text/SourceLocationTrackerTest.cs
new file mode 100644
index 00000000..35b4d281
--- /dev/null
+++ b/test/System.Web.Razor.Test/Text/SourceLocationTrackerTest.cs
@@ -0,0 +1,179 @@
+using System.Web.Razor.Text;
+using Xunit;
+
+namespace System.Web.Razor.Test.Text
+{
+ public class SourceLocationTrackerTest
+ {
+ private static readonly SourceLocation TestStartLocation = new SourceLocation(10, 42, 45);
+
+ [Fact]
+ public void ConstructorSetsCurrentLocationToZero()
+ {
+ Assert.Equal(SourceLocation.Zero, new SourceLocationTracker().CurrentLocation);
+ }
+
+ [Fact]
+ public void ConstructorWithSourceLocationSetsCurrentLocationToSpecifiedValue()
+ {
+ SourceLocation loc = new SourceLocation(10, 42, 4);
+ Assert.Equal(loc, new SourceLocationTracker(loc).CurrentLocation);
+ }
+
+ [Fact]
+ public void UpdateLocationAdvancesAbsoluteIndexOnNonNewlineCharacter()
+ {
+ // Arrange
+ SourceLocationTracker tracker = new SourceLocationTracker(TestStartLocation);
+
+ // Act
+ tracker.UpdateLocation('f', 'o');
+
+ // Assert
+ Assert.Equal(11, tracker.CurrentLocation.AbsoluteIndex);
+ }
+
+ [Fact]
+ public void UpdateLocationAdvancesCharacterIndexOnNonNewlineCharacter()
+ {
+ // Arrange
+ SourceLocationTracker tracker = new SourceLocationTracker(TestStartLocation);
+
+ // Act
+ tracker.UpdateLocation('f', 'o');
+
+ // Assert
+ Assert.Equal(46, tracker.CurrentLocation.CharacterIndex);
+ }
+
+ [Fact]
+ public void UpdateLocationDoesNotAdvanceLineIndexOnNonNewlineCharacter()
+ {
+ // Arrange
+ SourceLocationTracker tracker = new SourceLocationTracker(TestStartLocation);
+
+ // Act
+ tracker.UpdateLocation('f', 'o');
+
+ // Assert
+ Assert.Equal(42, tracker.CurrentLocation.LineIndex);
+ }
+
+ [Fact]
+ public void UpdateLocationAdvancesLineIndexOnSlashN()
+ {
+ // Arrange
+ SourceLocationTracker tracker = new SourceLocationTracker(TestStartLocation);
+
+ // Act
+ tracker.UpdateLocation('\n', 'o');
+
+ // Assert
+ Assert.Equal(43, tracker.CurrentLocation.LineIndex);
+ }
+
+ [Fact]
+ public void UpdateLocationAdvancesAbsoluteIndexOnSlashN()
+ {
+ // Arrange
+ SourceLocationTracker tracker = new SourceLocationTracker(TestStartLocation);
+
+ // Act
+ tracker.UpdateLocation('\n', 'o');
+
+ // Assert
+ Assert.Equal(11, tracker.CurrentLocation.AbsoluteIndex);
+ }
+
+ [Fact]
+ public void UpdateLocationResetsCharacterIndexOnSlashN()
+ {
+ // Arrange
+ SourceLocationTracker tracker = new SourceLocationTracker(TestStartLocation);
+
+ // Act
+ tracker.UpdateLocation('\n', 'o');
+
+ // Assert
+ Assert.Equal(0, tracker.CurrentLocation.CharacterIndex);
+ }
+
+ [Fact]
+ public void UpdateLocationAdvancesLineIndexOnSlashRFollowedByNonNewlineCharacter()
+ {
+ // Arrange
+ SourceLocationTracker tracker = new SourceLocationTracker(TestStartLocation);
+
+ // Act
+ tracker.UpdateLocation('\r', 'o');
+
+ // Assert
+ Assert.Equal(43, tracker.CurrentLocation.LineIndex);
+ }
+
+ [Fact]
+ public void UpdateLocationAdvancesAbsoluteIndexOnSlashRFollowedByNonNewlineCharacter()
+ {
+ // Arrange
+ SourceLocationTracker tracker = new SourceLocationTracker(TestStartLocation);
+
+ // Act
+ tracker.UpdateLocation('\r', 'o');
+
+ // Assert
+ Assert.Equal(11, tracker.CurrentLocation.AbsoluteIndex);
+ }
+
+ [Fact]
+ public void UpdateLocationResetsCharacterIndexOnSlashRFollowedByNonNewlineCharacter()
+ {
+ // Arrange
+ SourceLocationTracker tracker = new SourceLocationTracker(TestStartLocation);
+
+ // Act
+ tracker.UpdateLocation('\r', 'o');
+
+ // Assert
+ Assert.Equal(0, tracker.CurrentLocation.CharacterIndex);
+ }
+
+ [Fact]
+ public void UpdateLocationDoesNotAdvanceLineIndexOnSlashRFollowedBySlashN()
+ {
+ // Arrange
+ SourceLocationTracker tracker = new SourceLocationTracker(TestStartLocation);
+
+ // Act
+ tracker.UpdateLocation('\r', '\n');
+
+ // Assert
+ Assert.Equal(42, tracker.CurrentLocation.LineIndex);
+ }
+
+ [Fact]
+ public void UpdateLocationAdvancesAbsoluteIndexOnSlashRFollowedBySlashN()
+ {
+ // Arrange
+ SourceLocationTracker tracker = new SourceLocationTracker(TestStartLocation);
+
+ // Act
+ tracker.UpdateLocation('\r', '\n');
+
+ // Assert
+ Assert.Equal(11, tracker.CurrentLocation.AbsoluteIndex);
+ }
+
+ [Fact]
+ public void UpdateLocationAdvancesCharacterIndexOnSlashRFollowedBySlashN()
+ {
+ // Arrange
+ SourceLocationTracker tracker = new SourceLocationTracker(TestStartLocation);
+
+ // Act
+ tracker.UpdateLocation('\r', '\n');
+
+ // Assert
+ Assert.Equal(46, tracker.CurrentLocation.CharacterIndex);
+ }
+ }
+}
diff --git a/test/System.Web.Razor.Test/Text/TextBufferReaderTest.cs b/test/System.Web.Razor.Test/Text/TextBufferReaderTest.cs
new file mode 100644
index 00000000..bb244411
--- /dev/null
+++ b/test/System.Web.Razor.Test/Text/TextBufferReaderTest.cs
@@ -0,0 +1,229 @@
+using System.Web.Razor.Text;
+using System.Web.WebPages.TestUtils;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Razor.Test.Text
+{
+ public class TextBufferReaderTest : LookaheadTextReaderTestBase
+ {
+ protected override LookaheadTextReader CreateReader(string testString)
+ {
+ return new TextBufferReader(new StringTextBuffer(testString));
+ }
+
+ [Fact]
+ public void ConstructorRequiresNonNullTextBuffer()
+ {
+ Assert.ThrowsArgumentNull(() => new TextBufferReader(null), "buffer");
+ }
+
+ [Fact]
+ public void PeekReturnsCurrentCharacterWithoutAdvancingPosition()
+ {
+ RunPeekTest("abc", peekAt: 2);
+ }
+
+ [Fact]
+ public void PeekReturnsNegativeOneAtEndOfSourceReader()
+ {
+ RunPeekTest("abc", peekAt: 3);
+ }
+
+ [Fact]
+ public void ReadReturnsCurrentCharacterAndAdvancesToNextCharacter()
+ {
+ RunReadTest("abc", readAt: 2);
+ }
+
+ [Fact]
+ public void EndingLookaheadReturnsReaderToPreviousLocation()
+ {
+ RunLookaheadTest("abcdefg", "abcb",
+ Read,
+ Lookahead(
+ Read,
+ Read),
+ Read);
+ }
+
+ [Fact]
+ public void MultipleLookaheadsCanBePerformed()
+ {
+ RunLookaheadTest("abcdefg", "abcbcdc",
+ Read,
+ Lookahead(
+ Read,
+ Read),
+ Read,
+ Lookahead(
+ Read,
+ Read),
+ Read);
+ }
+
+ [Fact]
+ public void LookaheadsCanBeNested()
+ {
+ RunLookaheadTest("abcdefg", "abcdefebc",
+ Read, // Appended: "a" Reader: "bcdefg"
+ Lookahead( // Reader: "bcdefg"
+ Read, // Appended: "b" Reader: "cdefg";
+ Read, // Appended: "c" Reader: "defg";
+ Read, // Appended: "d" Reader: "efg";
+ Lookahead( // Reader: "efg"
+ Read, // Appended: "e" Reader: "fg";
+ Read // Appended: "f" Reader: "g";
+ ), // Reader: "efg"
+ Read // Appended: "e" Reader: "fg";
+ ), // Reader: "bcdefg"
+ Read, // Appended: "b" Reader: "cdefg";
+ Read); // Appended: "c" Reader: "defg";
+ }
+
+ [Fact]
+ public void SourceLocationIsZeroWhenInitialized()
+ {
+ RunSourceLocationTest("abcdefg", SourceLocation.Zero, checkAt: 0);
+ }
+
+ [Fact]
+ public void CharacterAndAbsoluteIndicesIncreaseAsCharactersAreRead()
+ {
+ RunSourceLocationTest("abcdefg", new SourceLocation(4, 0, 4), checkAt: 4);
+ }
+
+ [Fact]
+ public void CharacterAndAbsoluteIndicesIncreaseAsSlashRInTwoCharacterNewlineIsRead()
+ {
+ RunSourceLocationTest("f\r\nb", new SourceLocation(2, 0, 2), checkAt: 2);
+ }
+
+ [Fact]
+ public void CharacterIndexResetsToZeroAndLineIndexIncrementsWhenSlashNInTwoCharacterNewlineIsRead()
+ {
+ RunSourceLocationTest("f\r\nb", new SourceLocation(3, 1, 0), checkAt: 3);
+ }
+
+ [Fact]
+ public void CharacterIndexResetsToZeroAndLineIndexIncrementsWhenSlashRInSingleCharacterNewlineIsRead()
+ {
+ RunSourceLocationTest("f\rb", new SourceLocation(2, 1, 0), checkAt: 2);
+ }
+
+ [Fact]
+ public void CharacterIndexResetsToZeroAndLineIndexIncrementsWhenSlashNInSingleCharacterNewlineIsRead()
+ {
+ RunSourceLocationTest("f\nb", new SourceLocation(2, 1, 0), checkAt: 2);
+ }
+
+ [Fact]
+ public void EndingLookaheadResetsRawCharacterAndLineIndexToValuesWhenLookaheadBegan()
+ {
+ RunEndLookaheadUpdatesSourceLocationTest();
+ }
+
+ [Fact]
+ public void OnceBufferingBeginsReadsCanContinuePastEndOfBuffer()
+ {
+ RunLookaheadTest("abcdefg", "abcbcdefg",
+ Read,
+ Lookahead(Read(2)),
+ Read(2),
+ ReadToEnd);
+ }
+
+ [Fact]
+ public void DisposeDisposesSourceReader()
+ {
+ RunDisposeTest(r => r.Dispose());
+ }
+
+ [Fact]
+ public void CloseDisposesSourceReader()
+ {
+ RunDisposeTest(r => r.Close());
+ }
+
+ [Fact]
+ public void ReadWithBufferSupportsLookahead()
+ {
+ RunBufferReadTest((reader, buffer, index, count) => reader.Read(buffer, index, count));
+ }
+
+ [Fact]
+ public void ReadBlockSupportsLookahead()
+ {
+ RunBufferReadTest((reader, buffer, index, count) => reader.ReadBlock(buffer, index, count));
+ }
+
+ [Fact]
+ public void ReadLineSupportsLookahead()
+ {
+ RunReadUntilTest(r => r.ReadLine(), expectedRaw: 8, expectedChar: 0, expectedLine: 2);
+ }
+
+ [Fact]
+ public void ReadToEndSupportsLookahead()
+ {
+ RunReadUntilTest(r => r.ReadToEnd(), expectedRaw: 11, expectedChar: 3, expectedLine: 2);
+ }
+
+ [Fact]
+ public void ReadLineMaintainsCorrectCharacterPosition()
+ {
+ RunSourceLocationTest("abc\r\ndef", new SourceLocation(5, 1, 0), r => r.ReadLine());
+ }
+
+ [Fact]
+ public void ReadToEndWorksAsInNormalTextReader()
+ {
+ RunReadToEndTest();
+ }
+
+ [Fact]
+ public void CancelBacktrackStopsNextEndLookaheadFromBacktracking()
+ {
+ RunLookaheadTest("abcdefg", "abcdefg",
+ Lookahead(
+ Read(2),
+ CancelBacktrack
+ ),
+ ReadToEnd);
+ }
+
+ [Fact]
+ public void CancelBacktrackThrowsInvalidOperationExceptionIfCalledOutsideOfLookahead()
+ {
+ RunCancelBacktrackOutsideLookaheadTest();
+ }
+
+ [Fact]
+ public void CancelBacktrackOnlyCancelsBacktrackingForInnermostNestedLookahead()
+ {
+ RunLookaheadTest("abcdefg", "abcdabcdefg",
+ Lookahead(
+ Read(2),
+ Lookahead(
+ Read,
+ CancelBacktrack
+ ),
+ Read
+ ),
+ ReadToEnd);
+ }
+
+ private static void RunDisposeTest(Action<LookaheadTextReader> triggerAction)
+ {
+ // Arrange
+ StringTextBuffer source = new StringTextBuffer("abcdefg");
+ LookaheadTextReader reader = new TextBufferReader(source);
+
+ // Act
+ triggerAction(reader);
+
+ // Assert
+ Assert.True(source.Disposed);
+ }
+ }
+}
diff --git a/test/System.Web.Razor.Test/Text/TextChangeTest.cs b/test/System.Web.Razor.Test/Text/TextChangeTest.cs
new file mode 100644
index 00000000..6e9e64d5
--- /dev/null
+++ b/test/System.Web.Razor.Test/Text/TextChangeTest.cs
@@ -0,0 +1,249 @@
+using System.Web.Razor.Text;
+using System.Web.WebPages.TestUtils;
+using Moq;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Razor.Test.Text
+{
+ public class TextChangeTest
+ {
+ [Fact]
+ public void ConstructorRequiresNonNegativeOldPosition()
+ {
+ Assert.ThrowsArgumentOutOfRange(() => new TextChange(-1, 0, new Mock<ITextBuffer>().Object, 0, 0, new Mock<ITextBuffer>().Object), "oldPosition", "Value must be greater than or equal to 0.");
+ }
+
+ [Fact]
+ public void ConstructorRequiresNonNegativeNewPosition()
+ {
+ Assert.ThrowsArgumentOutOfRange(() => new TextChange(0, 0, new Mock<ITextBuffer>().Object, -1, 0, new Mock<ITextBuffer>().Object), "newPosition", "Value must be greater than or equal to 0.");
+ }
+
+ [Fact]
+ public void ConstructorRequiresNonNegativeOldLength()
+ {
+ Assert.ThrowsArgumentOutOfRange(() => new TextChange(0, -1, new Mock<ITextBuffer>().Object, 0, 0, new Mock<ITextBuffer>().Object), "oldLength", "Value must be greater than or equal to 0.");
+ }
+
+ [Fact]
+ public void ConstructorRequiresNonNegativeNewLength()
+ {
+ Assert.ThrowsArgumentOutOfRange(() => new TextChange(0, 0, new Mock<ITextBuffer>().Object, 0, -1, new Mock<ITextBuffer>().Object), "newLength", "Value must be greater than or equal to 0.");
+ }
+
+ [Fact]
+ public void ConstructorRequiresNonNullOldBuffer()
+ {
+ Assert.ThrowsArgumentNull(() => new TextChange(0, 0, null, 0, 0, new Mock<ITextBuffer>().Object), "oldBuffer");
+ }
+
+ [Fact]
+ public void ConstructorRequiresNonNullNewBuffer()
+ {
+ Assert.ThrowsArgumentNull(() => new TextChange(0, 0, new Mock<ITextBuffer>().Object, 0, 0, null), "newBuffer");
+ }
+
+ [Fact]
+ public void ConstructorInitializesProperties()
+ {
+ // Act
+ ITextBuffer oldBuffer = new Mock<ITextBuffer>().Object;
+ ITextBuffer newBuffer = new Mock<ITextBuffer>().Object;
+ TextChange change = new TextChange(42, 24, oldBuffer, 1337, newBuffer);
+
+ // Assert
+ Assert.Equal(42, change.OldPosition);
+ Assert.Equal(24, change.OldLength);
+ Assert.Equal(1337, change.NewLength);
+ Assert.Same(newBuffer, change.NewBuffer);
+ Assert.Same(oldBuffer, change.OldBuffer);
+ }
+
+ [Fact]
+ public void TestIsDelete()
+ {
+ // Arrange
+ ITextBuffer oldBuffer = new Mock<ITextBuffer>().Object;
+ ITextBuffer newBuffer = new Mock<ITextBuffer>().Object;
+ TextChange change = new TextChange(0, 1, oldBuffer, 0, newBuffer);
+
+ // Assert
+ Assert.True(change.IsDelete);
+ }
+
+ [Fact]
+ public void TestIsInsert()
+ {
+ // Arrange
+ ITextBuffer oldBuffer = new Mock<ITextBuffer>().Object;
+ ITextBuffer newBuffer = new Mock<ITextBuffer>().Object;
+ TextChange change = new TextChange(0, 0, oldBuffer, 35, newBuffer);
+
+ // Assert
+ Assert.True(change.IsInsert);
+ }
+
+ [Fact]
+ public void TestIsReplace()
+ {
+ // Arrange
+ ITextBuffer oldBuffer = new Mock<ITextBuffer>().Object;
+ ITextBuffer newBuffer = new Mock<ITextBuffer>().Object;
+ TextChange change = new TextChange(0, 5, oldBuffer, 10, newBuffer);
+
+ // Assert
+ Assert.True(change.IsReplace);
+ }
+
+ [Fact]
+ public void OldTextReturnsOldSpanFromOldBuffer()
+ {
+ // Arrange
+ var newBuffer = new StringTextBuffer("test");
+ var oldBuffer = new StringTextBuffer("text");
+ var textChange = new TextChange(2, 1, oldBuffer, 1, newBuffer);
+
+ // Act
+ string text = textChange.OldText;
+
+ // Assert
+ Assert.Equal("x", text);
+ }
+
+ [Fact]
+ public void NewTextWithInsertReturnsChangedTextFromBuffer()
+ {
+ // Arrange
+ var newBuffer = new StringTextBuffer("test");
+ var oldBuffer = new StringTextBuffer("");
+ var textChange = new TextChange(0, 0, oldBuffer, 3, newBuffer);
+
+ // Act
+ string text = textChange.NewText;
+
+ // Assert
+ Assert.Equal("tes", text);
+ }
+
+ [Fact]
+ public void NewTextWithDeleteReturnsEmptyString()
+ {
+ // Arrange
+ var newBuffer = new StringTextBuffer("test");
+ var oldBuffer = new StringTextBuffer("");
+ var textChange = new TextChange(1, 1, oldBuffer, 0, newBuffer);
+
+ // Act
+ string text = textChange.NewText;
+
+ // Assert
+ Assert.Equal(String.Empty, text);
+ }
+
+ [Fact]
+ public void NewTextWithReplaceReturnsChangedTextFromBuffer()
+ {
+ // Arrange
+ var newBuffer = new StringTextBuffer("test");
+ var oldBuffer = new StringTextBuffer("");
+ var textChange = new TextChange(2, 2, oldBuffer, 1, newBuffer);
+
+ // Act
+ string text = textChange.NewText;
+
+ // Assert
+ Assert.Equal("s", text);
+ }
+
+ [Fact]
+ public void ApplyChangeWithInsertedTextReturnsNewContentWithChangeApplied()
+ {
+ // Arrange
+ var newBuffer = new StringTextBuffer("test");
+ var oldBuffer = new StringTextBuffer("");
+ var textChange = new TextChange(0, 0, oldBuffer, 3, newBuffer);
+
+ // Act
+ string text = textChange.ApplyChange("abcd", 0);
+
+ // Assert
+ Assert.Equal("tesabcd", text);
+ }
+
+ [Fact]
+ public void ApplyChangeWithRemovedTextReturnsNewContentWithChangeApplied()
+ {
+ // Arrange
+ var newBuffer = new StringTextBuffer("abcdefg");
+ var oldBuffer = new StringTextBuffer("");
+ var textChange = new TextChange(1, 1, oldBuffer, 0, newBuffer);
+
+ // Act
+ string text = textChange.ApplyChange("abcdefg", 1);
+
+ // Assert
+ Assert.Equal("bcdefg", text);
+ }
+
+ [Fact]
+ public void ApplyChangeWithReplacedTextReturnsNewContentWithChangeApplied()
+ {
+ // Arrange
+ var newBuffer = new StringTextBuffer("abcdefg");
+ var oldBuffer = new StringTextBuffer("");
+ var textChange = new TextChange(1, 1, oldBuffer, 2, newBuffer);
+
+ // Act
+ string text = textChange.ApplyChange("abcdefg", 1);
+
+ // Assert
+ Assert.Equal("bcbcdefg", text);
+ }
+
+ [Fact]
+ public void NormalizeFixesUpIntelliSenseStyleReplacements()
+ {
+ // Arrange
+ var newBuffer = new StringTextBuffer("Date.");
+ var oldBuffer = new StringTextBuffer("Date");
+ var original = new TextChange(0, 4, oldBuffer, 5, newBuffer);
+
+ // Act
+ TextChange normalized = original.Normalize();
+
+ // Assert
+ Assert.Equal(new TextChange(4, 0, oldBuffer, 1, newBuffer), normalized);
+ }
+
+ [Fact]
+ public void NormalizeDoesntAffectChangesWithoutCommonPrefixes()
+ {
+ // Arrange
+ var newBuffer = new StringTextBuffer("DateTime.");
+ var oldBuffer = new StringTextBuffer("Date.");
+ var original = new TextChange(0, 5, oldBuffer, 9, newBuffer);
+
+ // Act
+ TextChange normalized = original.Normalize();
+
+ // Assert
+ Assert.Equal(original, normalized);
+ }
+
+ [Fact]
+ public void NormalizeDoesntAffectShrinkingReplacements()
+ {
+ // Arrange
+ var newBuffer = new StringTextBuffer("D");
+ var oldBuffer = new StringTextBuffer("DateTime");
+ var original = new TextChange(0, 8, oldBuffer, 1, newBuffer);
+
+ // Act
+ TextChange normalized = original.Normalize();
+
+ // Assert
+ Assert.Equal(original, normalized);
+ }
+ }
+}
diff --git a/test/System.Web.Razor.Test/Text/TextReaderExtensionsTest.cs b/test/System.Web.Razor.Test/Text/TextReaderExtensionsTest.cs
new file mode 100644
index 00000000..8d544a8b
--- /dev/null
+++ b/test/System.Web.Razor.Test/Text/TextReaderExtensionsTest.cs
@@ -0,0 +1,184 @@
+using System.IO;
+using System.Web.Razor.Parser;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Razor.Test.Text
+{
+ public class TextReaderExtensionsTest
+ {
+ [Fact]
+ public void ReadUntilWithCharThrowsArgNullIfReaderNull()
+ {
+ Assert.ThrowsArgumentNull(() => TextReaderExtensions.ReadUntil(null, '@'), "reader");
+ }
+
+ [Fact]
+ public void ReadUntilInclusiveWithCharThrowsArgNullIfReaderNull()
+ {
+ Assert.ThrowsArgumentNull(() => TextReaderExtensions.ReadUntil(null, '@', inclusive: true), "reader");
+ }
+
+ [Fact]
+ public void ReadUntilWithMultipleTerminatorsThrowsArgNullIfReaderNull()
+ {
+ Assert.ThrowsArgumentNull(() => TextReaderExtensions.ReadUntil(null, '/', '>'), "reader");
+ }
+
+ [Fact]
+ public void ReadUntilInclusiveWithMultipleTerminatorsThrowsArgNullIfReaderNull()
+ {
+ // NOTE: Using named parameters would be difficult here, hence the inline comment
+ Assert.ThrowsArgumentNull(() => TextReaderExtensions.ReadUntil(null, /* inclusive */ true, '/', '>'), "reader");
+ }
+
+ [Fact]
+ public void ReadUntilWithPredicateThrowsArgNullIfReaderNull()
+ {
+ Assert.ThrowsArgumentNull(() => TextReaderExtensions.ReadUntil(null, c => true), "reader");
+ }
+
+ [Fact]
+ public void ReadUntilInclusiveWithPredicateThrowsArgNullIfReaderNull()
+ {
+ Assert.ThrowsArgumentNull(() => TextReaderExtensions.ReadUntil(null, c => true, inclusive: true), "reader");
+ }
+
+ [Fact]
+ public void ReadUntilWithPredicateThrowsArgExceptionIfPredicateNull()
+ {
+ Assert.ThrowsArgumentNull(() => TextReaderExtensions.ReadUntil(new StringReader("Foo"), (Predicate<char>)null), "condition");
+ }
+
+ [Fact]
+ public void ReadUntilInclusiveWithPredicateThrowsArgExceptionIfPredicateNull()
+ {
+ Assert.ThrowsArgumentNull(() => TextReaderExtensions.ReadUntil(new StringReader("Foo"), (Predicate<char>)null, inclusive: true), "condition");
+ }
+
+ [Fact]
+ public void ReadWhileWithPredicateThrowsArgNullIfReaderNull()
+ {
+ Assert.ThrowsArgumentNull(() => TextReaderExtensions.ReadWhile(null, c => true), "reader");
+ }
+
+ [Fact]
+ public void ReadWhileInclusiveWithPredicateThrowsArgNullIfReaderNull()
+ {
+ Assert.ThrowsArgumentNull(() => TextReaderExtensions.ReadWhile(null, c => true, inclusive: true), "reader");
+ }
+
+ [Fact]
+ public void ReadWhileWithPredicateThrowsArgNullIfPredicateNull()
+ {
+ Assert.ThrowsArgumentNull(() => TextReaderExtensions.ReadWhile(new StringReader("Foo"), (Predicate<char>)null), "condition");
+ }
+
+ [Fact]
+ public void ReadWhileInclusiveWithPredicateThrowsArgNullIfPredicateNull()
+ {
+ Assert.ThrowsArgumentNull(() => TextReaderExtensions.ReadWhile(new StringReader("Foo"), (Predicate<char>)null, inclusive: true), "condition");
+ }
+
+ [Fact]
+ public void ReadUntilWithCharReadsAllTextUpToSpecifiedCharacterButNotPast()
+ {
+ RunReaderTest("foo bar baz @biz", "foo bar baz ", '@', r => r.ReadUntil('@'));
+ }
+
+ [Fact]
+ public void ReadUntilWithCharWithInclusiveFlagReadsAllTextUpToSpecifiedCharacterButNotPastIfInclusiveFalse()
+ {
+ RunReaderTest("foo bar baz @biz", "foo bar baz ", '@', r => r.ReadUntil('@', inclusive: false));
+ }
+
+ [Fact]
+ public void ReadUntilWithCharWithInclusiveFlagReadsAllTextUpToAndIncludingSpecifiedCharacterIfInclusiveTrue()
+ {
+ RunReaderTest("foo bar baz @biz", "foo bar baz @", 'b', r => r.ReadUntil('@', inclusive: true));
+ }
+
+ [Fact]
+ public void ReadUntilWithCharReadsToEndIfSpecifiedCharacterNotFound()
+ {
+ RunReaderTest("foo bar baz", "foo bar baz", -1, r => r.ReadUntil('@'));
+ }
+
+ [Fact]
+ public void ReadUntilWithMultipleTerminatorsReadsUntilAnyTerminatorIsFound()
+ {
+ RunReaderTest("<bar/>", "<bar", '/', r => r.ReadUntil('/', '>'));
+ }
+
+ [Fact]
+ public void ReadUntilWithMultipleTerminatorsHonorsInclusiveFlagWhenFalse()
+ {
+ // NOTE: Using named parameters would be difficult here, hence the inline comment
+ RunReaderTest("<bar/>", "<bar", '/', r => r.ReadUntil( /* inclusive */ false, '/', '>'));
+ }
+
+ [Fact]
+ public void ReadUntilWithMultipleTerminatorsHonorsInclusiveFlagWhenTrue()
+ {
+ // NOTE: Using named parameters would be difficult here, hence the inline comment
+ RunReaderTest("<bar/>", "<bar/", '>', r => r.ReadUntil( /* inclusive */ true, '/', '>'));
+ }
+
+ [Fact]
+ public void ReadUntilWithPredicateStopsWhenPredicateIsTrue()
+ {
+ RunReaderTest("foo bar baz 0 zoop zork zoink", "foo bar baz ", '0', r => r.ReadUntil(c => Char.IsDigit(c)));
+ }
+
+ [Fact]
+ public void ReadUntilWithPredicateHonorsInclusiveFlagWhenFalse()
+ {
+ RunReaderTest("foo bar baz 0 zoop zork zoink", "foo bar baz ", '0', r => r.ReadUntil(c => Char.IsDigit(c), inclusive: false));
+ }
+
+ [Fact]
+ public void ReadUntilWithPredicateHonorsInclusiveFlagWhenTrue()
+ {
+ RunReaderTest("foo bar baz 0 zoop zork zoink", "foo bar baz 0", ' ', r => r.ReadUntil(c => Char.IsDigit(c), inclusive: true));
+ }
+
+ [Fact]
+ public void ReadWhileWithPredicateStopsWhenPredicateIsFalse()
+ {
+ RunReaderTest("012345a67890", "012345", 'a', r => r.ReadWhile(c => Char.IsDigit(c)));
+ }
+
+ [Fact]
+ public void ReadWhileWithPredicateHonorsInclusiveFlagWhenFalse()
+ {
+ RunReaderTest("012345a67890", "012345", 'a', r => r.ReadWhile(c => Char.IsDigit(c), inclusive: false));
+ }
+
+ [Fact]
+ public void ReadWhileWithPredicateHonorsInclusiveFlagWhenTrue()
+ {
+ RunReaderTest("012345a67890", "012345a", '6', r => r.ReadWhile(c => Char.IsDigit(c), inclusive: true));
+ }
+
+ private static void RunReaderTest(string testString, string expectedOutput, int expectedPeek, Func<TextReader, string> action)
+ {
+ // Arrange
+ StringReader reader = new StringReader(testString);
+
+ // Act
+ string read = action(reader);
+
+ // Assert
+ Assert.Equal(expectedOutput, read);
+
+ if (expectedPeek == -1)
+ {
+ Assert.True(reader.Peek() == -1, "Expected that the reader would be positioned at the end of the input stream");
+ }
+ else
+ {
+ Assert.Equal((char)expectedPeek, (char)reader.Peek());
+ }
+ }
+ }
+}
diff --git a/test/System.Web.Razor.Test/Tokenizer/CSharpTokenizerCommentTest.cs b/test/System.Web.Razor.Test/Tokenizer/CSharpTokenizerCommentTest.cs
new file mode 100644
index 00000000..559b0117
--- /dev/null
+++ b/test/System.Web.Razor.Test/Tokenizer/CSharpTokenizerCommentTest.cs
@@ -0,0 +1,87 @@
+using System.Web.Razor.Tokenizer.Symbols;
+using Xunit;
+
+namespace System.Web.Razor.Test.Tokenizer
+{
+ public class CSharpTokenizerCommentTest : CSharpTokenizerTestBase
+ {
+ [Fact]
+ public void Next_Ignores_Star_At_EOF_In_RazorComment()
+ {
+ TestTokenizer("@* Foo * Bar * Baz *",
+ new CSharpSymbol(0, 0, 0, "@", CSharpSymbolType.RazorCommentTransition),
+ new CSharpSymbol(1, 0, 1, "*", CSharpSymbolType.RazorCommentStar),
+ new CSharpSymbol(2, 0, 2, " Foo * Bar * Baz *", CSharpSymbolType.RazorComment));
+ }
+
+ [Fact]
+ public void Next_Ignores_Star_Without_Trailing_At()
+ {
+ TestTokenizer("@* Foo * Bar * Baz *@",
+ new CSharpSymbol(0, 0, 0, "@", CSharpSymbolType.RazorCommentTransition),
+ new CSharpSymbol(1, 0, 1, "*", CSharpSymbolType.RazorCommentStar),
+ new CSharpSymbol(2, 0, 2, " Foo * Bar * Baz ", CSharpSymbolType.RazorComment),
+ new CSharpSymbol(19, 0, 19, "*", CSharpSymbolType.RazorCommentStar),
+ new CSharpSymbol(20, 0, 20, "@", CSharpSymbolType.RazorCommentTransition));
+ }
+
+ [Fact]
+ public void Next_Returns_RazorComment_Token_For_Entire_Razor_Comment()
+ {
+ TestTokenizer("@* Foo Bar Baz *@",
+ new CSharpSymbol(0, 0, 0, "@", CSharpSymbolType.RazorCommentTransition),
+ new CSharpSymbol(1, 0, 1, "*", CSharpSymbolType.RazorCommentStar),
+ new CSharpSymbol(2, 0, 2, " Foo Bar Baz ", CSharpSymbolType.RazorComment),
+ new CSharpSymbol(15, 0, 15, "*", CSharpSymbolType.RazorCommentStar),
+ new CSharpSymbol(16, 0, 16, "@", CSharpSymbolType.RazorCommentTransition));
+ }
+
+ [Fact]
+ public void Next_Returns_Comment_Token_For_Entire_Single_Line_Comment()
+ {
+ TestTokenizer("// Foo Bar Baz", new CSharpSymbol(0, 0, 0, "// Foo Bar Baz", CSharpSymbolType.Comment));
+ }
+
+ [Fact]
+ public void Single_Line_Comment_Is_Terminated_By_Newline()
+ {
+ TestTokenizer("// Foo Bar Baz\na", new CSharpSymbol(0, 0, 0, "// Foo Bar Baz", CSharpSymbolType.Comment), IgnoreRemaining);
+ }
+
+ [Fact]
+ public void Multi_Line_Comment_In_Single_Line_Comment_Has_No_Effect()
+ {
+ TestTokenizer("// Foo/*Bar*/ Baz\na", new CSharpSymbol(0, 0, 0, "// Foo/*Bar*/ Baz", CSharpSymbolType.Comment), IgnoreRemaining);
+ }
+
+ [Fact]
+ public void Next_Returns_Comment_Token_For_Entire_Multi_Line_Comment()
+ {
+ TestTokenizer("/* Foo\nBar\nBaz */", new CSharpSymbol(0, 0, 0, "/* Foo\nBar\nBaz */", CSharpSymbolType.Comment));
+ }
+
+ [Fact]
+ public void Multi_Line_Comment_Is_Terminated_By_End_Sequence()
+ {
+ TestTokenizer("/* Foo\nBar\nBaz */a", new CSharpSymbol(0, 0, 0, "/* Foo\nBar\nBaz */", CSharpSymbolType.Comment), IgnoreRemaining);
+ }
+
+ [Fact]
+ public void Unterminated_Multi_Line_Comment_Captures_To_EOF()
+ {
+ TestTokenizer("/* Foo\nBar\nBaz", new CSharpSymbol(0, 0, 0, "/* Foo\nBar\nBaz", CSharpSymbolType.Comment), IgnoreRemaining);
+ }
+
+ [Fact]
+ public void Nested_Multi_Line_Comments_Terminated_At_First_End_Sequence()
+ {
+ TestTokenizer("/* Foo/*\nBar\nBaz*/ */", new CSharpSymbol(0, 0, 0, "/* Foo/*\nBar\nBaz*/", CSharpSymbolType.Comment), IgnoreRemaining);
+ }
+
+ [Fact]
+ public void Nested_Multi_Line_Comments_Terminated_At_Full_End_Sequence()
+ {
+ TestTokenizer("/* Foo\nBar\nBaz* */", new CSharpSymbol(0, 0, 0, "/* Foo\nBar\nBaz* */", CSharpSymbolType.Comment), IgnoreRemaining);
+ }
+ }
+}
diff --git a/test/System.Web.Razor.Test/Tokenizer/CSharpTokenizerIdentifierTest.cs b/test/System.Web.Razor.Test/Tokenizer/CSharpTokenizerIdentifierTest.cs
new file mode 100644
index 00000000..a35f483c
--- /dev/null
+++ b/test/System.Web.Razor.Test/Tokenizer/CSharpTokenizerIdentifierTest.cs
@@ -0,0 +1,167 @@
+using System.Web.Razor.Tokenizer.Symbols;
+using Xunit;
+
+namespace System.Web.Razor.Test.Tokenizer
+{
+ public class CSharpTokenizerIdentifierTest : CSharpTokenizerTestBase
+ {
+ [Fact]
+ public void Simple_Identifier_Is_Recognized()
+ {
+ TestTokenizer("foo", new CSharpSymbol(0, 0, 0, "foo", CSharpSymbolType.Identifier));
+ }
+
+ [Fact]
+ public void Identifier_Starting_With_Underscore_Is_Recognized()
+ {
+ TestTokenizer("_foo", new CSharpSymbol(0, 0, 0, "_foo", CSharpSymbolType.Identifier));
+ }
+
+ [Fact]
+ public void Identifier_Can_Contain_Digits()
+ {
+ TestTokenizer("foo4", new CSharpSymbol(0, 0, 0, "foo4", CSharpSymbolType.Identifier));
+ }
+
+ [Fact]
+ public void Identifier_Can_Start_With_Titlecase_Letter()
+ {
+ TestTokenizer("ῼfoo", new CSharpSymbol(0, 0, 0, "ῼfoo", CSharpSymbolType.Identifier));
+ }
+
+ [Fact]
+ public void Identifier_Can_Start_With_Letter_Modifier()
+ {
+ TestTokenizer("ᵊfoo", new CSharpSymbol(0, 0, 0, "ᵊfoo", CSharpSymbolType.Identifier));
+ }
+
+ [Fact]
+ public void Identifier_Can_Start_With_Other_Letter()
+ {
+ TestTokenizer("ƻfoo", new CSharpSymbol(0, 0, 0, "ƻfoo", CSharpSymbolType.Identifier));
+ }
+
+ [Fact]
+ public void Identifier_Can_Start_With_Number_Letter()
+ {
+ TestTokenizer("Ⅽool", new CSharpSymbol(0, 0, 0, "Ⅽool", CSharpSymbolType.Identifier));
+ }
+
+ [Fact]
+ public void Identifier_Can_Contain_Non_Spacing_Mark()
+ {
+ TestTokenizer("foo\u0300", new CSharpSymbol(0, 0, 0, "foo\u0300", CSharpSymbolType.Identifier));
+ }
+
+ [Fact]
+ public void Identifier_Can_Contain_Spacing_Combining_Mark()
+ {
+ TestTokenizer("fooः", new CSharpSymbol(0, 0, 0, "fooः", CSharpSymbolType.Identifier));
+ }
+
+ [Fact]
+ public void Identifier_Can_Contain_Non_English_Digit()
+ {
+ TestTokenizer("foo١", new CSharpSymbol(0, 0, 0, "foo١", CSharpSymbolType.Identifier));
+ }
+
+ [Fact]
+ public void Identifier_Can_Contain_Connector_Punctuation()
+ {
+ TestTokenizer("foo‿bar", new CSharpSymbol(0, 0, 0, "foo‿bar", CSharpSymbolType.Identifier));
+ }
+
+ [Fact]
+ public void Identifier_Can_Contain_Format_Character()
+ {
+ TestTokenizer("foo؃bar", new CSharpSymbol(0, 0, 0, "foo؃bar", CSharpSymbolType.Identifier));
+ }
+
+ [Fact]
+ public void Keywords_Are_Recognized_As_Keyword_Tokens()
+ {
+ TestKeyword("abstract", CSharpKeyword.Abstract);
+ TestKeyword("byte", CSharpKeyword.Byte);
+ TestKeyword("class", CSharpKeyword.Class);
+ TestKeyword("delegate", CSharpKeyword.Delegate);
+ TestKeyword("event", CSharpKeyword.Event);
+ TestKeyword("fixed", CSharpKeyword.Fixed);
+ TestKeyword("if", CSharpKeyword.If);
+ TestKeyword("internal", CSharpKeyword.Internal);
+ TestKeyword("new", CSharpKeyword.New);
+ TestKeyword("override", CSharpKeyword.Override);
+ TestKeyword("readonly", CSharpKeyword.Readonly);
+ TestKeyword("short", CSharpKeyword.Short);
+ TestKeyword("struct", CSharpKeyword.Struct);
+ TestKeyword("try", CSharpKeyword.Try);
+ TestKeyword("unsafe", CSharpKeyword.Unsafe);
+ TestKeyword("volatile", CSharpKeyword.Volatile);
+ TestKeyword("as", CSharpKeyword.As);
+ TestKeyword("do", CSharpKeyword.Do);
+ TestKeyword("is", CSharpKeyword.Is);
+ TestKeyword("params", CSharpKeyword.Params);
+ TestKeyword("ref", CSharpKeyword.Ref);
+ TestKeyword("switch", CSharpKeyword.Switch);
+ TestKeyword("ushort", CSharpKeyword.Ushort);
+ TestKeyword("while", CSharpKeyword.While);
+ TestKeyword("case", CSharpKeyword.Case);
+ TestKeyword("const", CSharpKeyword.Const);
+ TestKeyword("explicit", CSharpKeyword.Explicit);
+ TestKeyword("float", CSharpKeyword.Float);
+ TestKeyword("null", CSharpKeyword.Null);
+ TestKeyword("sizeof", CSharpKeyword.Sizeof);
+ TestKeyword("typeof", CSharpKeyword.Typeof);
+ TestKeyword("implicit", CSharpKeyword.Implicit);
+ TestKeyword("private", CSharpKeyword.Private);
+ TestKeyword("this", CSharpKeyword.This);
+ TestKeyword("using", CSharpKeyword.Using);
+ TestKeyword("extern", CSharpKeyword.Extern);
+ TestKeyword("return", CSharpKeyword.Return);
+ TestKeyword("stackalloc", CSharpKeyword.Stackalloc);
+ TestKeyword("uint", CSharpKeyword.Uint);
+ TestKeyword("base", CSharpKeyword.Base);
+ TestKeyword("catch", CSharpKeyword.Catch);
+ TestKeyword("continue", CSharpKeyword.Continue);
+ TestKeyword("double", CSharpKeyword.Double);
+ TestKeyword("for", CSharpKeyword.For);
+ TestKeyword("in", CSharpKeyword.In);
+ TestKeyword("lock", CSharpKeyword.Lock);
+ TestKeyword("object", CSharpKeyword.Object);
+ TestKeyword("protected", CSharpKeyword.Protected);
+ TestKeyword("static", CSharpKeyword.Static);
+ TestKeyword("false", CSharpKeyword.False);
+ TestKeyword("public", CSharpKeyword.Public);
+ TestKeyword("sbyte", CSharpKeyword.Sbyte);
+ TestKeyword("throw", CSharpKeyword.Throw);
+ TestKeyword("virtual", CSharpKeyword.Virtual);
+ TestKeyword("decimal", CSharpKeyword.Decimal);
+ TestKeyword("else", CSharpKeyword.Else);
+ TestKeyword("operator", CSharpKeyword.Operator);
+ TestKeyword("string", CSharpKeyword.String);
+ TestKeyword("ulong", CSharpKeyword.Ulong);
+ TestKeyword("bool", CSharpKeyword.Bool);
+ TestKeyword("char", CSharpKeyword.Char);
+ TestKeyword("default", CSharpKeyword.Default);
+ TestKeyword("foreach", CSharpKeyword.Foreach);
+ TestKeyword("long", CSharpKeyword.Long);
+ TestKeyword("void", CSharpKeyword.Void);
+ TestKeyword("enum", CSharpKeyword.Enum);
+ TestKeyword("finally", CSharpKeyword.Finally);
+ TestKeyword("int", CSharpKeyword.Int);
+ TestKeyword("out", CSharpKeyword.Out);
+ TestKeyword("sealed", CSharpKeyword.Sealed);
+ TestKeyword("true", CSharpKeyword.True);
+ TestKeyword("goto", CSharpKeyword.Goto);
+ TestKeyword("unchecked", CSharpKeyword.Unchecked);
+ TestKeyword("interface", CSharpKeyword.Interface);
+ TestKeyword("break", CSharpKeyword.Break);
+ TestKeyword("checked", CSharpKeyword.Checked);
+ TestKeyword("namespace", CSharpKeyword.Namespace);
+ }
+
+ private void TestKeyword(string keyword, CSharpKeyword keywordType)
+ {
+ TestTokenizer(keyword, new CSharpSymbol(0, 0, 0, keyword, CSharpSymbolType.Keyword) { Keyword = keywordType });
+ }
+ }
+}
diff --git a/test/System.Web.Razor.Test/Tokenizer/CSharpTokenizerLiteralTest.cs b/test/System.Web.Razor.Test/Tokenizer/CSharpTokenizerLiteralTest.cs
new file mode 100644
index 00000000..b6a0df0f
--- /dev/null
+++ b/test/System.Web.Razor.Test/Tokenizer/CSharpTokenizerLiteralTest.cs
@@ -0,0 +1,222 @@
+using System.Web.Razor.Tokenizer.Symbols;
+using Xunit;
+
+namespace System.Web.Razor.Test.Tokenizer
+{
+ public class CSharpTokenizerLiteralTest : CSharpTokenizerTestBase
+ {
+ [Fact]
+ public void Simple_Integer_Literal_Is_Recognized()
+ {
+ TestSingleToken("01189998819991197253", CSharpSymbolType.IntegerLiteral);
+ }
+
+ [Fact]
+ public void Integer_Type_Suffix_Is_Recognized()
+ {
+ TestSingleToken("42U", CSharpSymbolType.IntegerLiteral);
+ TestSingleToken("42u", CSharpSymbolType.IntegerLiteral);
+
+ TestSingleToken("42L", CSharpSymbolType.IntegerLiteral);
+ TestSingleToken("42l", CSharpSymbolType.IntegerLiteral);
+
+ TestSingleToken("42UL", CSharpSymbolType.IntegerLiteral);
+ TestSingleToken("42Ul", CSharpSymbolType.IntegerLiteral);
+
+ TestSingleToken("42uL", CSharpSymbolType.IntegerLiteral);
+ TestSingleToken("42ul", CSharpSymbolType.IntegerLiteral);
+
+ TestSingleToken("42LU", CSharpSymbolType.IntegerLiteral);
+ TestSingleToken("42Lu", CSharpSymbolType.IntegerLiteral);
+
+ TestSingleToken("42lU", CSharpSymbolType.IntegerLiteral);
+ TestSingleToken("42lu", CSharpSymbolType.IntegerLiteral);
+ }
+
+ [Fact]
+ public void Trailing_Letter_Is_Not_Part_Of_Integer_Literal_If_Not_Type_Sufix()
+ {
+ TestTokenizer("42a", new CSharpSymbol(0, 0, 0, "42", CSharpSymbolType.IntegerLiteral), IgnoreRemaining);
+ }
+
+ [Fact]
+ public void Simple_Hex_Literal_Is_Recognized()
+ {
+ TestSingleToken("0x0123456789ABCDEF", CSharpSymbolType.IntegerLiteral);
+ }
+
+ [Fact]
+ public void Integer_Type_Suffix_Is_Recognized_In_Hex_Literal()
+ {
+ TestSingleToken("0xDEADBEEFU", CSharpSymbolType.IntegerLiteral);
+ TestSingleToken("0xDEADBEEFu", CSharpSymbolType.IntegerLiteral);
+
+ TestSingleToken("0xDEADBEEFL", CSharpSymbolType.IntegerLiteral);
+ TestSingleToken("0xDEADBEEFl", CSharpSymbolType.IntegerLiteral);
+
+ TestSingleToken("0xDEADBEEFUL", CSharpSymbolType.IntegerLiteral);
+ TestSingleToken("0xDEADBEEFUl", CSharpSymbolType.IntegerLiteral);
+
+ TestSingleToken("0xDEADBEEFuL", CSharpSymbolType.IntegerLiteral);
+ TestSingleToken("0xDEADBEEFul", CSharpSymbolType.IntegerLiteral);
+
+ TestSingleToken("0xDEADBEEFLU", CSharpSymbolType.IntegerLiteral);
+ TestSingleToken("0xDEADBEEFLu", CSharpSymbolType.IntegerLiteral);
+
+ TestSingleToken("0xDEADBEEFlU", CSharpSymbolType.IntegerLiteral);
+ TestSingleToken("0xDEADBEEFlu", CSharpSymbolType.IntegerLiteral);
+ }
+
+ [Fact]
+ public void Trailing_Letter_Is_Not_Part_Of_Hex_Literal_If_Not_Type_Sufix()
+ {
+ TestTokenizer("0xDEADBEEFz", new CSharpSymbol(0, 0, 0, "0xDEADBEEF", CSharpSymbolType.IntegerLiteral), IgnoreRemaining);
+ }
+
+ [Fact]
+ public void Dot_Followed_By_Non_Digit_Is_Not_Part_Of_Real_Literal()
+ {
+ TestTokenizer("3.a", new CSharpSymbol(0, 0, 0, "3", CSharpSymbolType.IntegerLiteral), IgnoreRemaining);
+ }
+
+ [Fact]
+ public void Simple_Real_Literal_Is_Recognized()
+ {
+ TestTokenizer("3.14159", new CSharpSymbol(0, 0, 0, "3.14159", CSharpSymbolType.RealLiteral));
+ }
+
+ [Fact]
+ public void Real_Literal_Between_Zero_And_One_Is_Recognized()
+ {
+ TestTokenizer(".14159", new CSharpSymbol(0, 0, 0, ".14159", CSharpSymbolType.RealLiteral));
+ }
+
+ [Fact]
+ public void Integer_With_Real_Type_Suffix_Is_Recognized()
+ {
+ TestSingleToken("42F", CSharpSymbolType.RealLiteral);
+ TestSingleToken("42f", CSharpSymbolType.RealLiteral);
+ TestSingleToken("42D", CSharpSymbolType.RealLiteral);
+ TestSingleToken("42d", CSharpSymbolType.RealLiteral);
+ TestSingleToken("42M", CSharpSymbolType.RealLiteral);
+ TestSingleToken("42m", CSharpSymbolType.RealLiteral);
+ }
+
+ [Fact]
+ public void Integer_With_Exponent_Is_Recognized()
+ {
+ TestSingleToken("1e10", CSharpSymbolType.RealLiteral);
+ TestSingleToken("1E10", CSharpSymbolType.RealLiteral);
+ TestSingleToken("1e+10", CSharpSymbolType.RealLiteral);
+ TestSingleToken("1E+10", CSharpSymbolType.RealLiteral);
+ TestSingleToken("1e-10", CSharpSymbolType.RealLiteral);
+ TestSingleToken("1E-10", CSharpSymbolType.RealLiteral);
+ }
+
+ [Fact]
+ public void Real_Number_With_Type_Suffix_Is_Recognized()
+ {
+ TestSingleToken("3.14F", CSharpSymbolType.RealLiteral);
+ TestSingleToken("3.14f", CSharpSymbolType.RealLiteral);
+ TestSingleToken("3.14D", CSharpSymbolType.RealLiteral);
+ TestSingleToken("3.14d", CSharpSymbolType.RealLiteral);
+ TestSingleToken("3.14M", CSharpSymbolType.RealLiteral);
+ TestSingleToken("3.14m", CSharpSymbolType.RealLiteral);
+ }
+
+ [Fact]
+ public void Real_Number_With_Exponent_Is_Recognized()
+ {
+ TestSingleToken("3.14E10", CSharpSymbolType.RealLiteral);
+ TestSingleToken("3.14e10", CSharpSymbolType.RealLiteral);
+ TestSingleToken("3.14E+10", CSharpSymbolType.RealLiteral);
+ TestSingleToken("3.14e+10", CSharpSymbolType.RealLiteral);
+ TestSingleToken("3.14E-10", CSharpSymbolType.RealLiteral);
+ TestSingleToken("3.14e-10", CSharpSymbolType.RealLiteral);
+ }
+
+ [Fact]
+ public void Real_Number_With_Exponent_And_Type_Suffix_Is_Recognized()
+ {
+ TestSingleToken("3.14E+10F", CSharpSymbolType.RealLiteral);
+ }
+
+ [Fact]
+ public void Single_Character_Literal_Is_Recognized()
+ {
+ TestSingleToken("'f'", CSharpSymbolType.CharacterLiteral);
+ }
+
+ [Fact]
+ public void Multi_Character_Literal_Is_Recognized()
+ {
+ TestSingleToken("'foo'", CSharpSymbolType.CharacterLiteral);
+ }
+
+ [Fact]
+ public void Character_Literal_Is_Terminated_By_EOF_If_Unterminated()
+ {
+ TestSingleToken("'foo bar", CSharpSymbolType.CharacterLiteral);
+ }
+
+ [Fact]
+ public void Character_Literal_Not_Terminated_By_Escaped_Quote()
+ {
+ TestSingleToken("'foo\\'bar'", CSharpSymbolType.CharacterLiteral);
+ }
+
+ [Fact]
+ public void Character_Literal_Is_Terminated_By_EOL_If_Unterminated()
+ {
+ TestTokenizer("'foo\n", new CSharpSymbol(0, 0, 0, "'foo", CSharpSymbolType.CharacterLiteral), IgnoreRemaining);
+ }
+
+ [Fact]
+ public void String_Literal_Is_Recognized()
+ {
+ TestSingleToken("\"foo\"", CSharpSymbolType.StringLiteral);
+ }
+
+ [Fact]
+ public void String_Literal_Is_Terminated_By_EOF_If_Unterminated()
+ {
+ TestSingleToken("\"foo bar", CSharpSymbolType.StringLiteral);
+ }
+
+ [Fact]
+ public void String_Literal_Not_Terminated_By_Escaped_Quote()
+ {
+ TestSingleToken("\"foo\\\"bar\"", CSharpSymbolType.StringLiteral);
+ }
+
+ [Fact]
+ public void String_Literal_Is_Terminated_By_EOL_If_Unterminated()
+ {
+ TestTokenizer("\"foo\n", new CSharpSymbol(0, 0, 0, "\"foo", CSharpSymbolType.StringLiteral), IgnoreRemaining);
+ }
+
+ [Fact]
+ public void Verbatim_String_Literal_Can_Contain_Newlines()
+ {
+ TestSingleToken("@\"foo\nbar\nbaz\"", CSharpSymbolType.StringLiteral);
+ }
+
+ [Fact]
+ public void Verbatim_String_Literal_Not_Terminated_By_Escaped_Double_Quote()
+ {
+ TestSingleToken("@\"foo\"\"bar\"", CSharpSymbolType.StringLiteral);
+ }
+
+ [Fact]
+ public void Verbatim_String_Literal_Is_Terminated_By_Slash_Double_Quote()
+ {
+ TestTokenizer("@\"foo\\\"bar\"", new CSharpSymbol(0, 0, 0, "@\"foo\\\"", CSharpSymbolType.StringLiteral), IgnoreRemaining);
+ }
+
+ [Fact]
+ public void Verbatim_String_Literal_Is_Terminated_By_EOF()
+ {
+ TestSingleToken("@\"foo", CSharpSymbolType.StringLiteral);
+ }
+ }
+}
diff --git a/test/System.Web.Razor.Test/Tokenizer/CSharpTokenizerOperatorsTest.cs b/test/System.Web.Razor.Test/Tokenizer/CSharpTokenizerOperatorsTest.cs
new file mode 100644
index 00000000..5507102a
--- /dev/null
+++ b/test/System.Web.Razor.Test/Tokenizer/CSharpTokenizerOperatorsTest.cs
@@ -0,0 +1,294 @@
+using System.Web.Razor.Tokenizer.Symbols;
+using Xunit;
+
+namespace System.Web.Razor.Test.Tokenizer
+{
+ public class CSharpTokenizerOperatorsTest : CSharpTokenizerTestBase
+ {
+ [Fact]
+ public void LeftBrace_Is_Recognized()
+ {
+ TestSingleToken("{", CSharpSymbolType.LeftBrace);
+ }
+
+ [Fact]
+ public void Plus_Is_Recognized()
+ {
+ TestSingleToken("+", CSharpSymbolType.Plus);
+ }
+
+ [Fact]
+ public void Assign_Is_Recognized()
+ {
+ TestSingleToken("=", CSharpSymbolType.Assign);
+ }
+
+ [Fact]
+ public void Arrow_Is_Recognized()
+ {
+ TestSingleToken("->", CSharpSymbolType.Arrow);
+ }
+
+ [Fact]
+ public void AndAssign_Is_Recognized()
+ {
+ TestSingleToken("&=", CSharpSymbolType.AndAssign);
+ }
+
+ [Fact]
+ public void RightBrace_Is_Recognized()
+ {
+ TestSingleToken("}", CSharpSymbolType.RightBrace);
+ }
+
+ [Fact]
+ public void Minus_Is_Recognized()
+ {
+ TestSingleToken("-", CSharpSymbolType.Minus);
+ }
+
+ [Fact]
+ public void LessThan_Is_Recognized()
+ {
+ TestSingleToken("<", CSharpSymbolType.LessThan);
+ }
+
+ [Fact]
+ public void Equals_Is_Recognized()
+ {
+ TestSingleToken("==", CSharpSymbolType.Equals);
+ }
+
+ [Fact]
+ public void OrAssign_Is_Recognized()
+ {
+ TestSingleToken("|=", CSharpSymbolType.OrAssign);
+ }
+
+ [Fact]
+ public void LeftBracket_Is_Recognized()
+ {
+ TestSingleToken("[", CSharpSymbolType.LeftBracket);
+ }
+
+ [Fact]
+ public void Star_Is_Recognized()
+ {
+ TestSingleToken("*", CSharpSymbolType.Star);
+ }
+
+ [Fact]
+ public void GreaterThan_Is_Recognized()
+ {
+ TestSingleToken(">", CSharpSymbolType.GreaterThan);
+ }
+
+ [Fact]
+ public void NotEqual_Is_Recognized()
+ {
+ TestSingleToken("!=", CSharpSymbolType.NotEqual);
+ }
+
+ [Fact]
+ public void XorAssign_Is_Recognized()
+ {
+ TestSingleToken("^=", CSharpSymbolType.XorAssign);
+ }
+
+ [Fact]
+ public void RightBracket_Is_Recognized()
+ {
+ TestSingleToken("]", CSharpSymbolType.RightBracket);
+ }
+
+ [Fact]
+ public void Slash_Is_Recognized()
+ {
+ TestSingleToken("/", CSharpSymbolType.Slash);
+ }
+
+ [Fact]
+ public void QuestionMark_Is_Recognized()
+ {
+ TestSingleToken("?", CSharpSymbolType.QuestionMark);
+ }
+
+ [Fact]
+ public void LessThanEqual_Is_Recognized()
+ {
+ TestSingleToken("<=", CSharpSymbolType.LessThanEqual);
+ }
+
+ [Fact]
+ public void LeftShift_Is_Not_Specially_Recognized()
+ {
+ TestTokenizer("<<",
+ new CSharpSymbol(0, 0, 0, "<", CSharpSymbolType.LessThan),
+ new CSharpSymbol(1, 0, 1, "<", CSharpSymbolType.LessThan));
+ }
+
+ [Fact]
+ public void LeftParen_Is_Recognized()
+ {
+ TestSingleToken("(", CSharpSymbolType.LeftParenthesis);
+ }
+
+ [Fact]
+ public void Modulo_Is_Recognized()
+ {
+ TestSingleToken("%", CSharpSymbolType.Modulo);
+ }
+
+ [Fact]
+ public void NullCoalesce_Is_Recognized()
+ {
+ TestSingleToken("??", CSharpSymbolType.NullCoalesce);
+ }
+
+ [Fact]
+ public void GreaterThanEqual_Is_Recognized()
+ {
+ TestSingleToken(">=", CSharpSymbolType.GreaterThanEqual);
+ }
+
+ [Fact]
+ public void EqualGreaterThan_Is_Recognized()
+ {
+ TestSingleToken("=>", CSharpSymbolType.GreaterThanEqual);
+ }
+
+ [Fact]
+ public void RightParen_Is_Recognized()
+ {
+ TestSingleToken(")", CSharpSymbolType.RightParenthesis);
+ }
+
+ [Fact]
+ public void And_Is_Recognized()
+ {
+ TestSingleToken("&", CSharpSymbolType.And);
+ }
+
+ [Fact]
+ public void DoubleColon_Is_Recognized()
+ {
+ TestSingleToken("::", CSharpSymbolType.DoubleColon);
+ }
+
+ [Fact]
+ public void PlusAssign_Is_Recognized()
+ {
+ TestSingleToken("+=", CSharpSymbolType.PlusAssign);
+ }
+
+ [Fact]
+ public void Semicolon_Is_Recognized()
+ {
+ TestSingleToken(";", CSharpSymbolType.Semicolon);
+ }
+
+ [Fact]
+ public void Tilde_Is_Recognized()
+ {
+ TestSingleToken("~", CSharpSymbolType.Tilde);
+ }
+
+ [Fact]
+ public void DoubleOr_Is_Recognized()
+ {
+ TestSingleToken("||", CSharpSymbolType.DoubleOr);
+ }
+
+ [Fact]
+ public void ModuloAssign_Is_Recognized()
+ {
+ TestSingleToken("%=", CSharpSymbolType.ModuloAssign);
+ }
+
+ [Fact]
+ public void Colon_Is_Recognized()
+ {
+ TestSingleToken(":", CSharpSymbolType.Colon);
+ }
+
+ [Fact]
+ public void Not_Is_Recognized()
+ {
+ TestSingleToken("!", CSharpSymbolType.Not);
+ }
+
+ [Fact]
+ public void DoubleAnd_Is_Recognized()
+ {
+ TestSingleToken("&&", CSharpSymbolType.DoubleAnd);
+ }
+
+ [Fact]
+ public void DivideAssign_Is_Recognized()
+ {
+ TestSingleToken("/=", CSharpSymbolType.DivideAssign);
+ }
+
+ [Fact]
+ public void Comma_Is_Recognized()
+ {
+ TestSingleToken(",", CSharpSymbolType.Comma);
+ }
+
+ [Fact]
+ public void Xor_Is_Recognized()
+ {
+ TestSingleToken("^", CSharpSymbolType.Xor);
+ }
+
+ [Fact]
+ public void Decrement_Is_Recognized()
+ {
+ TestSingleToken("--", CSharpSymbolType.Decrement);
+ }
+
+ [Fact]
+ public void MultiplyAssign_Is_Recognized()
+ {
+ TestSingleToken("*=", CSharpSymbolType.MultiplyAssign);
+ }
+
+ [Fact]
+ public void Dot_Is_Recognized()
+ {
+ TestSingleToken(".", CSharpSymbolType.Dot);
+ }
+
+ [Fact]
+ public void Or_Is_Recognized()
+ {
+ TestSingleToken("|", CSharpSymbolType.Or);
+ }
+
+ [Fact]
+ public void Increment_Is_Recognized()
+ {
+ TestSingleToken("++", CSharpSymbolType.Increment);
+ }
+
+ [Fact]
+ public void MinusAssign_Is_Recognized()
+ {
+ TestSingleToken("-=", CSharpSymbolType.MinusAssign);
+ }
+
+ [Fact]
+ public void RightShift_Is_Not_Specially_Recognized()
+ {
+ TestTokenizer(">>",
+ new CSharpSymbol(0, 0, 0, ">", CSharpSymbolType.GreaterThan),
+ new CSharpSymbol(1, 0, 1, ">", CSharpSymbolType.GreaterThan));
+ }
+
+ [Fact]
+ public void Hash_Is_Recognized()
+ {
+ TestSingleToken("#", CSharpSymbolType.Hash);
+ }
+ }
+}
diff --git a/test/System.Web.Razor.Test/Tokenizer/CSharpTokenizerTest.cs b/test/System.Web.Razor.Test/Tokenizer/CSharpTokenizerTest.cs
new file mode 100644
index 00000000..9b9b64cf
--- /dev/null
+++ b/test/System.Web.Razor.Test/Tokenizer/CSharpTokenizerTest.cs
@@ -0,0 +1,102 @@
+using System.Web.Razor.Tokenizer;
+using System.Web.Razor.Tokenizer.Symbols;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Razor.Test.Tokenizer
+{
+ public class CSharpTokenizerTest : CSharpTokenizerTestBase
+ {
+ [Fact]
+ public void Constructor_Throws_ArgNull_If_Null_Source_Provided()
+ {
+ Assert.ThrowsArgumentNull(() => new CSharpTokenizer(null), "source");
+ }
+
+ [Fact]
+ public void Next_Returns_Null_When_EOF_Reached()
+ {
+ TestTokenizer("");
+ }
+
+ [Fact]
+ public void Next_Returns_Newline_Token_For_Single_CR()
+ {
+ TestTokenizer("\r\ra",
+ new CSharpSymbol(0, 0, 0, "\r", CSharpSymbolType.NewLine),
+ new CSharpSymbol(1, 1, 0, "\r", CSharpSymbolType.NewLine),
+ IgnoreRemaining);
+ }
+
+ [Fact]
+ public void Next_Returns_Newline_Token_For_Single_LF()
+ {
+ TestTokenizer("\n\na",
+ new CSharpSymbol(0, 0, 0, "\n", CSharpSymbolType.NewLine),
+ new CSharpSymbol(1, 1, 0, "\n", CSharpSymbolType.NewLine),
+ IgnoreRemaining);
+ }
+
+ [Fact]
+ public void Next_Returns_Newline_Token_For_Single_NEL()
+ {
+ // NEL: Unicode "Next Line" U+0085
+ TestTokenizer("\u0085\u0085a",
+ new CSharpSymbol(0, 0, 0, "\u0085", CSharpSymbolType.NewLine),
+ new CSharpSymbol(1, 1, 0, "\u0085", CSharpSymbolType.NewLine),
+ IgnoreRemaining);
+ }
+
+ [Fact]
+ public void Next_Returns_Newline_Token_For_Single_Line_Separator()
+ {
+ // Unicode "Line Separator" U+2028
+ TestTokenizer("\u2028\u2028a",
+ new CSharpSymbol(0, 0, 0, "\u2028", CSharpSymbolType.NewLine),
+ new CSharpSymbol(1, 1, 0, "\u2028", CSharpSymbolType.NewLine),
+ IgnoreRemaining);
+ }
+
+ [Fact]
+ public void Next_Returns_Newline_Token_For_Single_Paragraph_Separator()
+ {
+ // Unicode "Paragraph Separator" U+2029
+ TestTokenizer("\u2029\u2029a",
+ new CSharpSymbol(0, 0, 0, "\u2029", CSharpSymbolType.NewLine),
+ new CSharpSymbol(1, 1, 0, "\u2029", CSharpSymbolType.NewLine),
+ IgnoreRemaining);
+ }
+
+ [Fact]
+ public void Next_Returns_Single_Newline_Token_For_CRLF()
+ {
+ TestTokenizer("\r\n\r\na",
+ new CSharpSymbol(0, 0, 0, "\r\n", CSharpSymbolType.NewLine),
+ new CSharpSymbol(2, 1, 0, "\r\n", CSharpSymbolType.NewLine),
+ IgnoreRemaining);
+ }
+
+ [Fact]
+ public void Next_Returns_Token_For_Whitespace_Characters()
+ {
+ TestTokenizer(" \f\t\u000B \n ",
+ new CSharpSymbol(0, 0, 0, " \f\t\u000B ", CSharpSymbolType.WhiteSpace),
+ new CSharpSymbol(5, 0, 5, "\n", CSharpSymbolType.NewLine),
+ new CSharpSymbol(6, 1, 0, " ", CSharpSymbolType.WhiteSpace));
+ }
+
+ [Fact]
+ public void Transition_Is_Recognized()
+ {
+ TestSingleToken("@", CSharpSymbolType.Transition);
+ }
+
+ [Fact]
+ public void Transition_Is_Recognized_As_SingleCharacter()
+ {
+ TestTokenizer("@(",
+ new CSharpSymbol(0, 0, 0, "@", CSharpSymbolType.Transition),
+ new CSharpSymbol(1, 0, 1, "(", CSharpSymbolType.LeftParenthesis));
+ }
+ }
+}
diff --git a/test/System.Web.Razor.Test/Tokenizer/CSharpTokenizerTestBase.cs b/test/System.Web.Razor.Test/Tokenizer/CSharpTokenizerTestBase.cs
new file mode 100644
index 00000000..9d7b4d97
--- /dev/null
+++ b/test/System.Web.Razor.Test/Tokenizer/CSharpTokenizerTestBase.cs
@@ -0,0 +1,26 @@
+using System.Web.Razor.Text;
+using System.Web.Razor.Tokenizer;
+using System.Web.Razor.Tokenizer.Symbols;
+
+namespace System.Web.Razor.Test.Tokenizer
+{
+ public abstract class CSharpTokenizerTestBase : TokenizerTestBase<CSharpSymbol, CSharpSymbolType>
+ {
+ private static CSharpSymbol _ignoreRemaining = new CSharpSymbol(0, 0, 0, String.Empty, CSharpSymbolType.Unknown);
+
+ protected override CSharpSymbol IgnoreRemaining
+ {
+ get { return _ignoreRemaining; }
+ }
+
+ protected override Tokenizer<CSharpSymbol, CSharpSymbolType> CreateTokenizer(ITextDocument source)
+ {
+ return new CSharpTokenizer(source);
+ }
+
+ protected void TestSingleToken(string text, CSharpSymbolType expectedSymbolType)
+ {
+ TestTokenizer(text, new CSharpSymbol(0, 0, 0, text, expectedSymbolType));
+ }
+ }
+}
diff --git a/test/System.Web.Razor.Test/Tokenizer/HtmlTokenizerTest.cs b/test/System.Web.Razor.Test/Tokenizer/HtmlTokenizerTest.cs
new file mode 100644
index 00000000..6a2387d8
--- /dev/null
+++ b/test/System.Web.Razor.Test/Tokenizer/HtmlTokenizerTest.cs
@@ -0,0 +1,166 @@
+using System.Web.Razor.Tokenizer;
+using System.Web.Razor.Tokenizer.Symbols;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Razor.Test.Tokenizer
+{
+ public class HtmlTokenizerTest : HtmlTokenizerTestBase
+ {
+ [Fact]
+ public void Constructor_Throws_ArgNull_If_Null_Source_Provided()
+ {
+ Assert.ThrowsArgumentNull(() => new HtmlTokenizer(null), "source");
+ }
+
+ [Fact]
+ public void Next_Returns_Null_When_EOF_Reached()
+ {
+ TestTokenizer("");
+ }
+
+ [Fact]
+ public void Text_Is_Recognized()
+ {
+ TestTokenizer("foo-9309&smlkmb;::-3029022,.sdkq92384",
+ new HtmlSymbol(0, 0, 0, "foo-9309&smlkmb;::-3029022,.sdkq92384", HtmlSymbolType.Text));
+ }
+
+ [Fact]
+ public void Whitespace_Is_Recognized()
+ {
+ TestTokenizer(" \t\f ",
+ new HtmlSymbol(0, 0, 0, " \t\f ", HtmlSymbolType.WhiteSpace));
+ }
+
+ [Fact]
+ public void Newline_Is_Recognized()
+ {
+ TestTokenizer("\n\r\r\n",
+ new HtmlSymbol(0, 0, 0, "\n", HtmlSymbolType.NewLine),
+ new HtmlSymbol(1, 1, 0, "\r", HtmlSymbolType.NewLine),
+ new HtmlSymbol(2, 2, 0, "\r\n", HtmlSymbolType.NewLine));
+ }
+
+ [Fact]
+ public void Transition_Is_Not_Recognized_Mid_Text_If_Surrounded_By_Alphanumeric_Characters()
+ {
+ TestSingleToken("foo@bar", HtmlSymbolType.Text);
+ }
+
+ [Fact]
+ public void OpenAngle_Is_Recognized()
+ {
+ TestSingleToken("<", HtmlSymbolType.OpenAngle);
+ }
+
+ [Fact]
+ public void Bang_Is_Recognized()
+ {
+ TestSingleToken("!", HtmlSymbolType.Bang);
+ }
+
+ [Fact]
+ public void Solidus_Is_Recognized()
+ {
+ TestSingleToken("/", HtmlSymbolType.Solidus);
+ }
+
+ [Fact]
+ public void QuestionMark_Is_Recognized()
+ {
+ TestSingleToken("?", HtmlSymbolType.QuestionMark);
+ }
+
+ [Fact]
+ public void LeftBracket_Is_Recognized()
+ {
+ TestSingleToken("[", HtmlSymbolType.LeftBracket);
+ }
+
+ [Fact]
+ public void CloseAngle_Is_Recognized()
+ {
+ TestSingleToken(">", HtmlSymbolType.CloseAngle);
+ }
+
+ [Fact]
+ public void RightBracket_Is_Recognized()
+ {
+ TestSingleToken("]", HtmlSymbolType.RightBracket);
+ }
+
+ [Fact]
+ public void Equals_Is_Recognized()
+ {
+ TestSingleToken("=", HtmlSymbolType.Equals);
+ }
+
+ [Fact]
+ public void DoubleQuote_Is_Recognized()
+ {
+ TestSingleToken("\"", HtmlSymbolType.DoubleQuote);
+ }
+
+ [Fact]
+ public void SingleQuote_Is_Recognized()
+ {
+ TestSingleToken("'", HtmlSymbolType.SingleQuote);
+ }
+
+ [Fact]
+ public void Transition_Is_Recognized()
+ {
+ TestSingleToken("@", HtmlSymbolType.Transition);
+ }
+
+ [Fact]
+ public void DoubleHyphen_Is_Recognized()
+ {
+ TestSingleToken("--", HtmlSymbolType.DoubleHyphen);
+ }
+
+ [Fact]
+ public void SingleHyphen_Is_Not_Recognized()
+ {
+ TestSingleToken("-", HtmlSymbolType.Text);
+ }
+
+ [Fact]
+ public void SingleHyphen_Mid_Text_Is_Not_Recognized_As_Separate_Token()
+ {
+ TestSingleToken("foo-bar", HtmlSymbolType.Text);
+ }
+
+ [Fact]
+ public void Next_Ignores_Star_At_EOF_In_RazorComment()
+ {
+ TestTokenizer("@* Foo * Bar * Baz *",
+ new HtmlSymbol(0, 0, 0, "@", HtmlSymbolType.RazorCommentTransition),
+ new HtmlSymbol(1, 0, 1, "*", HtmlSymbolType.RazorCommentStar),
+ new HtmlSymbol(2, 0, 2, " Foo * Bar * Baz *", HtmlSymbolType.RazorComment));
+ }
+
+ [Fact]
+ public void Next_Ignores_Star_Without_Trailing_At()
+ {
+ TestTokenizer("@* Foo * Bar * Baz *@",
+ new HtmlSymbol(0, 0, 0, "@", HtmlSymbolType.RazorCommentTransition),
+ new HtmlSymbol(1, 0, 1, "*", HtmlSymbolType.RazorCommentStar),
+ new HtmlSymbol(2, 0, 2, " Foo * Bar * Baz ", HtmlSymbolType.RazorComment),
+ new HtmlSymbol(19, 0, 19, "*", HtmlSymbolType.RazorCommentStar),
+ new HtmlSymbol(20, 0, 20, "@", HtmlSymbolType.RazorCommentTransition));
+ }
+
+ [Fact]
+ public void Next_Returns_RazorComment_Token_For_Entire_Razor_Comment()
+ {
+ TestTokenizer("@* Foo Bar Baz *@",
+ new HtmlSymbol(0, 0, 0, "@", HtmlSymbolType.RazorCommentTransition),
+ new HtmlSymbol(1, 0, 1, "*", HtmlSymbolType.RazorCommentStar),
+ new HtmlSymbol(2, 0, 2, " Foo Bar Baz ", HtmlSymbolType.RazorComment),
+ new HtmlSymbol(15, 0, 15, "*", HtmlSymbolType.RazorCommentStar),
+ new HtmlSymbol(16, 0, 16, "@", HtmlSymbolType.RazorCommentTransition));
+ }
+ }
+}
diff --git a/test/System.Web.Razor.Test/Tokenizer/HtmlTokenizerTestBase.cs b/test/System.Web.Razor.Test/Tokenizer/HtmlTokenizerTestBase.cs
new file mode 100644
index 00000000..06210d37
--- /dev/null
+++ b/test/System.Web.Razor.Test/Tokenizer/HtmlTokenizerTestBase.cs
@@ -0,0 +1,26 @@
+using System.Web.Razor.Text;
+using System.Web.Razor.Tokenizer;
+using System.Web.Razor.Tokenizer.Symbols;
+
+namespace System.Web.Razor.Test.Tokenizer
+{
+ public abstract class HtmlTokenizerTestBase : TokenizerTestBase<HtmlSymbol, HtmlSymbolType>
+ {
+ private static HtmlSymbol _ignoreRemaining = new HtmlSymbol(0, 0, 0, String.Empty, HtmlSymbolType.Unknown);
+
+ protected override HtmlSymbol IgnoreRemaining
+ {
+ get { return _ignoreRemaining; }
+ }
+
+ protected override Tokenizer<HtmlSymbol, HtmlSymbolType> CreateTokenizer(ITextDocument source)
+ {
+ return new HtmlTokenizer(source);
+ }
+
+ protected void TestSingleToken(string text, HtmlSymbolType expectedSymbolType)
+ {
+ TestTokenizer(text, new HtmlSymbol(0, 0, 0, text, expectedSymbolType));
+ }
+ }
+}
diff --git a/test/System.Web.Razor.Test/Tokenizer/TokenizerLookaheadTest.cs b/test/System.Web.Razor.Test/Tokenizer/TokenizerLookaheadTest.cs
new file mode 100644
index 00000000..bc1d2db9
--- /dev/null
+++ b/test/System.Web.Razor.Test/Tokenizer/TokenizerLookaheadTest.cs
@@ -0,0 +1,39 @@
+using System.IO;
+using System.Web.Razor.Text;
+using System.Web.Razor.Tokenizer;
+using System.Web.Razor.Tokenizer.Symbols;
+using Xunit;
+
+namespace System.Web.Razor.Test.Tokenizer
+{
+ public class TokenizerLookaheadTest : HtmlTokenizerTestBase
+ {
+ [Fact]
+ public void After_Cancelling_Lookahead_Tokenizer_Returns_Same_Tokens_As_It_Did_Before_Lookahead()
+ {
+ HtmlTokenizer tokenizer = new HtmlTokenizer(new SeekableTextReader(new StringReader("<foo>")));
+ using (tokenizer.Source.BeginLookahead())
+ {
+ Assert.Equal(new HtmlSymbol(0, 0, 0, "<", HtmlSymbolType.OpenAngle), tokenizer.NextSymbol());
+ Assert.Equal(new HtmlSymbol(1, 0, 1, "foo", HtmlSymbolType.Text), tokenizer.NextSymbol());
+ Assert.Equal(new HtmlSymbol(4, 0, 4, ">", HtmlSymbolType.CloseAngle), tokenizer.NextSymbol());
+ }
+ Assert.Equal(new HtmlSymbol(0, 0, 0, "<", HtmlSymbolType.OpenAngle), tokenizer.NextSymbol());
+ Assert.Equal(new HtmlSymbol(1, 0, 1, "foo", HtmlSymbolType.Text), tokenizer.NextSymbol());
+ Assert.Equal(new HtmlSymbol(4, 0, 4, ">", HtmlSymbolType.CloseAngle), tokenizer.NextSymbol());
+ }
+
+ [Fact]
+ public void After_Accepting_Lookahead_Tokenizer_Returns_Next_Token()
+ {
+ HtmlTokenizer tokenizer = new HtmlTokenizer(new SeekableTextReader(new StringReader("<foo>")));
+ using (LookaheadToken lookahead = tokenizer.Source.BeginLookahead())
+ {
+ Assert.Equal(new HtmlSymbol(0, 0, 0, "<", HtmlSymbolType.OpenAngle), tokenizer.NextSymbol());
+ Assert.Equal(new HtmlSymbol(1, 0, 1, "foo", HtmlSymbolType.Text), tokenizer.NextSymbol());
+ lookahead.Accept();
+ }
+ Assert.Equal(new HtmlSymbol(4, 0, 4, ">", HtmlSymbolType.CloseAngle), tokenizer.NextSymbol());
+ }
+ }
+}
diff --git a/test/System.Web.Razor.Test/Tokenizer/TokenizerTestBase.cs b/test/System.Web.Razor.Test/Tokenizer/TokenizerTestBase.cs
new file mode 100644
index 00000000..5b5e6612
--- /dev/null
+++ b/test/System.Web.Razor.Test/Tokenizer/TokenizerTestBase.cs
@@ -0,0 +1,74 @@
+using System.Diagnostics;
+using System.IO;
+using System.Text;
+using System.Web.Razor.Text;
+using System.Web.Razor.Tokenizer;
+using System.Web.Razor.Tokenizer.Symbols;
+using Xunit;
+
+namespace System.Web.Razor.Test.Tokenizer
+{
+ public abstract class TokenizerTestBase<TSymbol, TSymbolType>
+ where TSymbol : SymbolBase<TSymbolType>
+ {
+ protected abstract TSymbol IgnoreRemaining { get; }
+ protected abstract Tokenizer<TSymbol, TSymbolType> CreateTokenizer(ITextDocument source);
+
+ protected void TestTokenizer(string input, params TSymbol[] expectedSymbols)
+ {
+ // Arrange
+ bool success = true;
+ StringBuilder output = new StringBuilder();
+ using (StringReader reader = new StringReader(input))
+ {
+ using (SeekableTextReader source = new SeekableTextReader(reader))
+ {
+ Tokenizer<TSymbol, TSymbolType> tokenizer = CreateTokenizer(source);
+ int counter = 0;
+ TSymbol current = null;
+ while ((current = tokenizer.NextSymbol()) != null)
+ {
+ if (counter >= expectedSymbols.Length)
+ {
+ output.AppendLine(String.Format("F: Expected: << Nothing >>; Actual: {0}", current));
+ success = false;
+ }
+ else if (ReferenceEquals(expectedSymbols[counter], IgnoreRemaining))
+ {
+ output.AppendLine(String.Format("P: Ignored {0}", current));
+ }
+ else
+ {
+ if (!Equals(expectedSymbols[counter], current))
+ {
+ output.AppendLine(String.Format("F: Expected: {0}; Actual: {1}", expectedSymbols[counter], current));
+ success = false;
+ }
+ else
+ {
+ output.AppendLine(String.Format("P: Expected: {0}", expectedSymbols[counter]));
+ }
+ counter++;
+ }
+ }
+ if (counter < expectedSymbols.Length && !ReferenceEquals(expectedSymbols[counter], IgnoreRemaining))
+ {
+ success = false;
+ for (; counter < expectedSymbols.Length; counter++)
+ {
+ output.AppendLine(String.Format("F: Expected: {0}; Actual: << None >>", expectedSymbols[counter]));
+ }
+ }
+ }
+ }
+ Assert.True(success, "\r\n" + output.ToString());
+ WriteTraceLine(output.Replace("{", "{{").Replace("}", "}}").ToString());
+ }
+
+ [Conditional("PARSER_TRACE")]
+ private static void WriteTraceLine(string format, params object[] args)
+ {
+ Trace.WriteLine(String.Format(format, args));
+ }
+ }
+}
diff --git a/test/System.Web.Razor.Test/Tokenizer/VBTokenizerCommentTest.cs b/test/System.Web.Razor.Test/Tokenizer/VBTokenizerCommentTest.cs
new file mode 100644
index 00000000..cc32eb33
--- /dev/null
+++ b/test/System.Web.Razor.Test/Tokenizer/VBTokenizerCommentTest.cs
@@ -0,0 +1,91 @@
+using System.Web.Razor.Tokenizer.Symbols;
+using Xunit;
+
+namespace System.Web.Razor.Test.Tokenizer
+{
+ public class VBTokenizerCommentTest : VBTokenizerTestBase
+ {
+ [Fact]
+ public void Next_Ignores_Star_At_EOF_In_RazorComment()
+ {
+ TestTokenizer("@* Foo * Bar * Baz *",
+ new VBSymbol(0, 0, 0, "@", VBSymbolType.RazorCommentTransition),
+ new VBSymbol(1, 0, 1, "*", VBSymbolType.RazorCommentStar),
+ new VBSymbol(2, 0, 2, " Foo * Bar * Baz *", VBSymbolType.RazorComment));
+ }
+
+ [Fact]
+ public void Next_Ignores_Star_Without_Trailing_At()
+ {
+ TestTokenizer("@* Foo * Bar * Baz *@",
+ new VBSymbol(0, 0, 0, "@", VBSymbolType.RazorCommentTransition),
+ new VBSymbol(1, 0, 1, "*", VBSymbolType.RazorCommentStar),
+ new VBSymbol(2, 0, 2, " Foo * Bar * Baz ", VBSymbolType.RazorComment),
+ new VBSymbol(19, 0, 19, "*", VBSymbolType.RazorCommentStar),
+ new VBSymbol(20, 0, 20, "@", VBSymbolType.RazorCommentTransition));
+ }
+
+ [Fact]
+ public void Next_Returns_RazorComment_Token_For_Entire_Razor_Comment()
+ {
+ TestTokenizer("@* Foo Bar Baz *@",
+ new VBSymbol(0, 0, 0, "@", VBSymbolType.RazorCommentTransition),
+ new VBSymbol(1, 0, 1, "*", VBSymbolType.RazorCommentStar),
+ new VBSymbol(2, 0, 2, " Foo Bar Baz ", VBSymbolType.RazorComment),
+ new VBSymbol(15, 0, 15, "*", VBSymbolType.RazorCommentStar),
+ new VBSymbol(16, 0, 16, "@", VBSymbolType.RazorCommentTransition));
+ }
+
+ [Fact]
+ public void Tick_Comment_Is_Recognized()
+ {
+ TestTokenizer("' Foo Bar Baz", new VBSymbol(0, 0, 0, "' Foo Bar Baz", VBSymbolType.Comment));
+ }
+
+ [Fact]
+ public void Tick_Comment_Is_Terminated_By_Newline()
+ {
+ TestTokenizer("' Foo Bar Baz\na", new VBSymbol(0, 0, 0, "' Foo Bar Baz", VBSymbolType.Comment), IgnoreRemaining);
+ }
+
+ [Fact]
+ public void LeftQuote_Comment_Is_Recognized()
+ {
+ // U+2018 - Left Quote: ‘
+ TestTokenizer("‘ Foo Bar Baz", new VBSymbol(0, 0, 0, "‘ Foo Bar Baz", VBSymbolType.Comment));
+ }
+
+ [Fact]
+ public void LeftQuote_Comment_Is_Terminated_By_Newline()
+ {
+ // U+2018 - Left Quote: ‘
+ TestTokenizer("‘ Foo Bar Baz\na", new VBSymbol(0, 0, 0, "‘ Foo Bar Baz", VBSymbolType.Comment), IgnoreRemaining);
+ }
+
+ [Fact]
+ public void RightQuote_Comment_Is_Recognized()
+ {
+ // U+2019 - Right Quote: ’
+ TestTokenizer("’ Foo Bar Baz", new VBSymbol(0, 0, 0, "’ Foo Bar Baz", VBSymbolType.Comment));
+ }
+
+ [Fact]
+ public void RightQuote_Comment_Is_Terminated_By_Newline()
+ {
+ // U+2019 - Right Quote: ’
+ TestTokenizer("’ Foo Bar Baz\na", new VBSymbol(0, 0, 0, "’ Foo Bar Baz", VBSymbolType.Comment), IgnoreRemaining);
+ }
+
+ [Fact]
+ public void Rem_Comment_Is_Recognized()
+ {
+ TestTokenizer("REM Foo Bar Baz", new VBSymbol(0, 0, 0, "REM Foo Bar Baz", VBSymbolType.Comment));
+ }
+
+ [Fact]
+ public void Rem_Comment_Is_Terminated_By_Newline()
+ {
+ TestTokenizer("REM Foo Bar Baz\na", new VBSymbol(0, 0, 0, "REM Foo Bar Baz", VBSymbolType.Comment), IgnoreRemaining);
+ }
+ }
+}
diff --git a/test/System.Web.Razor.Test/Tokenizer/VBTokenizerIdentifierTest.cs b/test/System.Web.Razor.Test/Tokenizer/VBTokenizerIdentifierTest.cs
new file mode 100644
index 00000000..a4c5a3e7
--- /dev/null
+++ b/test/System.Web.Razor.Test/Tokenizer/VBTokenizerIdentifierTest.cs
@@ -0,0 +1,265 @@
+using System.Web.Razor.Tokenizer.Symbols;
+using Xunit;
+
+namespace System.Web.Razor.Test.Tokenizer
+{
+ public class VBTokenizerIdentifierTest : VBTokenizerTestBase
+ {
+ [Fact]
+ public void Simple_Identifier_Is_Recognized()
+ {
+ TestTokenizer("foo", new VBSymbol(0, 0, 0, "foo", VBSymbolType.Identifier));
+ }
+
+ [Fact]
+ public void Escaped_Identifier_Terminates_At_EOF()
+ {
+ TestTokenizer("[foo", new VBSymbol(0, 0, 0, "[foo", VBSymbolType.Identifier));
+ }
+
+ [Fact]
+ public void Escaped_Identifier_Terminates_At_Whitespace()
+ {
+ TestTokenizer("[foo ", new VBSymbol(0, 0, 0, "[foo", VBSymbolType.Identifier), IgnoreRemaining);
+ }
+
+ [Fact]
+ public void Escaped_Identifier_Terminates_At_RightBracket_And_Does_Not_Read_TypeCharacter()
+ {
+ TestTokenizer("[foo]&", new VBSymbol(0, 0, 0, "[foo]", VBSymbolType.Identifier), IgnoreRemaining);
+ }
+
+ [Fact]
+ public void Identifier_Starting_With_Underscore_Is_Recognized()
+ {
+ TestTokenizer("_foo", new VBSymbol(0, 0, 0, "_foo", VBSymbolType.Identifier));
+ }
+
+ [Fact]
+ public void Identifier_Can_Contain_Digits()
+ {
+ TestTokenizer("foo4", new VBSymbol(0, 0, 0, "foo4", VBSymbolType.Identifier));
+ }
+
+ [Fact]
+ public void Identifier_Can_Start_With_Titlecase_Letter()
+ {
+ TestTokenizer("ῼfoo", new VBSymbol(0, 0, 0, "ῼfoo", VBSymbolType.Identifier));
+ }
+
+ [Fact]
+ public void Identifier_Can_Start_With_Letter_Modifier()
+ {
+ TestTokenizer("ᵊfoo", new VBSymbol(0, 0, 0, "ᵊfoo", VBSymbolType.Identifier));
+ }
+
+ [Fact]
+ public void Identifier_Can_Start_With_Other_Letter()
+ {
+ TestTokenizer("ƻfoo", new VBSymbol(0, 0, 0, "ƻfoo", VBSymbolType.Identifier));
+ }
+
+ [Fact]
+ public void Identifier_Can_Start_With_Number_Letter()
+ {
+ TestTokenizer("Ⅽool", new VBSymbol(0, 0, 0, "Ⅽool", VBSymbolType.Identifier));
+ }
+
+ [Fact]
+ public void Identifier_Can_Contain_Non_Spacing_Mark()
+ {
+ TestTokenizer("foo\u0300", new VBSymbol(0, 0, 0, "foo\u0300", VBSymbolType.Identifier));
+ }
+
+ [Fact]
+ public void Identifier_Can_Contain_Spacing_Combining_Mark()
+ {
+ TestTokenizer("fooः", new VBSymbol(0, 0, 0, "fooः", VBSymbolType.Identifier));
+ }
+
+ [Fact]
+ public void Identifier_Can_Contain_Non_English_Digit()
+ {
+ TestTokenizer("foo١", new VBSymbol(0, 0, 0, "foo١", VBSymbolType.Identifier));
+ }
+
+ [Fact]
+ public void Identifier_Can_Contain_Connector_Punctuation()
+ {
+ TestTokenizer("foo‿bar", new VBSymbol(0, 0, 0, "foo‿bar", VBSymbolType.Identifier));
+ }
+
+ [Fact]
+ public void Identifier_Can_Contain_Format_Character()
+ {
+ TestTokenizer("foo؃bar", new VBSymbol(0, 0, 0, "foo؃bar", VBSymbolType.Identifier));
+ }
+
+ [Fact]
+ public void Escaped_Keyword_Is_Recognized_As_Identifier()
+ {
+ TestSingleToken("[AddHandler]", VBSymbolType.Identifier);
+ }
+
+ [Fact]
+ public void Keywords_Are_Recognized_As_Keyword_Tokens()
+ {
+ TestKeyword("AddHandler", VBKeyword.AddHandler);
+ TestKeyword("AndAlso", VBKeyword.AndAlso);
+ TestKeyword("Byte", VBKeyword.Byte);
+ TestKeyword("Catch", VBKeyword.Catch);
+ TestKeyword("CDate", VBKeyword.CDate);
+ TestKeyword("CInt", VBKeyword.CInt);
+ TestKeyword("Const", VBKeyword.Const);
+ TestKeyword("CSng", VBKeyword.CSng);
+ TestKeyword("CULng", VBKeyword.CULng);
+ TestKeyword("Declare", VBKeyword.Declare);
+ TestKeyword("DirectCast", VBKeyword.DirectCast);
+ TestKeyword("Else", VBKeyword.Else);
+ TestKeyword("Enum", VBKeyword.Enum);
+ TestKeyword("Exit", VBKeyword.Exit);
+ TestKeyword("Friend", VBKeyword.Friend);
+ TestKeyword("GetXmlNamespace", VBKeyword.GetXmlNamespace);
+ TestKeyword("Handles", VBKeyword.Handles);
+ TestKeyword("In", VBKeyword.In);
+ TestKeyword("Is", VBKeyword.Is);
+ TestKeyword("Like", VBKeyword.Like);
+ TestKeyword("Mod", VBKeyword.Mod);
+ TestKeyword("MyBase", VBKeyword.MyBase);
+ TestKeyword("New", VBKeyword.New);
+ TestKeyword("AddressOf", VBKeyword.AddressOf);
+ TestKeyword("As", VBKeyword.As);
+ TestKeyword("ByVal", VBKeyword.ByVal);
+ TestKeyword("CBool", VBKeyword.CBool);
+ TestKeyword("CDbl", VBKeyword.CDbl);
+ TestKeyword("Class", VBKeyword.Class);
+ TestKeyword("Continue", VBKeyword.Continue);
+ TestKeyword("CStr", VBKeyword.CStr);
+ TestKeyword("CUShort", VBKeyword.CUShort);
+ TestKeyword("Default", VBKeyword.Default);
+ TestKeyword("Do", VBKeyword.Do);
+ TestKeyword("ElseIf", VBKeyword.ElseIf);
+ TestKeyword("Erase", VBKeyword.Erase);
+ TestKeyword("False", VBKeyword.False);
+ TestKeyword("Function", VBKeyword.Function);
+ TestKeyword("Global", VBKeyword.Global);
+ TestKeyword("If", VBKeyword.If);
+ TestKeyword("Inherits", VBKeyword.Inherits);
+ TestKeyword("IsNot", VBKeyword.IsNot);
+ TestKeyword("Long", VBKeyword.Long);
+ TestKeyword("Module", VBKeyword.Module);
+ TestKeyword("MyClass", VBKeyword.MyClass);
+ TestKeyword("Next", VBKeyword.Next);
+ TestKeyword("Alias", VBKeyword.Alias);
+ TestKeyword("Boolean", VBKeyword.Boolean);
+ TestKeyword("Call", VBKeyword.Call);
+ TestKeyword("CByte", VBKeyword.CByte);
+ TestKeyword("CDec", VBKeyword.CDec);
+ TestKeyword("CLng", VBKeyword.CLng);
+ TestKeyword("CSByte", VBKeyword.CSByte);
+ TestKeyword("CType", VBKeyword.CType);
+ TestKeyword("Date", VBKeyword.Date);
+ TestKeyword("Delegate", VBKeyword.Delegate);
+ TestKeyword("Double", VBKeyword.Double);
+ TestKeyword("End", VBKeyword.End);
+ TestKeyword("Error", VBKeyword.Error);
+ TestKeyword("Finally", VBKeyword.Finally);
+ TestKeyword("Get", VBKeyword.Get);
+ TestKeyword("GoSub", VBKeyword.GoSub);
+ TestKeyword("Implements", VBKeyword.Implements);
+ TestKeyword("Integer", VBKeyword.Integer);
+ TestKeyword("Let", VBKeyword.Let);
+ TestKeyword("Loop", VBKeyword.Loop);
+ TestKeyword("MustInherit", VBKeyword.MustInherit);
+ TestKeyword("Namespace", VBKeyword.Namespace);
+ TestKeyword("Not", VBKeyword.Not);
+ TestKeyword("And", VBKeyword.And);
+ TestKeyword("ByRef", VBKeyword.ByRef);
+ TestKeyword("Case", VBKeyword.Case);
+ TestKeyword("CChar", VBKeyword.CChar);
+ TestKeyword("Char", VBKeyword.Char);
+ TestKeyword("CObj", VBKeyword.CObj);
+ TestKeyword("CShort", VBKeyword.CShort);
+ TestKeyword("CUInt", VBKeyword.CUInt);
+ TestKeyword("Decimal", VBKeyword.Decimal);
+ TestKeyword("Dim", VBKeyword.Dim);
+ TestKeyword("Each", VBKeyword.Each);
+ TestKeyword("EndIf", VBKeyword.EndIf);
+ TestKeyword("Event", VBKeyword.Event);
+ TestKeyword("For", VBKeyword.For);
+ TestKeyword("GetType", VBKeyword.GetType);
+ TestKeyword("GoTo", VBKeyword.GoTo);
+ TestKeyword("Imports", VBKeyword.Imports);
+ TestKeyword("Interface", VBKeyword.Interface);
+ TestKeyword("Lib", VBKeyword.Lib);
+ TestKeyword("Me", VBKeyword.Me);
+ TestKeyword("MustOverride", VBKeyword.MustOverride);
+ TestKeyword("Narrowing", VBKeyword.Narrowing);
+ TestKeyword("Nothing", VBKeyword.Nothing);
+ TestKeyword("NotInheritable", VBKeyword.NotInheritable);
+ TestKeyword("On", VBKeyword.On);
+ TestKeyword("Or", VBKeyword.Or);
+ TestKeyword("Overrides", VBKeyword.Overrides);
+ TestKeyword("Property", VBKeyword.Property);
+ TestKeyword("ReadOnly", VBKeyword.ReadOnly);
+ TestKeyword("Resume", VBKeyword.Resume);
+ TestKeyword("Set", VBKeyword.Set);
+ TestKeyword("Single", VBKeyword.Single);
+ TestKeyword("String", VBKeyword.String);
+ TestKeyword("Then", VBKeyword.Then);
+ TestKeyword("Try", VBKeyword.Try);
+ TestKeyword("ULong", VBKeyword.ULong);
+ TestKeyword("Wend", VBKeyword.Wend);
+ TestKeyword("With", VBKeyword.With);
+ TestKeyword("NotOverridable", VBKeyword.NotOverridable);
+ TestKeyword("Operator", VBKeyword.Operator);
+ TestKeyword("OrElse", VBKeyword.OrElse);
+ TestKeyword("ParamArray", VBKeyword.ParamArray);
+ TestKeyword("Protected", VBKeyword.Protected);
+ TestKeyword("ReDim", VBKeyword.ReDim);
+ TestKeyword("Return", VBKeyword.Return);
+ TestKeyword("Shadows", VBKeyword.Shadows);
+ TestKeyword("Static", VBKeyword.Static);
+ TestKeyword("Structure", VBKeyword.Structure);
+ TestKeyword("Throw", VBKeyword.Throw);
+ TestKeyword("TryCast", VBKeyword.TryCast);
+ TestKeyword("UShort", VBKeyword.UShort);
+ TestKeyword("When", VBKeyword.When);
+ TestKeyword("WithEvents", VBKeyword.WithEvents);
+ TestKeyword("Object", VBKeyword.Object);
+ TestKeyword("Option", VBKeyword.Option);
+ TestKeyword("Overloads", VBKeyword.Overloads);
+ TestKeyword("Partial", VBKeyword.Partial);
+ TestKeyword("Public", VBKeyword.Public);
+ TestKeyword("SByte", VBKeyword.SByte);
+ TestKeyword("Shared", VBKeyword.Shared);
+ TestKeyword("Step", VBKeyword.Step);
+ TestKeyword("Sub", VBKeyword.Sub);
+ TestKeyword("To", VBKeyword.To);
+ TestKeyword("TypeOf", VBKeyword.TypeOf);
+ TestKeyword("Using", VBKeyword.Using);
+ TestKeyword("While", VBKeyword.While);
+ TestKeyword("WriteOnly", VBKeyword.WriteOnly);
+ TestKeyword("Of", VBKeyword.Of);
+ TestKeyword("Optional", VBKeyword.Optional);
+ TestKeyword("Overridable", VBKeyword.Overridable);
+ TestKeyword("Private", VBKeyword.Private);
+ TestKeyword("RaiseEvent", VBKeyword.RaiseEvent);
+ TestKeyword("RemoveHandler", VBKeyword.RemoveHandler);
+ TestKeyword("Select", VBKeyword.Select);
+ TestKeyword("Short", VBKeyword.Short);
+ TestKeyword("Stop", VBKeyword.Stop);
+ TestKeyword("SyncLock", VBKeyword.SyncLock);
+ TestKeyword("True", VBKeyword.True);
+ TestKeyword("UInteger", VBKeyword.UInteger);
+ TestKeyword("Variant", VBKeyword.Variant);
+ TestKeyword("Widening", VBKeyword.Widening);
+ TestKeyword("Xor", VBKeyword.Xor);
+ }
+
+ private void TestKeyword(string keyword, VBKeyword keywordType)
+ {
+ TestTokenizer(keyword, new VBSymbol(0, 0, 0, keyword, VBSymbolType.Keyword) { Keyword = keywordType });
+ }
+ }
+}
diff --git a/test/System.Web.Razor.Test/Tokenizer/VBTokenizerLiteralTest.cs b/test/System.Web.Razor.Test/Tokenizer/VBTokenizerLiteralTest.cs
new file mode 100644
index 00000000..e6844e42
--- /dev/null
+++ b/test/System.Web.Razor.Test/Tokenizer/VBTokenizerLiteralTest.cs
@@ -0,0 +1,180 @@
+using System.Web.Razor.Tokenizer.Symbols;
+using Xunit;
+
+namespace System.Web.Razor.Test.Tokenizer
+{
+ public class VBTokenizerLiteralTest : VBTokenizerTestBase
+ {
+ [Fact]
+ public void Decimal_Integer_Literal_Is_Recognized()
+ {
+ TestSingleToken("01189998819991197253", VBSymbolType.IntegerLiteral);
+ }
+
+ [Fact]
+ public void Integer_Type_Suffixes_Are_Recognized_In_Decimal_Literal()
+ {
+ TestSingleToken("42S", VBSymbolType.IntegerLiteral);
+ TestSingleToken("42I", VBSymbolType.IntegerLiteral);
+ TestSingleToken("42L", VBSymbolType.IntegerLiteral);
+ TestSingleToken("42US", VBSymbolType.IntegerLiteral);
+ TestSingleToken("42UI", VBSymbolType.IntegerLiteral);
+ TestSingleToken("42UL", VBSymbolType.IntegerLiteral);
+ }
+
+ [Fact]
+ public void Hex_Integer_Literal_Is_Recognized()
+ {
+ TestSingleToken("&HDeadBeef", VBSymbolType.IntegerLiteral);
+ }
+
+ [Fact]
+ public void Integer_Type_Suffixes_Are_Recognized_In_Hex_Literal()
+ {
+ TestSingleToken("&HDeadBeefS", VBSymbolType.IntegerLiteral);
+ TestSingleToken("&HDeadBeefI", VBSymbolType.IntegerLiteral);
+ TestSingleToken("&HDeadBeefL", VBSymbolType.IntegerLiteral);
+ TestSingleToken("&HDeadBeefUS", VBSymbolType.IntegerLiteral);
+ TestSingleToken("&HDeadBeefUI", VBSymbolType.IntegerLiteral);
+ TestSingleToken("&HDeadBeefUL", VBSymbolType.IntegerLiteral);
+ }
+
+ [Fact]
+ public void Octal_Integer_Literal_Is_Recognized()
+ {
+ TestSingleToken("&O77", VBSymbolType.IntegerLiteral);
+ }
+
+ [Fact]
+ public void Integer_Type_Suffixes_Are_Recognized_In_Octal_Literal()
+ {
+ TestSingleToken("&O77S", VBSymbolType.IntegerLiteral);
+ TestSingleToken("&O77I", VBSymbolType.IntegerLiteral);
+ TestSingleToken("&O77L", VBSymbolType.IntegerLiteral);
+ TestSingleToken("&O77US", VBSymbolType.IntegerLiteral);
+ TestSingleToken("&O77UI", VBSymbolType.IntegerLiteral);
+ TestSingleToken("&O77UL", VBSymbolType.IntegerLiteral);
+ }
+
+ [Fact]
+ public void Incomplete_Type_Suffix_Is_Recognized()
+ {
+ TestSingleToken("42U", VBSymbolType.IntegerLiteral);
+ TestSingleToken("&H42U", VBSymbolType.IntegerLiteral);
+ TestSingleToken("&O77U", VBSymbolType.IntegerLiteral);
+ }
+
+ [Fact]
+ public void Integer_With_FloatingPoint_Type_Suffix_Is_Recognized_As_FloatingPointLiteral()
+ {
+ TestSingleToken("42F", VBSymbolType.FloatingPointLiteral);
+ TestSingleToken("42R", VBSymbolType.FloatingPointLiteral);
+ TestSingleToken("42D", VBSymbolType.FloatingPointLiteral);
+ }
+
+ [Fact]
+ public void Simple_FloatingPoint_Is_Recognized()
+ {
+ TestSingleToken("3.14159", VBSymbolType.FloatingPointLiteral);
+ }
+
+ [Fact]
+ public void Integer_With_Exponent_Is_Recognized()
+ {
+ TestSingleToken("1E10", VBSymbolType.FloatingPointLiteral);
+ TestSingleToken("1e10", VBSymbolType.FloatingPointLiteral);
+ TestSingleToken("1E+10", VBSymbolType.FloatingPointLiteral);
+ TestSingleToken("1e+10", VBSymbolType.FloatingPointLiteral);
+ TestSingleToken("1E-10", VBSymbolType.FloatingPointLiteral);
+ TestSingleToken("1e-10", VBSymbolType.FloatingPointLiteral);
+ }
+
+ [Fact]
+ public void Simple_FloatingPoint_With_Exponent_Is_Recognized()
+ {
+ TestSingleToken("3.14159e10", VBSymbolType.FloatingPointLiteral);
+ }
+
+ [Fact]
+ public void FloatingPoint_Between_Zero_And_One_Is_Recognized()
+ {
+ TestSingleToken(".314159e1", VBSymbolType.FloatingPointLiteral);
+ }
+
+ [Fact]
+ public void Simple_String_Literal_Is_Recognized()
+ {
+ TestSingleToken("\"Foo Bar Baz\"", VBSymbolType.StringLiteral);
+ }
+
+ [Fact]
+ public void Two_Double_Quotes_Are_Recognized_As_Escape_Sequence()
+ {
+ TestSingleToken("\"Foo \"\"Bar\"\" Baz\"", VBSymbolType.StringLiteral);
+ }
+
+ [Fact]
+ public void String_Literal_Is_Terminated_At_EOF()
+ {
+ TestSingleToken("\"Foo", VBSymbolType.StringLiteral);
+ }
+
+ [Fact]
+ public void String_Literal_Is_Terminated_At_EOL()
+ {
+ TestTokenizer("\"Foo\nBar", new VBSymbol(0, 0, 0, "\"Foo", VBSymbolType.StringLiteral), IgnoreRemaining);
+ }
+
+ [Fact]
+ public void Character_Literal_Is_Recognized_By_Trailing_C_After_String_Literal()
+ {
+ TestSingleToken("\"abc\"c", VBSymbolType.CharacterLiteral);
+ }
+
+ [Fact]
+ public void LeftDoubleQuote_Is_Valid_DoubleQuote()
+ {
+ // Repeat all the above tests with Unicode Left Double Quote Character U+201C: “
+ TestSingleToken("“Foo Bar Baz“", VBSymbolType.StringLiteral);
+ TestSingleToken("“Foo ““Bar““ Baz“", VBSymbolType.StringLiteral);
+ TestSingleToken("“Foo", VBSymbolType.StringLiteral);
+ TestSingleToken("“abc“c", VBSymbolType.CharacterLiteral);
+ TestTokenizer("“Foo\nBar", new VBSymbol(0, 0, 0, "“Foo", VBSymbolType.StringLiteral), IgnoreRemaining);
+ }
+
+ [Fact]
+ public void RightDoubleQuote_Is_Valid_DoubleQuote()
+ {
+ // Repeat all the above tests with Unicode Right Double Quote Character U+201D: ”
+ TestSingleToken("”Foo Bar Baz”", VBSymbolType.StringLiteral);
+ TestSingleToken("”Foo ””Bar”” Baz”", VBSymbolType.StringLiteral);
+ TestSingleToken("”Foo", VBSymbolType.StringLiteral);
+ TestSingleToken("”abc”c", VBSymbolType.CharacterLiteral);
+ TestTokenizer("”Foo\nBar", new VBSymbol(0, 0, 0, "”Foo", VBSymbolType.StringLiteral), IgnoreRemaining);
+ }
+
+ [Fact]
+ public void DateLiteral_Is_Recognized()
+ {
+ TestSingleToken("# 8/23/1970 3:45:39AM #", VBSymbolType.DateLiteral);
+ }
+
+ [Fact]
+ public void DateLiteral_Is_Terminated_At_EndHash()
+ {
+ TestTokenizer("# 8/23/1970 # 3:45:39AM", new VBSymbol(0, 0, 0, "# 8/23/1970 #", VBSymbolType.DateLiteral), IgnoreRemaining);
+ }
+
+ [Fact]
+ public void DateLiteral_Is_Terminated_At_EOF()
+ {
+ TestSingleToken("# 8/23/1970 3:45:39AM", VBSymbolType.DateLiteral);
+ }
+
+ [Fact]
+ public void DateLiteral_Is_Terminated_At_EOL()
+ {
+ TestTokenizer("# 8/23/1970\n3:45:39AM", new VBSymbol(0, 0, 0, "# 8/23/1970", VBSymbolType.DateLiteral), IgnoreRemaining);
+ }
+ }
+}
diff --git a/test/System.Web.Razor.Test/Tokenizer/VBTokenizerOperatorsTest.cs b/test/System.Web.Razor.Test/Tokenizer/VBTokenizerOperatorsTest.cs
new file mode 100644
index 00000000..47eab61c
--- /dev/null
+++ b/test/System.Web.Razor.Test/Tokenizer/VBTokenizerOperatorsTest.cs
@@ -0,0 +1,152 @@
+using System.Web.Razor.Tokenizer.Symbols;
+using Xunit;
+
+namespace System.Web.Razor.Test.Tokenizer
+{
+ public class VBTokenizerOperatorsTest : VBTokenizerTestBase
+ {
+ [Fact]
+ public void Line_Continuation_Character_Is_Recognized()
+ {
+ TestSingleToken("_", VBSymbolType.LineContinuation);
+ }
+
+ [Fact]
+ public void LeftParen_Is_Recognized()
+ {
+ TestSingleToken("(", VBSymbolType.LeftParenthesis);
+ }
+
+ [Fact]
+ public void RightParen_Is_Recognized()
+ {
+ TestSingleToken(")", VBSymbolType.RightParenthesis);
+ }
+
+ [Fact]
+ public void LeftBracket_Is_Recognized()
+ {
+ TestSingleToken("[", VBSymbolType.LeftBracket);
+ }
+
+ [Fact]
+ public void RightBracket_Is_Recognized()
+ {
+ TestSingleToken("]", VBSymbolType.RightBracket);
+ }
+
+ [Fact]
+ public void LeftBrace_Is_Recognized()
+ {
+ TestSingleToken("{", VBSymbolType.LeftBrace);
+ }
+
+ [Fact]
+ public void RightBrace_Is_Recognized()
+ {
+ TestSingleToken("}", VBSymbolType.RightBrace);
+ }
+
+ [Fact]
+ public void Bang_Is_Recognized()
+ {
+ TestSingleToken("!", VBSymbolType.Bang);
+ }
+
+ [Fact]
+ public void Hash_Is_Recognized()
+ {
+ TestSingleToken("#", VBSymbolType.Hash);
+ }
+
+ [Fact]
+ public void Comma_Is_Recognized()
+ {
+ TestSingleToken(",", VBSymbolType.Comma);
+ }
+
+ [Fact]
+ public void Dot_Is_Recognized()
+ {
+ TestSingleToken(".", VBSymbolType.Dot);
+ }
+
+ [Fact]
+ public void Colon_Is_Recognized()
+ {
+ TestSingleToken(":", VBSymbolType.Colon);
+ }
+
+ [Fact]
+ public void QuestionMark_Is_Recognized()
+ {
+ TestSingleToken("?", VBSymbolType.QuestionMark);
+ }
+
+ [Fact]
+ public void Concatenation_Is_Recognized()
+ {
+ TestSingleToken("&", VBSymbolType.Concatenation);
+ }
+
+ [Fact]
+ public void Multiply_Is_Recognized()
+ {
+ TestSingleToken("*", VBSymbolType.Multiply);
+ }
+
+ [Fact]
+ public void Add_Is_Recognized()
+ {
+ TestSingleToken("+", VBSymbolType.Add);
+ }
+
+ [Fact]
+ public void Subtract_Is_Recognized()
+ {
+ TestSingleToken("-", VBSymbolType.Subtract);
+ }
+
+ [Fact]
+ public void Divide_Is_Recognized()
+ {
+ TestSingleToken("/", VBSymbolType.Divide);
+ }
+
+ [Fact]
+ public void IntegerDivide_Is_Recognized()
+ {
+ TestSingleToken("\\", VBSymbolType.IntegerDivide);
+ }
+
+ [Fact]
+ public void Exponentiation_Is_Recognized()
+ {
+ TestSingleToken("^", VBSymbolType.Exponentiation);
+ }
+
+ [Fact]
+ public void Equal_Is_Recognized()
+ {
+ TestSingleToken("=", VBSymbolType.Equal);
+ }
+
+ [Fact]
+ public void LessThan_Is_Recognized()
+ {
+ TestSingleToken("<", VBSymbolType.LessThan);
+ }
+
+ [Fact]
+ public void GreaterThan_Is_Recognized()
+ {
+ TestSingleToken(">", VBSymbolType.GreaterThan);
+ }
+
+ [Fact]
+ public void Dollar_Is_Recognized()
+ {
+ TestSingleToken("$", VBSymbolType.Dollar);
+ }
+ }
+}
diff --git a/test/System.Web.Razor.Test/Tokenizer/VBTokenizerTest.cs b/test/System.Web.Razor.Test/Tokenizer/VBTokenizerTest.cs
new file mode 100644
index 00000000..01d8f5ca
--- /dev/null
+++ b/test/System.Web.Razor.Test/Tokenizer/VBTokenizerTest.cs
@@ -0,0 +1,94 @@
+using System.Web.Razor.Tokenizer;
+using System.Web.Razor.Tokenizer.Symbols;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Razor.Test.Tokenizer
+{
+ public class VBTokenizerTest : VBTokenizerTestBase
+ {
+ [Fact]
+ public void Constructor_Throws_ArgNull_If_Null_Source_Provided()
+ {
+ Assert.ThrowsArgumentNull(() => new CSharpTokenizer(null), "source");
+ }
+
+ [Fact]
+ public void Next_Returns_Null_When_EOF_Reached()
+ {
+ TestTokenizer("");
+ }
+
+ [Fact]
+ public void Next_Returns_Newline_Token_For_Single_CR()
+ {
+ TestTokenizer("\r\ra",
+ new VBSymbol(0, 0, 0, "\r", VBSymbolType.NewLine),
+ new VBSymbol(1, 1, 0, "\r", VBSymbolType.NewLine),
+ IgnoreRemaining);
+ }
+
+ [Fact]
+ public void Next_Returns_Newline_Token_For_Single_LF()
+ {
+ TestTokenizer("\n\na",
+ new VBSymbol(0, 0, 0, "\n", VBSymbolType.NewLine),
+ new VBSymbol(1, 1, 0, "\n", VBSymbolType.NewLine),
+ IgnoreRemaining);
+ }
+
+ [Fact]
+ public void Next_Returns_Newline_Token_For_Single_NEL()
+ {
+ // NEL: Unicode "Next Line" U+0085
+ TestTokenizer("\u0085\u0085a",
+ new VBSymbol(0, 0, 0, "\u0085", VBSymbolType.NewLine),
+ new VBSymbol(1, 1, 0, "\u0085", VBSymbolType.NewLine),
+ IgnoreRemaining);
+ }
+
+ [Fact]
+ public void Next_Returns_Newline_Token_For_Single_Line_Separator()
+ {
+ // Unicode "Line Separator" U+2028
+ TestTokenizer("\u2028\u2028a",
+ new VBSymbol(0, 0, 0, "\u2028", VBSymbolType.NewLine),
+ new VBSymbol(1, 1, 0, "\u2028", VBSymbolType.NewLine),
+ IgnoreRemaining);
+ }
+
+ [Fact]
+ public void Next_Returns_Newline_Token_For_Single_Paragraph_Separator()
+ {
+ // Unicode "Paragraph Separator" U+2029
+ TestTokenizer("\u2029\u2029a",
+ new VBSymbol(0, 0, 0, "\u2029", VBSymbolType.NewLine),
+ new VBSymbol(1, 1, 0, "\u2029", VBSymbolType.NewLine),
+ IgnoreRemaining);
+ }
+
+ [Fact]
+ public void Next_Returns_Single_Newline_Token_For_CRLF()
+ {
+ TestTokenizer("\r\n\r\na",
+ new VBSymbol(0, 0, 0, "\r\n", VBSymbolType.NewLine),
+ new VBSymbol(2, 1, 0, "\r\n", VBSymbolType.NewLine),
+ IgnoreRemaining);
+ }
+
+ [Fact]
+ public void Next_Returns_Token_For_Whitespace_Characters()
+ {
+ TestTokenizer(" \f\t\u000B \n ",
+ new VBSymbol(0, 0, 0, " \f\t\u000B ", VBSymbolType.WhiteSpace),
+ new VBSymbol(5, 0, 5, "\n", VBSymbolType.NewLine),
+ new VBSymbol(6, 1, 0, " ", VBSymbolType.WhiteSpace));
+ }
+
+ [Fact]
+ public void Transition_Is_Recognized()
+ {
+ TestSingleToken("@", VBSymbolType.Transition);
+ }
+ }
+}
diff --git a/test/System.Web.Razor.Test/Tokenizer/VBTokenizerTestBase.cs b/test/System.Web.Razor.Test/Tokenizer/VBTokenizerTestBase.cs
new file mode 100644
index 00000000..c6252c04
--- /dev/null
+++ b/test/System.Web.Razor.Test/Tokenizer/VBTokenizerTestBase.cs
@@ -0,0 +1,26 @@
+using System.Web.Razor.Text;
+using System.Web.Razor.Tokenizer;
+using System.Web.Razor.Tokenizer.Symbols;
+
+namespace System.Web.Razor.Test.Tokenizer
+{
+ public abstract class VBTokenizerTestBase : TokenizerTestBase<VBSymbol, VBSymbolType>
+ {
+ private static VBSymbol _ignoreRemaining = new VBSymbol(0, 0, 0, String.Empty, VBSymbolType.Unknown);
+
+ protected override VBSymbol IgnoreRemaining
+ {
+ get { return _ignoreRemaining; }
+ }
+
+ protected override Tokenizer<VBSymbol, VBSymbolType> CreateTokenizer(ITextDocument source)
+ {
+ return new VBTokenizer(source);
+ }
+
+ protected void TestSingleToken(string text, VBSymbolType expectedSymbolType)
+ {
+ TestTokenizer(text, new VBSymbol(0, 0, 0, text, expectedSymbolType));
+ }
+ }
+}
diff --git a/test/System.Web.Razor.Test/Utils/DisposableActionTest.cs b/test/System.Web.Razor.Test/Utils/DisposableActionTest.cs
new file mode 100644
index 00000000..9ddd06ad
--- /dev/null
+++ b/test/System.Web.Razor.Test/Utils/DisposableActionTest.cs
@@ -0,0 +1,29 @@
+using System.Web.Razor.Utils;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Razor.Test.Utils
+{
+ public class DisposableActionTest
+ {
+ [Fact]
+ public void ConstructorRequiresNonNullAction()
+ {
+ Assert.ThrowsArgumentNull(() => new DisposableAction(null), "action");
+ }
+
+ [Fact]
+ public void ActionIsExecutedOnDispose()
+ {
+ // Arrange
+ bool called = false;
+ DisposableAction action = new DisposableAction(() => { called = true; });
+
+ // Act
+ action.Dispose();
+
+ // Assert
+ Assert.True(called, "The action was not run when the DisposableAction was disposed");
+ }
+ }
+}
diff --git a/test/System.Web.Razor.Test/Utils/EnumerableUtils.cs b/test/System.Web.Razor.Test/Utils/EnumerableUtils.cs
new file mode 100644
index 00000000..1fbab3d7
--- /dev/null
+++ b/test/System.Web.Razor.Test/Utils/EnumerableUtils.cs
@@ -0,0 +1,17 @@
+using System.Collections.Generic;
+
+namespace System.Web.Razor.Test.Utils
+{
+ public static class EnumerableUtils
+ {
+ public static void RunPairwise<T, V>(IEnumerable<T> left, IEnumerable<V> right, Action<T, V> action)
+ {
+ IEnumerator<T> leftEnum = left.GetEnumerator();
+ IEnumerator<V> rightEnum = right.GetEnumerator();
+ while (leftEnum.MoveNext() && rightEnum.MoveNext())
+ {
+ action(leftEnum.Current, rightEnum.Current);
+ }
+ }
+ }
+}
diff --git a/test/System.Web.Razor.Test/Utils/MiscAssert.cs b/test/System.Web.Razor.Test/Utils/MiscAssert.cs
new file mode 100644
index 00000000..833800b0
--- /dev/null
+++ b/test/System.Web.Razor.Test/Utils/MiscAssert.cs
@@ -0,0 +1,31 @@
+using System.Linq.Expressions;
+using Xunit;
+
+namespace System.Web.Razor.Test.Utils
+{
+ public static class MiscAssert
+ {
+ public static void AssertBothNullOrPropertyEqual<T>(T expected, T actual, Expression<Func<T, object>> propertyExpr, string objectName)
+ {
+ // Unpack convert expressions
+ Expression expr = propertyExpr.Body;
+ while (expr.NodeType == ExpressionType.Convert)
+ {
+ expr = ((UnaryExpression)expr).Operand;
+ }
+
+ string propertyName = ((MemberExpression)expr).Member.Name;
+ Func<T, object> property = propertyExpr.Compile();
+
+ if (expected == null)
+ {
+ Assert.Null(actual);
+ }
+ else
+ {
+ Assert.NotNull(actual);
+ Assert.Equal(property(expected), property(actual));
+ }
+ }
+ }
+}
diff --git a/test/System.Web.Razor.Test/Utils/MiscUtils.cs b/test/System.Web.Razor.Test/Utils/MiscUtils.cs
new file mode 100644
index 00000000..c1e5db39
--- /dev/null
+++ b/test/System.Web.Razor.Test/Utils/MiscUtils.cs
@@ -0,0 +1,33 @@
+using System.Diagnostics;
+using System.Text.RegularExpressions;
+using System.Threading;
+using Xunit;
+
+namespace System.Web.Razor.Test.Utils
+{
+ class MiscUtils
+ {
+ public const int TimeoutInSeconds = 1;
+
+ public static string StripRuntimeVersion(string s)
+ {
+ return Regex.Replace(s, @"Runtime Version:[\d.]*", "Runtime Version:N.N.NNNNN.N");
+ }
+
+ public static void DoWithTimeoutIfNotDebugging(Func<int, bool> withTimeout)
+ {
+#if DEBUG
+ if (Debugger.IsAttached)
+ {
+ withTimeout(Timeout.Infinite);
+ }
+ else
+ {
+#endif
+ Assert.True(withTimeout((int)TimeSpan.FromSeconds(TimeoutInSeconds).TotalMilliseconds), "Timeout expired!");
+#if DEBUG
+ }
+#endif
+ }
+ }
+}
diff --git a/test/System.Web.Razor.Test/Utils/SpanAssert.cs b/test/System.Web.Razor.Test/Utils/SpanAssert.cs
new file mode 100644
index 00000000..b6a9b39d
--- /dev/null
+++ b/test/System.Web.Razor.Test/Utils/SpanAssert.cs
@@ -0,0 +1,56 @@
+using System.Collections.Generic;
+using System.Globalization;
+using System.Text;
+using System.Web.Razor.Parser.SyntaxTree;
+using System.Web.Razor.Text;
+using Xunit;
+
+namespace System.Web.Razor.Test.Utils
+{
+ public static class EventAssert
+ {
+ public static void NoMoreSpans(IEnumerator<Span> enumerator)
+ {
+ IList<Span> tokens = new List<Span>();
+ while (enumerator.MoveNext())
+ {
+ tokens.Add(enumerator.Current);
+ }
+
+ Assert.False(tokens.Count > 0, String.Format(CultureInfo.InvariantCulture, @"There are more tokens available from the source: {0}", FormatList(tokens)));
+ }
+
+ private static string FormatList<T>(IList<T> items)
+ {
+ StringBuilder tokenString = new StringBuilder();
+ foreach (T item in items)
+ {
+ tokenString.AppendLine(item.ToString());
+ }
+ return tokenString.ToString();
+ }
+
+ public static void NextSpanIs(IEnumerator<Span> enumerator, SpanKind type, string content, SourceLocation location)
+ {
+ Assert.True(enumerator.MoveNext(), "There is no next token!");
+ IsSpan(enumerator.Current, type, content, location);
+ }
+
+ public static void NextSpanIs(IEnumerator<Span> enumerator, SpanKind type, string content, int actualIndex, int lineIndex, int charIndex)
+ {
+ NextSpanIs(enumerator, type, content, new SourceLocation(actualIndex, lineIndex, charIndex));
+ }
+
+ public static void IsSpan(Span tok, SpanKind type, string content, int actualIndex, int lineIndex, int charIndex)
+ {
+ IsSpan(tok, type, content, new SourceLocation(actualIndex, lineIndex, charIndex));
+ }
+
+ public static void IsSpan(Span tok, SpanKind type, string content, SourceLocation location)
+ {
+ Assert.Equal(content, tok.Content);
+ Assert.Equal(type, tok.Kind);
+ Assert.Equal(location, tok.Start);
+ }
+ }
+}
diff --git a/test/System.Web.Razor.Test/VBRazorCodeLanguageTest.cs b/test/System.Web.Razor.Test/VBRazorCodeLanguageTest.cs
new file mode 100644
index 00000000..78e26a83
--- /dev/null
+++ b/test/System.Web.Razor.Test/VBRazorCodeLanguageTest.cs
@@ -0,0 +1,53 @@
+using System.Web.Razor.Generator;
+using System.Web.Razor.Parser;
+using Microsoft.VisualBasic;
+using Xunit;
+
+namespace System.Web.Razor.Test
+{
+ public class VBRazorCodeLanguageTest
+ {
+ [Fact]
+ public void CreateCodeParserReturnsNewVBCodeParser()
+ {
+ // Arrange
+ RazorCodeLanguage service = new VBRazorCodeLanguage();
+
+ // Act
+ ParserBase parser = service.CreateCodeParser();
+
+ // Assert
+ Assert.NotNull(parser);
+ Assert.IsType<VBCodeParser>(parser);
+ }
+
+ [Fact]
+ public void CreateCodeGeneratorParserListenerReturnsNewCSharpCodeGeneratorParserListener()
+ {
+ // Arrange
+ RazorCodeLanguage service = new VBRazorCodeLanguage();
+
+ // Act
+ RazorEngineHost host = new RazorEngineHost(new VBRazorCodeLanguage());
+ RazorCodeGenerator generator = service.CreateCodeGenerator("Foo", "Bar", "Baz", host);
+
+ // Assert
+ Assert.NotNull(generator);
+ Assert.IsType<VBRazorCodeGenerator>(generator);
+ Assert.Equal("Foo", generator.ClassName);
+ Assert.Equal("Bar", generator.RootNamespaceName);
+ Assert.Equal("Baz", generator.SourceFileName);
+ Assert.Same(host, generator.Host);
+ }
+
+ [Fact]
+ public void CodeDomProviderTypeReturnsVBCodeProvider()
+ {
+ // Arrange
+ RazorCodeLanguage service = new VBRazorCodeLanguage();
+
+ // Assert
+ Assert.Equal(typeof(VBCodeProvider), service.CodeDomProviderType);
+ }
+ }
+}
diff --git a/test/System.Web.Razor.Test/packages.config b/test/System.Web.Razor.Test/packages.config
new file mode 100644
index 00000000..d5aa6401
--- /dev/null
+++ b/test/System.Web.Razor.Test/packages.config
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<packages>
+ <package id="Moq" version="4.0.10827" />
+ <package id="xunit" version="1.9.0.1566" />
+ <package id="xunit.extensions" version="1.9.0.1566" />
+</packages> \ No newline at end of file
diff --git a/test/System.Web.WebPages.Administration.Test/AdminPackageTest.cs b/test/System.Web.WebPages.Administration.Test/AdminPackageTest.cs
new file mode 100644
index 00000000..aee0dbeb
--- /dev/null
+++ b/test/System.Web.WebPages.Administration.Test/AdminPackageTest.cs
@@ -0,0 +1,397 @@
+using System.Collections.Specialized;
+using System.IO;
+using System.Text;
+using System.Web.Helpers;
+using System.Web.Hosting;
+using System.Web.Security;
+using Moq;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.WebPages.Administration.Test
+{
+ public class AdminPackageTest
+ {
+ [Fact]
+ public void GetAdminVirtualPathThrowsIfPathIsNull()
+ {
+ // Act & Assert
+ Assert.ThrowsArgumentNull(() => SiteAdmin.GetVirtualPath(null), "virtualPath");
+ }
+
+ [Fact]
+ public void GetAdminVirtualPathDoesNotAppendAdminVirtualPathIfPathStartsWithAdminVirtualPath()
+ {
+ // Act
+ string vpath = SiteAdmin.GetVirtualPath("~/_Admin/Foo");
+
+ // Assert
+ Assert.Equal("~/_Admin/Foo", vpath);
+ }
+
+ [Fact]
+ public void GetAdminVirtualPathAppendsAdminVirtualPath()
+ {
+ // Act
+ string vpath = SiteAdmin.GetVirtualPath("~/Foo");
+
+ // Assert
+ Assert.Equal("~/_Admin/Foo", vpath);
+ }
+
+ [Fact]
+ public void SetAuthCookieAddsAuthCookieToResponseCollection()
+ {
+ // Arrange
+ var mockResponse = new Mock<HttpResponseBase>();
+ var cookies = new HttpCookieCollection();
+ mockResponse.Setup(m => m.Cookies).Returns(cookies);
+
+ // Act
+ AdminSecurity.SetAuthCookie(mockResponse.Object);
+
+ // Assert
+ Assert.NotNull(cookies[".ASPXADMINAUTH"]);
+ }
+
+ [Fact]
+ public void GetAuthAdminCookieCreatesAnAuthTicketWithUserDataSetToAdmin()
+ {
+ // Arrange
+ var cookie = AdminSecurity.GetAuthCookie();
+
+ // Act
+ FormsAuthenticationTicket ticket = FormsAuthentication.Decrypt(cookie.Value);
+
+ // Assert
+ Assert.Equal(".ASPXADMINAUTH", cookie.Name);
+ Assert.True(cookie.HttpOnly);
+ Assert.Equal(2, ticket.Version);
+ Assert.Equal("ADMIN", ticket.UserData);
+ }
+
+ [Fact]
+ public void IsAuthenticatedReturnsFalseIfAuthCookieNotInCollection()
+ {
+ // Arrange
+ var mockRequest = new Mock<HttpRequestBase>();
+ var cookies = new HttpCookieCollection();
+ mockRequest.Setup(m => m.Cookies).Returns(cookies);
+
+ // Act
+ bool authorized = AdminSecurity.IsAuthenticated(mockRequest.Object);
+
+ // Assert
+ Assert.False(authorized);
+ }
+
+ [Fact]
+ public void IsAuthenticatedReturnsFalseIfAuthCookieInCollectionAndIsNotAValidAdminAuthCookie()
+ {
+ // Arrange
+ var mockRequest = new Mock<HttpRequestBase>();
+ var cookies = new HttpCookieCollection();
+ mockRequest.Setup(m => m.Cookies).Returns(cookies);
+ cookies.Add(new HttpCookie(".ASPXADMINAUTH", "test"));
+
+ // Act
+ bool authorized = AdminSecurity.IsAuthenticated(mockRequest.Object);
+
+ // Assert
+ Assert.False(authorized);
+ }
+
+ [Fact]
+ public void IsAuthenticatedReturnsTrueIfAuthCookieIsValid()
+ {
+ // Arrange
+ var mockRequest = new Mock<HttpRequestBase>();
+ var cookies = new HttpCookieCollection();
+ mockRequest.Setup(m => m.Cookies).Returns(cookies);
+ cookies.Add(AdminSecurity.GetAuthCookie());
+
+ // Act
+ bool authorized = AdminSecurity.IsAuthenticated(mockRequest.Object);
+
+ // Assert
+ Assert.True(authorized);
+ }
+
+ [Fact]
+ public void GetRedirectUrlAppendsAppRelativePathAsReturnUrl()
+ {
+ // Arrange
+ var mockRequest = new Mock<HttpRequestBase>();
+ mockRequest.Setup(m => m.RawUrl).Returns("~/_Admin/foo/bar/baz");
+ mockRequest.Setup(m => m.QueryString).Returns(new NameValueCollection());
+
+ // Act
+ string redirectUrl = SiteAdmin.GetRedirectUrl(mockRequest.Object, "register", MakeAppRelative);
+
+ // Assert
+ Assert.Equal("~/_Admin/register?ReturnUrl=%7e%2f_Admin%2ffoo%2fbar%2fbaz", redirectUrl);
+ }
+
+ [Fact]
+ public void GetRedirectUrlDoesNotAppendsAppRelativePathAsReturnUrlIfAlreadyExists()
+ {
+ // Arrange
+ var mockRequest = new Mock<HttpRequestBase>();
+ mockRequest.Setup(m => m.RawUrl).Returns("~/_Admin/foo/bar/baz?ReturnUrl=~/foo");
+ var queryString = new NameValueCollection();
+ queryString["ReturnUrl"] = "~/foo";
+ mockRequest.Setup(m => m.QueryString).Returns(queryString);
+
+ // Act
+ string redirectUrl = SiteAdmin.GetRedirectUrl(mockRequest.Object, "register", MakeAppRelative);
+
+ // Assert
+ Assert.Equal("~/_Admin/register?ReturnUrl=%7e%2ffoo", redirectUrl);
+ }
+
+ [Fact]
+ public void GetReturnUrlReturnsNullIfNotSet()
+ {
+ // Arrange
+ var mockRequest = new Mock<HttpRequestBase>();
+ mockRequest.Setup(m => m.QueryString).Returns(new NameValueCollection());
+
+ // Act
+ string returlUrl = SiteAdmin.GetReturnUrl(mockRequest.Object);
+
+ // Assert
+ Assert.Null(returlUrl);
+ }
+
+ [Fact]
+ public void GetReturnUrlThrowsIfReturnUrlIsNotAppRelative()
+ {
+ // Arrange
+ var mockRequest = new Mock<HttpRequestBase>();
+ var queryString = new NameValueCollection();
+ queryString["ReturnUrl"] = "http://www.bing.com";
+ mockRequest.Setup(m => m.QueryString).Returns(queryString);
+
+ // Act & Assert
+ Assert.Throws<InvalidOperationException>(() => SiteAdmin.GetReturnUrl(mockRequest.Object), "The return URL specified for request redirection is invalid.");
+ }
+
+ [Fact]
+ public void GetReturnUrlReturnsReturlUrlQueryStringParameterIfItIsAppRelative()
+ {
+ // Arrange
+ var mockRequest = new Mock<HttpRequestBase>();
+ var queryString = new NameValueCollection();
+ queryString["ReturnUrl"] = "~/_Admin/bar?foo=1";
+ mockRequest.Setup(m => m.QueryString).Returns(queryString);
+
+ // Act
+ string returnUrl = SiteAdmin.GetReturnUrl(mockRequest.Object);
+
+ // Assert
+ Assert.Equal("~/_Admin/bar?foo=1", returnUrl);
+ }
+
+ [Fact]
+ public void SaveAdminPasswordUsesCryptoToWritePasswordAndSalt()
+ {
+ // Arrange
+ var password = "some-random-password";
+ MemoryStream ms = new MemoryStream();
+
+ // Act
+ bool passwordSaved = AdminSecurity.SaveTemporaryPassword(password, () => ms);
+
+ // Assert
+ Assert.True(passwordSaved);
+ string savedPassword = Encoding.Default.GetString(ms.ToArray());
+ // Trim everything after the new line. Cannot use the properties from the stream since it is already closed by the writer.
+ savedPassword = savedPassword.Substring(0, savedPassword.IndexOf(Environment.NewLine));
+
+ Assert.True(Crypto.VerifyHashedPassword(savedPassword, password));
+ }
+
+ [Fact]
+ public void SaveAdminPasswordReturnsFalseIfGettingStreamThrowsUnauthorizedAccessException()
+ {
+ // Act
+ bool passwordSaved = AdminSecurity.SaveTemporaryPassword("password", () => { throw new UnauthorizedAccessException(); });
+
+ // Assert
+ Assert.False(passwordSaved);
+ }
+
+ [Fact]
+ public void CheckPasswordReturnsTrueIfPasswordIsValid()
+ {
+ // Arrange
+ var ms = new MemoryStream();
+ var writer = new StreamWriter(ms);
+ writer.WriteLine(Crypto.HashPassword("password"));
+ writer.Flush();
+ ms.Seek(0, SeekOrigin.Begin);
+
+ // Act
+ bool passwordIsValid = AdminSecurity.CheckPassword("password", () => ms);
+
+ // Assert
+ Assert.True(passwordIsValid);
+
+ writer.Close();
+ }
+
+ [Fact]
+ public void HasAdminPasswordReturnsTrueIfAdminPasswordFileExists()
+ {
+ // Arrange
+ Mock<VirtualPathProvider> mockVpp = new Mock<VirtualPathProvider>();
+ mockVpp.Setup(m => m.FileExists("~/App_Data/Admin/Password.config")).Returns(true);
+
+ // Act
+ bool hasPassword = AdminSecurity.HasAdminPassword(mockVpp.Object);
+
+ // Assert
+ Assert.True(hasPassword);
+ }
+
+ [Fact]
+ public void HasAdminPasswordReturnsFalseIfAdminPasswordFileDoesNotExists()
+ {
+ // Arrange
+ Mock<VirtualPathProvider> mockVpp = new Mock<VirtualPathProvider>();
+ mockVpp.Setup(m => m.FileExists("~/App_Data/Admin/Password.config")).Returns(false);
+
+ // Act
+ bool hasPassword = AdminSecurity.HasAdminPassword(mockVpp.Object);
+
+ // Assert
+ Assert.False(hasPassword);
+ }
+
+ [Fact]
+ public void HasTemporaryPasswordReturnsTrueIfAdminPasswordFileExists()
+ {
+ // Arrange
+ Mock<VirtualPathProvider> mockVpp = new Mock<VirtualPathProvider>();
+ mockVpp.Setup(m => m.FileExists("~/App_Data/Admin/_Password.config")).Returns(true);
+
+ // Act
+ bool hasPassword = AdminSecurity.HasTemporaryPassword(mockVpp.Object);
+
+ // Assert
+ Assert.True(hasPassword);
+ }
+
+ [Fact]
+ public void HasTemporaryPasswordReturnsFalseIfAdminPasswordFileDoesNotExists()
+ {
+ // Arrange
+ Mock<VirtualPathProvider> mockVpp = new Mock<VirtualPathProvider>();
+ mockVpp.Setup(m => m.FileExists("~/App_Data/Admin/_Password.config")).Returns(false);
+
+ // Act
+ bool hasPassword = AdminSecurity.HasTemporaryPassword(mockVpp.Object);
+
+ // Assert
+ Assert.False(hasPassword);
+ }
+
+ [Fact]
+ public void NoPasswordOrTemporaryPasswordRedirectsToRegisterPage()
+ {
+ AssertSecure(requestUrl: "~/",
+ passwordExists: false,
+ temporaryPasswordExists: false,
+ expectedUrl: "~/_Admin/Register.cshtml?ReturnUrl=%7e%2f");
+ }
+
+ [Fact]
+ public void IfPasswordExistsRedirectsToLoginPage()
+ {
+ AssertSecure(requestUrl: "~/",
+ passwordExists: true,
+ temporaryPasswordExists: false,
+ expectedUrl: "~/_Admin/Login.cshtml?ReturnUrl=%7e%2f");
+ }
+
+ [Fact]
+ public void IfPasswordExistsRedirectsToLoginPageEvenIfTemporaryPasswordFileExists()
+ {
+ AssertSecure(requestUrl: "~/",
+ passwordExists: true,
+ temporaryPasswordExists: true,
+ expectedUrl: "~/_Admin/Login.cshtml?ReturnUrl=%7e%2f");
+ }
+
+ [Fact]
+ public void IfTemporaryPasswordExistsRedirectsToInstructionsPage()
+ {
+ AssertSecure(requestUrl: "~/",
+ passwordExists: false,
+ temporaryPasswordExists: true,
+ expectedUrl: "~/_Admin/EnableInstructions.cshtml?ReturnUrl=%7e%2f");
+ }
+
+ [Fact]
+ public void NoRedirectIfAlreadyGoingToRedirectPage()
+ {
+ AssertSecure(requestUrl: "~/_Admin/Register.cshtml",
+ passwordExists: false,
+ temporaryPasswordExists: false,
+ expectedUrl: null);
+
+ AssertSecure(requestUrl: "~/_Admin/Login.cshtml",
+ passwordExists: true,
+ temporaryPasswordExists: false,
+ expectedUrl: null);
+
+ AssertSecure(requestUrl: "~/_Admin/EnableInstructions.cshtml",
+ passwordExists: false,
+ temporaryPasswordExists: true,
+ expectedUrl: null);
+ }
+
+ private static void AssertSecure(string requestUrl, bool passwordExists, bool temporaryPasswordExists, string expectedUrl)
+ {
+ // Arrange
+ var vpp = new Mock<VirtualPathProvider>();
+ if (temporaryPasswordExists)
+ {
+ vpp.Setup(m => m.FileExists("~/App_Data/Admin/_Password.config")).Returns(true);
+ }
+
+ if (passwordExists)
+ {
+ vpp.Setup(m => m.FileExists("~/App_Data/Admin/Password.config")).Returns(true);
+ }
+
+ string redirectUrl = null;
+ var response = new Mock<HttpResponseBase>();
+ response.Setup(m => m.Redirect(It.IsAny<string>())).Callback<string>(url => redirectUrl = url);
+ var request = new Mock<HttpRequestBase>();
+ request.Setup(m => m.QueryString).Returns(new NameValueCollection());
+ request.Setup(m => m.RawUrl).Returns(requestUrl);
+ var cookies = new HttpCookieCollection();
+ request.Setup(m => m.Cookies).Returns(cookies);
+ var context = new Mock<HttpContextBase>();
+ context.Setup(m => m.Request).Returns(request.Object);
+ context.Setup(m => m.Response).Returns(response.Object);
+ var startPage = new Mock<StartPage>() { CallBase = true };
+ var page = new Mock<WebPageRenderingBase>();
+ page.Setup(m => m.VirtualPath).Returns(requestUrl);
+ startPage.Object.ChildPage = page.Object;
+ page.Setup(m => m.Context).Returns(context.Object);
+
+ // Act
+ AdminSecurity.Authorize(startPage.Object, vpp.Object, MakeAppRelative);
+
+ // Assert
+ Assert.Equal(expectedUrl, redirectUrl);
+ }
+
+ private static string MakeAppRelative(string path)
+ {
+ return path;
+ }
+ }
+}
diff --git a/test/System.Web.WebPages.Administration.Test/PackageManagerModuleTest.cs b/test/System.Web.WebPages.Administration.Test/PackageManagerModuleTest.cs
new file mode 100644
index 00000000..2f57bf85
--- /dev/null
+++ b/test/System.Web.WebPages.Administration.Test/PackageManagerModuleTest.cs
@@ -0,0 +1,150 @@
+using System.Collections.Generic;
+using System.Linq;
+using System.Web.WebPages.Administration.PackageManager;
+using Moq;
+using Xunit;
+
+namespace System.Web.WebPages.Administration.Test
+{
+ public class PackageManagerModuleTest
+ {
+ [Fact]
+ public void InitSourceFileDoesNotAffectSourcesFileWhenFeedIsNotNull()
+ {
+ // Arrange
+ bool sourceFileCalled = false;
+ var sourceFile = GetPackagesSourceFile();
+ sourceFile.Setup(s => s.Exists()).Returns(false);
+ sourceFile.Setup(s => s.WriteSources(It.IsAny<IEnumerable<WebPackageSource>>())).Callback(() => sourceFileCalled = true);
+ sourceFile.Setup(c => c.ReadSources()).Callback(() => sourceFileCalled = true);
+ ISet<WebPackageSource> set = new HashSet<WebPackageSource>();
+
+ // Act
+ PackageManagerModule.InitPackageSourceFile(sourceFile.Object, ref set);
+
+ // Assert
+ Assert.False(sourceFileCalled);
+ }
+
+ [Fact]
+ public void InitSourceFileWritesToDiskIfSourcesFileDoesNotExist()
+ {
+ // Arrange
+ ISet<WebPackageSource> set = null;
+ var sourceFile = GetPackagesSourceFile();
+ sourceFile.Setup(s => s.Exists()).Returns(false);
+ sourceFile.Setup(s => s.WriteSources(It.IsAny<IEnumerable<WebPackageSource>>()));
+
+ // Act
+ PackageManagerModule.InitPackageSourceFile(sourceFile.Object, ref set);
+
+ Assert.NotNull(set);
+ Assert.Equal(set.Count(), 2);
+ Assert.Equal(set.First().Source, "http://go.microsoft.com/fwlink/?LinkID=226946");
+ Assert.Equal(set.Last().Source, "http://go.microsoft.com/fwlink/?LinkID=226948");
+ }
+
+ [Fact]
+ public void InitSourceFileReadsFromDiskWhenFileAlreadyExists()
+ {
+ // Arrange
+ var sourceFile = GetPackagesSourceFile();
+ sourceFile.Setup(s => s.Exists()).Returns(true);
+ ISet<WebPackageSource> set = null;
+
+ // Act
+ PackageManagerModule.InitPackageSourceFile(sourceFile.Object, ref set);
+
+ // Assert
+ Assert.NotNull(set);
+ Assert.Equal(set.Count(), 2);
+ }
+
+ [Fact]
+ public void AddFeedWritesSourceIfItDoesNotExist()
+ {
+ // Arrange
+ bool writeCalled = false;
+ var sourceFile = GetPackagesSourceFile();
+ sourceFile.Setup(c => c.WriteSources(It.IsAny<IEnumerable<WebPackageSource>>())).Callback(() => writeCalled = true);
+ ISet<WebPackageSource> set = new HashSet<WebPackageSource>(GetSources());
+
+ // Act
+ bool returnValue = PackageManagerModule.AddPackageSource(sourceFile.Object, set, new WebPackageSource(source: "http://www.microsoft.com/feed3", name: "Feed3"));
+
+ // Assert
+ Assert.Equal(set.Count(), 3);
+ Assert.True(writeCalled);
+ Assert.True(returnValue);
+ }
+
+ [Fact]
+ public void AddFeedDoesNotWritesSourceIfExists()
+ {
+ // Arrange
+ bool writeCalled = false;
+ var sourceFile = GetPackagesSourceFile();
+ sourceFile.Setup(c => c.WriteSources(It.IsAny<IEnumerable<WebPackageSource>>())).Callback(() => writeCalled = true);
+ ISet<WebPackageSource> set = new HashSet<WebPackageSource>(GetSources());
+
+ // Act
+ bool returnValue = PackageManagerModule.AddPackageSource(sourceFile.Object, set, new WebPackageSource(source: "http://www.microsoft.com/feed1", name: "Feed1"));
+
+ // Assert
+ Assert.Equal(set.Count(), 2);
+ Assert.False(writeCalled);
+ Assert.False(returnValue);
+ }
+
+ [Fact]
+ public void RemoveFeedRemovesSourceFromSet()
+ {
+ // Arrange
+ bool writeCalled = false;
+ var sourceFile = GetPackagesSourceFile();
+ sourceFile.Setup(c => c.WriteSources(It.IsAny<IEnumerable<WebPackageSource>>())).Callback(() => writeCalled = true);
+ ISet<WebPackageSource> set = new HashSet<WebPackageSource>(GetSources());
+
+ // Act
+ PackageManagerModule.RemovePackageSource(sourceFile.Object, set, "feed1");
+
+ // Assert
+ Assert.Equal(set.Count(), 1);
+ Assert.False(set.Any(s => s.Name == "Feed1"));
+ Assert.True(writeCalled);
+ }
+
+ [Fact]
+ public void RemoveFeedDoesNotAffectSourceFileIsFeedDoesNotExist()
+ {
+ // Arrange
+ bool writeCalled = false;
+ var sourceFile = GetPackagesSourceFile();
+ sourceFile.Setup(c => c.WriteSources(It.IsAny<IEnumerable<WebPackageSource>>())).Callback(() => writeCalled = true);
+ ISet<WebPackageSource> set = new HashSet<WebPackageSource>(GetSources());
+
+ // Act
+ PackageManagerModule.RemovePackageSource(sourceFile.Object, set, "feed3");
+
+ // Assert
+ Assert.Equal(set.Count(), 2);
+ Assert.False(writeCalled);
+ }
+
+ private static Mock<IPackagesSourceFile> GetPackagesSourceFile()
+ {
+ var sourceFile = new Mock<IPackagesSourceFile>();
+ sourceFile.Setup(c => c.ReadSources()).Returns(GetSources());
+ return sourceFile;
+ }
+
+ private static IEnumerable<WebPackageSource> GetSources()
+ {
+ return new[]
+ {
+ new WebPackageSource(name: "Feed1", source: "http://www.microsoft.com/feed1"),
+ new WebPackageSource(name: "Feed2", source: "http://www.microsoft.com/feed2")
+ };
+ }
+ }
+}
diff --git a/test/System.Web.WebPages.Administration.Test/PackagesSourceFileTest.cs b/test/System.Web.WebPages.Administration.Test/PackagesSourceFileTest.cs
new file mode 100644
index 00000000..25156769
--- /dev/null
+++ b/test/System.Web.WebPages.Administration.Test/PackagesSourceFileTest.cs
@@ -0,0 +1,146 @@
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Web.WebPages.Administration.PackageManager;
+using System.Web.WebPages.TestUtils;
+using System.Xml.Linq;
+using Xunit;
+
+namespace System.Web.WebPages.Administration.Test
+{
+ public class PackagesSourceFileTest
+ {
+ [Fact]
+ public void PackagesSourceFileThrowsIfTheXmlElementDoesNotContainNameAndUrl()
+ {
+ // Arrange
+ var element = new XElement("source");
+
+ // Act and Assert
+ Assert.Throws<FormatException>(() => PackageSourceFile.ParsePackageSource(element));
+ }
+
+ [Fact]
+ public void PackagesSourceFileThrowsIfTheXmlElementDoesNotContainUrl()
+ {
+ // Arrange
+ var element = new XElement("source", new XAttribute("displayname", "foo"), new XAttribute("filterpreferred", false));
+
+ // Act and Assert
+ Assert.Throws<FormatException>(() => PackageSourceFile.ParsePackageSource(element));
+ }
+
+ [Fact]
+ public void PackagesSourceFileThrowsIfTheXmlElementDoesNotContainName()
+ {
+ // Arrange
+ var element = new XElement("source", new XAttribute("url", "http://microsoft.com"), new XAttribute("filterpreferred", false));
+
+ // Act and Assert
+ Assert.Throws<FormatException>(() => PackageSourceFile.ParsePackageSource(element));
+ }
+
+ [Fact]
+ public void PackagesSourceFileDoesNotThrowIfXmlElementDoesNotContainPreferred()
+ {
+ // Arrange
+ var element = new XElement("source", new XAttribute("displayname", "foo"), new XAttribute("url", "http://microsoft.com"));
+
+ // Act
+ var item = PackageSourceFile.ParsePackageSource(element);
+
+ // Assert
+ Assert.NotNull(item);
+ }
+
+ [Fact]
+ public void PackagesSourceFileThrowsIfTheFeedUrlIsMalformed()
+ {
+ // Arrange
+ var element = new XElement("source",
+ new XAttribute("displayname", "foo"),
+ new XAttribute("url", "bad-url.com"),
+ new XAttribute("filterpreferred", false)
+ );
+
+ // Act and Assert
+ Assert.Throws<FormatException>(() => PackageSourceFile.ParsePackageSource(element));
+ }
+
+ [Fact]
+ public void PackagesSourceFileParsesXElement()
+ {
+ // Arrange
+ var element = new XElement("source",
+ new XAttribute("displayname", "foo"),
+ new XAttribute("url", "http://www.microsoft.com"),
+ new XAttribute("filterpreferred", true)
+ );
+
+ // Act
+ var WebPackageSource = PackageSourceFile.ParsePackageSource(element);
+
+ // Assert
+ Assert.Equal("foo", WebPackageSource.Name);
+ Assert.Equal("http://www.microsoft.com", WebPackageSource.Source);
+ Assert.True(WebPackageSource.FilterPreferredPackages);
+ }
+
+ [Fact]
+ public void PackagesSourceFileReadsAllFeedsFromStream()
+ {
+ // Arrange
+ var document = new XDocument(
+ new XElement("sources",
+ new XElement("source", new XAttribute("displayname", "Feed1"), new XAttribute("url", "http://www.microsoft.com/feed1"), new XAttribute("filterpreferred", true)),
+ new XElement("source", new XAttribute("displayname", "Feed2"), new XAttribute("url", "http://www.microsoft.com/feed2"), new XAttribute("filterpreferred", true))
+ ));
+ var stream = new MemoryStream();
+ document.Save(stream);
+ stream = new MemoryStream(stream.ToArray());
+ string xml = new StreamReader(stream).ReadToEnd().TrimEnd('\0');
+
+ // Act
+ var result = PackageSourceFile.ReadFeeds(() => new MemoryStream(Encoding.Default.GetBytes(xml)));
+
+ // Assert
+ Assert.Equal(2, result.Count());
+ Assert.Equal("Feed1", result.First().Name);
+ Assert.Equal("Feed2", result.Last().Name);
+ }
+
+ [Fact]
+ public void PackagesSourceFileWritesAllFeedsToStream()
+ {
+ // Arrange
+ var packagesSources = new[]
+ {
+ new WebPackageSource(name: "Feed1", source: "http://www.microsoft.com/Feed1"),
+ new WebPackageSource(name: "Feed2", source: "http://www.microsoft.com/Feed2") { FilterPreferredPackages = true }
+ };
+ var stream = new MemoryStream();
+
+ // Act
+ PackageSourceFile.WriteFeeds(packagesSources, () => stream);
+ stream = new MemoryStream(stream.ToArray());
+ string result = new StreamReader(stream).ReadToEnd().TrimEnd('\0');
+
+ // Assert
+ var document = XDocument.Parse(result);
+ Assert.Equal(document.Root.Name, "sources");
+ Assert.Equal(document.Root.Elements().Count(), 2);
+
+ var firstFeed = document.Root.Elements().First();
+ Assert.Equal(firstFeed.Name, "source");
+ Assert.Equal(firstFeed.Attribute("displayname").Value, "Feed1");
+ Assert.Equal(firstFeed.Attribute("url").Value, "http://www.microsoft.com/Feed1");
+ Assert.Equal(firstFeed.Attribute("filterpreferred").Value, "false");
+
+ var secondFeed = document.Root.Elements().Last();
+ Assert.Equal(secondFeed.Name, "source");
+ Assert.Equal(secondFeed.Attribute("displayname").Value, "Feed2");
+ Assert.Equal(secondFeed.Attribute("url").Value, "http://www.microsoft.com/Feed2");
+ Assert.Equal(secondFeed.Attribute("filterpreferred").Value, "true");
+ }
+ }
+}
diff --git a/test/System.Web.WebPages.Administration.Test/PageUtilsTest.cs b/test/System.Web.WebPages.Administration.Test/PageUtilsTest.cs
new file mode 100644
index 00000000..72370d17
--- /dev/null
+++ b/test/System.Web.WebPages.Administration.Test/PageUtilsTest.cs
@@ -0,0 +1,153 @@
+using System.Collections.Generic;
+using System.Collections.Specialized;
+using System.Web.WebPages.Administration.PackageManager;
+using Moq;
+using Xunit;
+
+namespace System.Web.WebPages.Administration.Test
+{
+ public class PageUtilsTest
+ {
+ [Fact]
+ public void GetFilterValueReturnsNullIfValueWasNotFound()
+ {
+ // Arrange
+ var request = new Mock<HttpRequestBase>();
+ request.Setup(c => c.QueryString).Returns(new NameValueCollection());
+ request.Setup(c => c.Cookies).Returns(new HttpCookieCollection());
+
+ // Act
+ var value = PageUtils.GetFilterValue(request.Object, "foo", "my-key");
+
+ // Assert
+ Assert.Null(value);
+ }
+
+ [Fact]
+ public void GetFilterValueReturnsValueFromCookieIfQueryStringDoesNotContainKey()
+ {
+ // Arrange
+ const string key = "my-key";
+ const string value = "my-cookie-value";
+ var request = new Mock<HttpRequestBase>();
+ request.Setup(c => c.QueryString).Returns(new NameValueCollection());
+ var cookies = new HttpCookieCollection();
+ var cookie = new HttpCookie("foo");
+ cookie[key] = value;
+ cookies.Add(cookie);
+ request.Setup(c => c.Cookies).Returns(cookies);
+
+ // Act
+ var returnedValue = PageUtils.GetFilterValue(request.Object, "foo", key);
+
+ // Assert
+ Assert.Equal(value, returnedValue);
+ }
+
+ [Fact]
+ public void GetFilterValueReturnsValueFromQueryString()
+ {
+ // Arrange
+ const string key = "my-key";
+ const string requestValue = "my-request-value";
+ const string cookieValue = "my-cookie-value";
+ var request = new Mock<HttpRequestBase>();
+ var queryString = new NameValueCollection();
+ queryString[key] = requestValue;
+ request.Setup(c => c.QueryString).Returns(queryString);
+ var cookies = new HttpCookieCollection();
+ var cookie = new HttpCookie("foo");
+ cookie[key] = cookieValue;
+ request.Setup(c => c.Cookies).Returns(cookies);
+
+ // Act
+ var returnedValue = PageUtils.GetFilterValue(request.Object, "foo", key);
+
+ // Assert
+ Assert.Equal(requestValue, returnedValue);
+ }
+
+ [Fact]
+ public void PersistFilterCreatesCookieIfItDoesNotExist()
+ {
+ // Arrange
+ var cookies = new HttpCookieCollection();
+ var response = new Mock<HttpResponseBase>();
+ response.Setup(c => c.Cookies).Returns(cookies);
+
+ // Act
+ PageUtils.PersistFilter(response.Object, "my-cookie", new Dictionary<string, string>());
+
+ // Assert
+ Assert.NotNull(cookies["my-cookie"]);
+ }
+
+ [Fact]
+ public void PersistFilterUsesExistingCookie()
+ {
+ // Arrange
+ var cookieName = "my-cookie";
+ var cookies = new HttpCookieCollection();
+ cookies.Add(new HttpCookie(cookieName));
+ var response = new Mock<HttpResponseBase>();
+ response.Setup(c => c.Cookies).Returns(cookies);
+
+ // Act
+ PageUtils.PersistFilter(response.Object, "my-cookie", new Dictionary<string, string>());
+
+ // Assert
+ Assert.Equal(1, cookies.Count);
+ }
+
+ [Fact]
+ public void PersistFilterAddsDictionaryEntriesToCookie()
+ {
+ // Arrange
+ var cookies = new HttpCookieCollection();
+ var response = new Mock<HttpResponseBase>();
+ response.Setup(c => c.Cookies).Returns(cookies);
+
+ // Act
+ PageUtils.PersistFilter(response.Object, "my-cookie", new Dictionary<string, string>() { { "a", "b" }, { "x", "y" } });
+
+ // Assert
+ var cookie = cookies["my-cookie"];
+ Assert.Equal(cookie["a"], "b");
+ Assert.Equal(cookie["x"], "y");
+ }
+
+ [Fact]
+ public void IsValidLicenseUrlReturnsTrueForHttpUris()
+ {
+ // Arrange
+ var uri = new Uri("http://www.microsoft.com");
+
+ // Act and Assert
+ Assert.True(PageUtils.IsValidLicenseUrl(uri));
+ }
+
+ [Fact]
+ public void IsValidLicenseUrlReturnsTrueForHttpsUris()
+ {
+ // Arrange
+ var uri = new Uri("HTTPs://www.asp.net");
+
+ // Act and Assert
+ Assert.True(PageUtils.IsValidLicenseUrl(uri));
+ }
+
+ [Fact]
+ public void IsValidLicenseUrlReturnsFalseForNonHttpUris()
+ {
+ // Arrange
+ var jsUri = new Uri("javascript:alert('Hello world');");
+ var fileShareUri = new Uri(@"c:\windows\system32\notepad.exe");
+ var mailToUti = new Uri("mailto:invalid-email@microsoft.com");
+
+ // Act and Assert
+ Assert.False(PageUtils.IsValidLicenseUrl(jsUri));
+ Assert.False(PageUtils.IsValidLicenseUrl(fileShareUri));
+ Assert.False(PageUtils.IsValidLicenseUrl(mailToUti));
+ }
+ }
+}
diff --git a/test/System.Web.WebPages.Administration.Test/PreApplicationStartCodeTest.cs b/test/System.Web.WebPages.Administration.Test/PreApplicationStartCodeTest.cs
new file mode 100644
index 00000000..de9e5e74
--- /dev/null
+++ b/test/System.Web.WebPages.Administration.Test/PreApplicationStartCodeTest.cs
@@ -0,0 +1,33 @@
+using System.Linq;
+using System.Web.WebPages.TestUtils;
+using Xunit;
+
+namespace System.Web.WebPages.Administration.Test
+{
+ public class PreApplicationStartCodeTest
+ {
+ [Fact]
+ public void StartTest()
+ {
+ AppDomainUtils.RunInSeparateAppDomain(() =>
+ {
+ var adminPackageAssembly = typeof(PreApplicationStartCode).Assembly;
+ AppDomainUtils.SetPreAppStartStage();
+ PreApplicationStartCode.Start();
+ // Call a second time to ensure multiple calls do not cause issues
+ PreApplicationStartCode.Start();
+
+ // TODO: Need a way to see if the module was actually registered
+ var registeredAssemblies = ApplicationPart.GetRegisteredParts().ToList();
+ Assert.Equal(1, registeredAssemblies.Count);
+ registeredAssemblies.First().Assembly.Equals(adminPackageAssembly);
+ });
+ }
+
+ [Fact]
+ public void TestPreAppStartClass()
+ {
+ PreAppStartTestHelper.TestPreAppStartClass(typeof(PreApplicationStartCode));
+ }
+ }
+}
diff --git a/test/System.Web.WebPages.Administration.Test/RemoteAssemblyTest.cs b/test/System.Web.WebPages.Administration.Test/RemoteAssemblyTest.cs
new file mode 100644
index 00000000..fea9ddd1
--- /dev/null
+++ b/test/System.Web.WebPages.Administration.Test/RemoteAssemblyTest.cs
@@ -0,0 +1,147 @@
+using System.Linq;
+using System.Web.WebPages.Administration.PackageManager;
+using Moq;
+using NuGet.Runtime;
+using Xunit;
+
+namespace System.Web.WebPages.Administration.Test
+{
+ public class RemoteAssemblyTest
+ {
+ [Fact]
+ public void GetAssembliesForBindingRedirectReturnsEmptySequenceIfNoBinAssembliesAreFound()
+ {
+ // Act
+ var assemblies = RemoteAssembly.GetAssembliesForBindingRedirect(AppDomain.CurrentDomain, @"x:\site\bin", (_, __) => Enumerable.Empty<IAssembly>());
+
+ // Assert
+ Assert.Empty(assemblies);
+ }
+
+ [Fact]
+ public void RemoteAssemblyComparesById()
+ {
+ // Arrange
+ var assemblyA = new RemoteAssembly("A", null, null, null);
+ var assemblyB = new Mock<IAssembly>(MockBehavior.Strict);
+ assemblyB.SetupGet(b => b.Name).Returns("Z").Verifiable();
+
+ // Act
+ var result = RemoteAssembly.Compare(assemblyA, assemblyB.Object);
+
+ // Assert
+ Assert.Equal(-25, result);
+ assemblyB.Verify();
+ }
+
+ [Fact]
+ public void RemoteAssemblyComparesByVersionIfIdsAreIdentical()
+ {
+ // Arrange
+ var assemblyA = new RemoteAssembly("A", new Version("2.0.0.0"), null, null);
+ var assemblyB = new Mock<IAssembly>(MockBehavior.Strict);
+ assemblyB.SetupGet(b => b.Name).Returns("A").Verifiable();
+ assemblyB.SetupGet(b => b.Version).Returns(new Version("1.0.0.0")).Verifiable();
+
+ // Act
+ var result = RemoteAssembly.Compare(assemblyA, assemblyB.Object);
+
+ // Assert
+ Assert.Equal(1, result);
+ assemblyB.Verify();
+ }
+
+ [Fact]
+ public void RemoteAssemblyComparesByPublicKeyIfIdsAndVersionAreIdentical()
+ {
+ // Arrange
+ var assemblyA = new RemoteAssembly("A", new Version("1.0.0.0"), "C", null);
+ var assemblyB = new Mock<IAssembly>(MockBehavior.Strict);
+ assemblyB.SetupGet(b => b.Name).Returns("A").Verifiable();
+ assemblyB.SetupGet(b => b.Version).Returns(new Version("1.0.0.0")).Verifiable();
+ assemblyB.SetupGet(b => b.PublicKeyToken).Returns("E").Verifiable();
+
+ // Act
+ var result = RemoteAssembly.Compare(assemblyA, assemblyB.Object);
+
+ // Assert
+ Assert.Equal(-2, result);
+ assemblyB.Verify();
+ }
+
+ [Fact]
+ public void RemoteAssemblyComparesByCultureIfIdVersionAndPublicKeyAreIdentical()
+ {
+ // Arrange
+ var assemblyA = new RemoteAssembly("A", new Version("1.0.0.0"), "public-key", "en-us");
+ var assemblyB = new Mock<IAssembly>(MockBehavior.Strict);
+ assemblyB.SetupGet(b => b.Name).Returns("A").Verifiable();
+ assemblyB.SetupGet(b => b.Version).Returns(new Version("1.0.0.0")).Verifiable();
+ assemblyB.SetupGet(b => b.PublicKeyToken).Returns("public-key").Verifiable();
+ assemblyB.SetupGet(b => b.Culture).Returns("en-uk").Verifiable();
+
+ // Act
+ var result = RemoteAssembly.Compare(assemblyA, assemblyB.Object);
+
+ // Assert
+ Assert.Equal(8, result);
+ assemblyB.Verify();
+ }
+
+ [Fact]
+ public void RemoteAssemblyReturns0IfAllValuesAreIdentical()
+ {
+ // Arrange
+ var assemblyA = new RemoteAssembly("A", new Version("1.0.0.0"), "public-key", "en-us");
+ var assemblyB = new RemoteAssembly("A", new Version("1.0.0.0"), "public-key", "en-us");
+
+ // Act
+ var result = RemoteAssembly.Compare(assemblyA, assemblyB);
+
+ // Assert
+ Assert.Equal(0, result);
+ }
+
+ [Fact]
+ public void RemoteAssemblyReturns1IfValueToBeComparedToIsNull()
+ {
+ // Arrange
+ RemoteAssembly assemblyA = new RemoteAssembly("A", new Version("1.0.0.0"), "public-key", "en-us");
+ RemoteAssembly assemblyB = null;
+
+ // Act
+ var result = assemblyA.CompareTo(assemblyB);
+
+ // Assert
+ Assert.Equal(1, result);
+ }
+
+ [Fact]
+ public void EqualReturnsTrueIfValuesAreIdentical()
+ {
+ // Arrange
+ var assemblyA = new RemoteAssembly("A", new Version("1.0.0.0"), "public-key", "en-us");
+ var assemblyB = new RemoteAssembly("A", new Version("1.0.0.0"), "public-key", "en-us");
+
+ // Act
+ var result = assemblyA.Equals(assemblyB);
+
+ // Assert
+ Assert.True(result);
+ }
+
+ [Fact]
+ public void EqualReturnsFalseIfValuesAreNotIdentical()
+ {
+ // Arrange
+ var assemblyA = new RemoteAssembly("A", new Version("1.0.0.0"), "public-key", "en-us");
+ var assemblyB = new RemoteAssembly("A", new Version("1.0.0.1"), "public-key", "en-us");
+
+ // Act
+ var result = assemblyA.Equals(assemblyB);
+
+ // Assert
+ Assert.False(result);
+ }
+ }
+}
diff --git a/test/System.Web.WebPages.Administration.Test/System.Web.WebPages.Administration.Test.csproj b/test/System.Web.WebPages.Administration.Test/System.Web.WebPages.Administration.Test.csproj
new file mode 100644
index 00000000..5c4ba83e
--- /dev/null
+++ b/test/System.Web.WebPages.Administration.Test/System.Web.WebPages.Administration.Test.csproj
@@ -0,0 +1,102 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory),Runtime.sln))\tools\WebStack.settings.targets" />
+ <PropertyGroup>
+ <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+ <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+ <ProductVersion>9.0.30729</ProductVersion>
+ <SchemaVersion>2.0</SchemaVersion>
+ <ProjectGuid>{21C729D6-ECF8-47EF-A236-7C6A4272EAF0}</ProjectGuid>
+ <OutputType>Library</OutputType>
+ <AppDesignerFolder>Properties</AppDesignerFolder>
+ <RootNamespace>System.Web.WebPages.Administration.Test</RootNamespace>
+ <AssemblyName>System.Web.WebPages.Administration.Test</AssemblyName>
+ <FileAlignment>512</FileAlignment>
+ <ProjectTypeGuids>{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
+ <ErrorReport>prompt</ErrorReport>
+ <WarningLevel>4</WarningLevel>
+ <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
+ <DebugSymbols>true</DebugSymbols>
+ <DebugType>full</DebugType>
+ <Optimize>false</Optimize>
+ <OutputPath>$(WebStackRootPath)\bin\Debug\Test\</OutputPath>
+ <DefineConstants>TRACE;DEBUG</DefineConstants>
+ <CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
+ <DebugType>pdbonly</DebugType>
+ <Optimize>true</Optimize>
+ <OutputPath>$(WebStackRootPath)\bin\Release\Test\</OutputPath>
+ <DefineConstants>TRACE</DefineConstants>
+ <CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)' == 'CodeCoverage' ">
+ <DebugSymbols>true</DebugSymbols>
+ <DebugType>full</DebugType>
+ <Optimize>false</Optimize>
+ <OutputPath>$(WebStackRootPath)\bin\CodeCoverage\Test\</OutputPath>
+ <DefineConstants>TRACE;DEBUG</DefineConstants>
+ <CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
+ </PropertyGroup>
+ <ItemGroup>
+ <Reference Include="Microsoft.CSharp" />
+ <Reference Include="Moq, Version=4.0.10827.0, Culture=neutral, PublicKeyToken=69f491c39445e920, processorArchitecture=MSIL">
+ <HintPath>..\..\packages\Moq.4.0.10827\lib\NET40\Moq.dll</HintPath>
+ </Reference>
+ <Reference Include="NuGet.Core">
+ <HintPath>..\..\packages\Nuget.Core.1.6.2\lib\net40\NuGet.Core.dll</HintPath>
+ </Reference>
+ <Reference Include="System" />
+ <Reference Include="System.Core">
+ <RequiredTargetFramework>3.5</RequiredTargetFramework>
+ </Reference>
+ <Reference Include="System.Web" />
+ <Reference Include="System.XML" />
+ <Reference Include="System.Xml.Linq" />
+ <Reference Include="xunit">
+ <HintPath>..\..\packages\xunit.1.9.0.1566\lib\xunit.dll</HintPath>
+ </Reference>
+ <Reference Include="xunit.extensions">
+ <HintPath>..\..\packages\xunit.extensions.1.9.0.1566\lib\xunit.extensions.dll</HintPath>
+ </Reference>
+ </ItemGroup>
+ <ItemGroup>
+ <CodeAnalysisDependentAssemblyPaths Condition=" '$(VS100COMNTOOLS)' != '' " Include="$(VS100COMNTOOLS)..\IDE\PrivateAssemblies">
+ <Visible>False</Visible>
+ </CodeAnalysisDependentAssemblyPaths>
+ </ItemGroup>
+ <ItemGroup>
+ <ProjectReference Include="..\..\src\System.Web.WebPages.Administration\System.Web.WebPages.Administration.csproj">
+ <Project>{C23F02FC-4538-43F5-ABBA-38BA069AEA8F}</Project>
+ <Name>System.Web.WebPages.Administration</Name>
+ </ProjectReference>
+ <ProjectReference Include="..\..\src\System.Web.Helpers\System.Web.Helpers.csproj">
+ <Project>{9B7E3740-6161-4548-833C-4BBCA43B970E}</Project>
+ <Name>System.Web.Helpers</Name>
+ </ProjectReference>
+ <ProjectReference Include="..\..\src\System.Web.WebPages\System.Web.WebPages.csproj">
+ <Project>{76EFA9C5-8D7E-4FDF-B710-E20F8B6B00D2}</Project>
+ <Name>System.Web.WebPages</Name>
+ </ProjectReference>
+ <ProjectReference Include="..\Microsoft.TestCommon\Microsoft.TestCommon.csproj">
+ <Project>{FCCC4CB7-BAF7-4A57-9F89-E5766FE536C0}</Project>
+ <Name>Microsoft.TestCommon</Name>
+ </ProjectReference>
+ </ItemGroup>
+ <ItemGroup>
+ <Compile Include="AdminPackageTest.cs" />
+ <Compile Include="PackageManagerModuleTest.cs" />
+ <Compile Include="PackagesSourceFileTest.cs" />
+ <Compile Include="PageUtilsTest.cs" />
+ <Compile Include="PreApplicationStartCodeTest.cs" />
+ <Compile Include="RemoteAssemblyTest.cs" />
+ <Compile Include="WebProjectManagerTest.cs" />
+ <Compile Include="WebProjectSystemTest.cs" />
+ </ItemGroup>
+ <ItemGroup>
+ <None Include="packages.config" />
+ </ItemGroup>
+ <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
+</Project> \ No newline at end of file
diff --git a/test/System.Web.WebPages.Administration.Test/WebProjectManagerTest.cs b/test/System.Web.WebPages.Administration.Test/WebProjectManagerTest.cs
new file mode 100644
index 00000000..015da442
--- /dev/null
+++ b/test/System.Web.WebPages.Administration.Test/WebProjectManagerTest.cs
@@ -0,0 +1,160 @@
+using System.Collections.Generic;
+using System.Linq;
+using System.Web.WebPages.Administration.PackageManager;
+using Moq;
+using NuGet;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.WebPages.Administration.Test
+{
+ public class WebPackageManagerTest
+ {
+ [Fact]
+ public void ConstructorThrowsIfRemoteSourceIsNullOrEmpty()
+ {
+ // Act and Assert
+ Assert.ThrowsArgumentNullOrEmptyString(() => new WebProjectManager((string)null, "foo"), "remoteSource");
+ Assert.ThrowsArgumentNullOrEmptyString(() => new WebProjectManager("", @"D:\baz"), "remoteSource");
+ }
+
+ [Fact]
+ public void ConstructorThrowsIfSiteRootIsNullOrEmpty()
+ {
+ // Act and Assert
+ Assert.ThrowsArgumentNullOrEmptyString(() => new WebProjectManager("foo", null), "siteRoot");
+ Assert.ThrowsArgumentNullOrEmptyString(() => new WebProjectManager("foo", ""), "siteRoot");
+ }
+
+ [Fact]
+ public void AllowInstallingPackageWithToolsFolderDoNotThrow()
+ {
+ // Arrange
+ var projectManager = new Mock<IProjectManager>();
+ projectManager.Setup(p => p.AddPackageReference("A", new SemanticVersion("1.0"), false, false)).Verifiable();
+
+ var webProjectManager = new WebProjectManager(projectManager.Object, @"x:\")
+ {
+ DoNotAddBindingRedirects = true
+ };
+
+ var packageFile1 = new Mock<IPackageFile>();
+ packageFile1.Setup(p => p.Path).Returns("tools\\install.ps1");
+
+ var packageFile2 = new Mock<IPackageFile>();
+ packageFile2.Setup(p => p.Path).Returns("content\\A.txt");
+
+ var package = new Mock<IPackage>();
+ package.Setup(p => p.Id).Returns("A");
+ package.Setup(p => p.Version).Returns(new SemanticVersion("1.0"));
+ package.Setup(p => p.GetFiles()).Returns(new[] { packageFile1.Object, packageFile2.Object });
+
+ // Act
+ webProjectManager.InstallPackage(package.Object, appDomain: null);
+
+ // Assert
+ projectManager.Verify();
+ }
+
+ [Fact]
+ public void GetLocalRepositoryReturnsPackagesFolderUnderAppData()
+ {
+ // Arrange
+ var siteRoot = "my-site";
+
+ // Act
+ var repositoryFolder = WebProjectManager.GetWebRepositoryDirectory(siteRoot);
+
+ Assert.Equal(repositoryFolder, @"my-site\App_Data\packages");
+ }
+
+ [Fact]
+ public void GetPackagesReturnsAllItemsWhenNoSearchTermIsIncluded()
+ {
+ // Arrange
+ var repository = GetRepository();
+
+ // Act
+ var result = WebProjectManager.GetPackages(repository, String.Empty);
+
+ // Assert
+ Assert.Equal(3, result.Count());
+ }
+
+ [Fact]
+ public void GetPackagesReturnsItemsContainingSomeSearchToken()
+ {
+ // Arrange
+ var repository = GetRepository();
+
+ // Act
+ var result = WebProjectManager.GetPackages(repository, "testing .NET");
+ var package = result.SingleOrDefault();
+
+ // Assert
+ Assert.NotNull(package);
+ Assert.Equal(package.Id, "A");
+ }
+
+ [Fact]
+ public void GetPackagesWithLicenseReturnsAllDependenciesWithRequiresAcceptance()
+ {
+ // Arrange
+ var remoteRepository = GetRepository();
+ var localRepository = new Mock<IPackageRepository>().Object;
+
+ // Act
+ var package = remoteRepository.GetPackages().Find("C").SingleOrDefault();
+ var result = WebProjectManager.GetPackagesRequiringLicenseAcceptance(package, localRepository, remoteRepository);
+
+ // Assert
+ Assert.Equal(2, result.Count());
+ Assert.True(result.Any(c => c.Id == "C"));
+ Assert.True(result.Any(c => c.Id == "B"));
+ }
+
+ [Fact]
+ public void GetPackagesWithLicenseReturnsEmptyResultForPackageThatDoesNotRequireLicenses()
+ {
+ // Arrange
+ var remoteRepository = GetRepository();
+ var localRepository = new Mock<IPackageRepository>().Object;
+
+ // Act
+ var package = remoteRepository.GetPackages().Find("A").SingleOrDefault();
+ var result = WebProjectManager.GetPackagesRequiringLicenseAcceptance(package, localRepository, remoteRepository);
+
+ // Assert
+ Assert.False(result.Any());
+ }
+
+ private static IPackageRepository GetRepository()
+ {
+ Mock<IPackageRepository> repository = new Mock<IPackageRepository>();
+ var packages = new[]
+ {
+ GetPackage("A", desc: "testing"),
+ GetPackage("B", version: "1.1", requiresLicense: true),
+ GetPackage("C", requiresLicense: true, dependencies: new[]
+ {
+ new PackageDependency("B", new VersionSpec { MinVersion = new SemanticVersion("1.0") })
+ })
+ };
+ repository.Setup(c => c.GetPackages()).Returns(packages.AsQueryable());
+
+ return repository.Object;
+ }
+
+ private static IPackage GetPackage(string id, string version = "1.0", string desc = null, bool requiresLicense = false, IEnumerable<PackageDependency> dependencies = null)
+ {
+ Mock<IPackage> package = new Mock<IPackage>();
+ package.SetupGet(c => c.Id).Returns(id);
+ package.SetupGet(c => c.Version).Returns(SemanticVersion.Parse(version));
+ package.SetupGet(c => c.Description).Returns(desc ?? id);
+ package.SetupGet(c => c.RequireLicenseAcceptance).Returns(requiresLicense);
+ package.SetupGet(c => c.LicenseUrl).Returns(new Uri("http://www." + id + ".com"));
+ package.SetupGet(c => c.Dependencies).Returns(dependencies ?? Enumerable.Empty<PackageDependency>());
+ return package.Object;
+ }
+ }
+}
diff --git a/test/System.Web.WebPages.Administration.Test/WebProjectSystemTest.cs b/test/System.Web.WebPages.Administration.Test/WebProjectSystemTest.cs
new file mode 100644
index 00000000..8cf94a77
--- /dev/null
+++ b/test/System.Web.WebPages.Administration.Test/WebProjectSystemTest.cs
@@ -0,0 +1,253 @@
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Web.WebPages.Administration.PackageManager;
+using System.Xml.Linq;
+using Moq;
+using NuGet;
+using Xunit;
+
+namespace System.Web.WebPages.Administration.Test
+{
+ public class WebProjectSystemTest
+ {
+ [Fact]
+ public void ResolvePathReturnsAppCodePathIfPathIsSourceFile()
+ {
+ // Arrange
+ var path = "Foo.cs";
+ var webProjectSystem = new WebProjectSystem(@"x:\");
+
+ // Act
+ var resolvedPath = webProjectSystem.ResolvePath(path);
+
+ // Assert
+ Assert.Equal(@"App_Code\Foo.cs", resolvedPath);
+ }
+
+ [Fact]
+ public void ResolvePathReturnsOriginalPathIfSourceFilePathIsAlreadyUnderAppCode()
+ {
+ // Arrange
+ var path = @"App_Code\Foo.cs";
+ var webProjectSystem = new WebProjectSystem(@"x:\");
+
+ // Act
+ var resolvedPath = webProjectSystem.ResolvePath(path);
+
+ // Assert
+ Assert.Equal(path, resolvedPath);
+ }
+
+ [Fact]
+ public void ResolvePathReturnsOriginalPathIfFileIsNotSource()
+ {
+ // Arrange
+ var path = @"Foo.js";
+ var webProjectSystem = new WebProjectSystem(@"x:\");
+
+ // Act
+ var resolvedPath = webProjectSystem.ResolvePath(path);
+
+ // Assert
+ Assert.Equal(path, resolvedPath);
+ }
+
+ [Fact]
+ public void AddPackageWithFrameworkReferenceCreatesWebConfigIfItDoesNotExist()
+ {
+ // Arrange
+ string webConfigPath = @"x:\my-website\web.config";
+ MemoryStream memoryStream = new MemoryStream();
+
+ var fileSystem = new Mock<IFileSystem>();
+ fileSystem.SetupGet(f => f.Root).Returns("x:\\my-website");
+ fileSystem.Setup(f => f.FileExists(It.Is<string>(p => p.Equals(webConfigPath)))).Returns(false).Verifiable();
+ fileSystem.Setup(f => f.AddFile(It.Is<string>(p => p.Equals(webConfigPath)), It.IsAny<Stream>()))
+ .Callback<string, Stream>((_, s) => { s.CopyTo(memoryStream); });
+
+ var references = "System";
+
+ // Act
+ WebProjectSystem.AddReferencesToConfig(fileSystem.Object, references);
+
+ // Assert
+ memoryStream.Seek(0, SeekOrigin.Begin);
+ XDocument document = XDocument.Load(memoryStream);
+
+ var element = document.Root;
+ Assert.Equal(element.Name, "configuration");
+
+ // Use SingleOrDefault to ensure there's exactly one element with that name
+ var assemblies = document.Root
+ .Elements().SingleOrDefault(e => e.Name.ToString().Equals("system.web"))
+ .Elements().SingleOrDefault(e => e.Name.ToString().Equals("compilation"))
+ .Elements().SingleOrDefault(e => e.Name.ToString().Equals("assemblies"));
+
+ Assert.Equal(references, assemblies.Elements().First().Attribute("assembly").Value);
+ }
+
+ [Fact]
+ public void AddPackageWithFrameworkReferenceCreatesWebConfigIfItExistsWithoutAssembliesNode()
+ {
+ // Arrange
+ var webConfigPath = @"x:\my-website\web.config";
+ var webConfigContent = @"<?xml version=""1.0""?>
+ <configuration>
+ <connectionStrings>
+ <add name=""test"" />
+ </connectionStrings>
+ <system.web>
+ <profiles><add name=""awesomeprofile"" /></profiles>
+ </system.web>
+ </configuration>
+
+".AsStream();
+ MemoryStream memoryStream = new MemoryStream();
+
+ var fileSystem = new Mock<IFileSystem>();
+ fileSystem.SetupGet(f => f.Root).Returns("x:\\my-website");
+ fileSystem.Setup(f => f.FileExists(It.Is<string>(p => p.Equals(webConfigPath)))).Returns(true).Verifiable();
+ fileSystem.Setup(f => f.OpenFile(It.Is<string>(p => p.Equals(webConfigPath)))).Returns(webConfigContent);
+ fileSystem.Setup(f => f.AddFile(It.Is<string>(p => p.Equals(webConfigPath)), It.IsAny<Stream>()))
+ .Callback<string, Stream>((_, s) => { s.CopyTo(memoryStream); });
+
+ var references = "System.Data";
+
+ // Act
+ WebProjectSystem.AddReferencesToConfig(fileSystem.Object, references);
+
+ // Assert
+ memoryStream.Seek(0, SeekOrigin.Begin);
+ XDocument document = XDocument.Load(memoryStream);
+
+ var element = document.Root;
+ Assert.Equal(element.Name, "configuration");
+
+ // Use SingleOrDefault to ensure there's exactly one element with that name
+ var assemblies = document.Root
+ .Elements().SingleOrDefault(e => e.Name.ToString().Equals("system.web"))
+ .Elements().SingleOrDefault(e => e.Name.ToString().Equals("compilation"))
+ .Elements().SingleOrDefault(e => e.Name.ToString().Equals("assemblies"));
+
+ Assert.Equal(references, assemblies.Elements().First().Attribute("assembly").Value);
+
+ // Make sure the original web.config content is unaffected
+ Assert.Equal("test", document.Root
+ .Elements().SingleOrDefault(e => e.Name.ToString().Equals("connectionStrings"))
+ .Elements().SingleOrDefault(e => e.Name.ToString().Equals("add"))
+ .Attributes().SingleOrDefault(e => e.Name.ToString().Equals("name")).Value);
+
+ Assert.Equal("awesomeprofile", document.Root.Element("system.web").Element("profiles").Element("add").Attribute("name").Value);
+ }
+
+ [Fact]
+ public void AddPackageWithFrameworkReferenceDoesNotAffectWebConfigIfReferencesAlreadyExist()
+ {
+ // Arrange
+ var webConfigPath = @"x:\my-website\web.config";
+ var memoryStream = new NeverCloseMemoryStream(@"<?xml version=""1.0""?>
+ <configuration>
+ <connectionStrings>
+ <add name=""test"" />
+ </connectionStrings>
+ <system.web>
+ <compilation>
+ <assemblies>
+ <add assembly=""System.Data, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"" />
+ </assemblies>
+ </compilation>
+ <profiles><add name=""awesomeprofile"" /></profiles>
+ </system.web>
+ </configuration>
+
+");
+
+ var fileSystem = new Mock<IFileSystem>();
+ fileSystem.SetupGet(f => f.Root).Returns("x:\\my-website");
+ fileSystem.Setup(f => f.FileExists(It.Is<string>(p => p.Equals(webConfigPath)))).Returns(true);
+ fileSystem.Setup(f => f.OpenFile(It.Is<string>(p => p.Equals(webConfigPath)))).Returns(() =>
+ {
+ memoryStream.Seek(0, SeekOrigin.Begin);
+ return memoryStream;
+ });
+ fileSystem.Setup(f => f.AddFile(It.Is<string>(p => p.Equals(webConfigPath)), It.IsAny<Stream>()))
+ .Callback<string, Stream>((_, stream) => { memoryStream = new NeverCloseMemoryStream(stream.ReadToEnd()); });
+
+ // Act
+ WebProjectSystem.AddReferencesToConfig(fileSystem.Object, "System.Data");
+ WebProjectSystem.AddReferencesToConfig(fileSystem.Object, "Microsoft.Abstractions");
+
+ // Assert
+ memoryStream.Seek(0, SeekOrigin.Begin);
+ XDocument document = XDocument.Load(memoryStream);
+
+ var element = document.Root;
+ Assert.Equal(element.Name, "configuration");
+
+ // Use SingleOrDefault to ensure there's exactly one element with that name
+ var assemblies = document.Root
+ .Elements().SingleOrDefault(e => e.Name.ToString().Equals("system.web"))
+ .Elements().SingleOrDefault(e => e.Name.ToString().Equals("compilation"))
+ .Elements().SingleOrDefault(e => e.Name.ToString().Equals("assemblies"));
+
+ Assert.Equal(2, assemblies.Elements("add").Count());
+ Assert.Equal("System.Data, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35",
+ assemblies.Elements().First().Attribute("assembly").Value);
+ Assert.Equal("Microsoft.Abstractions",
+ assemblies.Elements().Last().Attribute("assembly").Value);
+
+ // Make sure the original web.config content is unaffected
+ Assert.Equal("test", document.Root
+ .Elements().SingleOrDefault(e => e.Name.ToString().Equals("connectionStrings"))
+ .Elements().SingleOrDefault(e => e.Name.ToString().Equals("add"))
+ .Attributes().SingleOrDefault(e => e.Name.ToString().Equals("name")).Value);
+
+ Assert.Equal("awesomeprofile", document.Root.Element("system.web").Element("profiles").Element("add").Attribute("name").Value);
+ }
+
+ [Fact]
+ public void ResolveAssemblyPartialNameForCommonAssemblies()
+ {
+ // Arrange
+ var commonAssemblies = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
+ {
+ { "System.Data", "System.Data, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" },
+ { "System.Data.Linq", "System.Data.Linq, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" },
+ { "System.Net", "System.Net, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" },
+ { "System.Runtime.Caching", "System.Runtime.Caching, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" },
+ { "System.Xml", "System.Xml, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" },
+ { "System.Web.DynamicData", "System.Web.DynamicData, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" },
+ };
+
+ // Act and Assert
+ foreach (var item in commonAssemblies)
+ {
+ var resolvedName = WebProjectSystem.ResolvePartialAssemblyName(item.Key);
+
+ Assert.Equal(item.Value, resolvedName);
+ }
+ }
+
+ private static IFileSystem GetFileSystem()
+ {
+ var fileSystem = new Mock<IFileSystem>();
+ fileSystem.Setup(c => c.Root).Returns(@"X:\packages\");
+ return fileSystem.Object;
+ }
+
+ private class NeverCloseMemoryStream : MemoryStream
+ {
+ public NeverCloseMemoryStream(string content)
+ : base(Encoding.UTF8.GetBytes(content))
+ {
+ }
+
+ protected override void Dispose(bool disposing)
+ {
+ // Do nothing
+ }
+ }
+ }
+}
diff --git a/test/System.Web.WebPages.Administration.Test/packages.config b/test/System.Web.WebPages.Administration.Test/packages.config
new file mode 100644
index 00000000..bec0dc35
--- /dev/null
+++ b/test/System.Web.WebPages.Administration.Test/packages.config
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<packages>
+ <package id="Moq" version="4.0.10827" />
+ <package id="NuGet.Core" version="1.6.2" />
+ <package id="xunit" version="1.9.0.1566" />
+ <package id="xunit.extensions" version="1.9.0.1566" />
+</packages> \ No newline at end of file
diff --git a/test/System.Web.WebPages.Deployment.Test/App.Config b/test/System.Web.WebPages.Deployment.Test/App.Config
new file mode 100644
index 00000000..49cc43e1
--- /dev/null
+++ b/test/System.Web.WebPages.Deployment.Test/App.Config
@@ -0,0 +1,3 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<configuration>
+</configuration> \ No newline at end of file
diff --git a/test/System.Web.WebPages.Deployment.Test/AssemblyUtilsTest.cs b/test/System.Web.WebPages.Deployment.Test/AssemblyUtilsTest.cs
new file mode 100644
index 00000000..7cf719f6
--- /dev/null
+++ b/test/System.Web.WebPages.Deployment.Test/AssemblyUtilsTest.cs
@@ -0,0 +1,266 @@
+using System.Collections.Generic;
+using System.Globalization;
+using System.IO;
+using System.Linq;
+using System.Reflection;
+using Xunit;
+
+namespace System.Web.WebPages.Deployment.Test
+{
+ public class AssemblyUtilsTest
+ {
+ [Fact]
+ public void GetMaxAssemblyVersionReturnsMaximumAvailableVersion()
+ {
+ // Arrange
+ var assemblies = new[]
+ {
+ new AssemblyName("System.Web.WebPages.Deployment, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"),
+ new AssemblyName("System.Web.WebPages.Deployment, Version=2.1.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"),
+ new AssemblyName("System.Web.WebPages.Deployment, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35")
+ };
+
+ // Act
+ var maxVersion = AssemblyUtils.GetMaxWebPagesVersion(assemblies);
+
+ // Assert
+ Assert.Equal(new Version("2.1.0.0"), maxVersion);
+ }
+
+ [Fact]
+ public void GetMaxAssemblyVersionMatchesExactName()
+ {
+ // Arrange
+ var assemblies = new[]
+ {
+ new AssemblyName("System.Web.WebPages.Deployment, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"),
+ new AssemblyName("System.Web.WebPages.Development, Version=2.1.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"),
+ new AssemblyName("System.Web.WebPages.Deployment, Version=2.1.0.0, Culture=neutral, PublicKeyToken=7777777777777777"),
+ new AssemblyName("System.Web.WebPages.Deployment, Version=2.3.0.0, Culture=en-US, PublicKeyToken=31bf3856ad364e35"),
+ new AssemblyName("System.Web.WebPages.Deployment, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35")
+ };
+
+ // Act
+ var maxVersion = AssemblyUtils.GetMaxWebPagesVersion(assemblies);
+
+ // Assert
+ Assert.Equal(new Version("2.0.0.0"), maxVersion);
+ }
+
+ [Fact]
+ public void GetVersionFromBinReturnsNullIfNoFileWithDeploymentAssemblyNameIsFoundInBin()
+ {
+ // Arrange
+ var binDirectory = @"X:\test\project";
+ TestFileSystem fileSystem = new TestFileSystem();
+
+ // Act
+ var binVersion = AssemblyUtils.GetVersionFromBin(binDirectory, fileSystem, getAssemblyNameThunk: null);
+
+ // Assert
+ Assert.Null(binVersion);
+ }
+
+ [Fact]
+ public void GetVersionFromBinReturnsVersionFromBinIfLower()
+ {
+ // Arrange
+ var binDirectory = @"X:\test\project";
+ TestFileSystem fileSystem = new TestFileSystem();
+ fileSystem.AddFile(Path.Combine(binDirectory, "System.Web.WebPages.Deployment.dll"));
+ Func<string, AssemblyName> getAssembyName = _ => new AssemblyName("System.Web.WebPages.Deployment, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35");
+
+ // Act
+ var binVersion = AssemblyUtils.GetVersionFromBin(binDirectory, fileSystem, getAssembyName);
+
+ // Assert
+ Assert.Equal(new Version("1.0.0.0"), binVersion);
+ }
+
+ [Fact]
+ public void GetVersionFromBinReturnsVersionFromBinIfSameVersion()
+ {
+ // Arrange
+ var binDirectory = @"X:\test\project";
+ TestFileSystem fileSystem = new TestFileSystem();
+ fileSystem.AddFile(Path.Combine(binDirectory, "System.Web.WebPages.Deployment.dll"));
+ Func<string, AssemblyName> getAssembyName = _ => new AssemblyName("System.Web.WebPages.Deployment, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35");
+
+ // Act
+ var binVersion = AssemblyUtils.GetVersionFromBin(binDirectory, fileSystem, getAssembyName);
+
+ // Assert
+ Assert.Equal(new Version("2.0.0.0"), binVersion);
+ }
+
+ [Fact]
+ public void GetVersionFromBinReturnsVersionFromBinIfHigherVersion()
+ {
+ // Arrange
+ var binDirectory = @"X:\test\project";
+ TestFileSystem fileSystem = new TestFileSystem();
+ fileSystem.AddFile(Path.Combine(binDirectory, "System.Web.WebPages.Deployment.dll"));
+ Func<string, AssemblyName> getAssembyName = _ => new AssemblyName("System.Web.WebPages.Deployment, Version=8.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35");
+
+ // Act
+ var binVersion = AssemblyUtils.GetVersionFromBin(binDirectory, fileSystem, getAssembyName);
+
+ // Assert
+ Assert.Equal(new Version("8.0.0.0"), binVersion);
+ }
+
+ [Fact]
+ public void GetVersionFromBinReturnsNullIfFileInBinIsNotAValidBinary()
+ {
+ // Arrange
+ var binDirectory = @"X:\test\project";
+ TestFileSystem fileSystem = new TestFileSystem();
+ fileSystem.AddFile(Path.Combine(binDirectory, "System.Web.WebPages.Deployment.dll"));
+ Func<string, AssemblyName> getAssembyName = _ => { throw new FileLoadException(); };
+
+ // Act
+ var binVersion = AssemblyUtils.GetVersionFromBin(binDirectory, fileSystem, getAssembyName);
+
+ // Assert
+ Assert.Null(binVersion);
+ }
+
+ [Fact]
+ public void GetAssembliesForVersionReturnsCorrectSetForV1()
+ {
+ // Arrange
+ var expectedAssemblies = new[]
+ {
+ "Microsoft.Web.Infrastructure, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35",
+ "System.Web.Razor, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35",
+ "System.Web.Helpers, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35",
+ "System.Web.WebPages, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35",
+ "System.Web.WebPages.Administration, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35",
+ "System.Web.WebPages.Razor, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35",
+ "WebMatrix.Data, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35",
+ "WebMatrix.WebData, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"
+ };
+
+ // Act
+ var assemblies = AssemblyUtils.GetAssembliesForVersion(new Version("1.0.0.0"))
+ .Select(c => c.ToString())
+ .ToArray();
+
+ // Assert
+ Assert.Equal(expectedAssemblies, assemblies);
+ }
+
+ [Fact]
+ public void GetAssembliesForVersionReturnsCorrectSetForVCurrent()
+ {
+ // Arrange
+ var expectedAssemblies = new[]
+ {
+ "Microsoft.Web.Infrastructure, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35",
+ "System.Web.Razor, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35",
+ "System.Web.Helpers, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35",
+ "System.Web.WebPages, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35",
+ "System.Web.WebPages.Administration, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35",
+ "System.Web.WebPages.Razor, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35",
+ "WebMatrix.Data, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35",
+ "WebMatrix.WebData, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35",
+ };
+
+ // Act
+ var assemblies = AssemblyUtils.GetAssembliesForVersion(new Version("2.0.0.0"))
+ .Select(c => c.ToString())
+ .ToArray();
+
+ // Assert
+ Assert.Equal(expectedAssemblies, assemblies);
+ }
+
+ [Fact]
+ public void GetMatchingAssembliesReturnsEmptyDictionaryIfNoReferencesMatchWebPagesAssemblies()
+ {
+ // Arrange
+ var assemblyReferences = new Dictionary<string, IEnumerable<string>>
+ {
+ { @"x:\site\bin\A.dll", new List<string> { "mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=null" }},
+ { @"x:\site\bin\B.dll", new List<string> { "System.Web.Mvc, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" }},
+ };
+
+ var a = "1";
+ var b = "2";
+
+ var c = new { a, b };
+ Console.WriteLine(c.a);
+
+ // Act
+ var referencedAssemblies = AssemblyUtils.GetAssembliesMatchingOtherVersions(assemblyReferences);
+
+ // Assert
+ Assert.Empty(referencedAssemblies);
+ }
+
+ [Fact]
+ public void GetMatchingAssembliesReturnsReferencingAssemblyAndWebPagesVersionForMatchingReferences()
+ {
+ // Arrange
+ var assemblyReferences = new Dictionary<string, IEnumerable<string>>
+ {
+ { @"x:\site\bin\A.dll", new[] { "mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=null" }},
+ { @"x:\site\bin\B.dll", new[]
+ {
+ "mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=null",
+ "System.Web.WebPages, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35",
+ "System.Web.Helpers, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35",
+ }
+ },
+ };
+
+ // Act
+ var referencedAssemblies = AssemblyUtils.GetAssembliesMatchingOtherVersions(assemblyReferences);
+
+ // Assert
+ Assert.Equal(1, referencedAssemblies.Count);
+ Assert.Equal(@"x:\site\bin\B.dll", referencedAssemblies.Single().Key);
+ Assert.Equal(new Version("1.0.0.0"), referencedAssemblies.Single().Value);
+ }
+
+ [Fact]
+ public void GetMatchingAssembliesFiltersWebPagesVersionsThatMatch()
+ {
+ // Arrange
+ var assemblyReferences = new Dictionary<string, IEnumerable<string>>
+ {
+ { @"x:\site\bin\A.dll", new[] { "mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=null" }},
+ { @"x:\site\bin\B.dll", new[]
+ {
+ "mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=null",
+ String.Format(CultureInfo.InvariantCulture, "System.Web.WebPages, Version={0}, Culture=neutral, PublicKeyToken=31bf3856ad364e35", AssemblyUtils.ThisAssemblyName.Version),
+ String.Format(CultureInfo.InvariantCulture, "System.Web.Helpers, Version={0}, Culture=neutral, PublicKeyToken=31bf3856ad364e35", AssemblyUtils.ThisAssemblyName.Version)
+ }
+ },
+ { @"x:\site\bin\C.dll", new[]
+ {
+ "mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=null",
+ "System.Web.WebPages.Razor, Version=1.2.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35",
+ "System.Web.WebPages.Razor, Version=1.3.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35",
+ }
+ },
+ };
+
+ // Act
+ var referencedAssemblies = AssemblyUtils.GetAssembliesMatchingOtherVersions(assemblyReferences);
+
+ // Assert
+ Assert.Equal(1, referencedAssemblies.Count);
+ Assert.Equal(@"x:\site\bin\C.dll", referencedAssemblies.Single().Key);
+ Assert.Equal(new Version("1.2.0.0"), referencedAssemblies.Single().Value);
+ }
+
+ private static void EnsureDirectory(string directory)
+ {
+ if (!Directory.Exists(directory))
+ {
+ Directory.CreateDirectory(directory);
+ }
+ }
+ }
+}
diff --git a/test/System.Web.WebPages.Deployment.Test/DeploymentUtil.cs b/test/System.Web.WebPages.Deployment.Test/DeploymentUtil.cs
new file mode 100644
index 00000000..514b5116
--- /dev/null
+++ b/test/System.Web.WebPages.Deployment.Test/DeploymentUtil.cs
@@ -0,0 +1,13 @@
+using System.IO;
+
+namespace System.Web.WebPages.Deployment.Test
+{
+ internal static class DeploymentUtil
+ {
+ public static string GetBinDirectory()
+ {
+ var tempDirectory = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName());
+ return Path.Combine(tempDirectory, "bin");
+ }
+ }
+}
diff --git a/test/System.Web.WebPages.Deployment.Test/PreApplicationStartCodeTest.cs b/test/System.Web.WebPages.Deployment.Test/PreApplicationStartCodeTest.cs
new file mode 100644
index 00000000..464d977a
--- /dev/null
+++ b/test/System.Web.WebPages.Deployment.Test/PreApplicationStartCodeTest.cs
@@ -0,0 +1,510 @@
+using System.Collections.Generic;
+using System.Collections.Specialized;
+using System.IO;
+using System.Linq;
+using System.Reflection;
+using System.Text;
+using System.Web.WebPages.TestUtils;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.WebPages.Deployment.Test
+{
+ public class PreApplicationStartCodeTest
+ {
+ private const string DeploymentVersionFile = "System.Web.WebPages.Deployment";
+ private static readonly Version MaxVersion = new Version(2, 0, 0, 0);
+
+ [Fact]
+ public void PreApplicationStartCodeDoesNothingIfWebPagesIsExplicitlyDisabled()
+ {
+ // Arrange
+ Version loadedVersion = null;
+ bool registeredForChangeNotification = false;
+ IEnumerable<AssemblyName> loadedAssemblies = GetAssemblies("1", "2");
+
+ var fileSystem = new TestFileSystem();
+ var buildManager = new TestBuildManager();
+ var nameValueCollection = GetAppSettings(enabled: false, webPagesVersion: null);
+ Action<Version> loadWebPages = (version) => { loadedVersion = version; };
+ Action registerForChange = () => { registeredForChangeNotification = true; };
+
+ // Act
+ bool loaded = PreApplicationStartCode.StartCore(fileSystem, "", "bin", nameValueCollection, loadedAssemblies, buildManager, loadWebPages, registerForChange, null);
+
+ // Assert
+ Assert.False(loaded);
+ Assert.Null(loadedVersion);
+ Assert.False(registeredForChangeNotification);
+ Assert.Equal(0, buildManager.Stream.Length);
+ }
+
+ [Fact]
+ public void PreApplicationStartCodeUsesVersionSpecifiedInConfigIfWebPagesIsImplicitlyEnabled()
+ {
+ // Arrange
+ Version loadedVersion = null;
+ bool registeredForChangeNotification = false;
+ IEnumerable<AssemblyName> loadedAssemblies = GetAssemblies("1.12.123.1234", "2.0.0.0");
+ Version webPagesVersion = new Version("1.12.123.1234");
+
+ var fileSystem = new TestFileSystem();
+ fileSystem.AddFile("Default.cshtml");
+ var buildManager = new TestBuildManager();
+ var nameValueCollection = GetAppSettings(enabled: null, webPagesVersion: webPagesVersion);
+ Action<Version> loadWebPages = (version) => { loadedVersion = version; };
+ Action registerForChange = () => { registeredForChangeNotification = true; };
+
+ // Act
+ bool loaded = PreApplicationStartCode.StartCore(fileSystem, "", "bin", nameValueCollection, loadedAssemblies, buildManager, loadWebPages, registerForChange, getAssemblyNameThunk: null);
+
+ // Assert
+ Assert.True(loaded);
+ Assert.Equal(webPagesVersion, loadedVersion);
+ Assert.False(registeredForChangeNotification);
+ VerifyVersionFile(buildManager, webPagesVersion);
+ }
+
+ [Fact]
+ public void PreApplicationStartCodeDoesNotLoadCurrentWebPagesIfOnlyVersionIsListedInConfigAndNoFilesAreFoundInSiteRoot()
+ {
+ // Arrange
+ Version loadedVersion = null;
+ bool registeredForChangeNotification = false;
+ Version webPagesVersion = AssemblyUtils.ThisAssemblyName.Version;
+ IEnumerable<AssemblyName> loadedAssemblies = GetAssemblies("2.0.0.0");
+
+ var fileSystem = new TestFileSystem();
+ var buildManager = new TestBuildManager();
+ var nameValueCollection = GetAppSettings(enabled: null, webPagesVersion: webPagesVersion);
+ Action<Version> loadWebPages = (version) => { loadedVersion = version; };
+ Action registerForChange = () => { registeredForChangeNotification = true; };
+
+ // Arrange
+ bool loaded = PreApplicationStartCode.StartCore(fileSystem, "", "bin", nameValueCollection, loadedAssemblies, buildManager, loadWebPages, registerForChange, null);
+
+ // Assert
+ Assert.False(loaded);
+ Assert.Null(loadedVersion);
+ Assert.True(registeredForChangeNotification);
+ Assert.Equal(0, buildManager.Stream.Length);
+ }
+
+ [Fact]
+ public void PreApplicationStartCodeRegistersForChangeNotificationIfNotExplicitlyDisabledAndNoFilesFoundInSiteRoot()
+ {
+ // Arrange
+ Version loadedVersion = null;
+ bool registeredForChangeNotification = false;
+ IEnumerable<AssemblyName> loadedAssemblies = GetAssemblies("2.0.0.0");
+
+ var fileSystem = new TestFileSystem();
+ var buildManager = new TestBuildManager();
+ var nameValueCollection = GetAppSettings(enabled: null, webPagesVersion: null);
+ Action<Version> loadWebPages = (version) => { loadedVersion = version; };
+ Action registerForChange = () => { registeredForChangeNotification = true; };
+
+ // Act
+ bool loaded = PreApplicationStartCode.StartCore(fileSystem, "", "bin", nameValueCollection, loadedAssemblies, buildManager, loadWebPages, registerForChange, null);
+
+ // Assert
+ Assert.False(loaded);
+ Assert.Null(loadedVersion);
+ Assert.True(registeredForChangeNotification);
+ Assert.Equal(0, buildManager.Stream.Length);
+ }
+
+ [Fact]
+ public void PreApplicationStartCodeDoesNothingIfV1IsAvailableInBinAndSiteIsExplicitlyEnabled()
+ {
+ // Arrange
+ Version loadedVersion = null;
+ bool registeredForChangeNotification = false;
+ var v1Version = new Version("1.0.0.0");
+ IEnumerable<AssemblyName> loadedAssemblies = GetAssemblies("1.0.0.0", "2.0.0.0");
+
+ var binDirectory = DeploymentUtil.GetBinDirectory();
+
+ var fileSystem = new TestFileSystem();
+ fileSystem.AddFile(Path.Combine(binDirectory, "System.Web.WebPages.Deployment.dll"));
+ var buildManager = new TestBuildManager();
+ var nameValueCollection = GetAppSettings(enabled: true, webPagesVersion: null);
+ Action<Version> loadWebPages = (version) => { loadedVersion = version; };
+ Action registerForChange = () => { registeredForChangeNotification = true; };
+ Func<string, AssemblyName> getAssembyName = _ => new AssemblyName("System.Web.WebPages.Deployment, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35");
+
+ // Act
+ bool loaded = PreApplicationStartCode.StartCore(fileSystem, "", binDirectory, nameValueCollection, loadedAssemblies, buildManager, loadWebPages, registerForChange, getAssembyName);
+
+ // Assert
+ Assert.False(loaded);
+ Assert.Null(loadedVersion);
+ Assert.False(registeredForChangeNotification);
+ Assert.Equal(0, buildManager.Stream.Length);
+ }
+
+ [Fact]
+ public void PreApplicationStartCodeDoesNothingIfV1IsAvailableInBinAndFileExistsInRootOfWebSite()
+ {
+ // Arrange
+ Version loadedVersion = null;
+ bool registeredForChangeNotification = false;
+ var v1Version = new Version("1.0.0.0");
+ IEnumerable<AssemblyName> loadedAssemblies = GetAssemblies("1.0.0.0", "2.0.0.0");
+
+ var binDirectory = DeploymentUtil.GetBinDirectory();
+
+ var fileSystem = new TestFileSystem();
+ var buildManager = new TestBuildManager();
+ fileSystem.AddFile("Default.cshtml");
+ fileSystem.AddFile(Path.Combine(binDirectory, "System.Web.WebPages.Deployment.dll"));
+ var nameValueCollection = GetAppSettings(enabled: null, webPagesVersion: null);
+ Action<Version> loadWebPages = (version) => { loadedVersion = version; };
+ Action registerForChange = () => { registeredForChangeNotification = true; };
+ Func<string, AssemblyName> getAssembyName = _ => new AssemblyName("System.Web.WebPages.Deployment, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35");
+
+ // Act
+ bool loaded = PreApplicationStartCode.StartCore(fileSystem, "", binDirectory, nameValueCollection, loadedAssemblies, buildManager, loadWebPages, registerForChange, getAssembyName);
+
+ // Assert
+ Assert.False(loaded);
+ Assert.Null(loadedVersion);
+ Assert.False(registeredForChangeNotification);
+ Assert.Equal(0, buildManager.Stream.Length);
+ }
+
+ [Fact]
+ public void PreApplicationStartCodeDoesNothingIfItIsAvailableInBinAndFileExistsInRootOfWebSite()
+ {
+ // Arrange
+ Version loadedVersion = null;
+ bool registeredForChangeNotification = false;
+ var webPagesVersion = AssemblyUtils.ThisAssemblyName.Version;
+ IEnumerable<AssemblyName> loadedAssemblies = GetAssemblies(AssemblyUtils.ThisAssemblyName.Version.ToString());
+
+ var fileSystem = new TestFileSystem();
+ var binDirectory = DeploymentUtil.GetBinDirectory();
+
+ var buildManager = new TestBuildManager();
+ fileSystem.AddFile("Default.vbhtml");
+ fileSystem.AddFile(Path.Combine(binDirectory, "System.Web.WebPages.Deployment.dll"));
+ var nameValueCollection = GetAppSettings(enabled: null, webPagesVersion: null);
+ Action<Version> loadWebPages = (version) => { loadedVersion = version; };
+ Action registerForChange = () => { registeredForChangeNotification = true; };
+ Func<string, AssemblyName> getAssembyName = _ => new AssemblyName("System.Web.WebPages.Deployment, Version=" + AssemblyUtils.ThisAssemblyName.Version.ToString() + ", Culture=neutral, PublicKeyToken=31bf3856ad364e35");
+
+ // Act
+ bool loaded = PreApplicationStartCode.StartCore(fileSystem, "", binDirectory, nameValueCollection, loadedAssemblies, buildManager, loadWebPages, registerForChange, getAssembyName);
+
+ // Assert
+ Assert.False(loaded);
+ Assert.Null(loadedVersion);
+ Assert.False(registeredForChangeNotification);
+ Assert.Equal(0, buildManager.Stream.Length);
+ }
+
+ [Fact]
+ public void PreApplicationStartCodeLoadsMaxVersionIfNoVersionIsSpecifiedAndCurrentAssemblyIsTheMaximumVersionAvailable()
+ {
+ // Arrange
+ Version loadedVersion = null;
+ bool registeredForChangeNotification = false;
+ var webPagesVersion = AssemblyUtils.ThisAssemblyName.Version;
+ var v1Version = new Version("1.0.0.0");
+ IEnumerable<AssemblyName> loadedAssemblies = GetAssemblies("1.0.0.0", "2.0.0.0");
+
+ // Note: For this test to work with future versions we would need to create corresponding embedded resources with that version in it.
+ var fileSystem = new TestFileSystem();
+ var buildManager = new TestBuildManager();
+ fileSystem.AddFile("Index.cshtml");
+ var nameValueCollection = GetAppSettings(enabled: null, webPagesVersion: null);
+ Action<Version> loadWebPages = (version) => { loadedVersion = version; };
+ Action registerForChange = () => { registeredForChangeNotification = true; };
+
+ // Act
+ bool loaded = PreApplicationStartCode.StartCore(fileSystem, "", "bin", nameValueCollection, loadedAssemblies, buildManager, loadWebPages, registerForChange, null);
+
+ // Assert
+ Assert.True(loaded);
+ Assert.Equal(MaxVersion, loadedVersion);
+ Assert.False(registeredForChangeNotification);
+ VerifyVersionFile(buildManager, MaxVersion);
+ }
+
+ [Fact]
+ public void PreApplicationStartCodeDoesNotLoadIfAHigherVersionIsAvailableInBin()
+ {
+ // Arrange
+ Version loadedVersion = null;
+ bool registeredForChangeNotification = false;
+ IEnumerable<AssemblyName> loadedAssemblies = GetAssemblies("2.0.0.0", "8.0.0.0");
+
+ var binDirectory = DeploymentUtil.GetBinDirectory();
+
+ var fileSystem = new TestFileSystem();
+ fileSystem.AddFile("Index.cshtml");
+ fileSystem.AddFile(Path.Combine(binDirectory, "System.Web.WebPages.Deployment.dll"));
+ var buildManager = new TestBuildManager();
+ var nameValueCollection = GetAppSettings(enabled: null, webPagesVersion: null);
+ Action<Version> loadWebPages = (version) => { loadedVersion = version; };
+ Action registerForChange = () => { registeredForChangeNotification = true; };
+ Func<string, AssemblyName> getAssembyName = _ => new AssemblyName("System.Web.WebPages.Deployment, Version=8.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35");
+
+ // Act
+ bool loaded = PreApplicationStartCode.StartCore(fileSystem, "", binDirectory, nameValueCollection, loadedAssemblies, buildManager, loadWebPages, registerForChange, getAssembyName);
+
+ // Assert
+ Assert.False(loaded);
+ Assert.Null(loadedVersion);
+ Assert.False(registeredForChangeNotification);
+ Assert.Equal(0, buildManager.Stream.Length);
+ }
+
+ [Fact]
+ public void PreApplicationStartCodeDoesNotLoadIfAHigherVersionIsAvailableInGac()
+ {
+ // Arrange
+ Version loadedVersion = null;
+ bool registeredForChangeNotification = false;
+ // Hopefully we'd have figured out a better way to load Plan9 by v8.
+ var webPagesVersion = new Version("8.0.0.0");
+ IEnumerable<AssemblyName> loadedAssemblies = GetAssemblies("1.0.0.0", "2.0.0.0", "8.0.0.0");
+
+ var fileSystem = new TestFileSystem();
+ fileSystem.AddFile("Index.cshtml");
+ var buildManager = new TestBuildManager();
+ var nameValueCollection = GetAppSettings(enabled: null, webPagesVersion: null);
+ Action<Version> loadWebPages = (version) => { loadedVersion = version; };
+ Action registerForChange = () => { registeredForChangeNotification = true; };
+
+ // Act
+ bool loaded = PreApplicationStartCode.StartCore(fileSystem, "", "bin", nameValueCollection, loadedAssemblies, buildManager, loadWebPages, registerForChange, null);
+
+ // Assert
+ Assert.False(loaded);
+ Assert.Null(loadedVersion);
+ Assert.False(registeredForChangeNotification);
+ Assert.Equal(0, buildManager.Stream.Length);
+ }
+
+ [Fact]
+ public void PreApplicationStartCodeForcesRecompileIfPreviousVersionIsNotTheSameAsCurrentVersion()
+ {
+ // Arrange
+ Version loadedVersion = null;
+ bool registeredForChangeNotification = false;
+ IEnumerable<AssemblyName> loadedAssemblies = GetAssemblies("2.0.0.0");
+
+ var fileSystem = new TestFileSystem();
+ fileSystem.AddFile("Index.cshtml");
+ var buildManager = new TestBuildManager();
+ var content = "1.0.0.0" + Environment.NewLine;
+ buildManager.Stream = new MemoryStream(Encoding.Default.GetBytes(content));
+
+ var nameValueCollection = GetAppSettings(enabled: null, webPagesVersion: new Version("2.0.0.0"));
+ Action<Version> loadWebPages = (version) => { loadedVersion = version; };
+ Action registerForChange = () => { registeredForChangeNotification = true; };
+
+ // Act
+ var ex = Assert.Throws<HttpCompileException>(() =>
+ PreApplicationStartCode.StartCore(fileSystem, "", @"site\bin", nameValueCollection, loadedAssemblies, buildManager, loadWebPages, registerForChange, null)
+ );
+
+ // Assert
+ Assert.Equal("Changes were detected in the Web Pages runtime version that require your application to be recompiled. Refresh your browser window to continue.", ex.Message);
+ Assert.Equal(ex.Data["WebPages.VersionChange"], true);
+ Assert.False(registeredForChangeNotification);
+ VerifyVersionFile(buildManager, new Version("2.0.0.0"));
+ Assert.True(fileSystem.FileExists(@"site\bin\WebPagesRecompilation.deleteme"));
+ }
+
+ [Fact]
+ public void PreApplicationStartCodeDoesNotForceRecompileIfNewVersionIsV1AndCurrentAssemblyIsNotMaxVersion()
+ {
+ // Arrange
+ Version loadedVersion = null;
+ bool registeredForChangeNotification = false;
+ IEnumerable<AssemblyName> loadedAssemblies = GetAssemblies("2.0.0.0", "5.0.0.0");
+
+ var fileSystem = new TestFileSystem();
+ fileSystem.AddFile("Index.cshtml");
+ var buildManager = new TestBuildManager();
+ var content = AssemblyUtils.ThisAssemblyName.Version + Environment.NewLine;
+ buildManager.Stream = new MemoryStream(Encoding.Default.GetBytes(content));
+
+ var nameValueCollection = GetAppSettings(enabled: null, webPagesVersion: new Version("1.0.0"));
+ Action<Version> loadWebPages = (version) => { loadedVersion = version; };
+ Action registerForChange = () => { registeredForChangeNotification = true; };
+
+ // Act
+ bool loaded = PreApplicationStartCode.StartCore(fileSystem, "", @"site\bin", nameValueCollection, loadedAssemblies, buildManager, loadWebPages, registerForChange, null);
+
+ // Assert
+ Assert.False(loaded);
+ Assert.False(registeredForChangeNotification);
+ VerifyVersionFile(buildManager, AssemblyUtils.ThisAssemblyName.Version);
+ Assert.False(fileSystem.FileExists(@"site\bin\WebPagesRecompilation.deleteme"));
+ }
+
+ [Fact]
+ public void PreApplicationStartCodeThrowsIfWebPagesIsInBinAndDifferentVersionIsSpecifiedInConfig()
+ {
+ // Arrange
+ Version loadedVersion = null;
+ bool registeredForChangeNotification = false;
+ IEnumerable<AssemblyName> loadedAssemblies = GetAssemblies("1.0.0.0", "2.0.0.0");
+
+ var binDirectory = DeploymentUtil.GetBinDirectory();
+
+ var fileSystem = new TestFileSystem();
+ fileSystem.AddFile("Index.cshtml");
+ fileSystem.AddFile(Path.Combine(binDirectory, "System.Web.WebPages.Deployment.dll"));
+ var buildManager = new TestBuildManager();
+ var content = AssemblyUtils.ThisAssemblyName.Version + Environment.NewLine;
+ buildManager.Stream = new MemoryStream(Encoding.Default.GetBytes(content));
+
+ var nameValueCollection = GetAppSettings(enabled: null, webPagesVersion: new Version("2.0.0"));
+ Action<Version> loadWebPages = (version) => { loadedVersion = version; };
+ Action registerForChange = () => { registeredForChangeNotification = true; };
+ Func<string, AssemblyName> getAssembyName = _ => new AssemblyName("System.Web.WebPages.Deployment, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35");
+
+ // Act and Assert
+ Assert.Throws<InvalidOperationException>(() =>
+ PreApplicationStartCode.StartCore(fileSystem, "", binDirectory, nameValueCollection, loadedAssemblies, buildManager, loadWebPages, registerForChange, getAssembyName),
+ @"Conflicting versions of ASP.NET Web Pages detected: specified version is ""2.0.0.0"", but the version in bin is ""1.0.0.0"". To continue, remove files from the application's bin directory or remove the version specification in web.config."
+ );
+
+ Assert.False(registeredForChangeNotification);
+ Assert.Null(loadedVersion);
+ }
+
+ [Fact]
+ public void PreApplicationStartCodeThrowsIfVersionIsSpecifiedInConfigAndDifferentVersionExistsInBin()
+ {
+ // Arrange
+ Version loadedVersion = null;
+
+ var binDirectory = DeploymentUtil.GetBinDirectory();
+ IEnumerable<AssemblyName> loadedAssemblies = GetAssemblies("1.0.0.0", AssemblyUtils.ThisAssemblyName.Version.ToString());
+
+ var fileSystem = new TestFileSystem();
+ fileSystem.AddFile("Index.cshtml");
+ fileSystem.AddFile(Path.Combine(binDirectory, "System.Web.WebPages.Deployment.dll"));
+ var buildManager = new TestBuildManager();
+ var content = AssemblyUtils.ThisAssemblyName.Version + Environment.NewLine;
+ buildManager.Stream = new MemoryStream(Encoding.Default.GetBytes(content));
+
+ var nameValueCollection = GetAppSettings(enabled: null, webPagesVersion: new Version("1.0.0"));
+ Action<Version> loadWebPages = (version) => { loadedVersion = version; };
+ Action registerForChange = () => { };
+ Func<string, AssemblyName> getAssembyName = _ => new AssemblyName("System.Web.WebPages.Deployment, Version=" + AssemblyUtils.ThisAssemblyName.Version + ", Culture=neutral, PublicKeyToken=31bf3856ad364e35");
+
+ // Act and Assert
+ Assert.Throws<InvalidOperationException>(() =>
+ PreApplicationStartCode.StartCore(fileSystem, "", binDirectory, nameValueCollection, loadedAssemblies, buildManager: buildManager, loadWebPages: loadWebPages, registerForChangeNotification: registerForChange, getAssemblyNameThunk: getAssembyName),
+ String.Format(@"Conflicting versions of ASP.NET Web Pages detected: specified version is ""1.0.0.0"", but the version in bin is ""{0}"". To continue, remove files from the application's bin directory or remove the version specification in web.config.",
+ AssemblyUtils.ThisAssemblyName.Version));
+ }
+
+ [Fact]
+ public void PreApplicationStartCodeThrowsIfVersionSpecifiedInConfigIsNotAvailable()
+ {
+ // Arrange
+ Version loadedVersion = null;
+
+ var binDirectory = DeploymentUtil.GetBinDirectory();
+ IEnumerable<AssemblyName> loadedAssemblies = GetAssemblies("1.0.0.0", AssemblyUtils.ThisAssemblyName.Version.ToString());
+
+ var fileSystem = new TestFileSystem();
+ fileSystem.AddFile("Index.cshtml");
+ var buildManager = new TestBuildManager();
+ var content = AssemblyUtils.ThisAssemblyName.Version + Environment.NewLine;
+ buildManager.Stream = new MemoryStream(Encoding.Default.GetBytes(content));
+
+ var nameValueCollection = GetAppSettings(enabled: null, webPagesVersion: new Version("1.5"));
+ Action<Version> loadWebPages = (version) => { loadedVersion = version; };
+ Action registerForChange = () => { };
+
+ // Act and Assert
+ Assert.Throws<InvalidOperationException>(() =>
+ PreApplicationStartCode.StartCore(fileSystem, "", binDirectory, nameValueCollection, loadedAssemblies, buildManager: buildManager, loadWebPages: loadWebPages, registerForChangeNotification: registerForChange, getAssemblyNameThunk: null),
+ String.Format("Specified Web Pages version \"1.5.0.0\" could not be found. Update your web.config to specify a different version. Current version: \"{0}\".",
+ AssemblyUtils.ThisAssemblyName.Version));
+ }
+
+ [Fact]
+ public void TestPreAppStartClass()
+ {
+ PreAppStartTestHelper.TestPreAppStartClass(typeof(PreApplicationStartCode));
+ }
+
+ private static NameValueCollection GetAppSettings(bool? enabled, Version webPagesVersion)
+ {
+ var nameValueCollection = new NameValueCollection();
+ if (enabled.HasValue)
+ {
+ nameValueCollection["webpages:enabled"] = enabled.Value ? "true" : "false";
+ }
+ if (webPagesVersion != null)
+ {
+ nameValueCollection["webpages:version"] = webPagesVersion.ToString();
+ }
+
+ return nameValueCollection;
+ }
+
+ private static void VerifyVersionFile(TestBuildManager buildManager, Version webPagesVersion)
+ {
+ var content = Encoding.UTF8.GetString(buildManager.Stream.ToArray());
+ Version version = Version.Parse(content);
+ Assert.Equal(webPagesVersion, version);
+ }
+
+ private class TestBuildManager : IBuildManager
+ {
+ private MemoryStream _memoryStream = new MemoryStream();
+
+ public MemoryStream Stream
+ {
+ get { return _memoryStream; }
+ set { _memoryStream = value; }
+ }
+
+ public Stream CreateCachedFile(string fileName)
+ {
+ Assert.Equal(DeploymentVersionFile, fileName);
+ CopyMemoryStream();
+ return _memoryStream;
+ }
+
+ public Stream ReadCachedFile(string fileName)
+ {
+ Assert.Equal(DeploymentVersionFile, fileName);
+ CopyMemoryStream();
+ return _memoryStream;
+ }
+
+ /// <summary>
+ /// Need to do this because the MemoryStream is read and written to in consecutive calls which causes it to be closed / non-expandable.
+ /// </summary>
+ private void CopyMemoryStream()
+ {
+ var content = _memoryStream.ToArray();
+ if (content.Length > 0)
+ {
+ _memoryStream = new MemoryStream(_memoryStream.ToArray());
+ }
+ else
+ {
+ _memoryStream = new MemoryStream();
+ }
+ }
+ }
+
+ private static IEnumerable<AssemblyName> GetAssemblies(params string[] versions)
+ {
+ return from version in versions
+ select new AssemblyName("System.Web.WebPages.Deployment, Version=" + version + ", Culture=neutral, PublicKeyToken=31bf3856ad364e35");
+ }
+ }
+}
diff --git a/test/System.Web.WebPages.Deployment.Test/Properties/AssemblyInfo.cs b/test/System.Web.WebPages.Deployment.Test/Properties/AssemblyInfo.cs
new file mode 100644
index 00000000..16a6ad5e
--- /dev/null
+++ b/test/System.Web.WebPages.Deployment.Test/Properties/AssemblyInfo.cs
@@ -0,0 +1,34 @@
+using System.Reflection;
+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("System.Web.WebPages.Deployment.Test")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("Microsoft")]
+[assembly: AssemblyProduct("System.Web.WebPages.Deployment.Test")]
+[assembly: AssemblyCopyright("Copyright © Microsoft 2009")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Setting ComVisible to false makes the types in this assembly not visible
+// to COM componenets. If you need to access a type in this assembly from
+// COM, set the ComVisible attribute to true on that type.
+
+[assembly: ComVisible(false)]
+
+// 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 Revision and Build Numbers
+// by using the '*' as shown below:
+
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
diff --git a/test/System.Web.WebPages.Deployment.Test/System.Web.WebPages.Deployment.Test.csproj b/test/System.Web.WebPages.Deployment.Test/System.Web.WebPages.Deployment.Test.csproj
new file mode 100644
index 00000000..cce1a22d
--- /dev/null
+++ b/test/System.Web.WebPages.Deployment.Test/System.Web.WebPages.Deployment.Test.csproj
@@ -0,0 +1,97 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory),Runtime.sln))\tools\WebStack.settings.targets" />
+ <PropertyGroup>
+ <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+ <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+ <ProductVersion>9.0.30729</ProductVersion>
+ <SchemaVersion>2.0</SchemaVersion>
+ <ProjectGuid>{268DEE9D-F323-4A00-8ED8-3784388C3E3A}</ProjectGuid>
+ <OutputType>Library</OutputType>
+ <AppDesignerFolder>Properties</AppDesignerFolder>
+ <RootNamespace>System.Web.WebPages.Deployment.Test</RootNamespace>
+ <AssemblyName>System.Web.WebPages.Deployment.Test</AssemblyName>
+ <FileAlignment>512</FileAlignment>
+ <ProjectTypeGuids>{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
+ <ErrorReport>prompt</ErrorReport>
+ <WarningLevel>4</WarningLevel>
+ <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
+ <DebugSymbols>true</DebugSymbols>
+ <DebugType>full</DebugType>
+ <Optimize>false</Optimize>
+ <OutputPath>$(WebStackRootPath)\bin\Debug\Test\</OutputPath>
+ <DefineConstants>TRACE;DEBUG</DefineConstants>
+ <CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
+ <DebugType>pdbonly</DebugType>
+ <Optimize>true</Optimize>
+ <OutputPath>$(WebStackRootPath)\bin\Release\Test\</OutputPath>
+ <DefineConstants>TRACE</DefineConstants>
+ <CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)' == 'CodeCoverage' ">
+ <DebugSymbols>true</DebugSymbols>
+ <DebugType>full</DebugType>
+ <Optimize>false</Optimize>
+ <OutputPath>$(WebStackRootPath)\bin\CodeCoverage\Test\</OutputPath>
+ <DefineConstants>TRACE;DEBUG</DefineConstants>
+ <CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
+ </PropertyGroup>
+ <ItemGroup>
+ <Reference Include="Microsoft.CSharp" />
+ <Reference Include="System" />
+ <Reference Include="System.Web" />
+ <Reference Include="xunit">
+ <HintPath>..\..\packages\xunit.1.9.0.1566\lib\xunit.dll</HintPath>
+ </Reference>
+ <Reference Include="xunit.extensions">
+ <HintPath>..\..\packages\xunit.extensions.1.9.0.1566\lib\xunit.extensions.dll</HintPath>
+ </Reference>
+ </ItemGroup>
+ <ItemGroup>
+ <Compile Include="AssemblyUtilsTest.cs">
+ <SubType>Code</SubType>
+ </Compile>
+ <Compile Include="DeploymentUtil.cs" />
+ <Compile Include="PreApplicationStartCodeTest.cs" />
+ <Compile Include="TestFileSystem.cs" />
+ <Compile Include="Properties\AssemblyInfo.cs" />
+ <Compile Include="WebPagesDeploymentTest.cs">
+ <SubType>Code</SubType>
+ </Compile>
+ </ItemGroup>
+ <ItemGroup>
+ <ProjectReference Include="..\..\src\System.Web.WebPages.Deployment\System.Web.WebPages.Deployment.csproj">
+ <Project>{22BABB60-8F02-4027-AFFC-ACF069954536}</Project>
+ <Name>System.Web.WebPages.Deployment</Name>
+ </ProjectReference>
+ <ProjectReference Include="..\Microsoft.TestCommon\Microsoft.TestCommon.csproj">
+ <Project>{FCCC4CB7-BAF7-4A57-9F89-E5766FE536C0}</Project>
+ <Name>Microsoft.TestCommon</Name>
+ </ProjectReference>
+ </ItemGroup>
+ <ItemGroup>
+ <None Include="App.Config" />
+ <None Include="packages.config" />
+ </ItemGroup>
+ <ItemGroup>
+ <EmbeddedResource Include="TestFiles\ConfigTestAssemblies\V2_Signed\System.Web.WebPages.Deployment.dll" />
+ <EmbeddedResource Include="TestFiles\ConfigTestAssemblies\V2_Unsigned\System.Web.WebPages.Deployment.dll" />
+ <EmbeddedResource Include="TestFiles\ConfigTestSites\CshtmlFileConfigV1\Default.cshtml" />
+ <EmbeddedResource Include="TestFiles\ConfigTestSites\CshtmlFileConfigV1\web.config" />
+ <EmbeddedResource Include="TestFiles\ConfigTestSites\CshtmlFileNoVersion\Default.cshtml" />
+ <EmbeddedResource Include="TestFiles\ConfigTestSites\NoCshtml\Default.htm" />
+ <EmbeddedResource Include="TestFiles\ConfigTestSites\NoCshtmlConfigV1\Default.htm" />
+ <EmbeddedResource Include="TestFiles\ConfigTestSites\NoCshtmlConfigV1\web.config" />
+ <EmbeddedResource Include="TestFiles\ConfigTestSites\NoCshtmlNoConfigSetting\Default.htm" />
+ <EmbeddedResource Include="TestFiles\ConfigTestSites\NoCshtmlNoConfigSetting\web.config" />
+ <EmbeddedResource Include="TestFiles\ConfigTestSites\NoCshtmlWithEnabledSetting\Default.htm" />
+ <EmbeddedResource Include="TestFiles\ConfigTestSites\NoCshtmlWithEnabledSetting\web.config" />
+ <EmbeddedResource Include="TestFiles\ConfigTestSites\NoCshtmlWithEnabledSettingFalse\Default.htm" />
+ <EmbeddedResource Include="TestFiles\ConfigTestSites\NoCshtmlWithEnabledSettingFalse\web.config" />
+ </ItemGroup>
+ <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
+</Project> \ No newline at end of file
diff --git a/test/System.Web.WebPages.Deployment.Test/TestFileSystem.cs b/test/System.Web.WebPages.Deployment.Test/TestFileSystem.cs
new file mode 100644
index 00000000..09263d14
--- /dev/null
+++ b/test/System.Web.WebPages.Deployment.Test/TestFileSystem.cs
@@ -0,0 +1,50 @@
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using Microsoft.Internal.Web.Utils;
+
+namespace System.Web.WebPages.Deployment.Test
+{
+ public class TestFileSystem : IFileSystem
+ {
+ private readonly Dictionary<string, MemoryStream> _files = new Dictionary<string, MemoryStream>(StringComparer.OrdinalIgnoreCase);
+
+ public void AddFile(string file, MemoryStream content = null)
+ {
+ content = content ?? new MemoryStream();
+ _files[file] = content;
+ }
+
+ public bool FileExists(string path)
+ {
+ return _files.ContainsKey(path);
+ }
+
+ public Stream ReadFile(string path)
+ {
+ return _files[path];
+ }
+
+ public Stream OpenFile(string path)
+ {
+ MemoryStream memoryStream;
+ if (_files.TryGetValue(path, out memoryStream))
+ {
+ var copiedStream = new MemoryStream(memoryStream.ToArray());
+ _files[path] = copiedStream;
+ }
+ else
+ {
+ AddFile(path);
+ }
+ return _files[path];
+ }
+
+ public IEnumerable<string> EnumerateFiles(string path)
+ {
+ return from file in _files.Keys
+ where Path.GetDirectoryName(file).Equals(path, StringComparison.OrdinalIgnoreCase)
+ select file;
+ }
+ }
+}
diff --git a/test/System.Web.WebPages.Deployment.Test/TestFiles/ConfigTestAssemblies/V2_Signed/System.Web.WebPages.Deployment.dll b/test/System.Web.WebPages.Deployment.Test/TestFiles/ConfigTestAssemblies/V2_Signed/System.Web.WebPages.Deployment.dll
new file mode 100644
index 00000000..d219ec85
--- /dev/null
+++ b/test/System.Web.WebPages.Deployment.Test/TestFiles/ConfigTestAssemblies/V2_Signed/System.Web.WebPages.Deployment.dll
Binary files differ
diff --git a/test/System.Web.WebPages.Deployment.Test/TestFiles/ConfigTestAssemblies/V2_Unsigned/System.Web.WebPages.Deployment.dll b/test/System.Web.WebPages.Deployment.Test/TestFiles/ConfigTestAssemblies/V2_Unsigned/System.Web.WebPages.Deployment.dll
new file mode 100644
index 00000000..ced14804
--- /dev/null
+++ b/test/System.Web.WebPages.Deployment.Test/TestFiles/ConfigTestAssemblies/V2_Unsigned/System.Web.WebPages.Deployment.dll
Binary files differ
diff --git a/test/System.Web.WebPages.Deployment.Test/TestFiles/ConfigTestSites/CshtmlFileConfigV1/Default.cshtml b/test/System.Web.WebPages.Deployment.Test/TestFiles/ConfigTestSites/CshtmlFileConfigV1/Default.cshtml
new file mode 100644
index 00000000..8557d604
--- /dev/null
+++ b/test/System.Web.WebPages.Deployment.Test/TestFiles/ConfigTestSites/CshtmlFileConfigV1/Default.cshtml
@@ -0,0 +1,9 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
+<html>
+ <head>
+ <title></title>
+ </head>
+ <body>
+ Not a plan9 app!
+ </body>
+</html> \ No newline at end of file
diff --git a/test/System.Web.WebPages.Deployment.Test/TestFiles/ConfigTestSites/CshtmlFileConfigV1/web.config b/test/System.Web.WebPages.Deployment.Test/TestFiles/ConfigTestSites/CshtmlFileConfigV1/web.config
new file mode 100644
index 00000000..2c83098e
--- /dev/null
+++ b/test/System.Web.WebPages.Deployment.Test/TestFiles/ConfigTestSites/CshtmlFileConfigV1/web.config
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<configuration>
+ <appSettings>
+ <add key="webpages:Version" value="1.0" />
+ </appSettings>
+</configuration> \ No newline at end of file
diff --git a/test/System.Web.WebPages.Deployment.Test/TestFiles/ConfigTestSites/CshtmlFileNoVersion/Default.cshtml b/test/System.Web.WebPages.Deployment.Test/TestFiles/ConfigTestSites/CshtmlFileNoVersion/Default.cshtml
new file mode 100644
index 00000000..8557d604
--- /dev/null
+++ b/test/System.Web.WebPages.Deployment.Test/TestFiles/ConfigTestSites/CshtmlFileNoVersion/Default.cshtml
@@ -0,0 +1,9 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
+<html>
+ <head>
+ <title></title>
+ </head>
+ <body>
+ Not a plan9 app!
+ </body>
+</html> \ No newline at end of file
diff --git a/test/System.Web.WebPages.Deployment.Test/TestFiles/ConfigTestSites/NoCshtml/Default.htm b/test/System.Web.WebPages.Deployment.Test/TestFiles/ConfigTestSites/NoCshtml/Default.htm
new file mode 100644
index 00000000..8557d604
--- /dev/null
+++ b/test/System.Web.WebPages.Deployment.Test/TestFiles/ConfigTestSites/NoCshtml/Default.htm
@@ -0,0 +1,9 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
+<html>
+ <head>
+ <title></title>
+ </head>
+ <body>
+ Not a plan9 app!
+ </body>
+</html> \ No newline at end of file
diff --git a/test/System.Web.WebPages.Deployment.Test/TestFiles/ConfigTestSites/NoCshtmlConfigv1/Default.htm b/test/System.Web.WebPages.Deployment.Test/TestFiles/ConfigTestSites/NoCshtmlConfigv1/Default.htm
new file mode 100644
index 00000000..8557d604
--- /dev/null
+++ b/test/System.Web.WebPages.Deployment.Test/TestFiles/ConfigTestSites/NoCshtmlConfigv1/Default.htm
@@ -0,0 +1,9 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
+<html>
+ <head>
+ <title></title>
+ </head>
+ <body>
+ Not a plan9 app!
+ </body>
+</html> \ No newline at end of file
diff --git a/test/System.Web.WebPages.Deployment.Test/TestFiles/ConfigTestSites/NoCshtmlConfigv1/web.config b/test/System.Web.WebPages.Deployment.Test/TestFiles/ConfigTestSites/NoCshtmlConfigv1/web.config
new file mode 100644
index 00000000..13c15c85
--- /dev/null
+++ b/test/System.Web.WebPages.Deployment.Test/TestFiles/ConfigTestSites/NoCshtmlConfigv1/web.config
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<configuration>
+ <appSettings>
+ <add key="webPages:version" value="1.0" />
+ </appSettings>
+</configuration> \ No newline at end of file
diff --git a/test/System.Web.WebPages.Deployment.Test/TestFiles/ConfigTestSites/NoCshtmlNoConfigSetting/Default.htm b/test/System.Web.WebPages.Deployment.Test/TestFiles/ConfigTestSites/NoCshtmlNoConfigSetting/Default.htm
new file mode 100644
index 00000000..8557d604
--- /dev/null
+++ b/test/System.Web.WebPages.Deployment.Test/TestFiles/ConfigTestSites/NoCshtmlNoConfigSetting/Default.htm
@@ -0,0 +1,9 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
+<html>
+ <head>
+ <title></title>
+ </head>
+ <body>
+ Not a plan9 app!
+ </body>
+</html> \ No newline at end of file
diff --git a/test/System.Web.WebPages.Deployment.Test/TestFiles/ConfigTestSites/NoCshtmlNoConfigSetting/web.config b/test/System.Web.WebPages.Deployment.Test/TestFiles/ConfigTestSites/NoCshtmlNoConfigSetting/web.config
new file mode 100644
index 00000000..49cc43e1
--- /dev/null
+++ b/test/System.Web.WebPages.Deployment.Test/TestFiles/ConfigTestSites/NoCshtmlNoConfigSetting/web.config
@@ -0,0 +1,3 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<configuration>
+</configuration> \ No newline at end of file
diff --git a/test/System.Web.WebPages.Deployment.Test/TestFiles/ConfigTestSites/NoCshtmlWithEnabledSetting/Default.htm b/test/System.Web.WebPages.Deployment.Test/TestFiles/ConfigTestSites/NoCshtmlWithEnabledSetting/Default.htm
new file mode 100644
index 00000000..8557d604
--- /dev/null
+++ b/test/System.Web.WebPages.Deployment.Test/TestFiles/ConfigTestSites/NoCshtmlWithEnabledSetting/Default.htm
@@ -0,0 +1,9 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
+<html>
+ <head>
+ <title></title>
+ </head>
+ <body>
+ Not a plan9 app!
+ </body>
+</html> \ No newline at end of file
diff --git a/test/System.Web.WebPages.Deployment.Test/TestFiles/ConfigTestSites/NoCshtmlWithEnabledSetting/web.config b/test/System.Web.WebPages.Deployment.Test/TestFiles/ConfigTestSites/NoCshtmlWithEnabledSetting/web.config
new file mode 100644
index 00000000..56151471
--- /dev/null
+++ b/test/System.Web.WebPages.Deployment.Test/TestFiles/ConfigTestSites/NoCshtmlWithEnabledSetting/web.config
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<configuration>
+ <appSettings>
+ <add key="webpages:Enabled" value="true" />
+ </appSettings>
+</configuration> \ No newline at end of file
diff --git a/test/System.Web.WebPages.Deployment.Test/TestFiles/ConfigTestSites/NoCshtmlWithEnabledSettingFalse/Default.htm b/test/System.Web.WebPages.Deployment.Test/TestFiles/ConfigTestSites/NoCshtmlWithEnabledSettingFalse/Default.htm
new file mode 100644
index 00000000..8557d604
--- /dev/null
+++ b/test/System.Web.WebPages.Deployment.Test/TestFiles/ConfigTestSites/NoCshtmlWithEnabledSettingFalse/Default.htm
@@ -0,0 +1,9 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
+<html>
+ <head>
+ <title></title>
+ </head>
+ <body>
+ Not a plan9 app!
+ </body>
+</html> \ No newline at end of file
diff --git a/test/System.Web.WebPages.Deployment.Test/TestFiles/ConfigTestSites/NoCshtmlWithEnabledSettingFalse/web.config b/test/System.Web.WebPages.Deployment.Test/TestFiles/ConfigTestSites/NoCshtmlWithEnabledSettingFalse/web.config
new file mode 100644
index 00000000..e8364c20
--- /dev/null
+++ b/test/System.Web.WebPages.Deployment.Test/TestFiles/ConfigTestSites/NoCshtmlWithEnabledSettingFalse/web.config
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<configuration>
+ <appSettings>
+ <add key="webpages:Enabled" value="false" />
+ </appSettings>
+</configuration> \ No newline at end of file
diff --git a/test/System.Web.WebPages.Deployment.Test/WebPagesDeploymentTest.cs b/test/System.Web.WebPages.Deployment.Test/WebPagesDeploymentTest.cs
new file mode 100644
index 00000000..7deabc85
--- /dev/null
+++ b/test/System.Web.WebPages.Deployment.Test/WebPagesDeploymentTest.cs
@@ -0,0 +1,303 @@
+using System.Collections.Generic;
+using System.Collections.Specialized;
+using System.IO;
+using System.Reflection;
+using System.Web.WebPages.TestUtils;
+using Xunit;
+using Xunit.Extensions;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.WebPages.Deployment.Test
+{
+ // We need to mark this type Serializable for TDD.Net to work with some of the AppDomain tests
+ [Serializable]
+ public class WebPagesDeploymentTest : IDisposable
+ {
+ private const string TestNamespacePrefix = "System.Web.WebPages.Deployment.Test.TestFiles.";
+ private static readonly Version MaxVersion = new Version(2, 0, 0, 0);
+
+ private static readonly IDictionary<string, string> _deploymentPaths = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
+ {
+ { @"ConfigTestSites.CshtmlFileNoVersion.Default.cshtml", @"ConfigTestSites\CshtmlFileNoVersion\Default.cshtml" },
+ { @"ConfigTestSites.NoCshtmlWithEnabledSetting.Default.htm", @"ConfigTestSites\NoCshtmlWithEnabledSetting\Default.htm" },
+ { @"ConfigTestSites.NoCshtmlNoConfigSetting.web.config", @"ConfigTestSites\NoCshtmlNoConfigSetting\web.config" },
+ { @"ConfigTestSites.NoCshtmlWithEnabledSetting.web.config", @"ConfigTestSites\NoCshtmlWithEnabledSetting\web.config" },
+ { @"ConfigTestSites.NoCshtmlWithEnabledSettingFalse.Default.htm", @"ConfigTestSites\NoCshtmlWithEnabledSettingFalse\Default.htm" },
+ { @"ConfigTestAssemblies.V2_Unsigned.System.Web.WebPages.Deployment.dll", @"ConfigTestAssemblies\V2_Unsigned\System.Web.WebPages.Deployment.dll" },
+ { @"ConfigTestAssemblies.V2_Signed.System.Web.WebPages.Deployment.dll", @"ConfigTestAssemblies\V2_Signed\System.Web.WebPages.Deployment.dll" },
+ { @"ConfigTestSites.NoCshtmlWithEnabledSettingFalse.web.config", @"ConfigTestSites\NoCshtmlWithEnabledSettingFalse\web.config" },
+ { @"ConfigTestSites.CshtmlFileConfigV1.Default.cshtml", @"ConfigTestSites\CshtmlFileConfigV1\Default.cshtml" },
+ { @"ConfigTestSites.NoCshtml.Default.htm", @"ConfigTestSites\NoCshtml\Default.htm" },
+ { @"ConfigTestSites.NoCshtmlNoConfigSetting.Default.htm", @"ConfigTestSites\NoCshtmlNoConfigSetting\Default.htm" },
+ { @"ConfigTestSites.CshtmlFileConfigV1.web.config", @"ConfigTestSites\CshtmlFileConfigV1\web.config" },
+ { @"ConfigTestSites.NoCshtmlConfigV1.Default.htm", @"ConfigTestSites\NoCshtmlConfigV1\Default.htm" },
+ { @"ConfigTestSites.NoCshtmlConfigV1.web.config", @"ConfigTestSites\NoCshtmlConfigV1\web.config" },
+ };
+
+ private readonly string _tempPath = GetTempPath();
+
+ public WebPagesDeploymentTest()
+ {
+ var assembly = typeof(WebPagesDeploymentTest).Assembly;
+ foreach (var item in _deploymentPaths)
+ {
+ new TestFile(TestNamespacePrefix + item.Key, assembly).Save(Path.Combine(_tempPath, item.Value));
+ }
+ }
+
+ public void Dispose()
+ {
+ try
+ {
+ Directory.Delete(_tempPath, recursive: true);
+ }
+ catch
+ {
+
+ }
+ }
+
+ [Fact]
+ public void IsEnabledReturnsFalseIfNoCshtmlOrConfigFile()
+ {
+ Assert.False(WebPagesDeployment.IsEnabled(Path.Combine(_tempPath, @"ConfigTestSites\NoCshtml")));
+ }
+
+ [Fact]
+ public void IsEnabledReturnsFalseIfNoCshtmlAndNoConfigSetting()
+ {
+ Assert.False(WebPagesDeployment.IsEnabled(Path.Combine(_tempPath, @"ConfigTestSites\NoCshtmlNoConfigSetting")));
+ }
+
+ [Fact]
+ public void IsEnabledReturnsTrueIfNoCshtmlAndEnabledConfigSetting()
+ {
+ Assert.True(WebPagesDeployment.IsEnabled(Path.Combine(_tempPath, @"ConfigTestSites\NoCshtmlWithEnabledSetting")));
+ }
+
+ [Fact]
+ public void IsEnabledReturnsTrueIfCshtmlFilePresent()
+ {
+ Assert.True(WebPagesDeployment.IsEnabled(Path.Combine(_tempPath, @"ConfigTestSites\CshtmlFileNoVersion")));
+ }
+
+ [Fact]
+ public void IsExplicitlyDisabledReturnsTrueIfNoCshtmlAndEnabledConfigSettingSetToFalse()
+ {
+ Assert.True(WebPagesDeployment.IsExplicitlyDisabled(Path.Combine(_tempPath, @"ConfigTestSites\NoCshtmlWithEnabledSettingFalse")));
+ }
+
+ [Fact]
+ public void IsExplicitlyDisabledReturnsFalseIfNoCshtmlAndEnabledConfigSettingSetToTrue()
+ {
+ Assert.False(WebPagesDeployment.IsExplicitlyDisabled(Path.Combine(_tempPath, @"ConfigTestSites\NoCshtmlWithEnabledSetting")));
+ }
+
+ [Fact]
+ public void IsExplicitlyDisabledReturnsFalseIfNoCshtmlAndNoConfigSetting()
+ {
+ Assert.False(WebPagesDeployment.IsExplicitlyDisabled(Path.Combine(_tempPath, @"ConfigTestSites\NoCshtmlNoConfigSetting")));
+ }
+
+ [Fact]
+ public void IsExplicitlyDisabledReturnsFalseIfNoCshtmlOrConfigFile()
+ {
+ Assert.False(WebPagesDeployment.IsExplicitlyDisabled(Path.Combine(_tempPath, @"ConfigTestSites\NoCshtml")));
+ }
+
+ [Fact]
+ public void GetVersionReturnsValueFromAppSettingsIfNotExplicitlyDisabled()
+ {
+ // Arrange
+ var version = "1.2.3.4";
+ var appSettings = new NameValueCollection { { "webPages:Version", version } };
+ var maxVersion = new Version("2.0");
+
+ // Act
+ var actualVersion = WebPagesDeployment.GetVersionInternal(appSettings, binVersion: null, defaultVersion: null);
+
+ // Assert
+ Assert.Equal(new Version(version), actualVersion);
+ }
+
+ [Fact]
+ public void GetVersionReturnsValueEvenIfExplicitlyDisabled()
+ {
+ // Arrange
+ var version = "1.2.3.4";
+ var appSettings = new NameValueCollection { { "webPages:Version", version }, { "webPages:Enabled", "False" } };
+ var maxVersion = new Version("2.0");
+
+ // Act
+ var actualVersion = WebPagesDeployment.GetVersionInternal(appSettings, binVersion: null, defaultVersion: null);
+
+ // Assert
+ Assert.Equal(new Version(version), actualVersion);
+ }
+
+ [Fact]
+ public void GetVersionReturnsLowerVersionIfSpecifiedInConfig()
+ {
+ AppDomainUtils.RunInSeparateAppDomain(() =>
+ {
+ // Arrange - Load v2 Config
+ Assembly asm = Assembly.LoadFrom(Path.Combine(_tempPath, @"ConfigTestAssemblies\V2_Signed\System.Web.WebPages.Deployment.dll"));
+ Assert.Equal(new Version(2, 0, 0, 0), asm.GetName().Version);
+ Assert.Equal("System.Web.WebPages.Deployment", asm.GetName().Name);
+
+ using (WebUtils.CreateHttpRuntime(@"~\foo", "."))
+ {
+ // Act
+ Version ver = WebPagesDeployment.GetVersionWithoutEnabledCheck(Path.Combine(_tempPath, @"ConfigTestSites\CshtmlFileConfigV1"));
+
+ // Assert
+ Assert.Equal(new Version(1, 0, 0, 0), ver);
+ }
+ });
+ }
+
+ [Fact]
+ public void GetVersionReturnsLowerVersionIfSpecifiedInConfigAndNotExplicitlyDisabled()
+ {
+ AppDomainUtils.RunInSeparateAppDomain(() =>
+ {
+ // Arrange - Load v2 Config
+ Assembly asm = Assembly.LoadFrom(Path.Combine(_tempPath, @"ConfigTestAssemblies\V2_Signed\System.Web.WebPages.Deployment.dll"));
+ Assert.Equal(new Version(2, 0, 0, 0), asm.GetName().Version);
+ Assert.Equal("System.Web.WebPages.Deployment", asm.GetName().Name);
+
+ using (WebUtils.CreateHttpRuntime(@"~\foo", "."))
+ {
+ // Act
+ Version ver = WebPagesDeployment.GetVersionWithoutEnabledCheck(Path.Combine(_tempPath, @"ConfigTestSites\NoCshtmlConfigV1"));
+
+ // Assert
+ Assert.Equal(new Version(1, 0, 0, 0), ver);
+ }
+ });
+ }
+
+ [Fact]
+ public void GetVersionIgnoresUnsignedConfigDll()
+ {
+ AppDomainUtils.RunInSeparateAppDomain(() =>
+ {
+ // Arrange - Load v2 Config
+ Assembly asm = Assembly.LoadFrom(Path.Combine(_tempPath, @"ConfigTestAssemblies\V2_Unsigned\System.Web.WebPages.Deployment.dll"));
+ Assert.Equal(new Version(2, 0, 0, 0), asm.GetName().Version);
+ Assert.Equal("System.Web.WebPages.Deployment", asm.GetName().Name);
+
+ using (WebUtils.CreateHttpRuntime(@"~\foo", "."))
+ {
+ // Act
+ Version ver = WebPagesDeployment.GetVersionWithoutEnabledCheck(Path.Combine(_tempPath, @"ConfigTestSites\CshtmlFileNoVersion"));
+
+ // Assert
+ Assert.Equal(MaxVersion, ver);
+ }
+ });
+ }
+
+ [Fact]
+ public void GetVersionReturnsVMaxAssemblyVersionIfCshtmlFilePresent()
+ {
+ AppDomainUtils.RunInSeparateAppDomain(() =>
+ {
+ // Arrange - Load v2 Config
+ Assembly asm = Assembly.LoadFrom(Path.Combine(_tempPath, @"ConfigTestAssemblies\V2_Signed\System.Web.WebPages.Deployment.dll"));
+ Assert.Equal(new Version(2, 0, 0, 0), asm.GetName().Version);
+ Assert.Equal("System.Web.WebPages.Deployment", asm.GetName().Name);
+
+ using (WebUtils.CreateHttpRuntime(@"~\foo", "."))
+ {
+ // Act
+ Version ver = WebPagesDeployment.GetVersionWithoutEnabledCheck(Path.Combine(_tempPath, @"ConfigTestSites\CshtmlFileNoVersion"));
+
+ // Assert
+ Assert.Equal(MaxVersion, ver);
+ }
+ });
+ }
+
+ [Fact]
+ public void GetVersionThrowsIfPathNullOrEmpty()
+ {
+ Assert.ThrowsArgumentNullOrEmptyString(() => WebPagesDeployment.GetVersionWithoutEnabledCheck(null), "path");
+ Assert.ThrowsArgumentNullOrEmptyString(() => WebPagesDeployment.GetVersionWithoutEnabledCheck(String.Empty), "path");
+ }
+
+ [Fact]
+ public void IsEnabledThrowsIfPathNullOrEmpty()
+ {
+ Assert.ThrowsArgumentNullOrEmptyString(() => WebPagesDeployment.IsEnabled(null), "path");
+ Assert.ThrowsArgumentNullOrEmptyString(() => WebPagesDeployment.IsEnabled(String.Empty), "path");
+ }
+
+ [Theory]
+ [InlineData(new object[] { null })]
+ [InlineData(new object[] { "" })]
+ public void ObsoleteGetVersionThrowsIfPathIsNullOrEmpty(string path)
+ {
+ // Arrange
+ var fileSystem = new TestFileSystem();
+ var configuration = new NameValueCollection();
+
+ // Act and Assert
+ Assert.ThrowsArgumentNullOrEmptyString(() => WebPagesDeployment.GetObsoleteVersionInternal(path, configuration, fileSystem, () => new Version("2.0.0.0")), "path");
+ }
+
+ [Fact]
+ public void ObsoleteGetVersionReturnsNullIfNoFilesInTheSite()
+ {
+ // Arrange
+ var path = "blah";
+ var fileSystem = new TestFileSystem();
+ var configuration = new NameValueCollection();
+
+ // Act
+ var version = WebPagesDeployment.GetObsoleteVersionInternal(path, configuration, fileSystem, () => new Version("2.0.0.0"));
+
+ // Assert
+ Assert.Null(version);
+ }
+
+ [Fact]
+ public void ObsoleteGetVersionReturnsMaxVersionIfNoValueInConfigNoFilesInBinSiteContainsCshtmlFiles()
+ {
+ // Arrange
+ var path = "blah";
+ var fileSystem = new TestFileSystem();
+ fileSystem.AddFile(@"blah\Foo.cshtml");
+ var configuration = new NameValueCollection();
+
+ // Act
+ var version = WebPagesDeployment.GetObsoleteVersionInternal(path, configuration, fileSystem, () => new Version("2.0.0.0"));
+
+ // Assert
+ Assert.Equal(new Version("2.0.0.0"), version);
+ }
+
+ [Fact]
+ public void ObsoleteGetVersionReturnsVersionFromConfigIfDisabled()
+ {
+ // Arrange
+ var maxVersion = new Version("2.1.3.4");
+ var fileSystem = new TestFileSystem();
+ var configuration = new NameValueCollection();
+ configuration["webPages:Enabled"] = "False";
+ configuration["webPages:Version"] = "2.0";
+ var path = "blah";
+
+ // Act
+ var version = WebPagesDeployment.GetObsoleteVersionInternal(path, configuration, fileSystem, () => maxVersion);
+
+ // Assert
+ Assert.Equal(new Version("2.0.0.0"), version);
+ }
+
+ private static string GetTempPath()
+ {
+ return Path.Combine(Path.GetTempPath(), Path.GetRandomFileName());
+ }
+ }
+}
diff --git a/test/System.Web.WebPages.Deployment.Test/packages.config b/test/System.Web.WebPages.Deployment.Test/packages.config
new file mode 100644
index 00000000..d82739c0
--- /dev/null
+++ b/test/System.Web.WebPages.Deployment.Test/packages.config
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<packages>
+ <package id="xunit" version="1.9.0.1566" />
+ <package id="xunit.extensions" version="1.9.0.1566" />
+</packages> \ No newline at end of file
diff --git a/test/System.Web.WebPages.Razor.Test/PreApplicationStartCodeTest.cs b/test/System.Web.WebPages.Razor.Test/PreApplicationStartCodeTest.cs
new file mode 100644
index 00000000..228b0bc5
--- /dev/null
+++ b/test/System.Web.WebPages.Razor.Test/PreApplicationStartCodeTest.cs
@@ -0,0 +1,28 @@
+using System.Reflection;
+using System.Web.Compilation;
+using System.Web.WebPages.TestUtils;
+using Xunit;
+
+namespace System.Web.WebPages.Razor.Test
+{
+ public class PreApplicationStartCodeTest
+ {
+ [Fact]
+ public void StartTest()
+ {
+ AppDomainUtils.RunInSeparateAppDomain(() =>
+ {
+ AppDomainUtils.SetPreAppStartStage();
+ PreApplicationStartCode.Start();
+ var buildProviders = typeof(BuildProvider).GetField("s_dynamicallyRegisteredProviders", BindingFlags.Static | BindingFlags.NonPublic).GetValue(null);
+ Assert.Equal(2, buildProviders.GetType().GetProperty("Count", BindingFlags.Public | BindingFlags.Instance).GetValue(buildProviders, new object[] { }));
+ });
+ }
+
+ [Fact]
+ public void TestPreAppStartClass()
+ {
+ PreAppStartTestHelper.TestPreAppStartClass(typeof(PreApplicationStartCode));
+ }
+ }
+}
diff --git a/test/System.Web.WebPages.Razor.Test/Properties/AssemblyInfo.cs b/test/System.Web.WebPages.Razor.Test/Properties/AssemblyInfo.cs
new file mode 100644
index 00000000..74ed3ab1
--- /dev/null
+++ b/test/System.Web.WebPages.Razor.Test/Properties/AssemblyInfo.cs
@@ -0,0 +1,34 @@
+using System.Reflection;
+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("System.Web.WebPages.Razor.Test")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("Microsoft")]
+[assembly: AssemblyProduct("System.Web.WebPages.Razor.Test")]
+[assembly: AssemblyCopyright("Copyright © Microsoft 2009")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Setting ComVisible to false makes the types in this assembly not visible
+// to COM componenets. If you need to access a type in this assembly from
+// COM, set the ComVisible attribute to true on that type.
+
+[assembly: ComVisible(false)]
+
+// 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 Revision and Build Numbers
+// by using the '*' as shown below:
+
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
diff --git a/test/System.Web.WebPages.Razor.Test/RazorBuildProviderTest.cs b/test/System.Web.WebPages.Razor.Test/RazorBuildProviderTest.cs
new file mode 100644
index 00000000..cea1ff23
--- /dev/null
+++ b/test/System.Web.WebPages.Razor.Test/RazorBuildProviderTest.cs
@@ -0,0 +1,248 @@
+using System.CodeDom;
+using System.CodeDom.Compiler;
+using System.Collections;
+using System.IO;
+using System.Linq;
+using System.Reflection;
+using System.Web.Compilation;
+using System.Web.WebPages.TestUtils;
+using ASP;
+using Microsoft.CSharp;
+using Moq;
+using Xunit;
+
+namespace ASP
+{
+ public class _Page_Foo_Test_cshtml
+ {
+ }
+}
+
+namespace System.Web.WebPages.Razor.Test
+{
+ public class RazorBuildProviderTest
+ {
+ private class MockAssemblyBuilder : IAssemblyBuilder
+ {
+ public BuildProvider BuildProvider { get; private set; }
+ public CodeCompileUnit CompileUnit { get; private set; }
+ public string LastTypeFactoryGenerated { get; private set; }
+
+ public void AddCodeCompileUnit(BuildProvider buildProvider, CodeCompileUnit compileUnit)
+ {
+ BuildProvider = buildProvider;
+ CompileUnit = compileUnit;
+ }
+
+ public void GenerateTypeFactory(string typeName)
+ {
+ LastTypeFactoryGenerated = typeName;
+ }
+ }
+
+ [Fact]
+ public void CodeCompilerTypeReturnsTypeFromCodeLanguage()
+ {
+ // Arrange
+ WebPageRazorHost host = new WebPageRazorHost("~/Foo/Baz.cshtml", @"C:\Foo\Baz.cshtml");
+ RazorBuildProvider provider = CreateBuildProvider("foo @bar baz");
+ provider.Host = host;
+
+ // Act
+ CompilerType type = provider.CodeCompilerType;
+
+ // Assert
+ Assert.Equal(typeof(CSharpCodeProvider), type.CodeDomProviderType);
+ }
+
+ [Fact]
+ public void CodeCompilerTypeSetsDebugFlagInFullTrust()
+ {
+ // Arrange
+ WebPageRazorHost host = new WebPageRazorHost("~/Foo/Baz.cshtml", @"C:\Foo\Baz.cshtml");
+ RazorBuildProvider provider = CreateBuildProvider("foo @bar baz");
+ provider.Host = host;
+
+ // Act
+ CompilerType type = provider.CodeCompilerType;
+
+ // Assert
+ Assert.True(type.CompilerParameters.IncludeDebugInformation);
+ }
+
+ [Fact]
+ public void GetGeneratedTypeUsesNameAndNamespaceFromHostToExtractType()
+ {
+ // Arrange
+ WebPageRazorHost host = new WebPageRazorHost("~/Foo/Test.cshtml", @"C:\Foo\Test.cshtml");
+ RazorBuildProvider provider = new RazorBuildProvider() { Host = host };
+ CompilerResults results = new CompilerResults(new TempFileCollection());
+ results.CompiledAssembly = typeof(_Page_Foo_Test_cshtml).Assembly;
+
+ // Act
+ Type typ = provider.GetGeneratedType(results);
+
+ // Assert
+ Assert.Equal(typeof(_Page_Foo_Test_cshtml), typ);
+ }
+
+ [Fact]
+ public void GenerateCodeCoreAddsGeneratedCodeToAssemblyBuilder()
+ {
+ // Arrange
+ WebPageRazorHost host = new WebPageRazorHost("~/Foo/Baz.cshtml", @"C:\Foo\Baz.cshtml");
+ RazorBuildProvider provider = new RazorBuildProvider();
+ CodeCompileUnit ccu = new CodeCompileUnit();
+ MockAssemblyBuilder asmBuilder = new MockAssemblyBuilder();
+ provider.Host = host;
+ provider.GeneratedCode = ccu;
+
+ // Act
+ provider.GenerateCodeCore(asmBuilder);
+
+ // Assert
+ Assert.Same(provider, asmBuilder.BuildProvider);
+ Assert.Same(ccu, asmBuilder.CompileUnit);
+ Assert.Equal("ASP._Page_Foo_Baz_cshtml", asmBuilder.LastTypeFactoryGenerated);
+ }
+
+ [Fact]
+ public void CodeGenerationStartedTest()
+ {
+ // Arrange
+ WebPageRazorHost host = new WebPageRazorHost("~/Foo/Baz.cshtml", @"C:\Foo\Baz.cshtml");
+ RazorBuildProvider provider = CreateBuildProvider("foo @bar baz");
+ provider.Host = host;
+
+ // Expected original base dependencies
+ var baseDependencies = new ArrayList();
+ baseDependencies.Add("/Samples/Foo/Baz.cshtml");
+
+ // Expected list of dependencies after GenerateCode is called
+ var dependencies = new ArrayList();
+ dependencies.Add(baseDependencies[0]);
+ dependencies.Add("/Samples/Foo/Foo.cshtml");
+
+ // Set up the event handler
+ provider.CodeGenerationStartedInternal += (sender, e) =>
+ {
+ var bp = sender as RazorBuildProvider;
+ bp.AddVirtualPathDependency("/Samples/Foo/Foo.cshtml");
+ };
+
+ // Set up the base dependency
+ MockAssemblyBuilder builder = new MockAssemblyBuilder();
+ typeof(BuildProvider).GetField("_virtualPath", BindingFlags.NonPublic | BindingFlags.Instance).SetValue(provider, CreateVirtualPath("/Samples/Foo/Baz.cshtml"));
+
+ // Test that VirtualPathDependencies returns the original dependency before GenerateCode is called
+ Assert.True(baseDependencies.OfType<string>().SequenceEqual(provider.VirtualPathDependencies.OfType<string>()));
+
+ // Act
+ provider.GenerateCodeCore(builder);
+
+ // Assert
+ Assert.NotNull(provider.AssemblyBuilderInternal);
+ Assert.Equal(builder, provider.AssemblyBuilderInternal);
+ Assert.True(dependencies.OfType<string>().SequenceEqual(provider.VirtualPathDependencies.OfType<string>()));
+ Assert.Equal("/Samples/Foo/Baz.cshtml", provider.VirtualPath);
+ }
+
+ [Fact]
+ public void AfterGeneratedCodeEventGetsExecutedAtCorrectTime()
+ {
+ // Arrange
+ WebPageRazorHost host = new WebPageRazorHost("~/Foo/Baz.cshtml", @"C:\Foo\Baz.cshtml");
+ RazorBuildProvider provider = CreateBuildProvider("foo @bar baz");
+ provider.Host = host;
+
+ provider.CodeGenerationCompletedInternal += (sender, e) =>
+ {
+ Assert.Equal("~/Foo/Baz.cshtml", e.VirtualPath);
+ e.GeneratedCode.Namespaces.Add(new CodeNamespace("DummyNamespace"));
+ };
+
+ // Act
+ CodeCompileUnit generated = provider.GeneratedCode;
+
+ // Assert
+ Assert.NotNull(generated.Namespaces
+ .OfType<CodeNamespace>()
+ .SingleOrDefault(ns => String.Equals(ns.Name, "DummyNamespace")));
+ }
+
+ [Fact]
+ public void GeneratedCodeThrowsHttpParseExceptionForLastParserError()
+ {
+ // Arrange
+ WebPageRazorHost host = new WebPageRazorHost("~/Foo/Baz.cshtml", @"C:\Foo\Baz.cshtml");
+ RazorBuildProvider provider = CreateBuildProvider("foo @{ if( baz");
+ provider.Host = host;
+
+ // Act
+ Assert.Throws<HttpParseException>(() => { CodeCompileUnit ccu = provider.GeneratedCode; });
+ }
+
+ [Fact]
+ public void BuildProviderFiresEventToAlterHostBeforeBuildingPath()
+ {
+ // Arrange
+ WebPageRazorHost expected = new TestHost("~/Foo/Boz.cshtml", @"C:\Foo\Boz.cshtml");
+ WebPageRazorHost expectedBefore = new WebPageRazorHost("~/Foo/Baz.cshtml", @"C:\Foo\Baz.cshtml");
+ RazorBuildProvider provider = CreateBuildProvider("foo");
+ typeof(BuildProvider).GetField("_virtualPath", BindingFlags.NonPublic | BindingFlags.Instance).SetValue(provider, CreateVirtualPath("/Samples/Foo/Baz.cshtml"));
+ Mock.Get(provider).Setup(p => p.GetHostFromConfig()).Returns(expectedBefore);
+ bool called = false;
+ EventHandler<CompilingPathEventArgs> handler = (sender, args) =>
+ {
+ Assert.Equal("/Samples/Foo/Baz.cshtml", args.VirtualPath);
+ Assert.Same(expectedBefore, args.Host);
+ args.Host = expected;
+ called = true;
+ };
+ RazorBuildProvider.CompilingPath += handler;
+
+ try
+ {
+ // Act
+ CodeCompileUnit ccu = provider.GeneratedCode;
+
+ // Assert
+ Assert.Equal("Test", ccu.Namespaces[0].Name);
+ Assert.Same(expected, provider.Host);
+ Assert.True(called);
+ }
+ finally
+ {
+ RazorBuildProvider.CompilingPath -= handler;
+ }
+ }
+
+ private static object CreateVirtualPath(string path)
+ {
+ var vPath = typeof(BuildProvider).Assembly.GetType("System.Web.VirtualPath");
+ var method = vPath.GetMethod("CreateNonRelative", BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public);
+ return method.Invoke(null, new object[] { path });
+ }
+
+ private static RazorBuildProvider CreateBuildProvider(string razorContent)
+ {
+ Mock<RazorBuildProvider> mockProvider = new Mock<RazorBuildProvider>()
+ {
+ CallBase = true
+ };
+ mockProvider.Setup(p => p.InternalOpenReader())
+ .Returns(() => new StringReader(razorContent));
+ return mockProvider.Object;
+ }
+
+ private class TestHost : WebPageRazorHost
+ {
+ public TestHost(string virtualPath, string physicalPath) : base(virtualPath, physicalPath) { }
+
+ public override void PostProcessGeneratedCode(Web.Razor.Generator.CodeGeneratorContext context)
+ {
+ context.CompileUnit.Namespaces.Insert(0, new CodeNamespace("Test"));
+ }
+ }
+ }
+}
diff --git a/test/System.Web.WebPages.Razor.Test/System.Web.WebPages.Razor.Test.csproj b/test/System.Web.WebPages.Razor.Test/System.Web.WebPages.Razor.Test.csproj
new file mode 100644
index 00000000..9c798da9
--- /dev/null
+++ b/test/System.Web.WebPages.Razor.Test/System.Web.WebPages.Razor.Test.csproj
@@ -0,0 +1,92 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory),Runtime.sln))\tools\WebStack.settings.targets" />
+ <PropertyGroup>
+ <!-- Temporarily disable Obsolete Warnings as Errors -->
+ <WarningsNotAsErrors>618</WarningsNotAsErrors>
+ <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+ <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+ <ProductVersion>9.0.30729</ProductVersion>
+ <SchemaVersion>2.0</SchemaVersion>
+ <ProjectGuid>{66A74F3C-A106-4C1E-BAA0-001908FEA2CA}</ProjectGuid>
+ <OutputType>Library</OutputType>
+ <AppDesignerFolder>Properties</AppDesignerFolder>
+ <RootNamespace>System.Web.WebPages.Razor.Test</RootNamespace>
+ <AssemblyName>System.Web.WebPages.Razor.Test</AssemblyName>
+ <FileAlignment>512</FileAlignment>
+ <ProjectTypeGuids>{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
+ <ErrorReport>prompt</ErrorReport>
+ <WarningLevel>4</WarningLevel>
+ <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
+ <DebugSymbols>true</DebugSymbols>
+ <DebugType>full</DebugType>
+ <Optimize>false</Optimize>
+ <OutputPath>$(WebStackRootPath)\bin\Debug\Test\</OutputPath>
+ <DefineConstants>TRACE;DEBUG</DefineConstants>
+ <CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
+ <DebugType>pdbonly</DebugType>
+ <Optimize>true</Optimize>
+ <OutputPath>$(WebStackRootPath)\bin\Release\Test\</OutputPath>
+ <DefineConstants>TRACE</DefineConstants>
+ <CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)' == 'CodeCoverage' ">
+ <DebugSymbols>true</DebugSymbols>
+ <DebugType>full</DebugType>
+ <Optimize>false</Optimize>
+ <OutputPath>$(WebStackRootPath)\bin\CodeCoverage\Test\</OutputPath>
+ <DefineConstants>TRACE;DEBUG</DefineConstants>
+ <CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
+ </PropertyGroup>
+ <ItemGroup>
+ <Compile Include="Properties\AssemblyInfo.cs" />
+ <Compile Include="PreApplicationStartCodeTest.cs" />
+ <Compile Include="RazorBuildProviderTest.cs" />
+ <Compile Include="WebCodeRazorEngineHostTest.cs" />
+ <Compile Include="WebPageRazorEngineHostTest.cs" />
+ <Compile Include="WebRazorHostFactoryTest.cs" />
+ </ItemGroup>
+ <ItemGroup>
+ <Reference Include="Microsoft.CSharp" />
+ <Reference Include="Moq, Version=4.0.10827.0, Culture=neutral, PublicKeyToken=69f491c39445e920, processorArchitecture=MSIL">
+ <HintPath>..\..\packages\Moq.4.0.10827\lib\NET40\Moq.dll</HintPath>
+ </Reference>
+ <Reference Include="System" />
+ <Reference Include="System.Configuration" />
+ <Reference Include="System.Web" />
+ <Reference Include="xunit">
+ <HintPath>..\..\packages\xunit.1.9.0.1566\lib\xunit.dll</HintPath>
+ </Reference>
+ <Reference Include="xunit.extensions">
+ <HintPath>..\..\packages\xunit.extensions.1.9.0.1566\lib\xunit.extensions.dll</HintPath>
+ </Reference>
+ </ItemGroup>
+ <ItemGroup>
+ <ProjectReference Include="..\..\src\System.Web.WebPages\System.Web.WebPages.csproj">
+ <Project>{76EFA9C5-8D7E-4FDF-B710-E20F8B6B00D2}</Project>
+ <Name>System.Web.WebPages</Name>
+ </ProjectReference>
+ <ProjectReference Include="..\..\src\System.Web.Razor\System.Web.Razor.csproj">
+ <Project>{8F18041B-9410-4C36-A9C5-067813DF5F31}</Project>
+ <Name>System.Web.Razor</Name>
+ </ProjectReference>
+ <ProjectReference Include="..\..\src\System.Web.WebPages.Razor\System.Web.WebPages.Razor.csproj">
+ <Project>{0939B11A-FE4E-4BA1-8AD6-D97741EE314F}</Project>
+ <Name>System.Web.WebPages.Razor</Name>
+ </ProjectReference>
+ <ProjectReference Include="..\Microsoft.TestCommon\Microsoft.TestCommon.csproj">
+ <Project>{FCCC4CB7-BAF7-4A57-9F89-E5766FE536C0}</Project>
+ <Name>Microsoft.TestCommon</Name>
+ </ProjectReference>
+ </ItemGroup>
+ <ItemGroup />
+ <ItemGroup>
+ <None Include="app.config" />
+ <None Include="packages.config" />
+ </ItemGroup>
+ <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
+</Project> \ No newline at end of file
diff --git a/test/System.Web.WebPages.Razor.Test/WebCodeRazorEngineHostTest.cs b/test/System.Web.WebPages.Razor.Test/WebCodeRazorEngineHostTest.cs
new file mode 100644
index 00000000..5824c8fd
--- /dev/null
+++ b/test/System.Web.WebPages.Razor.Test/WebCodeRazorEngineHostTest.cs
@@ -0,0 +1,110 @@
+using System.CodeDom;
+using System.Linq;
+using System.Web.Razor.Generator;
+using Xunit;
+
+namespace System.Web.WebPages.Razor.Test
+{
+ public class WebCodeRazorEngineHostTest
+ {
+ [Fact]
+ public void ConstructorWithMalformedVirtualPathSetsDefaultProperties()
+ {
+ // Act
+ WebCodeRazorHost host = new WebCodeRazorHost(@"~/Foo/App_Code\Bar\Baz\Qux.cshtml");
+
+ // Assert
+ Assert.Equal("System.Web.WebPages.HelperPage", host.DefaultBaseClass);
+ Assert.Equal("ASP.Bar.Baz", host.DefaultNamespace);
+ Assert.Equal("Qux", host.DefaultClassName);
+ Assert.False(host.DefaultDebugCompilation);
+ Assert.True(host.StaticHelpers);
+ }
+
+ [Fact]
+ public void ConstructorWithFileOnlyVirtualPathSetsDefaultProperties()
+ {
+ // Act
+ WebCodeRazorHost host = new WebCodeRazorHost(@"Foo.cshtml");
+
+ // Assert
+ Assert.Equal("System.Web.WebPages.HelperPage", host.DefaultBaseClass);
+ Assert.Equal("ASP", host.DefaultNamespace);
+ Assert.Equal("Foo", host.DefaultClassName);
+ Assert.False(host.DefaultDebugCompilation);
+ }
+
+ [Fact]
+ public void ConstructorWithVirtualPathSetsDefaultProperties()
+ {
+ // Act
+ WebCodeRazorHost host = new WebCodeRazorHost("~/Foo/App_Code/Bar/Baz/Qux.cshtml");
+
+ // Assert
+ Assert.Equal("System.Web.WebPages.HelperPage", host.DefaultBaseClass);
+ Assert.Equal("ASP.Bar.Baz", host.DefaultNamespace);
+ Assert.Equal("Qux", host.DefaultClassName);
+ Assert.False(host.DefaultDebugCompilation);
+ }
+
+ [Fact]
+ public void ConstructorWithVirtualAndPhysicalPathSetsDefaultProperties()
+ {
+ // Act
+ WebCodeRazorHost host = new WebCodeRazorHost("~/Foo/App_Code/Bar/Baz/Qux.cshtml", @"C:\Qux.doodad");
+
+ // Assert
+ Assert.Equal("System.Web.WebPages.HelperPage", host.DefaultBaseClass);
+ Assert.Equal("ASP.Bar.Baz", host.DefaultNamespace);
+ Assert.Equal("Qux", host.DefaultClassName);
+ Assert.False(host.DefaultDebugCompilation);
+ }
+
+ [Fact]
+ public void PostProcessGeneratedCodeRemovesExecuteMethod()
+ {
+ // Arrange
+ WebCodeRazorHost host = new WebCodeRazorHost("Foo.cshtml");
+ CodeGeneratorContext context = CodeGeneratorContext.Create(
+ host,
+ () => new CSharpCodeWriter(),
+ "TestClass",
+ "TestNamespace",
+ "TestFile.cshtml",
+ shouldGenerateLinePragmas: true);
+
+ // Act
+ host.PostProcessGeneratedCode(context);
+
+ // Assert
+ Assert.Equal(0, context.GeneratedClass.Members.OfType<CodeMemberMethod>().Count());
+ }
+
+ [Fact]
+ public void PostProcessGeneratedCodeAddsStaticApplicationInstanceProperty()
+ {
+ // Arrange
+ WebCodeRazorHost host = new WebCodeRazorHost("Foo.cshtml");
+ CodeGeneratorContext context =
+ CodeGeneratorContext.Create(
+ host,
+ () => new CSharpCodeWriter(),
+ "TestClass",
+ "TestNamespace",
+ "Foo.cshtml",
+ shouldGenerateLinePragmas: true);
+
+ // Act
+ host.PostProcessGeneratedCode(context);
+
+ // Assert
+ CodeMemberProperty appInstance = context.GeneratedClass
+ .Members
+ .OfType<CodeMemberProperty>()
+ .Where(p => p.Name.Equals("ApplicationInstance"))
+ .SingleOrDefault();
+ Assert.NotNull(appInstance);
+ Assert.True(appInstance.Attributes.HasFlag(MemberAttributes.Static));
+ }
+ }
+}
diff --git a/test/System.Web.WebPages.Razor.Test/WebPageRazorEngineHostTest.cs b/test/System.Web.WebPages.Razor.Test/WebPageRazorEngineHostTest.cs
new file mode 100644
index 00000000..24f0b39c
--- /dev/null
+++ b/test/System.Web.WebPages.Razor.Test/WebPageRazorEngineHostTest.cs
@@ -0,0 +1,100 @@
+using System.CodeDom;
+using System.CodeDom.Compiler;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Web.Razor;
+using System.Web.Razor.Generator;
+using Microsoft.CSharp;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.WebPages.Razor.Test
+{
+ public class WebPageRazorEngineHostTest
+ {
+ [Fact]
+ public void ConstructorRequiresNonNullOrEmptyVirtualPath()
+ {
+ Assert.ThrowsArgumentNullOrEmptyString(() => new WebPageRazorHost(null), "virtualPath");
+ Assert.ThrowsArgumentNullOrEmptyString(() => new WebPageRazorHost(String.Empty), "virtualPath");
+ Assert.ThrowsArgumentNullOrEmptyString(() => new WebPageRazorHost(null, "foo"), "virtualPath");
+ Assert.ThrowsArgumentNullOrEmptyString(() => new WebPageRazorHost(String.Empty, "foo"), "virtualPath");
+ }
+
+ [Fact]
+ public void ConstructorWithVirtualPathUsesItToDetermineBaseClassClassNameAndLanguage()
+ {
+ // Act
+ WebPageRazorHost host = new WebPageRazorHost("~/Foo/Bar.cshtml");
+
+ // Assert
+ Assert.Equal("_Page_Foo_Bar_cshtml", host.DefaultClassName);
+ Assert.Equal("System.Web.WebPages.WebPage", host.DefaultBaseClass);
+ Assert.IsType<CSharpRazorCodeLanguage>(host.CodeLanguage);
+ Assert.False(host.StaticHelpers);
+ }
+
+ [Fact]
+ public void PostProcessGeneratedCodeAddsGlobalImports()
+ {
+ // Arrange
+ WebPageRazorHost.AddGlobalImport("Foo.Bar");
+ WebPageRazorHost host = new WebPageRazorHost("Foo.cshtml");
+ CodeGeneratorContext context = CodeGeneratorContext.Create(
+ host,
+ () => new CSharpCodeWriter(),
+ "TestClass",
+ "TestNs",
+ "TestFile.cshtml",
+ shouldGenerateLinePragmas: true);
+
+ // Act
+ host.PostProcessGeneratedCode(context);
+
+ // Assert
+ Assert.True(context.Namespace.Imports.OfType<CodeNamespaceImport>().Any(import => String.Equals("Foo.Bar", import.Namespace)));
+ }
+
+ [Fact]
+ public void PostProcessGeneratedCodeAddsApplicationInstanceProperty()
+ {
+ const string expectedPropertyCode = @"
+protected Foo.Bar ApplicationInstance {
+ get {
+ return ((Foo.Bar)(Context.ApplicationInstance));
+ }
+}
+";
+
+ // Arrange
+ WebPageRazorHost host = new WebPageRazorHost("Foo.cshtml")
+ {
+ GlobalAsaxTypeName = "Foo.Bar"
+ };
+ CodeGeneratorContext context = CodeGeneratorContext.Create(
+ host,
+ () => new CSharpCodeWriter(),
+ "TestClass",
+ "TestNs",
+ "TestFile.cshtml",
+ shouldGenerateLinePragmas: true);
+
+ // Act
+ host.PostProcessGeneratedCode(context);
+
+ // Assert
+ CodeMemberProperty property = context.GeneratedClass.Members[0] as CodeMemberProperty;
+ Assert.NotNull(property);
+
+ CSharpCodeProvider provider = new CSharpCodeProvider();
+ StringBuilder builder = new StringBuilder();
+ using (StringWriter writer = new StringWriter(builder))
+ {
+ provider.GenerateCodeFromMember(property, writer, new CodeGeneratorOptions());
+ }
+
+ Assert.Equal(expectedPropertyCode, builder.ToString());
+ }
+ }
+}
diff --git a/test/System.Web.WebPages.Razor.Test/WebRazorHostFactoryTest.cs b/test/System.Web.WebPages.Razor.Test/WebRazorHostFactoryTest.cs
new file mode 100644
index 00000000..cfae372c
--- /dev/null
+++ b/test/System.Web.WebPages.Razor.Test/WebRazorHostFactoryTest.cs
@@ -0,0 +1,387 @@
+using System.Configuration;
+using System.Reflection;
+using System.Web.Configuration;
+using System.Web.WebPages.Razor.Configuration;
+using System.Web.WebPages.Razor.Resources;
+using System.Web.WebPages.TestUtils;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.WebPages.Razor.Test
+{
+ public class WebRazorHostFactoryTest
+ {
+ public class TestFactory : WebRazorHostFactory
+ {
+ public override WebPageRazorHost CreateHost(string virtualPath, string physicalPath = null)
+ {
+ return new TestHost();
+ }
+ }
+
+ public class TestHost : WebPageRazorHost
+ {
+ public TestHost()
+ : base("Foo.cshtml")
+ {
+ }
+
+ public new void RegisterSpecialFile(string fileName, Type baseType)
+ {
+ base.RegisterSpecialFile(fileName, baseType);
+ }
+
+ public new void RegisterSpecialFile(string fileName, string baseType)
+ {
+ base.RegisterSpecialFile(fileName, baseType);
+ }
+ }
+
+ [Fact]
+ public void CreateHostReturnsWebPageHostWithWebPageAsBaseClassIfVirtualPathIsNormalPage()
+ {
+ // Act
+ WebPageRazorHost host = new WebRazorHostFactory().CreateHost("~/Foo/Bar/Baz.cshtml", null);
+
+ // Assert
+ Assert.IsType<WebPageRazorHost>(host);
+ Assert.Equal(WebPageRazorHost.PageBaseClass, host.DefaultBaseClass);
+ }
+
+ [Fact]
+ public void CreateHostReturnsWebPageHostWithInitPageAsBaseClassIfVirtualPathIsPageStart()
+ {
+ // Act
+ WebPageRazorHost host = new WebRazorHostFactory().CreateHost("~/Foo/Bar/_pagestart.cshtml", null);
+
+ // Assert
+ Assert.IsType<WebPageRazorHost>(host);
+ Assert.Equal(typeof(StartPage).FullName, host.DefaultBaseClass);
+ }
+
+ [Fact]
+ public void CreateHostReturnsWebPageHostWithStartPageAsBaseClassIfVirtualPathIsAppStart()
+ {
+ // Act
+ WebPageRazorHost host = new WebRazorHostFactory().CreateHost("~/Foo/Bar/_appstart.cshtml", null);
+
+ // Assert
+ Assert.IsType<WebPageRazorHost>(host);
+ Assert.Equal(typeof(ApplicationStartPage).FullName, host.DefaultBaseClass);
+ }
+
+ [Fact]
+ public void CreateHostPassesPhysicalPathOnToWebCodeRazorHost()
+ {
+ // Act
+ WebPageRazorHost host = new WebRazorHostFactory().CreateHost("~/Foo/Bar/Baz/App_Code/Bar", @"C:\Foo.cshtml");
+
+ // Assert
+ Assert.Equal(@"C:\Foo.cshtml", host.PhysicalPath);
+ }
+
+ [Fact]
+ public void CreateHostPassesPhysicalPathOnToWebPageRazorHost()
+ {
+ // Act
+ WebPageRazorHost host = new WebRazorHostFactory().CreateHost("~/Foo/Bar/Baz/Bar", @"C:\Foo.cshtml");
+
+ // Assert
+ Assert.Equal(@"C:\Foo.cshtml", host.PhysicalPath);
+ }
+
+ [Fact]
+ public void CreateHostFromConfigRequiresNonNullVirtualPath()
+ {
+ Assert.ThrowsArgumentNullOrEmptyString(() => WebRazorHostFactory.CreateHostFromConfig(virtualPath: null,
+ physicalPath: "foo"), "virtualPath");
+ Assert.ThrowsArgumentNullOrEmptyString(() => WebRazorHostFactory.CreateHostFromConfig(config: new RazorWebSectionGroup(),
+ virtualPath: null,
+ physicalPath: "foo"), "virtualPath");
+ }
+
+ [Fact]
+ public void CreateHostFromConfigRequiresNonEmptyVirtualPath()
+ {
+ Assert.ThrowsArgumentNullOrEmptyString(() => WebRazorHostFactory.CreateHostFromConfig(virtualPath: String.Empty,
+ physicalPath: "foo"), "virtualPath");
+ Assert.ThrowsArgumentNullOrEmptyString(() => WebRazorHostFactory.CreateHostFromConfig(config: new RazorWebSectionGroup(),
+ virtualPath: String.Empty,
+ physicalPath: "foo"), "virtualPath");
+ }
+
+ [Fact]
+ public void CreateHostFromConfigRequiresNonNullSectionGroup()
+ {
+ Assert.ThrowsArgumentNull(() => WebRazorHostFactory.CreateHostFromConfig(config: (RazorWebSectionGroup)null,
+ virtualPath: String.Empty,
+ physicalPath: "foo"), "config");
+ }
+
+ [Fact]
+ public void CreateHostFromConfigReturnsWebCodeHostIfVirtualPathStartsWithAppCode()
+ {
+ // Act
+ WebPageRazorHost host = WebRazorHostFactory.CreateHostFromConfigCore(null, "~/App_Code/Bar.cshtml", null);
+
+ // Assert
+ Assert.IsType<WebCodeRazorHost>(host);
+ }
+
+ [Fact]
+ public void CreateHostFromConfigUsesDefaultFactoryIfNoRazorWebSectionGroupFound()
+ {
+ // Act
+ WebPageRazorHost host = WebRazorHostFactory.CreateHostFromConfigCore(null, "/Foo/Bar.cshtml", null);
+
+ // Assert
+ Assert.IsType<WebPageRazorHost>(host);
+ }
+
+ [Fact]
+ public void CreateHostFromConfigUsesDefaultFactoryIfNoHostSectionFound()
+ {
+ // Arrange
+ RazorWebSectionGroup config = new RazorWebSectionGroup()
+ {
+ Host = null,
+ Pages = null
+ };
+
+ // Act
+ WebPageRazorHost host = WebRazorHostFactory.CreateHostFromConfig(config, "/Foo/Bar.cshtml", null);
+
+ // Assert
+ Assert.IsType<WebPageRazorHost>(host);
+ }
+
+ [Fact]
+ public void CreateHostFromConfigUsesDefaultFactoryIfNullFactoryType()
+ {
+ // Arrange
+ RazorWebSectionGroup config = new RazorWebSectionGroup()
+ {
+ Host = new HostSection()
+ {
+ FactoryType = null
+ },
+ Pages = null
+ };
+
+ // Act
+ WebPageRazorHost host = WebRazorHostFactory.CreateHostFromConfig(config, "/Foo/Bar.cshtml", null);
+
+ // Assert
+ Assert.IsType<WebPageRazorHost>(host);
+ }
+
+ [Fact]
+ public void CreateHostFromConfigUsesFactorySpecifiedInConfig()
+ {
+ // Arrange
+ RazorWebSectionGroup config = new RazorWebSectionGroup()
+ {
+ Host = new HostSection()
+ {
+ FactoryType = typeof(TestFactory).FullName
+ },
+ Pages = null
+ };
+ WebRazorHostFactory.TypeFactory = name => Assembly.GetExecutingAssembly().GetType(name, throwOnError: false);
+
+ // Act
+ WebPageRazorHost host = WebRazorHostFactory.CreateHostFromConfig(config, "/Foo/Bar.cshtml", null);
+
+ // Assert
+ Assert.IsType<TestHost>(host);
+ }
+
+ [Fact]
+ public void CreateHostFromConfigThrowsInvalidOperationExceptionIfFactoryTypeNotFound()
+ {
+ // Arrange
+ RazorWebSectionGroup config = new RazorWebSectionGroup()
+ {
+ Host = new HostSection()
+ {
+ FactoryType = "Foo"
+ },
+ Pages = null
+ };
+ WebRazorHostFactory.TypeFactory = name => Assembly.GetExecutingAssembly().GetType(name, throwOnError: false);
+
+ // Act
+ Assert.Throws<InvalidOperationException>(
+ () => WebRazorHostFactory.CreateHostFromConfig(config, "/Foo/Bar.cshtml", null),
+ String.Format(RazorWebResources.Could_Not_Locate_FactoryType, "Foo"));
+ }
+
+ [Fact]
+ public void CreateHostFromConfigAppliesBaseTypeFromConfigToHost()
+ {
+ // Arrange
+ RazorWebSectionGroup config = new RazorWebSectionGroup()
+ {
+ Host = null,
+ Pages = new RazorPagesSection()
+ {
+ PageBaseType = "System.Foo.Bar"
+ }
+ };
+ WebRazorHostFactory.TypeFactory = name => Assembly.GetExecutingAssembly().GetType(name, throwOnError: false);
+
+ // Act
+ WebPageRazorHost host = WebRazorHostFactory.CreateHostFromConfig(config, "/Foo/Bar.cshtml", null);
+
+ // Assert
+ Assert.Equal("System.Foo.Bar", host.DefaultBaseClass);
+ }
+
+ [Fact]
+ public void CreateHostFromConfigIgnoresBaseTypeFromConfigIfPageIsPageStart()
+ {
+ // Arrange
+ RazorWebSectionGroup config = new RazorWebSectionGroup()
+ {
+ Host = null,
+ Pages = new RazorPagesSection()
+ {
+ PageBaseType = "System.Foo.Bar"
+ }
+ };
+ WebRazorHostFactory.TypeFactory = name => Assembly.GetExecutingAssembly().GetType(name, throwOnError: false);
+
+ // Act
+ WebPageRazorHost host = WebRazorHostFactory.CreateHostFromConfig(config, "/Foo/_pagestart.cshtml", null);
+
+ // Assert
+ Assert.Equal(typeof(StartPage).FullName, host.DefaultBaseClass);
+ }
+
+ [Fact]
+ public void CreateHostFromConfigIgnoresBaseTypeFromConfigIfPageIsAppStart()
+ {
+ // Arrange
+ RazorWebSectionGroup config = new RazorWebSectionGroup()
+ {
+ Host = null,
+ Pages = new RazorPagesSection()
+ {
+ PageBaseType = "System.Foo.Bar"
+ }
+ };
+ WebRazorHostFactory.TypeFactory = name => Assembly.GetExecutingAssembly().GetType(name, throwOnError: false);
+
+ // Act
+ WebPageRazorHost host = WebRazorHostFactory.CreateHostFromConfig(config, "/Foo/_appstart.cshtml", null);
+
+ // Assert
+ Assert.Equal(typeof(ApplicationStartPage).FullName, host.DefaultBaseClass);
+ }
+
+ [Fact]
+ public void CreateHostFromConfigMergesNamespacesFromConfigToHost()
+ {
+ // Arrange
+ RazorWebSectionGroup config = new RazorWebSectionGroup()
+ {
+ Host = null,
+ Pages = new RazorPagesSection()
+ {
+ Namespaces = new NamespaceCollection()
+ {
+ new NamespaceInfo("System"),
+ new NamespaceInfo("Foo")
+ }
+ }
+ };
+ WebRazorHostFactory.TypeFactory = name => Assembly.GetExecutingAssembly().GetType(name, throwOnError: false);
+
+ // Act
+ WebPageRazorHost host = WebRazorHostFactory.CreateHostFromConfig(config, "/Foo/Bar.cshtml", null);
+
+ // Assert
+ Assert.True(host.NamespaceImports.Contains("System"));
+ Assert.True(host.NamespaceImports.Contains("Foo"));
+ }
+
+ [Fact]
+ public void HostFactoryTypeIsCorrectlyLoadedFromConfig()
+ {
+ // Act
+ RazorWebSectionGroup group = GetRazorGroup();
+ HostSection host = (HostSection)group.Host;
+
+ // Assert
+ Assert.NotNull(host);
+ Assert.Equal("System.Web.WebPages.Razor.Test.TestRazorHostFactory, System.Web.WebPages.Razor.Test", host.FactoryType);
+ }
+
+ [Fact]
+ public void PageBaseTypeIsCorrectlyLoadedFromConfig()
+ {
+ // Act
+ RazorWebSectionGroup group = GetRazorGroup();
+ RazorPagesSection pages = (RazorPagesSection)group.Pages;
+
+ // Assert
+ Assert.NotNull(pages);
+ Assert.Equal("System.Web.WebPages.Razor.Test.TestPageBase, System.Web.WebPages.Razor.Test", pages.PageBaseType);
+ }
+
+ [Fact]
+ public void NamespacesAreCorrectlyLoadedFromConfig()
+ {
+ // Act
+ RazorWebSectionGroup group = GetRazorGroup();
+ RazorPagesSection pages = (RazorPagesSection)group.Pages;
+
+ // Assert
+ Assert.NotNull(pages);
+ Assert.Equal(1, pages.Namespaces.Count);
+ Assert.Equal("System.Text.RegularExpressions", pages.Namespaces[0].Namespace);
+ }
+
+ [Fact]
+ public void RegisterSpecialFile_ThrowsOnNullFileName()
+ {
+ TestHost host = new TestHost();
+ Assert.ThrowsArgumentNullOrEmptyString(() => host.RegisterSpecialFile(null, typeof(string)), "fileName");
+ Assert.ThrowsArgumentNullOrEmptyString(() => host.RegisterSpecialFile(null, "string"), "fileName");
+ }
+
+ [Fact]
+ public void RegisterSpecialFile_ThrowsOnEmptyFileName()
+ {
+ TestHost host = new TestHost();
+ Assert.ThrowsArgumentNullOrEmptyString(() => host.RegisterSpecialFile(String.Empty, typeof(string)), "fileName");
+ Assert.ThrowsArgumentNullOrEmptyString(() => host.RegisterSpecialFile(String.Empty, "string"), "fileName");
+ }
+
+ [Fact]
+ public void RegisterSpecialFile_ThrowsOnNullBaseType()
+ {
+ TestHost host = new TestHost();
+ Assert.ThrowsArgumentNull(() => host.RegisterSpecialFile("file", (Type)null), "baseType");
+ }
+
+ [Fact]
+ public void RegisterSpecialFile_ThrowsOnNullBaseTypeName()
+ {
+ TestHost host = new TestHost();
+ Assert.ThrowsArgumentNullOrEmptyString(() => host.RegisterSpecialFile("file", (string)null), "baseTypeName");
+ }
+
+ [Fact]
+ public void RegisterSpecialFile_ThrowsOnEmptyBaseTypeName()
+ {
+ TestHost host = new TestHost();
+ Assert.ThrowsArgumentNullOrEmptyString(() => host.RegisterSpecialFile("file", String.Empty), "baseTypeName");
+ }
+
+ private static RazorWebSectionGroup GetRazorGroup()
+ {
+ return (RazorWebSectionGroup)ConfigurationManager.OpenExeConfiguration(null).GetSectionGroup(RazorWebSectionGroup.GroupName);
+ }
+ }
+}
diff --git a/test/System.Web.WebPages.Razor.Test/app.config b/test/System.Web.WebPages.Razor.Test/app.config
new file mode 100644
index 00000000..a78b27b5
--- /dev/null
+++ b/test/System.Web.WebPages.Razor.Test/app.config
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<configuration>
+ <configSections>
+ <sectionGroup name="system.web.webPages.razor" type="System.Web.WebPages.Razor.Configuration.RazorWebSectionGroup, System.Web.WebPages.Razor, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35">
+ <section name="host" type="System.Web.WebPages.Razor.Configuration.HostSection, System.Web.WebPages.Razor, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" requirePermission="false" />
+ <section name="pages" type="System.Web.WebPages.Razor.Configuration.RazorPagesSection, System.Web.WebPages.Razor, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" requirePermission="false" />
+ </sectionGroup>
+ </configSections>
+ <system.web.webPages.razor>
+ <host factoryType="System.Web.WebPages.Razor.Test.TestRazorHostFactory, System.Web.WebPages.Razor.Test" />
+ <pages pageBaseType="System.Web.WebPages.Razor.Test.TestPageBase, System.Web.WebPages.Razor.Test">
+ <namespaces>
+ <add namespace="System.IO.Packaging" />
+ <clear />
+ <add namespace="System.Security" />
+ <add namespace="System.Text.RegularExpressions" />
+ <remove namespace="System.Security" />
+ </namespaces>
+ </pages>
+ </system.web.webPages.razor>
+</configuration> \ No newline at end of file
diff --git a/test/System.Web.WebPages.Razor.Test/packages.config b/test/System.Web.WebPages.Razor.Test/packages.config
new file mode 100644
index 00000000..d5aa6401
--- /dev/null
+++ b/test/System.Web.WebPages.Razor.Test/packages.config
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<packages>
+ <package id="Moq" version="4.0.10827" />
+ <package id="xunit" version="1.9.0.1566" />
+ <package id="xunit.extensions" version="1.9.0.1566" />
+</packages> \ No newline at end of file
diff --git a/test/System.Web.WebPages.Test/App.config b/test/System.Web.WebPages.Test/App.config
new file mode 100644
index 00000000..a740ab52
--- /dev/null
+++ b/test/System.Web.WebPages.Test/App.config
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<configuration>
+ <system.data>
+ <DbProviderFactories>
+ <remove invariant="System.Data.SqlServerCe.4.0"></remove>
+ <add name="Microsoft SQL Server Compact Data Provider" invariant="System.Data.SqlServerCe.4.0" description=".NET Framework Data Provider for Microsoft SQL Server Compact" type="System.Data.SqlServerCe.SqlCeProviderFactory, System.Data.SqlServerCe, Version=4.0.0.0, Culture=neutral, PublicKeyToken=89845dcd8080cc91"/>
+ </DbProviderFactories>
+ </system.data>
+ <runtime>
+ <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
+ <dependentAssembly>
+ <!-- Need this because the BinarySerializer uses the TypeForwardedFrom attribute and deserializes to the original assembly (MVC 2.0) for the HttpAntiForgeryException test -->
+ <assemblyIdentity name="System.Web.Mvc" publicKeyToken="31bf3856ad364e35" />
+ <bindingRedirect oldVersion="1.0.0.0-4.0.0.0" newVersion="4.0.0.0" />
+ </dependentAssembly>
+ </assemblyBinding>
+ </runtime>
+</configuration> \ No newline at end of file
diff --git a/test/System.Web.WebPages.Test/ApplicationParts/ApplicationPartRegistryTest.cs b/test/System.Web.WebPages.Test/ApplicationParts/ApplicationPartRegistryTest.cs
new file mode 100644
index 00000000..14acf0fc
--- /dev/null
+++ b/test/System.Web.WebPages.Test/ApplicationParts/ApplicationPartRegistryTest.cs
@@ -0,0 +1,150 @@
+using System.Web.WebPages.ApplicationParts;
+using Moq;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.WebPages.Test.ApplicationModule
+{
+ public class ApplicationPartRegistryTest
+ {
+ [Fact]
+ public void ApplicationModuleGeneratesRootRelativePaths()
+ {
+ // Arrange
+ var path1 = "foo/bar";
+ var path2 = "~/xyz/pqr";
+ var root1 = "~/myappmodule";
+ var root2 = "~/myappmodule2/";
+
+ // Act
+ var actualPath11 = ApplicationPartRegistry.GetRootRelativeVirtualPath(root1, path1);
+ var actualPath12 = ApplicationPartRegistry.GetRootRelativeVirtualPath(root1, path2);
+ var actualPath21 = ApplicationPartRegistry.GetRootRelativeVirtualPath(root2, path1);
+ var actualPath22 = ApplicationPartRegistry.GetRootRelativeVirtualPath(root2, path2);
+
+ // Assert
+ Assert.Equal(actualPath11, root1 + "/" + path1);
+ Assert.Equal(actualPath12, root1 + path2.TrimStart('~'));
+ Assert.Equal(actualPath21, root2 + path1);
+ Assert.Equal(actualPath22, root2 + path2.TrimStart('~', '/'));
+ }
+
+ [Fact]
+ public void ApplicationPartRegistryLooksUpPartsByName()
+ {
+ // Arrange
+ var part = new ApplicationPart(BuildAssembly(), "~/mymodule");
+ var dictionary = new DictionaryBasedVirtualPathFactory();
+ var registry = new ApplicationPartRegistry(dictionary);
+ Func<object> myFunc = () => "foo";
+
+ // Act
+ registry.Register(part, myFunc);
+
+ // Assert
+ Assert.Equal(registry["my-assembly"], part);
+ Assert.Equal(registry["MY-aSSembly"], part);
+ }
+
+ [Fact]
+ public void ApplicationPartRegistryLooksUpPartsByAssembly()
+ {
+ // Arrange
+ var assembly = BuildAssembly();
+ var part = new ApplicationPart(assembly, "~/mymodule");
+ var dictionary = new DictionaryBasedVirtualPathFactory();
+ var registry = new ApplicationPartRegistry(dictionary);
+ Func<object> myFunc = () => "foo";
+
+ // Act
+ registry.Register(part, myFunc);
+
+ // Assert
+ Assert.Equal(registry[assembly], part);
+ }
+
+ [Fact]
+ public void RegisterThrowsIfAssemblyAlreadyRegistered()
+ {
+ // Arrange
+ var part = new ApplicationPart(BuildAssembly(), "~/mymodule");
+ var dictionary = new DictionaryBasedVirtualPathFactory();
+ var registry = new ApplicationPartRegistry(dictionary);
+ Func<object> myFunc = () => "foo";
+
+ // Act
+ registry.Register(part, myFunc);
+
+ // Assert
+ Assert.Throws<InvalidOperationException>(() => registry.Register(part, myFunc),
+ String.Format("The assembly \"{0}\" is already registered.", part.Assembly.ToString()));
+ }
+
+ [Fact]
+ public void RegisterThrowsIfPathAlreadyRegistered()
+ {
+ // Arrange
+ var part = new ApplicationPart(BuildAssembly(), "~/mymodule");
+ var dictionary = new DictionaryBasedVirtualPathFactory();
+ var registry = new ApplicationPartRegistry(dictionary);
+ Func<object> myFunc = () => "foo";
+
+ // Act
+ registry.Register(part, myFunc);
+
+ // Assert
+ var newPart = new ApplicationPart(BuildAssembly("different-assembly"), "~/mymodule");
+ Assert.Throws<InvalidOperationException>(() => registry.Register(newPart, myFunc),
+ "An application module is already registered for virtual path \"~/mymodule/\".");
+ }
+
+ [Fact]
+ public void RegisterCreatesRoutesForValidPages()
+ {
+ // Arrange
+ var part = new ApplicationPart(BuildAssembly(), "~/mymodule");
+ var dictionary = new DictionaryBasedVirtualPathFactory();
+ var registry = new ApplicationPartRegistry(dictionary);
+ Func<object> myFunc = () => "foo";
+
+ // Act
+ registry.Register(part, myFunc);
+
+ // Assert
+ Assert.True(dictionary.Exists("~/mymodule/Page1"));
+ Assert.Equal(dictionary.CreateInstance("~/mymodule/Page1"), "foo");
+ Assert.False(dictionary.Exists("~/mymodule/Page2"));
+ Assert.False(dictionary.Exists("~/mymodule/Page3"));
+ }
+
+ private static IResourceAssembly BuildAssembly(string name = "my-assembly")
+ {
+ Mock<TestResourceAssembly> assembly = new Mock<TestResourceAssembly>();
+ assembly.SetupGet(c => c.Name).Returns(name);
+ assembly.Setup(c => c.GetHashCode()).Returns(name.GetHashCode());
+ assembly.Setup(c => c.Equals(It.IsAny<TestResourceAssembly>())).Returns((TestResourceAssembly c) => c.Name == name);
+
+ assembly.Setup(c => c.GetTypes()).Returns(new[]
+ {
+ BuildPageType(inherits: true, virtualPath: "~/Page1"),
+ BuildPageType(inherits: true, virtualPath: null),
+ BuildPageType(inherits: false, virtualPath: "~/Page3"),
+ });
+
+ return assembly.Object;
+ }
+
+ private static Type BuildPageType(bool inherits, string virtualPath)
+ {
+ Mock<Type> type = new Mock<Type>();
+ type.Setup(c => c.IsSubclassOf(typeof(WebPageRenderingBase))).Returns(inherits);
+
+ if (virtualPath != null)
+ {
+ type.Setup(c => c.GetCustomAttributes(typeof(PageVirtualPathAttribute), false))
+ .Returns(new[] { new PageVirtualPathAttribute(virtualPath) });
+ }
+ return type.Object;
+ }
+ }
+}
diff --git a/test/System.Web.WebPages.Test/ApplicationParts/ApplicationPartTest.cs b/test/System.Web.WebPages.Test/ApplicationParts/ApplicationPartTest.cs
new file mode 100644
index 00000000..488ee62e
--- /dev/null
+++ b/test/System.Web.WebPages.Test/ApplicationParts/ApplicationPartTest.cs
@@ -0,0 +1,198 @@
+using Moq;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.WebPages.Test
+{
+ public class ApplicationPartTest
+ {
+ [Fact]
+ public void ApplicationPartThrowsIfRootVirtualPathIsNullOrEmpty()
+ {
+ // Arrange
+ var assembly = new Mock<TestResourceAssembly>().Object;
+
+ Assert.ThrowsArgumentNullOrEmptyString(() => new ApplicationPart(assembly, rootVirtualPath: null), "rootVirtualPath");
+ Assert.ThrowsArgumentNullOrEmptyString(() => new ApplicationPart(assembly, rootVirtualPath: String.Empty), "rootVirtualPath");
+ }
+
+ [Fact]
+ public void ResolveVirtualPathResolvesRegularPathsUsingBaseVirtualPath()
+ {
+ // Arrange
+ var basePath = "~/base/";
+ var path = "somefile";
+ var appPartRoot = "~/app/";
+
+ // Act
+ var virtualPath = ApplicationPart.ResolveVirtualPath(appPartRoot, basePath, path);
+
+ // Assert
+ Assert.Equal(virtualPath, "~/base/somefile");
+ }
+
+ [Fact]
+ public void ResolveVirtualPathResolvesAppRelativePathsUsingAppVirtualPath()
+ {
+ // Arrange
+ var basePath = "~/base";
+ var path = "@/somefile";
+ var appPartRoot = "~/app/";
+
+ // Act
+ var virtualPath = ApplicationPart.ResolveVirtualPath(appPartRoot, basePath, path);
+
+ // Assert
+ Assert.Equal(virtualPath, "~/app/somefile");
+ }
+
+ [Fact]
+ public void ResolveVirtualPathDoesNotAffectRootRelativePaths()
+ {
+ // Arrange
+ var basePath = "~/base";
+ var path = "~/somefile";
+ var appPartRoot = "~/app/";
+
+ // Act
+ var virtualPath = ApplicationPart.ResolveVirtualPath(appPartRoot, basePath, path);
+
+ // Assert
+ Assert.Equal(virtualPath, "~/somefile");
+ }
+
+ [Fact]
+ public void GetResourceNameFromVirtualPathForTopLevelPath()
+ {
+ // Arrange
+ var moduleName = "my-module";
+ var path = "foo.baz";
+
+ // Act
+ var name = ApplicationPart.GetResourceNameFromVirtualPath(moduleName, path);
+
+ // Assert
+ Assert.Equal(name, moduleName + "." + path);
+ }
+
+ [Fact]
+ public void GetResourceNameFromVirtualPathForItemInSubDir()
+ {
+ // Arrange
+ var moduleName = "my-module";
+ var path = "/bar/foo";
+
+ // Act
+ var name = ApplicationPart.GetResourceNameFromVirtualPath(moduleName, path);
+
+ // Assert
+ Assert.Equal(name, "my-module.bar.foo");
+ }
+
+ [Fact]
+ public void GetResourceNameFromVirtualPathForItemWithSpaces()
+ {
+ // Arrange
+ var moduleName = "my-module";
+ var path = "/program files/data files/my file .foo";
+
+ // Act
+ var name = ApplicationPart.GetResourceNameFromVirtualPath(moduleName, path);
+
+ // Assert
+ Assert.Equal(name, "my-module.program_files.data_files.my file .foo");
+ }
+
+ [Fact]
+ public void GetResourceVirtualPathForTopLevelItem()
+ {
+ // Arrange
+ var moduleName = "my-module";
+ var moduleRoot = "~/root-path";
+ var path = moduleRoot + "/foo.txt";
+
+ // Act
+ var virtualPath = ApplicationPart.GetResourceVirtualPath(moduleName, moduleRoot, path);
+
+ // Assert
+ Assert.Equal(virtualPath, "~/r.ashx/" + moduleName + "/" + "foo.txt");
+ }
+
+ [Fact]
+ public void GetResourceVirtualPathForTopLevelItemAndModuleRootWithTrailingSlash()
+ {
+ // Arrange
+ var moduleName = "my-module";
+ var moduleRoot = "~/root-path/";
+ var path = moduleRoot + "/foo.txt";
+
+ // Act
+ var virtualPath = ApplicationPart.GetResourceVirtualPath(moduleName, moduleRoot, path);
+
+ // Assert
+ Assert.Equal(virtualPath, "~/r.ashx/" + moduleName + "/" + "foo.txt");
+ }
+
+ [Fact]
+ public void GetResourceVirtualPathForTopLevelItemAndNestedModuleRootPath()
+ {
+ // Arrange
+ var moduleName = "my-module";
+ var moduleRoot = "~/root-path/sub-path";
+ var path = moduleRoot + "/foo.txt";
+
+ // Act
+ var virtualPath = ApplicationPart.GetResourceVirtualPath(moduleName, moduleRoot, path);
+
+ // Assert
+ Assert.Equal(virtualPath, "~/r.ashx/" + moduleName + "/" + "foo.txt");
+ }
+
+ [Fact]
+ public void GetResourceVirtualPathEncodesModuleName()
+ {
+ // Arrange
+ var moduleName = "Debugger Package v?&%";
+ var moduleRoot = "~/root-path/sub-path";
+ var path = moduleRoot + "/foo.txt";
+
+ // Act
+ var virtualPath = ApplicationPart.GetResourceVirtualPath(moduleName, moduleRoot, path);
+
+ // Assert
+ Assert.Equal(virtualPath, "~/r.ashx/" + "Debugger%20Package%20v?&%" + "/" + "foo.txt");
+ }
+
+ [Fact]
+ public void GetResourceVirtualPathForNestedItemPath()
+ {
+ // Arrange
+ var moduleName = "DebuggerPackage";
+ var moduleRoot = "~/root-path/sub-path";
+ var itemPath = "some-path/some-more-please/foo.txt";
+ var path = moduleRoot + "/" + itemPath;
+
+ // Act
+ var virtualPath = ApplicationPart.GetResourceVirtualPath(moduleName, moduleRoot, path);
+
+ // Assert
+ Assert.Equal(virtualPath, "~/r.ashx/" + moduleName + "/" + itemPath);
+ }
+
+ [Fact]
+ public void GetResourceVirtualPathForItemPathWithParameters()
+ {
+ // Arrange
+ var moduleName = "DebuggerPackage";
+ var moduleRoot = "~/root-path/sub-path";
+ var itemPath = "some-path/some-more-please/foo.jpg?size=45&height=20";
+ var path = moduleRoot + "/" + itemPath;
+
+ // Act
+ var virtualPath = ApplicationPart.GetResourceVirtualPath(moduleName, moduleRoot, path);
+
+ // Assert
+ Assert.Equal(virtualPath, "~/r.ashx/" + moduleName + "/" + itemPath);
+ }
+ }
+}
diff --git a/test/System.Web.WebPages.Test/ApplicationParts/MimeMappingTest.cs b/test/System.Web.WebPages.Test/ApplicationParts/MimeMappingTest.cs
new file mode 100644
index 00000000..ef3ab63a
--- /dev/null
+++ b/test/System.Web.WebPages.Test/ApplicationParts/MimeMappingTest.cs
@@ -0,0 +1,61 @@
+using Microsoft.Internal.Web.Utils;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.WebPages.Test
+{
+ public class MimeMappingTest
+ {
+ [Fact]
+ public void MimeMappingThrowsForNullFileName()
+ {
+ // Arrange
+ string fileName = null;
+
+ // Act and Assert
+ Assert.ThrowsArgumentNull(() => MimeMapping.GetMimeMapping(fileName), "fileName");
+ }
+
+ [Fact]
+ public void MimeMappingReturnsGenericTypeForUnknownExtensions()
+ {
+ // Arrange
+ string fileName = "file.does-not-exist";
+
+ // Act
+ string mimeType = MimeMapping.GetMimeMapping(fileName);
+
+ // Assert
+ Assert.Equal("application/octet-stream", mimeType);
+ }
+
+ [Fact]
+ public void MimeMappingReturnsGenericTypeForNoExtensions()
+ {
+ // Arrange
+ string fileName = "file";
+
+ // Act
+ string mimeType = MimeMapping.GetMimeMapping(fileName);
+
+ // Assert
+ Assert.Equal("application/octet-stream", mimeType);
+ }
+
+ [Fact]
+ public void MimeMappingPerformsCaseInsensitiveSearches()
+ {
+ // Arrange
+ string fileName1 = "file.doc";
+ string fileName2 = "file.dOC";
+
+ // Act
+ string mimeType1 = MimeMapping.GetMimeMapping(fileName1);
+ string mimeType2 = MimeMapping.GetMimeMapping(fileName2);
+
+ // Assert
+ Assert.Equal("application/msword", mimeType1);
+ Assert.Equal("application/msword", mimeType2);
+ }
+ }
+}
diff --git a/test/System.Web.WebPages.Test/ApplicationParts/ResourceHandlerTest.cs b/test/System.Web.WebPages.Test/ApplicationParts/ResourceHandlerTest.cs
new file mode 100644
index 00000000..06e6225b
--- /dev/null
+++ b/test/System.Web.WebPages.Test/ApplicationParts/ResourceHandlerTest.cs
@@ -0,0 +1,62 @@
+using System.IO;
+using System.Text;
+using System.Web.WebPages.ApplicationParts;
+using Moq;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.WebPages.Test
+{
+ public class ResourceHandlerTest
+ {
+ private const string _fileContent = "contents of jpeg file";
+
+ [Fact]
+ public void ResourceHandlerWritesContentsOfFileToStream()
+ {
+ // Arrange
+ var applicationPart = new ApplicationPart(BuildAssembly(), "~/my-app-assembly");
+ MemoryStream stream = new MemoryStream();
+ var response = new Mock<HttpResponseBase>();
+ response.SetupGet(c => c.OutputStream).Returns(stream);
+ response.SetupSet(c => c.ContentType = "image/jpeg").Verifiable();
+ var resourceHandler = new ResourceHandler(applicationPart, "bar.foo.jpg");
+
+ // Act
+ resourceHandler.ProcessRequest(response.Object);
+
+ // Assert
+ response.Verify();
+ Assert.Equal(Encoding.Default.GetString(stream.ToArray()), _fileContent);
+ }
+
+ [Fact]
+ public void ResourceHandlerThrows404IfResourceNotFound()
+ {
+ // Arrange
+ var applicationPart = new ApplicationPart(BuildAssembly(), "~/my-app-assembly");
+ MemoryStream stream = new MemoryStream();
+ var response = new Mock<HttpResponseBase>();
+ response.SetupGet(c => c.OutputStream).Returns(stream);
+ response.SetupSet(c => c.ContentType = "image/jpeg").Verifiable();
+ var resourceHandler = new ResourceHandler(applicationPart, "does-not-exist");
+
+ // Act and Assert
+ Assert.Throws<HttpException>(() => resourceHandler.ProcessRequest(response.Object),
+ "The resource file \"does-not-exist\" could not be found.");
+ }
+
+ private static IResourceAssembly BuildAssembly(string name = "my-assembly")
+ {
+ Mock<TestResourceAssembly> assembly = new Mock<TestResourceAssembly>();
+ assembly.SetupGet(c => c.Name).Returns("my-assembly");
+
+ byte[] content = Encoding.Default.GetBytes(_fileContent);
+ assembly.Setup(c => c.GetManifestResourceStream("my-assembly.bar.foo.jpg")).Returns(new MemoryStream(content));
+
+ assembly.Setup(c => c.GetManifestResourceNames()).Returns(new[] { "my-assembly.bar.foo.jpg" });
+
+ return assembly.Object;
+ }
+ }
+}
diff --git a/test/System.Web.WebPages.Test/ApplicationParts/TestResourceAssembly.cs b/test/System.Web.WebPages.Test/ApplicationParts/TestResourceAssembly.cs
new file mode 100644
index 00000000..6e475f89
--- /dev/null
+++ b/test/System.Web.WebPages.Test/ApplicationParts/TestResourceAssembly.cs
@@ -0,0 +1,17 @@
+using System.Collections.Generic;
+using System.IO;
+using System.Web.WebPages.ApplicationParts;
+
+namespace System.Web.WebPages.Test
+{
+ public abstract class TestResourceAssembly : IResourceAssembly
+ {
+ public abstract string Name { get; }
+
+ public abstract Stream GetManifestResourceStream(string name);
+
+ public abstract IEnumerable<string> GetManifestResourceNames();
+
+ public abstract IEnumerable<Type> GetTypes();
+ }
+}
diff --git a/test/System.Web.WebPages.Test/Extensions/HttpContextExtensionsTest.cs b/test/System.Web.WebPages.Test/Extensions/HttpContextExtensionsTest.cs
new file mode 100644
index 00000000..d70be315
--- /dev/null
+++ b/test/System.Web.WebPages.Test/Extensions/HttpContextExtensionsTest.cs
@@ -0,0 +1,83 @@
+using System;
+using System.Web;
+using System.Web.WebPages;
+using Moq;
+using Xunit;
+
+namespace Microsoft.WebPages.Test.Helpers
+{
+ public class HttpContextExtensionsTest
+ {
+ class RedirectData
+ {
+ public string RequestUrl { get; set; }
+ public string RedirectUrl { get; set; }
+ }
+
+ private static HttpContextBase GetContextForRedirectLocal(RedirectData data)
+ {
+ Mock<HttpContextBase> contextMock = new Mock<HttpContextBase>();
+ contextMock.Setup(context => context.Request.Url).Returns(new Uri(data.RequestUrl));
+ contextMock.Setup(context => context.Response.Redirect(It.IsAny<string>())).Callback((string url) => data.RedirectUrl = url);
+ return contextMock.Object;
+ }
+
+ [Fact]
+ public void RedirectLocalWithNullGoesToRootTest()
+ {
+ RedirectData data = new RedirectData() { RequestUrl = "http://foo" };
+ var context = GetContextForRedirectLocal(data);
+ context.RedirectLocal("");
+ Assert.Equal("~/", data.RedirectUrl);
+ }
+
+ [Fact]
+ public void RedirectLocalWithEmptyStringGoesToRootTest()
+ {
+ RedirectData data = new RedirectData() { RequestUrl = "http://foo" };
+ var context = GetContextForRedirectLocal(data);
+ context.RedirectLocal("");
+ Assert.Equal("~/", data.RedirectUrl);
+ }
+
+ [Fact]
+ public void RedirectLocalWithNonLocalGoesToRootTest()
+ {
+ RedirectData data = new RedirectData() { RequestUrl = "http://foo" };
+ var context = GetContextForRedirectLocal(data);
+ context.RedirectLocal("");
+ Assert.Equal("~/", data.RedirectUrl);
+ }
+
+ [Fact]
+ public void RedirectLocalWithDifferentHostGoesToRootTest()
+ {
+ RedirectData data = new RedirectData() { RequestUrl = "http://foo" };
+ var context = GetContextForRedirectLocal(data);
+ context.RedirectLocal("http://bar");
+ Assert.Equal("~/", data.RedirectUrl);
+ }
+
+ [Fact]
+ public void RedirectLocalOnSameHostTest()
+ {
+ RedirectData data = new RedirectData() { RequestUrl = "http://foo" };
+ var context = GetContextForRedirectLocal(data);
+ context.RedirectLocal("http://foo/bar/baz");
+ Assert.Equal("~/", data.RedirectUrl);
+ context.RedirectLocal("http://foo/bar/baz/woot.htm");
+ Assert.Equal("~/", data.RedirectUrl);
+ }
+
+ [Fact]
+ public void RedirectLocalRelativeTest()
+ {
+ RedirectData data = new RedirectData() { RequestUrl = "http://foo" };
+ var context = GetContextForRedirectLocal(data);
+ context.RedirectLocal("/bar");
+ Assert.Equal("/bar", data.RedirectUrl);
+ context.RedirectLocal("/bar/hey.you");
+ Assert.Equal("/bar/hey.you", data.RedirectUrl);
+ }
+ }
+}
diff --git a/test/System.Web.WebPages.Test/Extensions/HttpRequestExtensionsTest.cs b/test/System.Web.WebPages.Test/Extensions/HttpRequestExtensionsTest.cs
new file mode 100644
index 00000000..e7975126
--- /dev/null
+++ b/test/System.Web.WebPages.Test/Extensions/HttpRequestExtensionsTest.cs
@@ -0,0 +1,130 @@
+using System;
+using System.Web;
+using System.Web.WebPages;
+using Moq;
+using Xunit;
+
+namespace Microsoft.WebPages.Test.Helpers
+{
+ public class HttpRequestExtensionsTest
+ {
+ private static HttpRequestBase GetRequestForIsUrlLocalToHost(string url)
+ {
+ Mock<HttpContextBase> contextMock = new Mock<HttpContextBase>();
+ contextMock.Setup(context => context.Request.Url).Returns(new Uri(url));
+ return contextMock.Object.Request;
+ }
+
+ [Fact]
+ public void IsUrlLocalToHost_ReturnsFalseOnEmpty()
+ {
+ var request = GetRequestForIsUrlLocalToHost("http://www.mysite.com/");
+ Assert.False(request.IsUrlLocalToHost(null));
+ Assert.False(request.IsUrlLocalToHost(String.Empty));
+ }
+
+ [Fact]
+ public void IsUrlLocalToHost_AcceptsRootedUrls()
+ {
+ var helper = GetRequestForIsUrlLocalToHost("http://www.mysite.com/");
+ Assert.True(helper.IsUrlLocalToHost("/foo.html"));
+ Assert.True(helper.IsUrlLocalToHost("/www.hackerz.com"));
+ Assert.True(helper.IsUrlLocalToHost("/"));
+ }
+
+ [Fact]
+ public void IsUrlLocalToHost_AcceptsApplicationRelativeUrls()
+ {
+ var helper = GetRequestForIsUrlLocalToHost("http://www.mysite.com/");
+ Assert.True(helper.IsUrlLocalToHost("~/"));
+ Assert.True(helper.IsUrlLocalToHost("~/foobar.html"));
+ }
+
+ [Fact]
+ public void IsUrlLocalToHost_RejectsRelativeUrls()
+ {
+ var helper = GetRequestForIsUrlLocalToHost("http://www.mysite.com/");
+ Assert.False(helper.IsUrlLocalToHost("foobar.html"));
+ Assert.False(helper.IsUrlLocalToHost("../foobar.html"));
+ Assert.False(helper.IsUrlLocalToHost("fold/foobar.html"));
+ }
+
+ [Fact]
+ public void IsUrlLocalToHost_RejectValidButUnsafeRelativeUrls()
+ {
+ var helper = GetRequestForIsUrlLocalToHost("http://www.mysite.com/");
+ Assert.False(helper.IsUrlLocalToHost("http:/foobar.html"));
+ Assert.False(helper.IsUrlLocalToHost("hTtP:foobar.html"));
+ Assert.False(helper.IsUrlLocalToHost("http:/www.hackerz.com"));
+ Assert.False(helper.IsUrlLocalToHost("HtTpS:/www.hackerz.com"));
+ }
+
+ [Fact]
+ public void IsUrlLocalToHost_RejectsUrlsOnTheSameHost()
+ {
+ var helper = GetRequestForIsUrlLocalToHost("http://www.mysite.com/");
+ Assert.False(helper.IsUrlLocalToHost("http://www.mysite.com/appDir/foobar.html"));
+ Assert.False(helper.IsUrlLocalToHost("http://WWW.MYSITE.COM"));
+ }
+
+ [Fact]
+ public void IsUrlLocalToHost_RejectsUrlsOnLocalHost()
+ {
+ var helper = GetRequestForIsUrlLocalToHost("http://www.mysite.com/");
+ Assert.False(helper.IsUrlLocalToHost("http://localhost/foobar.html"));
+ Assert.False(helper.IsUrlLocalToHost("http://127.0.0.1/foobar.html"));
+ }
+
+ [Fact]
+ public void IsUrlLocalToHost_RejectsUrlsOnTheSameHostButDifferentScheme()
+ {
+ var helper = GetRequestForIsUrlLocalToHost("http://www.mysite.com/");
+ Assert.False(helper.IsUrlLocalToHost("https://www.mysite.com/"));
+ }
+
+ [Fact]
+ public void IsUrlLocalToHost_RejectsUrlsOnDifferentHost()
+ {
+ var helper = GetRequestForIsUrlLocalToHost("http://www.mysite.com/");
+ Assert.False(helper.IsUrlLocalToHost("http://www.hackerz.com"));
+ Assert.False(helper.IsUrlLocalToHost("https://www.hackerz.com"));
+ Assert.False(helper.IsUrlLocalToHost("hTtP://www.hackerz.com"));
+ Assert.False(helper.IsUrlLocalToHost("HtTpS://www.hackerz.com"));
+ }
+
+ [Fact]
+ public void IsUrlLocalToHost_RejectsUrlsWithTooManySchemeSeparatorCharacters()
+ {
+ var helper = GetRequestForIsUrlLocalToHost("http://www.mysite.com/");
+ Assert.False(helper.IsUrlLocalToHost("http://///www.hackerz.com/foobar.html"));
+ Assert.False(helper.IsUrlLocalToHost("https://///www.hackerz.com/foobar.html"));
+ Assert.False(helper.IsUrlLocalToHost("HtTpS://///www.hackerz.com/foobar.html"));
+
+ Assert.False(helper.IsUrlLocalToHost("http:///www.hackerz.com/foobar.html"));
+ Assert.False(helper.IsUrlLocalToHost("http:////www.hackerz.com/foobar.html"));
+ Assert.False(helper.IsUrlLocalToHost("http://///www.hackerz.com/foobar.html"));
+ }
+
+ [Fact]
+ public void IsUrlLocalToHost_RejectsUrlsWithMissingSchemeName()
+ {
+ var helper = GetRequestForIsUrlLocalToHost("http://www.mysite.com/");
+ Assert.False(helper.IsUrlLocalToHost("//www.hackerz.com"));
+ Assert.False(helper.IsUrlLocalToHost("//www.hackerz.com?"));
+ Assert.False(helper.IsUrlLocalToHost("//www.hackerz.com:80"));
+ Assert.False(helper.IsUrlLocalToHost("//www.hackerz.com/foobar.html"));
+ Assert.False(helper.IsUrlLocalToHost("///www.hackerz.com"));
+ Assert.False(helper.IsUrlLocalToHost("//////www.hackerz.com"));
+ }
+
+ [Fact]
+ public void IsUrlLocalToHost_RejectsInvalidUrls()
+ {
+ var helper = GetRequestForIsUrlLocalToHost("http://www.mysite.com/");
+ Assert.False(helper.IsUrlLocalToHost(@"http:\\www.hackerz.com"));
+ Assert.False(helper.IsUrlLocalToHost(@"http:\\www.hackerz.com\"));
+ Assert.False(helper.IsUrlLocalToHost(@"/\"));
+ Assert.False(helper.IsUrlLocalToHost(@"/\foo"));
+ }
+ }
+}
diff --git a/test/System.Web.WebPages.Test/Extensions/HttpResponseExtensionsTest.cs b/test/System.Web.WebPages.Test/Extensions/HttpResponseExtensionsTest.cs
new file mode 100644
index 00000000..28820664
--- /dev/null
+++ b/test/System.Web.WebPages.Test/Extensions/HttpResponseExtensionsTest.cs
@@ -0,0 +1,128 @@
+using System;
+using System.IO;
+using System.Net;
+using System.Text;
+using System.Web;
+using System.Web.WebPages;
+using Moq;
+using Xunit;
+
+namespace Microsoft.WebPages.Test.Helpers
+{
+ public class HttpResponseExtensionsTest
+ {
+ HttpResponseBase _response;
+ string _redirectUrl;
+ StringBuilder _output;
+ Stream _outputStream;
+
+ public HttpResponseExtensionsTest()
+ {
+ _output = new StringBuilder();
+ _outputStream = new MemoryStream();
+ Mock<HttpResponseBase> mockResponse = new Mock<HttpResponseBase>();
+ mockResponse.SetupProperty(response => response.StatusCode);
+ mockResponse.SetupProperty(response => response.ContentType);
+ mockResponse.Setup(response => response.Redirect(It.IsAny<string>())).Callback((string url) => _redirectUrl = url);
+ mockResponse.Setup(response => response.Write(It.IsAny<string>())).Callback((string str) => _output.Append(str));
+ mockResponse.Setup(response => response.OutputStream).Returns(_outputStream);
+ mockResponse.Setup(response => response.OutputStream).Returns(_outputStream);
+ mockResponse.Setup(response => response.Output).Returns(new StringWriter(_output));
+ _response = mockResponse.Object;
+ }
+
+ [Fact]
+ public void SetStatusWithIntTest()
+ {
+ int status = 200;
+ _response.SetStatus(status);
+ Assert.Equal(status, _response.StatusCode);
+ }
+
+ [Fact]
+ public void SetStatusWithHttpStatusCodeTest()
+ {
+ HttpStatusCode status = HttpStatusCode.Forbidden;
+ _response.SetStatus(status);
+ Assert.Equal((int)status, _response.StatusCode);
+ }
+
+ [Fact]
+ public void WriteBinaryTest()
+ {
+ string foo = "I am a string, please don't mangle me!";
+ _response.WriteBinary(ASCIIEncoding.ASCII.GetBytes(foo));
+ _outputStream.Flush();
+ _outputStream.Position = 0;
+ StreamReader reader = new StreamReader(_outputStream);
+ Assert.Equal(foo, reader.ReadToEnd());
+ }
+
+ [Fact]
+ public void WriteBinaryWithMimeTypeTest()
+ {
+ string foo = "I am a string, please don't mangle me!";
+ string mimeType = "mime/foo";
+ _response.WriteBinary(ASCIIEncoding.ASCII.GetBytes(foo), mimeType);
+ _outputStream.Flush();
+ _outputStream.Position = 0;
+ StreamReader reader = new StreamReader(_outputStream);
+ Assert.Equal(foo, reader.ReadToEnd());
+ Assert.Equal(mimeType, _response.ContentType);
+ }
+
+ [Fact]
+ public void OutputCacheSetsExpirationTimeBasedOnCurrentContext()
+ {
+ // Arrange
+ var timestamp = new DateTime(2011, 1, 1, 0, 0, 0);
+ var context = new Mock<HttpContextBase>();
+ context.SetupGet(c => c.Timestamp).Returns(timestamp);
+ var response = new Mock<HttpResponseBase>().Object;
+
+ var cache = new Mock<HttpCachePolicyBase>();
+ cache.Setup(c => c.SetCacheability(It.Is<HttpCacheability>(p => p == HttpCacheability.Public))).Verifiable();
+ cache.Setup(c => c.SetExpires(It.Is<DateTime>(p => p == timestamp.AddSeconds(20)))).Verifiable();
+ cache.Setup(c => c.SetMaxAge(It.Is<TimeSpan>(p => p == TimeSpan.FromSeconds(20)))).Verifiable();
+ cache.Setup(c => c.SetValidUntilExpires(It.Is<bool>(p => p == true))).Verifiable();
+ cache.Setup(c => c.SetLastModified(It.Is<DateTime>(p => p == timestamp))).Verifiable();
+ cache.Setup(c => c.SetSlidingExpiration(It.Is<bool>(p => p == false))).Verifiable();
+
+ // Act
+ ResponseExtensions.OutputCache(context.Object, cache.Object, 20, false, null, null, null, HttpCacheability.Public);
+
+ // Assert
+ cache.VerifyAll();
+ }
+
+ [Fact]
+ public void OutputCacheSetsVaryByValues()
+ {
+ // Arrange
+ var timestamp = new DateTime(2011, 1, 1, 0, 0, 0);
+ var context = new Mock<HttpContextBase>();
+ context.SetupGet(c => c.Timestamp).Returns(timestamp);
+ var response = new Mock<HttpResponseBase>().Object;
+
+ var varyByParams = new HttpCacheVaryByParams();
+ var varyByHeader = new HttpCacheVaryByHeaders();
+ var varyByContentEncoding = new HttpCacheVaryByContentEncodings();
+
+ var cache = new Mock<HttpCachePolicyBase>();
+ cache.SetupGet(c => c.VaryByParams).Returns(varyByParams);
+ cache.SetupGet(c => c.VaryByHeaders).Returns(varyByHeader);
+ cache.SetupGet(c => c.VaryByContentEncodings).Returns(varyByContentEncoding);
+
+ // Act
+ ResponseExtensions.OutputCache(context.Object, cache.Object, 20, false, new[] { "foo" }, new[] { "bar", "bar2" },
+ new[] { "baz", "baz2" }, HttpCacheability.Public);
+
+ // Assert
+ Assert.Equal(varyByParams["foo"], true);
+ Assert.Equal(varyByHeader["bar"], true);
+ Assert.Equal(varyByHeader["bar2"], true);
+ Assert.Equal(varyByContentEncoding["baz"], true);
+ Assert.Equal(varyByContentEncoding["baz2"], true);
+ }
+ }
+}
diff --git a/test/System.Web.WebPages.Test/Extensions/StringExtensionsTest.cs b/test/System.Web.WebPages.Test/Extensions/StringExtensionsTest.cs
new file mode 100644
index 00000000..8fe2edf8
--- /dev/null
+++ b/test/System.Web.WebPages.Test/Extensions/StringExtensionsTest.cs
@@ -0,0 +1,251 @@
+using System.Globalization;
+using System.Web.TestUtil;
+using Xunit;
+using Xunit.Extensions;
+
+namespace System.Web.WebPages.Test
+{
+ public class StringExtensionsTest
+ {
+ [Fact]
+ public void IsIntTests()
+ {
+ Assert.False("1.3".IsInt());
+ Assert.False(".13".IsInt());
+ Assert.False("0.0".IsInt());
+ Assert.False("12345678900123456".IsInt());
+ Assert.False("gooblygook".IsInt());
+ Assert.True("0".IsInt());
+ Assert.True("123456".IsInt());
+ Assert.True(Int32.MaxValue.ToString().IsInt());
+ Assert.True(Int32.MinValue.ToString().IsInt());
+ Assert.False(((string)null).IsInt());
+ }
+
+ [Fact]
+ public void AsIntBasicTests()
+ {
+ Assert.Equal(-123, "-123".AsInt());
+ Assert.Equal(12345, "12345".AsInt());
+ Assert.Equal(0, "0".AsInt());
+ }
+
+ [Fact]
+ public void AsIntDefaultTests()
+ {
+ // Illegal values default to 0
+ Assert.Equal(0, "-100000000000000000000000".AsInt());
+
+ // Illegal values default to 0
+ Assert.Equal(0, "adlfkj".AsInt());
+
+ Assert.Equal(-1, "adlfkj".AsInt(-1));
+ Assert.Equal(-1, "-100000000000000000000000".AsInt(-1));
+ }
+
+ [Fact]
+ public void IsDecimalTests()
+ {
+ Assert.True("1.3".IsDecimal());
+ Assert.True(".13".IsDecimal());
+ Assert.True("0.0".IsDecimal());
+ Assert.True("12345678900123456".IsDecimal());
+ Assert.True("0".IsDecimal());
+ Assert.True("123456".IsDecimal());
+ Assert.True(decimal.MaxValue.ToString().IsDecimal());
+ Assert.True(decimal.MinValue.ToString().IsDecimal());
+ Assert.False("gooblygook".IsDecimal());
+ Assert.False("..0".IsDecimal());
+ Assert.False(((string)null).IsDecimal());
+ }
+
+ [Fact]
+ public void AsDecimalBasicTests()
+ {
+ Assert.Equal(-123m, "-123".AsDecimal());
+ Assert.Equal(9.99m, "9.99".AsDecimal());
+ Assert.Equal(0m, "0".AsDecimal());
+ Assert.Equal(-1.1111m, "-1.1111".AsDecimal());
+ }
+
+ [Fact]
+ public void AsDecimalDefaultTests()
+ {
+ // Illegal values default to 0
+ Assert.Equal(0m, "abc".AsDecimal());
+
+ Assert.Equal(-1.11m, "adlfkj".AsDecimal(-1.11m));
+ }
+
+ [Fact]
+ public void AsDecimalUsesCurrentCulture()
+ {
+ decimal value = 12345.00M;
+ using (new CultureReplacer("ar-DZ"))
+ {
+ Assert.Equal(value.ToString(CultureInfo.CurrentCulture), "12345.00");
+ Assert.Equal(value.ToString(), "12345.00");
+ }
+
+ using (new CultureReplacer("bg-BG"))
+ {
+ Assert.Equal(value.ToString(CultureInfo.CurrentCulture), "12345,00");
+ Assert.Equal(value.ToString(), "12345,00");
+ }
+ }
+
+ [Fact]
+ public void IsAndAsDecimalsUsesCurrentCulture()
+ {
+ // Pretty identical to the earlier test case. This was a post on the forums, making sure it works.
+ using (new CultureReplacer(culture: "lt-LT"))
+ {
+ Assert.False("1.2".IsDecimal());
+ Assert.True("1,2".IsDecimal());
+
+ Assert.Equal(1.2M, "1,2".AsDecimal());
+ Assert.Equal(0, "1.2".AsDecimal());
+ }
+ }
+
+ [Fact]
+ public void IsFloatTests()
+ {
+ Assert.True("1.3".IsFloat());
+ Assert.True(".13".IsFloat());
+ Assert.True("0.0".IsFloat());
+ Assert.True("12345678900123456".IsFloat());
+ Assert.True("0".IsFloat());
+ Assert.True("123456".IsFloat());
+ Assert.True(float.MaxValue.ToString().IsFloat());
+ Assert.True(float.MinValue.ToString().IsFloat());
+ Assert.True(float.NegativeInfinity.ToString().IsFloat());
+ Assert.True(float.PositiveInfinity.ToString().IsFloat());
+ Assert.False("gooblygook".IsFloat());
+ Assert.False(((string)null).IsFloat());
+ }
+
+ [Fact]
+ public void AsFloatBasicTests()
+ {
+ Assert.Equal(-123f, "-123".AsFloat());
+ Assert.Equal(9.99f, "9.99".AsFloat());
+ Assert.Equal(0f, "0".AsFloat());
+ Assert.Equal(-1.1111f, "-1.1111".AsFloat());
+ }
+
+ [Fact]
+ public void AsFloatDefaultTests()
+ {
+ // Illegal values default to 0
+ Assert.Equal(0f, "abc".AsFloat());
+
+ Assert.Equal(-1.11f, "adlfkj".AsFloat(-1.11f));
+ }
+
+ [Fact]
+ public void IsDateTimeTests()
+ {
+ using (new CultureReplacer())
+ {
+ Assert.True("Sat, 01 Nov 2008 19:35:00 GMT".IsDateTime());
+ Assert.True("1/5/1979".IsDateTime());
+ Assert.False("0".IsDateTime());
+ Assert.True(DateTime.MaxValue.ToString().IsDateTime());
+ Assert.True(DateTime.MinValue.ToString().IsDateTime());
+ Assert.True(new DateTime(2010, 12, 21).ToUniversalTime().ToString().IsDateTime());
+ Assert.False("gooblygook".IsDateTime());
+ Assert.False(((string)null).IsDateTime());
+ }
+ }
+
+ /// <remarks>Tests for bug 153439</remarks>
+ [Fact]
+ public void IsDateTimeUsesLocalCulture()
+ {
+ using (new CultureReplacer(culture: "en-gb"))
+ {
+ Assert.True(new DateTime(2010, 12, 21).ToString().IsDateTime());
+ Assert.True(new DateTime(2010, 12, 11).ToString().IsDateTime());
+ Assert.True("2010/01/01".IsDateTime());
+ Assert.True("12/01/2010".IsDateTime());
+ Assert.True("12/12/2010".IsDateTime());
+ Assert.True("13/12/2010".IsDateTime());
+ Assert.True("2010-12-01".IsDateTime());
+ Assert.True("2010-12-13".IsDateTime());
+
+ Assert.False("12/13/2010".IsDateTime());
+ Assert.False("13/13/2010".IsDateTime());
+ Assert.False("2010-13-12".IsDateTime());
+ }
+ }
+
+ [Fact]
+ public void AsDateTimeBasicTests()
+ {
+ using (new CultureReplacer())
+ {
+ Assert.Equal(DateTime.Parse("1/14/1979"), "1/14/1979".AsDateTime());
+ Assert.Equal(DateTime.Parse("Sat, 01 Nov 2008 19:35:00 GMT"), "Sat, 01 Nov 2008 19:35:00 GMT".AsDateTime());
+ }
+ }
+
+ [Theory]
+ [InlineData(new object[] { "en-us" })]
+ [InlineData(new object[] { "en-gb" })]
+ [InlineData(new object[] { "ug" })]
+ [InlineData(new object[] { "lt-LT" })]
+ public void AsDateTimeDefaultTests(string culture)
+ {
+ using (new CultureReplacer(culture))
+ {
+ // Illegal values default to MinTime
+ Assert.Equal(DateTime.MinValue, "1".AsDateTime());
+
+ DateTime defaultV = new DateTime(1979, 01, 05);
+ Assert.Equal(defaultV, "adlfkj".AsDateTime(defaultV));
+ Assert.Equal(defaultV, "Jn 69".AsDateTime(defaultV));
+ }
+ }
+
+ [Theory]
+ [InlineData(new object[] { "en-us" })]
+ [InlineData(new object[] { "en-gb" })]
+ [InlineData(new object[] { "lt-LT" })]
+ public void IsDateTimeDefaultTests(string culture)
+ {
+ using (new CultureReplacer(culture))
+ {
+ var dateTime = new DateTime(2011, 10, 25, 10, 10, 00);
+ Assert.True(dateTime.ToShortDateString().IsDateTime());
+ Assert.True(dateTime.ToString().IsDateTime());
+ Assert.True(dateTime.ToLongDateString().IsDateTime());
+ }
+ }
+
+ [Fact]
+ public void IsBoolTests()
+ {
+ Assert.True("TRUE".IsBool());
+ Assert.True("TRUE ".IsBool());
+ Assert.True("false".IsBool());
+ Assert.False("falsey".IsBool());
+ Assert.False("gooblygook".IsBool());
+ Assert.False("".IsBool());
+ Assert.False(((string)null).IsBool());
+ }
+
+ [Fact]
+ public void AsBoolTests()
+ {
+ Assert.True("TRuE".AsBool());
+ Assert.False("False".AsBool());
+ Assert.False("Die".AsBool(false));
+ Assert.True("true!".AsBool(true));
+ Assert.False("".AsBool());
+ Assert.False(((string)null).AsBool());
+ Assert.True("".AsBool(true));
+ Assert.True(((string)null).AsBool(true));
+ }
+ }
+}
diff --git a/test/System.Web.WebPages.Test/Helpers/AntiForgeryDataSerializerTest.cs b/test/System.Web.WebPages.Test/Helpers/AntiForgeryDataSerializerTest.cs
new file mode 100644
index 00000000..201a7aae
--- /dev/null
+++ b/test/System.Web.WebPages.Test/Helpers/AntiForgeryDataSerializerTest.cs
@@ -0,0 +1,103 @@
+using System.Web.Mvc;
+using System.Web.WebPages.TestUtils;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Helpers.Test
+{
+ public class AntiForgeryDataSerializerTest
+ {
+ [Fact]
+ public void GuardClauses()
+ {
+ // Arrange
+ AntiForgeryDataSerializer serializer = new AntiForgeryDataSerializer();
+
+ // Act & assert
+ Assert.ThrowsArgumentNull(
+ () => serializer.Serialize(null),
+ "token"
+ );
+ Assert.ThrowsArgumentNullOrEmptyString(
+ () => serializer.Deserialize(null),
+ "serializedToken"
+ );
+ Assert.ThrowsArgumentNullOrEmptyString(
+ () => serializer.Deserialize(String.Empty),
+ "serializedToken"
+ );
+ Assert.Throws<HttpAntiForgeryException>(
+ () => serializer.Deserialize("Corrupted Base-64 Value"),
+ "A required anti-forgery token was not supplied or was invalid."
+ );
+ }
+
+ [Fact]
+ public void DeserializationExceptionDoesNotContainInnerException()
+ {
+ // Arrange
+ AntiForgeryDataSerializer serializer = new AntiForgeryDataSerializer();
+
+ // Act & assert
+ HttpAntiForgeryException exception = null;
+ try
+ {
+ serializer.Deserialize("Can't deserialize this.");
+ }
+ catch (HttpAntiForgeryException ex)
+ {
+ exception = ex;
+ }
+
+ Assert.NotNull(exception);
+ Assert.Null(exception.InnerException);
+ }
+
+ [Fact]
+ public void CanRoundTripData()
+ {
+ // Arrange
+ AntiForgeryDataSerializer serializer = new AntiForgeryDataSerializer
+ {
+ Decoder = value => Convert.FromBase64String(value),
+ Encoder = bytes => Convert.ToBase64String(bytes),
+ };
+ AntiForgeryData input = new AntiForgeryData
+ {
+ Salt = "The Salt",
+ Username = "The Username",
+ Value = "The Value",
+ CreationDate = DateTime.Now,
+ };
+
+ // Act
+ AntiForgeryData output = serializer.Deserialize(serializer.Serialize(input));
+
+ // Assert
+ Assert.NotNull(output);
+ Assert.Equal(input.Salt, output.Salt);
+ Assert.Equal(input.Username, output.Username);
+ Assert.Equal(input.Value, output.Value);
+ Assert.Equal(input.CreationDate, output.CreationDate);
+ }
+
+ [Fact]
+ public void HexDigitConvertsIntegersToHexCharsCorrectly()
+ {
+ for (int i = 0; i < 0x10; i++)
+ {
+ Assert.Equal(i.ToString("X")[0], AntiForgeryDataSerializer.HexDigit(i));
+ }
+ }
+
+ [Fact]
+ public void HexValueConvertsCharValuesToIntegersCorrectly()
+ {
+ for (int i = 0; i < 0x10; i++)
+ {
+ var hexChar = i.ToString("X")[0];
+ Assert.Equal(i, AntiForgeryDataSerializer.HexValue(hexChar));
+ }
+ }
+ }
+}
diff --git a/test/System.Web.WebPages.Test/Helpers/AntiForgeryDataTest.cs b/test/System.Web.WebPages.Test/Helpers/AntiForgeryDataTest.cs
new file mode 100644
index 00000000..442b0c9f
--- /dev/null
+++ b/test/System.Web.WebPages.Test/Helpers/AntiForgeryDataTest.cs
@@ -0,0 +1,168 @@
+using System.Security.Principal;
+using Moq;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Helpers.Test
+{
+ public class AntiForgeryDataTest
+ {
+ [Fact]
+ public void CopyConstructor()
+ {
+ // Arrange
+ AntiForgeryData originalToken = new AntiForgeryData()
+ {
+ CreationDate = DateTime.Now,
+ Salt = "some salt",
+ Value = "some value"
+ };
+
+ // Act
+ AntiForgeryData newToken = new AntiForgeryData(originalToken);
+
+ // Assert
+ Assert.Equal(originalToken.CreationDate, newToken.CreationDate);
+ Assert.Equal(originalToken.Salt, newToken.Salt);
+ Assert.Equal(originalToken.Value, newToken.Value);
+ }
+
+ [Fact]
+ public void CopyConstructorThrowsIfTokenIsNull()
+ {
+ // Act & Assert
+ Assert.ThrowsArgumentNull(
+ delegate { new AntiForgeryData(null); }, "token");
+ }
+
+ [Fact]
+ public void CreationDateProperty()
+ {
+ // Arrange
+ AntiForgeryData token = new AntiForgeryData();
+
+ // Act & Assert
+ var now = DateTime.UtcNow;
+ token.CreationDate = now;
+ Assert.Equal(now, token.CreationDate);
+ }
+
+ [Fact]
+ public void GetAntiForgeryTokenNameReturnsEncodedCookieNameIfAppPathIsNotEmpty()
+ {
+ // Arrange
+ // the string below (as UTF-8 bytes) base64-encodes to "Pz4/Pj8+Pz4/Pj8+Pz4/Pg=="
+ string original = "?>?>?>?>?>?>?>?>";
+
+ // Act
+ string tokenName = AntiForgeryData.GetAntiForgeryTokenName(original);
+
+ // Assert
+ Assert.Equal("__RequestVerificationToken_Pz4-Pj8.Pz4-Pj8.Pz4-Pg__", tokenName);
+ }
+
+ [Fact]
+ public void GetAntiForgeryTokenNameReturnsFieldNameIfAppPathIsNull()
+ {
+ // Act
+ string tokenName = AntiForgeryData.GetAntiForgeryTokenName(null);
+
+ // Assert
+ Assert.Equal("__RequestVerificationToken", tokenName);
+ }
+
+ [Fact]
+ public void GetUsername_ReturnsEmptyStringIfIdentityIsNull()
+ {
+ // Arrange
+ Mock<IPrincipal> mockPrincipal = new Mock<IPrincipal>();
+ mockPrincipal.Setup(o => o.Identity).Returns((IIdentity)null);
+
+ // Act
+ string username = AntiForgeryData.GetUsername(mockPrincipal.Object);
+
+ // Assert
+ Assert.Equal("", username);
+ }
+
+ [Fact]
+ public void GetUsername_ReturnsEmptyStringIfPrincipalIsNull()
+ {
+ // Act
+ string username = AntiForgeryData.GetUsername(null);
+
+ // Assert
+ Assert.Equal("", username);
+ }
+
+ [Fact]
+ public void GetUsername_ReturnsEmptyStringIfUserNotAuthenticated()
+ {
+ // Arrange
+ Mock<IPrincipal> mockPrincipal = new Mock<IPrincipal>();
+ mockPrincipal.Setup(o => o.Identity.IsAuthenticated).Returns(false);
+ mockPrincipal.Setup(o => o.Identity.Name).Returns("SampleName");
+
+ // Act
+ string username = AntiForgeryData.GetUsername(mockPrincipal.Object);
+
+ // Assert
+ Assert.Equal("", username);
+ }
+
+ [Fact]
+ public void GetUsername_ReturnsUsernameIfUserIsAuthenticated()
+ {
+ // Arrange
+ Mock<IPrincipal> mockPrincipal = new Mock<IPrincipal>();
+ mockPrincipal.Setup(o => o.Identity.IsAuthenticated).Returns(true);
+ mockPrincipal.Setup(o => o.Identity.Name).Returns("SampleName");
+
+ // Act
+ string username = AntiForgeryData.GetUsername(mockPrincipal.Object);
+
+ // Assert
+ Assert.Equal("SampleName", username);
+ }
+
+ [Fact]
+ public void NewToken()
+ {
+ // Act
+ AntiForgeryData token = AntiForgeryData.NewToken();
+
+ // Assert
+ int valueLength = Convert.FromBase64String(token.Value).Length;
+ Assert.Equal(16, valueLength);
+ Assert.NotEqual(default(DateTime), token.CreationDate);
+ }
+
+ [Fact]
+ public void SaltProperty()
+ {
+ // Arrange
+ AntiForgeryData token = new AntiForgeryData();
+
+ // Act & Assert
+ Assert.Equal(String.Empty, token.Salt);
+ token.Salt = null;
+ Assert.Equal(String.Empty, token.Salt);
+ token.Salt = String.Empty;
+ Assert.Equal(String.Empty, token.Salt);
+ }
+
+ [Fact]
+ public void ValueProperty()
+ {
+ // Arrange
+ AntiForgeryData token = new AntiForgeryData();
+
+ // Act & Assert
+ Assert.Equal(String.Empty, token.Value);
+ token.Value = null;
+ Assert.Equal(String.Empty, token.Value);
+ token.Value = String.Empty;
+ Assert.Equal(String.Empty, token.Value);
+ }
+ }
+}
diff --git a/test/System.Web.WebPages.Test/Helpers/AntiForgeryTest.cs b/test/System.Web.WebPages.Test/Helpers/AntiForgeryTest.cs
new file mode 100644
index 00000000..db26de50
--- /dev/null
+++ b/test/System.Web.WebPages.Test/Helpers/AntiForgeryTest.cs
@@ -0,0 +1,36 @@
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Helpers.Test
+{
+ public class AntiForgeryTest
+ {
+ private static string _antiForgeryTokenCookieName = AntiForgeryData.GetAntiForgeryTokenName("/SomeAppPath");
+
+ [Fact]
+ public void GetHtml_ThrowsWhenNotCalledInWebContext()
+ {
+ Assert.Throws<ArgumentException>(() => AntiForgery.GetHtml(),
+ "An HttpContext is required to perform this operation. Check that this operation is being performed during a web request.");
+ }
+
+ [Fact]
+ public void GetHtml_ThrowsOnNullContext()
+ {
+ Assert.ThrowsArgumentNull(() => AntiForgery.GetHtml(null, null, null, null), "httpContext");
+ }
+
+ [Fact]
+ public void Validate_ThrowsWhenNotCalledInWebContext()
+ {
+ Assert.Throws<ArgumentException>(() => AntiForgery.Validate(),
+ "An HttpContext is required to perform this operation. Check that this operation is being performed during a web request.");
+ }
+
+ [Fact]
+ public void Validate_ThrowsOnNullContext()
+ {
+ Assert.ThrowsArgumentNull(() => AntiForgery.Validate(httpContext: null, salt: null), "httpContext");
+ }
+ }
+}
diff --git a/test/System.Web.WebPages.Test/Helpers/AntiForgeryWorkerTest.cs b/test/System.Web.WebPages.Test/Helpers/AntiForgeryWorkerTest.cs
new file mode 100644
index 00000000..81cd287d
--- /dev/null
+++ b/test/System.Web.WebPages.Test/Helpers/AntiForgeryWorkerTest.cs
@@ -0,0 +1,237 @@
+using System.Collections.Specialized;
+using System.Globalization;
+using System.Text.RegularExpressions;
+using System.Web.Mvc;
+using Moq;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+using Match = System.Text.RegularExpressions.Match;
+
+namespace System.Web.Helpers.Test
+{
+ public class AntiForgeryWorkerTest
+ {
+ private static string _antiForgeryTokenCookieName = AntiForgeryData.GetAntiForgeryTokenName("/SomeAppPath");
+ private const string _serializedValuePrefix = @"<input name=""__RequestVerificationToken"" type=""hidden"" value=""Creation: ";
+ private const string _someValueSuffix = @", Value: some value, Salt: some other salt, Username: username"" />";
+ private readonly Regex _randomFormValueSuffixRegex = new Regex(@", Value: (?<value>[A-Za-z0-9/\+=]{24}), Salt: some other salt, Username: username"" />$");
+ private readonly Regex _randomCookieValueSuffixRegex = new Regex(@", Value: (?<value>[A-Za-z0-9/\+=]{24}), Salt: ");
+
+ [Fact]
+ public void Serializer_DefaultValueIsAntiForgeryDataSerializer()
+ {
+ Assert.Same(typeof(AntiForgeryDataSerializer), new AntiForgeryWorker().Serializer.GetType());
+ }
+
+ [Fact]
+ public void GetHtml_ReturnsFormFieldAndSetsCookieValueIfDoesNotExist()
+ {
+ // Arrange
+ AntiForgeryWorker worker = new AntiForgeryWorker()
+ {
+ Serializer = new DummyAntiForgeryTokenSerializer()
+ };
+ var context = CreateContext();
+
+ // Act
+ string formValue = worker.GetHtml(context, "some other salt", null, null).ToHtmlString();
+
+ // Assert
+ Assert.True(formValue.StartsWith(_serializedValuePrefix), "Form value prefix did not match.");
+
+ Match formMatch = _randomFormValueSuffixRegex.Match(formValue);
+ string formTokenValue = formMatch.Groups["value"].Value;
+
+ HttpCookie cookie = context.Response.Cookies[_antiForgeryTokenCookieName];
+ Assert.NotNull(cookie);
+ Assert.True(cookie.HttpOnly, "Cookie should have HTTP-only flag set.");
+ Assert.True(String.IsNullOrEmpty(cookie.Domain), "Domain should not have been set.");
+ Assert.Equal("/", cookie.Path);
+
+ Match cookieMatch = _randomCookieValueSuffixRegex.Match(cookie.Value);
+ string cookieTokenValue = cookieMatch.Groups["value"].Value;
+
+ Assert.Equal(formTokenValue, cookieTokenValue);
+ }
+
+ [Fact]
+ public void GetHtml_SetsCookieDomainAndPathIfSpecified()
+ {
+ // Arrange
+ AntiForgeryWorker worker = new AntiForgeryWorker()
+ {
+ Serializer = new DummyAntiForgeryTokenSerializer()
+ };
+ var context = CreateContext();
+
+ // Act
+ string formValue = worker.GetHtml(context, "some other salt", "theDomain", "thePath").ToHtmlString();
+
+ // Assert
+ Assert.True(formValue.StartsWith(_serializedValuePrefix), "Form value prefix did not match.");
+
+ Match formMatch = _randomFormValueSuffixRegex.Match(formValue);
+ string formTokenValue = formMatch.Groups["value"].Value;
+
+ HttpCookie cookie = context.Response.Cookies[_antiForgeryTokenCookieName];
+ Assert.NotNull(cookie);
+ Assert.True(cookie.HttpOnly, "Cookie should have HTTP-only flag set.");
+ Assert.Equal("theDomain", cookie.Domain);
+ Assert.Equal("thePath", cookie.Path);
+
+ Match cookieMatch = _randomCookieValueSuffixRegex.Match(cookie.Value);
+ string cookieTokenValue = cookieMatch.Groups["value"].Value;
+
+ Assert.Equal(formTokenValue, cookieTokenValue);
+ }
+
+ [Fact]
+ public void GetHtml_ReusesCookieValueIfExistsAndIsValid()
+ {
+ // Arrange
+ AntiForgeryWorker worker = new AntiForgeryWorker()
+ {
+ Serializer = new DummyAntiForgeryTokenSerializer()
+ };
+ var context = CreateContext("2001-01-01:some value:some salt:username");
+
+ // Act
+ string formValue = worker.GetHtml(context, "some other salt", null, null).ToHtmlString();
+
+ // Assert
+ Assert.True(formValue.StartsWith(_serializedValuePrefix), "Form value prefix did not match.");
+ Assert.True(formValue.EndsWith(_someValueSuffix), "Form value suffix did not match.");
+ Assert.Equal(0, context.Response.Cookies.Count);
+ }
+
+ [Fact]
+ public void GetHtml_CreatesNewCookieValueIfCookieExistsButIsNotValid()
+ {
+ // Arrange
+ AntiForgeryWorker worker = new AntiForgeryWorker()
+ {
+ Serializer = new DummyAntiForgeryTokenSerializer()
+ };
+ var context = CreateContext("invalid");
+
+ // Act
+ string formValue = worker.GetHtml(context, "some other salt", null, null).ToHtmlString();
+
+ // Assert
+ Assert.True(formValue.StartsWith(_serializedValuePrefix), "Form value prefix did not match.");
+
+ Match formMatch = _randomFormValueSuffixRegex.Match(formValue);
+ string formTokenValue = formMatch.Groups["value"].Value;
+
+ HttpCookie cookie = context.Response.Cookies[_antiForgeryTokenCookieName];
+ Assert.NotNull(cookie);
+ Assert.True(cookie.HttpOnly, "Cookie should have HTTP-only flag set.");
+ Assert.True(String.IsNullOrEmpty(cookie.Domain), "Domain should not have been set.");
+ Assert.Equal("/", cookie.Path);
+
+ Match cookieMatch = _randomCookieValueSuffixRegex.Match(cookie.Value);
+ string cookieTokenValue = cookieMatch.Groups["value"].Value;
+
+ Assert.Equal(formTokenValue, cookieTokenValue);
+ }
+
+ [Fact]
+ public void Validate_ThrowsIfCookieMissing()
+ {
+ Validate_Helper(null, "2001-01-01:some other value:the real salt:username");
+ }
+
+ [Fact]
+ public void Validate_ThrowsIfCookieValueDoesNotMatchFormValue()
+ {
+ Validate_Helper("2001-01-01:some value:the real salt:username", "2001-01-01:some other value:the real salt:username");
+ }
+
+ [Fact]
+ public void Validate_ThrowsIfFormSaltDoesNotMatchAttributeSalt()
+ {
+ Validate_Helper("2001-01-01:some value:some salt:username", "2001-01-01:some value:some other salt:username");
+ }
+
+ [Fact]
+ public void Validate_ThrowsIfFormValueMissing()
+ {
+ Validate_Helper("2001-01-01:some value:the real salt:username", null);
+ }
+
+ [Fact]
+ public void Validate_ThrowsIfUsernameInFormIsIncorrect()
+ {
+ Validate_Helper("2001-01-01:value:salt:username", "2001-01-01:value:salt:different username");
+ }
+
+ private static void Validate_Helper(string cookieValue, string formValue, string username = "username")
+ {
+ // Arrange
+ //ValidateAntiForgeryTokenAttribute attribute = GetAttribute();
+ var context = CreateContext(cookieValue, formValue, username);
+
+ AntiForgeryWorker worker = new AntiForgeryWorker()
+ {
+ Serializer = new DummyAntiForgeryTokenSerializer()
+ };
+
+ // Act & Assert
+ Assert.Throws<HttpAntiForgeryException>(
+ delegate
+ {
+ //attribute.OnAuthorization(authContext);
+ worker.Validate(context, "the real salt");
+ }, "A required anti-forgery token was not supplied or was invalid.");
+ }
+
+ private static HttpContextBase CreateContext(string cookieValue = null, string formValue = null, string username = "username")
+ {
+ HttpCookieCollection requestCookies = new HttpCookieCollection();
+ if (!String.IsNullOrEmpty(cookieValue))
+ {
+ requestCookies.Set(new HttpCookie(_antiForgeryTokenCookieName, cookieValue));
+ }
+ NameValueCollection formCollection = new NameValueCollection();
+ if (!String.IsNullOrEmpty(formValue))
+ {
+ formCollection.Set(AntiForgeryData.GetAntiForgeryTokenName(null), formValue);
+ }
+
+ Mock<HttpContextBase> mockContext = new Mock<HttpContextBase>();
+ mockContext.Setup(c => c.Request.ApplicationPath).Returns("/SomeAppPath");
+ mockContext.Setup(c => c.Request.Cookies).Returns(requestCookies);
+ mockContext.Setup(c => c.Request.Form).Returns(formCollection);
+ mockContext.Setup(c => c.Response.Cookies).Returns(new HttpCookieCollection());
+ mockContext.Setup(c => c.User.Identity.IsAuthenticated).Returns(true);
+ mockContext.Setup(c => c.User.Identity.Name).Returns(username);
+
+ return mockContext.Object;
+ }
+
+ internal class DummyAntiForgeryTokenSerializer : AntiForgeryDataSerializer
+ {
+ public override string Serialize(AntiForgeryData token)
+ {
+ return String.Format(CultureInfo.InvariantCulture, "Creation: {0}, Value: {1}, Salt: {2}, Username: {3}",
+ token.CreationDate, token.Value, token.Salt, token.Username);
+ }
+
+ public override AntiForgeryData Deserialize(string serializedToken)
+ {
+ if (serializedToken == "invalid")
+ {
+ throw new HttpAntiForgeryException();
+ }
+ string[] parts = serializedToken.Split(':');
+ return new AntiForgeryData()
+ {
+ CreationDate = DateTime.Parse(parts[0], CultureInfo.InvariantCulture),
+ Value = parts[1],
+ Salt = parts[2],
+ Username = parts[3]
+ };
+ }
+ }
+ }
+}
diff --git a/test/System.Web.WebPages.Test/Helpers/UnvalidatedRequestValuesTest.cs b/test/System.Web.WebPages.Test/Helpers/UnvalidatedRequestValuesTest.cs
new file mode 100644
index 00000000..2be42784
--- /dev/null
+++ b/test/System.Web.WebPages.Test/Helpers/UnvalidatedRequestValuesTest.cs
@@ -0,0 +1,78 @@
+using System.Collections.Specialized;
+using System.Web;
+using System.Web.Helpers;
+using Moq;
+using Xunit;
+
+namespace Microsoft.WebPages.Test.Helpers
+{
+ public class UnvalidatedRequestValuesTest
+ {
+ [Fact]
+ public void Constructor_SetsPropertiesCorrectly()
+ {
+ // Arrange
+ NameValueCollection expectedForm = new NameValueCollection();
+ NameValueCollection expectedQueryString = new NameValueCollection();
+
+ // Act
+ UnvalidatedRequestValues unvalidatedValues = new UnvalidatedRequestValues(null, () => expectedForm, () => expectedQueryString);
+
+ // Assert
+ Assert.Same(expectedForm, unvalidatedValues.Form);
+ Assert.Same(expectedQueryString, unvalidatedValues.QueryString);
+ }
+
+ [Fact]
+ public void Indexer_LooksUpValuesInCorrectOrder()
+ {
+ // Order should be QueryString, Form, Cookies, ServerVariables
+
+ // Arrange
+ NameValueCollection queryString = new NameValueCollection()
+ {
+ { "foo", "fooQueryString" }
+ };
+
+ NameValueCollection form = new NameValueCollection()
+ {
+ { "foo", "fooForm" },
+ { "bar", "barForm" },
+ };
+
+ HttpCookieCollection cookies = new HttpCookieCollection()
+ {
+ new HttpCookie("foo", "fooCookie"),
+ new HttpCookie("bar", "barCookie"),
+ new HttpCookie("baz", "bazCookie")
+ };
+
+ NameValueCollection serverVars = new NameValueCollection()
+ {
+ { "foo", "fooServerVars" },
+ { "bar", "barServerVars" },
+ { "baz", "bazServerVars" },
+ { "quux", "quuxServerVars" },
+ };
+ Mock<HttpRequestBase> mockRequest = new Mock<HttpRequestBase>();
+ mockRequest.Setup(o => o.Cookies).Returns(cookies);
+ mockRequest.Setup(o => o.ServerVariables).Returns(serverVars);
+
+ UnvalidatedRequestValues unvalidatedValues = new UnvalidatedRequestValues(mockRequest.Object, () => form, () => queryString);
+
+ // Act
+ string fooValue = unvalidatedValues["foo"];
+ string barValue = unvalidatedValues["bar"];
+ string bazValue = unvalidatedValues["baz"];
+ string quuxValue = unvalidatedValues["quux"];
+ string notFoundValue = unvalidatedValues["not-found"];
+
+ // Assert
+ Assert.Equal("fooQueryString", fooValue);
+ Assert.Equal("barForm", barValue);
+ Assert.Equal("bazCookie", bazValue);
+ Assert.Equal("quuxServerVars", quuxValue);
+ Assert.Null(notFoundValue);
+ }
+ }
+}
diff --git a/test/System.Web.WebPages.Test/Html/CheckBoxTest.cs b/test/System.Web.WebPages.Test/Html/CheckBoxTest.cs
new file mode 100644
index 00000000..bf31d2b4
--- /dev/null
+++ b/test/System.Web.WebPages.Test/Html/CheckBoxTest.cs
@@ -0,0 +1,272 @@
+using System.Collections.Generic;
+using System.Web.WebPages.Html;
+using System.Web.WebPages.TestUtils;
+using Moq;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.WebPages.Test
+{
+ public class CheckBoxTest
+ {
+ [Fact]
+ public void CheckboxWithEmptyNameThrows()
+ {
+ // Arrange
+ HtmlHelper helper = HtmlHelperFactory.Create();
+
+ // Act and assert
+ Assert.ThrowsArgumentNullOrEmptyString(() => helper.CheckBox(null), "name");
+ Assert.ThrowsArgumentNullOrEmptyString(() => helper.CheckBox(String.Empty), "name");
+ }
+
+ [Fact]
+ public void CheckboxWithDefaultArguments()
+ {
+ // Arrange
+ HtmlHelper helper = HtmlHelperFactory.Create();
+
+ // Act
+ var html = helper.CheckBox("foo");
+
+ // Assert
+ Assert.Equal(@"<input id=""foo"" name=""foo"" type=""checkbox"" />",
+ html.ToHtmlString());
+ }
+
+ [Fact]
+ public void CheckboxWithObjectAttributes()
+ {
+ // Arrange
+ HtmlHelper helper = HtmlHelperFactory.Create();
+
+ // Act
+ var html = helper.CheckBox("foo", new { attr = "attr-value" });
+
+ // Assert
+ Assert.Equal(@"<input attr=""attr-value"" id=""foo"" name=""foo"" type=""checkbox"" />",
+ html.ToHtmlString());
+ }
+
+ [Fact]
+ public void CheckboxWithDictionaryAttributes()
+ {
+ // Arrange
+ HtmlHelper helper = HtmlHelperFactory.Create();
+
+ // Act
+ var html = helper.CheckBox("foo", new Dictionary<string, object> { { "attr", "attr-value" } });
+
+ // Assert
+ Assert.Equal(@"<input attr=""attr-value"" id=""foo"" name=""foo"" type=""checkbox"" />",
+ html.ToHtmlString());
+ }
+
+ [Fact]
+ public void CheckboxWithExplicitChecked()
+ {
+ // Arrange
+ HtmlHelper helper = HtmlHelperFactory.Create();
+
+ // Act
+ var html = helper.CheckBox("foo", true);
+
+ // Assert
+ Assert.Equal(@"<input checked=""checked"" id=""foo"" name=""foo"" type=""checkbox"" />",
+ html.ToHtmlString());
+ }
+
+ [Fact]
+ public void CheckboxWithModelValue()
+ {
+ // Arrange
+ var modelState = new ModelStateDictionary();
+ modelState.SetModelValue("foo", true);
+ HtmlHelper helper = HtmlHelperFactory.Create(modelState);
+
+ // Act
+ var html = helper.CheckBox("foo");
+
+ // Assert
+ Assert.Equal(@"<input checked=""checked"" id=""foo"" name=""foo"" type=""checkbox"" />",
+ html.ToHtmlString());
+ }
+
+ [Fact]
+ public void CheckboxWithNonBooleanModelValue()
+ {
+ // Arrange
+ var modelState = new ModelStateDictionary();
+ modelState.SetModelValue("foo", Boolean.TrueString);
+ HtmlHelper helper = HtmlHelperFactory.Create(modelState);
+
+ // Act
+ var html = helper.CheckBox("foo");
+
+ // Assert
+ Assert.Equal(@"<input checked=""checked"" id=""foo"" name=""foo"" type=""checkbox"" />",
+ html.ToHtmlString());
+
+ modelState.SetModelValue("foo", new object());
+ helper = HtmlHelperFactory.Create(modelState);
+
+ // Act and Assert
+ Assert.Throws<InvalidOperationException>(() => helper.CheckBox("foo"),
+ "The parameter conversion from type \"System.Object\" to type \"System.Boolean\" failed because no " +
+ "type converter can convert between these types.");
+ }
+
+ [Fact]
+ public void CheckboxWithModelAndExplictValue()
+ {
+ // Arrange
+ var modelState = new ModelStateDictionary();
+ modelState.SetModelValue("foo", false);
+ HtmlHelper helper = HtmlHelperFactory.Create(modelState);
+
+ // Act
+ var html = helper.CheckBox("foo", true);
+
+ // Assert
+ Assert.Equal(@"<input checked=""checked"" id=""foo"" name=""foo"" type=""checkbox"" />",
+ html.ToHtmlString());
+
+ modelState.SetModelValue("foo", true);
+
+ // Act
+ html = helper.CheckBox("foo", false);
+
+ // Assert
+ Assert.Equal(@"<input id=""foo"" name=""foo"" type=""checkbox"" />",
+ html.ToHtmlString());
+ }
+
+ [Fact]
+ public void CheckBoxWithCheckedHtmlAttribute()
+ {
+ // Arrange
+ HtmlHelper helper = HtmlHelperFactory.Create();
+
+ // Act
+ var html = helper.CheckBox("foo", new { @checked = "checked" });
+
+ // Assert
+ Assert.Equal(@"<input checked=""checked"" id=""foo"" name=""foo"" type=""checkbox"" />",
+ html.ToHtmlString());
+ }
+
+ [Fact]
+ public void CheckBoxWithExplicitCheckedOverwritesHtmlAttribute()
+ {
+ // Arrange
+ HtmlHelper helper = HtmlHelperFactory.Create();
+
+ // Act
+ var html = helper.CheckBox("foo", false, new { @checked = "checked" });
+
+ // Assert
+ Assert.Equal(@"<input id=""foo"" name=""foo"" type=""checkbox"" />",
+ html.ToHtmlString());
+ }
+
+ [Fact]
+ public void CheckBoxWithModelStateCheckedOverwritesHtmlAttribute()
+ {
+ // Arrange
+ var modelState = new ModelStateDictionary();
+ modelState.SetModelValue("foo", false);
+ HtmlHelper helper = HtmlHelperFactory.Create();
+
+ // Act
+ var html = helper.CheckBox("foo", false, new { @checked = "checked" });
+
+ // Assert
+ Assert.Equal(@"<input id=""foo"" name=""foo"" type=""checkbox"" />",
+ html.ToHtmlString());
+ }
+
+ [Fact]
+ public void CheckBoxWithError()
+ {
+ // Arrange
+ var modelState = new ModelStateDictionary();
+ modelState.SetModelValue("foo", false);
+ HtmlHelper helper = HtmlHelperFactory.Create(modelState);
+
+ // Act
+ var html = helper.CheckBox("foo", true);
+
+ // Assert
+ Assert.Equal(@"<input checked=""checked"" id=""foo"" name=""foo"" type=""checkbox"" />",
+ html.ToHtmlString());
+ }
+
+ [Fact]
+ public void CheckBoxWithErrorAndCustomCss()
+ {
+ // Arrange
+ var modelState = new ModelStateDictionary();
+ modelState.AddError("foo", "error");
+ HtmlHelper helper = HtmlHelperFactory.Create(modelState);
+
+ // Act
+ var html = helper.CheckBox("foo", true, new { @class = "my-class" });
+
+ // Assert
+ Assert.Equal(@"<input checked=""checked"" class=""input-validation-error my-class"" id=""foo"" name=""foo"" type=""checkbox"" />",
+ html.ToHtmlString());
+ }
+
+ //[Fact]
+ // Can't test as it sets a static property
+ // Review: Need to redo test once we fix set once property
+ public void CheckBoxUsesCustomErrorClass()
+ {
+ // Arrange
+ var modelState = new ModelStateDictionary();
+ modelState.AddError("foo", "error");
+ HtmlHelper.ValidationInputCssClassName = "my-error-class";
+ HtmlHelper helper = HtmlHelperFactory.Create(modelState);
+
+ // Act
+ var html = helper.CheckBox("foo", true, new { @class = "my-class" });
+
+ // Assert
+ Assert.Equal(@"<input checked=""checked"" class=""my-error-class my-class"" id=""foo"" name=""foo"" type=""checkbox"" />",
+ html.ToHtmlString());
+ }
+
+ [Fact]
+ public void CheckBoxOverwritesImplicitAttributes()
+ {
+ // Arrange
+ HtmlHelper helper = HtmlHelperFactory.Create();
+
+ // Act
+ var html = helper.CheckBox("foo", true, new { type = "fooType", name = "bar" });
+
+ // Assert
+ Assert.Equal(@"<input checked=""checked"" id=""foo"" name=""foo"" type=""fooType"" />",
+ html.ToHtmlString());
+ }
+
+ [Fact]
+ public void CheckboxAddsUnobtrusiveValidationAttributes()
+ {
+ // Arrange
+ const string fieldName = "name";
+ var modelStateDictionary = new ModelStateDictionary();
+ var validationHelper = new ValidationHelper(new Mock<HttpContextBase>().Object, modelStateDictionary);
+ HtmlHelper helper = HtmlHelperFactory.Create(modelStateDictionary, validationHelper);
+
+ // Act
+ validationHelper.RequireField(fieldName, "Please specify a valid Name.");
+ validationHelper.Add(fieldName, Validator.StringLength(30, errorMessage: "Name cannot exceed {0} characters"));
+ var html = helper.CheckBox(fieldName, new Dictionary<string, object> { { "data-some-val", "5" } });
+
+ // Assert
+ Assert.Equal(@"<input data-some-val=""5"" data-val=""true"" data-val-length=""Name cannot exceed 30 characters"" data-val-length-max=""30"" data-val-required=""Please specify a valid Name."" id=""name"" name=""name"" type=""checkbox"" />",
+ html.ToString());
+ }
+ }
+}
diff --git a/test/System.Web.WebPages.Test/Html/HtmlHelperFactory.cs b/test/System.Web.WebPages.Test/Html/HtmlHelperFactory.cs
new file mode 100644
index 00000000..2afef6f2
--- /dev/null
+++ b/test/System.Web.WebPages.Test/Html/HtmlHelperFactory.cs
@@ -0,0 +1,16 @@
+using System.Web.WebPages.Html;
+using Moq;
+
+namespace System.Web.WebPages.Test
+{
+ public static class HtmlHelperFactory
+ {
+ internal static HtmlHelper Create(ModelStateDictionary modelStateDictionary = null, ValidationHelper validationHelper = null)
+ {
+ modelStateDictionary = modelStateDictionary ?? new ModelStateDictionary();
+ var httpContext = new Mock<HttpContextBase>();
+ validationHelper = validationHelper ?? new ValidationHelper(httpContext.Object, modelStateDictionary);
+ return new HtmlHelper(modelStateDictionary, validationHelper);
+ }
+ }
+}
diff --git a/test/System.Web.WebPages.Test/Html/HtmlHelperTest.cs b/test/System.Web.WebPages.Test/Html/HtmlHelperTest.cs
new file mode 100644
index 00000000..a466ad93
--- /dev/null
+++ b/test/System.Web.WebPages.Test/Html/HtmlHelperTest.cs
@@ -0,0 +1,159 @@
+using System.Web.WebPages.Html;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.WebPages.Test
+{
+ public class HtmlHelperTest
+ {
+ [Fact]
+ public void ValidationInputCssClassNameThrowsWhenAssignedNull()
+ {
+ // Act and Assert
+ Assert.ThrowsArgumentNull(() => HtmlHelper.ValidationInputCssClassName = null, "value");
+ }
+
+ [Fact]
+ public void ValidationSummaryClassNameThrowsWhenAssignedNull()
+ {
+ // Act and Assert
+ Assert.ThrowsArgumentNull(() => HtmlHelper.ValidationSummaryClass = null, "value");
+ }
+
+ [Fact]
+ public void EncodeObject()
+ {
+ // Arrange
+ HtmlHelper htmlHelper = HtmlHelperFactory.Create();
+ object text = "<br />" as object;
+
+ // Act
+ string encodedHtml = htmlHelper.Encode(text);
+
+ // Assert
+ Assert.Equal(encodedHtml, "&lt;br /&gt;");
+ }
+
+ [Fact]
+ public void EncodeObjectNull()
+ {
+ // Arrange
+ HtmlHelper htmlHelper = HtmlHelperFactory.Create();
+ object text = null;
+
+ // Act
+ string encodedHtml = htmlHelper.Encode(text);
+
+ // Assert
+ Assert.Equal(String.Empty, encodedHtml);
+ }
+
+ [Fact]
+ public void EncodeString()
+ {
+ // Arrange
+ HtmlHelper htmlHelper = HtmlHelperFactory.Create();
+ var text = "<br />";
+
+ // Act
+ string encodedHtml = htmlHelper.Encode(text);
+
+ // Assert
+ Assert.Equal(encodedHtml, "&lt;br /&gt;");
+ }
+
+ [Fact]
+ public void EncodeStringNull()
+ {
+ // Arrange
+ HtmlHelper htmlHelper = HtmlHelperFactory.Create();
+ string text = null;
+
+ // Act
+ string encodedHtml = htmlHelper.Encode(text);
+
+ // Assert
+ Assert.Equal("", encodedHtml);
+ }
+
+ [Fact]
+ public void RawAllowsNullValue()
+ {
+ // Arrange
+ HtmlHelper htmlHelper = HtmlHelperFactory.Create();
+
+ // Act
+ IHtmlString markupHtml = htmlHelper.Raw(null);
+
+ // Assert
+ Assert.Equal(null, markupHtml.ToString());
+ Assert.Equal(null, markupHtml.ToHtmlString());
+ }
+
+ [Fact]
+ public void RawAllowsNullObjectValue()
+ {
+ // Arrange
+ HtmlHelper htmlHelper = HtmlHelperFactory.Create();
+
+ // Act
+ IHtmlString markupHtml = htmlHelper.Raw((object)null);
+
+ // Assert
+ Assert.Equal(null, markupHtml.ToString());
+ Assert.Equal(null, markupHtml.ToHtmlString());
+ }
+
+ [Fact]
+ public void RawAllowsEmptyValue()
+ {
+ // Arrange
+ HtmlHelper htmlHelper = HtmlHelperFactory.Create();
+
+ // Act
+ IHtmlString markupHtml = htmlHelper.Raw("");
+
+ // Assert
+ Assert.Equal("", markupHtml.ToString());
+ Assert.Equal("", markupHtml.ToHtmlString());
+ }
+
+ [Fact]
+ public void RawReturnsWrapperMarkup()
+ {
+ // Arrange
+ HtmlHelper htmlHelper = HtmlHelperFactory.Create();
+ string markup = "<b>bold</b>";
+
+ // Act
+ IHtmlString markupHtml = htmlHelper.Raw(markup);
+
+ // Assert
+ Assert.Equal("<b>bold</b>", markupHtml.ToString());
+ Assert.Equal("<b>bold</b>", markupHtml.ToHtmlString());
+ }
+
+ [Fact]
+ public void RawReturnsWrapperMarkupOfObject()
+ {
+ // Arrange
+ HtmlHelper htmlHelper = HtmlHelperFactory.Create();
+ ObjectWithWrapperMarkup obj = new ObjectWithWrapperMarkup();
+
+ // Act
+ IHtmlString markupHtml = htmlHelper.Raw(obj);
+
+ // Assert
+ Assert.Equal("<b>boldFromObject</b>", markupHtml.ToString());
+ Assert.Equal("<b>boldFromObject</b>", markupHtml.ToHtmlString());
+ }
+
+ private class ObjectWithWrapperMarkup
+ {
+ public override string ToString()
+ {
+ return "<b>boldFromObject</b>";
+ }
+ }
+ }
+}
diff --git a/test/System.Web.WebPages.Test/Html/InputHelperTest.cs b/test/System.Web.WebPages.Test/Html/InputHelperTest.cs
new file mode 100644
index 00000000..15c62472
--- /dev/null
+++ b/test/System.Web.WebPages.Test/Html/InputHelperTest.cs
@@ -0,0 +1,584 @@
+using System.Collections.Generic;
+using System.Data.Linq;
+using System.Web.WebPages.Html;
+using Moq;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.WebPages.Test
+{
+ public class InputHelperTest
+ {
+ private static readonly IDictionary<string, object> _attributesDictionary = new Dictionary<string, object> { { "baz", "BazValue" } };
+ private static readonly object _attributesObject = new { baz = "BazValue" };
+
+ [Fact]
+ public void HiddenWithBinaryArrayValueRendersBase64EncodedValue()
+ {
+ // Arrange
+ HtmlHelper helper = HtmlHelperFactory.Create();
+
+ // Act
+ var result = helper.Hidden("ProductName", new Binary(new byte[] { 23, 43, 53 }));
+
+ // Assert
+ Assert.Equal("<input id=\"ProductName\" name=\"ProductName\" type=\"hidden\" value=\"Fys1\" />", result.ToHtmlString());
+ }
+
+ [Fact]
+ public void HiddenWithEmptyNameThrows()
+ {
+ // Arrange
+ HtmlHelper helper = HtmlHelperFactory.Create();
+
+ // Act & Assert
+ Assert.ThrowsArgumentNullOrEmptyString(() => helper.Hidden(String.Empty), "name");
+ Assert.ThrowsArgumentNullOrEmptyString(() => helper.Hidden(null), "name");
+ }
+
+ [Fact]
+ public void HiddenWithExplicitValue()
+ {
+ // Arrange
+ HtmlHelper helper = HtmlHelperFactory.Create();
+
+ // Act
+ var html = helper.Hidden("foo", "DefaultFoo");
+
+ // Assert
+ Assert.Equal(@"<input id=""foo"" name=""foo"" type=""hidden"" value=""DefaultFoo"" />",
+ html.ToHtmlString());
+ }
+
+ [Fact]
+ public void HiddenWithExplicitValueAndAttributesDictionary()
+ {
+ // Arrange
+ HtmlHelper helper = HtmlHelperFactory.Create();
+
+ // Act
+ var html = helper.Hidden("foo", "DefaultFoo", new Dictionary<string, object> { { "attr", "attr-val" } });
+
+ // Assert
+ Assert.Equal(@"<input attr=""attr-val"" id=""foo"" name=""foo"" type=""hidden"" value=""DefaultFoo"" />",
+ html.ToHtmlString());
+ }
+
+ [Fact]
+ public void HiddenWithExplicitValueAndObjectDictionary()
+ {
+ // Arrange
+ HtmlHelper helper = HtmlHelperFactory.Create();
+
+ // Act
+ var html = helper.Hidden("foo", "DefaultFoo", new { attr = "attr-val" });
+
+ // Assert
+ Assert.Equal(@"<input attr=""attr-val"" id=""foo"" name=""foo"" type=""hidden"" value=""DefaultFoo"" />",
+ html.ToHtmlString());
+ }
+
+ [Fact]
+ public void HiddenWithExplicitValueNull()
+ {
+ // Arrange
+ HtmlHelper helper = HtmlHelperFactory.Create();
+
+ // Act
+ var html = helper.Hidden("foo", value: null);
+
+ // Assert
+ Assert.Equal(@"<input id=""foo"" name=""foo"" type=""hidden"" value="""" />", html.ToHtmlString());
+ }
+
+ [Fact]
+ public void HiddenWithModelValue()
+ {
+ // Arrange
+ var model = new ModelStateDictionary();
+ model.SetModelValue("foo", "bar");
+ HtmlHelper helper = HtmlHelperFactory.Create(model);
+
+ // Act
+ var html = helper.Hidden("foo");
+
+ // Assert
+ Assert.Equal(@"<input id=""foo"" name=""foo"" type=""hidden"" value=""bar"" />", html.ToHtmlString());
+ }
+
+ [Fact]
+ public void HiddenWithModelValueAndAttributesDictionary()
+ {
+ // Arrange
+ var model = new ModelStateDictionary();
+ model.SetModelValue("foo", "bar");
+ HtmlHelper helper = HtmlHelperFactory.Create(model);
+
+ // Act
+ var html = helper.Hidden("foo", null, new Dictionary<string, object> { { "attr", "attr-val" } });
+
+ // Assert
+ Assert.Equal(@"<input attr=""attr-val"" id=""foo"" name=""foo"" type=""hidden"" value=""bar"" />", html.ToHtmlString());
+ }
+
+ [Fact]
+ public void HiddenWithImplicitValueAndAttributesObject()
+ {
+ // Arrange
+ var model = new ModelStateDictionary();
+ model.SetModelValue("foo", "bar");
+ HtmlHelper helper = HtmlHelperFactory.Create(model);
+
+ // Act
+ var html = helper.Hidden("foo", null, new { attr = "attr-val" });
+
+ // Assert
+ Assert.Equal(@"<input attr=""attr-val"" id=""foo"" name=""foo"" type=""hidden"" value=""bar"" />", html.ToHtmlString());
+ }
+
+ [Fact]
+ public void HiddenWithNameAndValue()
+ {
+ // Arrange
+ HtmlHelper helper = HtmlHelperFactory.Create();
+
+ // Act
+ var html = helper.Hidden("foo", "fooValue");
+
+ // Assert
+ Assert.Equal(@"<input id=""foo"" name=""foo"" type=""hidden"" value=""fooValue"" />", html.ToHtmlString());
+ }
+
+ [Fact]
+ public void HiddenWithExplicitOverwritesAttributeValue()
+ {
+ // Arrange
+ HtmlHelper helper = HtmlHelperFactory.Create();
+
+ // Act
+ var html = helper.Hidden("foo", "fooValue", new { value = "barValue" });
+
+ // Assert
+ Assert.Equal(@"<input id=""foo"" name=""foo"" type=""hidden"" value=""fooValue"" />", html.ToHtmlString());
+ }
+
+ [Fact]
+ public void HiddenWithModelValueOverwritesAttributeValue()
+ {
+ // Arrange
+ var model = new ModelStateDictionary();
+ model.SetModelValue("foo", "fooValue");
+ HtmlHelper helper = HtmlHelperFactory.Create(model);
+
+ // Act
+ var html = helper.Hidden("foo", null, new { value = "barValue" });
+
+ // Assert
+ Assert.Equal(@"<input id=""foo"" name=""foo"" type=""hidden"" value=""fooValue"" />", html.ToHtmlString());
+ }
+
+ [Fact]
+ public void HiddenAddsUnobtrusiveValidationAttributes()
+ {
+ // Arrange
+ const string fieldName = "name";
+ var modelStateDictionary = new ModelStateDictionary();
+ var validationHelper = new ValidationHelper(new Mock<HttpContextBase>().Object, modelStateDictionary);
+ HtmlHelper helper = HtmlHelperFactory.Create(modelStateDictionary, validationHelper);
+
+ // Act
+ validationHelper.RequireField(fieldName, "Please specify a valid Name.");
+ validationHelper.Add(fieldName, Validator.StringLength(30, errorMessage: "Name cannot exceed {0} characters"));
+ var html = helper.Hidden(fieldName, value: null, htmlAttributes: new Dictionary<string, object> { { "data-some-val", "5" } });
+
+ // Assert
+ Assert.Equal(@"<input data-some-val=""5"" data-val=""true"" data-val-length=""Name cannot exceed 30 characters"" data-val-length-max=""30"" data-val-required=""Please specify a valid Name."" id=""name"" name=""name"" type=""hidden"" value="""" />",
+ html.ToString());
+ }
+
+ // Password
+
+ [Fact]
+ public void PasswordWithEmptyNameThrows()
+ {
+ // Arrange
+ HtmlHelper helper = HtmlHelperFactory.Create();
+
+ // Act & Assert
+ Assert.ThrowsArgumentNullOrEmptyString(() => helper.Password(String.Empty), "name");
+ Assert.ThrowsArgumentNullOrEmptyString(() => helper.Password(null), "name");
+ }
+
+ [Fact]
+ public void PasswordDictionaryOverridesImplicitParameters()
+ {
+ // Arrange
+ HtmlHelper helper = HtmlHelperFactory.Create();
+
+ // Act
+ var html = helper.Password("foo", "Some Value", new { type = "fooType" });
+
+ // Assert
+ Assert.Equal(@"<input id=""foo"" name=""foo"" type=""fooType"" value=""Some Value"" />", html.ToHtmlString());
+ }
+
+ [Fact]
+ public void PasswordExplicitParametersOverrideDictionary()
+ {
+ // Arrange
+ HtmlHelper helper = HtmlHelperFactory.Create();
+
+ // Act
+ var html = helper.Password("foo", "Some Value", new { value = "Another Value", name = "bar" });
+
+ // Assert
+ Assert.Equal(@"<input id=""foo"" name=""foo"" type=""password"" value=""Some Value"" />", html.ToHtmlString());
+ }
+
+ [Fact]
+ public void PasswordWithExplicitValue()
+ {
+ // Arrange
+ HtmlHelper helper = HtmlHelperFactory.Create();
+
+ // Act
+ var html = helper.Password("foo", "DefaultFoo", (object)null);
+
+ // Assert
+ Assert.Equal(@"<input id=""foo"" name=""foo"" type=""password"" value=""DefaultFoo"" />", html.ToHtmlString());
+ }
+
+ [Fact]
+ public void PasswordWithExplicitValueAndAttributesDictionary()
+ {
+ // Arrange
+ HtmlHelper helper = HtmlHelperFactory.Create();
+
+ // Act
+ var html = helper.Password("foo", "DefaultFoo", new { baz = "BazValue" });
+
+ // Assert
+ Assert.Equal(@"<input baz=""BazValue"" id=""foo"" name=""foo"" type=""password"" value=""DefaultFoo"" />",
+ html.ToHtmlString());
+ }
+
+ [Fact]
+ public void PasswordWithExplicitValueAndAttributesObject()
+ {
+ // Arrange
+ HtmlHelper helper = HtmlHelperFactory.Create();
+
+ // Act
+ var html = helper.Password("foo", "DefaultFoo", new Dictionary<string, object> { { "baz", "BazValue" } });
+
+ // Assert
+ Assert.Equal(@"<input baz=""BazValue"" id=""foo"" name=""foo"" type=""password"" value=""DefaultFoo"" />",
+ html.ToHtmlString());
+ }
+
+ [Fact]
+ public void PasswordWithExplicitValueNull()
+ {
+ // Arrange
+ HtmlHelper helper = HtmlHelperFactory.Create();
+
+ // Act
+ var html = helper.Password("foo", value: (string)null);
+
+ // Assert
+ Assert.Equal(@"<input id=""foo"" name=""foo"" type=""password"" />", html.ToHtmlString());
+ }
+
+ [Fact]
+ public void PasswordWithImplicitValue()
+ {
+ // Arrange
+ HtmlHelper helper = HtmlHelperFactory.Create();
+
+ // Act
+ var html = helper.Password("foo");
+
+ // Assert
+ Assert.Equal(@"<input id=""foo"" name=""foo"" type=""password"" />", html.ToHtmlString());
+ }
+
+ [Fact]
+ public void PasswordWithImplicitValueAndAttributesDictionary()
+ {
+ // Arrange
+ HtmlHelper helper = HtmlHelperFactory.Create();
+
+ // Act
+ var html = helper.Password("foo", null, _attributesDictionary);
+
+ // Assert
+ Assert.Equal(@"<input baz=""BazValue"" id=""foo"" name=""foo"" type=""password"" />", html.ToHtmlString());
+ }
+
+ [Fact]
+ public void PasswordWithImplicitValueAndAttributesDictionaryReturnsEmptyValueIfNotFound()
+ {
+ // Arrange
+ HtmlHelper helper = HtmlHelperFactory.Create();
+
+ // Act
+ var html = helper.Password("keyNotFound", null, _attributesDictionary);
+
+ // Assert
+ Assert.Equal(@"<input baz=""BazValue"" id=""keyNotFound"" name=""keyNotFound"" type=""password"" />", html.ToHtmlString());
+ }
+
+ [Fact]
+ public void PasswordWithImplicitValueAndAttributesObject()
+ {
+ // Arrange
+ HtmlHelper helper = HtmlHelperFactory.Create();
+
+ // Act
+ var html = helper.Password("foo", null, _attributesObject);
+
+ // Assert
+ Assert.Equal(@"<input baz=""BazValue"" id=""foo"" name=""foo"" type=""password"" />", html.ToHtmlString());
+ }
+
+ [Fact]
+ public void PasswordWithNameAndValue()
+ {
+ // Arrange
+ HtmlHelper helper = HtmlHelperFactory.Create();
+
+ // Act
+ var html = helper.Password("foo", "fooValue");
+
+ // Assert
+ Assert.Equal(@"<input id=""foo"" name=""foo"" type=""password"" value=""fooValue"" />", html.ToHtmlString());
+ }
+
+ [Fact]
+ public void PasswordWithNullNameThrows()
+ {
+ // Arrange
+ HtmlHelper helper = HtmlHelperFactory.Create();
+
+ // Act & Assert
+ Assert.ThrowsArgumentNullOrEmptyString(() => helper.Password(null), "name");
+ Assert.ThrowsArgumentNullOrEmptyString(() => helper.Password(String.Empty), "name");
+ }
+
+ [Fact]
+ public void PasswordAddsUnobtrusiveValidationAttributes()
+ {
+ // Arrange
+ const string fieldName = "name";
+ var modelStateDictionary = new ModelStateDictionary();
+ var validationHelper = new ValidationHelper(new Mock<HttpContextBase>().Object, modelStateDictionary);
+ HtmlHelper helper = HtmlHelperFactory.Create(modelStateDictionary, validationHelper);
+
+ // Act
+ validationHelper.RequireField(fieldName, "Please specify a valid Name.");
+ validationHelper.Add(fieldName, Validator.StringLength(30, errorMessage: "Name cannot exceed {0} characters"));
+ var html = helper.Password(fieldName, value: null, htmlAttributes: new Dictionary<string, object> { { "data-some-val", "5" } });
+
+ // Assert
+ Assert.Equal(@"<input data-some-val=""5"" data-val=""true"" data-val-length=""Name cannot exceed 30 characters"" data-val-length-max=""30"" data-val-required=""Please specify a valid Name."" id=""name"" name=""name"" type=""password"" />",
+ html.ToString());
+ }
+
+ //Input
+ [Fact]
+ public void TextBoxDictionaryOverridesImplicitValues()
+ {
+ // Arrange
+ HtmlHelper helper = HtmlHelperFactory.Create();
+
+ // Act
+ var html = helper.TextBox("foo", "DefaultFoo", new { type = "fooType" });
+
+ // Assert
+ Assert.Equal(@"<input id=""foo"" name=""foo"" type=""fooType"" value=""DefaultFoo"" />", html.ToHtmlString());
+ }
+
+ [Fact]
+ public void TextBoxExplicitParametersOverrideDictionaryValues()
+ {
+ // Arrange
+ HtmlHelper helper = HtmlHelperFactory.Create();
+
+ // Act
+ var html = helper.TextBox("foo", "DefaultFoo", new { value = "Some other value" });
+
+ // Assert
+ Assert.Equal(@"<input id=""foo"" name=""foo"" type=""text"" value=""DefaultFoo"" />", html.ToHtmlString());
+ }
+
+ [Fact]
+ public void TextBoxWithDotReplacementForId()
+ {
+ // Arrange
+ HtmlHelper helper = HtmlHelperFactory.Create();
+
+ // Act
+ var html = helper.TextBox("foo.bar.baz", null);
+
+ // Assert
+ Assert.Equal(@"<input id=""foo_bar_baz"" name=""foo.bar.baz"" type=""text"" value="""" />", html.ToHtmlString());
+ }
+
+ [Fact]
+ public void TextBoxWithEmptyNameThrows()
+ {
+ // Arrange
+ HtmlHelper helper = HtmlHelperFactory.Create();
+
+ // Act & Assert
+ Assert.ThrowsArgumentNullOrEmptyString(() => helper.TextBox(null), "name");
+ Assert.ThrowsArgumentNullOrEmptyString(() => helper.TextBox(String.Empty), "name");
+ }
+
+ [Fact]
+ public void TextBoxWithExplicitValue()
+ {
+ // Arrange
+ HtmlHelper helper = HtmlHelperFactory.Create();
+
+ // Act
+ var html = helper.TextBox("foo", "DefaultFoo", (object)null);
+
+ // Assert
+ Assert.Equal(@"<input id=""foo"" name=""foo"" type=""text"" value=""DefaultFoo"" />", html.ToHtmlString());
+ }
+
+ [Fact]
+ public void TextBoxWithExplicitValueAndAttributesDictionary()
+ {
+ // Arrange
+ HtmlHelper helper = HtmlHelperFactory.Create();
+
+ // Act
+ var html = helper.TextBox("foo", "DefaultFoo", _attributesDictionary);
+
+ // Assert
+ Assert.Equal(@"<input baz=""BazValue"" id=""foo"" name=""foo"" type=""text"" value=""DefaultFoo"" />", html.ToHtmlString());
+ }
+
+ [Fact]
+ public void TextBoxWithExplicitValueAndAttributesObject()
+ {
+ // Arrange
+ HtmlHelper helper = HtmlHelperFactory.Create();
+
+ // Act
+ var html = helper.TextBox("foo", "DefaultFoo", _attributesObject);
+
+ // Assert
+ Assert.Equal(@"<input baz=""BazValue"" id=""foo"" name=""foo"" type=""text"" value=""DefaultFoo"" />", html.ToHtmlString());
+ }
+
+ [Fact]
+ public void TextBoxWithExplicitValueNull()
+ {
+ // Arrange
+ var modelState = new ModelStateDictionary();
+ modelState.SetModelValue("foo", "fooModelValue");
+ HtmlHelper helper = HtmlHelperFactory.Create(modelState);
+
+ // Act
+ var html = helper.TextBox("foo", (string)null /* value */, (object)null);
+
+ // Assert
+ Assert.Equal(@"<input id=""foo"" name=""foo"" type=""text"" value=""fooModelValue"" />", html.ToHtmlString());
+ }
+
+ [Fact]
+ public void TextBoxWithImplicitValue()
+ {
+ // Arrange
+ var modelState = new ModelStateDictionary();
+ modelState.SetModelValue("foo", "fooModelValue");
+ HtmlHelper helper = HtmlHelperFactory.Create(modelState);
+
+ // Act
+ var html = helper.TextBox("foo");
+
+ // Assert
+ Assert.Equal(@"<input id=""foo"" name=""foo"" type=""text"" value=""fooModelValue"" />", html.ToHtmlString());
+ }
+
+ [Fact]
+ public void TextBoxWithImplicitValueAndAttributesDictionary()
+ {
+ // Arrange
+ var modelState = new ModelStateDictionary();
+ modelState.SetModelValue("foo", "fooModelValue");
+ HtmlHelper helper = HtmlHelperFactory.Create(modelState);
+
+ // Act
+ var html = helper.TextBox("foo", null, _attributesDictionary);
+
+ // Assert
+ Assert.Equal(@"<input baz=""BazValue"" id=""foo"" name=""foo"" type=""text"" value=""fooModelValue"" />", html.ToHtmlString());
+ }
+
+ [Fact]
+ public void TextBoxWithImplicitValueAndAttributesDictionaryReturnsEmptyValueIfNotFound()
+ {
+ // Arrange
+ var modelState = new ModelStateDictionary();
+ modelState.SetModelValue("foo", "fooModelValue");
+ HtmlHelper helper = HtmlHelperFactory.Create(modelState);
+
+ // Act
+ var html = helper.TextBox("keyNotFound", null, _attributesDictionary);
+
+ // Assert
+ Assert.Equal(@"<input baz=""BazValue"" id=""keyNotFound"" name=""keyNotFound"" type=""text"" value="""" />", html.ToHtmlString());
+ }
+
+ [Fact]
+ public void TextBoxWithImplicitValueAndAttributesObject()
+ {
+ // Arrange
+ var modelState = new ModelStateDictionary();
+ modelState.SetModelValue("foo", "fooModelValue");
+ HtmlHelper helper = HtmlHelperFactory.Create(modelState);
+
+ // Act
+ var html = helper.TextBox("foo", null, _attributesObject);
+
+ // Assert
+ Assert.Equal(@"<input baz=""BazValue"" id=""foo"" name=""foo"" type=""text"" value=""fooModelValue"" />", html.ToHtmlString());
+ }
+
+ [Fact]
+ public void TextBoxWithNameAndValue()
+ {
+ // Arrange
+ HtmlHelper helper = HtmlHelperFactory.Create();
+
+ // Act
+ var html = helper.TextBox("foo", "fooValue");
+
+ // Assert
+ Assert.Equal(@"<input id=""foo"" name=""foo"" type=""text"" value=""fooValue"" />", html.ToHtmlString());
+ }
+
+ [Fact]
+ public void TextBoxAddsUnobtrusiveValidationAttributes()
+ {
+ // Arrange
+ const string fieldName = "name";
+ var modelStateDictionary = new ModelStateDictionary();
+ var validationHelper = new ValidationHelper(new Mock<HttpContextBase>().Object, modelStateDictionary);
+ HtmlHelper helper = HtmlHelperFactory.Create(modelStateDictionary, validationHelper);
+
+ // Act
+ validationHelper.RequireField(fieldName, "Please specify a valid Name.");
+ validationHelper.Add(fieldName, Validator.StringLength(30, errorMessage: "Name cannot exceed {0} characters"));
+ var html = helper.TextBox(fieldName, value: null, htmlAttributes: new Dictionary<string, object> { { "data-some-val", "5" } });
+
+ // Assert
+ Assert.Equal(@"<input data-some-val=""5"" data-val=""true"" data-val-length=""Name cannot exceed 30 characters"" data-val-length-max=""30"" data-val-required=""Please specify a valid Name."" id=""name"" name=""name"" type=""text"" value="""" />",
+ html.ToString());
+ }
+ }
+}
diff --git a/test/System.Web.WebPages.Test/Html/RadioButtonTest.cs b/test/System.Web.WebPages.Test/Html/RadioButtonTest.cs
new file mode 100644
index 00000000..afab382e
--- /dev/null
+++ b/test/System.Web.WebPages.Test/Html/RadioButtonTest.cs
@@ -0,0 +1,197 @@
+using System.Collections.Generic;
+using System.Web.WebPages.Html;
+using Moq;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.WebPages.Test
+{
+ public class RadioButtonTest
+ {
+ [Fact]
+ public void RadioButtonWithEmptyNameThrows()
+ {
+ // Arrange
+ HtmlHelper helper = HtmlHelperFactory.Create();
+
+ // Act and assert
+ Assert.ThrowsArgumentNullOrEmptyString(() => helper.RadioButton(null, null), "name");
+ Assert.ThrowsArgumentNullOrEmptyString(() => helper.RadioButton(String.Empty, null), "name");
+ }
+
+ [Fact]
+ public void RadioButtonWithDefaultArguments()
+ {
+ // Arrange
+ HtmlHelper helper = HtmlHelperFactory.Create();
+
+ // Act
+ var html = helper.RadioButton("foo", "bar", true);
+
+ // Assert
+ Assert.Equal(@"<input checked=""checked"" id=""foo"" name=""foo"" type=""radio"" value=""bar"" />",
+ html.ToHtmlString());
+
+ html = helper.RadioButton("foo", "bar", false);
+
+ // Assert
+ Assert.Equal(@"<input id=""foo"" name=""foo"" type=""radio"" value=""bar"" />",
+ html.ToHtmlString());
+ }
+
+ [Fact]
+ public void RadioButtonWithObjectAttributes()
+ {
+ // Arrange
+ HtmlHelper helper = HtmlHelperFactory.Create();
+
+ // Act
+ var html = helper.RadioButton("foo", "bar", new { attr = "attr-value" });
+
+ // Assert
+ Assert.Equal(@"<input attr=""attr-value"" id=""foo"" name=""foo"" type=""radio"" value=""bar"" />",
+ html.ToHtmlString());
+ }
+
+ [Fact]
+ public void RadioButtonWithDictionaryAttributes()
+ {
+ // Arrange
+ HtmlHelper helper = HtmlHelperFactory.Create();
+
+ // Act
+ var html = helper.RadioButton("foo", "bar", new Dictionary<string, object> { { "attr", "attr-value" } });
+
+ // Assert
+ Assert.Equal(@"<input attr=""attr-value"" id=""foo"" name=""foo"" type=""radio"" value=""bar"" />",
+ html.ToHtmlString());
+ }
+
+ [Fact]
+ public void RadioButtonUsesModelStateToAssignChecked()
+ {
+ // Arrange
+ var modelState = new ModelStateDictionary();
+ modelState.SetModelValue("foo", "bar");
+ HtmlHelper helper = HtmlHelperFactory.Create(modelState);
+
+ // Act
+ var html = helper.RadioButton("foo", "bar");
+
+ // Assert
+ Assert.Equal(@"<input checked=""checked"" id=""foo"" name=""foo"" type=""radio"" value=""bar"" />",
+ html.ToHtmlString());
+ }
+
+ [Fact]
+ public void RadioButtonUsesModelStateToRemoveChecked()
+ {
+ // Arrange
+ var modelState = new ModelStateDictionary();
+ modelState.SetModelValue("foo", "not-a-bar");
+ HtmlHelper helper = HtmlHelperFactory.Create(modelState);
+
+ // Act
+ var html = helper.RadioButton("foo", "bar", new { @checked = "checked" });
+
+ // Assert
+ Assert.Equal(@"<input id=""foo"" name=""foo"" type=""radio"" value=""bar"" />",
+ html.ToHtmlString());
+ }
+
+ [Fact]
+ public void RadioButtonWithoutModelStateDoesNotAffectChecked()
+ {
+ // Arrange
+ HtmlHelper helper = HtmlHelperFactory.Create();
+
+ // Act
+ var html = helper.RadioButton("foo", "bar", new { @checked = "checked" });
+
+ // Assert
+ Assert.Equal(@"<input checked=""checked"" id=""foo"" name=""foo"" type=""radio"" value=""bar"" />",
+ html.ToHtmlString());
+ }
+
+ [Fact]
+ public void RadioButtonWithNonStringModelValue()
+ {
+ // Arrange
+ var modelState = new ModelStateDictionary();
+ modelState.SetModelValue("foo", new List<double>());
+ HtmlHelper helper = HtmlHelperFactory.Create(modelState);
+
+ // Act
+ var html = helper.RadioButton("foo", "bar");
+
+ // Assert
+ Assert.Equal(@"<input id=""foo"" name=""foo"" type=""radio"" value=""bar"" />",
+ html.ToHtmlString());
+ }
+
+ [Fact]
+ public void RadioButtonWithNonStringValue()
+ {
+ // Arrange
+ var modelState = new ModelStateDictionary();
+ modelState.SetModelValue("foo", "bar");
+ HtmlHelper helper = HtmlHelperFactory.Create(modelState);
+
+ // Act
+ var html = helper.RadioButton("foo", 2.53);
+
+ // Assert
+ Assert.Equal(@"<input id=""foo"" name=""foo"" type=""radio"" value=""2.53"" />",
+ html.ToHtmlString());
+ }
+
+ [Fact]
+ public void RadioButtonWithExplicitChecked()
+ {
+ // Arrange
+ var modelState = new ModelStateDictionary();
+ modelState.SetModelValue("foo", "bar");
+ HtmlHelper helper = HtmlHelperFactory.Create(modelState);
+
+ // Act
+ var html = helper.RadioButton("foo", "not-bar", true);
+
+ // Assert
+ Assert.Equal(@"<input checked=""checked"" id=""foo"" name=""foo"" type=""radio"" value=""not-bar"" />",
+ html.ToHtmlString());
+ }
+
+ [Fact]
+ public void RadioButtonOverwritesImplicitAttributes()
+ {
+ // Arrange
+ HtmlHelper helper = HtmlHelperFactory.Create();
+
+ // Act
+ var html = helper.RadioButton("foo", "foo-value", new { value = "bazValue", type = "fooType", name = "bar" });
+
+ // Assert
+ Assert.Equal(@"<input id=""foo"" name=""foo"" type=""fooType"" value=""foo-value"" />",
+ html.ToHtmlString());
+ }
+
+ [Fact]
+ public void RadioButtonAddsUnobtrusiveValidationAttributes()
+ {
+ // Arrange
+ const string fieldName = "name";
+ var modelStateDictionary = new ModelStateDictionary();
+ var validationHelper = new ValidationHelper(new Mock<HttpContextBase>().Object, modelStateDictionary);
+ HtmlHelper helper = HtmlHelperFactory.Create(modelStateDictionary, validationHelper);
+
+ // Act
+ validationHelper.RequireField(fieldName, "Please specify a valid Name.");
+ validationHelper.Add(fieldName, Validator.StringLength(30, errorMessage: "Name cannot exceed {0} characters"));
+ var html = helper.RadioButton(fieldName, value: 8, htmlAttributes: new Dictionary<string, object> { { "data-some-val", "5" } });
+
+ // Assert
+ Assert.Equal(@"<input data-some-val=""5"" data-val=""true"" data-val-length=""Name cannot exceed 30 characters"" data-val-length-max=""30"" data-val-required=""Please specify a valid Name."" id=""name"" name=""name"" type=""radio"" value=""8"" />",
+ html.ToString());
+ }
+ }
+}
diff --git a/test/System.Web.WebPages.Test/Html/SelectHelperTest.cs b/test/System.Web.WebPages.Test/Html/SelectHelperTest.cs
new file mode 100644
index 00000000..fd8fc670
--- /dev/null
+++ b/test/System.Web.WebPages.Test/Html/SelectHelperTest.cs
@@ -0,0 +1,776 @@
+using System.Collections.Generic;
+using System.Linq;
+using System.Web.WebPages.Html;
+using Moq;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.WebPages.Test
+{
+ public class SelectExtensionsTest
+ {
+ [Fact]
+ public void DropDownListThrowsWithNoName()
+ {
+ // Arrange
+ HtmlHelper helper = HtmlHelperFactory.Create();
+
+ // Act and assert
+ Assert.ThrowsArgumentNullOrEmptyString(() => helper.DropDownList(name: null, selectList: null), "name");
+ }
+
+ [Fact]
+ public void DropDownListWithNoSelectedItem()
+ {
+ // Arrange
+ HtmlHelper helper = HtmlHelperFactory.Create();
+
+ // Act
+ var html = helper.DropDownList("foo", GetSelectList());
+
+ // Assert
+ Assert.Equal(
+ @"<select id=""foo"" name=""foo"">
+<option value=""A"">Alpha</option>
+<option value=""B"">Bravo</option>
+<option value=""C"">Charlie</option>
+</select>",
+ html.ToHtmlString());
+ }
+
+ [Fact]
+ public void DropDownListWithDefaultOption()
+ {
+ // Arrange
+ HtmlHelper helper = HtmlHelperFactory.Create();
+
+ // Act
+ var html = helper.DropDownList("foo", "select-one", GetSelectList());
+
+ // Assert
+ Assert.Equal(
+ @"<select id=""foo"" name=""foo"">
+<option value="""">select-one</option>
+<option value=""A"">Alpha</option>
+<option value=""B"">Bravo</option>
+<option value=""C"">Charlie</option>
+</select>",
+ html.ToHtmlString());
+ }
+
+ [Fact]
+ public void DropDownListWithAttributes()
+ {
+ // Arrange
+ HtmlHelper helper = HtmlHelperFactory.Create();
+
+ // Act
+ var html = helper.DropDownList("foo", GetSelectList(), new { attr = "attr-val", attr2 = "attr-val2" });
+
+ // Assert
+ Assert.Equal(
+ @"<select attr=""attr-val"" attr2=""attr-val2"" id=""foo"" name=""foo"">
+<option value=""A"">Alpha</option>
+<option value=""B"">Bravo</option>
+<option value=""C"">Charlie</option>
+</select>",
+ html.ToHtmlString());
+ }
+
+ [Fact]
+ public void DropDownListWithExplicitValue()
+ {
+ // Arrange
+ HtmlHelper helper = HtmlHelperFactory.Create();
+
+ // Act
+ var html = helper.DropDownList("foo", null, GetSelectList(), "B", new Dictionary<string, object> { { "attr", "attr-val" } });
+
+ // Assert
+ Assert.Equal(
+ @"<select attr=""attr-val"" id=""foo"" name=""foo"">
+<option value=""A"">Alpha</option>
+<option selected=""selected"" value=""B"">Bravo</option>
+<option value=""C"">Charlie</option>
+</select>",
+ html.ToHtmlString());
+ }
+
+ [Fact]
+ public void DropDownWithModelValue()
+ {
+ // Arrange
+ var modelState = new ModelStateDictionary();
+ modelState.SetModelValue("foo", "C");
+ HtmlHelper helper = HtmlHelperFactory.Create(modelState);
+
+ // Act
+ var html = helper.DropDownList("foo", GetSelectList(), new { attr = "attr-val" });
+
+ // Assert
+ Assert.Equal(
+ @"<select attr=""attr-val"" id=""foo"" name=""foo"">
+<option value=""A"">Alpha</option>
+<option value=""B"">Bravo</option>
+<option selected=""selected"" value=""C"">Charlie</option>
+</select>",
+ html.ToHtmlString());
+ }
+
+ [Fact]
+ public void DropDownWithExplictAndModelValue()
+ {
+ // Arrange
+ var modelState = new ModelStateDictionary();
+ modelState.SetModelValue("foo", "C");
+ HtmlHelper helper = HtmlHelperFactory.Create(modelState);
+
+ // Act
+ var html = helper.DropDownList("foo", null, GetSelectList(), "B", new { attr = "attr-val" });
+
+ // Assert
+ Assert.Equal(
+ @"<select attr=""attr-val"" id=""foo"" name=""foo"">
+<option value=""A"">Alpha</option>
+<option selected=""selected"" value=""B"">Bravo</option>
+<option value=""C"">Charlie</option>
+</select>",
+ html.ToHtmlString());
+ }
+
+ [Fact]
+ public void DropDownWithNonStringModelValue()
+ {
+ // Arrange
+ var modelState = new ModelStateDictionary();
+ modelState.SetModelValue("foo", 23);
+ HtmlHelper helper = HtmlHelperFactory.Create(modelState);
+
+ // Act
+ var html = helper.DropDownList("foo", null, GetSelectList(), new { attr = "attr-val" });
+
+ // Assert
+ Assert.Equal(
+ @"<select attr=""attr-val"" id=""foo"" name=""foo"">
+<option value=""A"">Alpha</option>
+<option value=""B"">Bravo</option>
+<option value=""C"">Charlie</option>
+</select>",
+ html.ToHtmlString());
+ }
+
+ [Fact]
+ public void DropDownWithNonStringExplicitValue()
+ {
+ // Arrange
+ HtmlHelper helper = HtmlHelperFactory.Create();
+
+ // Act
+ var html = helper.DropDownList("foo", null, GetSelectList(), new List<int>(), new { attr = "attr-val" });
+
+ // Assert
+ Assert.Equal(
+ @"<select attr=""attr-val"" id=""foo"" name=""foo"">
+<option value=""A"">Alpha</option>
+<option value=""B"">Bravo</option>
+<option value=""C"">Charlie</option>
+</select>",
+ html.ToHtmlString());
+ }
+
+ [Fact]
+ public void DropDownWithErrors()
+ {
+ // Arrange
+ var modelState = new ModelStateDictionary();
+ modelState.AddError("foo", "some error");
+ HtmlHelper helper = HtmlHelperFactory.Create(modelState);
+
+ // Act
+ var html = helper.DropDownList("foo", GetSelectList());
+
+ // Assert
+ Assert.Equal(
+ @"<select class=""input-validation-error"" id=""foo"" name=""foo"">
+<option value=""A"">Alpha</option>
+<option value=""B"">Bravo</option>
+<option value=""C"">Charlie</option>
+</select>",
+ html.ToHtmlString());
+ }
+
+ [Fact]
+ public void DropDownListWithErrorsAndCustomClass()
+ {
+ // Arrange
+ var modelState = new ModelStateDictionary();
+ modelState.AddError("foo", "some error");
+ HtmlHelper helper = HtmlHelperFactory.Create(modelState);
+
+ // Act
+ var html = helper.DropDownList("foo", GetSelectList(), new { @class = "my-class" });
+
+ // Assert
+ Assert.Equal(
+ @"<select class=""input-validation-error my-class"" id=""foo"" name=""foo"">
+<option value=""A"">Alpha</option>
+<option value=""B"">Bravo</option>
+<option value=""C"">Charlie</option>
+</select>",
+ html.ToHtmlString());
+ }
+
+ [Fact]
+ public void DropDownListWithEmptyOptionLabel()
+ {
+ // Arrange
+ var modelState = new ModelStateDictionary();
+ modelState.AddError("foo", "some error");
+ HtmlHelper helper = HtmlHelperFactory.Create(modelState);
+
+ // Act
+ var html = helper.DropDownList("foo", GetSelectList(), new { @class = "my-class" });
+
+ // Assert
+ Assert.Equal(
+ @"<select class=""input-validation-error my-class"" id=""foo"" name=""foo"">
+<option value=""A"">Alpha</option>
+<option value=""B"">Bravo</option>
+<option value=""C"">Charlie</option>
+</select>",
+ html.ToHtmlString());
+ }
+
+ [Fact]
+ public void DropDownListWithObjectDictionaryAndTitle()
+ {
+ // Arrange
+ HtmlHelper helper = HtmlHelperFactory.Create();
+
+ // Act
+ var html = helper.DropDownList("foo", "Select One", GetSelectList(), new { @class = "my-class" });
+
+ // Assert
+ Assert.Equal(
+ @"<select class=""my-class"" id=""foo"" name=""foo"">
+<option value="""">Select One</option>
+<option value=""A"">Alpha</option>
+<option value=""B"">Bravo</option>
+<option value=""C"">Charlie</option>
+</select>",
+ html.ToHtmlString());
+ }
+
+ [Fact]
+ public void DropDownListWithDotReplacementForId()
+ {
+ // Arrange
+ HtmlHelper helper = HtmlHelperFactory.Create();
+
+ // Act
+ var html = helper.DropDownList("foo.bar", "Select One", GetSelectList());
+
+ // Assert
+ Assert.Equal(
+ @"<select id=""foo_bar"" name=""foo.bar"">
+<option value="""">Select One</option>
+<option value=""A"">Alpha</option>
+<option value=""B"">Bravo</option>
+<option value=""C"">Charlie</option>
+</select>",
+ html.ToHtmlString());
+ }
+
+ [Fact]
+ public void DropDownAddsUnobtrusiveValidationAttributes()
+ {
+ // Arrange
+ const string fieldName = "name";
+ var modelStateDictionary = new ModelStateDictionary();
+ var validationHelper = new ValidationHelper(new Mock<HttpContextBase>().Object, modelStateDictionary);
+ HtmlHelper helper = HtmlHelperFactory.Create(modelStateDictionary, validationHelper);
+
+ // Act
+ validationHelper.RequireField(fieldName, "Please specify a valid Name.");
+ validationHelper.Add(fieldName, Validator.StringLength(30, errorMessage: "Name cannot exceed {0} characters"));
+ var html = helper.DropDownList(fieldName, GetSelectList(), htmlAttributes: new Dictionary<string, object> { { "data-some-val", "5" } });
+
+ // Assert
+ Assert.Equal(@"<select data-some-val=""5"" data-val=""true"" data-val-length=""Name cannot exceed 30 characters"" data-val-length-max=""30"" data-val-required=""Please specify a valid Name."" id=""name"" name=""name"">
+<option value=""A"">Alpha</option>
+<option value=""B"">Bravo</option>
+<option value=""C"">Charlie</option>
+</select>", html.ToString());
+ }
+
+ // ListBox
+
+ [Fact]
+ public void ListBoxThrowsWithNoName()
+ {
+ // Arrange
+ HtmlHelper helper = HtmlHelperFactory.Create();
+
+ // Act and assert
+ Assert.ThrowsArgumentNullOrEmptyString(() => helper.ListBox(name: null, selectList: null), "name");
+ }
+
+ [Fact]
+ public void ListBoxWithNoSelectedItem()
+ {
+ // Arrange
+ HtmlHelper helper = HtmlHelperFactory.Create();
+
+ // Act
+ var html = helper.ListBox("foo", GetSelectList());
+
+ // Assert
+ Assert.Equal(
+ @"<select id=""foo"" name=""foo"">
+<option value=""A"">Alpha</option>
+<option value=""B"">Bravo</option>
+<option value=""C"">Charlie</option>
+</select>",
+ html.ToHtmlString());
+ }
+
+ [Fact]
+ public void ListBoxWithDefaultOption()
+ {
+ // Arrange
+ HtmlHelper helper = HtmlHelperFactory.Create();
+
+ // Act
+ var html = helper.ListBox("foo", "select-one", GetSelectList());
+
+ // Assert
+ Assert.Equal(
+ @"<select id=""foo"" name=""foo"">
+<option value="""">select-one</option>
+<option value=""A"">Alpha</option>
+<option value=""B"">Bravo</option>
+<option value=""C"">Charlie</option>
+</select>",
+ html.ToHtmlString());
+ }
+
+ [Fact]
+ public void ListBoxWithAttributes()
+ {
+ // Arrange
+ HtmlHelper helper = HtmlHelperFactory.Create();
+
+ // Act
+ var html = helper.ListBox("foo", GetSelectList(), new { attr = "attr-val", attr2 = "attr-val2" });
+
+ // Assert
+ Assert.Equal(
+ @"<select attr=""attr-val"" attr2=""attr-val2"" id=""foo"" name=""foo"">
+<option value=""A"">Alpha</option>
+<option value=""B"">Bravo</option>
+<option value=""C"">Charlie</option>
+</select>",
+ html.ToHtmlString());
+ }
+
+ [Fact]
+ public void ListBoxWithExplicitValue()
+ {
+ // Arrange
+ HtmlHelper helper = HtmlHelperFactory.Create();
+
+ // Act
+ var html = helper.ListBox("foo", null, GetSelectList(), "B", new Dictionary<string, object> { { "attr", "attr-val" } });
+
+ // Assert
+ Assert.Equal(
+ @"<select attr=""attr-val"" id=""foo"" name=""foo"">
+<option value=""A"">Alpha</option>
+<option selected=""selected"" value=""B"">Bravo</option>
+<option value=""C"">Charlie</option>
+</select>",
+ html.ToHtmlString());
+ }
+
+ [Fact]
+ public void ListBoxWithModelValue()
+ {
+ // Arrange
+ var modelState = new ModelStateDictionary();
+ modelState.SetModelValue("foo", "C");
+ HtmlHelper helper = HtmlHelperFactory.Create(modelState);
+
+ // Act
+ var html = helper.ListBox("foo", GetSelectList(), new { attr = "attr-val" });
+
+ // Assert
+ Assert.Equal(
+ @"<select attr=""attr-val"" id=""foo"" name=""foo"">
+<option value=""A"">Alpha</option>
+<option value=""B"">Bravo</option>
+<option selected=""selected"" value=""C"">Charlie</option>
+</select>",
+ html.ToHtmlString());
+ }
+
+ [Fact]
+ public void ListBoxWithExplicitMultipleValuesAndNoMultiple()
+ {
+ // Arrange
+ HtmlHelper helper = HtmlHelperFactory.Create();
+
+ // Act
+ var html = helper.ListBox("foo", null, GetSelectList(), new[] { "B", "C" }, new Dictionary<string, object> { { "attr", "attr-val" } });
+
+ // Assert
+ Assert.Equal(
+ @"<select attr=""attr-val"" id=""foo"" name=""foo"">
+<option value=""A"">Alpha</option>
+<option selected=""selected"" value=""B"">Bravo</option>
+<option value=""C"">Charlie</option>
+</select>",
+ html.ToHtmlString());
+ }
+
+ [Fact]
+ public void ListBoxWithExplicitMultipleValuesAndMultiple()
+ {
+ // Arrange
+ HtmlHelper helper = HtmlHelperFactory.Create();
+
+ // Act
+ var html = helper.ListBox("foo", null, GetSelectList(), new[] { "B", "C" }, 4, true);
+
+ // Assert
+ Assert.Equal(
+ @"<select id=""foo"" multiple=""multiple"" name=""foo"" size=""4"">
+<option value=""A"">Alpha</option>
+<option selected=""selected"" value=""B"">Bravo</option>
+<option selected=""selected"" value=""C"">Charlie</option>
+</select>",
+ html.ToHtmlString());
+ }
+
+ [Fact]
+ public void ListBoxWithMultipleModelValue()
+ {
+ // Arrange
+ var modelState = new ModelStateDictionary();
+ modelState.SetModelValue("foo", new[] { "A", "C" });
+ HtmlHelper helper = HtmlHelperFactory.Create(modelState);
+
+ // Act
+ var html = helper.ListBox("foo", GetSelectList(), new { attr = "attr-val" });
+
+ // Assert
+ Assert.Equal(
+ @"<select attr=""attr-val"" id=""foo"" name=""foo"">
+<option selected=""selected"" value=""A"">Alpha</option>
+<option value=""B"">Bravo</option>
+<option value=""C"">Charlie</option>
+</select>",
+ html.ToHtmlString());
+ }
+
+ [Fact]
+ public void ListBoxWithModelValueAndExplicitSelectItem()
+ {
+ // Arrange
+ var modelState = new ModelStateDictionary();
+ modelState.SetModelValue("foo", new[] { "C", "D" });
+ HtmlHelper helper = HtmlHelperFactory.Create(modelState);
+ var selectList = GetSelectList().ToList();
+ selectList[1].Selected = true;
+
+ // Act
+ var html = helper.ListBox("foo", selectList, new { attr = "attr-val" });
+
+ // Assert
+ Assert.Equal(
+ @"<select attr=""attr-val"" id=""foo"" name=""foo"">
+<option value=""A"">Alpha</option>
+<option selected=""selected"" value=""B"">Bravo</option>
+<option value=""C"">Charlie</option>
+</select>",
+ html.ToHtmlString());
+ }
+
+ [Fact]
+ public void ListBoxWithMultiSelectAndMultipleModelValue()
+ {
+ // Arrange
+ var modelState = new ModelStateDictionary();
+ modelState.SetModelValue("foo", new[] { "A", "C" });
+ HtmlHelper helper = HtmlHelperFactory.Create(modelState);
+
+ // Act
+ var html = helper.ListBox("foo", GetSelectList(), null, 4, true);
+
+ // Assert
+ Assert.Equal(
+ @"<select id=""foo"" multiple=""multiple"" name=""foo"" size=""4"">
+<option selected=""selected"" value=""A"">Alpha</option>
+<option value=""B"">Bravo</option>
+<option selected=""selected"" value=""C"">Charlie</option>
+</select>",
+ html.ToHtmlString());
+ }
+
+ [Fact]
+ public void ListBoxWithMultiSelectAndMultipleExplicitValues()
+ {
+ // Arrange
+ HtmlHelper helper = HtmlHelperFactory.Create();
+
+ // Act
+ var html = helper.ListBox("foo", GetSelectList(), new[] { "A", "C" }, 4, true);
+
+ // Assert
+ Assert.Equal(
+ @"<select id=""foo"" multiple=""multiple"" name=""foo"" size=""4"">
+<option selected=""selected"" value=""A"">Alpha</option>
+<option value=""B"">Bravo</option>
+<option selected=""selected"" value=""C"">Charlie</option>
+</select>",
+ html.ToHtmlString());
+ }
+
+ [Fact]
+ public void ListBoxWithMultiSelectAndExplitSelectValue()
+ {
+ // Arrange
+ HtmlHelper helper = HtmlHelperFactory.Create();
+ var selectList = GetSelectList().ToList();
+ selectList.First().Selected = selectList.Last().Selected = true;
+
+ // Act
+ var html = helper.ListBox("foo", selectList, new[] { "B" }, 4, true);
+
+ // Assert
+ Assert.Equal(
+ @"<select id=""foo"" multiple=""multiple"" name=""foo"" size=""4"">
+<option selected=""selected"" value=""A"">Alpha</option>
+<option selected=""selected"" value=""B"">Bravo</option>
+<option selected=""selected"" value=""C"">Charlie</option>
+</select>",
+ html.ToHtmlString());
+ }
+
+ [Fact]
+ public void ListBoxWithExplictAndModelValue()
+ {
+ // Arrange
+ var modelState = new ModelStateDictionary();
+ modelState.SetModelValue("foo", "C");
+ HtmlHelper helper = HtmlHelperFactory.Create(modelState);
+
+ // Act
+ var html = helper.ListBox("foo", defaultOption: null, selectList: GetSelectList(),
+ selectedValues: "B", htmlAttributes: new { attr = "attr-val" });
+
+ // Assert
+ Assert.Equal(
+ @"<select attr=""attr-val"" id=""foo"" name=""foo"">
+<option value=""A"">Alpha</option>
+<option selected=""selected"" value=""B"">Bravo</option>
+<option value=""C"">Charlie</option>
+</select>",
+ html.ToHtmlString());
+ }
+
+ [Fact]
+ public void ListBoxWithErrorAndExplictAndModelState()
+ {
+ // Arrange
+ var modelState = new ModelStateDictionary();
+ modelState.SetModelValue("foo", "C");
+ modelState.AddError("foo", "test");
+ HtmlHelper helper = HtmlHelperFactory.Create(modelState);
+
+ // Act
+ var html = helper.ListBox("foo.bar", "Select One", GetSelectList());
+
+ // Assert
+ Assert.Equal(
+ @"<select id=""foo_bar"" name=""foo.bar"">
+<option value="""">Select One</option>
+<option value=""A"">Alpha</option>
+<option value=""B"">Bravo</option>
+<option value=""C"">Charlie</option>
+</select>",
+ html.ToHtmlString());
+ }
+
+ [Fact]
+ public void ListBoxWithNonStringModelValue()
+ {
+ // Arrange
+ var modelState = new ModelStateDictionary();
+ modelState.SetModelValue("foo", 23);
+ HtmlHelper helper = HtmlHelperFactory.Create(modelState);
+
+ // Act
+ var html = helper.ListBox("foo", null, GetSelectList(), new { attr = "attr-val" });
+
+ // Assert
+ Assert.Equal(
+ @"<select attr=""attr-val"" id=""foo"" name=""foo"">
+<option value=""A"">Alpha</option>
+<option value=""B"">Bravo</option>
+<option value=""C"">Charlie</option>
+</select>",
+ html.ToHtmlString());
+ }
+
+ [Fact]
+ public void ListBoxWithNonStringExplicitValue()
+ {
+ // Arrange
+ HtmlHelper helper = HtmlHelperFactory.Create();
+
+ // Act
+ var html = helper.ListBox("foo", null, GetSelectList(), new List<int>(), new { attr = "attr-val" });
+
+ // Assert
+ Assert.Equal(
+ @"<select attr=""attr-val"" id=""foo"" name=""foo"">
+<option value=""A"">Alpha</option>
+<option value=""B"">Bravo</option>
+<option value=""C"">Charlie</option>
+</select>",
+ html.ToHtmlString());
+ }
+
+ [Fact]
+ public void ListBoxWithErrors()
+ {
+ // Arrange
+ var modelState = new ModelStateDictionary();
+ modelState.AddError("foo", "some error");
+ HtmlHelper helper = HtmlHelperFactory.Create(modelState);
+
+ // Act
+ var html = helper.ListBox("foo", GetSelectList());
+
+ // Assert
+ Assert.Equal(
+ @"<select class=""input-validation-error"" id=""foo"" name=""foo"">
+<option value=""A"">Alpha</option>
+<option value=""B"">Bravo</option>
+<option value=""C"">Charlie</option>
+</select>",
+ html.ToHtmlString());
+ }
+
+ [Fact]
+ public void ListBoxWithErrorsAndCustomClass()
+ {
+ // Arrange
+ var modelState = new ModelStateDictionary();
+ modelState.AddError("foo", "some error");
+ HtmlHelper helper = HtmlHelperFactory.Create(modelState);
+
+ // Act
+ var html = helper.ListBox("foo", GetSelectList(), new { @class = "my-class" });
+
+ // Assert
+ Assert.Equal(
+ @"<select class=""input-validation-error my-class"" id=""foo"" name=""foo"">
+<option value=""A"">Alpha</option>
+<option value=""B"">Bravo</option>
+<option value=""C"">Charlie</option>
+</select>",
+ html.ToHtmlString());
+ }
+
+ [Fact]
+ public void ListBoxWithEmptyOptionLabel()
+ {
+ // Arrange
+ var modelState = new ModelStateDictionary();
+ modelState.AddError("foo", "some error");
+ HtmlHelper helper = HtmlHelperFactory.Create(modelState);
+
+ // Act
+ var html = helper.ListBox("foo", GetSelectList(), new { @class = "my-class" });
+
+ // Assert
+ Assert.Equal(
+ @"<select class=""input-validation-error my-class"" id=""foo"" name=""foo"">
+<option value=""A"">Alpha</option>
+<option value=""B"">Bravo</option>
+<option value=""C"">Charlie</option>
+</select>",
+ html.ToHtmlString());
+ }
+
+ [Fact]
+ public void ListBoxWithObjectDictionaryAndTitle()
+ {
+ // Arrange
+ HtmlHelper helper = HtmlHelperFactory.Create();
+
+ // Act
+ var html = helper.ListBox("foo", "Select One", GetSelectList(), new { @class = "my-class" });
+
+ // Assert
+ Assert.Equal(
+ @"<select class=""my-class"" id=""foo"" name=""foo"">
+<option value="""">Select One</option>
+<option value=""A"">Alpha</option>
+<option value=""B"">Bravo</option>
+<option value=""C"">Charlie</option>
+</select>",
+ html.ToHtmlString());
+ }
+
+ [Fact]
+ public void ListBoxWithDotReplacementForId()
+ {
+ // Arrange
+ HtmlHelper helper = HtmlHelperFactory.Create();
+
+ // Act
+ var html = helper.ListBox("foo.bar", "Select One", GetSelectList());
+
+ // Assert
+ Assert.Equal(
+ @"<select id=""foo_bar"" name=""foo.bar"">
+<option value="""">Select One</option>
+<option value=""A"">Alpha</option>
+<option value=""B"">Bravo</option>
+<option value=""C"">Charlie</option>
+</select>",
+ html.ToHtmlString());
+ }
+
+ [Fact]
+ public void ListBoxAddsUnobtrusiveValidationAttributes()
+ {
+ // Arrange
+ const string fieldName = "name";
+ var modelStateDictionary = new ModelStateDictionary();
+ var validationHelper = new ValidationHelper(new Mock<HttpContextBase>().Object, modelStateDictionary);
+ HtmlHelper helper = HtmlHelperFactory.Create(modelStateDictionary, validationHelper);
+
+ // Act
+ validationHelper.RequireField(fieldName, "Please specify a valid Name.");
+ validationHelper.Add(fieldName, Validator.StringLength(30, errorMessage: "Name cannot exceed {0} characters"));
+ var html = helper.ListBox(fieldName, GetSelectList(), htmlAttributes: new Dictionary<string, object> { { "data-some-val", "5" } });
+
+ // Assert
+ Assert.Equal(@"<select data-some-val=""5"" data-val=""true"" data-val-length=""Name cannot exceed 30 characters"" data-val-length-max=""30"" data-val-required=""Please specify a valid Name."" id=""name"" name=""name"">
+<option value=""A"">Alpha</option>
+<option value=""B"">Bravo</option>
+<option value=""C"">Charlie</option>
+</select>", html.ToString());
+ }
+
+ private static IEnumerable<SelectListItem> GetSelectList()
+ {
+ yield return new SelectListItem() { Text = "Alpha", Value = "A" };
+ yield return new SelectListItem() { Text = "Bravo", Value = "B" };
+ yield return new SelectListItem() { Text = "Charlie", Value = "C" };
+ }
+ }
+}
diff --git a/test/System.Web.WebPages.Test/Html/TextAreaHelperTest.cs b/test/System.Web.WebPages.Test/Html/TextAreaHelperTest.cs
new file mode 100644
index 00000000..27f16845
--- /dev/null
+++ b/test/System.Web.WebPages.Test/Html/TextAreaHelperTest.cs
@@ -0,0 +1,209 @@
+using System.Collections.Generic;
+using System.Web.WebPages.Html;
+using Moq;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.WebPages.Test
+{
+ public class TextAreaExtensionsTest
+ {
+ [Fact]
+ public void TextAreaWithEmptyNameThrows()
+ {
+ // Arrange
+ HtmlHelper helper = HtmlHelperFactory.Create();
+
+ // Act and assert
+ Assert.ThrowsArgument(() => helper.TextArea(null), "name", "Value cannot be null or an empty string.");
+
+ // Act and assert
+ Assert.ThrowsArgument(() => helper.TextArea(String.Empty), "name", "Value cannot be null or an empty string.");
+ }
+
+ [Fact]
+ public void TextAreaWithDefaultRowsAndCols()
+ {
+ // Arrange
+ HtmlHelper helper = HtmlHelperFactory.Create();
+
+ // Act
+ var html = helper.TextArea("foo");
+
+ // Assert
+ Assert.Equal(@"<textarea cols=""20"" id=""foo"" name=""foo"" rows=""2""></textarea>", html.ToHtmlString());
+ }
+
+ [Fact]
+ public void TextAreaWithZeroRowsAndColumns()
+ {
+ // Arrange
+ HtmlHelper helper = HtmlHelperFactory.Create();
+
+ // Act
+ var html = helper.TextArea("foo", null, 0, 0, null);
+
+ // Assert
+ Assert.Equal(@"<textarea id=""foo"" name=""foo""></textarea>", html.ToHtmlString());
+ }
+
+ [Fact]
+ public void TextAreaWithNonZeroRowsAndColumns()
+ {
+ // Arrange
+ HtmlHelper helper = HtmlHelperFactory.Create();
+
+ // Act
+ var html = helper.TextArea("foo", null, 4, 10, null);
+
+ // Assert
+ Assert.Equal(@"<textarea cols=""10"" id=""foo"" name=""foo"" rows=""4""></textarea>", html.ToHtmlString());
+ }
+
+ [Fact]
+ public void TextAreaWithObjectAttributes()
+ {
+ // Arrange
+ ModelStateDictionary modelState = new ModelStateDictionary();
+ modelState.SetModelValue("foo", "foo-value");
+ HtmlHelper helper = HtmlHelperFactory.Create(modelState);
+
+ // Act
+ var html = helper.TextArea("foo", new { attr = "value", cols = 6 });
+
+ // Assert
+ Assert.Equal(@"<textarea attr=""value"" cols=""6"" id=""foo"" name=""foo"" rows=""2"">foo-value</textarea>", html.ToHtmlString());
+ }
+
+ [Fact]
+ public void TextAreaWithExplicitValue()
+ {
+ // Arrange
+ ModelStateDictionary modelState = new ModelStateDictionary();
+ modelState.SetModelValue("foo", "explicit-foo-value");
+ HtmlHelper helper = HtmlHelperFactory.Create(modelState);
+
+ // Act
+ var html = helper.TextArea("foo", "explicit-foo-value", new { attr = "attr-value", cols = 6 });
+
+ // Assert
+ Assert.Equal(@"<textarea attr=""attr-value"" cols=""6"" id=""foo"" name=""foo"" rows=""2"">explicit-foo-value</textarea>",
+ html.ToHtmlString());
+ }
+
+ [Fact]
+ public void TextAreaWithDictionaryAttributes()
+ {
+ // Arrange
+ ModelStateDictionary modelState = new ModelStateDictionary();
+ modelState.SetModelValue("foo", "explicit-foo-value");
+ HtmlHelper helper = HtmlHelperFactory.Create(modelState);
+ var attributes = new Dictionary<string, object>() { { "attr", "attr-val" }, { "rows", 15 }, { "cols", 12 } };
+ // Act
+ var html = helper.TextArea("foo", attributes);
+
+ // Assert
+ Assert.Equal(@"<textarea attr=""attr-val"" cols=""12"" id=""foo"" name=""foo"" rows=""15"">explicit-foo-value</textarea>",
+ html.ToHtmlString());
+ }
+
+ [Fact]
+ public void TextAreaWithNoValueAndObjectAttributes()
+ {
+ // Arrange
+ HtmlHelper helper = HtmlHelperFactory.Create();
+ var attributes = new Dictionary<string, object>() { { "attr", "attr-val" }, { "rows", 15 }, { "cols", 12 } };
+ // Act
+ var html = helper.TextArea("foo", attributes);
+
+ // Assert
+ Assert.Equal(@"<textarea attr=""attr-val"" cols=""12"" id=""foo"" name=""foo"" rows=""15""></textarea>",
+ html.ToHtmlString());
+ }
+
+ [Fact]
+ public void TextAreaWithNullValue()
+ {
+ // Arrange
+ ModelStateDictionary modelState = new ModelStateDictionary();
+ modelState.SetModelValue("foo", "explicit-foo-value");
+ HtmlHelper helper = HtmlHelperFactory.Create(modelState);
+ var attributes = new Dictionary<string, object>() { { "attr", "attr-val" }, { "rows", 15 }, { "cols", 12 } };
+ // Act
+ var html = helper.TextArea("foo", null, attributes);
+
+ // Assert
+ Assert.Equal(@"<textarea attr=""attr-val"" cols=""12"" id=""foo"" name=""foo"" rows=""15"">explicit-foo-value</textarea>",
+ html.ToHtmlString());
+ }
+
+ [Fact]
+ public void TextAreaWithError()
+ {
+ // Arrange
+ ModelStateDictionary modelState = new ModelStateDictionary();
+ modelState.AddError("foo", "some error");
+ HtmlHelper helper = HtmlHelperFactory.Create(modelState);
+
+ // Act
+ var html = helper.TextArea("foo", String.Empty);
+
+ // Assert
+ Assert.Equal(@"<textarea class=""input-validation-error"" cols=""20"" id=""foo"" name=""foo"" rows=""2""></textarea>",
+ html.ToHtmlString());
+ }
+
+ [Fact]
+ public void TextAreaWithErrorAndCustomCssClass()
+ {
+ // Arrange
+ ModelStateDictionary modelState = new ModelStateDictionary();
+ modelState.AddError("foo", "some error");
+ HtmlHelper helper = HtmlHelperFactory.Create(modelState);
+
+ // Act
+ var html = helper.TextArea("foo", String.Empty, new { @class = "my-css" });
+
+ // Assert
+ Assert.Equal(@"<textarea class=""input-validation-error my-css"" cols=""20"" id=""foo"" name=""foo"" rows=""2""></textarea>",
+ html.ToHtmlString());
+ }
+
+ // [Fact]
+ // Cant test this in multi-threaded
+ public void TextAreaWithCustomErrorClass()
+ {
+ // Arrange
+ ModelStateDictionary modelState = new ModelStateDictionary();
+ modelState.AddError("foo", "some error");
+ HtmlHelper.ValidationInputCssClassName = "custom-input-validation-error";
+ HtmlHelper helper = HtmlHelperFactory.Create(modelState);
+
+ // Act
+ var html = helper.TextArea("foo", String.Empty, new { @class = "my-css" });
+
+ // Assert
+ Assert.Equal(@"<textarea class=""custom-input-validation-error my-css"" cols=""20"" id=""foo"" name=""foo"" rows=""2""></textarea>",
+ html.ToHtmlString());
+ }
+
+ [Fact]
+ public void TextAreaAddsUnobtrusiveValidationAttributes()
+ {
+ // Arrange
+ const string fieldName = "name";
+ var modelStateDictionary = new ModelStateDictionary();
+ var validationHelper = new ValidationHelper(new Mock<HttpContextBase>().Object, modelStateDictionary);
+ HtmlHelper helper = HtmlHelperFactory.Create(modelStateDictionary, validationHelper);
+
+ // Act
+ validationHelper.RequireField(fieldName, "Please specify a valid Name.");
+ validationHelper.Add(fieldName, Validator.StringLength(30, errorMessage: "Name cannot exceed {0} characters"));
+ var html = helper.TextArea(fieldName, htmlAttributes: new Dictionary<string, object> { { "data-some-val", "5" } });
+
+ // Assert
+ Assert.Equal(@"<textarea cols=""20"" data-some-val=""5"" data-val=""true"" data-val-length=""Name cannot exceed 30 characters"" data-val-length-max=""30"" data-val-required=""Please specify a valid Name."" id=""name"" name=""name"" rows=""2""></textarea>",
+ html.ToString());
+ }
+ }
+}
diff --git a/test/System.Web.WebPages.Test/Html/ValidationHelperTest.cs b/test/System.Web.WebPages.Test/Html/ValidationHelperTest.cs
new file mode 100644
index 00000000..fbd09d2c
--- /dev/null
+++ b/test/System.Web.WebPages.Test/Html/ValidationHelperTest.cs
@@ -0,0 +1,342 @@
+using System.Collections.Generic;
+using System.Web.WebPages.Html;
+using Xunit;
+
+namespace System.Web.WebPages.Test
+{
+ public class ValidationHelperTest
+ {
+ [Fact]
+ public void ValidationMessageAllowsEmptyModelName()
+ {
+ // Arrange
+ ModelStateDictionary dictionary = new ModelStateDictionary();
+ dictionary.AddError("test", "some error text");
+ HtmlHelper htmlHelper = HtmlHelperFactory.Create(dictionary);
+
+ // Act
+ var html = htmlHelper.ValidationMessage("test");
+
+ // Assert
+ Assert.Equal(@"<span class=""field-validation-error"" data-valmsg-for=""test"" data-valmsg-replace=""true"">some error text</span>", html.ToHtmlString());
+ }
+
+ [Fact]
+ public void ValidationMessageReturnsFirstError()
+ {
+ // Arrange
+ HtmlHelper htmlHelper = HtmlHelperFactory.Create(GetModelStateWithErrors());
+
+ // Act
+ var html = htmlHelper.ValidationMessage("foo");
+
+ // Assert
+ Assert.Equal(@"<span class=""field-validation-error"" data-valmsg-for=""foo"" data-valmsg-replace=""true"">foo error &lt;1&gt;</span>", html.ToHtmlString());
+ }
+
+ [Fact]
+ public void ValidationMessageUsesValidCssClassIfFieldDoesNotHaveErrors()
+ {
+ // Arrange
+ HtmlHelper htmlHelper = HtmlHelperFactory.Create(GetModelStateWithErrors());
+
+ // Act
+ var html = htmlHelper.ValidationMessage("baz");
+
+ // Assert
+ Assert.Equal(@"<span class=""field-validation-valid"" data-valmsg-for=""baz"" data-valmsg-replace=""true""></span>", html.ToString());
+ }
+
+ [Fact]
+ public void ValidationMessageReturnsWithObjectAttributes()
+ {
+ // Arrange
+ HtmlHelper htmlHelper = HtmlHelperFactory.Create(GetModelStateWithErrors());
+
+ // Act
+ var html = htmlHelper.ValidationMessage("foo", new { attr = "attr-value" });
+
+ // Assert
+ Assert.Equal(@"<span attr=""attr-value"" class=""field-validation-error"" data-valmsg-for=""foo"" data-valmsg-replace=""true"">foo error &lt;1&gt;</span>",
+ html.ToHtmlString());
+ }
+
+ [Fact]
+ public void ValidationMessageReturnsWithCustomMessage()
+ {
+ // Arrange
+ HtmlHelper htmlHelper = HtmlHelperFactory.Create(GetModelStateWithErrors());
+
+ // Atc
+ var html = htmlHelper.ValidationMessage("foo", "bar error");
+
+ // Assert
+ Assert.Equal(@"<span class=""field-validation-error"" data-valmsg-for=""foo"" data-valmsg-replace=""false"">bar error</span>", html.ToHtmlString());
+ }
+
+ [Fact]
+ public void ValidationMessageReturnsWithCustomMessageAndObjectAttributes()
+ {
+ // Arrange
+ HtmlHelper htmlHelper = HtmlHelperFactory.Create(GetModelStateWithErrors());
+
+ // Act
+ var html = htmlHelper.ValidationMessage("foo", "bar error", new { baz = "baz" });
+
+ // Assert
+ Assert.Equal(@"<span baz=""baz"" class=""field-validation-error"" data-valmsg-for=""foo"" data-valmsg-replace=""false"">bar error</span>", html.ToHtmlString());
+ }
+
+ [Fact]
+ public void ValidationMessageWithModelStateAndNoErrors()
+ {
+ // Arrange
+ HtmlHelper htmlHelper = HtmlHelperFactory.Create(GetModelStateWithErrors());
+
+ // Act
+ var html = htmlHelper.ValidationMessage("baz");
+
+ // Assert
+ Assert.Equal(@"<span class=""field-validation-valid"" data-valmsg-for=""baz"" data-valmsg-replace=""true""></span>", html.ToString());
+ }
+
+ [Fact]
+ public void ValidationSummary()
+ {
+ // Arrange
+ HtmlHelper htmlHelper = HtmlHelperFactory.Create(GetModelStateWithErrors());
+
+ // Act
+ var html = htmlHelper.ValidationSummary();
+
+ // Assert
+ Assert.Equal(@"<div class=""validation-summary-errors"" data-valmsg-summary=""true""><ul>
+<li>foo error &lt;1&gt;</li>
+<li>foo error &lt;2&gt;</li>
+<li>bar error &lt;1&gt;</li>
+<li>bar error &lt;2&gt;</li>
+</ul></div>"
+ , html.ToHtmlString());
+ }
+
+ [Fact]
+ public void ValidationSummaryWithMessage()
+ {
+ // Arrange
+ HtmlHelper htmlHelper = HtmlHelperFactory.Create(GetModelStateWithErrors());
+
+ // Act
+ var html = htmlHelper.ValidationSummary("test message");
+
+ // Assert
+ Assert.Equal(@"<div class=""validation-summary-errors"" data-valmsg-summary=""true""><span>test message</span>
+<ul>
+<li>foo error &lt;1&gt;</li>
+<li>foo error &lt;2&gt;</li>
+<li>bar error &lt;1&gt;</li>
+<li>bar error &lt;2&gt;</li>
+</ul></div>"
+ , html.ToHtmlString());
+ }
+
+ [Fact]
+ public void ValidationSummaryWithFormErrors()
+ {
+ // Arrange
+ HtmlHelper htmlHelper = HtmlHelperFactory.Create(GetModelStateWithFormErrors());
+
+ // Act
+ var html = htmlHelper.ValidationSummary();
+
+ // Assert
+ Assert.Equal(@"<div class=""validation-summary-errors"" data-valmsg-summary=""true""><ul>
+<li>foo error &lt;1&gt;</li>
+<li>foo error &lt;2&gt;</li>
+<li>bar error &lt;1&gt;</li>
+<li>bar error &lt;2&gt;</li>
+<li>some form error &lt;1&gt;</li>
+<li>some form error &lt;2&gt;</li>
+</ul></div>"
+ , html.ToHtmlString());
+ }
+
+ [Fact]
+ public void ValidationSummaryWithFormErrorsAndExcludeFieldErrors()
+ {
+ // Arrange
+ HtmlHelper htmlHelper = HtmlHelperFactory.Create(GetModelStateWithFormErrors());
+
+ // Act
+ var html = htmlHelper.ValidationSummary(excludeFieldErrors: true);
+
+ // Assert
+ Assert.Equal(@"<div class=""validation-summary-errors""><ul>
+<li>some form error &lt;1&gt;</li>
+<li>some form error &lt;2&gt;</li>
+</ul></div>"
+ , html.ToHtmlString());
+ }
+
+ [Fact]
+ public void ValidationSummaryWithObjectProperties()
+ {
+ // Arrange
+ HtmlHelper htmlHelper = HtmlHelperFactory.Create(GetModelStateWithErrors());
+
+ // Act
+ var html = htmlHelper.ValidationSummary(new { attr = "attr-value", @class = "my-class" });
+
+ // Assert
+ Assert.Equal(@"<div attr=""attr-value"" class=""validation-summary-errors my-class"" data-valmsg-summary=""true""><ul>
+<li>foo error &lt;1&gt;</li>
+<li>foo error &lt;2&gt;</li>
+<li>bar error &lt;1&gt;</li>
+<li>bar error &lt;2&gt;</li>
+</ul></div>"
+ , html.ToHtmlString());
+ }
+
+ [Fact]
+ public void ValidationSummaryWithDictionary()
+ {
+ // Arrange
+ HtmlHelper htmlHelper = HtmlHelperFactory.Create(GetModelStateWithErrors());
+
+ // Act
+ var html = htmlHelper.ValidationSummary(new Dictionary<string, object> { { "attr", "attr-value" }, { "class", "my-class" } });
+
+ // Assert
+ Assert.Equal(@"<div attr=""attr-value"" class=""validation-summary-errors my-class"" data-valmsg-summary=""true""><ul>
+<li>foo error &lt;1&gt;</li>
+<li>foo error &lt;2&gt;</li>
+<li>bar error &lt;1&gt;</li>
+<li>bar error &lt;2&gt;</li>
+</ul></div>"
+ , html.ToHtmlString());
+ }
+
+ [Fact]
+ public void ValidationSummaryWithDictionaryAndMessage()
+ {
+ HtmlHelper htmlHelper = HtmlHelperFactory.Create(GetModelStateWithErrors());
+
+ // Act
+ var html = htmlHelper.ValidationSummary("This is a message.", new Dictionary<string, object> { { "attr", "attr-value" }, { "class", "my-class" } });
+
+ // Assert
+ Assert.Equal(@"<div attr=""attr-value"" class=""validation-summary-errors my-class"" data-valmsg-summary=""true""><span>This is a message.</span>
+<ul>
+<li>foo error &lt;1&gt;</li>
+<li>foo error &lt;2&gt;</li>
+<li>bar error &lt;1&gt;</li>
+<li>bar error &lt;2&gt;</li>
+</ul></div>"
+ , html.ToHtmlString());
+ }
+
+ //[Fact]
+ // Cant test this, as it sets a static property
+ public void ValidationSummaryWithCustomValidationSummaryClass()
+ {
+ // Arrange
+ HtmlHelper.ValidationSummaryClass = "my-val-class";
+ HtmlHelper htmlHelper = HtmlHelperFactory.Create(GetModelStateWithErrors());
+
+ // Act
+ var html = htmlHelper.ValidationSummary("This is a message.", new Dictionary<string, object> { { "attr", "attr-value" }, { "class", "my-class" } });
+
+ // Assert
+ Assert.Equal(@"<div attr=""attr-value"" class=""my-val-class my-class""><span>This is a message.</span>
+<ul>
+<li>foo error &lt;1&gt;</li>
+<li>foo error &lt;2&gt;</li>
+<li>bar error &lt;1&gt;</li>
+<li>bar error &lt;2&gt;</li>
+</ul></div>"
+ , html.ToHtmlString());
+ }
+
+ [Fact]
+ public void ValidationSummaryWithNoErrorReturnsNullIfExcludeFieldErrorsIsSetToFalse()
+ {
+ // Arrange
+ HtmlHelper htmlHelper = HtmlHelperFactory.Create();
+
+ // Act
+ var html = htmlHelper.ValidationSummary(excludeFieldErrors: false);
+
+ // Assert
+ Assert.Equal(@"<div class=""validation-summary-valid"" data-valmsg-summary=""true""><ul>
+</ul></div>", html.ToString());
+ }
+
+ [Fact]
+ public void ValidationSummaryWithNoErrorReturnsNull()
+ {
+ // Arrange
+ HtmlHelper htmlHelper = HtmlHelperFactory.Create();
+
+ // Act
+ var html = htmlHelper.ValidationSummary(excludeFieldErrors: true);
+
+ // Assert
+ Assert.Null(html);
+ }
+
+ [Fact]
+ public void ValidationSummaryWithNoFormErrorsAndExcludedFieldErrorsReturnsNull()
+ {
+ // Arrange
+ ModelStateDictionary modelState = new ModelStateDictionary();
+ modelState.AddError("foo", "error");
+ modelState.AddError("bar", "error");
+
+ HtmlHelper htmlHelper = HtmlHelperFactory.Create(modelState);
+
+ // Act
+ var html = htmlHelper.ValidationSummary(excludeFieldErrors: true);
+
+ // Assert
+ Assert.Null(html);
+ }
+
+ [Fact]
+ public void ValidationSummaryWithMultipleFormErrorsAndExcludedFieldErrors()
+ {
+ // Arrange
+ ModelStateDictionary modelState = new ModelStateDictionary();
+ modelState.AddFormError("error <1>");
+ modelState.AddFormError("error <2>");
+
+ HtmlHelper htmlHelper = HtmlHelperFactory.Create(modelState);
+
+ // Act
+ var html = htmlHelper.ValidationSummary(excludeFieldErrors: true);
+
+ // Assert
+ Assert.Equal(@"<div class=""validation-summary-errors""><ul>
+<li>error &lt;1&gt;</li>
+<li>error &lt;2&gt;</li>
+</ul></div>"
+ , html.ToHtmlString());
+ }
+
+ private static ModelStateDictionary GetModelStateWithErrors()
+ {
+ ModelStateDictionary modelState = new ModelStateDictionary();
+ modelState.AddError("foo", "foo error <1>");
+ modelState.AddError("foo", "foo error <2>");
+ modelState.AddError("bar", "bar error <1>");
+ modelState.AddError("bar", "bar error <2>");
+ return modelState;
+ }
+
+ private static ModelStateDictionary GetModelStateWithFormErrors()
+ {
+ ModelStateDictionary modelState = GetModelStateWithErrors();
+ modelState.AddFormError("some form error <1>");
+ modelState.AddFormError("some form error <2>");
+ return modelState;
+ }
+ }
+}
diff --git a/test/System.Web.WebPages.Test/Instrumentation/InstrumentationServiceTest.cs b/test/System.Web.WebPages.Test/Instrumentation/InstrumentationServiceTest.cs
new file mode 100644
index 00000000..c9226fef
--- /dev/null
+++ b/test/System.Web.WebPages.Test/Instrumentation/InstrumentationServiceTest.cs
@@ -0,0 +1,97 @@
+using System.Collections.Generic;
+using System.Dynamic;
+using System.IO;
+using System.Web.WebPages.Instrumentation;
+using Xunit;
+
+namespace System.Web.WebPages.Test.Instrumentation
+{
+ public class InstrumentationServiceTest
+ {
+ [Fact]
+ public void BeginContextDelegatesToRegisteredListeners()
+ {
+ // Arrange
+ dynamic listener1 = CreateListener();
+ dynamic listener2 = CreateListener();
+ InstrumentationService inst = CreateInstrumentationService(listener1, listener2);
+ TextWriter mockWriter = new StringWriter();
+
+ // Act
+ inst.BeginContext(null, "Foo.cshtml", mockWriter, 42, 24, isLiteral: false);
+
+ // Assert
+ Assert.Equal(1, listener1.BeginContextCalls.Count);
+ Assert.Equal(0, listener1.EndContextCalls.Count);
+ Assert.Equal(1, listener2.BeginContextCalls.Count);
+ Assert.Equal(0, listener2.EndContextCalls.Count);
+
+ AssertContext("Foo.cshtml", mockWriter, 42, 24, false, listener1.BeginContextCalls[0]);
+ AssertContext("Foo.cshtml", mockWriter, 42, 24, false, listener2.BeginContextCalls[0]);
+ }
+
+ [Fact]
+ public void EndContextDelegatesToRegisteredListeners()
+ {
+ // Arrange
+ dynamic listener1 = CreateListener();
+ dynamic listener2 = CreateListener();
+ InstrumentationService inst = CreateInstrumentationService(listener1, listener2);
+ TextWriter mockWriter = new StringWriter();
+
+ // Act
+ inst.EndContext(null, "Foo.cshtml", mockWriter, 42, 24, isLiteral: false);
+
+ // Assert
+ Assert.Equal(1, listener1.EndContextCalls.Count);
+ Assert.Equal(0, listener1.BeginContextCalls.Count);
+ Assert.Equal(1, listener2.EndContextCalls.Count);
+ Assert.Equal(0, listener2.BeginContextCalls.Count);
+
+ AssertContext("Foo.cshtml", mockWriter, 42, 24, false, listener1.EndContextCalls[0]);
+ AssertContext("Foo.cshtml", mockWriter, 42, 24, false, listener2.EndContextCalls[0]);
+ }
+
+ private void AssertContext(string virtualPath, TextWriter writer, int startPosition, int length, bool isLiteral, dynamic context)
+ {
+ PageExecutionContextAdapter ctx = new PageExecutionContextAdapter(context);
+ Assert.Equal(virtualPath, ctx.VirtualPath);
+ Assert.Same(writer, ctx.TextWriter);
+ Assert.Equal(startPosition, ctx.StartPosition);
+ Assert.Equal(length, ctx.Length);
+ Assert.Equal(isLiteral, ctx.IsLiteral);
+ }
+
+ private InstrumentationService CreateInstrumentationService(params dynamic[] listeners)
+ {
+ dynamic service = new ExpandoObject();
+ service.ExecutionListeners = new List<dynamic>(listeners);
+ InstrumentationService inst = new InstrumentationService();
+ inst.IsAvailable = true;
+ inst.ExtractInstrumentationService = _ => new PageInstrumentationServiceAdapter(service);
+ inst.CreateContext = CreateExpandoContext;
+ return inst;
+ }
+
+ private dynamic CreateListener()
+ {
+ dynamic listener = new ExpandoObject();
+ listener.BeginContextCalls = new List<dynamic>();
+ listener.EndContextCalls = new List<dynamic>();
+ listener.BeginContext = (Action<dynamic>)(d => { listener.BeginContextCalls.Add(d); });
+ listener.EndContext = (Action<dynamic>)(d => { listener.EndContextCalls.Add(d); });
+ return listener;
+ }
+
+ private PageExecutionContextAdapter CreateExpandoContext(string virtualPath, TextWriter writer, int startPosition, int length, bool isLiteral)
+ {
+ dynamic ctx = new ExpandoObject();
+ ctx.VirtualPath = virtualPath;
+ ctx.TextWriter = writer;
+ ctx.StartPosition = startPosition;
+ ctx.Length = length;
+ ctx.IsLiteral = isLiteral;
+ return new PageExecutionContextAdapter(ctx);
+ }
+ }
+}
diff --git a/test/System.Web.WebPages.Test/Mvc/HttpAntiForgeryExceptionTest.cs b/test/System.Web.WebPages.Test/Mvc/HttpAntiForgeryExceptionTest.cs
new file mode 100644
index 00000000..9d28d216
--- /dev/null
+++ b/test/System.Web.WebPages.Test/Mvc/HttpAntiForgeryExceptionTest.cs
@@ -0,0 +1,64 @@
+using System.IO;
+using System.Runtime.Serialization.Formatters.Binary;
+using Xunit;
+
+namespace System.Web.Mvc.Test
+{
+ public class HttpAntiForgeryExceptionTest
+ {
+ [Fact]
+ public void ConstructorWithMessageAndInnerExceptionParameter()
+ {
+ // Arrange
+ Exception innerException = new Exception();
+
+ // Act
+ HttpAntiForgeryException ex = new HttpAntiForgeryException("the message", innerException);
+
+ // Assert
+ Assert.Equal("the message", ex.Message);
+ Assert.Equal(innerException, ex.InnerException);
+ }
+
+ [Fact]
+ public void ConstructorWithMessageParameter()
+ {
+ // Act
+ HttpAntiForgeryException ex = new HttpAntiForgeryException("the message");
+
+ // Assert
+ Assert.Equal("the message", ex.Message);
+ }
+
+ [Fact]
+ public void ConstructorWithoutParameters()
+ {
+ // Act & assert
+ Assert.Throws<HttpAntiForgeryException>(
+ delegate { throw new HttpAntiForgeryException(); });
+ }
+
+ [Fact]
+ public void TypeIsSerializable()
+ {
+ // If this ever fails with SerializationException : Unable to find assembly 'System.Web.Mvc, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35'
+ // (usually when the assembly version is incremented) you need to modify the App.config file in this test project to reference the new version.
+
+ // Arrange
+ MemoryStream ms = new MemoryStream();
+ BinaryFormatter formatter = new BinaryFormatter();
+ HttpAntiForgeryException ex = new HttpAntiForgeryException("the message", new Exception("inner exception"));
+
+ // Act
+ formatter.Serialize(ms, ex);
+ ms.Position = 0;
+ HttpAntiForgeryException deserialized = formatter.Deserialize(ms) as HttpAntiForgeryException;
+
+ // Assert
+ Assert.NotNull(deserialized);
+ Assert.Equal("the message", deserialized.Message);
+ Assert.NotNull(deserialized.InnerException);
+ Assert.Equal("inner exception", deserialized.InnerException.Message);
+ }
+ }
+}
diff --git a/test/System.Web.WebPages.Test/Mvc/TagBuilderTest.cs b/test/System.Web.WebPages.Test/Mvc/TagBuilderTest.cs
new file mode 100644
index 00000000..8685bcf3
--- /dev/null
+++ b/test/System.Web.WebPages.Test/Mvc/TagBuilderTest.cs
@@ -0,0 +1,416 @@
+using System.Collections.Generic;
+using System.Web.WebPages.Html;
+using System.Web.WebPages.TestUtils;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Mvc.Test
+{
+ public class TagBuilderTest
+ {
+ [Fact]
+ public void AddCssClassPrepends()
+ {
+ // Arrange
+ TagBuilder builder = new TagBuilder("SomeTag");
+ builder.MergeAttribute("class", "oldA");
+
+ // Act
+ builder.AddCssClass("newA");
+
+ // Assert
+ Assert.Equal("newA oldA", builder.Attributes["class"]);
+ }
+
+ [Fact]
+ public void AttributesProperty()
+ {
+ // Arrange
+ TagBuilder builder = new TagBuilder("SomeTag");
+
+ // Act
+ SortedDictionary<string, string> attributes = builder.Attributes as SortedDictionary<string, string>;
+
+ // Assert
+ Assert.NotNull(attributes);
+ Assert.Equal(StringComparer.Ordinal, attributes.Comparer);
+ }
+
+ [Fact]
+ public void ConstructorSetsTagNameProperty()
+ {
+ // Arrange
+ TagBuilder builder = new TagBuilder("SomeTag");
+
+ // Act
+ string tagName = builder.TagName;
+
+ // Assert
+ Assert.Equal("SomeTag", tagName);
+ }
+
+ [Fact]
+ public void ConstructorWithEmptyTagNameThrows()
+ {
+ Assert.ThrowsArgumentNullOrEmptyString(
+ delegate { new TagBuilder(String.Empty); }, "tagName");
+ }
+
+ [Fact]
+ public void ConstructorWithNullTagNameThrows()
+ {
+ Assert.ThrowsArgumentNullOrEmptyString(
+ delegate { new TagBuilder(null /* tagName */); }, "tagName");
+ }
+
+ [Fact]
+ public void CreateSanitizedIdThrowsIfInvalidCharReplacementIsNull()
+ {
+ // Act & Assert
+ Assert.ThrowsArgumentNull(
+ () => TagBuilder.CreateSanitizedId("tagId", null),
+ "invalidCharReplacement");
+ }
+
+ [Fact]
+ public void CreateSanitizedIdDefaultsToHtmlHelperIdAttributeDotReplacement()
+ {
+ // Arrange
+ String defaultReplacementChar = HtmlHelper.IdAttributeDotReplacement;
+
+ // Act
+ string sanitizedId = TagBuilder.CreateSanitizedId("Hello world");
+
+ // Assert
+ Assert.Equal("Hello" + defaultReplacementChar + "world", sanitizedId);
+ }
+
+ [Fact]
+ public void CreateSanitizedId_ReturnsNullIfOriginalIdBeginsWithNonLetter()
+ {
+ // Act
+ string retVal = TagBuilder.CreateSanitizedId("_DoesNotBeginWithALetter", "!REPL!");
+
+ // Assert
+ Assert.Null(retVal);
+ }
+
+ [Fact]
+ public void CreateSanitizedId_ReturnsNullIfOriginalIdIsEmpty()
+ {
+ // Act
+ string retVal = TagBuilder.CreateSanitizedId("", "!REPL!");
+
+ // Assert
+ Assert.Null(retVal);
+ }
+
+ [Fact]
+ public void CreateSanitizedId_ReturnsNullIfOriginalIdIsNull()
+ {
+ // Act
+ string retVal = TagBuilder.CreateSanitizedId(null, "!REPL!");
+
+ // Assert
+ Assert.Null(retVal);
+ }
+
+ [Fact]
+ public void CreateSanitizedId_ReturnsSanitizedId()
+ {
+ // Arrange
+ string expected = "ABCXYZabcxyz012789!REPL!!REPL!!REPL!!REPL!!REPL!!REPL!!REPL!!REPL!!REPL!!REPL!-!REPL!_!REPL!!REPL!:";
+
+ // Act
+ string retVal = TagBuilder.CreateSanitizedId("ABCXYZabcxyz012789!@#$%^&*()-=_+.:", "!REPL!");
+
+ // Assert
+ Assert.Equal(expected, retVal);
+ }
+
+ [Fact]
+ public void GenerateId_AddsSanitizedId()
+ {
+ // Arrange
+ TagBuilder builder = new TagBuilder("div");
+ builder.IdAttributeDotReplacement = "x";
+
+ // Act
+ builder.GenerateId("Hello, world.");
+
+ // Assert
+ Assert.Equal("Helloxxworldx", builder.Attributes["id"]);
+ }
+
+ [Fact]
+ public void GenerateId_DoesNotAddIdIfIdAlreadyExists()
+ {
+ // Arrange
+ TagBuilder builder = new TagBuilder("div");
+ builder.GenerateId("old");
+
+ // Act
+ builder.GenerateId("new");
+
+ // Assert
+ Assert.Equal("old", builder.Attributes["id"]);
+ }
+
+ [Fact]
+ public void GenerateId_DoesNotAddIdIfSanitizationReturnsNull()
+ {
+ // Arrange
+ TagBuilder builder = new TagBuilder("div");
+
+ // Act
+ builder.GenerateId("");
+
+ // Assert
+ Assert.False(builder.Attributes.ContainsKey("id"));
+ }
+
+ [Fact]
+ public void InnerHtmlProperty()
+ {
+ // Arrange
+ TagBuilder builder = new TagBuilder("SomeTag");
+
+ // Act & Assert
+ Assert.Equal(String.Empty, builder.InnerHtml);
+ builder.InnerHtml = "foo";
+ Assert.Equal("foo", builder.InnerHtml);
+ builder.InnerHtml = null;
+ Assert.Equal(String.Empty, builder.InnerHtml);
+ }
+
+ [Fact]
+ public void MergeAttributeDoesNotOverwriteExistingValuesByDefault()
+ {
+ // Arrange
+ TagBuilder builder = new TagBuilder("SomeTag");
+ builder.MergeAttribute("a", "oldA");
+
+ // Act
+ builder.MergeAttribute("a", "newA");
+
+ // Assert
+ Assert.Equal("oldA", builder.Attributes["a"]);
+ }
+
+ [Fact]
+ public void MergeAttributeOverwritesExistingValueIfAsked()
+ {
+ // Arrange
+ TagBuilder builder = new TagBuilder("SomeTag");
+ builder.MergeAttribute("a", "oldA");
+
+ // Act
+ builder.MergeAttribute("a", "newA", true);
+
+ // Assert
+ Assert.Equal("newA", builder.Attributes["a"]);
+ }
+
+ [Fact]
+ public void MergeAttributeWithEmptyKeyThrows()
+ {
+ // Arrange
+ TagBuilder builder = new TagBuilder("SomeTag");
+
+ // Act & Assert
+ Assert.ThrowsArgumentNullOrEmptyString(
+ delegate { builder.MergeAttribute(String.Empty, "value"); }, "key");
+ }
+
+ [Fact]
+ public void MergeAttributeWithNullKeyThrows()
+ {
+ // Arrange
+ TagBuilder builder = new TagBuilder("SomeTag");
+
+ // Act & Assert
+ Assert.ThrowsArgumentNullOrEmptyString(
+ delegate { builder.MergeAttribute(null, "value"); }, "key");
+ }
+
+ [Fact]
+ public void MergeAttributesDoesNotOverwriteExistingValuesByDefault()
+ {
+ // Arrange
+ TagBuilder builder = new TagBuilder("SomeTag");
+ builder.Attributes["a"] = "oldA";
+
+ Dictionary<string, string> newAttrs = new Dictionary<string, string>
+ {
+ { "a", "newA" },
+ { "b", "newB" }
+ };
+
+ // Act
+ builder.MergeAttributes(newAttrs);
+
+ // Assert
+ Assert.Equal(2, builder.Attributes.Count);
+ Assert.Equal("oldA", builder.Attributes["a"]);
+ Assert.Equal("newB", builder.Attributes["b"]);
+ }
+
+ [Fact]
+ public void MergeAttributesOverwritesExistingValueIfAsked()
+ {
+ // Arrange
+ TagBuilder builder = new TagBuilder("SomeTag");
+ builder.Attributes["a"] = "oldA";
+
+ Dictionary<string, string> newAttrs = new Dictionary<string, string>
+ {
+ { "a", "newA" },
+ { "b", "newB" }
+ };
+
+ // Act
+ builder.MergeAttributes(newAttrs, true);
+
+ // Assert
+ Assert.Equal(2, builder.Attributes.Count);
+ Assert.Equal("newA", builder.Attributes["a"]);
+ Assert.Equal("newB", builder.Attributes["b"]);
+ }
+
+ [Fact]
+ public void MergeAttributesWithNullAttributesDoesNothing()
+ {
+ // Arrange
+ TagBuilder builder = new TagBuilder("SomeTag");
+
+ // Act
+ builder.MergeAttributes<string, string>(null);
+
+ // Assert
+ Assert.Equal(0, builder.Attributes.Count);
+ }
+
+ [Fact]
+ public void SetInnerTextEncodes()
+ {
+ // Arrange
+ TagBuilder builder = new TagBuilder("SomeTag");
+
+ // Act
+ builder.SetInnerText("<>");
+
+ // Assert
+ Assert.Equal("&lt;&gt;", builder.InnerHtml);
+ }
+
+ [Fact]
+ public void ToStringDefaultsToNormal()
+ {
+ // Arrange
+ TagBuilder builder = new TagBuilder("SomeTag")
+ {
+ InnerHtml = "<x&y>"
+ };
+ builder.MergeAttributes(GetAttributesDictionary());
+
+ // Act
+ string output = builder.ToString();
+
+ // Assert
+ Assert.Equal(@"<SomeTag a=""Foo"" b=""Bar&amp;Baz"" c=""&lt;&quot;Quux&quot;>""><x&y></SomeTag>", output);
+ }
+
+ [Fact]
+ public void ToStringDoesNotOutputEmptyIdTags()
+ {
+ // Arrange
+ TagBuilder builder = new TagBuilder("SomeTag");
+ builder.Attributes["foo"] = "fooValue";
+ builder.Attributes["bar"] = "barValue";
+ builder.Attributes["id"] = "";
+
+ // Act
+ string output = builder.ToString(TagRenderMode.SelfClosing);
+
+ Assert.Equal(@"<SomeTag bar=""barValue"" foo=""fooValue"" />", output);
+ }
+
+ [Fact]
+ public void ToStringEndTag()
+ {
+ // Arrange
+ TagBuilder builder = new TagBuilder("SomeTag")
+ {
+ InnerHtml = "<x&y>"
+ };
+ builder.MergeAttributes(GetAttributesDictionary());
+
+ // Act
+ string output = builder.ToString(TagRenderMode.EndTag);
+
+ // Assert
+ Assert.Equal(@"</SomeTag>", output);
+ }
+
+ [Fact]
+ public void ToStringNormal()
+ {
+ // Arrange
+ TagBuilder builder = new TagBuilder("SomeTag")
+ {
+ InnerHtml = "<x&y>"
+ };
+ builder.MergeAttributes(GetAttributesDictionary());
+
+ // Act
+ string output = builder.ToString(TagRenderMode.Normal);
+
+ // Assert
+ Assert.Equal(@"<SomeTag a=""Foo"" b=""Bar&amp;Baz"" c=""&lt;&quot;Quux&quot;>""><x&y></SomeTag>", output);
+ }
+
+ [Fact]
+ public void ToStringSelfClosing()
+ {
+ // Arrange
+ TagBuilder builder = new TagBuilder("SomeTag")
+ {
+ InnerHtml = "<x&y>"
+ };
+ builder.MergeAttributes(GetAttributesDictionary());
+
+ // Act
+ string output = builder.ToString(TagRenderMode.SelfClosing);
+
+ // Assert
+ Assert.Equal(@"<SomeTag a=""Foo"" b=""Bar&amp;Baz"" c=""&lt;&quot;Quux&quot;>"" />", output);
+ }
+
+ [Fact]
+ public void ToStringStartTag()
+ {
+ // Arrange
+ TagBuilder builder = new TagBuilder("SomeTag")
+ {
+ InnerHtml = "<x&y>"
+ };
+ builder.MergeAttributes(GetAttributesDictionary());
+
+ // Act
+ string output = builder.ToString(TagRenderMode.StartTag);
+
+ // Assert
+ Assert.Equal(@"<SomeTag a=""Foo"" b=""Bar&amp;Baz"" c=""&lt;&quot;Quux&quot;>"">", output);
+ }
+
+ private static IDictionary<string, string> GetAttributesDictionary()
+ {
+ return new SortedDictionary<string, string>
+ {
+ { "a", "Foo" },
+ { "b", "Bar&Baz" },
+ { "c", @"<""Quux"">" }
+ };
+ }
+ }
+}
diff --git a/test/System.Web.WebPages.Test/PreApplicationStartCodeTest.cs b/test/System.Web.WebPages.Test/PreApplicationStartCodeTest.cs
new file mode 100644
index 00000000..bca495a0
--- /dev/null
+++ b/test/System.Web.WebPages.Test/PreApplicationStartCodeTest.cs
@@ -0,0 +1,38 @@
+using System.Reflection;
+using System.Web.Routing;
+using System.Web.Security;
+using System.Web.UI;
+using System.Web.WebPages.TestUtils;
+using Xunit;
+
+namespace System.Web.WebPages.Test
+{
+ public class PreApplicationStartCodeTest
+ {
+ [Fact]
+ public void StartTest()
+ {
+ AppDomainUtils.RunInSeparateAppDomain(() =>
+ {
+ AppDomainUtils.SetPreAppStartStage();
+ PreApplicationStartCode.Start();
+ // Call a second time to ensure multiple calls do not cause issues
+ PreApplicationStartCode.Start();
+
+ Assert.False(RouteTable.Routes.RouteExistingFiles, "We should not be setting RouteExistingFiles");
+ Assert.Empty(RouteTable.Routes);
+
+ Assert.False(PageParser.EnableLongStringsAsResources);
+
+ string formsAuthLoginUrl = (string)typeof(FormsAuthentication).GetField("_LoginUrl", BindingFlags.Static | BindingFlags.NonPublic).GetValue(null);
+ Assert.Null(formsAuthLoginUrl);
+ });
+ }
+
+ [Fact]
+ public void TestPreAppStartClass()
+ {
+ PreAppStartTestHelper.TestPreAppStartClass(typeof(PreApplicationStartCode));
+ }
+ }
+}
diff --git a/test/System.Web.WebPages.Test/Properties/AssemblyInfo.cs b/test/System.Web.WebPages.Test/Properties/AssemblyInfo.cs
new file mode 100644
index 00000000..72db8000
--- /dev/null
+++ b/test/System.Web.WebPages.Test/Properties/AssemblyInfo.cs
@@ -0,0 +1,34 @@
+using System.Reflection;
+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("Microsoft.WebPages.Test")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("MSIT")]
+[assembly: AssemblyProduct("Microsoft.WebPages.Test")]
+[assembly: AssemblyCopyright("Copyright © MSIT 2010")]
+[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)]
+
+// 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/test/System.Web.WebPages.Test/ScopeStorage/AspNetRequestScopeStorageProviderTest.cs b/test/System.Web.WebPages.Test/ScopeStorage/AspNetRequestScopeStorageProviderTest.cs
new file mode 100644
index 00000000..75ca789e
--- /dev/null
+++ b/test/System.Web.WebPages.Test/ScopeStorage/AspNetRequestScopeStorageProviderTest.cs
@@ -0,0 +1,93 @@
+using System.Collections.Generic;
+using System.Web.WebPages.Scope;
+using Moq;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.WebPages.Test
+{
+ public class AspNetRequestStorageProvider
+ {
+ [Fact]
+ public void AspNetStorageProviderReturnsApplicationStateBeforeAppStart()
+ {
+ // Arrange
+ var provider = GetProvider(() => false);
+
+ // Act and Assert
+ Assert.NotNull(provider.ApplicationScope);
+ Assert.NotNull(provider.GlobalScope);
+ Assert.Equal(provider.ApplicationScope, provider.GlobalScope);
+ }
+
+ [Fact]
+ public void AspNetStorageProviderThrowsWhenAccessingRequestScopeBeforeAppStart()
+ {
+ // Arrange
+ var provider = GetProvider(() => false);
+
+ // Act and Assert
+ Assert.Throws<InvalidOperationException>(
+ () => { var x = provider.RequestScope; },
+ "RequestScope cannot be created when _AppStart is executing.");
+ }
+
+ [Fact]
+ public void AspNetStorageProviderThrowsWhenAssigningScopeBeforeAppStart()
+ {
+ // Arrange
+ var provider = GetProvider(() => false);
+
+ // Act and Assert
+ Assert.Throws<InvalidOperationException>(
+ () => { provider.CurrentScope = new ScopeStorageDictionary(); },
+ "Storage scopes cannot be created when _AppStart is executing.");
+ }
+
+ [Fact]
+ public void AspNetStorageProviderReturnsRequestScopeAfterAppStart()
+ {
+ // Arrange
+ var provider = GetProvider();
+
+ // Act and Assert
+ Assert.NotNull(provider.RequestScope);
+ Assert.Equal(provider.RequestScope, provider.CurrentScope);
+ }
+
+ [Fact]
+ public void AspNetStorageRetrievesRequestScopeAfterSettingAnonymousScopes()
+ {
+ // Arrange
+ var provider = GetProvider();
+
+ // Act
+ var requestScope = provider.RequestScope;
+
+ var Scope = new ScopeStorageDictionary();
+ provider.CurrentScope = Scope;
+
+ Assert.Equal(provider.CurrentScope, Scope);
+ Assert.Equal(provider.RequestScope, requestScope);
+ }
+
+ [Fact]
+ public void AspNetStorageUsesApplicationScopeAsGlobalScope()
+ {
+ // Arrange
+ var provider = GetProvider();
+
+ // Act and Assert
+ Assert.Equal(provider.GlobalScope, provider.ApplicationScope);
+ }
+
+ private AspNetRequestScopeStorageProvider GetProvider(Func<bool> appStartExecuted = null)
+ {
+ Mock<HttpContextBase> context = new Mock<HttpContextBase>();
+ context.Setup(c => c.Items).Returns(new Dictionary<object, object>());
+ appStartExecuted = appStartExecuted ?? (() => true);
+
+ return new AspNetRequestScopeStorageProvider(context.Object, appStartExecuted);
+ }
+ }
+}
diff --git a/test/System.Web.WebPages.Test/ScopeStorage/ScopeStorageDictionaryTest.cs b/test/System.Web.WebPages.Test/ScopeStorage/ScopeStorageDictionaryTest.cs
new file mode 100644
index 00000000..5f832d07
--- /dev/null
+++ b/test/System.Web.WebPages.Test/ScopeStorage/ScopeStorageDictionaryTest.cs
@@ -0,0 +1,170 @@
+using System.Collections.Generic;
+using System.Web.WebPages.Scope;
+using Xunit;
+
+namespace System.Web.WebPages.Test
+{
+ public class ScopeStorageDictionaryTest
+ {
+ [Fact]
+ public void ScopeStorageDictionaryLooksUpLocalValuesFirst()
+ {
+ // Arrange
+ var stateStorage = GetChainedStorageStateDictionary();
+
+ // Act and Assert
+ Assert.Equal(stateStorage["f"], "f2");
+ }
+
+ [Fact]
+ public void ScopeStorageDictionaryOverridesParentValuesWithLocalValues()
+ {
+ // Arrange
+ var stateStorage = GetChainedStorageStateDictionary();
+
+ // Act and Assert
+ Assert.Equal(stateStorage["a"], "a2");
+ Assert.Equal(stateStorage["d"], "d2");
+ }
+
+ [Fact]
+ public void ScopeStorageDictionaryLooksUpParentValuesWhenNotFoundLocally()
+ {
+ // Arrange
+ var stateStorage = GetChainedStorageStateDictionary();
+
+ // Act and Assert
+ Assert.Equal(stateStorage["c"], "c0");
+ Assert.Equal(stateStorage["b"], "b1");
+ }
+
+ [Fact]
+ public void ScopeStorageDictionaryTreatsNullAsOrdinaryValues()
+ {
+ // Arrange
+ var stateStorage = GetChainedStorageStateDictionary();
+ stateStorage["b"] = null;
+
+ // Act and Assert
+ Assert.Null(stateStorage["b"]);
+ }
+
+ [Fact]
+ public void ContainsKeyReturnsTrueIfItContainsKey()
+ {
+ // Arrange
+ var scopeStorage = GetChainedStorageStateDictionary();
+
+ // Act and Assert
+ Assert.True(scopeStorage.ContainsKey("f"));
+ }
+
+ [Fact]
+ public void ContainsKeyReturnsTrueIfBaseContainsKey()
+ {
+ // Arrange
+ var scopeStorage = GetChainedStorageStateDictionary();
+
+ // Act and Assert
+ Assert.True(scopeStorage.ContainsKey("e"));
+ }
+
+ [Fact]
+ public void ContainsKeyReturnsFalseIfItDoesNotContainKeyAndBaseIsNull()
+ {
+ // Arrange
+ var scopeStorage = new ScopeStorageDictionary() { { "foo", "bar" } };
+
+ // Act and Assert
+ Assert.False(scopeStorage.ContainsKey("baz"));
+ }
+
+ [Fact]
+ public void CountReturnsCountFromCurrentAndBaseScope()
+ {
+ // Arrange
+ var scopeStorage = GetChainedStorageStateDictionary();
+
+ // Act and Assert
+ Assert.Equal(6, scopeStorage.Count);
+ }
+
+ [Fact]
+ public void ScopeStorageDictionaryGetsValuesFromCurrentAndBaseScope()
+ {
+ // Arrange
+ var scopeStorage = GetChainedStorageStateDictionary();
+
+ // Act and Assert
+ Assert.Equal(scopeStorage["a"], "a2");
+ Assert.Equal(scopeStorage["b"], "b1");
+ Assert.Equal(scopeStorage["c"], "c0");
+ Assert.Equal(scopeStorage["d"], "d2");
+ Assert.Equal(scopeStorage["e"], "e1");
+ Assert.Equal(scopeStorage["f"], "f2");
+ }
+
+ [Fact]
+ public void ClearRemovesAllItemsFromCurrentScope()
+ {
+ // Arrange
+ var dictionary = new ScopeStorageDictionary { { "foo", "bar" }, { "foo2", "bar2" } };
+
+ // Act
+ dictionary.Clear();
+
+ // Assert
+ Assert.Equal(0, dictionary.Count);
+ }
+
+ [Fact]
+ public void ScopeStorageDictionaryIsNotReadOnly()
+ {
+ // Arrange
+ var dictionary = new ScopeStorageDictionary();
+
+ // Act and Assert
+ Assert.False(dictionary.IsReadOnly);
+ }
+
+ [Fact]
+ public void CopyToCopiesItemsToArrayAtSpecifiedIndex()
+ {
+ // Arrange
+ var dictionary = GetChainedStorageStateDictionary();
+ var array = new KeyValuePair<object, object>[8];
+
+ // Act
+ dictionary.CopyTo(array, 2);
+
+ // Assert
+ Assert.Equal(array[2].Key, "a");
+ Assert.Equal(array[2].Value, "a2");
+ Assert.Equal(array[4].Key, "f");
+ Assert.Equal(array[4].Value, "f2");
+ Assert.Equal(array[7].Key, "c");
+ Assert.Equal(array[7].Value, "c0");
+ }
+
+ private ScopeStorageDictionary GetChainedStorageStateDictionary()
+ {
+ var root = new ScopeStorageDictionary();
+ root["a"] = "a0";
+ root["b"] = "b0";
+ root["c"] = "c0";
+
+ var firstGen = new ScopeStorageDictionary(baseScope: root);
+ firstGen["a"] = "a1";
+ firstGen["b"] = "b1";
+ firstGen["d"] = "d1";
+ firstGen["e"] = "e1";
+
+ var secondGen = new ScopeStorageDictionary(baseScope: firstGen);
+ secondGen["a"] = "a2";
+ secondGen["d"] = "d2";
+ secondGen["f"] = "f2";
+
+ return secondGen;
+ }
+ }
+}
diff --git a/test/System.Web.WebPages.Test/ScopeStorage/ScopeStorageKeyComparerTest.cs b/test/System.Web.WebPages.Test/ScopeStorage/ScopeStorageKeyComparerTest.cs
new file mode 100644
index 00000000..a30ae2be
--- /dev/null
+++ b/test/System.Web.WebPages.Test/ScopeStorage/ScopeStorageKeyComparerTest.cs
@@ -0,0 +1,48 @@
+using System.Collections.Generic;
+using System.Web.WebPages.Scope;
+using Xunit;
+
+namespace System.Web.WebPages.Test
+{
+ public class ScopeStorageKeyComparerTest
+ {
+ [Fact]
+ public void ScopeStorageComparerPerformsCaseInsensitiveOrdinalComparisonForStrings()
+ {
+ // Arrange
+ var dictionary = new Dictionary<object, object>(ScopeStorageComparer.Instance) { { "foo", "bar" } };
+
+ // Act and Assert
+ Assert.Equal(dictionary["foo"], "bar");
+ Assert.Equal(dictionary["foo"], dictionary["FOo"]);
+ }
+
+ [Fact]
+ public void ScopeStorageComparerPerformsRegularComparisonForOtherTypes()
+ {
+ // Arrange
+ var stateStorage = new Dictionary<object, object> { { 4, "4-value" }, { new Person { ID = 10 }, "person-value" } };
+
+ // Act and Assert
+ Assert.Equal(stateStorage[4], "4-value");
+ Assert.Equal(stateStorage[(int)8 / 2], stateStorage[4]);
+ Assert.Equal(stateStorage[new Person { ID = 10 }], "person-value");
+ }
+
+ private class Person
+ {
+ public int ID { get; set; }
+
+ public override bool Equals(object o)
+ {
+ var other = o as Person;
+ return (other != null) && (other.ID == ID);
+ }
+
+ public override int GetHashCode()
+ {
+ return ID;
+ }
+ }
+ }
+}
diff --git a/test/System.Web.WebPages.Test/ScopeStorage/WebConfigScopeStorageTest.cs b/test/System.Web.WebPages.Test/ScopeStorage/WebConfigScopeStorageTest.cs
new file mode 100644
index 00000000..e99002fa
--- /dev/null
+++ b/test/System.Web.WebPages.Test/ScopeStorage/WebConfigScopeStorageTest.cs
@@ -0,0 +1,78 @@
+using System.Collections.Generic;
+using System.Collections.Specialized;
+using System.Linq;
+using System.Web.WebPages.Scope;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.WebPages.Test
+{
+ public class WebConfigScopeStorageTest
+ {
+ [Fact]
+ public void WebConfigScopeStorageReturnsConfigValue()
+ {
+ // Arrange
+ var stateStorage = GetWebConfigScopeStorage();
+
+ // Assert
+ Assert.Equal(stateStorage["foo1"], "bar1");
+ Assert.Equal(stateStorage["foo2"], "bar2");
+ }
+
+ [Fact]
+ public void WebConfigScopeStoragePerformsCaseInsensitiveKeyCompares()
+ {
+ // Arrange
+ var stateStorage = GetWebConfigScopeStorage();
+
+ // Assert
+ Assert.Equal(stateStorage["FOO1"], "bar1");
+ Assert.Equal(stateStorage["FoO2"], "bar2");
+ }
+
+ [Fact]
+ public void WebConfigScopeStorageThrowsWhenWriting()
+ {
+ // Arrange
+ var stateStorage = GetWebConfigScopeStorage();
+
+ // Act and Assert
+ Assert.Throws<NotSupportedException>(() => stateStorage["foo"] = "some value", "Storage scope is read only.");
+ Assert.Throws<NotSupportedException>(() => stateStorage.Add("foo", "value"), "Storage scope is read only.");
+ Assert.Throws<NotSupportedException>(() => stateStorage.Remove("foo"), "Storage scope is read only.");
+ Assert.Throws<NotSupportedException>(() => stateStorage.Clear(), "Storage scope is read only.");
+ Assert.Throws<NotSupportedException>(() => stateStorage.Remove(new KeyValuePair<object, object>("foo", "bar")), "Storage scope is read only.");
+ }
+
+ [Fact]
+ public void WebConfigStateAllowsEnumeratingOverConfigItems()
+ {
+ // Arrange
+ var dictionary = new Dictionary<string, string> { { "a", "b" }, { "c", "d" }, { "x12", "y34" } };
+ var stateStorage = GetWebConfigScopeStorage(dictionary);
+
+ // Act and Assert
+ Assert.True(dictionary.All(item => item.Value == stateStorage[item.Key] as string));
+ }
+
+ private WebConfigScopeDictionary GetWebConfigScopeStorage(IDictionary<string, string> values = null)
+ {
+ NameValueCollection collection = new NameValueCollection();
+ if (values == null)
+ {
+ collection.Add("foo1", "bar1");
+ collection.Add("foo2", "bar2");
+ }
+ else
+ {
+ foreach (var item in values)
+ {
+ collection.Add(item.Key, item.Value);
+ }
+ }
+
+ return new WebConfigScopeDictionary(collection);
+ }
+ }
+}
diff --git a/test/System.Web.WebPages.Test/System.Web.WebPages.Test.csproj b/test/System.Web.WebPages.Test/System.Web.WebPages.Test.csproj
new file mode 100644
index 00000000..6ad4e51d
--- /dev/null
+++ b/test/System.Web.WebPages.Test/System.Web.WebPages.Test.csproj
@@ -0,0 +1,162 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory),Runtime.sln))\tools\WebStack.settings.targets" />
+ <PropertyGroup>
+ <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+ <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+ <ProductVersion>9.0.30729</ProductVersion>
+ <SchemaVersion>2.0</SchemaVersion>
+ <ProjectGuid>{0F4870DB-A799-4DBA-99DF-0D74BB52FEC2}</ProjectGuid>
+ <OutputType>Library</OutputType>
+ <AppDesignerFolder>Properties</AppDesignerFolder>
+ <RootNamespace>System.Web.WebPages.Test</RootNamespace>
+ <AssemblyName>System.Web.WebPages.Test</AssemblyName>
+ <FileAlignment>512</FileAlignment>
+ <ProjectTypeGuids>{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
+ <ErrorReport>prompt</ErrorReport>
+ <WarningLevel>4</WarningLevel>
+ <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
+ <DebugSymbols>true</DebugSymbols>
+ <DebugType>full</DebugType>
+ <Optimize>false</Optimize>
+ <OutputPath>$(WebStackRootPath)\bin\Debug\Test\</OutputPath>
+ <DefineConstants>TRACE;DEBUG</DefineConstants>
+ <CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
+ <DebugType>pdbonly</DebugType>
+ <Optimize>true</Optimize>
+ <OutputPath>$(WebStackRootPath)\bin\Release\Test\</OutputPath>
+ <DefineConstants>TRACE</DefineConstants>
+ <CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)' == 'CodeCoverage' ">
+ <DebugSymbols>true</DebugSymbols>
+ <DebugType>full</DebugType>
+ <Optimize>false</Optimize>
+ <OutputPath>$(WebStackRootPath)\bin\CodeCoverage\Test\</OutputPath>
+ <DefineConstants>TRACE;DEBUG</DefineConstants>
+ <CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
+ </PropertyGroup>
+ <ItemGroup>
+ <Reference Include="Microsoft.CSharp" />
+ <Reference Include="Moq, Version=4.0.10827.0, Culture=neutral, PublicKeyToken=69f491c39445e920, processorArchitecture=MSIL">
+ <HintPath>..\..\packages\Moq.4.0.10827\lib\NET40\Moq.dll</HintPath>
+ </Reference>
+ <Reference Include="System" />
+ <Reference Include="System.ComponentModel.DataAnnotations" />
+ <Reference Include="System.Core">
+ <RequiredTargetFramework>3.5</RequiredTargetFramework>
+ </Reference>
+ <Reference Include="System.Data.Linq" />
+ <Reference Include="System.Web" />
+ <Reference Include="xunit">
+ <HintPath>..\..\packages\xunit.1.9.0.1566\lib\xunit.dll</HintPath>
+ </Reference>
+ <Reference Include="xunit.extensions">
+ <HintPath>..\..\packages\xunit.extensions.1.9.0.1566\lib\xunit.extensions.dll</HintPath>
+ </Reference>
+ </ItemGroup>
+ <ItemGroup>
+ <Compile Include="ApplicationParts\ApplicationPartTest.cs" />
+ <Compile Include="ApplicationParts\ApplicationPartRegistryTest.cs" />
+ <Compile Include="ApplicationParts\MimeMappingTest.cs" />
+ <Compile Include="ApplicationParts\ResourceHandlerTest.cs" />
+ <Compile Include="ApplicationParts\TestResourceAssembly.cs" />
+ <Compile Include="Utils\SessionStateUtilTest.cs" />
+ <Compile Include="WebPage\BrowserHelpersTest.cs" />
+ <Compile Include="WebPage\BrowserOverrideStoresTest.cs" />
+ <Compile Include="WebPage\CookieBrowserOverrideStoreTest.cs" />
+ <Compile Include="WebPage\DefaultDisplayModeTest.cs" />
+ <Compile Include="WebPage\DisplayInfoTest.cs" />
+ <Compile Include="WebPage\DisplayModeProviderTest.cs" />
+ <Compile Include="Extensions\HttpContextExtensionsTest.cs" />
+ <Compile Include="Extensions\HttpRequestExtensionsTest.cs" />
+ <Compile Include="Extensions\StringExtensionsTest.cs" />
+ <Compile Include="Extensions\HttpResponseExtensionsTest.cs" />
+ <Compile Include="Helpers\AntiForgeryDataSerializerTest.cs" />
+ <Compile Include="Helpers\AntiForgeryDataTest.cs" />
+ <Compile Include="Helpers\AntiForgeryTest.cs" />
+ <Compile Include="Helpers\AntiForgeryWorkerTest.cs" />
+ <Compile Include="Helpers\UnvalidatedRequestValuesTest.cs" />
+ <Compile Include="Html\CheckBoxTest.cs" />
+ <Compile Include="Html\HtmlHelperFactory.cs" />
+ <Compile Include="Html\HtmlHelperTest.cs" />
+ <Compile Include="Html\InputHelperTest.cs" />
+ <Compile Include="Html\RadioButtonTest.cs" />
+ <Compile Include="Html\SelectHelperTest.cs">
+ <SubType>Code</SubType>
+ </Compile>
+ <Compile Include="Html\TextAreaHelperTest.cs">
+ <SubType>Code</SubType>
+ </Compile>
+ <Compile Include="Html\ValidationHelperTest.cs" />
+ <Compile Include="Instrumentation\InstrumentationServiceTest.cs" />
+ <Compile Include="Mvc\HttpAntiForgeryExceptionTest.cs" />
+ <Compile Include="Mvc\TagBuilderTest.cs" />
+ <Compile Include="PreApplicationStartCodeTest.cs" />
+ <Compile Include="ScopeStorage\AspNetRequestScopeStorageProviderTest.cs" />
+ <Compile Include="ScopeStorage\ScopeStorageDictionaryTest.cs" />
+ <Compile Include="ScopeStorage\ScopeStorageKeyComparerTest.cs" />
+ <Compile Include="ScopeStorage\WebConfigScopeStorageTest.cs" />
+ <Compile Include="Utils\CultureUtilTest.cs" />
+ <Compile Include="Utils\PathUtilTest.cs" />
+ <Compile Include="Utils\TestObjectFactory.cs" />
+ <Compile Include="Utils\TypeHelperTest.cs" />
+ <Compile Include="Utils\UrlUtilTest.cs" />
+ <Compile Include="Validation\ValidationHelperTest.cs" />
+ <Compile Include="Validation\ValidatorTest.cs" />
+ <Compile Include="WebPage\ApplicationStartPageTest.cs" />
+ <Compile Include="WebPage\DynamicHttpApplicationStateTest.cs" />
+ <Compile Include="WebPage\DynamicPageDataDictionaryTest.cs" />
+ <Compile Include="WebPage\FileExistenceCacheTest.cs" />
+ <Compile Include="WebPage\RequestBrowserOverrideStoreTest.cs" />
+ <Compile Include="WebPage\RequestResourceTrackerTest.cs" />
+ <Compile Include="WebPage\TemplateStackTest.cs" />
+ <Compile Include="WebPage\BuildManagerWrapperTest.cs" />
+ <Compile Include="WebPage\VirtualPathFactoryExtensionsTest.cs" />
+ <Compile Include="WebPage\VirtualPathFactoryManagerTest.cs" />
+ <Compile Include="WebPage\WebPageContextTest.cs" />
+ <Compile Include="WebPage\WebPageExecutingBaseTest.cs" />
+ <Compile Include="WebPage\WebPageHttpModuleTest.cs" />
+ <Compile Include="WebPage\WebPageHttpHandlerTest.cs" />
+ <Compile Include="WebPage\UrlDataTest.cs" />
+ <Compile Include="WebPage\StartPageTest.cs" />
+ <Compile Include="WebPage\Utils.cs" />
+ <Compile Include="WebPage\PageDataDictionaryTest.cs" />
+ <Compile Include="Properties\AssemblyInfo.cs" />
+ <Compile Include="WebPage\WebPageRenderingBaseTest.cs" />
+ <Compile Include="WebPage\WebPageRouteTest.cs" />
+ <Compile Include="WebPage\RenderPageTest.cs" />
+ <Compile Include="WebPage\BuildManagerExceptionUtilTest.cs" />
+ <Compile Include="WebPage\LayoutTest.cs" />
+ <Compile Include="WebPage\WebPageTest.cs" />
+ </ItemGroup>
+ <ItemGroup>
+ <ProjectReference Include="..\..\src\System.Web.Razor\System.Web.Razor.csproj">
+ <Project>{8F18041B-9410-4C36-A9C5-067813DF5F31}</Project>
+ <Name>System.Web.Razor</Name>
+ </ProjectReference>
+ <ProjectReference Include="..\..\src\System.Web.WebPages\System.Web.WebPages.csproj">
+ <Project>{76EFA9C5-8D7E-4FDF-B710-E20F8B6B00D2}</Project>
+ <Name>System.Web.WebPages</Name>
+ </ProjectReference>
+ <ProjectReference Include="..\Microsoft.TestCommon\Microsoft.TestCommon.csproj">
+ <Project>{FCCC4CB7-BAF7-4A57-9F89-E5766FE536C0}</Project>
+ <Name>Microsoft.TestCommon</Name>
+ </ProjectReference>
+ </ItemGroup>
+ <ItemGroup>
+ <None Include="App.config" />
+ <None Include="packages.config" />
+ <None Include="TestFiles\Deployed\Bar" />
+ <None Include="TestFiles\Deployed\Bar.foohtml" />
+ </ItemGroup>
+ <ItemGroup>
+ <Content Include="TestFiles\Deployed\Bar.cshtml" />
+ </ItemGroup>
+ <ItemGroup />
+ <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
+</Project> \ No newline at end of file
diff --git a/test/System.Web.WebPages.Test/TestFiles/Deployed/Bar b/test/System.Web.WebPages.Test/TestFiles/Deployed/Bar
new file mode 100644
index 00000000..5f282702
--- /dev/null
+++ b/test/System.Web.WebPages.Test/TestFiles/Deployed/Bar
@@ -0,0 +1 @@
+ \ No newline at end of file
diff --git a/test/System.Web.WebPages.Test/TestFiles/Deployed/Bar.cshtml b/test/System.Web.WebPages.Test/TestFiles/Deployed/Bar.cshtml
new file mode 100644
index 00000000..5f282702
--- /dev/null
+++ b/test/System.Web.WebPages.Test/TestFiles/Deployed/Bar.cshtml
@@ -0,0 +1 @@
+ \ No newline at end of file
diff --git a/test/System.Web.WebPages.Test/TestFiles/Deployed/Bar.foohtml b/test/System.Web.WebPages.Test/TestFiles/Deployed/Bar.foohtml
new file mode 100644
index 00000000..5f282702
--- /dev/null
+++ b/test/System.Web.WebPages.Test/TestFiles/Deployed/Bar.foohtml
@@ -0,0 +1 @@
+ \ No newline at end of file
diff --git a/test/System.Web.WebPages.Test/Utils/CultureUtilTest.cs b/test/System.Web.WebPages.Test/Utils/CultureUtilTest.cs
new file mode 100644
index 00000000..628c0b21
--- /dev/null
+++ b/test/System.Web.WebPages.Test/Utils/CultureUtilTest.cs
@@ -0,0 +1,256 @@
+using System.Collections.Generic;
+using System.Globalization;
+using System.Linq;
+using System.Threading;
+using Moq;
+using Xunit;
+
+namespace System.Web.WebPages.Test
+{
+ public class CultureUtilTest
+ {
+ [Fact]
+ public void SetAutoCultureWithNoUserLanguagesDoesNothing()
+ {
+ // Arrange
+ var context = GetContextForSetCulture(null);
+ Thread thread = GetThread();
+ CultureInfo culture = thread.CurrentCulture;
+
+ // Act
+ CultureUtil.SetCulture(thread, context, "auto");
+
+ // Assert
+ Assert.Equal(culture, thread.CurrentCulture);
+ }
+
+ [Fact]
+ public void SetAutoUICultureWithNoUserLanguagesDoesNothing()
+ {
+ // Arrange
+ var context = GetContextForSetCulture(null);
+ Thread thread = GetThread();
+ CultureInfo culture = thread.CurrentUICulture;
+
+ // Act
+ CultureUtil.SetUICulture(thread, context, "auto");
+
+ // Assert
+ Assert.Equal(culture, thread.CurrentUICulture);
+ }
+
+ [Fact]
+ public void SetAutoCultureWithEmptyUserLanguagesDoesNothing()
+ {
+ // Arrange
+ var context = GetContextForSetCulture(Enumerable.Empty<string>());
+ Thread thread = GetThread();
+ CultureInfo culture = thread.CurrentCulture;
+
+ // Act
+ CultureUtil.SetCulture(thread, context, "auto");
+
+ // Assert
+ Assert.Equal(culture, thread.CurrentCulture);
+ }
+
+ [Fact]
+ public void SetAutoUICultureWithEmptyUserLanguagesDoesNothing()
+ {
+ // Arrange
+ var context = GetContextForSetCulture(Enumerable.Empty<string>());
+ Thread thread = GetThread();
+ CultureInfo culture = thread.CurrentUICulture;
+
+ // Act
+ CultureUtil.SetUICulture(thread, context, "auto");
+
+ // Assert
+ Assert.Equal(culture, thread.CurrentUICulture);
+ }
+
+ [Fact]
+ public void SetAutoCultureWithBlankUserLanguagesDoesNothing()
+ {
+ // Arrange
+ var context = GetContextForSetCulture(new[] { " " });
+ Thread thread = GetThread();
+ CultureInfo culture = thread.CurrentCulture;
+
+ // Act
+ CultureUtil.SetCulture(thread, context, "auto");
+
+ // Assert
+ Assert.Equal(culture, thread.CurrentCulture);
+ }
+
+ [Fact]
+ public void SetAutoUICultureWithBlankUserLanguagesDoesNothing()
+ {
+ // Arrange
+ var context = GetContextForSetCulture(new[] { " " });
+ Thread thread = GetThread();
+ CultureInfo culture = thread.CurrentUICulture;
+
+ // Act
+ CultureUtil.SetUICulture(thread, context, "auto");
+
+ // Assert
+ Assert.Equal(culture, thread.CurrentUICulture);
+ }
+
+ [Fact]
+ public void SetAutoCultureWithInvalidLanguageDoesNothing()
+ {
+ // Arrange
+ var context = GetContextForSetCulture(new[] { "aa-AA", "bb-BB", "cc-CC" });
+ Thread thread = GetThread();
+ CultureInfo culture = thread.CurrentCulture;
+
+ // Act
+ CultureUtil.SetCulture(thread, context, "auto");
+
+ // Assert
+ Assert.Equal(culture, thread.CurrentCulture);
+ }
+
+ [Fact]
+ public void SetAutoUICultureWithInvalidLanguageDoesNothing()
+ {
+ // Arrange
+ var context = GetContextForSetCulture(new[] { "aa-AA", "bb-BB", "cc-CC" });
+ Thread thread = GetThread();
+ CultureInfo culture = thread.CurrentUICulture;
+
+ // Act
+ CultureUtil.SetUICulture(thread, context, "auto");
+
+ // Assert
+ Assert.Equal(culture, thread.CurrentUICulture);
+ }
+
+ [Fact]
+ public void SetAutoCultureDetectsUserLanguageCulture()
+ {
+ // Arrange
+ var context = GetContextForSetCulture(new[] { "en-GB", "en-US", "ar-eg" });
+ Thread thread = GetThread();
+
+ // Act
+ CultureUtil.SetCulture(thread, context, "auto");
+
+ // Assert
+ Assert.Equal(CultureInfo.GetCultureInfo("en-GB"), thread.CurrentCulture);
+ Assert.Equal("05/01/1979", new DateTime(1979, 1, 5).ToString("d", thread.CurrentCulture));
+ }
+
+ [Fact]
+ public void SetAutoUICultureDetectsUserLanguageCulture()
+ {
+ // Arrange
+ var context = GetContextForSetCulture(new[] { "en-GB", "en-US", "ar-eg" });
+ Thread thread = GetThread();
+
+ // Act
+ CultureUtil.SetUICulture(thread, context, "auto");
+
+ // Assert
+ Assert.Equal(CultureInfo.GetCultureInfo("en-GB"), thread.CurrentUICulture);
+ Assert.Equal("05/01/1979", new DateTime(1979, 1, 5).ToString("d", thread.CurrentUICulture));
+ }
+
+ [Fact]
+ public void SetAutoCultureUserLanguageWithQParameterCulture()
+ {
+ // Arrange
+ var context = GetContextForSetCulture(new[] { "en-GB;q=0.3", "en-US", "ar-eg;q=0.5" });
+ Thread thread = GetThread();
+
+ // Act
+ CultureUtil.SetCulture(thread, context, "auto");
+
+ // Assert
+ Assert.Equal(CultureInfo.GetCultureInfo("en-GB"), thread.CurrentCulture);
+ Assert.Equal("05/01/1979", new DateTime(1979, 1, 5).ToString("d", thread.CurrentCulture));
+ }
+
+ [Fact]
+ public void SetAutoUICultureDetectsUserLanguageWithQParameterCulture()
+ {
+ // Arrange
+ var context = GetContextForSetCulture(new[] { "en-GB;q=0.3", "en-US", "ar-eg;q=0.5" });
+ Thread thread = GetThread();
+
+ // Act
+ CultureUtil.SetUICulture(thread, context, "auto");
+
+ // Assert
+ Assert.Equal(CultureInfo.GetCultureInfo("en-GB"), thread.CurrentUICulture);
+ Assert.Equal("05/01/1979", new DateTime(1979, 1, 5).ToString("d", thread.CurrentUICulture));
+ }
+
+ [Fact]
+ public void SetCultureWithInvalidCultureThrows()
+ {
+ // Arrange
+ var context = GetContextForSetCulture();
+ Thread thread = GetThread();
+
+ // Act and Assert
+ Assert.Throws<CultureNotFoundException>(() => CultureUtil.SetCulture(thread, context, "sans-culture"));
+ }
+
+ [Fact]
+ public void SetUICultureWithInvalidCultureThrows()
+ {
+ // Arrange
+ var context = GetContextForSetCulture();
+ Thread thread = GetThread();
+
+ // Act and Assert
+ Assert.Throws<CultureNotFoundException>(() => CultureUtil.SetUICulture(thread, context, "sans-culture"));
+ }
+
+ [Fact]
+ public void SetCultureWithValidCulture()
+ {
+ // Arrange
+ var context = GetContextForSetCulture();
+ Thread thread = GetThread();
+
+ // Act
+ CultureUtil.SetCulture(thread, context, "en-GB");
+
+ // Assert
+ Assert.Equal(CultureInfo.GetCultureInfo("en-GB"), thread.CurrentCulture);
+ Assert.Equal("05/01/1979", new DateTime(1979, 1, 5).ToString("d", thread.CurrentCulture));
+ }
+
+ [Fact]
+ public void SetUICultureWithValidCulture()
+ {
+ // Arrange
+ var context = GetContextForSetCulture();
+ Thread thread = GetThread();
+
+ // Act
+ CultureUtil.SetUICulture(thread, context, "en-GB");
+
+ // Assert
+ Assert.Equal(CultureInfo.GetCultureInfo("en-GB"), thread.CurrentUICulture);
+ Assert.Equal("05/01/1979", new DateTime(1979, 1, 5).ToString("d", thread.CurrentUICulture));
+ }
+
+ private static Thread GetThread()
+ {
+ return new Thread(() => { });
+ }
+
+ private static HttpContextBase GetContextForSetCulture(IEnumerable<string> userLanguages = null)
+ {
+ Mock<HttpContextBase> contextMock = new Mock<HttpContextBase>();
+ contextMock.Setup(context => context.Request.UserLanguages).Returns(userLanguages == null ? null : userLanguages.ToArray());
+ return contextMock.Object;
+ }
+ }
+}
diff --git a/test/System.Web.WebPages.Test/Utils/PathUtilTest.cs b/test/System.Web.WebPages.Test/Utils/PathUtilTest.cs
new file mode 100644
index 00000000..2911b748
--- /dev/null
+++ b/test/System.Web.WebPages.Test/Utils/PathUtilTest.cs
@@ -0,0 +1,177 @@
+using System.Linq;
+using System.Web.WebPages.TestUtils;
+using Microsoft.Internal.Web.Utils;
+using Xunit;
+
+namespace System.Web.WebPages.Test
+{
+ public class PathUtilTest
+ {
+ [Fact]
+ public void IsSimpleNameTest()
+ {
+ Assert.True(PathUtil.IsSimpleName("Test.cshtml"));
+ Assert.True(PathUtil.IsSimpleName("Test.Hello.cshtml"));
+ Assert.False(PathUtil.IsSimpleName("~/myapp/Test/Hello.cshtml"));
+ Assert.False(PathUtil.IsSimpleName("../Test/Hello.cshtml"));
+ Assert.False(PathUtil.IsSimpleName("../../Test/Hello.cshtml"));
+ Assert.False(PathUtil.IsSimpleName("/Test/Hello.cshtml"));
+ }
+
+ [Fact]
+ public void GetExtensionForNullPathsReturnsNull()
+ {
+ // Arrange
+ string path = null;
+
+ // Act
+ string extension = PathUtil.GetExtension(path);
+
+ // Assert
+ Assert.Null(extension);
+ }
+
+ [Fact]
+ public void GetExtensionForEmptyPathsReturnsEmptyString()
+ {
+ // Arrange
+ string path = String.Empty;
+
+ // Act
+ string extension = PathUtil.GetExtension(path);
+
+ // Assert
+ Assert.Equal(0, extension.Length);
+ }
+
+ [Fact]
+ public void GetExtensionReturnsEmptyStringForPathsThatDoNotContainExtension()
+ {
+ // Arrange
+ string[] paths = new[] { "SomePath", "SomePath/", "SomePath/MorePath", "SomePath/MorePath/" };
+
+ // Act
+ var extensions = paths.Select(PathUtil.GetExtension);
+
+ // Assert
+ Assert.True(extensions.All(ext => ext.Length == 0));
+ }
+
+ [Fact]
+ public void GetExtensionReturnsEmptyStringForPathsContainingPathInfo()
+ {
+ // Arrange
+ string[] paths = new[] { "SomePath.cshtml/", "SomePath.html/path/info" };
+
+ // Act
+ var extensions = paths.Select(PathUtil.GetExtension);
+
+ // Assert
+ Assert.True(extensions.All(ext => ext.Length == 0));
+ }
+
+ [Fact]
+ public void GetExtensionReturnsEmptyStringForPathsTerminatingWithADot()
+ {
+ // Arrange
+ string[] paths = new[] { "SomePath.", "SomeDirectory/SomePath/SomePath.", "SomeDirectory/SomePath.foo." };
+
+ // Act
+ var extensions = paths.Select(PathUtil.GetExtension);
+
+ // Assert
+ Assert.True(extensions.All(ext => ext.Length == 0));
+ }
+
+ [Fact]
+ public void GetExtensionReturnsExtensionsForPathsTerminatingInExtension()
+ {
+ // Arrange
+ string path1 = "SomePath.cshtml";
+ string path2 = "SomeDir/SomePath.txt";
+
+ // Act
+ string ext1 = PathUtil.GetExtension(path1);
+ string ext2 = PathUtil.GetExtension(path2);
+
+ // Assert
+ Assert.Equal(ext1, ".cshtml");
+ Assert.Equal(ext2, ".txt");
+ }
+
+ [Fact]
+ public void GetExtensionDoesNotThrowForPathsWithInvalidCharacters()
+ {
+ // Arrange
+ // Repro from test case in Bug 93828
+ string path = "Insights/110786998958803%7C2.d24wA6Y3MiT2w8p3OT4yTw__.3600.1289415600-708897727%7CRLN-t1w9bXtKWZ_11osz15Rk_jY";
+
+ // Act
+ string extension = PathUtil.GetExtension(path);
+
+ // Assert
+ Assert.Equal(".1289415600-708897727%7CRLN-t1w9bXtKWZ_11osz15Rk_jY", extension);
+ }
+
+ [Fact]
+ public void IsWithinAppRootNestedTest()
+ {
+ AppDomainUtils.RunInSeparateAppDomain(() =>
+ {
+ var root = "/subfolder1/website1";
+ using (Utils.CreateHttpRuntime(root))
+ {
+ Assert.True(PathUtil.IsWithinAppRoot(root, "~/"));
+ Assert.True(PathUtil.IsWithinAppRoot(root, "~/default.cshtml"));
+ Assert.True(PathUtil.IsWithinAppRoot(root, "~/test/default.cshtml"));
+ Assert.True(PathUtil.IsWithinAppRoot(root, "/subfolder1/website1"));
+ Assert.True(PathUtil.IsWithinAppRoot(root, "/subfolder1/website1/"));
+ Assert.True(PathUtil.IsWithinAppRoot(root, "/subfolder1/website1/default.cshtml"));
+ Assert.True(PathUtil.IsWithinAppRoot(root, "/subfolder1/website1/test/default.cshtml"));
+
+ Assert.False(PathUtil.IsWithinAppRoot(root, "/"));
+ Assert.False(PathUtil.IsWithinAppRoot(root, "/subfolder1"));
+ Assert.False(PathUtil.IsWithinAppRoot(root, "/subfolder1/"));
+ Assert.False(PathUtil.IsWithinAppRoot(root, "/subfolder1/website2"));
+ Assert.False(PathUtil.IsWithinAppRoot(root, "/subfolder2"));
+ }
+ });
+ }
+
+ [Fact]
+ public void IsWithinAppRootTest()
+ {
+ AppDomainUtils.RunInSeparateAppDomain(() =>
+ {
+ var root = "/website1";
+ using (Utils.CreateHttpRuntime(root))
+ {
+ Assert.True(PathUtil.IsWithinAppRoot(root, "~/"));
+ Assert.True(PathUtil.IsWithinAppRoot(root, "~/default.cshtml"));
+ Assert.True(PathUtil.IsWithinAppRoot(root, "~/test/default.cshtml"));
+ Assert.True(PathUtil.IsWithinAppRoot(root, "/website1"));
+ Assert.True(PathUtil.IsWithinAppRoot(root, "/website1/"));
+ Assert.True(PathUtil.IsWithinAppRoot(root, "/website1/default.cshtml"));
+ Assert.True(PathUtil.IsWithinAppRoot(root, "/website1/test/default.cshtml"));
+
+ Assert.False(PathUtil.IsWithinAppRoot(root, "/"));
+ Assert.False(PathUtil.IsWithinAppRoot(root, "/website2"));
+ Assert.False(PathUtil.IsWithinAppRoot(root, "/subfolder1/"));
+ }
+ });
+ }
+
+ private class TestVirtualPathUtility : IVirtualPathUtility
+ {
+ public string Combine(string basePath, string relativePath)
+ {
+ return basePath + "/" + relativePath;
+ }
+
+ public string ToAbsolute(string virtualPath)
+ {
+ return virtualPath;
+ }
+ }
+ }
+}
diff --git a/test/System.Web.WebPages.Test/Utils/SessionStateUtilTest.cs b/test/System.Web.WebPages.Test/Utils/SessionStateUtilTest.cs
new file mode 100644
index 00000000..97e4835d
--- /dev/null
+++ b/test/System.Web.WebPages.Test/Utils/SessionStateUtilTest.cs
@@ -0,0 +1,183 @@
+using System.Collections.Concurrent;
+using System.Web.Razor;
+using System.Web.SessionState;
+using Moq;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.WebPages.Test
+{
+ public class SessionStateUtilTest
+ {
+ [Fact]
+ public void SetUpSessionStateDoesNotInvokeSessionStateBehaviorIfNoPageHasDirective()
+ {
+ // Arrange
+ var page = new Mock<WebPage>(MockBehavior.Strict);
+ var startPage = new Mock<StartPage>(MockBehavior.Strict);
+ var webPageHttpHandler = new WebPageHttpHandler(page.Object, startPage: new Lazy<WebPageRenderingBase>(() => startPage.Object));
+ var context = new Mock<HttpContextBase>(MockBehavior.Strict);
+
+ // Act
+ SessionStateUtil.SetUpSessionState(context.Object, webPageHttpHandler, new ConcurrentDictionary<Type, SessionStateBehavior?>());
+
+ // Assert
+ context.Verify(c => c.SetSessionStateBehavior(It.IsAny<SessionStateBehavior>()), Times.Never());
+ }
+
+ [Fact]
+ public void SetUpSessionStateUsesSessionStateValueFromRequestingPageIfAvailable()
+ {
+ // Arrange
+ var page = new DisabledSessionWebPage();
+ var webPageHttpHandler = new WebPageHttpHandler(page, startPage: null);
+ var context = new Mock<HttpContextBase>(MockBehavior.Strict);
+ context.Setup(c => c.SetSessionStateBehavior(SessionStateBehavior.Disabled)).Verifiable();
+
+ // Act
+ SessionStateUtil.SetUpSessionState(context.Object, webPageHttpHandler, new ConcurrentDictionary<Type, SessionStateBehavior?>());
+
+ // Assert
+ context.Verify();
+ }
+
+ [Fact]
+ public void SetUpSessionStateUsesSessionStateValueFromStartPageHierarchy()
+ {
+ // Arrange
+ var page = new Mock<WebPage>(MockBehavior.Strict);
+ var startPage = new DefaultSessionWebPage
+ {
+ ChildPage = new ReadOnlySessionWebPage()
+ };
+ var webPageHttpHandler = new WebPageHttpHandler(page.Object, startPage: new Lazy<WebPageRenderingBase>(() => startPage));
+ var context = new Mock<HttpContextBase>(MockBehavior.Strict);
+ context.Setup(c => c.SetSessionStateBehavior(SessionStateBehavior.ReadOnly)).Verifiable();
+
+ // Act
+ SessionStateUtil.SetUpSessionState(context.Object, webPageHttpHandler, new ConcurrentDictionary<Type, SessionStateBehavior?>());
+
+ // Assert
+ context.Verify();
+ }
+
+ [Fact]
+ public void SetUpSessionStateThrowsIfSessionStateValueIsInvalid()
+ {
+ // Arrange
+ var page = new Mock<WebPage>(MockBehavior.Strict);
+ var startPage = new InvalidSessionState();
+ var webPageHttpHandler = new WebPageHttpHandler(page.Object, startPage: new Lazy<WebPageRenderingBase>(() => startPage));
+ var context = new Mock<HttpContextBase>(MockBehavior.Strict);
+
+ // Act
+ Assert.Throws<ArgumentException>(() => SessionStateUtil.SetUpSessionState(context.Object, webPageHttpHandler, new ConcurrentDictionary<Type, SessionStateBehavior?>()),
+ "Value \"jabberwocky\" specified in \"~/_Invalid.cshtml\" is an invalid value for the SessionState directive. Possible values are: \"Default, Required, ReadOnly, Disabled\".");
+ }
+
+ [Fact]
+ public void SetUpSessionStateThrowsIfMultipleSessionStateValueIsInvalid()
+ {
+ // Arrange
+ var page = new PageWithMultipleSesionStateAttributes();
+ var webPageHttpHandler = new WebPageHttpHandler(page, startPage: null);
+ var context = new Mock<HttpContextBase>(MockBehavior.Strict);
+
+ // Act
+ Assert.Throws<InvalidOperationException>(() => SessionStateUtil.SetUpSessionState(context.Object, webPageHttpHandler, new ConcurrentDictionary<Type, SessionStateBehavior?>()),
+ "At most one SessionState value can be declared per page.");
+ }
+
+ [Fact]
+ public void SetUpSessionStateUsesCache()
+ {
+ // Arrange
+ var page = new PageWithBadAttribute();
+ var webPageHttpHandler = new WebPageHttpHandler(page, startPage: null);
+ var context = new Mock<HttpContextBase>(MockBehavior.Strict);
+ var dictionary = new ConcurrentDictionary<Type, SessionStateBehavior?>();
+ dictionary.TryAdd(webPageHttpHandler.GetType(), SessionStateBehavior.Default);
+ context.Setup(c => c.SetSessionStateBehavior(SessionStateBehavior.Default)).Verifiable();
+
+ // Act
+ SessionStateUtil.SetUpSessionState(context.Object, webPageHttpHandler, dictionary);
+
+ // Assert
+ context.Verify();
+ Assert.Throws<Exception>(() => page.GetType().GetCustomAttributes(inherit: false), "Can't call me!");
+ }
+
+ [RazorDirective("sessionstate", "disabled")]
+ private sealed class DisabledSessionWebPage : WebPage
+ {
+ public override void Execute()
+ {
+ throw new NotSupportedException();
+ }
+ }
+
+ [RazorDirective("sessionstate", "ReadOnly")]
+ private sealed class ReadOnlySessionWebPage : StartPage
+ {
+ public override void Execute()
+ {
+ throw new NotSupportedException();
+ }
+ }
+
+ [RazorDirective("SessionState", "Default")]
+ private sealed class DefaultSessionWebPage : StartPage
+ {
+ public override void Execute()
+ {
+ throw new NotSupportedException();
+ }
+ }
+
+ [RazorDirective("SessionState", "jabberwocky")]
+ private sealed class InvalidSessionState : StartPage
+ {
+ public override string VirtualPath
+ {
+ get
+ {
+ return "~/_Invalid.cshtml";
+ }
+ set
+ {
+ VirtualPath = value;
+ }
+ }
+ public override void Execute()
+ {
+ throw new NotSupportedException();
+ }
+ }
+
+ private sealed class BadAttribute : Attribute
+ {
+ public BadAttribute()
+ {
+ throw new Exception("Can't call me!");
+ }
+ }
+
+ [RazorDirective("SessionState", "Default"), Bad]
+ private sealed class PageWithBadAttribute : WebPage
+ {
+ public override void Execute()
+ {
+ throw new NotSupportedException();
+ }
+ }
+
+ [RazorDirective("SessionState", "Disabled"), RazorDirective("SessionState", "ReadOnly")]
+ private sealed class PageWithMultipleSesionStateAttributes : WebPage
+ {
+ public override void Execute()
+ {
+ throw new NotSupportedException();
+ }
+ }
+ }
+}
diff --git a/test/System.Web.WebPages.Test/Utils/TestObjectFactory.cs b/test/System.Web.WebPages.Test/Utils/TestObjectFactory.cs
new file mode 100644
index 00000000..64ee9914
--- /dev/null
+++ b/test/System.Web.WebPages.Test/Utils/TestObjectFactory.cs
@@ -0,0 +1,30 @@
+using System.Collections.Generic;
+using System.Linq;
+
+namespace System.Web.WebPages.Test
+{
+ public class HashVirtualPathFactory : IVirtualPathFactory
+ {
+ private IDictionary<string, object> _pages;
+
+ public HashVirtualPathFactory(params WebPageExecutingBase[] pages)
+ {
+ _pages = pages.ToDictionary(p => p.VirtualPath, p => (object)p, StringComparer.OrdinalIgnoreCase);
+ }
+
+ public bool Exists(string virtualPath)
+ {
+ return _pages.ContainsKey(virtualPath);
+ }
+
+ public object CreateInstance(string virtualPath)
+ {
+ object value;
+ if (_pages.TryGetValue(virtualPath, out value))
+ {
+ return value;
+ }
+ return null;
+ }
+ }
+}
diff --git a/test/System.Web.WebPages.Test/Utils/TypeHelperTest.cs b/test/System.Web.WebPages.Test/Utils/TypeHelperTest.cs
new file mode 100644
index 00000000..7116344d
--- /dev/null
+++ b/test/System.Web.WebPages.Test/Utils/TypeHelperTest.cs
@@ -0,0 +1,111 @@
+using System.Collections.Generic;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.WebPages.Test
+{
+ public class TypeHelperTest
+ {
+ [Fact]
+ public void ObjectToDictionaryWithNullObjectReturnsEmptyDictionary()
+ {
+ // Arrange
+ object dict = null;
+
+ IDictionary<string, object> dictValues = TypeHelper.ObjectToDictionary(dict);
+
+ Assert.NotNull(dictValues);
+ Assert.Equal(0, dictValues.Count);
+ }
+
+ [Fact]
+ public void ObjectToDictionaryWithPlainObjectTypeReturnsEmptyDictionary()
+ {
+ // Arrange
+ object dict = new object();
+
+ // Act
+ IDictionary<string, object> dictValues = TypeHelper.ObjectToDictionary(dict);
+
+ // Assert
+ Assert.NotNull(dictValues);
+ Assert.Equal(0, dictValues.Count);
+ }
+
+ [Fact]
+ public void ObjectToDictionaryWithPrimitiveTypeLooksUpPublicProperties()
+ {
+ // Arrange
+ object dict = "test";
+
+ // Act
+ IDictionary<string, object> dictValues = TypeHelper.ObjectToDictionary(dict);
+
+ // Assert
+ Assert.NotNull(dictValues);
+ Assert.Equal(1, dictValues.Count);
+ Assert.Equal(4, dictValues["Length"]);
+ }
+
+ [Fact]
+ public void ObjectToDictionaryWithAnonymousTypeLooksUpProperties()
+ {
+ // Arrange
+ object dict = new { test = "value", other = 1 };
+
+ // Act
+ IDictionary<string, object> dictValues = TypeHelper.ObjectToDictionary(dict);
+
+ // Assert
+ Assert.NotNull(dictValues);
+ Assert.Equal(2, dictValues.Count);
+ Assert.Equal("value", dictValues["test"]);
+ Assert.Equal(1, dictValues["other"]);
+ }
+
+ [Fact]
+ public void ObjectToDictionaryReturnsCaseInsensitiveDictionary()
+ {
+ // Arrange
+ object dict = new { TEST = "value", oThEr = 1 };
+
+ // Act
+ IDictionary<string, object> dictValues = TypeHelper.ObjectToDictionary(dict);
+
+ // Assert
+ Assert.NotNull(dictValues);
+ Assert.Equal(2, dictValues.Count);
+ Assert.Equal("value", dictValues["test"]);
+ Assert.Equal(1, dictValues["other"]);
+ }
+
+ [Fact]
+ public void AddAnonymousTypeObjectToDictionaryTest()
+ {
+ IDictionary<string, object> d = new Dictionary<string, object>();
+ d.Add("X", "Xvalue");
+ TypeHelper.AddAnonymousObjectToDictionary(d, new { A = "a", B = "b" });
+ Assert.Equal("Xvalue", d["X"]);
+ Assert.Equal("a", d["A"]);
+ Assert.Equal("b", d["B"]);
+ }
+
+ [Fact]
+ public void IsAnonymousTypeTest()
+ {
+ Assert.False(TypeHelper.IsAnonymousType(typeof(object)));
+ Assert.False(TypeHelper.IsAnonymousType(typeof(string)));
+ Assert.False(TypeHelper.IsAnonymousType(typeof(IDictionary<object, object>)));
+ Assert.True(TypeHelper.IsAnonymousType((new { A = "a", B = "b" }.GetType())));
+ var x = "x";
+ var y = "y";
+ Assert.True(TypeHelper.IsAnonymousType((new { x, y }.GetType())));
+ }
+
+ [Fact]
+ public void IsAnonymousTypeNullTest()
+ {
+ Assert.ThrowsArgumentNull(() => TypeHelper.IsAnonymousType(null), "type");
+ }
+ }
+}
diff --git a/test/System.Web.WebPages.Test/Utils/UrlUtilTest.cs b/test/System.Web.WebPages.Test/Utils/UrlUtilTest.cs
new file mode 100644
index 00000000..042c6396
--- /dev/null
+++ b/test/System.Web.WebPages.Test/Utils/UrlUtilTest.cs
@@ -0,0 +1,207 @@
+using System.Web.WebPages.TestUtils;
+using Xunit;
+
+namespace System.Web.WebPages.Test
+{
+ public class UrlUtilTest
+ {
+ [Fact]
+ public void UrlTest()
+ {
+ AppDomainUtils.RunInSeparateAppDomain(() =>
+ {
+ using (IDisposable _ = Utils.CreateHttpContext("default.aspx", "http://localhost/WebSite1/subfolder1/default.aspx"),
+ __ = Utils.CreateHttpRuntime("/WebSite1/"))
+ {
+ var vpath = "~/subfolder1/default.aspx";
+ var href = "~/world/test.aspx";
+ var expected = "/WebSite1/world/test.aspx";
+ Assert.Equal(expected, UrlUtil.Url(vpath, href));
+ Assert.Equal(expected, new MockPage() { VirtualPath = vpath }.Href(href));
+ }
+ });
+ }
+
+ [Fact]
+ public void UrlTest2()
+ {
+ AppDomainUtils.RunInSeparateAppDomain(() =>
+ {
+ using (IDisposable _ = Utils.CreateHttpContext("default.aspx", "http://localhost/WebSite1/default.aspx"),
+ __ = Utils.CreateHttpRuntime("/WebSite1/"))
+ {
+ var vpath = "~/default.aspx";
+ var href = "~/world/test.aspx";
+ var expected = "/WebSite1/world/test.aspx";
+ Assert.Equal(expected, UrlUtil.Url(vpath, href));
+ Assert.Equal(expected, new MockPage() { VirtualPath = vpath }.Href(href));
+ }
+ });
+ }
+
+ [Fact]
+ public void UrlTest3()
+ {
+ AppDomainUtils.RunInSeparateAppDomain(() =>
+ {
+ using (IDisposable _ = Utils.CreateHttpContext("default.aspx", "http://localhost/WebSite1/subfolder1/default.aspx"),
+ __ = Utils.CreateHttpRuntime("/WebSite1/"))
+ {
+ var vpath = "~/subfolder1/default.aspx";
+ var href = "world/test.aspx";
+ var expected = "/WebSite1/subfolder1/world/test.aspx";
+ Assert.Equal(expected, UrlUtil.Url(vpath, href));
+ Assert.Equal(expected, new MockPage() { VirtualPath = vpath }.Href(href));
+ }
+ });
+ }
+
+ [Fact]
+ public void UrlTest4()
+ {
+ AppDomainUtils.RunInSeparateAppDomain(() =>
+ {
+ using (IDisposable _ = Utils.CreateHttpContext("default.aspx", "http://localhost/WebSite1/subfolder1/default.aspx"),
+ __ = Utils.CreateHttpRuntime("/WebSite1/"))
+ {
+ var vpath = "~/subfolder2/default.aspx";
+ var href = "world/test.aspx";
+ var expected = "/WebSite1/subfolder2/world/test.aspx";
+ Assert.Equal(expected, UrlUtil.Url(vpath, href));
+ Assert.Equal(expected, new MockPage() { VirtualPath = vpath }.Href(href));
+ }
+ });
+ }
+
+ [Fact]
+ public void BuildUrlEncodesPagePart()
+ {
+ // Arrange
+ var page = "This is a really bad name for a page";
+ var expected = "This%20is%20a%20really%20bad%20name%20for%20a%20page";
+
+ // Act
+ var actual = UrlUtil.BuildUrl(page);
+
+ // Assert
+ Assert.Equal(actual, expected);
+ }
+
+ [Fact]
+ public void BuildUrlAppendsNonAnonymousTypesToPathPortion()
+ {
+ // Arrange
+ object[] pathParts = new object[] { "part", Decimal.One, 1.25f };
+ var page = "home";
+
+ // Act
+ var actual = UrlUtil.BuildUrl(page, pathParts);
+
+ // Assert
+ Assert.Equal(actual, page + "/part/1/1.25");
+ }
+
+ [Fact]
+ public void BuildUrlEncodesAppendedPathPortion()
+ {
+ // Arrange
+ object[] pathParts = new object[] { "path portion", "ζ" };
+ var page = "home";
+
+ // Act
+ var actual = UrlUtil.BuildUrl(page, pathParts);
+
+ // Assert
+ Assert.Equal(actual, page + "/path%20portion/%ce%b6");
+ }
+
+ [Fact]
+ public void BuildUrlAppendsAnonymousObjectsToQueryString()
+ {
+ // Arrange
+ var page = "home";
+ var queryString = new { sort = "FName", dir = "desc" };
+
+ // Act
+ var actual = UrlUtil.BuildUrl(page, queryString);
+
+ // Assert
+ Assert.Equal(actual, page + "?sort=FName&dir=desc");
+ }
+
+ [Fact]
+ public void BuildUrlAppendsMultipleAnonymousObjectsToQueryString()
+ {
+ // Arrange
+ var page = "home";
+ var queryString1 = new { sort = "FName", dir = "desc" };
+ var queryString2 = new { view = "Activities", page = 7 };
+
+ // Act
+ var actual = UrlUtil.BuildUrl(page, queryString1, queryString2);
+
+ // Assert
+ Assert.Equal(actual, page + "?sort=FName&dir=desc&view=Activities&page=7");
+ }
+
+ [Fact]
+ public void BuildUrlEncodesQueryStringKeysAndValues()
+ {
+ // Arrange
+ var page = "home";
+ var queryString = new { ζ = "my=value&", mykey = "<π" };
+
+ // Act
+ var actual = UrlUtil.BuildUrl(page, queryString);
+
+ // Assert
+ Assert.Equal(actual, page + "?%ce%b6=my%3dvalue%26&mykey=%3c%cf%80");
+ }
+
+ [Fact]
+ public void BuildUrlGeneratesPathPartsAndQueryString()
+ {
+ // Arrange
+ var page = "home";
+
+ // Act
+ var actual = UrlUtil.BuildUrl(page, "products", new { cat = 37 }, "furniture", new { sort = "name", dir = "desc" });
+
+ // Assert
+ Assert.Equal(actual, page + "/products/furniture?cat=37&sort=name&dir=desc");
+ }
+
+ [Fact]
+ public void UrlAppRootTest()
+ {
+ AppDomainUtils.RunInSeparateAppDomain(() =>
+ {
+ using (IDisposable _ = Utils.CreateHttpContext("default.aspx", "http://localhost/"),
+ __ = Utils.CreateHttpRuntime("/"))
+ {
+ var vpath = "~/";
+ var href = "~/world/test.aspx";
+ var expected = "/world/test.aspx";
+ Assert.Equal(expected, UrlUtil.Url(vpath, href));
+ Assert.Equal(expected, new MockPage() { VirtualPath = vpath }.Href(href));
+ }
+ });
+ }
+
+ [Fact]
+ public void UrlAnonymousObjectTest()
+ {
+ AppDomainUtils.RunInSeparateAppDomain(() =>
+ {
+ using (IDisposable _ = Utils.CreateHttpContext("default.aspx", "http://localhost/"),
+ __ = Utils.CreateHttpRuntime("/"))
+ {
+ Assert.Equal("/world/test.cshtml?Prop1=value1",
+ UrlUtil.Url("~/world/page.cshtml", "test.cshtml", new { Prop1 = "value1" }));
+ Assert.Equal("/world/test.cshtml?Prop1=value1&Prop2=value2",
+ UrlUtil.Url("~/world/page.cshtml", "test.cshtml", new { Prop1 = "value1", Prop2 = "value2" }));
+ }
+ });
+ }
+ }
+}
diff --git a/test/System.Web.WebPages.Test/Validation/ValidationHelperTest.cs b/test/System.Web.WebPages.Test/Validation/ValidationHelperTest.cs
new file mode 100644
index 00000000..7f36d538
--- /dev/null
+++ b/test/System.Web.WebPages.Test/Validation/ValidationHelperTest.cs
@@ -0,0 +1,835 @@
+using System.Collections.Specialized;
+using System.ComponentModel.DataAnnotations;
+using System.Linq;
+using System.Web.Mvc;
+using System.Web.WebPages.Html;
+using System.Web.WebPages.Scope;
+using System.Web.WebPages.TestUtils;
+using Moq;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.WebPages.Validation.Test
+{
+ public class ValidationHelperTest
+ {
+ [Fact]
+ public void FormFieldKeyIsCommonToModelStateAndValidationHelper()
+ {
+ // Arrange
+ RequestFieldValidatorBase.IgnoreUseUnvalidatedValues = true;
+ string key = "_FORM";
+ ValidationHelper validationHelper = GetValidationHelper(GetContext());
+
+ // Act and Assert
+ Assert.Equal(key, ModelStateDictionary.FormFieldKey);
+ Assert.Equal(key, validationHelper.FormField);
+ }
+
+ [Fact]
+ public void AddThrowsIfFieldIsEmpty()
+ {
+ // Arrange
+ RequestFieldValidatorBase.IgnoreUseUnvalidatedValues = true;
+ ValidationHelper validationHelper = GetValidationHelper(GetContext());
+
+ // Act and Assert
+ Assert.ThrowsArgumentNullOrEmptyString(() => validationHelper.Add(field: null), "field");
+ Assert.ThrowsArgumentNullOrEmptyString(() => validationHelper.Add(field: String.Empty), "field");
+ }
+
+ [Fact]
+ public void AddThrowsIfValidatorsParamsArrayIsNull()
+ {
+ // Arrange
+ RequestFieldValidatorBase.IgnoreUseUnvalidatedValues = true;
+ ValidationHelper validationHelper = GetValidationHelper(GetContext());
+
+ // Act and Assert
+ Assert.ThrowsArgumentNull(() => validationHelper.Add("foo", null), "validators");
+ }
+
+ [Fact]
+ public void AddThrowsIfValidatorsAreNull()
+ {
+ // Arrange
+ RequestFieldValidatorBase.IgnoreUseUnvalidatedValues = true;
+ ValidationHelper validationHelper = GetValidationHelper(GetContext());
+
+ // Act and Assert
+ Assert.ThrowsArgumentNull(() => validationHelper.Add("foo", Validator.Required(), null, Validator.Range(0, 10)), "validators");
+ }
+
+ [Fact]
+ public void RequiredReturnsErrorMessageIfFieldIsNotPresentInForm()
+ {
+ // Arrange
+ RequestFieldValidatorBase.IgnoreUseUnvalidatedValues = true;
+ string message = "Foo is required.";
+ ValidationHelper validationHelper = GetValidationHelper(GetContext());
+
+ // Act
+ validationHelper.RequireField("foo", message);
+ var results = validationHelper.Validate();
+
+ // Assert
+ Assert.Equal(1, results.Count());
+ Assert.Equal(message, results.First().ErrorMessage);
+ }
+
+ [Fact]
+ public void RequiredReturnsErrorMessageIfFieldIsEmpty()
+ {
+ // Arrange
+ RequestFieldValidatorBase.IgnoreUseUnvalidatedValues = true;
+ string message = "Foo is required.";
+ ValidationHelper validationHelper = GetValidationHelper(GetContext(new { foo = "" }));
+
+ // Act
+ validationHelper.RequireField("foo", message);
+ var results = validationHelper.Validate();
+
+ // Assert
+ Assert.Equal(1, results.Count());
+ Assert.Equal(message, results.First().ErrorMessage);
+ }
+
+ [Fact]
+ public void RequiredReturnsNoValidationResultsIfFieldIsPresent()
+ {
+ // Arrange
+ RequestFieldValidatorBase.IgnoreUseUnvalidatedValues = true;
+ string message = "Foo is required.";
+ ValidationHelper validationHelper = GetValidationHelper(GetContext(new { foo = "some value" }));
+
+ // Act
+ validationHelper.RequireField("foo", message);
+ var results = validationHelper.Validate();
+
+ // Assert
+ Assert.Equal(0, results.Count());
+ }
+
+ [Fact]
+ public void RequiredUsesDefaultErrorMessageIfNoValueIsProvided()
+ {
+ // Arrange
+ RequestFieldValidatorBase.IgnoreUseUnvalidatedValues = true;
+ ValidationHelper validationHelper = GetValidationHelper(GetContext());
+
+ // Act
+ validationHelper.RequireField("foo");
+ var results = validationHelper.Validate();
+
+ // Assert
+ Assert.Equal(1, results.Count());
+ Assert.Equal("This field is required.", results.First().ErrorMessage);
+ }
+
+ [Fact]
+ public void RequiredReturnsValidationResultForEachFieldThatFailed()
+ {
+ // Arrange
+ RequestFieldValidatorBase.IgnoreUseUnvalidatedValues = true;
+ string message = "This field is required.";
+ ValidationHelper validationHelper = GetValidationHelper(GetContext());
+
+ // Act
+ validationHelper.RequireFields("foo", "bar");
+ var results = validationHelper.Validate();
+
+ // Assert
+ Assert.Equal(2, results.Count());
+ Assert.Equal(message, results.First().ErrorMessage);
+ Assert.Equal("foo", results.First().MemberNames.Single());
+
+ Assert.Equal(message, results.Last().ErrorMessage);
+ Assert.Equal("bar", results.Last().MemberNames.Single());
+ }
+
+ [Fact]
+ public void RequiredReturnsValidationResultForEachFieldThatFailedWhenFieldsIsEmpty()
+ {
+ // Arrange
+ RequestFieldValidatorBase.IgnoreUseUnvalidatedValues = true;
+ string message = "This field is required.";
+ ValidationHelper validationHelper = GetValidationHelper(GetContext());
+
+ // Act
+ validationHelper.RequireFields("foo", "bar");
+ var results = validationHelper.Validate(fields: null);
+
+ // Assert
+ Assert.Equal(2, results.Count());
+ Assert.Equal(message, results.First().ErrorMessage);
+ Assert.Equal("foo", results.First().MemberNames.Single());
+
+ Assert.Equal(message, results.Last().ErrorMessage);
+ Assert.Equal("bar", results.Last().MemberNames.Single());
+ }
+
+ [Fact]
+ public void GetValidationHtmlThrowsIfArgumentIsNullOrEmpty()
+ {
+ // Arrange
+ RequestFieldValidatorBase.IgnoreUseUnvalidatedValues = true;
+ ValidationHelper validationHelper = GetValidationHelper(GetContext());
+
+ // Act and Assert
+ Assert.ThrowsArgumentNullOrEmptyString(() => validationHelper.For(field: null), "field");
+ Assert.ThrowsArgumentNullOrEmptyString(() => validationHelper.For(field: String.Empty), "field");
+ }
+
+ [Fact]
+ public void RequireFieldThrowsIfValueIsNullOrEmpty()
+ {
+ // Arrange
+ RequestFieldValidatorBase.IgnoreUseUnvalidatedValues = true;
+ ValidationHelper validationHelper = GetValidationHelper(GetContext());
+
+ // Act and Assert
+ Assert.ThrowsArgumentNullOrEmptyString(() => validationHelper.RequireField(field: null), "field");
+ Assert.ThrowsArgumentNullOrEmptyString(() => validationHelper.RequireField(field: String.Empty), "field");
+ Assert.ThrowsArgumentNullOrEmptyString(() => validationHelper.RequireField(field: null, errorMessage: "baz"), "field");
+ Assert.ThrowsArgumentNullOrEmptyString(() => validationHelper.RequireField(field: String.Empty, errorMessage: null), "field");
+ }
+
+ [Fact]
+ public void RequireFieldsThrowsIfFieldsAreNullOrHasEmptyValues()
+ {
+ // Arrange
+ RequestFieldValidatorBase.IgnoreUseUnvalidatedValues = true;
+ ValidationHelper validationHelper = GetValidationHelper(GetContext());
+
+ // Act and Assert
+ Assert.ThrowsArgumentNull(() => validationHelper.RequireFields(fields: null), "fields");
+ Assert.ThrowsArgumentNullOrEmptyString(() => validationHelper.RequireFields(fields: new[] { "foo", null }), "field");
+ Assert.ThrowsArgumentNullOrEmptyString(() => validationHelper.RequireFields(fields: new[] { "foo", "" }), "field");
+ }
+
+ [Fact]
+ public void AddThrowsIfFieldIsNullOrEmpty()
+ {
+ // Arrange
+ RequestFieldValidatorBase.IgnoreUseUnvalidatedValues = true;
+ ValidationHelper validationHelper = GetValidationHelper(GetContext());
+
+ // Act and Assert
+ Assert.ThrowsArgumentNullOrEmptyString(() => validationHelper.Add(field: null), "field");
+ Assert.ThrowsArgumentNullOrEmptyString(() => validationHelper.Add(field: String.Empty), "field");
+ }
+
+ [Fact]
+ public void AddThrowsIfValidatorsIsNullOrAnyValidatorIsNull()
+ {
+ // Arrange
+ RequestFieldValidatorBase.IgnoreUseUnvalidatedValues = true;
+ ValidationHelper validationHelper = GetValidationHelper(GetContext());
+
+ // Act and Assert
+ Assert.ThrowsArgumentNull(() => validationHelper.Add(field: "foo", validators: null), "validators");
+ Assert.ThrowsArgumentNull(() => validationHelper.Add(field: "foo", validators: new[] { Validator.DateTime(), null }), "validators");
+ }
+
+ [Fact]
+ public void AddFormErrorCallsMethodInUnderlyingModelStateDictionary()
+ {
+ // Arrange
+ var message = "This is a form error.";
+ var dictionary = new ModelStateDictionary();
+ ValidationHelper validationHelper = GetValidationHelper(GetContext(), dictionary);
+
+ // Act
+ validationHelper.AddFormError(message);
+
+ // Assert
+ Assert.Equal(message, dictionary["_FORM"].Errors.Single());
+ }
+
+ [Fact]
+ public void GetValidationHtmlForRequired()
+ {
+ // Arrange
+ RequestFieldValidatorBase.IgnoreUseUnvalidatedValues = true;
+ string message = "Foo is required.";
+ ValidationHelper validationHelper = GetValidationHelper(GetContext());
+
+ // Act
+ validationHelper.RequireField("foo", message);
+ var validationHtml = validationHelper.For("foo");
+
+ // Assert
+ Assert.Equal(@"data-val-required=""Foo is required."" data-val=""true""", validationHtml.ToString());
+ }
+
+ [Fact]
+ public void ValidateReturnsAnEmptySequenceIfNoValidationsAreRegistered()
+ {
+ // Arrange
+ RequestFieldValidatorBase.IgnoreUseUnvalidatedValues = true;
+ ValidationHelper validationHelper = GetValidationHelper(GetContext());
+
+ // Act
+ var results = validationHelper.Validate();
+
+ // Assert
+ Assert.False(results.Any());
+ }
+
+ [Fact]
+ public void ValidatePopulatesModelStateDictionary()
+ {
+ // Arrange
+ RequestFieldValidatorBase.IgnoreUseUnvalidatedValues = true;
+ var modelStateDictionary = new ModelStateDictionary();
+ ValidationHelper validationHelper = GetValidationHelper(GetContext(), modelStateDictionary);
+
+ // Act
+ validationHelper.RequireFields(new[] { "foo", "bar" });
+ validationHelper.Validate();
+
+ // Assert
+ Assert.False(modelStateDictionary.IsValid);
+ Assert.False(modelStateDictionary.IsValidField("foo"));
+ Assert.False(modelStateDictionary.IsValidField("bar"));
+ Assert.Equal("This field is required.", modelStateDictionary["foo"].Errors.Single());
+ Assert.Equal("This field is required.", modelStateDictionary["bar"].Errors.Single());
+ }
+
+ [Fact]
+ public void IsValidPopulatesModelStateDictionary()
+ {
+ // Arrange
+ RequestFieldValidatorBase.IgnoreUseUnvalidatedValues = true;
+ var modelStateDictionary = new ModelStateDictionary();
+ ValidationHelper validationHelper = GetValidationHelper(GetContext(), modelStateDictionary);
+
+ // Act
+ validationHelper.RequireFields("foo", "bar");
+ validationHelper.IsValid();
+
+ // Assert
+ Assert.False(modelStateDictionary.IsValid);
+ Assert.False(modelStateDictionary.IsValidField("foo"));
+ Assert.False(modelStateDictionary.IsValidField("bar"));
+ Assert.Equal("This field is required.", modelStateDictionary["foo"].Errors.Single());
+ Assert.Equal("This field is required.", modelStateDictionary["bar"].Errors.Single());
+ }
+
+ [Fact]
+ public void GetErrorsPopulatesModelStateDictionary()
+ {
+ // Arrange
+ RequestFieldValidatorBase.IgnoreUseUnvalidatedValues = true;
+ var modelStateDictionary = new ModelStateDictionary();
+ ValidationHelper validationHelper = GetValidationHelper(GetContext(), modelStateDictionary);
+
+ // Act
+ validationHelper.RequireFields("foo", "bar");
+ validationHelper.GetErrors();
+
+ // Assert
+ Assert.False(modelStateDictionary.IsValid);
+ Assert.False(modelStateDictionary.IsValidField("foo"));
+ Assert.False(modelStateDictionary.IsValidField("bar"));
+ Assert.Equal("This field is required.", modelStateDictionary["foo"].Errors.Single());
+ Assert.Equal("This field is required.", modelStateDictionary["bar"].Errors.Single());
+ }
+
+ [Fact]
+ public void GetErrorsReturnsAnEmptySequenceIfNoValidationsAreRegistered()
+ {
+ // Arrange
+ RequestFieldValidatorBase.IgnoreUseUnvalidatedValues = true;
+ ValidationHelper validationHelper = GetValidationHelper(GetContext());
+
+ // Act
+ var results = validationHelper.GetErrors();
+
+ // Assert
+ Assert.False(results.Any());
+ }
+
+ [Fact]
+ public void GetErrorsReturnsErrorsAddedViaAddError()
+ {
+ // Arrange
+ var modelStateDictionary = new ModelStateDictionary();
+ ValidationHelper validationHelper = GetValidationHelper(GetContext(), modelStateDictionary);
+
+ // Act
+ modelStateDictionary.AddError("foo", "Foo error");
+ var errors = validationHelper.GetErrors("foo");
+
+ // Assert
+ Assert.Equal(new[] { "Foo error" }, errors);
+ }
+
+ [Fact]
+ public void GetErrorsReturnsFormErrors()
+ {
+ // Arrange
+ string error = "Unable to connect to remote servers.";
+ var modelStateDictionary = new ModelStateDictionary();
+ ValidationHelper validationHelper = GetValidationHelper(GetContext(), modelStateDictionary);
+
+ // Act
+ validationHelper.AddFormError(error);
+ var errors = validationHelper.GetErrors();
+
+ // Assert
+ Assert.Equal(error, errors.Single());
+ }
+
+ [Fact]
+ public void InvokingValidateMultipleTimesDoesNotCauseErrorMessagesToBeDuplicated()
+ {
+ // Arrange
+ RequestFieldValidatorBase.IgnoreUseUnvalidatedValues = true;
+ var modelStateDictionary = new ModelStateDictionary();
+ ValidationHelper validationHelper = GetValidationHelper(GetContext(), modelStateDictionary);
+
+ // Act
+ validationHelper.RequireField("foo", "Foo is required.");
+ validationHelper.RequireField("bar", "Bar is required.");
+ validationHelper.Validate();
+ Assert.False(validationHelper.IsValid());
+ validationHelper.Validate();
+ validationHelper.Validate();
+
+ // Assert
+ Assert.False(modelStateDictionary.IsValid);
+ Assert.False(modelStateDictionary.IsValidField("foo"));
+ Assert.False(modelStateDictionary.IsValidField("bar"));
+ Assert.Equal("Foo is required.", modelStateDictionary["foo"].Errors.Single());
+ Assert.Equal("Bar is required.", modelStateDictionary["bar"].Errors.Single());
+ }
+
+ [Fact]
+ public void AddWorksForCustomValidator()
+ {
+ // Arrange
+ RequestFieldValidatorBase.IgnoreUseUnvalidatedValues = true;
+ string message = "Foo is not an odd number.";
+ var oddValidator = new Mock<IValidator>();
+ oddValidator.Setup(c => c.Validate(It.IsAny<ValidationContext>())).Returns<ValidationContext>(v =>
+ {
+ Assert.IsAssignableFrom<HttpContextBase>(v.ObjectInstance);
+ var context = (HttpContextBase)v.ObjectInstance;
+ var value = Int32.Parse(context.Request.Form["foo"]);
+
+ if (value % 2 != 0)
+ {
+ return ValidationResult.Success;
+ }
+ return new ValidationResult(message);
+ }).Verifiable();
+ ValidationHelper validationHelper = GetValidationHelper(GetContext(new { foo = "6" }));
+
+ // Act
+ validationHelper.Add("foo", oddValidator.Object);
+ var result = validationHelper.Validate();
+
+ // Assert
+ Assert.Equal(1, result.Count());
+ Assert.Equal(message, result.First().ErrorMessage);
+ oddValidator.Verify();
+ }
+
+ [Fact]
+ public void ValidateRunsForSpecifiedFields()
+ {
+ // Arrange
+ RequestFieldValidatorBase.IgnoreUseUnvalidatedValues = true;
+ string message = "Foo is not an odd number.";
+ var oddValidator = new Mock<IValidator>();
+ oddValidator.Setup(c => c.Validate(It.IsAny<ValidationContext>())).Returns<ValidationContext>(v =>
+ {
+ Assert.IsAssignableFrom<HttpContextBase>(v.ObjectInstance);
+ var context = (HttpContextBase)v.ObjectInstance;
+ if (context.Request.Form["foo"].IsEmpty())
+ {
+ return ValidationResult.Success;
+ }
+ int value = context.Request.Form["foo"].AsInt();
+ if (value % 2 != 0)
+ {
+ return ValidationResult.Success;
+ }
+ return new ValidationResult(message);
+ }).Verifiable();
+ ValidationHelper validationHelper = GetValidationHelper(GetContext(new { foo = "", bar = "" }));
+
+ // Act
+ validationHelper.Add(new[] { "foo", "bar" }, oddValidator.Object);
+ validationHelper.RequireField("foo");
+ var result = validationHelper.Validate("foo");
+
+ // Assert
+ Assert.Equal(1, result.Count());
+ Assert.Equal("This field is required.", result.First().ErrorMessage);
+ }
+
+ [Fact]
+ public void GetErrorsReturnsAllErrorsIfNoParametersAreSpecified()
+ {
+ // Arrange
+ RequestFieldValidatorBase.IgnoreUseUnvalidatedValues = true;
+ string message = "Foo is not an odd number.";
+ var oddValidator = new Mock<IValidator>();
+ oddValidator.Setup(c => c.Validate(It.IsAny<ValidationContext>())).Returns<ValidationContext>(v =>
+ {
+ Assert.IsAssignableFrom<HttpContextBase>(v.ObjectInstance);
+ var context = (HttpContextBase)v.ObjectInstance;
+ if (context.Request.Form["foo"].IsEmpty())
+ {
+ return ValidationResult.Success;
+ }
+ int value = context.Request.Form["foo"].AsInt();
+ if (value % 2 != 0)
+ {
+ return ValidationResult.Success;
+ }
+ return new ValidationResult(message);
+ }).Verifiable();
+ ValidationHelper validationHelper = GetValidationHelper(GetContext(new { foo = "4", bar = "" }));
+
+ // Act
+ validationHelper.Add("foo", oddValidator.Object);
+ validationHelper.RequireFields(new[] { "bar", "foo" });
+ var result = validationHelper.GetErrors();
+
+ // Assert
+ Assert.Equal(2, result.Count());
+ Assert.Equal("Foo is not an odd number.", result.First());
+ Assert.Equal("This field is required.", result.Last());
+ }
+
+ [Fact]
+ public void IsValidReturnsTrueIfAllValuesPassValidation()
+ {
+ // Arrange
+ RequestFieldValidatorBase.IgnoreUseUnvalidatedValues = true;
+ string message = "Foo is not an odd number.";
+ var oddValidator = new Mock<IValidator>();
+ oddValidator.Setup(c => c.Validate(It.IsAny<ValidationContext>())).Returns<ValidationContext>(v =>
+ {
+ Assert.IsAssignableFrom<HttpContextBase>(v.ObjectInstance);
+ var context = (HttpContextBase)v.ObjectInstance;
+ if (context.Request.Form["foo"].IsEmpty())
+ {
+ return ValidationResult.Success;
+ }
+ int value = context.Request.Form["foo"].AsInt();
+ if (value % 2 != 0)
+ {
+ return ValidationResult.Success;
+ }
+ return new ValidationResult(message);
+ }).Verifiable();
+ ValidationHelper validationHelper = GetValidationHelper(GetContext(new { foo = "5", bar = "2" }));
+
+ // Act
+ validationHelper.Add(new[] { "foo", "bar" }, oddValidator.Object);
+ validationHelper.RequireField("foo");
+ var result = validationHelper.IsValid();
+
+ // Assert
+ Assert.True(result);
+ }
+
+ [Fact]
+ public void IsValidValidatesSpecifiedFields()
+ {
+ // Arrange
+ RequestFieldValidatorBase.IgnoreUseUnvalidatedValues = true;
+ string message = "Foo is not an odd number.";
+ var oddValidator = new Mock<IValidator>();
+ oddValidator.Setup(c => c.Validate(It.IsAny<ValidationContext>())).Returns<ValidationContext>(v =>
+ {
+ Assert.IsAssignableFrom<HttpContextBase>(v.ObjectInstance);
+ var context = (HttpContextBase)v.ObjectInstance;
+ int value;
+ if (!Int32.TryParse(context.Request.Form["foo"], out value))
+ {
+ return ValidationResult.Success;
+ }
+ if (value % 2 != 0)
+ {
+ return ValidationResult.Success;
+ }
+ return new ValidationResult(message);
+ }).Verifiable();
+ ValidationHelper validationHelper = GetValidationHelper(GetContext(new { foo = "3", bar = "" }));
+
+ // Act
+ validationHelper.Add(new[] { "foo", "bar" }, oddValidator.Object);
+ validationHelper.RequireFields(new[] { "foo", "bar" });
+ var result = validationHelper.IsValid("foo");
+
+ // Assert
+ Assert.True(result);
+ }
+
+ [Fact]
+ public void GetValidationHtmlReturnsNullIfNoRulesAreRegistered()
+ {
+ // Arrange
+ RequestFieldValidatorBase.IgnoreUseUnvalidatedValues = true;
+ ValidationHelper validationHelper = GetValidationHelper(GetContext());
+
+ // Assert
+ var validationAttributes = validationHelper.For("bar");
+
+ // Assert
+ Assert.Null(validationAttributes);
+ }
+
+ [Fact]
+ public void GetValidationHtmlReturnsAttributesForRegisteredValidators()
+ {
+ // Arrange
+ RequestFieldValidatorBase.IgnoreUseUnvalidatedValues = true;
+ var validator = new Mock<IValidator>();
+ var clientRules = new ModelClientValidationRule { ValidationType = "foo", ErrorMessage = "Foo error." };
+ clientRules.ValidationParameters["qux"] = "some data";
+ validator.Setup(c => c.ClientValidationRule).Returns(clientRules).Verifiable();
+ var expected = @"data-val-required=""This field is required."" data-val-foo=""Foo error."" data-val-foo-qux=""some data"" data-val=""true""";
+
+ ValidationHelper validationHelper = GetValidationHelper(GetContext());
+
+ // Act
+ validationHelper.RequireField("foo");
+ validationHelper.Add("foo", validator.Object);
+ var validationAttributes = validationHelper.For("foo");
+
+ // Assert
+ Assert.Equal(expected, validationAttributes.ToString());
+ }
+
+ [Fact]
+ public void GetValidationHtmlHtmlEncodesAttributeValues()
+ {
+ // Arrange
+ RequestFieldValidatorBase.IgnoreUseUnvalidatedValues = true;
+ var validator = new Mock<IValidator>();
+ var clientRules = new ModelClientValidationRule { ValidationType = "biz", ErrorMessage = "<Biz error.>" };
+ clientRules.ValidationParameters["qux"] = "<some ' data>";
+ validator.Setup(c => c.ClientValidationRule).Returns(clientRules).Verifiable();
+ var expected = @"data-val-required=""This field is required."" data-val-biz=""&lt;Biz error.&gt;"" data-val-biz-qux=""&lt;some &#39; data&gt;"" data-val=""true""";
+
+ // Act
+ ValidationHelper validationHelper = GetValidationHelper(GetContext());
+
+ // Assert
+ validationHelper.RequireField("foo");
+ validationHelper.Add("foo", validator.Object);
+ var validationAttributes = validationHelper.For("foo");
+
+ // Assert
+ Assert.Equal(expected, validationAttributes.ToString());
+ }
+
+ [Fact]
+ public void GetValidationFromClientValidationRulesThrowsIfValidationTypeIsNullOrEmpty()
+ {
+ // Arrange
+ RequestFieldValidatorBase.IgnoreUseUnvalidatedValues = true;
+ var clientRule = new ModelClientValidationRule { ValidationType = null };
+
+ // Act and Assert
+ Assert.Throws<InvalidOperationException>(() => ValidationHelper.GenerateHtmlFromClientValidationRules(new[] { clientRule }),
+ "Validation type names in unobtrusive client validation rules cannot be empty. Client rule type: System.Web.Mvc.ModelClientValidationRule");
+
+ clientRule.ValidationType = String.Empty;
+ Assert.Throws<InvalidOperationException>(() => ValidationHelper.GenerateHtmlFromClientValidationRules(new[] { clientRule }),
+ "Validation type names in unobtrusive client validation rules cannot be empty. Client rule type: System.Web.Mvc.ModelClientValidationRule");
+ }
+
+ [Fact]
+ public void GetValidationFromClientValidationRulesThrowsIfSameValidationTypeIsSpecifiedMultipleTimes()
+ {
+ // Arrange
+ RequestFieldValidatorBase.IgnoreUseUnvalidatedValues = true;
+ var clientRule1 = new ModelClientValidationRule { ValidationType = "foo" };
+ var clientRule2 = new ModelClientValidationRule { ValidationType = "foo" };
+
+ // Act and Assert
+ Assert.Throws<InvalidOperationException>(() => ValidationHelper.GenerateHtmlFromClientValidationRules(new[] { clientRule1, clientRule2 }),
+ "Validation type names in unobtrusive client validation rules must be unique. The following validation type was seen more than once: foo");
+ }
+
+ [Fact]
+ public void GetValidationFromClientValidationRulesThrowsIfValidationTypeDoesNotContainAllLowerCaseCharacters()
+ {
+ // Arrange
+ RequestFieldValidatorBase.IgnoreUseUnvalidatedValues = true;
+ var clientRule = new ModelClientValidationRule { ValidationType = "Foo" };
+
+ // Act and Assert
+ Assert.Throws<InvalidOperationException>(() => ValidationHelper.GenerateHtmlFromClientValidationRules(new[] { clientRule }),
+ "Validation type names in unobtrusive client validation rules must consist of only lowercase letters. Invalid name: \"Foo\", client rule type: System.Web.Mvc.ModelClientValidationRule");
+
+ clientRule.ValidationType = "bAr";
+ Assert.Throws<InvalidOperationException>(() => ValidationHelper.GenerateHtmlFromClientValidationRules(new[] { clientRule }),
+ "Validation type names in unobtrusive client validation rules must consist of only lowercase letters. Invalid name: \"bAr\", client rule type: System.Web.Mvc.ModelClientValidationRule");
+
+ clientRule.ValidationType = "bar123";
+ Assert.Throws<InvalidOperationException>(() => ValidationHelper.GenerateHtmlFromClientValidationRules(new[] { clientRule }),
+ "Validation type names in unobtrusive client validation rules must consist of only lowercase letters. Invalid name: \"bar123\", client rule type: System.Web.Mvc.ModelClientValidationRule");
+ }
+
+ [Fact]
+ public void GetValidationFromClientValidationRulesThrowsIfValidationParamaterContainsNonAlphaNumericCharacters()
+ {
+ // Arrange
+ RequestFieldValidatorBase.IgnoreUseUnvalidatedValues = true;
+ var clientRule = new ModelClientValidationRule { ValidationType = "required" };
+ clientRule.ValidationParameters["min^"] = "some-val";
+
+ // Act and Assert
+ Assert.Throws<InvalidOperationException>(() => ValidationHelper.GenerateHtmlFromClientValidationRules(new[] { clientRule }),
+ "Validation parameter names in unobtrusive client validation rules must start with a lowercase letter and consist of only lowercase letters or digits. Validation parameter name: min^, client rule type: System.Web.Mvc.ModelClientValidationRule");
+
+ clientRule.ValidationParameters.Clear();
+ clientRule.ValidationParameters["Min"] = "some-val";
+
+ Assert.Throws<InvalidOperationException>(() => ValidationHelper.GenerateHtmlFromClientValidationRules(new[] { clientRule }),
+ "Validation parameter names in unobtrusive client validation rules must start with a lowercase letter and consist of only lowercase letters or digits. Validation parameter name: Min, client rule type: System.Web.Mvc.ModelClientValidationRule");
+ }
+
+ [Fact]
+ public void DefaultValidCssClassIsNull()
+ {
+ Assert.Null(ValidationHelper.ValidCssClass);
+ }
+
+ [Fact]
+ public void DefaultInvalidCssClassIsSameAsHtmlHelper()
+ {
+ Assert.Equal(HtmlHelper.DefaultValidationInputErrorCssClass, ValidationHelper.InvalidCssClass);
+ }
+
+ [Fact]
+ public void InvalidCssClassIsNullIfExplicitlySetToNull()
+ {
+ using (ValidationHelper.OverrideScope())
+ {
+ ValidationHelper.InvalidCssClass = null;
+ Assert.Null(ValidationHelper.InvalidCssClass);
+ }
+ }
+
+ [Fact]
+ public void ValidCssClassIsScopeBacked()
+ {
+ // Set a value
+ string old = ValidationHelper.ValidCssClass;
+ ValidationHelper.ValidCssClass = "outer";
+ using (ScopeStorage.CreateTransientScope())
+ {
+ ValidationHelper.ValidCssClass = "inner";
+ Assert.Equal("inner", ValidationHelper.ValidCssClass);
+ }
+ Assert.Equal("outer", ValidationHelper.ValidCssClass);
+ ValidationHelper.ValidCssClass = old;
+ }
+
+ [Fact]
+ public void InvalidCssClassIsScopeBacked()
+ {
+ // Set a value
+ string old = ValidationHelper.InvalidCssClass;
+ ValidationHelper.InvalidCssClass = "outer";
+ using (ScopeStorage.CreateTransientScope())
+ {
+ ValidationHelper.InvalidCssClass = "inner";
+ Assert.Equal("inner", ValidationHelper.InvalidCssClass);
+ }
+ Assert.Equal("outer", ValidationHelper.InvalidCssClass);
+ ValidationHelper.InvalidCssClass = old;
+ }
+
+ [Fact]
+ public void ClassForReturnsNullIfNotPost()
+ {
+ // Arrange
+ ValidationHelper helper = GetValidationHelper();
+
+ // Act/Assert
+ Assert.Null(helper.ClassFor("foo"));
+ }
+
+ [Fact]
+ public void ClassForReturnsValidClassNameIfNoErrorsAddedForField()
+ {
+ // Arrange
+ ValidationHelper helper = GetPostValidationHelper();
+
+ // Act/Assert
+ HtmlString html = helper.ClassFor("foo");
+ string str = html == null ? null : html.ToHtmlString();
+ Assert.Equal(ValidationHelper.ValidCssClass, str);
+ }
+
+ [Fact]
+ public void ClassForReturnsInvalidClassNameIfFieldHasErrors()
+ {
+ // Arrange
+ ValidationHelper helper = GetPostValidationHelper();
+ helper.Add("foo", new AutoFailValidator());
+
+ // Act/Assert
+ Assert.Equal(ValidationHelper.InvalidCssClass, helper.ClassFor("foo").ToHtmlString());
+ }
+
+ private static ValidationHelper GetPostValidationHelper()
+ {
+ HttpContextBase context = GetContext();
+ Mock.Get(context.Request).SetupGet(c => c.HttpMethod).Returns("POST");
+ ValidationHelper helper = GetValidationHelper(httpContext: context);
+ return helper;
+ }
+
+ private static HttpContextBase GetContext(object formValues = null)
+ {
+ var context = new Mock<HttpContextBase>();
+ var request = new Mock<HttpRequestBase>();
+
+ var nameValueCollection = new NameValueCollection();
+ if (formValues != null)
+ {
+ foreach (var prop in formValues.GetType().GetProperties())
+ {
+ nameValueCollection.Add(prop.Name, prop.GetValue(formValues, null).ToString());
+ }
+ }
+ request.SetupGet(c => c.Form).Returns(nameValueCollection);
+ context.SetupGet(c => c.Request).Returns(request.Object);
+
+ return context.Object;
+ }
+
+ private static ValidationHelper GetValidationHelper(HttpContextBase httpContext = null, ModelStateDictionary modelStateDictionary = null)
+ {
+ httpContext = httpContext ?? GetContext();
+ modelStateDictionary = modelStateDictionary ?? new ModelStateDictionary();
+
+ return new ValidationHelper(httpContext, modelStateDictionary);
+ }
+
+ private class AutoFailValidator : IValidator
+ {
+
+ public ValidationResult Validate(ValidationContext validationContext)
+ {
+ return new ValidationResult("Failed!");
+ }
+
+ public ModelClientValidationRule ClientValidationRule
+ {
+ get { throw new NotImplementedException(); }
+ }
+ }
+ }
+}
diff --git a/test/System.Web.WebPages.Test/Validation/ValidatorTest.cs b/test/System.Web.WebPages.Test/Validation/ValidatorTest.cs
new file mode 100644
index 00000000..b934a110
--- /dev/null
+++ b/test/System.Web.WebPages.Test/Validation/ValidatorTest.cs
@@ -0,0 +1,882 @@
+using System.Collections.Specialized;
+using System.ComponentModel.DataAnnotations;
+using System.Linq;
+using Moq;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.WebPages.Validation.Test
+{
+ public class ValidatorTest
+ {
+ [Fact]
+ public void RequiredValidatorValidatesIfStringIsNull()
+ {
+ // Arrange
+ RequestFieldValidatorBase.IgnoreUseUnvalidatedValues = true;
+ var requiredValidator = Validator.Required();
+ var validationContext = GetValidationContext(GetContext(), "foo");
+
+ // Act
+ var result = requiredValidator.Validate(validationContext);
+
+ // Assert
+ Assert.NotEqual(ValidationResult.Success, result);
+ Assert.Equal("This field is required.", result.ErrorMessage);
+ Assert.Equal("foo", result.MemberNames.Single());
+ }
+
+ [Fact]
+ public void RequiredValidatorValidatesIfStringIsEmpty()
+ {
+ // Arrange
+ RequestFieldValidatorBase.IgnoreUseUnvalidatedValues = true;
+ var requiredValidator = Validator.Required();
+ var validationContext = GetValidationContext(GetContext(new { foo = "" }), "foo");
+
+ // Act
+ var result = requiredValidator.Validate(validationContext);
+
+ // Assert
+ Assert.NotEqual(ValidationResult.Success, result);
+ Assert.Equal("This field is required.", result.ErrorMessage);
+ Assert.Equal("foo", result.MemberNames.Single());
+ }
+
+ [Fact]
+ public void RequiredValidatorReturnsCustomErrorMessagesIfSpecified()
+ {
+ // Arrange
+ RequestFieldValidatorBase.IgnoreUseUnvalidatedValues = true;
+ var requiredValidator = Validator.Required("There is no string");
+ var httpContext = GetContext(new { foo = "" });
+ var validationContext = GetValidationContext(httpContext, "foo");
+
+ // Act
+ var result = requiredValidator.Validate(validationContext);
+
+ // Assert
+ Assert.NotEqual(ValidationResult.Success, result);
+ Assert.Equal("There is no string", result.ErrorMessage);
+ Assert.Equal("foo", result.MemberNames.Single());
+ }
+
+ [Fact]
+ public void RequiredValidatorReturnsSuccessIfNoFieldIsNotNullOrEmpty()
+ {
+ // Arrange
+ RequestFieldValidatorBase.IgnoreUseUnvalidatedValues = true;
+ var requiredValidator = Validator.Required("foo");
+ var validationContext = GetValidationContext(GetContext(new { foo = "some value" }), "foo");
+
+ // Act
+ var result = requiredValidator.Validate(validationContext);
+
+ // Assert
+ Assert.Equal(ValidationResult.Success, result);
+ }
+
+ [Fact]
+ public void GetClientValidationRulesForRequiredValidatorWithDefaultErrorMessage()
+ {
+ // Arrange
+ RequestFieldValidatorBase.IgnoreUseUnvalidatedValues = true;
+ var requiredValidator = Validator.Required();
+
+ // Act
+ var result = requiredValidator.ClientValidationRule;
+
+ // Assert
+ Assert.Equal("required", result.ValidationType);
+ Assert.Equal("This field is required.", result.ErrorMessage);
+ Assert.False(result.ValidationParameters.Any());
+ }
+
+ [Fact]
+ public void GetClientValidationRulesForRequiredValidatorWithCustomErrorMessage()
+ {
+ // Arrange
+ RequestFieldValidatorBase.IgnoreUseUnvalidatedValues = true;
+ var requiredValidator = Validator.Required("custom message");
+
+ // Act
+ var result = requiredValidator.ClientValidationRule;
+
+ // Assert
+ Assert.Equal("required", result.ValidationType);
+ Assert.Equal("custom message", result.ErrorMessage);
+ Assert.False(result.ValidationParameters.Any());
+ }
+
+ [Fact]
+ public void RangeValidatorReturnsSuccessIfValueIsInRange()
+ {
+ // Arrange
+ RequestFieldValidatorBase.IgnoreUseUnvalidatedValues = true;
+ var rangeValidator = Validator.Range(10, 12);
+ var validationContext = GetValidationContext(GetContext(new { foo = 11 }), "foo");
+
+ // Act
+ var result = rangeValidator.Validate(validationContext);
+
+ // Assert
+ Assert.Equal(ValidationResult.Success, result);
+ }
+
+ [Fact]
+ public void RangeValidatorReturnsDefaultErrorMessageIfValueIsNotInRange()
+ {
+ // Arrange
+ RequestFieldValidatorBase.IgnoreUseUnvalidatedValues = true;
+ var rangeValidator = Validator.Range(10, 12);
+ var validationContext = GetValidationContext(GetContext(new { foo = 7 }), "foo");
+
+ // Act
+ var result = rangeValidator.Validate(validationContext);
+
+ // Assert
+ Assert.NotEqual(ValidationResult.Success, result);
+ Assert.Equal("Value must be an integer between 10 and 12.", result.ErrorMessage);
+ Assert.Equal("foo", result.MemberNames.Single());
+ }
+
+ [Fact]
+ public void RangeValidatorReturnsDefaultErrorMessageIfValueIsNotInRangeForFloatingPointValues()
+ {
+ // Arrange
+ RequestFieldValidatorBase.IgnoreUseUnvalidatedValues = true;
+ var rangeValidator = Validator.Range(10.8, 12.2);
+ var validationContext = GetValidationContext(GetContext(new { foo = 7 }), "foo");
+
+ // Act
+ var result = rangeValidator.Validate(validationContext);
+
+ // Assert
+ Assert.NotEqual(ValidationResult.Success, result);
+ Assert.Equal("Value must be a decimal between 10.8 and 12.2.", result.ErrorMessage);
+ Assert.Equal("foo", result.MemberNames.Single());
+ }
+
+ [Fact]
+ public void RangeValidatorReturnsCustomErrorMessageIfSpecified()
+ {
+ // Arrange
+ RequestFieldValidatorBase.IgnoreUseUnvalidatedValues = true;
+ var rangeValidator = Validator.Range(10, 12, "Custom error message");
+ var validationContext = GetValidationContext(GetContext(new { foo = 13 }), "foo");
+
+ // Act
+ var result = rangeValidator.Validate(validationContext);
+
+ // Assert
+ Assert.NotEqual(ValidationResult.Success, result);
+ Assert.Equal("Custom error message", result.ErrorMessage);
+ Assert.Equal("foo", result.MemberNames.Single());
+ }
+
+ [Fact]
+ public void RangeValidatorFormatsCustomErrorMessage()
+ {
+ // Arrange
+ RequestFieldValidatorBase.IgnoreUseUnvalidatedValues = true;
+ var rangeValidator = Validator.Range(10, 12, "Valid range: {0}-{1}");
+ var validationContext = GetValidationContext(GetContext(new { foo = 13 }), "foo");
+
+ // Act
+ var result = rangeValidator.Validate(validationContext);
+
+ // Assert
+ Assert.NotEqual(ValidationResult.Success, result);
+ Assert.Equal("Valid range: 10-12", result.ErrorMessage);
+ Assert.Equal("foo", result.MemberNames.Single());
+ }
+
+ [Fact]
+ public void GetClientValidationRulesForRangeValidatorWithDefaultErrorMessage()
+ {
+ // Arrange
+ RequestFieldValidatorBase.IgnoreUseUnvalidatedValues = true;
+ var rangeValidator = Validator.Range(10, 12);
+
+ // Act
+ var result = rangeValidator.ClientValidationRule;
+
+ // Assert
+ Assert.Equal("range", result.ValidationType);
+ Assert.Equal("Value must be an integer between 10 and 12.", result.ErrorMessage);
+ Assert.Equal(10, result.ValidationParameters["min"]);
+ Assert.Equal(12, result.ValidationParameters["max"]);
+ }
+
+ [Fact]
+ public void GetClientValidationRulesForRangeValidatorWithCustomErrorMessage()
+ {
+ // Arrange
+ RequestFieldValidatorBase.IgnoreUseUnvalidatedValues = true;
+ var rangeValidator = Validator.Range(10, 11, "Range: {0}-{1}");
+
+ // Act
+ var result = rangeValidator.ClientValidationRule;
+
+ // Assert
+ Assert.Equal("range", result.ValidationType);
+ Assert.Equal("Range: 10-11", result.ErrorMessage);
+ Assert.Equal(10, result.ValidationParameters["min"]);
+ Assert.Equal(11, result.ValidationParameters["max"]);
+ }
+
+ [Fact]
+ public void StringLengthValidatorReturnsSuccessIfStringLengthIsSmallerThanMaxValue()
+ {
+ // Arrange
+ RequestFieldValidatorBase.IgnoreUseUnvalidatedValues = true;
+ var validator = Validator.StringLength(10);
+ var validationContext = GetValidationContext(GetContext(new { baz = "hello" }), "baz");
+
+ // Act
+ var result = validator.Validate(validationContext);
+
+ // Assert
+ Assert.Equal(ValidationResult.Success, result);
+ }
+
+ [Fact]
+ public void StringLengthValidatorReturnsSuccessIfStringLengthIsRangeOfMinAndMaxValue()
+ {
+ // Arrange
+ RequestFieldValidatorBase.IgnoreUseUnvalidatedValues = true;
+ var validator = Validator.StringLength(10, minLength: 6);
+ var validationContext = GetValidationContext(GetContext(new { bar = "woof woof" }), "bar");
+
+ // Act
+ var result = validator.Validate(validationContext);
+
+ // Assert
+ Assert.Equal(ValidationResult.Success, result);
+ }
+
+ [Fact]
+ public void StringLengthValidatorReturnsFailureIfStringLengthIsLongerThanMaxValue()
+ {
+ // Arrange
+ RequestFieldValidatorBase.IgnoreUseUnvalidatedValues = true;
+ var validator = Validator.StringLength(4);
+ var validationContext = GetValidationContext(GetContext(new { baz = "woof woof" }), "baz");
+
+ // Act
+ var result = validator.Validate(validationContext);
+
+ // Assert
+ Assert.NotEqual(ValidationResult.Success, result);
+ Assert.Equal("Max length: 4.", result.ErrorMessage);
+ Assert.Equal("baz", result.MemberNames.Single());
+ }
+
+ [Fact]
+ public void StringLengthValidatorReturnsCustomErrorMessageIfStringLengthIsLongerThanMaxValue()
+ {
+ // Arrange
+ RequestFieldValidatorBase.IgnoreUseUnvalidatedValues = true;
+ var validator = Validator.StringLength(4, errorMessage: "String must be at least {0} characters long.");
+ var validationContext = GetValidationContext(GetContext(new { baz = "woof woof" }), "baz");
+
+ // Act
+ var result = validator.Validate(validationContext);
+
+ // Assert
+ Assert.NotEqual(ValidationResult.Success, result);
+ Assert.Equal("String must be at least 4 characters long.", result.ErrorMessage);
+ Assert.Equal("baz", result.MemberNames.Single());
+ }
+
+ [Fact]
+ public void StringLengthValidatorReturnsFailureIfStringLengthIsNotInRange()
+ {
+ // Arrange
+ RequestFieldValidatorBase.IgnoreUseUnvalidatedValues = true;
+ var validator = Validator.StringLength(6, 4);
+ var validationContext = GetValidationContext(GetContext(new { baz = "woof woof" }), "baz");
+
+ // Act
+ var result = validator.Validate(validationContext);
+
+ // Assert
+ Assert.NotEqual(ValidationResult.Success, result);
+ Assert.Equal("String must be between 4 and 6 characters.", result.ErrorMessage);
+ Assert.Equal("baz", result.MemberNames.Single());
+ }
+
+ [Fact]
+ public void StringLengthValidatorReturnsCustomErrorMessageIfStringLengthIsNotInRange()
+ {
+ // Arrange
+ RequestFieldValidatorBase.IgnoreUseUnvalidatedValues = true;
+ var validator = Validator.StringLength(6, 4, "Range {0} - {1}");
+ var validationContext = GetValidationContext(GetContext(new { baz = "woof woof" }), "baz");
+
+ // Act
+ var result = validator.Validate(validationContext);
+
+ // Assert
+ Assert.NotEqual(ValidationResult.Success, result);
+ Assert.Equal("Range 4 - 6", result.ErrorMessage);
+ Assert.Equal("baz", result.MemberNames.Single());
+ }
+
+ [Fact]
+ public void GetClientValidationRulesForStringLengthValidatorWithDefaultErrorMessage()
+ {
+ // Arrange
+ RequestFieldValidatorBase.IgnoreUseUnvalidatedValues = true;
+ var validator = Validator.StringLength(6, 4);
+
+ // Act
+ var result = validator.ClientValidationRule;
+
+ // Assert
+ Assert.Equal(result.ValidationType, "length");
+ Assert.Equal(result.ErrorMessage, "String must be between 4 and 6 characters.");
+ Assert.Equal(result.ValidationParameters["min"], 4);
+ Assert.Equal(result.ValidationParameters["max"], 6);
+ }
+
+ [Fact]
+ public void GetClientValidationRulesForStringLengthValidatorWithCustomErrorMessage()
+ {
+ // Arrange
+ RequestFieldValidatorBase.IgnoreUseUnvalidatedValues = true;
+ var validator = Validator.StringLength(6, errorMessage: "Must be at least 6 letters.");
+
+ // Act
+ var result = validator.ClientValidationRule;
+
+ // Assert
+ Assert.Equal(result.ValidationType, "length");
+ Assert.Equal(result.ErrorMessage, "Must be at least 6 letters.");
+ Assert.Equal(result.ValidationParameters["max"], 6);
+ }
+
+ [Fact]
+ public void RegexThrowsIfPatternIsNullOrEmpty()
+ {
+ // Act and Assert
+ Assert.ThrowsArgumentNullOrEmptyString(() => Validator.Regex(null), "pattern");
+ Assert.ThrowsArgumentNullOrEmptyString(() => Validator.Regex(String.Empty), "pattern");
+ }
+
+ [Fact]
+ public void RegexReturnsSuccessIfValueMatches()
+ {
+ // Arrange
+ RequestFieldValidatorBase.IgnoreUseUnvalidatedValues = true;
+ var validator = Validator.Regex("^a+b+c$");
+ var context = GetValidationContext(GetContext(new { foo = "aaabbc" }), "foo");
+
+ // Act
+ var result = validator.Validate(context);
+
+ // Assert
+ Assert.Equal(ValidationResult.Success, result);
+ }
+
+ [Fact]
+ public void RegexReturnsDefaultErrorMessageIfValidationFails()
+ {
+ // Arrange
+ RequestFieldValidatorBase.IgnoreUseUnvalidatedValues = true;
+ var validator = Validator.Regex("^a+b+c$");
+ var context = GetValidationContext(GetContext(new { foo = "aaXabbc" }), "foo");
+
+ // Act
+ var result = validator.Validate(context);
+
+ // Assert
+ Assert.NotEqual(ValidationResult.Success, result);
+ Assert.Equal("Value is invalid.", result.ErrorMessage);
+ Assert.Equal("foo", result.MemberNames.Single());
+ }
+
+ [Fact]
+ public void RegexReturnsCustomErrorMessageIfValidationFails()
+ {
+ // Arrange
+ RequestFieldValidatorBase.IgnoreUseUnvalidatedValues = true;
+ var validator = Validator.Regex("^a+b+c$");
+ var context = GetValidationContext(GetContext(new { foo = "aaXabbc" }), "foo");
+
+ // Act
+ var result = validator.Validate(context);
+
+ // Assert
+ Assert.NotEqual(ValidationResult.Success, result);
+ Assert.Equal("Value is invalid.", result.ErrorMessage);
+ Assert.Equal("foo", result.MemberNames.Single());
+ }
+
+ [Fact]
+ public void IntegerReturnsSuccessIfValueIsNull()
+ {
+ // Arrange
+ RequestFieldValidatorBase.IgnoreUseUnvalidatedValues = true;
+ var validator = Validator.Integer();
+ var context = GetValidationContext(GetContext(new { Name = "Not-Age" }), "age");
+
+ // Act
+ var result = validator.Validate(context);
+
+ // Assert
+ Assert.Equal(ValidationResult.Success, result);
+ }
+
+ [Fact]
+ public void IntegerReturnsSuccessIfValueIsEmpty()
+ {
+ // Arrange
+ RequestFieldValidatorBase.IgnoreUseUnvalidatedValues = true;
+ var validator = Validator.Integer();
+ var context = GetValidationContext(GetContext(new { Age = "" }), "age");
+
+ // Act
+ var result = validator.Validate(context);
+
+ // Assert
+ Assert.Equal(ValidationResult.Success, result);
+ }
+
+ [Fact]
+ public void IntegerReturnsSuccessIfValueIsValidInteger()
+ {
+ // Arrange
+ RequestFieldValidatorBase.IgnoreUseUnvalidatedValues = true;
+ var validator = Validator.Integer();
+ var context = GetValidationContext(GetContext(new { Age = "10" }), "age");
+
+ // Act
+ var result = validator.Validate(context);
+
+ // Assert
+ Assert.Equal(ValidationResult.Success, result);
+ }
+
+ [Fact]
+ public void IntegerReturnsSuccessIfValueIsNegativeInteger()
+ {
+ // Arrange
+ RequestFieldValidatorBase.IgnoreUseUnvalidatedValues = true;
+ var validator = Validator.Integer();
+ var context = GetValidationContext(GetContext(new { Age = "-42" }), "age");
+
+ // Act
+ var result = validator.Validate(context);
+
+ // Assert
+ Assert.Equal(ValidationResult.Success, result);
+ }
+
+ [Fact]
+ public void IntegerReturnsSuccessIfValueIsZero()
+ {
+ // Arrange
+ RequestFieldValidatorBase.IgnoreUseUnvalidatedValues = true;
+ var validator = Validator.Integer();
+ var context = GetValidationContext(GetContext(new { Age = 0 }), "age");
+
+ // Act
+ var result = validator.Validate(context);
+
+ // Assert
+ Assert.Equal(ValidationResult.Success, result);
+ }
+
+ [Fact]
+ public void IntegerReturnsErrorMessageIfValueIsFloatingPointValue()
+ {
+ // Arrange
+ RequestFieldValidatorBase.IgnoreUseUnvalidatedValues = true;
+ var validator = Validator.Integer();
+ var context = GetValidationContext(GetContext(new { Age = 1.3 }), "age");
+
+ // Act
+ var result = validator.Validate(context);
+
+ // Assert
+ Assert.NotEqual(ValidationResult.Success, result);
+ Assert.Equal("Input format is invalid.", result.ErrorMessage);
+ Assert.Equal("age", result.MemberNames.Single());
+ }
+
+ [Fact]
+ public void IntegerReturnsErrorMessageIfValueIsNotAnInteger()
+ {
+ // Arrange
+ RequestFieldValidatorBase.IgnoreUseUnvalidatedValues = true;
+ var validator = Validator.Integer();
+ var context = GetValidationContext(GetContext(new { Age = "2008-04-01" }), "age");
+
+ // Act
+ var result = validator.Validate(context);
+
+ // Assert
+ Assert.NotEqual(ValidationResult.Success, result);
+ Assert.Equal("Input format is invalid.", result.ErrorMessage);
+ Assert.Equal("age", result.MemberNames.Single());
+ }
+
+ [Fact]
+ public void FloatReturnsSuccessIfValueIsNull()
+ {
+ // Arrange
+ RequestFieldValidatorBase.IgnoreUseUnvalidatedValues = true;
+ var validator = Validator.Float();
+ var context = GetValidationContext(GetContext(new { }), "Price");
+
+ // Act
+ var result = validator.Validate(context);
+
+ // Assert
+ Assert.Equal(ValidationResult.Success, result);
+ }
+
+ [Fact]
+ public void FloatReturnsSuccessIfValueIsEmptyString()
+ {
+ // Arrange
+ RequestFieldValidatorBase.IgnoreUseUnvalidatedValues = true;
+ var validator = Validator.Float();
+ var context = GetValidationContext(GetContext(new { Price = "" }), "Price");
+
+ // Act
+ var result = validator.Validate(context);
+
+ // Assert
+ Assert.Equal(ValidationResult.Success, result);
+ }
+
+ [Fact]
+ public void FloatReturnsSuccessIfValueIsValidFloatString()
+ {
+ // Arrange
+ RequestFieldValidatorBase.IgnoreUseUnvalidatedValues = true;
+ var validator = Validator.Float();
+ var context = GetValidationContext(GetContext(new { Price = Single.MaxValue.ToString() }), "Price");
+
+ // Act
+ var result = validator.Validate(context);
+
+ // Assert
+ Assert.Equal(ValidationResult.Success, result);
+ }
+
+ [Fact]
+ public void FloatReturnsSuccessIfValueIsValidInteger()
+ {
+ // Arrange
+ RequestFieldValidatorBase.IgnoreUseUnvalidatedValues = true;
+ var validator = Validator.Float();
+ var context = GetValidationContext(GetContext(new { Price = "1" }), "Price");
+
+ // Act
+ var result = validator.Validate(context);
+
+ // Assert
+ Assert.Equal(ValidationResult.Success, result);
+ }
+
+ [Fact]
+ public void FloatReturnsErrorIfValueIsNotAValidFloat()
+ {
+ // Arrange
+ RequestFieldValidatorBase.IgnoreUseUnvalidatedValues = true;
+ var validator = Validator.Float();
+ var context = GetValidationContext(GetContext(new { Price = "Free!!!" }), "Price");
+
+ // Act
+ var result = validator.Validate(context);
+
+ // Assert
+ Assert.NotEqual(ValidationResult.Success, result);
+ Assert.Equal("Input format is invalid.", result.ErrorMessage);
+ Assert.Equal("Price", result.MemberNames.Single());
+ }
+
+ [Fact]
+ public void DateTimeReturnsSuccessIfValueIsNull()
+ {
+ // Arrange
+ RequestFieldValidatorBase.IgnoreUseUnvalidatedValues = true;
+ var validator = Validator.DateTime();
+ var context = GetValidationContext(GetContext(new { }), "dateOfBirth");
+
+ // Act
+ var result = validator.Validate(context);
+
+ // Assert
+ Assert.Equal(ValidationResult.Success, result);
+ }
+
+ [Fact]
+ public void DateTimeReturnsSuccessIfValueIsEmpty()
+ {
+ // Arrange
+ RequestFieldValidatorBase.IgnoreUseUnvalidatedValues = true;
+ var validator = Validator.DateTime();
+ var context = GetValidationContext(GetContext(new { dateOfBirth = "" }), "dateOfBirth");
+
+ // Act
+ var result = validator.Validate(context);
+
+ // Assert
+ Assert.Equal(ValidationResult.Success, result);
+ }
+
+ [Fact]
+ public void DateTimeReturnsSuccessIfValueIsValidDateTime()
+ {
+ // Arrange
+ RequestFieldValidatorBase.IgnoreUseUnvalidatedValues = true;
+ var validator = Validator.DateTime();
+ var context = GetValidationContext(GetContext(new { dateOfBirth = DateTime.Now.ToString() }), "dateOfBirth");
+
+ // Act
+ var result = validator.Validate(context);
+
+ // Assert
+ Assert.Equal(ValidationResult.Success, result);
+ }
+
+ [Fact]
+ public void DateTimeReturnsErrorIfValueIsInvalidDateTime()
+ {
+ // Arrange
+ RequestFieldValidatorBase.IgnoreUseUnvalidatedValues = true;
+ var validator = Validator.DateTime();
+ var context = GetValidationContext(GetContext(new { dateOfBirth = "23.28" }), "dateOfBirth");
+
+ // Act
+ var result = validator.Validate(context);
+
+ // Assert
+ Assert.NotEqual(ValidationResult.Success, result);
+ Assert.Equal("Input format is invalid.", result.ErrorMessage);
+ Assert.Equal("dateOfBirth", result.MemberNames.Single());
+ }
+
+ [Fact]
+ public void UrlReturnsSuccessIfInputIsNull()
+ {
+ // Arrange
+ RequestFieldValidatorBase.IgnoreUseUnvalidatedValues = true;
+ var validator = Validator.Url();
+ var context = GetValidationContext(GetContext(new { }), "blogUrl");
+
+ // Act
+ var result = validator.Validate(context);
+
+ // Assert
+ Assert.Equal(ValidationResult.Success, result);
+ }
+
+ [Fact]
+ public void UrlReturnsSuccessIfInputIsEmptyString()
+ {
+ // Arrange
+ RequestFieldValidatorBase.IgnoreUseUnvalidatedValues = true;
+ var validator = Validator.Url();
+ var context = GetValidationContext(GetContext(new { blogUrl = "" }), "blogUrl");
+
+ // Act
+ var result = validator.Validate(context);
+
+ // Assert
+ Assert.Equal(ValidationResult.Success, result);
+ }
+
+ [Fact]
+ public void UrlReturnsSuccessIfInputIsValidUrl()
+ {
+ // Arrange
+ RequestFieldValidatorBase.IgnoreUseUnvalidatedValues = true;
+ var validator = Validator.Url();
+ var context = GetValidationContext(GetContext(new { blogUrl = "http://www.microsoft.com?query-param=query-param-value&some-val=&quot;true&quot;" }), "blogUrl");
+
+ // Act
+ var result = validator.Validate(context);
+
+ // Assert
+ Assert.Equal(ValidationResult.Success, result);
+ }
+
+ [Fact]
+ public void UrlReturnsErrorMessageIfInputIsPhysicalPath()
+ {
+ // Arrange
+ RequestFieldValidatorBase.IgnoreUseUnvalidatedValues = true;
+ var validator = Validator.Url();
+ var context = GetValidationContext(GetContext(new { blogUrl = @"x:\some-path\foo.txt" }), "blogUrl");
+
+ // Act
+ var result = validator.Validate(context);
+
+ // Assert
+ Assert.NotEqual(ValidationResult.Success, result);
+ Assert.Equal("Input format is invalid.", result.ErrorMessage);
+ Assert.Equal("blogUrl", result.MemberNames.Single());
+ }
+
+ [Fact]
+ public void UrlReturnsErrorMessageIfInputIsNetworkPath()
+ {
+ // Arrange
+ RequestFieldValidatorBase.IgnoreUseUnvalidatedValues = true;
+ var validator = Validator.Url();
+ var context = GetValidationContext(GetContext(new { blogUrl = @"\\network-share\some-path\" }), "blogUrl");
+
+ // Act
+ var result = validator.Validate(context);
+
+ // Assert
+ Assert.NotEqual(ValidationResult.Success, result);
+ Assert.Equal("Input format is invalid.", result.ErrorMessage);
+ Assert.Equal("blogUrl", result.MemberNames.Single());
+ }
+
+ [Fact]
+ public void UrlReturnsErrorMessageIfInputIsNotAnUrl()
+ {
+ // Arrange
+ RequestFieldValidatorBase.IgnoreUseUnvalidatedValues = true;
+ var validator = Validator.Url();
+ var context = GetValidationContext(GetContext(new { blogUrl = 65 }), "blogUrl");
+
+ // Act
+ var result = validator.Validate(context);
+
+ // Assert
+ Assert.NotEqual(ValidationResult.Success, result);
+ Assert.Equal("Input format is invalid.", result.ErrorMessage);
+ Assert.Equal("blogUrl", result.MemberNames.Single());
+ }
+
+ [Fact]
+ public void EqualsToValidatorThrowsIfFieldIsNullOrEmpty()
+ {
+ // Act and Assert
+ Assert.ThrowsArgumentNullOrEmptyString(() => Validator.EqualsTo(null), "otherFieldName");
+ }
+
+ [Fact]
+ public void EqualsToValidatorReturnsFalseIfEitherFieldIsEmpty()
+ {
+ // Arrange
+ RequestFieldValidatorBase.IgnoreUseUnvalidatedValues = true;
+ var validator = Validator.EqualsTo("password");
+ var context = GetValidationContext(GetContext(new { password = "", confirmPassword = "abcd" }), "confirmPassword");
+
+ // Act
+ var result = validator.Validate(context);
+
+ // Assert
+ Assert.NotEqual(ValidationResult.Success, result);
+ Assert.Equal("Values do not match.", result.ErrorMessage);
+ Assert.Equal("confirmPassword", result.MemberNames.Single());
+
+ context = GetValidationContext(GetContext(new { password = "abcd", confirmPassword = "" }), "confirmPassword");
+
+ // Act
+ result = validator.Validate(context);
+
+ // Assert
+ Assert.NotEqual(ValidationResult.Success, result);
+ Assert.Equal("Values do not match.", result.ErrorMessage);
+ Assert.Equal("confirmPassword", result.MemberNames.Single());
+ }
+
+ [Fact]
+ public void EqualsToValidatorReturnsFalseIfValuesDoNotMatch()
+ {
+ // Arrange
+ RequestFieldValidatorBase.IgnoreUseUnvalidatedValues = true;
+ var validator = Validator.EqualsTo("password");
+ var context = GetValidationContext(GetContext(new { password = "password2", confirmPassword = "abcd" }), "confirmPassword");
+
+ // Act
+ var result = validator.Validate(context);
+
+ // Assert
+ Assert.NotEqual(ValidationResult.Success, result);
+ Assert.Equal("Values do not match.", result.ErrorMessage);
+ Assert.Equal("confirmPassword", result.MemberNames.Single());
+ }
+
+ [Fact]
+ public void EqualsToValidatorReturnsTrueIfValuesMatch()
+ {
+ // Arrange
+ RequestFieldValidatorBase.IgnoreUseUnvalidatedValues = true;
+ var validator = Validator.EqualsTo("password");
+ var context = GetValidationContext(GetContext(new { password = "abcd", confirmPassword = "abcd" }), "confirmPassword");
+
+ // Act
+ var result = validator.Validate(context);
+
+ // Assert
+ Assert.Equal(ValidationResult.Success, result);
+ }
+
+ [Fact]
+ public void GetClientValidationRulesForRegex()
+ {
+ // Arrange
+ RequestFieldValidatorBase.IgnoreUseUnvalidatedValues = true;
+ var validator = Validator.Regex("^a+b+c$");
+
+ // Act
+ var result = validator.ClientValidationRule;
+
+ // Assert
+ Assert.Equal("regex", result.ValidationType);
+ Assert.Equal("Value is invalid.", result.ErrorMessage);
+ Assert.Equal("^a+b+c$", result.ValidationParameters["pattern"]);
+ }
+
+ [Fact]
+ public void GetClientValidationRulesForRegexWithCustomErrorMessage()
+ {
+ // Arrange
+ RequestFieldValidatorBase.IgnoreUseUnvalidatedValues = true;
+ var validator = Validator.Regex("^a+b+c$", "Example aaabbc");
+
+ // Act
+ var result = validator.ClientValidationRule;
+
+ // Assert
+ Assert.Equal("regex", result.ValidationType);
+ Assert.Equal("Example aaabbc", result.ErrorMessage);
+ Assert.Equal("^a+b+c$", result.ValidationParameters["pattern"]);
+ }
+
+ private static HttpContextBase GetContext(object formValues = null)
+ {
+ var context = new Mock<HttpContextBase>();
+ var request = new Mock<HttpRequestBase>();
+
+ var nameValueCollection = new NameValueCollection();
+ if (formValues != null)
+ {
+ foreach (var prop in formValues.GetType().GetProperties())
+ {
+ nameValueCollection.Add(prop.Name, prop.GetValue(formValues, null).ToString());
+ }
+ }
+ request.SetupGet(c => c.Form).Returns(nameValueCollection);
+ context.SetupGet(c => c.Request).Returns(request.Object);
+
+ return context.Object;
+ }
+
+ private static ValidationContext GetValidationContext(HttpContextBase httpContext, string memberName)
+ {
+ return new ValidationContext(httpContext, null, null) { MemberName = memberName };
+ }
+ }
+}
diff --git a/test/System.Web.WebPages.Test/WebPage/ApplicationStartPageTest.cs b/test/System.Web.WebPages.Test/WebPage/ApplicationStartPageTest.cs
new file mode 100644
index 00000000..89729996
--- /dev/null
+++ b/test/System.Web.WebPages.Test/WebPage/ApplicationStartPageTest.cs
@@ -0,0 +1,166 @@
+using System.IO;
+using System.Reflection;
+using System.Text;
+using System.Web.WebPages.TestUtils;
+using Moq;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.WebPages.Test
+{
+ public class ApplicationStartPageTest
+ {
+ [Fact]
+ public void StartPageBasicTest()
+ {
+ AppDomainUtils.RunInSeparateAppDomain(() =>
+ {
+ var page = new ApplicationStartPageTest().CreateStartPage(p =>
+ {
+ p.AppState["x"] = "y";
+ p.WriteLiteral("test");
+ });
+ page.ExecuteInternal();
+ Assert.Equal("y", page.ApplicationState["x"]);
+ Assert.Equal("test", ApplicationStartPage.Markup.ToHtmlString());
+ });
+ }
+
+ [Fact]
+ public void StartPageDynamicAppStateBasicTest()
+ {
+ AppDomainUtils.RunInSeparateAppDomain(() =>
+ {
+ var page = new ApplicationStartPageTest().CreateStartPage(p =>
+ {
+ p.App.x = "y";
+ p.WriteLiteral("test");
+ });
+ page.ExecuteInternal();
+ Assert.Equal("y", page.ApplicationState["x"]);
+ Assert.Equal("y", page.App["x"]);
+ Assert.Equal("y", page.App.x);
+ Assert.Equal("test", ApplicationStartPage.Markup.ToHtmlString());
+ });
+ }
+
+ [Fact]
+ public void ExceptionTest()
+ {
+ AppDomainUtils.RunInSeparateAppDomain(() =>
+ {
+ var msg = "This is an error message";
+ var e = new InvalidOperationException(msg);
+ var page = new ApplicationStartPageTest().CreateStartPage(p => { throw e; });
+ var ex = Assert.Throws<HttpException>(() => page.ExecuteStartPage());
+ Assert.Equal(msg, ex.InnerException.Message);
+ Assert.Equal(e, ApplicationStartPage.Exception);
+ });
+ }
+
+ [Fact]
+ public void HtmlEncodeTest()
+ {
+ AppDomainUtils.RunInSeparateAppDomain(() =>
+ {
+ // Set HideRequestResponse to true to simulate the condition in IIS 7/7.5
+ var context = new HttpContext(new HttpRequest("default.cshtml", "http://localhost/default.cshtml", null), new HttpResponse(new StringWriter(new StringBuilder())));
+ var hideRequestResponse = typeof(HttpContext).GetField("HideRequestResponse", BindingFlags.NonPublic | BindingFlags.Instance);
+ hideRequestResponse.SetValue(context, true);
+
+ HttpContext.Current = context;
+ var page = new ApplicationStartPageTest().CreateStartPage(p => { p.Write("test"); });
+ page.ExecuteStartPage();
+ });
+ }
+
+ [Fact]
+ public void GetVirtualPathTest()
+ {
+ var page = new MockStartPage();
+ Assert.Equal(ApplicationStartPage.StartPageVirtualPath, page.VirtualPath);
+ }
+
+ [Fact]
+ public void SetVirtualPathTest()
+ {
+ var page = new MockStartPage();
+ Assert.Throws<NotSupportedException>(() => { page.VirtualPath = "~/hello.cshtml"; });
+ }
+
+ [Fact]
+ public void ExecuteStartPageTest()
+ {
+ AppDomainUtils.RunInSeparateAppDomain(() =>
+ {
+ var startPage = new MockStartPage() { ExecuteAction = p => p.AppState["x"] = "y" };
+ var objectFactory = GetMockVirtualPathFactory(startPage);
+ ApplicationStartPage.ExecuteStartPage(new WebPageHttpApplication(),
+ p => { },
+ objectFactory,
+ new string[] { "cshtml", "vbhtml" });
+ Assert.Equal("y", startPage.ApplicationState["x"]);
+ });
+ }
+
+ [Fact]
+ public void ExecuteStartPageDynamicAppStateTest()
+ {
+ AppDomainUtils.RunInSeparateAppDomain(() =>
+ {
+ var startPage = new MockStartPage() { ExecuteAction = p => p.App.x = "y" };
+ var objectFactory = GetMockVirtualPathFactory(startPage);
+ ApplicationStartPage.ExecuteStartPage(new WebPageHttpApplication(),
+ p => { },
+ objectFactory,
+ new string[] { "cshtml", "vbhtml" });
+ Assert.Equal("y", startPage.ApplicationState["x"]);
+ Assert.Equal("y", startPage.App.x);
+ Assert.Equal("y", startPage.App["x"]);
+ });
+ }
+
+ public class MockStartPage : ApplicationStartPage
+ {
+ public Action<ApplicationStartPage> ExecuteAction { get; set; }
+ public HttpApplicationStateBase ApplicationState = new HttpApplicationStateWrapper(Activator.CreateInstance(typeof(HttpApplicationState), true) as HttpApplicationState);
+
+ public override void Execute()
+ {
+ ExecuteAction(this);
+ }
+
+ public override HttpApplicationStateBase AppState
+ {
+ get { return ApplicationState; }
+ }
+
+ public void ExecuteStartPage()
+ {
+ ExecuteStartPage(new WebPageHttpApplication(),
+ p => { },
+ GetMockVirtualPathFactory(this),
+ new string[] { "cshtml", "vbhtml" });
+ }
+ }
+
+ public MockStartPage CreateStartPage(Action<ApplicationStartPage> action)
+ {
+ var startPage = new MockStartPage() { ExecuteAction = action };
+ return startPage;
+ }
+
+ public sealed class WebPageHttpApplication : HttpApplication
+ {
+ }
+
+ private static IVirtualPathFactory GetMockVirtualPathFactory(ApplicationStartPage page)
+ {
+ var mockFactory = new Mock<IVirtualPathFactory>();
+ mockFactory.Setup(c => c.Exists(It.IsAny<string>())).Returns<string>(_ => true);
+ mockFactory.Setup(c => c.CreateInstance(It.IsAny<string>())).Returns<string>(_ => page);
+
+ return mockFactory.Object;
+ }
+ }
+}
diff --git a/test/System.Web.WebPages.Test/WebPage/BrowserHelpersTest.cs b/test/System.Web.WebPages.Test/WebPage/BrowserHelpersTest.cs
new file mode 100644
index 00000000..9ee6e998
--- /dev/null
+++ b/test/System.Web.WebPages.Test/WebPage/BrowserHelpersTest.cs
@@ -0,0 +1,285 @@
+using System.Collections.Generic;
+using System.Collections.Specialized;
+using System.Web.Configuration;
+using Moq;
+using Xunit;
+
+namespace System.Web.WebPages.Test
+{
+ public class BrowserHelpersTest
+ {
+ [Fact]
+ public void GetOverriddenUserAgentGetsUserAgentFromHttpContext()
+ {
+ // Arrange
+ Mock<HttpContextBase> context = CookieBrowserOverrideStoreTest.CreateCookieContext();
+ string testUserAgent = "testUserAgent";
+
+ // Act
+ context.Object.SetOverriddenBrowser(testUserAgent);
+ Assert.Equal(testUserAgent, context.Object.GetOverriddenUserAgent());
+ context.Object.Response.Cookies.Clear();
+ context.Object.Request.Cookies.Clear();
+
+ // Assert
+ Assert.Equal(testUserAgent, context.Object.GetOverriddenUserAgent());
+ }
+
+ [Fact]
+ public void GetOverriddenUserAgentFallsBackToStoreUserAgent()
+ {
+ // Arrange
+ string testUserAgent = "testUserAgent";
+ HttpCookie existingOverrideCookie = new HttpCookie(CookieBrowserOverrideStore.BrowserOverrideCookieName, testUserAgent);
+ HttpContextBase context = CookieBrowserOverrideStoreTest.CreateCookieContext(requestCookie: existingOverrideCookie).Object;
+
+ // Act & Assert
+ Assert.Equal(testUserAgent, context.GetOverriddenUserAgent());
+ }
+
+ [Fact]
+ public void GetOverriddenUserAgentDefaultsToRequestUserAgent()
+ {
+ // Arrange
+ Mock<HttpContextBase> context = CookieBrowserOverrideStoreTest.CreateCookieContext();
+ context.Setup(c => c.Request.UserAgent).Returns("requestUserAgent");
+
+ // Act & Assert
+ Assert.Equal("requestUserAgent", context.Object.GetOverriddenUserAgent());
+ }
+
+ [Fact]
+ public void SetOverriddenBrowserWithBrowserOverrideSetBrowserMobile()
+ {
+ // Arrange
+ Mock<HttpContextBase> context = CookieBrowserOverrideStoreTest.CreateCookieContext();
+ context.Setup(c => c.Request.Browser.IsMobileDevice).Returns(false);
+
+ // Act
+ context.Object.SetOverriddenBrowser(BrowserOverride.Mobile);
+
+ // Assert
+ Assert.True(context.Object.GetOverriddenBrowser(CreateBrowserThroughFactory).IsMobileDevice);
+ }
+
+ [Fact]
+ public void SetOverriddenBrowserWithUnsupportedBrowserOverrideClearsBrowser()
+ {
+ // Arrange
+ Mock<HttpContextBase> context = CookieBrowserOverrideStoreTest.CreateCookieContext();
+ Mock<HttpBrowserCapabilitiesBase> requestBrowser = new Mock<HttpBrowserCapabilitiesBase>();
+ context.Setup(c => c.Request.Browser).Returns(requestBrowser.Object);
+
+ // Act & Assert
+ context.Object.SetOverriddenBrowser(BrowserOverride.Mobile);
+ Assert.NotSame(requestBrowser.Object, context.Object.GetOverriddenBrowser(CreateBrowserThroughFactory));
+
+ context.Object.SetOverriddenBrowser((BrowserOverride)(-1));
+ Assert.Same(requestBrowser.Object, context.Object.GetOverriddenBrowser(CreateBrowserThroughFactory));
+ }
+
+ [Fact]
+ public void SetOverriddenBrowserWithBrowserOverrideSetBrowserDesktop()
+ {
+ // Arrange
+ Mock<HttpContextBase> context = CookieBrowserOverrideStoreTest.CreateCookieContext();
+ context.Setup(c => c.Request.Browser.IsMobileDevice).Returns(true);
+
+ // Act
+ context.Object.SetOverriddenBrowser(BrowserOverride.Desktop);
+
+ // Assert
+ Assert.False(context.Object.GetOverriddenBrowser(CreateBrowserThroughFactory).IsMobileDevice);
+ }
+
+ [Fact]
+ public void SetOverriddenBrowserWithStringOverrideSetBrowser()
+ {
+ // Arrange
+ Mock<HttpContextBase> context = CookieBrowserOverrideStoreTest.CreateCookieContext();
+ context.Setup(c => c.Request.Browser.IsMobileDevice).Returns(false);
+
+ // Act
+ context.Object.SetOverriddenBrowser("Mozilla/5.0 (iPhone; U; CPU iPhone OS 3_0 like Mac OS X; en-us) AppleWebKit/528.18 (KHTML, like Gecko) Version/4.0 Mobile/7A341 Safari/528.16");
+
+ // Assert
+ Assert.True(context.Object.GetOverriddenBrowser(CreateBrowserThroughFactory).IsMobileDevice);
+ }
+
+ [Fact]
+ public void SetOverriddenBrowserClearsCachedBrowser()
+ {
+ // Arrange
+ Mock<HttpContextBase> context = CookieBrowserOverrideStoreTest.CreateCookieContext();
+ context.Setup(c => c.Request.UserAgent).Returns("testUserAgent");
+
+ // Act
+ context.Object.SetOverriddenBrowser(BrowserOverride.Mobile);
+ context.Object.GetOverriddenBrowser(CreateBrowserThroughFactory);
+
+ // If the browser is generated this will throw an exception because we are going through the provider.
+ // We must be getting the cached overridden browser.
+ context.Object.GetOverriddenBrowser();
+ context.Object.SetOverriddenBrowser("testUserAgent");
+
+ // Assert
+
+ // The browser has been cleared from HttpContext and the user agent has been set to the original user agent.
+ // Otherwise we will either get the cached browser or an exception when trying to generate the browser from the
+ // mobile user agent.
+ Assert.Null(context.Object.GetOverriddenBrowser());
+ }
+
+ [Fact]
+ public void SetOverridenBrowserInSameOverrideClassClearsOverridenBrowser()
+ {
+ // Arrange
+ Mock<HttpContextBase> context = CookieBrowserOverrideStoreTest.CreateCookieContext();
+ context.Setup(c => c.Request.Browser.IsMobileDevice).Returns(true);
+ context.Setup(c => c.Request.UserAgent).Returns("sampleUserAgent");
+
+ // Act
+ context.Object.SetOverriddenBrowser(BrowserOverride.Desktop);
+ context.Object.SetOverriddenBrowser(BrowserOverride.Mobile);
+
+ // Assert
+ Assert.Equal("sampleUserAgent", context.Object.GetOverriddenUserAgent());
+ }
+
+ [Fact]
+ public void GetOverriddenBrowserGetsBrowserFromHttpContext()
+ {
+ // Arrange
+ Mock<HttpContextBase> context = CookieBrowserOverrideStoreTest.CreateCookieContext();
+
+ // Act
+ context.Object.SetOverriddenBrowser(BrowserOverride.Mobile);
+ context.Object.GetOverriddenBrowser(CreateBrowserThroughFactory);
+
+ // Assert
+
+ // If the browser is generated this will throw an exception because we are going through the provider.
+ // We must be getting the cached overridden browser.
+ Assert.True(context.Object.GetOverriddenBrowser().IsMobileDevice);
+ }
+
+ [Fact]
+ public void GetOverriddenBrowserWithStoredBrowserAndNoBrowserInContext()
+ {
+ // Arrange
+ string mobileUserAgent = "Mozilla/5.0 (iPhone; U; CPU iPhone OS 3_0 like Mac OS X; en-us) AppleWebKit/528.18 (KHTML, like Gecko) Version/4.0 Mobile/7A341 Safari/528.16";
+ HttpCookie existingOverrideCookie = new HttpCookie(CookieBrowserOverrideStore.BrowserOverrideCookieName, mobileUserAgent);
+ HttpContextBase context = CookieBrowserOverrideStoreTest.CreateCookieContext(requestCookie: existingOverrideCookie).Object;
+
+ // Act & Assert
+ Assert.True(context.GetOverriddenBrowser(CreateBrowserThroughFactory).IsMobileDevice);
+ }
+
+ [Fact]
+ public void GetOverriddenBrowserDefaultsToRequestBrowser()
+ {
+ // Arrange
+ Mock<HttpContextBase> context = CookieBrowserOverrideStoreTest.CreateCookieContext();
+ Mock<HttpBrowserCapabilitiesBase> currentBrowser = new Mock<HttpBrowserCapabilitiesBase>();
+ context.Setup(c => c.Request.Browser).Returns(currentBrowser.Object);
+
+ // Act & Assert
+ Assert.Same(currentBrowser.Object, context.Object.GetOverriddenBrowser());
+ }
+
+ [Fact]
+ public void ClearOverriddenBrowserClearsSetBrowser()
+ {
+ // Arrange
+ Mock<HttpContextBase> context = CookieBrowserOverrideStoreTest.CreateCookieContext();
+ Mock<HttpBrowserCapabilitiesBase> requestBrowser = new Mock<HttpBrowserCapabilitiesBase>();
+ context.Setup(c => c.Request.Browser).Returns(requestBrowser.Object);
+
+ // Act & Assert
+ context.Object.SetOverriddenBrowser(BrowserOverride.Mobile);
+ Assert.NotSame(requestBrowser.Object, context.Object.GetOverriddenBrowser(CreateBrowserThroughFactory));
+
+ context.Object.ClearOverriddenBrowser();
+ Assert.Same(requestBrowser.Object, context.Object.GetOverriddenBrowser(CreateBrowserThroughFactory));
+ }
+
+ [Fact]
+ public void ClearOverriddenBrowserWithNoSetBrowser()
+ {
+ // Arrange
+ Mock<HttpContextBase> context = CookieBrowserOverrideStoreTest.CreateCookieContext();
+ Mock<HttpBrowserCapabilitiesBase> requestBrowser = new Mock<HttpBrowserCapabilitiesBase>();
+ context.Setup(c => c.Request.Browser).Returns(requestBrowser.Object);
+
+ // Act & Assert
+ context.Object.ClearOverriddenBrowser();
+ Assert.Same(requestBrowser.Object, context.Object.GetOverriddenBrowser(CreateBrowserThroughFactory));
+ }
+
+ [Fact]
+ public void GetVaryByCustomStringVariesBySetOverriddenBrowserMobile()
+ {
+ // Arrange
+ Mock<HttpContextBase> context = CookieBrowserOverrideStoreTest.CreateCookieContext();
+ Mock<HttpBrowserCapabilitiesBase> currentBrowser = new Mock<HttpBrowserCapabilitiesBase>();
+ currentBrowser.Setup(c => c.IsMobileDevice).Returns(true);
+ context.Setup(c => c.Request.Browser).Returns(currentBrowser.Object);
+
+ // Act
+ string originalBrowserType = context.Object.GetVaryByCustomStringForOverriddenBrowser(CreateBrowserThroughFactory);
+
+ context.Object.SetOverriddenBrowser(BrowserOverride.Desktop);
+ string deskTopBrowserType = context.Object.GetVaryByCustomStringForOverriddenBrowser(CreateBrowserThroughFactory);
+
+ context.Object.SetOverriddenBrowser(BrowserOverride.Mobile);
+ string mobileBrowserType = context.Object.GetVaryByCustomStringForOverriddenBrowser(CreateBrowserThroughFactory);
+
+ // Assert
+ Assert.Equal(originalBrowserType, mobileBrowserType);
+ Assert.NotEqual(originalBrowserType, deskTopBrowserType);
+ Assert.NotEqual(mobileBrowserType, deskTopBrowserType);
+ }
+
+ [Fact]
+ public void GetVaryByCustomStringVariesBySetOverriddenBrowserDesktop()
+ {
+ // Arrange
+ Mock<HttpContextBase> context = CookieBrowserOverrideStoreTest.CreateCookieContext();
+ Mock<HttpBrowserCapabilitiesBase> currentBrowser = new Mock<HttpBrowserCapabilitiesBase>();
+ currentBrowser.Setup(c => c.IsMobileDevice).Returns(false);
+ context.Setup(c => c.Request.Browser).Returns(currentBrowser.Object);
+
+ // Act
+ string originalBrowserType = context.Object.GetVaryByCustomStringForOverriddenBrowser(CreateBrowserThroughFactory);
+
+ context.Object.SetOverriddenBrowser(BrowserOverride.Mobile);
+ string mobileBrowserType = context.Object.GetVaryByCustomStringForOverriddenBrowser(CreateBrowserThroughFactory);
+
+ context.Object.SetOverriddenBrowser(BrowserOverride.Desktop);
+ string deskTopBrowserType = context.Object.GetVaryByCustomStringForOverriddenBrowser(CreateBrowserThroughFactory);
+
+ // Assert
+ Assert.NotEqual(originalBrowserType, mobileBrowserType);
+ Assert.Equal(originalBrowserType, deskTopBrowserType);
+ Assert.NotEqual(mobileBrowserType, deskTopBrowserType);
+ }
+
+ // We need to call the .ctor of SimpleWorkerRequest that depends on HttpRuntime so for unit testing
+ // simply create the browser capabilities by going directly through the factory.
+ private static HttpBrowserCapabilitiesBase CreateBrowserThroughFactory(string userAgent)
+ {
+ HttpBrowserCapabilities browser = new HttpBrowserCapabilities
+ {
+ Capabilities = new Dictionary<string, string>
+ {
+ { String.Empty, userAgent }
+ }
+ };
+
+ BrowserCapabilitiesFactory factory = new BrowserCapabilitiesFactory();
+ factory.ConfigureBrowserCapabilities(new NameValueCollection(), browser);
+
+ return new HttpBrowserCapabilitiesWrapper(browser);
+ }
+ }
+}
diff --git a/test/System.Web.WebPages.Test/WebPage/BrowserOverrideStoresTest.cs b/test/System.Web.WebPages.Test/WebPage/BrowserOverrideStoresTest.cs
new file mode 100644
index 00000000..ebe29318
--- /dev/null
+++ b/test/System.Web.WebPages.Test/WebPage/BrowserOverrideStoresTest.cs
@@ -0,0 +1,42 @@
+using Moq;
+using Xunit;
+
+namespace System.Web.WebPages.Test
+{
+ public class BrowserOverrideStoresTest
+ {
+ [Fact]
+ public void DefaultBrowserOverrideStoreIsCookie()
+ {
+ // Act & Assert
+ Assert.Equal(typeof(CookieBrowserOverrideStore), BrowserOverrideStores.Current.GetType());
+ }
+
+ [Fact]
+ public void SetBrowserOverrideStoreReturnsSetBrowserOverrideStore()
+ {
+ // Arrange
+ BrowserOverrideStores stores = new BrowserOverrideStores();
+ Mock<BrowserOverrideStore> store = new Mock<BrowserOverrideStore>();
+
+ // Act
+ stores.CurrentInternal = store.Object;
+
+ // Assert
+ Assert.Same(store.Object, stores.CurrentInternal);
+ }
+
+ [Fact]
+ public void SetBrowserOverrideStoreNullReturnsRequestBrowserOverrideStore()
+ {
+ //Arrange
+ BrowserOverrideStores stores = new BrowserOverrideStores();
+
+ // Act
+ stores.CurrentInternal = null;
+
+ // Assert
+ Assert.Equal(typeof(RequestBrowserOverrideStore), stores.CurrentInternal.GetType());
+ }
+ }
+}
diff --git a/test/System.Web.WebPages.Test/WebPage/BuildManagerExceptionUtilTest.cs b/test/System.Web.WebPages.Test/WebPage/BuildManagerExceptionUtilTest.cs
new file mode 100644
index 00000000..ca1c915c
--- /dev/null
+++ b/test/System.Web.WebPages.Test/WebPage/BuildManagerExceptionUtilTest.cs
@@ -0,0 +1,90 @@
+using System.Globalization;
+using System.Runtime.CompilerServices;
+using System.Web.WebPages.Resources;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.WebPages.Test
+{
+ public class UtilTest
+ {
+ [Fact]
+ public void IsUnsupportedExtensionError()
+ {
+ Assert.False(BuildManagerExceptionUtil.IsUnsupportedExtensionError(new HttpException("The following file could not be rendered because its extension \".txt\" might not be supported: \"myfile.txt\".")));
+
+ var e = CompilationUtil.GetBuildProviderException(".txt");
+ Assert.NotNull(e);
+ Assert.True(BuildManagerExceptionUtil.IsUnsupportedExtensionError(e));
+ }
+
+ [Fact]
+ public void IsUnsupportedExtensionThrowsTest()
+ {
+ var extension = ".txt";
+ var virtualPath = "Layout.txt";
+ var e = CompilationUtil.GetBuildProviderException(extension);
+
+ Assert.Throws<HttpException>(
+ () => { BuildManagerExceptionUtil.ThrowIfUnsupportedExtension(virtualPath, e); }, String.Format(CultureInfo.CurrentCulture, WebPageResources.WebPage_FileNotSupported, extension, virtualPath));
+ }
+
+ [Fact]
+ public void CodeDomDefinedExtensionThrowsTest()
+ {
+ var extension = ".js";
+ var virtualPath = "Layout.js";
+
+ Assert.Throws<HttpException>(
+ () => { BuildManagerExceptionUtil.ThrowIfCodeDomDefinedExtension(virtualPath, new HttpCompileException()); }, String.Format(CultureInfo.CurrentCulture, WebPageResources.WebPage_FileNotSupported, extension, virtualPath));
+ }
+
+ [Fact]
+ public void CodeDomDefinedExtensionDoesNotThrowTest()
+ {
+ var virtualPath = "Layout.txt";
+ // Should not throw an exception
+ BuildManagerExceptionUtil.ThrowIfCodeDomDefinedExtension(virtualPath, new HttpCompileException());
+ }
+ }
+
+ // Dummy class to simulate exception from CompilationUtil.GetBuildProviderTypeFromExtension
+ internal class CompilationUtil : IVirtualPathFactory
+ {
+ /// <remarks>
+ /// The method that consumes this exception walks the stack trace and uses the class name and method name to uniquely identify an exception.
+ /// In release build, the method is inlined causing the call site to appear as the method GetBuildProviderException which causes the test to fail.
+ /// These attributes prevent the compiler from inlining this method.
+ /// </remarks>
+ [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)]
+ public static void GetBuildProviderTypeFromExtension(string extension)
+ {
+ throw new HttpException(extension);
+ }
+
+ public static HttpException GetBuildProviderException(string extension)
+ {
+ try
+ {
+ GetBuildProviderTypeFromExtension(extension);
+ }
+ catch (HttpException e)
+ {
+ return e;
+ }
+ return null;
+ }
+
+ public bool Exists(string virtualPath)
+ {
+ string extension = PathUtil.GetExtension(virtualPath);
+ GetBuildProviderTypeFromExtension(extension);
+ return false;
+ }
+
+ public object CreateInstance(string virtualPath)
+ {
+ throw new NotSupportedException();
+ }
+ }
+}
diff --git a/test/System.Web.WebPages.Test/WebPage/BuildManagerWrapperTest.cs b/test/System.Web.WebPages.Test/WebPage/BuildManagerWrapperTest.cs
new file mode 100644
index 00000000..ff0e9241
--- /dev/null
+++ b/test/System.Web.WebPages.Test/WebPage/BuildManagerWrapperTest.cs
@@ -0,0 +1,277 @@
+using System.Collections.Generic;
+using System.IO;
+using System.Text;
+using System.Web.Hosting;
+using Microsoft.Internal.Web.Utils;
+using Moq;
+using Xunit;
+
+namespace System.Web.WebPages.Test
+{
+ public class BuildManagerWrapperTest
+ {
+ private const string _precompileConfigFileName = "~/PrecompiledApp.config";
+
+ [Fact]
+ public void CanCreateObjectFactoryReturnsFalseIfExtensionIsNotRegistered()
+ {
+ // Arrange
+ var buildManagerWrapper = CreateWrapperInstance();
+
+ // Act
+ bool supported = buildManagerWrapper.IsPathExtensionSupported("~/styles/index.css");
+
+ // Assert
+ Assert.False(supported);
+ }
+
+ [Fact]
+ public void CanCreateObjectFactoryReturnsFalseIfVirtualPathIsExtensionless()
+ {
+ // Arrange
+ var buildManagerWrapper = CreateWrapperInstance();
+
+ // Act
+ bool supported = buildManagerWrapper.IsPathExtensionSupported("~/default");
+
+ // Assert
+ Assert.False(supported);
+ }
+
+ [Fact]
+ public void CanCreateObjectFactoryReturnsFalseIfVirtualPathExtensionIsEmpty()
+ {
+ // Arrange
+ var buildManagerWrapper = CreateWrapperInstance();
+
+ // Act
+ bool supported = buildManagerWrapper.IsPathExtensionSupported("~/default.");
+
+ // Assert
+ Assert.False(supported);
+ }
+
+ [Fact]
+ public void CanCreateObjectFactoryReturnsTrueIfVirtualPathExtensionIsRegistered()
+ {
+ // Arrange
+ var buildManagerWrapper = CreateWrapperInstance();
+
+ // Act
+ bool supported = buildManagerWrapper.IsPathExtensionSupported("~/default.cshtml");
+
+ // Assert
+ Assert.True(supported);
+ }
+
+ [Fact]
+ public void CanCreateObjectFactoryPerformsCaseInsenitiveComparison()
+ {
+ // Arrange
+ var buildManagerWrapper = CreateWrapperInstance();
+
+ // Act
+ bool supported = buildManagerWrapper.IsPathExtensionSupported("~/default.CShTml");
+
+ // Assert
+ Assert.True(supported);
+ }
+
+ [Fact]
+ public void CanCreateObjectFactoryWorksForAllRegisteredExtensions()
+ {
+ // Arrange
+ var buildManagerWrapper = CreateWrapperInstance();
+
+ // Act
+ bool supported = buildManagerWrapper.IsPathExtensionSupported("~/default.vbHtml");
+
+ // Assert
+ Assert.True(supported);
+ }
+
+ [Fact]
+ public void IsNonPrecompiledAppReturnsFalseIfPrecompiledConfigFileDoesNotExist()
+ {
+ // Arrange
+ var vpp = new Mock<VirtualPathProvider>();
+ vpp.Setup(c => c.FileExists(It.IsAny<string>())).Returns(false);
+ var buildManagerWrapper = new BuildManagerWrapper(vpp.Object, GetVirtualPathUtility());
+
+ // Act
+ var isPrecompiled = buildManagerWrapper.IsNonUpdatablePrecompiledApp();
+
+ // Assert
+ Assert.False(isPrecompiled);
+ vpp.Verify();
+ }
+
+ [Fact]
+ public void IsNonPrecompiledAppReturnsFalseIfPrecompiledConfigFileIsNotValidXml()
+ {
+ // Arrange
+ var vpp = new Mock<VirtualPathProvider>();
+ vpp.Setup(c => c.FileExists(It.Is<string>(p => p.Equals(_precompileConfigFileName)))).Returns(true).Verifiable();
+ var file = new Mock<VirtualFile>();
+ vpp.Setup(c => c.GetFile(It.Is<string>(p => p.Equals(_precompileConfigFileName)))).Returns(GetFile("some random text that is clearly not xml!"));
+ var buildManagerWrapper = new BuildManagerWrapper(vpp.Object, GetVirtualPathUtility());
+
+ // Act
+ var isPrecompiled = buildManagerWrapper.IsNonUpdatablePrecompiledApp();
+
+ // Assert
+ Assert.False(isPrecompiled);
+ vpp.Verify();
+ }
+
+ [Fact]
+ public void IsNonPrecompiledAppReturnsFalseIfConfigFileDoesNotContainExpectedElements()
+ {
+ // Arrange
+ var fileContent = @"<?xml version=""1.0""?><configuration><appSettings></appSettings></configuration>";
+ var vpp = new Mock<VirtualPathProvider>();
+ vpp.Setup(c => c.FileExists(It.Is<string>(p => p.Equals(_precompileConfigFileName)))).Returns(true).Verifiable();
+ vpp.Setup(c => c.GetFile(It.Is<string>(p => p.Equals(_precompileConfigFileName)))).Returns(GetFile(fileContent)).Verifiable();
+ var buildManagerWrapper = new BuildManagerWrapper(vpp.Object, GetVirtualPathUtility());
+
+ // Act
+ var isPrecompiled = buildManagerWrapper.IsNonUpdatablePrecompiledApp();
+
+ // Assert
+ Assert.False(isPrecompiled);
+ vpp.Verify();
+ }
+
+ [Fact]
+ public void IsNonPrecompiledAppReturnsFalseIfAppIsUpdatable()
+ {
+ // Arrange
+ var fileContent = @"<precompiledApp version=""2"" updatable=""true""/>";
+ var vpp = new Mock<VirtualPathProvider>();
+ vpp.Setup(c => c.FileExists(It.Is<string>(p => p.Equals(_precompileConfigFileName)))).Returns(true).Verifiable();
+ vpp.Setup(c => c.GetFile(It.Is<string>(p => p.Equals(_precompileConfigFileName)))).Returns(GetFile(fileContent)).Verifiable();
+ var buildManagerWrapper = new BuildManagerWrapper(vpp.Object, GetVirtualPathUtility());
+
+ // Act
+ var isPrecompiled = buildManagerWrapper.IsNonUpdatablePrecompiledApp();
+
+ // Assert
+ Assert.False(isPrecompiled);
+ vpp.Verify();
+ }
+
+ [Fact]
+ public void IsNonPrecompiledAppReturnsTrueIfConfigFileIsValidAndIsAppIsNotUpdatable()
+ {
+ // Arrange
+ var fileContent = @"<precompiledApp version=""2"" updatable=""false""/>";
+ var vpp = new Mock<VirtualPathProvider>();
+ vpp.Setup(c => c.FileExists(It.Is<string>(p => p.Equals(_precompileConfigFileName)))).Returns(true).Verifiable();
+ vpp.Setup(c => c.GetFile(It.Is<string>(p => p.Equals(_precompileConfigFileName)))).Returns(GetFile(fileContent)).Verifiable();
+ var buildManagerWrapper = new BuildManagerWrapper(vpp.Object, GetVirtualPathUtility());
+
+ // Act
+ var isPrecompiled = buildManagerWrapper.IsNonUpdatablePrecompiledApp();
+
+ // Assert
+ Assert.True(isPrecompiled);
+ vpp.Verify();
+ }
+
+ [Fact]
+ public void ExistsUsesVppIfSiteIfNotPrecompiled()
+ {
+ // Arrange
+ var virtualPath = "~/default.cshtml";
+ var vpp = new Mock<VirtualPathProvider>();
+ vpp.Setup(c => c.FileExists(It.Is<string>(p => p.Equals(_precompileConfigFileName)))).Returns(false).Verifiable();
+ vpp.Setup(c => c.FileExists(It.Is<string>(p => p.Equals(virtualPath)))).Returns(true).Verifiable();
+ var buildManagerWrapper = new BuildManagerWrapper(vpp.Object, GetVirtualPathUtility());
+
+ // Act
+ var exists = buildManagerWrapper.Exists(virtualPath);
+
+ // Assert
+ vpp.Verify();
+ Assert.True(exists);
+ }
+
+ [Fact]
+ public void ExistsUsesVppIfSiteIfSiteIsPrecompiledButUpdateable()
+ {
+ // Arrange
+ var virtualPath = "~/default.cshtml";
+ var fileContent = @"<precompiledApp version=""2"" updatable=""true""/>";
+ var vpp = new Mock<VirtualPathProvider>();
+ vpp.Setup(c => c.FileExists(It.Is<string>(p => p.Equals(_precompileConfigFileName)))).Returns(true).Verifiable();
+ vpp.Setup(c => c.GetFile(It.Is<string>(p => p.Equals(_precompileConfigFileName)))).Returns(GetFile(fileContent)).Verifiable();
+ vpp.Setup(c => c.FileExists(It.Is<string>(p => p.Equals(virtualPath)))).Returns(true).Verifiable();
+ var buildManagerWrapper = new BuildManagerWrapper(vpp.Object, GetVirtualPathUtility());
+
+ // Act
+ var exists = buildManagerWrapper.Exists(virtualPath);
+
+ // Assert
+ vpp.Verify();
+ Assert.True(exists);
+ }
+
+ /// <remarks>
+ /// This method adds items to HttpRuntime.Cache.
+ /// </summary>
+ [Fact]
+ public void ExistsInPrecompiledReturnsFalseIfExtensionIsUnsupported()
+ {
+ // Arrange
+ var virtualPath = "~/ExistsInPrecompiledReturnsFalseIfExtensionIsUnsupported.jpeg";
+ var fileContent = @"<precompiledApp version=""2"" updatable=""false""/>";
+ var vpp = new Mock<VirtualPathProvider>();
+ vpp.Setup(c => c.FileExists(It.Is<string>(p => p.Equals(_precompileConfigFileName)))).Returns(true).Verifiable();
+ vpp.Setup(c => c.GetFile(It.Is<string>(p => p.Equals(_precompileConfigFileName)))).Returns(GetFile(fileContent)).Verifiable();
+ var buildManagerWrapper = new BuildManagerWrapper(vpp.Object, GetVirtualPathUtility());
+
+ // Act
+ var exists = buildManagerWrapper.Exists(virtualPath);
+
+ // Assert
+ vpp.Verify();
+ Assert.False(exists);
+ object cachedValue = HttpRuntime.Cache.Get(BuildManagerWrapper.KeyGuid + "_" + virtualPath);
+ Assert.NotNull(cachedValue);
+ Assert.False((bool)cachedValue.GetType().GetProperty("Exists").GetValue(cachedValue, null));
+ }
+
+ private static BuildManagerWrapper CreateWrapperInstance(IEnumerable<string> supportedExtensions = null)
+ {
+ return new BuildManagerWrapper(new Mock<VirtualPathProvider>().Object, GetVirtualPathUtility()) { SupportedExtensions = supportedExtensions ?? new[] { "cshtml", "vbhtml" } };
+ }
+
+ private static VirtualFile GetFile(string content)
+ {
+ var file = new Mock<VirtualFile>("test file");
+ file.Setup(f => f.Open()).Returns(() => new MemoryStream(Encoding.UTF8.GetBytes(content)));
+ return file.Object;
+ }
+
+ private static IVirtualPathUtility GetVirtualPathUtility()
+ {
+ var utility = new Mock<VirtualPathUtilityBase>();
+ utility.Setup(c => c.ToAbsolute(It.IsAny<string>())).Returns<string>(c => c);
+
+ return utility.Object;
+ }
+ }
+
+ public abstract class VirtualPathUtilityBase : IVirtualPathUtility
+ {
+ public virtual string Combine(string basePath, string relativePath)
+ {
+ throw new NotImplementedException();
+ }
+
+ public virtual string ToAbsolute(string virtualPath)
+ {
+ throw new NotImplementedException();
+ }
+ }
+}
diff --git a/test/System.Web.WebPages.Test/WebPage/CookieBrowserOverrideStoreTest.cs b/test/System.Web.WebPages.Test/WebPage/CookieBrowserOverrideStoreTest.cs
new file mode 100644
index 00000000..6d5410f3
--- /dev/null
+++ b/test/System.Web.WebPages.Test/WebPage/CookieBrowserOverrideStoreTest.cs
@@ -0,0 +1,153 @@
+using System.Collections;
+using Moq;
+using Xunit;
+
+namespace System.Web.WebPages.Test
+{
+ public class CookieBrowserOverrideStoreTest
+ {
+ [Fact]
+ public void GetOverriddenUserAgentReturnsNullIfNoResponseOrRequestCookieIsSet()
+ {
+ // Arrange
+ CookieBrowserOverrideStore store = new CookieBrowserOverrideStore();
+
+ // Act & Assert
+ Assert.Null(store.GetOverriddenUserAgent(CreateCookieContext().Object));
+ }
+
+ [Fact]
+ public void GetOverriddenUserAgentReturnsUserAgentFromRequestCookieIfNoResponseCookie()
+ {
+ // Arrange
+ CookieBrowserOverrideStore store = new CookieBrowserOverrideStore();
+ HttpCookie existingOverrideCookie = new HttpCookie(CookieBrowserOverrideStore.BrowserOverrideCookieName, "existingRequestAgent");
+ HttpContextBase context = CreateCookieContext(requestCookie: existingOverrideCookie).Object;
+
+ // Act & Assert
+ Assert.Equal("existingRequestAgent", store.GetOverriddenUserAgent(context));
+ }
+
+ [Fact]
+ public void SetOverriddenUserAgentWithNoExistingCookie()
+ {
+ // Arrange
+ CookieBrowserOverrideStore store = new CookieBrowserOverrideStore();
+ HttpContextBase context = CreateCookieContext().Object;
+
+ // Act
+ store.SetOverriddenUserAgent(context, "setUserAgent");
+
+ // Assert
+ Assert.Equal("setUserAgent", store.GetOverriddenUserAgent(context));
+ }
+
+ [Fact]
+ public void SetOverriddenUserWithExistingRequestCookie()
+ {
+ // Arrange
+ CookieBrowserOverrideStore store = new CookieBrowserOverrideStore();
+ HttpCookie existingOverrideCookie = new HttpCookie(CookieBrowserOverrideStore.BrowserOverrideCookieName, "existingRequestAgent");
+ HttpContextBase context = CreateCookieContext(requestCookie: existingOverrideCookie).Object;
+
+ // Act
+ store.SetOverriddenUserAgent(context, "setUserAgent");
+
+ // Assert
+ Assert.Equal("setUserAgent", store.GetOverriddenUserAgent(context));
+ }
+
+ [Fact]
+ public void SetOverriddenUserWithExistingResponseCookie()
+ {
+ // Arrange
+ CookieBrowserOverrideStore store = new CookieBrowserOverrideStore();
+ HttpContextBase context = CreateCookieContext().Object;
+
+ // Act & Assert
+ store.SetOverriddenUserAgent(context, "testUserAgent");
+ Assert.Equal("testUserAgent", store.GetOverriddenUserAgent(context));
+
+ store.SetOverriddenUserAgent(context, "subsequentTestUserAgent");
+ Assert.Equal("subsequentTestUserAgent", store.GetOverriddenUserAgent(context));
+ }
+
+ [Fact]
+ public void SetOverriddenUserAgentNullWithRequestCookie()
+ {
+ // Arrange
+ CookieBrowserOverrideStore store = new CookieBrowserOverrideStore();
+ HttpCookie existingOverrideCookie = new HttpCookie(CookieBrowserOverrideStore.BrowserOverrideCookieName, "setUserAgent");
+ HttpContextBase context = CreateCookieContext(requestCookie: existingOverrideCookie).Object;
+
+ // Act
+ store.SetOverriddenUserAgent(context, null);
+
+ // Assert
+ Assert.Null(store.GetOverriddenUserAgent(context));
+ }
+
+ [Fact]
+ public void SetOverriddenUserAgentNullWithNoExistingCookie()
+ {
+ // Arrange
+ CookieBrowserOverrideStore store = new CookieBrowserOverrideStore();
+ HttpContextBase context = CreateCookieContext().Object;
+
+ // Act
+ store.SetOverriddenUserAgent(context, null);
+
+ // Assert
+ Assert.Null(store.GetOverriddenUserAgent(context));
+ }
+
+ [Fact]
+ public void SetOverriddenUserAgentSetsExpiration()
+ {
+ // Arrange
+ CookieBrowserOverrideStore store = new CookieBrowserOverrideStore();
+ CookieBrowserOverrideStore sessionStore = new CookieBrowserOverrideStore(daysToExpire: 0);
+ CookieBrowserOverrideStore longTermStore = new CookieBrowserOverrideStore(daysToExpire: 100);
+ CookieBrowserOverrideStore negativeTermStore = new CookieBrowserOverrideStore(daysToExpire: -1);
+
+ HttpContextBase context = CreateCookieContext().Object;
+
+ // Act & Assert
+ store.SetOverriddenUserAgent(context, "testUserAgent");
+ Assert.True(DateTime.Now.AddDays(6.5) < context.Response.Cookies[CookieBrowserOverrideStore.BrowserOverrideCookieName].Expires &&
+ context.Response.Cookies[CookieBrowserOverrideStore.BrowserOverrideCookieName].Expires < DateTime.Now.AddDays(7.5));
+
+ sessionStore.SetOverriddenUserAgent(context, "testUserAgent");
+ Assert.True(context.Response.Cookies[CookieBrowserOverrideStore.BrowserOverrideCookieName].Expires < DateTime.Now);
+
+ longTermStore.SetOverriddenUserAgent(context, "testUserAgent");
+ Assert.True(context.Response.Cookies[CookieBrowserOverrideStore.BrowserOverrideCookieName].Expires > DateTime.Now.AddDays(99));
+
+ negativeTermStore.SetOverriddenUserAgent(context, "testUserAgent");
+ Assert.True(context.Response.Cookies[CookieBrowserOverrideStore.BrowserOverrideCookieName].Expires < DateTime.Now);
+ }
+
+ internal static Mock<HttpContextBase> CreateCookieContext(HttpCookie requestCookie = null, HttpCookie responseCookie = null)
+ {
+ Mock<HttpContextBase> context = new Mock<HttpContextBase>();
+
+ HttpCookieCollection requestCookies = new HttpCookieCollection();
+ if (requestCookie != null)
+ {
+ requestCookies.Add(requestCookie);
+ }
+
+ HttpCookieCollection responseCookies = new HttpCookieCollection();
+ if (responseCookie != null)
+ {
+ responseCookies.Add(responseCookie);
+ }
+
+ context.Setup(c => c.Request.Cookies).Returns(requestCookies);
+ context.Setup(c => c.Response.Cookies).Returns(responseCookies);
+ context.Setup(c => c.Items).Returns(new Hashtable());
+
+ return context;
+ }
+ }
+}
diff --git a/test/System.Web.WebPages.Test/WebPage/DefaultDisplayModeTest.cs b/test/System.Web.WebPages.Test/WebPage/DefaultDisplayModeTest.cs
new file mode 100644
index 00000000..cc857fbf
--- /dev/null
+++ b/test/System.Web.WebPages.Test/WebPage/DefaultDisplayModeTest.cs
@@ -0,0 +1,139 @@
+using Moq;
+using Xunit;
+
+namespace System.Web.WebPages.Test
+{
+ public class DefaultDisplayModeTest
+ {
+ [Fact]
+ public void DefaultDisplayModeWithEmptySuffix()
+ {
+ // Arrange
+ DefaultDisplayMode displayMode = new DefaultDisplayMode();
+
+ // Act
+ DisplayInfo info = displayMode.GetDisplayInfo(new Mock<HttpContextBase>(MockBehavior.Strict).Object, "/bar/baz.aspx", virtualPath => true);
+
+ // Assert
+ Assert.Equal(String.Empty, displayMode.DisplayModeId);
+ Assert.Equal("/bar/baz.aspx", info.FilePath);
+ }
+
+ [Fact]
+ public void DefaultDisplayModeWithNullSuffix()
+ {
+ // Arrange
+ DefaultDisplayMode displayMode = new DefaultDisplayMode(null);
+
+ // Act
+ DisplayInfo info = displayMode.GetDisplayInfo(new Mock<HttpContextBase>(MockBehavior.Strict).Object, "/bar/baz.aspx", virtualPath => true);
+
+ // Assert
+ Assert.Equal(String.Empty, displayMode.DisplayModeId);
+ Assert.Equal("/bar/baz.aspx", info.FilePath);
+ }
+
+ [Fact]
+ public void DefaultDisplayModeSetSuffixAsId()
+ {
+ // Arrange
+ DefaultDisplayMode displayMode = new DefaultDisplayMode("foo");
+
+ // Act & Assert
+ Assert.Equal("foo", displayMode.DisplayModeId);
+ }
+
+ [Fact]
+ public void GetDisplayInfoWithNullOrEmptySuffixReturnsPathThatExists()
+ {
+ // Arrange
+ DefaultDisplayMode displayMode = new DefaultDisplayMode("foo");
+
+ // Act
+ DisplayInfo info = displayMode.GetDisplayInfo(new Mock<HttpContextBase>(MockBehavior.Strict).Object, "/bar/baz.aspx", virtualPath => true);
+
+ // Assert
+ Assert.IsType<DefaultDisplayMode>(info.DisplayMode);
+ Assert.Equal("/bar/baz.foo.aspx", info.FilePath);
+ }
+
+ [Fact]
+ public void GetDisplayInfoInsertsSuffixIntoVirtualPathThatExists()
+ {
+ // Arrange
+ DefaultDisplayMode displayMode = new DefaultDisplayMode("foo");
+
+ // Act
+ DisplayInfo info = displayMode.GetDisplayInfo(new Mock<HttpContextBase>(MockBehavior.Strict).Object, "/bar/baz.aspx", virtualPath => true);
+
+ // Assert
+ Assert.IsType<DefaultDisplayMode>(info.DisplayMode);
+ Assert.Equal("/bar/baz.foo.aspx", info.FilePath);
+ }
+
+ [Fact]
+ public void GetDisplayInfoInsertsSuffixBeforeLastSectionOfExtension()
+ {
+ // Arrange
+ DefaultDisplayMode displayMode = new DefaultDisplayMode("foo");
+
+ // Act
+ DisplayInfo info = displayMode.GetDisplayInfo(new Mock<HttpContextBase>(MockBehavior.Strict).Object, "/bar/baz.txt.aspx", virtualPath => true);
+
+ // Assert
+ Assert.Equal("/bar/baz.txt.foo.aspx", info.FilePath);
+ }
+
+ [Fact]
+ public void GetDisplayInfoSuffixesPathWithNoExtension()
+ {
+ // Arrange
+ DefaultDisplayMode displayMode = new DefaultDisplayMode("foo");
+
+ // Act
+ DisplayInfo info = displayMode.GetDisplayInfo(new Mock<HttpContextBase>(MockBehavior.Strict).Object, "/bar/baz", virtualPath => true);
+
+ // Assert
+ Assert.Equal("/bar/baz.foo", info.FilePath);
+ }
+
+ [Fact]
+ public void GetDisplayInfoWithNullVirtualPath()
+ {
+ // Arrange
+ DefaultDisplayMode displayMode = new DefaultDisplayMode("foo");
+
+ // Act
+ DisplayInfo info = displayMode.GetDisplayInfo(new Mock<HttpContextBase>(MockBehavior.Strict).Object, virtualPath: null, virtualPathExists: virtualPath => true);
+
+ // Assert
+ Assert.Null(info);
+ }
+
+ [Fact]
+ public void GetDisplayInfoSuffixesPathWithEmptyVirtualPath()
+ {
+ // Arrange
+ DefaultDisplayMode displayMode = new DefaultDisplayMode("foo");
+
+ // Act
+ DisplayInfo info = displayMode.GetDisplayInfo(new Mock<HttpContextBase>(MockBehavior.Strict).Object, String.Empty, virtualPath => true);
+
+ // Assert
+ Assert.Equal(String.Empty, info.FilePath);
+ }
+
+ [Fact]
+ public void GetDisplayInfoReturnsNullIfPathDoesNotExist()
+ {
+ // Arrange
+ DefaultDisplayMode displayMode = new DefaultDisplayMode("foo");
+
+ // Act
+ DisplayInfo info = displayMode.GetDisplayInfo(new Mock<HttpContextBase>(MockBehavior.Strict).Object, "/bar/baz", virtualPath => false);
+
+ // Assert
+ Assert.Null(info);
+ }
+ }
+}
diff --git a/test/System.Web.WebPages.Test/WebPage/DisplayInfoTest.cs b/test/System.Web.WebPages.Test/WebPage/DisplayInfoTest.cs
new file mode 100644
index 00000000..2772f501
--- /dev/null
+++ b/test/System.Web.WebPages.Test/WebPage/DisplayInfoTest.cs
@@ -0,0 +1,37 @@
+using Moq;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.WebPages.Test
+{
+ public class DisplayInfoTest
+ {
+ [Fact]
+ public void GuardClauses()
+ {
+ // Act & Assert
+ Assert.ThrowsArgumentNull(() => new DisplayInfo(filePath: null, displayMode: new Mock<IDisplayMode>().Object), "filePath");
+ Assert.ThrowsArgumentNull(() => new DisplayInfo("testPath", displayMode: null), "displayMode");
+ }
+
+ public void ConstructorSetsDisplayInfoProperties()
+ {
+ // Arrange
+ string path = "testPath";
+ IDisplayMode displayMode = new Mock<IDisplayMode>().Object;
+
+ // Act
+ DisplayInfo info = new DisplayInfo(path, displayMode);
+
+ // Assert
+ Assert.Equal(path, info.FilePath);
+ Assert.Equal(displayMode, info.DisplayMode);
+ }
+
+ public void ConstructorSetsEmptyFilePath()
+ {
+ // Act & Assert
+ Assert.Equal(String.Empty, new DisplayInfo(String.Empty, new Mock<IDisplayMode>().Object).FilePath);
+ }
+ }
+}
diff --git a/test/System.Web.WebPages.Test/WebPage/DisplayModeProviderTest.cs b/test/System.Web.WebPages.Test/WebPage/DisplayModeProviderTest.cs
new file mode 100644
index 00000000..6143910d
--- /dev/null
+++ b/test/System.Web.WebPages.Test/WebPage/DisplayModeProviderTest.cs
@@ -0,0 +1,229 @@
+using System.Collections.Generic;
+using System.Linq;
+using Moq;
+using Xunit;
+
+namespace System.Web.WebPages.Test
+{
+ public class DisplayModesTest
+ {
+ [Fact]
+ public void DefaultInstanceHasDefaultModes()
+ {
+ // Act
+ IList<IDisplayMode> displayModes = DisplayModeProvider.Instance.Modes;
+
+ // Assert
+ Assert.Equal(2, displayModes.Count);
+
+ Assert.IsType<DefaultDisplayMode>(displayModes[0]);
+ Assert.Equal(displayModes[0].DisplayModeId, DisplayModeProvider.MobileDisplayModeId);
+
+ Assert.IsType<DefaultDisplayMode>(displayModes[1]);
+ Assert.Equal(displayModes[1].DisplayModeId, DisplayModeProvider.DefaultDisplayModeId);
+ }
+
+ [Fact]
+ public void GetDisplayInfoForVirtualPathReturnsDisplayInfoFromFirstDisplayModeToHandleRequest()
+ {
+ // Arrange
+ var displayModeProvider = new DisplayModeProvider();
+ displayModeProvider.Modes.Clear();
+ var displayMode1 = new Mock<IDisplayMode>(MockBehavior.Strict);
+ displayMode1.Setup(d => d.CanHandleContext(It.IsAny<HttpContextBase>())).Returns(false);
+ displayModeProvider.Modes.Add(displayMode1.Object);
+
+ var displayMode2 = new Mock<IDisplayMode>(MockBehavior.Strict);
+ displayMode2.Setup(d => d.CanHandleContext(It.IsAny<HttpContextBase>())).Returns(false);
+ displayModeProvider.Modes.Add(displayMode2.Object);
+
+ var displayMode3 = new Mock<IDisplayMode>(MockBehavior.Strict);
+ displayMode3.Setup(d => d.CanHandleContext(It.IsAny<HttpContextBase>())).Returns(true);
+ displayModeProvider.Modes.Add(displayMode3.Object);
+
+ Mock<HttpContextBase> httpContext = new Mock<HttpContextBase>(MockBehavior.Strict);
+
+ var expected = new DisplayInfo("Foo", displayMode3.Object);
+ Func<string, bool> fileExists = path => true;
+ displayMode3.Setup(d => d.GetDisplayInfo(httpContext.Object, "path", fileExists)).Returns(expected);
+
+ // Act
+ DisplayInfo result = displayModeProvider.GetDisplayInfoForVirtualPath("path", httpContext.Object, fileExists, currentDisplayMode: null);
+
+ // Assert
+ Assert.Same(expected, result);
+ }
+
+ [Fact]
+ public void GetDisplayInfoForVirtualPathWithConsistentDisplayModeBeginsSearchAtCurrentDisplayMode()
+ {
+ // Arrange
+ Mock<HttpContextBase> httpContext = new Mock<HttpContextBase>(MockBehavior.Strict);
+
+ var displayModeProvider = new DisplayModeProvider();
+ displayModeProvider.Modes.Clear();
+ var displayMode1 = new Mock<IDisplayMode>(MockBehavior.Strict);
+ displayMode1.Setup(d => d.CanHandleContext(It.IsAny<HttpContextBase>())).Returns(true);
+ displayModeProvider.Modes.Add(displayMode1.Object);
+
+ var displayMode2 = new Mock<IDisplayMode>(MockBehavior.Strict);
+ displayMode2.Setup(d => d.CanHandleContext(It.IsAny<HttpContextBase>())).Returns(false);
+ displayModeProvider.Modes.Add(displayMode2.Object);
+
+ var displayMode3 = new Mock<IDisplayMode>(MockBehavior.Strict);
+ displayMode3.Setup(d => d.CanHandleContext(It.IsAny<HttpContextBase>())).Returns(true);
+ displayModeProvider.Modes.Add(displayMode3.Object);
+
+ var displayInfo = new DisplayInfo("Foo", displayMode3.Object);
+ Func<string, bool> fileExists = path => true;
+ displayMode3.Setup(d => d.GetDisplayInfo(httpContext.Object, "path", fileExists)).Returns(displayInfo);
+
+ // Act
+ DisplayInfo result = displayModeProvider.GetDisplayInfoForVirtualPath("path", httpContext.Object, fileExists, currentDisplayMode: displayMode2.Object,
+ requireConsistentDisplayMode: true);
+
+ // Assert
+ Assert.Same(displayInfo, result);
+ }
+
+ [Fact]
+ public void GetDisplayInfoForVirtualPathWithoutConsistentDisplayModeIgnoresCurrentDisplayMode()
+ {
+ // Arrange
+ Mock<HttpContextBase> httpContext = new Mock<HttpContextBase>(MockBehavior.Strict);
+ var displayModeProvider = new DisplayModeProvider();
+ displayModeProvider.Modes.Clear();
+ var displayMode1 = new Mock<IDisplayMode>(MockBehavior.Strict);
+ displayMode1.Setup(d => d.CanHandleContext(It.IsAny<HttpContextBase>())).Returns(true);
+ displayModeProvider.Modes.Add(displayMode1.Object);
+
+ var displayMode2 = new Mock<IDisplayMode>(MockBehavior.Strict);
+ displayMode2.Setup(d => d.CanHandleContext(It.IsAny<HttpContextBase>())).Returns(false);
+ displayModeProvider.Modes.Add(displayMode2.Object);
+
+ var displayMode3 = new Mock<IDisplayMode>(MockBehavior.Strict);
+ displayMode3.Setup(d => d.CanHandleContext(It.IsAny<HttpContextBase>())).Returns(true);
+ displayModeProvider.Modes.Add(displayMode3.Object);
+
+ var displayInfo = new DisplayInfo("Foo", displayMode3.Object);
+ Func<string, bool> fileExists = path => true;
+ displayMode1.Setup(d => d.GetDisplayInfo(httpContext.Object, "path", fileExists)).Returns(displayInfo);
+
+ // Act
+ DisplayInfo result = displayModeProvider.GetDisplayInfoForVirtualPath("path", httpContext.Object, fileExists, currentDisplayMode: displayMode1.Object,
+ requireConsistentDisplayMode: false);
+
+ // Assert
+ Assert.Same(displayInfo, result);
+ }
+
+ [Fact]
+ public void GetDisplayModesForRequestReturnsNullIfNoDisplayModesHandleRequest()
+ {
+ // Arrange
+ Mock<HttpContextBase> httpContext = new Mock<HttpContextBase>(MockBehavior.Strict);
+ var displayModeProvider = new DisplayModeProvider();
+ displayModeProvider.Modes.Clear();
+ var displayMode1 = new Mock<IDisplayMode>(MockBehavior.Strict);
+ displayMode1.Setup(d => d.CanHandleContext(It.IsAny<HttpContextBase>())).Returns(false);
+ displayModeProvider.Modes.Add(displayMode1.Object);
+
+ var displayMode2 = new Mock<IDisplayMode>(MockBehavior.Strict);
+ displayMode2.Setup(d => d.CanHandleContext(It.IsAny<HttpContextBase>())).Returns(false);
+ displayModeProvider.Modes.Add(displayMode2.Object);
+
+ var displayMode3 = new Mock<IDisplayMode>(MockBehavior.Strict);
+ displayMode3.Setup(d => d.CanHandleContext(It.IsAny<HttpContextBase>())).Returns(false);
+ displayModeProvider.Modes.Add(displayMode3.Object);
+
+ // Act
+ DisplayInfo displayModeForRequest = displayModeProvider.GetDisplayInfoForVirtualPath("path", httpContext.Object, path => false, currentDisplayMode: null);
+
+ // Assert
+ Assert.Null(displayModeForRequest);
+ }
+
+ [Fact]
+ public void GetAvailableDisplayModesForContextWithRestrictingPageElements()
+ {
+ // Arrange
+ Mock<HttpContextBase> httpContext = new Mock<HttpContextBase>(MockBehavior.Strict);
+ var displayModeProvider = new DisplayModeProvider();
+ displayModeProvider.Modes.Clear();
+ var displayMode1 = new Mock<IDisplayMode>(MockBehavior.Strict);
+ displayMode1.Setup(d => d.CanHandleContext(It.IsAny<HttpContextBase>())).Returns(true);
+ displayModeProvider.Modes.Add(displayMode1.Object);
+
+ var displayMode2 = new Mock<IDisplayMode>(MockBehavior.Strict);
+ displayMode2.Setup(d => d.CanHandleContext(It.IsAny<HttpContextBase>())).Returns(false);
+ displayModeProvider.Modes.Add(displayMode2.Object);
+
+ var displayMode3 = new Mock<IDisplayMode>(MockBehavior.Strict);
+ displayMode3.Setup(d => d.CanHandleContext(It.IsAny<HttpContextBase>())).Returns(true);
+ displayModeProvider.Modes.Add(displayMode3.Object);
+
+ // Act
+ var availableDisplayModes = displayModeProvider.GetAvailableDisplayModesForContext(httpContext.Object, displayMode2.Object, requireConsistentDisplayMode: true).ToList();
+
+ // Assert
+ Assert.Equal(1, availableDisplayModes.Count);
+ Assert.Equal(displayMode3.Object, availableDisplayModes[0]);
+ }
+
+ [Fact]
+ public void GetAvailableDisplayModesForContextWithoutRestrictingPageElements()
+ {
+ // Arrange
+ Mock<HttpContextBase> httpContext = new Mock<HttpContextBase>(MockBehavior.Strict);
+ var displayModeProvider = new DisplayModeProvider();
+ displayModeProvider.Modes.Clear();
+
+ var displayMode1 = new Mock<IDisplayMode>(MockBehavior.Strict);
+ displayMode1.Setup(d => d.CanHandleContext(It.IsAny<HttpContextBase>())).Returns(true);
+ displayModeProvider.Modes.Add(displayMode1.Object);
+
+ var displayMode2 = new Mock<IDisplayMode>(MockBehavior.Strict);
+ displayMode2.Setup(d => d.CanHandleContext(It.IsAny<HttpContextBase>())).Returns(false);
+ displayModeProvider.Modes.Add(displayMode2.Object);
+
+ var displayMode3 = new Mock<IDisplayMode>(MockBehavior.Strict);
+ displayMode3.Setup(d => d.CanHandleContext(It.IsAny<HttpContextBase>())).Returns(true);
+ displayModeProvider.Modes.Add(displayMode3.Object);
+
+ // Act
+ var availableDisplayModes = displayModeProvider.GetAvailableDisplayModesForContext(httpContext.Object, displayMode2.Object, requireConsistentDisplayMode: false).ToList();
+
+ // Assert
+ Assert.Equal(2, availableDisplayModes.Count);
+ Assert.Same(displayMode1.Object, availableDisplayModes[0]);
+ Assert.Same(displayMode3.Object, availableDisplayModes[1]);
+ }
+
+ [Fact]
+ public void GetAvailableDisplayModesReturnsOnlyModesThatCanHandleContext()
+ {
+ // Arrange
+ Mock<HttpContextBase> httpContext = new Mock<HttpContextBase>(MockBehavior.Strict);
+ var displayModeProvider = new DisplayModeProvider();
+ displayModeProvider.Modes.Clear();
+ var displayMode1 = new Mock<IDisplayMode>(MockBehavior.Strict);
+ displayMode1.Setup(d => d.CanHandleContext(It.IsAny<HttpContextBase>())).Returns(false);
+ displayModeProvider.Modes.Add(displayMode1.Object);
+
+ var displayMode2 = new Mock<IDisplayMode>(MockBehavior.Strict);
+ displayMode2.Setup(d => d.CanHandleContext(It.IsAny<HttpContextBase>())).Returns(true);
+ displayModeProvider.Modes.Add(displayMode2.Object);
+
+ var displayMode3 = new Mock<IDisplayMode>(MockBehavior.Strict);
+ displayMode3.Setup(d => d.CanHandleContext(It.IsAny<HttpContextBase>())).Returns(false);
+ displayModeProvider.Modes.Add(displayMode3.Object);
+
+ // Act
+ var availableDisplayModes = displayModeProvider.GetAvailableDisplayModesForContext(httpContext.Object, displayMode1.Object, requireConsistentDisplayMode: false).ToList();
+
+ // Assert
+ Assert.Equal(1, availableDisplayModes.Count);
+ Assert.Equal(displayMode2.Object, availableDisplayModes[0]);
+ }
+ }
+}
diff --git a/test/System.Web.WebPages.Test/WebPage/DynamicHttpApplicationStateTest.cs b/test/System.Web.WebPages.Test/WebPage/DynamicHttpApplicationStateTest.cs
new file mode 100644
index 00000000..6241e88f
--- /dev/null
+++ b/test/System.Web.WebPages.Test/WebPage/DynamicHttpApplicationStateTest.cs
@@ -0,0 +1,75 @@
+using System.Web.WebPages.Resources;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.WebPages.Test
+{
+ public class DynamicHttpApplicationStateTest
+ {
+ private static HttpApplicationStateBase CreateAppStateInstance()
+ {
+ return new HttpApplicationStateWrapper((HttpApplicationState)Activator.CreateInstance(typeof(HttpApplicationState), true));
+ }
+
+ [Fact]
+ public void DynamicTest()
+ {
+ HttpApplicationStateBase appState = CreateAppStateInstance();
+ dynamic d = new DynamicHttpApplicationState(appState);
+ d["x"] = "y";
+ Assert.Equal("y", d.x);
+ Assert.Equal("y", d[0]);
+ d.a = "b";
+ Assert.Equal("b", d["a"]);
+ d.Foo = "bar";
+ Assert.Equal("bar", d.Foo);
+ Assert.Null(d.XYZ);
+ Assert.Null(d["xyz"]);
+ Assert.Throws<ArgumentOutOfRangeException>(() => { var x = d[5]; });
+ var a = d.Baz = 42;
+ Assert.Equal(42, a);
+ var b = d["test"] = 666;
+ Assert.Equal(666, b);
+ }
+
+ [Fact]
+ public void InvalidNumberOfIndexes()
+ {
+ Assert.Throws<ArgumentException>(() =>
+ {
+ HttpApplicationStateBase appState = CreateAppStateInstance();
+ dynamic d = new DynamicHttpApplicationState(appState);
+ d[1, 2] = 3;
+ }, WebPageResources.DynamicDictionary_InvalidNumberOfIndexes);
+
+ Assert.Throws<ArgumentException>(() =>
+ {
+ HttpApplicationStateBase appState = CreateAppStateInstance();
+ dynamic d = new DynamicHttpApplicationState(appState);
+ var x = d[1, 2];
+ }, WebPageResources.DynamicDictionary_InvalidNumberOfIndexes);
+ }
+
+ [Fact]
+ public void InvalidTypeWhenSetting()
+ {
+ Assert.Throws<ArgumentException>(() =>
+ {
+ HttpApplicationStateBase appState = CreateAppStateInstance();
+ dynamic d = new DynamicHttpApplicationState(appState);
+ d[new object()] = 3;
+ }, WebPageResources.DynamicHttpApplicationState_UseOnlyStringToSet);
+ }
+
+ [Fact]
+ public void InvalidTypeWhenGetting()
+ {
+ Assert.Throws<ArgumentException>(() =>
+ {
+ HttpApplicationStateBase appState = CreateAppStateInstance();
+ dynamic d = new DynamicHttpApplicationState(appState);
+ var x = d[new object()];
+ }, WebPageResources.DynamicHttpApplicationState_UseOnlyStringOrIntToGet);
+ }
+ }
+}
diff --git a/test/System.Web.WebPages.Test/WebPage/DynamicPageDataDictionaryTest.cs b/test/System.Web.WebPages.Test/WebPage/DynamicPageDataDictionaryTest.cs
new file mode 100644
index 00000000..09c29149
--- /dev/null
+++ b/test/System.Web.WebPages.Test/WebPage/DynamicPageDataDictionaryTest.cs
@@ -0,0 +1,243 @@
+using System.Collections;
+using System.Collections.Generic;
+using System.Web.WebPages.Resources;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.WebPages.Test
+{
+ public class DynamicPageDataDictionaryTest
+ {
+ [Fact]
+ public void DynamicTest()
+ {
+ dynamic d = new DynamicPageDataDictionary<dynamic>(new PageDataDictionary<dynamic>());
+ d["x"] = "y";
+ Assert.Equal("y", d.x);
+ d.a = "b";
+ Assert.Equal("b", d["a"]);
+ d[0] = "zero";
+ Assert.Equal("zero", d[0]);
+ d.Foo = "bar";
+ Assert.Equal("bar", d.Foo);
+ var a = d.Baz = 42;
+ Assert.Equal(42, a);
+ var b = d[new object()] = 666;
+ Assert.Equal(666, b);
+ }
+
+ [Fact]
+ public void CastToDictionaryTest()
+ {
+ dynamic d = new DynamicPageDataDictionary<dynamic>(new PageDataDictionary<dynamic>());
+ var typeCheckCast = d as IDictionary<object, dynamic>;
+ var directCast = (IDictionary<object, dynamic>)d;
+
+ Assert.NotNull(typeCheckCast);
+ Assert.NotNull(directCast);
+ }
+
+ [Fact]
+ public void AddTest()
+ {
+ dynamic d = new DynamicPageDataDictionary<dynamic>(new PageDataDictionary<dynamic>());
+ var item = new KeyValuePair<object, object>("x", 2);
+ d.Add(item);
+ Assert.True(d.Contains(item));
+ Assert.Equal(2, d.x);
+ Assert.Equal(2, d["x"]);
+ }
+
+ [Fact]
+ public void AddTest1()
+ {
+ dynamic d = new DynamicPageDataDictionary<dynamic>(new PageDataDictionary<dynamic>());
+ object key = "x";
+ object value = 1;
+ d.Add(key, value);
+ Assert.True(d.ContainsKey(key));
+ Assert.Equal(1, d[key]);
+ Assert.Equal(1, d.x);
+ }
+
+ [Fact]
+ public void ClearTest()
+ {
+ dynamic d = new DynamicPageDataDictionary<dynamic>(new PageDataDictionary<dynamic>());
+ d.x = 2;
+ d.Clear();
+ Assert.Equal(0, d.Count);
+ }
+
+ [Fact]
+ public void ContainsTest()
+ {
+ dynamic d = new DynamicPageDataDictionary<dynamic>(new PageDataDictionary<dynamic>());
+ var item = new KeyValuePair<object, object>("x", 1);
+ d.Add(item);
+ Assert.True(d.Contains(item));
+ var item2 = new KeyValuePair<object, object>("y", 2);
+ Assert.False(d.Contains(item2));
+ }
+
+ [Fact]
+ public void ContainsKeyTest()
+ {
+ dynamic d = new DynamicPageDataDictionary<dynamic>(new PageDataDictionary<dynamic>());
+ object key = "x";
+ Assert.False(d.ContainsKey(key));
+ d.Add(key, 1);
+ Assert.True(d.ContainsKey(key));
+ Assert.True(d.ContainsKey("x"));
+ }
+
+ [Fact]
+ public void CopyToTest()
+ {
+ dynamic d = new DynamicPageDataDictionary<dynamic>(new PageDataDictionary<dynamic>());
+ KeyValuePair<object, object>[] array = new KeyValuePair<object, object>[1];
+ d.Add("x", 1);
+ d.CopyTo(array, 0);
+ Assert.Equal(new KeyValuePair<object, object>("x", 1), array[0]);
+ }
+
+ [Fact]
+ public void GetEnumeratorTest()
+ {
+ dynamic d = new DynamicPageDataDictionary<dynamic>(new PageDataDictionary<dynamic>());
+ d.Add("x", 1);
+ var e = d.GetEnumerator();
+ e.MoveNext();
+ Assert.Equal(new KeyValuePair<object, object>("x", 1), e.Current);
+ }
+
+ [Fact]
+ public void RemoveTest()
+ {
+ dynamic d = new DynamicPageDataDictionary<dynamic>(new PageDataDictionary<dynamic>());
+ var key = "x";
+ d.Add(key, 1);
+ d.Remove(key);
+ Assert.False(d.ContainsKey(key));
+ }
+
+ [Fact]
+ public void RemoveTest1()
+ {
+ dynamic d = new DynamicPageDataDictionary<dynamic>(new PageDataDictionary<dynamic>());
+ var item = new KeyValuePair<object, object>("x", 2);
+ d.Add(item);
+ Assert.True(d.Contains(item));
+ d.Remove(item);
+ Assert.False(d.Contains(item));
+ }
+
+ [Fact]
+ public void GetEnumeratorTest1()
+ {
+ dynamic d = new DynamicPageDataDictionary<dynamic>(new PageDataDictionary<dynamic>());
+ d.Add("x", 1);
+ var e = ((IEnumerable)d).GetEnumerator();
+ e.MoveNext();
+ Assert.Equal(new KeyValuePair<object, object>("x", 1), e.Current);
+ }
+
+ [Fact]
+ public void TryGetValueTest()
+ {
+ dynamic d = new DynamicPageDataDictionary<dynamic>(new PageDataDictionary<dynamic>());
+ object key = "x";
+ d.Add(key, 1);
+ object value = null;
+ Assert.True(d.TryGetValue(key, out value));
+ Assert.Equal(1, value);
+ }
+
+ [Fact]
+ public void CountTest()
+ {
+ var d = new DynamicPageDataDictionary<dynamic>(new PageDataDictionary<dynamic>());
+ dynamic dyn = d;
+ Assert.IsType<int>(dyn.Count);
+ d.Add("x", 1);
+ Assert.Equal(1, d.Count);
+ d.Add("y", 2);
+ Assert.Equal(2, d.Count);
+ dyn.Count = "foo";
+ Assert.Equal("foo", d["Count"]);
+ Assert.Equal(3, d.Count);
+ }
+
+ [Fact]
+ public void IsReadOnlyTest()
+ {
+ PageDataDictionary<dynamic> dict = new PageDataDictionary<dynamic>();
+ var d = new DynamicPageDataDictionary<dynamic>(dict);
+ dynamic dyn = d;
+ Assert.IsType<bool>(dyn.IsReadOnly);
+ Assert.Equal(dict.IsReadOnly, d.IsReadOnly);
+ dyn.IsReadOnly = "foo";
+ Assert.Equal("foo", d["IsReadOnly"]);
+ Assert.Equal(dict.IsReadOnly, d.IsReadOnly);
+ Assert.Equal(dict.IsReadOnly, dyn.IsReadOnly);
+ }
+
+ [Fact]
+ public void ItemTest()
+ {
+ dynamic d = new DynamicPageDataDictionary<dynamic>(new PageDataDictionary<dynamic>());
+ d.Add("x", 1);
+ d.Add("y", 2);
+ Assert.Equal(1, d["x"]);
+ Assert.Equal(2, d["y"]);
+ }
+
+ [Fact]
+ public void KeysTest()
+ {
+ var d = new DynamicPageDataDictionary<dynamic>(new PageDataDictionary<dynamic>());
+ dynamic dyn = d;
+ Assert.IsAssignableFrom<ICollection<object>>(dyn.Keys);
+ d.Add("x", 1);
+ d.Add("y", 2);
+ Assert.True(d.Keys.Contains("x"));
+ Assert.True(d.Keys.Contains("y"));
+ Assert.Equal(2, d.Keys.Count);
+ dyn.Keys = "foo";
+ Assert.Equal("foo", dyn["Keys"]);
+ Assert.Equal(3, d.Count);
+ }
+
+ [Fact]
+ public void ValuesTest()
+ {
+ var d = new DynamicPageDataDictionary<dynamic>(new PageDataDictionary<dynamic>());
+ dynamic dyn = d;
+ Assert.IsAssignableFrom<ICollection<dynamic>>(dyn.Values);
+ d.Add("x", 1);
+ d.Add("y", 2);
+ Assert.True(d.Values.Contains(1));
+ Assert.True(d.Values.Contains(2));
+ Assert.Equal(2, d.Values.Count);
+ dyn.Values = "foo";
+ Assert.Equal("foo", dyn["Values"]);
+ Assert.Equal(3, d.Count);
+ }
+
+ [Fact]
+ public void InvalidNumberOfIndexes()
+ {
+ Assert.Throws<ArgumentException>(() =>
+ {
+ dynamic d = new DynamicPageDataDictionary<dynamic>(new PageDataDictionary<dynamic>());
+ d[1, 2] = 3;
+ }, WebPageResources.DynamicDictionary_InvalidNumberOfIndexes);
+
+ Assert.Throws<ArgumentException>(() =>
+ {
+ dynamic d = new DynamicPageDataDictionary<dynamic>(new PageDataDictionary<dynamic>());
+ var x = d[1, 2];
+ }, WebPageResources.DynamicDictionary_InvalidNumberOfIndexes);
+ }
+ }
+}
diff --git a/test/System.Web.WebPages.Test/WebPage/FileExistenceCacheTest.cs b/test/System.Web.WebPages.Test/WebPage/FileExistenceCacheTest.cs
new file mode 100644
index 00000000..b8d26cad
--- /dev/null
+++ b/test/System.Web.WebPages.Test/WebPage/FileExistenceCacheTest.cs
@@ -0,0 +1,95 @@
+using System.Linq;
+using System.Threading;
+using System.Web.Hosting;
+using Moq;
+using Xunit;
+
+namespace System.Web.WebPages.Test
+{
+ public class FileExistenceCacheTest
+ {
+ [Fact]
+ public void ConstructorTest()
+ {
+ var ms = 1000;
+ var cache = new FileExistenceCache(null);
+ Assert.Null(cache.VirtualPathProvider);
+
+ var vpp = new Mock<VirtualPathProvider>().Object;
+ cache = new FileExistenceCache(vpp);
+ Assert.Equal(vpp, cache.VirtualPathProvider);
+ Assert.Equal(ms, cache.MilliSecondsBeforeReset);
+
+ ms = 9999;
+ cache = new FileExistenceCache(vpp, ms);
+ Assert.Equal(vpp, cache.VirtualPathProvider);
+ Assert.Equal(ms, cache.MilliSecondsBeforeReset);
+ }
+
+ [Fact]
+ public void TimeExceededFalseTest()
+ {
+ var ms = 100000;
+ var cache = new FileExistenceCache(GetVpp(), ms);
+ Assert.False(cache.TimeExceeded);
+ }
+
+ [Fact]
+ public void TimeExceededTrueTest()
+ {
+ var ms = 5;
+ var cache = new FileExistenceCache(GetVpp(), ms);
+ Thread.Sleep(300);
+ Assert.True(cache.TimeExceeded);
+ }
+
+ [Fact]
+ public void ResetTest()
+ {
+ var cache = new FileExistenceCache(GetVpp());
+ var cacheInternal = cache.CacheInternal;
+ cache.Reset();
+ Assert.NotSame(cacheInternal, cache.CacheInternal);
+ }
+
+ [Fact]
+ public void FileExistsTest()
+ {
+ var path = "~/index.cshtml";
+ var cache = new FileExistenceCache(GetVpp(path));
+ Assert.True(cache.FileExists(path));
+ Assert.False(cache.FileExists("~/test.cshtml"));
+ }
+
+ [Fact]
+ public void FileExistsVppLaterTest()
+ {
+ var path = "~/index.cshtml";
+ var cache = new FileExistenceCache(GetVpp(path));
+ Assert.True(cache.FileExists(path));
+ Assert.False(cache.FileExists("~/test.cshtml"));
+ }
+
+ [Fact]
+ public void FileExistsTimeExceededTest()
+ {
+ var path = "~/index.cshtml";
+ Utils.SetupVirtualPathInAppDomain(path, "");
+
+ var cache = new FileExistenceCache(GetVpp(path));
+ var cacheInternal = cache.CacheInternal;
+ cache.MilliSecondsBeforeReset = 5;
+ Thread.Sleep(300);
+ Assert.True(cache.FileExists(path));
+ Assert.False(cache.FileExists("~/test.cshtml"));
+ Assert.NotEqual(cacheInternal, cache.CacheInternal);
+ }
+
+ private static VirtualPathProvider GetVpp(params string[] files)
+ {
+ var vpp = new Mock<VirtualPathProvider>();
+ vpp.Setup(c => c.FileExists(It.IsAny<string>())).Returns<string>(p => files.Contains(p, StringComparer.OrdinalIgnoreCase));
+ return vpp.Object;
+ }
+ }
+}
diff --git a/test/System.Web.WebPages.Test/WebPage/LayoutTest.cs b/test/System.Web.WebPages.Test/WebPage/LayoutTest.cs
new file mode 100644
index 00000000..0d78321b
--- /dev/null
+++ b/test/System.Web.WebPages.Test/WebPage/LayoutTest.cs
@@ -0,0 +1,714 @@
+using System.Globalization;
+using System.Web.WebPages.Resources;
+using Moq;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.WebPages.Test
+{
+ public class LayoutTest
+ {
+ [Fact]
+ public void LayoutBasicTest()
+ {
+ var layoutPath = "~/Layout.cshtml";
+ LayoutBasicTestInternal(layoutPath);
+ }
+
+ [Fact]
+ public void RelativeLayoutPageTest()
+ {
+ var pagePath = "~/MyApp/index.cshtml";
+ var layoutPath = "~/MyFiles/Layout.cshtml";
+ var layoutPage = "../MyFiles/Layout.cshtml";
+ LayoutBasicTestInternal(layoutPath, pagePath, layoutPage);
+ }
+
+ [Fact]
+ public void AppRelativeLayoutPageTest()
+ {
+ var pagePath = "~/MyApp/index.cshtml";
+ var layoutPath = "~/MyFiles/Layout.cshtml";
+ var layoutPage = "~/MyFiles/Layout.cshtml";
+ LayoutBasicTestInternal(layoutPath, pagePath, layoutPage);
+ }
+
+ [Fact]
+ public void SourceFileWithLayoutPageTest()
+ {
+ // Arrange
+ var pagePath = "~/MyApp/index.cshtml";
+ var layoutPath = "~/MyFiles/Layout.cshtml";
+ var layoutPage = "~/MyFiles/Layout.cshtml";
+ var content = "hello world";
+ var title = "MyPage";
+ var page = CreatePageWithLayout(
+ p =>
+ {
+ p.PageData["Title"] = title;
+ p.WriteLiteral(content);
+ },
+ p =>
+ {
+ p.WriteLiteral(p.PageData["Title"]);
+ p.Write(p.RenderBody());
+ }, pagePath, layoutPath, layoutPage);
+ var request = new Mock<HttpRequestBase>();
+ request.SetupGet(c => c.Path).Returns("/myapp/index.cshtml");
+ request.SetupGet(c => c.RawUrl).Returns("http://localhost:8080/index.cshtml");
+ request.SetupGet(c => c.IsLocal).Returns(true);
+ request.Setup(c => c.MapPath(It.IsAny<string>())).Returns<string>(c => c);
+ request.Setup(c => c.Browser.IsMobileDevice).Returns(false);
+ request.Setup(c => c.Cookies).Returns(new HttpCookieCollection());
+
+ var result = Utils.RenderWebPage(page, request: request.Object);
+ Assert.Equal(2, page.PageContext.SourceFiles.Count);
+ Assert.True(page.PageContext.SourceFiles.Contains("~/MyApp/index.cshtml"));
+ Assert.True(page.PageContext.SourceFiles.Contains("~/MyFiles/Layout.cshtml"));
+ }
+
+ private static void LayoutBasicTestInternal(string layoutPath, string pagePath = "~/index.cshtml", string layoutPage = "Layout.cshtml")
+ {
+ // The page ~/index.cshtml does the following:
+ // PageData["Title"] = "MyPage";
+ // Layout = "Layout.cshtml";
+ // WriteLiteral("hello world");
+ //
+ // The layout page ~/Layout.cshtml does the following:
+ // WriteLiteral(Title);
+ // RenderBody();
+ //
+ // Expected rendered result is "MyPagehello world"
+
+ var content = "hello world";
+ var title = "MyPage";
+ var result = RenderPageWithLayout(
+ p =>
+ {
+ p.PageData["Title"] = title;
+ p.WriteLiteral(content);
+ },
+ p =>
+ {
+ p.WriteLiteral(p.PageData["Title"]);
+ p.Write(p.RenderBody());
+ },
+ pagePath, layoutPath, layoutPage);
+
+ Assert.Equal(title + content, result);
+ }
+
+ [Fact]
+ public void LayoutNestedTest()
+ {
+ // Testing nested layout pages
+ //
+ // The page ~/index.cshtml does the following:
+ // PageData["Title"] = "MyPage";
+ // Layout = "Layout1.cshtml";
+ // WriteLiteral("hello world");
+ //
+ // The first layout page ~/Layout1.cshtml does the following:
+ // Layout = "Layout2.cshtml";
+ // WriteLiteral("<layout1>");
+ // RenderBody();
+ // WriteLiteral("</layout1>");
+ //
+ // The second layout page ~/Layout2.cshtml does the following:
+ // WriteLiteral(Title);
+ // WriteLiteral("<layout2>");
+ // RenderBody();
+ // WriteLiteral("</layout2>");
+ //
+ // Expected rendered result is "MyPage<layout2><layout1>hello world</layout1></layout2>"
+
+ var layout2Path = "~/Layout2.cshtml";
+ var layout2 = Utils.CreatePage(
+ p =>
+ {
+ p.WriteLiteral(p.PageData["Title"]);
+ p.WriteLiteral("<layout2>");
+ p.Write(p.RenderBody());
+ p.WriteLiteral("</layout2>");
+ },
+ layout2Path);
+
+ var layout1Path = "~/Layout1.cshtml";
+ var layout1 = Utils.CreatePage(
+ p =>
+ {
+ p.Layout = "Layout2.cshtml";
+ p.WriteLiteral("<layout1>");
+ p.Write(p.RenderBody());
+ p.WriteLiteral("</layout1>");
+ },
+ layout1Path);
+
+ var page = Utils.CreatePage(
+ p =>
+ {
+ p.PageData["Title"] = "MyPage";
+ p.Layout = "Layout1.cshtml";
+ p.WriteLiteral("hello world");
+ });
+
+ Utils.AssignObjectFactoriesAndDisplayModeProvider(page, layout1, layout2);
+
+ var result = Utils.RenderWebPage(page);
+ Assert.Equal("MyPage<layout2><layout1>hello world</layout1></layout2>", result);
+ }
+
+ [Fact]
+ public void LayoutSectionsTest()
+ {
+ // Testing nested layout pages with sections
+ //
+ // The page ~/index.cshtml does the following:
+ // PageData["Title"] = "MyPage";
+ // Layout = "Layout1.cshtml";
+ // DefineSection("header1", () => {
+ // WriteLiteral("index header");
+ // });
+ // WriteLiteral("hello world");
+ // DefineSection("footer1", () => {
+ // WriteLiteral("index footer");
+ // });
+ //
+ // The first layout page ~/Layout1.cshtml does the following:
+ // Layout = "Layout2.cshtml";
+ // DefineSection("header2", () => {
+ // WriteLiteral("<layout1 header>");
+ // RenderSection("header1");
+ // WriteLiteral("</layout1 header>");
+ // });
+ // WriteLiteral("<layout1>");
+ // RenderBody();
+ // WriteLiteral("</layout1>");
+ // DefineSection("footer2", () => {
+ // WriteLiteral("<layout1 footer>");
+ // RenderSection("header2");
+ // WriteLiteral("</layout1 footer>");
+ // });
+ //
+ // The second layout page ~/Layout2.cshtml does the following:
+ // WriteLiteral(Title);
+ // WriteLiteral("\n<layout2 header>");
+ // RenderSection("header2");
+ // WriteLiteral("</layout2 header>\n");
+ // WriteLiteral("<layout2>");
+ // RenderBody();
+ // WriteLiteral("</layout2>\n");
+ // WriteLiteral("<layout2 footer>");
+ // RenderSection("footer");
+ // WriteLiteral("</layout2 footer>");
+ //
+ // Expected rendered result is:
+ // MyPage
+ // <layout2 header><layout1 header>index header</layout1 header></layout2 header>
+ // <layout2><layout1>hello world</layout1></layout2>"
+ // <layout2 footer><layout1 footer>index footer</layout1 footer></layout2 footer>
+
+ var layout2Path = "~/Layout2.cshtml";
+ var layout2 = Utils.CreatePage(
+ p =>
+ {
+ p.WriteLiteral(p.PageData["Title"]);
+ p.WriteLiteral("\r\n");
+ p.WriteLiteral("<layout2 header>");
+ p.Write(p.RenderSection("header2"));
+ p.WriteLiteral("</layout2 header>");
+ p.WriteLiteral("\r\n");
+
+ p.WriteLiteral("<layout2>");
+ p.Write(p.RenderBody());
+ p.WriteLiteral("</layout2>");
+ p.WriteLiteral("\r\n");
+
+ p.WriteLiteral("<layout2 footer>");
+ p.Write(p.RenderSection("footer2"));
+ p.WriteLiteral("</layout2 footer>");
+ },
+ layout2Path);
+
+ var layout1Path = "~/Layout1.cshtml";
+ var layout1 = Utils.CreatePage(
+ p =>
+ {
+ p.Layout = "Layout2.cshtml";
+ p.DefineSection("header2", () =>
+ {
+ p.WriteLiteral("<layout1 header>");
+ p.Write(p.RenderSection("header1"));
+ p.WriteLiteral("</layout1 header>");
+ });
+
+ p.WriteLiteral("<layout1>");
+ p.Write(p.RenderBody());
+ p.WriteLiteral("</layout1>");
+
+ p.DefineSection("footer2", () =>
+ {
+ p.WriteLiteral("<layout1 footer>");
+ p.Write(p.RenderSection("footer1"));
+ p.WriteLiteral("</layout1 footer>");
+ });
+ },
+ layout1Path);
+
+ var page = Utils.CreatePage(
+ p =>
+ {
+ p.PageData["Title"] = "MyPage";
+ p.Layout = "Layout1.cshtml";
+ p.DefineSection("header1", () => { p.WriteLiteral("index header"); });
+ p.WriteLiteral("hello world");
+ p.DefineSection("footer1", () => { p.WriteLiteral("index footer"); });
+ });
+
+ Utils.AssignObjectFactoriesAndDisplayModeProvider(page, layout1, layout2);
+
+ var result = Utils.RenderWebPage(page);
+ var expected = @"MyPage
+<layout2 header><layout1 header>index header</layout1 header></layout2 header>
+<layout2><layout1>hello world</layout1></layout2>
+<layout2 footer><layout1 footer>index footer</layout1 footer></layout2 footer>";
+ Assert.Equal(expected, result);
+ }
+
+ [Fact]
+ public void LayoutSectionsNestedNamesTest()
+ {
+ // Tests nested layout using the same section names at different levels.
+ //
+ // The page ~/index.cshtml does the following:
+ // Layout = "Layout1.cshtml";
+ // @section body {
+ // body in index
+ // }
+ //
+ // The page ~/layout1.cshtml does the following:
+ // Layout = "Layout2.cshtml";
+ // @section body {
+ // body in layout1
+ // @RenderSection("body")
+ // }
+ //
+ // The page ~/layout2.cshtml does the following:
+ // body in layout2
+ // @RenderSection("body")
+ //
+ // Expected rendered result is:
+ // body in layout2 body in layout1 body in index
+ var layout2Path = "~/Layout2.cshtml";
+ var layout2 = Utils.CreatePage(
+ p =>
+ {
+ p.WriteLiteral("body in layout2 ");
+ p.Write(p.RenderSection("body"));
+ },
+ layout2Path);
+ var layout1Path = "~/Layout1.cshtml";
+ var layout1 = Utils.CreatePage(
+ p =>
+ {
+ p.Layout = "Layout2.cshtml";
+ p.DefineSection("body", () =>
+ {
+ p.WriteLiteral("body in layout1 ");
+ p.Write(p.RenderSection("body"));
+ });
+ },
+ layout1Path);
+
+ var page = Utils.CreatePage(
+ p =>
+ {
+ p.Layout = "Layout1.cshtml";
+ p.DefineSection("body", () => { p.WriteLiteral("body in index"); });
+ });
+
+ Utils.AssignObjectFactoriesAndDisplayModeProvider(page, layout1, layout2);
+
+ var result = Utils.RenderWebPage(page);
+ var expected = "body in layout2 body in layout1 body in index";
+ Assert.Equal(expected, result);
+ }
+
+ [Fact]
+ public void CaseInsensitiveSectionNamesTest()
+ {
+ var page = CreatePageWithLayout(
+ p =>
+ {
+ p.Write("123");
+ p.DefineSection("abc", () => { p.Write("abc"); });
+ p.DefineSection("XYZ", () => { p.Write("xyz"); });
+ p.Write("456");
+ },
+ p =>
+ {
+ p.Write(p.RenderSection("AbC"));
+ p.Write(p.RenderSection("xyZ"));
+ p.Write(p.RenderBody());
+ });
+ var result = Utils.RenderWebPage(page);
+ var expected = "abcxyz123456";
+ Assert.Equal(expected, result);
+ }
+
+ [Fact]
+ public void MissingLayoutPageTest()
+ {
+ var layoutPage = "Layout.cshtml";
+ var page = Utils.CreatePage(
+ p =>
+ {
+ p.PageData["Title"] = "MyPage";
+ p.Layout = layoutPage;
+ });
+ var layoutPath1 = "~/Layout.cshtml";
+
+ Assert.Throws<HttpException>(() => Utils.RenderWebPage(page),
+ String.Format(CultureInfo.CurrentCulture, WebPageResources.WebPage_LayoutPageNotFound, layoutPage, layoutPath1));
+ }
+
+ [Fact]
+ public void RenderBodyAlreadyCalledTest()
+ {
+ // Layout page calls RenderBody more than once.
+ var page = CreatePageWithLayout(
+ p => { },
+ p =>
+ {
+ p.Write(p.RenderBody());
+ p.Write(p.RenderBody());
+ });
+
+ Assert.Throws<HttpException>(() => Utils.RenderWebPage(page), WebPageResources.WebPage_RenderBodyAlreadyCalled);
+ }
+
+ [Fact]
+ public void RenderBodyNotCalledTest()
+ {
+ // Page does not define any sections, but layout page does not call RenderBody
+ var layoutPath = "~/Layout.cshtml";
+ var page = CreatePageWithLayout(
+ p => { },
+ p => { },
+ layoutPath: layoutPath);
+
+ Assert.Throws<HttpException>(() => Utils.RenderWebPage(page),
+ String.Format(CultureInfo.CurrentCulture, WebPageResources.WebPage_RenderBodyNotCalled, layoutPath));
+ }
+
+ [Fact]
+ public void RenderBodyCalledDirectlyTest()
+ {
+ // A Page that is not a layout page calls the RenderBody method
+ var page = Utils.CreatePage(p => { p.RenderBody(); });
+ Assert.Throws<HttpException>(() => Utils.RenderWebPage(page),
+ String.Format(CultureInfo.CurrentCulture, WebPageResources.WebPage_CannotRequestDirectly, "~/index.cshtml", "RenderBody"));
+ }
+
+ [Fact]
+ public void RenderSectionCalledDirectlyTest()
+ {
+ // A Page that is not a layout page calls the RenderBody method
+ var page = Utils.CreatePage(p => { p.RenderSection(""); });
+ Assert.Throws<HttpException>(() => Utils.RenderWebPage(page),
+ String.Format(CultureInfo.CurrentCulture, WebPageResources.WebPage_CannotRequestDirectly, "~/index.cshtml", "RenderSection"));
+ }
+
+ [Fact]
+ public void SectionAlreadyDefinedTest()
+ {
+ // The page calls DefineSection more than once on the same name
+ var sectionName = "header";
+ var page = Utils.CreatePage(p =>
+ {
+ p.Layout = "Layout.cshtml";
+ p.DefineSection(sectionName, () => { });
+ p.DefineSection(sectionName, () => { });
+ });
+
+ Assert.Throws<HttpException>(() => Utils.RenderWebPage(page),
+ String.Format(CultureInfo.InvariantCulture, WebPageResources.WebPage_SectionAleadyDefined, sectionName));
+ }
+
+ [Fact]
+ public void SectionAlreadyDefinedCaseInsensitiveTest()
+ {
+ // The page calls DefineSection more than once on the same name but with different casing
+ var name1 = "section1";
+ var name2 = "SecTion1";
+
+ var page = Utils.CreatePage(p =>
+ {
+ p.Layout = "Layout.cshtml";
+ p.DefineSection(name1, () => { });
+ p.DefineSection(name2, () => { });
+ });
+
+ Assert.Throws<HttpException>(() => Utils.RenderWebPage(page),
+ String.Format(CultureInfo.InvariantCulture, WebPageResources.WebPage_SectionAleadyDefined, name2));
+ }
+
+ [Fact]
+ public void SectionNotDefinedTest()
+ {
+ // Layout page calls RenderSection on a name that has not been defined.
+ var sectionName = "NoSuchSection";
+ var page = CreatePageWithLayout(
+ p => { },
+ p => { p.Write(p.RenderSection(sectionName)); });
+
+ Assert.Throws<HttpException>(() => Utils.RenderWebPage(page),
+ String.Format(CultureInfo.InvariantCulture, WebPageResources.WebPage_SectionNotDefined, sectionName));
+ }
+
+ [Fact]
+ public void SectionAlreadyRenderedTest()
+ {
+ // Layout page calls RenderSection on the same name more than once.
+ var sectionName = "header";
+ var page = CreatePageWithLayout(
+ p =>
+ {
+ p.Layout = "Layout.cshtml";
+ p.DefineSection(sectionName, () => { });
+ },
+ p =>
+ {
+ p.Write(p.RenderSection(sectionName));
+ p.Write(p.RenderSection(sectionName));
+ });
+
+ Assert.Throws<HttpException>(() => Utils.RenderWebPage(page),
+ String.Format(CultureInfo.InvariantCulture, WebPageResources.WebPage_SectionAleadyRendered, sectionName));
+ }
+
+ [Fact]
+ public void SectionsNotRenderedTest()
+ {
+ // Layout page does not render all the defined sections.
+
+ var layoutPath = "~/Layout.cshtml";
+ var sectionName1 = "section1";
+ var sectionName2 = "section2";
+ var sectionName3 = "section3";
+ var sectionName4 = "section4";
+ var sectionName5 = "section5";
+ // A dummy section action that does nothing
+ SectionWriter sectionAction = () => { };
+
+ // The page defines 5 sections.
+ var page = CreatePageWithLayout(
+ p =>
+ {
+ p.DefineSection(sectionName1, sectionAction);
+ p.DefineSection(sectionName2, sectionAction);
+ p.DefineSection(sectionName3, sectionAction);
+ p.DefineSection(sectionName4, sectionAction);
+ p.DefineSection(sectionName5, sectionAction);
+ },
+ // The layout page renders only two of the sections
+ p =>
+ {
+ p.Write(p.RenderSection(sectionName2));
+ p.Write(p.RenderSection(sectionName4));
+ },
+ layoutPath: layoutPath);
+
+ var sectionsNotRendered = "section1; section3; section5";
+ Assert.Throws<HttpException>(() => Utils.RenderWebPage(page),
+ String.Format(CultureInfo.CurrentCulture, WebPageResources.WebPage_SectionsNotRendered, layoutPath, sectionsNotRendered));
+ }
+
+ [Fact]
+ public void SectionsNotRenderedRenderBodyTest()
+ {
+ // Layout page does not render all the defined sections, but it calls RenderBody.
+ var layoutPath = "~/Layout.cshtml";
+ var sectionName1 = "section1";
+ var sectionName2 = "section2";
+ // A dummy section action that does nothing
+ SectionWriter sectionAction = () => { };
+
+ var page = CreatePageWithLayout(
+ p =>
+ {
+ p.DefineSection(sectionName1, sectionAction);
+ p.DefineSection(sectionName2, sectionAction);
+ },
+ // The layout page only calls RenderBody
+ p => { p.Write(p.RenderBody()); },
+ layoutPath: layoutPath);
+
+ var sectionsNotRendered = "section1; section2";
+ Assert.Throws<HttpException>(() => Utils.RenderWebPage(page),
+ String.Format(CultureInfo.CurrentCulture, WebPageResources.WebPage_SectionsNotRendered, layoutPath, sectionsNotRendered));
+ }
+
+ [Fact]
+ public void InvalidPageTypeTest()
+ {
+ var layoutPath = "~/Layout.js";
+ var contents = "hello world";
+ var page = Utils.CreatePage(p =>
+ {
+ p.Layout = layoutPath;
+ p.Write(contents);
+ });
+ var layoutPage = new object();
+
+ var objectFactory = new Mock<IVirtualPathFactory>();
+ objectFactory.Setup(c => c.Exists(It.IsAny<string>())).Returns<string>(p => layoutPath.Equals(p, StringComparison.OrdinalIgnoreCase));
+ objectFactory.Setup(c => c.CreateInstance(It.IsAny<string>())).Returns<string>(_ => layoutPage as WebPageBase);
+ page.VirtualPathFactory = objectFactory.Object;
+
+ Assert.Throws<HttpException>(() => Utils.RenderWebPage(page),
+ String.Format(CultureInfo.CurrentCulture, WebPageResources.WebPage_InvalidPageType, layoutPath));
+
+ Assert.Throws<HttpException>(() => Utils.RenderWebPage(page),
+ String.Format(CultureInfo.CurrentCulture, WebPageResources.WebPage_InvalidPageType, layoutPath));
+ }
+
+ [Fact]
+ public void ValidPageTypeTest()
+ {
+ var layoutPath = "~/Layout.js";
+ var contents = "hello world";
+ var page = Utils.CreatePage(p =>
+ {
+ p.Layout = layoutPath;
+ p.Write(contents);
+ });
+ var layoutPage = Utils.CreatePage(p => p.WriteLiteral(p.RenderBody()), layoutPath);
+
+ Utils.AssignObjectFactoriesAndDisplayModeProvider(page, layoutPage);
+
+ Assert.Equal(contents, Utils.RenderWebPage(page));
+ }
+
+ [Fact]
+ public void IsSectionDefinedTest()
+ {
+ // Tests for the IsSectionDefined method
+
+ // Only sections named section1 and section3 are defined.
+ var page = CreatePageWithLayout(
+ p =>
+ {
+ p.DefineSection("section1", () => { });
+ p.DefineSection("section3", () => { });
+ },
+ p =>
+ {
+ p.Write(p.RenderSection("section1"));
+ p.Write(p.RenderSection("section3"));
+ p.Write("section1: " + p.IsSectionDefined("section1") + "; ");
+ p.Write("section2: " + p.IsSectionDefined("section2") + "; ");
+ p.Write("section3: " + p.IsSectionDefined("section3") + "; ");
+ p.Write("section4: " + p.IsSectionDefined("section4") + "; ");
+ });
+ var result = Utils.RenderWebPage(page);
+ var expected = "section1: True; section2: False; section3: True; section4: False; ";
+ Assert.Equal(expected, result);
+ }
+
+ [Fact]
+ public void OptionalSectionsTest()
+ {
+ // Only sections named section1 and section3 are defined.
+ var page = CreatePageWithLayout(
+ p =>
+ {
+ p.DefineSection("section1", () => { p.Write("section1 "); });
+ p.DefineSection("section3", () => { p.Write("section3"); });
+ },
+ p =>
+ {
+ p.Write(p.RenderSection("section1", required: false));
+ p.Write(p.RenderSection("section2", required: false));
+ p.Write(p.RenderSection("section3", required: false));
+ p.Write(p.RenderSection("section4", required: false));
+ });
+ var result = Utils.RenderWebPage(page);
+ var expected = "section1 section3";
+ Assert.Equal(expected, result);
+ }
+
+ [Fact]
+ public void PageDataTest()
+ {
+ // Layout page uses items in PageData set by content page
+ var contents = "my contents";
+ var page = CreatePageWithLayout(
+ p =>
+ {
+ p.PageData["contents"] = contents;
+ p.Write(" body");
+ },
+ p =>
+ {
+ p.Write(p.PageData["contents"]);
+ p.Write(p.RenderBody());
+ });
+ var result = Utils.RenderWebPage(page);
+ var expected = contents + " body";
+ Assert.Equal(expected, result);
+ }
+
+ [Fact]
+ public void RenderPageAndLayoutPage()
+ {
+ //Dev10 bug 928341 - a page that has a layout page, and the page calls RenderPage should not cause an error
+ var layoutPagePath = "~/layout.cshtml";
+ var page = Utils.CreatePage(p =>
+ {
+ p.DefineSection("foo", () => { p.Write("This is foo"); });
+ p.Write(p.RenderPage("bar.cshtml"));
+ p.Layout = layoutPagePath;
+ });
+ var layoutPage = Utils.CreatePage(p =>
+ {
+ p.Write(p.RenderBody());
+ p.Write(" ");
+ p.Write(p.RenderSection("foo"));
+ }, layoutPagePath);
+
+ var subPage = Utils.CreatePage(p => p.Write("This is bar"), "~/bar.cshtml");
+ Utils.AssignObjectFactoriesAndDisplayModeProvider(page, layoutPage, subPage);
+
+ var result = Utils.RenderWebPage(page);
+ var expected = "This is bar This is foo";
+ Assert.Equal(expected, result);
+ }
+
+ public static string RenderPageWithLayout(Action<WebPage> pageExecuteAction, Action<WebPage> layoutExecuteAction,
+ string pagePath = "~/index.cshtml", string layoutPath = "~/Layout.cshtml", string layoutPage = "Layout.cshtml")
+ {
+ var page = CreatePageWithLayout(pageExecuteAction, layoutExecuteAction, pagePath, layoutPath, layoutPage);
+ return Utils.RenderWebPage(page);
+ }
+
+ public static MockPage CreatePageWithLayout(Action<WebPage> pageExecuteAction, Action<WebPage> layoutExecuteAction,
+ string pagePath = "~/index.cshtml", string layoutPath = "~/Layout.cshtml", string layoutPageName = "Layout.cshtml")
+ {
+ var page = Utils.CreatePage(
+ p =>
+ {
+ p.Layout = layoutPageName;
+ pageExecuteAction(p);
+ },
+ pagePath);
+ var layoutPage = Utils.CreatePage(
+ p => { layoutExecuteAction(p); },
+ layoutPath);
+
+ Utils.AssignObjectFactoriesAndDisplayModeProvider(layoutPage, page);
+
+ return page;
+ }
+ }
+}
diff --git a/test/System.Web.WebPages.Test/WebPage/PageDataDictionaryTest.cs b/test/System.Web.WebPages.Test/WebPage/PageDataDictionaryTest.cs
new file mode 100644
index 00000000..8c370b91
--- /dev/null
+++ b/test/System.Web.WebPages.Test/WebPage/PageDataDictionaryTest.cs
@@ -0,0 +1,236 @@
+using System.Collections;
+using System.Collections.Generic;
+using System.Linq;
+using Xunit;
+
+namespace System.Web.WebPages.Test
+{
+ public class PageDataDictionaryTest
+ {
+ [Fact]
+ public void PageDataDictionaryConstructorTest()
+ {
+ var d = new PageDataDictionary<dynamic>();
+ Assert.NotNull(d.Data);
+ }
+
+ [Fact]
+ public void AddTest()
+ {
+ var d = new PageDataDictionary<dynamic>();
+ var item = new KeyValuePair<object, object>("x", 2);
+ d.Add(item);
+ Assert.True(d.Data.Contains(item));
+ }
+
+ [Fact]
+ public void AddTest1()
+ {
+ var d = new PageDataDictionary<dynamic>();
+ object key = "x";
+ object value = 1;
+ d.Add(key, value);
+ Assert.True(d.Data.ContainsKey(key));
+ Assert.Equal(1, d.Data[key]);
+ // Use uppercase string key
+ Assert.Equal(1, d.Data["X"]);
+ }
+
+ [Fact]
+ public void ClearTest()
+ {
+ var d = new PageDataDictionary<dynamic>();
+ d.Add("x", 2);
+ d.Clear();
+ Assert.Equal(0, d.Data.Count);
+ }
+
+ [Fact]
+ public void ContainsTest()
+ {
+ var d = new PageDataDictionary<dynamic>();
+ var item = new KeyValuePair<object, object>("x", 1);
+ d.Add(item);
+ Assert.True(d.Contains(item));
+ var item2 = new KeyValuePair<object, object>("y", 2);
+ Assert.False(d.Contains(item2));
+ }
+
+ [Fact]
+ public void ContainsKeyTest()
+ {
+ var d = new PageDataDictionary<dynamic>();
+ object key = "x";
+ Assert.False(d.ContainsKey(key));
+ d.Add(key, 1);
+ Assert.True(d.ContainsKey(key));
+ Assert.True(d.ContainsKey("X"));
+ }
+
+ [Fact]
+ public void CopyToTest()
+ {
+ var d = new PageDataDictionary<dynamic>();
+ KeyValuePair<object, object>[] array = new KeyValuePair<object, object>[1];
+ d.Add("x", 1);
+ d.CopyTo(array, 0);
+ Assert.Equal(new KeyValuePair<object, object>("x", 1), array[0]);
+ }
+
+ [Fact]
+ public void GetEnumeratorTest()
+ {
+ var d = new PageDataDictionary<dynamic>();
+ d.Add("x", 1);
+ var e = d.GetEnumerator();
+ e.MoveNext();
+ Assert.Equal<object>(new KeyValuePair<object, object>("x", 1), e.Current);
+ }
+
+ [Fact]
+ public void RemoveTest()
+ {
+ var d = new PageDataDictionary<dynamic>();
+ var key = "x";
+ d.Add(key, 1);
+ d.Remove(key);
+ Assert.False(d.Data.ContainsKey(key));
+ }
+
+ [Fact]
+ public void RemoveTest1()
+ {
+ var d = new PageDataDictionary<dynamic>();
+ var item = new KeyValuePair<object, object>("x", 2);
+ d.Add(item);
+ Assert.True(d.Contains(item));
+ d.Remove(item);
+ Assert.False(d.Contains(item));
+ }
+
+ [Fact]
+ public void GetEnumeratorTest1()
+ {
+ var d = new PageDataDictionary<dynamic>();
+ d.Add("x", 1);
+ var e = ((IEnumerable)d).GetEnumerator();
+ e.MoveNext();
+ Assert.Equal(new KeyValuePair<object, object>("x", 1), e.Current);
+ }
+
+ [Fact]
+ public void TryGetValueTest()
+ {
+ var d = new PageDataDictionary<dynamic>();
+ object key = "x";
+ d.Add(key, 1);
+ object value = null;
+ Assert.True(d.TryGetValue(key, out value));
+ Assert.Equal(1, value);
+ }
+
+ [Fact]
+ public void CountTest()
+ {
+ var d = new PageDataDictionary<dynamic>();
+ d.Add("x", 1);
+ Assert.Equal(1, d.Count);
+ d.Add("y", 2);
+ Assert.Equal(2, d.Count);
+ }
+
+ [Fact]
+ public void IsReadOnlyTest()
+ {
+ var d = new PageDataDictionary<dynamic>();
+ Assert.Equal(d.Data.IsReadOnly, d.IsReadOnly);
+ }
+
+ [Fact]
+ public void ItemTest()
+ {
+ var d = new PageDataDictionary<dynamic>();
+ d.Add("x", 1);
+ d.Add("y", 2);
+ Assert.Equal(1, d["x"]);
+ Assert.Equal(2, d["y"]);
+ }
+
+ [Fact]
+ public void KeysTest()
+ {
+ var d = new PageDataDictionary<dynamic>();
+ d.Add("x", 1);
+ d.Add("y", 2);
+ Assert.True(d.Keys.Contains("x"));
+ Assert.True(d.Keys.Contains("y"));
+ Assert.Equal(2, d.Keys.Count);
+ }
+
+ [Fact]
+ public void ValuesTest()
+ {
+ var d = new PageDataDictionary<dynamic>();
+ d.Add("x", 1);
+ d.Add("y", 2);
+ Assert.True(d.Values.Contains(1));
+ Assert.True(d.Values.Contains(2));
+ Assert.Equal(2, d.Values.Count);
+ }
+
+ [Fact]
+ public void KeysReturnsNumericKeysIfPresent()
+ {
+ // Arrange
+ var d = new PageDataDictionary<string>();
+
+ // Act
+ d[100] = "foo";
+ d[200] = "bar";
+
+ // Assert
+ Assert.Equal(2, d.Keys.Count);
+ Assert.Equal(100, d.Keys.ElementAt(0));
+ Assert.Equal(200, d.Keys.ElementAt(1));
+ }
+
+ [Fact]
+ public void KeysReturnsUniqueSetOfValues()
+ {
+ // Act
+ var innerDict = new Dictionary<string, object>()
+ {
+ { "my-key", "value" },
+ { "test", "test-val" }
+ };
+ var dict = PageDataDictionary<dynamic>.CreatePageDataFromParameters(new PageDataDictionary<dynamic>(), innerDict);
+
+ // Act
+ dict.Add("my-key", "added-value");
+ dict["foo"] = "bar";
+
+ // Assert
+ Assert.Equal(4, dict.Keys.Count);
+ Assert.Equal("my-key", dict.Keys.ElementAt(0));
+ Assert.Equal("test", dict.Keys.ElementAt(1));
+ Assert.Equal(0, dict.Keys.ElementAt(2));
+ Assert.Equal("foo", dict.Keys.ElementAt(3));
+ Assert.Equal(dict[0], innerDict);
+ }
+
+ [Fact]
+ public void AddValueOverwritesIndexDictionaryIfKeyExists()
+ {
+ // Act
+ var dict = PageDataDictionary<dynamic>.CreatePageDataFromParameters(new PageDataDictionary<dynamic>(), new[] { "index-0-orig", "index-1" });
+
+ // Act
+ dict[0] = "index-0-new";
+
+ // Assert
+ Assert.Equal(2, dict.Keys.Count);
+ Assert.Equal("index-0-new", dict[0]);
+ Assert.Equal("index-1", dict[1]);
+ }
+ }
+}
diff --git a/test/System.Web.WebPages.Test/WebPage/RenderPageTest.cs b/test/System.Web.WebPages.Test/WebPage/RenderPageTest.cs
new file mode 100644
index 00000000..f9c84af2
--- /dev/null
+++ b/test/System.Web.WebPages.Test/WebPage/RenderPageTest.cs
@@ -0,0 +1,867 @@
+using System.Collections.Generic;
+using System.Globalization;
+using System.IO;
+using System.Web.WebPages.Resources;
+using System.Web.WebPages.TestUtils;
+using Moq;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.WebPages.Test
+{
+ public class RenderPageTest
+ {
+ [Fact]
+ public void RenderBasicTest()
+ {
+ // A simple page that does the following:
+ // @{ PageData["Title"] = "MyPage"; }
+ // @PageData["Title"]
+ // hello world
+ //
+ // Expected rendered result is "MyPagehello world"
+
+ var content = "hello world";
+ var title = "MyPage";
+ var result = Utils.RenderWebPage(
+ p =>
+ {
+ p.PageData["Title"] = title;
+ p.Write(p.PageData["Title"]);
+ p.Write(content);
+ });
+
+ Assert.Equal(title + content, result);
+ }
+
+ [Fact]
+ public void RenderDynamicDictionaryBasicTest()
+ {
+ // A simple page that does the following:
+ // @{ Page.Title = "MyPage"; }
+ // @Page.Title
+ // hello world
+ //
+ // Expected rendered result is "MyPagehello world"
+
+ var content = "hello world";
+ var title = "MyPage";
+ var result = Utils.RenderWebPage(
+ p =>
+ {
+ p.Page.Title = title;
+ p.Write(p.Page.Title);
+ p.Write(content);
+ });
+
+ Assert.Equal(title + content, result);
+ }
+
+ [Fact]
+ public void RenderPageBasicTest()
+ {
+ // ~/index.cshtml does the following:
+ // hello
+ // @RenderPage("subpage.cshtml")
+ //
+ // ~/subpage.cshtml does the following:
+ // world
+ //
+ // Expected output is "helloworld"
+
+ var result = Utils.RenderWebPageWithSubPage(
+ p =>
+ {
+ p.Write("hello");
+ p.Write(p.RenderPage("subpage.cshtml"));
+ },
+ p => { p.Write("world"); });
+ Assert.Equal("helloworld", result);
+ }
+
+ [Fact]
+ public void RenderPageAnonymousTypeTest()
+ {
+ // Test for passing an anonymous type object as an argument to RenderPage
+ //
+ // ~/index.cshtml does the following:
+ // @RenderPage("subpage.cshtml", new { HelloKey = "hellovalue", MyKey = "myvalue" })
+ //
+ // ~/subpage.cshtml does the following:
+ // @PageData["HelloKey"] @PageData["MyKey"] @Model.HelloKey @Model.MyKey
+ //
+ // Expected result: hellovalue myvalue hellovalue myvalue
+ var result = Utils.RenderWebPageWithSubPage(
+ p => { p.Write(p.RenderPage("subpage.cshtml", new { HelloKey = "hellovalue", MyKey = "myvalue" })); },
+ p =>
+ {
+ p.Write(p.PageData["HelloKey"]);
+ p.Write(" ");
+ p.Write(p.PageData["MyKey"]);
+ p.Write(" ");
+ p.Write(p.Model.HelloKey);
+ p.Write(" ");
+ p.Write(p.Model.MyKey);
+ });
+ Assert.Equal("hellovalue myvalue hellovalue myvalue", result);
+ }
+
+ [Fact]
+ public void RenderPageDynamicDictionaryAnonymousTypeTest()
+ {
+ // Test for passing an anonymous type object as an argument to RenderPage
+ //
+ // ~/index.cshtml does the following:
+ // @RenderPage("subpage.cshtml", new { HelloKey = "hellovalue", MyKey = "myvalue" })
+ //
+ // ~/subpage.cshtml does the following:
+ // @Page.HelloKey @Page.MyKey @Model.HelloKey @Model.MyKey
+ //
+ // Expected result: hellovalue myvalue hellovalue myvalue
+ var result = Utils.RenderWebPageWithSubPage(
+ p => { p.Write(p.RenderPage("subpage.cshtml", new { HelloKey = "hellovalue", MyKey = "myvalue" })); },
+ p =>
+ {
+ p.Write(p.Page.HelloKey);
+ p.Write(" ");
+ p.Write(p.Page.MyKey);
+ p.Write(" ");
+ p.Write(p.Model.HelloKey);
+ p.Write(" ");
+ p.Write(p.Model.MyKey);
+ });
+ Assert.Equal("hellovalue myvalue hellovalue myvalue", result);
+ }
+
+ [Fact]
+ public void RenderPageDictionaryTest()
+ {
+ // Test for passing a dictionary instance as an argument to RenderPage
+ //
+ // ~/index.cshtml does the following:
+ // @RenderPage("subpage.cshtml", new Dictionary<string, object>(){ { "foo", 1 }, { "bar", "hello"} })
+ //
+ // ~/subpage.cshtml does the following:
+ // @PageData["foo"] @PageData["bar"] @PageData[0]
+ //
+ // Expected result: 1 hello System.Collections.Generic.Dictionary`2[System.String,System.Object]
+
+ var result = Utils.RenderWebPageWithSubPage(
+ p => { p.Write(p.RenderPage("subpage.cshtml", new Dictionary<string, object>() { { "foo", 1 }, { "bar", "hello" } })); },
+ p =>
+ {
+ p.Write(p.PageData["foo"]);
+ p.Write(" ");
+ p.Write(p.PageData["bar"]);
+ p.Write(" ");
+ p.Write(p.PageData[0]);
+ });
+ Assert.Equal("1 hello System.Collections.Generic.Dictionary`2[System.String,System.Object]", result);
+ }
+
+ [Fact]
+ public void RenderPageDynamicDictionaryTest()
+ {
+ // Test for passing a dictionary instance as an argument to RenderPage
+ //
+ // ~/index.cshtml does the following:
+ // @RenderPage("subpage.cshtml", new Dictionary<string, object>(){ { "foo", 1 }, { "bar", "hello"} })
+ //
+ // ~/subpage.cshtml does the following:
+ // @Page.foo @Page.bar @Page[0]
+ //
+ // Expected result: 1 hello System.Collections.Generic.Dictionary`2[System.String,System.Object]
+
+ var result = Utils.RenderWebPageWithSubPage(
+ p => { p.Write(p.RenderPage("subpage.cshtml", new Dictionary<string, object>() { { "foo", 1 }, { "bar", "hello" } })); },
+ p =>
+ {
+ p.Write(p.Page.foo);
+ p.Write(" ");
+ p.Write(p.Page.bar);
+ p.Write(" ");
+ p.Write(p.Page[0]);
+ });
+ Assert.Equal("1 hello System.Collections.Generic.Dictionary`2[System.String,System.Object]", result);
+ }
+
+ [Fact]
+ public void RenderPageListTest()
+ {
+ // Test for passing a list of arguments to RenderPage
+ //
+ // ~/index.cshtml does the following:
+ // @RenderPage("subpage.cshtml", "hello", "world", 1, 2, 3)
+ //
+ // ~/subpage.cshtml does the following:
+ // @PageData[0] @PageData[1] @PageData[2] @PageData[3] @PageData[4]
+ //
+ // Expected result: hello world 1 2 3
+
+ var result = Utils.RenderWebPageWithSubPage(
+ p => { p.Write(p.RenderPage("subpage.cshtml", "hello", "world", 1, 2, 3)); },
+ p =>
+ {
+ p.Write(p.PageData[0]);
+ p.Write(" ");
+ p.Write(p.PageData[1]);
+ p.Write(" ");
+ p.Write(p.PageData[2]);
+ p.Write(" ");
+ p.Write(p.PageData[3]);
+ p.Write(" ");
+ p.Write(p.PageData[4]);
+ });
+ Assert.Equal("hello world 1 2 3", result);
+ }
+
+ [Fact]
+ public void RenderPageDynamicDictionaryListTest()
+ {
+ // Test for passing a list of arguments to RenderPage
+ //
+ // ~/index.cshtml does the following:
+ // @RenderPage("subpage.cshtml", "hello", "world", 1, 2, 3)
+ //
+ // ~/subpage.cshtml does the following:
+ // @Page[0] @Page[1] @Page[2] @Page[3] @Page[4]
+ //
+ // Expected result: hello world 1 2 3
+
+ var result = Utils.RenderWebPageWithSubPage(
+ p => { p.Write(p.RenderPage("subpage.cshtml", "hello", "world", 1, 2, 3)); },
+ p =>
+ {
+ p.Write(p.Page[0]);
+ p.Write(" ");
+ p.Write(p.Page[1]);
+ p.Write(" ");
+ p.Write(p.Page[2]);
+ p.Write(" ");
+ p.Write(p.Page[3]);
+ p.Write(" ");
+ p.Write(p.Page[4]);
+ });
+ Assert.Equal("hello world 1 2 3", result);
+ }
+
+ private class Person
+ {
+ public string FirstName { get; set; }
+ }
+
+ [Fact]
+ public void RenderPageDynamicValueTest()
+ {
+ // Test that PageData[key] returns a dynamic value.
+ // ~/index.cshtml does the following:
+ // @RenderPage("subpage.cshtml", new Person(){ FirstName="MyFirstName" })
+ //
+ // ~/subpage.cshtml does the following:
+ // @PageData[0].FirstName
+ //
+ // Expected result: MyFirstName
+ var result = Utils.RenderWebPageWithSubPage(
+ p => { p.Write(p.RenderPage("subpage.cshtml", new Person() { FirstName = "MyFirstName" })); },
+ p => { p.Write(p.PageData[0].FirstName); });
+ Assert.Equal("MyFirstName", result);
+ }
+
+ [Fact]
+ public void RenderPageDynamicDictionaryDynamicValueTest()
+ {
+ // Test that PageData[key] returns a dynamic value.
+ // ~/index.cshtml does the following:
+ // @RenderPage("subpage.cshtml", new Person(){ FirstName="MyFirstName" })
+ //
+ // ~/subpage.cshtml does the following:
+ // @Page[0].FirstName
+ //
+ // Expected result: MyFirstName
+ var result = Utils.RenderWebPageWithSubPage(
+ p => { p.Write(p.RenderPage("subpage.cshtml", new Person() { FirstName = "MyFirstName" })); },
+ p => { p.Write(p.Page[0].FirstName); });
+ Assert.Equal("MyFirstName", result);
+ }
+
+ [Fact]
+ public void PageDataSetByParentTest()
+ {
+ // Items set in the PageData should be accessible by the subpage
+ var result = Utils.RenderWebPageWithSubPage(
+ p =>
+ {
+ p.PageData["test"] = "hello";
+ p.Write(p.RenderPage("subpage.cshtml"));
+ },
+ p => { p.Write(p.PageData["test"]); });
+ Assert.Equal("hello", result);
+ }
+
+ [Fact]
+ public void DynamicDictionarySetByParentTest()
+ {
+ // Items set in the PageData should be accessible by the subpage
+ var result = Utils.RenderWebPageWithSubPage(
+ p =>
+ {
+ p.Page.test = "hello";
+ p.Write(p.RenderPage("subpage.cshtml"));
+ },
+ p => { p.Write(p.Page.test); });
+ Assert.Equal("hello", result);
+ }
+
+ [Fact]
+ public void OverridePageDataSetByParentTest()
+ {
+ // Items set in the PageData should be accessible by the subpage unless
+ // overriden by parameters passed into RenderPage, in which case the
+ // specified value should be used.
+ var result = Utils.RenderWebPageWithSubPage(
+ p =>
+ {
+ p.PageData["test"] = "hello";
+ p.Write(p.RenderPage("subpage.cshtml", new { Test = "world" }));
+ },
+ p =>
+ {
+ p.Write(p.PageData["test"]);
+ p.Write(p.PageData[0].Test);
+ });
+ Assert.Equal("worldworld", result);
+ }
+
+ [Fact]
+ public void OverrideDynamicDictionarySetByParentTest()
+ {
+ // Items set in the PageData should be accessible by the subpage unless
+ // overriden by parameters passed into RenderPage, in which case the
+ // specified value should be used.
+ var result = Utils.RenderWebPageWithSubPage(
+ p =>
+ {
+ p.PageData["test"] = "hello";
+ p.Write(p.RenderPage("subpage.cshtml", new { Test = "world" }));
+ },
+ p =>
+ {
+ p.Write(p.Page.test);
+ p.Write(p.Page[0].Test);
+ });
+ Assert.Equal("worldworld", result);
+ }
+
+ [Fact]
+ public void RenderPageMissingKeyTest()
+ {
+ // Test that using PageData with a missing key returns null
+ //
+ // ~/index.cshtml does the following:
+ // @RenderPage("subpage.cshtml", new Dictionary<string, object>(){ { "foo", 1 }, { "bar", "hello"} })
+ // @RenderPage("subpage.cshtml", "x", "y", "z")
+ //
+ // ~/subpage.cshtml does the following:
+ // @(PageData[1] ?? "null")
+ // @(PageData["bar"] ?? "null")
+ //
+ // Expected result: null hello y null
+
+ var result = Utils.RenderWebPageWithSubPage(
+ p =>
+ {
+ p.Write(p.RenderPage("subpage.cshtml", new Dictionary<string, object>() { { "foo", 1 }, { "bar", "hello" } }));
+ p.Write(p.RenderPage("subpage.cshtml", "x", "y", "z"));
+ },
+ p =>
+ {
+ p.Write(p.PageData[1] ?? "null1");
+ p.Write(" ");
+ p.Write(p.PageData["bar"] ?? "null2");
+ p.Write(" ");
+ });
+ Assert.Equal("null1 hello y null2 ", result);
+ }
+
+ [Fact]
+ public void RenderPageDynamicDictionaryMissingKeyTest()
+ {
+ // Test that using PageData with a missing key returns null
+ //
+ // ~/index.cshtml does the following:
+ // @RenderPage("subpage.cshtml", new Dictionary<string, object>(){ { "foo", 1 }, { "bar", "hello"} })
+ // @RenderPage("subpage.cshtml", "x", "y", "z")
+ //
+ // ~/subpage.cshtml does the following:
+ // @(Page[1] ?? "null")
+ // @(Page.bar ?? "null")
+ //
+ // Expected result: null hello y null
+
+ Action<WebPage> subPage = p =>
+ {
+ p.Write(p.Page[1] ?? "null1");
+ p.Write(" ");
+ p.Write(p.Page.bar ?? "null2");
+ p.Write(" ");
+ };
+ var result = Utils.RenderWebPageWithSubPage(
+ p => { p.Write(p.RenderPage("subpage.cshtml", new Dictionary<string, object>() { { "foo", 1 }, { "bar", "hello" } })); }, subPage);
+ Assert.Equal("null1 hello ", result);
+ result = Utils.RenderWebPageWithSubPage(
+ p => { p.Write(p.RenderPage("subpage.cshtml", "x", "y", "z")); }, subPage);
+ Assert.Equal("y null2 ", result);
+ }
+
+ [Fact]
+ public void RenderPageNoArgumentsTest()
+ {
+ // Test that using PageData within the calling page, and also
+ // within the subppage when the calling page doesn't provide any arguments
+ //
+ // ~/index.cshtml does the following:
+ // @(PageData["foo"] ?? "null1")
+ // @RenderPage("subpage.cshtml")
+ //
+ // ~/subpage.cshtml does the following:
+ // @(PageData[1] ?? "null2")
+ // @(PageData["bar"] ?? "null3")
+ //
+ // Expected result: null1 null2 null3
+
+ var result = Utils.RenderWebPageWithSubPage(
+ p =>
+ {
+ p.Write(p.PageData["foo"] ?? "null1 ");
+ p.Write(p.RenderPage("subpage.cshtml"));
+ },
+ p =>
+ {
+ p.Write(p.PageData[1] ?? "null2");
+ p.Write(" ");
+ p.Write(p.PageData["bar"] ?? "null3");
+ });
+ Assert.Equal("null1 null2 null3", result);
+ }
+
+ [Fact]
+ public void RenderPageDynamicDictionaryNoArgumentsTest()
+ {
+ // Test that using PageData within the calling page, and also
+ // within the subppage when the calling page doesn't provide any arguments
+ //
+ // ~/index.cshtml does the following:
+ // @(Page.foo ?? "null1")
+ // @RenderPage("subpage.cshtml")
+ //
+ // ~/subpage.cshtml does the following:
+ // @(Page[1] ?? "null2")
+ // @(Page.bar ?? "null3")
+ //
+ // Expected result: null1 null2 null3
+
+ var result = Utils.RenderWebPageWithSubPage(
+ p =>
+ {
+ p.Write(p.Page.foo ?? "null1 ");
+ p.Write(p.RenderPage("subpage.cshtml"));
+ },
+ p =>
+ {
+ p.Write(p.Page[1] ?? "null2");
+ p.Write(" ");
+ p.Write(p.Page.bar ?? "null3");
+ });
+ Assert.Equal("null1 null2 null3", result);
+ }
+
+ [Fact]
+ public void RenderPageNestedSubPageListTest()
+ {
+ // Test that PageData for each level of nesting returns the values as specified in the
+ // previous calling page.
+ //
+ // ~/index.cshtml does the following:
+ // @(PageData["foo"] ?? "null")
+ // @RenderPage("subpage1.cshtml", "a", "b", "c")
+ //
+ // ~/subpage1.cshtml does the following:
+ // @(PageData[0] ?? "sub1null0")
+ // @(PageData[1] ?? "sub1null1")
+ // @(PageData[2] ?? "sub1null2")
+ // @(PageData[3] ?? "sub1null3")
+ // @RenderPage("subpage2.cshtml", "x", "y", "z")
+ //
+ // ~/subpage2.cshtml does the following:
+ // @(PageData[0] ?? "sub2null0")
+ // @(PageData[1] ?? "sub2null1")
+ // @(PageData[2] ?? "sub2null2")
+ // @(PageData[3] ?? "sub2null3")
+ //
+ // Expected result: null a b c sub1null3 x y z sub2null3
+ var page = Utils.CreatePage(
+ p =>
+ {
+ p.Write(p.PageData["foo"] ?? "null ");
+ p.Write(p.RenderPage("subpage1.cshtml", "a", "b", "c"));
+ });
+ var subpage1Path = "~/subpage1.cshtml";
+ var subpage1 = Utils.CreatePage(
+ p =>
+ {
+ p.Write(p.PageData[0] ?? "sub1null0");
+ p.Write(" ");
+ p.Write(p.PageData[1] ?? "sub1null1");
+ p.Write(" ");
+ p.Write(p.PageData[2] ?? "sub1null2");
+ p.Write(" ");
+ p.Write(p.PageData[3] ?? "sub1null3");
+ p.Write(" ");
+ p.Write(p.RenderPage("subpage2.cshtml", "x", "y", "z"));
+ }, subpage1Path);
+ var subpage2Path = "~/subpage2.cshtml";
+ var subpage2 = Utils.CreatePage(
+ p =>
+ {
+ p.Write(p.PageData[0] ?? "sub2null0");
+ p.Write(" ");
+ p.Write(p.PageData[1] ?? "sub2null1");
+ p.Write(" ");
+ p.Write(p.PageData[2] ?? "sub2null2");
+ p.Write(" ");
+ p.Write(p.PageData[3] ?? "sub2null3");
+ }, subpage2Path);
+
+ Utils.AssignObjectFactoriesAndDisplayModeProvider(page, subpage1, subpage2);
+
+ var result = Utils.RenderWebPage(page);
+ Assert.Equal("null a b c sub1null3 x y z sub2null3", result);
+ }
+
+ [Fact]
+ public void RenderPageNestedSubPageAnonymousTypeTest()
+ {
+ // Test that PageData for each level of nesting returns the values as specified in the
+ // previous calling page.
+ //
+ // ~/index.cshtml does the following:
+ // @(PageData["foo"] ?? "null")
+ // @RenderPage("subpage.cshtml", new { foo = 1 , bar = "hello" })
+ //
+ // ~/subpage1.cshtml does the following:
+ // @(PageData["foo"] ?? "sub1nullfoo")
+ // @(PageData["bar"] ?? "sub1nullbar")
+ // @(PageData["x"] ?? "sub1nullx")
+ // @(PageData["y"] ?? "sub1nully")
+ // @RenderPage("subpage2.cshtml", new { bar = "world", x = "good", y = "bye"})
+ //
+ // ~/subpage2.cshtml does the following:
+ // @(PageData["foo"] ?? "sub2nullfoo")
+ // @(PageData["bar"] ?? "sub2nullbar")
+ // @(PageData["x"] ?? "sub2nullx")
+ // @(PageData["y"] ?? "sub2nully")
+ //
+ // Expected result: null 1 hello sub1nullx sub1nully sub2nullfoo world good bye
+ var page = Utils.CreatePage(
+ p =>
+ {
+ p.Write(p.PageData["foo"] ?? "null ");
+ p.Write(p.RenderPage("subpage1.cshtml", new { foo = 1, bar = "hello" }));
+ });
+ var subpage1Path = "~/subpage1.cshtml";
+ var subpage1 = Utils.CreatePage(
+ p =>
+ {
+ p.Write(p.PageData["foo"] ?? "sub1nullfoo");
+ p.Write(" ");
+ p.Write(p.PageData["bar"] ?? "sub1nullbar");
+ p.Write(" ");
+ p.Write(p.PageData["x"] ?? "sub1nullx");
+ p.Write(" ");
+ p.Write(p.PageData["y"] ?? "sub1nully");
+ p.Write(" ");
+ p.Write(p.RenderPage("subpage2.cshtml", new { bar = "world", x = "good", y = "bye" }));
+ }, subpage1Path);
+ var subpage2Path = "~/subpage2.cshtml";
+ var subpage2 = Utils.CreatePage(
+ p =>
+ {
+ p.Write(p.PageData["foo"] ?? "sub2nullfoo");
+ p.Write(" ");
+ p.Write(p.PageData["bar"] ?? "sub2nullbar");
+ p.Write(" ");
+ p.Write(p.PageData["x"] ?? "sub2nullx");
+ p.Write(" ");
+ p.Write(p.PageData["y"] ?? "sub2nully");
+ }, subpage2Path);
+
+ Utils.AssignObjectFactoriesAndDisplayModeProvider(subpage1, subpage2, page);
+
+ var result = Utils.RenderWebPage(page);
+ Assert.Equal("null 1 hello sub1nullx sub1nully sub2nullfoo world good bye", result);
+ }
+
+ [Fact]
+ public void RenderPageNestedSubPageDictionaryTest()
+ {
+ // Test that PageData for each level of nesting returns the values as specified in the
+ // previous calling page.
+ //
+ // ~/index.cshtml does the following:
+ // @(PageData["foo"] ?? "null")
+ // @RenderPage("subpage.cshtml", new Dictionary<string, object>(){ { "foo", 1 }, { "bar", "hello"} })
+ //
+ // ~/subpage1.cshtml does the following:
+ // @(PageData["foo"] ?? "sub1nullfoo")
+ // @(PageData["bar"] ?? "sub1nullbar")
+ // @(PageData["x"] ?? "sub1nullx")
+ // @(PageData["y"] ?? "sub1nully")
+ // @RenderPage("subpage2.cshtml", new Dictionary<string, object>(){ { { "bar", "world"}, {"x", "good"}, {"y", "bye"} })
+ //
+ // ~/subpage2.cshtml does the following:
+ // @(PageData["foo"] ?? "sub2nullfoo")
+ // @(PageData["bar"] ?? "sub2nullbar")
+ // @(PageData["x"] ?? "sub2nullx")
+ // @(PageData["y"] ?? "sub2nully")
+ //
+ // Expected result: null 1 hello sub1nullx sub1nully sub2nullfoo world good bye
+ var page = Utils.CreatePage(
+ p =>
+ {
+ p.Write(p.PageData["foo"] ?? "null ");
+ p.Write(p.RenderPage("subpage1.cshtml", new Dictionary<string, object>() { { "foo", 1 }, { "bar", "hello" } }));
+ });
+ var subpage1Path = "~/subpage1.cshtml";
+ var subpage1 = Utils.CreatePage(
+ p =>
+ {
+ p.Write(p.PageData["foo"] ?? "sub1nullfoo");
+ p.Write(" ");
+ p.Write(p.PageData["bar"] ?? "sub1nullbar");
+ p.Write(" ");
+ p.Write(p.PageData["x"] ?? "sub1nullx");
+ p.Write(" ");
+ p.Write(p.PageData["y"] ?? "sub1nully");
+ p.Write(" ");
+ p.Write(p.RenderPage("subpage2.cshtml", new Dictionary<string, object>() { { "bar", "world" }, { "x", "good" }, { "y", "bye" } }));
+ }, subpage1Path);
+ var subpage2Path = "~/subpage2.cshtml";
+ var subpage2 = Utils.CreatePage(
+ p =>
+ {
+ p.Write(p.PageData["foo"] ?? "sub2nullfoo");
+ p.Write(" ");
+ p.Write(p.PageData["bar"] ?? "sub2nullbar");
+ p.Write(" ");
+ p.Write(p.PageData["x"] ?? "sub2nullx");
+ p.Write(" ");
+ p.Write(p.PageData["y"] ?? "sub2nully");
+ }, subpage2Path);
+
+ Utils.AssignObjectFactoriesAndDisplayModeProvider(page, subpage1, subpage2);
+
+ var result = Utils.RenderWebPage(page);
+ Assert.Equal("null 1 hello sub1nullx sub1nully sub2nullfoo world good bye", result);
+ }
+
+ [Fact]
+ public void RenderPageNestedParentPageDataTest()
+ {
+ // PageData should return values set by parent pages.
+ var page = Utils.CreatePage(
+ p =>
+ {
+ p.PageData["key1"] = "value1";
+ p.Write(p.RenderPage("subpage1.cshtml"));
+ });
+ var subpage1Path = "~/subpage1.cshtml";
+ var subpage1 = Utils.CreatePage(
+ p =>
+ {
+ p.WriteLiteral("<subpage1>");
+ p.Write(p.PageData["key1"]);
+ p.Write(p.RenderPage("subpage2.cshtml"));
+ p.Write(p.PageData["key1"]);
+ p.PageData["key1"] = "value2";
+ p.Write(p.RenderPage("subpage2.cshtml"));
+ p.WriteLiteral("</subpage1>");
+ }, subpage1Path);
+ var subpage2Path = "~/subpage2.cshtml";
+ var subpage2 = Utils.CreatePage(
+ p =>
+ {
+ p.WriteLiteral("<subpage2>");
+ p.Write(p.PageData["key1"]);
+ // Setting the value in the child page should
+ // not affect the parent page
+ p.PageData["key1"] = "value3";
+ p.Write(p.RenderPage("subpage3.cshtml", new { Key1 = "value4" }));
+ p.WriteLiteral("</subpage2>");
+ }, subpage2Path);
+ var subpage3Path = "~/subpage3.cshtml";
+ var subpage3 = Utils.CreatePage(
+ p =>
+ {
+ p.WriteLiteral("<subpage3>");
+ p.Write(p.PageData["key1"]);
+ p.WriteLiteral("</subpage3>");
+ }, subpage3Path);
+
+ Utils.AssignObjectFactoriesAndDisplayModeProvider(subpage1, subpage2, subpage3, page);
+
+ var result = Utils.RenderWebPage(page);
+ Assert.Equal("<subpage1>value1<subpage2>value1<subpage3>value4</subpage3></subpage2>value1<subpage2>value2<subpage3>value4</subpage3></subpage2></subpage1>", result);
+ }
+
+ [Fact]
+ public void WriteNullTest()
+ {
+ // Test for @null
+ var result = Utils.RenderWebPage(
+ p =>
+ {
+ p.Write(null);
+ p.Write((object)null);
+ p.Write((HelperResult)null);
+ });
+
+ Assert.Equal("", result);
+ }
+
+ [Fact]
+ public void WriteTest()
+ {
+ // Test for calling WebPage.Write on text and HtmlHelper
+ var text = "Hello";
+ var wrote = false;
+ Action<TextWriter> action = tw =>
+ {
+ tw.Write(text);
+ wrote = true;
+ };
+ var helper = new HelperResult(action);
+ var result = Utils.RenderWebPage(
+ p => { p.Write(helper); });
+ Assert.Equal(text, result);
+ Assert.True(wrote);
+ }
+
+ [Fact]
+ public void WriteLiteralTest()
+ {
+ // Test for calling WebPage.WriteLiteral on text and HtmlHelper
+ var text = "Hello";
+ var wrote = false;
+ Action<TextWriter> action = tw =>
+ {
+ tw.Write(text);
+ wrote = true;
+ };
+ var helper = new HelperResult(action);
+ var result = Utils.RenderWebPage(
+ p => { p.WriteLiteral(helper); });
+ Assert.Equal(text, result);
+ Assert.True(wrote);
+ }
+
+ [Fact]
+ public void ExtensionNotSupportedTest()
+ {
+ // Tests that calling RenderPage on an unsupported extension returns a new simpler error message
+ // instead of the full error about build providers in system.web.dll.
+ var vpath = "~/hello/world.txt";
+ var ext = ".txt";
+ var compilationUtilThrowingBuildManager = new CompilationUtil();
+ var otherExceptionBuildManager = new Mock<IVirtualPathFactory>();
+ var msg = "The file \"~/hello/world.txt\" could not be rendered, because it does not exist or is not a valid page.";
+ otherExceptionBuildManager.Setup(c => c.CreateInstance(It.IsAny<string>())).Throws(new HttpException(msg));
+
+ Assert.Throws<HttpException>(() =>
+ WebPage.CreateInstanceFromVirtualPath(vpath, new VirtualPathFactoryManager(compilationUtilThrowingBuildManager)),
+ String.Format(CultureInfo.CurrentCulture, WebPageResources.WebPage_FileNotSupported, ext, vpath));
+
+ // Test that other error messages are thrown unmodified.
+ Assert.Throws<HttpException>(() => WebPage.CreateInstanceFromVirtualPath(vpath, otherExceptionBuildManager.Object), msg);
+ }
+
+ [Fact]
+ public void RenderBodyCalledInChildPageTest()
+ {
+ // A Page that is called by RenderPage should not be able to call RenderBody().
+
+ Assert.Throws<HttpException>(() =>
+ Utils.RenderWebPageWithSubPage(
+ p =>
+ {
+ p.Write("hello");
+ p.Write(p.RenderPage("subpage.cshtml"));
+ },
+ p =>
+ {
+ p.Write("world");
+ p.RenderBody();
+ }),
+ String.Format(CultureInfo.CurrentCulture, WebPageResources.WebPage_CannotRequestDirectly, "~/subpage.cshtml", "RenderBody"));
+ }
+
+ [Fact]
+ public void RenderPageInvalidPageType()
+ {
+ var pagePath = "~/foo.js";
+ var page = Utils.CreatePage(p => { p.Write(p.RenderPage(pagePath)); });
+
+ var objectFactory = new Mock<IVirtualPathFactory>();
+ objectFactory.Setup(c => c.Exists(It.IsAny<string>())).Returns<string>(p => pagePath.Equals(p, StringComparison.OrdinalIgnoreCase));
+ objectFactory.Setup(c => c.CreateInstance(It.IsAny<string>())).Returns<string>(_ => null);
+ page.VirtualPathFactory = objectFactory.Object;
+
+ Assert.Throws<HttpException>(() =>
+ {
+ page.VirtualPathFactory = objectFactory.Object;
+ page.DisplayModeProvider = new DisplayModeProvider();
+ Utils.RenderWebPage(page);
+ },
+ String.Format(CultureInfo.CurrentCulture, WebPageResources.WebPage_InvalidPageType, pagePath));
+ }
+
+ [Fact]
+ public void RenderPageValidPageType()
+ {
+ var pagePath = "~/foo.js";
+ var page = Utils.CreatePage(p => { p.Write(p.RenderPage(pagePath)); });
+
+ var contents = "hello world";
+ var subPage = Utils.CreatePage(p => p.Write(contents), pagePath);
+
+ Utils.AssignObjectFactoriesAndDisplayModeProvider(page, subPage);
+
+ Assert.Equal(contents, Utils.RenderWebPage(page));
+ }
+
+ [Fact]
+ public void RenderPageNull()
+ {
+ Assert.ThrowsArgumentNullOrEmptyString(() => Utils.RenderWebPage(p => p.RenderPage(null)), "path");
+ }
+
+ [Fact]
+ public void RenderPageEmptyString()
+ {
+ Assert.ThrowsArgumentNullOrEmptyString(() => Utils.RenderWebPage(p => p.RenderPage("")), "path");
+ }
+
+ [Fact]
+ public void SamePageCaseInsensitiveTest()
+ {
+ var result = Utils.RenderWebPage(
+ p =>
+ {
+ p.PageData["xyz"] = "value";
+ p.PageData["XYZ"] = "VALUE";
+ p.Write(p.PageData["xYz"]);
+ });
+
+ Assert.Equal("VALUE", result);
+ }
+ }
+}
diff --git a/test/System.Web.WebPages.Test/WebPage/RequestBrowserOverrideStoreTest.cs b/test/System.Web.WebPages.Test/WebPage/RequestBrowserOverrideStoreTest.cs
new file mode 100644
index 00000000..091e03cb
--- /dev/null
+++ b/test/System.Web.WebPages.Test/WebPage/RequestBrowserOverrideStoreTest.cs
@@ -0,0 +1,35 @@
+using Moq;
+using Xunit;
+
+namespace System.Web.WebPages.Test
+{
+ public class RequestBrowserOverrideStoreTest
+ {
+ [Fact]
+ public void GetOverriddenUserAgentReturnsRequestUserAgent()
+ {
+ // Arrange
+ RequestBrowserOverrideStore requestStore = new RequestBrowserOverrideStore();
+ Mock<HttpContextBase> context = new Mock<HttpContextBase>();
+ context.Setup(c => c.Request.UserAgent).Returns("testUserAgent");
+
+ // Act & Assert
+ Assert.Equal("testUserAgent", requestStore.GetOverriddenUserAgent(context.Object));
+ }
+
+ [Fact]
+ public void SetOverriddenUserAgentDoesNotOverrideUserAgent()
+ {
+ // Arrange
+ RequestBrowserOverrideStore requestStore = new RequestBrowserOverrideStore();
+ Mock<HttpContextBase> context = new Mock<HttpContextBase>();
+ context.Setup(c => c.Request.UserAgent).Returns("testUserAgent");
+
+ // Act
+ requestStore.SetOverriddenUserAgent(context.Object, "setUserAgent");
+
+ // Assert
+ Assert.Equal("testUserAgent", requestStore.GetOverriddenUserAgent(context.Object));
+ }
+ }
+}
diff --git a/test/System.Web.WebPages.Test/WebPage/RequestResourceTrackerTest.cs b/test/System.Web.WebPages.Test/WebPage/RequestResourceTrackerTest.cs
new file mode 100644
index 00000000..6ee204f4
--- /dev/null
+++ b/test/System.Web.WebPages.Test/WebPage/RequestResourceTrackerTest.cs
@@ -0,0 +1,38 @@
+using System.Collections;
+using Moq;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.WebPages.Test
+{
+ public class RequestResourceTrackerTest
+ {
+ [Fact]
+ public void RegisteringForDisposeDisposesObjects()
+ {
+ // Arrange
+ var context = new Mock<HttpContextBase>();
+ IDictionary items = new Hashtable();
+ context.Setup(m => m.Items).Returns(items);
+ var disposable = new Mock<IDisposable>();
+ disposable.Setup(m => m.Dispose()).Verifiable();
+
+ // Act
+ RequestResourceTracker.RegisterForDispose(context.Object, disposable.Object);
+ RequestResourceTracker.DisposeResources(context.Object);
+
+ // Assert
+ disposable.VerifyAll();
+ }
+
+ [Fact]
+ public void RegisteringForDisposeExtensionMethodNullContextThrows()
+ {
+ // Arrange
+ var disposable = new Mock<IDisposable>();
+
+ // Act
+ Assert.ThrowsArgumentNull(() => HttpContextExtensions.RegisterForDispose(null, disposable.Object), "context");
+ }
+ }
+}
diff --git a/test/System.Web.WebPages.Test/WebPage/SectionControlBuilderTest.cs b/test/System.Web.WebPages.Test/WebPage/SectionControlBuilderTest.cs
new file mode 100644
index 00000000..136bfba0
--- /dev/null
+++ b/test/System.Web.WebPages.Test/WebPage/SectionControlBuilderTest.cs
@@ -0,0 +1,249 @@
+using System.CodeDom;
+using System.Collections.Generic;
+using System.Linq;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+namespace Microsoft.WebPages.Test {
+ /// <summary>
+ ///This is a test class for SectionControlBuilderTestand is intended
+ ///to contain all SectionControlBuilder Unit Tests
+ ///</summary>
+ [TestClass()]
+ public class SectionControlBuilderTest {
+
+ private const int defaultIndex = 2 ;
+ private const int defaultIndexAfterOffset = 4;
+
+ private TestContext testContextInstance;
+
+ /// <summary>
+ ///Gets or sets the test context which provides
+ ///information about and functionality for the current test run.
+ ///</summary>
+ public TestContext TestContext {
+ get {
+ return testContextInstance;
+ }
+ set {
+ testContextInstance = value;
+ }
+ }
+
+ #region Additional test attributes
+ //
+ //You can use the following additional attributes as you write your tests:
+ //
+ //Use ClassInitialize to run code before running the first test in the class
+ //[ClassInitialize()]
+ //public static void MyClassInitialize(TestContext testContext)
+ //{
+ //}
+ //
+ //Use ClassCleanup to run code after all tests in a class have run
+ //[ClassCleanup()]
+ //public static void MyClassCleanup()
+ //{
+ //}
+ //
+ //Use TestInitialize to run code before running each test
+ //[TestInitialize()]
+ //public void MyTestInitialize()
+ //{
+ //}
+ //
+ //Use TestCleanup to run code after each test has run
+ //[TestCleanup()]
+ //public void MyTestCleanup()
+ //{
+ //}
+ //
+ #endregion
+
+ [TestMethod]
+ public void GetSectionRenderMethodTest() {
+ var members = GetRenderMembers();
+ var result = SectionControlBuilder.GetSectionRenderMethod(members, defaultIndex);
+ Assert.AreEqual(members[0], result);
+ }
+
+ [TestMethod]
+ public void GetSectionNameTest() {
+ var sectionName = "MySectionName";
+ var result = SectionControlBuilder.GetSectionName(GetBuildMembers(), defaultIndex);
+ Assert.AreEqual(sectionName, result);
+ }
+
+ [TestMethod]
+ public void GetSectionNameNullTest() {
+ var index = defaultIndexAfterOffset;
+ var methodName = "__BuildControl__control";
+ Assert.IsNull(SectionControlBuilder.GetSectionName(new CodeTypeMember[] { }, defaultIndex));
+
+ var method = new CodeMemberMethod();
+ method = new CodeMemberMethod() { Name = methodName + index.ToString() };
+ method.Statements.Add(new CodeSnippetStatement("test"));
+ Assert.IsNull(SectionControlBuilder.GetSectionName(new CodeTypeMember[] { method }, defaultIndex));
+
+ var statement = new CodeAssignStatement(null, null);
+ method.Statements.Clear();
+ method.Statements.Add(statement);
+ Assert.IsNull(SectionControlBuilder.GetSectionName(new CodeTypeMember[] { method }, defaultIndex));
+
+ var left = new CodePropertyReferenceExpression(null, "test");
+ statement = new CodeAssignStatement(left, null);
+ method.Statements.Clear();
+ method.Statements.Add(statement);
+ Assert.IsNull(SectionControlBuilder.GetSectionName(new CodeTypeMember[] { method }, defaultIndex));
+
+ left = new CodePropertyReferenceExpression(null, "Name");
+ statement = new CodeAssignStatement(left, null);
+ method.Statements.Clear();
+ method.Statements.Add(statement);
+ Assert.IsNull(SectionControlBuilder.GetSectionName(new CodeTypeMember[] { method }, defaultIndex));
+
+ left = new CodePropertyReferenceExpression(new CodeVariableReferenceExpression("test"), "Name");
+ statement = new CodeAssignStatement(left, null);
+ method.Statements.Clear();
+ method.Statements.Add(statement);
+ Assert.IsNull(SectionControlBuilder.GetSectionName(new CodeTypeMember[] { method }, defaultIndex));
+
+ }
+
+ [TestMethod]
+ public void GetDefineSectionStatementsTest() {
+ var renderMembers = GetRenderMembers();
+ var buildMembers = GetBuildMembers();
+ var members = new List<CodeTypeMember>(renderMembers);
+ foreach (var m in buildMembers) {
+ members.Add(m);
+ }
+ var result = SectionControlBuilder.GetDefineSectionStatements(members, defaultIndex, Language.CSharp);
+ VerifyDefineSection(((CodeMemberMethod)renderMembers[0]).Statements[0], result);
+ }
+
+ private static void VerifyDefineSection(CodeStatement renderStatement, IList<CodeStatement> result) {
+ Assert.AreEqual(3, result.Count);
+ Assert.IsInstanceOfType(result[0], typeof(CodeSnippetStatement));
+ var snippet = result[0] as CodeSnippetStatement;
+ Assert.AreEqual("DefineSection(\"MySectionName\", delegate () {", snippet.Value);
+ Assert.IsInstanceOfType(result[1], typeof(CodeExpressionStatement));
+ Assert.AreEqual(renderStatement, result[1]);
+ Assert.IsInstanceOfType(result[2], typeof(CodeSnippetStatement));
+ snippet = result[2] as CodeSnippetStatement;
+ Assert.AreEqual("});", snippet.Value);
+ }
+
+ public IList<CodeTypeMember> GetRenderMembers() {
+ // Create a method of the following form:
+ // void _Render__control4() {
+ // this.Write("Hello");
+ // }
+ var sectionName = "MySectionName";
+ var methodName = "__Render__control";
+ var expr = new CodeMethodInvokeExpression(new CodeThisReferenceExpression(), "Write", new CodeExpression[] { new CodePrimitiveExpression("Hello") });
+ var statement = new CodeExpressionStatement(expr);
+ return GetBuildMembers(methodName, sectionName, defaultIndexAfterOffset, statement);
+ }
+
+ public IList<CodeTypeMember> GetBuildMembers() {
+ // Create a method of the following form:
+ // void __BuildControl__control4() {
+ // __ctrl.Name = "my_section_name";
+ // }
+ var sectionName = "MySectionName";
+ var methodName = "__BuildControl__control";
+ var left = new CodePropertyReferenceExpression(new CodeVariableReferenceExpression("__ctrl"), "Name");
+ var right = new CodePrimitiveExpression(sectionName);
+ var statement = new CodeAssignStatement(left, right);
+ return GetBuildMembers(methodName, sectionName, defaultIndexAfterOffset, statement);
+ }
+
+ public IList<CodeTypeMember> GetBuildMembers(string methodName, string sectionName, int index, CodeStatement statement = null) {
+
+ var method = new CodeMemberMethod() { Name = methodName + index.ToString() };
+ if (statement != null) {
+ method.Statements.Add(statement);
+ }
+ return new CodeTypeMember[] { method };
+ }
+
+ [TestMethod]
+ public void ProcessRenderControlMethodTest() {
+ // Create a statement of the following form:
+ // parameterContainer.Controls[0].RenderControl(@__w);
+ // where parameterContainer is a parameter.
+ var container = new CodeArgumentReferenceExpression("parameterContainer");
+ var controls = new CodePropertyReferenceExpression(container, "Controls");
+ var indexer = new CodeIndexerExpression(controls, new CodeExpression[] { new CodePrimitiveExpression(defaultIndex) });
+ var invoke = new CodeMethodInvokeExpression(indexer, "RenderControl", new CodeExpression[] { new CodeArgumentReferenceExpression("__w") });
+ var stmt = new CodeExpressionStatement(invoke);
+ var renderMethod = new CodeMemberMethod();
+ var renderMembers = GetRenderMembers();
+ var buildMembers = GetBuildMembers();
+ var members = new List<CodeTypeMember>(renderMembers);
+ foreach (var m in buildMembers) {
+ members.Add(m);
+ }
+ SectionControlBuilder.ProcessRenderControlMethod(members, renderMethod, stmt, Language.CSharp);
+ VerifyDefineSection(((CodeMemberMethod)renderMembers[0]).Statements[0], renderMethod.Statements.OfType<CodeStatement>().ToList());
+ }
+
+ [TestMethod]
+ public void ProcessRenderControlMethodFalseTest() {
+ Assert.IsFalse(SectionControlBuilder.ProcessRenderControlMethod(null, null, null, Language.CSharp));
+ Assert.IsFalse(SectionControlBuilder.ProcessRenderControlMethod(null, null, new CodeExpressionStatement(new CodeSnippetExpression("test")), Language.CSharp));
+
+ var invoke = new CodeMethodInvokeExpression(null, "RenderControlX", new CodeExpression[] { new CodeArgumentReferenceExpression("__w") });
+ var stmt = new CodeExpressionStatement(invoke);
+ Assert.IsFalse(SectionControlBuilder.ProcessRenderControlMethod(null, null, stmt, Language.CSharp));
+
+ invoke = new CodeMethodInvokeExpression(new CodeSnippetExpression(""), "RenderControl", new CodeExpression[] { new CodeArgumentReferenceExpression("__w") });
+ stmt = new CodeExpressionStatement(invoke);
+ Assert.IsFalse(SectionControlBuilder.ProcessRenderControlMethod(null, null, stmt, Language.CSharp));
+
+ var indexer = new CodeIndexerExpression(new CodeSnippetExpression(""), new CodeExpression[] { new CodePrimitiveExpression(defaultIndex) });
+ invoke = new CodeMethodInvokeExpression(indexer, "RenderControl", new CodeExpression[] { new CodeArgumentReferenceExpression("__w") });
+ stmt = new CodeExpressionStatement(invoke);
+ Assert.IsFalse(SectionControlBuilder.ProcessRenderControlMethod(null, null, stmt, Language.CSharp));
+
+ var container = new CodeArgumentReferenceExpression("parameterContainer");
+ var controls = new CodePropertyReferenceExpression(container, "Controls");
+ indexer = new CodeIndexerExpression(controls, new CodeExpression[] { new CodeSnippetExpression("") });
+ invoke = new CodeMethodInvokeExpression(indexer, "RenderControl", new CodeExpression[] { new CodeArgumentReferenceExpression("__w") });
+ stmt = new CodeExpressionStatement(invoke);
+ Assert.IsFalse(SectionControlBuilder.ProcessRenderControlMethod(null, null, stmt, Language.CSharp));
+
+ controls = new CodePropertyReferenceExpression(container, "ControlsX");
+ indexer = new CodeIndexerExpression(controls, new CodeExpression[] { new CodePrimitiveExpression(defaultIndex) });
+ invoke = new CodeMethodInvokeExpression(indexer, "RenderControl", new CodeExpression[] { new CodeArgumentReferenceExpression("__w") });
+ stmt = new CodeExpressionStatement(invoke);
+ Assert.IsFalse(SectionControlBuilder.ProcessRenderControlMethod(null, null, stmt, Language.CSharp));
+
+ controls = new CodePropertyReferenceExpression(new CodeSnippetExpression("test"), "Controls");
+ indexer = new CodeIndexerExpression(controls, new CodeExpression[] { new CodePrimitiveExpression(defaultIndex) });
+ invoke = new CodeMethodInvokeExpression(indexer, "RenderControl", new CodeExpression[] { new CodeArgumentReferenceExpression("__w") });
+ stmt = new CodeExpressionStatement(invoke);
+ Assert.IsFalse(SectionControlBuilder.ProcessRenderControlMethod(null, null, stmt, Language.CSharp));
+
+ container = new CodeArgumentReferenceExpression("parameterContainerX");
+ controls = new CodePropertyReferenceExpression(container, "Controls");
+ indexer = new CodeIndexerExpression(controls, new CodeExpression[] { new CodePrimitiveExpression(defaultIndex) });
+ invoke = new CodeMethodInvokeExpression(indexer, "RenderControl", new CodeExpression[] { new CodeArgumentReferenceExpression("__w") });
+ stmt = new CodeExpressionStatement(invoke);
+ Assert.IsFalse(SectionControlBuilder.ProcessRenderControlMethod(null, null, stmt, Language.CSharp));
+ }
+
+ [TestMethod]
+ public void GetDefineSectionStartSnippetTest() {
+ var snippetStmt = SectionControlBuilder.GetDefineSectionStartSnippet("HelloWorld", Language.CSharp) as CodeSnippetStatement;
+ Assert.AreEqual("DefineSection(\"HelloWorld\", delegate () {", snippetStmt.Value);
+ snippetStmt = SectionControlBuilder.GetDefineSectionStartSnippet("HelloWorld", Language.VisualBasic) as CodeSnippetStatement;
+ Assert.AreEqual("DefineSection(\"HelloWorld\", Sub()", snippetStmt.Value);
+ }
+
+ [TestMethod]
+ public void HasAspCodeTest() {
+ Assert.IsTrue(new SectionControlBuilder().HasAspCode);
+ }
+ }
+} \ No newline at end of file
diff --git a/test/System.Web.WebPages.Test/WebPage/StartPageTest.cs b/test/System.Web.WebPages.Test/WebPage/StartPageTest.cs
new file mode 100644
index 00000000..36430f20
--- /dev/null
+++ b/test/System.Web.WebPages.Test/WebPage/StartPageTest.cs
@@ -0,0 +1,568 @@
+using System.Reflection;
+using System.Web.Caching;
+using System.Web.Compilation;
+using System.Web.Hosting;
+using System.Web.Profile;
+using System.Web.WebPages.TestUtils;
+using Moq;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.WebPages.Test
+{
+ public class StartPageTest
+ {
+ // The page ~/_pagestart.cshtml does the following:
+ // this is the init page
+ //
+ // The page ~/index.cshtml does the following:
+ // hello world
+ // Expected result:
+ // this is the init page hello world
+ [Fact]
+ public void InitPageBasicTest()
+ {
+ var init = Utils.CreateStartPage(p =>
+ p.Write("this is the init page "));
+ var page = Utils.CreatePage(p =>
+ p.Write("hello world"));
+
+ init.ChildPage = page;
+
+ var result = Utils.RenderWebPage(page, init);
+ Assert.Equal("this is the init page hello world", result);
+ }
+
+ // The page ~/_pagestart.cshtml does the following:
+ // this is the init page
+ //
+ // The page ~/folder1/index.cshtml does the following:
+ // hello world
+ // Expected result:
+ // this is the init page hello world
+ [Fact]
+ public void InitSubfolderTest()
+ {
+ var init = Utils.CreateStartPage(p =>
+ p.Write("this is the init page "));
+ var page = Utils.CreatePage(p =>
+ p.Write("hello world"), "~/folder1/index.cshtml");
+
+ init.ChildPage = page;
+
+ var result = Utils.RenderWebPage(page, init);
+ Assert.Equal("this is the init page hello world", result);
+ }
+
+ // The page ~/_pagestart.cshtml does the following:
+ // PageData["Title"] = "InitPage";
+ // Layout = "Layout.cshtml";
+ // this is the init page
+ //
+ // The page ~/index.cshtml does the following:
+ // PageData["Title"] = "IndexCshtmlPage"
+ // hello world
+ //
+ // The layout page ~/Layout.cshtml does the following:
+ // layout start
+ // @PageData["Title"]
+ // @RenderBody()
+ // layout end
+ //
+ // Expected result:
+ // layout start IndexCshtmlPage this is the init page hello world layout end
+ [Fact]
+ public void InitPageLayoutTest()
+ {
+ var init = Utils.CreateStartPage(p =>
+ {
+ p.Layout = "Layout.cshtml";
+ p.Write(" this is the init page ");
+ Assert.Equal("~/Layout.cshtml", p.Layout);
+ });
+ var page = Utils.CreatePage(p =>
+ {
+ p.PageData["Title"] = "IndexCshtmlPage";
+ p.Write("hello world");
+ });
+ var layoutPage = Utils.CreatePage(p =>
+ {
+ p.Write("layout start ");
+ p.Write(p.PageData["Title"]);
+ p.WriteLiteral(p.RenderBody());
+ p.Write(" layout end");
+ }, "~/Layout.cshtml");
+
+ init.ChildPage = page;
+ Utils.AssignObjectFactoriesAndDisplayModeProvider(init, page, layoutPage);
+
+ var result = Utils.RenderWebPage(page, init);
+ Assert.Equal("layout start IndexCshtmlPage this is the init page hello world layout end", result);
+ }
+
+ // _pagestart.cshtml sets the LayoutPage to be null
+ [Fact]
+ public void InitPageNullLayoutPageTest()
+ {
+ var init1 = Utils.CreateStartPage(
+ p =>
+ {
+ p.Layout = "~/Layout.cshtml";
+ p.WriteLiteral("<init1>");
+ p.RunPage();
+ p.WriteLiteral("</init1>");
+ });
+ var init2path = "~/folder1/_pagestart.cshtml";
+ var init2 = Utils.CreateStartPage(
+ p =>
+ {
+ p.Layout = null;
+ p.WriteLiteral("<init2>");
+ p.RunPage();
+ p.WriteLiteral("</init2>");
+ }, init2path);
+ var page = Utils.CreatePage(p =>
+ p.Write("hello world"), "~/folder1/index.cshtml");
+ var layoutPage = Utils.CreatePage(p =>
+ p.Write("layout page"), "~/Layout.cshtml");
+
+ Utils.AssignObjectFactoriesAndDisplayModeProvider(page, layoutPage, init1, init2);
+
+ init1.ChildPage = init2;
+ init2.ChildPage = page;
+
+ var result = Utils.RenderWebPage(page, init1);
+ Assert.Equal("<init1><init2>hello world</init2></init1>", result);
+ }
+
+ // _pagestart.cshtml sets the LayoutPage, but page sets it to null
+ [Fact]
+ public void PageSetsNullLayoutPageTest()
+ {
+ var init1 = Utils.CreateStartPage(
+ p =>
+ {
+ p.Layout = "~/Layout.cshtml";
+ p.WriteLiteral("<init1>");
+ p.RunPage();
+ p.WriteLiteral("</init1>");
+ });
+ var layoutPage = Utils.CreatePage(p =>
+ p.Write("layout page"), "~/Layout.cshtml");
+ var page = Utils.CreatePage(p =>
+ {
+ p.Layout = null;
+ p.Write("hello world");
+ });
+ Utils.AssignObjectFactoriesAndDisplayModeProvider(init1, layoutPage, page);
+ init1.ChildPage = page;
+ var result = Utils.RenderWebPage(page, init1);
+ Assert.Equal("<init1>hello world</init1>", result);
+ }
+
+ [Fact]
+ public void PageSetsEmptyLayoutPageTest()
+ {
+ var init1 = Utils.CreateStartPage(
+ p =>
+ {
+ p.Layout = "~/Layout.cshtml";
+ p.WriteLiteral("<init1>");
+ p.RunPage();
+ p.WriteLiteral("</init1>");
+ });
+ var layoutPage = Utils.CreatePage(p =>
+ p.Write("layout page"), "~/Layout.cshtml");
+ var page = Utils.CreatePage(p =>
+ {
+ p.Layout = "";
+ p.Write("hello world");
+ });
+ Utils.AssignObjectFactoriesAndDisplayModeProvider(init1, layoutPage, page);
+ init1.ChildPage = page;
+ var result = Utils.RenderWebPage(page, init1);
+ Assert.Equal("<init1>hello world</init1>", result);
+ }
+
+ // The page ~/_pagestart.cshtml does the following:
+ // init page start
+ // @RunPage()
+ // init page end
+ //
+ // The page ~/index.cshtml does the following:
+ // hello world
+ //
+ // Expected result:
+ // init page start hello world init page end
+ [Fact]
+ public void RunPageTest()
+ {
+ var init = Utils.CreateStartPage(
+ p =>
+ {
+ p.Write("init page start ");
+ p.RunPage();
+ p.Write(" init page end");
+ });
+ var page = Utils.CreatePage(p =>
+ p.Write("hello world"));
+
+ init.ChildPage = page;
+
+ var result = Utils.RenderWebPage(page, init);
+ Assert.Equal("init page start hello world init page end", result);
+ }
+
+ // The page ~/_pagestart.cshtml does the following:
+ // <init1>
+ // @RunPage()
+ // </init1>
+ //
+ // The page ~/folder1/_pagestart.cshtml does the following:
+ // <init2>
+ // @RunPage()
+ // </init2>
+ //
+ // The page ~/folder1/index.cshtml does the following:
+ // hello world
+ //
+ // Expected result:
+ // <init1><init2>hello world</init2></init1>
+ [Fact]
+ public void NestedRunPageTest()
+ {
+ var init1 = Utils.CreateStartPage(
+ p =>
+ {
+ p.WriteLiteral("<init1>");
+ p.RunPage();
+ p.WriteLiteral("</init1>");
+ });
+ var init2path = "~/folder1/_pagestart.cshtml";
+ var init2 = Utils.CreateStartPage(
+ p =>
+ {
+ p.WriteLiteral("<init2>");
+ p.RunPage();
+ p.WriteLiteral("</init2>");
+ }, init2path);
+ var page = Utils.CreatePage(p =>
+ p.Write("hello world"), "~/folder1/index.cshtml");
+
+ init1.ChildPage = init2;
+ init2.ChildPage = page;
+
+ var result = Utils.RenderWebPage(page, init1);
+ Assert.Equal("<init1><init2>hello world</init2></init1>", result);
+ }
+
+ // The page ~/_pagestart.cshtml does the following:
+ // PageData["key1"] = "value1";
+ //
+ // The page ~/folder1/_pagestart.cshtml does the following:
+ // PageData["key2"] = "value2";
+ //
+ // The page ~/folder1/index.cshtml does the following:
+ // @PageData["key1"] @PageData["key2"] @PageData["key3"]
+ //
+ // Expected result:
+ // value1 value2
+ [Fact]
+ public void PageDataTest()
+ {
+ var init1 = Utils.CreateStartPage(p => p.PageData["key1"] = "value1");
+ var init2path = "~/folder1/_pagestart.cshtml";
+ var init2 = Utils.CreateStartPage(p => p.PageData["key2"] = "value2", init2path);
+ var page = Utils.CreatePage(
+ p =>
+ {
+ p.Write(p.PageData["key1"]);
+ p.Write(" ");
+ p.Write(p.PageData["key2"]);
+ },
+ "~/folder1/index.cshtml");
+
+ init1.ChildPage = init2;
+ init2.ChildPage = page;
+
+ var result = Utils.RenderWebPage(page, init1);
+ Assert.Equal("value1 value2", result);
+ }
+
+ // The page ~/_pagestart.cshtml does the following:
+ // init page
+ // @RenderPage("subpage.cshtml", "init_data");
+ //
+ // The page ~/subpage.cshtml does the following:
+ // subpage
+ // @PageData[0]
+ //
+ // The page ~/index.cshtml does the following:
+ // hello world
+ //
+ // Expected result:
+ // init page subpage init_data hello world
+ [Fact]
+ public void RenderPageTest()
+ {
+ var init = Utils.CreateStartPage(
+ p =>
+ {
+ p.Write("init page ");
+ p.Write(p.RenderPage("subpage.cshtml", "init_data"));
+ });
+ var subpagePath = "~/subpage.cshtml";
+ var subpage = Utils.CreatePage(
+ p =>
+ {
+ p.Write("subpage ");
+ p.Write(p.PageData[0]);
+ }, subpagePath);
+ var page = Utils.CreatePage(p =>
+ p.Write(" hello world"));
+
+ init.ChildPage = page;
+ Utils.AssignObjectFactoriesAndDisplayModeProvider(init, page, subpage);
+
+ var result = Utils.RenderWebPage(page, init);
+ Assert.Equal("init page subpage init_data hello world", result);
+ }
+
+ [Fact]
+ // The page ~/_pagestart.cshtml does the following:
+ // <init>
+ // @{
+ // try {
+ // RunPage();
+ // } catch (Exception e) {
+ // Write("Exception: " + e.Message);
+ // }
+ // }
+ // </init>
+ //
+ // The page ~/index.cshtml does the following:
+ // hello world
+ // @{throw new InvalidOperation("exception from index.cshtml");}
+ //
+ // Expected result:
+ // <init>hello world Exception: exception from index.cshtml</init>
+ public void InitCatchExceptionTest()
+ {
+ var init = Utils.CreateStartPage(
+ p =>
+ {
+ p.WriteLiteral("<init>");
+ try
+ {
+ p.RunPage();
+ }
+ catch (Exception e)
+ {
+ p.Write("Exception: " + e.Message);
+ }
+ p.WriteLiteral("</init>");
+ });
+ var page = Utils.CreatePage(
+ p =>
+ {
+ p.WriteLiteral("hello world ");
+ throw new InvalidOperationException("exception from index.cshtml");
+ });
+
+ init.ChildPage = page;
+
+ var result = Utils.RenderWebPage(page, init);
+ Assert.Equal("<init>hello world Exception: exception from index.cshtml</init>", result);
+ }
+
+ public class MockInitPage : MockStartPage
+ {
+ internal object GetBuildManager()
+ {
+ return typeof(BuildManager).GetField("_theBuildManager", BindingFlags.Static | BindingFlags.NonPublic).GetValue(null);
+ }
+ }
+
+ // Simulate a site that is nested, eg /subfolder1/website1
+ [Fact]
+ public void ExecuteWithinInitTest()
+ {
+ AppDomainUtils.RunInSeparateAppDomain(() =>
+ {
+ Utils.CreateHttpRuntime("/subfolder1/website1");
+ new HostingEnvironment();
+ var stringSet = Activator.CreateInstance(typeof(BuildManager).Assembly.GetType("System.Web.Util.StringSet"), true);
+ typeof(BuildManager).GetField("_forbiddenTopLevelDirectories", BindingFlags.Instance | BindingFlags.NonPublic).SetValue(new MockInitPage().GetBuildManager(), stringSet);
+ ;
+
+ var init = new MockInitPage()
+ {
+ VirtualPath = "~/_pagestart.cshtml",
+ ExecuteAction = p => { },
+ };
+ var page = Utils.CreatePage(p => { });
+
+ Utils.AssignObjectFactoriesAndDisplayModeProvider(page, init);
+
+ var result = Utils.RenderWebPage(page);
+ });
+ }
+
+ [Fact]
+ public void SetGetPropertiesTest()
+ {
+ var init = new MockInitPage();
+ var page = new MockPage();
+ init.ChildPage = page;
+
+ // Context
+ var context = new Mock<HttpContextBase>().Object;
+ init.Context = context;
+ Assert.Equal(context, init.Context);
+ Assert.Equal(context, page.Context);
+
+ // Profile/Request/Response/Server/Cache/Session/Application
+ var profile = new Mock<ProfileBase>().Object;
+ var request = new Mock<HttpRequestBase>().Object;
+ var response = new Mock<HttpResponseBase>().Object;
+ var server = new Mock<HttpServerUtilityBase>().Object;
+ var cache = new Cache();
+ var app = new Mock<HttpApplicationStateBase>().Object;
+ var session = new Mock<HttpSessionStateBase>().Object;
+
+ var contextMock = new Mock<HttpContextBase>();
+ contextMock.Setup(c => c.Profile).Returns(profile);
+ contextMock.Setup(c => c.Request).Returns(request);
+ contextMock.Setup(c => c.Response).Returns(response);
+ contextMock.Setup(c => c.Cache).Returns(cache);
+ contextMock.Setup(c => c.Server).Returns(server);
+ contextMock.Setup(c => c.Application).Returns(app);
+ contextMock.Setup(c => c.Session).Returns(session);
+
+ context = contextMock.Object;
+ page.Context = context;
+ Assert.Same(profile, init.Profile);
+ Assert.Same(request, init.Request);
+ Assert.Same(response, init.Response);
+ Assert.Same(cache, init.Cache);
+ Assert.Same(server, init.Server);
+ Assert.Same(session, init.Session);
+ Assert.Same(app, init.AppState);
+ }
+
+ [Fact]
+ public void GetDirectoryTest()
+ {
+ var initPage = new Mock<StartPage>().Object;
+ Assert.Equal("/website1/", initPage.GetDirectory("/website1/default.cshtml"));
+ Assert.Equal("~/", initPage.GetDirectory("~/default.cshtml"));
+ Assert.Equal("/", initPage.GetDirectory("/website1/"));
+ Assert.Equal(null, initPage.GetDirectory("/"));
+ }
+
+ [Fact]
+ public void GetStartPageReturnsStartPageFromCurrentDirectoryIfExists()
+ {
+ // Arrange
+ var initPage = Utils.CreateStartPage(p => p.Write("<init>"), "~/subdir/_pagestart.vbhtml");
+ var page = Utils.CreatePage(p => p.Write("test"), "~/subdir/_index.cshtml");
+ var objectFactory = Utils.AssignObjectFactoriesAndDisplayModeProvider(page, initPage);
+
+ // Act
+ var result = StartPage.GetStartPage(page, objectFactory, null, WebPageHttpHandler.StartPageFileName, new string[] { "cshtml", "vbhtml" });
+
+ // Assert
+ Assert.Equal(initPage, result);
+ }
+
+ [Fact]
+ public void GetStartPageReturnsStartPageFromParentDirectoryIfStartPageDoesNotExistInCurrentDirectory()
+ {
+ // Arrange
+ var initPage = Utils.CreateStartPage(null, "~/subdir/_pagestart.vbhtml");
+ var page = Utils.CreatePage(null, "~/subdir/subsubdir/test.cshtml");
+ var objectFactory = Utils.AssignObjectFactoriesAndDisplayModeProvider(page, initPage);
+
+ // Act
+ var result = StartPage.GetStartPage(page, objectFactory, null, WebPageHttpHandler.StartPageFileName, new string[] { "cshtml", "vbhtml" });
+
+ // Assert
+ Assert.Equal(initPage, result);
+ }
+
+ [Fact]
+ public void GetStartPageCreatesChainOfStartPages()
+ {
+ // Arrange
+ var subInitPage = Utils.CreateStartPage(null, "~/subdir/_pagestart.vbhtml");
+ var initPage = Utils.CreateStartPage(null, "~/_pagestart.vbhtml");
+ var page = Utils.CreatePage(null, "~/subdir/subsubdir/subsubsubdir/test.cshtml");
+ var objectFactory = Utils.AssignObjectFactoriesAndDisplayModeProvider(page, initPage, subInitPage);
+
+ // Act
+ var result = StartPage.GetStartPage(page, objectFactory, null, WebPageHttpHandler.StartPageFileName, new string[] { "cshtml", "vbhtml" });
+
+ // Assert
+ Assert.Equal(initPage, result);
+ Assert.Equal(subInitPage, (result as StartPage).ChildPage);
+ }
+
+ [Fact]
+ public void GetStartPageReturnsStartPageFromRoot()
+ {
+ // Arrange
+ var initPage = Utils.CreateStartPage(null, "~/_pagestart.vbhtml");
+ var page = Utils.CreatePage(null, "~/subdir/subsubdir/subsubsubdir/subsubsubsubdir/why-does-this-remind-me-of-a-movie-title.cshtml");
+ var objectFactory = Utils.AssignObjectFactoriesAndDisplayModeProvider(page, initPage);
+
+ // Act
+ var result = StartPage.GetStartPage(page, objectFactory, null, WebPageHttpHandler.StartPageFileName, new string[] { "cshtml", "vbhtml" });
+
+ // Assert
+ Assert.Equal(initPage, result);
+ }
+
+ [Fact]
+ public void GetStartPageUsesBothFileNamesAndExtensionsWhenDeterminingStartPage()
+ {
+ // Arrange
+ var subInitPage = Utils.CreateStartPage(null, "~/subdir/_pagestart.jshtml");
+ var initPage = Utils.CreateStartPage(null, "~/_pagestart.vbhtml");
+ var page = Utils.CreatePage(null, "~/subdir/test.cshtml");
+ var objectFactory = Utils.AssignObjectFactoriesAndDisplayModeProvider(page, initPage, subInitPage);
+
+ // Act
+ var result = StartPage.GetStartPage(page, objectFactory, null, WebPageHttpHandler.StartPageFileName, new string[] { "cshtml", "vbhtml" });
+
+ // Assert
+ Assert.Equal(initPage, result);
+ }
+
+ [Fact]
+ public void GetStartPage_ThrowsOnNullPage()
+ {
+ Assert.ThrowsArgumentNull(() => StartPage.GetStartPage(null, "name", new[] { "cshtml" }), "page");
+ }
+
+ [Fact]
+ public void GetStartPage_ThrowsOnNullFileName()
+ {
+ var page = Utils.CreatePage(p => p.Write("test"));
+ Assert.ThrowsArgumentNullOrEmptyString(() => StartPage.GetStartPage(page, null, new[] { "cshtml" }), "fileName");
+ }
+
+ [Fact]
+ public void GetStartPage_ThrowsOnEmptyFileName()
+ {
+ var page = Utils.CreatePage(p => p.Write("test"));
+ Assert.ThrowsArgumentNullOrEmptyString(() => StartPage.GetStartPage(page, String.Empty, new[] { "cshtml" }), "fileName");
+ }
+
+ [Fact]
+ public void GetStartPage_ThrowsOnNullSupportedExtensions()
+ {
+ var page = Utils.CreatePage(p => p.Write("test"));
+ Assert.ThrowsArgumentNull(() => StartPage.GetStartPage(page, "name", null), "supportedExtensions");
+ }
+ }
+}
diff --git a/test/System.Web.WebPages.Test/WebPage/TemplateStackTest.cs b/test/System.Web.WebPages.Test/WebPage/TemplateStackTest.cs
new file mode 100644
index 00000000..2e468121
--- /dev/null
+++ b/test/System.Web.WebPages.Test/WebPage/TemplateStackTest.cs
@@ -0,0 +1,86 @@
+using System.Collections.Generic;
+using Moq;
+using Xunit;
+
+namespace System.Web.WebPages.Test
+{
+ public class WebPageContextStackTest
+ {
+ [Fact]
+ public void GetCurrentContextReturnsNullWhenStackIsEmpty()
+ {
+ // Arrange
+ var httpContext = GetHttpContext();
+
+ // Act
+ var template = TemplateStack.GetCurrentTemplate(httpContext);
+
+ // Assert
+ Assert.Equal(1, httpContext.Items.Count);
+ Assert.Null(template);
+ }
+
+ [Fact]
+ public void GetCurrentContextReturnsCurrentContext()
+ {
+ // Arrange
+ var template = GetTemplateFile();
+ var httpContext = GetHttpContext();
+
+ // Act
+ TemplateStack.Push(httpContext, template);
+
+ // Assert
+ var currentTemplate = TemplateStack.GetCurrentTemplate(httpContext);
+ Assert.Equal(template, currentTemplate);
+ }
+
+ [Fact]
+ public void GetCurrentContextReturnsLastPushedContext()
+ {
+ // Arrange
+ var httpContext = GetHttpContext();
+ var template1 = GetTemplateFile("page1");
+ var template2 = GetTemplateFile("page2");
+
+ // Act
+ TemplateStack.Push(httpContext, template1);
+ TemplateStack.Push(httpContext, template2);
+
+ // Assert
+ var currentTemplate = TemplateStack.GetCurrentTemplate(httpContext);
+ Assert.Equal(template2, currentTemplate);
+ }
+
+ [Fact]
+ public void GetCurrentContextReturnsNullAfterPop()
+ {
+ // Arrange
+ var httpContext = GetHttpContext();
+ var template = GetTemplateFile();
+
+ // Act
+ TemplateStack.Push(httpContext, template);
+ TemplateStack.Pop(httpContext);
+
+ // Assert
+ Assert.Null(TemplateStack.GetCurrentTemplate(httpContext));
+ }
+
+ private static HttpContextBase GetHttpContext()
+ {
+ Mock<HttpContextBase> context = new Mock<HttpContextBase>();
+ context.Setup(c => c.Items).Returns(new Dictionary<object, object>());
+
+ return context.Object;
+ }
+
+ private static ITemplateFile GetTemplateFile(string path = null)
+ {
+ Mock<ITemplateFile> templateFile = new Mock<ITemplateFile>();
+ templateFile.Setup(f => f.TemplateInfo).Returns(new TemplateFileInfo(path));
+
+ return templateFile.Object;
+ }
+ }
+}
diff --git a/test/System.Web.WebPages.Test/WebPage/UrlDataTest.cs b/test/System.Web.WebPages.Test/WebPage/UrlDataTest.cs
new file mode 100644
index 00000000..5f2de652
--- /dev/null
+++ b/test/System.Web.WebPages.Test/WebPage/UrlDataTest.cs
@@ -0,0 +1,111 @@
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.WebPages.Test
+{
+ public class UrlDataTest
+ {
+ [Fact]
+ public void UrlDataListConstructorTests()
+ {
+ Assert.NotNull(new UrlDataList(null));
+ Assert.NotNull(new UrlDataList(String.Empty));
+ Assert.NotNull(new UrlDataList("abc/foo"));
+ }
+
+ [Fact]
+ public void AddTest()
+ {
+ var d = new UrlDataList(null);
+ var item = "!!@#$#$";
+ Assert.Throws<NotSupportedException>(() => { d.Add(item); }, "The UrlData collection is read-only.");
+ }
+
+ [Fact]
+ public void ClearTest()
+ {
+ var d = new UrlDataList(null);
+ Assert.Throws<NotSupportedException>(() => { d.Clear(); }, "The UrlData collection is read-only.");
+ }
+
+ [Fact]
+ public void IndexOfTest()
+ {
+ var item = "!!@#$#$";
+ var item2 = "13l53125";
+ var d = new UrlDataList(item + "/" + item2);
+ Assert.True(d.IndexOf(item) == 0);
+ Assert.True(d.IndexOf(item2) == 1);
+ }
+
+ [Fact]
+ public void InsertAtTest()
+ {
+ var d = new UrlDataList("x/y/z");
+ Assert.Throws<NotSupportedException>(() => { d.Insert(1, "a"); }, "The UrlData collection is read-only.");
+ }
+
+ [Fact]
+ public void ContainsTest()
+ {
+ var item = "!!@#$#$";
+ var d = new UrlDataList(item);
+ Assert.True(d.Contains(item));
+ }
+
+ [Fact]
+ public void CopyToTest()
+ {
+ var d = new UrlDataList("x/y");
+ string[] array = new string[2];
+ d.CopyTo(array, 0);
+ Assert.Equal(array[0], d[0]);
+ Assert.Equal(array[1], d[1]);
+ }
+
+ [Fact]
+ public void GetEnumeratorTest()
+ {
+ var d = new UrlDataList("x");
+ var e = d.GetEnumerator();
+ e.MoveNext();
+ Assert.Equal("x", e.Current);
+ }
+
+ [Fact]
+ public void RemoveTest()
+ {
+ var d = new UrlDataList("x");
+ Assert.Throws<NotSupportedException>(() => { d.Remove("x"); }, "The UrlData collection is read-only.");
+ }
+
+ [Fact]
+ public void RemoveAtTest()
+ {
+ var d = new UrlDataList("x/y");
+ Assert.Throws<NotSupportedException>(() => { d.RemoveAt(0); }, "The UrlData collection is read-only.");
+ }
+
+ [Fact]
+ public void CountTest()
+ {
+ var d = new UrlDataList("x");
+ Assert.Equal(1, d.Count);
+ }
+
+ [Fact]
+ public void IsReadOnlyTest()
+ {
+ var d = new UrlDataList(null);
+ Assert.Equal(true, d.IsReadOnly);
+ }
+
+ [Fact]
+ public void ItemTest()
+ {
+ var d = new UrlDataList("x/y");
+ Assert.Equal("x", d[0]);
+ Assert.Equal("y", d[1]);
+ }
+ }
+}
diff --git a/test/System.Web.WebPages.Test/WebPage/Utils.cs b/test/System.Web.WebPages.Test/WebPage/Utils.cs
new file mode 100644
index 00000000..2651e950
--- /dev/null
+++ b/test/System.Web.WebPages.Test/WebPage/Utils.cs
@@ -0,0 +1,261 @@
+using System.Collections;
+using System.Collections.Generic;
+using System.Collections.Specialized;
+using System.IO;
+using System.Reflection;
+using System.Text;
+using System.Web.Hosting;
+using System.Web.WebPages.TestUtils;
+using Moq;
+
+namespace System.Web.WebPages.Test
+{
+ public static class Utils
+ {
+ public static string RenderWebPage(WebPage page, StartPage startPage = null, HttpRequestBase request = null)
+ {
+ var writer = new StringWriter();
+
+ // Create an actual dummy HttpContext that has a request object
+ var filename = "default.aspx";
+ var url = "http://localhost/default.aspx";
+
+ request = request ?? CreateTestRequest(filename, url).Object;
+ var httpContext = CreateTestContext(request);
+
+ var pageContext = new WebPageContext { HttpContext = httpContext.Object };
+ page.ExecutePageHierarchy(pageContext, writer, startPage);
+ return writer.ToString();
+ }
+
+ public static Mock<HttpContextBase> CreateTestContext(HttpRequestBase request = null, HttpResponseBase response = null, IDictionary items = null)
+ {
+ items = items ?? new Hashtable();
+ request = request ?? CreateTestRequest("default.cshtml", "http://localhost/default.cshtml").Object;
+
+ if (response == null)
+ {
+ var mockResponse = new Mock<HttpResponseBase>();
+ mockResponse.Setup(r => r.Cookies).Returns(new HttpCookieCollection());
+ response = mockResponse.Object;
+ }
+
+ var httpContext = new Mock<HttpContextBase>();
+ httpContext.SetupGet(c => c.Items).Returns(items);
+ httpContext.SetupGet(c => c.Request).Returns(request);
+ httpContext.SetupGet(c => c.Response).Returns(response);
+ return httpContext;
+ }
+
+ public static Mock<HttpRequestBase> CreateTestRequest(string filename, string url)
+ {
+ var mockRequest = new Mock<HttpRequestBase> { CallBase = true };
+ mockRequest.SetupGet(r => r.Path).Returns(filename);
+ mockRequest.SetupGet(r => r.RawUrl).Returns(url);
+ mockRequest.SetupGet(r => r.IsLocal).Returns(false);
+ mockRequest.SetupGet(r => r.QueryString).Returns(new NameValueCollection());
+ mockRequest.SetupGet(r => r.Browser.IsMobileDevice).Returns(false);
+ mockRequest.SetupGet(r => r.Cookies).Returns(new HttpCookieCollection());
+ mockRequest.SetupGet(r => r.UserAgent).Returns(String.Empty);
+
+ return mockRequest;
+ }
+
+ public static string RenderWebPage(Action<WebPage> pageExecuteAction, string pagePath = "~/index.cshtml")
+ {
+ var page = CreatePage(pageExecuteAction, pagePath);
+ return RenderWebPage(page);
+ }
+
+ public static MockPage CreatePage(Action<WebPage> pageExecuteAction, string pagePath = "~/index.cshtml")
+ {
+ var page = new MockPage()
+ {
+ VirtualPath = pagePath,
+ ExecuteAction = p => { pageExecuteAction(p); }
+ };
+ page.VirtualPathFactory = new HashVirtualPathFactory(page);
+ page.DisplayModeProvider = new DisplayModeProvider();
+ return page;
+ }
+
+ public static MockStartPage CreateStartPage(Action<StartPage> pageExecuteAction, string pagePath = "~/_pagestart.cshtml")
+ {
+ var page = new MockStartPage()
+ {
+ VirtualPath = pagePath,
+ ExecuteAction = p => { pageExecuteAction(p); }
+ };
+ page.VirtualPathFactory = new HashVirtualPathFactory(page);
+ page.DisplayModeProvider = new DisplayModeProvider();
+ return page;
+ }
+
+ public static string RenderWebPageWithSubPage(Action<WebPage> pageExecuteAction, Action<WebPage> subpageExecuteAction,
+ string pagePath = "~/index.cshtml", string subpagePath = "~/subpage.cshtml")
+ {
+ var page = CreatePage(pageExecuteAction);
+ var subPage = CreatePage(subpageExecuteAction, subpagePath);
+ var virtualPathFactory = new HashVirtualPathFactory(page, subPage);
+ subPage.VirtualPathFactory = virtualPathFactory;
+ page.VirtualPathFactory = virtualPathFactory;
+ return RenderWebPage(page);
+ }
+
+ // E.g. "default.aspx", "http://localhost/WebSite1/subfolder1/default.aspx"
+ /// <summary>
+ /// Creates an instance of HttpContext and assigns it to HttpContext.Current. Ensure that the returned value is disposed at the end of the test.
+ /// </summary>
+ /// <returns>Returns an IDisposable that restores the original HttpContext.</returns>
+ internal static IDisposable CreateHttpContext(string filename, string url)
+ {
+ var request = new HttpRequest(filename, url, null);
+ var httpContext = new HttpContext(request, new HttpResponse(new StringWriter(new StringBuilder())));
+ HttpContext.Current = httpContext;
+
+ return new DisposableAction(RestoreHttpContext);
+ }
+
+ internal static void RestoreHttpContext()
+ {
+ HttpContext.Current = null;
+ }
+
+ internal static IDisposable CreateHttpRuntime(string appVPath)
+ {
+ return WebUtils.CreateHttpRuntime(appVPath);
+ }
+
+ public static void SetupVirtualPathInAppDomain(string vpath, string contents)
+ {
+ var file = new Mock<VirtualFile>(vpath);
+ file.Setup(f => f.Open()).Returns(new MemoryStream(ASCIIEncoding.Default.GetBytes(contents)));
+ var vpp = new Mock<VirtualPathProvider>();
+ vpp.Setup(p => p.FileExists(vpath)).Returns(true);
+ vpp.Setup(p => p.GetFile(vpath)).Returns(file.Object);
+ AppDomainUtils.SetAppData();
+ var env = new HostingEnvironment();
+
+ var register = typeof(HostingEnvironment).GetMethod("RegisterVirtualPathProviderInternal", BindingFlags.Static | BindingFlags.NonPublic);
+ register.Invoke(null, new object[] { vpp.Object });
+ }
+
+ /// <summary>
+ /// Assigns a common object factory to the pages.
+ /// </summary>
+ internal static IVirtualPathFactory AssignObjectFactoriesAndDisplayModeProvider(params WebPageExecutingBase[] pages)
+ {
+ var objectFactory = new HashVirtualPathFactory(pages);
+ var displayModeProvider = new DisplayModeProvider();
+ foreach (var item in pages)
+ {
+ item.VirtualPathFactory = objectFactory;
+ var webPageRenderingBase = item as WebPageRenderingBase;
+ if (webPageRenderingBase != null)
+ {
+ webPageRenderingBase.DisplayModeProvider = displayModeProvider;
+ }
+ }
+
+ return objectFactory;
+ }
+
+ internal static DisplayModeProvider AssignDisplayModeProvider(params WebPageRenderingBase[] pages)
+ {
+ var displayModeProvider = new DisplayModeProvider();
+ foreach (var item in pages)
+ {
+ item.DisplayModeProvider = displayModeProvider;
+ }
+ return displayModeProvider;
+ }
+ }
+
+ public class MockPageHelper
+ {
+ internal static string GetDirectory(string virtualPath)
+ {
+ var dir = Path.GetDirectoryName(virtualPath);
+ if (dir == "~")
+ {
+ return null;
+ }
+ return dir;
+ }
+ }
+
+ // This is a mock implementation of WebPage mainly to make the Render method work and
+ // generate a string.
+ // The Execute method simulates what is typically generated based on markup by the parsers.
+ public class MockPage : WebPage
+ {
+ public Action<WebPage> ExecuteAction { get; set; }
+
+ internal override string GetDirectory(string virtualPath)
+ {
+ return MockPageHelper.GetDirectory(virtualPath);
+ }
+
+ public override void Execute()
+ {
+ ExecuteAction(this);
+ }
+ }
+
+ public class MockStartPage : StartPage
+ {
+ public Action<StartPage> ExecuteAction { get; set; }
+
+ internal override string GetDirectory(string virtualPath)
+ {
+ return MockPageHelper.GetDirectory(virtualPath);
+ }
+
+ public override void Execute()
+ {
+ ExecuteAction(this);
+ }
+
+ public Dictionary<string, object> CombinedPageInstances
+ {
+ get
+ {
+ var combinedInstances = new Dictionary<string, object>();
+ var instances = new Dictionary<string, object>();
+ WebPageRenderingBase childPage = this;
+ while (childPage != null)
+ {
+ if (childPage is MockStartPage)
+ {
+ var initPage = childPage as MockStartPage;
+ childPage = initPage.ChildPage;
+ }
+ else if (childPage is MockPage)
+ {
+ childPage = null;
+ }
+ foreach (var kvp in instances)
+ {
+ combinedInstances.Add(kvp.Key, kvp.Value);
+ }
+ }
+ return combinedInstances;
+ }
+ }
+ }
+
+ public class MockHttpRuntime
+ {
+ public static Version RequestValidationMode { get; set; }
+ }
+
+ public class MockHttpApplication
+ {
+ public static Type ModuleType { get; set; }
+
+ public static void RegisterModule(Type moduleType)
+ {
+ ModuleType = moduleType;
+ }
+ }
+}
diff --git a/test/System.Web.WebPages.Test/WebPage/VirtualPathFactoryExtensionsTest.cs b/test/System.Web.WebPages.Test/WebPage/VirtualPathFactoryExtensionsTest.cs
new file mode 100644
index 00000000..50bc393e
--- /dev/null
+++ b/test/System.Web.WebPages.Test/WebPage/VirtualPathFactoryExtensionsTest.cs
@@ -0,0 +1,61 @@
+using Moq;
+using Xunit;
+
+namespace System.Web.WebPages.Test
+{
+ public class VirtualPathFactoryExtensionsTest
+ {
+ [Fact]
+ public void VirtualPathFactoryExtensionsSpecialCasesVirtualPathFactoryManager()
+ {
+ // Arrange
+ var virtualPath = "~/index.cshtml";
+ var mockPage = Utils.CreatePage(_ => { }, virtualPath);
+ var factory = new Mock<IVirtualPathFactory>();
+ factory.Setup(c => c.Exists(virtualPath)).Returns(true).Verifiable();
+ factory.Setup(c => c.CreateInstance(virtualPath)).Returns(mockPage);
+
+ // Act
+ var factoryManager = new VirtualPathFactoryManager(factory.Object);
+ var page = factoryManager.CreateInstance<WebPageBase>(virtualPath);
+
+ // Assert
+ Assert.Equal(mockPage, page);
+ factory.Verify();
+ }
+
+ [Fact]
+ public void GenericCreateInstanceLoopsOverAllRegisteredFactories()
+ {
+ // Arrange
+ var virtualPath = "~/index.cshtml";
+ var mockPage = Utils.CreatePage(_ => { }, virtualPath);
+ var factory1 = new HashVirtualPathFactory(mockPage);
+ var factory2 = new HashVirtualPathFactory(Utils.CreatePage(null, "~/_admin/index.cshtml"));
+
+ // Act
+ var factoryManager = new VirtualPathFactoryManager(factory2);
+ factoryManager.RegisterVirtualPathFactoryInternal(factory1);
+ var page = factoryManager.CreateInstance<WebPageBase>(virtualPath);
+
+ // Assert
+ Assert.Equal(mockPage, page);
+ }
+
+ [Fact]
+ public void GenericCreateInstanceReturnsNullIfNoFactoryCanCreateVirtualPath()
+ {
+ // Arrange
+ var factory1 = new HashVirtualPathFactory(Utils.CreatePage(_ => { }, "~/index.cshtml"));
+ var factory2 = new HashVirtualPathFactory(Utils.CreatePage(null, "~/_admin/index.cshtml"));
+
+ // Act
+ var factoryManager = new VirtualPathFactoryManager(factory2);
+ factoryManager.RegisterVirtualPathFactoryInternal(factory1);
+ var page = factoryManager.CreateInstance<WebPageBase>("~/does-not-exist.cshtml");
+
+ // Assert
+ Assert.Null(page);
+ }
+ }
+}
diff --git a/test/System.Web.WebPages.Test/WebPage/VirtualPathFactoryManagerTest.cs b/test/System.Web.WebPages.Test/WebPage/VirtualPathFactoryManagerTest.cs
new file mode 100644
index 00000000..d68ab21d
--- /dev/null
+++ b/test/System.Web.WebPages.Test/WebPage/VirtualPathFactoryManagerTest.cs
@@ -0,0 +1,103 @@
+using System.Linq;
+using Moq;
+using Xunit;
+
+namespace System.Web.WebPages.Test
+{
+ public class VirtualPathFactoryManagerTest
+ {
+ [Fact]
+ public void DefaultFactoryIsListedInRegisteredFactories()
+ {
+ // Arrange
+ var factory = new HashVirtualPathFactory();
+
+ // Act
+ var factoryManager = new VirtualPathFactoryManager(factory);
+
+ // Assert
+ Assert.Equal(factory, factoryManager.RegisteredFactories.Single());
+ }
+
+ [Fact]
+ public void RegisterFactoryEnsuresDefaultFactoryRemainsTheLast()
+ {
+ // Arrange
+ var defaultFactory = new HashVirtualPathFactory();
+ var factory1 = new HashVirtualPathFactory();
+ var factory2 = new HashVirtualPathFactory();
+ var factory3 = new HashVirtualPathFactory();
+
+ // Act
+ var factoryManager = new VirtualPathFactoryManager(defaultFactory);
+ factoryManager.RegisterVirtualPathFactoryInternal(factory1);
+ factoryManager.RegisterVirtualPathFactoryInternal(factory2);
+ factoryManager.RegisterVirtualPathFactoryInternal(factory3);
+
+ // Assert
+ Assert.Equal(factory1, factoryManager.RegisteredFactories.ElementAt(0));
+ Assert.Equal(factory2, factoryManager.RegisteredFactories.ElementAt(1));
+ Assert.Equal(factory3, factoryManager.RegisteredFactories.ElementAt(2));
+ Assert.Equal(defaultFactory, factoryManager.RegisteredFactories.Last());
+ }
+
+ [Fact]
+ public void CreateInstanceUsesRegisteredFactoriesForExistence()
+ {
+ // Arrange
+ var path = "~/index.cshtml";
+ var factory1 = new Mock<IVirtualPathFactory>();
+ factory1.Setup(c => c.Exists(path)).Returns(false).Verifiable();
+ var factory2 = new Mock<IVirtualPathFactory>();
+ factory2.Setup(c => c.Exists(path)).Returns(true).Verifiable();
+ var factory3 = new Mock<IVirtualPathFactory>();
+ factory3.Setup(c => c.Exists(path)).Throws(new Exception("This factory should not be called since the page has already been found in 2"));
+ var defaultFactory = new Mock<IVirtualPathFactory>();
+ defaultFactory.Setup(c => c.Exists(path)).Throws(new Exception("This factory should not be called since it always called last"));
+
+ var vpfm = new VirtualPathFactoryManager(defaultFactory.Object);
+ vpfm.RegisterVirtualPathFactoryInternal(factory1.Object);
+ vpfm.RegisterVirtualPathFactoryInternal(factory2.Object);
+ vpfm.RegisterVirtualPathFactoryInternal(factory3.Object);
+
+ // Act
+ var result = vpfm.Exists(path);
+
+ // Assert
+ Assert.True(result);
+
+ factory1.Verify();
+ factory2.Verify();
+ }
+
+ [Fact]
+ public void CreateInstanceLooksThroughAllRegisteredFactoriesForExistence()
+ {
+ // Arrange
+ var page = Utils.CreatePage(null);
+ var factory1 = new Mock<IVirtualPathFactory>();
+ factory1.Setup(c => c.Exists(page.VirtualPath)).Returns(false).Verifiable();
+ var factory2 = new Mock<IVirtualPathFactory>();
+ factory2.Setup(c => c.Exists(page.VirtualPath)).Returns(true).Verifiable();
+ factory2.Setup(c => c.CreateInstance(page.VirtualPath)).Returns(page).Verifiable();
+ var factory3 = new Mock<IVirtualPathFactory>();
+ factory3.Setup(c => c.Exists(page.VirtualPath)).Throws(new Exception("This factory should not be called since the page has already been found in 2"));
+ var defaultFactory = new Mock<IVirtualPathFactory>();
+ defaultFactory.Setup(c => c.Exists(page.VirtualPath)).Throws(new Exception("This factory should not be called since it always called last"));
+
+ var vpfm = new VirtualPathFactoryManager(defaultFactory.Object);
+ vpfm.RegisterVirtualPathFactoryInternal(factory1.Object);
+ vpfm.RegisterVirtualPathFactoryInternal(factory2.Object);
+ vpfm.RegisterVirtualPathFactoryInternal(factory3.Object);
+
+ // Act
+ var result = vpfm.CreateInstance(page.VirtualPath);
+
+ // Assert
+ Assert.Equal(page, result);
+
+ factory1.Verify();
+ factory2.Verify();
+ }
+ }
+}
diff --git a/test/System.Web.WebPages.Test/WebPage/WebPageContextTest.cs b/test/System.Web.WebPages.Test/WebPage/WebPageContextTest.cs
new file mode 100644
index 00000000..4e466571
--- /dev/null
+++ b/test/System.Web.WebPages.Test/WebPage/WebPageContextTest.cs
@@ -0,0 +1,57 @@
+using System.Collections.Generic;
+using System.IO;
+using Moq;
+using Xunit;
+
+namespace System.Web.WebPages.Test
+{
+ public class WebPageContextTest
+ {
+ [Fact]
+ public void CreateNestedPageContextCopiesPropertiesFromParentPageContext()
+ {
+ // Arrange
+ var httpContext = new Mock<HttpContextBase>();
+ var pageDataDictionary = new Dictionary<object, dynamic>();
+ var model = new { Hello = "World" };
+ Action<TextWriter> bodyAction = writer => { };
+ var sectionWritersStack = new Stack<Dictionary<string, SectionWriter>>();
+ var basePageContext = new WebPageContext(httpContext.Object, null, null) { BodyAction = bodyAction, SectionWritersStack = sectionWritersStack };
+
+ // Act
+ var subPageContext = WebPageContext.CreateNestedPageContext(basePageContext, pageDataDictionary, model, isLayoutPage: false);
+
+ // Assert
+ Assert.Equal(basePageContext.HttpContext, subPageContext.HttpContext);
+ Assert.Equal(basePageContext.OutputStack, subPageContext.OutputStack);
+ Assert.Equal(basePageContext.Validation, subPageContext.Validation);
+ Assert.Equal(pageDataDictionary, subPageContext.PageData);
+ Assert.Equal(model, subPageContext.Model);
+ Assert.Null(subPageContext.BodyAction);
+ }
+
+ [Fact]
+ public void CreateNestedPageCopiesBodyActionAndSectionWritersWithOtherPropertiesFromParentPageContext()
+ {
+ // Arrange
+ var httpContext = new Mock<HttpContextBase>();
+ var pageDataDictionary = new Dictionary<object, dynamic>();
+ var model = new { Hello = "World" };
+ Action<TextWriter> bodyAction = writer => { };
+ var sectionWritersStack = new Stack<Dictionary<string, SectionWriter>>();
+ var basePageContext = new WebPageContext(httpContext.Object, null, null) { BodyAction = bodyAction, SectionWritersStack = sectionWritersStack };
+
+ // Act
+ var subPageContext = WebPageContext.CreateNestedPageContext(basePageContext, pageDataDictionary, model, isLayoutPage: true);
+
+ // Assert
+ Assert.Equal(basePageContext.HttpContext, subPageContext.HttpContext);
+ Assert.Equal(basePageContext.OutputStack, subPageContext.OutputStack);
+ Assert.Equal(basePageContext.Validation, subPageContext.Validation);
+ Assert.Equal(pageDataDictionary, subPageContext.PageData);
+ Assert.Equal(model, subPageContext.Model);
+ Assert.Equal(sectionWritersStack, subPageContext.SectionWritersStack);
+ Assert.Equal(bodyAction, subPageContext.BodyAction);
+ }
+ }
+}
diff --git a/test/System.Web.WebPages.Test/WebPage/WebPageExecutingBaseTest.cs b/test/System.Web.WebPages.Test/WebPage/WebPageExecutingBaseTest.cs
new file mode 100644
index 00000000..211e4e8b
--- /dev/null
+++ b/test/System.Web.WebPages.Test/WebPage/WebPageExecutingBaseTest.cs
@@ -0,0 +1,189 @@
+using System.IO;
+using System.Text;
+using System.Web.WebPages.Instrumentation;
+using Moq;
+using Xunit;
+
+namespace System.Web.WebPages.Test
+{
+ public class WebPageExecutingBaseTest
+ {
+ [Fact]
+ public void NormalizeLayoutPageUsesVirtualPathFactoryManagerToDetermineIfLayoutFileExists()
+ {
+ // Arrange
+ var layoutPagePath = "~/sitelayout.cshtml";
+ var layoutPage = Utils.CreatePage(null, layoutPagePath);
+ var page = Utils.CreatePage(null);
+ var objectFactory = new Mock<IVirtualPathFactory>();
+ objectFactory.Setup(c => c.Exists(It.Is<string>(p => p.Equals(layoutPagePath)))).Returns(true).Verifiable();
+ page.VirtualPathFactory = objectFactory.Object;
+
+ // Act
+ var path = page.NormalizeLayoutPagePath(layoutPage.VirtualPath);
+
+ // Assert
+ objectFactory.Verify();
+ Assert.Equal(path, layoutPage.VirtualPath);
+ }
+
+ [Fact]
+ public void NormalizeLayoutPageAcceptsRelativePathsToLayoutPage()
+ {
+ // Arrange
+ var page = Utils.CreatePage(null, "~/dir/default.cshtml");
+ var layoutPage = Utils.CreatePage(null, "~/layouts/sitelayout.cshtml");
+ var objectFactory = new HashVirtualPathFactory(page, layoutPage);
+ page.VirtualPathFactory = objectFactory;
+
+ // Act
+ var path = page.NormalizeLayoutPagePath(@"../layouts/sitelayout.cshtml");
+
+ // Assert
+ Assert.Equal(path, layoutPage.VirtualPath);
+ }
+
+ [Fact]
+ public void BeginContextSilentlyFailsIfInstrumentationIsNotAvailable()
+ {
+ // Arrange
+ bool called = false;
+
+ var pageMock = new Mock<WebPageExecutingBase>() { CallBase = true };
+ pageMock.Setup(p => p.Context).Returns(new Mock<HttpContextBase>().Object);
+ pageMock.Object.InstrumentationService.IsAvailable = false;
+ pageMock.Object.InstrumentationService.ExtractInstrumentationService = c =>
+ {
+ called = true;
+ return null;
+ };
+
+ // Act
+ pageMock.Object.BeginContext("~/dir/default.cshtml", 0, 1, true);
+
+ // Assert
+ Assert.False(called);
+ }
+
+ [Fact]
+ public void EndContextSilentlyFailsIfInstrumentationIsNotAvailable()
+ {
+ // Arrange
+ bool called = false;
+
+ var pageMock = new Mock<WebPageExecutingBase>() { CallBase = true };
+ pageMock.Setup(p => p.Context).Returns(new Mock<HttpContextBase>().Object);
+ pageMock.Object.InstrumentationService.IsAvailable = false;
+ pageMock.Object.InstrumentationService.ExtractInstrumentationService = c =>
+ {
+ called = true;
+ return null;
+ };
+
+ // Act
+ pageMock.Object.EndContext("~/dir/default.cshtml", 0, 1, true);
+
+ // Assert
+ Assert.False(called);
+ }
+
+ [Fact]
+ public void WriteAttributeToWritesAttributeNormallyIfNoValuesSpecified()
+ {
+ WriteAttributeTest(
+ name: "alt",
+ prefix: new PositionTagged<string>(" alt=\"", 42),
+ suffix: new PositionTagged<string>("\"", 24),
+ expected: " alt=\"\"");
+ }
+
+ [Fact]
+ public void WriteAttributeToWritesNothingIfSingleNullValueProvided()
+ {
+ WriteAttributeTest(
+ name: "alt",
+ prefix: new PositionTagged<string>(" alt=\"", 42),
+ suffix: new PositionTagged<string>("\"", 24),
+ values: new[] {
+ new AttributeValue(new PositionTagged<string>(String.Empty, 142), new PositionTagged<object>(null, 124), literal: true)
+ },
+ expected: "");
+ }
+
+ [Fact]
+ public void WriteAttributeToWritesNothingIfSingleFalseValueProvided()
+ {
+ WriteAttributeTest(
+ name: "alt",
+ prefix: new PositionTagged<string>(" alt=\"", 42),
+ suffix: new PositionTagged<string>("\"", 24),
+ values: new[] {
+ new AttributeValue(new PositionTagged<string>(String.Empty, 142), new PositionTagged<object>(false, 124), literal: true)
+ },
+ expected: "");
+ }
+
+ [Fact]
+ public void WriteAttributeToWritesGlobalPrefixIfSingleValueProvided()
+ {
+ WriteAttributeTest(
+ name: "alt",
+ prefix: new PositionTagged<string>(" alt=\"", 42),
+ suffix: new PositionTagged<string>("\"", 24),
+ values: new[] {
+ new AttributeValue(new PositionTagged<string>(" ", 142), new PositionTagged<object>("foo", 124), literal: true)
+ },
+ expected: " alt=\"foo\"");
+ }
+
+ [Fact]
+ public void WriteAttributeToWritesLocalPrefixForSecondValueProvided()
+ {
+ WriteAttributeTest(
+ name: "alt",
+ prefix: new PositionTagged<string>(" alt=\"", 42),
+ suffix: new PositionTagged<string>("\"", 24),
+ values: new[] {
+ new AttributeValue(new PositionTagged<string>(" ", 142), new PositionTagged<object>("foo", 124), literal: true),
+ new AttributeValue(new PositionTagged<string>("glorb", 142), new PositionTagged<object>("bar", 124), literal: true)
+ },
+ expected: " alt=\"fooglorbbar\"");
+ }
+
+ [Fact]
+ public void WriteAttributeToWritesGlobalPrefixOnlyIfSecondValueIsFirstNonNullOrFalse()
+ {
+ WriteAttributeTest(
+ name: "alt",
+ prefix: new PositionTagged<string>(" alt=\"", 42),
+ suffix: new PositionTagged<string>("\"", 24),
+ values: new[] {
+ new AttributeValue(new PositionTagged<string>(" ", 142), new PositionTagged<object>(null, 124), literal: true),
+ new AttributeValue(new PositionTagged<string>("glorb", 142), new PositionTagged<object>("bar", 124), literal: true)
+ },
+ expected: " alt=\"bar\"");
+ }
+
+ private void WriteAttributeTest(string name, PositionTagged<string> prefix, PositionTagged<string> suffix, string expected)
+ {
+ WriteAttributeTest(name, prefix, suffix, new AttributeValue[0], expected);
+ }
+
+ private void WriteAttributeTest(string name, PositionTagged<string> prefix, PositionTagged<string> suffix, AttributeValue[] values, string expected)
+ {
+ // Arrange
+ var pageMock = new Mock<WebPageExecutingBase>() { CallBase = true };
+ pageMock.Setup(p => p.Context).Returns(new Mock<HttpContextBase>().Object);
+ pageMock.Object.InstrumentationService.IsAvailable = false;
+
+ StringBuilder written = new StringBuilder();
+ StringWriter writer = new StringWriter(written);
+
+ // Act
+ pageMock.Object.WriteAttributeTo(writer, name, prefix, suffix, values);
+
+ // Assert
+ Assert.Equal(expected, written.ToString());
+ }
+ }
+}
diff --git a/test/System.Web.WebPages.Test/WebPage/WebPageHttpHandlerTest.cs b/test/System.Web.WebPages.Test/WebPage/WebPageHttpHandlerTest.cs
new file mode 100644
index 00000000..902ac3d3
--- /dev/null
+++ b/test/System.Web.WebPages.Test/WebPage/WebPageHttpHandlerTest.cs
@@ -0,0 +1,249 @@
+using System.Collections;
+using System.Collections.Specialized;
+using System.IO;
+using System.Security;
+using System.Text;
+using Moq;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.WebPages.Test
+{
+ public class WebPageHttpHandlerTest
+ {
+ [Fact]
+ public void ConstructorThrowsWithNullPage()
+ {
+ Assert.ThrowsArgumentNull(() => new WebPageHttpHandler(null), "webPage");
+ }
+
+ [Fact]
+ public void IsReusableTest()
+ {
+ WebPage dummyPage = new DummyPage();
+ Assert.False(new WebPageHttpHandler(dummyPage).IsReusable);
+ }
+
+ [Fact]
+ public void ProcessRequestTest()
+ {
+ var contents = "test";
+ var tw = new StringWriter(new StringBuilder());
+ var httpContext = CreateTestContext(tw);
+ var page = Utils.CreatePage(p => p.Write(contents));
+ new WebPageHttpHandler(page).ProcessRequestInternal(httpContext);
+ Assert.Equal(contents, tw.ToString());
+ }
+
+ [Fact]
+ public void SourceFileHeaderTest()
+ {
+ // Arrange
+ var contents = "test";
+ var writer = new StringWriter();
+
+ Mock<HttpResponseBase> httpResponse = new Mock<HttpResponseBase>();
+ httpResponse.SetupGet(r => r.Output).Returns(writer);
+ Mock<HttpRequestBase> httpRequest = Utils.CreateTestRequest("~/index.cshtml", "~/index.cshtml");
+ httpRequest.SetupGet(r => r.IsLocal).Returns(true);
+ httpRequest.Setup(r => r.MapPath(It.IsAny<string>())).Returns<string>(p => p);
+ Mock<HttpContextBase> context = Utils.CreateTestContext(httpRequest.Object, httpResponse.Object);
+ var page = Utils.CreatePage(p => p.Write(contents));
+
+ // Act
+ var webPageHttpHandler = new WebPageHttpHandler(page);
+ webPageHttpHandler.ProcessRequestInternal(context.Object);
+
+ // Assert
+ Assert.Equal(contents, writer.ToString());
+ Assert.Equal(1, page.PageContext.SourceFiles.Count);
+ Assert.True(page.PageContext.SourceFiles.Contains("~/index.cshtml"));
+ }
+
+ [Fact]
+ public void GenerateSourceFilesHeaderGenerates2047EncodedValue()
+ {
+ // Arrange
+ string headerKey = null, headerValue = null;
+ var context = new Mock<HttpContextBase>();
+ var response = new Mock<HttpResponseBase>();
+ response.Setup(c => c.AddHeader(It.IsAny<string>(), It.IsAny<string>())).Callback(
+ (string key, string value) =>
+ {
+ headerKey = key;
+ headerValue = value;
+ });
+ context.Setup(c => c.Response).Returns(response.Object);
+ context.Setup(c => c.Items).Returns(new Hashtable());
+
+ var webPageContext = new WebPageContext(context.Object, page: null, model: null);
+ webPageContext.SourceFiles.Add("foo");
+ webPageContext.SourceFiles.Add("bar");
+ webPageContext.SourceFiles.Add("λ");
+
+ // Act
+ WebPageHttpHandler.GenerateSourceFilesHeader(webPageContext);
+
+ // Assert
+ Assert.Equal(headerKey, "X-SourceFiles");
+ Assert.Equal(headerValue, "=?UTF-8?B?Zm9vfGJhcnzOuw==?=");
+ }
+
+ [Fact]
+ public void HttpHandlerGeneratesSourceFilesHeadersIfRequestIsLocal()
+ {
+ // Arrange
+ string pagePath = "~/index.cshtml", layoutPath = "~/Layout.cshtml", layoutPageName = "Layout.cshtml";
+ var page = Utils.CreatePage(p => { p.Layout = layoutPageName; }, pagePath);
+ var layoutPage = Utils.CreatePage(p => { p.RenderBody(); }, layoutPath);
+ Utils.AssignObjectFactoriesAndDisplayModeProvider(layoutPage, page);
+
+
+ var headers = new NameValueCollection();
+ var request = Utils.CreateTestRequest(pagePath, pagePath);
+ request.Setup(c => c.IsLocal).Returns(true);
+ request.Setup(c => c.MapPath(It.IsAny<string>())).Returns<string>(path => path);
+ request.Setup(c => c.Cookies).Returns(new HttpCookieCollection());
+
+ var response = new Mock<HttpResponseBase>() { CallBase = true };
+ response.SetupGet(r => r.Headers).Returns(headers);
+ response.SetupGet(r => r.Output).Returns(TextWriter.Null);
+ response.Setup(r => r.AppendHeader(It.IsAny<string>(), It.IsAny<string>())).Callback<string, string>((name, value) => headers.Add(name, value));
+ response.Setup(r => r.AddHeader(It.IsAny<string>(), It.IsAny<string>())).Callback<string, string>((name, value) => headers.Add(name, value));
+ response.Setup(r => r.Cookies).Returns(new HttpCookieCollection());
+
+ var context = Utils.CreateTestContext(request.Object, response.Object);
+
+ // Act
+ var webPageHttpHandler = new WebPageHttpHandler(page);
+ webPageHttpHandler.ProcessRequestInternal(context.Object);
+
+ // Assert
+ Assert.Equal("2.0", headers[WebPageHttpHandler.WebPagesVersionHeaderName]);
+ Assert.Equal("=?UTF-8?B?fi9pbmRleC5jc2h0bWx8fi9MYXlvdXQuY3NodG1s?=", headers["X-SourceFiles"]);
+ }
+
+ [Fact]
+ public void ExceptionTest()
+ {
+ var contents = "test";
+ var httpContext = Utils.CreateTestContext().Object;
+ var page = Utils.CreatePage(p => { throw new InvalidOperationException(contents); });
+ var e = Assert.Throws<HttpUnhandledException>(
+ () => new WebPageHttpHandler(page).ProcessRequestInternal(httpContext)
+ );
+ Assert.IsType<InvalidOperationException>(e.InnerException);
+ Assert.Equal(contents, e.InnerException.Message, StringComparer.Ordinal);
+ }
+
+ [Fact]
+ public void SecurityExceptionTest()
+ {
+ var contents = "test";
+ var httpContext = Utils.CreateTestContext().Object;
+ var page = Utils.CreatePage(p => { throw new SecurityException(contents); });
+ Assert.Throws<SecurityException>(
+ () => new WebPageHttpHandler(page).ProcessRequestInternal(httpContext),
+ contents);
+ }
+
+ [Fact]
+ public void CreateFromVirtualPathTest()
+ {
+ var contents = "test";
+ var textWriter = new StringWriter();
+
+ var httpResponse = new Mock<HttpResponseBase>();
+ httpResponse.SetupGet(r => r.Output).Returns(textWriter);
+ var httpContext = Utils.CreateTestContext(response: httpResponse.Object);
+ var mockBuildManager = new Mock<IVirtualPathFactory>();
+ var virtualPath = "~/hello/test.cshtml";
+ var page = Utils.CreatePage(p => p.Write(contents));
+ mockBuildManager.Setup(c => c.Exists(It.Is<string>(p => p.Equals(virtualPath)))).Returns<string>(_ => true).Verifiable();
+ mockBuildManager.Setup(c => c.CreateInstance(It.Is<string>(p => p.Equals(virtualPath)))).Returns(page).Verifiable();
+
+ // Act
+ IHttpHandler handler = WebPageHttpHandler.CreateFromVirtualPath(virtualPath, new VirtualPathFactoryManager(mockBuildManager.Object));
+ Assert.IsType<WebPageHttpHandler>(handler);
+ WebPageHttpHandler webPageHttpHandler = (WebPageHttpHandler)handler;
+ webPageHttpHandler.ProcessRequestInternal(httpContext.Object);
+
+ // Assert
+ Assert.Equal(contents, textWriter.ToString());
+ mockBuildManager.Verify();
+ }
+
+ [Fact]
+ public void VersionHeaderTest()
+ {
+ bool headerSet = false;
+ Mock<HttpResponseBase> mockResponse = new Mock<HttpResponseBase>();
+ mockResponse.Setup(response => response.AppendHeader("X-AspNetWebPages-Version", "2.0")).Callback(() => headerSet = true);
+
+ Mock<HttpContextBase> mockContext = new Mock<HttpContextBase>();
+ mockContext.SetupGet(context => context.Response).Returns(mockResponse.Object);
+
+ WebPageHttpHandler.AddVersionHeader(mockContext.Object);
+ Assert.True(headerSet);
+ }
+
+ [Fact]
+ public void CreateFromVirtualPathNonWebPageTest()
+ {
+ // Arrange
+ var virtualPath = "~/hello/test.cshtml";
+ var handler = new WebPageHttpHandler(new DummyPage());
+ var mockBuildManager = new Mock<IVirtualPathFactory>();
+ mockBuildManager.Setup(c => c.CreateInstance(It.IsAny<string>())).Returns(handler);
+ mockBuildManager.Setup(c => c.Exists(It.Is<string>(p => p.Equals(virtualPath)))).Returns<string>(_ => true).Verifiable();
+
+ // Act
+ var result = WebPageHttpHandler.CreateFromVirtualPath(virtualPath, new VirtualPathFactoryManager(mockBuildManager.Object));
+
+ // Assert
+ Assert.Equal(handler, result);
+ mockBuildManager.Verify();
+ }
+
+ [Fact]
+ public void CreateFromVirtualPathReturnsIHttpHandlerIfItCannotCreateAWebPageType()
+ {
+ // Arrange
+ var pageVirtualPath = "~/hello/test.cshtml";
+ var handlerVirtualPath = "~/handler.asmx";
+ var page = new DummyPage();
+ var handler = new Mock<IHttpHandler>().Object;
+ var mockFactory = new Mock<IVirtualPathFactory>();
+ mockFactory.Setup(c => c.Exists(It.IsAny<string>())).Returns(true);
+ mockFactory.Setup(c => c.CreateInstance(pageVirtualPath)).Returns(page);
+ mockFactory.Setup(c => c.CreateInstance(handlerVirtualPath)).Returns(handler);
+
+ // Act
+ var handlerResult = WebPageHttpHandler.CreateFromVirtualPath(handlerVirtualPath, mockFactory.Object);
+ var pageResult = WebPageHttpHandler.CreateFromVirtualPath(pageVirtualPath, mockFactory.Object);
+
+ // Assert
+ Assert.Equal(handler, handlerResult);
+ Assert.NotNull(pageResult as WebPageHttpHandler);
+ }
+
+ private static HttpContextBase CreateTestContext(TextWriter textWriter)
+ {
+ var filename = "default.aspx";
+ var url = "http://localhost/WebSite1/subfolder1/default.aspx";
+ var request = Utils.CreateTestRequest(filename, url);
+
+ var response = new Mock<HttpResponseBase>();
+ response.SetupGet(r => r.Output).Returns(textWriter);
+
+ return Utils.CreateTestContext(request: request.Object, response: response.Object).Object;
+ }
+
+ private sealed class DummyPage : WebPage
+ {
+ public override void Execute()
+ {
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/test/System.Web.WebPages.Test/WebPage/WebPageHttpModuleTest.cs b/test/System.Web.WebPages.Test/WebPage/WebPageHttpModuleTest.cs
new file mode 100644
index 00000000..95866b35
--- /dev/null
+++ b/test/System.Web.WebPages.Test/WebPage/WebPageHttpModuleTest.cs
@@ -0,0 +1,83 @@
+using System.Web.WebPages.TestUtils;
+using Xunit;
+
+namespace System.Web.WebPages.Test
+{
+ public class WebPageHttpModuleTest
+ {
+ [Fact]
+ public void InitializeApplicationTest()
+ {
+ AppDomainUtils.RunInSeparateAppDomain(() =>
+ {
+ var moduleEvents = new ModuleEvents();
+ var app = new MyHttpApplication();
+ WebPageHttpModule.InitializeApplication(app,
+ moduleEvents.OnApplicationPostResolveRequestCache,
+ moduleEvents.Initialize);
+ Assert.True(moduleEvents.CalledInitialize);
+ });
+ }
+
+ [Fact]
+ public void StartApplicationTest()
+ {
+ AppDomainUtils.RunInSeparateAppDomain(() =>
+ {
+ var moduleEvents = new ModuleEvents();
+ var app = new MyHttpApplication();
+ WebPageHttpModule.StartApplication(app, moduleEvents.ExecuteStartPage, moduleEvents.ApplicationStart);
+ Assert.Equal(1, moduleEvents.CalledExecuteStartPage);
+ Assert.Equal(1, moduleEvents.CalledApplicationStart);
+
+ // Call a second time to make sure the methods are only called once
+ WebPageHttpModule.StartApplication(app, moduleEvents.ExecuteStartPage, moduleEvents.ApplicationStart);
+ Assert.Equal(1, moduleEvents.CalledExecuteStartPage);
+ Assert.Equal(1, moduleEvents.CalledApplicationStart);
+ });
+ }
+
+ public class MyHttpApplication : HttpApplication
+ {
+ public MyHttpApplication()
+ {
+ }
+ }
+
+ public class ModuleEvents
+ {
+ public void OnApplicationPostResolveRequestCache(object sender, EventArgs e)
+ {
+ }
+
+ public void OnBeginRequest(object sender, EventArgs e)
+ {
+ }
+
+ public void OnEndRequest(object sender, EventArgs e)
+ {
+ }
+
+ public bool CalledInitialize;
+
+ public void Initialize(object sender, EventArgs e)
+ {
+ CalledInitialize = true;
+ }
+
+ public int CalledExecuteStartPage;
+
+ public void ExecuteStartPage(HttpApplication application)
+ {
+ CalledExecuteStartPage++;
+ }
+
+ public int CalledApplicationStart;
+
+ public void ApplicationStart(object sender, EventArgs e)
+ {
+ CalledApplicationStart++;
+ }
+ }
+ }
+}
diff --git a/test/System.Web.WebPages.Test/WebPage/WebPageRenderingBaseTest.cs b/test/System.Web.WebPages.Test/WebPage/WebPageRenderingBaseTest.cs
new file mode 100644
index 00000000..66546446
--- /dev/null
+++ b/test/System.Web.WebPages.Test/WebPage/WebPageRenderingBaseTest.cs
@@ -0,0 +1,65 @@
+using Moq;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.WebPages.Test
+{
+ public class WebPageRenderingBaseTest
+ {
+ [Fact]
+ public void SetCultureThrowsIfValueIsNull()
+ {
+ // Arrange
+ string value = null;
+ var webPageRenderingBase = new Mock<WebPageRenderingBase>() { CallBase = true }.Object;
+
+ // Act and Assert
+ Assert.ThrowsArgumentNullOrEmptyString(() => webPageRenderingBase.Culture = value, "value");
+ }
+
+ [Fact]
+ public void SetCultureThrowsIfValueIsEmpty()
+ {
+ // Arrange
+ string value = String.Empty;
+ var webPageRenderingBase = new Mock<WebPageRenderingBase>() { CallBase = true }.Object;
+
+ // Act and Assert
+ Assert.ThrowsArgumentNullOrEmptyString(() => webPageRenderingBase.Culture = value, "value");
+ }
+
+ [Fact]
+ public void SetUICultureThrowsIfValueIsNull()
+ {
+ // Arrange
+ string value = null;
+ var webPageRenderingBase = new Mock<WebPageRenderingBase>() { CallBase = true }.Object;
+
+ // Act and Assert
+ Assert.ThrowsArgumentNullOrEmptyString(() => webPageRenderingBase.UICulture = value, "value");
+ }
+
+ [Fact]
+ public void SetUICultureThrowsIfValueIsEmpty()
+ {
+ // Arrange
+ string value = String.Empty;
+ var webPageRenderingBase = new Mock<WebPageRenderingBase>() { CallBase = true }.Object;
+
+ // Act and Assert
+ Assert.ThrowsArgumentNullOrEmptyString(() => webPageRenderingBase.UICulture = value, "value");
+ }
+
+ [Fact]
+ public void DisplayModePropertyWithNullContext()
+ {
+ // Arrange
+ var context = new Mock<HttpContextBase>();
+ var displayMode = new DefaultDisplayMode("test");
+ var webPageRenderingBase = new Mock<WebPageRenderingBase>() { CallBase = true };
+
+ // Act & Assert
+ Assert.Null(webPageRenderingBase.Object.DisplayMode);
+ }
+ }
+}
diff --git a/test/System.Web.WebPages.Test/WebPage/WebPageRouteTest.cs b/test/System.Web.WebPages.Test/WebPage/WebPageRouteTest.cs
new file mode 100644
index 00000000..2cd2b48e
--- /dev/null
+++ b/test/System.Web.WebPages.Test/WebPage/WebPageRouteTest.cs
@@ -0,0 +1,307 @@
+using System.Collections;
+using System.Collections.Generic;
+using Moq;
+using Xunit;
+using Xunit.Extensions;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.WebPages.Test
+{
+ public class WebPageRouteTest
+ {
+ private class HashyBuildManager : IVirtualPathFactory
+ {
+ private readonly HashSet<string> _existingFiles;
+
+ public HashyBuildManager(IEnumerable<string> validFilePaths)
+ {
+ _existingFiles = new HashSet<string>(validFilePaths, StringComparer.InvariantCultureIgnoreCase);
+ }
+
+ public bool Exists(string virtualPath)
+ {
+ return _existingFiles.Contains(virtualPath);
+ }
+
+ public object CreateInstance(string virtualPath)
+ {
+ throw new NotSupportedException();
+ }
+ }
+
+ // Helper to test smarty route match, null match string is used for no expected match
+ private static void ConstraintTest(IEnumerable<string> validFiles, IEnumerable<string> supportedExt, string url, string match, string pathInfo, bool mobileDevice = false)
+ {
+ var objectFactory = new HashyBuildManager(validFiles);
+ var mockContext = new Mock<HttpContextBase>();
+ mockContext.Setup(context => context.Items).Returns(new Hashtable());
+ mockContext.Setup(c => c.Request.Browser.IsMobileDevice).Returns(mobileDevice);
+ mockContext.Setup(c => c.Request.Cookies).Returns(new HttpCookieCollection());
+ mockContext.Setup(c => c.Response.Cookies).Returns(new HttpCookieCollection());
+ var displayModeProvider = new DisplayModeProvider();
+
+ WebPageMatch smartyMatch = WebPageRoute.MatchRequest(url, supportedExt, objectFactory, mockContext.Object, displayModeProvider);
+ if (match != null)
+ {
+ Assert.NotNull(smartyMatch);
+ Assert.Equal(match, smartyMatch.MatchedPath);
+ Assert.Equal(pathInfo, smartyMatch.PathInfo);
+ }
+ else
+ {
+ Assert.Null(smartyMatch);
+ }
+ }
+
+ [Theory,
+ InlineData("1.1/2/3", "1.1/2/3.3", ""),
+ InlineData("1/2/3/4", "1.one", "2/3/4"),
+ InlineData("2/3/4", "2.two", "3/4"),
+ InlineData("one/two/3/4/5/6", "one/two/3/4.4", "5/6"),
+ InlineData("one/two/3/4/5/6/foo", "one/two/3/4.4", "5/6/foo"),
+ InlineData("one/two/3/4/5/6/foo.htm", null, null)]
+ public void MultipleExtensionsTest(string url, string match, string pathInfo)
+ {
+ string[] files = new[] { "~/1.one", "~/2.two", "~/1.1/2/3.3", "~/one/two/3/4.4", "~/one/two/3/4/5/6/foo.htm" };
+ string[] extensions = new[] { "aspx", "hao", "one", "two", "3", "4" };
+
+ ConstraintTest(files, extensions, url, match, pathInfo);
+ }
+
+ [Theory,
+ InlineData("1.1/2/3", "1.1/2/3.Mobile.3", ""),
+ InlineData("1/2/3/4", "1.Mobile.one", "2/3/4"),
+ InlineData("2/3/4", "2.Mobile.two", "3/4"),
+ InlineData("one/two/3/4/5/6", "one/two/3/4.Mobile.4", "5/6"),
+ InlineData("one/two/3/4/5/6/foo", "one/two/3/4.Mobile.4", "5/6/foo"),
+ InlineData("one/two/3/4/5/6/foo.Mobile.htm", null, null)]
+ public void MultipleExtensionsMobileTest(string url, string match, string pathInfo)
+ {
+ string[] files = new[]
+ {
+ "~/1.one", "~/2.two", "~/1.1/2/3.3", "~/one/two/3/4.4", "~/one/two/3/4/5/6/foo.htm",
+ "~/1.Mobile.one", "~/2.Mobile.two", "~/1.1/2/3.Mobile.3", "~/one/two/3/4.Mobile.4", "~/one/two/3/4/5/6/foo.Mobile.htm"
+ };
+ string[] extensions = new[] { "aspx", "hao", "one", "two", "3", "4" };
+
+ ConstraintTest(files, extensions, url, match, pathInfo, mobileDevice: true);
+ }
+
+ [Fact]
+ public void FilesWithLeadingUnderscoresAreNeverServed()
+ {
+ string[] files = new[] { "~/hi.evil", "~/_hi.evil", "~/_nest/good.evil", "~/_nest/_hide.evil", "~/_ok.good" };
+ string[] extensions = new[] { "evil" };
+
+ ConstraintTest(files, extensions, "hi", "hi.evil", "");
+ ConstraintTest(files, extensions, "_nest/good/some/extra/path/info", "_nest/good.evil", "some/extra/path/info");
+ Assert.Throws<HttpException>(() => { ConstraintTest(files, extensions, "_hi", null, null); }, "Files with leading underscores (\"_\") cannot be served.");
+ Assert.Throws<HttpException>(() => { ConstraintTest(files, extensions, "_nest/_hide", null, null); }, "Files with leading underscores (\"_\") cannot be served.");
+ }
+
+ [Theory]
+ [InlineData(new object[] { "_foo", "_foo/default.cshtml" })]
+ [InlineData(new object[] { "_bar/_baz", "_bar/_baz/index.cshtml" })]
+ public void DirectoriesWithLeadingUnderscoresAreServed(string requestPath, string expectedPath)
+ {
+ // Arramge
+ var files = new[] { "~/_foo/default.cshtml", "~/_bar/_baz/index.cshtml" };
+ var extensions = new[] { "cshtml" };
+
+ // Act
+ ConstraintTest(files, extensions, requestPath, expectedPath, "");
+ }
+
+ [Fact]
+ public void TransformedUnderscoreAreNotServed()
+ {
+ string[] files = new[] { "~/_ok.Mobile.ext", "~/ok.ext" };
+ string[] extensions = new[] { "ext" };
+
+ ConstraintTest(files, extensions, "ok.ext", "ok.ext", "", mobileDevice: true);
+ ConstraintTest(files, extensions, "ok/some/extra/path/info", "ok.ext", "some/extra/path/info", mobileDevice: true);
+
+ Assert.Throws<HttpException>(() => { ConstraintTest(files, extensions, "_ok.Mobile.ext", null, null, mobileDevice: true); }, "Files with leading underscores (\"_\") cannot be served.");
+ }
+
+ [Fact]
+ public void MobileFilesAreReturnedInthePresenceOfUnderscoreFiles()
+ {
+ string[] files = new[] { "~/_ok.Mobile.ext", "~/ok.ext", "~/ok.mobile.ext" };
+ string[] extensions = new[] { "ext" };
+
+ ConstraintTest(files, extensions, "ok.ext", "ok.Mobile.ext", "", mobileDevice: true);
+ ConstraintTest(files, extensions, "ok/some/extra/path/info", "ok.Mobile.ext", "some/extra/path/info", mobileDevice: true);
+ ConstraintTest(files, extensions, "ok.mobile", "ok.mobile.ext", "", mobileDevice: false);
+ }
+
+ [Fact]
+ public void UnsupportedExtensionExistingFileTest()
+ {
+ ConstraintTest(new[] { "~/hao.aspx", "~/hao/hao.txt" }, new[] { "aspx" }, "hao/hao.txt", null, null);
+ }
+
+ [Fact]
+ public void NullPathValueDoesNotMatchTest()
+ {
+ ConstraintTest(new[] { "~/hao.aspx", "~/hao/hao.txt" }, new[] { "aspx" }, null, null, null);
+ }
+
+ [Fact]
+ public void RightToLeftPrecedenceTest()
+ {
+ ConstraintTest(new[] { "~/one/two/three.aspx", "~/one/two.aspx", "~/one.aspx" }, new[] { "aspx" }, "one/two/three", "one/two/three.aspx", "");
+ }
+
+ [Fact]
+ public void DefaultPrecedenceTests()
+ {
+ string[] files = new[] { "~/one/two/default.aspx", "~/one/default.aspx", "~/default.aspx" };
+ string[] extensions = new[] { "aspx" };
+
+ // Default only tries to look at the full path level
+ ConstraintTest(files, extensions, "one/two/three", null, null);
+ ConstraintTest(files, extensions, "one/two", "one/two/default.aspx", "");
+ ConstraintTest(files, extensions, "one", "one/default.aspx", "");
+ ConstraintTest(files, extensions, "", "default.aspx", "");
+ ConstraintTest(files, extensions, "one/two/three/four/five/six/7/8", null, null);
+ }
+
+ [Fact]
+ public void IndexTests()
+ {
+ string[] files = new[] { "~/one/two/index.aspx", "~/one/index.aspx", "~/index.aspq" };
+ string[] extensions = new[] { "aspx", "aspq" };
+
+ // index only tries to look at the full path level
+ ConstraintTest(files, extensions, "one/two/three", null, null);
+ ConstraintTest(files, extensions, "one/two", "one/two/index.aspx", "");
+ ConstraintTest(files, extensions, "one", "one/index.aspx", "");
+ ConstraintTest(files, extensions, "", "index.aspq", "");
+ ConstraintTest(files, extensions, "one/two/three/four/five/six/7/8", null, null);
+ }
+
+ [Fact]
+ public void DefaultVsIndexNestedTest()
+ {
+ string[] files = new[] { "~/one/two/index.aspx", "~/one/index.aspx", "~/one/default.aspx", "~/index.aspq", "~/default.aspx" };
+ string[] extensions = new[] { "aspx", "aspq" };
+
+ ConstraintTest(files, extensions, "one/two", "one/two/index.aspx", "");
+ ConstraintTest(files, extensions, "one", "one/default.aspx", "");
+ ConstraintTest(files, extensions, "", "default.aspx", "");
+ }
+
+ [Fact]
+ public void DefaultVsIndexSameExtensionTest()
+ {
+ string[] files = new[] { "~/one/two/index.aspx", "~/one/index.aspx", "~/one/default.aspx", "~/index.aspq", "~/default.aspx" };
+ string[] extensions = new[] { "aspx" };
+
+ ConstraintTest(files, extensions, "one", "one/default.aspx", "");
+ }
+
+ [Fact]
+ public void DefaultVsIndexDifferentExtensionTest()
+ {
+ string[] files = new[] { "~/index.aspq", "~/default.aspx" };
+ string[] extensions = new[] { "aspx", "aspq" };
+
+ ConstraintTest(files, extensions, "", "default.aspx", "");
+ }
+
+ [Fact]
+ public void DefaultVsIndexOnlyOneExtensionTest()
+ {
+ string[] files = new[] { "~/index.aspq", "~/default.aspx" };
+ string[] extensions = new[] { "aspq" };
+
+ ConstraintTest(files, extensions, "", "index.aspq", "");
+ }
+
+ [Fact]
+ public void FullMatchNoPathInfoTest()
+ {
+ ConstraintTest(new[] { "~/hao.aspx" }, new[] { "aspx" }, "hao", "hao.aspx", "");
+ }
+
+ [Fact]
+ public void MatchFileWithExtensionTest()
+ {
+ string[] files = new[] { "~/page.aspq" };
+ string[] extensions = new[] { "aspq" };
+
+ ConstraintTest(files, extensions, "page.aspq", "page.aspq", "");
+ }
+
+ [Fact]
+ public void NoMatchFileWithWrongExtensionTest()
+ {
+ string[] files = new[] { "~/page.aspx" };
+ string[] extensions = new[] { "aspq" };
+
+ ConstraintTest(files, extensions, "page.aspx", null, null);
+ }
+
+ [Fact]
+ public void WebPageRouteDoesNotPerformMappingIfRootLevelIsExplicitlyDisabled()
+ {
+ // Arrange
+ var webPageRoute = new WebPageRoute { IsExplicitlyDisabled = true };
+ var context = new Mock<HttpContextBase>();
+ context.Setup(c => c.RemapHandler(It.IsAny<IHttpHandler>())).Throws(new Exception("Smarty route should be disabled."));
+ context.SetupGet(c => c.Request).Throws(new Exception("We do not need to use the request to identify if the app is disabled."));
+
+ // Act
+ webPageRoute.DoPostResolveRequestCache(context.Object);
+
+ // Assert.
+ // If we've come this far, neither of the setups threw.
+ Assert.True(true);
+ }
+
+ [Fact]
+ public void MatchRequestSetsDisplayModeOfFirstMatchPerContext()
+ {
+ // Arrange
+ var objectFactory = new HashyBuildManager(new string[] { "~/page.Mobile.aspx", "~/nonMobile.aspx" });
+ var mockContext = new Mock<HttpContextBase>();
+ mockContext.Setup(context => context.Items).Returns(new Hashtable());
+ mockContext.Setup(c => c.Request.Browser.IsMobileDevice).Returns(true);
+ mockContext.Setup(c => c.Request.Cookies).Returns(new HttpCookieCollection());
+ mockContext.Setup(c => c.Response.Cookies).Returns(new HttpCookieCollection());
+
+ var displayModeProvider = new DisplayModeProvider();
+
+ // Act
+ WebPageMatch mobileMatch = WebPageRoute.MatchRequest("page.aspx", new string[] { "aspx" }, objectFactory, mockContext.Object, displayModeProvider);
+
+ // Assert
+ Assert.NotNull(mobileMatch.MatchedPath);
+ Assert.Equal(DisplayModeProvider.MobileDisplayModeId, DisplayModeProvider.GetDisplayMode(mockContext.Object).DisplayModeId);
+ }
+
+ [Fact]
+ public void MatchRequestDoesNotSetDisplayModeIfNoMatch()
+ {
+ // Arrange
+ var objectFactory = new HashyBuildManager(new string[] { "~/page.Mobile.aspx" });
+ var mockContext = new Mock<HttpContextBase>();
+ mockContext.Setup(context => context.Items).Returns(new Hashtable());
+ mockContext.Setup(c => c.Request.Browser.IsMobileDevice).Returns(true);
+ mockContext.Setup(c => c.Request.Cookies).Returns(new HttpCookieCollection());
+ mockContext.Setup(c => c.Response.Cookies).Returns(new HttpCookieCollection());
+
+ var displayModeProvider = new DisplayModeProvider();
+ var displayMode = new Mock<IDisplayMode>(MockBehavior.Strict);
+ displayMode.Setup(d => d.CanHandleContext(mockContext.Object)).Returns(false);
+ displayModeProvider.Modes.Add(displayMode.Object);
+
+ // Act
+ WebPageMatch smartyMatch = WebPageRoute.MatchRequest("notThere.aspx", new string[] { "aspx" }, objectFactory, mockContext.Object, displayModeProvider);
+
+ // Assert
+ Assert.Null(DisplayModeProvider.GetDisplayMode(mockContext.Object));
+ }
+ }
+}
diff --git a/test/System.Web.WebPages.Test/WebPage/WebPageSurrogateControlBuilderTest.cs b/test/System.Web.WebPages.Test/WebPage/WebPageSurrogateControlBuilderTest.cs
new file mode 100644
index 00000000..8c7e5f5c
--- /dev/null
+++ b/test/System.Web.WebPages.Test/WebPage/WebPageSurrogateControlBuilderTest.cs
@@ -0,0 +1,550 @@
+using System;
+using System.CodeDom;
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection;
+using System.Web;
+using System.Web.UI;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Microsoft.WebPages.Resources;
+using Microsoft.WebPages.TestUtils;
+
+namespace Microsoft.WebPages.Test {
+ /// <summary>
+ ///This is a test class for WebPageSurrogateControlBuilderTest and is intended
+ ///to contain all WebPageSurrogateControlBuilderTest Unit Tests
+ ///</summary>
+ [TestClass()]
+ public class WebPageSurrogateControlBuilderTest {
+
+
+ private TestContext testContextInstance;
+
+ /// <summary>
+ ///Gets or sets the test context which provides
+ ///information about and functionality for the current test run.
+ ///</summary>
+ public TestContext TestContext {
+ get {
+ return testContextInstance;
+ }
+ set {
+ testContextInstance = value;
+ }
+ }
+
+ #region Additional test attributes
+ //
+ //You can use the following additional attributes as you write your tests:
+ //
+ //Use ClassInitialize to run code before running the first test in the class
+ //[ClassInitialize()]
+ //public static void MyClassInitialize(TestContext testContext)
+ //{
+ //}
+ //
+ //Use ClassCleanup to run code after all tests in a class have run
+ //[ClassCleanup()]
+ //public static void MyClassCleanup()
+ //{
+ //}
+ //
+ //Use TestInitialize to run code before running each test
+ //[TestInitialize()]
+ //public void MyTestInitialize()
+ //{
+ //}
+ //
+ //Use TestCleanup to run code after each test has run
+ //[TestCleanup()]
+ //public void MyTestCleanup()
+ //{
+ //}
+ //
+ #endregion
+
+ /// <summary>
+ ///A test for IsAspx
+ ///</summary>
+ [TestMethod()]
+ public void IsAspxTest() {
+ Dictionary<string, bool> testCases = new Dictionary<string, bool>() {
+ { "test.abc", false },
+ { "test", false },
+ { "test.aspx", true },
+ { "TEST.AspX", true },
+ { "TEST.xyzabc.AspX", true},
+ };
+ foreach (var kvp in testCases) {
+ string virtualPath = kvp.Key;
+ bool expected = kvp.Value;
+ bool actual;
+ actual = WebPageSurrogateControlBuilder.IsAspx(virtualPath);
+ Assert.AreEqual(expected, actual);
+ }
+ }
+
+ /// <summary>
+ ///A test for IsAspq
+ ///</summary>
+ [TestMethod()]
+ public void IsAspqTest() {
+ Dictionary<string, bool> testCases = new Dictionary<string, bool>() {
+ { "test.abc", false },
+ { "test", false },
+ { "test.aspq", true },
+ { "TEST.AspQ", true },
+ { "TEST.xyzabc.AsPq", true},
+ };
+ foreach (var kvp in testCases) {
+ string virtualPath = kvp.Key;
+ bool expected = kvp.Value;
+ bool actual;
+ actual = WebPageSurrogateControlBuilder.IsAspq(virtualPath);
+ Assert.AreEqual(expected, actual);
+ }
+ }
+
+
+ /// <summary>
+ ///A test for IsAscq
+ ///</summary>
+ [TestMethod()]
+ public void IsAscqTest() {
+ Dictionary<string, bool> testCases = new Dictionary<string, bool>() {
+ { "test.abc", false },
+ { "test", false },
+ { "test.ascq", true },
+ { "TEST.AscQ", true },
+ { "TEST.xyzabc.AsCQ", true},
+ };
+ foreach (var kvp in testCases) {
+ string virtualPath = kvp.Key;
+ bool expected = kvp.Value;
+ bool actual;
+ actual = WebPageSurrogateControlBuilder.IsAscq(virtualPath);
+ Assert.AreEqual(expected, actual);
+ }
+ }
+
+ /// <summary>
+ ///A test for AddImports
+ ///</summary>
+ [TestMethod]
+ public void AddImportsTest() {
+ CodeCompileUnit ccu = new CodeCompileUnit();
+ ccu.Namespaces.Add(new CodeNamespace());
+ WebPageSurrogateControlBuilder.AddImports(ccu);
+ VerifyDefaultNameSpaces(ccu);
+
+ // Temporarily set the static field to something else
+ var originalNamespaces = new List<string>(WebPageSurrogateControlBuilder.Namespaces);
+ WebPageSurrogateControlBuilder.Namespaces.Clear();
+ WebPageSurrogateControlBuilder.Namespaces.Add("System.ABC");
+ ccu = new CodeCompileUnit();
+ ccu.Namespaces.Add(new CodeNamespace());
+ WebPageSurrogateControlBuilder.AddImports(ccu);
+ Assert.AreEqual(1, ccu.Namespaces[0].Imports.Count);
+ Assert.AreEqual("System.ABC", ccu.Namespaces[0].Imports[0].Namespace);
+
+ // Restore the static field
+ WebPageSurrogateControlBuilder.Namespaces.Clear();
+ foreach (var ns in originalNamespaces) {
+ WebPageSurrogateControlBuilder.Namespaces.Add(ns);
+ }
+ }
+
+ [TestMethod]
+ public void ProcessGeneratedCodeTest() {
+ VerifyProcessGeneratedCode(new WebPageSurrogateControlBuilder());
+ VerifyProcessGeneratedCode(new WebUserControlSurrogateControlBuilder());
+ }
+
+ public void VerifyProcessGeneratedCode(ControlBuilder builder) {
+ CodeCompileUnit ccu = new CodeCompileUnit();
+ ccu.Namespaces.Add(new CodeNamespace());
+ builder.ProcessGeneratedCode(ccu, null, null, null, null);
+ VerifyDefaultNameSpaces(ccu);
+ }
+
+ public void VerifyDefaultNameSpaces(CodeCompileUnit ccu) {
+ Assert.AreEqual(13, ccu.Namespaces[0].Imports.Count);
+ Assert.AreEqual(WebPageSurrogateControlBuilder.Namespaces.Count, ccu.Namespaces[0].Imports.Count);
+ }
+
+ /// <summary>
+ ///A test for GetLanguageAttributeFromText
+ ///</summary>
+ [TestMethod()]
+ public void GetLanguageAttributeFromTextTest() {
+ Assert.AreEqual("abcd", WebPageSurrogateControlBuilder.GetLanguageAttributeFromText("<%@ Page Language=\" abcd\" %> csharp c# cs "));
+ Assert.AreEqual("xxx", WebPageSurrogateControlBuilder.GetLanguageAttributeFromText("<%@ Page lanGUAGE='xxx' %> csharp c# cs "));
+ Assert.AreEqual("", WebPageSurrogateControlBuilder.GetLanguageAttributeFromText("<%@ Page languagE='' %> csharp c# cs "));
+ Assert.AreEqual(null, WebPageSurrogateControlBuilder.GetLanguageAttributeFromText("<%@ Page hello world %> csharp c# cs "));
+ }
+
+ /// <summary>
+ ///A test for GetLanguageFromText
+ ///</summary>
+ [TestMethod()]
+ public void GetLanguageFromTextPositiveTest() {
+ Assert.AreEqual(Language.VisualBasic, WebPageSurrogateControlBuilder.GetLanguageFromText("<%@ Page %> csharp c# cs "));
+ Assert.AreEqual(Language.VisualBasic, WebPageSurrogateControlBuilder.GetLanguageFromText("<%@ Page Language=' vB ' %> csharp c# cs "));
+ Assert.AreEqual(Language.VisualBasic, WebPageSurrogateControlBuilder.GetLanguageFromText("<%@ Page Language=\" vb \" %> csharp c# cs "));
+ Assert.AreEqual(Language.VisualBasic, WebPageSurrogateControlBuilder.GetLanguageFromText("<%@ Page Language=' Vb ' %> csharp c# cs "));
+ Assert.AreEqual(Language.VisualBasic, WebPageSurrogateControlBuilder.GetLanguageFromText("<%@ Page Language=' vB ' %> csharp c# cs "));
+ Assert.AreEqual(Language.VisualBasic, WebPageSurrogateControlBuilder.GetLanguageFromText("<%@ Page Language=' vBs ' %> csharp c# cs "));
+ Assert.AreEqual(Language.VisualBasic, WebPageSurrogateControlBuilder.GetLanguageFromText("<%@ Page Language=' vBscript ' %> csharp c# cs "));
+ Assert.AreEqual(Language.VisualBasic, WebPageSurrogateControlBuilder.GetLanguageFromText("<%@ Page Language=' vBscript ' %> csharp c# cs "));
+ Assert.AreEqual(Language.VisualBasic, WebPageSurrogateControlBuilder.GetLanguageFromText("<%@ Page Language=' vIsuaLBasic ' %> csharp c# cs "));
+
+ Assert.AreEqual(Language.CSharp, WebPageSurrogateControlBuilder.GetLanguageFromText("<%@ Page Language=' c# ' %> vb vbs visualbasic vbscript "));
+ Assert.AreEqual(Language.CSharp, WebPageSurrogateControlBuilder.GetLanguageFromText("<%@ Page Language=' cS ' %> vb vbs visualbasic vbscript "));
+ Assert.AreEqual(Language.CSharp, WebPageSurrogateControlBuilder.GetLanguageFromText("<%@ Page Language=' cShaRP ' %> vb vbs visualbasic vbscript "));
+ }
+
+ /// <summary>
+ ///A test for GetLanguageFromText
+ ///</summary>
+ [TestMethod()]
+ public void GetLanguageFromTextNegativeTest() {
+ ExceptionAssert.Throws<HttpException>(() =>
+ WebPageSurrogateControlBuilder.GetLanguageFromText("<%@ Page Language=' zxc' %> vb vbs visualbasic vbscript "),
+ String.Format(WebPageResources.WebPage_InvalidLanguage, "zxc"));
+ }
+
+ /// <summary>
+ ///A test for FixUpWriteSnippetStatement
+ ///</summary>
+ [TestMethod()]
+ public void FixUpWriteSnippetStatementTest() {
+ var code = " abc zyx";
+ var stmt = new CodeSnippetStatement(code);
+ WebPageSurrogateControlBuilder.FixUpWriteSnippetStatement(stmt);
+ Assert.AreEqual(code, stmt.Value);
+
+ code = " this.Write(\"hello\"); ";
+ stmt = new CodeSnippetStatement(code);
+ WebPageSurrogateControlBuilder.FixUpWriteSnippetStatement(stmt);
+ Assert.AreEqual(code, stmt.Value);
+
+ // @__w.Write case
+ code = " @__w.Write(\"hello\"); ";
+ stmt = new CodeSnippetStatement(code);
+ WebPageSurrogateControlBuilder.FixUpWriteSnippetStatement(stmt);
+ Assert.AreEqual(" WriteLiteral(\"hello\"); ", stmt.Value);
+
+ // __w.Write case
+ code = " __w.Write(\"hello\"); ";
+ stmt = new CodeSnippetStatement(code);
+ WebPageSurrogateControlBuilder.FixUpWriteSnippetStatement(stmt);
+ Assert.AreEqual(" WriteLiteral(\"hello\"); ", stmt.Value);
+ }
+
+ /// <summary>
+ ///A test for FixUpWriteCodeExpressionStatement
+ ///</summary>
+ [TestMethod()]
+ public void FixUpWriteCodeExpressionStatementTest() {
+ // Null test
+ WebPageSurrogateControlBuilder.FixUpWriteCodeExpressionStatement(null);
+
+ // Should fix up the statement
+ var invoke = new CodeMethodInvokeExpression(new CodeArgumentReferenceExpression("__w"), "Write");
+ CodeExpressionStatement exprStmt = new CodeExpressionStatement(invoke);
+ WebPageSurrogateControlBuilder.FixUpWriteCodeExpressionStatement(exprStmt);
+ Assert.AreEqual(invoke.Method.MethodName, "WriteLiteral");
+ Assert.AreEqual(invoke.Method.TargetObject.GetType(), typeof(CodeThisReferenceExpression));
+
+ // Should NOT fix up the statement
+ invoke = new CodeMethodInvokeExpression(new CodeArgumentReferenceExpression("xyz"), "Write");
+ exprStmt = new CodeExpressionStatement(invoke);
+ WebPageSurrogateControlBuilder.FixUpWriteCodeExpressionStatement(exprStmt);
+ Assert.AreEqual(invoke.Method.MethodName, "Write");
+ Assert.IsInstanceOfType(invoke.Method.TargetObject, typeof(CodeArgumentReferenceExpression));
+ }
+
+ /// <summary>
+ ///A test for FixUpWriteStatement
+ ///</summary>
+ [TestMethod()]
+ public void FixUpWriteStatementTest() {
+
+ var invoke = new CodeMethodInvokeExpression(new CodeArgumentReferenceExpression("__w"), "Write");
+ CodeExpressionStatement exprStmt = new CodeExpressionStatement(invoke);
+ WebPageSurrogateControlBuilder.FixUpWriteStatement(exprStmt);
+ Assert.AreEqual(invoke.Method.MethodName, "WriteLiteral");
+ Assert.IsInstanceOfType(invoke.Method.TargetObject, typeof(CodeThisReferenceExpression));
+
+ // @__w.Write case
+ var code = " @__w.Write(\"hello\"); ";
+ var stmt = new CodeSnippetStatement(code);
+ WebPageSurrogateControlBuilder.FixUpWriteStatement(stmt);
+ Assert.AreEqual(" WriteLiteral(\"hello\"); ", stmt.Value);
+
+ // __w.Write case
+ code = " __w.Write(\"hello\"); ";
+ stmt = new CodeSnippetStatement(code);
+ WebPageSurrogateControlBuilder.FixUpWriteStatement(stmt);
+ Assert.AreEqual(" WriteLiteral(\"hello\"); ", stmt.Value);
+ }
+
+ [TestMethod]
+ public void FixUpClassNull() {
+ WebPageSurrogateControlBuilder.FixUpClass(null, null);
+ }
+
+ [TestMethod]
+ public void OnCodeGenerationCompleteTest() {
+ Utils.RunInSeparateAppDomain(() => {
+ var vpath = "/WebSite1/index.aspq";
+ var contents = "<%@ Page Language=C# %>";
+ Utils.SetupVirtualPathInAppDomain(vpath, contents);
+ new WebPageSurrogateControlBuilderTest().FixUpClassTest(type => {
+ var ccu = new CodeCompileUnit();
+ ccu.Namespaces.Add(new CodeNamespace());
+
+ var builder = new MockControlBuilder() { VPath = vpath };
+ builder.ProcessGeneratedCode(ccu, null, type, null, null);
+ builder.CallOnCodeGenerationComplete();
+ });
+ });
+ }
+
+ [TestMethod]
+ public void OnCodeGenerationCompleteControlBuilderTest() {
+ Utils.RunInSeparateAppDomain(() => {
+ var vpath = "/WebSite1/index.ascq";
+ var contents = "<%@ Page Language=C# %>";
+ Utils.SetupVirtualPathInAppDomain(vpath, contents);
+ new WebPageSurrogateControlBuilderTest().FixUpClassTest(type => {
+ var ccu = new CodeCompileUnit();
+ ccu.Namespaces.Add(new CodeNamespace());
+
+ var builder = new MockUserControlBuilder() { VPath = vpath };
+ builder.ProcessGeneratedCode(ccu, null, type, null, null);
+ builder.CallOnCodeGenerationComplete();
+ });
+ });
+ }
+
+ public class MockControlBuilder : WebPageSurrogateControlBuilder {
+ public string VPath { get; set; }
+ public void CallOnCodeGenerationComplete() {
+ base.OnCodeGenerationComplete();
+ }
+
+ internal override string GetPageVirtualPath() {
+ return VPath;
+ }
+ }
+
+ public class MockUserControlBuilder : WebUserControlSurrogateControlBuilder {
+ public string VPath { get; set; }
+ public void CallOnCodeGenerationComplete() {
+ base.OnCodeGenerationComplete();
+ }
+
+ internal override string GetPageVirtualPath() {
+ return VPath;
+ }
+ }
+
+ [TestMethod]
+ public void FixUpClassVirtualPathTest() {
+ Utils.RunInSeparateAppDomain(() => {
+ var vpath = "/WebSite1/index.aspq";
+ var contents = "<%@ Page Language=C# %>";
+ Utils.SetupVirtualPathInAppDomain(vpath, contents);
+
+ new WebPageSurrogateControlBuilderTest().FixUpClassTest(type =>
+ WebPageSurrogateControlBuilder.FixUpClass(type, vpath));
+ });
+ }
+
+ /// <summary>
+ ///A test for FixUpClass
+ ///</summary>
+ [TestMethod()]
+ public void FixUpClassLanguageTest() {
+ FixUpClassTest(type =>
+ WebPageSurrogateControlBuilder.FixUpClass(type, Language.CSharp));
+ }
+
+ [TestMethod]
+ public void FixUpClassDefaultApplicationBaseTypeTest() {
+ Utils.RunInSeparateAppDomain(() => {
+ var baseTypeField = typeof(PageParser).GetField("s_defaultApplicationBaseType", BindingFlags.Static | BindingFlags.NonPublic);
+ baseTypeField.SetValue(null, typeof(WebPageHttpApplication));
+ var pageType = new WebPageSurrogateControlBuilderTest().FixUpClassTest(type =>
+ WebPageSurrogateControlBuilder.FixUpClass(type, Language.CSharp));
+ var properties = pageType.Members.OfType<CodeMemberProperty>().ToList();
+ var prop = properties[0];
+ Assert.AreEqual(typeof(WebPageHttpApplication).FullName, prop.Type.BaseType);
+ });
+ }
+
+ public CodeTypeDeclaration FixUpClassTest(Action<CodeTypeDeclaration> fixUpClassMethod) {
+ var type = new CodeTypeDeclaration();
+ // Add some dummy base types which should get removed
+ type.BaseTypes.Add("basetype1");
+ type.BaseTypes.Add("basetype2");
+ type.BaseTypes.Add("basetype3");
+
+ // Add a property which should get retained
+ var appInstance = new CodeMemberProperty() { Name = "ApplicationInstance" };
+ var returnStatement = new CodeMethodReturnStatement(new CodeCastExpression(typeof(HttpApplication), null));
+ appInstance.GetStatements.Add(returnStatement);
+ type.Members.Add(appInstance);
+
+ // Add a render method which should get retained but modified
+ var renderMethod = new CodeMemberMethod();
+ renderMethod.Name = "__Render__control1";
+
+ // Add a code snippet statement that should not be modified
+ var stmt1 = new CodeSnippetStatement("MyCode.DoSomething");
+ renderMethod.Statements.Add(stmt1);
+
+ // Add a code snippet statement that should be modified
+ var code2 = " @__w.Write(\"hello\"); ";
+ var stmt2 = new CodeSnippetStatement(code2);
+ renderMethod.Statements.Add(stmt2);
+
+ // Add a method invoke statement that should be modified
+ var invoke3 = new CodeMethodInvokeExpression(new CodeArgumentReferenceExpression("__w"), "Write");
+ CodeExpressionStatement stmt3 = new CodeExpressionStatement(invoke3);
+ WebPageSurrogateControlBuilder.FixUpWriteStatement(stmt3);
+ renderMethod.Statements.Add(stmt3);
+
+ type.Members.Add(renderMethod);
+
+ // Snippets should get retained
+ var snippet1 = "public void Test1() { }";
+ var snippet2 = "public void Test2() { }";
+ type.Members.Add(new CodeSnippetTypeMember(snippet1));
+ type.Members.Add(new CodeSnippetTypeMember(snippet2));
+
+ // Add dummy members which should get removed
+ type.Members.Add(new CodeMemberProperty() { Name = "DummyProperty1" });
+ type.Members.Add(new CodeMemberProperty() { Name = "DummyProperty2" });
+ type.Members.Add(new CodeMemberMethod() { Name = "DummyMethod1" });
+ type.Members.Add(new CodeMemberMethod() { Name = "DummyMethod2" });
+
+ // Run the method we are testing
+ fixUpClassMethod(type);
+
+ // Basic verification
+ Assert.AreEqual(1, type.BaseTypes.Count);
+ Assert.AreEqual(4, type.Members.Count);
+
+ // Verify properties
+ var properties = type.Members.OfType<CodeMemberProperty>().ToList();
+ Assert.AreEqual(1, properties.Count);
+ Assert.AreEqual("ApplicationInstance", properties[0].Name);
+
+ // Verify snippets
+ var snippets = type.Members.OfType<CodeSnippetTypeMember>().ToList();
+ Assert.AreEqual(2, snippets.Count);
+ Assert.IsNotNull(snippets.Find(s => s.Text == snippet1));
+ Assert.IsNotNull(snippets.Find(s => s.Text == snippet2));
+
+ // Verify methods
+ var methods = type.Members.OfType<CodeMemberMethod>().ToList();
+ Assert.AreEqual(1, methods.Count);
+ Assert.AreEqual("Execute", methods[0].Name);
+
+ // Verify statements in the method
+ var statements = methods[0].Statements;
+ Assert.AreEqual(4, statements.Count); // The fourth statement is a snippet generated for use as helper.
+
+ // First statement should be unchanged
+ Assert.AreEqual(stmt1, statements[0]);
+
+ // Second statement should be fixed to use WriteLiteral
+ Assert.IsInstanceOfType(statements[1], typeof(CodeSnippetStatement));
+ Assert.AreEqual(" WriteLiteral(\"hello\"); ", ((CodeSnippetStatement)statements[1]).Value);
+
+ // Third statement should be fixed to use WriteLiteral
+ Assert.IsInstanceOfType(statements[2], typeof(CodeExpressionStatement));
+ var invokeExpr = ((CodeExpressionStatement)statements[2]).Expression;
+ Assert.IsInstanceOfType(invokeExpr, typeof(CodeMethodInvokeExpression));
+ var invoke = invokeExpr as CodeMethodInvokeExpression;
+ Assert.AreEqual(invoke.Method.MethodName, "WriteLiteral");
+ Assert.IsInstanceOfType(invoke.Method.TargetObject, typeof(CodeThisReferenceExpression));
+
+ // Fourth statement should be a generated code snippet
+ Assert.IsInstanceOfType(statements[3], typeof(CodeSnippetStatement));
+ return type;
+ }
+
+ [TestMethod]
+ public void FixUpAspxClassUserControlTest() {
+ CodeTypeDeclaration type;
+ CodeCastExpression cast;
+ GetAspxClass(out type, out cast, typeof(WebUserControlSurrogate));
+ WebPageSurrogateControlBuilder.FixUpAspxClass(type, "/test/test.ascx");
+ Assert.AreEqual(typeof(UserControl).FullName, cast.TargetType.BaseType);
+ }
+
+ [TestMethod]
+ public void FixUpClassUserControlTest() {
+ CodeTypeDeclaration type;
+ CodeCastExpression cast;
+ GetAspxClass(out type, out cast, typeof(WebUserControlSurrogate));
+ WebPageSurrogateControlBuilder.FixUpClass(type, "/test/test.ascx");
+ Assert.AreEqual(typeof(UserControl).FullName, cast.TargetType.BaseType);
+ }
+
+ private static void GetAspxClass(out CodeTypeDeclaration type, out CodeCastExpression cast, Type surrogateType) {
+ type = new CodeTypeDeclaration();
+ type.BaseTypes.Add(new CodeTypeReference(surrogateType));
+ var ctor = new CodeConstructor();
+ cast = new CodeCastExpression();
+ cast.TargetType = new CodeTypeReference(surrogateType);
+ var prop = new CodePropertyReferenceExpression();
+ prop.TargetObject = cast;
+ var assign = new CodeAssignStatement();
+ assign.Left = prop;
+ ctor.Statements.Add(assign);
+ type.Members.Add(ctor);
+ }
+
+ [TestMethod]
+ public void ProcessScriptBlocksCSTest() {
+ var pageType = new CodeTypeDeclaration("MyControl");
+ var snippet = new CodeSnippetTypeMember("public int foo;");
+ pageType.Members.Add(snippet);
+ var renderMethod = new CodeMemberMethod();
+ WebPageSurrogateControlBuilder.ProcessScriptBlocks(pageType, renderMethod, Language.CSharp);
+ var snippets = renderMethod.Statements.OfType<CodeSnippetStatement>().ToList();
+ Assert.AreEqual(1, snippets.Count);
+ var snip = snippets[0];
+ Assert.IsTrue(snip.Value.Contains("public static class MyControlExtensions"));
+ Assert.IsTrue(snip.Value.Contains("public static HelperResult MyControl(this System.Web.Mvc.HtmlHelper htmlHelper, int foo = default(int))"));
+ Assert.IsTrue(snip.Value.Contains("uc.foo = foo;"));
+ }
+
+ [TestMethod]
+ public void ProcessScriptBlocksVBTest() {
+ var pageType = new CodeTypeDeclaration("MyControl");
+ var snippet = new CodeSnippetTypeMember("public foo as int");
+ pageType.Members.Add(snippet);
+ var renderMethod = new CodeMemberMethod();
+ WebPageSurrogateControlBuilder.ProcessScriptBlocks(pageType, renderMethod, Language.VisualBasic);
+ var snippets = renderMethod.Statements.OfType<CodeSnippetStatement>().ToList();
+ Assert.AreEqual(1, snippets.Count);
+ var snip = snippets[0];
+ Assert.IsTrue(snip.Value.Contains("Public Module MyControlExtensions"));
+ Assert.IsTrue(snip.Value.Contains("Public Function MyControl(htmlHelper As System.Web.Mvc.HtmlHelper, optional foo as int = Nothing) As HelperResult"));
+ Assert.IsTrue(snip.Value.Contains("uc.foo = foo"));
+ }
+
+ [TestMethod]
+ public void HasAspCodeTest() {
+ Assert.IsTrue(new WebPageSurrogateControlBuilder().HasAspCode);
+ Assert.IsTrue(new WebUserControlSurrogateControlBuilder().HasAspCode);
+ }
+ }
+
+}
+
diff --git a/test/System.Web.WebPages.Test/WebPage/WebPageTest.cs b/test/System.Web.WebPages.Test/WebPage/WebPageTest.cs
new file mode 100644
index 00000000..1ae87304
--- /dev/null
+++ b/test/System.Web.WebPages.Test/WebPage/WebPageTest.cs
@@ -0,0 +1,309 @@
+using System.Collections;
+using System.Collections.Generic;
+using System.Collections.Specialized;
+using System.Linq;
+using System.Web.Caching;
+using Moq;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.WebPages.Test
+{
+ public class WebPageTest
+ {
+ private const string XmlHttpRequestKey = "X-Requested-With";
+ private const string XmlHttpRequestValue = "XMLHttpRequest";
+
+ [Fact]
+ public void CreatePageFromVirtualPathAssignsVirtualPathFactory()
+ {
+ // Arrange
+ var path = "~/index.cshtml";
+ var page = Utils.CreatePage(null, path);
+ var factory = new HashVirtualPathFactory(page);
+
+ // Act
+ var result = WebPage.CreateInstanceFromVirtualPath(path, factory);
+
+ // Assert
+ Assert.Equal(page, result);
+ Assert.Equal(page.VirtualPathFactory, factory);
+ Assert.Equal(page.VirtualPath, path);
+ }
+
+ [Fact]
+ public void NormalizeLayoutPagePathTest()
+ {
+ var layoutPage = "Layout.cshtml";
+ var layoutPath1 = "~/MyApp/Layout.cshtml";
+ var page = new Mock<WebPage>() { CallBase = true }.Object;
+ page.VirtualPath = "~/MyApp/index.cshtml";
+
+ var mockBuildManager = new Mock<IVirtualPathFactory>();
+ mockBuildManager.Setup(c => c.Exists(It.IsAny<string>())).Returns<string>(p => p.Equals(layoutPath1, StringComparison.OrdinalIgnoreCase));
+ page.VirtualPathFactory = mockBuildManager.Object;
+
+ Assert.Equal(layoutPath1, page.NormalizeLayoutPagePath(layoutPage));
+
+ mockBuildManager.Setup(c => c.Exists(It.IsAny<string>())).Returns<string>(_ => false);
+
+ Assert.Throws<HttpException>(() => page.NormalizeLayoutPagePath(layoutPage),
+ @"The layout page ""Layout.cshtml"" could not be found at the following path: ""~/MyApp/Layout.cshtml"".");
+ }
+
+ [Fact]
+ public void UrlDataBasicTests()
+ {
+ Mock<HttpContextBase> mockContext = new Mock<HttpContextBase>();
+ mockContext.Setup(context => context.Items).Returns(new Hashtable());
+ mockContext.Object.Items[typeof(WebPageMatch)] = new WebPageMatch("~/a.cshtml", "one/2/3.0/4.0005");
+ WebPage page = new Mock<WebPage>() { CallBase = true }.Object;
+ page.Context = mockContext.Object;
+
+ Assert.Equal("one", page.UrlData[0]);
+ Assert.Equal(2, page.UrlData[1].AsInt());
+ Assert.Equal(3.0f, page.UrlData[2].AsFloat());
+ Assert.Equal(4.0005m, page.UrlData[3].AsDecimal());
+ }
+
+ [Fact]
+ public void UrlDataEmptyTests()
+ {
+ Mock<HttpContextBase> mockContext = new Mock<HttpContextBase>();
+ mockContext.Setup(context => context.Items).Returns(new Hashtable());
+ mockContext.Object.Items[typeof(WebPageMatch)] = new WebPageMatch("~/a.cshtml", "one///two/");
+ WebPage page = new Mock<WebPage>() { CallBase = true }.Object;
+ page.Context = mockContext.Object;
+
+ Assert.Equal("one", page.UrlData[0]);
+ Assert.True(page.UrlData[1].IsEmpty());
+ Assert.True(page.UrlData[2].IsEmpty());
+ Assert.Equal("two", page.UrlData[3]);
+ Assert.True(page.UrlData[4].IsEmpty());
+ }
+
+ [Fact]
+ public void UrlDataReadOnlyTest()
+ {
+ Mock<HttpContextBase> mockContext = new Mock<HttpContextBase>();
+ mockContext.Setup(context => context.Items).Returns(new Hashtable());
+ mockContext.Object.Items[typeof(WebPageMatch)] = new WebPageMatch("~/a.cshtml", "one/2/3.0/4.0005");
+ WebPage page = new Mock<WebPage>() { CallBase = true }.Object;
+ page.Context = mockContext.Object;
+
+ Assert.Throws<NotSupportedException>(() => { page.UrlData.Add("bogus"); }, "The UrlData collection is read-only.");
+ Assert.Throws<NotSupportedException>(() => { page.UrlData.Insert(0, "bogus"); }, "The UrlData collection is read-only.");
+ Assert.Throws<NotSupportedException>(() => { page.UrlData.Remove("one"); }, "The UrlData collection is read-only.");
+ }
+
+ [Fact]
+ public void UrlDataOutOfBoundsTest()
+ {
+ Mock<HttpContextBase> mockContext = new Mock<HttpContextBase>();
+ mockContext.Setup(context => context.Items).Returns(new Hashtable());
+ mockContext.Object.Items[typeof(WebPageMatch)] = new WebPageMatch("~/a.cshtml", "");
+ WebPage page = new Mock<WebPage>() { CallBase = true }.Object;
+ page.Context = mockContext.Object;
+
+ Assert.Equal(String.Empty, page.UrlData[0]);
+ Assert.Equal(String.Empty, page.UrlData[1]);
+ }
+
+ [Fact]
+ public void NullModelTest()
+ {
+ var page = CreateMockPageWithPostContext().Object;
+ page.PageContext.Model = null;
+ Assert.Null(page.Model);
+ }
+
+ internal class ModelTestClass
+ {
+ public string Prop1 { get; set; }
+
+ public string GetProp1()
+ {
+ return Prop1;
+ }
+
+ public override string ToString()
+ {
+ return Prop1;
+ }
+ }
+
+ [Fact]
+ public void ModelTest()
+ {
+ var v = "value1";
+ var page = CreateMockPageWithPostContext().Object;
+ var model = new ModelTestClass() { Prop1 = v };
+ page.PageContext.Model = model;
+ Assert.NotNull(page.Model);
+ Assert.Equal(v, page.Model.Prop1);
+ Assert.Equal(v, page.Model.GetProp1());
+ Assert.Equal(v, page.Model.ToString());
+ Assert.Equal(model, (ModelTestClass)page.Model);
+ // No such property
+ Assert.Null(page.Model.Prop2);
+ // No such method
+ Assert.Throws<MissingMethodException>(() => page.Model.DoSomething());
+ }
+
+ [Fact]
+ public void AnonymousObjectModelTest()
+ {
+ var v = "value1";
+ var page = CreateMockPageWithPostContext().Object;
+ var model = new { Prop1 = v };
+ page.PageContext.Model = model;
+ Assert.NotNull(page.Model);
+ Assert.Equal(v, page.Model.Prop1);
+ // No such property
+ Assert.Null(page.Model.Prop2);
+ // No such method
+ Assert.Throws<MissingMethodException>(() => page.Model.DoSomething());
+ }
+
+ [Fact]
+ public void SessionPropertyTest()
+ {
+ var page = CreateMockPageWithPostContext().Object;
+ Assert.Equal(0, page.Session.Count);
+ }
+
+ [Fact]
+ public void AppStatePropertyTest()
+ {
+ var page = CreateMockPageWithPostContext().Object;
+ Assert.Equal(0, page.AppState.Count);
+ }
+
+ [Fact]
+ public void ExecutePageHierarchyTest()
+ {
+ var page = new Mock<WebPage>();
+ page.Object.TopLevelPage = true;
+
+ var executors = new List<IWebPageRequestExecutor>();
+
+ // First executor returns false
+ var executor1 = new Mock<IWebPageRequestExecutor>();
+ executor1.Setup(exec => exec.Execute(It.IsAny<WebPage>())).Returns(false);
+ executors.Add(executor1.Object);
+
+ // Second executor returns true
+ var executor2 = new Mock<IWebPageRequestExecutor>();
+ executor2.Setup(exec => exec.Execute(It.IsAny<WebPage>())).Returns(true);
+ executors.Add(executor2.Object);
+
+ // Third executor should never get called, since we stop after the first true
+ var executor3 = new Mock<IWebPageRequestExecutor>();
+ executor3.Setup(exec => exec.Execute(It.IsAny<WebPage>())).Returns(false);
+ executors.Add(executor3.Object);
+
+ page.Object.ExecutePageHierarchy(executors);
+
+ // Make sure the first two got called but not the third
+ executor1.Verify(exec => exec.Execute(It.IsAny<WebPage>()));
+ executor2.Verify(exec => exec.Execute(It.IsAny<WebPage>()));
+ executor3.Verify(exec => exec.Execute(It.IsAny<WebPage>()), Times.Never());
+ }
+
+ [Fact]
+ public void IsPostReturnsTrueWhenMethodIsPost()
+ {
+ // Arrange
+ var page = CreateMockPageWithPostContext();
+
+ // Act and Assert
+ Assert.True(page.Object.IsPost);
+ }
+
+ [Fact]
+ public void IsPostReturnsFalseWhenMethodIsNotPost()
+ {
+ // Arrange
+ var methods = new[] { "GET", "DELETE", "PUT", "RANDOM" };
+
+ // Act and Assert
+ Assert.True(methods.All(method => !CreateMockPageWithContext(method).Object.IsPost));
+ }
+
+ [Fact]
+ public void IsAjaxReturnsTrueWhenRequestContainsAjaxHeader()
+ {
+ // Arrange
+ var headers = new NameValueCollection();
+ headers.Add("X-Requested-With", "XMLHttpRequest");
+ var context = CreateContext("GET", new NameValueCollection(), headers);
+ var page = CreatePage(context);
+
+ // Act and Assert
+ Assert.True(page.Object.IsAjax);
+ }
+
+ [Fact]
+ public void IsAjaxReturnsTrueWhenRequestBodyContainsAjaxHeader()
+ {
+ // Arrange
+ var headers = new NameValueCollection();
+ headers.Add("X-Requested-With", "XMLHttpRequest");
+ var context = CreateContext("POST", headers, headers);
+ var page = CreatePage(context);
+
+ // Act and Assert
+ Assert.True(page.Object.IsAjax);
+ }
+
+ [Fact]
+ public void IsAjaxReturnsFalseWhenRequestDoesNotContainAjaxHeaders()
+ {
+ // Arrange
+ var page = CreateMockPageWithPostContext();
+
+ // Act and Assert
+ Assert.True(!page.Object.IsAjax);
+ }
+
+ private static Mock<WebPage> CreatePage(Mock<HttpContextBase> context)
+ {
+ var page = new Mock<WebPage>() { CallBase = true };
+ var pageContext = new WebPageContext();
+ page.Object.Context = context.Object;
+ page.Object.PageContext = pageContext;
+ return page;
+ }
+
+ private static Mock<WebPage> CreateMockPageWithPostContext()
+ {
+ return CreateMockPageWithContext("POST");
+ }
+
+ private static Mock<WebPage> CreateMockPageWithContext(string httpMethod)
+ {
+ var context = CreateContext(httpMethod, new NameValueCollection());
+ var page = CreatePage(context);
+ return page;
+ }
+
+ private static Mock<HttpContextBase> CreateContext(string httpMethod, NameValueCollection queryString, NameValueCollection httpHeaders = null)
+ {
+ var request = new Mock<HttpRequestBase>();
+ request.Setup(r => r.HttpMethod).Returns(httpMethod);
+ request.Setup(r => r.QueryString).Returns(queryString);
+ request.Setup(r => r.Form).Returns(new NameValueCollection());
+ request.Setup(r => r.Files).Returns(new Mock<HttpFileCollectionBase>().Object);
+ request.Setup(c => c.Headers).Returns(httpHeaders);
+ var context = new Mock<HttpContextBase>();
+ context.Setup(c => c.Response).Returns(new Mock<HttpResponseBase>().Object);
+ context.Setup(c => c.Request).Returns(request.Object);
+ context.Setup(c => c.Items).Returns(new Hashtable());
+ context.Setup(c => c.Session).Returns(new Mock<HttpSessionStateBase>().Object);
+ context.Setup(c => c.Application).Returns(new Mock<HttpApplicationStateBase>().Object);
+ context.Setup(c => c.Cache).Returns(new Cache());
+ context.Setup(c => c.Server).Returns(new Mock<HttpServerUtilityBase>().Object);
+ return context;
+ }
+ }
+}
diff --git a/test/System.Web.WebPages.Test/packages.config b/test/System.Web.WebPages.Test/packages.config
new file mode 100644
index 00000000..d5aa6401
--- /dev/null
+++ b/test/System.Web.WebPages.Test/packages.config
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<packages>
+ <package id="Moq" version="4.0.10827" />
+ <package id="xunit" version="1.9.0.1566" />
+ <package id="xunit.extensions" version="1.9.0.1566" />
+</packages> \ No newline at end of file
diff --git a/test/WebMatrix.Data.Test/App.config b/test/WebMatrix.Data.Test/App.config
new file mode 100644
index 00000000..31c34cb6
--- /dev/null
+++ b/test/WebMatrix.Data.Test/App.config
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<configuration>
+ <system.data>
+ <DbProviderFactories>
+ <remove invariant="System.Data.SqlServerCe.4.0"></remove>
+ <add name="Microsoft SQL Server Compact Data Provider" invariant="System.Data.SqlServerCe.4.0" description=".NET Framework Data Provider for Microsoft SQL Server Compact" type="System.Data.SqlServerCe.SqlCeProviderFactory, System.Data.SqlServerCe, Version=4.0.0.0, Culture=neutral, PublicKeyToken=89845dcd8080cc91"/>
+ </DbProviderFactories>
+ </system.data>
+</configuration> \ No newline at end of file
diff --git a/test/WebMatrix.Data.Test/ConfigurationManagerWrapperTest.cs b/test/WebMatrix.Data.Test/ConfigurationManagerWrapperTest.cs
new file mode 100644
index 00000000..dbf05a61
--- /dev/null
+++ b/test/WebMatrix.Data.Test/ConfigurationManagerWrapperTest.cs
@@ -0,0 +1,90 @@
+using System;
+using System.Collections.Generic;
+using Moq;
+using WebMatrix.Data.Test.Mocks;
+using Xunit;
+
+namespace WebMatrix.Data.Test
+{
+ public class ConfigurationManagerWrapperTest
+ {
+ [Fact]
+ public void GetConnectionGetsConnectionFromConfig()
+ {
+ // Arrange
+ var configManager = new ConfigurationManagerWrapper(new Dictionary<string, IDbFileHandler>(), "DataDirectory");
+ Func<string, bool> fileExists = path => false;
+ Func<string, IConnectionConfiguration> getFromConfig = name => new MockConnectionConfiguration("connection string");
+
+ // Act
+ IConnectionConfiguration configuration = configManager.GetConnection("foo", getFromConfig, fileExists);
+
+ // Assert
+ Assert.NotNull(configuration);
+ Assert.Equal("connection string", configuration.ConnectionString);
+ }
+
+ [Fact]
+ public void GetConnectionGetsConnectionFromDataDirectoryIfFileWithSupportedExtensionExists()
+ {
+ // Arrange
+ var mockHandler = new Mock<MockDbFileHandler>();
+ mockHandler.Setup(m => m.GetConnectionConfiguration(@"DataDirectory\Bar.foo")).Returns(new MockConnectionConfiguration("some file based connection"));
+ var handlers = new Dictionary<string, IDbFileHandler>
+ {
+ { ".foo", mockHandler.Object }
+ };
+ var configManager = new ConfigurationManagerWrapper(handlers, "DataDirectory");
+ Func<string, bool> fileExists = path => path.Equals(@"DataDirectory\Bar.foo");
+ Func<string, IConnectionConfiguration> getFromConfig = name => null;
+
+ // Act
+ IConnectionConfiguration configuration = configManager.GetConnection("Bar", getFromConfig, fileExists);
+
+ // Assert
+ Assert.NotNull(configuration);
+ Assert.Equal("some file based connection", configuration.ConnectionString);
+ }
+
+ [Fact]
+ public void GetConnectionSdfAndMdfFile_MdfFileWins()
+ {
+ // Arrange
+ var mockSdfHandler = new Mock<MockDbFileHandler>();
+ mockSdfHandler.Setup(m => m.GetConnectionConfiguration(@"DataDirectory\Bar.sdf")).Returns(new MockConnectionConfiguration("sdf connection"));
+ var mockMdfHandler = new Mock<MockDbFileHandler>();
+ mockMdfHandler.Setup(m => m.GetConnectionConfiguration(@"DataDirectory\Bar.mdf")).Returns(new MockConnectionConfiguration("mdf connection"));
+ var handlers = new Dictionary<string, IDbFileHandler>
+ {
+ { ".sdf", mockSdfHandler.Object },
+ { ".mdf", mockMdfHandler.Object },
+ };
+ var configManager = new ConfigurationManagerWrapper(handlers, "DataDirectory");
+ Func<string, bool> fileExists = path => path.Equals(@"DataDirectory\Bar.mdf") ||
+ path.Equals(@"DataDirectory\Bar.sdf");
+ Func<string, IConnectionConfiguration> getFromConfig = name => null;
+
+ // Act
+ IConnectionConfiguration configuration = configManager.GetConnection("Bar", getFromConfig, fileExists);
+
+ // Assert
+ Assert.NotNull(configuration);
+ Assert.Equal("mdf connection", configuration.ConnectionString);
+ }
+
+ [Fact]
+ public void GetConnectionReturnsNullIfNoConnectionFound()
+ {
+ // Act
+ var configManager = new ConfigurationManagerWrapper(new Dictionary<string, IDbFileHandler>(), "DataDirectory");
+ Func<string, bool> fileExists = path => false;
+ Func<string, IConnectionConfiguration> getFromConfig = name => null;
+
+ // Act
+ IConnectionConfiguration configuration = configManager.GetConnection("test", getFromConfig, fileExists);
+
+ // Assert
+ Assert.Null(configuration);
+ }
+ }
+}
diff --git a/test/WebMatrix.Data.Test/DatabaseTest.cs b/test/WebMatrix.Data.Test/DatabaseTest.cs
new file mode 100644
index 00000000..d0f7fbfc
--- /dev/null
+++ b/test/WebMatrix.Data.Test/DatabaseTest.cs
@@ -0,0 +1,89 @@
+using System;
+using System.Collections.Generic;
+using System.Data.Common;
+using System.Web.WebPages.TestUtils;
+using Moq;
+using WebMatrix.Data.Test.Mocks;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace WebMatrix.Data.Test
+{
+ public class DatabaseTest
+ {
+ [Fact]
+ public void OpenWithNullConnectionStringNameThrowsException()
+ {
+ Assert.ThrowsArgumentNullOrEmptyString(() => Database.Open(null), "name");
+ }
+
+ [Fact]
+ public void OpenConnectionStringWithNullConnectionStringThrowsException()
+ {
+ Assert.ThrowsArgumentNullOrEmptyString(() => Database.OpenConnectionString(null), "connectionString");
+ }
+
+ [Fact]
+ public void OpenConnectionStringWithEmptyConnectionStringThrowsException()
+ {
+ Assert.ThrowsArgumentNullOrEmptyString(() => Database.OpenConnectionString(String.Empty), "connectionString");
+ }
+
+ [Fact]
+ public void OpenNamedConnectionUsesConnectionStringFromConfigurationIfExists()
+ {
+ // Arrange
+ MockConfigurationManager mockConfigurationManager = new MockConfigurationManager();
+ Mock<DbConnection> mockConnection = new Mock<DbConnection>();
+ mockConnection.Setup(m => m.ConnectionString).Returns("connection string");
+ Mock<MockDbProviderFactory> mockProviderFactory = new Mock<MockDbProviderFactory>();
+ mockProviderFactory.Setup(m => m.CreateConnection("connection string")).Returns(mockConnection.Object);
+ mockConfigurationManager.AddConnection("foo", new ConnectionConfiguration(mockProviderFactory.Object, "connection string"));
+
+ // Act
+ Database db = Database.OpenNamedConnection("foo", mockConfigurationManager);
+
+ // Assert
+ Assert.Equal("connection string", db.Connection.ConnectionString);
+ }
+
+ [Fact]
+ public void OpenNamedConnectionThrowsIfNoConnectionFound()
+ {
+ // Arrange
+ IConfigurationManager mockConfigurationManager = new MockConfigurationManager();
+
+ // Act & Assert
+ Assert.Throws<InvalidOperationException>(() => Database.OpenNamedConnection("foo", mockConfigurationManager), "Connection string \"foo\" was not found.");
+ }
+
+ [Fact]
+ public void GetConnectionConfigurationGetConnectionForFileHandlersIfRegistered()
+ {
+ // Arrange
+ var mockHandler = new Mock<MockDbFileHandler>();
+ mockHandler.Setup(m => m.GetConnectionConfiguration("filename.foo")).Returns(new MockConnectionConfiguration("some file based connection"));
+ var handlers = new Dictionary<string, IDbFileHandler>
+ {
+ { ".foo", mockHandler.Object }
+ };
+
+ // Act
+ IConnectionConfiguration configuration = Database.GetConnectionConfiguration("filename.foo", handlers);
+
+ // Assert
+ Assert.NotNull(configuration);
+ Assert.Equal("some file based connection", configuration.ConnectionString);
+ }
+
+ [Fact]
+ public void GetConnectionThrowsIfNoHandlersRegisteredForExtension()
+ {
+ // Arrange
+ var handlers = new Dictionary<string, IDbFileHandler>();
+
+ // Act
+ Assert.Throws<InvalidOperationException>(() => Database.GetConnectionConfiguration("filename.foo", handlers), "Unable to determine the provider for the database file \"filename.foo\".");
+ }
+ }
+}
diff --git a/test/WebMatrix.Data.Test/DynamicRecordTest.cs b/test/WebMatrix.Data.Test/DynamicRecordTest.cs
new file mode 100644
index 00000000..2e30e5fb
--- /dev/null
+++ b/test/WebMatrix.Data.Test/DynamicRecordTest.cs
@@ -0,0 +1,150 @@
+using System;
+using System.ComponentModel;
+using System.Data;
+using System.Linq;
+using Moq;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace WebMatrix.Data.Test
+{
+ public class DynamicRecordTest
+ {
+ [Fact]
+ public void GetFieldValueByNameAccessesUnderlyingRecordForValue()
+ {
+ // Arrange
+ var mockRecord = new Mock<IDataRecord>();
+ mockRecord.SetupGet(m => m["A"]).Returns(1);
+ mockRecord.SetupGet(m => m["B"]).Returns(2);
+
+ dynamic record = new DynamicRecord(new[] { "A", "B" }, mockRecord.Object);
+
+ // Assert
+ Assert.Equal(1, record.A);
+ Assert.Equal(2, record.B);
+ }
+
+ [Fact]
+ public void GetFieldValueByIndexAccessesUnderlyingRecordForValue()
+ {
+ // Arrange
+ var mockRecord = new Mock<IDataRecord>();
+ mockRecord.SetupGet(m => m[0]).Returns(1);
+ mockRecord.SetupGet(m => m[1]).Returns(2);
+
+ dynamic record = new DynamicRecord(new[] { "A", "B" }, mockRecord.Object);
+
+ // Assert
+ Assert.Equal(1, record[0]);
+ Assert.Equal(2, record[1]);
+ }
+
+ [Fact]
+ public void GetFieldValueByNameReturnsNullIfValueIsDbNull()
+ {
+ // Arrange
+ var mockRecord = new Mock<IDataRecord>();
+ mockRecord.SetupGet(m => m["A"]).Returns(DBNull.Value);
+
+ dynamic record = new DynamicRecord(new[] { "A" }, mockRecord.Object);
+
+ // Assert
+ Assert.Null(record.A);
+ }
+
+ [Fact]
+ public void GetFieldValueByIndexReturnsNullIfValueIsDbNull()
+ {
+ // Arrange
+ var mockRecord = new Mock<IDataRecord>();
+ mockRecord.SetupGet(m => m[0]).Returns(DBNull.Value);
+
+ dynamic record = new DynamicRecord(new[] { "A" }, mockRecord.Object);
+
+ // Assert
+ Assert.Null(record[0]);
+ }
+
+ [Fact]
+ public void GetInvalidFieldValueThrows()
+ {
+ // Arrange
+ var mockRecord = new Mock<IDataRecord>();
+ dynamic record = new DynamicRecord(Enumerable.Empty<string>(), mockRecord.Object);
+
+ // Assert
+ Assert.Throws<InvalidOperationException>(() => { var value = record.C; }, "Invalid column name \"C\".");
+ }
+
+ [Fact]
+ public void VerfiyCustomTypeDescriptorMethods()
+ {
+ // Arrange
+ var mockRecord = new Mock<IDataRecord>();
+ mockRecord.SetupGet(m => m["A"]).Returns(1);
+ mockRecord.SetupGet(m => m["B"]).Returns(2);
+
+ // Act
+ ICustomTypeDescriptor record = new DynamicRecord(new[] { "A", "B" }, mockRecord.Object);
+
+ // Assert
+ Assert.Equal(AttributeCollection.Empty, record.GetAttributes());
+ Assert.Null(record.GetClassName());
+ Assert.Null(record.GetConverter());
+ Assert.Null(record.GetDefaultEvent());
+ Assert.Null(record.GetComponentName());
+ Assert.Null(record.GetDefaultProperty());
+ Assert.Null(record.GetEditor(null));
+ Assert.Equal(EventDescriptorCollection.Empty, record.GetEvents());
+ Assert.Equal(EventDescriptorCollection.Empty, record.GetEvents(null));
+ Assert.Same(record, record.GetPropertyOwner(null));
+ Assert.Equal(2, record.GetProperties().Count);
+ Assert.Equal(2, record.GetProperties(null).Count);
+ Assert.NotNull(record.GetProperties()["A"]);
+ Assert.NotNull(record.GetProperties()["B"]);
+ }
+
+ [Fact]
+ public void VerifyPropertyDescriptorProperties()
+ {
+ // Arrange
+ var mockRecord = new Mock<IDataRecord>();
+ mockRecord.SetupGet(m => m["A"]).Returns(1);
+ mockRecord.Setup(m => m.GetOrdinal("A")).Returns(0);
+ mockRecord.Setup(m => m.GetFieldType(0)).Returns(typeof(string));
+
+ // Act
+ ICustomTypeDescriptor record = new DynamicRecord(new[] { "A" }, mockRecord.Object);
+
+ // Assert
+ var aDescriptor = record.GetProperties().Find("A", ignoreCase: false);
+
+ Assert.NotNull(aDescriptor);
+ Assert.Null(aDescriptor.GetValue(null));
+ Assert.Equal(1, aDescriptor.GetValue(record));
+ Assert.True(aDescriptor.IsReadOnly);
+ Assert.Equal(typeof(string), aDescriptor.PropertyType);
+ Assert.Equal(typeof(DynamicRecord), aDescriptor.ComponentType);
+ Assert.False(aDescriptor.ShouldSerializeValue(record));
+ Assert.False(aDescriptor.CanResetValue(record));
+ }
+
+ [Fact]
+ public void SetAndResetValueOnPropertyDescriptorThrows()
+ {
+ // Arrange
+ var mockRecord = new Mock<IDataRecord>();
+ mockRecord.SetupGet(m => m["A"]).Returns(1);
+
+ // Act
+ ICustomTypeDescriptor record = new DynamicRecord(new[] { "A" }, mockRecord.Object);
+
+ // Assert
+ var aDescriptor = record.GetProperties().Find("A", ignoreCase: false);
+ Assert.NotNull(aDescriptor);
+ Assert.Throws<InvalidOperationException>(() => aDescriptor.SetValue(record, 1), "Unable to modify the value of column \"A\" because the record is read only.");
+ Assert.Throws<InvalidOperationException>(() => aDescriptor.ResetValue(record), "Unable to modify the value of column \"A\" because the record is read only.");
+ }
+ }
+}
diff --git a/test/WebMatrix.Data.Test/FileHandlerTest.cs b/test/WebMatrix.Data.Test/FileHandlerTest.cs
new file mode 100644
index 00000000..51d833ba
--- /dev/null
+++ b/test/WebMatrix.Data.Test/FileHandlerTest.cs
@@ -0,0 +1,52 @@
+using Xunit;
+
+namespace WebMatrix.Data.Test
+{
+ public class FileHandlerTest
+ {
+ [Fact]
+ public void SqlCeFileHandlerReturnsDataDirectoryRelativeConnectionStringIfPathIsNotRooted()
+ {
+ // Act
+ string connectionString = SqlCeDbFileHandler.GetConnectionString("foo.sdf");
+
+ // Assert
+ Assert.NotNull(connectionString);
+ Assert.Equal(@"Data Source=|DataDirectory|\foo.sdf;File Access Retry Timeout=10", connectionString);
+ }
+
+ [Fact]
+ public void SqlCeFileHandlerReturnsFullPathConnectionStringIfPathIsNotRooted()
+ {
+ // Act
+ string connectionString = SqlCeDbFileHandler.GetConnectionString(@"c:\foo.sdf");
+
+ // Assert
+ Assert.NotNull(connectionString);
+ Assert.Equal(@"Data Source=c:\foo.sdf;File Access Retry Timeout=10", connectionString);
+ }
+
+ [Fact]
+ public void SqlServerFileHandlerReturnsDataDirectoryRelativeConnectionStringIfPathIsNotRooted()
+ {
+ // Act
+ string connectionString = SqlServerDbFileHandler.GetConnectionString("foo.mdf", "datadir");
+
+ // Assert
+ Assert.NotNull(connectionString);
+ Assert.Equal(@"Data Source=.\SQLEXPRESS;AttachDbFilename=|DataDirectory|\foo.mdf;Initial Catalog=datadir\foo.mdf;Integrated Security=True;User Instance=True;MultipleActiveResultSets=True",
+ connectionString);
+ }
+
+ [Fact]
+ public void SqlServerFileHandlerReturnsFullPathConnectionStringIfPathIsNotRooted()
+ {
+ // Act
+ string connectionString = SqlServerDbFileHandler.GetConnectionString(@"c:\foo.mdf", "datadir");
+
+ // Assert
+ Assert.NotNull(connectionString);
+ Assert.Equal(@"Data Source=.\SQLEXPRESS;AttachDbFilename=c:\foo.mdf;Initial Catalog=c:\foo.mdf;Integrated Security=True;User Instance=True;MultipleActiveResultSets=True", connectionString);
+ }
+ }
+}
diff --git a/test/WebMatrix.Data.Test/Mocks/MockConfigurationManager.cs b/test/WebMatrix.Data.Test/Mocks/MockConfigurationManager.cs
new file mode 100644
index 00000000..49275fa8
--- /dev/null
+++ b/test/WebMatrix.Data.Test/Mocks/MockConfigurationManager.cs
@@ -0,0 +1,28 @@
+using System.Collections.Generic;
+
+namespace WebMatrix.Data.Test.Mocks
+{
+ internal class MockConfigurationManager : IConfigurationManager
+ {
+ private Dictionary<string, IConnectionConfiguration> _connectionStrings = new Dictionary<string, IConnectionConfiguration>();
+
+ public MockConfigurationManager()
+ {
+ AppSettings = new Dictionary<string, string>();
+ }
+
+ public IDictionary<string, string> AppSettings { get; private set; }
+
+ public void AddConnection(string name, IConnectionConfiguration configuration)
+ {
+ _connectionStrings.Add(name, configuration);
+ }
+
+ public IConnectionConfiguration GetConnection(string name)
+ {
+ IConnectionConfiguration configuration;
+ _connectionStrings.TryGetValue(name, out configuration);
+ return configuration;
+ }
+ }
+}
diff --git a/test/WebMatrix.Data.Test/Mocks/MockConnectionConfiguration.cs b/test/WebMatrix.Data.Test/Mocks/MockConnectionConfiguration.cs
new file mode 100644
index 00000000..395a9556
--- /dev/null
+++ b/test/WebMatrix.Data.Test/Mocks/MockConnectionConfiguration.cs
@@ -0,0 +1,22 @@
+namespace WebMatrix.Data.Test.Mocks
+{
+ public class MockConnectionConfiguration : IConnectionConfiguration
+ {
+ public MockConnectionConfiguration(string connectionString)
+ {
+ ConnectionString = connectionString;
+ }
+
+ public string ConnectionString { get; private set; }
+
+ string IConnectionConfiguration.ConnectionString
+ {
+ get { return ConnectionString; }
+ }
+
+ IDbProviderFactory IConnectionConfiguration.ProviderFactory
+ {
+ get { return null; }
+ }
+ }
+}
diff --git a/test/WebMatrix.Data.Test/Mocks/MockDbFileHandler.cs b/test/WebMatrix.Data.Test/Mocks/MockDbFileHandler.cs
new file mode 100644
index 00000000..fe5129e2
--- /dev/null
+++ b/test/WebMatrix.Data.Test/Mocks/MockDbFileHandler.cs
@@ -0,0 +1,12 @@
+namespace WebMatrix.Data.Test.Mocks
+{
+ public abstract class MockDbFileHandler : IDbFileHandler
+ {
+ IConnectionConfiguration IDbFileHandler.GetConnectionConfiguration(string fileName)
+ {
+ return GetConnectionConfiguration(fileName);
+ }
+
+ public abstract MockConnectionConfiguration GetConnectionConfiguration(string fileName);
+ }
+}
diff --git a/test/WebMatrix.Data.Test/Mocks/MockDbProviderFactory.cs b/test/WebMatrix.Data.Test/Mocks/MockDbProviderFactory.cs
new file mode 100644
index 00000000..34a09c93
--- /dev/null
+++ b/test/WebMatrix.Data.Test/Mocks/MockDbProviderFactory.cs
@@ -0,0 +1,10 @@
+using System.Data.Common;
+
+namespace WebMatrix.Data.Test.Mocks
+{
+ // Needs to be public for Moq to work
+ public abstract class MockDbProviderFactory : IDbProviderFactory
+ {
+ public abstract DbConnection CreateConnection(string connectionString);
+ }
+}
diff --git a/test/WebMatrix.Data.Test/Properties/AssemblyInfo.cs b/test/WebMatrix.Data.Test/Properties/AssemblyInfo.cs
new file mode 100644
index 00000000..60ce4512
--- /dev/null
+++ b/test/WebMatrix.Data.Test/Properties/AssemblyInfo.cs
@@ -0,0 +1,8 @@
+using System.Reflection;
+
+// 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("WebMatrix.Data.Test")]
+[assembly: AssemblyDescription("")]
diff --git a/test/WebMatrix.Data.Test/WebMatrix.Data.Test.csproj b/test/WebMatrix.Data.Test/WebMatrix.Data.Test.csproj
new file mode 100644
index 00000000..5a0e6691
--- /dev/null
+++ b/test/WebMatrix.Data.Test/WebMatrix.Data.Test.csproj
@@ -0,0 +1,90 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory),Runtime.sln))\tools\WebStack.settings.targets" />
+ <PropertyGroup>
+ <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+ <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+ <ProductVersion>9.0.30729</ProductVersion>
+ <SchemaVersion>2.0</SchemaVersion>
+ <ProjectGuid>{E2D008A9-4D1D-4F6B-8325-4ED717D6EA0A}</ProjectGuid>
+ <OutputType>Library</OutputType>
+ <AppDesignerFolder>Properties</AppDesignerFolder>
+ <RootNamespace>WebMatrix.Data.Test</RootNamespace>
+ <AssemblyName>WebMatrix.Data.Test</AssemblyName>
+ <FileAlignment>512</FileAlignment>
+ <ProjectTypeGuids>{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
+ <ErrorReport>prompt</ErrorReport>
+ <WarningLevel>4</WarningLevel>
+ <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
+ <DebugSymbols>true</DebugSymbols>
+ <DebugType>full</DebugType>
+ <Optimize>false</Optimize>
+ <OutputPath>$(WebStackRootPath)\bin\Debug\Test\</OutputPath>
+ <DefineConstants>TRACE;DEBUG</DefineConstants>
+ <CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
+ <DebugType>pdbonly</DebugType>
+ <Optimize>true</Optimize>
+ <OutputPath>$(WebStackRootPath)\bin\Release\Test\</OutputPath>
+ <DefineConstants>TRACE</DefineConstants>
+ <CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)' == 'CodeCoverage' ">
+ <DebugSymbols>true</DebugSymbols>
+ <DebugType>full</DebugType>
+ <Optimize>false</Optimize>
+ <OutputPath>$(WebStackRootPath)\bin\CodeCoverage\Test\</OutputPath>
+ <DefineConstants>TRACE;DEBUG</DefineConstants>
+ <CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
+ </PropertyGroup>
+ <ItemGroup>
+ <Reference Include="Microsoft.CSharp" />
+ <Reference Include="Moq, Version=4.0.10827.0, Culture=neutral, PublicKeyToken=69f491c39445e920, processorArchitecture=MSIL">
+ <HintPath>..\..\packages\Moq.4.0.10827\lib\NET40\Moq.dll</HintPath>
+ </Reference>
+ <Reference Include="System" />
+ <Reference Include="System.Core">
+ <RequiredTargetFramework>3.5</RequiredTargetFramework>
+ </Reference>
+ <Reference Include="System.Data" />
+ <Reference Include="xunit">
+ <HintPath>..\..\packages\xunit.1.9.0.1566\lib\xunit.dll</HintPath>
+ </Reference>
+ <Reference Include="xunit.extensions">
+ <HintPath>..\..\packages\xunit.extensions.1.9.0.1566\lib\xunit.extensions.dll</HintPath>
+ </Reference>
+ </ItemGroup>
+ <ItemGroup>
+ <CodeAnalysisDependentAssemblyPaths Condition=" '$(VS100COMNTOOLS)' != '' " Include="$(VS100COMNTOOLS)..\IDE\PrivateAssemblies">
+ <Visible>False</Visible>
+ </CodeAnalysisDependentAssemblyPaths>
+ </ItemGroup>
+ <ItemGroup>
+ <ProjectReference Include="..\..\src\WebMatrix.Data\WebMatrix.Data.csproj">
+ <Project>{4D39BAAF-8A96-473E-AB79-C8A341885137}</Project>
+ <Name>WebMatrix.Data</Name>
+ </ProjectReference>
+ <ProjectReference Include="..\Microsoft.TestCommon\Microsoft.TestCommon.csproj">
+ <Project>{FCCC4CB7-BAF7-4A57-9F89-E5766FE536C0}</Project>
+ <Name>Microsoft.TestCommon</Name>
+ </ProjectReference>
+ </ItemGroup>
+ <ItemGroup>
+ <Compile Include="ConfigurationManagerWrapperTest.cs" />
+ <Compile Include="DynamicRecordTest.cs" />
+ <Compile Include="FileHandlerTest.cs" />
+ <Compile Include="Mocks\MockConfigurationManager.cs" />
+ <Compile Include="Mocks\MockConnectionConfiguration.cs" />
+ <Compile Include="Mocks\MockDbFileHandler.cs" />
+ <Compile Include="Mocks\MockDbProviderFactory.cs" />
+ <Compile Include="Properties\AssemblyInfo.cs" />
+ <Compile Include="DatabaseTest.cs" />
+ </ItemGroup>
+ <ItemGroup>
+ <None Include="packages.config" />
+ </ItemGroup>
+ <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
+</Project> \ No newline at end of file
diff --git a/test/WebMatrix.Data.Test/packages.config b/test/WebMatrix.Data.Test/packages.config
new file mode 100644
index 00000000..d5aa6401
--- /dev/null
+++ b/test/WebMatrix.Data.Test/packages.config
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<packages>
+ <package id="Moq" version="4.0.10827" />
+ <package id="xunit" version="1.9.0.1566" />
+ <package id="xunit.extensions" version="1.9.0.1566" />
+</packages> \ No newline at end of file
diff --git a/test/WebMatrix.WebData.Test/MockDatabase.cs b/test/WebMatrix.WebData.Test/MockDatabase.cs
new file mode 100644
index 00000000..d77edb0b
--- /dev/null
+++ b/test/WebMatrix.WebData.Test/MockDatabase.cs
@@ -0,0 +1,20 @@
+using System.Collections.Generic;
+
+namespace WebMatrix.WebData.Test
+{
+ public abstract class MockDatabase : IDatabase
+ {
+ public abstract dynamic QuerySingle(string commandText, params object[] args);
+
+ public abstract IEnumerable<dynamic> Query(string commandText, params object[] parameters);
+
+ public abstract dynamic QueryValue(string commandText, params object[] parameters);
+
+ public abstract int Execute(string commandText, params object[] args);
+
+ public void Dispose()
+ {
+ // Do nothing.
+ }
+ }
+}
diff --git a/test/WebMatrix.WebData.Test/PreApplicationStartCodeTest.cs b/test/WebMatrix.WebData.Test/PreApplicationStartCodeTest.cs
new file mode 100644
index 00000000..9ff9efc2
--- /dev/null
+++ b/test/WebMatrix.WebData.Test/PreApplicationStartCodeTest.cs
@@ -0,0 +1,102 @@
+using System.Configuration;
+using System.Linq;
+using System.Reflection;
+using System.Web.Security;
+using System.Web.WebPages.Razor;
+using System.Web.WebPages.TestUtils;
+using Xunit;
+
+namespace WebMatrix.WebData.Test
+{
+ public class PreApplicationStartCodeTest
+ {
+ [Fact]
+ public void StartRegistersRazorNamespaces()
+ {
+ AppDomainUtils.RunInSeparateAppDomain(() =>
+ {
+ AppDomainUtils.SetPreAppStartStage();
+ PreApplicationStartCode.Start();
+ // Call a second time to ensure multiple calls do not cause issues
+ PreApplicationStartCode.Start();
+
+ // Verify namespaces
+ var imports = WebPageRazorHost.GetGlobalImports();
+ Assert.True(imports.Any(ns => ns.Equals("WebMatrix.Data")));
+ Assert.True(imports.Any(ns => ns.Equals("WebMatrix.WebData")));
+ });
+ }
+
+ [Fact]
+ public void StartInitializesFormsAuthByDefault()
+ {
+ AppDomainUtils.RunInSeparateAppDomain(() =>
+ {
+ AppDomainUtils.SetPreAppStartStage();
+ PreApplicationStartCode.Start();
+
+ string formsAuthLoginUrl = (string)typeof(FormsAuthentication).GetField("_LoginUrl", BindingFlags.Static | BindingFlags.NonPublic).GetValue(null);
+ Assert.Equal(FormsAuthenticationSettings.DefaultLoginUrl, formsAuthLoginUrl);
+ });
+ }
+
+ [Fact]
+ public void StartDoesNotInitializeFormsAuthWhenDisabled()
+ {
+ AppDomainUtils.RunInSeparateAppDomain(() =>
+ {
+ AppDomainUtils.SetPreAppStartStage();
+ ConfigurationManager.AppSettings[WebSecurity.EnableSimpleMembershipKey] = "False";
+ PreApplicationStartCode.Start();
+
+ string formsAuthLoginUrl = (string)typeof(FormsAuthentication).GetField("_LoginUrl", BindingFlags.Static | BindingFlags.NonPublic).GetValue(null);
+ Assert.Null(formsAuthLoginUrl);
+ });
+ }
+
+ [Fact]
+ public void StartInitializesSimpleMembershipByDefault()
+ {
+ AppDomainUtils.RunInSeparateAppDomain(() =>
+ {
+ AppDomainUtils.SetPreAppStartStage();
+ PreApplicationStartCode.Start();
+
+ // Verify simple membership
+ var providers = Membership.Providers;
+ Assert.Equal(1, providers.Count);
+ foreach (var provider in providers)
+ {
+ Assert.IsAssignableFrom<SimpleMembershipProvider>(provider);
+ }
+ Assert.True(Roles.Enabled);
+ });
+ }
+
+ [Fact]
+ public void StartDoesNotInitializeSimpleMembershipWhenDisabled()
+ {
+ AppDomainUtils.RunInSeparateAppDomain(() =>
+ {
+ AppDomainUtils.SetPreAppStartStage();
+ ConfigurationManager.AppSettings[WebSecurity.EnableSimpleMembershipKey] = "False";
+ PreApplicationStartCode.Start();
+
+ // Verify simple membership
+ var providers = Membership.Providers;
+ Assert.Equal(1, providers.Count);
+ foreach (var provider in providers)
+ {
+ Assert.IsAssignableFrom<SqlMembershipProvider>(provider);
+ }
+ Assert.False(Roles.Enabled);
+ });
+ }
+
+ [Fact]
+ public void TestPreAppStartClass()
+ {
+ PreAppStartTestHelper.TestPreAppStartClass(typeof(PreApplicationStartCode));
+ }
+ }
+}
diff --git a/test/WebMatrix.WebData.Test/Properties/AssemblyInfo.cs b/test/WebMatrix.WebData.Test/Properties/AssemblyInfo.cs
new file mode 100644
index 00000000..8e70bc6e
--- /dev/null
+++ b/test/WebMatrix.WebData.Test/Properties/AssemblyInfo.cs
@@ -0,0 +1,8 @@
+using System.Reflection;
+
+// 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("WebMatrix.WebData.Test")]
+[assembly: AssemblyDescription("")]
diff --git a/test/WebMatrix.WebData.Test/SimpleMembershipProviderTest.cs b/test/WebMatrix.WebData.Test/SimpleMembershipProviderTest.cs
new file mode 100644
index 00000000..fde44182
--- /dev/null
+++ b/test/WebMatrix.WebData.Test/SimpleMembershipProviderTest.cs
@@ -0,0 +1,199 @@
+using System;
+using System.Data;
+using System.Linq;
+using System.Security.Cryptography;
+using System.Text;
+using Moq;
+using WebMatrix.Data;
+using Xunit;
+
+namespace WebMatrix.WebData.Test
+{
+ public class SimpleMembershipProviderTest
+ {
+ [Fact]
+ public void ConfirmAccountReturnsFalseIfNoRecordExistsForToken()
+ {
+ // Arrange
+ var database = new Mock<MockDatabase>(MockBehavior.Strict);
+ database.Setup(d => d.Query("SELECT [UserId], [ConfirmationToken] FROM webpages_Membership WHERE [ConfirmationToken] = @0", "foo"))
+ .Returns(Enumerable.Empty<DynamicRecord>());
+ var simpleMembershipProvider = new TestSimpleMembershipProvider(database.Object);
+
+ // Act
+ bool result = simpleMembershipProvider.ConfirmAccount("foo");
+
+ // Assert
+ Assert.False(result);
+ }
+
+ [Fact]
+ public void ConfirmAccountReturnsFalseIfConfirmationTokenDoesNotMatchInCase()
+ {
+ // Arrange
+ var database = new Mock<MockDatabase>(MockBehavior.Strict);
+ DynamicRecord record = GetRecord(98, "Foo");
+ database.Setup(d => d.Query("SELECT [UserId], [ConfirmationToken] FROM webpages_Membership WHERE [ConfirmationToken] = @0", "foo"))
+ .Returns(new[] { record });
+ var simpleMembershipProvider = new TestSimpleMembershipProvider(database.Object);
+
+ // Act
+ bool result = simpleMembershipProvider.ConfirmAccount("foo");
+
+ // Assert
+ Assert.False(result);
+ }
+
+ [Fact]
+ public void ConfirmAccountReturnsFalseIfNoConfirmationTokenFromMultipleListMatchesInCase()
+ {
+ // Arrange
+ var database = new Mock<MockDatabase>(MockBehavior.Strict);
+ DynamicRecord recordA = GetRecord(98, "Foo");
+ DynamicRecord recordB = GetRecord(99, "fOo");
+ database.Setup(d => d.Query("SELECT [UserId], [ConfirmationToken] FROM webpages_Membership WHERE [ConfirmationToken] = @0", "foo"))
+ .Returns(new[] { recordA, recordB });
+ var simpleMembershipProvider = new TestSimpleMembershipProvider(database.Object);
+
+ // Act
+ bool result = simpleMembershipProvider.ConfirmAccount("foo");
+
+ // Assert
+ Assert.False(result);
+ }
+
+ [Fact]
+ public void ConfirmAccountUpdatesIsConfirmedFieldIfConfirmationTokenMatches()
+ {
+ // Arrange
+ var database = new Mock<MockDatabase>(MockBehavior.Strict);
+ DynamicRecord record = GetRecord(100, "foo");
+ database.Setup(d => d.Query("SELECT [UserId], [ConfirmationToken] FROM webpages_Membership WHERE [ConfirmationToken] = @0", "foo"))
+ .Returns(new[] { record }).Verifiable();
+ database.Setup(d => d.Execute("UPDATE webpages_Membership SET [IsConfirmed] = 1 WHERE [UserId] = @0", 100)).Returns(1).Verifiable();
+ var simpleMembershipProvider = new TestSimpleMembershipProvider(database.Object);
+
+ // Act
+ bool result = simpleMembershipProvider.ConfirmAccount("foo");
+
+ // Assert
+ Assert.True(result);
+ database.Verify();
+ }
+
+ [Fact]
+ public void ConfirmAccountUpdatesIsConfirmedFieldIfAnyOneOfReturnRecordConfirmationTokenMatches()
+ {
+ // Arrange
+ var database = new Mock<MockDatabase>(MockBehavior.Strict);
+ DynamicRecord recordA = GetRecord(100, "Foo");
+ DynamicRecord recordB = GetRecord(101, "foo");
+ DynamicRecord recordC = GetRecord(102, "fOo");
+ database.Setup(d => d.Query("SELECT [UserId], [ConfirmationToken] FROM webpages_Membership WHERE [ConfirmationToken] = @0", "foo"))
+ .Returns(new[] { recordA, recordB, recordC }).Verifiable();
+ database.Setup(d => d.Execute("UPDATE webpages_Membership SET [IsConfirmed] = 1 WHERE [UserId] = @0", 101)).Returns(1).Verifiable();
+ var simpleMembershipProvider = new TestSimpleMembershipProvider(database.Object);
+
+ // Act
+ bool result = simpleMembershipProvider.ConfirmAccount("foo");
+
+ // Assert
+ Assert.True(result);
+ database.Verify();
+ }
+
+ [Fact]
+ public void ConfirmAccountWithUserNameReturnsFalseIfNoRecordExistsForToken()
+ {
+ // Arrange
+ var database = new Mock<MockDatabase>(MockBehavior.Strict);
+ database.Setup(d => d.QuerySingle("SELECT m.[UserId], m.[ConfirmationToken] FROM webpages_Membership m JOIN [Users] u ON m.[UserId] = u.[UserId] WHERE m.[ConfirmationToken] = @0 AND u.[UserName] = @1", "foo", "user12")).Returns(null);
+ var simpleMembershipProvider = new TestSimpleMembershipProvider(database.Object) { UserIdColumn = "UserId", UserNameColumn = "UserName", UserTableName = "Users" };
+
+ // Act
+ bool result = simpleMembershipProvider.ConfirmAccount("user12", "foo");
+
+ // Assert
+ Assert.False(result);
+ }
+
+ [Fact]
+ public void ConfirmAccountWithUserNameReturnsFalseIfConfirmationTokenDoesNotMatchInCase()
+ {
+ // Arrange
+ var database = new Mock<MockDatabase>(MockBehavior.Strict);
+ DynamicRecord record = GetRecord(98, "Foo");
+ database.Setup(d => d.QuerySingle("SELECT m.[UserId], m.[ConfirmationToken] FROM webpages_Membership m JOIN [Users_bkp2_1] u ON m.[UserId] = u.[wishlist_site_real_user_id] WHERE m.[ConfirmationToken] = @0 AND u.[wishlist_site_real_user_name] = @1", "foo", "user13")).Returns(record);
+ var simpleMembershipProvider = new TestSimpleMembershipProvider(database.Object) { UserIdColumn = "wishlist_site_real_user_id", UserNameColumn = "wishlist_site_real_user_name", UserTableName = "Users_bkp2_1" };
+
+ // Act
+ bool result = simpleMembershipProvider.ConfirmAccount("user13", "foo");
+
+ // Assert
+ Assert.False(result);
+ }
+
+ [Fact]
+ public void ConfirmAccountWithUserNameUpdatesIsConfirmedFieldIfConfirmationTokenMatches()
+ {
+ // Arrange
+ var database = new Mock<MockDatabase>(MockBehavior.Strict);
+ DynamicRecord record = GetRecord(100, "foo");
+ database.Setup(d => d.QuerySingle("SELECT m.[UserId], m.[ConfirmationToken] FROM webpages_Membership m JOIN [Users] u ON m.[UserId] = u.[Id] WHERE m.[ConfirmationToken] = @0 AND u.[UserName] = @1", "foo", "user14"))
+ .Returns(record).Verifiable();
+ database.Setup(d => d.Execute("UPDATE webpages_Membership SET [IsConfirmed] = 1 WHERE [UserId] = @0", 100)).Returns(1).Verifiable();
+ var simpleMembershipProvider = new TestSimpleMembershipProvider(database.Object) { UserTableName = "Users", UserIdColumn = "Id", UserNameColumn = "UserName" };
+
+ // Act
+ bool result = simpleMembershipProvider.ConfirmAccount("user14", "foo");
+
+ // Assert
+ Assert.True(result);
+ database.Verify();
+ }
+
+ [Fact]
+ public void GenerateTokenHtmlEncodesValues()
+ {
+ // Arrange
+ var generator = new Mock<RandomNumberGenerator>(MockBehavior.Strict);
+ var generatedBytes = Encoding.Default.GetBytes("|aÿx§#½oÿ↨îA8Eµ");
+ generator.Setup(g => g.GetBytes(It.IsAny<byte[]>())).Callback((byte[] array) => Array.Copy(generatedBytes, array, generatedBytes.Length));
+
+ // Act
+ var result = SimpleMembershipProvider.GenerateToken(generator.Object);
+
+ // Assert
+ Assert.Equal("fGH/eKcjvW//P+5BOEW1", Convert.ToBase64String(generatedBytes));
+ Assert.Equal("fGH_eKcjvW__P-5BOEW1AA2", result);
+ }
+
+ private static DynamicRecord GetRecord(int userId, string confirmationToken)
+ {
+ var data = new Mock<IDataRecord>(MockBehavior.Strict);
+ data.Setup(c => c[0]).Returns(userId);
+ data.Setup(c => c[1]).Returns(confirmationToken);
+ return new DynamicRecord(new[] { "UserId", "ConfirmationToken" }, data.Object);
+ }
+
+ private class TestSimpleMembershipProvider : SimpleMembershipProvider
+ {
+ private readonly IDatabase _database;
+
+ public TestSimpleMembershipProvider(IDatabase database)
+ {
+ _database = database;
+ }
+
+ internal override IDatabase ConnectToDatabase()
+ {
+ return _database;
+ }
+
+ internal override void VerifyInitialized()
+ {
+ // Do nothing.
+ }
+ }
+ }
+}
diff --git a/test/WebMatrix.WebData.Test/WebMatrix.WebData.Test.csproj b/test/WebMatrix.WebData.Test/WebMatrix.WebData.Test.csproj
new file mode 100644
index 00000000..7587f03c
--- /dev/null
+++ b/test/WebMatrix.WebData.Test/WebMatrix.WebData.Test.csproj
@@ -0,0 +1,94 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory),Runtime.sln))\tools\WebStack.settings.targets" />
+ <PropertyGroup>
+ <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+ <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+ <ProductVersion>9.0.30729</ProductVersion>
+ <SchemaVersion>2.0</SchemaVersion>
+ <ProjectGuid>{CD48EB41-92A5-4628-A0F7-6A43DF58827E}</ProjectGuid>
+ <OutputType>Library</OutputType>
+ <AppDesignerFolder>Properties</AppDesignerFolder>
+ <RootNamespace>WebMatrix.WebData.Test</RootNamespace>
+ <AssemblyName>WebMatrix.WebData.Test</AssemblyName>
+ <FileAlignment>512</FileAlignment>
+ <ProjectTypeGuids>{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
+ <ErrorReport>prompt</ErrorReport>
+ <WarningLevel>4</WarningLevel>
+ <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
+ <DebugSymbols>true</DebugSymbols>
+ <DebugType>full</DebugType>
+ <Optimize>false</Optimize>
+ <OutputPath>$(WebStackRootPath)\bin\Debug\Test\</OutputPath>
+ <DefineConstants>TRACE;DEBUG</DefineConstants>
+ <CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
+ <DebugType>pdbonly</DebugType>
+ <Optimize>true</Optimize>
+ <OutputPath>$(WebStackRootPath)\bin\Release\Test\</OutputPath>
+ <DefineConstants>TRACE</DefineConstants>
+ <CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)' == 'CodeCoverage' ">
+ <DebugSymbols>true</DebugSymbols>
+ <DebugType>full</DebugType>
+ <Optimize>false</Optimize>
+ <OutputPath>$(WebStackRootPath)\bin\CodeCoverage\Test\</OutputPath>
+ <DefineConstants>TRACE;DEBUG</DefineConstants>
+ <CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
+ </PropertyGroup>
+ <ItemGroup>
+ <Reference Include="Moq, Version=4.0.10827.0, Culture=neutral, PublicKeyToken=69f491c39445e920, processorArchitecture=MSIL">
+ <HintPath>..\..\packages\Moq.4.0.10827\lib\NET40\Moq.dll</HintPath>
+ </Reference>
+ <Reference Include="System" />
+ <Reference Include="System.configuration" />
+ <Reference Include="System.Core" />
+ <Reference Include="System.Web" />
+ <Reference Include="System.Web.ApplicationServices" />
+ <Reference Include="Microsoft.CSharp" />
+ <Reference Include="System.Data" />
+ <Reference Include="xunit">
+ <HintPath>..\..\packages\xunit.1.9.0.1566\lib\xunit.dll</HintPath>
+ </Reference>
+ <Reference Include="xunit.extensions">
+ <HintPath>..\..\packages\xunit.extensions.1.9.0.1566\lib\xunit.extensions.dll</HintPath>
+ </Reference>
+ </ItemGroup>
+ <ItemGroup>
+ <Compile Include="MockDatabase.cs" />
+ <Compile Include="PreApplicationStartCodeTest.cs" />
+ <Compile Include="Properties\AssemblyInfo.cs" />
+ <Compile Include="SimpleMembershipProviderTest.cs" />
+ <Compile Include="WebSecurityTest.cs" />
+ </ItemGroup>
+ <ItemGroup>
+ <ProjectReference Include="..\..\src\System.Web.Razor\System.Web.Razor.csproj">
+ <Project>{8F18041B-9410-4C36-A9C5-067813DF5F31}</Project>
+ <Name>System.Web.Razor</Name>
+ </ProjectReference>
+ <ProjectReference Include="..\..\src\System.Web.WebPages.Razor\System.Web.WebPages.Razor.csproj">
+ <Project>{0939B11A-FE4E-4BA1-8AD6-D97741EE314F}</Project>
+ <Name>System.Web.WebPages.Razor</Name>
+ </ProjectReference>
+ <ProjectReference Include="..\..\src\WebMatrix.Data\WebMatrix.Data.csproj">
+ <Project>{4D39BAAF-8A96-473E-AB79-C8A341885137}</Project>
+ <Name>WebMatrix.Data</Name>
+ </ProjectReference>
+ <ProjectReference Include="..\..\src\WebMatrix.WebData\WebMatrix.WebData.csproj">
+ <Project>{55A15F40-1435-4248-A7F2-2A146BB83586}</Project>
+ <Name>WebMatrix.WebData</Name>
+ </ProjectReference>
+ <ProjectReference Include="..\Microsoft.TestCommon\Microsoft.TestCommon.csproj">
+ <Project>{FCCC4CB7-BAF7-4A57-9F89-E5766FE536C0}</Project>
+ <Name>Microsoft.TestCommon</Name>
+ </ProjectReference>
+ </ItemGroup>
+ <ItemGroup>
+ <None Include="packages.config" />
+ </ItemGroup>
+ <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
+</Project> \ No newline at end of file
diff --git a/test/WebMatrix.WebData.Test/WebSecurityTest.cs b/test/WebMatrix.WebData.Test/WebSecurityTest.cs
new file mode 100644
index 00000000..aa58c978
--- /dev/null
+++ b/test/WebMatrix.WebData.Test/WebSecurityTest.cs
@@ -0,0 +1,26 @@
+using System;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace WebMatrix.WebData.Test
+{
+ public class WebSecurityTest
+ {
+ [Fact]
+ public void VerifyExtendedMembershipProviderMethodsThrowWithInvalidProvider()
+ {
+ const string errorString = "To call this method, the \"Membership.Provider\" property must be an instance of \"ExtendedMembershipProvider\".";
+ Assert.Throws<InvalidOperationException>(() => WebSecurity.ConfirmAccount(""), errorString);
+ Assert.Throws<InvalidOperationException>(() => WebSecurity.GeneratePasswordResetToken(""), errorString);
+ Assert.Throws<InvalidOperationException>(() => WebSecurity.GetUserIdFromPasswordResetToken(""), errorString);
+ Assert.Throws<InvalidOperationException>(() => WebSecurity.ResetPassword("", "whatever"), errorString);
+ Assert.Throws<InvalidOperationException>(() => WebSecurity.CreateUserAndAccount("", "whatever"), errorString);
+ Assert.Throws<InvalidOperationException>(() => WebSecurity.CreateAccount("", "whatever"), errorString);
+ Assert.Throws<InvalidOperationException>(() => WebSecurity.IsConfirmed("whatever"), errorString);
+ Assert.Throws<InvalidOperationException>(() => WebSecurity.GetPasswordFailuresSinceLastSuccess("whatever"), errorString);
+ Assert.Throws<InvalidOperationException>(() => WebSecurity.GetCreateDate("whatever"), errorString);
+ Assert.Throws<InvalidOperationException>(() => WebSecurity.GetLastPasswordFailureDate("whatever"), errorString);
+ Assert.Throws<InvalidOperationException>(() => WebSecurity.GetPasswordChangedDate("whatever"), errorString);
+ }
+ }
+}
diff --git a/test/WebMatrix.WebData.Test/packages.config b/test/WebMatrix.WebData.Test/packages.config
new file mode 100644
index 00000000..d5aa6401
--- /dev/null
+++ b/test/WebMatrix.WebData.Test/packages.config
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<packages>
+ <package id="Moq" version="4.0.10827" />
+ <package id="xunit" version="1.9.0.1566" />
+ <package id="xunit.extensions" version="1.9.0.1566" />
+</packages> \ No newline at end of file