Skip to content

Commit

Permalink
feat: add pause/resume methods to pause updates
Browse files Browse the repository at this point in the history
  • Loading branch information
pimlie committed Feb 20, 2019
1 parent f270318 commit d237180
Show file tree
Hide file tree
Showing 8 changed files with 108 additions and 38 deletions.
4 changes: 2 additions & 2 deletions scripts/rollup.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,8 @@ export default [{
output: {
...baseConfig.output,
file: pkg.main,
intro: 'var window',
format: 'cjs'
format: 'cjs',
intro: 'var window'
},
external: Object.keys(pkg.dependencies)
}]
5 changes: 4 additions & 1 deletion src/client/$meta.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { pause, resume } from '../shared/pausing'
import refresh from './refresh'

export default function _$meta(options = {}) {
Expand All @@ -9,7 +10,9 @@ export default function _$meta(options = {}) {
return function $meta() {
return {
inject: () => {},
refresh: refresh(options).bind(this)
refresh: refresh(options).bind(this),
pause: pause.bind(this),
resume: resume.bind(this)
}
}
}
5 changes: 4 additions & 1 deletion src/client/batchUpdate.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,10 @@ const startUpdate = (!isUndefined(window) ? window.requestAnimationFrame : null)
* @return {Number} id - a new ID
*/
export default function batchUpdate(id, callback) {
stopUpdate(id)
if (id) {
stopUpdate(id)
}

return startUpdate(() => {
id = null
callback()
Expand Down
1 change: 0 additions & 1 deletion src/client/refresh.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ export default function _refresh(options = {}) {
const metaInfo = getMetaInfo(options, this.$root)

const tags = updateClientMetaInfo(options, metaInfo)

// emit "event" with new info
if (tags && isFunction(metaInfo.changed)) {
metaInfo.changed.call(this, metaInfo, tags.addedTags, tags.removedTags)
Expand Down
5 changes: 4 additions & 1 deletion src/server/$meta.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import refresh from '../client/refresh'
import { pause, resume } from '../shared/pausing'
import inject from './inject'

export default function _$meta(options = {}) {
Expand All @@ -10,7 +11,9 @@ export default function _$meta(options = {}) {
return function $meta() {
return {
inject: inject(options).bind(this),
refresh: refresh(options).bind(this)
refresh: refresh(options).bind(this),
pause: pause.bind(this),
resume: resume.bind(this)
}
}
}
53 changes: 22 additions & 31 deletions src/shared/mixin.js
Original file line number Diff line number Diff line change
@@ -1,20 +1,11 @@
import batchUpdate from '../client/batchUpdate'
import triggerUpdate from '../client/triggerUpdate'
import { isUndefined, isFunction } from '../shared/typeof'
import { ensuredPush } from '../shared/ensure'

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
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
return {
beforeCreate() {
Expand All @@ -36,41 +27,41 @@ export default function createMixin(options) {
// if computed $metaInfo exists, watch it for updates & trigger a refresh
// 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)
this.$options.created = this.$options.created || []
this.$options.created.push(() => {
this.$watch('$metaInfo', () => triggerUpdate(this))
ensuredPush(this.$options, 'created', () => {
this.$watch('$metaInfo', function () {
triggerUpdate(this, 'watcher')
})
})
}
}

updateOnLifecycleHook.forEach((lifecycleHook) => {
this.$options[lifecycleHook] = this.$options[lifecycleHook] || []
this.$options[lifecycleHook].push(() => triggerUpdate(this))
ensuredPush(this.$options, lifecycleHook, () => triggerUpdate(this, lifecycleHook))
})

// force an initial refresh on page load and prevent other lifecycleHooks
// to triggerUpdate until this initial refresh is finished
// 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
if (isUndefined(this.$root._vueMetaInitialized)) {
this.$root._vueMetaInitialized = false

this.$root.$options.mounted = this.$root.$options.mounted || []
this.$root.$options.mounted.push(() => {
if (!this.$root._vueMetaInitialized) {
this.$nextTick(function () {
this.$root.$meta().refresh()
this.$root._vueMetaInitialized = true
})
}
})
if (isUndefined(this.$root._vueMetaPaused)) {
this.$root._vueMetaInitialized = this.$isServer

if (!this.$root._vueMetaInitialized) {
ensuredPush(this.$options, 'mounted', () => {
if (!this.$root._vueMetaInitialized) {
this.$nextTick(function () {
this.$root.$meta().refresh()
this.$root._vueMetaInitialized = true
})
}
})
}
}

// do not trigger refresh on the server side
if (!this.$isServer) {
// re-render meta data when returning from a child component to parent
this.$options.destroyed = this.$options.destroyed || []
this.$options.destroyed.push(() => {
ensuredPush(this.$options, 'destroyed', () => {
// Wait that element is hidden before refreshing meta tags (to support animations)
const interval = setInterval(() => {
if (this.$el && this.$el.offsetParent !== null) {
Expand All @@ -83,7 +74,7 @@ export default function createMixin(options) {
return
}

triggerUpdate(this)
triggerUpdate(this, 'destroyed')
}, 50)
})
}
Expand Down
67 changes: 66 additions & 1 deletion test/plugin-browser.test.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
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', () => ({
version: 'test-version'
}))

describe('plugin', () => {
let Vue

beforeEach(() => jest.clearAllMocks())
beforeAll(() => (Vue = loadVueMetaPlugin(true)))

test('is loaded', () => {
Expand Down Expand Up @@ -35,4 +40,64 @@ describe('plugin', () => {
test('plugin sets package 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 changes: 6 additions & 0 deletions test/utils/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,3 +41,9 @@ export function loadVueMetaPlugin(browser, options, localVue = getVue()) {

return localVue
}

export const vmTick = (vm) => {
return new Promise((resolve) => {
vm.$nextTick(resolve)
})
}

0 comments on commit d237180

Please sign in to comment.