diff --git a/Adyen.xcodeproj/project.pbxproj b/Adyen.xcodeproj/project.pbxproj index ad511456b0..8116700d3b 100644 --- a/Adyen.xcodeproj/project.pbxproj +++ b/Adyen.xcodeproj/project.pbxproj @@ -428,6 +428,9 @@ A0F4559B295F0F58001742C7 /* MealVoucherPaymentMethod.swift in Sources */ = {isa = PBXBuildFile; fileRef = A0F4559A295F0F58001742C7 /* MealVoucherPaymentMethod.swift */; }; A0F455A12968472B001742C7 /* PartialPaymentMethodDetails.swift in Sources */ = {isa = PBXBuildFile; fileRef = A0F455A02968472B001742C7 /* PartialPaymentMethodDetails.swift */; }; A0FA143F26D65A5300627127 /* InstallmentPickerElement.swift in Sources */ = {isa = PBXBuildFile; fileRef = A0FA143E26D65A5300627127 /* InstallmentPickerElement.swift */; }; + B605AC0B2D897A930084D583 /* CardScannerControllerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B605AC092D897A930084D583 /* CardScannerControllerTests.swift */; }; + B605AC112D8D988D0084D583 /* AdyenCardScanner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C9FC2C3D2D50D0B700C9D0F3 /* AdyenCardScanner.framework */; }; + B605AC122D8D988D0084D583 /* AdyenCardScanner.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = C9FC2C3D2D50D0B700C9D0F3 /* AdyenCardScanner.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; B62A075C2D71FB43006072E7 /* FormCardNumberItemView+ScanCard.swift in Sources */ = {isa = PBXBuildFile; fileRef = B62A075B2D71FB43006072E7 /* FormCardNumberItemView+ScanCard.swift */; }; B62D48B42BBE8DBE001EF01A /* AnalyticsFlavorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C97C16AB280702B200534419 /* AnalyticsFlavorTests.swift */; }; B62D48B52BBE8DBE001EF01A /* AnalyticsEventTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C97C16A928059A5A00534419 /* AnalyticsEventTests.swift */; }; @@ -438,8 +441,6 @@ B62D48C62BBE8F45001EF01A /* XCTestCase+Wait.swift in Sources */ = {isa = PBXBuildFile; fileRef = B62D48C22BBE8ED6001EF01A /* XCTestCase+Wait.swift */; }; B62D48C82BBE8F47001EF01A /* XCTestCase+Wait.swift in Sources */ = {isa = PBXBuildFile; fileRef = B62D48C22BBE8ED6001EF01A /* XCTestCase+Wait.swift */; }; B639C0822D8039F600472EBB /* CardScannerController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B639C0812D80399800472EBB /* CardScannerController.swift */; }; - B639C0832D80456300472EBB /* AdyenCardScanner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C9FC2C3D2D50D0B700C9D0F3 /* AdyenCardScanner.framework */; }; - B639C0842D80456300472EBB /* AdyenCardScanner.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = C9FC2C3D2D50D0B700C9D0F3 /* AdyenCardScanner.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; B6ABA1C32CA6B058003514E5 /* ListItemTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6ABA1C22CA6B058003514E5 /* ListItemTests.swift */; }; B6C9DA792D0C3F62005D65C7 /* DualBrandView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6C9DA782D0C3F62005D65C7 /* DualBrandView.swift */; }; B6C9DA7B2D102C91005D65C7 /* DualBrandViewTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6C9DA7A2D102C91005D65C7 /* DualBrandViewTests.swift */; }; @@ -1286,7 +1287,14 @@ remoteGlobalIDString = E2C0E03222097917008616F6; remoteInfo = Adyen; }; - B639C0852D80456300472EBB /* PBXContainerItemProxy */ = { + B605AC0E2D897F960084D583 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = E2C0E02A22097917008616F6 /* Project object */; + proxyType = 1; + remoteGlobalIDString = C9FC2C3C2D50D0B700C9D0F3; + remoteInfo = AdyenCardScanner; + }; + B605AC132D8D988D0084D583 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = E2C0E02A22097917008616F6 /* Project object */; proxyType = 1; @@ -1499,6 +1507,16 @@ /* End PBXContainerItemProxy section */ /* Begin PBXCopyFilesBuildPhase section */ + B605AC102D897F960084D583 /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; E2C0E0A8220B0827008616F6 /* Embed Frameworks */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; @@ -1517,7 +1535,7 @@ E2C0E0AE220B0840008616F6 /* AdyenCard.framework in Embed Frameworks */, F92980AD27CE2B33000CA5CA /* AdyenSession.framework in Embed Frameworks */, A020EC4929E6EC4B0050B2FE /* AdyenCashAppPay.framework in Embed Frameworks */, - B639C0842D80456300472EBB /* AdyenCardScanner.framework in Embed Frameworks */, + B605AC122D8D988D0084D583 /* AdyenCardScanner.framework in Embed Frameworks */, ); name = "Embed Frameworks"; runOnlyForDeploymentPostprocessing = 0; @@ -1882,6 +1900,7 @@ A0F4559A295F0F58001742C7 /* MealVoucherPaymentMethod.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MealVoucherPaymentMethod.swift; sourceTree = ""; }; A0F455A02968472B001742C7 /* PartialPaymentMethodDetails.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PartialPaymentMethodDetails.swift; sourceTree = ""; }; A0FA143E26D65A5300627127 /* InstallmentPickerElement.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstallmentPickerElement.swift; sourceTree = ""; }; + B605AC092D897A930084D583 /* CardScannerControllerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CardScannerControllerTests.swift; sourceTree = ""; }; B62A075B2D71FB43006072E7 /* FormCardNumberItemView+ScanCard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "FormCardNumberItemView+ScanCard.swift"; sourceTree = ""; }; B62D48AC2BBE8D79001EF01A /* UnitTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = UnitTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; B62D48C22BBE8ED6001EF01A /* XCTestCase+Wait.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "XCTestCase+Wait.swift"; sourceTree = ""; }; @@ -2646,7 +2665,6 @@ 8182AABD2B974E2B0087568E /* AdyenTwint.framework in Frameworks */, E97B971322980CBA00505476 /* Adyen3DS2.framework in Frameworks */, 81B8036B2BE0E714003D037F /* AdyenWeChatPayInternal in Frameworks */, - 81B8036B2BE0E714003D037F /* AdyenWeChatPayInternal in Frameworks */, E97B970E22980C8400505476 /* AdyenDropIn.framework in Frameworks */, E97B970D22980C7900505476 /* AdyenCard.framework in Frameworks */, E2C0E03D22097917008616F6 /* Adyen.framework in Frameworks */, @@ -2674,7 +2692,7 @@ 81088A1F2BDBACB7007FCDB9 /* AdyenTwint.framework in Frameworks */, F9175EEE2593955C00D653BE /* AdyenComponents.framework in Frameworks */, F92327C425A46EF0002C5BC4 /* AdyenEncryption.framework in Frameworks */, - B639C0832D80456300472EBB /* AdyenCardScanner.framework in Frameworks */, + B605AC112D8D988D0084D583 /* AdyenCardScanner.framework in Frameworks */, 81A91AC22BEC12A2001E00C8 /* TwintSDK.xcframework in Frameworks */, F9A2D01225DBF104008944BE /* PassKit.framework in Frameworks */, F973A9192791C3B0005AA753 /* AdyenActions.framework in Frameworks */, @@ -3420,6 +3438,14 @@ path = Installment; sourceTree = ""; }; + B605AC0A2D897A930084D583 /* Card Scanner */ = { + isa = PBXGroup; + children = ( + B605AC092D897A930084D583 /* CardScannerControllerTests.swift */, + ); + path = "Card Scanner"; + sourceTree = ""; + }; B62D48AD2BBE8D79001EF01A /* UnitTests */ = { isa = PBXGroup; children = ( @@ -4923,6 +4949,7 @@ F9EDB799239664B500CFB3C9 /* StoredPaymentMethodComponentTests.swift */, F919DF9824EA64680027976E /* CardPublicKeyProviderTests.swift */, 81D7EC8F2CD24143005159F6 /* FormViewControllerTests.swift */, + B605AC0A2D897A930084D583 /* Card Scanner */, ); path = "Card Tests"; sourceTree = ""; @@ -6231,6 +6258,7 @@ E2C0E03822097917008616F6 /* Sources */, E2C0E03922097917008616F6 /* Frameworks */, E2C0E03A22097917008616F6 /* Resources */, + B605AC102D897F960084D583 /* Embed Frameworks */, ); buildRules = ( ); @@ -6239,6 +6267,7 @@ E97B971222980C8B00505476 /* PBXTargetDependency */, E2C0E03F22097917008616F6 /* PBXTargetDependency */, F9D57522237C345C009C18B5 /* PBXTargetDependency */, + B605AC0F2D897F960084D583 /* PBXTargetDependency */, ); name = IntegrationUIKitTests; packageProductDependencies = ( @@ -6294,7 +6323,7 @@ F94D65EA2B036A450095D61E /* PBXTargetDependency */, 81088A222BDBACB7007FCDB9 /* PBXTargetDependency */, 819BFB3E2BDBED960018DC9B /* PBXTargetDependency */, - B639C0862D80456300472EBB /* PBXTargetDependency */, + B605AC142D8D988D0084D583 /* PBXTargetDependency */, ); name = AdyenUIHost; packageProductDependencies = ( @@ -7469,6 +7498,7 @@ F9AC61C0243750D80062A00D /* AppLauncherMock.swift in Sources */, C9BB460927622F4100E6730B /* BACSConfirmationPresenterTests.swift in Sources */, C96688BF26A6FC1C00DC7297 /* AffirmComponentTests.swift in Sources */, + B605AC0B2D897A930084D583 /* CardScannerControllerTests.swift in Sources */, 81FC2C8F2BB18F0F007F1316 /* ImageLoaderMock.swift in Sources */, A0BDF3F22BD29E69001FF7E5 /* PostalCodeValidatorTests.swift in Sources */, 81DA70872BDA6075006CE5D5 /* Twint+Spy.swift in Sources */, @@ -8216,10 +8246,15 @@ target = E2C0E03222097917008616F6 /* Adyen */; targetProxy = A04F8C2D29E5957B00F3F62B /* PBXContainerItemProxy */; }; - B639C0862D80456300472EBB /* PBXTargetDependency */ = { + B605AC0F2D897F960084D583 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = C9FC2C3C2D50D0B700C9D0F3 /* AdyenCardScanner */; + targetProxy = B605AC0E2D897F960084D583 /* PBXContainerItemProxy */; + }; + B605AC142D8D988D0084D583 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = C9FC2C3C2D50D0B700C9D0F3 /* AdyenCardScanner */; - targetProxy = B639C0852D80456300472EBB /* PBXContainerItemProxy */; + targetProxy = B605AC132D8D988D0084D583 /* PBXContainerItemProxy */; }; B6EE0F482BBECBEF00B9810D /* PBXTargetDependency */ = { isa = PBXTargetDependency; diff --git a/AdyenCard/Components/Card/CardScannerController.swift b/AdyenCard/Components/Card/CardScannerController.swift index a8f7fe7942..d160fcad75 100644 --- a/AdyenCard/Components/Card/CardScannerController.swift +++ b/AdyenCard/Components/Card/CardScannerController.swift @@ -7,71 +7,156 @@ import Foundation import UIKit -internal protocol CardScannerControlling { - typealias CardModel = (String?, Date?) - +internal protocol CardScannerAvailability { var isScannerAvailable: Bool { get } +} + +internal typealias CardScanDetails = (number: String?, expirationDate: Date?) + +internal protocol CardScannerProviding { + func createCardScanner(completion: @escaping (Result) -> Void) -> UIViewController? +} - init(presenter: UIViewController) +internal protocol CardScannerControlling: CardScannerAvailability { + + init(presenter: UIViewController, availabilityProvider: CardScannerAvailability, cardScannerProvider: CardScannerProviding) func openCardScanner() - - var onScanComplete: ((Result) -> Void)? { get set } + + var title: String? { get set } + var onScanComplete: ((Result) -> Void)? { get set } } #if canImport(AdyenCardScanner) import AdyenCardScanner + private struct CardScannerAvailabilityWrapper: CardScannerAvailability { + var isScannerAvailable: Bool { + AdyenCardScanner.CardScanner.isAvailable + } + } + + private struct CardScannerProviderWrapper: CardScannerProviding { + func createCardScanner(completion: @escaping (Result) -> Void) -> UIViewController? { + AdyenCardScanner.CardScanner.createCardScanner { result in + switch result { + case let .success(details): completion(.success((details.number, details.expirationDate))) + case let .failure(error): completion(.failure(error)) + } + } + } + } + internal final class CardScannerController: CardScannerControlling { internal enum CardScannerError: Error { case scanningError } - + private let presenter: UIViewController - internal var onScanComplete: ((Result<(String?, Date?), any Error>) -> Void)? - - internal init(presenter: UIViewController) { + private let availabilityProvider: CardScannerAvailability + private let cardScannerProvider: CardScannerProviding + internal var title: String? + + internal var onScanComplete: ((Result) -> Void)? + + internal init( + presenter: UIViewController, + availabilityProvider: CardScannerAvailability = CardScannerAvailabilityWrapper(), + cardScannerProvider: CardScannerProviding = CardScannerProviderWrapper() + ) { + self.availabilityProvider = availabilityProvider + self.cardScannerProvider = cardScannerProvider self.presenter = presenter } - + internal var isScannerAvailable: Bool { - if #available(iOS 13.0, *), AdyenCardScanner.CardScanner.isAvailable { true } else { false } + if #available(iOS 13.0, *), availabilityProvider.isScannerAvailable { true } else { false } } - + internal func openCardScanner() { - let scannerNavigationController = UINavigationController() - guard let scannerViewController = AdyenCardScanner.CardScanner.createCardScanner(completion: { [weak self] result in + let scannerNavigationController = makeNavigationController() + guard let scannerViewController = cardScannerProvider.createCardScanner(completion: { [weak self] result in guard let self else { return } - self.onScanComplete?(self.map(result)) + self.onScanComplete?(map(result)) scannerNavigationController.dismiss(animated: true) }) else { return } - + + scannerViewController.navigationItem.leftBarButtonItem = makeCancelBarButton() + scannerViewController.title = title + scannerNavigationController.setViewControllers( [scannerViewController], animated: false ) presenter.present(scannerNavigationController, animated: true) } - + // MARK: - Private - - private func map(_ result: Result) -> Result { + + private func map(_ result: Result) -> Result { switch result { - case let .success(card): - .success((card.number, card.expirationDate)) + case let .success(cardScanDetails): + .success(cardScanDetails) case .failure: .failure(CardScannerError.scanningError) } } + + private func makeNavigationController() -> UINavigationController { + guard #available(iOS 13.0, *) else { return UINavigationController() } + + let appearance = UINavigationBarAppearance() + appearance.configureWithDefaultBackground() + + let navigationController = UINavigationController() + navigationController.navigationBar.standardAppearance = appearance + navigationController.navigationBar.compactAppearance = appearance + navigationController.navigationBar.scrollEdgeAppearance = appearance + + return navigationController + } + + private func makeCancelBarButton() -> UIBarButtonItem { + UIBarButtonItem(barButtonSystemItem: .cancel, target: self, action: #selector(handleCardScanningCancelation)) + } + + @objc + private func handleCardScanningCancelation() { + handleCardScanningCancelationWithCompletion(nil) + } + + @objc + internal func handleCardScanningCancelationWithCompletion(_ completion: (() -> Void)?) { + presenter.presentedViewController?.dismiss(animated: true, completion: completion) + } } #else // canImport(AdyenCardScanner) internal final class CardScannerController: CardScannerControlling { internal var isScannerAvailable: Bool { false } - internal var onScanComplete: ((Result) -> Void)? + internal var onScanComplete: ((Result) -> Void)? + internal var title: String? internal func openCardScanner() {} - internal init(presenter: UIViewController) {} + internal init( + presenter: UIViewController, + availabilityProvider: CardScannerAvailability = DummyCardScannerAvailability(), + cardScannerProvider: CardScannerProviding = DummyCardScannerProvider() + ) {} + + // MARK: - Helpers + + internal struct DummyCardScannerAvailability: CardScannerAvailability { + internal var isScannerAvailable: Bool { false } + } + + internal struct DummyCardScannerProvider: CardScannerProviding { + internal func createCardScanner( + completion: @escaping (Result) -> Void + ) -> UIViewController? { + UIViewController() + } + } } #endif // canImport(AdyenCardScanner) diff --git a/AdyenCard/Components/Card/CardViewController.swift b/AdyenCard/Components/Card/CardViewController.swift index eec252a0d4..fbd767f259 100644 --- a/AdyenCard/Components/Card/CardViewController.swift +++ b/AdyenCard/Components/Card/CardViewController.swift @@ -28,6 +28,7 @@ internal class CardViewController: FormViewController { private let cardLogos: [FormCardLogosItem.CardTypeLogo] private lazy var cardScannerController: CardScannerControlling = { let controller = CardScannerController(presenter: self) + controller.title = localizedString(.scanYourCardButton, localizationParameters) controller.onScanComplete = { [weak self] result in self?.handleCardScanningResult(result) } @@ -409,7 +410,7 @@ extension CardViewController: CardViewControllerProtocol { // MARK: - Card scanner extension CardViewController { - private func handleCardScanningResult(_ result: Result<(String?, Date?), Error>) { + private func handleCardScanningResult(_ result: Result) { switch result { case let .success((number, expiryDate)): items.numberContainerItem.setCardNumber(number ?? "") diff --git a/Cartfile b/Cartfile index f8c62fbbb3..ffc9192740 100644 --- a/Cartfile +++ b/Cartfile @@ -1,4 +1,4 @@ github "adyen/adyen-3ds2-ios" == 2.4.2 -github "adyen/adyen-networking-ios" == 2.0.0 +github "adyen/adyen-networking-ios" == 3.0.1 github "adyen/adyen-wechatpay-ios" == 2.1.0 github "adyen/adyen-authentication-ios" == 3.1.0 \ No newline at end of file diff --git a/Tests/IntegrationTests/Card Tests/Card Scanner/CardScannerControllerTests.swift b/Tests/IntegrationTests/Card Tests/Card Scanner/CardScannerControllerTests.swift new file mode 100644 index 0000000000..cfbd6583de --- /dev/null +++ b/Tests/IntegrationTests/Card Tests/Card Scanner/CardScannerControllerTests.swift @@ -0,0 +1,148 @@ +// +// Copyright (c) 2025 Adyen N.V. +// +// This file is open source and available under the MIT license. See the LICENSE file for more info. +// + +#if canImport(AdyenCardScanner) + @testable import AdyenCard + @testable import AdyenCardScanner + import XCTest + + class CardScannerControllerTests: XCTestCase { + + // This test requires AdyenCardScanner framework to be imported for the test target + func test_scannerIsAvailable() { + let (sut, _, _) = makeSUT() + XCTAssertTrue(sut.isScannerAvailable) + } + + func test_openCardScanner_withTitle_presentsCorrectTitle() throws { + let expectation = XCTestExpectation(description: "Card scanner should complete the flow") + let (sut, presenter, _) = makeSUT() + + let window = UIWindow(frame: UIScreen.main.bounds) + window.rootViewController = presenter + window.makeKeyAndVisible() + + sut.onScanComplete = { result in + expectation.fulfill() + } + + let expectedTitle = "Scan your card" + sut.title = expectedTitle + sut.openCardScanner() + + let scannerNavigationController = presenter.presentedViewController as? UINavigationController + let scannerViewController = scannerNavigationController?.topViewController + XCTAssertEqual(scannerViewController?.title, expectedTitle) + + sut.onScanComplete?(.success((nil, Date()))) + wait(for: [expectation], timeout: 3.0) + } + + func testHandleCardScanningCancelation() throws { + let (sut, presenter, _) = makeSUT() + + sut.openCardScanner() + + sut.handleCardScanningCancelationWithCompletion { + XCTAssertNil(presenter.presentedViewController) + } + } + + func test_controller_returnsScannedCardValue() { + // Given + let expectation = XCTestExpectation(description: "Card scanner should complete the flow") + + let cardNumber = "1111 2222 3333 4444" + let expiryDate = Date(timeIntervalSince1970: 1742456818) + + let expectedResult: CardScanDetails = (cardNumber, expiryDate) + let mockCard = CardScanDetails(cardNumber, expiryDate) + + let (sut, presenter, cardScanner) = makeSUT() + sut.onScanComplete = { result in + // Then + self.expect(result, toMatch: .success(expectedResult)) + expectation.fulfill() + } + + // When + sut.openCardScanner() + cardScanner.onScanComplete(result: .success(mockCard)) + + wait(for: [expectation], timeout: 1.0) + } + + func test_controller_returnsSimplifiedScannerError() { + // Given + let expectation = XCTestExpectation(description: "Card scanner should complete the flow") + let mockError = AdyenCardScanner.CardScannerError(kind: .authorizationDenied) + let expectedError = CardScannerController.CardScannerError.scanningError + let (sut, presenter, cardScanner) = makeSUT() + + sut.onScanComplete = { result in + // Then + self.expect(result, toMatch: .failure(expectedError)) + expectation.fulfill() + } + + // When + sut.openCardScanner() + cardScanner.onScanComplete(result: .failure(mockError)) + + wait(for: [expectation], timeout: 1.0) + } + + // MARK: - Helpers + + private func makeSUT() -> (CardScannerController, UIViewController, CardScannerProviderSpy) { + let presenter = UIViewController() + let cardScanner = CardScannerProviderSpy() + + let sut = CardScannerController( + presenter: presenter, + availabilityProvider: CardScannerAvailalabilityMock(), + cardScannerProvider: cardScanner + ) + return (sut, presenter, cardScanner) + } + + private func expect( + _ result: Result, + toMatch expectedResult: Result, + file: StaticString = #file, + line: UInt = #line + ) { + switch (result, expectedResult) { + case (.success(let (receivedCard, receivedDate)), .success(let (expectedCard, expectedDate))): + XCTAssertEqual(receivedCard, expectedCard, file: file, line: line) + XCTAssertEqual(receivedDate, expectedDate, file: file, line: line) + case let (.failure(receivedError as NSError), .failure(expectedError as NSError)): + XCTAssertEqual(receivedError, expectedError, file: file, line: line) + default: + XCTFail() + } + } + + private struct CardScannerAvailalabilityMock: CardScannerAvailability { + var isScannerAvailable: Bool { true } + } + + private class CardScannerProviderSpy: CardScannerProviding { + private var completion: ((Result) -> Void)? = nil + + func createCardScanner( + completion: @escaping (Result) -> Void + ) -> UIViewController? { + self.completion = completion + return UIViewController() + } + + func onScanComplete(result: Result) { + self.completion?(result) + } + } + } +#endif diff --git a/Tests/IntegrationTests/Components Tests/BACS Direct Debit/DocumentComponentTests.swift b/Tests/IntegrationTests/Components Tests/BACS Direct Debit/DocumentComponentTests.swift index 644f91d6e3..c777ca2ff4 100644 --- a/Tests/IntegrationTests/Components Tests/BACS Direct Debit/DocumentComponentTests.swift +++ b/Tests/IntegrationTests/Components Tests/BACS Direct Debit/DocumentComponentTests.swift @@ -28,12 +28,12 @@ class DocumentComponentTests: XCTestCase { sut.presentationDelegate = presentationDelegate sut.configuration.localizationParameters = LocalizationParameters(tableName: "test_table") - presentationDelegate.doPresent = { [self] component in + presentationDelegate.doPresent = { component in XCTAssertNotNil(component.viewController as? ADYViewController) let viewController = component.viewController as! ADYViewController - setupRootViewController(viewController) - + viewController.loadViewIfNeeded() + let pdfButton: UIButton? = viewController.view.findView(by: "mainButton") let messageLabel: UILabel? = viewController.view.findView(by: "messageLabel") let logo: UIImageView? = viewController.view.findView(by: "icon") diff --git a/Tests/IntegrationTests/Components Tests/Cash App Pay/CashAppPayComponentTests.swift b/Tests/IntegrationTests/Components Tests/Cash App Pay/CashAppPayComponentTests.swift index 99c738b13a..d635e3400e 100644 --- a/Tests/IntegrationTests/Components Tests/Cash App Pay/CashAppPayComponentTests.swift +++ b/Tests/IntegrationTests/Components Tests/Cash App Pay/CashAppPayComponentTests.swift @@ -100,9 +100,8 @@ import XCTest let config = CashAppPayConfiguration(redirectURL: URL(string: "test")!, showsStorePaymentMethodField: true, style: componentStyle) let sut = CashAppPayComponent(paymentMethod: paymentMethod, context: context, configuration: config) - setupRootViewController(sut.viewController) - wait(for: .milliseconds(300)) - + sut.viewController.loadViewIfNeeded() + let storeDetailsItemView: FormToggleItemView? = sut.viewController.view.findView(with: "AdyenCashAppPay.CashAppPayComponent.storeDetailsItem") let storeDetailsItemTitleLabel: UILabel? = sut.viewController.view.findView(with: "AdyenCashAppPay.CashAppPayComponent.storeDetailsItem.titleLabel") @@ -121,9 +120,8 @@ import XCTest let config = CashAppPayConfiguration(redirectURL: URL(string: "test")!, showsStorePaymentMethodField: true) let sut = CashAppPayComponent(paymentMethod: paymentMethod, context: context, configuration: config) - setupRootViewController(sut.viewController) - wait(for: .milliseconds(300)) - + sut.viewController.loadViewIfNeeded() + let storeDetailsToggleView: UIView? = sut.viewController.view.findView(with: "AdyenCashAppPay.CashAppPayComponent.storeDetailsItem") XCTAssertNotNil(storeDetailsToggleView) @@ -134,9 +132,8 @@ import XCTest let config = CashAppPayConfiguration(redirectURL: URL(string: "test")!, showsStorePaymentMethodField: false) let sut = CashAppPayComponent(paymentMethod: paymentMethod, context: context, configuration: config) - setupRootViewController(sut.viewController) - wait(for: .milliseconds(300)) - + sut.viewController.loadViewIfNeeded() + let storeDetailsToggleView: UIView? = sut.viewController.view.findView(with: "AdyenCashAppPay.CashAppPayComponent.storeDetailsItem") XCTAssertNil(storeDetailsToggleView) @@ -146,12 +143,8 @@ import XCTest let config = CashAppPayConfiguration(redirectURL: URL(string: "test")!, showsStorePaymentMethodField: true) let sut = CashAppPayComponent(paymentMethod: paymentMethod, context: context, configuration: config) - setupRootViewController(sut.viewController) - wait(for: .milliseconds(300)) + sut.viewController.loadViewIfNeeded() - setupRootViewController(sut.viewController) - wait(for: .milliseconds(300)) - XCTAssertFalse(sut.cashAppPayButton.showsActivityIndicator) sut.cashAppPayButton.showsActivityIndicator = true sut.stopLoadingIfNeeded() @@ -208,8 +201,8 @@ import XCTest let delegate = PaymentComponentDelegateMock() sut.delegate = delegate - setupRootViewController(sut.viewController) - + sut.viewController.loadViewIfNeeded() + let delegateExpectation = expectation(description: "PaymentComponentDelegate must be called when submit button is clicked.") let finalizationExpectation = expectation(description: "Component should finalize.") delegate.onDidSubmit = { data, component in @@ -228,8 +221,6 @@ import XCTest delegateExpectation.fulfill() } - wait(for: .milliseconds(300)) - sut.submitApprovedRequest(with: [oneTimeGrant], profile: .init(id: "testId", cashtag: "testtag")) waitForExpectations(timeout: 10, handler: nil) @@ -241,8 +232,8 @@ import XCTest let delegate = PaymentComponentDelegateMock() sut.delegate = delegate - setupRootViewController(sut.viewController) - + sut.viewController.loadViewIfNeeded() + let delegateExpectation = expectation(description: "PaymentComponentDelegate must be called when submit button is clicked.") let finalizationExpectation = expectation(description: "Component should finalize.") delegate.onDidSubmit = { data, component in @@ -261,8 +252,6 @@ import XCTest delegateExpectation.fulfill() } - wait(for: .milliseconds(300)) - sut.submitApprovedRequest(with: [oneTimeGrant, onFileGrant], profile: .init(id: "testId", cashtag: "testtag")) waitForExpectations(timeout: 10, handler: nil) @@ -276,7 +265,7 @@ import XCTest context: context, configuration: configuration ) - setupRootViewController(sut.viewController) + sut.viewController.loadViewIfNeeded() let paymentDelegateMock = PaymentComponentDelegateMock() sut.delegate = paymentDelegateMock @@ -309,7 +298,7 @@ import XCTest configuration: config ) - setupRootViewController(sut.viewController) + sut.viewController.loadViewIfNeeded() let paymentDelegateMock = PaymentComponentDelegateMock() sut.delegate = paymentDelegateMock @@ -459,7 +448,7 @@ import XCTest context: context, configuration: configuration ) - setupRootViewController(sut.viewController) + sut.viewController.loadViewIfNeeded() let formViewController = try XCTUnwrap((sut.viewController as? SecuredViewController)?.childViewController) let expectedResult = formViewController.validate() diff --git a/Tests/IntegrationTests/Components Tests/Doku/DokuComponentTests.swift b/Tests/IntegrationTests/Components Tests/Doku/DokuComponentTests.swift index 83bc235217..4f9e90a78f 100644 --- a/Tests/IntegrationTests/Components Tests/Doku/DokuComponentTests.swift +++ b/Tests/IntegrationTests/Components Tests/Doku/DokuComponentTests.swift @@ -90,7 +90,7 @@ class DokuComponentTests: XCTestCase { configuration: DokuComponent.Configuration() ) - setupRootViewController(sut.viewController) + sut.viewController.loadViewIfNeeded() wait(for: .milliseconds(300))