From 7bb5ded534afdd8a7923eb1a9d5038320b2de20c Mon Sep 17 00:00:00 2001 From: Josh Heald Date: Mon, 3 Nov 2025 12:02:34 +0000 Subject: [PATCH 1/7] Add catalog refresh warning --- .../Models/PointOfSaleAggregateModel.swift | 39 +++++++++- .../Presentation/ItemListView.swift | 65 +++++++++++++--- .../PointOfSaleEntryPointView.swift | 5 +- .../PointOfSale/Utils/PreviewHelpers.swift | 10 ++- .../Tools/POS/POSCatalogSyncCoordinator.swift | 22 ++++++ .../Mocks/MockPOSCatalogSyncCoordinator.swift | 6 ++ .../POS/POSCatalogSyncCoordinatorTests.swift | 74 +++++++++++++++++++ ...egroundPOSCatalogSyncDispatcherTests.swift | 4 + 8 files changed, 211 insertions(+), 14 deletions(-) diff --git a/Modules/Sources/PointOfSale/Models/PointOfSaleAggregateModel.swift b/Modules/Sources/PointOfSale/Models/PointOfSaleAggregateModel.swift index 815b0e63f1d..0c347fa9cd5 100644 --- a/Modules/Sources/PointOfSale/Models/PointOfSaleAggregateModel.swift +++ b/Modules/Sources/PointOfSale/Models/PointOfSaleAggregateModel.swift @@ -60,6 +60,7 @@ protocol PointOfSaleAggregateModelProtocol { private var cardReaderDisconnection: AnyCancellable? private let soundPlayer: PointOfSaleSoundPlayerProtocol + private let isLocalCatalogEligible: Bool private var cancellables: Set = [] @@ -76,6 +77,18 @@ protocol PointOfSaleAggregateModelProtocol { _viewStateCoordinator } + // Track stale sync warning (only relevant when using local catalog) + var isSyncStale: Bool = false + var isStaleSyncWarningDismissed: Bool = false + + var showStaleSyncWarning: Bool { + // Only show warning if using local catalog + guard isLocalCatalogEligible else { + return false + } + return isSyncStale && !isStaleSyncWarningDismissed + } + init(entryPointController: POSEntryPointController, itemsController: PointOfSaleItemsControllerProtocol, purchasableItemsSearchController: PointOfSaleSearchingItemsControllerProtocol, @@ -92,7 +105,8 @@ protocol PointOfSaleAggregateModelProtocol { soundPlayer: PointOfSaleSoundPlayerProtocol = PointOfSaleSoundPlayer(), paymentState: PointOfSalePaymentState = .idle, siteID: Int64, - catalogSyncCoordinator: POSCatalogSyncCoordinatorProtocol? = nil) { + catalogSyncCoordinator: POSCatalogSyncCoordinatorProtocol? = nil, + isLocalCatalogEligible: Bool = false) { self.entryPointController = entryPointController self.purchasableItemsController = itemsController self.purchasableItemsSearchController = purchasableItemsSearchController @@ -110,6 +124,7 @@ protocol PointOfSaleAggregateModelProtocol { self.soundPlayer = soundPlayer self.siteID = siteID self.catalogSyncCoordinator = catalogSyncCoordinator + self.isLocalCatalogEligible = isLocalCatalogEligible publishCardReaderConnectionStatus() publishPaymentMessages() @@ -632,6 +647,28 @@ private extension PointOfSaleAggregateModel { } } +// MARK: - Stale Sync Warning +extension PointOfSaleAggregateModel { + var staleSyncThresholdDays: Int { + Constants.staleSyncThresholdDays + } + + func dismissStaleSyncWarning() { + isStaleSyncWarningDismissed = true + } + + func checkStaleSyncStatus() async { + guard let catalogSyncCoordinator else { return } + isSyncStale = await catalogSyncCoordinator.isSyncStale(for: siteID, maxDays: Constants.staleSyncThresholdDays) + } +} + +// MARK: - Constants +private enum Constants { + /// Number of days before showing a stale catalog sync warning + static let staleSyncThresholdDays: Int = 7 +} + #if DEBUG extension PointOfSaleAggregateModel { func setPreviewState(paymentState: PointOfSalePaymentState, inlineMessage: PointOfSaleCardPresentPaymentMessageType?) { diff --git a/Modules/Sources/PointOfSale/Presentation/ItemListView.swift b/Modules/Sources/PointOfSale/Presentation/ItemListView.swift index 48a1d6260d5..e33c40abfb1 100644 --- a/Modules/Sources/PointOfSale/Presentation/ItemListView.swift +++ b/Modules/Sources/PointOfSale/Presentation/ItemListView.swift @@ -161,20 +161,49 @@ struct ItemListView: View { @ViewBuilder private func listView(itemListType: ItemListType) -> some View { - ItemList( - itemsController: itemsController(itemListType), - node: .root, - itemActionHandler: actionHandler(itemListType), - willLoadMore: { - analyticsTracker.trackNextPageWillLoad() + VStack(spacing: 0) { + // Stale sync warning banner + if posModel.showStaleSyncWarning { + staleSyncWarningBanner + .padding(.horizontal, POSPadding.medium) + .padding(.vertical, POSPadding.small) + .transition(.move(edge: .top).combined(with: .opacity)) } - ) - .refreshable { - analyticsTracker.trackRefresh() - await itemsController(itemListType).refreshItems(base: .root) + + ItemList( + itemsController: itemsController(itemListType), + node: .root, + itemActionHandler: actionHandler(itemListType), + willLoadMore: { + analyticsTracker.trackNextPageWillLoad() + } + ) + .refreshable { + analyticsTracker.trackRefresh() + await itemsController(itemListType).refreshItems(base: .root) + } + } + .task { + // Check stale sync status when view appears + await posModel.checkStaleSyncStatus() } } + @ViewBuilder + private var staleSyncWarningBanner: some View { + POSNoticeView( + title: Localization.staleSyncWarningTitle, + icon: Image(systemName: "info.circle"), + onDismiss: { + withAnimation { + posModel.dismissStaleSyncWarning() + } + }, content: { + Text(Localization.staleSyncWarningDescription(days: posModel.staleSyncThresholdDays)) + .font(POSFontStyle.posBodyMediumRegular()) + }) + } + private func actionHandler(_ itemListType: ItemListType) -> POSItemActionHandler { POSItemActionHandlerFactory.itemActionHandler( itemListType: itemListType, @@ -400,6 +429,22 @@ private extension ItemListView { value: "Coupons", comment: "Title of the button at the top of Point of Sale to switch to Coupons list." ) + + static let staleSyncWarningTitle = NSLocalizedString( + "pos.itemlistview.staleSyncWarning.title", + value: "Refresh catalog", + comment: "Warning title shown when the product catalog hasn't synced in several days" + ) + + static let staleSyncWarningDescriptionFormat = NSLocalizedString( + "pos.itemlistview.staleSyncWarning.description", + value: "The catalog hasn't been synced in the last %1$ld days. Please ensure you're connected to the internet and sync again in POS Settings.", + comment: "Message shown when the product catalog hasn't synced in the specified number of days. %1$ld is the number of days. Reads like: The catalog hasn't been synced in the last 7 days." + ) + + static func staleSyncWarningDescription(days: Int) -> String { + String.localizedStringWithFormat(staleSyncWarningDescriptionFormat, days) + } } } diff --git a/Modules/Sources/PointOfSale/Presentation/PointOfSaleEntryPointView.swift b/Modules/Sources/PointOfSale/Presentation/PointOfSaleEntryPointView.swift index 4c41fb2eb99..66aaaa45c56 100644 --- a/Modules/Sources/PointOfSale/Presentation/PointOfSaleEntryPointView.swift +++ b/Modules/Sources/PointOfSale/Presentation/PointOfSaleEntryPointView.swift @@ -42,6 +42,7 @@ public struct PointOfSaleEntryPointView: View { private let services: POSDependencyProviding private let siteID: Int64 private let catalogSyncCoordinator: POSCatalogSyncCoordinatorProtocol? + private let isLocalCatalogEligible: Bool /// periphery: ignore - public in preparation of move to POS module public init(siteID: Int64, @@ -132,6 +133,7 @@ public struct PointOfSaleEntryPointView: View { self.services = services self.siteID = siteID self.catalogSyncCoordinator = catalogSyncCoordinator + self.isLocalCatalogEligible = isLocalCatalogEligible } public var body: some View { @@ -162,7 +164,8 @@ public struct PointOfSaleEntryPointView: View { popularPurchasableItemsController: popularPurchasableItemsController, barcodeScanService: barcodeScanService, siteID: siteID, - catalogSyncCoordinator: catalogSyncCoordinator) + catalogSyncCoordinator: catalogSyncCoordinator, + isLocalCatalogEligible: isLocalCatalogEligible) } .environment(\.posAnalytics, services.analytics) .environment(\.posCurrencyProvider, services.currency) diff --git a/Modules/Sources/PointOfSale/Utils/PreviewHelpers.swift b/Modules/Sources/PointOfSale/Utils/PreviewHelpers.swift index 50904721b16..0a35c0c294b 100644 --- a/Modules/Sources/PointOfSale/Utils/PreviewHelpers.swift +++ b/Modules/Sources/PointOfSale/Utils/PreviewHelpers.swift @@ -217,7 +217,8 @@ struct POSPreviewHelpers { barcodeScanService: PointOfSaleBarcodeScanServiceProtocol = PointOfSalePreviewBarcodeScanService(), analytics: POSAnalyticsProviding = EmptyPOSAnalytics(), siteID: Int64 = 1, - catalogSyncCoordinator: POSCatalogSyncCoordinatorProtocol? = nil + catalogSyncCoordinator: POSCatalogSyncCoordinatorProtocol? = nil, + isLocalCatalogEligible: Bool = false ) -> PointOfSaleAggregateModel { return PointOfSaleAggregateModel( entryPointController: POSEntryPointController(eligibilityChecker: PointOfSalePreviewTabEligibilityChecker()), @@ -234,7 +235,8 @@ struct POSPreviewHelpers { popularPurchasableItemsController: popularItemsController, barcodeScanService: barcodeScanService, siteID: siteID, - catalogSyncCoordinator: catalogSyncCoordinator + catalogSyncCoordinator: catalogSyncCoordinator, + isLocalCatalogEligible: isLocalCatalogEligible ) } @@ -635,6 +637,10 @@ final class POSPreviewCatalogSyncCoordinator: POSCatalogSyncCoordinatorProtocol func loadLastFullSyncState(for siteID: Int64) async -> POSCatalogSyncState { return fullSyncStateModel.state[siteID] ?? .syncCompleted(siteID: siteID) } + + func isSyncStale(for siteID: Int64, maxDays: Int) async -> Bool { + return false + } } #endif diff --git a/Modules/Sources/Yosemite/Tools/POS/POSCatalogSyncCoordinator.swift b/Modules/Sources/Yosemite/Tools/POS/POSCatalogSyncCoordinator.swift index 44d28d67b47..66d4946cb09 100644 --- a/Modules/Sources/Yosemite/Tools/POS/POSCatalogSyncCoordinator.swift +++ b/Modules/Sources/Yosemite/Tools/POS/POSCatalogSyncCoordinator.swift @@ -34,6 +34,13 @@ public protocol POSCatalogSyncCoordinatorProtocol { /// Returns the last known full sync state for a site /// If no state is cached, determines state from lastSyncDate func loadLastFullSyncState(for siteID: Int64) async -> POSCatalogSyncState + + /// Checks if the last sync is older than the specified number of days + /// - Parameters: + /// - siteID: The site ID to check + /// - maxDays: Maximum number of days before a sync is considered stale + /// - Returns: True if the last sync is older than the specified days or if there has been no sync + func isSyncStale(for siteID: Int64, maxDays: Int) async -> Bool } public extension POSCatalogSyncCoordinatorProtocol { @@ -305,6 +312,21 @@ public actor POSCatalogSyncCoordinator: POSCatalogSyncCoordinatorProtocol { fullSyncStateModel.state[siteID] = state return state } + + public func isSyncStale(for siteID: Int64, maxDays: Int) async -> Bool { + // Check only the last full sync date, incremental syncs don't refresh well enough to consider non-stale. + guard let lastFullSync = await lastFullSyncDate(for: siteID) else { + // If we've never done a full sync, we're stale. + return true + } + + guard let thresholdDate = Calendar.current.date(byAdding: .day, value: -maxDays, to: Date()) else { + // This shouldn't fail, and if it does, we can assume the catalog is fine + return false + } + + return lastFullSync < thresholdDate + } } // MARK: - Syncing State diff --git a/Modules/Tests/PointOfSaleTests/Mocks/MockPOSCatalogSyncCoordinator.swift b/Modules/Tests/PointOfSaleTests/Mocks/MockPOSCatalogSyncCoordinator.swift index d0da6326193..3b247cef2ab 100644 --- a/Modules/Tests/PointOfSaleTests/Mocks/MockPOSCatalogSyncCoordinator.swift +++ b/Modules/Tests/PointOfSaleTests/Mocks/MockPOSCatalogSyncCoordinator.swift @@ -68,4 +68,10 @@ final class MockPOSCatalogSyncCoordinator: POSCatalogSyncCoordinatorProtocol { func loadLastFullSyncState(for siteID: Int64) async -> POSCatalogSyncState { return fullSyncStateModel.state[siteID] ?? .syncNeverDone(siteID: siteID) } + + var isSyncStaleResult: Bool = false + + func isSyncStale(for siteID: Int64, maxDays: Int) async -> Bool { + return isSyncStaleResult + } } diff --git a/Modules/Tests/YosemiteTests/Tools/POS/POSCatalogSyncCoordinatorTests.swift b/Modules/Tests/YosemiteTests/Tools/POS/POSCatalogSyncCoordinatorTests.swift index b4542ee795f..f9a520c0d83 100644 --- a/Modules/Tests/YosemiteTests/Tools/POS/POSCatalogSyncCoordinatorTests.swift +++ b/Modules/Tests/YosemiteTests/Tools/POS/POSCatalogSyncCoordinatorTests.swift @@ -860,4 +860,78 @@ extension POSCatalogSyncCoordinatorTests { // Then - sync should proceed (exactly at 30-day boundary is still eligible) #expect(mockIncrementalSyncService.startIncrementalSyncCallCount == 1) } + + // MARK: - isSyncStale Tests + + @Test func test_isSyncStale_returns_true_when_no_full_sync_performed() async throws { + // Given - no full sync date set + + // When + let isStale = await sut.isSyncStale(for: sampleSiteID, maxDays: 7) + + // Then + #expect(isStale == true) + } + + @Test func test_isSyncStale_returns_false_when_full_sync_is_recent() async throws { + // Given - last full sync was 3 days ago + let threeDaysAgo = Calendar.current.date(byAdding: .day, value: -3, to: Date())! + try createSiteInDatabase(siteID: sampleSiteID, lastFullSyncDate: threeDaysAgo) + + // When + let isStale = await sut.isSyncStale(for: sampleSiteID, maxDays: 7) + + // Then + #expect(isStale == false) + } + + @Test func test_isSyncStale_returns_true_when_full_sync_is_old() async throws { + // Given - last full sync was 10 days ago + let tenDaysAgo = Calendar.current.date(byAdding: .day, value: -10, to: Date())! + try createSiteInDatabase(siteID: sampleSiteID, lastFullSyncDate: tenDaysAgo) + + // When + let isStale = await sut.isSyncStale(for: sampleSiteID, maxDays: 7) + + // Then + #expect(isStale == true) + } + + @Test func test_isSyncStale_ignores_incremental_sync_date() async throws { + // Given - incremental sync was recent, but full sync was old + let yesterday = Calendar.current.date(byAdding: .day, value: -1, to: Date())! + let tenDaysAgo = Calendar.current.date(byAdding: .day, value: -10, to: Date())! + try createSiteInDatabase(siteID: sampleSiteID, lastFullSyncDate: tenDaysAgo, lastIncrementalSyncDate: yesterday) + + // When + let isStale = await sut.isSyncStale(for: sampleSiteID, maxDays: 7) + + // Then - should only check full sync date + #expect(isStale == true) + } + + @Test func test_isSyncStale_boundary_within_threshold() async throws { + // Given - last full sync was 6 days and 23 hours ago (just under 7 days) + let justUnderSevenDays = Calendar.current.date(byAdding: .day, value: -6, to: Date())! + .addingTimeInterval(-23 * 60 * 60) // minus 23 hours + try createSiteInDatabase(siteID: sampleSiteID, lastFullSyncDate: justUnderSevenDays) + + // When + let isStale = await sut.isSyncStale(for: sampleSiteID, maxDays: 7) + + // Then - just under threshold should not be stale + #expect(isStale == false) + } + + @Test func test_isSyncStale_boundary_past_threshold() async throws { + // Given - last full sync was 8 days ago (clearly past 7 days) + let eightDaysAgo = Calendar.current.date(byAdding: .day, value: -8, to: Date())! + try createSiteInDatabase(siteID: sampleSiteID, lastFullSyncDate: eightDaysAgo) + + // When + let isStale = await sut.isSyncStale(for: sampleSiteID, maxDays: 7) + + // Then - past threshold should be stale + #expect(isStale == true) + } } diff --git a/WooCommerce/WooCommerceTests/Tools/ForegroundPOSCatalogSyncDispatcherTests.swift b/WooCommerce/WooCommerceTests/Tools/ForegroundPOSCatalogSyncDispatcherTests.swift index 1987a72bd13..e8bfaced280 100644 --- a/WooCommerce/WooCommerceTests/Tools/ForegroundPOSCatalogSyncDispatcherTests.swift +++ b/WooCommerce/WooCommerceTests/Tools/ForegroundPOSCatalogSyncDispatcherTests.swift @@ -297,4 +297,8 @@ private final class MockPOSCatalogSyncCoordinator: POSCatalogSyncCoordinatorProt func loadLastFullSyncState(for siteID: Int64) async -> POSCatalogSyncState { return fullSyncStateModel.state[siteID] ?? .syncNeverDone(siteID: siteID) } + + func isSyncStale(for siteID: Int64, maxDays: Int) async -> Bool { + return false + } } From 0c53ee1cad9b0ca2e3ca4aa402a4f15ebe172f5c Mon Sep 17 00:00:00 2001 From: Josh Heald Date: Mon, 3 Nov 2025 13:47:31 +0000 Subject: [PATCH 2/7] Recheck catalog freshness when leaving settings The user may have synced their catalog in settings. --- .../PointOfSale/Presentation/PointOfSaleDashboardView.swift | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Modules/Sources/PointOfSale/Presentation/PointOfSaleDashboardView.swift b/Modules/Sources/PointOfSale/Presentation/PointOfSaleDashboardView.swift index 67ab07385c1..d9442228dd8 100644 --- a/Modules/Sources/PointOfSale/Presentation/PointOfSaleDashboardView.swift +++ b/Modules/Sources/PointOfSale/Presentation/PointOfSaleDashboardView.swift @@ -143,6 +143,12 @@ struct PointOfSaleDashboardView: View { .posFullScreenCover(isPresented: $showSettings) { PointOfSaleSettingsView(settingsController: posModel.settingsController) } + .onChange(of: showSettings) { oldValue, newValue in + guard !newValue, oldValue else { return } + Task { + await posModel.checkStaleSyncStatus() + } + } .onChange(of: posModel.entryPointController.eligibilityState) { oldValue, newValue in guard newValue == .eligible else { return } Task { @MainActor in From 2cb9f5cddf3e7bb211585b7370ec323221e18f42 Mon Sep 17 00:00:00 2001 From: Josh Heald Date: Tue, 4 Nov 2025 10:47:56 +0000 Subject: [PATCH 3/7] Fix lint line length --- Modules/Sources/PointOfSale/Presentation/ItemListView.swift | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Modules/Sources/PointOfSale/Presentation/ItemListView.swift b/Modules/Sources/PointOfSale/Presentation/ItemListView.swift index e33c40abfb1..db206589d6b 100644 --- a/Modules/Sources/PointOfSale/Presentation/ItemListView.swift +++ b/Modules/Sources/PointOfSale/Presentation/ItemListView.swift @@ -439,7 +439,8 @@ private extension ItemListView { static let staleSyncWarningDescriptionFormat = NSLocalizedString( "pos.itemlistview.staleSyncWarning.description", value: "The catalog hasn't been synced in the last %1$ld days. Please ensure you're connected to the internet and sync again in POS Settings.", - comment: "Message shown when the product catalog hasn't synced in the specified number of days. %1$ld is the number of days. Reads like: The catalog hasn't been synced in the last 7 days." + comment: "Message shown when the product catalog hasn't synced in the specified number of days. " + + "%1$ld will be replaced with the number of days. Reads like: The catalog hasn't been synced in the last 7 days." ) static func staleSyncWarningDescription(days: Int) -> String { From 2b25270cee28a7bd17f547b4de4fe76c7a786caa Mon Sep 17 00:00:00 2001 From: Josh Heald Date: Tue, 4 Nov 2025 10:53:13 +0000 Subject: [PATCH 4/7] Remove test_ prefixes --- .../Tools/POS/POSCatalogSyncCoordinatorTests.swift | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Modules/Tests/YosemiteTests/Tools/POS/POSCatalogSyncCoordinatorTests.swift b/Modules/Tests/YosemiteTests/Tools/POS/POSCatalogSyncCoordinatorTests.swift index f9a520c0d83..649dccfa466 100644 --- a/Modules/Tests/YosemiteTests/Tools/POS/POSCatalogSyncCoordinatorTests.swift +++ b/Modules/Tests/YosemiteTests/Tools/POS/POSCatalogSyncCoordinatorTests.swift @@ -863,7 +863,7 @@ extension POSCatalogSyncCoordinatorTests { // MARK: - isSyncStale Tests - @Test func test_isSyncStale_returns_true_when_no_full_sync_performed() async throws { + @Test func isSyncStale_returns_true_when_no_full_sync_performed() async throws { // Given - no full sync date set // When @@ -873,7 +873,7 @@ extension POSCatalogSyncCoordinatorTests { #expect(isStale == true) } - @Test func test_isSyncStale_returns_false_when_full_sync_is_recent() async throws { + @Test func isSyncStale_returns_false_when_full_sync_is_recent() async throws { // Given - last full sync was 3 days ago let threeDaysAgo = Calendar.current.date(byAdding: .day, value: -3, to: Date())! try createSiteInDatabase(siteID: sampleSiteID, lastFullSyncDate: threeDaysAgo) @@ -885,7 +885,7 @@ extension POSCatalogSyncCoordinatorTests { #expect(isStale == false) } - @Test func test_isSyncStale_returns_true_when_full_sync_is_old() async throws { + @Test func isSyncStale_returns_true_when_full_sync_is_old() async throws { // Given - last full sync was 10 days ago let tenDaysAgo = Calendar.current.date(byAdding: .day, value: -10, to: Date())! try createSiteInDatabase(siteID: sampleSiteID, lastFullSyncDate: tenDaysAgo) @@ -897,7 +897,7 @@ extension POSCatalogSyncCoordinatorTests { #expect(isStale == true) } - @Test func test_isSyncStale_ignores_incremental_sync_date() async throws { + @Test func isSyncStale_ignores_incremental_sync_date() async throws { // Given - incremental sync was recent, but full sync was old let yesterday = Calendar.current.date(byAdding: .day, value: -1, to: Date())! let tenDaysAgo = Calendar.current.date(byAdding: .day, value: -10, to: Date())! @@ -910,7 +910,7 @@ extension POSCatalogSyncCoordinatorTests { #expect(isStale == true) } - @Test func test_isSyncStale_boundary_within_threshold() async throws { + @Test func isSyncStale_boundary_within_threshold() async throws { // Given - last full sync was 6 days and 23 hours ago (just under 7 days) let justUnderSevenDays = Calendar.current.date(byAdding: .day, value: -6, to: Date())! .addingTimeInterval(-23 * 60 * 60) // minus 23 hours @@ -923,7 +923,7 @@ extension POSCatalogSyncCoordinatorTests { #expect(isStale == false) } - @Test func test_isSyncStale_boundary_past_threshold() async throws { + @Test func isSyncStale_boundary_past_threshold() async throws { // Given - last full sync was 8 days ago (clearly past 7 days) let eightDaysAgo = Calendar.current.date(byAdding: .day, value: -8, to: Date())! try createSiteInDatabase(siteID: sampleSiteID, lastFullSyncDate: eightDaysAgo) From e4153ec84aee495715eaf6ad165acecb4d84feaf Mon Sep 17 00:00:00 2001 From: Josh Heald Date: Tue, 4 Nov 2025 10:55:07 +0000 Subject: [PATCH 5/7] remove force unwrap in tests --- .../Tools/POS/POSCatalogSyncCoordinatorTests.swift | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Modules/Tests/YosemiteTests/Tools/POS/POSCatalogSyncCoordinatorTests.swift b/Modules/Tests/YosemiteTests/Tools/POS/POSCatalogSyncCoordinatorTests.swift index 649dccfa466..caed63dd7f8 100644 --- a/Modules/Tests/YosemiteTests/Tools/POS/POSCatalogSyncCoordinatorTests.swift +++ b/Modules/Tests/YosemiteTests/Tools/POS/POSCatalogSyncCoordinatorTests.swift @@ -875,7 +875,7 @@ extension POSCatalogSyncCoordinatorTests { @Test func isSyncStale_returns_false_when_full_sync_is_recent() async throws { // Given - last full sync was 3 days ago - let threeDaysAgo = Calendar.current.date(byAdding: .day, value: -3, to: Date())! + let threeDaysAgo = try #require(Calendar.current.date(byAdding: .day, value: -3, to: Date())) try createSiteInDatabase(siteID: sampleSiteID, lastFullSyncDate: threeDaysAgo) // When @@ -887,7 +887,7 @@ extension POSCatalogSyncCoordinatorTests { @Test func isSyncStale_returns_true_when_full_sync_is_old() async throws { // Given - last full sync was 10 days ago - let tenDaysAgo = Calendar.current.date(byAdding: .day, value: -10, to: Date())! + let tenDaysAgo = try #require(Calendar.current.date(byAdding: .day, value: -10, to: Date())) try createSiteInDatabase(siteID: sampleSiteID, lastFullSyncDate: tenDaysAgo) // When @@ -899,8 +899,8 @@ extension POSCatalogSyncCoordinatorTests { @Test func isSyncStale_ignores_incremental_sync_date() async throws { // Given - incremental sync was recent, but full sync was old - let yesterday = Calendar.current.date(byAdding: .day, value: -1, to: Date())! - let tenDaysAgo = Calendar.current.date(byAdding: .day, value: -10, to: Date())! + let yesterday = try #require(Calendar.current.date(byAdding: .day, value: -1, to: Date())) + let tenDaysAgo = try #require(Calendar.current.date(byAdding: .day, value: -10, to: Date())) try createSiteInDatabase(siteID: sampleSiteID, lastFullSyncDate: tenDaysAgo, lastIncrementalSyncDate: yesterday) // When @@ -912,7 +912,7 @@ extension POSCatalogSyncCoordinatorTests { @Test func isSyncStale_boundary_within_threshold() async throws { // Given - last full sync was 6 days and 23 hours ago (just under 7 days) - let justUnderSevenDays = Calendar.current.date(byAdding: .day, value: -6, to: Date())! + let justUnderSevenDays = try #require(Calendar.current.date(byAdding: .day, value: -6, to: Date())) .addingTimeInterval(-23 * 60 * 60) // minus 23 hours try createSiteInDatabase(siteID: sampleSiteID, lastFullSyncDate: justUnderSevenDays) From b67f14af957772f886bf02d226b8b1aec2c1decf Mon Sep 17 00:00:00 2001 From: Josh Heald Date: Tue, 4 Nov 2025 10:55:59 +0000 Subject: [PATCH 6/7] Make boundary test 1s past deadline --- .../Tools/POS/POSCatalogSyncCoordinatorTests.swift | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Modules/Tests/YosemiteTests/Tools/POS/POSCatalogSyncCoordinatorTests.swift b/Modules/Tests/YosemiteTests/Tools/POS/POSCatalogSyncCoordinatorTests.swift index caed63dd7f8..0aedfa3fd6f 100644 --- a/Modules/Tests/YosemiteTests/Tools/POS/POSCatalogSyncCoordinatorTests.swift +++ b/Modules/Tests/YosemiteTests/Tools/POS/POSCatalogSyncCoordinatorTests.swift @@ -924,9 +924,10 @@ extension POSCatalogSyncCoordinatorTests { } @Test func isSyncStale_boundary_past_threshold() async throws { - // Given - last full sync was 8 days ago (clearly past 7 days) - let eightDaysAgo = Calendar.current.date(byAdding: .day, value: -8, to: Date())! - try createSiteInDatabase(siteID: sampleSiteID, lastFullSyncDate: eightDaysAgo) + // Given - last full sync was 7 days and 1 second ago (just past 7 days) + let justPastSevenDays = try #require(Calendar.current.date(byAdding: .day, value: -7, to: Date())) + .addingTimeInterval(-1) + try createSiteInDatabase(siteID: sampleSiteID, lastFullSyncDate: justPastSevenDays) // When let isStale = await sut.isSyncStale(for: sampleSiteID, maxDays: 7) From 19a75ae8e103bb9f911472c29576702fa315e734 Mon Sep 17 00:00:00 2001 From: Josh Heald Date: Tue, 4 Nov 2025 11:42:23 +0000 Subject: [PATCH 7/7] Limit the size of notices to keep the list visible --- .../PointOfSale/Presentation/Reusable Views/POSNoticeView.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/Modules/Sources/PointOfSale/Presentation/Reusable Views/POSNoticeView.swift b/Modules/Sources/PointOfSale/Presentation/Reusable Views/POSNoticeView.swift index 7906cb8627e..43788072034 100644 --- a/Modules/Sources/PointOfSale/Presentation/Reusable Views/POSNoticeView.swift +++ b/Modules/Sources/PointOfSale/Presentation/Reusable Views/POSNoticeView.swift @@ -45,6 +45,7 @@ struct POSNoticeView: View { .accessibilityElement(children: .combine) } } + .dynamicTypeSize(...DynamicTypeSize.accessibility2) .frame(maxWidth: .infinity, alignment: .leading) if let onDismiss {