From 0d34a045771067a085005583abf71a32ce7ec016 Mon Sep 17 00:00:00 2001 From: "cgero.eth" Date: Tue, 30 Jul 2024 09:45:11 +0200 Subject: [PATCH] feat(APP-3460): Export VoteIndicator type, update buildEntityUrl function to support a chainId parameter (#254) --- CHANGELOG.md | 6 ++ src/modules/components/vote/index.ts | 1 + .../useBlockExplorer/useBlockExplorer.test.ts | 66 +++++++++++++------ .../useBlockExplorer/useBlockExplorer.ts | 38 ++++++++--- 4 files changed, 82 insertions(+), 29 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index dab9b1f5b..94f0a8687 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,12 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), ## [Unreleased] +### Added + +- Export `VoteIndicator` type from `Vote` module. +- Update `useBlockExplorer` hook to export a `getBlockExplorer` function, update `buildEntityUrl` function to support + a `chainId` parameter which overrides the `chainId` hook parameter. + ## [1.0.40] - 2024-07-26 ### Added diff --git a/src/modules/components/vote/index.ts b/src/modules/components/vote/index.ts index 9f2c3d594..188a8d798 100644 --- a/src/modules/components/vote/index.ts +++ b/src/modules/components/vote/index.ts @@ -1,2 +1,3 @@ export * from './voteDataListItem'; export * from './voteProposalDataListItem'; +export { VoteIndicator } from './voteUtils'; diff --git a/src/modules/hooks/useBlockExplorer/useBlockExplorer.test.ts b/src/modules/hooks/useBlockExplorer/useBlockExplorer.test.ts index e9b5aa3cd..adca4b65a 100644 --- a/src/modules/hooks/useBlockExplorer/useBlockExplorer.test.ts +++ b/src/modules/hooks/useBlockExplorer/useBlockExplorer.test.ts @@ -14,57 +14,85 @@ describe('useBlockExplorer hook', () => { useChainsSpy.mockReset(); }); - describe('blockExplorer', () => { + describe('getBlockExplorer', () => { it('returns the block explorer definitions for the given chain id from the specified list of chains', () => { const chains: [Chain, ...Chain[]] = [mainnet, polygon, sepolia]; - const chainId = polygon.id; - const { result } = renderHook(() => useBlockExplorer({ chains, chainId })); - expect(result.current.blockExplorer).toEqual(polygon.blockExplorers.default); + const { result } = renderHook(() => useBlockExplorer({ chains })); + expect(result.current.getBlockExplorer(polygon.id)).toEqual(polygon.blockExplorers.default); }); it('returns the block explorer definitions for the given chain id from the chains defined on wagmi provider', () => { useChainsSpy.mockReturnValue([polygon, sepolia]); - const chainId = sepolia.id; - const { result } = renderHook(() => useBlockExplorer({ chainId })); - expect(result.current.blockExplorer).toEqual(sepolia.blockExplorers.default); + const { result } = renderHook(() => useBlockExplorer()); + expect(result.current.getBlockExplorer(sepolia.id)).toEqual(sepolia.blockExplorers.default); }); it('returns the block explorer of the first chain in the chains prop when chainId is not defined', () => { const chains: [Chain, ...Chain[]] = [mainnet, sepolia, polygon]; const { result } = renderHook(() => useBlockExplorer({ chains })); - expect(result.current.blockExplorer).toEqual(chains[0].blockExplorers?.default); + expect(result.current.getBlockExplorer()).toEqual(chains[0].blockExplorers?.default); }); it('returns the block explorer of the first chain in the wagmi config when chainId and chains are not defined', () => { useChainsSpy.mockReturnValue([sepolia, polygon]); const { result } = renderHook(() => useBlockExplorer()); - expect(result.current.blockExplorer).toEqual(sepolia.blockExplorers?.default); + expect(result.current.getBlockExplorer()).toEqual(sepolia.blockExplorers?.default); + }); + + it('returns the block explorer of the chain specified on the hook when function chain-id parameter is not specified', () => { + const chains: [Chain, ...Chain[]] = [mainnet, polygon]; + const { result } = renderHook(() => useBlockExplorer({ chains, chainId: polygon.id })); + expect(result.current.getBlockExplorer()).toEqual(polygon.blockExplorers.default); }); it('returns the block explorer set to undefined when chain definitions cannot be found for the given chain id', () => { const chains: [Chain, ...Chain[]] = [mainnet, polygon]; - const { result } = renderHook(() => useBlockExplorer({ chains, chainId: sepolia.id })); - expect(result.current.blockExplorer).toBeUndefined(); + const { result } = renderHook(() => useBlockExplorer({ chains })); + expect(result.current.getBlockExplorer(sepolia.id)).toBeUndefined(); }); it('returns the block explorer set to undefined when related chain definitions does not include info about the block explorer', () => { const { blockExplorers, ...mainnetWithoutBlockExplorer } = mainnet; useChainsSpy.mockReturnValue([mainnetWithoutBlockExplorer]); - const { result } = renderHook(() => useBlockExplorer({ chainId: mainnet.id })); - expect(result.current.blockExplorer).toBeUndefined(); + const { result } = renderHook(() => useBlockExplorer()); + expect(result.current.getBlockExplorer(mainnet.id)).toBeUndefined(); + }); + }); + + describe('blockExplorer', () => { + it('returns the block explorer definitions for the given chain id from the specified list of chains', () => { + const chains: [Chain, ...Chain[]] = [mainnet, polygon, sepolia]; + const { result } = renderHook(() => useBlockExplorer({ chains, chainId: polygon.id })); + expect(result.current.blockExplorer).toEqual(polygon.blockExplorers.default); }); }); describe('buildEntityUrl', () => { it('generates correct URL for different entity types', () => { - useChainsSpy.mockReturnValue([mainnet, polygon]); + useChainsSpy.mockReturnValue([mainnet, polygon, sepolia]); - const { result } = renderHook(() => useBlockExplorer({ chainId: mainnet.id })); - const addressUrl = result.current.buildEntityUrl({ type: ChainEntityType.ADDRESS, id: '0x123' }); - expect(addressUrl).toMatch(/address\/0x123/); + const { result } = renderHook(() => useBlockExplorer()); + const { buildEntityUrl } = result.current; + + const addressUrl = buildEntityUrl({ type: ChainEntityType.ADDRESS, id: '0x123', chainId: mainnet.id }); + expect(addressUrl).toEqual('https://etherscan.io/address/0x123'); + + const tokenUrl = buildEntityUrl({ type: ChainEntityType.TOKEN, id: '0xabc', chainId: polygon.id }); + expect(tokenUrl).toEqual('https://polygonscan.com/token/0xabc'); + + const txUrl = buildEntityUrl({ type: ChainEntityType.TRANSACTION, id: '123', chainId: sepolia.id }); + expect(txUrl).toEqual('https://sepolia.etherscan.io/tx/123'); + }); + + it('defaults to the chainId parameter set on the hook when chainId function parameter is not set', () => { + useChainsSpy.mockReturnValue([mainnet, polygon, sepolia]); + + const { result } = renderHook(() => useBlockExplorer({ chainId: sepolia.id })); + const { buildEntityUrl } = result.current; - const transactionUrl = result.current.buildEntityUrl({ type: ChainEntityType.TRANSACTION, id: '0xabc' }); - expect(transactionUrl).toMatch(/tx\/0xabc/); + expect(buildEntityUrl({ type: ChainEntityType.TRANSACTION, id: '123' })).toEqual( + 'https://sepolia.etherscan.io/tx/123', + ); }); it('returns undefined when block explorer info is missing', () => { diff --git a/src/modules/hooks/useBlockExplorer/useBlockExplorer.ts b/src/modules/hooks/useBlockExplorer/useBlockExplorer.ts index e550781b1..d44f2ef28 100644 --- a/src/modules/hooks/useBlockExplorer/useBlockExplorer.ts +++ b/src/modules/hooks/useBlockExplorer/useBlockExplorer.ts @@ -9,12 +9,13 @@ export enum ChainEntityType { export interface IUseBlockExplorerParams { /** - * Chains definitions to use for building the block explorer URLs. Defaults to the chains defined on the Wagmi - * context provider. + * Chains definitions to use for returning the block explorer definitions and building the URLs. Defaults to the + * chains defined on the Wagmi context provider. */ chains?: Config['chains']; /** - * Chain ID to build the URLs for. Defaults to the id of the first chain on the chains list. + * Uses the block explorer definition of the specified Chain ID when set. Defaults to the ID of the first chain on + * the chains list. */ chainId?: number; } @@ -24,6 +25,10 @@ export interface IBuildEntityUrlParams { * The type of the entity (e.g. address, transaction, token) */ type: ChainEntityType; + /** + * ID of the chain related to the entity. When set, overrides the chainId set as hook parameter. + */ + chainId?: number; /** * The ID of the entity (e.g. transaction hash for a transaction) */ @@ -31,22 +36,35 @@ export interface IBuildEntityUrlParams { } export const useBlockExplorer = (params?: IUseBlockExplorerParams) => { - const { chains, chainId } = params ?? {}; + const { chains, chainId: hookChainId } = params ?? {}; const globalChains = useChains(); - const processedChains = chains ?? globalChains; - const chainDefinitions = chainId ? processedChains.find((chain) => chain.id === chainId) : processedChains[0]; - const { default: blockExplorer } = chainDefinitions?.blockExplorers ?? {}; + const getBlockExplorer = useCallback( + (chainId?: number) => { + const processedChains = chains ?? globalChains; + const processedChainId = chainId ?? hookChainId; + + const chainDefinitions = processedChainId + ? processedChains.find((chain) => chain.id === processedChainId) + : processedChains[0]; + + return chainDefinitions?.blockExplorers?.default; + }, + [chains, globalChains, hookChainId], + ); const buildEntityUrl = useCallback( - ({ type, id }: IBuildEntityUrlParams) => { + ({ type, chainId, id }: IBuildEntityUrlParams) => { + const blockExplorer = getBlockExplorer(chainId ?? hookChainId); const baseUrl = blockExplorer?.url; return baseUrl != null ? `${baseUrl}/${type}/${id}` : undefined; }, - [blockExplorer], + [getBlockExplorer, hookChainId], ); - return { blockExplorer, buildEntityUrl }; + const blockExplorer = getBlockExplorer(hookChainId); + + return { blockExplorer, getBlockExplorer, buildEntityUrl }; };