| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380 |
- import { SpyInstanceFn } from 'vitest'
- import Vue from 'vue'
- describe('Options directives', () => {
- it('basic usage', done => {
- const bindSpy = vi.fn()
- const insertedSpy = vi.fn()
- const updateSpy = vi.fn()
- const componentUpdatedSpy = vi.fn()
- const unbindSpy = vi.fn()
- const assertContext = (el, binding, vnode) => {
- expect(vnode.context).toBe(vm)
- expect(binding.arg).toBe('arg')
- expect(binding.modifiers).toEqual({ hello: true })
- }
- const vm = new Vue({
- template:
- '<div class="hi"><div v-if="ok" v-test:arg.hello="a">{{ msg }}</div></div>',
- data: {
- msg: 'hi',
- a: 'foo',
- ok: true
- },
- directives: {
- test: {
- bind(el, binding, vnode) {
- bindSpy()
- assertContext(el, binding, vnode)
- expect(binding.value).toBe('foo')
- expect(binding.expression).toBe('a')
- expect(binding.oldValue).toBeUndefined()
- expect(el.parentNode).toBeNull()
- },
- inserted(el, binding, vnode) {
- insertedSpy()
- assertContext(el, binding, vnode)
- expect(binding.value).toBe('foo')
- expect(binding.expression).toBe('a')
- expect(binding.oldValue).toBeUndefined()
- expect(el.parentNode.className).toBe('hi')
- },
- update(el, binding, vnode, oldVnode) {
- updateSpy()
- assertContext(el, binding, vnode)
- expect(el).toBe(vm.$el.children[0])
- expect(oldVnode).not.toBe(vnode)
- expect(binding.expression).toBe('a')
- if (binding.value !== binding.oldValue) {
- expect(binding.value).toBe('bar')
- expect(binding.oldValue).toBe('foo')
- }
- },
- componentUpdated(el, binding, vnode) {
- componentUpdatedSpy()
- assertContext(el, binding, vnode)
- },
- unbind(el, binding, vnode) {
- unbindSpy()
- assertContext(el, binding, vnode)
- }
- }
- }
- })
- vm.$mount()
- expect(bindSpy).toHaveBeenCalled()
- expect(insertedSpy).toHaveBeenCalled()
- expect(updateSpy).not.toHaveBeenCalled()
- expect(componentUpdatedSpy).not.toHaveBeenCalled()
- expect(unbindSpy).not.toHaveBeenCalled()
- vm.a = 'bar'
- waitForUpdate(() => {
- expect(updateSpy).toHaveBeenCalled()
- expect(componentUpdatedSpy).toHaveBeenCalled()
- expect(unbindSpy).not.toHaveBeenCalled()
- vm.msg = 'bye'
- })
- .then(() => {
- expect(componentUpdatedSpy.mock.calls.length).toBe(2)
- vm.ok = false
- })
- .then(() => {
- expect(unbindSpy).toHaveBeenCalled()
- })
- .then(done)
- })
- it('function shorthand', done => {
- const spy = vi.fn()
- const vm = new Vue({
- template: '<div v-test:arg.hello="a"></div>',
- data: { a: 'foo' },
- directives: {
- test(el, binding, vnode) {
- expect(vnode.context).toBe(vm)
- expect(binding.arg).toBe('arg')
- expect(binding.modifiers).toEqual({ hello: true })
- spy(binding.value, binding.oldValue)
- }
- }
- })
- vm.$mount()
- expect(spy).toHaveBeenCalledWith('foo', undefined)
- vm.a = 'bar'
- waitForUpdate(() => {
- expect(spy).toHaveBeenCalledWith('bar', 'foo')
- }).then(done)
- })
- it('function shorthand (global)', done => {
- const spy = vi.fn()
- Vue.directive('test', function (el, binding, vnode) {
- expect(vnode.context).toBe(vm)
- expect(binding.arg).toBe('arg')
- expect(binding.modifiers).toEqual({ hello: true })
- spy(binding.value, binding.oldValue)
- })
- const vm = new Vue({
- template: '<div v-test:arg.hello="a"></div>',
- data: { a: 'foo' }
- })
- vm.$mount()
- expect(spy).toHaveBeenCalledWith('foo', undefined)
- vm.a = 'bar'
- waitForUpdate(() => {
- expect(spy).toHaveBeenCalledWith('bar', 'foo')
- delete Vue.options.directives.test
- }).then(done)
- })
- it('should teardown directives on old vnodes when new vnodes have none', done => {
- const vm = new Vue({
- data: {
- ok: true
- },
- template: `
- <div>
- <div v-if="ok" v-test>a</div>
- <div v-else class="b">b</div>
- </div>
- `,
- directives: {
- test: {
- bind: el => {
- el.id = 'a'
- },
- unbind: el => {
- el.id = ''
- }
- }
- }
- }).$mount()
- expect(vm.$el.children[0].id).toBe('a')
- vm.ok = false
- waitForUpdate(() => {
- expect(vm.$el.children[0].id).toBe('')
- expect(vm.$el.children[0].className).toBe('b')
- }).then(done)
- })
- it('should properly handle same node with different directive sets', done => {
- const spies: Record<string, SpyInstanceFn> = {}
- const createSpy = name => (spies[name] = vi.fn())
- const vm = new Vue({
- data: {
- ok: true,
- val: 123
- },
- template: `
- <div>
- <div v-if="ok" v-test="val" v-test.hi="val"></div>
- <div v-if="!ok" v-test.hi="val" v-test2="val"></div>
- </div>
- `,
- directives: {
- test: {
- bind: createSpy('bind1'),
- inserted: createSpy('inserted1'),
- update: createSpy('update1'),
- componentUpdated: createSpy('componentUpdated1'),
- unbind: createSpy('unbind1')
- },
- test2: {
- bind: createSpy('bind2'),
- inserted: createSpy('inserted2'),
- update: createSpy('update2'),
- componentUpdated: createSpy('componentUpdated2'),
- unbind: createSpy('unbind2')
- }
- }
- }).$mount()
- expect(spies.bind1.mock.calls.length).toBe(2)
- expect(spies.inserted1.mock.calls.length).toBe(2)
- expect(spies.bind2.mock.calls.length).toBe(0)
- expect(spies.inserted2.mock.calls.length).toBe(0)
- vm.ok = false
- waitForUpdate(() => {
- // v-test with modifier should be updated
- expect(spies.update1.mock.calls.length).toBe(1)
- expect(spies.componentUpdated1.mock.calls.length).toBe(1)
- // v-test without modifier should be unbound
- expect(spies.unbind1.mock.calls.length).toBe(1)
- // v-test2 should be bound
- expect(spies.bind2.mock.calls.length).toBe(1)
- expect(spies.inserted2.mock.calls.length).toBe(1)
- vm.ok = true
- })
- .then(() => {
- // v-test without modifier should be bound again
- expect(spies.bind1.mock.calls.length).toBe(3)
- expect(spies.inserted1.mock.calls.length).toBe(3)
- // v-test2 should be unbound
- expect(spies.unbind2.mock.calls.length).toBe(1)
- // v-test with modifier should be updated again
- expect(spies.update1.mock.calls.length).toBe(2)
- expect(spies.componentUpdated1.mock.calls.length).toBe(2)
- vm.val = 234
- })
- .then(() => {
- expect(spies.update1.mock.calls.length).toBe(4)
- expect(spies.componentUpdated1.mock.calls.length).toBe(4)
- })
- .then(done)
- })
- it('warn non-existent', () => {
- new Vue({
- template: '<div v-test></div>'
- }).$mount()
- expect('Failed to resolve directive: test').toHaveBeenWarned()
- })
- // #6513
- it('should invoke unbind & inserted on inner component root element change', done => {
- const dir = {
- bind: vi.fn(),
- inserted: vi.fn(),
- unbind: vi.fn()
- }
- const Child = {
- template: `<div v-if="ok"/><span v-else/>`,
- data: () => ({ ok: true })
- }
- const vm = new Vue({
- template: `<child ref="child" v-test />`,
- directives: { test: dir },
- components: { Child }
- }).$mount()
- const oldEl = vm.$el
- expect(dir.bind.mock.calls.length).toBe(1)
- expect(dir.bind.mock.calls[0][0]).toBe(oldEl)
- expect(dir.inserted.mock.calls.length).toBe(1)
- expect(dir.inserted.mock.calls[0][0]).toBe(oldEl)
- expect(dir.unbind).not.toHaveBeenCalled()
- vm.$refs.child.ok = false
- waitForUpdate(() => {
- expect(vm.$el.tagName).toBe('SPAN')
- expect(dir.bind.mock.calls.length).toBe(2)
- expect(dir.bind.mock.calls[1][0]).toBe(vm.$el)
- expect(dir.inserted.mock.calls.length).toBe(2)
- expect(dir.inserted.mock.calls[1][0]).toBe(vm.$el)
- expect(dir.unbind.mock.calls.length).toBe(1)
- expect(dir.unbind.mock.calls[0][0]).toBe(oldEl)
- }).then(done)
- })
- it('dynamic arguments', done => {
- const vm = new Vue({
- template: `<div v-my:[key]="1"/>`,
- data: {
- key: 'foo'
- },
- directives: {
- my: {
- bind(el, binding) {
- expect(binding.arg).toBe('foo')
- },
- update(el, binding) {
- expect(binding.arg).toBe('bar')
- expect(binding.oldArg).toBe('foo')
- done()
- }
- }
- }
- }).$mount()
- vm.key = 'bar'
- })
- it('deep object like `deep.a` as dynamic arguments', done => {
- const vm = new Vue({
- template: `<div v-my:[deep.a]="1"/>`,
- data: {
- deep: {
- a: 'foo'
- }
- },
- directives: {
- my: {
- bind(el, binding) {
- expect(binding.arg).toBe('foo')
- },
- update(el, binding) {
- expect(binding.arg).toBe('bar')
- expect(binding.oldArg).toBe('foo')
- done()
- }
- }
- }
- }).$mount()
- vm.deep.a = 'bar'
- })
- it('deep object like `deep.a.b` as dynamic arguments', done => {
- const vm = new Vue({
- template: `<div v-my:[deep.a.b]="1"/>`,
- data: {
- deep: {
- a: {
- b: 'foo'
- }
- }
- },
- directives: {
- my: {
- bind(el, binding) {
- expect(binding.arg).toBe('foo')
- },
- update(el, binding) {
- expect(binding.arg).toBe('bar')
- expect(binding.oldArg).toBe('foo')
- done()
- }
- }
- }
- }).$mount()
- vm.deep.a.b = 'bar'
- })
- it('deep object as dynamic arguments with modifiers', done => {
- const vm = new Vue({
- template: `<div v-my:[deep.a.b].x.y="1"/>`,
- data: {
- deep: {
- a: {
- b: 'foo'
- }
- }
- },
- directives: {
- my: {
- bind(el, binding) {
- expect(binding.arg).toBe('foo')
- expect(binding.modifiers.x).toBe(true)
- expect(binding.modifiers.y).toBe(true)
- },
- update(el, binding) {
- expect(binding.arg).toBe('bar')
- expect(binding.oldArg).toBe('foo')
- done()
- }
- }
- }
- }).$mount()
- vm.deep.a.b = 'bar'
- })
- })
|