apiSetupContext.spec.ts 5.1 KB

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