apiSetupContext.spec.ts 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210
  1. import {
  2. createComponent,
  3. createSlot,
  4. createTextNode,
  5. defineVaporComponent,
  6. delegate,
  7. delegateEvents,
  8. insert,
  9. renderEffect,
  10. setDynamicProps,
  11. template,
  12. } from '../src'
  13. import { nextTick, reactive, ref, watchEffect } from '@vue/runtime-dom'
  14. import { makeRender } from './_utils'
  15. const define = makeRender()
  16. describe('api: setup context', () => {
  17. it('should expose return values to template render context', () => {
  18. const { html } = define({
  19. setup() {
  20. return {
  21. ref: ref('foo'),
  22. object: reactive({ msg: 'bar' }),
  23. value: 'baz',
  24. }
  25. },
  26. render(ctx) {
  27. return createTextNode([`${ctx.ref} ${ctx.object.msg} ${ctx.value}`])
  28. },
  29. }).render()
  30. expect(html()).toMatch(`foo bar baz`)
  31. })
  32. it('should support returning render function', () => {
  33. const { html } = define({
  34. setup() {
  35. return createTextNode([`hello`])
  36. },
  37. }).render()
  38. expect(html()).toMatch(`hello`)
  39. })
  40. it('props', async () => {
  41. const count = ref(0)
  42. let dummy
  43. const Child = defineVaporComponent({
  44. props: { count: Number },
  45. setup(props) {
  46. watchEffect(() => {
  47. dummy = props.count
  48. })
  49. return createTextNode(() => [props.count])
  50. },
  51. })
  52. const { html } = define({
  53. render: () => createComponent(Child, { count: () => count.value }),
  54. }).render()
  55. expect(html()).toMatch(`0`)
  56. count.value++
  57. await nextTick()
  58. expect(dummy).toBe(1)
  59. expect(html()).toMatch(`1`)
  60. })
  61. it('context.attrs', async () => {
  62. const toggle = ref(true)
  63. const Child = defineVaporComponent({
  64. inheritAttrs: false,
  65. setup(_props, { attrs }) {
  66. const el = document.createElement('div')
  67. renderEffect(() => setDynamicProps(el, [attrs]))
  68. return el
  69. },
  70. })
  71. const { html } = define({
  72. render: () =>
  73. createComponent(Child, {
  74. $: [() => (toggle.value ? { id: 'foo' } : { class: 'baz' })],
  75. }),
  76. }).render()
  77. expect(html()).toMatch(`<div id="foo"></div>`)
  78. toggle.value = false
  79. await nextTick()
  80. expect(html()).toMatch(`<div class="baz"></div>`)
  81. })
  82. // #4161
  83. it('context.attrs in child component slots', async () => {
  84. const toggle = ref(true)
  85. const Wrapper = defineVaporComponent({
  86. setup(_) {
  87. const n0 = createSlot('default')
  88. return n0
  89. },
  90. })
  91. const Child = defineVaporComponent({
  92. inheritAttrs: false,
  93. setup(_: any, { attrs }: any) {
  94. const n0 = createComponent(Wrapper, null, {
  95. default: () => {
  96. const n0 = template('<div>')() as HTMLDivElement
  97. renderEffect(() => setDynamicProps(n0, [attrs]))
  98. return n0
  99. },
  100. })
  101. return n0
  102. },
  103. })
  104. const { html } = define({
  105. render: () =>
  106. createComponent(Child, {
  107. $: [() => (toggle.value ? { id: 'foo' } : { class: 'baz' })],
  108. }),
  109. }).render()
  110. expect(html()).toMatch(`<div id="foo"></div>`)
  111. // should update even though it's not reactive
  112. toggle.value = false
  113. await nextTick()
  114. expect(html()).toMatch(`<div class="baz"></div>`)
  115. })
  116. it('context.slots', async () => {
  117. const id = ref('foo')
  118. const Child = defineVaporComponent({
  119. render() {
  120. return [createSlot('foo'), createSlot('bar')]
  121. },
  122. })
  123. const { html } = define({
  124. render() {
  125. return createComponent(Child, null, {
  126. $: [
  127. () => ({
  128. name: 'foo',
  129. fn: () => createTextNode(() => [id.value]),
  130. }),
  131. () => ({
  132. name: 'bar',
  133. fn: () => createTextNode(['bar']),
  134. }),
  135. ],
  136. })
  137. },
  138. }).render()
  139. expect(html()).toMatch(`foo<!--slot-->bar<!--slot-->`)
  140. id.value = 'baz'
  141. await nextTick()
  142. expect(html()).toMatch(`baz<!--slot-->bar<!--slot-->`)
  143. })
  144. it('context.emit', async () => {
  145. const count = ref(0)
  146. const spy = vi.fn()
  147. delegateEvents('click')
  148. const Child = defineVaporComponent({
  149. props: {
  150. count: { type: Number, default: 1 },
  151. },
  152. setup(props, { emit }) {
  153. const n0 = template('<div>')() as HTMLDivElement
  154. delegate(n0, 'click', () => () => {
  155. emit('inc', props.count + 1)
  156. })
  157. insert(
  158. createTextNode(() => [props.count]),
  159. n0,
  160. )
  161. return n0
  162. },
  163. })
  164. const { host, html } = define({
  165. render: () =>
  166. createComponent(Child, {
  167. count: () => count.value,
  168. onInc: () => (newVal: number) => {
  169. spy()
  170. count.value = newVal
  171. },
  172. }),
  173. }).render()
  174. expect(html()).toMatch(`<div>0</div>`)
  175. ;(host.children[0] as HTMLDivElement).click()
  176. expect(spy).toHaveBeenCalled()
  177. await nextTick()
  178. expect(html()).toMatch(`<div>1</div>`)
  179. })
  180. })