componentSlots.spec.ts 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422
  1. // NOTE: This test is implemented based on the case of `runtime-core/__test__/componentSlots.spec.ts`.
  2. import {
  3. createComponent,
  4. createForSlots,
  5. createSlot,
  6. createVaporApp,
  7. defineVaporComponent,
  8. insert,
  9. prepend,
  10. renderEffect,
  11. setText,
  12. template,
  13. } from '../src'
  14. import { currentInstance, nextTick, ref } from '@vue/runtime-dom'
  15. import { makeRender } from './_utils'
  16. const define = makeRender<any>()
  17. function renderWithSlots(slots: any): any {
  18. let instance: any
  19. const Comp = defineVaporComponent({
  20. setup() {
  21. const t0 = template('<div></div>')
  22. const n0 = t0()
  23. instance = currentInstance
  24. return n0
  25. },
  26. })
  27. const { render } = define({
  28. render() {
  29. return createComponent(Comp, {}, slots)
  30. },
  31. })
  32. render()
  33. return instance
  34. }
  35. describe('component: slots', () => {
  36. test('initSlots: instance.slots should be set correctly', () => {
  37. const { slots } = renderWithSlots({
  38. default: () => template('<span></span>')(),
  39. })
  40. expect(slots.default()).toMatchObject(document.createElement('span'))
  41. })
  42. test('updateSlots: instance.slots should be updated correctly', async () => {
  43. const flag1 = ref(true)
  44. let instance: any
  45. const Child = () => {
  46. instance = currentInstance
  47. return template('child')()
  48. }
  49. const { render } = define({
  50. render() {
  51. return createComponent(
  52. Child,
  53. {},
  54. {
  55. $: [
  56. () =>
  57. flag1.value
  58. ? { name: 'one', fn: () => template('<span></span>')() }
  59. : { name: 'two', fn: () => template('<div></div>')() },
  60. ],
  61. },
  62. )
  63. },
  64. })
  65. render()
  66. expect(instance.slots).toHaveProperty('one')
  67. expect(instance.slots).not.toHaveProperty('two')
  68. flag1.value = false
  69. await nextTick()
  70. expect(instance.slots).not.toHaveProperty('one')
  71. expect(instance.slots).toHaveProperty('two')
  72. })
  73. test.todo('should work with createFlorSlots', async () => {
  74. const loop = ref([1, 2, 3])
  75. let instance: any
  76. const Child = () => {
  77. instance = currentInstance
  78. return template('child')()
  79. }
  80. const { render } = define({
  81. setup() {
  82. return createComponent(Child, null, {
  83. $: [
  84. () =>
  85. // @ts-expect-error
  86. createForSlots(loop.value, (item, i) => ({
  87. name: item,
  88. fn: () => template(item + i)(),
  89. })),
  90. ],
  91. })
  92. },
  93. })
  94. render()
  95. expect(instance.slots).toHaveProperty('1')
  96. expect(instance.slots).toHaveProperty('2')
  97. expect(instance.slots).toHaveProperty('3')
  98. loop.value.push(4)
  99. await nextTick()
  100. expect(instance.slots).toHaveProperty('4')
  101. loop.value.shift()
  102. await nextTick()
  103. expect(instance.slots).not.toHaveProperty('1')
  104. })
  105. // passes but no warning for slot invocation in vapor currently
  106. test.todo('should not warn when mounting another app in setup', () => {
  107. const Comp = defineVaporComponent({
  108. setup(_, { slots }) {
  109. return slots.default!()
  110. },
  111. })
  112. const mountComp = () => {
  113. createVaporApp({
  114. render() {
  115. return createComponent(
  116. Comp,
  117. {},
  118. { default: () => template('msg')() },
  119. )!
  120. },
  121. })
  122. }
  123. const App = {
  124. setup() {
  125. mountComp()
  126. return []
  127. },
  128. }
  129. createVaporApp(App).mount(document.createElement('div'))
  130. expect(
  131. 'Slot "default" invoked outside of the render function',
  132. ).not.toHaveBeenWarned()
  133. })
  134. describe('createSlot', () => {
  135. test('slot should be rendered correctly', () => {
  136. const Comp = defineVaporComponent(() => {
  137. const n0 = template('<div>')()
  138. insert(createSlot('header'), n0 as any as ParentNode)
  139. return n0
  140. })
  141. const { host } = define(() => {
  142. return createComponent(Comp, null, {
  143. header: () => template('header')(),
  144. })
  145. }).render()
  146. expect(host.innerHTML).toBe('<div>header<!--slot--></div>')
  147. })
  148. test('slot should be rendered correctly with slot props', async () => {
  149. const Comp = defineVaporComponent(() => {
  150. const n0 = template('<div></div>')()
  151. insert(
  152. createSlot('header', { title: () => 'header' }),
  153. n0 as any as ParentNode,
  154. )
  155. return n0
  156. })
  157. const { host } = define(() => {
  158. return createComponent(Comp, null, {
  159. header: props => {
  160. const el = template('<h1></h1>')()
  161. renderEffect(() => {
  162. setText(el, props.title)
  163. })
  164. return el
  165. },
  166. })
  167. }).render()
  168. expect(host.innerHTML).toBe('<div><h1>header</h1><!--slot--></div>')
  169. })
  170. test('dynamic slot props', async () => {
  171. let props: any
  172. const bindObj = ref<Record<string, any>>({ foo: 1, baz: 'qux' })
  173. const Comp = defineVaporComponent(() =>
  174. createSlot('default', { $: [() => bindObj.value] }),
  175. )
  176. define(() =>
  177. createComponent(Comp, null, {
  178. default: _props => ((props = _props), []),
  179. }),
  180. ).render()
  181. expect(props).toEqual({ foo: 1, baz: 'qux' })
  182. bindObj.value.foo = 2
  183. await nextTick()
  184. expect(props).toEqual({ foo: 2, baz: 'qux' })
  185. delete bindObj.value.baz
  186. await nextTick()
  187. expect(props).toEqual({ foo: 2 })
  188. })
  189. test('dynamic slot props with static slot props', async () => {
  190. let props: any
  191. const foo = ref(0)
  192. const bindObj = ref<Record<string, any>>({ foo: 100, baz: 'qux' })
  193. const Comp = defineVaporComponent(() =>
  194. createSlot('default', {
  195. foo: () => foo.value,
  196. $: [() => bindObj.value],
  197. }),
  198. )
  199. define(() =>
  200. createComponent(Comp, null, {
  201. default: _props => ((props = _props), []),
  202. }),
  203. ).render()
  204. expect(props).toEqual({ foo: 100, baz: 'qux' })
  205. foo.value = 2
  206. await nextTick()
  207. expect(props).toEqual({ foo: 100, baz: 'qux' })
  208. delete bindObj.value.foo
  209. await nextTick()
  210. expect(props).toEqual({ foo: 2, baz: 'qux' })
  211. })
  212. test('dynamic slot should be rendered correctly with slot props', async () => {
  213. const val = ref('header')
  214. const Comp = defineVaporComponent(() => {
  215. const n0 = template('<div></div>')()
  216. prepend(
  217. n0 as any as ParentNode,
  218. createSlot('header', { title: () => val.value }),
  219. )
  220. return n0
  221. })
  222. const { host } = define(() => {
  223. // dynamic slot
  224. return createComponent(Comp, null, {
  225. $: [
  226. () => ({
  227. name: 'header',
  228. fn: (props: any) => template(props.title)(),
  229. }),
  230. ],
  231. })
  232. }).render()
  233. expect(host.innerHTML).toBe('<div>header<!--slot--></div>')
  234. val.value = 'footer'
  235. await nextTick()
  236. expect(host.innerHTML).toBe('<div>footer<!--slot--></div>')
  237. })
  238. test('dynamic slot outlet should be render correctly with slot props', async () => {
  239. const val = ref('header')
  240. const Comp = defineVaporComponent(() => {
  241. const n0 = template('<div></div>')()
  242. prepend(
  243. n0 as any as ParentNode,
  244. createSlot(
  245. () => val.value, // dynamic slot outlet name
  246. ),
  247. )
  248. return n0
  249. })
  250. const { host } = define(() => {
  251. return createComponent(Comp, null, {
  252. header: () => template('header')(),
  253. footer: () => template('footer')(),
  254. })
  255. }).render()
  256. expect(host.innerHTML).toBe('<div>header<!--slot--></div>')
  257. val.value = 'footer'
  258. await nextTick()
  259. expect(host.innerHTML).toBe('<div>footer<!--slot--></div>')
  260. })
  261. test('fallback should be render correctly', () => {
  262. const Comp = defineVaporComponent(() => {
  263. const n0 = template('<div></div>')()
  264. insert(
  265. createSlot('header', undefined, () => template('fallback')()),
  266. n0 as any as ParentNode,
  267. )
  268. return n0
  269. })
  270. const { host } = define(() => {
  271. return createComponent(Comp, {}, {})
  272. }).render()
  273. expect(host.innerHTML).toBe('<div>fallback<!--slot--></div>')
  274. })
  275. test('dynamic slot should be updated correctly', async () => {
  276. const flag1 = ref(true)
  277. const Child = defineVaporComponent(() => {
  278. const temp0 = template('<p></p>')
  279. const el0 = temp0()
  280. const el1 = temp0()
  281. const slot1 = createSlot('one', null, () => template('one fallback')())
  282. const slot2 = createSlot('two', null, () => template('two fallback')())
  283. insert(slot1, el0 as any as ParentNode)
  284. insert(slot2, el1 as any as ParentNode)
  285. return [el0, el1]
  286. })
  287. const { host } = define(() => {
  288. return createComponent(Child, null, {
  289. $: [
  290. () =>
  291. flag1.value
  292. ? { name: 'one', fn: () => template('one content')() }
  293. : { name: 'two', fn: () => template('two content')() },
  294. ],
  295. })
  296. }).render()
  297. expect(host.innerHTML).toBe(
  298. '<p>one content<!--slot--></p><p>two fallback<!--slot--></p>',
  299. )
  300. flag1.value = false
  301. await nextTick()
  302. expect(host.innerHTML).toBe(
  303. '<p>one fallback<!--slot--></p><p>two content<!--slot--></p>',
  304. )
  305. flag1.value = true
  306. await nextTick()
  307. expect(host.innerHTML).toBe(
  308. '<p>one content<!--slot--></p><p>two fallback<!--slot--></p>',
  309. )
  310. })
  311. test('dynamic slot outlet should be updated correctly', async () => {
  312. const slotOutletName = ref('one')
  313. const Child = defineVaporComponent(() => {
  314. const temp0 = template('<p>')
  315. const el0 = temp0()
  316. const slot1 = createSlot(
  317. () => slotOutletName.value,
  318. undefined,
  319. () => template('fallback')(),
  320. )
  321. insert(slot1, el0 as any as ParentNode)
  322. return el0
  323. })
  324. const { host } = define(() => {
  325. return createComponent(
  326. Child,
  327. {},
  328. {
  329. one: () => template('one content')(),
  330. two: () => template('two content')(),
  331. },
  332. )
  333. }).render()
  334. expect(host.innerHTML).toBe('<p>one content<!--slot--></p>')
  335. slotOutletName.value = 'two'
  336. await nextTick()
  337. expect(host.innerHTML).toBe('<p>two content<!--slot--></p>')
  338. slotOutletName.value = 'none'
  339. await nextTick()
  340. expect(host.innerHTML).toBe('<p>fallback<!--slot--></p>')
  341. })
  342. test('non-exist slot', async () => {
  343. const Child = defineVaporComponent(() => {
  344. const el0 = template('<p>')()
  345. const slot = createSlot('not-exist', undefined)
  346. insert(slot, el0 as any as ParentNode)
  347. return el0
  348. })
  349. const { host } = define(() => {
  350. return createComponent(Child)
  351. }).render()
  352. expect(host.innerHTML).toBe('<p><!--slot--></p>')
  353. })
  354. })
  355. })