slot_spec.js 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388
  1. var Vue = require('../../../../../src/vue')
  2. var _ = require('../../../../../src/util')
  3. describe('Slot Distribution', function () {
  4. var el, vm, options
  5. beforeEach(function () {
  6. el = document.createElement('div')
  7. options = {
  8. el: el,
  9. data: {
  10. msg: 'self'
  11. }
  12. }
  13. })
  14. function mount () {
  15. vm = new Vue(options)
  16. }
  17. it('no content', function () {
  18. options.template = '<div><slot></slot></div>'
  19. mount()
  20. expect(el.firstChild.childNodes.length).toBe(0)
  21. })
  22. it('default content', function () {
  23. el.innerHTML = '<p>hi</p>'
  24. options.template = '<div><slot></slot></div>'
  25. mount()
  26. expect(el.firstChild.tagName).toBe('DIV')
  27. expect(el.firstChild.firstChild.tagName).toBe('P')
  28. expect(el.firstChild.firstChild.textContent).toBe('hi')
  29. })
  30. it('no template auto content', function () {
  31. el.innerHTML = '<p>hi</p>'
  32. options._asComponent = true
  33. mount()
  34. expect(el.firstChild.tagName).toBe('P')
  35. expect(el.firstChild.textContent).toBe('hi')
  36. })
  37. it('fallback content', function () {
  38. options.template = '<slot><p>{{msg}}</p></slot>'
  39. mount()
  40. expect(el.firstChild.tagName).toBe('P')
  41. expect(el.firstChild.textContent).toBe('self')
  42. })
  43. it('fallback content with multiple named slots', function () {
  44. el.innerHTML = '<p slot="b">slot b</p>'
  45. options.template =
  46. '<slot name="a"><p>fallback a</p></slot>' +
  47. '<slot name="b">fallback b</slot>'
  48. mount()
  49. expect(el.childNodes.length).toBe(2)
  50. expect(el.firstChild.textContent).toBe('fallback a')
  51. expect(el.lastChild.textContent).toBe('slot b')
  52. })
  53. it('selector matching multiple elements', function () {
  54. el.innerHTML = '<p slot="t">1</p><div></div><p slot="t">2</p>'
  55. options.template = '<slot name="t"></slot>'
  56. mount()
  57. expect(el.innerHTML).toBe('<p slot="t">1</p><p slot="t">2</p>')
  58. })
  59. it('default content should only render parts not selected', function () {
  60. el.innerHTML = '<div>hi</div><p slot="a">1</p><p slot="b">2</p>'
  61. options.template =
  62. '<slot name="a"></slot>' +
  63. '<slot></slot>' +
  64. '<slot name="b"></slot>'
  65. mount()
  66. expect(el.innerHTML).toBe('<p slot="a">1</p><div>hi</div><p slot="b">2</p>')
  67. })
  68. it('content transclusion with replace', function () {
  69. el.innerHTML = '<p>hi</p>'
  70. options.template = '<div><div><slot></slot></div></div>'
  71. options.replace = true
  72. mount()
  73. var res = vm.$el
  74. expect(res).not.toBe(el)
  75. expect(res.firstChild.tagName).toBe('DIV')
  76. expect(res.firstChild.firstChild.tagName).toBe('P')
  77. expect(res.firstChild.firstChild.textContent).toBe('hi')
  78. })
  79. it('block instance content transclusion', function () {
  80. el.innerHTML = '<p slot="p">hi</p><span slot="span">ho</span>'
  81. options.template = '<div></div><slot name="p"></slot><slot name="span"></slot>'
  82. options.replace = true
  83. mount()
  84. expect(getChild(1).tagName).toBe('DIV')
  85. expect(getChild(2).tagName).toBe('P')
  86. expect(getChild(3).tagName).toBe('SPAN')
  87. function getChild (n) {
  88. var el = vm._fragmentStart
  89. while (n--) {
  90. el = el.nextSibling
  91. }
  92. return el
  93. }
  94. })
  95. it('name should only match children', function () {
  96. el.innerHTML =
  97. '<p slot="b">select b</p>' +
  98. '<span><p slot="b">nested b</p></span>' +
  99. '<span><p slot="c">nested c</p></span>'
  100. options.template =
  101. '<slot name="a"><p>fallback a</p></slot>' +
  102. '<slot name="b">fallback b</slot>' +
  103. '<slot name="c">fallback c</slot>'
  104. mount()
  105. expect(el.childNodes.length).toBe(3)
  106. expect(el.firstChild.textContent).toBe('fallback a')
  107. expect(el.childNodes[1].textContent).toBe('select b')
  108. expect(el.lastChild.textContent).toBe('fallback c')
  109. })
  110. it('should accept expressions in selectors', function () {
  111. el.innerHTML = '<p>one</p><p slot="two">two</p>'
  112. options.template = '<slot :name="theName"></slot>'
  113. options.data = {
  114. theName: 'two'
  115. }
  116. mount()
  117. expect(el.innerHTML).toBe('<p slot="two">two</p>')
  118. })
  119. it('content should be dynamic and compiled in parent scope', function (done) {
  120. var vm = new Vue({
  121. el: el,
  122. data: {
  123. msg: 'hello'
  124. },
  125. template: '<test>{{msg}}</test>',
  126. components: {
  127. test: {
  128. template: '<slot></slot>'
  129. }
  130. }
  131. })
  132. expect(el.innerHTML).toBe('<test>hello</test>')
  133. vm.msg = 'what'
  134. _.nextTick(function () {
  135. expect(el.innerHTML).toBe('<test>what</test>')
  136. done()
  137. })
  138. })
  139. it('v-if with content transclusion', function (done) {
  140. var vm = new Vue({
  141. el: el,
  142. data: {
  143. a: 1,
  144. show: true
  145. },
  146. template: '<test :show="show">{{a}}</test>',
  147. components: {
  148. test: {
  149. props: ['show'],
  150. template: '<div v-if="show"><slot></cotent></div>'
  151. }
  152. }
  153. })
  154. expect(el.textContent).toBe('1')
  155. vm.a = 2
  156. _.nextTick(function () {
  157. expect(el.textContent).toBe('2')
  158. vm.show = false
  159. _.nextTick(function () {
  160. expect(el.textContent).toBe('')
  161. vm.show = true
  162. vm.a = 3
  163. _.nextTick(function () {
  164. expect(el.textContent).toBe('3')
  165. done()
  166. })
  167. })
  168. })
  169. })
  170. it('inline v-for', function () {
  171. el.innerHTML = '<p slot="1">1</p><p slot="2">2</p><p slot="3">3</p>'
  172. new Vue({
  173. el: el,
  174. template: '<div v-for="n in list"><slot :name="$index + 1"></slot></div>',
  175. data: {
  176. list: 0
  177. },
  178. beforeCompile: function () {
  179. this.list = this.$options._content.querySelectorAll('p').length
  180. }
  181. })
  182. expect(el.innerHTML).toBe('<div><p slot="1">1</p></div><div><p slot="2">2</p></div><div><p slot="3">3</p></div>')
  183. })
  184. it('v-for + component + parent directive + transclusion', function (done) {
  185. var vm = new Vue({
  186. el: el,
  187. template: '<test v-for="n in list" :class="cls" :a="n.a">{{msg}}</test>',
  188. data: {
  189. cls: 'parent',
  190. msg: 'hi',
  191. list: [{a: 1}, {a: 2}, {a: 3}]
  192. },
  193. components: {
  194. test: {
  195. replace: true,
  196. props: ['a'],
  197. template: '<div class="child">{{a}} <slot></slot></div>'
  198. }
  199. }
  200. })
  201. var markup = vm.list.map(function (item) {
  202. return '<div class="child parent">' + item.a + ' hi</div>'
  203. }).join('')
  204. expect(el.innerHTML).toBe(markup)
  205. vm.msg = 'ho'
  206. markup = vm.list.map(function (item) {
  207. return '<div class="child parent">' + item.a + ' ho</div>'
  208. }).join('')
  209. _.nextTick(function () {
  210. expect(el.innerHTML).toBe(markup)
  211. done()
  212. })
  213. })
  214. it('nested transclusions', function (done) {
  215. vm = new Vue({
  216. el: el,
  217. template:
  218. '<testa>' +
  219. '<testb>' +
  220. '<div v-for="n in list">{{n}}</div>' +
  221. '</testb>' +
  222. '</testa>',
  223. data: {
  224. list: [1, 2]
  225. },
  226. components: {
  227. testa: { template: '<slot></slot>' },
  228. testb: { template: '<slot></slot>' }
  229. }
  230. })
  231. expect(el.innerHTML).toBe(
  232. '<testa><testb>' +
  233. '<div>1</div><div>2</div>' +
  234. '</testb></testa>'
  235. )
  236. vm.list.push(3)
  237. _.nextTick(function () {
  238. expect(el.innerHTML).toBe(
  239. '<testa><testb>' +
  240. '<div>1</div><div>2</div><div>3</div>' +
  241. '</testb></testa>'
  242. )
  243. done()
  244. })
  245. })
  246. it('nested transclusion, container dirs & props', function (done) {
  247. vm = new Vue({
  248. el: el,
  249. template:
  250. '<testa>' +
  251. '<testb v-if="ok" :msg="msg"></testb>' +
  252. '</testa>',
  253. data: {
  254. ok: false,
  255. msg: 'hello'
  256. },
  257. components: {
  258. testa: { template: '<slot></slot>' },
  259. testb: {
  260. props: ['msg'],
  261. template: '{{msg}}'
  262. }
  263. }
  264. })
  265. expect(el.innerHTML).toBe('<testa></testa>')
  266. vm.ok = true
  267. _.nextTick(function () {
  268. expect(el.innerHTML).toBe('<testa><testb>hello</testb></testa>')
  269. done()
  270. })
  271. })
  272. // #1010
  273. it('v-for inside transcluded content', function () {
  274. vm = new Vue({
  275. el: el,
  276. template:
  277. '<testa>' +
  278. '{{inner}} {{outer}}' +
  279. '<div v-for="item in list"> {{item.inner}} {{outer}}</div>' +
  280. '</testa>',
  281. data: {
  282. outer: 'outer',
  283. inner: 'parent-inner',
  284. list: [
  285. { inner: 'list-inner' }
  286. ]
  287. },
  288. components: {
  289. testa: {
  290. data: function () {
  291. return {
  292. inner: 'component-inner'
  293. }
  294. },
  295. template: '<slot></slot>'
  296. }
  297. }
  298. })
  299. expect(el.textContent).toBe('parent-inner outer list-inner outer')
  300. })
  301. it('single content outlet with replace: true', function () {
  302. vm = new Vue({
  303. el: el,
  304. template:
  305. '<test><p>1</p><p>2</p></test>',
  306. components: {
  307. test: {
  308. template: '<slot></slot>',
  309. replace: true
  310. }
  311. }
  312. })
  313. expect(el.innerHTML).toBe('<p>1</p><p>2</p>')
  314. })
  315. it('template slot', function () {
  316. vm = new Vue({
  317. el: el,
  318. template:
  319. '<test><template slot="test">hello</template></test>',
  320. components: {
  321. test: {
  322. template: '<slot name="test"></slot> world',
  323. replace: true
  324. }
  325. }
  326. })
  327. expect(el.innerHTML).toBe('hello world')
  328. })
  329. it('inside v-for', function () {
  330. new Vue({
  331. el: el,
  332. template: '<comp v-for="item in items">{{item.value}}</comp>',
  333. data: {
  334. items: [{value: 123}, {value: 234}]
  335. },
  336. components: {
  337. comp: {
  338. tempalte: '<div><slot></slot></div>'
  339. }
  340. }
  341. })
  342. expect(el.textContent).toBe('123234')
  343. })
  344. it('fallback inside v-for', function () {
  345. new Vue({
  346. el: el,
  347. template: '<div v-for="n in 3"><comp></comp></div>',
  348. components: {
  349. comp: {
  350. template: '<div><slot>{{something}}</slot></div>',
  351. data: function () {
  352. return {
  353. something: 'hi'
  354. }
  355. }
  356. }
  357. }
  358. })
  359. expect(el.textContent).toBe('hihihi')
  360. })
  361. })