apiCreateDynamicComponent.spec.ts 6.4 KB

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