diff --git a/src/client/batchUpdate.js b/src/client/batchUpdate.js deleted file mode 100644 index 6e9a7a9..0000000 --- a/src/client/batchUpdate.js +++ /dev/null @@ -1,24 +0,0 @@ -import { hasGlobalWindow } from '../utils/window' - -// fallback to timers if rAF not present -const stopUpdate = (hasGlobalWindow ? window.cancelAnimationFrame : null) || clearTimeout -const startUpdate = (hasGlobalWindow ? window.requestAnimationFrame : null) || (cb => setTimeout(cb, 0)) - -/** - * Performs a batched update. Uses requestAnimationFrame to prevent - * calling a function too many times in quick succession. - * You need to pass it an ID (which can initially be `null`), - * but be sure to overwrite that ID with the return value of batchUpdate. - * - * @param {(null|Number)} id - the ID of this update - * @param {Function} callback - the update to perform - * @return {Number} id - a new ID - */ -export default function batchUpdate(id, callback) { - stopUpdate(id) - - return startUpdate(() => { - id = null - callback() - }) -} diff --git a/src/client/triggerUpdate.js b/src/client/triggerUpdate.js deleted file mode 100644 index b0e6903..0000000 --- a/src/client/triggerUpdate.js +++ /dev/null @@ -1,14 +0,0 @@ -import batchUpdate from './batchUpdate' - -// store an id to keep track of DOM updates -let batchId = null - -export default function triggerUpdate(vm, hookName) { - if (vm.$root._vueMeta.initialized && !vm.$root._vueMeta.paused) { - // batch potential DOM updates to prevent extraneous re-rendering - batchId = batchUpdate(batchId, () => { - vm.$meta().refresh() - batchId = null - }) - } -} diff --git a/src/client/update.js b/src/client/update.js new file mode 100644 index 0000000..c03a88e --- /dev/null +++ b/src/client/update.js @@ -0,0 +1,26 @@ +// store an id to keep track of DOM updates +let batchId = null + +export function triggerUpdate(vm, hookName) { + if (vm.$root._vueMeta.initialized && !vm.$root._vueMeta.paused) { + // batch potential DOM updates to prevent extraneous re-rendering + batchUpdate(() => vm.$meta().refresh()) + } +} + +/** + * Performs a batched update. + * + * @param {(null|Number)} id - the ID of this update + * @param {Function} callback - the update to perform + * @return {Number} id - a new ID + */ +export function batchUpdate(callback, timeout = 10) { + clearTimeout(batchId) + + batchId = setTimeout(() => { + callback() + }, timeout) + + return batchId +} diff --git a/src/shared/mixin.js b/src/shared/mixin.js index ef384e2..25c4d0a 100644 --- a/src/shared/mixin.js +++ b/src/shared/mixin.js @@ -1,4 +1,4 @@ -import triggerUpdate from '../client/triggerUpdate' +import { triggerUpdate } from '../client/update' import { isUndefined, isFunction } from '../utils/is-type' import { ensuredPush } from '../utils/ensure' import { hasMetaInfo } from './meta-helpers' diff --git a/test/unit/plugin-browser.test.js b/test/unit/plugin-browser.test.js index 606a2b1..427481b 100644 --- a/test/unit/plugin-browser.test.js +++ b/test/unit/plugin-browser.test.js @@ -1,10 +1,8 @@ -import triggerUpdate from '../../src/client/triggerUpdate' -import batchUpdate from '../../src/client/batchUpdate' +import { triggerUpdate, batchUpdate } from '../../src/client/update' import { mount, vmTick, VueMetaBrowserPlugin, loadVueMetaPlugin } from '../utils' import { defaultOptions } from '../../src/shared/constants' -jest.mock('../../src/client/triggerUpdate') -jest.mock('../../src/client/batchUpdate') +jest.mock('../../src/client/update') jest.mock('../../package.json', () => ({ version: 'test-version' })) @@ -48,11 +46,16 @@ describe('plugin', () => { }) test('updates can be paused and resumed', async () => { - const _triggerUpdate = jest.requireActual('../../src/client/triggerUpdate').default - const _batchUpdate = jest.requireActual('../../src/client/batchUpdate').default - - const triggerUpdateSpy = triggerUpdate.mockImplementation(_triggerUpdate) + 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((vm, hookName) => { + if (vm.$root._vueMeta.initialized && !vm.$root._vueMeta.paused) { + // batch potential DOM updates to prevent extraneous re-rendering + batchUpdateSpy(() => vm.$meta().refresh()) + } + }) const Component = Vue.component('test-component', { metaInfo() { @@ -109,4 +112,52 @@ describe('plugin', () => { const { metaInfo } = wrapper.vm.$meta().resume() expect(metaInfo.title).toBe(title) }) + + test('updates are batched', 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((vm, hookName) => { + if (vm.$root._vueMeta.initialized && !vm.$root._vueMeta.paused) { + // 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: '
Test
' + }) + + let title = 'first title' + const wrapper = mount(Component, { + localVue: Vue, + propsData: { + title + } + }) + await vmTick(wrapper.vm) + jest.clearAllMocks() + + title = 'second title' + wrapper.setProps({ title }) + jest.advanceTimersByTime(2) + expect(refreshSpy).not.toHaveBeenCalled() + jest.advanceTimersByTime(10) + expect(refreshSpy).toHaveBeenCalled() + }) })