From da5249a2fc9abf888d0dd628c07d95217eb754a3 Mon Sep 17 00:00:00 2001 From: Fernando Olivares Date: Thu, 19 Sep 2024 11:23:32 -0600 Subject: [PATCH 1/2] Update refresh with last visitable --- .../NavigationHierarchyController.swift | 15 +++-- ...avigationHierarchyControllerDelegate.swift | 22 +++++-- Source/Turbo/Navigator/Navigator.swift | 10 ++-- .../NavigationHierarchyControllerTests.swift | 57 ++++++++++++++++++- 4 files changed, 91 insertions(+), 13 deletions(-) diff --git a/Source/Turbo/Navigator/NavigationHierarchyController.swift b/Source/Turbo/Navigator/NavigationHierarchyController.swift index 072fe0e..dcc7e84 100644 --- a/Source/Turbo/Navigator/NavigationHierarchyController.swift +++ b/Source/Turbo/Navigator/NavigationHierarchyController.swift @@ -73,9 +73,9 @@ class NavigationHierarchyController { } func clearAll(animated: Bool) { - delegate.refresh(navigationStack: .main) navigationController.dismiss(animated: animated) navigationController.popToRootViewController(animated: animated) + refreshIfTopViewControllerIsVisitable(from: .main) } // MARK: Private @@ -173,15 +173,15 @@ class NavigationHierarchyController { private func refresh(via proposal: VisitProposal) { if navigationController.presentedViewController != nil { if modalNavigationController.viewControllers.count == 1 { - delegate.refresh(navigationStack: .main) navigationController.dismiss(animated: proposal.animated) + refreshIfTopViewControllerIsVisitable(from: .main) } else { - delegate.refresh(navigationStack: .modal) modalNavigationController.popViewController(animated: proposal.animated) + refreshIfTopViewControllerIsVisitable(from: .modal) } } else { - delegate.refresh(navigationStack: .main) navigationController.popViewController(animated: proposal.animated) + refreshIfTopViewControllerIsVisitable(from: .main) } } @@ -193,4 +193,11 @@ class NavigationHierarchyController { navigationController.dismiss(animated: proposal.animated) navigationController.setViewControllers([controller], animated: proposal.animated) } + + private func refreshIfTopViewControllerIsVisitable(from stack: NavigationStackType) { + if let navControllerTopmostVisitable = navController(for: stack).topViewController as? Visitable { + delegate.refreshVisitable(navigationStack: stack, + newTopmostVisitable: navControllerTopmostVisitable) + } + } } diff --git a/Source/Turbo/Navigator/NavigationHierarchyControllerDelegate.swift b/Source/Turbo/Navigator/NavigationHierarchyControllerDelegate.swift index cc13b48..4cff23b 100644 --- a/Source/Turbo/Navigator/NavigationHierarchyControllerDelegate.swift +++ b/Source/Turbo/Navigator/NavigationHierarchyControllerDelegate.swift @@ -1,9 +1,23 @@ import SafariServices import WebKit -/// Implement to be notified when certain navigations are performed -/// or to render a native controller instead of a Turbo web visit. protocol NavigationHierarchyControllerDelegate: AnyObject { - func visit(_: Visitable, on: NavigationHierarchyController.NavigationStackType, with: VisitOptions) - func refresh(navigationStack: NavigationHierarchyController.NavigationStackType) + + /// Once the navigation hierarchy is modified, begin a visit on a navigation controller. + /// + /// - Parameters: + /// - _: the Visitable destination + /// - on: the navigation controller that was modified + /// - with: the visit options + func visit(_ : Visitable, + on: NavigationStackType, + with: VisitOptions) + + /// A refresh will pop (or dismiss) then ask the session to refresh the previous (or underlying) Visitable. + /// + /// - Parameters: + /// - navigationStack: the stack where the refresh is happening + /// - newTopmostVisitable: the visitable to be refreshed + func refreshVisitable(navigationStack: NavigationStackType, + newTopmostVisitable: Visitable) } diff --git a/Source/Turbo/Navigator/Navigator.swift b/Source/Turbo/Navigator/Navigator.swift index 519a955..b1ad268 100644 --- a/Source/Turbo/Navigator/Navigator.swift +++ b/Source/Turbo/Navigator/Navigator.swift @@ -223,11 +223,13 @@ extension Navigator: NavigationHierarchyControllerDelegate { case .modal: modalSession.visit(controller, options: options) } } - - func refresh(navigationStack: NavigationHierarchyController.NavigationStackType) { + + func refreshVisitable(navigationStack: <>, newTopmostVisitable: any Visitable) { switch navigationStack { - case .main: session.reload() - case .modal: modalSession.reload() + case .main: + session.visit(newTopmostVisitable, action: .restore) + case .modal: + modalSession.visit(newTopmostVisitable, action: .restore) } } } diff --git a/Tests/Turbo/Navigator/NavigationHierarchyControllerTests.swift b/Tests/Turbo/Navigator/NavigationHierarchyControllerTests.swift index 0df3d02..2fed5fa 100644 --- a/Tests/Turbo/Navigator/NavigationHierarchyControllerTests.swift +++ b/Tests/Turbo/Navigator/NavigationHierarchyControllerTests.swift @@ -82,6 +82,61 @@ final class NavigationHierarchyControllerTests: XCTestCase { XCTAssert(navigationController.viewControllers.last is VisitableViewController) assertVisited(url: proposal.url, on: .main) } + + func test_default_default_refresh_refreshesPreviousController() { + navigator.route(oneURL) + XCTAssertEqual(navigationController.viewControllers.count, 1) + + navigator.route(twoURL) + XCTAssertEqual(navigator.rootViewController.viewControllers.count, 2) + + /// Refreshing should pop the view controller and refresh the underlying controller. + let proposal = VisitProposal(presentation: .refresh) + navigator.route(proposal) + + let visitable = navigator.session.activeVisitable as! VisitableViewController + XCTAssertEqual(visitable.visitableURL, oneURL) + XCTAssertEqual(navigator.rootViewController.viewControllers.count, 1) + } + + func test_default_modal_refresh_refreshesPreviousController() { + navigationController.pushViewController(UIViewController(), animated: false) + XCTAssertEqual(navigationController.viewControllers.count, 1) + + let oneURLProposal = VisitProposal(path: "/one", context: .modal) + navigator.route(oneURLProposal) + + let twoURLProposal = VisitProposal(path: "/two", context: .modal) + navigator.route(twoURLProposal) + XCTAssertEqual(modalNavigationController.viewControllers.count, 2) + + /// Refreshing should pop the view controller and refresh the underlying controller. + let proposal = VisitProposal(presentation: .refresh) + navigator.route(proposal) + + let visitable = navigator.modalSession.activeVisitable as! VisitableViewController + XCTAssertEqual(visitable.visitableURL, oneURL) + XCTAssertEqual(modalNavigationController.viewControllers.count, 1) + } + + func test_default_modal_refresh_dismissesAndRefreshesMainStackTopViewController() { + navigator.route(oneURL) + XCTAssertEqual(navigationController.viewControllers.count, 1) + + let twoURLProposal = VisitProposal(path: "/two", context: .modal) + navigator.route(twoURLProposal) + XCTAssertEqual(modalNavigationController.viewControllers.count, 1) + + /// Refreshing should dismiss the view controller and refresh the underlying controller. + let proposal = VisitProposal(context: .modal, presentation: .refresh) + navigator.route(proposal) + + let visitable = navigator.session.activeVisitable as! VisitableViewController + XCTAssertEqual(visitable.visitableURL, oneURL) + + XCTAssertNil(navigationController.presentedViewController) + XCTAssertEqual(navigator.rootViewController.viewControllers.count, 1) + } func test_default_modal_default_presentsModal() { navigationController.pushViewController(UIViewController(), animated: false) @@ -309,7 +364,7 @@ final class NavigationHierarchyControllerTests: XCTestCase { private class EmptyNavigationDelegate: NavigationHierarchyControllerDelegate { func visit(_: Visitable, on: NavigationHierarchyController.NavigationStackType, with: VisitOptions) {} - func refresh(navigationStack: NavigationHierarchyController.NavigationStackType) {} + func refreshVisitable(navigationStack: NavigationHierarchyController.NavigationStackType, newTopmostVisitable: any Visitable) { } } // MARK: - VisitProposal extension From b3ba3cc2759f04ee9b41c71d88a049692602ca5b Mon Sep 17 00:00:00 2001 From: Fernando Olivares Date: Thu, 19 Sep 2024 16:23:19 -0600 Subject: [PATCH 2/2] Fix missing references --- .../Navigator/NavigationHierarchyControllerDelegate.swift | 4 ++-- Source/Turbo/Navigator/Navigator.swift | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Source/Turbo/Navigator/NavigationHierarchyControllerDelegate.swift b/Source/Turbo/Navigator/NavigationHierarchyControllerDelegate.swift index 4cff23b..813ff1d 100644 --- a/Source/Turbo/Navigator/NavigationHierarchyControllerDelegate.swift +++ b/Source/Turbo/Navigator/NavigationHierarchyControllerDelegate.swift @@ -10,7 +10,7 @@ protocol NavigationHierarchyControllerDelegate: AnyObject { /// - on: the navigation controller that was modified /// - with: the visit options func visit(_ : Visitable, - on: NavigationStackType, + on: NavigationHierarchyController.NavigationStackType, with: VisitOptions) /// A refresh will pop (or dismiss) then ask the session to refresh the previous (or underlying) Visitable. @@ -18,6 +18,6 @@ protocol NavigationHierarchyControllerDelegate: AnyObject { /// - Parameters: /// - navigationStack: the stack where the refresh is happening /// - newTopmostVisitable: the visitable to be refreshed - func refreshVisitable(navigationStack: NavigationStackType, + func refreshVisitable(navigationStack: NavigationHierarchyController.NavigationStackType, newTopmostVisitable: Visitable) } diff --git a/Source/Turbo/Navigator/Navigator.swift b/Source/Turbo/Navigator/Navigator.swift index b1ad268..3a5e635 100644 --- a/Source/Turbo/Navigator/Navigator.swift +++ b/Source/Turbo/Navigator/Navigator.swift @@ -224,7 +224,7 @@ extension Navigator: NavigationHierarchyControllerDelegate { } } - func refreshVisitable(navigationStack: <>, newTopmostVisitable: any Visitable) { + func refreshVisitable(navigationStack: NavigationHierarchyController.NavigationStackType, newTopmostVisitable: any Visitable) { switch navigationStack { case .main: session.visit(newTopmostVisitable, action: .restore)