electrum-store
Advanced tools
Comparing version 0.9.0 to 0.9.1
@@ -7,2 +7,6 @@ 'use strict'; | ||
var _middleware = require('./store/middleware.js'); | ||
var _middleware2 = _interopRequireDefault(_middleware); | ||
var _state = require('./store/state.js'); | ||
@@ -16,6 +20,2 @@ | ||
var _theme = require('./store/theme.js'); | ||
var _theme2 = _interopRequireDefault(_theme); | ||
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } | ||
@@ -25,5 +25,5 @@ | ||
Activity: _activity2.default, | ||
Middleware: _middleware2.default, | ||
State: _state2.default, | ||
Store: _store2.default, | ||
Theme: _theme2.default | ||
Store: _store2.default | ||
}; |
@@ -7,2 +7,4 @@ 'use strict'; | ||
var _electrumTheme = require('electrum-theme'); | ||
describe('Store', function () { | ||
@@ -32,3 +34,3 @@ describe('Store.link()', function () { | ||
var store = _index.Store.create('x'); | ||
var theme = _index.Theme.create('default'); | ||
var theme = _electrumTheme.Theme.create('default'); | ||
store.select('a.b.c'); | ||
@@ -43,4 +45,4 @@ var props1 = { state: store.find('a'), theme: theme }; | ||
var store = _index.Store.create('x'); | ||
var theme1 = _index.Theme.create('default'); | ||
var theme2 = _index.Theme.create('default'); | ||
var theme1 = _electrumTheme.Theme.create('default'); | ||
var theme2 = _electrumTheme.Theme.create('default'); | ||
store.select('a.b.c'); | ||
@@ -53,2 +55,63 @@ var props1 = { state: store.find('a'), theme: theme1 }; | ||
}); | ||
describe('Store.link() using Middleware', function () { | ||
it('produces properties linked to child state', function () { | ||
var store = _index.Store.create('x'); | ||
var middleware = new _index.Middleware(); | ||
middleware.register('state', function (id, prop) { | ||
return prop.select(id); | ||
}); | ||
store.select('a.b.c'); | ||
var props1 = { state: store.find('a') }; | ||
var props2 = middleware.link(props1, 'b'); | ||
(0, _maiChai.expect)(props1.state).to.equal(store.find('a')); | ||
(0, _maiChai.expect)(props2.state).to.equal(store.find('a.b')); | ||
}); | ||
it('produces properties without unrelated stuff', function () { | ||
var store = _index.Store.create('x'); | ||
var middleware = new _index.Middleware(); | ||
middleware.register('state', function (id, prop) { | ||
return prop.select(id); | ||
}); | ||
store.select('a.b.c'); | ||
var props1 = { state: store.find('a'), foo: 'bar' }; | ||
var props2 = middleware.link(props1, 'b'); | ||
(0, _maiChai.expect)(props1).to.have.property('state'); | ||
(0, _maiChai.expect)(props1).to.have.property('foo'); | ||
(0, _maiChai.expect)(props2).to.have.property('state'); | ||
(0, _maiChai.expect)(props2).to.not.have.property('foo'); | ||
}); | ||
it('produces properties which propagate the theme', function () { | ||
var store = _index.Store.create('x'); | ||
var middleware = new _index.Middleware(); | ||
middleware.register('state', function (id, prop) { | ||
return prop.select(id); | ||
}); | ||
middleware.register('theme'); | ||
var theme = _electrumTheme.Theme.create('default'); | ||
store.select('a.b.c'); | ||
var props1 = { state: store.find('a'), theme: theme }; | ||
var props2 = middleware.link(props1, 'b'); | ||
(0, _maiChai.expect)(props1.theme).to.equal(theme); | ||
(0, _maiChai.expect)(props2.theme).to.equal(theme); | ||
}); | ||
it('allows for the theme to be overridden', function () { | ||
var store = _index.Store.create('x'); | ||
var middleware = new _index.Middleware(); | ||
middleware.register('state', function (id, prop) { | ||
return prop.select(id); | ||
}); | ||
middleware.register('theme'); | ||
var theme1 = _electrumTheme.Theme.create('default'); | ||
var theme2 = _electrumTheme.Theme.create('default'); | ||
store.select('a.b.c'); | ||
var props1 = { state: store.find('a'), theme: theme1 }; | ||
var props2 = middleware.link(props1, 'b', { theme: theme2 }); | ||
(0, _maiChai.expect)(props1.theme).to.equal(theme1); | ||
(0, _maiChai.expect)(props2.theme).to.equal(theme2); | ||
}); | ||
}); | ||
}); |
{ | ||
"name": "electrum-store", | ||
"version": "0.9.0", | ||
"version": "0.9.1", | ||
"description": "Electrum store provides a store implementation tailored for Electrum.", | ||
@@ -27,2 +27,3 @@ "main": "lib/index.js", | ||
"chai": "^3.4.1", | ||
"electrum-theme": "^0.9.0", | ||
"jsdom": "^7.0.2", | ||
@@ -29,0 +30,0 @@ "mai-chai": "^1.1.2", |
146
README.md
@@ -9,1 +9,147 @@ # Electrum Store | ||
for use with `electrum-arc`, the Electrum Agnostic Reactive Components. | ||
The **store** maintains **state** organized as a tree. State is | ||
**immutable**. When the store is updated, new state is produced and | ||
nodes get replaced in the tree. | ||
Neither the store nor its states will emit notifications when things | ||
change, since `electrum` does not need the feature. | ||
Thanks to immutability, whole trees can be compared for equality | ||
with `===`. Whenever a (sub-)tree changes, the store guarantees that | ||
the `state` objects change too, from the node in the tree where the | ||
change happened up to the root of the tree (_change percolation_). | ||
# Store | ||
## Create a store | ||
To create a store, call `Store.create()`. The constructor is not available | ||
for public consumption and should not be called. | ||
```javascript | ||
const store = Store.create (); | ||
``` | ||
## Access state in the store | ||
The store maintains its state as a tree. Selecting state located at `a.b.c` | ||
will automatically create `a`, `a.b` and `a.b.c` if they did not yet exist | ||
in the tree. You can call `select()` on a `state` object, which can be used | ||
to navigate down the tree. | ||
`select()` creates missing nodes whereas `find()` returns `undefined` if | ||
it does not find the specified nodes. | ||
```javascript | ||
const store = Store.create (); | ||
const state1 = store.select ('a.b.c'); | ||
const state2 = store.select ('a').select ('b.c'); | ||
const state3 = store.find ('a.b.c'); | ||
const state4 = store.find ('x.y'); | ||
expect (state1 === state2); | ||
expect (state1 === state3); | ||
expect (state4 === undefined); | ||
``` | ||
## Mutate the store | ||
Whenever new state needs to be recorded in the store, the tree will be | ||
updated and new _generation_ tags will be applied to the parts of the | ||
tree which changed as a result of this. | ||
Setting `a.b.c` first will produce nodes `a`, `b` and `c` in generation 1. | ||
Adding `a.b.d` will mutate `a.b` (it contains a new child `d`) and | ||
also mutate `a` (it contains an updated `a.b`); all this will happen | ||
inside generation 2. Nodes `a`, `b` and `d` will have `generation:2` | ||
whereas node `c` will remain at `generation:1`. | ||
```javascript | ||
const store = Store.create (); | ||
store.select ('a.b.c'); // generation 1 | ||
store.select ('a.b.d'); // generation 2 | ||
expect (store.find ('a').generation === 2); | ||
expect (store.find ('a.b').generation === 2); | ||
expect (store.find ('a.b.c').generation === 1); | ||
expect (store.find ('a.b.d').generation === 2); | ||
``` | ||
## Explicitly set state | ||
State is usually updated using `with()`, `withValue()` and `withValues()` | ||
or created implicitly by `select()`. It is also possible to set state | ||
explicitly: | ||
```javascript | ||
const store = Store.create (); | ||
const state1 = State.create ('x.y'); | ||
const state2 = store.setState (state1); | ||
expect (state1.generation === 0); | ||
expect (state1 !== state2); | ||
expect (state2.generation === 1); | ||
``` | ||
# State | ||
State holds following information: | ||
* `id` → the absolute path of the node (e.g. `'a.b.c'`) | ||
* `store` → a reference to the containing store. | ||
* `generation` → the generation number of last update. | ||
* `values` → a collection of values - this is never accessed directly. | ||
The default value is accessed with `state.value`. Named values can be | ||
accessed using `state.get(name)`. | ||
## Create state | ||
To create state with an initial value, use `State.create()`. | ||
```javascript | ||
const state1 = State.create ('empty'); | ||
expect (state1.value === undefined); | ||
const state2 = State.create ('message', {'': 'Hello'}); | ||
expect (state2.value === 'Hello'); | ||
const state3 = State.create ('person', {name: 'Joe', age: 78}); | ||
expect (state3.get ('name') === 'Joe'); | ||
expect (state3.get ('age') === 78); | ||
``` | ||
## Mutate state | ||
State objects are immutable. Updating state will produce a copy of | ||
the state object with the new values. | ||
```javascript | ||
const state1 = State.create ('a', {x: 1, y: 2}); | ||
const state2 = State.withValue (state1, 'x', 10); | ||
const state3 = State.withValues (state1, 'x', 10, 'y', 20); | ||
const state4 = State.with (state1, {values: {x: 10, y: 20}}); | ||
// Setting same values does not mutate state: | ||
const state5 = State.with (state1, {values: {x: 1, y: 2}}); | ||
expect (state1 === state5); // same values, same state | ||
``` | ||
## Mutate state in a store | ||
When a state attached to a store is being mutated, the new state will | ||
be stored in the tree, and all nodes up to the root will get updated | ||
while doing so. | ||
```javascript | ||
const store = Store.create (); | ||
expect (store.select ('a.b.c').generation === 1); // gen. 1 | ||
expect (store.select ('a.b.d').generation === 2); // gen. 2 | ||
State.withValue (store.select ('a.b.c'), 'x', 10); // gen. 3 | ||
expect (store.select ('a.b.c').generation === 3); | ||
expect (store.select ('a.b.d').generation === 2); // unchanged | ||
expect (store.select ('a.b').generation === 3); | ||
expect (store.select ('a').generation === 3); | ||
State.withValue (store.select ('a.b'), 'y', 20); // gen. 4 | ||
expect (store.select ('a.b.c').generation === 3); // unchanged | ||
expect (store.select ('a.b.d').generation === 2); // unchanged | ||
expect (store.select ('a.b').generation === 4); | ||
expect (store.select ('a').generation === 4); | ||
``` |
'use strict'; | ||
import Activity from './store/activity.js'; | ||
import Middleware from './store/middleware.js'; | ||
import State from './store/state.js'; | ||
import Store from './store/store.js'; | ||
import Theme from './store/theme.js'; | ||
module.exports = { | ||
Activity, | ||
Middleware, | ||
State, | ||
Store, | ||
Theme | ||
Store | ||
}; |
'use strict'; | ||
import {expect} from 'mai-chai'; | ||
import {Store, Theme} from '../index.js'; | ||
import {Middleware, Store} from '../index.js'; | ||
import {Theme} from 'electrum-theme'; | ||
@@ -49,2 +50,55 @@ describe ('Store', () => { | ||
}); | ||
describe ('Store.link() using Middleware', () => { | ||
it ('produces properties linked to child state', () => { | ||
const store = Store.create ('x'); | ||
const middleware = new Middleware (); | ||
middleware.register ('state', (id, prop) => prop.select (id)); | ||
store.select ('a.b.c'); | ||
const props1 = {state: store.find ('a')}; | ||
const props2 = middleware.link (props1, 'b'); | ||
expect (props1.state).to.equal (store.find ('a')); | ||
expect (props2.state).to.equal (store.find ('a.b')); | ||
}); | ||
it ('produces properties without unrelated stuff', () => { | ||
const store = Store.create ('x'); | ||
const middleware = new Middleware (); | ||
middleware.register ('state', (id, prop) => prop.select (id)); | ||
store.select ('a.b.c'); | ||
const props1 = {state: store.find ('a'), foo: 'bar'}; | ||
const props2 = middleware.link (props1, 'b'); | ||
expect (props1).to.have.property ('state'); | ||
expect (props1).to.have.property ('foo'); | ||
expect (props2).to.have.property ('state'); | ||
expect (props2).to.not.have.property ('foo'); | ||
}); | ||
it ('produces properties which propagate the theme', () => { | ||
const store = Store.create ('x'); | ||
const middleware = new Middleware (); | ||
middleware.register ('state', (id, prop) => prop.select (id)); | ||
middleware.register ('theme'); | ||
const theme = Theme.create ('default'); | ||
store.select ('a.b.c'); | ||
const props1 = {state: store.find ('a'), theme: theme}; | ||
const props2 = middleware.link (props1, 'b'); | ||
expect (props1.theme).to.equal (theme); | ||
expect (props2.theme).to.equal (theme); | ||
}); | ||
it ('allows for the theme to be overridden', () => { | ||
const store = Store.create ('x'); | ||
const middleware = new Middleware (); | ||
middleware.register ('state', (id, prop) => prop.select (id)); | ||
middleware.register ('theme'); | ||
const theme1 = Theme.create ('default'); | ||
const theme2 = Theme.create ('default'); | ||
store.select ('a.b.c'); | ||
const props1 = {state: store.find ('a'), theme: theme1}; | ||
const props2 = middleware.link (props1, 'b', {theme: theme2}); | ||
expect (props1.theme).to.equal (theme1); | ||
expect (props2.theme).to.equal (theme2); | ||
}); | ||
}); | ||
}); |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
155
89743
20
37
2074