Skip to content

Commit 2eac0e1

Browse files
authored
[POS as a tab i2] Refresh POS eligibility in ineligible UI: site settings and feature switch disabled cases (#15895)
2 parents 5358b2b + 793a492 commit 2eac0e1

File tree

8 files changed

+245
-6
lines changed

8 files changed

+245
-6
lines changed

WooCommerce/Classes/POS/Controllers/POSEntryPointController.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ import protocol Experiments.FeatureFlagService
2222
}
2323

2424
@MainActor
25-
func refreshEligibility() async throws {
26-
// TODO: WOOMOB-720 - refresh eligibility
25+
func refreshEligibility(reason: POSIneligibleReason) async throws {
26+
eligibilityState = try await posEligibilityChecker.refreshEligibility(ineligibleReason: reason)
2727
}
2828
}

WooCommerce/Classes/POS/Presentation/PointOfSaleEntryPointView.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ struct PointOfSaleEntryPointView: View {
6262
}
6363
case let .ineligible(reason):
6464
POSIneligibleView(reason: reason, onRefresh: {
65-
try await posEntryPointController.refreshEligibility()
65+
try await posEntryPointController.refreshEligibility(reason: reason)
6666
})
6767
}
6868
}

WooCommerce/Classes/POS/TabBar/POSIneligibleView.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -99,8 +99,8 @@ struct POSIneligibleView: View {
9999
comment: "Suggestion for disabled feature switch: enable feature in WooCommerce settings")
100100
case .featureSwitchSyncFailure:
101101
return NSLocalizedString("pos.ineligible.suggestion.featureSwitchSyncFailure",
102-
value: "Try relaunching the app or check your internet connection and try again.",
103-
comment: "Suggestion for feature switch sync failure: relaunch or check connection")
102+
value: "Please check your internet connection and try again.",
103+
comment: "Suggestion for feature switch sync failure: check connection and retry")
104104
case let .unsupportedCurrency(supportedCurrencies):
105105
let currencyList = supportedCurrencies.map { $0.rawValue }
106106
let formattedCurrencyList = ListFormatter.localizedString(byJoining: currencyList)
@@ -114,7 +114,7 @@ struct POSIneligibleView: View {
114114
return String.localizedStringWithFormat(format, formattedCurrencyList)
115115
case .siteSettingsNotAvailable:
116116
return NSLocalizedString("pos.ineligible.suggestion.siteSettingsNotAvailable",
117-
value: "Check your internet connection and try relaunching the app. If the issue persists, please contact support.",
117+
value: "Check your internet connection and try again. If the issue persists, please contact support.",
118118
comment: "Suggestion for site settings unavailable: check connection or contact support")
119119
case .selfDeallocated:
120120
return NSLocalizedString("pos.ineligible.suggestion.selfDeallocated",

WooCommerce/Classes/ViewRelated/Dashboard/Settings/POS/LegacyPOSTabEligibilityChecker.swift

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,11 @@ final class LegacyPOSTabEligibilityChecker: POSEntryPointEligibilityCheckerProto
113113
let eligibility = await checkI1Eligibility()
114114
return eligibility == .eligible
115115
}
116+
117+
func refreshEligibility(ineligibleReason: POSIneligibleReason) async throws -> POSEligibilityState {
118+
assertionFailure("POS as a tab i1 implementation should not refresh eligibility as the eligibility check is performed in the visibility check.")
119+
return .eligible
120+
}
116121
}
117122

