// // 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 StartChatScreenViewModelTests { private var viewModel: StartChatScreenViewModelProtocol! private var clientProxy: ClientProxyMock! private var userDiscoveryService: UserDiscoveryServiceMock! private var context: StartChatScreenViewModel.Context { viewModel.context } init() { clientProxy = .init(.init(userID: "")) userDiscoveryService = UserDiscoveryServiceMock() userDiscoveryService.searchProfilesWithReturnValue = .success([]) let userSession = UserSessionMock(.init(clientProxy: clientProxy)) viewModel = StartChatScreenViewModel(userSession: userSession, analytics: ServiceLocator.shared.analytics, userIndicatorController: UserIndicatorControllerMock(), userDiscoveryService: userDiscoveryService, appSettings: ServiceLocator.shared.settings) } @Test mutating func queryShowingNoResults() async { await search(query: "A") #expect(context.viewState.usersSection.type == .suggestions) await search(query: "AA") #expect(context.viewState.usersSection.type == .suggestions) #expect(!userDiscoveryService.searchProfilesWithCalled) await search(query: "AAA") assertSearchResults(toBe: 0) #expect(userDiscoveryService.searchProfilesWithCalled) } @Test func joinRoomByAddress() async throws { clientProxy.resolveRoomAliasReturnValue = .success(.init(roomId: "id", servers: [])) let deferredViewState = deferFulfillment(viewModel.context.$viewState) { viewState in viewState.joinByAddressState == .addressFound(address: "#room:example.com", roomID: "id") } viewModel.context.roomAddress = "#room:example.com" try await deferredViewState.fulfill() let deferredAction = deferFulfillment(viewModel.actions) { action in action == .showRoom(roomID: "id") } context.send(viewAction: .joinRoomByAddress) try await deferredAction.fulfill() } @Test func joinRoomByAddressFailsBecauseInvalid() async throws { let deferred = deferFulfillment(viewModel.context.$viewState) { viewState in viewState.joinByAddressState == .invalidAddress } viewModel.context.roomAddress = ":" context.send(viewAction: .joinRoomByAddress) try await deferred.fulfill() } @Test func joinRoomByAddressFailsBecauseNotFound() async throws { clientProxy.resolveRoomAliasReturnValue = .failure(.failedResolvingRoomAlias) let deferred = deferFulfillment(viewModel.context.$viewState) { viewState in viewState.joinByAddressState == .addressNotFound } viewModel.context.roomAddress = "#room:example.com" context.send(viewAction: .joinRoomByAddress) try await deferred.fulfill() } // MARK: - Private private func assertSearchResults(toBe count: Int) { #expect(count >= 0) #expect(context.viewState.usersSection.type == .searchResult) #expect(context.viewState.usersSection.users.count == count) #expect(context.viewState.hasEmptySearchResults == (count == 0)) } @discardableResult private mutating func search(query: String) async -> StartChatScreenViewState? { viewModel.context.searchQuery = query return await context.$viewState.nextValue } }