@@ -297,6 +297,269 @@ describe('Store', () => {
297297 } ) ;
298298 } ) ;
299299
300+ describe ( 'Activity hidden state' , ( ) => {
301+ // @reactVersion >= 19
302+ it ( 'should mark Activity subtree elements as hidden when mode is hidden' , async ( ) => {
303+ const Activity = React . Activity || React . unstable_Activity ;
304+
305+ function Child ( ) {
306+ return < div > child</ div > ;
307+ }
308+
309+ function App ( { hidden} ) {
310+ return (
311+ < Activity mode = { hidden ? 'hidden' : 'visible' } >
312+ < Child />
313+ </ Activity >
314+ ) ;
315+ }
316+
317+ await actAsync ( ( ) => {
318+ render ( < App hidden = { true } /> ) ;
319+ } ) ;
320+
321+ // Activity element should be marked as hidden and collapsed
322+ const activityElement = store . getElementAtIndex ( 1 ) ;
323+ expect ( activityElement . displayName ) . toBe ( 'Activity' ) ;
324+ expect ( activityElement . isActivityHidden ) . toBe ( true ) ;
325+ expect ( activityElement . isInsideHiddenActivity ) . toBe ( false ) ;
326+ expect ( activityElement . isCollapsed ) . toBe ( true ) ;
327+
328+ // Expand to access children
329+ store . toggleIsCollapsed ( activityElement . id , false ) ;
330+
331+ // Children should still be in the tree but marked as inside hidden Activity
332+ const childElement = store . getElementAtIndex ( 2 ) ;
333+ expect ( childElement . displayName ) . toBe ( 'Child' ) ;
334+ expect ( childElement . isInsideHiddenActivity ) . toBe ( true ) ;
335+ } ) ;
336+
337+ // @reactVersion >= 19
338+ it ( 'should not mark Activity subtree as hidden when mode is visible' , async ( ) => {
339+ const Activity = React . Activity || React . unstable_Activity ;
340+
341+ function Child ( ) {
342+ return < div > child</ div > ;
343+ }
344+
345+ function App ( ) {
346+ return (
347+ < Activity mode = "visible" >
348+ < Child />
349+ </ Activity >
350+ ) ;
351+ }
352+
353+ await actAsync ( ( ) => {
354+ render ( < App /> ) ;
355+ } ) ;
356+
357+ const activityElement = store . getElementAtIndex ( 1 ) ;
358+ expect ( activityElement . displayName ) . toBe ( 'Activity' ) ;
359+ expect ( activityElement . isActivityHidden ) . toBe ( false ) ;
360+ expect ( activityElement . isInsideHiddenActivity ) . toBe ( false ) ;
361+ expect ( activityElement . isCollapsed ) . toBe ( false ) ;
362+
363+ const childElement = store . getElementAtIndex ( 2 ) ;
364+ expect ( childElement . displayName ) . toBe ( 'Child' ) ;
365+ expect ( childElement . isInsideHiddenActivity ) . toBe ( false ) ;
366+ } ) ;
367+
368+ // @reactVersion >= 19
369+ it ( 'should update hidden state when Activity mode toggles' , async ( ) => {
370+ const Activity = React . Activity || React . unstable_Activity ;
371+
372+ function Child ( ) {
373+ return < div > child</ div > ;
374+ }
375+
376+ function App ( { hidden} ) {
377+ return (
378+ < Activity mode = { hidden ? 'hidden' : 'visible' } >
379+ < Child />
380+ </ Activity >
381+ ) ;
382+ }
383+
384+ // Start visible
385+ await actAsync ( ( ) => {
386+ render ( < App hidden = { false } /> ) ;
387+ } ) ;
388+
389+ let activityElement = store . getElementAtIndex ( 1 ) ;
390+ expect ( activityElement . isActivityHidden ) . toBe ( false ) ;
391+ expect ( activityElement . isCollapsed ) . toBe ( false ) ;
392+
393+ let childElement = store . getElementAtIndex ( 2 ) ;
394+ expect ( childElement . isInsideHiddenActivity ) . toBe ( false ) ;
395+
396+ // Toggle to hidden — children remain but subtree collapses
397+ await actAsync ( ( ) => {
398+ render ( < App hidden = { true } /> ) ;
399+ } ) ;
400+
401+ activityElement = store . getElementAtIndex ( 1 ) ;
402+ expect ( activityElement . isActivityHidden ) . toBe ( true ) ;
403+ expect ( activityElement . isCollapsed ) . toBe ( true ) ;
404+
405+ // Expand to verify children are still marked
406+ store . toggleIsCollapsed ( activityElement . id , false ) ;
407+
408+ childElement = store . getElementAtIndex ( 2 ) ;
409+ expect ( childElement . displayName ) . toBe ( 'Child' ) ;
410+ expect ( childElement . isInsideHiddenActivity ) . toBe ( true ) ;
411+
412+ // Toggle back to visible — subtree expands automatically
413+ await actAsync ( ( ) => {
414+ render ( < App hidden = { false } /> ) ;
415+ } ) ;
416+
417+ activityElement = store . getElementAtIndex ( 1 ) ;
418+ expect ( activityElement . isActivityHidden ) . toBe ( false ) ;
419+ expect ( activityElement . isCollapsed ) . toBe ( false ) ;
420+
421+ childElement = store . getElementAtIndex ( 2 ) ;
422+ expect ( childElement . isInsideHiddenActivity ) . toBe ( false ) ;
423+ } ) ;
424+
425+ // @reactVersion >= 19
426+ it ( 'should propagate hidden state to deeply nested children' , async ( ) => {
427+ const Activity = React . Activity || React . unstable_Activity ;
428+
429+ function GrandChild ( ) {
430+ return < div > grandchild</ div > ;
431+ }
432+ function Child ( ) {
433+ return < GrandChild /> ;
434+ }
435+
436+ function App ( { hidden} ) {
437+ return (
438+ < Activity mode = { hidden ? 'hidden' : 'visible' } >
439+ < Child />
440+ </ Activity >
441+ ) ;
442+ }
443+
444+ await actAsync ( ( ) => {
445+ render ( < App hidden = { true } /> ) ;
446+ } ) ;
447+
448+ const activityElement = store . getElementAtIndex ( 1 ) ;
449+ expect ( activityElement . displayName ) . toBe ( 'Activity' ) ;
450+ expect ( activityElement . isActivityHidden ) . toBe ( true ) ;
451+ expect ( activityElement . isCollapsed ) . toBe ( true ) ;
452+
453+ // Expand to access children
454+ store . toggleIsCollapsed ( activityElement . id , false ) ;
455+
456+ const childElement = store . getElementAtIndex ( 2 ) ;
457+ expect ( childElement . displayName ) . toBe ( 'Child' ) ;
458+ expect ( childElement . isInsideHiddenActivity ) . toBe ( true ) ;
459+
460+ const grandChildElement = store . getElementAtIndex ( 3 ) ;
461+ expect ( grandChildElement . displayName ) . toBe ( 'GrandChild' ) ;
462+ expect ( grandChildElement . isInsideHiddenActivity ) . toBe ( true ) ;
463+ } ) ;
464+
465+ // @reactVersion >= 19
466+ it ( 'should collapse hidden Activity subtree by default' , async ( ) => {
467+ const Activity = React . Activity || React . unstable_Activity ;
468+
469+ function Child ( ) {
470+ return < div > child</ div > ;
471+ }
472+
473+ function App ( { hidden} ) {
474+ return (
475+ < Activity mode = { hidden ? 'hidden' : 'visible' } >
476+ < Child />
477+ </ Activity >
478+ ) ;
479+ }
480+
481+ // Hidden Activity should be collapsed
482+ await actAsync ( ( ) => {
483+ render ( < App hidden = { true } /> ) ;
484+ } ) ;
485+
486+ expect ( store ) . toMatchInlineSnapshot ( `
487+ [root]
488+ ▾ <App>
489+ ▸ <Activity mode="hidden">
490+ ` ) ;
491+
492+ // Toggle to visible — should expand
493+ await actAsync ( ( ) => {
494+ render ( < App hidden = { false } /> ) ;
495+ } ) ;
496+
497+ expect ( store ) . toMatchInlineSnapshot ( `
498+ [root]
499+ ▾ <App>
500+ ▾ <Activity mode="visible">
501+ <Child>
502+ ` ) ;
503+
504+ // Toggle back to hidden — should collapse again
505+ await actAsync ( ( ) => {
506+ render ( < App hidden = { true } /> ) ;
507+ } ) ;
508+
509+ expect ( store ) . toMatchInlineSnapshot ( `
510+ [root]
511+ ▾ <App>
512+ ▸ <Activity mode="hidden">
513+ ` ) ;
514+ } ) ;
515+
516+ // @reactVersion >= 19
517+ it ( 'should dim nested visible Activity inside a hidden Activity' , async ( ) => {
518+ const Activity = React . Activity || React . unstable_Activity ;
519+
520+ function Leaf ( ) {
521+ return < div > leaf</ div > ;
522+ }
523+
524+ function App ( ) {
525+ return (
526+ < Activity mode = "hidden" name = "outer" >
527+ < Activity mode = "visible" name = "inner" >
528+ < Leaf />
529+ </ Activity >
530+ </ Activity >
531+ ) ;
532+ }
533+
534+ await actAsync ( ( ) => {
535+ render ( < App /> ) ;
536+ } ) ;
537+
538+ // Outer Activity: hidden, collapsed, not dimmed itself
539+ const outerActivity = store . getElementAtIndex ( 1 ) ;
540+ expect ( outerActivity . displayName ) . toBe ( 'Activity' ) ;
541+ expect ( outerActivity . nameProp ) . toBe ( 'outer' ) ;
542+ expect ( outerActivity . isActivityHidden ) . toBe ( true ) ;
543+ expect ( outerActivity . isInsideHiddenActivity ) . toBe ( false ) ;
544+ expect ( outerActivity . isCollapsed ) . toBe ( true ) ;
545+
546+ // Expand to access inner elements
547+ store . toggleIsCollapsed ( outerActivity . id , false ) ;
548+
549+ // Inner Activity: visible, but inside hidden outer so still dimmed
550+ const innerActivity = store . getElementAtIndex ( 2 ) ;
551+ expect ( innerActivity . displayName ) . toBe ( 'Activity' ) ;
552+ expect ( innerActivity . nameProp ) . toBe ( 'inner' ) ;
553+ expect ( innerActivity . isActivityHidden ) . toBe ( false ) ;
554+ expect ( innerActivity . isInsideHiddenActivity ) . toBe ( true ) ;
555+
556+ // Leaf: inside both, dimmed
557+ const leaf = store . getElementAtIndex ( 3 ) ;
558+ expect ( leaf . displayName ) . toBe ( 'Leaf' ) ;
559+ expect ( leaf . isInsideHiddenActivity ) . toBe ( true ) ;
560+ } ) ;
561+ } ) ;
562+
300563 describe ( 'collapseNodesByDefault:false' , ( ) => {
301564 beforeEach ( ( ) => {
302565 store . collapseNodesByDefault = false ;
@@ -3361,9 +3624,10 @@ describe('Store', () => {
33613624 expect ( store ) . toMatchInlineSnapshot ( `
33623625 [root]
33633626 ▾ <App>
3364- <Activity>
3627+ ▸ <Activity mode="hidden" >
33653628 <Suspense name="outer-suspense">
33663629 [suspense-root] rects={[{x:1,y:2,width:15,height:1}]}
3630+ <Suspense name="inside-activity" uniqueSuspenders={false} rects={[{x:1,y:2,width:15,height:1}]}>
33673631 <Suspense name="outer-suspense" uniqueSuspenders={true} rects={null}>
33683632 ` ) ;
33693633
@@ -3378,7 +3642,7 @@ describe('Store', () => {
33783642 expect ( store ) . toMatchInlineSnapshot ( `
33793643 [root]
33803644 ▾ <App>
3381- ▾ <Activity>
3645+ ▾ <Activity mode="visible" >
33823646 ▾ <Suspense name="inside-activity">
33833647 <Component key="inside-activity">
33843648 ▾ <Suspense name="outer-suspense">
@@ -3397,9 +3661,10 @@ describe('Store', () => {
33973661 expect ( store ) . toMatchInlineSnapshot ( `
33983662 [root]
33993663 ▾ <App>
3400- <Activity>
3664+ ▸ <Activity mode="hidden" >
34013665 <Suspense name="outer-suspense">
34023666 [suspense-root] rects={[{x:1,y:2,width:15,height:1}, {x:1,y:2,width:15,height:1}]}
3667+ <Suspense name="inside-activity" uniqueSuspenders={false} rects={[{x:1,y:2,width:15,height:1}]}>
34033668 <Suspense name="outer-suspense" uniqueSuspenders={true} rects={[{x:1,y:2,width:15,height:1}]}>
34043669 <Suspense name="inner-suspense" uniqueSuspenders={false} rects={[{x:1,y:2,width:15,height:1}]}>
34053670 ` ) ;
@@ -3411,7 +3676,7 @@ describe('Store', () => {
34113676 expect ( store ) . toMatchInlineSnapshot ( `
34123677 [root]
34133678 ▾ <App>
3414- ▾ <Activity>
3679+ ▾ <Activity mode="visible" >
34153680 ▾ <Suspense name="inside-activity">
34163681 <Component key="inside-activity">
34173682 ▾ <Suspense name="outer-suspense">
@@ -3604,7 +3869,7 @@ describe('Store', () => {
36043869
36053870 expect ( store ) . toMatchInlineSnapshot ( `
36063871 [root]
3607- <Activity>
3872+ ▸ <Activity mode="hidden" >
36083873 ` ) ;
36093874
36103875 await actAsync ( ( ) => {
@@ -3613,7 +3878,7 @@ describe('Store', () => {
36133878
36143879 expect ( store ) . toMatchInlineSnapshot ( `
36153880 [root]
3616- ▾ <Activity>
3881+ ▾ <Activity mode="visible" >
36173882 ▾ <Component key="left">
36183883 <div>
36193884 ` ) ;
0 commit comments