From 4edb144fbd827c87fe1ee6beb2708a9520041644 Mon Sep 17 00:00:00 2001 From: daiwei Date: Tue, 28 Oct 2025 16:37:30 +0800 Subject: [PATCH] wip: save --- .../__tests__/customElement.spec.ts | 917 +++++++++--------- 1 file changed, 468 insertions(+), 449 deletions(-) diff --git a/packages/runtime-vapor/__tests__/customElement.spec.ts b/packages/runtime-vapor/__tests__/customElement.spec.ts index 754f2b8aec..894d497639 100644 --- a/packages/runtime-vapor/__tests__/customElement.spec.ts +++ b/packages/runtime-vapor/__tests__/customElement.spec.ts @@ -13,6 +13,7 @@ import { child, createComponent, createComponentWithFallback, + createIf, createSlot, createVaporApp, defineVaporAsyncComponent, @@ -1131,492 +1132,510 @@ describe('defineVaporCustomElement', () => { }) }) - // describe('async', () => { - // test('should work', async () => { - // const loaderSpy = vi.fn() - // const E = defineVaporCustomElement( - // defineVaporAsyncComponent(() => { - // loaderSpy() - // return Promise.resolve({ - // props: ['msg'], - // styles: [`div { color: red }`], - // render(this: any) { - // return h('div', null, this.msg) - // }, - // }) - // }), - // ) - // customElements.define('my-el-async', E) - // container.innerHTML = - // `` + - // `` - - // await new Promise(r => setTimeout(r)) - - // // loader should be called only once - // expect(loaderSpy).toHaveBeenCalledTimes(1) + describe('async', () => { + test('should work', async () => { + const loaderSpy = vi.fn() + const E = defineVaporCustomElement( + defineVaporAsyncComponent(() => { + loaderSpy() + return Promise.resolve({ + props: ['msg'], + styles: [`div { color: red }`], + setup(props: any) { + const n0 = template('
', true)() as any + const x0 = txt(n0) as any + renderEffect(() => setText(x0, props.msg)) + return n0 + }, + }) + }), + ) + customElements.define('my-el-async', E) + container.innerHTML = + `` + + `` - // const e1 = container.childNodes[0] as VaporElement - // const e2 = container.childNodes[1] as VaporElement + await new Promise(r => setTimeout(r)) - // // should inject styles - // expect(e1.shadowRoot!.innerHTML).toBe( - // `
hello
`, - // ) - // expect(e2.shadowRoot!.innerHTML).toBe( - // `
world
`, - // ) + // loader should be called only once + expect(loaderSpy).toHaveBeenCalledTimes(1) - // // attr - // e1.setAttribute('msg', 'attr') - // await nextTick() - // expect((e1 as any).msg).toBe('attr') - // expect(e1.shadowRoot!.innerHTML).toBe( - // `
attr
`, - // ) + const e1 = container.childNodes[0] as VaporElement + const e2 = container.childNodes[1] as VaporElement - // // props - // expect(`msg` in e1).toBe(true) - // ;(e1 as any).msg = 'prop' - // expect(e1.getAttribute('msg')).toBe('prop') - // expect(e1.shadowRoot!.innerHTML).toBe( - // `
prop
`, - // ) - // }) - - // test('set DOM property before resolve', async () => { - // const E = defineVaporCustomElement( - // defineVaporAsyncComponent(() => { - // return Promise.resolve({ - // props: ['msg'], - // setup(props) { - // expect(typeof props.msg).toBe('string') - // }, - // render(this: any) { - // return h('div', this.msg) - // }, - // }) - // }), - // ) - // customElements.define('my-el-async-2', E) - - // const e1 = new E() + // should inject styles + expect(e1.shadowRoot!.innerHTML).toBe( + `
hello
`, + ) + expect(e2.shadowRoot!.innerHTML).toBe( + `
world
`, + ) - // // set property before connect - // e1.msg = 'hello' + // attr + e1.setAttribute('msg', 'attr') + await nextTick() + expect((e1 as any).msg).toBe('attr') + expect(e1.shadowRoot!.innerHTML).toBe( + `
attr
`, + ) - // const e2 = new E() + // props + expect(`msg` in e1).toBe(true) + ;(e1 as any).msg = 'prop' + expect(e1.getAttribute('msg')).toBe('prop') + expect(e1.shadowRoot!.innerHTML).toBe( + `
prop
`, + ) + }) - // container.appendChild(e1) - // container.appendChild(e2) + test('set DOM property before resolve', async () => { + const E = defineVaporCustomElement( + defineVaporAsyncComponent(() => { + return Promise.resolve({ + props: ['msg'], + setup(props: any) { + expect(typeof props.msg).toBe('string') + const n0 = template('
', true)() as any + const x0 = txt(n0) as any + renderEffect(() => setText(x0, props.msg)) + return n0 + }, + }) + }), + ) + customElements.define('my-el-async-2', E) - // // set property after connect but before resolve - // e2.msg = 'world' + const e1 = new E() as any - // await new Promise(r => setTimeout(r)) + // set property before connect + e1.msg = 'hello' - // expect(e1.shadowRoot!.innerHTML).toBe(`
hello
`) - // expect(e2.shadowRoot!.innerHTML).toBe(`
world
`) + const e2 = new E() as any - // e1.msg = 'world' - // expect(e1.shadowRoot!.innerHTML).toBe(`
world
`) + container.appendChild(e1) + container.appendChild(e2) - // e2.msg = 'hello' - // expect(e2.shadowRoot!.innerHTML).toBe(`
hello
`) - // }) + // set property after connect but before resolve + e2.msg = 'world' - // test('Number prop casting before resolve', async () => { - // const E = defineVaporCustomElement( - // defineVaporAsyncComponent(() => { - // return Promise.resolve({ - // props: { n: Number }, - // setup(props) { - // expect(props.n).toBe(20) - // }, - // render(this: any) { - // return h('div', this.n + ',' + typeof this.n) - // }, - // }) - // }), - // ) - // customElements.define('my-el-async-3', E) - // container.innerHTML = `` + await new Promise(r => setTimeout(r)) - // await new Promise(r => setTimeout(r)) + expect(e1.shadowRoot!.innerHTML).toBe(`
hello
`) + expect(e2.shadowRoot!.innerHTML).toBe(`
world
`) - // const e = container.childNodes[0] as VaporElement - // expect(e.shadowRoot!.innerHTML).toBe(`
20,number
`) - // }) + e1.msg = 'world' + expect(e1.shadowRoot!.innerHTML).toBe(`
world
`) - // test('with slots', async () => { - // const E = defineVaporCustomElement( - // defineVaporAsyncComponent(() => { - // return Promise.resolve({ - // render(this: any) { - // return [ - // h('div', null, [ - // renderSlot(this.$slots, 'default', undefined, () => [ - // h('div', 'fallback'), - // ]), - // ]), - // h('div', null, renderSlot(this.$slots, 'named')), - // ] - // }, - // }) - // }), - // ) - // customElements.define('my-el-async-slots', E) - // container.innerHTML = `hi` + e2.msg = 'hello' + expect(e2.shadowRoot!.innerHTML).toBe(`
hello
`) + }) - // await new Promise(r => setTimeout(r)) + test('Number prop casting before resolve', async () => { + const E = defineVaporCustomElement( + defineVaporAsyncComponent(() => { + return Promise.resolve({ + props: { n: Number }, + setup(props: any) { + expect(props.n).toBe(20) + const n0 = template('
', true)() as any + const x0 = txt(n0) as any + renderEffect(() => setText(x0, `${props.n},${typeof props.n}`)) + return n0 + }, + }) + }), + ) + customElements.define('my-el-async-3', E) + container.innerHTML = `` - // const e = container.childNodes[0] as VaporElement - // expect(e.shadowRoot!.innerHTML).toBe( - // `
fallback
`, - // ) - // }) - // }) + await new Promise(r => setTimeout(r)) - // describe('shadowRoot: false', () => { - // const E = defineVaporCustomElement({ - // shadowRoot: false, - // props: { - // msg: { - // type: String, - // default: 'hello', - // }, - // }, - // render() { - // return h('div', this.msg) - // }, - // }) - // customElements.define('my-el-shadowroot-false', E) + const e = container.childNodes[0] as VaporElement + expect(e.shadowRoot!.innerHTML).toBe(`
20,number
`) + }) - // test('should work', async () => { - // function raf() { - // return new Promise(resolve => { - // requestAnimationFrame(resolve) - // }) - // } + test('with slots', async () => { + const E = defineVaporCustomElement( + defineVaporAsyncComponent(() => { + return Promise.resolve({ + setup() { + const t0 = template('
fallback
') + const t1 = template('
') + const n3 = t1() as any + setInsertionState(n3, null) + createSlot('default', null, () => { + const n2 = t0() + return n2 + }) + const n5 = t1() as any + setInsertionState(n5, null) + createSlot('named', null) + return [n3, n5] + }, + }) + }), + ) + customElements.define('my-el-async-slots', E) + container.innerHTML = `hi` - // container.innerHTML = `` - // const e = container.childNodes[0] as VaporElement - // await raf() - // expect(e).toBeInstanceOf(E) - // expect(e._instance).toBeTruthy() - // expect(e.innerHTML).toBe(`
hello
`) - // expect(e.shadowRoot).toBe(null) - // }) + await new Promise(r => setTimeout(r)) - // const toggle = ref(true) - // const ES = defineVaporCustomElement( - // { - // render() { - // return [ - // renderSlot(this.$slots, 'default'), - // toggle.value ? renderSlot(this.$slots, 'named') : null, - // renderSlot(this.$slots, 'omitted', {}, () => [ - // h('div', 'fallback'), - // ]), - // ] - // }, - // }, - // { shadowRoot: false }, - // ) - // customElements.define('my-el-shadowroot-false-slots', ES) - - // test('should render slots', async () => { - // container.innerHTML = - // `` + - // `defaulttext` + - // `
named
` + - // `
` - // const e = container.childNodes[0] as VaporElement - // // native slots allocation does not affect innerHTML, so we just - // // verify that we've rendered the correct native slots here... - // expect(e.innerHTML).toBe( - // `defaulttext` + - // `
named
` + - // `
fallback
`, - // ) - - // toggle.value = false - // await nextTick() - // expect(e.innerHTML).toBe( - // `defaulttext` + `` + `
fallback
`, - // ) - // }) + const e = container.childNodes[0] as VaporElement + expect(e.shadowRoot!.innerHTML).toBe( + `
` + + `
fallback
` + + `
` + + `` + + `
`, + ) + }) + }) - // test('render nested customElement w/ shadowRoot false', async () => { - // const calls: string[] = [] + describe('shadowRoot: false', () => { + const E = defineVaporCustomElement({ + shadowRoot: false, + props: { + msg: { + type: String, + default: 'hello', + }, + }, + setup(props: any) { + const n0 = template('
')() as any + const x0 = txt(n0) as any + renderEffect(() => setText(x0, toDisplayString(props.msg))) + return n0 + }, + }) + customElements.define('my-el-shadowroot-false', E) - // const Child = defineVaporCustomElement( - // { - // setup() { - // calls.push('child rendering') - // onMounted(() => { - // calls.push('child mounted') - // }) - // }, - // render() { - // return renderSlot(this.$slots, 'default') - // }, - // }, - // { shadowRoot: false }, - // ) - // customElements.define('my-child', Child) + test('should work', async () => { + function raf() { + return new Promise(resolve => { + requestAnimationFrame(resolve) + }) + } - // const Parent = defineVaporCustomElement( - // { - // setup() { - // calls.push('parent rendering') - // onMounted(() => { - // calls.push('parent mounted') - // }) - // }, - // render() { - // return renderSlot(this.$slots, 'default') - // }, - // }, - // { shadowRoot: false }, - // ) - // customElements.define('my-parent', Parent) + container.innerHTML = `` + const e = container.childNodes[0] as VaporElement + await raf() + expect(e).toBeInstanceOf(E) + expect(e._instance).toBeTruthy() + expect(e.innerHTML).toBe(`
hello
`) + expect(e.shadowRoot).toBe(null) + }) - // const App = { - // render() { - // return h('my-parent', null, { - // default: () => [ - // h('my-child', null, { - // default: () => [h('span', null, 'default')], - // }), - // ], - // }) - // }, - // } - // const app = createVaporApp(App) - // app.mount(container) - // await nextTick() - // const e = container.childNodes[0] as VaporElement - // expect(e.innerHTML).toBe( - // `default`, - // ) - // expect(calls).toEqual([ - // 'parent rendering', - // 'parent mounted', - // 'child rendering', - // 'child mounted', - // ]) - // app.unmount() - // }) + const toggle = ref(true) + const ES = defineVaporCustomElement( + { + setup() { + const n0 = createSlot('default') + const n1 = createIf( + () => toggle.value, + () => createSlot('named'), + ) + const n2 = createSlot('omitted', null, () => + template('
fallback
')(), + ) + return [n0, n1, n2] + }, + }, + { shadowRoot: false } as any, + ) + customElements.define('my-el-shadowroot-false-slots', ES) - // test('render nested Teleport w/ shadowRoot false', async () => { - // const target = document.createElement('div') - // const Child = defineVaporCustomElement( - // { - // render() { - // return h( - // Teleport, - // { to: target }, - // { - // default: () => [renderSlot(this.$slots, 'default')], - // }, - // ) - // }, - // }, - // { shadowRoot: false }, - // ) - // customElements.define('my-el-teleport-child', Child) - // const Parent = defineVaporCustomElement( - // { - // render() { - // return renderSlot(this.$slots, 'default') - // }, - // }, - // { shadowRoot: false }, - // ) - // customElements.define('my-el-teleport-parent', Parent) + test.todo('should render slots', async () => { + container.innerHTML = + `` + + `defaulttext` + + `
named
` + + `
` + const e = container.childNodes[0] as VaporElement + // native slots allocation does not affect innerHTML, so we just + // verify that we've rendered the correct native slots here... + expect(e.innerHTML).toBe( + `defaulttext` + + `
named
` + + `
fallback
`, + ) - // const App = { - // render() { - // return h('my-el-teleport-parent', null, { - // default: () => [ - // h('my-el-teleport-child', null, { - // default: () => [h('span', null, 'default')], - // }), - // ], - // }) - // }, - // } - // const app = createVaporApp(App) - // app.mount(container) - // await nextTick() - // expect(target.innerHTML).toBe(`default`) - // app.unmount() - // }) + toggle.value = false + await nextTick() + expect(e.innerHTML).toBe( + `defaulttext` + `` + `
fallback
`, + ) + }) - // test('render two Teleports w/ shadowRoot false', async () => { - // const target1 = document.createElement('div') - // const target2 = document.createElement('span') - // const Child = defineVaporCustomElement( - // { - // render() { - // return [ - // h(Teleport, { to: target1 }, [renderSlot(this.$slots, 'header')]), - // h(Teleport, { to: target2 }, [renderSlot(this.$slots, 'body')]), - // ] - // }, - // }, - // { shadowRoot: false }, - // ) - // customElements.define('my-el-two-teleport-child', Child) + // test('render nested customElement w/ shadowRoot false', async () => { + // const calls: string[] = [] - // const App = { - // render() { - // return h('my-el-two-teleport-child', null, { - // default: () => [ - // h('div', { slot: 'header' }, 'header'), - // h('span', { slot: 'body' }, 'body'), - // ], - // }) - // }, - // } - // const app = createVaporApp(App) - // app.mount(container) - // await nextTick() - // expect(target1.outerHTML).toBe( - // `
header
`, - // ) - // expect(target2.outerHTML).toBe( - // `body`, - // ) - // app.unmount() - // }) + // const Child = defineVaporCustomElement( + // { + // setup() { + // calls.push('child rendering') + // onMounted(() => { + // calls.push('child mounted') + // }) + // }, + // render() { + // return renderSlot(this.$slots, 'default') + // }, + // }, + // { shadowRoot: false }, + // ) + // customElements.define('my-child', Child) - // test('render two Teleports w/ shadowRoot false (with disabled)', async () => { - // const target1 = document.createElement('div') - // const target2 = document.createElement('span') - // const Child = defineVaporCustomElement( - // { - // render() { - // return [ - // // with disabled: true - // h(Teleport, { to: target1, disabled: true }, [ - // renderSlot(this.$slots, 'header'), - // ]), - // h(Teleport, { to: target2 }, [renderSlot(this.$slots, 'body')]), - // ] - // }, - // }, - // { shadowRoot: false }, - // ) - // customElements.define('my-el-two-teleport-child-0', Child) + // const Parent = defineVaporCustomElement( + // { + // setup() { + // calls.push('parent rendering') + // onMounted(() => { + // calls.push('parent mounted') + // }) + // }, + // render() { + // return renderSlot(this.$slots, 'default') + // }, + // }, + // { shadowRoot: false }, + // ) + // customElements.define('my-parent', Parent) + + // const App = { + // render() { + // return h('my-parent', null, { + // default: () => [ + // h('my-child', null, { + // default: () => [h('span', null, 'default')], + // }), + // ], + // }) + // }, + // } + // const app = createVaporApp(App) + // app.mount(container) + // await nextTick() + // const e = container.childNodes[0] as VaporElement + // expect(e.innerHTML).toBe( + // `default`, + // ) + // expect(calls).toEqual([ + // 'parent rendering', + // 'parent mounted', + // 'child rendering', + // 'child mounted', + // ]) + // app.unmount() + // }) - // const App = { - // render() { - // return h('my-el-two-teleport-child-0', null, { - // default: () => [ - // h('div', { slot: 'header' }, 'header'), - // h('span', { slot: 'body' }, 'body'), - // ], - // }) - // }, - // } - // const app = createVaporApp(App) - // app.mount(container) - // await nextTick() - // expect(target1.outerHTML).toBe(`
`) - // expect(target2.outerHTML).toBe( - // `body`, - // ) - // app.unmount() - // }) + // test('render nested Teleport w/ shadowRoot false', async () => { + // const target = document.createElement('div') + // const Child = defineVaporCustomElement( + // { + // render() { + // return h( + // Teleport, + // { to: target }, + // { + // default: () => [renderSlot(this.$slots, 'default')], + // }, + // ) + // }, + // }, + // { shadowRoot: false }, + // ) + // customElements.define('my-el-teleport-child', Child) + // const Parent = defineVaporCustomElement( + // { + // render() { + // return renderSlot(this.$slots, 'default') + // }, + // }, + // { shadowRoot: false }, + // ) + // customElements.define('my-el-teleport-parent', Parent) + + // const App = { + // render() { + // return h('my-el-teleport-parent', null, { + // default: () => [ + // h('my-el-teleport-child', null, { + // default: () => [h('span', null, 'default')], + // }), + // ], + // }) + // }, + // } + // const app = createVaporApp(App) + // app.mount(container) + // await nextTick() + // expect(target.innerHTML).toBe(`default`) + // app.unmount() + // }) - // test('toggle nested custom element with shadowRoot: false', async () => { - // customElements.define( - // 'my-el-child-shadow-false', - // defineVaporCustomElement( - // { - // render(ctx: any) { - // return h('div', null, [renderSlot(ctx.$slots, 'default')]) - // }, - // }, - // { shadowRoot: false }, - // ), - // ) - // const ChildWrapper = { - // render() { - // return h('my-el-child-shadow-false', null, 'child') - // }, - // } + // test('render two Teleports w/ shadowRoot false', async () => { + // const target1 = document.createElement('div') + // const target2 = document.createElement('span') + // const Child = defineVaporCustomElement( + // { + // render() { + // return [ + // h(Teleport, { to: target1 }, [renderSlot(this.$slots, 'header')]), + // h(Teleport, { to: target2 }, [renderSlot(this.$slots, 'body')]), + // ] + // }, + // }, + // { shadowRoot: false }, + // ) + // customElements.define('my-el-two-teleport-child', Child) + + // const App = { + // render() { + // return h('my-el-two-teleport-child', null, { + // default: () => [ + // h('div', { slot: 'header' }, 'header'), + // h('span', { slot: 'body' }, 'body'), + // ], + // }) + // }, + // } + // const app = createVaporApp(App) + // app.mount(container) + // await nextTick() + // expect(target1.outerHTML).toBe( + // `
header
`, + // ) + // expect(target2.outerHTML).toBe( + // `body`, + // ) + // app.unmount() + // }) - // customElements.define( - // 'my-el-parent-shadow-false', - // defineVaporCustomElement( - // { - // props: { - // isShown: { type: Boolean, required: true }, - // }, - // render(ctx: any, _: any, $props: any) { - // return $props.isShown - // ? h('div', { key: 0 }, [renderSlot(ctx.$slots, 'default')]) - // : null - // }, - // }, - // { shadowRoot: false }, - // ), - // ) - // const ParentWrapper = { - // props: { - // isShown: { type: Boolean, required: true }, - // }, - // render(ctx: any, _: any, $props: any) { - // return h('my-el-parent-shadow-false', { isShown: $props.isShown }, [ - // renderSlot(ctx.$slots, 'default'), - // ]) - // }, - // } + // test('render two Teleports w/ shadowRoot false (with disabled)', async () => { + // const target1 = document.createElement('div') + // const target2 = document.createElement('span') + // const Child = defineVaporCustomElement( + // { + // render() { + // return [ + // // with disabled: true + // h(Teleport, { to: target1, disabled: true }, [ + // renderSlot(this.$slots, 'header'), + // ]), + // h(Teleport, { to: target2 }, [renderSlot(this.$slots, 'body')]), + // ] + // }, + // }, + // { shadowRoot: false }, + // ) + // customElements.define('my-el-two-teleport-child-0', Child) + + // const App = { + // render() { + // return h('my-el-two-teleport-child-0', null, { + // default: () => [ + // h('div', { slot: 'header' }, 'header'), + // h('span', { slot: 'body' }, 'body'), + // ], + // }) + // }, + // } + // const app = createVaporApp(App) + // app.mount(container) + // await nextTick() + // expect(target1.outerHTML).toBe(`
`) + // expect(target2.outerHTML).toBe( + // `body`, + // ) + // app.unmount() + // }) - // const isShown = ref(true) - // const App = { - // render() { - // return h(ParentWrapper, { isShown: isShown.value } as any, { - // default: () => [h(ChildWrapper)], - // }) - // }, - // } - // const container = document.createElement('div') - // document.body.appendChild(container) - // const app = createVaporApp(App) - // app.mount(container) - // expect(container.innerHTML).toBe( - // `` + - // `
` + - // `` + - // `
child
` + - // `
` + - // `
` + - // `
`, - // ) + // test('toggle nested custom element with shadowRoot: false', async () => { + // customElements.define( + // 'my-el-child-shadow-false', + // defineVaporCustomElement( + // { + // render(ctx: any) { + // return h('div', null, [renderSlot(ctx.$slots, 'default')]) + // }, + // }, + // { shadowRoot: false }, + // ), + // ) + // const ChildWrapper = { + // render() { + // return h('my-el-child-shadow-false', null, 'child') + // }, + // } + + // customElements.define( + // 'my-el-parent-shadow-false', + // defineVaporCustomElement( + // { + // props: { + // isShown: { type: Boolean, required: true }, + // }, + // render(ctx: any, _: any, $props: any) { + // return $props.isShown + // ? h('div', { key: 0 }, [renderSlot(ctx.$slots, 'default')]) + // : null + // }, + // }, + // { shadowRoot: false }, + // ), + // ) + // const ParentWrapper = { + // props: { + // isShown: { type: Boolean, required: true }, + // }, + // render(ctx: any, _: any, $props: any) { + // return h('my-el-parent-shadow-false', { isShown: $props.isShown }, [ + // renderSlot(ctx.$slots, 'default'), + // ]) + // }, + // } + + // const isShown = ref(true) + // const App = { + // render() { + // return h(ParentWrapper, { isShown: isShown.value } as any, { + // default: () => [h(ChildWrapper)], + // }) + // }, + // } + // const container = document.createElement('div') + // document.body.appendChild(container) + // const app = createVaporApp(App) + // app.mount(container) + // expect(container.innerHTML).toBe( + // `` + + // `
` + + // `` + + // `
child
` + + // `
` + + // `
` + + // `
`, + // ) - // isShown.value = false - // await nextTick() - // expect(container.innerHTML).toBe( - // ``, - // ) + // isShown.value = false + // await nextTick() + // expect(container.innerHTML).toBe( + // ``, + // ) - // isShown.value = true - // await nextTick() - // expect(container.innerHTML).toBe( - // `` + - // `
` + - // `` + - // `
child
` + - // `
` + - // `
` + - // `
`, - // ) - // }) - // }) + // isShown.value = true + // await nextTick() + // expect(container.innerHTML).toBe( + // `` + + // `
` + + // `` + + // `
child
` + + // `
` + + // `
` + + // `
`, + // ) + // }) + }) // describe('helpers', () => { // test('useHost', () => { -- 2.47.3