Update the unit test to use the latest version of ViewInspector, which allows UIView/NSView inspection

This commit is contained in:
DreamPiggy 2020-02-04 11:04:55 +08:00
parent 53de385099
commit f193c612b1
8 changed files with 113 additions and 164 deletions

View File

@ -355,8 +355,7 @@ SDWebImageSwiftUI has Unit Test to increase code quality. For SwiftUI, there are
However, since SwiftUI is State-Based and Attributed-Implemented layout system, there are open source projects who provide the solution:
+ [ViewInspector](https://github.com/nalexn/ViewInspector): Inspect View's runtime attribute value (like `.frame` modifier, `.image` value). We use this to test `AnimatedImage` and `WebImage`
+ [SwiftUI-Introspect](https://github.com/siteline/SwiftUI-Introspect): Introspect the native UIKit/AppKit View, even for SwiftUI component (like `List`, which is actually `UITableView` in implementation). We use this to test `AnimatedImage`
+ [ViewInspector](https://github.com/nalexn/ViewInspector): Inspect View's runtime attribute value (like `.frame` modifier, `.image` value). We use this to test `AnimatedImage` and `WebImage`. It also allows the inspect to native UIView/NSView, which we use to test `ActivityIndicator` and `ProgressIndicator`.
To run the test:

View File

@ -21,9 +21,6 @@
321C1D5D23DEC222009CF62A /* Images.bundle in Resources */ = {isa = PBXBuildFile; fileRef = 3211F85423DE9D2700FC757F /* Images.bundle */; };
321C1D5E23DEC22D009CF62A /* SDWebImage.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 32C43E2922FD586200BE87F5 /* SDWebImage.framework */; };
321C1D6023DEC231009CF62A /* ViewInspector in Frameworks */ = {isa = PBXBuildFile; productRef = 321C1D5F23DEC231009CF62A /* ViewInspector */; };
321C1D6523DEDB23009CF62A /* Introspect in Frameworks */ = {isa = PBXBuildFile; productRef = 321C1D6423DEDB23009CF62A /* Introspect */; };
321C1D6723DEDB8E009CF62A /* Introspect in Frameworks */ = {isa = PBXBuildFile; productRef = 321C1D6623DEDB8E009CF62A /* Introspect */; };
321C1D6923DEDB91009CF62A /* Introspect in Frameworks */ = {isa = PBXBuildFile; productRef = 321C1D6823DEDB91009CF62A /* Introspect */; };
321C1D6A23DEDB98009CF62A /* AnimatedImageTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3211F84623DE984D00FC757F /* AnimatedImageTests.swift */; };
321C1D6B23DEDB98009CF62A /* WebImageTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3211F84F23DE98E300FC757F /* WebImageTests.swift */; };
321C1D6C23DEDB98009CF62A /* AnimatedImageTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3211F84623DE984D00FC757F /* AnimatedImageTests.swift */; };
@ -192,7 +189,6 @@
buildActionMask = 2147483647;
files = (
321C1D3323DEA28E009CF62A /* SDWebImage.framework in Frameworks */,
321C1D6523DEDB23009CF62A /* Introspect in Frameworks */,
3211F84923DE984D00FC757F /* SDWebImageSwiftUI.framework in Frameworks */,
3211F85323DE996700FC757F /* ViewInspector in Frameworks */,
);
@ -203,7 +199,6 @@
buildActionMask = 2147483647;
files = (
321C1D5B23DEC219009CF62A /* SDWebImage.framework in Frameworks */,
321C1D6723DEDB8E009CF62A /* Introspect in Frameworks */,
321C1D4023DEC17D009CF62A /* SDWebImageSwiftUI.framework in Frameworks */,
321C1D5A23DEC207009CF62A /* ViewInspector in Frameworks */,
);
@ -214,7 +209,6 @@
buildActionMask = 2147483647;
files = (
321C1D5E23DEC22D009CF62A /* SDWebImage.framework in Frameworks */,
321C1D6923DEDB91009CF62A /* Introspect in Frameworks */,
321C1D4F23DEC185009CF62A /* SDWebImageSwiftUI.framework in Frameworks */,
321C1D6023DEC231009CF62A /* ViewInspector in Frameworks */,
);
@ -400,7 +394,6 @@
name = SDWebImageSwiftUITests;
packageProductDependencies = (
3211F85223DE996700FC757F /* ViewInspector */,
321C1D6423DEDB23009CF62A /* Introspect */,
);
productName = SDWebImageSwiftUITests;
productReference = 3211F84423DE984D00FC757F /* SDWebImageSwiftUITests.xctest */;
@ -423,7 +416,6 @@
name = "SDWebImageSwiftUITests macOS";
packageProductDependencies = (
321C1D5923DEC207009CF62A /* ViewInspector */,
321C1D6623DEDB8E009CF62A /* Introspect */,
);
productName = "SDWebImageSwiftUITests macOS";
productReference = 321C1D3B23DEC17D009CF62A /* SDWebImageSwiftUITests macOS.xctest */;
@ -446,7 +438,6 @@
name = "SDWebImageSwiftUITests tvOS";
packageProductDependencies = (
321C1D5F23DEC231009CF62A /* ViewInspector */,
321C1D6823DEDB91009CF62A /* Introspect */,
);
productName = "SDWebImageSwiftUITests tvOS";
productReference = 321C1D4A23DEC185009CF62A /* SDWebImageSwiftUITests tvOS.xctest */;
@ -576,7 +567,6 @@
mainGroup = 32C43DC222FD540D00BE87F5;
packageReferences = (
3211F85123DE996700FC757F /* XCRemoteSwiftPackageReference "ViewInspector" */,
321C1D6323DEDB23009CF62A /* XCRemoteSwiftPackageReference "SwiftUI-Introspect" */,
);
productRefGroup = 32C43DCD22FD540D00BE87F5 /* Products */;
projectDirPath = "";
@ -1420,15 +1410,7 @@
repositoryURL = "https://github.com/nalexn/ViewInspector.git";
requirement = {
kind = upToNextMajorVersion;
minimumVersion = 0.0.0;
};
};
321C1D6323DEDB23009CF62A /* XCRemoteSwiftPackageReference "SwiftUI-Introspect" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/dreampiggy/SwiftUI-Introspect.git";
requirement = {
kind = upToNextMajorVersion;
minimumVersion = 0.0.0;
minimumVersion = 0.3.5;
};
};
/* End XCRemoteSwiftPackageReference section */
@ -1449,21 +1431,6 @@
package = 3211F85123DE996700FC757F /* XCRemoteSwiftPackageReference "ViewInspector" */;
productName = ViewInspector;
};
321C1D6423DEDB23009CF62A /* Introspect */ = {
isa = XCSwiftPackageProductDependency;
package = 321C1D6323DEDB23009CF62A /* XCRemoteSwiftPackageReference "SwiftUI-Introspect" */;
productName = Introspect;
};
321C1D6623DEDB8E009CF62A /* Introspect */ = {
isa = XCSwiftPackageProductDependency;
package = 321C1D6323DEDB23009CF62A /* XCRemoteSwiftPackageReference "SwiftUI-Introspect" */;
productName = Introspect;
};
321C1D6823DEDB91009CF62A /* Introspect */ = {
isa = XCSwiftPackageProductDependency;
package = 321C1D6323DEDB23009CF62A /* XCRemoteSwiftPackageReference "SwiftUI-Introspect" */;
productName = Introspect;
};
/* End XCSwiftPackageProductDependency section */
};
rootObject = 32C43DC322FD540D00BE87F5 /* Project object */;

