Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ public class Form1Page : Page<Form1ViewModel>
public AssertionResultPage InputFixtureA()
{
Input.Model(Form1Fixtures.A);
return Navigate.To<AssertionResultPage>(By.CssSelector("input[type=submit]"));
return Navigate.To<AssertionResultPage>(By.TagName("button"));
}

public Form1ViewModel ReadModel()
Expand Down
Original file line number Diff line number Diff line change
@@ -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;

Expand All @@ -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<Type, Func<object, string>> PropertyHandling;

public void Given_a_model()
{
Model = Builder<TestViewModel>.CreateNew()
.With(m => m.SubViewModel = Builder<TestViewModel>.CreateNew().With(mm => mm.SubViewModel = new TestViewModel { Name = "TripleNestedName" }).Build())
.Build();
}

public void AndGiven_property_handling_setup_for_datetime()
{
PropertyHandling = new Dictionary<Type, Func<object, string>>
{
{typeof(DateTime), d => ((DateTime)d).ToString(DateFormat)}
};
}

protected void AssertPropertyIgnored<TProperty>(Expression<Func<TestViewModel, TProperty>> property)
{
var expectedId = _controlIdGenerator.GetControlId(_controlIdGenerator.GetControlName(property));
SubstituteFor<IExecutor>().DidNotReceive().Script(Arg.Is<string>(s => s.Contains("#" + expectedId)));
}

protected void AssertPropertyValueSet<TProperty>(Expression<Func<TestViewModel, TProperty>> 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<IExecutor>().Received().Script(string.Format("$('#{0}').val(\"{1}\")", expectedId, expectedValue.ToJavaScriptString()));
}

public override void Setup()
{
SubstituteFor<IComponentFactory>().HtmlControlFor<TextBox>(Arg.Any<LambdaExpression>())
.Returns(a => new TextBox
{
PageNavigator = SubstituteFor<IPageNavigator>(),
Executor = SubstituteFor<IExecutor>(),
ControlIdGenerator = _controlIdGenerator
}
.Initialize(a.Arg<LambdaExpression>())
);
}
}
}
Original file line number Diff line number Diff line change
@@ -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);
}
}
}
Original file line number Diff line number Diff line change
@@ -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);
}
}
}
14 changes: 14 additions & 0 deletions src/TestStack.Seleno.Tests/TestObjects/TestViewModel.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
using System;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.Web.Mvc;

