Add max selections support for polls (#5726)

This commit is contained in:
svetoslav-sportinno
2026-06-22 14:39:12 +03:00
committed by GitHub
parent 173a927bf8
commit 30e4e6339d
49 changed files with 506 additions and 251 deletions
+4
View File
@@ -793,6 +793,7 @@
828EA5009557C2B9DCD4CA0F /* UserDiscoverySection.swift in Sources */ = {isa = PBXBuildFile; fileRef = D071F86CD47582B9196C9D16 /* UserDiscoverySection.swift */; }; 828EA5009557C2B9DCD4CA0F /* UserDiscoverySection.swift in Sources */ = {isa = PBXBuildFile; fileRef = D071F86CD47582B9196C9D16 /* UserDiscoverySection.swift */; };
832A4EA1094B8FE423A08700 /* RoomChangeRolesScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6B2A421198FD20AAAED20004 /* RoomChangeRolesScreen.swift */; }; 832A4EA1094B8FE423A08700 /* RoomChangeRolesScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6B2A421198FD20AAAED20004 /* RoomChangeRolesScreen.swift */; };
8358D145F9BF94F412BEDCA8 /* RoomRolesAndPermissionsScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1DE7969EBCAF078813E18EA1 /* RoomRolesAndPermissionsScreenModels.swift */; }; 8358D145F9BF94F412BEDCA8 /* RoomRolesAndPermissionsScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1DE7969EBCAF078813E18EA1 /* RoomRolesAndPermissionsScreenModels.swift */; };
83A01FE4DACABDE97F04D338 /* PollTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = FBD507F529D440215892618A /* PollTests.swift */; };
83B17A44D3E7E6DF22D9A2A4 /* RoomModerationRole.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0B32BBA8887BD7A5C4ECF16F /* RoomModerationRole.swift */; }; 83B17A44D3E7E6DF22D9A2A4 /* RoomModerationRole.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0B32BBA8887BD7A5C4ECF16F /* RoomModerationRole.swift */; };
83D519C509F0F76EDBB60455 /* KnockRequestProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F062DD2CCD95DC33528A16F /* KnockRequestProxy.swift */; }; 83D519C509F0F76EDBB60455 /* KnockRequestProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F062DD2CCD95DC33528A16F /* KnockRequestProxy.swift */; };
84196A8F1963D55726261879 /* RoomLiveLocationServiceMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = C79C2C6E7F66646D1D254927 /* RoomLiveLocationServiceMock.swift */; }; 84196A8F1963D55726261879 /* RoomLiveLocationServiceMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = C79C2C6E7F66646D1D254927 /* RoomLiveLocationServiceMock.swift */; };
@@ -3070,6 +3071,7 @@
FB9EABCA9348DFA27439A809 /* WaveformCursorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WaveformCursorView.swift; sourceTree = "<group>"; }; FB9EABCA9348DFA27439A809 /* WaveformCursorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WaveformCursorView.swift; sourceTree = "<group>"; };
FBB0328F2887BF0A65BC5D49 /* NotificationSettingsEditScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationSettingsEditScreen.swift; sourceTree = "<group>"; }; FBB0328F2887BF0A65BC5D49 /* NotificationSettingsEditScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationSettingsEditScreen.swift; sourceTree = "<group>"; };
FBC776F301D374A3298C69DA /* AppCoordinatorProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppCoordinatorProtocol.swift; sourceTree = "<group>"; }; FBC776F301D374A3298C69DA /* AppCoordinatorProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppCoordinatorProtocol.swift; sourceTree = "<group>"; };
FBD507F529D440215892618A /* PollTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PollTests.swift; sourceTree = "<group>"; };
FC3797A2325BE44FFB478BE9 /* LeaveSpaceRoomDetailsCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LeaveSpaceRoomDetailsCell.swift; sourceTree = "<group>"; }; FC3797A2325BE44FFB478BE9 /* LeaveSpaceRoomDetailsCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LeaveSpaceRoomDetailsCell.swift; sourceTree = "<group>"; };
FC83F47D2173B7538AA72E0E /* RoomSummaryProviderMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomSummaryProviderMock.swift; sourceTree = "<group>"; }; FC83F47D2173B7538AA72E0E /* RoomSummaryProviderMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomSummaryProviderMock.swift; sourceTree = "<group>"; };
FC9044BE0E4A66F5B963E834 /* AudioFileEventsTimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudioFileEventsTimelineView.swift; sourceTree = "<group>"; }; FC9044BE0E4A66F5B963E834 /* AudioFileEventsTimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudioFileEventsTimelineView.swift; sourceTree = "<group>"; };
@@ -5006,6 +5008,7 @@
31A6314FDC51DA25712D9A81 /* PillContextTests.swift */, 31A6314FDC51DA25712D9A81 /* PillContextTests.swift */,
6FA38E813BE14149F173F461 /* PinnedEventsBannerStateTests.swift */, 6FA38E813BE14149F173F461 /* PinnedEventsBannerStateTests.swift */,
347D708104CCEF771428C9A3 /* PollFormScreenViewModelTests.swift */, 347D708104CCEF771428C9A3 /* PollFormScreenViewModelTests.swift */,
FBD507F529D440215892618A /* PollTests.swift */,
25E7E9B7FEAB6169D960C206 /* QRCodeLoginScreenViewModelTests.swift */, 25E7E9B7FEAB6169D960C206 /* QRCodeLoginScreenViewModelTests.swift */,
181CF280BC8E3F335AFCB4B8 /* RemotePreferenceTests.swift */, 181CF280BC8E3F335AFCB4B8 /* RemotePreferenceTests.swift */,
30EA681527A8FE65E4C8E9A9 /* ReportContentScreenViewModelTests.swift */, 30EA681527A8FE65E4C8E9A9 /* ReportContentScreenViewModelTests.swift */,
@@ -7984,6 +7987,7 @@
3982E60F9C126437D5E488A3 /* PillContextTests.swift in Sources */, 3982E60F9C126437D5E488A3 /* PillContextTests.swift in Sources */,
2D2D8A53B35BE8D8A01449C6 /* PinnedEventsBannerStateTests.swift in Sources */, 2D2D8A53B35BE8D8A01449C6 /* PinnedEventsBannerStateTests.swift in Sources */,
FF7E8ECC8E7E1D1851517536 /* PollFormScreenViewModelTests.swift in Sources */, FF7E8ECC8E7E1D1851517536 /* PollFormScreenViewModelTests.swift in Sources */,
83A01FE4DACABDE97F04D338 /* PollTests.swift in Sources */,
E3EBC3BF7CE3960B41757BAA /* Publisher.swift in Sources */, E3EBC3BF7CE3960B41757BAA /* Publisher.swift in Sources */,
BDC4EB54CC3036730475CB8B /* QRCodeLoginScreenViewModelTests.swift in Sources */, BDC4EB54CC3036730475CB8B /* QRCodeLoginScreenViewModelTests.swift in Sources */,
522269133E6F65F68482F4F4 /* RemotePreferenceTests.swift in Sources */, 522269133E6F65F68482F4F4 /* RemotePreferenceTests.swift in Sources */,
@@ -2,6 +2,9 @@
"screen_search_empty_state_message" = "Search for rooms"; "screen_search_empty_state_message" = "Search for rooms";
"screen_search_empty_state_title" = "Start searching..."; "screen_search_empty_state_title" = "Start searching...";
"screen_search_no_results_message" = "There are no results for “%1$@.” Try a new search term."; "screen_search_no_results_message" = "There are no results for “%1$@.” Try a new search term.";
"a11y_screen_create_poll_votes_allowed_decrease" = "Decrease votes allowed per person";
"a11y_screen_create_poll_votes_allowed_increase" = "Increase votes allowed per person";
"screen_create_poll_votes_allowed_per_person" = "Votes allowed per person";
"soft_logout_clear_data_dialog_content" = "Clear all data currently stored on this device?\nSign in again to access your account data and messages."; "soft_logout_clear_data_dialog_content" = "Clear all data currently stored on this device?\nSign in again to access your account data and messages.";
"soft_logout_clear_data_dialog_title" = "Clear data"; "soft_logout_clear_data_dialog_title" = "Clear data";
"soft_logout_clear_data_notice" = "Warning: Your personal data (including encryption keys) is still stored on this device.\n\nClear it if you’re finished using this device, or want to sign in to another account."; "soft_logout_clear_data_notice" = "Warning: Your personal data (including encryption keys) is still stored on this device.\n\nClear it if you’re finished using this device, or want to sign in to another account.";
@@ -10,6 +10,12 @@ import Foundation
// swiftlint:disable explicit_type_interface function_parameter_count identifier_name line_length // swiftlint:disable explicit_type_interface function_parameter_count identifier_name line_length
// swiftlint:disable nesting type_body_length type_name vertical_whitespace_opening_braces // swiftlint:disable nesting type_body_length type_name vertical_whitespace_opening_braces
internal nonisolated enum UntranslatedL10n { internal nonisolated enum UntranslatedL10n {
/// Decrease votes allowed per person
internal static var a11yScreenCreatePollVotesAllowedDecrease: String { return UntranslatedL10n.tr("Untranslated", "a11y_screen_create_poll_votes_allowed_decrease") }
/// Increase votes allowed per person
internal static var a11yScreenCreatePollVotesAllowedIncrease: String { return UntranslatedL10n.tr("Untranslated", "a11y_screen_create_poll_votes_allowed_increase") }
/// Votes allowed per person
internal static var screenCreatePollVotesAllowedPerPerson: String { return UntranslatedL10n.tr("Untranslated", "screen_create_poll_votes_allowed_per_person") }
/// Search /// Search
internal static var screenHomeTabSearch: String { return UntranslatedL10n.tr("Untranslated", "screen_home_tab_search") } internal static var screenHomeTabSearch: String { return UntranslatedL10n.tr("Untranslated", "screen_home_tab_search") }
/// Search for rooms /// Search for rooms
@@ -9561,44 +9561,44 @@ nonisolated class PollInteractionHandlerMock: PollInteractionHandlerProtocol, @u
//MARK: - sendPollResponse //MARK: - sendPollResponse
private let sendPollResponsePollStartIDOptionIDCallsCountLock = NSLock() private let sendPollResponsePollStartIDAnswerIDsCallsCountLock = NSLock()
private nonisolated(unsafe) var sendPollResponsePollStartIDOptionIDUnderlyingCallsCount = 0 private nonisolated(unsafe) var sendPollResponsePollStartIDAnswerIDsUnderlyingCallsCount = 0
var sendPollResponsePollStartIDOptionIDCallsCount: Int { var sendPollResponsePollStartIDAnswerIDsCallsCount: Int {
get { sendPollResponsePollStartIDOptionIDCallsCountLock.withLock { sendPollResponsePollStartIDOptionIDUnderlyingCallsCount } } get { sendPollResponsePollStartIDAnswerIDsCallsCountLock.withLock { sendPollResponsePollStartIDAnswerIDsUnderlyingCallsCount } }
set { sendPollResponsePollStartIDOptionIDCallsCountLock.withLock { sendPollResponsePollStartIDOptionIDUnderlyingCallsCount = newValue } } set { sendPollResponsePollStartIDAnswerIDsCallsCountLock.withLock { sendPollResponsePollStartIDAnswerIDsUnderlyingCallsCount = newValue } }
} }
var sendPollResponsePollStartIDOptionIDCalled: Bool { var sendPollResponsePollStartIDAnswerIDsCalled: Bool {
return sendPollResponsePollStartIDOptionIDCallsCount > 0 return sendPollResponsePollStartIDAnswerIDsCallsCount > 0
} }
private let sendPollResponsePollStartIDOptionIDReceivedArgumentsLock = NSLock() private let sendPollResponsePollStartIDAnswerIDsReceivedArgumentsLock = NSLock()
private nonisolated(unsafe) var sendPollResponsePollStartIDOptionIDUnderlyingReceivedArguments: (pollStartID: String, optionID: String)? private nonisolated(unsafe) var sendPollResponsePollStartIDAnswerIDsUnderlyingReceivedArguments: (pollStartID: String, answerIDs: [String])?
var sendPollResponsePollStartIDOptionIDReceivedArguments: (pollStartID: String, optionID: String)? { var sendPollResponsePollStartIDAnswerIDsReceivedArguments: (pollStartID: String, answerIDs: [String])? {
get { sendPollResponsePollStartIDOptionIDReceivedArgumentsLock.withLock { sendPollResponsePollStartIDOptionIDUnderlyingReceivedArguments } } get { sendPollResponsePollStartIDAnswerIDsReceivedArgumentsLock.withLock { sendPollResponsePollStartIDAnswerIDsUnderlyingReceivedArguments } }
set { sendPollResponsePollStartIDOptionIDReceivedArgumentsLock.withLock { sendPollResponsePollStartIDOptionIDUnderlyingReceivedArguments = newValue } } set { sendPollResponsePollStartIDAnswerIDsReceivedArgumentsLock.withLock { sendPollResponsePollStartIDAnswerIDsUnderlyingReceivedArguments = newValue } }
} }
private let sendPollResponsePollStartIDOptionIDReceivedInvocationsLock = NSLock() private let sendPollResponsePollStartIDAnswerIDsReceivedInvocationsLock = NSLock()
private nonisolated(unsafe) var sendPollResponsePollStartIDOptionIDUnderlyingReceivedInvocations: [(pollStartID: String, optionID: String)] = [] private nonisolated(unsafe) var sendPollResponsePollStartIDAnswerIDsUnderlyingReceivedInvocations: [(pollStartID: String, answerIDs: [String])] = []
var sendPollResponsePollStartIDOptionIDReceivedInvocations: [(pollStartID: String, optionID: String)] { var sendPollResponsePollStartIDAnswerIDsReceivedInvocations: [(pollStartID: String, answerIDs: [String])] {
get { sendPollResponsePollStartIDOptionIDReceivedInvocationsLock.withLock { sendPollResponsePollStartIDOptionIDUnderlyingReceivedInvocations } } get { sendPollResponsePollStartIDAnswerIDsReceivedInvocationsLock.withLock { sendPollResponsePollStartIDAnswerIDsUnderlyingReceivedInvocations } }
set { sendPollResponsePollStartIDOptionIDReceivedInvocationsLock.withLock { sendPollResponsePollStartIDOptionIDUnderlyingReceivedInvocations = newValue } } set { sendPollResponsePollStartIDAnswerIDsReceivedInvocationsLock.withLock { sendPollResponsePollStartIDAnswerIDsUnderlyingReceivedInvocations = newValue } }
} }
private let sendPollResponsePollStartIDOptionIDReturnValueLock = NSLock() private let sendPollResponsePollStartIDAnswerIDsReturnValueLock = NSLock()
private nonisolated(unsafe) var sendPollResponsePollStartIDOptionIDUnderlyingReturnValue: Result<Void, Error>! private nonisolated(unsafe) var sendPollResponsePollStartIDAnswerIDsUnderlyingReturnValue: Result<Void, Error>!
var sendPollResponsePollStartIDOptionIDReturnValue: Result<Void, Error>! { var sendPollResponsePollStartIDAnswerIDsReturnValue: Result<Void, Error>! {
get { sendPollResponsePollStartIDOptionIDReturnValueLock.withLock { sendPollResponsePollStartIDOptionIDUnderlyingReturnValue } } get { sendPollResponsePollStartIDAnswerIDsReturnValueLock.withLock { sendPollResponsePollStartIDAnswerIDsUnderlyingReturnValue } }
set { sendPollResponsePollStartIDOptionIDReturnValueLock.withLock { sendPollResponsePollStartIDOptionIDUnderlyingReturnValue = newValue } } set { sendPollResponsePollStartIDAnswerIDsReturnValueLock.withLock { sendPollResponsePollStartIDAnswerIDsUnderlyingReturnValue = newValue } }
} }
nonisolated(unsafe) var sendPollResponsePollStartIDOptionIDClosure: ((String, String) async -> Result<Void, Error>)? nonisolated(unsafe) var sendPollResponsePollStartIDAnswerIDsClosure: ((String, [String]) async -> Result<Void, Error>)?
@concurrent func sendPollResponse(pollStartID: String, optionID: String) async -> Result<Void, Error> { @concurrent func sendPollResponse(pollStartID: String, answerIDs: [String]) async -> Result<Void, Error> {
sendPollResponsePollStartIDOptionIDCallsCountLock.withLock { sendPollResponsePollStartIDOptionIDUnderlyingCallsCount += 1 } sendPollResponsePollStartIDAnswerIDsCallsCountLock.withLock { sendPollResponsePollStartIDAnswerIDsUnderlyingCallsCount += 1 }
sendPollResponsePollStartIDOptionIDReceivedArguments = (pollStartID: pollStartID, optionID: optionID) sendPollResponsePollStartIDAnswerIDsReceivedArguments = (pollStartID: pollStartID, answerIDs: answerIDs)
sendPollResponsePollStartIDOptionIDReceivedInvocationsLock.withLock { sendPollResponsePollStartIDOptionIDUnderlyingReceivedInvocations.append((pollStartID: pollStartID, optionID: optionID)) } sendPollResponsePollStartIDAnswerIDsReceivedInvocationsLock.withLock { sendPollResponsePollStartIDAnswerIDsUnderlyingReceivedInvocations.append((pollStartID: pollStartID, answerIDs: answerIDs)) }
if let sendPollResponsePollStartIDOptionIDClosure = sendPollResponsePollStartIDOptionIDClosure { if let sendPollResponsePollStartIDAnswerIDsClosure = sendPollResponsePollStartIDAnswerIDsClosure {
return await sendPollResponsePollStartIDOptionIDClosure(pollStartID, optionID) return await sendPollResponsePollStartIDAnswerIDsClosure(pollStartID, answerIDs)
} else { } else {
return sendPollResponsePollStartIDOptionIDReturnValue return sendPollResponsePollStartIDAnswerIDsReturnValue
} }
} }
//MARK: - endPoll //MARK: - endPoll
@@ -12772,86 +12772,86 @@ nonisolated class TimelineControllerMock: TimelineControllerProtocol, @unchecked
} }
//MARK: - createPoll //MARK: - createPoll
private let createPollQuestionAnswersPollKindCallsCountLock = NSLock() private let createPollQuestionAnswersMaxSelectionsPollKindCallsCountLock = NSLock()
private nonisolated(unsafe) var createPollQuestionAnswersPollKindUnderlyingCallsCount = 0 private nonisolated(unsafe) var createPollQuestionAnswersMaxSelectionsPollKindUnderlyingCallsCount = 0
var createPollQuestionAnswersPollKindCallsCount: Int { var createPollQuestionAnswersMaxSelectionsPollKindCallsCount: Int {
get { createPollQuestionAnswersPollKindCallsCountLock.withLock { createPollQuestionAnswersPollKindUnderlyingCallsCount } } get { createPollQuestionAnswersMaxSelectionsPollKindCallsCountLock.withLock { createPollQuestionAnswersMaxSelectionsPollKindUnderlyingCallsCount } }
set { createPollQuestionAnswersPollKindCallsCountLock.withLock { createPollQuestionAnswersPollKindUnderlyingCallsCount = newValue } } set { createPollQuestionAnswersMaxSelectionsPollKindCallsCountLock.withLock { createPollQuestionAnswersMaxSelectionsPollKindUnderlyingCallsCount = newValue } }
} }
var createPollQuestionAnswersPollKindCalled: Bool { var createPollQuestionAnswersMaxSelectionsPollKindCalled: Bool {
return createPollQuestionAnswersPollKindCallsCount > 0 return createPollQuestionAnswersMaxSelectionsPollKindCallsCount > 0
} }
private let createPollQuestionAnswersPollKindReceivedArgumentsLock = NSLock() private let createPollQuestionAnswersMaxSelectionsPollKindReceivedArgumentsLock = NSLock()
private nonisolated(unsafe) var createPollQuestionAnswersPollKindUnderlyingReceivedArguments: (question: String, answers: [String], pollKind: Poll.Kind)? private nonisolated(unsafe) var createPollQuestionAnswersMaxSelectionsPollKindUnderlyingReceivedArguments: (question: String, answers: [String], maxSelections: Int, pollKind: Poll.Kind)?
var createPollQuestionAnswersPollKindReceivedArguments: (question: String, answers: [String], pollKind: Poll.Kind)? { var createPollQuestionAnswersMaxSelectionsPollKindReceivedArguments: (question: String, answers: [String], maxSelections: Int, pollKind: Poll.Kind)? {
get { createPollQuestionAnswersPollKindReceivedArgumentsLock.withLock { createPollQuestionAnswersPollKindUnderlyingReceivedArguments } } get { createPollQuestionAnswersMaxSelectionsPollKindReceivedArgumentsLock.withLock { createPollQuestionAnswersMaxSelectionsPollKindUnderlyingReceivedArguments } }
set { createPollQuestionAnswersPollKindReceivedArgumentsLock.withLock { createPollQuestionAnswersPollKindUnderlyingReceivedArguments = newValue } } set { createPollQuestionAnswersMaxSelectionsPollKindReceivedArgumentsLock.withLock { createPollQuestionAnswersMaxSelectionsPollKindUnderlyingReceivedArguments = newValue } }
} }
private let createPollQuestionAnswersPollKindReceivedInvocationsLock = NSLock() private let createPollQuestionAnswersMaxSelectionsPollKindReceivedInvocationsLock = NSLock()
private nonisolated(unsafe) var createPollQuestionAnswersPollKindUnderlyingReceivedInvocations: [(question: String, answers: [String], pollKind: Poll.Kind)] = [] private nonisolated(unsafe) var createPollQuestionAnswersMaxSelectionsPollKindUnderlyingReceivedInvocations: [(question: String, answers: [String], maxSelections: Int, pollKind: Poll.Kind)] = []
var createPollQuestionAnswersPollKindReceivedInvocations: [(question: String, answers: [String], pollKind: Poll.Kind)] { var createPollQuestionAnswersMaxSelectionsPollKindReceivedInvocations: [(question: String, answers: [String], maxSelections: Int, pollKind: Poll.Kind)] {
get { createPollQuestionAnswersPollKindReceivedInvocationsLock.withLock { createPollQuestionAnswersPollKindUnderlyingReceivedInvocations } } get { createPollQuestionAnswersMaxSelectionsPollKindReceivedInvocationsLock.withLock { createPollQuestionAnswersMaxSelectionsPollKindUnderlyingReceivedInvocations } }
set { createPollQuestionAnswersPollKindReceivedInvocationsLock.withLock { createPollQuestionAnswersPollKindUnderlyingReceivedInvocations = newValue } } set { createPollQuestionAnswersMaxSelectionsPollKindReceivedInvocationsLock.withLock { createPollQuestionAnswersMaxSelectionsPollKindUnderlyingReceivedInvocations = newValue } }
} }
private let createPollQuestionAnswersPollKindReturnValueLock = NSLock() private let createPollQuestionAnswersMaxSelectionsPollKindReturnValueLock = NSLock()
private nonisolated(unsafe) var createPollQuestionAnswersPollKindUnderlyingReturnValue: Result<Void, TimelineControllerError>! private nonisolated(unsafe) var createPollQuestionAnswersMaxSelectionsPollKindUnderlyingReturnValue: Result<Void, TimelineControllerError>!
var createPollQuestionAnswersPollKindReturnValue: Result<Void, TimelineControllerError>! { var createPollQuestionAnswersMaxSelectionsPollKindReturnValue: Result<Void, TimelineControllerError>! {
get { createPollQuestionAnswersPollKindReturnValueLock.withLock { createPollQuestionAnswersPollKindUnderlyingReturnValue } } get { createPollQuestionAnswersMaxSelectionsPollKindReturnValueLock.withLock { createPollQuestionAnswersMaxSelectionsPollKindUnderlyingReturnValue } }
set { createPollQuestionAnswersPollKindReturnValueLock.withLock { createPollQuestionAnswersPollKindUnderlyingReturnValue = newValue } } set { createPollQuestionAnswersMaxSelectionsPollKindReturnValueLock.withLock { createPollQuestionAnswersMaxSelectionsPollKindUnderlyingReturnValue = newValue } }
} }
nonisolated(unsafe) var createPollQuestionAnswersPollKindClosure: ((String, [String], Poll.Kind) async -> Result<Void, TimelineControllerError>)? nonisolated(unsafe) var createPollQuestionAnswersMaxSelectionsPollKindClosure: ((String, [String], Int, Poll.Kind) async -> Result<Void, TimelineControllerError>)?
@concurrent func createPoll(question: String, answers: [String], pollKind: Poll.Kind) async -> Result<Void, TimelineControllerError> { @concurrent func createPoll(question: String, answers: [String], maxSelections: Int, pollKind: Poll.Kind) async -> Result<Void, TimelineControllerError> {
createPollQuestionAnswersPollKindCallsCountLock.withLock { createPollQuestionAnswersPollKindUnderlyingCallsCount += 1 } createPollQuestionAnswersMaxSelectionsPollKindCallsCountLock.withLock { createPollQuestionAnswersMaxSelectionsPollKindUnderlyingCallsCount += 1 }
createPollQuestionAnswersPollKindReceivedArguments = (question: question, answers: answers, pollKind: pollKind) createPollQuestionAnswersMaxSelectionsPollKindReceivedArguments = (question: question, answers: answers, maxSelections: maxSelections, pollKind: pollKind)
createPollQuestionAnswersPollKindReceivedInvocationsLock.withLock { createPollQuestionAnswersPollKindUnderlyingReceivedInvocations.append((question: question, answers: answers, pollKind: pollKind)) } createPollQuestionAnswersMaxSelectionsPollKindReceivedInvocationsLock.withLock { createPollQuestionAnswersMaxSelectionsPollKindUnderlyingReceivedInvocations.append((question: question, answers: answers, maxSelections: maxSelections, pollKind: pollKind)) }
if let createPollQuestionAnswersPollKindClosure = createPollQuestionAnswersPollKindClosure { if let createPollQuestionAnswersMaxSelectionsPollKindClosure = createPollQuestionAnswersMaxSelectionsPollKindClosure {
return await createPollQuestionAnswersPollKindClosure(question, answers, pollKind) return await createPollQuestionAnswersMaxSelectionsPollKindClosure(question, answers, maxSelections, pollKind)
} else { } else {
return createPollQuestionAnswersPollKindReturnValue return createPollQuestionAnswersMaxSelectionsPollKindReturnValue
} }
} }
//MARK: - editPoll //MARK: - editPoll
private let editPollOriginalQuestionAnswersPollKindCallsCountLock = NSLock() private let editPollOriginalQuestionAnswersMaxSelectionsPollKindCallsCountLock = NSLock()
private nonisolated(unsafe) var editPollOriginalQuestionAnswersPollKindUnderlyingCallsCount = 0 private nonisolated(unsafe) var editPollOriginalQuestionAnswersMaxSelectionsPollKindUnderlyingCallsCount = 0
var editPollOriginalQuestionAnswersPollKindCallsCount: Int { var editPollOriginalQuestionAnswersMaxSelectionsPollKindCallsCount: Int {
get { editPollOriginalQuestionAnswersPollKindCallsCountLock.withLock { editPollOriginalQuestionAnswersPollKindUnderlyingCallsCount } } get { editPollOriginalQuestionAnswersMaxSelectionsPollKindCallsCountLock.withLock { editPollOriginalQuestionAnswersMaxSelectionsPollKindUnderlyingCallsCount } }
set { editPollOriginalQuestionAnswersPollKindCallsCountLock.withLock { editPollOriginalQuestionAnswersPollKindUnderlyingCallsCount = newValue } } set { editPollOriginalQuestionAnswersMaxSelectionsPollKindCallsCountLock.withLock { editPollOriginalQuestionAnswersMaxSelectionsPollKindUnderlyingCallsCount = newValue } }
} }
var editPollOriginalQuestionAnswersPollKindCalled: Bool { var editPollOriginalQuestionAnswersMaxSelectionsPollKindCalled: Bool {
return editPollOriginalQuestionAnswersPollKindCallsCount > 0 return editPollOriginalQuestionAnswersMaxSelectionsPollKindCallsCount > 0
} }
private let editPollOriginalQuestionAnswersPollKindReceivedArgumentsLock = NSLock() private let editPollOriginalQuestionAnswersMaxSelectionsPollKindReceivedArgumentsLock = NSLock()
private nonisolated(unsafe) var editPollOriginalQuestionAnswersPollKindUnderlyingReceivedArguments: (eventID: String, question: String, answers: [String], pollKind: Poll.Kind)? private nonisolated(unsafe) var editPollOriginalQuestionAnswersMaxSelectionsPollKindUnderlyingReceivedArguments: (eventID: String, question: String, answers: [String], maxSelections: Int, pollKind: Poll.Kind)?
var editPollOriginalQuestionAnswersPollKindReceivedArguments: (eventID: String, question: String, answers: [String], pollKind: Poll.Kind)? { var editPollOriginalQuestionAnswersMaxSelectionsPollKindReceivedArguments: (eventID: String, question: String, answers: [String], maxSelections: Int, pollKind: Poll.Kind)? {
get { editPollOriginalQuestionAnswersPollKindReceivedArgumentsLock.withLock { editPollOriginalQuestionAnswersPollKindUnderlyingReceivedArguments } } get { editPollOriginalQuestionAnswersMaxSelectionsPollKindReceivedArgumentsLock.withLock { editPollOriginalQuestionAnswersMaxSelectionsPollKindUnderlyingReceivedArguments } }
set { editPollOriginalQuestionAnswersPollKindReceivedArgumentsLock.withLock { editPollOriginalQuestionAnswersPollKindUnderlyingReceivedArguments = newValue } } set { editPollOriginalQuestionAnswersMaxSelectionsPollKindReceivedArgumentsLock.withLock { editPollOriginalQuestionAnswersMaxSelectionsPollKindUnderlyingReceivedArguments = newValue } }
} }
private let editPollOriginalQuestionAnswersPollKindReceivedInvocationsLock = NSLock() private let editPollOriginalQuestionAnswersMaxSelectionsPollKindReceivedInvocationsLock = NSLock()
private nonisolated(unsafe) var editPollOriginalQuestionAnswersPollKindUnderlyingReceivedInvocations: [(eventID: String, question: String, answers: [String], pollKind: Poll.Kind)] = [] private nonisolated(unsafe) var editPollOriginalQuestionAnswersMaxSelectionsPollKindUnderlyingReceivedInvocations: [(eventID: String, question: String, answers: [String], maxSelections: Int, pollKind: Poll.Kind)] = []
var editPollOriginalQuestionAnswersPollKindReceivedInvocations: [(eventID: String, question: String, answers: [String], pollKind: Poll.Kind)] { var editPollOriginalQuestionAnswersMaxSelectionsPollKindReceivedInvocations: [(eventID: String, question: String, answers: [String], maxSelections: Int, pollKind: Poll.Kind)] {
get { editPollOriginalQuestionAnswersPollKindReceivedInvocationsLock.withLock { editPollOriginalQuestionAnswersPollKindUnderlyingReceivedInvocations } } get { editPollOriginalQuestionAnswersMaxSelectionsPollKindReceivedInvocationsLock.withLock { editPollOriginalQuestionAnswersMaxSelectionsPollKindUnderlyingReceivedInvocations } }
set { editPollOriginalQuestionAnswersPollKindReceivedInvocationsLock.withLock { editPollOriginalQuestionAnswersPollKindUnderlyingReceivedInvocations = newValue } } set { editPollOriginalQuestionAnswersMaxSelectionsPollKindReceivedInvocationsLock.withLock { editPollOriginalQuestionAnswersMaxSelectionsPollKindUnderlyingReceivedInvocations = newValue } }
} }
private let editPollOriginalQuestionAnswersPollKindReturnValueLock = NSLock() private let editPollOriginalQuestionAnswersMaxSelectionsPollKindReturnValueLock = NSLock()
private nonisolated(unsafe) var editPollOriginalQuestionAnswersPollKindUnderlyingReturnValue: Result<Void, TimelineControllerError>! private nonisolated(unsafe) var editPollOriginalQuestionAnswersMaxSelectionsPollKindUnderlyingReturnValue: Result<Void, TimelineControllerError>!
var editPollOriginalQuestionAnswersPollKindReturnValue: Result<Void, TimelineControllerError>! { var editPollOriginalQuestionAnswersMaxSelectionsPollKindReturnValue: Result<Void, TimelineControllerError>! {
get { editPollOriginalQuestionAnswersPollKindReturnValueLock.withLock { editPollOriginalQuestionAnswersPollKindUnderlyingReturnValue } } get { editPollOriginalQuestionAnswersMaxSelectionsPollKindReturnValueLock.withLock { editPollOriginalQuestionAnswersMaxSelectionsPollKindUnderlyingReturnValue } }
set { editPollOriginalQuestionAnswersPollKindReturnValueLock.withLock { editPollOriginalQuestionAnswersPollKindUnderlyingReturnValue = newValue } } set { editPollOriginalQuestionAnswersMaxSelectionsPollKindReturnValueLock.withLock { editPollOriginalQuestionAnswersMaxSelectionsPollKindUnderlyingReturnValue = newValue } }
} }
nonisolated(unsafe) var editPollOriginalQuestionAnswersPollKindClosure: ((String, String, [String], Poll.Kind) async -> Result<Void, TimelineControllerError>)? nonisolated(unsafe) var editPollOriginalQuestionAnswersMaxSelectionsPollKindClosure: ((String, String, [String], Int, Poll.Kind) async -> Result<Void, TimelineControllerError>)?
@concurrent func editPoll(original eventID: String, question: String, answers: [String], pollKind: Poll.Kind) async -> Result<Void, TimelineControllerError> { @concurrent func editPoll(original eventID: String, question: String, answers: [String], maxSelections: Int, pollKind: Poll.Kind) async -> Result<Void, TimelineControllerError> {
editPollOriginalQuestionAnswersPollKindCallsCountLock.withLock { editPollOriginalQuestionAnswersPollKindUnderlyingCallsCount += 1 } editPollOriginalQuestionAnswersMaxSelectionsPollKindCallsCountLock.withLock { editPollOriginalQuestionAnswersMaxSelectionsPollKindUnderlyingCallsCount += 1 }
editPollOriginalQuestionAnswersPollKindReceivedArguments = (eventID: eventID, question: question, answers: answers, pollKind: pollKind) editPollOriginalQuestionAnswersMaxSelectionsPollKindReceivedArguments = (eventID: eventID, question: question, answers: answers, maxSelections: maxSelections, pollKind: pollKind)
editPollOriginalQuestionAnswersPollKindReceivedInvocationsLock.withLock { editPollOriginalQuestionAnswersPollKindUnderlyingReceivedInvocations.append((eventID: eventID, question: question, answers: answers, pollKind: pollKind)) } editPollOriginalQuestionAnswersMaxSelectionsPollKindReceivedInvocationsLock.withLock { editPollOriginalQuestionAnswersMaxSelectionsPollKindUnderlyingReceivedInvocations.append((eventID: eventID, question: question, answers: answers, maxSelections: maxSelections, pollKind: pollKind)) }
if let editPollOriginalQuestionAnswersPollKindClosure = editPollOriginalQuestionAnswersPollKindClosure { if let editPollOriginalQuestionAnswersMaxSelectionsPollKindClosure = editPollOriginalQuestionAnswersMaxSelectionsPollKindClosure {
return await editPollOriginalQuestionAnswersPollKindClosure(eventID, question, answers, pollKind) return await editPollOriginalQuestionAnswersMaxSelectionsPollKindClosure(eventID, question, answers, maxSelections, pollKind)
} else { } else {
return editPollOriginalQuestionAnswersPollKindReturnValue return editPollOriginalQuestionAnswersMaxSelectionsPollKindReturnValue
} }
} }
//MARK: - sendPollResponse //MARK: - sendPollResponse
@@ -13737,86 +13737,86 @@ nonisolated class TimelineProxyMock: TimelineProxyProtocol, @unchecked Sendable
} }
//MARK: - createPoll //MARK: - createPoll
private let createPollQuestionAnswersPollKindCallsCountLock = NSLock() private let createPollQuestionAnswersMaxSelectionsPollKindCallsCountLock = NSLock()
private nonisolated(unsafe) var createPollQuestionAnswersPollKindUnderlyingCallsCount = 0 private nonisolated(unsafe) var createPollQuestionAnswersMaxSelectionsPollKindUnderlyingCallsCount = 0
var createPollQuestionAnswersPollKindCallsCount: Int { var createPollQuestionAnswersMaxSelectionsPollKindCallsCount: Int {
get { createPollQuestionAnswersPollKindCallsCountLock.withLock { createPollQuestionAnswersPollKindUnderlyingCallsCount } } get { createPollQuestionAnswersMaxSelectionsPollKindCallsCountLock.withLock { createPollQuestionAnswersMaxSelectionsPollKindUnderlyingCallsCount } }
set { createPollQuestionAnswersPollKindCallsCountLock.withLock { createPollQuestionAnswersPollKindUnderlyingCallsCount = newValue } } set { createPollQuestionAnswersMaxSelectionsPollKindCallsCountLock.withLock { createPollQuestionAnswersMaxSelectionsPollKindUnderlyingCallsCount = newValue } }
} }
var createPollQuestionAnswersPollKindCalled: Bool { var createPollQuestionAnswersMaxSelectionsPollKindCalled: Bool {
return createPollQuestionAnswersPollKindCallsCount > 0 return createPollQuestionAnswersMaxSelectionsPollKindCallsCount > 0
} }
private let createPollQuestionAnswersPollKindReceivedArgumentsLock = NSLock() private let createPollQuestionAnswersMaxSelectionsPollKindReceivedArgumentsLock = NSLock()
private nonisolated(unsafe) var createPollQuestionAnswersPollKindUnderlyingReceivedArguments: (question: String, answers: [String], pollKind: Poll.Kind)? private nonisolated(unsafe) var createPollQuestionAnswersMaxSelectionsPollKindUnderlyingReceivedArguments: (question: String, answers: [String], maxSelections: Int, pollKind: Poll.Kind)?
var createPollQuestionAnswersPollKindReceivedArguments: (question: String, answers: [String], pollKind: Poll.Kind)? { var createPollQuestionAnswersMaxSelectionsPollKindReceivedArguments: (question: String, answers: [String], maxSelections: Int, pollKind: Poll.Kind)? {
get { createPollQuestionAnswersPollKindReceivedArgumentsLock.withLock { createPollQuestionAnswersPollKindUnderlyingReceivedArguments } } get { createPollQuestionAnswersMaxSelectionsPollKindReceivedArgumentsLock.withLock { createPollQuestionAnswersMaxSelectionsPollKindUnderlyingReceivedArguments } }
set { createPollQuestionAnswersPollKindReceivedArgumentsLock.withLock { createPollQuestionAnswersPollKindUnderlyingReceivedArguments = newValue } } set { createPollQuestionAnswersMaxSelectionsPollKindReceivedArgumentsLock.withLock { createPollQuestionAnswersMaxSelectionsPollKindUnderlyingReceivedArguments = newValue } }
} }
private let createPollQuestionAnswersPollKindReceivedInvocationsLock = NSLock() private let createPollQuestionAnswersMaxSelectionsPollKindReceivedInvocationsLock = NSLock()
private nonisolated(unsafe) var createPollQuestionAnswersPollKindUnderlyingReceivedInvocations: [(question: String, answers: [String], pollKind: Poll.Kind)] = [] private nonisolated(unsafe) var createPollQuestionAnswersMaxSelectionsPollKindUnderlyingReceivedInvocations: [(question: String, answers: [String], maxSelections: Int, pollKind: Poll.Kind)] = []
var createPollQuestionAnswersPollKindReceivedInvocations: [(question: String, answers: [String], pollKind: Poll.Kind)] { var createPollQuestionAnswersMaxSelectionsPollKindReceivedInvocations: [(question: String, answers: [String], maxSelections: Int, pollKind: Poll.Kind)] {
get { createPollQuestionAnswersPollKindReceivedInvocationsLock.withLock { createPollQuestionAnswersPollKindUnderlyingReceivedInvocations } } get { createPollQuestionAnswersMaxSelectionsPollKindReceivedInvocationsLock.withLock { createPollQuestionAnswersMaxSelectionsPollKindUnderlyingReceivedInvocations } }
set { createPollQuestionAnswersPollKindReceivedInvocationsLock.withLock { createPollQuestionAnswersPollKindUnderlyingReceivedInvocations = newValue } } set { createPollQuestionAnswersMaxSelectionsPollKindReceivedInvocationsLock.withLock { createPollQuestionAnswersMaxSelectionsPollKindUnderlyingReceivedInvocations = newValue } }
} }
private let createPollQuestionAnswersPollKindReturnValueLock = NSLock() private let createPollQuestionAnswersMaxSelectionsPollKindReturnValueLock = NSLock()
private nonisolated(unsafe) var createPollQuestionAnswersPollKindUnderlyingReturnValue: Result<Void, TimelineProxyError>! private nonisolated(unsafe) var createPollQuestionAnswersMaxSelectionsPollKindUnderlyingReturnValue: Result<Void, TimelineProxyError>!
var createPollQuestionAnswersPollKindReturnValue: Result<Void, TimelineProxyError>! { var createPollQuestionAnswersMaxSelectionsPollKindReturnValue: Result<Void, TimelineProxyError>! {
get { createPollQuestionAnswersPollKindReturnValueLock.withLock { createPollQuestionAnswersPollKindUnderlyingReturnValue } } get { createPollQuestionAnswersMaxSelectionsPollKindReturnValueLock.withLock { createPollQuestionAnswersMaxSelectionsPollKindUnderlyingReturnValue } }
set { createPollQuestionAnswersPollKindReturnValueLock.withLock { createPollQuestionAnswersPollKindUnderlyingReturnValue = newValue } } set { createPollQuestionAnswersMaxSelectionsPollKindReturnValueLock.withLock { createPollQuestionAnswersMaxSelectionsPollKindUnderlyingReturnValue = newValue } }
} }
nonisolated(unsafe) var createPollQuestionAnswersPollKindClosure: ((String, [String], Poll.Kind) async -> Result<Void, TimelineProxyError>)? nonisolated(unsafe) var createPollQuestionAnswersMaxSelectionsPollKindClosure: ((String, [String], Int, Poll.Kind) async -> Result<Void, TimelineProxyError>)?
@concurrent func createPoll(question: String, answers: [String], pollKind: Poll.Kind) async -> Result<Void, TimelineProxyError> { @concurrent func createPoll(question: String, answers: [String], maxSelections: Int, pollKind: Poll.Kind) async -> Result<Void, TimelineProxyError> {
createPollQuestionAnswersPollKindCallsCountLock.withLock { createPollQuestionAnswersPollKindUnderlyingCallsCount += 1 } createPollQuestionAnswersMaxSelectionsPollKindCallsCountLock.withLock { createPollQuestionAnswersMaxSelectionsPollKindUnderlyingCallsCount += 1 }
createPollQuestionAnswersPollKindReceivedArguments = (question: question, answers: answers, pollKind: pollKind) createPollQuestionAnswersMaxSelectionsPollKindReceivedArguments = (question: question, answers: answers, maxSelections: maxSelections, pollKind: pollKind)
createPollQuestionAnswersPollKindReceivedInvocationsLock.withLock { createPollQuestionAnswersPollKindUnderlyingReceivedInvocations.append((question: question, answers: answers, pollKind: pollKind)) } createPollQuestionAnswersMaxSelectionsPollKindReceivedInvocationsLock.withLock { createPollQuestionAnswersMaxSelectionsPollKindUnderlyingReceivedInvocations.append((question: question, answers: answers, maxSelections: maxSelections, pollKind: pollKind)) }
if let createPollQuestionAnswersPollKindClosure = createPollQuestionAnswersPollKindClosure { if let createPollQuestionAnswersMaxSelectionsPollKindClosure = createPollQuestionAnswersMaxSelectionsPollKindClosure {
return await createPollQuestionAnswersPollKindClosure(question, answers, pollKind) return await createPollQuestionAnswersMaxSelectionsPollKindClosure(question, answers, maxSelections, pollKind)
} else { } else {
return createPollQuestionAnswersPollKindReturnValue return createPollQuestionAnswersMaxSelectionsPollKindReturnValue
} }
} }
//MARK: - editPoll //MARK: - editPoll
private let editPollOriginalQuestionAnswersPollKindCallsCountLock = NSLock() private let editPollOriginalQuestionAnswersMaxSelectionsPollKindCallsCountLock = NSLock()
private nonisolated(unsafe) var editPollOriginalQuestionAnswersPollKindUnderlyingCallsCount = 0 private nonisolated(unsafe) var editPollOriginalQuestionAnswersMaxSelectionsPollKindUnderlyingCallsCount = 0
var editPollOriginalQuestionAnswersPollKindCallsCount: Int { var editPollOriginalQuestionAnswersMaxSelectionsPollKindCallsCount: Int {
get { editPollOriginalQuestionAnswersPollKindCallsCountLock.withLock { editPollOriginalQuestionAnswersPollKindUnderlyingCallsCount } } get { editPollOriginalQuestionAnswersMaxSelectionsPollKindCallsCountLock.withLock { editPollOriginalQuestionAnswersMaxSelectionsPollKindUnderlyingCallsCount } }
set { editPollOriginalQuestionAnswersPollKindCallsCountLock.withLock { editPollOriginalQuestionAnswersPollKindUnderlyingCallsCount = newValue } } set { editPollOriginalQuestionAnswersMaxSelectionsPollKindCallsCountLock.withLock { editPollOriginalQuestionAnswersMaxSelectionsPollKindUnderlyingCallsCount = newValue } }
} }
var editPollOriginalQuestionAnswersPollKindCalled: Bool { var editPollOriginalQuestionAnswersMaxSelectionsPollKindCalled: Bool {
return editPollOriginalQuestionAnswersPollKindCallsCount > 0 return editPollOriginalQuestionAnswersMaxSelectionsPollKindCallsCount > 0
} }
private let editPollOriginalQuestionAnswersPollKindReceivedArgumentsLock = NSLock() private let editPollOriginalQuestionAnswersMaxSelectionsPollKindReceivedArgumentsLock = NSLock()
private nonisolated(unsafe) var editPollOriginalQuestionAnswersPollKindUnderlyingReceivedArguments: (eventID: String, question: String, answers: [String], pollKind: Poll.Kind)? private nonisolated(unsafe) var editPollOriginalQuestionAnswersMaxSelectionsPollKindUnderlyingReceivedArguments: (eventID: String, question: String, answers: [String], maxSelections: Int, pollKind: Poll.Kind)?
var editPollOriginalQuestionAnswersPollKindReceivedArguments: (eventID: String, question: String, answers: [String], pollKind: Poll.Kind)? { var editPollOriginalQuestionAnswersMaxSelectionsPollKindReceivedArguments: (eventID: String, question: String, answers: [String], maxSelections: Int, pollKind: Poll.Kind)? {
get { editPollOriginalQuestionAnswersPollKindReceivedArgumentsLock.withLock { editPollOriginalQuestionAnswersPollKindUnderlyingReceivedArguments } } get { editPollOriginalQuestionAnswersMaxSelectionsPollKindReceivedArgumentsLock.withLock { editPollOriginalQuestionAnswersMaxSelectionsPollKindUnderlyingReceivedArguments } }
set { editPollOriginalQuestionAnswersPollKindReceivedArgumentsLock.withLock { editPollOriginalQuestionAnswersPollKindUnderlyingReceivedArguments = newValue } } set { editPollOriginalQuestionAnswersMaxSelectionsPollKindReceivedArgumentsLock.withLock { editPollOriginalQuestionAnswersMaxSelectionsPollKindUnderlyingReceivedArguments = newValue } }
} }
private let editPollOriginalQuestionAnswersPollKindReceivedInvocationsLock = NSLock() private let editPollOriginalQuestionAnswersMaxSelectionsPollKindReceivedInvocationsLock = NSLock()
private nonisolated(unsafe) var editPollOriginalQuestionAnswersPollKindUnderlyingReceivedInvocations: [(eventID: String, question: String, answers: [String], pollKind: Poll.Kind)] = [] private nonisolated(unsafe) var editPollOriginalQuestionAnswersMaxSelectionsPollKindUnderlyingReceivedInvocations: [(eventID: String, question: String, answers: [String], maxSelections: Int, pollKind: Poll.Kind)] = []
var editPollOriginalQuestionAnswersPollKindReceivedInvocations: [(eventID: String, question: String, answers: [String], pollKind: Poll.Kind)] { var editPollOriginalQuestionAnswersMaxSelectionsPollKindReceivedInvocations: [(eventID: String, question: String, answers: [String], maxSelections: Int, pollKind: Poll.Kind)] {
get { editPollOriginalQuestionAnswersPollKindReceivedInvocationsLock.withLock { editPollOriginalQuestionAnswersPollKindUnderlyingReceivedInvocations } } get { editPollOriginalQuestionAnswersMaxSelectionsPollKindReceivedInvocationsLock.withLock { editPollOriginalQuestionAnswersMaxSelectionsPollKindUnderlyingReceivedInvocations } }
set { editPollOriginalQuestionAnswersPollKindReceivedInvocationsLock.withLock { editPollOriginalQuestionAnswersPollKindUnderlyingReceivedInvocations = newValue } } set { editPollOriginalQuestionAnswersMaxSelectionsPollKindReceivedInvocationsLock.withLock { editPollOriginalQuestionAnswersMaxSelectionsPollKindUnderlyingReceivedInvocations = newValue } }
} }
private let editPollOriginalQuestionAnswersPollKindReturnValueLock = NSLock() private let editPollOriginalQuestionAnswersMaxSelectionsPollKindReturnValueLock = NSLock()
private nonisolated(unsafe) var editPollOriginalQuestionAnswersPollKindUnderlyingReturnValue: Result<Void, TimelineProxyError>! private nonisolated(unsafe) var editPollOriginalQuestionAnswersMaxSelectionsPollKindUnderlyingReturnValue: Result<Void, TimelineProxyError>!
var editPollOriginalQuestionAnswersPollKindReturnValue: Result<Void, TimelineProxyError>! { var editPollOriginalQuestionAnswersMaxSelectionsPollKindReturnValue: Result<Void, TimelineProxyError>! {
get { editPollOriginalQuestionAnswersPollKindReturnValueLock.withLock { editPollOriginalQuestionAnswersPollKindUnderlyingReturnValue } } get { editPollOriginalQuestionAnswersMaxSelectionsPollKindReturnValueLock.withLock { editPollOriginalQuestionAnswersMaxSelectionsPollKindUnderlyingReturnValue } }
set { editPollOriginalQuestionAnswersPollKindReturnValueLock.withLock { editPollOriginalQuestionAnswersPollKindUnderlyingReturnValue = newValue } } set { editPollOriginalQuestionAnswersMaxSelectionsPollKindReturnValueLock.withLock { editPollOriginalQuestionAnswersMaxSelectionsPollKindUnderlyingReturnValue = newValue } }
} }
nonisolated(unsafe) var editPollOriginalQuestionAnswersPollKindClosure: ((String, String, [String], Poll.Kind) async -> Result<Void, TimelineProxyError>)? nonisolated(unsafe) var editPollOriginalQuestionAnswersMaxSelectionsPollKindClosure: ((String, String, [String], Int, Poll.Kind) async -> Result<Void, TimelineProxyError>)?
@concurrent func editPoll(original eventID: String, question: String, answers: [String], pollKind: Poll.Kind) async -> Result<Void, TimelineProxyError> { @concurrent func editPoll(original eventID: String, question: String, answers: [String], maxSelections: Int, pollKind: Poll.Kind) async -> Result<Void, TimelineProxyError> {
editPollOriginalQuestionAnswersPollKindCallsCountLock.withLock { editPollOriginalQuestionAnswersPollKindUnderlyingCallsCount += 1 } editPollOriginalQuestionAnswersMaxSelectionsPollKindCallsCountLock.withLock { editPollOriginalQuestionAnswersMaxSelectionsPollKindUnderlyingCallsCount += 1 }
editPollOriginalQuestionAnswersPollKindReceivedArguments = (eventID: eventID, question: question, answers: answers, pollKind: pollKind) editPollOriginalQuestionAnswersMaxSelectionsPollKindReceivedArguments = (eventID: eventID, question: question, answers: answers, maxSelections: maxSelections, pollKind: pollKind)
editPollOriginalQuestionAnswersPollKindReceivedInvocationsLock.withLock { editPollOriginalQuestionAnswersPollKindUnderlyingReceivedInvocations.append((eventID: eventID, question: question, answers: answers, pollKind: pollKind)) } editPollOriginalQuestionAnswersMaxSelectionsPollKindReceivedInvocationsLock.withLock { editPollOriginalQuestionAnswersMaxSelectionsPollKindUnderlyingReceivedInvocations.append((eventID: eventID, question: question, answers: answers, maxSelections: maxSelections, pollKind: pollKind)) }
if let editPollOriginalQuestionAnswersPollKindClosure = editPollOriginalQuestionAnswersPollKindClosure { if let editPollOriginalQuestionAnswersMaxSelectionsPollKindClosure = editPollOriginalQuestionAnswersMaxSelectionsPollKindClosure {
return await editPollOriginalQuestionAnswersPollKindClosure(eventID, question, answers, pollKind) return await editPollOriginalQuestionAnswersMaxSelectionsPollKindClosure(eventID, question, answers, maxSelections, pollKind)
} else { } else {
return editPollOriginalQuestionAnswersPollKindReturnValue return editPollOriginalQuestionAnswersMaxSelectionsPollKindReturnValue
} }
} }
//MARK: - sendPollResponse //MARK: - sendPollResponse
+2 -1
View File
@@ -11,13 +11,14 @@ import Foundation
extension Poll { extension Poll {
static func mock(question: String, static func mock(question: String,
pollKind: Poll.Kind = .disclosed, pollKind: Poll.Kind = .disclosed,
maxSelections: Int = 1,
options: [Poll.Option], options: [Poll.Option],
votes: [String: [String]] = [:], votes: [String: [String]] = [:],
ended: Bool = false, ended: Bool = false,
createdByAccountOwner: Bool = false) -> Self { createdByAccountOwner: Bool = false) -> Self {
.init(question: question, .init(question: question,
kind: pollKind, kind: pollKind,
maxSelections: 1, maxSelections: maxSelections,
options: options, options: options,
votes: votes, votes: votes,
endDate: ended ? Date() : nil, endDate: ended ? Date() : nil,
@@ -176,17 +176,17 @@ struct TimelineControllerMockConfiguration {
return .success(()) return .success(())
} }
createPollQuestionAnswersPollKindClosure = { [weak self, timelineProxy] question, answers, pollKind in createPollQuestionAnswersMaxSelectionsPollKindClosure = { [weak self, timelineProxy] question, answers, maxSelections, pollKind in
self?.callbacks.send(.messageSentOrEdited) self?.callbacks.send(.messageSentOrEdited)
if let timelineProxy { if let timelineProxy {
_ = await timelineProxy.createPoll(question: question, answers: answers, pollKind: pollKind) _ = await timelineProxy.createPoll(question: question, answers: answers, maxSelections: maxSelections, pollKind: pollKind)
} }
return .success(()) return .success(())
} }
editPollOriginalQuestionAnswersPollKindClosure = { [timelineProxy] eventID, question, answers, pollKind in editPollOriginalQuestionAnswersMaxSelectionsPollKindClosure = { [timelineProxy] eventID, question, answers, maxSelections, pollKind in
if let timelineProxy { if let timelineProxy {
_ = await timelineProxy.editPoll(original: eventID, question: question, answers: answers, pollKind: pollKind) _ = await timelineProxy.editPoll(original: eventID, question: question, answers: answers, maxSelections: maxSelections, pollKind: pollKind)
} }
return .success(()) return .success(())
} }
@@ -29,8 +29,8 @@ extension TimelineProxyMock {
paginateBackwardsRequestSizeReturnValue = .success(()) paginateBackwardsRequestSizeReturnValue = .success(())
paginateForwardsRequestSizeReturnValue = .success(()) paginateForwardsRequestSizeReturnValue = .success(())
sendReadReceiptForTypeReturnValue = .success(()) sendReadReceiptForTypeReturnValue = .success(())
createPollQuestionAnswersPollKindReturnValue = .success(()) createPollQuestionAnswersMaxSelectionsPollKindReturnValue = .success(())
editPollOriginalQuestionAnswersPollKindReturnValue = .success(()) editPollOriginalQuestionAnswersMaxSelectionsPollKindReturnValue = .success(())
if let provider = configuration.timelineItemProvider { if let provider = configuration.timelineItemProvider {
timelineItemProvider = provider timelineItemProvider = provider
@@ -295,6 +295,9 @@ enum A11yIdentifiers {
struct PollFormScreen { struct PollFormScreen {
let addOption = "poll_form-add_option" let addOption = "poll_form-add_option"
let maxSelections = "poll_form-max_selections"
let maxSelectionsDecrement = "poll_form-max_selections-decrement"
let maxSelectionsIncrement = "poll_form-max_selections-increment"
let pollKind = "poll_form-kind" let pollKind = "poll_form-kind"
let question = "poll_form-question" let question = "poll_form-question"
let submit = "poll_form-submit" let submit = "poll_form-submit"
@@ -77,7 +77,18 @@ enum PollFormMode: Hashable {
struct PollFormScreenViewStateBindings: Equatable { struct PollFormScreenViewStateBindings: Equatable {
var question = "" var question = ""
var options: [Option] = [.init(), .init()] var options: [Option] = [.init(), .init()] {
didSet {
maxSelections = clampedMaxSelections
}
}
var maxSelections = 1 {
didSet {
maxSelections = clampedMaxSelections
}
}
var isUndisclosed = false var isUndisclosed = false
struct Option: Identifiable, Equatable { struct Option: Identifiable, Equatable {
@@ -86,13 +97,20 @@ struct PollFormScreenViewStateBindings: Equatable {
} }
var hasValidContent: Bool { var hasValidContent: Bool {
!question.isEmpty && options.count >= 2 && options.allSatisfy { !$0.text.isEmpty } !question.isEmpty && options.count >= 2 && options.allSatisfy { !$0.text.isEmpty } && maxSelections >= 1 && maxSelections <= options.count
} }
var alertInfo: AlertInfo<UUID>? var alertInfo: AlertInfo<UUID>?
static func == (lhs: PollFormScreenViewStateBindings, rhs: PollFormScreenViewStateBindings) -> Bool { static func == (lhs: PollFormScreenViewStateBindings, rhs: PollFormScreenViewStateBindings) -> Bool {
lhs.question == rhs.question && lhs.options.map(\.text) == rhs.options.map(\.text) && lhs.isUndisclosed == rhs.isUndisclosed lhs.question == rhs.question &&
lhs.options.map(\.text) == rhs.options.map(\.text) &&
lhs.maxSelections == rhs.maxSelections &&
lhs.isUndisclosed == rhs.isUndisclosed
}
private var clampedMaxSelections: Int {
min(max(maxSelections, 1), max(options.count, 1))
} }
} }
@@ -100,6 +118,7 @@ extension PollFormScreenViewStateBindings {
init(poll: Poll) { init(poll: Poll) {
self.init(question: poll.question, self.init(question: poll.question,
options: poll.options.map { .init(text: $0.text) }, options: poll.options.map { .init(text: $0.text) },
maxSelections: min(max(poll.maxSelections, 1), max(poll.options.count, 1)),
isUndisclosed: poll.kind == .undisclosed) isUndisclosed: poll.kind == .undisclosed)
} }
} }
@@ -110,4 +129,6 @@ enum PollFormScreenViewAction {
case delete case delete
case deleteOption(index: Int) case deleteOption(index: Int)
case addOption case addOption
case decrementMaxSelections
case incrementMaxSelections
} }
@@ -40,14 +40,15 @@ class PollFormScreenViewModel: PollFormScreenViewModelType, PollFormScreenViewMo
case .submit: case .submit:
let question = state.bindings.question let question = state.bindings.question
let options = state.bindings.options.map(\.text) let options = state.bindings.options.map(\.text)
let maxSelections = state.bindings.maxSelections
let pollKind = state.bindings.isUndisclosed ? Poll.Kind.undisclosed : .disclosed let pollKind = state.bindings.isUndisclosed ? Poll.Kind.undisclosed : .disclosed
Task { Task {
switch state.mode { switch state.mode {
case .new: case .new:
await createPoll(question: question, options: options, pollKind: pollKind) await createPoll(question: question, options: options, maxSelections: maxSelections, pollKind: pollKind)
case .edit(let eventID, _): case .edit(let eventID, _):
await editPoll(pollStartID: eventID, question: question, options: options, pollKind: pollKind) await editPoll(pollStartID: eventID, question: question, options: options, maxSelections: maxSelections, pollKind: pollKind)
} }
} }
case .delete: case .delete:
@@ -80,13 +81,17 @@ class PollFormScreenViewModel: PollFormScreenViewModelType, PollFormScreenViewMo
return return
} }
state.bindings.options.append(.init()) state.bindings.options.append(.init())
case .decrementMaxSelections:
state.bindings.maxSelections -= 1
case .incrementMaxSelections:
state.bindings.maxSelections += 1
} }
} }
// MARK: - Private // MARK: - Private
private func createPoll(question: String, options: [String], pollKind: Poll.Kind) async { private func createPoll(question: String, options: [String], maxSelections: Int, pollKind: Poll.Kind) async {
guard case .success = await timelineController.createPoll(question: question, answers: options, pollKind: pollKind) else { guard case .success = await timelineController.createPoll(question: question, answers: options, maxSelections: maxSelections, pollKind: pollKind) else {
userIndicatorController.submitIndicator(UserIndicator(title: L10n.errorUnknown)) userIndicatorController.submitIndicator(UserIndicator(title: L10n.errorUnknown))
return return
} }
@@ -102,8 +107,8 @@ class PollFormScreenViewModel: PollFormScreenViewModelType, PollFormScreenViewMo
analytics.trackPollCreated(isUndisclosed: pollKind == .undisclosed, numberOfAnswers: options.count) analytics.trackPollCreated(isUndisclosed: pollKind == .undisclosed, numberOfAnswers: options.count)
} }
private func editPoll(pollStartID: String, question: String, options: [String], pollKind: Poll.Kind) async { private func editPoll(pollStartID: String, question: String, options: [String], maxSelections: Int, pollKind: Poll.Kind) async {
switch await timelineController.editPoll(original: pollStartID, question: question, answers: options, pollKind: pollKind) { switch await timelineController.editPoll(original: pollStartID, question: question, answers: options, maxSelections: maxSelections, pollKind: pollKind) {
case .success: case .success:
actionsSubject.send(.close) actionsSubject.send(.close)
case .failure: case .failure:
@@ -22,7 +22,7 @@ struct PollFormScreen: View {
Form { Form {
questionSection questionSection
optionsSection optionsSection
showResultsSection settingsSection
deletePollSection deletePollSection
} }
.trackAnalyticsIfNeeded(context: context) .trackAnalyticsIfNeeded(context: context)
@@ -106,8 +106,16 @@ struct PollFormScreen: View {
focus = nextOptionIndex.map { .option(index: $0) } focus = nextOptionIndex.map { .option(index: $0) }
} }
private var showResultsSection: some View { private var settingsSection: some View {
Section { Section {
PollFormMaxSelectionsRow(maxSelections: context.maxSelections,
maxNumberOfSelections: context.options.count) {
context.send(viewAction: .decrementMaxSelections)
} incrementAction: {
context.send(viewAction: .incrementMaxSelections)
}
.accessibilityIdentifier(A11yIdentifiers.pollFormScreen.maxSelections)
ListRow(label: .plain(title: L10n.screenCreatePollAnonymousDesc), ListRow(label: .plain(title: L10n.screenCreatePollAnonymousDesc),
kind: .toggle($context.isUndisclosed)) kind: .toggle($context.isUndisclosed))
.accessibilityIdentifier(A11yIdentifiers.pollFormScreen.pollKind) .accessibilityIdentifier(A11yIdentifiers.pollFormScreen.pollKind)
@@ -160,6 +168,55 @@ private extension View {
} }
} }
private struct PollFormMaxSelectionsRow: View {
let maxSelections: Int
let maxNumberOfSelections: Int
let decrementAction: () -> Void
let incrementAction: () -> Void
var body: some View {
ListRow(kind: .custom {
HStack(spacing: 16) {
Text(UntranslatedL10n.screenCreatePollVotesAllowedPerPerson)
.font(.compound.bodyLG)
.foregroundStyle(.compound.textPrimary)
.lineLimit(2)
.frame(maxWidth: .infinity, alignment: .leading)
HStack(spacing: 8) {
Button(action: decrementAction) {
Image(systemSymbol: .minus)
.frame(width: 20, height: 20)
}
.disabled(maxSelections <= 1)
.buttonStyle(.compound(.tertiary, size: .toolbarIcon))
.accessibilityLabel(UntranslatedL10n.a11yScreenCreatePollVotesAllowedDecrease)
.accessibilityIdentifier(A11yIdentifiers.pollFormScreen.maxSelectionsDecrement)
Text("\(maxSelections)")
.font(.compound.bodyLGSemibold)
.foregroundStyle(.compound.textPrimary)
.monospacedDigit()
.frame(minWidth: 28)
.accessibilityLabel(Text(maxSelections.formatted()))
Button(action: incrementAction) {
Image(systemSymbol: .plus)
.frame(width: 20, height: 20)
}
.disabled(maxSelections >= maxNumberOfSelections)
.buttonStyle(.compound(.tertiary, size: .toolbarIcon))
.accessibilityLabel(UntranslatedL10n.a11yScreenCreatePollVotesAllowedIncrease)
.accessibilityIdentifier(A11yIdentifiers.pollFormScreen.maxSelectionsIncrement)
}
.layoutPriority(1)
}
.padding(.horizontal, ListRowPadding.horizontal)
.padding(.vertical, ListRowPadding.vertical)
})
}
}
private struct PollFormOptionRow: View { private struct PollFormOptionRow: View {
@Environment(\.editMode) var editMode @Environment(\.editMode) var editMode
@Binding var text: String @Binding var text: String
@@ -42,7 +42,7 @@ enum RoomPollsHistoryScreenViewAction {
case filter(RoomPollsHistoryFilter) case filter(RoomPollsHistoryFilter)
case end(pollStartID: String) case end(pollStartID: String)
case edit(pollStartID: String, poll: Poll) case edit(pollStartID: String, poll: Poll)
case sendPollResponse(pollStartID: String, optionID: String) case sendPollResponse(pollStartID: String, answerIDs: [String])
case loadMore case loadMore
} }
@@ -58,8 +58,8 @@ class RoomPollsHistoryScreenViewModel: RoomPollsHistoryScreenViewModelType, Room
updatePollsList(filter: filter) updatePollsList(filter: filter)
case .loadMore: case .loadMore:
paginateBackwards() paginateBackwards()
case .sendPollResponse(let pollStartID, let optionID): case .sendPollResponse(let pollStartID, let answerIDs):
sendPollResponse(pollStartID: pollStartID, optionID: optionID) sendPollResponse(pollStartID: pollStartID, answerIDs: answerIDs)
} }
} }
@@ -117,10 +117,10 @@ class RoomPollsHistoryScreenViewModel: RoomPollsHistoryScreenViewModelType, Room
} }
} }
private func sendPollResponse(pollStartID: String, optionID: String) { private func sendPollResponse(pollStartID: String, answerIDs: [String]) {
Task { Task {
do { do {
try await pollInteractionHandler.sendPollResponse(pollStartID: pollStartID, optionID: optionID).get() try await pollInteractionHandler.sendPollResponse(pollStartID: pollStartID, answerIDs: answerIDs).get()
} catch { } catch {
MXLog.error("Failed to send poll response. \(error)") MXLog.error("Failed to send poll response. \(error)")
displayError(message: L10n.errorUnknown) displayError(message: L10n.errorUnknown)
@@ -62,9 +62,9 @@ struct RoomPollsHistoryScreen: View {
PollView(poll: pollTimelineItem.item.poll, PollView(poll: pollTimelineItem.item.poll,
state: .full(isEditable: pollTimelineItem.item.isEditable), sender: pollTimelineItem.item.sender) { action in state: .full(isEditable: pollTimelineItem.item.isEditable), sender: pollTimelineItem.item.sender) { action in
switch action { switch action {
case .selectOption(let optionID): case .sendResponse(let answerIDs):
guard let pollStartID = pollTimelineItem.item.id.eventID else { return } guard let pollStartID = pollTimelineItem.item.id.eventID else { return }
context.send(viewAction: .sendPollResponse(pollStartID: pollStartID, optionID: optionID)) context.send(viewAction: .sendPollResponse(pollStartID: pollStartID, answerIDs: answerIDs))
case .edit: case .edit:
guard let pollStartID = pollTimelineItem.item.id.eventID else { return } guard let pollStartID = pollTimelineItem.item.id.eventID else { return }
context.send(viewAction: .edit(pollStartID: pollStartID, poll: pollTimelineItem.item.poll)) context.send(viewAction: .edit(pollStartID: pollStartID, poll: pollTimelineItem.item.poll))
@@ -248,9 +248,9 @@ class TimelineInteractionHandler {
// MARK: Polls // MARK: Polls
func sendPollResponse(pollStartID: String, optionID: String) { func sendPollResponse(pollStartID: String, answerIDs: [String]) {
Task { Task {
let sendPollResponseResult = await pollInteractionHandler.sendPollResponse(pollStartID: pollStartID, optionID: optionID) let sendPollResponseResult = await pollInteractionHandler.sendPollResponse(pollStartID: pollStartID, answerIDs: answerIDs)
switch sendPollResponseResult { switch sendPollResponseResult {
case .success: case .success:
@@ -36,7 +36,7 @@ enum TimelineViewModelAction {
} }
enum TimelineViewPollAction { enum TimelineViewPollAction {
case selectOption(pollStartID: String, optionID: String) case sendResponse(pollStartID: String, answerIDs: [String])
case end(pollStartID: String) case end(pollStartID: String)
case edit(pollStartID: String, poll: Poll) case edit(pollStartID: String, poll: Poll)
} }
@@ -376,8 +376,8 @@ class TimelineViewModel: TimelineViewModelType, TimelineViewModelProtocol {
private func handlePollAction(_ action: TimelineViewPollAction) { private func handlePollAction(_ action: TimelineViewPollAction) {
switch action { switch action {
case let .selectOption(pollStartID, optionID): case let .sendResponse(pollStartID, answerIDs):
timelineInteractionHandler.sendPollResponse(pollStartID: pollStartID, optionID: optionID) timelineInteractionHandler.sendPollResponse(pollStartID: pollStartID, answerIDs: answerIDs)
case let .end(pollStartID): case let .end(pollStartID):
displayAlert(.pollEndConfirmation(pollStartID)) displayAlert(.pollEndConfirmation(pollStartID))
case .edit(let eventID, let poll): case .edit(let eventID, let poll):
@@ -10,7 +10,7 @@ import Compound
import SwiftUI import SwiftUI
enum PollViewAction { enum PollViewAction {
case selectOption(optionID: String) case sendResponse(answerIDs: [String])
case edit case edit
case end case end
} }
@@ -100,13 +100,13 @@ struct PollView: View {
} }
private func isRemovePreviousSelectionHintEnabled(option: Poll.Option) -> Bool { private func isRemovePreviousSelectionHintEnabled(option: Poll.Option) -> Bool {
!poll.hasEnded && poll.hasMaxSelections && !option.isSelected !poll.hasEnded && poll.maxSelections == 1 && poll.hasMaxSelections && !option.isSelected
} }
private func pollOption(option: Poll.Option) -> some View { private func pollOption(option: Poll.Option) -> some View {
Button { Button {
guard !option.isSelected else { return } guard let answerIDs = poll.answerIDsAfterSelecting(optionID: option.id) else { return }
actionHandler(.selectOption(optionID: option.id)) actionHandler(.sendResponse(answerIDs: answerIDs))
feedbackGenerator.impactOccurred() feedbackGenerator.impactOccurred()
} label: { } label: {
PollOptionView(pollOption: option, PollOptionView(pollOption: option,
@@ -114,7 +114,25 @@ struct PollView: View {
isFinalResult: poll.hasEnded) isFinalResult: poll.hasEnded)
.foregroundColor(progressBarColor(for: option)) .foregroundColor(progressBarColor(for: option))
} }
.disabled(poll.hasEnded) .disabled(isOptionDisabled(option))
}
private func isOptionDisabled(_ option: Poll.Option) -> Bool {
if poll.hasEnded {
return true
}
// Single-choice polls replace the current answer; multi-choice polls keep
// at least one answer while enforcing the max.
if poll.maxSelections == 1 {
return option.isSelected
}
if option.isSelected {
return poll.options.filter(\.isSelected).count == 1
}
return poll.hasMaxSelections
} }
@ViewBuilder @ViewBuilder
@@ -26,9 +26,9 @@ struct PollRoomTimelineView: View {
state: state, state: state,
sender: timelineItem.sender) { action in sender: timelineItem.sender) { action in
switch action { switch action {
case .selectOption(let optionID): case .sendResponse(let answerIDs):
guard let eventID, let option = poll.options.first(where: { $0.id == optionID }), !option.isSelected else { return } guard let eventID else { return }
context.send(viewAction: .handlePollAction(.selectOption(pollStartID: eventID, optionID: option.id))) context.send(viewAction: .handlePollAction(.sendResponse(pollStartID: eventID, answerIDs: answerIDs)))
case .edit: case .edit:
guard let eventID else { return } guard let eventID else { return }
context.send(viewAction: .handlePollAction(.edit(pollStartID: eventID, poll: poll))) context.send(viewAction: .handlePollAction(.edit(pollStartID: eventID, poll: poll)))
@@ -17,8 +17,8 @@ class PollInteractionHandler: PollInteractionHandlerProtocol {
self.timelineController = timelineController self.timelineController = timelineController
} }
func sendPollResponse(pollStartID: String, optionID: String) async -> Result<Void, Error> { func sendPollResponse(pollStartID: String, answerIDs: [String]) async -> Result<Void, Error> {
let sendPollResponseResult = await timelineController.sendPollResponse(pollStartID: pollStartID, answers: [optionID]) let sendPollResponseResult = await timelineController.sendPollResponse(pollStartID: pollStartID, answers: answerIDs)
analyticsService.trackPollVote() analyticsService.trackPollVote()
return sendPollResponseResult.mapError { $0 } return sendPollResponseResult.mapError { $0 }
@@ -9,7 +9,7 @@
import Foundation import Foundation
protocol PollInteractionHandlerProtocol { protocol PollInteractionHandlerProtocol {
func sendPollResponse(pollStartID: String, optionID: String) async -> Result<Void, Error> func sendPollResponse(pollStartID: String, answerIDs: [String]) async -> Result<Void, Error>
func endPoll(pollStartID: String) async -> Result<Void, Error> func endPoll(pollStartID: String) async -> Result<Void, Error>
} }
@@ -404,8 +404,8 @@ class TimelineController: TimelineControllerProtocol {
// MARK: - Polls // MARK: - Polls
func createPoll(question: String, answers: [String], pollKind: Poll.Kind) async -> Result<Void, TimelineControllerError> { func createPoll(question: String, answers: [String], maxSelections: Int, pollKind: Poll.Kind) async -> Result<Void, TimelineControllerError> {
switch await activeTimeline.createPoll(question: question, answers: answers, pollKind: pollKind).mapError(TimelineControllerError.timelineProxyError) { switch await activeTimeline.createPoll(question: question, answers: answers, maxSelections: maxSelections, pollKind: pollKind).mapError(TimelineControllerError.timelineProxyError) {
case .success: case .success:
callbacks.send(.messageSentOrEdited) callbacks.send(.messageSentOrEdited)
return .success(()) return .success(())
@@ -414,8 +414,8 @@ class TimelineController: TimelineControllerProtocol {
} }
} }
func editPoll(original eventID: String, question: String, answers: [String], pollKind: Poll.Kind) async -> Result<Void, TimelineControllerError> { func editPoll(original eventID: String, question: String, answers: [String], maxSelections: Int, pollKind: Poll.Kind) async -> Result<Void, TimelineControllerError> {
await activeTimeline.editPoll(original: eventID, question: question, answers: answers, pollKind: pollKind).mapError(TimelineControllerError.timelineProxyError) await activeTimeline.editPoll(original: eventID, question: question, answers: answers, maxSelections: maxSelections, pollKind: pollKind).mapError(TimelineControllerError.timelineProxyError)
} }
func sendPollResponse(pollStartID: String, answers: [String]) async -> Result<Void, TimelineControllerError> { func sendPollResponse(pollStartID: String, answers: [String]) async -> Result<Void, TimelineControllerError> {
@@ -137,11 +137,12 @@ protocol TimelineControllerProtocol: Sendable {
// MARK: - Poll // MARK: - Poll
func createPoll(question: String, answers: [String], pollKind: Poll.Kind) async -> Result<Void, TimelineControllerError> func createPoll(question: String, answers: [String], maxSelections: Int, pollKind: Poll.Kind) async -> Result<Void, TimelineControllerError>
func editPoll(original eventID: String, func editPoll(original eventID: String,
question: String, question: String,
answers: [String], answers: [String],
maxSelections: Int,
pollKind: Poll.Kind) async -> Result<Void, TimelineControllerError> pollKind: Poll.Kind) async -> Result<Void, TimelineControllerError>
func sendPollResponse(pollStartID: String, answers: [String]) async -> Result<Void, TimelineControllerError> func sendPollResponse(pollStartID: String, answers: [String]) async -> Result<Void, TimelineControllerError>
@@ -43,6 +43,23 @@ nonisolated struct Poll: Hashable {
options.filter(\.isSelected).count == maxSelections options.filter(\.isSelected).count == maxSelections
} }
func answerIDsAfterSelecting(optionID: String) -> [String]? {
guard options.contains(where: { $0.id == optionID }) else { return nil }
let selectedOptionIDs = options.filter(\.isSelected).map(\.id)
if selectedOptionIDs.contains(optionID) {
guard maxSelections > 1, selectedOptionIDs.count > 1 else { return nil }
return selectedOptionIDs.filter { $0 != optionID }
}
guard maxSelections > 1 else {
return [optionID]
}
guard selectedOptionIDs.count < maxSelections else { return nil }
return selectedOptionIDs + [optionID]
}
struct Option: Hashable { struct Option: Hashable {
let id: String let id: String
let text: String let text: String
@@ -494,12 +494,15 @@ final class TimelineProxy: TimelineProxyProtocol {
// MARK: - Polls // MARK: - Polls
func createPoll(question: String, answers: [String], func createPoll(question: String, answers: [String], maxSelections: Int,
pollKind: Poll.Kind) async -> Result<Void, TimelineProxyError> { pollKind: Poll.Kind) async -> Result<Void, TimelineProxyError> {
MXLog.info("Creating poll") MXLog.info("Creating poll")
do { do {
try await timeline.createPoll(question: question, answers: answers, maxSelections: 1, pollKind: .init(pollKind: pollKind)) try await timeline.createPoll(question: question,
answers: answers,
maxSelections: UInt8(max(1, min(maxSelections, answers.count))),
pollKind: .init(pollKind: pollKind))
MXLog.info("Finished creating poll") MXLog.info("Finished creating poll")
@@ -513,6 +516,7 @@ final class TimelineProxy: TimelineProxyProtocol {
func editPoll(original eventID: String, func editPoll(original eventID: String,
question: String, question: String,
answers: [String], answers: [String],
maxSelections: Int,
pollKind: Poll.Kind) async -> Result<Void, TimelineProxyError> { pollKind: Poll.Kind) async -> Result<Void, TimelineProxyError> {
MXLog.info("Editing poll with eventID: \(eventID)") MXLog.info("Editing poll with eventID: \(eventID)")
@@ -522,7 +526,7 @@ final class TimelineProxy: TimelineProxyProtocol {
try await timeline.edit(eventOrTransactionId: originalEvent.eventOrTransactionId, try await timeline.edit(eventOrTransactionId: originalEvent.eventOrTransactionId,
newContent: .pollStart(pollData: .init(question: question, newContent: .pollStart(pollData: .init(question: question,
answers: answers, answers: answers,
maxSelections: 1, maxSelections: UInt8(max(1, min(maxSelections, answers.count))),
pollKind: .init(pollKind: pollKind)))) pollKind: .init(pollKind: pollKind))))
MXLog.info("Finished editing poll with eventID: \(eventID)") MXLog.info("Finished editing poll with eventID: \(eventID)")
@@ -124,11 +124,12 @@ protocol TimelineProxyProtocol: Sendable {
func toggleReaction(_ reaction: String, to eventID: TimelineItemIdentifier.EventOrTransactionID) async -> Result<Void, TimelineProxyError> func toggleReaction(_ reaction: String, to eventID: TimelineItemIdentifier.EventOrTransactionID) async -> Result<Void, TimelineProxyError>
func createPoll(question: String, answers: [String], pollKind: Poll.Kind) async -> Result<Void, TimelineProxyError> func createPoll(question: String, answers: [String], maxSelections: Int, pollKind: Poll.Kind) async -> Result<Void, TimelineProxyError>
func editPoll(original eventID: String, func editPoll(original eventID: String,
question: String, question: String,
answers: [String], answers: [String],
maxSelections: Int,
pollKind: Poll.Kind) async -> Result<Void, TimelineProxyError> pollKind: Poll.Kind) async -> Result<Void, TimelineProxyError>
func sendPollResponse(pollStartID: String, answers: [String]) async -> Result<Void, TimelineProxyError> func sendPollResponse(pollStartID: String, answers: [String]) async -> Result<Void, TimelineProxyError>
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:ba717565284f34e677e6885e82eaba19a07b1009f7a104f4fa394e36fab91f34 oid sha256:7b63bb88da132634b7490bcc65e70bcf7491a4a8fd96da116a4391c8019f2988
size 124711 size 131165
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:3973ef87c63089e471555d0a03396180fae10556566101555a3aea71fcb82137 oid sha256:5b76b64f6a8ba7fbb58b265fb1024ae570d268c8b2686485a4f3009185cdbb62
size 132776 size 141152
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:c1c666e0578df65052bd04aa3cfd5794977e53e2d1bd5aa3b32264c12e5a481a oid sha256:91a5da4b154e3d830aeefe321f149e5326b58c2c8e635947fcb3f43a3c65cc8c
size 72536 size 77889
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:40236a2b9ca075fecf6d15efa6ddc99039d18b292d9dcd914c7399843e186bd4 oid sha256:da6a9884746b4c77fd6b38c83d377a872a9b61daf85d4cf06dde020507ecfb50
size 83218 size 96021
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:ab9b991b75cf2c878f7ac9276115b222ce73fa726173e24f3ddb2c12f19fa17d oid sha256:02f623ab052f5e52597fc351680224550ec4c156296c0861b9aa4ae146097e3c
size 127261 size 133670
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:56e8b0e5bfcbfe83726fc02d5c05534788144d480770a93a9db02339156b9338 oid sha256:4789ada7478537bb62c9fc8342f548d3cd1b8998517f33f7c172884c7a39e731
size 139917 size 148409
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:0f060c42f3c8911ba0774fd8a243b7fd644cd986e79a170d6325b1a2f85191c0 oid sha256:117c79bbaab1643281c91689d50e12f9f05b4bcb91a165330222dc8b8c4fdb91
size 72823 size 79156
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:d5ec9841fda6f2b97516be4bf329af612dab6808a6c4f5328806764e161afcb5 oid sha256:557924d719f75dad520ebbc22f25a2c0530b267b40188a0876cfdb31f881ace4
size 88352 size 99168
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:2d72a6670dc29f4e551f3a44b461abb7a96ac65fdb526f4d3c788ee735013237 oid sha256:eec6eadd12659fc444d02f2a69b1afee006d8e174e4fddda4211b11fdc09e6d6
size 111649 size 111815
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:a4503c7655d8c9d091b9108ed64281ca25a554e2ead4c24309a063eee58c120d oid sha256:b60d1e89d772ef70e667058d3a3c2cb4e02b9daa810674a27e1c60fe3eb2bfcc
size 117698 size 117881
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:64a104c0a76e9064808a12ce5c02843374ce25cd1a99d92139d30ed33d207519 oid sha256:b032f8457f7e1da3466e9066641ca74f3f4bc4739ce8bf933855bdfef4c69d38
size 67214 size 67157
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:9c601624b71617f5716a234685a7fb938163843da50f7db2f5072647cbd3119f oid sha256:212e4d5ce6c3b7120362efc13a5bd8f801f5761e23d78cb3009812514b76e7d6
size 74024 size 74083
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:ae3c8a487fd10ced84bbdc7344e7c747a09069923a918a50526338aeb38ef343 oid sha256:d2ed9dc31a4994088e7d7f868fa7eb7d4cee524f74ab3db9ee8f13489445bb8f
size 101065 size 101070
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:8722708cb7205ed1932599053b0197eed41b1a15461ca22a192dc89af3897a32 oid sha256:445c6ee56bcfb7890881e4443476a1c8883025ebcf753d75541fe08ea00665de
size 105696 size 105702
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:711f0c1d1e191bba195a6a16dbbc9063b10f7a7938c291e21abbe2d3be64199c oid sha256:ca06fd7001e9a4fc47a40e3cb343112d5faeba57df1ae69bccc9b3d77a57d7fd
size 56467 size 56531
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:81a3dc9b25a1c38911e970c4464618d5fda782fdf7ad6cbc8a9e246deaa01b62 oid sha256:b6859e48d238a1ff08f138c13d839b87120e6d9556ea9f929335e8f51286415b
size 61213 size 61293
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:2c63b201b2931e8a222876e0c9e5a3e32b000ec83c8a903103c80be08c7c42f6 oid sha256:4cda062114a6ae3c5b7cdb35505f17eb722d685c5613702f501c5b0f1495ff5e
size 196187 size 196187
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:045214688aba9b8497231bc46d4b70f05cf73006cd7f4ce53dbf7200b3a6a930 oid sha256:c54de901975dcfccdbc9d59b3bb72367cd7a78c6b5634d9b992523da58aca64b
size 227087 size 227152
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:79a3655a47e3c1ed6a6361bb2dc55bb01c9f4b803d3194481e47bdab24cbfccc oid sha256:38789539575c066aef112024b9eb391ea0bfaf7380087bbac2f25028ab364186
size 128547 size 128616
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:9549e8e59da3d131d54da01ae7eb04c4d9c876a3b7745b3d710cf23e3ec32c84 oid sha256:29da80586d1664e8ee15dd9d8e5eaf53e107c0dd3d08f60013a079e1d21def77
size 149037 size 149093
@@ -26,6 +26,7 @@ struct PollFormScreenViewModelTests {
let isEmpty = context.options.allSatisfy(\.text.isEmpty) let isEmpty = context.options.allSatisfy(\.text.isEmpty)
#expect(isEmpty) #expect(isEmpty)
#expect(context.question.isEmpty) #expect(context.question.isEmpty)
#expect(context.maxSelections == 1)
#expect(context.viewState.isSubmitButtonDisabled) #expect(context.viewState.isSubmitButtonDisabled)
#expect(!context.viewState.bindings.isUndisclosed) #expect(!context.viewState.bindings.isUndisclosed)
@@ -44,6 +45,7 @@ struct PollFormScreenViewModelTests {
#expect(context.options.count == 3) #expect(context.options.count == 3)
#expect(context.options.allSatisfy { !$0.text.isEmpty }) #expect(context.options.allSatisfy { !$0.text.isEmpty })
#expect(!context.question.isEmpty) #expect(!context.question.isEmpty)
#expect(context.maxSelections == 1)
#expect(context.viewState.isSubmitButtonDisabled) #expect(context.viewState.isSubmitButtonDisabled)
#expect(!context.viewState.bindings.isUndisclosed) #expect(!context.viewState.bindings.isUndisclosed)
@@ -90,22 +92,58 @@ struct PollFormScreenViewModelTests {
#expect(context.alertInfo != nil) #expect(context.alertInfo != nil)
} }
@Test
mutating func maxSelectionsBounds() async throws {
setupViewModel()
context.send(viewAction: .decrementMaxSelections)
#expect(context.maxSelections == 1)
context.send(viewAction: .incrementMaxSelections)
#expect(context.maxSelections == 2)
context.send(viewAction: .incrementMaxSelections)
#expect(context.maxSelections == 2)
context.send(viewAction: .addOption)
context.send(viewAction: .incrementMaxSelections)
#expect(context.maxSelections == 3)
let deferredMaxSelections = deferFulfillment(context.observe(\.viewState.bindings.maxSelections)) { $0 == 2 }
context.send(viewAction: .deleteOption(index: 2))
try await deferredMaxSelections.fulfill()
#expect(context.maxSelections == 2)
}
@Test
mutating func editPollInitialMaxSelections() {
setupViewModel(mode: .edit(eventID: "foo", poll: .mock(question: "Pick two",
maxSelections: 2,
options: [.mock(text: "One"),
.mock(text: "Two"),
.mock(text: "Three")])))
#expect(context.maxSelections == 2)
}
@Test @Test
mutating func newPollSubmit() async throws { mutating func newPollSubmit() async throws {
setupViewModel() setupViewModel()
context.question = "foo" context.question = "foo"
context.options[0].text = "bla1" context.options[0].text = "bla1"
context.options[1].text = "bla2" context.options[1].text = "bla2"
context.send(viewAction: .incrementMaxSelections)
#expect(!context.viewState.isSubmitButtonDisabled) #expect(!context.viewState.isSubmitButtonDisabled)
let deferred = deferFulfillment(viewModel.actions) { $0 == .close } let deferred = deferFulfillment(viewModel.actions) { $0 == .close }
try await confirmation { confirmation in try await confirmation { confirmation in
timelineProxy.createPollQuestionAnswersPollKindClosure = { question, options, kind in timelineProxy.createPollQuestionAnswersMaxSelectionsPollKindClosure = { question, options, maxSelections, kind in
#expect(question == "foo") #expect(question == "foo")
#expect(options.count == 2) #expect(options.count == 2)
#expect(options[0] == "bla1") #expect(options[0] == "bla1")
#expect(options[1] == "bla2") #expect(options[1] == "bla2")
#expect(maxSelections == 2)
#expect(kind == .disclosed) #expect(kind == .disclosed)
confirmation() confirmation()
return .success(()) return .success(())
@@ -122,12 +160,13 @@ struct PollFormScreenViewModelTests {
context.question = "What is your favorite country?" context.question = "What is your favorite country?"
context.options.append(.init(text: "France 🇫🇷")) context.options.append(.init(text: "France 🇫🇷"))
context.send(viewAction: .incrementMaxSelections)
#expect(!context.viewState.isSubmitButtonDisabled) #expect(!context.viewState.isSubmitButtonDisabled)
let deferred = deferFulfillment(viewModel.actions) { $0 == .close } let deferred = deferFulfillment(viewModel.actions) { $0 == .close }
try await confirmation { confirmation in try await confirmation { confirmation in
timelineProxy.editPollOriginalQuestionAnswersPollKindClosure = { eventID, question, options, kind in timelineProxy.editPollOriginalQuestionAnswersMaxSelectionsPollKindClosure = { eventID, question, options, maxSelections, kind in
#expect(eventID == "foo") #expect(eventID == "foo")
#expect(question == "What is your favorite country?") #expect(question == "What is your favorite country?")
#expect(options.count == 4) #expect(options.count == 4)
@@ -135,6 +174,7 @@ struct PollFormScreenViewModelTests {
#expect(options[1] == "China 🇨🇳") #expect(options[1] == "China 🇨🇳")
#expect(options[2] == "USA 🇺🇸") #expect(options[2] == "USA 🇺🇸")
#expect(options[3] == "France 🇫🇷") #expect(options[3] == "France 🇫🇷")
#expect(maxSelections == 2)
#expect(kind == .disclosed) #expect(kind == .disclosed)
confirmation() confirmation()
return .success(()) return .success(())
+74
View File
@@ -0,0 +1,74 @@
//
// Copyright 2026 Element Creations Ltd.
//
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
// Please see LICENSE files in the repository root for full details.
//
@testable import ElementX
import Testing
struct PollTests {
@Test
func singleSelectionReplacesSelectedAnswer() {
let poll = Poll.mock(question: "Question?",
maxSelections: 1,
options: [
.mock(id: "first", text: "First", isSelected: true),
.mock(id: "second", text: "Second")
])
#expect(poll.answerIDsAfterSelecting(optionID: "second") == ["second"])
#expect(poll.answerIDsAfterSelecting(optionID: "first") == nil)
}
@Test
func multipleSelectionAddsAnswerUntilLimit() {
let poll = Poll.mock(question: "Question?",
maxSelections: 2,
options: [
.mock(id: "first", text: "First", isSelected: true),
.mock(id: "second", text: "Second"),
.mock(id: "third", text: "Third")
])
#expect(poll.answerIDsAfterSelecting(optionID: "second") == ["first", "second"])
}
@Test
func multipleSelectionCanRemoveAnswerWhenMoreThanOneIsSelected() {
let poll = Poll.mock(question: "Question?",
maxSelections: 2,
options: [
.mock(id: "first", text: "First", isSelected: true),
.mock(id: "second", text: "Second", isSelected: true),
.mock(id: "third", text: "Third")
])
#expect(poll.answerIDsAfterSelecting(optionID: "first") == ["second"])
}
@Test
func multipleSelectionDoesNotAddAnswerOverLimit() {
let poll = Poll.mock(question: "Question?",
maxSelections: 2,
options: [
.mock(id: "first", text: "First", isSelected: true),
.mock(id: "second", text: "Second", isSelected: true),
.mock(id: "third", text: "Third")
])
#expect(poll.answerIDsAfterSelecting(optionID: "third") == nil)
}
}
private extension Poll.Option {
static func mock(id: String, text: String, isSelected: Bool = false) -> Self {
.init(id: id,
text: text,
votes: 0,
allVotes: 0,
isSelected: isSelected,
isWinning: false)
}
}
@@ -139,14 +139,14 @@ struct RoomPollsHistoryScreenViewModelTests {
func sendPollResponse() async throws { func sendPollResponse() async throws {
let deferred = deferFulfillment(interactionHandler.publisher.delay(for: 0.1, scheduler: DispatchQueue.main)) { _ in true } let deferred = deferFulfillment(interactionHandler.publisher.delay(for: 0.1, scheduler: DispatchQueue.main)) { _ in true }
interactionHandler.sendPollResponsePollStartIDOptionIDReturnValue = .success(()) interactionHandler.sendPollResponsePollStartIDAnswerIDsReturnValue = .success(())
viewModel.context.send(viewAction: .sendPollResponse(pollStartID: "somePollID", optionID: "someOptionID")) viewModel.context.send(viewAction: .sendPollResponse(pollStartID: "somePollID", answerIDs: ["firstOptionID", "secondOptionID"]))
try await deferred.fulfill() try await deferred.fulfill()
#expect(interactionHandler.sendPollResponsePollStartIDOptionIDCalled) #expect(interactionHandler.sendPollResponsePollStartIDAnswerIDsCalled)
#expect(interactionHandler.sendPollResponsePollStartIDOptionIDReceivedInvocations[0].pollStartID == "somePollID") #expect(interactionHandler.sendPollResponsePollStartIDAnswerIDsReceivedInvocations[0].pollStartID == "somePollID")
#expect(interactionHandler.sendPollResponsePollStartIDOptionIDReceivedInvocations[0].optionID == "someOptionID") #expect(interactionHandler.sendPollResponsePollStartIDAnswerIDsReceivedInvocations[0].answerIDs == ["firstOptionID", "secondOptionID"])
} }
@Test @Test
@@ -155,14 +155,14 @@ struct RoomPollsHistoryScreenViewModelTests {
value.bindings.alertInfo != nil value.bindings.alertInfo != nil
} }
interactionHandler.sendPollResponsePollStartIDOptionIDReturnValue = .failure(SDKError.generic) interactionHandler.sendPollResponsePollStartIDAnswerIDsReturnValue = .failure(SDKError.generic)
viewModel.context.send(viewAction: .sendPollResponse(pollStartID: "somePollID", optionID: "someOptionID")) viewModel.context.send(viewAction: .sendPollResponse(pollStartID: "somePollID", answerIDs: ["someOptionID"]))
try await deferred.fulfill() try await deferred.fulfill()
#expect(interactionHandler.sendPollResponsePollStartIDOptionIDCalled) #expect(interactionHandler.sendPollResponsePollStartIDAnswerIDsCalled)
#expect(interactionHandler.sendPollResponsePollStartIDOptionIDReceivedInvocations[0].pollStartID == "somePollID") #expect(interactionHandler.sendPollResponsePollStartIDAnswerIDsReceivedInvocations[0].pollStartID == "somePollID")
#expect(interactionHandler.sendPollResponsePollStartIDOptionIDReceivedInvocations[0].optionID == "someOptionID") #expect(interactionHandler.sendPollResponsePollStartIDAnswerIDsReceivedInvocations[0].answerIDs == ["someOptionID"])
} }
@Test @Test