Skip to content

Commit 6f6c479

Browse files
committed
Add unit tests
1 parent 9e81f47 commit 6f6c479

File tree

4 files changed

+281
-7
lines changed

4 files changed

+281
-7
lines changed

WooCommerce/Classes/Bookings/BookingsTabEligibilityChecker.swift

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
// periphery:ignore:all
21
import Foundation
32
import Yosemite
43
import Experiments

WooCommerce/Classes/ViewRelated/MainTabBarController.swift

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,7 @@ final class MainTabBarController: UITabBarController {
143143
private let analytics: Analytics
144144
private let posEligibilityCheckerFactory: ((_ site: Site) -> POSEntryPointEligibilityCheckerProtocol)
145145
private let posEligibilityService: POSEligibilityServiceProtocol
146+
private let bookingsEligibilityCheckerFactory: ((_ site: Site) -> BookingsTabEligibilityCheckerProtocol)
146147

147148
private var productImageUploadErrorsSubscription: AnyCancellable?
148149

@@ -166,7 +167,8 @@ final class MainTabBarController: UITabBarController {
166167
analytics: Analytics = ServiceLocator.analytics,
167168
stores: StoresManager = ServiceLocator.stores,
168169
posEligibilityCheckerFactory: ((Site) -> POSEntryPointEligibilityCheckerProtocol)? = nil,
169-
posEligibilityService: POSEligibilityServiceProtocol = POSEligibilityService()) {
170+
posEligibilityService: POSEligibilityServiceProtocol = POSEligibilityService(),
171+
bookingsEligibilityCheckerFactory: ((Site) -> BookingsTabEligibilityCheckerProtocol)? = nil) {
170172
self.featureFlagService = featureFlagService
171173
self.noticePresenter = noticePresenter
172174
self.productImageUploader = productImageUploader
@@ -180,6 +182,9 @@ final class MainTabBarController: UITabBarController {
180182
}
181183
}
182184
self.posEligibilityService = posEligibilityService
185+
self.bookingsEligibilityCheckerFactory = bookingsEligibilityCheckerFactory ?? { site in
186+
BookingsTabEligibilityChecker(site: site)
187+
}
183188
super.init(coder: coder)
184189
}
185190

@@ -198,6 +203,9 @@ final class MainTabBarController: UITabBarController {
198203
}
199204
}
200205
self.posEligibilityService = POSEligibilityService()
206+
self.bookingsEligibilityCheckerFactory = { site in
207+
BookingsTabEligibilityChecker(site: site)
208+
}
201209
super.init(coder: coder)
202210
}
203211

@@ -790,7 +798,7 @@ private extension MainTabBarController {
790798
}
791799

792800
// Configures Booking tab.
793-
let bookingsEligibilityChecker = BookingsTabEligibilityChecker(site: site)
801+
let bookingsEligibilityChecker = bookingsEligibilityCheckerFactory(site)
794802
self.bookingsEligibilityChecker = bookingsEligibilityChecker
795803
let bookingsViewController = createBookingsViewController(siteID: site.siteID)
796804
bookingsContainerController.wrappedController = bookingsViewController

WooCommerce/WooCommerceTests/ViewRelated/MainTabBarController+TabsTests.swift

Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,154 @@ final class MainTabBarController_TabsTests: XCTestCase {
158158
XCTAssertEqual(tabBarController.tabRootViewControllers.count, 4)
159159
}
160160

