mirror of
https://github.com/mgroves/MonodroidStockPortfolio.git
synced 2024-12-26 19:34:49 +00:00
refactored edit-position to MVP, wrote tests, completely removed old validation stuff
This commit is contained in:
parent
9a0d711294
commit
efdf271f1a
16 changed files with 476 additions and 266 deletions
|
@ -8,12 +8,12 @@ namespace MonoStockPortfolio.Tests.Framework
|
|||
{
|
||||
public class ValidationTests
|
||||
{
|
||||
static NewFormValidator _validator;
|
||||
static FormValidator _validator;
|
||||
static IEnumerable<string> _errors;
|
||||
|
||||
Establish context = () =>
|
||||
{
|
||||
_validator = new NewFormValidator();
|
||||
_validator = new FormValidator();
|
||||
};
|
||||
|
||||
Because of = () =>
|
||||
|
|
|
@ -64,6 +64,7 @@
|
|||
<ItemGroup>
|
||||
<Compile Include="Framework\ValidationTests.cs" />
|
||||
<Compile Include="Presenters\EditPortfolioTests.cs" />
|
||||
<Compile Include="Presenters\EditPositionTests.cs" />
|
||||
<Compile Include="Presenters\MainPresenterTests.cs" />
|
||||
<Compile Include="Presenters\PortfolioPresenterTests.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
|
|
|
@ -15,23 +15,23 @@ namespace MonoStockPortfolio.Tests.Presenters
|
|||
protected static IEditPortfolioView _mockEditPortfolioView;
|
||||
|
||||
Establish context = () =>
|
||||
{
|
||||
_mockPortfolioRepository = Mock.Create<IPortfolioRepository>();
|
||||
Mock.Arrange(() => _mockPortfolioRepository.GetPortfolioById(999)).Returns(
|
||||
new Portfolio(999) { Name = "Testing Portfolio!" });
|
||||
{
|
||||
_mockPortfolioRepository = Mock.Create<IPortfolioRepository>();
|
||||
Mock.Arrange(() => _mockPortfolioRepository.GetPortfolioById(999)).Returns(
|
||||
new Portfolio(999) {Name = "Testing Portfolio!"});
|
||||
|
||||
_mockEditPortfolioView = Mock.Create<IEditPortfolioView>();
|
||||
_mockEditPortfolioView = Mock.Create<IEditPortfolioView>();
|
||||
|
||||
_presenter = new EditPortfolioPresenter(_mockPortfolioRepository);
|
||||
};
|
||||
_presenter = new EditPortfolioPresenter(_mockPortfolioRepository);
|
||||
};
|
||||
}
|
||||
|
||||
public class When_initializing_the_edit_portfolio_presenter_with_no_id : EditPortfolioTests
|
||||
{
|
||||
Because of = () =>
|
||||
{
|
||||
_presenter.Initialize(_mockEditPortfolioView, null);
|
||||
};
|
||||
{
|
||||
_presenter.Initialize(_mockEditPortfolioView, null);
|
||||
};
|
||||
|
||||
It should_set_the_title_to_Add_New_Portfolio = () =>
|
||||
Mock.Assert(() => _mockEditPortfolioView.SetTitle("Add New Portfolio"), Occurs.Exactly(1));
|
||||
|
@ -42,9 +42,9 @@ namespace MonoStockPortfolio.Tests.Presenters
|
|||
public class When_initializing_the_edit_portfolio_presenter_with_an_id : EditPortfolioTests
|
||||
{
|
||||
Because of = () =>
|
||||
{
|
||||
_presenter.Initialize(_mockEditPortfolioView, 999);
|
||||
};
|
||||
{
|
||||
_presenter.Initialize(_mockEditPortfolioView, 999);
|
||||
};
|
||||
|
||||
It should_set_the_title_to_Edit_Portfolio = () =>
|
||||
Mock.Assert(() => _mockEditPortfolioView.SetTitle("Edit Portfolio"), Occurs.Exactly(1));
|
||||
|
@ -55,14 +55,14 @@ namespace MonoStockPortfolio.Tests.Presenters
|
|||
public class When_the_user_wants_to_save_a_valid_portfolio : EditPortfolioTests
|
||||
{
|
||||
Establish context = () =>
|
||||
{
|
||||
_presenter.Initialize(_mockEditPortfolioView, null);
|
||||
};
|
||||
{
|
||||
_presenter.Initialize(_mockEditPortfolioView, null);
|
||||
};
|
||||
|
||||
Because of = () =>
|
||||
{
|
||||
_presenter.SavePortfolio(new Portfolio(999) { Name = "Whatever Portfolio" });
|
||||
};
|
||||
{
|
||||
_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));
|
||||
|
@ -75,14 +75,14 @@ namespace MonoStockPortfolio.Tests.Presenters
|
|||
public class When_the_user_tries_to_save_a_new_portfolio_with_a_blank_name : EditPortfolioTests
|
||||
{
|
||||
Establish context = () =>
|
||||
{
|
||||
_presenter.Initialize(_mockEditPortfolioView);
|
||||
};
|
||||
{
|
||||
_presenter.Initialize(_mockEditPortfolioView);
|
||||
};
|
||||
|
||||
Because of = () =>
|
||||
{
|
||||
_presenter.SavePortfolio(new Portfolio { Name = "" });
|
||||
};
|
||||
{
|
||||
_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));
|
||||
|
@ -93,16 +93,16 @@ namespace MonoStockPortfolio.Tests.Presenters
|
|||
public class When_the_user_tries_to_save_a_portfolio_with_a_duplicated_name : EditPortfolioTests
|
||||
{
|
||||
Establish context = () =>
|
||||
{
|
||||
_presenter.Initialize(_mockEditPortfolioView);
|
||||
};
|
||||
{
|
||||
_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" });
|
||||
};
|
||||
{
|
||||
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));
|
||||
|
|
164
MonoStockPortfolio.Tests/Presenters/EditPositionTests.cs
Normal file
164
MonoStockPortfolio.Tests/Presenters/EditPositionTests.cs
Normal file
|
@ -0,0 +1,164 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Linq.Expressions;
|
||||
using Machine.Specifications;
|
||||
using MonoStockPortfolio.Activites.EditPositionScreen;
|
||||
using MonoStockPortfolio.Core.PortfolioRepositories;
|
||||
using MonoStockPortfolio.Core.StockData;
|
||||
using MonoStockPortfolio.Entities;
|
||||
using Telerik.JustMock;
|
||||
|
||||
namespace MonoStockPortfolio.Tests.Presenters
|
||||
{
|
||||
public class EditPositionTests
|
||||
{
|
||||
protected static EditPositionPresenter _presenter;
|
||||
protected static IPortfolioRepository _mockPortfolioRepository;
|
||||
protected static IStockDataProvider _mockStockService;
|
||||
protected static IEditPositionView _mockView;
|
||||
|
||||
Establish context = () =>
|
||||
{
|
||||
_mockPortfolioRepository = Mock.Create<IPortfolioRepository>();
|
||||
_mockStockService = Mock.Create<IStockDataProvider>();
|
||||
_mockView = Mock.Create<IEditPositionView>();
|
||||
|
||||
_presenter = new EditPositionPresenter(_mockPortfolioRepository, _mockStockService);
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
public class When_initializing_the_edit_position_presenter_with_no_id : EditPositionTests
|
||||
{
|
||||
Because of = () =>
|
||||
{
|
||||
_presenter.Initialize(_mockView, 1);
|
||||
};
|
||||
|
||||
It should_set_the_title_to_Add_Position = () =>
|
||||
Mock.Assert(() => _mockView.SetTitle("Add Position"), Occurs.Exactly(1));
|
||||
It shouldnt_prepopulate_the_form_with_anything = () =>
|
||||
Mock.Assert(() => _mockView.PopulateForm(Arg.IsAny<Position>()),Occurs.Never());
|
||||
}
|
||||
|
||||
public class When_initializing_the_edit_position_presenter_with_an_id : EditPositionTests
|
||||
{
|
||||
Establish context = () =>
|
||||
{
|
||||
var fakePosition = new Position(999) { ContainingPortfolioID = 1, PricePerShare = 5.99M, Shares = 50M, Ticker = "FAKE" };
|
||||
Mock.Arrange(() => _mockPortfolioRepository.GetPositionById(999)).Returns(fakePosition);
|
||||
};
|
||||
|
||||
Because of = () =>
|
||||
{
|
||||
_presenter.Initialize(_mockView, 1, 999);
|
||||
};
|
||||
|
||||
It should_set_the_title_to_Edit_Position = () =>
|
||||
Mock.Assert(() => _mockView.SetTitle("Edit Position"), Occurs.Exactly(1));
|
||||
It should_prepopulate_the_PricePerShare_on_the_form = () =>
|
||||
Mock.Assert(() => _mockView.PopulateForm(Arg.Matches<Position>(p => p.PricePerShare == 5.99M)), Occurs.Exactly(1));
|
||||
It should_prepopulate_the_Shares_on_the_form = () =>
|
||||
Mock.Assert(() => _mockView.PopulateForm(Arg.Matches<Position>(p => p.Shares == 50M)), Occurs.Exactly(1));
|
||||
It should_prepopulate_the_Ticker_on_the_form = () =>
|
||||
Mock.Assert(() => _mockView.PopulateForm(Arg.Matches<Position>(p => p.Ticker == "FAKE")), Occurs.Exactly(1));
|
||||
}
|
||||
|
||||
public class When_the_user_wants_to_save_a_valid_position : EditPositionTests
|
||||
{
|
||||
Establish context = () =>
|
||||
{
|
||||
_presenter.Initialize(_mockView, 1);
|
||||
|
||||
Mock.Arrange(() => _mockStockService.IsValidTicker(Arg.AnyString)).Returns(true);
|
||||
};
|
||||
|
||||
Because of = () =>
|
||||
{
|
||||
var fakeInputModel = new PositionInputModel {PriceText = "2.34", SharesText = "671", TickerText = "LOL"};
|
||||
_presenter.Save(fakeInputModel);
|
||||
};
|
||||
|
||||
It should_save_a_position_with_the_portfolio_repository = () =>
|
||||
Mock.Assert(() => _mockPortfolioRepository.SavePosition(Arg.IsAny<Position>()), Occurs.Exactly(1));
|
||||
It should_save_a_position_with_the_correct_Price = () =>
|
||||
Mock.Assert(() => _mockPortfolioRepository.SavePosition(Arg.Matches<Position>(p => p.PricePerShare == 2.34M)), Occurs.Exactly(1));
|
||||
It should_save_a_position_with_the_correct_Shares = () =>
|
||||
Mock.Assert(() => _mockPortfolioRepository.SavePosition(Arg.Matches<Position>(p => p.Shares == 671M)), Occurs.Exactly(1));
|
||||
It should_save_a_position_with_the_correct_Ticker = () =>
|
||||
Mock.Assert(() => _mockPortfolioRepository.SavePosition(Arg.Matches<Position>(p => p.Ticker == "LOL")), Occurs.Exactly(1));
|
||||
It should_save_a_position_with_the_correct_Containing_Portfolio_ID = () =>
|
||||
Mock.Assert(() => _mockPortfolioRepository.SavePosition(Arg.Matches<Position>(p => p.ContainingPortfolioID == 1)), Occurs.Exactly(1));
|
||||
It should_tell_the_view_to_go_back_to_the_main_activity = () =>
|
||||
Mock.Assert(() => _mockView.GoBackToMainActivity(), Occurs.Exactly(1));
|
||||
}
|
||||
|
||||
public class When_the_user_wants_to_save_an_invalid_position : EditPositionTests
|
||||
{
|
||||
Establish context = () =>
|
||||
{
|
||||
_presenter.Initialize(_mockView, 1);
|
||||
|
||||
Mock.Arrange(() => _mockStockService.IsValidTicker(Arg.AnyString)).Returns(false);
|
||||
};
|
||||
|
||||
Because of = () =>
|
||||
{
|
||||
var fakeInputModel = new PositionInputModel {PriceText = "cows", SharesText = "WALRUS!!", TickerText = "fail"};
|
||||
_presenter.Save(fakeInputModel);
|
||||
};
|
||||
|
||||
It should_not_try_to_save_the_portfolio = () =>
|
||||
Mock.Assert(() => _mockPortfolioRepository.SavePosition(Arg.IsAny<Position>()), Occurs.Never());
|
||||
It should_send_the_validation_errors_to_the_view = () =>
|
||||
Mock.Assert(() => _mockView.ShowErrorMessages(Arg.IsAny<IList<string>>()), Occurs.Exactly(1));
|
||||
It should_send_an_invalid_ticker_error_to_the_view = () =>
|
||||
MockAssertPositionMatches(x => x.Any(p => p == "Invalid Ticker Name"));
|
||||
It should_send_an_invalid_shares_number_error_to_the_view = () =>
|
||||
MockAssertPositionMatches(x => x.Any(p => p == "Please enter a valid, positive number of shares"));
|
||||
It should_send_an_invalid_price_per_share_error_to_the_view = () =>
|
||||
MockAssertPositionMatches(x => x.Any(p => p == "Please enter a valid, positive price per share"));
|
||||
It should_not_tell_the_view_to_go_back_to_the_main_activity = () =>
|
||||
Mock.Assert(() => _mockView.GoBackToMainActivity(), Occurs.Never());
|
||||
|
||||
private static void MockAssertPositionMatches(Expression<Predicate<IList<string>>> match)
|
||||
{
|
||||
Mock.Assert(() => _mockView.ShowErrorMessages(Arg.Matches(match)), Occurs.Exactly(1));
|
||||
}
|
||||
}
|
||||
|
||||
public class When_the_user_wants_to_save_an_invalid_position_with_blank_fields : EditPositionTests
|
||||
{
|
||||
Establish context = () =>
|
||||
{
|
||||
_presenter.Initialize(_mockView, 1);
|
||||
|
||||
Mock.Arrange(() => _mockStockService.IsValidTicker(Arg.AnyString)).Returns(false);
|
||||
};
|
||||
|
||||
Because of = () =>
|
||||
{
|
||||
var fakeInputModel = new PositionInputModel { PriceText = "", SharesText = "", TickerText = "" };
|
||||
_presenter.Save(fakeInputModel);
|
||||
};
|
||||
|
||||
It should_not_try_to_save_the_portfolio = () =>
|
||||
Mock.Assert(() => _mockPortfolioRepository.SavePosition(Arg.IsAny<Position>()), Occurs.Never());
|
||||
It should_send_the_validation_errors_to_the_view = () =>
|
||||
Mock.Assert(() => _mockView.ShowErrorMessages(Arg.IsAny<IList<string>>()), Occurs.Exactly(1));
|
||||
It should_send_an_invalid_ticker_error_to_the_view = () =>
|
||||
MockPositionMatches(x => x.Any(p => p == "Please enter a ticker"));
|
||||
It should_send_an_invalid_shares_number_error_to_the_view = () =>
|
||||
MockPositionMatches(x => x.Any(p => p == "Please enter a valid, positive number of shares"));
|
||||
It should_send_an_invalid_price_per_share_error_to_the_view = () =>
|
||||
MockPositionMatches(x => x.Any(p => p == "Please enter a valid, positive price per share"));
|
||||
It should_not_tell_the_view_to_go_back_to_the_main_activity = () =>
|
||||
Mock.Assert(() => _mockView.GoBackToMainActivity(), Occurs.Never());
|
||||
|
||||
private static void MockPositionMatches(Expression<Predicate<IList<string>>> match)
|
||||
{
|
||||
Mock.Assert(() => _mockView.ShowErrorMessages(Arg.Matches(match)), Occurs.Exactly(1));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -46,9 +46,9 @@ namespace MonoStockPortfolio.Activites.EditPortfolioScreen
|
|||
|
||||
private IEnumerable<string> Validate(Portfolio portfolioToSave)
|
||||
{
|
||||
var validator = new NewFormValidator();
|
||||
var validator = new FormValidator();
|
||||
validator.AddRequired(() => portfolioToSave.Name, "Please enter a portfolio name");
|
||||
validator.AddValidation(() => portfolioToSave.Name, () => IsDuplicateName(portfolioToSave));
|
||||
validator.AddValidation(() => IsDuplicateName(portfolioToSave));
|
||||
|
||||
return validator.Apply().ToList();
|
||||
}
|
||||
|
|
|
@ -1,140 +0,0 @@
|
|||
using System;
|
||||
using Android.App;
|
||||
using Android.Content;
|
||||
using Android.OS;
|
||||
using Android.Widget;
|
||||
using MonoStockPortfolio.Core.PortfolioRepositories;
|
||||
using MonoStockPortfolio.Core.StockData;
|
||||
using MonoStockPortfolio.Entities;
|
||||
using MonoStockPortfolio.Framework;
|
||||
|
||||
namespace MonoStockPortfolio.Activites
|
||||
{
|
||||
[Activity(Label = "Add Position", MainLauncher = false, Name = "monostockportfolio.activites.EditPositionActivity")]
|
||||
public class EditPositionActivity : Activity
|
||||
{
|
||||
[IoC] private IPortfolioRepository _repo;
|
||||
[IoC] private IStockDataProvider _svc;
|
||||
|
||||
[LazyView(Resource.Id.addPositionTicker)] protected EditText TickerTextBox;
|
||||
[LazyView(Resource.Id.addPositionPrice)] protected EditText PriceTextBox;
|
||||
[LazyView(Resource.Id.addPositionShares)] protected EditText SharesTextBox;
|
||||
[LazyView(Resource.Id.addPositionSaveButton)] protected Button SaveButton;
|
||||
|
||||
private const string POSITIONIDEXTRA = "monoStockPortfolio.EditPositionActivity.PositionID";
|
||||
private const string PORTFOLIOIDEXTRA = "monoStockPortfolio.EditPositionActivity.PortfolioID";
|
||||
|
||||
public static Intent AddIntent(Context context, long portfolioId)
|
||||
{
|
||||
var intent = new Intent();
|
||||
intent.SetClassName(context, ManifestNames.GetName<EditPositionActivity>());
|
||||
intent.PutExtra(PORTFOLIOIDEXTRA, portfolioId);
|
||||
return intent;
|
||||
}
|
||||
public static Intent EditIntent(Context context, long positionId, long portfolioId)
|
||||
{
|
||||
var intent = new Intent();
|
||||
intent.SetClassName(context, ManifestNames.GetName<EditPositionActivity>());
|
||||
intent.PutExtra(POSITIONIDEXTRA, positionId);
|
||||
intent.PutExtra(PORTFOLIOIDEXTRA, portfolioId);
|
||||
return intent;
|
||||
}
|
||||
public long ThisPortfolioId { get { return Intent.GetLongExtra(PORTFOLIOIDEXTRA, -1); } }
|
||||
public long ThisPositionId { get { return Intent.GetLongExtra(POSITIONIDEXTRA, -1); } }
|
||||
|
||||
protected override void OnCreate(Bundle bundle)
|
||||
{
|
||||
base.OnCreate(bundle);
|
||||
|
||||
SetContentView(Resource.Layout.addposition);
|
||||
|
||||
var positionId = ThisPositionId;
|
||||
if (positionId != -1)
|
||||
{
|
||||
this.Title = "Edit Position";
|
||||
PopulateForm(positionId);
|
||||
}
|
||||
|
||||
WireUpEvents();
|
||||
}
|
||||
|
||||
private void PopulateForm(long positionId)
|
||||
{
|
||||
var position = _repo.GetPositionById(positionId);
|
||||
this.TickerTextBox.Text = position.Ticker;
|
||||
this.PriceTextBox.Text = position.PricePerShare.ToString();
|
||||
this.SharesTextBox.Text = position.Shares.ToString();
|
||||
}
|
||||
|
||||
private void WireUpEvents()
|
||||
{
|
||||
SaveButton.Click += saveButton_Click;
|
||||
}
|
||||
|
||||
void saveButton_Click(object sender, EventArgs e)
|
||||
{
|
||||
if(Validate())
|
||||
{
|
||||
var positionToSave = GetPositionToSave();
|
||||
_repo.SavePosition(positionToSave);
|
||||
|
||||
this.EndActivity();
|
||||
}
|
||||
}
|
||||
|
||||
private bool Validate()
|
||||
{
|
||||
var result = ValidationRules.Apply();
|
||||
|
||||
if (result == string.Empty)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
this.LongToast(result);
|
||||
return false;
|
||||
}
|
||||
|
||||
private FormValidator ValidationRules
|
||||
{
|
||||
get
|
||||
{
|
||||
var validator = new FormValidator();
|
||||
validator.AddRequired(TickerTextBox, "Please enter a ticker");
|
||||
validator.AddValidPositiveDecimal(SharesTextBox, "Please enter a valid, positive number of shares");
|
||||
validator.AddValidPositiveDecimal(PriceTextBox, "Please enter a valid, positive price per share");
|
||||
validator.AddValidation(TickerTextBox, () => ValidateTicker(TickerTextBox.Text));
|
||||
return validator;
|
||||
}
|
||||
}
|
||||
|
||||
private string ValidateTicker(string ticker)
|
||||
{
|
||||
if(_svc.IsValidTicker(ticker))
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
return "Invalid Ticker Name";
|
||||
}
|
||||
|
||||
private Position GetPositionToSave()
|
||||
{
|
||||
Position positionToSave;
|
||||
var positionId = ThisPositionId;
|
||||
if (positionId != -1)
|
||||
{
|
||||
positionToSave = new Position(positionId);
|
||||
}
|
||||
else
|
||||
{
|
||||
positionToSave = new Position();
|
||||
}
|
||||
|
||||
positionToSave.Shares = decimal.Parse(SharesTextBox.Text);
|
||||
positionToSave.PricePerShare = decimal.Parse(PriceTextBox.Text);
|
||||
positionToSave.Ticker = TickerTextBox.Text.ToUpper();
|
||||
positionToSave.ContainingPortfolioID = ThisPortfolioId;
|
||||
return positionToSave;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,112 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Android.App;
|
||||
using Android.Content;
|
||||
using Android.OS;
|
||||
using Android.Widget;
|
||||
using MonoStockPortfolio.Entities;
|
||||
using MonoStockPortfolio.Framework;
|
||||
|
||||
namespace MonoStockPortfolio.Activites.EditPositionScreen
|
||||
{
|
||||
[Activity(Label = "Add Position", MainLauncher = false, Name = "monostockportfolio.activites.EditPositionActivity")]
|
||||
public class EditPositionActivity : Activity, IEditPositionView
|
||||
{
|
||||
[IoC] IEditPositionPresenter _presenter;
|
||||
|
||||
[LazyView(Resource.Id.addPositionTicker)] protected EditText TickerTextBox;
|
||||
[LazyView(Resource.Id.addPositionPrice)] protected EditText PriceTextBox;
|
||||
[LazyView(Resource.Id.addPositionShares)] protected EditText SharesTextBox;
|
||||
[LazyView(Resource.Id.addPositionSaveButton)] protected Button SaveButton;
|
||||
|
||||
private const string POSITIONIDEXTRA = "monoStockPortfolio.EditPositionActivity.PositionID";
|
||||
private const string PORTFOLIOIDEXTRA = "monoStockPortfolio.EditPositionActivity.PortfolioID";
|
||||
|
||||
public static Intent AddIntent(Context context, long portfolioId)
|
||||
{
|
||||
var intent = new Intent();
|
||||
intent.SetClassName(context, ManifestNames.GetName<EditPositionActivity>());
|
||||
intent.PutExtra(PORTFOLIOIDEXTRA, portfolioId);
|
||||
return intent;
|
||||
}
|
||||
public static Intent EditIntent(Context context, long positionId, long portfolioId)
|
||||
{
|
||||
var intent = new Intent();
|
||||
intent.SetClassName(context, ManifestNames.GetName<EditPositionActivity>());
|
||||
intent.PutExtra(POSITIONIDEXTRA, positionId);
|
||||
intent.PutExtra(PORTFOLIOIDEXTRA, portfolioId);
|
||||
return intent;
|
||||
}
|
||||
|
||||
#region IEditPositionView implementation
|
||||
|
||||
public void SetTitle(string title)
|
||||
{
|
||||
this.Title = title;
|
||||
}
|
||||
|
||||
public void PopulateForm(Position position)
|
||||
{
|
||||
this.TickerTextBox.Text = position.Ticker;
|
||||
this.PriceTextBox.Text = position.PricePerShare.ToString();
|
||||
this.SharesTextBox.Text = position.Shares.ToString();
|
||||
}
|
||||
|
||||
public void GoBackToMainActivity()
|
||||
{
|
||||
this.EndActivity();
|
||||
}
|
||||
|
||||
public void ShowErrorMessages(IList<string> errorMessages)
|
||||
{
|
||||
var errorMessage = string.Empty;
|
||||
foreach (var error in errorMessages)
|
||||
{
|
||||
errorMessage += error + "\n";
|
||||
}
|
||||
errorMessage = errorMessage.Trim('\n');
|
||||
this.LongToast(errorMessage);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
protected override void OnCreate(Bundle bundle)
|
||||
{
|
||||
base.OnCreate(bundle);
|
||||
|
||||
SetContentView(Resource.Layout.addposition);
|
||||
|
||||
var portfolioId = Intent.GetLongExtra(PORTFOLIOIDEXTRA, -1);
|
||||
var positionId = Intent.GetLongExtra(POSITIONIDEXTRA, -1);
|
||||
if (positionId != -1)
|
||||
{
|
||||
_presenter.Initialize(this, portfolioId, positionId);
|
||||
}
|
||||
else
|
||||
{
|
||||
_presenter.Initialize(this, portfolioId);
|
||||
}
|
||||
|
||||
WireUpEvents();
|
||||
}
|
||||
|
||||
private void WireUpEvents()
|
||||
{
|
||||
SaveButton.Click += saveButton_Click;
|
||||
}
|
||||
|
||||
void saveButton_Click(object sender, EventArgs e)
|
||||
{
|
||||
_presenter.Save(GetPositionInputModel());
|
||||
}
|
||||
|
||||
private PositionInputModel GetPositionInputModel()
|
||||
{
|
||||
var model = new PositionInputModel();
|
||||
model.TickerText = this.TickerTextBox.Text;
|
||||
model.PriceText = this.PriceTextBox.Text;
|
||||
model.SharesText = this.SharesTextBox.Text;
|
||||
return model;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,89 @@
|
|||
using System.Linq;
|
||||
using MonoStockPortfolio.Core.PortfolioRepositories;
|
||||
using MonoStockPortfolio.Core.StockData;
|
||||
using MonoStockPortfolio.Entities;
|
||||
using MonoStockPortfolio.Framework;
|
||||
|
||||
namespace MonoStockPortfolio.Activites.EditPositionScreen
|
||||
{
|
||||
public class EditPositionPresenter : IEditPositionPresenter
|
||||
{
|
||||
private IEditPositionView _currentView;
|
||||
private readonly IPortfolioRepository _portfolioRepository;
|
||||
private readonly IStockDataProvider _stockService;
|
||||
private long? _positionId;
|
||||
private long _portfolioId;
|
||||
|
||||
public EditPositionPresenter(IPortfolioRepository portfolioRepository, IStockDataProvider stockService)
|
||||
{
|
||||
_portfolioRepository = portfolioRepository;
|
||||
_stockService = stockService;
|
||||
}
|
||||
|
||||
public void Initialize(IEditPositionView editPositionView, long portfolioId, long? positionId = null)
|
||||
{
|
||||
_currentView = editPositionView;
|
||||
_positionId = positionId;
|
||||
_portfolioId = portfolioId;
|
||||
|
||||
if (positionId != null)
|
||||
{
|
||||
_currentView.SetTitle("Edit Position");
|
||||
var position = _portfolioRepository.GetPositionById(positionId ?? -1);
|
||||
_currentView.PopulateForm(position);
|
||||
}
|
||||
else
|
||||
{
|
||||
_currentView.SetTitle("Add Position");
|
||||
}
|
||||
}
|
||||
|
||||
public void Save(PositionInputModel positionInputModel)
|
||||
{
|
||||
var validator = new FormValidator();
|
||||
validator.AddRequired(() => positionInputModel.TickerText, "Please enter a ticker");
|
||||
validator.AddValidPositiveDecimal(() => positionInputModel.SharesText, "Please enter a valid, positive number of shares");
|
||||
validator.AddValidPositiveDecimal(() => positionInputModel.PriceText, "Please enter a valid, positive price per share");
|
||||
validator.AddValidation(() => ValidateTicker(positionInputModel.TickerText));
|
||||
|
||||
var errorMessages = validator.Apply();
|
||||
if (!errorMessages.Any())
|
||||
{
|
||||
_portfolioRepository.SavePosition(GetPosition(positionInputModel));
|
||||
_currentView.GoBackToMainActivity();
|
||||
}
|
||||
else
|
||||
{
|
||||
_currentView.ShowErrorMessages(errorMessages.ToList());
|
||||
}
|
||||
}
|
||||
|
||||
private Position GetPosition(PositionInputModel positionInputModel)
|
||||
{
|
||||
Position positionToSave;
|
||||
if (_positionId != null)
|
||||
{
|
||||
positionToSave = new Position(_positionId ?? -1);
|
||||
}
|
||||
else
|
||||
{
|
||||
positionToSave = new Position();
|
||||
}
|
||||
|
||||
positionToSave.Shares = decimal.Parse(positionInputModel.SharesText);
|
||||
positionToSave.PricePerShare = decimal.Parse(positionInputModel.PriceText);
|
||||
positionToSave.Ticker = positionInputModel.TickerText.ToUpper();
|
||||
positionToSave.ContainingPortfolioID = _portfolioId;
|
||||
return positionToSave;
|
||||
}
|
||||
|
||||
private string ValidateTicker(string ticker)
|
||||
{
|
||||
if (_stockService.IsValidTicker(ticker))
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
return "Invalid Ticker Name";
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
namespace MonoStockPortfolio.Activites.EditPositionScreen
|
||||
{
|
||||
public interface IEditPositionPresenter
|
||||
{
|
||||
void Initialize(IEditPositionView editPositionActivity, long portfolioId, long? positionId = null);
|
||||
void Save(PositionInputModel getPositionInputModel);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
using System.Collections.Generic;
|
||||
using MonoStockPortfolio.Entities;
|
||||
|
||||
namespace MonoStockPortfolio.Activites.EditPositionScreen
|
||||
{
|
||||
public interface IEditPositionView
|
||||
{
|
||||
void SetTitle(string title);
|
||||
void PopulateForm(Position position);
|
||||
void GoBackToMainActivity();
|
||||
void ShowErrorMessages(IList<string> errorMessages);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
namespace MonoStockPortfolio.Activites.EditPositionScreen
|
||||
{
|
||||
public class PositionInputModel
|
||||
{
|
||||
public string TickerText { get; set; }
|
||||
public string PriceText { get; set; }
|
||||
public string SharesText { get; set; }
|
||||
}
|
||||
}
|
|
@ -6,6 +6,7 @@ using Android.Content;
|
|||
using Android.OS;
|
||||
using Android.Views;
|
||||
using Android.Widget;
|
||||
using MonoStockPortfolio.Activites.EditPositionScreen;
|
||||
using MonoStockPortfolio.Core;
|
||||
using MonoStockPortfolio.Entities;
|
||||
using MonoStockPortfolio.Framework;
|
||||
|
|
|
@ -1,80 +1,73 @@
|
|||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Android.Widget;
|
||||
using System.Linq;
|
||||
|
||||
namespace MonoStockPortfolio.Framework
|
||||
{
|
||||
public class FormValidator
|
||||
{
|
||||
private IList<KeyValuePair<EditText, Func<string>>> _dict;
|
||||
private readonly IList<Func<string>> _list;
|
||||
|
||||
public FormValidator()
|
||||
{
|
||||
_dict = new List<KeyValuePair<EditText, Func<string>>>();
|
||||
_list = new List<Func<string>>();
|
||||
}
|
||||
|
||||
public void AddValidation(EditText control, Func<string> validationFunction)
|
||||
public void AddValidation(Func<string> validationFunction)
|
||||
{
|
||||
_dict.Add(new KeyValuePair<EditText, Func<string>>(control, validationFunction));
|
||||
}
|
||||
public void AddRequired(EditText control, string errorMessage)
|
||||
{
|
||||
AddValidation(control, () => Required(control, errorMessage));
|
||||
}
|
||||
public void AddValidDecimal(EditText control, string errorMessage)
|
||||
{
|
||||
AddValidation(control, () => ValidDecimal(control, errorMessage));
|
||||
}
|
||||
public void AddValidPositiveDecimal(EditText control, string errorMessage)
|
||||
{
|
||||
AddValidation(control, () => ValidPositiveDecimal(control, errorMessage));
|
||||
_list.Add(validationFunction);
|
||||
}
|
||||
|
||||
public string Apply()
|
||||
public void AddRequired(Func<string> getValue, string errorMessage)
|
||||
{
|
||||
var errorMessage = string.Empty;
|
||||
foreach (var keyValuePair in _dict)
|
||||
{
|
||||
var result = keyValuePair.Value();
|
||||
errorMessage += keyValuePair.Value();
|
||||
if(result != string.Empty)
|
||||
{
|
||||
errorMessage += "\n";
|
||||
}
|
||||
}
|
||||
return errorMessage;
|
||||
AddValidation(() => Required(getValue(), errorMessage));
|
||||
}
|
||||
|
||||
#region Validation Functions
|
||||
|
||||
private static string Required(EditText control, string errorMessage)
|
||||
public void AddValidDecimal(Func<string> getValue, string errorMessage)
|
||||
{
|
||||
if (string.IsNullOrEmpty(control.Text.ToString()))
|
||||
AddValidation(() => ValidDecimal(getValue(), errorMessage));
|
||||
}
|
||||
public void AddValidPositiveDecimal(Func<string> getValue, string errorMessage)
|
||||
{
|
||||
AddValidation(() => ValidPositiveDecimal(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;
|
||||
}
|
||||
private static string ValidDecimal(EditText control, string errorMessage)
|
||||
|
||||
private static string ValidDecimal(string getValue, string errorMessage)
|
||||
{
|
||||
var test = control.Text.ToString();
|
||||
decimal dummy;
|
||||
if(!decimal.TryParse(test, out dummy))
|
||||
if (!decimal.TryParse(getValue, out dummy))
|
||||
{
|
||||
return errorMessage;
|
||||
}
|
||||
return string.Empty;
|
||||
}
|
||||
private static string ValidPositiveDecimal(EditText control, string errorMessage)
|
||||
|
||||
private static string ValidPositiveDecimal(string getValue, string errorMessage)
|
||||
{
|
||||
if(ValidDecimal(control, errorMessage) == string.Empty)
|
||||
if (ValidDecimal(getValue, errorMessage) == string.Empty)
|
||||
{
|
||||
var val = decimal.Parse(control.Text.ToString());
|
||||
var val = decimal.Parse(getValue);
|
||||
if (val >= 0) return string.Empty;
|
||||
}
|
||||
return errorMessage;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
|
@ -1,45 +0,0 @@
|
|||
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
|
||||
}
|
||||
}
|
|
@ -2,6 +2,7 @@
|
|||
using Android.Content;
|
||||
using Android.Util;
|
||||
using MonoStockPortfolio.Activites.EditPortfolioScreen;
|
||||
using MonoStockPortfolio.Activites.EditPositionScreen;
|
||||
using MonoStockPortfolio.Activites.MainScreen;
|
||||
using MonoStockPortfolio.Activites.PortfolioScreen;
|
||||
using MonoStockPortfolio.Core.Config;
|
||||
|
@ -28,6 +29,7 @@ namespace MonoStockPortfolio.Framework
|
|||
IttyBittyIoC.Register<IMainPresenter, MainPresenter>();
|
||||
IttyBittyIoC.Register<IPortfolioPresenter, PortfolioPresenter>();
|
||||
IttyBittyIoC.Register<IEditPortfolioPresenter, EditPortfolioPresenter>();
|
||||
IttyBittyIoC.Register<IEditPositionPresenter, EditPositionPresenter>();
|
||||
}
|
||||
|
||||
public static object Get(Type serviceType)
|
||||
|
|
|
@ -59,7 +59,11 @@
|
|||
<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\EditPositionScreen\EditPositionPresenter.cs" />
|
||||
<Compile Include="Activites\EditPositionScreen\IEditPositionPresenter.cs" />
|
||||
<Compile Include="Activites\EditPositionScreen\IEditPositionView.cs" />
|
||||
<Compile Include="Activites\EditPositionScreen\PositionInputModel.cs" />
|
||||
<Compile Include="Framework\FormValidator.cs" />
|
||||
<Compile Include="Activites\MainScreen\IMainPresenter.cs" />
|
||||
<Compile Include="Activites\MainScreen\IMainView.cs" />
|
||||
<Compile Include="Activites\MainScreen\MainPresenter.cs" />
|
||||
|
@ -73,7 +77,7 @@
|
|||
<Compile Include="Framework\GenericArrayAdapter.cs" />
|
||||
<Compile Include="Activites\PortfolioScreen\PortfolioActivity.cs" />
|
||||
<Compile Include="Activites\MainScreen\MainActivity.cs" />
|
||||
<Compile Include="Activites\EditPositionActivity.cs" />
|
||||
<Compile Include="Activites\EditPositionScreen\EditPositionActivity.cs" />
|
||||
<Compile Include="Framework\IoCAttribute.cs" />
|
||||
<Compile Include="Framework\IttyBittyIoC.cs" />
|
||||
<Compile Include="Framework\LazyViewAttribute.cs" />
|
||||
|
@ -84,7 +88,6 @@
|
|||
<Compile Include="Resources\Resource.Designer.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
<Compile Include="Framework\StringExtensions.cs" />
|
||||
<Compile Include="Framework\FormValidator.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="PostSharp.Custom.targets" />
|
||||
|
|
Loading…
Reference in a new issue