mvp refactor of the edit-portfolio activity, also half-way refactoring the Validation framework stuff

This commit is contained in:
mgroves 2011-03-16 23:05:49 -04:00
parent f6d7c71188
commit 9a0d711294
13 changed files with 353 additions and 55 deletions

View file

@ -31,6 +31,8 @@
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<AndroidUseSharedRuntime>True</AndroidUseSharedRuntime>
<AndroidLinkMode>SdkOnly</AndroidLinkMode>
</PropertyGroup>
<ItemGroup>
<Reference Include="Mono.Android" />

View file

@ -0,0 +1,28 @@
using System.Collections.Generic;
using System.Linq;
using Machine.Specifications;
using MonoStockPortfolio.Activites.EditPortfolioScreen;
using MonoStockPortfolio.Framework;
namespace MonoStockPortfolio.Tests.Framework
{
public class ValidationTests
{
static NewFormValidator _validator;
static IEnumerable<string> _errors;
Establish context = () =>
{
_validator = new NewFormValidator();
};
Because of = () =>
{
_validator.AddRequired(() => "", "This is required");
_errors = _validator.Apply();
};
It should_return_1_error_message = () =>
_errors.Count().ShouldEqual(1);
}
}

View file

@ -62,6 +62,8 @@
</Reference>
</ItemGroup>
<ItemGroup>
<Compile Include="Framework\ValidationTests.cs" />
<Compile Include="Presenters\EditPortfolioTests.cs" />
<Compile Include="Presenters\MainPresenterTests.cs" />
<Compile Include="Presenters\PortfolioPresenterTests.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />

View file

@ -0,0 +1,112 @@
using System.Collections.Generic;
using System.Linq;
using Machine.Specifications;
using MonoStockPortfolio.Activites.EditPortfolioScreen;
using MonoStockPortfolio.Core.PortfolioRepositories;
using MonoStockPortfolio.Entities;
using Telerik.JustMock;
namespace MonoStockPortfolio.Tests.Presenters
{
public class EditPortfolioTests
{
protected static EditPortfolioPresenter _presenter;
protected static IPortfolioRepository _mockPortfolioRepository;
protected static IEditPortfolioView _mockEditPortfolioView;
Establish context = () =>
{
_mockPortfolioRepository = Mock.Create<IPortfolioRepository>();
Mock.Arrange(() => _mockPortfolioRepository.GetPortfolioById(999)).Returns(
new Portfolio(999) { Name = "Testing Portfolio!" });
_mockEditPortfolioView = Mock.Create<IEditPortfolioView>();
_presenter = new EditPortfolioPresenter(_mockPortfolioRepository);
};
}
public class When_initializing_the_edit_portfolio_presenter_with_no_id : EditPortfolioTests
{
Because of = () =>
{
_presenter.Initialize(_mockEditPortfolioView, null);
};
It should_set_the_title_to_Add_New_Portfolio = () =>
Mock.Assert(() => _mockEditPortfolioView.SetTitle("Add New Portfolio"), Occurs.Exactly(1));
It shouldnt_prepopulate_the_form_with_anything = () =>
Mock.Assert(() => _mockEditPortfolioView.PopulateForm(Arg.IsAny<Portfolio>()), Occurs.Never());
}
public class When_initializing_the_edit_portfolio_presenter_with_an_id : EditPortfolioTests
{
Because of = () =>
{
_presenter.Initialize(_mockEditPortfolioView, 999);
};
It should_set_the_title_to_Edit_Portfolio = () =>
Mock.Assert(() => _mockEditPortfolioView.SetTitle("Edit Portfolio"), Occurs.Exactly(1));
It should_prepopulate_the_form_with_a_portfolio_name = () =>
Mock.Assert(() => _mockEditPortfolioView.PopulateForm(Arg.Matches<Portfolio>(x => x.Name == "Testing Portfolio!")), Occurs.Exactly(1));
}
public class When_the_user_wants_to_save_a_valid_portfolio : EditPortfolioTests
{
Establish context = () =>
{
_presenter.Initialize(_mockEditPortfolioView, null);
};
Because of = () =>
{
_presenter.SavePortfolio(new Portfolio(999) { Name = "Whatever Portfolio" });
};
It should_use_the_repository_to_save_the_portfolio = () =>
Mock.Assert(() => _mockPortfolioRepository.SavePortfolio(Arg.Matches<Portfolio>(x => x.ID == 999 && x.Name == "Whatever Portfolio")), Occurs.Exactly(1));
It should_tell_the_view_to_show_a_nice_saved_message = () =>
Mock.Assert(() => _mockEditPortfolioView.ShowSaveSuccessMessage("You saved: Whatever Portfolio"), Occurs.Exactly(1));
It should_tell_the_view_to_go_back_to_the_main_activity = () =>
Mock.Assert(() => _mockEditPortfolioView.GoBackToMainActivity(), Occurs.Exactly(1));
}
public class When_the_user_tries_to_save_a_new_portfolio_with_a_blank_name : EditPortfolioTests
{
Establish context = () =>
{
_presenter.Initialize(_mockEditPortfolioView);
};
Because of = () =>
{
_presenter.SavePortfolio(new Portfolio { Name = "" });
};
It should_return_1_validation_error = () =>
Mock.Assert(() => _mockEditPortfolioView.ShowValidationErrors(Arg.Matches<IEnumerable<string>>(x => x.Count() == 1)), Occurs.Exactly(1));
It should_return_a_nice_required_validation_error_message = () =>
Mock.Assert(() => _mockEditPortfolioView.ShowValidationErrors(Arg.Matches<IEnumerable<string>>(x => x.Single() == "Please enter a portfolio name")), Occurs.Exactly(1));
}
public class When_the_user_tries_to_save_a_portfolio_with_a_duplicated_name : EditPortfolioTests
{
Establish context = () =>
{
_presenter.Initialize(_mockEditPortfolioView);
};
Because of = () =>
{
Mock.Arrange(() => _mockPortfolioRepository.GetPortfolioByName(Arg.AnyString)).Returns(
new Portfolio(998) {Name = "Some Name"});
_presenter.SavePortfolio(new Portfolio { Name = "Some Name" });
};
It should_return_1_validation_error = () =>
Mock.Assert(() => _mockEditPortfolioView.ShowValidationErrors(Arg.Matches<IEnumerable<string>>(x => x.Count() == 1)), Occurs.Exactly(1));
It should_return_a_nice_duplication_error_message = () =>
Mock.Assert(() => _mockEditPortfolioView.ShowValidationErrors(Arg.Matches<IEnumerable<string>>(x => x.Single() == "Portfolio name is already taken")), Occurs.Exactly(1));
}
}

