vdomInterop.spec.ts 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278
  1. import {
  2. createVNode,
  3. defineComponent,
  4. h,
  5. nextTick,
  6. ref,
  7. renderSlot,
  8. toDisplayString,
  9. useModel,
  10. } from '@vue/runtime-dom'
  11. import { makeInteropRender } from './_utils'
  12. import {
  13. applyTextModel,
  14. applyVShow,
  15. child,
  16. createComponent,
  17. defineVaporComponent,
  18. renderEffect,
  19. setText,
  20. template,
  21. } from '../src'
  22. const define = makeInteropRender()
  23. describe('vdomInterop', () => {
  24. describe('props', () => {
  25. test('should work if props are not provided', () => {
  26. const VaporChild = defineVaporComponent({
  27. props: {
  28. msg: String,
  29. },
  30. setup(_, { attrs }) {
  31. return [document.createTextNode(attrs.class || 'foo')]
  32. },
  33. })
  34. const { html } = define({
  35. setup() {
  36. return () => h(VaporChild as any)
  37. },
  38. }).render()
  39. expect(html()).toBe('foo')
  40. })
  41. })
  42. describe('v-model', () => {
  43. test('basic work', async () => {
  44. const VaporChild = defineVaporComponent({
  45. props: {
  46. modelValue: {},
  47. modelModifiers: {},
  48. },
  49. emits: ['update:modelValue'],
  50. setup(__props) {
  51. const modelValue = useModel(__props, 'modelValue')
  52. const n0 = template('<h1> </h1>')() as any
  53. const n1 = template('<input>')() as any
  54. const x0 = child(n0) as any
  55. applyTextModel(
  56. n1,
  57. () => modelValue.value,
  58. _value => (modelValue.value = _value),
  59. )
  60. renderEffect(() => setText(x0, toDisplayString(modelValue.value)))
  61. return [n0, n1]
  62. },
  63. })
  64. const { html, host } = define({
  65. setup() {
  66. const msg = ref('foo')
  67. return () =>
  68. h(VaporChild as any, {
  69. modelValue: msg.value,
  70. 'onUpdate:modelValue': (value: string) => {
  71. msg.value = value
  72. },
  73. })
  74. },
  75. }).render()
  76. expect(html()).toBe('<h1>foo</h1><input>')
  77. const inputEl = host.querySelector('input')!
  78. inputEl.value = 'bar'
  79. inputEl.dispatchEvent(new Event('input'))
  80. await nextTick()
  81. expect(html()).toBe('<h1>bar</h1><input>')
  82. })
  83. })
  84. describe('emit', () => {
  85. test('emit from vapor child to vdom parent', () => {
  86. const VaporChild = defineVaporComponent({
  87. emits: ['click'],
  88. setup(_, { emit }) {
  89. emit('click')
  90. return []
  91. },
  92. })
  93. const fn = vi.fn()
  94. define({
  95. setup() {
  96. return () => h(VaporChild as any, { onClick: fn })
  97. },
  98. }).render()
  99. // fn should be called once
  100. expect(fn).toHaveBeenCalledTimes(1)
  101. })
  102. })
  103. describe('v-show', () => {
  104. test('apply v-show to vdom child', async () => {
  105. const VDomChild = {
  106. setup() {
  107. return () => h('div')
  108. },
  109. }
  110. const show = ref(false)
  111. const VaporChild = defineVaporComponent({
  112. setup() {
  113. const n1 = createComponent(VDomChild as any)
  114. applyVShow(n1, () => show.value)
  115. return n1
  116. },
  117. })
  118. const { html } = define({
  119. setup() {
  120. return () => h(VaporChild as any)
  121. },
  122. }).render()
  123. expect(html()).toBe('<div style="display: none;"></div>')
  124. show.value = true
  125. await nextTick()
  126. expect(html()).toBe('<div style=""></div>')
  127. })
  128. })
  129. describe('slots', () => {
  130. test('basic', () => {
  131. const VDomChild = defineComponent({
  132. setup(_, { slots }) {
  133. return () => renderSlot(slots, 'default')
  134. },
  135. })
  136. const VaporChild = defineVaporComponent({
  137. setup() {
  138. return createComponent(
  139. VDomChild as any,
  140. null,
  141. {
  142. default: () => document.createTextNode('default slot'),
  143. },
  144. true,
  145. )
  146. },
  147. })
  148. const { html } = define({
  149. setup() {
  150. return () => h(VaporChild as any)
  151. },
  152. }).render()
  153. expect(html()).toBe('default slot')
  154. })
  155. test('functional slot', () => {
  156. const VDomChild = defineComponent({
  157. setup(_, { slots }) {
  158. return () => createVNode(slots.default!)
  159. },
  160. })
  161. const VaporChild = defineVaporComponent({
  162. setup() {
  163. return createComponent(
  164. VDomChild as any,
  165. null,
  166. {
  167. default: () => document.createTextNode('default slot'),
  168. },
  169. true,
  170. )
  171. },
  172. })
  173. const { html } = define({
  174. setup() {
  175. return () => h(VaporChild as any)
  176. },
  177. }).render()
  178. expect(html()).toBe('default slot')
  179. })
  180. })
  181. describe.todo('provide', () => {})
  182. describe.todo('inject', () => {})
  183. describe.todo('template ref', () => {})
  184. describe.todo('dynamic component', () => {})
  185. describe('attribute fallthrough', () => {
  186. it('should fallthrough attrs to vdom child', () => {
  187. const VDomChild = defineComponent({
  188. setup() {
  189. return () => h('div')
  190. },
  191. })
  192. const VaporChild = defineVaporComponent({
  193. setup() {
  194. return createComponent(
  195. VDomChild as any,
  196. { foo: () => 'vapor foo' },
  197. null,
  198. true,
  199. )
  200. },
  201. })
  202. const { html } = define({
  203. setup() {
  204. return () => h(VaporChild as any, { foo: 'foo', bar: 'bar' })
  205. },
  206. }).render()
  207. expect(html()).toBe('<div foo="foo" bar="bar"></div>')
  208. })
  209. it('should not fallthrough emit handlers to vdom child', () => {
  210. const VDomChild = defineComponent({
  211. emits: ['click'],
  212. setup(_, { emit }) {
  213. return () => h('button', { onClick: () => emit('click') }, 'click me')
  214. },
  215. })
  216. const fn = vi.fn()
  217. const VaporChild = defineVaporComponent({
  218. emits: ['click'],
  219. setup() {
  220. return createComponent(
  221. VDomChild as any,
  222. { onClick: () => fn },
  223. null,
  224. true,
  225. )
  226. },
  227. })
  228. const { host, html } = define({
  229. setup() {
  230. return () => h(VaporChild as any)
  231. },
  232. }).render()
  233. expect(html()).toBe('<button>click me</button>')
  234. const button = host.querySelector('button')!
  235. button.dispatchEvent(new Event('click'))
  236. // fn should be called once
  237. expect(fn).toHaveBeenCalledTimes(1)
  238. })
  239. })
  240. })