component-slot.spec.ts 27 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076
  1. import Vue from 'vue'
  2. describe('Component slot', () => {
  3. let vm, child
  4. function mount(options) {
  5. vm = new Vue({
  6. data: {
  7. msg: 'parent message'
  8. },
  9. template: `<div><test>${options.parentContent || ''}</test></div>`,
  10. components: {
  11. test: {
  12. template: options.childTemplate,
  13. data() {
  14. return {
  15. msg: 'child message'
  16. }
  17. }
  18. }
  19. }
  20. }).$mount()
  21. child = vm.$children[0]
  22. }
  23. it('no content', () => {
  24. mount({
  25. childTemplate: '<div><slot></slot></div>'
  26. })
  27. expect(child.$el.childNodes.length).toBe(0)
  28. })
  29. it('default slot', done => {
  30. mount({
  31. childTemplate: '<div><slot></slot></div>',
  32. parentContent: '<p>{{ msg }}</p>'
  33. })
  34. expect(child.$el.tagName).toBe('DIV')
  35. expect(child.$el.children[0].tagName).toBe('P')
  36. expect(child.$el.children[0].textContent).toBe('parent message')
  37. vm.msg = 'changed'
  38. waitForUpdate(() => {
  39. expect(child.$el.children[0].textContent).toBe('changed')
  40. }).then(done)
  41. })
  42. it('named slot', done => {
  43. mount({
  44. childTemplate: '<div><slot name="test"></slot></div>',
  45. parentContent: '<p slot="test">{{ msg }}</p>'
  46. })
  47. expect(child.$el.tagName).toBe('DIV')
  48. expect(child.$el.children[0].tagName).toBe('P')
  49. expect(child.$el.children[0].textContent).toBe('parent message')
  50. vm.msg = 'changed'
  51. waitForUpdate(() => {
  52. expect(child.$el.children[0].textContent).toBe('changed')
  53. }).then(done)
  54. })
  55. it('named slot with 0 as a number', done => {
  56. mount({
  57. childTemplate: '<div><slot :name="0"></slot></div>',
  58. parentContent: '<p :slot="0">{{ msg }}</p>'
  59. })
  60. expect(child.$el.tagName).toBe('DIV')
  61. expect(child.$el.children[0].tagName).toBe('P')
  62. expect(child.$el.children[0].textContent).toBe('parent message')
  63. vm.msg = 'changed'
  64. waitForUpdate(() => {
  65. expect(child.$el.children[0].textContent).toBe('changed')
  66. }).then(done)
  67. })
  68. it('fallback content', () => {
  69. mount({
  70. childTemplate: '<div><slot><p>{{msg}}</p></slot></div>'
  71. })
  72. expect(child.$el.children[0].tagName).toBe('P')
  73. expect(child.$el.textContent).toBe('child message')
  74. })
  75. it('fallback content with multiple named slots', () => {
  76. mount({
  77. childTemplate: `
  78. <div>
  79. <slot name="a"><p>fallback a</p></slot>
  80. <slot name="b">fallback b</slot>
  81. </div>
  82. `,
  83. parentContent: '<p slot="b">slot b</p>'
  84. })
  85. expect(child.$el.children.length).toBe(2)
  86. expect(child.$el.children[0].textContent).toBe('fallback a')
  87. expect(child.$el.children[1].textContent).toBe('slot b')
  88. })
  89. it('fallback content with mixed named/unnamed slots', () => {
  90. mount({
  91. childTemplate: `
  92. <div>
  93. <slot><p>fallback a</p></slot>
  94. <slot name="b">fallback b</slot>
  95. </div>
  96. `,
  97. parentContent: '<p slot="b">slot b</p>'
  98. })
  99. expect(child.$el.children.length).toBe(2)
  100. expect(child.$el.children[0].textContent).toBe('fallback a')
  101. expect(child.$el.children[1].textContent).toBe('slot b')
  102. })
  103. it('it should work with previous versions of the templates', () => {
  104. const Test = {
  105. render() {
  106. const _vm = this
  107. // const _h = _vm.$createElement;
  108. const _c = _vm._self._c || vm._h
  109. return _c(
  110. 'div',
  111. [_vm._t('default', [_c('p', [_vm._v('slot default')])])],
  112. 2
  113. )
  114. }
  115. }
  116. let vm = new Vue({
  117. template: `<test/>`,
  118. components: { Test }
  119. }).$mount()
  120. expect(vm.$el.textContent).toBe('slot default')
  121. vm = new Vue({
  122. template: `<test>custom content</test>`,
  123. components: { Test }
  124. }).$mount()
  125. expect(vm.$el.textContent).toBe('custom content')
  126. })
  127. it('fallback content should not be evaluated when the parent is providing it', () => {
  128. const test = vi.fn()
  129. const vm = new Vue({
  130. template: '<test>slot default</test>',
  131. components: {
  132. test: {
  133. template: '<div><slot>{{test()}}</slot></div>',
  134. methods: {
  135. test() {
  136. test()
  137. return 'test'
  138. }
  139. }
  140. }
  141. }
  142. }).$mount()
  143. expect(vm.$el.textContent).toBe('slot default')
  144. expect(test).not.toHaveBeenCalled()
  145. })
  146. it('selector matching multiple elements', () => {
  147. mount({
  148. childTemplate: '<div><slot name="t"></slot></div>',
  149. parentContent: '<p slot="t">1</p><div></div><p slot="t">2</p>'
  150. })
  151. expect(child.$el.innerHTML).toBe('<p>1</p><p>2</p>')
  152. })
  153. it('default content should only render parts not selected', () => {
  154. mount({
  155. childTemplate: `
  156. <div>
  157. <slot name="a"></slot>
  158. <slot></slot>
  159. <slot name="b"></slot>
  160. </div>
  161. `,
  162. parentContent: '<div>foo</div><p slot="a">1</p><p slot="b">2</p>'
  163. })
  164. expect(child.$el.innerHTML).toBe('<p>1</p> <div>foo</div> <p>2</p>')
  165. })
  166. it('name should only match children', function () {
  167. mount({
  168. childTemplate: `
  169. <div>
  170. <slot name="a"><p>fallback a</p></slot>
  171. <slot name="b"><p>fallback b</p></slot>
  172. <slot name="c"><p>fallback c</p></slot>
  173. </div>
  174. `,
  175. parentContent: `
  176. '<p slot="b">select b</p>
  177. '<span><p slot="b">nested b</p></span>
  178. '<span><p slot="c">nested c</p></span>
  179. `
  180. })
  181. expect(child.$el.children.length).toBe(3)
  182. expect(child.$el.children[0].textContent).toBe('fallback a')
  183. expect(child.$el.children[1].textContent).toBe('select b')
  184. expect(child.$el.children[2].textContent).toBe('fallback c')
  185. })
  186. it('should accept expressions in slot attribute and slot names', () => {
  187. mount({
  188. childTemplate: `<div><slot :name="'a'"></slot></div>`,
  189. parentContent: `<p>one</p><p :slot="'a'">two</p>`
  190. })
  191. expect(child.$el.innerHTML).toBe('<p>two</p>')
  192. })
  193. it('slot inside v-if', done => {
  194. const vm = new Vue({
  195. data: {
  196. a: 1,
  197. b: 2,
  198. show: true
  199. },
  200. template: '<test :show="show"><p slot="b">{{b}}</p><p>{{a}}</p></test>',
  201. components: {
  202. test: {
  203. props: ['show'],
  204. template: '<div v-if="show"><slot></slot><slot name="b"></slot></div>'
  205. }
  206. }
  207. }).$mount()
  208. expect(vm.$el.textContent).toBe('12')
  209. vm.a = 2
  210. waitForUpdate(() => {
  211. expect(vm.$el.textContent).toBe('22')
  212. vm.show = false
  213. })
  214. .then(() => {
  215. expect(vm.$el.textContent).toBe('')
  216. vm.show = true
  217. vm.a = 3
  218. })
  219. .then(() => {
  220. expect(vm.$el.textContent).toBe('32')
  221. })
  222. .then(done)
  223. })
  224. it('slot inside v-for', () => {
  225. mount({
  226. childTemplate: '<div><slot v-for="i in 3" :name="i"></slot></div>',
  227. parentContent: '<p v-for="i in 3" :slot="i">{{ i - 1 }}</p>'
  228. })
  229. expect(child.$el.innerHTML).toBe('<p>0</p><p>1</p><p>2</p>')
  230. })
  231. it('nested slots', done => {
  232. const vm = new Vue({
  233. template: '<test><test2><p>{{ msg }}</p></test2></test>',
  234. data: {
  235. msg: 'foo'
  236. },
  237. components: {
  238. test: {
  239. template: '<div><slot></slot></div>'
  240. },
  241. test2: {
  242. template: '<div><slot></slot></div>'
  243. }
  244. }
  245. }).$mount()
  246. expect(vm.$el.innerHTML).toBe('<div><p>foo</p></div>')
  247. vm.msg = 'bar'
  248. waitForUpdate(() => {
  249. expect(vm.$el.innerHTML).toBe('<div><p>bar</p></div>')
  250. }).then(done)
  251. })
  252. it('v-if on inserted content', done => {
  253. const vm = new Vue({
  254. template: '<test><p v-if="ok">{{ msg }}</p></test>',
  255. data: {
  256. ok: true,
  257. msg: 'hi'
  258. },
  259. components: {
  260. test: {
  261. template: '<div><slot>fallback</slot></div>'
  262. }
  263. }
  264. }).$mount()
  265. expect(vm.$el.innerHTML).toBe('<p>hi</p>')
  266. vm.ok = false
  267. waitForUpdate(() => {
  268. expect(vm.$el.innerHTML).toBe('fallback')
  269. vm.ok = true
  270. vm.msg = 'bye'
  271. })
  272. .then(() => {
  273. expect(vm.$el.innerHTML).toBe('<p>bye</p>')
  274. })
  275. .then(done)
  276. })
  277. it('template slot', function () {
  278. const vm = new Vue({
  279. template: '<test><template slot="test">hello</template></test>',
  280. components: {
  281. test: {
  282. template: '<div><slot name="test"></slot> world</div>'
  283. }
  284. }
  285. }).$mount()
  286. expect(vm.$el.innerHTML).toBe('hello world')
  287. })
  288. it('combined with v-for', () => {
  289. const vm = new Vue({
  290. template: '<div><test v-for="i in 3" :key="i">{{ i }}</test></div>',
  291. components: {
  292. test: {
  293. template: '<div><slot></slot></div>'
  294. }
  295. }
  296. }).$mount()
  297. expect(vm.$el.innerHTML).toBe('<div>1</div><div>2</div><div>3</div>')
  298. })
  299. it('inside template v-if', () => {
  300. mount({
  301. childTemplate: `
  302. <div>
  303. <template v-if="true"><slot></slot></template>
  304. </div>
  305. `,
  306. parentContent: 'foo'
  307. })
  308. expect(child.$el.innerHTML).toBe('foo')
  309. })
  310. it('default slot should use fallback content if has only whitespace', () => {
  311. mount({
  312. childTemplate: `
  313. <div>
  314. <slot name="first"><p>first slot</p></slot>
  315. <slot><p>this is the default slot</p></slot>
  316. <slot name="second"><p>second named slot</p></slot>
  317. </div>
  318. `,
  319. parentContent: `<div slot="first">1</div> <div slot="second">2</div> <div slot="second">2+</div>`
  320. })
  321. expect(child.$el.innerHTML).toBe(
  322. '<div>1</div> <p>this is the default slot</p> <div>2</div><div>2+</div>'
  323. )
  324. })
  325. it('programmatic access to $slots', () => {
  326. const vm = new Vue({
  327. template: '<test><p slot="a">A</p><div>C</div><p slot="b">B</p></test>',
  328. components: {
  329. test: {
  330. render() {
  331. expect(this.$slots.a.length).toBe(1)
  332. expect(this.$slots.a[0].tag).toBe('p')
  333. expect(this.$slots.a[0].children.length).toBe(1)
  334. expect(this.$slots.a[0].children[0].text).toBe('A')
  335. expect(this.$slots.b.length).toBe(1)
  336. expect(this.$slots.b[0].tag).toBe('p')
  337. expect(this.$slots.b[0].children.length).toBe(1)
  338. expect(this.$slots.b[0].children[0].text).toBe('B')
  339. expect(this.$slots.default.length).toBe(1)
  340. expect(this.$slots.default[0].tag).toBe('div')
  341. expect(this.$slots.default[0].children.length).toBe(1)
  342. expect(this.$slots.default[0].children[0].text).toBe('C')
  343. return this.$slots.default[0]
  344. }
  345. }
  346. }
  347. }).$mount()
  348. expect(vm.$el.tagName).toBe('DIV')
  349. expect(vm.$el.textContent).toBe('C')
  350. })
  351. it('warn if user directly returns array', () => {
  352. new Vue({
  353. template: '<test><div slot="foo"></div><div slot="foo"></div></test>',
  354. components: {
  355. test: {
  356. render() {
  357. return this.$slots.foo
  358. }
  359. }
  360. }
  361. }).$mount()
  362. expect(
  363. 'Render function should return a single root node'
  364. ).toHaveBeenWarned()
  365. })
  366. // #3254
  367. it('should not keep slot name when passed further down', () => {
  368. const vm = new Vue({
  369. template: '<test><span slot="foo">foo</span></test>',
  370. components: {
  371. test: {
  372. template: '<child><slot name="foo"></slot></child>',
  373. components: {
  374. child: {
  375. template: `
  376. <div>
  377. <div class="default"><slot></slot></div>
  378. <div class="named"><slot name="foo"></slot></div>
  379. </div>
  380. `
  381. }
  382. }
  383. }
  384. }
  385. }).$mount()
  386. expect(vm.$el.querySelector('.default').textContent).toBe('foo')
  387. expect(vm.$el.querySelector('.named').textContent).toBe('')
  388. })
  389. it('should not keep slot name when passed further down (nested)', () => {
  390. const vm = new Vue({
  391. template: '<wrap><test><span slot="foo">foo</span></test></wrap>',
  392. components: {
  393. wrap: {
  394. template: '<div><slot></slot></div>'
  395. },
  396. test: {
  397. template: '<child><slot name="foo"></slot></child>',
  398. components: {
  399. child: {
  400. template: `
  401. <div>
  402. <div class="default"><slot></slot></div>
  403. <div class="named"><slot name="foo"></slot></div>
  404. </div>
  405. `
  406. }
  407. }
  408. }
  409. }
  410. }).$mount()
  411. expect(vm.$el.querySelector('.default').textContent).toBe('foo')
  412. expect(vm.$el.querySelector('.named').textContent).toBe('')
  413. })
  414. it('should not keep slot name when passed further down (functional)', () => {
  415. const child = {
  416. template: `
  417. <div>
  418. <div class="default"><slot></slot></div>
  419. <div class="named"><slot name="foo"></slot></div>
  420. </div>
  421. `
  422. }
  423. const vm = new Vue({
  424. template: '<test><span slot="foo">foo</span></test>',
  425. components: {
  426. test: {
  427. functional: true,
  428. render(h, ctx) {
  429. const slots = ctx.slots()
  430. return h(child, slots.foo)
  431. }
  432. }
  433. }
  434. }).$mount()
  435. expect(vm.$el.querySelector('.default').textContent).toBe('foo')
  436. expect(vm.$el.querySelector('.named').textContent).toBe('')
  437. })
  438. // #3400
  439. it('named slots should be consistent across re-renders', done => {
  440. const vm = new Vue({
  441. template: `
  442. <comp>
  443. <div slot="foo">foo</div>
  444. </comp>
  445. `,
  446. components: {
  447. comp: {
  448. data() {
  449. return { a: 1 }
  450. },
  451. template: `<div><slot name="foo"></slot>{{ a }}</div>`
  452. }
  453. }
  454. }).$mount()
  455. expect(vm.$el.textContent).toBe('foo1')
  456. vm.$children[0].a = 2
  457. waitForUpdate(() => {
  458. expect(vm.$el.textContent).toBe('foo2')
  459. }).then(done)
  460. })
  461. // #3437
  462. it('should correctly re-create components in slot', done => {
  463. const calls: any[] = []
  464. const vm = new Vue({
  465. template: `
  466. <comp ref="child">
  467. <div slot="foo">
  468. <child></child>
  469. </div>
  470. </comp>
  471. `,
  472. components: {
  473. comp: {
  474. data() {
  475. return { ok: true }
  476. },
  477. template: `<div><slot name="foo" v-if="ok"></slot></div>`
  478. },
  479. child: {
  480. template: '<div>child</div>',
  481. created() {
  482. calls.push(1)
  483. },
  484. destroyed() {
  485. calls.push(2)
  486. }
  487. }
  488. }
  489. }).$mount()
  490. expect(calls).toEqual([1])
  491. vm.$refs.child.ok = false
  492. waitForUpdate(() => {
  493. expect(calls).toEqual([1, 2])
  494. vm.$refs.child.ok = true
  495. })
  496. .then(() => {
  497. expect(calls).toEqual([1, 2, 1])
  498. vm.$refs.child.ok = false
  499. })
  500. .then(() => {
  501. expect(calls).toEqual([1, 2, 1, 2])
  502. })
  503. .then(done)
  504. })
  505. it('should support duplicate slots', done => {
  506. const vm = new Vue({
  507. template: `
  508. <foo ref="foo">
  509. <div slot="a">{{ n }}</div>
  510. </foo>
  511. `,
  512. data: {
  513. n: 1
  514. },
  515. components: {
  516. foo: {
  517. data() {
  518. return { ok: true }
  519. },
  520. template: `
  521. <div>
  522. <slot name="a" />
  523. <slot v-if="ok" name="a" />
  524. <pre><slot name="a" /></pre>
  525. </div>
  526. `
  527. }
  528. }
  529. }).$mount()
  530. expect(vm.$el.innerHTML).toBe(
  531. `<div>1</div> <div>1</div> <pre><div>1</div></pre>`
  532. )
  533. vm.n++
  534. waitForUpdate(() => {
  535. expect(vm.$el.innerHTML).toBe(
  536. `<div>2</div> <div>2</div> <pre><div>2</div></pre>`
  537. )
  538. vm.n++
  539. })
  540. .then(() => {
  541. expect(vm.$el.innerHTML).toBe(
  542. `<div>3</div> <div>3</div> <pre><div>3</div></pre>`
  543. )
  544. vm.$refs.foo.ok = false
  545. })
  546. .then(() => {
  547. expect(vm.$el.innerHTML).toBe(
  548. `<div>3</div> <!----> <pre><div>3</div></pre>`
  549. )
  550. vm.n++
  551. vm.$refs.foo.ok = true
  552. })
  553. .then(() => {
  554. expect(vm.$el.innerHTML).toBe(
  555. `<div>4</div> <div>4</div> <pre><div>4</div></pre>`
  556. )
  557. })
  558. .then(done)
  559. })
  560. // #3518
  561. it('events should not break when slot is toggled by v-if', done => {
  562. const spy = vi.fn()
  563. const vm = new Vue({
  564. template: `<test><div class="click" @click="test">hi</div></test>`,
  565. methods: {
  566. test: spy
  567. },
  568. components: {
  569. test: {
  570. data: () => ({
  571. toggle: true
  572. }),
  573. template: `<div v-if="toggle"><slot></slot></div>`
  574. }
  575. }
  576. }).$mount()
  577. document.body.appendChild(vm.$el)
  578. expect(vm.$el.textContent).toBe('hi')
  579. vm.$children[0].toggle = false
  580. waitForUpdate(() => {
  581. vm.$children[0].toggle = true
  582. })
  583. .then(() => {
  584. global.triggerEvent(vm.$el.querySelector('.click'), 'click')
  585. expect(spy).toHaveBeenCalled()
  586. })
  587. .then(() => {
  588. document.body.removeChild(vm.$el)
  589. })
  590. .then(done)
  591. })
  592. it('renders static tree with text', () => {
  593. const vm = new Vue({
  594. template: `<div><test><template><div></div>Hello<div></div></template></test></div>`,
  595. components: {
  596. test: {
  597. template: '<div><slot></slot></div>'
  598. }
  599. }
  600. })
  601. vm.$mount()
  602. expect('Error when rendering root').not.toHaveBeenWarned()
  603. })
  604. // #3872
  605. it('functional component as slot', () => {
  606. const vm = new Vue({
  607. template: `
  608. <parent>
  609. <child>one</child>
  610. <child slot="a">two</child>
  611. </parent>
  612. `,
  613. components: {
  614. parent: {
  615. template: `<div><slot name="a"></slot><slot></slot></div>`
  616. },
  617. child: {
  618. functional: true,
  619. render(h, { slots }) {
  620. return h('div', slots().default)
  621. }
  622. }
  623. }
  624. }).$mount()
  625. expect(vm.$el.innerHTML.trim()).toBe('<div>two</div><div>one</div>')
  626. })
  627. // #4209
  628. it('slot of multiple text nodes should not be infinitely merged', done => {
  629. const wrap = {
  630. template: `<inner ref="inner">foo<slot></slot></inner>`,
  631. components: {
  632. inner: {
  633. data: () => ({ a: 1 }),
  634. template: `<div>{{a}}<slot></slot></div>`
  635. }
  636. }
  637. }
  638. const vm = new Vue({
  639. template: `<wrap ref="wrap">bar</wrap>`,
  640. components: { wrap }
  641. }).$mount()
  642. expect(vm.$el.textContent).toBe('1foobar')
  643. vm.$refs.wrap.$refs.inner.a++
  644. waitForUpdate(() => {
  645. expect(vm.$el.textContent).toBe('2foobar')
  646. }).then(done)
  647. })
  648. // #4315
  649. it('functional component passing slot content to stateful child component', done => {
  650. const ComponentWithSlots = {
  651. render(h) {
  652. return h('div', this.$slots.slot1)
  653. }
  654. }
  655. const FunctionalComp = {
  656. functional: true,
  657. render(h) {
  658. return h(ComponentWithSlots, [h('span', { slot: 'slot1' }, 'foo')])
  659. }
  660. }
  661. const vm = new Vue({
  662. data: { n: 1 },
  663. render(h) {
  664. return h('div', [this.n, h(FunctionalComp)])
  665. }
  666. }).$mount()
  667. expect(vm.$el.textContent).toBe('1foo')
  668. vm.n++
  669. waitForUpdate(() => {
  670. // should not lose named slot
  671. expect(vm.$el.textContent).toBe('2foo')
  672. }).then(done)
  673. })
  674. it('the elements of slot should be updated correctly', done => {
  675. const vm = new Vue({
  676. data: { n: 1 },
  677. template:
  678. '<div><test><span v-for="i in n" :key="i">{{ i }}</span><input value="a"/></test></div>',
  679. components: {
  680. test: {
  681. template: '<div><slot></slot></div>'
  682. }
  683. }
  684. }).$mount()
  685. expect(vm.$el.innerHTML).toBe('<div><span>1</span><input value="a"></div>')
  686. const input = vm.$el.querySelector('input')
  687. input.value = 'b'
  688. vm.n++
  689. waitForUpdate(() => {
  690. expect(vm.$el.innerHTML).toBe(
  691. '<div><span>1</span><span>2</span><input value="a"></div>'
  692. )
  693. expect(vm.$el.querySelector('input')).toBe(input)
  694. expect(vm.$el.querySelector('input').value).toBe('b')
  695. }).then(done)
  696. })
  697. // GitHub issue #5888
  698. it('should resolve correctly slot with keep-alive', () => {
  699. const vm = new Vue({
  700. template: `
  701. <div>
  702. <container>
  703. <keep-alive slot="foo">
  704. <child></child>
  705. </keep-alive>
  706. </container>
  707. </div>
  708. `,
  709. components: {
  710. container: {
  711. template:
  712. '<div><slot>default</slot><slot name="foo">named</slot></div>'
  713. },
  714. child: {
  715. template: '<span>foo</span>'
  716. }
  717. }
  718. }).$mount()
  719. expect(vm.$el.innerHTML).toBe('<div>default<span>foo</span></div>')
  720. })
  721. // #6372, #6915
  722. it('should handle nested components in slots properly', done => {
  723. const TestComponent = {
  724. template: `
  725. <component :is="toggleEl ? 'b' : 'i'">
  726. <slot />
  727. </component>
  728. `,
  729. data() {
  730. return {
  731. toggleEl: true
  732. }
  733. }
  734. }
  735. const vm = new Vue({
  736. template: `
  737. <div>
  738. <test-component ref="test">
  739. <div>
  740. <foo/>
  741. </div>
  742. <bar>
  743. <foo/>
  744. </bar>
  745. </test-component>
  746. </div>
  747. `,
  748. components: {
  749. TestComponent,
  750. foo: {
  751. template: `<div>foo</div>`
  752. },
  753. bar: {
  754. template: `<div>bar<slot/></div>`
  755. }
  756. }
  757. }).$mount()
  758. expect(vm.$el.innerHTML).toBe(
  759. `<b><div><div>foo</div></div> <div>bar<div>foo</div></div></b>`
  760. )
  761. vm.$refs.test.toggleEl = false
  762. waitForUpdate(() => {
  763. expect(vm.$el.innerHTML).toBe(
  764. `<i><div><div>foo</div></div> <div>bar<div>foo</div></div></i>`
  765. )
  766. }).then(done)
  767. })
  768. it('should preserve slot attribute if not absorbed by a Vue component', () => {
  769. const vm = new Vue({
  770. template: `
  771. <div>
  772. <div slot="foo"></div>
  773. </div>
  774. `
  775. }).$mount()
  776. expect(vm.$el.children[0].getAttribute('slot')).toBe('foo')
  777. })
  778. it('passing a slot down as named slot', () => {
  779. const Bar = {
  780. template: `<div class="bar"><slot name="foo"/></div>`
  781. }
  782. const Foo = {
  783. components: { Bar },
  784. template: `<div class="foo"><bar><slot slot="foo"/></bar></div>`
  785. }
  786. const vm = new Vue({
  787. components: { Foo },
  788. template: `<div><foo>hello</foo></div>`
  789. }).$mount()
  790. expect(vm.$el.innerHTML).toBe(
  791. '<div class="foo"><div class="bar">hello</div></div>'
  792. )
  793. })
  794. it('fallback content for named template slot', () => {
  795. const Bar = {
  796. template: `<div class="bar"><slot name="foo">fallback</slot></div>`
  797. }
  798. const Foo = {
  799. components: { Bar },
  800. template: `<div class="foo"><bar><template slot="foo"/><slot/></template></bar></div>`
  801. }
  802. const vm = new Vue({
  803. components: { Foo },
  804. template: `<div><foo></foo></div>`
  805. }).$mount()
  806. expect(vm.$el.innerHTML).toBe(
  807. '<div class="foo"><div class="bar">fallback</div></div>'
  808. )
  809. })
  810. // #7106
  811. it('should not lose functional slot across renders', done => {
  812. const One = {
  813. data: () => ({
  814. foo: true
  815. }),
  816. render(h) {
  817. this.foo
  818. return h('div', this.$slots.slot)
  819. }
  820. }
  821. const Two = {
  822. render(h) {
  823. return h('span', this.$slots.slot)
  824. }
  825. }
  826. const Three = {
  827. functional: true,
  828. render: (h, { children }) => h('span', children)
  829. }
  830. const vm = new Vue({
  831. template: `
  832. <div>
  833. <one ref="one">
  834. <two slot="slot">
  835. <three slot="slot">hello</three>
  836. </two>
  837. </one>
  838. </div>
  839. `,
  840. components: { One, Two, Three }
  841. }).$mount()
  842. expect(vm.$el.textContent).toBe('hello')
  843. // trigger re-render of <one>
  844. vm.$refs.one.foo = false
  845. waitForUpdate(() => {
  846. // should still be there
  847. expect(vm.$el.textContent).toBe('hello')
  848. }).then(done)
  849. })
  850. it('should allow passing named slots as raw children down multiple layers of functional component', () => {
  851. const CompB = {
  852. functional: true,
  853. render(h, { slots }) {
  854. return slots().foo
  855. }
  856. }
  857. const CompA = {
  858. functional: true,
  859. render(h, { children }) {
  860. return h(CompB, children)
  861. }
  862. }
  863. const vm = new Vue({
  864. components: {
  865. CompA
  866. },
  867. template: `
  868. <div>
  869. <comp-a>
  870. <span slot="foo">foo</span>
  871. </comp-a>
  872. </div>
  873. `
  874. }).$mount()
  875. expect(vm.$el.textContent).toBe('foo')
  876. })
  877. // #7817
  878. it('should not match wrong named slot in functional component on re-render', done => {
  879. const Functional = {
  880. functional: true,
  881. render: (h, ctx) => ctx.slots().default
  882. }
  883. const Stateful = {
  884. data() {
  885. return { ok: true }
  886. },
  887. render(h) {
  888. this.ok // register dep
  889. return h('div', [h(Functional, this.$slots.named)])
  890. }
  891. }
  892. const vm = new Vue({
  893. template: `<stateful ref="stateful"><div slot="named">foo</div></stateful>`,
  894. components: { Stateful }
  895. }).$mount()
  896. expect(vm.$el.textContent).toBe('foo')
  897. vm.$refs.stateful.ok = false
  898. waitForUpdate(() => {
  899. expect(vm.$el.textContent).toBe('foo')
  900. }).then(done)
  901. })
  902. // #7975
  903. it('should update named slot correctly when its position in the tree changed', done => {
  904. const ChildComponent = {
  905. template: '<b>{{ message }}</b>',
  906. props: ['message']
  907. }
  908. let parentVm
  909. const ParentComponent = {
  910. template: `
  911. <div>
  912. <span v-if="alter">
  913. <span><slot name="foo" /></span>
  914. </span>
  915. <span v-else>
  916. <slot name="foo" />
  917. </span>
  918. </div>
  919. `,
  920. data() {
  921. return {
  922. alter: true
  923. }
  924. },
  925. mounted() {
  926. parentVm = this
  927. }
  928. }
  929. const vm = new Vue({
  930. template: `
  931. <parent-component>
  932. <span slot="foo">
  933. <child-component :message="message" />
  934. </span>
  935. </parent-component>
  936. `,
  937. components: {
  938. ChildComponent,
  939. ParentComponent
  940. },
  941. data() {
  942. return {
  943. message: 1
  944. }
  945. }
  946. }).$mount()
  947. expect(vm.$el.firstChild.innerHTML).toBe(
  948. '<span><span><b>1</b></span></span>'
  949. )
  950. parentVm.alter = false
  951. waitForUpdate(() => {
  952. vm.message = 2
  953. })
  954. .then(() => {
  955. expect(vm.$el.firstChild.innerHTML).toBe('<span><b>2</b></span>')
  956. })
  957. .then(done)
  958. })
  959. // #12102
  960. it('v-if inside scoped slot', () => {
  961. const vm = new Vue({
  962. template: `<test><template #custom><span v-if="false">a</span><span>b</span></template></test>`,
  963. components: {
  964. test: {
  965. template: `<div><slot name="custom"/></div>`
  966. }
  967. }
  968. }).$mount()
  969. expect(vm.$el.innerHTML).toBe(`<!----><span>b</span>`)
  970. })
  971. // regression 2.7.0-alpha.4
  972. it('passing scoped slots through nested parent chain', () => {
  973. const Foo = {
  974. template: `
  975. <div><slot>foo default</slot></div>
  976. `
  977. }
  978. const Bar = {
  979. components: { Foo },
  980. template: `<Foo><slot name="bar"/></Foo>`
  981. }
  982. const App = {
  983. components: { Bar },
  984. template: `<Bar>
  985. <template #bar>
  986. <span>App content for Bar#bar</span>
  987. </template>
  988. </Bar>`
  989. }
  990. const vm = new Vue({
  991. render: h => h(App)
  992. }).$mount()
  993. expect(vm.$el.innerHTML).toMatch(`App content for Bar#bar`)
  994. })
  995. })