From ad4d61c38ff8e83cf4d933b961cc8875e1e22f60 Mon Sep 17 00:00:00 2001 From: adrienbaron Date: Thu, 6 Sep 2018 11:47:07 +0200 Subject: [PATCH] feat: merge mixins options Closes #261 --- src/shared/getComponentOption.js | 27 ++++++---- src/shared/plugin.js | 3 ++ test/getComponentOption.spec.js | 79 +++++++++++++++++++++++---- test/getMetaInfo.spec.js | 91 ++++++++++++++++++++++++++++++++ test/plugin.spec.js | 5 ++ 5 files changed, 186 insertions(+), 19 deletions(-) diff --git a/src/shared/getComponentOption.js b/src/shared/getComponentOption.js index 1df2c0eb..267421d6 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 1c34e9a3..18920a8a 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 af2e2a91..103e86c8 100644 --- a/test/getComponentOption.spec.js +++ b/test/getComponentOption.spec.js @@ -43,20 +43,77 @@ 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: [ + foo: { + flowers: [ + { name: 'flower', content: 'rose' } + ] + } + }) + + component = new Vue({ + render: (h) => h('div', null, [h('array-child')]), + foo: { + flowers: [ + { name: 'flower', content: 'tulip' } + ] + }, + el: container + }) + + const mergedOption = getComponentOption({ + component, + option: 'foo', + deep: true, + arrayMerge (target, source) { + return target.concat(source) + } + }) + + 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({ - render: (h) => h('div', null, [h('array-child')]), + 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: [ - { name: 'flower', content: 'tulip' } - ], - el: container + { cars: [{ brand: 'renault', color: 'red' }] }, + function someFunction () { + return { cars: [{ brand: 'peugeot', color: 'blue' }] } + } + ] }) const mergedOption = getComponentOption({ @@ -68,9 +125,11 @@ describe('getComponentOption', () => { } }) - expect(mergedOption).to.eql([ - { name: 'flower', content: 'tulip' }, - { name: 'flower', content: 'rose' } - ]) + 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 e72ab33c..e24ea07a 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 aa8d080c..cac00400 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) + }) })