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",
"babel-core": "^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-object-rest-spread": "^6.16.0",
"babel-plugin-transform-regenerator": "^6.16.1",
"babel-polyfill": "^6.6.1",
"babel-preset-es2015": "^6.6.0",
"babel-register": "^6.6.0",

View File

@@ -1,5 +1,9 @@
{
"presets": ["es2015"],
"plugins": ["transform-object-rest-spread"],
"plugins": [
"transform-object-rest-spread",
"syntax-async-functions",
"transform-regenerator",
],
"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', () => {
it('returns the user\'s gems', () => {

View File

@@ -1,6 +1,7 @@
import Vue from 'vue';
import storeInjector from 'inject?-vue!client/store';
import { mapState, mapGetters, mapActions } from 'client/store';
import { flattenAndNamespace } from 'client/store/helpers/internals';
describe('Store', () => {
let injectedStore;
@@ -14,11 +15,25 @@ describe('Store', () => {
computedName ({ state }) {
return `${state.name} computed!`;
},
...flattenAndNamespace({
nested: {
computedName ({ state }) {
return `${state.name} computed!`;
},
},
}),
},
'./actions': {
getName ({ state }, ...args) {
return [state.name, ...args];
},
...flattenAndNamespace({
nested: {
getName ({ state }, ...args) {
return [state.name, ...args];
},
},
}),
},
}).default;
});
@@ -41,17 +56,29 @@ describe('Store', () => {
injectedStore.state.name = 'test updated';
});
describe('getters', () => {
it('supports getters', () => {
expect(injectedStore.getters.computedName).to.equal('test computed!');
injectedStore.state.name = 'test updated';
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', () => {
it('can be dispatched', () => {
it('can dispatch an action', () => {
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', () => {
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"],
"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 {
computed: {
...mapGetters(['userGems']),
...mapGetters({
userGems: 'user:gems',
}),
...mapState(['user']),
},
};

View File

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

View File

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

View File

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

View File

@@ -1,3 +1,11 @@
export function userGems (store) {
return store.state.user.balance * 4;
}
import { flattenAndNamespace } from '../helpers/internals';
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 state from './state';
import actions from './actions';
import * as getters from './getters';
import { get } from 'lodash';
import getters from './getters';
// Central application store for Habitica
// 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)
// They get passed the store instance and any additional argument passed to dispatch()
dispatch (type, ...args) {
let action = get(actions, type);
let action = actions[type];
if (!action) throw new Error(`Action "${type}" not found.`);
return action(store, ...args);
@@ -60,7 +59,7 @@ export {
mapState,
mapGetters,
mapActions,
} from './helpers';
} from './helpers/public';
// Setup internal Vue instance to make state and getters reactive
_vm = new Vue({