import Vue from 'vue'
import { SSR_ATTR } from 'shared/constants'
describe('vdom patch: edge cases', () => {
// exposed by #3406
// When a static vnode is inside v-for, it's possible for the same vnode
// to be used in multiple places, and its element will be replaced. This
// causes patch errors when node ops depend on the vnode's element position.
it('should handle static vnodes by key', done => {
const vm = new Vue({
data: {
ok: true
},
template: `
a
b
c
d
`
}).$mount()
expect(vm.$el.textContent).toBe('abdabd')
vm.ok = false
waitForUpdate(() => {
expect(vm.$el.textContent).toBe('bcdbcd')
}).then(done)
})
// exposed by #7705
// methods and function expressions with modifiers should return result instead of undefined
// skipped odd children[1,3, ...] because they are rendered as text nodes with undefined value
it("should return listener's result for method name and function expression with and w/o modifiers", done => {
const dummyEvt = { preventDefault: () => {} }
new Vue({
template: `
`,
methods: {
addFive($event, toAdd = 0) {
return toAdd + 5
}
},
directives: {
test: {
bind(el, binding, vnode) {
waitForUpdate(() => {
expect(vnode.children[0].data.on.click()).toBe(5)
expect(vnode.children[2].data.on.click(dummyEvt)).toBe(5)
expect(vnode.children[4].data.on.click()).toBe(10)
expect(vnode.children[6].data.on.click(dummyEvt)).toBe(10)
}).then(done)
}
}
}
}).$mount()
})
// #3533
// a static node is reused in createElm, which changes its elm reference
// and is inserted into a different parent.
// later when patching the next element a DOM insertion uses it as the
// reference node, causing a parent mismatch.
it("should handle static node edge case when it's reused AND used as a reference node for insertion", done => {
const vm = new Vue({
data: {
ok: true
},
template: `
123
{{ 1 }}
123
`
}).$mount()
expect(vm.$el.querySelector('.c').textContent).toBe('1')
expect(vm.$el.querySelector('.d').textContent).toBe('2')
vm.ok = false
waitForUpdate(() => {
expect(vm.$el.querySelector('.c').textContent).toBe('1')
expect(vm.$el.querySelector('.d').textContent).toBe('2')
}).then(done)
})
it('should handle slot nodes being reused across render', done => {
const vm = new Vue({
template: `
`
}).$mount()
expect(vm.$el.querySelector('custom-foo').getAttribute('selected')).toBe(
'1'
)
Vue.config.ignoredElements = []
})
// #7805
it('should not cause duplicate init when components share data object', () => {
const Base = {
render(h) {
return h('div', this.$options.name)
}
}
const Foo = {
name: 'Foo',
extends: Base
}
const Bar = {
name: 'Bar',
extends: Base
}
// sometimes we do need to tap into these internal hooks (e.g. in vue-router)
// so make sure it does work
const inlineHookSpy = vi.fn()
const vm = new Vue({
render(h) {
const data = {
staticClass: 'text-red',
hook: {
init: inlineHookSpy
}
}
return h('div', [h(Foo, data), h(Bar, data)])
}
}).$mount()
expect(vm.$el.textContent).toBe('FooBar')
expect(inlineHookSpy.mock.calls.length).toBe(2)
})
// #9549
it('DOM props set throwing should not break app', done => {
const vm = new Vue({
data: {
n: Infinity
},
template: `
{{ n }}
`
}).$mount()
expect(vm.$el.textContent).toMatch('Infinity')
vm.n = 1
waitForUpdate(() => {
expect(vm.$el.textContent).toMatch('1')
expect(vm.$el.textContent).not.toMatch('Infinity')
}).then(done)
})
it('should not throw when hydrated pending async component is patched by v-if="false"', done => {
const PendingAsyncComponent = () => new Promise(() => {})
const ssrAsyncComponent = document.createElement('div')
ssrAsyncComponent.setAttribute(SSR_ATTR, 'true')
const vm = new Vue({
data: {
visible: true
},
components: {
PendingAsyncComponent
},
template:
''
}).$mount(ssrAsyncComponent)
vm.visible = false
vm.$nextTick(done)
})
})