apiSetupContext.spec.ts 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216
  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. let prev: any
  68. renderEffect(() => {
  69. prev = setDynamicProps(el, [attrs], prev, true)
  70. })
  71. return el
  72. },
  73. })
  74. const { html } = define({
  75. render: () =>
  76. createComponent(Child, {
  77. $: [() => (toggle.value ? { id: 'foo' } : { class: 'baz' })],
  78. }),
  79. }).render()
  80. expect(html()).toMatch(`<div id="foo"></div>`)
  81. toggle.value = false
  82. await nextTick()
  83. expect(html()).toMatch(`<div class="baz"></div>`)
  84. })
  85. // #4161
  86. it('context.attrs in child component slots', async () => {
  87. const toggle = ref(true)
  88. const Wrapper = defineVaporComponent({
  89. setup(_) {
  90. const n0 = createSlot('default')
  91. return n0
  92. },
  93. })
  94. const Child = defineVaporComponent({
  95. inheritAttrs: false,
  96. setup(_: any, { attrs }: any) {
  97. const n0 = createComponent(Wrapper, null, {
  98. default: () => {
  99. const n0 = template('<div>')() as HTMLDivElement
  100. let prev: any
  101. renderEffect(() => {
  102. prev = setDynamicProps(n0, [attrs], prev, true)
  103. })
  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: () => createTextNode(() => [id.value]),
  136. }),
  137. () => ({
  138. name: 'bar',
  139. fn: () => createTextNode(['bar']),
  140. }),
  141. ],
  142. })
  143. },
  144. }).render()
  145. expect(html()).toMatch(`foo<!--slot-->bar<!--slot-->`)
  146. id.value = 'baz'
  147. await nextTick()
  148. expect(html()).toMatch(`baz<!--slot-->bar<!--slot-->`)
  149. })
  150. it('context.emit', async () => {
  151. const count = ref(0)
  152. const spy = vi.fn()
  153. delegateEvents('click')
  154. const Child = defineVaporComponent({
  155. props: {
  156. count: { type: Number, default: 1 },
  157. },
  158. setup(props, { emit }) {
  159. const n0 = template('<div>')() as HTMLDivElement
  160. delegate(n0, 'click', () => () => {
  161. emit('inc', props.count + 1)
  162. })
  163. insert(
  164. createTextNode(() => [props.count]),
  165. n0,
  166. )
  167. return n0
  168. },
  169. })
  170. const { host, html } = define({
  171. render: () =>
  172. createComponent(Child, {
  173. count: () => count.value,
  174. onInc: () => (newVal: number) => {
  175. spy()
  176. count.value = newVal
  177. },
  178. }),
  179. }).render()
  180. expect(html()).toMatch(`<div>0</div>`)
  181. ;(host.children[0] as HTMLDivElement).click()
  182. expect(spy).toHaveBeenCalled()
  183. await nextTick()
  184. expect(html()).toMatch(`<div>1</div>`)
  185. })
  186. })