Skip to content

Commit 9e305a7

Browse files
authored
Merge pull request #29 from Ryu0118/feature/test
Add Tests of SimplexArchitecture
2 parents a4642b3 + b5a0676 commit 9e305a7

File tree

12 files changed

+575
-10
lines changed

12 files changed

+575
-10
lines changed

Package.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ let package = Package(
2424
.package(url: "https://github.com/pointfreeco/swift-case-paths.git", exact: "1.0.0"),
2525
.package(url: "https://github.com/pointfreeco/swift-custom-dump.git", exact: "1.0.0"),
2626
.package(url: "https://github.com/pointfreeco/swift-dependencies.git", exact: "1.0.0"),
27+
.package(url: "https://github.com/pointfreeco/swift-macro-testing", exact: "0.1.0")
2728
],
2829
targets: [
2930
// Targets are the basic building blocks of a package, defining a module or a test suite.
@@ -53,6 +54,7 @@ let package = Package(
5354
"SimplexArchitectureMacrosPlugin",
5455
.product(name: "SwiftSyntaxMacros", package: "swift-syntax"),
5556
.product(name: "SwiftSyntaxMacrosTestSupport", package: "swift-syntax"),
57+
.product(name: "MacroTesting", package: "swift-macro-testing")
5658
]
5759
),
5860
]

Sources/SimplexArchitecture/Effect/SideEffect.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ public extension SideEffect {
4747
.init(effectKind: .sendAction(action))
4848
}
4949

