component-slot.spec.js 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948
  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('selector matching multiple elements', () => {
  104. mount({
  105. childTemplate: '<div><slot name="t"></slot></div>',
  106. parentContent: '<p slot="t">1</p><div></div><p slot="t">2</p>'
  107. })
  108. expect(child.$el.innerHTML).toBe('<p>1</p><p>2</p>')
  109. })
  110. it('default content should only render parts not selected', () => {
  111. mount({
  112. childTemplate: `
  113. <div>
  114. <slot name="a"></slot>
  115. <slot></slot>
  116. <slot name="b"></slot>
  117. </div>
  118. `,
  119. parentContent: '<div>foo</div><p slot="a">1</p><p slot="b">2</p>'
  120. })
  121. expect(child.$el.innerHTML).toBe('<p>1</p> <div>foo</div> <p>2</p>')
  122. })
  123. it('name should only match children', function () {
  124. mount({
  125. childTemplate: `
  126. <div>
  127. <slot name="a"><p>fallback a</p></slot>
  128. <slot name="b"><p>fallback b</p></slot>
  129. <slot name="c"><p>fallback c</p></slot>
  130. </div>
  131. `,
  132. parentContent: `
  133. '<p slot="b">select b</p>
  134. '<span><p slot="b">nested b</p></span>
  135. '<span><p slot="c">nested c</p></span>
  136. `
  137. })
  138. expect(child.$el.children.length).toBe(3)
  139. expect(child.$el.children[0].textContent).toBe('fallback a')
  140. expect(child.$el.children[1].textContent).toBe('select b')
  141. expect(child.$el.children[2].textContent).toBe('fallback c')
  142. })
  143. it('should accept expressions in slot attribute and slot names', () => {
  144. mount({
  145. childTemplate: `<div><slot :name="'a'"></slot></div>`,
  146. parentContent: `<p>one</p><p :slot="'a'">two</p>`
  147. })
  148. expect(child.$el.innerHTML).toBe('<p>two</p>')
  149. })
  150. it('slot inside v-if', done => {
  151. const vm = new Vue({
  152. data: {
  153. a: 1,
  154. b: 2,
  155. show: true
  156. },
  157. template: '<test :show="show"><p slot="b">{{b}}</p><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. }).$mount()
  165. expect(vm.$el.textContent).toBe('12')
  166. vm.a = 2
  167. waitForUpdate(() => {
  168. expect(vm.$el.textContent).toBe('22')
  169. vm.show = false
  170. }).then(() => {
  171. expect(vm.$el.textContent).toBe('')
  172. vm.show = true
  173. vm.a = 3
  174. }).then(() => {
  175. expect(vm.$el.textContent).toBe('32')
  176. }).then(done)
  177. })
  178. it('slot inside v-for', () => {
  179. mount({
  180. childTemplate: '<div><slot v-for="i in 3" :name="i"></slot></div>',
  181. parentContent: '<p v-for="i in 3" :slot="i">{{ i - 1 }}</p>'
  182. })
  183. expect(child.$el.innerHTML).toBe('<p>0</p><p>1</p><p>2</p>')
  184. })
  185. it('nested slots', done => {
  186. const vm = new Vue({
  187. template: '<test><test2><p>{{ msg }}</p></test2></test>',
  188. data: {
  189. msg: 'foo'
  190. },
  191. components: {
  192. test: {
  193. template: '<div><slot></slot></div>'
  194. },
  195. test2: {
  196. template: '<div><slot></slot></div>'
  197. }
  198. }
  199. }).$mount()
  200. expect(vm.$el.innerHTML).toBe('<div><p>foo</p></div>')
  201. vm.msg = 'bar'
  202. waitForUpdate(() => {
  203. expect(vm.$el.innerHTML).toBe('<div><p>bar</p></div>')
  204. }).then(done)
  205. })
  206. it('v-if on inserted content', done => {
  207. const vm = new Vue({
  208. template: '<test><p v-if="ok">{{ msg }}</p></test>',
  209. data: {
  210. ok: true,
  211. msg: 'hi'
  212. },
  213. components: {
  214. test: {
  215. template: '<div><slot>fallback</slot></div>'
  216. }
  217. }
  218. }).$mount()
  219. expect(vm.$el.innerHTML).toBe('<p>hi</p>')
  220. vm.ok = false
  221. waitForUpdate(() => {
  222. expect(vm.$el.innerHTML).toBe('fallback')
  223. vm.ok = true
  224. vm.msg = 'bye'
  225. }).then(() => {
  226. expect(vm.$el.innerHTML).toBe('<p>bye</p>')
  227. }).then(done)
  228. })
  229. it('template slot', function () {
  230. const vm = new Vue({
  231. template: '<test><template slot="test">hello</template></test>',
  232. components: {
  233. test: {
  234. template: '<div><slot name="test"></slot> world</div>'
  235. }
  236. }
  237. }).$mount()
  238. expect(vm.$el.innerHTML).toBe('hello world')
  239. })
  240. it('combined with v-for', () => {
  241. const vm = new Vue({
  242. template: '<div><test v-for="i in 3" :key="i">{{ i }}</test></div>',
  243. components: {
  244. test: {
  245. template: '<div><slot></slot></div>'
  246. }
  247. }
  248. }).$mount()
  249. expect(vm.$el.innerHTML).toBe('<div>1</div><div>2</div><div>3</div>')
  250. })
  251. it('inside template v-if', () => {
  252. mount({
  253. childTemplate: `
  254. <div>
  255. <template v-if="true"><slot></slot></template>
  256. </div>
  257. `,
  258. parentContent: 'foo'
  259. })
  260. expect(child.$el.innerHTML).toBe('foo')
  261. })
  262. it('default slot should use fallback content if has only whitespace', () => {
  263. mount({
  264. childTemplate: `
  265. <div>
  266. <slot name="first"><p>first slot</p></slot>
  267. <slot><p>this is the default slot</p></slot>
  268. <slot name="second"><p>second named slot</p></slot>
  269. </div>
  270. `,
  271. parentContent: `<div slot="first">1</div> <div slot="second">2</div> <div slot="second">2+</div>`
  272. })
  273. expect(child.$el.innerHTML).toBe(
  274. '<div>1</div> <p>this is the default slot</p> <div>2</div><div>2+</div>'
  275. )
  276. })
  277. it('programmatic access to $slots', () => {
  278. const vm = new Vue({
  279. template: '<test><p slot="a">A</p><div>C</div><p slot="b">B</p></test>',
  280. components: {
  281. test: {
  282. render () {
  283. expect(this.$slots.a.length).toBe(1)
  284. expect(this.$slots.a[0].tag).toBe('p')
  285. expect(this.$slots.a[0].children.length).toBe(1)
  286. expect(this.$slots.a[0].children[0].text).toBe('A')
  287. expect(this.$slots.b.length).toBe(1)
  288. expect(this.$slots.b[0].tag).toBe('p')
  289. expect(this.$slots.b[0].children.length).toBe(1)
  290. expect(this.$slots.b[0].children[0].text).toBe('B')
  291. expect(this.$slots.default.length).toBe(1)
  292. expect(this.$slots.default[0].tag).toBe('div')
  293. expect(this.$slots.default[0].children.length).toBe(1)
  294. expect(this.$slots.default[0].children[0].text).toBe('C')
  295. return this.$slots.default[0]
  296. }
  297. }
  298. }
  299. }).$mount()
  300. expect(vm.$el.tagName).toBe('DIV')
  301. expect(vm.$el.textContent).toBe('C')
  302. })
  303. it('warn if user directly returns array', () => {
  304. new Vue({
  305. template: '<test><div slot="foo"></div><div slot="foo"></div></test>',
  306. components: {
  307. test: {
  308. render () {
  309. return this.$slots.foo
  310. }
  311. }
  312. }
  313. }).$mount()
  314. expect('Render function should return a single root node').toHaveBeenWarned()
  315. })
  316. // #3254
  317. it('should not keep slot name when passed further down', () => {
  318. const vm = new Vue({
  319. template: '<test><span slot="foo">foo</span></test>',
  320. components: {
  321. test: {
  322. template: '<child><slot name="foo"></slot></child>',
  323. components: {
  324. child: {
  325. template: `
  326. <div>
  327. <div class="default"><slot></slot></div>
  328. <div class="named"><slot name="foo"></slot></div>
  329. </div>
  330. `
  331. }
  332. }
  333. }
  334. }
  335. }).$mount()
  336. expect(vm.$el.querySelector('.default').textContent).toBe('foo')
  337. expect(vm.$el.querySelector('.named').textContent).toBe('')
  338. })
  339. it('should not keep slot name when passed further down (nested)', () => {
  340. const vm = new Vue({
  341. template: '<wrap><test><span slot="foo">foo</span></test></wrap>',
  342. components: {
  343. wrap: {
  344. template: '<div><slot></slot></div>'
  345. },
  346. test: {
  347. template: '<child><slot name="foo"></slot></child>',
  348. components: {
  349. child: {
  350. template: `
  351. <div>
  352. <div class="default"><slot></slot></div>
  353. <div class="named"><slot name="foo"></slot></div>
  354. </div>
  355. `
  356. }
  357. }
  358. }
  359. }
  360. }).$mount()
  361. expect(vm.$el.querySelector('.default').textContent).toBe('foo')
  362. expect(vm.$el.querySelector('.named').textContent).toBe('')
  363. })
  364. it('should not keep slot name when passed further down (functional)', () => {
  365. const child = {
  366. template: `
  367. <div>
  368. <div class="default"><slot></slot></div>
  369. <div class="named"><slot name="foo"></slot></div>
  370. </div>
  371. `
  372. }
  373. const vm = new Vue({
  374. template: '<test><span slot="foo">foo</span></test>',
  375. components: {
  376. test: {
  377. functional: true,
  378. render (h, ctx) {
  379. const slots = ctx.slots()
  380. return h(child, slots.foo)
  381. }
  382. }
  383. }
  384. }).$mount()
  385. expect(vm.$el.querySelector('.default').textContent).toBe('foo')
  386. expect(vm.$el.querySelector('.named').textContent).toBe('')
  387. })
  388. // #3400
  389. it('named slots should be consistent across re-renders', done => {
  390. const vm = new Vue({
  391. template: `
  392. <comp>
  393. <div slot="foo">foo</div>
  394. </comp>
  395. `,
  396. components: {
  397. comp: {
  398. data () {
  399. return { a: 1 }
  400. },
  401. template: `<div><slot name="foo"></slot>{{ a }}</div>`
  402. }
  403. }
  404. }).$mount()
  405. expect(vm.$el.textContent).toBe('foo1')
  406. vm.$children[0].a = 2
  407. waitForUpdate(() => {
  408. expect(vm.$el.textContent).toBe('foo2')
  409. }).then(done)
  410. })
  411. // #3437
  412. it('should correctly re-create components in slot', done => {
  413. const calls = []
  414. const vm = new Vue({
  415. template: `
  416. <comp ref="child">
  417. <div slot="foo">
  418. <child></child>
  419. </div>
  420. </comp>
  421. `,
  422. components: {
  423. comp: {
  424. data () {
  425. return { ok: true }
  426. },
  427. template: `<div><slot name="foo" v-if="ok"></slot></div>`
  428. },
  429. child: {
  430. template: '<div>child</div>',
  431. created () {
  432. calls.push(1)
  433. },
  434. destroyed () {
  435. calls.push(2)
  436. }
  437. }
  438. }
  439. }).$mount()
  440. expect(calls).toEqual([1])
  441. vm.$refs.child.ok = false
  442. waitForUpdate(() => {
  443. expect(calls).toEqual([1, 2])
  444. vm.$refs.child.ok = true
  445. }).then(() => {
  446. expect(calls).toEqual([1, 2, 1])
  447. vm.$refs.child.ok = false
  448. }).then(() => {
  449. expect(calls).toEqual([1, 2, 1, 2])
  450. }).then(done)
  451. })
  452. it('should support duplicate slots', done => {
  453. const vm = new Vue({
  454. template: `
  455. <foo ref="foo">
  456. <div slot="a">{{ n }}</div>
  457. </foo>
  458. `,
  459. data: {
  460. n: 1
  461. },
  462. components: {
  463. foo: {
  464. data() {
  465. return { ok: true }
  466. },
  467. template: `
  468. <div>
  469. <slot name="a" />
  470. <slot v-if="ok" name="a" />
  471. <pre><slot name="a" /></pre>
  472. </div>
  473. `
  474. }
  475. }
  476. }).$mount()
  477. expect(vm.$el.innerHTML).toBe(`<div>1</div> <div>1</div> <pre><div>1</div></pre>`)
  478. vm.n++
  479. waitForUpdate(() => {
  480. expect(vm.$el.innerHTML).toBe(`<div>2</div> <div>2</div> <pre><div>2</div></pre>`)
  481. vm.n++
  482. }).then(() => {
  483. expect(vm.$el.innerHTML).toBe(`<div>3</div> <div>3</div> <pre><div>3</div></pre>`)
  484. vm.$refs.foo.ok = false
  485. }).then(() => {
  486. expect(vm.$el.innerHTML).toBe(`<div>3</div> <!----> <pre><div>3</div></pre>`)
  487. vm.n++
  488. vm.$refs.foo.ok = true
  489. }).then(() => {
  490. expect(vm.$el.innerHTML).toBe(`<div>4</div> <div>4</div> <pre><div>4</div></pre>`)
  491. }).then(done)
  492. })
  493. // #3518
  494. it('events should not break when slot is toggled by v-if', done => {
  495. const spy = jasmine.createSpy()
  496. const vm = new Vue({
  497. template: `<test><div class="click" @click="test">hi</div></test>`,
  498. methods: {
  499. test: spy
  500. },
  501. components: {
  502. test: {
  503. data: () => ({
  504. toggle: true
  505. }),
  506. template: `<div v-if="toggle"><slot></slot></div>`
  507. }
  508. }
  509. }).$mount()
  510. document.body.appendChild(vm.$el)
  511. expect(vm.$el.textContent).toBe('hi')
  512. vm.$children[0].toggle = false
  513. waitForUpdate(() => {
  514. vm.$children[0].toggle = true
  515. }).then(() => {
  516. triggerEvent(vm.$el.querySelector('.click'), 'click')
  517. expect(spy).toHaveBeenCalled()
  518. }).then(() => {
  519. document.body.removeChild(vm.$el)
  520. }).then(done)
  521. })
  522. it('renders static tree with text', () => {
  523. const vm = new Vue({
  524. template: `<div><test><template><div></div>Hello<div></div></template></test></div>`,
  525. components: {
  526. test: {
  527. template: '<div><slot></slot></div>'
  528. }
  529. }
  530. })
  531. vm.$mount()
  532. expect('Error when rendering root').not.toHaveBeenWarned()
  533. })
  534. // #3872
  535. it('functional component as slot', () => {
  536. const vm = new Vue({
  537. template: `
  538. <parent>
  539. <child>one</child>
  540. <child slot="a">two</child>
  541. </parent>
  542. `,
  543. components: {
  544. parent: {
  545. template: `<div><slot name="a"></slot><slot></slot></div>`
  546. },
  547. child: {
  548. functional: true,
  549. render (h, { slots }) {
  550. return h('div', slots().default)
  551. }
  552. }
  553. }
  554. }).$mount()
  555. expect(vm.$el.innerHTML.trim()).toBe('<div>two</div><div>one</div>')
  556. })
  557. // #4209
  558. it('slot of multiple text nodes should not be infinitely merged', done => {
  559. const wrap = {
  560. template: `<inner ref="inner">foo<slot></slot></inner>`,
  561. components: {
  562. inner: {
  563. data: () => ({ a: 1 }),
  564. template: `<div>{{a}}<slot></slot></div>`
  565. }
  566. }
  567. }
  568. const vm = new Vue({
  569. template: `<wrap ref="wrap">bar</wrap>`,
  570. components: { wrap }
  571. }).$mount()
  572. expect(vm.$el.textContent).toBe('1foobar')
  573. vm.$refs.wrap.$refs.inner.a++
  574. waitForUpdate(() => {
  575. expect(vm.$el.textContent).toBe('2foobar')
  576. }).then(done)
  577. })
  578. // #4315
  579. it('functional component passing slot content to stateful child component', done => {
  580. const ComponentWithSlots = {
  581. render (h) {
  582. return h('div', this.$slots.slot1)
  583. }
  584. }
  585. const FunctionalComp = {
  586. functional: true,
  587. render (h) {
  588. return h(ComponentWithSlots, [h('span', { slot: 'slot1' }, 'foo')])
  589. }
  590. }
  591. const vm = new Vue({
  592. data: { n: 1 },
  593. render (h) {
  594. return h('div', [this.n, h(FunctionalComp)])
  595. }
  596. }).$mount()
  597. expect(vm.$el.textContent).toBe('1foo')
  598. vm.n++
  599. waitForUpdate(() => {
  600. // should not lose named slot
  601. expect(vm.$el.textContent).toBe('2foo')
  602. }).then(done)
  603. })
  604. it('the elements of slot should be updated correctly', done => {
  605. const vm = new Vue({
  606. data: { n: 1 },
  607. template: '<div><test><span v-for="i in n" :key="i">{{ i }}</span><input value="a"/></test></div>',
  608. components: {
  609. test: {
  610. template: '<div><slot></slot></div>'
  611. }
  612. }
  613. }).$mount()
  614. expect(vm.$el.innerHTML).toBe('<div><span>1</span><input value="a"></div>')
  615. const input = vm.$el.querySelector('input')
  616. input.value = 'b'
  617. vm.n++
  618. waitForUpdate(() => {
  619. expect(vm.$el.innerHTML).toBe('<div><span>1</span><span>2</span><input value="a"></div>')
  620. expect(vm.$el.querySelector('input')).toBe(input)
  621. expect(vm.$el.querySelector('input').value).toBe('b')
  622. }).then(done)
  623. })
  624. // GitHub issue #5888
  625. it('should resolve correctly slot with keep-alive', () => {
  626. const vm = new Vue({
  627. template: `
  628. <div>
  629. <container>
  630. <keep-alive slot="foo">
  631. <child></child>
  632. </keep-alive>
  633. </container>
  634. </div>
  635. `,
  636. components: {
  637. container: {
  638. template:
  639. '<div><slot>default</slot><slot name="foo">named</slot></div>'
  640. },
  641. child: {
  642. template: '<span>foo</span>'
  643. }
  644. }
  645. }).$mount()
  646. expect(vm.$el.innerHTML).toBe('<div>default<span>foo</span></div>')
  647. })
  648. // #6372, #6915
  649. it('should handle nested components in slots properly', done => {
  650. const TestComponent = {
  651. template: `
  652. <component :is="toggleEl ? 'b' : 'i'">
  653. <slot />
  654. </component>
  655. `,
  656. data () {
  657. return {
  658. toggleEl: true
  659. }
  660. }
  661. }
  662. const vm = new Vue({
  663. template: `
  664. <div>
  665. <test-component ref="test">
  666. <div>
  667. <foo/>
  668. </div>
  669. <bar>
  670. <foo/>
  671. </bar>
  672. </test-component>
  673. </div>
  674. `,
  675. components: {
  676. TestComponent,
  677. foo: {
  678. template: `<div>foo</div>`
  679. },
  680. bar: {
  681. template: `<div>bar<slot/></div>`
  682. }
  683. }
  684. }).$mount()
  685. expect(vm.$el.innerHTML).toBe(`<b><div><div>foo</div></div> <div>bar<div>foo</div></div></b>`)
  686. vm.$refs.test.toggleEl = false
  687. waitForUpdate(() => {
  688. expect(vm.$el.innerHTML).toBe(`<i><div><div>foo</div></div> <div>bar<div>foo</div></div></i>`)
  689. }).then(done)
  690. })
  691. it('should preserve slot attribute if not absorbed by a Vue component', () => {
  692. const vm = new Vue({
  693. template: `
  694. <div>
  695. <div slot="foo"></div>
  696. </div>
  697. `
  698. }).$mount()
  699. expect(vm.$el.children[0].getAttribute('slot')).toBe('foo')
  700. })
  701. it('passing a slot down as named slot', () => {
  702. const Bar = {
  703. template: `<div class="bar"><slot name="foo"/></div>`
  704. }
  705. const Foo = {
  706. components: { Bar },
  707. template: `<div class="foo"><bar><slot slot="foo"/></bar></div>`
  708. }
  709. const vm = new Vue({
  710. components: { Foo },
  711. template: `<div><foo>hello</foo></div>`
  712. }).$mount()
  713. expect(vm.$el.innerHTML).toBe('<div class="foo"><div class="bar">hello</div></div>')
  714. })
  715. it('fallback content for named template slot', () => {
  716. const Bar = {
  717. template: `<div class="bar"><slot name="foo">fallback</slot></div>`
  718. }
  719. const Foo = {
  720. components: { Bar },
  721. template: `<div class="foo"><bar><template slot="foo"/><slot/></template></bar></div>`
  722. }
  723. const vm = new Vue({
  724. components: { Foo },
  725. template: `<div><foo></foo></div>`
  726. }).$mount()
  727. expect(vm.$el.innerHTML).toBe('<div class="foo"><div class="bar">fallback</div></div>')
  728. })
  729. // #7106
  730. it('should not lose functional slot across renders', done => {
  731. const One = {
  732. data: () => ({
  733. foo: true
  734. }),
  735. render (h) {
  736. this.foo
  737. return h('div', this.$slots.slot)
  738. }
  739. }
  740. const Two = {
  741. render (h) {
  742. return h('span', this.$slots.slot)
  743. }
  744. }
  745. const Three = {
  746. functional: true,
  747. render: (h, { children }) => h('span', children)
  748. }
  749. const vm = new Vue({
  750. template: `
  751. <div>
  752. <one ref="one">
  753. <two slot="slot">
  754. <three slot="slot">hello</three>
  755. </two>
  756. </one>
  757. </div>
  758. `,
  759. components: { One, Two, Three }
  760. }).$mount()
  761. expect(vm.$el.textContent).toBe('hello')
  762. // trigger re-render of <one>
  763. vm.$refs.one.foo = false
  764. waitForUpdate(() => {
  765. // should still be there
  766. expect(vm.$el.textContent).toBe('hello')
  767. }).then(done)
  768. })
  769. it('should allow passing named slots as raw children down multiple layers of functional component', () => {
  770. const CompB = {
  771. functional: true,
  772. render (h, { slots }) {
  773. return slots().foo
  774. }
  775. }
  776. const CompA = {
  777. functional: true,
  778. render (h, { children }) {
  779. return h(CompB, children)
  780. }
  781. }
  782. const vm = new Vue({
  783. components: {
  784. CompA
  785. },
  786. template: `
  787. <div>
  788. <comp-a>
  789. <span slot="foo">foo</span>
  790. </comp-a>
  791. </div>
  792. `
  793. }).$mount()
  794. expect(vm.$el.textContent).toBe('foo')
  795. })
  796. // #7817
  797. it('should not match wrong named slot in functional component on re-render', done => {
  798. const Functional = {
  799. functional: true,
  800. render: (h, ctx) => ctx.slots().default
  801. }
  802. const Stateful = {
  803. data () {
  804. return { ok: true }
  805. },
  806. render (h) {
  807. this.ok // register dep
  808. return h('div', [
  809. h(Functional, this.$slots.named)
  810. ])
  811. }
  812. }
  813. const vm = new Vue({
  814. template: `<stateful ref="stateful"><div slot="named">foo</div></stateful>`,
  815. components: { Stateful }
  816. }).$mount()
  817. expect(vm.$el.textContent).toBe('foo')
  818. vm.$refs.stateful.ok = false
  819. waitForUpdate(() => {
  820. expect(vm.$el.textContent).toBe('foo')
  821. }).then(done)
  822. })
  823. // #7975
  824. it('should update named slot correctly when its position in the tree changed', done => {
  825. const ChildComponent = {
  826. template: '<b>{{ message }}</b>',
  827. props: ['message']
  828. }
  829. let parentVm
  830. const ParentComponent = {
  831. template: `
  832. <div>
  833. <span v-if="alter">
  834. <span><slot name="foo" /></span>
  835. </span>
  836. <span v-else>
  837. <slot name="foo" />
  838. </span>
  839. </div>
  840. `,
  841. data () {
  842. return {
  843. alter: true
  844. }
  845. },
  846. mounted () {
  847. parentVm = this
  848. }
  849. }
  850. const vm = new Vue({
  851. template: `
  852. <parent-component>
  853. <span slot="foo">
  854. <child-component :message="message" />
  855. </span>
  856. </parent-component>
  857. `,
  858. components: {
  859. ChildComponent,
  860. ParentComponent
  861. },
  862. data () {
  863. return {
  864. message: 1
  865. }
  866. }
  867. }).$mount()
  868. expect(vm.$el.firstChild.innerHTML).toBe('<span><span><b>1</b></span></span>')
  869. parentVm.alter = false
  870. waitForUpdate(() => {
  871. vm.message = 2
  872. }).then(() => {
  873. expect(vm.$el.firstChild.innerHTML).toBe('<span><b>2</b></span>')
  874. }).then(done)
  875. })
  876. })