Skip to content

Commit d237180

Browse files
committed
feat: add pause/resume methods to pause updates
1 parent f270318 commit d237180

File tree

8 files changed

+108
-38
lines changed

8 files changed

+108
-38
lines changed

scripts/rollup.config.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -48,8 +48,8 @@ export default [{
4848
output: {
4949
...baseConfig.output,
5050
file: pkg.main,
51-
intro: 'var window',
52-
format: 'cjs'
51+
format: 'cjs',
52+
intro: 'var window'
5353
},
5454
external: Object.keys(pkg.dependencies)
5555
}]

src/client/$meta.js

+4-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { pause, resume } from '../shared/pausing'
12
import refresh from './refresh'
23

34
export default function _$meta(options = {}) {
@@ -9,7 +10,9 @@ export default function _$meta(options = {}) {
910
return function $meta() {
1011
return {
1112
inject: () => {},
12-
refresh: refresh(options).bind(this)
13+
refresh: refresh(options).bind(this),
14+
pause: pause.bind(this),
15+
resume: resume.bind(this)
1316
}
1417
}
1518
}

src/client/batchUpdate.js

+4-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,10 @@ const startUpdate = (!isUndefined(window) ? window.requestAnimationFrame : null)
1515
* @return {Number} id - a new ID
1616
*/
1717
export default function batchUpdate(id, callback) {
18-
stopUpdate(id)
18+
if (id) {
19+
stopUpdate(id)
20+
}
21+
1922
return startUpdate(() => {
2023
id = null
2124
callback()

src/client/refresh.js

-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ export default function _refresh(options = {}) {
1717
const metaInfo = getMetaInfo(options, this.$root)
1818

1919
const tags = updateClientMetaInfo(options, metaInfo)
20-
2120
// emit "event" with new info
2221
if (tags && isFunction(metaInfo.changed)) {
2322
metaInfo.changed.call(this, metaInfo, tags.addedTags, tags.removedTags)

src/server/$meta.js

+4-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import refresh from '../client/refresh'
2+
import { pause, resume } from '../shared/pausing'
23
import inject from './inject'
34

45
export default function _$meta(options = {}) {
@@ -10,7 +11,9 @@ export default function _$meta(options = {}) {
1011
return function $meta() {
1112
return {
1213
inject: inject(options).bind(this),
13-
refresh: refresh(options).bind(this)
14+
refresh: refresh(options).bind(this),
15+
pause: pause.bind(this),
16+
resume: resume.bind(this)
1417
}
1518
}
1619
}

src/shared/mixin.js

+22-31
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,11 @@
1-
import batchUpdate from '../client/batchUpdate'
1+
import triggerUpdate from '../client/triggerUpdate'
22
import { isUndefined, isFunction } from '../shared/typeof'
3+
import { ensuredPush } from '../shared/ensure'
34

45
export default function createMixin(options) {
5-
// store an id to keep track of DOM updates
6-
let batchID = null
7-
86
// for which Vue lifecycle hooks should the metaInfo be refreshed
97
const updateOnLifecycleHook = ['activated', 'deactivated', 'beforeMount']
108

11-
const triggerUpdate = (vm) => {
12-
if (vm.$root._vueMetaInitialized) {
13-
// batch potential DOM updates to prevent extraneous re-rendering
14-
batchID = batchUpdate(batchID, () => vm.$meta().refresh())
15-
}
16-
}
17-
189
// watch for client side component updates
1910
return {
2011
beforeCreate() {
@@ -36,41 +27,41 @@ export default function createMixin(options) {
3627
// if computed $metaInfo exists, watch it for updates & trigger a refresh
3728
// when it changes (i.e. automatically handle async actions that affect metaInfo)
3829
// credit for this suggestion goes to [Sébastien Chopin](https://github.com/Atinux)
39-
this.$options.created = this.$options.created || []
40-
this.$options.created.push(() => {
41-
this.$watch('$metaInfo', () => triggerUpdate(this))
30+
ensuredPush(this.$options, 'created', () => {
31+
this.$watch('$metaInfo', function () {
32+
triggerUpdate(this, 'watcher')
33+
})
4234
})
4335
}
4436
}
4537

4638
updateOnLifecycleHook.forEach((lifecycleHook) => {
47-
this.$options[lifecycleHook] = this.$options[lifecycleHook] || []
48-
this.$options[lifecycleHook].push(() => triggerUpdate(this))
39+
ensuredPush(this.$options, lifecycleHook, () => triggerUpdate(this, lifecycleHook))
4940
})
5041

5142
// force an initial refresh on page load and prevent other lifecycleHooks
5243
// to triggerUpdate until this initial refresh is finished
5344
// this is to make sure that when a page is opened in an inactive tab which
5445
// has throttled rAF/timers we still immeditately set the page title
55-
if (isUndefined(this.$root._vueMetaInitialized)) {
56-
this.$root._vueMetaInitialized = false
57-
58-
this.$root.$options.mounted = this.$root.$options.mounted || []
59-
this.$root.$options.mounted.push(() => {
60-
if (!this.$root._vueMetaInitialized) {
61-
this.$nextTick(function () {
62-
this.$root.$meta().refresh()
63-
this.$root._vueMetaInitialized = true
64-
})
65-
}
66-
})
46+
if (isUndefined(this.$root._vueMetaPaused)) {
47+
this.$root._vueMetaInitialized = this.$isServer
48+
49+
if (!this.$root._vueMetaInitialized) {
50+
ensuredPush(this.$options, 'mounted', () => {
51+
if (!this.$root._vueMetaInitialized) {
52+
this.$nextTick(function () {
53+
this.$root.$meta().refresh()
54+
this.$root._vueMetaInitialized = true
55+
})
56+
}
57+
})
58+
}
6759
}
6860

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

86-
triggerUpdate(this)
77+
triggerUpdate(this, 'destroyed')
8778
}, 50)
8879
})
8980
}

test/plugin-browser.test.js

+66-1
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,17 @@
1-
import { mount, defaultOptions, VueMetaBrowserPlugin, loadVueMetaPlugin } from './utils'
1+
import triggerUpdate from '../src/client/triggerUpdate'
2+
import batchUpdate from '../src/client/batchUpdate'
3+
import { mount, defaultOptions, vmTick, VueMetaBrowserPlugin, loadVueMetaPlugin } from './utils'
24

5+
jest.mock('../src/client/triggerUpdate')
6+
jest.mock('../src/client/batchUpdate')
37
jest.mock('../package.json', () => ({
48
version: 'test-version'
59
}))
610

711
describe('plugin', () => {
812
let Vue
913

14+
beforeEach(() => jest.clearAllMocks())
1015
beforeAll(() => (Vue = loadVueMetaPlugin(true)))
1116

1217
test('is loaded', () => {
@@ -35,4 +40,64 @@ describe('plugin', () => {
3540
test('plugin sets package version', () => {
3641
expect(VueMetaBrowserPlugin.version).toBe('test-version')
3742
})
43+
44+
test('updates can be paused and resumed', async () => {
45+
const _triggerUpdate = jest.requireActual('../src/client/triggerUpdate').default
46+
const triggerUpdateSpy = triggerUpdate.mockImplementation(_triggerUpdate)
47+
48+
const Component = Vue.component('test-component', {
49+
metaInfo() {
50+
return {
51+
title: this.title
52+
}
53+
},
54+
props: {
55+
title: {
56+
type: String,
57+
default: ''
58+
}
59+
},
60+
template: '<div>Test</div>'
61+
})
62+
63+
let title = 'first title'
64+
const wrapper = mount(Component, {
65+
localVue: Vue,
66+
propsData: {
67+
title
68+
}
69+
})
70+
71+
// no batchUpdate on initialization
72+
expect(wrapper.vm.$root._vueMetaInitialized).toBe(false)
73+
expect(wrapper.vm.$root._vueMetaPaused).toBeFalsy()
74+
expect(triggerUpdateSpy).toHaveBeenCalledTimes(1)
75+
expect(batchUpdate).not.toHaveBeenCalled()
76+
jest.clearAllMocks()
77+
await vmTick(wrapper.vm)
78+
79+
title = 'second title'
80+
wrapper.setProps({ title })
81+
82+
// batchUpdate on normal update
83+
expect(wrapper.vm.$root._vueMetaInitialized).toBe(true)
84+
expect(wrapper.vm.$root._vueMetaPaused).toBeFalsy()
85+
expect(triggerUpdateSpy).toHaveBeenCalledTimes(1)
86+
expect(batchUpdate).toHaveBeenCalledTimes(1)
87+
jest.clearAllMocks()
88+
89+
wrapper.vm.$meta().pause()
90+
title = 'third title'
91+
wrapper.setProps({ title })
92+
93+
// no batchUpdate when paused
94+
expect(wrapper.vm.$root._vueMetaInitialized).toBe(true)
95+
expect(wrapper.vm.$root._vueMetaPaused).toBe(true)
96+
expect(triggerUpdateSpy).toHaveBeenCalledTimes(1)
97+
expect(batchUpdate).not.toHaveBeenCalled()
98+
jest.clearAllMocks()
99+
100+
const metaInfo = wrapper.vm.$meta().resume()
101+
expect(metaInfo.title).toBe(title)
102+
})
38103
})

test/utils/index.js

+6
Original file line numberDiff line numberDiff line change
@@ -41,3 +41,9 @@ export function loadVueMetaPlugin(browser, options, localVue = getVue()) {
4141

4242
return localVue
4343
}
44+
45+
export const vmTick = (vm) => {
46+
return new Promise((resolve) => {
47+
vm.$nextTick(resolve)
48+
})
49+
}

0 commit comments

Comments
 (0)