diff --git a/Modules/Sources/Experiments/DefaultFeatureFlagService.swift b/Modules/Sources/Experiments/DefaultFeatureFlagService.swift index 08d57cb8f29..e7cb2279872 100644 --- a/Modules/Sources/Experiments/DefaultFeatureFlagService.swift +++ b/Modules/Sources/Experiments/DefaultFeatureFlagService.swift @@ -100,8 +100,6 @@ public struct DefaultFeatureFlagService: FeatureFlagService { return true case .pointOfSaleHistoricalOrdersi1: return buildConfig == .localDeveloper || buildConfig == .alpha - case .applicationPasswordExperiment: - return buildConfig == .localDeveloper || buildConfig == .alpha case .pointOfSaleLocalCatalogi1: return buildConfig == .localDeveloper || buildConfig == .alpha default: diff --git a/Modules/Sources/Experiments/FeatureFlag.swift b/Modules/Sources/Experiments/FeatureFlag.swift index 797ad5e0495..5f1b9be3c07 100644 --- a/Modules/Sources/Experiments/FeatureFlag.swift +++ b/Modules/Sources/Experiments/FeatureFlag.swift @@ -207,10 +207,6 @@ public enum FeatureFlag: Int { /// case pointOfSaleHistoricalOrdersi1 - /// Enables switching Jetpack requests to use application password - /// - case applicationPasswordExperiment - /// Enables Local Catalog i1 in Point of Sale. /// It syncs products and variations to local storage and display them in POS for quick access. /// diff --git a/Modules/Sources/Networking/Remote/FeatureFlagRemote.swift b/Modules/Sources/Networking/Remote/FeatureFlagRemote.swift index 48b9355197b..529cae6a297 100644 --- a/Modules/Sources/Networking/Remote/FeatureFlagRemote.swift +++ b/Modules/Sources/Networking/Remote/FeatureFlagRemote.swift @@ -30,6 +30,7 @@ public enum RemoteFeatureFlag: Decodable { case storeCreationCompleteNotification case hardcodedPlanUpgradeDetailsMilestone1AreAccurate case pointOfSale + case appPasswordsForJetpackSites init?(rawValue: String) { switch rawValue { @@ -39,6 +40,8 @@ public enum RemoteFeatureFlag: Decodable { self = .hardcodedPlanUpgradeDetailsMilestone1AreAccurate case "woo_pos": self = .pointOfSale + case "woo_app_passwords_for_jetpack_sites": + self = .appPasswordsForJetpackSites default: return nil } diff --git a/RELEASE-NOTES.txt b/RELEASE-NOTES.txt index f35374b1dd3..863cae81b7c 100644 --- a/RELEASE-NOTES.txt +++ b/RELEASE-NOTES.txt @@ -5,6 +5,7 @@ ----- - [*] Order details: Display only physical items in the Shipping Labels section. [https://github.com/woocommerce/woocommerce-ios/pull/16127] - [internal] Address deprecated view modifiers usage following iOS17 API updates [https://github.com/woocommerce/woocommerce-ios/pull/16080] +- [***] Performance improvements for users logging in with WordPress.com accounts [https://github.com/woocommerce/woocommerce-ios/pull/16126] - [internal] Fix back swipe gesture detection compatibility for iOS26 [https://github.com/woocommerce/woocommerce-ios/pull/16133] - [internal] POS Modularization: Removed direct ServiceLocator usage within POS by requiring complex Woo app target dependencies to be injected via POS dependency protocols, and moved reusable dependencies to WooFoundation and Yosemite [https://github.com/woocommerce/woocommerce-ios/pull/16132] - [*] Workaround to make custom field decoding more reliable with sites which return incorrectly formatted meta_data for products, variations, and orders. [https://github.com/woocommerce/woocommerce-ios/pull/16141] diff --git a/WooCommerce/Classes/ViewRelated/Dashboard/Settings/Beta features/ApplicationPasswordsExperimentState.swift b/WooCommerce/Classes/ViewRelated/Dashboard/Settings/Beta features/ApplicationPasswordsExperimentState.swift index bff04ed9f0e..f4b36b04fe3 100644 --- a/WooCommerce/Classes/ViewRelated/Dashboard/Settings/Beta features/ApplicationPasswordsExperimentState.swift +++ b/WooCommerce/Classes/ViewRelated/Dashboard/Settings/Beta features/ApplicationPasswordsExperimentState.swift @@ -84,19 +84,17 @@ final class ApplicationPasswordsExperimentAvailabilityChecker: ApplicationPasswo } } + @MainActor func fetchAvailability() async -> Bool { - await withCheckedContinuation { continuation in - //TODO: - put the remote FF checking here - //For now rely on local FF for mocked value to avoid unwanted exposure - let mockResultValue = ServiceLocator.featureFlagService.isFeatureFlagEnabled( - .applicationPasswordExperiment - ) - - DispatchQueue.main.asyncAfter(deadline: .now() + 3) { - continuation.resume(returning: mockResultValue) - } - - cachedRemoteFFValue = mockResultValue + let isEnabled = await withCheckedContinuation { continuation in + stores.dispatch(FeatureFlagAction.isRemoteFeatureFlagEnabled( + .appPasswordsForJetpackSites, + defaultValue: false + ) { isEnabled in + continuation.resume(returning: isEnabled) + }) } + cachedRemoteFFValue = isEnabled + return isEnabled } } diff --git a/WooCommerce/WooCommerceTests/ViewRelated/Settings/ApplicationPasswordsExperimentAvailabilityCheckerTests.swift b/WooCommerce/WooCommerceTests/ViewRelated/Settings/ApplicationPasswordsExperimentAvailabilityCheckerTests.swift index e46879a0d7a..111d12c01db 100644 --- a/WooCommerce/WooCommerceTests/ViewRelated/Settings/ApplicationPasswordsExperimentAvailabilityCheckerTests.swift +++ b/WooCommerce/WooCommerceTests/ViewRelated/Settings/ApplicationPasswordsExperimentAvailabilityCheckerTests.swift @@ -4,7 +4,7 @@ import Yosemite final class ApplicationPasswordsExperimentAvailabilityCheckerTests: XCTestCase { private var availabilityChecker: ApplicationPasswordsExperimentAvailabilityCheckerProtocol! - private var stores: StoresManager! + private var stores: MockStoresManager! private var userDefaults: UserDefaults! override func tearDown() { @@ -16,7 +16,7 @@ final class ApplicationPasswordsExperimentAvailabilityCheckerTests: XCTestCase { private func setupEnvironment( isWPComAuthenticated: Bool, - isRemoteFFEnabled: Bool + cachedRemoteFFEnabled: Bool ) throws { stores = MockStoresManager( sessionManager: .makeForTesting( @@ -26,7 +26,7 @@ final class ApplicationPasswordsExperimentAvailabilityCheckerTests: XCTestCase { ) userDefaults = try XCTUnwrap(UserDefaults(suiteName: "TestingSuite")) - userDefaults[.applicationPasswordsExperimentRemoteFFValue] = isRemoteFFEnabled + userDefaults[.applicationPasswordsExperimentRemoteFFValue] = cachedRemoteFFEnabled availabilityChecker = ApplicationPasswordsExperimentAvailabilityChecker( userDefaults: userDefaults, @@ -34,23 +34,85 @@ final class ApplicationPasswordsExperimentAvailabilityCheckerTests: XCTestCase { ) } - func test_when_wpcom_authenticated_and_remote_ff_enabled_then_isAvailable_returns_true() throws { - try setupEnvironment(isWPComAuthenticated: true, isRemoteFFEnabled: true) + func test_when_wpcom_authenticated_and_cached_remote_ff_enabled_then_isAvailable_returns_true() throws { + try setupEnvironment(isWPComAuthenticated: true, cachedRemoteFFEnabled: true) XCTAssertTrue(availabilityChecker.isAvailable) } - func test_when_wpcom_authenticated_and_remote_ff_disabled_then_isAvailable_returns_false() throws { - try setupEnvironment(isWPComAuthenticated: true, isRemoteFFEnabled: false) + func test_when_wpcom_authenticated_and_cached_remote_ff_disabled_then_isAvailable_returns_false() throws { + try setupEnvironment(isWPComAuthenticated: true, cachedRemoteFFEnabled: false) XCTAssertFalse(availabilityChecker.isAvailable) } - func test_when_not_wpcom_authenticated_and_remote_ff_enabled_then_isAvailable_returns_false() throws { - try setupEnvironment(isWPComAuthenticated: false, isRemoteFFEnabled: true) + func test_when_not_wpcom_authenticated_and_cached_remote_ff_enabled_then_isAvailable_returns_false() throws { + try setupEnvironment(isWPComAuthenticated: false, cachedRemoteFFEnabled: true) XCTAssertFalse(availabilityChecker.isAvailable) } - func test_when_not_wpcom_authenticated_and_remote_ff_disabled_then_isAvailable_returns_false() throws { - try setupEnvironment(isWPComAuthenticated: false, isRemoteFFEnabled: false) + func test_when_not_wpcom_authenticated_and_cached_remote_ff_disabled_then_isAvailable_returns_false() throws { + try setupEnvironment(isWPComAuthenticated: false, cachedRemoteFFEnabled: false) XCTAssertFalse(availabilityChecker.isAvailable) } + + @MainActor + func test_when_cached_flag_is_disabled_and_remote_flag_is_enabled_then_fetchAvailability_returns_true() async throws { + // Given + stores = MockStoresManager( + sessionManager: .makeForTesting( + authenticated: true, + isWPCom: true + ) + ) + stores.whenReceivingAction(ofType: FeatureFlagAction.self) { action in + switch action { + case .isRemoteFeatureFlagEnabled(_, _, let completion): + completion(true) + } + } + + userDefaults = try XCTUnwrap(UserDefaults(suiteName: UUID().uuidString)) + userDefaults[.applicationPasswordsExperimentRemoteFFValue] = false + + availabilityChecker = ApplicationPasswordsExperimentAvailabilityChecker( + userDefaults: userDefaults, + stores: stores + ) + + // When + let availability = await availabilityChecker.fetchAvailability() + + // Then + XCTAssertTrue(availability) + } + + @MainActor + func test_when_cached_flag_is_enabled_and_remote_flag_is_disabled_then_fetchAvailability_returns_false() async throws { + // Given + stores = MockStoresManager( + sessionManager: .makeForTesting( + authenticated: true, + isWPCom: true + ) + ) + stores.whenReceivingAction(ofType: FeatureFlagAction.self) { action in + switch action { + case .isRemoteFeatureFlagEnabled(_, _, let completion): + completion(false) + } + } + + userDefaults = try XCTUnwrap(UserDefaults(suiteName: UUID().uuidString)) + userDefaults[.applicationPasswordsExperimentRemoteFFValue] = true + + availabilityChecker = ApplicationPasswordsExperimentAvailabilityChecker( + userDefaults: userDefaults, + stores: stores + ) + + // When + let availability = await availabilityChecker.fetchAvailability() + + // Then + XCTAssertFalse(availability) + } }