118123
private extension LegacyPOSTabEligibilityChecker {

WooCommerce/Classes/ViewRelated/Dashboard/Settings/POS/POSTabEligibilityChecker.swift

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ protocol POSEntryPointEligibilityCheckerProtocol {
3939
func checkVisibility() async -> Bool
4040
/// Determines whether the site is eligible for POS.
4141
func checkEligibility() async -> POSEligibilityState
42+
/// Refreshes the eligibility state based on the provided ineligible reason.
43+
func refreshEligibility(ineligibleReason: POSIneligibleReason) async throws -> POSEligibilityState
4244
}
4345

4446
final class POSTabEligibilityChecker: POSEntryPointEligibilityCheckerProtocol {
@@ -113,6 +115,33 @@ final class POSTabEligibilityChecker: POSEntryPointEligibilityCheckerProtocol {
113115

114116
return await featureFlagEligibility == .eligible
115117
}
118+
119+
func refreshEligibility(ineligibleReason: POSIneligibleReason) async throws -> POSEligibilityState {
120+
switch ineligibleReason {
121+
case .unsupportedIOSVersion:
122+
// TODO: WOOMOB-768 - hide refresh CTA in this case
123+
return .ineligible(reason: .unsupportedIOSVersion)
124+
case .siteSettingsNotAvailable, .unsupportedCurrency:
125+
do {
126+
try await syncSiteSettingsRemotely()
127+
return await checkEligibility()
128+
} catch POSTabEligibilityCheckerError.selfDeallocated {
129+
return .ineligible(reason: .selfDeallocated)
130+
} catch {
131+
throw error
132+
}
133+
case .unsupportedWooCommerceVersion, .wooCommercePluginNotFound:
134+
// TODO: WOOMOB-799 - sync the WooCommerce plugin then check eligibility again.
135+
// For now, it requires relaunching the app or switching stores to refresh the plugin info.
136+
return await checkEligibility()
137+
case .featureSwitchDisabled:
138+
// TODO: WOOMOB-759 - enable feature switch via API and check eligibility again
139+
// For now, just checks eligibility again.
140+
return await checkEligibility()
141+
case .featureSwitchSyncFailure, .selfDeallocated:
142+
return await checkEligibility()
143+
}
144+
}
116145
}
117146

118147
// MARK: - WC Plugin Related Eligibility Check
@@ -235,6 +264,25 @@ private extension POSTabEligibilityChecker {
235264
}
236265
return .eligible
237266
}
267+
268+
@MainActor
269+
func syncSiteSettingsRemotely() async throws {
270+
try await withCheckedThrowingContinuation { [weak self] (continuation: CheckedContinuation<Void, Error>) in
271+
guard let self else {
272+
return continuation.resume(throwing: POSTabEligibilityCheckerError.selfDeallocated)
273+
}
274+
stores.dispatch(SettingAction.synchronizeGeneralSiteSettings(siteID: siteID) { [weak self] error in
275+
guard let self else {
276+
return continuation.resume(throwing: POSTabEligibilityCheckerError.selfDeallocated)
277+
}
278+
if let error {
279+
return continuation.resume(throwing: error)
280+
}
281+
siteSettings.refresh()
282+
continuation.resume(returning: ())
283+
})
284+
}
285+
}
238286
}
239287

240288
// MARK: - Remote Feature Flag Eligibility Check
@@ -277,6 +325,10 @@ private extension POSTabEligibilityChecker {
277325
}
278326
}
279327

328+
private enum POSTabEligibilityCheckerError: Error {
329+
case selfDeallocated
330+
}
331+
280332
private extension POSTabEligibilityChecker {
281333
enum Constants {
282334
static let wcPlugin = "woocommerce/woocommerce.php"

WooCommerce/WooCommerceTests/Mocks/MockPOSEligibilityChecker.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,4 +19,8 @@ final class MockPOSEligibilityChecker: POSEntryPointEligibilityCheckerProtocol {
1919
func checkEligibility() async -> POSEligibilityState {
2020
eligibility
2121
}
22+
23+
func refreshEligibility(ineligibleReason: POSIneligibleReason) async throws -> POSEligibilityState {
24+
.ineligible(reason: ineligibleReason)
25+
}
2226
}

WooCommerce/WooCommerceTests/ViewRelated/MainTabBarControllerTests.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -692,4 +692,8 @@ private final class MockAsyncPOSEligibilityChecker: POSEntryPointEligibilityChec
692692
}
693693
}
694694
}
695+
696+
func refreshEligibility(ineligibleReason: POSIneligibleReason) async throws -> POSEligibilityState {
697+
.ineligible(reason: ineligibleReason)
698+
}
695699
}

