Skip to content

Commit 3ef2d04

Browse files
committed
Fix tests and Linux support
## Changes - Fix unit tests. - Remove unnecessary Combine imports - Fix imports on Linux. - Fix unit tests on Linux + Add missing compile checks for `XCTExpectFailure` in Linux + Disable tests that use `@MainActor` on Linux because of an issue gathering tests: https://github.com/apple/swift-corelibs-xctest/issues/424 - Remove Swift installation from SwiftPM Linux job, as Swift is already included in `ubuntu-latest` (`ubuntu-20.04`): https://github.com/actions/runner-images/blob/main/images/linux/Ubuntu2004-Readme.md#language-and-runtime
1 parent 19cb15f commit 3ef2d04

40 files changed

+2192
-2151
lines changed

.github/workflows/ci.yml

+4-11
Original file line numberDiff line numberDiff line change
@@ -45,15 +45,8 @@ jobs:
4545
name: SwiftPM Linux
4646
runs-on: ubuntu-latest
4747
steps:
48-
- name: Install Swift
49-
run: |
50-
eval "$(curl -sL https://swiftenv.fuller.li/install.sh)"
51-
- name: Checkout
52-
uses: actions/checkout@v2
53-
- name: Pull dependencies
54-
run: |
55-
swift package resolve
48+
- uses: actions/checkout@v2
49+
- name: Swift version
50+
run: swift --version
5651
- name: Test via SwiftPM
57-
run: |
58-
swift --version
59-
swift test --enable-test-discovery
52+
run: swift test --enable-test-discovery

Examples/CaseStudies/CaseStudies.xcodeproj/project.pbxproj

