Files
purchases-ios/Sources/Purchasing/StoreKitAbstractions/Test Data/TestStoreProduct.swift
T
Rick 9ebd514f15 Format test store price strings with same currency code and locale as localizedPriceString (#5784)
* Passing price currencyCode to the TestStore product to use when formatting a price string

* Use the same locale for formatting localizedPriceString as well as other price strings for Test Store product

* Fixed test compilation + ensuring backwards compatibility in TestStoreProduct initializer

* Added unit tests

* Fixed swiftlint warnings

* Deprecated old TestProduct initializer and updated all tests to pass currencyCode and locale for proper price formatting

* Added API test for new initializer

* Fixed test compilation + updated remaining uses of TestStoreProduct

* Reusing Locale extension across packages

* Fixed import

* Fixed test imports

* Implemented PR feedback

* Updated doc + fixed test compilation

* Fixed test compilation
2025-12-15 13:31:52 +01:00

174 lines
6.5 KiB
Swift

//
// Copyright RevenueCat Inc. All Rights Reserved.
//
// Licensed under the MIT License (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://opensource.org/licenses/MIT
//
// TestStoreProduct.swift
//
// Created by Nacho Soto on 6/23/23.
import Foundation
/// A type that contains the necessary data to create a ``StoreProduct``.
/// This can be used to create mock data for tests or SwiftUI previews.
///
/// Example:
/// ```swift
/// let product = TestStoreProduct(
/// localizedTitle: "PRO monthly",
/// price: 3.99,
/// localizedPriceString: "$3.99",
/// productIdentifier: "com.revenuecat.product",
/// productType: .autoRenewableSubscription,
/// localizedDescription: "Description",
/// subscriptionGroupIdentifier: "group",
/// subscriptionPeriod: .init(value: 1, unit: .month)
/// )
///
/// let offering = Offering(
/// identifier: "offering",
/// serverDescription: "Main offering",
/// metadata: [:],
/// availablePackages: [
/// .init(
/// identifier: "monthly",
/// packageType: .monthly,
/// storeProduct: product.toStoreProduct(),
/// offeringIdentifier: offering
/// ),
/// ]
/// )
/// ```
public struct TestStoreProduct {
// Note: this class inherits its docs from `StoreProductType`
// swiftlint:disable missing_docs
public var localizedTitle: String
public var price: Decimal
public var currencyCode: String?
public var localizedPriceString: String
public var localizedPricePerDay: String?
public var localizedPricePerWeek: String?
public var localizedPricePerMonth: String?
public var localizedPricePerYear: String?
public var productIdentifier: String
public var productType: StoreProduct.ProductType
public var localizedDescription: String
public var subscriptionGroupIdentifier: String?
public var subscriptionPeriod: SubscriptionPeriod?
public var isFamilyShareable: Bool
public var introductoryDiscount: StoreProductDiscount?
public var discounts: [StoreProductDiscount]
public var locale: Locale
// swiftlint:disable:next line_length
@available(*, deprecated, message: "Use init(localizedTitle:price:currencyCode:localizedPriceString:productIdentifier:productType:localizedDescription:subscriptionGroupIdentifier:subscriptionPeriod:isFamilyShareable:introductoryDiscount:discounts:locale:) instead")
public init(
localizedTitle: String,
price: Decimal,
localizedPriceString: String,
productIdentifier: String,
productType: StoreProduct.ProductType,
localizedDescription: String,
subscriptionGroupIdentifier: String? = nil,
subscriptionPeriod: SubscriptionPeriod? = nil,
isFamilyShareable: Bool = false,
introductoryDiscount: TestStoreProductDiscount? = nil,
discounts: [TestStoreProductDiscount] = [],
locale: Locale = .current
) {
self.localizedTitle = localizedTitle
self.price = price
self.currencyCode = locale.rc_currencyCode
self.localizedPriceString = localizedPriceString
self.productIdentifier = productIdentifier
self.productType = productType
self.localizedDescription = localizedDescription
self.subscriptionGroupIdentifier = subscriptionGroupIdentifier
self.subscriptionPeriod = subscriptionPeriod
self.isFamilyShareable = isFamilyShareable
self.introductoryDiscount = introductoryDiscount?.toStoreProductDiscount()
self.discounts = discounts.map { $0.toStoreProductDiscount() }
self.locale = locale
}
/// Creates a new ``TestStoreProduct``.
///
/// - Parameters:
/// - localizedTitle: The localized title of the product
/// - price: The price of the product
/// - currencyCode: The currency code (e.g., "USD", "EUR").
/// - localizedPriceString: The localized price string (e.g., "$3.99")
/// - productIdentifier: The product identifier
/// - productType: The type of product
/// - localizedDescription: The localized description
/// - subscriptionGroupIdentifier: Optional subscription group identifier
/// - subscriptionPeriod: Optional subscription period
/// - isFamilyShareable: Whether the product is family shareable
/// - introductoryDiscount: Optional introductory discount
/// - discounts: Array of discounts
/// - locale: The locale that should be used when formatting prices.
/// It is important that this matches with the price strings passed (e.g. localizedPriceString)
public init(
localizedTitle: String,
price: Decimal,
currencyCode: String,
localizedPriceString: String,
productIdentifier: String,
productType: StoreProduct.ProductType,
localizedDescription: String,
subscriptionGroupIdentifier: String? = nil,
subscriptionPeriod: SubscriptionPeriod? = nil,
isFamilyShareable: Bool = false,
introductoryDiscount: TestStoreProductDiscount? = nil,
discounts: [TestStoreProductDiscount] = [],
locale: Locale
) {
self.localizedTitle = localizedTitle
self.price = price
self.currencyCode = currencyCode
self.localizedPriceString = localizedPriceString
self.productIdentifier = productIdentifier
self.productType = productType
self.localizedDescription = localizedDescription
self.subscriptionGroupIdentifier = subscriptionGroupIdentifier
self.subscriptionPeriod = subscriptionPeriod
self.isFamilyShareable = isFamilyShareable
self.introductoryDiscount = introductoryDiscount?.toStoreProductDiscount()
self.discounts = discounts.map { $0.toStoreProductDiscount() }
self.locale = locale
}
// swiftlint:enable missing_docs
private let priceFormatterProvider: PriceFormatterProvider = .init()
}
// Ensure consistency with the internal type
extension TestStoreProduct: StoreProductType {
internal var productCategory: StoreProduct.ProductCategory { return self.productType.productCategory }
internal var priceFormatter: NumberFormatter? {
return self.currencyCode.map {
self.priceFormatterProvider.priceFormatterForSK2(withCurrencyCode: $0, locale: self.locale)
}
}
}
extension TestStoreProduct {
/// Convert it into a ``StoreProduct``.
public func toStoreProduct() -> StoreProduct {
return .from(product: self)
}
}