Skip to content

Commit

Permalink
chore: add UI tests
Browse files Browse the repository at this point in the history
  • Loading branch information
descorp committed May 13, 2024
1 parent e0100aa commit bfb6ea4
Show file tree
Hide file tree
Showing 7 changed files with 409 additions and 21 deletions.
15 changes: 15 additions & 0 deletions example/ios/AdyenExample.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -618,6 +618,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 +639,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 +686,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 +707,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)
}
}
60 changes: 60 additions & 0 deletions example/ios/AdyenExampleTests/Wait+UIKit.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
//
// 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

extension XCTestCase {

/// Waits for a viewController of a certain type to become a child of another viewController
///
/// Instead of waiting for a specific amount of time it polls if the expecation is returning true in time intervals of 10ms until the timeout is reached.
/// Use it whenever a value change is not guaranteed to be instant or happening after a short amount of time.
///
/// - Parameters:
/// - ofType: the type of the expected child viewController
/// - viewController: the parent viewController
/// - timeout: the maximum time (in seconds) to wait.
@discardableResult
func waitForViewController<T: UIViewController>(
ofType: T.Type,
toBecomeChildOf viewController: UIViewController,
timeout: TimeInterval = 60
) throws -> T {

wait(
until: { viewController.firstChild(of: T.self) != nil },
timeout: timeout,
message: "\(String(describing: T.self)) should appear on \(String(describing: viewController.self)) before timeout \(timeout)s"
)

return try XCTUnwrap(viewController.firstChild(of: T.self))
}

/// Waits for a viewController of a certain type to become a child of another viewController
///
/// Instead of waiting for a specific amount of time it polls if the expecation is returning true in time intervals of 10ms until the timeout is reached.
/// Use it whenever a value change is not guaranteed to be instant or happening after a short amount of time.
///
/// - Parameters:
/// - ofType: the type of the expected child viewController
/// - viewController: the parent viewController
/// - timeout: the maximum time (in seconds) to wait.
@discardableResult
func waitUntilTopPresenter<T: UIViewController>(
isOfType: T.Type,
timeout: TimeInterval = 60
) throws -> T {

wait(
until: { (try? UIViewController.topPresenter() is T) ?? false },
timeout: timeout,
message: "\(String(describing: T.self)) should become top presenter before timeout \(timeout)s"
)

return try XCTUnwrap(UIViewController.topPresenter() as? T)
}
}
Loading

0 comments on commit bfb6ea4

Please sign in to comment.