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 new file mode 100644 index 00000000..1a185bf7 --- /dev/null +++ b/src/TestStack.Seleno.Tests/PageObjects/Actions/PageWriter/When_inputting_a_model.cs @@ -0,0 +1,85 @@ +namespace TestStack.Seleno.Tests.PageObjects.Actions.PageWriter +{ + class When_inputting_a_model : PageWriterInputSpecification + { + public void When_inputting_that_model_with_property_type_handling() + { + SUT.Model(Model, PropertyHandling); + } + + public void Then_input_string_property() + { + AssertPropertyValueSet(m => m.Name); + } + + public void And_input_datetime_property_using_property_type_handling() + { + AssertPropertyValueSet(m => m.Modified); + } + + public void And_ignore_hidden_input_property() + { + AssertPropertyIgnored(m => m.HiddenProperty); + } + + public void And_ignore_readonly_property() + { + AssertPropertyIgnored(m => m.ReadonlyProperty); + } + + public void And_input_non_readonly_property() + { + AssertPropertyValueSet(m => m.NonReadonlyProperty); + } + + public void And_input_scaffolded_property() + { + AssertPropertyValueSet(m => m.ScaffoldedProperty); + } + + public void And_ignore_non_scaffolded_property() + { + AssertPropertyIgnored(m => m.NonScaffoldedProperty); + } + + public void And_input_sub_view_model_string_property() + { + AssertPropertyValueSet(m => m.SubViewModel.Name); + } + + public void And_input_sub_view_model_datetime_property_using_property_type_handling() + { + AssertPropertyValueSet(m => m.SubViewModel.Modified); + } + + public void And_ignore_sub_view_model_hidden_input_property() + { + AssertPropertyIgnored(m => m.SubViewModel.HiddenProperty); + } + + public void And_ignore_sub_view_model_readonly_property() + { + AssertPropertyIgnored(m => m.SubViewModel.ReadonlyProperty); + } + + public void And_input_sub_view_model_non_readonly_property() + { + AssertPropertyValueSet(m => m.SubViewModel.NonReadonlyProperty); + } + + public void And_input_sub_view_model_scaffolded_property() + { + AssertPropertyValueSet(m => m.SubViewModel.ScaffoldedProperty); + } + + public void And_ignore_sub_view_model_non_scaffolded_property() + { + AssertPropertyIgnored(m => m.SubViewModel.NonScaffoldedProperty); + } + + public void And_input_deep_nested_model_property() + { + AssertPropertyValueSet(m => m.SubViewModel.SubViewModel.Name); + } + } +} 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..e52e9f44 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,8 @@ + + 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 8d922234..9387d9cb 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; @@ -21,29 +22,61 @@ 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 propertyValue = property.GetValue(viewModel, null); + InputProperty(o, parentParameter, expression, propertyTypeHandling, property); + } + + private void InputProperty(object o, ParameterExpression parentParameter, LambdaExpression expression, + IDictionary> propertyTypeHandling, PropertyInfo property) + { + var customAttributes = property.GetCustomAttributes(false); - if (property.GetCustomAttributes(typeof(HiddenInputAttribute), false).Length > 0) - continue; + if (customAttributes.OfType().Any()) + return; - if (property.GetCustomAttributes(typeof(ScaffoldColumnAttribute), false).Length > 0) - continue; + if (customAttributes.OfType().Any(x => !x.Scaffold)) + return; - if (propertyValue == null) - continue; + if (customAttributes.OfType().Any(x => x.IsReadOnly)) + return; - var stringValue = GetStringValue(propertyTypeHandling, propertyValue, property); + var propertyValue = property.GetValue(o, null); + if (propertyValue == null) + return; - var textBox = _componentFactory.HtmlControlFor(propertyName); - textBox.ReplaceInputValueWith(stringValue); + 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); + return; } + + var stringValue = GetStringValue(propertyTypeHandling, propertyValue, property.PropertyType); + + _componentFactory.HtmlControlFor(propertyExpression) + .ReplaceInputValueWith(stringValue); + } + + public void Model(TModel viewModel, IDictionary> propertyTypeHandling = null) + { + 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")] @@ -52,13 +85,11 @@ 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];