Skip to content

Commit

Permalink
[MBL-1688] Estimated Shipping Range Should Include Add-Ons Estimates (#…
Browse files Browse the repository at this point in the history
…2143)

* create new VC and VCTests files

* create new VM and VMTests files

* revert PostCampaignViewController

* revert PostCampaignViewModel

* remove snapshots under the old VC and add new ones for the properly named NoShippingPostCampaignCheckoutVC

* fix snapshots

* include add-ons shipping estimate in displayed range when applicable

* include add-ons shipping estimate in displayed range when applicable

* add test

* sum shipping mins and maxes + cleanup

* pr feedback
  • Loading branch information
scottkicks authored Sep 5, 2024
1 parent 427b313 commit f1cd7bd
Show file tree
Hide file tree
Showing 6 changed files with 87 additions and 39 deletions.
75 changes: 47 additions & 28 deletions Library/SharedFunctions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -662,25 +662,16 @@ public func isRewardDigital(_ reward: Reward?) -> Bool {
}

public func estimatedShippingText(
for reward: Reward,
for rewards: [Reward],
project: Project,
selectedShippingRule: ShippingRule
) -> String {
guard reward.shipping.enabled else { return "" }

/// Make sure the current reward has shipping rules and that one of them matches the selected shipping rule (from the locations dropdown).
guard let shippingRules = reward.shippingRules,
let currentRewardShippingRule = shippingRules
.first(where: { $0.location.country == selectedShippingRule.location.country })
else {
return ""
}
let (estimatedMin, estimatedMax) = estimatedMinMax(
from: rewards,
selectedShippingRule: selectedShippingRule
)

guard let estimatedMin = currentRewardShippingRule.estimatedMin?.amount,
let estimatedMax = currentRewardShippingRule.estimatedMax?.amount,
estimatedMin > 0 || estimatedMax > 0 else {
return ""
}
guard estimatedMin > 0, estimatedMax > 0 else { return "" }

let currentCountry = project.stats.currentCountry ?? Project.Country.us

Expand All @@ -706,25 +697,18 @@ public func estimatedShippingText(
}

public func estimatedShippingConversionText(
for reward: Reward,
for rewards: [Reward],
project: Project,
selectedShippingRule: ShippingRule
) -> String {
guard project.stats.needsConversion else { return "" }

/// Make sure the current reward has shipping rules and that one of them matches the selected shipping rule (from the locations dropdown).
guard let shippingRules = reward.shippingRules,
let selectedShippingRule = shippingRules
.first(where: { $0.location.country == selectedShippingRule.location.country })
else {
return ""
}
let (estimatedMin, estimatedMax) = estimatedMinMax(
from: rewards,
selectedShippingRule: selectedShippingRule
)

guard let estimatedMin = selectedShippingRule.estimatedMin?.amount,
let estimatedMax = selectedShippingRule.estimatedMax?.amount,
estimatedMin > 0 || estimatedMax > 0 else {
return ""
}
guard estimatedMin > 0, estimatedMax > 0 else { return "" }

let convertedMin = estimatedMin * Double(project.stats.currentCurrencyRate ?? project.stats.staticUsdRate)
let convertedMax = estimatedMax * Double(project.stats.currentCurrencyRate ?? project.stats.staticUsdRate)
Expand Down Expand Up @@ -764,3 +748,38 @@ public func attributedCurrency(withProject project: Project, total: Double) -> N
superscriptAttributes: checkoutCurrencySuperscriptAttributes()
)
}

private func estimatedMinMax(
from rewards: [Reward],
selectedShippingRule: ShippingRule
) -> (Double, Double) {
var min: Double = 0
var max: Double = 0

rewards.forEach { reward in
guard reward.shipping.enabled, let shippingRules = reward.shippingRules else { return }

var shippingRule: ShippingRule?

/// If the reward's shipping prefernce is Anywhere in the world, use its first and only shipping rule.
/// Else use the rule that matches the selected shipping rule (from the locations dropdown).
shippingRule = reward.shipping.preference == .unrestricted
? shippingRules.first
: shippingRules
.first(where: { $0.location.country == selectedShippingRule.location.country })

guard let shipping = shippingRule else { return }

/// Verify there are estimated amounts greater than 0
guard let estimatedMin = shipping.estimatedMin?.amount,
let estimatedMax = shipping.estimatedMax?.amount,
estimatedMin > 0 || estimatedMax > 0 else {
return
}

min += estimatedMin
max += estimatedMax
}

return (min, max)
}
28 changes: 28 additions & 0 deletions Library/SharedFunctionsTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -671,4 +671,32 @@ final class SharedFunctionsTests: TestCase {

XCTAssertTrue(isRewardDigital(reward))
}