+1
Original file line numberDiff line numberDiff line change
@@ -374,6 +374,7 @@
374374
DC4C6EA82450DD380066A05D /* UIKitCaseStudies */,
375375
DC4C6EBF2450DD390066A05D /* UIKitCaseStudiesTests */,
376376
);
377+
indentWidth = 2;
377378
sourceTree = "<group>";
378379
};
379380
DC89C41424460F95006900B9 /* Products */ = {

Examples/CaseStudies/SwiftUICaseStudiesTests/01-GettingStarted-AnimationsTests.swift

+43-43
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import XCTest
66

77
@MainActor
88
class AnimationTests: XCTestCase {
9-
func testRainbow() {
9+
func testRainbow() async {
1010
let mainQueue = TestScheduler()
1111

1212
let store = TestStore(
@@ -17,44 +17,44 @@ class AnimationTests: XCTestCase {
1717
)
1818
)
1919

20-
store.send(.rainbowButtonTapped)
20+
await store.send(.rainbowButtonTapped)
2121

22-
store.receive(.setColor(.red)) {
22+
await store.receive(.setColor(.red)) {
2323
$0.circleColor = .red
2424
}
2525

2626
await mainQueue.advance(by: .seconds(1))
27-
store.receive(.setColor(.blue)) {
27+
await store.receive(.setColor(.blue)) {
2828
$0.circleColor = .blue
2929
}
3030

3131
await mainQueue.advance(by: .seconds(1))
32-
store.receive(.setColor(.green)) {
32+
await store.receive(.setColor(.green)) {
3333
$0.circleColor = .green
3434
}
3535

3636
await mainQueue.advance(by: .seconds(1))
37-
store.receive(.setColor(.orange)) {
37+
await store.receive(.setColor(.orange)) {
3838
$0.circleColor = .orange
3939
}
4040

4141
await mainQueue.advance(by: .seconds(1))
42-
store.receive(.setColor(.pink)) {
42+
await store.receive(.setColor(.pink)) {
4343
$0.circleColor = .pink
4444
}
4545

4646
await mainQueue.advance(by: .seconds(1))
47-
store.receive(.setColor(.purple)) {
47+
await store.receive(.setColor(.purple)) {
4848
$0.circleColor = .purple
4949
}
5050

5151
await mainQueue.advance(by: .seconds(1))
52-
store.receive(.setColor(.yellow)) {
52+
await store.receive(.setColor(.yellow)) {
5353
$0.circleColor = .yellow
5454
}
5555

5656
await mainQueue.advance(by: .seconds(1))
57-
store.receive(.setColor(.black)) {
57+
await store.receive(.setColor(.black)) {
5858
$0.circleColor = .black
5959
}
6060

@@ -63,41 +63,41 @@ class AnimationTests: XCTestCase {
6363
await mainQueue.advance(by: .seconds(10))
6464
}
6565

66-
func testReset() async {
67-
let mainQueue = TestScheduler()
66+
func testReset() async {
67+
let mainQueue = TestScheduler()
6868

69-
let store = TestStore(
70-
initialState: AnimationsState(),
71-
reducer: animationsReducer,
72-
environment: AnimationsEnvironment(
73-
mainQueue: mainQueue
74-
)
69+
let store = TestStore(
70+
initialState: AnimationsState(),
71+
reducer: animationsReducer,
72+
environment: AnimationsEnvironment(
73+
mainQueue: mainQueue
7574
)
75+
)
76+
77+
await store.send(.rainbowButtonTapped)
7678

77-
await store.send(.rainbowButtonTapped)
78-
79-
await store.receive(.setColor(.red)) {
80-
$0.circleColor = .red
81-
}
82-
83-
await mainQueue.advance(by: .seconds(1))
84-
await store.receive(.setColor(.blue)) {
85-
$0.circleColor = .blue
86-
}
87-
88-
await store.send(.resetButtonTapped) {
89-
$0.alert = AlertState(
90-
title: TextState("Reset state?"),
91-
primaryButton: .destructive(
92-
TextState("Reset"),
93-
action: .send(.resetConfirmationButtonTapped, animation: .default)
94-
),
95-
secondaryButton: .cancel(TextState("Cancel"))
96-
)
97-
}
98-
99-
await store.send(.resetConfirmationButtonTapped) {
100-
$0 = AnimationsState()
101-
}
79+
await store.receive(.setColor(.red)) {
80+
$0.circleColor = .red
10281
}
82+
83+
await mainQueue.advance(by: .seconds(1))
84+
await store.receive(.setColor(.blue)) {
85+
$0.circleColor = .blue
86+
}
87+
88+
await store.send(.resetButtonTapped) {
89+
$0.alert = AlertState(
90+
title: TextState("Reset state?"),
91+
primaryButton: .destructive(
92+
TextState("Reset"),
93+
action: .send(.resetConfirmationButtonTapped, animation: .default)
94+
),
95+
secondaryButton: .cancel(TextState("Cancel"))
96+
)
97+
}
98+
99+
await store.send(.resetConfirmationButtonTapped) {
100+
$0 = AnimationsState()
101+
}
102+
}
103103
}

Examples/Search/Search.xcodeproj/project.pbxproj

+1
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@
8888
CA86E49924253C2500357AD9 /* Search */,
8989
CA86E4B024253C2700357AD9 /* SearchTests */,
9090
);
91+
indentWidth = 2;
9192
sourceTree = "<group>";
9293
};
9394
CA86E49824253C2500357AD9 /* Products */ = {

Examples/Search/Search/WeatherClient.swift

-1
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,6 @@ extension WeatherClient {
6565

6666
let (data, _) = try await URLSession.shared.data(from: components.url!)
6767
return try jsonDecoder.decode(Search.self, from: data)
68-
}
6968
}
7069
)
7170
}

Examples/SpeechRecognition/SpeechRecognition.xcodeproj/project.pbxproj

+1
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@
9595
CA23320C2447ACFA00B818EB /* SpeechRecognition */,
9696
CA2332232447ACFC00B818EB /* SpeechRecognitionTests */,
9797
);
98+
indentWidth = 2;
9899
sourceTree = "<group>";
99100
};
100101
CA23320B2447ACFA00B818EB /* Products */ = {

Examples/SpeechRecognition/SpeechRecognition/SpeechClient/Client.swift

-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import Combine
21
import Speech
32

43
struct SpeechClient {

Examples/SpeechRecognition/SpeechRecognition/SpeechClient/Live.swift

-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import ComposableArchitecture
2-
import ReactiveSwift
32
import Speech
43

54
extension SpeechClient {
@@ -79,7 +78,6 @@ private actor Speech {
7978
audioEngine.wrappedValue?.inputNode.removeTap(onBus: 0)
8079
recognitionTask.wrappedValue?.finish()
8180
}
82-
lifetime += cancellable
8381

8482
self.audioEngine?.inputNode.installTap(
8583
onBus: 0,

Examples/SpeechRecognition/SpeechRecognition/SpeechClient/Unimplemented.swift

-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import Combine
21
import ComposableArchitecture
32
import Speech
43
import XCTestDynamicOverlay

Examples/SpeechRecognition/SpeechRecognition/SpeechRecognition.swift

+1-4
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import ComposableArchitecture
2-
import ReactiveSwift
32
import Speech
43
@preconcurrency import SwiftUI
54

@@ -190,7 +189,7 @@ extension SpeechClient {
190189
}
191190
text += word + " "
192191
c.yield(
193-
value: .init(
192+
.init(
194193
bestTranscription: .init(
195194
formattedString: text,
196195
segments: []
@@ -200,8 +199,6 @@ extension SpeechClient {
200199
)
201200
)
202201
}
203-
204-
lifetime += disposable
205202
}
206203
}
207204
}

Examples/TicTacToe/TicTacToe.xcodeproj/project.pbxproj

+1
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@
5656
CA6AC25C2450FDB800C71CB3 /* App */,
5757
DC9193F72420104100A5BE1F /* Products */,
5858
);
59+
indentWidth = 2;
5960
sourceTree = "<group>";
6061
};
6162
DC9193F72420104100A5BE1F /* Products */ = {

Examples/Todos/Todos.xcodeproj/project.pbxproj

+1
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@
8888
DCBCB77224290F6C00DE1F59 /* Todos */,
8989
DCBCB78924290F6D00DE1F59 /* TodosTests */,
9090
);
91+
indentWidth = 2;
9192
sourceTree = "<group>";
9293
};
9394
DCBCB77124290F6C00DE1F59 /* Products */ = {

Examples/Todos/Todos/Todos.swift

+1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import ComposableArchitecture
2+
import ReactiveSwift
23
import SwiftUI
34

45
enum Filter: LocalizedStringKey, CaseIterable, Hashable {

Examples/Todos/Todos/TodosApp.swift

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import ComposableArchitecture
2+
import ReactiveSwift
23
import SwiftUI
34

45
@main
@@ -10,7 +11,7 @@ struct TodosApp: App {
1011
initialState: AppState(),
1112
reducer: appReducer,
1213
environment: AppEnvironment(
13-
mainQueue: .main,
14+
mainQueue: QueueScheduler.main,
1415
uuid: { UUID() }
1516
)
1617
)

Examples/Todos/TodosTests/TodosTests.swift

+1
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ class TodosTests: XCTestCase {
1414
reducer: appReducer,
1515
environment: AppEnvironment(
1616
mainQueue: self.mainQueue,
17+
uuid: { UUID.incrementing() }
1718
)
1819
)
1920

Examples/VoiceMemos/VoiceMemos.xcodeproj/project.pbxproj

+2-1
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@
5757

5858
/* Begin PBXFileReference section */
5959
23EDBE6B271CD8DD004F7430 /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = "<group>"; };
60-
CA93D05B249BF42500A6F65D /* VoiceMemo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VoiceMemo.swift; sourceTree = "<group>"; };
60+
CA93D05B249BF42500A6F65D /* VoiceMemo.swift */ = {isa = PBXFileReference; indentWidth = 2; lastKnownFileType = sourcecode.swift; path = VoiceMemo.swift; sourceTree = "<group>"; };
6161
CA93D05D249BF46E00A6F65D /* Helpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Helpers.swift; sourceTree = "<group>"; };
6262
DC5BDCAA24589177009C65A3 /* VoiceMemos.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = VoiceMemos.app; sourceTree = BUILT_PRODUCTS_DIR; };
6363
DC5BDCAF24589177009C65A3 /* VoiceMemosApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VoiceMemosApp.swift; sourceTree = "<group>"; };
@@ -103,6 +103,7 @@
103103
DC5BDCAC24589177009C65A3 /* VoiceMemos */,
104104
DC5BDCC324589179009C65A3 /* VoiceMemosTests */,
105105
);
106+
indentWidth = 2;
106107
sourceTree = "<group>";
107108
};
108109
DC5BDCAB24589177009C65A3 /* Products */ = {

Examples/VoiceMemos/VoiceMemos/VoiceMemo.swift

+4-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import ComposableArchitecture
22
import Foundation
3+
import ReactiveSwift
34
import SwiftUI
45

56
struct VoiceMemo: Equatable, Identifiable {
@@ -61,14 +62,14 @@ let voiceMemoReducer = Reducer<
6162
memo.mode = .playing(progress: 0)
6263

6364
return .run { [url = memo.url] send in
64-
let start = environment.mainRunLoop.now
65+
let start = environment.mainRunLoop.currentDate
6566

6667
async let playAudio: Void = send(
6768
.audioPlayerClient(TaskResult { try await environment.audioPlayerClient.play(url) })
6869
)
6970

70-
for try await tick in environment.mainRunLoop.timer(interval: 0.5) {
71-
await send(.timerUpdated(tick.date.timeIntervalSince(start.date)))
71+
for try await tick in environment.mainRunLoop.timer(interval: .milliseconds(500)) {
72+
await send(.timerUpdated(tick.timeIntervalSince(start)))
7273
}
7374
}
7475
.cancellable(id: PlayID.self, cancelInFlight: true)

Examples/VoiceMemos/VoiceMemos/VoiceMemos.swift

+1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import AVFoundation
22
import ComposableArchitecture
33
import Foundation
4+
import ReactiveSwift
45
import SwiftUI
56

67
struct VoiceMemosState: Equatable {

Examples/VoiceMemos/VoiceMemosTests/VoiceMemosTests.swift

+5-6
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@ class VoiceMemosTests: XCTestCase {
2525
environment.audioRecorder.stopRecording = {
2626
didFinish.continuation.yield(true)
2727
didFinish.continuation.finish()
28-
}
2928
}
3029
environment.mainRunLoop = mainRunLoop
3130
environment.temporaryDirectory = { URL(fileURLWithPath: "/tmp") }
@@ -82,7 +81,7 @@ class VoiceMemosTests: XCTestCase {
8281

8382
var environment = VoiceMemosEnvironment.unimplemented
8483
environment.audioRecorder.requestRecordPermission = { false }
85-
environment.mainRunLoop = ImmediateScheduler()
84+
environment.mainRunLoop = mainRunLoop
8685
environment.openSettings = { await didOpenSettings.setValue(true) }
8786

8887
let store = TestStore(
@@ -113,7 +112,7 @@ class VoiceMemosTests: XCTestCase {
113112
environment.audioRecorder.startRecording = { _ in
114113
try await didFinish.stream.first { _ in true }!
115114
}
116-
environment.mainRunLoop = ImmediateScheduler()
115+
environment.mainRunLoop = TestScheduler(startDate: Date(timeIntervalSince1970: 0))
117116
environment.temporaryDirectory = { URL(fileURLWithPath: "/tmp") }
118117
environment.uuid = { UUID(uuidString: "DEADBEEF-DEAD-BEEF-DEAD-BEEFDEADBEEF")! }
119118

@@ -145,7 +144,7 @@ class VoiceMemosTests: XCTestCase {
145144
func testPlayMemoHappyPath() async {
146145
var environment = VoiceMemosEnvironment.unimplemented
147146
environment.audioPlayer.play = { _ in
148-
try await self.mainRunLoop.sleep(for: 1.25)
147+
try await self.mainRunLoop.sleep(for: .milliseconds(1250))
149148
return true
150149
}
151150
environment.mainRunLoop = mainRunLoop
@@ -190,7 +189,7 @@ class VoiceMemosTests: XCTestCase {
190189

191190
var environment = VoiceMemosEnvironment.unimplemented
192191
environment.audioPlayer.play = { _ in throw SomeError() }
193-
environment.mainRunLoop = ImmediateScheduler()
192+
environment.mainRunLoop = mainRunLoop
194193

195194
let url = URL(fileURLWithPath: "pointfreeco/functions.m4a")
196195
let store = TestStore(
@@ -269,7 +268,7 @@ class VoiceMemosTests: XCTestCase {
269268
let url = URL(fileURLWithPath: "pointfreeco/functions.m4a")
270269
var environment = VoiceMemosEnvironment.unimplemented
271270
environment.audioPlayer.play = { _ in try await Task.never() }
272-
environment.mainRunLoop = ImmediateScheduler()
271+
environment.mainRunLoop = mainRunLoop
273272

274273
let store = TestStore(
275274
initialState: VoiceMemosState(

Sources/ComposableArchitecture/Documentation.docc/Articles/Testing.md

+2-2
Original file line numberDiff line numberDiff line change
@@ -320,10 +320,10 @@ time-based asynchrony, but in a way that is controllable. One way to do this is
320320
scheduler to the environment:
321321

322322
```swift
323-
import CombineSchedulers
323+
import ReactiveSwift
324324

325325
struct Environment {
326-
var mainQueue: any SchedulerOf<DispatchQueue>
326+
var mainQueue: DateScheduler
327327
}
328328
```
329329

0 commit comments

Comments
 (0)