diff --git a/Apps/Locations/Libraries/Sources/Core/Protocols/Coordinator.swift b/Apps/Locations/Libraries/Sources/Core/Protocols/Coordinator.swift index 8563606..e7ebaa2 100644 --- a/Apps/Locations/Libraries/Sources/Core/Protocols/Coordinator.swift +++ b/Apps/Locations/Libraries/Sources/Core/Protocols/Coordinator.swift @@ -22,8 +22,8 @@ public protocol Coordinator: AnyObject { /// Present the coordinator animatedly or not, dependencing on the given `animated` parameter, and also pass a closure that should be called on dismissal. /// - Parameters: /// - animated: A boolean that represents whether the coordinator should be dismissed animatedly or not. - /// - onDismissed: A closure to be called or executed when the presented coordinator is dismissed. - func present(animated: Bool, onDismissed: Router.OnDismissedClosure?) + /// - onDismiss: A closure to be called or executed when the presented coordinator is dismissed. + func present(animated: Bool, onDismiss: Router.OnDismissedClosure?) } @@ -35,11 +35,11 @@ public extension Coordinator { /// - Parameters: /// - child: A child coordinator to be presented. /// - animated: A boolean that represents whether the coordinator should be dismissed animatedly or not. - /// - onDismissed: A closure to be called or executed when the presented coordinator is dismissed. + /// - onDismiss: A closure to be called or executed when the presented coordinator is dismissed. func present( child: Coordinator, animated: Bool, - onDismissed: Router.OnDismissedClosure? = nil + onDismiss: Router.OnDismissedClosure? = nil ) { store(child) child.present(animated: animated) { [weak self, weak child] in @@ -48,7 +48,7 @@ public extension Coordinator { } self.free(child) - onDismissed?() + onDismiss?() } } diff --git a/Apps/Locations/Libraries/Tests/CoreTests/Helpers/TestCoordinators.swift b/Apps/Locations/Libraries/Tests/CoreTests/Helpers/TestCoordinators.swift new file mode 100644 index 0000000..3db996e --- /dev/null +++ b/Apps/Locations/Libraries/Tests/CoreTests/Helpers/TestCoordinators.swift @@ -0,0 +1,106 @@ +// +// TestCoordinators.swift +// CoreTests +// +// Created by Javier Cicchelli on 11/04/2023. +// Copyright © 2023 Röck+Cöde. All rights reserved. +// + +import Core +import UIKit + +// MARK: - Test coordinators + +class SomeCoordinator: Coordinator { + + // MARK: Properties + + var children: [Coordinator] = [] + var router: Router + + // MARK: Initialisers + + init(router: Router) { + self.router = router + } + + // MARK: Functions + + func present(animated: Bool, onDismiss: (() -> Void)?) { + router.present( + SomeViewController(), + animated: animated, + onDismiss: onDismiss + ) + } + +} + +class SomeOtherCoordinator: Coordinator { + + // MARK: Properties + + var children: [Coordinator] = [] + var router: Router + + // MARK: Initialisers + + init(router: Router) { + self.router = router + } + + // MARK: Functions + + func present(animated: Bool, onDismiss: (() -> Void)?) { + router.present( + SomeOtherViewController(), + animated: animated, + onDismiss: onDismiss + ) + } + +} + +// MARK: - SpyRouter + +class SpyRouter: Router { + + // MARK: Enumerations + + enum State { + case initialised + case presented + case dismissed + } + + // MARK: Properties + + var state: State = .initialised + var viewController: UIViewController? + var animated: Bool? + var onDismiss: OnDismissedClosure? + + // MARK: Functions + + func present( + _ viewController: UIViewController, + animated: Bool, + onDismiss: OnDismissedClosure? + ) { + self.viewController = viewController + self.animated = animated + self.onDismiss = onDismiss + self.state = .presented + } + + func dismiss(animated: Bool) { + self.animated = animated + self.state = .dismissed + } + +} + +// MARK: - Test view controllers + +class SomeViewController: UIViewController {} +class SomeOtherViewController: UIViewController {} diff --git a/Apps/Locations/Libraries/Tests/CoreTests/Protocols/CoordinatorTests.swift b/Apps/Locations/Libraries/Tests/CoreTests/Protocols/CoordinatorTests.swift new file mode 100644 index 0000000..57bb5fe --- /dev/null +++ b/Apps/Locations/Libraries/Tests/CoreTests/Protocols/CoordinatorTests.swift @@ -0,0 +1,138 @@ +// +// CoordinatorTests.swift +// CoreTests +// +// Created by Javier Cicchelli on 11/04/2023. +// Copyright © 2023 Röck+Cöde. All rights reserved. +// + +import UIKit +import XCTest + +@testable import Core + +final class CoordinatorTests: XCTestCase { + + // MARK: Properties + + private var router: SpyRouter! + private var coordinator: Coordinator! + + // MARK: Tests + + func test_present_withoutOnDismissClosure() { + // Executing this test on the main thread is required as a `UIViewController` instance in being initialised here. + DispatchQueue.main.async { + // GIVEN + self.router = SpyRouter() + self.coordinator = SomeCoordinator(router: self.router) + + + // WHEN + self.coordinator.present(animated: false, onDismiss: nil) + + // THEN + XCTAssertTrue(self.coordinator.children.isEmpty) + XCTAssertEqual(self.router.state, .presented) + XCTAssertTrue(self.router.viewController is SomeViewController) + XCTAssertFalse(self.router.animated ?? true) + XCTAssertNil(self.router.onDismiss) + } + } + + func test_present_withOnDismissClosure() { + // Executing this test on the main thread is required as a `UIViewController` instance in being initialised here. + DispatchQueue.main.async { + // GIVEN + self.router = SpyRouter() + self.coordinator = SomeOtherCoordinator(router: self.router) + + // WHEN + self.coordinator.present(animated: true) {} + + // THEN + XCTAssertTrue(self.coordinator.children.isEmpty) + XCTAssertEqual(self.router.state, .presented) + XCTAssertTrue(self.router.viewController is SomeOtherViewController) + XCTAssertTrue(self.router.animated ?? false) + XCTAssertNotNil(self.router.onDismiss) + } + } + + func test_presentChild_withoutOnDismissClosure() { + // Executing this test on the main thread is required as a `UIViewController` instance in being initialised here. + DispatchQueue.main.async { + // GIVEN + self.router = SpyRouter() + self.coordinator = SomeCoordinator(router: self.router) + + let child = SomeOtherCoordinator(router: self.router) + + // WHEN + self.coordinator.present(child: child, animated: false) + + // THEN + XCTAssertFalse(self.coordinator.children.isEmpty) + XCTAssertEqual(self.coordinator.children.count, 1) + XCTAssertEqual(self.router.state, .presented) + XCTAssertTrue(self.router.viewController is SomeOtherViewController) + XCTAssertFalse(self.router.animated ?? true) + XCTAssertNil(self.router.onDismiss) + } + } + + func test_presentChild_withOnDismissClosure() { + // Executing this test on the main thread is required as a `UIViewController` instance in being initialised here. + DispatchQueue.main.async { + // GIVEN + self.router = SpyRouter() + self.coordinator = SomeOtherCoordinator(router: self.router) + + let child = SomeCoordinator(router: self.router) + + // WHEN + self.coordinator.present(child: child, animated: true) {} + + // THEN + XCTAssertFalse(self.coordinator.children.isEmpty) + XCTAssertEqual(self.coordinator.children.count, 1) + XCTAssertEqual(self.router.state, .presented) + XCTAssertTrue(self.router.viewController is SomeViewController) + XCTAssertTrue(self.router.animated ?? false) + XCTAssertNotNil(self.router.onDismiss) + } + } + + func test_dismiss_notAnimated() { + // GIVEN + router = SpyRouter() + coordinator = SomeOtherCoordinator(router: router) + + // WHEN + coordinator.dismiss(animated: false) + + // THEN + XCTAssertTrue(coordinator.children.isEmpty) + XCTAssertEqual(router.state, .dismissed) + XCTAssertNil(router.viewController) + XCTAssertFalse(router.animated ?? true) + XCTAssertNil(router.onDismiss) + } + + func test_dismiss_animated() { + // GIVEN + router = SpyRouter() + coordinator = SomeOtherCoordinator(router: router) + + // WHEN + coordinator.dismiss(animated: true) + + // THEN + XCTAssertTrue(coordinator.children.isEmpty) + XCTAssertEqual(router.state, .dismissed) + XCTAssertNil(router.viewController) + XCTAssertTrue(router.animated ?? false) + XCTAssertNil(router.onDismiss) + } + +}