]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
test: add more tests
authordaiwei <daiwei521@126.com>
Tue, 28 Oct 2025 01:35:09 +0000 (09:35 +0800)
committerdaiwei <daiwei521@126.com>
Tue, 28 Oct 2025 01:35:09 +0000 (09:35 +0800)
packages/runtime-vapor/__tests__/customElement.spec.ts
packages/runtime-vapor/src/componentProps.ts
packages/runtime-vapor/src/dom/prop.ts

index e83bb700429cb8ef4136616e31b8aa125df92035..843b4ace8d84b7f285dd93916260f5815cb44266 100644 (file)
@@ -7,11 +7,13 @@ import {
   toDisplayString,
 } from '@vue/runtime-dom'
 import {
+  child,
   createComponentWithFallback,
   createVaporApp,
   defineVaporComponent,
   defineVaporCustomElement,
   delegateEvents,
+  next,
   renderEffect,
   setInsertionState,
   setText,
@@ -467,132 +469,152 @@ describe('defineVaporCustomElement', () => {
       expect(e.value).toBe('hi')
     })
 
-    // // #12214
-    // test('Boolean prop with default true', async () => {
-    //   const E = defineVaporCustomElement({
-    //     props: {
-    //       foo: {
-    //         type: Boolean,
-    //         default: true,
-    //       },
-    //     },
-    //     render() {
-    //       return String(this.foo)
-    //     },
-    //   })
-    //   customElements.define('my-el-default-true', E)
-    //   container.innerHTML = `<my-el-default-true></my-el-default-true>`
-    //   const e = container.childNodes[0] as HTMLElement & { foo: any },
-    //     shadowRoot = e.shadowRoot as ShadowRoot
-    //   expect(shadowRoot.innerHTML).toBe('true')
-    //   e.foo = undefined
-    //   await nextTick()
-    //   expect(shadowRoot.innerHTML).toBe('true')
-    //   e.foo = false
-    //   await nextTick()
-    //   expect(shadowRoot.innerHTML).toBe('false')
-    //   e.foo = null
-    //   await nextTick()
-    //   expect(shadowRoot.innerHTML).toBe('null')
-    //   e.foo = ''
-    //   await nextTick()
-    //   expect(shadowRoot.innerHTML).toBe('true')
-    // })
+    test('Boolean prop with default true', async () => {
+      const E = defineVaporCustomElement({
+        props: {
+          foo: {
+            type: Boolean,
+            default: true,
+          },
+        },
+        setup(props: any) {
+          const n0 = template(' ')() as any
+          renderEffect(() => setText(n0, String(props.foo)))
+          return n0
+        },
+      })
+      customElements.define('my-el-default-true', E)
+      container.innerHTML = `<my-el-default-true></my-el-default-true>`
+      const e = container.childNodes[0] as HTMLElement & { foo: any },
+        shadowRoot = e.shadowRoot as ShadowRoot
+      expect(shadowRoot.innerHTML).toBe('true')
+      e.foo = undefined
+      await nextTick()
+      expect(shadowRoot.innerHTML).toBe('true')
+      e.foo = false
+      await nextTick()
+      expect(shadowRoot.innerHTML).toBe('false')
+      e.foo = null
+      await nextTick()
+      expect(shadowRoot.innerHTML).toBe('null')
+      e.foo = ''
+      await nextTick()
+      expect(shadowRoot.innerHTML).toBe('true')
+    })
 
-    // test('support direct setup function syntax with extra options', () => {
-    //   const E = defineVaporCustomElement(
-    //     props => {
-    //       return () => props.text
-    //     },
-    //     {
-    //       props: {
-    //         text: String,
-    //       },
-    //     },
-    //   )
-    //   customElements.define('my-el-setup-with-props', E)
-    //   container.innerHTML = `<my-el-setup-with-props text="hello"></my-el-setup-with-props>`
-    //   const e = container.childNodes[0] as VaporElement
-    //   expect(e.shadowRoot!.innerHTML).toBe('hello')
-    // })
+    test('support direct setup function syntax with extra options', () => {
+      const E = defineVaporCustomElement(
+        (props: any) => {
+          const n0 = template(' ')() as any
+          renderEffect(() => setText(n0, props.text))
+          return n0
+        },
+        {
+          props: {
+            text: String,
+          },
+        },
+      )
+      customElements.define('my-el-setup-with-props', E)
+      container.innerHTML = `<my-el-setup-with-props text="hello"></my-el-setup-with-props>`
+      const e = container.childNodes[0] as VaporElement
+      expect(e.shadowRoot!.innerHTML).toBe('hello')
+    })
 
-    // test('prop types validation', async () => {
-    //   const E = defineVaporCustomElement({
-    //     props: {
-    //       num: {
-    //         type: [Number, String],
-    //       },
-    //       bool: {
-    //         type: Boolean,
-    //       },
-    //     },
-    //     render() {
-    //       return h('div', [
-    //         h('span', [`${this.num} is ${typeof this.num}`]),
-    //         h('span', [`${this.bool} is ${typeof this.bool}`]),
-    //       ])
-    //     },
-    //   })
-
-    //   customElements.define('my-el-with-type-props', E)
-    //   render(h('my-el-with-type-props', { num: 1, bool: true }), container)
-    //   const e = container.childNodes[0] as VaporElement
-    //   // @ts-expect-error
-    //   expect(e.num).toBe(1)
-    //   // @ts-expect-error
-    //   expect(e.bool).toBe(true)
-    //   expect(e.shadowRoot!.innerHTML).toBe(
-    //     '<div><span>1 is number</span><span>true is boolean</span></div>',
-    //   )
-    // })
+    test('prop types validation', async () => {
+      const E = defineVaporCustomElement({
+        props: {
+          num: {
+            type: [Number, String],
+          },
+          bool: {
+            type: Boolean,
+          },
+        },
+        setup(props: any) {
+          const n0 = template(
+            '<div><span> </span><span> </span></div>',
+            true,
+          )() as any
+          const n1 = child(n0) as any
+          const n2 = next(n1) as any
+          const x0 = txt(n1) as any
+          const x1 = txt(n2) as any
+          renderEffect(() => setText(x0, `${props.num} is ${typeof props.num}`))
+          renderEffect(() =>
+            setText(x1, `${props.bool} is ${typeof props.bool}`),
+          )
+          return n0
+        },
+      })
+
+      customElements.define('my-el-with-type-props', E)
+      const { container } = render('my-el-with-type-props', {
+        num: () => 1,
+        bool: () => true,
+      })
+      const e = container.childNodes[0] as VaporElement
+      // @ts-expect-error
+      expect(e.num).toBe(1)
+      // @ts-expect-error
+      expect(e.bool).toBe(true)
+      expect(e.shadowRoot!.innerHTML).toBe(
+        '<div><span>1 is number</span><span>true is boolean</span></div>',
+      )
+    })
   })
 
