client: namespaces for actions and getteters

This commit is contained in:
Matteo Pagliazzi
2016-12-08 23:01:59 -08:00
parent f9f22f313f
commit 1de4ab3612
14 changed files with 135 additions and 47 deletions

View File

@@ -15,8 +15,10 @@
"aws-sdk": "^2.0.25", "aws-sdk": "^2.0.25",
"babel-core": "^6.0.0", "babel-core": "^6.0.0",
"babel-loader": "^6.0.0", "babel-loader": "^6.0.0",
"babel-plugin-syntax-async-functions": "^6.13.0",
"babel-plugin-transform-async-to-module-method": "^6.8.0", "babel-plugin-transform-async-to-module-method": "^6.8.0",
"babel-plugin-transform-object-rest-spread": "^6.16.0", "babel-plugin-transform-object-rest-spread": "^6.16.0",
"babel-plugin-transform-regenerator": "^6.16.1",
"babel-polyfill": "^6.6.1", "babel-polyfill": "^6.6.1",
"babel-preset-es2015": "^6.6.0", "babel-preset-es2015": "^6.6.0",
"babel-register": "^6.6.0", "babel-register": "^6.6.0",

View File

@@ -1,5 +1,9 @@
{ {
"presets": ["es2015"], "presets": ["es2015"],
"plugins": ["transform-object-rest-spread"], "plugins": [
"transform-object-rest-spread",
"syntax-async-functions",
"transform-regenerator",
],
"comments": false "comments": false
} }

View File

@@ -1,4 +1,4 @@
import { userGems } from 'client/store/getters'; import { gems as userGems } from 'client/store/getters/user';
describe('userGems getter', () => { describe('userGems getter', () => {
it('returns the user\'s gems', () => { it('returns the user\'s gems', () => {

View File

@@ -1,6 +1,7 @@
import Vue from 'vue'; import Vue from 'vue';
import storeInjector from 'inject?-vue!client/store'; import storeInjector from 'inject?-vue!client/store';
import { mapState, mapGetters, mapActions } from 'client/store'; import { mapState, mapGetters, mapActions } from 'client/store';
import { flattenAndNamespace } from 'client/store/helpers/internals';
describe('Store', () => { describe('Store', () => {
let injectedStore; let injectedStore;
@@ -14,11 +15,25 @@ describe('Store', () => {
computedName ({ state }) { computedName ({ state }) {
return `${state.name} computed!`; return `${state.name} computed!`;
}, },
...flattenAndNamespace({
nested: {
computedName ({ state }) {
return `${state.name} computed!`;
},
},
}),
}, },
'./actions': { './actions': {
getName ({ state }, ...args) { getName ({ state }, ...args) {
return [state.name, ...args]; return [state.name, ...args];
}, },
...flattenAndNamespace({
nested: {
getName ({ state }, ...args) {
return [state.name, ...args];
},
},
}),
}, },
}).default; }).default;
}); });
@@ -41,17 +56,29 @@ describe('Store', () => {
injectedStore.state.name = 'test updated'; injectedStore.state.name = 'test updated';
}); });
describe('getters', () => {
it('supports getters', () => { it('supports getters', () => {
expect(injectedStore.getters.computedName).to.equal('test computed!'); expect(injectedStore.getters.computedName).to.equal('test computed!');
injectedStore.state.name = 'test updated'; injectedStore.state.name = 'test updated';
expect(injectedStore.getters.computedName).to.equal('test updated computed!'); expect(injectedStore.getters.computedName).to.equal('test updated computed!');
}); });
it('supports nested getters', () => {
expect(injectedStore.getters['nested:computedName']).to.equal('test computed!');
injectedStore.state.name = 'test updated';
expect(injectedStore.getters['nested:computedName']).to.equal('test updated computed!');
});
});
describe('actions', () => { describe('actions', () => {
it('can be dispatched', () => { it('can dispatch an action', () => {
expect(injectedStore.dispatch('getName', 1, 2, 3)).to.deep.equal(['test', 1, 2, 3]); expect(injectedStore.dispatch('getName', 1, 2, 3)).to.deep.equal(['test', 1, 2, 3]);
}); });
it('can dispatch a nested action', () => {
expect(injectedStore.dispatch('nested:getName', 1, 2, 3)).to.deep.equal(['test', 1, 2, 3]);
});
it('throws an error is the action doesn\'t exists', () => { it('throws an error is the action doesn\'t exists', () => {
expect(() => injectedStore.dispatched('wrong')).to.throw; expect(() => injectedStore.dispatched('wrong')).to.throw;
}); });
@@ -116,5 +143,26 @@ describe('Store', () => {
}, },
}); });
}); });
it('flattenAndNamespace', () => {
let result = flattenAndNamespace({
nested: {
computed ({ state }, ...args) {
return [state.name, ...args];
},
getName ({ state }, ...args) {
return [state.name, ...args];
},
},
nested2: {
getName ({ state }, ...args) {
return [state.name, ...args];
},
},
});
expect(Object.keys(result).length).to.equal(3);
expect(Object.keys(result).sort()).to.deep.equal(['nested2:getName', 'nested:computed', 'nested:getName']);
});
}); });
}); });