View File

@ -1,22 +1,13 @@
{
"object": {
"pins": [
{
"package": "Introspect",
"repositoryURL": "https://github.com/dreampiggy/SwiftUI-Introspect.git",
"state": {
"branch": null,
"revision": "53a3b54dcc6ecb2a37edb5ea13ffab76abffee1f",
"version": "0.1.0"
}
},
{
"package": "ViewInspector",
"repositoryURL": "https://github.com/nalexn/ViewInspector.git",
"state": {
"branch": null,
"revision": "2f83ea202c9d80f0fdb959ed5e0e52c1cc97e411",
"version": "0.3.3"
"revision": "17852b6bbe0ac0d023734d7551bf5f30d1b3935a",
"version": "0.3.5"
}
}
]

View File

@ -55,3 +55,22 @@ public typealias PlatformViewRepresentable = UIViewRepresentable
@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *)
public typealias PlatformViewRepresentable = WKInterfaceObjectRepresentable
#endif
#if os(macOS)
@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *)
extension NSViewRepresentable {
typealias PlatformViewType = NSViewType
}
#endif
#if os(iOS) || os(tvOS)
@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *)
extension UIViewRepresentable {
typealias PlatformViewType = UIViewType
}
#endif
#if os(watchOS)
@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *)
extension WKInterfaceObjectRepresentable {
typealias PlatformViewType = WKInterfaceObjectType
}
#endif