-  // describe('attrs', () => {
-  //   const E = defineVaporCustomElement({
-  //     render() {
-  //       return [h('div', null, this.$attrs.foo as string)]
-  //     },
-  //   })
-  //   customElements.define('my-el-attrs', E)
+  describe('attrs', () => {
+    const E = defineVaporCustomElement({
+      setup(_: any, { attrs }: any) {
+        const n0 = template('<div> </div>')() as any
+        const x0 = txt(n0) as any
+        renderEffect(() => setText(x0, toDisplayString(attrs.foo)))
+        return [n0]
+      },
+    })
+    customElements.define('my-el-attrs', E)
 
-  //   test('attrs via attribute', async () => {
-  //     container.innerHTML = `<my-el-attrs foo="hello"></my-el-attrs>`
-  //     const e = container.childNodes[0] as VaporElement
-  //     expect(e.shadowRoot!.innerHTML).toBe('<div>hello</div>')
+    test('attrs via attribute', async () => {
+      container.innerHTML = `<my-el-attrs foo="hello"></my-el-attrs>`
+      const e = container.childNodes[0] as VaporElement
+      expect(e.shadowRoot!.innerHTML).toBe('<div>hello</div>')
 
-  //     e.setAttribute('foo', 'changed')
-  //     await nextTick()
-  //     expect(e.shadowRoot!.innerHTML).toBe('<div>changed</div>')
-  //   })
+      e.setAttribute('foo', 'changed')
+      await nextTick()
+      expect(e.shadowRoot!.innerHTML).toBe('<div>changed</div>')
+    })
 
-  //   test('non-declared properties should not show up in $attrs', () => {
-  //     const e = new E()
-  //     // @ts-expect-error
-  //     e.foo = '123'
-  //     container.appendChild(e)
-  //     expect(e.shadowRoot!.innerHTML).toBe('<div></div>')
-  //   })
+    test('non-declared properties should not show up in $attrs', () => {
+      const e = new E()
+      // @ts-expect-error
+      e.foo = '123'
+      container.appendChild(e)
+      expect(e.shadowRoot!.innerHTML).toBe('<div></div>')
+    })
 
-  //   // https://github.com/vuejs/core/issues/12964
-  //   // Disabled because of missing support for `delegatesFocus` in jsdom
-  //   // https://github.com/jsdom/jsdom/issues/3418
-  //   // eslint-disable-next-line vitest/no-disabled-tests
-  //   test.skip('shadowRoot should be initialized with delegatesFocus', () => {
-  //     const E = defineVaporCustomElement(
-  //       {
-  //         render() {
-  //           return [h('input', { tabindex: 1 })]
-  //         },
-  //       },
-  //       { shadowRootOptions: { delegatesFocus: true } },
-  //     )
-  //     customElements.define('my-el-with-delegate-focus', E)
+    // https://github.com/vuejs/core/issues/12964
+    // Disabled because of missing support for `delegatesFocus` in jsdom
+    // https://github.com/jsdom/jsdom/issues/3418
+    // test.skip('shadowRoot should be initialized with delegatesFocus', () => {
+    //   const E = defineVaporCustomElement(
+    //     {
+    //       // render() {
+    //       //   return [h('input', { tabindex: 1 })]
+    //       // },
+    //       setup() {
+    //         return template('<input tabindex="1">', true)()
+    //       },
+    //     },
+    //     { shadowRootOptions: { delegatesFocus: true } },
+    //   )
+    //   customElements.define('my-el-with-delegate-focus', E)
 
-  //     const e = new E()
-  //     container.appendChild(e)
-  //     expect(e.shadowRoot!.delegatesFocus).toBe(true)
-  //   })
-  // })
+    //   const e = new E()
+    //   container.appendChild(e)
+    //   expect(e.shadowRoot!.delegatesFocus).toBe(true)
+    // })
+  })
 
   // describe('emits', () => {
   //   const CompDef = defineVaporComponent({
