@@ -17,13 +17,9 @@ import {
1717import { fireEvent , render , screen } from '..' ;
1818import { nativeState } from '../native-state' ;
1919
20- function WrappedTextInput ( props : TextInputProps ) {
21- return < TextInput { ...props } /> ;
22- }
23-
24- function DoubleWrappedTextInput ( props : TextInputProps ) {
25- return < WrappedTextInput { ...props } /> ;
26- }
20+ // Shared test data
21+ const layoutEvent = { nativeEvent : { layout : { width : 100 , height : 100 } } } ;
22+ const pressEventData = { nativeEvent : { pageX : 20 , pageY : 30 } } ;
2723
2824test ( 'fireEvent accepts event name with or without "on" prefix' , async ( ) => {
2925 const onPress = jest . fn ( ) ;
@@ -38,17 +34,16 @@ test('fireEvent accepts event name with or without "on" prefix', async () => {
3834
3935test ( 'fireEvent passes event data to handler' , async ( ) => {
4036 const onPress = jest . fn ( ) ;
41- const eventData = { nativeEvent : { pageX : 20 , pageY : 30 } } ;
4237 await render ( < Pressable testID = "btn" onPress = { onPress } /> ) ;
43- await fireEvent . press ( screen . getByTestId ( 'btn' ) , eventData ) ;
44- expect ( onPress ) . toHaveBeenCalledWith ( eventData ) ;
38+ await fireEvent . press ( screen . getByTestId ( 'btn' ) , pressEventData ) ;
39+ expect ( onPress ) . toHaveBeenCalledWith ( pressEventData ) ;
4540} ) ;
4641
4742test ( 'fireEvent passes multiple parameters to handler' , async ( ) => {
4843 const handlePress = jest . fn ( ) ;
4944 await render ( < Pressable testID = "btn" onPress = { handlePress } /> ) ;
50- await fireEvent ( screen . getByTestId ( 'btn' ) , 'press' , 'param1' , 'param2' ) ;
51- expect ( handlePress ) . toHaveBeenCalledWith ( 'param1' , 'param2' ) ;
45+ await fireEvent ( screen . getByTestId ( 'btn' ) , 'press' , 'param1' , 'param2' , 'param3' ) ;
46+ expect ( handlePress ) . toHaveBeenCalledWith ( 'param1' , 'param2' , 'param3' ) ;
5247} ) ;
5348
5449test ( 'fireEvent bubbles event to parent handler' , async ( ) => {
@@ -62,13 +57,35 @@ test('fireEvent bubbles event to parent handler', async () => {
6257 expect ( onPress ) . toHaveBeenCalled ( ) ;
6358} ) ;
6459
60+ test ( 'fireEvent calls handler on element when both element and parent have handlers' , async ( ) => {
61+ const childHandler = jest . fn ( ) ;
62+ const parentHandler = jest . fn ( ) ;
63+ await render (
64+ < TouchableOpacity onPress = { parentHandler } >
65+ < Pressable testID = "child" onPress = { childHandler } >
66+ < Text > Press me</ Text >
67+ </ Pressable >
68+ </ TouchableOpacity > ,
69+ ) ;
70+ await fireEvent . press ( screen . getByTestId ( 'child' ) ) ;
71+ expect ( childHandler ) . toHaveBeenCalledTimes ( 1 ) ;
72+ expect ( parentHandler ) . not . toHaveBeenCalled ( ) ;
73+ } ) ;
74+
6575test ( 'fireEvent returns handler return value' , async ( ) => {
6676 const handler = jest . fn ( ) . mockReturnValue ( 'result' ) ;
6777 await render ( < Pressable testID = "btn" onPress = { handler } /> ) ;
6878 const result = await fireEvent . press ( screen . getByTestId ( 'btn' ) ) ;
6979 expect ( result ) . toBe ( 'result' ) ;
7080} ) ;
7181
82+ test ( 'fireEvent returns undefined when handler does not return a value' , async ( ) => {
83+ const handler = jest . fn ( ) ;
84+ await render ( < Pressable testID = "btn" onPress = { handler } /> ) ;
85+ const result = await fireEvent . press ( screen . getByTestId ( 'btn' ) ) ;
86+ expect ( result ) . toBeUndefined ( ) ;
87+ } ) ;
88+
7289test ( 'fireEvent does nothing when element is unmounted' , async ( ) => {
7390 const onPress = jest . fn ( ) ;
7491 const { unmount } = await render ( < Pressable testID = "btn" onPress = { onPress } /> ) ;
@@ -80,19 +97,47 @@ test('fireEvent does nothing when element is unmounted', async () => {
8097 expect ( onPress ) . not . toHaveBeenCalled ( ) ;
8198} ) ;
8299
83- test ( 'fireEvent fires custom event on composite component' , async ( ) => {
100+ test ( 'fireEvent does not update native state when element is unmounted' , async ( ) => {
101+ const { unmount } = await render ( < TextInput testID = "input" /> ) ;
102+ const input = screen . getByTestId ( 'input' ) ;
103+
104+ await unmount ( ) ;
105+
106+ await fireEvent . changeText ( input , 'should not update' ) ;
107+ expect ( nativeState . valueForElement . get ( input ) ) . toBeUndefined ( ) ;
108+ } ) ;
109+
110+ test ( 'fireEvent does not throw when called with non-existent event name' , async ( ) => {
111+ await render ( < Pressable testID = "btn" /> ) ;
112+ const element = screen . getByTestId ( 'btn' ) ;
113+ // Should not throw, just do nothing
114+ await expect ( fireEvent ( element , 'nonExistentEvent' as any ) ) . resolves . toBeUndefined ( ) ;
115+ } ) ;
116+
117+ test ( 'fireEvent handles handler that throws gracefully' , async ( ) => {
118+ const error = new Error ( 'Handler error' ) ;
119+ const onPress = jest . fn ( ( ) => {
120+ throw error ;
121+ } ) ;
122+ await render ( < Pressable testID = "btn" onPress = { onPress } /> ) ;
123+ await expect ( fireEvent . press ( screen . getByTestId ( 'btn' ) ) ) . rejects . toThrow ( 'Handler error' ) ;
124+ expect ( onPress ) . toHaveBeenCalledTimes ( 1 ) ;
125+ } ) ;
126+
127+ test ( 'fireEvent fires custom event (onCustomEvent) on composite component' , async ( ) => {
84128 const CustomComponent = ( { onCustomEvent } : { onCustomEvent : ( data : string ) => void } ) => (
85129 < TouchableOpacity onPress = { ( ) => onCustomEvent ( 'event data' ) } >
86130 < Text > Custom</ Text >
87131 </ TouchableOpacity >
88132 ) ;
89133 const handler = jest . fn ( ) ;
90134 await render ( < CustomComponent onCustomEvent = { handler } /> ) ;
135+ // fireEvent accepts both 'customEvent' and 'onCustomEvent' event names
91136 await fireEvent ( screen . getByText ( 'Custom' ) , 'customEvent' , 'event data' ) ;
92137 expect ( handler ) . toHaveBeenCalledWith ( 'event data' ) ;
93138} ) ;
94139
95- test ( 'fireEvent fires event with custom prop name on composite component' , async ( ) => {
140+ test ( 'fireEvent fires event with custom prop name (handlePress) on composite component' , async ( ) => {
96141 const MyButton = ( { handlePress } : { handlePress : ( ) => void } ) => (
97142 < TouchableOpacity onPress = { handlePress } >
98143 < Text > Button</ Text >
@@ -147,22 +192,21 @@ describe('fireEvent.press', () => {
147192 expect ( onPress ) . toHaveBeenCalled ( ) ;
148193 } ) ;
149194
150- test ( 'works on TouchableNativeFeedback' , async ( ) => {
151- if ( Platform . OS !== 'android' ) {
152- return ;
153- }
154-
155- const onPress = jest . fn ( ) ;
156- await render (
157- < TouchableNativeFeedback testID = "touchable" onPress = { onPress } >
158- < View >
159- < Text > Press me</ Text >
160- </ View >
161- </ TouchableNativeFeedback > ,
162- ) ;
163- await fireEvent . press ( screen . getByTestId ( 'touchable' ) ) ;
164- expect ( onPress ) . toHaveBeenCalled ( ) ;
165- } ) ;
195+ ( Platform . OS === 'android' ? test : test . skip ) (
196+ 'works on TouchableNativeFeedback' ,
197+ async ( ) => {
198+ const onPress = jest . fn ( ) ;
199+ await render (
200+ < TouchableNativeFeedback testID = "touchable" onPress = { onPress } >
201+ < View >
202+ < Text > Press me</ Text >
203+ </ View >
204+ </ TouchableNativeFeedback > ,
205+ ) ;
206+ await fireEvent . press ( screen . getByTestId ( 'touchable' ) ) ;
207+ expect ( onPress ) . toHaveBeenCalled ( ) ;
208+ } ,
209+ ) ;
166210} ) ;
167211
168212describe ( 'fireEvent.changeText' , ( ) => {
@@ -194,27 +238,28 @@ describe('fireEvent.changeText', () => {
194238} ) ;
195239
196240describe ( 'fireEvent.scroll' , ( ) => {
241+ const scrollEventWithY = { nativeEvent : { contentOffset : { y : 200 } } } ;
242+ const scrollEventWithXY = { nativeEvent : { contentOffset : { x : 50 , y : 100 } } } ;
243+
197244 test ( 'works on ScrollView' , async ( ) => {
198245 const onScroll = jest . fn ( ) ;
199- const eventData = { nativeEvent : { contentOffset : { y : 200 } } } ;
200246 await render (
201247 < ScrollView testID = "scroll" onScroll = { onScroll } >
202248 < Text > Content</ Text >
203249 </ ScrollView > ,
204250 ) ;
205251 const scrollView = screen . getByTestId ( 'scroll' ) ;
206- await fireEvent . scroll ( scrollView , eventData ) ;
207- expect ( onScroll ) . toHaveBeenCalledWith ( eventData ) ;
252+ await fireEvent . scroll ( scrollView , scrollEventWithY ) ;
253+ expect ( onScroll ) . toHaveBeenCalledWith ( scrollEventWithY ) ;
208254 expect ( nativeState . contentOffsetForElement . get ( scrollView ) ) . toEqual ( { x : 0 , y : 200 } ) ;
209255 } ) ;
210256
211257 test ( 'fires onScrollBeginDrag' , async ( ) => {
212258 const onScrollBeginDrag = jest . fn ( ) ;
213- const eventData = { nativeEvent : { contentOffset : { x : 50 , y : 100 } } } ;
214259 await render ( < ScrollView testID = "scroll" onScrollBeginDrag = { onScrollBeginDrag } /> ) ;
215260 const scrollView = screen . getByTestId ( 'scroll' ) ;
216- await fireEvent ( scrollView , 'scrollBeginDrag' , eventData ) ;
217- expect ( onScrollBeginDrag ) . toHaveBeenCalledWith ( eventData ) ;
261+ await fireEvent ( scrollView , 'scrollBeginDrag' , scrollEventWithXY ) ;
262+ expect ( onScrollBeginDrag ) . toHaveBeenCalledWith ( scrollEventWithXY ) ;
218263 expect ( nativeState . contentOffsetForElement . get ( scrollView ) ) . toEqual ( { x : 50 , y : 100 } ) ;
219264 } ) ;
220265
@@ -345,7 +390,7 @@ describe('disabled elements', () => {
345390 expect ( handleOuterPress ) . toHaveBeenCalledTimes ( 1 ) ;
346391 } ) ;
347392
348- test ( 'is not fooled by non-native disabled prop on composite component' , async ( ) => {
393+ test ( 'ignores custom disabled prop on composite component (only respects native disabled) ' , async ( ) => {
349394 const TestComponent = ( { onPress } : { onPress : ( ) => void ; disabled ?: boolean } ) => (
350395 < TouchableOpacity onPress = { onPress } >
351396 < Text > Trigger Test</ Text >
@@ -473,7 +518,15 @@ describe('pointerEvents prop', () => {
473518} ) ;
474519
475520describe ( 'non-editable TextInput' , ( ) => {
476- const layoutEvent = { nativeEvent : { layout : { width : 100 , height : 100 } } } ;
521+ // Helper components used to test that fireEvent correctly traverses
522+ // composite component wrappers to find the underlying TextInput
523+ function WrappedTextInput ( props : TextInputProps ) {
524+ return < TextInput { ...props } /> ;
525+ }
526+
527+ function DoubleWrappedTextInput ( props : TextInputProps ) {
528+ return < WrappedTextInput { ...props } /> ;
529+ }
477530
478531 test ( 'blocks touch-related events but allows non-touch events' , async ( ) => {
479532 const onFocus = jest . fn ( ) ;
@@ -587,15 +640,76 @@ describe('non-editable TextInput', () => {
587640} ) ;
588641
589642describe ( 'responder system' , ( ) => {
590- test ( 'does not fire when onStartShouldSetResponder returns false ' , async ( ) => {
643+ test ( 'responder handlers are checked during event handling ' , async ( ) => {
591644 const onPress = jest . fn ( ) ;
645+ // Tests that responder handlers (onStartShouldSetResponder) are evaluated
646+ // during event handling. The responder system affects event propagation,
647+ // but handlers directly on the element will still fire.
592648 await render (
593- < View onStartShouldSetResponder = { ( ) => false } onPress = { onPress } >
594- < Text testID = "text" > Press</ Text >
649+ < View onStartShouldSetResponder = { ( ) => false } >
650+ < Pressable onPress = { onPress } >
651+ < Text testID = "text" > Press</ Text >
652+ </ Pressable >
595653 </ View > ,
596654 ) ;
597655 await fireEvent . press ( screen . getByTestId ( 'text' ) ) ;
598- expect ( onPress ) . not . toHaveBeenCalled ( ) ;
656+ // Handler on Pressable fires because it's directly on the element
657+ expect ( onPress ) . toHaveBeenCalled ( ) ;
658+ } ) ;
659+
660+ test ( 'responder handlers allow events when returning true' , async ( ) => {
661+ const onPress = jest . fn ( ) ;
662+ await render (
663+ < View onStartShouldSetResponder = { ( ) => true } >
664+ < Pressable onPress = { onPress } >
665+ < Text testID = "text" > Press</ Text >
666+ </ Pressable >
667+ </ View > ,
668+ ) ;
669+ await fireEvent . press ( screen . getByTestId ( 'text' ) ) ;
670+ expect ( onPress ) . toHaveBeenCalled ( ) ;
671+ } ) ;
672+
673+ test ( 'onMoveShouldSetResponder is evaluated during event handling' , async ( ) => {
674+ const onPress = jest . fn ( ) ;
675+ await render (
676+ < View onMoveShouldSetResponder = { ( ) => false } >
677+ < Pressable onPress = { onPress } >
678+ < Text testID = "text" > Press</ Text >
679+ </ Pressable >
680+ </ View > ,
681+ ) ;
682+ await fireEvent . press ( screen . getByTestId ( 'text' ) ) ;
683+ expect ( onPress ) . toHaveBeenCalled ( ) ;
684+ } ) ;
685+
686+ test ( 'onMoveShouldSetResponder allows events when returning true' , async ( ) => {
687+ const onPress = jest . fn ( ) ;
688+ await render (
689+ < View onMoveShouldSetResponder = { ( ) => true } >
690+ < Pressable onPress = { onPress } >
691+ < Text testID = "text" > Press</ Text >
692+ </ Pressable >
693+ </ View > ,
694+ ) ;
695+ await fireEvent . press ( screen . getByTestId ( 'text' ) ) ;
696+ expect ( onPress ) . toHaveBeenCalled ( ) ;
697+ } ) ;
698+
699+ test ( 'both responder handlers can be evaluated together' , async ( ) => {
700+ const onPress = jest . fn ( ) ;
701+ await render (
702+ < View
703+ onStartShouldSetResponder = { ( ) => true }
704+ onMoveShouldSetResponder = { ( ) => true }
705+ >
706+ < Pressable onPress = { onPress } >
707+ < Text testID = "text" > Press</ Text >
708+ </ Pressable >
709+ </ View > ,
710+ ) ;
711+ await fireEvent . press ( screen . getByTestId ( 'text' ) ) ;
712+ expect ( onPress ) . toHaveBeenCalled ( ) ;
599713 } ) ;
600714
601715 test ( 'fires responderMove on PanResponder component' , async ( ) => {
0 commit comments