Skip to content

Commit a01971e

Browse files
committed
Added tests
1 parent 2574d9f commit a01971e

File tree

6 files changed

+73
-37
lines changed

6 files changed

+73
-37
lines changed

.codecov.yml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,5 @@ coverage:
33
ignore:
44
- Templates
55
- Scripts
6-
- Tests
6+
- Tests
7+
- docs

README.md

Lines changed: 33 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ With Mini, you can create a thread-safe application with a predictable unidirect
2626

2727
### [Swift Package Manager](https://github.com/apple/swift-package-manager)
2828

29-
- Create a Package.swift file.
29+
- Create a `Package.swift` file.
3030

3131
```swift
3232
// swift-tools-version:5.0
@@ -39,10 +39,12 @@ let package = Package(
3939
.package(url: "https://github.com/bq/mini-swift.git"),
4040
],
4141
targets: [
42-
.target(name: "MiniSwiftProject", dependencies: ["Mini"])
42+
.target(name: "MiniSwiftProject", dependencies: ["Mini" /*, "MiniPromises, MiniTasks"*/])
4343
]
4444
)
4545
```
46+
- Mini comes with a bare implementation and two external utility packages in order to ease the usage of the library named `MiniTasks` and `MiniPromises`, both dependant on the `Mini` base or core package.
47+
4648
```
4749
$ swift build
4850
```
@@ -53,6 +55,8 @@ $ swift build
5355

5456
```
5557
pod "Mini-Swift"
58+
# pod "Mini-Swift/MiniPromises"
59+
# pod "Mini-Swift/MiniTasks"
5660
```
5761

5862
- We also offer two subpecs for logging and testing:
@@ -77,42 +81,46 @@ pod "Mini-Swift/Test"
7781
- For example:
7882

7983
```swift
80-
struct MyCoolState: State {
84+
// If you're using MiniPromises
85+
struct MyCoolState: StateType {
8186
let cool: Promise<Bool>
87+
}
8288

83-
init(cool: Promise<Bool> = .idle()) {
84-
self.cool = cool
85-
}
86-
87-
// Conform to State protocol
88-
func isEqual(to other: State) -> Bool {
89-
guard let state = other as? MyCoolState else { return false }
90-
return self.cool == state.cool
91-
}
89+
// If you're using MiniTasks
90+
struct MyCoolState: StateType {
91+
let cool: Bool?
92+
let coolTask: AnyTask
9293
}
9394
```
9495

95-
- The default inner state of a `Promise` is `idle`. It means that no `Action` (see more below), has started any operation over that `Promise`.
96+
- The default inner state of a `Promise` is `idle`. On the other hand, the default inner state of a `Task` is `idle` as well. This means that no `Action` (see more below), has started any operation over that `Promise` or `Task`.
9697

97-
- A `Promise` can hold any kind of aditional properties that the developer might encounter useful for its implementation, for example, hold a `Date` for cache usage:
98+
- Both `Promise` and `Task` can hold any kind of aditional properties that the developer might encounter useful for its implementation, for example, hold a `Date` for cache usage:
9899

99100
```swift
100101
let promise: Promise<Bool> = .idle()
101102
promise.date = Date()
102103
// Later on...
103104
let date: Date = promise.date
105+
106+
let task: AnyTask = .idle()
107+
task.date = Date()
108+
// Later on...
109+
let date: Date = promise.date
104110
```
105111

