Skip to content

Commit

Permalink
Move the navigation dismissal callbacks to the NavigationModule, add …
Browse files Browse the repository at this point in the history
…SingleScreenCoordinator tests
  • Loading branch information
stefanceriu committed Dec 8, 2022
1 parent b8830d9 commit 826c19c
Show file tree
Hide file tree
Showing 5 changed files with 94 additions and 63 deletions.
4 changes: 4 additions & 0 deletions ElementX.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,7 @@
64FF5CB4E35971255872E1BB /* AuthenticationServiceProxyProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F0CB536D1C3CC15AA740CC6 /* AuthenticationServiceProxyProtocol.swift */; };
652ACCF104A8CEF30788963C /* NotificationManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1423AB065857FA546444DB15 /* NotificationManager.swift */; };
65C1FABA4C7DB0F95759539B /* StackScreenCoordinatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F196745C0836B6807786D3D /* StackScreenCoordinatorTests.swift */; };
6627C6B0A599456376773C74 /* SingleScreenCoordinatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB603913DBF2B370F657A257 /* SingleScreenCoordinatorTests.swift */; };
663E198678778F7426A9B27D /* Collection.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9FAFE1C2149E6AC8156ED2B /* Collection.swift */; };
6647430A45B4A8E692909A8F /* EmoteRoomTimelineItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = F77C060C2ACC4CB7336A29E7 /* EmoteRoomTimelineItem.swift */; };
67C05C50AD734283374605E3 /* MatrixEntityRegex.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6AD1A853D605C2146B0DC028 /* MatrixEntityRegex.swift */; };
Expand Down Expand Up @@ -901,6 +902,7 @@
CA9D14D6F914324865C7DB9F /* ActivityCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActivityCoordinator.swift; sourceTree = "<group>"; };
CAAE4A709C0A2144C103AA0F /* ang */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ang; path = ang.lproj/Localizable.strings; sourceTree = "<group>"; };
CACA846B3E3E9A521D98B178 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = "<group>"; };
CB603913DBF2B370F657A257 /* SingleScreenCoordinatorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SingleScreenCoordinatorTests.swift; sourceTree = "<group>"; };
CBA95E52C4C6EE8769A63E57 /* eo */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = eo; path = eo.lproj/Localizable.strings; sourceTree = "<group>"; };
CBBCC6E74774E79B599625D0 /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/Localizable.strings; sourceTree = "<group>"; };
CBF9AEA706926DD0DA2B954C /* JoinedRoomSize+MemberCount.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "JoinedRoomSize+MemberCount.swift"; sourceTree = "<group>"; };
Expand Down Expand Up @@ -1649,6 +1651,7 @@
A1C22B1B5FA3A765EADB2CC9 /* SessionVerificationStateMachineTests.swift */,
DF05DA24F71B455E8EFEBC3B /* SessionVerificationViewModelTests.swift */,
3D487C1185D658F8B15B8F55 /* SettingsViewModelTests.swift */,
CB603913DBF2B370F657A257 /* SingleScreenCoordinatorTests.swift */,
32C5DAA1773F57653BF1C4F9 /* SoftLogoutViewModelTests.swift */,
4407F9A2054ACCED2275B863 /* SplitScreenCoordinatorTests.swift */,
6F196745C0836B6807786D3D /* StackScreenCoordinatorTests.swift */,
Expand Down Expand Up @@ -2771,6 +2774,7 @@
86675910612A12409262DFBD /* SessionVerificationStateMachineTests.swift in Sources */,
755727E0B756430DFFEC4732 /* SessionVerificationViewModelTests.swift in Sources */,
206F0DBAB6AF042CA1FF2C0D /* SettingsViewModelTests.swift in Sources */,
6627C6B0A599456376773C74 /* SingleScreenCoordinatorTests.swift in Sources */,
09AAF04B27732046C755D914 /* SoftLogoutViewModelTests.swift in Sources */,
5D52BDB8A8DF82E08D8FA1ED /* SplitScreenCoordinatorTests.swift in Sources */,
65C1FABA4C7DB0F95759539B /* StackScreenCoordinatorTests.swift in Sources */,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,11 @@ import Foundation
struct NavigationModule: Identifiable, Hashable {
let id = UUID()
let coordinator: any CoordinatorProtocol
let dismissalCallback: (() -> Void)?

init(_ coordinator: any CoordinatorProtocol) {
init(_ coordinator: any CoordinatorProtocol, dismissalCallback: (() -> Void)? = nil) {
self.coordinator = coordinator
self.dismissalCallback = dismissalCallback
}

static func == (lhs: NavigationModule, rhs: NavigationModule) -> Bool {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,16 +22,14 @@ import SwiftUI
class SplitScreenCoordinator: CoordinatorProtocol, ObservableObject, CustomStringConvertible {
fileprivate let placeholderModule: NavigationModule

private var dismissalCallbacks = [UUID: () -> Void]()
private var cancellables = Set<AnyCancellable>()

@Published fileprivate var sidebarModule: NavigationModule? {
didSet {
if let oldValue {
logPresentationChange("Remove sidebar", oldValue)
oldValue.coordinator.stop()
dismissalCallbacks[oldValue.id]?()
dismissalCallbacks.removeValue(forKey: oldValue.id)
oldValue.dismissalCallback?()
}

if let sidebarModule {
Expand All @@ -53,8 +51,7 @@ class SplitScreenCoordinator: CoordinatorProtocol, ObservableObject, CustomStrin
if let oldValue {
logPresentationChange("Remove detail", oldValue)
oldValue.coordinator.stop()
dismissalCallbacks[oldValue.id]?()
dismissalCallbacks.removeValue(forKey: oldValue.id)
oldValue.dismissalCallback?()
}

if let detailModule {
Expand All @@ -76,8 +73,7 @@ class SplitScreenCoordinator: CoordinatorProtocol, ObservableObject, CustomStrin
if let oldValue {
logPresentationChange("Remove sheet", oldValue)
oldValue.coordinator.stop()
dismissalCallbacks[oldValue.id]?()
dismissalCallbacks.removeValue(forKey: oldValue.id)
oldValue.dismissalCallback?()
}

if let sheetModule {
Expand Down Expand Up @@ -124,13 +120,7 @@ class SplitScreenCoordinator: CoordinatorProtocol, ObservableObject, CustomStrin
return
}

let module = NavigationModule(coordinator)

if let dismissalCallback {
dismissalCallbacks[module.id] = dismissalCallback
}

sidebarModule = module
sidebarModule = NavigationModule(coordinator, dismissalCallback: dismissalCallback)
}

/// Set the coordinator to be used on the split's right pannel
Expand All @@ -143,13 +133,7 @@ class SplitScreenCoordinator: CoordinatorProtocol, ObservableObject, CustomStrin
return
}

let module = NavigationModule(coordinator)

if let dismissalCallback {
dismissalCallbacks[module.id] = dismissalCallback
}

detailModule = module
detailModule = NavigationModule(coordinator, dismissalCallback: dismissalCallback)
}

/// Present a sheet on top of the split view
Expand All @@ -162,13 +146,7 @@ class SplitScreenCoordinator: CoordinatorProtocol, ObservableObject, CustomStrin
return
}

let module = NavigationModule(coordinator)

if let dismissalCallback {
dismissalCallbacks[module.id] = dismissalCallback
}

sheetModule = module
sheetModule = NavigationModule(coordinator, dismissalCallback: dismissalCallback)
}

// MARK: - CoordinatorProtocol
Expand Down Expand Up @@ -362,17 +340,14 @@ private struct SplitScreenCoordinatorView: View {

/// Class responsible for displaying a normal "NavigationController" style hierarchy
class StackScreenCoordinator: ObservableObject, CoordinatorProtocol, CustomStringConvertible {
private var dismissalCallbacks = [UUID: () -> Void]()

private(set) weak var splitScreenCoordinator: SplitScreenCoordinator?

@Published fileprivate var rootModule: NavigationModule? {
didSet {
if let oldValue {
logPresentationChange("Remove root", oldValue)
oldValue.coordinator.stop()
dismissalCallbacks[oldValue.id]?()
dismissalCallbacks.removeValue(forKey: oldValue.id)
oldValue.dismissalCallback?()
}

if let rootModule {
Expand All @@ -392,8 +367,7 @@ class StackScreenCoordinator: ObservableObject, CoordinatorProtocol, CustomStrin
if let oldValue {
logPresentationChange("Remove sheet", oldValue)
oldValue.coordinator.stop()
dismissalCallbacks[oldValue.id]?()
dismissalCallbacks.removeValue(forKey: oldValue.id)
oldValue.dismissalCallback?()
}

if let sheetModule {
Expand Down Expand Up @@ -424,9 +398,7 @@ class StackScreenCoordinator: ObservableObject, CoordinatorProtocol, CustomStrin
case .remove(_, let module, _):
logPresentationChange("Pop", module)
module.coordinator.stop()

dismissalCallbacks[module.id]?()
dismissalCallbacks.removeValue(forKey: module.id)
module.dismissalCallback?()
}
}
}
Expand Down Expand Up @@ -457,27 +429,15 @@ class StackScreenCoordinator: ObservableObject, CoordinatorProtocol, CustomStrin

popToRoot(animated: false)

let module = NavigationModule(coordinator)

if let dismissalCallback {
dismissalCallbacks[module.id] = dismissalCallback
}

rootModule = module
rootModule = NavigationModule(coordinator, dismissalCallback: dismissalCallback)
}

/// Pushes a new coordinator on the navigation stack
/// - Parameters:
/// - coordinator: the coordinator to be displayed
/// - dismissalCallback: called when the coordinator has been popped, programatically or otherwise
func push(_ coordinator: any CoordinatorProtocol, dismissalCallback: (() -> Void)? = nil) {
let module = NavigationModule(coordinator)

if let dismissalCallback {
dismissalCallbacks[module.id] = dismissalCallback
}

stackModules.append(module)
stackModules.append(NavigationModule(coordinator, dismissalCallback: dismissalCallback))
}

/// Pop all the coordinators from the stack, returning to the root coordinator
Expand Down Expand Up @@ -523,13 +483,7 @@ class StackScreenCoordinator: ObservableObject, CoordinatorProtocol, CustomStrin
return
}

let module = NavigationModule(coordinator)

if let dismissalCallback {
dismissalCallbacks[module.id] = dismissalCallback
}

sheetModule = module
sheetModule = NavigationModule(coordinator, dismissalCallback: dismissalCallback)
}

// MARK: - CoordinatorProtocol
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,11 @@
import SwiftUI

class SingleScreenCoordinator: ObservableObject, CoordinatorProtocol, CustomStringConvertible {
private var dismissalCallbacks = [UUID: () -> Void]()

@Published fileprivate var rootModule: NavigationModule? {
didSet {
if let oldValue {
oldValue.coordinator.stop()
oldValue.dismissalCallback?()
}

if let rootModule {
Expand All @@ -39,13 +38,13 @@ class SingleScreenCoordinator: ObservableObject, CoordinatorProtocol, CustomStri

/// Sets or replaces the presented coordinator
/// - Parameter coordinator: the coordinator to display
func setRootCoordinator(_ coordinator: (any CoordinatorProtocol)?) {
func setRootCoordinator(_ coordinator: (any CoordinatorProtocol)?, dismissalCallback: (() -> Void)? = nil) {
guard let coordinator else {
rootModule = nil
return
}

rootModule = NavigationModule(coordinator)
rootModule = NavigationModule(coordinator, dismissalCallback: dismissalCallback)
}

// MARK: - CoordinatorProtocol
Expand Down
72 changes: 72 additions & 0 deletions UnitTests/Sources/SingleScreenCoordinatorTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
//
// Copyright 2022 New Vector Ltd
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//

import XCTest

@testable import ElementX

@MainActor
class SingleScreenCoordinatorTests: XCTestCase {
private var singleScreenCoordinator: SingleScreenCoordinator!

override func setUp() {
singleScreenCoordinator = SingleScreenCoordinator()
}

func testRootChanges() {
XCTAssertNil(singleScreenCoordinator.rootCoordinator)

let firstRootCoordinator = SomeTestCoordinator()
singleScreenCoordinator.setRootCoordinator(firstRootCoordinator)

assertCoordinatorsEqual(firstRootCoordinator, singleScreenCoordinator.rootCoordinator)

let secondRootCoordinator = SomeTestCoordinator()
singleScreenCoordinator.setRootCoordinator(secondRootCoordinator)

assertCoordinatorsEqual(secondRootCoordinator, singleScreenCoordinator.rootCoordinator)
}

func testReplacementDismissalCallbacks() {
XCTAssertNil(singleScreenCoordinator.rootCoordinator)

let rootCoordinator = SomeTestCoordinator()

let expectation = expectation(description: "Wait for callback")
singleScreenCoordinator.setRootCoordinator(rootCoordinator) {
expectation.fulfill()
}

singleScreenCoordinator.setRootCoordinator(nil)
waitForExpectations(timeout: 1.0)
}

// MARK: - Private

private func assertCoordinatorsEqual(_ lhs: CoordinatorProtocol?, _ rhs: CoordinatorProtocol?) {
guard let lhs = lhs as? SomeTestCoordinator,
let rhs = rhs as? SomeTestCoordinator else {
XCTFail("Coordinators are not the same")
return
}

XCTAssertEqual(lhs.id, rhs.id)
}
}

private struct SomeTestCoordinator: CoordinatorProtocol {
let id = UUID()
}

0 comments on commit 826c19c

Please sign in to comment.