Skip to content

Commit 225428c

Browse files
committed
test: add more tests
1 parent cf806db commit 225428c

File tree

3 files changed

+151
-131
lines changed

3 files changed

+151
-131
lines changed

packages/runtime-vapor/__tests__/customElement.spec.ts

Lines changed: 139 additions & 117 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,13 @@ import {
77
toDisplayString,
88
} from '@vue/runtime-dom'
99
import {
10+
child,
1011
createComponentWithFallback,
1112
createVaporApp,
1213
defineVaporComponent,
1314
defineVaporCustomElement,
1415
delegateEvents,
16+
next,
1517
renderEffect,
1618
setInsertionState,
1719
setText,
@@ -467,132 +469,152 @@ describe('defineVaporCustomElement', () => {
467469
expect(e.value).toBe('hi')
468470
})
469471

470-
// // #12214
471-
// test('Boolean prop with default true', async () => {
472-
// const E = defineVaporCustomElement({
473-
// props: {
474-
// foo: {
475-
// type: Boolean,
476-
// default: true,
477-
// },
478-
// },
479-
// render() {
480-
// return String(this.foo)
481-
// },
482-
// })
483-
// customElements.define('my-el-default-true', E)
484-
// container.innerHTML = `<my-el-default-true></my-el-default-true>`
485-
// const e = container.childNodes[0] as HTMLElement & { foo: any },
486-
// shadowRoot = e.shadowRoot as ShadowRoot
487-
// expect(shadowRoot.innerHTML).toBe('true')
488-
// e.foo = undefined
489-
// await nextTick()
490-
// expect(shadowRoot.innerHTML).toBe('true')
491-
// e.foo = false
492-
// await nextTick()
493-
// expect(shadowRoot.innerHTML).toBe('false')
494-
// e.foo = null
495-
// await nextTick()
496-
// expect(shadowRoot.innerHTML).toBe('null')
497-
// e.foo = ''
498-
// await nextTick()
499-
// expect(shadowRoot.innerHTML).toBe('true')
500-
// })
472+
test('Boolean prop with default true', async () => {
473+
const E = defineVaporCustomElement({
474+
props: {
475+
foo: {
476+
type: Boolean,
477+
default: true,
478+
},
479+
},
480+
setup(props: any) {
481+
const n0 = template(' ')() as any
482+
renderEffect(() => setText(n0, String(props.foo)))
483+
return n0
484+
},
485+
})
486+
customElements.define('my-el-default-true', E)
487+
container.innerHTML = `<my-el-default-true></my-el-default-true>`
488+
const e = container.childNodes[0] as HTMLElement & { foo: any },
489+
shadowRoot = e.shadowRoot as ShadowRoot
490+
expect(shadowRoot.innerHTML).toBe('true')
491+
e.foo = undefined
492+
await nextTick()
493+
expect(shadowRoot.innerHTML).toBe('true')
494+
e.foo = false
495+
await nextTick()
496+
expect(shadowRoot.innerHTML).toBe('false')
497+
e.foo = null
498+
await nextTick()
499+
expect(shadowRoot.innerHTML).toBe('null')
500+
e.foo = ''
501+
await nextTick()
502+
expect(shadowRoot.innerHTML).toBe('true')
503+
})
501504

502-
// test('support direct setup function syntax with extra options', () => {
503-
// const E = defineVaporCustomElement(
504-
// props => {
505-
// return () => props.text
506-
// },
507-
// {
508-
// props: {
509-
// text: String,
510-
// },
511-
// },
512-
// )
513-
// customElements.define('my-el-setup-with-props', E)
514-
// container.innerHTML = `<my-el-setup-with-props text="hello"></my-el-setup-with-props>`
515-
// const e = container.childNodes[0] as VaporElement
516-
// expect(e.shadowRoot!.innerHTML).toBe('hello')
517-
// })
505+
test('support direct setup function syntax with extra options', () => {
506+
const E = defineVaporCustomElement(
507+
(props: any) => {
508+
const n0 = template(' ')() as any
509+
renderEffect(() => setText(n0, props.text))
510+
return n0
511+
},
512+
{
513+
props: {
514+
text: String,
515+
},
516+
},
517+
)
518+
customElements.define('my-el-setup-with-props', E)
519+
container.innerHTML = `<my-el-setup-with-props text="hello"></my-el-setup-with-props>`
520+
const e = container.childNodes[0] as VaporElement
521+
expect(e.shadowRoot!.innerHTML).toBe('hello')
522+
})
518523

519-
// test('prop types validation', async () => {
520-
// const E = defineVaporCustomElement({
521-
// props: {
522-
// num: {
523-
// type: [Number, String],
524-
// },
525-
// bool: {
526-
// type: Boolean,
527-
// },
528-
// },
529-
// render() {
530-
// return h('div', [
531-
// h('span', [`${this.num} is ${typeof this.num}`]),
532-
// h('span', [`${this.bool} is ${typeof this.bool}`]),
533-
// ])
534-
// },
535-
// })
536-
537-
// customElements.define('my-el-with-type-props', E)
538-
// render(h('my-el-with-type-props', { num: 1, bool: true }), container)
539-
// const e = container.childNodes[0] as VaporElement
540-
// // @ts-expect-error
541-
// expect(e.num).toBe(1)
542-
// // @ts-expect-error
543-
// expect(e.bool).toBe(true)
544-
// expect(e.shadowRoot!.innerHTML).toBe(
545-
// '<div><span>1 is number</span><span>true is boolean</span></div>',
546-
// )
547-
// })
524+
test('prop types validation', async () => {
525+
const E = defineVaporCustomElement({
526+
props: {
527+
num: {
528+
type: [Number, String],
529+
},
530+
bool: {
531+
type: Boolean,
532+
},
533+
},
534+
setup(props: any) {
535+
const n0 = template(
536+
'<div><span> </span><span> </span></div>',
537+
true,
538+
)() as any
539+
const n1 = child(n0) as any
540+
const n2 = next(n1) as any
541+
const x0 = txt(n1) as any
542+
const x1 = txt(n2) as any
543+
renderEffect(() => setText(x0, `${props.num} is ${typeof props.num}`))
544+
renderEffect(() =>
545+
setText(x1, `${props.bool} is ${typeof props.bool}`),
546+
)
547+
return n0
548+
},
549+
})
550+
551+
customElements.define('my-el-with-type-props', E)
552+
const { container } = render('my-el-with-type-props', {
553+
num: () => 1,
554+
bool: () => true,
555+
})
556+
const e = container.childNodes[0] as VaporElement
557+
// @ts-expect-error
558+
expect(e.num).toBe(1)
559+
// @ts-expect-error
560+
expect(e.bool).toBe(true)
561+
expect(e.shadowRoot!.innerHTML).toBe(
562+
'<div><span>1 is number</span><span>true is boolean</span></div>',
563+
)
564+
})
548565
})
549566

