Skip to content

Commit 0a9a2cc

Browse files
fix(ramps): address Bugbot async handler; expand OrderDetails callback tests
Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent adb6c97 commit 0a9a2cc

2 files changed

Lines changed: 136 additions & 1 deletion

File tree

app/components/UI/Ramp/Views/Checkout/Checkout.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,7 @@ const Checkout = () => {
138138
]);
139139

140140
const handleNavigationStateChange = useCallback(
141-
async (navState: WebViewNavigation) => {
141+
(navState: WebViewNavigation) => {
142142
if (
143143
!hasCallbackFlow ||
144144
isRedirectionHandledRef.current ||

app/components/UI/Ramp/Views/OrderDetails/OrderDetails.test.tsx

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,21 @@ describe('OrderDetails', () => {
189189
expect(mockRefreshOrder).toHaveBeenCalled();
190190
});
191191

192+
it('shows localized error when pending order refresh rejects with non-Error', async () => {
193+
mockUseParams.mockReturnValue({ orderId: 'ord-pending' });
194+
mockGetOrderById.mockReturnValue({
195+
...mockOrder,
196+
status: RampsOrderStatus.Pending,
197+
});
198+
mockRefreshOrder.mockRejectedValue('not-an-error');
199+
200+
const { getByText } = render();
201+
202+
await waitFor(() => {
203+
expect(getByText('ramps_order_details.error_message')).toBeOnTheScreen();
204+
});
205+
});
206+
192207
it('tracks RAMPS_SCREEN_VIEWED when order is displayed', async () => {
193208
render();
194209
await waitFor(() => {
@@ -256,6 +271,126 @@ describe('OrderDetails', () => {
256271
});
257272
});
258273

274+
it('resets to build quote when callback returns no order', async () => {
275+
mockUseParams.mockReturnValue({
276+
callbackUrl: 'https://callback.example?x=1',
277+
providerCode: 'moonpay',
278+
walletAddress: '0x123',
279+
});
280+
mockGetOrderById.mockReturnValue(undefined);
281+
mockGetOrderFromCallback.mockResolvedValue(null);
282+
283+
render();
284+
285+
await waitFor(() => {
286+
expect(mockReset).toHaveBeenCalled();
287+
});
288+
const resetArg = mockReset.mock.calls[0][0] as {
289+
routes: { name: string }[];
290+
};
291+
expect(resetArg.routes[0].name).toBe(Routes.RAMP.BUILD_QUOTE);
292+
});
293+
294+
it('resets to build quote when callback order is in a bailed status', async () => {
295+
mockUseParams.mockReturnValue({
296+
callbackUrl: 'https://callback.example?x=1',
297+
providerCode: 'moonpay',
298+
walletAddress: '0x123',
299+
});
300+
mockGetOrderById.mockReturnValue(undefined);
301+
mockGetOrderFromCallback.mockResolvedValue({
302+
providerOrderId: 'ord-bail',
303+
status: RampsOrderStatus.Precreated,
304+
provider: { id: 'moonpay' },
305+
walletAddress: '0x123',
306+
});
307+
308+
render();
309+
310+
await waitFor(() => {
311+
expect(mockReset).toHaveBeenCalled();
312+
});
313+
expect(mockAddOrder).not.toHaveBeenCalled();
314+
});
315+
316+
it('uses route cryptocurrency for toast when callback order has no crypto symbol', async () => {
317+
const { showV2OrderToast } = jest.requireMock(
318+
'../../utils/v2OrderToast',
319+
) as { showV2OrderToast: jest.Mock };
320+
const orderWithoutCryptoSymbol = {
321+
providerOrderId: 'ord-cb-2',
322+
status: RampsOrderStatus.Completed,
323+
cryptoAmount: '1',
324+
provider: { id: 'moonpay' },
325+
walletAddress: '0x123',
326+
};
327+
mockUseParams.mockReturnValue({
328+
callbackUrl: 'https://callback.example?x=1',
329+
providerCode: 'moonpay',
330+
walletAddress: '0x123',
331+
cryptocurrency: 'SOL',
332+
});
333+
mockGetOrderById.mockReturnValue(undefined);
334+
mockGetOrderFromCallback.mockResolvedValue(orderWithoutCryptoSymbol);
335+
336+
render();
337+
338+
await waitFor(() => {
339+
expect(showV2OrderToast).toHaveBeenCalledWith(
340+
expect.objectContaining({
341+
orderId: 'ord-cb-2',
342+
cryptocurrency: 'SOL',
343+
}),
344+
);
345+
});
346+
});
347+
348+
it('does not emit status metrics when callback order status matches prior order', async () => {
349+
const { showV2OrderToast } = jest.requireMock(
350+
'../../utils/v2OrderToast',
351+
) as { showV2OrderToast: jest.Mock };
352+
const completedOrder = {
353+
providerOrderId: 'ord-same',
354+
status: RampsOrderStatus.Completed,
355+
cryptoCurrency: { symbol: 'ETH' },
356+
cryptoAmount: '0.1',
357+
provider: { id: 'moonpay' },
358+
walletAddress: '0x123',
359+
};
360+
mockUseParams.mockReturnValue({
361+
callbackUrl: 'https://callback.example?x=1',
362+
providerCode: 'moonpay',
363+
walletAddress: '0x123',
364+
});
365+
mockGetOrderById.mockImplementation((id: string) =>
366+
id === 'ord-same' ? completedOrder : undefined,
367+
);
368+
mockGetOrderFromCallback.mockResolvedValue(completedOrder);
369+
370+
render();
371+
372+
await waitFor(() => {
373+
expect(showV2OrderToast).toHaveBeenCalled();
374+
});
375+
expect(mockHandleOrderStatusChangedForMetrics).not.toHaveBeenCalled();
376+
});
377+
378+
it('shows localized error when callback fetch rejects with Error that has no message', async () => {
379+
mockUseParams.mockReturnValue({
380+
callbackUrl: 'https://callback.example?x=1',
381+
providerCode: 'moonpay',
382+
walletAddress: '0x123',
383+
});
384+
mockGetOrderById.mockReturnValue(undefined);
385+
mockGetOrderFromCallback.mockRejectedValue(new Error(''));
386+
387+
const { getByText } = render();
388+
389+
await waitFor(() => {
390+
expect(getByText('ramps_order_details.error_message')).toBeOnTheScreen();
391+
});
392+
});
393+
259394
it('shows error state with retry when initial callback fetch fails', async () => {
260395
mockUseParams.mockReturnValue({
261396
callbackUrl: 'metamask://on-ramp/providers/paypal?orderId=abc',

0 commit comments

Comments
 (0)