| // Copyright 2018-present the Material Components for iOS authors. All Rights Reserved. | |
| // | |
| // 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 | |
| import MaterialComponents.MaterialAppBar | |
| private class MockAppBarNavigationControllerDelegate: | |
| NSObject, MDCAppBarNavigationControllerDelegate { | |
| var trackingScrollView: UIScrollView? | |
| func appBarNavigationController(_ navigationController: MDCAppBarNavigationController, | |
| trackingScrollViewFor trackingScrollViewForViewController: UIViewController, | |
| suggestedTrackingScrollView: UIScrollView?) -> UIScrollView? { | |
| return trackingScrollView | |
| } | |
| } | |
| class AppBarNavigationControllerTests: XCTestCase { | |
| var navigationController: MDCAppBarNavigationController! | |
| override func setUp() { | |
| super.setUp() | |
| navigationController = MDCAppBarNavigationController() | |
| } | |
| override func tearDown() { | |
| navigationController = nil | |
| super.tearDown() | |
| } | |
| // MARK: - AppBar injection | |
| func testInitializingWithRootViewControllerInjectsAnAppBar() { | |
| // Given | |
| let viewController = UIViewController() | |
| // When | |
| let navigationController = MDCAppBarNavigationController(rootViewController: viewController) | |
| // Then | |
| XCTAssertEqual(viewController.children.count, 1, | |
| "Expected there to be exactly one child view controller added to the view" | |
| + " controller.") | |
| XCTAssertEqual(navigationController.topViewController, viewController, | |
| "The navigation controller's top view controller is supposed to be the pushed" | |
| + " view controller, but it is \(viewController).") | |
| XCTAssertTrue(viewController.children.first is MDCFlexibleHeaderViewController, | |
| "The injected view controller is not a flexible header view controller, it is" | |
| + "\(String(describing: viewController.children.first)) instead.") | |
| if let headerViewController | |
| = viewController.children.first as? MDCFlexibleHeaderViewController { | |
| XCTAssertEqual(headerViewController.headerView.frame.height, | |
| headerViewController.headerView.maximumHeight) | |
| } | |
| } | |
| func testPushingAViewControllerInjectsAnAppBar() { | |
| // Given | |
| let viewController = UIViewController() | |
| // When | |
| navigationController.pushViewController(viewController, animated: false) | |
| // Then | |
| XCTAssertEqual(viewController.children.count, 1, | |
| "Expected there to be exactly one child view controller added to the view" | |
| + " controller.") | |
| XCTAssertEqual(navigationController.topViewController, viewController, | |
| "The navigation controller's top view controller is supposed to be the pushed" | |
| + " view controller, but it is \(viewController).") | |
| XCTAssertTrue(viewController.children.first is MDCFlexibleHeaderViewController, | |
| "The injected view controller is not a flexible header view controller, it is" | |
| + "\(String(describing: viewController.children.first)) instead.") | |
| if let headerViewController | |
| = viewController.children.first as? MDCFlexibleHeaderViewController { | |
| XCTAssertEqual(headerViewController.headerView.frame.height, | |
| headerViewController.headerView.maximumHeight) | |
| } | |
| } | |
| func testPushingAnAppBarContainerViewControllerDoesNotInjectAnAppBar() { | |
| // Given | |
| let viewController = UIViewController() | |
| let container = MDCAppBarContainerViewController(contentViewController: viewController) | |
| // When | |
| navigationController.pushViewController(container, animated: false) | |
| // Then | |
| XCTAssertEqual(container.children.count, 2, | |
| "An App Bar container view controller should have exactly two child view" | |
| + " controllers. A failure of this assertion implies that the navigation" | |
| + " controller may have injected another App Bar.") | |
| } | |
| func testPushingAContainedAppBarContainerViewControllerDoesNotInjectAnAppBar() { | |
| // Given | |
| let viewController = UIViewController() | |
| let container = MDCAppBarContainerViewController(contentViewController: viewController) | |
| let nestedContainer = UIViewController() | |
| nestedContainer.addChild(container) | |
| nestedContainer.view.addSubview(container.view) | |
| container.didMove(toParent: nestedContainer) | |
| // When | |
| navigationController.pushViewController(nestedContainer, animated: false) | |
| // Then | |
| XCTAssertEqual(nestedContainer.children.count, 1, | |
| "The view controller hierarchy already has one app bar view controller, but it" | |
| + " appears to have possibly added another.") | |
| } | |
| func testPushingAViewControllerWithAFlexibleHeaderDoesNotInjectAnAppBar() { | |
| // Given | |
| let viewController = UIViewController() | |
| let fhvc = MDCFlexibleHeaderViewController() | |
| viewController.addChild(fhvc) | |
| viewController.view.addSubview(fhvc.view) | |
| fhvc.didMove(toParent: viewController) | |
| // When | |
| navigationController.pushViewController(viewController, animated: false) | |
| // Then | |
| XCTAssertEqual(viewController.children.count, 1, | |
| "The navigation controller may have injected another App Bar when it shouldn't" | |
| + " have.") | |
| } | |
| // MARK: - traitCollectionDidChangeBlock support | |
| func testInitializingWithRootViewControllerDoesNotSetTraitCollectionDidChangeBlock() { | |
| // Given | |
| let viewController = UIViewController() | |
| // When | |
| let _ = MDCAppBarNavigationController(rootViewController: viewController) | |
| // Then | |
| let injectedAppBarViewController = viewController.children.first as! MDCAppBarViewController | |
| XCTAssertNotNil(injectedAppBarViewController) | |
| XCTAssertNil(injectedAppBarViewController.traitCollectionDidChangeBlock) | |
| } | |
| func testPushingAViewControllerAssignsTraitCollectionDidChangeBlock() { | |
| // Given | |
| let viewController = UIViewController() | |
| let block: ((MDCFlexibleHeaderViewController, UITraitCollection?) -> Void)? = {_, _ in } | |
| navigationController.traitCollectionDidChangeBlockForAppBarController = block | |
| // When | |
| navigationController.pushViewController(viewController, animated: false) | |
| // Then | |
| let injectedAppBarViewController = viewController.children.first as! MDCAppBarViewController | |
| XCTAssertNotNil(injectedAppBarViewController) | |
| XCTAssertNotNil(injectedAppBarViewController.traitCollectionDidChangeBlock) | |
| } | |
| func testPushingAnAppBarContainerViewControllerDoesNotAssignTraitCollectionDidChangeBlock() { | |
| // Given | |
| let viewController = UIViewController() | |
| let container = MDCAppBarContainerViewController(contentViewController: viewController) | |
| var blockSemaphore = false | |
| let block: ((MDCFlexibleHeaderViewController, UITraitCollection?) -> Void)? = | |
| { _, _ in | |
| blockSemaphore = true | |
| } | |
| container.appBarViewController.traitCollectionDidChangeBlock = block | |
| // When | |
| navigationController.pushViewController(container, animated: false) | |
| container.appBarViewController.traitCollectionDidChange(nil) | |
| // Then | |
| XCTAssertTrue(blockSemaphore) | |
| } | |
| func testPushingAContainedAppBarContainerViewControllerDoesNotAssignTraitCollectionDidChangeBlock() { | |
| // Given | |
| let viewController = UIViewController() | |
| let container = MDCAppBarContainerViewController(contentViewController: viewController) | |
| let nestedContainer = UIViewController() | |
| nestedContainer.addChild(container) | |
| nestedContainer.view.addSubview(container.view) | |
| container.didMove(toParent: nestedContainer) | |
| var blockSemaphore = false | |
| let block: ((MDCFlexibleHeaderViewController, UITraitCollection?) -> Void)? = | |
| { _, _ in | |
| blockSemaphore = true | |
| } | |
| container.appBarViewController.traitCollectionDidChangeBlock = block | |
| // When | |
| navigationController.pushViewController(nestedContainer, animated: false) | |
| container.appBarViewController.traitCollectionDidChange(nil) | |
| // Then | |
| XCTAssertTrue(blockSemaphore) | |
| } | |
| // MARK: - The rest | |
| func testStatusBarStyleIsFetchedFromFlexibleHeaderViewController() { | |
| // Given | |
| let viewController = UIViewController() | |
| // When | |
| navigationController.pushViewController(viewController, animated: false) | |
| // Then | |
| let appBar = navigationController.appBar(for: viewController) | |
| XCTAssertNotNil(appBar, "Could not retrieve the injected App Bar.") | |
| if let appBar = appBar { | |
| XCTAssertEqual(navigationController.childForStatusBarStyle, | |
| appBar.headerViewController, | |
| "The navigation controller should be using the injected app bar's flexible" | |
| + "header view controller for status bar style updates.") | |
| } | |
| } | |
| func testInfersFirstTrackingScrollViewByDefault() { | |
| // Given | |
| let viewController = UIViewController() | |
| let scrollView1 = UIScrollView() | |
| viewController.view.addSubview(scrollView1) | |
| let scrollView2 = UIScrollView() | |
| viewController.view.addSubview(scrollView2) | |
| // When | |
| navigationController.pushViewController(viewController, animated: false) | |
| // Then | |
| guard let appBarViewController = navigationController.appBarViewController(for: viewController) else { | |
| XCTFail("No app bar view controller found.") | |
| return | |
| } | |
| XCTAssertEqual(appBarViewController.headerView.trackingScrollView, scrollView1) | |
| } | |
| func testDelegateCanReturnNilTrackingScrollView() { | |
| // Given | |
| let viewController = UIViewController() | |
| let scrollView1 = UIScrollView() | |
| viewController.view.addSubview(scrollView1) | |
| let scrollView2 = UIScrollView() | |
| viewController.view.addSubview(scrollView2) | |
| let delegate = MockAppBarNavigationControllerDelegate() | |
| navigationController.delegate = delegate | |
| // When | |
| delegate.trackingScrollView = nil | |
| navigationController.pushViewController(viewController, animated: false) | |
| // Then | |
| guard let appBarViewController = navigationController.appBarViewController(for: viewController) else { | |
| XCTFail("No app bar view controller found.") | |
| return | |
| } | |
| XCTAssertNil(appBarViewController.headerView.trackingScrollView) | |
| } | |
| func testDelegateCanPickDifferentTrackingScrollView() { | |
| // Given | |
| let viewController = UIViewController() | |
| let scrollView1 = UIScrollView() | |
| viewController.view.addSubview(scrollView1) | |
| let scrollView2 = UIScrollView() | |
| viewController.view.addSubview(scrollView2) | |
| let delegate = MockAppBarNavigationControllerDelegate() | |
| navigationController.delegate = delegate | |
| // When | |
| delegate.trackingScrollView = scrollView2 | |
| navigationController.pushViewController(viewController, animated: false) | |
| // Then | |
| guard let appBarViewController = navigationController.appBarViewController(for: viewController) else { | |
| XCTFail("No app bar view controller found.") | |
| return | |
| } | |
| XCTAssertEqual(appBarViewController.headerView.trackingScrollView, scrollView2) | |
| } | |
| } |