550-
// describe('attrs', () => {
551-
// const E = defineVaporCustomElement({
552-
// render() {
553-
// return [h('div', null, this.$attrs.foo as string)]
554-
// },
555-
// })
556-
// customElements.define('my-el-attrs', E)
567+
describe('attrs', () => {
568+
const E = defineVaporCustomElement({
569+
setup(_: any, { attrs }: any) {
570+
const n0 = template('<div> </div>')() as any
571+
const x0 = txt(n0) as any
572+
renderEffect(() => setText(x0, toDisplayString(attrs.foo)))
573+
return [n0]
574+
},
575+
})
576+
customElements.define('my-el-attrs', E)
557577

558-
// test('attrs via attribute', async () => {
559-
// container.innerHTML = `<my-el-attrs foo="hello"></my-el-attrs>`
560-
// const e = container.childNodes[0] as VaporElement
561-
// expect(e.shadowRoot!.innerHTML).toBe('<div>hello</div>')
578+
test('attrs via attribute', async () => {
579+
container.innerHTML = `<my-el-attrs foo="hello"></my-el-attrs>`
580+
const e = container.childNodes[0] as VaporElement
581+
expect(e.shadowRoot!.innerHTML).toBe('<div>hello</div>')
562582

563-
// e.setAttribute('foo', 'changed')
564-
// await nextTick()
565-
// expect(e.shadowRoot!.innerHTML).toBe('<div>changed</div>')
566-
// })
583+
e.setAttribute('foo', 'changed')
584+
await nextTick()
585+
expect(e.shadowRoot!.innerHTML).toBe('<div>changed</div>')
586+
})
567587

568-
// test('non-declared properties should not show up in $attrs', () => {
569-
// const e = new E()
570-
// // @ts-expect-error
571-
// e.foo = '123'
572-
// container.appendChild(e)
573-
// expect(e.shadowRoot!.innerHTML).toBe('<div></div>')
574-
// })
588+
test('non-declared properties should not show up in $attrs', () => {
589+
const e = new E()
590+
// @ts-expect-error
591+
e.foo = '123'
592+
container.appendChild(e)
593+
expect(e.shadowRoot!.innerHTML).toBe('<div></div>')
594+
})
575595

576-
// // https://github.com/vuejs/core/issues/12964
577-
// // Disabled because of missing support for `delegatesFocus` in jsdom
578-
// // https://github.com/jsdom/jsdom/issues/3418
579-
// // eslint-disable-next-line vitest/no-disabled-tests
580-
// test.skip('shadowRoot should be initialized with delegatesFocus', () => {
581-
// const E = defineVaporCustomElement(
582-
// {
583-
// render() {
584-
// return [h('input', { tabindex: 1 })]
585-
// },
586-
// },
587-
// { shadowRootOptions: { delegatesFocus: true } },
588-
// )
589-
// customElements.define('my-el-with-delegate-focus', E)
596+
// https://github.com/vuejs/core/issues/12964
597+
// Disabled because of missing support for `delegatesFocus` in jsdom
598+
// https://github.com/jsdom/jsdom/issues/3418
599+
// test.skip('shadowRoot should be initialized with delegatesFocus', () => {
600+
// const E = defineVaporCustomElement(
601+
// {
602+
// // render() {
603+
// // return [h('input', { tabindex: 1 })]
604+
// // },
605+
// setup() {
606+
// return template('<input tabindex="1">', true)()
607+
// },
608+
// },
609+
// { shadowRootOptions: { delegatesFocus: true } },
610+
// )
611+
// customElements.define('my-el-with-delegate-focus', E)
590612

591-
// const e = new E()
592-
// container.appendChild(e)
593-
// expect(e.shadowRoot!.delegatesFocus).toBe(true)
594-
// })
595-
// })
613+
// const e = new E()
614+
// container.appendChild(e)
615+
// expect(e.shadowRoot!.delegatesFocus).toBe(true)
616+
// })
617+
})
596618

