component-slot.spec.js 26 KB

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