Skip to content

Commit 5f8025e

Browse files
pimlieTheAlexLichter
authored andcommitted
test: increase coverage, add missing tests
fix: issues discovered by adding missing tests
1 parent ce7eaf5 commit 5f8025e

18 files changed

+488
-106
lines changed

jest.config.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
module.exports = {
2-
testEnvironment: 'node',
2+
testEnvironment: 'jest-environment-jsdom-global',
33

44
expand: true,
55

package.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -80,8 +80,9 @@
8080
"eslint-plugin-vue": "^5.2.2",
8181
"esm": "^3.2.5",
8282
"jest": "^24.1.0",
83+
"jest-environment-jsdom": "^24.3.1",
84+
"jest-environment-jsdom-global": "^1.1.1",
8385
"jsdom": "^13.2.0",
84-
"jsdom-global": "^3.0.2",
8586
"rimraf": "^2.6.3",
8687
"rollup": "^1.2.2",
8788
"rollup-plugin-babel": "^4.3.2",

src/client/updateClientMetaInfo.js

+41-42
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { metaInfoOptionKeys, metaInfoAttributeKeys } from '../shared/constants'
22
import isArray from '../shared/isArray'
33
import { updateAttribute, updateTag, updateTitle } from './updaters'
44

5-
const getTag = (tags, tag) => {
5+
function getTag(tags, tag) {
66
if (!tags[tag]) {
77
tags[tag] = document.getElementsByTagName(tag)[0]
88
}
@@ -23,54 +23,53 @@ export default function updateClientMetaInfo(options = {}, newInfo) {
2323

2424
const htmlTag = getTag(tags, 'html')
2525

26-
// if this is not a server render, then update
27-
if (htmlTag.getAttribute(ssrAttribute) === null) {
28-
// initialize tracked changes
29-
const addedTags = {}
30-
const removedTags = {}
31-
32-
for (const type in newInfo) {
33-
// ignore these
34-
if (metaInfoOptionKeys.includes(type)) {
35-
continue
36-
}
26+
// if this is a server render, then dont update
27+
if (htmlTag.getAttribute(ssrAttribute)) {
28+
// remove the server render attribute so we can update on (next) changes
29+
htmlTag.removeAttribute(ssrAttribute)
30+
return false
31+
}
3732

38-
if (type === 'title') {
39-
// update the title
40-
updateTitle(newInfo.title)
41-
continue
42-
}
33+
// initialize tracked changes
34+
const addedTags = {}
35+
const removedTags = {}
4336

44-
if (metaInfoAttributeKeys.includes(type)) {
45-
const tagName = type.substr(0, 4)
46-
updateAttribute(options, newInfo[type], getTag(tags, tagName))
47-
continue
48-
}
37+
for (const type in newInfo) {
38+
// ignore these
39+
if (metaInfoOptionKeys.includes(type)) {
40+
continue
41+
}
4942

50-
// tags should always be an array, ignore if it isnt
51-
if (!isArray(newInfo[type])) {
52-
continue
53-
}
43+
if (type === 'title') {
44+
// update the title
45+
updateTitle(newInfo.title)
46+
continue
47+
}
5448

55-
const { oldTags, newTags } = updateTag(
56-
options,
57-
type,
58-
newInfo[type],
59-
getTag(tags, 'head'),
60-
getTag(tags, 'body')
61-
)
49+
if (metaInfoAttributeKeys.includes(type)) {
50+
const tagName = type.substr(0, 4)
51+
updateAttribute(options, newInfo[type], getTag(tags, tagName))
52+
continue
53+
}
6254

63-
if (newTags.length) {
64-
addedTags[type] = newTags
65-
removedTags[type] = oldTags
66-
}
55+
// tags should always be an array, ignore if it isnt
56+
if (!isArray(newInfo[type])) {
57+
continue
6758
}
6859

69-
return { addedTags, removedTags }
70-
} else {
71-
// remove the server render attribute so we can update on changes
72-
htmlTag.removeAttribute(ssrAttribute)
60+
const { oldTags, newTags } = updateTag(
61+
options,
62+
type,
63+
newInfo[type],
64+
getTag(tags, 'head'),
65+
getTag(tags, 'body')
66+
)
67+
68+
if (newTags.length) {
69+
addedTags[type] = newTags
70+
removedTags[type] = oldTags
71+
}
7372
}
7473

75-
return false
74+
return { addedTags, removedTags }
7675
}

src/shared/getMetaInfo.js

-4
Original file line numberDiff line numberDiff line change
@@ -38,10 +38,6 @@ export default function getMetaInfo(options = {}, component, escapeSequences = [
3838
}
3939

4040
disableOptionKeys.forEach((disableKey, index) => {
41-
if (!info[disableKey]) {
42-
return
43-
}
44-
4541
if (index === 0) {
4642
ensureIsArray(info, disableKey)
4743
} else if (index === 1) {

src/shared/merge.js

+18-10
Original file line numberDiff line numberDiff line change
@@ -18,24 +18,32 @@ export function arrayMerge({ component, tagIDKeyName, metaTemplateKeyName, conte
1818
const sourceIndex = source.findIndex(item => item[tagIDKeyName] === targetItem[tagIDKeyName])
1919
const sourceItem = source[sourceIndex]
2020

21-
// source doesnt contain any duplicate id's
22-
// or the source item should be ignored
23-
if (sourceIndex === -1 ||
24-
(sourceItem.hasOwnProperty(contentKeyName) && sourceItem[contentKeyName] === undefined) ||
25-
(sourceItem.hasOwnProperty('innerHTML') && sourceItem.innerHTML === undefined)
26-
) {
21+
// source doesnt contain any duplicate vmid's, we can keep targetItem
22+
if (sourceIndex === -1) {
2723
destination.push(targetItem)
2824
return
2925
}
3026

27+
// when sourceItem explictly defines contentKeyName or innerHTML as undefined, its
28+
// an indication that we need to skip the default behaviour
29+
// So we keep the targetItem and ignore/remove the sourceItem
30+
if ((sourceItem.hasOwnProperty(contentKeyName) && sourceItem[contentKeyName] === undefined) ||
31+
(sourceItem.hasOwnProperty('innerHTML') && sourceItem.innerHTML === undefined)) {
32+
destination.push(targetItem)
33+
// remove current index from source array so its not concatenated to destination below
34+
source.splice(sourceIndex, 1)
35+
return
36+
}
37+
38+
// we now know that targetItem is a duplicate and we should ignore it in favor of sourceItem
39+
3140
// if source specifies null as content then ignore both the target as the source
3241
if (sourceItem[contentKeyName] === null || sourceItem.innerHTML === null) {
3342
// remove current index from source array so its not concatenated to destination below
3443
source.splice(sourceIndex, 1)
3544
return
3645
}
3746

38-
// we now know that targetItem is a duplicate and we should ignore it in favor of sourceItem
3947
// now we only need to check if the target has a template to combine it with the source
4048
const targetTemplate = targetItem[metaTemplateKeyName]
4149
if (!targetTemplate) {
@@ -64,17 +72,17 @@ export function merge(target, source, options = {}) {
6472
delete source.title
6573
}
6674

67-
for (const attrKey in metaInfoAttributeKeys) {
75+
metaInfoAttributeKeys.forEach((attrKey) => {
6876
if (!source[attrKey]) {
69-
continue
77+
return
7078
}
7179

7280
for (const key in source[attrKey]) {
7381
if (source[attrKey].hasOwnProperty(key) && source[attrKey][key] === undefined) {
7482
delete source[attrKey][key]
7583
}
7684
}
77-
}
85+
})
7886

7987
return deepmerge(target, source, {
8088
arrayMerge: (t, s) => arrayMerge(options, t, s)

src/shared/mixin.js

+2
Original file line numberDiff line numberDiff line change
@@ -114,12 +114,14 @@ export default function createMixin(Vue, options) {
114114
// Wait that element is hidden before refreshing meta tags (to support animations)
115115
const interval = setInterval(() => {
116116
if (this.$el && this.$el.offsetParent !== null) {
117+
/* istanbul ignore next line */
117118
return
118119
}
119120

120121
clearInterval(interval)
121122

122123
if (!this.$parent) {
124+
/* istanbul ignore next line */
123125
return
124126
}
125127

test/components.test.js

+67-6
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import _getMetaInfo from '../src/shared/getMetaInfo'
2-
import { mount, defaultOptions, loadVueMetaPlugin } from './utils'
2+
import { mount, loadVueMetaPlugin, vmTick } from './utils'
3+
import { defaultOptions } from './utils/constants'
34

45
import GoodbyeWorld from './fixtures/goodbye-world.vue'
56
import HelloWorld from './fixtures/hello-world.vue'
@@ -8,10 +9,31 @@ import Changed from './fixtures/changed.vue'
89

910
const getMetaInfo = component => _getMetaInfo(defaultOptions, component)
1011

12+
jest.mock('../src/shared/window', () => ({
13+
hasGlobalWindow: false
14+
}))
15+
1116
describe('client', () => {
1217
let Vue
18+
let html
19+
20+
beforeAll(() => {
21+
Vue = loadVueMetaPlugin()
22+
23+
// force using timers, jest cant mock rAF
24+
delete window.requestAnimationFrame
25+
delete window.cancelAnimationFrame
1326

14-
beforeAll(() => (Vue = loadVueMetaPlugin()))
27+
html = document.createElement('html')
28+
document._getElementsByTagName = document.getElementsByTagName
29+
jest.spyOn(document, 'getElementsByTagName').mockImplementation((tag) => {
30+
if (tag === 'html') {
31+
return [html]
32+
}
33+
34+
return document._getElementsByTagName(tag)
35+
})
36+
})
1537

1638
test('meta-info refreshed on component\'s data change', () => {
1739
const wrapper = mount(HelloWorld, { localVue: Vue })
@@ -78,20 +100,59 @@ describe('client', () => {
78100
expect(metaInfo.title.text()).toEqual('<title data-vue-meta="true">Hello World</title>')
79101
})
80102

81-
test('changed function is called', () => {
103+
test('doesnt update when ssr attribute is set', () => {
104+
html.setAttribute(defaultOptions.ssrAttribute, 'true')
105+
const wrapper = mount(HelloWorld, { localVue: Vue })
106+
107+
const { tags } = wrapper.vm.$meta().refresh()
108+
expect(tags).toBe(false)
109+
})
110+
111+
test('changed function is called', async () => {
82112
const parentComponent = new Vue({ render: h => h('div') })
83113
const wrapper = mount(Changed, { localVue: Vue, parentComponent })
84114

115+
await vmTick(wrapper.vm)
116+
expect(wrapper.vm.$root._vueMeta.initialized).toBe(true)
117+
85118
let context
86119
const changed = jest.fn(function () {
87120
context = this
88121
})
89-
wrapper.setData({ changed })
90-
wrapper.setData({ childVisible: true })
122+
wrapper.setData({ changed, childVisible: true })
123+
jest.runAllTimers()
91124

92-
wrapper.vm.$parent.$meta().refresh()
93125
expect(changed).toHaveBeenCalledTimes(1)
94126
// TODO: this isnt what the docs say
95127
expect(context._uid).not.toBe(wrapper.vm._uid)
96128
})
129+
130+
test('afterNavigation function is called', () => {
131+
const Vue = loadVueMetaPlugin(false, { refreshOnceOnNavigation: true })
132+
const afterNavigation = jest.fn()
133+
const component = Vue.component('nav-component', {
134+
render: h => h('div'),
135+
metaInfo: { afterNavigation }
136+
})
137+
138+
const guards = {}
139+
Vue.prototype.$router = {
140+
beforeEach(fn) {
141+
guards.before = fn
142+
},
143+
afterEach(fn) {
144+
guards.after = fn
145+
}
146+
}
147+
const wrapper = mount(component, { localVue: Vue })
148+
149+
expect(guards.before).toBeDefined()
150+
expect(guards.after).toBeDefined()
151+
152+
guards.before(null, null, () => {})
153+
expect(wrapper.vm.$root._vueMeta.paused).toBe(true)
154+
155+
guards.after()
156+
expect(afterNavigation).toHaveBeenCalled()
157+
})
97158
})

test/escaping.test.js

+6-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import _getMetaInfo from '../src/shared/getMetaInfo'
2-
import { defaultOptions, loadVueMetaPlugin } from './utils'
2+
import { loadVueMetaPlugin } from './utils'
3+
import { defaultOptions } from './utils/constants'
34

45
const getMetaInfo = (component, escapeSequences) => _getMetaInfo(defaultOptions, component, escapeSequences)
56

@@ -11,6 +12,7 @@ describe('escaping', () => {
1112
test('special chars are escaped unless disabled', () => {
1213
const component = new Vue({
1314
metaInfo: {
15+
htmlAttrs: { key: 1 },
1416
title: 'Hello & Goodbye',
1517
script: [{ innerHTML: 'Hello & Goodbye' }],
1618
__dangerouslyDisableSanitizers: ['script']
@@ -21,7 +23,9 @@ describe('escaping', () => {
2123
title: 'Hello &amp; Goodbye',
2224
titleChunk: 'Hello & Goodbye',
2325
titleTemplate: '%s',
24-
htmlAttrs: {},
26+
htmlAttrs: {
27+
key: 1
28+
},
2529
headAttrs: {},
2630
bodyAttrs: {},
2731
meta: [],

test/generators.test.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import _generateServerInjector from '../src/server/generateServerInjector'
2-
import { defaultOptions } from './utils'
2+
import { defaultOptions } from './utils/constants'
33
import metaInfoData from './utils/meta-info-data'
44

55
const generateServerInjector = (type, data) => _generateServerInjector(defaultOptions, type, data)

0 commit comments

Comments
 (0)