componentSlots.spec.ts 65 KB

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