@@ -2,22 +2,105 @@ import { prop, render } from '@fictjs/runtime'
22import { createSignal } from '@fictjs/runtime/advanced'
33import React from 'react'
44
5- import { installReactIslands , reactify , reactify$ } from '../../src/index'
5+ import {
6+ ReactIsland ,
7+ installReactIslands ,
8+ reactAction$ ,
9+ reactify ,
10+ reactify$ ,
11+ } from '../../src/index'
12+
13+ interface E2EState {
14+ lifecycleMounts : number
15+ lifecycleUnmounts : number
16+ actionCalls : string [ ]
17+ }
18+
19+ const globalHost = globalThis as typeof globalThis & {
20+ __FICT_E2E__ ?: E2EState
21+ }
22+
23+ function ensureE2EState ( ) : E2EState {
24+ if ( ! globalHost . __FICT_E2E__ ) {
25+ globalHost . __FICT_E2E__ = {
26+ lifecycleMounts : 0 ,
27+ lifecycleUnmounts : 0 ,
28+ actionCalls : [ ] ,
29+ }
30+ }
31+
32+ return globalHost . __FICT_E2E__
33+ }
34+
35+ const e2eState = ensureE2EState ( )
36+ e2eState . lifecycleMounts = 0
37+ e2eState . lifecycleUnmounts = 0
38+ e2eState . actionCalls = [ ]
39+
40+ const remoteModuleUrl = new URL ( './remote-widget.tsx' , import . meta. url ) . href
41+
42+ let actionCallSignal : ( ( value ?: number ) => number ) | null = null
43+
44+ export function recordE2EAction ( payload : string ) {
45+ const state = ensureE2EState ( )
46+ state . actionCalls . push ( payload )
47+
48+ if ( actionCallSignal ) {
49+ const current = actionCallSignal ( )
50+ actionCallSignal ( current + 1 )
51+ }
52+ }
653
754const RemoteIsland = reactify$ < { label : string ; count : number } > ( {
8- module : new URL ( './remote-widget.tsx' , import . meta . url ) . href ,
55+ module : remoteModuleUrl ,
956 export : 'RemoteWidget' ,
1057 ssr : false ,
1158 client : 'load' ,
1259} )
1360
61+ const ActionIsland = reactify$ < { label : string ; onAction : ReturnType < typeof reactAction$ > } > ( {
62+ module : remoteModuleUrl ,
63+ export : 'ActionWidget' ,
64+ ssr : false ,
65+ client : 'load' ,
66+ } )
67+
1468const Eager = reactify ( ( { count } : { count : number } ) => {
1569 return React . createElement ( 'div' , { 'data-testid' : 'eager-value' } , `eager:${ count } ` )
1670} )
1771
72+ function StrategyView ( props : { testId : string ; label : string } ) {
73+ return React . createElement ( 'div' , { 'data-testid' : props . testId } , props . label )
74+ }
75+
76+ function ReactIslandLabel ( props : { label : string } ) {
77+ return React . createElement ( 'div' , { 'data-testid' : 'react-island-value' } , props . label )
78+ }
79+
80+ const HoverStrategyIsland = reactify ( StrategyView , {
81+ client : 'hover' ,
82+ tagName : 'button' ,
83+ } )
84+
85+ const EventStrategyIsland = reactify ( StrategyView , {
86+ client : 'event' ,
87+ event : 'dblclick' ,
88+ tagName : 'button' ,
89+ } )
90+
91+ const VisibleStrategyIsland = reactify ( StrategyView , {
92+ client : 'visible' ,
93+ visibleRootMargin : '0px' ,
94+ } )
95+
1896function App ( ) {
1997 const eagerCount = createSignal ( 0 )
2098 const qrlCount = createSignal ( 0 )
99+ const islandLabel = createSignal ( 'alpha' )
100+ const signalMountGate = createSignal ( false )
101+ const actionCount = createSignal ( 0 )
102+
103+ actionCallSignal = actionCount
21104
22105 return {
23106 type : 'div' ,
@@ -52,6 +135,57 @@ function App() {
52135 count : prop ( ( ) => qrlCount ( ) ) ,
53136 } ,
54137 } ,
138+ {
139+ type : 'button' ,
140+ props : {
141+ id : 'react-island-swap' ,
142+ onClick : ( ) => islandLabel ( islandLabel ( ) === 'alpha' ? 'beta' : 'alpha' ) ,
143+ children : 'swap react island' ,
144+ } ,
145+ } ,
146+ {
147+ type : ReactIsland ,
148+ props : {
149+ component : ReactIslandLabel ,
150+ props : ( ) => ( {
151+ label : islandLabel ( ) ,
152+ } ) ,
153+ } ,
154+ } ,
155+ {
156+ type : 'button' ,
157+ props : {
158+ id : 'signal-mount' ,
159+ onClick : ( ) => signalMountGate ( true ) ,
160+ children : 'mount signal strategy' ,
161+ } ,
162+ } ,
163+ {
164+ type : ReactIsland ,
165+ props : {
166+ component : StrategyView ,
167+ client : 'signal' ,
168+ signal : ( ) => signalMountGate ( ) ,
169+ props : {
170+ testId : 'signal-strategy-value' ,
171+ label : 'signal-mounted' ,
172+ } ,
173+ } ,
174+ } ,
175+ {
176+ type : ActionIsland ,
177+ props : {
178+ label : 'main' ,
179+ onAction : reactAction$ ( import . meta. url , 'recordE2EAction' ) ,
180+ } ,
181+ } ,
182+ {
183+ type : 'div' ,
184+ props : {
185+ 'data-testid' : 'action-call-count' ,
186+ children : prop ( ( ) => String ( actionCount ( ) ) ) ,
187+ } ,
188+ } ,
55189 ] ,
56190 } ,
57191 }
@@ -60,34 +194,104 @@ function App() {
60194const app = document . getElementById ( 'app' ) as HTMLElement
61195render ( ( ) => ( { type : App , props : { } } ) , app )
62196
63- const loaderHost = document . getElementById ( 'loader-island' ) as HTMLElement
64- const loaderQrl = `${ new URL ( './remote-widget.tsx' , import . meta. url ) . href } #LoaderWidget`
65- let loaderCount = 1
197+ const hoverRoot = document . createElement ( 'div' )
198+ hoverRoot . id = 'hover-root'
199+ document . body . appendChild ( hoverRoot )
200+ render (
201+ ( ) => ( {
202+ type : HoverStrategyIsland ,
203+ props : {
204+ testId : 'hover-strategy-value' ,
205+ label : 'hover-mounted' ,
206+ } ,
207+ } ) ,
208+ hoverRoot ,
209+ )
210+
211+ const eventRoot = document . createElement ( 'div' )
212+ eventRoot . id = 'event-root'
213+ document . body . appendChild ( eventRoot )
214+ render (
215+ ( ) => ( {
216+ type : EventStrategyIsland ,
217+ props : {
218+ testId : 'event-strategy-value' ,
219+ label : 'event-mounted' ,
220+ } ,
221+ } ) ,
222+ eventRoot ,
223+ )
224+
225+ const visibleRoot = document . createElement ( 'div' )
226+ visibleRoot . id = 'visible-root'
227+ visibleRoot . style . marginTop = '2200px'
228+ visibleRoot . style . minHeight = '20px'
229+ document . body . appendChild ( visibleRoot )
230+ render (
231+ ( ) => ( {
232+ type : VisibleStrategyIsland ,
233+ props : {
234+ testId : 'visible-strategy-value' ,
235+ label : 'visible-mounted' ,
236+ } ,
237+ } ) ,
238+ visibleRoot ,
239+ )
66240
67241const encode = ( value : Record < string , unknown > ) => encodeURIComponent ( JSON . stringify ( value ) )
68242
243+ function appendControlButton ( id : string , text : string , onClick : ( ) => void ) {
244+ const button = document . createElement ( 'button' )
245+ button . id = id
246+ button . textContent = text
247+ button . addEventListener ( 'click' , onClick )
248+ document . body . appendChild ( button )
249+ }
250+
251+ const loaderHost = document . getElementById ( 'loader-island' ) as HTMLElement
252+ const loaderQrl = `${ remoteModuleUrl } #LoaderWidget`
253+ const loaderAltQrl = `${ remoteModuleUrl } #LoaderWidgetAlt`
254+ const loaderLifecycleQrl = `${ remoteModuleUrl } #LifecycleWidget`
255+ let loaderCount = 1
256+ let dynamicLoaderHost : HTMLElement | null = null
257+
69258loaderHost . setAttribute ( 'data-fict-react' , loaderQrl )
70259loaderHost . setAttribute ( 'data-fict-react-client' , 'load' )
71260loaderHost . setAttribute ( 'data-fict-react-ssr' , '0' )
72261loaderHost . setAttribute ( 'data-fict-react-props' , encode ( { label : 'loader' , count : loaderCount } ) )
73262
74- const button = document . createElement ( 'button' )
75- button . id = 'loader-inc'
76- button . textContent = 'inc loader'
77- button . addEventListener ( 'click' , ( ) => {
263+ appendControlButton ( 'loader-inc' , 'inc loader' , ( ) => {
78264 loaderCount += 1
79265 loaderHost . setAttribute ( 'data-fict-react-props' , encode ( { label : 'loader' , count : loaderCount } ) )
80266} )
81- document . body . appendChild ( button )
82267
83- const immutableMutationButton = document . createElement ( 'button' )
84- immutableMutationButton . id = 'loader-mutate-immutable'
85- immutableMutationButton . textContent = 'mutate loader immutable attrs'
86- immutableMutationButton . addEventListener ( 'click' , ( ) => {
268+ appendControlButton ( 'loader-switch-qrl' , 'switch loader qrl' , ( ) => {
269+ loaderHost . setAttribute ( 'data-fict-react' , loaderAltQrl )
270+ loaderHost . setAttribute ( 'data-fict-react-props' , encode ( { label : 'loader' , count : loaderCount } ) )
271+ } )
272+
273+ appendControlButton ( 'loader-add-dynamic' , 'add dynamic loader host' , ( ) => {
274+ if ( dynamicLoaderHost ?. isConnected ) return
275+
276+ dynamicLoaderHost = document . createElement ( 'div' )
277+ dynamicLoaderHost . id = 'loader-dynamic-host'
278+ dynamicLoaderHost . setAttribute ( 'data-fict-react' , loaderLifecycleQrl )
279+ dynamicLoaderHost . setAttribute ( 'data-fict-react-client' , 'load' )
280+ dynamicLoaderHost . setAttribute ( 'data-fict-react-ssr' , '0' )
281+ dynamicLoaderHost . setAttribute ( 'data-fict-react-props' , encode ( { label : 'dynamic' } ) )
282+ document . body . appendChild ( dynamicLoaderHost )
283+ } )
284+
285+ appendControlButton ( 'loader-remove-dynamic' , 'remove dynamic loader host' , ( ) => {
286+ if ( ! dynamicLoaderHost ) return
287+ dynamicLoaderHost . remove ( )
288+ dynamicLoaderHost = null
289+ } )
290+
291+ appendControlButton ( 'loader-mutate-immutable' , 'mutate loader immutable attrs' , ( ) => {
87292 loaderHost . setAttribute ( 'data-fict-react-client' , 'idle' )
88293 loaderHost . setAttribute ( 'data-fict-react-ssr' , '1' )
89294 loaderHost . setAttribute ( 'data-fict-react-prefix' , 'mutated-prefix' )
90295} )
91- document . body . appendChild ( immutableMutationButton )
92296
93297installReactIslands ( )
0 commit comments