Skip to content

Navigator

ohitsdaniel edited this page Apr 29, 2021 · 8 revisions

Navigator

Facade type erasing the type of the underlying datasource

public struct Navigator 

Nested Type Aliases

DismissSuccessorInvocation

Testing helper

typealias DismissSuccessorInvocation = DismissInvocation

Example

 var invocations = [Navigator.DismissInvocation]()
 let expectectedInvocations = [
   Navigator.DismissInvocation(id: .id(expectedID))
 ]

 let sut = Navigator.mock(
   path: { self.path },
   dismissSuccessor: { id in
     invocations.append(.init(id: id))
   }
 )

 sut.dismissSuccessor(of: expectedID) // invoke code that invokes dismissSuccessor(of:)

 XCTAssertEqual(expectectedInvocations, invocations)

Initializers

init(path:go:goToOnScreen:goToPath:goToPathOnScreen:goBack:goBackToID:replace:dismiss:dismissScreen:dismissSuccessor:dismissSuccessorOfScreen:replaceContent:replaceScreen:didAppear:)

public init(
    path: @escaping () -> PathUpdate,
    go: @escaping (AnyScreen, ScreenID) -> Void,
    goToOnScreen: @escaping (AnyScreen, AnyScreen) -> Void,
    goToPath: @escaping ([AnyScreen], ScreenID) -> Void,
    goToPathOnScreen: @escaping ([AnyScreen], AnyScreen) -> Void,
    goBack: @escaping (AnyScreen) -> Void,
    goBackToID: @escaping (ScreenID) -> Void,
    replace: @escaping ([AnyScreen]) -> Void,
    dismiss: @escaping (ScreenID) -> Void,
    dismissScreen: @escaping (AnyScreen) -> Void,
    dismissSuccessor: @escaping (ScreenID) -> Void,
    dismissSuccessorOfScreen: @escaping (AnyScreen) -> Void,
    replaceContent: @escaping (ScreenID, AnyScreen) -> Void,
    replaceScreen: @escaping (AnyScreen, AnyScreen) -> Void,
    didAppear: @escaping (ScreenID) -> Void
  ) 

init(dataSource:)

Initialises a Navigator wrapping a Datasource object

init(dataSource: Navigator.Datasource) 

Parameters

  • dataSource: The wrapped data source

Properties

stub

static let stub 

Methods

debug(log:dumpPath:)

Enable logging received function calls and path changes.

func debug(
    log: @escaping (String) -> Void = { print($0) },
    dumpPath: @escaping (PathUpdate) -> Void = { dump($0) }
  ) -> Navigator 

mock(path:go:goToOnScreen:goToPath:goToPathOnScreen:goBack:goBackToID:replace:dismiss:dismissScreen:dismissSuccessor:dismissSuccessorOfScreen:replaceContent:replaceScreen:didAppear:)

