directives.spec.js 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368
  1. import Vue from 'vue'
  2. describe('Options directives', () => {
  3. it('basic usage', done => {
  4. const bindSpy = jasmine.createSpy('bind')
  5. const insertedSpy = jasmine.createSpy('inserted')
  6. const updateSpy = jasmine.createSpy('update')
  7. const componentUpdatedSpy = jasmine.createSpy('componentUpdated')
  8. const unbindSpy = jasmine.createSpy('unbind')
  9. const assertContext = (el, binding, vnode) => {
  10. expect(vnode.context).toBe(vm)
  11. expect(binding.arg).toBe('arg')
  12. expect(binding.modifiers).toEqual({ hello: true })
  13. }
  14. const vm = new Vue({
  15. template: '<div class="hi"><div v-if="ok" v-test:arg.hello="a">{{ msg }}</div></div>',
  16. data: {
  17. msg: 'hi',
  18. a: 'foo',
  19. ok: true
  20. },
  21. directives: {
  22. test: {
  23. bind (el, binding, vnode) {
  24. bindSpy()
  25. assertContext(el, binding, vnode)
  26. expect(binding.value).toBe('foo')
  27. expect(binding.expression).toBe('a')
  28. expect(binding.oldValue).toBeUndefined()
  29. expect(el.parentNode).toBeNull()
  30. },
  31. inserted (el, binding, vnode) {
  32. insertedSpy()
  33. assertContext(el, binding, vnode)
  34. expect(binding.value).toBe('foo')
  35. expect(binding.expression).toBe('a')
  36. expect(binding.oldValue).toBeUndefined()
  37. expect(el.parentNode.className).toBe('hi')
  38. },
  39. update (el, binding, vnode, oldVnode) {
  40. updateSpy()
  41. assertContext(el, binding, vnode)
  42. expect(el).toBe(vm.$el.children[0])
  43. expect(oldVnode).not.toBe(vnode)
  44. expect(binding.expression).toBe('a')
  45. if (binding.value !== binding.oldValue) {
  46. expect(binding.value).toBe('bar')
  47. expect(binding.oldValue).toBe('foo')
  48. }
  49. },
  50. componentUpdated (el, binding, vnode) {
  51. componentUpdatedSpy()
  52. assertContext(el, binding, vnode)
  53. },
  54. unbind (el, binding, vnode) {
  55. unbindSpy()
  56. assertContext(el, binding, vnode)
  57. }
  58. }
  59. }
  60. })
  61. vm.$mount()
  62. expect(bindSpy).toHaveBeenCalled()
  63. expect(insertedSpy).toHaveBeenCalled()
  64. expect(updateSpy).not.toHaveBeenCalled()
  65. expect(componentUpdatedSpy).not.toHaveBeenCalled()
  66. expect(unbindSpy).not.toHaveBeenCalled()
  67. vm.a = 'bar'
  68. waitForUpdate(() => {
  69. expect(updateSpy).toHaveBeenCalled()
  70. expect(componentUpdatedSpy).toHaveBeenCalled()
  71. expect(unbindSpy).not.toHaveBeenCalled()
  72. vm.msg = 'bye'
  73. }).then(() => {
  74. expect(componentUpdatedSpy.calls.count()).toBe(2)
  75. vm.ok = false
  76. }).then(() => {
  77. expect(unbindSpy).toHaveBeenCalled()
  78. }).then(done)
  79. })
  80. it('function shorthand', done => {
  81. const spy = jasmine.createSpy('directive')
  82. const vm = new Vue({
  83. template: '<div v-test:arg.hello="a"></div>',
  84. data: { a: 'foo' },
  85. directives: {
  86. test (el, binding, vnode) {
  87. expect(vnode.context).toBe(vm)
  88. expect(binding.arg).toBe('arg')
  89. expect(binding.modifiers).toEqual({ hello: true })
  90. spy(binding.value, binding.oldValue)
  91. }
  92. }
  93. })
  94. vm.$mount()
  95. expect(spy).toHaveBeenCalledWith('foo', undefined)
  96. vm.a = 'bar'
  97. waitForUpdate(() => {
  98. expect(spy).toHaveBeenCalledWith('bar', 'foo')
  99. }).then(done)
  100. })
  101. it('function shorthand (global)', done => {
  102. const spy = jasmine.createSpy('directive')
  103. Vue.directive('test', function (el, binding, vnode) {
  104. expect(vnode.context).toBe(vm)
  105. expect(binding.arg).toBe('arg')
  106. expect(binding.modifiers).toEqual({ hello: true })
  107. spy(binding.value, binding.oldValue)
  108. })
  109. const vm = new Vue({
  110. template: '<div v-test:arg.hello="a"></div>',
  111. data: { a: 'foo' }
  112. })
  113. vm.$mount()
  114. expect(spy).toHaveBeenCalledWith('foo', undefined)
  115. vm.a = 'bar'
  116. waitForUpdate(() => {
  117. expect(spy).toHaveBeenCalledWith('bar', 'foo')
  118. delete Vue.options.directives.test
  119. }).then(done)
  120. })
  121. it('should teardown directives on old vnodes when new vnodes have none', done => {
  122. const vm = new Vue({
  123. data: {
  124. ok: true
  125. },
  126. template: `
  127. <div>
  128. <div v-if="ok" v-test>a</div>
  129. <div v-else class="b">b</div>
  130. </div>
  131. `,
  132. directives: {
  133. test: {
  134. bind: el => { el.id = 'a' },
  135. unbind: el => { el.id = '' }
  136. }
  137. }
  138. }).$mount()
  139. expect(vm.$el.children[0].id).toBe('a')
  140. vm.ok = false
  141. waitForUpdate(() => {
  142. expect(vm.$el.children[0].id).toBe('')
  143. expect(vm.$el.children[0].className).toBe('b')
  144. }).then(done)
  145. })
  146. it('should properly handle same node with different directive sets', done => {
  147. const spies = {}
  148. const createSpy = name => (spies[name] = jasmine.createSpy(name))
  149. const vm = new Vue({
  150. data: {
  151. ok: true,
  152. val: 123
  153. },
  154. template: `
  155. <div>
  156. <div v-if="ok" v-test="val" v-test.hi="val"></div>
  157. <div v-if="!ok" v-test.hi="val" v-test2="val"></div>
  158. </div>
  159. `,
  160. directives: {
  161. test: {
  162. bind: createSpy('bind1'),
  163. inserted: createSpy('inserted1'),
  164. update: createSpy('update1'),
  165. componentUpdated: createSpy('componentUpdated1'),
  166. unbind: createSpy('unbind1')
  167. },
  168. test2: {
  169. bind: createSpy('bind2'),
  170. inserted: createSpy('inserted2'),
  171. update: createSpy('update2'),
  172. componentUpdated: createSpy('componentUpdated2'),
  173. unbind: createSpy('unbind2')
  174. }
  175. }
  176. }).$mount()
  177. expect(spies.bind1.calls.count()).toBe(2)
  178. expect(spies.inserted1.calls.count()).toBe(2)
  179. expect(spies.bind2.calls.count()).toBe(0)
  180. expect(spies.inserted2.calls.count()).toBe(0)
  181. vm.ok = false
  182. waitForUpdate(() => {
  183. // v-test with modifier should be updated
  184. expect(spies.update1.calls.count()).toBe(1)
  185. expect(spies.componentUpdated1.calls.count()).toBe(1)
  186. // v-test without modifier should be unbound
  187. expect(spies.unbind1.calls.count()).toBe(1)
  188. // v-test2 should be bound
  189. expect(spies.bind2.calls.count()).toBe(1)
  190. expect(spies.inserted2.calls.count()).toBe(1)
  191. vm.ok = true
  192. }).then(() => {
  193. // v-test without modifier should be bound again
  194. expect(spies.bind1.calls.count()).toBe(3)
  195. expect(spies.inserted1.calls.count()).toBe(3)
  196. // v-test2 should be unbound
  197. expect(spies.unbind2.calls.count()).toBe(1)
  198. // v-test with modifier should be updated again
  199. expect(spies.update1.calls.count()).toBe(2)
  200. expect(spies.componentUpdated1.calls.count()).toBe(2)
  201. vm.val = 234
  202. }).then(() => {
  203. expect(spies.update1.calls.count()).toBe(4)
  204. expect(spies.componentUpdated1.calls.count()).toBe(4)
  205. }).then(done)
  206. })
  207. it('warn non-existent', () => {
  208. new Vue({
  209. template: '<div v-test></div>'
  210. }).$mount()
  211. expect('Failed to resolve directive: test').toHaveBeenWarned()
  212. })
  213. // #6513
  214. it('should invoke unbind & inserted on inner component root element change', done => {
  215. const dir = {
  216. bind: jasmine.createSpy('bind'),
  217. inserted: jasmine.createSpy('inserted'),
  218. unbind: jasmine.createSpy('unbind')
  219. }
  220. const Child = {
  221. template: `<div v-if="ok"/><span v-else/>`,
  222. data: () => ({ ok: true })
  223. }
  224. const vm = new Vue({
  225. template: `<child ref="child" v-test />`,
  226. directives: { test: dir },
  227. components: { Child }
  228. }).$mount()
  229. const oldEl = vm.$el
  230. expect(dir.bind.calls.count()).toBe(1)
  231. expect(dir.bind.calls.argsFor(0)[0]).toBe(oldEl)
  232. expect(dir.inserted.calls.count()).toBe(1)
  233. expect(dir.inserted.calls.argsFor(0)[0]).toBe(oldEl)
  234. expect(dir.unbind).not.toHaveBeenCalled()
  235. vm.$refs.child.ok = false
  236. waitForUpdate(() => {
  237. expect(vm.$el.tagName).toBe('SPAN')
  238. expect(dir.bind.calls.count()).toBe(2)
  239. expect(dir.bind.calls.argsFor(1)[0]).toBe(vm.$el)
  240. expect(dir.inserted.calls.count()).toBe(2)
  241. expect(dir.inserted.calls.argsFor(1)[0]).toBe(vm.$el)
  242. expect(dir.unbind.calls.count()).toBe(1)
  243. expect(dir.unbind.calls.argsFor(0)[0]).toBe(oldEl)
  244. }).then(done)
  245. })
  246. it('dynamic arguments', done => {
  247. const vm = new Vue({
  248. template: `<div v-my:[key]="1"/>`,
  249. data: {
  250. key: 'foo'
  251. },
  252. directives: {
  253. my: {
  254. bind(el, binding) {
  255. expect(binding.arg).toBe('foo')
  256. },
  257. update(el, binding) {
  258. expect(binding.arg).toBe('bar')
  259. expect(binding.oldArg).toBe('foo')
  260. done()
  261. }
  262. }
  263. }
  264. }).$mount()
  265. vm.key = 'bar'
  266. })
  267. it('deep object like `deep.a` as dynamic arguments', done => {
  268. const vm = new Vue({
  269. template: `<div v-my:[deep.a]="1"/>`,
  270. data: {
  271. deep: {
  272. a: 'foo'
  273. }
  274. },
  275. directives: {
  276. my: {
  277. bind(el, binding) {
  278. expect(binding.arg).toBe('foo')
  279. },
  280. update(el, binding) {
  281. expect(binding.arg).toBe('bar')
  282. expect(binding.oldArg).toBe('foo')
  283. done()
  284. }
  285. }
  286. }
  287. }).$mount()
  288. vm.deep.a = 'bar'
  289. })
  290. it('deep object like `deep.a.b` as dynamic arguments', done => {
  291. const vm = new Vue({
  292. template: `<div v-my:[deep.a.b]="1"/>`,
  293. data: {
  294. deep: {
  295. a: {
  296. b: 'foo'
  297. }
  298. }
  299. },
  300. directives: {
  301. my: {
  302. bind(el, binding) {
  303. expect(binding.arg).toBe('foo')
  304. },
  305. update(el, binding) {
  306. expect(binding.arg).toBe('bar')
  307. expect(binding.oldArg).toBe('foo')
  308. done()
  309. }
  310. }
  311. }
  312. }).$mount()
  313. vm.deep.a.b = 'bar'
  314. })
  315. it('deep object as dynamic arguments with modifiers', done => {
  316. const vm = new Vue({
  317. template: `<div v-my:[deep.a.b].x.y="1"/>`,
  318. data: {
  319. deep: {
  320. a: {
  321. b: 'foo'
  322. }
  323. }
  324. },
  325. directives: {
  326. my: {
  327. bind(el, binding) {
  328. expect(binding.arg).toBe('foo')
  329. expect(binding.modifiers.x).toBe(true)
  330. expect(binding.modifiers.y).toBe(true)
  331. },
  332. update(el, binding) {
  333. expect(binding.arg).toBe('bar')
  334. expect(binding.oldArg).toBe('foo')
  335. done()
  336. }
  337. }
  338. }
  339. }).$mount()
  340. vm.deep.a.b = 'bar'
  341. })
  342. })