apiSetupContext.spec.ts 5.1 KB

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