namespace TestStack.Seleno.Tests.TestObjects
{
Expand All @@ -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
Expand Down
6 changes: 6 additions & 0 deletions src/TestStack.Seleno.Tests/TestStack.Seleno.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,9 @@
<SpecificVersion>False</SpecificVersion>
<HintPath>..\packages\Castle.Core.3.2.0\lib\net40-client\Castle.Core.dll</HintPath>
</Reference>
<Reference Include="FizzWare.NBuilder">
<HintPath>..\packages\NBuilder.3.0.1.1\lib\FizzWare.NBuilder.dll</HintPath>
</Reference>
<Reference Include="FluentAssertions, Version=2.0.1.0, Culture=neutral, PublicKeyToken=33f2691a05b67b6a, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\packages\FluentAssertions.2.0.1\lib\net40\FluentAssertions.dll</HintPath>
Expand All @@ -67,6 +70,7 @@
<HintPath>..\packages\NUnit.2.6.2\lib\nunit.framework.dll</HintPath>
</Reference>
<Reference Include="System" />
<Reference Include="System.ComponentModel.DataAnnotations" />
<Reference Include="System.Core" />
<Reference Include="System.Drawing" />
<Reference Include="System.Web.Mvc, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL" />
Expand Down Expand Up @@ -142,6 +146,8 @@
<Compile Include="PageObjects\Actions\PageReader\When_getting_a_strongly_typed_textBox_value.cs" />
<Compile Include="PageObjects\Actions\PageReader\When_getting_a_web_element_strongly_typed_text.cs" />
<Compile Include="PageObjects\Actions\PageReader\When_getting_strongly_typed_attribute_value.cs" />
<Compile Include="PageObjects\Actions\PageWriter\When_inputting_a_field.cs" />
<Compile Include="PageObjects\Actions\PageWriter\When_inputting_a_model.cs" />
<Compile Include="PageObjects\Actions\PageWriter\When_updating_combined_content_as_a_single_string_with_return_separator_of_a_textarea.cs" />
<Compile Include="PageObjects\Actions\PageWriter\When_updating_multiline_content_of_a_textarea.cs" />
<Compile Include="PageObjects\Actions\PageWriter\When_only_sending_keys_to_web_element.cs" />
Expand Down
1 change: 1 addition & 0 deletions src/TestStack.Seleno.Tests/packages.config
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
<package id="Castle.Core" version="3.2.0" targetFramework="net40" />
<package id="FluentAssertions" version="2.0.1" targetFramework="net40" />
<package id="Humanizer" version="1.0" targetFramework="net40" />
<package id="NBuilder" version="3.0.1.1" targetFramework="net40" />
<package id="NSubstitute" version="1.6.1.0" targetFramework="net40" />
<package id="NUnit" version="2.6.2" targetFramework="net40" />
<package id="Selenium.WebDriver" version="2.37.0" targetFramework="net40" />
Expand Down
65 changes: 48 additions & 17 deletions src/TestStack.Seleno/PageObjects/Actions/PageWriter.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Linq.Expressions;
Expand All @@ -21,29 +22,61 @@ public PageWriter(IElementFinder elementFinder, IComponentFactory componentFacto
_componentFactory = componentFactory;
}

public void Model(TModel viewModel, IDictionary<Type, Func<object, string>> propertyTypeHandling = null)
private void Input(object o, ParameterExpression parentParameter, LambdaExpression expression, IDictionary<Type, Func<object, string>> 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<Type, Func<object, string>> propertyTypeHandling, PropertyInfo property)
{
var customAttributes = property.GetCustomAttributes(false);

if (property.GetCustomAttributes(typeof(HiddenInputAttribute), false).Length > 0)
continue;
if (customAttributes.OfType<HiddenInputAttribute>().Any())
return;

if (property.GetCustomAttributes(typeof(ScaffoldColumnAttribute), false).Length > 0)
continue;
if (customAttributes.OfType<ScaffoldColumnAttribute>().Any(x => !x.Scaffold))
return;

if (propertyValue == null)
continue;
if (customAttributes.OfType<ReadOnlyAttribute>().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<TextBox>(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<TextBox>(propertyExpression)
.ReplaceInputValueWith(stringValue);
}

public void Model(TModel viewModel, IDictionary<Type, Func<object, string>> propertyTypeHandling = null)
{
Input(viewModel, Expression.Parameter(viewModel.GetType(), "m"), null, propertyTypeHandling);
}

public void Field<T>(Expression<Func<TModel, T>> field, T value, IDictionary<Type, Func<object, string>> 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<TextBox>(field)
.ReplaceInputValueWith(stringValue);
}

[Obsolete("Use ReplaceInputValueWith instead")]
Expand All @@ -52,13 +85,11 @@ public void TextInField(string fieldName, string value)
ReplaceInputValueWith(fieldName,value);
}

protected string GetStringValue(IDictionary<Type, Func<object, string>> propertyTypeHandling, object propertyValue, PropertyInfo property)
protected string GetStringValue(IDictionary<Type, Func<object, string>> propertyTypeHandling, object propertyValue, Type propertyType)
{
if (propertyTypeHandling == null)
return propertyValue.ToString();

var propertyType = property.PropertyType;

if (propertyTypeHandling.ContainsKey(propertyType))
{
var handler = propertyTypeHandling[propertyType];
Expand Down