Skip to content

Commit

Permalink
feat(APP-3460): Export VoteIndicator type, update buildEntityUrl func…
Browse files Browse the repository at this point in the history
…tion to support a chainId parameter (#254)
  • Loading branch information
cgero-eth authored Jul 30, 2024
1 parent edcf6d4 commit 0d34a04
Show file tree
Hide file tree
Showing 4 changed files with 82 additions and 29 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions src/modules/components/vote/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export * from './voteDataListItem';
export * from './voteProposalDataListItem';
export { VoteIndicator } from './voteUtils';
66 changes: 47 additions & 19 deletions src/modules/hooks/useBlockExplorer/useBlockExplorer.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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', () => {
Expand Down
38 changes: 28 additions & 10 deletions src/modules/hooks/useBlockExplorer/useBlockExplorer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand All @@ -24,29 +25,46 @@ 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)
*/
id?: string;
}

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 };
};

0 comments on commit 0d34a04

Please sign in to comment.