From 55539aa632beb417243bd7c281b3a89f68c6b772 Mon Sep 17 00:00:00 2001 From: achingono Date: Fri, 29 Nov 2013 10:54:29 -0500 Subject: [PATCH 1/7] Only skip property if ScaffolColumnAttribut is false --- src/TestStack.Seleno/PageObjects/Actions/PageWriter.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/TestStack.Seleno/PageObjects/Actions/PageWriter.cs b/src/TestStack.Seleno/PageObjects/Actions/PageWriter.cs index 8d922234..366cf224 100644 --- a/src/TestStack.Seleno/PageObjects/Actions/PageWriter.cs +++ b/src/TestStack.Seleno/PageObjects/Actions/PageWriter.cs @@ -29,11 +29,12 @@ public void Model(TModel viewModel, IDictionary> prop { var propertyName = property.Name; var propertyValue = property.GetValue(viewModel, null); + var customAttributes = property.GetCustomAttributes(false); - if (property.GetCustomAttributes(typeof(HiddenInputAttribute), false).Length > 0) + if (customAttributes.OfType().Any()) continue; - if (property.GetCustomAttributes(typeof(ScaffoldColumnAttribute), false).Length > 0) + if (customAttributes.OfType().Any(x => !x.Scaffold)) continue; if (propertyValue == null) From 561b7089ab64f2d35ec863aa7a8def643265b865 Mon Sep 17 00:00:00 2001 From: achingono Date: Fri, 29 Nov 2013 10:59:42 -0500 Subject: [PATCH 2/7] Added support for complex properties --- .../PageObjects/Actions/PageWriter.cs | 49 +++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/src/TestStack.Seleno/PageObjects/Actions/PageWriter.cs b/src/TestStack.Seleno/PageObjects/Actions/PageWriter.cs index 366cf224..50ca46c6 100644 --- a/src/TestStack.Seleno/PageObjects/Actions/PageWriter.cs +++ b/src/TestStack.Seleno/PageObjects/Actions/PageWriter.cs @@ -37,6 +37,14 @@ public void Model(TModel viewModel, IDictionary> prop if (customAttributes.OfType().Any(x => !x.Scaffold)) continue; + // iterate through sub-properties of complex type + if (!(property.PropertyType.IsValueType || + property.PropertyType.Equals(typeof(string)))) + { + Property(propertyName, viewModel); + continue; + } + if (propertyValue == null) continue; @@ -47,6 +55,47 @@ public void Model(TModel viewModel, IDictionary> prop } } + /// + /// Fill out the fields rendered by a complex property + /// + /// + /// + /// + /// + public void Property(string propertyName, TModel model, IDictionary> propertyTypeHandling = null) + { + var viewProperty = typeof(TModel).GetProperty(propertyName); + var viewType = viewProperty.PropertyType; + var viewModel = viewProperty.GetValue(model, null); + + if (viewModel == null) + { + return; + } + + foreach (var property in viewType.GetProperties()) + { + var prefixedPropertyName = string.Format("{0}_{1}", propertyName, property.Name); + var propertyValue = property.GetValue(viewModel, null); + var customAttributes = property.GetCustomAttributes(false); + + if (customAttributes.OfType().Any()) + continue; + + if (customAttributes.OfType().Any(x => !x.Scaffold)) + continue; + + if (propertyValue == null) + continue; + + //var stringValue = pageWriter.GetStringValue(propertyTypeHandling, propertyValue, property); + var stringValue = propertyValue.ToString(); + + var textBox = _componentFactory.HtmlControlFor(propertyName); + textBox.ReplaceInputValueWith(stringValue); + } + } + [Obsolete("Use ReplaceInputValueWith instead")] public void TextInField(string fieldName, string value) { From ac3a3b91882265d4105fabc88077ba24fae8941d Mon Sep 17 00:00:00 2001 From: achingono Date: Fri, 29 Nov 2013 11:26:30 -0500 Subject: [PATCH 3/7] Reorganized code a bit --- .../PageObjects/Actions/PageWriter.cs | 29 +++++++++---------- 1 file changed, 13 insertions(+), 16 deletions(-) diff --git a/src/TestStack.Seleno/PageObjects/Actions/PageWriter.cs b/src/TestStack.Seleno/PageObjects/Actions/PageWriter.cs index 50ca46c6..ff5f9762 100644 --- a/src/TestStack.Seleno/PageObjects/Actions/PageWriter.cs +++ b/src/TestStack.Seleno/PageObjects/Actions/PageWriter.cs @@ -28,7 +28,6 @@ public void Model(TModel viewModel, IDictionary> prop foreach (var property in type.GetProperties()) { var propertyName = property.Name; - var propertyValue = property.GetValue(viewModel, null); var customAttributes = property.GetCustomAttributes(false); if (customAttributes.OfType().Any()) @@ -41,10 +40,11 @@ public void Model(TModel viewModel, IDictionary> prop if (!(property.PropertyType.IsValueType || property.PropertyType.Equals(typeof(string)))) { - Property(propertyName, viewModel); + ModelProperty(viewModel, property, propertyTypeHandling); continue; } + var propertyValue = property.GetValue(viewModel, null); if (propertyValue == null) continue; @@ -58,26 +58,24 @@ public void Model(TModel viewModel, IDictionary> prop /// /// Fill out the fields rendered by a complex property /// - /// - /// /// - /// - public void Property(string propertyName, TModel model, IDictionary> propertyTypeHandling = null) + /// + /// + private void ModelProperty(TModel model, PropertyInfo property, IDictionary> propertyTypeHandling = null) { - var viewProperty = typeof(TModel).GetProperty(propertyName); - var viewType = viewProperty.PropertyType; - var viewModel = viewProperty.GetValue(model, null); + var viewType = property.PropertyType; + var viewModel = property.GetValue(model, null); if (viewModel == null) { return; } - foreach (var property in viewType.GetProperties()) + foreach (var subProperty in viewType.GetProperties()) { - var prefixedPropertyName = string.Format("{0}_{1}", propertyName, property.Name); - var propertyValue = property.GetValue(viewModel, null); - var customAttributes = property.GetCustomAttributes(false); + var prefixedPropertyName = string.Format("{0}_{1}", property.Name, subProperty.Name); + var propertyValue = subProperty.GetValue(viewModel, null); + var customAttributes = subProperty.GetCustomAttributes(false); if (customAttributes.OfType().Any()) continue; @@ -88,10 +86,9 @@ public void Property(string propertyName, TModel model, IDictionary(propertyName); + var textBox = _componentFactory.HtmlControlFor(prefixedPropertyName); textBox.ReplaceInputValueWith(stringValue); } } From 463330c54da1f7df9cf42727b0526c9895a935fa Mon Sep 17 00:00:00 2001 From: Robert Moore Date: Sat, 21 Dec 2013 16:02:27 +0800 Subject: [PATCH 4/7] Added a test for the Input.Model functionality as well as ignoring [ReadOnly(true)] properties --- .../PageWriter/When_inputting_a_model.cs | 133 ++++++++++++++++++ .../TestObjects/TestViewModel.cs | 14 ++ .../TestStack.Seleno.Tests.csproj | 5 + src/TestStack.Seleno.Tests/packages.config | 1 + .../PageObjects/Actions/PageWriter.cs | 7 + 5 files changed, 160 insertions(+) create mode 100644 src/TestStack.Seleno.Tests/PageObjects/Actions/PageWriter/When_inputting_a_model.cs diff --git a/src/TestStack.Seleno.Tests/PageObjects/Actions/PageWriter/When_inputting_a_model.cs b/src/TestStack.Seleno.Tests/PageObjects/Actions/PageWriter/When_inputting_a_model.cs new file mode 100644 index 00000000..d6aea56a --- /dev/null +++ b/src/TestStack.Seleno.Tests/PageObjects/Actions/PageWriter/When_inputting_a_model.cs @@ -0,0 +1,133 @@ +using System; +using System.Collections.Generic; +using FizzWare.NBuilder; +using TestStack.Seleno.Configuration.ControlIdGenerators; +using TestStack.Seleno.PageObjects; +using TestStack.Seleno.PageObjects.Actions; +using TestStack.Seleno.PageObjects.Controls; +using TestStack.Seleno.Tests.TestObjects; +using NSubstitute; +using TestStack.Seleno.Extensions; + +namespace TestStack.Seleno.Tests.PageObjects.Actions.PageWriter +{ + class When_inputting_a_model : PageWriterSpecification + { + private const string DateFormat = "d/M/yyyy"; + private TestViewModel _model; + private Dictionary> _propertyHandling; + + public void AndGiven_property_handling_setup_for_datetime() + { + _propertyHandling = new Dictionary> + { + {typeof(DateTime), d => ((DateTime)d).ToString(DateFormat)} + }; + } + + public void Given_a_model() + { + _model = Builder.CreateNew() + .With(m => m.SubViewModel = Builder.CreateNew().Build()) + .Build(); + } + + public void When_inputting_that_model_with_property_type_handling() + { + SUT.Model(_model, _propertyHandling); + } + + public void Then_input_string_property() + { + AssertPropertyValueSet("Name", _model.Name); + } + + public void And_input_datetime_property_using_property_type_handling() + { + AssertPropertyValueSet("Modified", _model.Modified.ToString(DateFormat)); + } + + public void And_ignore_hidden_input_property() + { + AssertPropertyIgnored("HiddenProperty"); + } + + public void And_ignore_readonly_property() + { + AssertPropertyIgnored("ReadonlyProperty"); + } + + public void And_input_non_readonly_property() + { + AssertPropertyValueSet("NonReadonlyProperty", _model.NonReadonlyProperty); + } + + public void And_input_scaffolded_property() + { + AssertPropertyValueSet("ScaffoldedProperty", _model.ScaffoldedProperty); + } + + public void And_ignore_non_scaffolded_property() + { + AssertPropertyIgnored("NonScaffoldedProperty"); + } + + public void And_input_sub_view_model_string_property() + { + AssertPropertyValueSet("SubViewModel_Name", _model.SubViewModel.Name); + } + + public void And_input_sub_view_model_datetime_property_using_property_type_handling() + { + AssertPropertyValueSet("SubViewModel_Modified", _model.Modified.ToString(DateFormat)); + } + + public void And_ignore_sub_view_model_hidden_input_property() + { + AssertPropertyIgnored("SubViewModel_HiddenProperty"); + } + + public void And_ignore_sub_view_model_readonly_property() + { + AssertPropertyIgnored("SubViewModel_ReadonlyProperty"); + } + + public void And_input_sub_view_model_non_readonly_property() + { + AssertPropertyValueSet("SubViewModel_NonReadonlyProperty", _model.SubViewModel.NonReadonlyProperty); + } + + public void And_input_sub_view_model_scaffolded_property() + { + AssertPropertyValueSet("SubViewModel_ScaffoldedProperty", _model.SubViewModel.ScaffoldedProperty); + } + + public void And_ignore_sub_view_model_non_scaffolded_property() + { + AssertPropertyIgnored("SubViewModel_NonScaffoldedProperty"); + } + + private void AssertPropertyIgnored(string fieldId) + { + SubstituteFor().DidNotReceive().Script(Arg.Is(s => s.Contains("#" + fieldId))); + } + + private void AssertPropertyValueSet(string fieldId, string fieldValue) + { + SubstituteFor().Received().Script(string.Format("$('#{0}').val(\"{1}\")", fieldId, fieldValue.ToJavaScriptString())); + } + + public override void Setup() + { + SubstituteFor().HtmlControlFor(Arg.Any()) + .Returns(a => new TextBox + { + PageNavigator = SubstituteFor(), + Executor = SubstituteFor(), + ControlIdGenerator = new MvcControlIdGenerator() + } + .Initialize(a.Arg()) + ); + } + } +} diff --git a/src/TestStack.Seleno.Tests/TestObjects/TestViewModel.cs b/src/TestStack.Seleno.Tests/TestObjects/TestViewModel.cs index 15c9d0e1..d24fd41d 100644 --- a/src/TestStack.Seleno.Tests/TestObjects/TestViewModel.cs +++ b/src/TestStack.Seleno.Tests/TestObjects/TestViewModel.cs @@ -1,4 +1,7 @@ using System; +using System.ComponentModel; +using System.ComponentModel.DataAnnotations; +using System.Web.Mvc; namespace TestStack.Seleno.Tests.TestObjects { @@ -14,6 +17,17 @@ public class TestViewModel public string AnotherChoice { get; set; } public TestViewModel SubViewModel { get; set; } + + [HiddenInput] + public string HiddenProperty { get; set; } + [ReadOnly(true)] + public string ReadonlyProperty { get; set; } + [ReadOnly(false)] + public string NonReadonlyProperty { get; set; } + [ScaffoldColumn(true)] + public string ScaffoldedProperty { get; set; } + [ScaffoldColumn(false)] + public string NonScaffoldedProperty { get; set; } } public enum ChoiceType diff --git a/src/TestStack.Seleno.Tests/TestStack.Seleno.Tests.csproj b/src/TestStack.Seleno.Tests/TestStack.Seleno.Tests.csproj index 0bd148a9..88904b26 100644 --- a/src/TestStack.Seleno.Tests/TestStack.Seleno.Tests.csproj +++ b/src/TestStack.Seleno.Tests/TestStack.Seleno.Tests.csproj @@ -50,6 +50,9 @@ False ..\packages\Castle.Core.3.2.0\lib\net40-client\Castle.Core.dll + + ..\packages\NBuilder.3.0.1.1\lib\FizzWare.NBuilder.dll + False ..\packages\FluentAssertions.2.0.1\lib\net40\FluentAssertions.dll @@ -67,6 +70,7 @@ ..\packages\NUnit.2.6.2\lib\nunit.framework.dll + @@ -142,6 +146,7 @@ + diff --git a/src/TestStack.Seleno.Tests/packages.config b/src/TestStack.Seleno.Tests/packages.config index 70f8b8fb..22ba5e52 100644 --- a/src/TestStack.Seleno.Tests/packages.config +++ b/src/TestStack.Seleno.Tests/packages.config @@ -5,6 +5,7 @@ + diff --git a/src/TestStack.Seleno/PageObjects/Actions/PageWriter.cs b/src/TestStack.Seleno/PageObjects/Actions/PageWriter.cs index ff5f9762..ab2e5c9c 100644 --- a/src/TestStack.Seleno/PageObjects/Actions/PageWriter.cs +++ b/src/TestStack.Seleno/PageObjects/Actions/PageWriter.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.ComponentModel; using System.ComponentModel.DataAnnotations; using System.Linq; using System.Linq.Expressions; @@ -36,6 +37,9 @@ public void Model(TModel viewModel, IDictionary> prop if (customAttributes.OfType().Any(x => !x.Scaffold)) continue; + if (customAttributes.OfType().Any(x => x.IsReadOnly)) + continue; + // iterate through sub-properties of complex type if (!(property.PropertyType.IsValueType || property.PropertyType.Equals(typeof(string)))) @@ -83,6 +87,9 @@ private void ModelProperty(TModel model, PropertyInfo property, IDictionary().Any(x => !x.Scaffold)) continue; + if (customAttributes.OfType().Any(x => x.IsReadOnly)) + continue; + if (propertyValue == null) continue; From 7c09a2e8b4e25c0ce7c3857403058939cacbb741 Mon Sep 17 00:00:00 2001 From: Robert Moore Date: Sat, 21 Dec 2013 17:18:11 +0800 Subject: [PATCH 5/7] Refactored Input.Model code to pull out common logic for subview model and make sure the Id generator is being used rather than hardcoding logic Also supports infinitely nested view models now Fixed #136 --- .../PageWriter/When_inputting_a_model.cs | 12 +++- .../PageObjects/Actions/PageWriter.cs | 66 +++++-------------- 2 files changed, 24 insertions(+), 54 deletions(-) diff --git a/src/TestStack.Seleno.Tests/PageObjects/Actions/PageWriter/When_inputting_a_model.cs b/src/TestStack.Seleno.Tests/PageObjects/Actions/PageWriter/When_inputting_a_model.cs index d6aea56a..d7b056e9 100644 --- a/src/TestStack.Seleno.Tests/PageObjects/Actions/PageWriter/When_inputting_a_model.cs +++ b/src/TestStack.Seleno.Tests/PageObjects/Actions/PageWriter/When_inputting_a_model.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Linq.Expressions; using FizzWare.NBuilder; using TestStack.Seleno.Configuration.ControlIdGenerators; using TestStack.Seleno.PageObjects; @@ -28,7 +29,7 @@ public void AndGiven_property_handling_setup_for_datetime() public void Given_a_model() { _model = Builder.CreateNew() - .With(m => m.SubViewModel = Builder.CreateNew().Build()) + .With(m => m.SubViewModel = Builder.CreateNew().With(mm => mm.SubViewModel = new TestViewModel{Name = "TripleNestedName"}).Build()) .Build(); } @@ -106,6 +107,11 @@ public void And_ignore_sub_view_model_non_scaffolded_property() { AssertPropertyIgnored("SubViewModel_NonScaffoldedProperty"); } + + public void And_input_deep_nested_model_property() + { + AssertPropertyValueSet("SubViewModel_SubViewModel_Name", _model.SubViewModel.SubViewModel.Name); + } private void AssertPropertyIgnored(string fieldId) { @@ -119,14 +125,14 @@ private void AssertPropertyValueSet(string fieldId, string fieldValue) public override void Setup() { - SubstituteFor().HtmlControlFor(Arg.Any()) + SubstituteFor().HtmlControlFor(Arg.Any()) .Returns(a => new TextBox { PageNavigator = SubstituteFor(), Executor = SubstituteFor(), ControlIdGenerator = new MvcControlIdGenerator() } - .Initialize(a.Arg()) + .Initialize(a.Arg()) ); } } diff --git a/src/TestStack.Seleno/PageObjects/Actions/PageWriter.cs b/src/TestStack.Seleno/PageObjects/Actions/PageWriter.cs index ab2e5c9c..c3f4c248 100644 --- a/src/TestStack.Seleno/PageObjects/Actions/PageWriter.cs +++ b/src/TestStack.Seleno/PageObjects/Actions/PageWriter.cs @@ -22,13 +22,12 @@ public PageWriter(IElementFinder elementFinder, IComponentFactory componentFacto _componentFactory = componentFactory; } - public void Model(TModel viewModel, IDictionary> propertyTypeHandling = null) + private void Input(object o, ParameterExpression parentParameter, LambdaExpression expression, IDictionary> propertyTypeHandling) { - var type = typeof(TModel); + var type = o.GetType(); foreach (var property in type.GetProperties()) { - var propertyName = property.Name; var customAttributes = property.GetCustomAttributes(false); if (customAttributes.OfType().Any()) @@ -40,64 +39,29 @@ public void Model(TModel viewModel, IDictionary> prop if (customAttributes.OfType().Any(x => x.IsReadOnly)) continue; - // iterate through sub-properties of complex type - if (!(property.PropertyType.IsValueType || - property.PropertyType.Equals(typeof(string)))) - { - ModelProperty(viewModel, property, propertyTypeHandling); + var propertyValue = property.GetValue(o, null); + if (propertyValue == null) continue; - } - var propertyValue = property.GetValue(viewModel, null); - if (propertyValue == null) + var p = Expression.Property(expression != null ? expression.Body : parentParameter, property); + var propertyExpression = Expression.Lambda(p, parentParameter); + + if (!property.PropertyType.IsValueType && property.PropertyType != typeof (string)) + { + Input(propertyValue, parentParameter, propertyExpression, propertyTypeHandling); continue; + } var stringValue = GetStringValue(propertyTypeHandling, propertyValue, property); - var textBox = _componentFactory.HtmlControlFor(propertyName); - textBox.ReplaceInputValueWith(stringValue); + _componentFactory.HtmlControlFor(propertyExpression) + .ReplaceInputValueWith(stringValue); } } - /// - /// Fill out the fields rendered by a complex property - /// - /// - /// - /// - private void ModelProperty(TModel model, PropertyInfo property, IDictionary> propertyTypeHandling = null) + public void Model(TModel viewModel, IDictionary> propertyTypeHandling = null) { - var viewType = property.PropertyType; - var viewModel = property.GetValue(model, null); - - if (viewModel == null) - { - return; - } - - foreach (var subProperty in viewType.GetProperties()) - { - var prefixedPropertyName = string.Format("{0}_{1}", property.Name, subProperty.Name); - var propertyValue = subProperty.GetValue(viewModel, null); - var customAttributes = subProperty.GetCustomAttributes(false); - - if (customAttributes.OfType().Any()) - continue; - - if (customAttributes.OfType().Any(x => !x.Scaffold)) - continue; - - if (customAttributes.OfType().Any(x => x.IsReadOnly)) - continue; - - if (propertyValue == null) - continue; - - var stringValue = GetStringValue(propertyTypeHandling, propertyValue, subProperty); - - var textBox = _componentFactory.HtmlControlFor(prefixedPropertyName); - textBox.ReplaceInputValueWith(stringValue); - } + Input(viewModel, Expression.Parameter(viewModel.GetType(), "m"), null, propertyTypeHandling); } [Obsolete("Use ReplaceInputValueWith instead")] From 67c2a552ddb8887b3a1af6b8f3c205811d3376a0 Mon Sep 17 00:00:00 2001 From: Robert Moore Date: Sat, 21 Dec 2013 17:30:28 +0800 Subject: [PATCH 6/7] Refactored the Input.Model tests so that they are more resilient (dropped the magic strings) --- .../PageWriter/When_inputting_a_model.cs | 52 +++++++++++-------- 1 file changed, 31 insertions(+), 21 deletions(-) diff --git a/src/TestStack.Seleno.Tests/PageObjects/Actions/PageWriter/When_inputting_a_model.cs b/src/TestStack.Seleno.Tests/PageObjects/Actions/PageWriter/When_inputting_a_model.cs index d7b056e9..2f11aa2d 100644 --- a/src/TestStack.Seleno.Tests/PageObjects/Actions/PageWriter/When_inputting_a_model.cs +++ b/src/TestStack.Seleno.Tests/PageObjects/Actions/PageWriter/When_inputting_a_model.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq.Expressions; using FizzWare.NBuilder; +using TestStack.Seleno.Configuration.Contracts; using TestStack.Seleno.Configuration.ControlIdGenerators; using TestStack.Seleno.PageObjects; using TestStack.Seleno.PageObjects.Actions; @@ -17,6 +18,7 @@ class When_inputting_a_model : PageWriterSpecification private const string DateFormat = "d/M/yyyy"; private TestViewModel _model; private Dictionary> _propertyHandling; + private readonly IControlIdGenerator _controlIdGenerator = new MvcControlIdGenerator(); public void AndGiven_property_handling_setup_for_datetime() { @@ -40,87 +42,95 @@ public void When_inputting_that_model_with_property_type_handling() public void Then_input_string_property() { - AssertPropertyValueSet("Name", _model.Name); + AssertPropertyValueSet(m => m.Name); } public void And_input_datetime_property_using_property_type_handling() { - AssertPropertyValueSet("Modified", _model.Modified.ToString(DateFormat)); + AssertPropertyValueSet(m => m.Modified); } public void And_ignore_hidden_input_property() { - AssertPropertyIgnored("HiddenProperty"); + AssertPropertyIgnored(m => m.HiddenProperty); } public void And_ignore_readonly_property() { - AssertPropertyIgnored("ReadonlyProperty"); + AssertPropertyIgnored(m => m.ReadonlyProperty); } public void And_input_non_readonly_property() { - AssertPropertyValueSet("NonReadonlyProperty", _model.NonReadonlyProperty); + AssertPropertyValueSet(m => m.NonReadonlyProperty); } public void And_input_scaffolded_property() { - AssertPropertyValueSet("ScaffoldedProperty", _model.ScaffoldedProperty); + AssertPropertyValueSet(m => m.ScaffoldedProperty); } public void And_ignore_non_scaffolded_property() { - AssertPropertyIgnored("NonScaffoldedProperty"); + AssertPropertyIgnored(m => m.NonScaffoldedProperty); } public void And_input_sub_view_model_string_property() { - AssertPropertyValueSet("SubViewModel_Name", _model.SubViewModel.Name); + AssertPropertyValueSet(m => m.SubViewModel.Name); } public void And_input_sub_view_model_datetime_property_using_property_type_handling() { - AssertPropertyValueSet("SubViewModel_Modified", _model.Modified.ToString(DateFormat)); + AssertPropertyValueSet(m => m.SubViewModel.Modified); } public void And_ignore_sub_view_model_hidden_input_property() { - AssertPropertyIgnored("SubViewModel_HiddenProperty"); + AssertPropertyIgnored(m => m.SubViewModel.HiddenProperty); } public void And_ignore_sub_view_model_readonly_property() { - AssertPropertyIgnored("SubViewModel_ReadonlyProperty"); + AssertPropertyIgnored(m => m.SubViewModel.ReadonlyProperty); } public void And_input_sub_view_model_non_readonly_property() { - AssertPropertyValueSet("SubViewModel_NonReadonlyProperty", _model.SubViewModel.NonReadonlyProperty); + AssertPropertyValueSet(m => m.SubViewModel.NonReadonlyProperty); } public void And_input_sub_view_model_scaffolded_property() { - AssertPropertyValueSet("SubViewModel_ScaffoldedProperty", _model.SubViewModel.ScaffoldedProperty); + AssertPropertyValueSet(m => m.SubViewModel.ScaffoldedProperty); } public void And_ignore_sub_view_model_non_scaffolded_property() { - AssertPropertyIgnored("SubViewModel_NonScaffoldedProperty"); + AssertPropertyIgnored(m => m.SubViewModel.NonScaffoldedProperty); } public void And_input_deep_nested_model_property() { - AssertPropertyValueSet("SubViewModel_SubViewModel_Name", _model.SubViewModel.SubViewModel.Name); + AssertPropertyValueSet(m => m.SubViewModel.SubViewModel.Name); } - - private void AssertPropertyIgnored(string fieldId) + + private void AssertPropertyIgnored(Expression> property) { - SubstituteFor().DidNotReceive().Script(Arg.Is(s => s.Contains("#" + fieldId))); + var expectedId = _controlIdGenerator.GetControlId(_controlIdGenerator.GetControlName(property)); + SubstituteFor().DidNotReceive().Script(Arg.Is(s => s.Contains("#" + expectedId))); } - private void AssertPropertyValueSet(string fieldId, string fieldValue) + private void AssertPropertyValueSet(Expression> property) { - SubstituteFor().Received().Script(string.Format("$('#{0}').val(\"{1}\")", fieldId, fieldValue.ToJavaScriptString())); + var expectedId = _controlIdGenerator.GetControlId(_controlIdGenerator.GetControlName(property)); + var propertyValue = property.Compile().Invoke(_model); + + var expectedValue = propertyValue.ToString(); + if (propertyValue is DateTime) + expectedValue = ((DateTime)(object)propertyValue).ToString(DateFormat); + + SubstituteFor().Received().Script(string.Format("$('#{0}').val(\"{1}\")", expectedId, expectedValue.ToJavaScriptString())); } public override void Setup() @@ -130,7 +140,7 @@ public override void Setup() { PageNavigator = SubstituteFor(), Executor = SubstituteFor(), - ControlIdGenerator = new MvcControlIdGenerator() + ControlIdGenerator = _controlIdGenerator } .Initialize(a.Arg()) ); From c2cc78146029399ca570cb3e2f2eab839d6e96d5 Mon Sep 17 00:00:00 2001 From: Robert Moore Date: Sat, 21 Dec 2013 18:13:06 +0800 Subject: [PATCH 7/7] Added Input.Field method. Fixes #133. --- .../PageObjects/Form1Page.cs | 2 +- .../PageWriter/PageWriterSpecification.cs | 64 +++++++++++++++++ .../PageWriter/When_inputting_a_field.cs | 38 ++++++++++ .../PageWriter/When_inputting_a_model.cs | 70 +------------------ .../TestStack.Seleno.Tests.csproj | 1 + .../PageObjects/Actions/PageWriter.cs | 63 ++++++++++------- 6 files changed, 145 insertions(+), 93 deletions(-) create mode 100644 src/TestStack.Seleno.Tests/PageObjects/Actions/PageWriter/When_inputting_a_field.cs diff --git a/src/TestStack.Seleno.AcceptanceTests/PageObjects/Form1Page.cs b/src/TestStack.Seleno.AcceptanceTests/PageObjects/Form1Page.cs index 64e5b8c6..065dc59b 100644 --- a/src/TestStack.Seleno.AcceptanceTests/PageObjects/Form1Page.cs +++ b/src/TestStack.Seleno.AcceptanceTests/PageObjects/Form1Page.cs @@ -15,7 +15,7 @@ public class Form1Page : Page public AssertionResultPage InputFixtureA() { Input.Model(Form1Fixtures.A); - return Navigate.To(By.CssSelector("input[type=submit]")); + return Navigate.To(By.TagName("button")); } public Form1ViewModel ReadModel() diff --git a/src/TestStack.Seleno.Tests/PageObjects/Actions/PageWriter/PageWriterSpecification.cs b/src/TestStack.Seleno.Tests/PageObjects/Actions/PageWriter/PageWriterSpecification.cs index 2e433e70..c909ce4f 100644 --- a/src/TestStack.Seleno.Tests/PageObjects/Actions/PageWriter/PageWriterSpecification.cs +++ b/src/TestStack.Seleno.Tests/PageObjects/Actions/PageWriter/PageWriterSpecification.cs @@ -1,5 +1,14 @@ using System; +using System.Collections.Generic; +using System.Linq.Expressions; +using FizzWare.NBuilder; +using NSubstitute; +using TestStack.Seleno.Configuration.Contracts; +using TestStack.Seleno.Configuration.ControlIdGenerators; +using TestStack.Seleno.Extensions; +using TestStack.Seleno.PageObjects; using TestStack.Seleno.PageObjects.Actions; +using TestStack.Seleno.PageObjects.Controls; using TestStack.Seleno.Tests.Specify; using TestStack.Seleno.Tests.TestObjects; @@ -12,4 +21,59 @@ public override Type Story get { return typeof (PageWriterSpecification); } } } + + abstract class PageWriterInputSpecification : PageWriterSpecification + { + private const string DateFormat = "d/M/yyyy"; + private readonly IControlIdGenerator _controlIdGenerator = new MvcControlIdGenerator(); + + protected TestViewModel Model; + protected Dictionary> PropertyHandling; + + public void Given_a_model() + { + Model = Builder.CreateNew() + .With(m => m.SubViewModel = Builder.CreateNew().With(mm => mm.SubViewModel = new TestViewModel { Name = "TripleNestedName" }).Build()) + .Build(); + } + + public void AndGiven_property_handling_setup_for_datetime() + { + PropertyHandling = new Dictionary> + { + {typeof(DateTime), d => ((DateTime)d).ToString(DateFormat)} + }; + } + + protected void AssertPropertyIgnored(Expression> property) + { + var expectedId = _controlIdGenerator.GetControlId(_controlIdGenerator.GetControlName(property)); + SubstituteFor().DidNotReceive().Script(Arg.Is(s => s.Contains("#" + expectedId))); + } + + protected void AssertPropertyValueSet(Expression> property) + { + var expectedId = _controlIdGenerator.GetControlId(_controlIdGenerator.GetControlName(property)); + var propertyValue = property.Compile().Invoke(Model); + + var expectedValue = propertyValue.ToString(); + if (propertyValue is DateTime) + expectedValue = ((DateTime)(object)propertyValue).ToString(DateFormat); + + SubstituteFor().Received().Script(string.Format("$('#{0}').val(\"{1}\")", expectedId, expectedValue.ToJavaScriptString())); + } + + public override void Setup() + { + SubstituteFor().HtmlControlFor(Arg.Any()) + .Returns(a => new TextBox + { + PageNavigator = SubstituteFor(), + Executor = SubstituteFor(), + ControlIdGenerator = _controlIdGenerator + } + .Initialize(a.Arg()) + ); + } + } } \ No newline at end of file diff --git a/src/TestStack.Seleno.Tests/PageObjects/Actions/PageWriter/When_inputting_a_field.cs b/src/TestStack.Seleno.Tests/PageObjects/Actions/PageWriter/When_inputting_a_field.cs new file mode 100644 index 00000000..bebb1dde --- /dev/null +++ b/src/TestStack.Seleno.Tests/PageObjects/Actions/PageWriter/When_inputting_a_field.cs @@ -0,0 +1,38 @@ +namespace TestStack.Seleno.Tests.PageObjects.Actions.PageWriter +{ + class When_inputting_a_simple_field : PageWriterInputSpecification + { + public void When_inputting_a_simple_field_with_property_type_handling() + { + SUT.Field(z => z.Modified, Model.Modified, PropertyHandling); + } + + public void Then_input_field_using_property_type_handling() + { + AssertPropertyValueSet(m => m.Modified); + } + } + + class When_inputting_a_complex_field : PageWriterInputSpecification + { + public void When_inputting_a_complex_field_value() + { + SUT.Field(z => z.SubViewModel, Model.SubViewModel); + } + + public void Then_input_nested_field() + { + AssertPropertyValueSet(m => m.SubViewModel.Name); + } + + public void And_input_deep_nested_field() + { + AssertPropertyValueSet(m => m.SubViewModel.SubViewModel.Name); + } + + public void And_ignore_subling_properties() + { + AssertPropertyIgnored(m => m.Modified); + } + } +} diff --git a/src/TestStack.Seleno.Tests/PageObjects/Actions/PageWriter/When_inputting_a_model.cs b/src/TestStack.Seleno.Tests/PageObjects/Actions/PageWriter/When_inputting_a_model.cs index 2f11aa2d..1a185bf7 100644 --- a/src/TestStack.Seleno.Tests/PageObjects/Actions/PageWriter/When_inputting_a_model.cs +++ b/src/TestStack.Seleno.Tests/PageObjects/Actions/PageWriter/When_inputting_a_model.cs @@ -1,43 +1,10 @@ -using System; -using System.Collections.Generic; -using System.Linq.Expressions; -using FizzWare.NBuilder; -using TestStack.Seleno.Configuration.Contracts; -using TestStack.Seleno.Configuration.ControlIdGenerators; -using TestStack.Seleno.PageObjects; -using TestStack.Seleno.PageObjects.Actions; -using TestStack.Seleno.PageObjects.Controls; -using TestStack.Seleno.Tests.TestObjects; -using NSubstitute; -using TestStack.Seleno.Extensions; - -namespace TestStack.Seleno.Tests.PageObjects.Actions.PageWriter +namespace TestStack.Seleno.Tests.PageObjects.Actions.PageWriter { - class When_inputting_a_model : PageWriterSpecification + class When_inputting_a_model : PageWriterInputSpecification { - private const string DateFormat = "d/M/yyyy"; - private TestViewModel _model; - private Dictionary> _propertyHandling; - private readonly IControlIdGenerator _controlIdGenerator = new MvcControlIdGenerator(); - - public void AndGiven_property_handling_setup_for_datetime() - { - _propertyHandling = new Dictionary> - { - {typeof(DateTime), d => ((DateTime)d).ToString(DateFormat)} - }; - } - - public void Given_a_model() - { - _model = Builder.CreateNew() - .With(m => m.SubViewModel = Builder.CreateNew().With(mm => mm.SubViewModel = new TestViewModel{Name = "TripleNestedName"}).Build()) - .Build(); - } - public void When_inputting_that_model_with_property_type_handling() { - SUT.Model(_model, _propertyHandling); + SUT.Model(Model, PropertyHandling); } public void Then_input_string_property() @@ -114,36 +81,5 @@ public void And_input_deep_nested_model_property() { AssertPropertyValueSet(m => m.SubViewModel.SubViewModel.Name); } - - private void AssertPropertyIgnored(Expression> property) - { - var expectedId = _controlIdGenerator.GetControlId(_controlIdGenerator.GetControlName(property)); - SubstituteFor().DidNotReceive().Script(Arg.Is(s => s.Contains("#" + expectedId))); - } - - private void AssertPropertyValueSet(Expression> property) - { - var expectedId = _controlIdGenerator.GetControlId(_controlIdGenerator.GetControlName(property)); - var propertyValue = property.Compile().Invoke(_model); - - var expectedValue = propertyValue.ToString(); - if (propertyValue is DateTime) - expectedValue = ((DateTime)(object)propertyValue).ToString(DateFormat); - - SubstituteFor().Received().Script(string.Format("$('#{0}').val(\"{1}\")", expectedId, expectedValue.ToJavaScriptString())); - } - - public override void Setup() - { - SubstituteFor().HtmlControlFor(Arg.Any()) - .Returns(a => new TextBox - { - PageNavigator = SubstituteFor(), - Executor = SubstituteFor(), - ControlIdGenerator = _controlIdGenerator - } - .Initialize(a.Arg()) - ); - } } } diff --git a/src/TestStack.Seleno.Tests/TestStack.Seleno.Tests.csproj b/src/TestStack.Seleno.Tests/TestStack.Seleno.Tests.csproj index 88904b26..e52e9f44 100644 --- a/src/TestStack.Seleno.Tests/TestStack.Seleno.Tests.csproj +++ b/src/TestStack.Seleno.Tests/TestStack.Seleno.Tests.csproj @@ -146,6 +146,7 @@ + diff --git a/src/TestStack.Seleno/PageObjects/Actions/PageWriter.cs b/src/TestStack.Seleno/PageObjects/Actions/PageWriter.cs index c3f4c248..9387d9cb 100644 --- a/src/TestStack.Seleno/PageObjects/Actions/PageWriter.cs +++ b/src/TestStack.Seleno/PageObjects/Actions/PageWriter.cs @@ -27,36 +27,40 @@ private void Input(object o, ParameterExpression parentParameter, LambdaExpressi var type = o.GetType(); foreach (var property in type.GetProperties()) - { - var customAttributes = property.GetCustomAttributes(false); - - if (customAttributes.OfType().Any()) - continue; + InputProperty(o, parentParameter, expression, propertyTypeHandling, property); + } - if (customAttributes.OfType().Any(x => !x.Scaffold)) - continue; + private void InputProperty(object o, ParameterExpression parentParameter, LambdaExpression expression, + IDictionary> propertyTypeHandling, PropertyInfo property) + { + var customAttributes = property.GetCustomAttributes(false); - if (customAttributes.OfType().Any(x => x.IsReadOnly)) - continue; + if (customAttributes.OfType().Any()) + return; - var propertyValue = property.GetValue(o, null); - if (propertyValue == null) - continue; + if (customAttributes.OfType().Any(x => !x.Scaffold)) + return; - var p = Expression.Property(expression != null ? expression.Body : parentParameter, property); - var propertyExpression = Expression.Lambda(p, parentParameter); + if (customAttributes.OfType().Any(x => x.IsReadOnly)) + return; - if (!property.PropertyType.IsValueType && property.PropertyType != typeof (string)) - { - Input(propertyValue, parentParameter, propertyExpression, propertyTypeHandling); - continue; - } + var propertyValue = property.GetValue(o, null); + if (propertyValue == null) + return; - var stringValue = GetStringValue(propertyTypeHandling, propertyValue, property); + var p = Expression.Property(expression != null ? expression.Body : parentParameter, property); + var propertyExpression = Expression.Lambda(p, parentParameter); - _componentFactory.HtmlControlFor(propertyExpression) - .ReplaceInputValueWith(stringValue); + if (!property.PropertyType.IsValueType && property.PropertyType != typeof (string)) + { + Input(propertyValue, parentParameter, propertyExpression, propertyTypeHandling); + return; } + + var stringValue = GetStringValue(propertyTypeHandling, propertyValue, property.PropertyType); + + _componentFactory.HtmlControlFor(propertyExpression) + .ReplaceInputValueWith(stringValue); } public void Model(TModel viewModel, IDictionary> propertyTypeHandling = null) @@ -64,19 +68,28 @@ public void Model(TModel viewModel, IDictionary> prop Input(viewModel, Expression.Parameter(viewModel.GetType(), "m"), null, propertyTypeHandling); } + public void Field(Expression> field, T value, IDictionary> propertyTypeHandling = null) + { + var param = field.Parameters.First(); + if (!typeof(T).IsValueType && typeof(T) != typeof(string)) + Input(value, param, field, propertyTypeHandling); + + var stringValue = GetStringValue(propertyTypeHandling, value, typeof(T)); + _componentFactory.HtmlControlFor(field) + .ReplaceInputValueWith(stringValue); + } + [Obsolete("Use ReplaceInputValueWith instead")] public void TextInField(string fieldName, string value) { ReplaceInputValueWith(fieldName,value); } - protected string GetStringValue(IDictionary> propertyTypeHandling, object propertyValue, PropertyInfo property) + protected string GetStringValue(IDictionary> propertyTypeHandling, object propertyValue, Type propertyType) { if (propertyTypeHandling == null) return propertyValue.ToString(); - var propertyType = property.PropertyType; - if (propertyTypeHandling.ContainsKey(propertyType)) { var handler = propertyTypeHandling[propertyType];