Skip to content

Commit e16466b

Browse files
author
george treviranus
committed
add more tests & update readme with api change
1 parent c6a867a commit e16466b

File tree

3 files changed

+149
-62
lines changed

3 files changed

+149
-62
lines changed

README.md

Lines changed: 37 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<h1 align="center">Core Flux</h1>
2-
<p align="center">0.5kb unopinionated flux utility. Control the flow of state data between subscribers.</p>
2+
<p align="center">0.5kb functional flux utility. Control the flow of state data between subscribers.</p>
33
<br>
44
<p align="center">
55
<a href="https://www.npmjs.com/package/core-flux"><img src="https://img.shields.io/npm/v/core-flux.svg?sanitize=true" alt="Version"></a>
@@ -16,6 +16,7 @@
1616
- [`createStore()`](#createstore)
1717
- [`subscribe()`](#subscribe)
1818
- [`dispatch()`](#dispatch)
19+
- [Exposing the store](#exposing-the-store)
1920
- [Data model](#data-model)
2021
- [Data flow](#data-flow)
2122

@@ -55,7 +56,7 @@ The CDN puts the library on `window.CoreFlux`.
5556

5657
## API
5758

58-
### `createStore(initialSate, reducer, bindSubscriber, bindState)`
59+
<h3 id="createstore"><code>createStore(initialSate, reducer, bindSubscriber, bindState)</code></h3>
5960

6061
The one and only export of Core Flux. Use it to create a store instance. You can create as few or as many stores as your heart desires! They will all be independent from one another.
6162

@@ -84,27 +85,29 @@ export { subscribe, dispatch }
8485

8586
Once a store is created, you'll be able to add subscriptions with `subscribe` and request state updates with `dispatch`.
8687

88+
#### Bindings
89+
8790
Here's a breakdown of each binding needed when initializing a new store:
8891

89-
#### `reducer(state, { type, payload } = action)`
92+
**`reducer(state, action)`**
9093

91-
> `state (object)`: A copy of the current state object.<br/>`action ({ type, payload })`: The dispatched action type and its payload.
94+
> `state (object)`: A _copy_ of the current state object.<br/>`action ({ type: string, payload: object })`: The dispatched action type and its payload.
9295
93-
Creates a new version of state and returns it, based on the `type` and `payload`. If the return value is falsy, nothing happens.
96+
Creates a new version of state and returns it, based on the `type` and `payload`. If the return value is falsy, the update process ends.
9497

95-
#### `bindSubscriber(subscription, state)`
98+
**`bindSubscriber(newSubscription, state)`**
9699

97-
> `subscription (array)`: A tuple containing the subscribed object and its relational data.<br/>`state (object)`: A copy of the current state object.
100+
> `newSubscription ([subscriber, data])`: A tuple containing the subscribed object and its state-relational data.<br/>`state (object)`: A _copy_ of the current state object.
98101
99-
Called after a new `subscribe` call is made and a subscription has been added to the store. Use it to set initial state on the new subscriber.
102+
Called after a new `subscribe` call is made and a subscription has been added to the store. Use it to set initial state on the new subscriber based on the `data` defined by your subscriber.
100103

101-
#### `bindState(subscriptions, nextState, setState)`
104+
**`bindState(subscriptions, reducedState, setState)`**
102105

103-
> `subscriptions (subscription[])`: An array containing all subscriptions.<br/>`nextState (object)`: The state object as returned by the reducer.<br/>`setState (function)`:
106+
> `subscriptions (subscription[])`: An array containing all subscriptions.<br/>`reducedState (object)`: The state object as returned by the reducer.<br/>`setState (function)`:
104107
105-
Called after the reducer has processed the next state value. Use it to set the new state back to subscribers **and** back to the store.
108+
Called after the reducer has processed the next state value. Use it to set the reduced state back to subscribers **and** back to the store.
106109

107-
### subscribe
110+
<h3 id="subscribe"><code>subscribe(subscriber, data)</code></h3>
108111

109112
Adds a subscription to your store. It will always be tied to a single store, and subsequently state object.
110113

@@ -122,20 +125,20 @@ class FooItems {
122125
}
123126
```
124127

125-
In the above example, we've designed our subscriber, the `FooItems` class, to declare an array of strings correlating to properties in the store's state. If you're from the Redux world, this is akin to "connecting" a consumer to a provider.
128+
In the above example, we've designed the subscriber, the `FooItems` class, to declare an array of strings correlating to properties in the store's state. If you're from the Redux world, this is akin to "connecting" a consumer to a provider via higher-order function/component.
126129

127-
Additionally, when this `subscribe` call is made, the `bindSubscriber` function will be called where the result of a subscription can be defined. E.g., assigning a default value from state into the subscriber.
130+
After the subscribe call is made, your `bindSubscriber` function will be called where you can pass along the default values as you see fit.
128131

129132
> In general, you should try to use a simple data structure as the second argument to `subscribe`; this ensures your bindings have generic and consistent expectations.
130133
131-
### dispatch
134+
<h3 id="dispatch"><code>dispatch(type, payload)</code></h3>
132135

133-
Let's say you have some subscriptions in your store. How do you kick off a state update for subscribers? That's where `dispatch` comes into play.
136+
Requests a state change in your store.
134137

135-
Let's extend the previous example:
138+
We can extend the previous example with a setter to call `dispatch`:
136139

137140
```js
138-
import { subscribe } from "./foo-store"
141+
import { subscribe, dispatch } from "./foo-store"
139142

140143
class FooItems {
141144
constructor() {
@@ -155,13 +158,25 @@ const fooBar = new FooItems()
155158
fooBar.addItem("bop")
156159
```
157160

158-
Now when the `addItem` method is called, `dispatch` will tell your store to begin the state update process. Your reducer receives the action type and payload.
161+
Now when the `addItem` method is called, Core Flux will pass along the action type and payload to your reducer.
162+
163+
The reducer could have a logic branch on the action type called `ADD_ITEM` which adds the given item to state, then returns the resulting new state (containing the full items list).
164+
165+
Finally, the result would then be handed over to your `bindState` binding.
166+
167+
> Much like in `subscribe`, it's best to maintain data types in the payload so your reducer can have consistent expectations.
159168
160-
The next step being that your reducer could have a logic branch on the action type called `ADD_ITEM` which adds the given item to state, then returns the resulting new state (containing the full items list).
169+
## Exposing the store
161170

162-
Finally, the result would then be handed over to `bindState`.
171+
For utility or debugging reasons, you may want to look at the store you're working with. To do so, you can use the `__data` property when creating a store.
163172

164-
> Any data type can be used as the payload, however, much like in `subscribe`, it's best to keep your data consistent so your reducer can have reliable expectations.
173+
```js
174+
const fooStore = createStore(initialState, reducer, bindSubscriber, bindState)
175+
176+
window.fooStoreData = fooStore.__data
177+
178+
console.log(window.fooStoreData) // { state: {...}, subscriptions: [...] }
179+
```
165180

166181
## Data model
167182

__tests__/core-flux.spec.js

Lines changed: 108 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,51 +1,123 @@
11
import { createStore } from "../core-flux"
22

3-
const testBindState = jest.fn()
4-
const testBindSubscriber = jest.fn()
5-
const testReducer = jest.fn()
3+
const mockBindState = jest.fn()
4+
const mockBindSubscriber = jest.fn()
5+
const mockReducer = jest.fn()
6+
7+
const dataObj = "variable test data"
8+
const subscriberObj = {}
9+
10+
function testReducer(state, action) {
11+
if (action.type === "TEST_TYPE") {
12+
state.foo = action.payload.foo
13+
return state
14+
}
15+
}
16+
17+
function testBindState(_, reducedState, setState) {
18+
setState(reducedState)
19+
}
20+
21+
function getMockStore() {
22+
return createStore({}, mockReducer, mockBindSubscriber, mockBindState)
23+
}
624

725
describe("createStore", () => {
8-
let store,
9-
data = "data",
10-
subscriber = {},
11-
type = "TEST_TYPE",
12-
payload = {}
13-
14-
beforeAll(() => {
15-
store = createStore({}, testReducer, testBindSubscriber, testBindState)
16-
})
26+
describe("artifacts", () => {
27+
it("returns dispatch and subscribe helper functions", () => {
28+
// Given
29+
const store = getMockStore()
1730

18-
it("creates dispatch and subscribe helper functions", () => {
19-
expect(store).toEqual(expect.any(Object))
20-
expect(store.dispatch).toEqual(expect.any(Function))
21-
expect(store.subscribe).toEqual(expect.any(Function))
22-
})
31+
// Then
32+
expect(store).toEqual(expect.any(Object))
33+
expect(store.dispatch).toEqual(expect.any(Function))
34+
expect(store.subscribe).toEqual(expect.any(Function))
35+
})
2336

24-
it("calls subscriber binding when subscription is added", () => {
25-
store.subscribe(subscriber, data)
37+
it("returns live data pointer", () => {
38+
// Given
39+
const store = getMockStore()
2640

27-
expect(testBindSubscriber).toBeCalledWith(
28-
expect.arrayContaining([subscriber, data]),
29-
expect.any(Object)
30-
)
41+
// Then
42+
expect(store.__data).toEqual(
43+
expect.objectContaining({ state: {}, subscriptions: [] })
44+
)
45+
})
3146
})
3247

33-
it("calls reducer when dispatch is made", () => {
34-
store.dispatch(type, payload)
48+
describe("state bindings", () => {
49+
const TEST_TYPE = "TEST_TYPE"
50+
const payload = { foo: "bar" }
51+
52+
it("calls reducer on dispatch", () => {
53+
// Given
54+
const store = getMockStore()
55+
56+
// When
57+
store.dispatch(TEST_TYPE, payload)
3558

36-
expect(testReducer).toBeCalledWith(
37-
expect.any(Object),
38-
expect.objectContaining({ type, payload })
39-
)
59+
// Then
60+
expect(mockReducer).toBeCalledWith(
61+
expect.any(Object),
62+
expect.objectContaining({ payload, type: TEST_TYPE })
63+
)
64+
})
65+
66+
it("calls state binding on dispatch", () => {
67+
// Given
68+
const store = createStore(
69+
{},
70+
testReducer,
71+
mockBindSubscriber,
72+
mockBindState
73+
)
74+
75+
// When
76+
store.subscribe(subscriberObj, dataObj)
77+
store.dispatch(TEST_TYPE, payload)
78+
79+
// Then
80+
expect(mockBindState).toBeCalledWith(
81+
expect.arrayContaining([
82+
expect.arrayContaining([subscriberObj, dataObj]),
83+
]),
84+
{ foo: "bar" },
85+
expect.any(Function)
86+
)
87+
})
88+
89+
it("sets reduced state back to store using setState helper", () => {
90+
// Given
91+
const store = createStore(
92+
{},
93+
testReducer,
94+
mockBindSubscriber,
95+
testBindState
96+
)
97+
98+
// When
99+
store.dispatch(TEST_TYPE, payload)
100+
101+
// Then
102+
expect(store.__data.state).toEqual(
103+
expect.objectContaining({ foo: "bar" })
104+
)
105+
})
40106
})
41107

42-
it("calls state binding when dispatch is made", () => {
43-
store.dispatch(type, payload)
108+
describe("subscriber bindings", () => {
109+
it("calls subscriber binding when subscribe is called", () => {
110+
// Given
111+
const store = getMockStore()
112+
113+
// When
114+
store.subscribe(subscriberObj, dataObj)
44115

45-
expect(testBindState).toBeCalledWith(
46-
expect.arrayContaining([expect.arrayContaining([subscriber, data])]),
47-
undefined,
48-
expect.any(Function)
49-
)
116+
// Then
117+
expect(mockBindSubscriber).toBeCalledWith(
118+
expect.arrayContaining([subscriberObj, dataObj]),
119+
expect.any(Object)
120+
)
121+
})
50122
})
51123
})

core-flux.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -103,17 +103,17 @@ export function createStore(
103103
}
104104
)
105105
},
106-
subscribe(subscriber, request) {
107-
if (!subscriber || !request) {
106+
subscribe(subscriber, data) {
107+
if (!subscriber || !data) {
108108
return
109109
}
110110

111111
const { subscriptions, state } = Stores[id]
112112

113-
subscriptions.push([subscriber, request])
113+
subscriptions.push([subscriber, data])
114114
const length = subscriptions.length
115115
bindSubscriber(subscriptions[length - 1], state)
116116
},
117-
// data: Stores[id],
117+
__data: Stores[id],
118118
}
119119
}

0 commit comments

Comments
 (0)