diff --git a/src/shared/getComponentOption.js b/src/shared/getComponentOption.js index 1df2c0e..267421d 100644 --- a/src/shared/getComponentOption.js +++ b/src/shared/getComponentOption.js @@ -1,4 +1,5 @@ import deepmerge from 'deepmerge' +import isArray from './isArray' /** * Returns the `opts.option` $option value of the given `opts.component`. @@ -24,16 +25,10 @@ export default function getComponentOption (opts, result = {}) { if (typeof $options[option] !== 'undefined' && $options[option] !== null) { let data = $options[option] - // if option is a function, replace it with it's result - if (typeof data === 'function') { - data = data.call(component) - } - - if (typeof data === 'object') { - // merge with existing options - result = deepmerge(result, data, { arrayMerge }) + if (isArray(data)) { + result = data.reduce((result, dataItem) => mergeDataInResult(dataItem, result, component, arrayMerge), result) } else { - result = data + result = mergeDataInResult(data, result, component, arrayMerge) } } @@ -67,3 +62,17 @@ export default function getComponentOption (opts, result = {}) { } return result } + +function mergeDataInResult (data, result, component, arrayMerge) { + // if option is a function, replace it with it's result + if (typeof data === 'function') { + data = data.call(component) + } + + if (typeof data === 'object') { + // merge with existing options + return deepmerge(result, data, { arrayMerge }) + } else { + return data + } +} diff --git a/src/shared/plugin.js b/src/shared/plugin.js index 1c34e9a..18920a8 100644 --- a/src/shared/plugin.js +++ b/src/shared/plugin.js @@ -35,6 +35,9 @@ export default function VueMeta (Vue, options = {}) { // bind the $meta method to this component instance Vue.prototype.$meta = $meta(options) + // define optionMergeStrategies for the keyName + Vue.config.optionMergeStrategies[options.keyName] = Vue.config.optionMergeStrategies.created + // store an id to keep track of DOM updates let batchID = null diff --git a/test/getComponentOption.spec.js b/test/getComponentOption.spec.js index af2e2a9..103e86c 100644 --- a/test/getComponentOption.spec.js +++ b/test/getComponentOption.spec.js @@ -43,19 +43,23 @@ describe('getComponentOption', () => { expect(mergedOption).to.eql({ bar: 'baz', fizz: 'buzz' }) }) - it('allows for a custom array merge strategy', () => { + it('allows for a custom array merge strategy in object literal', () => { Vue.component('array-child', { template: '
', - foo: [ - { name: 'flower', content: 'rose' } - ] + foo: { + flowers: [ + { name: 'flower', content: 'rose' } + ] + } }) component = new Vue({ render: (h) => h('div', null, [h('array-child')]), - foo: [ - { name: 'flower', content: 'tulip' } - ], + foo: { + flowers: [ + { name: 'flower', content: 'tulip' } + ] + }, el: container }) @@ -68,9 +72,64 @@ describe('getComponentOption', () => { } }) - expect(mergedOption).to.eql([ - { name: 'flower', content: 'tulip' }, - { name: 'flower', content: 'rose' } - ]) + expect(mergedOption).to.eql({ + flowers: [ + { name: 'flower', content: 'tulip' }, + { name: 'flower', content: 'rose' } + ] + }) + }) + + it('merges arrays of objects literal options', () => { + component = new Vue({ someOption: [{ foo: 'hello' }, { bar: 'there' }] }) + + const mergedOption = getComponentOption({ component, option: 'someOption' }) + expect(mergedOption).to.eql({ foo: 'hello', bar: 'there' }) + }) + + it('merges arrays of mixed object literals and functions', () => { + component = new Vue({ + cake: 'good', + desserts: [ + { yogurt: 'meh' }, + function someFunction () { + return { cake: this.$options.cake } + }, + function someOtherFunction () { + return { pineapple: 'not bad' } + } + ] + }) + + const mergedOption = getComponentOption({ component, option: 'desserts' }) + expect(mergedOption).to.eql({ yogurt: 'meh', cake: 'good', pineapple: 'not bad' }) + }) + + it('uses custom array merge strategy when merging arrays in arrays of options', () => { + component = new Vue({ + template: '
', + foo: [ + { cars: [{ brand: 'renault', color: 'red' }] }, + function someFunction () { + return { cars: [{ brand: 'peugeot', color: 'blue' }] } + } + ] + }) + + const mergedOption = getComponentOption({ + component, + option: 'foo', + deep: true, + arrayMerge (target, source) { + return target.concat(source) + } + }) + + expect(mergedOption).to.eql({ + cars: [ + { brand: 'renault', color: 'red' }, + { brand: 'peugeot', color: 'blue' } + ] + }) }) }) diff --git a/test/getMetaInfo.spec.js b/test/getMetaInfo.spec.js index e72ab33..e24ea07 100644 --- a/test/getMetaInfo.spec.js +++ b/test/getMetaInfo.spec.js @@ -21,6 +21,9 @@ const defaultOptions = { const getMetaInfo = _getMetaInfo(defaultOptions) +// define optionMergeStrategies for the keyName +Vue.config.optionMergeStrategies[VUE_META_KEY_NAME] = Vue.config.optionMergeStrategies.created + describe('getMetaInfo', () => { // const container = document.createElement('div') let component @@ -530,4 +533,92 @@ describe('getMetaInfo', () => { __dangerouslyDisableSanitizersByTagID: {} }) }) + + it('properly merges mixins options', () => { + const mixin1 = { + metaInfo: function () { + return { + title: 'This title will be overridden', + meta: [ + { + vmid: 'og:title', + property: 'og:title', + content: 'This title will be overridden' + }, + { + vmid: 'og:fromMixin1', + property: 'og:fromMixin1', + content: 'This is from mixin1' + } + ] + } + } + } + const mixin2 = { + metaInfo: { + meta: [ + { + vmid: 'og:fromMixin2', + property: 'og:fromMixin2', + content: 'This is from mixin2' + } + ] + } + } + const component = new Vue({ + mixins: [mixin1, mixin2], + metaInfo: { + title: 'New Title', + meta: [ + { + vmid: 'og:title', + property: 'og:title', + content: 'New Title! - My page' + }, + { + vmid: 'og:description', + property: 'og:description', + content: 'Some Description' + } + ] + } + }) + expect(getMetaInfo(component)).to.eql({ + title: 'New Title', + titleChunk: 'New Title', + titleTemplate: '%s', + htmlAttrs: {}, + headAttrs: {}, + bodyAttrs: {}, + meta: [ + { + vmid: 'og:fromMixin1', + property: 'og:fromMixin1', + content: 'This is from mixin1' + }, + { + vmid: 'og:fromMixin2', + property: 'og:fromMixin2', + content: 'This is from mixin2' + }, + { + vmid: 'og:title', + property: 'og:title', + content: 'New Title! - My page' + }, + { + vmid: 'og:description', + property: 'og:description', + content: 'Some Description' + } + ], + base: [], + link: [], + style: [], + script: [], + noscript: [], + __dangerouslyDisableSanitizers: [], + __dangerouslyDisableSanitizersByTagID: {} + }) + }) }) diff --git a/test/plugin.spec.js b/test/plugin.spec.js index aa8d080..cac0040 100644 --- a/test/plugin.spec.js +++ b/test/plugin.spec.js @@ -30,4 +30,9 @@ describe('plugin', () => { const vm = new Vue(Component).$mount() expect(vm._hasMetaInfo).to.equal(true) }) + + it('setup optionMergeStrategies for the keyName', () => { + const strats = Vue.config.optionMergeStrategies + expect(strats[VUE_META_KEY_NAME]).to.equal(strats.created) + }) })