161+
func test_tab_view_controllers_include_bookings_tab_when_bookings_tab_is_visible() throws {
162+
// Given
163+
let mockBookingsEligibilityChecker = MockBookingsEligibilityChecker()
164+
mockBookingsEligibilityChecker.visibility = true
165+
166+
let storesManager = MockStoresManager(sessionManager: .makeForTesting())
167+
168+
guard let tabBarController = UIStoryboard(name: "Main", bundle: nil).instantiateInitialViewController(creator: { coder in
169+
return MainTabBarController(coder: coder,
170+
featureFlagService: MockFeatureFlagService(),
171+
stores: storesManager,
172+
bookingsEligibilityCheckerFactory: { _ in mockBookingsEligibilityChecker })
173+
}) else {
174+
return
175+
}
176+
177+
// Trigger `viewDidLoad`
178+
XCTAssertNotNil(tabBarController.view)
179+
180+
// When
181+
let siteID: Int64 = 314
182+
storesManager.updateDefaultStore(storeID: siteID)
183+
storesManager.updateDefaultStore(.fake().copy(siteID: siteID))
184+
185+
// Then
186+
waitUntil {
187+
tabBarController.tabRootViewControllers.count == 5
188+
}
189+
assertThat(tabBarController.tabRootViewController(
190+
tab: .myStore,
191+
isPOSTabVisible: false,
192+
isBookingsTabVisible: true
193+
), isAnInstanceOf: DashboardViewHostingController.self)
194+
assertThat(tabBarController.tabRootViewController(
195+
tab: .orders,
196+
isPOSTabVisible: false,
197+
isBookingsTabVisible: true
198+
), isAnInstanceOf: OrdersSplitViewWrapperController.self)
199+
assertThat(tabBarController.tabRootViewController(
200+
tab: .products,
201+
isPOSTabVisible: false,
202+
isBookingsTabVisible: true
203+
), isAnInstanceOf: ProductsViewController.self)
204+
assertThat(tabBarController.tabRootViewController(
205+
tab: .bookings,
206+
isPOSTabVisible: false,
207+
isBookingsTabVisible: true
208+
), isAnInstanceOf: BookingsTabViewHostingController.self)
209+
210+
let hubMenuNavigationController = try XCTUnwrap(tabBarController.tabRootViewController(
211+
tab: .hubMenu,
212+
isPOSTabVisible: false,
213+
isBookingsTabVisible: true
214+
) as? UINavigationController)
215+
assertThat(hubMenuNavigationController.topViewController,
216+
isAnInstanceOf: HubMenuViewController.self)
217+
}
218+
219+
func test_tab_view_controllers_exclude_bookings_tab_when_bookings_tab_is_not_visible() throws {
220+
// Given
221+
let mockBookingsEligibilityChecker = MockBookingsEligibilityChecker()
222+
mockBookingsEligibilityChecker.visibility = false
223+
224+
let storesManager = MockStoresManager(sessionManager: .makeForTesting())
225+
226+
guard let tabBarController = UIStoryboard(name: "Main", bundle: nil).instantiateInitialViewController(creator: { coder in
227+
return MainTabBarController(coder: coder,
228+
featureFlagService: MockFeatureFlagService(),
229+
stores: storesManager,
230+
bookingsEligibilityCheckerFactory: { _ in mockBookingsEligibilityChecker })
231+
}) else {
232+
return
233+
}
234+
235+
// Trigger `viewDidLoad`
236+
XCTAssertNotNil(tabBarController.view)
237+
238+
// When
239+
let siteID: Int64 = 707
240+
storesManager.updateDefaultStore(storeID: siteID)
241+
storesManager.updateDefaultStore(.fake().copy(siteID: siteID))
242+
243+
// Then
244+
waitUntil {
245+
tabBarController.tabRootViewControllers.count == 4
246+
}
247+
assertThat(tabBarController.tabRootViewController(
248+
tab: .myStore,
249+
isPOSTabVisible: false,
250+
isBookingsTabVisible: false
251+
), isAnInstanceOf: DashboardViewHostingController.self)
252+
assertThat(tabBarController.tabRootViewController(
253+
tab: .orders,
254+
isPOSTabVisible: false,
255+
isBookingsTabVisible: false
256+
), isAnInstanceOf: OrdersSplitViewWrapperController.self)
257+
assertThat(tabBarController.tabRootViewController(
258+
tab: .products,
259+
isPOSTabVisible: false,
260+
isBookingsTabVisible: false
261+
), isAnInstanceOf: ProductsViewController.self)
262+
263+
let hubMenuNavigationController = try XCTUnwrap(tabBarController.tabRootViewController(
264+
tab: .hubMenu,
265+
isPOSTabVisible: false,
266+
isBookingsTabVisible: false
267+
) as? UINavigationController)
268+
assertThat(hubMenuNavigationController.topViewController,
269+
isAnInstanceOf: HubMenuViewController.self)
270+
}
271+
272+
func test_tab_view_controllers_do_not_change_when_bookings_visibility_changes() throws {
273+
// Given
274+
let mockBookingsEligibilityChecker = MockBookingsEligibilityChecker()
275+
mockBookingsEligibilityChecker.visibility = false
276+
277+
let storesManager = MockStoresManager(sessionManager: .makeForTesting())
278+
279+
guard let tabBarController = UIStoryboard(name: "Main", bundle: nil).instantiateInitialViewController(creator: { coder in
280+
return MainTabBarController(coder: coder,
281+
featureFlagService: MockFeatureFlagService(),
282+
stores: storesManager,
283+
bookingsEligibilityCheckerFactory: { _ in mockBookingsEligibilityChecker })
284+
}) else {
285+
return
286+
}
287+
288+
// Trigger `viewDidLoad`
289+
XCTAssertNotNil(tabBarController.view)
290+
291+
// When
292+
let siteID: Int64 = 303
293+
storesManager.updateDefaultStore(storeID: siteID)
294+
storesManager.updateDefaultStore(.fake().copy(siteID: siteID))
295+
296+
// Then initial state
297+
waitUntil {
298+
tabBarController.tabRootViewControllers.count == 4
299+
}
300+
301+
// When - change bookings eligibility
302+
mockBookingsEligibilityChecker.visibility = true
303+
304+
// Then tabs remain the same
305+
XCTAssertEqual(tabBarController.tabRootViewControllers.count, 4)
306+
}
307+
308+
161309
func test_tab_root_viewControllers_are_replaced_after_updating_to_a_different_site() throws {
162310
// Arrange
163311
let stores = MockStoresManager(sessionManager: .makeForTesting())
@@ -219,3 +367,17 @@ final class MainTabBarController_TabsTests: XCTestCase {
219367
XCTAssertEqual(viewControllersBeforeSiteChange, viewControllersAfterSiteChange)
220368
}
221369
}
370+
371+
private final class MockBookingsEligibilityChecker {
372+
var visibility: Bool = false
373+
}
374+
375+
extension MockBookingsEligibilityChecker: BookingsTabEligibilityCheckerProtocol {
376+
func checkInitialVisibility() -> Bool {
377+
visibility
378+
}
379+
380+
func checkVisibility() async -> Bool {
381+
visibility
382+
}
383+
}

