diff --git a/Modules/Sources/Yosemite/Base/StoresManager.swift b/Modules/Sources/Yosemite/Base/StoresManager.swift index c328b040bfc..2babb332e78 100644 --- a/Modules/Sources/Yosemite/Base/StoresManager.swift +++ b/Modules/Sources/Yosemite/Base/StoresManager.swift @@ -63,6 +63,7 @@ public protocol StoresManager { /// The currently logged in store/site ID. Nil when the app is logged out. /// + /// periphery: ignore - used in tests var siteID: AnyPublisher { get } /// Observable currently selected site. diff --git a/Modules/Sources/Yosemite/Model/Mocks/Graphs/ScreenshotsObjectGraph.swift b/Modules/Sources/Yosemite/Model/Mocks/Graphs/ScreenshotsObjectGraph.swift index 91066706e66..7c884229ef8 100644 --- a/Modules/Sources/Yosemite/Model/Mocks/Graphs/ScreenshotsObjectGraph.swift +++ b/Modules/Sources/Yosemite/Model/Mocks/Graphs/ScreenshotsObjectGraph.swift @@ -33,34 +33,7 @@ struct ScreenshotObjectGraph: MockObjectGraph { gravatarUrl: nil ) - let defaultSite = Site( - siteID: 1, - name: Defaults.Site.name, - description: "", - url: Defaults.Site.url, - adminURL: Defaults.Site.adminURL, - loginURL: Defaults.Site.loginURL, - isSiteOwner: false, - frameNonce: "", - plan: "", - isAIAssistantFeatureActive: false, - isJetpackThePluginInstalled: true, - isJetpackConnected: true, - isWooCommerceActive: true, - isWordPressComStore: false, - jetpackConnectionActivePlugins: [], - timezone: "UTC", - gmtOffset: 0, - visibility: .publicSite, - canBlaze: false, - isAdmin: false, - wasEcommerceTrial: false, - hasSSOEnabled: false, - applicationPasswordAvailable: false, - isGarden: false, - gardenName: nil, - gardenPartner: nil - ) + let defaultSite = Site.defaultMock() /// May not be needed anymore if we're not mocking the API let defaultSiteAPI = SiteAPI(siteID: 1, namespaces: [ diff --git a/Modules/Sources/Yosemite/Model/Mocks/Site+Mocks.swift b/Modules/Sources/Yosemite/Model/Mocks/Site+Mocks.swift new file mode 100644 index 00000000000..2ded0bbe193 --- /dev/null +++ b/Modules/Sources/Yosemite/Model/Mocks/Site+Mocks.swift @@ -0,0 +1,35 @@ +import Foundation +import struct Networking.Site + +public extension Site { + static func defaultMock() -> Self { + return Site( + siteID: 1, + name: Defaults.Site.name, + description: "", + url: Defaults.Site.url, + adminURL: Defaults.Site.adminURL, + loginURL: Defaults.Site.loginURL, + isSiteOwner: false, + frameNonce: "", + plan: "", + isAIAssistantFeatureActive: false, + isJetpackThePluginInstalled: true, + isJetpackConnected: true, + isWooCommerceActive: true, + isWordPressComStore: false, + jetpackConnectionActivePlugins: [], + timezone: "UTC", + gmtOffset: 0, + visibility: .publicSite, + canBlaze: false, + isAdmin: false, + wasEcommerceTrial: false, + hasSSOEnabled: false, + applicationPasswordAvailable: false, + isGarden: false, + gardenName: nil, + gardenPartner: nil + ) + } +} diff --git a/WooCommerce/Classes/CIAB/CIABAffectedFeature.swift b/WooCommerce/Classes/CIAB/CIABAffectedFeature.swift index 1bd6fe1195c..0d3a55059df 100644 --- a/WooCommerce/Classes/CIAB/CIABAffectedFeature.swift +++ b/WooCommerce/Classes/CIAB/CIABAffectedFeature.swift @@ -10,6 +10,7 @@ enum CIABAffectedFeature: CaseIterable { case variableProducts case giftCardEditing case productsStockDashboardCard + case pointOfSale } extension CIABAffectedFeature { diff --git a/WooCommerce/Classes/POS/Analytics/WooAnalyticsEvent+PointOfSaleIneligibleUI.swift b/WooCommerce/Classes/POS/Analytics/WooAnalyticsEvent+PointOfSaleIneligibleUI.swift index 94eb13a2200..48cd7ee928b 100644 --- a/WooCommerce/Classes/POS/Analytics/WooAnalyticsEvent+PointOfSaleIneligibleUI.swift +++ b/WooCommerce/Classes/POS/Analytics/WooAnalyticsEvent+PointOfSaleIneligibleUI.swift @@ -28,6 +28,8 @@ private extension POSIneligibleReason { return "unknown_wc_plugin" case .unsupportedIOSVersion: return "ios_version" + case .unsupportedInCIABSites: + return "feature_unsupported_in_ciab" case .siteSettingsNotAvailable, .selfDeallocated: return "other" diff --git a/WooCommerce/Classes/POS/Models/POSIneligibleReason.swift b/WooCommerce/Classes/POS/Models/POSIneligibleReason.swift index 5c93a077e43..e6ef5cc4b6a 100644 --- a/WooCommerce/Classes/POS/Models/POSIneligibleReason.swift +++ b/WooCommerce/Classes/POS/Models/POSIneligibleReason.swift @@ -11,6 +11,7 @@ enum POSIneligibleReason: Equatable { case featureSwitchDisabled case unsupportedCurrency(countryCode: CountryCode, supportedCurrencies: [CurrencyCode]) case selfDeallocated + case unsupportedInCIABSites } /// Represents the eligibility state for POS. diff --git a/WooCommerce/Classes/POS/Presentation/PointOfSaleEntryPointView.swift b/WooCommerce/Classes/POS/Presentation/PointOfSaleEntryPointView.swift index 9d418fc5392..7dfb6ce2ac7 100644 --- a/WooCommerce/Classes/POS/Presentation/PointOfSaleEntryPointView.swift +++ b/WooCommerce/Classes/POS/Presentation/PointOfSaleEntryPointView.swift @@ -129,7 +129,7 @@ struct PointOfSaleEntryPointView: View { searchHistoryService: PointOfSalePreviewHistoryService(), popularPurchasableItemsController: PointOfSalePreviewItemsController(), barcodeScanService: PointOfSalePreviewBarcodeScanService(), - posEligibilityChecker: POSTabEligibilityChecker(siteID: 0), + posEligibilityChecker: POSTabEligibilityChecker(site: .defaultMock()), services: POSPreviewServices()) } diff --git a/WooCommerce/Classes/POS/TabBar/POSIneligibleView.swift b/WooCommerce/Classes/POS/TabBar/POSIneligibleView.swift index 6d7e4190372..802e5f348b7 100644 --- a/WooCommerce/Classes/POS/TabBar/POSIneligibleView.swift +++ b/WooCommerce/Classes/POS/TabBar/POSIneligibleView.swift @@ -154,6 +154,12 @@ struct POSIneligibleView: View { return NSLocalizedString("pos.ineligible.suggestion.selfDeallocated", value: "Try relaunching the app to resolve this issue.", comment: "Suggestion for self deallocated: relaunch") + case .unsupportedInCIABSites: + return NSLocalizedString( + "pos.ineligible.suggestion.notSupportedForCIAB", + value: "The POS system is not supported for your store.", + comment: "Suggestion for CIAB sites: feature is not supported" + ) } } } @@ -177,7 +183,8 @@ private extension POSIneligibleView { private extension POSIneligibleReason { var shouldShowRetryButton: Bool { switch self { - case .unsupportedIOSVersion: + case .unsupportedIOSVersion, + .unsupportedInCIABSites: return false case .unsupportedWooCommerceVersion, .siteSettingsNotAvailable, @@ -208,6 +215,9 @@ private extension POSIneligibleReason { value: "Retry", comment: "Button title to refresh POS eligibility check" ) + case .unsupportedInCIABSites: + assertionFailure("Retry button should not be shown for `unsupportedInCIABSites`") + return String() } } } diff --git a/WooCommerce/Classes/POS/Utils/PreviewHelpers.swift b/WooCommerce/Classes/POS/Utils/PreviewHelpers.swift index 80077da1b5d..485dd987346 100644 --- a/WooCommerce/Classes/POS/Utils/PreviewHelpers.swift +++ b/WooCommerce/Classes/POS/Utils/PreviewHelpers.swift @@ -28,6 +28,7 @@ import struct Yosemite.POSOrderRefund import typealias Yosemite.OrderItemAttribute import class Yosemite.POSOrderListService import class Yosemite.POSOrderListFetchStrategyFactory +import struct Yosemite.Site // MARK: - PreviewProvider helpers // @@ -224,8 +225,9 @@ struct POSPreviewHelpers { featureFlags: POSFeatureFlagProviding = EmptyPOSFeatureFlags() ) -> PointOfSaleAggregateModel { return PointOfSaleAggregateModel( - entryPointController: POSEntryPointController(eligibilityChecker: LegacyPOSTabEligibilityChecker(siteID: 0), - featureFlagService: featureFlags), + entryPointController: POSEntryPointController( + eligibilityChecker: LegacyPOSTabEligibilityChecker(site: Site.defaultMock()), + featureFlagService: featureFlags), itemsController: itemsController, purchasableItemsSearchController: purchasableItemsSearchController, couponsController: couponsController, diff --git a/WooCommerce/Classes/ViewRelated/Dashboard/Settings/POS/LegacyPOSTabEligibilityChecker.swift b/WooCommerce/Classes/ViewRelated/Dashboard/Settings/POS/LegacyPOSTabEligibilityChecker.swift index dcb98a8ed94..f9d70e180c6 100644 --- a/WooCommerce/Classes/ViewRelated/Dashboard/Settings/POS/LegacyPOSTabEligibilityChecker.swift +++ b/WooCommerce/Classes/ViewRelated/Dashboard/Settings/POS/LegacyPOSTabEligibilityChecker.swift @@ -5,6 +5,7 @@ import enum WooFoundation.CountryCode import enum WooFoundation.CurrencyCode import protocol Experiments.FeatureFlagService import struct Yosemite.SiteSetting +import struct Yosemite.Site import protocol Yosemite.POSEligibilityServiceProtocol import protocol Yosemite.StoresManager import class Yosemite.POSEligibilityService @@ -28,6 +29,7 @@ private enum LegacyPOSIneligibleReason: Equatable { case unsupportedCountry(supportedCountries: [CountryCode]) case unsupportedCurrency(supportedCurrencies: [CurrencyCode]) case selfDeallocated + case unsupportedInCIABSites } /// Legacy POS eligibility state for i1. @@ -38,33 +40,36 @@ private enum LegacyPOSEligibilityState: Equatable { /// POS tab eligibility checker for i1. Will be replaced by `POSTabEligibilityCheckerI2` when removing `pointOfSaleAsATabi2` feature flag. final class LegacyPOSTabEligibilityChecker: POSEntryPointEligibilityCheckerProtocol { - private let siteID: Int64 + private let site: Site private let userInterfaceIdiom: UIUserInterfaceIdiom private let siteSettings: SelectedSiteSettingsProtocol private let pluginsService: PluginsServiceProtocol private let eligibilityService: POSEligibilityServiceProtocol private let stores: StoresManager private let featureFlagService: FeatureFlagService + private let siteCIABEligibilityChecker: CIABEligibilityCheckerProtocol - init(siteID: Int64, + init(site: Site, userInterfaceIdiom: UIUserInterfaceIdiom = UIDevice.current.userInterfaceIdiom, siteSettings: SelectedSiteSettingsProtocol = ServiceLocator.selectedSiteSettings, pluginsService: PluginsServiceProtocol = PluginsService(storageManager: ServiceLocator.storageManager), eligibilityService: POSEligibilityServiceProtocol = POSEligibilityService(), stores: StoresManager = ServiceLocator.stores, - featureFlagService: FeatureFlagService = ServiceLocator.featureFlagService) { - self.siteID = siteID + featureFlagService: FeatureFlagService = ServiceLocator.featureFlagService, + siteCIABEligibilityChecker: CIABEligibilityCheckerProtocol = CIABEligibilityChecker()) { + self.site = site self.userInterfaceIdiom = userInterfaceIdiom self.siteSettings = siteSettings self.pluginsService = pluginsService self.eligibilityService = eligibilityService self.stores = stores self.featureFlagService = featureFlagService + self.siteCIABEligibilityChecker = siteCIABEligibilityChecker } /// Checks the initial visibility of the POS tab without dependance on network requests. func checkInitialVisibility() -> Bool { - eligibilityService.loadCachedPOSTabVisibility(siteID: siteID) ?? false + eligibilityService.loadCachedPOSTabVisibility(siteID: site.siteID) ?? false } /// Determines whether the POS entry point can be shown based on the selected store and feature gates. @@ -73,6 +78,10 @@ final class LegacyPOSTabEligibilityChecker: POSEntryPointEligibilityCheckerProto } private func checkI1Eligibility() async -> LegacyPOSEligibilityState { + guard siteCIABEligibilityChecker.isFeatureSupported(.pointOfSale, for: site) else { + return .ineligible(reason: .unsupportedInCIABSites) + } + switch checkDeviceEligibility() { case .eligible: break @@ -139,7 +148,7 @@ private extension LegacyPOSTabEligibilityChecker { private extension LegacyPOSTabEligibilityChecker { func checkPluginEligibility() async -> LegacyPOSEligibilityState { - let wcPlugin = await fetchWooCommercePlugin(siteID: siteID) + let wcPlugin = await fetchWooCommercePlugin(siteID: site.siteID) guard VersionHelpers.isVersionSupported(version: wcPlugin.version, minimumRequired: Constants.wcPluginMinimumVersion) else { @@ -155,7 +164,7 @@ private extension LegacyPOSTabEligibilityChecker { } // For versions that support the feature switch, checks if the feature switch is enabled. - return await checkFeatureSwitchEnabled(siteID: siteID) + return await checkFeatureSwitchEnabled(siteID: site.siteID) } @MainActor @@ -201,7 +210,7 @@ private extension LegacyPOSTabEligibilityChecker { func waitForSiteSettingsRefresh() async -> [SiteSetting] { for await siteSettings in siteSettings.settingsStream.values { - guard siteSettings.siteID == siteID, siteSettings.settings.isNotEmpty, siteSettings.source != .initialLoad else { + guard siteSettings.siteID == site.siteID, siteSettings.settings.isNotEmpty, siteSettings.source != .initialLoad else { continue } return siteSettings.settings diff --git a/WooCommerce/Classes/ViewRelated/Dashboard/Settings/POS/POSTabEligibilityChecker.swift b/WooCommerce/Classes/ViewRelated/Dashboard/Settings/POS/POSTabEligibilityChecker.swift index 5784d6e50fd..8e8264252c7 100644 --- a/WooCommerce/Classes/ViewRelated/Dashboard/Settings/POS/POSTabEligibilityChecker.swift +++ b/WooCommerce/Classes/ViewRelated/Dashboard/Settings/POS/POSTabEligibilityChecker.swift @@ -5,6 +5,7 @@ import enum WooFoundation.CountryCode import enum WooFoundation.CurrencyCode import protocol Experiments.FeatureFlagService import struct Yosemite.SiteSetting +import struct Yosemite.Site import protocol Yosemite.POSEligibilityServiceProtocol import protocol Yosemite.StoresManager import class Yosemite.POSEligibilityService @@ -30,7 +31,7 @@ protocol POSEntryPointEligibilityCheckerProtocol { } final class POSTabEligibilityChecker: POSEntryPointEligibilityCheckerProtocol { - private let siteID: Int64 + private let site: Site private let userInterfaceIdiom: UIUserInterfaceIdiom private let siteSettings: SelectedSiteSettingsProtocol private let eligibilityService: POSEligibilityServiceProtocol @@ -38,17 +39,19 @@ final class POSTabEligibilityChecker: POSEntryPointEligibilityCheckerProtocol { private let featureFlagService: FeatureFlagService private let systemStatusService: POSSystemStatusServiceProtocol private let siteSettingService: POSSiteSettingServiceProtocol + private let siteCIABEligibilityChecker: CIABEligibilityCheckerProtocol private let appPasswordSupportState: ApplicationPasswordsExperimentState - init(siteID: Int64, + init(site: Site, userInterfaceIdiom: UIUserInterfaceIdiom = UIDevice.current.userInterfaceIdiom, siteSettings: SelectedSiteSettingsProtocol = ServiceLocator.selectedSiteSettings, eligibilityService: POSEligibilityServiceProtocol = POSEligibilityService(), stores: StoresManager = ServiceLocator.stores, featureFlagService: FeatureFlagService = ServiceLocator.featureFlagService, systemStatusService: POSSystemStatusServiceProtocol? = nil, - siteSettingService: POSSiteSettingServiceProtocol? = nil) { - self.siteID = siteID + siteSettingService: POSSiteSettingServiceProtocol? = nil, + siteCIABEligibilityChecker: CIABEligibilityCheckerProtocol = CIABEligibilityChecker()) { + self.site = site self.userInterfaceIdiom = userInterfaceIdiom self.siteSettings = siteSettings self.eligibilityService = eligibilityService @@ -58,27 +61,32 @@ final class POSTabEligibilityChecker: POSEntryPointEligibilityCheckerProtocol { let credentials = stores.sessionManager.defaultCredentials let selectedSite = stores.sessionManager.defaultSitePublisher.map { $0?.toJetpackSite() }.eraseToAnyPublisher() - let appPasswordSupportState = appPasswordSupportState.$isAvailableAndEnabled.eraseToAnyPublisher() + let appPasswordSupport = appPasswordSupportState.$isAvailableAndEnabled.eraseToAnyPublisher() self.systemStatusService = systemStatusService ?? POSSystemStatusService( credentials: credentials, selectedSite: selectedSite, - appPasswordSupportState: appPasswordSupportState, + appPasswordSupportState: appPasswordSupport, storageManager: ServiceLocator.storageManager ) self.siteSettingService = siteSettingService ?? POSSiteSettingService( credentials: credentials, selectedSite: selectedSite, - appPasswordSupportState: appPasswordSupportState + appPasswordSupportState: appPasswordSupport ) + self.siteCIABEligibilityChecker = siteCIABEligibilityChecker } /// Checks the initial visibility of the POS tab without dependance on network requests. func checkInitialVisibility() -> Bool { - eligibilityService.loadCachedPOSTabVisibility(siteID: siteID) ?? false + eligibilityService.loadCachedPOSTabVisibility(siteID: site.siteID) ?? false } /// Determines whether the POS entry point can be shown based on the selected store and feature gates. func checkEligibility() async -> POSEligibilityState { + guard siteCIABEligibilityChecker.isFeatureSupported(.pointOfSale, for: site) else { + return .ineligible(reason: .unsupportedInCIABSites) + } + guard #available(iOS 17.0, *) else { return .ineligible(reason: .unsupportedIOSVersion) } @@ -103,6 +111,10 @@ final class POSTabEligibilityChecker: POSEntryPointEligibilityCheckerProtocol { /// Checks the final visibility of the POS tab. func checkVisibility() async -> Bool { + guard siteCIABEligibilityChecker.isFeatureSupported(.pointOfSale, for: site) else { + return false + } + guard userInterfaceIdiom == .pad else { return false } @@ -137,10 +149,12 @@ final class POSTabEligibilityChecker: POSEntryPointEligibilityCheckerProtocol { case .unsupportedWooCommerceVersion, .wooCommercePluginNotFound: return await checkEligibility() case .featureSwitchDisabled: - _ = try await siteSettingService.setFeature(siteID: siteID, feature: .pointOfSale, enabled: true) + _ = try await siteSettingService.setFeature(siteID: site.siteID, feature: .pointOfSale, enabled: true) return await checkEligibility() case .selfDeallocated: return await checkEligibility() + case .unsupportedInCIABSites: + return await checkEligibility() } } } @@ -154,7 +168,7 @@ private extension POSTabEligibilityChecker { /// - Returns: The eligibility state for POS based on the WooCommerce plugin and POS feature switch. func checkPluginEligibility() async -> POSEligibilityState { do { - let info = try await systemStatusService.loadWooCommercePluginAndPOSFeatureSwitch(siteID: siteID) + let info = try await systemStatusService.loadWooCommercePluginAndPOSFeatureSwitch(siteID: site.siteID) let wcPluginEligibility = checkWooCommercePluginEligibility(wcPlugin: info.wcPlugin) switch wcPluginEligibility { case .eligible: @@ -247,7 +261,7 @@ private extension POSTabEligibilityChecker { func waitForSiteSettingsRefresh() async -> [SiteSetting] { for await siteSettings in siteSettings.settingsStream.values { - guard siteSettings.siteID == siteID, siteSettings.settings.isNotEmpty, siteSettings.source != .initialLoad else { + guard siteSettings.siteID == site.siteID, siteSettings.settings.isNotEmpty, siteSettings.source != .initialLoad else { continue } return siteSettings.settings @@ -279,7 +293,7 @@ private extension POSTabEligibilityChecker { guard let self else { return continuation.resume(throwing: POSTabEligibilityCheckerError.selfDeallocated) } - stores.dispatch(SettingAction.synchronizeGeneralSiteSettings(siteID: siteID) { [weak self] error in + stores.dispatch(SettingAction.synchronizeGeneralSiteSettings(siteID: site.siteID) { [weak self] error in guard let self else { return continuation.resume(throwing: POSTabEligibilityCheckerError.selfDeallocated) } diff --git a/WooCommerce/Classes/ViewRelated/MainTabBarController.swift b/WooCommerce/Classes/ViewRelated/MainTabBarController.swift index 577f17ee367..a8276b1d346 100644 --- a/WooCommerce/Classes/ViewRelated/MainTabBarController.swift +++ b/WooCommerce/Classes/ViewRelated/MainTabBarController.swift @@ -118,12 +118,13 @@ final class MainTabBarController: UITabBarController { private var hubMenuTabCoordinator: HubMenuCoordinator? private var cancellableSiteID: AnyCancellable? + private var cancellableSite: AnyCancellable? private let featureFlagService: FeatureFlagService private let noticePresenter: NoticePresenter private let productImageUploader: ProductImageUploaderProtocol private let stores: StoresManager private let analytics: Analytics - private let posEligibilityCheckerFactory: ((_ siteID: Int64) -> POSEntryPointEligibilityCheckerProtocol) + private let posEligibilityCheckerFactory: ((_ site: Site) -> POSEntryPointEligibilityCheckerProtocol) private let posEligibilityService: POSEligibilityServiceProtocol private var productImageUploadErrorsSubscription: AnyCancellable? @@ -136,24 +137,25 @@ final class MainTabBarController: UITabBarController { private lazy var isProductsSplitViewFeatureFlagOn = featureFlagService.isFeatureFlagEnabled(.splitViewInProductsTab) + /// periphery: ignore - used in tests init?(coder: NSCoder, featureFlagService: FeatureFlagService = ServiceLocator.featureFlagService, noticePresenter: NoticePresenter = ServiceLocator.noticePresenter, productImageUploader: ProductImageUploaderProtocol = ServiceLocator.productImageUploader, analytics: Analytics = ServiceLocator.analytics, stores: StoresManager = ServiceLocator.stores, - posEligibilityCheckerFactory: ((Int64) -> POSEntryPointEligibilityCheckerProtocol)? = nil, + posEligibilityCheckerFactory: ((Site) -> POSEntryPointEligibilityCheckerProtocol)? = nil, posEligibilityService: POSEligibilityServiceProtocol = POSEligibilityService()) { self.featureFlagService = featureFlagService self.noticePresenter = noticePresenter self.productImageUploader = productImageUploader self.analytics = analytics self.stores = stores - self.posEligibilityCheckerFactory = posEligibilityCheckerFactory ?? { siteID in + self.posEligibilityCheckerFactory = posEligibilityCheckerFactory ?? { site in if featureFlagService.isFeatureFlagEnabled(.pointOfSaleAsATabi2) { - POSTabEligibilityChecker(siteID: siteID) + POSTabEligibilityChecker(site: site) } else { - LegacyPOSTabEligibilityChecker(siteID: siteID) + LegacyPOSTabEligibilityChecker(site: site) } } self.posEligibilityService = posEligibilityService @@ -167,11 +169,11 @@ final class MainTabBarController: UITabBarController { self.productImageUploader = ServiceLocator.productImageUploader self.analytics = ServiceLocator.analytics self.stores = ServiceLocator.stores - self.posEligibilityCheckerFactory = { siteID in + self.posEligibilityCheckerFactory = { site in if featureFlagService.isFeatureFlagEnabled(.pointOfSaleAsATabi2) { - POSTabEligibilityChecker(siteID: siteID) + POSTabEligibilityChecker(site: site) } else { - LegacyPOSTabEligibilityChecker(siteID: siteID) + LegacyPOSTabEligibilityChecker(site: site) } } self.posEligibilityService = POSEligibilityService() @@ -194,6 +196,7 @@ final class MainTabBarController: UITabBarController { // POS tab is hidden by default. updateTabViewControllers(isPOSTabVisible: false) observeSiteIDForViewControllers() + observeSiteForConditionalTabs() observeProductImageUploadStatusUpdates() startListeningToHubMenuTabBadgeUpdates() @@ -724,6 +727,18 @@ private extension MainTabBarController { } } + func observeSiteForConditionalTabs() { + cancellableSite = stores.site + .compactMap { $0 } + .sink { [weak self] site in + guard let self else { + return + } + + observeConditionalTabsAvailabilityWith(site) + } + } + func observeSiteIDForViewControllers() { cancellableSiteID = stores.siteID.sink { [weak self] siteID in guard let self = self else { @@ -733,8 +748,23 @@ private extension MainTabBarController { } } + func observeConditionalTabsAvailabilityWith(_ site: Site) { + // Configures POS tab coordinator once per logged in site session. + let posEligibilityChecker = posEligibilityCheckerFactory(site) + self.posEligibilityChecker = posEligibilityChecker + posTabCoordinator = POSTabCoordinator( + siteID: site.siteID, + tabContainerController: posContainerController, + viewControllerToPresent: self, + storesManager: stores, + eligibilityChecker: posEligibilityChecker + ) + + observePOSEligibilityForPOSTabVisibility(siteID: site.siteID) + } + func updateViewControllers(siteID: Int64?) { - guard let siteID = siteID else { + guard let siteID else { return } @@ -755,17 +785,6 @@ private extension MainTabBarController { navigateToContent: { _ in })] } - // Configures POS tab coordinator once per logged in site session. - let posEligibilityChecker = posEligibilityCheckerFactory(siteID) - self.posEligibilityChecker = posEligibilityChecker - posTabCoordinator = POSTabCoordinator( - siteID: siteID, - tabContainerController: posContainerController, - viewControllerToPresent: self, - storesManager: stores, - eligibilityChecker: posEligibilityChecker - ) - // Configure POS catalog sync coordinator for local catalog syncing // Get POS catalog sync coordinator (will be nil if feature flag disabled or not authenticated) posCatalogSyncCoordinator = ServiceLocator.posCatalogSyncCoordinator @@ -779,8 +798,6 @@ private extension MainTabBarController { // Set dashboard to be the default tab. selectedIndex = WooTab.myStore.visibleIndex(isPOSTabVisible: isPOSTabVisible) - - observePOSEligibilityForPOSTabVisibility(siteID: siteID) } func createDashboardViewController(siteID: Int64) -> UIViewController { diff --git a/WooCommerce/WooCommerceTests/ViewRelated/MainTabBarController+TabsTests.swift b/WooCommerce/WooCommerceTests/ViewRelated/MainTabBarController+TabsTests.swift index 55c6e5aaf82..7ab97cd28e6 100644 --- a/WooCommerce/WooCommerceTests/ViewRelated/MainTabBarController+TabsTests.swift +++ b/WooCommerce/WooCommerceTests/ViewRelated/MainTabBarController+TabsTests.swift @@ -22,7 +22,9 @@ final class MainTabBarController_TabsTests: XCTestCase { XCTAssertNotNil(tabBarController.view) // Action - storesManager.updateDefaultStore(storeID: 980) + let siteID: Int64 = 980 + storesManager.updateDefaultStore(storeID: siteID) + storesManager.updateDefaultStore(.fake().copy(siteID: siteID)) // Assert XCTAssertEqual(tabBarController.viewControllers?.count, 4) @@ -58,7 +60,9 @@ final class MainTabBarController_TabsTests: XCTestCase { XCTAssertNotNil(tabBarController.view) // When - storesManager.updateDefaultStore(storeID: 314) + let siteID: Int64 = 314 + storesManager.updateDefaultStore(storeID: siteID) + storesManager.updateDefaultStore(.fake().copy(siteID: siteID)) // Then waitUntil { @@ -98,7 +102,9 @@ final class MainTabBarController_TabsTests: XCTestCase { XCTAssertNotNil(tabBarController.view) // When - storesManager.updateDefaultStore(storeID: 707) + let siteID: Int64 = 707 + storesManager.updateDefaultStore(storeID: siteID) + storesManager.updateDefaultStore(.fake().copy(siteID: siteID)) // Then waitUntil { @@ -136,7 +142,9 @@ final class MainTabBarController_TabsTests: XCTestCase { XCTAssertNotNil(tabBarController.view) // When - storesManager.updateDefaultStore(storeID: 303) + let siteID: Int64 = 303 + storesManager.updateDefaultStore(storeID: siteID) + storesManager.updateDefaultStore(.fake().copy(siteID: siteID)) // Then initial state waitUntil { @@ -163,9 +171,14 @@ final class MainTabBarController_TabsTests: XCTestCase { XCTAssertNotNil(tabBarController.view) // Action - stores.updateDefaultStore(storeID: 134) + let siteIDBefore: Int64 = 134 + stores.updateDefaultStore(storeID: siteIDBefore) + stores.updateDefaultStore(.fake().copy(siteID: siteIDBefore)) let viewControllersBeforeSiteChange = tabBarController.tabRootViewControllers - stores.updateDefaultStore(storeID: 630) + + let siteIDAfter: Int64 = 630 + stores.updateDefaultStore(storeID: siteIDAfter) + stores.updateDefaultStore(.fake().copy(siteID: siteIDAfter)) let viewControllersAfterSiteChange = tabBarController.tabRootViewControllers // Assert @@ -195,8 +208,11 @@ final class MainTabBarController_TabsTests: XCTestCase { // Action let siteID: Int64 = 610 stores.updateDefaultStore(storeID: siteID) + stores.updateDefaultStore(.fake().copy(siteID: siteID)) let viewControllersBeforeSiteChange = try XCTUnwrap(tabBarController.viewControllers) + stores.updateDefaultStore(storeID: siteID) + stores.updateDefaultStore(.fake().copy(siteID: siteID)) let viewControllersAfterSiteChange = try XCTUnwrap(tabBarController.viewControllers) // Assert diff --git a/WooCommerce/WooCommerceTests/ViewRelated/MainTabBarControllerTests.swift b/WooCommerce/WooCommerceTests/ViewRelated/MainTabBarControllerTests.swift index 52987be4214..65b006763ab 100644 --- a/WooCommerce/WooCommerceTests/ViewRelated/MainTabBarControllerTests.swift +++ b/WooCommerce/WooCommerceTests/ViewRelated/MainTabBarControllerTests.swift @@ -51,14 +51,18 @@ final class MainTabBarControllerTests: XCTestCase { XCTAssertNotNil(tabBarController.view) // Action - stores.updateDefaultStore(storeID: 134) + let siteIDBefore: Int64 = 134 + stores.updateDefaultStore(storeID: siteIDBefore) + stores.updateDefaultStore(.fake().copy(siteID: siteIDBefore)) waitFor { promise in tabBarController.navigateTo(.products) { promise(()) } } let selectedTabIndexBeforeSiteChange = tabBarController.selectedIndex - stores.updateDefaultStore(storeID: 630) + let siteIDAfter: Int64 = 630 + stores.updateDefaultStore(storeID: siteIDAfter) + stores.updateDefaultStore(.fake().copy(siteID: siteIDAfter)) let selectedTabIndexAfterSiteChange = tabBarController.selectedIndex // Assert @@ -90,7 +94,9 @@ final class MainTabBarControllerTests: XCTestCase { // Trigger `viewDidLoad` XCTAssertNotNil(tabBarController.view) - storesManager.updateDefaultStore(storeID: 782) + let siteID: Int64 = 782 + storesManager.updateDefaultStore(storeID: siteID) + storesManager.updateDefaultStore(.fake().copy(siteID: siteID)) // Simulate successful state resetting after logging out from push notification store switching storesManager.whenReceivingAction(ofType: StatsActionV4.self) { action in @@ -349,7 +355,9 @@ final class MainTabBarControllerTests: XCTestCase { func test_navigateToTabWithNavigationController_returns_UIViewController_of_the_newly_selected_tab() throws { // Given - stores.updateDefaultStore(storeID: 134) + let siteID: Int64 = 134 + stores.updateDefaultStore(storeID: siteID) + stores.updateDefaultStore(.fake().copy(siteID: siteID)) let tabBarController = try XCTUnwrap(UIStoryboard(name: "Main", bundle: nil).instantiateInitialViewController() as? MainTabBarController) @@ -372,6 +380,7 @@ final class MainTabBarControllerTests: XCTestCase { // Given let siteID: Int64 = 256 stores.updateDefaultStore(storeID: siteID) + stores.updateDefaultStore(.fake().copy(siteID: siteID)) let mockFeatureFlagService = MockFeatureFlagService() ServiceLocator.setFeatureFlagService(mockFeatureFlagService) @@ -410,6 +419,7 @@ final class MainTabBarControllerTests: XCTestCase { let siteID: Int64 = 256 stores.updateDefaultStore(storeID: siteID) + stores.updateDefaultStore(.fake().copy(siteID: siteID)) stores.whenReceivingAction(ofType: NotificationAction.self) { action in guard case let .synchronizeNotification(_, completion) = action else { @@ -476,7 +486,9 @@ final class MainTabBarControllerTests: XCTestCase { XCTAssertNotNil(tabBarController.view) // When POS tab initial visibility is set to true - stores.updateDefaultStore(storeID: 1126) + let siteID: Int64 = 1126 + stores.updateDefaultStore(storeID: siteID) + stores.updateDefaultStore(.fake().copy(siteID: siteID)) // Then POS tab is visible before eligibility check is returned waitUntil { @@ -537,7 +549,9 @@ final class MainTabBarControllerTests: XCTestCase { XCTAssertNotNil(tabBarController.view) // When POS tab initial visibility is set to true - stores.updateDefaultStore(storeID: 1216) + let siteID: Int64 = 1216 + stores.updateDefaultStore(storeID: siteID) + stores.updateDefaultStore(.fake().copy(siteID: siteID)) mockPOSEligibilityChecker.setVisibilityResult(true) waitUntil { @@ -545,7 +559,7 @@ final class MainTabBarControllerTests: XCTestCase { } // Then - XCTAssertEqual(mockPOSEligibilityService.loadCachedPOSTabVisibility(siteID: 1216), true) + XCTAssertEqual(mockPOSEligibilityService.loadCachedPOSTabVisibility(siteID: siteID), true) } func test_event_is_tracked_after_eligibility_check() throws { @@ -568,7 +582,9 @@ final class MainTabBarControllerTests: XCTestCase { XCTAssertNotNil(tabBarController.view) // When - storesManager.updateDefaultStore(storeID: 322) + let siteID: Int64 = 322 + storesManager.updateDefaultStore(storeID: siteID) + storesManager.updateDefaultStore(.fake().copy(siteID: siteID)) // Then waitUntil { diff --git a/WooCommerce/WooCommerceTests/ViewRelated/Settings/POS/LegacyPOSTabEligibilityCheckerTests.swift b/WooCommerce/WooCommerceTests/ViewRelated/Settings/POS/LegacyPOSTabEligibilityCheckerTests.swift index 4f5ecc23812..97059db57a8 100644 --- a/WooCommerce/WooCommerceTests/ViewRelated/Settings/POS/LegacyPOSTabEligibilityCheckerTests.swift +++ b/WooCommerce/WooCommerceTests/ViewRelated/Settings/POS/LegacyPOSTabEligibilityCheckerTests.swift @@ -12,7 +12,8 @@ struct LegacyPOSTabEligibilityCheckerTests { private var siteSettings: MockSelectedSiteSettings! private var pluginsService: MockPluginsService! private var eligibilityService: MockPOSEligibilityService! - private let siteID: Int64 = 2 + private let site = Site.fake().copy(siteID: 2) + private var siteID: Int64 { site.siteID } init() async throws { stores = MockStoresManager(sessionManager: .makeForTesting(authenticated: true)) @@ -33,7 +34,7 @@ struct LegacyPOSTabEligibilityCheckerTests { let featureFlagService = MockFeatureFlagService(isPointOfSaleAsATabi2Enabled: false) setupCountry(country: country, currency: currency) accountWhitelistedInBackend(true) - let checker = LegacyPOSTabEligibilityChecker(siteID: siteID, + let checker = LegacyPOSTabEligibilityChecker(site: site, userInterfaceIdiom: .pad, siteSettings: siteSettings, pluginsService: pluginsService, @@ -52,7 +53,7 @@ struct LegacyPOSTabEligibilityCheckerTests { let featureFlagService = MockFeatureFlagService(isPointOfSaleAsATabi2Enabled: false) setupCountry(country: .us) accountWhitelistedInBackend(false) - let checker = LegacyPOSTabEligibilityChecker(siteID: siteID, + let checker = LegacyPOSTabEligibilityChecker(site: site, userInterfaceIdiom: .pad, siteSettings: siteSettings, pluginsService: pluginsService, @@ -71,7 +72,7 @@ struct LegacyPOSTabEligibilityCheckerTests { let featureFlagService = MockFeatureFlagService(isPointOfSaleAsATabi2Enabled: false) setupCountry(country: .us) accountWhitelistedInBackend(true) - let checker = LegacyPOSTabEligibilityChecker(siteID: siteID, + let checker = LegacyPOSTabEligibilityChecker(site: site, userInterfaceIdiom: .phone, siteSettings: siteSettings, pluginsService: pluginsService, @@ -85,6 +86,29 @@ struct LegacyPOSTabEligibilityCheckerTests { #expect(result == false) } + @Test func is_invisible_when_site_is_CIAB() async throws { + // Given + let featureFlagService = MockFeatureFlagService(isPointOfSaleAsATabi2Enabled: false) + setupCountry(country: .us) + accountWhitelistedInBackend(true) + let ciabEligibilityChecker = MockCIABEligibilityChecker(mockedIsCurrentSiteCIAB: true, + mockedCIABSites: [site], + mockedCIABDisabledFeatures: [.pointOfSale]) + let checker = LegacyPOSTabEligibilityChecker(site: site, + userInterfaceIdiom: .pad, + siteSettings: siteSettings, + pluginsService: pluginsService, + stores: stores, + featureFlagService: featureFlagService, + siteCIABEligibilityChecker: ciabEligibilityChecker) + + // When + let result = await checker.checkVisibility() + + // Then + #expect(result == false) + } + @Test(arguments: [ (country: Country.ca, currency: CurrencyCode.CAD), (country: Country.es, currency: CurrencyCode.EUR) @@ -94,7 +118,7 @@ struct LegacyPOSTabEligibilityCheckerTests { let featureFlagService = MockFeatureFlagService(isPointOfSaleAsATabi2Enabled: false) setupCountry(country: country, currency: currency) accountWhitelistedInBackend(true) - let checker = LegacyPOSTabEligibilityChecker(siteID: siteID, + let checker = LegacyPOSTabEligibilityChecker(site: site, userInterfaceIdiom: .pad, siteSettings: siteSettings, pluginsService: pluginsService, @@ -120,7 +144,7 @@ struct LegacyPOSTabEligibilityCheckerTests { let featureFlagService = MockFeatureFlagService(isPointOfSaleAsATabi2Enabled: false) setupCountry(country: country, currency: currency) accountWhitelistedInBackend(true) - let checker = LegacyPOSTabEligibilityChecker(siteID: siteID, + let checker = LegacyPOSTabEligibilityChecker(site: site, userInterfaceIdiom: .pad, siteSettings: siteSettings, pluginsService: pluginsService, @@ -140,7 +164,7 @@ struct LegacyPOSTabEligibilityCheckerTests { setupCountry(country: .us) accountWhitelistedInBackend(true) setupWooCommerceVersion("9.5.0") - let checker = LegacyPOSTabEligibilityChecker(siteID: siteID, + let checker = LegacyPOSTabEligibilityChecker(site: site, userInterfaceIdiom: .pad, siteSettings: siteSettings, pluginsService: pluginsService, @@ -161,7 +185,7 @@ struct LegacyPOSTabEligibilityCheckerTests { accountWhitelistedInBackend(true) setupWooCommerceVersion("10.0.0") setupPOSFeatureEnabled(.success(true)) - let checker = LegacyPOSTabEligibilityChecker(siteID: siteID, + let checker = LegacyPOSTabEligibilityChecker(site: site, userInterfaceIdiom: .pad, siteSettings: siteSettings, pluginsService: pluginsService, @@ -182,7 +206,7 @@ struct LegacyPOSTabEligibilityCheckerTests { accountWhitelistedInBackend(true) setupWooCommerceVersion("10.0.0") setupPOSFeatureEnabled(.success(false)) - let checker = LegacyPOSTabEligibilityChecker(siteID: siteID, + let checker = LegacyPOSTabEligibilityChecker(site: site, userInterfaceIdiom: .pad, siteSettings: siteSettings, pluginsService: pluginsService, @@ -203,7 +227,7 @@ struct LegacyPOSTabEligibilityCheckerTests { accountWhitelistedInBackend(true) setupWooCommerceVersion("10.0.0") setupPOSFeatureEnabled(.failure(NSError(domain: "test", code: 0))) - let checker = LegacyPOSTabEligibilityChecker(siteID: siteID, + let checker = LegacyPOSTabEligibilityChecker(site: site, userInterfaceIdiom: .pad, siteSettings: siteSettings, pluginsService: pluginsService, @@ -224,7 +248,7 @@ struct LegacyPOSTabEligibilityCheckerTests { accountWhitelistedInBackend(true) setupWooCommerceVersion("9.9.9") setupPOSFeatureEnabled(.success(false)) - let checker = LegacyPOSTabEligibilityChecker(siteID: siteID, + let checker = LegacyPOSTabEligibilityChecker(site: site, userInterfaceIdiom: .pad, siteSettings: siteSettings, pluginsService: pluginsService, @@ -240,7 +264,7 @@ struct LegacyPOSTabEligibilityCheckerTests { @Test func checkInitialVisibility_returns_true_when_cached_tab_visibility_is_enabled() async throws { // Given - let checker = LegacyPOSTabEligibilityChecker(siteID: siteID, eligibilityService: eligibilityService, stores: stores) + let checker = LegacyPOSTabEligibilityChecker(site: site, eligibilityService: eligibilityService, stores: stores) setupPOSTabVisibility(siteID: siteID, isVisible: true) // When @@ -252,7 +276,7 @@ struct LegacyPOSTabEligibilityCheckerTests { @Test func checkInitialVisibility_returns_false_when_cached_tab_visibility_is_disabled() async throws { // Given - let checker = LegacyPOSTabEligibilityChecker(siteID: siteID, eligibilityService: eligibilityService, stores: stores) + let checker = LegacyPOSTabEligibilityChecker(site: site, eligibilityService: eligibilityService, stores: stores) setupPOSTabVisibility(siteID: siteID, isVisible: false) // When @@ -264,7 +288,7 @@ struct LegacyPOSTabEligibilityCheckerTests { @Test func checkInitialVisibility_returns_false_when_cached_tab_visibility_is_unavailable() async throws { // Given - let checker = LegacyPOSTabEligibilityChecker(siteID: siteID, eligibilityService: eligibilityService, stores: stores) + let checker = LegacyPOSTabEligibilityChecker(site: site, eligibilityService: eligibilityService, stores: stores) setupPOSTabVisibility(siteID: siteID, isVisible: nil) // When @@ -296,7 +320,7 @@ struct LegacyPOSTabEligibilityCheckerTests { ].publisher.eraseToAnyPublisher() accountWhitelistedInBackend(true) - let checker = LegacyPOSTabEligibilityChecker(siteID: siteID, + let checker = LegacyPOSTabEligibilityChecker(site: site, userInterfaceIdiom: .pad, siteSettings: siteSettings, pluginsService: pluginsService, @@ -335,7 +359,7 @@ struct LegacyPOSTabEligibilityCheckerTests { ].publisher.eraseToAnyPublisher() accountWhitelistedInBackend(true) - let checker = LegacyPOSTabEligibilityChecker(siteID: siteID, + let checker = LegacyPOSTabEligibilityChecker(site: site, userInterfaceIdiom: .pad, siteSettings: siteSettings, pluginsService: pluginsService, diff --git a/WooCommerce/WooCommerceTests/ViewRelated/Settings/POS/POSTabEligibilityCheckerTests.swift b/WooCommerce/WooCommerceTests/ViewRelated/Settings/POS/POSTabEligibilityCheckerTests.swift index ba6492fd65d..8eea672dece 100644 --- a/WooCommerce/WooCommerceTests/ViewRelated/Settings/POS/POSTabEligibilityCheckerTests.swift +++ b/WooCommerce/WooCommerceTests/ViewRelated/Settings/POS/POSTabEligibilityCheckerTests.swift @@ -13,7 +13,8 @@ struct POSTabEligibilityCheckerTests { private var eligibilityService: MockPOSEligibilityService! private var mockSystemStatusService: MockPOSSystemStatusService! private var mockSiteSettingService: MockPOSSiteSettingService! - private let siteID: Int64 = 2 + private let site = Site.fake().copy(siteID: 2) + private var siteID: Int64 { site.siteID } init() async throws { stores = MockStoresManager(sessionManager: .makeForTesting(authenticated: true)) @@ -37,7 +38,7 @@ struct POSTabEligibilityCheckerTests { let featureFlagService = MockFeatureFlagService(isPointOfSaleAsATabi2Enabled: true) setupCountry(country: country, currency: currency) accountWhitelistedInBackend(true) - let checker = POSTabEligibilityChecker(siteID: siteID, + let checker = POSTabEligibilityChecker(site: site, userInterfaceIdiom: .pad, siteSettings: siteSettings, stores: stores, @@ -59,7 +60,7 @@ struct POSTabEligibilityCheckerTests { let featureFlagService = MockFeatureFlagService(isPointOfSaleAsATabi2Enabled: true) setupCountry(country: country, currency: currency) accountWhitelistedInBackend(true) - let checker = POSTabEligibilityChecker(siteID: siteID, + let checker = POSTabEligibilityChecker(site: site, userInterfaceIdiom: .pad, siteSettings: siteSettings, stores: stores, @@ -84,7 +85,7 @@ struct POSTabEligibilityCheckerTests { let featureFlagService = MockFeatureFlagService(isPointOfSaleAsATabi2Enabled: true) setupCountry(country: country, currency: currency) accountWhitelistedInBackend(true) - let checker = POSTabEligibilityChecker(siteID: siteID, + let checker = POSTabEligibilityChecker(site: site, userInterfaceIdiom: .pad, siteSettings: siteSettings, stores: stores, @@ -104,7 +105,7 @@ struct POSTabEligibilityCheckerTests { setupCountry(country: .us) accountWhitelistedInBackend(true) setupWooCommerceVersion("9.5.0") - let checker = POSTabEligibilityChecker(siteID: siteID, + let checker = POSTabEligibilityChecker(site: site, userInterfaceIdiom: .pad, siteSettings: siteSettings, stores: stores, @@ -123,7 +124,7 @@ struct POSTabEligibilityCheckerTests { setupCountry(country: .us) accountWhitelistedInBackend(true) setupWooCommerceVersion("10.0.0", featureSwitchEnabled: true) - let checker = POSTabEligibilityChecker(siteID: siteID, + let checker = POSTabEligibilityChecker(site: site, userInterfaceIdiom: .pad, siteSettings: siteSettings, stores: stores, @@ -143,7 +144,7 @@ struct POSTabEligibilityCheckerTests { setupCountry(country: .us) accountWhitelistedInBackend(true) setupWooCommerceVersion("10.0.0", featureSwitchEnabled: false) - let checker = POSTabEligibilityChecker(siteID: siteID, + let checker = POSTabEligibilityChecker(site: site, userInterfaceIdiom: .pad, siteSettings: siteSettings, stores: stores, @@ -163,7 +164,7 @@ struct POSTabEligibilityCheckerTests { setupCountry(country: .us) accountWhitelistedInBackend(true) setupWooCommerceVersion("10.0.0", featureSwitchEnabled: nil) - let checker = POSTabEligibilityChecker(siteID: siteID, + let checker = POSTabEligibilityChecker(site: site, userInterfaceIdiom: .pad, siteSettings: siteSettings, stores: stores, @@ -182,7 +183,7 @@ struct POSTabEligibilityCheckerTests { setupCountry(country: .us) accountWhitelistedInBackend(true) setupWooCommerceVersion("9.9.9", featureSwitchEnabled: false) - let checker = POSTabEligibilityChecker(siteID: siteID, + let checker = POSTabEligibilityChecker(site: site, userInterfaceIdiom: .pad, siteSettings: siteSettings, stores: stores, @@ -221,7 +222,7 @@ struct POSTabEligibilityCheckerTests { accountWhitelistedInBackend(true) setupWooCommerceVersion("9.6.0") - let checker = POSTabEligibilityChecker(siteID: siteID, + let checker = POSTabEligibilityChecker(site: site, userInterfaceIdiom: .pad, siteSettings: siteSettings, stores: stores, @@ -240,7 +241,7 @@ struct POSTabEligibilityCheckerTests { let featureFlagService = MockFeatureFlagService(isPointOfSaleAsATabi2Enabled: true) setupCountry(country: .us) accountWhitelistedInBackend(false) - let checker = POSTabEligibilityChecker(siteID: siteID, + let checker = POSTabEligibilityChecker(site: site, userInterfaceIdiom: .pad, siteSettings: siteSettings, stores: stores, @@ -275,7 +276,7 @@ struct POSTabEligibilityCheckerTests { ].publisher.eraseToAnyPublisher() accountWhitelistedInBackend(true) - let checker = POSTabEligibilityChecker(siteID: siteID, + let checker = POSTabEligibilityChecker(site: site, userInterfaceIdiom: .pad, siteSettings: siteSettings, stores: stores, @@ -293,7 +294,7 @@ struct POSTabEligibilityCheckerTests { let featureFlagService = MockFeatureFlagService(isPointOfSaleAsATabi2Enabled: true) setupCountry(country: .us) accountWhitelistedInBackend(true) - let checker = POSTabEligibilityChecker(siteID: siteID, + let checker = POSTabEligibilityChecker(site: site, userInterfaceIdiom: .phone, // Not iPad siteSettings: siteSettings, stores: stores, @@ -306,6 +307,28 @@ struct POSTabEligibilityCheckerTests { #expect(result == false) } + @Test func is_invisible_when_site_is_CIAB() async throws { + // Given + let featureFlagService = MockFeatureFlagService(isPointOfSaleAsATabi2Enabled: true) + setupCountry(country: .us, currency: .USD) + accountWhitelistedInBackend(true) + let ciabEligibilityChecker = MockCIABEligibilityChecker(mockedIsCurrentSiteCIAB: true, + mockedCIABSites: [site], + mockedCIABDisabledFeatures: [.pointOfSale]) + let checker = POSTabEligibilityChecker(site: site, + userInterfaceIdiom: .pad, + siteSettings: siteSettings, + stores: stores, + featureFlagService: featureFlagService, + siteCIABEligibilityChecker: ciabEligibilityChecker) + + // When + let result = await checker.checkVisibility() + + // Then + #expect(result == false) + } + @Test func checkVisibility_and_checkEligibility_return_expected_result_after_site_settings_available() async throws { // Given - no site settings are immediately available (empty stream that will emit values later) let featureFlagService = MockFeatureFlagService(isPointOfSaleAsATabi2Enabled: true) @@ -318,7 +341,7 @@ struct POSTabEligibilityCheckerTests { siteSettings.mockSettingsStream = settingsSubject.eraseToAnyPublisher() setupWooCommerceVersion("9.6.0") - let checker = POSTabEligibilityChecker(siteID: siteID, + let checker = POSTabEligibilityChecker(site: site, userInterfaceIdiom: .pad, siteSettings: siteSettings, stores: stores, @@ -343,11 +366,34 @@ struct POSTabEligibilityCheckerTests { #expect(eligibilityResult == .eligible) } + @Test func is_ineligible_when_site_is_CIAB() async throws { + // Given + let featureFlagService = MockFeatureFlagService(isPointOfSaleAsATabi2Enabled: true) + setupCountry(country: .us, currency: .USD) + accountWhitelistedInBackend(true) + let ciabEligibilityChecker = MockCIABEligibilityChecker(mockedIsCurrentSiteCIAB: true, + mockedCIABSites: [site], + mockedCIABDisabledFeatures: [.pointOfSale]) + let checker = POSTabEligibilityChecker(site: site, + userInterfaceIdiom: .pad, + siteSettings: siteSettings, + stores: stores, + featureFlagService: featureFlagService, + systemStatusService: mockSystemStatusService, + siteCIABEligibilityChecker: ciabEligibilityChecker) + + // When + let result = await checker.checkEligibility() + + // Then + #expect(result == .ineligible(reason: .unsupportedInCIABSites)) + } + // MARK: - `checkInitialVisibility Tests @Test func checkInitialVisibility_returns_true_when_cached_tab_visibility_is_enabled() async throws { // Given - let checker = POSTabEligibilityChecker(siteID: siteID, eligibilityService: eligibilityService, stores: stores) + let checker = POSTabEligibilityChecker(site: site, eligibilityService: eligibilityService, stores: stores) setupPOSTabVisibility(siteID: siteID, isVisible: true) // When @@ -359,7 +405,7 @@ struct POSTabEligibilityCheckerTests { @Test func checkInitialVisibility_returns_false_when_cached_tab_visibility_is_disabled() async throws { // Given - let checker = POSTabEligibilityChecker(siteID: siteID, eligibilityService: eligibilityService, stores: stores) + let checker = POSTabEligibilityChecker(site: site, eligibilityService: eligibilityService, stores: stores) setupPOSTabVisibility(siteID: siteID, isVisible: false) // When @@ -371,7 +417,7 @@ struct POSTabEligibilityCheckerTests { @Test func checkInitialVisibility_returns_false_when_cached_tab_visibility_is_unavailable() async throws { // Given - let checker = POSTabEligibilityChecker(siteID: siteID, eligibilityService: eligibilityService, stores: stores) + let checker = POSTabEligibilityChecker(site: site, eligibilityService: eligibilityService, stores: stores) setupPOSTabVisibility(siteID: siteID, isVisible: nil) // When @@ -392,7 +438,7 @@ struct POSTabEligibilityCheckerTests { let featureFlagService = MockFeatureFlagService(isPointOfSaleAsATabi2Enabled: true) setupCountry(country: country, currency: currency) accountWhitelistedInBackend(true) - let checker = POSTabEligibilityChecker(siteID: siteID, + let checker = POSTabEligibilityChecker(site: site, userInterfaceIdiom: .pad, siteSettings: siteSettings, stores: stores, @@ -415,7 +461,7 @@ struct POSTabEligibilityCheckerTests { let featureFlagService = MockFeatureFlagService(isPointOfSaleAsATabi2Enabled: true) setupCountry(country: country, currency: currency) accountWhitelistedInBackend(true) - let checker = POSTabEligibilityChecker(siteID: siteID, + let checker = POSTabEligibilityChecker(site: site, userInterfaceIdiom: .pad, siteSettings: siteSettings, stores: stores, @@ -441,7 +487,7 @@ struct POSTabEligibilityCheckerTests { let featureFlagService = MockFeatureFlagService(isPointOfSaleAsATabi2Enabled: true) setupCountry(country: country, currency: currency) accountWhitelistedInBackend(true) - let checker = POSTabEligibilityChecker(siteID: siteID, + let checker = POSTabEligibilityChecker(site: site, userInterfaceIdiom: .pad, siteSettings: siteSettings, stores: stores, @@ -460,7 +506,7 @@ struct POSTabEligibilityCheckerTests { setupCountry(country: .us) accountWhitelistedInBackend(true) setupWooCommerceVersion("9.5.0") - let checker = POSTabEligibilityChecker(siteID: siteID, + let checker = POSTabEligibilityChecker(site: site, userInterfaceIdiom: .pad, siteSettings: siteSettings, stores: stores, @@ -480,7 +526,7 @@ struct POSTabEligibilityCheckerTests { setupCountry(country: .us) accountWhitelistedInBackend(true) setupWooCommerceVersion("10.0.0", featureSwitchEnabled: true) - let checker = POSTabEligibilityChecker(siteID: siteID, + let checker = POSTabEligibilityChecker(site: site, userInterfaceIdiom: .pad, siteSettings: siteSettings, stores: stores, @@ -500,7 +546,7 @@ struct POSTabEligibilityCheckerTests { setupCountry(country: .us) accountWhitelistedInBackend(true) setupWooCommerceVersion("10.0.0", featureSwitchEnabled: false) - let checker = POSTabEligibilityChecker(siteID: siteID, + let checker = POSTabEligibilityChecker(site: site, userInterfaceIdiom: .pad, siteSettings: siteSettings, stores: stores, @@ -520,7 +566,7 @@ struct POSTabEligibilityCheckerTests { setupCountry(country: .us) accountWhitelistedInBackend(true) setupWooCommerceVersion("9.9.9", featureSwitchEnabled: false) - let checker = POSTabEligibilityChecker(siteID: siteID, + let checker = POSTabEligibilityChecker(site: site, userInterfaceIdiom: .pad, siteSettings: siteSettings, stores: stores, @@ -538,7 +584,7 @@ struct POSTabEligibilityCheckerTests { @Test func refreshEligibility_returns_ineligible_for_unsupportedIOSVersion() async throws { // Given - let checker = POSTabEligibilityChecker(siteID: siteID, + let checker = POSTabEligibilityChecker(site: site, siteSettings: siteSettings, stores: stores) @@ -571,7 +617,7 @@ struct POSTabEligibilityCheckerTests { } } - let checker = POSTabEligibilityChecker(siteID: siteID, + let checker = POSTabEligibilityChecker(site: site, siteSettings: siteSettings, stores: stores, systemStatusService: mockSystemStatusService) @@ -604,7 +650,7 @@ struct POSTabEligibilityCheckerTests { } } - let checker = POSTabEligibilityChecker(siteID: siteID, + let checker = POSTabEligibilityChecker(site: site, siteSettings: siteSettings, stores: stores) @@ -621,7 +667,7 @@ struct POSTabEligibilityCheckerTests { setupCountry(country: .us, currency: .USD) setupWooCommerceVersion("10.0.0", featureSwitchEnabled: true) - let checker = POSTabEligibilityChecker(siteID: siteID, + let checker = POSTabEligibilityChecker(site: site, siteSettings: siteSettings, stores: stores, systemStatusService: mockSystemStatusService, @@ -639,7 +685,7 @@ struct POSTabEligibilityCheckerTests { setupCountry(country: .us, currency: .USD) setupWooCommerceVersion("9.6.0", featureSwitchEnabled: true) - let checker = POSTabEligibilityChecker(siteID: siteID, + let checker = POSTabEligibilityChecker(site: site, siteSettings: siteSettings, stores: stores, systemStatusService: mockSystemStatusService) @@ -663,7 +709,7 @@ struct POSTabEligibilityCheckerTests { mockSystemStatusService.resultToReturn = .success(POSPluginAndFeatureInfo(wcPlugin: wcPlugin, featureValue: nil)) setupCountry(country: .us, currency: .USD) - let checker = POSTabEligibilityChecker(siteID: siteID, + let checker = POSTabEligibilityChecker(site: site, siteSettings: siteSettings, stores: stores, systemStatusService: mockSystemStatusService) @@ -685,7 +731,7 @@ struct POSTabEligibilityCheckerTests { mockSystemStatusService.resultToReturn = .success(POSPluginAndFeatureInfo(wcPlugin: wcPlugin, featureValue: true)) setupCountry(country: .us, currency: .USD) - let checker = POSTabEligibilityChecker(siteID: siteID, + let checker = POSTabEligibilityChecker(site: site, siteSettings: siteSettings, stores: stores, systemStatusService: mockSystemStatusService) @@ -706,7 +752,7 @@ struct POSTabEligibilityCheckerTests { mockSystemStatusService.resultToReturn = .success(POSPluginAndFeatureInfo(wcPlugin: nil, featureValue: nil)) setupCountry(country: .us, currency: .USD) - let checker = POSTabEligibilityChecker(siteID: siteID, + let checker = POSTabEligibilityChecker(site: site, siteSettings: siteSettings, stores: stores, systemStatusService: mockSystemStatusService) @@ -728,7 +774,7 @@ struct POSTabEligibilityCheckerTests { mockSystemStatusService.resultToReturn = .success(POSPluginAndFeatureInfo(wcPlugin: wcPlugin, featureValue: nil)) setupCountry(country: .us, currency: .USD) - let checker = POSTabEligibilityChecker(siteID: siteID, + let checker = POSTabEligibilityChecker(site: site, siteSettings: siteSettings, stores: stores, systemStatusService: mockSystemStatusService) @@ -750,7 +796,7 @@ struct POSTabEligibilityCheckerTests { mockSystemStatusService.resultToReturn = .success(POSPluginAndFeatureInfo(wcPlugin: wcPlugin, featureValue: nil)) setupCountry(country: .us, currency: .USD) - let checker = POSTabEligibilityChecker(siteID: siteID, + let checker = POSTabEligibilityChecker(site: site, siteSettings: siteSettings, stores: stores, systemStatusService: mockSystemStatusService) @@ -771,7 +817,7 @@ struct POSTabEligibilityCheckerTests { mockSystemStatusService.resultToReturn = .failure(NSError(domain: "test", code: 500)) setupCountry(country: .us, currency: .USD) - let checker = POSTabEligibilityChecker(siteID: siteID, + let checker = POSTabEligibilityChecker(site: site, siteSettings: siteSettings, stores: stores, systemStatusService: mockSystemStatusService)