106112
- The core idea of a `State` is its [immutability](https://en.wikipedia.org/wiki/Immutable_object), so once created, no third-party objects are able to mutate it out of the control of the architecture flow.
107113

108-
- As can be seen in the example, a `State` has a pair of `Task` + `Result` *usually* (that can be any object, if any), which is related with the execution of the `Task`. In the example above, `CoolTask` is responsible, through its `Reducer` to fulfill the `Action` with the `Task` result and furthermore, the new `State`.
114+
- As can be seen in the example, a `State` has a pair of `Task` + `Result` *usually* (that can be any object, if any), which is related with the execution of the `Task`. In the example above, `CoolTask` is responsible, through its `Reducer` to fulfill the `Action` with the `Task` result and furthermore, the new `State`.
115+
116+
- Furthermore, the `Promise` object unifies the _Status_ + _Result_ tuple, so it can store both the status of an ongoing task and the associated payload produced by it.
109117

110118
### Action
111119

112-
- An `Action` is the piece of information that is being dispatched through the architecture. Any `class` can conform to the `Action` protocol, with the only requirement of being unique its name per application.
120+
- An `Action` is the piece of information that is being dispatched through the architecture. Any `struct` can conform to the `Action` protocol, with the only requirement of being unique its name per application.
113121

114122
```swift
115-
class RequestContactsAccess: Action {
123+
struct RequestContactsAccess: Action {
116124
// As simple as this is.
117125
}
118126
```
@@ -122,27 +130,17 @@ class RequestContactsAccess: Action {
122130
- A `CompletableAction` is a specialization of the `Action` protocol, which allows the user attach both a `Task` and some kind of object that gets fulfilled when the `Task` succeeds.
123131

124132
```swift
125-
class RequestContactsAccessResult: CompletableAction {
126-
127-
let requestContactsAccessPromise: Promise<Bool?>
133+
struct RequestContactsAccessResult: CompletableAction {
134+
let promise: Promise<Bool>
128135

129136
typealias Payload = Bool
130-
131-
required init(promise: Promise<Bool?>) {
132-
self.requestContactsAccessPromise = promise
133-
}
134137
}
135138
```
136-
- An `EmptyAction` is a specialization of `CompletableAction` where the `Payload` is a `Swift.Never`, this means it only has associated a `Promise<Never>`.
139+
- An `EmptyAction` is a specialization of `CompletableAction` where the `Payload` is a `Swift.Void`, this means it only has associated a `Promise<Void>`.
137140

138141
```swift
139142
class ActivateVoucherLoaded: EmptyAction {
140-
141-
let activateVoucherPromise: Promise<Never>
142-
143-
required init(promise: Promise<Never>) {
144-
self.activateVoucherPromise = promise
145-
}
143+
let promise: Promise<Void>
146144
}
147145
```
148146
- A `KeyedPayloadAction`, adds a `Key` (which is `Hashable`) to the `CompletableAction`. This is a special case where the same `Action` produces results that can be grouped together, tipically, under a `Dictionary` (i.e., an `Action` to search contacts, and grouped by their main phone number).
@@ -153,13 +151,12 @@ class RequestContactsAccess: Action {
153151
typealias Payload = CNContact
154152
typealias Key = String
155153

156-
let requestContactPromise: [Key: Promise<Payload?>]
157-
158-
required init(promise: [Key: Promise<Payload?>]) {
159-
self.requestContactPromise = promise
160-
}
154+
let promise: [Key: Promise<Payload?>]
161155
}
162156
```
157+
158+
> We take the advantage of using `struct`, so all initializers are automatically synthesized.
159+
163160
### Store
164161

165162
- A `Store` is the hub where decissions and side-efects are made through the ingoing and outgoing `Action`s. A `Store` is a generic class to inherit from and associate a `State` for it.

Sources/MiniTasks/Task.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,10 @@ public class TypedTask<T>: Equatable {
4141
self.error = error
4242
}
4343

44+
public static func idle() -> AnyTask {
45+
AnyTask(status: .idle)
46+
}
47+
4448
public static func running() -> AnyTask {
4549
AnyTask(status: .running)
4650
}

Tests/MiniSwiftTests/Common.swift

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,12 @@ struct TestState: StateType {
6565
}
6666
}
6767

68+
extension TestState: Equatable {
69+
static func == (lhs: Self, rhs: Self) -> Bool {
70+
return lhs.isEqual(to: rhs)
71+
}
72+
}
73+
6874
class TestStoreController: Disposable {
6975
let dispatcher: Dispatcher
7076

Tests/MiniSwiftTests/RxTests/ObservableTypeTests.swift

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,11 @@ import RxSwift
55
import RxTest
66
import XCTest
77

8+
private struct TestFilterKeyPath: Equatable {
9+
let number: Int
10+
let done: Bool
11+
}
12+
813
private func matchPromiseHash<K: Hashable, Type: Equatable>(_ by: [K: Promise<Type>]) -> Predicate<[K: Promise<Type>]> {
914
return Predicate { expression in
1015
guard let dict = try expression.evaluate() else {
@@ -52,6 +57,28 @@ final class ObservableTypeTests: XCTestCase {
5257
])
5358
}
5459

60+
func test_filter_keypath() {
61+
let filterKeyPathObserver = scheduler.createObserver(TestFilterKeyPath.self)
62+
63+
scheduler.createColdObservable(
64+
[
65+
.next(10, TestFilterKeyPath(number: 0, done: false)),
66+
.next(20, TestFilterKeyPath(number: 1, done: true)),
67+
.completed(40),
68+
]
69+
)
70+
.filter(\TestFilterKeyPath.done)
71+
.subscribe(filterKeyPathObserver)
72+
.disposed(by: disposeBag)
73+
74+
scheduler.start()
75+
76+
XCTAssertEqual(filterKeyPathObserver.events, [
77+
.next(20, TestFilterKeyPath(number: 1, done: true)),
78+
.completed(40),
79+
])
80+
}
81+
5582
func test_skipping_next() {
5683
let skippingNextObserver = scheduler.createObserver(Int.self)
5784

Tests/MiniSwiftTests/XCTestManifests.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@
5656
static let __allTests__ObservableTypeTests = [
5757
("test_dispatch_action_from_store", test_dispatch_action_from_store),
5858
("test_dispatch_hashable_action_from_store", test_dispatch_hashable_action_from_store),
59+
("test_filter_keypath", test_filter_keypath),
5960
("test_filter_one", test_filter_one),
6061
("test_select", test_select),
6162
("test_skipping_next", test_skipping_next),

0 commit comments

Comments
 (0)