WooCommerce/WooCommerceTests/ViewRelated/MainTabBarControllerTests.swift

Lines changed: 109 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -594,6 +594,77 @@ final class MainTabBarControllerTests: XCTestCase {
594594
let indexOfEvent = try XCTUnwrap(analyticsProvider.receivedEvents.firstIndex(of: WooAnalyticsStat.pointOfSaleTabVisibilityChecked.rawValue))
595595
assertEqual(true, analyticsProvider.receivedProperties[safe: indexOfEvent]?["is_visible"] as? Bool)
596596
}
597+
598+
func test_bookings_tab_becomes_invisible_after_being_selected_when_initially_visible_then_eligibility_changes() throws {
599+
// Given
600+
let mockBookingsEligibilityChecker = MockAsyncBookingsEligibilityChecker()
601+
mockBookingsEligibilityChecker.initialVisibility = true
602+
603+
let mockFeatureFlagService = MockFeatureFlagService()
604+
ServiceLocator.setFeatureFlagService(mockFeatureFlagService)
605+
606+
let stores = MockStoresManager(sessionManager: .makeForTesting(authenticated: true))
607+
608+
guard let tabBarController = UIStoryboard(name: "Main", bundle: nil).instantiateInitialViewController(creator: { coder in
609+
return MainTabBarController(coder: coder,
610+
stores: stores,
611+
bookingsEligibilityCheckerFactory: { _ in mockBookingsEligibilityChecker })
612+
}) else {
613+
return
614+
}
615+
616+
window.rootViewController = tabBarController
617+
618+
// Trigger `viewDidLoad`
619+
XCTAssertNotNil(tabBarController.view)
620+
621+
// When bookings tab initial visibility is set to true
622+
let siteID: Int64 = 1126
623+
stores.updateDefaultStore(storeID: siteID)
624+
stores.updateDefaultStore(.fake().copy(siteID: siteID))
625+
626+
// Then bookings tab is visible before eligibility check is returned
627+
waitUntil {
628+
tabBarController.tabRootViewControllers.count == 5
629+
}
630+
assertThat(tabBarController.tabRootViewController(
631+
tab: .bookings,
632+
isPOSTabVisible: false,
633+
isBookingsTabVisible: true
634+
), isAnInstanceOf: BookingsTabViewHostingController.self)
635+
636+
// When bookings tab becomes invisible
637+
mockBookingsEligibilityChecker.setVisibilityResult(false)
638+
639+
// Then bookings tab is hidden
640+
waitUntil {
641+
tabBarController.tabRootViewControllers.count == 4
642+
}
643+
644+
assertThat(tabBarController.tabRootViewController(
645+
tab: .myStore,
646+
isPOSTabVisible: false,
647+
isBookingsTabVisible: false
648+
), isAnInstanceOf: DashboardViewHostingController.self)
649+
assertThat(tabBarController.tabRootViewController(
650+
tab: .orders,
651+
isPOSTabVisible: false,
652+
isBookingsTabVisible: false
653+
), isAnInstanceOf: OrdersSplitViewWrapperController.self)
654+
assertThat(tabBarController.tabRootViewController(
655+
tab: .products,
656+
isPOSTabVisible: false,
657+
isBookingsTabVisible: false
658+
), isAnInstanceOf: ProductsViewController.self)
659+
660+
let hubMenuNavigationController = try XCTUnwrap(tabBarController.tabRootViewController(
661+
tab: .hubMenu,
662+
isPOSTabVisible: false,
663+
isBookingsTabVisible: false
664+
) as? UINavigationController)
665+
assertThat(hubMenuNavigationController.topViewController,
666+
isAnInstanceOf: HubMenuViewController.self)
667+
}
597668
}
598669

