@@ -2827,4 +2827,258 @@ describe('signals', () => {
28272827 destroy2 ( ) ;
28282828 } ;
28292829 } ) ;
2830+
2831+ test ( 'multiple deriveds interacting with proxy - one updates, other reacts' , ( ) => {
2832+ // Tests multiple deriveds that depend on each other and on a proxy
2833+ const log : any [ ] = [ ] ;
2834+
2835+ return ( ) => {
2836+ const objSignal = state ( proxy < { foo ?: number } > ( { foo : 1 } ) ) ;
2837+
2838+ // First derived reads from proxy
2839+ const d1 = derived ( ( ) => {
2840+ const obj = $ . get ( objSignal ) ;
2841+ return obj . foo ?? 0 ;
2842+ } ) ;
2843+
2844+ // Second derived depends on first derived
2845+ const d2 = derived ( ( ) => $ . get ( d1 ) * 2 ) ;
2846+
2847+ // Third derived also depends on first
2848+ const d3 = derived ( ( ) => $ . get ( d1 ) + 10 ) ;
2849+
2850+ // Effect reads all deriveds
2851+ let destroy1 = effect_root ( ( ) => {
2852+ render_effect ( ( ) => {
2853+ log . push ( { d1 : $ . get ( d1 ) , d2 : $ . get ( d2 ) , d3 : $ . get ( d3 ) } ) ;
2854+ } ) ;
2855+ } ) ;
2856+
2857+ flushSync ( ) ;
2858+ assert . deepEqual ( log , [ { d1 : 1 , d2 : 2 , d3 : 11 } ] ) ;
2859+
2860+ // Destroy effect - all deriveds disconnect
2861+ destroy1 ( ) ;
2862+ flushSync ( ) ;
2863+
2864+ // Replace proxy with one WITHOUT the property
2865+ set ( objSignal , proxy < { foo ?: number } > ( { } ) ) ;
2866+ flushSync ( ) ;
2867+
2868+ // Replace proxy with one WITH the property (new value)
2869+ set ( objSignal , proxy < { foo ?: number } > ( { foo : 42 } ) ) ;
2870+ flushSync ( ) ;
2871+
2872+ // Reconnect with new effect
2873+ const destroy2 = effect_root ( ( ) => {
2874+ render_effect ( ( ) => {
2875+ log . push ( { d1 : $ . get ( d1 ) , d2 : $ . get ( d2 ) , d3 : $ . get ( d3 ) } ) ;
2876+ } ) ;
2877+ } ) ;
2878+
2879+ flushSync ( ) ;
2880+ assert . deepEqual ( log [ log . length - 1 ] , { d1 : 42 , d2 : 84 , d3 : 52 } ) ;
2881+
2882+ // Verify reactivity on new proxy
2883+ $ . get ( objSignal ) . foo = 100 ;
2884+ flushSync ( ) ;
2885+ assert . deepEqual ( log [ log . length - 1 ] , { d1 : 100 , d2 : 200 , d3 : 110 } ) ;
2886+
2887+ destroy2 ( ) ;
2888+ } ;
2889+ } ) ;
2890+
2891+ test ( 'derived created in effect context survives after effect destruction' , ( ) => {
2892+ // Tests a derived created inside an effect that continues to live
2893+ // after that effect is destroyed
2894+ const log : any [ ] = [ ] ;
2895+
2896+ return ( ) => {
2897+ const objSignal = state ( proxy < { foo ?: number } > ( { foo : 1 } ) ) ;
2898+ let escapedDerived : Derived < number > ;
2899+
2900+ // Effect 1: creates a derived and "escapes" it
2901+ let destroy1 = effect_root ( ( ) => {
2902+ render_effect ( ( ) => {
2903+ // Create derived inside effect
2904+ escapedDerived = derived ( ( ) => {
2905+ const obj = $ . get ( objSignal ) ;
2906+ return obj . foo ?? 0 ;
2907+ } ) ;
2908+ log . push ( 'effect1: ' + $ . get ( escapedDerived ) ) ;
2909+ } ) ;
2910+ } ) ;
2911+
2912+ flushSync ( ) ;
2913+ assert . deepEqual ( log , [ 'effect1: 1' ] ) ;
2914+
2915+ // Destroy the effect that created the derived
2916+ destroy1 ( ) ;
2917+ flushSync ( ) ;
2918+
2919+ // The derived should still be usable
2920+ // Replace proxy
2921+ set ( objSignal , proxy < { foo ?: number } > ( { } ) ) ;
2922+ set ( objSignal , proxy < { foo ?: number } > ( { foo : 42 } ) ) ;
2923+ flushSync ( ) ;
2924+
2925+ // Use the escaped derived in a NEW effect
2926+ const destroy2 = effect_root ( ( ) => {
2927+ render_effect ( ( ) => {
2928+ log . push ( 'effect2: ' + $ . get ( escapedDerived ! ) ) ;
2929+ } ) ;
2930+ } ) ;
2931+
2932+ flushSync ( ) ;
2933+ assert . deepEqual ( log [ log . length - 1 ] , 'effect2: 42' ) ;
2934+
2935+ // Verify reactivity works on the escaped derived
2936+ $ . get ( objSignal ) . foo = 100 ;
2937+ flushSync ( ) ;
2938+ assert . deepEqual ( log [ log . length - 1 ] , 'effect2: 100' ) ;
2939+
2940+ destroy2 ( ) ;
2941+ } ;
2942+ } ) ;
2943+
2944+ test ( 'derived moved between effect contexts with proxy changes' , ( ) => {
2945+ // Tests a derived that is read in one effect, then that effect is destroyed,
2946+ // and the derived is read in a different effect
2947+ const log : any [ ] = [ ] ;
2948+
2949+ return ( ) => {
2950+ const objSignal = state ( proxy < { foo ?: number } > ( { foo : 1 } ) ) ;
2951+
2952+ // Create derived outside effects
2953+ const d = derived ( ( ) => {
2954+ const obj = $ . get ( objSignal ) ;
2955+ return obj . foo ?? 0 ;
2956+ } ) ;
2957+
2958+ // Effect 1: reads the derived
2959+ let destroy1 = effect_root ( ( ) => {
2960+ render_effect ( ( ) => {
2961+ log . push ( 'effect1: ' + $ . get ( d ) ) ;
2962+ } ) ;
2963+ } ) ;
2964+
2965+ flushSync ( ) ;
2966+ assert . deepEqual ( log , [ 'effect1: 1' ] ) ;
2967+
2968+ // Effect 2: also reads the derived (both effects active)
2969+ let destroy2 = effect_root ( ( ) => {
2970+ render_effect ( ( ) => {
2971+ log . push ( 'effect2: ' + $ . get ( d ) ) ;
2972+ } ) ;
2973+ } ) ;
2974+
2975+ flushSync ( ) ;
2976+ assert . deepEqual ( log [ log . length - 1 ] , 'effect2: 1' ) ;
2977+
2978+ // Destroy effect 1 - derived still connected via effect 2
2979+ destroy1 ( ) ;
2980+ flushSync ( ) ;
2981+
2982+ // Change proxy
2983+ set ( objSignal , proxy < { foo ?: number } > ( { foo : 50 } ) ) ;
2984+ flushSync ( ) ;
2985+
2986+ // Effect 2 should still react
2987+ assert . deepEqual ( log [ log . length - 1 ] , 'effect2: 50' ) ;
2988+
2989+ // Destroy effect 2 - derived now fully disconnected
2990+ destroy2 ( ) ;
2991+ flushSync ( ) ;
2992+
2993+ // Replace proxy while disconnected
2994+ set ( objSignal , proxy < { foo ?: number } > ( { } ) ) ;
2995+ set ( objSignal , proxy < { foo ?: number } > ( { foo : 99 } ) ) ;
2996+ flushSync ( ) ;
2997+
2998+ // Effect 3: reads the derived (reconnection)
2999+ const destroy3 = effect_root ( ( ) => {
3000+ render_effect ( ( ) => {
3001+ log . push ( 'effect3: ' + $ . get ( d ) ) ;
3002+ } ) ;
3003+ } ) ;
3004+
3005+ flushSync ( ) ;
3006+ assert . deepEqual ( log [ log . length - 1 ] , 'effect3: 99' ) ;
3007+
3008+ // Verify reactivity
3009+ $ . get ( objSignal ) . foo = 200 ;
3010+ flushSync ( ) ;
3011+ assert . deepEqual ( log [ log . length - 1 ] , 'effect3: 200' ) ;
3012+
3013+ destroy3 ( ) ;
3014+ } ;
3015+ } ) ;
3016+
3017+ test ( 'derived created in destroyed effect - verify reactions' , ( ) => {
3018+ // Directly verify reactions arrays when derived outlives its creating effect
3019+ return ( ) => {
3020+ const objSignal = state ( proxy < { foo ?: number } > ( { foo : 1 } ) ) ;
3021+ let escapedDerived : Derived < number > ;
3022+
3023+ // Create derived inside effect
3024+ let destroy1 = effect_root ( ( ) => {
3025+ render_effect ( ( ) => {
3026+ escapedDerived = derived ( ( ) => {
3027+ const obj = $ . get ( objSignal ) ;
3028+ return obj . foo ?? 0 ;
3029+ } ) ;
3030+ $ . get ( escapedDerived ) ; // Read it to establish deps
3031+ } ) ;
3032+ } ) ;
3033+
3034+ flushSync ( ) ;
3035+
3036+ // Verify initial state
3037+ assert . ok ( escapedDerived ! . deps !== null , 'derived should have deps' ) ;
3038+
3039+ // Destroy creating effect
3040+ destroy1 ( ) ;
3041+ flushSync ( ) ;
3042+
3043+ // Replace proxy while derived is orphaned
3044+ set ( objSignal , proxy < { foo ?: number } > ( { } ) ) ;
3045+ set ( objSignal , proxy < { foo ?: number } > ( { foo : 42 } ) ) ;
3046+ flushSync ( ) ;
3047+
3048+ // Use derived in new effect
3049+ const destroy2 = effect_root ( ( ) => {
3050+ render_effect ( ( ) => {
3051+ $ . get ( escapedDerived ! ) ;
3052+ } ) ;
3053+ } ) ;
3054+
3055+ flushSync ( ) ;
3056+
3057+ // Verify deps are updated and reactions are correct
3058+ assert . ok ( escapedDerived ! . deps !== null , 'derived should still have deps' ) ;
3059+ const fooSource = escapedDerived ! . deps ! [ 1 ] ; // objSignal is [0], foo source is [1]
3060+ assert . ok ( fooSource . reactions !== null , 'foo source should have reactions' ) ;
3061+ assert . ok (
3062+ fooSource . reactions ! . includes ( escapedDerived ! ) ,
3063+ 'derived should be in foo source reactions'
3064+ ) ;
3065+
3066+ // Verify reactivity
3067+ const log : any [ ] = [ ] ;
3068+ const destroy3 = effect_root ( ( ) => {
3069+ render_effect ( ( ) => {
3070+ log . push ( $ . get ( escapedDerived ! ) ) ;
3071+ } ) ;
3072+ } ) ;
3073+
3074+ flushSync ( ) ;
3075+ $ . get ( objSignal ) . foo = 100 ;
3076+ flushSync ( ) ;
3077+
3078+ assert . deepEqual ( log , [ 42 , 100 ] , 'should react to changes' ) ;
3079+
3080+ destroy2 ( ) ;
3081+ destroy3 ( ) ;
3082+ } ;
3083+ } ) ;
28303084} ) ;
0 commit comments