diff --git a/platforms/android/README.md b/platforms/android/README.md index 1741b0bbd..8c75d4129 100644 --- a/platforms/android/README.md +++ b/platforms/android/README.md @@ -258,6 +258,11 @@ Clear unused preloaded checkout work with `invalidate`: ShopifyCheckoutKit.invalidate() ``` +Updating configuration through `ShopifyCheckoutKit.configure {}` triggers +`ShopifyCheckoutKit.invalidate()`. Discarding cached preloads avoids presenting a +stale checkout. If a preload may already have run when configuration changes, +call `preload` again. + Preloading is enabled by default. Disable it when appropriate, for example for data-saver modes or app-specific runtime conditions: ```kotlin diff --git a/platforms/android/lib/src/main/java/com/shopify/checkoutkit/ShopifyCheckoutKit.kt b/platforms/android/lib/src/main/java/com/shopify/checkoutkit/ShopifyCheckoutKit.kt index c1f79394b..8b5a9b953 100644 --- a/platforms/android/lib/src/main/java/com/shopify/checkoutkit/ShopifyCheckoutKit.kt +++ b/platforms/android/lib/src/main/java/com/shopify/checkoutkit/ShopifyCheckoutKit.kt @@ -49,6 +49,8 @@ public object ShopifyCheckoutKit { /** * Allows configuring ShopifyCheckoutKit. * + * Calling this function invalidates any cached preload. + * * Kotlin example: * {@code ShopifyCheckoutKit.configure { it.appearance = CheckoutAppearance.App(ColorScheme.Dark()) }} * diff --git a/platforms/react-native/README.md b/platforms/react-native/README.md index 6107c1c82..fbb9a1542 100644 --- a/platforms/react-native/README.md +++ b/platforms/react-native/README.md @@ -775,7 +775,15 @@ Instead, a better approach is to call `preload()` when you have a strong enough ### Cache invalidation -Should you wish to manually clear the preload cache, call `invalidate()` on your `ShopifyCheckout` instance or the value returned by `useShopifyCheckout()`. +To manually clear the preload cache, call `invalidate()` on your `ShopifyCheckout` +instance or the value returned by `useShopifyCheckout()`. + +Updating configuration through `shopifyCheckout.setConfig(...)` or the +`configuration` prop on `ShopifyCheckoutProvider` triggers +`shopifyCheckout.invalidate()`. Discarding cached preloads avoids presenting a +stale checkout. If a preload may already have run when configuration changes, +call `preload` again. Keep a provider's configuration object stable across renders to +avoid unintentionally invalidating preloads. ## Checkout lifecycle diff --git a/platforms/swift/README.md b/platforms/swift/README.md index 2b27a8e6a..768cbdbd5 100644 --- a/platforms/swift/README.md +++ b/platforms/swift/README.md @@ -228,6 +228,12 @@ Clear unused preloaded checkout work with `invalidate`: ShopifyCheckoutKit.invalidate() ``` +Updating configuration through `ShopifyCheckoutKit.configure {}` or a direct +mutation such as `ShopifyCheckoutKit.configuration.title = "Checkout"` triggers +`ShopifyCheckoutKit.invalidate()`. Discarding cached preloads avoids presenting a +stale checkout. If a preload may already have run when configuration changes, +call `preload` again. + Preloading is enabled by default. Disable it when appropriate, for example for data-saver modes or app-specific runtime conditions: ```swift @@ -253,6 +259,8 @@ ShopifyCheckoutKit.configure { } ``` +`ShopifyCheckout` uses the global configuration as its defaults. When present, modifiers such as `.appearance(...)`, `.tintColor(...)`, and `.title(...)` take precedence over the corresponding `ShopifyCheckoutKit.configuration` values for that checkout. + | Option | Default | Purpose | | --- | --- | --- | | `appearance` | `.storefront` | Match the storefront's web checkout branding with a light color scheme, or use the Checkout Kit style with `.app(.automatic)`, `.app(.light)`, or `.app(.dark)`. | diff --git a/platforms/swift/Sources/ShopifyCheckoutKit/CheckoutViewController.swift b/platforms/swift/Sources/ShopifyCheckoutKit/CheckoutViewController.swift index dbc701305..fa36ab9df 100644 --- a/platforms/swift/Sources/ShopifyCheckoutKit/CheckoutViewController.swift +++ b/platforms/swift/Sources/ShopifyCheckoutKit/CheckoutViewController.swift @@ -6,15 +6,24 @@ import UIKit @MainActor public class CheckoutViewController: UINavigationController { - public init(checkout url: URL, delegate: (any CheckoutDelegate)? = nil, client: (any CheckoutCommunicationProtocol)? = nil) { - let rootViewController = CheckoutWebViewController(checkoutURL: url, delegate: delegate, client: client, entryPoint: nil) - super.init(rootViewController: rootViewController) - configureNavigationBar() - presentationController?.delegate = rootViewController - } - - package init(checkout url: URL, delegate: (any CheckoutDelegate)? = nil, client: (any CheckoutCommunicationProtocol)? = nil, entryPoint: MetaData.EntryPoint? = nil) { - let rootViewController = CheckoutWebViewController(checkoutURL: url, delegate: delegate, client: client, entryPoint: entryPoint) + public convenience init(checkout url: URL, delegate: (any CheckoutDelegate)? = nil, client: (any CheckoutCommunicationProtocol)? = nil) { + self.init( + checkout: url, + configuration: ShopifyCheckoutKit.configuration, + delegate: delegate, + client: client + ) + } + + /// Shared initializer that lets SwiftUI inject instance-scoped configuration while package callers use the global default. + package init(checkout url: URL, configuration: Configuration = ShopifyCheckoutKit.configuration, delegate: (any CheckoutDelegate)? = nil, client: (any CheckoutCommunicationProtocol)? = nil, entryPoint: MetaData.EntryPoint? = nil) { + let rootViewController = CheckoutWebViewController( + checkoutURL: url, + configuration: configuration, + delegate: delegate, + client: client, + entryPoint: entryPoint + ) super.init(rootViewController: rootViewController) configureNavigationBar() presentationController?.delegate = rootViewController @@ -40,20 +49,30 @@ public struct ShopifyCheckout: UIViewControllerRepresentable, CheckoutConfigurab public typealias UIViewControllerType = CheckoutViewController var checkoutURL: URL + + /// The configuration captured when this value was created, plus any modifier + /// overrides. Isolated from ``ShopifyCheckoutKit/configuration``. + public var configuration: Configuration + var client: (any CheckoutCommunicationProtocol)? var onDismissAction: (() -> Void)? var onFailAction: ((CheckoutError) -> Void)? public init(checkout url: URL) { checkoutURL = url + configuration = ShopifyCheckoutKit.configuration } var decoratedCheckoutURL: URL { - CheckoutURLDecorator.decorate(checkoutURL) + CheckoutURLDecorator.decorate(checkoutURL, configuration: configuration) } public func makeUIViewController(context _: Self.Context) -> CheckoutViewController { - let viewController = CheckoutViewController(checkout: decoratedCheckoutURL, client: client) + let viewController = CheckoutViewController( + checkout: decoratedCheckoutURL, + configuration: configuration, + client: client + ) configureWebViewController(viewController) return viewController } @@ -102,6 +121,11 @@ public struct ShopifyCheckout: UIViewControllerRepresentable, CheckoutConfigurab @MainActor public protocol CheckoutConfigurable { + /// The configuration the modifiers read and write. Modifiers copy `self`, + /// update the copy's configuration, and return the copy, so conforming + /// types should have value semantics. + var configuration: ShopifyCheckoutKit.Configuration { get set } + func backgroundColor(_ color: UIColor) -> Self func appearance(_ appearance: ShopifyCheckoutKit.Configuration.Appearance) -> Self func tintColor(_ color: UIColor) -> Self @@ -111,27 +135,28 @@ public protocol CheckoutConfigurable { extension CheckoutConfigurable { @discardableResult public func backgroundColor(_ color: UIColor) -> Self { - ShopifyCheckoutKit.configuration.backgroundColor = color - return self + modifyingConfiguration { $0.backgroundColor = color } } @discardableResult public func appearance(_ appearance: ShopifyCheckoutKit.Configuration.Appearance) -> Self { - ShopifyCheckoutKit.configuration.appearance = appearance - return self + modifyingConfiguration { $0.appearance = appearance } } @discardableResult public func tintColor(_ color: UIColor) -> Self { - ShopifyCheckoutKit.configuration.tintColor = color - return self + modifyingConfiguration { $0.tintColor = color } } @discardableResult public func title(_ title: String) -> Self { - ShopifyCheckoutKit.configuration.title = title - return self + modifyingConfiguration { $0.title = title } } @discardableResult public func closeButtonTintColor(_ color: UIColor?) -> Self { - ShopifyCheckoutKit.configuration.closeButtonTintColor = color - return self + modifyingConfiguration { $0.closeButtonTintColor = color } + } + + private func modifyingConfiguration(_ update: (inout ShopifyCheckoutKit.Configuration) -> Void) -> Self { + var copy = self + update(©.configuration) + return copy } } diff --git a/platforms/swift/Sources/ShopifyCheckoutKit/CheckoutWebViewController.swift b/platforms/swift/Sources/ShopifyCheckoutKit/CheckoutWebViewController.swift index 26b889a12..3949a5046 100644 --- a/platforms/swift/Sources/ShopifyCheckoutKit/CheckoutWebViewController.swift +++ b/platforms/swift/Sources/ShopifyCheckoutKit/CheckoutWebViewController.swift @@ -15,7 +15,7 @@ class CheckoutWebViewController: UIViewController, UIAdaptivePresentationControl var checkoutView: CheckoutWebView? lazy var progressBar: ProgressBarView = { - let progressBar = ProgressBarView(frame: .zero) + let progressBar = ProgressBarView(frame: .zero, tintColor: configuration.tintColor) progressBar.translatesAutoresizingMaskIntoConstraints = false return progressBar }() @@ -23,9 +23,10 @@ class CheckoutWebViewController: UIViewController, UIAdaptivePresentationControl var initialNavigation: Bool = true private let checkoutURL: URL + private let configuration: Configuration private lazy var closeBarButtonItem: UIBarButtonItem = { - if let closeButtonTintColor = ShopifyCheckoutKit.configuration.closeButtonTintColor { + if let closeButtonTintColor = configuration.closeButtonTintColor { var item: UIBarButtonItem if #available(iOS 26.0, *) { @@ -62,13 +63,16 @@ class CheckoutWebViewController: UIViewController, UIAdaptivePresentationControl // MARK: Initializers - public init(checkoutURL url: URL, delegate: (any CheckoutDelegate)? = nil, client: (any CheckoutCommunicationProtocol)? = nil, entryPoint: MetaData.EntryPoint? = nil) { + public init(checkoutURL url: URL, configuration: Configuration = ShopifyCheckoutKit.configuration, delegate: (any CheckoutDelegate)? = nil, client: (any CheckoutCommunicationProtocol)? = nil, entryPoint: MetaData.EntryPoint? = nil) { checkoutURL = url + self.configuration = configuration self.delegate = delegate self.client = client let checkoutView = CheckoutWebView.for(checkout: url, entryPoint: entryPoint) checkoutView.isPresented = true + checkoutView.backgroundColor = configuration.backgroundColor + checkoutView.underPageBackgroundColor = configuration.backgroundColor checkoutView.translatesAutoresizingMaskIntoConstraints = false checkoutView.scrollView.contentInsetAdjustmentBehavior = .automatic checkoutView.client = client @@ -76,13 +80,13 @@ class CheckoutWebViewController: UIViewController, UIAdaptivePresentationControl super.init(nibName: nil, bundle: nil) - title = ShopifyCheckoutKit.configuration.title + title = configuration.title navigationItem.rightBarButtonItem = closeBarButtonItem checkoutView.viewDelegate = self - view.backgroundColor = ShopifyCheckoutKit.configuration.backgroundColor + view.backgroundColor = configuration.backgroundColor } @available(*, unavailable) @@ -95,7 +99,7 @@ class CheckoutWebViewController: UIViewController, UIAdaptivePresentationControl override public func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) - view.backgroundColor = ShopifyCheckoutKit.configuration.backgroundColor + view.backgroundColor = configuration.backgroundColor } override public func viewDidLoad() { diff --git a/platforms/swift/Sources/ShopifyCheckoutKit/ProgressBarView.swift b/platforms/swift/Sources/ShopifyCheckoutKit/ProgressBarView.swift index c95136172..256afe2d8 100644 --- a/platforms/swift/Sources/ShopifyCheckoutKit/ProgressBarView.swift +++ b/platforms/swift/Sources/ShopifyCheckoutKit/ProgressBarView.swift @@ -10,8 +10,10 @@ class ProgressBarView: UIView { }() private var progressAnimation: UIViewPropertyAnimator? + private let configuredTintColor: UIColor - override init(frame: CGRect) { + init(frame: CGRect, tintColor: UIColor) { + configuredTintColor = tintColor super.init(frame: frame) addSubview(progressBar) @@ -21,7 +23,7 @@ class ProgressBarView: UIView { progressBar.heightAnchor.constraint(equalToConstant: 1) ]) - progressBar.tintColor = ShopifyCheckoutKit.configuration.tintColor + progressBar.tintColor = configuredTintColor } override func didMoveToSuperview() { diff --git a/platforms/swift/Sources/ShopifyCheckoutKit/ShopifyCheckoutKit.swift b/platforms/swift/Sources/ShopifyCheckoutKit/ShopifyCheckoutKit.swift index 167a4062e..778df7fc0 100644 --- a/platforms/swift/Sources/ShopifyCheckoutKit/ShopifyCheckoutKit.swift +++ b/platforms/swift/Sources/ShopifyCheckoutKit/ShopifyCheckoutKit.swift @@ -9,6 +9,8 @@ public let version = "4.0.0-alpha.6" private let lockedCheckoutKitConfiguration = LockedValue(Configuration()) /// The configuration options for the `ShopifyCheckoutKit` library. +/// +/// Assigning configuration invalidates any cached preload. public var configuration: Configuration { get { lockedCheckoutKitConfiguration.get() } set { @@ -21,7 +23,9 @@ public var configuration: Configuration { } } -/// A convienence function for configuring the `ShopifyCheckoutKit` library. +/// A convenience function for configuring the `ShopifyCheckoutKit` library. +/// +/// Calling this function invalidates any cached preload. public func configure(_ block: (inout Configuration) -> Void) { let previousConfiguration = lockedCheckoutKitConfiguration.get() lockedCheckoutKitConfiguration.update(block) @@ -38,10 +42,8 @@ private func applyConfigurationChange(configuration: Configuration, previousConf CheckoutTelemetry.disable() } - if configuration.preloading.enabled != previousConfiguration.preloading.enabled { - Task { @MainActor in - invalidate() - } + Task { @MainActor in + invalidate() } } diff --git a/platforms/swift/Tests/ShopifyCheckoutKitTests/CheckoutViewControllerPackageInitializerTests.swift b/platforms/swift/Tests/ShopifyCheckoutKitTests/CheckoutViewControllerPackageInitializerTests.swift new file mode 100644 index 000000000..4237342ec --- /dev/null +++ b/platforms/swift/Tests/ShopifyCheckoutKitTests/CheckoutViewControllerPackageInitializerTests.swift @@ -0,0 +1,19 @@ +import Foundation +import ShopifyCheckoutKit +import XCTest + +@MainActor +final class CheckoutViewControllerPackageInitializerTests: XCTestCase { + func testInjectedConfigurationIsAppliedToCheckout() throws { + var configuration = ShopifyCheckoutKit.configuration + configuration.title = "Instance checkout" + + let viewController = try CheckoutViewController( + checkout: XCTUnwrap(URL(string: "https://checkout-sdk.myshopify.com")), + configuration: configuration + ) + + let checkoutViewController = try XCTUnwrap(viewController.viewControllers.first) + XCTAssertEqual(checkoutViewController.title, "Instance checkout") + } +} diff --git a/platforms/swift/Tests/ShopifyCheckoutKitTests/CheckoutViewControllerTests.swift b/platforms/swift/Tests/ShopifyCheckoutKitTests/CheckoutViewControllerTests.swift index 8bfed0ceb..4aa9f61f4 100644 --- a/platforms/swift/Tests/ShopifyCheckoutKitTests/CheckoutViewControllerTests.swift +++ b/platforms/swift/Tests/ShopifyCheckoutKitTests/CheckoutViewControllerTests.swift @@ -56,6 +56,29 @@ class CheckoutViewDelegateTests: XCTestCase { XCTAssertEqual(viewController.title, "Custom title") } + func testInstanceConfigurationIsAppliedToCheckoutChrome() throws { + var configuration = Configuration() + configuration.backgroundColor = .red + configuration.tintColor = .blue + configuration.title = "Instance checkout" + configuration.closeButtonTintColor = .green + + let controller = MockCheckoutWebViewController( + checkoutURL: checkoutURL, + configuration: configuration + ) + controller.loadViewIfNeeded() + + XCTAssertEqual(controller.title, "Instance checkout") + assertColor(controller.view.backgroundColor, equals: .red) + assertColor(controller.checkoutView?.backgroundColor, equals: .red) + assertColor(controller.checkoutView?.underPageBackgroundColor, equals: .red) + assertColor(controller.progressBar.progressBar.tintColor, equals: .blue) + let closeButton = try XCTUnwrap(controller.navigationItem.rightBarButtonItem) + assertColor(closeButton.tintColor, equals: .green) + XCTAssertNotNil(closeButton.image) + } + func testCheckoutViewDidFailWithErrorDismissesViewController() { viewController.checkoutViewDidFailWithError(error: CheckoutError(code: .httpError, message: "error", httpStatusCode: 500)) @@ -125,6 +148,13 @@ class CheckoutViewDelegateTests: XCTestCase { let closeButton = controller.navigationItem.rightBarButtonItem XCTAssertNotNil(closeButton?.image) } + + private func assertColor(_ actual: UIColor?, equals expected: UIColor, file: StaticString = #filePath, line: UInt = #line) { + let actualComponents = actual?.cgColor.components + let expectedComponents = expected.cgColor.components + + XCTAssertEqual(actualComponents, expectedComponents, file: file, line: line) + } } @MainActor diff --git a/platforms/swift/Tests/ShopifyCheckoutKitTests/ConfigurationTests.swift b/platforms/swift/Tests/ShopifyCheckoutKitTests/ConfigurationTests.swift index 19debd3cc..235d331ea 100644 --- a/platforms/swift/Tests/ShopifyCheckoutKitTests/ConfigurationTests.swift +++ b/platforms/swift/Tests/ShopifyCheckoutKitTests/ConfigurationTests.swift @@ -69,9 +69,10 @@ class ConfigurationTests: XCTestCase { XCTAssertFalse(CheckoutWebView.preloadCache.hasEntry()) } - func testChangingConfigurationWithoutChangingPreloadingDoesNotInvalidatePreload() async throws { + func testChangingConfigurationWithoutChangingPreloadingInvalidatesPreload() async throws { let checkoutURL = try XCTUnwrap(URL(string: "https://shopify1.shopify.com/checkouts/cn/123")) + await Task.yield() ShopifyCheckoutKit.preload(checkout: checkoutURL) XCTAssertTrue(CheckoutWebView.preloadCache.hasEntry()) @@ -79,11 +80,11 @@ class ConfigurationTests: XCTestCase { $0.title = "Thank you!" } - for _ in 0 ..< 10 { + for _ in 0 ..< 10 where CheckoutWebView.preloadCache.hasEntry() { await Task.yield() } - XCTAssertTrue(CheckoutWebView.preloadCache.hasEntry()) + XCTAssertFalse(CheckoutWebView.preloadCache.hasEntry()) } func testAppearanceCanBeSetDirectly() { diff --git a/platforms/swift/Tests/ShopifyCheckoutKitTests/SwiftUITests.swift b/platforms/swift/Tests/ShopifyCheckoutKitTests/SwiftUITests.swift index 12b2b5909..b95e18107 100644 --- a/platforms/swift/Tests/ShopifyCheckoutKitTests/SwiftUITests.swift +++ b/platforms/swift/Tests/ShopifyCheckoutKitTests/SwiftUITests.swift @@ -83,46 +83,111 @@ class CheckoutConfigurableTests: XCTestCase { try await super.tearDown() } - func testBackgroundColor() { + func testBackgroundColorIsCapturedWithoutChangingGlobalConfiguration() { + let globalColor = ShopifyCheckoutKit.configuration.backgroundColor let color = UIColor.red - shopifyCheckout.backgroundColor(color) - XCTAssertEqual(ShopifyCheckoutKit.configuration.backgroundColor, color) + + let sheet = shopifyCheckout.backgroundColor(color) + + XCTAssertEqual(sheet.configuration.backgroundColor, color) + XCTAssertEqual(shopifyCheckout.configuration.backgroundColor, globalColor) + XCTAssertEqual(ShopifyCheckoutKit.configuration.backgroundColor, globalColor) } - func testAppearance() { + func testAppearanceIsCapturedWithoutChangingGlobalConfiguration() { + let globalAppearance = ShopifyCheckoutKit.configuration.appearance let appearance = ShopifyCheckoutKit.Configuration.Appearance.app(.light) - shopifyCheckout.appearance(appearance) - XCTAssertEqual(ShopifyCheckoutKit.configuration.appearance, appearance) + + let sheet = shopifyCheckout.appearance(appearance) + + XCTAssertEqual(sheet.configuration.appearance, appearance) + XCTAssertEqual(shopifyCheckout.configuration.appearance, globalAppearance) + XCTAssertEqual(ShopifyCheckoutKit.configuration.appearance, globalAppearance) } - func testAppearanceDecoratesCheckoutURLAfterModifierRuns() throws { - let sheet = shopifyCheckout.appearance(.storefront) + func testAppearanceDecoratesCheckoutURLFromCapturedConfiguration() throws { + let sheet = shopifyCheckout.appearance(.app(.dark)) let items = try XCTUnwrap(URLComponents(url: sheet.decoratedCheckoutURL, resolvingAgainstBaseURL: false)?.queryItems) - XCTAssertEqual(items.first(where: { $0.name == "ec_color_scheme" })?.value, "light") - XCTAssertEqual(items.first(where: { $0.name == "ck_branding" })?.value, "shop") + XCTAssertEqual(items.first(where: { $0.name == "ec_color_scheme" })?.value, "dark") + XCTAssertEqual(items.first(where: { $0.name == "ck_branding" })?.value, "app") } - func testTintColor() { + func testTintColorIsCapturedWithoutChangingGlobalConfiguration() { + let globalColor = ShopifyCheckoutKit.configuration.tintColor let color = UIColor.blue - shopifyCheckout.tintColor(color) - XCTAssertEqual(ShopifyCheckoutKit.configuration.tintColor, color) + + let sheet = shopifyCheckout.tintColor(color) + + XCTAssertEqual(sheet.configuration.tintColor, color) + XCTAssertEqual(shopifyCheckout.configuration.tintColor, globalColor) + XCTAssertEqual(ShopifyCheckoutKit.configuration.tintColor, globalColor) } - func testTitle() { + func testTitleIsCapturedWithoutChangingGlobalConfiguration() { + let globalTitle = ShopifyCheckoutKit.configuration.title let title = "Test Title" - shopifyCheckout.title(title) - XCTAssertEqual(ShopifyCheckoutKit.configuration.title, title) + + let sheet = shopifyCheckout.title(title) + + XCTAssertEqual(sheet.configuration.title, title) + XCTAssertEqual(shopifyCheckout.configuration.title, globalTitle) + XCTAssertEqual(ShopifyCheckoutKit.configuration.title, globalTitle) } - func testCloseButtonTintColor() { + func testCloseButtonTintColorIsCapturedWithoutChangingGlobalConfiguration() { + let globalColor = ShopifyCheckoutKit.configuration.closeButtonTintColor let color = UIColor.green - shopifyCheckout.closeButtonTintColor(color) - XCTAssertEqual(ShopifyCheckoutKit.configuration.closeButtonTintColor, color) + + let sheet = shopifyCheckout.closeButtonTintColor(color) + + XCTAssertEqual(sheet.configuration.closeButtonTintColor, color) + XCTAssertEqual(shopifyCheckout.configuration.closeButtonTintColor, globalColor) + XCTAssertEqual(ShopifyCheckoutKit.configuration.closeButtonTintColor, globalColor) } - func testCloseButtonTintColorNil() { - shopifyCheckout.closeButtonTintColor(nil) + func testCloseButtonTintColorCanBeClearedOnInstance() { + let sheet = shopifyCheckout + .closeButtonTintColor(.green) + .closeButtonTintColor(nil) + + XCTAssertNil(sheet.configuration.closeButtonTintColor) XCTAssertNil(ShopifyCheckoutKit.configuration.closeButtonTintColor) } + + func testModifiersDoNotInvalidatePreload() async { + await Task.yield() + ShopifyCheckoutKit.preload(checkout: checkoutURL) + XCTAssertTrue(CheckoutWebView.preloadCache.hasEntry()) + + _ = shopifyCheckout + .backgroundColor(.red) + .appearance(.app(.dark)) + .tintColor(.blue) + .title("Instance checkout") + .closeButtonTintColor(.green) + await Task.yield() + + XCTAssertTrue(CheckoutWebView.preloadCache.hasEntry()) + } + + func testModifiersApplyToAnyConformerWithoutCasts() { + let fixture = ConfigurableFixture() + .backgroundColor(.red) + .appearance(.app(.dark)) + .tintColor(.blue) + .title("Fixture checkout") + .closeButtonTintColor(.green) + + XCTAssertEqual(fixture.configuration.backgroundColor, .red) + XCTAssertEqual(fixture.configuration.appearance, .app(.dark)) + XCTAssertEqual(fixture.configuration.tintColor, .blue) + XCTAssertEqual(fixture.configuration.title, "Fixture checkout") + XCTAssertEqual(fixture.configuration.closeButtonTintColor, .green) + XCTAssertNotEqual(ShopifyCheckoutKit.configuration.title, "Fixture checkout") + } +} + +private struct ConfigurableFixture: CheckoutConfigurable { + var configuration = Configuration() } diff --git a/platforms/swift/api/ShopifyCheckoutKit.json b/platforms/swift/api/ShopifyCheckoutKit.json index 7c3b2c2db..8c79aaeac 100644 --- a/platforms/swift/api/ShopifyCheckoutKit.json +++ b/platforms/swift/api/ShopifyCheckoutKit.json @@ -2232,12 +2232,12 @@ "declAttributes": [ "Custom" ], - "init_kind": "Designated" + "init_kind": "Convenience" }, { "kind": "Constructor", "name": "init", - "printedName": "init(checkout:delegate:client:entryPoint:)", + "printedName": "init(checkout:configuration:delegate:client:entryPoint:)", "children": [ { "kind": "TypeNominal", @@ -2251,6 +2251,13 @@ "printedName": "Foundation.URL", "usr": "s:10Foundation3URLV" }, + { + "kind": "TypeNominal", + "name": "Configuration", + "printedName": "ShopifyCheckoutKit.Configuration", + "hasDefaultArg": true, + "usr": "s:18ShopifyCheckoutKit13ConfigurationV" + }, { "kind": "TypeNominal", "name": "Optional", @@ -2298,8 +2305,8 @@ } ], "declKind": "Constructor", - "usr": "s:18ShopifyCheckoutKit0B14ViewControllerC8checkout8delegate6client10entryPointAC10Foundation3URLV_AA0B8Delegate_pSgAA0B21CommunicationProtocol_pSgAA8MetaDataO05EntryJ0OSgtcfc", - "mangledName": "$s18ShopifyCheckoutKit0B14ViewControllerC8checkout8delegate6client10entryPointAC10Foundation3URLV_AA0B8Delegate_pSgAA0B21CommunicationProtocol_pSgAA8MetaDataO05EntryJ0OSgtcfc", + "usr": "s:18ShopifyCheckoutKit0B14ViewControllerC8checkout13configuration8delegate6client10entryPointAC10Foundation3URLV_AA13ConfigurationVAA0B8Delegate_pSgAA0B21CommunicationProtocol_pSgAA8MetaDataO05EntryK0OSgtcfc", + "mangledName": "$s18ShopifyCheckoutKit0B14ViewControllerC8checkout13configuration8delegate6client10entryPointAC10Foundation3URLV_AA13ConfigurationVAA0B8Delegate_pSgAA0B21CommunicationProtocol_pSgAA8MetaDataO05EntryK0OSgtcfc", "moduleName": "ShopifyCheckoutKit", "isInternal": true, "declAttributes": [ @@ -2676,6 +2683,79 @@ "mangledName": "$s18ShopifyCheckoutKit0aB0V20UIViewControllerTypea", "moduleName": "ShopifyCheckoutKit" }, + { + "kind": "Var", + "name": "configuration", + "printedName": "configuration", + "children": [ + { + "kind": "TypeNominal", + "name": "Configuration", + "printedName": "ShopifyCheckoutKit.Configuration", + "usr": "s:18ShopifyCheckoutKit13ConfigurationV" + } + ], + "declKind": "Var", + "usr": "s:18ShopifyCheckoutKit0aB0V13configurationAA13ConfigurationVvp", + "mangledName": "$s18ShopifyCheckoutKit0aB0V13configurationAA13ConfigurationVvp", + "moduleName": "ShopifyCheckoutKit", + "declAttributes": [ + "HasStorage", + "Custom" + ], + "hasStorage": true, + "accessors": [ + { + "kind": "Accessor", + "name": "Get", + "printedName": "Get()", + "children": [ + { + "kind": "TypeNominal", + "name": "Configuration", + "printedName": "ShopifyCheckoutKit.Configuration", + "usr": "s:18ShopifyCheckoutKit13ConfigurationV" + } + ], + "declKind": "Accessor", + "usr": "s:18ShopifyCheckoutKit0aB0V13configurationAA13ConfigurationVvg", + "mangledName": "$s18ShopifyCheckoutKit0aB0V13configurationAA13ConfigurationVvg", + "moduleName": "ShopifyCheckoutKit", + "implicit": true, + "declAttributes": [ + "Transparent" + ], + "accessorKind": "get" + }, + { + "kind": "Accessor", + "name": "Set", + "printedName": "Set()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "Configuration", + "printedName": "ShopifyCheckoutKit.Configuration", + "usr": "s:18ShopifyCheckoutKit13ConfigurationV" + } + ], + "declKind": "Accessor", + "usr": "s:18ShopifyCheckoutKit0aB0V13configurationAA13ConfigurationVvs", + "mangledName": "$s18ShopifyCheckoutKit0aB0V13configurationAA13ConfigurationVvs", + "moduleName": "ShopifyCheckoutKit", + "implicit": true, + "declAttributes": [ + "Transparent" + ], + "accessorKind": "set" + } + ] + }, { "kind": "Constructor", "name": "init", @@ -3083,6 +3163,76 @@ "name": "CheckoutConfigurable", "printedName": "CheckoutConfigurable", "children": [ + { + "kind": "Var", + "name": "configuration", + "printedName": "configuration", + "children": [ + { + "kind": "TypeNominal", + "name": "Configuration", + "printedName": "ShopifyCheckoutKit.Configuration", + "usr": "s:18ShopifyCheckoutKit13ConfigurationV" + } + ], + "declKind": "Var", + "usr": "s:18ShopifyCheckoutKit0B12ConfigurableP13configurationAA13ConfigurationVvp", + "mangledName": "$s18ShopifyCheckoutKit0B12ConfigurableP13configurationAA13ConfigurationVvp", + "moduleName": "ShopifyCheckoutKit", + "protocolReq": true, + "declAttributes": [ + "Custom" + ], + "accessors": [ + { + "kind": "Accessor", + "name": "Get", + "printedName": "Get()", + "children": [ + { + "kind": "TypeNominal", + "name": "Configuration", + "printedName": "ShopifyCheckoutKit.Configuration", + "usr": "s:18ShopifyCheckoutKit13ConfigurationV" + } + ], + "declKind": "Accessor", + "usr": "s:18ShopifyCheckoutKit0B12ConfigurableP13configurationAA13ConfigurationVvg", + "mangledName": "$s18ShopifyCheckoutKit0B12ConfigurableP13configurationAA13ConfigurationVvg", + "moduleName": "ShopifyCheckoutKit", + "genericSig": "", + "protocolReq": true, + "reqNewWitnessTableEntry": true, + "accessorKind": "get" + }, + { + "kind": "Accessor", + "name": "Set", + "printedName": "Set()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "Configuration", + "printedName": "ShopifyCheckoutKit.Configuration", + "usr": "s:18ShopifyCheckoutKit13ConfigurationV" + } + ], + "declKind": "Accessor", + "usr": "s:18ShopifyCheckoutKit0B12ConfigurableP13configurationAA13ConfigurationVvs", + "mangledName": "$s18ShopifyCheckoutKit0B12ConfigurableP13configurationAA13ConfigurationVvs", + "moduleName": "ShopifyCheckoutKit", + "genericSig": "", + "protocolReq": true, + "reqNewWitnessTableEntry": true, + "accessorKind": "set" + } + ] + }, { "kind": "Function", "name": "backgroundColor",