597619
// describe('emits', () => {
598620
// const CompDef = defineVaporComponent({

packages/runtime-vapor/src/componentProps.ts

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -217,10 +217,11 @@ export function getAttrFromRawProps(rawProps: RawProps, key: string): unknown {
217217
}
218218
}
219219
if (hasOwn(rawProps, key)) {
220+
const value = resolveSource(rawProps[key])
220221
if (merged) {
221-
merged.push(rawProps[key]())
222+
merged.push(value)
222223
} else {
223-
return rawProps[key]()
224+
return value
224225
}
225226
}
226227
if (merged && merged.length) {
@@ -318,22 +319,19 @@ export function setupPropsValidation(instance: VaporComponentInstance): void {
318319
renderEffect(() => {
319320
pushWarningContext(instance)
320321
validateProps(
321-
resolveDynamicProps(rawProps, !!instance.type.ce),
322+
resolveDynamicProps(rawProps),
322323
instance.props,
323324
normalizePropsOptions(instance.type)[0]!,
324325
)
325326
popWarningContext()
326327
}, true /* noLifecycle */)
327328
}
328329

329-
export function resolveDynamicProps(
330-
props: RawProps,
331-
isResolved: boolean = false,
332-
): Record<string, unknown> {
330+
export function resolveDynamicProps(props: RawProps): Record<string, unknown> {
333331
const mergedRawProps: Record<string, any> = {}
334332
for (const key in props) {
335333
if (key !== '$') {
336-
mergedRawProps[key] = isResolved ? props[key] : props[key]()
334+
mergedRawProps[key] = resolveSource(props[key])
337335
}
338336
}
339337
if (props.$) {

packages/runtime-vapor/src/dom/prop.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -485,12 +485,12 @@ export function optimizePropertyLookup(): void {
485485
proto.$key = undefined
486486
proto.$fc = proto.$evtclick = undefined
487487
proto.$root = false
488-
proto.$html =
489-
proto.$txt =
490-
proto.$cls =
491-
proto.$sty =
492-
(Text.prototype as any).$txt =
493-
''
488+
proto.$html = proto.$cls = proto.$sty = ''
489+
// Initialize $txt to undefined instead of empty string to ensure setText()
490+
// properly updates the text node even when the value is empty string.
491+
// This prevents issues where setText(node, '') would be skipped because
492+
// $txt === '' would return true, leaving the original nodeValue unchanged.
493+
;(Text.prototype as any).$txt = undefined
494494
}
495495

496496
function classHasMismatch(

0 commit comments

Comments
 (0)