// // 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 Foundation import Testing @MainActor struct NavigationTabCoordinatorTests { enum TestTab { case tab, chats, spaces } private var navigationTabCoordinator: NavigationTabCoordinator init() { navigationTabCoordinator = NavigationTabCoordinator() } @Test mutating func tabs() { #expect(navigationTabCoordinator.tabCoordinators.isEmpty) let someCoordinator = SomeTestCoordinator() navigationTabCoordinator.setTabs([.init(coordinator: someCoordinator, details: .init(tag: .tab, title: "Tab", icon: \.help, selectedIcon: \.helpSolid))]) assertCoordinatorsEqual(navigationTabCoordinator.tabCoordinators, [someCoordinator]) let chatsCoordinator = SomeTestCoordinator() let spacesCoordinator = SomeTestCoordinator() navigationTabCoordinator.setTabs([ .init(coordinator: chatsCoordinator, details: .init(tag: .chats, title: "Chats", icon: \.chat, selectedIcon: \.chatSolid)), .init(coordinator: spacesCoordinator, details: .init(tag: .spaces, title: "Spaces", icon: \.space, selectedIcon: \.spaceSolid)) ]) assertCoordinatorsEqual(navigationTabCoordinator.tabCoordinators, [chatsCoordinator, spacesCoordinator]) } @Test mutating func singleSheet() { let tabCoordinator = SomeTestCoordinator() navigationTabCoordinator.setTabs([.init(coordinator: tabCoordinator, details: .init(tag: .tab, title: "Tab", icon: \.help, selectedIcon: \.helpSolid))]) let coordinator = SomeTestCoordinator() navigationTabCoordinator.setSheetCoordinator(coordinator) assertCoordinatorsEqual(navigationTabCoordinator.tabCoordinators, [tabCoordinator]) assertCoordinatorsEqual(coordinator, navigationTabCoordinator.sheetCoordinator) navigationTabCoordinator.setSheetCoordinator(nil) assertCoordinatorsEqual(navigationTabCoordinator.tabCoordinators, [tabCoordinator]) #expect(navigationTabCoordinator.sheetCoordinator == nil) } @Test mutating func multipleSheets() { let tabCoordinator = SomeTestCoordinator() navigationTabCoordinator.setTabs([.init(coordinator: tabCoordinator, details: .init(tag: .tab, title: "Tab", icon: \.help, selectedIcon: \.helpSolid))]) let sheetCoordinator = SomeTestCoordinator() navigationTabCoordinator.setSheetCoordinator(sheetCoordinator) assertCoordinatorsEqual(navigationTabCoordinator.tabCoordinators, [tabCoordinator]) assertCoordinatorsEqual(sheetCoordinator, navigationTabCoordinator.sheetCoordinator) let someOtherSheetCoordinator = SomeTestCoordinator() navigationTabCoordinator.setSheetCoordinator(someOtherSheetCoordinator) assertCoordinatorsEqual(navigationTabCoordinator.tabCoordinators, [tabCoordinator]) assertCoordinatorsEqual(someOtherSheetCoordinator, navigationTabCoordinator.sheetCoordinator) } @Test mutating func fullScreenCover() { let tabCoordinator = SomeTestCoordinator() navigationTabCoordinator.setTabs([.init(coordinator: tabCoordinator, details: .init(tag: .tab, title: "Tab", icon: \.help, selectedIcon: \.helpSolid))]) let coordinator = SomeTestCoordinator() navigationTabCoordinator.setFullScreenCoverCoordinator(coordinator) assertCoordinatorsEqual(navigationTabCoordinator.tabCoordinators, [tabCoordinator]) assertCoordinatorsEqual(coordinator, navigationTabCoordinator.fullScreenCoverCoordinator) navigationTabCoordinator.setFullScreenCoverCoordinator(nil) assertCoordinatorsEqual(navigationTabCoordinator.tabCoordinators, [tabCoordinator]) #expect(navigationTabCoordinator.fullScreenCoverCoordinator == nil) } @Test mutating func overlay() { let tabCoordinator = SomeTestCoordinator() navigationTabCoordinator.setTabs([.init(coordinator: tabCoordinator, details: .init(tag: .tab, title: "Tab", icon: \.help, selectedIcon: \.helpSolid))]) let overlayCoordinator = SomeTestCoordinator() navigationTabCoordinator.setOverlayCoordinator(overlayCoordinator) assertCoordinatorsEqual(navigationTabCoordinator.tabCoordinators, [tabCoordinator]) assertCoordinatorsEqual(overlayCoordinator, navigationTabCoordinator.overlayCoordinator) // The coordinator should still be retained when changing the presentation mode. navigationTabCoordinator.setOverlayPresentationMode(.minimized) assertCoordinatorsEqual(overlayCoordinator, navigationTabCoordinator.overlayCoordinator) navigationTabCoordinator.setOverlayPresentationMode(.fullScreen) assertCoordinatorsEqual(overlayCoordinator, navigationTabCoordinator.overlayCoordinator) navigationTabCoordinator.setOverlayCoordinator(nil) assertCoordinatorsEqual(navigationTabCoordinator.tabCoordinators, [tabCoordinator]) #expect(navigationTabCoordinator.overlayCoordinator == nil) } // MARK: - Dismissal Callbacks @Test mutating func tabDismissalCallbacks() async { let chatsCoordinator = SomeTestCoordinator() let spacesCoordinator = SomeTestCoordinator() await confirmation("Wait for callback", expectedCount: 2) { confirm in navigationTabCoordinator.setTabs([ .init(coordinator: chatsCoordinator, details: .init(tag: .chats, title: "Chats", icon: \.chat, selectedIcon: \.chatSolid)) { confirm() }, .init(coordinator: spacesCoordinator, details: .init(tag: .spaces, title: "Spaces", icon: \.space, selectedIcon: \.spaceSolid)) { confirm() } ]) assertCoordinatorsEqual(navigationTabCoordinator.tabCoordinators, [chatsCoordinator, spacesCoordinator]) navigationTabCoordinator.setTabs([.init(coordinator: SomeTestCoordinator(), details: .init(tag: .tab, title: "Tab", icon: \.help, selectedIcon: \.helpSolid))]) } } @Test mutating func sheetDismissalCallback() async { let coordinator = SomeTestCoordinator() await confirmation("Wait for callback") { confirm in navigationTabCoordinator.setSheetCoordinator(coordinator) { confirm() } navigationTabCoordinator.setSheetCoordinator(nil) } } @Test mutating func fullScreenCoverDismissalCallback() async { let coordinator = SomeTestCoordinator() await confirmation("Wait for callback") { confirm in navigationTabCoordinator.setFullScreenCoverCoordinator(coordinator) { confirm() } navigationTabCoordinator.setFullScreenCoverCoordinator(nil) } } @Test mutating func overlayDismissalCallback() async { let overlayCoordinator = SomeTestCoordinator() await confirmation("Wait for callback") { confirm in navigationTabCoordinator.setOverlayCoordinator(overlayCoordinator) { confirm() } navigationTabCoordinator.setOverlayCoordinator(nil) } } @Test mutating func overlayDismissalCallbackWhenChangingMode() async { let overlayCoordinator = SomeTestCoordinator() await waitForConfirmation("Callback should not be called when just changing mode", expectedCount: 0, timeout: .seconds(1)) { confirmation in navigationTabCoordinator.setOverlayCoordinator(overlayCoordinator) { confirmation() } navigationTabCoordinator.setOverlayPresentationMode(.minimized) } } // MARK: - Private private func assertCoordinatorsEqual(_ lhs: CoordinatorProtocol?, _ rhs: CoordinatorProtocol?) { guard let lhs = lhs as? SomeTestCoordinator, let rhs = rhs as? SomeTestCoordinator else { Issue.record("Coordinators are not the same") return } #expect(lhs.id == rhs.id) } private func assertCoordinatorsEqual(_ lhs: [CoordinatorProtocol], _ rhs: [CoordinatorProtocol]) { guard lhs.count == rhs.count else { Issue.record("Coordinators are not the same") return } for (index, coordinator) in lhs.enumerated() { assertCoordinatorsEqual(coordinator, rhs[index]) } } } private class SomeTestCoordinator: CoordinatorProtocol { let id = UUID() }