mirror of
https://github.com/tenrok/vue-meta.git
synced 2026-06-18 05:20:34 +03:00
feat: major refactor, cleanup and jest tests
This commit is contained in:
@@ -0,0 +1,79 @@
|
||||
import _getMetaInfo from '../src/shared/getMetaInfo'
|
||||
import { mount, defaultOptions, loadVueMetaPlugin } from './utils'
|
||||
|
||||
import GoodbyeWorld from './fixtures/goodbye-world.vue'
|
||||
import HelloWorld from './fixtures/hello-world.vue'
|
||||
import KeepAlive from './fixtures/keep-alive.vue'
|
||||
|
||||
const getMetaInfo = component => _getMetaInfo(defaultOptions, component)
|
||||
|
||||
describe('client', () => {
|
||||
let Vue
|
||||
|
||||
beforeAll(() => (Vue = loadVueMetaPlugin()))
|
||||
|
||||
test('meta-info refreshed on component\'s data change', () => {
|
||||
const wrapper = mount(HelloWorld, { localVue: Vue })
|
||||
|
||||
let metaInfo = getMetaInfo(wrapper.vm)
|
||||
expect(metaInfo.title).toEqual('Hello World')
|
||||
wrapper.setData({ title: 'Goodbye World' })
|
||||
|
||||
metaInfo = getMetaInfo(wrapper.vm)
|
||||
expect(metaInfo.title).toEqual('Goodbye World')
|
||||
})
|
||||
|
||||
test('child meta-info removed when child is toggled', () => {
|
||||
const wrapper = mount(GoodbyeWorld, { localVue: Vue })
|
||||
|
||||
let metaInfo = getMetaInfo(wrapper.vm)
|
||||
expect(metaInfo.title).toEqual('Hello World')
|
||||
|
||||
wrapper.setData({ childVisible: false })
|
||||
|
||||
metaInfo = getMetaInfo(wrapper.vm)
|
||||
expect(metaInfo.title).toEqual('Goodbye World')
|
||||
|
||||
wrapper.setData({ childVisible: true })
|
||||
|
||||
metaInfo = getMetaInfo(wrapper.vm)
|
||||
expect(metaInfo.title).toEqual('Hello World')
|
||||
})
|
||||
|
||||
test('child meta-info removed when keep-alive child is toggled', () => {
|
||||
const wrapper = mount(KeepAlive, { localVue: Vue })
|
||||
|
||||
let metaInfo = getMetaInfo(wrapper.vm)
|
||||
expect(metaInfo.title).toEqual('Hello World')
|
||||
|
||||
wrapper.setData({ childVisible: false })
|
||||
|
||||
metaInfo = getMetaInfo(wrapper.vm)
|
||||
expect(metaInfo.title).toEqual('Alive World')
|
||||
|
||||
wrapper.setData({ childVisible: true })
|
||||
|
||||
metaInfo = getMetaInfo(wrapper.vm)
|
||||
expect(metaInfo.title).toEqual('Hello World')
|
||||
})
|
||||
|
||||
test('meta-info is removed when destroyed', () => {
|
||||
const parentComponent = new Vue({ render: h => h('div') })
|
||||
const wrapper = mount(HelloWorld, { localVue: Vue, parentComponent })
|
||||
|
||||
let metaInfo = getMetaInfo(wrapper.vm.$parent)
|
||||
expect(metaInfo.title).toEqual('Hello World')
|
||||
wrapper.destroy()
|
||||
|
||||
jest.runAllTimers()
|
||||
metaInfo = getMetaInfo(wrapper.vm.$parent)
|
||||
expect(metaInfo.title).toEqual('')
|
||||
})
|
||||
|
||||
test('meta-info can be rendered with inject', () => {
|
||||
const wrapper = mount(HelloWorld, { localVue: Vue })
|
||||
|
||||
const metaInfo = wrapper.vm.$meta().inject()
|
||||
expect(metaInfo.title.text()).toEqual('<title data-vue-meta="true">Hello World</title>')
|
||||
})
|
||||
})
|
||||
Vendored
+33
@@ -0,0 +1,33 @@
|
||||
<template>
|
||||
<html {{ head.headAttrs.text() }}>
|
||||
<head></head>
|
||||
bla
|
||||
</html>
|
||||
</template>
|
||||
|
||||
|
||||
<script>
|
||||
export default {
|
||||
metaInfo() {
|
||||
return {
|
||||
title: this.title
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
title: 'Hello World',
|
||||
htmlAttrs: {
|
||||
lang: 'en'
|
||||
},
|
||||
meta: [
|
||||
{ charset: 'utf-8' }
|
||||
]
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
head() {
|
||||
return meta.inject()
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
Vendored
+26
@@ -0,0 +1,26 @@
|
||||
<template>
|
||||
<div>
|
||||
<hello-world v-if="childVisible"></hello-world>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import HelloWorld from './hello-world.vue'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
HelloWorld
|
||||
},
|
||||
metaInfo() {
|
||||
return {
|
||||
title: this.title,
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
childVisible: true,
|
||||
title: 'Goodbye World'
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
Vendored
+24
@@ -0,0 +1,24 @@
|
||||
<template>
|
||||
<div>Test</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
metaInfo() {
|
||||
return {
|
||||
title: this.title
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
title: 'Hello World',
|
||||
htmlAttrs: {
|
||||
lang: 'en'
|
||||
},
|
||||
meta: [
|
||||
{ charset: 'utf-8' }
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
Vendored
+28
@@ -0,0 +1,28 @@
|
||||
<template>
|
||||
<div>
|
||||
<keep-alive>
|
||||
<hello-world v-if="childVisible"></hello-world>
|
||||
</keep-alive>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import HelloWorld from './hello-world.vue'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
HelloWorld
|
||||
},
|
||||
metaInfo() {
|
||||
return {
|
||||
title: this.title,
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
childVisible: true,
|
||||
title: 'Alive World'
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,61 @@
|
||||
import _generateServerInjector from '../src/server/generateServerInjector'
|
||||
import { defaultOptions } from './utils'
|
||||
import metaInfoData from './utils/meta-info-data'
|
||||
|
||||
const generateServerInjector = (type, data) => _generateServerInjector(defaultOptions, type, data)
|
||||
|
||||
describe('generators', () => {
|
||||
Object.keys(metaInfoData).forEach((type) => {
|
||||
const typeTests = metaInfoData[type]
|
||||
|
||||
const testCases = {
|
||||
add: (tags) => {
|
||||
let html = tags.text()
|
||||
|
||||
// ssr only returns the attributes, convert to full tag
|
||||
if (['htmlAttrs', 'headAttrs', 'bodyAttrs'].includes(type)) {
|
||||
html = `<${type.substr(0, 4)} ${html}>`
|
||||
}
|
||||
|
||||
typeTests.add.expect.forEach((expected) => {
|
||||
expect(html).toContain(expected)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
describe(`${type} type tests`, () => {
|
||||
Object.keys(typeTests).forEach((action) => {
|
||||
const testInfo = typeTests[action]
|
||||
|
||||
// return when no test case available
|
||||
if (!testCases[action] && !testInfo.test) {
|
||||
return
|
||||
}
|
||||
|
||||
const defaultTestFn = () => {
|
||||
const tags = generateServerInjector(type, testInfo.data)
|
||||
testCases[action](tags)
|
||||
return tags
|
||||
}
|
||||
|
||||
let testFn
|
||||
if (testInfo.test) {
|
||||
testFn = testInfo.test('server', defaultTestFn)
|
||||
|
||||
if (testFn === true) {
|
||||
testFn = defaultTestFn
|
||||
}
|
||||
} else {
|
||||
testFn = defaultTestFn
|
||||
}
|
||||
|
||||
if (testFn && typeof testFn === 'function') {
|
||||
test.only(`${action} a tag`, () => {
|
||||
expect.hasAssertions()
|
||||
testFn()
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -1,76 +0,0 @@
|
||||
import Vue from 'vue'
|
||||
import getComponentOption from '../src/shared/getComponentOption'
|
||||
|
||||
describe('getComponentOption', () => {
|
||||
const container = document.createElement('div')
|
||||
let component
|
||||
|
||||
afterEach(() => component.$destroy())
|
||||
|
||||
it('returns an empty object when no matching options are found', () => {
|
||||
component = new Vue()
|
||||
const mergedOption = getComponentOption({ component, option: 'noop' })
|
||||
expect(mergedOption).to.eql({})
|
||||
})
|
||||
|
||||
it('fetches the given option from the given component', () => {
|
||||
component = new Vue({ someOption: 'foo' })
|
||||
const mergedOption = getComponentOption({ component, option: 'someOption' })
|
||||
expect(mergedOption).to.eql('foo')
|
||||
})
|
||||
|
||||
it('calls a function option, injecting the component as context', () => {
|
||||
component = new Vue({
|
||||
name: 'foobar',
|
||||
someFunc () {
|
||||
return this.$options.name
|
||||
}
|
||||
})
|
||||
const mergedOption = getComponentOption({ component, option: 'someFunc' })
|
||||
expect(mergedOption).to.eql('foobar')
|
||||
})
|
||||
|
||||
it('fetches deeply nested component options and merges them', () => {
|
||||
Vue.component('merge-child', { template: '<div></div>', foo: { bar: 'baz' } })
|
||||
|
||||
component = new Vue({
|
||||
foo: { fizz: 'buzz' },
|
||||
render: (h) => h('div', null, [h('merge-child')]),
|
||||
el: container
|
||||
})
|
||||
|
||||
const mergedOption = getComponentOption({ component, option: 'foo', deep: true })
|
||||
expect(mergedOption).to.eql({ bar: 'baz', fizz: 'buzz' })
|
||||
})
|
||||
|
||||
it('allows for a custom array merge strategy', () => {
|
||||
Vue.component('array-child', {
|
||||
template: '<div></div>',
|
||||
foo: [
|
||||
{ name: 'flower', content: 'rose' }
|
||||
]
|
||||
})
|
||||
|
||||
component = new Vue({
|
||||
render: (h) => h('div', null, [h('array-child')]),
|
||||
foo: [
|
||||
{ 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([
|
||||
{ name: 'flower', content: 'tulip' },
|
||||
{ name: 'flower', content: 'rose' }
|
||||
])
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,76 @@
|
||||
import getComponentOption from '../src/shared/getComponentOption'
|
||||
import { getVue } from './utils'
|
||||
|
||||
describe('getComponentOption', () => {
|
||||
let Vue
|
||||
|
||||
beforeAll(() => (Vue = getVue()))
|
||||
|
||||
it('returns an empty object when no matching options are found', () => {
|
||||
const component = new Vue()
|
||||
const mergedOption = getComponentOption({ component, keyName: 'noop' })
|
||||
expect(mergedOption).toEqual({})
|
||||
})
|
||||
|
||||
it('fetches the given option from the given component', () => {
|
||||
const component = new Vue({ someOption: 'foo' })
|
||||
const mergedOption = getComponentOption({ component, keyName: 'someOption' })
|
||||
expect(mergedOption).toEqual('foo')
|
||||
})
|
||||
|
||||
it('calls a function option, injecting the component as context', () => {
|
||||
const component = new Vue({
|
||||
name: 'Foobar',
|
||||
someFunc() {
|
||||
return this.$options.name
|
||||
}
|
||||
})
|
||||
const mergedOption = getComponentOption({ component, keyName: 'someFunc' })
|
||||
// TODO: Should this be foobar or Foobar
|
||||
expect(mergedOption).toEqual('Foobar')
|
||||
})
|
||||
|
||||
it('fetches deeply nested component options and merges them', () => {
|
||||
Vue.component('merge-child', { render: h => h('div'), foo: { bar: 'baz' } })
|
||||
|
||||
const component = new Vue({
|
||||
foo: { fizz: 'buzz' },
|
||||
el: document.createElement('div'),
|
||||
render: h => h('div', null, [h('merge-child')])
|
||||
})
|
||||
|
||||
const mergedOption = getComponentOption({ component, keyName: 'foo', deep: true })
|
||||
expect(mergedOption).toEqual({ bar: 'baz', fizz: 'buzz' })
|
||||
})
|
||||
|
||||
it('allows for a custom array merge strategy', () => {
|
||||
Vue.component('array-child', {
|
||||
render: h => h('div'),
|
||||
foo: [
|
||||
{ name: 'flower', content: 'rose' }
|
||||
]
|
||||
})
|
||||
|
||||
const component = new Vue({
|
||||
foo: [
|
||||
{ name: 'flower', content: 'tulip' }
|
||||
],
|
||||
el: document.createElement('div'),
|
||||
render: h => h('div', null, [h('array-child')])
|
||||
})
|
||||
|
||||
const mergedOption = getComponentOption({
|
||||
component,
|
||||
keyName: 'foo',
|
||||
deep: true,
|
||||
arrayMerge(target, source) {
|
||||
return target.concat(source)
|
||||
}
|
||||
})
|
||||
|
||||
expect(mergedOption).toEqual([
|
||||
{ name: 'flower', content: 'tulip' },
|
||||
{ name: 'flower', content: 'rose' }
|
||||
])
|
||||
})
|
||||
})
|
||||
@@ -1,35 +1,17 @@
|
||||
import Vue from 'vue'
|
||||
import _getMetaInfo from '../src/shared/getMetaInfo'
|
||||
import {
|
||||
VUE_META_ATTRIBUTE,
|
||||
VUE_META_CONTENT_KEY,
|
||||
VUE_META_KEY_NAME,
|
||||
VUE_META_SERVER_RENDERED_ATTRIBUTE,
|
||||
VUE_META_TAG_LIST_ID_KEY_NAME,
|
||||
VUE_META_TEMPLATE_KEY_NAME
|
||||
} from '../src/shared/constants'
|
||||
import { defaultOptions, loadVueMetaPlugin } from './utils'
|
||||
|
||||
// set some default options
|
||||
const defaultOptions = {
|
||||
keyName: VUE_META_KEY_NAME,
|
||||
attribute: VUE_META_ATTRIBUTE,
|
||||
ssrAttribute: VUE_META_SERVER_RENDERED_ATTRIBUTE,
|
||||
metaTemplateKeyName: VUE_META_TEMPLATE_KEY_NAME,
|
||||
contentKeyName: VUE_META_CONTENT_KEY,
|
||||
tagIDKeyName: VUE_META_TAG_LIST_ID_KEY_NAME
|
||||
}
|
||||
|
||||
const getMetaInfo = _getMetaInfo(defaultOptions)
|
||||
const getMetaInfo = component => _getMetaInfo(defaultOptions, component)
|
||||
|
||||
describe('getMetaInfo', () => {
|
||||
// const container = document.createElement('div')
|
||||
let component
|
||||
let Vue
|
||||
|
||||
afterEach(() => component.$destroy())
|
||||
beforeAll(() => (Vue = loadVueMetaPlugin()))
|
||||
|
||||
it('returns appropriate defaults when no meta info is found', () => {
|
||||
component = new Vue()
|
||||
expect(getMetaInfo(component)).to.eql({
|
||||
test('returns appropriate defaults when no meta info is found', () => {
|
||||
const component = new Vue()
|
||||
|
||||
expect(getMetaInfo(component)).toEqual({
|
||||
title: '',
|
||||
titleChunk: '',
|
||||
titleTemplate: '%s',
|
||||
@@ -47,8 +29,8 @@ describe('getMetaInfo', () => {
|
||||
})
|
||||
})
|
||||
|
||||
it('returns metaInfo when used in component', () => {
|
||||
component = new Vue({
|
||||
test('returns metaInfo when used in component', () => {
|
||||
const component = new Vue({
|
||||
metaInfo: {
|
||||
title: 'Hello',
|
||||
meta: [
|
||||
@@ -56,7 +38,8 @@ describe('getMetaInfo', () => {
|
||||
]
|
||||
}
|
||||
})
|
||||
expect(getMetaInfo(component)).to.eql({
|
||||
|
||||
expect(getMetaInfo(component)).toEqual({
|
||||
title: 'Hello',
|
||||
titleChunk: 'Hello',
|
||||
titleTemplate: '%s',
|
||||
@@ -75,8 +58,37 @@ describe('getMetaInfo', () => {
|
||||
__dangerouslyDisableSanitizersByTagID: {}
|
||||
})
|
||||
})
|
||||
it('removes duplicate metaInfo in same component', () => {
|
||||
component = new Vue({
|
||||
|
||||
test('converts base tag to array', () => {
|
||||
const component = new Vue({
|
||||
metaInfo: {
|
||||
title: 'Hello',
|
||||
base: { href: 'href' }
|
||||
}
|
||||
})
|
||||
|
||||
expect(getMetaInfo(component)).toEqual({
|
||||
title: 'Hello',
|
||||
titleChunk: 'Hello',
|
||||
titleTemplate: '%s',
|
||||
htmlAttrs: {},
|
||||
headAttrs: {},
|
||||
bodyAttrs: {},
|
||||
meta: [],
|
||||
base: [
|
||||
{ href: 'href' }
|
||||
],
|
||||
link: [],
|
||||
style: [],
|
||||
script: [],
|
||||
noscript: [],
|
||||
__dangerouslyDisableSanitizers: [],
|
||||
__dangerouslyDisableSanitizersByTagID: {}
|
||||
})
|
||||
})
|
||||
|
||||
test('removes duplicate metaInfo in same component', () => {
|
||||
const component = new Vue({
|
||||
metaInfo: {
|
||||
title: 'Hello',
|
||||
meta: [
|
||||
@@ -93,7 +105,8 @@ describe('getMetaInfo', () => {
|
||||
]
|
||||
}
|
||||
})
|
||||
expect(getMetaInfo(component)).to.eql({
|
||||
|
||||
expect(getMetaInfo(component)).toEqual({
|
||||
title: 'Hello',
|
||||
titleChunk: 'Hello',
|
||||
titleTemplate: '%s',
|
||||
@@ -117,8 +130,8 @@ describe('getMetaInfo', () => {
|
||||
})
|
||||
})
|
||||
|
||||
it('properly uses string titleTemplates', () => {
|
||||
component = new Vue({
|
||||
test('properly uses string titleTemplates', () => {
|
||||
const component = new Vue({
|
||||
metaInfo: {
|
||||
title: 'Hello',
|
||||
titleTemplate: '%s World',
|
||||
@@ -127,7 +140,8 @@ describe('getMetaInfo', () => {
|
||||
]
|
||||
}
|
||||
})
|
||||
expect(getMetaInfo(component)).to.eql({
|
||||
|
||||
expect(getMetaInfo(component)).toEqual({
|
||||
title: 'Hello World',
|
||||
titleChunk: 'Hello',
|
||||
titleTemplate: '%s World',
|
||||
@@ -147,10 +161,10 @@ describe('getMetaInfo', () => {
|
||||
})
|
||||
})
|
||||
|
||||
it('properly uses function titleTemplates', () => {
|
||||
test('properly uses function titleTemplates', () => {
|
||||
const titleTemplate = chunk => `${chunk} Function World`
|
||||
|
||||
component = new Vue({
|
||||
const component = new Vue({
|
||||
metaInfo: {
|
||||
title: 'Hello',
|
||||
titleTemplate,
|
||||
@@ -159,7 +173,8 @@ describe('getMetaInfo', () => {
|
||||
]
|
||||
}
|
||||
})
|
||||
expect(getMetaInfo(component)).to.eql({
|
||||
|
||||
expect(getMetaInfo(component)).toEqual({
|
||||
title: 'Hello Function World',
|
||||
titleChunk: 'Hello',
|
||||
titleTemplate,
|
||||
@@ -179,12 +194,12 @@ describe('getMetaInfo', () => {
|
||||
})
|
||||
})
|
||||
|
||||
it('has the proper `this` binding when using function titleTemplates', () => {
|
||||
test('has the proper `this` binding when using function titleTemplates', () => {
|
||||
const titleTemplate = function (chunk) {
|
||||
return `${chunk} ${this.helloWorldText}`
|
||||
}
|
||||
|
||||
component = new Vue({
|
||||
const component = new Vue({
|
||||
metaInfo: {
|
||||
title: 'Hello',
|
||||
titleTemplate,
|
||||
@@ -192,13 +207,14 @@ describe('getMetaInfo', () => {
|
||||
{ charset: 'utf-8' }
|
||||
]
|
||||
},
|
||||
data () {
|
||||
data() {
|
||||
return {
|
||||
helloWorldText: 'Function World'
|
||||
}
|
||||
}
|
||||
})
|
||||
expect(getMetaInfo(component)).to.eql({
|
||||
|
||||
expect(getMetaInfo(component)).toEqual({
|
||||
title: 'Hello Function World',
|
||||
titleChunk: 'Hello',
|
||||
titleTemplate,
|
||||
@@ -218,8 +234,8 @@ describe('getMetaInfo', () => {
|
||||
})
|
||||
})
|
||||
|
||||
it('properly uses string meta templates', () => {
|
||||
component = new Vue({
|
||||
test('properly uses string meta templates', () => {
|
||||
const component = new Vue({
|
||||
metaInfo: {
|
||||
title: 'Hello',
|
||||
meta: [
|
||||
@@ -232,7 +248,8 @@ describe('getMetaInfo', () => {
|
||||
]
|
||||
}
|
||||
})
|
||||
expect(getMetaInfo(component)).to.eql({
|
||||
|
||||
expect(getMetaInfo(component)).toEqual({
|
||||
title: 'Hello',
|
||||
titleChunk: 'Hello',
|
||||
titleTemplate: '%s',
|
||||
@@ -256,8 +273,8 @@ describe('getMetaInfo', () => {
|
||||
})
|
||||
})
|
||||
|
||||
it('properly uses function meta templates', () => {
|
||||
component = new Vue({
|
||||
test('properly uses function meta templates', () => {
|
||||
const component = new Vue({
|
||||
metaInfo: {
|
||||
title: 'Hello',
|
||||
meta: [
|
||||
@@ -270,7 +287,8 @@ describe('getMetaInfo', () => {
|
||||
]
|
||||
}
|
||||
})
|
||||
expect(getMetaInfo(component)).to.eql({
|
||||
|
||||
expect(getMetaInfo(component)).toEqual({
|
||||
title: 'Hello',
|
||||
titleChunk: 'Hello',
|
||||
titleTemplate: '%s',
|
||||
@@ -294,8 +312,8 @@ describe('getMetaInfo', () => {
|
||||
})
|
||||
})
|
||||
|
||||
it('properly uses content only if template is not defined', () => {
|
||||
component = new Vue({
|
||||
test('properly uses content only if template is not defined', () => {
|
||||
const component = new Vue({
|
||||
metaInfo: {
|
||||
title: 'Hello',
|
||||
meta: [
|
||||
@@ -307,7 +325,8 @@ describe('getMetaInfo', () => {
|
||||
]
|
||||
}
|
||||
})
|
||||
expect(getMetaInfo(component)).to.eql({
|
||||
|
||||
expect(getMetaInfo(component)).toEqual({
|
||||
title: 'Hello',
|
||||
titleChunk: 'Hello',
|
||||
titleTemplate: '%s',
|
||||
@@ -331,8 +350,8 @@ describe('getMetaInfo', () => {
|
||||
})
|
||||
})
|
||||
|
||||
it('properly uses content only if template is null', () => {
|
||||
component = new Vue({
|
||||
test('properly uses content only if template is null', () => {
|
||||
const component = new Vue({
|
||||
metaInfo: {
|
||||
title: 'Hello',
|
||||
meta: [
|
||||
@@ -345,7 +364,8 @@ describe('getMetaInfo', () => {
|
||||
]
|
||||
}
|
||||
})
|
||||
expect(getMetaInfo(component)).to.eql({
|
||||
|
||||
expect(getMetaInfo(component)).toEqual({
|
||||
title: 'Hello',
|
||||
titleChunk: 'Hello',
|
||||
titleTemplate: '%s',
|
||||
@@ -369,8 +389,8 @@ describe('getMetaInfo', () => {
|
||||
})
|
||||
})
|
||||
|
||||
it('properly uses content only if template is false', () => {
|
||||
component = new Vue({
|
||||
test('properly uses content only if template is false', () => {
|
||||
const component = new Vue({
|
||||
metaInfo: {
|
||||
title: 'Hello',
|
||||
meta: [
|
||||
@@ -383,7 +403,8 @@ describe('getMetaInfo', () => {
|
||||
]
|
||||
}
|
||||
})
|
||||
expect(getMetaInfo(component)).to.eql({
|
||||
|
||||
expect(getMetaInfo(component)).toEqual({
|
||||
title: 'Hello',
|
||||
titleChunk: 'Hello',
|
||||
titleTemplate: '%s',
|
||||
@@ -407,9 +428,9 @@ describe('getMetaInfo', () => {
|
||||
})
|
||||
})
|
||||
|
||||
it('properly uses meta templates with one-level-deep nested children content', () => {
|
||||
test('properly uses meta templates with one-level-deep nested children content', () => {
|
||||
Vue.component('merge-child', {
|
||||
template: '<div></div>',
|
||||
render: h => h('div'),
|
||||
metaInfo: {
|
||||
title: 'Hello',
|
||||
meta: [
|
||||
@@ -422,7 +443,7 @@ describe('getMetaInfo', () => {
|
||||
}
|
||||
})
|
||||
|
||||
component = new Vue({
|
||||
const component = new Vue({
|
||||
metaInfo: {
|
||||
meta: [
|
||||
{
|
||||
@@ -433,11 +454,11 @@ describe('getMetaInfo', () => {
|
||||
}
|
||||
]
|
||||
},
|
||||
render: (h) => h('div', null, [h('merge-child')]),
|
||||
el: document.createElement('div')
|
||||
el: document.createElement('div'),
|
||||
render: h => h('div', null, [h('merge-child')])
|
||||
})
|
||||
|
||||
expect(getMetaInfo(component)).to.eql({
|
||||
expect(getMetaInfo(component)).toEqual({
|
||||
title: 'Hello',
|
||||
titleChunk: 'Hello',
|
||||
titleTemplate: '%s',
|
||||
@@ -463,9 +484,9 @@ describe('getMetaInfo', () => {
|
||||
|
||||
// TODO: Still failing :( Child template won't be applied if child has no content as well
|
||||
|
||||
it('properly uses meta templates with one-level-deep nested children template', () => {
|
||||
test('properly uses meta templates with one-level-deep nested children template', () => {
|
||||
Vue.component('merge-child', {
|
||||
template: '<div></div>',
|
||||
render: h => h('div'),
|
||||
metaInfo: {
|
||||
title: 'Hello',
|
||||
meta: [
|
||||
@@ -478,7 +499,7 @@ describe('getMetaInfo', () => {
|
||||
}
|
||||
})
|
||||
|
||||
component = new Vue({
|
||||
const component = new Vue({
|
||||
metaInfo: {
|
||||
meta: [
|
||||
{
|
||||
@@ -489,11 +510,11 @@ describe('getMetaInfo', () => {
|
||||
}
|
||||
]
|
||||
},
|
||||
render: (h) => h('div', null, [h('merge-child')]),
|
||||
el: document.createElement('div')
|
||||
el: document.createElement('div'),
|
||||
render: h => h('div', null, [h('merge-child')])
|
||||
})
|
||||
|
||||
expect(getMetaInfo(component)).to.eql({
|
||||
expect(getMetaInfo(component)).toEqual({
|
||||
title: 'Hello',
|
||||
titleChunk: 'Hello',
|
||||
titleTemplate: '%s',
|
||||
@@ -517,9 +538,9 @@ describe('getMetaInfo', () => {
|
||||
})
|
||||
})
|
||||
|
||||
it('properly uses meta templates with one-level-deep nested children template and content', () => {
|
||||
test('properly uses meta templates with one-level-deep nested children template and content', () => {
|
||||
Vue.component('merge-child', {
|
||||
template: '<div></div>',
|
||||
render: h => h('div'),
|
||||
metaInfo: {
|
||||
title: 'Hello',
|
||||
meta: [
|
||||
@@ -533,7 +554,7 @@ describe('getMetaInfo', () => {
|
||||
}
|
||||
})
|
||||
|
||||
component = new Vue({
|
||||
const component = new Vue({
|
||||
metaInfo: {
|
||||
meta: [
|
||||
{
|
||||
@@ -544,11 +565,11 @@ describe('getMetaInfo', () => {
|
||||
}
|
||||
]
|
||||
},
|
||||
render: (h) => h('div', null, [h('merge-child')]),
|
||||
el: document.createElement('div')
|
||||
el: document.createElement('div'),
|
||||
render: h => h('div', null, [h('merge-child')])
|
||||
})
|
||||
|
||||
expect(getMetaInfo(component)).to.eql({
|
||||
expect(getMetaInfo(component)).toEqual({
|
||||
title: 'Hello',
|
||||
titleChunk: 'Hello',
|
||||
titleTemplate: '%s',
|
||||
@@ -1,4 +0,0 @@
|
||||
const testsContext = require.context('.', true, /\.spec$/)
|
||||
const srcContext = require.context('../src', true, /\.js$/)
|
||||
testsContext.keys().forEach(testsContext)
|
||||
srcContext.keys().forEach(srcContext)
|
||||
@@ -1,33 +0,0 @@
|
||||
import Vue from 'vue'
|
||||
import VueMeta from '../src/shared/plugin'
|
||||
import {
|
||||
VUE_META_KEY_NAME,
|
||||
VUE_META_ATTRIBUTE,
|
||||
VUE_META_SERVER_RENDERED_ATTRIBUTE,
|
||||
VUE_META_TAG_LIST_ID_KEY_NAME
|
||||
} from '../src/shared/constants'
|
||||
|
||||
describe('plugin', () => {
|
||||
Vue.use(VueMeta, {
|
||||
keyName: VUE_META_KEY_NAME,
|
||||
attribute: VUE_META_ATTRIBUTE,
|
||||
ssrAttribute: VUE_META_SERVER_RENDERED_ATTRIBUTE,
|
||||
tagIDKeyName: VUE_META_TAG_LIST_ID_KEY_NAME
|
||||
})
|
||||
|
||||
it('adds $meta() to Vue prototype', () => {
|
||||
const instance = new Vue()
|
||||
expect(instance.$meta).to.be.a('function')
|
||||
})
|
||||
|
||||
it('components have _hasMetaInfo set to true', () => {
|
||||
const Component = Vue.component('test-component', {
|
||||
template: '<div>Test</div>',
|
||||
[VUE_META_KEY_NAME]: {
|
||||
title: 'helloworld'
|
||||
}
|
||||
})
|
||||
const vm = new Vue(Component).$mount()
|
||||
expect(vm._hasMetaInfo).to.equal(true)
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,32 @@
|
||||
import { mount, defaultOptions, VueMetaPlugin, loadVueMetaPlugin } from './utils'
|
||||
|
||||
jest.mock('../package.json', () => ({
|
||||
version: 'test-version'
|
||||
}))
|
||||
|
||||
describe('plugin', () => {
|
||||
let Vue
|
||||
|
||||
beforeAll(() => (Vue = loadVueMetaPlugin()))
|
||||
|
||||
test('is loaded', () => {
|
||||
const instance = new Vue()
|
||||
expect(instance.$meta).toEqual(expect.any(Function))
|
||||
})
|
||||
|
||||
test('component has _hasMetaInfo set to true', () => {
|
||||
const Component = Vue.component('test-component', {
|
||||
template: '<div>Test</div>',
|
||||
[defaultOptions.keyName]: {
|
||||
title: 'Hello World'
|
||||
}
|
||||
})
|
||||
|
||||
const { vm } = mount(Component, { localVue: Vue })
|
||||
expect(vm._hasMetaInfo).toBe(true)
|
||||
})
|
||||
|
||||
test('plugin sets package version', () => {
|
||||
expect(VueMetaPlugin.version).toBe('test-version')
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,97 @@
|
||||
import _updateClientMetaInfo from '../src/client/updateClientMetaInfo'
|
||||
import { defaultOptions } from './utils'
|
||||
import metaInfoData from './utils/meta-info-data'
|
||||
|
||||
const updateClientMetaInfo = (type, data) => _updateClientMetaInfo(defaultOptions, { [type]: data })
|
||||
|
||||
describe('updaters', () => {
|
||||
let html
|
||||
|
||||
beforeAll(() => {
|
||||
html = document.getElementsByTagName('html')[0]
|
||||
|
||||
// remove default meta charset
|
||||
Array.from(html.getElementsByTagName('meta')).forEach(el => el.parentNode.removeChild(el))
|
||||
})
|
||||
|
||||
Object.keys(metaInfoData).forEach((type) => {
|
||||
const typeTests = metaInfoData[type]
|
||||
|
||||
const testCases = {
|
||||
add: (tags) => {
|
||||
typeTests.add.expect.forEach((expected, index) => {
|
||||
if (!['title', 'htmlAttrs', 'headAttrs', 'bodyAttrs'].includes(type)) {
|
||||
expect(tags.addedTags[type][index].outerHTML).toBe(expected)
|
||||
}
|
||||
expect(html.outerHTML).toContain(expected)
|
||||
})
|
||||
},
|
||||
change: (tags) => {
|
||||
typeTests.add.expect.forEach((expected, index) => {
|
||||
if (!typeTests.change.expect.includes(expected)) {
|
||||
expect(html.outerHTML).not.toContain(expected)
|
||||
}
|
||||
})
|
||||
|
||||
typeTests.change.expect.forEach((expected, index) => {
|
||||
if (!['title', 'htmlAttrs', 'headAttrs', 'bodyAttrs'].includes(type)) {
|
||||
expect(tags.addedTags[type][index].outerHTML).toBe(expected)
|
||||
}
|
||||
expect(html.outerHTML).toContain(expected)
|
||||
})
|
||||
},
|
||||
remove: (tags) => {
|
||||
// TODO: i'd expect tags.removedTags to be populated
|
||||
|
||||
typeTests.add.expect.forEach((expected, index) => {
|
||||
expect(html.outerHTML).not.toContain(expected)
|
||||
})
|
||||
|
||||
typeTests.change.expect.forEach((expected, index) => {
|
||||
expect(html.outerHTML).not.toContain(expected)
|
||||
})
|
||||
|
||||
expect(html.outerHTML).not.toContain(`<${type}`)
|
||||
}
|
||||
}
|
||||
|
||||
describe(`${type} type tests`, () => {
|
||||
Object.keys(typeTests).forEach((action) => {
|
||||
const testInfo = typeTests[action]
|
||||
|
||||
// return when no test case available
|
||||
if (!testCases[action] && !testInfo.test) {
|
||||
return
|
||||
}
|
||||
|
||||
const defaultTestFn = () => {
|
||||
const tags = updateClientMetaInfo(type, testInfo.data)
|
||||
|
||||
if (testCases[action]) {
|
||||
testCases[action](tags)
|
||||
}
|
||||
|
||||
return tags
|
||||
}
|
||||
|
||||
let testFn
|
||||
if (testInfo.test) {
|
||||
testFn = testInfo.test('client', defaultTestFn)
|
||||
|
||||
if (testFn === true) {
|
||||
testFn = defaultTestFn
|
||||
}
|
||||
} else {
|
||||
testFn = defaultTestFn
|
||||
}
|
||||
|
||||
if (testFn && typeof testFn === 'function') {
|
||||
test.only(`${action} a tag`, () => {
|
||||
expect.hasAssertions()
|
||||
testFn()
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,37 @@
|
||||
import { mount, createLocalVue } from '@vue/test-utils'
|
||||
import { renderToString } from '@vue/server-test-utils'
|
||||
import VueMetaPlugin from '../../src'
|
||||
|
||||
import {
|
||||
VUE_META_ATTRIBUTE,
|
||||
VUE_META_CONTENT_KEY,
|
||||
VUE_META_KEY_NAME,
|
||||
VUE_META_SERVER_RENDERED_ATTRIBUTE,
|
||||
VUE_META_TAG_LIST_ID_KEY_NAME,
|
||||
VUE_META_TEMPLATE_KEY_NAME
|
||||
} from '../../src/shared/constants'
|
||||
|
||||
export {
|
||||
mount,
|
||||
renderToString,
|
||||
VueMetaPlugin
|
||||
}
|
||||
|
||||
export const defaultOptions = {
|
||||
keyName: VUE_META_KEY_NAME,
|
||||
attribute: VUE_META_ATTRIBUTE,
|
||||
ssrAttribute: VUE_META_SERVER_RENDERED_ATTRIBUTE,
|
||||
metaTemplateKeyName: VUE_META_TEMPLATE_KEY_NAME,
|
||||
contentKeyName: VUE_META_CONTENT_KEY,
|
||||
tagIDKeyName: VUE_META_TAG_LIST_ID_KEY_NAME
|
||||
}
|
||||
|
||||
export function getVue() {
|
||||
return createLocalVue()
|
||||
}
|
||||
|
||||
export function loadVueMetaPlugin(options, localVue = getVue()) {
|
||||
localVue.use(VueMetaPlugin, Object.assign({}, defaultOptions, options))
|
||||
|
||||
return localVue
|
||||
}
|
||||
@@ -0,0 +1,216 @@
|
||||
import { defaultOptions } from './'
|
||||
|
||||
const metaInfoData = {
|
||||
title: {
|
||||
add: {
|
||||
data: 'Hello World',
|
||||
expect: ['<title data-vue-meta="true">Hello World</title>'],
|
||||
test(side, defaultTest) {
|
||||
if (side === 'client') {
|
||||
// client side vue-meta uses document.title and doesnt change the html
|
||||
return () => {
|
||||
this.expect[0] = '<title>Hello World</title>'
|
||||
const spy = jest.spyOn(document, 'title', 'set')
|
||||
|
||||
defaultTest()
|
||||
|
||||
expect(spy).toHaveBeenCalledTimes(1)
|
||||
expect(spy).toHaveBeenCalledWith('Hello World')
|
||||
}
|
||||
} else {
|
||||
return defaultTest
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
base: {
|
||||
add: {
|
||||
data: [{ href: 'href' }],
|
||||
expect: ['<base data-vue-meta="true" href="href">']
|
||||
},
|
||||
change: {
|
||||
data: [{ href: 'href2' }],
|
||||
expect: ['<base data-vue-meta="true" href="href2">']
|
||||
},
|
||||
remove: {
|
||||
data: [],
|
||||
expect: ['']
|
||||
}
|
||||
},
|
||||
meta: {
|
||||
add: {
|
||||
data: [{ charset: 'utf-8' }, { property: 'a', content: 'a' }],
|
||||
expect: [
|
||||
'<meta data-vue-meta="true" charset="utf-8">',
|
||||
'<meta data-vue-meta="true" property="a" content="a">'
|
||||
]
|
||||
},
|
||||
change: {
|
||||
data: [
|
||||
{ charset: 'utf-16' },
|
||||
{ property: 'a', content: 'b' }
|
||||
],
|
||||
expect: [
|
||||
'<meta data-vue-meta="true" charset="utf-16">',
|
||||
'<meta data-vue-meta="true" property="a" content="b">'
|
||||
]
|
||||
},
|
||||
// make sure elements that already exists are not unnecessarily updated
|
||||
duplicate: {
|
||||
data: [
|
||||
{ charset: 'utf-16' },
|
||||
{ property: 'a', content: 'c' }
|
||||
],
|
||||
expect: [
|
||||
'<meta data-vue-meta="true" charset="utf-16">',
|
||||
'<meta data-vue-meta="true" property="a" content="c">'
|
||||
],
|
||||
test(side, defaultTest) {
|
||||
if (side === 'client') {
|
||||
return () => {
|
||||
const tags = defaultTest()
|
||||
|
||||
expect(tags.addedTags.meta.length).toBe(1)
|
||||
// TODO: not sure if we really expect this
|
||||
expect(tags.removedTags.meta.length).toBe(1)
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
remove: {
|
||||
data: [],
|
||||
expect: ['']
|
||||
}
|
||||
},
|
||||
link: {
|
||||
add: {
|
||||
data: [{ rel: 'stylesheet', href: 'href' }],
|
||||
expect: ['<link data-vue-meta="true" rel="stylesheet" href="href">']
|
||||
},
|
||||
change: {
|
||||
data: [{ rel: 'stylesheet', href: 'href', media: 'screen' }],
|
||||
expect: ['<link data-vue-meta="true" rel="stylesheet" href="href" media="screen">']
|
||||
},
|
||||
remove: {
|
||||
data: [],
|
||||
expect: ['']
|
||||
}
|
||||
},
|
||||
style: {
|
||||
add: {
|
||||
data: [{ type: 'text/css', cssText: '.foo { color: red; }' }],
|
||||
expect: ['<style data-vue-meta="true" type="text/css">.foo { color: red; }</style>']
|
||||
},
|
||||
change: {
|
||||
data: [{ type: 'text/css', cssText: '.foo { color: blue; }' }],
|
||||
expect: ['<style data-vue-meta="true" type="text/css">.foo { color: blue; }</style>']
|
||||
},
|
||||
remove: {
|
||||
data: [],
|
||||
expect: ['']
|
||||
}
|
||||
},
|
||||
script: {
|
||||
add: {
|
||||
data: [
|
||||
{ src: 'src', async: true, defer: true, [defaultOptions.tagIDKeyName]: 'content' },
|
||||
{ src: 'src', async: true, defer: true, body: true }
|
||||
],
|
||||
expect: [
|
||||
'<script data-vue-meta="true" src="src" async="true" defer="true" data-vmid="content"></script>',
|
||||
'<script data-vue-meta="true" src="src" async="true" defer="true" data-body="true"></script>'
|
||||
],
|
||||
test(side, defaultTest) {
|
||||
return () => {
|
||||
if (side === 'client') {
|
||||
const tags = defaultTest()
|
||||
|
||||
expect(tags.addedTags.script[0].parentNode.tagName).toBe('HEAD')
|
||||
expect(tags.addedTags.script[1].parentNode.tagName).toBe('BODY')
|
||||
} else {
|
||||
// ssr doesnt generate data-body tags
|
||||
const bodyScript = this.expect[1]
|
||||
this.expect = [this.expect[0]]
|
||||
|
||||
const tags = defaultTest()
|
||||
|
||||
expect(tags.text()).not.toContain(bodyScript)
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
change: {
|
||||
data: [{ src: 'src', async: true, defer: true, [defaultOptions.tagIDKeyName]: 'content2' }],
|
||||
expect: ['<script data-vue-meta="true" src="src" async="true" defer="true" data-vmid="content2"></script>']
|
||||
},
|
||||
remove: {
|
||||
data: [],
|
||||
expect: ['']
|
||||
}
|
||||
},
|
||||
noscript: {
|
||||
add: {
|
||||
data: [{ innerHTML: '<p>noscript</p>' }],
|
||||
expect: ['<noscript data-vue-meta="true"><p>noscript</p></noscript>']
|
||||
},
|
||||
change: {
|
||||
data: [{ innerHTML: '<p>noscript, no really</p>' }],
|
||||
expect: ['<noscript data-vue-meta="true"><p>noscript, no really</p></noscript>']
|
||||
},
|
||||
remove: {
|
||||
data: [],
|
||||
expect: ['']
|
||||
}
|
||||
},
|
||||
htmlAttrs: {
|
||||
add: {
|
||||
data: { foo: 'bar' },
|
||||
expect: ['<html foo="bar" data-vue-meta="foo">']
|
||||
},
|
||||
change: {
|
||||
data: { foo: 'baz' },
|
||||
expect: ['<html foo="baz" data-vue-meta="foo">']
|
||||
},
|
||||
remove: {
|
||||
data: {},
|
||||
expect: ['<html>']
|
||||
}
|
||||
},
|
||||
headAttrs: {
|
||||
add: {
|
||||
data: { foo: 'bar' },
|
||||
expect: ['<head foo="bar" data-vue-meta="foo">']
|
||||
},
|
||||
change: {
|
||||
data: { foo: 'baz' },
|
||||
expect: ['<head foo="baz" data-vue-meta="foo">']
|
||||
},
|
||||
remove: {
|
||||
data: {},
|
||||
expect: ['<head>']
|
||||
}
|
||||
},
|
||||
bodyAttrs: {
|
||||
add: {
|
||||
data: { foo: 'bar' },
|
||||
expect: ['<body foo="bar" data-vue-meta="foo">']
|
||||
},
|
||||
change: {
|
||||
data: { foo: 'baz' },
|
||||
expect: ['<body foo="baz" data-vue-meta="foo">']
|
||||
},
|
||||
remove: {
|
||||
data: {},
|
||||
expect: ['<body>']
|
||||
}
|
||||
},
|
||||
empty: {
|
||||
add: {
|
||||
data: [{}],
|
||||
expect: [''],
|
||||
test: side => side === 'server'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default metaInfoData
|
||||
@@ -0,0 +1,5 @@
|
||||
import jsdom from 'jsdom-global'
|
||||
|
||||
jsdom()
|
||||
|
||||
jest.useFakeTimers()
|
||||
Reference in New Issue
Block a user