diff --git a/src/index.js b/src/index.js index 90a88331..6edfdbd3 100644 --- a/src/index.js +++ b/src/index.js @@ -4,6 +4,10 @@ function objectEntries(obj) { return Object.keys(obj).map(key => [key, obj[key]]); } +function typeOf(obj) { + return ({}).toString.call(obj).match(/\s([a-zA-Z]+)/)[1].toLowerCase(); +} + function normalizeNamespace(fn) { return (...params) => { // eslint-disable-next-line prefer-const @@ -23,11 +27,11 @@ function normalizeNamespace(fn) { } export function getField(state) { - return path => path.split(/[.[\]]+/).reduce((prev, key) => prev[key], state); + return path => path.split(/[.[\]]+/).filter(x => x).reduce((prev, key) => prev[key], state); } export function updateField(state, { path, value }) { - path.split(/[.[\]]+/).reduce((prev, key, index, array) => { + path.split(/[.[\]]+/).filter(x => x).reduce((prev, key, index, array) => { if (array.length === index + 1) { // eslint-disable-next-line no-param-reassign prev[key] = value; @@ -37,24 +41,44 @@ export function updateField(state, { path, value }) { }, state); } -export const mapFields = normalizeNamespace((namespace, fields, getterType, mutationType) => { - const fieldsObject = Array.isArray(fields) ? arrayToObject(fields) : fields; +export const mapFields = normalizeNamespace(( + namespace, + paths, + getterType, + mutationType, +) => { + const pathsObject = Array.isArray(paths) ? arrayToObject(paths) : paths; + return Object.keys(pathsObject).reduce((entries, key) => { + const path = pathsObject[key]; - return Object.keys(fieldsObject).reduce((prev, key) => { - const path = fieldsObject[key]; - const field = { + // eslint-disable-next-line no-param-reassign + entries[key] = { get() { - return this.$store.getters[getterType](path); + const tpath = typeof path === `function` ? path.call(null, this) : path; + const store = this.$store; + const row = store.getters[getterType](tpath); + + if (!row || typeOf(row) !== `object`) return row; + + return Object.keys(row).reduce((prev, fieldKey) => { + const fieldPath = `${tpath}.${fieldKey}`; + return Object.defineProperty(prev, fieldKey, { + get() { + return store.getters[getterType](fieldPath); + }, + set(value) { + store.commit(mutationType, { path: fieldPath, value }); + }, + }); + }, {}); }, set(value) { - this.$store.commit(mutationType, { path, value }); + const tpath = typeof path === `function` ? path.call(null, this) : path; + this.$store.commit(mutationType, { path: tpath, value }); }, }; - // eslint-disable-next-line no-param-reassign - prev[key] = field; - - return prev; + return entries; }, {}); }); @@ -72,12 +96,13 @@ export const mapMultiRowFields = normalizeNamespace(( // eslint-disable-next-line no-param-reassign entries[key] = { get() { + const tpath = typeof path === `function` ? path.call(null, this) : path; const store = this.$store; - const rows = objectEntries(store.getters[getterType](path)); + const rows = objectEntries(store.getters[getterType](tpath)); return rows .map(fieldsObject => Object.keys(fieldsObject[1]).reduce((prev, fieldKey) => { - const fieldPath = `${path}[${fieldsObject[0]}].${fieldKey}`; + const fieldPath = `${tpath}[${fieldsObject[0]}].${fieldKey}`; return Object.defineProperty(prev, fieldKey, { get() { diff --git a/src/lib/array-to-object.js b/src/lib/array-to-object.js index 7528582e..452098cb 100644 --- a/src/lib/array-to-object.js +++ b/src/lib/array-to-object.js @@ -1,5 +1,7 @@ export default function arrayToObject(fields = []) { return fields.reduce((prev, path) => { + if (typeof path === `object`) return { ...prev, ...path }; + const key = path.split(`.`).slice(-1)[0]; if (prev[key]) { diff --git a/test/function-param.test.js b/test/function-param.test.js new file mode 100644 index 00000000..b93af628 --- /dev/null +++ b/test/function-param.test.js @@ -0,0 +1,81 @@ +import Vuex from 'vuex'; +import { createLocalVue, shallowMount } from '@vue/test-utils'; + +import { createHelpers, getField, updateField } from '../src'; + +const localVue = createLocalVue(); + +localVue.use(Vuex); + +const { mapFields } = createHelpers({ + getterType: `getField`, + mutationType: `updateField`, +}); + +describe(`Component initialized with row setup.`, () => { + let Component; + let store; + let wrapper; + + beforeEach(() => { + + Component = { + template: ` +
+
+ +
+
+ `, + data() { + return { + ative_room: '0' + }; + }, + computed: { + ...mapFields([{name: (c) => `users[${c.ative_room}].name` }]), + } + }; + + store = new Vuex.Store({ + state: { + users: [ + { + name: `Foo`, + email: `foo@foo.com`, + }, + { + name: `Bar`, + email: `bar@bar.com`, + }, + ], + }, + getters: { + getField, + }, + mutations: { + updateField, + }, + }); + + wrapper = shallowMount(Component, { localVue, store }); + }); + + test(`It should render the component.`, () => { + expect(wrapper.exists()).toBe(true); + }); + + test(`It should update field values when the store is updated.`, () => { + store.state.users[0].name = `New Name`; + // store.state.users[0].email = `new@email.com`; + + expect(wrapper.find(`input`).element.value).toBe(`New Name`); + }); + + test(`It should update the store when the field values are updated.`, () => { + wrapper.find(`input`).element.value = `New Name`; + wrapper.find(`input`).trigger(`input`); + + expect(store.state.users[0].name).toBe(`New Name`); + }); +}); diff --git a/test/map-object-properties.test.js b/test/map-object-properties.test.js new file mode 100644 index 00000000..1d1622c9 --- /dev/null +++ b/test/map-object-properties.test.js @@ -0,0 +1,86 @@ +import Vuex from 'vuex'; +import { createLocalVue, shallowMount } from '@vue/test-utils'; + +import { createHelpers, getField, updateField } from '../src'; + +const localVue = createLocalVue(); + +localVue.use(Vuex); + +const { mapFields } = createHelpers({ + getterType: `getField`, + mutationType: `updateField`, +}); + +describe(`Component initialized with row setup.`, () => { + let Component; + let store; + let wrapper; + + beforeEach(() => { + + Component = { + template: ` +
+
+ + +
+
+ `, + data() { + return { + ative_room: '0' + }; + }, + computed: { + ...mapFields({user: `users[0]` }), + } + }; + + store = new Vuex.Store({ + state: { + users: [ + { + name: `Foo`, + email: `foo@foo.com`, + }, + { + name: `Bar`, + email: `bar@bar.com`, + }, + ], + }, + getters: { + getField, + }, + mutations: { + updateField, + }, + }); + + wrapper = shallowMount(Component, { localVue, store }); + }); + + test(`It should render the component.`, () => { + expect(wrapper.exists()).toBe(true); + }); + + test(`It should update field values when the store is updated.`, () => { + store.state.users[0].name = `New Name`; + store.state.users[0].email = `new@email.com`; + + expect(wrapper.find(`input`).element.value).toBe(`New Name`); + expect(wrapper.find(`input:nth-child(2)`).element.value).toBe(`new@email.com`); + }); + + test(`It should update the store when the field values are updated.`, () => { + wrapper.find(`input`).element.value = `Foo`; + wrapper.find(`input:nth-child(2)`).element.value = `foo@foo.com`; + wrapper.find(`input`).trigger(`input`); + wrapper.find(`input:nth-child(2)`).trigger(`input`); + + expect(store.state.users[0].name).toBe(`Foo`); + expect(store.state.users[0].email).toBe(`foo@foo.com`); + }); +}); diff --git a/test/multi-row.test.js b/test/multi-row.test.js index 3cfa43ee..a321156e 100644 --- a/test/multi-row.test.js +++ b/test/multi-row.test.js @@ -20,10 +20,14 @@ describe(`Component initialized with multi row setup.`, () => { +
+ + +
`, computed: { - ...mapMultiRowFields([`users`]), + ...mapMultiRowFields([`users`, { usersfun: () => `users` }]), }, };