From c4abe62a7340808e54661428bb94a61e02fc9bf4 Mon Sep 17 00:00:00 2001 From: Hari Singh <15615702+singhhari@users.noreply.github.com> Date: Wed, 7 Oct 2020 18:27:34 -0400 Subject: [PATCH 1/2] Accounting for startsAt property when rendering rewards and addOns --- Library/SharedFunctions.swift | 12 +++++++ Library/SharedFunctionsTests.swift | 22 +++++++++++- .../RewardAddOnSelectionViewModel.swift | 10 +++--- .../RewardAddOnSelectionViewModelTests.swift | 35 +++++++++++++++++-- .../RewardsCollectionViewModel.swift | 5 ++- 5 files changed, 75 insertions(+), 9 deletions(-) diff --git a/Library/SharedFunctions.swift b/Library/SharedFunctions.swift index 86b4fc7534..f708ca3ea1 100644 --- a/Library/SharedFunctions.swift +++ b/Library/SharedFunctions.swift @@ -364,3 +364,15 @@ public func rewardsCarouselCanNavigateToReward(_ reward: Reward, in project: Pro ] .allSatisfy(isTrue) } + +/** + Determines if a start date from a given reward predates the current date. + + - parameter reward: The reward being evaluated + + - returns: A Bool representing whether the reward has a start date prior to the current date/time. + */ +public func isStartDateBeforeToday(for reward: Reward) -> Bool { + return (reward.startsAt == nil || (reward.startsAt ?? 0) <= AppEnvironment.current.dateType.init() + .timeIntervalSince1970) +} diff --git a/Library/SharedFunctionsTests.swift b/Library/SharedFunctionsTests.swift index 3eae2130b2..5ed40a68fa 100644 --- a/Library/SharedFunctionsTests.swift +++ b/Library/SharedFunctionsTests.swift @@ -239,7 +239,6 @@ final class SharedFunctionsTests: TestCase { XCTAssertTrue(rewardsCarouselCanNavigateToReward(reward, in: project)) } - func testRewardsCarouselCanNavigateToReward_Reward_Unavailable_NotBacked_HasAddOns() { let reward = Reward.template |> Reward.lens.limit .~ 5 @@ -305,4 +304,25 @@ final class SharedFunctionsTests: TestCase { XCTAssertTrue(rewardsCarouselCanNavigateToReward(reward, in: project)) } + + func testIsStartDateBeforeToday_Reward_StartsAt_Nil() { + let reward = Reward.template + |> Reward.lens.startsAt .~ nil + + XCTAssertTrue(isStartDateBeforeToday(for: reward)) + } + + func testIsStartDateBeforeToday_Reward_StartsAt_PastDate() { + let reward = Reward.template + |> Reward.lens.startsAt .~ (MockDate().timeIntervalSince1970 - 60) + + XCTAssertTrue(isStartDateBeforeToday(for: reward)) + } + + func testIsStartDateBeforeToday_Reward_StartsAt_FutureDate() { + let reward = Reward.template + |> Reward.lens.startsAt .~ (MockDate().timeIntervalSince1970 + 60) + + XCTAssertFalse(isStartDateBeforeToday(for: reward)) + } } diff --git a/Library/ViewModels/RewardAddOnSelectionViewModel.swift b/Library/ViewModels/RewardAddOnSelectionViewModel.swift index d054a412f9..21092750d8 100644 --- a/Library/ViewModels/RewardAddOnSelectionViewModel.swift +++ b/Library/ViewModels/RewardAddOnSelectionViewModel.swift @@ -378,14 +378,14 @@ private func addOnIsAvailable(_ addOn: Reward, in project: Project) -> Bool { } let isUnlimitedOrAvailable = addOn.limit == nil || addOn.remaining ?? 0 > 0 - let hasNoTimeLimitOrHasNotEnded = ( - addOn.endsAt == nil || - (addOn.endsAt ?? 0) >= AppEnvironment.current.dateType.init().timeIntervalSince1970 - ) + // Assuming the user has not backed the addOn, we only display if it's within range of the start and end date + let hasNoTimeLimitOrIsWithinRange = isStartDateBeforeToday(for: addOn) && + (addOn.endsAt == nil || (addOn.endsAt ?? 0) >= AppEnvironment.current.dateType.init() + .timeIntervalSince1970) return [ project.state == .live, - hasNoTimeLimitOrHasNotEnded, + hasNoTimeLimitOrIsWithinRange, isUnlimitedOrAvailable ] .allSatisfy(isTrue) diff --git a/Library/ViewModels/RewardAddOnSelectionViewModelTests.swift b/Library/ViewModels/RewardAddOnSelectionViewModelTests.swift index 12b67365dc..52abee2649 100644 --- a/Library/ViewModels/RewardAddOnSelectionViewModelTests.swift +++ b/Library/ViewModels/RewardAddOnSelectionViewModelTests.swift @@ -394,9 +394,40 @@ final class RewardAddOnSelectionViewModelTests: TestCase { |> Reward.lens.limit .~ 5 |> Reward.lens.remaining .~ 2 + // timebased add-on, starts in 60 seconds + let addOn7 = Reward.template + |> Reward.lens.id .~ 7 + |> Reward.lens.limit .~ nil + |> Reward.lens.remaining .~ nil + |> Reward.lens.startsAt .~ (MockDate().timeIntervalSince1970 + 60) + + // timebased add-on, started 60 seconds ago. + let addOn8 = Reward.template + |> Reward.lens.id .~ 8 + |> Reward.lens.limit .~ nil + |> Reward.lens.remaining .~ nil + |> Reward.lens.startsAt .~ (MockDate().timeIntervalSince1970 - 60) + + // timebased add-on, both startsAt and endsAt are within a valid range + let addOn9 = Reward.template + |> Reward.lens.id .~ 9 + |> Reward.lens.limit .~ nil + |> Reward.lens.remaining .~ nil + |> Reward.lens.startsAt .~ (MockDate().timeIntervalSince1970 - 60) + |> Reward.lens.endsAt .~ (MockDate().timeIntervalSince1970 + 60) + + // timebased add-on, invalid range + let addOn10 = Reward.template + |> Reward.lens.id .~ 10 + |> Reward.lens.limit .~ nil + |> Reward.lens.remaining .~ nil + |> Reward.lens.startsAt .~ (MockDate().timeIntervalSince1970 + 30) + |> Reward.lens.endsAt .~ (MockDate().timeIntervalSince1970 + 60) + let project = Project.template |> Project.lens.rewardData.rewards .~ [baseReward] - |> Project.lens.rewardData.addOns .~ [addOn1, addOn2, addOn3, addOn4, addOn5, addOn6] + |> Project.lens.rewardData + .addOns .~ [addOn1, addOn2, addOn3, addOn4, addOn5, addOn6, addOn7, addOn8, addOn9, addOn10] |> Project.lens.personalization.backing .~ ( .template |> Backing.lens.addOns .~ [addOn3] @@ -404,7 +435,7 @@ final class RewardAddOnSelectionViewModelTests: TestCase { |> Backing.lens.rewardId .~ baseReward.id ) - let expectedAddOns = [addOn1, addOn3, addOn5, addOn6] + let expectedAddOns = [addOn1, addOn3, addOn5, addOn6, addOn8, addOn9] let expected = expectedAddOns .map { reward in diff --git a/Library/ViewModels/RewardsCollectionViewModel.swift b/Library/ViewModels/RewardsCollectionViewModel.swift index 6402ad1bc1..b7fdc2c01f 100644 --- a/Library/ViewModels/RewardsCollectionViewModel.swift +++ b/Library/ViewModels/RewardsCollectionViewModel.swift @@ -71,7 +71,8 @@ public final class RewardsCollectionViewModel: RewardsCollectionViewModelType, self.reloadDataWithValues = Signal.combineLatest(project, rewards) .map { project, rewards in - rewards.map { reward in (project, reward, .pledge) } + rewards.filter { reward in isStartDateBeforeToday(for: reward) } + .map { reward in (project, reward, .pledge) } } self.configureRewardsCollectionViewFooterWithCount = self.reloadDataWithValues @@ -255,6 +256,8 @@ public final class RewardsCollectionViewModel: RewardsCollectionViewModelType, public var outputs: RewardsCollectionViewModelOutputs { return self } } +// MARK: - Functions + private func titleForContext(_ context: RewardsCollectionViewContext, project: Project) -> String { if currentUserIsCreator(of: project) { return Strings.View_your_rewards() From 69262818dd85bce97b4a17c54d713d796c3f246d Mon Sep 17 00:00:00 2001 From: Hari Singh <15615702+singhhari@users.noreply.github.com> Date: Thu, 8 Oct 2020 11:30:38 -0400 Subject: [PATCH 2/2] Cleaning up the end date logic into a separate method --- Library/SharedFunctions.swift | 14 ++++++++++++- Library/SharedFunctionsTests.swift | 21 +++++++++++++++++++ .../RewardAddOnSelectionViewModel.swift | 5 ++--- 3 files changed, 36 insertions(+), 4 deletions(-) diff --git a/Library/SharedFunctions.swift b/Library/SharedFunctions.swift index f708ca3ea1..303b01b2e2 100644 --- a/Library/SharedFunctions.swift +++ b/Library/SharedFunctions.swift @@ -366,7 +366,7 @@ public func rewardsCarouselCanNavigateToReward(_ reward: Reward, in project: Pro } /** - Determines if a start date from a given reward predates the current date. + Determines if a start date from a given reward/add on predates the current date. - parameter reward: The reward being evaluated @@ -376,3 +376,15 @@ public func isStartDateBeforeToday(for reward: Reward) -> Bool { return (reward.startsAt == nil || (reward.startsAt ?? 0) <= AppEnvironment.current.dateType.init() .timeIntervalSince1970) } + +/** + Determines if an end date from a given reward/add on is after the current date. + + - parameter reward: The reward being evaluated + + - returns: A Bool representing whether the reward has an end date after to the current date/time. + */ +public func isEndDateAfterToday(for reward: Reward) -> Bool { + return (reward.endsAt == nil || (reward.endsAt ?? 0) >= AppEnvironment.current.dateType.init() + .timeIntervalSince1970) +} diff --git a/Library/SharedFunctionsTests.swift b/Library/SharedFunctionsTests.swift index 5ed40a68fa..2084ab5c7c 100644 --- a/Library/SharedFunctionsTests.swift +++ b/Library/SharedFunctionsTests.swift @@ -325,4 +325,25 @@ final class SharedFunctionsTests: TestCase { XCTAssertFalse(isStartDateBeforeToday(for: reward)) } + + func testIsEndDateAfterToday_Reward_EndsAt_Nil() { + let reward = Reward.template + |> Reward.lens.endsAt .~ nil + + XCTAssertTrue(isEndDateAfterToday(for: reward)) + } + + func testIsEndDateAfterToday_Reward_EndsAt_PastDate() { + let reward = Reward.template + |> Reward.lens.endsAt .~ (MockDate().timeIntervalSince1970 - 60) + + XCTAssertFalse(isEndDateAfterToday(for: reward)) + } + + func testIsEndDateAfterToday_Reward_EndsAt_FutureDate() { + let reward = Reward.template + |> Reward.lens.endsAt .~ (MockDate().timeIntervalSince1970 + 60) + + XCTAssertTrue(isEndDateAfterToday(for: reward)) + } } diff --git a/Library/ViewModels/RewardAddOnSelectionViewModel.swift b/Library/ViewModels/RewardAddOnSelectionViewModel.swift index 21092750d8..e76bf66e59 100644 --- a/Library/ViewModels/RewardAddOnSelectionViewModel.swift +++ b/Library/ViewModels/RewardAddOnSelectionViewModel.swift @@ -380,9 +380,8 @@ private func addOnIsAvailable(_ addOn: Reward, in project: Project) -> Bool { let isUnlimitedOrAvailable = addOn.limit == nil || addOn.remaining ?? 0 > 0 // Assuming the user has not backed the addOn, we only display if it's within range of the start and end date - let hasNoTimeLimitOrIsWithinRange = isStartDateBeforeToday(for: addOn) && - (addOn.endsAt == nil || (addOn.endsAt ?? 0) >= AppEnvironment.current.dateType.init() - .timeIntervalSince1970) + let hasNoTimeLimitOrIsWithinRange = isStartDateBeforeToday(for: addOn) && isEndDateAfterToday(for: addOn) + return [ project.state == .live, hasNoTimeLimitOrIsWithinRange,