50+
@_disfavoredOverload
5051
@inlinable
5152
static func send(_ action: Reducer.ReducerAction) -> Self {
5253
.init(effectKind: .sendReducerAction(action))

Sources/SimplexArchitecture/Store/Store.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ extension Store {
118118
case let .sendAction(action):
119119
return [
120120
SendTask(
121-
task: Task.withEffectContext {
121+
task: Task.withEffectContext { @MainActor in
122122
send(action)
123123
}
124124
),
@@ -127,7 +127,7 @@ extension Store {
127127
case let .sendReducerAction(action):
128128
return [
129129
SendTask(
130-
task: Task.withEffectContext {
130+
task: Task.withEffectContext { @MainActor in
131131
send(action)
132132
}
133133
),

Sources/SimplexArchitecture/TestStore.swift

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ public final class TestStore<Reducer: ReducerProtocol> where Reducer.Action: Equ
7070
/// - timeout: The amount of time to wait for the expected action.
7171
/// - expected: A closure that asserts state changed by sending the action to the store. The mutable state sent to this closure must be modified to match the state of the store after processing the given action. Do not provide a closure if no change is expected.
7272
public func receive(
73-
_ action: Reducer.ReducerAction,
73+
_ action: Reducer.Action,
7474
timeout: TimeInterval = 5,
7575
expected: ((StateContainer<Reducer.Target>) -> Void)? = nil,
7676
file: StaticString = #file,
@@ -91,8 +91,9 @@ public final class TestStore<Reducer: ReducerProtocol> where Reducer.Action: Equ
9191
/// - action: An action expected from an effect.
9292
/// - timeout: The amount of time to wait for the expected action.
9393
/// - expected: A closure that asserts state changed by sending the action to the store. The mutable state sent to this closure must be modified to match the state of the store after processing the given action. Do not provide a closure if no change is expected.
94+
@_disfavoredOverload
9495
public func receive(
95-
_ action: Reducer.Action,
96+
_ action: Reducer.ReducerAction,
9697
timeout: TimeInterval = 5,
9798
expected: ((StateContainer<Reducer.Target>) -> Void)? = nil,
9899
file: StaticString = #file,

Sources/SimplexArchitectureMacrosPlugin/ScopeState/ScopeState+memberMacro.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ public struct ScopeState: MemberMacro {
5050

5151
let modifier = declaration.modifiers
5252
.compactMap { $0.as(DeclModifierSyntax.self)?.name.text }
53-
.filter { $0 != "final" }
53+
.filter { $0 != "final" && $0 != "private" && $0 != "fileprivate" }
5454
.first ?? "internal"
5555

5656
return [

Sources/SimplexArchitectureMacrosPlugin/ScopeState/ScopeStateDiagnostic.swift

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,10 +30,19 @@ extension ScopeStateMacroDiagnostic: DiagnosticMessage {
3030

3131
public extension ScopeState {
3232
static func decodeExpansion(
33-
of _: AttributeSyntax,
33+
of syntax: AttributeSyntax,
3434
attachedTo declaration: some DeclGroupSyntax,
35-
in _: some MacroExpansionContext
35+
in context: some MacroExpansionContext
3636
) -> Bool {
37-
declaration.as(StructDeclSyntax.self) != nil || declaration.as(ClassDeclSyntax.self) != nil
37+
if declaration.as(StructDeclSyntax.self) != nil || declaration.as(ClassDeclSyntax.self) != nil {
38+
return true
39+
}
40+
else if declaration.as(ActorDeclSyntax.self) != nil
41+
|| declaration.as(ProtocolDeclSyntax.self) != nil
42+
|| declaration.as(ExtensionDeclSyntax.self) != nil
43+
{
44+
context.diagnose(ScopeStateMacroDiagnostic.requiresStructOrClass.diagnose(at: syntax))
45+
}
46+
return false
3847
}
3948
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
@testable import SimplexArchitecture
2+
import SwiftUI
3+
import XCTest
4+
5+
final class ActionSendableTests: XCTestCase {
6+
func testSend() {
7+
let testView = TestView()
8+
let sendTask1 = testView.send(.c1)
9+
XCTAssertNil(sendTask1.task)
10+
let sendTask2 = testView.send(.c2)
11+
XCTAssertNotNil(sendTask2.task)
12+
}
13+
}
14+
15+
private struct TestReducer: ReducerProtocol {
16+
enum Action: Equatable {
17+
case c1
18+
case c2
19+
}
20+
21+
func reduce(into state: StateContainer<TestView>, action: Action) -> SideEffect<Self> {
22+
switch action {
23+
case .c1:
24+
return .none
25+
case .c2:
26+
return .send(.c1)
27+
}
28+
}
29+
}
30+
31+
@ScopeState
32+
private struct TestView: View {
33+
let store: Store<TestReducer>
34+
35+
init(store: Store<TestReducer> = Store(reducer: TestReducer())) {
36+
self.store = store
37+
}
38+
39+
var body: some View {
40+
EmptyView()
41+
}
42+
}

Tests/SimplexArchitectureTests/ReducerTests.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ final class ReducerTests: XCTestCase {
9696
}
9797
}
9898

99-
struct TestReducer: ReducerProtocol {
99+
private struct TestReducer: ReducerProtocol {
100100
struct ReducerState: Equatable {
101101
var count = 0
102102
var string = "string"
@@ -176,7 +176,7 @@ struct TestReducer: ReducerProtocol {
176176
}
177177

178178
@ScopeState
179-
struct TestView: View {
179+
private struct TestView: View {
180180
@State var count = 0
181181
let store: Store<TestReducer>
182182

Lines changed: 241 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,241 @@
1+
@testable import SimplexArchitecture
2+
@testable import SimplexArchitectureMacrosPlugin
3+
import SwiftUI
4+
import XCTest
5+
import MacroTesting
6+
7+
final class ScopeStateMacroTests: XCTestCase {
8+
override func invokeTest() {
9+
withMacroTesting(
10+
macros: ["ScopeState": ScopeState.self]
11+
) {
12+
super.invokeTest()
13+
}
14+
}
15+
16+
func testDiagnostic() {
17+
assertMacro {
18+
"""
19+
@ScopeState
20+
public actor Test {
21+
}
22+
"""
23+
} matches: {
24+
"""
25+
@ScopeState
26+
┬──────────
27+
├─ 🛑 'ScopeState' macro can only be applied to struct or class
28+
╰─ 🛑 'ScopeState' macro can only be applied to struct or class
29+
public actor Test {
30+
}
31+
"""
32+
}
33+
34+
assertMacro {
35+
"""
36+
@ScopeState
37+
public protocol Test {
38+
}
39+
"""
40+
} matches: {
41+
"""
42+
@ScopeState
43+
┬──────────
44+
├─ 🛑 'ScopeState' macro can only be applied to struct or class
45+
╰─ 🛑 'ScopeState' macro can only be applied to struct or class
46+
public protocol Test {
47+
}
48+
"""
49+
}
50+
51+
assertMacro {
52+
"""
53+
@ScopeState
54+
public extension Test {
55+
}
56+
"""
57+
} matches: {
58+
"""
59+
@ScopeState
60+
┬──────────
61+
├─ 🛑 'ScopeState' macro can only be applied to struct or class
62+
╰─ 🛑 'ScopeState' macro can only be applied to struct or class
63+
public extension Test {
64+
}
65+
"""
66+
}
67+
}
68+
69+
func testPublicExpansion() {
70+
assertMacro {
71+
"""
72+
@ScopeState
73+
public struct TestView: View {
74+
@State var count = 0
75+
let store: Store<TestReducer>
76+
77+
init(store: Store<TestReducer> = Store(reducer: TestReducer(), initialReducerState: .init())) {
78+
self.store = store
79+
}
80+
81+
var body: some View {
82+
EmptyView()
83+
}
84+
}
85+
"""
86+
} matches: {
87+
#"""
88+
public struct TestView: View {
89+
@State var count = 0
90+
let store: Store<TestReducer>
91+
92+
init(store: Store<TestReducer> = Store(reducer: TestReducer(), initialReducerState: .init())) {
93+
self.store = store
94+
}
95+
96+
var body: some View {
97+
EmptyView()
98+
}
99+
100+
public struct States: StatesProtocol {
101+
var count = 0
102+
public static let keyPathMap: [PartialKeyPath<States>: PartialKeyPath<TestView>] = [\.count: \.count]
103+
}
104+
}
105+
106+
extension TestView: ActionSendable {
107+
}
108+
"""#
109+
}
110+
}
111+
112+
func testInternalExpansion() {
113+
assertMacro {
114+
"""
115+
@ScopeState
116+
struct TestView: View {
117+
@State var count = 0
118+
let store: Store<TestReducer>
119+
120+
init(store: Store<TestReducer> = Store(reducer: TestReducer(), initialReducerState: .init())) {
121+
self.store = store
122+
}
123+
124+
var body: some View {
125+
EmptyView()
126+
}
127+
}
128+
"""
129+
} matches: {
130+
#"""
131+
struct TestView: View {
132+
@State var count = 0
133+
let store: Store<TestReducer>
134+
135+
init(store: Store<TestReducer> = Store(reducer: TestReducer(), initialReducerState: .init())) {
136+
self.store = store
137+
}
138+
139+
var body: some View {
140+
EmptyView()
141+
}
142+
143+
internal struct States: StatesProtocol {
144+
var count = 0
145+
internal static let keyPathMap: [PartialKeyPath<States>: PartialKeyPath<TestView>] = [\.count: \.count]
146+
}
147+
}
148+
149+
extension TestView: ActionSendable {
150+
}
151+
"""#
152+
}
153+
154+
}
155+
156+
func testFileprivateExpansion() {
157+
assertMacro {
158+
"""
159+
@ScopeState
160+
fileprivate struct TestView: View {
161+
@State fileprivate var count = 0
162+
let store: Store<TestReducer>
163+
164+
init(store: Store<TestReducer> = Store(reducer: TestReducer(), initialReducerState: .init())) {
165+
self.store = store
166+
}
167+
168+
var body: some View {
169+
EmptyView()
170+
}
171+
}
172+
"""
173+
} matches: {
174+
#"""
175+
fileprivate struct TestView: View {
176+
@State fileprivate var count = 0
177+
let store: Store<TestReducer>
178+
179+
init(store: Store<TestReducer> = Store(reducer: TestReducer(), initialReducerState: .init())) {
180+
self.store = store
181+
}
182+
183+
var body: some View {
184+
EmptyView()
185+
}
186+
187+
internal struct States: StatesProtocol {
188+
var count = 0
189+
internal static let keyPathMap: [PartialKeyPath<States>: PartialKeyPath<TestView>] = [\.count: \.count]
190+
}
191+
}
192+
193+
extension TestView: ActionSendable {
194+
}
195+
"""#
196+
}
197+
}
198+
199+
func testPrivateExpansion() {
200+
assertMacro {
201+
"""
202+
@ScopeState
203+
private struct TestView: View {
204+
@State private var count = 0
205+
let store: Store<TestReducer>
206+
207+
init(store: Store<TestReducer> = Store(reducer: TestReducer(), initialReducerState: .init())) {
208+
self.store = store
209+
}
210+
211+
var body: some View {
212+
EmptyView()
213+
}
214+
}
215+
"""
216+
} matches: {
217+
#"""
218+
private struct TestView: View {
219+
@State private var count = 0
220+
let store: Store<TestReducer>
221+
222+
init(store: Store<TestReducer> = Store(reducer: TestReducer(), initialReducerState: .init())) {
223+
self.store = store
224+
}
225+
226+
var body: some View {
227+
EmptyView()
228+
}
229+
230+
internal struct States: StatesProtocol {
231+
var count = 0
232+
internal static let keyPathMap: [PartialKeyPath<States>: PartialKeyPath<TestView>] = [\.count: \.count]
233+
}
234+
}
235+
236+
extension TestView: ActionSendable {
237+
}
238+
"""#
239+
}
240+
}
241+
}

0 commit comments

Comments
 (0)