@@ -9,13 +9,27 @@ import { amountToPercent } from '../../../utils/musdCalculatorSlider';
99const mockPanGestureHandlers : {
1010 onBegin ?: ( event : { x : number } ) => void ;
1111 onUpdate ?: ( event : { x : number } ) => void ;
12+ onEnd ?: ( event : { x : number } ) => void ;
1213 onFinalize ?: ( event : { x : number } ) => void ;
1314} = { } ;
15+ const mockTapGestureHandlers : {
16+ onEnd ?: ( event : { x : number } ) => void ;
17+ } = { } ;
1418
1519jest . mock ( 'react-native-gesture-handler' , ( ) => ( {
1620 GestureHandlerRootView : jest . requireActual ( 'react-native' ) . View ,
1721 GestureDetector : ( { children } : { children : React . ReactNode } ) => children ,
1822 Gesture : {
23+ Simultaneous : jest . fn ( ( ...gestures : unknown [ ] ) => gestures ) ,
24+ Tap : jest . fn ( ( ) => ( {
25+ onEnd : jest . fn ( function (
26+ this : unknown ,
27+ handler : ( event : { x : number } ) => void ,
28+ ) {
29+ mockTapGestureHandlers . onEnd = handler ;
30+ return this ;
31+ } ) ,
32+ } ) ) ,
1933 Pan : jest . fn ( ( ) => ( {
2034 minDistance : jest . fn ( ) . mockReturnThis ( ) ,
2135 onBegin : jest . fn ( function (
@@ -32,6 +46,13 @@ jest.mock('react-native-gesture-handler', () => ({
3246 mockPanGestureHandlers . onUpdate = handler ;
3347 return this ;
3448 } ) ,
49+ onEnd : jest . fn ( function (
50+ this : unknown ,
51+ handler : ( event : { x : number } ) => void ,
52+ ) {
53+ mockPanGestureHandlers . onEnd = handler ;
54+ return this ;
55+ } ) ,
3556 onFinalize : jest . fn ( function (
3657 this : unknown ,
3758 handler : ( event : { x : number } ) => void ,
@@ -60,12 +81,18 @@ jest.mock('../../../../../../../locales/i18n', () => ({
6081 strings : jest . fn ( ( key : string ) => key ) ,
6182} ) ) ;
6283
63- jest . mock ( '../../../../SimulationDetails/FiatDisplay/useFiatFormatter' , ( ) =>
64- jest . fn (
65- ( ) => ( value : { toNumber : ( ) => number } ) =>
66- `$${ value . toNumber ( ) . toLocaleString ( 'en-US' ) } ` ,
67- ) ,
68- ) ;
84+ jest . mock ( 'react-native-keyboard-aware-scroll-view' , ( ) => {
85+ const ReactActual = jest . requireActual ( 'react' ) ;
86+ const { ScrollView } = jest . requireActual ( 'react-native' ) ;
87+ return {
88+ KeyboardAwareScrollView : ( {
89+ children,
90+ ...props
91+ } : {
92+ children ?: React . ReactNode ;
93+ } ) => ReactActual . createElement ( ScrollView , props , children ) ,
94+ } ;
95+ } ) ;
6996
7097jest . mock ( '../../../../../../core/DeeplinkManager' , ( ) => ( {
7198 handleDeeplink : jest . fn ( ) ,
@@ -90,7 +117,9 @@ describe('MusdCalculatorTab', () => {
90117 jest . clearAllMocks ( ) ;
91118 mockPanGestureHandlers . onBegin = undefined ;
92119 mockPanGestureHandlers . onUpdate = undefined ;
120+ mockPanGestureHandlers . onEnd = undefined ;
93121 mockPanGestureHandlers . onFinalize = undefined ;
122+ mockTapGestureHandlers . onEnd = undefined ;
94123 jest . mocked ( useAnalytics ) . mockReturnValue (
95124 createMockUseAnalyticsHook ( {
96125 trackEvent : mockTrackEvent ,
@@ -114,6 +143,20 @@ describe('MusdCalculatorTab', () => {
114143 expect ( getByTestId ( 'musd-slider-track' ) ) . toBeOnTheScreen ( ) ;
115144 } ) ;
116145
146+ it ( 'uses a keyboard-aware scroll container for the amount input' , ( ) => {
147+ const { getByTestId } = render ( < MusdCalculatorTab /> ) ;
148+ const scrollView = getByTestId (
149+ 'musd-calculator-keyboard-aware-scroll-view' ,
150+ ) ;
151+
152+ expect ( scrollView ) . toHaveProp ( 'keyboardShouldPersistTaps' , 'handled' ) ;
153+ expect ( scrollView ) . toHaveProp ( 'keyboardDismissMode' , 'none' ) ;
154+ expect ( scrollView ) . toHaveProp ( 'enableOnAndroid' , true ) ;
155+ expect ( scrollView ) . toHaveProp ( 'enableAutomaticScroll' , true ) ;
156+ expect ( scrollView ) . toHaveProp ( 'enableResetScrollToCoords' , false ) ;
157+ expect ( scrollView ) . toHaveProp ( 'extraScrollHeight' , 20 ) ;
158+ } ) ;
159+
117160 it ( 'calls handleDeeplink and tracks buy_musd event when Buy button is pressed' , ( ) => {
118161 const { handleDeeplink } = jest . requireMock (
119162 '../../../../../../core/DeeplinkManager' ,
@@ -159,14 +202,14 @@ describe('MusdCalculatorTab', () => {
159202 } ) ;
160203
161204 const locationX = ( amountToPercent ( 5000 ) / 100 ) * 300 ;
162- fireEvent ( track , 'pressIn' , {
163- nativeEvent : { locationX } ,
205+ await act ( async ( ) => {
206+ mockTapGestureHandlers . onEnd ?. ( { x : locationX } ) ;
164207 } ) ;
165208
166209 await waitFor ( ( ) => {
167210 expect ( getByTestId ( 'musd-slider-amount-display' ) ) . toHaveProp (
168211 'value' ,
169- '$5,000 ' ,
212+ '5000 ' ,
170213 ) ;
171214 expect (
172215 getByText ( / r e w a r d s \. m u s d \. e a r n i n g s _ p e r _ d a y _ s u f f i x / ) ,
@@ -183,52 +226,42 @@ describe('MusdCalculatorTab', () => {
183226 const { getByTestId, getByText } = render ( < MusdCalculatorTab /> ) ;
184227 const amountInput = getByTestId ( 'musd-slider-amount-display' ) ;
185228
186- fireEvent ( amountInput , 'focus' ) ;
187229 fireEvent . changeText ( amountInput , '5000' ) ;
188230
189231 await waitFor ( ( ) => {
190232 expect ( amountInput ) . toHaveProp ( 'value' , '5000' ) ;
191233 expect ( getByText ( / \$ 1 5 0 / ) ) . toBeOnTheScreen ( ) ;
192234 } ) ;
193-
194- fireEvent ( amountInput , 'endEditing' ) ;
195-
196- await waitFor ( ( ) => {
197- expect ( amountInput ) . toHaveProp ( 'value' , '$5,000' ) ;
198- } ) ;
199235 } ) ;
200236
201- it ( 'accepts amounts over the slider maximum' , async ( ) => {
237+ it ( 'caps typed amounts at the slider maximum' , async ( ) => {
202238 const { getByTestId, getByText } = render ( < MusdCalculatorTab /> ) ;
203239 const amountInput = getByTestId ( 'musd-slider-amount-display' ) ;
204240
205- fireEvent ( amountInput , 'focus' ) ;
206241 fireEvent . changeText ( amountInput , '12000' ) ;
207242
208243 await waitFor ( ( ) => {
209- expect ( amountInput ) . toHaveProp ( 'value' , '12000 ' ) ;
210- expect ( getByText ( / \$ 3 6 0 / ) ) . toBeOnTheScreen ( ) ;
244+ expect ( amountInput ) . toHaveProp ( 'value' , '10000 ' ) ;
245+ expect ( getByText ( / \$ 3 0 0 / ) ) . toBeOnTheScreen ( ) ;
211246 } ) ;
212247 } ) ;
213248
214- it ( 'normalizes decorated decimal input while editing' , async ( ) => {
249+ it ( 'normalizes decorated decimal input to two decimal places while editing' , async ( ) => {
215250 const { getByTestId, getByText } = render ( < MusdCalculatorTab /> ) ;
216251 const amountInput = getByTestId ( 'musd-slider-amount-display' ) ;
217252
218- fireEvent ( amountInput , 'focus' ) ;
219253 fireEvent . changeText ( amountInput , '$1,234.56.78' ) ;
220254
221255 await waitFor ( ( ) => {
222- expect ( amountInput ) . toHaveProp ( 'value' , '1234.5678 ' ) ;
223- expect ( getByText ( / \$ 3 7 \. 0 3 7 / ) ) . toBeOnTheScreen ( ) ;
256+ expect ( amountInput ) . toHaveProp ( 'value' , '1234.56 ' ) ;
257+ expect ( getByText ( / \$ 3 7 \. 0 4 / ) ) . toBeOnTheScreen ( ) ;
224258 } ) ;
225259 } ) ;
226260
227261 it ( 'treats invalid numeric input as zero' , async ( ) => {
228262 const { getByTestId, getAllByText } = render ( < MusdCalculatorTab /> ) ;
229263 const amountInput = getByTestId ( 'musd-slider-amount-display' ) ;
230264
231- fireEvent ( amountInput , 'focus' ) ;
232265 fireEvent . changeText ( amountInput , '.' ) ;
233266
234267 await waitFor ( ( ) => {
@@ -240,13 +273,13 @@ describe('MusdCalculatorTab', () => {
240273 it ( 'ignores slider presses before the track is measured' , ( ) => {
241274 const { getByTestId } = render ( < MusdCalculatorTab /> ) ;
242275
243- fireEvent ( getByTestId ( 'musd-slider-track' ) , 'pressIn' , {
244- nativeEvent : { locationX : 200 } ,
276+ act ( ( ) => {
277+ mockTapGestureHandlers . onEnd ?. ( { x : 200 } ) ;
245278 } ) ;
246279
247280 expect ( getByTestId ( 'musd-slider-amount-display' ) ) . toHaveProp (
248281 'value' ,
249- '$1,000 ' ,
282+ '1000 ' ,
250283 ) ;
251284 } ) ;
252285
@@ -261,17 +294,79 @@ describe('MusdCalculatorTab', () => {
261294 act ( ( ) => {
262295 mockPanGestureHandlers . onBegin ?.( { x : 150 } ) ;
263296 mockPanGestureHandlers . onUpdate ?.( { x : 300 } ) ;
297+ mockPanGestureHandlers . onEnd ?.( { x : 300 } ) ;
264298 mockPanGestureHandlers . onFinalize ?.( { x : 300 } ) ;
265299 } ) ;
266300
267301 await waitFor ( ( ) => {
268302 expect ( getByTestId ( 'musd-slider-amount-display' ) ) . toHaveProp (
269303 'value' ,
270- '$10,000 ' ,
304+ '10000 ' ,
271305 ) ;
272306 } ) ;
273307 } ) ;
274308
309+ it ( 'does not reset the slider when a press finalizes without a pan' , async ( ) => {
310+ const { getByTestId } = render ( < MusdCalculatorTab /> ) ;
311+ const track = getByTestId ( 'musd-slider-track' ) ;
312+
313+ fireEvent ( track , 'layout' , {
314+ nativeEvent : { layout : { width : 300 , height : 32 , x : 0 , y : 0 } } ,
315+ } ) ;
316+
317+ const locationX = ( amountToPercent ( 5000 ) / 100 ) * 300 ;
318+ await act ( async ( ) => {
319+ mockTapGestureHandlers . onEnd ?.( { x : locationX } ) ;
320+ } ) ;
321+
322+ await waitFor ( ( ) => {
323+ expect ( getByTestId ( 'musd-slider-amount-display' ) ) . toHaveProp (
324+ 'value' ,
325+ '5000' ,
326+ ) ;
327+ } ) ;
328+
329+ act ( ( ) => {
330+ mockPanGestureHandlers . onFinalize ?.( { x : 0 } ) ;
331+ } ) ;
332+
333+ expect ( getByTestId ( 'musd-slider-amount-display' ) ) . toHaveProp (
334+ 'value' ,
335+ '5000' ,
336+ ) ;
337+ } ) ;
338+
339+ it ( 'does not reset when tapping the same slider position twice' , async ( ) => {
340+ const { getByTestId } = render ( < MusdCalculatorTab /> ) ;
341+ const track = getByTestId ( 'musd-slider-track' ) ;
342+
343+ fireEvent ( track , 'layout' , {
344+ nativeEvent : { layout : { width : 300 , height : 32 , x : 0 , y : 0 } } ,
345+ } ) ;
346+
347+ const locationX = ( amountToPercent ( 5000 ) / 100 ) * 300 ;
348+
349+ await act ( async ( ) => {
350+ mockTapGestureHandlers . onEnd ?.( { x : locationX } ) ;
351+ } ) ;
352+
353+ await waitFor ( ( ) => {
354+ expect ( getByTestId ( 'musd-slider-amount-display' ) ) . toHaveProp (
355+ 'value' ,
356+ '5000' ,
357+ ) ;
358+ } ) ;
359+
360+ await act ( async ( ) => {
361+ mockTapGestureHandlers . onEnd ?.( { x : locationX } ) ;
362+ } ) ;
363+
364+ expect ( getByTestId ( 'musd-slider-amount-display' ) ) . toHaveProp (
365+ 'value' ,
366+ '5000' ,
367+ ) ;
368+ } ) ;
369+
275370 it ( 'ignores pan gesture handlers before the track is measured' , ( ) => {
276371 const { getByTestId } = render ( < MusdCalculatorTab /> ) ;
277372
@@ -282,7 +377,7 @@ describe('MusdCalculatorTab', () => {
282377
283378 expect ( getByTestId ( 'musd-slider-amount-display' ) ) . toHaveProp (
284379 'value' ,
285- '$1,000 ' ,
380+ '1000 ' ,
286381 ) ;
287382 } ) ;
288383} ) ;
0 commit comments