From 1de4ab36123ab7dfd43dddfb7e696a073b30c68c Mon Sep 17 00:00:00 2001 From: Matteo Pagliazzi Date: Thu, 8 Dec 2016 23:01:59 -0800 Subject: [PATCH] client: namespaces for actions and getteters --- package.json | 2 + test/client/.babelrc | 6 +- .../unit/specs/getters/userGems.spec.js | 2 +- test/client/unit/specs/store.spec.js | 58 +++++++++++++++++-- website/client/.babelrc | 6 +- website/client/components/appMenu.vue | 4 +- website/client/store/actions/index.js | 14 +++-- website/client/store/actions/tasks.js | 17 ++---- website/client/store/actions/user.js | 17 ++---- website/client/store/getters/index.js | 14 ++++- website/client/store/getters/user.js | 3 + website/client/store/helpers/internals.js | 32 ++++++++++ .../store/{helpers.js => helpers/public.js} | 0 website/client/store/index.js | 7 +-- 14 files changed, 135 insertions(+), 47 deletions(-) create mode 100644 website/client/store/getters/user.js create mode 100644 website/client/store/helpers/internals.js rename website/client/store/{helpers.js => helpers/public.js} (100%) diff --git a/package.json b/package.json index d8c0542a7c..3d40342768 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/test/client/.babelrc b/test/client/.babelrc index 7743d07e4f..e7f6cf376e 100644 --- a/test/client/.babelrc +++ b/test/client/.babelrc @@ -1,5 +1,9 @@ { "presets": ["es2015"], - "plugins": ["transform-object-rest-spread"], + "plugins": [ + "transform-object-rest-spread", + "syntax-async-functions", + "transform-regenerator", + ], "comments": false } \ No newline at end of file diff --git a/test/client/unit/specs/getters/userGems.spec.js b/test/client/unit/specs/getters/userGems.spec.js index 7f9375ea6c..f1a754822e 100644 --- a/test/client/unit/specs/getters/userGems.spec.js +++ b/test/client/unit/specs/getters/userGems.spec.js @@ -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', () => { diff --git a/test/client/unit/specs/store.spec.js b/test/client/unit/specs/store.spec.js index a8eb66c381..3d04d828dd 100644 --- a/test/client/unit/specs/store.spec.js +++ b/test/client/unit/specs/store.spec.js @@ -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'; }); - 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!'); + 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']); + }); }); }); diff --git a/website/client/.babelrc b/website/client/.babelrc index 9e7b49a2ec..3559fac3f6 100644 --- a/website/client/.babelrc +++ b/website/client/.babelrc @@ -1,4 +1,8 @@ { "presets": ["es2015"], - "plugins": ["transform-object-rest-spread"], + "plugins": [ + "transform-object-rest-spread", + "syntax-async-functions", + "transform-regenerator", + ], } \ No newline at end of file diff --git a/website/client/components/appMenu.vue b/website/client/components/appMenu.vue index 0adfb9360a..beb00057b4 100644 --- a/website/client/components/appMenu.vue +++ b/website/client/components/appMenu.vue @@ -213,7 +213,9 @@ import { mapState, mapGetters } from '../store'; export default { computed: { - ...mapGetters(['userGems']), + ...mapGetters({ + userGems: 'user:gems', + }), ...mapState(['user']), }, }; diff --git a/website/client/store/actions/index.js b/website/client/store/actions/index.js index 383d8e5c1d..d2b632efe0 100644 --- a/website/client/store/actions/index.js +++ b/website/client/store/actions/index.js @@ -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; \ No newline at end of file diff --git a/website/client/store/actions/tasks.js b/website/client/store/actions/tasks.js index 84263f99fa..6357e9de5a 100644 --- a/website/client/store/actions/tasks.js +++ b/website/client/store/actions/tasks.js @@ -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) => { - store.state.tasks = response.body.data; - }); - - return promise; -}; - -export default actions; +export async function fetchUserTasks (store) { + let response = await Vue.http.get('/api/v3/tasks/user'); + store.state.tasks = response.body.data; +} \ No newline at end of file diff --git a/website/client/store/actions/user.js b/website/client/store/actions/user.js index aeef1ae848..4320a156df 100644 --- a/website/client/store/actions/user.js +++ b/website/client/store/actions/user.js @@ -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) => { - store.state.user = response.body.data; - }); - - return promise; -}; - -export default actions; \ No newline at end of file +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; +} \ No newline at end of file diff --git a/website/client/store/getters/index.js b/website/client/store/getters/index.js index 535612325b..4c3a5a0374 100644 --- a/website/client/store/getters/index.js +++ b/website/client/store/getters/index.js @@ -1,3 +1,11 @@ -export function userGems (store) { - return store.state.user.balance * 4; -} \ No newline at end of file +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; \ No newline at end of file diff --git a/website/client/store/getters/user.js b/website/client/store/getters/user.js new file mode 100644 index 0000000000..645483d360 --- /dev/null +++ b/website/client/store/getters/user.js @@ -0,0 +1,3 @@ +export function gems (store) { + return store.state.user.balance * 4; +} \ No newline at end of file diff --git a/website/client/store/helpers/internals.js b/website/client/store/helpers/internals.js new file mode 100644 index 0000000000..01f06997a2 --- /dev/null +++ b/website/client/store/helpers/internals.js @@ -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; +} \ No newline at end of file diff --git a/website/client/store/helpers.js b/website/client/store/helpers/public.js similarity index 100% rename from website/client/store/helpers.js rename to website/client/store/helpers/public.js diff --git a/website/client/store/index.js b/website/client/store/index.js index eefc988dda..f4dfd92eb8 100644 --- a/website/client/store/index.js +++ b/website/client/store/index.js @@ -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({