From e4d91db4d9ad3523b85dde17da25c94f5e8c6737 Mon Sep 17 00:00:00 2001 From: jack Date: Sat, 25 Jul 2026 23:49:34 +0200 Subject: [PATCH] Isolate nearby notes settings in tests --- bitchat/App/NearbyNotesCounter.swift | 19 +++++-- bitchatTests/NearbyNotesCounterTests.swift | 60 +++++++++++++--------- 2 files changed, 49 insertions(+), 30 deletions(-) diff --git a/bitchat/App/NearbyNotesCounter.swift b/bitchat/App/NearbyNotesCounter.swift index ae173bbd..d1863970 100644 --- a/bitchat/App/NearbyNotesCounter.swift +++ b/bitchat/App/NearbyNotesCounter.swift @@ -33,15 +33,25 @@ final class NearbyNotesCounter: ObservableObject { private let locationManager: LocationChannelManager private let managerFactory: @MainActor (String) -> LocationNotesManager private let releaseManager: @MainActor (LocationNotesManager?) -> Void + private let locationNotesEnabled: @MainActor () -> Bool + private let locationNotesSettingsPublisher: AnyPublisher init( locationManager: LocationChannelManager = .shared, managerFactory: @escaping @MainActor (String) -> LocationNotesManager = { LocationNotesPool.shared.acquire($0) }, - releaseManager: @escaping @MainActor (LocationNotesManager?) -> Void = { LocationNotesPool.shared.release($0) } + releaseManager: @escaping @MainActor (LocationNotesManager?) -> Void = { LocationNotesPool.shared.release($0) }, + locationNotesEnabled: @escaping @MainActor () -> Bool = { LocationNotesSettings.enabled }, + locationNotesSettings: AnyPublisher? = nil ) { self.locationManager = locationManager self.managerFactory = managerFactory self.releaseManager = releaseManager + self.locationNotesEnabled = locationNotesEnabled + self.locationNotesSettingsPublisher = locationNotesSettings + ?? NotificationCenter.default + .publisher(for: LocationNotesSettings.didChangeNotification) + .map { _ in () } + .eraseToAnyPublisher() } /// Whether the empty-timeline "check for notes" hint should render. @@ -53,7 +63,7 @@ final class NearbyNotesCounter: ObservableObject { /// passes its own observed permission state so the hint re-renders when /// authorization changes. func offersRevealHint(permissionState: LocationChannelManager.PermissionState) -> Bool { - !revealed && LocationNotesSettings.enabled && permissionState == .authorized + !revealed && locationNotesEnabled() && permissionState == .authorized } /// Marks the one explicit act that lets the counter subscribe. Sticky for @@ -83,8 +93,7 @@ final class NearbyNotesCounter: ObservableObject { .sink { [weak self] _ in self?.retarget() } // The app-info kill switch must take effect immediately, not on the // next location change or remount. - settingCancellable = NotificationCenter.default - .publisher(for: LocationNotesSettings.didChangeNotification) + settingCancellable = locationNotesSettingsPublisher .receive(on: DispatchQueue.main) .sink { [weak self] _ in self?.retarget() } retarget() @@ -105,7 +114,7 @@ final class NearbyNotesCounter: ObservableObject { private func retarget() { guard activeHolders > 0, revealed, - LocationNotesSettings.enabled, + locationNotesEnabled(), locationManager.permissionState == .authorized, let geohash = locationManager.availableChannels .first(where: { $0.level == .building })?.geohash diff --git a/bitchatTests/NearbyNotesCounterTests.swift b/bitchatTests/NearbyNotesCounterTests.swift index b1036778..b548a8ae 100644 --- a/bitchatTests/NearbyNotesCounterTests.swift +++ b/bitchatTests/NearbyNotesCounterTests.swift @@ -8,25 +8,9 @@ import XCTest /// the pooled subscription must come up exactly once and go down exactly once. @MainActor final class NearbyNotesCounterTests: XCTestCase { - private var previousNotesEnabled: Any? - - override func setUp() { - super.setUp() - previousNotesEnabled = UserDefaults.standard.object(forKey: "locationNotes.enabled") - UserDefaults.standard.set(true, forKey: "locationNotes.enabled") - } - - override func tearDown() { - if let previous = previousNotesEnabled as? Bool { - UserDefaults.standard.set(previous, forKey: "locationNotes.enabled") - } else { - UserDefaults.standard.removeObject(forKey: "locationNotes.enabled") - } - super.tearDown() - } - func test_counterOnlySubscribesAfterReveal_countsUnexpiredNotes_andUnsubscribesOnDeactivate() async throws { let relays = SubscriptionRecorder() + let settings = LocationNotesSettingsStub() let locationManager = try await makeAuthorizedLocationManager() let buildingGeohash = try XCTUnwrap( locationManager.availableChannels.first(where: { $0.level == .building })?.geohash @@ -35,7 +19,9 @@ final class NearbyNotesCounterTests: XCTestCase { let counter = NearbyNotesCounter( locationManager: locationManager, managerFactory: { LocationNotesManager(geohash: $0, dependencies: relays.dependencies) }, - releaseManager: { $0?.cancel() } + releaseManager: { $0?.cancel() }, + locationNotesEnabled: { settings.enabled }, + locationNotesSettings: settings.changes ) counter.activate() @@ -93,11 +79,14 @@ final class NearbyNotesCounterTests: XCTestCase { func test_permissionRevocation_releasesBuildingSubscriptionDespiteCachedChannels() async throws { let relays = SubscriptionRecorder() + let settings = LocationNotesSettingsStub() let locationManager = try await makeAuthorizedLocationManager() let counter = NearbyNotesCounter( locationManager: locationManager, managerFactory: { LocationNotesManager(geohash: $0, dependencies: relays.dependencies) }, - releaseManager: { $0?.cancel() } + releaseManager: { $0?.cancel() }, + locationNotesEnabled: { settings.enabled }, + locationNotesSettings: settings.changes ) counter.activate() @@ -122,24 +111,27 @@ final class NearbyNotesCounterTests: XCTestCase { func test_locationNotesKillSwitch_releasesAndCanReacquireBuildingSubscription() async throws { let relays = SubscriptionRecorder() + let settings = LocationNotesSettingsStub() let locationManager = try await makeAuthorizedLocationManager() let counter = NearbyNotesCounter( locationManager: locationManager, managerFactory: { LocationNotesManager(geohash: $0, dependencies: relays.dependencies) }, - releaseManager: { $0?.cancel() } + releaseManager: { $0?.cancel() }, + locationNotesEnabled: { settings.enabled }, + locationNotesSettings: settings.changes ) counter.activate() counter.reveal() XCTAssertEqual(relays.subscribeCount, 1) - LocationNotesSettings.enabled = false + settings.setEnabled(false) let released = await waitUntil { relays.unsubscribeCount == 1 } XCTAssertTrue(released) XCTAssertEqual(counter.noteCount, 0) - LocationNotesSettings.enabled = true + settings.setEnabled(true) let reacquired = await waitUntil { relays.subscribeCount == 2 } XCTAssertTrue(reacquired) @@ -149,10 +141,13 @@ final class NearbyNotesCounterTests: XCTestCase { func test_checkNotesHint_requiresAuthorizedLocationPermission() { let relays = SubscriptionRecorder() + let settings = LocationNotesSettingsStub() let counter = NearbyNotesCounter( locationManager: makeBareLocationManager(), managerFactory: { LocationNotesManager(geohash: $0, dependencies: relays.dependencies) }, - releaseManager: { $0?.cancel() } + releaseManager: { $0?.cancel() }, + locationNotesEnabled: { settings.enabled }, + locationNotesSettings: settings.changes ) // An unauthorized install must never see the hint: the tap can't @@ -164,9 +159,9 @@ final class NearbyNotesCounterTests: XCTestCase { XCTAssertTrue(counter.offersRevealHint(permissionState: .authorized)) // The app-info kill switch hides it too. - LocationNotesSettings.enabled = false + settings.setEnabled(false) XCTAssertFalse(counter.offersRevealHint(permissionState: .authorized)) - LocationNotesSettings.enabled = true + settings.setEnabled(true) // Once revealed, the hint yields to the live strip and count. counter.reveal() @@ -411,6 +406,21 @@ final class NearbyNotesCounterTests: XCTestCase { } } +@MainActor +private final class LocationNotesSettingsStub { + private let changesSubject = PassthroughSubject() + private(set) var enabled = true + + var changes: AnyPublisher { + changesSubject.eraseToAnyPublisher() + } + + func setEnabled(_ enabled: Bool) { + self.enabled = enabled + changesSubject.send(()) + } +} + /// Stub relay layer: counts REQs, captures the last filter/handler, and never /// touches the network. @MainActor