@@ -2,6 +2,7 @@ import Testing
22import Foundation
33import Yosemite
44@testable import WooCommerce
5+ import UIKit
56
67@MainActor
78struct ForegroundPOSCatalogSyncDispatcherTests {
@@ -29,7 +30,7 @@ struct ForegroundPOSCatalogSyncDispatcherTests {
2930 @Test
3031 func start_whenFeatureFlagEnabled_createsTimer( ) async throws {
3132 // When
32- sut. start ( )
33+ await sut. start ( )
3334
3435 // Then
3536 #expect( timerProvider. createdTimers. count == 1 )
@@ -39,18 +40,10 @@ struct ForegroundPOSCatalogSyncDispatcherTests {
3940 @Test
4041 func start_whenFeatureFlagDisabled_doesNotCreateTimer( ) async throws {
4142 // Given
42- let disabledFlags = MockFeatureFlagService ( )
43- disabledFlags. isFeatureFlagEnabledReturnValue [ . pointOfSaleLocalCatalogi1] = false
44- let dispatcher = ForegroundPOSCatalogSyncDispatcher (
45- notificationCenter: notificationCenter,
46- timerProvider: timerProvider,
47- featureFlagService: disabledFlags,
48- stores: stores,
49- isAppActive: { true }
50- )
43+ featureFlags. isFeatureFlagEnabledReturnValue [ . pointOfSaleLocalCatalogi1] = false
5144
5245 // When
53- dispatcher . start ( )
46+ await sut . start ( )
5447
5548 // Then
5649 #expect( timerProvider. createdTimers. isEmpty)
@@ -62,7 +55,7 @@ struct ForegroundPOSCatalogSyncDispatcherTests {
6255 let coordinator = MockPOSCatalogSyncCoordinator ( )
6356 stores. testPOSCatalogSyncCoordinator = coordinator
6457
65- sut. start ( )
58+ await sut. start ( )
6659 let timer = try #require( timerProvider. createdTimers. first)
6760
6861 // When - fire timer and wait for sync to be called
@@ -82,19 +75,10 @@ struct ForegroundPOSCatalogSyncDispatcherTests {
8275 func timerFires_whenNoDefaultStore_skipsSync( ) async throws {
8376 // Given
8477 let coordinator = MockPOSCatalogSyncCoordinator ( )
85- let noStoreSession = SessionManager . testingInstance
86- noStoreSession. setStoreId ( nil )
87- let noStoreStores = MockStoresManager ( sessionManager: noStoreSession)
88- noStoreStores. testPOSCatalogSyncCoordinator = coordinator
89- let dispatcher = ForegroundPOSCatalogSyncDispatcher (
90- notificationCenter: notificationCenter,
91- timerProvider: timerProvider,
92- featureFlagService: featureFlags,
93- stores: noStoreStores,
94- isAppActive: { true }
95- )
78+ sessionManager. setStoreId ( nil )
79+ stores. testPOSCatalogSyncCoordinator = coordinator
9680
97- dispatcher . start ( )
81+ await sut . start ( )
9882 let timer = try #require( timerProvider. createdTimers. first)
9983
10084 // When
@@ -111,7 +95,7 @@ struct ForegroundPOSCatalogSyncDispatcherTests {
11195 coordinator. performSmartSyncResult = . failure( POSCatalogSyncError . syncAlreadyInProgress ( siteID: 123 ) )
11296 stores. testPOSCatalogSyncCoordinator = coordinator
11397
114- sut. start ( )
98+ await sut. start ( )
11599 let timer = try #require( timerProvider. createdTimers. first)
116100
117101 // When - fire timer and wait for sync to be called
@@ -129,13 +113,13 @@ struct ForegroundPOSCatalogSyncDispatcherTests {
129113 @Test
130114 func start_whenSiteChanges_resetsAndRestartsSync( ) async throws {
131115 // Given - start with site 123
132- sut. start ( )
116+ await sut. start ( )
133117 let firstTimer = try #require( timerProvider. createdTimers. first)
134118 #expect( timerProvider. createdTimers. count == 1 )
135119
136120 // When - change site to 456 and start again
137121 sessionManager. setStoreId ( 456 )
138- sut. start ( )
122+ await sut. start ( )
139123
140124 // Then - old timer cancelled, new timer created
141125 #expect( firstTimer. isCancelled == true )
@@ -160,18 +144,76 @@ struct ForegroundPOSCatalogSyncDispatcherTests {
160144 @Test
161145 func defaultSiteNotification_stopsDispatcher( ) async throws {
162146 // Given
163- sut. start ( )
147+ await sut. start ( )
164148 let timer = try #require( timerProvider. createdTimers. first)
165149 #expect( timer. isResumed == true )
166150
167151 // When - default site changes
168- notificationCenter. post ( name: . StoresManagerDidUpdateDefaultSite, object: nil )
152+ await withCheckedContinuation { continuation in
153+ timer. onCancelled = { continuation. resume ( ) }
169154
170- // Then - dispatcher stops
171- #expect ( timer . isCancelled == true )
155+ notificationCenter . post ( name : . StoresManagerDidUpdateDefaultSite , object : nil )
156+ }
172157
173158 // Verify dispatcher can restart after notification
174- sut. start ( )
159+ await sut. start ( )
160+ #expect( timerProvider. createdTimers. count == 2 )
161+ #expect( timerProvider. createdTimers. last? . isResumed == true )
162+ }
163+
164+ @Test
165+ func didBecomeActiveNotification_startsTimer( ) async throws {
166+ // Given - dispatcher started but app not active initially
167+ let inactiveDispatcher = ForegroundPOSCatalogSyncDispatcher (
168+ notificationCenter: notificationCenter,
169+ timerProvider: timerProvider,
170+ featureFlagService: featureFlags,
171+ stores: stores,
172+ isAppActive: { false }
173+ )
174+ await inactiveDispatcher. start ( )
175+
176+ // Then - no timer created yet
177+ #expect( timerProvider. createdTimers. isEmpty)
178+
179+ // When - app becomes active, wait for timer to be created
180+ await withCheckedContinuation { continuation in
181+ timerProvider. onTimerCreated = { continuation. resume ( ) }
182+
183+ notificationCenter. post ( name: UIApplication . didBecomeActiveNotification, object: nil )
184+ }
185+
186+ // Then - timer should be created and started
187+ #expect( timerProvider. createdTimers. count == 1 )
188+ #expect( timerProvider. createdTimers. first? . isResumed == true )
189+ }
190+
191+ @Test
192+ func didEnterBackgroundNotification_stopsTimer( ) async throws {
193+ // Given
194+ await sut. start ( )
195+ let timer = try #require( timerProvider. createdTimers. first)
196+ #expect( timer. isResumed == true )
197+ #expect( timer. isCancelled == false )
198+
199+ // When - app enters background
200+ await withCheckedContinuation { continuation in
201+ timer. onCancelled = { continuation. resume ( ) }
202+
203+ notificationCenter. post ( name: UIApplication . didEnterBackgroundNotification, object: nil )
204+ }
205+
206+ // Then - timer should be cancelled
207+ #expect( timer. isCancelled == true )
208+
209+ // When - app becomes active again, wait for new timer
210+ await withCheckedContinuation { continuation in
211+ timerProvider. onTimerCreated = { continuation. resume ( ) }
212+
213+ notificationCenter. post ( name: UIApplication . didBecomeActiveNotification, object: nil )
214+ }
215+
216+ // Then - new timer should be created
175217 #expect( timerProvider. createdTimers. count == 2 )
176218 #expect( timerProvider. createdTimers. last? . isResumed == true )
177219 }
@@ -182,6 +224,7 @@ struct ForegroundPOSCatalogSyncDispatcherTests {
182224private final class MockDispatchTimer : DispatchTimerProtocol {
183225 private( set) var isResumed = false
184226 private( set) var isCancelled = false
227+ var onCancelled : ( ) -> Void = { }
185228 private var eventHandler : ( ( ) -> Void ) ?
186229
187230 func schedule( deadline: DispatchTime , repeating: Double , leeway: DispatchTimeInterval ) {
@@ -198,6 +241,7 @@ private final class MockDispatchTimer: DispatchTimerProtocol {
198241
199242 func cancel( ) {
200243 isCancelled = true
244+ onCancelled ( )
201245 }
202246
203247 /// Test helper to manually fire the timer
@@ -208,10 +252,12 @@ private final class MockDispatchTimer: DispatchTimerProtocol {
208252
209253private final class MockDispatchTimerProvider : DispatchTimerProviding {
210254 private( set) var createdTimers : [ MockDispatchTimer ] = [ ]
255+ var onTimerCreated : ( ) -> Void = { }
211256
212257 func makeTimer( queue: DispatchQueue ) -> DispatchTimerProtocol {
213258 let timer = MockDispatchTimer ( )
214259 createdTimers. append ( timer)
260+ onTimerCreated ( )
215261 return timer
216262 }
217263}
0 commit comments