From 78198c8b6822ad5a56eb552b38a07791de5dd964 Mon Sep 17 00:00:00 2001 From: NachoSoto Date: Sun, 30 Nov 2025 11:06:56 +0900 Subject: [PATCH 01/13] Add macOS CI workflow --- .github/workflows/build-and-test.yml | 31 ++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 .github/workflows/build-and-test.yml diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml new file mode 100644 index 0000000..d7a62a5 --- /dev/null +++ b/.github/workflows/build-and-test.yml @@ -0,0 +1,31 @@ +name: Build and Test + +on: + pull_request: + branches: + - master + - develop + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + test: + runs-on: macos-latest + timeout-minutes: 30 + env: + CI: true + + steps: + - name: Check out repository + uses: actions/checkout@v4 + + - name: Run test suite + uses: mxcl/xcodebuild@v3 + with: + xcode: '26.0' + scheme: AsyncImageView + platform: iOS + action: test + verbosity: xcbeautify From 018deb4c9aa7e7119a46e796864dbecdd608a76e Mon Sep 17 00:00:00 2001 From: NachoSoto Date: Sun, 30 Nov 2025 11:37:39 +0900 Subject: [PATCH 02/13] Update .github/workflows/build-and-test.yml --- .github/workflows/build-and-test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index d7a62a5..c41e331 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -25,7 +25,7 @@ jobs: uses: mxcl/xcodebuild@v3 with: xcode: '26.0' - scheme: AsyncImageView + scheme: AsyncImageView-AsyncImageView platform: iOS action: test verbosity: xcbeautify From de902381d76b2004c142033a2f1bb78b56e1204b Mon Sep 17 00:00:00 2001 From: NachoSoto Date: Sun, 30 Nov 2025 11:37:46 +0900 Subject: [PATCH 03/13] Update .github/workflows/build-and-test.yml --- .github/workflows/build-and-test.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index c41e331..795be38 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -4,7 +4,6 @@ on: pull_request: branches: - master - - develop concurrency: group: ${{ github.workflow }}-${{ github.ref }} From b69be945cd70d02b91fca30dc4e43d590010d3df Mon Sep 17 00:00:00 2001 From: NachoSoto Date: Sun, 30 Nov 2025 11:41:15 +0900 Subject: [PATCH 04/13] Update build-and-test.yml --- .github/workflows/build-and-test.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index 795be38..20414cd 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -20,6 +20,9 @@ jobs: - name: Check out repository uses: actions/checkout@v4 + - name: List schemes + run: xcodebuild -list + - name: Run test suite uses: mxcl/xcodebuild@v3 with: From 0e75c91bdde9fd95e171a628ad6a931558c95794 Mon Sep 17 00:00:00 2001 From: NachoSoto Date: Sun, 30 Nov 2025 11:41:38 +0900 Subject: [PATCH 05/13] Update build-and-test.yml --- .github/workflows/build-and-test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index 20414cd..3de7ad8 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -26,7 +26,7 @@ jobs: - name: Run test suite uses: mxcl/xcodebuild@v3 with: - xcode: '26.0' + xcode: '26.1' scheme: AsyncImageView-AsyncImageView platform: iOS action: test From 826d93aa650cb5c25ee58a0859c6dcbce5a5799a Mon Sep 17 00:00:00 2001 From: NachoSoto Date: Sun, 30 Nov 2025 11:49:56 +0900 Subject: [PATCH 06/13] Update build-and-test.yml --- .github/workflows/build-and-test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index 3de7ad8..213e65f 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -27,7 +27,7 @@ jobs: uses: mxcl/xcodebuild@v3 with: xcode: '26.1' - scheme: AsyncImageView-AsyncImageView + scheme: AsyncImageView platform: iOS action: test verbosity: xcbeautify From cc70c65fb7fe18581920d0e1d27c003ad9841c94 Mon Sep 17 00:00:00 2001 From: NachoSoto Date: Sun, 30 Nov 2025 12:07:58 +0900 Subject: [PATCH 07/13] Fix QuickSpec overrides and imports (#62) --- AsyncImageViewTests/AsyncImageViewSpec.swift | 4 +- AsyncImageViewTests/CachingSpec.swift | 8 ++- .../ImageInflaterRendererSpec.swift | 3 +- .../MulticastedRendererSpec.swift | 5 +- Package.resolved | 63 +++++++++++++++++++ 5 files changed, 77 insertions(+), 6 deletions(-) diff --git a/AsyncImageViewTests/AsyncImageViewSpec.swift b/AsyncImageViewTests/AsyncImageViewSpec.swift index 164d9ac..2447163 100644 --- a/AsyncImageViewTests/AsyncImageViewSpec.swift +++ b/AsyncImageViewTests/AsyncImageViewSpec.swift @@ -8,13 +8,15 @@ import Quick import Nimble +import UIKit +import XCTest import ReactiveSwift import AsyncImageView class AsyncImageViewSpec: QuickSpec { - override func spec() { + override class func spec() { describe("AsyncImageView") { // To ensure that updating UI takes one extra cycle // and we can verify image is reset before that diff --git a/AsyncImageViewTests/CachingSpec.swift b/AsyncImageViewTests/CachingSpec.swift index baad1eb..20b2ea2 100644 --- a/AsyncImageViewTests/CachingSpec.swift +++ b/AsyncImageViewTests/CachingSpec.swift @@ -8,6 +8,8 @@ import Quick import Nimble +import Foundation +import CoreGraphics import AsyncImageView @@ -62,7 +64,7 @@ private func testCache( } class InMemoryCacheSpec: QuickSpec { - override func spec() { + override class func spec() { describe("InMemoryCache") { testCache( cacheCreator: { InMemoryCache(cacheName: "test") }, @@ -74,7 +76,7 @@ class InMemoryCacheSpec: QuickSpec { } class DiskCacheSpec: QuickSpec { - override func spec() { + override class func spec() { describe("DiskCache") { let directoryCreator = { return URL(fileURLWithPath: NSTemporaryDirectory(), isDirectory: true) @@ -109,7 +111,7 @@ class DiskCacheSpec: QuickSpec { } class RenderDataTypeCacheSubdirectorySpec: QuickSpec { - override func spec() { + override class func spec() { it("works with integer sizes") { expect(subdirectoryForSize(CGSize(width: 15.0, height: 10.0))) == "15.00x10.00" } diff --git a/AsyncImageViewTests/ImageInflaterRendererSpec.swift b/AsyncImageViewTests/ImageInflaterRendererSpec.swift index 706d994..8a9f3f8 100644 --- a/AsyncImageViewTests/ImageInflaterRendererSpec.swift +++ b/AsyncImageViewTests/ImageInflaterRendererSpec.swift @@ -8,11 +8,12 @@ import Quick import Nimble +import CoreGraphics import AsyncImageView class ImageInflaterRendererSpec: QuickSpec { - override func spec() { + override class func spec() { describe("ImageInflaterRenderer") { context("Aspect Fit") { it("returns identity frame if sizes match") { diff --git a/AsyncImageViewTests/MulticastedRendererSpec.swift b/AsyncImageViewTests/MulticastedRendererSpec.swift index 34c6cde..710acaf 100644 --- a/AsyncImageViewTests/MulticastedRendererSpec.swift +++ b/AsyncImageViewTests/MulticastedRendererSpec.swift @@ -8,13 +8,16 @@ import Quick import Nimble +import UIKit +import Foundation +import XCTest import ReactiveSwift import AsyncImageView class MulticastedRendererSpec: QuickSpec { - override func spec() { + override class func spec() { describe("MulticastedRenderer") { let data: TestData = .a let size = CGSize(width: 1, height: 1) diff --git a/Package.resolved b/Package.resolved index 013ea3b..ad6b40e 100644 --- a/Package.resolved +++ b/Package.resolved @@ -1,5 +1,41 @@ { "pins" : [ + { + "identity" : "cwlcatchexception", + "kind" : "remoteSourceControl", + "location" : "https://github.com/mattgallagher/CwlCatchException.git", + "state" : { + "revision" : "07b2ba21d361c223e25e3c1e924288742923f08c", + "version" : "2.2.1" + } + }, + { + "identity" : "cwlpreconditiontesting", + "kind" : "remoteSourceControl", + "location" : "https://github.com/mattgallagher/CwlPreconditionTesting.git", + "state" : { + "revision" : "0139c665ebb45e6a9fbdb68aabfd7c39f3fe0071", + "version" : "2.2.2" + } + }, + { + "identity" : "nimble", + "kind" : "remoteSourceControl", + "location" : "https://github.com/Quick/Nimble.git", + "state" : { + "revision" : "edaedc1ec86f14ac6e2ca495b94f0ff7150d98d0", + "version" : "12.3.0" + } + }, + { + "identity" : "quick", + "kind" : "remoteSourceControl", + "location" : "https://github.com/Quick/Quick.git", + "state" : { + "revision" : "1163a1b1b114a657c7432b63dd1f92ce99fe11a6", + "version" : "7.6.2" + } + }, { "identity" : "reactiveswift", "kind" : "remoteSourceControl", @@ -8,6 +44,33 @@ "revision" : "40c465af19b993344e84355c00669ba2022ca3cd", "version" : "7.1.1" } + }, + { + "identity" : "swift-algorithms", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-algorithms.git", + "state" : { + "revision" : "87e50f483c54e6efd60e885f7f5aa946cee68023", + "version" : "1.2.1" + } + }, + { + "identity" : "swift-argument-parser", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-argument-parser.git", + "state" : { + "revision" : "cdd0ef3755280949551dc26dee5de9ddeda89f54", + "version" : "1.6.2" + } + }, + { + "identity" : "swift-numerics", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-numerics.git", + "state" : { + "revision" : "0c0290ff6b24942dadb83a929ffaaa1481df04a2", + "version" : "1.1.1" + } } ], "version" : 2 From 4220b8e400bd3614d536134cc055d17fe2e886a3 Mon Sep 17 00:00:00 2001 From: NachoSoto Date: Sun, 30 Nov 2025 12:19:20 +0900 Subject: [PATCH 08/13] Fix cache specs to compile with Quick (#63) ## Summary - inline the cache behavior specs inside their respective `QuickSpec` classes so the Quick DSL is available when compiling ## Testing - `swift test` *(fails: Combine is unavailable on Linux in this environment)* ------ [Codex Task](https://chatgpt.com/codex/tasks/task_e_692bb5ae6b2c8320b9952183021160ba) --- AsyncImageViewTests/CachingSpec.swift | 128 +++++++++++++++----------- 1 file changed, 74 insertions(+), 54 deletions(-) diff --git a/AsyncImageViewTests/CachingSpec.swift b/AsyncImageViewTests/CachingSpec.swift index 20b2ea2..4aeb97d 100644 --- a/AsyncImageViewTests/CachingSpec.swift +++ b/AsyncImageViewTests/CachingSpec.swift @@ -13,64 +13,50 @@ import CoreGraphics import AsyncImageView -private func testCache( - cacheCreator: @escaping () -> T, - keyCreator: @escaping () -> T.Key, - valueCreator: @escaping () -> T.Value -) - where T.Value: Equatable -{ - var cache: T! - - beforeEach { - cache = cacheCreator() - } +class InMemoryCacheSpec: QuickSpec { + override class func spec() { + describe("InMemoryCache") { + var cache: InMemoryCache! - it("returns nil when not cached") { - expect(cache.valueForKey(keyCreator())).to(beNil()) - } + beforeEach { + cache = InMemoryCache(cacheName: "test") + } - it("recovers value after saving it") { - let key = keyCreator() - let value = valueCreator() + it("returns nil when not cached") { + expect(cache.valueForKey(UUID().uuidString)).to(beNil()) + } - cache.setValue(value, forKey: key) + it("recovers value after saving it") { + let key = UUID().uuidString + let value = String.randomReadableString() - expect(cache.valueForKey(key)) == value - } + cache.setValue(value, forKey: key) - it("values don't override") { - let key1 = keyCreator() - let key2 = keyCreator() - let value1 = valueCreator() - let value2 = valueCreator() + expect(cache.valueForKey(key)) == value + } - cache.setValue(value1, forKey: key1) - cache.setValue(value2, forKey: key2) + it("values don't override") { + let key1 = UUID().uuidString + let key2 = UUID().uuidString + let value1 = String.randomReadableString() + let value2 = String.randomReadableString() - expect(cache.valueForKey(key1)) == value1 - expect(cache.valueForKey(key2)) == value2 - } + cache.setValue(value1, forKey: key1) + cache.setValue(value2, forKey: key2) - it("can remove a value") { - let key = keyCreator() - let value = valueCreator() + expect(cache.valueForKey(key1)) == value1 + expect(cache.valueForKey(key2)) == value2 + } - cache.setValue(value, forKey: key) - cache.setValue(nil, forKey: key) + it("can remove a value") { + let key = UUID().uuidString + let value = String.randomReadableString() - expect(cache.valueForKey(key)).to(beNil()) - } -} + cache.setValue(value, forKey: key) + cache.setValue(nil, forKey: key) -class InMemoryCacheSpec: QuickSpec { - override class func spec() { - describe("InMemoryCache") { - testCache( - cacheCreator: { InMemoryCache(cacheName: "test") }, - keyCreator: String.randomReadableString, - valueCreator: String.randomReadableString - ) + expect(cache.valueForKey(key)).to(beNil()) + } } } } @@ -83,13 +69,47 @@ class DiskCacheSpec: QuickSpec { .appendingPathComponent(ProcessInfo.processInfo.globallyUniqueString, isDirectory: true) } - testCache( - cacheCreator: { () -> DiskCache in - return DiskCache(rootDirectory: directoryCreator()) - }, - keyCreator: String.randomReadableString, - valueCreator: String.randomReadableString - ) + var cache: DiskCache! + + beforeEach { + cache = DiskCache(rootDirectory: directoryCreator()) + } + + it("returns nil when not cached") { + expect(cache.valueForKey(UUID().uuidString)).to(beNil()) + } + + it("recovers value after saving it") { + let key = UUID().uuidString + let value = String.randomReadableString() + + cache.setValue(value, forKey: key) + + expect(cache.valueForKey(key)) == value + } + + it("values don't override") { + let key1 = UUID().uuidString + let key2 = UUID().uuidString + let value1 = String.randomReadableString() + let value2 = String.randomReadableString() + + cache.setValue(value1, forKey: key1) + cache.setValue(value2, forKey: key2) + + expect(cache.valueForKey(key1)) == value1 + expect(cache.valueForKey(key2)) == value2 + } + + it("can remove a value") { + let key = UUID().uuidString + let value = String.randomReadableString() + + cache.setValue(value, forKey: key) + cache.setValue(nil, forKey: key) + + expect(cache.valueForKey(key)).to(beNil()) + } it("saves files in subdirectory") { func readFile(_ url: URL) -> String? { From 74e13356daf231277501389ef0a2724dd375a65d Mon Sep 17 00:00:00 2001 From: NachoSoto Date: Sun, 30 Nov 2025 12:34:36 +0900 Subject: [PATCH 09/13] Fix test warnings --- AsyncImageViewTests/CachingSpec.swift | 4 ++-- AsyncImageViewTests/MulticastedRendererSpec.swift | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/AsyncImageViewTests/CachingSpec.swift b/AsyncImageViewTests/CachingSpec.swift index 4aeb97d..f08fb6f 100644 --- a/AsyncImageViewTests/CachingSpec.swift +++ b/AsyncImageViewTests/CachingSpec.swift @@ -142,7 +142,7 @@ class RenderDataTypeCacheSubdirectorySpec: QuickSpec { } } -extension String: DataFileType { +@retroactive extension String: DataFileType { public var subdirectory: String? { return "\(self.count)" } @@ -152,7 +152,7 @@ extension String: DataFileType { } } -extension String: NSDataConvertible { +@retroactive extension String: NSDataConvertible { public init?(data: Data) { self.init(data: data, encoding: String.encoding) } diff --git a/AsyncImageViewTests/MulticastedRendererSpec.swift b/AsyncImageViewTests/MulticastedRendererSpec.swift index 710acaf..c54eca2 100644 --- a/AsyncImageViewTests/MulticastedRendererSpec.swift +++ b/AsyncImageViewTests/MulticastedRendererSpec.swift @@ -34,7 +34,7 @@ class MulticastedRendererSpec: QuickSpec { } func getImageForData(_ data: TestData, _ size: CGSize) -> ImageResult? { - return try? getProducerForData(data, size) + return getProducerForData(data, size) .single()? .get() } @@ -56,8 +56,8 @@ class MulticastedRendererSpec: QuickSpec { let result2 = getProducerForData(data, size) // Starting the producers should yield the same image. - guard let image1 = try? result1.single()?.get().image else { XCTFail("Failed to produce image"); return } - guard let image2 = try? result2.single()?.get().image else { XCTFail("Failed to produce image"); return } + guard let image1 = result1.single()?.get().image else { XCTFail("Failed to produce image"); return } + guard let image2 = result2.single()?.get().image else { XCTFail("Failed to produce image"); return } expect(image1) === image2 } @@ -82,7 +82,7 @@ class MulticastedRendererSpec: QuickSpec { } func getImageForData(_ data: TestData, _ size: CGSize) -> ImageResult? { - return try? getProducerForData(data, size) + return getProducerForData(data, size) .single()? .get() } From ad370b68d90e2256d3464446cce8a0bf5dfe7da7 Mon Sep 17 00:00:00 2001 From: NachoSoto Date: Sun, 30 Nov 2025 12:44:40 +0900 Subject: [PATCH 10/13] Update CachingSpec.swift --- AsyncImageViewTests/CachingSpec.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AsyncImageViewTests/CachingSpec.swift b/AsyncImageViewTests/CachingSpec.swift index f08fb6f..ebb4d38 100644 --- a/AsyncImageViewTests/CachingSpec.swift +++ b/AsyncImageViewTests/CachingSpec.swift @@ -152,7 +152,7 @@ class RenderDataTypeCacheSubdirectorySpec: QuickSpec { } } -@retroactive extension String: NSDataConvertible { +extension String: @retroactive NSDataConvertible { public init?(data: Data) { self.init(data: data, encoding: String.encoding) } From 8ff5e52ac44b9076523abd60810d435667d74d45 Mon Sep 17 00:00:00 2001 From: NachoSoto Date: Sun, 30 Nov 2025 12:44:48 +0900 Subject: [PATCH 11/13] Update CachingSpec.swift --- AsyncImageViewTests/CachingSpec.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AsyncImageViewTests/CachingSpec.swift b/AsyncImageViewTests/CachingSpec.swift index ebb4d38..b7f7127 100644 --- a/AsyncImageViewTests/CachingSpec.swift +++ b/AsyncImageViewTests/CachingSpec.swift @@ -142,7 +142,7 @@ class RenderDataTypeCacheSubdirectorySpec: QuickSpec { } } -@retroactive extension String: DataFileType { +extension String: @retroactive DataFileType { public var subdirectory: String? { return "\(self.count)" } From 321641e82815f87882e096f7a0f02c558428d4fd Mon Sep 17 00:00:00 2001 From: NachoSoto Date: Sun, 30 Nov 2025 13:06:33 +0900 Subject: [PATCH 12/13] Add warnings-as-errors flag to CI build (#65) --- .github/workflows/build-and-test.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index 3f6a186..0ced1f5 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -25,4 +25,5 @@ jobs: scheme: AsyncImageView platform: iOS action: test + warnings-as-errors: true verbosity: xcbeautify From 9c4b53293c413a3dd3d418d0a1f48a4cde7038f3 Mon Sep 17 00:00:00 2001 From: NachoSoto Date: Sun, 30 Nov 2025 13:10:21 +0900 Subject: [PATCH 13/13] Update build-and-test.yml --- .github/workflows/build-and-test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index 0ced1f5..cd49a70 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -25,5 +25,5 @@ jobs: scheme: AsyncImageView platform: iOS action: test - warnings-as-errors: true + warnings-as-errors: false verbosity: xcbeautify