Remove the dynamic member lookup from UserSettings in favour of nested access. (#6294)

* Remove the dynamic member lookup from UserSettings in favour of nested access.

* Remove forwarding methods from UserSettings.

* Update the Enterprise submodule.
This commit is contained in:
Doug
2026-10-08 15:19:44 +01:00
committed by GitHub
parent fba28ed422
commit fc75f90ef5
75 changed files with 346 additions and 374 deletions
@@ -18,7 +18,7 @@ final class BugReportServiceTests {
init() throws {
userSettings = UserSettings.mock()
userSettings.bugReportRageshakeURL.reset()
userSettings.app.bugReportRageshakeURL.reset()
let bugReportServiceMock = BugReportServiceMock()
bugReportServiceMock.lastCrashEventIDSubject = .init(nil)
@@ -27,7 +27,7 @@ final class BugReportServiceTests {
}
deinit {
userSettings.bugReportRageshakeURL.reset()
userSettings.app.bugReportRageshakeURL.reset()
}
@Test
@@ -104,22 +104,22 @@ final class BugReportServiceTests {
@Test
@MainActor
func configurations() async throws {
guard case let .url(initialURL) = userSettings.bugReportRageshakeURL.publisher.value else {
guard case let .url(initialURL) = userSettings.app.bugReportRageshakeURL.publisher.value else {
Issue.record("Unexpected initial configuration.")
return
}
let service = BugReportService(rageshakeURLPublisher: userSettings.bugReportRageshakeURL.publisher,
let service = BugReportService(rageshakeURLPublisher: userSettings.app.bugReportRageshakeURL.publisher,
applicationID: "mock_app_id",
sdkGitSHA: "1234",
session: .mock,
appHooks: AppHooks())
#expect(service.isEnabled)
userSettings.bugReportRageshakeURL.applyRemoteValue(.disabled)
userSettings.app.bugReportRageshakeURL.applyRemoteValue(.disabled)
#expect(!service.isEnabled)
userSettings.bugReportRageshakeURL.applyRemoteValue(.url("https://bugs.server.net/submit"))
userSettings.app.bugReportRageshakeURL.applyRemoteValue(.url("https://bugs.server.net/submit"))
#expect(service.isEnabled)
let bugReport = BugReport(userID: "@mock:client.com",
@@ -136,7 +136,7 @@ final class BugReportServiceTests {
#expect(customConfigurationResponse.reportURL == "https://bugs.server.net/123")
userSettings.bugReportRageshakeURL.reset()
userSettings.app.bugReportRageshakeURL.reset()
#expect(service.isEnabled)
let defaultConfigurationResponse = try await service.submitBugReport(bugReport, progressListener: progressSubject).get()
@@ -85,7 +85,7 @@ final class CreateRoomScreenViewModelTests {
spaceServiceConfiguration: .init(spaceRoomLists: ["1": .init()])))
clientProxy.roomForIdentifierClosure = { roomID in .joined(JoinedRoomProxyMock(.init(id: roomID))) }
userSession = UserSessionMock(.init(clientProxy: clientProxy))
userSettings.knockingEnabled = true
userSettings.app.knockingEnabled = true
let viewModel = CreateRoomScreenViewModel(isSpace: true,
spaceSelectionMode: .none,
shouldShowCancelButton: false,
@@ -336,7 +336,7 @@ final class CreateRoomScreenViewModelTests {
clientProxy.spaceService = spaceService
clientProxy.roomForIdentifierClosure = { roomID in .joined(JoinedRoomProxyMock(.init(id: roomID))) }
userSession = UserSessionMock(.init(clientProxy: clientProxy))
userSettings.knockingEnabled = true
userSettings.app.knockingEnabled = true
let viewModel = CreateRoomScreenViewModel(isSpace: isSpace,
spaceSelectionMode: spacesSelectionMode,
shouldShowCancelButton: false,
@@ -275,7 +275,7 @@ final class HomeScreenViewModelTests {
room.roomID == invites[0].roomID && room.badges.isDotShown == false
}
}
userSettings.seenInvites = Set(invites.compactMap(\.roomID))
userSettings.app.seenInvites = Set(invites.compactMap(\.roomID))
try await deferred.fulfill()
invites = context.viewState.rooms.invites
@@ -289,14 +289,14 @@ final class HomeScreenViewModelTests {
setupViewModel(invites: .rooms)
let invitedRoomIDs = context.viewState.rooms.invites.compactMap(\.roomID)
userSettings.seenInvites = Set(invitedRoomIDs)
userSettings.app.seenInvites = Set(invitedRoomIDs)
#expect(invitedRoomIDs.count == 2)
let deferred = deferFulfillment(viewModel.actions) { $0 == .presentRoom(roomIdentifier: invitedRoomIDs[0]) }
context.send(viewAction: .acceptInvite(roomIdentifier: invitedRoomIDs[0]))
try await deferred.fulfill()
#expect(userSettings.seenInvites == [invitedRoomIDs[1]])
#expect(userSettings.app.seenInvites == [invitedRoomIDs[1]])
#expect(!notificationManager.removeDeliveredMessageNotificationsForCalled, "The notification will be dismissed when opening the room.")
}
@@ -305,7 +305,7 @@ final class HomeScreenViewModelTests {
setupViewModel(invites: .spaces)
let invitedRoomIDs = context.viewState.rooms.invites.compactMap(\.roomID)
userSettings.seenInvites = Set(invitedRoomIDs)
userSettings.app.seenInvites = Set(invitedRoomIDs)
#expect(invitedRoomIDs.count == 2)
let deferred = deferFulfillment(viewModel.actions) {
@@ -314,7 +314,7 @@ final class HomeScreenViewModelTests {
context.send(viewAction: .acceptInvite(roomIdentifier: invitedRoomIDs[0]))
try await deferred.fulfill()
#expect(userSettings.seenInvites == [invitedRoomIDs[1]])
#expect(userSettings.app.seenInvites == [invitedRoomIDs[1]])
#expect(!notificationManager.removeDeliveredMessageNotificationsForCalled, "The notification will be dismissed when opening the room.")
}
@@ -322,7 +322,7 @@ final class HomeScreenViewModelTests {
func declineInvite() async throws {
setupViewModel(invites: .rooms)
let invitedRoomIDs = context.viewState.rooms.invites.compactMap(\.roomID)
userSettings.seenInvites = Set(invitedRoomIDs)
userSettings.app.seenInvites = Set(invitedRoomIDs)
#expect(invitedRoomIDs.count == 2)
let deferred = deferFulfillment(context.$viewState) { $0.bindings.alertInfo != nil }
@@ -345,7 +345,7 @@ final class HomeScreenViewModelTests {
try await Task.sleep(for: .milliseconds(100))
#expect(rejectCalled)
#expect(userSettings.seenInvites == [invitedRoomIDs[1]])
#expect(userSettings.app.seenInvites == [invitedRoomIDs[1]])
#expect(notificationManager.removeDeliveredMessageNotificationsForCalled)
#expect(notificationManager.removeDeliveredMessageNotificationsForReceivedInvocations == [invitedRoomIDs[0]])
}
@@ -354,7 +354,7 @@ final class HomeScreenViewModelTests {
func declineAndBlockInvite() async throws {
setupViewModel(invites: .rooms)
let invitedRoomIDs = context.viewState.rooms.invites.compactMap(\.roomID)
userSettings.seenInvites = Set(invitedRoomIDs)
userSettings.app.seenInvites = Set(invitedRoomIDs)
#expect(invitedRoomIDs.count == 2)
let deferred = deferFulfillment(context.$viewState) { $0.bindings.alertInfo != nil }
@@ -368,7 +368,7 @@ final class HomeScreenViewModelTests {
@Test
func newSoundBanner() {
userSettings.hasSeenNewSoundBanner = false
userSettings.app.hasSeenNewSoundBanner = false
setupViewModel()
#expect(context.viewState.shouldShowBanner)
@@ -377,26 +377,26 @@ final class HomeScreenViewModelTests {
context.send(viewAction: .dismissNewSoundBanner)
#expect(!context.viewState.shouldShowBanner)
#expect(!context.viewState.shouldShowNewSoundBanner)
#expect(userSettings.hasSeenNewSoundBanner)
#expect(userSettings.app.hasSeenNewSoundBanner)
}
@Test
func multiAccountAnnouncement() async throws {
userSettings.multiAccountEnabled = true
userSettings.app.multiAccountEnabled = true
setupViewModel()
// Arming it after the view model exists (Developer options) doesn't present it on its own.
userSettings.hasSeenMultiAccountAnnouncement = false
userSettings.app.hasSeenMultiAccountAnnouncement = false
let deferredFailure = deferFailure(context.$viewState, timeout: .seconds(1)) { $0.bindings.isPresentingMultiAccountAnnouncement }
try await deferredFailure.fulfill()
let deferred = deferFulfillment(context.$viewState) { $0.bindings.isPresentingMultiAccountAnnouncement }
context.send(viewAction: .screenAppeared)
try await deferred.fulfill()
#expect(!userSettings.hasSeenMultiAccountAnnouncement)
#expect(!userSettings.app.hasSeenMultiAccountAnnouncement)
context.send(viewAction: .multiAccountAnnouncementAppeared)
#expect(userSettings.hasSeenMultiAccountAnnouncement)
#expect(userSettings.app.hasSeenMultiAccountAnnouncement)
#expect(context.viewState.bindings.isPresentingMultiAccountAnnouncement)
// A swipe down dismisses it without any of the announcement's actions.
@@ -408,8 +408,8 @@ final class HomeScreenViewModelTests {
@Test(arguments: [HomeScreenViewAction.dismissMultiAccountAnnouncement, .addAccount])
func multiAccountAnnouncementActionsDismissIt(_ action: HomeScreenViewAction) async throws {
userSettings.multiAccountEnabled = true
userSettings.hasSeenMultiAccountAnnouncement = false
userSettings.app.multiAccountEnabled = true
userSettings.app.hasSeenMultiAccountAnnouncement = false
setupViewModel()
let deferred = deferFulfillment(context.$viewState) { $0.bindings.isPresentingMultiAccountAnnouncement }
@@ -422,8 +422,8 @@ final class HomeScreenViewModelTests {
@Test(arguments: MultiAccountAnnouncementHiddenCase.allCases)
func multiAccountAnnouncementHidden(_ hiddenCase: MultiAccountAnnouncementHiddenCase) async throws {
userSettings.multiAccountEnabled = hiddenCase != .flagOff
userSettings.hasSeenMultiAccountAnnouncement = hiddenCase == .alreadySeen
userSettings.app.multiAccountEnabled = hiddenCase != .flagOff
userSettings.app.hasSeenMultiAccountAnnouncement = hiddenCase == .alreadySeen
let verificationState: SessionVerificationState = switch hiddenCase {
case .unverified: .unverified
case .unknownVerificationState: .unknown
@@ -435,13 +435,13 @@ final class HomeScreenViewModelTests {
let deferred = deferFailure(context.$viewState, timeout: .seconds(1)) { $0.bindings.isPresentingMultiAccountAnnouncement }
context.send(viewAction: .screenAppeared)
try await deferred.fulfill()
#expect(userSettings.hasSeenMultiAccountAnnouncement == (hiddenCase == .alreadySeen))
#expect(userSettings.app.hasSeenMultiAccountAnnouncement == (hiddenCase == .alreadySeen))
}
@Test
func multiAccountAnnouncementPresentsOnTheNextAppearanceOnceVerified() async throws {
userSettings.multiAccountEnabled = true
userSettings.hasSeenMultiAccountAnnouncement = false
userSettings.app.multiAccountEnabled = true
userSettings.app.hasSeenMultiAccountAnnouncement = false
let securityStateSubject = CurrentValueSubject<SessionSecurityState, Never>(.init(verificationState: .unverified, recoveryState: .enabled))
setupViewModel(securityStatePublisher: securityStateSubject.asCurrentValuePublisher())
@@ -45,12 +45,12 @@ final class JoinRoomScreenViewModelTests {
@Test
func interaction() async throws {
#expect(userSettings.seenInvites.isEmpty, "There shouldn't be any seen invites before running the tests.")
#expect(userSettings.app.seenInvites.isEmpty, "There shouldn't be any seen invites before running the tests.")
setupViewModel()
try await deferFulfillment(viewModel.context.$viewState) { $0.mode == .joinable }.fulfill()
#expect(userSettings.seenInvites.isEmpty, "Only an invited room should register the room ID as a seen invite.")
#expect(userSettings.app.seenInvites.isEmpty, "Only an invited room should register the room ID as a seen invite.")
let deferred = deferFulfillment(viewModel.actionsPublisher) { $0 == .joined(.roomID("1")) }
context.send(viewAction: .join)
@@ -59,28 +59,28 @@ final class JoinRoomScreenViewModelTests {
@Test
func acceptInviteInteraction() async throws {
#expect(userSettings.seenInvites.isEmpty, "There shouldn't be any seen invites before running the tests.")
#expect(userSettings.app.seenInvites.isEmpty, "There shouldn't be any seen invites before running the tests.")
setupViewModel(mode: .invited)
try await deferFulfillment(viewModel.context.$viewState) { $0.mode == .invited(isDM: false) }.fulfill()
#expect(userSettings.seenInvites == ["1"], "The invited room's ID should be registered as a seen invite.")
#expect(userSettings.app.seenInvites == ["1"], "The invited room's ID should be registered as a seen invite.")
let deferred = deferFulfillment(viewModel.actionsPublisher) { $0 == .joined(.roomID("1")) }
context.send(viewAction: .acceptInvite)
try await deferred.fulfill()
#expect(userSettings.seenInvites.isEmpty, "The after accepting an invite the invite should be forgotten in case the user leaves.")
#expect(userSettings.app.seenInvites.isEmpty, "The after accepting an invite the invite should be forgotten in case the user leaves.")
}
@Test
func declineInviteInteraction() async throws {
#expect(userSettings.seenInvites.isEmpty, "There shouldn't be any seen invites before running the tests.")
#expect(userSettings.app.seenInvites.isEmpty, "There shouldn't be any seen invites before running the tests.")
setupViewModel(mode: .invited)
try await deferFulfillment(viewModel.context.$viewState) { $0.mode == .invited(isDM: false) }.fulfill()
#expect(userSettings.seenInvites == ["1"], "The invited room's ID should be registered as a seen invite.")
#expect(userSettings.app.seenInvites == ["1"], "The invited room's ID should be registered as a seen invite.")
context.send(viewAction: .declineInvite)
@@ -89,17 +89,17 @@ final class JoinRoomScreenViewModelTests {
context.alertInfo?.secondaryButton?.action?()
try await deferred.fulfill()
#expect(userSettings.seenInvites.isEmpty, "The after declining an invite the invite should be forgotten in case another invite is received.")
#expect(userSettings.app.seenInvites.isEmpty, "The after declining an invite the invite should be forgotten in case another invite is received.")
}
@Test
func knockedState() async throws {
#expect(userSettings.seenInvites.isEmpty, "There shouldn't be any seen invites before running the tests.")
#expect(userSettings.app.seenInvites.isEmpty, "There shouldn't be any seen invites before running the tests.")
setupViewModel(mode: .knocked)
try await deferFulfillment(viewModel.context.$viewState) { $0.mode == .knocked }.fulfill()
#expect(userSettings.seenInvites.isEmpty, "Only an invited room should register the room ID as a seen invite.")
#expect(userSettings.app.seenInvites.isEmpty, "Only an invited room should register the room ID as a seen invite.")
}
@Test
@@ -41,11 +41,11 @@ final class LiveLocationManagerTests {
try result.get()
#expect(callOrder == ["stop", "start"])
#expect(userSettings.liveLocationSharingSessionsByRoomID["!room:matrix.org"] == nil)
#expect(userSettings.app.liveLocationSharingSessionsByRoomID["!room:matrix.org"] == nil)
try await simulateBeaconEcho(roomID: "!room:matrix.org", eventID: "$event:matrix.org")
#expect(userSettings.liveLocationSharingSessionsByRoomID["!room:matrix.org"] != nil)
#expect(userSettings.app.liveLocationSharingSessionsByRoomID["!room:matrix.org"] != nil)
#expect(locationManagerMock.startUpdatingLocationCalled)
}
@@ -54,7 +54,7 @@ final class LiveLocationManagerTests {
setUp()
let roomProxy = makeRoomProxy(roomID: "!room:matrix.org")
clientProxy.roomForIdentifierClosure = { _ in .joined(roomProxy) }
userSettings.liveLocationSharingSessionsByRoomID["!room:matrix.org"] = LiveLocationSession(eventID: "$old_event:matrix.org", expirationDate: Date().addingTimeInterval(300))
userSettings.app.liveLocationSharingSessionsByRoomID["!room:matrix.org"] = LiveLocationSession(eventID: "$old_event:matrix.org", expirationDate: Date().addingTimeInterval(300))
var callOrder: [String] = []
roomProxy.stopLiveLocationShareClosure = {
@@ -73,7 +73,7 @@ final class LiveLocationManagerTests {
try await simulateBeaconEcho(roomID: "!room:matrix.org", eventID: "$event:matrix.org")
#expect(userSettings.liveLocationSharingSessionsByRoomID["!room:matrix.org"] != nil)
#expect(userSettings.app.liveLocationSharingSessionsByRoomID["!room:matrix.org"] != nil)
}
@Test
@@ -82,12 +82,12 @@ final class LiveLocationManagerTests {
let roomProxy = makeRoomProxy(roomID: "!room1:matrix.org")
clientProxy.roomForIdentifierClosure = { _ in .joined(roomProxy) }
userSettings.liveLocationSharingSessionsByRoomID["!room2:matrix.org"] = LiveLocationSession(eventID: "$event:matrix.org", expirationDate: Date().addingTimeInterval(300))
userSettings.app.liveLocationSharingSessionsByRoomID["!room2:matrix.org"] = LiveLocationSession(eventID: "$event:matrix.org", expirationDate: Date().addingTimeInterval(300))
_ = await manager.startLiveLocation(roomID: "!room1:matrix.org", duration: .seconds(300))
#expect(roomProxy.stopLiveLocationShareCalled)
#expect(userSettings.liveLocationSharingSessionsByRoomID["!room2:matrix.org"] != nil)
#expect(userSettings.app.liveLocationSharingSessionsByRoomID["!room2:matrix.org"] != nil)
}
@Test
@@ -98,7 +98,7 @@ final class LiveLocationManagerTests {
let result = await manager.startLiveLocation(roomID: "!room:matrix.org", duration: .seconds(300))
#expect(throws: LiveLocationManagerError.roomNotJoined) { try result.get() }
#expect(userSettings.liveLocationSharingSessionsByRoomID["!room:matrix.org"] == nil)
#expect(userSettings.app.liveLocationSharingSessionsByRoomID["!room:matrix.org"] == nil)
}
@Test
@@ -111,7 +111,7 @@ final class LiveLocationManagerTests {
let result = await manager.startLiveLocation(roomID: "!room:matrix.org", duration: .seconds(300))
#expect(throws: LiveLocationManagerError.startFailed) { try result.get() }
#expect(userSettings.liveLocationSharingSessionsByRoomID["!room:matrix.org"] == nil)
#expect(userSettings.app.liveLocationSharingSessionsByRoomID["!room:matrix.org"] == nil)
}
@Test
@@ -127,7 +127,7 @@ final class LiveLocationManagerTests {
try await simulateBeaconEcho(roomID: "!room:matrix.org", eventID: "$event:matrix.org")
let storedSession = try #require(userSettings.liveLocationSharingSessionsByRoomID["!room:matrix.org"])
let storedSession = try #require(userSettings.app.liveLocationSharingSessionsByRoomID["!room:matrix.org"])
let expectedMinTimeout = beforeStart.addingTimeInterval(TimeInterval(duration.seconds))
let expectedMaxTimeout = afterStart.addingTimeInterval(TimeInterval(duration.seconds))
@@ -142,12 +142,12 @@ final class LiveLocationManagerTests {
setUp()
let roomProxy = makeRoomProxy(roomID: "!room:matrix.org")
clientProxy.roomForIdentifierClosure = { _ in .joined(roomProxy) }
userSettings.liveLocationSharingSessionsByRoomID["!room:matrix.org"] = LiveLocationSession(eventID: "$event:matrix.org", expirationDate: Date().addingTimeInterval(300))
userSettings.app.liveLocationSharingSessionsByRoomID["!room:matrix.org"] = LiveLocationSession(eventID: "$event:matrix.org", expirationDate: Date().addingTimeInterval(300))
await manager.stopLiveLocation(roomID: "!room:matrix.org")
#expect(roomProxy.stopLiveLocationShareCalled)
#expect(userSettings.liveLocationSharingSessionsByRoomID["!room:matrix.org"] == nil)
#expect(userSettings.app.liveLocationSharingSessionsByRoomID["!room:matrix.org"] == nil)
// Setting the timeout date above starts tracking; removing it stops tracking.
#expect(locationManagerMock.startUpdatingLocationCalled)
#expect(locationManagerMock.stopUpdatingLocationCalled)
@@ -169,13 +169,13 @@ final class LiveLocationManagerTests {
setUp()
let roomProxy = makeRoomProxy(roomID: "!room1:matrix.org")
clientProxy.roomForIdentifierClosure = { _ in .joined(roomProxy) }
userSettings.liveLocationSharingSessionsByRoomID["!room1:matrix.org"] = LiveLocationSession(eventID: "$event:matrix.org", expirationDate: Date().addingTimeInterval(300))
userSettings.liveLocationSharingSessionsByRoomID["!room2:matrix.org"] = LiveLocationSession(eventID: "$event:matrix.org", expirationDate: Date().addingTimeInterval(300))
userSettings.app.liveLocationSharingSessionsByRoomID["!room1:matrix.org"] = LiveLocationSession(eventID: "$event:matrix.org", expirationDate: Date().addingTimeInterval(300))
userSettings.app.liveLocationSharingSessionsByRoomID["!room2:matrix.org"] = LiveLocationSession(eventID: "$event:matrix.org", expirationDate: Date().addingTimeInterval(300))
await manager.stopLiveLocation(roomID: "!room1:matrix.org")
#expect(userSettings.liveLocationSharingSessionsByRoomID["!room1:matrix.org"] == nil)
#expect(userSettings.liveLocationSharingSessionsByRoomID["!room2:matrix.org"] != nil)
#expect(userSettings.app.liveLocationSharingSessionsByRoomID["!room1:matrix.org"] == nil)
#expect(userSettings.app.liveLocationSharingSessionsByRoomID["!room2:matrix.org"] != nil)
}
// MARK: - Beacon info updates
@@ -188,13 +188,13 @@ final class LiveLocationManagerTests {
try await manager.startLiveLocation(roomID: "!room:matrix.org", duration: .seconds(300)).get()
try await simulateBeaconEcho(roomID: "!room:matrix.org", eventID: "$event:matrix.org")
#expect(userSettings.liveLocationSharingSessionsByRoomID["!room:matrix.org"] != nil)
#expect(userSettings.app.liveLocationSharingSessionsByRoomID["!room:matrix.org"] != nil)
let deferred = deferFulfillment(userSettings.liveLocationSharingSessionsByRoomIDPublisher) { $0["!room:matrix.org"] == nil }
let deferred = deferFulfillment(userSettings.app.liveLocationSharingSessionsByRoomIDPublisher) { $0["!room:matrix.org"] == nil }
beaconInfoSubject.send(LiveLocationOwnInfoUpdate(roomID: "!room:matrix.org", eventID: "$external_event:matrix.org", isLive: true))
try await deferred.fulfill()
#expect(userSettings.liveLocationSharingSessionsByRoomID["!room:matrix.org"] == nil)
#expect(userSettings.app.liveLocationSharingSessionsByRoomID["!room:matrix.org"] == nil)
}
// MARK: - Reduced accuracy
@@ -237,7 +237,7 @@ final class LiveLocationManagerTests {
}
private func simulateBeaconEcho(roomID: String, eventID: String) async throws {
let deferred = deferFulfillment(userSettings.liveLocationSharingSessionsByRoomIDPublisher) { $0[roomID] != nil }
let deferred = deferFulfillment(userSettings.app.liveLocationSharingSessionsByRoomIDPublisher) { $0[roomID] != nil }
beaconInfoSubject.send(LiveLocationOwnInfoUpdate(roomID: roomID, eventID: eventID, isLive: true))
try await deferred.fulfill()
}
@@ -420,7 +420,7 @@ struct LocationSharingScreenViewModelTests {
let userSettings = UserSettings.mock()
viewModel = LocationSharingScreenViewModel(interactionMode: .viewLive(sender: nil, initialLiveLocationShare: nil),
mapURLBuilder: userSettings.mapTilerConfiguration.publisher.value,
mapURLBuilder: userSettings.app.mapTilerConfiguration.publisher.value,
roomProxy: roomProxyMock,
timelineController: TimelineControllerMock(.init(timelineProxy: TimelineProxyMock(.init()))),
liveLocationManager: LiveLocationManagerMock(.init()),
@@ -546,7 +546,7 @@ struct LocationSharingScreenViewModelTests {
#expect(context.showsUserLocationMode == .hide)
// Another device takes over: this device's session is removed and a new own share arrives.
userSettings.liveLocationSharingSessionsByRoomID.removeValue(forKey: Self.roomID)
userSettings.app.liveLocationSharingSessionsByRoomID.removeValue(forKey: Self.roomID)
let newOwnShare = makeLiveLocationShare(userID: RoomMemberProxyMock.mockMe.userID, latitude: 48.8, longitude: 2.3)
let deferred = deferFulfillment(context.observe(\.showsUserLocationMode)) { $0 == .show }
@@ -633,7 +633,7 @@ struct LocationSharingScreenViewModelTests {
userSettings = UserSettings.mock()
timelineProxy = TimelineProxyMock(.init())
viewModel = LocationSharingScreenViewModel(interactionMode: .picker(shouldShowLiveLocationOption: true),
mapURLBuilder: userSettings.mapTilerConfiguration.publisher.value,
mapURLBuilder: userSettings.app.mapTilerConfiguration.publisher.value,
roomProxy: JoinedRoomProxyMock(.init(members: members)),
timelineController: TimelineControllerMock(.init(timelineProxy: timelineProxy)),
liveLocationManager: liveLocationManagerMock,
@@ -651,7 +651,7 @@ struct LocationSharingScreenViewModelTests {
isSharingLiveLocationFromThisDevice: Bool = false) {
userSettings = UserSettings.mock()
if isSharingLiveLocationFromThisDevice {
userSettings.liveLocationSharingSessionsByRoomID[Self.roomID] = .init(eventID: "$event:matrix.org", expirationDate: .distantFuture)
userSettings.app.liveLocationSharingSessionsByRoomID[Self.roomID] = .init(eventID: "$event:matrix.org", expirationDate: .distantFuture)
}
let liveLocationServiceMock = RoomLiveLocationServiceMock()
@@ -661,7 +661,7 @@ struct LocationSharingScreenViewModelTests {
roomProxyMock.makeLiveLocationServiceReturnValue = liveLocationServiceMock
viewModel = LocationSharingScreenViewModel(interactionMode: .viewLive(sender: sender, initialLiveLocationShare: initialShare),
mapURLBuilder: userSettings.mapTilerConfiguration.publisher.value,
mapURLBuilder: userSettings.app.mapTilerConfiguration.publisher.value,
roomProxy: roomProxyMock,
timelineController: TimelineControllerMock(.init(timelineProxy: TimelineProxyMock(.init()))),
liveLocationManager: LiveLocationManagerMock(.init()),
@@ -29,7 +29,7 @@ final class MediaUploadPreviewScreenViewModelTests {
init() {
userSettings = UserSettings.mock()
userSettings.optimizeMediaUploads = false
userSettings.app.optimizeMediaUploads = false
userIndicatorController = UserIndicatorControllerMock()
}
@@ -18,7 +18,7 @@ final class MediaUploadingPreprocessorTests {
init() {
userSettings = UserSettings.mock()
userSettings.optimizeMediaUploads = false
userSettings.app.optimizeMediaUploads = false
mediaUploadingPreprocessor = MediaUploadingPreprocessor(userSettings: userSettings)
}
@@ -80,7 +80,7 @@ final class MediaUploadingPreprocessorTests {
func landscapeMovVideoOptimizedProcessing() async throws {
let url = try #require(Bundle(for: Self.self).url(forResource: "landscape_test_video.mov", withExtension: nil), "Failed retrieving test asset")
userSettings.optimizeMediaUploads = true
userSettings.app.optimizeMediaUploads = true
guard case let .success(optimizedResult) = await mediaUploadingPreprocessor.processMedia(at: url, maxUploadSize: maxUploadSize),
case let .video(optimizedVideoURL, _, optimizedVideoInfo) = optimizedResult else {
@@ -139,7 +139,7 @@ final class MediaUploadingPreprocessorTests {
func portraitMp4VideoOptimizedProcessing() async throws {
let url = try #require(Bundle(for: Self.self).url(forResource: "portrait_test_video.mp4", withExtension: nil), "Failed retrieving test asset")
userSettings.optimizeMediaUploads = true
userSettings.app.optimizeMediaUploads = true
guard case let .success(optimizedResult) = await mediaUploadingPreprocessor.processMedia(at: url, maxUploadSize: maxUploadSize),
case let .video(optimizedVideoURL, _, optimizedVideoInfo) = optimizedResult else {
@@ -184,7 +184,7 @@ final class MediaUploadingPreprocessorTests {
#expect(imageInfo.thumbnailInfo?.height == 344)
// Repeat with optimised media setting
userSettings.optimizeMediaUploads = true
userSettings.app.optimizeMediaUploads = true
guard case let .success(optimizedResult) = await mediaUploadingPreprocessor.processMedia(at: url, maxUploadSize: maxUploadSize),
case let .image(optimizedImageURL, thumbnailURL, optimizedImageInfo) = optimizedResult else {
@@ -228,7 +228,7 @@ final class MediaUploadingPreprocessorTests {
#expect(imageInfo.thumbnailInfo?.height == 800)
// Repeat with optimised media setting
userSettings.optimizeMediaUploads = true
userSettings.app.optimizeMediaUploads = true
guard case let .success(optimizedResult) = await mediaUploadingPreprocessor.processMedia(at: url, maxUploadSize: maxUploadSize),
case let .image(optimizedImageURL, thumbnailURL, optimizedImageInfo) = optimizedResult else {
@@ -274,7 +274,7 @@ final class MediaUploadingPreprocessorTests {
#expect(imageInfo.thumbnailInfo?.height == 240)
// Repeat with optimised media setting
userSettings.optimizeMediaUploads = true
userSettings.app.optimizeMediaUploads = true
guard case let .success(optimizedResult) = await mediaUploadingPreprocessor.processMedia(at: url, maxUploadSize: maxUploadSize),
case let .image(optimizedImageURL, _, optimizedImageInfo) = optimizedResult else {
@@ -325,7 +325,7 @@ final class MediaUploadingPreprocessorTests {
#expect(imageInfo.thumbnailInfo?.height == 800)
// Repeat with optimised media setting
userSettings.optimizeMediaUploads = true
userSettings.app.optimizeMediaUploads = true
guard case let .success(optimizedResult) = await mediaUploadingPreprocessor.processMedia(at: url, maxUploadSize: maxUploadSize),
case let .image(optimizedImageURL, thumbnailURL, optimizedImageInfo) = optimizedResult else {
@@ -377,7 +377,7 @@ final class MediaUploadingPreprocessorTests {
#expect(imageInfo.thumbnailInfo?.height == 472)
// Repeat with optimised media setting
userSettings.optimizeMediaUploads = true
userSettings.app.optimizeMediaUploads = true
guard case let .success(optimizedResult) = await mediaUploadingPreprocessor.processMedia(at: url, maxUploadSize: maxUploadSize),
case let .image(optimizedImageURL, _, optimizedImageInfo) = optimizedResult else {
@@ -419,7 +419,7 @@ final class MediaUploadingPreprocessorTests {
#expect(imageInfo.thumbnailInfo?.height == 800)
// Repeat with optimised media setting
userSettings.optimizeMediaUploads = true
userSettings.app.optimizeMediaUploads = true
guard case let .success(optimizedResult) = await mediaUploadingPreprocessor.processMedia(at: url, maxUploadSize: maxUploadSize),
case let .image(optimizedImageURL, thumbnailURL, optimizedImageInfo) = optimizedResult else {
@@ -453,7 +453,7 @@ final class MediaUploadingPreprocessorTests {
fatalError()
}
if userSettings.optimizeMediaUploads {
if userSettings.app.optimizeMediaUploads {
// Check that new image has been scaled within the requirements for an optimised image
#expect(convertedImage.size.width <= MediaUploadingPreprocessor.Constants.optimizedMaxPixelSize)
#expect(convertedImage.size.height <= MediaUploadingPreprocessor.Constants.optimizedMaxPixelSize)
@@ -42,16 +42,16 @@ struct NotificationSettingsScreenViewModelTests {
@Test
func enableNotifications() {
userSettings.enableNotifications = false
userSettings.app.enableNotifications = false
context.send(viewAction: .changedEnableNotifications)
#expect(userSettings.enableNotifications)
#expect(userSettings.app.enableNotifications)
}
@Test
func disableNotifications() {
userSettings.enableNotifications = true
userSettings.app.enableNotifications = true
context.send(viewAction: .changedEnableNotifications)
#expect(!userSettings.enableNotifications)
#expect(!userSettings.app.enableNotifications)
}
@Test
@@ -411,7 +411,7 @@ struct NotificationSettingsScreenViewModelTests {
// Given a tone and a manager that persists the selection to app settings
let tone = NotificationTone.createBundledSound(label: "Test", filename: "test.caf")
toneManager.setSelectedToneClosure = { [userSettings] selectedTone in
userSettings.selectedNotificationTone = selectedTone
userSettings.app.selectedNotificationTone = selectedTone
return NotificationToneManager.libraryLocation
}
@@ -419,21 +419,21 @@ struct NotificationSettingsScreenViewModelTests {
context.send(viewAction: .selectAlertTone(tone))
// Then it is reflected in app settings
#expect(userSettings.selectedNotificationTone == tone)
#expect(userSettings.app.selectedNotificationTone == tone)
}
@Test
func deletingActiveToneResetsSelection() {
// Given the active tone is a custom tone
let customTone = NotificationTone.createCustomUserSound(filename: "custom.caf")
userSettings.selectedNotificationTone = customTone
userSettings.app.selectedNotificationTone = customTone
toneManager.customTonesReturnValue = [customTone]
// When that tone is deleted
context.send(viewAction: .deleteCustomAlertTones([customTone]))
// Then the selection is cleared, falling back to the default
#expect(userSettings.selectedNotificationTone == nil)
#expect(userSettings.app.selectedNotificationTone == nil)
}
@Test
@@ -441,14 +441,14 @@ struct NotificationSettingsScreenViewModelTests {
// Given a custom tone is active and a different custom tone also exists
let activeTone = NotificationTone.createCustomUserSound(filename: "active.caf")
let otherTone = NotificationTone.createCustomUserSound(filename: "other.caf")
userSettings.selectedNotificationTone = activeTone
userSettings.app.selectedNotificationTone = activeTone
toneManager.customTonesReturnValue = [activeTone, otherTone]
// When the non-active tone is deleted
context.send(viewAction: .deleteCustomAlertTones([otherTone]))
// Then the active selection is unchanged
#expect(userSettings.selectedNotificationTone == activeTone)
#expect(userSettings.app.selectedNotificationTone == activeTone)
}
@Test
@@ -476,7 +476,7 @@ struct NotificationSettingsScreenViewModelTests {
func deleteCustomToneFailurePreservesSelection() {
// Given the active tone is a custom tone and deletion will fail
let customTone = NotificationTone.createCustomUserSound(filename: "custom.caf")
userSettings.selectedNotificationTone = customTone
userSettings.app.selectedNotificationTone = customTone
toneManager.deleteCustomToneThrowableError = NSError(domain: "test", code: 1)
toneManager.customTonesReturnValue = [customTone]
@@ -484,7 +484,7 @@ struct NotificationSettingsScreenViewModelTests {
context.send(viewAction: .deleteCustomAlertTones([customTone]))
// Then the selection is unchanged
#expect(userSettings.selectedNotificationTone == customTone)
#expect(userSettings.app.selectedNotificationTone == customTone)
}
@Test
+12 -12
View File
@@ -39,7 +39,7 @@ struct PresenceServiceTests {
@Test
func serviceSeededActiveWhileSharingReportsOnline() async throws {
userSettings.sharePresence = true
userSettings.app.sharePresence = true
let deferred = deferFulfillment(sendsSubject) { $0.presence == .online }
let service = makeService(initialApplicationState: .active)
@@ -53,7 +53,7 @@ struct PresenceServiceTests {
@Test
func serviceSeededBackgroundedWhileSharingReportsUnavailable() async throws {
userSettings.sharePresence = true
userSettings.app.sharePresence = true
let deferred = deferFulfillment(sendsSubject) { $0.presence == .unavailable }
let service = makeService(initialApplicationState: .background)
@@ -66,7 +66,7 @@ struct PresenceServiceTests {
@Test
func serviceSeededActiveWhileNotSharingReportsOffline() async throws {
userSettings.sharePresence = false
userSettings.app.sharePresence = false
let deferred = deferFulfillment(sendsSubject) { $0.presence == .offline }
let service = makeService(initialApplicationState: .active)
@@ -79,7 +79,7 @@ struct PresenceServiceTests {
@Test
func becomingActiveNotificationWhileSharingReportsOnlineImmediately() async throws {
userSettings.sharePresence = true
userSettings.app.sharePresence = true
let service = makeService(initialApplicationState: .inactive)
let deferred = deferFulfillment(sendsSubject) { $0.presence == .online }
@@ -93,7 +93,7 @@ struct PresenceServiceTests {
@Test
func resigningActiveNotificationWhileSharingReportsUnavailableImmediately() async throws {
userSettings.sharePresence = true
userSettings.app.sharePresence = true
let initialDeferred = deferFulfillment(sendsSubject) { $0.presence == .online }
let service = makeService(initialApplicationState: .active)
_ = try await initialDeferred.fulfill()
@@ -109,19 +109,19 @@ struct PresenceServiceTests {
@Test
func togglingSharingWhileActiveDrivesOnlineAndOffline() async throws {
userSettings.sharePresence = false
userSettings.app.sharePresence = false
let initialDeferred = deferFulfillment(sendsSubject) { $0.presence == .offline }
let service = makeService(initialApplicationState: .active)
_ = try await initialDeferred.fulfill()
let onlineDeferred = deferFulfillment(sendsSubject) { $0.presence == .online }
userSettings.sharePresence = true
userSettings.app.sharePresence = true
let online = try await onlineDeferred.fulfill()
#expect(online.presence == .online)
#expect(online.sendImmediately)
let offlineDeferred = deferFulfillment(sendsSubject) { $0.presence == .offline }
userSettings.sharePresence = false
userSettings.app.sharePresence = false
let offline = try await offlineDeferred.fulfill()
#expect(offline.presence == .offline)
#expect(offline.sendImmediately)
@@ -131,7 +131,7 @@ struct PresenceServiceTests {
@Test
func repeatingTheSameStateSendsOnlyOnce() async throws {
userSettings.sharePresence = true
userSettings.app.sharePresence = true
let initialDeferred = deferFulfillment(sendsSubject) { $0.presence == .online }
let service = makeService(initialApplicationState: .active)
@@ -148,7 +148,7 @@ struct PresenceServiceTests {
@Test
func failedImmediateSendIsNotRetriedUntilTheDesiredPresenceChanges() async throws {
userSettings.sharePresence = true
userSettings.app.sharePresence = true
clientProxy.configurePresenceSendImmediatelyClosure = { [sendsSubject] presence, sendImmediately in
sendsSubject.send((presence, sendImmediately))
return presence == .online ? .failure(.forbiddenAccess) : .success(())
@@ -173,7 +173,7 @@ struct PresenceServiceTests {
@Test
func newerTransitionWinsWhenAnEarlierSendIsInFlight() async throws {
userSettings.sharePresence = true
userSettings.app.sharePresence = true
let (onlineStartedStream, onlineStartedContinuation) = AsyncStream<Void>.makeStream()
let (resumeOnlineSendStream, resumeOnlineSendContinuation) = AsyncStream<Void>.makeStream()
@@ -206,7 +206,7 @@ struct PresenceServiceTests {
@Test
func deinitCancelsPendingSend() async throws {
userSettings.sharePresence = true
userSettings.app.sharePresence = true
let (onlineStartedStream, onlineStartedContinuation) = AsyncStream.makeStream(of: Void.self)
let (onlineFinishedStream, onlineFinishedContinuation) = AsyncStream.makeStream(of: Void.self)
@@ -230,7 +230,7 @@ final class RoomFlowCoordinatorTests {
@Test
func threadedEventRoutes() async throws {
userSettings.threadsEnabled = true
userSettings.app.threadsEnabled = true
setupRoomFlowCoordinator()
// Navigate directly to the threaded event
@@ -148,12 +148,12 @@ final class RoomListFiltersStateTests {
// MARK: - Helpers
private func enableLowPriorityFeature() {
userSettings.lowPriorityFilterEnabled = true
userSettings.app.lowPriorityFilterEnabled = true
state = RoomListFiltersState(userSettings: userSettings)
}
private func enableMentionsFeature() {
userSettings.mentionsFilterEnabled = true
userSettings.app.mentionsFilterEnabled = true
state = RoomListFiltersState(userSettings: userSettings)
}
}
@@ -154,7 +154,7 @@ final class RoomScreenViewModelTests {
@Test
func pinnedEventsBannerThreadedSelection() async throws {
let userSettings = UserSettings.mock()
userSettings.threadsEnabled = true
userSettings.app.threadsEnabled = true
let userSession = UserSessionMock(.init(userSettings: userSettings))
let roomProxyMock = JoinedRoomProxyMock(.init())
@@ -123,7 +123,7 @@ final class RoomSummaryProviderTests {
private func setup(isLowPriorityFilterEnabled: Bool = false, includesOnlyJoinedRooms: Bool = false, setsRoomList: Bool = true) {
userSettings = UserSettings.mock()
userSettings.lowPriorityFilterEnabled = isLowPriorityFilterEnabled
userSettings.app.lowPriorityFilterEnabled = isLowPriorityFilterEnabled
let stateEventStringBuilder = RoomStateEventStringBuilder(userID: "@me:matrix.org")
let attributedStringBuilder = AttributedStringBuilder(mentionBuilder: MentionBuilder())
@@ -124,44 +124,44 @@ struct SearchScreenViewModelTests {
context.searchQuery = "Second"
context.send(viewAction: .selectRoom(roomID: "2"))
#expect(userSettings.searchBreadcrumbs == [.room(roomID: "2")])
#expect(userSettings.account.searchBreadcrumbs == [.room(roomID: "2")])
context.send(viewAction: .submitQuery)
#expect(userSettings.searchBreadcrumbs == [.query("Second"), .room(roomID: "2")])
#expect(userSettings.account.searchBreadcrumbs == [.query("Second"), .room(roomID: "2")])
// Selecting the same room again from the empty state only bumps it back to the top.
context.searchQuery = ""
context.send(viewAction: .selectRoom(roomID: "2"))
#expect(userSettings.searchBreadcrumbs == [.room(roomID: "2"), .query("Second")])
#expect(userSettings.account.searchBreadcrumbs == [.room(roomID: "2"), .query("Second")])
// Submitting an empty query records nothing.
context.send(viewAction: .submitQuery)
#expect(userSettings.searchBreadcrumbs == [.room(roomID: "2"), .query("Second")])
#expect(userSettings.account.searchBreadcrumbs == [.room(roomID: "2"), .query("Second")])
}
@Test
func removingBreadcrumbs() {
userSettings.searchBreadcrumbs = [.query("Second"), .room(roomID: "2")]
userSettings.account.searchBreadcrumbs = [.query("Second"), .room(roomID: "2")]
context.send(viewAction: .removeBreadcrumb(.query("Second")))
#expect(userSettings.searchBreadcrumbs == [.room(roomID: "2")])
#expect(userSettings.account.searchBreadcrumbs == [.room(roomID: "2")])
context.send(viewAction: .removeBreadcrumb(.room(SearchScreenRoom(.mock(id: "2", name: "Second")))))
#expect(userSettings.searchBreadcrumbs.isEmpty)
#expect(userSettings.account.searchBreadcrumbs.isEmpty)
}
@Test
func clearingBreadcrumbs() {
userSettings.searchBreadcrumbs = [.query("Second"), .room(roomID: "2")]
userSettings.account.searchBreadcrumbs = [.query("Second"), .room(roomID: "2")]
context.send(viewAction: .clearBreadcrumbs)
#expect(userSettings.searchBreadcrumbs.isEmpty)
#expect(userSettings.account.searchBreadcrumbs.isEmpty)
}
@Test
@@ -169,7 +169,7 @@ struct SearchScreenViewModelTests {
staticRoomListSubject.send([.mock(id: "2", name: "Second")])
let deferred = deferFulfillment(context.observe(\.viewState.breadcrumbs)) { $0.count == 2 }
userSettings.searchBreadcrumbs = [.query("Second"), .room(roomID: "2")]
userSettings.account.searchBreadcrumbs = [.query("Second"), .room(roomID: "2")]
try await deferred.fulfill()
#expect(context.viewState.breadcrumbs.map(\.id) == ["query-Second", "room-2"])
@@ -180,7 +180,7 @@ struct SearchScreenViewModelTests {
staticRoomListSubject.send([.mock(id: "2", name: "Second"), .mock(id: "left", name: "Left", isJoined: false)])
let deferred = deferFulfillment(context.observe(\.viewState.breadcrumbs)) { !$0.isEmpty }
userSettings.searchBreadcrumbs = [.room(roomID: "left"), .room(roomID: "2")]
userSettings.account.searchBreadcrumbs = [.room(roomID: "left"), .room(roomID: "2")]
try await deferred.fulfill()
#expect(context.viewState.breadcrumbs.map(\.id) == ["room-2"])
@@ -190,7 +190,7 @@ struct SearchScreenViewModelTests {
@Test
func roomBreadcrumbsAreDisplayedAfterTheRoomListLoads() async throws {
var deferred = deferFulfillment(context.observe(\.viewState.breadcrumbs)) { $0.count == 1 }
userSettings.searchBreadcrumbs = [.query("Second"), .room(roomID: "2")]
userSettings.account.searchBreadcrumbs = [.query("Second"), .room(roomID: "2")]
try await deferred.fulfill()
#expect(context.viewState.breadcrumbs.map(\.id) == ["query-Second"])
@@ -458,8 +458,8 @@ final class SecurityAndPrivacyScreenViewModelTests {
joinRule: ElementX.JoinRule,
forceDisableE2EE: Bool = false) {
let userSettings = UserSettings.mock()
userSettings.knockingEnabled = true
userSettings.forceDisableE2EE.applyRemoteValue(forceDisableE2EE)
userSettings.app.knockingEnabled = true
userSettings.app.forceDisableE2EE.applyRemoteValue(forceDisableE2EE)
roomProxy = JoinedRoomProxyMock(.init(isEncrypted: false,
canonicalAlias: "#room:matrix.org",
members: .allMembersAsCreator,
@@ -181,11 +181,11 @@ struct SettingsScreenViewModelTests {
#expect(!context.viewState.showAddAccountButton)
var deferred = deferFulfillment(context.observe(\.viewState.showAddAccountButton)) { $0 }
userSettings.multiAccountEnabled = true
userSettings.app.multiAccountEnabled = true
try await deferred.fulfill()
deferred = deferFulfillment(context.observe(\.viewState.showAddAccountButton)) { !$0 }
userSettings.multiAccountEnabled = false
userSettings.app.multiAccountEnabled = false
try await deferred.fulfill()
}
@@ -900,14 +900,14 @@ final class TimelineViewModelTests {
func disablingTheFlagClearsTheSelection() async throws {
let items = [TextRoomTimelineItem(eventID: "$1")]
let userSettings = UserSettings.mock()
userSettings.messageMultiSelectEnabled = true
userSettings.app.messageMultiSelectEnabled = true
let viewModel = makeViewModel(timelineController: TimelineControllerMock(.init(timelineItems: items)), userSettings: userSettings)
startSelection(items[0].id, in: viewModel)
#expect(viewModel.state.messageSelection.isActive)
let deferred = deferFulfillment(viewModel.context.$viewState) { !$0.messageSelection.isEnabled && !$0.messageSelection.isActive }
userSettings.messageMultiSelectEnabled = false
userSettings.app.messageMultiSelectEnabled = false
try await deferred.fulfill()
}
@@ -945,7 +945,7 @@ final class TimelineViewModelTests {
private func makeSelectionViewModel(timelineController: TimelineControllerProtocol,
userIndicatorController: UserIndicatorControllerProtocol = UserIndicatorControllerMock()) -> TimelineViewModel {
let userSettings = UserSettings.mock()
userSettings.messageMultiSelectEnabled = true
userSettings.app.messageMultiSelectEnabled = true
return makeViewModel(timelineController: timelineController,
userIndicatorController: userIndicatorController,
userSettings: userSettings)
+31 -31
View File
@@ -24,31 +24,31 @@ struct UserSettingsTests {
@Test
func accountSettingsAreScopedToAccount() {
// Given settings for two different accounts.
#expect(aliceSettings.searchBreadcrumbs.isEmpty)
#expect(bobSettings.searchBreadcrumbs.isEmpty)
#expect(aliceSettings.account.searchBreadcrumbs.isEmpty)
#expect(bobSettings.account.searchBreadcrumbs.isEmpty)
// When updating an account setting for Alice.
aliceSettings.searchBreadcrumbs = [.query("Alice")]
aliceSettings.account.searchBreadcrumbs = [.query("Alice")]
// Then only Alice should see the change.
#expect(aliceSettings.searchBreadcrumbs == [.query("Alice")])
#expect(bobSettings.searchBreadcrumbs.isEmpty)
#expect(aliceSettings.account.searchBreadcrumbs == [.query("Alice")])
#expect(bobSettings.account.searchBreadcrumbs.isEmpty)
}
@Test
func appSettingsAreShared() {
// Given settings for two different accounts.
#expect(!appSettings.hasSignedInBefore)
#expect(!aliceSettings.hasSignedInBefore)
#expect(!bobSettings.hasSignedInBefore)
#expect(!aliceSettings.app.hasSignedInBefore)
#expect(!bobSettings.app.hasSignedInBefore)
// When updating an app setting through Alice's settings.
aliceSettings.hasSignedInBefore = true
aliceSettings.app.hasSignedInBefore = true
// Then the change should be seen by the app and every account.
#expect(appSettings.hasSignedInBefore)
#expect(aliceSettings.hasSignedInBefore)
#expect(bobSettings.hasSignedInBefore)
#expect(aliceSettings.app.hasSignedInBefore)
#expect(bobSettings.app.hasSignedInBefore)
}
@Test
@@ -59,18 +59,18 @@ struct UserSettingsTests {
try store.set(JSONEncoder().encode([SearchBreadcrumb.query("Alice")]), forKey: "searchBreadcrumbs")
#expect(store.object(forKey: "hasRunIdentityConfirmationOnboarding") != nil)
#expect(store.object(forKey: "searchBreadcrumbs") != nil)
#expect(!aliceSettings.hasRunIdentityConfirmationOnboarding)
#expect(aliceSettings.searchBreadcrumbs.isEmpty)
#expect(!aliceSettings.account.hasRunIdentityConfirmationOnboarding)
#expect(aliceSettings.account.searchBreadcrumbs.isEmpty)
// When migrating those values into Alice's account settings.
aliceSettings.migrateAppSettingsValueToAccountSettings(\.hasRunIdentityConfirmationOnboardingKey)
aliceSettings.migrateAppSettingsValueToAccountSettings(\.searchBreadcrumbsKey)
aliceSettings.account.migrateAppSettingsValue(\.hasRunIdentityConfirmationOnboardingKey)
aliceSettings.account.migrateAppSettingsValue(\.searchBreadcrumbsKey)
// Then the values should belong to Alice only.
#expect(aliceSettings.hasRunIdentityConfirmationOnboarding)
#expect(aliceSettings.searchBreadcrumbs == [.query("Alice")])
#expect(!bobSettings.hasRunIdentityConfirmationOnboarding)
#expect(bobSettings.searchBreadcrumbs.isEmpty)
#expect(aliceSettings.account.hasRunIdentityConfirmationOnboarding)
#expect(aliceSettings.account.searchBreadcrumbs == [.query("Alice")])
#expect(!bobSettings.account.hasRunIdentityConfirmationOnboarding)
#expect(bobSettings.account.searchBreadcrumbs.isEmpty)
#expect(store.object(forKey: "hasRunIdentityConfirmationOnboarding") == nil)
#expect(store.object(forKey: "searchBreadcrumbs") == nil)
}
@@ -79,24 +79,24 @@ struct UserSettingsTests {
func resetSessionSpecificSettings() {
// Given app settings and account settings for two different accounts.
appSettings.hasSignedInBefore = true
aliceSettings.hasRunIdentityConfirmationOnboarding = true
aliceSettings.searchBreadcrumbs = [.query("Alice")]
bobSettings.hasRunIdentityConfirmationOnboarding = true
bobSettings.searchBreadcrumbs = [.query("Bob")]
aliceSettings.account.hasRunIdentityConfirmationOnboarding = true
aliceSettings.account.searchBreadcrumbs = [.query("Alice")]
bobSettings.account.hasRunIdentityConfirmationOnboarding = true
bobSettings.account.searchBreadcrumbs = [.query("Bob")]
#expect(appSettings.hasSignedInBefore)
#expect(aliceSettings.hasRunIdentityConfirmationOnboarding)
#expect(aliceSettings.searchBreadcrumbs == [.query("Alice")])
#expect(bobSettings.hasRunIdentityConfirmationOnboarding)
#expect(bobSettings.searchBreadcrumbs == [.query("Bob")])
#expect(aliceSettings.account.hasRunIdentityConfirmationOnboarding)
#expect(aliceSettings.account.searchBreadcrumbs == [.query("Alice")])
#expect(bobSettings.account.hasRunIdentityConfirmationOnboarding)
#expect(bobSettings.account.searchBreadcrumbs == [.query("Bob")])
// When resetting Alice's session specific settings.
aliceSettings.resetSessionSpecificSettings()
aliceSettings.account.reset()
// Then only Alice's account settings should be reset.
#expect(!aliceSettings.hasRunIdentityConfirmationOnboarding)
#expect(aliceSettings.searchBreadcrumbs.isEmpty)
#expect(bobSettings.hasRunIdentityConfirmationOnboarding)
#expect(bobSettings.searchBreadcrumbs == [.query("Bob")])
#expect(!aliceSettings.account.hasRunIdentityConfirmationOnboarding)
#expect(aliceSettings.account.searchBreadcrumbs.isEmpty)
#expect(bobSettings.account.hasRunIdentityConfirmationOnboarding)
#expect(bobSettings.account.searchBreadcrumbs == [.query("Bob")])
#expect(appSettings.hasSignedInBefore)
}
}