Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[MBL-1273] Implement Create Checkout Mutation #1982

Merged
merged 7 commits into from
Mar 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,13 @@ public enum ConfirmDetailsLayout {

protocol ConfirmDetailsViewControllerDelegate: AnyObject {}

final class ConfirmDetailsViewController: UIViewController {
final class ConfirmDetailsViewController: UIViewController, MessageBannerViewControllerPresenting {
// MARK: - Properties

public weak var delegate: ConfirmDetailsViewControllerDelegate?

internal var messageBannerViewController: MessageBannerViewController?

private lazy var titleLabel = UILabel(frame: .zero)

/// The pledge and bonus steppers used to change the pledge amount
Expand Down Expand Up @@ -74,6 +76,7 @@ final class ConfirmDetailsViewController: UIViewController {
private lazy var continueCTAView: ConfirmDetailsContinueCTAView = {
ConfirmDetailsContinueCTAView(frame: .zero)
|> \.translatesAutoresizingMaskIntoConstraints .~ false
|> \.delegate .~ self
}()

private lazy var keyboardDimissingTapGestureRecognizer: UITapGestureRecognizer = {
Expand Down Expand Up @@ -119,14 +122,10 @@ final class ConfirmDetailsViewController: UIViewController {
_ = self
|> \.title .~ Strings.Back_this_project()

self.continueCTAView.continueButton.addTarget(
self,
action: #selector(self.continueButtonTapped),
for: .touchUpInside
)

self.view.addGestureRecognizer(self.keyboardDimissingTapGestureRecognizer)

self.messageBannerViewController = self.configureMessageBannerViewController(on: self)

self.configureChildViewControllers()
self.setupConstraints()

Expand Down Expand Up @@ -272,6 +271,19 @@ final class ConfirmDetailsViewController: UIViewController {
self?.rootScrollView.handleKeyboardVisibilityDidChange(change)
}

self.viewModel.outputs.createCheckoutSuccess
.observeForUI()
.observeValues { checkoutId in
// TODO: Navigate to checkout screen
print("navigate to checkout screen with checkoutID: \(checkoutId)")
}

self.viewModel.outputs.showErrorBannerWithMessage
.observeForControllerAction()
.observeValues { [weak self] errorMessage in
self?.messageBannerViewController?.showBanner(with: .error, message: errorMessage)
}

self.pledgeAmountViewController.view.rac.hidden = self.viewModel.outputs.pledgeAmountViewHidden

self.shippingLocationViewController.view.rac.hidden = self.viewModel.outputs.shippingLocationViewHidden
Expand All @@ -292,11 +304,6 @@ final class ConfirmDetailsViewController: UIViewController {

// MARK: - Actions

@objc func continueButtonTapped() {
// TODO: Navigate to Checkout Screen
// self.viewModel.inputs.continueButtonTapped()
}

@objc private func dismissKeyboard() {
self.view.endEditing(true)
}
Expand Down Expand Up @@ -327,6 +334,14 @@ extension ConfirmDetailsViewController: PledgeShippingLocationViewControllerDele
func pledgeShippingLocationViewControllerFailedToLoad(_: PledgeShippingLocationViewController) {}
}

// MARK: - ConfirmDetailsContinueCTAViewDelegate

extension ConfirmDetailsViewController: ConfirmDetailsContinueCTAViewDelegate {
func continueButtonTapped() {
self.viewModel.inputs.continueCTATapped()
}
}

// MARK: - Styles

private let titleLabelStyle: LabelStyle = { label in
Expand Down
47 changes: 47 additions & 0 deletions Library/ViewModels/ConfirmDetailsViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import ReactiveSwift

public protocol ConfirmDetailsViewModelInputs {
func configure(with data: PledgeViewData)
func continueCTATapped()
func pledgeAmountViewControllerDidUpdate(with data: PledgeAmountData)
func shippingRuleSelected(_ shippingRule: ShippingRule)
func viewDidLoad()
Expand All @@ -22,6 +23,8 @@ public protocol ConfirmDetailsViewModelOutputs {
var configurePledgeSummaryViewControllerWithData: Signal<PledgeSummaryViewData, Never> { get }
var configureShippingLocationViewWithData: Signal<PledgeShippingLocationViewData, Never> { get }
var configureShippingSummaryViewWithData: Signal<PledgeShippingSummaryViewData, Never> { get }
var createCheckoutSuccess: Signal<String, Never> { get }
var showErrorBannerWithMessage: Signal<String, Never> { get }
var localPickupViewHidden: Signal<Bool, Never> { get }
var pledgeAmountViewHidden: Signal<Bool, Never> { get }
var pledgeRewardsSummaryViewHidden: Signal<Bool, Never> { get }
Expand Down Expand Up @@ -51,6 +54,7 @@ public class ConfirmDetailsViewModel: ConfirmDetailsViewModelType, ConfirmDetail
let selectedQuantities = initialData.map(\.selectedQuantities)
let selectedLocationId = initialData.map(\.selectedLocationId)
let context = initialData.map(\.context)
let refTag = initialData.map(\.refTag)

let backing = project.map { $0.personalization.backing }.skipNil()

Expand Down Expand Up @@ -313,10 +317,51 @@ public class ConfirmDetailsViewModel: ConfirmDetailsViewModelType, ConfirmDetail
}

self.configureCTAWithPledgeTotal = Signal.combineLatest(project, pledgeTotal)

// MARK: CreateCheckout GraphQL Call

let pledgeDetailsData = Signal.combineLatest(
project,
rewards,
pledgeTotal,
refTag
)

let createCheckoutEvents = pledgeDetailsData
.takeWhen(self.continueCTATappedProperty.signal)
.map { project, rewards, pledgeTotal, refTag in
let rewardsIDs = rewards.map { $0.graphID }

return CreateCheckoutInput(
projectId: project.graphID,
amount: String(format: "%.2f", pledgeTotal),
locationId: "\(project.location.id)",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unconcerned about this one because it's an int.

rewardIds: rewardsIDs,
refParam: refTag?.stringTag
)
}
.switchMap { input in
AppEnvironment.current.apiService
.createCheckout(input: input)
.ksr_delay(AppEnvironment.current.apiDelayInterval, on: AppEnvironment.current.scheduler)
.materialize()
}

self.createCheckoutSuccess = createCheckoutEvents.values()
.map { $0.checkout.id }

// TODO: [MBL-1217] Update string once translations are done
self.showErrorBannerWithMessage = createCheckoutEvents.errors()
.map { _ in Strings.Something_went_wrong_please_try_again() }
}

// MARK: - Inputs

private let continueCTATappedProperty = MutableProperty(())
public func continueCTATapped() {
self.continueCTATappedProperty.value = ()
}

private let configureWithDataProperty = MutableProperty<PledgeViewData?>(nil)
public func configure(with data: PledgeViewData) {
self.configureWithDataProperty.value = data
Expand Down Expand Up @@ -355,6 +400,8 @@ public class ConfirmDetailsViewModel: ConfirmDetailsViewModelType, ConfirmDetail
public let configurePledgeSummaryViewControllerWithData: Signal<PledgeSummaryViewData, Never>
public let configureShippingLocationViewWithData: Signal<PledgeShippingLocationViewData, Never>
public let configureShippingSummaryViewWithData: Signal<PledgeShippingSummaryViewData, Never>
public let createCheckoutSuccess: Signal<String, Never>
public let showErrorBannerWithMessage: Signal<String, Never>
public let localPickupViewHidden: Signal<Bool, Never>
public let pledgeAmountViewHidden: Signal<Bool, Never>
public let pledgeRewardsSummaryViewHidden: Signal<Bool, Never>
Expand Down
92 changes: 92 additions & 0 deletions Library/ViewModels/ConfirmDetailsViewModelTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,15 @@ final class ConfirmDetailsViewModelTests: TestCase {
private let configureShippingLocationViewWithDataReward = TestObserver<Reward, Never>()
private let configureShippingLocationViewWithDataShowAmount = TestObserver<Bool, Never>()

private let createCheckoutSuccess = TestObserver<String, Never>()

private let localPickupViewHidden = TestObserver<Bool, Never>()
private let pledgeAmountViewHidden = TestObserver<Bool, Never>()
private let shippingLocationViewHidden = TestObserver<Bool, Never>()
private let shippingSummaryViewHidden = TestObserver<Bool, Never>()

private let showErrorBannerWithMessage = TestObserver<String, Never>()

override func setUp() {
super.setUp()

Expand All @@ -51,10 +55,14 @@ final class ConfirmDetailsViewModelTests: TestCase {
self.vm.outputs.configurePledgeSummaryViewControllerWithData.map { $0.0 }
.observe(self.configurePledgeSummaryViewControllerWithDataProject.observer)

self.vm.outputs.createCheckoutSuccess.observe(self.createCheckoutSuccess.observer)

self.vm.outputs.localPickupViewHidden.observe(self.localPickupViewHidden.observer)
self.vm.outputs.pledgeAmountViewHidden.observe(self.pledgeAmountViewHidden.observer)
self.vm.outputs.shippingLocationViewHidden.observe(self.shippingLocationViewHidden.observer)
self.vm.outputs.shippingSummaryViewHidden.observe(self.shippingSummaryViewHidden.observer)

self.vm.outputs.showErrorBannerWithMessage.observe(self.showErrorBannerWithMessage.observer)
}

func testPledgeContext_LoggedIn() {
Expand Down Expand Up @@ -800,4 +808,88 @@ final class ConfirmDetailsViewModelTests: TestCase {
PledgeLocalPickupViewData(locationName: "Los Angeles, CA")
])
}

func testContinueButton_CallsCreateBackingMutation_Success() {
let createCheckout = CreateCheckoutEnvelope.Checkout(id: "id", paymentUrl: "paymentUrl")

let mockService = MockService(
createCheckoutResult:
Result.success(CreateCheckoutEnvelope(checkout: createCheckout))
)

withEnvironment(apiService: mockService, currentUser: .template) {
let reward = Reward.template
|> Reward.lens.shipping.enabled .~ true
|> Reward.lens.shipping.preference .~ .unrestricted
|> Reward.lens.localPickup .~ .losAngeles
let addOnReward1 = Reward.template
|> Reward.lens.id .~ 2
let project = Project.template
|> Project.lens.rewardData.rewards .~ [reward]

let data = PledgeViewData(
project: project,
rewards: [reward, addOnReward1],
selectedQuantities: [reward.id: 1, addOnReward1.id: 1],
selectedLocationId: ShippingRule.template.id,
refTag: nil,
context: .pledge
)

self.vm.inputs.configure(with: data)
self.vm.inputs.viewDidLoad()

self.vm.inputs.continueCTATapped()

self.scheduler.run()

self.showErrorBannerWithMessage.assertDidNotEmitValue()

self.createCheckoutSuccess.assertDidEmitValue()
self.createCheckoutSuccess.assertValue("id")
}
}

func testContinueButton_CallsCreateBackingMutation_Failure_ShowsErrorMessageBanner() {
let createCheckout = CreateCheckoutEnvelope.Checkout(id: "id", paymentUrl: "paymentUrl")
let errorUnknown = ErrorEnvelope(
errorMessages: ["Something went wrong yo."],
ksrCode: .UnknownCode,
httpCode: 400,
exception: nil
)

let mockService = MockService(createCheckoutResult: Result.failure(errorUnknown))

withEnvironment(apiService: mockService, currentUser: .template) {
let reward = Reward.template
|> Reward.lens.shipping.enabled .~ true
|> Reward.lens.shipping.preference .~ .unrestricted
|> Reward.lens.localPickup .~ .losAngeles
let addOnReward1 = Reward.template
|> Reward.lens.id .~ 2
let project = Project.template
|> Project.lens.rewardData.rewards .~ [reward]

let data = PledgeViewData(
project: project,
rewards: [reward, addOnReward1],
selectedQuantities: [reward.id: 1, addOnReward1.id: 1],
selectedLocationId: ShippingRule.template.id,
refTag: nil,
context: .pledge
)

self.vm.inputs.configure(with: data)
self.vm.inputs.viewDidLoad()

self.vm.inputs.continueCTATapped()

self.scheduler.run()

self.createCheckoutSuccess.assertDidNotEmitValue()

self.showErrorBannerWithMessage.assertValue(Strings.Something_went_wrong_please_try_again())
}
}
}