Skip to content
This repository was archived by the owner on Oct 1, 2025. It is now read-only.

Commit f031a35

Browse files
authored
Merge pull request #262 from adrienbaron/master
feat: merge mixins options
2 parents 33a7aa4 + eacad9a commit f031a35

File tree

5 files changed

+185
-19
lines changed

5 files changed

+185
-19
lines changed

src/shared/getComponentOption.js

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import deepmerge from 'deepmerge'
2+
import isArray from './isArray'
23

34
/**
45
* Returns the `opts.option` $option value of the given `opts.component`.
@@ -24,16 +25,10 @@ export default function getComponentOption (opts, result = {}) {
2425
if (typeof $options[option] !== 'undefined' && $options[option] !== null) {
2526
let data = $options[option]
2627

27-
// if option is a function, replace it with it's result
28-
if (typeof data === 'function') {
29-
data = data.call(component)
30-
}
31-
32-
if (typeof data === 'object') {
33-
// merge with existing options
34-
result = deepmerge(result, data, { arrayMerge })
28+
if (isArray(data)) {
29+
result = data.reduce((result, dataItem) => mergeDataInResult(dataItem, result, component, arrayMerge), result)
3530
} else {
36-
result = data
31+
result = mergeDataInResult(data, result, component, arrayMerge)
3732
}
3833
}
3934

@@ -67,3 +62,16 @@ export default function getComponentOption (opts, result = {}) {
6762
}
6863
return result
6964
}
65+
66+
function mergeDataInResult (data, result, component, arrayMerge) {
67+
// if option is a function, replace it with it's result
68+
if (typeof data === 'function') {
69+
data = data.call(component)
70+
}
71+
72+
if (typeof data === 'object') {
73+
// merge with existing options
74+
return deepmerge(result, data, { arrayMerge })
75+
}
76+
return data
77+
}

src/shared/plugin.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,9 @@ export default function VueMeta (Vue, options = {}) {
3535
// bind the $meta method to this component instance
3636
Vue.prototype.$meta = $meta(options)
3737

38+
// define optionMergeStrategies for the keyName
39+
Vue.config.optionMergeStrategies[options.keyName] = Vue.config.optionMergeStrategies.created
40+
3841
// store an id to keep track of DOM updates
3942
let batchID = null
4043

test/getComponentOption.spec.js

Lines changed: 69 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -43,20 +43,77 @@ describe('getComponentOption', () => {
4343
expect(mergedOption).to.eql({ bar: 'baz', fizz: 'buzz' })
4444
})
4545

46-
it('allows for a custom array merge strategy', () => {
46+
it('allows for a custom array merge strategy in object literal', () => {
4747
Vue.component('array-child', {
4848
template: '<div></div>',
49-
foo: [
49+
foo: {
50+
flowers: [
51+
{ name: 'flower', content: 'rose' }
52+
]
53+
}
54+
})
55+
56+
component = new Vue({
57+
render: (h) => h('div', null, [h('array-child')]),
58+
foo: {
59+
flowers: [
60+
{ name: 'flower', content: 'tulip' }
61+
]
62+
},
63+
el: container
64+
})
65+
66+
const mergedOption = getComponentOption({
67+
component,
68+
option: 'foo',
69+
deep: true,
70+
arrayMerge (target, source) {
71+
return target.concat(source)
72+
}
73+
})
74+
75+
expect(mergedOption).to.eql({
76+
flowers: [
77+
{ name: 'flower', content: 'tulip' },
5078
{ name: 'flower', content: 'rose' }
5179
]
5280
})
81+
})
82+
83+
it('merges arrays of objects literal options', () => {
84+
component = new Vue({ someOption: [{ foo: 'hello' }, { bar: 'there' }] })
85+
86+
const mergedOption = getComponentOption({ component, option: 'someOption' })
87+
expect(mergedOption).to.eql({ foo: 'hello', bar: 'there' })
88+
})
5389

90+
it('merges arrays of mixed object literals and functions', () => {
5491
component = new Vue({
55-
render: (h) => h('div', null, [h('array-child')]),
92+
cake: 'good',
93+
desserts: [
94+
{ yogurt: 'meh' },
95+
function someFunction () {
96+
return { cake: this.$options.cake }
97+
},
98+
function someOtherFunction () {
99+
return { pineapple: 'not bad' }
100+
}
101+
]
102+
})
103+
104+
const mergedOption = getComponentOption({ component, option: 'desserts' })
105+
expect(mergedOption).to.eql({ yogurt: 'meh', cake: 'good', pineapple: 'not bad' })
106+
})
107+
108+
it('uses custom array merge strategy when merging arrays in arrays of options', () => {
109+
component = new Vue({
110+
template: '<div></div>',
56111
foo: [
57-
{ name: 'flower', content: 'tulip' }
58-
],
59-
el: container
112+
{ cars: [{ brand: 'renault', color: 'red' }] },
113+
function someFunction () {
114+
return { cars: [{ brand: 'peugeot', color: 'blue' }] }
115+
}
116+
]
60117
})
61118

62119
const mergedOption = getComponentOption({
@@ -68,9 +125,11 @@ describe('getComponentOption', () => {
68125
}
69126
})
70127

71-
expect(mergedOption).to.eql([
72-
{ name: 'flower', content: 'tulip' },
73-
{ name: 'flower', content: 'rose' }
74-
])
128+
expect(mergedOption).to.eql({
129+
cars: [
130+
{ brand: 'renault', color: 'red' },
131+
{ brand: 'peugeot', color: 'blue' }
132+
]
133+
})
75134
})
76135
})

test/getMetaInfo.spec.js

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@ const defaultOptions = {
2121

2222
const getMetaInfo = _getMetaInfo(defaultOptions)
2323

24+
// define optionMergeStrategies for the keyName
25+
Vue.config.optionMergeStrategies[VUE_META_KEY_NAME] = Vue.config.optionMergeStrategies.created
26+
2427
describe('getMetaInfo', () => {
2528
// const container = document.createElement('div')
2629
let component
@@ -530,4 +533,92 @@ describe('getMetaInfo', () => {
530533
__dangerouslyDisableSanitizersByTagID: {}
531534
})
532535
})
536+
537+
it('properly merges mixins options', () => {
538+
const mixin1 = {
539+
metaInfo: function () {
540+
return {
541+
title: 'This title will be overridden',
542+
meta: [
543+
{
544+
vmid: 'og:title',
545+
property: 'og:title',
546+
content: 'This title will be overridden'
547+
},
548+
{
549+
vmid: 'og:fromMixin1',
550+
property: 'og:fromMixin1',
551+
content: 'This is from mixin1'
552+
}
553+
]
554+
}
555+
}
556+
}
557+
const mixin2 = {
558+
metaInfo: {
559+
meta: [
560+
{
561+
vmid: 'og:fromMixin2',
562+
property: 'og:fromMixin2',
563+
content: 'This is from mixin2'
564+
}
565+
]
566+
}
567+
}
568+
const component = new Vue({
569+
mixins: [mixin1, mixin2],
570+
metaInfo: {
571+
title: 'New Title',
572+
meta: [
573+
{
574+
vmid: 'og:title',
575+
property: 'og:title',
576+
content: 'New Title! - My page'
577+
},
578+
{
579+
vmid: 'og:description',
580+
property: 'og:description',
581+
content: 'Some Description'
582+
}
583+
]
584+
}
585+
})
586+
expect(getMetaInfo(component)).to.eql({
587+
title: 'New Title',
588+
titleChunk: 'New Title',
589+
titleTemplate: '%s',
590+
htmlAttrs: {},
591+
headAttrs: {},
592+
bodyAttrs: {},
593+
meta: [
594+
{
595+
vmid: 'og:fromMixin1',
596+
property: 'og:fromMixin1',
597+
content: 'This is from mixin1'
598+
},
599+
{
600+
vmid: 'og:fromMixin2',
601+
property: 'og:fromMixin2',
602+
content: 'This is from mixin2'
603+
},
604+
{
605+
vmid: 'og:title',
606+
property: 'og:title',
607+
content: 'New Title! - My page'
608+
},
609+
{
610+
vmid: 'og:description',
611+
property: 'og:description',
612+
content: 'Some Description'
613+
}
614+
],
615+
base: [],
616+
link: [],
617+
style: [],
618+
script: [],
619+
noscript: [],
620+
__dangerouslyDisableSanitizers: [],
621+
__dangerouslyDisableSanitizersByTagID: {}
622+
})
623+
})
533624
})

test/plugin.spec.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,4 +30,9 @@ describe('plugin', () => {
3030
const vm = new Vue(Component).$mount()
3131
expect(vm._hasMetaInfo).to.equal(true)
3232
})
33+
34+
it('setup optionMergeStrategies for the keyName', () => {
35+
const strats = Vue.config.optionMergeStrategies
36+
expect(strats[VUE_META_KEY_NAME]).to.equal(strats.created)
37+
})
3338
})

0 commit comments

Comments
 (0)