mirror of
https://github.com/tenrok/vue-meta.git
synced 2026-05-17 04:29:37 +03:00
chore: remove old tests
This commit is contained in:
@@ -1,30 +0,0 @@
|
||||
<template>
|
||||
<div>
|
||||
<hello-world v-if="childVisible"></hello-world>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import HelloWorld from './hello-world.vue'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
HelloWorld
|
||||
},
|
||||
props: {
|
||||
changed: {
|
||||
type: Function
|
||||
}
|
||||
},
|
||||
metaInfo() {
|
||||
return {
|
||||
changed: this.changed.bind(this)
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
childVisible: false
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -1,26 +0,0 @@
|
||||
<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>
|
||||
@@ -1,24 +0,0 @@
|
||||
<template>
|
||||
<div>Test</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
metaInfo() {
|
||||
return {
|
||||
title: this.title,
|
||||
htmlAttrs: {
|
||||
lang: 'en'
|
||||
},
|
||||
meta: [
|
||||
{ charset: 'utf-8' }
|
||||
]
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
title: 'Hello World',
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -1,28 +0,0 @@
|
||||
<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>
|
||||
@@ -1,149 +0,0 @@
|
||||
/**
|
||||
* @jest-environment node
|
||||
*/
|
||||
import fs from 'fs'
|
||||
import path from 'path'
|
||||
import env from 'node-env-file'
|
||||
import { createBrowser } from 'tib'
|
||||
import { getPort } from '../utils/build'
|
||||
|
||||
const browserString = process.env.BROWSER_STRING || 'puppeteer/core'
|
||||
|
||||
describe(browserString, () => {
|
||||
let browser
|
||||
let page
|
||||
const folder = path.resolve(__dirname, '..', 'fixtures/basic/.vue-meta/')
|
||||
|
||||
beforeAll(async () => {
|
||||
if (
|
||||
browserString.includes('browserstack') &&
|
||||
browserString.includes('local')
|
||||
) {
|
||||
const envFile = path.resolve(__dirname, '..', '..', '.env-browserstack')
|
||||
if (fs.existsSync(envFile)) {
|
||||
env(envFile)
|
||||
}
|
||||
}
|
||||
|
||||
const port = await getPort()
|
||||
|
||||
browser = await createBrowser(
|
||||
browserString,
|
||||
{
|
||||
folder,
|
||||
staticServer: {
|
||||
folder,
|
||||
port,
|
||||
},
|
||||
/* BrowserStackLocal: {
|
||||
localIdentifier: Math.round(99999 * Math.random())
|
||||
}, */
|
||||
extendPage(page) {
|
||||
return {
|
||||
async navigate(path) {
|
||||
await page.runAsyncScript(path => {
|
||||
return new Promise(resolve => {
|
||||
const oldTitle = document.title
|
||||
|
||||
// local firefox has sometimes not updated the title
|
||||
// even when the DOM is supposed to be fully updated
|
||||
const waitTitleChanged = function () {
|
||||
setTimeout(function () {
|
||||
if (oldTitle !== document.title) {
|
||||
resolve()
|
||||
} else {
|
||||
waitTitleChanged()
|
||||
}
|
||||
}, 50)
|
||||
}
|
||||
|
||||
window.$vueMeta.$once('routeChanged', waitTitleChanged)
|
||||
window.$vueMeta.$router.push(path)
|
||||
})
|
||||
}, path)
|
||||
},
|
||||
routeData() {
|
||||
return page.runScript(() => ({
|
||||
path: window.$vueMeta.$route.path,
|
||||
query: window.$vueMeta.$route.query,
|
||||
}))
|
||||
},
|
||||
}
|
||||
},
|
||||
},
|
||||
false
|
||||
)
|
||||
|
||||
browser.addCapability('browserstack.console', 'info')
|
||||
browser.addCapability('browserstack.networkLogs', 'true')
|
||||
|
||||
await browser.start()
|
||||
|
||||
// browser.setLogLevel(['warn', 'error', 'log', 'info'])
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
if (browser) {
|
||||
await browser.close()
|
||||
}
|
||||
})
|
||||
|
||||
test('open page', async () => {
|
||||
const url = browser.getUrl('/index.html')
|
||||
|
||||
page = await browser.page(url)
|
||||
|
||||
expect(
|
||||
await page.getAttribute('html', 'data-vue-meta-server-rendered')
|
||||
).toBe(null)
|
||||
expect(await page.getAttribute('html', 'lang')).toBe('en')
|
||||
expect(await page.getAttribute('html', 'amp')).toBe('')
|
||||
expect(await page.getAttribute('html', 'allowfullscreen')).toBe(null)
|
||||
expect(await page.getAttribute('head', 'test')).toBe('true')
|
||||
expect(await page.getText('h1')).toBe('Basic')
|
||||
expect(await page.getText('title')).toBe('Home | Vue Meta Test')
|
||||
expect(await page.getElementCount('meta')).toBe(2)
|
||||
|
||||
let sanitizeCheck = await page.getTexts('script')
|
||||
sanitizeCheck.push(...(await page.getTexts('noscript')))
|
||||
sanitizeCheck = sanitizeCheck.filter(v => !!v)
|
||||
|
||||
expect(sanitizeCheck.length).toBe(4)
|
||||
expect(() => JSON.parse(sanitizeCheck[0])).not.toThrow()
|
||||
// TODO: check why this doesnt Throw when Home is dynamic loaded
|
||||
// (but that causes hydration error)
|
||||
expect(() => JSON.parse(sanitizeCheck[1])).toThrow()
|
||||
expect(() => JSON.parse(sanitizeCheck[2])).not.toThrow()
|
||||
expect(() => JSON.parse(sanitizeCheck[3])).not.toThrow()
|
||||
|
||||
expect(await page.getElementCount('body noscript:first-child')).toBe(1)
|
||||
expect(await page.getElementCount('body noscript:last-child')).toBe(1)
|
||||
|
||||
expect(
|
||||
await page.runScript(() => {
|
||||
return window.loadTest
|
||||
})
|
||||
).toBe('loaded')
|
||||
|
||||
expect(
|
||||
await page.runScript(() => {
|
||||
return window.loadCallback
|
||||
})
|
||||
).toBe('yes')
|
||||
})
|
||||
|
||||
test('/about', async () => {
|
||||
try {
|
||||
await page.navigate('/about', false)
|
||||
} catch (e) {
|
||||
if (e.constructor.name !== 'ScriptTimeoutError') {
|
||||
throw e
|
||||
} else {
|
||||
console.warn(e) // eslint-disable-line no-console
|
||||
}
|
||||
}
|
||||
|
||||
expect(await page.getText('title')).toBe('About')
|
||||
expect(await page.getElementCount('meta')).toBe(1)
|
||||
})
|
||||
})
|
||||
@@ -1,45 +0,0 @@
|
||||
import { buildFixture } from '../utils/build'
|
||||
|
||||
describe('basic browser with ssr page', () => {
|
||||
let html
|
||||
|
||||
test('build', async () => {
|
||||
const fixture = await buildFixture('basic')
|
||||
|
||||
expect(fixture).toBeDefined()
|
||||
expect(fixture.html).toBeDefined()
|
||||
|
||||
html = fixture.html
|
||||
})
|
||||
|
||||
test('validate ssr', () => {
|
||||
const htmlTag = html.match(/<html([^>]+)>/)[0]
|
||||
expect(htmlTag).toContain('data-vue-meta-server-rendered ')
|
||||
expect(htmlTag).toContain(' lang="en" ')
|
||||
expect(htmlTag).toContain(' amp ')
|
||||
expect(htmlTag).not.toContain('allowfullscreen')
|
||||
expect(html.match(/<title[^>]*>(.*?)<\/title>/)[1]).toBe(
|
||||
'Home | Vue Meta Test'
|
||||
)
|
||||
expect(html.match(/<meta/g).length).toBe(2)
|
||||
expect(html.match(/<meta/g).length).toBe(2)
|
||||
|
||||
// body prepend
|
||||
expect(html.match(/<body[^>]*>\s*<noscript/g).length).toBe(1)
|
||||
// body append
|
||||
expect(html.match(/noscript>\s*<\/body/g).length).toBe(1)
|
||||
|
||||
const re = /<(no)?script[^>]+type="application\/ld\+json"[^>]*>(.*?)</g
|
||||
const sanitizeCheck = []
|
||||
let match
|
||||
while ((match = re.exec(html))) {
|
||||
sanitizeCheck.push(match[2])
|
||||
}
|
||||
|
||||
expect(sanitizeCheck.length).toBe(4)
|
||||
expect(() => JSON.parse(sanitizeCheck[0])).not.toThrow()
|
||||
expect(() => JSON.parse(sanitizeCheck[1])).toThrow()
|
||||
expect(() => JSON.parse(sanitizeCheck[2])).not.toThrow()
|
||||
expect(() => JSON.parse(sanitizeCheck[3])).not.toThrow()
|
||||
})
|
||||
})
|
||||
@@ -1,20 +0,0 @@
|
||||
<!doctype html>
|
||||
<html {{ htmlAttrs.text(true) }}>
|
||||
<head {{ headAttrs.text() }}>
|
||||
{{ meta.text() }}
|
||||
{{ title.text() }}
|
||||
{{ link.text() }}
|
||||
{{ style.text() }}
|
||||
{{ headAssets }}
|
||||
{{ script.text() }}
|
||||
{{ noscript.text() }}
|
||||
</head>
|
||||
<body {{ bodyAttrs.text() }}>
|
||||
{{ script.text({ pbody: true }) }}
|
||||
{{ noscript.text({ pbody: true }) }}
|
||||
{{ app }}
|
||||
{{ bodyAssets }}
|
||||
{{ script.text({ body: true }) }}
|
||||
{{ noscript.text({ body: true }) }}
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,25 +0,0 @@
|
||||
<template>
|
||||
<div id="app">
|
||||
<h1>Basic</h1>
|
||||
<router-view></router-view>
|
||||
<p>Inspect Element to see the meta info</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
metaInfo() {
|
||||
return {
|
||||
meta: [
|
||||
{ vmid: 'charset', charset: 'utf-8' }
|
||||
],
|
||||
afterNavigation: () => {
|
||||
this.$emit('routeChanged')
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
window.$vueMeta = this
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -1,10 +0,0 @@
|
||||
import Vue from 'vue'
|
||||
import VueMeta from 'vue-meta'
|
||||
import App from './App.vue'
|
||||
import createRouter from './router'
|
||||
|
||||
Vue.use(VueMeta)
|
||||
|
||||
App.router = createRouter()
|
||||
|
||||
new Vue(App).$mount('#app')
|
||||
@@ -1,18 +0,0 @@
|
||||
import Vue from 'vue'
|
||||
import Router from 'vue-router'
|
||||
import Home from './views/home.vue'
|
||||
|
||||
Vue.use(Router)
|
||||
|
||||
const Post = () => import('./views/about.vue')
|
||||
|
||||
export default function createRouter() {
|
||||
return new Router({
|
||||
mode: 'hash',
|
||||
base: '/',
|
||||
routes: [
|
||||
{ path: '/', component: Home },
|
||||
{ path: '/about', component: Post },
|
||||
],
|
||||
})
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
import Vue from 'vue'
|
||||
import { _import, getVueMetaPath } from '../../utils/build'
|
||||
import App from './App.vue'
|
||||
import createRouter from './router'
|
||||
|
||||
export default async function createServerApp() {
|
||||
const VueMeta = await _import(getVueMetaPath())
|
||||
|
||||
Vue.use(VueMeta)
|
||||
|
||||
App.router = createRouter()
|
||||
|
||||
return new Vue(App)
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
window.loadTest = 'loaded'
|
||||
@@ -1,16 +0,0 @@
|
||||
<template>
|
||||
<div>
|
||||
<h2>About</h2>
|
||||
<router-link to="/">Go to Home</router-link>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
metaInfo() {
|
||||
return {
|
||||
title: 'About'
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -1,39 +0,0 @@
|
||||
<template>
|
||||
<div>
|
||||
<h2>Home</h2>
|
||||
<router-link to="/about">Go to About</router-link>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
metaInfo() {
|
||||
return {
|
||||
title: 'Home',
|
||||
titleTemplate: '%s | Vue Meta Test',
|
||||
htmlAttrs: {
|
||||
lang: 'en',
|
||||
allowfullscreen: undefined,
|
||||
amp: true
|
||||
},
|
||||
headAttrs: {
|
||||
test: true
|
||||
},
|
||||
meta: [
|
||||
{ name: 'description', content: 'Hello', vmid: 'test' }
|
||||
],
|
||||
script: [
|
||||
{ vmid: 'ldjson', innerHTML: '{ "@context": "http://www.schema.org", "@type": "Organization" }', type: 'application/ld+json' },
|
||||
{ innerHTML: '{ "more": "data" }', type: 'application/ld+json' },
|
||||
{ vmid: 'loadtest', src: '/load-test.js', body: true, async: true, callback: () => (window.loadCallback = 'yes') }
|
||||
],
|
||||
noscript: [
|
||||
{ innerHTML: '{ "pbody": "yes" }', pbody: true, type: 'application/ld+json' },
|
||||
{ innerHTML: '{ "body": "yes" }', body: true, type: 'application/ld+json' }
|
||||
],
|
||||
__dangerouslyDisableSanitizers: ['noscript'],
|
||||
__dangerouslyDisableSanitizersByTagID: { ldjson: ['innerHTML'] }
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -1,626 +0,0 @@
|
||||
import { getComponentMetaInfo } from '../../src/shared/getComponentOption'
|
||||
import _getMetaInfo from '../../src/shared/getMetaInfo'
|
||||
import { triggerUpdate, batchUpdate } from '../../src/client/update'
|
||||
import {
|
||||
mount,
|
||||
createWrapper,
|
||||
loadVueMetaPlugin,
|
||||
vmTick,
|
||||
clearClientAttributeMap,
|
||||
} from '../utils'
|
||||
import { defaultOptions } from '../../src/shared/constants'
|
||||
|
||||
import GoodbyeWorld from '../components/goodbye-world.vue'
|
||||
import HelloWorld from '../components/hello-world.vue'
|
||||
import KeepAlive from '../components/keep-alive.vue'
|
||||
import Changed from '../components/changed.vue'
|
||||
|
||||
const getMetaInfo = component =>
|
||||
_getMetaInfo(defaultOptions, getComponentMetaInfo(defaultOptions, component))
|
||||
|
||||
jest.mock('../../src/client/update')
|
||||
jest.mock('../../src/utils/window', () => ({
|
||||
hasGlobalWindow: false,
|
||||
}))
|
||||
|
||||
describe('components', () => {
|
||||
let Vue
|
||||
let elements
|
||||
|
||||
beforeAll(() => {
|
||||
Vue = loadVueMetaPlugin()
|
||||
|
||||
// force using timers, jest cant mock rAF
|
||||
delete window.requestAnimationFrame
|
||||
delete window.cancelAnimationFrame
|
||||
|
||||
elements = {
|
||||
html: document.createElement('html'),
|
||||
head: document.createElement('head'),
|
||||
body: document.createElement('body'),
|
||||
}
|
||||
|
||||
elements.html.appendChild(elements.head)
|
||||
elements.html.appendChild(elements.body)
|
||||
|
||||
document._getElementsByTagName = document.getElementsByTagName
|
||||
jest.spyOn(document, 'getElementsByTagName').mockImplementation(tag => {
|
||||
if (elements[tag]) {
|
||||
return [elements[tag]]
|
||||
}
|
||||
|
||||
return document._getElementsByTagName(tag)
|
||||
})
|
||||
jest.spyOn(document, 'querySelectorAll').mockImplementation(query => {
|
||||
return elements.html.querySelectorAll(query)
|
||||
})
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks()
|
||||
|
||||
elements.html
|
||||
.getAttributeNames()
|
||||
.forEach(name => elements.html.removeAttribute(name))
|
||||
elements.head.childNodes.forEach(child => child.remove())
|
||||
elements.head
|
||||
.getAttributeNames()
|
||||
.forEach(name => elements.head.removeAttribute(name))
|
||||
elements.body.childNodes.forEach(child => child.remove())
|
||||
elements.body
|
||||
.getAttributeNames()
|
||||
.forEach(name => elements.body.removeAttribute(name))
|
||||
|
||||
clearClientAttributeMap()
|
||||
})
|
||||
|
||||
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', async () => {
|
||||
const wrapper = mount(GoodbyeWorld, { localVue: Vue })
|
||||
|
||||
let metaInfo = getMetaInfo(wrapper.vm)
|
||||
expect(metaInfo.title).toEqual('Hello World')
|
||||
|
||||
wrapper.setData({ childVisible: false })
|
||||
await vmTick(wrapper.vm)
|
||||
|
||||
metaInfo = getMetaInfo(wrapper.vm)
|
||||
expect(metaInfo.title).toEqual('Goodbye World')
|
||||
|
||||
wrapper.setData({ childVisible: true })
|
||||
await vmTick(wrapper.vm)
|
||||
|
||||
metaInfo = getMetaInfo(wrapper.vm)
|
||||
expect(metaInfo.title).toEqual('Hello World')
|
||||
})
|
||||
|
||||
test('child meta-info removed when keep-alive child is toggled', async () => {
|
||||
const wrapper = mount(KeepAlive, { localVue: Vue })
|
||||
|
||||
let metaInfo = getMetaInfo(wrapper.vm)
|
||||
expect(metaInfo.title).toEqual('Hello World')
|
||||
|
||||
wrapper.setData({ childVisible: false })
|
||||
await vmTick(wrapper.vm)
|
||||
|
||||
metaInfo = getMetaInfo(wrapper.vm)
|
||||
expect(metaInfo.title).toEqual('Alive World')
|
||||
|
||||
wrapper.setData({ childVisible: true })
|
||||
await vmTick(wrapper.vm)
|
||||
|
||||
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(undefined)
|
||||
})
|
||||
|
||||
test('warns when component doesnt has metaInfo', () => {
|
||||
const warn = jest.spyOn(console, 'warn').mockImplementation(() => {})
|
||||
const metaInfo = HelloWorld.metaInfo
|
||||
delete HelloWorld.metaInfo
|
||||
|
||||
const wrapper = mount(HelloWorld, { localVue: Vue })
|
||||
wrapper.vm.$meta().inject()
|
||||
|
||||
HelloWorld.metaInfo = metaInfo
|
||||
|
||||
expect(warn).toHaveBeenCalledTimes(1)
|
||||
expect(warn).toHaveBeenCalledWith(
|
||||
'This vue app/component has no vue-meta configuration'
|
||||
)
|
||||
|
||||
warn.mockRestore()
|
||||
})
|
||||
|
||||
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>Hello World</title>')
|
||||
})
|
||||
|
||||
test('inject also renders additional app info', () => {
|
||||
HelloWorld.created = function () {
|
||||
const { set } = this.$meta().addApp('inject-test-app')
|
||||
set({
|
||||
htmlAttrs: { lang: 'nl' },
|
||||
meta: [{ name: 'description', content: 'test-description' }],
|
||||
})
|
||||
}
|
||||
|
||||
const wrapper = mount(HelloWorld, { localVue: Vue })
|
||||
|
||||
const metaInfo = wrapper.vm.$meta().inject()
|
||||
expect(metaInfo.title.text()).toEqual('<title>Hello World</title>')
|
||||
|
||||
expect(metaInfo.htmlAttrs.text()).toEqual(
|
||||
'lang="en nl" data-vue-meta="%7B%22lang%22:%7B%22ssr%22:%22en%22,%22inject-test-app%22:%22nl%22%7D%7D"'
|
||||
)
|
||||
expect(metaInfo.meta.text()).toEqual(
|
||||
'<meta data-vue-meta="ssr" charset="utf-8"><meta data-vue-meta="inject-test-app" name="description" content="test-description">'
|
||||
)
|
||||
|
||||
delete HelloWorld.created
|
||||
})
|
||||
|
||||
test('attributes with special meaning or functioning correct with inject', () => {
|
||||
HelloWorld.created = function () {
|
||||
const { set } = this.$meta().addApp('inject-test-app')
|
||||
set({
|
||||
meta: [
|
||||
{ skip: true, name: 'description', content: 'test-description' },
|
||||
],
|
||||
script: [
|
||||
{
|
||||
once: true,
|
||||
callback: true,
|
||||
async: false,
|
||||
json: {
|
||||
a: 1,
|
||||
},
|
||||
},
|
||||
],
|
||||
})
|
||||
}
|
||||
|
||||
const wrapper = mount(HelloWorld, { localVue: Vue })
|
||||
|
||||
const metaInfo = wrapper.vm.$meta().inject()
|
||||
|
||||
expect(metaInfo.meta.text()).toEqual(
|
||||
'<meta data-vue-meta="ssr" charset="utf-8">'
|
||||
)
|
||||
expect(metaInfo.script.text()).toEqual(
|
||||
'<script onload="this.__vm_l=1">{"a":1}</script>'
|
||||
)
|
||||
|
||||
delete HelloWorld.created
|
||||
})
|
||||
|
||||
test('doesnt update when ssr attribute is set', () => {
|
||||
elements.html.setAttribute(defaultOptions.ssrAttribute, 'true')
|
||||
|
||||
const el = document.createElement('div')
|
||||
el.setAttribute('id', 'app')
|
||||
el.setAttribute('data-server-rendered', true)
|
||||
document.body.appendChild(el)
|
||||
|
||||
const Component = Vue.extend({
|
||||
metaInfo: { title: 'Test' },
|
||||
render(h) {
|
||||
return h('div', null, 'Test')
|
||||
},
|
||||
})
|
||||
|
||||
const vm = new Component().$mount(el)
|
||||
const wrapper = createWrapper(vm, { attachToDocument: true })
|
||||
|
||||
const { tags } = wrapper.vm.$meta().refresh()
|
||||
expect(tags).toBe(false)
|
||||
|
||||
wrapper.destroy()
|
||||
})
|
||||
|
||||
test('changed function is called', async () => {
|
||||
const update = jest.requireActual('../../src/client/update')
|
||||
triggerUpdate.mockImplementation(update.triggerUpdate)
|
||||
batchUpdate.mockImplementation(update.batchUpdate)
|
||||
|
||||
let context
|
||||
const changed = jest.fn(function () {
|
||||
context = this
|
||||
})
|
||||
|
||||
const wrapper = mount(Changed, { localVue: Vue, propsData: { changed } })
|
||||
|
||||
await vmTick(wrapper.vm)
|
||||
expect(wrapper.vm.$root._vueMeta.initialized).toBe(true)
|
||||
// TODO: does changed need to run on initialization?
|
||||
expect(changed).toHaveBeenCalledTimes(1)
|
||||
|
||||
wrapper.setData({ childVisible: true })
|
||||
await vmTick(wrapper.vm)
|
||||
jest.runAllTimers()
|
||||
|
||||
expect(changed).toHaveBeenCalledTimes(2)
|
||||
expect(context._uid).toBe(wrapper.vm._uid)
|
||||
|
||||
triggerUpdate.mockRestore()
|
||||
batchUpdate.mockRestore()
|
||||
})
|
||||
|
||||
test('afterNavigation function is called with refreshOnce: true', async () => {
|
||||
const Vue = loadVueMetaPlugin({ refreshOnceOnNavigation: true })
|
||||
const afterNavigation = jest.fn()
|
||||
const component = Vue.component('nav-component', {
|
||||
render: h => h('div'),
|
||||
metaInfo: { afterNavigation },
|
||||
})
|
||||
|
||||
const guards = {}
|
||||
const wrapper = mount(component, {
|
||||
localVue: Vue,
|
||||
mocks: {
|
||||
$router: {
|
||||
beforeEach(fn) {
|
||||
guards.before = fn
|
||||
},
|
||||
afterEach(fn) {
|
||||
guards.after = fn
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
await vmTick(wrapper.vm)
|
||||
|
||||
expect(guards.before).toBeDefined()
|
||||
expect(guards.after).toBeDefined()
|
||||
|
||||
guards.before(null, null, () => {})
|
||||
expect(wrapper.vm.$root._vueMeta.pausing).toBe(true)
|
||||
|
||||
guards.after()
|
||||
expect(afterNavigation).not.toHaveBeenCalled()
|
||||
await vmTick(wrapper.vm)
|
||||
expect(afterNavigation).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
test('afterNavigation function is called with refreshOnce: false', async () => {
|
||||
const Vue = loadVueMetaPlugin({ refreshOnceOnNavigation: false })
|
||||
const afterNavigation = jest.fn()
|
||||
const component = Vue.component('nav-component', {
|
||||
render: h => h('div'),
|
||||
metaInfo: { afterNavigation },
|
||||
})
|
||||
|
||||
const guards = {}
|
||||
const wrapper = mount(component, {
|
||||
localVue: Vue,
|
||||
mocks: {
|
||||
$router: {
|
||||
beforeEach(fn) {
|
||||
guards.before = fn
|
||||
},
|
||||
afterEach(fn) {
|
||||
guards.after = fn
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
await vmTick(wrapper.vm)
|
||||
|
||||
expect(guards.before).toBeDefined()
|
||||
expect(guards.after).toBeDefined()
|
||||
|
||||
guards.before(null, null, () => {})
|
||||
expect(wrapper.vm.$root._vueMeta.pausing).toBe(true)
|
||||
|
||||
guards.after()
|
||||
expect(afterNavigation).not.toHaveBeenCalled()
|
||||
await vmTick(wrapper.vm)
|
||||
expect(afterNavigation).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
test('changes before hydration initialization trigger an update', async () => {
|
||||
const update = jest.requireActual('../../src/client/update')
|
||||
triggerUpdate.mockImplementation(update.triggerUpdate)
|
||||
batchUpdate.mockImplementation(update.batchUpdate)
|
||||
|
||||
const { html } = elements
|
||||
html.setAttribute(defaultOptions.ssrAttribute, 'true')
|
||||
|
||||
const el = document.createElement('div')
|
||||
el.setAttribute('id', 'app')
|
||||
el.setAttribute('data-server-rendered', true)
|
||||
document.body.appendChild(el)
|
||||
|
||||
// this component uses a computed prop to simulate a non-synchronous
|
||||
// metaInfo update like you would have with a Vuex mutation
|
||||
const Component = Vue.extend({
|
||||
metaInfo() {
|
||||
return {
|
||||
htmlAttrs: {
|
||||
theme: this.theme,
|
||||
},
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
hiddenTheme: 'light',
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
theme() {
|
||||
return this.hiddenTheme
|
||||
},
|
||||
},
|
||||
beforeMount() {
|
||||
this.hiddenTheme = 'dark'
|
||||
},
|
||||
render: h => h('div'),
|
||||
})
|
||||
|
||||
const vm = new Component().$mount(el)
|
||||
const wrapper = createWrapper(vm, { attachToDocument: true })
|
||||
expect(html.getAttribute('theme')).not.toBe('dark')
|
||||
|
||||
await vmTick(wrapper.vm)
|
||||
jest.runAllTimers()
|
||||
|
||||
expect(html.getAttribute('theme')).toBe('dark')
|
||||
wrapper.destroy()
|
||||
|
||||
triggerUpdate.mockRestore()
|
||||
batchUpdate.mockRestore()
|
||||
})
|
||||
|
||||
test('changes during hydration initialization trigger an update', async () => {
|
||||
const update = jest.requireActual('../../src/client/update')
|
||||
triggerUpdate.mockImplementation(update.triggerUpdate)
|
||||
batchUpdate.mockImplementation(update.batchUpdate)
|
||||
|
||||
const { html } = elements
|
||||
html.setAttribute(defaultOptions.ssrAttribute, 'true')
|
||||
|
||||
const el = document.createElement('div')
|
||||
el.setAttribute('id', 'app')
|
||||
el.setAttribute('data-server-rendered', true)
|
||||
document.body.appendChild(el)
|
||||
|
||||
const Component = Vue.extend({
|
||||
data() {
|
||||
return {
|
||||
hiddenTheme: 'light',
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
theme() {
|
||||
return this.hiddenTheme
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.hiddenTheme = 'dark'
|
||||
},
|
||||
render: h => h('div'),
|
||||
metaInfo() {
|
||||
return {
|
||||
htmlAttrs: {
|
||||
theme: this.theme,
|
||||
},
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
const vm = new Component().$mount(el)
|
||||
const wrapper = createWrapper(vm, { attachToDocument: true })
|
||||
expect(html.getAttribute('theme')).not.toBe('dark')
|
||||
|
||||
await vmTick(wrapper.vm)
|
||||
jest.runAllTimers()
|
||||
|
||||
expect(html.getAttribute('theme')).toBe('dark')
|
||||
wrapper.destroy()
|
||||
|
||||
triggerUpdate.mockRestore()
|
||||
batchUpdate.mockRestore()
|
||||
})
|
||||
|
||||
test('can add/remove meta info from additional app ', () => {
|
||||
const { html } = elements
|
||||
let app
|
||||
|
||||
HelloWorld.created = function () {
|
||||
// make sure that app's which set data but are removed before mounting
|
||||
// are really removed
|
||||
const { set, remove } = this.$meta().addApp('my-bogus-app')
|
||||
set({
|
||||
meta: [{ name: 'og:description', content: 'test-description' }],
|
||||
})
|
||||
remove()
|
||||
|
||||
app = this.$meta().addApp('my-test-app')
|
||||
app.set({
|
||||
htmlAttrs: { lang: 'nl' },
|
||||
meta: [{ name: 'description', content: 'test-description' }],
|
||||
script: [{ innerHTML: 'var test = true;' }],
|
||||
})
|
||||
}
|
||||
|
||||
const wrapper = mount(HelloWorld, {
|
||||
localVue: Vue,
|
||||
})
|
||||
|
||||
wrapper.vm.$meta().refresh()
|
||||
|
||||
expect(html.getAttribute('lang')).toEqual('en nl')
|
||||
expect(Array.from(html.querySelectorAll('meta')).length).toBe(2)
|
||||
expect(Array.from(html.querySelectorAll('script')).length).toBe(1)
|
||||
expect(
|
||||
Array.from(html.querySelectorAll('[data-vue-meta="my-test-app"]')).length
|
||||
).toBe(2)
|
||||
|
||||
app.remove()
|
||||
|
||||
// add another app to make sure on client data is immediately added
|
||||
const anotherApp = wrapper.vm.$meta().addApp('another-test-app')
|
||||
anotherApp.set({
|
||||
meta: [{ name: 'og:description', content: 'test-description' }],
|
||||
})
|
||||
|
||||
expect(html.getAttribute('lang')).toEqual('en')
|
||||
expect(Array.from(html.querySelectorAll('meta')).length).toBe(2)
|
||||
expect(Array.from(html.querySelectorAll('script')).length).toBe(0)
|
||||
expect(
|
||||
Array.from(html.querySelectorAll('[data-vue-meta="my-test-app"]')).length
|
||||
).toBe(0)
|
||||
expect(
|
||||
Array.from(html.querySelectorAll('[data-vue-meta="another-test-app"]'))
|
||||
.length
|
||||
).toBe(1)
|
||||
|
||||
wrapper.destroy()
|
||||
delete HelloWorld.created
|
||||
})
|
||||
|
||||
test('retrieves ssr app config from attribute', () => {
|
||||
const { html, body } = elements
|
||||
html.setAttribute(defaultOptions.ssrAttribute, 'true')
|
||||
|
||||
body.setAttribute('foo', 'bar')
|
||||
body.setAttribute(
|
||||
'data-vue-meta',
|
||||
'%7B%22foo%22:%7B%22ssr%22:%22bar%22%7D%7D'
|
||||
)
|
||||
|
||||
const el = document.createElement('div')
|
||||
el.setAttribute('id', 'app')
|
||||
el.setAttribute('data-server-rendered', true)
|
||||
document.body.appendChild(el)
|
||||
|
||||
const Component = Vue.extend({
|
||||
metaInfo: {
|
||||
title: 'Test',
|
||||
bodyAttrs: {
|
||||
foo: 'bar',
|
||||
},
|
||||
},
|
||||
render: h => h('div', null, 'Test'),
|
||||
})
|
||||
|
||||
const vm = new Component().$mount(el)
|
||||
|
||||
const wrapper = createWrapper(vm)
|
||||
|
||||
wrapper.vm.$meta().refresh()
|
||||
expect(body.getAttribute('foo')).toBe('bar')
|
||||
expect(body.getAttribute('data-vue-meta')).toBe(
|
||||
'%7B%22foo%22:%7B%22ssr%22:%22bar%22%7D%7D'
|
||||
)
|
||||
|
||||
wrapper.vm.$meta().refresh()
|
||||
expect(body.getAttribute('foo')).toBe('bar')
|
||||
expect(body.getAttribute('data-vue-meta')).toBeNull()
|
||||
|
||||
wrapper.vm.$meta().refresh()
|
||||
expect(body.getAttribute('foo')).toBe('bar')
|
||||
expect(body.getAttribute('data-vue-meta')).toBeNull()
|
||||
|
||||
wrapper.destroy()
|
||||
})
|
||||
|
||||
test('can enable option refreshOnceOnNavigation runtime', () => {
|
||||
const guards = {}
|
||||
const wrapper = mount(HelloWorld, {
|
||||
localVue: Vue,
|
||||
mocks: {
|
||||
$router: {
|
||||
beforeEach(fn) {
|
||||
guards.before = fn
|
||||
},
|
||||
afterEach(fn) {
|
||||
guards.after = fn
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
expect(guards.before).toBeUndefined()
|
||||
expect(guards.after).toBeUndefined()
|
||||
|
||||
wrapper.vm.$meta().setOptions({ refreshOnceOnNavigation: true })
|
||||
|
||||
expect(guards.before).not.toBeUndefined()
|
||||
expect(guards.after).not.toBeUndefined()
|
||||
})
|
||||
|
||||
test('destroyed hook calls triggerUpdate delayed', async () => {
|
||||
jest.useFakeTimers()
|
||||
const wrapper = mount(HelloWorld, {
|
||||
localVue: Vue,
|
||||
parentComponent: { render: h => h('div') },
|
||||
})
|
||||
const spy = jest
|
||||
.spyOn(wrapper.vm.$el, 'offsetParent', 'get')
|
||||
.mockReturnValue(true)
|
||||
|
||||
wrapper.destroy()
|
||||
|
||||
await vmTick(wrapper.vm)
|
||||
|
||||
expect(triggerUpdate).toHaveBeenCalledTimes(1)
|
||||
spy.mockRestore()
|
||||
|
||||
jest.advanceTimersByTime(51)
|
||||
|
||||
expect(triggerUpdate).toHaveBeenCalledTimes(2)
|
||||
expect(triggerUpdate).toHaveBeenCalledWith(
|
||||
expect.any(Object),
|
||||
expect.any(Object),
|
||||
'destroyed'
|
||||
)
|
||||
})
|
||||
|
||||
test('destroyed hook calls triggerUpdate immediately when waitOnDestroyed: false', async () => {
|
||||
jest.useFakeTimers()
|
||||
|
||||
const wrapper = mount(HelloWorld, {
|
||||
localVue: Vue,
|
||||
parentComponent: { render: h => h('div') },
|
||||
})
|
||||
wrapper.vm.$meta().setOptions({ waitOnDestroyed: false })
|
||||
wrapper.destroy()
|
||||
|
||||
await vmTick(wrapper.vm)
|
||||
|
||||
expect(triggerUpdate).toHaveBeenCalledTimes(2)
|
||||
expect(triggerUpdate).toHaveBeenCalledWith(
|
||||
expect.any(Object),
|
||||
expect.any(Object),
|
||||
'destroyed'
|
||||
)
|
||||
})
|
||||
})
|
||||
@@ -1,147 +0,0 @@
|
||||
import { getComponentMetaInfo } from '../../src/shared/getComponentOption'
|
||||
import _getMetaInfo from '../../src/shared/getMetaInfo'
|
||||
import { loadVueMetaPlugin } from '../utils'
|
||||
import { defaultOptions } from '../../src/shared/constants'
|
||||
import { serverSequences } from '../../src/shared/escaping'
|
||||
|
||||
const getMetaInfo = (component, escapeSequences) =>
|
||||
_getMetaInfo(
|
||||
defaultOptions,
|
||||
getComponentMetaInfo(defaultOptions, component),
|
||||
escapeSequences
|
||||
)
|
||||
|
||||
describe('escaping', () => {
|
||||
let Vue
|
||||
|
||||
beforeAll(() => (Vue = loadVueMetaPlugin()))
|
||||
|
||||
test('special chars are escaped unless disabled', () => {
|
||||
const component = new Vue({
|
||||
metaInfo: {
|
||||
htmlAttrs: { key: 1 },
|
||||
title: 'Hello & Goodbye',
|
||||
script: [{ innerHTML: 'Hello & Goodbye' }],
|
||||
__dangerouslyDisableSanitizers: ['script'],
|
||||
},
|
||||
})
|
||||
|
||||
expect(getMetaInfo(component, [[/&/g, '&']])).toEqual({
|
||||
title: 'Hello & Goodbye',
|
||||
titleChunk: 'Hello & Goodbye',
|
||||
titleTemplate: '%s',
|
||||
htmlAttrs: {
|
||||
key: 1,
|
||||
},
|
||||
headAttrs: {},
|
||||
bodyAttrs: {},
|
||||
meta: [],
|
||||
base: [],
|
||||
link: [],
|
||||
style: [],
|
||||
script: [{ innerHTML: 'Hello & Goodbye' }],
|
||||
noscript: [],
|
||||
__dangerouslyDisableSanitizers: ['script'],
|
||||
__dangerouslyDisableSanitizersByTagID: {},
|
||||
})
|
||||
})
|
||||
|
||||
test('null title is left as it is', () => {
|
||||
const component = new Vue({
|
||||
metaInfo: {
|
||||
title: null,
|
||||
},
|
||||
})
|
||||
|
||||
expect(getMetaInfo(component, [[/&/g, '&']])).toEqual({
|
||||
title: null,
|
||||
titleChunk: '',
|
||||
titleTemplate: '%s',
|
||||
htmlAttrs: {},
|
||||
headAttrs: {},
|
||||
bodyAttrs: {},
|
||||
meta: [],
|
||||
base: [],
|
||||
link: [],
|
||||
style: [],
|
||||
script: [],
|
||||
noscript: [],
|
||||
__dangerouslyDisableSanitizers: [],
|
||||
__dangerouslyDisableSanitizersByTagID: {},
|
||||
})
|
||||
})
|
||||
|
||||
test('special chars are escaped unless disabled by vmid', () => {
|
||||
const component = new Vue({
|
||||
metaInfo: {
|
||||
title: 'Hello',
|
||||
script: [
|
||||
{ vmid: 'yescape', innerHTML: 'Hello & Goodbye' },
|
||||
{ vmid: 'noscape', innerHTML: 'Hello & Goodbye' },
|
||||
],
|
||||
__dangerouslyDisableSanitizersByTagID: { noscape: ['innerHTML'] },
|
||||
},
|
||||
})
|
||||
|
||||
expect(getMetaInfo(component, [[/&/g, '&']])).toEqual({
|
||||
title: 'Hello',
|
||||
titleChunk: 'Hello',
|
||||
titleTemplate: '%s',
|
||||
htmlAttrs: {},
|
||||
headAttrs: {},
|
||||
bodyAttrs: {},
|
||||
meta: [],
|
||||
base: [],
|
||||
link: [],
|
||||
style: [],
|
||||
script: [
|
||||
{ innerHTML: 'Hello & Goodbye', vmid: 'yescape' },
|
||||
{ innerHTML: 'Hello & Goodbye', vmid: 'noscape' },
|
||||
],
|
||||
noscript: [],
|
||||
__dangerouslyDisableSanitizers: [],
|
||||
__dangerouslyDisableSanitizersByTagID: { noscape: ['innerHTML'] },
|
||||
})
|
||||
})
|
||||
|
||||
test('json is still safely escaped', () => {
|
||||
const component = new Vue({
|
||||
metaInfo: {
|
||||
script: [
|
||||
{
|
||||
json: {
|
||||
perfectlySave:
|
||||
'</script><p class="unsafe">This is safe</p><script>',
|
||||
'</script>unsafeKey': 'This is also still safe',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
})
|
||||
|
||||
expect(getMetaInfo(component, serverSequences)).toEqual({
|
||||
title: undefined,
|
||||
titleChunk: '',
|
||||
titleTemplate: '%s',
|
||||
htmlAttrs: {},
|
||||
headAttrs: {},
|
||||
bodyAttrs: {},
|
||||
meta: [],
|
||||
base: [],
|
||||
link: [],
|
||||
style: [],
|
||||
script: [
|
||||
{
|
||||
json: {
|
||||
perfectlySave:
|
||||
'</script><p class="unsafe">This is safe</p><script>',
|
||||
'</script>unsafeKey': 'This is also still safe',
|
||||
},
|
||||
},
|
||||
],
|
||||
noscript: [],
|
||||
__dangerouslyDisableSanitizers: [],
|
||||
__dangerouslyDisableSanitizersByTagID: {},
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -1,158 +0,0 @@
|
||||
import _generateServerInjector from '../../src/server/generateServerInjector'
|
||||
import { defaultOptions } from '../../src/shared/constants'
|
||||
import metaInfoData from '../utils/meta-info-data'
|
||||
import { titleGenerator } from '../../src/server/generators'
|
||||
|
||||
const generateServerInjector = metaInfo =>
|
||||
_generateServerInjector(defaultOptions, metaInfo).injectors
|
||||
|
||||
describe('generators', () => {
|
||||
for (const type in metaInfoData) {
|
||||
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]) {
|
||||
return
|
||||
}
|
||||
|
||||
const defaultTestFn = () => {
|
||||
const newInfo = generateServerInjector({ [type]: testInfo.data })
|
||||
testCases[action](newInfo[type])
|
||||
return newInfo[type]
|
||||
}
|
||||
|
||||
let testFn
|
||||
if (testInfo.test) {
|
||||
testFn = testInfo.test('server', defaultTestFn)
|
||||
|
||||
if (testFn === true) {
|
||||
testFn = defaultTestFn
|
||||
}
|
||||
} else {
|
||||
testFn = defaultTestFn
|
||||
}
|
||||
|
||||
if (testFn && typeof testFn === 'function') {
|
||||
test(`${action} a tag`, () => {
|
||||
expect.hasAssertions()
|
||||
testFn()
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
describe('extra tests', () => {
|
||||
test('empty config doesnt generate a tag', () => {
|
||||
const { meta } = generateServerInjector({ meta: [] })
|
||||
|
||||
expect(meta.text()).toEqual('')
|
||||
})
|
||||
|
||||
test('config with empty object doesnt generate a tag', () => {
|
||||
const { meta } = generateServerInjector({ meta: [{}] })
|
||||
|
||||
expect(meta.text()).toEqual('')
|
||||
})
|
||||
|
||||
test('title generator should return an empty string when title is null', () => {
|
||||
const title = null
|
||||
const generatedTitle = titleGenerator({}, 'title', title)
|
||||
|
||||
expect(generatedTitle).toEqual('')
|
||||
})
|
||||
|
||||
test('auto add ssrAttribute', () => {
|
||||
const { htmlAttrs } = generateServerInjector({ htmlAttrs: {} })
|
||||
expect(htmlAttrs.text(true)).toBe('data-vue-meta-server-rendered')
|
||||
|
||||
const { headAttrs } = generateServerInjector({ headAttrs: {} })
|
||||
expect(headAttrs.text(true)).toBe('')
|
||||
|
||||
const { bodyAttrs } = generateServerInjector({ bodyAttrs: {} })
|
||||
expect(bodyAttrs.text(true)).toBe('')
|
||||
})
|
||||
|
||||
test('script prepend body', () => {
|
||||
const tags = [{ src: '/script.js', pbody: true }]
|
||||
const { script: scriptTags } = generateServerInjector({ script: tags })
|
||||
|
||||
expect(scriptTags.text()).toBe('')
|
||||
expect(scriptTags.text({ body: true })).toBe('')
|
||||
expect(scriptTags.text({ pbody: true })).toBe(
|
||||
'<script data-vue-meta="ssr" src="/script.js" data-pbody="true"></script>'
|
||||
)
|
||||
})
|
||||
|
||||
test('script append body', () => {
|
||||
const tags = [{ src: '/script.js', body: true }]
|
||||
const { script: scriptTags } = generateServerInjector({ script: tags })
|
||||
|
||||
expect(scriptTags.text()).toBe('')
|
||||
expect(scriptTags.text({ body: true })).toBe(
|
||||
'<script data-vue-meta="ssr" src="/script.js" data-body="true"></script>'
|
||||
)
|
||||
expect(scriptTags.text({ pbody: true })).toBe('')
|
||||
})
|
||||
|
||||
test('add additional app and test head/body injector helpers', () => {
|
||||
const baseInfo = {
|
||||
title: 'hello',
|
||||
htmlAttrs: { lang: 'en' },
|
||||
bodyAttrs: { class: 'base-class' },
|
||||
script: [{ src: '/script.js', body: true }],
|
||||
}
|
||||
const extraInfo = {
|
||||
bodyAttrs: { class: 'extra-class' },
|
||||
script: [{ src: '/script.js', pbody: true }],
|
||||
}
|
||||
|
||||
const serverInjector = _generateServerInjector(defaultOptions, baseInfo)
|
||||
serverInjector.addInfo('test-app', extraInfo)
|
||||
|
||||
const meta = serverInjector.injectors
|
||||
|
||||
expect(meta.script.text()).toBe('')
|
||||
expect(meta.script.text({ body: true })).toBe(
|
||||
'<script data-vue-meta="ssr" src="/script.js" data-body="true"></script>'
|
||||
)
|
||||
expect(meta.script.text({ pbody: true })).toBe(
|
||||
'<script data-vue-meta="test-app" src="/script.js" data-pbody="true"></script>'
|
||||
)
|
||||
|
||||
expect(meta.head(true)).toBe('<title>hello</title>\n')
|
||||
expect(meta.bodyPrepend(true)).toBe(
|
||||
'<script data-vue-meta="test-app" src="/script.js" data-pbody="true"></script>\n'
|
||||
)
|
||||
expect(meta.bodyAppend()).toBe(
|
||||
'<script data-vue-meta="ssr" src="/script.js" data-body="true"></script>'
|
||||
)
|
||||
|
||||
expect(meta.htmlAttrs.text()).toBe(
|
||||
'lang="en" data-vue-meta="%7B%22lang%22:%7B%22ssr%22:%22en%22%7D%7D"'
|
||||
)
|
||||
expect(meta.bodyAttrs.text()).toBe(
|
||||
'class="base-class extra-class" data-vue-meta="%7B%22class%22:%7B%22ssr%22:%22base-class%22,%22test-app%22:%22extra-class%22%7D%7D"'
|
||||
)
|
||||
})
|
||||
})
|
||||
@@ -1,171 +0,0 @@
|
||||
import { getComponentOption } from '../../src/shared/getComponentOption'
|
||||
import { inMetaInfoBranch } from '../../src/shared/meta-helpers'
|
||||
import { mount, getVue, loadVueMetaPlugin } from '../utils'
|
||||
|
||||
describe('getComponentOption', () => {
|
||||
let Vue
|
||||
|
||||
beforeAll(() => (Vue = getVue()))
|
||||
|
||||
test('returns an empty object when no matching options are found', () => {
|
||||
const component = new Vue()
|
||||
const mergedOption = getComponentOption({ keyName: 'noop' }, component)
|
||||
expect(mergedOption).toEqual({})
|
||||
})
|
||||
|
||||
test('fetches the given option from the given component', () => {
|
||||
const component = new Vue({ someOption: { foo: 'bar' } })
|
||||
const mergedOption = getComponentOption(
|
||||
{ keyName: 'someOption' },
|
||||
component
|
||||
)
|
||||
expect(mergedOption.foo).toBeDefined()
|
||||
expect(mergedOption.foo).toEqual('bar')
|
||||
})
|
||||
|
||||
test('calls a function as computed prop, injecting the component as context', () => {
|
||||
const component = new Vue({
|
||||
name: 'Foobar',
|
||||
someFunc() {
|
||||
return { opt: this.name }
|
||||
},
|
||||
computed: {
|
||||
$metaInfo() {
|
||||
return this.$options.someFunc()
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
const mergedOption = getComponentOption({ keyName: 'someFunc' }, component)
|
||||
// TODO: Should this be foobar or Foobar
|
||||
expect(mergedOption.opt).toBeDefined()
|
||||
expect(mergedOption.opt).toEqual('Foobar')
|
||||
})
|
||||
|
||||
test('fetches deeply nested component options and merges them', () => {
|
||||
const localVue = loadVueMetaPlugin({ keyName: 'foo' })
|
||||
localVue.component('merge-child', {
|
||||
render: h => h('div'),
|
||||
foo: { bar: 'baz' },
|
||||
})
|
||||
|
||||
const component = localVue.component('parent', {
|
||||
foo: { fizz: 'buzz' },
|
||||
render: h => h('div', null, [h('merge-child')]),
|
||||
})
|
||||
|
||||
const wrapper = mount(component, { localVue })
|
||||
|
||||
const mergedOption = getComponentOption({ keyName: 'foo' }, wrapper.vm)
|
||||
expect(mergedOption).toEqual({ bar: 'baz', fizz: 'buzz' })
|
||||
})
|
||||
|
||||
/* this undocumented functionality has been removed
|
||||
test('allows for a custom array merge strategy', () => {
|
||||
const localVue = loadVueMetaPlugin(false, { keyName: 'foo' })
|
||||
localVue.component('array-child', {
|
||||
render: h => h('div'),
|
||||
foo: {
|
||||
meta: [
|
||||
{ name: 'flower', content: 'rose' }
|
||||
]
|
||||
}
|
||||
})
|
||||
|
||||
const component = localVue.component('parent', {
|
||||
foo: {
|
||||
meta: [
|
||||
{ name: 'flower', content: 'tulip' }
|
||||
]
|
||||
},
|
||||
render: h => h('div', null, [h('array-child')])
|
||||
})
|
||||
|
||||
const wrapper = mount(component, { localVue })
|
||||
|
||||
const mergedOption = getComponentOption({
|
||||
component: wrapper.vm,
|
||||
keyName: 'foo',
|
||||
arrayMerge(target, source) {
|
||||
return target.concat(source)
|
||||
}
|
||||
})
|
||||
|
||||
expect(mergedOption).toEqual({ meta: [
|
||||
{ name: 'flower', content: 'tulip' },
|
||||
{ name: 'flower', content: 'rose' }
|
||||
] })
|
||||
}) */
|
||||
|
||||
test('only traverses branches with metaInfo components', () => {
|
||||
const localVue = loadVueMetaPlugin({ keyName: 'foo' })
|
||||
|
||||
localVue.component('meta-child', {
|
||||
foo: { bar: 'baz' },
|
||||
render(h) {
|
||||
return h('div', this.$slots.default)
|
||||
},
|
||||
})
|
||||
|
||||
localVue.component('nometa-child', {
|
||||
render(h) {
|
||||
return h('div', this.$slots.default)
|
||||
},
|
||||
})
|
||||
|
||||
const component = localVue.component('parent', {
|
||||
render: h =>
|
||||
h('div', null, [
|
||||
h('meta-child', null, [h('nometa-child')]),
|
||||
h('nometa-child', null, [h('meta-child')]),
|
||||
h('nometa-child'),
|
||||
]),
|
||||
})
|
||||
|
||||
const wrapper = mount(component, { localVue })
|
||||
|
||||
const mergedOption = getComponentOption({ keyName: 'foo' }, wrapper.vm)
|
||||
|
||||
expect(mergedOption).toEqual({ bar: 'baz' })
|
||||
expect(wrapper.vm.$children[0]._vueMeta).toBe(true)
|
||||
expect(wrapper.vm.$children[1]._vueMeta).toBe(false)
|
||||
expect(wrapper.vm.$children[2]._vueMeta).toBeUndefined()
|
||||
|
||||
expect(inMetaInfoBranch(wrapper.vm.$children[0])).toBe(true)
|
||||
expect(inMetaInfoBranch(wrapper.vm.$children[0].$children[0])).toBe(false)
|
||||
expect(inMetaInfoBranch(wrapper.vm.$children[1])).toBe(true)
|
||||
expect(inMetaInfoBranch(wrapper.vm.$children[1].$children[0])).toBe(true)
|
||||
expect(inMetaInfoBranch(wrapper.vm.$children[2])).toBe(false)
|
||||
})
|
||||
|
||||
test('retrieves child options even if parent returns null', () => {
|
||||
const localVue = loadVueMetaPlugin({ keyName: 'foo' })
|
||||
|
||||
localVue.component('meta-child', {
|
||||
foo: { bar: 'baz' },
|
||||
render(h) {
|
||||
return h('div', this.$slots.default)
|
||||
},
|
||||
})
|
||||
|
||||
localVue.component('nometa-child', {
|
||||
render(h) {
|
||||
return h('div', this.$slots.default)
|
||||
},
|
||||
})
|
||||
|
||||
const component = localVue.component('parent', {
|
||||
foo: () => {},
|
||||
render: h => h('div', null, [h('meta-child')]),
|
||||
})
|
||||
|
||||
const wrapper = mount(component, { localVue })
|
||||
|
||||
const mergedOption = getComponentOption({ keyName: 'foo' }, wrapper.vm)
|
||||
|
||||
expect(mergedOption).toEqual({ bar: 'baz' })
|
||||
expect(wrapper.vm.$children[0]._vueMeta).toBe(true)
|
||||
|
||||
expect(inMetaInfoBranch(wrapper.vm.$children[0])).toBe(true)
|
||||
})
|
||||
})
|
||||
@@ -1,871 +0,0 @@
|
||||
import { getComponentMetaInfo } from '../../src/shared/getComponentOption'
|
||||
import _getMetaInfo from '../../src/shared/getMetaInfo'
|
||||
import { loadVueMetaPlugin } from '../utils'
|
||||
import { defaultOptions } from '../../src/shared/constants'
|
||||
|
||||
const getMetaInfo = component =>
|
||||
_getMetaInfo(
|
||||
defaultOptions,
|
||||
getComponentMetaInfo(defaultOptions, component),
|
||||
[],
|
||||
component
|
||||
)
|
||||
|
||||
describe('getMetaInfo', () => {
|
||||
let Vue
|
||||
|
||||
beforeAll(() => (Vue = loadVueMetaPlugin()))
|
||||
|
||||
test('returns appropriate defaults when no meta info is found', () => {
|
||||
const component = new Vue()
|
||||
|
||||
expect(getMetaInfo(component)).toEqual({
|
||||
title: undefined,
|
||||
titleChunk: '',
|
||||
titleTemplate: '%s',
|
||||
htmlAttrs: {},
|
||||
headAttrs: {},
|
||||
bodyAttrs: {},
|
||||
meta: [],
|
||||
base: [],
|
||||
link: [],
|
||||
style: [],
|
||||
script: [],
|
||||
noscript: [],
|
||||
__dangerouslyDisableSanitizers: [],
|
||||
__dangerouslyDisableSanitizersByTagID: {},
|
||||
})
|
||||
})
|
||||
|
||||
test('returns metaInfo when used in component', () => {
|
||||
const component = new Vue({
|
||||
metaInfo: {
|
||||
title: 'Hello',
|
||||
meta: [{ charset: 'utf-8' }],
|
||||
},
|
||||
})
|
||||
|
||||
expect(getMetaInfo(component)).toEqual({
|
||||
title: 'Hello',
|
||||
titleChunk: 'Hello',
|
||||
titleTemplate: '%s',
|
||||
htmlAttrs: {},
|
||||
headAttrs: {},
|
||||
bodyAttrs: {},
|
||||
meta: [{ charset: 'utf-8' }],
|
||||
base: [],
|
||||
link: [],
|
||||
style: [],
|
||||
script: [],
|
||||
noscript: [],
|
||||
__dangerouslyDisableSanitizers: [],
|
||||
__dangerouslyDisableSanitizersByTagID: {},
|
||||
})
|
||||
})
|
||||
|
||||
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: [
|
||||
{
|
||||
vmid: 'a',
|
||||
property: 'a',
|
||||
content: 'a',
|
||||
},
|
||||
{
|
||||
vmid: 'a',
|
||||
property: 'a',
|
||||
content: 'b',
|
||||
},
|
||||
],
|
||||
},
|
||||
})
|
||||
|
||||
expect(getMetaInfo(component)).toEqual({
|
||||
title: 'Hello',
|
||||
titleChunk: 'Hello',
|
||||
titleTemplate: '%s',
|
||||
htmlAttrs: {},
|
||||
headAttrs: {},
|
||||
bodyAttrs: {},
|
||||
meta: [
|
||||
{
|
||||
vmid: 'a',
|
||||
property: 'a',
|
||||
content: 'a',
|
||||
},
|
||||
],
|
||||
base: [],
|
||||
link: [],
|
||||
style: [],
|
||||
script: [],
|
||||
noscript: [],
|
||||
__dangerouslyDisableSanitizers: [],
|
||||
__dangerouslyDisableSanitizersByTagID: {},
|
||||
})
|
||||
})
|
||||
|
||||
test('properly uses string titleTemplates', () => {
|
||||
const component = new Vue({
|
||||
metaInfo: {
|
||||
title: 'Hello',
|
||||
titleTemplate: '%s World',
|
||||
meta: [{ charset: 'utf-8' }],
|
||||
},
|
||||
})
|
||||
|
||||
expect(getMetaInfo(component)).toEqual({
|
||||
title: 'Hello World',
|
||||
titleChunk: 'Hello',
|
||||
titleTemplate: '%s World',
|
||||
htmlAttrs: {},
|
||||
headAttrs: {},
|
||||
bodyAttrs: {},
|
||||
meta: [{ charset: 'utf-8' }],
|
||||
base: [],
|
||||
link: [],
|
||||
style: [],
|
||||
script: [],
|
||||
noscript: [],
|
||||
__dangerouslyDisableSanitizers: [],
|
||||
__dangerouslyDisableSanitizersByTagID: {},
|
||||
})
|
||||
})
|
||||
|
||||
test('properly uses function titleTemplates', () => {
|
||||
const titleTemplate = chunk => `${chunk} Function World`
|
||||
|
||||
const component = new Vue({
|
||||
metaInfo: {
|
||||
title: 'Hello',
|
||||
titleTemplate,
|
||||
meta: [{ charset: 'utf-8' }],
|
||||
},
|
||||
})
|
||||
|
||||
expect(getMetaInfo(component)).toEqual({
|
||||
title: 'Hello Function World',
|
||||
titleChunk: 'Hello',
|
||||
titleTemplate,
|
||||
htmlAttrs: {},
|
||||
headAttrs: {},
|
||||
bodyAttrs: {},
|
||||
meta: [{ charset: 'utf-8' }],
|
||||
base: [],
|
||||
link: [],
|
||||
style: [],
|
||||
script: [],
|
||||
noscript: [],
|
||||
__dangerouslyDisableSanitizers: [],
|
||||
__dangerouslyDisableSanitizersByTagID: {},
|
||||
})
|
||||
})
|
||||
|
||||
test('has the proper `this` binding when using function titleTemplates', () => {
|
||||
const titleTemplate = function (chunk) {
|
||||
return `${chunk} ${this.helloWorldText}`
|
||||
}
|
||||
|
||||
const component = new Vue({
|
||||
metaInfo: {
|
||||
title: 'Hello',
|
||||
titleTemplate,
|
||||
meta: [{ charset: 'utf-8' }],
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
helloWorldText: 'Function World',
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
expect(getMetaInfo(component)).toEqual({
|
||||
title: 'Hello Function World',
|
||||
titleChunk: 'Hello',
|
||||
titleTemplate,
|
||||
htmlAttrs: {},
|
||||
headAttrs: {},
|
||||
bodyAttrs: {},
|
||||
meta: [{ charset: 'utf-8' }],
|
||||
base: [],
|
||||
link: [],
|
||||
style: [],
|
||||
script: [],
|
||||
noscript: [],
|
||||
__dangerouslyDisableSanitizers: [],
|
||||
__dangerouslyDisableSanitizersByTagID: {},
|
||||
})
|
||||
})
|
||||
|
||||
test('properly uses string meta templates', () => {
|
||||
const component = new Vue({
|
||||
metaInfo: {
|
||||
title: 'Hello',
|
||||
meta: [
|
||||
{
|
||||
vmid: 'og:title',
|
||||
property: 'og:title',
|
||||
content: 'Test title',
|
||||
template: '%s - My page',
|
||||
},
|
||||
],
|
||||
},
|
||||
})
|
||||
|
||||
const expectedMetaInfo = {
|
||||
title: 'Hello',
|
||||
titleChunk: 'Hello',
|
||||
titleTemplate: '%s',
|
||||
htmlAttrs: {},
|
||||
headAttrs: {},
|
||||
bodyAttrs: {},
|
||||
meta: [
|
||||
{
|
||||
vmid: 'og:title',
|
||||
property: 'og:title',
|
||||
content: 'Test title - My page',
|
||||
template: true,
|
||||
},
|
||||
],
|
||||
base: [],
|
||||
link: [],
|
||||
style: [],
|
||||
script: [],
|
||||
noscript: [],
|
||||
__dangerouslyDisableSanitizers: [],
|
||||
__dangerouslyDisableSanitizersByTagID: {},
|
||||
}
|
||||
|
||||
expect(getMetaInfo(component)).toEqual(expectedMetaInfo)
|
||||
expect(getMetaInfo(component)).toEqual(expectedMetaInfo)
|
||||
})
|
||||
|
||||
test('properly uses function meta templates', () => {
|
||||
const component = new Vue({
|
||||
metaInfo: {
|
||||
title: 'Hello',
|
||||
meta: [
|
||||
{
|
||||
vmid: 'og:title',
|
||||
property: 'og:title',
|
||||
content: 'Test title',
|
||||
template: chunk => `${chunk} - My page`,
|
||||
},
|
||||
],
|
||||
},
|
||||
})
|
||||
|
||||
const expectedMetaInfo = {
|
||||
title: 'Hello',
|
||||
titleChunk: 'Hello',
|
||||
titleTemplate: '%s',
|
||||
htmlAttrs: {},
|
||||
headAttrs: {},
|
||||
bodyAttrs: {},
|
||||
meta: [
|
||||
{
|
||||
vmid: 'og:title',
|
||||
property: 'og:title',
|
||||
content: 'Test title - My page',
|
||||
template: true,
|
||||
},
|
||||
],
|
||||
base: [],
|
||||
link: [],
|
||||
style: [],
|
||||
script: [],
|
||||
noscript: [],
|
||||
__dangerouslyDisableSanitizers: [],
|
||||
__dangerouslyDisableSanitizersByTagID: {},
|
||||
}
|
||||
|
||||
expect(getMetaInfo(component)).toEqual(expectedMetaInfo)
|
||||
expect(getMetaInfo(component)).toEqual(expectedMetaInfo)
|
||||
})
|
||||
|
||||
test('properly uses content only if template is not defined', () => {
|
||||
const component = new Vue({
|
||||
metaInfo: {
|
||||
title: 'Hello',
|
||||
meta: [
|
||||
{
|
||||
vmid: 'og:title',
|
||||
property: 'og:title',
|
||||
content: 'Test title',
|
||||
},
|
||||
],
|
||||
},
|
||||
})
|
||||
|
||||
expect(getMetaInfo(component)).toEqual({
|
||||
title: 'Hello',
|
||||
titleChunk: 'Hello',
|
||||
titleTemplate: '%s',
|
||||
htmlAttrs: {},
|
||||
headAttrs: {},
|
||||
bodyAttrs: {},
|
||||
meta: [
|
||||
{
|
||||
vmid: 'og:title',
|
||||
property: 'og:title',
|
||||
content: 'Test title',
|
||||
},
|
||||
],
|
||||
base: [],
|
||||
link: [],
|
||||
style: [],
|
||||
script: [],
|
||||
noscript: [],
|
||||
__dangerouslyDisableSanitizers: [],
|
||||
__dangerouslyDisableSanitizersByTagID: {},
|
||||
})
|
||||
})
|
||||
|
||||
test('properly uses content only if template is null', () => {
|
||||
const component = new Vue({
|
||||
metaInfo: {
|
||||
title: 'Hello',
|
||||
meta: [
|
||||
{
|
||||
vmid: 'og:title',
|
||||
property: 'og:title',
|
||||
content: 'Test title',
|
||||
template: null,
|
||||
},
|
||||
],
|
||||
},
|
||||
})
|
||||
|
||||
expect(getMetaInfo(component)).toEqual({
|
||||
title: 'Hello',
|
||||
titleChunk: 'Hello',
|
||||
titleTemplate: '%s',
|
||||
htmlAttrs: {},
|
||||
headAttrs: {},
|
||||
bodyAttrs: {},
|
||||
meta: [
|
||||
{
|
||||
vmid: 'og:title',
|
||||
property: 'og:title',
|
||||
content: 'Test title',
|
||||
},
|
||||
],
|
||||
base: [],
|
||||
link: [],
|
||||
style: [],
|
||||
script: [],
|
||||
noscript: [],
|
||||
__dangerouslyDisableSanitizers: [],
|
||||
__dangerouslyDisableSanitizersByTagID: {},
|
||||
})
|
||||
})
|
||||
|
||||
test('properly uses content only if template is false', () => {
|
||||
const component = new Vue({
|
||||
metaInfo: {
|
||||
title: 'Hello',
|
||||
meta: [
|
||||
{
|
||||
vmid: 'og:title',
|
||||
property: 'og:title',
|
||||
content: 'Test title',
|
||||
template: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
})
|
||||
|
||||
expect(getMetaInfo(component)).toEqual({
|
||||
title: 'Hello',
|
||||
titleChunk: 'Hello',
|
||||
titleTemplate: '%s',
|
||||
htmlAttrs: {},
|
||||
headAttrs: {},
|
||||
bodyAttrs: {},
|
||||
meta: [
|
||||
{
|
||||
vmid: 'og:title',
|
||||
property: 'og:title',
|
||||
content: 'Test title',
|
||||
},
|
||||
],
|
||||
base: [],
|
||||
link: [],
|
||||
style: [],
|
||||
script: [],
|
||||
noscript: [],
|
||||
__dangerouslyDisableSanitizers: [],
|
||||
__dangerouslyDisableSanitizersByTagID: {},
|
||||
})
|
||||
})
|
||||
|
||||
test('properly uses meta templates with one-level-deep nested children content', () => {
|
||||
Vue.component('merge-child', {
|
||||
render: h => h('div'),
|
||||
metaInfo: {
|
||||
title: 'Hello',
|
||||
meta: [
|
||||
{
|
||||
vmid: 'og:title',
|
||||
property: 'og:title',
|
||||
content: 'An important title!',
|
||||
},
|
||||
],
|
||||
},
|
||||
})
|
||||
|
||||
const component = new Vue({
|
||||
metaInfo: {
|
||||
meta: [
|
||||
{
|
||||
vmid: 'og:title',
|
||||
property: 'og:title',
|
||||
content: 'Test title',
|
||||
template: chunk => `${chunk} - My page`,
|
||||
},
|
||||
],
|
||||
},
|
||||
el: document.createElement('div'),
|
||||
render: h => h('div', null, [h('merge-child')]),
|
||||
})
|
||||
|
||||
const expectedMetaInfo = {
|
||||
title: 'Hello',
|
||||
titleChunk: 'Hello',
|
||||
titleTemplate: '%s',
|
||||
htmlAttrs: {},
|
||||
headAttrs: {},
|
||||
bodyAttrs: {},
|
||||
meta: [
|
||||
{
|
||||
vmid: 'og:title',
|
||||
property: 'og:title',
|
||||
content: 'An important title! - My page',
|
||||
template: true,
|
||||
},
|
||||
],
|
||||
base: [],
|
||||
link: [],
|
||||
style: [],
|
||||
script: [],
|
||||
noscript: [],
|
||||
__dangerouslyDisableSanitizers: [],
|
||||
__dangerouslyDisableSanitizersByTagID: {},
|
||||
}
|
||||
|
||||
expect(getMetaInfo(component)).toEqual(expectedMetaInfo)
|
||||
expect(getMetaInfo(component)).toEqual(expectedMetaInfo)
|
||||
})
|
||||
|
||||
test('properly uses meta templates with one-level-deep nested children template', () => {
|
||||
Vue.component('merge-child', {
|
||||
render: h => h('div'),
|
||||
metaInfo: {
|
||||
title: 'Hello',
|
||||
meta: [
|
||||
{
|
||||
vmid: 'og:title',
|
||||
property: 'og:title',
|
||||
template: chunk => `${chunk} - My page`,
|
||||
},
|
||||
],
|
||||
},
|
||||
})
|
||||
|
||||
const component = new Vue({
|
||||
metaInfo: {
|
||||
meta: [
|
||||
{
|
||||
vmid: 'og:title',
|
||||
property: 'og:title',
|
||||
content: 'Test title',
|
||||
template: chunk => `${chunk} - SHOULD NEVER HAPPEN`,
|
||||
},
|
||||
],
|
||||
},
|
||||
el: document.createElement('div'),
|
||||
render: h => h('div', null, [h('merge-child')]),
|
||||
})
|
||||
|
||||
const expectedMetaInfo = {
|
||||
title: 'Hello',
|
||||
titleChunk: 'Hello',
|
||||
titleTemplate: '%s',
|
||||
htmlAttrs: {},
|
||||
headAttrs: {},
|
||||
bodyAttrs: {},
|
||||
meta: [
|
||||
{
|
||||
vmid: 'og:title',
|
||||
property: 'og:title',
|
||||
content: 'Test title - My page',
|
||||
template: true,
|
||||
},
|
||||
],
|
||||
base: [],
|
||||
link: [],
|
||||
style: [],
|
||||
script: [],
|
||||
noscript: [],
|
||||
__dangerouslyDisableSanitizers: [],
|
||||
__dangerouslyDisableSanitizersByTagID: {},
|
||||
}
|
||||
|
||||
expect(getMetaInfo(component)).toEqual(expectedMetaInfo)
|
||||
expect(getMetaInfo(component)).toEqual(expectedMetaInfo)
|
||||
})
|
||||
|
||||
test('properly uses meta templates with one-level-deep nested children template and content', () => {
|
||||
Vue.component('merge-child', {
|
||||
render: h => h('div'),
|
||||
metaInfo: {
|
||||
title: 'Hello',
|
||||
meta: [
|
||||
{
|
||||
vmid: 'og:title',
|
||||
property: 'og:title',
|
||||
content: 'An important title!',
|
||||
template: chunk => `${chunk} - My page`,
|
||||
},
|
||||
],
|
||||
},
|
||||
})
|
||||
|
||||
const component = new Vue({
|
||||
metaInfo: {
|
||||
meta: [
|
||||
{
|
||||
vmid: 'og:title',
|
||||
property: 'og:title',
|
||||
content: 'Test title',
|
||||
template: chunk => `${chunk} - SHOULD NEVER HAPPEN`,
|
||||
},
|
||||
],
|
||||
},
|
||||
el: document.createElement('div'),
|
||||
render: h => h('div', null, [h('merge-child')]),
|
||||
})
|
||||
|
||||
const expectedMetaInfo = {
|
||||
title: 'Hello',
|
||||
titleChunk: 'Hello',
|
||||
titleTemplate: '%s',
|
||||
htmlAttrs: {},
|
||||
headAttrs: {},
|
||||
bodyAttrs: {},
|
||||
meta: [
|
||||
{
|
||||
vmid: 'og:title',
|
||||
property: 'og:title',
|
||||
content: 'An important title! - My page',
|
||||
template: true,
|
||||
},
|
||||
],
|
||||
base: [],
|
||||
link: [],
|
||||
style: [],
|
||||
script: [],
|
||||
noscript: [],
|
||||
__dangerouslyDisableSanitizers: [],
|
||||
__dangerouslyDisableSanitizersByTagID: {},
|
||||
}
|
||||
|
||||
expect(getMetaInfo(component)).toEqual(expectedMetaInfo)
|
||||
expect(getMetaInfo(component)).toEqual(expectedMetaInfo)
|
||||
})
|
||||
|
||||
test('properly uses meta templates with one-level-deep nested children when parent has no template', () => {
|
||||
Vue.component('merge-child', {
|
||||
render: h => h('div'),
|
||||
metaInfo: {
|
||||
title: 'Hello',
|
||||
meta: [
|
||||
{
|
||||
vmid: 'og:title',
|
||||
property: 'og:title',
|
||||
content: 'An important title!',
|
||||
template: chunk => `${chunk} - My page`,
|
||||
},
|
||||
],
|
||||
},
|
||||
})
|
||||
|
||||
const component = new Vue({
|
||||
metaInfo: {
|
||||
meta: [
|
||||
{
|
||||
vmid: 'og:title',
|
||||
property: 'og:title',
|
||||
content: 'Test title',
|
||||
},
|
||||
],
|
||||
},
|
||||
el: document.createElement('div'),
|
||||
render: h => h('div', null, [h('merge-child')]),
|
||||
})
|
||||
|
||||
const expectedMetaInfo = {
|
||||
title: 'Hello',
|
||||
titleChunk: 'Hello',
|
||||
titleTemplate: '%s',
|
||||
htmlAttrs: {},
|
||||
headAttrs: {},
|
||||
bodyAttrs: {},
|
||||
meta: [
|
||||
{
|
||||
vmid: 'og:title',
|
||||
property: 'og:title',
|
||||
content: 'An important title! - My page',
|
||||
template: true,
|
||||
},
|
||||
],
|
||||
base: [],
|
||||
link: [],
|
||||
style: [],
|
||||
script: [],
|
||||
noscript: [],
|
||||
__dangerouslyDisableSanitizers: [],
|
||||
__dangerouslyDisableSanitizersByTagID: {},
|
||||
}
|
||||
|
||||
expect(getMetaInfo(component)).toEqual(expectedMetaInfo)
|
||||
expect(getMetaInfo(component)).toEqual(expectedMetaInfo)
|
||||
})
|
||||
|
||||
test('no errors when metaInfo returns nothing', () => {
|
||||
const component = new Vue({
|
||||
metaInfo() {},
|
||||
el: document.createElement('div'),
|
||||
render: h => h('div', null, []),
|
||||
})
|
||||
|
||||
expect(getMetaInfo(component)).toEqual({
|
||||
title: undefined,
|
||||
titleChunk: '',
|
||||
titleTemplate: '%s',
|
||||
htmlAttrs: {},
|
||||
headAttrs: {},
|
||||
bodyAttrs: {},
|
||||
meta: [],
|
||||
base: [],
|
||||
link: [],
|
||||
style: [],
|
||||
script: [],
|
||||
noscript: [],
|
||||
__dangerouslyDisableSanitizers: [],
|
||||
__dangerouslyDisableSanitizersByTagID: {},
|
||||
})
|
||||
})
|
||||
|
||||
test('child can indicate its content should be ignored', () => {
|
||||
Vue.component('merge-child', {
|
||||
render: h => h('div'),
|
||||
metaInfo: {
|
||||
title: undefined,
|
||||
bodyAttrs: {
|
||||
class: undefined,
|
||||
},
|
||||
meta: [
|
||||
{
|
||||
vmid: 'og:title',
|
||||
content: undefined,
|
||||
},
|
||||
],
|
||||
},
|
||||
})
|
||||
|
||||
const component = new Vue({
|
||||
metaInfo: {
|
||||
title: 'Hello',
|
||||
bodyAttrs: {
|
||||
class: 'class',
|
||||
},
|
||||
meta: [
|
||||
{
|
||||
vmid: 'og:title',
|
||||
property: 'og:title',
|
||||
content: 'Test title',
|
||||
template: chunk => `${chunk} - My page`,
|
||||
},
|
||||
],
|
||||
},
|
||||
el: document.createElement('div'),
|
||||
render: h => h('div', null, [h('merge-child')]),
|
||||
})
|
||||
|
||||
expect(getMetaInfo(component)).toEqual({
|
||||
title: 'Hello',
|
||||
titleChunk: 'Hello',
|
||||
titleTemplate: '%s',
|
||||
bodyAttrs: {
|
||||
class: 'class',
|
||||
},
|
||||
headAttrs: {},
|
||||
htmlAttrs: {},
|
||||
meta: [
|
||||
{
|
||||
vmid: 'og:title',
|
||||
property: 'og:title',
|
||||
content: 'Test title - My page',
|
||||
template: true,
|
||||
},
|
||||
],
|
||||
base: [],
|
||||
link: [],
|
||||
style: [],
|
||||
script: [],
|
||||
noscript: [],
|
||||
__dangerouslyDisableSanitizers: [],
|
||||
__dangerouslyDisableSanitizersByTagID: {},
|
||||
})
|
||||
})
|
||||
|
||||
test('child can indicate to remove parent vmids', () => {
|
||||
Vue.component('merge-child', {
|
||||
render: h => h('div'),
|
||||
metaInfo: {
|
||||
title: 'Hi',
|
||||
meta: [
|
||||
{
|
||||
vmid: 'og:title',
|
||||
content: null,
|
||||
},
|
||||
],
|
||||
},
|
||||
})
|
||||
|
||||
const component = new Vue({
|
||||
metaInfo: {
|
||||
title: 'Hello',
|
||||
meta: [
|
||||
{
|
||||
vmid: 'og:title',
|
||||
property: 'og:title',
|
||||
content: 'Test title',
|
||||
template: chunk => `${chunk} - My page`,
|
||||
},
|
||||
],
|
||||
},
|
||||
el: document.createElement('div'),
|
||||
render: h => h('div', null, [h('merge-child')]),
|
||||
})
|
||||
|
||||
expect(getMetaInfo(component)).toEqual({
|
||||
title: 'Hi',
|
||||
titleChunk: 'Hi',
|
||||
titleTemplate: '%s',
|
||||
htmlAttrs: {},
|
||||
headAttrs: {},
|
||||
bodyAttrs: {},
|
||||
meta: [],
|
||||
base: [],
|
||||
link: [],
|
||||
style: [],
|
||||
script: [],
|
||||
noscript: [],
|
||||
__dangerouslyDisableSanitizers: [],
|
||||
__dangerouslyDisableSanitizersByTagID: {},
|
||||
})
|
||||
})
|
||||
|
||||
test('attribute values can be an array', () => {
|
||||
Vue.component('merge-child', {
|
||||
render: h => h('div'),
|
||||
metaInfo: {
|
||||
bodyAttrs: {
|
||||
class: ['foo'],
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
const component = new Vue({
|
||||
metaInfo: {
|
||||
bodyAttrs: {
|
||||
class: ['bar'],
|
||||
},
|
||||
},
|
||||
el: document.createElement('div'),
|
||||
render: h => h('div', null, [h('merge-child')]),
|
||||
})
|
||||
|
||||
expect(getMetaInfo(component)).toEqual({
|
||||
title: undefined,
|
||||
titleChunk: '',
|
||||
titleTemplate: '%s',
|
||||
htmlAttrs: {},
|
||||
headAttrs: {},
|
||||
bodyAttrs: {
|
||||
class: ['bar', 'foo'],
|
||||
},
|
||||
meta: [],
|
||||
base: [],
|
||||
link: [],
|
||||
style: [],
|
||||
script: [],
|
||||
noscript: [],
|
||||
__dangerouslyDisableSanitizers: [],
|
||||
__dangerouslyDisableSanitizersByTagID: {},
|
||||
})
|
||||
})
|
||||
|
||||
test('prints warning for boolean attributes with value undefined', () => {
|
||||
const warn = jest.spyOn(console, 'warn').mockImplementation(() => {})
|
||||
|
||||
const component = new Vue({
|
||||
metaInfo: {
|
||||
htmlAttrs: {
|
||||
amp: undefined,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
expect(getMetaInfo(component)).toEqual({
|
||||
title: undefined,
|
||||
titleChunk: '',
|
||||
titleTemplate: '%s',
|
||||
htmlAttrs: {},
|
||||
headAttrs: {},
|
||||
bodyAttrs: {},
|
||||
meta: [],
|
||||
base: [],
|
||||
link: [],
|
||||
style: [],
|
||||
script: [],
|
||||
noscript: [],
|
||||
__dangerouslyDisableSanitizers: [],
|
||||
__dangerouslyDisableSanitizersByTagID: {},
|
||||
})
|
||||
|
||||
expect(warn).toHaveBeenCalledTimes(1)
|
||||
expect(warn).toHaveBeenCalledWith(
|
||||
expect.stringMatching('the value undefined')
|
||||
)
|
||||
})
|
||||
})
|
||||
@@ -1,226 +0,0 @@
|
||||
import { pTick, createDOM } from '../utils'
|
||||
|
||||
const onLoadAttribute = {
|
||||
k: 'onload',
|
||||
v: 'this.__vm_l=1',
|
||||
}
|
||||
|
||||
const getLoadAttribute = () => `${onLoadAttribute.k}="${onLoadAttribute.v}"`
|
||||
|
||||
describe('load callbacks', () => {
|
||||
let load
|
||||
beforeEach(async () => {
|
||||
jest.resetModules()
|
||||
load = await import('../../src/client/load')
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
jest.restoreAllMocks()
|
||||
})
|
||||
|
||||
test('isDOMLoaded', async () => {
|
||||
jest.useRealTimers()
|
||||
const { document } = createDOM()
|
||||
await pTick()
|
||||
|
||||
jest.spyOn(document, 'readyState', 'get').mockReturnValue('loading')
|
||||
expect(load.isDOMLoaded(document)).toBe(false)
|
||||
|
||||
jest.spyOn(document, 'readyState', 'get').mockReturnValue('interactive')
|
||||
expect(load.isDOMLoaded(document)).toBe(true)
|
||||
|
||||
jest.spyOn(document, 'readyState', 'get').mockReturnValue('complete')
|
||||
expect(load.isDOMLoaded(document)).toBe(true)
|
||||
})
|
||||
|
||||
test('isDOMComplete', async () => {
|
||||
jest.useRealTimers()
|
||||
const { document } = createDOM()
|
||||
await pTick()
|
||||
|
||||
jest.spyOn(document, 'readyState', 'get').mockReturnValue('loading')
|
||||
expect(load.isDOMComplete(document)).toBe(false)
|
||||
|
||||
jest.spyOn(document, 'readyState', 'get').mockReturnValue('interactive')
|
||||
expect(load.isDOMComplete(document)).toBe(false)
|
||||
|
||||
jest.spyOn(document, 'readyState', 'get').mockReturnValue('complete')
|
||||
expect(load.isDOMComplete(document)).toBe(true)
|
||||
})
|
||||
|
||||
test('waitDOMLoaded', async () => {
|
||||
expect(load.waitDOMLoaded()).toBe(true)
|
||||
|
||||
jest.spyOn(document, 'readyState', 'get').mockReturnValue('loading')
|
||||
const waitPromise = load.waitDOMLoaded()
|
||||
expect(waitPromise).toEqual(expect.any(Promise))
|
||||
|
||||
const domLoaded = new Event('DOMContentLoaded')
|
||||
document.dispatchEvent(domLoaded)
|
||||
|
||||
await expect(waitPromise).resolves.toEqual(expect.any(Object))
|
||||
})
|
||||
|
||||
test('addCallback (no query)', () => {
|
||||
const callback = () => {}
|
||||
load.addCallback(callback)
|
||||
|
||||
const matches = jest.fn(() => false)
|
||||
load.applyCallbacks({ matches })
|
||||
|
||||
expect(matches).toHaveBeenCalledTimes(1)
|
||||
expect(matches).toHaveBeenCalledWith(`[${getLoadAttribute()}]`)
|
||||
})
|
||||
|
||||
test('addCallback (query)', () => {
|
||||
const callback = () => {}
|
||||
load.addCallback('script', callback)
|
||||
|
||||
const matches = jest.fn(() => false)
|
||||
load.applyCallbacks({ matches })
|
||||
|
||||
expect(matches).toHaveBeenCalledTimes(1)
|
||||
expect(matches).toHaveBeenCalledWith(`script[${getLoadAttribute()}]`)
|
||||
})
|
||||
|
||||
test('addCallbacks', () => {
|
||||
const addListeners = jest
|
||||
.spyOn(document, 'querySelectorAll')
|
||||
.mockReturnValue(false)
|
||||
|
||||
const config = { tagIDKeyName: 'test-id' }
|
||||
|
||||
const tags = [
|
||||
{ [config.tagIDKeyName]: 'test1', callback: false },
|
||||
{ [config.tagIDKeyName]: false, callback: () => {} },
|
||||
{ [config.tagIDKeyName]: 'test1', callback: () => {} },
|
||||
{ [config.tagIDKeyName]: 'test2', callback: () => {} },
|
||||
]
|
||||
|
||||
load.addCallbacks(config, 'link', tags)
|
||||
|
||||
const matches = jest.fn(() => false)
|
||||
load.applyCallbacks({ matches })
|
||||
|
||||
expect(matches).toHaveBeenCalledTimes(2)
|
||||
expect(matches).toHaveBeenCalledWith(
|
||||
`link[data-${config.tagIDKeyName}="test1"][${getLoadAttribute()}]`
|
||||
)
|
||||
expect(matches).toHaveBeenCalledWith(
|
||||
`link[data-${config.tagIDKeyName}="test2"][${getLoadAttribute()}]`
|
||||
)
|
||||
|
||||
expect(addListeners).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
test('addCallbacks (auto add listeners)', () => {
|
||||
const addListeners = jest
|
||||
.spyOn(document, 'querySelectorAll')
|
||||
.mockReturnValue(false)
|
||||
|
||||
const config = {
|
||||
tagIDKeyName: 'test-id',
|
||||
loadCallbackAttribute: 'test-load',
|
||||
}
|
||||
|
||||
const tags = [{ [config.tagIDKeyName]: 'test1', callback: () => {} }]
|
||||
|
||||
load.addCallbacks(config, 'style', tags, true)
|
||||
|
||||
const matches = jest.fn(() => false)
|
||||
load.applyCallbacks({ matches })
|
||||
|
||||
expect(matches).toHaveBeenCalledTimes(1)
|
||||
expect(matches).toHaveBeenCalledWith(
|
||||
`style[data-${config.tagIDKeyName}="test1"][${getLoadAttribute()}]`
|
||||
)
|
||||
|
||||
expect(addListeners).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
test('callback trigger', () => {
|
||||
const { window, document } = createDOM()
|
||||
|
||||
const callback = jest.fn()
|
||||
|
||||
const el = document.createElement('script')
|
||||
el.setAttribute(onLoadAttribute.k, onLoadAttribute.v)
|
||||
document.body.appendChild(el)
|
||||
|
||||
load.addCallback(callback)
|
||||
load.applyCallbacks(el)
|
||||
|
||||
const loadEvent = new window.Event('load')
|
||||
el.dispatchEvent(loadEvent)
|
||||
|
||||
expect(callback).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
test('callback trigger (loaded before adding)', () => {
|
||||
const { document } = createDOM()
|
||||
|
||||
const callback = jest.fn()
|
||||
|
||||
const el = document.createElement('script')
|
||||
el.setAttribute(onLoadAttribute.k, onLoadAttribute.v)
|
||||
el.__vm_l = 1
|
||||
document.body.appendChild(el)
|
||||
|
||||
load.addCallback(callback)
|
||||
load.applyCallbacks(el)
|
||||
|
||||
expect(callback).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
test('callback trigger (only once)', () => {
|
||||
const { window, document } = createDOM()
|
||||
|
||||
const callback = jest.fn()
|
||||
|
||||
const el = document.createElement('script')
|
||||
el.setAttribute(onLoadAttribute.k, onLoadAttribute.v)
|
||||
document.body.appendChild(el)
|
||||
|
||||
el.__vm_l = 1
|
||||
|
||||
load.addCallback(callback)
|
||||
load.applyCallbacks(el)
|
||||
|
||||
el.__vm_cb = true
|
||||
|
||||
const loadEvent = new window.Event('load')
|
||||
el.dispatchEvent(loadEvent)
|
||||
|
||||
expect(callback).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
test('only one event listener added', () => {
|
||||
const { window, document } = createDOM()
|
||||
|
||||
const el = document.createElement('script')
|
||||
const addEventListener = el.addEventListener.bind(el)
|
||||
const addEventListenerSpy = jest
|
||||
.spyOn(el, 'addEventListener')
|
||||
.mockImplementation((...args) => {
|
||||
return addEventListener(...args)
|
||||
})
|
||||
el.setAttribute(onLoadAttribute.k, onLoadAttribute.v)
|
||||
document.body.appendChild(el)
|
||||
|
||||
load.addCallback(() => {})
|
||||
load.applyCallbacks(el)
|
||||
|
||||
const loadEvent1 = new window.Event('load')
|
||||
el.dispatchEvent(loadEvent1)
|
||||
|
||||
expect(addEventListenerSpy).toHaveBeenCalledTimes(1)
|
||||
|
||||
el.setAttribute(onLoadAttribute.k, onLoadAttribute.v)
|
||||
load.applyCallbacks(el)
|
||||
|
||||
const loadEvent2 = new window.Event('load')
|
||||
el.dispatchEvent(loadEvent2)
|
||||
|
||||
expect(addEventListenerSpy).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
})
|
||||
@@ -1,302 +0,0 @@
|
||||
import { triggerUpdate, batchUpdate } from '../../src/client/update'
|
||||
import { mount, vmTick, VueMetaPlugin, loadVueMetaPlugin } from '../utils'
|
||||
import { defaultOptions } from '../../src/shared/constants'
|
||||
|
||||
jest.mock('../../src/client/update')
|
||||
jest.mock('../../package.json', () => ({
|
||||
version: 'test-version',
|
||||
}))
|
||||
|
||||
describe('plugin', () => {
|
||||
let Vue
|
||||
|
||||
beforeEach(() => jest.clearAllMocks())
|
||||
beforeAll(() => (Vue = loadVueMetaPlugin(true)))
|
||||
|
||||
test('not loaded when no metaInfo defined', () => {
|
||||
const warn = jest.spyOn(console, 'warn').mockImplementation(() => {})
|
||||
process.server = false
|
||||
|
||||
const instance = new Vue()
|
||||
expect(instance.$meta).toEqual(expect.any(Function))
|
||||
|
||||
expect(instance.$meta().inject).toEqual(expect.any(Function))
|
||||
expect(instance.$meta().refresh).toEqual(expect.any(Function))
|
||||
expect(instance.$meta().getOptions).toEqual(expect.any(Function))
|
||||
|
||||
expect(instance.$meta().inject()).toBeUndefined()
|
||||
expect(warn).toHaveBeenCalledTimes(1)
|
||||
expect(instance.$meta().refresh()).toEqual({})
|
||||
expect(warn).toHaveBeenCalledTimes(2)
|
||||
|
||||
instance.$meta().getOptions()
|
||||
expect(warn).toHaveBeenCalledTimes(2)
|
||||
|
||||
warn.mockRestore()
|
||||
delete process.server
|
||||
})
|
||||
|
||||
test('is loaded', () => {
|
||||
const instance = new Vue({ metaInfo: {} })
|
||||
expect(instance.$meta).toEqual(expect.any(Function))
|
||||
|
||||
expect(instance.$meta().inject).toEqual(expect.any(Function))
|
||||
expect(instance.$meta().refresh).toEqual(expect.any(Function))
|
||||
expect(instance.$meta().getOptions).toEqual(expect.any(Function))
|
||||
|
||||
expect(instance.$meta().inject()).toBeUndefined()
|
||||
expect(instance.$meta().refresh()).toBeDefined()
|
||||
|
||||
const options = instance.$meta().getOptions()
|
||||
expect(options).toBeDefined()
|
||||
expect(options.keyName).toBe(defaultOptions.keyName)
|
||||
})
|
||||
|
||||
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')
|
||||
})
|
||||
|
||||
test('plugin isnt be installed twice', () => {
|
||||
expect(Vue.__vuemeta_installed).toBe(true)
|
||||
|
||||
Vue.prototype.$meta = undefined
|
||||
Vue.use({ ...VueMetaPlugin })
|
||||
|
||||
expect(Vue.prototype.$meta).toBeUndefined()
|
||||
|
||||
// reset Vue
|
||||
Vue = loadVueMetaPlugin(true)
|
||||
})
|
||||
|
||||
test('prints deprecation warning once when using _hasMetaInfo', () => {
|
||||
const warn = jest.spyOn(console, 'warn').mockImplementation(() => {})
|
||||
|
||||
const Component = Vue.component('test-component', {
|
||||
template: '<div>Test</div>',
|
||||
[defaultOptions.keyName]: {
|
||||
title: 'Hello World',
|
||||
},
|
||||
})
|
||||
|
||||
Vue.config.devtools = true
|
||||
const { vm } = mount(Component, { localVue: Vue })
|
||||
|
||||
expect(vm._hasMetaInfo).toBe(true)
|
||||
expect(warn).toHaveBeenCalledTimes(1)
|
||||
|
||||
expect(vm._hasMetaInfo).toBe(true)
|
||||
expect(warn).toHaveBeenCalledTimes(1)
|
||||
warn.mockRestore()
|
||||
})
|
||||
|
||||
test('can use hasMetaInfo export', () => {
|
||||
const warn = jest.spyOn(console, 'warn').mockImplementation(() => {})
|
||||
|
||||
const Component = Vue.component('test-component', {
|
||||
template: '<div>Test</div>',
|
||||
[defaultOptions.keyName]: {
|
||||
title: 'Hello World',
|
||||
},
|
||||
})
|
||||
|
||||
const { vm } = mount(Component, { localVue: Vue })
|
||||
|
||||
expect(VueMetaPlugin.hasMetaInfo(vm)).toBe(true)
|
||||
expect(warn).not.toHaveBeenCalled()
|
||||
|
||||
warn.mockRestore()
|
||||
})
|
||||
|
||||
test('can use generate export with options', () => {
|
||||
process.server = true
|
||||
const rawInfo = {
|
||||
meta: [{ charset: 'utf-8' }],
|
||||
}
|
||||
|
||||
const metaInfo = VueMetaPlugin.generate(rawInfo, {
|
||||
ssrAppId: 'my-test-app-id',
|
||||
})
|
||||
expect(metaInfo.meta.text()).toBe(
|
||||
'<meta data-vue-meta="my-test-app-id" charset="utf-8">'
|
||||
)
|
||||
|
||||
// no error on not provided metaInfo types
|
||||
expect(metaInfo.script.text()).toBe('')
|
||||
})
|
||||
|
||||
test('warning when calling generate in browser build', () => {
|
||||
process.server = false
|
||||
const warn = jest.spyOn(console, 'warn').mockImplementation(() => {})
|
||||
|
||||
const rawInfo = {
|
||||
meta: [{ charset: 'utf-8' }],
|
||||
}
|
||||
|
||||
const metaInfo = VueMetaPlugin.generate(rawInfo)
|
||||
expect(metaInfo).toBeUndefined()
|
||||
expect(warn).toHaveBeenCalledTimes(1)
|
||||
expect(warn).toHaveBeenCalledWith(
|
||||
'generate is not supported in browser builds'
|
||||
)
|
||||
|
||||
warn.mockRestore()
|
||||
})
|
||||
|
||||
test('updates can be paused and resumed', async () => {
|
||||
const { batchUpdate: _batchUpdate } = jest.requireActual(
|
||||
'../../src/client/update'
|
||||
)
|
||||
const batchUpdateSpy = batchUpdate.mockImplementation(_batchUpdate)
|
||||
// because triggerUpdate & batchUpdate reside in the same file we cant mock them both,
|
||||
// so just recreate the triggerUpdate fn by copying its implementation
|
||||
const triggerUpdateSpy = triggerUpdate.mockImplementation(
|
||||
(options, vm, hookName) => {
|
||||
if (vm.$root._vueMeta.initialized && !vm.$root._vueMeta.pausing) {
|
||||
// batch potential DOM updates to prevent extraneous re-rendering
|
||||
batchUpdateSpy(() => vm.$meta().refresh())
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
const Component = Vue.component('test-component', {
|
||||
metaInfo() {
|
||||
return {
|
||||
title: this.title,
|
||||
}
|
||||
},
|
||||
props: {
|
||||
title: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
},
|
||||
template: '<div>Test</div>',
|
||||
})
|
||||
|
||||
let title = 'first title'
|
||||
const wrapper = mount(Component, {
|
||||
localVue: Vue,
|
||||
propsData: {
|
||||
title,
|
||||
},
|
||||
})
|
||||
|
||||
// no batchUpdate on initialization
|
||||
expect(wrapper.vm.$root._vueMeta.initialized).toBe(false)
|
||||
expect(wrapper.vm.$root._vueMeta.pausing).toBeFalsy()
|
||||
expect(triggerUpdateSpy).toHaveBeenCalledTimes(1)
|
||||
expect(batchUpdateSpy).not.toHaveBeenCalled()
|
||||
jest.clearAllMocks()
|
||||
await vmTick(wrapper.vm)
|
||||
|
||||
title = 'second title'
|
||||
wrapper.setProps({ title })
|
||||
await vmTick(wrapper.vm)
|
||||
|
||||
// batchUpdate on normal update
|
||||
expect(wrapper.vm.$root._vueMeta.initialized).toBe(true)
|
||||
expect(wrapper.vm.$root._vueMeta.pausing).toBeFalsy()
|
||||
expect(triggerUpdateSpy).toHaveBeenCalledTimes(1)
|
||||
expect(batchUpdateSpy).toHaveBeenCalledTimes(1)
|
||||
jest.clearAllMocks()
|
||||
|
||||
wrapper.vm.$meta().pause()
|
||||
title = 'third title'
|
||||
wrapper.setProps({ title })
|
||||
await vmTick(wrapper.vm)
|
||||
|
||||
// no batchUpdate when pausing
|
||||
expect(wrapper.vm.$root._vueMeta.initialized).toBe(true)
|
||||
expect(wrapper.vm.$root._vueMeta.pausing).toBe(true)
|
||||
expect(triggerUpdateSpy).toHaveBeenCalledTimes(1)
|
||||
expect(batchUpdateSpy).not.toHaveBeenCalled()
|
||||
jest.clearAllMocks()
|
||||
|
||||
const { metaInfo } = wrapper.vm.$meta().resume()
|
||||
expect(metaInfo.title).toBe(title)
|
||||
})
|
||||
|
||||
test('updates are batched by default', async () => {
|
||||
jest.useFakeTimers()
|
||||
|
||||
const { batchUpdate: _batchUpdate } = jest.requireActual(
|
||||
'../../src/client/update'
|
||||
)
|
||||
const batchUpdateSpy = batchUpdate.mockImplementation(_batchUpdate)
|
||||
const refreshSpy = jest.fn()
|
||||
// because triggerUpdate & batchUpdate reside in the same file we cant mock them both,
|
||||
// so just recreate the triggerUpdate fn by copying its implementation
|
||||
triggerUpdate.mockImplementation((options, vm, hookName) => {
|
||||
if (vm.$root._vueMeta.initialized && !vm.$root._vueMeta.pausing) {
|
||||
// batch potential DOM updates to prevent extraneous re-rendering
|
||||
batchUpdateSpy(refreshSpy)
|
||||
}
|
||||
})
|
||||
|
||||
const Component = Vue.component('test-component', {
|
||||
metaInfo() {
|
||||
return {
|
||||
title: this.title,
|
||||
}
|
||||
},
|
||||
props: {
|
||||
title: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
},
|
||||
template: '<div>Test</div>',
|
||||
})
|
||||
|
||||
let title = 'first title'
|
||||
const wrapper = mount(Component, {
|
||||
localVue: Vue,
|
||||
propsData: {
|
||||
title,
|
||||
},
|
||||
})
|
||||
await vmTick(wrapper.vm)
|
||||
jest.clearAllMocks()
|
||||
|
||||
title = 'second title'
|
||||
wrapper.setProps({ title })
|
||||
await vmTick(wrapper.vm)
|
||||
|
||||
jest.advanceTimersByTime(2)
|
||||
expect(refreshSpy).not.toHaveBeenCalled()
|
||||
jest.advanceTimersByTime(10)
|
||||
expect(refreshSpy).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
test('can set option waitOnDestroyed runtime', () => {
|
||||
const wrapper = mount({ render: h => h('div') }, { localVue: Vue })
|
||||
|
||||
expect(wrapper.vm.$meta().getOptions().waitOnDestroyed).toBe(true)
|
||||
|
||||
wrapper.vm.$meta().setOptions({ waitOnDestroyed: false })
|
||||
|
||||
expect(wrapper.vm.$meta().getOptions().waitOnDestroyed).toBe(false)
|
||||
})
|
||||
|
||||
test('can set option debounceWait runtime', () => {
|
||||
const wrapper = mount({ render: h => h('div') }, { localVue: Vue })
|
||||
|
||||
expect(wrapper.vm.$meta().getOptions().debounceWait).toBe(10)
|
||||
|
||||
wrapper.vm.$meta().setOptions({ debounceWait: 69420 })
|
||||
|
||||
expect(wrapper.vm.$meta().getOptions().debounceWait).toBe(69420)
|
||||
})
|
||||
})
|
||||
@@ -1,44 +0,0 @@
|
||||
import { setOptions } from '../../src/shared/options'
|
||||
import { defaultOptions } from '../../src/shared/constants'
|
||||
import { triggerUpdate } from '../../src/client/update'
|
||||
|
||||
describe('shared', () => {
|
||||
test('can use setOptions', () => {
|
||||
const keyName = 'MY KEY'
|
||||
let options = { keyName }
|
||||
options = setOptions(options)
|
||||
|
||||
expect(options.keyName).toBe(keyName)
|
||||
expect(options.contentKeyName).toBeDefined()
|
||||
expect(options.contentKeyName).toBe(defaultOptions.contentKeyName)
|
||||
})
|
||||
|
||||
test('options.debounceWait is used', () => {
|
||||
jest.useFakeTimers()
|
||||
|
||||
const refresh = jest.fn()
|
||||
const componentMock = {
|
||||
_vueMeta: {
|
||||
initialized: true,
|
||||
pausing: false,
|
||||
},
|
||||
$meta: () => ({ refresh }),
|
||||
}
|
||||
|
||||
triggerUpdate({ debounceWait: 0 }, componentMock, 'test')
|
||||
|
||||
expect(refresh).toHaveBeenCalledTimes(1)
|
||||
|
||||
triggerUpdate({}, componentMock, 'test')
|
||||
expect(refresh).toHaveBeenCalledTimes(1)
|
||||
jest.advanceTimersByTime(11)
|
||||
expect(refresh).toHaveBeenCalledTimes(2)
|
||||
|
||||
triggerUpdate({ debounceWait: 69420 }, componentMock, 'test')
|
||||
expect(refresh).toHaveBeenCalledTimes(2)
|
||||
jest.advanceTimersByTime(11)
|
||||
expect(refresh).toHaveBeenCalledTimes(2)
|
||||
jest.advanceTimersByTime(69500)
|
||||
expect(refresh).toHaveBeenCalledTimes(3)
|
||||
})
|
||||
})
|
||||
@@ -1,136 +0,0 @@
|
||||
import _updateClientMetaInfo from '../../src/client/updateClientMetaInfo'
|
||||
import {
|
||||
defaultOptions,
|
||||
ssrAppId,
|
||||
ssrAttribute,
|
||||
} from '../../src/shared/constants'
|
||||
import metaInfoData from '../utils/meta-info-data'
|
||||
import * as load from '../../src/client/load'
|
||||
import { clearClientAttributeMap } from '../utils'
|
||||
|
||||
const updateClientMetaInfo = (type, data) =>
|
||||
_updateClientMetaInfo(ssrAppId, 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)
|
||||
)
|
||||
})
|
||||
|
||||
for (const type in metaInfoData) {
|
||||
const typeTests = metaInfoData[type]
|
||||
|
||||
const testCases = {
|
||||
add: tags => {
|
||||
typeTests.add.expect.forEach((expected, index) => {
|
||||
if (
|
||||
!['title', 'htmlAttrs', 'headAttrs', 'bodyAttrs'].includes(type)
|
||||
) {
|
||||
expect(tags.tagsAdded[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.tagsAdded[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`, () => {
|
||||
beforeAll(() => clearClientAttributeMap())
|
||||
|
||||
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(`${action} a tag`, () => {
|
||||
expect.hasAssertions()
|
||||
testFn()
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
describe('extra tests', () => {
|
||||
test('adds callback listener on hydration', () => {
|
||||
const addListeners = load.addListeners
|
||||
const addListenersSpy = jest
|
||||
.spyOn(load, 'addListeners')
|
||||
.mockImplementation(addListeners)
|
||||
|
||||
const html = document.getElementsByTagName('html')[0]
|
||||
html.setAttribute(ssrAttribute, 'true')
|
||||
|
||||
const data = [
|
||||
{
|
||||
src: 'src1',
|
||||
[defaultOptions.tagIDKeyName]: 'content',
|
||||
callback: () => {},
|
||||
},
|
||||
]
|
||||
const tags = updateClientMetaInfo('script', data)
|
||||
|
||||
expect(tags).toBe(false)
|
||||
expect(html.hasAttribute(ssrAttribute)).toBe(false)
|
||||
expect(addListenersSpy).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
})
|
||||
@@ -1,74 +0,0 @@
|
||||
/**
|
||||
* @jest-environment node
|
||||
*/
|
||||
import { find, findIndex, includes, toArray } from '../../src/utils/array'
|
||||
import { ensureIsArray } from '../../src/utils/ensure'
|
||||
import { hasGlobalWindowFn } from '../../src/utils/window'
|
||||
|
||||
describe('shared', () => {
|
||||
afterEach(() => jest.restoreAllMocks())
|
||||
|
||||
test('ensureIsArray ensures var is array', () => {
|
||||
let a = { p: 1 }
|
||||
expect(ensureIsArray(a)).toEqual([])
|
||||
|
||||
a = 1
|
||||
expect(ensureIsArray(a)).toEqual([])
|
||||
|
||||
a = [1]
|
||||
expect(ensureIsArray(a)).toBe(a)
|
||||
})
|
||||
|
||||
test('ensureIsArray ensures obj prop is array', () => {
|
||||
const a = { p: 1 }
|
||||
expect(ensureIsArray(a, 'p')).toEqual({ p: [] })
|
||||
})
|
||||
|
||||
test('no error when window is not defined', () => {
|
||||
expect(hasGlobalWindowFn()).toBe(false)
|
||||
})
|
||||
|
||||
/* eslint-disable no-extend-native */
|
||||
test('find polyfill', () => {
|
||||
const _find = Array.prototype.find
|
||||
Array.prototype.find = false
|
||||
|
||||
const arr = [1, 2, 3]
|
||||
expect(find(arr, (v, i) => i === 0)).toBe(1)
|
||||
expect(find(arr, (v, i) => i === 3)).toBe(undefined)
|
||||
|
||||
Array.prototype.find = _find
|
||||
})
|
||||
|
||||
test('findIndex polyfill', () => {
|
||||
const _findIndex = Array.prototype.findIndex
|
||||
Array.prototype.findIndex = false
|
||||
|
||||
const arr = [1, 2, 3]
|
||||
expect(findIndex(arr, v => v === 2)).toBe(1)
|
||||
expect(findIndex(arr, v => v === 4)).toBe(-1)
|
||||
|
||||
Array.prototype.findIndex = _findIndex
|
||||
})
|
||||
|
||||
test('includes polyfill', () => {
|
||||
const _includes = Array.prototype.includes
|
||||
Array.prototype.includes = false
|
||||
|
||||
const arr = [1, 2, 3]
|
||||
expect(includes(arr, 2)).toBe(true)
|
||||
expect(includes(arr, 4)).toBe(false)
|
||||
|
||||
Array.prototype.includes = _includes
|
||||
})
|
||||
|
||||
test('from/toArray polyfill', () => {
|
||||
const _from = Array.from
|
||||
Array.from = false
|
||||
|
||||
expect(toArray('foo')).toEqual(['f', 'o', 'o'])
|
||||
|
||||
Array.from = _from
|
||||
})
|
||||
/* eslint-enable no-extend-native */
|
||||
})
|
||||
@@ -1,181 +0,0 @@
|
||||
import path from 'path'
|
||||
import fs from 'fs-extra'
|
||||
import { template } from 'lodash'
|
||||
import webpack from 'webpack'
|
||||
import CopyWebpackPlugin from 'copy-webpack-plugin'
|
||||
import VueLoaderPlugin from 'vue-loader/lib/plugin'
|
||||
import { createRenderer } from 'vue-server-renderer'
|
||||
import stdEnv from 'std-env'
|
||||
|
||||
const renderer = createRenderer()
|
||||
|
||||
export { default as getPort } from 'get-port'
|
||||
|
||||
export function _import(moduleName) {
|
||||
return import(moduleName).then(m => m.default || m)
|
||||
}
|
||||
|
||||
export const useDist = stdEnv.test && stdEnv.ci
|
||||
|
||||
export function getVueMetaPath(browser) {
|
||||
if (useDist) {
|
||||
return path.resolve(
|
||||
__dirname,
|
||||
`../..${browser ? '/dist/vue-meta.min.js' : ''}`
|
||||
)
|
||||
}
|
||||
|
||||
process.server = !browser
|
||||
return path.resolve(__dirname, '../../src')
|
||||
}
|
||||
|
||||
export function webpackRun(config) {
|
||||
const compiler = webpack(config)
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
compiler.run((err, stats) => {
|
||||
if (err) {
|
||||
reject(err)
|
||||
}
|
||||
|
||||
resolve(stats.toJson())
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
export async function buildFixture(fixture, config = {}) {
|
||||
if (!fixture) {
|
||||
throw new Error('buildFixture should be called with a fixture name')
|
||||
}
|
||||
|
||||
const fixturePath = path.resolve(__dirname, '..', 'fixtures', fixture)
|
||||
config.entry = path.resolve(fixturePath, 'client.js')
|
||||
|
||||
if (!config.name) {
|
||||
config.name = path.basename(path.dirname(config.entry))
|
||||
}
|
||||
|
||||
const webpackConfig = createWebpackConfig(config)
|
||||
// remove old files
|
||||
await fs.remove(webpackConfig.output.path)
|
||||
|
||||
// run webpack
|
||||
process.env.NODE_ENV = 'test'
|
||||
const webpackStats = await webpackRun(webpackConfig)
|
||||
|
||||
// for test debugging
|
||||
webpackStats.errors.forEach(e => console.error(e)) // eslint-disable-line no-console
|
||||
webpackStats.warnings.forEach(e => console.warn(e)) // eslint-disable-line no-console
|
||||
|
||||
const createApp = await _import(path.resolve(fixturePath, 'server'))
|
||||
const vueApp = await createApp()
|
||||
|
||||
const templateFile = await fs.readFile(
|
||||
path.resolve(fixturePath, '..', 'app.template.html'),
|
||||
{ encoding: 'utf8' }
|
||||
)
|
||||
const compiled = template(templateFile, { interpolate: /{{([\s\S]+?)}}/g })
|
||||
|
||||
const assets = webpackStats.assets.filter(
|
||||
asset => !asset.name.includes('load-test')
|
||||
)
|
||||
|
||||
const headAssets = assets
|
||||
.filter(asset => asset.name.includes('chunk'))
|
||||
.reduce((s, asset) => `${s}<script src="./${asset.name}"></script>\n`, '')
|
||||
|
||||
const bodyAssets = assets
|
||||
.filter(asset => !asset.name.includes('chunk'))
|
||||
.reduce((s, asset) => `${s}<script src="./${asset.name}"></script>\n`, '')
|
||||
|
||||
const app = await renderer.renderToString(vueApp)
|
||||
|
||||
const metaInfo = vueApp.$meta().inject()
|
||||
|
||||
const appFile = path.resolve(webpackStats.outputPath, 'index.html')
|
||||
const html = compiled({ app, headAssets, bodyAssets, ...metaInfo })
|
||||
|
||||
await fs.writeFile(appFile, html)
|
||||
|
||||
return {
|
||||
url: `file://${appFile}`,
|
||||
appFile,
|
||||
webpackStats,
|
||||
html,
|
||||
metaInfo,
|
||||
}
|
||||
}
|
||||
|
||||
export function createWebpackConfig(config = {}) {
|
||||
const publicPath = '.vue-meta'
|
||||
|
||||
return {
|
||||
mode: 'development',
|
||||
devtool: 'none',
|
||||
output: {
|
||||
path: path.join(path.dirname(config.entry), publicPath),
|
||||
filename: '[name].js',
|
||||
chunkFilename: '[id].chunk.js',
|
||||
publicPath: `/${publicPath}/`,
|
||||
},
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.js$/,
|
||||
exclude: /(node_modules|dist)/,
|
||||
use: {
|
||||
loader: 'babel-loader',
|
||||
options: {
|
||||
presets: [
|
||||
[
|
||||
'@babel/preset-env',
|
||||
{
|
||||
useBuiltIns: 'usage',
|
||||
corejs: 'core-js@3',
|
||||
targets: { ie: 9, safari: '5.1' },
|
||||
},
|
||||
],
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
{ test: /\.vue$/, use: 'vue-loader' },
|
||||
],
|
||||
},
|
||||
// Expose __dirname to allow automatically setting basename.
|
||||
context: __dirname,
|
||||
node: {
|
||||
__dirname: true,
|
||||
},
|
||||
optimization: {
|
||||
splitChunks: {
|
||||
cacheGroups: {
|
||||
vendor: {
|
||||
test: /[\\/]node_modules[\\/]/,
|
||||
name: 'vendor',
|
||||
chunks: 'all',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: [
|
||||
new VueLoaderPlugin(),
|
||||
new webpack.DefinePlugin({
|
||||
'process.env': {
|
||||
// make sure our simple polyfills are enabled
|
||||
NODE_ENV: '"test"',
|
||||
},
|
||||
}),
|
||||
new CopyWebpackPlugin([
|
||||
{ from: path.join(path.dirname(config.entry), 'static') },
|
||||
]),
|
||||
],
|
||||
resolve: {
|
||||
alias: {
|
||||
vue: 'vue/dist/vue.esm.js',
|
||||
'vue-meta': getVueMetaPath(true),
|
||||
},
|
||||
},
|
||||
...config,
|
||||
}
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
import { JSDOM } from 'jsdom'
|
||||
import {
|
||||
mount,
|
||||
shallowMount,
|
||||
createWrapper,
|
||||
createLocalVue,
|
||||
} from '@vue/test-utils'
|
||||
import { render, renderToString } from '@vue/server-test-utils'
|
||||
import { attributeMap } from '../../src/client/updaters/attribute'
|
||||
import { defaultOptions } from '../../src/shared/constants'
|
||||
import VueMetaPlugin from '../../src'
|
||||
|
||||
export {
|
||||
mount,
|
||||
shallowMount,
|
||||
createWrapper,
|
||||
render,
|
||||
renderToString,
|
||||
VueMetaPlugin,
|
||||
attributeMap,
|
||||
}
|
||||
|
||||
export function getVue() {
|
||||
return createLocalVue()
|
||||
}
|
||||
|
||||
export function loadVueMetaPlugin(options, localVue = getVue()) {
|
||||
localVue.use(VueMetaPlugin, Object.assign({}, defaultOptions, options))
|
||||
|
||||
return localVue
|
||||
}
|
||||
|
||||
export const vmTick = vm => {
|
||||
return new Promise(resolve => {
|
||||
vm.$nextTick(resolve)
|
||||
})
|
||||
}
|
||||
|
||||
export const pTick = () => new Promise(resolve => process.nextTick(resolve))
|
||||
|
||||
export function createDOM(html = '<!DOCTYPE html>', options = {}) {
|
||||
const dom = new JSDOM(html, options)
|
||||
|
||||
return {
|
||||
dom,
|
||||
window: dom.window,
|
||||
document: dom.window.document,
|
||||
}
|
||||
}
|
||||
|
||||
// dirty hack to remove data from previous test
|
||||
// this is ok because this code normally only runs on
|
||||
// the client and not during ssr
|
||||
// TODO: findout why jest.resetModules doesnt work for this
|
||||
export function clearClientAttributeMap() {
|
||||
Object.keys(attributeMap).forEach(key => delete attributeMap[key])
|
||||
}
|
||||
@@ -1,359 +0,0 @@
|
||||
import { defaultOptions } from '../../src/shared/constants'
|
||||
import { attributeMap } from '../../src/client/updaters/attribute'
|
||||
|
||||
const metaInfoData = {
|
||||
title: {
|
||||
add: {
|
||||
data: 'Hello World',
|
||||
expect: ['<title>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="ssr" href="href">'],
|
||||
},
|
||||
change: {
|
||||
data: [{ href: 'href2' }],
|
||||
expect: ['<base data-vue-meta="ssr" href="href2">'],
|
||||
},
|
||||
remove: {
|
||||
data: [],
|
||||
expect: [''],
|
||||
},
|
||||
},
|
||||
meta: {
|
||||
add: {
|
||||
data: [{ charset: 'utf-8' }, { property: 'a', content: 'a' }],
|
||||
expect: [
|
||||
'<meta data-vue-meta="ssr" charset="utf-8">',
|
||||
'<meta data-vue-meta="ssr" property="a" content="a">',
|
||||
],
|
||||
},
|
||||
change: {
|
||||
data: [{ charset: 'utf-16' }, { property: 'a', content: 'b' }],
|
||||
expect: [
|
||||
'<meta data-vue-meta="ssr" charset="utf-16">',
|
||||
'<meta data-vue-meta="ssr" 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="ssr" charset="utf-16">',
|
||||
'<meta data-vue-meta="ssr" property="a" content="c">',
|
||||
],
|
||||
test(side, defaultTest) {
|
||||
if (side === 'client') {
|
||||
return () => {
|
||||
const tags = defaultTest()
|
||||
|
||||
expect(tags.tagsAdded.meta.length).toBe(1)
|
||||
// TODO: not sure if we really expect this
|
||||
expect(tags.tagsRemoved.meta.length).toBe(1)
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
remove: {
|
||||
data: [],
|
||||
expect: [''],
|
||||
},
|
||||
},
|
||||
link: {
|
||||
add: {
|
||||
data: [{ rel: 'stylesheet', href: 'href' }],
|
||||
expect: ['<link data-vue-meta="ssr" rel="stylesheet" href="href">'],
|
||||
},
|
||||
change: {
|
||||
data: [{ rel: 'stylesheet', href: 'href', media: 'screen' }],
|
||||
expect: [
|
||||
'<link data-vue-meta="ssr" rel="stylesheet" href="href" media="screen">',
|
||||
],
|
||||
},
|
||||
remove: {
|
||||
data: [],
|
||||
expect: [''],
|
||||
},
|
||||
},
|
||||
style: {
|
||||
add: {
|
||||
data: [{ type: 'text/css', cssText: '.foo { color: red; }' }],
|
||||
expect: [
|
||||
'<style data-vue-meta="ssr" type="text/css">.foo { color: red; }</style>',
|
||||
],
|
||||
},
|
||||
change: {
|
||||
data: [{ type: 'text/css', cssText: '.foo { color: blue; }' }],
|
||||
expect: [
|
||||
'<style data-vue-meta="ssr" type="text/css">.foo { color: blue; }</style>',
|
||||
],
|
||||
},
|
||||
remove: {
|
||||
data: [],
|
||||
expect: [''],
|
||||
},
|
||||
},
|
||||
script: {
|
||||
add: {
|
||||
data: [
|
||||
{
|
||||
src: 'src1',
|
||||
async: false,
|
||||
defer: true,
|
||||
[defaultOptions.tagIDKeyName]: 'content',
|
||||
callback: () => {},
|
||||
},
|
||||
{ src: 'src-prepend', async: true, defer: false, pbody: true },
|
||||
{ src: 'src2', async: false, defer: true, body: true },
|
||||
{ src: 'src3', async: false, skip: true },
|
||||
{
|
||||
type: 'application/ld+json',
|
||||
json: {
|
||||
'@context': 'http://schema.org',
|
||||
'@type': 'Organization',
|
||||
name: 'MyApp',
|
||||
url: 'https://www.myurl.com',
|
||||
logo: 'https://www.myurl.com/images/logo.png',
|
||||
},
|
||||
},
|
||||
],
|
||||
expect: [
|
||||
'<script data-vue-meta="ssr" src="src1" defer data-vmid="content" onload="this.__vm_l=1"></script>',
|
||||
'<script data-vue-meta="ssr" src="src-prepend" async data-pbody="true"></script>',
|
||||
'<script data-vue-meta="ssr" src="src2" defer data-body="true"></script>',
|
||||
'<script data-vue-meta="ssr" type="application/ld+json">{"@context":"http://schema.org","@type":"Organization","name":"MyApp","url":"https://www.myurl.com","logo":"https://www.myurl.com/images/logo.png"}</script>',
|
||||
],
|
||||
test(side, defaultTest) {
|
||||
return () => {
|
||||
if (side === 'client') {
|
||||
for (const index in this.expect) {
|
||||
this.expect[index] = this.expect[index].replace(
|
||||
/(async|defer)/g,
|
||||
'$1=""'
|
||||
)
|
||||
this.expect[index] = this.expect[index].replace(
|
||||
/ onload="this.__vm_l=1"/,
|
||||
''
|
||||
)
|
||||
}
|
||||
const tags = defaultTest()
|
||||
|
||||
expect(tags.tagsAdded.script[0].parentNode.tagName).toBe('HEAD')
|
||||
expect(tags.tagsAdded.script[1].parentNode.tagName).toBe('BODY')
|
||||
expect(tags.tagsAdded.script[2].parentNode.tagName).toBe('BODY')
|
||||
} else {
|
||||
// ssr doesnt generate data-body tags
|
||||
const bodyPrepended = this.expect[1]
|
||||
const bodyAppended = this.expect[2]
|
||||
this.expect = [this.expect.shift(), this.expect.pop()]
|
||||
|
||||
const tags = defaultTest()
|
||||
const html = tags.text()
|
||||
|
||||
expect(html).not.toContain(bodyPrepended)
|
||||
expect(html).not.toContain(bodyAppended)
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
// this test only runs for client so we can directly expect wrong boolean attributes
|
||||
change: {
|
||||
data: [
|
||||
{
|
||||
src: 'src',
|
||||
async: true,
|
||||
defer: true,
|
||||
[defaultOptions.tagIDKeyName]: 'content2',
|
||||
},
|
||||
],
|
||||
expect: [
|
||||
'<script data-vue-meta="ssr" src="src" async="" defer="" data-vmid="content2"></script>',
|
||||
],
|
||||
},
|
||||
remove: {
|
||||
data: [],
|
||||
expect: [''],
|
||||
},
|
||||
},
|
||||
noscript: {
|
||||
add: {
|
||||
data: [{ innerHTML: '<p>noscript</p>' }],
|
||||
expect: ['<noscript data-vue-meta="ssr"><p>noscript</p></noscript>'],
|
||||
},
|
||||
change: {
|
||||
data: [{ innerHTML: '<p>noscript, no really</p>' }],
|
||||
expect: [
|
||||
'<noscript data-vue-meta="ssr"><p>noscript, no really</p></noscript>',
|
||||
],
|
||||
},
|
||||
remove: {
|
||||
data: [],
|
||||
expect: [''],
|
||||
},
|
||||
},
|
||||
htmlAttrs: {
|
||||
add: {
|
||||
data: { foo: 'bar' },
|
||||
expect: [
|
||||
'<html foo="bar" data-vue-meta="%7B%22foo%22:%7B%22ssr%22:%22bar%22%7D%7D">',
|
||||
],
|
||||
test(side, defaultTest) {
|
||||
return () => {
|
||||
if (side === 'client') {
|
||||
this.expect[0] = this.expect[0].replace(
|
||||
/ data-vue-meta="[^"]+"/,
|
||||
''
|
||||
)
|
||||
}
|
||||
|
||||
defaultTest()
|
||||
|
||||
if (side === 'client') {
|
||||
expect(attributeMap).toEqual({ htmlAttrs: { foo: { ssr: 'bar' } } })
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
change: {
|
||||
data: { foo: 'baz' },
|
||||
expect: ['<html foo="baz">'],
|
||||
test(side, defaultTest) {
|
||||
return () => {
|
||||
defaultTest()
|
||||
|
||||
expect(attributeMap).toEqual({ htmlAttrs: { foo: { ssr: 'baz' } } })
|
||||
}
|
||||
},
|
||||
},
|
||||
remove: {
|
||||
data: {},
|
||||
expect: ['<html>'],
|
||||
test(side, defaultTest) {
|
||||
return () => {
|
||||
defaultTest()
|
||||
|
||||
expect(attributeMap).toEqual({ htmlAttrs: { foo: {} } })
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
headAttrs: {
|
||||
add: {
|
||||
data: { foo: 'bar' },
|
||||
expect: [
|
||||
'<head foo="bar" data-vue-meta="%7B%22foo%22:%7B%22ssr%22:%22bar%22%7D%7D">',
|
||||
],
|
||||
test(side, defaultTest) {
|
||||
return () => {
|
||||
if (side === 'client') {
|
||||
this.expect[0] = this.expect[0].replace(
|
||||
/ data-vue-meta="[^"]+"/,
|
||||
''
|
||||
)
|
||||
}
|
||||
|
||||
defaultTest()
|
||||
|
||||
if (side === 'client') {
|
||||
expect(attributeMap).toEqual({ headAttrs: { foo: { ssr: 'bar' } } })
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
change: {
|
||||
data: { foo: 'baz' },
|
||||
expect: ['<head foo="baz">'],
|
||||
test(side, defaultTest) {
|
||||
return () => {
|
||||
defaultTest()
|
||||
|
||||
expect(attributeMap).toEqual({ headAttrs: { foo: { ssr: 'baz' } } })
|
||||
}
|
||||
},
|
||||
},
|
||||
remove: {
|
||||
data: {},
|
||||
expect: ['<head>'],
|
||||
test(side, defaultTest) {
|
||||
return () => {
|
||||
defaultTest()
|
||||
|
||||
expect(attributeMap).toEqual({ headAttrs: { foo: {} } })
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
bodyAttrs: {
|
||||
add: {
|
||||
data: { foo: 'bar', fizz: ['fuzz', 'fozz'] },
|
||||
expect: [
|
||||
'<body foo="bar" fizz="fuzz fozz" data-vue-meta="%7B%22foo%22:%7B%22ssr%22:%22bar%22%7D,%22fizz%22:%7B%22ssr%22:%5B%22fuzz%22,%22fozz%22%5D%7D%7D">',
|
||||
],
|
||||
test(side, defaultTest) {
|
||||
return () => {
|
||||
if (side === 'client') {
|
||||
this.expect[0] = this.expect[0].replace(
|
||||
/ data-vue-meta="[^"]+"/,
|
||||
''
|
||||
)
|
||||
}
|
||||
|
||||
defaultTest()
|
||||
|
||||
if (side === 'client') {
|
||||
expect(attributeMap).toEqual({
|
||||
bodyAttrs: {
|
||||
foo: { ssr: 'bar' },
|
||||
fizz: { ssr: ['fuzz', 'fozz'] },
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
change: {
|
||||
data: { foo: 'baz' },
|
||||
expect: ['<body foo="baz">'],
|
||||
test(side, defaultTest) {
|
||||
return () => {
|
||||
defaultTest()
|
||||
|
||||
expect(attributeMap).toEqual({
|
||||
bodyAttrs: { foo: { ssr: 'baz' }, fizz: {} },
|
||||
})
|
||||
}
|
||||
},
|
||||
},
|
||||
remove: {
|
||||
data: {},
|
||||
expect: ['<body>'],
|
||||
test(side, defaultTest) {
|
||||
return () => {
|
||||
defaultTest()
|
||||
|
||||
expect(attributeMap).toEqual({ bodyAttrs: { foo: {}, fizz: {} } })
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
export default metaInfoData
|
||||
@@ -1,4 +0,0 @@
|
||||
process.server = true
|
||||
|
||||
jest.useFakeTimers()
|
||||
jest.setTimeout(30000)
|
||||
Reference in New Issue
Block a user