static func mock(
    path: @escaping () -> PathUpdate = {
      fatalError("path() unimplemented in stub. Make sure to wrap your application in a Root view or inject Navigator via .environment(\\.navigator, navigator) for testing purposes.")
    },
    go: @escaping (AnyScreen, ScreenID) -> Void = { _, _ in
      fatalError("go(to:) unimplemented in stub. Make sure to wrap your application in a Root view or inject Navigator via .environment(\\.navigator, navigator) for testing purposes.")
    },
    goToOnScreen: @escaping (AnyScreen, AnyScreen) -> Void = { _, _ in
      fatalError("go(to:, on screen:) unimplemented in stub. Make sure to wrap your application in a Root view or inject Navigator via .environment(\\.navigator, navigator) for testing purposes.")
    },
    goToPath: @escaping ([AnyScreen], ScreenID) -> Void = { _, _ in
      fatalError("goTo(path:, to:) unimplemented in stub. Make sure to wrap your application in a Root view or inject Navigator via .environment(\\.navigator, navigator) for testing purposes.")
    },
    goToPathOnScreen: @escaping ([AnyScreen], AnyScreen) -> Void = { _, _ in
      fatalError("goTo(path:, to:) unimplemented in stub. Make sure to wrap your application in a Root view or inject Navigator via .environment(\\.navigator, navigator) for testing purposes.")
    },
    goBack: @escaping (AnyScreen) -> Void = { _ in
      fatalError("goBack(to:) unimplemented in stub. Make sure to wrap your application in a Root view or inject Navigator via .environment(\\.navigator, navigator) for testing purposes.")
    },
    goBackToID: @escaping (ScreenID) -> Void = { _ in
      fatalError("goBack(to:) unimplemented in stub. Make sure to wrap your application in a Root view or inject Navigator via .environment(\\.navigator, navigator) for testing purposes.")
    },
    replace: @escaping ([AnyScreen]) -> Void = { _ in
      fatalError("replace(path:) unimplemented in stub. Make sure to wrap your application in a Root view or inject Navigator via .environment(\\.navigator, navigator) for testing purposes.")
    },
    dismiss: @escaping (ScreenID) -> Void = { _ in
      fatalError("dismiss(id:) unimplemented in stub. Make sure to wrap your application in a Root view or inject Navigator via .environment(\\.navigator, navigator) for testing purposes.")
    },
    dismissScreen: @escaping (AnyScreen) -> Void = { _ in
      fatalError("dismiss(screen:) unimplemented in stub. Make sure to wrap your application in a Root view or inject Navigator via .environment(\\.navigator, navigator) for testing purposes.")
    },
    dismissSuccessor: @escaping (ScreenID) -> Void = { _ in
      fatalError("dismissSuccessor(of:) unimplemented in stub. Make sure to wrap your application in a Root view or inject Navigator via .environment(\\.navigator, navigator) for testing purposes.")
    },
    dismissSuccessorOfScreen: @escaping (AnyScreen) -> Void = { _ in
      fatalError("dismissSuccessor(of:) unimplemented in stub. Make sure to wrap your application in a Root view or inject Navigator via .environment(\\.navigator, navigator) for testing purposes.")
    },
    replaceContent: @escaping (ScreenID, AnyScreen) -> Void = { _, _ in
        fatalError("replaceContent(of id:, with:) unimplemented in stub. Make sure to wrap your application in a Root view or inject Navigator via .environment(\\.navigator, navigator) for testing purposes.")
    },
    replaceScreen: @escaping (AnyScreen, AnyScreen) -> Void = { _, _ in
      fatalError("replace(screen:, with:) unimplemented in stub. Make sure to wrap your application in a Root view or inject Navigator via .environment(\\.navigator, navigator) for testing purposes.")
    },
    didAppear: @escaping (ScreenID) -> Void = { _ in
      fatalError("didAppear(id:) unimplemented in stub. Make sure to wrap your application in a Root view or inject Navigator via .environment(\\.navigator, navigator) for testing purposes.")
    }
  ) -> Navigator 

mock(path:goToInvoked:goToPathInvoked:goBackToInvoked:replacePathInvoked:dismissInvoked:dismissSuccessorInvoked:replaceContentInvoked:replaceScreenInvoked:didAppearInvoked:)

static func mock(
    path: @escaping () -> PathUpdate = {
      fatalError("path() unimplemented in stub. Make sure to wrap your application in a Root view or inject Navigator via .environment(\\.navigator, navigator) for testing purposes.")
    },
    goToInvoked: @escaping (Navigator.GoToInvocation) -> Void = { _ in
      fatalError("go(to screen:) unimplemented in stub. Make sure to wrap your application in a Root view or inject Navigator via .environment(\\.navigator, navigator) for testing purposes.")
    },
    goToPathInvoked: @escaping (Navigator.GoToPathInvocation) -> Void = { _ in
      fatalError("go(to path:) unimplemented in stub. Make sure to wrap your application in a Root view or inject Navigator via .environment(\\.navigator, navigator) for testing purposes.")
    },
    goBackToInvoked: @escaping (Navigator.GoBackToInvocation) -> Void = { _ in
      fatalError("goBack(to:) unimplemented in stub. Make sure to wrap your application in a Root view or inject Navigator via .environment(\\.navigator, navigator) for testing purposes.")
    },
    replacePathInvoked: @escaping (Navigator.ReplacePathInvocation) -> Void = { _ in
      fatalError("replace(path:) unimplemented in stub. Make sure to wrap your application in a Root view or inject Navigator via .environment(\\.navigator, navigator) for testing purposes.")
    },
    dismissInvoked: @escaping (Navigator.DismissInvocation) -> Void = { _ in
      fatalError("dismiss(id:) unimplemented in stub. Make sure to wrap your application in a Root view or inject Navigator via .environment(\\.navigator, navigator) for testing purposes.")
    },
    dismissSuccessorInvoked: @escaping (Navigator.DismissSuccessorInvocation) -> Void = { _ in
      fatalError("dismissSuccessor(of:) unimplemented in stub. Make sure to wrap your application in a Root view or inject Navigator via .environment(\\.navigator, navigator) for testing purposes.")
    },
    replaceContentInvoked: @escaping (Navigator.ReplaceContentInvocation) -> Void = { _ in
        fatalError("replaceContent(of id:, with:) unimplemented in stub. Make sure to wrap your application in a Root view or inject Navigator via .environment(\\.navigator, navigator) for testing purposes.")
    },
    replaceScreenInvoked: @escaping (Navigator.ReplaceScreenInvocation) -> Void = { _ in
      fatalError("replace(screen:, with:) unimplemented in stub. Make sure to wrap your application in a Root view or inject Navigator via .environment(\\.navigator, navigator) for testing purposes.")
    },
    didAppearInvoked: @escaping (Navigator.DidAppearInvocation) -> Void = { _ in
      fatalError("didAppear(id:) unimplemented in stub. Make sure to wrap your application in a Root view or inject Navigator via .environment(\\.navigator, navigator) for testing purposes.")
    }
  ) -> Navigator 

