Skip to content

Commit

Permalink
Paywalls: more unit tests for purchasing state (#2931)
Browse files Browse the repository at this point in the history
Follow up to #2930.

Added `PresentIfNecessaryTests` and `PurchaseHandlerTests`.
This also removes the last source of "crashes" whenever `Purchases`
isn't configured, leading to errors instead.
  • Loading branch information
NachoSoto committed Jul 31, 2023
1 parent d97f1a0 commit 2f76a36
Show file tree
Hide file tree
Showing 7 changed files with 137 additions and 6 deletions.
4 changes: 4 additions & 0 deletions RevenueCatUI/Helpers/TrialOrIntroEligibilityChecker.swift
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@ final class TrialOrIntroEligibilityChecker: ObservableObject {
self.checker = checker
}

static func `default`() -> Self? {
return Purchases.isConfigured ? .init() : nil
}

}

@available(iOS 13.0, tvOS 13.0, macOS 10.15, watchOS 6.2, *)
Expand Down
12 changes: 8 additions & 4 deletions RevenueCatUI/PaywallView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@ public struct PaywallView: View {
self.init(
offering: nil,
mode: mode,
introEligibility: Purchases.isConfigured ? .init() : nil,
purchaseHandler: Purchases.isConfigured ? .init() : nil
introEligibility: .default(),
purchaseHandler: .default()
)
}

Expand All @@ -39,8 +39,8 @@ public struct PaywallView: View {
self.init(
offering: offering,
mode: mode,
introEligibility: Purchases.isConfigured ? .init() : nil,
purchaseHandler: Purchases.isConfigured ? .init() : nil
introEligibility: .default(),
purchaseHandler: .default()
)
}

Expand Down Expand Up @@ -77,6 +77,10 @@ public struct PaywallView: View {
.transition(Self.transition)
.task {
do {
guard Purchases.isConfigured else {
throw PaywallError.purchasesNotConfigured
}

guard let offering = try await Purchases.shared.offerings().current else {
throw PaywallError.noCurrentOffering
}
Expand Down
4 changes: 4 additions & 0 deletions RevenueCatUI/Purchasing/PurchaseHandler.swift
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,10 @@ final class PurchaseHandler: ObservableObject {
self.restoreBlock = restorePurchases
}

static func `default`() -> Self? {
return Purchases.isConfigured ? .init() : nil
}

}

@available(iOS 16.0, macOS 13.0, tvOS 16.0, *)
Expand Down
18 changes: 16 additions & 2 deletions RevenueCatUI/View+PresentPaywall.swift
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,9 @@ extension View {
// Visible overload for tests
func presentPaywallIfNecessary(
mode: PaywallViewMode = .default,
offering: Offering? = nil,
introEligibility: TrialOrIntroEligibilityChecker? = nil,
purchaseHandler: PurchaseHandler? = nil,
shouldDisplay: @escaping @Sendable (CustomerInfo) -> Bool,
purchaseCompleted: PurchaseCompletedHandler? = nil,
customerInfoFetcher: @escaping CustomerInfoFetcher
Expand All @@ -86,7 +89,10 @@ extension View {
shouldDisplay: shouldDisplay,
purchaseCompleted: purchaseCompleted,
mode: mode,
customerInfoFetcher: customerInfoFetcher
offering: offering,
customerInfoFetcher: customerInfoFetcher,
introEligibility: introEligibility,
purchaseHandler: purchaseHandler
))
}

Expand All @@ -99,16 +105,24 @@ private struct PresentingPaywallModifier: ViewModifier {
var shouldDisplay: @Sendable (CustomerInfo) -> Bool
var purchaseCompleted: PurchaseCompletedHandler?
var mode: PaywallViewMode
var offering: Offering?

var customerInfoFetcher: View.CustomerInfoFetcher
var introEligibility: TrialOrIntroEligibilityChecker?
var purchaseHandler: PurchaseHandler?

@State
private var isDisplayed = false

func body(content: Content) -> some View {
content
.sheet(isPresented: self.$isDisplayed) {
PaywallView(mode: self.mode)
PaywallView(
offering: self.offering,
mode: self.mode,
introEligibility: self.introEligibility ?? .default(),
purchaseHandler: self.purchaseHandler ?? .default()
)
.onPurchaseCompleted {
self.purchaseCompleted?($0)
}
Expand Down
48 changes: 48 additions & 0 deletions Tests/RevenueCatUITests/PresentIfNecessaryTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
//
// PresentIfNecessaryTests.swift
//
//
// Created by Nacho Soto on 7/31/23.
//

import Nimble
import RevenueCat
@testable import RevenueCatUI
import SwiftUI
import XCTest

#if !os(macOS)

@available(iOS 16.0, macOS 13.0, tvOS 16.0, watchOS 9.0, *)
@MainActor
class PresentIfNecessaryTests: TestCase {

func testPresentWithPurchaseHandler() throws {
var customerInfo: CustomerInfo?

Text("")
.presentPaywallIfNecessary(offering: Self.offering,
introEligibility: .producing(eligibility: .eligible),
purchaseHandler: Self.purchaseHandler) { _ in
return true
} purchaseCompleted: {
customerInfo = $0
} customerInfoFetcher: {
return TestData.customerInfo
}
.addToHierarchy()

Task {
_ = try await Self.purchaseHandler.purchase(package: Self.package)
}

expect(customerInfo).toEventually(be(TestData.customerInfo))
}

private static let purchaseHandler: PurchaseHandler = .mock()
private static let offering = TestData.offeringWithNoIntroOffer
private static let package = TestData.annualPackage

}

#endif
57 changes: 57 additions & 0 deletions Tests/RevenueCatUITests/Purchasing/PurchaseHandlerTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
//
// PurchaseHandlerTests.swift
//
//
// Created by Nacho Soto on 7/31/23.
//

import Nimble
import RevenueCat
@testable import RevenueCatUI
import XCTest

#if !os(macOS)

@available(iOS 16.0, macOS 13.0, tvOS 16.0, watchOS 9.0, *)
@MainActor
class PurchaseHandlerTests: TestCase {

func testInitialState() async throws {
let handler: PurchaseHandler = .mock()

expect(handler.purchasedCustomerInfo).to(beNil())
expect(handler.purchased) == false
expect(handler.restored) == false
expect(handler.actionInProgress) == false
}

func testPurchaseSetsCustomerInfo() async throws {
let handler: PurchaseHandler = .mock()

_ = try await handler.purchase(package: TestData.packageWithIntroOffer)

expect(handler.purchasedCustomerInfo) === TestData.customerInfo
expect(handler.purchased) == true
expect(handler.actionInProgress) == false
}

func testCancellingPurchase() async throws {
let handler: PurchaseHandler = .cancelling()

_ = try await handler.purchase(package: TestData.packageWithIntroOffer)
expect(handler.purchasedCustomerInfo).to(beNil())
expect(handler.purchased) == false
expect(handler.actionInProgress) == false
}

func testRestorePurchases() async throws {
let handler: PurchaseHandler = .mock()

_ = try await handler.restorePurchases()
expect(handler.restored) == true
expect(handler.actionInProgress) == false
}

}

#endif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit 2f76a36

Please sign in to comment.