Skip to content

Commit dc2b251

Browse files
add UIViewController lifecycle test and dispose used viewController (pointfreeco#308)
* add UIViewController lifecycle test and dispose used viewController * add #if os(iOS) * Apply suggestions from code review Co-authored-by: Stephen Celis <stephen.celis@gmail.com> Co-authored-by: Stephen Celis <stephen.celis@gmail.com> Co-authored-by: Stephen Celis <stephen@stephencelis.com>
1 parent 6910630 commit dc2b251

File tree

6 files changed

+97
-9
lines changed

6 files changed

+97
-9
lines changed

Sources/SnapshotTesting/Common/View.swift

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -670,7 +670,7 @@ func prepareView(
670670
traits: UITraitCollection,
671671
view: UIView,
672672
viewController: UIViewController
673-
) {
673+
) -> () -> Void {
674674
let size = config.size ?? viewController.view.frame.size
675675
view.frame.size = size
676676
if view != viewController.view {
@@ -691,14 +691,16 @@ func prepareView(
691691
viewController: viewController
692692
)
693693
}
694-
add(traits: traits, viewController: viewController, to: window)
694+
let dispose = add(traits: traits, viewController: viewController, to: window)
695695

696696
if size.width == 0 || size.height == 0 {
697697
// Try to call sizeToFit() if the view still has invalid size
698698
view.sizeToFit()
699699
view.setNeedsLayout()
700700
view.layoutIfNeeded()
701701
}
702+
703+
return dispose
702704
}
703705

704706
func snapshotView(
@@ -710,7 +712,7 @@ func snapshotView(
710712
)
711713
-> Async<UIImage> {
712714
let initialFrame = view.frame
713-
prepareView(
715+
let dispose = prepareView(
714716
config: config,
715717
drawHierarchyInKeyWindow: drawHierarchyInKeyWindow,
716718
traits: traits,
@@ -719,7 +721,8 @@ func snapshotView(
719721
)
720722
// NB: Avoid safe area influence.
721723
if config.safeArea == .zero { view.frame.origin = .init(x: offscreen, y: offscreen) }
722-
return view.snapshot ?? Async { callback in
724+
725+
return (view.snapshot ?? Async { callback in
723726
addImagesForRenderedViews(view).sequence().run { views in
724727
callback(
725728
renderer(bounds: view.bounds, for: traits).image { ctx in
@@ -733,7 +736,7 @@ func snapshotView(
733736
views.forEach { $0.removeFromSuperview() }
734737
view.frame = initialFrame
735738
}
736-
}
739+
}).map { dispose(); return $0 }
737740
}
738741

739742
private let offscreen: CGFloat = 10_000
@@ -748,7 +751,7 @@ func renderer(bounds: CGRect, for traits: UITraitCollection) -> UIGraphicsImageR
748751
return renderer
749752
}
750753

751-
private func add(traits: UITraitCollection, viewController: UIViewController, to window: UIWindow) {
754+
private func add(traits: UITraitCollection, viewController: UIViewController, to window: UIWindow) -> () -> Void {
752755
let rootViewController: UIViewController
753756
if viewController != window.rootViewController {
754757
rootViewController = UIViewController()
@@ -786,6 +789,12 @@ private func add(traits: UITraitCollection, viewController: UIViewController, to
786789

787790
viewController.view.setNeedsLayout()
788791
viewController.view.layoutIfNeeded()
792+
793+
return {
794+
rootViewController.beginAppearanceTransition(false, animated: false)
795+
rootViewController.endAppearanceTransition()
796+
window.rootViewController = nil
797+
}
789798
}
790799

791800
private final class Window: UIWindow {

Sources/SnapshotTesting/Snapshotting/UIView.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,13 +47,14 @@ extension Snapshotting where Value == UIView, Format == String {
4747
)
4848
-> Snapshotting<UIView, String> {
4949
return SimplySnapshotting.lines.pullback { view in
50-
prepareView(
50+
let dispose = prepareView(
5151
config: .init(safeArea: .zero, size: size ?? view.frame.size, traits: traits),
5252
drawHierarchyInKeyWindow: false,
5353
traits: .init(),
5454
view: view,
5555
viewController: .init()
5656
)
57+
defer { dispose() }
5758
return purgePointers(
5859
view.perform(Selector(("recursiveDescription"))).retain().takeUnretainedValue()
5960
as! String

Sources/SnapshotTesting/Snapshotting/UIViewController.swift

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,13 +64,14 @@ extension Snapshotting where Value == UIViewController, Format == String {
6464
/// A snapshot strategy for comparing view controllers based on their embedded controller hierarchy.
6565
public static var hierarchy: Snapshotting {
6666
return Snapshotting<String, String>.lines.pullback { viewController in
67-
prepareView(
67+
let dispose = prepareView(
6868
config: .init(),
6969
drawHierarchyInKeyWindow: false,
7070
traits: .init(),
7171
view: viewController.view,
7272
viewController: viewController
7373
)
74+
defer { dispose() }
7475
return purgePointers(
7576
viewController.perform(Selector(("_printHierarchy"))).retain().takeUnretainedValue() as! String
7677
)
@@ -95,13 +96,14 @@ extension Snapshotting where Value == UIViewController, Format == String {
9596
)
9697
-> Snapshotting<UIViewController, String> {
9798
return SimplySnapshotting.lines.pullback { viewController in
98-
prepareView(
99+
let dispose = prepareView(
99100
config: .init(safeArea: config.safeArea, size: size ?? config.size, traits: config.traits),
100101
drawHierarchyInKeyWindow: false,
101102
traits: traits,
102103
view: viewController.view,
103104
viewController: viewController
104105
)
106+
defer { dispose() }
105107
return purgePointers(
106108
viewController.view.perform(Selector(("recursiveDescription"))).retain().takeUnretainedValue()
107109
as! String

Tests/SnapshotTestingTests/SnapshotTestingTests.swift

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -789,6 +789,82 @@ final class SnapshotTestingTests: XCTestCase {
789789
#endif
790790
}
791791

792+
func testUIViewControllerLifeCycle() {
793+
#if os(iOS)
794+
class ViewController: UIViewController {
795+
let viewDidLoadExpectation: XCTestExpectation
796+
let viewWillAppearExpectation: XCTestExpectation
797+
let viewDidAppearExpectation: XCTestExpectation
798+
let viewWillDisappearExpectation: XCTestExpectation
799+
let viewDidDisappearExpectation: XCTestExpectation
800+
init(viewDidLoadExpectation: XCTestExpectation,
801+
viewWillAppearExpectation: XCTestExpectation,
802+
viewDidAppearExpectation: XCTestExpectation,
803+
viewWillDisappearExpectation: XCTestExpectation,
804+
viewDidDisappearExpectation: XCTestExpectation){
805+
self.viewDidLoadExpectation = viewDidLoadExpectation
806+
self.viewWillAppearExpectation = viewWillAppearExpectation
807+
self.viewDidAppearExpectation = viewDidAppearExpectation
808+
self.viewWillDisappearExpectation = viewWillDisappearExpectation
809+
self.viewDidDisappearExpectation = viewDidDisappearExpectation
810+
super.init(nibName: nil, bundle: nil)
811+
}
812+
required init?(coder: NSCoder) {
813+
fatalError("init(coder:) has not been implemented")
814+
}
815+
override func viewDidLoad() {
816+
super.viewDidLoad()
817+
viewDidLoadExpectation.fulfill()
818+
}
819+
override func viewWillAppear(_ animated: Bool) {
820+
super.viewWillAppear(animated)
821+
viewWillAppearExpectation.fulfill()
822+
}
823+
override func viewDidAppear(_ animated: Bool) {
824+
super.viewDidAppear(animated)
825+
viewDidAppearExpectation.fulfill()
826+
}
827+
override func viewWillDisappear(_ animated: Bool) {
828+
super.viewWillDisappear(animated)
829+
viewWillDisappearExpectation.fulfill()
830+
}
831+
override func viewDidDisappear(_ animated: Bool) {
832+
super.viewDidDisappear(animated)
833+
viewDidDisappearExpectation.fulfill()
834+
}
835+
}
836+
837+
let viewDidLoadExpectation = expectation(description: "viewDidLoad")
838+
let viewWillAppearExpectation = expectation(description: "viewWillAppear")
839+
let viewDidAppearExpectation = expectation(description: "viewDidAppear")
840+
let viewWillDisappearExpectation = expectation(description: "viewWillDisappear")
841+
let viewDidDisappearExpectation = expectation(description: "viewDidDisappear")
842+
viewWillAppearExpectation.expectedFulfillmentCount = 4
843+
viewDidAppearExpectation.expectedFulfillmentCount = 4
844+
viewWillDisappearExpectation.expectedFulfillmentCount = 4
845+
viewDidDisappearExpectation.expectedFulfillmentCount = 4
846+
847+
let viewController = ViewController(
848+
viewDidLoadExpectation: viewDidLoadExpectation,
849+
viewWillAppearExpectation: viewWillAppearExpectation,
850+
viewDidAppearExpectation: viewDidAppearExpectation,
851+
viewWillDisappearExpectation: viewWillDisappearExpectation,
852+
viewDidDisappearExpectation: viewDidDisappearExpectation
853+
)
854+
855+
assertSnapshot(matching: viewController, as: .image)
856+
assertSnapshot(matching: viewController, as: .image)
857+
858+
wait(for: [
859+
viewDidLoadExpectation,
860+
viewWillAppearExpectation,
861+
viewDidAppearExpectation,
862+
viewWillDisappearExpectation,
863+
viewDidDisappearExpectation,
864+
], timeout: 1.0, enforceOrder: true)
865+
#endif
866+
}
867+
792868
func testCALayer() {
793869
#if os(iOS)
794870
let layer = CALayer()
31.9 KB
Loading
32 KB
Loading

0 commit comments

Comments
 (0)