Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
96 changes: 96 additions & 0 deletions packages/react/__tests__/hooks/useSigningClient.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import { WalletState } from '@interchain-kit/core';
import { renderHook, waitFor } from '@testing-library/react';
import { useState } from 'react';

import { activeRequests, cache } from '../../src/hooks/useAsync';
import { useSigningClient } from '../../src/hooks/useSigningClient';
import { useWalletManager } from '../../src/hooks/useWalletManager';

jest.mock('../../src/hooks/useWalletManager', () => ({
useWalletManager: jest.fn(),
}));

describe('useSigningClient chain type changes', () => {
const signingClient = { sign: jest.fn() };
const manager = {
getChainByName: jest.fn((chainName: string) => ({
chainType: chainName === 'cosmoshub' ? 'cosmos' : chainName,
})),
getChainWalletState: jest.fn(),
getRpcEndpoint: jest.fn(),
getSigningClient: jest.fn(),
isReady: true,
};

beforeEach(() => {
jest.clearAllMocks();
cache.clear();
activeRequests.clear();
manager.getChainWalletState.mockReturnValue({
walletState: WalletState.Connected,
});
manager.getRpcEndpoint.mockResolvedValue('https://rpc.example.com');
manager.getSigningClient.mockResolvedValue(signingClient);
(useWalletManager as jest.Mock).mockReturnValue(manager);
});

const useClient = (chainName: string) => {
const client = useSigningClient(chainName, 'test-wallet');
// A consumer can call other hooks after useSigningClient.
const [marker] = useState('unchanged');
return { ...client, marker };
};

it.each(['ethereum', 'solana'])(
'switches from %s to Cosmos and back',
async (chainName) => {
const { result, rerender } = renderHook(({ chain }) => useClient(chain), {
initialProps: { chain: chainName },
});
expect(result.current).toEqual({
signingClient: null,
isLoading: false,
error: null,
marker: 'unchanged',
});
expect(manager.getRpcEndpoint).not.toHaveBeenCalled();
expect(manager.getSigningClient).not.toHaveBeenCalled();

rerender({ chain: 'cosmoshub' });
await waitFor(() =>
expect(result.current.signingClient).toBe(signingClient)
);
expect(manager.getSigningClient).toHaveBeenCalledWith(
'test-wallet',
'cosmoshub'
);

rerender({ chain: chainName });
expect(result.current).toEqual({
signingClient: null,
isLoading: false,
error: null,
marker: 'unchanged',
});
expect(manager.getRpcEndpoint).toHaveBeenCalledTimes(1);
expect(manager.getSigningClient).toHaveBeenCalledTimes(1);
}
);

it('reports Cosmos query errors and clears them on a non-Cosmos chain', async () => {
const error = new Error('RPC unavailable');
manager.getRpcEndpoint.mockRejectedValue(error);
const { result, rerender } = renderHook(({ chain }) => useClient(chain), {
initialProps: { chain: 'cosmoshub' },
});
await waitFor(() => expect(result.current.error).toBe(error));
expect(manager.getSigningClient).not.toHaveBeenCalled();
rerender({ chain: 'ethereum' });
expect(result.current).toEqual({
signingClient: null,
isLoading: false,
error: null,
marker: 'unchanged',
});
});
});
40 changes: 28 additions & 12 deletions packages/react/src/hooks/useSigningClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,33 +4,49 @@ import { useAsync } from './useAsync';
import { useWalletManager } from './useWalletManager';

export const useSigningClient = (chainName: string, walletName: string) => {
const { getSigningClient, getChainWalletState, getRpcEndpoint, isReady, getChainByName } = useWalletManager();
const {
getSigningClient,
getChainWalletState,
getRpcEndpoint,
isReady,
getChainByName,
} = useWalletManager();

const chain = getChainByName(chainName);

if (chain.chainType !== 'cosmos') {
return {
signingClient: null,
isLoading: false,
error: null,
};
}

const chainWalletState = getChainWalletState(walletName, chainName);
const isCosmosChain = chain.chainType === 'cosmos';
const chainWalletState = isCosmosChain
? getChainWalletState(walletName, chainName)
: undefined;

const { data, isLoading, error } = useAsync({
queryKey: `signing-client-${chainName}-${walletName}`,
queryFn: async () => {
await getRpcEndpoint(walletName, chainName);
const currentWalletState = getChainWalletState(walletName, chainName)?.walletState;
const currentWalletState = getChainWalletState(
walletName,
chainName
)?.walletState;
if (currentWalletState === WalletState.Connected) {
return getSigningClient(walletName, chainName);
}
},
enabled: chainWalletState?.walletState === WalletState.Connected && isReady,
enabled:
isCosmosChain &&
chainWalletState?.walletState === WalletState.Connected &&
isReady,
disableCache: true,
});

// Keep hook order stable when the selected chain changes type.
if (!isCosmosChain) {
return {
signingClient: null,
isLoading: false,
error: null,
};
}

return {
signingClient: data,
error,
Expand Down