mirror of
https://github.com/tenrok/vue-meta.git
synced 2026-06-23 17:30:34 +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