diff --git a/WooCommerce/Classes/Authentication/Epilogue/StorePickerCoordinator.swift b/WooCommerce/Classes/Authentication/Epilogue/StorePickerCoordinator.swift index 75b5caee673..23d312bfdef 100644 --- a/WooCommerce/Classes/Authentication/Epilogue/StorePickerCoordinator.swift +++ b/WooCommerce/Classes/Authentication/Epilogue/StorePickerCoordinator.swift @@ -7,7 +7,7 @@ import Yosemite /// final class StorePickerCoordinator: Coordinator { - unowned var navigationController: UINavigationController + unowned private(set) var navigationController: UINavigationController /// Determines how the store picker should initialized /// diff --git a/WooCommerce/Classes/Authentication/Store Creation/LoggedOutStoreCreationCoordinator.swift b/WooCommerce/Classes/Authentication/Store Creation/LoggedOutStoreCreationCoordinator.swift index c4e252d5884..c2ddc6cd7df 100644 --- a/WooCommerce/Classes/Authentication/Store Creation/LoggedOutStoreCreationCoordinator.swift +++ b/WooCommerce/Classes/Authentication/Store Creation/LoggedOutStoreCreationCoordinator.swift @@ -10,8 +10,7 @@ final class LoggedOutStoreCreationCoordinator: Coordinator { case loginEmailError } - /// Mutable to conform to `Coordinator` protocol. - var navigationController: UINavigationController + let navigationController: UINavigationController private var storePickerCoordinator: StorePickerCoordinator? diff --git a/WooCommerce/Classes/Authentication/Store Creation/StoreCreationCoordinator.swift b/WooCommerce/Classes/Authentication/Store Creation/StoreCreationCoordinator.swift index 01ca91a7e7f..95f3cdf4b4d 100644 --- a/WooCommerce/Classes/Authentication/Store Creation/StoreCreationCoordinator.swift +++ b/WooCommerce/Classes/Authentication/Store Creation/StoreCreationCoordinator.swift @@ -13,7 +13,7 @@ final class StoreCreationCoordinator: Coordinator { case storePicker } - var navigationController: UINavigationController + let navigationController: UINavigationController @Published private var possibleSiteURLsFromStoreCreation: Set = [] private var possibleSiteURLsFromStoreCreationSubscription: AnyCancellable? diff --git a/WooCommerce/Classes/Tools/Coordinator.swift b/WooCommerce/Classes/Tools/Coordinator.swift index 65d80bd6d4e..0263c030c72 100644 --- a/WooCommerce/Classes/Tools/Coordinator.swift +++ b/WooCommerce/Classes/Tools/Coordinator.swift @@ -5,7 +5,7 @@ import UIKit /// See: http://khanlou.com/2015/01/the-coordinator/ /// protocol Coordinator { - var navigationController: UINavigationController { get set } + var navigationController: UINavigationController { get } func start() } diff --git a/WooCommerce/Classes/ViewRelated/Hub Menu/HubMenuCoordinator.swift b/WooCommerce/Classes/ViewRelated/Hub Menu/HubMenuCoordinator.swift index 54b2562fa92..26abcbadb58 100644 --- a/WooCommerce/Classes/ViewRelated/Hub Menu/HubMenuCoordinator.swift +++ b/WooCommerce/Classes/ViewRelated/Hub Menu/HubMenuCoordinator.swift @@ -10,7 +10,7 @@ import protocol Yosemite.StoresManager /// Coordinator for the HubMenu tab. /// final class HubMenuCoordinator: Coordinator { - var navigationController: UINavigationController + let navigationController: UINavigationController var hubMenuController: HubMenuViewController? private let pushNotificationsManager: PushNotesManager diff --git a/WooCommerce/Classes/ViewRelated/Orders/Order Creation/FlowCoordinator/AddOrderCoordinator.swift b/WooCommerce/Classes/ViewRelated/Orders/Order Creation/FlowCoordinator/AddOrderCoordinator.swift index 3a141766e34..aef056aae9b 100644 --- a/WooCommerce/Classes/ViewRelated/Orders/Order Creation/FlowCoordinator/AddOrderCoordinator.swift +++ b/WooCommerce/Classes/ViewRelated/Orders/Order Creation/FlowCoordinator/AddOrderCoordinator.swift @@ -7,7 +7,7 @@ import WordPressUI /// Manages the different navigation flows that start from the Orders main tab /// final class AddOrderCoordinator: Coordinator { - var navigationController: UINavigationController + let navigationController: UINavigationController private let siteID: Int64 private let sourceBarButtonItem: UIBarButtonItem? diff --git a/WooCommerce/Classes/ViewRelated/Products/Add Product/AddProductCoordinator.swift b/WooCommerce/Classes/ViewRelated/Products/Add Product/AddProductCoordinator.swift index 62958d810d9..d575046b154 100644 --- a/WooCommerce/Classes/ViewRelated/Products/Add Product/AddProductCoordinator.swift +++ b/WooCommerce/Classes/ViewRelated/Products/Add Product/AddProductCoordinator.swift @@ -11,7 +11,7 @@ import class Networking.ProductsRemote /// coordinator.start() /// final class AddProductCoordinator: Coordinator { - var navigationController: UINavigationController + let navigationController: UINavigationController private let siteID: Int64 private let sourceBarButtonItem: UIBarButtonItem? diff --git a/WooCommerce/Classes/ViewRelated/Products/SKU Scanner/ProductSKUBarcodeScannerCoordinator.swift b/WooCommerce/Classes/ViewRelated/Products/SKU Scanner/ProductSKUBarcodeScannerCoordinator.swift index a328c7d2326..813357b1f75 100644 --- a/WooCommerce/Classes/ViewRelated/Products/SKU Scanner/ProductSKUBarcodeScannerCoordinator.swift +++ b/WooCommerce/Classes/ViewRelated/Products/SKU Scanner/ProductSKUBarcodeScannerCoordinator.swift @@ -3,7 +3,7 @@ import UIKit /// Coordinates navigation for product SKU barcode scanner based on camera permission. final class ProductSKUBarcodeScannerCoordinator: Coordinator { - var navigationController: UINavigationController + let navigationController: UINavigationController private let permissionChecker: CaptureDevicePermissionChecker private let onSKUBarcodeScanned: (_ barcode: String) -> Void diff --git a/WooCommerce/Classes/ViewRelated/Reviews/ReviewsCoordinator.swift b/WooCommerce/Classes/ViewRelated/Reviews/ReviewsCoordinator.swift index 13db7fee1d4..a659b6cd8d8 100644 --- a/WooCommerce/Classes/ViewRelated/Reviews/ReviewsCoordinator.swift +++ b/WooCommerce/Classes/ViewRelated/Reviews/ReviewsCoordinator.swift @@ -10,7 +10,7 @@ import protocol Yosemite.StoresManager /// Coordinator for the Reviews tab. /// final class ReviewsCoordinator: Coordinator { - var navigationController: UINavigationController + let navigationController: UINavigationController private let pushNotificationsManager: PushNotesManager private let storesManager: StoresManager diff --git a/WooCommerce/WooCommerce.xcodeproj/project.pbxproj b/WooCommerce/WooCommerce.xcodeproj/project.pbxproj index 14def4dc5db..df86aa9a927 100644 --- a/WooCommerce/WooCommerce.xcodeproj/project.pbxproj +++ b/WooCommerce/WooCommerce.xcodeproj/project.pbxproj @@ -46,6 +46,8 @@ 020BE77523B4A7EC007FE54C /* AztecSourceCodeFormatBarCommandTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 020BE77423B4A7EC007FE54C /* AztecSourceCodeFormatBarCommandTests.swift */; }; 020BE77723B4A9D9007FE54C /* AztecLinkFormatBarCommandTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 020BE77623B4A9D9007FE54C /* AztecLinkFormatBarCommandTests.swift */; }; 020C908424C84652001E2BEB /* ProductListMultiSelectorSearchUICommandTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 020C908324C84652001E2BEB /* ProductListMultiSelectorSearchUICommandTests.swift */; }; + 020D0BFD2914E92800BB3DCE /* StorePickerCoordinatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 020D0BFC2914E92800BB3DCE /* StorePickerCoordinatorTests.swift */; }; + 020D0BFF2914F6BA00BB3DCE /* LoggedOutStoreCreationCoordinatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 020D0BFE2914F6BA00BB3DCE /* LoggedOutStoreCreationCoordinatorTests.swift */; }; 020DD48A23229495005822B1 /* ProductsTabProductTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 020DD48923229495005822B1 /* ProductsTabProductTableViewCell.swift */; }; 020DD48D2322A617005822B1 /* ProductsTabProductViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 020DD48C2322A617005822B1 /* ProductsTabProductViewModel.swift */; }; 020DD48F232392C9005822B1 /* UIViewController+AppReview.swift in Sources */ = {isa = PBXBuildFile; fileRef = 020DD48E232392C9005822B1 /* UIViewController+AppReview.swift */; }; @@ -229,6 +231,7 @@ 0269576A23726304001BA0BF /* KeyboardFrameObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0269576923726304001BA0BF /* KeyboardFrameObserver.swift */; }; 0269576D23726401001BA0BF /* KeyboardFrameObserverTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0269576C23726401001BA0BF /* KeyboardFrameObserverTests.swift */; }; 02695770237281A9001BA0BF /* AztecTextViewAttachmentHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0269576F237281A9001BA0BF /* AztecTextViewAttachmentHandler.swift */; }; + 0269A5E72913FD22003B20EB /* StoreCreationCoordinatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0269A5E62913FD22003B20EB /* StoreCreationCoordinatorTests.swift */; }; 0269A63C2581D26C007B49ED /* ShippingLabelPrintingStepListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0269A63B2581D26C007B49ED /* ShippingLabelPrintingStepListView.swift */; }; 026B3C57249A046E00F7823C /* TextFieldTextAlignment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 026B3C56249A046E00F7823C /* TextFieldTextAlignment.swift */; }; 026B80CF289A5E0B001960E4 /* LoginOnboardingSurveyView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 026B80CE289A5E0B001960E4 /* LoginOnboardingSurveyView.swift */; }; @@ -1998,6 +2001,8 @@ 020BE77423B4A7EC007FE54C /* AztecSourceCodeFormatBarCommandTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AztecSourceCodeFormatBarCommandTests.swift; sourceTree = ""; }; 020BE77623B4A9D9007FE54C /* AztecLinkFormatBarCommandTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AztecLinkFormatBarCommandTests.swift; sourceTree = ""; }; 020C908324C84652001E2BEB /* ProductListMultiSelectorSearchUICommandTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProductListMultiSelectorSearchUICommandTests.swift; sourceTree = ""; }; + 020D0BFC2914E92800BB3DCE /* StorePickerCoordinatorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StorePickerCoordinatorTests.swift; sourceTree = ""; }; + 020D0BFE2914F6BA00BB3DCE /* LoggedOutStoreCreationCoordinatorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoggedOutStoreCreationCoordinatorTests.swift; sourceTree = ""; }; 020DD48923229495005822B1 /* ProductsTabProductTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProductsTabProductTableViewCell.swift; sourceTree = ""; }; 020DD48C2322A617005822B1 /* ProductsTabProductViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProductsTabProductViewModel.swift; sourceTree = ""; }; 020DD48E232392C9005822B1 /* UIViewController+AppReview.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIViewController+AppReview.swift"; sourceTree = ""; }; @@ -2181,6 +2186,7 @@ 0269576923726304001BA0BF /* KeyboardFrameObserver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyboardFrameObserver.swift; sourceTree = ""; }; 0269576C23726401001BA0BF /* KeyboardFrameObserverTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyboardFrameObserverTests.swift; sourceTree = ""; }; 0269576F237281A9001BA0BF /* AztecTextViewAttachmentHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AztecTextViewAttachmentHandler.swift; sourceTree = ""; }; + 0269A5E62913FD22003B20EB /* StoreCreationCoordinatorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StoreCreationCoordinatorTests.swift; sourceTree = ""; }; 0269A63B2581D26C007B49ED /* ShippingLabelPrintingStepListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShippingLabelPrintingStepListView.swift; sourceTree = ""; }; 026B3C56249A046E00F7823C /* TextFieldTextAlignment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextFieldTextAlignment.swift; sourceTree = ""; }; 026B80CE289A5E0B001960E4 /* LoginOnboardingSurveyView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginOnboardingSurveyView.swift; sourceTree = ""; }; @@ -6153,6 +6159,9 @@ DE61979428A25842005E4362 /* StorePickerViewModelTests.swift */, DE3404E928B4C1D000CF0D97 /* NonAtomicSiteViewModelTests.swift */, DE50295228BF4A8A00551736 /* JetpackConnectionWebViewModelTests.swift */, + 0269A5E62913FD22003B20EB /* StoreCreationCoordinatorTests.swift */, + 020D0BFC2914E92800BB3DCE /* StorePickerCoordinatorTests.swift */, + 020D0BFE2914F6BA00BB3DCE /* LoggedOutStoreCreationCoordinatorTests.swift */, ); path = Authentication; sourceTree = ""; @@ -10737,6 +10746,7 @@ CC53FB402759042600C4CA4F /* ProductSelectorViewModelTests.swift in Sources */, DE0A2EB1281BED38007A8015 /* ProductCategorySelectorViewModelTests.swift in Sources */, 03AA16602719B83D005CCB7B /* ReceiptActionCoordinatorTests.swift in Sources */, + 020D0BFD2914E92800BB3DCE /* StorePickerCoordinatorTests.swift in Sources */, CEEC9B6621E7C5200055EEF0 /* AppRatingManagerTests.swift in Sources */, 263EB409242C58EA00F3A15F /* ProductFormActionsFactoryTests.swift in Sources */, 02BA23C022EE9DAF009539E7 /* AsyncDictionaryTests.swift in Sources */, @@ -10760,6 +10770,7 @@ DEC51AA0274F9922009F3DF4 /* JetpackInstallStepsViewModelTests.swift in Sources */, E1068058285C787100668B46 /* BetaFeaturesTests.swift in Sources */, 26C6E8E426E2D87C00C7BB0F /* CountrySelectorViewModelTests.swift in Sources */, + 0269A5E72913FD22003B20EB /* StoreCreationCoordinatorTests.swift in Sources */, 02CE4307276994920006EAEF /* ProductSKUBarcodeScannerCoordinatorTests.swift in Sources */, 0999877427D2819F00F82C65 /* BulkUpdateViewControllerTests.swift in Sources */, 3190D61D26D6E97B00EF364D /* CardPresentModalRetryableErrorTests.swift in Sources */, @@ -11077,6 +11088,7 @@ 579CDF01274D811D00E8903D /* StoreStatsUsageTracksEventEmitterTests.swift in Sources */, 262A2C2B2537A3330086C1BE /* MockRefunds.swift in Sources */, 027F240C258371150021DB06 /* RefundShippingLabelViewModelTests.swift in Sources */, + 020D0BFF2914F6BA00BB3DCE /* LoggedOutStoreCreationCoordinatorTests.swift in Sources */, D85136DD231E613900DD0539 /* ReviewsViewModelTests.swift in Sources */, DEFD6E61264990FB00E51E0D /* SitePluginListViewModelTests.swift in Sources */, 02B2C831249C4C8D0040C83C /* TextFieldTextAlignmentTests.swift in Sources */, diff --git a/WooCommerce/WooCommerceTests/Authentication/LoggedOutStoreCreationCoordinatorTests.swift b/WooCommerce/WooCommerceTests/Authentication/LoggedOutStoreCreationCoordinatorTests.swift new file mode 100644 index 00000000000..8962ec00b2f --- /dev/null +++ b/WooCommerce/WooCommerceTests/Authentication/LoggedOutStoreCreationCoordinatorTests.swift @@ -0,0 +1,36 @@ +import TestKit +import XCTest +@testable import WooCommerce + +final class LoggedOutStoreCreationCoordinatorTests: XCTestCase { + private var navigationController: UINavigationController! + private let window = UIWindow(frame: UIScreen.main.bounds) + + override func setUp() { + super.setUp() + + window.makeKeyAndVisible() + navigationController = .init() + window.rootViewController = navigationController + } + + override func tearDown() { + navigationController = nil + window.resignKey() + window.rootViewController = nil + + super.tearDown() + } + + func test_start_shows_AccountCreationFormHostingController() throws { + // Given + let coordinator = LoggedOutStoreCreationCoordinator(source: .prologue, navigationController: navigationController) + XCTAssertNil(navigationController.topViewController) + + // When + coordinator.start() + + // Then + assertThat(navigationController.topViewController, isAnInstanceOf: AccountCreationFormHostingController.self) + } +} diff --git a/WooCommerce/WooCommerceTests/Authentication/StoreCreationCoordinatorTests.swift b/WooCommerce/WooCommerceTests/Authentication/StoreCreationCoordinatorTests.swift new file mode 100644 index 00000000000..3f5aef1c6ef --- /dev/null +++ b/WooCommerce/WooCommerceTests/Authentication/StoreCreationCoordinatorTests.swift @@ -0,0 +1,65 @@ +import TestKit +import XCTest +@testable import WooCommerce + +final class StoreCreationCoordinatorTests: XCTestCase { + private var navigationController: UINavigationController! + private let window = UIWindow(frame: UIScreen.main.bounds) + + override func setUp() { + super.setUp() + + window.makeKeyAndVisible() + navigationController = .init() + window.rootViewController = navigationController + } + + override func tearDown() { + navigationController = nil + window.resignKey() + window.rootViewController = nil + + super.tearDown() + } + + // MARK: - Presentation in different states + + func test_AuthenticatedWebViewController_is_presented_when_navigationController_is_presenting_another_view() throws { + // Given + let coordinator = StoreCreationCoordinator(source: .storePicker, navigationController: navigationController) + waitFor { promise in + self.navigationController.present(.init(), animated: false) { + promise(()) + } + } + XCTAssertNotNil(navigationController.presentedViewController) + + // When + coordinator.start() + + // Then + waitUntil { + self.navigationController.presentedViewController is WooNavigationController + } + let storeCreationNavigationController = try XCTUnwrap(navigationController.presentedViewController as? UINavigationController) + assertThat(storeCreationNavigationController.topViewController, isAnInstanceOf: AuthenticatedWebViewController.self) + } + + func test_AuthenticatedWebViewController_is_presented_when_navigationController_is_showing_another_view() throws { + // Given + navigationController.show(.init(), sender: nil) + let coordinator = StoreCreationCoordinator(source: .loggedOut(source: .loginEmailError), navigationController: navigationController) + XCTAssertNotNil(navigationController.topViewController) + XCTAssertNil(navigationController.presentedViewController) + + // When + coordinator.start() + + // Then + waitUntil { + self.navigationController.presentedViewController is WooNavigationController + } + let storeCreationNavigationController = try XCTUnwrap(navigationController.presentedViewController as? UINavigationController) + assertThat(storeCreationNavigationController.topViewController, isAnInstanceOf: AuthenticatedWebViewController.self) + } +} diff --git a/WooCommerce/WooCommerceTests/Authentication/StorePickerCoordinatorTests.swift b/WooCommerce/WooCommerceTests/Authentication/StorePickerCoordinatorTests.swift new file mode 100644 index 00000000000..605e6e32395 --- /dev/null +++ b/WooCommerce/WooCommerceTests/Authentication/StorePickerCoordinatorTests.swift @@ -0,0 +1,105 @@ +import TestKit +import WordPressAuthenticator +import XCTest +@testable import WooCommerce + +final class StorePickerCoordinatorTests: XCTestCase { + private var navigationController: UINavigationController! + private let window = UIWindow(frame: UIScreen.main.bounds) + + override func setUp() { + super.setUp() + + window.makeKeyAndVisible() + navigationController = .init() + window.rootViewController = navigationController + + WordPressAuthenticator.initializeAuthenticator() + } + + override func tearDown() { + navigationController = nil + window.resignKey() + window.rootViewController = nil + + super.tearDown() + } + + func test_storeCreationFromLogin_configuration_shows_storePicker_then_presents_storeCreation() throws { + // Given + let coordinator = StorePickerCoordinator(navigationController, config: .storeCreationFromLogin(source: .prologue)) + + // When + coordinator.start() + + // Then + waitUntil { + self.navigationController.presentedViewController is WooNavigationController + } + // Store picker should be pushed to the navigation stack. + assertThat(navigationController.topViewController, isAnInstanceOf: StorePickerViewController.self) + + let storeCreationNavigationController = try XCTUnwrap(navigationController.presentedViewController as? UINavigationController) + assertThat(storeCreationNavigationController.topViewController, isAnInstanceOf: AuthenticatedWebViewController.self) + } + + func test_standard_configuration_presents_storePicker() throws { + // Given + let coordinator = StorePickerCoordinator(navigationController, config: .standard) + + // When + coordinator.start() + + // Then + waitUntil { + self.navigationController.presentedViewController is WooNavigationController + } + XCTAssertNil(navigationController.topViewController) + + let storePickerNavigationController = try XCTUnwrap(navigationController.presentedViewController as? UINavigationController) + assertThat(storePickerNavigationController.topViewController, isAnInstanceOf: StorePickerViewController.self) + } + + func test_switchingStores_configuration_presents_storePicker() throws { + // Given + let coordinator = StorePickerCoordinator(navigationController, config: .switchingStores) + + // When + coordinator.start() + + // Then + waitUntil { + self.navigationController.presentedViewController is WooNavigationController + } + XCTAssertNil(navigationController.topViewController) + + let storePickerNavigationController = try XCTUnwrap(navigationController.presentedViewController as? UINavigationController) + assertThat(storePickerNavigationController.topViewController, isAnInstanceOf: StorePickerViewController.self) + } + + func test_login_configuration_shows_storePicker() throws { + // Given + let coordinator = StorePickerCoordinator(navigationController, config: .login) + + // When + coordinator.start() + + // Then + waitUntil { + self.navigationController.topViewController is StorePickerViewController + } + } + + func test_listStores_configuration_shows_storePicker() throws { + // Given + let coordinator = StorePickerCoordinator(navigationController, config: .listStores) + + // When + coordinator.start() + + // Then + waitUntil { + self.navigationController.topViewController is StorePickerViewController + } + } +}