From bb4c2506c08594e2160bf4a1b576693039684a46 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A9ry=20Debongnie?= Date: Sun, 25 Aug 2019 21:38:16 +0200 Subject: [PATCH] [REF] store: merge actions and mutations closes #256 --- src/store/store.ts | 79 +---- tests/store/connected_component.test.ts | 77 +++-- tests/store/store.test.ts | 398 +++++------------------- tools/playground/samples.js | 68 ++-- 4 files changed, 150 insertions(+), 472 deletions(-) diff --git a/src/store/store.ts b/src/store/store.ts index bce38e7f..10e85817 100644 --- a/src/store/store.ts +++ b/src/store/store.ts @@ -11,7 +11,7 @@ import { Observer } from "../core/observer"; * * The Owl store is our answer to the problem of managing complex state across * components. The main idea is that the store owns some state, allow external - * code to modify it through actions/mutations, and for each state changes, + * code to modify it through actions, and for each state changes, * connected component will be notified, and updated if necessary. * * Note that this code is partly inspired by VueX and React/Redux @@ -21,16 +21,14 @@ import { Observer } from "../core/observer"; // Store Definition //------------------------------------------------------------------------------ -type Mutation = ({ state, commit, getters }, ...payload: any) => void; -type Action = ({ commit, state, dispatch, env, getters }, ...payload: any) => void; -type Getter = ({ state, getters }, payload) => any; +export type Action = ({ state, dispatch, env, getters }, ...payload: any) => any; +export type Getter = ({ state: any, getters }, payload?) => any; interface StoreConfig { env?: Env; state?: any; actions?: { [name: string]: Action }; getters?: { [name: string]: Getter }; - mutations?: { [name: string]: Mutation }; } interface StoreOption { @@ -41,46 +39,28 @@ export class Store extends EventBus { state: any; actions: any; mutations: any; - _commitLevel: number = 0; - history: any[] = []; debug: boolean; env: any; observer: Observer; - getters: { [name: string]: (payload?) => any }; - _gettersCache: { [name: string]: {} }; - _updateId: number = 1; + getters: { [name: string]: (payload?) => any }; constructor(config: StoreConfig, options: StoreOption = {}) { super(); this.debug = options.debug || false; this.actions = config.actions; - this.mutations = config.mutations; this.env = config.env; this.observer = new Observer(); this.observer.notifyCB = this.__notifyComponents.bind(this); - this.observer.allowMutations = false; this.state = this.observer.observe(config.state || {}); this.getters = {}; - this._gettersCache = {}; - - if (this.debug) { - this.history.push({ state: this.state }); - } - - const cTypes = ["undefined", "number", "string"]; - for (let entry of Object.entries(config.getters || {})) { - const name: string = entry[0]; - const func: (...any) => any = entry[1]; - this.getters[name] = payload => { - if (this._commitLevel === 0 && cTypes.indexOf(typeof payload) >= 0) { - this._gettersCache[name] = this._gettersCache[name] || {}; - this._gettersCache[name][payload] = - this._gettersCache[name][payload] || - func({ state: this.state, getters: this.getters }, payload); - return this._gettersCache[name][payload]; + if (config.getters) { + const firstArg = { + state: this.state, + getters: this.getters, + }; + for (let g in config.getters) { + this.getters[g] = config.getters[g].bind(this, firstArg); } - return func({ state: this.state, getters: this.getters }, payload); - }; } } @@ -90,7 +70,6 @@ export class Store extends EventBus { } const result = this.actions[action]( { - commit: this.commit.bind(this), dispatch: this.dispatch.bind(this), env: this.env, state: this.state, @@ -101,37 +80,6 @@ export class Store extends EventBus { return result; } - commit(type: string, ...payload: any): any { - if (!this.mutations[type]) { - throw new Error(`[Error] mutation ${type} is undefined`); - } - this._commitLevel++; - this.observer.allowMutations = true; - - const res = this.mutations[type].call( - null, - { - commit: this.commit.bind(this), - state: this.state, - getters: this.getters - }, - ...payload - ); - - if (this._commitLevel === 1) { - this.observer.allowMutations = false; - if (this.debug) { - this.history.push({ - state: this.state, - mutation: type, - payload: [...payload] - }); - } - } - this._commitLevel--; - return res; - } - /** * Instead of using trigger to emit an update event, we actually implement * our own function to do that. The reason is that we need to be smarter than @@ -147,15 +95,12 @@ export class Store extends EventBus { * updating all widgets concurrently, except for parents/children. */ async __notifyComponents() { - this._updateId++; - const current = this._updateId; - this._gettersCache = {}; const subs = this.subscriptions.update || []; for (let i = 0, iLen = subs.length; i < iLen; i++) { const sub = subs[i]; const shouldCallback = sub.owner ? sub.owner.__owl__.isMounted : true; if (shouldCallback) { - await sub.callback.call(sub.owner, current); + await sub.callback.call(sub.owner); } } } diff --git a/tests/store/connected_component.test.ts b/tests/store/connected_component.test.ts index 29914c11..82e84ba8 100644 --- a/tests/store/connected_component.test.ts +++ b/tests/store/connected_component.test.ts @@ -35,19 +35,19 @@ describe("connecting a component to store", () => { } class Todo extends Component {} const state = { todos: [] }; - const mutations = { + const actions = { addTodo({ state }, msg) { state.todos.push({ msg }); } }; - const store = new Store({ state, mutations }); + const store = new Store({ state, actions }); (env).store = store; const app = new App(env); await app.mount(fixture); expect(fixture.innerHTML).toMatchSnapshot(); - store.commit("addTodo", "hello"); + store.dispatch("addTodo", "hello"); await nextTick(); expect(fixture.innerHTML).toMatchSnapshot(); }); @@ -63,12 +63,12 @@ describe("connecting a component to store", () => { `); const state = { todos: [{ title: "Kasteel" }] }; - const mutations = { + const actions = { edit({ state }, title) { state.todos[0].title = title; } }; - const store = new Store({ state, mutations }); + const store = new Store({ state, actions }); class App extends ConnectedComponent { static mapStoreToProps(s) { @@ -94,7 +94,7 @@ describe("connecting a component to store", () => { expect(fixture.innerHTML).toMatchSnapshot(); expect(shallowFix.innerHTML).toMatchSnapshot(); - store.commit("edit", "Bertinchamps"); + store.dispatch("edit", "Bertinchamps"); await nextTick(); expect(fixture.innerHTML).toMatchSnapshot(); expect(shallowFix.innerHTML).toMatchSnapshot(); @@ -113,10 +113,9 @@ describe("connecting a component to store", () => { `); class Todo extends Component {} - (env).store = new Store({}); const store = new Store({ state: { todos: [] }, - mutations: { + actions: { addTodo({ state }, msg) { state.todos.push({ msg }); } @@ -136,7 +135,7 @@ describe("connecting a component to store", () => { await app.mount(fixture); expect(fixture.innerHTML).toMatchSnapshot(); - (app.__owl__).store.commit("addTodo", "hello"); + (app.__owl__).store.dispatch("addTodo", "hello"); await nextTick(); expect(fixture.innerHTML).toMatchSnapshot(); }); @@ -187,12 +186,12 @@ describe("connecting a component to store", () => { test("mapStoreToProps receives ownprops as second argument", async () => { const state = { todos: [{ id: 1, text: "jupiler" }] }; let nextId = 2; - const mutations = { + const actions = { addTodo({ state }, text) { state.todos.push({ text, id: nextId++ }); } }; - const store = new Store({ state, mutations }); + const store = new Store({ state, actions }); env.qweb.addTemplates(` @@ -224,7 +223,7 @@ describe("connecting a component to store", () => { await app.mount(fixture); expect(fixture.innerHTML).toBe("
jupiler
"); - store.commit("addTodo", "hoegaarden"); + store.dispatch("addTodo", "hoegaarden"); await nextTick(); expect(fixture.innerHTML).toBe("
jupilerhoegaarden
"); }); @@ -333,14 +332,14 @@ describe("connecting a component to store", () => { } } - const mutations = { + const actions = { addBeer({ state }, name) { state.beers.push({ name }); } }; const state = { beers: [{ name: "jupiler" }] }; - const store = new Store({ state, mutations }); + const store = new Store({ state, actions }); (env).store = store; const app = new App(env); @@ -348,7 +347,7 @@ describe("connecting a component to store", () => { await app.mount(fixture); expect(fixture.innerHTML).toBe("
jupiler
"); - store.commit("addBeer", "kwak"); + store.dispatch("addBeer", "kwak"); await nextTick(); expect(fixture.innerHTML).toBe("
jupilerkwak
"); }); @@ -382,7 +381,7 @@ describe("connecting a component to store", () => { state = { beerId: 0 }; } - const mutations = { + const actions = { consume({ state }, beerId) { state.consumedID = beerId; } @@ -394,7 +393,7 @@ describe("connecting a component to store", () => { consumedID: null, taster: "aaron" }; - const store = new Store({ state, mutations }); + const store = new Store({ state, actions }); (env).store = store; const app = new App(env); @@ -407,7 +406,7 @@ describe("connecting a component to store", () => { "
taster:aaronselected:jupiler
" ); - store.commit("consume", 1); + store.dispatch("consume", 1); await nextTick(); expect(fixture.innerHTML).toBe( "
taster:aaronselected:jupilerconsumed:jupiler
" @@ -449,7 +448,7 @@ describe("connecting a component to store", () => { state = { beerId: 0 }; } - const mutations = { + const actions = { changeTaster({ state }, newTaster) { state.taster = newTaster; }, @@ -467,7 +466,7 @@ describe("connecting a component to store", () => { consumedID: null, taster: "aaron" }; - const store = new Store({ state, mutations }); + const store = new Store({ state, actions }); (env).store = store; const app = new App(env); @@ -480,13 +479,13 @@ describe("connecting a component to store", () => { "
taster:aaronselected:jupiler
" ); - store.commit("renameBeer", { beerId: 1, name: "kwak" }); + store.dispatch("renameBeer", { beerId: 1, name: "kwak" }); await nextTick(); expect(fixture.innerHTML).toBe( "
taster:aaronselected:kwak
" ); - store.commit("consume", 1); + store.dispatch("consume", 1); await nextTick(); expect(fixture.innerHTML).toBe( "
taster:aaronselected:kwakconsumed:kwak
" @@ -498,13 +497,13 @@ describe("connecting a component to store", () => { "
taster:aaronconsumed:kwak
" ); - store.commit("renameBeer", { beerId: 1, name: "jupiler" }); + store.dispatch("renameBeer", { beerId: 1, name: "jupiler" }); await nextTick(); expect(fixture.innerHTML).toBe( "
taster:aaronconsumed:jupiler
" ); - store.commit("changeTaster", "matthieu"); + store.dispatch("changeTaster", "matthieu"); await nextTick(); expect(fixture.innerHTML).toBe( "
taster:matthieuconsumed:jupiler
" @@ -539,13 +538,13 @@ describe("connecting a component to store", () => { } const state = { current: "a", msg: { a: "a", b: "b" } }; - const mutations = { + const actions = { setCurrent({ state }, c) { state.current = c; } }; - const store = new Store({ state, mutations }); + const store = new Store({ state, actions }); (env).store = store; const app = new Parent(env); @@ -553,14 +552,14 @@ describe("connecting a component to store", () => { expect(fixture.innerHTML).toBe("
a
"); expect(steps).toEqual(["parent", "child"]); - store.commit("setCurrent", "b"); + store.dispatch("setCurrent", "b"); await nextTick(); expect(fixture.innerHTML).toBe("
b
"); expect(steps).toEqual(["parent", "child", "parent", "child"]); }); test("connected parent/children: no double rendering", async () => { - const mutations = { + const actions = { editTodo({ state }) { state.todos[1].title = "abc"; } @@ -571,7 +570,7 @@ describe("connecting a component to store", () => { }; const store = new Store({ state, - mutations + actions }); env.qweb.addTemplates(` @@ -611,7 +610,7 @@ describe("connecting a component to store", () => { } editTodo() { - this.env.store.commit("editTodo"); + this.env.store.dispatch("editTodo"); } __render(...args) { renderCount++; @@ -639,7 +638,7 @@ describe("connecting a component to store", () => { }); test("connected parent/children: no rendering if child is destroyed", async () => { - const mutations = { + const actions = { removeTodo({ state }) { delete state.todos[1]; } @@ -650,7 +649,7 @@ describe("connecting a component to store", () => { }; const store = new Store({ state, - mutations + actions }); env.qweb.addTemplates(` @@ -690,7 +689,7 @@ describe("connecting a component to store", () => { }; } removeTodo() { - this.env.store.commit("removeTodo"); + this.env.store.dispatch("removeTodo"); } __render(...args) { renderCount++; @@ -737,20 +736,20 @@ describe("connecting a component to store", () => { } const state = { msg: "a" }; - const mutations = { + const actions = { setMsg({ state }, c) { state.msg = c; } }; - const store = new Store({ state, mutations }); + const store = new Store({ state, actions }); (env).store = store; const app = new App(env); await app.mount(fixture); expect(fixture.innerHTML).toBe("
a
"); - store.commit("setMsg", "b"); + store.dispatch("setMsg", "b"); await nextTick(); expect(fixture.innerHTML).toBe("
b
"); expect(steps).toEqual(["willpatch", "patched"]); @@ -889,13 +888,13 @@ describe("connected components and default values", () => { } }; - const mutations = { + const actions = { changeMessageContent({ state }, messageId, newContent) { state.messages[messageId].content = newContent; } }; - const store = new Store({ state, mutations }); + const store = new Store({ state, actions }); (env).store = store; const app = new App(env); @@ -908,7 +907,7 @@ describe("connected components and default values", () => { await app.render(); expect(fixture.innerHTML).toBe("
200Message200
"); - store.commit("changeMessageContent", 200, "UpdatedMessage200"); + store.dispatch("changeMessageContent", 200, "UpdatedMessage200"); await nextTick(); expect(fixture.innerHTML).toBe("
200UpdatedMessage200
"); }); diff --git a/tests/store/store.test.ts b/tests/store/store.test.ts index b83ff3c4..4e8ffc93 100644 --- a/tests/store/store.test.ts +++ b/tests/store/store.test.ts @@ -1,57 +1,33 @@ import { Env } from "../../src/component/component"; -import { Store } from "../../src/store/store"; -import { nextMicroTick, nextTick } from "../helpers"; - +import { Store, Getter } from "../../src/store/store"; +import { nextTick, nextMicroTick } from "../helpers"; describe("basic use", () => { - test("commit a mutation", () => { - const state = { n: 1 }; - const mutations = { - inc({ state }, delta) { - state.n += delta; - } - }; - const store = new Store({ state, mutations }); - - expect(store.state.n).toBe(1); - store.commit("inc", 14); - expect(store.state.n).toBe(15); - }); - test("dispatch an action", () => { const state = { n: 1 }; - const mutations = { + const actions = { inc({ state }, delta) { state.n += delta; } }; - const actions = { - inc({ commit }, delta) { - commit("inc", delta); - } - }; - const store = new Store({ state, mutations, actions }); + const store = new Store({ state, actions }); expect(store.state.n).toBe(1); store.dispatch("inc", 14); expect(store.state.n).toBe(15); }); - test("dispatch an action + commit a mutation with positional arguments", () => { + test("dispatch an action with positional arguments", () => { const state = { n1: 1, n2: 1, n3: 1 }; - const mutations = { + + const actions = { batchInc({ state }, delta1, delta2, delta3) { state.n1 += delta1; state.n2 += delta2; state.n3 += delta3; } }; - const actions = { - batchInc({ commit }, delta1, delta2, delta3) { - commit("batchInc", delta1, delta2, delta3); - } - }; - const store = new Store({ state, mutations, actions }); + const store = new Store({ state, actions }); expect(store.state.n1).toBe(1); expect(store.state.n2).toBe(1); @@ -62,96 +38,58 @@ describe("basic use", () => { expect(store.state.n3).toBe(89); }); - test("modifying state outside of mutations trigger error", () => { - const state = { n: 1 }; - const actions = { - inc({ state }) { - state.n++; - } - }; - const store = new Store({ state, mutations: {}, actions }); - - expect(() => store.dispatch("inc")).toThrow(); - expect(() => (store.state.n = 15)).toThrow(); - }); - test("can dispatch an action in an action", () => { const state = { n: 1 }; - const mutations = { + + const actions = { inc({ state }, delta) { state.n += delta; - } - }; - const actions = { - inc({ commit }, delta) { - commit("inc", delta); }, inc100({ dispatch }) { dispatch("inc", 100); } }; - const store = new Store({ state, mutations, actions }); + const store = new Store({ state, actions }); expect(store.state.n).toBe(1); store.dispatch("inc100"); expect(store.state.n).toBe(101); }); - test("can commit a mutation in a mutation", () => { + test("return data from dispatching an action", () => { const state = { n: 1 }; - const mutations = { - inc({ state }) { - state.n++; - }, - inc10({ commit }) { - for (let i = 0; i < 10; i++) { - commit("inc"); - } - } - }; - const store = new Store({ state, mutations }); - - expect(store.state.n).toBe(1); - store.commit("inc10"); - expect(store.state.n).toBe(11); - }); - - test("return data from committing a mutation", () => { - const state = { n: 1 }; - const mutations = { + const actions = { inc({ state }) { return ++state.n; } }; - const store = new Store({ state, mutations }); + const store = new Store({ state, actions }); expect(store.state.n).toBe(1); - const res = store.commit("inc"); + const res = store.dispatch("inc"); expect(store.state.n).toBe(2); expect(res).toBe(2); }); test("dispatch allow synchronizing between actions", async () => { const state = { n: 1 }; - const mutations = { + const actions = { inc({ state }, delta) { state.n += delta; }, setN({ state }, n) { state.n = n; - } - }; - const actions = { - async dosomething({ commit, dispatch }) { - await dispatch("setTo10"); - commit("inc", 3); }, - async setTo10({ commit }) { + async dosomething({ dispatch }) { + await dispatch("setTo10"); + dispatch("inc", 3); + }, + async setTo10({ dispatch }) { await Promise.resolve(); - commit("setN", 10); + dispatch("setN", 10); } }; - const store = new Store({ state, mutations, actions }); + const store = new Store({ state, actions }); expect(store.state.n).toBe(1); store.dispatch("dosomething"); @@ -162,26 +100,24 @@ describe("basic use", () => { test("return data from dispatching an action", async () => { const state = { n: 1 }; - const mutations = { + const actions = { inc({ state }, delta) { state.n += delta; }, setN({ state }, n) { state.n = n; - } - }; - const actions = { - async dosomething({ commit, dispatch }) { - const val = await dispatch("setTo10"); - commit("inc", val); }, - async setTo10({ commit }) { + async dosomething({ dispatch }) { + const val = await dispatch("setTo10"); + dispatch("inc", val); + }, + async setTo10({ dispatch }) { await Promise.resolve(); - commit("setN", 10); + dispatch("setN", 10); return 5; } }; - const store = new Store({ state, mutations, actions }); + const store = new Store({ state, actions }); expect(store.state.n).toBe(1); store.dispatch("dosomething"); @@ -219,7 +155,7 @@ describe("basic use", () => { } } }; - const getters = { + const getters: { [key: string]: Getter } = { beerTasterName({ state }, beerID) { return state.tasters[state.beers[beerID].tasterID].name; }, @@ -227,161 +163,12 @@ describe("basic use", () => { return state.beers[1].name; } }; - const store = new Store({ state, mutations: {}, actions: {}, getters }); + const store = new Store({ state, actions: {}, getters }); expect(store.getters).toBeDefined(); expect((store.getters).bestBeerName()).toBe("bertinchamps"); expect((store.getters).beerTasterName(1)).toBe("aaron"); }); - test("getters are memoized", async () => { - const state = { - beers: { - 1: { - id: 1, - name: "bertinchamps", - tasterID: 1 - } - }, - tasters: { - 1: { - id: 1, - name: "aaron" - } - } - }; - let n = 0; - const getters = { - beerTasterName({ state }, beerID) { - n++; - return state.tasters[state.beers[beerID].tasterID].name; - }, - bestBeerName({ state }) { - n++; - return state.beers[1].name; - } - }; - const store = new Store({ state, mutations: {}, actions: {}, getters }); - expect((store.getters).bestBeerName()).toBe("bertinchamps"); - expect((store.getters).beerTasterName(1)).toBe("aaron"); - expect(n).toBe(2); - expect((store.getters).bestBeerName()).toBe("bertinchamps"); - expect((store.getters).beerTasterName(1)).toBe("aaron"); - expect(n).toBe(2); - }); - - test("getters taking Array as argument aren't memoized", async () => { - const state = { - beers: { - 1: { - id: 1, - name: "bertinchamps", - tasterID: 1 - } - } - }; - let n = 0; - const getters = { - getBeerNames({ state }, beerIDs) { - n++; - return beerIDs.map(beerID => { - return state.beers[beerID].name; - }); - } - }; - const store = new Store({ state, mutations: {}, actions: {}, getters }); - expect((store.getters).getBeerNames([1])).toEqual(["bertinchamps"]); - expect(n).toBe(1); - expect((store.getters).getBeerNames([1])).toEqual(["bertinchamps"]); - expect(n).toBe(2); - }); - - test("getters cache is nuked on store changes", async () => { - const state = { - beers: { - 1: { - id: 1, - name: "bertinchamps", - tasterID: 1 - } - }, - tasters: { - 1: { - id: 1, - name: "aaron" - }, - 2: { - id: 2, - name: "gery" - } - } - }; - const mutations = { - changeTaster({ state }, { beerID, tasterID }) { - state.beers[beerID].tasterID = tasterID; - } - }; - let n = 0; - const getters = { - beerTasterName({ state }, beerID) { - n++; - return state.tasters[state.beers[beerID].tasterID].name; - } - }; - const store = new Store({ - state, - mutations: mutations, - actions: {}, - getters - }); - expect((store.getters).beerTasterName(1)).toBe("aaron"); - expect(n).toBe(1); - expect((store.getters).beerTasterName(1)).toBe("aaron"); - expect(n).toBe(1); - - store.commit("changeTaster", { beerID: 1, tasterID: 2 }); - await nextTick(); - - expect((store.getters).beerTasterName(1)).toBe("gery"); - expect(n).toBe(2); - }); - - test("getters cache is disabled during a mutation", async () => { - const state = { - beers: { - 1: { - id: 1, - name: "bertinchamps" - } - } - }; - const mutations = { - renameBeer({ state, getters }, beerID) { - expect(getters.beerName(beerID)).toBe("bertinchamps"); - state.beers[1].name = "chouffe"; - expect(getters.beerName(beerID)).toBe("chouffe"); - } - }; - let n = 0; - const getters = { - beerName({ state }, beerID) { - n++; - return state.beers[beerID].name; - } - }; - const store = new Store({ - state, - mutations: mutations, - actions: {}, - getters - }); - - store.commit("renameBeer", 1); - expect((store.getters).beerName(1)).toBe("chouffe"); - await nextTick(); - - expect(n).toBe(3); - }); - test("getters given to actions", async () => { expect.assertions(3); const state = { @@ -414,46 +201,10 @@ describe("basic use", () => { expect(getters.beerTasterName(1)).toBe("aaron"); } }; - const store = new Store({ state, mutations: {}, actions, getters }); + const store = new Store({ state, actions, getters }); store.dispatch("action"); }); - test("getters given to mutations", async () => { - expect.assertions(3); - const state = { - beers: { - 1: { - id: 1, - name: "bertinchamps", - tasterID: 1 - } - }, - tasters: { - 1: { - id: 1, - name: "aaron" - } - } - }; - const getters = { - beerTasterName({ state }, beerID) { - return state.tasters[state.beers[beerID].tasterID].name; - }, - bestBeerName({ state }) { - return state.beers[1].name; - } - }; - const mutations = { - mutation({ getters }) { - expect(getters).toBeDefined(); - expect(getters.bestBeerName()).toBe("bertinchamps"); - expect(getters.beerTasterName(1)).toBe("aaron"); - } - }; - const store = new Store({ state, mutations, actions: {}, getters }); - store.commit("mutation"); - }); - test("can use getters inside a getter", () => { const getters = { a({ getters }) { @@ -466,7 +217,7 @@ describe("basic use", () => { return `c${i}`; } }; - const store = new Store({ getters }); + const store = new Store({ getters, state: {}, actions: {} }); expect(store.getters.a()).toBe("bc1"); }); @@ -474,73 +225,73 @@ describe("basic use", () => { describe("advanced state properties", () => { test("state in the store is reference equal after mutation", async () => { - const mutations = { + const actions = { donothing() {} }; - const store = new Store({ state:{}, mutations }); + const store = new Store({ state: {}, actions }); const state = store.state; - store.commit("donothing"); + store.dispatch("donothing"); expect(store.state).toBe(state); }); test("can use array properties in mutations", () => { expect.assertions(3); const state = { a: [1, 2, 3] }; - const mutations = { + const actions = { m({ state }) { expect(state.a.length).toBe(3); const l = state.a.push(53); expect(l).toBe(4); } }; - const store = new Store({ state, mutations }); - store.commit("m"); + const store = new Store({ state, actions }); + store.dispatch("m"); expect(store.state.a).toEqual([1, 2, 3, 53]); }); - test("can use object assign in store", async () => { - const mutations = { - dosomething({ state }) { - Object.assign(state.westmalle, { a: 3, b: 4 }); - } - }; - const store = new Store({ - state: { westmalle: { a: 1, b: 2 } }, - mutations + test("can use object assign in store", async () => { + const actions = { + dosomething({ state }) { + Object.assign(state.westmalle, { a: 3, b: 4 }); + } + }; + const store = new Store({ + state: { westmalle: { a: 1, b: 2 } }, + actions + }); + store.dispatch("dosomething"); + expect(store.state.westmalle).toEqual({ a: 3, b: 4 }); }); - store.commit("dosomething"); - expect(store.state.westmalle).toEqual({ a: 3, b: 4 }); - }); - test("aku reactive store state 1", async () => { - const mutations = { - inc({ state }) { - state.counter++; - } - }; - const state = { counter: 0 }; - const store = new Store({ state, mutations }); - expect(store.state.counter).toBe(0); - store.commit("inc", {}); - expect(store.state.counter).toBe(1); - }); + test("aku reactive store state 1", async () => { + const actions = { + inc({ state }) { + state.counter++; + } + }; + const state = { counter: 0 }; + const store = new Store({ state, actions }); + expect(store.state.counter).toBe(0); + store.dispatch("inc", {}); + expect(store.state.counter).toBe(1); + }); }); describe("updates triggered by the store", () => { test("multiple commits trigger one update", async () => { let updateCounter = 0; const state = { n: 1 }; - const mutations = { + const actions = { inc({ state }, delta) { state.n += delta; } }; - const store = new Store({ state, mutations }); + const store = new Store({ state, actions }); store.on("update", null, () => updateCounter++); - store.commit("inc", 14); + store.dispatch("inc", 14); expect(updateCounter).toBe(0); - store.commit("inc", 50); + store.dispatch("inc", 50); expect(updateCounter).toBe(0); await nextMicroTick(); expect(updateCounter).toBe(1); @@ -549,7 +300,7 @@ describe("updates triggered by the store", () => { test("empty commits do not trigger updates", async () => { let updateCounter = 0; const state = { n: 1 }; - const mutations = { + const actions = { inc({ state }, delta) { state.n += delta; }, @@ -559,20 +310,19 @@ describe("updates triggered by the store", () => { state.n = val; } }; - const store = new Store({ state, mutations }); + const store = new Store({ state, actions }); store.on("update", null, () => updateCounter++); - store.commit("noop"); + store.dispatch("noop"); await nextMicroTick(); expect(updateCounter).toBe(0); - store.commit("inc", 50); + store.dispatch("inc", 50); await nextMicroTick(); expect(updateCounter).toBe(1); - store.commit("noop2"); + store.dispatch("noop2"); await nextMicroTick(); expect(updateCounter).toBe(1); }); }); - diff --git a/tools/playground/samples.js b/tools/playground/samples.js index ca9411a2..88d93275 100644 --- a/tools/playground/samples.js +++ b/tools/playground/samples.js @@ -274,37 +274,6 @@ const LOCALSTORAGE_KEY = "todos-odoo"; // Store Definition //------------------------------------------------------------------------------ const actions = { - addTodo({ commit }, title) { - commit("addTodo", title); - }, - removeTodo({ commit }, id) { - commit("removeTodo", id); - }, - toggleTodo({ state, commit }, id) { - const todo = state.todos.find(t => t.id === id); - commit("editTodo", { id, completed: !todo.completed }); - }, - clearCompleted({ state, commit }) { - state.todos - .filter(todo => todo.completed) - .forEach(todo => { - commit("removeTodo", todo.id); - }); - }, - toggleAll({ state, commit }, completed) { - state.todos.forEach(todo => { - commit("editTodo", { - id: todo.id, - completed - }); - }); - }, - editTodo({ commit }, { id, title }) { - commit("editTodo", { id, title }); - } -}; - -const mutations = { addTodo({ state }, title) { const id = state.nextId++; const todo = { @@ -318,6 +287,25 @@ const mutations = { const index = state.todos.findIndex(t => t.id === id); state.todos.splice(index, 1); }, + toggleTodo({ state, dispatch }, id) { + const todo = state.todos.find(t => t.id === id); + dispatch("editTodo", { id, completed: !todo.completed }); + }, + clearCompleted({ state, dispatch }) { + state.todos + .filter(todo => todo.completed) + .forEach(todo => { + dispatch("removeTodo", todo.id); + }); + }, + toggleAll({ state, commit }, completed) { + state.todos.forEach(todo => { + dispatch("editTodo", { + id: todo.id, + completed + }); + }); + }, editTodo({ state }, { id, title, completed }) { const todo = state.todos.find(t => t.id === id); if (title !== undefined) { @@ -338,11 +326,7 @@ function makeStore() { todos, nextId }; - const store = new owl.store.Store({ - state, - actions, - mutations - }); + const store = new owl.store.Store({ state, actions }); store.on("update", null, () => { const state = JSON.stringify(store.state.todos); window.localStorage.setItem(LOCALSTORAGE_KEY, state); @@ -415,7 +399,7 @@ class TodoApp extends owl.store.ConnectedComponent { }; } get visibleTodos() { - let todos = this.props.todos; + let todos = this.storeProps.todos; if (this.state.filter === "active") { todos = todos.filter(t => !t.completed); } @@ -426,11 +410,11 @@ class TodoApp extends owl.store.ConnectedComponent { } get allChecked() { - return this.props.todos.every(todo => todo.completed); + return this.storeProps.todos.every(todo => todo.completed); } get remaining() { - return this.props.todos.filter(todo => !todo.completed).length; + return this.storeProps.todos.filter(todo => !todo.completed).length; } get remainingText() { @@ -481,7 +465,7 @@ const TODO_APP_STORE_XML = `

todos

-
+
    @@ -490,7 +474,7 @@ const TODO_APP_STORE_XML = `
-