2
0
mirror of https://github.com/tenrok/vue-meta.git synced 2026-05-17 04:29:37 +03:00

chore: remove old tests

This commit is contained in:
pimlie
2020-11-01 20:54:34 +01:00
parent 642a62c561
commit b61b44d5a8
28 changed files with 0 additions and 3801 deletions
-30
View File
@@ -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>
-26
View File
@@ -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>
-24
View File
@@ -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>
-28
View File
@@ -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>
-149
View File
@@ -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)
})
})
-45
View File
@@ -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()
})
})
-20
View File
@@ -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>
-25
View File
@@ -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>
-10
View File
@@ -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')
-18
View File
@@ -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 },
],
})
}
-14
View File
@@ -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'
-16
View File
@@ -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>
-39
View File
@@ -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>
-626
View File
@@ -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'
)
})
})
-147
View File
@@ -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, '&amp;']])).toEqual({
title: 'Hello &amp; 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, '&amp;']])).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, '&amp;']])).toEqual({
title: 'Hello',
titleChunk: 'Hello',
titleTemplate: '%s',
htmlAttrs: {},
headAttrs: {},
bodyAttrs: {},
meta: [],
base: [],
link: [],
style: [],
script: [
{ innerHTML: 'Hello &amp; 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:
'&lt;/script&gt;&lt;p class=&quot;unsafe&quot;&gt;This is safe&lt;/p&gt;&lt;script&gt;',
'&lt;/script&gt;unsafeKey': 'This is also still safe',
},
},
],
noscript: [],
__dangerouslyDisableSanitizers: [],
__dangerouslyDisableSanitizersByTagID: {},
})
})
})
-158
View File
@@ -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"'
)
})
})
-171
View File
@@ -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)
})
})
-871
View File
@@ -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')
)
})
})
-226
View File
@@ -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)
})
})
-302
View File
@@ -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)
})
})
-44
View File
@@ -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)
})
})
-136
View File
@@ -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)
})
})
-74
View File
@@ -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 */
})
-181
View File
@@ -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,
}
}
-57
View File
@@ -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])
}
-359
View File
@@ -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
-4
View File
@@ -1,4 +0,0 @@
process.server = true
jest.useFakeTimers()
jest.setTimeout(30000)