ssr-custom-element.spec.ts 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131
  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. // #11641
  69. test('pass key to custom element', async () => {
  70. const messages: string[] = []
  71. page().on('console', e => messages.push(e.text()))
  72. await setContent(
  73. `<!--[--><my-element str="1"><template shadowrootmode="open"><div>1</div></template></my-element><!--]-->`,
  74. )
  75. await page().evaluate(() => {
  76. const {
  77. h,
  78. ref,
  79. defineSSRCustomElement,
  80. onBeforeUnmount,
  81. onMounted,
  82. createSSRApp,
  83. renderList,
  84. } = (window as any).Vue
  85. const MyElement = defineSSRCustomElement({
  86. props: {
  87. str: String,
  88. },
  89. setup(props: any) {
  90. onMounted(() => {
  91. console.log('child mounted')
  92. })
  93. onBeforeUnmount(() => {
  94. console.log('child unmount')
  95. })
  96. return () => h('div', props.str)
  97. },
  98. })
  99. customElements.define('my-element', MyElement)
  100. createSSRApp({
  101. setup() {
  102. const arr = ref(['1'])
  103. // pass key to custom element
  104. return () =>
  105. renderList(arr.value, (i: string) =>
  106. h('my-element', { key: i, str: i }, null),
  107. )
  108. },
  109. }).mount('#app')
  110. })
  111. expect(messages.includes('child mounted')).toBe(true)
  112. expect(messages.includes('child unmount')).toBe(false)
  113. expect(await text('my-element >>> div')).toBe('1')
  114. })