WooCommerce/WooCommerceTests/ViewRelated/Settings/POS/POSTabEligibilityCheckerTests.swift

Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -570,6 +570,180 @@ struct POSTabEligibilityCheckerTests {
570570
// Then
571571
#expect(result == .eligible)
572572
}
573+
574+
// MARK: - `refreshEligibility` Tests
575+
576+
@Test func refreshEligibility_returns_ineligible_for_unsupportedIOSVersion() async throws {
577+
// Given
578+
let checker = POSTabEligibilityChecker(siteID: siteID,
579+
siteSettings: siteSettings,
580+
pluginsService: pluginsService,
581+
stores: stores)
582+
583+
// When
584+
let result = try await checker.refreshEligibility(ineligibleReason: .unsupportedIOSVersion)
585+
586+
// Then
587+
#expect(result == .ineligible(reason: .unsupportedIOSVersion))
588+
}
589+
590+
@Test(arguments: [
591+
POSIneligibleReason.siteSettingsNotAvailable,
592+
POSIneligibleReason.unsupportedCurrency(supportedCurrencies: [.USD])
593+
])
594+
fileprivate func refreshEligibility_syncs_site_settings_and_checks_eligibility_for_site_settings_issues(ineligibleReason: POSIneligibleReason) async throws {
595+
// Given
596+
setupCountry(country: .us, currency: .USD)
597+
setupWooCommerceVersion("9.6.0")
598+
setupPOSFeatureEnabled(.success(true))
599+
600+
var syncCalled = false
601+
stores.whenReceivingAction(ofType: SettingAction.self) { action in
602+
switch action {
603+
case .synchronizeGeneralSiteSettings(_, let completion):
604+
syncCalled = true
605+
completion(nil) // Success
606+
case .isFeatureEnabled(_, _, let completion):
607+
completion(.success(true))
608+
default:
609+
break
610+
}
611+
}
612+
613+
let checker = POSTabEligibilityChecker(siteID: siteID,
614+
siteSettings: siteSettings,
615+
pluginsService: pluginsService,
616+
stores: stores)
617+
618+
// When
619+
let result = try await checker.refreshEligibility(ineligibleReason: ineligibleReason)
620+
621+
// Then
622+
#expect(syncCalled == true)
623+
#expect(result == .eligible)
624+
}
625+
626+
@Test(arguments: [
627+
POSIneligibleReason.siteSettingsNotAvailable,
628+
POSIneligibleReason.unsupportedCurrency(supportedCurrencies: [.USD])
629+
])
630+
fileprivate func refreshEligibility_returns_siteSettingsNotAvailable_when_site_settings_sync_fails(ineligibleReason: POSIneligibleReason) async throws {
631+
// Given
632+
setupCountry(country: .us, currency: .USD)
633+
setupWooCommerceVersion("9.6.0")
634+
635+
var syncCalled = false
636+
stores.whenReceivingAction(ofType: SettingAction.self) { action in
637+
switch action {
638+
case .synchronizeGeneralSiteSettings(_, let completion):
639+
syncCalled = true
640+
completion(NSError(domain: "test", code: 500)) // Network error
641+
default:
642+
break
643+
}
644+
}
645+
646+
let checker = POSTabEligibilityChecker(siteID: siteID,
647+
siteSettings: siteSettings,
648+
pluginsService: pluginsService,
649+
stores: stores)
650+
651+
// When & Then - Should throw the network error
652+
#expect(syncCalled == false) // Not called yet
653+
await #expect(throws: NSError.self) {
654+
try await checker.refreshEligibility(ineligibleReason: ineligibleReason)
655+
}
656+
#expect(syncCalled == true) // Called during the attempt
657+
}
658+
659+
@Test func refreshEligibility_checks_eligibility_for_unsupportedWooCommerceVersion() async throws {
660+
// Given
661+
setupCountry(country: .us, currency: .USD)
662+
setupWooCommerceVersion("9.5.0") // Still below minimum
663+
664+
let checker = POSTabEligibilityChecker(siteID: siteID,
665+
siteSettings: siteSettings,
666+
pluginsService: pluginsService,
667+
stores: stores)
668+
669+
// When
670+
let result = try await checker.refreshEligibility(ineligibleReason: .unsupportedWooCommerceVersion(minimumVersion: "9.6.0-beta"))
671+
672+
// Then - Should check eligibility again (still ineligible due to version)
673+
#expect(result == .ineligible(reason: .unsupportedWooCommerceVersion(minimumVersion: "9.6.0-beta")))
674+
}
675+
676+
@Test func refreshEligibility_checks_eligibility_for_wooCommercePluginNotFound() async throws {
677+
// Given
678+
setupCountry(country: .us, currency: .USD)
679+
setupWooCommerceVersion("9.6.0") // Now eligible version
680+
setupPOSFeatureEnabled(.success(true))
681+
682+
let checker = POSTabEligibilityChecker(siteID: siteID,
683+
siteSettings: siteSettings,
684+
pluginsService: pluginsService,
685+
stores: stores)
686+
687+
// When
688+
let result = try await checker.refreshEligibility(ineligibleReason: .wooCommercePluginNotFound)
689+
690+
// Then - Should check eligibility again (now eligible)
691+
#expect(result == .eligible)
692+
}
693+
694+
@Test func refreshEligibility_checks_eligibility_for_featureSwitchDisabled() async throws {
695+
// Given
696+
setupCountry(country: .us, currency: .USD)
697+
setupWooCommerceVersion("10.0.0") // Version that supports feature switch
698+
setupPOSFeatureEnabled(.success(true)) // Now enabled
699+
700+
let checker = POSTabEligibilityChecker(siteID: siteID,
701+
siteSettings: siteSettings,
702+
pluginsService: pluginsService,
703+
stores: stores)
704+
705+
// When
706+
let result = try await checker.refreshEligibility(ineligibleReason: .featureSwitchDisabled)
707+
708+
// Then - Should check eligibility again (now eligible)
709+
#expect(result == .eligible)
710+
}
711+
712+
@Test func refreshEligibility_checks_eligibility_for_featureSwitchSyncFailure() async throws {
713+
// Given
714+
setupCountry(country: .us, currency: .USD)
715+
setupWooCommerceVersion("10.0.0")
716+
setupPOSFeatureEnabled(.failure(NSError(domain: "test", code: 0))) // Still failing
717+
718+
let checker = POSTabEligibilityChecker(siteID: siteID,
719+
siteSettings: siteSettings,
720+
pluginsService: pluginsService,
721+
stores: stores)
722+
723+
// When
724+
let result = try await checker.refreshEligibility(ineligibleReason: .featureSwitchSyncFailure)
725+
726+
// Then - Should check eligibility again (still failing)
727+
#expect(result == .ineligible(reason: .featureSwitchSyncFailure))
728+
}
729+
730+
@Test func refreshEligibility_checks_eligibility_for_selfDeallocated() async throws {
731+
// Given
732+
setupCountry(country: .us, currency: .USD)
733+
setupWooCommerceVersion("9.6.0")
734+
setupPOSFeatureEnabled(.success(true))
735+
736+
let checker = POSTabEligibilityChecker(siteID: siteID,
737+
siteSettings: siteSettings,
738+
pluginsService: pluginsService,
739+
stores: stores)
740+
741+
// When
742+
let result = try await checker.refreshEligibility(ineligibleReason: .selfDeallocated)
743+
744+
// Then - Should check eligibility again (now eligible)
745+
#expect(result == .eligible)
746+
}
573747
}
574748

575749
private extension POSTabEligibilityCheckerTests {

0 commit comments

Comments
 (0)