From 9a0d71129407ce80bee613a7a49dadaac14acd86 Mon Sep 17 00:00:00 2001 From: mgroves Date: Wed, 16 Mar 2011 23:05:49 -0400 Subject: [PATCH] mvp refactor of the edit-portfolio activity, also half-way refactoring the Validation framework stuff --- .../MonoStockPortfolio.Core.csproj | 2 + .../Framework/ValidationTests.cs | 28 +++++ .../MonoStockPortfolio.Tests.csproj | 2 + .../Presenters/EditPortfolioTests.cs | 112 ++++++++++++++++++ .../EditPortfolioActivity.cs | 103 ++++++++-------- .../EditPortfolioPresenter.cs | 80 +++++++++++++ .../IEditPortfolioPresenter.cs | 10 ++ .../EditPortfolioScreen/IEditPortfolioView.cs | 14 +++ .../Activites/MainScreen/MainActivity.cs | 1 + .../Framework/ActivityExtensions.cs | 1 - .../Framework/NewFormValidator.cs | 45 +++++++ .../Framework/ServiceLocator.cs | 2 + MonoStockPortfolio/MonoStockPortfolio.csproj | 8 +- 13 files changed, 353 insertions(+), 55 deletions(-) create mode 100644 MonoStockPortfolio.Tests/Framework/ValidationTests.cs create mode 100644 MonoStockPortfolio.Tests/Presenters/EditPortfolioTests.cs rename MonoStockPortfolio/Activites/{ => EditPortfolioScreen}/EditPortfolioActivity.cs (52%) create mode 100644 MonoStockPortfolio/Activites/EditPortfolioScreen/EditPortfolioPresenter.cs create mode 100644 MonoStockPortfolio/Activites/EditPortfolioScreen/IEditPortfolioPresenter.cs create mode 100644 MonoStockPortfolio/Activites/EditPortfolioScreen/IEditPortfolioView.cs create mode 100644 MonoStockPortfolio/Framework/NewFormValidator.cs diff --git a/MonoStockPortfolio.Core/MonoStockPortfolio.Core.csproj b/MonoStockPortfolio.Core/MonoStockPortfolio.Core.csproj index 35d8453..82c2fe6 100644 --- a/MonoStockPortfolio.Core/MonoStockPortfolio.Core.csproj +++ b/MonoStockPortfolio.Core/MonoStockPortfolio.Core.csproj @@ -31,6 +31,8 @@ TRACE prompt 4 + True + SdkOnly diff --git a/MonoStockPortfolio.Tests/Framework/ValidationTests.cs b/MonoStockPortfolio.Tests/Framework/ValidationTests.cs new file mode 100644 index 0000000..236cbcb --- /dev/null +++ b/MonoStockPortfolio.Tests/Framework/ValidationTests.cs @@ -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 _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); + } +} \ No newline at end of file diff --git a/MonoStockPortfolio.Tests/MonoStockPortfolio.Tests.csproj b/MonoStockPortfolio.Tests/MonoStockPortfolio.Tests.csproj index 262eb7c..f7289a9 100644 --- a/MonoStockPortfolio.Tests/MonoStockPortfolio.Tests.csproj +++ b/MonoStockPortfolio.Tests/MonoStockPortfolio.Tests.csproj @@ -62,6 +62,8 @@ + + diff --git a/MonoStockPortfolio.Tests/Presenters/EditPortfolioTests.cs b/MonoStockPortfolio.Tests/Presenters/EditPortfolioTests.cs new file mode 100644 index 0000000..32085cf --- /dev/null +++ b/MonoStockPortfolio.Tests/Presenters/EditPortfolioTests.cs @@ -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(); + Mock.Arrange(() => _mockPortfolioRepository.GetPortfolioById(999)).Returns( + new Portfolio(999) { Name = "Testing Portfolio!" }); + + _mockEditPortfolioView = Mock.Create(); + + _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()), 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(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(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>(x => x.Count() == 1)), Occurs.Exactly(1)); + It should_return_a_nice_required_validation_error_message = () => + Mock.Assert(() => _mockEditPortfolioView.ShowValidationErrors(Arg.Matches>(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>(x => x.Count() == 1)), Occurs.Exactly(1)); + It should_return_a_nice_duplication_error_message = () => + Mock.Assert(() => _mockEditPortfolioView.ShowValidationErrors(Arg.Matches>(x => x.Single() == "Portfolio name is already taken")), Occurs.Exactly(1)); + } +} \ No newline at end of file diff --git a/MonoStockPortfolio/Activites/EditPortfolioActivity.cs b/MonoStockPortfolio/Activites/EditPortfolioScreen/EditPortfolioActivity.cs similarity index 52% rename from MonoStockPortfolio/Activites/EditPortfolioActivity.cs rename to MonoStockPortfolio/Activites/EditPortfolioScreen/EditPortfolioActivity.cs index b676a37..99ee0c1 100644 --- a/MonoStockPortfolio/Activites/EditPortfolioActivity.cs +++ b/MonoStockPortfolio/Activites/EditPortfolioScreen/EditPortfolioActivity.cs @@ -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 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; } } diff --git a/MonoStockPortfolio/Activites/EditPortfolioScreen/EditPortfolioPresenter.cs b/MonoStockPortfolio/Activites/EditPortfolioScreen/EditPortfolioPresenter.cs new file mode 100644 index 0000000..c2c9392 --- /dev/null +++ b/MonoStockPortfolio/Activites/EditPortfolioScreen/EditPortfolioPresenter.cs @@ -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 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"); + } + } +} \ No newline at end of file diff --git a/MonoStockPortfolio/Activites/EditPortfolioScreen/IEditPortfolioPresenter.cs b/MonoStockPortfolio/Activites/EditPortfolioScreen/IEditPortfolioPresenter.cs new file mode 100644 index 0000000..dc489a5 --- /dev/null +++ b/MonoStockPortfolio/Activites/EditPortfolioScreen/IEditPortfolioPresenter.cs @@ -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); + } +} \ No newline at end of file diff --git a/MonoStockPortfolio/Activites/EditPortfolioScreen/IEditPortfolioView.cs b/MonoStockPortfolio/Activites/EditPortfolioScreen/IEditPortfolioView.cs new file mode 100644 index 0000000..d370e07 --- /dev/null +++ b/MonoStockPortfolio/Activites/EditPortfolioScreen/IEditPortfolioView.cs @@ -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 errors); + } +} \ No newline at end of file diff --git a/MonoStockPortfolio/Activites/MainScreen/MainActivity.cs b/MonoStockPortfolio/Activites/MainScreen/MainActivity.cs index c6cec43..b808aeb 100644 --- a/MonoStockPortfolio/Activites/MainScreen/MainActivity.cs +++ b/MonoStockPortfolio/Activites/MainScreen/MainActivity.cs @@ -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; diff --git a/MonoStockPortfolio/Framework/ActivityExtensions.cs b/MonoStockPortfolio/Framework/ActivityExtensions.cs index 38717f9..2b233e0 100644 --- a/MonoStockPortfolio/Framework/ActivityExtensions.cs +++ b/MonoStockPortfolio/Framework/ActivityExtensions.cs @@ -1,4 +1,3 @@ -using System.Reflection; using Android.App; using Android.Content; using Android.Widget; diff --git a/MonoStockPortfolio/Framework/NewFormValidator.cs b/MonoStockPortfolio/Framework/NewFormValidator.cs new file mode 100644 index 0000000..db9dd38 --- /dev/null +++ b/MonoStockPortfolio/Framework/NewFormValidator.cs @@ -0,0 +1,45 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace MonoStockPortfolio.Framework +{ + public class NewFormValidator + { + private readonly IList> _list; + + public NewFormValidator() + { + _list = new List>(); + } + + public void AddValidation(Func getValue, Func validationFunction) + { + _list.Add(validationFunction); + } + + public void AddRequired(Func getValue, string errorMessage) + { + AddValidation(getValue, () => Required(getValue(), errorMessage)); + } + + public IEnumerable 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 + } +} \ No newline at end of file diff --git a/MonoStockPortfolio/Framework/ServiceLocator.cs b/MonoStockPortfolio/Framework/ServiceLocator.cs index 8766e2c..0d24e4d 100644 --- a/MonoStockPortfolio/Framework/ServiceLocator.cs +++ b/MonoStockPortfolio/Framework/ServiceLocator.cs @@ -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(); IttyBittyIoC.Register(); + IttyBittyIoC.Register(); } public static object Get(Type serviceType) diff --git a/MonoStockPortfolio/MonoStockPortfolio.csproj b/MonoStockPortfolio/MonoStockPortfolio.csproj index c9a5b88..25b9a96 100644 --- a/MonoStockPortfolio/MonoStockPortfolio.csproj +++ b/MonoStockPortfolio/MonoStockPortfolio.csproj @@ -38,6 +38,8 @@ TRACE prompt 4 + True + SdkOnly @@ -53,7 +55,11 @@ - + + + + +