once.spec.js 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342
  1. import Vue from 'vue'
  2. describe('Directive v-once', () => {
  3. it('should not rerender component', done => {
  4. const vm = new Vue({
  5. template: '<div v-once>{{ a }}</div>',
  6. data: { a: 'hello' }
  7. }).$mount()
  8. expect(vm.$el.innerHTML).toBe('hello')
  9. vm.a = 'world'
  10. waitForUpdate(() => {
  11. expect(vm.$el.innerHTML).toBe('hello')
  12. }).then(done)
  13. })
  14. it('should not rerender self and child component', done => {
  15. const vm = new Vue({
  16. template: `
  17. <div v-once>
  18. <span>{{ a }}</span>
  19. <item :b="a"></item>
  20. </div>`,
  21. data: { a: 'hello' },
  22. components: {
  23. item: {
  24. template: '<div>{{ b }}</div>',
  25. props: ['b']
  26. }
  27. }
  28. }).$mount()
  29. expect(vm.$children.length).toBe(1)
  30. expect(vm.$el.innerHTML)
  31. .toBe('<span>hello</span> <div>hello</div>')
  32. vm.a = 'world'
  33. waitForUpdate(() => {
  34. expect(vm.$el.innerHTML)
  35. .toBe('<span>hello</span> <div>hello</div>')
  36. }).then(done)
  37. })
  38. it('should rerender parent but not self', done => {
  39. const vm = new Vue({
  40. template: `
  41. <div>
  42. <span>{{ a }}</span>
  43. <item v-once :b="a"></item>
  44. </div>`,
  45. data: { a: 'hello' },
  46. components: {
  47. item: {
  48. template: '<div>{{ b }}</div>',
  49. props: ['b']
  50. }
  51. }
  52. }).$mount()
  53. expect(vm.$children.length).toBe(1)
  54. expect(vm.$el.innerHTML)
  55. .toBe('<span>hello</span> <div>hello</div>')
  56. vm.a = 'world'
  57. waitForUpdate(() => {
  58. expect(vm.$el.innerHTML)
  59. .toBe('<span>world</span> <div>hello</div>')
  60. }).then(done)
  61. })
  62. it('should not rerender static sub nodes', done => {
  63. const vm = new Vue({
  64. template: `
  65. <div>
  66. <span v-once>{{ a }}</span>
  67. <item :b="a"></item>
  68. <span>{{ suffix }}</span>
  69. </div>`,
  70. data: {
  71. a: 'hello',
  72. suffix: '?'
  73. },
  74. components: {
  75. item: {
  76. template: '<div>{{ b }}</div>',
  77. props: ['b']
  78. }
  79. }
  80. }).$mount()
  81. expect(vm.$el.innerHTML)
  82. .toBe('<span>hello</span> <div>hello</div> <span>?</span>')
  83. vm.a = 'world'
  84. waitForUpdate(() => {
  85. expect(vm.$el.innerHTML)
  86. .toBe('<span>hello</span> <div>world</div> <span>?</span>')
  87. vm.suffix = '!'
  88. }).then(() => {
  89. expect(vm.$el.innerHTML)
  90. .toBe('<span>hello</span> <div>world</div> <span>!</span>')
  91. }).then(done)
  92. })
  93. it('should work with v-if', done => {
  94. const vm = new Vue({
  95. data: {
  96. tester: true,
  97. yes: 'y',
  98. no: 'n'
  99. },
  100. template: `
  101. <div>
  102. <div v-if="tester">{{ yes }}</div>
  103. <div v-else>{{ no }}</div>
  104. <div v-if="tester" v-once>{{ yes }}</div>
  105. <div v-else>{{ no }}</div>
  106. <div v-if="tester">{{ yes }}</div>
  107. <div v-else v-once>{{ no }}</div>
  108. <div v-if="tester" v-once>{{ yes }}</div>
  109. <div v-else v-once>{{ no }}</div>
  110. </div>
  111. `
  112. }).$mount()
  113. expectTextContent(vm, 'yyyy')
  114. vm.yes = 'yes'
  115. waitForUpdate(() => {
  116. expectTextContent(vm, 'yesyyesy')
  117. vm.tester = false
  118. }).then(() => {
  119. expectTextContent(vm, 'nnnn')
  120. vm.no = 'no'
  121. }).then(() => {
  122. expectTextContent(vm, 'nononn')
  123. }).then(done)
  124. })
  125. it('should work with v-for', done => {
  126. const vm = new Vue({
  127. data: {
  128. list: [1, 2, 3]
  129. },
  130. template: `<div><div v-for="i in list" v-once>{{i}}</div></div>`
  131. }).$mount()
  132. expect(vm.$el.textContent).toBe('123')
  133. vm.list.reverse()
  134. waitForUpdate(() => {
  135. expect(vm.$el.textContent).toBe('123')
  136. }).then(done)
  137. })
  138. it('should work inside v-for', done => {
  139. const vm = new Vue({
  140. data: {
  141. list: [
  142. { id: 0, text: 'a' },
  143. { id: 1, text: 'b' },
  144. { id: 2, text: 'c' }
  145. ]
  146. },
  147. template: `
  148. <div>
  149. <div v-for="i in list" :key="i.id">
  150. <div>
  151. <span v-once>{{ i.text }}</span><span>{{ i.text }}</span>
  152. </div>
  153. </div>
  154. </div>
  155. `
  156. }).$mount()
  157. expect(vm.$el.textContent).toBe('aabbcc')
  158. vm.list[0].text = 'd'
  159. waitForUpdate(() => {
  160. expect(vm.$el.textContent).toBe('adbbcc')
  161. vm.list[1].text = 'e'
  162. }).then(() => {
  163. expect(vm.$el.textContent).toBe('adbecc')
  164. vm.list.reverse()
  165. }).then(() => {
  166. expect(vm.$el.textContent).toBe('ccbead')
  167. }).then(done)
  168. })
  169. it('should work inside v-for with v-if', done => {
  170. const vm = new Vue({
  171. data: {
  172. list: [
  173. { id: 0, text: 'a', tester: true, truthy: 'y' }
  174. ]
  175. },
  176. template: `
  177. <div>
  178. <div v-for="i in list" :key="i.id">
  179. <span v-if="i.tester" v-once>{{ i.truthy }}</span>
  180. <span v-else v-once>{{ i.text }}</span>
  181. <span v-if="i.tester" v-once>{{ i.truthy }}</span>
  182. <span v-else>{{ i.text }}</span>
  183. <span v-if="i.tester">{{ i.truthy }}</span>
  184. <span v-else v-once>{{ i.text }}</span>
  185. <span v-if="i.tester">{{ i.truthy }}</span>
  186. <span v-else>{{ i.text }}</span>
  187. </div>
  188. </div>
  189. `
  190. }).$mount()
  191. expectTextContent(vm, 'yyyy')
  192. vm.list[0].truthy = 'yy'
  193. waitForUpdate(() => {
  194. expectTextContent(vm, 'yyyyyy')
  195. vm.list[0].tester = false
  196. }).then(() => {
  197. expectTextContent(vm, 'aaaa')
  198. vm.list[0].text = 'nn'
  199. }).then(() => {
  200. expectTextContent(vm, 'annann')
  201. }).then(done)
  202. })
  203. it('should work inside v-for with nested v-else', done => {
  204. const vm = new Vue({
  205. data: {
  206. list: [{ id: 0, text: 'a', tester: true, truthy: 'y' }]
  207. },
  208. template: `
  209. <div v-if="0"></div>
  210. <div v-else>
  211. <div v-for="i in list" :key="i.id">
  212. <span v-if="i.tester" v-once>{{ i.truthy }}</span>
  213. <span v-else v-once>{{ i.text }}</span>
  214. </div>
  215. </div>
  216. `
  217. }).$mount()
  218. expectTextContent(vm, 'y')
  219. vm.list[0].truthy = 'yy'
  220. waitForUpdate(() => {
  221. expectTextContent(vm, 'y')
  222. vm.list[0].tester = false
  223. }).then(() => {
  224. expectTextContent(vm, 'a')
  225. vm.list[0].text = 'nn'
  226. }).then(() => {
  227. expectTextContent(vm, 'a')
  228. }).then(done)
  229. })
  230. it('should work inside v-for with nested v-else-if and v-else', done => {
  231. const vm = new Vue({
  232. data: {
  233. tester: false,
  234. list: [{ id: 0, text: 'a', tester: true, truthy: 'y' }]
  235. },
  236. template: `
  237. <div v-if="0"></div>
  238. <div v-else-if="tester">
  239. <div v-for="i in list" :key="i.id">
  240. <span v-if="i.tester" v-once>{{ i.truthy }}</span>
  241. <span v-else-if="tester" v-once>{{ i.text }}elseif</span>
  242. <span v-else v-once>{{ i.text }}</span>
  243. </div>
  244. </div>
  245. <div v-else>
  246. <div v-for="i in list" :key="i.id">
  247. <span v-if="i.tester" v-once>{{ i.truthy }}</span>
  248. <span v-else-if="tester">{{ i.text }}elseif</span>
  249. <span v-else v-once>{{ i.text }}</span>
  250. </div>
  251. </div>
  252. `
  253. }).$mount()
  254. expectTextContent(vm, 'y')
  255. vm.list[0].truthy = 'yy'
  256. waitForUpdate(() => {
  257. expectTextContent(vm, 'y')
  258. vm.list[0].tester = false
  259. }).then(() => {
  260. expectTextContent(vm, 'a')
  261. vm.list[0].text = 'nn'
  262. }).then(() => {
  263. expectTextContent(vm, 'a')
  264. vm.tester = true
  265. }).then(() => {
  266. expectTextContent(vm, 'nnelseif')
  267. vm.list[0].text = 'xx'
  268. }).then(() => {
  269. expectTextContent(vm, 'nnelseif')
  270. vm.list[0].tester = true
  271. }).then(() => {
  272. expectTextContent(vm, 'yy')
  273. vm.list[0].truthy = 'nn'
  274. }).then(() => {
  275. expectTextContent(vm, 'yy')
  276. }).then(done)
  277. })
  278. it('should warn inside non-keyed v-for', () => {
  279. const vm = new Vue({
  280. data: {
  281. list: [
  282. { id: 0, text: 'a' },
  283. { id: 1, text: 'b' },
  284. { id: 2, text: 'c' }
  285. ]
  286. },
  287. template: `
  288. <div>
  289. <div v-for="i in list">
  290. <span v-once>{{ i.text }}</span><span>{{ i.text }}</span>
  291. </div>
  292. </div>
  293. `
  294. }).$mount()
  295. expect(vm.$el.textContent).toBe('aabbcc')
  296. expect(`v-once can only be used inside v-for that is keyed.`).toHaveBeenWarned()
  297. })
  298. // #4288
  299. it('should inherit child reference for v-once', done => {
  300. const vm = new Vue({
  301. template: `<div>{{a}}<test v-if="ok" v-once></test></div>`,
  302. data: {
  303. a: 0,
  304. ok: true
  305. },
  306. components: {
  307. test: {
  308. template: '<div>foo</div>'
  309. }
  310. }
  311. }).$mount()
  312. vm.a++ // first update to force a patch
  313. waitForUpdate(() => {
  314. expect(vm.$el.textContent).toBe('1foo')
  315. }).then(() => {
  316. vm.ok = false // teardown component with v-once
  317. }).then(done) // should not throw
  318. })
  319. })
  320. function expectTextContent (vm, text) {
  321. expect(vm.$el.textContent.replace(/\s+/g, '')).toBe(text)
  322. }