From f7450592708e4b9cce21e48f41085900c68217e4 Mon Sep 17 00:00:00 2001 From: pimlie Date: Tue, 17 Sep 2019 13:30:08 +0200 Subject: [PATCH] feat: add option waitOnDestroyed --- src/shared/$meta.js | 9 ++++-- src/shared/constants.js | 4 +++ src/shared/mixin.js | 25 ++++++++++------ src/shared/options.js | 5 ++-- test/unit/components.test.js | 56 ++++++++++++++++++++++++++++++++---- test/unit/generators.test.js | 2 +- test/unit/plugin.test.js | 20 +++++++++++++ 7 files changed, 102 insertions(+), 19 deletions(-) diff --git a/src/shared/$meta.js b/src/shared/$meta.js index 4cde83d..26b4ea9 100644 --- a/src/shared/$meta.js +++ b/src/shared/$meta.js @@ -20,17 +20,22 @@ export default function $meta (options) { 'setOptions': (newOptions) => { const refreshNavKey = 'refreshOnceOnNavigation' if (newOptions && newOptions[refreshNavKey]) { - options.refreshOnceOnNavigation = newOptions[refreshNavKey] + options.refreshOnceOnNavigation = !!newOptions[refreshNavKey] addNavGuards($root) } const debounceWaitKey = 'debounceWait' - if (newOptions && newOptions[debounceWaitKey]) { + if (newOptions && debounceWaitKey in newOptions) { const debounceWait = parseInt(newOptions[debounceWaitKey]) if (!isNaN(debounceWait)) { options.debounceWait = debounceWait } } + + const waitOnDestroyedKey = 'waitOnDestroyed' + if (newOptions && waitOnDestroyedKey in newOptions) { + options.waitOnDestroyed = !!newOptions[waitOnDestroyedKey] + } }, 'refresh': () => refresh($root, options), 'inject': () => process.server ? inject($root, options) : showWarningNotSupportedInBrowserBundle('inject'), diff --git a/src/shared/constants.js b/src/shared/constants.js index 2a8e717..539d56a 100644 --- a/src/shared/constants.js +++ b/src/shared/constants.js @@ -52,6 +52,9 @@ export const ssrAppId = 'ssr' // How long meta update export const debounceWait = 10 +// How long meta update +export const waitOnDestroyed = true + export const defaultOptions = { keyName, attribute, @@ -59,6 +62,7 @@ export const defaultOptions = { tagIDKeyName, contentKeyName, metaTemplateKeyName, + waitOnDestroyed, debounceWait, ssrAppId } diff --git a/src/shared/mixin.js b/src/shared/mixin.js index e792b2b..517ce57 100644 --- a/src/shared/mixin.js +++ b/src/shared/mixin.js @@ -147,25 +147,32 @@ export default function createMixin (Vue, options) { }, // TODO: move back into beforeCreate when Vue issue is resolved destroyed () { - const $this = this // do not trigger refresh: + // - when user configured to not wait for transitions on destroyed // - when the component doesnt have a parent // - doesnt have metaInfo defined - if (!$this.$parent || !hasMetaInfo($this)) { + if (!this.$parent || !hasMetaInfo(this)) { return } - // Wait that element is hidden before refreshing meta tags (to support animations) - const interval = setInterval(() => { - if ($this.$el && $this.$el.offsetParent !== null) { - /* istanbul ignore next line */ + this.$nextTick(() => { + if (!options.waitOnDestroyed || !this.$el || !this.$el.offsetParent) { + triggerUpdate(options, this.$root, 'destroyed') return } - clearInterval(interval) + // Wait that element is hidden before refreshing meta tags (to support animations) + const interval = setInterval(() => { + if (this.$el && this.$el.offsetParent !== null) { + /* istanbul ignore next line */ + return + } - triggerUpdate(options, $this.$root, 'destroyed') - }, 50) + clearInterval(interval) + + triggerUpdate(options, this.$root, 'destroyed') + }, 50) + }) } } } diff --git a/src/shared/options.js b/src/shared/options.js index b961c15..ba8a206 100644 --- a/src/shared/options.js +++ b/src/shared/options.js @@ -1,4 +1,4 @@ -import { isObject } from '../utils/is-type' +import { isObject, isUndefined } from '../utils/is-type' import { defaultOptions } from './constants' export function setOptions (options) { @@ -17,7 +17,8 @@ export function setOptions (options) { tagIDKeyName: options['tagIDKeyName'] || defaultOptions.tagIDKeyName, contentKeyName: options['contentKeyName'] || defaultOptions.contentKeyName, 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, refreshOnceOnNavigation: !!options['refreshOnceOnNavigation'] } diff --git a/test/unit/components.test.js b/test/unit/components.test.js index 8f011b4..2b9e3d0 100644 --- a/test/unit/components.test.js +++ b/test/unit/components.test.js @@ -1,5 +1,6 @@ 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' @@ -10,6 +11,7 @@ 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 })) @@ -48,6 +50,8 @@ describe('components', () => { }) 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)) @@ -209,6 +213,10 @@ describe('components', () => { }) 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 @@ -226,6 +234,9 @@ describe('components', () => { expect(changed).toHaveBeenCalledTimes(2) expect(context._uid).toBe(wrapper.vm._uid) + + triggerUpdate.mockRestore() + batchUpdate.mockRestore() }) test('afterNavigation function is called with refreshOnce: true', async () => { @@ -299,6 +310,10 @@ describe('components', () => { }) 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') @@ -342,9 +357,16 @@ describe('components', () => { 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') @@ -386,6 +408,9 @@ describe('components', () => { expect(html.getAttribute('theme')).toBe('dark') wrapper.destroy() + + triggerUpdate.mockRestore() + batchUpdate.mockRestore() }) test('can add/remove meta info from additional app ', () => { @@ -504,13 +529,34 @@ describe('components', () => { expect(guards.after).not.toBeUndefined() }) - test('can set option debounceWait runtime', () => { - const wrapper = mount(HelloWorld, { localVue: Vue }) + 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) - 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') }) }) diff --git a/test/unit/generators.test.js b/test/unit/generators.test.js index 84de215..9dc2664 100644 --- a/test/unit/generators.test.js +++ b/test/unit/generators.test.js @@ -61,7 +61,7 @@ describe('generators', () => { } }) -describe.only('extra tests', () => { +describe('extra tests', () => { test('empty config doesnt generate a tag', () => { const { meta } = generateServerInjector({ meta: [] }) diff --git a/test/unit/plugin.test.js b/test/unit/plugin.test.js index dd22a71..1d2cccc 100644 --- a/test/unit/plugin.test.js +++ b/test/unit/plugin.test.js @@ -263,4 +263,24 @@ describe('plugin', () => { 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) + }) })