componentSlots.spec.ts 62 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150
  1. // NOTE: This test is implemented based on the case of `runtime-core/__test__/componentSlots.spec.ts`.
  2. import {
  3. child,
  4. createComponent,
  5. createFor,
  6. createForSlots,
  7. createIf,
  8. createSlot,
  9. createVaporApp,
  10. defineVaporComponent,
  11. insert,
  12. prepend,
  13. renderEffect,
  14. setInsertionState,
  15. template,
  16. txt,
  17. vaporInteropPlugin,
  18. withVaporCtx,
  19. } from '../src'
  20. import {
  21. type Ref,
  22. createApp,
  23. createSlots,
  24. currentInstance,
  25. h,
  26. nextTick,
  27. ref,
  28. renderSlot,
  29. toDisplayString,
  30. } from '@vue/runtime-dom'
  31. import { makeRender } from './_utils'
  32. import type { DynamicSlot } from '../src/componentSlots'
  33. import { setElementText, setText } from '../src/dom/prop'
  34. const define = makeRender<any>()
  35. function renderWithSlots(slots: any): any {
  36. let instance: any
  37. const Comp = defineVaporComponent({
  38. setup() {
  39. const t0 = template('<div></div>')
  40. const n0 = t0()
  41. instance = currentInstance
  42. return n0
  43. },
  44. })
  45. const { render } = define({
  46. render() {
  47. return createComponent(Comp, {}, slots)
  48. },
  49. })
  50. render()
  51. return instance
  52. }
  53. describe('component: slots', () => {
  54. test('initSlots: instance.slots should be set correctly', () => {
  55. const { slots } = renderWithSlots({
  56. default: () => template('<span></span>')(),
  57. })
  58. expect(slots.default()).toMatchObject(document.createElement('span'))
  59. })
  60. test('updateSlots: instance.slots should be updated correctly', async () => {
  61. const flag1 = ref(true)
  62. let instance: any
  63. const Child = () => {
  64. instance = currentInstance
  65. return template('child')()
  66. }
  67. const { render } = define({
  68. render() {
  69. return createComponent(
  70. Child,
  71. {},
  72. {
  73. $: [
  74. () =>
  75. flag1.value
  76. ? { name: 'one', fn: () => template('<span></span>')() }
  77. : { name: 'two', fn: () => template('<div></div>')() },
  78. ],
  79. },
  80. )
  81. },
  82. })
  83. render()
  84. expect(instance.slots).toHaveProperty('one')
  85. expect(instance.slots).not.toHaveProperty('two')
  86. flag1.value = false
  87. await nextTick()
  88. expect(instance.slots).not.toHaveProperty('one')
  89. expect(instance.slots).toHaveProperty('two')
  90. })
  91. // passes but no warning for slot invocation in vapor currently
  92. test.todo('should not warn when mounting another app in setup', () => {
  93. const Comp = defineVaporComponent({
  94. setup(_, { slots }) {
  95. return slots.default!()
  96. },
  97. })
  98. const mountComp = () => {
  99. createVaporApp({
  100. render() {
  101. return createComponent(
  102. Comp,
  103. {},
  104. { default: () => template('msg')() },
  105. )!
  106. },
  107. })
  108. }
  109. const App = {
  110. setup() {
  111. mountComp()
  112. return []
  113. },
  114. }
  115. createVaporApp(App).mount(document.createElement('div'))
  116. expect(
  117. 'Slot "default" invoked outside of the render function',
  118. ).not.toHaveBeenWarned()
  119. })
  120. describe('createSlot', () => {
  121. test('slot should be rendered correctly', () => {
  122. const Comp = defineVaporComponent(() => {
  123. const n0 = template('<div>')()
  124. insert(createSlot('header'), n0 as any as ParentNode)
  125. return n0
  126. })
  127. const { host } = define(() => {
  128. return createComponent(Comp, null, {
  129. header: () => template('header')(),
  130. })
  131. }).render()
  132. expect(host.innerHTML).toBe('<div>header<!--slot--></div>')
  133. })
  134. test('slot should be rendered correctly with slot props', async () => {
  135. const src = ref('header')
  136. const Comp = defineVaporComponent(() => {
  137. const n0 = template('<div></div>')()
  138. insert(
  139. createSlot('header', { title: () => src.value }),
  140. n0 as any as ParentNode,
  141. )
  142. return n0
  143. })
  144. const { host } = define(() => {
  145. return createComponent(Comp, null, {
  146. header: props => {
  147. const el = template('<h1></h1>')()
  148. renderEffect(() => {
  149. setElementText(el, props.title)
  150. })
  151. return el
  152. },
  153. })
  154. }).render()
  155. expect(host.innerHTML).toBe('<div><h1>header</h1><!--slot--></div>')
  156. src.value = 'footer'
  157. await nextTick()
  158. expect(host.innerHTML).toBe('<div><h1>footer</h1><!--slot--></div>')
  159. })
  160. test('dynamic slot props', async () => {
  161. let props: any
  162. const bindObj = ref<Record<string, any>>({ foo: 1, baz: 'qux' })
  163. const Comp = defineVaporComponent(() =>
  164. createSlot('default', { $: [() => bindObj.value] }),
  165. )
  166. define(() =>
  167. createComponent(Comp, null, {
  168. default: (_props: any) => ((props = _props), []),
  169. }),
  170. ).render()
  171. expect(props).toEqual({ foo: 1, baz: 'qux' })
  172. bindObj.value.foo = 2
  173. await nextTick()
  174. expect(props).toEqual({ foo: 2, baz: 'qux' })
  175. delete bindObj.value.baz
  176. await nextTick()
  177. expect(props).toEqual({ foo: 2 })
  178. })
  179. test('dynamic slot props with static slot props', async () => {
  180. let props: any
  181. const foo = ref(0)
  182. const bindObj = ref<Record<string, any>>({ foo: 100, baz: 'qux' })
  183. const Comp = defineVaporComponent(() =>
  184. createSlot('default', {
  185. foo: () => foo.value,
  186. $: [() => bindObj.value],
  187. }),
  188. )
  189. define(() =>
  190. createComponent(Comp, null, {
  191. default: (_props: any) => ((props = _props), []),
  192. }),
  193. ).render()
  194. expect(props).toEqual({ foo: 100, baz: 'qux' })
  195. foo.value = 2
  196. await nextTick()
  197. expect(props).toEqual({ foo: 100, baz: 'qux' })
  198. delete bindObj.value.foo
  199. await nextTick()
  200. expect(props).toEqual({ foo: 2, baz: 'qux' })
  201. })
  202. test('dynamic slot should be rendered correctly with slot props', async () => {
  203. const val = ref('header')
  204. const Comp = defineVaporComponent(() => {
  205. const n0 = template('<div></div>')()
  206. prepend(
  207. n0 as any as ParentNode,
  208. createSlot('header', { title: () => val.value }),
  209. )
  210. return n0
  211. })
  212. const { host } = define(() => {
  213. // dynamic slot
  214. return createComponent(Comp, null, {
  215. $: [
  216. () => ({
  217. name: 'header',
  218. fn: (props: any) => {
  219. const el = template('<h1></h1>')()
  220. renderEffect(() => {
  221. setElementText(el, props.title)
  222. })
  223. return el
  224. },
  225. }),
  226. ],
  227. })
  228. }).render()
  229. expect(host.innerHTML).toBe('<div><h1>header</h1><!--slot--></div>')
  230. val.value = 'footer'
  231. await nextTick()
  232. expect(host.innerHTML).toBe('<div><h1>footer</h1><!--slot--></div>')
  233. })
  234. test('dynamic slot outlet should be render correctly with slot props', async () => {
  235. const val = ref('header')
  236. const Comp = defineVaporComponent(() => {
  237. const n0 = template('<div></div>')()
  238. prepend(
  239. n0 as any as ParentNode,
  240. createSlot(
  241. () => val.value, // dynamic slot outlet name
  242. ),
  243. )
  244. return n0
  245. })
  246. const { host } = define(() => {
  247. return createComponent(Comp, null, {
  248. header: () => template('header')(),
  249. footer: () => template('footer')(),
  250. })
  251. }).render()
  252. expect(host.innerHTML).toBe('<div>header<!--slot--></div>')
  253. val.value = 'footer'
  254. await nextTick()
  255. expect(host.innerHTML).toBe('<div>footer<!--slot--></div>')
  256. })
  257. test('fallback should be render correctly', () => {
  258. const Comp = defineVaporComponent(() => {
  259. const n0 = template('<div></div>')()
  260. insert(
  261. createSlot('header', undefined, () => template('fallback')()),
  262. n0 as any as ParentNode,
  263. )
  264. return n0
  265. })
  266. const { host } = define(() => {
  267. return createComponent(Comp, {}, {})
  268. }).render()
  269. expect(host.innerHTML).toBe('<div>fallback<!--slot--></div>')
  270. })
  271. test('dynamic slot should be updated correctly', async () => {
  272. const flag1 = ref(true)
  273. const Child = defineVaporComponent(() => {
  274. const temp0 = template('<p></p>')
  275. const el0 = temp0()
  276. const el1 = temp0()
  277. const slot1 = createSlot('one', null, () => template('one fallback')())
  278. const slot2 = createSlot('two', null, () => template('two fallback')())
  279. insert(slot1, el0 as any as ParentNode)
  280. insert(slot2, el1 as any as ParentNode)
  281. return [el0, el1]
  282. })
  283. const { host } = define(() => {
  284. return createComponent(Child, null, {
  285. $: [
  286. () =>
  287. flag1.value
  288. ? {
  289. name: 'one',
  290. fn: () => template('one content')(),
  291. }
  292. : {
  293. name: 'two',
  294. fn: () => template('two content')(),
  295. },
  296. ],
  297. })
  298. }).render()
  299. expect(host.innerHTML).toBe(
  300. '<p>one content<!--slot--></p><p>two fallback<!--slot--></p>',
  301. )
  302. flag1.value = false
  303. await nextTick()
  304. expect(host.innerHTML).toBe(
  305. '<p>one fallback<!--slot--></p><p>two content<!--slot--></p>',
  306. )
  307. flag1.value = true
  308. await nextTick()
  309. expect(host.innerHTML).toBe(
  310. '<p>one content<!--slot--></p><p>two fallback<!--slot--></p>',
  311. )
  312. })
  313. test('dynamic slot outlet should be updated correctly', async () => {
  314. const slotOutletName = ref('one')
  315. const Child = defineVaporComponent(() => {
  316. const temp0 = template('<p>')
  317. const el0 = temp0()
  318. const slot1 = createSlot(
  319. () => slotOutletName.value,
  320. undefined,
  321. () => template('fallback')(),
  322. )
  323. insert(slot1, el0 as any as ParentNode)
  324. return el0
  325. })
  326. const { host } = define(() => {
  327. return createComponent(
  328. Child,
  329. {},
  330. {
  331. one: () => template('one content')(),
  332. two: () => template('two content')(),
  333. },
  334. )
  335. }).render()
  336. expect(host.innerHTML).toBe('<p>one content<!--slot--></p>')
  337. slotOutletName.value = 'two'
  338. await nextTick()
  339. expect(host.innerHTML).toBe('<p>two content<!--slot--></p>')
  340. slotOutletName.value = 'none'
  341. await nextTick()
  342. expect(host.innerHTML).toBe('<p>fallback<!--slot--></p>')
  343. })
  344. test('non-exist slot', async () => {
  345. const Child = defineVaporComponent(() => {
  346. const el0 = template('<p>')()
  347. const slot = createSlot('not-exist', undefined)
  348. insert(slot, el0 as any as ParentNode)
  349. return el0
  350. })
  351. const { host } = define(() => {
  352. return createComponent(Child)
  353. }).render()
  354. expect(host.innerHTML).toBe('<p><!--slot--></p>')
  355. })
  356. test('use fallback when inner content changes', async () => {
  357. const Child = {
  358. setup() {
  359. return createSlot('default', null, () =>
  360. document.createTextNode('fallback'),
  361. )
  362. },
  363. }
  364. const toggle = ref(true)
  365. const { html } = define({
  366. setup() {
  367. return createComponent(Child, null, {
  368. default: () => {
  369. return createIf(
  370. () => toggle.value,
  371. () => {
  372. return document.createTextNode('content')
  373. },
  374. )
  375. },
  376. })
  377. },
  378. }).render()
  379. expect(html()).toBe('content<!--if--><!--slot-->')
  380. toggle.value = false
  381. await nextTick()
  382. expect(html()).toBe('fallback<!--if--><!--slot-->')
  383. toggle.value = true
  384. await nextTick()
  385. expect(html()).toBe('content<!--if--><!--slot-->')
  386. })
  387. test('use fallback on initial render', async () => {
  388. const Child = {
  389. setup() {
  390. return createSlot('default', null, () =>
  391. document.createTextNode('fallback'),
  392. )
  393. },
  394. }
  395. const toggle = ref(false)
  396. const { html } = define({
  397. setup() {
  398. return createComponent(Child, null, {
  399. default: () => {
  400. return createIf(
  401. () => toggle.value,
  402. () => {
  403. return document.createTextNode('content')
  404. },
  405. )
  406. },
  407. })
  408. },
  409. }).render()
  410. expect(html()).toBe('fallback<!--if--><!--slot-->')
  411. toggle.value = true
  412. await nextTick()
  413. expect(html()).toBe('content<!--if--><!--slot-->')
  414. toggle.value = false
  415. await nextTick()
  416. expect(html()).toBe('fallback<!--if--><!--slot-->')
  417. })
  418. test('dynamic slot work with v-if', async () => {
  419. const val = ref('header')
  420. const toggle = ref(false)
  421. const Comp = defineVaporComponent(() => {
  422. const n0 = template('<div></div>')()
  423. prepend(n0 as any as ParentNode, createSlot('header', null))
  424. return n0
  425. })
  426. const { host } = define(() => {
  427. // dynamic slot
  428. return createComponent(Comp, null, {
  429. $: [
  430. () =>
  431. (toggle.value
  432. ? {
  433. name: val.value,
  434. fn: () => {
  435. return template('<h1></h1>')()
  436. },
  437. }
  438. : void 0) as DynamicSlot,
  439. ],
  440. })
  441. }).render()
  442. expect(host.innerHTML).toBe('<div><!--slot--></div>')
  443. toggle.value = true
  444. await nextTick()
  445. expect(host.innerHTML).toBe('<div><h1></h1><!--slot--></div>')
  446. })
  447. test('render fallback when slot content is not valid', async () => {
  448. const Child = {
  449. setup() {
  450. return createSlot('default', null, () =>
  451. document.createTextNode('fallback'),
  452. )
  453. },
  454. }
  455. const { html } = define({
  456. setup() {
  457. return createComponent(Child, null, {
  458. default: () => {
  459. return template('<!--comment-->')()
  460. },
  461. })
  462. },
  463. }).render()
  464. expect(html()).toBe('fallback<!--slot-->')
  465. })
  466. test('render fallback when v-if condition is false', async () => {
  467. const Child = {
  468. setup() {
  469. return createSlot('default', null, () =>
  470. document.createTextNode('fallback'),
  471. )
  472. },
  473. }
  474. const toggle = ref(false)
  475. const { html } = define({
  476. setup() {
  477. return createComponent(Child, null, {
  478. default: () => {
  479. return createIf(
  480. () => toggle.value,
  481. () => {
  482. return document.createTextNode('content')
  483. },
  484. )
  485. },
  486. })
  487. },
  488. }).render()
  489. expect(html()).toBe('fallback<!--if--><!--slot-->')
  490. toggle.value = true
  491. await nextTick()
  492. expect(html()).toBe('content<!--if--><!--slot-->')
  493. toggle.value = false
  494. await nextTick()
  495. expect(html()).toBe('fallback<!--if--><!--slot-->')
  496. })
  497. test('render fallback with nested v-if', async () => {
  498. const Child = {
  499. setup() {
  500. return createSlot('default', null, () =>
  501. document.createTextNode('fallback'),
  502. )
  503. },
  504. }
  505. const outerShow = ref(false)
  506. const innerShow = ref(false)
  507. const { html } = define({
  508. setup() {
  509. return createComponent(Child, null, {
  510. default: () => {
  511. return createIf(
  512. () => outerShow.value,
  513. () => {
  514. return createIf(
  515. () => innerShow.value,
  516. () => {
  517. return document.createTextNode('content')
  518. },
  519. )
  520. },
  521. )
  522. },
  523. })
  524. },
  525. }).render()
  526. expect(html()).toBe('fallback<!--if--><!--slot-->')
  527. outerShow.value = true
  528. await nextTick()
  529. expect(html()).toBe('fallback<!--if--><!--if--><!--slot-->')
  530. innerShow.value = true
  531. await nextTick()
  532. expect(html()).toBe('content<!--if--><!--if--><!--slot-->')
  533. innerShow.value = false
  534. await nextTick()
  535. expect(html()).toBe('fallback<!--if--><!--if--><!--slot-->')
  536. outerShow.value = false
  537. await nextTick()
  538. expect(html()).toBe('fallback<!--if--><!--slot-->')
  539. outerShow.value = true
  540. await nextTick()
  541. expect(html()).toBe('fallback<!--if--><!--if--><!--slot-->')
  542. innerShow.value = true
  543. await nextTick()
  544. expect(html()).toBe('content<!--if--><!--if--><!--slot-->')
  545. })
  546. test('render fallback with v-for', async () => {
  547. const Child = {
  548. setup() {
  549. return createSlot('default', null, () =>
  550. document.createTextNode('fallback'),
  551. )
  552. },
  553. }
  554. const items = ref<number[]>([1])
  555. const { html } = define({
  556. setup() {
  557. return createComponent(Child, null, {
  558. default: () => {
  559. const n2 = createFor(
  560. () => items.value,
  561. for_item0 => {
  562. const n4 = template('<span> </span>')() as any
  563. const x4 = child(n4) as any
  564. renderEffect(() =>
  565. setText(x4, toDisplayString(for_item0.value)),
  566. )
  567. return n4
  568. },
  569. )
  570. return n2
  571. },
  572. })
  573. },
  574. }).render()
  575. expect(html()).toBe('<span>1</span><!--for--><!--slot-->')
  576. items.value.pop()
  577. await nextTick()
  578. expect(html()).toBe('fallback<!--for--><!--slot-->')
  579. items.value.pop()
  580. await nextTick()
  581. expect(html()).toBe('fallback<!--for--><!--slot-->')
  582. items.value.push(2)
  583. await nextTick()
  584. expect(html()).toBe('<span>2</span><!--for--><!--slot-->')
  585. })
  586. test('render fallback with v-for (empty source)', async () => {
  587. const Child = {
  588. setup() {
  589. return createSlot('default', null, () =>
  590. document.createTextNode('fallback'),
  591. )
  592. },
  593. }
  594. const items = ref<number[]>([])
  595. const { html } = define({
  596. setup() {
  597. return createComponent(Child, null, {
  598. default: () => {
  599. const n2 = createFor(
  600. () => items.value,
  601. for_item0 => {
  602. const n4 = template('<span> </span>')() as any
  603. const x4 = child(n4) as any
  604. renderEffect(() =>
  605. setText(x4, toDisplayString(for_item0.value)),
  606. )
  607. return n4
  608. },
  609. )
  610. return n2
  611. },
  612. })
  613. },
  614. }).render()
  615. expect(html()).toBe('fallback<!--for--><!--slot-->')
  616. items.value.push(1)
  617. await nextTick()
  618. expect(html()).toBe('<span>1</span><!--for--><!--slot-->')
  619. items.value.pop()
  620. await nextTick()
  621. expect(html()).toBe('fallback<!--for--><!--slot-->')
  622. items.value.pop()
  623. await nextTick()
  624. expect(html()).toBe('fallback<!--for--><!--slot-->')
  625. items.value.push(2)
  626. await nextTick()
  627. expect(html()).toBe('<span>2</span><!--for--><!--slot-->')
  628. })
  629. test('work with v-once', async () => {
  630. const Child = defineVaporComponent({
  631. setup() {
  632. return createSlot(
  633. 'default',
  634. null,
  635. undefined,
  636. undefined,
  637. true /* once */,
  638. )
  639. },
  640. })
  641. const count = ref(0)
  642. const { html } = define({
  643. setup() {
  644. return createComponent(Child, null, {
  645. default: () => {
  646. const n3 = template('<div> </div>')() as any
  647. const x3 = txt(n3) as any
  648. renderEffect(() => setText(x3, toDisplayString(count.value)))
  649. return n3
  650. },
  651. })
  652. },
  653. }).render()
  654. expect(html()).toBe('<div>0</div><!--slot-->')
  655. // expect no changes due to v-once
  656. count.value++
  657. await nextTick()
  658. expect(html()).toBe('<div>0</div><!--slot-->')
  659. })
  660. })
  661. describe('forwarded slot', () => {
  662. test('should work', async () => {
  663. const Child = defineVaporComponent({
  664. setup() {
  665. return createSlot('foo', null)
  666. },
  667. })
  668. const Parent = defineVaporComponent({
  669. setup() {
  670. const n2 = createComponent(
  671. Child,
  672. null,
  673. {
  674. foo: withVaporCtx(() => {
  675. return createSlot('foo', null)
  676. }),
  677. },
  678. true,
  679. )
  680. return n2
  681. },
  682. })
  683. const foo = ref('foo')
  684. const { host } = define({
  685. setup() {
  686. const n2 = createComponent(
  687. Parent,
  688. null,
  689. {
  690. foo: () => {
  691. const n0 = template(' ')() as any
  692. renderEffect(() => setText(n0, foo.value))
  693. return n0
  694. },
  695. },
  696. true,
  697. )
  698. return n2
  699. },
  700. }).render()
  701. expect(host.innerHTML).toBe('foo<!--slot--><!--slot-->')
  702. foo.value = 'bar'
  703. await nextTick()
  704. expect(host.innerHTML).toBe('bar<!--slot--><!--slot-->')
  705. })
  706. test('mixed with non-forwarded slot', async () => {
  707. const Child = defineVaporComponent({
  708. setup() {
  709. return [createSlot('foo', null)]
  710. },
  711. })
  712. const Parent = defineVaporComponent({
  713. setup() {
  714. const n2 = createComponent(Child, null, {
  715. foo: withVaporCtx(() => {
  716. const n0 = createSlot('foo', null)
  717. return n0
  718. }),
  719. })
  720. const n3 = createSlot('default', null)
  721. return [n2, n3]
  722. },
  723. })
  724. const foo = ref('foo')
  725. const { host } = define({
  726. setup() {
  727. const n2 = createComponent(
  728. Parent,
  729. null,
  730. {
  731. foo: () => {
  732. const n0 = template(' ')() as any
  733. renderEffect(() => setText(n0, foo.value))
  734. return n0
  735. },
  736. default: () => {
  737. const n3 = template(' ')() as any
  738. renderEffect(() => setText(n3, foo.value))
  739. return n3
  740. },
  741. },
  742. true,
  743. )
  744. return n2
  745. },
  746. }).render()
  747. expect(host.innerHTML).toBe('foo<!--slot--><!--slot-->foo<!--slot-->')
  748. foo.value = 'bar'
  749. await nextTick()
  750. expect(host.innerHTML).toBe('bar<!--slot--><!--slot-->bar<!--slot-->')
  751. })
  752. test('forwarded slot with fallback', async () => {
  753. const Child = defineVaporComponent({
  754. setup() {
  755. return createSlot('default', null, () => template('child fallback')())
  756. },
  757. })
  758. const Parent = defineVaporComponent({
  759. setup() {
  760. const n2 = createComponent(Child, null, {
  761. default: withVaporCtx(() => {
  762. const n0 = createSlot('default', null, () => {
  763. return template('<!-- <div></div> -->')()
  764. })
  765. return n0
  766. }),
  767. })
  768. return n2
  769. },
  770. })
  771. const { html } = define({
  772. setup() {
  773. return createComponent(Parent, null, {
  774. default: () => template('<!-- <div>App</div> -->')(),
  775. })
  776. },
  777. }).render()
  778. expect(html()).toBe('child fallback<!--slot--><!--slot-->')
  779. })
  780. test('named forwarded slot with v-if', async () => {
  781. const Child = defineVaporComponent({
  782. setup() {
  783. return createSlot('default', null)
  784. },
  785. })
  786. const Parent = defineVaporComponent({
  787. props: {
  788. show: Boolean,
  789. },
  790. setup(props) {
  791. const n6 = createComponent(
  792. Child,
  793. null,
  794. {
  795. default: withVaporCtx(() => {
  796. const n0 = createIf(
  797. () => props.show,
  798. () => {
  799. const n5 = template('<div></div>')() as any
  800. setInsertionState(n5, null, true)
  801. createSlot('header', null, () => {
  802. const n4 = template('default header')()
  803. return n4
  804. })
  805. return n5
  806. },
  807. )
  808. return n0
  809. }),
  810. },
  811. true,
  812. )
  813. return n6
  814. },
  815. })
  816. const show = ref(false)
  817. const { html } = define({
  818. setup() {
  819. return createComponent(
  820. Parent,
  821. {
  822. show: () => show.value,
  823. },
  824. {
  825. header: () => template('custom header')(),
  826. },
  827. )
  828. },
  829. }).render()
  830. expect(html()).toBe('<!--if--><!--slot-->')
  831. show.value = true
  832. await nextTick()
  833. expect(html()).toBe(
  834. '<div>custom header<!--slot--></div><!--if--><!--slot-->',
  835. )
  836. show.value = false
  837. await nextTick()
  838. expect(html()).toBe('<!--if--><!--slot-->')
  839. })
  840. test('forwarded slot with fallback (v-if)', async () => {
  841. const Child = defineVaporComponent({
  842. setup() {
  843. return createSlot('default', null, () => template('child fallback')())
  844. },
  845. })
  846. const show = ref(false)
  847. const Parent = defineVaporComponent({
  848. setup() {
  849. const n2 = createComponent(Child, null, {
  850. default: withVaporCtx(() => {
  851. const n0 = createSlot('default', null, () => {
  852. const n2 = createIf(
  853. () => show.value,
  854. () => {
  855. const n4 = template('<div>if content</div>')()
  856. return n4
  857. },
  858. )
  859. return n2
  860. })
  861. return n0
  862. }),
  863. })
  864. return n2
  865. },
  866. })
  867. const { html } = define({
  868. setup() {
  869. return createComponent(Parent, null, {
  870. default: () => template('<!-- <div>App</div> -->')(),
  871. })
  872. },
  873. }).render()
  874. expect(html()).toBe('child fallback<!--if--><!--slot--><!--slot-->')
  875. show.value = true
  876. await nextTick()
  877. expect(html()).toBe(
  878. '<div>if content</div><!--if--><!--slot--><!--slot-->',
  879. )
  880. })
  881. test('forwarded slot with fallback (v-for)', async () => {
  882. const Child = defineVaporComponent({
  883. setup() {
  884. return createSlot('default', null, () => template('child fallback')())
  885. },
  886. })
  887. const items = ref<number[]>([])
  888. const Parent = defineVaporComponent({
  889. setup() {
  890. const n2 = createComponent(Child, null, {
  891. default: withVaporCtx(() => {
  892. const n0 = createSlot('default', null, () => {
  893. const n2 = createFor(
  894. () => items.value,
  895. for_item0 => {
  896. const n4 = template('<span> </span>')() as any
  897. const x4 = child(n4) as any
  898. renderEffect(() =>
  899. setText(x4, toDisplayString(for_item0.value)),
  900. )
  901. return n4
  902. },
  903. )
  904. return n2
  905. })
  906. return n0
  907. }),
  908. })
  909. return n2
  910. },
  911. })
  912. const { html } = define({
  913. setup() {
  914. return createComponent(Parent, null, {
  915. default: () => template('<!-- <div>App</div> -->')(),
  916. })
  917. },
  918. }).render()
  919. expect(html()).toBe('child fallback<!--for--><!--slot--><!--slot-->')
  920. items.value.push(1)
  921. await nextTick()
  922. expect(html()).toBe('<span>1</span><!--for--><!--slot--><!--slot-->')
  923. items.value.pop()
  924. await nextTick()
  925. expect(html()).toBe('child fallback<!--for--><!--slot--><!--slot-->')
  926. })
  927. test('consecutive slots with insertion state', async () => {
  928. const { component: Child } = define({
  929. setup() {
  930. const n2 = template('<div><div>baz</div></div>', true)() as any
  931. setInsertionState(n2, 0)
  932. createSlot('default', null)
  933. setInsertionState(n2, 0)
  934. createSlot('foo', null)
  935. return n2
  936. },
  937. })
  938. const { html } = define({
  939. setup() {
  940. return createComponent(Child, null, {
  941. default: () => template('default')(),
  942. foo: () => template('foo')(),
  943. })
  944. },
  945. }).render()
  946. expect(html()).toBe(
  947. `<div>` +
  948. `default<!--slot-->` +
  949. `foo<!--slot-->` +
  950. `<div>baz</div>` +
  951. `</div>`,
  952. )
  953. })
  954. describe('vdom interop', () => {
  955. const createVaporSlot = (fallbackText = 'fallback') => {
  956. return defineVaporComponent({
  957. setup() {
  958. const n0 = createSlot('foo', null, () => {
  959. const n2 = template(`<div>${fallbackText}</div>`)()
  960. return n2
  961. })
  962. return n0
  963. },
  964. })
  965. }
  966. const createVdomSlot = (fallbackText = 'fallback') => {
  967. return {
  968. render(this: any) {
  969. return renderSlot(this.$slots, 'foo', {}, () => [
  970. h('div', fallbackText),
  971. ])
  972. },
  973. }
  974. }
  975. const createVaporForwardedSlot = (
  976. targetComponent: any,
  977. fallbackText?: string,
  978. ) => {
  979. return defineVaporComponent({
  980. setup() {
  981. const n2 = createComponent(
  982. targetComponent,
  983. null,
  984. {
  985. foo: withVaporCtx(() => {
  986. return fallbackText
  987. ? createSlot('foo', null, () => {
  988. const n2 = template(`<div>${fallbackText}</div>`)()
  989. return n2
  990. })
  991. : createSlot('foo', null)
  992. }),
  993. },
  994. true,
  995. )
  996. return n2
  997. },
  998. })
  999. }
  1000. const createVdomForwardedSlot = (
  1001. targetComponent: any,
  1002. fallbackText?: string,
  1003. ) => {
  1004. return {
  1005. render(this: any) {
  1006. return h(targetComponent, null, {
  1007. foo: () => [
  1008. fallbackText
  1009. ? renderSlot(this.$slots, 'foo', {}, () => [
  1010. h('div', fallbackText),
  1011. ])
  1012. : renderSlot(this.$slots, 'foo'),
  1013. ],
  1014. _: 3 /* FORWARDED */,
  1015. })
  1016. },
  1017. }
  1018. }
  1019. const createMultipleVaporForwardedSlots = (
  1020. targetComponent: any,
  1021. count: number,
  1022. ) => {
  1023. let current = targetComponent
  1024. for (let i = 0; i < count; i++) {
  1025. current = createVaporForwardedSlot(current)
  1026. }
  1027. return current
  1028. }
  1029. const createMultipleVdomForwardedSlots = (
  1030. targetComponent: any,
  1031. count: number,
  1032. ) => {
  1033. let current = targetComponent
  1034. for (let i = 0; i < count; i++) {
  1035. current = createVdomForwardedSlot(current)
  1036. }
  1037. return current
  1038. }
  1039. const createTestApp = (
  1040. rootComponent: any,
  1041. foo: Ref<string>,
  1042. show: Ref<boolean>,
  1043. ) => {
  1044. return {
  1045. setup() {
  1046. return () =>
  1047. h(
  1048. rootComponent,
  1049. null,
  1050. createSlots({ _: 2 /* DYNAMIC */ } as any, [
  1051. show.value
  1052. ? {
  1053. name: 'foo',
  1054. fn: () => [h('span', foo.value)],
  1055. key: '0',
  1056. }
  1057. : undefined,
  1058. ]),
  1059. )
  1060. },
  1061. }
  1062. }
  1063. const createEmptyTestApp = (rootComponent: any) => {
  1064. return {
  1065. setup() {
  1066. return () => h(rootComponent)
  1067. },
  1068. }
  1069. }
  1070. test('vdom slot > vapor forwarded slot > vapor slot', async () => {
  1071. const foo = ref('foo')
  1072. const show = ref(true)
  1073. const VaporSlot = createVaporSlot()
  1074. const VaporForwardedSlot = createVaporForwardedSlot(VaporSlot)
  1075. const App = createTestApp(VaporForwardedSlot, foo, show)
  1076. const root = document.createElement('div')
  1077. createApp(App).use(vaporInteropPlugin).mount(root)
  1078. expect(root.innerHTML).toBe('<span>foo</span><!--slot-->')
  1079. foo.value = 'bar'
  1080. await nextTick()
  1081. expect(root.innerHTML).toBe('<span>bar</span><!--slot-->')
  1082. show.value = false
  1083. await nextTick()
  1084. expect(root.innerHTML).toBe('<div>fallback</div><!--slot-->')
  1085. })
  1086. test('vdom slot > vapor forwarded slot(with fallback) > vapor slot', async () => {
  1087. const foo = ref('foo')
  1088. const show = ref(true)
  1089. const VaporSlot = createVaporSlot()
  1090. const VaporForwardedSlotWithFallback = createVaporForwardedSlot(
  1091. VaporSlot,
  1092. 'forwarded fallback',
  1093. )
  1094. const App = createTestApp(VaporForwardedSlotWithFallback, foo, show)
  1095. const root = document.createElement('div')
  1096. createApp(App).use(vaporInteropPlugin).mount(root)
  1097. expect(root.innerHTML).toBe('<span>foo</span><!--slot-->')
  1098. foo.value = 'bar'
  1099. await nextTick()
  1100. expect(root.innerHTML).toBe('<span>bar</span><!--slot-->')
  1101. show.value = false
  1102. await nextTick()
  1103. expect(root.innerHTML).toBe('<div>forwarded fallback</div><!--slot-->')
  1104. })
  1105. test('vdom slot > vapor forwarded slot > vdom slot', async () => {
  1106. const foo = ref('foo')
  1107. const show = ref(true)
  1108. const VdomSlot = createVdomSlot()
  1109. const VaporForwardedSlot = createVaporForwardedSlot(VdomSlot)
  1110. const App = createTestApp(VaporForwardedSlot, foo, show)
  1111. const root = document.createElement('div')
  1112. createApp(App).use(vaporInteropPlugin).mount(root)
  1113. expect(root.innerHTML).toBe('<span>foo</span>')
  1114. foo.value = 'bar'
  1115. await nextTick()
  1116. expect(root.innerHTML).toBe('<span>bar</span>')
  1117. show.value = false
  1118. await nextTick()
  1119. expect(root.innerHTML).toBe('<div>fallback</div>')
  1120. show.value = true
  1121. await nextTick()
  1122. expect(root.innerHTML).toBe('<span>bar</span>')
  1123. })
  1124. test('vdom slot > vapor forwarded slot(with fallback) > vdom slot', async () => {
  1125. const foo = ref('foo')
  1126. const show = ref(true)
  1127. const VdomSlot = createVdomSlot()
  1128. const VaporForwardedSlotWithFallback = createVaporForwardedSlot(
  1129. VdomSlot,
  1130. 'forwarded fallback',
  1131. )
  1132. const App = createTestApp(VaporForwardedSlotWithFallback, foo, show)
  1133. const root = document.createElement('div')
  1134. createApp(App).use(vaporInteropPlugin).mount(root)
  1135. expect(root.innerHTML).toBe('<span>foo</span>')
  1136. foo.value = 'bar'
  1137. await nextTick()
  1138. expect(root.innerHTML).toBe('<span>bar</span>')
  1139. show.value = false
  1140. await nextTick()
  1141. expect(root.innerHTML).toBe('<div>forwarded fallback</div>')
  1142. })
  1143. test('vdom slot > vapor forwarded slot > vdom forwarded slot > vapor slot', async () => {
  1144. const foo = ref('foo')
  1145. const show = ref(true)
  1146. const VaporSlot = createVaporSlot()
  1147. const VdomForwardedSlot = createVdomForwardedSlot(VaporSlot)
  1148. const VaporForwardedSlot = createVaporForwardedSlot(VdomForwardedSlot)
  1149. const App = createTestApp(VaporForwardedSlot, foo, show)
  1150. const root = document.createElement('div')
  1151. createApp(App).use(vaporInteropPlugin).mount(root)
  1152. expect(root.innerHTML).toBe('<span>foo</span>')
  1153. foo.value = 'bar'
  1154. await nextTick()
  1155. expect(root.innerHTML).toBe('<span>bar</span>')
  1156. show.value = false
  1157. await nextTick()
  1158. expect(root.innerHTML).toBe('<div>fallback</div>')
  1159. show.value = true
  1160. await nextTick()
  1161. expect(root.innerHTML).toBe('<span>bar</span>')
  1162. })
  1163. test('vdom slot > vapor forwarded slot(with fallback) > vdom forwarded slot > vapor slot', async () => {
  1164. const foo = ref('foo')
  1165. const show = ref(true)
  1166. const VaporSlot = createVaporSlot()
  1167. const VdomForwardedSlot = createVdomForwardedSlot(VaporSlot)
  1168. const VaporForwardedSlotWithFallback = createVaporForwardedSlot(
  1169. VdomForwardedSlot,
  1170. 'forwarded fallback',
  1171. )
  1172. const App = createTestApp(VaporForwardedSlotWithFallback, foo, show)
  1173. const root = document.createElement('div')
  1174. createApp(App).use(vaporInteropPlugin).mount(root)
  1175. expect(root.innerHTML).toBe('<span>foo</span>')
  1176. foo.value = 'bar'
  1177. await nextTick()
  1178. expect(root.innerHTML).toBe('<span>bar</span>')
  1179. show.value = false
  1180. await nextTick()
  1181. expect(root.innerHTML).toBe('<div>forwarded fallback</div>')
  1182. show.value = true
  1183. await nextTick()
  1184. expect(root.innerHTML).toBe('<span>bar</span>')
  1185. })
  1186. test('vdom slot > vapor forwarded slot > vdom forwarded slot(with fallback) > vapor slot', async () => {
  1187. const foo = ref('foo')
  1188. const show = ref(true)
  1189. const VaporSlot = createVaporSlot()
  1190. const VdomForwardedSlotWithFallback = createVdomForwardedSlot(
  1191. VaporSlot,
  1192. 'vdom fallback',
  1193. )
  1194. const VaporForwardedSlot = createVaporForwardedSlot(
  1195. VdomForwardedSlotWithFallback,
  1196. )
  1197. const App = createTestApp(VaporForwardedSlot, foo, show)
  1198. const root = document.createElement('div')
  1199. createApp(App).use(vaporInteropPlugin).mount(root)
  1200. expect(root.innerHTML).toBe('<span>foo</span>')
  1201. foo.value = 'bar'
  1202. await nextTick()
  1203. expect(root.innerHTML).toBe('<span>bar</span>')
  1204. show.value = false
  1205. await nextTick()
  1206. expect(root.innerHTML).toBe('<div>vdom fallback</div>')
  1207. show.value = true
  1208. await nextTick()
  1209. expect(root.innerHTML).toBe('<span>bar</span>')
  1210. })
  1211. test('vdom slot(empty) > vapor forwarded slot > vdom forwarded slot(with fallback) > vapor slot', async () => {
  1212. const VaporSlot = createVaporSlot()
  1213. const VdomForwardedSlotWithFallback = createVdomForwardedSlot(
  1214. VaporSlot,
  1215. 'vdom fallback',
  1216. )
  1217. const VaporForwardedSlot = createVaporForwardedSlot(
  1218. VdomForwardedSlotWithFallback,
  1219. )
  1220. const App = createEmptyTestApp(VaporForwardedSlot)
  1221. const root = document.createElement('div')
  1222. createApp(App).use(vaporInteropPlugin).mount(root)
  1223. expect(root.innerHTML).toBe('<div>vdom fallback</div>')
  1224. })
  1225. test('vdom slot > vapor forwarded slot > vdom forwarded slot > vdom slot', async () => {
  1226. const foo = ref('foo')
  1227. const show = ref(true)
  1228. const VdomSlot = createVdomSlot()
  1229. const VdomForwardedSlot = createVdomForwardedSlot(VdomSlot)
  1230. const VaporForwardedSlot = createVaporForwardedSlot(VdomForwardedSlot)
  1231. const App = createTestApp(VaporForwardedSlot, foo, show)
  1232. const root = document.createElement('div')
  1233. createApp(App).use(vaporInteropPlugin).mount(root)
  1234. expect(root.innerHTML).toBe('<span>foo</span>')
  1235. foo.value = 'bar'
  1236. await nextTick()
  1237. expect(root.innerHTML).toBe('<span>bar</span>')
  1238. show.value = false
  1239. await nextTick()
  1240. expect(root.innerHTML).toBe('<div>fallback</div>')
  1241. show.value = true
  1242. await nextTick()
  1243. expect(root.innerHTML).toBe('<span>bar</span>')
  1244. })
  1245. test('vdom slot > vapor forwarded slot(with fallback) > vdom forwarded slot > vdom slot', async () => {
  1246. const foo = ref('foo')
  1247. const show = ref(true)
  1248. const VdomSlot = createVdomSlot()
  1249. const VdomForwardedSlot = createVdomForwardedSlot(VdomSlot)
  1250. const VaporForwardedSlotWithFallback = createVaporForwardedSlot(
  1251. VdomForwardedSlot,
  1252. 'vapor fallback',
  1253. )
  1254. const App = createTestApp(VaporForwardedSlotWithFallback, foo, show)
  1255. const root = document.createElement('div')
  1256. createApp(App).use(vaporInteropPlugin).mount(root)
  1257. expect(root.innerHTML).toBe('<span>foo</span>')
  1258. foo.value = 'bar'
  1259. await nextTick()
  1260. expect(root.innerHTML).toBe('<span>bar</span>')
  1261. show.value = false
  1262. await nextTick()
  1263. expect(root.innerHTML).toBe('<div>vapor fallback</div>')
  1264. show.value = true
  1265. await nextTick()
  1266. expect(root.innerHTML).toBe('<span>bar</span>')
  1267. })
  1268. test('vdom slot > vapor forwarded slot > vdom forwarded slot(with fallback) > vdom slot', async () => {
  1269. const foo = ref('foo')
  1270. const show = ref(true)
  1271. const VdomSlot = createVdomSlot()
  1272. const VdomForwardedSlotWithFallback = createVdomForwardedSlot(
  1273. VdomSlot,
  1274. 'vdom fallback',
  1275. )
  1276. const VaporForwardedSlot = createVaporForwardedSlot(
  1277. VdomForwardedSlotWithFallback,
  1278. )
  1279. const App = createTestApp(VaporForwardedSlot, foo, show)
  1280. const root = document.createElement('div')
  1281. createApp(App).use(vaporInteropPlugin).mount(root)
  1282. expect(root.innerHTML).toBe('<span>foo</span>')
  1283. foo.value = 'bar'
  1284. await nextTick()
  1285. expect(root.innerHTML).toBe('<span>bar</span>')
  1286. show.value = false
  1287. await nextTick()
  1288. expect(root.innerHTML).toBe('<div>vdom fallback</div>')
  1289. show.value = true
  1290. await nextTick()
  1291. expect(root.innerHTML).toBe('<span>bar</span>')
  1292. })
  1293. test('vdom slot > vapor forwarded slot (multiple) > vdom forwarded slot > vdom slot', async () => {
  1294. const foo = ref('foo')
  1295. const show = ref(true)
  1296. const VdomSlot = createVdomSlot()
  1297. const VdomForwardedSlot = createVdomForwardedSlot(VdomSlot)
  1298. const VaporForwardedSlot = createMultipleVaporForwardedSlots(
  1299. VdomForwardedSlot,
  1300. 3,
  1301. )
  1302. const App = createTestApp(VaporForwardedSlot, foo, show)
  1303. const root = document.createElement('div')
  1304. createApp(App).use(vaporInteropPlugin).mount(root)
  1305. expect(root.innerHTML).toBe('<span>foo</span><!--slot--><!--slot-->')
  1306. foo.value = 'bar'
  1307. await nextTick()
  1308. expect(root.innerHTML).toBe('<span>bar</span><!--slot--><!--slot-->')
  1309. show.value = false
  1310. await nextTick()
  1311. expect(root.innerHTML).toBe('<div>fallback</div><!--slot--><!--slot-->')
  1312. show.value = true
  1313. await nextTick()
  1314. expect(root.innerHTML).toBe('<span>bar</span><!--slot--><!--slot-->')
  1315. })
  1316. test('vdom slot > vapor forwarded slot (multiple) > vdom forwarded slot(with fallback) > vdom slot', async () => {
  1317. const foo = ref('foo')
  1318. const show = ref(true)
  1319. const VdomSlot = createVdomSlot()
  1320. const VdomForwardedSlotWithFallback = createVdomForwardedSlot(
  1321. VdomSlot,
  1322. 'vdom fallback',
  1323. )
  1324. const VaporForwardedSlot = createMultipleVaporForwardedSlots(
  1325. VdomForwardedSlotWithFallback,
  1326. 3,
  1327. )
  1328. const App = createTestApp(VaporForwardedSlot, foo, show)
  1329. const root = document.createElement('div')
  1330. createApp(App).use(vaporInteropPlugin).mount(root)
  1331. expect(root.innerHTML).toBe('<span>foo</span><!--slot--><!--slot-->')
  1332. foo.value = 'bar'
  1333. await nextTick()
  1334. expect(root.innerHTML).toBe('<span>bar</span><!--slot--><!--slot-->')
  1335. show.value = false
  1336. await nextTick()
  1337. expect(root.innerHTML).toBe(
  1338. '<div>vdom fallback</div><!--slot--><!--slot-->',
  1339. )
  1340. show.value = true
  1341. await nextTick()
  1342. expect(root.innerHTML).toBe('<span>bar</span><!--slot--><!--slot-->')
  1343. })
  1344. test('vdom slot > vdom forwarded slot > vapor slot', async () => {
  1345. const foo = ref('foo')
  1346. const show = ref(true)
  1347. const VaporSlot = createVaporSlot()
  1348. const VdomForwardedSlot = createVdomForwardedSlot(VaporSlot)
  1349. const App = createTestApp(VdomForwardedSlot, foo, show)
  1350. const root = document.createElement('div')
  1351. createApp(App).use(vaporInteropPlugin).mount(root)
  1352. expect(root.innerHTML).toBe('<span>foo</span>')
  1353. foo.value = 'bar'
  1354. await nextTick()
  1355. expect(root.innerHTML).toBe('<span>bar</span>')
  1356. show.value = false
  1357. await nextTick()
  1358. expect(root.innerHTML).toBe('<div>fallback</div>')
  1359. show.value = true
  1360. await nextTick()
  1361. expect(root.innerHTML).toBe('<span>bar</span>')
  1362. })
  1363. test('vdom slot > vdom forwarded slot > vapor forwarded slot > vapor slot', async () => {
  1364. const foo = ref('foo')
  1365. const show = ref(true)
  1366. const VaporSlot = createVaporSlot()
  1367. const VaporForwardedSlot = createVaporForwardedSlot(VaporSlot)
  1368. const VdomForwardedSlot = createVdomForwardedSlot(VaporForwardedSlot)
  1369. const App = createTestApp(VdomForwardedSlot, foo, show)
  1370. const root = document.createElement('div')
  1371. createApp(App).use(vaporInteropPlugin).mount(root)
  1372. expect(root.innerHTML).toBe('<span>foo</span><!--slot-->')
  1373. foo.value = 'bar'
  1374. await nextTick()
  1375. expect(root.innerHTML).toBe('<span>bar</span><!--slot-->')
  1376. show.value = false
  1377. await nextTick()
  1378. expect(root.innerHTML).toBe('<div>fallback</div><!--slot-->')
  1379. show.value = true
  1380. await nextTick()
  1381. expect(root.innerHTML).toBe('<span>bar</span><!--slot-->')
  1382. })
  1383. test('vdom slot > vdom forwarded slot (multiple) > vapor forwarded slot > vdom slot', async () => {
  1384. const foo = ref('foo')
  1385. const show = ref(true)
  1386. const VaporSlot = createVaporSlot()
  1387. const VaporForwardedSlot = createVaporForwardedSlot(VaporSlot)
  1388. const VdomForwardedSlot = createMultipleVdomForwardedSlots(
  1389. VaporForwardedSlot,
  1390. 3,
  1391. )
  1392. const App = createTestApp(VdomForwardedSlot, foo, show)
  1393. const root = document.createElement('div')
  1394. createApp(App).use(vaporInteropPlugin).mount(root)
  1395. expect(root.innerHTML).toBe('<span>foo</span><!--slot-->')
  1396. foo.value = 'bar'
  1397. await nextTick()
  1398. expect(root.innerHTML).toBe('<span>bar</span><!--slot-->')
  1399. show.value = false
  1400. await nextTick()
  1401. expect(root.innerHTML).toBe('<div>fallback</div><!--slot-->')
  1402. show.value = true
  1403. await nextTick()
  1404. expect(root.innerHTML).toBe('<span>bar</span><!--slot-->')
  1405. })
  1406. test('vdom slot > vdom forwarded slot (multiple) > vapor forwarded slot(with fallback) > vdom slot', async () => {
  1407. const foo = ref('foo')
  1408. const show = ref(true)
  1409. const VaporSlot = createVaporSlot()
  1410. const VaporForwardedSlot = createVaporForwardedSlot(
  1411. VaporSlot,
  1412. 'vapor fallback',
  1413. )
  1414. const VdomForwardedSlot = createMultipleVdomForwardedSlots(
  1415. VaporForwardedSlot,
  1416. 3,
  1417. )
  1418. const App = createTestApp(VdomForwardedSlot, foo, show)
  1419. const root = document.createElement('div')
  1420. createApp(App).use(vaporInteropPlugin).mount(root)
  1421. expect(root.innerHTML).toBe('<span>foo</span><!--slot-->')
  1422. foo.value = 'bar'
  1423. await nextTick()
  1424. expect(root.innerHTML).toBe('<span>bar</span><!--slot-->')
  1425. show.value = false
  1426. await nextTick()
  1427. expect(root.innerHTML).toBe('<div>vapor fallback</div><!--slot-->')
  1428. show.value = true
  1429. await nextTick()
  1430. expect(root.innerHTML).toBe('<span>bar</span><!--slot-->')
  1431. })
  1432. test('vdom slot > vapor forwarded slot > vapor forwarded slot > vdom slot', async () => {
  1433. const foo = ref('foo')
  1434. const show = ref(true)
  1435. const VdomSlot = createVdomSlot()
  1436. const VaporForwardedSlot1 = createMultipleVaporForwardedSlots(
  1437. VdomSlot,
  1438. 2,
  1439. )
  1440. const App = createTestApp(VaporForwardedSlot1, foo, show)
  1441. const root = document.createElement('div')
  1442. createApp(App).use(vaporInteropPlugin).mount(root)
  1443. expect(root.innerHTML).toBe('<span>foo</span><!--slot-->')
  1444. foo.value = 'bar'
  1445. await nextTick()
  1446. expect(root.innerHTML).toBe('<span>bar</span><!--slot-->')
  1447. show.value = false
  1448. await nextTick()
  1449. expect(root.innerHTML).toBe('<div>fallback</div><!--slot-->')
  1450. show.value = true
  1451. await nextTick()
  1452. expect(root.innerHTML).toBe('<span>bar</span><!--slot-->')
  1453. })
  1454. test('vdom slot > vapor forwarded slot(with fallback) > vapor forwarded slot > vdom slot', async () => {
  1455. const foo = ref('foo')
  1456. const show = ref(true)
  1457. const VdomSlot = createVdomSlot()
  1458. const VaporForwardedSlot2 = createVaporForwardedSlot(VdomSlot)
  1459. const VaporForwardedSlot1WithFallback = createVaporForwardedSlot(
  1460. VaporForwardedSlot2,
  1461. 'vapor1 fallback',
  1462. )
  1463. const App = createTestApp(VaporForwardedSlot1WithFallback, foo, show)
  1464. const root = document.createElement('div')
  1465. createApp(App).use(vaporInteropPlugin).mount(root)
  1466. expect(root.innerHTML).toBe('<span>foo</span><!--slot-->')
  1467. foo.value = 'bar'
  1468. await nextTick()
  1469. expect(root.innerHTML).toBe('<span>bar</span><!--slot-->')
  1470. show.value = false
  1471. await nextTick()
  1472. expect(root.innerHTML).toBe('<div>vapor1 fallback</div><!--slot-->')
  1473. show.value = true
  1474. await nextTick()
  1475. expect(root.innerHTML).toBe('<span>bar</span><!--slot-->')
  1476. })
  1477. test('vdom slot > vapor forwarded slot > vapor forwarded slot(with fallback) > vdom slot', async () => {
  1478. const foo = ref('foo')
  1479. const show = ref(true)
  1480. const VdomSlot = createVdomSlot()
  1481. const VaporForwardedSlot2WithFallback = createVaporForwardedSlot(
  1482. VdomSlot,
  1483. 'vapor2 fallback',
  1484. )
  1485. const VaporForwardedSlot1 = createVaporForwardedSlot(
  1486. VaporForwardedSlot2WithFallback,
  1487. )
  1488. const App = createTestApp(VaporForwardedSlot1, foo, show)
  1489. const root = document.createElement('div')
  1490. createApp(App).use(vaporInteropPlugin).mount(root)
  1491. expect(root.innerHTML).toBe('<span>foo</span><!--slot-->')
  1492. foo.value = 'bar'
  1493. await nextTick()
  1494. expect(root.innerHTML).toBe('<span>bar</span><!--slot-->')
  1495. show.value = false
  1496. await nextTick()
  1497. expect(root.innerHTML).toBe('<div>vapor2 fallback</div><!--slot-->')
  1498. show.value = true
  1499. await nextTick()
  1500. expect(root.innerHTML).toBe('<span>bar</span><!--slot-->')
  1501. })
  1502. test('vdom slot > vapor forwarded slot > vapor forwarded slot > vapor slot', async () => {
  1503. const foo = ref('foo')
  1504. const show = ref(true)
  1505. const VaporSlot = createVaporSlot()
  1506. const VaporForwardedSlot2 = createVaporForwardedSlot(VaporSlot)
  1507. const VaporForwardedSlot1 =
  1508. createVaporForwardedSlot(VaporForwardedSlot2)
  1509. const App = createTestApp(VaporForwardedSlot1, foo, show)
  1510. const root = document.createElement('div')
  1511. createApp(App).use(vaporInteropPlugin).mount(root)
  1512. expect(root.innerHTML).toBe('<span>foo</span><!--slot--><!--slot-->')
  1513. foo.value = 'bar'
  1514. await nextTick()
  1515. expect(root.innerHTML).toBe('<span>bar</span><!--slot--><!--slot-->')
  1516. show.value = false
  1517. await nextTick()
  1518. expect(root.innerHTML).toBe('<div>fallback</div><!--slot--><!--slot-->')
  1519. show.value = true
  1520. await nextTick()
  1521. expect(root.innerHTML).toBe('<span>bar</span><!--slot--><!--slot-->')
  1522. })
  1523. test('vdom slot > vapor forwarded slot(with fallback) > vapor forwarded slot(with fallback) > vdom slot', async () => {
  1524. const foo = ref('foo')
  1525. const show = ref(true)
  1526. const VdomSlot = createVdomSlot()
  1527. const VaporForwardedSlot2WithFallback = createVaporForwardedSlot(
  1528. VdomSlot,
  1529. 'vapor2 fallback',
  1530. )
  1531. const VaporForwardedSlot1WithFallback = createVaporForwardedSlot(
  1532. VaporForwardedSlot2WithFallback,
  1533. 'vapor1 fallback',
  1534. )
  1535. const App = createTestApp(VaporForwardedSlot1WithFallback, foo, show)
  1536. const root = document.createElement('div')
  1537. createApp(App).use(vaporInteropPlugin).mount(root)
  1538. expect(root.innerHTML).toBe('<span>foo</span><!--slot-->')
  1539. foo.value = 'bar'
  1540. await nextTick()
  1541. expect(root.innerHTML).toBe('<span>bar</span><!--slot-->')
  1542. show.value = false
  1543. await nextTick()
  1544. expect(root.innerHTML).toBe('<div>vapor1 fallback</div><!--slot-->')
  1545. show.value = true
  1546. await nextTick()
  1547. expect(root.innerHTML).toBe('<span>bar</span><!--slot-->')
  1548. })
  1549. test('vdom slot > vapor forwarded slot(with fallback) > vapor forwarded slot(with fallback) > vapor slot', async () => {
  1550. const foo = ref('foo')
  1551. const show = ref(true)
  1552. const VaporSlot = createVaporSlot()
  1553. const VaporForwardedSlot2WithFallback = createVaporForwardedSlot(
  1554. VaporSlot,
  1555. 'vapor2 fallback',
  1556. )
  1557. const VaporForwardedSlot1WithFallback = createVaporForwardedSlot(
  1558. VaporForwardedSlot2WithFallback,
  1559. 'vapor1 fallback',
  1560. )
  1561. const App = createTestApp(VaporForwardedSlot1WithFallback, foo, show)
  1562. const root = document.createElement('div')
  1563. createApp(App).use(vaporInteropPlugin).mount(root)
  1564. expect(root.innerHTML).toBe('<span>foo</span><!--slot--><!--slot-->')
  1565. foo.value = 'bar'
  1566. await nextTick()
  1567. expect(root.innerHTML).toBe('<span>bar</span><!--slot--><!--slot-->')
  1568. show.value = false
  1569. await nextTick()
  1570. expect(root.innerHTML).toBe(
  1571. '<div>vapor1 fallback</div><!--slot--><!--slot-->',
  1572. )
  1573. show.value = true
  1574. await nextTick()
  1575. expect(root.innerHTML).toBe('<span>bar</span><!--slot--><!--slot-->')
  1576. })
  1577. test('vdom slot > vdom forwarded slot(with fallback) > vdom forwarded slot(with fallback) > vapor slot', async () => {
  1578. const foo = ref('foo')
  1579. const show = ref(true)
  1580. const VaporSlot = createVaporSlot()
  1581. const VdomForwardedSlot2WithFallback = createVdomForwardedSlot(
  1582. VaporSlot,
  1583. 'vdom2 fallback',
  1584. )
  1585. const VdomForwardedSlot1WithFallback = createVdomForwardedSlot(
  1586. VdomForwardedSlot2WithFallback,
  1587. 'vdom1 fallback',
  1588. )
  1589. const App = createTestApp(VdomForwardedSlot1WithFallback, foo, show)
  1590. const root = document.createElement('div')
  1591. createApp(App).use(vaporInteropPlugin).mount(root)
  1592. expect(root.innerHTML).toBe('<span>foo</span>')
  1593. foo.value = 'bar'
  1594. await nextTick()
  1595. expect(root.innerHTML).toBe('<span>bar</span>')
  1596. show.value = false
  1597. await nextTick()
  1598. expect(root.innerHTML).toBe('<div>vdom1 fallback</div>')
  1599. show.value = true
  1600. await nextTick()
  1601. expect(root.innerHTML).toBe('<span>bar</span>')
  1602. })
  1603. test('vdom slot > vdom forwarded slot(with fallback) > vdom forwarded slot(with fallback) > vdom slot', async () => {
  1604. const foo = ref('foo')
  1605. const show = ref(true)
  1606. const VdomSlot = createVdomSlot()
  1607. const VdomForwardedSlot2WithFallback = createVdomForwardedSlot(
  1608. VdomSlot,
  1609. 'vdom2 fallback',
  1610. )
  1611. const VdomForwardedSlot1WithFallback = createVdomForwardedSlot(
  1612. VdomForwardedSlot2WithFallback,
  1613. 'vdom1 fallback',
  1614. )
  1615. const App = createTestApp(VdomForwardedSlot1WithFallback, foo, show)
  1616. const root = document.createElement('div')
  1617. createApp(App).use(vaporInteropPlugin).mount(root)
  1618. expect(root.innerHTML).toBe('<span>foo</span>')
  1619. foo.value = 'bar'
  1620. await nextTick()
  1621. expect(root.innerHTML).toBe('<span>bar</span>')
  1622. show.value = false
  1623. await nextTick()
  1624. expect(root.innerHTML).toBe('<div>vdom1 fallback</div>')
  1625. show.value = true
  1626. await nextTick()
  1627. expect(root.innerHTML).toBe('<span>bar</span>')
  1628. })
  1629. test('vdom slot > vdom forwarded slot(with fallback) > vdom forwarded slot(with fallback) (multiple) > vapor slot', async () => {
  1630. const foo = ref('foo')
  1631. const show = ref(true)
  1632. const VaporSlot = createVaporSlot()
  1633. const VdomForwardedSlot3WithFallback = createVdomForwardedSlot(
  1634. VaporSlot,
  1635. 'vdom3 fallback',
  1636. )
  1637. const VdomForwardedSlot2WithFallback = createVdomForwardedSlot(
  1638. VdomForwardedSlot3WithFallback,
  1639. 'vdom2 fallback',
  1640. )
  1641. const VdomForwardedSlot1WithFallback = createVdomForwardedSlot(
  1642. VdomForwardedSlot2WithFallback,
  1643. 'vdom1 fallback',
  1644. )
  1645. const App = createTestApp(VdomForwardedSlot1WithFallback, foo, show)
  1646. const root = document.createElement('div')
  1647. createApp(App).use(vaporInteropPlugin).mount(root)
  1648. expect(root.innerHTML).toBe('<span>foo</span>')
  1649. foo.value = 'bar'
  1650. await nextTick()
  1651. expect(root.innerHTML).toBe('<span>bar</span>')
  1652. show.value = false
  1653. await nextTick()
  1654. expect(root.innerHTML).toBe('<div>vdom1 fallback</div>')
  1655. show.value = true
  1656. await nextTick()
  1657. expect(root.innerHTML).toBe('<span>bar</span>')
  1658. })
  1659. })
  1660. })
  1661. describe('createForSlots', () => {
  1662. test('should work', async () => {
  1663. const loop = ref([1, 2, 3])
  1664. let instance: any
  1665. const Child = () => {
  1666. instance = currentInstance
  1667. return template('child')()
  1668. }
  1669. const { render } = define({
  1670. setup() {
  1671. return createComponent(Child, null, {
  1672. $: [
  1673. () =>
  1674. createForSlots(loop.value, (item, i) => ({
  1675. name: item,
  1676. fn: () => template(item + i)(),
  1677. })),
  1678. ],
  1679. })
  1680. },
  1681. })
  1682. render()
  1683. expect(instance.slots).toHaveProperty('1')
  1684. expect(instance.slots).toHaveProperty('2')
  1685. expect(instance.slots).toHaveProperty('3')
  1686. loop.value.push(4)
  1687. await nextTick()
  1688. expect(instance.slots).toHaveProperty('4')
  1689. loop.value.shift()
  1690. await nextTick()
  1691. expect(instance.slots).not.toHaveProperty('1')
  1692. })
  1693. test('should cache dynamic slot source result', async () => {
  1694. const items = ref([1, 2, 3])
  1695. let callCount = 0
  1696. const getItems = () => {
  1697. callCount++
  1698. return items.value
  1699. }
  1700. let instance: any
  1701. const Child = defineVaporComponent(() => {
  1702. instance = currentInstance
  1703. // Create multiple slots to trigger multiple getSlot calls
  1704. const n1 = template('<div></div>')()
  1705. const n2 = template('<div></div>')()
  1706. const n3 = template('<div></div>')()
  1707. insert(createSlot('slot1'), n1 as any as ParentNode)
  1708. insert(createSlot('slot2'), n2 as any as ParentNode)
  1709. insert(createSlot('slot3'), n3 as any as ParentNode)
  1710. return [n1, n2, n3]
  1711. })
  1712. define({
  1713. setup() {
  1714. return createComponent(Child, null, {
  1715. $: [
  1716. () =>
  1717. createForSlots(getItems(), (item, i) => ({
  1718. name: 'slot' + item,
  1719. fn: () => template(String(item))(),
  1720. })),
  1721. ],
  1722. })
  1723. },
  1724. }).render()
  1725. // getItems should only be called once
  1726. expect(callCount).toBe(1)
  1727. expect(instance.slots).toHaveProperty('slot1')
  1728. expect(instance.slots).toHaveProperty('slot2')
  1729. expect(instance.slots).toHaveProperty('slot3')
  1730. })
  1731. test('should update when source changes', async () => {
  1732. const items = ref([1, 2])
  1733. let callCount = 0
  1734. const getItems = () => {
  1735. callCount++
  1736. return items.value
  1737. }
  1738. let instance: any
  1739. const Child = defineVaporComponent(() => {
  1740. instance = currentInstance
  1741. const n1 = template('<div></div>')()
  1742. const n2 = template('<div></div>')()
  1743. const n3 = template('<div></div>')()
  1744. insert(createSlot('slot1'), n1 as any as ParentNode)
  1745. insert(createSlot('slot2'), n2 as any as ParentNode)
  1746. insert(createSlot('slot3'), n3 as any as ParentNode)
  1747. return [n1, n2, n3]
  1748. })
  1749. define({
  1750. setup() {
  1751. return createComponent(Child, null, {
  1752. $: [
  1753. () =>
  1754. createForSlots(getItems(), (item, i) => ({
  1755. name: 'slot' + item,
  1756. fn: () => template(String(item))(),
  1757. })),
  1758. ],
  1759. })
  1760. },
  1761. }).render()
  1762. expect(callCount).toBe(1)
  1763. expect(instance.slots).toHaveProperty('slot1')
  1764. expect(instance.slots).toHaveProperty('slot2')
  1765. expect(instance.slots).not.toHaveProperty('slot3')
  1766. // Update items
  1767. items.value.push(3)
  1768. await nextTick()
  1769. // Should be called again after source changes
  1770. expect(callCount).toBe(2)
  1771. expect(instance.slots).toHaveProperty('slot1')
  1772. expect(instance.slots).toHaveProperty('slot2')
  1773. expect(instance.slots).toHaveProperty('slot3')
  1774. })
  1775. test('should render slots correctly with caching', async () => {
  1776. const items = ref([1, 2, 3, 4, 5])
  1777. const Child = defineVaporComponent(() => {
  1778. const containers: any[] = []
  1779. for (let i = 1; i <= 5; i++) {
  1780. const n = template('<div></div>')()
  1781. insert(createSlot('slot' + i), n as any as ParentNode)
  1782. containers.push(n)
  1783. }
  1784. return containers
  1785. })
  1786. const { host } = define({
  1787. setup() {
  1788. return createComponent(Child, null, {
  1789. $: [
  1790. () =>
  1791. createForSlots(items.value, item => ({
  1792. name: 'slot' + item,
  1793. fn: () => template('content' + item)(),
  1794. })),
  1795. ],
  1796. })
  1797. },
  1798. }).render()
  1799. expect(host.innerHTML).toBe(
  1800. '<div>content1<!--slot--></div>' +
  1801. '<div>content2<!--slot--></div>' +
  1802. '<div>content3<!--slot--></div>' +
  1803. '<div>content4<!--slot--></div>' +
  1804. '<div>content5<!--slot--></div>',
  1805. )
  1806. // Update items
  1807. items.value = [2, 4]
  1808. await nextTick()
  1809. expect(host.innerHTML).toBe(
  1810. '<div><!--slot--></div>' +
  1811. '<div>content2<!--slot--></div>' +
  1812. '<div><!--slot--></div>' +
  1813. '<div>content4<!--slot--></div>' +
  1814. '<div><!--slot--></div>',
  1815. )
  1816. })
  1817. })
  1818. })