2
0
mirror of https://github.com/tenrok/vue-meta.git synced 2026-06-25 11:40:33 +03:00

feat: add option waitOnDestroyed

This commit is contained in:
pimlie
2019-09-17 13:30:08 +02:00
committed by Pim
parent d43b77cce6
commit f745059270
7 changed files with 102 additions and 19 deletions
+7 -2
View File
@@ -20,17 +20,22 @@ export default function $meta (options) {
'setOptions': (newOptions) => { 'setOptions': (newOptions) => {
const refreshNavKey = 'refreshOnceOnNavigation' const refreshNavKey = 'refreshOnceOnNavigation'
if (newOptions && newOptions[refreshNavKey]) { if (newOptions && newOptions[refreshNavKey]) {
options.refreshOnceOnNavigation = newOptions[refreshNavKey] options.refreshOnceOnNavigation = !!newOptions[refreshNavKey]
addNavGuards($root) addNavGuards($root)
} }
const debounceWaitKey = 'debounceWait' const debounceWaitKey = 'debounceWait'
if (newOptions && newOptions[debounceWaitKey]) { if (newOptions && debounceWaitKey in newOptions) {
const debounceWait = parseInt(newOptions[debounceWaitKey]) const debounceWait = parseInt(newOptions[debounceWaitKey])
if (!isNaN(debounceWait)) { if (!isNaN(debounceWait)) {
options.debounceWait = debounceWait options.debounceWait = debounceWait
} }
} }
const waitOnDestroyedKey = 'waitOnDestroyed'
if (newOptions && waitOnDestroyedKey in newOptions) {
options.waitOnDestroyed = !!newOptions[waitOnDestroyedKey]
}
}, },
'refresh': () => refresh($root, options), 'refresh': () => refresh($root, options),
'inject': () => process.server ? inject($root, options) : showWarningNotSupportedInBrowserBundle('inject'), 'inject': () => process.server ? inject($root, options) : showWarningNotSupportedInBrowserBundle('inject'),
+4
View File
@@ -52,6 +52,9 @@ export const ssrAppId = 'ssr'
// How long meta update // How long meta update
export const debounceWait = 10 export const debounceWait = 10
// How long meta update
export const waitOnDestroyed = true
export const defaultOptions = { export const defaultOptions = {
keyName, keyName,
attribute, attribute,
@@ -59,6 +62,7 @@ export const defaultOptions = {
tagIDKeyName, tagIDKeyName,
contentKeyName, contentKeyName,
metaTemplateKeyName, metaTemplateKeyName,
waitOnDestroyed,
debounceWait, debounceWait,
ssrAppId ssrAppId
} }
+11 -4
View File
@@ -147,25 +147,32 @@ export default function createMixin (Vue, options) {
}, },
// TODO: move back into beforeCreate when Vue issue is resolved // TODO: move back into beforeCreate when Vue issue is resolved
destroyed () { destroyed () {
const $this = this
// do not trigger refresh: // do not trigger refresh:
// - when user configured to not wait for transitions on destroyed
// - when the component doesnt have a parent // - when the component doesnt have a parent
// - doesnt have metaInfo defined // - doesnt have metaInfo defined
if (!$this.$parent || !hasMetaInfo($this)) { if (!this.$parent || !hasMetaInfo(this)) {
return
}
this.$nextTick(() => {
if (!options.waitOnDestroyed || !this.$el || !this.$el.offsetParent) {
triggerUpdate(options, this.$root, 'destroyed')
return return
} }
// Wait that element is hidden before refreshing meta tags (to support animations) // Wait that element is hidden before refreshing meta tags (to support animations)
const interval = setInterval(() => { const interval = setInterval(() => {
if ($this.$el && $this.$el.offsetParent !== null) { if (this.$el && this.$el.offsetParent !== null) {
/* istanbul ignore next line */ /* istanbul ignore next line */
return return
} }
clearInterval(interval) clearInterval(interval)
triggerUpdate(options, $this.$root, 'destroyed') triggerUpdate(options, this.$root, 'destroyed')
}, 50) }, 50)
})
} }
} }
} }
+3 -2
View File
@@ -1,4 +1,4 @@
import { isObject } from '../utils/is-type' import { isObject, isUndefined } from '../utils/is-type'
import { defaultOptions } from './constants' import { defaultOptions } from './constants'
export function setOptions (options) { export function setOptions (options) {
@@ -17,7 +17,8 @@ export function setOptions (options) {
tagIDKeyName: options['tagIDKeyName'] || defaultOptions.tagIDKeyName, tagIDKeyName: options['tagIDKeyName'] || defaultOptions.tagIDKeyName,
contentKeyName: options['contentKeyName'] || defaultOptions.contentKeyName, contentKeyName: options['contentKeyName'] || defaultOptions.contentKeyName,
metaTemplateKeyName: options['metaTemplateKeyName'] || defaultOptions.metaTemplateKeyName, metaTemplateKeyName: options['metaTemplateKeyName'] || defaultOptions.metaTemplateKeyName,
debounceWait: options['debounceWait'] || defaultOptions.debounceWait, debounceWait: isUndefined(options['debounceWait']) ? defaultOptions.debounceWait : options['debounceWait'],
waitOnDestroyed: isUndefined(options['waitOnDestroyed']) ? defaultOptions.waitOnDestroyed : options['waitOnDestroyed'],
ssrAppId: options['ssrAppId'] || defaultOptions.ssrAppId, ssrAppId: options['ssrAppId'] || defaultOptions.ssrAppId,
refreshOnceOnNavigation: !!options['refreshOnceOnNavigation'] refreshOnceOnNavigation: !!options['refreshOnceOnNavigation']
} }
+51 -5
View File
@@ -1,5 +1,6 @@
import { getComponentMetaInfo } from '../../src/shared/getComponentOption' import { getComponentMetaInfo } from '../../src/shared/getComponentOption'
import _getMetaInfo from '../../src/shared/getMetaInfo' import _getMetaInfo from '../../src/shared/getMetaInfo'
import { triggerUpdate, batchUpdate } from '../../src/client/update'
import { mount, createWrapper, loadVueMetaPlugin, vmTick, clearClientAttributeMap } from '../utils' import { mount, createWrapper, loadVueMetaPlugin, vmTick, clearClientAttributeMap } from '../utils'
import { defaultOptions } from '../../src/shared/constants' import { defaultOptions } from '../../src/shared/constants'
@@ -10,6 +11,7 @@ import Changed from '../components/changed.vue'
const getMetaInfo = component => _getMetaInfo(defaultOptions, getComponentMetaInfo(defaultOptions, component)) const getMetaInfo = component => _getMetaInfo(defaultOptions, getComponentMetaInfo(defaultOptions, component))
jest.mock('../../src/client/update')
jest.mock('../../src/utils/window', () => ({ jest.mock('../../src/utils/window', () => ({
hasGlobalWindow: false hasGlobalWindow: false
})) }))
@@ -48,6 +50,8 @@ describe('components', () => {
}) })
afterEach(() => { afterEach(() => {
jest.clearAllMocks()
elements.html.getAttributeNames().forEach(name => elements.html.removeAttribute(name)) elements.html.getAttributeNames().forEach(name => elements.html.removeAttribute(name))
elements.head.childNodes.forEach(child => child.remove()) elements.head.childNodes.forEach(child => child.remove())
elements.head.getAttributeNames().forEach(name => elements.head.removeAttribute(name)) elements.head.getAttributeNames().forEach(name => elements.head.removeAttribute(name))
@@ -209,6 +213,10 @@ describe('components', () => {
}) })
test('changed function is called', async () => { test('changed function is called', async () => {
const update = jest.requireActual('../../src/client/update')
triggerUpdate.mockImplementation(update.triggerUpdate)
batchUpdate.mockImplementation(update.batchUpdate)
let context let context
const changed = jest.fn(function () { const changed = jest.fn(function () {
context = this context = this
@@ -226,6 +234,9 @@ describe('components', () => {
expect(changed).toHaveBeenCalledTimes(2) expect(changed).toHaveBeenCalledTimes(2)
expect(context._uid).toBe(wrapper.vm._uid) expect(context._uid).toBe(wrapper.vm._uid)
triggerUpdate.mockRestore()
batchUpdate.mockRestore()
}) })
test('afterNavigation function is called with refreshOnce: true', async () => { test('afterNavigation function is called with refreshOnce: true', async () => {
@@ -299,6 +310,10 @@ describe('components', () => {
}) })
test('changes before hydration initialization trigger an update', async () => { 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 const { html } = elements
html.setAttribute(defaultOptions.ssrAttribute, 'true') html.setAttribute(defaultOptions.ssrAttribute, 'true')
@@ -342,9 +357,16 @@ describe('components', () => {
expect(html.getAttribute('theme')).toBe('dark') expect(html.getAttribute('theme')).toBe('dark')
wrapper.destroy() wrapper.destroy()
triggerUpdate.mockRestore()
batchUpdate.mockRestore()
}) })
test('changes during hydration initialization trigger an update', async () => { 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 const { html } = elements
html.setAttribute(defaultOptions.ssrAttribute, 'true') html.setAttribute(defaultOptions.ssrAttribute, 'true')
@@ -386,6 +408,9 @@ describe('components', () => {
expect(html.getAttribute('theme')).toBe('dark') expect(html.getAttribute('theme')).toBe('dark')
wrapper.destroy() wrapper.destroy()
triggerUpdate.mockRestore()
batchUpdate.mockRestore()
}) })
test('can add/remove meta info from additional app ', () => { test('can add/remove meta info from additional app ', () => {
@@ -504,13 +529,34 @@ describe('components', () => {
expect(guards.after).not.toBeUndefined() expect(guards.after).not.toBeUndefined()
}) })
test('can set option debounceWait runtime', () => { test('destroyed hook calls triggerUpdate delayed', async () => {
const wrapper = mount(HelloWorld, { localVue: Vue }) jest.useFakeTimers()
const wrapper = mount(HelloWorld, { localVue: Vue, parentComponent: { render: h => h('div') } })
const spy = jest.spyOn(wrapper.vm.$el, 'offsetParent', 'get').mockReturnValue(true)
expect(wrapper.vm.$meta().getOptions().debounceWait).toBe(10) wrapper.destroy()
wrapper.vm.$meta().setOptions({ debounceWait: 69420 }) await vmTick(wrapper.vm)
expect(wrapper.vm.$meta().getOptions().debounceWait).toBe(69420) 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 -1
View File
@@ -61,7 +61,7 @@ describe('generators', () => {
} }
}) })
describe.only('extra tests', () => { describe('extra tests', () => {
test('empty config doesnt generate a tag', () => { test('empty config doesnt generate a tag', () => {
const { meta } = generateServerInjector({ meta: [] }) const { meta } = generateServerInjector({ meta: [] })
+20
View File
@@ -263,4 +263,24 @@ describe('plugin', () => {
jest.advanceTimersByTime(10) jest.advanceTimersByTime(10)
expect(refreshSpy).toHaveBeenCalled() 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)
})
}) })