599670
extension MainTabBarController {
@@ -621,17 +692,20 @@ extension MainTabBarController {
621692
return rootViewControllers
622693
}
623694

624-
func tabRootViewController(tab: WooTab, isPOSTabVisible: Bool) -> UIViewController? {
695+
func tabRootViewController(tab: WooTab, isPOSTabVisible: Bool, isBookingsTabVisible: Bool = false) -> UIViewController? {
625696
// swiftlint:disable:next empty_enum_arguments
626-
guard let viewController = tabRootViewControllers[safe: tab.visibleIndex(isPOSTabVisible: isPOSTabVisible)] else {
697+
guard let viewController = tabRootViewControllers[safe: tab.visibleIndex(
698+
isPOSTabVisible: isPOSTabVisible,
699+
isBookingsTabVisible: isBookingsTabVisible
700+
)] else {
627701
XCTFail("Unexpected access to root controller at tab: \(tab)")
628702
return nil
629703
}
630704
return viewController
631705
}
632706

633-
func tabContainerController(tab: WooTab, isPOSTabVisible: Bool) -> UIViewController? {
634-
guard let viewController = viewControllers?[tab.visibleIndex(isPOSTabVisible: isPOSTabVisible)] else {
707+
func tabContainerController(tab: WooTab, isPOSTabVisible: Bool, isBookingsTabVisible: Bool = false) -> UIViewController? {
708+
guard let viewController = viewControllers?[tab.visibleIndex(isPOSTabVisible: isPOSTabVisible, isBookingsTabVisible: isBookingsTabVisible)] else {
635709
XCTFail("Unexpected access to container controller at tab: \(tab)")
636710
return nil
637711
}
@@ -696,3 +770,34 @@ private final class MockAsyncPOSEligibilityChecker: POSEntryPointEligibilityChec
696770
.ineligible(reason: ineligibleReason)
697771
}
698772
}
773+
774+
private final class MockAsyncBookingsEligibilityChecker: BookingsTabEligibilityCheckerProtocol {
775+
var initialVisibility: Bool = false
776+
private var visibilityResult: Bool?
777+
private var visibilityContinuation: CheckedContinuation<Bool, Never>?
778+
779+
func setVisibilityResult(_ result: Bool) {
780+
visibilityResult = result
781+
if let continuation = visibilityContinuation {
782+
visibilityContinuation = nil
783+
continuation.resume(returning: result)
784+
}
785+
}
786+
787+
func checkInitialVisibility() -> Bool {
788+
initialVisibility
789+
}
790+
791+
func checkVisibility() async -> Bool {
792+
if let visibilityResult {
793+
return visibilityResult
794+
}
795+
return await withCheckedContinuation { continuation in
796+
visibilityContinuation = continuation
797+
// If we already have a result, return it immediately.
798+
if visibilityContinuation == nil {
799+
continuation.resume(returning: visibilityResult ?? true)
800+
}
801+
}
802+
}
803+
}

0 commit comments

Comments
 (0)