@@ -15,7 +15,7 @@ import type {HostInstance} from 'react-native';
1515
1616import ensureInstance from '../../../src/private/__tests__/utilities/ensureInstance' ;
1717import * as Fantom from '@react-native/fantom' ;
18- import { createRef } from 'react' ;
18+ import { createRef , useMemo } from 'react' ;
1919import { Animated , View , useAnimatedValue } from 'react-native' ;
2020import { allowStyleProp } from 'react-native/Libraries/Animated/NativeAnimatedAllowlist' ;
2121import * as ReactNativeFeatureFlags from 'react-native/src/private/featureflags/ReactNativeFeatureFlags' ;
@@ -747,3 +747,98 @@ test('Animated.sequence', () => {
747747
748748 expect ( _isSequenceFinished ) . toBe ( true ) ;
749749} ) ;
750+
751+ test ( 'Props default value is restored when disconnected from animated' , ( ) => {
752+ let _animatedOpacity ;
753+ const elementRef = createRef < HostInstance > ( ) ;
754+
755+ function MyApp ( {
756+ shouldAnimate,
757+ } : $ReadOnly < {
758+ shouldAnimate ?: boolean ,
759+ } > ) {
760+ const animatedOpacity = useAnimatedValue ( 1 , { useNativeDriver : true } ) ;
761+
762+ const opacity = useMemo (
763+ ( ) => ( shouldAnimate === true ? animatedOpacity : undefined ) ,
764+ [ shouldAnimate , animatedOpacity ] ,
765+ ) ;
766+ const scale = useMemo (
767+ ( ) =>
768+ opacity ?. interpolate ( {
769+ inputRange : [ 0 , 1 ] ,
770+ outputRange : [ 0.95 , 1 ] ,
771+ } ) ?? new Animated . Value ( 1 ) ,
772+ [ opacity ] ,
773+ ) ;
774+ _animatedOpacity = animatedOpacity ;
775+
776+ return (
777+ < Animated . View
778+ ref = { elementRef }
779+ style = { [
780+ {
781+ opacity,
782+ transform : [ { scale} ] ,
783+ height : 100 ,
784+ width : 100 ,
785+ } ,
786+ ] }
787+ />
788+ ) ;
789+ }
790+
791+ const root = Fantom . createRoot ( ) ;
792+
793+ Fantom . runTask ( ( ) => {
794+ root . render ( < MyApp shouldAnimate = { true } /> ) ;
795+ } ) ;
796+
797+ const element = ensureInstance ( elementRef . current , ReactNativeElement ) ;
798+
799+ expect ( root . getRenderedOutput ( { props : [ 'opacity' ] } ) . toJSX ( ) ) . toEqual (
800+ < rn-view /> , // default opacity is 1
801+ ) ;
802+
803+ Fantom . runTask ( ( ) => {
804+ Animated . timing ( _animatedOpacity , {
805+ toValue : 0 ,
806+ duration : 500 ,
807+ useNativeDriver : true ,
808+ } ) . start ( ) ;
809+ } ) ;
810+
811+ Fantom . unstable_produceFramesForDuration ( 500 ) ;
812+
813+ Fantom . runWorkLoop ( ) ;
814+
815+ expect ( Fantom . unstable_getDirectManipulationProps ( element ) ) . toEqual ( {
816+ opacity : 0 ,
817+ transform : [ { scale : 0.95 } ] ,
818+ } ) ;
819+
820+ expect (
821+ root . getRenderedOutput ( { props : [ 'opacity' , 'transform' ] } ) . toJSX ( ) ,
822+ ) . toEqual ( < rn-view opacity = "0" transform = '[{"scale": 0.950000}]' /> ) ;
823+
824+ Fantom . runTask ( ( ) => {
825+ root . render ( < MyApp shouldAnimate = { false } /> ) ;
826+ } ) ;
827+
828+ expect ( Fantom . unstable_getDirectManipulationProps ( element ) ) . toEqual ( {
829+ opacity : null ,
830+ transform : null ,
831+ } ) ;
832+
833+ if ( ReactNativeFeatureFlags . cxxNativeAnimatedRemoveJsSync ( ) ) {
834+ expect (
835+ root . getRenderedOutput ( { props : [ 'opacity' , 'transform' ] } ) . toJSX ( ) ,
836+ ) . toEqual ( < rn-view transform = '[{"scale": 0.950000}]' /> ) ; // TODO: T223344928 scale should be 1
837+ } else {
838+ expect (
839+ root . getRenderedOutput ( { props : [ 'opacity' , 'transform' ] } ) . toJSX ( ) ,
840+ ) . toEqual ( < rn-view transform = '[{"scale": 1.000000}]' /> ) ;
841+ }
842+
843+ Fantom . runWorkLoop ( ) ;
844+ } ) ;
0 commit comments