View file

@ -1,18 +1,18 @@
using System;
using System.Collections.Generic;
using Android.App;
using Android.Content;
using Android.OS;
using Android.Widget;
using MonoStockPortfolio.Core.PortfolioRepositories;
using MonoStockPortfolio.Entities;
using MonoStockPortfolio.Framework;
namespace MonoStockPortfolio.Activites
namespace MonoStockPortfolio.Activites.EditPortfolioScreen
{
[Activity(Label = "Add Portfolio", MainLauncher = false, Name = "monostockportfolio.activites.EditPortfolioActivity")]
public class EditPortfolioActivity : Activity
public class EditPortfolioActivity : Activity, IEditPortfolioView
{
[IoC] private IPortfolioRepository _repo;
[IoC] private IEditPortfolioPresenter _presenter;
[LazyView(Resource.Id.btnSave)] protected Button SaveButton;
[LazyView(Resource.Id.portfolioName)] protected EditText PortfolioName;
@ -32,7 +32,41 @@ namespace MonoStockPortfolio.Activites
intent.PutExtra(PORTFOLIOIDEXTRA, portfolioId);
return intent;
}
public long ThisPortfolioId { get { return Intent.GetLongExtra(PORTFOLIOIDEXTRA, -1); } }
#region IEditPortfolioView members
public void SetTitle(string title)
{
this.Title = title;
}
public void PopulateForm(Portfolio portfolio)
{
PortfolioName.Text = portfolio.Name;
}
public void ShowSaveSuccessMessage(string message)
{
this.LongToast(message);
}
public void GoBackToMainActivity()
{
this.EndActivity();
}
public void ShowValidationErrors(IEnumerable<string> errors)
{
var errorMessage = string.Empty;
foreach (var error in errors)
{
errorMessage += error + "\n";
}
errorMessage = errorMessage.Trim('\n');
this.LongToast(errorMessage);
}
#endregion
protected override void OnCreate(Bundle bundle)
{
@ -40,20 +74,17 @@ namespace MonoStockPortfolio.Activites
SetContentView(Resource.Layout.addportfolio);
WireUpEvents();
var portfolioId = ThisPortfolioId;
if(portfolioId != -1)
var portfolioId = Intent.GetLongExtra(PORTFOLIOIDEXTRA, -1);
if (portfolioId != -1)
{
this.Title = "Edit Portfolio";
PopulateForm(portfolioId);
_presenter.Initialize(this, portfolioId);
}
else
{
_presenter.Initialize(this);
}
}
private void PopulateForm(long portfolioId)
{
var portfolio = _repo.GetPortfolioById(portfolioId);
PortfolioName.Text = portfolio.Name;
WireUpEvents();
}
private void WireUpEvents()
@ -63,47 +94,13 @@ namespace MonoStockPortfolio.Activites
private void saveButton_Click(object sender, EventArgs e)
{
Portfolio portfolioToSave = GetPortfolioToSave();
if (Validate(portfolioToSave))
{
_repo.SavePortfolio(portfolioToSave);
this.LongToast("You saved: " + PortfolioName.Text);
this.EndActivity();
}
}
private bool Validate(Portfolio portfolioToSave)
{
var validator = new FormValidator();
validator.AddRequired(PortfolioName, "Please enter a portfolio name");
validator.AddValidation(PortfolioName, () => IsDuplicateName(portfolioToSave));
var result = validator.Apply();
if(result != string.Empty)
{
this.LongToast(result);
return false;
}
return true;
}
private string IsDuplicateName(Portfolio portfolioToSave)
{
var portfolio = _repo.GetPortfolioByName(portfolioToSave.Name);
if(portfolio != null && portfolio.ID != portfolioToSave.ID)
{
return "Portfolio name is already taken";
}
return string.Empty;
_presenter.SavePortfolio(GetPortfolioToSave());
}
private Portfolio GetPortfolioToSave()
{
Portfolio portfolioToSave;
var portfolioId = ThisPortfolioId;
var portfolioId = Intent.GetLongExtra(PORTFOLIOIDEXTRA, -1);
if (portfolioId != -1)
{
portfolioToSave = new Portfolio(portfolioId);
@ -112,7 +109,7 @@ namespace MonoStockPortfolio.Activites
{
portfolioToSave = new Portfolio();
}
portfolioToSave.Name = PortfolioName.Text.ToString();
portfolioToSave.Name = PortfolioName.Text;
return portfolioToSave;
}
}

View file

@ -0,0 +1,80 @@
using System.Collections.Generic;
using System.Linq;
using MonoStockPortfolio.Core.PortfolioRepositories;
using MonoStockPortfolio.Entities;
using MonoStockPortfolio.Framework;
namespace MonoStockPortfolio.Activites.EditPortfolioScreen
{
public class EditPortfolioPresenter : IEditPortfolioPresenter
{
private IEditPortfolioView _currentView;
private long? _portfolioId;
private readonly IPortfolioRepository _porfolioRepository;
public EditPortfolioPresenter(IPortfolioRepository portfolioRepository)
{
_porfolioRepository = portfolioRepository;
}
public void Initialize(IEditPortfolioView editPortfolioView, long? portfolioId = null)
{
_portfolioId = portfolioId;
_currentView = editPortfolioView;
SetTitle();
PrepopulateForm();
}
public void SavePortfolio(Portfolio portfolioToSave)
{
var errors = Validate((portfolioToSave));
if (!errors.Any())
{
_porfolioRepository.SavePortfolio(portfolioToSave);
_currentView.ShowSaveSuccessMessage("You saved: " + portfolioToSave.Name);
_currentView.GoBackToMainActivity();
}
else
{
_currentView.ShowValidationErrors(errors);
}
}
private IEnumerable<string> Validate(Portfolio portfolioToSave)
{
var validator = new NewFormValidator();
validator.AddRequired(() => portfolioToSave.Name, "Please enter a portfolio name");
validator.AddValidation(() => portfolioToSave.Name, () => IsDuplicateName(portfolioToSave));
return validator.Apply().ToList();
}
private string IsDuplicateName(Portfolio portfolioToSave)
{
var portfolio = _porfolioRepository.GetPortfolioByName(portfolioToSave.Name);
if (portfolio != null && portfolio.ID != portfolioToSave.ID)
{
return "Portfolio name is already taken";
}
return string.Empty;
}
private void PrepopulateForm()
{
if (_portfolioId != null)
{
var portfolio = _porfolioRepository.GetPortfolioById(_portfolioId ?? -1);
_currentView.PopulateForm(portfolio);
}
}
private void SetTitle()
{
_currentView.SetTitle(_portfolioId == null ? "Add New Portfolio" : "Edit Portfolio");
}
}
}

View file

@ -0,0 +1,10 @@
using MonoStockPortfolio.Entities;
namespace MonoStockPortfolio.Activites.EditPortfolioScreen
{
public interface IEditPortfolioPresenter
{
void Initialize(IEditPortfolioView editPortfolioView, long? portfolioId = null);
void SavePortfolio(Portfolio portfolioToSave);
}
}

View file

@ -0,0 +1,14 @@
using System.Collections.Generic;
using MonoStockPortfolio.Entities;
namespace MonoStockPortfolio.Activites.EditPortfolioScreen
{
public interface IEditPortfolioView
{
void SetTitle(string title);
void PopulateForm(Portfolio portfolio);
void ShowSaveSuccessMessage(string message);
void GoBackToMainActivity();
void ShowValidationErrors(IEnumerable<string> errors);
}
}

View file

@ -5,6 +5,7 @@ using Android.Content;
using Android.OS;
using Android.Views;
using Android.Widget;
using MonoStockPortfolio.Activites.EditPortfolioScreen;
using MonoStockPortfolio.Activites.PortfolioScreen;
using MonoStockPortfolio.Framework;

View file

@ -1,4 +1,3 @@
using System.Reflection;
using Android.App;
using Android.Content;
using Android.Widget;

View file

@ -0,0 +1,45 @@
using System;
using System.Collections.Generic;
using System.Linq;
namespace MonoStockPortfolio.Framework
{
public class NewFormValidator
{
private readonly IList<Func<string>> _list;
public NewFormValidator()
{
_list = new List<Func<string>>();
}
public void AddValidation(Func<string> getValue, Func<string> validationFunction)
{
_list.Add(validationFunction);
}
public void AddRequired(Func<string> getValue, string errorMessage)
{
AddValidation(getValue, () => Required(getValue(), errorMessage));
}
public IEnumerable<string> Apply()
{
return _list.Select(validation => validation())
.Where(result => !string.IsNullOrEmpty(result));
}
#region validation functions
private static string Required(string getValue, string errorMessage)
{
if (string.IsNullOrEmpty(getValue))
{
return errorMessage;
}
return string.Empty;
}
#endregion
}
}

View file

@ -1,6 +1,7 @@
using System;
using Android.Content;
using Android.Util;
using MonoStockPortfolio.Activites.EditPortfolioScreen;
using MonoStockPortfolio.Activites.MainScreen;
using MonoStockPortfolio.Activites.PortfolioScreen;
using MonoStockPortfolio.Core.Config;
@ -26,6 +27,7 @@ namespace MonoStockPortfolio.Framework
// presenters
IttyBittyIoC.Register<IMainPresenter, MainPresenter>();
IttyBittyIoC.Register<IPortfolioPresenter, PortfolioPresenter>();
IttyBittyIoC.Register<IEditPortfolioPresenter, EditPortfolioPresenter>();
}
public static object Get(Type serviceType)

View file

@ -38,6 +38,8 @@
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<AndroidUseSharedRuntime>True</AndroidUseSharedRuntime>
<AndroidLinkMode>SdkOnly</AndroidLinkMode>
</PropertyGroup>
<ItemGroup>
<Reference Include="Mono.Android" />
@ -53,7 +55,11 @@
</ItemGroup>
<ItemGroup>
<Compile Include="Activites\ConfigActivity.cs" />
<Compile Include="Activites\EditPortfolioActivity.cs" />
<Compile Include="Activites\EditPortfolioScreen\EditPortfolioActivity.cs" />
<Compile Include="Activites\EditPortfolioScreen\EditPortfolioPresenter.cs" />
<Compile Include="Activites\EditPortfolioScreen\IEditPortfolioPresenter.cs" />
<Compile Include="Activites\EditPortfolioScreen\IEditPortfolioView.cs" />
<Compile Include="Framework\NewFormValidator.cs" />
<Compile Include="Activites\MainScreen\IMainPresenter.cs" />
<Compile Include="Activites\MainScreen\IMainView.cs" />
<Compile Include="Activites\MainScreen\MainPresenter.cs" />