apiSetupContext.spec.ts 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213
  1. import {
  2. createComponent,
  3. createSlot,
  4. createTextNode,
  5. defineComponent,
  6. delegate,
  7. delegateEvents,
  8. insert,
  9. nextTick,
  10. reactive,
  11. ref,
  12. renderEffect,
  13. setDynamicProps,
  14. template,
  15. watchEffect,
  16. } from '../src'
  17. import { makeRender } from './_utils'
  18. const define = makeRender()
  19. describe('api: setup context', () => {
  20. it('should expose return values to template render context', () => {
  21. const { html } = define({
  22. setup() {
  23. return {
  24. ref: ref('foo'),
  25. object: reactive({ msg: 'bar' }),
  26. value: 'baz',
  27. }
  28. },
  29. render(ctx) {
  30. return createTextNode([`${ctx.ref} ${ctx.object.msg} ${ctx.value}`])
  31. },
  32. }).render()
  33. expect(html()).toMatch(`foo bar baz`)
  34. })
  35. it('should support returning render function', () => {
  36. const { html } = define({
  37. setup() {
  38. return createTextNode([`hello`])
  39. },
  40. }).render()
  41. expect(html()).toMatch(`hello`)
  42. })
  43. it('props', async () => {
  44. const count = ref(0)
  45. let dummy
  46. const Child = defineComponent({
  47. props: { count: Number },
  48. setup(props) {
  49. watchEffect(() => {
  50. dummy = props.count
  51. })
  52. return createTextNode(() => [props.count])
  53. },
  54. })
  55. const { html } = define({
  56. render: () => createComponent(Child, { count: () => count.value }),
  57. }).render()
  58. expect(html()).toMatch(`0`)
  59. count.value++
  60. await nextTick()
  61. expect(dummy).toBe(1)
  62. expect(html()).toMatch(`1`)
  63. })
  64. it('context.attrs', async () => {
  65. const toggle = ref(true)
  66. const Child = defineComponent({
  67. inheritAttrs: false,
  68. setup(props, { attrs }) {
  69. const el = document.createElement('div')
  70. renderEffect(() => {
  71. setDynamicProps(el, attrs)
  72. })
  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 = defineComponent({
  91. setup(_, { slots }) {
  92. return slots.default!()
  93. },
  94. })
  95. const Child = defineComponent({
  96. inheritAttrs: false,
  97. setup(_: any, { attrs }: any) {
  98. return createComponent(Wrapper, null, {
  99. default: () => {
  100. const n0 = template('<div>')() as HTMLDivElement
  101. renderEffect(() => {
  102. setDynamicProps(n0, attrs)
  103. })
  104. return n0
  105. },
  106. })
  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 = defineComponent({
  124. render() {
  125. return [createSlot('foo'), createSlot('bar')]
  126. },
  127. })
  128. const { html } = define({
  129. render() {
  130. return createComponent(Child, null, [
  131. () => ({
  132. name: 'foo',
  133. fn: () => createTextNode(() => [id.value]),
  134. }),
  135. () => ({
  136. name: 'bar',
  137. fn: () => createTextNode(['bar']),
  138. }),
  139. ])
  140. },
  141. }).render()
  142. expect(html()).toMatch(`foo<!--slot-->bar<!--slot-->`)
  143. id.value = 'baz'
  144. await nextTick()
  145. expect(html()).toMatch(`baz<!--slot-->bar<!--slot-->`)
  146. })
  147. it('context.emit', async () => {
  148. const count = ref(0)
  149. const spy = vi.fn()
  150. delegateEvents('click')
  151. const Child = defineComponent({
  152. props: {
  153. count: { type: Number, default: 1 },
  154. },
  155. setup(props, { emit }) {
  156. const n0 = template('<div>')() as HTMLDivElement
  157. delegate(n0, 'click', () => () => {
  158. emit('inc', props.count + 1)
  159. })
  160. insert(
  161. createTextNode(() => [props.count]),
  162. n0,
  163. )
  164. return n0
  165. },
  166. })
  167. const { host, html } = define({
  168. render: () =>
  169. createComponent(Child, {
  170. count: () => count.value,
  171. onInc: () => (newVal: number) => {
  172. spy()
  173. count.value = newVal
  174. },
  175. }),
  176. }).render()
  177. expect(html()).toMatch(`<div>0</div>`)
  178. ;(host.children[0] as HTMLDivElement).click()
  179. expect(spy).toHaveBeenCalled()
  180. await nextTick()
  181. expect(html()).toMatch(`<div>1</div>`)
  182. })
  183. })