From 2c6ae72139d8d816eb63f054ccb7711bf099e67c Mon Sep 17 00:00:00 2001 From: Nicolas Mauri Date: Fri, 28 Jul 2023 18:02:53 +0200 Subject: [PATCH] Code refactoring --- ...otificationSettingsScreenCoordinator.swift | 2 +- .../NotificationSettingsScreenModels.swift | 58 ++----- .../NotificationSettingsScreenViewModel.swift | 155 ++++++++---------- ...ationSettingsScreenViewModelProtocol.swift | 2 +- .../View/NotificationSettingsScreen.swift | 78 +++++---- .../NotificationManagerTests.swift | 1 + ...ficationSettingsScreenViewModelTests.swift | 107 ++++++------ 7 files changed, 188 insertions(+), 215 deletions(-) diff --git a/ElementX/Sources/Screens/Settings/NotificationSettingsScreen/NotificationSettingsScreenCoordinator.swift b/ElementX/Sources/Screens/Settings/NotificationSettingsScreen/NotificationSettingsScreenCoordinator.swift index 8c6ce2379e..7a27db3868 100644 --- a/ElementX/Sources/Screens/Settings/NotificationSettingsScreen/NotificationSettingsScreenCoordinator.swift +++ b/ElementX/Sources/Screens/Settings/NotificationSettingsScreen/NotificationSettingsScreenCoordinator.swift @@ -43,7 +43,7 @@ final class NotificationSettingsScreenCoordinator: CoordinatorProtocol { } func start() { - viewModel.start() + viewModel.fetchInitialContent() } func toPresentable() -> AnyView { diff --git a/ElementX/Sources/Screens/Settings/NotificationSettingsScreen/NotificationSettingsScreenModels.swift b/ElementX/Sources/Screens/Settings/NotificationSettingsScreen/NotificationSettingsScreenModels.swift index 788f447feb..2d6d8d2e14 100644 --- a/ElementX/Sources/Screens/Settings/NotificationSettingsScreen/NotificationSettingsScreenModels.swift +++ b/ElementX/Sources/Screens/Settings/NotificationSettingsScreen/NotificationSettingsScreenModels.swift @@ -28,21 +28,28 @@ struct NotificationSettingsScreenViewState: BindableState { var showSystemNotificationsAlert: Bool { bindings.enableNotifications && isUserPermissionGranted == false } - - var groupChatNotificationSettingsState: NotificationSettingsScreenModeState = .loading - var directChatNotificationSettingsState: NotificationSettingsScreenModeState = .loading + + var settings: NotificationSettingsScreenSettings? var applyingChange = false - var inconsistentGroupChatsSettings = false - var inconsistentDirectChatsSettings = false } struct NotificationSettingsScreenViewStateBindings { var enableNotifications = false - var enableRoomMention = false - var enableCalls = false + var roomMentionsEnabled = false + var callsEnabled = false var alertInfo: AlertInfo? } +struct NotificationSettingsScreenSettings { + let groupChatsMode: RoomNotificationModeProxy + let directChatsMode: RoomNotificationModeProxy + let roomMentionsEnabled: Bool? + let callsEnabled: Bool? + // Old clients were having specific settings for encrypted and unencrypted rooms, + // so it's possible for `group chats` and `direct chats` settings to be inconsistent (e.g. encrypted `direct chats` can have a different mode that unencrypted `direct chats`) + let inconsistentSettings: Bool +} + struct NotificationSettingsScreenStrings { let changeYourSystemSettings: AttributedString = { let linkPlaceholder = "{link}" @@ -71,39 +78,10 @@ struct NotificationSettingsScreenStrings { enum NotificationSettingsScreenViewAction { case linkClicked(url: URL) case changedEnableNotifications - case processTapGroupChats - case processTapDirectChats - case processToggleRoomMention - case processToggleCalls -} - -enum NotificationSettingsScreenModeState { - case loading - case loaded(mode: RoomNotificationModeProxy) - case error -} - -extension NotificationSettingsScreenModeState { - var isLoading: Bool { - if case .loading = self { - return true - } - return false - } - - var isLoaded: Bool { - if case .loaded = self { - return true - } - return false - } - - var isError: Bool { - if case .error = self { - return true - } - return false - } + case groupChatsTapped + case directChatsTapped + case roomMentionChanged + case callsChanged } enum NotificationSettingsScreenErrorType: Hashable { diff --git a/ElementX/Sources/Screens/Settings/NotificationSettingsScreen/NotificationSettingsScreenViewModel.swift b/ElementX/Sources/Screens/Settings/NotificationSettingsScreen/NotificationSettingsScreenViewModel.swift index 53cb0dcadb..464d2a1aa5 100644 --- a/ElementX/Sources/Screens/Settings/NotificationSettingsScreen/NotificationSettingsScreenViewModel.swift +++ b/ElementX/Sources/Screens/Settings/NotificationSettingsScreen/NotificationSettingsScreenViewModel.swift @@ -47,7 +47,7 @@ class NotificationSettingsScreenViewModel: NotificationSettingsScreenViewModelTy setupNotificationSettingsSubscription() } - func start() { + func fetchInitialContent() { fetchSettings() } @@ -59,14 +59,20 @@ class NotificationSettingsScreenViewModel: NotificationSettingsScreenViewModelTy MXLog.warning("Link clicked: \(url)") case .changedEnableNotifications: toggleNotifications() - case .processTapGroupChats: + case .groupChatsTapped: break - case .processTapDirectChats: + case .directChatsTapped: break - case .processToggleRoomMention: - toggleRoomMention() - case .processToggleCalls: - toggleCalls() + case .roomMentionChanged: + guard let settings = state.settings, settings.roomMentionsEnabled != state.bindings.roomMentionsEnabled else { + return + } + Task { await enableRoomMention(state.bindings.roomMentionsEnabled) } + case .callsChanged: + guard let settings = state.settings, settings.callsEnabled != state.bindings.callsEnabled else { + return + } + Task { await enableCalls(state.bindings.callsEnabled) } } } @@ -109,104 +115,73 @@ class NotificationSettingsScreenViewModel: NotificationSettingsScreenViewModelTy .store(in: &cancellables) } - private struct Settings { - var groupChatsMode: RoomNotificationModeProxy = .allMessages - var encryptedGroupChatsMode: RoomNotificationModeProxy = .allMessages - var directChatsMode: RoomNotificationModeProxy = .allMessages - var encryptedDirectChatsMode: RoomNotificationModeProxy = .allMessages - var roomMentionState: Result = .success(false) - var callState: Result = .success(false) - } - private func fetchSettings() { fetchSettingsTask = Task { - var settings = Settings() - // Group chats - settings.groupChatsMode = await notificationSettingsProxy.getDefaultNotificationRoomMode(isEncrypted: false, activeMembersCount: 3) - settings.encryptedGroupChatsMode = await notificationSettingsProxy.getDefaultNotificationRoomMode(isEncrypted: true, activeMembersCount: 3) + // A group chat is a chat having more than 2 active members + let groupChatActiveMembers: UInt64 = 3 + var groupChatsMode = await notificationSettingsProxy.getDefaultNotificationRoomMode(isEncrypted: false, activeMembersCount: groupChatActiveMembers) + let encryptedGroupChatsMode = await notificationSettingsProxy.getDefaultNotificationRoomMode(isEncrypted: true, activeMembersCount: groupChatActiveMembers) // Direct chats - settings.directChatsMode = await notificationSettingsProxy.getDefaultNotificationRoomMode(isEncrypted: false, activeMembersCount: 2) - settings.encryptedDirectChatsMode = await notificationSettingsProxy.getDefaultNotificationRoomMode(isEncrypted: true, activeMembersCount: 2) - - // Room mentions - do { - settings.roomMentionState = try await .success(notificationSettingsProxy.isRoomMentionEnabled()) - } catch { - settings.roomMentionState = .failure(error) + // A direct chat is a chat having exactly 2 active members + let directChatActiveMembers: UInt64 = 2 + var directChatsMode = await notificationSettingsProxy.getDefaultNotificationRoomMode(isEncrypted: false, activeMembersCount: directChatActiveMembers) + let encryptedDirectChatsMode = await notificationSettingsProxy.getDefaultNotificationRoomMode(isEncrypted: true, activeMembersCount: directChatActiveMembers) + + // Old clients were having specific settings for encrypted and unencrypted rooms, + // so it's possible for `group chats` and `direct chats` settings to be inconsistent (e.g. encrypted `direct chats` can have a different mode that unencrypted `direct chats`) + var inconsistencyDetected = false + if groupChatsMode != encryptedGroupChatsMode { + groupChatsMode = .allMessages + inconsistencyDetected = true } - - // Calls - do { - settings.callState = try await .success(notificationSettingsProxy.isCallEnabled()) - } catch { - settings.callState = .failure(error) + if directChatsMode != encryptedDirectChatsMode { + directChatsMode = .allMessages + inconsistencyDetected = true } + // The following calls may fail if the associated push rule doesn't exist + let roomMentionsEnabled = try? await notificationSettingsProxy.isRoomMentionEnabled() + let callEnabled = try? await notificationSettingsProxy.isCallEnabled() + guard !Task.isCancelled else { return } - - applySettings(settings) - } - } - - private func applySettings(_ settings: Settings) { - if settings.groupChatsMode == settings.encryptedGroupChatsMode { - state.groupChatNotificationSettingsState = .loaded(mode: settings.groupChatsMode) - state.inconsistentGroupChatsSettings = false - } else { - state.groupChatNotificationSettingsState = .loaded(mode: .allMessages) - state.inconsistentGroupChatsSettings = true - } - - if settings.directChatsMode == settings.encryptedDirectChatsMode { - state.directChatNotificationSettingsState = .loaded(mode: settings.directChatsMode) - state.inconsistentDirectChatsSettings = false - } else { - state.directChatNotificationSettingsState = .loaded(mode: .allMessages) - state.inconsistentDirectChatsSettings = true - } - - if case .success(let enabled) = settings.roomMentionState { - state.bindings.enableRoomMention = enabled - } else { - state.bindings.enableRoomMention = false - } - - if case .success(let enabled) = settings.callState { - state.bindings.enableCalls = enabled - } else { - state.bindings.enableCalls = false + + let notificationSettings = NotificationSettingsScreenSettings(groupChatsMode: groupChatsMode, + directChatsMode: directChatsMode, + roomMentionsEnabled: roomMentionsEnabled, + callsEnabled: callEnabled, + inconsistentSettings: inconsistencyDetected) + + state.settings = notificationSettings + state.bindings.roomMentionsEnabled = notificationSettings.roomMentionsEnabled ?? false + state.bindings.callsEnabled = notificationSettings.callsEnabled ?? false } } - private func toggleRoomMention() { - Task { - do { - let currentValue = try await notificationSettingsProxy.isRoomMentionEnabled() - let newValue = state.bindings.enableRoomMention - guard currentValue != newValue else { return } - state.applyingChange = true - try await notificationSettingsProxy.setRoomMentionEnabled(enabled: newValue) - } catch { - state.bindings.alertInfo = AlertInfo(id: .alert) - } - state.applyingChange = false + private func enableRoomMention(_ enable: Bool) async { + guard let notificationSettings = state.settings else { return } + do { + state.applyingChange = true + MXLog.info("[NotificationSettingsScreenViewMode] setRoomMentionEnabled(\(enable))") + try await notificationSettingsProxy.setRoomMentionEnabled(enabled: enable) + } catch { + state.bindings.alertInfo = AlertInfo(id: .alert) + state.bindings.roomMentionsEnabled = notificationSettings.roomMentionsEnabled ?? false } + state.applyingChange = false } - func toggleCalls() { - Task { - do { - let currentValue = try await notificationSettingsProxy.isCallEnabled() - let newValue = state.bindings.enableCalls - guard currentValue != newValue else { return } - state.applyingChange = true - try await notificationSettingsProxy.setCallEnabled(enabled: newValue) - } catch { - state.bindings.alertInfo = AlertInfo(id: .alert) - } - state.applyingChange = false + func enableCalls(_ enable: Bool) async { + guard let notificationSettings = state.settings else { return } + do { + state.applyingChange = true + MXLog.info("[NotificationSettingsScreenViewMode] setCallEnabled(\(enable))") + try await notificationSettingsProxy.setCallEnabled(enabled: enable) + } catch { + state.bindings.alertInfo = AlertInfo(id: .alert) + state.bindings.callsEnabled = notificationSettings.callsEnabled ?? false } + state.applyingChange = false } } diff --git a/ElementX/Sources/Screens/Settings/NotificationSettingsScreen/NotificationSettingsScreenViewModelProtocol.swift b/ElementX/Sources/Screens/Settings/NotificationSettingsScreen/NotificationSettingsScreenViewModelProtocol.swift index 4928041295..418527361d 100644 --- a/ElementX/Sources/Screens/Settings/NotificationSettingsScreen/NotificationSettingsScreenViewModelProtocol.swift +++ b/ElementX/Sources/Screens/Settings/NotificationSettingsScreen/NotificationSettingsScreenViewModelProtocol.swift @@ -21,5 +21,5 @@ protocol NotificationSettingsScreenViewModelProtocol { var actions: AnyPublisher { get } var context: NotificationSettingsScreenViewModelType.Context { get } - func start() + func fetchInitialContent() } diff --git a/ElementX/Sources/Screens/Settings/NotificationSettingsScreen/View/NotificationSettingsScreen.swift b/ElementX/Sources/Screens/Settings/NotificationSettingsScreen/View/NotificationSettingsScreen.swift index 4add600b25..9efe102655 100644 --- a/ElementX/Sources/Screens/Settings/NotificationSettingsScreen/View/NotificationSettingsScreen.swift +++ b/ElementX/Sources/Screens/Settings/NotificationSettingsScreen/View/NotificationSettingsScreen.swift @@ -27,8 +27,12 @@ struct NotificationSettingsScreen: View { enableNotificationSection if context.enableNotifications { roomsNotificationSection - mentionsSection - callsSection + if context.viewState.settings?.roomMentionsEnabled != nil { + mentionsSection + } + if context.viewState.settings?.callsEnabled != nil { + callsSection + } } } .compoundForm() @@ -76,85 +80,83 @@ struct NotificationSettingsScreen: View { .compoundFormSection() } - @ViewBuilder private var roomsNotificationSection: some View { Section { // Group chats Button { - context.send(viewAction: .processTapGroupChats) + context.send(viewAction: .groupChatsTapped) } label: { LabeledContent { - notificationModeStateView(context.viewState.groupChatNotificationSettingsState) + if let settings = context.viewState.settings { + Text(context.viewState.strings.string(for: settings.groupChatsMode)) + } else { + ProgressView() + } } label: { Text(L10n.screenNotificationSettingsGroupChats) } } .accessibilityIdentifier(A11yIdentifiers.roomDetailsScreen.notifications) - .buttonStyle(.compoundForm(accessory: context.viewState.groupChatNotificationSettingsState.isLoaded ? .navigationLink : nil)) - .disabled(context.viewState.groupChatNotificationSettingsState.isLoading) + .buttonStyle(.compoundForm(accessory: context.viewState.settings.flatMap { _ in .navigationLink })) + .disabled(context.viewState.settings == nil) // Direct chats Button { - context.send(viewAction: .processTapDirectChats) + context.send(viewAction: .directChatsTapped) } label: { LabeledContent { - notificationModeStateView(context.viewState.directChatNotificationSettingsState) + if let settings = context.viewState.settings { + Text(context.viewState.strings.string(for: settings.directChatsMode)) + } else { + ProgressView() + } } label: { Text(L10n.screenNotificationSettingsDirectChats) } } .accessibilityIdentifier(A11yIdentifiers.roomDetailsScreen.notifications) - .buttonStyle(.compoundForm(accessory: context.viewState.groupChatNotificationSettingsState.isLoaded ? .navigationLink : nil)) - .disabled(context.viewState.groupChatNotificationSettingsState.isLoading) + .buttonStyle(.compoundForm(accessory: context.viewState.settings.flatMap { _ in .navigationLink })) + .disabled(context.viewState.settings == nil) } header: { Text(L10n.screenNotificationSettingsNotificationSectionTitle) + .compoundFormSectionHeader() } .compoundFormSection() } - @ViewBuilder - private func notificationModeStateView(_ state: NotificationSettingsScreenModeState) -> some View { - switch state { - case .loading: - ProgressView() - case .loaded(let mode): - Text(context.viewState.strings.string(for: mode)) - case .error: - Image(systemName: "exclamationmark.circle") - } - } - - @ViewBuilder private var mentionsSection: some View { Section { - Toggle(isOn: $context.enableRoomMention) { + Toggle(isOn: $context.roomMentionsEnabled) { Text(L10n.screenNotificationSettingsRoomMentionLabel) } .toggleStyle(.compoundForm) - .onChange(of: context.enableRoomMention) { _ in - context.send(viewAction: .processToggleRoomMention) + .onChange(of: context.roomMentionsEnabled) { _ in + context.send(viewAction: .roomMentionChanged) } + .disabled(context.viewState.settings?.roomMentionsEnabled == nil) .allowsHitTesting(!context.viewState.applyingChange) } header: { Text(L10n.screenNotificationSettingsMentionsSectionTitle) + .compoundFormSectionHeader() } .compoundFormSection() } - @ViewBuilder private var callsSection: some View { Section { - Toggle(isOn: $context.enableCalls) { + Toggle(isOn: $context.callsEnabled) { Text(L10n.screenNotificationSettingsCallsLabel) } .toggleStyle(.compoundForm) - .onChange(of: context.enableCalls) { _ in - context.send(viewAction: .processToggleCalls) + .onChange(of: context.callsEnabled) { _ in + context.send(viewAction: .callsChanged) } + .disabled(context.viewState.settings?.callsEnabled == nil) .allowsHitTesting(!context.viewState.applyingChange) } header: { Text(L10n.screenNotificationSettingsAdditionalSettingsSectionTitle) + .compoundFormSectionHeader() } .compoundFormSection() } @@ -168,15 +170,21 @@ struct NotificationSettingsScreen_Previews: PreviewProvider { let notificationCenter = UserNotificationCenterMock() notificationCenter.authorizationStatusReturnValue = .notDetermined let notificationSettingsProxy = NotificationSettingsProxyMock(with: .init()) + notificationSettingsProxy.getDefaultNotificationRoomModeIsEncryptedActiveMembersCountClosure = { isEncrypted, activeMembersCount in + switch (isEncrypted, activeMembersCount) { + case (_, 2): + return .allMessages + default: + return .mentionsAndKeywordsOnly + } + } notificationSettingsProxy.isRoomMentionEnabledReturnValue = true - notificationSettingsProxy.isCallEnabledReturnValue = true + notificationSettingsProxy.isCallEnabledReturnValue = false var viewModel = NotificationSettingsScreenViewModel(appSettings: appSettings, userNotificationCenter: notificationCenter, notificationSettingsProxy: notificationSettingsProxy) - viewModel.state.groupChatNotificationSettingsState = .loaded(mode: .allMessages) - viewModel.state.directChatNotificationSettingsState = .loaded(mode: .mentionsAndKeywordsOnly) - viewModel.state.bindings.enableRoomMention = true + viewModel.fetchInitialContent() return viewModel }() diff --git a/UnitTests/Sources/NotificationManager/NotificationManagerTests.swift b/UnitTests/Sources/NotificationManager/NotificationManagerTests.swift index 96fd9b486d..02dd580dea 100644 --- a/UnitTests/Sources/NotificationManager/NotificationManagerTests.swift +++ b/UnitTests/Sources/NotificationManager/NotificationManagerTests.swift @@ -95,6 +95,7 @@ final class NotificationManagerTests: XCTestCase { } func test_whenRegisteredAndPusherTagIsSetInSettings_tagNotGenerated() async throws { + notificationCenter.requestAuthorizationOptionsReturnValue = true appSettings.pusherProfileTag = "12345" _ = await notificationManager.register(with: Data()) XCTAssertEqual(appSettings.pusherProfileTag, "12345") diff --git a/UnitTests/Sources/NotificationSettingsScreenViewModelTests.swift b/UnitTests/Sources/NotificationSettingsScreenViewModelTests.swift index 590d25b372..ee75a3e787 100644 --- a/UnitTests/Sources/NotificationSettingsScreenViewModelTests.swift +++ b/UnitTests/Sources/NotificationSettingsScreenViewModelTests.swift @@ -65,32 +65,21 @@ class NotificationSettingsScreenViewModelTests: XCTestCase { return .mentionsAndKeywordsOnly } } - let deferredDirectChatsSettings = deferFulfillment(viewModel.context.$viewState - .collect(6) - .first()) + let deferred = deferFulfillment(viewModel.context.$viewState.map(\.settings) + .first(where: { $0 != nil })) notificationSettingsProxy.callbacks.send(.settingsDidChange) - try await deferredDirectChatsSettings.fulfill() + try await deferred.fulfill() XCTAssertEqual(notificationSettingsProxy.getDefaultNotificationRoomModeIsEncryptedActiveMembersCountCallsCount, 4) XCTAssert(notificationSettingsProxy.isRoomMentionEnabledCalled) XCTAssert(notificationSettingsProxy.isCallEnabledCalled) - XCTAssert(isState(context.viewState.groupChatNotificationSettingsState, loadedWithValue: .mentionsAndKeywordsOnly)) - XCTAssert(isState(context.viewState.directChatNotificationSettingsState, loadedWithValue: .allMessages)) - XCTAssertFalse(context.viewState.inconsistentGroupChatsSettings) - XCTAssertFalse(context.viewState.inconsistentDirectChatsSettings) + XCTAssertEqual(context.viewState.settings?.groupChatsMode, .mentionsAndKeywordsOnly) + XCTAssertEqual(context.viewState.settings?.directChatsMode, .allMessages) + XCTAssertEqual(context.viewState.settings?.inconsistentSettings, false) XCTAssertNil(context.viewState.bindings.alertInfo) } - - func isState(_ state: NotificationSettingsScreenModeState, loadedWithValue value: RoomNotificationModeProxy) -> Bool { - switch state { - case .loaded(let mode): - return mode == value - default: - return false - } - } - + func testInconsistentGroupChatsSettings() async throws { notificationSettingsProxy.getDefaultNotificationRoomModeIsEncryptedActiveMembersCountClosure = { isEncrypted, activeMembersCount in switch (isEncrypted, activeMembersCount) { @@ -103,17 +92,13 @@ class NotificationSettingsScreenViewModelTests: XCTestCase { } } - let deferred = deferFulfillment(viewModel.context.$viewState.map(\.inconsistentGroupChatsSettings) - .removeDuplicates() - .collect(2) - .first()) + let deferred = deferFulfillment(viewModel.context.$viewState.map(\.settings) + .first(where: { $0 != nil })) notificationSettingsProxy.callbacks.send(.settingsDidChange) - let states = try await deferred.fulfill() + try await deferred.fulfill() - XCTAssertEqual(states, [false, true]) - XCTAssert(isState(context.viewState.groupChatNotificationSettingsState, loadedWithValue: .allMessages)) - XCTAssert(context.viewState.inconsistentGroupChatsSettings) - XCTAssertFalse(context.viewState.inconsistentDirectChatsSettings) + XCTAssertEqual(context.viewState.settings?.groupChatsMode, .allMessages) + XCTAssertEqual(context.viewState.settings?.inconsistentSettings, true) } func testInconsistentDirectChatsSettings() async throws { @@ -128,25 +113,26 @@ class NotificationSettingsScreenViewModelTests: XCTestCase { } } - let deferred = deferFulfillment(viewModel.context.$viewState.map(\.inconsistentDirectChatsSettings) - .removeDuplicates() - .collect(2) - .first()) + let deferred = deferFulfillment(viewModel.context.$viewState.map(\.settings) + .first(where: { $0 != nil })) notificationSettingsProxy.callbacks.send(.settingsDidChange) - let states = try await deferred.fulfill() + try await deferred.fulfill() - XCTAssertEqual(states, [false, true]) - XCTAssert(isState(context.viewState.directChatNotificationSettingsState, loadedWithValue: .allMessages)) - XCTAssertFalse(context.viewState.inconsistentGroupChatsSettings) - XCTAssert(context.viewState.inconsistentDirectChatsSettings) + XCTAssertEqual(context.viewState.settings?.directChatsMode, .allMessages) + XCTAssertEqual(context.viewState.settings?.inconsistentSettings, true) } func testToggleRoomMentionOff() async throws { notificationSettingsProxy.isRoomMentionEnabledReturnValue = true - context.enableRoomMention = false + let deferredInitialFetch = deferFulfillment(viewModel.context.$viewState.map(\.settings) + .first(where: { $0 != nil })) + notificationSettingsProxy.callbacks.send(.settingsDidChange) + try await deferredInitialFetch.fulfill() + + context.roomMentionsEnabled = false let deferred = deferFulfillment(notificationSettingsProxy.callbacks .first(where: { $0 == .settingsDidChange })) - context.send(viewAction: .processToggleRoomMention) + context.send(viewAction: .roomMentionChanged) try await deferred.fulfill() XCTAssert(notificationSettingsProxy.setRoomMentionEnabledEnabledCalled) @@ -155,10 +141,15 @@ class NotificationSettingsScreenViewModelTests: XCTestCase { func testToggleRoomMentionOn() async throws { notificationSettingsProxy.isRoomMentionEnabledReturnValue = false - context.enableRoomMention = true + let deferredInitialFetch = deferFulfillment(viewModel.context.$viewState.map(\.settings) + .first(where: { $0 != nil })) + viewModel.fetchInitialContent() + try await deferredInitialFetch.fulfill() + + context.roomMentionsEnabled = true let deferred = deferFulfillment(notificationSettingsProxy.callbacks .first(where: { $0 == .settingsDidChange })) - context.send(viewAction: .processToggleRoomMention) + context.send(viewAction: .roomMentionChanged) try await deferred.fulfill() XCTAssert(notificationSettingsProxy.setRoomMentionEnabledEnabledCalled) @@ -168,12 +159,17 @@ class NotificationSettingsScreenViewModelTests: XCTestCase { func testToggleRoomMentionFailure() async throws { notificationSettingsProxy.setRoomMentionEnabledEnabledThrowableError = NotificationSettingsError.Generic(message: "error") notificationSettingsProxy.isRoomMentionEnabledReturnValue = false - context.enableRoomMention = true + let deferredInitialFetch = deferFulfillment(viewModel.context.$viewState.map(\.settings) + .first(where: { $0 != nil })) + viewModel.fetchInitialContent() + try await deferredInitialFetch.fulfill() + + context.roomMentionsEnabled = true let deferred = deferFulfillment(context.$viewState.map(\.applyingChange) .removeDuplicates() .collect(3) .first()) - context.send(viewAction: .processToggleRoomMention) + context.send(viewAction: .roomMentionChanged) let states = try await deferred.fulfill() XCTAssertEqual(states, [false, true, false]) @@ -182,10 +178,15 @@ class NotificationSettingsScreenViewModelTests: XCTestCase { func testToggleCallsOff() async throws { notificationSettingsProxy.isCallEnabledReturnValue = true - context.enableCalls = false + let deferredInitialFetch = deferFulfillment(viewModel.context.$viewState.map(\.settings) + .first(where: { $0 != nil })) + viewModel.fetchInitialContent() + try await deferredInitialFetch.fulfill() + + context.callsEnabled = false let deferred = deferFulfillment(notificationSettingsProxy.callbacks .first(where: { $0 == .settingsDidChange })) - context.send(viewAction: .processToggleCalls) + context.send(viewAction: .callsChanged) try await deferred.fulfill() XCTAssert(notificationSettingsProxy.setCallEnabledEnabledCalled) @@ -194,10 +195,15 @@ class NotificationSettingsScreenViewModelTests: XCTestCase { func testToggleCallsOn() async throws { notificationSettingsProxy.isCallEnabledReturnValue = false - context.enableCalls = true + let deferredInitialFetch = deferFulfillment(viewModel.context.$viewState.map(\.settings) + .first(where: { $0 != nil })) + viewModel.fetchInitialContent() + try await deferredInitialFetch.fulfill() + + context.callsEnabled = true let deferred = deferFulfillment(notificationSettingsProxy.callbacks .first(where: { $0 == .settingsDidChange })) - context.send(viewAction: .processToggleCalls) + context.send(viewAction: .callsChanged) try await deferred.fulfill() XCTAssert(notificationSettingsProxy.setCallEnabledEnabledCalled) @@ -207,12 +213,17 @@ class NotificationSettingsScreenViewModelTests: XCTestCase { func testToggleCallsFailure() async throws { notificationSettingsProxy.setCallEnabledEnabledThrowableError = NotificationSettingsError.Generic(message: "error") notificationSettingsProxy.isCallEnabledReturnValue = false - context.enableCalls = true + let deferredInitialFetch = deferFulfillment(viewModel.context.$viewState.map(\.settings) + .first(where: { $0 != nil })) + viewModel.fetchInitialContent() + try await deferredInitialFetch.fulfill() + + context.callsEnabled = true let deferred = deferFulfillment(context.$viewState.map(\.applyingChange) .removeDuplicates() .collect(3) .first()) - context.send(viewAction: .processToggleCalls) + context.send(viewAction: .callsChanged) let states = try await deferred.fulfill() XCTAssertEqual(states, [false, true, false])