import XCTest import SwiftUI import ViewInspector @testable import SDWebImageSwiftUI extension AnimatedImage : Inspectable {} extension AnimatedImage { struct WrapperView: View & Inspectable { var name: String var bundle: Bundle? @State var isAnimating: Bool var onViewUpdate: (Self, PlatformView, AnimatedImage.Context) -> Void var body: some View { AnimatedImage(name: name, bundle: bundle, isAnimating: $isAnimating) .onViewUpdate { view, context in self.onViewUpdate(self, view, context) } } } } class AnimatedImageTests: XCTestCase { override func setUp() { super.setUp() // Put setup code here. This method is called before the invocation of each test method in the class. } override func tearDown() { // Put teardown code here. This method is called after the invocation of each test method in the class. super.tearDown() } func testAnimatedImageWithName() throws { let expectation = self.expectation(description: "AnimatedImage name initializer") 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") } 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: "TestLoopCount.gif")) let imageView = AnimatedImage(data: imageData) ViewHosting.host(view: imageView) let animatedImageView = try imageView.inspect().actualView().platformView().wrapped if let animatedImage = animatedImageView.image as? SDAnimatedImage { XCTAssertEqual(animatedImage.animatedImageLoopCount, 1) XCTAssertEqual(animatedImage.animatedImageFrameCount, 2) } else { XCTFail("SDAnimatedImageView.image invalid") } 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) .onSuccess { image, data, cacheType in XCTAssertNotNil(image) if let animatedImage = image as? SDAnimatedImage { XCTAssertEqual(animatedImage.animatedImageLoopCount, 0) XCTAssertEqual(animatedImage.animatedImageFrameCount, 389) } else { XCTFail("SDAnimatedImageView.image invalid") } expectation.fulfill() }.onFailure { error in XCTFail(error.localizedDescription) } ViewHosting.host(view: imageView) let animatedImageView = try imageView.inspect().actualView().platformView().wrapped XCTAssertEqual(animatedImageView.sd_imageURL, imageUrl) self.waitForExpectations(timeout: 10, handler: nil) ViewHosting.expel() } func testAnimatedImageBinding() throws { let expectation = self.expectation(description: "AnimatedImage binding control") var isStopped = false // Use wrapper to make the @Binding works let wrapperView = AnimatedImage.WrapperView(name: "TestImageAnimated.apng", bundle: TestUtils.testImageBundle(), isAnimating: true) { wrapperView, view, context in guard let animatedImageView = view as? SDAnimatedImageView else { XCTFail("AnimatedImage's view should be SDAnimatedImageView") return } if let animatedImage = animatedImageView.image as? SDAnimatedImage { XCTAssertEqual(animatedImage.animatedImageLoopCount, 0) XCTAssertEqual(animatedImage.animatedImageFrameCount, 101) } else { XCTFail("AnimatedImage's image should be SDAnimatedImage") } // Wait 1 second for SwiftUI's own `updateUIView` callback finished. // It's suck that the actual callback behavior is different on different iOS version or Simulator version, so I can assume which is the last callback using the callback count. if !isStopped { // # SwiftUI's own updateUIView call // Ignore in Travis-CI because of macOS 10.14's bug behavior on iPhone Simulator // #if os(iOS) || os(tvOS) // XCTAssertTrue(animatedImageView.isAnimating) // #else // XCTAssertTrue(animatedImageView.animates) // #endif DispatchQueue.main.asyncAfter(deadline: .now() + 1) { if !isStopped { isStopped = true wrapperView.isAnimating = false } else { // Extra `updateUIView` from SwiftUI, ignore } } } else { // # AnimatedImage's isAnimating @Binding trigger update (from above) #if os(iOS) || os(tvOS) XCTAssertFalse(animatedImageView.isAnimating) #else XCTAssertFalse(animatedImageView.animates) #endif expectation.fulfill() } } 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") let imageView = AnimatedImage(url: imageUrl, options: [.progressiveLoad], context: [.imageScaleFactor: 1]) { Circle() } let introspectView = imageView .onSuccess { _, _, _ in expectation.fulfill() } .onFailure { _ in XCTFail() } .onProgress { _, _ in } .onViewCreate { view, context in XCTAssert(view.isKind(of: SDAnimatedImageView.self)) context.coordinator.userInfo = ["foo" : "bar"] } .onViewUpdate { view, context in XCTAssert(view.isKind(of: SDAnimatedImageView.self)) XCTAssertEqual(context.coordinator.userInfo?["foo"] as? String, "bar") } .indicator(.activity) // Image .resizable() .renderingMode(.original) .interpolation(.high) .antialiased(true) // Animation .runLoopMode(.common) .customLoopCount(1) .maxBufferSize(0) .pausable(true) .purgeable(true) .playbackRate(1) .transition(.fade) .animation(.easeInOut) _ = try introspectView.inspect() ViewHosting.host(view: introspectView) self.waitForExpectations(timeout: 10, handler: nil) ViewHosting.expel() } }