Files
element-x-ios/UnitTests/Sources/PollFormScreenViewModelTests.swift
T
Michael ff5dcb040d Replace the ServiceLocator with direct dependency injection (#5548)
Removes the ServiceLocator singleton and instead directly injects the dependencies it was holding for more control over instantiation of the various component subtrees and better visibility over how they're being used.
2026-05-14 09:45:49 +03:00

186 lines
6.8 KiB
Swift

//
// 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: .mock(),
userIndicatorController: UserIndicatorControllerMock())
}
}