componentSlots.spec.ts 69 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407
  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('render fallback with invalid v-for branch', async () => {
  703. const Child = {
  704. setup() {
  705. return createSlot('default', null, () =>
  706. document.createTextNode('fallback'),
  707. )
  708. },
  709. }
  710. const items = ref([{ text: 'bar', show: false }])
  711. const { html } = define({
  712. setup() {
  713. return createComponent(Child, null, {
  714. default: () => {
  715. return createFor(
  716. () => items.value,
  717. for_item0 => {
  718. return createIf(
  719. () => for_item0.value.show,
  720. () => {
  721. const n5 = template('<span> </span>')() as any
  722. const x5 = child(n5) as any
  723. renderEffect(() =>
  724. setText(x5, toDisplayString(for_item0.value.text)),
  725. )
  726. return n5
  727. },
  728. )
  729. },
  730. item => item.text,
  731. )
  732. },
  733. })
  734. },
  735. }).render()
  736. expect(html()).toBe('fallback<!--if--><!--for--><!--slot-->')
  737. items.value[0].show = true
  738. await nextTick()
  739. expect(html()).toBe('<span>bar</span><!--if--><!--for--><!--slot-->')
  740. items.value[0].show = false
  741. await nextTick()
  742. expect(html()).toBe('fallback<!--if--><!--for--><!--slot-->')
  743. })
  744. test('should not render fallback for a single empty item in v-for', async () => {
  745. const Child = {
  746. setup() {
  747. return createSlot('default', null, () =>
  748. document.createTextNode('fallback'),
  749. )
  750. },
  751. }
  752. const items = ref([
  753. { text: 'bar', show: true },
  754. { text: 'baz', show: true },
  755. ])
  756. const { html } = define({
  757. setup() {
  758. return createComponent(Child, null, {
  759. default: () => {
  760. return createFor(
  761. () => items.value,
  762. for_item0 => {
  763. return createIf(
  764. () => for_item0.value.show,
  765. () => {
  766. const n5 = template('<span> </span>')() as any
  767. const x5 = child(n5) as any
  768. renderEffect(() =>
  769. setText(x5, toDisplayString(for_item0.value.text)),
  770. )
  771. return n5
  772. },
  773. )
  774. },
  775. item => item.text,
  776. )
  777. },
  778. })
  779. },
  780. }).render()
  781. expect(html()).toBe(
  782. '<span>bar</span><!--if--><span>baz</span><!--if--><!--for--><!--slot-->',
  783. )
  784. items.value[1].show = false
  785. await nextTick()
  786. expect(html()).toBe(
  787. '<span>bar</span><!--if--><!--if--><!--for--><!--slot-->',
  788. )
  789. })
  790. test('work with v-once', async () => {
  791. const Child = defineVaporComponent({
  792. setup() {
  793. return createSlot(
  794. 'default',
  795. null,
  796. undefined,
  797. undefined,
  798. true /* once */,
  799. )
  800. },
  801. })
  802. const count = ref(0)
  803. const { html } = define({
  804. setup() {
  805. return createComponent(Child, null, {
  806. default: () => {
  807. const n3 = template('<div> </div>')() as any
  808. const x3 = txt(n3) as any
  809. renderEffect(() => setText(x3, toDisplayString(count.value)))
  810. return n3
  811. },
  812. })
  813. },
  814. }).render()
  815. expect(html()).toBe('<div>0</div><!--slot-->')
  816. // expect no changes due to v-once
  817. count.value++
  818. await nextTick()
  819. expect(html()).toBe('<div>0</div><!--slot-->')
  820. })
  821. })
  822. describe('forwarded slot', () => {
  823. test('should work', async () => {
  824. const Child = defineVaporComponent({
  825. setup() {
  826. return createSlot('foo', null)
  827. },
  828. })
  829. const Parent = defineVaporComponent({
  830. setup() {
  831. const n2 = createComponent(
  832. Child,
  833. null,
  834. {
  835. foo: withVaporCtx(() => {
  836. return createSlot('foo', null)
  837. }),
  838. },
  839. true,
  840. )
  841. return n2
  842. },
  843. })
  844. const foo = ref('foo')
  845. const { host } = define({
  846. setup() {
  847. const n2 = createComponent(
  848. Parent,
  849. null,
  850. {
  851. foo: () => {
  852. const n0 = template(' ')() as any
  853. renderEffect(() => setText(n0, foo.value))
  854. return n0
  855. },
  856. },
  857. true,
  858. )
  859. return n2
  860. },
  861. }).render()
  862. expect(host.innerHTML).toBe('foo<!--slot--><!--slot-->')
  863. foo.value = 'bar'
  864. await nextTick()
  865. expect(host.innerHTML).toBe('bar<!--slot--><!--slot-->')
  866. })
  867. test('mixed with non-forwarded slot', async () => {
  868. const Child = defineVaporComponent({
  869. setup() {
  870. return [createSlot('foo', null)]
  871. },
  872. })
  873. const Parent = defineVaporComponent({
  874. setup() {
  875. const n2 = createComponent(Child, null, {
  876. foo: withVaporCtx(() => {
  877. const n0 = createSlot('foo', null)
  878. return n0
  879. }),
  880. })
  881. const n3 = createSlot('default', null)
  882. return [n2, n3]
  883. },
  884. })
  885. const foo = ref('foo')
  886. const { host } = define({
  887. setup() {
  888. const n2 = createComponent(
  889. Parent,
  890. null,
  891. {
  892. foo: () => {
  893. const n0 = template(' ')() as any
  894. renderEffect(() => setText(n0, foo.value))
  895. return n0
  896. },
  897. default: () => {
  898. const n3 = template(' ')() as any
  899. renderEffect(() => setText(n3, foo.value))
  900. return n3
  901. },
  902. },
  903. true,
  904. )
  905. return n2
  906. },
  907. }).render()
  908. expect(host.innerHTML).toBe('foo<!--slot--><!--slot-->foo<!--slot-->')
  909. foo.value = 'bar'
  910. await nextTick()
  911. expect(host.innerHTML).toBe('bar<!--slot--><!--slot-->bar<!--slot-->')
  912. })
  913. test('forwarded slot with fallback', async () => {
  914. const Child = defineVaporComponent({
  915. setup() {
  916. return createSlot('default', null, () => template('child fallback')())
  917. },
  918. })
  919. const Parent = defineVaporComponent({
  920. setup() {
  921. const n2 = createComponent(Child, null, {
  922. default: withVaporCtx(() => {
  923. const n0 = createSlot('default', null, () => {
  924. return template('<!-- <div></div> -->')()
  925. })
  926. return n0
  927. }),
  928. })
  929. return n2
  930. },
  931. })
  932. const { html } = define({
  933. setup() {
  934. return createComponent(Parent, null, {
  935. default: () => template('<!-- <div>App</div> -->')(),
  936. })
  937. },
  938. }).render()
  939. expect(html()).toBe('child fallback<!--slot--><!--slot-->')
  940. })
  941. test('named forwarded slot with v-if', async () => {
  942. const Child = defineVaporComponent({
  943. setup() {
  944. return createSlot('default', null)
  945. },
  946. })
  947. const Parent = defineVaporComponent({
  948. props: {
  949. show: Boolean,
  950. },
  951. setup(props) {
  952. const n6 = createComponent(
  953. Child,
  954. null,
  955. {
  956. default: withVaporCtx(() => {
  957. const n0 = createIf(
  958. () => props.show,
  959. () => {
  960. const n5 = template('<div></div>')() as any
  961. setInsertionState(n5, null, 0, true)
  962. createSlot('header', null, () => {
  963. const n4 = template('default header')()
  964. return n4
  965. })
  966. return n5
  967. },
  968. )
  969. return n0
  970. }),
  971. },
  972. true,
  973. )
  974. return n6
  975. },
  976. })
  977. const show = ref(false)
  978. const { html } = define({
  979. setup() {
  980. return createComponent(
  981. Parent,
  982. {
  983. show: () => show.value,
  984. },
  985. {
  986. header: () => template('custom header')(),
  987. },
  988. )
  989. },
  990. }).render()
  991. expect(html()).toBe('<!--if--><!--slot-->')
  992. show.value = true
  993. await nextTick()
  994. expect(html()).toBe(
  995. '<div>custom header<!--slot--></div><!--if--><!--slot-->',
  996. )
  997. show.value = false
  998. await nextTick()
  999. expect(html()).toBe('<!--if--><!--slot-->')
  1000. })
  1001. test('forwarded slot with fallback (v-if)', async () => {
  1002. const Child = defineVaporComponent({
  1003. setup() {
  1004. return createSlot('default', null, () => template('child fallback')())
  1005. },
  1006. })
  1007. const show = ref(false)
  1008. const Parent = defineVaporComponent({
  1009. setup() {
  1010. const n2 = createComponent(Child, null, {
  1011. default: withVaporCtx(() => {
  1012. const n0 = createSlot('default', null, () => {
  1013. const n2 = createIf(
  1014. () => show.value,
  1015. () => {
  1016. const n4 = template('<div>if content</div>')()
  1017. return n4
  1018. },
  1019. )
  1020. return n2
  1021. })
  1022. return n0
  1023. }),
  1024. })
  1025. return n2
  1026. },
  1027. })
  1028. const { html } = define({
  1029. setup() {
  1030. return createComponent(Parent, null, {
  1031. default: () => template('<!-- <div>App</div> -->')(),
  1032. })
  1033. },
  1034. }).render()
  1035. expect(html()).toBe('child fallback<!--if--><!--slot--><!--slot-->')
  1036. show.value = true
  1037. await nextTick()
  1038. expect(html()).toBe(
  1039. '<div>if content</div><!--if--><!--slot--><!--slot-->',
  1040. )
  1041. })
  1042. test('forwarded slot with fallback (v-for)', async () => {
  1043. const Child = defineVaporComponent({
  1044. setup() {
  1045. return createSlot('default', null, () => template('child fallback')())
  1046. },
  1047. })
  1048. const items = ref<number[]>([])
  1049. const Parent = defineVaporComponent({
  1050. setup() {
  1051. const n2 = createComponent(Child, null, {
  1052. default: withVaporCtx(() => {
  1053. const n0 = createSlot('default', null, () => {
  1054. const n2 = createFor(
  1055. () => items.value,
  1056. for_item0 => {
  1057. const n4 = template('<span> </span>')() as any
  1058. const x4 = child(n4) as any
  1059. renderEffect(() =>
  1060. setText(x4, toDisplayString(for_item0.value)),
  1061. )
  1062. return n4
  1063. },
  1064. )
  1065. return n2
  1066. })
  1067. return n0
  1068. }),
  1069. })
  1070. return n2
  1071. },
  1072. })
  1073. const { html } = define({
  1074. setup() {
  1075. return createComponent(Parent, null, {
  1076. default: () => template('<!-- <div>App</div> -->')(),
  1077. })
  1078. },
  1079. }).render()
  1080. expect(html()).toBe('child fallback<!--for--><!--slot--><!--slot-->')
  1081. items.value.push(1)
  1082. await nextTick()
  1083. expect(html()).toBe('<span>1</span><!--for--><!--slot--><!--slot-->')
  1084. items.value.pop()
  1085. await nextTick()
  1086. expect(html()).toBe('child fallback<!--for--><!--slot--><!--slot-->')
  1087. })
  1088. test('consecutive slots with insertion state', async () => {
  1089. const { component: Child } = define({
  1090. setup() {
  1091. const n2 = template('<div><div>baz</div></div>', true)() as any
  1092. setInsertionState(n2, 0)
  1093. createSlot('default', null)
  1094. setInsertionState(n2, 0)
  1095. createSlot('foo', null)
  1096. return n2
  1097. },
  1098. })
  1099. const { html } = define({
  1100. setup() {
  1101. return createComponent(Child, null, {
  1102. default: () => template('default')(),
  1103. foo: () => template('foo')(),
  1104. })
  1105. },
  1106. }).render()
  1107. expect(html()).toBe(
  1108. `<div>` +
  1109. `default<!--slot-->` +
  1110. `foo<!--slot-->` +
  1111. `<div>baz</div>` +
  1112. `</div>`,
  1113. )
  1114. })
  1115. describe('vdom interop', () => {
  1116. const createVaporSlot = (fallbackText = 'fallback') => {
  1117. return defineVaporComponent({
  1118. setup() {
  1119. const n0 = createSlot('foo', null, () => {
  1120. const n2 = template(`<div>${fallbackText}</div>`)()
  1121. return n2
  1122. })
  1123. return n0
  1124. },
  1125. })
  1126. }
  1127. const createVdomSlot = (fallbackText = 'fallback') => {
  1128. return {
  1129. render(this: any) {
  1130. return renderSlot(this.$slots, 'foo', {}, () => [
  1131. h('div', fallbackText),
  1132. ])
  1133. },
  1134. }
  1135. }
  1136. const createVaporForwardedSlot = (
  1137. targetComponent: any,
  1138. fallbackText?: string,
  1139. ) => {
  1140. return defineVaporComponent({
  1141. setup() {
  1142. const n2 = createComponent(
  1143. targetComponent,
  1144. null,
  1145. {
  1146. foo: withVaporCtx(() => {
  1147. return fallbackText
  1148. ? createSlot('foo', null, () => {
  1149. const n2 = template(`<div>${fallbackText}</div>`)()
  1150. return n2
  1151. })
  1152. : createSlot('foo', null)
  1153. }),
  1154. },
  1155. true,
  1156. )
  1157. return n2
  1158. },
  1159. })
  1160. }
  1161. const createVdomForwardedSlot = (
  1162. targetComponent: any,
  1163. fallbackText?: string,
  1164. ) => {
  1165. return {
  1166. render(this: any) {
  1167. return h(targetComponent, null, {
  1168. foo: () => [
  1169. fallbackText
  1170. ? renderSlot(this.$slots, 'foo', {}, () => [
  1171. h('div', fallbackText),
  1172. ])
  1173. : renderSlot(this.$slots, 'foo'),
  1174. ],
  1175. _: 3 /* FORWARDED */,
  1176. })
  1177. },
  1178. }
  1179. }
  1180. const createMultipleVaporForwardedSlots = (
  1181. targetComponent: any,
  1182. count: number,
  1183. ) => {
  1184. let current = targetComponent
  1185. for (let i = 0; i < count; i++) {
  1186. current = createVaporForwardedSlot(current)
  1187. }
  1188. return current
  1189. }
  1190. const createMultipleVdomForwardedSlots = (
  1191. targetComponent: any,
  1192. count: number,
  1193. ) => {
  1194. let current = targetComponent
  1195. for (let i = 0; i < count; i++) {
  1196. current = createVdomForwardedSlot(current)
  1197. }
  1198. return current
  1199. }
  1200. const createTestApp = (
  1201. rootComponent: any,
  1202. foo: Ref<string>,
  1203. show: Ref<boolean>,
  1204. ) => {
  1205. return {
  1206. setup() {
  1207. return () =>
  1208. h(
  1209. rootComponent,
  1210. null,
  1211. createSlots({ _: 2 /* DYNAMIC */ } as any, [
  1212. show.value
  1213. ? {
  1214. name: 'foo',
  1215. fn: () => [h('span', foo.value)],
  1216. key: '0',
  1217. }
  1218. : undefined,
  1219. ]),
  1220. )
  1221. },
  1222. }
  1223. }
  1224. const createEmptyTestApp = (rootComponent: any) => {
  1225. return {
  1226. setup() {
  1227. return () => h(rootComponent)
  1228. },
  1229. }
  1230. }
  1231. test('vdom slot > vapor forwarded slot > vapor slot', async () => {
  1232. const foo = ref('foo')
  1233. const show = ref(true)
  1234. const VaporSlot = createVaporSlot()
  1235. const VaporForwardedSlot = createVaporForwardedSlot(VaporSlot)
  1236. const App = createTestApp(VaporForwardedSlot, foo, show)
  1237. const root = document.createElement('div')
  1238. createApp(App).use(vaporInteropPlugin).mount(root)
  1239. expect(root.innerHTML).toBe('<span>foo</span><!--slot-->')
  1240. foo.value = 'bar'
  1241. await nextTick()
  1242. expect(root.innerHTML).toBe('<span>bar</span><!--slot-->')
  1243. show.value = false
  1244. await nextTick()
  1245. expect(root.innerHTML).toBe('<div>fallback</div><!--slot-->')
  1246. })
  1247. test('vdom slot > vapor forwarded slot(with fallback) > vapor slot', async () => {
  1248. const foo = ref('foo')
  1249. const show = ref(true)
  1250. const VaporSlot = createVaporSlot()
  1251. const VaporForwardedSlotWithFallback = createVaporForwardedSlot(
  1252. VaporSlot,
  1253. 'forwarded fallback',
  1254. )
  1255. const App = createTestApp(VaporForwardedSlotWithFallback, foo, show)
  1256. const root = document.createElement('div')
  1257. createApp(App).use(vaporInteropPlugin).mount(root)
  1258. expect(root.innerHTML).toBe('<span>foo</span><!--slot-->')
  1259. foo.value = 'bar'
  1260. await nextTick()
  1261. expect(root.innerHTML).toBe('<span>bar</span><!--slot-->')
  1262. show.value = false
  1263. await nextTick()
  1264. expect(root.innerHTML).toBe('<div>forwarded fallback</div><!--slot-->')
  1265. })
  1266. test('vdom slot > vapor forwarded slot > vdom slot', async () => {
  1267. const foo = ref('foo')
  1268. const show = ref(true)
  1269. const VdomSlot = createVdomSlot()
  1270. const VaporForwardedSlot = createVaporForwardedSlot(VdomSlot)
  1271. const App = createTestApp(VaporForwardedSlot, foo, show)
  1272. const root = document.createElement('div')
  1273. createApp(App).use(vaporInteropPlugin).mount(root)
  1274. expect(root.innerHTML).toBe('<span>foo</span>')
  1275. foo.value = 'bar'
  1276. await nextTick()
  1277. expect(root.innerHTML).toBe('<span>bar</span>')
  1278. show.value = false
  1279. await nextTick()
  1280. expect(root.innerHTML).toBe('<div>fallback</div>')
  1281. show.value = true
  1282. await nextTick()
  1283. expect(root.innerHTML).toBe('<span>bar</span>')
  1284. })
  1285. test('vdom slot > vapor forwarded slot(with fallback) > vdom slot', async () => {
  1286. const foo = ref('foo')
  1287. const show = ref(true)
  1288. const VdomSlot = createVdomSlot()
  1289. const VaporForwardedSlotWithFallback = createVaporForwardedSlot(
  1290. VdomSlot,
  1291. 'forwarded fallback',
  1292. )
  1293. const App = createTestApp(VaporForwardedSlotWithFallback, foo, show)
  1294. const root = document.createElement('div')
  1295. createApp(App).use(vaporInteropPlugin).mount(root)
  1296. expect(root.innerHTML).toBe('<span>foo</span>')
  1297. foo.value = 'bar'
  1298. await nextTick()
  1299. expect(root.innerHTML).toBe('<span>bar</span>')
  1300. show.value = false
  1301. await nextTick()
  1302. expect(root.innerHTML).toBe('<div>forwarded fallback</div>')
  1303. })
  1304. test('vdom slot > vapor forwarded slot > vdom forwarded slot > vapor slot', async () => {
  1305. const foo = ref('foo')
  1306. const show = ref(true)
  1307. const VaporSlot = createVaporSlot()
  1308. const VdomForwardedSlot = createVdomForwardedSlot(VaporSlot)
  1309. const VaporForwardedSlot = createVaporForwardedSlot(VdomForwardedSlot)
  1310. const App = createTestApp(VaporForwardedSlot, foo, show)
  1311. const root = document.createElement('div')
  1312. createApp(App).use(vaporInteropPlugin).mount(root)
  1313. expect(root.innerHTML).toBe('<span>foo</span>')
  1314. foo.value = 'bar'
  1315. await nextTick()
  1316. expect(root.innerHTML).toBe('<span>bar</span>')
  1317. show.value = false
  1318. await nextTick()
  1319. expect(root.innerHTML).toBe('<div>fallback</div>')
  1320. show.value = true
  1321. await nextTick()
  1322. expect(root.innerHTML).toBe('<span>bar</span>')
  1323. })
  1324. test('vdom slot > vapor forwarded slot(with fallback) > vdom forwarded slot > vapor slot', async () => {
  1325. const foo = ref('foo')
  1326. const show = ref(true)
  1327. const VaporSlot = createVaporSlot()
  1328. const VdomForwardedSlot = createVdomForwardedSlot(VaporSlot)
  1329. const VaporForwardedSlotWithFallback = createVaporForwardedSlot(
  1330. VdomForwardedSlot,
  1331. 'forwarded fallback',
  1332. )
  1333. const App = createTestApp(VaporForwardedSlotWithFallback, foo, show)
  1334. const root = document.createElement('div')
  1335. createApp(App).use(vaporInteropPlugin).mount(root)
  1336. expect(root.innerHTML).toBe('<span>foo</span>')
  1337. foo.value = 'bar'
  1338. await nextTick()
  1339. expect(root.innerHTML).toBe('<span>bar</span>')
  1340. show.value = false
  1341. await nextTick()
  1342. expect(root.innerHTML).toBe('<div>forwarded fallback</div>')
  1343. show.value = true
  1344. await nextTick()
  1345. expect(root.innerHTML).toBe('<span>bar</span>')
  1346. })
  1347. test('vdom slot > vapor forwarded slot > vdom forwarded slot(with fallback) > vapor slot', async () => {
  1348. const foo = ref('foo')
  1349. const show = ref(true)
  1350. const VaporSlot = createVaporSlot()
  1351. const VdomForwardedSlotWithFallback = createVdomForwardedSlot(
  1352. VaporSlot,
  1353. 'vdom fallback',
  1354. )
  1355. const VaporForwardedSlot = createVaporForwardedSlot(
  1356. VdomForwardedSlotWithFallback,
  1357. )
  1358. const App = createTestApp(VaporForwardedSlot, foo, show)
  1359. const root = document.createElement('div')
  1360. createApp(App).use(vaporInteropPlugin).mount(root)
  1361. expect(root.innerHTML).toBe('<span>foo</span>')
  1362. foo.value = 'bar'
  1363. await nextTick()
  1364. expect(root.innerHTML).toBe('<span>bar</span>')
  1365. show.value = false
  1366. await nextTick()
  1367. expect(root.innerHTML).toBe('<div>vdom fallback</div>')
  1368. show.value = true
  1369. await nextTick()
  1370. expect(root.innerHTML).toBe('<span>bar</span>')
  1371. })
  1372. test('vdom slot(empty) > vapor forwarded slot > vdom forwarded slot(with fallback) > vapor slot', async () => {
  1373. const VaporSlot = createVaporSlot()
  1374. const VdomForwardedSlotWithFallback = createVdomForwardedSlot(
  1375. VaporSlot,
  1376. 'vdom fallback',
  1377. )
  1378. const VaporForwardedSlot = createVaporForwardedSlot(
  1379. VdomForwardedSlotWithFallback,
  1380. )
  1381. const App = createEmptyTestApp(VaporForwardedSlot)
  1382. const root = document.createElement('div')
  1383. createApp(App).use(vaporInteropPlugin).mount(root)
  1384. expect(root.innerHTML).toBe('<div>vdom fallback</div>')
  1385. })
  1386. test('vdom slot > vapor forwarded slot > vdom forwarded slot > vdom slot', async () => {
  1387. const foo = ref('foo')
  1388. const show = ref(true)
  1389. const VdomSlot = createVdomSlot()
  1390. const VdomForwardedSlot = createVdomForwardedSlot(VdomSlot)
  1391. const VaporForwardedSlot = createVaporForwardedSlot(VdomForwardedSlot)
  1392. const App = createTestApp(VaporForwardedSlot, foo, show)
  1393. const root = document.createElement('div')
  1394. createApp(App).use(vaporInteropPlugin).mount(root)
  1395. expect(root.innerHTML).toBe('<span>foo</span>')
  1396. foo.value = 'bar'
  1397. await nextTick()
  1398. expect(root.innerHTML).toBe('<span>bar</span>')
  1399. show.value = false
  1400. await nextTick()
  1401. expect(root.innerHTML).toBe('<div>fallback</div>')
  1402. show.value = true
  1403. await nextTick()
  1404. expect(root.innerHTML).toBe('<span>bar</span>')
  1405. })
  1406. test('vdom slot > vapor forwarded slot(with fallback) > vdom forwarded slot > vdom slot', async () => {
  1407. const foo = ref('foo')
  1408. const show = ref(true)
  1409. const VdomSlot = createVdomSlot()
  1410. const VdomForwardedSlot = createVdomForwardedSlot(VdomSlot)
  1411. const VaporForwardedSlotWithFallback = createVaporForwardedSlot(
  1412. VdomForwardedSlot,
  1413. 'vapor fallback',
  1414. )
  1415. const App = createTestApp(VaporForwardedSlotWithFallback, foo, show)
  1416. const root = document.createElement('div')
  1417. createApp(App).use(vaporInteropPlugin).mount(root)
  1418. expect(root.innerHTML).toBe('<span>foo</span>')
  1419. foo.value = 'bar'
  1420. await nextTick()
  1421. expect(root.innerHTML).toBe('<span>bar</span>')
  1422. show.value = false
  1423. await nextTick()
  1424. expect(root.innerHTML).toBe('<div>vapor fallback</div>')
  1425. show.value = true
  1426. await nextTick()
  1427. expect(root.innerHTML).toBe('<span>bar</span>')
  1428. })
  1429. test('vdom slot > vapor forwarded slot > vdom forwarded slot(with fallback) > vdom slot', async () => {
  1430. const foo = ref('foo')
  1431. const show = ref(true)
  1432. const VdomSlot = createVdomSlot()
  1433. const VdomForwardedSlotWithFallback = createVdomForwardedSlot(
  1434. VdomSlot,
  1435. 'vdom fallback',
  1436. )
  1437. const VaporForwardedSlot = createVaporForwardedSlot(
  1438. VdomForwardedSlotWithFallback,
  1439. )
  1440. const App = createTestApp(VaporForwardedSlot, foo, show)
  1441. const root = document.createElement('div')
  1442. createApp(App).use(vaporInteropPlugin).mount(root)
  1443. expect(root.innerHTML).toBe('<span>foo</span>')
  1444. foo.value = 'bar'
  1445. await nextTick()
  1446. expect(root.innerHTML).toBe('<span>bar</span>')
  1447. show.value = false
  1448. await nextTick()
  1449. expect(root.innerHTML).toBe('<div>vdom fallback</div>')
  1450. show.value = true
  1451. await nextTick()
  1452. expect(root.innerHTML).toBe('<span>bar</span>')
  1453. })
  1454. test('vdom slot > vapor forwarded slot (multiple) > vdom forwarded slot > vdom slot', async () => {
  1455. const foo = ref('foo')
  1456. const show = ref(true)
  1457. const VdomSlot = createVdomSlot()
  1458. const VdomForwardedSlot = createVdomForwardedSlot(VdomSlot)
  1459. const VaporForwardedSlot = createMultipleVaporForwardedSlots(
  1460. VdomForwardedSlot,
  1461. 3,
  1462. )
  1463. const App = createTestApp(VaporForwardedSlot, foo, show)
  1464. const root = document.createElement('div')
  1465. createApp(App).use(vaporInteropPlugin).mount(root)
  1466. expect(root.innerHTML).toBe('<span>foo</span><!--slot--><!--slot-->')
  1467. foo.value = 'bar'
  1468. await nextTick()
  1469. expect(root.innerHTML).toBe('<span>bar</span><!--slot--><!--slot-->')
  1470. show.value = false
  1471. await nextTick()
  1472. expect(root.innerHTML).toBe('<div>fallback</div><!--slot--><!--slot-->')
  1473. show.value = true
  1474. await nextTick()
  1475. expect(root.innerHTML).toBe('<span>bar</span><!--slot--><!--slot-->')
  1476. })
  1477. test('vdom slot > vapor forwarded slot (multiple) > vdom forwarded slot(with fallback) > vdom slot', async () => {
  1478. const foo = ref('foo')
  1479. const show = ref(true)
  1480. const VdomSlot = createVdomSlot()
  1481. const VdomForwardedSlotWithFallback = createVdomForwardedSlot(
  1482. VdomSlot,
  1483. 'vdom fallback',
  1484. )
  1485. const VaporForwardedSlot = createMultipleVaporForwardedSlots(
  1486. VdomForwardedSlotWithFallback,
  1487. 3,
  1488. )
  1489. const App = createTestApp(VaporForwardedSlot, foo, show)
  1490. const root = document.createElement('div')
  1491. createApp(App).use(vaporInteropPlugin).mount(root)
  1492. expect(root.innerHTML).toBe('<span>foo</span><!--slot--><!--slot-->')
  1493. foo.value = 'bar'
  1494. await nextTick()
  1495. expect(root.innerHTML).toBe('<span>bar</span><!--slot--><!--slot-->')
  1496. show.value = false
  1497. await nextTick()
  1498. expect(root.innerHTML).toBe(
  1499. '<div>vdom fallback</div><!--slot--><!--slot-->',
  1500. )
  1501. show.value = true
  1502. await nextTick()
  1503. expect(root.innerHTML).toBe('<span>bar</span><!--slot--><!--slot-->')
  1504. })
  1505. test('vdom slot > vdom forwarded slot > vapor slot', async () => {
  1506. const foo = ref('foo')
  1507. const show = ref(true)
  1508. const VaporSlot = createVaporSlot()
  1509. const VdomForwardedSlot = createVdomForwardedSlot(VaporSlot)
  1510. const App = createTestApp(VdomForwardedSlot, foo, show)
  1511. const root = document.createElement('div')
  1512. createApp(App).use(vaporInteropPlugin).mount(root)
  1513. expect(root.innerHTML).toBe('<span>foo</span>')
  1514. foo.value = 'bar'
  1515. await nextTick()
  1516. expect(root.innerHTML).toBe('<span>bar</span>')
  1517. show.value = false
  1518. await nextTick()
  1519. expect(root.innerHTML).toBe('<div>fallback</div>')
  1520. show.value = true
  1521. await nextTick()
  1522. expect(root.innerHTML).toBe('<span>bar</span>')
  1523. })
  1524. test('vdom slot > vdom forwarded slot > vapor forwarded slot > vapor slot', async () => {
  1525. const foo = ref('foo')
  1526. const show = ref(true)
  1527. const VaporSlot = createVaporSlot()
  1528. const VaporForwardedSlot = createVaporForwardedSlot(VaporSlot)
  1529. const VdomForwardedSlot = createVdomForwardedSlot(VaporForwardedSlot)
  1530. const App = createTestApp(VdomForwardedSlot, foo, show)
  1531. const root = document.createElement('div')
  1532. createApp(App).use(vaporInteropPlugin).mount(root)
  1533. expect(root.innerHTML).toBe('<span>foo</span><!--slot-->')
  1534. foo.value = 'bar'
  1535. await nextTick()
  1536. expect(root.innerHTML).toBe('<span>bar</span><!--slot-->')
  1537. show.value = false
  1538. await nextTick()
  1539. expect(root.innerHTML).toBe('<div>fallback</div><!--slot-->')
  1540. show.value = true
  1541. await nextTick()
  1542. expect(root.innerHTML).toBe('<span>bar</span><!--slot-->')
  1543. })
  1544. test('vdom slot > vdom forwarded slot (multiple) > vapor forwarded slot > vdom slot', async () => {
  1545. const foo = ref('foo')
  1546. const show = ref(true)
  1547. const VaporSlot = createVaporSlot()
  1548. const VaporForwardedSlot = createVaporForwardedSlot(VaporSlot)
  1549. const VdomForwardedSlot = createMultipleVdomForwardedSlots(
  1550. VaporForwardedSlot,
  1551. 3,
  1552. )
  1553. const App = createTestApp(VdomForwardedSlot, foo, show)
  1554. const root = document.createElement('div')
  1555. createApp(App).use(vaporInteropPlugin).mount(root)
  1556. expect(root.innerHTML).toBe('<span>foo</span><!--slot-->')
  1557. foo.value = 'bar'
  1558. await nextTick()
  1559. expect(root.innerHTML).toBe('<span>bar</span><!--slot-->')
  1560. show.value = false
  1561. await nextTick()
  1562. expect(root.innerHTML).toBe('<div>fallback</div><!--slot-->')
  1563. show.value = true
  1564. await nextTick()
  1565. expect(root.innerHTML).toBe('<span>bar</span><!--slot-->')
  1566. })
  1567. test('vdom slot > vdom forwarded slot (multiple) > vapor forwarded slot(with fallback) > vdom slot', async () => {
  1568. const foo = ref('foo')
  1569. const show = ref(true)
  1570. const VaporSlot = createVaporSlot()
  1571. const VaporForwardedSlot = createVaporForwardedSlot(
  1572. VaporSlot,
  1573. 'vapor fallback',
  1574. )
  1575. const VdomForwardedSlot = createMultipleVdomForwardedSlots(
  1576. VaporForwardedSlot,
  1577. 3,
  1578. )
  1579. const App = createTestApp(VdomForwardedSlot, foo, show)
  1580. const root = document.createElement('div')
  1581. createApp(App).use(vaporInteropPlugin).mount(root)
  1582. expect(root.innerHTML).toBe('<span>foo</span><!--slot-->')
  1583. foo.value = 'bar'
  1584. await nextTick()
  1585. expect(root.innerHTML).toBe('<span>bar</span><!--slot-->')
  1586. show.value = false
  1587. await nextTick()
  1588. expect(root.innerHTML).toBe('<div>vapor fallback</div><!--slot-->')
  1589. show.value = true
  1590. await nextTick()
  1591. expect(root.innerHTML).toBe('<span>bar</span><!--slot-->')
  1592. })
  1593. test('vdom slot > vapor forwarded slot > vapor forwarded slot > vdom slot', async () => {
  1594. const foo = ref('foo')
  1595. const show = ref(true)
  1596. const VdomSlot = createVdomSlot()
  1597. const VaporForwardedSlot1 = createMultipleVaporForwardedSlots(
  1598. VdomSlot,
  1599. 2,
  1600. )
  1601. const App = createTestApp(VaporForwardedSlot1, foo, show)
  1602. const root = document.createElement('div')
  1603. createApp(App).use(vaporInteropPlugin).mount(root)
  1604. expect(root.innerHTML).toBe('<span>foo</span><!--slot-->')
  1605. foo.value = 'bar'
  1606. await nextTick()
  1607. expect(root.innerHTML).toBe('<span>bar</span><!--slot-->')
  1608. show.value = false
  1609. await nextTick()
  1610. expect(root.innerHTML).toBe('<div>fallback</div><!--slot-->')
  1611. show.value = true
  1612. await nextTick()
  1613. expect(root.innerHTML).toBe('<span>bar</span><!--slot-->')
  1614. })
  1615. test('vdom slot > vapor forwarded slot(with fallback) > vapor forwarded slot > vdom slot', async () => {
  1616. const foo = ref('foo')
  1617. const show = ref(true)
  1618. const VdomSlot = createVdomSlot()
  1619. const VaporForwardedSlot2 = createVaporForwardedSlot(VdomSlot)
  1620. const VaporForwardedSlot1WithFallback = createVaporForwardedSlot(
  1621. VaporForwardedSlot2,
  1622. 'vapor1 fallback',
  1623. )
  1624. const App = createTestApp(VaporForwardedSlot1WithFallback, foo, show)
  1625. const root = document.createElement('div')
  1626. createApp(App).use(vaporInteropPlugin).mount(root)
  1627. expect(root.innerHTML).toBe('<span>foo</span><!--slot-->')
  1628. foo.value = 'bar'
  1629. await nextTick()
  1630. expect(root.innerHTML).toBe('<span>bar</span><!--slot-->')
  1631. show.value = false
  1632. await nextTick()
  1633. expect(root.innerHTML).toBe('<div>vapor1 fallback</div><!--slot-->')
  1634. show.value = true
  1635. await nextTick()
  1636. expect(root.innerHTML).toBe('<span>bar</span><!--slot-->')
  1637. })
  1638. test('vdom slot > vapor forwarded slot > vapor forwarded slot(with fallback) > vdom slot', async () => {
  1639. const foo = ref('foo')
  1640. const show = ref(true)
  1641. const VdomSlot = createVdomSlot()
  1642. const VaporForwardedSlot2WithFallback = createVaporForwardedSlot(
  1643. VdomSlot,
  1644. 'vapor2 fallback',
  1645. )
  1646. const VaporForwardedSlot1 = createVaporForwardedSlot(
  1647. VaporForwardedSlot2WithFallback,
  1648. )
  1649. const App = createTestApp(VaporForwardedSlot1, foo, show)
  1650. const root = document.createElement('div')
  1651. createApp(App).use(vaporInteropPlugin).mount(root)
  1652. expect(root.innerHTML).toBe('<span>foo</span><!--slot-->')
  1653. foo.value = 'bar'
  1654. await nextTick()
  1655. expect(root.innerHTML).toBe('<span>bar</span><!--slot-->')
  1656. show.value = false
  1657. await nextTick()
  1658. expect(root.innerHTML).toBe('<div>vapor2 fallback</div><!--slot-->')
  1659. show.value = true
  1660. await nextTick()
  1661. expect(root.innerHTML).toBe('<span>bar</span><!--slot-->')
  1662. })
  1663. test('vdom slot > vapor forwarded slot > vapor forwarded slot > vapor slot', async () => {
  1664. const foo = ref('foo')
  1665. const show = ref(true)
  1666. const VaporSlot = createVaporSlot()
  1667. const VaporForwardedSlot2 = createVaporForwardedSlot(VaporSlot)
  1668. const VaporForwardedSlot1 =
  1669. createVaporForwardedSlot(VaporForwardedSlot2)
  1670. const App = createTestApp(VaporForwardedSlot1, foo, show)
  1671. const root = document.createElement('div')
  1672. createApp(App).use(vaporInteropPlugin).mount(root)
  1673. expect(root.innerHTML).toBe('<span>foo</span><!--slot--><!--slot-->')
  1674. foo.value = 'bar'
  1675. await nextTick()
  1676. expect(root.innerHTML).toBe('<span>bar</span><!--slot--><!--slot-->')
  1677. show.value = false
  1678. await nextTick()
  1679. expect(root.innerHTML).toBe('<div>fallback</div><!--slot--><!--slot-->')
  1680. show.value = true
  1681. await nextTick()
  1682. expect(root.innerHTML).toBe('<span>bar</span><!--slot--><!--slot-->')
  1683. })
  1684. test('vdom slot > vapor forwarded slot(with fallback) > vapor forwarded slot(with fallback) > vdom slot', async () => {
  1685. const foo = ref('foo')
  1686. const show = ref(true)
  1687. const VdomSlot = createVdomSlot()
  1688. const VaporForwardedSlot2WithFallback = createVaporForwardedSlot(
  1689. VdomSlot,
  1690. 'vapor2 fallback',
  1691. )
  1692. const VaporForwardedSlot1WithFallback = createVaporForwardedSlot(
  1693. VaporForwardedSlot2WithFallback,
  1694. 'vapor1 fallback',
  1695. )
  1696. const App = createTestApp(VaporForwardedSlot1WithFallback, foo, show)
  1697. const root = document.createElement('div')
  1698. createApp(App).use(vaporInteropPlugin).mount(root)
  1699. expect(root.innerHTML).toBe('<span>foo</span><!--slot-->')
  1700. foo.value = 'bar'
  1701. await nextTick()
  1702. expect(root.innerHTML).toBe('<span>bar</span><!--slot-->')
  1703. show.value = false
  1704. await nextTick()
  1705. expect(root.innerHTML).toBe('<div>vapor1 fallback</div><!--slot-->')
  1706. show.value = true
  1707. await nextTick()
  1708. expect(root.innerHTML).toBe('<span>bar</span><!--slot-->')
  1709. })
  1710. test('vdom slot > vapor forwarded slot(with fallback) > vapor forwarded slot(with fallback) > vapor slot', async () => {
  1711. const foo = ref('foo')
  1712. const show = ref(true)
  1713. const VaporSlot = createVaporSlot()
  1714. const VaporForwardedSlot2WithFallback = createVaporForwardedSlot(
  1715. VaporSlot,
  1716. 'vapor2 fallback',
  1717. )
  1718. const VaporForwardedSlot1WithFallback = createVaporForwardedSlot(
  1719. VaporForwardedSlot2WithFallback,
  1720. 'vapor1 fallback',
  1721. )
  1722. const App = createTestApp(VaporForwardedSlot1WithFallback, foo, show)
  1723. const root = document.createElement('div')
  1724. createApp(App).use(vaporInteropPlugin).mount(root)
  1725. expect(root.innerHTML).toBe('<span>foo</span><!--slot--><!--slot-->')
  1726. foo.value = 'bar'
  1727. await nextTick()
  1728. expect(root.innerHTML).toBe('<span>bar</span><!--slot--><!--slot-->')
  1729. show.value = false
  1730. await nextTick()
  1731. expect(root.innerHTML).toBe(
  1732. '<div>vapor1 fallback</div><!--slot--><!--slot-->',
  1733. )
  1734. show.value = true
  1735. await nextTick()
  1736. expect(root.innerHTML).toBe('<span>bar</span><!--slot--><!--slot-->')
  1737. })
  1738. test('vdom slot > vdom forwarded slot(with fallback) > vdom forwarded slot(with fallback) > vapor slot', async () => {
  1739. const foo = ref('foo')
  1740. const show = ref(true)
  1741. const VaporSlot = createVaporSlot()
  1742. const VdomForwardedSlot2WithFallback = createVdomForwardedSlot(
  1743. VaporSlot,
  1744. 'vdom2 fallback',
  1745. )
  1746. const VdomForwardedSlot1WithFallback = createVdomForwardedSlot(
  1747. VdomForwardedSlot2WithFallback,
  1748. 'vdom1 fallback',
  1749. )
  1750. const App = createTestApp(VdomForwardedSlot1WithFallback, foo, show)
  1751. const root = document.createElement('div')
  1752. createApp(App).use(vaporInteropPlugin).mount(root)
  1753. expect(root.innerHTML).toBe('<span>foo</span>')
  1754. foo.value = 'bar'
  1755. await nextTick()
  1756. expect(root.innerHTML).toBe('<span>bar</span>')
  1757. show.value = false
  1758. await nextTick()
  1759. expect(root.innerHTML).toBe('<div>vdom1 fallback</div>')
  1760. show.value = true
  1761. await nextTick()
  1762. expect(root.innerHTML).toBe('<span>bar</span>')
  1763. })
  1764. test('vdom slot > vdom forwarded slot(with fallback) > vdom forwarded slot(with fallback) > vdom slot', async () => {
  1765. const foo = ref('foo')
  1766. const show = ref(true)
  1767. const VdomSlot = createVdomSlot()
  1768. const VdomForwardedSlot2WithFallback = createVdomForwardedSlot(
  1769. VdomSlot,
  1770. 'vdom2 fallback',
  1771. )
  1772. const VdomForwardedSlot1WithFallback = createVdomForwardedSlot(
  1773. VdomForwardedSlot2WithFallback,
  1774. 'vdom1 fallback',
  1775. )
  1776. const App = createTestApp(VdomForwardedSlot1WithFallback, foo, show)
  1777. const root = document.createElement('div')
  1778. createApp(App).use(vaporInteropPlugin).mount(root)
  1779. expect(root.innerHTML).toBe('<span>foo</span>')
  1780. foo.value = 'bar'
  1781. await nextTick()
  1782. expect(root.innerHTML).toBe('<span>bar</span>')
  1783. show.value = false
  1784. await nextTick()
  1785. expect(root.innerHTML).toBe('<div>vdom1 fallback</div>')
  1786. show.value = true
  1787. await nextTick()
  1788. expect(root.innerHTML).toBe('<span>bar</span>')
  1789. })
  1790. test('vdom slot > vdom forwarded slot(with fallback) > vdom forwarded slot(with fallback) (multiple) > vapor slot', async () => {
  1791. const foo = ref('foo')
  1792. const show = ref(true)
  1793. const VaporSlot = createVaporSlot()
  1794. const VdomForwardedSlot3WithFallback = createVdomForwardedSlot(
  1795. VaporSlot,
  1796. 'vdom3 fallback',
  1797. )
  1798. const VdomForwardedSlot2WithFallback = createVdomForwardedSlot(
  1799. VdomForwardedSlot3WithFallback,
  1800. 'vdom2 fallback',
  1801. )
  1802. const VdomForwardedSlot1WithFallback = createVdomForwardedSlot(
  1803. VdomForwardedSlot2WithFallback,
  1804. 'vdom1 fallback',
  1805. )
  1806. const App = createTestApp(VdomForwardedSlot1WithFallback, foo, show)
  1807. const root = document.createElement('div')
  1808. createApp(App).use(vaporInteropPlugin).mount(root)
  1809. expect(root.innerHTML).toBe('<span>foo</span>')
  1810. foo.value = 'bar'
  1811. await nextTick()
  1812. expect(root.innerHTML).toBe('<span>bar</span>')
  1813. show.value = false
  1814. await nextTick()
  1815. expect(root.innerHTML).toBe('<div>vdom1 fallback</div>')
  1816. show.value = true
  1817. await nextTick()
  1818. expect(root.innerHTML).toBe('<span>bar</span>')
  1819. })
  1820. })
  1821. })
  1822. describe('createForSlots', () => {
  1823. test('should work', async () => {
  1824. const loop = ref([1, 2, 3])
  1825. let instance: any
  1826. const Child = () => {
  1827. instance = currentInstance
  1828. return template('child')()
  1829. }
  1830. const { render } = define({
  1831. setup() {
  1832. return createComponent(Child, null, {
  1833. $: [
  1834. () =>
  1835. createForSlots(loop.value, (item, i) => ({
  1836. name: item,
  1837. fn: () => template(item + i)(),
  1838. })),
  1839. ],
  1840. })
  1841. },
  1842. })
  1843. render()
  1844. expect(instance.slots).toHaveProperty('1')
  1845. expect(instance.slots).toHaveProperty('2')
  1846. expect(instance.slots).toHaveProperty('3')
  1847. loop.value.push(4)
  1848. await nextTick()
  1849. expect(instance.slots).toHaveProperty('4')
  1850. loop.value.shift()
  1851. await nextTick()
  1852. expect(instance.slots).not.toHaveProperty('1')
  1853. })
  1854. test('should cache dynamic slot source result', async () => {
  1855. const items = ref([1, 2, 3])
  1856. let callCount = 0
  1857. const getItems = () => {
  1858. callCount++
  1859. return items.value
  1860. }
  1861. let instance: any
  1862. const Child = defineVaporComponent(() => {
  1863. instance = currentInstance
  1864. // Create multiple slots to trigger multiple getSlot calls
  1865. const n1 = template('<div></div>')()
  1866. const n2 = template('<div></div>')()
  1867. const n3 = template('<div></div>')()
  1868. insert(createSlot('slot1'), n1 as any as ParentNode)
  1869. insert(createSlot('slot2'), n2 as any as ParentNode)
  1870. insert(createSlot('slot3'), n3 as any as ParentNode)
  1871. return [n1, n2, n3]
  1872. })
  1873. define({
  1874. setup() {
  1875. return createComponent(Child, null, {
  1876. $: [
  1877. () =>
  1878. createForSlots(getItems(), (item, i) => ({
  1879. name: 'slot' + item,
  1880. fn: () => template(String(item))(),
  1881. })),
  1882. ],
  1883. })
  1884. },
  1885. }).render()
  1886. // getItems should only be called once
  1887. expect(callCount).toBe(1)
  1888. expect(instance.slots).toHaveProperty('slot1')
  1889. expect(instance.slots).toHaveProperty('slot2')
  1890. expect(instance.slots).toHaveProperty('slot3')
  1891. })
  1892. test('should update when source changes', async () => {
  1893. const items = ref([1, 2])
  1894. let callCount = 0
  1895. const getItems = () => {
  1896. callCount++
  1897. return items.value
  1898. }
  1899. let instance: any
  1900. const Child = defineVaporComponent(() => {
  1901. instance = currentInstance
  1902. const n1 = template('<div></div>')()
  1903. const n2 = template('<div></div>')()
  1904. const n3 = template('<div></div>')()
  1905. insert(createSlot('slot1'), n1 as any as ParentNode)
  1906. insert(createSlot('slot2'), n2 as any as ParentNode)
  1907. insert(createSlot('slot3'), n3 as any as ParentNode)
  1908. return [n1, n2, n3]
  1909. })
  1910. define({
  1911. setup() {
  1912. return createComponent(Child, null, {
  1913. $: [
  1914. () =>
  1915. createForSlots(getItems(), (item, i) => ({
  1916. name: 'slot' + item,
  1917. fn: () => template(String(item))(),
  1918. })),
  1919. ],
  1920. })
  1921. },
  1922. }).render()
  1923. expect(callCount).toBe(1)
  1924. expect(instance.slots).toHaveProperty('slot1')
  1925. expect(instance.slots).toHaveProperty('slot2')
  1926. expect(instance.slots).not.toHaveProperty('slot3')
  1927. // Update items
  1928. items.value.push(3)
  1929. await nextTick()
  1930. // Should be called again after source changes
  1931. expect(callCount).toBe(2)
  1932. expect(instance.slots).toHaveProperty('slot1')
  1933. expect(instance.slots).toHaveProperty('slot2')
  1934. expect(instance.slots).toHaveProperty('slot3')
  1935. })
  1936. test('should render slots correctly with caching', async () => {
  1937. const items = ref([1, 2, 3, 4, 5])
  1938. const Child = defineVaporComponent(() => {
  1939. const containers: any[] = []
  1940. for (let i = 1; i <= 5; i++) {
  1941. const n = template('<div></div>')()
  1942. insert(createSlot('slot' + i), n as any as ParentNode)
  1943. containers.push(n)
  1944. }
  1945. return containers
  1946. })
  1947. const { host } = define({
  1948. setup() {
  1949. return createComponent(Child, null, {
  1950. $: [
  1951. () =>
  1952. createForSlots(items.value, item => ({
  1953. name: 'slot' + item,
  1954. fn: () => template('content' + item)(),
  1955. })),
  1956. ],
  1957. })
  1958. },
  1959. }).render()
  1960. expect(host.innerHTML).toBe(
  1961. '<div>content1<!--slot--></div>' +
  1962. '<div>content2<!--slot--></div>' +
  1963. '<div>content3<!--slot--></div>' +
  1964. '<div>content4<!--slot--></div>' +
  1965. '<div>content5<!--slot--></div>',
  1966. )
  1967. // Update items
  1968. items.value = [2, 4]
  1969. await nextTick()
  1970. expect(host.innerHTML).toBe(
  1971. '<div><!--slot--></div>' +
  1972. '<div>content2<!--slot--></div>' +
  1973. '<div><!--slot--></div>' +
  1974. '<div>content4<!--slot--></div>' +
  1975. '<div><!--slot--></div>',
  1976. )
  1977. })
  1978. // #14648
  1979. test('should use last slot when v-for generates duplicate slot names', async () => {
  1980. const list = ref([0, 1, 2])
  1981. const Child = defineVaporComponent(() => {
  1982. const n = template('<div></div>')()
  1983. insert(createSlot('default'), n as any as ParentNode)
  1984. return n
  1985. })
  1986. const { host } = define({
  1987. setup() {
  1988. return createComponent(Child, null, {
  1989. $: [
  1990. () =>
  1991. createForSlots(list.value, item => ({
  1992. name: 'default',
  1993. fn: () => template(String(item))(),
  1994. })),
  1995. ],
  1996. })
  1997. },
  1998. }).render()
  1999. // should display the last item (last wins, matching vDOM behavior)
  2000. expect(host.innerHTML).toBe('<div>2<!--slot--></div>')
  2001. // push: new last item should be displayed
  2002. list.value.push(3)
  2003. await nextTick()
  2004. expect(host.innerHTML).toBe('<div>3<!--slot--></div>')
  2005. // pop: should fall back to previous last item
  2006. list.value.pop()
  2007. await nextTick()
  2008. expect(host.innerHTML).toBe('<div>2<!--slot--></div>')
  2009. // splice middle: last item unchanged
  2010. list.value.splice(1, 1)
  2011. await nextTick()
  2012. expect(host.innerHTML).toBe('<div>2<!--slot--></div>')
  2013. })
  2014. test('should work with null and undefined', async () => {
  2015. const loop = ref<number[] | null | undefined>(undefined)
  2016. let instance: any
  2017. const Child = () => {
  2018. instance = currentInstance
  2019. return template('child')()
  2020. }
  2021. const { render } = define({
  2022. setup() {
  2023. return createComponent(Child, null, {
  2024. $: [
  2025. () =>
  2026. createForSlots(loop.value as any, (item, i) => ({
  2027. name: item,
  2028. fn: () => template(item + i)(),
  2029. })),
  2030. ],
  2031. })
  2032. },
  2033. })
  2034. render()
  2035. expect(instance.slots).toEqual({})
  2036. loop.value = [1]
  2037. await nextTick()
  2038. expect(instance.slots).toHaveProperty('1')
  2039. loop.value = null
  2040. await nextTick()
  2041. expect(instance.slots).toEqual({})
  2042. })
  2043. })
  2044. })