apiCreateDynamicComponent.spec.ts 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240
  1. import { ref, shallowRef } from '@vue/reactivity'
  2. import { nextTick, resolveDynamicComponent } from '@vue/runtime-dom'
  3. import {
  4. createComponent,
  5. createComponentWithFallback,
  6. createDynamicComponent,
  7. createSlot,
  8. defineVaporComponent,
  9. insert,
  10. renderEffect,
  11. setHtml,
  12. setInsertionState,
  13. template,
  14. withVaporCtx,
  15. } from '../src'
  16. import { makeRender } from './_utils'
  17. const define = makeRender()
  18. describe('api: createDynamicComponent', () => {
  19. const A = () => document.createTextNode('AAA')
  20. const B = () => document.createTextNode('BBB')
  21. test('direct value', async () => {
  22. const val = shallowRef<any>(A)
  23. const { html } = define({
  24. setup() {
  25. return createDynamicComponent(() => val.value)
  26. },
  27. }).render()
  28. expect(html()).toBe('AAA<!--dynamic-component-->')
  29. val.value = B
  30. await nextTick()
  31. expect(html()).toBe('BBB<!--dynamic-component-->')
  32. // fallback
  33. val.value = 'foo'
  34. await nextTick()
  35. expect(html()).toBe('<foo></foo><!--dynamic-component-->')
  36. })
  37. test('global registration', async () => {
  38. const val = shallowRef('foo')
  39. const { app, html, mount } = define({
  40. setup() {
  41. return createDynamicComponent(() => val.value)
  42. },
  43. }).create()
  44. app.component('foo', A)
  45. app.component('bar', B)
  46. mount()
  47. expect(html()).toBe('AAA<!--dynamic-component-->')
  48. val.value = 'bar'
  49. await nextTick()
  50. expect(html()).toBe('BBB<!--dynamic-component-->')
  51. // fallback
  52. val.value = 'baz'
  53. await nextTick()
  54. expect(html()).toBe('<baz></baz><!--dynamic-component-->')
  55. })
  56. test('with v-once', async () => {
  57. const val = shallowRef<any>(A)
  58. const { html } = define({
  59. setup() {
  60. return createDynamicComponent(() => val.value, null, null, true, true)
  61. },
  62. }).render()
  63. expect(html()).toBe('AAA<!--dynamic-component-->')
  64. val.value = B
  65. await nextTick()
  66. expect(html()).toBe('AAA<!--dynamic-component-->') // still AAA
  67. })
  68. test('fallback with v-once', async () => {
  69. const val = shallowRef<any>('button')
  70. const id = ref(0)
  71. const { html } = define({
  72. setup() {
  73. return createDynamicComponent(
  74. () => val.value,
  75. { id: () => id.value },
  76. null,
  77. true,
  78. true,
  79. )
  80. },
  81. }).render()
  82. expect(html()).toBe('<button id="0"></button><!--dynamic-component-->')
  83. id.value++
  84. await nextTick()
  85. expect(html()).toBe('<button id="0"></button><!--dynamic-component-->')
  86. })
  87. test('render fallback with insertionState', async () => {
  88. const { html, mount } = define({
  89. setup() {
  90. const html = ref('hi')
  91. const n1 = template('<div></div>', true)() as any
  92. setInsertionState(n1)
  93. const n0 = createComponentWithFallback(
  94. resolveDynamicComponent('button') as any,
  95. ) as any
  96. renderEffect(() => setHtml(n0, html.value))
  97. return n1
  98. },
  99. }).create()
  100. mount()
  101. expect(html()).toBe('<div><button>hi</button></div>')
  102. })
  103. test('switch dynamic component children', async () => {
  104. const CompA = defineVaporComponent({
  105. setup() {
  106. return template('<div>A</div>')()
  107. },
  108. })
  109. const CompB = defineVaporComponent({
  110. setup() {
  111. return template('<div>B</div>')()
  112. },
  113. })
  114. const current = shallowRef(CompA)
  115. const { html } = define({
  116. setup() {
  117. const t1 = template('<div></div>')
  118. const n2 = t1() as any
  119. setInsertionState(n2)
  120. createDynamicComponent(() => current.value)
  121. return n2
  122. },
  123. }).render()
  124. expect(html()).toBe('<div><div>A</div><!--dynamic-component--></div>')
  125. current.value = CompB
  126. await nextTick()
  127. expect(html()).toBe('<div><div>B</div><!--dynamic-component--></div>')
  128. })
  129. test('fallback with dynamic slots', async () => {
  130. const slotName = ref('default')
  131. const { html } = define({
  132. setup() {
  133. return createDynamicComponent(() => 'div', null, {
  134. $: [
  135. () => ({
  136. name: slotName.value,
  137. fn: () => template('<span>hi</span>')(),
  138. }),
  139. ] as any,
  140. })
  141. },
  142. }).render()
  143. expect(html()).toBe(
  144. '<div><span>hi</span><!--slot--></div><!--dynamic-component-->',
  145. )
  146. // update slot name
  147. slotName.value = 'custom'
  148. await nextTick()
  149. expect(html()).toBe('<div><!--slot--></div><!--dynamic-component-->')
  150. slotName.value = 'default'
  151. await nextTick()
  152. expect(html()).toBe(
  153. '<div><span>hi</span><!--slot--></div><!--dynamic-component-->',
  154. )
  155. })
  156. test('resolves slot owner local components after dynamic updates', async () => {
  157. const current = shallowRef('Foo')
  158. const Foo = defineVaporComponent({
  159. setup() {
  160. return template('<span>foo</span>')()
  161. },
  162. })
  163. const Child = defineVaporComponent({
  164. setup() {
  165. const n0 = template('<section></section>')()
  166. insert(createSlot('default'), n0 as ParentNode)
  167. return n0
  168. },
  169. })
  170. const { html } = define({
  171. components: { Foo },
  172. setup() {
  173. return createComponent(Child, null, {
  174. default: withVaporCtx(() =>
  175. createDynamicComponent(() => current.value),
  176. ),
  177. })
  178. },
  179. }).render()
  180. expect(html()).toBe(
  181. '<section><span>foo</span><!--dynamic-component--><!--slot--></section>',
  182. )
  183. current.value = 'div'
  184. await nextTick()
  185. expect(html()).toBe(
  186. '<section><div></div><!--dynamic-component--><!--slot--></section>',
  187. )
  188. current.value = 'Foo'
  189. await nextTick()
  190. expect(html()).toBe(
  191. '<section><span>foo</span><!--dynamic-component--><!--slot--></section>',
  192. )
  193. })
  194. test('accept blocks', async () => {
  195. const { html } = define({
  196. setup() {
  197. const n0 = template('<a>router link</a>')()
  198. return createDynamicComponent(() => n0)
  199. },
  200. }).render()
  201. expect(html()).toBe('<a>router link</a><!--dynamic-component-->')
  202. })
  203. })