From 1d910f1e50f5aafbc6988044e68f6e5290d665e8 Mon Sep 17 00:00:00 2001 From: jonathanlei Date: Mon, 3 Jul 2023 14:51:06 -0700 Subject: [PATCH] feat: support more search currency formats (#762) The search currently supports `{currencyCode}.{issuer}`, this PR added support for `{currencyCode}:{issuer}`, `{currencyCode}+{issuer}`, and `{currencyCode}-{issuer}`. These are formats used by some many dexs. https://gatehub.net/markets/XRP/EUR+rhub8VRN55s94qWKDv6jmDy1pUykJzF3wq https://xdex.com/tokens/SOLO:rsoLo2S1kiGeCcn6hCUXVrCpGMWLrRrLZz https://sologenic.org/trade?network=mainnet&market=534F4C4F00000000000000000000000000000000%2BrsoLo2S1kiGeCcn6hCUXVrCpGMWLrRrLZz%2FXRP https://xpmarket.com/dex/XRP/USD-rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B --- src/containers/Header/Search.tsx | 7 ++-- src/containers/Header/test/Search.test.js | 43 +++++++++++++++++++++++ src/containers/shared/utils.js | 4 +-- 3 files changed, 49 insertions(+), 5 deletions(-) diff --git a/src/containers/Header/Search.tsx b/src/containers/Header/Search.tsx index 424c53604..9bce64873 100644 --- a/src/containers/Header/Search.tsx +++ b/src/containers/Header/Search.tsx @@ -29,6 +29,8 @@ const determineHashType = async (id: string, rippledContext: XrplClient) => { return 'nft' } } +// separator for currency formats +const separators = /[.:+-]/ const getIdType = async (id: string, rippledContext: XrplClient) => { if (DECIMAL_REGEX.test(id)) { @@ -50,7 +52,7 @@ const getIdType = async (id: string, rippledContext: XrplClient) => { } if ( (CURRENCY_REGEX.test(id) || FULL_CURRENCY_REGEX.test(id)) && - isValidClassicAddress(id.split('.')[1]) + isValidClassicAddress(id.split(separators)[1]) ) { return 'token' } @@ -87,7 +89,7 @@ const normalize = (id: string, type: string) => { return id.replace('@', '$') } } else if (type === 'token') { - const components = id.split('.') + const components = id.split(separators) return `${components[0].toLowerCase()}.${components[1]}` } return id @@ -110,7 +112,6 @@ export const Search = ({ callback = () => {} }: SearchProps) => { search_term: strippedId, search_category: type, }) - history.push( type === 'invalid' ? `/search/${strippedId}` diff --git a/src/containers/Header/test/Search.test.js b/src/containers/Header/test/Search.test.js index d458373c7..a085a6015 100644 --- a/src/containers/Header/test/Search.test.js +++ b/src/containers/Header/test/Search.test.js @@ -54,8 +54,19 @@ describe('Search component', () => { const hash = '59239EA78084F6E2F288473F8AE02F3E6FC92F44BDE59668B5CAE361D3D32838' const token1 = 'cny.rJ1adrpGS3xsnQMb9Cw54tWJVFPuSdZHK' + const token1VariantPlus = 'cny.rJ1adrpGS3xsnQMb9Cw54tWJVFPuSdZHK' + const token1VariantMinus = 'cny-rJ1adrpGS3xsnQMb9Cw54tWJVFPuSdZHK' + const token1VariantColon = 'cny:rJ1adrpGS3xsnQMb9Cw54tWJVFPuSdZHK' + const token2 = '534f4c4f00000000000000000000000000000000.rsoLo2S1kiGeCcn6hCUXVrCpGMWLrRrLZz' + const token2VariantPlus = + '534f4c4f00000000000000000000000000000000+rsoLo2S1kiGeCcn6hCUXVrCpGMWLrRrLZz' + const token2VariantMinus = + '534f4c4f00000000000000000000000000000000-rsoLo2S1kiGeCcn6hCUXVrCpGMWLrRrLZz' + const token2VariantColon = + '534f4c4f00000000000000000000000000000000:rsoLo2S1kiGeCcn6hCUXVrCpGMWLrRrLZz' + const nftoken = '000800011C7D8ED1D715A0017E41BF9499ECC17E7FB666320000099B00000000' const invalidString = '123invalid' @@ -132,11 +143,43 @@ describe('Search component', () => { await flushPromises() expect(window.location.pathname).toEqual(`/token/${token1}`) + // testing multiple variants of token format + input.instance().value = token1VariantColon + input.simulate('keyDown', { key: 'Enter' }) + await flushPromises() + expect(window.location.pathname).toEqual(`/token/${token1}`) + + input.instance().value = token1VariantPlus + input.simulate('keyDown', { key: 'Enter' }) + await flushPromises() + expect(window.location.pathname).toEqual(`/token/${token1}`) + + input.instance().value = token1VariantMinus + input.simulate('keyDown', { key: 'Enter' }) + await flushPromises() + expect(window.location.pathname).toEqual(`/token/${token1}`) + input.instance().value = token2 input.simulate('keyDown', { key: 'Enter' }) await flushPromises() expect(window.location.pathname).toEqual(`/token/${token2}`) + // testing multiple variants of full token format + input.instance().value = token2VariantColon + input.simulate('keyDown', { key: 'Enter' }) + await flushPromises() + expect(window.location.pathname).toEqual(`/token/${token2}`) + + input.instance().value = token2VariantPlus + input.simulate('keyDown', { key: 'Enter' }) + await flushPromises() + expect(window.location.pathname).toEqual(`/token/${token2}`) + + input.instance().value = token2VariantMinus + input.simulate('keyDown', { key: 'Enter' }) + await flushPromises() + expect(window.location.pathname).toEqual(`/token/${token2}`) + // Returns a response upon a valid nft_id, redirect to NFT mockAPI.mockImplementation(() => { throw new Error('Tx not found', 404) diff --git a/src/containers/shared/utils.js b/src/containers/shared/utils.js index 5d2cd811f..d1dd8eab9 100644 --- a/src/containers/shared/utils.js +++ b/src/containers/shared/utils.js @@ -25,9 +25,9 @@ export const FETCH_INTERVAL_ERROR_MILLIS = 300 export const DECIMAL_REGEX = /^\d+$/ export const HASH_REGEX = /[0-9A-Fa-f]{64}/i export const CURRENCY_REGEX = - /^[a-zA-Z0-9]{3,}\.r[rpshnaf39wBUDNEGHJKLM4PQRST7VWXYZ2bcdeCg65jkm8oFqi1tuvAxyz]{27,35}$/ + /^[a-zA-Z0-9]{3,}[.:+-]r[rpshnaf39wBUDNEGHJKLM4PQRST7VWXYZ2bcdeCg65jkm8oFqi1tuvAxyz]{27,35}$/ export const FULL_CURRENCY_REGEX = - /^[0-9A-Fa-f]{40}\.r[rpshnaf39wBUDNEGHJKLM4PQRST7VWXYZ2bcdeCg65jkm8oFqi1tuvAxyz]{27,35}$/ + /^[0-9A-Fa-f]{40}[.:+-]r[rpshnaf39wBUDNEGHJKLM4PQRST7VWXYZ2bcdeCg65jkm8oFqi1tuvAxyz]{27,35}$/ export const VALIDATORS_REGEX = /^n[9H][0-9A-Za-z]{50}$/ export const GREY = '#9BA2B0'