ssr-custom-element.spec.ts 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174
  1. import path from 'node:path'
  2. import { setupPuppeteer } from './e2eUtils'
  3. const { page, click, text } = setupPuppeteer()
  4. beforeEach(async () => {
  5. await page().addScriptTag({
  6. path: path.resolve(__dirname, '../../dist/vue.global.js'),
  7. })
  8. })
  9. async function setContent(html: string) {
  10. await page().setContent(`<div id="app">${html}</div>`)
  11. }
  12. // this must be tested in actual Chrome because jsdom does not support
  13. // declarative shadow DOM
  14. test('ssr custom element hydration', async () => {
  15. await setContent(
  16. `<my-element><template shadowrootmode="open"><button>1</button></template></my-element><my-element-async><template shadowrootmode="open"><button>1</button></template></my-element-async>`,
  17. )
  18. await page().evaluate(() => {
  19. const {
  20. h,
  21. ref,
  22. defineSSRCustomElement,
  23. defineAsyncComponent,
  24. onMounted,
  25. useHost,
  26. } = (window as any).Vue
  27. const def = {
  28. setup() {
  29. const count = ref(1)
  30. const el = useHost()
  31. onMounted(() => (el.style.border = '1px solid red'))
  32. return () => h('button', { onClick: () => count.value++ }, count.value)
  33. },
  34. }
  35. customElements.define('my-element', defineSSRCustomElement(def))
  36. customElements.define(
  37. 'my-element-async',
  38. defineSSRCustomElement(
  39. defineAsyncComponent(
  40. () =>
  41. new Promise(r => {
  42. ;(window as any).resolve = () => r(def)
  43. }),
  44. ),
  45. ),
  46. )
  47. })
  48. function getColor() {
  49. return page().evaluate(() => {
  50. return [
  51. (document.querySelector('my-element') as any).style.border,
  52. (document.querySelector('my-element-async') as any).style.border,
  53. ]
  54. })
  55. }
  56. expect(await getColor()).toMatchObject(['1px solid red', ''])
  57. await page().evaluate(() => (window as any).resolve()) // exposed by test
  58. expect(await getColor()).toMatchObject(['1px solid red', '1px solid red'])
  59. async function assertInteraction(el: string) {
  60. const selector = `${el} >>> button`
  61. expect(await text(selector)).toBe('1')
  62. await click(selector)
  63. expect(await text(selector)).toBe('2')
  64. }
  65. await assertInteraction('my-element')
  66. await assertInteraction('my-element-async')
  67. })
  68. test('work with Teleport (shadowRoot: false)', async () => {
  69. await setContent(
  70. `<div id='test'></div><my-p><my-y><span>default</span></my-y></my-p>`,
  71. )
  72. await page().evaluate(() => {
  73. const { h, defineSSRCustomElement, Teleport, renderSlot } = (window as any)
  74. .Vue
  75. const Y = defineSSRCustomElement(
  76. {
  77. render() {
  78. return h(
  79. Teleport,
  80. { to: '#test' },
  81. {
  82. default: () => [renderSlot(this.$slots, 'default')],
  83. },
  84. )
  85. },
  86. },
  87. { shadowRoot: false },
  88. )
  89. customElements.define('my-y', Y)
  90. const P = defineSSRCustomElement(
  91. {
  92. render() {
  93. return renderSlot(this.$slots, 'default')
  94. },
  95. },
  96. { shadowRoot: false },
  97. )
  98. customElements.define('my-p', P)
  99. })
  100. function getInnerHTML() {
  101. return page().evaluate(() => {
  102. return (document.querySelector('#test') as any).innerHTML
  103. })
  104. }
  105. expect(await getInnerHTML()).toBe('<span>default</span>')
  106. })
  107. // #11641
  108. test('pass key to custom element', async () => {
  109. const messages: string[] = []
  110. page().on('console', e => messages.push(e.text()))
  111. await setContent(
  112. `<!--[--><my-element str="1"><template shadowrootmode="open"><div>1</div></template></my-element><!--]-->`,
  113. )
  114. await page().evaluate(() => {
  115. const {
  116. h,
  117. ref,
  118. defineSSRCustomElement,
  119. onBeforeUnmount,
  120. onMounted,
  121. createSSRApp,
  122. renderList,
  123. } = (window as any).Vue
  124. const MyElement = defineSSRCustomElement({
  125. props: {
  126. str: String,
  127. },
  128. setup(props: any) {
  129. onMounted(() => {
  130. console.log('child mounted')
  131. })
  132. onBeforeUnmount(() => {
  133. console.log('child unmount')
  134. })
  135. return () => h('div', props.str)
  136. },
  137. })
  138. customElements.define('my-element', MyElement)
  139. createSSRApp({
  140. setup() {
  141. const arr = ref(['1'])
  142. // pass key to custom element
  143. return () =>
  144. renderList(arr.value, (i: string) =>
  145. h('my-element', { key: i, str: i }, null),
  146. )
  147. },
  148. }).mount('#app')
  149. })
  150. expect(messages.includes('child mounted')).toBe(true)
  151. expect(messages.includes('child unmount')).toBe(false)
  152. expect(await text('my-element >>> div')).toBe('1')
  153. })