From aa71bd31baa94d8aaddf1f11180c991aeff1ddeb Mon Sep 17 00:00:00 2001 From: Antonio Pallares Date: Wed, 5 Feb 2025 14:53:36 +0100 Subject: [PATCH 1/9] feat: add support for presenting draft paywalls under `@_spi(Internal)` attribute --- .../Data/PaywallViewConfiguration.swift | 5 ++++ RevenueCatUI/PaywallView.swift | 26 +++++++++++++++-- .../Responses/OfferingsResponse.swift | 2 +- Sources/Purchasing/Offering.swift | 28 ++++++++++++++++++- Sources/Purchasing/OfferingsFactory.swift | 11 ++++++++ 5 files changed, 68 insertions(+), 4 deletions(-) diff --git a/RevenueCatUI/Data/PaywallViewConfiguration.swift b/RevenueCatUI/Data/PaywallViewConfiguration.swift index f29dfe6572..0307e4c2b1 100644 --- a/RevenueCatUI/Data/PaywallViewConfiguration.swift +++ b/RevenueCatUI/Data/PaywallViewConfiguration.swift @@ -19,6 +19,7 @@ struct PaywallViewConfiguration { var mode: PaywallViewMode var fonts: PaywallFontProvider var displayCloseButton: Bool + let useDraftPaywall: Bool var introEligibility: TrialOrIntroEligibilityChecker? var purchaseHandler: PurchaseHandler var locale: Locale @@ -29,6 +30,7 @@ struct PaywallViewConfiguration { mode: PaywallViewMode = .default, fonts: PaywallFontProvider = DefaultPaywallFontProvider(), displayCloseButton: Bool = false, + useDraftPaywall: Bool = false, introEligibility: TrialOrIntroEligibilityChecker? = nil, purchaseHandler: PurchaseHandler, locale: Locale = .current @@ -38,6 +40,7 @@ struct PaywallViewConfiguration { self.mode = mode self.fonts = fonts self.displayCloseButton = displayCloseButton + self.useDraftPaywall = useDraftPaywall self.introEligibility = introEligibility self.purchaseHandler = purchaseHandler self.locale = locale @@ -72,6 +75,7 @@ extension PaywallViewConfiguration { mode: PaywallViewMode = .default, fonts: PaywallFontProvider = DefaultPaywallFontProvider(), displayCloseButton: Bool = false, + useDraftPaywall: Bool = false, introEligibility: TrialOrIntroEligibilityChecker? = nil, purchaseHandler: PurchaseHandler = PurchaseHandler.default(), locale: Locale = .current @@ -84,6 +88,7 @@ extension PaywallViewConfiguration { mode: mode, fonts: fonts, displayCloseButton: displayCloseButton, + useDraftPaywall: useDraftPaywall, introEligibility: introEligibility, purchaseHandler: handler, locale: locale diff --git a/RevenueCatUI/PaywallView.swift b/RevenueCatUI/PaywallView.swift index 57ea849b90..db165a47db 100644 --- a/RevenueCatUI/PaywallView.swift +++ b/RevenueCatUI/PaywallView.swift @@ -11,7 +11,7 @@ // // Created by Nacho Soto. -import RevenueCat +@_spi(Internal) import RevenueCat import SwiftUI #if !os(macOS) && !os(tvOS) @@ -31,6 +31,7 @@ public struct PaywallView: View { private let fonts: PaywallFontProvider private let displayCloseButton: Bool private let paywallViewOwnsPurchaseHandler: Bool + private let useDraftPaywall: Bool private var locale: Locale @@ -104,6 +105,24 @@ public struct PaywallView: View { displayCloseButton: Bool = false, performPurchase: PerformPurchase? = nil, performRestore: PerformRestore? = nil + ) { + self.init( + offering: offering, + fonts: fonts, + displayCloseButton: displayCloseButton, + useDraftPaywall: false, + performPurchase: performPurchase, + performRestore: performRestore + ) + } + + @_spi(Internal) public init( + offering: Offering, + fonts: PaywallFontProvider = DefaultPaywallFontProvider(), + displayCloseButton: Bool = false, + useDraftPaywall: Bool, + performPurchase: PerformPurchase? = nil, + performRestore: PerformRestore? = nil ) { let purchaseHandler = PurchaseHandler.default(performPurchase: performPurchase, performRestore: performRestore) self.init( @@ -111,6 +130,7 @@ public struct PaywallView: View { offering: offering, fonts: fonts, displayCloseButton: displayCloseButton, + useDraftPaywall: useDraftPaywall, purchaseHandler: purchaseHandler ) ) @@ -198,6 +218,7 @@ public struct PaywallView: View { } else if self.introEligibility.isConfigured, self.purchaseHandler.isConfigured { if let offering = self.offering, let customerInfo = self.customerInfo { self.paywallView(for: offering, + useDraftPaywall: false, // TODO: implement activelySubscribedProductIdentifiers: customerInfo.activeSubscriptions, fonts: self.fonts, checker: self.introEligibility, @@ -243,6 +264,7 @@ public struct PaywallView: View { // swiftlint:disable:next function_body_length private func paywallView( for offering: Offering, + useDraftPaywall: Bool, activelySubscribedProductIdentifiers: Set, fonts: PaywallFontProvider, checker: TrialOrIntroEligibilityChecker, @@ -253,7 +275,7 @@ public struct PaywallView: View { countries: offering.paywall?.zeroDecimalPlaceCountries ) - if let paywallComponents = offering.paywallComponents { + if let paywallComponents = useDraftPaywall ? offering.draftPaywallComponents : offering.paywallComponents { // For fallback view or footer let paywall: PaywallData = .createDefault(with: offering.availablePackages, diff --git a/Sources/Networking/Responses/OfferingsResponse.swift b/Sources/Networking/Responses/OfferingsResponse.swift index 33a0485938..6ab7e08544 100644 --- a/Sources/Networking/Responses/OfferingsResponse.swift +++ b/Sources/Networking/Responses/OfferingsResponse.swift @@ -33,7 +33,7 @@ struct OfferingsResponse { @DefaultDecodable.EmptyDictionary var metadata: [String: AnyDecodable] var paywallComponents: PaywallComponentsData? - + var draftPaywallComponents: PaywallComponentsData? } struct Placements { diff --git a/Sources/Purchasing/Offering.swift b/Sources/Purchasing/Offering.swift index 7fd863ed8a..5acd8dd71f 100644 --- a/Sources/Purchasing/Offering.swift +++ b/Sources/Purchasing/Offering.swift @@ -76,6 +76,11 @@ import Foundation */ public let paywallComponents: PaywallComponents? + /** + Draft paywall components configuration defined in RevenueCat dashboard. + */ + @_spi(Internal) public let draftPaywallComponents: PaywallComponents? + /** Array of ``Package`` objects available for purchase. */ @@ -176,12 +181,32 @@ import Foundation } /// Initialize an ``Offering`` given a list of ``Package``s. - public init( + public convenience init( + identifier: String, + serverDescription: String, + metadata: [String: Any] = [:], + paywall: PaywallData? = nil, + paywallComponents: PaywallComponents? = nil, + availablePackages: [Package] + ) { + self.init( + identifier: identifier, + serverDescription: serverDescription, + metadata: metadata, + paywall: paywall, + paywallComponents: paywallComponents, + draftPaywallComponents: nil, + availablePackages: availablePackages + ) + } + + init( identifier: String, serverDescription: String, metadata: [String: Any] = [:], paywall: PaywallData? = nil, paywallComponents: PaywallComponents? = nil, + draftPaywallComponents: PaywallComponents?, availablePackages: [Package] ) { self.identifier = identifier @@ -190,6 +215,7 @@ import Foundation self._metadata = Metadata(data: metadata) self.paywall = paywall self.paywallComponents = paywallComponents + self.draftPaywallComponents = draftPaywallComponents var foundPackages: [PackageType: Package] = [:] diff --git a/Sources/Purchasing/OfferingsFactory.swift b/Sources/Purchasing/OfferingsFactory.swift index a6a9c18abe..c8151163ed 100644 --- a/Sources/Purchasing/OfferingsFactory.swift +++ b/Sources/Purchasing/OfferingsFactory.swift @@ -62,11 +62,22 @@ class OfferingsFactory { return nil }() + let paywallDraftComponents: Offering.PaywallComponents? = { + if let uiConfig, let paywallDraftComponents = offering.draftPaywallComponents { + return .init( + uiConfig: uiConfig, + data: paywallDraftComponents + ) + } + return nil + }() + return Offering(identifier: offering.identifier, serverDescription: offering.description, metadata: offering.metadata.mapValues(\.asAny), paywall: offering.paywall, paywallComponents: paywallComponents, + draftPaywallComponents: paywallDraftComponents, availablePackages: availablePackages) } From 29cbdd68e9a3d3832934a9bbf73c1395f1c4c91c Mon Sep 17 00:00:00 2001 From: Antonio Pallares Date: Fri, 7 Feb 2025 13:10:47 +0100 Subject: [PATCH 2/9] fix: initialize `useDraftPaywall` property of `PaywallView` --- RevenueCatUI/PaywallView.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/RevenueCatUI/PaywallView.swift b/RevenueCatUI/PaywallView.swift index db165a47db..130c11148b 100644 --- a/RevenueCatUI/PaywallView.swift +++ b/RevenueCatUI/PaywallView.swift @@ -162,6 +162,7 @@ public struct PaywallView: View { self.mode = configuration.mode self.fonts = configuration.fonts self.displayCloseButton = configuration.displayCloseButton + self.useDraftPaywall = configuration.useDraftPaywall self.initializationError = Self.checkForConfigurationConsistency(purchaseHandler: configuration.purchaseHandler) From 528312f4f99dff05071851f89ed5095f78ccf290 Mon Sep 17 00:00:00 2001 From: Antonio Pallares Date: Fri, 7 Feb 2025 13:16:30 +0100 Subject: [PATCH 3/9] feat: have `PaywallView` actually show draft paywall when `useDraftPaywall` is `true` --- RevenueCatUI/PaywallView.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RevenueCatUI/PaywallView.swift b/RevenueCatUI/PaywallView.swift index 130c11148b..95b6ec979e 100644 --- a/RevenueCatUI/PaywallView.swift +++ b/RevenueCatUI/PaywallView.swift @@ -219,7 +219,7 @@ public struct PaywallView: View { } else if self.introEligibility.isConfigured, self.purchaseHandler.isConfigured { if let offering = self.offering, let customerInfo = self.customerInfo { self.paywallView(for: offering, - useDraftPaywall: false, // TODO: implement + useDraftPaywall: self.useDraftPaywall, activelySubscribedProductIdentifiers: customerInfo.activeSubscriptions, fonts: self.fonts, checker: self.introEligibility, From e45cfabea9de437f7e549033ce561d44725b1677 Mon Sep 17 00:00:00 2001 From: Antonio Pallares Date: Fri, 7 Feb 2025 13:36:51 +0100 Subject: [PATCH 4/9] remove lint warning --- RevenueCatUI/PaywallView.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RevenueCatUI/PaywallView.swift b/RevenueCatUI/PaywallView.swift index 95b6ec979e..e6cd11a336 100644 --- a/RevenueCatUI/PaywallView.swift +++ b/RevenueCatUI/PaywallView.swift @@ -262,7 +262,7 @@ public struct PaywallView: View { } @ViewBuilder - // swiftlint:disable:next function_body_length + // swiftlint:disable:next function_body_length function_parameter_count private func paywallView( for offering: Offering, useDraftPaywall: Bool, From ee2d83ed2348213a7b33884e5581936bf2fbbb33 Mon Sep 17 00:00:00 2001 From: Antonio Pallares Date: Fri, 7 Feb 2025 14:21:02 +0100 Subject: [PATCH 5/9] silence linter warning --- RevenueCatUI/PaywallView.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/RevenueCatUI/PaywallView.swift b/RevenueCatUI/PaywallView.swift index e6cd11a336..ed7efe1665 100644 --- a/RevenueCatUI/PaywallView.swift +++ b/RevenueCatUI/PaywallView.swift @@ -116,6 +116,7 @@ public struct PaywallView: View { ) } + // swiftlint:disable:next missing_docs @_spi(Internal) public init( offering: Offering, fonts: PaywallFontProvider = DefaultPaywallFontProvider(), From 065fbee88923f907a55573250f75fc1be3c7f8f9 Mon Sep 17 00:00:00 2001 From: Antonio Pallares Date: Thu, 13 Feb 2025 11:38:23 +0100 Subject: [PATCH 6/9] tests: add unit tests for `paywallComponents` and `draftPaywallComponents` in `OfferingsResponse` --- RevenueCat.xcodeproj/project.pbxproj | 4 + .../Responses/OfferingsResponse.swift | 1 + .../Responses/Fixtures/Offerings.json | 188 ++++++++++++++++++ .../PaywallComponentsDataTests.swift | 74 +++++++ 4 files changed, 267 insertions(+) create mode 100644 Tests/UnitTests/Networking/Responses/PaywallComponentsDataTests.swift diff --git a/RevenueCat.xcodeproj/project.pbxproj b/RevenueCat.xcodeproj/project.pbxproj index 428daa76d6..f4d0c00442 100644 --- a/RevenueCat.xcodeproj/project.pbxproj +++ b/RevenueCat.xcodeproj/project.pbxproj @@ -798,6 +798,7 @@ 57FFD2512922DBED00A9A878 /* MockStoreTransaction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 57FFD2502922DBED00A9A878 /* MockStoreTransaction.swift */; }; 57FFD2522922DBED00A9A878 /* MockStoreTransaction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 57FFD2502922DBED00A9A878 /* MockStoreTransaction.swift */; }; 6E38843A0CAFD551013D0A3F /* StoreProduct.swift in Sources */ = {isa = PBXBuildFile; fileRef = FECF627761D375C8431EB866 /* StoreProduct.swift */; }; + 75425E0F2D5DFA9F00E25F60 /* PaywallComponentsDataTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 75425E0E2D5DFA9F00E25F60 /* PaywallComponentsDataTests.swift */; }; 7706ED3E2C6E374D0004B9F9 /* ButtonStyles.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7706ED3D2C6E374D0004B9F9 /* ButtonStyles.swift */; }; 7707A94C2CAD93AC006E0313 /* PaywallButtonComponent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7707A94B2CAD93AC006E0313 /* PaywallButtonComponent.swift */; }; 7707A94E2CAD94D2006E0313 /* ButtonComponentViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7707A94D2CAD94D2006E0313 /* ButtonComponentViewModel.swift */; }; @@ -2097,6 +2098,7 @@ 57FDAABD28493A29009A48F1 /* SandboxEnvironmentDetectorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SandboxEnvironmentDetectorTests.swift; sourceTree = ""; }; 57FDAABF28493C13009A48F1 /* MockSandboxEnvironmentDetector.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockSandboxEnvironmentDetector.swift; sourceTree = ""; }; 57FFD2502922DBED00A9A878 /* MockStoreTransaction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockStoreTransaction.swift; sourceTree = ""; }; + 75425E0E2D5DFA9F00E25F60 /* PaywallComponentsDataTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PaywallComponentsDataTests.swift; sourceTree = ""; }; 7706ED3D2C6E374D0004B9F9 /* ButtonStyles.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ButtonStyles.swift; sourceTree = ""; }; 7707A93B2CAD8AA2006E0313 /* Local.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Local.xcconfig; sourceTree = ""; }; 7707A94B2CAD93AC006E0313 /* PaywallButtonComponent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PaywallButtonComponent.swift; sourceTree = ""; }; @@ -4437,6 +4439,7 @@ 5774F9C02805EA3000997128 /* BaseHTTPResponseTest.swift */, 574A2F3E282D75E300150D40 /* OfferingsDecodingTests.swift */, 03A98D312D2441B2009BCA61 /* PaywallDataDecodingTests.swift */, + 75425E0E2D5DFA9F00E25F60 /* PaywallComponentsDataTests.swift */, 03A98D352D244321009BCA61 /* UIConfigDecodingTests.swift */, 574A2F4E282D7B9E00150D40 /* PostOfferDecodingTests.swift */, 5766C61F282DA3D50067D886 /* GetIntroEligibilityDecodingTests.swift */, @@ -6532,6 +6535,7 @@ 351B51C126D450E800BD2BD7 /* OfferingsManagerTests.swift in Sources */, 5796A39927D6C1E000653165 /* BackendPostSubscriberAttributesTests.swift in Sources */, 35D8330A262FBA9A00E60AC5 /* MockUserDefaults.swift in Sources */, + 75425E0F2D5DFA9F00E25F60 /* PaywallComponentsDataTests.swift in Sources */, 2DDF41DF24F6F527005BC22D /* MockProductsManager.swift in Sources */, 351B514F26D44ACE00BD2BD7 /* PurchasesSubscriberAttributesTests.swift in Sources */, 57DBFA5D28AADA43002D18CA /* PurchasesLogInTests.swift in Sources */, diff --git a/Sources/Networking/Responses/OfferingsResponse.swift b/Sources/Networking/Responses/OfferingsResponse.swift index 6ab7e08544..bf27d4980e 100644 --- a/Sources/Networking/Responses/OfferingsResponse.swift +++ b/Sources/Networking/Responses/OfferingsResponse.swift @@ -33,6 +33,7 @@ struct OfferingsResponse { @DefaultDecodable.EmptyDictionary var metadata: [String: AnyDecodable] var paywallComponents: PaywallComponentsData? + @IgnoreDecodeErrors var draftPaywallComponents: PaywallComponentsData? } diff --git a/Tests/UnitTests/Networking/Responses/Fixtures/Offerings.json b/Tests/UnitTests/Networking/Responses/Fixtures/Offerings.json index 141283e042..a22c62a1da 100644 --- a/Tests/UnitTests/Networking/Responses/Fixtures/Offerings.json +++ b/Tests/UnitTests/Networking/Responses/Fixtures/Offerings.json @@ -134,6 +134,194 @@ "paywall": { "Missing": "data" } + }, + { + "description": "Offering with paywall components", + "identifier": "paywall_components", + "packages": [ + { + "identifier": "$rc_monthly", + "platform_product_identifier": "com.revenuecat.monthly_4.99.1_week_intro" + } + ], + "paywall_components": { + "default_locale": "en_US", + "revision": 3, + "template_name": "componentsTEST", + "asset_base_url": "https://assets.pawwalls.com", + "components_localizations": [], + "components_config": { + "base": { + "background": { + "type": "color", + "value": { + "light": { + "type": "hex", + "value": "#220000ff" + } + } + }, + "stack": { + "type": "stack", + "components": [], + "margin": {}, + "padding": {}, + "size": { + "width": { + "type": "fill" + }, + "height": { + "type": "fill" + } + }, + "dimension": { + "type": "vertical", + "alignment": "center", + "distribution": "center" + }, + "spacing": 16 + } + } + } + } + }, + { + "description": "Offering with paywall components + draft paywall", + "identifier": "paywall_components_with_draft", + "packages": [ + { + "identifier": "$rc_monthly", + "platform_product_identifier": "com.revenuecat.monthly_4.99.1_week_intro" + } + ], + "paywall_components": { + "default_locale": "en_US", + "revision": 3, + "template_name": "componentsTEST", + "asset_base_url": "https://assets.pawwalls.com", + "components_localizations": [], + "components_config": { + "base": { + "background": { + "type": "color", + "value": { + "light": { + "type": "hex", + "value": "#220000ff" + } + } + }, + "stack": { + "type": "stack", + "components": [ + { + "type": "text", + "fontWeight": "thin", + "text_lid": "title", + "font_size": 24, + "color": { + "light": { + "type": "hex", + "value": "#FFFFFFff" + } + }, + "size": { + "width": { + "type": "fit" + }, + "height": { + "type": "fill" + } + }, + "horizontal_alignment": "center", + "margin": {}, + "padding": {} + } + ], + "margin": {}, + "padding": {}, + "size": { + "width": { + "type": "fill" + }, + "height": { + "type": "fill" + } + }, + "dimension": { + "type": "vertical", + "alignment": "center", + "distribution": "center" + }, + "spacing": 16 + } + } + } + }, + "draft_paywall_components": { + "default_locale": "en_US", + "revision": 4, + "template_name": "newComponentsTEST", + "asset_base_url": "https://assets.pawwalls.com", + "components_localizations": [], + "components_config": { + "base": { + "background": { + "type": "color", + "value": { + "light": { + "type": "hex", + "value": "#110000ff" + } + } + }, + "stack": { + "type": "stack", + "components": [ + { + "type": "text", + "fontWeight": "bold", + "text_lid": "title", + "font_size": 24, + "color": { + "light": { + "type": "hex", + "value": "#FFFFFFff" + } + }, + "size": { + "width": { + "type": "fit" + }, + "height": { + "type": "fill" + } + }, + "horizontal_alignment": "center", + "margin": {}, + "padding": {} + } + ], + "margin": {}, + "padding": {}, + "size": { + "width": { + "type": "fill" + }, + "height": { + "type": "fill" + } + }, + "dimension": { + "type": "vertical", + "alignment": "leading", + "distribution": "end" + }, + "spacing": 0 + } + } + } + } } ] } diff --git a/Tests/UnitTests/Networking/Responses/PaywallComponentsDataTests.swift b/Tests/UnitTests/Networking/Responses/PaywallComponentsDataTests.swift new file mode 100644 index 0000000000..4927aaf9c2 --- /dev/null +++ b/Tests/UnitTests/Networking/Responses/PaywallComponentsDataTests.swift @@ -0,0 +1,74 @@ +// +// 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 +// +// PaywallDecodingTests.swift +// +// Created by Antonio Pallares on 13/2/25. + +import Nimble +@testable import RevenueCat +import XCTest + +class PaywallComponentsDecodingTests: BaseHTTPResponseTest { + + private var response: OfferingsResponse! + + override func setUpWithError() throws { + try super.setUpWithError() + + self.response = try self.decodeFixture("Offerings") + } + + func testDecodesPaywallComponentsNoDraftPaywallComponents() throws { + let offering = try XCTUnwrap(self.response.offerings[safe: 6]) + + expect(offering.identifier) == "paywall_components" + expect(offering.description) == "Offering with paywall components" + expect(offering.metadata) == [:] + expect(offering.packages).to(haveCount(1)) + + let components = try XCTUnwrap(offering.paywallComponents) + expect(components.templateName) == "componentsTEST" + expect(components.revision) == 3 + expect(components.componentsConfig.base.background) == .color(.init(light: .hex("#220000ff"), dark: nil)) + expect(components.componentsConfig.base.stickyFooter) == nil + expect(components.componentsConfig.base.stack.spacing) == 16 + expect(components.componentsConfig.base.stack.dimension) == .vertical(.center, .center) + expect(components.componentsConfig.base.stack.components).to(haveCount(0)) + + expect(offering.draftPaywallComponents) == nil + } + + func testDecodesPaywallComponentsWithDraftPaywallComponents() throws { + let offering = try XCTUnwrap(self.response.offerings[safe: 7]) + + expect(offering.identifier) == "paywall_components_with_draft" + expect(offering.description) == "Offering with paywall components + draft paywall" + expect(offering.metadata) == [:] + expect(offering.packages).to(haveCount(1)) + + let components = try XCTUnwrap(offering.paywallComponents) + expect(components.templateName) == "componentsTEST" + expect(components.revision) == 3 + expect(components.componentsConfig.base.background) == .color(.init(light: .hex("#220000ff"), dark: nil)) + expect(components.componentsConfig.base.stickyFooter) == nil + expect(components.componentsConfig.base.stack.spacing) == 16 + expect(components.componentsConfig.base.stack.dimension) == .vertical(.center, .center) + expect(components.componentsConfig.base.stack.components).to(haveCount(1)) + + let draftComponents = try XCTUnwrap(offering.draftPaywallComponents) + expect(draftComponents.templateName) == "newComponentsTEST" + expect(draftComponents.revision) == 4 + expect(draftComponents.componentsConfig.base.background) == .color(.init(light: .hex("#110000ff"), dark: nil)) + expect(draftComponents.componentsConfig.base.stickyFooter) == nil + expect(draftComponents.componentsConfig.base.stack.spacing) == 0 + expect(draftComponents.componentsConfig.base.stack.dimension) == .vertical(.leading, .end) + expect(draftComponents.componentsConfig.base.stack.components).to(haveCount(1)) + } +} From 4dad9bff106aa4e6c9887e36d78d330680b6dad1 Mon Sep 17 00:00:00 2001 From: Antonio Pallares Date: Thu, 13 Feb 2025 12:00:22 +0100 Subject: [PATCH 7/9] tests: move test offerings with v2 paywalls into a new file not to affect existing decode + encode test --- .../Responses/Fixtures/Offerings.json | 188 ----------------- .../OfferingsWithPaywallComponents.json | 193 ++++++++++++++++++ .../PaywallComponentsDataTests.swift | 6 +- 3 files changed, 196 insertions(+), 191 deletions(-) create mode 100644 Tests/UnitTests/Networking/Responses/Fixtures/OfferingsWithPaywallComponents.json diff --git a/Tests/UnitTests/Networking/Responses/Fixtures/Offerings.json b/Tests/UnitTests/Networking/Responses/Fixtures/Offerings.json index a22c62a1da..141283e042 100644 --- a/Tests/UnitTests/Networking/Responses/Fixtures/Offerings.json +++ b/Tests/UnitTests/Networking/Responses/Fixtures/Offerings.json @@ -134,194 +134,6 @@ "paywall": { "Missing": "data" } - }, - { - "description": "Offering with paywall components", - "identifier": "paywall_components", - "packages": [ - { - "identifier": "$rc_monthly", - "platform_product_identifier": "com.revenuecat.monthly_4.99.1_week_intro" - } - ], - "paywall_components": { - "default_locale": "en_US", - "revision": 3, - "template_name": "componentsTEST", - "asset_base_url": "https://assets.pawwalls.com", - "components_localizations": [], - "components_config": { - "base": { - "background": { - "type": "color", - "value": { - "light": { - "type": "hex", - "value": "#220000ff" - } - } - }, - "stack": { - "type": "stack", - "components": [], - "margin": {}, - "padding": {}, - "size": { - "width": { - "type": "fill" - }, - "height": { - "type": "fill" - } - }, - "dimension": { - "type": "vertical", - "alignment": "center", - "distribution": "center" - }, - "spacing": 16 - } - } - } - } - }, - { - "description": "Offering with paywall components + draft paywall", - "identifier": "paywall_components_with_draft", - "packages": [ - { - "identifier": "$rc_monthly", - "platform_product_identifier": "com.revenuecat.monthly_4.99.1_week_intro" - } - ], - "paywall_components": { - "default_locale": "en_US", - "revision": 3, - "template_name": "componentsTEST", - "asset_base_url": "https://assets.pawwalls.com", - "components_localizations": [], - "components_config": { - "base": { - "background": { - "type": "color", - "value": { - "light": { - "type": "hex", - "value": "#220000ff" - } - } - }, - "stack": { - "type": "stack", - "components": [ - { - "type": "text", - "fontWeight": "thin", - "text_lid": "title", - "font_size": 24, - "color": { - "light": { - "type": "hex", - "value": "#FFFFFFff" - } - }, - "size": { - "width": { - "type": "fit" - }, - "height": { - "type": "fill" - } - }, - "horizontal_alignment": "center", - "margin": {}, - "padding": {} - } - ], - "margin": {}, - "padding": {}, - "size": { - "width": { - "type": "fill" - }, - "height": { - "type": "fill" - } - }, - "dimension": { - "type": "vertical", - "alignment": "center", - "distribution": "center" - }, - "spacing": 16 - } - } - } - }, - "draft_paywall_components": { - "default_locale": "en_US", - "revision": 4, - "template_name": "newComponentsTEST", - "asset_base_url": "https://assets.pawwalls.com", - "components_localizations": [], - "components_config": { - "base": { - "background": { - "type": "color", - "value": { - "light": { - "type": "hex", - "value": "#110000ff" - } - } - }, - "stack": { - "type": "stack", - "components": [ - { - "type": "text", - "fontWeight": "bold", - "text_lid": "title", - "font_size": 24, - "color": { - "light": { - "type": "hex", - "value": "#FFFFFFff" - } - }, - "size": { - "width": { - "type": "fit" - }, - "height": { - "type": "fill" - } - }, - "horizontal_alignment": "center", - "margin": {}, - "padding": {} - } - ], - "margin": {}, - "padding": {}, - "size": { - "width": { - "type": "fill" - }, - "height": { - "type": "fill" - } - }, - "dimension": { - "type": "vertical", - "alignment": "leading", - "distribution": "end" - }, - "spacing": 0 - } - } - } - } } ] } diff --git a/Tests/UnitTests/Networking/Responses/Fixtures/OfferingsWithPaywallComponents.json b/Tests/UnitTests/Networking/Responses/Fixtures/OfferingsWithPaywallComponents.json new file mode 100644 index 0000000000..ce509450d3 --- /dev/null +++ b/Tests/UnitTests/Networking/Responses/Fixtures/OfferingsWithPaywallComponents.json @@ -0,0 +1,193 @@ +{ + "current_offering_id": "default", + "offerings": [ + { + "description": "Offering with paywall components", + "identifier": "paywall_components", + "packages": [ + { + "identifier": "$rc_monthly", + "platform_product_identifier": "com.revenuecat.monthly_4.99.1_week_intro" + } + ], + "paywall_components": { + "default_locale": "en_US", + "revision": 3, + "template_name": "componentsTEST", + "asset_base_url": "https://assets.pawwalls.com", + "components_localizations": [], + "components_config": { + "base": { + "background": { + "type": "color", + "value": { + "light": { + "type": "hex", + "value": "#220000ff" + } + } + }, + "stack": { + "type": "stack", + "components": [], + "margin": {}, + "padding": {}, + "size": { + "width": { + "type": "fill" + }, + "height": { + "type": "fill" + } + }, + "dimension": { + "type": "vertical", + "alignment": "center", + "distribution": "center" + }, + "spacing": 16 + } + } + } + } + }, + { + "description": "Offering with paywall components + draft paywall", + "identifier": "paywall_components_with_draft", + "packages": [ + { + "identifier": "$rc_monthly", + "platform_product_identifier": "com.revenuecat.monthly_4.99.1_week_intro" + } + ], + "paywall_components": { + "default_locale": "en_US", + "revision": 3, + "template_name": "componentsTEST", + "asset_base_url": "https://assets.pawwalls.com", + "components_localizations": [], + "components_config": { + "base": { + "background": { + "type": "color", + "value": { + "light": { + "type": "hex", + "value": "#220000ff" + } + } + }, + "stack": { + "type": "stack", + "components": [ + { + "type": "text", + "fontWeight": "thin", + "text_lid": "title", + "font_size": 24, + "color": { + "light": { + "type": "hex", + "value": "#FFFFFFff" + } + }, + "size": { + "width": { + "type": "fit" + }, + "height": { + "type": "fill" + } + }, + "horizontal_alignment": "center", + "margin": {}, + "padding": {} + } + ], + "margin": {}, + "padding": {}, + "size": { + "width": { + "type": "fill" + }, + "height": { + "type": "fill" + } + }, + "dimension": { + "type": "vertical", + "alignment": "center", + "distribution": "center" + }, + "spacing": 16 + } + } + } + }, + "draft_paywall_components": { + "default_locale": "en_US", + "revision": 4, + "template_name": "newComponentsTEST", + "asset_base_url": "https://assets.pawwalls.com", + "components_localizations": [], + "components_config": { + "base": { + "background": { + "type": "color", + "value": { + "light": { + "type": "hex", + "value": "#110000ff" + } + } + }, + "stack": { + "type": "stack", + "components": [ + { + "type": "text", + "fontWeight": "bold", + "text_lid": "title", + "font_size": 24, + "color": { + "light": { + "type": "hex", + "value": "#FFFFFFff" + } + }, + "size": { + "width": { + "type": "fit" + }, + "height": { + "type": "fill" + } + }, + "horizontal_alignment": "center", + "margin": {}, + "padding": {} + } + ], + "margin": {}, + "padding": {}, + "size": { + "width": { + "type": "fill" + }, + "height": { + "type": "fill" + } + }, + "dimension": { + "type": "vertical", + "alignment": "leading", + "distribution": "end" + }, + "spacing": 0 + } + } + } + } + } + ] +} diff --git a/Tests/UnitTests/Networking/Responses/PaywallComponentsDataTests.swift b/Tests/UnitTests/Networking/Responses/PaywallComponentsDataTests.swift index 4927aaf9c2..ee8d4bda6f 100644 --- a/Tests/UnitTests/Networking/Responses/PaywallComponentsDataTests.swift +++ b/Tests/UnitTests/Networking/Responses/PaywallComponentsDataTests.swift @@ -22,11 +22,11 @@ class PaywallComponentsDecodingTests: BaseHTTPResponseTest { override func setUpWithError() throws { try super.setUpWithError() - self.response = try self.decodeFixture("Offerings") + self.response = try self.decodeFixture("OfferingsWithPaywallComponents") } func testDecodesPaywallComponentsNoDraftPaywallComponents() throws { - let offering = try XCTUnwrap(self.response.offerings[safe: 6]) + let offering = try XCTUnwrap(self.response.offerings[safe: 0]) expect(offering.identifier) == "paywall_components" expect(offering.description) == "Offering with paywall components" @@ -46,7 +46,7 @@ class PaywallComponentsDecodingTests: BaseHTTPResponseTest { } func testDecodesPaywallComponentsWithDraftPaywallComponents() throws { - let offering = try XCTUnwrap(self.response.offerings[safe: 7]) + let offering = try XCTUnwrap(self.response.offerings[safe: 1]) expect(offering.identifier) == "paywall_components_with_draft" expect(offering.description) == "Offering with paywall components + draft paywall" From 92a4bf90595233a73145b7a5e05796b9f4caaa0b Mon Sep 17 00:00:00 2001 From: Antonio Pallares Date: Thu, 13 Feb 2025 12:30:24 +0100 Subject: [PATCH 8/9] remove unnecessary property wrapper --- Sources/Networking/Responses/OfferingsResponse.swift | 1 - .../Networking/Responses/PaywallComponentsDataTests.swift | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/Sources/Networking/Responses/OfferingsResponse.swift b/Sources/Networking/Responses/OfferingsResponse.swift index bf27d4980e..6ab7e08544 100644 --- a/Sources/Networking/Responses/OfferingsResponse.swift +++ b/Sources/Networking/Responses/OfferingsResponse.swift @@ -33,7 +33,6 @@ struct OfferingsResponse { @DefaultDecodable.EmptyDictionary var metadata: [String: AnyDecodable] var paywallComponents: PaywallComponentsData? - @IgnoreDecodeErrors var draftPaywallComponents: PaywallComponentsData? } diff --git a/Tests/UnitTests/Networking/Responses/PaywallComponentsDataTests.swift b/Tests/UnitTests/Networking/Responses/PaywallComponentsDataTests.swift index ee8d4bda6f..5d24cbc864 100644 --- a/Tests/UnitTests/Networking/Responses/PaywallComponentsDataTests.swift +++ b/Tests/UnitTests/Networking/Responses/PaywallComponentsDataTests.swift @@ -7,7 +7,7 @@ // // https://opensource.org/licenses/MIT // -// PaywallDecodingTests.swift +// PaywallComponentsDecodingTests.swift // // Created by Antonio Pallares on 13/2/25. From caa5b417d870cc875d9b0666161182c7a19643b1 Mon Sep 17 00:00:00 2001 From: Antonio Pallares Date: Thu, 13 Feb 2025 12:38:11 +0100 Subject: [PATCH 9/9] silence lint warning --- RevenueCatUI/PaywallView.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/RevenueCatUI/PaywallView.swift b/RevenueCatUI/PaywallView.swift index cd0abe8fc5..b634574186 100644 --- a/RevenueCatUI/PaywallView.swift +++ b/RevenueCatUI/PaywallView.swift @@ -23,6 +23,7 @@ import SwiftUI @available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *) @available(macOS, unavailable, message: "RevenueCatUI does not support macOS yet") @available(tvOS, unavailable, message: "RevenueCatUI does not support tvOS yet") +// swiftlint:disable:next type_body_length public struct PaywallView: View { private let contentToDisplay: PaywallViewConfiguration.Content