apiSetupContext.spec.ts 4.8 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. setInheritAttrs,
  15. template,
  16. watchEffect,
  17. } from '../src'
  18. import { makeRender } from './_utils'
  19. const define = makeRender()
  20. describe('api: setup context', () => {
  21. it('should expose return values to template render context', () => {
  22. const { html } = define({
  23. setup() {
  24. return {
  25. ref: ref('foo'),
  26. object: reactive({ msg: 'bar' }),
  27. value: 'baz',
  28. }
  29. },
  30. render(ctx) {
  31. return createTextNode([`${ctx.ref} ${ctx.object.msg} ${ctx.value}`])
  32. },
  33. }).render()
  34. expect(html()).toMatch(`foo bar baz`)
  35. })
  36. it('should support returning render function', () => {
  37. const { html } = define({
  38. setup() {
  39. return createTextNode([`hello`])
  40. },
  41. }).render()
  42. expect(html()).toMatch(`hello`)
  43. })
  44. it('props', async () => {
  45. const count = ref(0)
  46. let dummy
  47. const Child = defineComponent({
  48. props: { count: Number },
  49. setup(props) {
  50. watchEffect(() => {
  51. dummy = props.count
  52. })
  53. return createTextNode(() => [props.count])
  54. },
  55. })
  56. const { html } = define({
  57. render: () => createComponent(Child, { count: () => count.value }),
  58. }).render()
  59. expect(html()).toMatch(`0`)
  60. count.value++
  61. await nextTick()
  62. expect(dummy).toBe(1)
  63. expect(html()).toMatch(`1`)
  64. })
  65. it('context.attrs', async () => {
  66. const toggle = ref(true)
  67. const Child = defineComponent({
  68. inheritAttrs: false,
  69. setup(props, { attrs }) {
  70. const el = document.createElement('div')
  71. renderEffect(() => setDynamicProps(el, [attrs]))
  72. return el
  73. },
  74. })
  75. const { html } = define({
  76. render: () =>
  77. createComponent(Child, () =>
  78. toggle.value ? { id: 'foo' } : { class: 'baz' },
  79. ),
  80. }).render()
  81. expect(html()).toMatch(`<div id="foo"></div>`)
  82. toggle.value = false
  83. await nextTick()
  84. expect(html()).toMatch(`<div class="baz"></div>`)
  85. })
  86. // #4161
  87. it('context.attrs in child component slots', async () => {
  88. const toggle = ref(true)
  89. const Wrapper = defineComponent({
  90. setup(_) {
  91. const n0 = createSlot('default')
  92. setInheritAttrs(true)
  93. return n0
  94. },
  95. })
  96. const Child = defineComponent({
  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], true))
  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 = 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. })