diff --git a/src/client/update.js b/src/client/update.js index 2e557d9..3e661ee 100644 --- a/src/client/update.js +++ b/src/client/update.js @@ -3,7 +3,7 @@ import { rootConfigKey } from '../shared/constants' // store an id to keep track of DOM updates let batchId = null -export function triggerUpdate (rootVm, hookName) { +export function triggerUpdate ({ debounceWait }, rootVm, hookName) { // if an update was triggered during initialization or when an update was triggered by the // metaInfo watcher, set initialized to null // then we keep falsy value but know we need to run a triggerUpdate after initialization @@ -13,7 +13,7 @@ export function triggerUpdate (rootVm, hookName) { if (rootVm[rootConfigKey].initialized && !rootVm[rootConfigKey].pausing) { // batch potential DOM updates to prevent extraneous re-rendering - batchUpdate(() => rootVm.$meta().refresh()) + batchUpdate(() => void rootVm.$meta().refresh(), debounceWait) } } @@ -25,10 +25,14 @@ export function triggerUpdate (rootVm, hookName) { * @return {Number} id - a new ID */ export function batchUpdate (callback, timeout) { - timeout = timeout || 10 + timeout = timeout === undefined ? 10 : timeout + + if (!timeout) { + callback() + return + } clearTimeout(batchId) - batchId = setTimeout(() => { callback() }, timeout) diff --git a/src/shared/$meta.js b/src/shared/$meta.js index b1c331d..4cde83d 100644 --- a/src/shared/$meta.js +++ b/src/shared/$meta.js @@ -23,6 +23,14 @@ export default function $meta (options) { options.refreshOnceOnNavigation = newOptions[refreshNavKey] addNavGuards($root) } + + const debounceWaitKey = 'debounceWait' + if (newOptions && newOptions[debounceWaitKey]) { + const debounceWait = parseInt(newOptions[debounceWaitKey]) + if (!isNaN(debounceWait)) { + options.debounceWait = debounceWait + } + } }, '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 58962c6..2a8e717 100644 --- a/src/shared/constants.js +++ b/src/shared/constants.js @@ -49,6 +49,9 @@ export const contentKeyName = 'content' // The id used for the ssr app export const ssrAppId = 'ssr' +// How long meta update +export const debounceWait = 10 + export const defaultOptions = { keyName, attribute, @@ -56,6 +59,7 @@ export const defaultOptions = { tagIDKeyName, contentKeyName, metaTemplateKeyName, + debounceWait, ssrAppId } diff --git a/src/shared/mixin.js b/src/shared/mixin.js index f0dc76f..e792b2b 100644 --- a/src/shared/mixin.js +++ b/src/shared/mixin.js @@ -69,7 +69,7 @@ export default function createMixin (Vue, options) { // credit for this suggestion goes to [Sébastien Chopin](https://github.com/Atinux) ensuredPush($options, 'created', function () { this.$watch('$metaInfo', function () { - triggerUpdate(this[rootKey], 'watcher') + triggerUpdate(options, this[rootKey], 'watcher') }) }) } @@ -112,7 +112,7 @@ export default function createMixin (Vue, options) { // current hook was called // (during initialization all changes are blocked) if (tags === false && $root[rootConfigKey].initialized === null) { - this.$nextTick(() => triggerUpdate($root, 'init')) + this.$nextTick(() => triggerUpdate(options, $root, 'init')) } $root[rootConfigKey].initialized = true @@ -126,7 +126,6 @@ export default function createMixin (Vue, options) { }) } }) - // add the navigation guards if requested if (options.refreshOnceOnNavigation) { addNavGuards($root) @@ -142,7 +141,7 @@ export default function createMixin (Vue, options) { // no need to add this hooks on server side updateOnLifecycleHook.forEach((lifecycleHook) => { ensuredPush($options, lifecycleHook, function () { - triggerUpdate(this[rootKey], lifecycleHook) + triggerUpdate(options, this[rootKey], lifecycleHook) }) }) }, @@ -165,7 +164,7 @@ export default function createMixin (Vue, options) { clearInterval(interval) - triggerUpdate($this.$root, 'destroyed') + triggerUpdate(options, $this.$root, 'destroyed') }, 50) } } diff --git a/src/shared/options.js b/src/shared/options.js index 80c6493..b961c15 100644 --- a/src/shared/options.js +++ b/src/shared/options.js @@ -17,6 +17,7 @@ export function setOptions (options) { tagIDKeyName: options['tagIDKeyName'] || defaultOptions.tagIDKeyName, contentKeyName: options['contentKeyName'] || defaultOptions.contentKeyName, metaTemplateKeyName: options['metaTemplateKeyName'] || defaultOptions.metaTemplateKeyName, + debounceWait: options['debounceWait'] || defaultOptions.debounceWait, ssrAppId: options['ssrAppId'] || defaultOptions.ssrAppId, refreshOnceOnNavigation: !!options['refreshOnceOnNavigation'] } diff --git a/test/unit/components.test.js b/test/unit/components.test.js index 8b47e4e..8f011b4 100644 --- a/test/unit/components.test.js +++ b/test/unit/components.test.js @@ -479,7 +479,7 @@ describe('components', () => { wrapper.destroy() }) - test('can toggle refreshOnceOnNavigation runtime', () => { + test('can enable option refreshOnceOnNavigation runtime', () => { const guards = {} const wrapper = mount(HelloWorld, { localVue: Vue, @@ -503,4 +503,14 @@ describe('components', () => { expect(guards.before).not.toBeUndefined() expect(guards.after).not.toBeUndefined() }) + + test('can set option debounceWait runtime', () => { + const wrapper = mount(HelloWorld, { localVue: Vue }) + + expect(wrapper.vm.$meta().getOptions().debounceWait).toBe(10) + + wrapper.vm.$meta().setOptions({ debounceWait: 69420 }) + + expect(wrapper.vm.$meta().getOptions().debounceWait).toBe(69420) + }) }) diff --git a/test/unit/plugin.test.js b/test/unit/plugin.test.js index 079dc72..dd22a71 100644 --- a/test/unit/plugin.test.js +++ b/test/unit/plugin.test.js @@ -153,7 +153,7 @@ describe('plugin', () => { 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((vm, hookName) => { + 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()) @@ -216,7 +216,7 @@ describe('plugin', () => { expect(metaInfo.title).toBe(title) }) - test('updates are batched', async () => { + test('updates are batched by default', async () => { jest.useFakeTimers() const { batchUpdate: _batchUpdate } = jest.requireActual('../../src/client/update') @@ -224,7 +224,7 @@ describe('plugin', () => { 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((vm, hookName) => { + 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) diff --git a/test/unit/shared.test.js b/test/unit/shared.test.js index 118a11a..29d0d2b 100644 --- a/test/unit/shared.test.js +++ b/test/unit/shared.test.js @@ -1,5 +1,6 @@ import { setOptions } from '../../src/shared/options' import { defaultOptions } from '../../src/shared/constants' +import { triggerUpdate } from '../../src/client/update' describe('shared', () => { test('can use setOptions', () => { @@ -11,4 +12,33 @@ describe('shared', () => { 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) + }) })