From 6195a9e44413212224890abb337477e0c393af4a Mon Sep 17 00:00:00 2001 From: georgeweiler Date: Wed, 29 Jan 2025 11:49:32 -0700 Subject: [PATCH 1/5] feat: upgrades ramp-sdk to v2.0.3 --- .../UI/Ramp/Views/Quotes/Quotes.test.tsx | 21 +++++++++---------- .../UI/Ramp/Views/Quotes/Quotes.tsx | 8 ++++++- package.json | 2 +- yarn.lock | 8 +++---- 4 files changed, 22 insertions(+), 17 deletions(-) diff --git a/app/components/UI/Ramp/Views/Quotes/Quotes.test.tsx b/app/components/UI/Ramp/Views/Quotes/Quotes.test.tsx index 22c84f9f7ad..bf2658448dc 100644 --- a/app/components/UI/Ramp/Views/Quotes/Quotes.test.tsx +++ b/app/components/UI/Ramp/Views/Quotes/Quotes.test.tsx @@ -1,8 +1,8 @@ import React from 'react'; import { cloneDeep } from 'lodash'; import { + AllQuotesResponse, ProviderBuyFeatureBrowserEnum, - QuoteError, QuoteResponse, } from '@consensys/on-ramp-sdk'; import { @@ -15,15 +15,12 @@ import { renderScreen, DeepPartial, } from '../../../../../util/test/renderWithProvider'; - import Quotes, { QuotesParams } from './Quotes'; import { mockQuotesData } from './Quotes.constants'; import Timer from './Timer'; import LoadingQuotes from './LoadingQuotes'; - import { RampSDK } from '../../sdk'; import useQuotes from '../../hooks/useQuotes'; - import Routes from '../../../../../constants/navigation/Routes'; import { backgroundState } from '../../../../../util/test/initial-root-state'; import { RampType } from '../../types'; @@ -121,7 +118,7 @@ jest.mock('../../../../../util/navigation/navUtils', () => ({ const mockQueryGetQuotes = jest.fn(); const mockUseQuotesInitialValues: Partial> = { - data: mockQuotesData as (QuoteResponse | QuoteError)[], + data: { quotes: mockQuotesData } as AllQuotesResponse, isFetching: false, error: null, query: mockQueryGetQuotes, @@ -211,7 +208,7 @@ describe('Quotes', () => { it('renders correctly after animation without quotes', async () => { mockUseQuotesValues = { ...mockUseQuotesInitialValues, - data: [], + data: { quotes: [], sorted: [] } as AllQuotesResponse, }; render(Quotes); act(() => { @@ -240,10 +237,12 @@ describe('Quotes', () => { it('renders correctly after animation with quotes and expanded', async () => { mockUseQuotesValues = { ...mockUseQuotesInitialValues, - data: [ - ...mockQuotesData.slice(0, 2), - { ...mockQuotesData[2], error: false }, - ] as (QuoteResponse | QuoteError)[], + data: { + quotes: [ + ...mockQuotesData.slice(0, 2), + { ...mockQuotesData[2], error: false }, + ], + } as AllQuotesResponse, }; render(Quotes); fireEvent.press( @@ -302,7 +301,7 @@ describe('Quotes', () => { mockUseQuotesValues = { ...mockUseQuotesInitialValues, - data: mockData as (QuoteResponse | QuoteError)[], + data: { quotes: mockData } as AllQuotesResponse, }; render(Quotes); act(() => { diff --git a/app/components/UI/Ramp/Views/Quotes/Quotes.tsx b/app/components/UI/Ramp/Views/Quotes/Quotes.tsx index 5738cdbc549..b3aa216dfbe 100644 --- a/app/components/UI/Ramp/Views/Quotes/Quotes.tsx +++ b/app/components/UI/Ramp/Views/Quotes/Quotes.tsx @@ -134,13 +134,19 @@ function Quotes() { return { opacity: value }; }); + let quotes = null; + const { - data: quotes, + data, isFetching: isFetchingQuotes, error: ErrorFetchingQuotes, query: fetchQuotes, } = useQuotes(params.amount); + if (data) { + quotes = data.quotes; + } + const [filteredQuotes, highlightedQuotes] = useMemo(() => { if (quotes) { const allQuotes = quotes.filter( diff --git a/package.json b/package.json index d48df88919c..d5e3defc1da 100644 --- a/package.json +++ b/package.json @@ -145,7 +145,7 @@ }, "dependencies": { "@config-plugins/detox": "^8.0.0", - "@consensys/on-ramp-sdk": "1.28.8", + "@consensys/on-ramp-sdk": "2.0.3", "@keystonehq/bc-ur-registry-eth": "^0.19.1", "@keystonehq/metamask-airgapped-keyring": "^0.13.1", "@keystonehq/ur-decoder": "^0.12.2", diff --git a/yarn.lock b/yarn.lock index 651c62ce858..de4c63ebdf9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1429,10 +1429,10 @@ dependencies: expo-build-properties "~0.12.1" -"@consensys/on-ramp-sdk@1.28.8": - version "1.28.8" - resolved "https://registry.yarnpkg.com/@consensys/on-ramp-sdk/-/on-ramp-sdk-1.28.8.tgz#e39d833974d9d49653a2ec107ffbebadb49580d1" - integrity sha512-snm1hGjIaFMHvB7seLoaBgUQXH1n8/iXPMSm96d5QItFKVw4GZyqsKRnuT2u/CwIeXtYOKf59q7+Lp0UMg+mFQ== +"@consensys/on-ramp-sdk@2.0.3": + version "2.0.3" + resolved "https://consensys.jfrog.io/artifactory/api/npm/npm/@consensys/on-ramp-sdk/-/on-ramp-sdk-2.0.3.tgz#c4a9068c75e9a2e7bb8b33071900d6f1388bab69" + integrity sha512-zMNefDC6xGu8vuo/WRxQvsvaEmqRdOCs4afv8yaCRaDalnds4l9ph4lcs+NLrdJFUYjKMo8Y+pU8SidSUug0Wg== dependencies: async "^3.2.3" axios "^0.28.0" From 750bf403b77989605e2542a7df771e4278841c83 Mon Sep 17 00:00:00 2001 From: georgeweiler Date: Wed, 29 Jan 2025 11:53:59 -0700 Subject: [PATCH 2/5] chore: add carrot to ramp-sdk version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index d5e3defc1da..1984660e47e 100644 --- a/package.json +++ b/package.json @@ -145,7 +145,7 @@ }, "dependencies": { "@config-plugins/detox": "^8.0.0", - "@consensys/on-ramp-sdk": "2.0.3", + "@consensys/on-ramp-sdk": "^2.0.3", "@keystonehq/bc-ur-registry-eth": "^0.19.1", "@keystonehq/metamask-airgapped-keyring": "^0.13.1", "@keystonehq/ur-decoder": "^0.12.2", From ed80a29e4488db2068f79a047698516bee2918bf Mon Sep 17 00:00:00 2001 From: georgeweiler Date: Wed, 29 Jan 2025 11:56:54 -0700 Subject: [PATCH 3/5] chore: updates yarn lockfile --- yarn.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/yarn.lock b/yarn.lock index de4c63ebdf9..2e5b8b4b976 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1429,7 +1429,7 @@ dependencies: expo-build-properties "~0.12.1" -"@consensys/on-ramp-sdk@2.0.3": +"@consensys/on-ramp-sdk@^2.0.3": version "2.0.3" resolved "https://consensys.jfrog.io/artifactory/api/npm/npm/@consensys/on-ramp-sdk/-/on-ramp-sdk-2.0.3.tgz#c4a9068c75e9a2e7bb8b33071900d6f1388bab69" integrity sha512-zMNefDC6xGu8vuo/WRxQvsvaEmqRdOCs4afv8yaCRaDalnds4l9ph4lcs+NLrdJFUYjKMo8Y+pU8SidSUug0Wg== From 519535ce01ee769962fd4d90188bab4826aacdf3 Mon Sep 17 00:00:00 2001 From: georgeweiler Date: Thu, 30 Jan 2025 06:57:33 -0700 Subject: [PATCH 4/5] refactor: updates usequote hook interface and write test --- .../UI/Ramp/Views/Quotes/Quotes.tsx | 8 +- .../UI/Ramp/hooks/useQuotes.test.ts | 186 ++++++++++++++++++ app/components/UI/Ramp/hooks/useQuotes.ts | 5 +- 3 files changed, 191 insertions(+), 8 deletions(-) create mode 100644 app/components/UI/Ramp/hooks/useQuotes.test.ts diff --git a/app/components/UI/Ramp/Views/Quotes/Quotes.tsx b/app/components/UI/Ramp/Views/Quotes/Quotes.tsx index b3aa216dfbe..20476b2c9f6 100644 --- a/app/components/UI/Ramp/Views/Quotes/Quotes.tsx +++ b/app/components/UI/Ramp/Views/Quotes/Quotes.tsx @@ -134,19 +134,13 @@ function Quotes() { return { opacity: value }; }); - let quotes = null; - const { - data, + quotes, isFetching: isFetchingQuotes, error: ErrorFetchingQuotes, query: fetchQuotes, } = useQuotes(params.amount); - if (data) { - quotes = data.quotes; - } - const [filteredQuotes, highlightedQuotes] = useMemo(() => { if (quotes) { const allQuotes = quotes.filter( diff --git a/app/components/UI/Ramp/hooks/useQuotes.test.ts b/app/components/UI/Ramp/hooks/useQuotes.test.ts new file mode 100644 index 00000000000..73464df04d7 --- /dev/null +++ b/app/components/UI/Ramp/hooks/useQuotes.test.ts @@ -0,0 +1,186 @@ +import { RampSDK } from '../sdk'; +import useSDKMethod from './useSDKMethod'; +import { renderHookWithProvider } from '../../../../util/test/renderWithProvider'; +import useQuotes from './useQuotes'; + +type DeepPartial = { + [key in keyof BaseType]?: DeepPartial; +}; + +const mockuseRampSDKInitialValues: DeepPartial = { + selectedRegion: { id: 'test-region-id' }, + selectedPaymentMethodId: 'test-payment-method-id', + selectedAsset: { id: 'test-crypto-id' }, + selectedFiatCurrencyId: 'test-fiat-currency-id-1', + selectedAddress: 'test-address', + isBuy: true, +}; + +let mockUseRampSDKValues: DeepPartial = { + ...mockuseRampSDKInitialValues, +}; + +jest.mock('../sdk', () => ({ + useRampSDK: () => mockUseRampSDKValues, +})); + +jest.mock('./useSDKMethod'); + +describe('useQuotes', () => { + beforeEach(() => { + jest.clearAllMocks(); + mockUseRampSDKValues = { + ...mockuseRampSDKInitialValues, + }; + }); + + it('calls useSDKMethod with the correct parameters for buy', () => { + (useSDKMethod as jest.Mock).mockReturnValue([ + { + data: { quotes: [] }, + error: null, + isFetching: false, + }, + jest.fn(), + ]); + renderHookWithProvider(() => useQuotes(100)); + + expect(useSDKMethod).toHaveBeenCalledWith( + 'getQuotes', + 'test-region-id', + 'test-payment-method-id', + 'test-crypto-id', + 'test-fiat-currency-id-1', + 100, + 'test-address', + ); + }); + + it('calls useSDKMethod with the correct parameters for sell', () => { + mockUseRampSDKValues.isBuy = false; + (useSDKMethod as jest.Mock).mockReturnValue([ + { + data: { quotes: [] }, + error: null, + isFetching: false, + }, + jest.fn(), + ]); + renderHookWithProvider(() => useQuotes(100)); + + expect(useSDKMethod).toHaveBeenCalledWith( + 'getSellQuotes', + 'test-region-id', + 'test-payment-method-id', + 'test-crypto-id', + 'test-fiat-currency-id-1', + 100, + 'test-address', + ); + }); + + it('returns loading state if fetching quotes', () => { + const mockQuery = jest.fn(); + (useSDKMethod as jest.Mock).mockReturnValue([ + { + data: null, + error: null, + isFetching: true, + }, + mockQuery, + ]); + const { result } = renderHookWithProvider(() => useQuotes(100)); + expect(result.current).toEqual({ + quotes: null, + isFetching: true, + error: null, + query: mockQuery, + }); + }); + + it('returns error state if there is an error fetching quotes', () => { + const mockQuery = jest.fn(); + (useSDKMethod as jest.Mock).mockReturnValue([ + { + data: null, + error: 'error-fetching-quotes', + isFetching: false, + }, + mockQuery, + ]); + const { result } = renderHookWithProvider(() => useQuotes(100)); + expect(result.current).toEqual({ + quotes: null, + isFetching: false, + error: 'error-fetching-quotes', + query: mockQuery, + }); + }); + + it('returns quotes if fetching is successful', () => { + const mockQuery = jest.fn(); + (useSDKMethod as jest.Mock).mockReturnValue([ + { + data: { quotes: [{ id: 'quote-1' }, { id: 'quote-2' }] }, + error: null, + isFetching: false, + }, + mockQuery, + ]); + const { result } = renderHookWithProvider(() => useQuotes(100)); + expect(result.current).toEqual({ + quotes: [{ id: 'quote-1' }, { id: 'quote-2' }], + isFetching: false, + error: null, + query: mockQuery, + }); + }); + + it('handles different amount types (string and number)', () => { + const mockQuery = jest.fn(); + (useSDKMethod as jest.Mock).mockReturnValue([ + { + data: { quotes: [{ id: 'quote-1' }] }, + error: null, + isFetching: false, + }, + mockQuery, + ]); + + const { result: resultNumber } = renderHookWithProvider(() => + useQuotes(100), + ); + expect(resultNumber.current.quotes).toEqual([{ id: 'quote-1' }]); + + const { result: resultString } = renderHookWithProvider(() => + useQuotes('100'), + ); + expect(resultString.current.quotes).toEqual([{ id: 'quote-1' }]); + }); + + it('updates correctly when parameters change', () => { + const mockQuery = jest.fn(); + (useSDKMethod as jest.Mock).mockReturnValue([ + { + data: { quotes: [{ id: 'quote-1' }] }, + error: null, + isFetching: false, + }, + mockQuery, + ]); + + const { result, rerender } = renderHookWithProvider(() => useQuotes(100)); + expect(result.current.quotes).toEqual([{ id: 'quote-1' }]); + + (useSDKMethod as jest.Mock).mockReturnValue([ + { + data: { quotes: [{ id: 'quote-2' }] }, + error: null, + isFetching: false, + }, + mockQuery, + ]); + rerender(() => useQuotes(200)); + expect(result.current.quotes).toEqual([{ id: 'quote-2' }]); + }); +}); diff --git a/app/components/UI/Ramp/hooks/useQuotes.ts b/app/components/UI/Ramp/hooks/useQuotes.ts index eea49507946..29cd75b8c35 100644 --- a/app/components/UI/Ramp/hooks/useQuotes.ts +++ b/app/components/UI/Ramp/hooks/useQuotes.ts @@ -1,5 +1,6 @@ import { useRampSDK } from '../sdk'; import useSDKMethod from './useSDKMethod'; +import { useMemo } from 'react'; function useQuotes(amount: number | string) { const { @@ -20,8 +21,10 @@ function useQuotes(amount: number | string) { selectedAddress, ); + const quotes = useMemo(() => data?.quotes || null, [data]); + return { - data, + quotes, isFetching, error, query, From b505bc17bf03fd122cfd38d44751cb73022bff40 Mon Sep 17 00:00:00 2001 From: georgeweiler Date: Thu, 30 Jan 2025 08:46:10 -0700 Subject: [PATCH 5/5] chore: fixes yarn lock issues --- yarn.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/yarn.lock b/yarn.lock index 2e5b8b4b976..164afe672fb 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1431,7 +1431,7 @@ "@consensys/on-ramp-sdk@^2.0.3": version "2.0.3" - resolved "https://consensys.jfrog.io/artifactory/api/npm/npm/@consensys/on-ramp-sdk/-/on-ramp-sdk-2.0.3.tgz#c4a9068c75e9a2e7bb8b33071900d6f1388bab69" + resolved "https://registry.yarnpkg.com/@consensys/on-ramp-sdk/-/on-ramp-sdk-2.0.3.tgz#c4a9068c75e9a2e7bb8b33071900d6f1388bab69" integrity sha512-zMNefDC6xGu8vuo/WRxQvsvaEmqRdOCs4afv8yaCRaDalnds4l9ph4lcs+NLrdJFUYjKMo8Y+pU8SidSUug0Wg== dependencies: async "^3.2.3"