go(to:on:)

Append a screen after a given ScreenID.

public func go<S: Screen>(to screen: S, on id: ScreenID) 

go(to:, on:) appends the given screen after the screen associated with the passed ScreenID. If you call go(to:, on:) for a ScreenID that is not associated with the last screen in the current navigation path, the navigation path after the ScreenID is replaced with [screen] and therefore cut off.

Example

// Curent path [(Content, ID)]
// [(A, 0), (B, 1)]
navigator.go(to: C(), on: 1)
// New path
// [(A, 0), (B, 1)], (C, 2)]

Parameters

  • screen: Destination
  • id: ScreenID used to identify where the destination should be appended

go(to:on:)

Append a screen after a given Screen.

public func go<S: Screen, Parent: Screen>(to screen: S, on parent: Parent) 

go(to:, on:) appends the given screen after the last occurrence of the passed Parent screen object. If you call go(to:, on:) for a Screen that is not associated with the last screen in the current navigation path, the navigation path after the Parent is replaced with [screen] and therefore cut off.

Example

// Curent path [(Content, ID)]
// [(A, 0), (B, 1)]
navigator.go(to: C(), on: B())
// New path
// [(A, 0), (B, 1)], (C, 2)]

Parameters

  • screen: Destination
  • parent: Parent screen object used to identify where the destination should be appended

go(to:on:)

Replace the path after a given ScreenID with the passed path.

public func go(to path: [AnyScreen], on id: ScreenID) 

go(to:, on:) appends the given path after the screen associated with the passed ScreenID. If you call go(to:, on:) for a ScreenID that is not associated with the last screen in the current navigation path, the navigation path after the ScreenID is replaced with path and potentially cut off.

Example

// Curent path [(Content, ID)]
// [(A, 0), (B, 1)]

navigator.go(
 to: [C().eraseToAnyScreen(), D().eraseToAnyScreen()],
 on: 0
)

// New path
// [(A, 0), (C, 2), (D, 3)]

Parameters

  • path: New path after id
  • id: ScreenID used to identify where the path should be appended

go(to:on:)

Replace the path after the last occurrence of a given Parent with the passed path.

public func go<Parent: Screen>(to path: [AnyScreen], on parent: Parent) 

go(to:, on:) appends the given path after the last occurrence of the passed Parent Screen object. If you call go(to:, on:) for a Parent screen that is not associated with the last screen in the current navigation path, the navigation path after the ScreenID is replaced with path and potentially cut off.

Example

// Curent path [(Content, ID)]
// [(A, 0), (B, 1)]

navigator.go(
 to: [C().eraseToAnyScreen(), D().eraseToAnyScreen()],
 on: A()
)

// New path
// [(A, 0), (C, 2), (D, 3)]

Parameters

  • path: New path after Parent
  • parent: Screen used to identify where the path should be appended

goBack(to:)

Go back to the last occurrence of the screen instance in the navigation path.

public func goBack<S: Screen>(to screen: S) 

goBack(to:) trims the navigation path to up to the last occurrence of the passed screen.

Example

// Curent path [(Content, ID)]
// [(A, 0), (B, 1), (C, 2)]

navigator.goBack(to: A())

// New path
// [(A, 0)]

Parameters

  • screen: Destination

goBack(to:)

Go back to the specified ScreenID in the navigation path.

public func goBack(to id: ScreenID) 

goBack(to:) trims the navigation path to up to the last occurrence of the passed screen.

Example

// Curent path [(Content, ID)]
// [(A, 0), (B, 1), (C, 2)]