View File

@ -1,25 +1,10 @@
import XCTest
import SwiftUI
import ViewInspector
import Introspect
@testable import SDWebImageSwiftUI
extension AnimatedImage : Inspectable {}
extension View {
func introspectAnimatedImage(customize: @escaping (SDAnimatedImageView) -> ()) -> some View {
return inject(IntrospectionView(
selector: { introspectionView in
guard let viewHost = Introspect.findViewHost(from: introspectionView) else {
return nil
}
return Introspect.previousSibling(containing: SDAnimatedImageView.self, from: viewHost)
},
customize: customize
))
}
}
extension AnimatedImage {
struct WrapperView: View & Inspectable {
var name: String
@ -51,44 +36,45 @@ class AnimatedImageTests: XCTestCase {
func testAnimatedImageWithName() throws {
let expectation = self.expectation(description: "AnimatedImage name initializer")
let imageView = AnimatedImage(name: "TestImage.gif", bundle: TestUtils.testImageBundle())
let introspectView = imageView.introspectAnimatedImage { animatedImageView in
if let animatedImage = animatedImageView.image as? SDAnimatedImage {
XCTAssertEqual(animatedImage.animatedImageLoopCount, 0)
XCTAssertEqual(animatedImage.animatedImageFrameCount, 5)
} else {
XCTFail("SDAnimatedImageView.image invalid")
}
expectation.fulfill()
let imageName = "TestImage.gif"
let imageView = AnimatedImage(name: imageName, bundle: TestUtils.testImageBundle())
ViewHosting.host(view: imageView)
let animatedImageView = try imageView.inspect().actualView().platformView().wrapped
if let animatedImage = animatedImageView.image as? SDAnimatedImage {
XCTAssertEqual(animatedImage.animatedImageLoopCount, 0)
XCTAssertEqual(animatedImage.animatedImageFrameCount, 5)
} else {
XCTFail("SDAnimatedImageView.image invalid")
}
_ = try introspectView.inspect(AnimatedImage.self)
ViewHosting.host(view: introspectView)
XCTAssertEqual(animatedImageView.sd_imageName, imageName)
expectation.fulfill()
self.waitForExpectations(timeout: 5, handler: nil)
ViewHosting.expel()
}
func testAnimatedImageWithData() throws {
let expectation = self.expectation(description: "AnimatedImage data initializer")
let imageData = try XCTUnwrap(TestUtils.testImageData(name: "TestImageAnimated.apng"))
let imageView = AnimatedImage(data: imageData)
let introspectView = imageView.introspectAnimatedImage { animatedImageView in
if let animatedImage = animatedImageView.image as? SDAnimatedImage {
XCTAssertEqual(animatedImage.animatedImageLoopCount, 0)
XCTAssertEqual(animatedImage.animatedImageFrameCount, 101)
} else {
XCTFail("SDAnimatedImageView.image invalid")
}
expectation.fulfill()
ViewHosting.host(view: imageView)
let animatedImageView = try imageView.inspect().actualView().platformView().wrapped
if let animatedImage = animatedImageView.image as? SDAnimatedImage {
XCTAssertEqual(animatedImage.animatedImageLoopCount, 0)
XCTAssertEqual(animatedImage.animatedImageFrameCount, 101)
} else {
XCTFail("SDAnimatedImageView.image invalid")
}
_ = try introspectView.inspect(AnimatedImage.self)
ViewHosting.host(view: introspectView)
XCTAssertEqual(animatedImageView.sd_imageData, imageData)
expectation.fulfill()
self.waitForExpectations(timeout: 5, handler: nil)
ViewHosting.expel()
}
func testAnimatedImageWithURL() throws {
let expectation = self.expectation(description: "AnimatedImage url initializer")
let imageUrl = URL(string: "https://raw.githubusercontent.com/liyong03/YLGIFImage/master/YLGIFImageDemo/YLGIFImageDemo/joy.gif")
let imageView = AnimatedImage(url: imageUrl)
let introspectView = imageView.onSuccess { image, cacheType in
.onSuccess { image, cacheType in
if let animatedImage = image as? SDAnimatedImage {
XCTAssertEqual(animatedImage.animatedImageLoopCount, 0)
XCTAssertEqual(animatedImage.animatedImageFrameCount, 389)
@ -99,9 +85,11 @@ class AnimatedImageTests: XCTestCase {
}.onFailure { error in
XCTFail(error.localizedDescription)
}
_ = try introspectView.inspect(AnimatedImage.self)
ViewHosting.host(view: introspectView)
ViewHosting.host(view: imageView)
let animatedImageView = try imageView.inspect().actualView().platformView().wrapped
XCTAssertEqual(animatedImageView.sd_imageURL, imageUrl)
self.waitForExpectations(timeout: 5, handler: nil)
ViewHosting.expel()
}
func testAnimatedImageBinding() throws {
@ -151,15 +139,12 @@ class AnimatedImageTests: XCTestCase {
}
ViewHosting.host(view: wrapperView)
self.waitForExpectations(timeout: 5, handler: nil)
ViewHosting.expel()
}
func testAnimatedImageModifier() throws {
let expectation = self.expectation(description: "WebImage modifier")
let imageUrl = URL(string: "https://assets.sbnation.com/assets/2512203/dogflops.gif")
AnimatedImage.onViewDestroy { view, coordinator in
XCTAssert(view.isKind(of: SDAnimatedImageView.self))
XCTAssertEqual(coordinator.userInfo?["foo"] as? String, "bar")
}
let imageView = AnimatedImage(url: imageUrl, options: [.progressiveLoad], context: [.imageScaleFactor: 1])
let introspectView = imageView
.onSuccess { _, _ in
@ -195,9 +180,9 @@ class AnimatedImageTests: XCTestCase {
.playbackRate(1)
.transition(.fade)
.animation(.easeInOut)
_ = try introspectView.inspect(AnimatedImage.self)
_ = try introspectView.inspect()
ViewHosting.host(view: introspectView)
self.waitForExpectations(timeout: 5, handler: nil)
AnimatedImage.onViewDestroy()
ViewHosting.expel()
}
}

View File

@ -1,7 +1,6 @@
import XCTest
import SwiftUI
import ViewInspector
import Introspect
@testable import SDWebImageSwiftUI
extension ActivityIndicator : Inspectable {}
@ -15,32 +14,6 @@ typealias ActivityIndicatorViewType = NSProgressIndicator
typealias ProgressIndicatorViewType = NSProgressIndicator
#endif
extension View {
func introspectActivityIndicator(customize: @escaping (ActivityIndicatorViewType) -> ()) -> some View {
return inject(IntrospectionView(
selector: { introspectionView in
guard let viewHost = Introspect.findViewHost(from: introspectionView) else {
return nil
}
return Introspect.previousSibling(containing: ActivityIndicatorViewType.self, from: viewHost)
},
customize: customize
))
}
func introspectProgressIndicator(customize: @escaping (ProgressIndicatorViewType) -> ()) -> some View {
return inject(IntrospectionView(
selector: { introspectionView in
guard let viewHost = Introspect.findViewHost(from: introspectionView) else {
return nil
}
return Introspect.previousSibling(containing: ProgressIndicatorViewType.self, from: viewHost)
},
customize: customize
))
}
}
class IndicatorTests: XCTestCase {
override func setUp() {
@ -58,26 +31,25 @@ class IndicatorTests: XCTestCase {
let expectation = self.expectation(description: "Activity indicator")
let binding = Binding<Bool>(wrappedValue: true)
let indicator = ActivityIndicator(binding, style: .medium)
let introspectView = indicator.introspectActivityIndicator { indicatorView in
#if os(iOS) || os(tvOS)
XCTAssertTrue(indicatorView.isAnimating)
#endif
binding.wrappedValue = false
XCTAssertFalse(binding.wrappedValue)
XCTAssertFalse(indicator.isAnimating)
#if os(iOS) || os(tvOS)
indicatorView.stopAnimating()
#else
indicatorView.stopAnimation(nil)
#endif
#if os(iOS) || os(tvOS)
XCTAssertFalse(indicatorView.isAnimating)
#endif
expectation.fulfill()
}
_ = try introspectView.inspect(ActivityIndicator.self)
ViewHosting.host(view: introspectView)
ViewHosting.host(view: indicator)
let indicatorView = try indicator.inspect().actualView().platformView()
#if os(iOS) || os(tvOS)
XCTAssertTrue(indicatorView.isAnimating)
#endif
binding.wrappedValue = false
XCTAssertFalse(binding.wrappedValue)
XCTAssertFalse(indicator.isAnimating)
#if os(iOS) || os(tvOS)
indicatorView.stopAnimating()
#else
indicatorView.stopAnimation(nil)
#endif
#if os(iOS) || os(tvOS)
XCTAssertFalse(indicatorView.isAnimating)
#endif
expectation.fulfill()
self.waitForExpectations(timeout: 5, handler: nil)
ViewHosting.expel()
}
func testProgressIndicator() throws {
@ -85,29 +57,28 @@ class IndicatorTests: XCTestCase {
let binding = Binding<Bool>(wrappedValue: true)
let progress = Binding<CGFloat>(wrappedValue: 0)
let indicator = ProgressIndicator(binding, progress: progress)
let introspectView = indicator.introspectProgressIndicator { indicatorView in
#if os(iOS) || os(tvOS)
XCTAssertEqual(indicatorView.progress, 0.0)
#else
XCTAssertEqual(indicatorView.doubleValue, 0.0)
#endif
progress.wrappedValue = 1.0
XCTAssertEqual(indicator.progress, 1.0)
#if os(iOS) || os(tvOS)
indicatorView.setProgress(1.0, animated: true)
#else
indicatorView.increment(by: 1.0)
#endif
#if os(iOS) || os(tvOS)
XCTAssertEqual(indicatorView.progress, 1.0)
#else
XCTAssertEqual(indicatorView.doubleValue, 1.0)
#endif
expectation.fulfill()
}
_ = try introspectView.inspect(ProgressIndicator.self)
ViewHosting.host(view: introspectView)
ViewHosting.host(view: indicator)
let indicatorView = try indicator.inspect().actualView().platformView().wrapped
#if os(iOS) || os(tvOS)
XCTAssertEqual(indicatorView.progress, 0.0)
#else
XCTAssertEqual(indicatorView.doubleValue, 0.0)
#endif
progress.wrappedValue = 1.0
XCTAssertEqual(indicator.progress, 1.0)
#if os(iOS) || os(tvOS)
indicatorView.setProgress(1.0, animated: true)
#else
indicatorView.increment(by: 1.0)
#endif
#if os(iOS) || os(tvOS)
XCTAssertEqual(indicatorView.progress, 1.0)
#else
XCTAssertEqual(indicatorView.doubleValue, 1.0)
#endif
expectation.fulfill()
self.waitForExpectations(timeout: 5, handler: nil)
ViewHosting.expel()
}
}

View File

@ -1,5 +1,18 @@
import XCTest
import SwiftUI
import ViewInspector
@testable import SDWebImageSwiftUI
public extension PlatformViewRepresentable where Self: Inspectable {
func platformView() throws -> PlatformViewType {
#if os(macOS)
return try nsView()
#else
return try uiView()
#endif
}
}
class TestUtils {
static var testBundle = Bundle(for: TestUtils.self)

View File

@ -32,9 +32,10 @@ class WebImageTests: XCTestCase {
}.onFailure { error in
XCTFail(error.localizedDescription)
}
_ = try introspectView.inspect(WebImage.self)
_ = try introspectView.inspect()
ViewHosting.host(view: introspectView)
self.waitForExpectations(timeout: 5, handler: nil)
ViewHosting.expel()
}
func testWebImageWithAnimatedURL() throws {
@ -61,9 +62,10 @@ class WebImageTests: XCTestCase {
}.onFailure { error in
XCTFail(error.localizedDescription)
}
_ = try introspectView.inspect(WebImage.self)
_ = try introspectView.inspect()
ViewHosting.host(view: introspectView)
self.waitForExpectations(timeout: 5, handler: nil)
ViewHosting.expel()
}
func testWebImageModifier() throws {
@ -101,9 +103,10 @@ class WebImageTests: XCTestCase {
.indicator(.activity)
.transition(.fade)
.animation(.easeInOut)
_ = try introspectView.inspect(WebImage.self)
_ = try introspectView.inspect()
ViewHosting.host(view: introspectView)
self.waitForExpectations(timeout: 5, handler: nil)
ViewHosting.expel()
}
func testWebImageOnSuccessWhenMemoryCacheHit() throws {
@ -123,9 +126,10 @@ class WebImageTests: XCTestCase {
XCTAssertEqual(image, testImage)
expectation.fulfill()
}
_ = try introspectView.inspect(WebImage.self)
_ = try introspectView.inspect()
ViewHosting.host(view: introspectView)
self.waitForExpectations(timeout: 5, handler: nil)
ViewHosting.expel()
}
}