diff --git a/SwiftGoal.xcodeproj/project.pbxproj b/SwiftGoal.xcodeproj/project.pbxproj index 7d3d177..65ca298 100644 --- a/SwiftGoal.xcodeproj/project.pbxproj +++ b/SwiftGoal.xcodeproj/project.pbxproj @@ -46,6 +46,7 @@ 0FF57F361B431E41001DD570 /* ManagePlayersViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0FF57F351B431E41001DD570 /* ManagePlayersViewModel.swift */; }; 0FF57F381B433EFD001DD570 /* PlayerCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0FF57F371B433EFD001DD570 /* PlayerCell.swift */; }; 0FF7B8B81B3A0CB300808433 /* Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0FF7B8B71B3A0CB300808433 /* Extensions.swift */; }; + 0FFA97041B76C9D900DD4F58 /* EditMatchViewModelSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0FFA97031B76C9D900DD4F58 /* EditMatchViewModelSpec.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -113,6 +114,7 @@ 0FF57F351B431E41001DD570 /* ManagePlayersViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ManagePlayersViewModel.swift; sourceTree = ""; }; 0FF57F371B433EFD001DD570 /* PlayerCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PlayerCell.swift; sourceTree = ""; }; 0FF7B8B71B3A0CB300808433 /* Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Extensions.swift; sourceTree = ""; }; + 0FFA97031B76C9D900DD4F58 /* EditMatchViewModelSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EditMatchViewModelSpec.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -279,6 +281,7 @@ isa = PBXGroup; children = ( 0FB81C481B72C44A00432698 /* MatchesViewModelSpec.swift */, + 0FFA97031B76C9D900DD4F58 /* EditMatchViewModelSpec.swift */, ); path = ViewModels; sourceTree = ""; @@ -455,6 +458,7 @@ buildActionMask = 2147483647; files = ( 0F74F7061B0002C400F963E0 /* ChangesetSpec.swift in Sources */, + 0FFA97041B76C9D900DD4F58 /* EditMatchViewModelSpec.swift in Sources */, 0FB81C491B72C44A00432698 /* MatchesViewModelSpec.swift in Sources */, 0FB81C4C1B72CAFE00432698 /* MockStore.swift in Sources */, ); diff --git a/SwiftGoal/Stores/Store.swift b/SwiftGoal/Stores/Store.swift index ea4532e..d498b84 100644 --- a/SwiftGoal/Stores/Store.swift +++ b/SwiftGoal/Stores/Store.swift @@ -100,7 +100,7 @@ public class Store: NSObject { // MARK: Players - func fetchPlayers() -> SignalProducer<[Player], NSError> { + public func fetchPlayers() -> SignalProducer<[Player], NSError> { let request = NSURLRequest(URL: playersURL) return NSURLSession.sharedSession().rac_dataWithRequest(request) |> map { data, response in diff --git a/SwiftGoal/ViewModels/EditMatchViewModel.swift b/SwiftGoal/ViewModels/EditMatchViewModel.swift index 35593e2..ca2ed9f 100644 --- a/SwiftGoal/ViewModels/EditMatchViewModel.swift +++ b/SwiftGoal/ViewModels/EditMatchViewModel.swift @@ -11,8 +11,8 @@ import ReactiveCocoa public class EditMatchViewModel { // Inputs - let homeGoals: MutableProperty - let awayGoals: MutableProperty + public let homeGoals: MutableProperty + public let awayGoals: MutableProperty // Outputs public let title: String @@ -46,7 +46,7 @@ public class EditMatchViewModel { // MARK: Lifecycle - init(store: Store, match: Match?) { + public init(store: Store, match: Match?) { self.store = store self.match = match @@ -74,13 +74,13 @@ public class EditMatchViewModel { } } - convenience init(store: Store) { + public convenience init(store: Store) { self.init(store: store, match: nil) } // MARK: View Models - func manageHomePlayersViewModel() -> ManagePlayersViewModel { + public func manageHomePlayersViewModel() -> ManagePlayersViewModel { let homePlayersViewModel = ManagePlayersViewModel( store: store, initialPlayers: homePlayers.value, @@ -91,7 +91,7 @@ public class EditMatchViewModel { return homePlayersViewModel } - func manageAwayPlayersViewModel() -> ManagePlayersViewModel { + public func manageAwayPlayersViewModel() -> ManagePlayersViewModel { let awayPlayersViewModel = ManagePlayersViewModel( store: store, initialPlayers: awayPlayers.value, diff --git a/SwiftGoal/ViewModels/ManagePlayersViewModel.swift b/SwiftGoal/ViewModels/ManagePlayersViewModel.swift index 75ecadd..62597ed 100644 --- a/SwiftGoal/ViewModels/ManagePlayersViewModel.swift +++ b/SwiftGoal/ViewModels/ManagePlayersViewModel.swift @@ -8,10 +8,10 @@ import ReactiveCocoa -class ManagePlayersViewModel { +public class ManagePlayersViewModel { // Inputs - let active = MutableProperty(false) + public let active = MutableProperty(false) let playerName = MutableProperty("") let refreshSink: SinkOf> @@ -123,12 +123,12 @@ class ManagePlayersViewModel { // MARK: Player Selection - func selectPlayerAtIndexPath(indexPath: NSIndexPath) { + public func selectPlayerAtIndexPath(indexPath: NSIndexPath) { let player = playerAtIndexPath(indexPath) selectedPlayers.value.insert(player) } - func deselectPlayerAtIndexPath(indexPath: NSIndexPath) { + public func deselectPlayerAtIndexPath(indexPath: NSIndexPath) { let player = playerAtIndexPath(indexPath) selectedPlayers.value.remove(player) } diff --git a/SwiftGoalTests/MockStore.swift b/SwiftGoalTests/MockStore.swift index d266c31..e81a358 100644 --- a/SwiftGoalTests/MockStore.swift +++ b/SwiftGoalTests/MockStore.swift @@ -58,4 +58,8 @@ class MockStore: Store { deletedMatch = match return SignalProducer(value: true) } + + override func fetchPlayers() -> SignalProducer<[Player], NSError> { + return SignalProducer(value: players) + } } diff --git a/SwiftGoalTests/ViewModels/EditMatchViewModelSpec.swift b/SwiftGoalTests/ViewModels/EditMatchViewModelSpec.swift new file mode 100644 index 0000000..06fb361 --- /dev/null +++ b/SwiftGoalTests/ViewModels/EditMatchViewModelSpec.swift @@ -0,0 +1,80 @@ +// +// EditMatchViewModelSpec.swift +// SwiftGoal +// +// Created by Martin Richter on 09/08/15. +// Copyright (c) 2015 Martin Richter. All rights reserved. +// + +import Quick +import Nimble +import ReactiveCocoa +import SwiftGoal + +class EditMatchViewModelSpec: QuickSpec { + override func spec() { + describe("EditMatchViewModel") { + var mockStore: MockStore! + var viewModel: EditMatchViewModel! + + context("when initialized without an existing match") { + beforeEach { + mockStore = MockStore() + viewModel = EditMatchViewModel(store: mockStore) + } + + it("has the correct title") { + expect(viewModel.title).to(equal("New Match")) + } + + it("formats the home goals correctly") { + viewModel.homeGoals.put(1) + expect(viewModel.formattedHomeGoals.value).to(equal("1")) + } + + it("formats the away goals correctly") { + viewModel.awayGoals.put(1) + expect(viewModel.formattedAwayGoals.value).to(equal("1")) + } + + describe("validation") { + it("fails initially") { + expect(viewModel.inputIsValid.value).to(beFalse()) + } + + it("fails when there are no home players") { + let awayPlayersViewModel = viewModel.manageAwayPlayersViewModel() + let awayPlayerIndexPath = NSIndexPath(forRow: 1, inSection: 0) + awayPlayersViewModel.active.put(true) + awayPlayersViewModel.selectPlayerAtIndexPath(awayPlayerIndexPath) + + expect(viewModel.inputIsValid.value).to(beFalse()) + } + + it("fails when there are no away players") { + let homePlayersViewModel = viewModel.manageHomePlayersViewModel() + let homePlayerIndexPath = NSIndexPath(forRow: 0, inSection: 0) + homePlayersViewModel.active.put(true) + homePlayersViewModel.selectPlayerAtIndexPath(homePlayerIndexPath) + + expect(viewModel.inputIsValid.value).to(beFalse()) + } + + it("passes when there are both home and away players") { + let homePlayersViewModel = viewModel.manageHomePlayersViewModel() + let homePlayerIndexPath = NSIndexPath(forRow: 0, inSection: 0) + homePlayersViewModel.active.put(true) + homePlayersViewModel.selectPlayerAtIndexPath(homePlayerIndexPath) + + let awayPlayersViewModel = viewModel.manageAwayPlayersViewModel() + let awayPlayerIndexPath = NSIndexPath(forRow: 1, inSection: 0) + awayPlayersViewModel.active.put(true) + awayPlayersViewModel.selectPlayerAtIndexPath(awayPlayerIndexPath) + + expect(viewModel.inputIsValid.value).to(beTrue()) + } + } + } + } + } +} \ No newline at end of file