navigator.goBack(to: 0)

// New path
// [(A, 0)]

Parameters

  • id: Destination ID

replace(path:)

Replace the current navigation path with a new navigation path.

public func replace(path: AnyScreen...) 

Example

// Curent path [(Content, ID)]
// [(A, 0), (B, 1)]

navigator.replace(
  path: C().eraseToAnyScreen(), D().eraseToAnyScreen()
)

// New path
// [(C, 0), (D, 1)]

Parameters

  • path: The new navigation path

replace(path:)

Replace the current navigation path with a new navigation path.

public func replace(path: [AnyScreen]) 

replace(path:) checks if a prefix of the new path was already part of the replaced navigation path and makes sure to keep the IDs and hasAppeared state intact.

Example

// Curent path [(Content, ID)]
// [(A, 0), (B, 1)]

navigator.replace(
  path: [
    C().eraseToAnyScreen(),
    D().eraseToAnyScreen()
  ]
)

// New path
// [(C, 0), (D, 1)]

Parameters

  • path: The new navigation path

dismiss(id:)

Removes the screen associated with the passed screenID from the navigation path.

public func dismiss(id: ScreenID) 

dismiss(id:) does not care take the screen's presentation style into account and cuts the navigation path up to the passed id.

Example

// Curent path [(Content, ID)]
// [(A, 0), (B, 1), (C, 2), (D,3)]

navigator.dismiss(id: 2)

// New path
// [(A, 0), (B, 1)]

Parameters

  • id: The id identifying the screen that needs to be dismissed

dismiss(screen:)

Removes the last occurrence screen from the navigation path.

public func dismiss<S: Screen>(screen: S) 

dismiss(screen:) does not care take the screen's presentation style into account and cuts the navigation path up to the passed id.

Example

// Curent path [(Content, ID)]
// [(A, 0), (B, 1), (C, 2), (D,3)]

navigator.dismiss(screen: C())

// New path
// [(A, 0), (B, 1)]

Parameters

  • screen: The screen that needs to be dismissed

dismissSuccessor(of:)

Removes the screen successors from the navigation path.

public func dismissSuccessor(of id: ScreenID) 

dismissSuccessor(of id:) does not care take the screen's presentation style into account and cuts the navigation path after the passed id.

Example

// Curent path [(Content, ID)]
// [(A, 0), (B, 1), (C, 2), (D,3)]

navigator.dismissSuccessor(of id: 2)

// New path
// [(A, 0), (B, 1), (C, 2)]

Parameters

  • id: The id identifying the screen that needs to be dismissed

dismissSuccessor(of:)

Removes successors of the last occurrence of the passed screen from the navigation path.

public func dismissSuccessor<S: Screen>(of screen: S) 

dismissSuccessor(of screen:) does not care take the screen's presentation style into account and cuts the navigation path after the passed id.

Example

// Curent path [(Content, ID)]
// [(A, 0), (B, 1), (C, 2), (D,3)]

navigator.dismissSuccessor(of: C())

// New path
// [(A, 0), (B, 1), (C, 2)]

Parameters

  • screen: The screen that needs to be dismissed

replaceContent(of:with:)

Replace the Screen associated with an id with a new Screen

public func replaceContent<NewContent: Screen>(of id: ScreenID, with newContent: NewContent) 

replaceContent(of id:, with:) replaces the content associated with the passed ScreenID and will not assign a new ScreenID to the changed path element.

Example

// Curent path [(Content, ID)]
// [(A, 0), (B, 1), (C, 2), (D,3)]

navigator.replaceContent(of: 0, with: E())

// New path
// [(E, 0), (B, 1), (C, 2), (D,3)]

Parameters

  • id: ScreenID used to identify the screen that needs to be replaced
  • newContent: The new screen that will replace the screen associated with the passed ScreenID

replace(screen:with:)

Replace the last occurence of a Screen with a new Screen

public func replace<OldContent: Screen, NewContent: Screen>(
    screen: OldContent,
    with newContent: NewContent
  ) 

replace(screen:, with:) replaces the last occurence of the passed Screen with the passed newContent Screen and will not assign a new ScreenID to the changed path element.

Example

// Curent path [(Content, ID)]
// [(A, 0), (B, 1), (C, 2), (D,3)]

navigator.replace(screen: A(), with: E())

// New path
// [(E, 0), (B, 1), (C, 2), (D,3)]

Parameters

  • screen: Screen getting replaced
  • newContent: The new screen that will replace the screen associated with the passed ScreenID
Clone this wiki locally