diff --git a/.gitignore b/.gitignore index 96ffa51..156f951 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,4 @@ package-lock.json /preact.js.map /react.js /react.js.map +.vscode/ diff --git a/README.md b/README.md index f66d32c..1e79fa1 100644 --- a/README.md +++ b/README.md @@ -69,10 +69,10 @@ You can find the library on `window.unistore`. import createStore from 'unistore' import { Provider, connect } from 'unistore/preact' -let store = createStore({ count: 0 }) +const store = createStore({ count: 0 }) // If actions is a function, it gets passed the store: -let actions = store => ({ +const actions = store => ({ // Actions can just return a state update: increment(state) { return { count: state.count+1 } @@ -90,7 +90,7 @@ let actions = store => ({ // Async actions can be pure async/promise functions: async getStuff(state) { - let res = await fetch('/foo.json') + const res = await fetch('/foo.json') return { stuff: await res.json() } }, @@ -126,8 +126,8 @@ Make sure to have [Redux devtools extension](https://github.com/zalmoxisus/redux import createStore from 'unistore' import devtools from 'unistore/devtools' -let initialState = { count: 0 }; -let store = process.env.NODE_ENV === 'production' ? createStore(initialState) : devtools(createStore(initialState)); +const initialState = { count: 0 }; +const store = process.env.NODE_ENV === 'production' ? createStore(initialState) : devtools(createStore(initialState)); // ... ``` diff --git a/package.json b/package.json index 5aab526..4115bfb 100644 --- a/package.json +++ b/package.json @@ -10,12 +10,13 @@ "scripts": { "build": "npm-run-all --silent -p build:main build:integrations build:combined -s size docs", "build:main": "microbundle", - "build:integrations": "microbundle src/integrations/*.js -o x.js -f cjs", - "build:combined": "microbundle src/combined/*.js -o full/x.js", + "build:integrations": "microbundle \"src/integrations/*.js\" -o x.js -f cjs", + "build:combined": "microbundle \"src/combined/*.js\" -o full/x.js", "size": "strip-json-comments --no-whitespace dist/unistore.js | gzip-size && bundlesize", "docs": "documentation readme unistore.js -q --section API && npm run -s fixreadme", "fixreadme": "node -e 'var fs=require(\"fs\");fs.writeFileSync(\"README.md\", fs.readFileSync(\"README.md\", \"utf8\").replace(/^- /gm, \"- \"))'", "test": "eslint src && npm run build && jest", + "test:jest": "jest", "prepare": "npm t", "release": "npm t && git commit -am $npm_package_version && git tag $npm_package_version && git push && git push --tags && npm publish" }, @@ -28,15 +29,15 @@ "bundlesize": [ { "path": "full/preact.js", - "maxSize": "750b" + "maxSize": "730b" }, { "path": "dist/unistore.js", - "maxSize": "400b" + "maxSize": "351b" }, { "path": "preact.js", - "maxSize": "600b" + "maxSize": "523b" } ], "babel": { diff --git a/src/index.js b/src/index.js index 2e50188..07c4e55 100644 --- a/src/index.js +++ b/src/index.js @@ -11,26 +11,18 @@ import { assign } from './util'; * store.setState({ c: 'd' }); // logs { a: 'b', c: 'd' } */ export default function createStore(state) { - let listeners = []; + const listeners = []; state = state || {}; function unsubscribe(listener) { - let out = []; - for (let i=0; i 0) listeners[i](state, action); } /** An observable state container, returned from {@link createStore} @@ -46,15 +38,15 @@ export default function createStore(state) { * @returns {Function} boundAction() */ action(action) { - function apply(result) { - setState(result, false, action); + function apply(update) { + setState(update, false, action); } // Note: perf tests verifying this implementation: https://esbench.com/bench/5a295e6299634800a0349500 return function() { - let args = [state]; - for (let i=0; i { function Wrapper(props, { store }) { let state = mapStateToProps(store ? store.getState() : {}, props); - let boundActions = actions ? mapActions(actions, store) : { store }; - let update = () => { + const boundActions = actions ? mapActions(actions, store) : { store }; + const update = () => { let mapped = mapStateToProps(store ? store.getState() : {}, this.props); for (let i in mapped) if (mapped[i]!==state[i]) { state = mapped; diff --git a/src/integrations/react.js b/src/integrations/react.js index 1aa981c..70eed70 100644 --- a/src/integrations/react.js +++ b/src/integrations/react.js @@ -27,9 +27,9 @@ export function connect(mapStateToProps, actions) { Component.call(this, props, context); let { store } = context; let state = mapStateToProps(store ? store.getState() : {}, props); - let boundActions = actions ? mapActions(actions, store) : { store }; - let update = () => { - let mapped = mapStateToProps(store ? store.getState() : {}, this.props); + const boundActions = actions ? mapActions(actions, store) : { store }; + const update = () => { + const mapped = mapStateToProps(store ? store.getState() : {}, this.props); for (let i in mapped) if (mapped[i]!==state[i]) { state = mapped; return this.forceUpdate(); diff --git a/src/util.js b/src/util.js index 7050423..b0b98e1 100644 --- a/src/util.js +++ b/src/util.js @@ -1,10 +1,8 @@ // Bind an object/factory of actions to the store and wrap them. export function mapActions(actions, store) { if (typeof actions==='function') actions = actions(store); - let mapped = {}; - for (let i in actions) { - mapped[i] = store.action(actions[i]); - } + const mapped = {}; + for (let i in actions) mapped[i] = store.action(actions[i]); return mapped; } @@ -13,10 +11,9 @@ export function mapActions(actions, store) { export function select(properties) { if (typeof properties==='string') properties = properties.split(/\s*,\s*/); return state => { - let selected = {}; - for (let i=0; i 0) selected[properties[i]] = state[properties[i]]; return selected; }; } diff --git a/test/preact/unistore.test.js b/test/preact/unistore.test.js index 1ade326..d2913e0 100644 --- a/test/preact/unistore.test.js +++ b/test/preact/unistore.test.js @@ -51,6 +51,52 @@ describe('createStore()', () => { expect(sub2).toBeCalledWith(store.getState(), action); }); + it('should invoke all subscriptions at time of setState, no matter unsubscriptions', () => { + let store = createStore(); + + let called = []; + let unsub2; + + let sub1 = jest.fn(() => { + called.push(1); + }); + let sub2 = jest.fn(() => { + called.push(2); + unsub2(); // unsubscribe during a listener callback + }); + let sub3 = jest.fn(() => { + called.push(3); + }); + + let unsub1 = store.subscribe(sub1); + unsub2 = store.subscribe(sub2); + let unsub3 = store.subscribe(sub3); + + store.setState({ a: 'a' }); + + expect(sub1).toHaveBeenCalledTimes(1); + expect(sub2).toHaveBeenCalledTimes(1); + expect(sub3).toHaveBeenCalledTimes(1); + expect(called.sort()).toEqual([1, 2, 3]); + + store.setState({ a: 'b' }); + + expect(sub1).toHaveBeenCalledTimes(2); + expect(sub2).toHaveBeenCalledTimes(1); + expect(sub3).toHaveBeenCalledTimes(2); + expect(called.sort()).toEqual([1, 1, 2, 3, 3]); + + unsub1(); + unsub3(); + + store.setState({ a: 'c' }); + + expect(sub1).toHaveBeenCalledTimes(2); + expect(sub2).toHaveBeenCalledTimes(1); + expect(sub3).toHaveBeenCalledTimes(2); + expect(called.sort()).toEqual([1, 1, 2, 3, 3]); + }); + it('should unsubscribe', () => { let store = createStore(); diff --git a/test/react/unistore.test.js b/test/react/unistore.test.js index d102d78..c8b1738 100644 --- a/test/react/unistore.test.js +++ b/test/react/unistore.test.js @@ -51,6 +51,41 @@ describe('createStore()', () => { expect(sub1).toHaveBeenLastCalledWith(store.getState(), undefined); expect(sub2).toBeCalledWith(store.getState(), undefined); }); + it('should invoke all subscriptions at time of setState, no matter unsubscriptions', () => { + let store = createStore(); + let called = []; + let unsub2; + let sub1 = jest.fn(() => { + called.push(1); + }); + let sub2 = jest.fn(() => { + called.push(2); + unsub2(); // unsubscribe during a listener callback + }); + let sub3 = jest.fn(() => { + called.push(3); + }); + let unsub1 = store.subscribe(sub1); + unsub2 = store.subscribe(sub2); + let unsub3 = store.subscribe(sub3); + store.setState({ a: 'a' }); + expect(sub1).toHaveBeenCalledTimes(1); + expect(sub2).toHaveBeenCalledTimes(1); + expect(sub3).toHaveBeenCalledTimes(1); + expect(called.sort()).toEqual([1, 2, 3]); + store.setState({ a: 'b' }); + expect(sub1).toHaveBeenCalledTimes(2); + expect(sub2).toHaveBeenCalledTimes(1); + expect(sub3).toHaveBeenCalledTimes(2); + expect(called.sort()).toEqual([1, 1, 2, 3, 3]); + unsub1(); + unsub3(); + store.setState({ a: 'c' }); + expect(sub1).toHaveBeenCalledTimes(2); + expect(sub2).toHaveBeenCalledTimes(1); + expect(sub3).toHaveBeenCalledTimes(2); + expect(called.sort()).toEqual([1, 1, 2, 3, 3]); + }); it('should unsubscribe', () => { let store = createStore(); let sub1 = jest.fn();