func test_estimatedShippingText() {
let rewardShippingRule = ShippingRule.template
|> ShippingRule.lens.estimatedMin .~ Money(amount: 2)
|> ShippingRule.lens.estimatedMax .~ Money(amount: 7)
let addOnShippingRule = ShippingRule.template
|> ShippingRule.lens.estimatedMin .~ Money(amount: 1)
|> ShippingRule.lens.estimatedMax .~ Money(amount: 5)

let reward = Reward.template
|> Reward.lens.shipping .~ (.template |> Reward.Shipping.lens.enabled .~ true)
|> Reward.lens.shippingRules .~ [rewardShippingRule]
|> Reward.lens.id .~ 99
let addOn = Reward.template
|> Reward.lens.shipping .~ (.template |> Reward.Shipping.lens.enabled .~ true)
|> Reward.lens.id .~ 5
|> Reward.lens.shippingRules .~ [addOnShippingRule]

let project = Project.template

let estimatedShipping = estimatedShippingText(
for: [reward, addOn],
project: project,
selectedShippingRule: .template
)

XCTAssertEqual(estimatedShipping, "$3-$12")
}
}
8 changes: 4 additions & 4 deletions Library/ViewModels/NoShippingPledgeViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -313,13 +313,13 @@ public class NoShippingPledgeViewModel: NoShippingPledgeViewModelType, NoShippin
self.pledgeDisclaimerViewDidTapLearnMoreSignal.mapConst(.trust)
)

self.configureEstimatedShippingView = Signal.combineLatest(project, baseReward, selectedShippingRule)
.map { project, reward, shippingRule in
self.configureEstimatedShippingView = Signal.combineLatest(project, rewards, selectedShippingRule)
.map { project, rewards, shippingRule in
guard let rule = shippingRule else { return ("", "") }

return (
estimatedShippingText(for: reward, project: project, selectedShippingRule: rule),
estimatedShippingConversionText(for: reward, project: project, selectedShippingRule: rule)
estimatedShippingText(for: rewards, project: project, selectedShippingRule: rule),
estimatedShippingConversionText(for: rewards, project: project, selectedShippingRule: rule)
)
}

Expand Down
11 changes: 6 additions & 5 deletions Library/ViewModels/NoShippingPostCampaignCheckoutViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ public class NoShippingPostCampaignCheckoutViewModel: NoShippingPostCampaignChec
let context = initialData.map(\.context)
let checkoutId = initialData.map(\.checkoutId)
let backingId = initialData.map(\.backingId)
let rewards = initialData.map(\.rewards)
let baseReward = initialData.map(\.rewards).map(\.first)
let project = initialData.map(\.project)
let selectedShippingRule = initialData.map(\.selectedShippingRule)
Expand Down Expand Up @@ -108,13 +109,13 @@ public class NoShippingPostCampaignCheckoutViewModel: NoShippingPostCampaignChec
)
}

self.configureEstimatedShippingView = Signal.combineLatest(project, baseReward, selectedShippingRule)
.map { project, baseReward, shippingRule in
guard let rule = shippingRule, let reward = baseReward else { return ("", "") }
self.configureEstimatedShippingView = Signal.combineLatest(project, rewards, selectedShippingRule)
.map { project, rewards, shippingRule in
guard let rule = shippingRule else { return ("", "") }

return (
estimatedShippingText(for: reward, project: project, selectedShippingRule: rule),
estimatedShippingConversionText(for: reward, project: project, selectedShippingRule: rule)
estimatedShippingText(for: rewards, project: project, selectedShippingRule: rule),
estimatedShippingConversionText(for: rewards, project: project, selectedShippingRule: rule)
)
}

Expand Down
2 changes: 1 addition & 1 deletion Library/ViewModels/RewardAddOnCardViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ public final class RewardAddOnCardViewModel: RewardAddOnCardViewModelType, Rewar

self.estimatedShippingLabelText = Signal.combineLatest(reward, project, shippingRule)
.map { reward, project, shippingRule in
estimatedShippingText(for: reward, project: project, selectedShippingRule: shippingRule)
estimatedShippingText(for: [reward], project: project, selectedShippingRule: shippingRule)
}

self.estimatedShippingStackViewHidden = Signal.combineLatest(reward, self.estimatedShippingLabelText)
Expand Down
2 changes: 1 addition & 1 deletion Library/ViewModels/RewardCardViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ public final class RewardCardViewModel: RewardCardViewModelType, RewardCardViewM

self.estimatedShippingLabelText = Signal.combineLatest(reward, project, currentShippingRule)
.map { reward, project, shippingRule in
estimatedShippingText(for: reward, project: project, selectedShippingRule: shippingRule)
estimatedShippingText(for: [reward], project: project, selectedShippingRule: shippingRule)
}

self.estimatedDeliveryDateLabelText = reward.map(estimatedDeliveryDateText(with:)).skipNil()
Expand Down

0 comments on commit f1cd7bd

Please sign in to comment.