mirror of
https://github.com/odoo/owl.git
synced 2025-10-06 19:59:41 +07:00
+11
-66
@@ -11,7 +11,7 @@ import { Observer } from "../core/observer";
|
|||||||
*
|
*
|
||||||
* The Owl store is our answer to the problem of managing complex state across
|
* 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
|
* 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.
|
* connected component will be notified, and updated if necessary.
|
||||||
*
|
*
|
||||||
* Note that this code is partly inspired by VueX and React/Redux
|
* Note that this code is partly inspired by VueX and React/Redux
|
||||||
@@ -21,16 +21,14 @@ import { Observer } from "../core/observer";
|
|||||||
// Store Definition
|
// Store Definition
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
type Mutation = ({ state, commit, getters }, ...payload: any) => void;
|
export type Action = ({ state, dispatch, env, getters }, ...payload: any) => any;
|
||||||
type Action = ({ commit, state, dispatch, env, getters }, ...payload: any) => void;
|
export type Getter = ({ state: any, getters }, payload?) => any;
|
||||||
type Getter = ({ state, getters }, payload) => any;
|
|
||||||
|
|
||||||
interface StoreConfig {
|
interface StoreConfig {
|
||||||
env?: Env;
|
env?: Env;
|
||||||
state?: any;
|
state?: any;
|
||||||
actions?: { [name: string]: Action };
|
actions?: { [name: string]: Action };
|
||||||
getters?: { [name: string]: Getter };
|
getters?: { [name: string]: Getter };
|
||||||
mutations?: { [name: string]: Mutation };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface StoreOption {
|
interface StoreOption {
|
||||||
@@ -41,46 +39,28 @@ export class Store extends EventBus {
|
|||||||
state: any;
|
state: any;
|
||||||
actions: any;
|
actions: any;
|
||||||
mutations: any;
|
mutations: any;
|
||||||
_commitLevel: number = 0;
|
|
||||||
history: any[] = [];
|
|
||||||
debug: boolean;
|
debug: boolean;
|
||||||
env: any;
|
env: any;
|
||||||
observer: Observer;
|
observer: Observer;
|
||||||
getters: { [name: string]: (payload?) => any };
|
getters: { [name: string]: (payload?) => any };
|
||||||
_gettersCache: { [name: string]: {} };
|
|
||||||
_updateId: number = 1;
|
|
||||||
|
|
||||||
constructor(config: StoreConfig, options: StoreOption = {}) {
|
constructor(config: StoreConfig, options: StoreOption = {}) {
|
||||||
super();
|
super();
|
||||||
this.debug = options.debug || false;
|
this.debug = options.debug || false;
|
||||||
this.actions = config.actions;
|
this.actions = config.actions;
|
||||||
this.mutations = config.mutations;
|
|
||||||
this.env = config.env;
|
this.env = config.env;
|
||||||
this.observer = new Observer();
|
this.observer = new Observer();
|
||||||
this.observer.notifyCB = this.__notifyComponents.bind(this);
|
this.observer.notifyCB = this.__notifyComponents.bind(this);
|
||||||
this.observer.allowMutations = false;
|
|
||||||
this.state = this.observer.observe(config.state || {});
|
this.state = this.observer.observe(config.state || {});
|
||||||
this.getters = {};
|
this.getters = {};
|
||||||
this._gettersCache = {};
|
if (config.getters) {
|
||||||
|
const firstArg = {
|
||||||
if (this.debug) {
|
state: this.state,
|
||||||
this.history.push({ state: this.state });
|
getters: this.getters,
|
||||||
}
|
|
||||||
|
|
||||||
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];
|
|
||||||
}
|
|
||||||
return func({ state: this.state, getters: this.getters }, payload);
|
|
||||||
};
|
};
|
||||||
|
for (let g in config.getters) {
|
||||||
|
this.getters[g] = config.getters[g].bind(this, firstArg);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -90,7 +70,6 @@ export class Store extends EventBus {
|
|||||||
}
|
}
|
||||||
const result = this.actions[action](
|
const result = this.actions[action](
|
||||||
{
|
{
|
||||||
commit: this.commit.bind(this),
|
|
||||||
dispatch: this.dispatch.bind(this),
|
dispatch: this.dispatch.bind(this),
|
||||||
env: this.env,
|
env: this.env,
|
||||||
state: this.state,
|
state: this.state,
|
||||||
@@ -101,37 +80,6 @@ export class Store extends EventBus {
|
|||||||
return result;
|
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
|
* 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
|
* 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.
|
* updating all widgets concurrently, except for parents/children.
|
||||||
*/
|
*/
|
||||||
async __notifyComponents() {
|
async __notifyComponents() {
|
||||||
this._updateId++;
|
|
||||||
const current = this._updateId;
|
|
||||||
this._gettersCache = {};
|
|
||||||
const subs = this.subscriptions.update || [];
|
const subs = this.subscriptions.update || [];
|
||||||
for (let i = 0, iLen = subs.length; i < iLen; i++) {
|
for (let i = 0, iLen = subs.length; i < iLen; i++) {
|
||||||
const sub = subs[i];
|
const sub = subs[i];
|
||||||
const shouldCallback = sub.owner ? sub.owner.__owl__.isMounted : true;
|
const shouldCallback = sub.owner ? sub.owner.__owl__.isMounted : true;
|
||||||
if (shouldCallback) {
|
if (shouldCallback) {
|
||||||
await sub.callback.call(sub.owner, current);
|
await sub.callback.call(sub.owner);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,19 +35,19 @@ describe("connecting a component to store", () => {
|
|||||||
}
|
}
|
||||||
class Todo extends Component<any, any, any> {}
|
class Todo extends Component<any, any, any> {}
|
||||||
const state = { todos: [] };
|
const state = { todos: [] };
|
||||||
const mutations = {
|
const actions = {
|
||||||
addTodo({ state }, msg) {
|
addTodo({ state }, msg) {
|
||||||
state.todos.push({ msg });
|
state.todos.push({ msg });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
const store = new Store({ state, mutations });
|
const store = new Store({ state, actions });
|
||||||
(<any>env).store = store;
|
(<any>env).store = store;
|
||||||
const app = new App(env);
|
const app = new App(env);
|
||||||
|
|
||||||
await app.mount(fixture);
|
await app.mount(fixture);
|
||||||
expect(fixture.innerHTML).toMatchSnapshot();
|
expect(fixture.innerHTML).toMatchSnapshot();
|
||||||
|
|
||||||
store.commit("addTodo", "hello");
|
store.dispatch("addTodo", "hello");
|
||||||
await nextTick();
|
await nextTick();
|
||||||
expect(fixture.innerHTML).toMatchSnapshot();
|
expect(fixture.innerHTML).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
@@ -63,12 +63,12 @@ describe("connecting a component to store", () => {
|
|||||||
</templates>
|
</templates>
|
||||||
`);
|
`);
|
||||||
const state = { todos: [{ title: "Kasteel" }] };
|
const state = { todos: [{ title: "Kasteel" }] };
|
||||||
const mutations = {
|
const actions = {
|
||||||
edit({ state }, title) {
|
edit({ state }, title) {
|
||||||
state.todos[0].title = title;
|
state.todos[0].title = title;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
const store = new Store({ state, mutations });
|
const store = new Store({ state, actions });
|
||||||
|
|
||||||
class App extends ConnectedComponent<any, any, any> {
|
class App extends ConnectedComponent<any, any, any> {
|
||||||
static mapStoreToProps(s) {
|
static mapStoreToProps(s) {
|
||||||
@@ -94,7 +94,7 @@ describe("connecting a component to store", () => {
|
|||||||
expect(fixture.innerHTML).toMatchSnapshot();
|
expect(fixture.innerHTML).toMatchSnapshot();
|
||||||
expect(shallowFix.innerHTML).toMatchSnapshot();
|
expect(shallowFix.innerHTML).toMatchSnapshot();
|
||||||
|
|
||||||
store.commit("edit", "Bertinchamps");
|
store.dispatch("edit", "Bertinchamps");
|
||||||
await nextTick();
|
await nextTick();
|
||||||
expect(fixture.innerHTML).toMatchSnapshot();
|
expect(fixture.innerHTML).toMatchSnapshot();
|
||||||
expect(shallowFix.innerHTML).toMatchSnapshot();
|
expect(shallowFix.innerHTML).toMatchSnapshot();
|
||||||
@@ -113,10 +113,9 @@ describe("connecting a component to store", () => {
|
|||||||
`);
|
`);
|
||||||
class Todo extends Component<any, any, any> {}
|
class Todo extends Component<any, any, any> {}
|
||||||
|
|
||||||
(<any>env).store = new Store({});
|
|
||||||
const store = new Store({
|
const store = new Store({
|
||||||
state: { todos: [] },
|
state: { todos: [] },
|
||||||
mutations: {
|
actions: {
|
||||||
addTodo({ state }, msg) {
|
addTodo({ state }, msg) {
|
||||||
state.todos.push({ msg });
|
state.todos.push({ msg });
|
||||||
}
|
}
|
||||||
@@ -136,7 +135,7 @@ describe("connecting a component to store", () => {
|
|||||||
await app.mount(fixture);
|
await app.mount(fixture);
|
||||||
expect(fixture.innerHTML).toMatchSnapshot();
|
expect(fixture.innerHTML).toMatchSnapshot();
|
||||||
|
|
||||||
(<any>app.__owl__).store.commit("addTodo", "hello");
|
(<any>app.__owl__).store.dispatch("addTodo", "hello");
|
||||||
await nextTick();
|
await nextTick();
|
||||||
expect(fixture.innerHTML).toMatchSnapshot();
|
expect(fixture.innerHTML).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
@@ -187,12 +186,12 @@ describe("connecting a component to store", () => {
|
|||||||
test("mapStoreToProps receives ownprops as second argument", async () => {
|
test("mapStoreToProps receives ownprops as second argument", async () => {
|
||||||
const state = { todos: [{ id: 1, text: "jupiler" }] };
|
const state = { todos: [{ id: 1, text: "jupiler" }] };
|
||||||
let nextId = 2;
|
let nextId = 2;
|
||||||
const mutations = {
|
const actions = {
|
||||||
addTodo({ state }, text) {
|
addTodo({ state }, text) {
|
||||||
state.todos.push({ text, id: nextId++ });
|
state.todos.push({ text, id: nextId++ });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
const store = new Store({ state, mutations });
|
const store = new Store({ state, actions });
|
||||||
|
|
||||||
env.qweb.addTemplates(`
|
env.qweb.addTemplates(`
|
||||||
<templates>
|
<templates>
|
||||||
@@ -224,7 +223,7 @@ describe("connecting a component to store", () => {
|
|||||||
await app.mount(fixture);
|
await app.mount(fixture);
|
||||||
expect(fixture.innerHTML).toBe("<div><span>jupiler</span></div>");
|
expect(fixture.innerHTML).toBe("<div><span>jupiler</span></div>");
|
||||||
|
|
||||||
store.commit("addTodo", "hoegaarden");
|
store.dispatch("addTodo", "hoegaarden");
|
||||||
await nextTick();
|
await nextTick();
|
||||||
expect(fixture.innerHTML).toBe("<div><span>jupiler</span><span>hoegaarden</span></div>");
|
expect(fixture.innerHTML).toBe("<div><span>jupiler</span><span>hoegaarden</span></div>");
|
||||||
});
|
});
|
||||||
@@ -333,14 +332,14 @@ describe("connecting a component to store", () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const mutations = {
|
const actions = {
|
||||||
addBeer({ state }, name) {
|
addBeer({ state }, name) {
|
||||||
state.beers.push({ name });
|
state.beers.push({ name });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const state = { beers: [{ name: "jupiler" }] };
|
const state = { beers: [{ name: "jupiler" }] };
|
||||||
const store = new Store({ state, mutations });
|
const store = new Store({ state, actions });
|
||||||
(<any>env).store = store;
|
(<any>env).store = store;
|
||||||
|
|
||||||
const app = new App(env);
|
const app = new App(env);
|
||||||
@@ -348,7 +347,7 @@ describe("connecting a component to store", () => {
|
|||||||
await app.mount(fixture);
|
await app.mount(fixture);
|
||||||
expect(fixture.innerHTML).toBe("<div><span>jupiler</span></div>");
|
expect(fixture.innerHTML).toBe("<div><span>jupiler</span></div>");
|
||||||
|
|
||||||
store.commit("addBeer", "kwak");
|
store.dispatch("addBeer", "kwak");
|
||||||
await nextTick();
|
await nextTick();
|
||||||
expect(fixture.innerHTML).toBe("<div><span>jupiler</span><span>kwak</span></div>");
|
expect(fixture.innerHTML).toBe("<div><span>jupiler</span><span>kwak</span></div>");
|
||||||
});
|
});
|
||||||
@@ -382,7 +381,7 @@ describe("connecting a component to store", () => {
|
|||||||
state = { beerId: 0 };
|
state = { beerId: 0 };
|
||||||
}
|
}
|
||||||
|
|
||||||
const mutations = {
|
const actions = {
|
||||||
consume({ state }, beerId) {
|
consume({ state }, beerId) {
|
||||||
state.consumedID = beerId;
|
state.consumedID = beerId;
|
||||||
}
|
}
|
||||||
@@ -394,7 +393,7 @@ describe("connecting a component to store", () => {
|
|||||||
consumedID: null,
|
consumedID: null,
|
||||||
taster: "aaron"
|
taster: "aaron"
|
||||||
};
|
};
|
||||||
const store = new Store({ state, mutations });
|
const store = new Store({ state, actions });
|
||||||
(<any>env).store = store;
|
(<any>env).store = store;
|
||||||
const app = new App(env);
|
const app = new App(env);
|
||||||
|
|
||||||
@@ -407,7 +406,7 @@ describe("connecting a component to store", () => {
|
|||||||
"<div><div><span>taster:aaron</span><span>selected:jupiler</span></div></div>"
|
"<div><div><span>taster:aaron</span><span>selected:jupiler</span></div></div>"
|
||||||
);
|
);
|
||||||
|
|
||||||
store.commit("consume", 1);
|
store.dispatch("consume", 1);
|
||||||
await nextTick();
|
await nextTick();
|
||||||
expect(fixture.innerHTML).toBe(
|
expect(fixture.innerHTML).toBe(
|
||||||
"<div><div><span>taster:aaron</span><span>selected:jupiler</span><span>consumed:jupiler</span></div></div>"
|
"<div><div><span>taster:aaron</span><span>selected:jupiler</span><span>consumed:jupiler</span></div></div>"
|
||||||
@@ -449,7 +448,7 @@ describe("connecting a component to store", () => {
|
|||||||
state = { beerId: 0 };
|
state = { beerId: 0 };
|
||||||
}
|
}
|
||||||
|
|
||||||
const mutations = {
|
const actions = {
|
||||||
changeTaster({ state }, newTaster) {
|
changeTaster({ state }, newTaster) {
|
||||||
state.taster = newTaster;
|
state.taster = newTaster;
|
||||||
},
|
},
|
||||||
@@ -467,7 +466,7 @@ describe("connecting a component to store", () => {
|
|||||||
consumedID: null,
|
consumedID: null,
|
||||||
taster: "aaron"
|
taster: "aaron"
|
||||||
};
|
};
|
||||||
const store = new Store({ state, mutations });
|
const store = new Store({ state, actions });
|
||||||
(<any>env).store = store;
|
(<any>env).store = store;
|
||||||
const app = new App(env);
|
const app = new App(env);
|
||||||
|
|
||||||
@@ -480,13 +479,13 @@ describe("connecting a component to store", () => {
|
|||||||
"<div><div><span>taster:aaron</span><span>selected:jupiler</span></div></div>"
|
"<div><div><span>taster:aaron</span><span>selected:jupiler</span></div></div>"
|
||||||
);
|
);
|
||||||
|
|
||||||
store.commit("renameBeer", { beerId: 1, name: "kwak" });
|
store.dispatch("renameBeer", { beerId: 1, name: "kwak" });
|
||||||
await nextTick();
|
await nextTick();
|
||||||
expect(fixture.innerHTML).toBe(
|
expect(fixture.innerHTML).toBe(
|
||||||
"<div><div><span>taster:aaron</span><span>selected:kwak</span></div></div>"
|
"<div><div><span>taster:aaron</span><span>selected:kwak</span></div></div>"
|
||||||
);
|
);
|
||||||
|
|
||||||
store.commit("consume", 1);
|
store.dispatch("consume", 1);
|
||||||
await nextTick();
|
await nextTick();
|
||||||
expect(fixture.innerHTML).toBe(
|
expect(fixture.innerHTML).toBe(
|
||||||
"<div><div><span>taster:aaron</span><span>selected:kwak</span><span>consumed:kwak</span></div></div>"
|
"<div><div><span>taster:aaron</span><span>selected:kwak</span><span>consumed:kwak</span></div></div>"
|
||||||
@@ -498,13 +497,13 @@ describe("connecting a component to store", () => {
|
|||||||
"<div><div><span>taster:aaron</span><span>consumed:kwak</span></div></div>"
|
"<div><div><span>taster:aaron</span><span>consumed:kwak</span></div></div>"
|
||||||
);
|
);
|
||||||
|
|
||||||
store.commit("renameBeer", { beerId: 1, name: "jupiler" });
|
store.dispatch("renameBeer", { beerId: 1, name: "jupiler" });
|
||||||
await nextTick();
|
await nextTick();
|
||||||
expect(fixture.innerHTML).toBe(
|
expect(fixture.innerHTML).toBe(
|
||||||
"<div><div><span>taster:aaron</span><span>consumed:jupiler</span></div></div>"
|
"<div><div><span>taster:aaron</span><span>consumed:jupiler</span></div></div>"
|
||||||
);
|
);
|
||||||
|
|
||||||
store.commit("changeTaster", "matthieu");
|
store.dispatch("changeTaster", "matthieu");
|
||||||
await nextTick();
|
await nextTick();
|
||||||
expect(fixture.innerHTML).toBe(
|
expect(fixture.innerHTML).toBe(
|
||||||
"<div><div><span>taster:matthieu</span><span>consumed:jupiler</span></div></div>"
|
"<div><div><span>taster:matthieu</span><span>consumed:jupiler</span></div></div>"
|
||||||
@@ -539,13 +538,13 @@ describe("connecting a component to store", () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const state = { current: "a", msg: { a: "a", b: "b" } };
|
const state = { current: "a", msg: { a: "a", b: "b" } };
|
||||||
const mutations = {
|
const actions = {
|
||||||
setCurrent({ state }, c) {
|
setCurrent({ state }, c) {
|
||||||
state.current = c;
|
state.current = c;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const store = new Store({ state, mutations });
|
const store = new Store({ state, actions });
|
||||||
(<any>env).store = store;
|
(<any>env).store = store;
|
||||||
const app = new Parent(env);
|
const app = new Parent(env);
|
||||||
|
|
||||||
@@ -553,14 +552,14 @@ describe("connecting a component to store", () => {
|
|||||||
expect(fixture.innerHTML).toBe("<div><span>a</span></div>");
|
expect(fixture.innerHTML).toBe("<div><span>a</span></div>");
|
||||||
expect(steps).toEqual(["parent", "child"]);
|
expect(steps).toEqual(["parent", "child"]);
|
||||||
|
|
||||||
store.commit("setCurrent", "b");
|
store.dispatch("setCurrent", "b");
|
||||||
await nextTick();
|
await nextTick();
|
||||||
expect(fixture.innerHTML).toBe("<div><span>b</span></div>");
|
expect(fixture.innerHTML).toBe("<div><span>b</span></div>");
|
||||||
expect(steps).toEqual(["parent", "child", "parent", "child"]);
|
expect(steps).toEqual(["parent", "child", "parent", "child"]);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("connected parent/children: no double rendering", async () => {
|
test("connected parent/children: no double rendering", async () => {
|
||||||
const mutations = {
|
const actions = {
|
||||||
editTodo({ state }) {
|
editTodo({ state }) {
|
||||||
state.todos[1].title = "abc";
|
state.todos[1].title = "abc";
|
||||||
}
|
}
|
||||||
@@ -571,7 +570,7 @@ describe("connecting a component to store", () => {
|
|||||||
};
|
};
|
||||||
const store = new Store({
|
const store = new Store({
|
||||||
state,
|
state,
|
||||||
mutations
|
actions
|
||||||
});
|
});
|
||||||
|
|
||||||
env.qweb.addTemplates(`
|
env.qweb.addTemplates(`
|
||||||
@@ -611,7 +610,7 @@ describe("connecting a component to store", () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
editTodo() {
|
editTodo() {
|
||||||
this.env.store.commit("editTodo");
|
this.env.store.dispatch("editTodo");
|
||||||
}
|
}
|
||||||
__render(...args) {
|
__render(...args) {
|
||||||
renderCount++;
|
renderCount++;
|
||||||
@@ -639,7 +638,7 @@ describe("connecting a component to store", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test("connected parent/children: no rendering if child is destroyed", async () => {
|
test("connected parent/children: no rendering if child is destroyed", async () => {
|
||||||
const mutations = {
|
const actions = {
|
||||||
removeTodo({ state }) {
|
removeTodo({ state }) {
|
||||||
delete state.todos[1];
|
delete state.todos[1];
|
||||||
}
|
}
|
||||||
@@ -650,7 +649,7 @@ describe("connecting a component to store", () => {
|
|||||||
};
|
};
|
||||||
const store = new Store({
|
const store = new Store({
|
||||||
state,
|
state,
|
||||||
mutations
|
actions
|
||||||
});
|
});
|
||||||
|
|
||||||
env.qweb.addTemplates(`
|
env.qweb.addTemplates(`
|
||||||
@@ -690,7 +689,7 @@ describe("connecting a component to store", () => {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
removeTodo() {
|
removeTodo() {
|
||||||
this.env.store.commit("removeTodo");
|
this.env.store.dispatch("removeTodo");
|
||||||
}
|
}
|
||||||
__render(...args) {
|
__render(...args) {
|
||||||
renderCount++;
|
renderCount++;
|
||||||
@@ -737,20 +736,20 @@ describe("connecting a component to store", () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const state = { msg: "a" };
|
const state = { msg: "a" };
|
||||||
const mutations = {
|
const actions = {
|
||||||
setMsg({ state }, c) {
|
setMsg({ state }, c) {
|
||||||
state.msg = c;
|
state.msg = c;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const store = new Store({ state, mutations });
|
const store = new Store({ state, actions });
|
||||||
(<any>env).store = store;
|
(<any>env).store = store;
|
||||||
const app = new App(env);
|
const app = new App(env);
|
||||||
|
|
||||||
await app.mount(fixture);
|
await app.mount(fixture);
|
||||||
expect(fixture.innerHTML).toBe("<div>a</div>");
|
expect(fixture.innerHTML).toBe("<div>a</div>");
|
||||||
|
|
||||||
store.commit("setMsg", "b");
|
store.dispatch("setMsg", "b");
|
||||||
await nextTick();
|
await nextTick();
|
||||||
expect(fixture.innerHTML).toBe("<div>b</div>");
|
expect(fixture.innerHTML).toBe("<div>b</div>");
|
||||||
expect(steps).toEqual(["willpatch", "patched"]);
|
expect(steps).toEqual(["willpatch", "patched"]);
|
||||||
@@ -889,13 +888,13 @@ describe("connected components and default values", () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const mutations = {
|
const actions = {
|
||||||
changeMessageContent({ state }, messageId, newContent) {
|
changeMessageContent({ state }, messageId, newContent) {
|
||||||
state.messages[messageId].content = newContent;
|
state.messages[messageId].content = newContent;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const store = new Store({ state, mutations });
|
const store = new Store({ state, actions });
|
||||||
(<any>env).store = store;
|
(<any>env).store = store;
|
||||||
const app = new App(env);
|
const app = new App(env);
|
||||||
|
|
||||||
@@ -908,7 +907,7 @@ describe("connected components and default values", () => {
|
|||||||
await app.render();
|
await app.render();
|
||||||
expect(fixture.innerHTML).toBe("<div><div><div>200Message200</div></div></div>");
|
expect(fixture.innerHTML).toBe("<div><div><div>200Message200</div></div></div>");
|
||||||
|
|
||||||
store.commit("changeMessageContent", 200, "UpdatedMessage200");
|
store.dispatch("changeMessageContent", 200, "UpdatedMessage200");
|
||||||
await nextTick();
|
await nextTick();
|
||||||
expect(fixture.innerHTML).toBe("<div><div><div>200UpdatedMessage200</div></div></div>");
|
expect(fixture.innerHTML).toBe("<div><div><div>200UpdatedMessage200</div></div></div>");
|
||||||
});
|
});
|
||||||
|
|||||||
+56
-306
@@ -1,57 +1,33 @@
|
|||||||
import { Env } from "../../src/component/component";
|
import { Env } from "../../src/component/component";
|
||||||
import { Store } from "../../src/store/store";
|
import { Store, Getter } from "../../src/store/store";
|
||||||
import { nextMicroTick, nextTick } from "../helpers";
|
import { nextTick, nextMicroTick } from "../helpers";
|
||||||
|
|
||||||
|
|
||||||
describe("basic use", () => {
|
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", () => {
|
test("dispatch an action", () => {
|
||||||
const state = { n: 1 };
|
const state = { n: 1 };
|
||||||
const mutations = {
|
const actions = {
|
||||||
inc({ state }, delta) {
|
inc({ state }, delta) {
|
||||||
state.n += delta;
|
state.n += delta;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
const actions = {
|
const store = new Store({ state, actions });
|
||||||
inc({ commit }, delta) {
|
|
||||||
commit("inc", delta);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
const store = new Store({ state, mutations, actions });
|
|
||||||
|
|
||||||
expect(store.state.n).toBe(1);
|
expect(store.state.n).toBe(1);
|
||||||
store.dispatch("inc", 14);
|
store.dispatch("inc", 14);
|
||||||
expect(store.state.n).toBe(15);
|
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 state = { n1: 1, n2: 1, n3: 1 };
|
||||||
const mutations = {
|
|
||||||
|
const actions = {
|
||||||
batchInc({ state }, delta1, delta2, delta3) {
|
batchInc({ state }, delta1, delta2, delta3) {
|
||||||
state.n1 += delta1;
|
state.n1 += delta1;
|
||||||
state.n2 += delta2;
|
state.n2 += delta2;
|
||||||
state.n3 += delta3;
|
state.n3 += delta3;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
const actions = {
|
const store = new Store({ state, actions });
|
||||||
batchInc({ commit }, delta1, delta2, delta3) {
|
|
||||||
commit("batchInc", delta1, delta2, delta3);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
const store = new Store({ state, mutations, actions });
|
|
||||||
|
|
||||||
expect(store.state.n1).toBe(1);
|
expect(store.state.n1).toBe(1);
|
||||||
expect(store.state.n2).toBe(1);
|
expect(store.state.n2).toBe(1);
|
||||||
@@ -62,96 +38,58 @@ describe("basic use", () => {
|
|||||||
expect(store.state.n3).toBe(89);
|
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", () => {
|
test("can dispatch an action in an action", () => {
|
||||||
const state = { n: 1 };
|
const state = { n: 1 };
|
||||||
const mutations = {
|
|
||||||
|
const actions = {
|
||||||
inc({ state }, delta) {
|
inc({ state }, delta) {
|
||||||
state.n += delta;
|
state.n += delta;
|
||||||
}
|
|
||||||
};
|
|
||||||
const actions = {
|
|
||||||
inc({ commit }, delta) {
|
|
||||||
commit("inc", delta);
|
|
||||||
},
|
},
|
||||||
inc100({ dispatch }) {
|
inc100({ dispatch }) {
|
||||||
dispatch("inc", 100);
|
dispatch("inc", 100);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
const store = new Store({ state, mutations, actions });
|
const store = new Store({ state, actions });
|
||||||
|
|
||||||
expect(store.state.n).toBe(1);
|
expect(store.state.n).toBe(1);
|
||||||
store.dispatch("inc100");
|
store.dispatch("inc100");
|
||||||
expect(store.state.n).toBe(101);
|
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 state = { n: 1 };
|
||||||
const mutations = {
|
const actions = {
|
||||||
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 = {
|
|
||||||
inc({ state }) {
|
inc({ state }) {
|
||||||
return ++state.n;
|
return ++state.n;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
const store = new Store({ state, mutations });
|
const store = new Store({ state, actions });
|
||||||
|
|
||||||
expect(store.state.n).toBe(1);
|
expect(store.state.n).toBe(1);
|
||||||
const res = store.commit("inc");
|
const res = store.dispatch("inc");
|
||||||
expect(store.state.n).toBe(2);
|
expect(store.state.n).toBe(2);
|
||||||
expect(res).toBe(2);
|
expect(res).toBe(2);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("dispatch allow synchronizing between actions", async () => {
|
test("dispatch allow synchronizing between actions", async () => {
|
||||||
const state = { n: 1 };
|
const state = { n: 1 };
|
||||||
const mutations = {
|
const actions = {
|
||||||
inc({ state }, delta) {
|
inc({ state }, delta) {
|
||||||
state.n += delta;
|
state.n += delta;
|
||||||
},
|
},
|
||||||
setN({ state }, n) {
|
setN({ state }, n) {
|
||||||
state.n = 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();
|
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);
|
expect(store.state.n).toBe(1);
|
||||||
store.dispatch("dosomething");
|
store.dispatch("dosomething");
|
||||||
@@ -162,26 +100,24 @@ describe("basic use", () => {
|
|||||||
|
|
||||||
test("return data from dispatching an action", async () => {
|
test("return data from dispatching an action", async () => {
|
||||||
const state = { n: 1 };
|
const state = { n: 1 };
|
||||||
const mutations = {
|
const actions = {
|
||||||
inc({ state }, delta) {
|
inc({ state }, delta) {
|
||||||
state.n += delta;
|
state.n += delta;
|
||||||
},
|
},
|
||||||
setN({ state }, n) {
|
setN({ state }, n) {
|
||||||
state.n = 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();
|
await Promise.resolve();
|
||||||
commit("setN", 10);
|
dispatch("setN", 10);
|
||||||
return 5;
|
return 5;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
const store = new Store({ state, mutations, actions });
|
const store = new Store({ state, actions });
|
||||||
|
|
||||||
expect(store.state.n).toBe(1);
|
expect(store.state.n).toBe(1);
|
||||||
store.dispatch("dosomething");
|
store.dispatch("dosomething");
|
||||||
@@ -219,7 +155,7 @@ describe("basic use", () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
const getters = {
|
const getters: { [key: string]: Getter } = {
|
||||||
beerTasterName({ state }, beerID) {
|
beerTasterName({ state }, beerID) {
|
||||||
return state.tasters[state.beers[beerID].tasterID].name;
|
return state.tasters[state.beers[beerID].tasterID].name;
|
||||||
},
|
},
|
||||||
@@ -227,161 +163,12 @@ describe("basic use", () => {
|
|||||||
return state.beers[1].name;
|
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).toBeDefined();
|
||||||
expect((<any>store.getters).bestBeerName()).toBe("bertinchamps");
|
expect((<any>store.getters).bestBeerName()).toBe("bertinchamps");
|
||||||
expect((<any>store.getters).beerTasterName(1)).toBe("aaron");
|
expect((<any>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((<any>store.getters).bestBeerName()).toBe("bertinchamps");
|
|
||||||
expect((<any>store.getters).beerTasterName(1)).toBe("aaron");
|
|
||||||
expect(n).toBe(2);
|
|
||||||
expect((<any>store.getters).bestBeerName()).toBe("bertinchamps");
|
|
||||||
expect((<any>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((<any>store.getters).getBeerNames([1])).toEqual(["bertinchamps"]);
|
|
||||||
expect(n).toBe(1);
|
|
||||||
expect((<any>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((<any>store.getters).beerTasterName(1)).toBe("aaron");
|
|
||||||
expect(n).toBe(1);
|
|
||||||
expect((<any>store.getters).beerTasterName(1)).toBe("aaron");
|
|
||||||
expect(n).toBe(1);
|
|
||||||
|
|
||||||
store.commit("changeTaster", { beerID: 1, tasterID: 2 });
|
|
||||||
await nextTick();
|
|
||||||
|
|
||||||
expect((<any>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((<any>store.getters).beerName(1)).toBe("chouffe");
|
|
||||||
await nextTick();
|
|
||||||
|
|
||||||
expect(n).toBe(3);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("getters given to actions", async () => {
|
test("getters given to actions", async () => {
|
||||||
expect.assertions(3);
|
expect.assertions(3);
|
||||||
const state = {
|
const state = {
|
||||||
@@ -414,46 +201,10 @@ describe("basic use", () => {
|
|||||||
expect(getters.beerTasterName(1)).toBe("aaron");
|
expect(getters.beerTasterName(1)).toBe("aaron");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
const store = new Store({ state, mutations: {}, actions, getters });
|
const store = new Store({ state, actions, getters });
|
||||||
store.dispatch("action");
|
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", () => {
|
test("can use getters inside a getter", () => {
|
||||||
const getters = {
|
const getters = {
|
||||||
a({ getters }) {
|
a({ getters }) {
|
||||||
@@ -466,7 +217,7 @@ describe("basic use", () => {
|
|||||||
return `c${i}`;
|
return `c${i}`;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
const store = new Store({ getters });
|
const store = new Store({ getters, state: {}, actions: {} });
|
||||||
|
|
||||||
expect(store.getters.a()).toBe("bc1");
|
expect(store.getters.a()).toBe("bc1");
|
||||||
});
|
});
|
||||||
@@ -474,54 +225,54 @@ describe("basic use", () => {
|
|||||||
|
|
||||||
describe("advanced state properties", () => {
|
describe("advanced state properties", () => {
|
||||||
test("state in the store is reference equal after mutation", async () => {
|
test("state in the store is reference equal after mutation", async () => {
|
||||||
const mutations = {
|
const actions = {
|
||||||
donothing() {}
|
donothing() {}
|
||||||
};
|
};
|
||||||
const store = new Store({ state:{}, mutations });
|
const store = new Store({ state: {}, actions });
|
||||||
const state = store.state;
|
const state = store.state;
|
||||||
store.commit("donothing");
|
store.dispatch("donothing");
|
||||||
expect(store.state).toBe(state);
|
expect(store.state).toBe(state);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("can use array properties in mutations", () => {
|
test("can use array properties in mutations", () => {
|
||||||
expect.assertions(3);
|
expect.assertions(3);
|
||||||
const state = { a: [1, 2, 3] };
|
const state = { a: [1, 2, 3] };
|
||||||
const mutations = {
|
const actions = {
|
||||||
m({ state }) {
|
m({ state }) {
|
||||||
expect(state.a.length).toBe(3);
|
expect(state.a.length).toBe(3);
|
||||||
const l = state.a.push(53);
|
const l = state.a.push(53);
|
||||||
expect(l).toBe(4);
|
expect(l).toBe(4);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
const store = new Store({ state, mutations });
|
const store = new Store({ state, actions });
|
||||||
store.commit("m");
|
store.dispatch("m");
|
||||||
expect(store.state.a).toEqual([1, 2, 3, 53]);
|
expect(store.state.a).toEqual([1, 2, 3, 53]);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("can use object assign in store", async () => {
|
test("can use object assign in store", async () => {
|
||||||
const mutations = {
|
const actions = {
|
||||||
dosomething({ state }) {
|
dosomething({ state }) {
|
||||||
Object.assign(state.westmalle, { a: 3, b: 4 });
|
Object.assign(state.westmalle, { a: 3, b: 4 });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
const store = new Store({
|
const store = new Store({
|
||||||
state: { westmalle: { a: 1, b: 2 } },
|
state: { westmalle: { a: 1, b: 2 } },
|
||||||
mutations
|
actions
|
||||||
});
|
});
|
||||||
store.commit("dosomething");
|
store.dispatch("dosomething");
|
||||||
expect(store.state.westmalle).toEqual({ a: 3, b: 4 });
|
expect(store.state.westmalle).toEqual({ a: 3, b: 4 });
|
||||||
});
|
});
|
||||||
|
|
||||||
test("aku reactive store state 1", async () => {
|
test("aku reactive store state 1", async () => {
|
||||||
const mutations = {
|
const actions = {
|
||||||
inc({ state }) {
|
inc({ state }) {
|
||||||
state.counter++;
|
state.counter++;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
const state = { counter: 0 };
|
const state = { counter: 0 };
|
||||||
const store = new Store({ state, mutations });
|
const store = new Store({ state, actions });
|
||||||
expect(store.state.counter).toBe(0);
|
expect(store.state.counter).toBe(0);
|
||||||
store.commit("inc", {});
|
store.dispatch("inc", {});
|
||||||
expect(store.state.counter).toBe(1);
|
expect(store.state.counter).toBe(1);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -530,17 +281,17 @@ describe("updates triggered by the store", () => {
|
|||||||
test("multiple commits trigger one update", async () => {
|
test("multiple commits trigger one update", async () => {
|
||||||
let updateCounter = 0;
|
let updateCounter = 0;
|
||||||
const state = { n: 1 };
|
const state = { n: 1 };
|
||||||
const mutations = {
|
const actions = {
|
||||||
inc({ state }, delta) {
|
inc({ state }, delta) {
|
||||||
state.n += delta;
|
state.n += delta;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
const store = new Store({ state, mutations });
|
const store = new Store({ state, actions });
|
||||||
store.on("update", null, () => updateCounter++);
|
store.on("update", null, () => updateCounter++);
|
||||||
|
|
||||||
store.commit("inc", 14);
|
store.dispatch("inc", 14);
|
||||||
expect(updateCounter).toBe(0);
|
expect(updateCounter).toBe(0);
|
||||||
store.commit("inc", 50);
|
store.dispatch("inc", 50);
|
||||||
expect(updateCounter).toBe(0);
|
expect(updateCounter).toBe(0);
|
||||||
await nextMicroTick();
|
await nextMicroTick();
|
||||||
expect(updateCounter).toBe(1);
|
expect(updateCounter).toBe(1);
|
||||||
@@ -549,7 +300,7 @@ describe("updates triggered by the store", () => {
|
|||||||
test("empty commits do not trigger updates", async () => {
|
test("empty commits do not trigger updates", async () => {
|
||||||
let updateCounter = 0;
|
let updateCounter = 0;
|
||||||
const state = { n: 1 };
|
const state = { n: 1 };
|
||||||
const mutations = {
|
const actions = {
|
||||||
inc({ state }, delta) {
|
inc({ state }, delta) {
|
||||||
state.n += delta;
|
state.n += delta;
|
||||||
},
|
},
|
||||||
@@ -559,20 +310,19 @@ describe("updates triggered by the store", () => {
|
|||||||
state.n = val;
|
state.n = val;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
const store = new Store({ state, mutations });
|
const store = new Store({ state, actions });
|
||||||
store.on("update", null, () => updateCounter++);
|
store.on("update", null, () => updateCounter++);
|
||||||
|
|
||||||
store.commit("noop");
|
store.dispatch("noop");
|
||||||
await nextMicroTick();
|
await nextMicroTick();
|
||||||
expect(updateCounter).toBe(0);
|
expect(updateCounter).toBe(0);
|
||||||
|
|
||||||
store.commit("inc", 50);
|
store.dispatch("inc", 50);
|
||||||
await nextMicroTick();
|
await nextMicroTick();
|
||||||
expect(updateCounter).toBe(1);
|
expect(updateCounter).toBe(1);
|
||||||
|
|
||||||
store.commit("noop2");
|
store.dispatch("noop2");
|
||||||
await nextMicroTick();
|
await nextMicroTick();
|
||||||
expect(updateCounter).toBe(1);
|
expect(updateCounter).toBe(1);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
+26
-42
@@ -274,37 +274,6 @@ const LOCALSTORAGE_KEY = "todos-odoo";
|
|||||||
// Store Definition
|
// Store Definition
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
const actions = {
|
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) {
|
addTodo({ state }, title) {
|
||||||
const id = state.nextId++;
|
const id = state.nextId++;
|
||||||
const todo = {
|
const todo = {
|
||||||
@@ -318,6 +287,25 @@ const mutations = {
|
|||||||
const index = state.todos.findIndex(t => t.id === id);
|
const index = state.todos.findIndex(t => t.id === id);
|
||||||
state.todos.splice(index, 1);
|
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 }) {
|
editTodo({ state }, { id, title, completed }) {
|
||||||
const todo = state.todos.find(t => t.id === id);
|
const todo = state.todos.find(t => t.id === id);
|
||||||
if (title !== undefined) {
|
if (title !== undefined) {
|
||||||
@@ -338,11 +326,7 @@ function makeStore() {
|
|||||||
todos,
|
todos,
|
||||||
nextId
|
nextId
|
||||||
};
|
};
|
||||||
const store = new owl.store.Store({
|
const store = new owl.store.Store({ state, actions });
|
||||||
state,
|
|
||||||
actions,
|
|
||||||
mutations
|
|
||||||
});
|
|
||||||
store.on("update", null, () => {
|
store.on("update", null, () => {
|
||||||
const state = JSON.stringify(store.state.todos);
|
const state = JSON.stringify(store.state.todos);
|
||||||
window.localStorage.setItem(LOCALSTORAGE_KEY, state);
|
window.localStorage.setItem(LOCALSTORAGE_KEY, state);
|
||||||
@@ -415,7 +399,7 @@ class TodoApp extends owl.store.ConnectedComponent {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
get visibleTodos() {
|
get visibleTodos() {
|
||||||
let todos = this.props.todos;
|
let todos = this.storeProps.todos;
|
||||||
if (this.state.filter === "active") {
|
if (this.state.filter === "active") {
|
||||||
todos = todos.filter(t => !t.completed);
|
todos = todos.filter(t => !t.completed);
|
||||||
}
|
}
|
||||||
@@ -426,11 +410,11 @@ class TodoApp extends owl.store.ConnectedComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
get allChecked() {
|
get allChecked() {
|
||||||
return this.props.todos.every(todo => todo.completed);
|
return this.storeProps.todos.every(todo => todo.completed);
|
||||||
}
|
}
|
||||||
|
|
||||||
get remaining() {
|
get remaining() {
|
||||||
return this.props.todos.filter(todo => !todo.completed).length;
|
return this.storeProps.todos.filter(todo => !todo.completed).length;
|
||||||
}
|
}
|
||||||
|
|
||||||
get remainingText() {
|
get remainingText() {
|
||||||
@@ -481,7 +465,7 @@ const TODO_APP_STORE_XML = `<templates>
|
|||||||
<h1>todos</h1>
|
<h1>todos</h1>
|
||||||
<input class="new-todo" autofocus="true" autocomplete="off" placeholder="What needs to be done?" t-on-keyup="addTodo"/>
|
<input class="new-todo" autofocus="true" autocomplete="off" placeholder="What needs to be done?" t-on-keyup="addTodo"/>
|
||||||
</header>
|
</header>
|
||||||
<section class="main" t-if="props.todos.length">
|
<section class="main" t-if="storeProps.todos.length">
|
||||||
<input class="toggle-all" id="toggle-all" type="checkbox" t-att-checked="allChecked" t-on-click="toggleAll"/>
|
<input class="toggle-all" id="toggle-all" type="checkbox" t-att-checked="allChecked" t-on-click="toggleAll"/>
|
||||||
<label for="toggle-all"></label>
|
<label for="toggle-all"></label>
|
||||||
<ul class="todo-list">
|
<ul class="todo-list">
|
||||||
@@ -490,7 +474,7 @@ const TODO_APP_STORE_XML = `<templates>
|
|||||||
</t>
|
</t>
|
||||||
</ul>
|
</ul>
|
||||||
</section>
|
</section>
|
||||||
<footer class="footer" t-if="props.todos.length">
|
<footer class="footer" t-if="storeProps.todos.length">
|
||||||
<span class="todo-count">
|
<span class="todo-count">
|
||||||
<strong>
|
<strong>
|
||||||
<t t-esc="remaining"/>
|
<t t-esc="remaining"/>
|
||||||
@@ -508,7 +492,7 @@ const TODO_APP_STORE_XML = `<templates>
|
|||||||
<a t-on-click="setFilter('completed')" t-att-class="{selected: state.filter === 'completed'}">Completed</a>
|
<a t-on-click="setFilter('completed')" t-att-class="{selected: state.filter === 'completed'}">Completed</a>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
<button class="clear-completed" t-if="props.todos.length gt remaining" t-on-click="clearCompleted">
|
<button class="clear-completed" t-if="storeProps.todos.length gt remaining" t-on-click="clearCompleted">
|
||||||
Clear completed
|
Clear completed
|
||||||
</button>
|
</button>
|
||||||
</footer>
|
</footer>
|
||||||
|
|||||||
Reference in New Issue
Block a user