// // Copyright 2025 Element Creations Ltd. // Copyright 2022-2025 New Vector 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 @MainActor struct PollFormScreenViewModelTests { private let timelineProxy = TimelineProxyMock(.init()) private var viewModel: PollFormScreenViewModelProtocol! private var context: PollFormScreenViewModelType.Context { viewModel.context } @Test mutating func newPollInitialState() async throws { setupViewModel() #expect(context.options.count == 2) // This due to a bug in Swift testing that raises an error when allSatisfy is used in an #expect let isEmpty = context.options.allSatisfy(\.text.isEmpty) #expect(isEmpty) #expect(context.question.isEmpty) #expect(context.viewState.isSubmitButtonDisabled) #expect(!context.viewState.bindings.isUndisclosed) // Cancellation should work without confirmation let deferred = deferFulfillment(viewModel.actions) { _ in true } context.send(viewAction: .cancel) let action = try await deferred.fulfill() #expect(context.alertInfo == nil) #expect(action == .close) } @Test mutating func editPollInitialState() async throws { setupViewModel(mode: .edit(eventID: "foo", poll: .emptyDisclosed)) #expect(context.options.count == 3) #expect(context.options.allSatisfy { !$0.text.isEmpty }) #expect(!context.question.isEmpty) #expect(context.viewState.isSubmitButtonDisabled) #expect(!context.viewState.bindings.isUndisclosed) // Cancellation should work without confirmation let deferred = deferFulfillment(viewModel.actions) { _ in true } context.send(viewAction: .cancel) let action = try await deferred.fulfill() #expect(context.alertInfo == nil) #expect(action == .close) } @Test mutating func newPollInvalidEmptyOption() { setupViewModel() context.question = "foo" context.options[0].text = "bla" context.options[1].text = "bla" context.send(viewAction: .addOption) #expect(context.viewState.isSubmitButtonDisabled) } @Test mutating func editPollInvalidEmptyOption() { setupViewModel(mode: .edit(eventID: "foo", poll: .emptyDisclosed)) context.send(viewAction: .addOption) #expect(context.viewState.isSubmitButtonDisabled) // Cancellation requires a confirmation context.send(viewAction: .cancel) #expect(context.alertInfo != nil) } @Test mutating func editPollSubmitButtonState() { setupViewModel(mode: .edit(eventID: "foo", poll: .emptyDisclosed)) #expect(context.viewState.isSubmitButtonDisabled) context.options[0].text = "foo" #expect(!context.viewState.isSubmitButtonDisabled) // Cancellation requires a confirmation context.send(viewAction: .cancel) #expect(context.alertInfo != nil) } @Test mutating func newPollSubmit() async throws { setupViewModel() context.question = "foo" context.options[0].text = "bla1" context.options[1].text = "bla2" #expect(!context.viewState.isSubmitButtonDisabled) let deferred = deferFulfillment(viewModel.actions) { $0 == .close } try await confirmation { confirmation in timelineProxy.createPollQuestionAnswersPollKindClosure = { question, options, kind in #expect(question == "foo") #expect(options.count == 2) #expect(options[0] == "bla1") #expect(options[1] == "bla2") #expect(kind == .disclosed) confirmation() return .success(()) } context.send(viewAction: .submit) try await deferred.fulfill() } } @Test mutating func editPollSubmit() async throws { setupViewModel(mode: .edit(eventID: "foo", poll: .emptyDisclosed)) context.question = "What is your favorite country?" context.options.append(.init(text: "France 🇫🇷")) #expect(!context.viewState.isSubmitButtonDisabled) let deferred = deferFulfillment(viewModel.actions) { $0 == .close } try await confirmation { confirmation in timelineProxy.editPollOriginalQuestionAnswersPollKindClosure = { eventID, question, options, kind in #expect(eventID == "foo") #expect(question == "What is your favorite country?") #expect(options.count == 4) #expect(options[0] == "Italy 🇮🇹") #expect(options[1] == "China 🇨🇳") #expect(options[2] == "USA 🇺🇸") #expect(options[3] == "France 🇫🇷") #expect(kind == .disclosed) confirmation() return .success(()) } context.send(viewAction: .submit) try await deferred.fulfill() } } @Test mutating func deletePoll() async throws { setupViewModel(mode: .edit(eventID: "foo", poll: .emptyDisclosed)) context.question = "What is your favorite country?" context.options.append(.init(text: "France 🇫🇷")) #expect(!context.viewState.isSubmitButtonDisabled) let deferredFailure = deferFailure(viewModel.actions, timeout: .seconds(1)) { $0 == .close } context.send(viewAction: .delete) try await deferredFailure.fulfill() #expect(context.alertInfo != nil, "An alert should be shown before deleting the poll.") let deferred = deferFulfillment(viewModel.actions) { $0 == .close } await waitForConfirmation(timeout: .seconds(1)) { confirmation in timelineProxy.redactReasonClosure = { eventID, _ in defer { confirmation() } #expect(eventID == .eventID("foo")) return .success(()) } context.alertInfo?.secondaryButton?.action?() } try await deferred.fulfill() } // MARK: - Helpers private mutating func setupViewModel(mode: PollFormMode = .new) { viewModel = PollFormScreenViewModel(mode: mode, timelineController: MockTimelineController(timelineProxy: timelineProxy), analytics: ServiceLocator.shared.analytics, userIndicatorController: UserIndicatorControllerMock()) } }