@@ -13,6 +13,7 @@ import { Binder } from '../../src/bind/Binder'
1313import { hasSwitch } from '../../src/bind/switch'
1414import { Parser } from '../../src/parser/Parser'
1515import { htmlEqual } from '../common/html-equal'
16+ import { createDom } from '../minidom/createDom'
1617
1718test ( 'should render components with reactive properties' , ( ) => {
1819 const root = document . createElement ( 'div' )
@@ -705,6 +706,142 @@ test('component inheritAttrs merges class and style from host onto inheritor roo
705706 expect ( div . style . marginTop ) . toBe ( '5px' )
706707} )
707708
709+ test ( 'component attribute fallthrough can carry :r-teleport to component root' , ( ) => {
710+ const cleanup = createDom (
711+ '<html><body><div id="app"></div><div id="teleport-host"></div></body></html>' ,
712+ )
713+ try {
714+ const root = document . querySelector ( '#app' ) as HTMLElement
715+ const teleComp = defineComponent (
716+ html `< section class ="tele-root "> teleported</ section > ` ,
717+ )
718+
719+ createApp (
720+ {
721+ components : { teleComp } ,
722+ target : '#teleport-host' ,
723+ } ,
724+ {
725+ element : root ,
726+ template : html `< main >
727+ < TeleComp
728+ :r-teleport ="target "
729+ class ="from-host "
730+ data-origin ="host "
731+ > </ TeleComp >
732+ </ main > ` ,
733+ } ,
734+ )
735+
736+ const host = document . querySelector ( '#teleport-host' ) as HTMLElement
737+ const moved = host . querySelector ( '.tele-root' ) as HTMLElement | null
738+ expect ( moved ) . toBeTruthy ( )
739+ expect ( moved ?. classList . contains ( 'from-host' ) ) . toBe ( true )
740+ expect ( moved ?. getAttribute ( 'data-origin' ) ) . toBe ( 'host' )
741+ expect ( root . innerHTML ) . toContain ( "teleported => '#teleport-host'" )
742+ } finally {
743+ cleanup ( )
744+ }
745+ } )
746+
747+ test ( 'nested component tree teleports when parent host binds :r-teleport' , ( ) => {
748+ const cleanup = createDom (
749+ '<html><body><div id="app"></div><div id="teleport-host"></div></body></html>' ,
750+ )
751+ try {
752+ const root = document . querySelector ( '#app' ) as HTMLElement
753+
754+ const childComp = defineComponent (
755+ html `< span class ="nested-child "> nested payload</ span > ` ,
756+ )
757+ const parentComp = defineComponent (
758+ html `< section class ="parent-root " r-inherit >
759+ < ChildComp > </ ChildComp >
760+ </ section > ` ,
761+ )
762+
763+ createApp (
764+ {
765+ components : { parentComp, childComp } ,
766+ target : '#teleport-host' ,
767+ } ,
768+ {
769+ element : root ,
770+ template : html `< main >
771+ < ParentComp
772+ :r-teleport ="target "
773+ class ="from-parent-host "
774+ data-parent ="yes "
775+ > </ ParentComp >
776+ </ main > ` ,
777+ } ,
778+ )
779+
780+ const host = document . querySelector ( '#teleport-host' ) as HTMLElement
781+ const moved = host . querySelector ( '.parent-root' ) as HTMLElement | null
782+ const nested = host . querySelector ( '.nested-child' ) as HTMLElement | null
783+
784+ expect ( moved ) . toBeTruthy ( )
785+ expect ( moved ?. classList . contains ( 'from-parent-host' ) ) . toBe ( true )
786+ expect ( moved ?. getAttribute ( 'data-parent' ) ) . toBe ( 'yes' )
787+ expect ( nested ?. textContent ?. trim ( ) ) . toBe ( 'nested payload' )
788+ expect ( root . innerHTML ) . toContain ( "teleported => '#teleport-host'" )
789+ } finally {
790+ cleanup ( )
791+ }
792+ } )
793+
794+ test ( 'nested component tree teleports when parent binds :r-teleport on ChildComp' , ( ) => {
795+ const cleanup = createDom (
796+ '<html><body><div id="app"></div><div id="teleport-host"></div></body></html>' ,
797+ )
798+ try {
799+ const root = document . querySelector ( '#app' ) as HTMLElement
800+
801+ const childComp = defineComponent (
802+ html `< article class ="child-root ">
803+ < span class ="inner-child "> inner payload</ span >
804+ </ article > ` ,
805+ )
806+ const parentComp = defineComponent (
807+ html `< section class ="parent-shell ">
808+ < ChildComp
809+ :r-teleport ="target "
810+ class ="child-from-parent "
811+ data-from-parent ="yes "
812+ > </ ChildComp >
813+ </ section > ` ,
814+ {
815+ context : ( ) => ( {
816+ target : '#teleport-host' ,
817+ } ) ,
818+ } ,
819+ )
820+
821+ createApp (
822+ {
823+ components : { parentComp, childComp } ,
824+ } ,
825+ {
826+ element : root ,
827+ template : html `< main > < ParentComp > </ ParentComp > </ main > ` ,
828+ } ,
829+ )
830+ const host = document . querySelector ( '#teleport-host' ) as HTMLElement
831+ const moved = host . querySelector ( '.child-root' ) as HTMLElement | null
832+ const nested = host . querySelector ( '.inner-child' ) as HTMLElement | null
833+
834+ expect ( moved ) . toBeTruthy ( )
835+ expect ( moved ?. classList . contains ( 'child-from-parent' ) ) . toBe ( true )
836+ expect ( moved ?. getAttribute ( 'data-from-parent' ) ) . toBe ( 'yes' )
837+ expect ( nested ?. textContent ?. trim ( ) ) . toBe ( 'inner payload' )
838+ expect ( root . querySelector ( '.parent-shell' ) ) . toBeTruthy ( )
839+ expect ( root . innerHTML ) . toContain ( "teleported => '#teleport-host'" )
840+ } finally {
841+ cleanup ( )
842+ }
843+ } )
844+
708845test ( 'component named slot fallback renders when slot content is not provided' , ( ) => {
709846 const root = document . createElement ( 'div' )
710847 const slotComp = defineComponent (
0 commit comments