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 */; };
832A4EA1094B8FE423A08700 /* RoomChangeRolesScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6B2A421198FD20AAAED20004 /* RoomChangeRolesScreen.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 */; };
83D519C509F0F76EDBB60455 /* KnockRequestProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F062DD2CCD95DC33528A16F /* KnockRequestProxy.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>"; };
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>"; };
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>"; };
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>"; };
@@ -5006,6 +5008,7 @@
31A6314FDC51DA25712D9A81 /* PillContextTests.swift */,
6FA38E813BE14149F173F461 /* PinnedEventsBannerStateTests.swift */,
347D708104CCEF771428C9A3 /* PollFormScreenViewModelTests.swift */,
FBD507F529D440215892618A /* PollTests.swift */,
25E7E9B7FEAB6169D960C206 /* QRCodeLoginScreenViewModelTests.swift */,
181CF280BC8E3F335AFCB4B8 /* RemotePreferenceTests.swift */,
30EA681527A8FE65E4C8E9A9 /* ReportContentScreenViewModelTests.swift */,
@@ -7984,6 +7987,7 @@
3982E60F9C126437D5E488A3 /* PillContextTests.swift in Sources */,
2D2D8A53B35BE8D8A01449C6 /* PinnedEventsBannerStateTests.swift in Sources */,
FF7E8ECC8E7E1D1851517536 /* PollFormScreenViewModelTests.swift in Sources */,
83A01FE4DACABDE97F04D338 /* PollTests.swift in Sources */,
E3EBC3BF7CE3960B41757BAA /* Publisher.swift in Sources */,
BDC4EB54CC3036730475CB8B /* QRCodeLoginScreenViewModelTests.swift in Sources */,
522269133E6F65F68482F4F4 /* RemotePreferenceTests.swift in Sources */,
@@ -2,6 +2,9 @@
"screen_search_empty_state_message" = "Search for rooms";
"screen_search_empty_state_title" = "Start searching...";
"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_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.";
@@ -10,6 +10,12 @@ import Foundation
// swiftlint:disable explicit_type_interface function_parameter_count identifier_name line_length
// swiftlint:disable nesting type_body_length type_name vertical_whitespace_opening_braces
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
internal static var screenHomeTabSearch: String { return UntranslatedL10n.tr("Untranslated", "screen_home_tab_search") }
/// Search for rooms
@@ -9561,44 +9561,44 @@ nonisolated class PollInteractionHandlerMock: PollInteractionHandlerProtocol, @u
//MARK: - sendPollResponse
private let sendPollResponsePollStartIDOptionIDCallsCountLock = NSLock()
private nonisolated(unsafe) var sendPollResponsePollStartIDOptionIDUnderlyingCallsCount = 0
var sendPollResponsePollStartIDOptionIDCallsCount: Int {
get { sendPollResponsePollStartIDOptionIDCallsCountLock.withLock { sendPollResponsePollStartIDOptionIDUnderlyingCallsCount } }
set { sendPollResponsePollStartIDOptionIDCallsCountLock.withLock { sendPollResponsePollStartIDOptionIDUnderlyingCallsCount = newValue } }
private let sendPollResponsePollStartIDAnswerIDsCallsCountLock = NSLock()
private nonisolated(unsafe) var sendPollResponsePollStartIDAnswerIDsUnderlyingCallsCount = 0
var sendPollResponsePollStartIDAnswerIDsCallsCount: Int {
get { sendPollResponsePollStartIDAnswerIDsCallsCountLock.withLock { sendPollResponsePollStartIDAnswerIDsUnderlyingCallsCount } }
set { sendPollResponsePollStartIDAnswerIDsCallsCountLock.withLock { sendPollResponsePollStartIDAnswerIDsUnderlyingCallsCount = newValue } }
}
var sendPollResponsePollStartIDOptionIDCalled: Bool {
return sendPollResponsePollStartIDOptionIDCallsCount > 0
var sendPollResponsePollStartIDAnswerIDsCalled: Bool {
return sendPollResponsePollStartIDAnswerIDsCallsCount > 0
}
private let sendPollResponsePollStartIDOptionIDReceivedArgumentsLock = NSLock()
private nonisolated(unsafe) var sendPollResponsePollStartIDOptionIDUnderlyingReceivedArguments: (pollStartID: String, optionID: String)?
var sendPollResponsePollStartIDOptionIDReceivedArguments: (pollStartID: String, optionID: String)? {
get { sendPollResponsePollStartIDOptionIDReceivedArgumentsLock.withLock { sendPollResponsePollStartIDOptionIDUnderlyingReceivedArguments } }
set { sendPollResponsePollStartIDOptionIDReceivedArgumentsLock.withLock { sendPollResponsePollStartIDOptionIDUnderlyingReceivedArguments = newValue } }
private let sendPollResponsePollStartIDAnswerIDsReceivedArgumentsLock = NSLock()
private nonisolated(unsafe) var sendPollResponsePollStartIDAnswerIDsUnderlyingReceivedArguments: (pollStartID: String, answerIDs: [String])?
var sendPollResponsePollStartIDAnswerIDsReceivedArguments: (pollStartID: String, answerIDs: [String])? {
get { sendPollResponsePollStartIDAnswerIDsReceivedArgumentsLock.withLock { sendPollResponsePollStartIDAnswerIDsUnderlyingReceivedArguments } }
set { sendPollResponsePollStartIDAnswerIDsReceivedArgumentsLock.withLock { sendPollResponsePollStartIDAnswerIDsUnderlyingReceivedArguments = newValue } }
}
private let sendPollResponsePollStartIDOptionIDReceivedInvocationsLock = NSLock()
private nonisolated(unsafe) var sendPollResponsePollStartIDOptionIDUnderlyingReceivedInvocations: [(pollStartID: String, optionID: String)] = []
var sendPollResponsePollStartIDOptionIDReceivedInvocations: [(pollStartID: String, optionID: String)] {
get { sendPollResponsePollStartIDOptionIDReceivedInvocationsLock.withLock { sendPollResponsePollStartIDOptionIDUnderlyingReceivedInvocations } }
set { sendPollResponsePollStartIDOptionIDReceivedInvocationsLock.withLock { sendPollResponsePollStartIDOptionIDUnderlyingReceivedInvocations = newValue } }
private let sendPollResponsePollStartIDAnswerIDsReceivedInvocationsLock = NSLock()
private nonisolated(unsafe) var sendPollResponsePollStartIDAnswerIDsUnderlyingReceivedInvocations: [(pollStartID: String, answerIDs: [String])] = []
var sendPollResponsePollStartIDAnswerIDsReceivedInvocations: [(pollStartID: String, answerIDs: [String])] {
get { sendPollResponsePollStartIDAnswerIDsReceivedInvocationsLock.withLock { sendPollResponsePollStartIDAnswerIDsUnderlyingReceivedInvocations } }
set { sendPollResponsePollStartIDAnswerIDsReceivedInvocationsLock.withLock { sendPollResponsePollStartIDAnswerIDsUnderlyingReceivedInvocations = newValue } }
}
private let sendPollResponsePollStartIDOptionIDReturnValueLock = NSLock()
private nonisolated(unsafe) var sendPollResponsePollStartIDOptionIDUnderlyingReturnValue: Result<Void, Error>!
var sendPollResponsePollStartIDOptionIDReturnValue: Result<Void, Error>! {
get { sendPollResponsePollStartIDOptionIDReturnValueLock.withLock { sendPollResponsePollStartIDOptionIDUnderlyingReturnValue } }
set { sendPollResponsePollStartIDOptionIDReturnValueLock.withLock { sendPollResponsePollStartIDOptionIDUnderlyingReturnValue = newValue } }
private let sendPollResponsePollStartIDAnswerIDsReturnValueLock = NSLock()
private nonisolated(unsafe) var sendPollResponsePollStartIDAnswerIDsUnderlyingReturnValue: Result<Void, Error>!
var sendPollResponsePollStartIDAnswerIDsReturnValue: Result<Void, Error>! {
get { sendPollResponsePollStartIDAnswerIDsReturnValueLock.withLock { sendPollResponsePollStartIDAnswerIDsUnderlyingReturnValue } }
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> {
sendPollResponsePollStartIDOptionIDCallsCountLock.withLock { sendPollResponsePollStartIDOptionIDUnderlyingCallsCount += 1 }
sendPollResponsePollStartIDOptionIDReceivedArguments = (pollStartID: pollStartID, optionID: optionID)
sendPollResponsePollStartIDOptionIDReceivedInvocationsLock.withLock { sendPollResponsePollStartIDOptionIDUnderlyingReceivedInvocations.append((pollStartID: pollStartID, optionID: optionID)) }
if let sendPollResponsePollStartIDOptionIDClosure = sendPollResponsePollStartIDOptionIDClosure {
return await sendPollResponsePollStartIDOptionIDClosure(pollStartID, optionID)
@concurrent func sendPollResponse(pollStartID: String, answerIDs: [String]) async -> Result<Void, Error> {
sendPollResponsePollStartIDAnswerIDsCallsCountLock.withLock { sendPollResponsePollStartIDAnswerIDsUnderlyingCallsCount += 1 }
sendPollResponsePollStartIDAnswerIDsReceivedArguments = (pollStartID: pollStartID, answerIDs: answerIDs)
sendPollResponsePollStartIDAnswerIDsReceivedInvocationsLock.withLock { sendPollResponsePollStartIDAnswerIDsUnderlyingReceivedInvocations.append((pollStartID: pollStartID, answerIDs: answerIDs)) }
if let sendPollResponsePollStartIDAnswerIDsClosure = sendPollResponsePollStartIDAnswerIDsClosure {
return await sendPollResponsePollStartIDAnswerIDsClosure(pollStartID, answerIDs)
} else {
return sendPollResponsePollStartIDOptionIDReturnValue
return sendPollResponsePollStartIDAnswerIDsReturnValue
}
}
//MARK: - endPoll
@@ -12772,86 +12772,86 @@ nonisolated class TimelineControllerMock: TimelineControllerProtocol, @unchecked
}
//MARK: - createPoll
private let createPollQuestionAnswersPollKindCallsCountLock = NSLock()
private nonisolated(unsafe) var createPollQuestionAnswersPollKindUnderlyingCallsCount = 0
var createPollQuestionAnswersPollKindCallsCount: Int {
get { createPollQuestionAnswersPollKindCallsCountLock.withLock { createPollQuestionAnswersPollKindUnderlyingCallsCount } }
set { createPollQuestionAnswersPollKindCallsCountLock.withLock { createPollQuestionAnswersPollKindUnderlyingCallsCount = newValue } }
private let createPollQuestionAnswersMaxSelectionsPollKindCallsCountLock = NSLock()
private nonisolated(unsafe) var createPollQuestionAnswersMaxSelectionsPollKindUnderlyingCallsCount = 0
var createPollQuestionAnswersMaxSelectionsPollKindCallsCount: Int {
get { createPollQuestionAnswersMaxSelectionsPollKindCallsCountLock.withLock { createPollQuestionAnswersMaxSelectionsPollKindUnderlyingCallsCount } }
set { createPollQuestionAnswersMaxSelectionsPollKindCallsCountLock.withLock { createPollQuestionAnswersMaxSelectionsPollKindUnderlyingCallsCount = newValue } }
}
var createPollQuestionAnswersPollKindCalled: Bool {
return createPollQuestionAnswersPollKindCallsCount > 0
var createPollQuestionAnswersMaxSelectionsPollKindCalled: Bool {
return createPollQuestionAnswersMaxSelectionsPollKindCallsCount > 0
}
private let createPollQuestionAnswersPollKindReceivedArgumentsLock = NSLock()
private nonisolated(unsafe) var createPollQuestionAnswersPollKindUnderlyingReceivedArguments: (question: String, answers: [String], pollKind: Poll.Kind)?
var createPollQuestionAnswersPollKindReceivedArguments: (question: String, answers: [String], pollKind: Poll.Kind)? {
get { createPollQuestionAnswersPollKindReceivedArgumentsLock.withLock { createPollQuestionAnswersPollKindUnderlyingReceivedArguments } }
set { createPollQuestionAnswersPollKindReceivedArgumentsLock.withLock { createPollQuestionAnswersPollKindUnderlyingReceivedArguments = newValue } }
private let createPollQuestionAnswersMaxSelectionsPollKindReceivedArgumentsLock = NSLock()
private nonisolated(unsafe) var createPollQuestionAnswersMaxSelectionsPollKindUnderlyingReceivedArguments: (question: String, answers: [String], maxSelections: Int, pollKind: Poll.Kind)?
var createPollQuestionAnswersMaxSelectionsPollKindReceivedArguments: (question: String, answers: [String], maxSelections: Int, pollKind: Poll.Kind)? {
get { createPollQuestionAnswersMaxSelectionsPollKindReceivedArgumentsLock.withLock { createPollQuestionAnswersMaxSelectionsPollKindUnderlyingReceivedArguments } }
set { createPollQuestionAnswersMaxSelectionsPollKindReceivedArgumentsLock.withLock { createPollQuestionAnswersMaxSelectionsPollKindUnderlyingReceivedArguments = newValue } }
}
private let createPollQuestionAnswersPollKindReceivedInvocationsLock = NSLock()
private nonisolated(unsafe) var createPollQuestionAnswersPollKindUnderlyingReceivedInvocations: [(question: String, answers: [String], pollKind: Poll.Kind)] = []
var createPollQuestionAnswersPollKindReceivedInvocations: [(question: String, answers: [String], pollKind: Poll.Kind)] {
get { createPollQuestionAnswersPollKindReceivedInvocationsLock.withLock { createPollQuestionAnswersPollKindUnderlyingReceivedInvocations } }
set { createPollQuestionAnswersPollKindReceivedInvocationsLock.withLock { createPollQuestionAnswersPollKindUnderlyingReceivedInvocations = newValue } }
private let createPollQuestionAnswersMaxSelectionsPollKindReceivedInvocationsLock = NSLock()
private nonisolated(unsafe) var createPollQuestionAnswersMaxSelectionsPollKindUnderlyingReceivedInvocations: [(question: String, answers: [String], maxSelections: Int, pollKind: Poll.Kind)] = []
var createPollQuestionAnswersMaxSelectionsPollKindReceivedInvocations: [(question: String, answers: [String], maxSelections: Int, pollKind: Poll.Kind)] {
get { createPollQuestionAnswersMaxSelectionsPollKindReceivedInvocationsLock.withLock { createPollQuestionAnswersMaxSelectionsPollKindUnderlyingReceivedInvocations } }
set { createPollQuestionAnswersMaxSelectionsPollKindReceivedInvocationsLock.withLock { createPollQuestionAnswersMaxSelectionsPollKindUnderlyingReceivedInvocations = newValue } }
}
private let createPollQuestionAnswersPollKindReturnValueLock = NSLock()
private nonisolated(unsafe) var createPollQuestionAnswersPollKindUnderlyingReturnValue: Result<Void, TimelineControllerError>!
var createPollQuestionAnswersPollKindReturnValue: Result<Void, TimelineControllerError>! {
get { createPollQuestionAnswersPollKindReturnValueLock.withLock { createPollQuestionAnswersPollKindUnderlyingReturnValue } }
set { createPollQuestionAnswersPollKindReturnValueLock.withLock { createPollQuestionAnswersPollKindUnderlyingReturnValue = newValue } }
private let createPollQuestionAnswersMaxSelectionsPollKindReturnValueLock = NSLock()
private nonisolated(unsafe) var createPollQuestionAnswersMaxSelectionsPollKindUnderlyingReturnValue: Result<Void, TimelineControllerError>!
var createPollQuestionAnswersMaxSelectionsPollKindReturnValue: Result<Void, TimelineControllerError>! {
get { createPollQuestionAnswersMaxSelectionsPollKindReturnValueLock.withLock { createPollQuestionAnswersMaxSelectionsPollKindUnderlyingReturnValue } }
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> {
createPollQuestionAnswersPollKindCallsCountLock.withLock { createPollQuestionAnswersPollKindUnderlyingCallsCount += 1 }
createPollQuestionAnswersPollKindReceivedArguments = (question: question, answers: answers, pollKind: pollKind)
createPollQuestionAnswersPollKindReceivedInvocationsLock.withLock { createPollQuestionAnswersPollKindUnderlyingReceivedInvocations.append((question: question, answers: answers, pollKind: pollKind)) }
if let createPollQuestionAnswersPollKindClosure = createPollQuestionAnswersPollKindClosure {
return await createPollQuestionAnswersPollKindClosure(question, answers, pollKind)
@concurrent func createPoll(question: String, answers: [String], maxSelections: Int, pollKind: Poll.Kind) async -> Result<Void, TimelineControllerError> {
createPollQuestionAnswersMaxSelectionsPollKindCallsCountLock.withLock { createPollQuestionAnswersMaxSelectionsPollKindUnderlyingCallsCount += 1 }
createPollQuestionAnswersMaxSelectionsPollKindReceivedArguments = (question: question, answers: answers, maxSelections: maxSelections, pollKind: pollKind)
createPollQuestionAnswersMaxSelectionsPollKindReceivedInvocationsLock.withLock { createPollQuestionAnswersMaxSelectionsPollKindUnderlyingReceivedInvocations.append((question: question, answers: answers, maxSelections: maxSelections, pollKind: pollKind)) }
if let createPollQuestionAnswersMaxSelectionsPollKindClosure = createPollQuestionAnswersMaxSelectionsPollKindClosure {
return await createPollQuestionAnswersMaxSelectionsPollKindClosure(question, answers, maxSelections, pollKind)
} else {
return createPollQuestionAnswersPollKindReturnValue
return createPollQuestionAnswersMaxSelectionsPollKindReturnValue
}
}
//MARK: - editPoll
private let editPollOriginalQuestionAnswersPollKindCallsCountLock = NSLock()
private nonisolated(unsafe) var editPollOriginalQuestionAnswersPollKindUnderlyingCallsCount = 0
var editPollOriginalQuestionAnswersPollKindCallsCount: Int {
get { editPollOriginalQuestionAnswersPollKindCallsCountLock.withLock { editPollOriginalQuestionAnswersPollKindUnderlyingCallsCount } }
set { editPollOriginalQuestionAnswersPollKindCallsCountLock.withLock { editPollOriginalQuestionAnswersPollKindUnderlyingCallsCount = newValue } }
private let editPollOriginalQuestionAnswersMaxSelectionsPollKindCallsCountLock = NSLock()
private nonisolated(unsafe) var editPollOriginalQuestionAnswersMaxSelectionsPollKindUnderlyingCallsCount = 0
var editPollOriginalQuestionAnswersMaxSelectionsPollKindCallsCount: Int {
get { editPollOriginalQuestionAnswersMaxSelectionsPollKindCallsCountLock.withLock { editPollOriginalQuestionAnswersMaxSelectionsPollKindUnderlyingCallsCount } }
set { editPollOriginalQuestionAnswersMaxSelectionsPollKindCallsCountLock.withLock { editPollOriginalQuestionAnswersMaxSelectionsPollKindUnderlyingCallsCount = newValue } }
}
var editPollOriginalQuestionAnswersPollKindCalled: Bool {
return editPollOriginalQuestionAnswersPollKindCallsCount > 0
var editPollOriginalQuestionAnswersMaxSelectionsPollKindCalled: Bool {
return editPollOriginalQuestionAnswersMaxSelectionsPollKindCallsCount > 0
}
private let editPollOriginalQuestionAnswersPollKindReceivedArgumentsLock = NSLock()
private nonisolated(unsafe) var editPollOriginalQuestionAnswersPollKindUnderlyingReceivedArguments: (eventID: String, question: String, answers: [String], pollKind: Poll.Kind)?
var editPollOriginalQuestionAnswersPollKindReceivedArguments: (eventID: String, question: String, answers: [String], pollKind: Poll.Kind)? {
get { editPollOriginalQuestionAnswersPollKindReceivedArgumentsLock.withLock { editPollOriginalQuestionAnswersPollKindUnderlyingReceivedArguments } }
set { editPollOriginalQuestionAnswersPollKindReceivedArgumentsLock.withLock { editPollOriginalQuestionAnswersPollKindUnderlyingReceivedArguments = newValue } }
private let editPollOriginalQuestionAnswersMaxSelectionsPollKindReceivedArgumentsLock = NSLock()
private nonisolated(unsafe) var editPollOriginalQuestionAnswersMaxSelectionsPollKindUnderlyingReceivedArguments: (eventID: String, question: String, answers: [String], maxSelections: Int, pollKind: Poll.Kind)?
var editPollOriginalQuestionAnswersMaxSelectionsPollKindReceivedArguments: (eventID: String, question: String, answers: [String], maxSelections: Int, pollKind: Poll.Kind)? {
get { editPollOriginalQuestionAnswersMaxSelectionsPollKindReceivedArgumentsLock.withLock { editPollOriginalQuestionAnswersMaxSelectionsPollKindUnderlyingReceivedArguments } }
set { editPollOriginalQuestionAnswersMaxSelectionsPollKindReceivedArgumentsLock.withLock { editPollOriginalQuestionAnswersMaxSelectionsPollKindUnderlyingReceivedArguments = newValue } }
}
private let editPollOriginalQuestionAnswersPollKindReceivedInvocationsLock = NSLock()
private nonisolated(unsafe) var editPollOriginalQuestionAnswersPollKindUnderlyingReceivedInvocations: [(eventID: String, question: String, answers: [String], pollKind: Poll.Kind)] = []
var editPollOriginalQuestionAnswersPollKindReceivedInvocations: [(eventID: String, question: String, answers: [String], pollKind: Poll.Kind)] {
get { editPollOriginalQuestionAnswersPollKindReceivedInvocationsLock.withLock { editPollOriginalQuestionAnswersPollKindUnderlyingReceivedInvocations } }
set { editPollOriginalQuestionAnswersPollKindReceivedInvocationsLock.withLock { editPollOriginalQuestionAnswersPollKindUnderlyingReceivedInvocations = newValue } }
private let editPollOriginalQuestionAnswersMaxSelectionsPollKindReceivedInvocationsLock = NSLock()
private nonisolated(unsafe) var editPollOriginalQuestionAnswersMaxSelectionsPollKindUnderlyingReceivedInvocations: [(eventID: String, question: String, answers: [String], maxSelections: Int, pollKind: Poll.Kind)] = []
var editPollOriginalQuestionAnswersMaxSelectionsPollKindReceivedInvocations: [(eventID: String, question: String, answers: [String], maxSelections: Int, pollKind: Poll.Kind)] {
get { editPollOriginalQuestionAnswersMaxSelectionsPollKindReceivedInvocationsLock.withLock { editPollOriginalQuestionAnswersMaxSelectionsPollKindUnderlyingReceivedInvocations } }
set { editPollOriginalQuestionAnswersMaxSelectionsPollKindReceivedInvocationsLock.withLock { editPollOriginalQuestionAnswersMaxSelectionsPollKindUnderlyingReceivedInvocations = newValue } }
}
private let editPollOriginalQuestionAnswersPollKindReturnValueLock = NSLock()
private nonisolated(unsafe) var editPollOriginalQuestionAnswersPollKindUnderlyingReturnValue: Result<Void, TimelineControllerError>!
var editPollOriginalQuestionAnswersPollKindReturnValue: Result<Void, TimelineControllerError>! {
get { editPollOriginalQuestionAnswersPollKindReturnValueLock.withLock { editPollOriginalQuestionAnswersPollKindUnderlyingReturnValue } }
set { editPollOriginalQuestionAnswersPollKindReturnValueLock.withLock { editPollOriginalQuestionAnswersPollKindUnderlyingReturnValue = newValue } }
private let editPollOriginalQuestionAnswersMaxSelectionsPollKindReturnValueLock = NSLock()
private nonisolated(unsafe) var editPollOriginalQuestionAnswersMaxSelectionsPollKindUnderlyingReturnValue: Result<Void, TimelineControllerError>!
var editPollOriginalQuestionAnswersMaxSelectionsPollKindReturnValue: Result<Void, TimelineControllerError>! {
get { editPollOriginalQuestionAnswersMaxSelectionsPollKindReturnValueLock.withLock { editPollOriginalQuestionAnswersMaxSelectionsPollKindUnderlyingReturnValue } }
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> {
editPollOriginalQuestionAnswersPollKindCallsCountLock.withLock { editPollOriginalQuestionAnswersPollKindUnderlyingCallsCount += 1 }
editPollOriginalQuestionAnswersPollKindReceivedArguments = (eventID: eventID, question: question, answers: answers, pollKind: pollKind)
editPollOriginalQuestionAnswersPollKindReceivedInvocationsLock.withLock { editPollOriginalQuestionAnswersPollKindUnderlyingReceivedInvocations.append((eventID: eventID, question: question, answers: answers, pollKind: pollKind)) }
if let editPollOriginalQuestionAnswersPollKindClosure = editPollOriginalQuestionAnswersPollKindClosure {
return await editPollOriginalQuestionAnswersPollKindClosure(eventID, question, answers, pollKind)
@concurrent func editPoll(original eventID: String, question: String, answers: [String], maxSelections: Int, pollKind: Poll.Kind) async -> Result<Void, TimelineControllerError> {
editPollOriginalQuestionAnswersMaxSelectionsPollKindCallsCountLock.withLock { editPollOriginalQuestionAnswersMaxSelectionsPollKindUnderlyingCallsCount += 1 }
editPollOriginalQuestionAnswersMaxSelectionsPollKindReceivedArguments = (eventID: eventID, question: question, answers: answers, maxSelections: maxSelections, pollKind: pollKind)
editPollOriginalQuestionAnswersMaxSelectionsPollKindReceivedInvocationsLock.withLock { editPollOriginalQuestionAnswersMaxSelectionsPollKindUnderlyingReceivedInvocations.append((eventID: eventID, question: question, answers: answers, maxSelections: maxSelections, pollKind: pollKind)) }
if let editPollOriginalQuestionAnswersMaxSelectionsPollKindClosure = editPollOriginalQuestionAnswersMaxSelectionsPollKindClosure {
return await editPollOriginalQuestionAnswersMaxSelectionsPollKindClosure(eventID, question, answers, maxSelections, pollKind)
} else {
return editPollOriginalQuestionAnswersPollKindReturnValue
return editPollOriginalQuestionAnswersMaxSelectionsPollKindReturnValue
}
}
//MARK: - sendPollResponse
@@ -13737,86 +13737,86 @@ nonisolated class TimelineProxyMock: TimelineProxyProtocol, @unchecked Sendable
}
//MARK: - createPoll
private let createPollQuestionAnswersPollKindCallsCountLock = NSLock()
private nonisolated(unsafe) var createPollQuestionAnswersPollKindUnderlyingCallsCount = 0
var createPollQuestionAnswersPollKindCallsCount: Int {
get { createPollQuestionAnswersPollKindCallsCountLock.withLock { createPollQuestionAnswersPollKindUnderlyingCallsCount } }
set { createPollQuestionAnswersPollKindCallsCountLock.withLock { createPollQuestionAnswersPollKindUnderlyingCallsCount = newValue } }
private let createPollQuestionAnswersMaxSelectionsPollKindCallsCountLock = NSLock()
private nonisolated(unsafe) var createPollQuestionAnswersMaxSelectionsPollKindUnderlyingCallsCount = 0
var createPollQuestionAnswersMaxSelectionsPollKindCallsCount: Int {
get { createPollQuestionAnswersMaxSelectionsPollKindCallsCountLock.withLock { createPollQuestionAnswersMaxSelectionsPollKindUnderlyingCallsCount } }
set { createPollQuestionAnswersMaxSelectionsPollKindCallsCountLock.withLock { createPollQuestionAnswersMaxSelectionsPollKindUnderlyingCallsCount = newValue } }
}
var createPollQuestionAnswersPollKindCalled: Bool {
return createPollQuestionAnswersPollKindCallsCount > 0
var createPollQuestionAnswersMaxSelectionsPollKindCalled: Bool {
return createPollQuestionAnswersMaxSelectionsPollKindCallsCount > 0
}
private let createPollQuestionAnswersPollKindReceivedArgumentsLock = NSLock()
private nonisolated(unsafe) var createPollQuestionAnswersPollKindUnderlyingReceivedArguments: (question: String, answers: [String], pollKind: Poll.Kind)?
var createPollQuestionAnswersPollKindReceivedArguments: (question: String, answers: [String], pollKind: Poll.Kind)? {
get { createPollQuestionAnswersPollKindReceivedArgumentsLock.withLock { createPollQuestionAnswersPollKindUnderlyingReceivedArguments } }
set { createPollQuestionAnswersPollKindReceivedArgumentsLock.withLock { createPollQuestionAnswersPollKindUnderlyingReceivedArguments = newValue } }
private let createPollQuestionAnswersMaxSelectionsPollKindReceivedArgumentsLock = NSLock()
private nonisolated(unsafe) var createPollQuestionAnswersMaxSelectionsPollKindUnderlyingReceivedArguments: (question: String, answers: [String], maxSelections: Int, pollKind: Poll.Kind)?
var createPollQuestionAnswersMaxSelectionsPollKindReceivedArguments: (question: String, answers: [String], maxSelections: Int, pollKind: Poll.Kind)? {
get { createPollQuestionAnswersMaxSelectionsPollKindReceivedArgumentsLock.withLock { createPollQuestionAnswersMaxSelectionsPollKindUnderlyingReceivedArguments } }
set { createPollQuestionAnswersMaxSelectionsPollKindReceivedArgumentsLock.withLock { createPollQuestionAnswersMaxSelectionsPollKindUnderlyingReceivedArguments = newValue } }
}
private let createPollQuestionAnswersPollKindReceivedInvocationsLock = NSLock()
private nonisolated(unsafe) var createPollQuestionAnswersPollKindUnderlyingReceivedInvocations: [(question: String, answers: [String], pollKind: Poll.Kind)] = []
var createPollQuestionAnswersPollKindReceivedInvocations: [(question: String, answers: [String], pollKind: Poll.Kind)] {
get { createPollQuestionAnswersPollKindReceivedInvocationsLock.withLock { createPollQuestionAnswersPollKindUnderlyingReceivedInvocations } }
set { createPollQuestionAnswersPollKindReceivedInvocationsLock.withLock { createPollQuestionAnswersPollKindUnderlyingReceivedInvocations = newValue } }
private let createPollQuestionAnswersMaxSelectionsPollKindReceivedInvocationsLock = NSLock()
private nonisolated(unsafe) var createPollQuestionAnswersMaxSelectionsPollKindUnderlyingReceivedInvocations: [(question: String, answers: [String], maxSelections: Int, pollKind: Poll.Kind)] = []
var createPollQuestionAnswersMaxSelectionsPollKindReceivedInvocations: [(question: String, answers: [String], maxSelections: Int, pollKind: Poll.Kind)] {
get { createPollQuestionAnswersMaxSelectionsPollKindReceivedInvocationsLock.withLock { createPollQuestionAnswersMaxSelectionsPollKindUnderlyingReceivedInvocations } }
set { createPollQuestionAnswersMaxSelectionsPollKindReceivedInvocationsLock.withLock { createPollQuestionAnswersMaxSelectionsPollKindUnderlyingReceivedInvocations = newValue } }
}
private let createPollQuestionAnswersPollKindReturnValueLock = NSLock()
private nonisolated(unsafe) var createPollQuestionAnswersPollKindUnderlyingReturnValue: Result<Void, TimelineProxyError>!
var createPollQuestionAnswersPollKindReturnValue: Result<Void, TimelineProxyError>! {
get { createPollQuestionAnswersPollKindReturnValueLock.withLock { createPollQuestionAnswersPollKindUnderlyingReturnValue } }
set { createPollQuestionAnswersPollKindReturnValueLock.withLock { createPollQuestionAnswersPollKindUnderlyingReturnValue = newValue } }
private let createPollQuestionAnswersMaxSelectionsPollKindReturnValueLock = NSLock()
private nonisolated(unsafe) var createPollQuestionAnswersMaxSelectionsPollKindUnderlyingReturnValue: Result<Void, TimelineProxyError>!
var createPollQuestionAnswersMaxSelectionsPollKindReturnValue: Result<Void, TimelineProxyError>! {
get { createPollQuestionAnswersMaxSelectionsPollKindReturnValueLock.withLock { createPollQuestionAnswersMaxSelectionsPollKindUnderlyingReturnValue } }
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> {
createPollQuestionAnswersPollKindCallsCountLock.withLock { createPollQuestionAnswersPollKindUnderlyingCallsCount += 1 }
createPollQuestionAnswersPollKindReceivedArguments = (question: question, answers: answers, pollKind: pollKind)
createPollQuestionAnswersPollKindReceivedInvocationsLock.withLock { createPollQuestionAnswersPollKindUnderlyingReceivedInvocations.append((question: question, answers: answers, pollKind: pollKind)) }
if let createPollQuestionAnswersPollKindClosure = createPollQuestionAnswersPollKindClosure {
return await createPollQuestionAnswersPollKindClosure(question, answers, pollKind)
@concurrent func createPoll(question: String, answers: [String], maxSelections: Int, pollKind: Poll.Kind) async -> Result<Void, TimelineProxyError> {
createPollQuestionAnswersMaxSelectionsPollKindCallsCountLock.withLock { createPollQuestionAnswersMaxSelectionsPollKindUnderlyingCallsCount += 1 }
createPollQuestionAnswersMaxSelectionsPollKindReceivedArguments = (question: question, answers: answers, maxSelections: maxSelections, pollKind: pollKind)
createPollQuestionAnswersMaxSelectionsPollKindReceivedInvocationsLock.withLock { createPollQuestionAnswersMaxSelectionsPollKindUnderlyingReceivedInvocations.append((question: question, answers: answers, maxSelections: maxSelections, pollKind: pollKind)) }
if let createPollQuestionAnswersMaxSelectionsPollKindClosure = createPollQuestionAnswersMaxSelectionsPollKindClosure {
return await createPollQuestionAnswersMaxSelectionsPollKindClosure(question, answers, maxSelections, pollKind)
} else {
return createPollQuestionAnswersPollKindReturnValue
return createPollQuestionAnswersMaxSelectionsPollKindReturnValue
}
}
//MARK: - editPoll
private let editPollOriginalQuestionAnswersPollKindCallsCountLock = NSLock()
private nonisolated(unsafe) var editPollOriginalQuestionAnswersPollKindUnderlyingCallsCount = 0
var editPollOriginalQuestionAnswersPollKindCallsCount: Int {
get { editPollOriginalQuestionAnswersPollKindCallsCountLock.withLock { editPollOriginalQuestionAnswersPollKindUnderlyingCallsCount } }
set { editPollOriginalQuestionAnswersPollKindCallsCountLock.withLock { editPollOriginalQuestionAnswersPollKindUnderlyingCallsCount = newValue } }
private let editPollOriginalQuestionAnswersMaxSelectionsPollKindCallsCountLock = NSLock()
private nonisolated(unsafe) var editPollOriginalQuestionAnswersMaxSelectionsPollKindUnderlyingCallsCount = 0
var editPollOriginalQuestionAnswersMaxSelectionsPollKindCallsCount: Int {
get { editPollOriginalQuestionAnswersMaxSelectionsPollKindCallsCountLock.withLock { editPollOriginalQuestionAnswersMaxSelectionsPollKindUnderlyingCallsCount } }
set { editPollOriginalQuestionAnswersMaxSelectionsPollKindCallsCountLock.withLock { editPollOriginalQuestionAnswersMaxSelectionsPollKindUnderlyingCallsCount = newValue } }
}
var editPollOriginalQuestionAnswersPollKindCalled: Bool {
return editPollOriginalQuestionAnswersPollKindCallsCount > 0
var editPollOriginalQuestionAnswersMaxSelectionsPollKindCalled: Bool {
return editPollOriginalQuestionAnswersMaxSelectionsPollKindCallsCount > 0
}
private let editPollOriginalQuestionAnswersPollKindReceivedArgumentsLock = NSLock()
private nonisolated(unsafe) var editPollOriginalQuestionAnswersPollKindUnderlyingReceivedArguments: (eventID: String, question: String, answers: [String], pollKind: Poll.Kind)?
var editPollOriginalQuestionAnswersPollKindReceivedArguments: (eventID: String, question: String, answers: [String], pollKind: Poll.Kind)? {
get { editPollOriginalQuestionAnswersPollKindReceivedArgumentsLock.withLock { editPollOriginalQuestionAnswersPollKindUnderlyingReceivedArguments } }
set { editPollOriginalQuestionAnswersPollKindReceivedArgumentsLock.withLock { editPollOriginalQuestionAnswersPollKindUnderlyingReceivedArguments = newValue } }
private let editPollOriginalQuestionAnswersMaxSelectionsPollKindReceivedArgumentsLock = NSLock()
private nonisolated(unsafe) var editPollOriginalQuestionAnswersMaxSelectionsPollKindUnderlyingReceivedArguments: (eventID: String, question: String, answers: [String], maxSelections: Int, pollKind: Poll.Kind)?
var editPollOriginalQuestionAnswersMaxSelectionsPollKindReceivedArguments: (eventID: String, question: String, answers: [String], maxSelections: Int, pollKind: Poll.Kind)? {
get { editPollOriginalQuestionAnswersMaxSelectionsPollKindReceivedArgumentsLock.withLock { editPollOriginalQuestionAnswersMaxSelectionsPollKindUnderlyingReceivedArguments } }
set { editPollOriginalQuestionAnswersMaxSelectionsPollKindReceivedArgumentsLock.withLock { editPollOriginalQuestionAnswersMaxSelectionsPollKindUnderlyingReceivedArguments = newValue } }
}
private let editPollOriginalQuestionAnswersPollKindReceivedInvocationsLock = NSLock()
private nonisolated(unsafe) var editPollOriginalQuestionAnswersPollKindUnderlyingReceivedInvocations: [(eventID: String, question: String, answers: [String], pollKind: Poll.Kind)] = []
var editPollOriginalQuestionAnswersPollKindReceivedInvocations: [(eventID: String, question: String, answers: [String], pollKind: Poll.Kind)] {
get { editPollOriginalQuestionAnswersPollKindReceivedInvocationsLock.withLock { editPollOriginalQuestionAnswersPollKindUnderlyingReceivedInvocations } }
set { editPollOriginalQuestionAnswersPollKindReceivedInvocationsLock.withLock { editPollOriginalQuestionAnswersPollKindUnderlyingReceivedInvocations = newValue } }
private let editPollOriginalQuestionAnswersMaxSelectionsPollKindReceivedInvocationsLock = NSLock()
private nonisolated(unsafe) var editPollOriginalQuestionAnswersMaxSelectionsPollKindUnderlyingReceivedInvocations: [(eventID: String, question: String, answers: [String], maxSelections: Int, pollKind: Poll.Kind)] = []
var editPollOriginalQuestionAnswersMaxSelectionsPollKindReceivedInvocations: [(eventID: String, question: String, answers: [String], maxSelections: Int, pollKind: Poll.Kind)] {
get { editPollOriginalQuestionAnswersMaxSelectionsPollKindReceivedInvocationsLock.withLock { editPollOriginalQuestionAnswersMaxSelectionsPollKindUnderlyingReceivedInvocations } }
set { editPollOriginalQuestionAnswersMaxSelectionsPollKindReceivedInvocationsLock.withLock { editPollOriginalQuestionAnswersMaxSelectionsPollKindUnderlyingReceivedInvocations = newValue } }
}
private let editPollOriginalQuestionAnswersPollKindReturnValueLock = NSLock()
private nonisolated(unsafe) var editPollOriginalQuestionAnswersPollKindUnderlyingReturnValue: Result<Void, TimelineProxyError>!
var editPollOriginalQuestionAnswersPollKindReturnValue: Result<Void, TimelineProxyError>! {
get { editPollOriginalQuestionAnswersPollKindReturnValueLock.withLock { editPollOriginalQuestionAnswersPollKindUnderlyingReturnValue } }
set { editPollOriginalQuestionAnswersPollKindReturnValueLock.withLock { editPollOriginalQuestionAnswersPollKindUnderlyingReturnValue = newValue } }
private let editPollOriginalQuestionAnswersMaxSelectionsPollKindReturnValueLock = NSLock()
private nonisolated(unsafe) var editPollOriginalQuestionAnswersMaxSelectionsPollKindUnderlyingReturnValue: Result<Void, TimelineProxyError>!
var editPollOriginalQuestionAnswersMaxSelectionsPollKindReturnValue: Result<Void, TimelineProxyError>! {
get { editPollOriginalQuestionAnswersMaxSelectionsPollKindReturnValueLock.withLock { editPollOriginalQuestionAnswersMaxSelectionsPollKindUnderlyingReturnValue } }
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> {
editPollOriginalQuestionAnswersPollKindCallsCountLock.withLock { editPollOriginalQuestionAnswersPollKindUnderlyingCallsCount += 1 }
editPollOriginalQuestionAnswersPollKindReceivedArguments = (eventID: eventID, question: question, answers: answers, pollKind: pollKind)
editPollOriginalQuestionAnswersPollKindReceivedInvocationsLock.withLock { editPollOriginalQuestionAnswersPollKindUnderlyingReceivedInvocations.append((eventID: eventID, question: question, answers: answers, pollKind: pollKind)) }
if let editPollOriginalQuestionAnswersPollKindClosure = editPollOriginalQuestionAnswersPollKindClosure {
return await editPollOriginalQuestionAnswersPollKindClosure(eventID, question, answers, pollKind)
@concurrent func editPoll(original eventID: String, question: String, answers: [String], maxSelections: Int, pollKind: Poll.Kind) async -> Result<Void, TimelineProxyError> {
editPollOriginalQuestionAnswersMaxSelectionsPollKindCallsCountLock.withLock { editPollOriginalQuestionAnswersMaxSelectionsPollKindUnderlyingCallsCount += 1 }
editPollOriginalQuestionAnswersMaxSelectionsPollKindReceivedArguments = (eventID: eventID, question: question, answers: answers, maxSelections: maxSelections, pollKind: pollKind)
editPollOriginalQuestionAnswersMaxSelectionsPollKindReceivedInvocationsLock.withLock { editPollOriginalQuestionAnswersMaxSelectionsPollKindUnderlyingReceivedInvocations.append((eventID: eventID, question: question, answers: answers, maxSelections: maxSelections, pollKind: pollKind)) }
if let editPollOriginalQuestionAnswersMaxSelectionsPollKindClosure = editPollOriginalQuestionAnswersMaxSelectionsPollKindClosure {
return await editPollOriginalQuestionAnswersMaxSelectionsPollKindClosure(eventID, question, answers, maxSelections, pollKind)
} else {
return editPollOriginalQuestionAnswersPollKindReturnValue
return editPollOriginalQuestionAnswersMaxSelectionsPollKindReturnValue
}
}
//MARK: - sendPollResponse
+2 -1
View File
@@ -11,13 +11,14 @@ import Foundation
extension Poll {
static func mock(question: String,
pollKind: Poll.Kind = .disclosed,
maxSelections: Int = 1,
options: [Poll.Option],
votes: [String: [String]] = [:],
ended: Bool = false,
createdByAccountOwner: Bool = false) -> Self {
.init(question: question,
kind: pollKind,
maxSelections: 1,
maxSelections: maxSelections,
options: options,
votes: votes,
endDate: ended ? Date() : nil,
@@ -176,17 +176,17 @@ struct TimelineControllerMockConfiguration {
return .success(())
}
createPollQuestionAnswersPollKindClosure = { [weak self, timelineProxy] question, answers, pollKind in
createPollQuestionAnswersMaxSelectionsPollKindClosure = { [weak self, timelineProxy] question, answers, maxSelections, pollKind in
self?.callbacks.send(.messageSentOrEdited)
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(())
}
editPollOriginalQuestionAnswersPollKindClosure = { [timelineProxy] eventID, question, answers, pollKind in
editPollOriginalQuestionAnswersMaxSelectionsPollKindClosure = { [timelineProxy] eventID, question, answers, maxSelections, pollKind in
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(())
}
@@ -29,8 +29,8 @@ extension TimelineProxyMock {
paginateBackwardsRequestSizeReturnValue = .success(())
paginateForwardsRequestSizeReturnValue = .success(())
sendReadReceiptForTypeReturnValue = .success(())
createPollQuestionAnswersPollKindReturnValue = .success(())
editPollOriginalQuestionAnswersPollKindReturnValue = .success(())
createPollQuestionAnswersMaxSelectionsPollKindReturnValue = .success(())
editPollOriginalQuestionAnswersMaxSelectionsPollKindReturnValue = .success(())
if let provider = configuration.timelineItemProvider {
timelineItemProvider = provider
@@ -295,6 +295,9 @@ enum A11yIdentifiers {
struct PollFormScreen {
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 question = "poll_form-question"
let submit = "poll_form-submit"
@@ -77,7 +77,18 @@ enum PollFormMode: Hashable {
struct PollFormScreenViewStateBindings: Equatable {
var question = ""
var options: [Option] = [.init(), .init()]
var options: [Option] = [.init(), .init()] {
didSet {
maxSelections = clampedMaxSelections
}
}
var maxSelections = 1 {
didSet {
maxSelections = clampedMaxSelections
}
}
var isUndisclosed = false
struct Option: Identifiable, Equatable {
@@ -86,13 +97,20 @@ struct PollFormScreenViewStateBindings: Equatable {
}
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>?
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) {
self.init(question: poll.question,
options: poll.options.map { .init(text: $0.text) },
maxSelections: min(max(poll.maxSelections, 1), max(poll.options.count, 1)),
isUndisclosed: poll.kind == .undisclosed)
}
}
@@ -110,4 +129,6 @@ enum PollFormScreenViewAction {
case delete
case deleteOption(index: Int)
case addOption
case decrementMaxSelections
case incrementMaxSelections
}
@@ -40,14 +40,15 @@ class PollFormScreenViewModel: PollFormScreenViewModelType, PollFormScreenViewMo
case .submit:
let question = state.bindings.question
let options = state.bindings.options.map(\.text)
let maxSelections = state.bindings.maxSelections
let pollKind = state.bindings.isUndisclosed ? Poll.Kind.undisclosed : .disclosed
Task {
switch state.mode {
case .new:
await createPoll(question: question, options: options, pollKind: pollKind)
await createPoll(question: question, options: options, maxSelections: maxSelections, pollKind: pollKind)
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:
@@ -80,13 +81,17 @@ class PollFormScreenViewModel: PollFormScreenViewModelType, PollFormScreenViewMo
return
}
state.bindings.options.append(.init())
case .decrementMaxSelections:
state.bindings.maxSelections -= 1
case .incrementMaxSelections:
state.bindings.maxSelections += 1
}
}
// MARK: - Private
private func createPoll(question: String, options: [String], pollKind: Poll.Kind) async {
guard case .success = await timelineController.createPoll(question: question, answers: options, pollKind: pollKind) else {
private func createPoll(question: String, options: [String], maxSelections: Int, pollKind: Poll.Kind) async {
guard case .success = await timelineController.createPoll(question: question, answers: options, maxSelections: maxSelections, pollKind: pollKind) else {
userIndicatorController.submitIndicator(UserIndicator(title: L10n.errorUnknown))
return
}
@@ -102,8 +107,8 @@ class PollFormScreenViewModel: PollFormScreenViewModelType, PollFormScreenViewMo
analytics.trackPollCreated(isUndisclosed: pollKind == .undisclosed, numberOfAnswers: options.count)
}
private func editPoll(pollStartID: String, question: String, options: [String], pollKind: Poll.Kind) async {
switch await timelineController.editPoll(original: pollStartID, question: question, answers: options, pollKind: pollKind) {
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, maxSelections: maxSelections, pollKind: pollKind) {
case .success:
actionsSubject.send(.close)
case .failure:
@@ -22,7 +22,7 @@ struct PollFormScreen: View {
Form {
questionSection
optionsSection
showResultsSection
settingsSection
deletePollSection
}
.trackAnalyticsIfNeeded(context: context)
@@ -106,8 +106,16 @@ struct PollFormScreen: View {
focus = nextOptionIndex.map { .option(index: $0) }
}
private var showResultsSection: some View {
private var settingsSection: some View {
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),
kind: .toggle($context.isUndisclosed))
.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 {
@Environment(\.editMode) var editMode
@Binding var text: String
@@ -42,7 +42,7 @@ enum RoomPollsHistoryScreenViewAction {
case filter(RoomPollsHistoryFilter)
case end(pollStartID: String)
case edit(pollStartID: String, poll: Poll)
case sendPollResponse(pollStartID: String, optionID: String)
case sendPollResponse(pollStartID: String, answerIDs: [String])
case loadMore
}
@@ -58,8 +58,8 @@ class RoomPollsHistoryScreenViewModel: RoomPollsHistoryScreenViewModelType, Room
updatePollsList(filter: filter)
case .loadMore:
paginateBackwards()
case .sendPollResponse(let pollStartID, let optionID):
sendPollResponse(pollStartID: pollStartID, optionID: optionID)
case .sendPollResponse(let pollStartID, let answerIDs):
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 {
do {
try await pollInteractionHandler.sendPollResponse(pollStartID: pollStartID, optionID: optionID).get()
try await pollInteractionHandler.sendPollResponse(pollStartID: pollStartID, answerIDs: answerIDs).get()
} catch {
MXLog.error("Failed to send poll response. \(error)")
displayError(message: L10n.errorUnknown)
@@ -62,9 +62,9 @@ struct RoomPollsHistoryScreen: View {
PollView(poll: pollTimelineItem.item.poll,
state: .full(isEditable: pollTimelineItem.item.isEditable), sender: pollTimelineItem.item.sender) { action in
switch action {
case .selectOption(let optionID):
case .sendResponse(let answerIDs):
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:
guard let pollStartID = pollTimelineItem.item.id.eventID else { return }
context.send(viewAction: .edit(pollStartID: pollStartID, poll: pollTimelineItem.item.poll))
@@ -248,9 +248,9 @@ class TimelineInteractionHandler {
// MARK: Polls
func sendPollResponse(pollStartID: String, optionID: String) {
func sendPollResponse(pollStartID: String, answerIDs: [String]) {
Task {
let sendPollResponseResult = await pollInteractionHandler.sendPollResponse(pollStartID: pollStartID, optionID: optionID)
let sendPollResponseResult = await pollInteractionHandler.sendPollResponse(pollStartID: pollStartID, answerIDs: answerIDs)
switch sendPollResponseResult {
case .success:
@@ -36,7 +36,7 @@ enum TimelineViewModelAction {
}
enum TimelineViewPollAction {
case selectOption(pollStartID: String, optionID: String)
case sendResponse(pollStartID: String, answerIDs: [String])
case end(pollStartID: String)
case edit(pollStartID: String, poll: Poll)
}
@@ -376,8 +376,8 @@ class TimelineViewModel: TimelineViewModelType, TimelineViewModelProtocol {
private func handlePollAction(_ action: TimelineViewPollAction) {
switch action {
case let .selectOption(pollStartID, optionID):
timelineInteractionHandler.sendPollResponse(pollStartID: pollStartID, optionID: optionID)
case let .sendResponse(pollStartID, answerIDs):
timelineInteractionHandler.sendPollResponse(pollStartID: pollStartID, answerIDs: answerIDs)
case let .end(pollStartID):
displayAlert(.pollEndConfirmation(pollStartID))
case .edit(let eventID, let poll):
@@ -10,7 +10,7 @@ import Compound
import SwiftUI
enum PollViewAction {
case selectOption(optionID: String)
case sendResponse(answerIDs: [String])
case edit
case end
}
@@ -100,13 +100,13 @@ struct PollView: View {
}
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 {
Button {
guard !option.isSelected else { return }
actionHandler(.selectOption(optionID: option.id))
guard let answerIDs = poll.answerIDsAfterSelecting(optionID: option.id) else { return }
actionHandler(.sendResponse(answerIDs: answerIDs))
feedbackGenerator.impactOccurred()
} label: {
PollOptionView(pollOption: option,
@@ -114,7 +114,25 @@ struct PollView: View {
isFinalResult: poll.hasEnded)
.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
@@ -26,9 +26,9 @@ struct PollRoomTimelineView: View {
state: state,
sender: timelineItem.sender) { action in
switch action {
case .selectOption(let optionID):
guard let eventID, let option = poll.options.first(where: { $0.id == optionID }), !option.isSelected else { return }
context.send(viewAction: .handlePollAction(.selectOption(pollStartID: eventID, optionID: option.id)))
case .sendResponse(let answerIDs):
guard let eventID else { return }
context.send(viewAction: .handlePollAction(.sendResponse(pollStartID: eventID, answerIDs: answerIDs)))
case .edit:
guard let eventID else { return }
context.send(viewAction: .handlePollAction(.edit(pollStartID: eventID, poll: poll)))
@@ -17,8 +17,8 @@ class PollInteractionHandler: PollInteractionHandlerProtocol {
self.timelineController = timelineController
}
func sendPollResponse(pollStartID: String, optionID: String) async -> Result<Void, Error> {
let sendPollResponseResult = await timelineController.sendPollResponse(pollStartID: pollStartID, answers: [optionID])
func sendPollResponse(pollStartID: String, answerIDs: [String]) async -> Result<Void, Error> {
let sendPollResponseResult = await timelineController.sendPollResponse(pollStartID: pollStartID, answers: answerIDs)
analyticsService.trackPollVote()
return sendPollResponseResult.mapError { $0 }
@@ -9,7 +9,7 @@
import Foundation
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>
}
@@ -404,8 +404,8 @@ class TimelineController: TimelineControllerProtocol {
// MARK: - Polls
func createPoll(question: String, answers: [String], pollKind: Poll.Kind) async -> Result<Void, TimelineControllerError> {
switch await activeTimeline.createPoll(question: question, answers: answers, pollKind: pollKind).mapError(TimelineControllerError.timelineProxyError) {
func createPoll(question: String, answers: [String], maxSelections: Int, pollKind: Poll.Kind) async -> Result<Void, TimelineControllerError> {
switch await activeTimeline.createPoll(question: question, answers: answers, maxSelections: maxSelections, pollKind: pollKind).mapError(TimelineControllerError.timelineProxyError) {
case .success:
callbacks.send(.messageSentOrEdited)
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> {
await activeTimeline.editPoll(original: eventID, question: question, answers: answers, pollKind: pollKind).mapError(TimelineControllerError.timelineProxyError)
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, maxSelections: maxSelections, pollKind: pollKind).mapError(TimelineControllerError.timelineProxyError)
}
func sendPollResponse(pollStartID: String, answers: [String]) async -> Result<Void, TimelineControllerError> {
@@ -137,11 +137,12 @@ protocol TimelineControllerProtocol: Sendable {
// 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,
question: String,
answers: [String],
maxSelections: Int,
pollKind: Poll.Kind) 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
}
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 {
let id: String
let text: String
@@ -494,12 +494,15 @@ final class TimelineProxy: TimelineProxyProtocol {
// MARK: - Polls
func createPoll(question: String, answers: [String],
func createPoll(question: String, answers: [String], maxSelections: Int,
pollKind: Poll.Kind) async -> Result<Void, TimelineProxyError> {
MXLog.info("Creating poll")
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")
@@ -513,6 +516,7 @@ final class TimelineProxy: TimelineProxyProtocol {
func editPoll(original eventID: String,
question: String,
answers: [String],
maxSelections: Int,
pollKind: Poll.Kind) async -> Result<Void, TimelineProxyError> {
MXLog.info("Editing poll with eventID: \(eventID)")
@@ -522,7 +526,7 @@ final class TimelineProxy: TimelineProxyProtocol {
try await timeline.edit(eventOrTransactionId: originalEvent.eventOrTransactionId,
newContent: .pollStart(pollData: .init(question: question,
answers: answers,
maxSelections: 1,
maxSelections: UInt8(max(1, min(maxSelections, answers.count))),
pollKind: .init(pollKind: pollKind))))
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 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,
question: String,
answers: [String],
maxSelections: Int,
pollKind: Poll.Kind) 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
oid sha256:ba717565284f34e677e6885e82eaba19a07b1009f7a104f4fa394e36fab91f34
size 124711
oid sha256:7b63bb88da132634b7490bcc65e70bcf7491a4a8fd96da116a4391c8019f2988
size 131165
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:3973ef87c63089e471555d0a03396180fae10556566101555a3aea71fcb82137
size 132776
oid sha256:5b76b64f6a8ba7fbb58b265fb1024ae570d268c8b2686485a4f3009185cdbb62
size 141152
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:c1c666e0578df65052bd04aa3cfd5794977e53e2d1bd5aa3b32264c12e5a481a
size 72536
oid sha256:91a5da4b154e3d830aeefe321f149e5326b58c2c8e635947fcb3f43a3c65cc8c
size 77889
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:40236a2b9ca075fecf6d15efa6ddc99039d18b292d9dcd914c7399843e186bd4
size 83218
oid sha256:da6a9884746b4c77fd6b38c83d377a872a9b61daf85d4cf06dde020507ecfb50
size 96021
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:ab9b991b75cf2c878f7ac9276115b222ce73fa726173e24f3ddb2c12f19fa17d
size 127261
oid sha256:02f623ab052f5e52597fc351680224550ec4c156296c0861b9aa4ae146097e3c
size 133670
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:56e8b0e5bfcbfe83726fc02d5c05534788144d480770a93a9db02339156b9338
size 139917
oid sha256:4789ada7478537bb62c9fc8342f548d3cd1b8998517f33f7c172884c7a39e731
size 148409
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:0f060c42f3c8911ba0774fd8a243b7fd644cd986e79a170d6325b1a2f85191c0
size 72823
oid sha256:117c79bbaab1643281c91689d50e12f9f05b4bcb91a165330222dc8b8c4fdb91
size 79156
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:d5ec9841fda6f2b97516be4bf329af612dab6808a6c4f5328806764e161afcb5
size 88352
oid sha256:557924d719f75dad520ebbc22f25a2c0530b267b40188a0876cfdb31f881ace4
size 99168
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:2d72a6670dc29f4e551f3a44b461abb7a96ac65fdb526f4d3c788ee735013237
size 111649
oid sha256:eec6eadd12659fc444d02f2a69b1afee006d8e174e4fddda4211b11fdc09e6d6
size 111815
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:a4503c7655d8c9d091b9108ed64281ca25a554e2ead4c24309a063eee58c120d
size 117698
oid sha256:b60d1e89d772ef70e667058d3a3c2cb4e02b9daa810674a27e1c60fe3eb2bfcc
size 117881
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:64a104c0a76e9064808a12ce5c02843374ce25cd1a99d92139d30ed33d207519
size 67214
oid sha256:b032f8457f7e1da3466e9066641ca74f3f4bc4739ce8bf933855bdfef4c69d38
size 67157
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:9c601624b71617f5716a234685a7fb938163843da50f7db2f5072647cbd3119f
size 74024
oid sha256:212e4d5ce6c3b7120362efc13a5bd8f801f5761e23d78cb3009812514b76e7d6
size 74083
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:ae3c8a487fd10ced84bbdc7344e7c747a09069923a918a50526338aeb38ef343
size 101065
oid sha256:d2ed9dc31a4994088e7d7f868fa7eb7d4cee524f74ab3db9ee8f13489445bb8f
size 101070
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:8722708cb7205ed1932599053b0197eed41b1a15461ca22a192dc89af3897a32
size 105696
oid sha256:445c6ee56bcfb7890881e4443476a1c8883025ebcf753d75541fe08ea00665de
size 105702
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:711f0c1d1e191bba195a6a16dbbc9063b10f7a7938c291e21abbe2d3be64199c
size 56467
oid sha256:ca06fd7001e9a4fc47a40e3cb343112d5faeba57df1ae69bccc9b3d77a57d7fd
size 56531
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:81a3dc9b25a1c38911e970c4464618d5fda782fdf7ad6cbc8a9e246deaa01b62
size 61213
oid sha256:b6859e48d238a1ff08f138c13d839b87120e6d9556ea9f929335e8f51286415b
size 61293
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:2c63b201b2931e8a222876e0c9e5a3e32b000ec83c8a903103c80be08c7c42f6
oid sha256:4cda062114a6ae3c5b7cdb35505f17eb722d685c5613702f501c5b0f1495ff5e
size 196187
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:045214688aba9b8497231bc46d4b70f05cf73006cd7f4ce53dbf7200b3a6a930
size 227087
oid sha256:c54de901975dcfccdbc9d59b3bb72367cd7a78c6b5634d9b992523da58aca64b
size 227152
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:79a3655a47e3c1ed6a6361bb2dc55bb01c9f4b803d3194481e47bdab24cbfccc
size 128547
oid sha256:38789539575c066aef112024b9eb391ea0bfaf7380087bbac2f25028ab364186
size 128616
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:9549e8e59da3d131d54da01ae7eb04c4d9c876a3b7745b3d710cf23e3ec32c84
size 149037
oid sha256:29da80586d1664e8ee15dd9d8e5eaf53e107c0dd3d08f60013a079e1d21def77
size 149093
@@ -26,6 +26,7 @@ struct PollFormScreenViewModelTests {
let isEmpty = context.options.allSatisfy(\.text.isEmpty)
#expect(isEmpty)
#expect(context.question.isEmpty)
#expect(context.maxSelections == 1)
#expect(context.viewState.isSubmitButtonDisabled)
#expect(!context.viewState.bindings.isUndisclosed)
@@ -44,6 +45,7 @@ struct PollFormScreenViewModelTests {
#expect(context.options.count == 3)
#expect(context.options.allSatisfy { !$0.text.isEmpty })
#expect(!context.question.isEmpty)
#expect(context.maxSelections == 1)
#expect(context.viewState.isSubmitButtonDisabled)
#expect(!context.viewState.bindings.isUndisclosed)
@@ -90,22 +92,58 @@ struct PollFormScreenViewModelTests {
#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
mutating func newPollSubmit() async throws {
setupViewModel()
context.question = "foo"
context.options[0].text = "bla1"
context.options[1].text = "bla2"
context.send(viewAction: .incrementMaxSelections)
#expect(!context.viewState.isSubmitButtonDisabled)
let deferred = deferFulfillment(viewModel.actions) { $0 == .close }
try await confirmation { confirmation in
timelineProxy.createPollQuestionAnswersPollKindClosure = { question, options, kind in
timelineProxy.createPollQuestionAnswersMaxSelectionsPollKindClosure = { question, options, maxSelections, kind in
#expect(question == "foo")
#expect(options.count == 2)
#expect(options[0] == "bla1")
#expect(options[1] == "bla2")
#expect(maxSelections == 2)
#expect(kind == .disclosed)
confirmation()
return .success(())
@@ -122,12 +160,13 @@ struct PollFormScreenViewModelTests {
context.question = "What is your favorite country?"
context.options.append(.init(text: "France 🇫🇷"))
context.send(viewAction: .incrementMaxSelections)
#expect(!context.viewState.isSubmitButtonDisabled)
let deferred = deferFulfillment(viewModel.actions) { $0 == .close }
try await confirmation { confirmation in
timelineProxy.editPollOriginalQuestionAnswersPollKindClosure = { eventID, question, options, kind in
timelineProxy.editPollOriginalQuestionAnswersMaxSelectionsPollKindClosure = { eventID, question, options, maxSelections, kind in
#expect(eventID == "foo")
#expect(question == "What is your favorite country?")
#expect(options.count == 4)
@@ -135,6 +174,7 @@ struct PollFormScreenViewModelTests {
#expect(options[1] == "China 🇨🇳")
#expect(options[2] == "USA 🇺🇸")
#expect(options[3] == "France 🇫🇷")
#expect(maxSelections == 2)
#expect(kind == .disclosed)
confirmation()
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 {
let deferred = deferFulfillment(interactionHandler.publisher.delay(for: 0.1, scheduler: DispatchQueue.main)) { _ in true }
interactionHandler.sendPollResponsePollStartIDOptionIDReturnValue = .success(())
viewModel.context.send(viewAction: .sendPollResponse(pollStartID: "somePollID", optionID: "someOptionID"))
interactionHandler.sendPollResponsePollStartIDAnswerIDsReturnValue = .success(())
viewModel.context.send(viewAction: .sendPollResponse(pollStartID: "somePollID", answerIDs: ["firstOptionID", "secondOptionID"]))
try await deferred.fulfill()
#expect(interactionHandler.sendPollResponsePollStartIDOptionIDCalled)
#expect(interactionHandler.sendPollResponsePollStartIDOptionIDReceivedInvocations[0].pollStartID == "somePollID")
#expect(interactionHandler.sendPollResponsePollStartIDOptionIDReceivedInvocations[0].optionID == "someOptionID")
#expect(interactionHandler.sendPollResponsePollStartIDAnswerIDsCalled)
#expect(interactionHandler.sendPollResponsePollStartIDAnswerIDsReceivedInvocations[0].pollStartID == "somePollID")
#expect(interactionHandler.sendPollResponsePollStartIDAnswerIDsReceivedInvocations[0].answerIDs == ["firstOptionID", "secondOptionID"])
}
@Test
@@ -155,14 +155,14 @@ struct RoomPollsHistoryScreenViewModelTests {
value.bindings.alertInfo != nil
}
interactionHandler.sendPollResponsePollStartIDOptionIDReturnValue = .failure(SDKError.generic)
viewModel.context.send(viewAction: .sendPollResponse(pollStartID: "somePollID", optionID: "someOptionID"))
interactionHandler.sendPollResponsePollStartIDAnswerIDsReturnValue = .failure(SDKError.generic)
viewModel.context.send(viewAction: .sendPollResponse(pollStartID: "somePollID", answerIDs: ["someOptionID"]))
try await deferred.fulfill()
#expect(interactionHandler.sendPollResponsePollStartIDOptionIDCalled)
#expect(interactionHandler.sendPollResponsePollStartIDOptionIDReceivedInvocations[0].pollStartID == "somePollID")
#expect(interactionHandler.sendPollResponsePollStartIDOptionIDReceivedInvocations[0].optionID == "someOptionID")
#expect(interactionHandler.sendPollResponsePollStartIDAnswerIDsCalled)
#expect(interactionHandler.sendPollResponsePollStartIDAnswerIDsReceivedInvocations[0].pollStartID == "somePollID")
#expect(interactionHandler.sendPollResponsePollStartIDAnswerIDsReceivedInvocations[0].answerIDs == ["someOptionID"])
}
@Test