slot_spec.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479
  1. var Vue = require('src')
  2. var nextTick = Vue.nextTick
  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>foo</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('foo')
  29. })
  30. it('no template auto content', function () {
  31. el.innerHTML = '<p>foo</p>'
  32. options._asComponent = true
  33. mount()
  34. expect(el.firstChild.tagName).toBe('P')
  35. expect(el.firstChild.textContent).toBe('foo')
  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('fallback content with mixed named/unnamed slots', function () {
  54. el.innerHTML = '<p slot="b">slot b</p>'
  55. options.template =
  56. '<slot><p>fallback a</p></slot>' +
  57. '<slot name="b">fallback b</slot>'
  58. mount()
  59. expect(el.childNodes.length).toBe(2)
  60. expect(el.firstChild.textContent).toBe('fallback a')
  61. expect(el.lastChild.textContent).toBe('slot b')
  62. })
  63. it('selector matching multiple elements', function () {
  64. el.innerHTML = '<p slot="t">1</p><div></div><p slot="t">2</p>'
  65. options.template = '<slot name="t"></slot>'
  66. mount()
  67. expect(el.innerHTML).toBe('<p slot="t">1</p><p slot="t">2</p>')
  68. })
  69. it('default content should only render parts not selected', function () {
  70. el.innerHTML = '<div>foo</div><p slot="a">1</p><p slot="b">2</p>'
  71. options.template =
  72. '<slot name="a"></slot>' +
  73. '<slot></slot>' +
  74. '<slot name="b"></slot>'
  75. mount()
  76. expect(el.innerHTML).toBe('<p slot="a">1</p><div>foo</div><p slot="b">2</p>')
  77. })
  78. it('content transclusion with replace', function () {
  79. el.innerHTML = '<p>foo</p>'
  80. options.template = '<div><div><slot></slot></div></div>'
  81. options.replace = true
  82. mount()
  83. var res = vm.$el
  84. expect(res).not.toBe(el)
  85. expect(res.firstChild.tagName).toBe('DIV')
  86. expect(res.firstChild.firstChild.tagName).toBe('P')
  87. expect(res.firstChild.firstChild.textContent).toBe('foo')
  88. })
  89. it('block instance content transclusion', function () {
  90. el.innerHTML = '<p slot="p">foo</p><span slot="span">ho</span>'
  91. options.template = '<div></div><slot name="p"></slot><slot name="span"></slot>'
  92. options.replace = true
  93. mount()
  94. expect(getChild(1).tagName).toBe('DIV')
  95. expect(getChild(2).tagName).toBe('P')
  96. expect(getChild(3).tagName).toBe('SPAN')
  97. function getChild (n) {
  98. var el = vm._fragmentStart
  99. while (n--) {
  100. el = el.nextSibling
  101. }
  102. return el
  103. }
  104. })
  105. it('name should only match children', function () {
  106. el.innerHTML =
  107. '<p slot="b">select b</p>' +
  108. '<span><p slot="b">nested b</p></span>' +
  109. '<span><p slot="c">nested c</p></span>'
  110. options.template =
  111. '<slot name="a"><p>fallback a</p></slot>' +
  112. '<slot name="b">fallback b</slot>' +
  113. '<slot name="c">fallback c</slot>'
  114. mount()
  115. expect(el.childNodes.length).toBe(3)
  116. expect(el.firstChild.textContent).toBe('fallback a')
  117. expect(el.childNodes[1].textContent).toBe('select b')
  118. expect(el.lastChild.textContent).toBe('fallback c')
  119. })
  120. it('should accept expressions in selectors', function () {
  121. el.innerHTML = '<p>one</p><p slot="two">two</p>'
  122. options.template = '<slot :name="theName"></slot>'
  123. options.data = {
  124. theName: 'two'
  125. }
  126. mount()
  127. expect(el.innerHTML).toBe('<p slot="two">two</p>')
  128. })
  129. it('content should be dynamic and compiled in parent scope', function (done) {
  130. var vm = new Vue({
  131. el: el,
  132. data: {
  133. msg: 'foo'
  134. },
  135. template: '<test>{{msg}}</test>',
  136. components: {
  137. test: {
  138. template: '<slot></slot>'
  139. }
  140. }
  141. })
  142. expect(el.innerHTML).toBe('<test>foo</test>')
  143. vm.msg = 'bar'
  144. nextTick(function () {
  145. expect(el.innerHTML).toBe('<test>bar</test>')
  146. done()
  147. })
  148. })
  149. it('v-if with content transclusion', function (done) {
  150. var vm = new Vue({
  151. el: el,
  152. data: {
  153. a: 1,
  154. b: 2,
  155. show: true
  156. },
  157. template: '<test :show="show"><p slot="b">{{b}}</a><p>{{a}}</p></test>',
  158. components: {
  159. test: {
  160. props: ['show'],
  161. template: '<div v-if="show"><slot></slot><slot name="b"></slot></div>'
  162. }
  163. }
  164. })
  165. expect(el.textContent).toBe('12')
  166. vm.a = 2
  167. nextTick(function () {
  168. expect(el.textContent).toBe('22')
  169. vm.show = false
  170. nextTick(function () {
  171. expect(el.textContent).toBe('')
  172. vm.show = true
  173. vm.a = 3
  174. nextTick(function () {
  175. expect(el.textContent).toBe('32')
  176. done()
  177. })
  178. })
  179. })
  180. })
  181. it('inline v-for', function () {
  182. el.innerHTML = '<p slot="1">1</p><p slot="2">2</p><p slot="3">3</p>'
  183. new Vue({
  184. el: el,
  185. template: '<div v-for="n in list"><slot :name="$index + 1"></slot></div>',
  186. data: {
  187. list: 0
  188. },
  189. beforeCompile: function () {
  190. this.list = this.$options._content.querySelectorAll('p').length
  191. }
  192. })
  193. 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>')
  194. })
  195. it('v-for + component + parent directive + transclusion', function (done) {
  196. var vm = new Vue({
  197. el: el,
  198. template: '<test v-for="n in list" :class="cls" :a="n.a">{{msg}}</test>',
  199. data: {
  200. cls: 'parent',
  201. msg: 'foo',
  202. list: [{a: 1}, {a: 2}, {a: 3}]
  203. },
  204. components: {
  205. test: {
  206. replace: true,
  207. props: ['a'],
  208. template: '<div class="child">{{a}} <slot></slot></div>'
  209. }
  210. }
  211. })
  212. var markup = vm.list.map(function (item) {
  213. return '<div class="child parent">' + item.a + ' foo</div>'
  214. }).join('')
  215. expect(el.innerHTML).toBe(markup)
  216. vm.msg = 'bar'
  217. markup = vm.list.map(function (item) {
  218. return '<div class="child parent">' + item.a + ' bar</div>'
  219. }).join('')
  220. nextTick(function () {
  221. expect(el.innerHTML).toBe(markup)
  222. done()
  223. })
  224. })
  225. it('nested transclusions', function (done) {
  226. vm = new Vue({
  227. el: el,
  228. template:
  229. '<testa>' +
  230. '<testb>' +
  231. '<div v-for="n in list">{{n}}</div>' +
  232. '</testb>' +
  233. '</testa>',
  234. data: {
  235. list: [1, 2]
  236. },
  237. components: {
  238. testa: { template: '<slot></slot>' },
  239. testb: { template: '<slot></slot>' }
  240. }
  241. })
  242. expect(el.innerHTML).toBe(
  243. '<testa><testb>' +
  244. '<div>1</div><div>2</div>' +
  245. '</testb></testa>'
  246. )
  247. vm.list.push(3)
  248. nextTick(function () {
  249. expect(el.innerHTML).toBe(
  250. '<testa><testb>' +
  251. '<div>1</div><div>2</div><div>3</div>' +
  252. '</testb></testa>'
  253. )
  254. done()
  255. })
  256. })
  257. it('nested transclusion, container dirs & props', function (done) {
  258. vm = new Vue({
  259. el: el,
  260. template:
  261. '<testa>' +
  262. '<testb v-if="ok" :msg="msg"></testb>' +
  263. '</testa>',
  264. data: {
  265. ok: false,
  266. msg: 'hello'
  267. },
  268. components: {
  269. testa: { template: '<slot></slot>' },
  270. testb: {
  271. props: ['msg'],
  272. template: '{{msg}}'
  273. }
  274. }
  275. })
  276. expect(el.innerHTML).toBe('<testa></testa>')
  277. vm.ok = true
  278. nextTick(function () {
  279. expect(el.innerHTML).toBe('<testa><testb>hello</testb></testa>')
  280. done()
  281. })
  282. })
  283. // #1010
  284. it('v-for inside transcluded content', function () {
  285. vm = new Vue({
  286. el: el,
  287. template:
  288. '<testa>' +
  289. '{{inner}} {{outer}}' +
  290. '<div v-for="item in list"> {{item.inner}} {{outer}}</div>' +
  291. '</testa>',
  292. data: {
  293. outer: 'outer',
  294. inner: 'parent-inner',
  295. list: [
  296. { inner: 'list-inner' }
  297. ]
  298. },
  299. components: {
  300. testa: {
  301. data: function () {
  302. return {
  303. inner: 'component-inner'
  304. }
  305. },
  306. template: '<slot></slot>'
  307. }
  308. }
  309. })
  310. expect(el.textContent).toBe('parent-inner outer list-inner outer')
  311. })
  312. it('single content outlet with replace: true', function () {
  313. vm = new Vue({
  314. el: el,
  315. template:
  316. '<test><p>1</p><p>2</p></test>',
  317. components: {
  318. test: {
  319. template: '<slot></slot>',
  320. replace: true
  321. }
  322. }
  323. })
  324. expect(el.innerHTML).toBe('<p>1</p><p>2</p>')
  325. })
  326. it('template slot', function () {
  327. vm = new Vue({
  328. el: el,
  329. template:
  330. '<test><template slot="test">hello</template></test>',
  331. components: {
  332. test: {
  333. template: '<slot name="test"></slot> world',
  334. replace: true
  335. }
  336. }
  337. })
  338. expect(el.innerHTML).toBe('hello world')
  339. })
  340. it('inside v-for', function () {
  341. new Vue({
  342. el: el,
  343. template: '<comp v-for="item in items">{{item.value}}</comp>',
  344. data: {
  345. items: [{value: 123}, {value: 234}]
  346. },
  347. components: {
  348. comp: {
  349. tempalte: '<div><slot></slot></div>'
  350. }
  351. }
  352. })
  353. expect(el.textContent).toBe('123234')
  354. })
  355. it('fallback inside v-for', function () {
  356. new Vue({
  357. el: el,
  358. template: '<div v-for="n in 3"><comp></comp></div>',
  359. components: {
  360. comp: {
  361. template: '<div><slot>{{foo}}</slot></div>',
  362. data: function () {
  363. return {
  364. foo: 'bar'
  365. }
  366. }
  367. }
  368. }
  369. })
  370. expect(el.textContent).toBe('barbarbar')
  371. })
  372. it('fallback for slot with v-if', function (done) {
  373. var vm = new Vue({
  374. el: el,
  375. data: {
  376. ok: false,
  377. msg: 'inserted'
  378. },
  379. template: '<div><comp><div v-if="ok">{{ msg }}</div></comp></div>',
  380. components: {
  381. comp: {
  382. data: function () {
  383. return { msg: 'fallback' }
  384. },
  385. template: '<div><slot>{{ msg }}</slot></div>'
  386. }
  387. }
  388. })
  389. expect(el.textContent).toBe('fallback')
  390. vm.ok = true
  391. nextTick(function () {
  392. expect(el.textContent).toBe('inserted')
  393. done()
  394. })
  395. })
  396. // #2435
  397. it('slot inside template', function () {
  398. var vm = new Vue({
  399. el: el,
  400. template: '<test>foo</test>',
  401. components: {
  402. test: {
  403. data: function () {
  404. return { ok: true }
  405. },
  406. template:
  407. '<div>' +
  408. '<template v-if="ok">' +
  409. '<template v-if="ok">' +
  410. '<slot>{{ msg }}</slot>' +
  411. '</template>' +
  412. '</template>' +
  413. '</div>'
  414. }
  415. }
  416. })
  417. expect(vm.$el.textContent).toBe('foo')
  418. })
  419. it('warn dynamic slot attribute', function () {
  420. new Vue({
  421. el: el,
  422. template: '<test><div :slot="1"></div></test>',
  423. components: {
  424. test: {
  425. template: '<div><slot></slot></div>'
  426. }
  427. }
  428. })
  429. expect('"slot" attribute must be static').toHaveBeenWarned()
  430. })
  431. it('default slot should use fallback content if has only whitespace', function () {
  432. new Vue({
  433. el: el,
  434. template: '<test><div slot="first">1</div> <div slot="second">2</div></test>',
  435. components: {
  436. test: {
  437. replace: true,
  438. template:
  439. '<div class="wrapper">' +
  440. '<slot name="first"><p>first slot</p></slot>' +
  441. '<slot><p>this is the default slot</p></slot>' +
  442. '<slot name="second"><p>second named slot</p></slot>' +
  443. '</div>'
  444. }
  445. }
  446. })
  447. expect(el.children[0].innerHTML).toBe('<div slot="first">1</div><p>this is the default slot</p><div slot="second">2</div>')
  448. })
  449. })