index 393e6ef2256aaf625364a8c81d8f7c0ae5a5c910..496b001d3a26ea5de6199b5c5619dbcdcebff1e8 100644 (file)
@@ -217,10 +217,11 @@ export function getAttrFromRawProps(rawProps: RawProps, key: string): unknown {
     }
   }
   if (hasOwn(rawProps, key)) {
+    const value = resolveSource(rawProps[key])
     if (merged) {
-      merged.push(rawProps[key]())
+      merged.push(value)
     } else {
-      return rawProps[key]()
+      return value
     }
   }
   if (merged && merged.length) {
@@ -318,7 +319,7 @@ export function setupPropsValidation(instance: VaporComponentInstance): void {
   renderEffect(() => {
     pushWarningContext(instance)
     validateProps(
-      resolveDynamicProps(rawProps, !!instance.type.ce),
+      resolveDynamicProps(rawProps),
       instance.props,
       normalizePropsOptions(instance.type)[0]!,
     )
@@ -326,14 +327,11 @@ export function setupPropsValidation(instance: VaporComponentInstance): void {
   }, true /* noLifecycle */)
 }
 
-export function resolveDynamicProps(
-  props: RawProps,
-  isResolved: boolean = false,
-): Record<string, unknown> {
+export function resolveDynamicProps(props: RawProps): Record<string, unknown> {
   const mergedRawProps: Record<string, any> = {}
   for (const key in props) {
     if (key !== '$') {
-      mergedRawProps[key] = isResolved ? props[key] : props[key]()
+      mergedRawProps[key] = resolveSource(props[key])
     }
   }
   if (props.$) {
index 7642dab9627ecc09272da5995ff64ca12c3772b0..5aae0560aec90bbccf260631ad97f6d54dd14c57 100644 (file)
@@ -485,12 +485,12 @@ export function optimizePropertyLookup(): void {
   proto.$key = undefined
   proto.$fc = proto.$evtclick = undefined
   proto.$root = false
-  proto.$html =
-    proto.$txt =
-    proto.$cls =
-    proto.$sty =
-    (Text.prototype as any).$txt =
-      ''
+  proto.$html = proto.$cls = proto.$sty = ''
+  // Initialize $txt to undefined instead of empty string to ensure setText()
+  // properly updates the text node even when the value is empty string.
+  // This prevents issues where setText(node, '') would be skipped because
+  // $txt === '' would return true, leaving the original nodeValue unchanged.
+  ;(Text.prototype as any).$txt = undefined
 }
 
 function classHasMismatch(