diff --git a/src/shared/getComponentOption.js b/src/shared/getComponentOption.js index a2ffd51..8a01318 100644 --- a/src/shared/getComponentOption.js +++ b/src/shared/getComponentOption.js @@ -10,6 +10,7 @@ import deepmerge from 'deepmerge' * @param {Object} opts.component - Vue component to fetch option data from * @param {String} opts.option - what option to look for * @param {Boolean} opts.deep - look for data in child components as well? + * @param {Function} opts.arrayMerge - how should arrays be merged? * @param {Object} [result={}] - result so far * @return {Object} - final aggregated result */ @@ -18,32 +19,35 @@ export default function getComponentOption (opts, result = {}) { const { $options } = component // only collect option data if it exists - if ($options[option]) { + if (typeof $options[option] !== 'undefined' && $options[option] !== null) { const data = $options[option] - // TODO: check data is plain object, throw if not - - // bind context of option methods (if any) to this component - for (const key in data) { - if (data.hasOwnProperty(key)) { - const value = data[key] - if (typeof value === 'function') { - data[key] = value.bind(component) + if (typeof data === 'object') { + // bind context of option methods (if any) to this component + for (const key in data) { + if (data.hasOwnProperty(key)) { + const value = data[key] + if (typeof value === 'function') { + data[key] = value.bind(component) + } } } - } - // merge with existing options - result = deepmerge(result, data, { arrayMerge }) - } + // merge with existing options + result = deepmerge(result, data, { arrayMerge }) - // collect & aggregate child options if deep = true - if (deep) { - const { $children } = component - for (let i = 0, len = $children.length; i < len; i++) { - const component = $children[i] - result = getComponentOption({ option, deep, component, arrayMerge }, result) + // collect & aggregate child options if deep = true + if (deep) { + const { $children } = component + for (let i = 0, len = $children.length; i < len; i++) { + const component = $children[i] + result = getComponentOption({ option, deep, component, arrayMerge }, result) + } + } + + return result } + result = data } return result diff --git a/test/getComponentOption.spec.js b/test/getComponentOption.spec.js new file mode 100644 index 0000000..ef4e2eb --- /dev/null +++ b/test/getComponentOption.spec.js @@ -0,0 +1,59 @@ +import Vue from 'vue' +import getComponentOption from '../src/shared/getComponentOption' + +describe('getComponentOption', () => { + const container = document.createElement('div') + let component + + afterEach(() => component.$destroy()) + + it('fetches the given option from the given component', () => { + component = new Vue({ someOption: 'foo' }) + const fetchedOption = getComponentOption({ component, option: 'someOption' }) + expect(fetchedOption).to.eql('foo') + }) + + it('fetches deeply nested component options and merges them', () => { + Vue.component('merge-child', { template: '
', foo: { bar: 'baz' } }) + + component = new Vue({ + foo: { fizz: 'buzz' }, + render: (h) => h('div', null, [h('merge-child')]), + el: container + }) + + const fetchedOption = getComponentOption({ component, option: 'foo', deep: true }) + expect(fetchedOption).to.eql({ bar: 'baz', fizz: 'buzz' }) + }) + + it('allows for a custom array merge strategy', () => { + Vue.component('array-child', { + template: '
', + foo: [ + { name: 'flower', content: 'rose' } + ] + }) + + component = new Vue({ + render: (h) => h('div', null, [h('array-child')]), + foo: [ + { name: 'flower', content: 'tulip' } + ], + el: container + }) + + const fetchedOption = getComponentOption({ + component, + option: 'foo', + deep: true, + arrayMerge (target, source) { + return target.concat(source) + } + }) + + expect(fetchedOption).to.eql([ + { name: 'flower', content: 'tulip' }, + { name: 'flower', content: 'rose' } + ]) + }) +})