Skip to content

Commit

Permalink
Improved documentation (#73)
Browse files Browse the repository at this point in the history
* Add documentation

* Create generate_docs.sh

* Proof reading documentation + better for Jazzy

* Lint fix
  • Loading branch information
MortenGregersen authored Jun 4, 2020
1 parent b0849d1 commit d463f7b
Show file tree
Hide file tree
Showing 13 changed files with 192 additions and 132 deletions.
10 changes: 8 additions & 2 deletions .jazzy.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@ custom_categories:
- name: Actions
children:
- Action
- IdentifiableAction
- ActionTemplate
- AnonymousAction
- IdentifiableAction
- name: Reducers
children:
- Reducer
Expand All @@ -25,6 +25,11 @@ custom_categories:
children:
- SelectorProtocol
- Selector
- Selector1
- Selector2
- Selector3
- Selector4
- Selector5
- name: Effects
children:
- Effects
Expand All @@ -36,11 +41,12 @@ custom_categories:
children:
- Interceptor
- PrintInterceptor
- name: SwiftUI
- name: Using Fluxor with SwiftUI
children:
- ObservableValue
- ValueBinding
- name: Test Support
children:
- MockStore
- EffectRunner
- TestInterceptor
76 changes: 0 additions & 76 deletions Documentation/Abstracts/SwiftUI.md

This file was deleted.

31 changes: 12 additions & 19 deletions Documentation/Abstracts/Test Support.md
Original file line number Diff line number Diff line change
@@ -1,23 +1,16 @@
<p align="center">
<br />
<img src="https://raw.githubusercontent.com/FluxorOrg/Fluxor/master/Assets/Fluxor-logo.png" width="400" max-width="90%" alt="Fluxor" />
</p>
Every part of an application using Fluxor is highly testable. The separation of the `Action` (instructions), `Selectors` (reading), `Reducer` (mutating) and `Effect` (asynchronous) make each part decoupled, testable and easier to grasp.

# Testing Selectors, Reducers and Effects
But to help out when testing components using Fluxor or asynchronous `Effects`, Fluxor comes with a separate package (**FluxorTestSupport**) with a `MockStore` and a runner for running `Effect` syncronously.

Every part of an application using Fluxor is highly testable. The separation of the [`Action`](Sources/Fluxor/Action.swift) (instructions), [`Selectors`](Sources/Fluxor/Selector.swift) (reading), [`Reducer`](Sources/Fluxor/Reducer.swift) (mutating) and [`Effect`](Sources/Fluxor/Effects.swift) (asynchronous) make each part decoupled, testable and easier to grasp.

But to help out when testing components using Fluxor or asynchronous [`Effects`](Sources/Fluxor/Effects.swift), Fluxor comes with a separate package (**[FluxorTestSupport](Sources/FluxorTestSupport)**) with a [`MockStore`](Sources/FluxorTestSupport/MockStore.swift) and extensions on [`Effect`](Sources/Fluxor/Effects.swift) to make them run syncronously.

[FluxorTestSupport](Sources/FluxorTestSupport) should be linked in unit testing targets.
FluxorTestSupport should only be linked in unit testing targets.

## Mocking out the `Store`

The [`MockStore`](Sources/FluxorTestSupport/MockStore.swift) can be used to mock the [`Store`](Sources/Fluxor/Store.swift) being used.
The `MockStore` can be used to mock the `Store` being used.

### Setting a specific `State`

With [`MockStore`](Sources/FluxorTestSupport/MockStore.swift) it is possible, from a test, to set a specific `State` to help test a specific scenario.
With `MockStore` it is possible, from a test, to set a specific `State` to help test a specific scenario.

```swift
import FluxorTestSupport
Expand All @@ -36,7 +29,7 @@ class GreetingView: XCTestCase {

### Overriding `Selectors`

The [`MockStore`](Sources/FluxorTestSupport/MockStore.swift) can be used to override [`Selectors`](Sources/Fluxor/Selector.swift) so that they always return a specific value.
The `MockStore` can be used to override `Selectors` so that they always return a specific value.

```swift
import FluxorTestSupport
Expand All @@ -53,9 +46,9 @@ class GreetingView: XCTestCase {
}
```

## Intercepting `State` changes
## Intercepting state changes

The [`TestInterceptor`](Sources/FluxorTestSupport/TestInterceptor.swift) can be registered on the [`Store`](Sources/Fluxor/Store.swift). When registered it gets all [`Action`s](Sources/Fluxor/Action.swift) dispatched and `State` changes. Everything it intercepts gets saved in an array in the order received. This can be used to assert which [`Action`s](Sources/Fluxor/Action.swift) are dispatched in a test.
The `TestInterceptor` can be registered on the `Store`. When registered it gets all `Action`s dispatched and state changes. Everything it intercepts gets saved in an array in the order received. This can be used to assert which `Action`s are dispatched in a test.

```swift
import FluxorTestSupport
Expand All @@ -74,13 +67,13 @@ class GreetingView: XCTestCase {
}
```

The [`MockStore`](Sources/FluxorTestSupport/MockStore.swift) uses this internally behind the `stateChanges` property.
The `MockStore` uses this internally behind the `stateChanges` property.

## Running an `Effect`

An [`Effect`](Sources/Fluxor/Effects.swift) is inherently asynchronous, so in order to test it in a synchronous test, without a lot of boilerplate code, FluxorTestSupport comes with a` run` function that executes the [`Effect`](Sources/Fluxor/Effects.swift) with a specific [`Action`](Sources/Fluxor/Action.swift). It is possible to run both `.dispatchingOne`,` .dispatchingMultiple` and `.nonDispatching`, but the result will be different.
An `Effect` is inherently asynchronous, so in order to test it in a synchronous test, without a lot of boilerplate code, FluxorTestSupport comes with an `EffectRunner` that executes the `Effect` with a specific `Action` and `Environment`. It is possible to run both `.dispatchingOne`,` .dispatchingMultiple` and `.nonDispatching`, but the result will be different.

When running `.dispatchingOne` and` .dispatchingMultiple`, it is possible to specify the expected number of dispatched [`Action`s](Sources/Fluxor/Action.swift) and the dispatched [`Action`s](Sources/Fluxor/Action.swift) will also be returned.
When running `.dispatchingOne` and` .dispatchingMultiple`, it is possible to specify the expected number of dispatched `Action`s and the dispatched `Action`s will also be returned.

When running `.nonDispatching`, nothing is awaited and nothing is returned.

Expand All @@ -92,7 +85,7 @@ class SettingsEffectsTests: XCTestCase {
func testSetBackground() {
let effects = SettingsEffects()
let action = Actions.setBackgroundColor(payload: .red)
let result = try EffectRunner.run(EffectRunnereffects.setBackgroundColor, with: action)!
let result = try EffectRunner.run(effects.setBackgroundColor, with: action, environment: AppEnvironment())!
XCTAssertEqual(result.count, 1)
XCTAssertEqual(result[0], Actions.hideColorPicker())
}
Expand Down
69 changes: 69 additions & 0 deletions Documentation/Abstracts/Using Fluxor with SwiftUI.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
Fluxor is based on Combine and is therefore ideal to use with SwiftUI. It comes with a separate package (**FluxorSwiftUI**) with extensions for `Store` and types that can be used directly in [SwiftUI Views](https://developer.apple.com/documentation/swiftui/view).

## Observing a value

The `ObservableValue` can be used to observe a value in the `State`. It can be created by calling the `observe` function found in the `Store` extension. When wrapping the `ObservableValue` in an [`@ObservedObject`](https://developer.apple.com/documentation/swiftui/observedobject) property wrapper, its `current` property can be used like any other value wrapped in a [`@State`](https://developer.apple.com/documentation/swiftui/state) property wrapper.

```swift
import FluxorSwiftUI
import SwiftUI

struct DrawView: View {
@ObservedObject var canClear = Current.store.observe(Selectors.canClear)

var body: some View {
Button(action: { ... }, label: { Text("Clear") })
.disabled(!canClear.current)
}
}
```

## Binding a value which can be changed

The `ValueBinding` can be used to create a [`Binding`](https://developer.apple.com/documentation/swiftui/binding) to a value in the `State` and use an `ActionTemplate` to update the value through the `Store`. The `binding` property on `ValueBinding` will create a [`Binding`](https://developer.apple.com/documentation/swiftui/binding) which can be used like any other bindings. The value can also manually be updated using the `update` function. This will dispatch an `Action` on the `Store` based on the specified `ActionTemplate`.

```swift
import FluxorSwiftUI
import SwiftUI

struct GreetingView: View {
var greeting = Current.store.binding(get: Selectors.getGreeting, send: Actions.setGreeting)

var body: some View {
TextField("Greeting", text: greeting.binding)
}
}
```

## Binding a value which can be enabled and disabled

When the `ValueBinding` has a `Bool` value, it can be created with a `ActionTemplate` for enabling the value (making it `true`) and another one for disabling the value (making it `false`). When the value is `Bool` and the payload for the `ActionTemplate` is either `Void` or `Bool`, the `ValueBinding` gains more functions beside the `update` function to `toggle`, `enable` and `disable` the value.

```swift
import FluxorSwiftUI
import SwiftUI

struct DrawView: View {
var showClearActionSheet = Current.store.binding(get: Selectors.isClearOptionsVisible,
enable: Actions.askToClear,
disable: Actions.cancelClear)

var body: some View {
Button(action: { self.showClearActionSheet.enable() }, label: { Text("Clear") })
.actionSheet(isPresented: showClearActionSheet.binding) { clearActionSheet }
}

private var clearActionSheet: ActionSheet {
.init(title: Text("Clear"),
message: Text("Are you sure you want to clear?"),
buttons: [
.destructive(Text("Yes, clear")) {
Current.store.dispatch(action: Actions.clear())
},
.cancel(Text("Cancel")) {
self.showClearActionSheet.disable()
}
])
}
}
```
6 changes: 6 additions & 0 deletions Documentation/generate_docs.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
rm -rf docs
sourcekitten doc --spm --module-name Fluxor > mod1.json
sourcekitten doc --spm --module-name FluxorTestSupport > mod2.json
sourcekitten doc --spm --module-name FluxorSwiftUI > mod3.json
jazzy --sourcekitten-sourcefile mod1.json,mod2.json,mod3.json
open docs/index.html
25 changes: 14 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,12 @@ With Fluxor, data flows in only one direction, there is only one *Single Source
## How does it work?
Fluxor is made up from the following types:

* [**Store**](Sources/Fluxor/Store.swift) contains an immutable state (the **Single Source of Truth**).
* [**Actions**](Sources/Fluxor/Action.swift) are dispatched on the **Store** to update the state.
* [**Reducers**](Sources/Fluxor/Reducer.swift) gives the **Store** a new state based on the **Actions** dispatched.
* [**Selectors**](Sources/Fluxor/Selector.swift) selects (and eventually transform) part(s) of the state to use (eg. in views).
* [**Effects**](Sources/Fluxor/Effects.swift) gets triggered by **Actions**, and can perform async task which in turn can dispatch new **Actions**.
* [**Interceptors**](Sources/Fluxor/Interceptor.swift) intercepts every dispatched **Action** and state change for easier debugging.
* `Store` contains an immutable state (the **Single Source of Truth**).
* `Action`s are dispatched on the **Store** to update the state.
* `Reducer`s gives the **Store** a new state based on the **Actions** dispatched.
* `Selector`s selects (and eventually transform) part(s) of the state to use (eg. in views).
* `Effect`s gets triggered by **Actions**, and can perform async task which in turn can dispatch new **Actions**.
* `Interceptor`s intercepts every dispatched **Action** and state change for easier debugging.

![](https://raw.githubusercontent.com/FluxorOrg/Fluxor/master/Assets/Diagram.png)

Expand Down Expand Up @@ -106,7 +106,9 @@ import Fluxor
import Foundation

class TodosEffects: Effects {
let fetchTodos = Effect.dispatchingOne { actions, environment in
typealias Environment = AppEnvironment

let fetchTodos = Effect<Environment>.dispatchingOne { actions, environment in
actions.ofType(FetchTodosAction.self)
.flatMap { _ in
environment.todoService.fetchTodos()
Expand All @@ -123,18 +125,19 @@ If read-only access to all `Action`s dispatched and state changes is needed, an

Fluxor comes with two implementations of `Interceptor`:

* [**PrintInterceptor**](Sources/Fluxor/Interceptors/PrintInterceptor.swift) for printing `Action`s and state changes to the log.
* [**TestInterceptor**](Sources/FluxorTestSupport/TestInterceptor.swift) to help assert that specific `Action`s was dispatched in unit tests.
* `PrintInterceptor` for printing `Action`s and state changes to the log.
* `TestInterceptor` to help assert that specific `Action`s was dispatched in unit tests.

## Packages for using it with SwiftUI and testing
Fluxor comes with packages, to make it easier to use it with SwiftUI and for testing apps using Fluxor.

* [More info on how to use it with SwiftUI](FluxorSwiftUI.md)
* [More info on how to test apps using Fluxor](FluxorTestSupport.md)
* [More info on how to use it with SwiftUI](https://fluxor.dev/Using%20Fluxor%20With%20SwiftUI.html)
* [More info on how to test apps using Fluxor](https://fluxor.dev/Test%20Support.html)

## Debugging with FluxorExplorer
Fluxor has a companion app, [**FluxorExplorer**](https://github.com/FluxorOrg/FluxorExplorer), which helps when debugging apps using Fluxor. FluxorExplorer lets you look through the dispatched `Action`s and state changes, to debug the data flow of the app.

FluxorExplorer is available on the App Store but also available as open source.
To learn more about how to use FluxorExplorer, [go to the repository for the app](https://github.com/FluxorOrg/FluxorExplorer).

![](https://raw.githubusercontent.com/FluxorOrg/Fluxor/master/Assets/FluxorExplorer.png)
Expand Down
1 change: 1 addition & 0 deletions Sources/Fluxor/Effects.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ public enum Effect<Environment> {

/// A collection of `Effect`s.
public protocol Effects {
/// The environment set up in the `Store` passed to every `Effect`.
associatedtype Environment
/// The `Effect`s to register on the `Store`.
var enabledEffects: [Effect<Environment>] { get }
Expand Down
2 changes: 1 addition & 1 deletion Sources/Fluxor/Interceptors/PrintInterceptor.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

import Foundation

/// A `Interceptor` to use when debugging. Every `Action`s and `State` change are printed ot the console.
/// A `Interceptor` to use when debugging. Every `Action`s and `State` change are printed to the console.
public class PrintInterceptor<State: Encodable>: Interceptor {
private let print: (String) -> Void

Expand Down
Loading

0 comments on commit d463f7b

Please sign in to comment.