Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

🚧 [WIP] Add UI tests for iOS #442

Draft
wants to merge 3 commits into
base: develop
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion .github/workflows/ios_tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@ jobs:
key: temp-node-${{ github.sha }}

- name: Build dependency if needed
if: steps.yarn-cache.outputs.cache-hit != 'true'
run: yarn

- uses: actions/cache@v4
Expand All @@ -54,6 +53,12 @@ jobs:
working-directory: example/ios
run: pod update

- name: Run Metro
working-directory: example
run: |
yarn
osascript -e 'tell application "Terminal" to do script "'"cd $(pwd) && npx react-native start"'"'

- name: Run tests
working-directory: example/ios
run: |
Expand Down
35 changes: 35 additions & 0 deletions example/ios/AdyenExample.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,11 @@
B6CB27F7E4011E633F2A25A7 /* libPods-AdyenExample.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 74FF1EDD8A3337F905F9FF61 /* libPods-AdyenExample.a */; };
E773B1092BE3B48900638431 /* DropInConfigurationParserTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E773B1082BE3B48900638431 /* DropInConfigurationParserTests.swift */; };
E79C3AD02BE161F100FACBCA /* ThreeDSConfigurationParserTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E79C3ACF2BE161F100FACBCA /* ThreeDSConfigurationParserTests.swift */; };
E7ED1EC22BF25F6C00B5F080 /* DropInNativeModuleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E7ED1EBD2BF25F6C00B5F080 /* DropInNativeModuleTests.swift */; };
E7ED1EC32BF25F6C00B5F080 /* Wait+UIKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = E7ED1EBE2BF25F6C00B5F080 /* Wait+UIKit.swift */; };
E7ED1EC42BF25F6C00B5F080 /* UIViewController+Search.swift in Sources */ = {isa = PBXBuildFile; fileRef = E7ED1EBF2BF25F6C00B5F080 /* UIViewController+Search.swift */; };
E7ED1EC52BF25F6C00B5F080 /* Wait.swift in Sources */ = {isa = PBXBuildFile; fileRef = E7ED1EC02BF25F6C00B5F080 /* Wait.swift */; };
E7ED1EC62BF25F6C00B5F080 /* UIView+Search.swift in Sources */ = {isa = PBXBuildFile; fileRef = E7ED1EC12BF25F6C00B5F080 /* UIView+Search.swift */; };
F5C555F429F6C9F5008E7AA8 /* AdyenAppearance.swift in Sources */ = {isa = PBXBuildFile; fileRef = F5C555F329F6C9F5008E7AA8 /* AdyenAppearance.swift */; };
/* End PBXBuildFile section */

