diff --git a/Airship/AirshipCore/Source/ThomasAsyncImage.swift b/Airship/AirshipCore/Source/ThomasAsyncImage.swift index aba836deb..1dca0ed68 100644 --- a/Airship/AirshipCore/Source/ThomasAsyncImage.swift +++ b/Airship/AirshipCore/Source/ThomasAsyncImage.swift @@ -125,7 +125,11 @@ public struct ThomasAsyncImage: View { self.currentImage = frame?.image - while !Task.isCancelled && (loadedImage.loopCount == nil || loopsCompleted < loadedImage.loopCount!) { + /// GIFs will sometimes have a 0 in their loop count metadata to denote infinite loops + let loopCount = loadedImage.loopCount ?? 0 + + /// Continue looping if loop count is nil (coalesces to zero), zero or nonzero and greater than the loops completed + while !Task.isCancelled && (loopCount <= 0 || loopCount > loopsCompleted) { let duration = frame?.duration ?? AirshipImageData.minFrameDuration async let delay: () = Task.sleep(nanoseconds: UInt64(duration * 1_000_000_000)) diff --git a/Airship/AirshipCore/Tests/Events/EventTest.swift b/Airship/AirshipCore/Tests/Events/EventTest.swift index c58d83945..01b2ba18b 100644 --- a/Airship/AirshipCore/Tests/Events/EventTest.swift +++ b/Airship/AirshipCore/Tests/Events/EventTest.swift @@ -146,22 +146,37 @@ class EventTest: XCTestCase { } func testScreenTracking() throws { - let event = ScreenTrackingEvent( + guard let event = ScreenTrackingEvent( screen: "test_screen", previousScreen: "previous_screen", startDate: Date(timeIntervalSince1970: 0), duration: 1 - ) - - - XCTAssertEqual(event!.data["duration"] as! String, "1.000") - XCTAssertEqual(event!.data["entered_time"] as! String, "0.000") - XCTAssertEqual(event!.data["exited_time"] as! String, "1.000") - XCTAssertEqual( - event!.data["previous_screen"] as! String, - "previous_screen" - ) - XCTAssertEqual(event!.data["screen"] as! String, "test_screen") + ) else { + XCTFail("Event is nil") + return + } + + guard let durationString = event.data["duration"] as? String, + let enteredTimeString = event.data["entered_time"] as? String, + let exitedTimeString = event.data["exited_time"] as? String, + let duration = TimeInterval(durationString), + let enteredTime = TimeInterval(enteredTimeString), + let exitedTime = TimeInterval(exitedTimeString) else { + XCTFail("One or more strings could not be converted to TimeIntervals for comparison") + return + } + + let expectedDurationInSeconds: TimeInterval = 1.0 + let expectedEnteredTimeInSeconds: TimeInterval = 0.0 + let expectedExitedTimeInSeconds: TimeInterval = 1.0 + let errorMarginInSeconds: TimeInterval = 1 /// Use accuracy margin to avoid timing issues in ci + + XCTAssertEqual(duration, expectedDurationInSeconds, accuracy: errorMarginInSeconds, "Duration does not match within the acceptable error margin.") + XCTAssertEqual(enteredTime, expectedEnteredTimeInSeconds, accuracy: errorMarginInSeconds, "Entered time does not match within the acceptable error margin.") + XCTAssertEqual(exitedTime, expectedExitedTimeInSeconds, accuracy: errorMarginInSeconds, "Exited time does not match within the acceptable error margin.") + + XCTAssertEqual(event.data["previous_screen"] as? String, "previous_screen", "Previous screen does not match.") + XCTAssertEqual(event.data["screen"] as? String, "test_screen", "Screen does not match.") } func testScreenValidation() throws {