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

feat: add pause/resume methods to pause updates

This commit is contained in:
pimlie
2019-02-20 14:46:05 +01:00
parent f270318c29
commit d237180cbd
8 changed files with 107 additions and 37 deletions
+2 -2
View File
@@ -48,8 +48,8 @@ export default [{
output: { output: {
...baseConfig.output, ...baseConfig.output,
file: pkg.main, file: pkg.main,
intro: 'var window', format: 'cjs',
format: 'cjs' intro: 'var window'
}, },
external: Object.keys(pkg.dependencies) external: Object.keys(pkg.dependencies)
}] }]
+4 -1
View File
@@ -1,3 +1,4 @@
import { pause, resume } from '../shared/pausing'
import refresh from './refresh' import refresh from './refresh'
export default function _$meta(options = {}) { export default function _$meta(options = {}) {
@@ -9,7 +10,9 @@ export default function _$meta(options = {}) {
return function $meta() { return function $meta() {
return { return {
inject: () => {}, inject: () => {},
refresh: refresh(options).bind(this) refresh: refresh(options).bind(this),
pause: pause.bind(this),
resume: resume.bind(this)
} }
} }
} }
+4 -1
View File
@@ -15,7 +15,10 @@ const startUpdate = (!isUndefined(window) ? window.requestAnimationFrame : null)
* @return {Number} id - a new ID * @return {Number} id - a new ID
*/ */
export default function batchUpdate(id, callback) { export default function batchUpdate(id, callback) {
stopUpdate(id) if (id) {
stopUpdate(id)
}
return startUpdate(() => { return startUpdate(() => {
id = null id = null
callback() callback()
-1
View File
@@ -17,7 +17,6 @@ export default function _refresh(options = {}) {
const metaInfo = getMetaInfo(options, this.$root) const metaInfo = getMetaInfo(options, this.$root)
const tags = updateClientMetaInfo(options, metaInfo) const tags = updateClientMetaInfo(options, metaInfo)
// emit "event" with new info // emit "event" with new info
if (tags && isFunction(metaInfo.changed)) { if (tags && isFunction(metaInfo.changed)) {
metaInfo.changed.call(this, metaInfo, tags.addedTags, tags.removedTags) metaInfo.changed.call(this, metaInfo, tags.addedTags, tags.removedTags)
+4 -1
View File
@@ -1,4 +1,5 @@
import refresh from '../client/refresh' import refresh from '../client/refresh'
import { pause, resume } from '../shared/pausing'
import inject from './inject' import inject from './inject'
export default function _$meta(options = {}) { export default function _$meta(options = {}) {
@@ -10,7 +11,9 @@ export default function _$meta(options = {}) {
return function $meta() { return function $meta() {
return { return {
inject: inject(options).bind(this), inject: inject(options).bind(this),
refresh: refresh(options).bind(this) refresh: refresh(options).bind(this),
pause: pause.bind(this),
resume: resume.bind(this)
} }
} }
} }
+21 -30
View File
@@ -1,20 +1,11 @@
import batchUpdate from '../client/batchUpdate' import triggerUpdate from '../client/triggerUpdate'
import { isUndefined, isFunction } from '../shared/typeof' import { isUndefined, isFunction } from '../shared/typeof'
import { ensuredPush } from '../shared/ensure'
export default function createMixin(options) { export default function createMixin(options) {
// store an id to keep track of DOM updates
let batchID = null
// for which Vue lifecycle hooks should the metaInfo be refreshed // for which Vue lifecycle hooks should the metaInfo be refreshed
const updateOnLifecycleHook = ['activated', 'deactivated', 'beforeMount'] const updateOnLifecycleHook = ['activated', 'deactivated', 'beforeMount']
const triggerUpdate = (vm) => {
if (vm.$root._vueMetaInitialized) {
// batch potential DOM updates to prevent extraneous re-rendering
batchID = batchUpdate(batchID, () => vm.$meta().refresh())
}
}
// watch for client side component updates // watch for client side component updates
return { return {
beforeCreate() { beforeCreate() {
@@ -36,41 +27,41 @@ export default function createMixin(options) {
// if computed $metaInfo exists, watch it for updates & trigger a refresh // if computed $metaInfo exists, watch it for updates & trigger a refresh
// when it changes (i.e. automatically handle async actions that affect metaInfo) // when it changes (i.e. automatically handle async actions that affect metaInfo)
// credit for this suggestion goes to [Sébastien Chopin](https://github.com/Atinux) // credit for this suggestion goes to [Sébastien Chopin](https://github.com/Atinux)
this.$options.created = this.$options.created || [] ensuredPush(this.$options, 'created', () => {
this.$options.created.push(() => { this.$watch('$metaInfo', function () {
this.$watch('$metaInfo', () => triggerUpdate(this)) triggerUpdate(this, 'watcher')
})
}) })
} }
} }
updateOnLifecycleHook.forEach((lifecycleHook) => { updateOnLifecycleHook.forEach((lifecycleHook) => {
this.$options[lifecycleHook] = this.$options[lifecycleHook] || [] ensuredPush(this.$options, lifecycleHook, () => triggerUpdate(this, lifecycleHook))
this.$options[lifecycleHook].push(() => triggerUpdate(this))
}) })
// force an initial refresh on page load and prevent other lifecycleHooks // force an initial refresh on page load and prevent other lifecycleHooks
// to triggerUpdate until this initial refresh is finished // to triggerUpdate until this initial refresh is finished
// this is to make sure that when a page is opened in an inactive tab which // this is to make sure that when a page is opened in an inactive tab which
// has throttled rAF/timers we still immeditately set the page title // has throttled rAF/timers we still immeditately set the page title
if (isUndefined(this.$root._vueMetaInitialized)) { if (isUndefined(this.$root._vueMetaPaused)) {
this.$root._vueMetaInitialized = false this.$root._vueMetaInitialized = this.$isServer
this.$root.$options.mounted = this.$root.$options.mounted || [] if (!this.$root._vueMetaInitialized) {
this.$root.$options.mounted.push(() => { ensuredPush(this.$options, 'mounted', () => {
if (!this.$root._vueMetaInitialized) { if (!this.$root._vueMetaInitialized) {
this.$nextTick(function () { this.$nextTick(function () {
this.$root.$meta().refresh() this.$root.$meta().refresh()
this.$root._vueMetaInitialized = true this.$root._vueMetaInitialized = true
}) })
} }
}) })
}
} }
// do not trigger refresh on the server side // do not trigger refresh on the server side
if (!this.$isServer) { if (!this.$isServer) {
// re-render meta data when returning from a child component to parent // re-render meta data when returning from a child component to parent
this.$options.destroyed = this.$options.destroyed || [] ensuredPush(this.$options, 'destroyed', () => {
this.$options.destroyed.push(() => {
// 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) {
@@ -83,7 +74,7 @@ export default function createMixin(options) {
return return
} }
triggerUpdate(this) triggerUpdate(this, 'destroyed')
}, 50) }, 50)
}) })
} }
+66 -1
View File
@@ -1,5 +1,9 @@
import { mount, defaultOptions, VueMetaBrowserPlugin, loadVueMetaPlugin } from './utils' import triggerUpdate from '../src/client/triggerUpdate'
import batchUpdate from '../src/client/batchUpdate'
import { mount, defaultOptions, vmTick, VueMetaBrowserPlugin, loadVueMetaPlugin } from './utils'
jest.mock('../src/client/triggerUpdate')
jest.mock('../src/client/batchUpdate')
jest.mock('../package.json', () => ({ jest.mock('../package.json', () => ({
version: 'test-version' version: 'test-version'
})) }))
@@ -7,6 +11,7 @@ jest.mock('../package.json', () => ({
describe('plugin', () => { describe('plugin', () => {
let Vue let Vue
beforeEach(() => jest.clearAllMocks())
beforeAll(() => (Vue = loadVueMetaPlugin(true))) beforeAll(() => (Vue = loadVueMetaPlugin(true)))
test('is loaded', () => { test('is loaded', () => {
@@ -35,4 +40,64 @@ describe('plugin', () => {
test('plugin sets package version', () => { test('plugin sets package version', () => {
expect(VueMetaBrowserPlugin.version).toBe('test-version') expect(VueMetaBrowserPlugin.version).toBe('test-version')
}) })
test('updates can be paused and resumed', async () => {
const _triggerUpdate = jest.requireActual('../src/client/triggerUpdate').default
const triggerUpdateSpy = triggerUpdate.mockImplementation(_triggerUpdate)
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._vueMetaInitialized).toBe(false)
expect(wrapper.vm.$root._vueMetaPaused).toBeFalsy()
expect(triggerUpdateSpy).toHaveBeenCalledTimes(1)
expect(batchUpdate).not.toHaveBeenCalled()
jest.clearAllMocks()
await vmTick(wrapper.vm)
title = 'second title'
wrapper.setProps({ title })
// batchUpdate on normal update
expect(wrapper.vm.$root._vueMetaInitialized).toBe(true)
expect(wrapper.vm.$root._vueMetaPaused).toBeFalsy()
expect(triggerUpdateSpy).toHaveBeenCalledTimes(1)
expect(batchUpdate).toHaveBeenCalledTimes(1)
jest.clearAllMocks()
wrapper.vm.$meta().pause()
title = 'third title'
wrapper.setProps({ title })
// no batchUpdate when paused
expect(wrapper.vm.$root._vueMetaInitialized).toBe(true)
expect(wrapper.vm.$root._vueMetaPaused).toBe(true)
expect(triggerUpdateSpy).toHaveBeenCalledTimes(1)
expect(batchUpdate).not.toHaveBeenCalled()
jest.clearAllMocks()
const metaInfo = wrapper.vm.$meta().resume()
expect(metaInfo.title).toBe(title)
})
}) })
+6
View File
@@ -41,3 +41,9 @@ export function loadVueMetaPlugin(browser, options, localVue = getVue()) {
return localVue return localVue
} }
export const vmTick = (vm) => {
return new Promise((resolve) => {
vm.$nextTick(resolve)
})
}