Files
letro-ios/UnitTests/Sources/KnockRequestsListScreenViewModelTests.swift
Mauro 173b39a07f Swift Testing for Unit Tests PART 1 (#5119)
* migrated a lot of unit tests to Swift Testing and added a new implementation for deferred fulfillment

more tests migration

Cleaned the code manually to establish some good patterns

more code improvements

some more code improvements

removed empty tests

update project

* more pr suggestions and cleanups

* removed the TestSetup pattern

* fixing claude not reusing tests

* pr suggestion + added indent rule to swiftformat so that we can prevent AIs to change that
2026-02-19 16:20:47 +01:00

225 lines
11 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
@Suite
struct KnockRequestsListScreenViewModelTests {
init() {
AppSettings.resetAllSettings()
}
@Test
func loadingState() async throws {
let roomProxyMock = JoinedRoomProxyMock(.init(knockRequestsState: .loading, joinRule: .knock))
let viewModel = KnockRequestsListScreenViewModel(roomProxy: roomProxyMock,
mediaProvider: MediaProviderMock(),
userIndicatorController: UserIndicatorControllerMock())
let context = viewModel.context
let deferred = deferFulfillment(context.$viewState) { state in
!state.shouldDisplayRequests &&
state.isKnockableRoom &&
state.canAccept &&
!state.canBan &&
!state.canDecline &&
state.isLoading &&
!state.shouldDisplayEmptyView
}
try await deferred.fulfill()
}
@Test
func emptyState() async throws {
let roomProxyMock = JoinedRoomProxyMock(.init(knockRequestsState: .loaded([]), joinRule: .knock))
let viewModel = KnockRequestsListScreenViewModel(roomProxy: roomProxyMock,
mediaProvider: MediaProviderMock(),
userIndicatorController: UserIndicatorControllerMock())
let context = viewModel.context
let deferred = deferFulfillment(context.$viewState) { state in
!state.shouldDisplayRequests &&
state.isKnockableRoom &&
state.canAccept &&
!state.canBan &&
!state.canDecline &&
!state.isLoading &&
state.shouldDisplayEmptyView
}
try await deferred.fulfill()
}
@Test
func loadedState() async throws {
let roomProxyMock = JoinedRoomProxyMock(.init(members: [.mockAdmin],
knockRequestsState: .loaded([KnockRequestProxyMock(.init(eventID: "1", userID: "@alice:matrix.org")),
KnockRequestProxyMock(.init(eventID: "2", userID: "@bob:matrix.org")),
KnockRequestProxyMock(.init(eventID: "3", userID: "@charlie:matrix.org")),
KnockRequestProxyMock(.init(eventID: "4", userID: "@dan:matrix.org"))]),
ownUserID: RoomMemberProxyMock.mockAdmin.userID,
joinRule: .knock))
let viewModel = KnockRequestsListScreenViewModel(roomProxy: roomProxyMock,
mediaProvider: MediaProviderMock(),
userIndicatorController: UserIndicatorControllerMock())
let context = viewModel.context
var deferred = deferFulfillment(context.$viewState) { state in
state.shouldDisplayRequests &&
state.isKnockableRoom &&
state.canAccept &&
state.canBan &&
state.canDecline &&
!state.isLoading &&
!state.shouldDisplayEmptyView &&
state.displayedRequests.count == 4 &&
state.handledEventIDs.isEmpty &&
state.shouldDisplayAcceptAllButton
}
try await deferred.fulfill()
deferred = deferFulfillment(context.$viewState) { state in
state.shouldDisplayRequests &&
state.handledEventIDs == ["1"] &&
!state.shouldDisplayEmptyView &&
state.displayedRequests.count == 3 &&
state.shouldDisplayAcceptAllButton
}
context.send(viewAction: .acceptRequest(eventID: "1"))
try await deferred.fulfill()
deferred = deferFulfillment(context.$viewState) { state in
state.bindings.alertInfo?.id == .declineRequest
}
context.send(viewAction: .declineRequest(eventID: "2"))
try await deferred.fulfill()
let declineAlertInfo = try #require(context.alertInfo)
deferred = deferFulfillment(context.$viewState) { state in
state.shouldDisplayRequests &&
state.handledEventIDs == ["1", "2"] &&
!state.shouldDisplayEmptyView &&
state.displayedRequests.count == 2 &&
state.shouldDisplayAcceptAllButton
}
declineAlertInfo.primaryButton.action?()
try await deferred.fulfill()
deferred = deferFulfillment(context.$viewState) { state in
state.bindings.alertInfo?.id == .declineAndBan
}
context.send(viewAction: .ban(eventID: "3"))
try await deferred.fulfill()
let banAlertInfo = try #require(context.alertInfo)
deferred = deferFulfillment(context.$viewState) { state in
state.shouldDisplayRequests &&
state.handledEventIDs == ["1", "2", "3"] &&
!state.shouldDisplayEmptyView &&
state.displayedRequests.count == 1 &&
!state.shouldDisplayAcceptAllButton
}
banAlertInfo.primaryButton.action?()
try await deferred.fulfill()
}
@Test
func acceptAll() async throws {
let roomProxyMock = JoinedRoomProxyMock(.init(knockRequestsState: .loaded([KnockRequestProxyMock(.init(eventID: "1", userID: "@alice:matrix.org")),
KnockRequestProxyMock(.init(eventID: "2", userID: "@bob:matrix.org")),
KnockRequestProxyMock(.init(eventID: "3", userID: "@charlie:matrix.org")),
KnockRequestProxyMock(.init(eventID: "4", userID: "@dan:matrix.org"))]),
joinRule: .knock))
let viewModel = KnockRequestsListScreenViewModel(roomProxy: roomProxyMock,
mediaProvider: MediaProviderMock(),
userIndicatorController: UserIndicatorControllerMock())
let context = viewModel.context
var deferred = deferFulfillment(context.$viewState) { state in
state.shouldDisplayRequests &&
state.isKnockableRoom &&
state.canAccept &&
!state.canBan &&
!state.canDecline &&
!state.isLoading &&
!state.shouldDisplayEmptyView &&
state.displayedRequests.count == 4 &&
state.handledEventIDs.isEmpty &&
state.shouldDisplayAcceptAllButton
}
try await deferred.fulfill()
deferred = deferFulfillment(context.$viewState) { state in
state.bindings.alertInfo?.id == .acceptAllRequests
}
context.send(viewAction: .acceptAllRequests)
try await deferred.fulfill()
let alertInfo = try #require(context.alertInfo)
deferred = deferFulfillment(context.$viewState) { state in
!state.shouldDisplayRequests &&
state.handledEventIDs == ["1", "2", "3", "4"] &&
!state.isLoading &&
state.shouldDisplayEmptyView
}
alertInfo.primaryButton.action?()
try await deferred.fulfill()
}
@Test
func loadedStateBecomesEmptyIfTheJoinRuleIsNotKnocking() async throws {
// If there is a sudden change in the rule, but the requests are still published, we want to hide all of them and show the empty view
let roomProxyMock = JoinedRoomProxyMock(.init(members: [.mockAdmin],
knockRequestsState: .loaded([KnockRequestProxyMock(.init(eventID: "1", userID: "@alice:matrix.org")),
KnockRequestProxyMock(.init(eventID: "2", userID: "@bob:matrix.org")),
KnockRequestProxyMock(.init(eventID: "3", userID: "@charlie:matrix.org")),
KnockRequestProxyMock(.init(eventID: "4", userID: "@dan:matrix.org"))]),
ownUserID: RoomMemberProxyMock.mockAdmin.userID,
joinRule: .invite))
let viewModel = KnockRequestsListScreenViewModel(roomProxy: roomProxyMock,
mediaProvider: MediaProviderMock(),
userIndicatorController: UserIndicatorControllerMock())
let context = viewModel.context
let deferred = deferFulfillment(context.$viewState) { state in
!state.shouldDisplayRequests &&
state.shouldDisplayEmptyView &&
!state.isLoading &&
!state.isKnockableRoom
}
try await deferred.fulfill()
}
@Test
func loadedStateBecomesEmptyIfPermissionsAreRemoved() async throws {
// If there is a sudden change in permissions, and the user can't do any other action, we hide all the requests and shoe the empty view
let roomProxyMock = JoinedRoomProxyMock(.init(knockRequestsState: .loaded([KnockRequestProxyMock(.init(eventID: "1", userID: "@alice:matrix.org")),
KnockRequestProxyMock(.init(eventID: "2", userID: "@bob:matrix.org")),
KnockRequestProxyMock(.init(eventID: "3", userID: "@charlie:matrix.org")),
KnockRequestProxyMock(.init(eventID: "4", userID: "@dan:matrix.org"))]),
joinRule: .knock,
powerLevelsConfiguration: .init(canUserInvite: false)))
let viewModel = KnockRequestsListScreenViewModel(roomProxy: roomProxyMock,
mediaProvider: MediaProviderMock(),
userIndicatorController: UserIndicatorControllerMock())
let context = viewModel.context
let deferred = deferFulfillment(context.$viewState) { state in
!state.shouldDisplayRequests &&
state.shouldDisplayEmptyView &&
!state.canAccept &&
!state.canBan &&
!state.canDecline &&
!state.isLoading
}
try await deferred.fulfill()
}
}