Expand Down Expand Up @@ -53,6 +58,11 @@
E757C9D727737E6700B62256 /* AdyenExample.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; name = AdyenExample.entitlements; path = AdyenExample/AdyenExample.entitlements; sourceTree = "<group>"; };
E773B1082BE3B48900638431 /* DropInConfigurationParserTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DropInConfigurationParserTests.swift; sourceTree = "<group>"; };
E79C3ACF2BE161F100FACBCA /* ThreeDSConfigurationParserTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ThreeDSConfigurationParserTests.swift; sourceTree = "<group>"; };
E7ED1EBD2BF25F6C00B5F080 /* DropInNativeModuleTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DropInNativeModuleTests.swift; sourceTree = "<group>"; };
E7ED1EBE2BF25F6C00B5F080 /* Wait+UIKit.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Wait+UIKit.swift"; sourceTree = "<group>"; };
E7ED1EBF2BF25F6C00B5F080 /* UIViewController+Search.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIViewController+Search.swift"; sourceTree = "<group>"; };
E7ED1EC02BF25F6C00B5F080 /* Wait.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Wait.swift; sourceTree = "<group>"; };
E7ED1EC12BF25F6C00B5F080 /* UIView+Search.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIView+Search.swift"; sourceTree = "<group>"; };
ED297162215061F000B7C4FE /* JavaScriptCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = JavaScriptCore.framework; path = System/Library/Frameworks/JavaScriptCore.framework; sourceTree = SDKROOT; };
F5C555F329F6C9F5008E7AA8 /* AdyenAppearance.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdyenAppearance.swift; sourceTree = "<group>"; };
F6D60E250285EB5A965E4921 /* Pods-AdyenExample.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-AdyenExample.debug.xcconfig"; path = "Target Support Files/Pods-AdyenExample/Pods-AdyenExample.debug.xcconfig"; sourceTree = "<group>"; };
Expand Down Expand Up @@ -81,6 +91,11 @@
00E356EF1AD99517003FC87E /* AdyenExampleTests */ = {
isa = PBXGroup;
children = (
E7ED1EBD2BF25F6C00B5F080 /* DropInNativeModuleTests.swift */,
E7ED1EC12BF25F6C00B5F080 /* UIView+Search.swift */,
E7ED1EBF2BF25F6C00B5F080 /* UIViewController+Search.swift */,
E7ED1EC02BF25F6C00B5F080 /* Wait.swift */,
E7ED1EBE2BF25F6C00B5F080 /* Wait+UIKit.swift */,
E773B10A2BE3B5B000638431 /* Parsers */,
00E356F01AD99517003FC87E /* Supporting Files */,
948B9B012A83D269003D15B4 /* DropInTest.swift */,
Expand Down Expand Up @@ -431,9 +446,14 @@
buildActionMask = 2147483647;
files = (
94FC2FEC2AF28DAE009F6FC1 /* CardConfigurationTests.swift in Sources */,
E7ED1EC52BF25F6C00B5F080 /* Wait.swift in Sources */,
E7ED1EC62BF25F6C00B5F080 /* UIView+Search.swift in Sources */,
945936B12AE185B700F3E676 /* ApplePayConfigurationTests.swift in Sources */,
E773B1092BE3B48900638431 /* DropInConfigurationParserTests.swift in Sources */,
948B9B022A83D269003D15B4 /* DropInTest.swift in Sources */,
E7ED1EC22BF25F6C00B5F080 /* DropInNativeModuleTests.swift in Sources */,
E7ED1EC32BF25F6C00B5F080 /* Wait+UIKit.swift in Sources */,
E7ED1EC42BF25F6C00B5F080 /* UIViewController+Search.swift in Sources */,
E79C3AD02BE161F100FACBCA /* ThreeDSConfigurationParserTests.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
Expand Down Expand Up @@ -618,6 +638,7 @@
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
_LIBCPP_ENABLE_CXX17_REMOVED_UNARY_BINARY_FUNCTION,
);
GCC_SYMBOLS_PRIVATE_EXTERN = NO;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
Expand All @@ -638,6 +659,11 @@
);
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
OTHER_LDFLAGS = (
"$(inherited)",
"-Wl",
"-ld_classic",
);
REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native";
SDKROOT = iphoneos;
};
Expand Down Expand Up @@ -680,6 +706,10 @@
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = "";
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_NO_COMMON_BLOCKS = YES;
GCC_PREPROCESSOR_DEFINITIONS = (
"$(inherited)",
_LIBCPP_ENABLE_CXX17_REMOVED_UNARY_BINARY_FUNCTION,
);
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
Expand All @@ -697,6 +727,11 @@
"\"$(inherited)\"",
);
MTL_ENABLE_DEBUG_INFO = NO;
OTHER_LDFLAGS = (
"$(inherited)",
"-Wl",
"-ld_classic",
);
REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native";
SDKROOT = iphoneos;
VALIDATE_PRODUCT = YES;
Expand Down
170 changes: 170 additions & 0 deletions example/ios/AdyenExampleTests/DropInNativeModuleTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
//
// Copyright (c) 2024 Adyen N.V.
//
// This file is open source and available under the MIT license. See the LICENSE file for more info.
//

import Adyen
@testable import adyen_react_native
import XCTest

final class DropInNativeModuleTests: XCTestCase {

let shortPaymentMethods: NSDictionary = ["paymentMethods": [
[
"type": "scheme",
"name": "Cards"
],
[
"type": "klarna",
"name": "Klarna"
],
]]

let fullPaymentMethods: NSDictionary = [
"paymentMethods": [
[
"type": "scheme",
"name": "Cards"
],
[
"type": "klarna",
"name": "Klarna"
],
],
"storedPaymentMethods": [
[
"brand": "visa",
"expiryMonth": "03",
"expiryYear": "30",
"id": "J469JCZC5KPBGP65",
"lastFour": "6746",
"name": "VISA",
"supportedShopperInteractions": [
"Ecommerce",
"ContAuth"
],
"type": "scheme"
],
]
]


func testSimpleList() throws {
// GIVEN
let sut = DropInModule()
let config: NSDictionary = ["clientKey": "live_XXXXXXXXXX"]


// WHEN
sut.open(shortPaymentMethods, configuration: config)

// THEN
XCTAssertTrue(try isPresentingDropIn())

let topView = try XCTUnwrap(getDropInView() as? UITableViewController)
XCTAssertEqual(getNumberOfElement(in: topView.tableView, section: 0), 2)
XCTAssertEqual(topView.title, "Payment Methods")

// TEAR DOWN
dissmissDropIn()
}

func testStoredList() throws {
// GIVEN
let sut = DropInModule()
let config: NSDictionary = [
"clientKey": "live_XXXXXXXXXX",
]

// WHEN
sut.open(fullPaymentMethods, configuration: config)

// THEN
XCTAssertTrue(try isPresentingDropIn())

let topView = try XCTUnwrap(getDropInView())
XCTAssertEqual(topView.title, "AdyenExample")

// TEAR DOWN
dissmissDropIn()
}

func testTitleSetter() throws {
// GIVEN
let sut = DropInModule()
let config: NSDictionary = [
"clientKey": "live_XXXXXXXXXX",
"dropin": [
"title": "MY_TITLE"
]
]

// WHEN
sut.open(fullPaymentMethods, configuration: config)

// THEN
XCTAssertTrue(try isPresentingDropIn())

let topView = try XCTUnwrap(getDropInView())
XCTAssertEqual(topView.title, "MY_TITLE")

// TEAR DOWN
dissmissDropIn()
}

func testSkipingPreset() throws {
// GIVEN
let sut = DropInModule()
let config: NSDictionary = [
"clientKey": "live_XXXXXXXXXX",
"dropin": [
"showPreselectedStoredPaymentMethod": false
]
]

// WHEN
sut.open(fullPaymentMethods, configuration: config)

// THEN
XCTAssertTrue(try isPresentingDropIn())

let topView = try XCTUnwrap(getDropInView() as? UITableViewController)
XCTAssertEqual(getNumberOfElement(in: topView.tableView, section: 0), 1)
XCTAssertEqual(getNumberOfElement(in: topView.tableView, section: 1), 2)
XCTAssertEqual(topView.title, "Payment Methods")

// TEAR DOWN
dissmissDropIn()
}

func isPresentingDropIn() throws -> Bool {
let dropin = try waitUntilTopPresenter(isOfType: UINavigationController.self)
return dropin is AdyenObserver
}

func getDropInView() -> UIViewController? {

var controller: UIViewController?
var nextController: UIViewController? = UIApplication.shared.keyWindow?.rootViewController?.presentedViewController

while nextController != nil {
controller = nextController
nextController = nextController?.children.first
}
return controller
}

func getNumberOfElement(in tableView: UITableView, section: Int) -> Int? {
tableView.dataSource?.tableView(tableView, numberOfRowsInSection: section)
}

func dissmissDropIn() {
let expectation = expectation(description: "DropIn closing")
UIApplication.shared.keyWindow?.rootViewController?.dismiss(animated: false, completion: {
expectation.fulfill()
})
wait(for: [expectation])
}

}
37 changes: 37 additions & 0 deletions example/ios/AdyenExampleTests/UIView+Search.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
//
// Copyright (c) 2024 Adyen N.V.
//
// This file is open source and available under the MIT license. See the LICENSE file for more info.
//

import UIKit

internal extension UIView {
func findView<T: UIView>(with accessibilityIdentifier: String) -> T? {
if self.accessibilityIdentifier == accessibilityIdentifier {
return self as? T
}

for subview in subviews {
if let v = subview.findView(with: accessibilityIdentifier) {
return v as? T
}
}

return nil
}

func findView<T: UIView>(by lastAccessibilityIdentifierComponent: String) -> T? {
if self.accessibilityIdentifier?.hasSuffix(lastAccessibilityIdentifierComponent) == true {
return self as? T
}

for subview in subviews {
if let v = subview.findView(by: lastAccessibilityIdentifierComponent) {
return v as? T
}
}

return nil
}
}
44 changes: 44 additions & 0 deletions example/ios/AdyenExampleTests/UIViewController+Search.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
//
// Copyright (c) 2024 Adyen N.V.
//
// This file is open source and available under the MIT license. See the LICENSE file for more info.
//

import UIKit
import XCTest
@_spi(AdyenInternal) @testable import Adyen

public extension UIViewController {

/// Returns the first child of the viewControllers children that matches the type
///
/// - Parameters:
/// - type: The type of the viewController
func firstChild<T: UIViewController>(of type: T.Type) -> T? {
if let result = self as? T {
return result
}

for child in self.children {
if let result = child.firstChild(of: T.self) {
return result
}
}

return nil
}

/// Returns the current top viewController
///
/// - Throws: if there is no rootViewController can be found on the window
static func topPresenter() throws -> UIViewController {
let rootViewController = try XCTUnwrap(UIApplication.shared.adyen.mainKeyWindow?.rootViewController)
return rootViewController.adyen.topPresenter
}
}

extension UIViewController: PresentationDelegate {
public func present(component: PresentableComponent) {
self.present(component.viewController, animated: false, completion: nil)
}
}
Loading
Loading