mirror of
https://github.com/kean/Nuke.git
synced 2024-11-28 12:04:01 +03:00
Compare commits
16 Commits
bd8a7700e5
...
ae2c57a7df
Author | SHA1 | Date | |
---|---|---|---|
|
ae2c57a7df | ||
|
b62cc4346a | ||
|
4e0bca9161 | ||
|
52771a052b | ||
|
13d363e609 | ||
|
9f7eb9cafb | ||
|
26487ea9e6 | ||
|
bec2101b77 | ||
|
ef82a7d183 | ||
|
7e684d13e3 | ||
|
28967695f4 | ||
|
1d748d4eac | ||
|
840b743058 | ||
|
ef56fdd004 | ||
|
639832849c | ||
|
0e953da391 |
@ -1837,7 +1837,6 @@
|
||||
OTHER_SWIFT_FLAGS = "-D DEBUG";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.github.kean.nukeui;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_STRICT_CONCURRENCY = complete;
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
@ -1857,7 +1856,6 @@
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.github.kean.nukeui;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_STRICT_CONCURRENCY = complete;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
@ -1875,6 +1873,7 @@
|
||||
OTHER_SWIFT_FLAGS = "-D DEBUG";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "com.github.kean.nukeui-unit-tests";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_VERSION = 5.0;
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
@ -1891,6 +1890,7 @@
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "com.github.kean.nukeui-unit-tests";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_VERSION = 5.0;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
@ -1907,6 +1907,7 @@
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "com.github.kean.Nuke-Thread-Safety-Tests";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
|
||||
SWIFT_VERSION = 5.0;
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
@ -1922,6 +1923,7 @@
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "com.github.kean.Nuke-Thread-Safety-Tests";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_VERSION = 5.0;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
@ -1942,7 +1944,6 @@
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "com.github.kean.nuke-extensions";
|
||||
PRODUCT_NAME = NukeExtensions;
|
||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
|
||||
SWIFT_STRICT_CONCURRENCY = complete;
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
@ -1962,7 +1963,6 @@
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "com.github.kean.nuke-extensions";
|
||||
PRODUCT_NAME = NukeExtensions;
|
||||
SWIFT_STRICT_CONCURRENCY = complete;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
@ -1984,6 +1984,7 @@
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.github.kean.NukeExtensionsTests;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
|
||||
SWIFT_VERSION = 5.0;
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
@ -2004,6 +2005,7 @@
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.github.kean.NukeExtensionsTests;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_VERSION = 5.0;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
@ -2024,7 +2026,6 @@
|
||||
OTHER_SWIFT_FLAGS = "-D DEBUG";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.github.kean.nukevideo;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_STRICT_CONCURRENCY = complete;
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
@ -2044,7 +2045,6 @@
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.github.kean.nukevideo;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_STRICT_CONCURRENCY = complete;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
@ -2061,6 +2061,8 @@
|
||||
OTHER_SWIFT_FLAGS = "-D DEBUG";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "com.github.kean.Nuke-Tests";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_STRICT_CONCURRENCY = complete;
|
||||
SWIFT_VERSION = 5.0;
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
@ -2076,6 +2078,8 @@
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "com.github.kean.Nuke-Tests";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_STRICT_CONCURRENCY = complete;
|
||||
SWIFT_VERSION = 5.0;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
@ -2140,6 +2144,7 @@
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
SUPPORTED_PLATFORMS = "iphonesimulator iphoneos";
|
||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
|
||||
SWIFT_VERSION = 5.0;
|
||||
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Nuke Tests Host.app/Nuke Tests Host";
|
||||
};
|
||||
name = Debug;
|
||||
@ -2161,6 +2166,7 @@
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
SUPPORTED_PLATFORMS = "iphonesimulator iphoneos";
|
||||
SWIFT_VERSION = 5.0;
|
||||
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Nuke Tests Host.app/Nuke Tests Host";
|
||||
};
|
||||
name = Release;
|
||||
@ -2222,7 +2228,7 @@
|
||||
SUPPORTED_PLATFORMS = "watchsimulator watchos macosx iphonesimulator iphoneos driverkit appletvsimulator appletvos";
|
||||
SUPPORTS_MACCATALYST = YES;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
SWIFT_VERSION = 5.0;
|
||||
SWIFT_VERSION = 6.0;
|
||||
TVOS_DEPLOYMENT_TARGET = 13.0;
|
||||
VERSIONING_SYSTEM = "apple-generic";
|
||||
VERSION_INFO_PREFIX = "";
|
||||
@ -2279,7 +2285,7 @@
|
||||
SKIP_INSTALL = YES;
|
||||
SUPPORTED_PLATFORMS = "watchsimulator watchos macosx iphonesimulator iphoneos driverkit appletvsimulator appletvos";
|
||||
SUPPORTS_MACCATALYST = YES;
|
||||
SWIFT_VERSION = 5.0;
|
||||
SWIFT_VERSION = 6.0;
|
||||
TVOS_DEPLOYMENT_TARGET = 13.0;
|
||||
VALIDATE_PRODUCT = YES;
|
||||
VERSIONING_SYSTEM = "apple-generic";
|
||||
@ -2305,7 +2311,6 @@
|
||||
OTHER_SWIFT_FLAGS = "-D DEBUG";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.github.kean.nuke;
|
||||
PRODUCT_NAME = Nuke;
|
||||
SWIFT_STRICT_CONCURRENCY = complete;
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
@ -2325,7 +2330,6 @@
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.github.kean.nuke;
|
||||
PRODUCT_NAME = Nuke;
|
||||
SWIFT_STRICT_CONCURRENCY = complete;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
|
22
README.md
22
README.md
@ -18,19 +18,29 @@ The framework is lean and compiles in under 2 seconds[¹](#footnote-1). It has a
|
||||
|
||||
## Sponsors
|
||||
|
||||
[**Lapse**](https://www.lapse.com): friends *not followers*.
|
||||
|
||||
<a href="https://www.lapse.com"><img src="https://github.com/user-attachments/assets/28bb4f4b-affc-4580-879d-5aeef633b80c" height="140px" alt="Lapse Logo"></a>
|
||||
|
||||
Nuke is also supported by:
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<td valign="center">
|
||||
<a href="https://proxyman.io"><img src="https://kean.blog/images/logos/proxyman.png" height="40px" alt="Proxyman Logo"></a>
|
||||
<td valign="center" align="center">
|
||||
<a href="https://proxyman.io">
|
||||
<img src="https://kean.blog/images/logos/proxyman.png" height="50px" alt="Proxyman Logo">
|
||||
<p>Proxyman</p>
|
||||
</a>
|
||||
</td>
|
||||
<td valign="center">
|
||||
<a href="https://www.namiml.com#gh-light-mode-only"><img src="https://kean.blog/images/logos/nami-light.png#gh-light-mode-only" height="40px" alt="Nami Logo"></a><a href="https://www.namiml.com#gh-dark-mode-only"><img src="https://kean.blog/images/logos/nami-dark.png#gh-dark-mode-only" height="40px" alt="Nami Logo"></a>
|
||||
<td valign="center" align="center">
|
||||
<a href="https://backspace.travel">
|
||||
<img src="https://avatars.githubusercontent.com/u/175125884?s=200&v=4" height="50px", alt="Backspace Travel"/>
|
||||
<p>Backspace Travel</p>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
> [Support](https://github.com/sponsors/kean) Nuke on GitHub Sponsors.
|
||||
|
||||
## Installation
|
||||
|
||||
Nuke supports [Swift Package Manager](https://www.swift.org/package-manager/), which is the recommended option. If that doesn't work for you, you can use binary frameworks attached to the [releases](https://github.com/kean/Nuke/releases).
|
||||
|
@ -56,8 +56,8 @@ final class Cache<Key: Hashable, Value>: @unchecked Sendable {
|
||||
self.memoryPressure.resume()
|
||||
|
||||
#if os(iOS) || os(tvOS) || os(visionOS)
|
||||
Task {
|
||||
await registerForEnterBackground()
|
||||
Task { @MainActor in
|
||||
registerForEnterBackground()
|
||||
}
|
||||
#endif
|
||||
}
|
||||
@ -70,7 +70,7 @@ final class Cache<Key: Hashable, Value>: @unchecked Sendable {
|
||||
}
|
||||
|
||||
#if os(iOS) || os(tvOS) || os(visionOS)
|
||||
@MainActor private func registerForEnterBackground() {
|
||||
private func registerForEnterBackground() {
|
||||
notificationObserver = NotificationCenter.default.addObserver(forName: UIApplication.didEnterBackgroundNotification, object: nil, queue: nil) { [weak self] _ in
|
||||
self?.clearCacheOnEnterBackground()
|
||||
}
|
||||
|
@ -32,8 +32,10 @@ extension ImageEncoding {
|
||||
}
|
||||
}
|
||||
|
||||
// note: @unchecked was added to surpress build errors with NSImage on macOS
|
||||
|
||||
/// Image encoding context used when selecting which encoder to use.
|
||||
public struct ImageEncodingContext: Sendable {
|
||||
public struct ImageEncodingContext: @unchecked Sendable {
|
||||
public let request: ImageRequest
|
||||
public let image: PlatformImage
|
||||
public let urlResponse: URLResponse?
|
||||
|
@ -5,7 +5,7 @@
|
||||
import Foundation
|
||||
import Nuke
|
||||
|
||||
extension ImagePipeline.Error: Equatable {
|
||||
extension ImagePipeline.Error: @retroactive Equatable {
|
||||
public static func == (lhs: ImagePipeline.Error, rhs: ImagePipeline.Error) -> Bool {
|
||||
switch (lhs, rhs) {
|
||||
case (.dataMissingInCache, .dataMissingInCache): return true
|
||||
@ -22,7 +22,7 @@ extension ImagePipeline.Error: Equatable {
|
||||
}
|
||||
}
|
||||
|
||||
extension ImageResponse: Equatable {
|
||||
extension ImageResponse: @retroactive Equatable {
|
||||
public static func == (lhs: ImageResponse, rhs: ImageResponse) -> Bool {
|
||||
return lhs.image === rhs.image
|
||||
}
|
||||
|
@ -2,422 +2,397 @@
|
||||
//
|
||||
// Copyright (c) 2015-2024 Alexander Grebenyuk (github.com/kean).
|
||||
|
||||
import XCTest
|
||||
import Foundation
|
||||
import Testing
|
||||
@testable import Nuke
|
||||
|
||||
#if canImport(UIKit)
|
||||
import UIKit
|
||||
#endif
|
||||
|
||||
@Suite struct ImageCacheTests {
|
||||
let cache = ImageCache()
|
||||
|
||||
init() {
|
||||
cache.entryCostLimit = 2
|
||||
}
|
||||
|
||||
// MARK: - Basics
|
||||
|
||||
@Test func cacheCreation() {
|
||||
#expect(cache.totalCount == 0)
|
||||
#expect(cache[Test.request] == nil)
|
||||
}
|
||||
|
||||
@Test func imageIsStored() {
|
||||
// When
|
||||
cache[Test.request] = Test.container
|
||||
|
||||
// Then
|
||||
#expect(cache.totalCount == 1)
|
||||
#expect(cache[Test.request] != nil)
|
||||
}
|
||||
|
||||
// MARK: - Subscript
|
||||
|
||||
@Test func imageIsStoredUsingSubscript() {
|
||||
// When
|
||||
cache[Test.request] = Test.container
|
||||
|
||||
// Then
|
||||
#expect(cache[Test.request] != nil)
|
||||
}
|
||||
|
||||
// MARK: - Count
|
||||
|
||||
@Test func totalCountChanges() {
|
||||
#expect(cache.totalCount == 0)
|
||||
|
||||
cache[request1] = Test.container
|
||||
#expect(cache.totalCount == 1)
|
||||
|
||||
cache[request2] = Test.container
|
||||
#expect(cache.totalCount == 2)
|
||||
|
||||
cache[request2] = nil
|
||||
#expect(cache.totalCount == 1)
|
||||
|
||||
cache[request1] = nil
|
||||
#expect(cache.totalCount == 0)
|
||||
}
|
||||
|
||||
@Test func countLimitChanges() {
|
||||
// When
|
||||
cache.countLimit = 1
|
||||
|
||||
// Then
|
||||
#expect(cache.countLimit == 1)
|
||||
}
|
||||
|
||||
@Test func ttlChanges() {
|
||||
// when
|
||||
cache.ttl = 1
|
||||
|
||||
// Then
|
||||
#expect(cache.ttl == 1)
|
||||
}
|
||||
|
||||
@Test func itemsAreRemoveImmediatelyWhenCountLimitIsReached() {
|
||||
// Given
|
||||
cache.countLimit = 1
|
||||
|
||||
// When
|
||||
cache[request1] = Test.container
|
||||
cache[request2] = Test.container
|
||||
|
||||
// Then
|
||||
#expect(cache[request1] == nil)
|
||||
#expect(cache[request2] != nil)
|
||||
}
|
||||
|
||||
@Test func trimToCount() {
|
||||
// Given
|
||||
cache[request1] = Test.container
|
||||
cache[request2] = Test.container
|
||||
|
||||
// When
|
||||
cache.trim(toCount: 1)
|
||||
|
||||
// Then
|
||||
#expect(cache[request1] == nil)
|
||||
#expect(cache[request2] != nil)
|
||||
}
|
||||
|
||||
@Test func imagesAreRemovedOnCountLimitChange() {
|
||||
// Given
|
||||
cache.countLimit = 2
|
||||
|
||||
cache[request1] = Test.container
|
||||
cache[request2] = Test.container
|
||||
|
||||
// When
|
||||
cache.countLimit = 1
|
||||
|
||||
// Then
|
||||
#expect(cache[request1] == nil)
|
||||
#expect(cache[request2] != nil)
|
||||
}
|
||||
|
||||
// MARK: Cost
|
||||
|
||||
#if canImport(UIKit)
|
||||
|
||||
@Test func defaultImageCost() {
|
||||
#expect(cache.cost(for: ImageContainer(image: Test.image)) == 1228800)
|
||||
}
|
||||
|
||||
@Test func totalCostChanges() {
|
||||
let imageCost = cache.cost(for: ImageContainer(image: Test.image))
|
||||
#expect(cache.totalCost == 0)
|
||||
|
||||
cache[request1] = Test.container
|
||||
#expect(cache.totalCost == imageCost)
|
||||
|
||||
cache[request2] = Test.container
|
||||
#expect(cache.totalCost == 2 * imageCost)
|
||||
|
||||
cache[request2] = nil
|
||||
#expect(cache.totalCost == imageCost)
|
||||
|
||||
cache[request1] = nil
|
||||
#expect(cache.totalCost == 0)
|
||||
}
|
||||
|
||||
@Test func costLimitChanged() {
|
||||
// Given
|
||||
let cost = cache.cost(for: ImageContainer(image: Test.image))
|
||||
|
||||
// When
|
||||
cache.costLimit = Int(Double(cost) * 1.5)
|
||||
|
||||
// Then
|
||||
#expect(cache.costLimit == Int(Double(cost) * 1.5))
|
||||
}
|
||||
|
||||
@Test func itemsAreRemoveImmediatelyWhenCostLimitIsReached() {
|
||||
// Given
|
||||
let cost = cache.cost(for: ImageContainer(image: Test.image))
|
||||
cache.costLimit = Int(Double(cost) * 1.5)
|
||||
|
||||
// When/Then
|
||||
cache[request1] = Test.container
|
||||
|
||||
// LRU item is released
|
||||
cache[request2] = Test.container
|
||||
#expect(cache[request1] == nil)
|
||||
#expect(cache[request2] != nil)
|
||||
}
|
||||
|
||||
@Test func entryCostLimitEntryStored() {
|
||||
// Given
|
||||
let container = ImageContainer(image: Test.image)
|
||||
let cost = cache.cost(for: container)
|
||||
cache.costLimit = Int(Double(cost) * 15)
|
||||
cache.entryCostLimit = 0.1
|
||||
|
||||
// When
|
||||
cache[Test.request] = container
|
||||
|
||||
// Then
|
||||
#expect(cache[Test.request] != nil)
|
||||
#expect(cache.totalCount == 1)
|
||||
}
|
||||
|
||||
@Test func entryCostLimitEntryNotStored() {
|
||||
// Given
|
||||
let container = ImageContainer(image: Test.image)
|
||||
let cost = cache.cost(for: container)
|
||||
cache.costLimit = Int(Double(cost) * 3)
|
||||
cache.entryCostLimit = 0.1
|
||||
|
||||
// When
|
||||
cache[Test.request] = container
|
||||
|
||||
// Then
|
||||
#expect(cache[Test.request] == nil)
|
||||
#expect(cache.totalCount == 0)
|
||||
}
|
||||
|
||||
@Test func trimToCost() {
|
||||
// Given
|
||||
cache.costLimit = Int.max
|
||||
|
||||
cache[request1] = Test.container
|
||||
cache[request2] = Test.container
|
||||
|
||||
// When
|
||||
let cost = cache.cost(for: ImageContainer(image: Test.image))
|
||||
cache.trim(toCost: Int(Double(cost) * 1.5))
|
||||
|
||||
// Then
|
||||
#expect(cache[request1] == nil)
|
||||
#expect(cache[request2] != nil)
|
||||
}
|
||||
|
||||
@Test func imagesAreRemovedOnCostLimitChange() {
|
||||
// Given
|
||||
let cost = cache.cost(for: ImageContainer(image: Test.image))
|
||||
cache.costLimit = Int(Double(cost) * 2.5)
|
||||
|
||||
cache[request1] = Test.container
|
||||
cache[request2] = Test.container
|
||||
|
||||
// When
|
||||
cache.costLimit = cost
|
||||
|
||||
// Then
|
||||
#expect(cache[request1] == nil)
|
||||
#expect(cache[request2] != nil)
|
||||
}
|
||||
|
||||
@Test func imageContainerWithoutAssociatedDataCost() {
|
||||
// Given
|
||||
let data = Test.data(name: "cat", extension: "gif")
|
||||
let image = PlatformImage(data: data)!
|
||||
let container = ImageContainer(image: image, data: nil)
|
||||
|
||||
// Then
|
||||
#expect(cache.cost(for: container) == 558000)
|
||||
}
|
||||
|
||||
@Test func imageContainerWithAssociatedDataCost() {
|
||||
// Given
|
||||
let data = Test.data(name: "cat", extension: "gif")
|
||||
let image = PlatformImage(data: data)!
|
||||
let container = ImageContainer(image: image, data: data)
|
||||
|
||||
// Then
|
||||
#expect(cache.cost(for: container) == 558000 + 427672)
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
// MARK: LRU
|
||||
|
||||
@Test func leastRecentItemsAreRemoved() {
|
||||
// Given
|
||||
let cost = cache.cost(for: ImageContainer(image: Test.image))
|
||||
cache.costLimit = Int(Double(cost) * 2.5)
|
||||
|
||||
cache[request1] = Test.container
|
||||
cache[request2] = Test.container
|
||||
cache[request3] = Test.container
|
||||
|
||||
// Then
|
||||
#expect(cache[request1] == nil)
|
||||
#expect(cache[request2] != nil)
|
||||
#expect(cache[request3] != nil)
|
||||
}
|
||||
|
||||
@Test func itemsAreTouched() {
|
||||
// Given
|
||||
let cost = cache.cost(for: ImageContainer(image: Test.image))
|
||||
cache.costLimit = Int(Double(cost) * 2.5)
|
||||
|
||||
cache[request1] = Test.container
|
||||
cache[request2] = Test.container
|
||||
_ = cache[request1] // Touched image
|
||||
|
||||
// When
|
||||
cache[request3] = Test.container
|
||||
|
||||
// Then
|
||||
#expect(cache[request1] != nil)
|
||||
#expect(cache[request2] == nil)
|
||||
#expect(cache[request3] != nil)
|
||||
}
|
||||
|
||||
// MARK: Misc
|
||||
|
||||
@Test func removeAll() {
|
||||
// GIVEN
|
||||
cache[request1] = Test.container
|
||||
cache[request2] = Test.container
|
||||
|
||||
// WHEN
|
||||
cache.removeAll()
|
||||
|
||||
// THEN
|
||||
#expect(cache.totalCount == 0)
|
||||
#expect(cache.totalCost == 0)
|
||||
}
|
||||
|
||||
#if canImport(UIKit)
|
||||
@Test @MainActor func someImagesAreRemovedOnDidEnterBackground() async {
|
||||
// GIVEN
|
||||
cache.costLimit = Int.max
|
||||
cache.countLimit = 10 // 1 out of 10 images should remain
|
||||
|
||||
for i in 0..<10 {
|
||||
cache[_request(index: i)] = Test.container
|
||||
}
|
||||
#expect(cache.totalCount == 10)
|
||||
|
||||
// WHEN
|
||||
let task = Task { @MainActor in
|
||||
NotificationCenter.default.post(name: UIApplication.didEnterBackgroundNotification, object: nil)
|
||||
|
||||
// THEN
|
||||
#expect(cache.totalCount == 1)
|
||||
}
|
||||
await task.value
|
||||
}
|
||||
|
||||
@Test @MainActor func someImagesAreRemovedBasedOnCostOnDidEnterBackground() async {
|
||||
// GIVEN
|
||||
let cost = cache.cost(for: ImageContainer(image: Test.image))
|
||||
cache.costLimit = cost * 10
|
||||
cache.countLimit = Int.max
|
||||
|
||||
for index in 0..<10 {
|
||||
let request = ImageRequest(url: URL(string: "http://example.com/img\(index)")!)
|
||||
cache[request] = Test.container
|
||||
}
|
||||
#expect(cache.totalCount == 10)
|
||||
|
||||
// WHEN
|
||||
let task = Task { @MainActor in
|
||||
NotificationCenter.default.post(name: UIApplication.didEnterBackgroundNotification, object: nil)
|
||||
|
||||
// THEN
|
||||
#expect(cache.totalCount == 1)
|
||||
}
|
||||
await task.value
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
@Suite struct InternalCacheTTLTests {
|
||||
let cache = Cache<Int, Int>(costLimit: 1000, countLimit: 1000)
|
||||
|
||||
// MARK: TTL
|
||||
|
||||
@Test func ttl() {
|
||||
// Given
|
||||
cache.set(1, forKey: 1, cost: 1, ttl: 0.05) // 50 ms
|
||||
#expect(cache.value(forKey: 1) != nil)
|
||||
|
||||
// When
|
||||
usleep(55 * 1000)
|
||||
|
||||
// Then
|
||||
#expect(cache.value(forKey: 1) == nil)
|
||||
}
|
||||
|
||||
@Test func defaultTTLIsUsed() {
|
||||
// Given
|
||||
cache.conf.ttl = 0.05// 50 ms
|
||||
cache.set(1, forKey: 1, cost: 1)
|
||||
#expect(cache.value(forKey: 1) != nil)
|
||||
|
||||
// When
|
||||
usleep(55 * 1000)
|
||||
|
||||
// Then
|
||||
#expect(cache.value(forKey: 1) == nil)
|
||||
}
|
||||
|
||||
@Test func defaultToNonExpiringEntries() {
|
||||
// Given
|
||||
cache.set(1, forKey: 1, cost: 1)
|
||||
#expect(cache.value(forKey: 1) != nil)
|
||||
|
||||
// When
|
||||
usleep(55 * 1000)
|
||||
|
||||
// Then
|
||||
#expect(cache.value(forKey: 1) != nil)
|
||||
}
|
||||
}
|
||||
|
||||
private func _request(index: Int) -> ImageRequest {
|
||||
return ImageRequest(url: URL(string: "http://example.com/img\(index)")!)
|
||||
}
|
||||
private let request1 = _request(index: 1)
|
||||
private let request2 = _request(index: 2)
|
||||
private let request3 = _request(index: 3)
|
||||
|
||||
class ImageCacheTests: XCTestCase, @unchecked Sendable {
|
||||
var cache: ImageCache!
|
||||
|
||||
override func setUp() {
|
||||
super.setUp()
|
||||
|
||||
cache = ImageCache()
|
||||
cache.entryCostLimit = 2
|
||||
}
|
||||
|
||||
// MARK: - Basics
|
||||
|
||||
@MainActor
|
||||
func testCacheCreation() {
|
||||
XCTAssertEqual(cache.totalCount, 0)
|
||||
XCTAssertNil(cache[Test.request])
|
||||
}
|
||||
|
||||
@MainActor
|
||||
func testThatImageIsStored() {
|
||||
// When
|
||||
cache[Test.request] = Test.container
|
||||
|
||||
// Then
|
||||
XCTAssertEqual(cache.totalCount, 1)
|
||||
XCTAssertNotNil(cache[Test.request])
|
||||
}
|
||||
|
||||
// MARK: - Subscript
|
||||
|
||||
@MainActor
|
||||
func testThatImageIsStoredUsingSubscript() {
|
||||
// When
|
||||
cache[Test.request] = Test.container
|
||||
|
||||
// Then
|
||||
XCTAssertNotNil(cache[Test.request])
|
||||
}
|
||||
|
||||
// MARK: - Count
|
||||
|
||||
@MainActor
|
||||
func testThatTotalCountChanges() {
|
||||
XCTAssertEqual(cache.totalCount, 0)
|
||||
|
||||
cache[request1] = Test.container
|
||||
XCTAssertEqual(cache.totalCount, 1)
|
||||
|
||||
cache[request2] = Test.container
|
||||
XCTAssertEqual(cache.totalCount, 2)
|
||||
|
||||
cache[request2] = nil
|
||||
XCTAssertEqual(cache.totalCount, 1)
|
||||
|
||||
cache[request1] = nil
|
||||
XCTAssertEqual(cache.totalCount, 0)
|
||||
}
|
||||
|
||||
@MainActor
|
||||
func testThatCountLimitChanges() {
|
||||
// When
|
||||
cache.countLimit = 1
|
||||
|
||||
// Then
|
||||
XCTAssertEqual(cache.countLimit, 1)
|
||||
}
|
||||
|
||||
@MainActor
|
||||
func testThatTTLChanges() {
|
||||
//when
|
||||
cache.ttl = 1
|
||||
|
||||
// Then
|
||||
XCTAssertEqual(cache.ttl, 1)
|
||||
}
|
||||
|
||||
@MainActor
|
||||
func testThatItemsAreRemoveImmediatelyWhenCountLimitIsReached() {
|
||||
// Given
|
||||
cache.countLimit = 1
|
||||
|
||||
// When
|
||||
cache[request1] = Test.container
|
||||
cache[request2] = Test.container
|
||||
|
||||
// Then
|
||||
XCTAssertNil(cache[request1])
|
||||
XCTAssertNotNil(cache[request2])
|
||||
}
|
||||
|
||||
@MainActor
|
||||
func testTrimToCount() {
|
||||
// Given
|
||||
cache[request1] = Test.container
|
||||
cache[request2] = Test.container
|
||||
|
||||
// When
|
||||
cache.trim(toCount: 1)
|
||||
|
||||
// Then
|
||||
XCTAssertNil(cache[request1])
|
||||
XCTAssertNotNil(cache[request2])
|
||||
}
|
||||
|
||||
@MainActor
|
||||
func testThatImagesAreRemovedOnCountLimitChange() {
|
||||
// Given
|
||||
cache.countLimit = 2
|
||||
|
||||
cache[request1] = Test.container
|
||||
cache[request2] = Test.container
|
||||
|
||||
// When
|
||||
cache.countLimit = 1
|
||||
|
||||
// Then
|
||||
XCTAssertNil(cache[request1])
|
||||
XCTAssertNotNil(cache[request2])
|
||||
}
|
||||
|
||||
// MARK: Cost
|
||||
|
||||
#if !os(macOS)
|
||||
|
||||
@MainActor
|
||||
func testDefaultImageCost() {
|
||||
XCTAssertEqual(cache.cost(for: ImageContainer(image: Test.image)), 1228800)
|
||||
}
|
||||
|
||||
@MainActor
|
||||
func testThatTotalCostChanges() {
|
||||
let imageCost = cache.cost(for: ImageContainer(image: Test.image))
|
||||
XCTAssertEqual(cache.totalCost, 0)
|
||||
|
||||
cache[request1] = Test.container
|
||||
XCTAssertEqual(cache.totalCost, imageCost)
|
||||
|
||||
cache[request2] = Test.container
|
||||
XCTAssertEqual(cache.totalCost, 2 * imageCost)
|
||||
|
||||
cache[request2] = nil
|
||||
XCTAssertEqual(cache.totalCost, imageCost)
|
||||
|
||||
cache[request1] = nil
|
||||
XCTAssertEqual(cache.totalCost, 0)
|
||||
}
|
||||
|
||||
@MainActor
|
||||
func testThatCostLimitChanged() {
|
||||
// Given
|
||||
let cost = cache.cost(for: ImageContainer(image: Test.image))
|
||||
|
||||
// When
|
||||
cache.costLimit = Int(Double(cost) * 1.5)
|
||||
|
||||
// Then
|
||||
XCTAssertEqual(cache.costLimit, Int(Double(cost) * 1.5))
|
||||
}
|
||||
|
||||
@MainActor
|
||||
func testThatItemsAreRemoveImmediatelyWhenCostLimitIsReached() {
|
||||
// Given
|
||||
let cost = cache.cost(for: ImageContainer(image: Test.image))
|
||||
cache.costLimit = Int(Double(cost) * 1.5)
|
||||
|
||||
// When/Then
|
||||
cache[request1] = Test.container
|
||||
|
||||
// LRU item is released
|
||||
cache[request2] = Test.container
|
||||
XCTAssertNil(cache[request1])
|
||||
XCTAssertNotNil(cache[request2])
|
||||
}
|
||||
|
||||
@MainActor
|
||||
func testEntryCostLimitEntryStored() {
|
||||
// Given
|
||||
let container = ImageContainer(image: Test.image)
|
||||
let cost = cache.cost(for: container)
|
||||
cache.costLimit = Int(Double(cost) * 15)
|
||||
cache.entryCostLimit = 0.1
|
||||
|
||||
// When
|
||||
cache[Test.request] = container
|
||||
|
||||
// Then
|
||||
XCTAssertNotNil(cache[Test.request])
|
||||
XCTAssertEqual(cache.totalCount, 1)
|
||||
}
|
||||
|
||||
@MainActor
|
||||
func testEntryCostLimitEntryNotStored() {
|
||||
// Given
|
||||
let container = ImageContainer(image: Test.image)
|
||||
let cost = cache.cost(for: container)
|
||||
cache.costLimit = Int(Double(cost) * 3)
|
||||
cache.entryCostLimit = 0.1
|
||||
|
||||
// When
|
||||
cache[Test.request] = container
|
||||
|
||||
// Then
|
||||
XCTAssertNil(cache[Test.request])
|
||||
XCTAssertEqual(cache.totalCount, 0)
|
||||
}
|
||||
|
||||
@MainActor
|
||||
func testTrimToCost() {
|
||||
// Given
|
||||
cache.costLimit = Int.max
|
||||
|
||||
cache[request1] = Test.container
|
||||
cache[request2] = Test.container
|
||||
|
||||
// When
|
||||
let cost = cache.cost(for: ImageContainer(image: Test.image))
|
||||
cache.trim(toCost: Int(Double(cost) * 1.5))
|
||||
|
||||
// Then
|
||||
XCTAssertNil(cache[request1])
|
||||
XCTAssertNotNil(cache[request2])
|
||||
}
|
||||
|
||||
@MainActor
|
||||
func testThatImagesAreRemovedOnCostLimitChange() {
|
||||
// Given
|
||||
let cost = cache.cost(for: ImageContainer(image: Test.image))
|
||||
cache.costLimit = Int(Double(cost) * 2.5)
|
||||
|
||||
cache[request1] = Test.container
|
||||
cache[request2] = Test.container
|
||||
|
||||
// When
|
||||
cache.costLimit = cost
|
||||
|
||||
// Then
|
||||
XCTAssertNil(cache[request1])
|
||||
XCTAssertNotNil(cache[request2])
|
||||
}
|
||||
|
||||
@MainActor
|
||||
func testImageContainerWithoutAssociatedDataCost() {
|
||||
// Given
|
||||
let data = Test.data(name: "cat", extension: "gif")
|
||||
let image = PlatformImage(data: data)!
|
||||
let container = ImageContainer(image: image, data: nil)
|
||||
|
||||
// Then
|
||||
XCTAssertEqual(cache.cost(for: container), 558000)
|
||||
}
|
||||
|
||||
@MainActor
|
||||
func testImageContainerWithAssociatedDataCost() {
|
||||
// Given
|
||||
let data = Test.data(name: "cat", extension: "gif")
|
||||
let image = PlatformImage(data: data)!
|
||||
let container = ImageContainer(image: image, data: data)
|
||||
|
||||
// Then
|
||||
XCTAssertEqual(cache.cost(for: container), 558000 + 427672)
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
// MARK: LRU
|
||||
|
||||
@MainActor
|
||||
func testThatLeastRecentItemsAreRemoved() {
|
||||
// Given
|
||||
let cost = cache.cost(for: ImageContainer(image: Test.image))
|
||||
cache.costLimit = Int(Double(cost) * 2.5)
|
||||
|
||||
cache[request1] = Test.container
|
||||
cache[request2] = Test.container
|
||||
cache[request3] = Test.container
|
||||
|
||||
// Then
|
||||
XCTAssertNil(cache[request1])
|
||||
XCTAssertNotNil(cache[request2])
|
||||
XCTAssertNotNil(cache[request3])
|
||||
}
|
||||
|
||||
@MainActor
|
||||
func testThatItemsAreTouched() {
|
||||
// Given
|
||||
let cost = cache.cost(for: ImageContainer(image: Test.image))
|
||||
cache.costLimit = Int(Double(cost) * 2.5)
|
||||
|
||||
cache[request1] = Test.container
|
||||
cache[request2] = Test.container
|
||||
_ = cache[request1] // Touched image
|
||||
|
||||
// When
|
||||
cache[request3] = Test.container
|
||||
|
||||
// Then
|
||||
XCTAssertNotNil(cache[request1])
|
||||
XCTAssertNil(cache[request2])
|
||||
XCTAssertNotNil(cache[request3])
|
||||
}
|
||||
|
||||
// MARK: Misc
|
||||
|
||||
@MainActor
|
||||
func testRemoveAll() {
|
||||
// GIVEN
|
||||
cache[request1] = Test.container
|
||||
cache[request2] = Test.container
|
||||
|
||||
// WHEN
|
||||
cache.removeAll()
|
||||
|
||||
// THEN
|
||||
XCTAssertEqual(cache.totalCount, 0)
|
||||
XCTAssertEqual(cache.totalCost, 0)
|
||||
}
|
||||
|
||||
#if os(iOS) || os(tvOS) || os(visionOS)
|
||||
@MainActor
|
||||
func testThatSomeImagesAreRemovedOnDidEnterBackground() async {
|
||||
// GIVEN
|
||||
cache.costLimit = Int.max
|
||||
cache.countLimit = 10 // 1 out of 10 images should remain
|
||||
|
||||
for i in 0..<10 {
|
||||
cache[_request(index: i)] = Test.container
|
||||
}
|
||||
XCTAssertEqual(cache.totalCount, 10)
|
||||
|
||||
// WHEN
|
||||
let task = Task { @MainActor in
|
||||
NotificationCenter.default.post(name: UIApplication.didEnterBackgroundNotification, object: nil)
|
||||
|
||||
// THEN
|
||||
XCTAssertEqual(cache.totalCount, 1)
|
||||
}
|
||||
await task.value
|
||||
}
|
||||
|
||||
@MainActor
|
||||
func testThatSomeImagesAreRemovedBasedOnCostOnDidEnterBackground() async {
|
||||
// GIVEN
|
||||
let cost = cache.cost(for: ImageContainer(image: Test.image))
|
||||
cache.costLimit = cost * 10
|
||||
cache.countLimit = Int.max
|
||||
|
||||
for index in 0..<10 {
|
||||
let request = ImageRequest(url: URL(string: "http://example.com/img\(index)")!)
|
||||
cache[request] = Test.container
|
||||
}
|
||||
XCTAssertEqual(cache.totalCount, 10)
|
||||
|
||||
// WHEN
|
||||
let task = Task { @MainActor in
|
||||
NotificationCenter.default.post(name: UIApplication.didEnterBackgroundNotification, object: nil)
|
||||
|
||||
// THEN
|
||||
XCTAssertEqual(cache.totalCount, 1)
|
||||
}
|
||||
await task.value
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
class InternalCacheTTLTests: XCTestCase {
|
||||
let cache = Cache<Int, Int>(costLimit: 1000, countLimit: 1000)
|
||||
|
||||
// MARK: TTL
|
||||
|
||||
@MainActor
|
||||
func testTTL() {
|
||||
// Given
|
||||
cache.set(1, forKey: 1, cost: 1, ttl: 0.05) // 50 ms
|
||||
XCTAssertNotNil(cache.value(forKey: 1))
|
||||
|
||||
// When
|
||||
usleep(55 * 1000)
|
||||
|
||||
// Then
|
||||
XCTAssertNil(cache.value(forKey: 1))
|
||||
}
|
||||
|
||||
@MainActor
|
||||
func testDefaultTTLIsUsed() {
|
||||
// Given
|
||||
cache.conf.ttl = 0.05// 50 ms
|
||||
cache.set(1, forKey: 1, cost: 1)
|
||||
XCTAssertNotNil(cache.value(forKey: 1))
|
||||
|
||||
// When
|
||||
usleep(55 * 1000)
|
||||
|
||||
// Then
|
||||
XCTAssertNil(cache.value(forKey: 1))
|
||||
}
|
||||
|
||||
@MainActor
|
||||
func testDefaultToNonExpiringEntries() {
|
||||
// Given
|
||||
cache.set(1, forKey: 1, cost: 1)
|
||||
XCTAssertNotNil(cache.value(forKey: 1))
|
||||
|
||||
// When
|
||||
usleep(55 * 1000)
|
||||
|
||||
// Then
|
||||
XCTAssertNotNil(cache.value(forKey: 1))
|
||||
}
|
||||
}
|
||||
|
@ -2,237 +2,238 @@
|
||||
//
|
||||
// Copyright (c) 2015-2024 Alexander Grebenyuk (github.com/kean).
|
||||
|
||||
import XCTest
|
||||
import Foundation
|
||||
import Testing
|
||||
@testable import Nuke
|
||||
|
||||
class ImageDecoderTests: XCTestCase {
|
||||
func testDecodePNG() throws {
|
||||
@Suite struct ImageDecoderTests {
|
||||
@Test func decodePNG() throws {
|
||||
// Given
|
||||
let data = Test.data(name: "fixture", extension: "png")
|
||||
let decoder = ImageDecoders.Default()
|
||||
|
||||
|
||||
// When
|
||||
let container = try XCTUnwrap(decoder.decode(data))
|
||||
|
||||
let container = try #require(try decoder.decode(data))
|
||||
|
||||
// Then
|
||||
XCTAssertEqual(container.type, .png)
|
||||
XCTAssertFalse(container.isPreview)
|
||||
XCTAssertNil(container.data)
|
||||
XCTAssertTrue(container.userInfo.isEmpty)
|
||||
#expect(container.type == .png)
|
||||
#expect(!container.isPreview)
|
||||
#expect(container.data == nil)
|
||||
#expect(container.userInfo.isEmpty)
|
||||
}
|
||||
|
||||
func testDecodeJPEG() throws {
|
||||
|
||||
@Test func decodeJPEG() throws {
|
||||
// Given
|
||||
let data = Test.data(name: "baseline", extension: "jpeg")
|
||||
let decoder = ImageDecoders.Default()
|
||||
|
||||
|
||||
// When
|
||||
let container = try XCTUnwrap(decoder.decode(data))
|
||||
|
||||
let container = try #require(try decoder.decode(data))
|
||||
|
||||
// Then
|
||||
XCTAssertEqual(container.type, .jpeg)
|
||||
XCTAssertFalse(container.isPreview)
|
||||
XCTAssertNil(container.data)
|
||||
XCTAssertTrue(container.userInfo.isEmpty)
|
||||
#expect(container.type == .jpeg)
|
||||
#expect(!container.isPreview)
|
||||
#expect(container.data == nil)
|
||||
#expect(container.userInfo.isEmpty)
|
||||
}
|
||||
|
||||
func testDecodingProgressiveJPEG() {
|
||||
|
||||
@Test func decodingProgressiveJPEG() {
|
||||
let data = Test.data(name: "progressive", extension: "jpeg")
|
||||
let decoder = ImageDecoders.Default()
|
||||
|
||||
|
||||
// Just before the Start Of Frame
|
||||
XCTAssertNil(decoder.decodePartiallyDownloadedData(data[0...358]))
|
||||
XCTAssertEqual(decoder.numberOfScans, 0)
|
||||
|
||||
#expect(decoder.decodePartiallyDownloadedData(data[0...358]) == nil)
|
||||
#expect(decoder.numberOfScans == 0)
|
||||
|
||||
// Right after the Start Of Frame
|
||||
XCTAssertNil(decoder.decodePartiallyDownloadedData(data[0...359]))
|
||||
XCTAssertEqual(decoder.numberOfScans, 0) // still haven't finished the first scan
|
||||
|
||||
#expect(decoder.decodePartiallyDownloadedData(data[0...359]) == nil)
|
||||
#expect(decoder.numberOfScans == 0) // still haven't finished the first scan // still haven't finished the first scan
|
||||
|
||||
// Just before the first Start Of Scan
|
||||
XCTAssertNil(decoder.decodePartiallyDownloadedData(data[0...438]))
|
||||
XCTAssertEqual(decoder.numberOfScans, 0) // still haven't finished the first scan
|
||||
|
||||
#expect(decoder.decodePartiallyDownloadedData(data[0...438]) == nil)
|
||||
#expect(decoder.numberOfScans == 0) // still haven't finished the first scan // still haven't finished the first scan
|
||||
|
||||
// Found the first Start Of Scan
|
||||
XCTAssertNil(decoder.decodePartiallyDownloadedData(data[0...439]))
|
||||
XCTAssertEqual(decoder.numberOfScans, 1)
|
||||
|
||||
#expect(decoder.decodePartiallyDownloadedData(data[0...439]) == nil)
|
||||
#expect(decoder.numberOfScans == 1)
|
||||
|
||||
// Found the second Start of Scan
|
||||
let scan1 = decoder.decodePartiallyDownloadedData(data[0...2952])
|
||||
XCTAssertNotNil(scan1)
|
||||
XCTAssertEqual(scan1?.isPreview, true)
|
||||
#expect(scan1 != nil)
|
||||
#expect(scan1?.isPreview == true)
|
||||
if let image = scan1?.image {
|
||||
#if os(macOS)
|
||||
XCTAssertEqual(image.size.width, 450)
|
||||
XCTAssertEqual(image.size.height, 300)
|
||||
#expect(image.size.width == 450)
|
||||
#expect(image.size.height == 300)
|
||||
#else
|
||||
XCTAssertEqual(image.size.width * image.scale, 450)
|
||||
XCTAssertEqual(image.size.height * image.scale, 300)
|
||||
#expect(image.size.width * image.scale == 450)
|
||||
#expect(image.size.height * image.scale == 300)
|
||||
#endif
|
||||
}
|
||||
XCTAssertEqual(decoder.numberOfScans, 2)
|
||||
XCTAssertEqual(scan1?.userInfo[.scanNumberKey] as? Int, 2)
|
||||
|
||||
#expect(decoder.numberOfScans == 2)
|
||||
#expect(scan1?.userInfo[.scanNumberKey] as? Int == 2)
|
||||
|
||||
// Feed all data and see how many scans are there
|
||||
// In practice the moment we finish receiving data we call
|
||||
// `decode(data: data, isCompleted: true)` so we might not scan all the
|
||||
// of the bytes and encounter all of the scans (e.g. the final chunk
|
||||
// of data that we receive contains multiple scans).
|
||||
XCTAssertNotNil(decoder.decodePartiallyDownloadedData(data))
|
||||
XCTAssertEqual(decoder.numberOfScans, 10)
|
||||
#expect(decoder.decodePartiallyDownloadedData(data) != nil)
|
||||
#expect(decoder.numberOfScans == 10)
|
||||
}
|
||||
|
||||
func testDecodeGIF() throws {
|
||||
|
||||
@Test func decodeGIF() throws {
|
||||
// Given
|
||||
let data = Test.data(name: "cat", extension: "gif")
|
||||
let decoder = ImageDecoders.Default()
|
||||
|
||||
|
||||
// When
|
||||
let container = try XCTUnwrap(decoder.decode(data))
|
||||
|
||||
let container = try #require(try decoder.decode(data))
|
||||
|
||||
// Then
|
||||
XCTAssertEqual(container.type, .gif)
|
||||
XCTAssertFalse(container.isPreview)
|
||||
XCTAssertNotNil(container.data)
|
||||
XCTAssertTrue(container.userInfo.isEmpty)
|
||||
#expect(container.type == .gif)
|
||||
#expect(!container.isPreview)
|
||||
#expect(container.data != nil)
|
||||
#expect(container.userInfo.isEmpty)
|
||||
}
|
||||
|
||||
func testDecodeHEIC() throws {
|
||||
|
||||
@Test func decodeHEIC() throws {
|
||||
// Given
|
||||
let data = Test.data(name: "img_751", extension: "heic")
|
||||
let decoder = ImageDecoders.Default()
|
||||
|
||||
|
||||
// When
|
||||
let container = try XCTUnwrap(decoder.decode(data))
|
||||
|
||||
let container = try #require(try decoder.decode(data))
|
||||
|
||||
// Then
|
||||
XCTAssertNil(container.type) // TODO: update when HEIF support is added
|
||||
XCTAssertFalse(container.isPreview)
|
||||
XCTAssertNil(container.data)
|
||||
XCTAssertTrue(container.userInfo.isEmpty)
|
||||
#expect(container.type == nil) // TODO: update when HEIF support is added // TODO: update when HEIF support is added
|
||||
#expect(!container.isPreview)
|
||||
#expect(container.data == nil)
|
||||
#expect(container.userInfo.isEmpty)
|
||||
}
|
||||
|
||||
func testDecodingGIFDataAttached() throws {
|
||||
|
||||
@Test func decodingGIFDataAttached() throws {
|
||||
let data = Test.data(name: "cat", extension: "gif")
|
||||
XCTAssertNotNil(try ImageDecoders.Default().decode(data).data)
|
||||
#expect(try ImageDecoders.Default().decode(data).data != nil)
|
||||
}
|
||||
|
||||
func testDecodingGIFPreview() throws {
|
||||
|
||||
@Test func decodingGIFPreview() throws {
|
||||
let data = Test.data(name: "cat", extension: "gif")
|
||||
XCTAssertEqual(data.count, 427672) // 427 KB
|
||||
#expect(data.count == 427672) // 427 KB // 427 KB
|
||||
let chunk = data[...60000] // 6 KB
|
||||
let response = try ImageDecoders.Default().decode(chunk)
|
||||
XCTAssertEqual(response.image.sizeInPixels, CGSize(width: 500, height: 279))
|
||||
#expect(response.image.sizeInPixels == CGSize(width: 500, height: 279))
|
||||
}
|
||||
|
||||
func testDecodingGIFPreviewGeneratedOnlyOnce() throws {
|
||||
|
||||
@Test func decodingGIFPreviewGeneratedOnlyOnce() throws {
|
||||
let data = Test.data(name: "cat", extension: "gif")
|
||||
XCTAssertEqual(data.count, 427672) // 427 KB
|
||||
#expect(data.count == 427672) // 427 KB // 427 KB
|
||||
let chunk = data[...60000] // 6 KB
|
||||
|
||||
|
||||
let context = ImageDecodingContext.mock(data: chunk)
|
||||
let decoder = try XCTUnwrap(ImageDecoders.Default(context: context))
|
||||
|
||||
XCTAssertNotNil(decoder.decodePartiallyDownloadedData(chunk))
|
||||
XCTAssertNil(decoder.decodePartiallyDownloadedData(chunk))
|
||||
let decoder = try #require(ImageDecoders.Default(context: context))
|
||||
|
||||
#expect(decoder.decodePartiallyDownloadedData(chunk) != nil)
|
||||
#expect(decoder.decodePartiallyDownloadedData(chunk) == nil)
|
||||
}
|
||||
|
||||
func testDecodingPNGDataNotAttached() throws {
|
||||
|
||||
@Test func decodingPNGDataNotAttached() throws {
|
||||
let data = Test.data(name: "fixture", extension: "png")
|
||||
let container = try ImageDecoders.Default().decode(data)
|
||||
XCTAssertNil(container.data)
|
||||
#expect(container.data == nil)
|
||||
}
|
||||
|
||||
|
||||
#if os(iOS) || os(tvOS) || os(macOS) || os(visionOS)
|
||||
func testDecodeBaselineWebP() throws {
|
||||
@Test func decodeBaselineWebP() throws {
|
||||
if #available(OSX 11.0, iOS 14.0, watchOS 7.0, tvOS 999.0, *) {
|
||||
let data = Test.data(name: "baseline", extension: "webp")
|
||||
let container = try ImageDecoders.Default().decode(data)
|
||||
XCTAssertEqual(container.image.sizeInPixels, CGSize(width: 550, height: 368))
|
||||
XCTAssertNil(container.data)
|
||||
#expect(container.image.sizeInPixels == CGSize(width: 550, height: 368))
|
||||
#expect(container.data == nil)
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
class ImageTypeTests: XCTestCase {
|
||||
@Suite struct ImageTypeTests {
|
||||
// MARK: PNG
|
||||
|
||||
func testDetectPNG() {
|
||||
|
||||
@Test func detectPNG() {
|
||||
let data = Test.data(name: "fixture", extension: "png")
|
||||
XCTAssertNil(AssetType(data[0..<1]))
|
||||
XCTAssertNil(AssetType(data[0..<7]))
|
||||
XCTAssertEqual(AssetType(data[0..<8]), .png)
|
||||
XCTAssertEqual(AssetType(data), .png)
|
||||
#expect(AssetType(data[0..<1]) == nil)
|
||||
#expect(AssetType(data[0..<7]) == nil)
|
||||
#expect(AssetType(data[0..<8]) == .png)
|
||||
#expect(AssetType(data) == .png)
|
||||
}
|
||||
|
||||
|
||||
// MARK: GIF
|
||||
|
||||
func testDetectGIF() {
|
||||
|
||||
@Test func detectGIF() {
|
||||
let data = Test.data(name: "cat", extension: "gif")
|
||||
XCTAssertEqual(AssetType(data), .gif)
|
||||
#expect(AssetType(data) == .gif)
|
||||
}
|
||||
|
||||
|
||||
// MARK: JPEG
|
||||
|
||||
func testDetectBaselineJPEG() {
|
||||
|
||||
@Test func detectBaselineJPEG() {
|
||||
let data = Test.data(name: "baseline", extension: "jpeg")
|
||||
XCTAssertNil(AssetType(data[0..<1]))
|
||||
XCTAssertNil(AssetType(data[0..<2]))
|
||||
XCTAssertEqual(AssetType(data[0..<3]), .jpeg)
|
||||
XCTAssertEqual(AssetType(data), .jpeg)
|
||||
#expect(AssetType(data[0..<1]) == nil)
|
||||
#expect(AssetType(data[0..<2]) == nil)
|
||||
#expect(AssetType(data[0..<3]) == .jpeg)
|
||||
#expect(AssetType(data) == .jpeg)
|
||||
}
|
||||
|
||||
func testDetectProgressiveJPEG() {
|
||||
|
||||
@Test func detectProgressiveJPEG() {
|
||||
let data = Test.data(name: "progressive", extension: "jpeg")
|
||||
// Not enough data
|
||||
XCTAssertNil(AssetType(Data()))
|
||||
XCTAssertNil(AssetType(data[0..<2]))
|
||||
|
||||
#expect(AssetType(Data()) == nil)
|
||||
#expect(AssetType(data[0..<2]) == nil)
|
||||
|
||||
// Enough to determine image format
|
||||
XCTAssertEqual(AssetType(data[0..<3]), .jpeg)
|
||||
XCTAssertEqual(AssetType(data[0..<33]), .jpeg)
|
||||
|
||||
#expect(AssetType(data[0..<3]) == .jpeg)
|
||||
#expect(AssetType(data[0..<33]) == .jpeg)
|
||||
|
||||
// Full image
|
||||
XCTAssertEqual(AssetType(data), .jpeg)
|
||||
#expect(AssetType(data) == .jpeg)
|
||||
}
|
||||
|
||||
|
||||
// MARK: WebP
|
||||
|
||||
func testDetectBaselineWebP() {
|
||||
|
||||
@Test func detectBaselineWebP() {
|
||||
let data = Test.data(name: "baseline", extension: "webp")
|
||||
XCTAssertNil(AssetType(data[0..<1]))
|
||||
XCTAssertNil(AssetType(data[0..<2]))
|
||||
XCTAssertEqual(AssetType(data[0..<12]), .webp)
|
||||
XCTAssertEqual(AssetType(data), .webp)
|
||||
#expect(AssetType(data[0..<1]) == nil)
|
||||
#expect(AssetType(data[0..<2]) == nil)
|
||||
#expect(AssetType(data[0..<12]) == .webp)
|
||||
#expect(AssetType(data) == .webp)
|
||||
}
|
||||
}
|
||||
|
||||
class ImagePropertiesTests: XCTestCase {
|
||||
@Suite struct ImagePropertiesTests {
|
||||
// MARK: JPEG
|
||||
|
||||
func testDetectBaselineJPEG() {
|
||||
|
||||
@Test func detectBaselineJPEG() {
|
||||
let data = Test.data(name: "baseline", extension: "jpeg")
|
||||
XCTAssertNil(ImageProperties.JPEG(data[0..<1]))
|
||||
XCTAssertNil(ImageProperties.JPEG(data[0..<2]))
|
||||
XCTAssertNil(ImageProperties.JPEG(data[0..<3]))
|
||||
XCTAssertEqual(ImageProperties.JPEG(data)?.isProgressive, false)
|
||||
#expect(ImageProperties.JPEG(data[0..<1]) == nil)
|
||||
#expect(ImageProperties.JPEG(data[0..<2]) == nil)
|
||||
#expect(ImageProperties.JPEG(data[0..<3]) == nil)
|
||||
#expect(ImageProperties.JPEG(data)?.isProgressive == false)
|
||||
}
|
||||
|
||||
func testDetectProgressiveJPEG() {
|
||||
|
||||
@Test func detectProgressiveJPEG() {
|
||||
let data = Test.data(name: "progressive", extension: "jpeg")
|
||||
// Not enough data
|
||||
XCTAssertNil(ImageProperties.JPEG(Data()))
|
||||
XCTAssertNil(ImageProperties.JPEG(data[0..<2]))
|
||||
|
||||
#expect(ImageProperties.JPEG(Data()) == nil)
|
||||
#expect(ImageProperties.JPEG(data[0..<2]) == nil)
|
||||
|
||||
// Enough to determine image format
|
||||
XCTAssertNil(ImageProperties.JPEG(data[0..<3]))
|
||||
XCTAssertNil(ImageProperties.JPEG(data[0...30]))
|
||||
|
||||
#expect(ImageProperties.JPEG(data[0..<3]) == nil)
|
||||
#expect(ImageProperties.JPEG(data[0...30]) == nil)
|
||||
|
||||
// Just before the first scan
|
||||
XCTAssertNil(ImageProperties.JPEG(data[0...358]))
|
||||
XCTAssertEqual(ImageProperties.JPEG(data[0...359])?.isProgressive, true)
|
||||
|
||||
#expect(ImageProperties.JPEG(data[0...358]) == nil)
|
||||
#expect(ImageProperties.JPEG(data[0...359])?.isProgressive == true)
|
||||
|
||||
// Full image
|
||||
XCTAssertEqual(ImageProperties.JPEG(data[0...359])?.isProgressive, true)
|
||||
#expect(ImageProperties.JPEG(data[0...359])?.isProgressive == true)
|
||||
}
|
||||
}
|
||||
|
@ -2,12 +2,13 @@
|
||||
//
|
||||
// Copyright (c) 2015-2024 Alexander Grebenyuk (github.com/kean).
|
||||
|
||||
import XCTest
|
||||
import Testing
|
||||
import Foundation
|
||||
@testable import Nuke
|
||||
|
||||
class ImageRequestTests: XCTestCase {
|
||||
@Suite struct ImageRequestTests {
|
||||
// The compiler picks up the new version
|
||||
func testInit() {
|
||||
@Test func testInit() {
|
||||
_ = ImageRequest(url: Test.url)
|
||||
_ = ImageRequest(url: Test.url, processors: [])
|
||||
_ = ImageRequest(url: Test.url, processors: [])
|
||||
@ -15,13 +16,13 @@ class ImageRequestTests: XCTestCase {
|
||||
_ = ImageRequest(url: Test.url, options: [.reloadIgnoringCachedData])
|
||||
}
|
||||
|
||||
func testExpressibleByStringLiteral() {
|
||||
@Test func expressibleByStringLiteral() {
|
||||
let _: ImageRequest = "https://example.com/image.jpeg"
|
||||
}
|
||||
|
||||
// MARK: - CoW
|
||||
|
||||
func testCopyOnWrite() {
|
||||
@Test func copyOnWrite() {
|
||||
// GIVEN
|
||||
var request = ImageRequest(url: URL(string: "http://test.com/1.png"))
|
||||
request.options.insert(.disableMemoryCacheReads)
|
||||
@ -35,165 +36,165 @@ class ImageRequestTests: XCTestCase {
|
||||
copy.priority = .low
|
||||
|
||||
// THEN
|
||||
XCTAssertEqual(copy.options.contains(.disableMemoryCacheReads), true)
|
||||
XCTAssertEqual(copy.userInfo["key"] as? String, "3")
|
||||
XCTAssertEqual((copy.processors.first as? MockImageProcessor)?.identifier, "4")
|
||||
XCTAssertEqual(request.priority, .high) // Original request no updated
|
||||
XCTAssertEqual(copy.priority, .low)
|
||||
#expect(copy.options.contains(.disableMemoryCacheReads) == true)
|
||||
#expect(copy.userInfo["key"] as? String == "3")
|
||||
#expect((copy.processors.first as? MockImageProcessor)?.identifier == "4")
|
||||
#expect(request.priority == .high) // Original request no updated // Original request no updated
|
||||
#expect(copy.priority == .low)
|
||||
}
|
||||
|
||||
// MARK: - Misc
|
||||
|
||||
// Just to make sure that comparison works as expected.
|
||||
func testPriorityComparison() {
|
||||
@Test func priorityComparison() {
|
||||
typealias Priority = ImageRequest.Priority
|
||||
XCTAssertTrue(Priority.veryLow < Priority.veryHigh)
|
||||
XCTAssertTrue(Priority.low < Priority.normal)
|
||||
XCTAssertTrue(Priority.normal == Priority.normal)
|
||||
#expect(Priority.veryLow < Priority.veryHigh)
|
||||
#expect(Priority.low < Priority.normal)
|
||||
#expect(Priority.normal == Priority.normal)
|
||||
}
|
||||
|
||||
func testUserInfoKey() {
|
||||
@Test func userInfoKey() {
|
||||
// WHEN
|
||||
let request = ImageRequest(url: Test.url, userInfo: [.init("a"): 1])
|
||||
|
||||
// THEN
|
||||
XCTAssertNotNil(request.userInfo["a"])
|
||||
#expect(request.userInfo["a"] != nil)
|
||||
}
|
||||
}
|
||||
|
||||
class ImageRequestCacheKeyTests: XCTestCase {
|
||||
func testDefaults() {
|
||||
@Suite struct ImageRequestCacheKeyTests {
|
||||
@Test func defaults() {
|
||||
let request = Test.request
|
||||
AssertHashableEqual(MemoryCacheKey(request), MemoryCacheKey(request)) // equal to itself
|
||||
expectHashableMatch(MemoryCacheKey(request), MemoryCacheKey(request)) // equal to itself
|
||||
}
|
||||
|
||||
func testRequestsWithTheSameURLsAreEquivalent() {
|
||||
@Test func requestsWithTheSameURLsAreEquivalent() {
|
||||
let lhs = ImageRequest(url: Test.url)
|
||||
let rhs = ImageRequest(url: Test.url)
|
||||
AssertHashableEqual(MemoryCacheKey(lhs), MemoryCacheKey(rhs))
|
||||
expectHashableMatch(MemoryCacheKey(lhs), MemoryCacheKey(rhs))
|
||||
}
|
||||
|
||||
func testRequestsWithDefaultURLRequestAndURLAreEquivalent() {
|
||||
@Test func requestsWithDefaultURLRequestAndURLAreEquivalent() {
|
||||
let lhs = ImageRequest(url: Test.url)
|
||||
let rhs = ImageRequest(urlRequest: URLRequest(url: Test.url))
|
||||
AssertHashableEqual(MemoryCacheKey(lhs), MemoryCacheKey(rhs))
|
||||
expectHashableMatch(MemoryCacheKey(lhs), MemoryCacheKey(rhs))
|
||||
}
|
||||
|
||||
func testRequestsWithDifferentURLsAreNotEquivalent() {
|
||||
@Test func requestsWithDifferentURLsAreNotEquivalent() {
|
||||
let lhs = ImageRequest(url: URL(string: "http://test.com/1.png"))
|
||||
let rhs = ImageRequest(url: URL(string: "http://test.com/2.png"))
|
||||
XCTAssertNotEqual(MemoryCacheKey(lhs), MemoryCacheKey(rhs))
|
||||
#expect(MemoryCacheKey(lhs) != MemoryCacheKey(rhs))
|
||||
}
|
||||
|
||||
func testRequestsWithTheSameProcessorsAreEquivalent() {
|
||||
@Test func requestsWithTheSameProcessorsAreEquivalent() {
|
||||
let lhs = ImageRequest(url: Test.url, processors: [MockImageProcessor(id: "1")])
|
||||
let rhs = ImageRequest(url: Test.url, processors: [MockImageProcessor(id: "1")])
|
||||
AssertHashableEqual(MemoryCacheKey(lhs), MemoryCacheKey(rhs))
|
||||
expectHashableMatch(MemoryCacheKey(lhs), MemoryCacheKey(rhs))
|
||||
}
|
||||
|
||||
func testRequestsWithDifferentProcessorsAreNotEquivalent() {
|
||||
@Test func requestsWithDifferentProcessorsAreNotEquivalent() {
|
||||
let lhs = ImageRequest(url: Test.url, processors: [MockImageProcessor(id: "1")])
|
||||
let rhs = ImageRequest(url: Test.url, processors: [MockImageProcessor(id: "2")])
|
||||
XCTAssertNotEqual(MemoryCacheKey(lhs), MemoryCacheKey(rhs))
|
||||
#expect(MemoryCacheKey(lhs) != MemoryCacheKey(rhs))
|
||||
}
|
||||
|
||||
func testURLRequestParametersAreIgnored() {
|
||||
@Test func uRLRequestParametersAreIgnored() {
|
||||
let lhs = ImageRequest(urlRequest: URLRequest(url: Test.url, cachePolicy: .reloadRevalidatingCacheData, timeoutInterval: 50))
|
||||
let rhs = ImageRequest(urlRequest: URLRequest(url: Test.url, cachePolicy: .useProtocolCachePolicy, timeoutInterval: 0))
|
||||
AssertHashableEqual(MemoryCacheKey(lhs), MemoryCacheKey(rhs))
|
||||
expectHashableMatch(MemoryCacheKey(lhs), MemoryCacheKey(rhs))
|
||||
}
|
||||
|
||||
func testSettingDefaultProcessorManually() {
|
||||
@Test func settingDefaultProcessorManually() {
|
||||
let lhs = ImageRequest(url: Test.url)
|
||||
let rhs = ImageRequest(url: Test.url, processors: lhs.processors)
|
||||
AssertHashableEqual(MemoryCacheKey(lhs), MemoryCacheKey(rhs))
|
||||
expectHashableMatch(MemoryCacheKey(lhs), MemoryCacheKey(rhs))
|
||||
}
|
||||
}
|
||||
|
||||
class ImageRequestLoadKeyTests: XCTestCase {
|
||||
func testDefaults() {
|
||||
@Suite struct ImageRequestLoadKeyTests {
|
||||
@Test func defaults() {
|
||||
let request = ImageRequest(url: Test.url)
|
||||
AssertHashableEqual(TaskFetchOriginalDataKey(request), TaskFetchOriginalDataKey(request))
|
||||
expectHashableMatch(TaskFetchOriginalDataKey(request), TaskFetchOriginalDataKey(request))
|
||||
}
|
||||
|
||||
func testRequestsWithTheSameURLsAreEquivalent() {
|
||||
@Test func requestsWithTheSameURLsAreEquivalent() {
|
||||
let lhs = ImageRequest(url: Test.url)
|
||||
let rhs = ImageRequest(url: Test.url)
|
||||
AssertHashableEqual(TaskFetchOriginalDataKey(lhs), TaskFetchOriginalDataKey(rhs))
|
||||
expectHashableMatch(TaskFetchOriginalDataKey(lhs), TaskFetchOriginalDataKey(rhs))
|
||||
}
|
||||
|
||||
func testRequestsWithDifferentURLsAreNotEquivalent() {
|
||||
@Test func requestsWithDifferentURLsAreNotEquivalent() {
|
||||
let lhs = ImageRequest(url: URL(string: "http://test.com/1.png"))
|
||||
let rhs = ImageRequest(url: URL(string: "http://test.com/2.png"))
|
||||
XCTAssertNotEqual(TaskFetchOriginalDataKey(lhs), TaskFetchOriginalDataKey(rhs))
|
||||
#expect(TaskFetchOriginalDataKey(lhs) != TaskFetchOriginalDataKey(rhs))
|
||||
}
|
||||
|
||||
func testRequestsWithTheSameProcessorsAreEquivalent() {
|
||||
@Test func requestsWithTheSameProcessorsAreEquivalent() {
|
||||
let lhs = ImageRequest(url: Test.url, processors: [MockImageProcessor(id: "1")])
|
||||
let rhs = ImageRequest(url: Test.url, processors: [MockImageProcessor(id: "1")])
|
||||
AssertHashableEqual(TaskFetchOriginalDataKey(lhs), TaskFetchOriginalDataKey(rhs))
|
||||
expectHashableMatch(TaskFetchOriginalDataKey(lhs), TaskFetchOriginalDataKey(rhs))
|
||||
}
|
||||
|
||||
func testRequestsWithDifferentProcessorsAreEquivalent() {
|
||||
@Test func requestsWithDifferentProcessorsAreEquivalent() {
|
||||
let lhs = ImageRequest(url: Test.url, processors: [MockImageProcessor(id: "1")])
|
||||
let rhs = ImageRequest(url: Test.url, processors: [MockImageProcessor(id: "2")])
|
||||
AssertHashableEqual(TaskFetchOriginalDataKey(lhs), TaskFetchOriginalDataKey(rhs))
|
||||
expectHashableMatch(TaskFetchOriginalDataKey(lhs), TaskFetchOriginalDataKey(rhs))
|
||||
}
|
||||
|
||||
func testRequestWithDifferentURLRequestParametersAreNotEquivalent() {
|
||||
@Test func requestWithDifferentURLRequestParametersAreNotEquivalent() {
|
||||
let lhs = ImageRequest(urlRequest: URLRequest(url: Test.url, cachePolicy: .reloadRevalidatingCacheData, timeoutInterval: 50))
|
||||
let rhs = ImageRequest(urlRequest: URLRequest(url: Test.url, cachePolicy: .useProtocolCachePolicy, timeoutInterval: 0))
|
||||
XCTAssertNotEqual(TaskFetchOriginalDataKey(lhs), TaskFetchOriginalDataKey(rhs))
|
||||
#expect(TaskFetchOriginalDataKey(lhs) != TaskFetchOriginalDataKey(rhs))
|
||||
}
|
||||
|
||||
func testMockImageProcessorCorrectlyImplementsIdentifiers() {
|
||||
XCTAssertEqual(MockImageProcessor(id: "1").identifier, MockImageProcessor(id: "1").identifier)
|
||||
XCTAssertEqual(MockImageProcessor(id: "1").hashableIdentifier, MockImageProcessor(id: "1").hashableIdentifier)
|
||||
@Test func mockImageProcessorCorrectlyImplementsIdentifiers() {
|
||||
#expect(MockImageProcessor(id: "1").identifier == MockImageProcessor(id: "1").identifier)
|
||||
#expect(MockImageProcessor(id: "1").hashableIdentifier == MockImageProcessor(id: "1").hashableIdentifier)
|
||||
|
||||
XCTAssertNotEqual(MockImageProcessor(id: "1").identifier, MockImageProcessor(id: "2").identifier)
|
||||
XCTAssertNotEqual(MockImageProcessor(id: "1").hashableIdentifier, MockImageProcessor(id: "2").hashableIdentifier)
|
||||
#expect(MockImageProcessor(id: "1").identifier != MockImageProcessor(id: "2").identifier)
|
||||
#expect(MockImageProcessor(id: "1").hashableIdentifier != MockImageProcessor(id: "2").hashableIdentifier)
|
||||
}
|
||||
}
|
||||
|
||||
class ImageRequestImageIdTests: XCTestCase {
|
||||
func testThatCacheKeyUsesAbsoluteURLByDefault() {
|
||||
@Suite struct ImageRequestImageIdTests {
|
||||
@Test func thatCacheKeyUsesAbsoluteURLByDefault() {
|
||||
let lhs = ImageRequest(url: Test.url)
|
||||
let rhs = ImageRequest(url: Test.url.appendingPathComponent("?token=1"))
|
||||
XCTAssertNotEqual(MemoryCacheKey(lhs), MemoryCacheKey(rhs))
|
||||
#expect(MemoryCacheKey(lhs) != MemoryCacheKey(rhs))
|
||||
}
|
||||
|
||||
func testThatCacheKeyUsesFilteredURLWhenSet() {
|
||||
@Test func thatCacheKeyUsesFilteredURLWhenSet() {
|
||||
let lhs = ImageRequest(url: Test.url, userInfo: [.imageIdKey: Test.url.absoluteString])
|
||||
let rhs = ImageRequest(url: Test.url.appendingPathComponent("?token=1"), userInfo: [.imageIdKey: Test.url.absoluteString])
|
||||
AssertHashableEqual(MemoryCacheKey(lhs), MemoryCacheKey(rhs))
|
||||
expectHashableMatch(MemoryCacheKey(lhs), MemoryCacheKey(rhs))
|
||||
}
|
||||
|
||||
func testThatCacheKeyForProcessedImageDataUsesAbsoluteURLByDefault() {
|
||||
@Test func thatCacheKeyForProcessedImageDataUsesAbsoluteURLByDefault() {
|
||||
let lhs = ImageRequest(url: Test.url)
|
||||
let rhs = ImageRequest(url: Test.url.appendingPathComponent("?token=1"))
|
||||
XCTAssertNotEqual(MemoryCacheKey(lhs), MemoryCacheKey(rhs))
|
||||
#expect(MemoryCacheKey(lhs) != MemoryCacheKey(rhs))
|
||||
}
|
||||
|
||||
func testThatCacheKeyForProcessedImageDataUsesFilteredURLWhenSet() {
|
||||
@Test func thatCacheKeyForProcessedImageDataUsesFilteredURLWhenSet() {
|
||||
let lhs = ImageRequest(url: Test.url, userInfo: [.imageIdKey: Test.url.absoluteString])
|
||||
let rhs = ImageRequest(url: Test.url.appendingPathComponent("?token=1"), userInfo: [.imageIdKey: Test.url.absoluteString])
|
||||
AssertHashableEqual(MemoryCacheKey(lhs), MemoryCacheKey(rhs))
|
||||
expectHashableMatch(MemoryCacheKey(lhs), MemoryCacheKey(rhs))
|
||||
}
|
||||
|
||||
func testThatLoadKeyForProcessedImageDoesntUseFilteredURL() {
|
||||
@Test func thatLoadKeyForProcessedImageDoesntUseFilteredURL() {
|
||||
let lhs = ImageRequest(url: Test.url, userInfo: [.imageIdKey: Test.url.absoluteString])
|
||||
let rhs = ImageRequest(url: Test.url.appendingPathComponent("?token=1"), userInfo: [.imageIdKey: Test.url.absoluteString])
|
||||
XCTAssertNotEqual(TaskLoadImageKey(lhs), TaskLoadImageKey(rhs))
|
||||
#expect(TaskLoadImageKey(lhs) != TaskLoadImageKey(rhs))
|
||||
}
|
||||
|
||||
func testThatLoadKeyForOriginalImageDoesntUseFilteredURL() {
|
||||
@Test func thatLoadKeyForOriginalImageDoesntUseFilteredURL() {
|
||||
let lhs = ImageRequest(url: Test.url, userInfo: [.imageIdKey: Test.url.absoluteString])
|
||||
let rhs = ImageRequest(url: Test.url.appendingPathComponent("?token=1"), userInfo: [.imageIdKey: Test.url.absoluteString])
|
||||
XCTAssertNotEqual(TaskFetchOriginalDataKey(lhs), TaskFetchOriginalDataKey(rhs))
|
||||
#expect(TaskFetchOriginalDataKey(lhs) != TaskFetchOriginalDataKey(rhs))
|
||||
}
|
||||
}
|
||||
|
||||
private func AssertHashableEqual<T: Hashable>(_ lhs: T, _ rhs: T, file: StaticString = #file, line: UInt = #line) {
|
||||
XCTAssertEqual(lhs.hashValue, rhs.hashValue, file: file, line: line)
|
||||
XCTAssertEqual(lhs, rhs, file: file, line: line)
|
||||
private func expectHashableMatch<T: Hashable>(_ lhs: T, _ rhs: T) {
|
||||
#expect(lhs.hashValue == rhs.hashValue)
|
||||
#expect(lhs == rhs)
|
||||
}
|
||||
|
@ -2,74 +2,46 @@
|
||||
//
|
||||
// Copyright (c) 2015-2024 Alexander Grebenyuk (github.com/kean).
|
||||
|
||||
import XCTest
|
||||
import Testing
|
||||
@testable import Nuke
|
||||
|
||||
class RateLimiterTests: XCTestCase {
|
||||
var rateLimiter: RateLimiter!
|
||||
@Suite @ImagePipelineActor struct RateLimiterTests {
|
||||
let rateLimiter = RateLimiter(rate: 10, burst: 2)
|
||||
|
||||
override func setUp() {
|
||||
super.setUp()
|
||||
|
||||
// Note: we set very short rate to avoid bucket form being refilled too quickly
|
||||
rateLimiter = RateLimiter(rate: 10, burst: 2)
|
||||
}
|
||||
|
||||
@ImagePipelineActor
|
||||
func testThatBurstIsExecutedimmediately() {
|
||||
// Given
|
||||
@Test func burstIsExecutedImmediately() {
|
||||
var isExecuted = Array(repeating: false, count: 4)
|
||||
|
||||
// When
|
||||
for i in isExecuted.indices {
|
||||
rateLimiter.execute {
|
||||
isExecuted[i] = true
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// Then
|
||||
XCTAssertEqual(isExecuted, [true, true, false, false], "Expect first 2 items to be executed immediately")
|
||||
#expect(isExecuted == [true, true, false, false], "Expect first 2 items to be executed immediately")
|
||||
}
|
||||
|
||||
@ImagePipelineActor
|
||||
func testThatNotExecutedItemDoesntExtractFromBucket() {
|
||||
// Given
|
||||
@Test func posponedItemsDoNotExtractFromBucket() {
|
||||
var isExecuted = Array(repeating: false, count: 4)
|
||||
|
||||
// When
|
||||
for i in isExecuted.indices {
|
||||
rateLimiter.execute {
|
||||
isExecuted[i] = true
|
||||
return i != 1 // important!
|
||||
}
|
||||
}
|
||||
|
||||
// Then
|
||||
XCTAssertEqual(isExecuted, [true, true, true, false], "Expect first 2 items to be executed immediately")
|
||||
#expect(isExecuted == [true, true, true, false], "Expect first 2 items to be executed immediately")
|
||||
}
|
||||
|
||||
@ImagePipelineActor
|
||||
func testOverflow() {
|
||||
// Given
|
||||
var isExecuted = Array(repeating: false, count: 3)
|
||||
|
||||
// When
|
||||
let expectation = self.expectation(description: "All work executed")
|
||||
expectation.expectedFulfillmentCount = isExecuted.count
|
||||
|
||||
for i in isExecuted.indices {
|
||||
rateLimiter.execute {
|
||||
isExecuted[i] = true
|
||||
expectation.fulfill()
|
||||
return true
|
||||
|
||||
@Test func overflow() async {
|
||||
let count = 3
|
||||
await confirmation(expectedCount: count) { done in
|
||||
for _ in 0..<count {
|
||||
await withUnsafeContinuation { continuation in
|
||||
rateLimiter.execute {
|
||||
done()
|
||||
continuation.resume(returning: ())
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// When time is passed
|
||||
wait()
|
||||
|
||||
// Then
|
||||
XCTAssertEqual(isExecuted, [true, true, true], "Expect 3rd item to be executed after a short delay")
|
||||
}
|
||||
}
|
||||
|
@ -2,14 +2,14 @@
|
||||
//
|
||||
// Copyright (c) 2015-2024 Alexander Grebenyuk (github.com/kean).
|
||||
|
||||
import XCTest
|
||||
import Testing
|
||||
import Foundation
|
||||
@testable import Nuke
|
||||
|
||||
@ImagePipelineActor
|
||||
class TaskTests: XCTestCase {
|
||||
@Suite @ImagePipelineActor struct TaskTests {
|
||||
// MARK: - Starter
|
||||
|
||||
func testStarterCalledOnFirstSubscription() {
|
||||
@Test func starterCalledOnFirstSubscription() {
|
||||
// Given
|
||||
var startCount = 0
|
||||
_ = SimpleTask<Int, Error>(starter: { _ in
|
||||
@ -17,10 +17,10 @@ class TaskTests: XCTestCase {
|
||||
})
|
||||
|
||||
// Then
|
||||
XCTAssertEqual(startCount, 0)
|
||||
#expect(startCount == 0)
|
||||
}
|
||||
|
||||
func testStarterCalledWhenSubscriptionIsAdded() {
|
||||
@Test func starterCalledWhenSubscriptionIsAdded() {
|
||||
// Given
|
||||
var startCount = 0
|
||||
let task = SimpleTask<Int, Error>(starter: { _ in
|
||||
@ -31,10 +31,10 @@ class TaskTests: XCTestCase {
|
||||
_ = task.subscribe { _ in }
|
||||
|
||||
// Then started is called
|
||||
XCTAssertEqual(startCount, 1)
|
||||
#expect(startCount == 1)
|
||||
}
|
||||
|
||||
func testStarterOnlyCalledOnce() {
|
||||
@Test func starterOnlyCalledOnce() {
|
||||
// Given
|
||||
var startCount = 0
|
||||
let task = SimpleTask<Int, Error>(starter: { _ in
|
||||
@ -46,10 +46,10 @@ class TaskTests: XCTestCase {
|
||||
_ = task.subscribe { _ in }
|
||||
|
||||
// Then started is only called once
|
||||
XCTAssertEqual(startCount, 1)
|
||||
#expect(startCount == 1)
|
||||
}
|
||||
|
||||
func testStarterIsDeallocated() {
|
||||
@Test func tarterIsDeallocated() {
|
||||
// Given
|
||||
class Foo {
|
||||
}
|
||||
@ -64,18 +64,18 @@ class TaskTests: XCTestCase {
|
||||
})
|
||||
}
|
||||
|
||||
XCTAssertNotNil(weakFoo, "Foo is retained by starter")
|
||||
#expect(weakFoo != nil, "Foo is retained by starter")
|
||||
|
||||
// When first subscription is added and starter is called
|
||||
_ = task.subscribe { _ in }
|
||||
|
||||
// Then
|
||||
XCTAssertNil(weakFoo, "Started wasn't deallocated")
|
||||
#expect(weakFoo == nil, "Started wasn't deallocated")
|
||||
}
|
||||
|
||||
// MARK: - Subscribe
|
||||
|
||||
func testWhenSubscriptionAddedEventsAreForwarded() {
|
||||
@Test func whenSubscriptionAddedEventsAreForwarded() {
|
||||
// Given
|
||||
let task = SimpleTask<Int, MyError>(starter: {
|
||||
$0.send(progress: TaskProgress(completed: 1, total: 2))
|
||||
@ -91,7 +91,7 @@ class TaskTests: XCTestCase {
|
||||
}
|
||||
|
||||
// Then
|
||||
XCTAssertEqual(recordedEvents, [
|
||||
#expect(recordedEvents == [
|
||||
.progress(TaskProgress(completed: 1, total: 2)),
|
||||
.value(1, isCompleted: false),
|
||||
.progress(TaskProgress(completed: 2, total: 2)),
|
||||
@ -99,7 +99,7 @@ class TaskTests: XCTestCase {
|
||||
])
|
||||
}
|
||||
|
||||
func testBothSubscriptionsReceiveEvents() {
|
||||
@Test func bothSubscriptionsReceiveEvents() {
|
||||
// Given
|
||||
let task = AsyncTask<Int, MyError>()
|
||||
|
||||
@ -107,20 +107,20 @@ class TaskTests: XCTestCase {
|
||||
var eventCount = 0
|
||||
|
||||
_ = task.subscribe { event in
|
||||
XCTAssertEqual(event, .value(1, isCompleted: false))
|
||||
#expect(event == .value(1, isCompleted: false))
|
||||
eventCount += 1 }
|
||||
_ = task.subscribe { event in
|
||||
XCTAssertEqual(event, .value(1, isCompleted: false))
|
||||
#expect(event == .value(1, isCompleted: false))
|
||||
eventCount += 1
|
||||
}
|
||||
|
||||
task.send(value: 1)
|
||||
|
||||
// Then
|
||||
XCTAssertEqual(eventCount, 2)
|
||||
#expect(eventCount == 2)
|
||||
}
|
||||
|
||||
func testCantSubscribeToAlreadyCancelledTask() {
|
||||
@Test func cantSubscribeToAlreadyCancelledTask() {
|
||||
// Given
|
||||
let task = SimpleTask<Int, MyError>(starter: { _ in })
|
||||
let subscription = task.subscribe { _ in }
|
||||
@ -129,10 +129,10 @@ class TaskTests: XCTestCase {
|
||||
subscription?.unsubscribe()
|
||||
|
||||
// Then
|
||||
XCTAssertNil(task.subscribe { _ in })
|
||||
#expect(task.subscribe { _ in } == nil)
|
||||
}
|
||||
|
||||
func testCantSubscribeToAlreadySucceededTask() {
|
||||
@Test func cantSubscribeToAlreadySucceededTask() {
|
||||
// Given
|
||||
let task = AsyncTask<Int, MyError>()
|
||||
_ = task.subscribe { _ in }
|
||||
@ -141,10 +141,10 @@ class TaskTests: XCTestCase {
|
||||
task.send(value: 1, isCompleted: true)
|
||||
|
||||
// Then
|
||||
XCTAssertNil(task.subscribe { _ in })
|
||||
#expect(task.subscribe { _ in } == nil)
|
||||
}
|
||||
|
||||
func testCantSubscribeToAlreadyFailedTasks() {
|
||||
@Test func cantSubscribeToAlreadyFailedTasks() {
|
||||
// Given
|
||||
let task = AsyncTask<Int, MyError>()
|
||||
_ = task.subscribe { _ in }
|
||||
@ -153,29 +153,27 @@ class TaskTests: XCTestCase {
|
||||
task.send(error: .init(raw: "1"))
|
||||
|
||||
// Then
|
||||
XCTAssertNil(task.subscribe { _ in })
|
||||
#expect(task.subscribe { _ in } == nil)
|
||||
}
|
||||
|
||||
func testSubscribeToTaskWithSynchronousCompletionReturnsNil() {
|
||||
@Test func subscribeToTaskWithSynchronousCompletionReturnsNil() async {
|
||||
// Given
|
||||
let task = SimpleTask<Int, MyError> { (task) in
|
||||
task.send(value: 0, isCompleted: true)
|
||||
}
|
||||
|
||||
// When
|
||||
let expectation = self.expectation(description: "Observer called")
|
||||
let subscription = task.subscribe { _ in
|
||||
expectation.fulfill()
|
||||
// When/Then
|
||||
await withUnsafeContinuation { continuation in
|
||||
let subscription = task.subscribe { _ in
|
||||
continuation.resume()
|
||||
}
|
||||
#expect(subscription == nil)
|
||||
}
|
||||
|
||||
// Then
|
||||
XCTAssertNil(subscription)
|
||||
wait()
|
||||
}
|
||||
|
||||
// MARK: - Ubsubscribe
|
||||
|
||||
func testWhenSubscriptionIsRemovedNoEventsAreSent() {
|
||||
@Test func whenSubscriptionIsRemovedNoEventsAreSent() {
|
||||
// Given
|
||||
let task = AsyncTask<Int, MyError>()
|
||||
var recordedEvents = [AsyncTask<Int, MyError>.Event]()
|
||||
@ -186,10 +184,10 @@ class TaskTests: XCTestCase {
|
||||
task.send(value: 1)
|
||||
|
||||
// Then
|
||||
XCTAssertTrue(recordedEvents.isEmpty, "Expect no events to be received by observer after subscription is removed")
|
||||
#expect(recordedEvents.isEmpty, "Expect no events to be received by observer after subscription is removed")
|
||||
}
|
||||
|
||||
func testWhenSubscriptionIsRemovedTaskBecomesDisposed() {
|
||||
@Test func whenSubscriptionIsRemovedTaskBecomesDisposed() {
|
||||
// Given
|
||||
let task = AsyncTask<Int, MyError>()
|
||||
let subscription = task.subscribe { _ in }
|
||||
@ -198,10 +196,10 @@ class TaskTests: XCTestCase {
|
||||
subscription?.unsubscribe()
|
||||
|
||||
// Then
|
||||
XCTAssertTrue(task.isDisposed, "Expect task to be marked as disposed")
|
||||
#expect(task.isDisposed, "Expect task to be marked as disposed")
|
||||
}
|
||||
|
||||
func testWhenSubscriptionIsRemovedOnCancelIsCalled() {
|
||||
@Test func whenSubscriptionIsRemovedOnCancelIsCalled() {
|
||||
// Given
|
||||
let task = AsyncTask<Int, MyError>()
|
||||
let subscription = task.subscribe { _ in }
|
||||
@ -215,39 +213,39 @@ class TaskTests: XCTestCase {
|
||||
subscription?.unsubscribe()
|
||||
|
||||
// Then
|
||||
XCTAssertTrue(onCancelledIsCalled)
|
||||
#expect(onCancelledIsCalled)
|
||||
}
|
||||
|
||||
func testWhenSubscriptionIsRemovedOperationIsCancelled() {
|
||||
@Test func whenSubscriptionIsRemovedOperationIsCancelled() {
|
||||
// Given
|
||||
let operation = Foundation.Operation()
|
||||
let task = SimpleTask<Int, MyError>(starter: { $0.operation = operation })
|
||||
let subscription = task.subscribe { _ in }
|
||||
XCTAssertFalse(operation.isCancelled)
|
||||
#expect(!operation.isCancelled)
|
||||
|
||||
// When
|
||||
subscription?.unsubscribe()
|
||||
|
||||
// Then
|
||||
XCTAssertTrue(operation.isCancelled)
|
||||
#expect(operation.isCancelled)
|
||||
}
|
||||
|
||||
func testWhenSubscriptionIsRemovedDependencyIsCancelled() {
|
||||
@Test func whenSubscriptionIsRemovedDependencyIsCancelled() {
|
||||
// Given
|
||||
let operation = Foundation.Operation()
|
||||
let dependency = SimpleTask<Int, MyError>(starter: { $0.operation = operation })
|
||||
let task = SimpleTask<Int, MyError>(starter: { $0.dependency = dependency.subscribe { _ in } })
|
||||
let subscription = task.subscribe { _ in }
|
||||
XCTAssertFalse(operation.isCancelled)
|
||||
#expect(!operation.isCancelled)
|
||||
|
||||
// When
|
||||
subscription?.unsubscribe()
|
||||
|
||||
// Then
|
||||
XCTAssertTrue(operation.isCancelled)
|
||||
#expect(operation.isCancelled)
|
||||
}
|
||||
|
||||
func testWhenOneOfTwoSubscriptionsAreRemovedTaskNotCancelled() {
|
||||
@Test func whenOneOfTwoSubscriptionsAreRemovedTaskNotCancelled() {
|
||||
// Given
|
||||
let operation = Foundation.Operation()
|
||||
let task = SimpleTask<Int, MyError>(starter: { $0.operation = operation })
|
||||
@ -258,10 +256,10 @@ class TaskTests: XCTestCase {
|
||||
subscription1?.unsubscribe()
|
||||
|
||||
// Then
|
||||
XCTAssertFalse(operation.isCancelled)
|
||||
#expect(!operation.isCancelled)
|
||||
}
|
||||
|
||||
func testWhenTwoOfTwoSubscriptionsAreRemovedTaskIsCancelled() {
|
||||
@Test func whenTwoOfTwoSubscriptionsAreRemovedTaskIsCancelled() {
|
||||
// Given
|
||||
let operation = Foundation.Operation()
|
||||
let task = SimpleTask<Int, MyError>(starter: { $0.operation = operation })
|
||||
@ -273,12 +271,12 @@ class TaskTests: XCTestCase {
|
||||
subscription2?.unsubscribe()
|
||||
|
||||
// Then
|
||||
XCTAssertTrue(operation.isCancelled)
|
||||
#expect(operation.isCancelled)
|
||||
}
|
||||
|
||||
// MARK: - Priority
|
||||
|
||||
func testWhenPriorityIsUpdatedOperationPriorityAlsoUpdated() {
|
||||
@Test func whenPriorityIsUpdatedOperationPriorityAlsoUpdated() {
|
||||
// Given
|
||||
let operation = Foundation.Operation()
|
||||
let task = SimpleTask<Int, MyError>(starter: { $0.operation = operation })
|
||||
@ -288,10 +286,10 @@ class TaskTests: XCTestCase {
|
||||
subscription?.setPriority(.high)
|
||||
|
||||
// Then
|
||||
XCTAssertEqual(operation.queuePriority, .high)
|
||||
#expect(operation.queuePriority == .high)
|
||||
}
|
||||
|
||||
func testWhenTaskChangesOperationPriorityUpdated() { // Or sets operation later
|
||||
@Test func whenTaskChangesOperationPriorityUpdated() { // Or sets operation later
|
||||
// Given
|
||||
let task = AsyncTask<Int, MyError>()
|
||||
let subscription = task.subscribe { _ in }
|
||||
@ -302,10 +300,10 @@ class TaskTests: XCTestCase {
|
||||
task.operation = operation
|
||||
|
||||
// Then
|
||||
XCTAssertEqual(operation.queuePriority, .high)
|
||||
#expect(operation.queuePriority == .high)
|
||||
}
|
||||
|
||||
func testThatPriorityCanBeLowered() {
|
||||
@Test func priorityCanBeLowered() {
|
||||
// Given
|
||||
let operation = Foundation.Operation()
|
||||
let task = SimpleTask<Int, MyError>(starter: { $0.operation = operation })
|
||||
@ -315,10 +313,10 @@ class TaskTests: XCTestCase {
|
||||
subscription?.setPriority(.low)
|
||||
|
||||
// Then
|
||||
XCTAssertEqual(operation.queuePriority, .low)
|
||||
#expect(operation.queuePriority == .low)
|
||||
}
|
||||
|
||||
func testThatPriorityEqualMaximumPriorityOfAllSubscriptions() {
|
||||
@Test func priorityEqualMaximumPriorityOfAllSubscriptions() {
|
||||
// Given
|
||||
let operation = Foundation.Operation()
|
||||
let task = SimpleTask<Int, MyError>(starter: { $0.operation = operation })
|
||||
@ -330,10 +328,10 @@ class TaskTests: XCTestCase {
|
||||
subscription2?.setPriority(.high)
|
||||
|
||||
// Then
|
||||
XCTAssertEqual(operation.queuePriority, .high)
|
||||
#expect(operation.queuePriority == .high)
|
||||
}
|
||||
|
||||
func testWhenSubscriptionIsRemovedPriorityIsUpdated() {
|
||||
@Test func subscriptionIsRemovedPriorityIsUpdated() {
|
||||
// Given
|
||||
let operation = Foundation.Operation()
|
||||
let task = SimpleTask<Int, MyError>(starter: { $0.operation = operation })
|
||||
@ -347,10 +345,10 @@ class TaskTests: XCTestCase {
|
||||
subscription2?.unsubscribe()
|
||||
|
||||
// Then
|
||||
XCTAssertEqual(operation.queuePriority, .low)
|
||||
#expect(operation.queuePriority == .low)
|
||||
}
|
||||
|
||||
func testWhenSubscriptionLowersPriorityButExistingSubscriptionHasHigherPriporty() {
|
||||
@Test func whenSubscriptionLowersPriorityButExistingSubscriptionHasHigherPriporty() {
|
||||
// Given
|
||||
let operation = Foundation.Operation()
|
||||
let task = SimpleTask<Int, MyError>(starter: { $0.operation = operation })
|
||||
@ -362,10 +360,10 @@ class TaskTests: XCTestCase {
|
||||
subscription1?.setPriority(.low)
|
||||
|
||||
// Then order of updating sub
|
||||
XCTAssertEqual(operation.queuePriority, .high)
|
||||
#expect(operation.queuePriority == .high)
|
||||
}
|
||||
|
||||
func testPriorityOfDependencyUpdated() {
|
||||
@Test func priorityOfDependencyUpdated() {
|
||||
// Given
|
||||
let operation = Foundation.Operation()
|
||||
let dependency = SimpleTask<Int, MyError>(starter: { $0.operation = operation })
|
||||
@ -376,12 +374,12 @@ class TaskTests: XCTestCase {
|
||||
subscription?.setPriority(.high)
|
||||
|
||||
// Then
|
||||
XCTAssertEqual(operation.queuePriority, .high)
|
||||
#expect(operation.queuePriority == .high)
|
||||
}
|
||||
|
||||
// MARK: - Dispose
|
||||
|
||||
func testExecutingTaskIsntDisposed() {
|
||||
@Test func executingTaskIsntDisposed() {
|
||||
// Given
|
||||
let task = AsyncTask<Int, MyError>()
|
||||
var isDisposeCalled = false
|
||||
@ -392,11 +390,11 @@ class TaskTests: XCTestCase {
|
||||
task.send(value: 1) // Casually sending value
|
||||
|
||||
// Then
|
||||
XCTAssertFalse(isDisposeCalled)
|
||||
XCTAssertFalse(task.isDisposed)
|
||||
#expect(!isDisposeCalled)
|
||||
#expect(!task.isDisposed)
|
||||
}
|
||||
|
||||
func testThatTaskIsDisposedWhenCancelled() {
|
||||
@Test func taskIsDisposedWhenCancelled() {
|
||||
// Given
|
||||
let task = SimpleTask<Int, MyError>(starter: { _ in })
|
||||
var isDisposeCalled = false
|
||||
@ -407,11 +405,11 @@ class TaskTests: XCTestCase {
|
||||
subscription?.unsubscribe()
|
||||
|
||||
// Then
|
||||
XCTAssertTrue(isDisposeCalled)
|
||||
XCTAssertTrue(task.isDisposed)
|
||||
#expect(isDisposeCalled)
|
||||
#expect(task.isDisposed)
|
||||
}
|
||||
|
||||
func testThatTaskIsDisposedWhenCompletedWithSuccess() {
|
||||
@Test func taskIsDisposedWhenCompletedWithSuccess() {
|
||||
// Given
|
||||
let task = AsyncTask<Int, MyError>()
|
||||
var isDisposeCalled = false
|
||||
@ -422,11 +420,11 @@ class TaskTests: XCTestCase {
|
||||
task.send(value: 1, isCompleted: true)
|
||||
|
||||
// Then
|
||||
XCTAssertTrue(isDisposeCalled)
|
||||
XCTAssertTrue(task.isDisposed)
|
||||
#expect(isDisposeCalled)
|
||||
#expect(task.isDisposed)
|
||||
}
|
||||
|
||||
func testThatTaskIsDisposedWhenCompletedWithFailure() {
|
||||
@Test func taskIsDisposedWhenCompletedWithFailure() {
|
||||
// Given
|
||||
let task = AsyncTask<Int, MyError>()
|
||||
var isDisposeCalled = false
|
||||
@ -437,8 +435,8 @@ class TaskTests: XCTestCase {
|
||||
task.send(error: .init(raw: "1"))
|
||||
|
||||
// Then
|
||||
XCTAssertTrue(isDisposeCalled)
|
||||
XCTAssertTrue(task.isDisposed)
|
||||
#expect(isDisposeCalled)
|
||||
#expect(task.isDisposed)
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user