View File

@@ -1,4 +1,8 @@
{ {
"presets": ["es2015"], "presets": ["es2015"],
"plugins": ["transform-object-rest-spread"], "plugins": [
"transform-object-rest-spread",
"syntax-async-functions",
"transform-regenerator",
],
} }

View File

@@ -213,7 +213,9 @@ import { mapState, mapGetters } from '../store';
export default { export default {
computed: { computed: {
...mapGetters(['userGems']), ...mapGetters({
userGems: 'user:gems',
}),
...mapState(['user']), ...mapState(['user']),
}, },
}; };

View File

@@ -1,9 +1,13 @@
import tasks from './tasks'; import { flattenAndNamespace } from '../helpers/internals';
import user from './user'; import * as tasks from './tasks';
import * as user from './user';
const actions = { // Actions should be named as 'actionName' and can be accessed as 'namespace.actionName'
tasks, // Example: fetch in user.js -> 'user.fetch'
const actions = flattenAndNamespace({
user, user,
}; tasks,
});
export default actions; export default actions;

View File

@@ -1,15 +1,6 @@
import Vue from 'vue'; import Vue from 'vue';
const actions = {}; export async function fetchUserTasks (store) {
let response = await Vue.http.get('/api/v3/tasks/user');
actions.fetchUserTasks = function fetchUserTasks (store) {
let promise = Vue.http.get('/api/v3/tasks/user');
promise.then((response) => {
store.state.tasks = response.body.data; store.state.tasks = response.body.data;
}); }
return promise;
};
export default actions;

View File

@@ -1,15 +1,6 @@
import Vue from 'vue'; import Vue from 'vue';
const actions = {}; export async function fetch (store) { // eslint-disable-line no-shadow
let response = await Vue.http.get('/api/v3/user');
actions.fetch = function fetchUser (store) {
let promise = Vue.http.get('/api/v3/user');
promise.then((response) => {
store.state.user = response.body.data; store.state.user = response.body.data;
}); }
return promise;
};
export default actions;

View File

@@ -1,3 +1,11 @@
export function userGems (store) { import { flattenAndNamespace } from '../helpers/internals';
return store.state.user.balance * 4; import * as user from './user';
}
// Getters should be named as 'getterName' and can be accessed as 'namespace.getterName'
// Example: gems in user.js -> 'user.gems'
const getters = flattenAndNamespace({
user,
});
export default getters;

View File

@@ -0,0 +1,3 @@
export function gems (store) {
return store.state.user.balance * 4;
}

View File

@@ -0,0 +1,32 @@
/* Flatten multiple objects into a single, namespaced object.
Example:
getters
user
gems
tasks
...
tasks
todos
dailys
...
Result:
getters
user.gems
user.tasks
tasks.todos
tasks.dailys
*/
export function flattenAndNamespace (namespaces) {
let result = {};
Object.keys(namespaces).forEach(namespace => {
Object.keys(namespaces[namespace]).forEach(itemName => {
result[`${namespace}:${itemName}`] = namespaces[namespace][itemName];
});
});
return result;
}

View File

@@ -1,8 +1,7 @@
import Vue from 'vue'; import Vue from 'vue';
import state from './state'; import state from './state';
import actions from './actions'; import actions from './actions';
import * as getters from './getters'; import getters from './getters';
import { get } from 'lodash';
// Central application store for Habitica // Central application store for Habitica
// Heavily inspired to Vuex (https://github.com/vuejs/vuex) with a very // Heavily inspired to Vuex (https://github.com/vuejs/vuex) with a very
@@ -22,7 +21,7 @@ const store = {
// Actions should be called using store.dispatch(ACTION_NAME, ...ARGS) // Actions should be called using store.dispatch(ACTION_NAME, ...ARGS)
// They get passed the store instance and any additional argument passed to dispatch() // They get passed the store instance and any additional argument passed to dispatch()
dispatch (type, ...args) { dispatch (type, ...args) {
let action = get(actions, type); let action = actions[type];
if (!action) throw new Error(`Action "${type}" not found.`); if (!action) throw new Error(`Action "${type}" not found.`);
return action(store, ...args); return action(store, ...args);
@@ -60,7 +59,7 @@ export {
mapState, mapState,
mapGetters, mapGetters,
mapActions, mapActions,
} from './helpers'; } from './helpers/public';
// Setup internal Vue instance to make state and getters reactive // Setup internal Vue instance to make state and getters reactive
_vm = new Vue({ _vm = new Vue({