Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Set the provider's network. #18

Merged
merged 10 commits into from
Jan 10, 2025
72 changes: 72 additions & 0 deletions packages/sdk/src/core/__tests__/providers/add-network.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import { GnoWalletProvider } from '../../../providers';
import { WalletResponseFailureType, WalletResponseSuccessType } from '../../types';
import { AddNetworkOptions } from '../../types/methods';
import { makeResponseMessage } from '../../utils';

describe('GnoWalletProvider.addNetwork', () => {
let provider: GnoWalletProvider;

beforeEach(() => {
provider = new GnoWalletProvider();
});

// Normal network addition case
it('should return ADD_NETWORK_SUCCESS response when adding network', async () => {
const options: AddNetworkOptions = {
chainId: 'test-chain',
chainName: 'Test Chain',
rpcUrl: 'https://test.network.com',
};

const addResponse = await provider.addNetwork(options);
expect(addResponse).toEqual(makeResponseMessage(WalletResponseSuccessType.ADD_NETWORK_SUCCESS));
});

// Validate if a required field is missing
it('should return INVALID_FORMAT when required fields are missing', async () => {
const testCases = [
{ chainName: 'Test Chain', rpcUrl: 'https://test.com' },
{ chainId: 'test-chain', rpcUrl: 'https://test.com' },
{ chainId: 'test-chain', chainName: 'Test Chain' },
];

for (const testCase of testCases) {
const response = await provider.addNetwork(testCase as unknown as AddNetworkOptions);
expect(response).toEqual(makeResponseMessage(WalletResponseFailureType.INVALID_FORMAT));
}
});

// Validating whitespace characters in RPC URL
it('should return INVALID_FORMAT when rpcUrl contains whitespace', async () => {
const options = {
chainId: 'test-chain',
chainName: 'Test Chain',
rpcUrl: 'https://test network.com', // Blank space in the rpcUrl
};
const response = await provider.addNetwork(options);

expect(response).toEqual(makeResponseMessage(WalletResponseFailureType.INVALID_FORMAT));
});

// Attempting to add a duplicate network case
it('should return duplicate error when adding network with same network', async () => {
const testNetworkOptions: AddNetworkOptions[] = [
{
chainId: 'test-chain',
chainName: 'Test Chain',
rpcUrl: 'https://test.network.com/', // With trailing slash
},
{
chainId: 'test-chain2',
chainName: 'Test Chain 2',
rpcUrl: 'https://test.network.com', // Without trailing slash
},
];

const response = await provider.addNetwork(testNetworkOptions[0]);
expect(response).toEqual(makeResponseMessage(WalletResponseSuccessType.ADD_NETWORK_SUCCESS));

const duplicateResponse = await provider.addNetwork(testNetworkOptions[1]);
expect(duplicateResponse).toEqual(makeResponseMessage(WalletResponseFailureType.NETWORK_ALREADY_EXISTS));
});
});
52 changes: 52 additions & 0 deletions packages/sdk/src/core/__tests__/providers/connect-provider.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { GnoWalletProvider } from '../../../providers';
import { NetworkInfo } from '../../types';
import { JSONRPCProvider, Wallet as TM2Wallet } from '@gnolang/tm2-js-client';
import { GNO_ADDRESS_PREFIX } from '../../constants/chains.constant';

// Mock JSONRPCProvider to test without a real network connection
jest.mock('@gnolang/tm2-js-client', () => ({
...jest.requireActual('@gnolang/tm2-js-client'),
JSONRPCProvider: jest.fn(),
}));

describe('GnoWalletProvider.connectProvider', () => {
let provider: GnoWalletProvider;
let mockWallet: TM2Wallet;

const TEST_RPC_URL = 'https://testnet.gno.land';

const mockNetwork: NetworkInfo = {
chainId: 'test-chain-1',
networkName: 'Test Network 1',
rpcUrl: TEST_RPC_URL,
addressPrefix: GNO_ADDRESS_PREFIX,
indexerUrl: null,
};

beforeEach(() => {
jest.clearAllMocks();

mockWallet = {
connect: jest.fn(),
} as unknown as TM2Wallet;

provider = new GnoWalletProvider(mockWallet, [mockNetwork]);
provider['currentChainId'] = mockNetwork.chainId;
});

// Test the normal Provider connection case
it('should connect provider with correct rpcUrl', async () => {
const result = provider['connectProvider']();

expect(JSONRPCProvider).toHaveBeenCalledWith(TEST_RPC_URL);
expect(mockWallet.connect).toHaveBeenCalled();
expect(result).toBe(true);
});

// Test the exception case where the wallet is not set up
it('should return false when wallet is not set', () => {
provider['wallet'] = null;
const result = provider['connectProvider']();
expect(result).toBe(false);
});
});
60 changes: 60 additions & 0 deletions packages/sdk/src/core/__tests__/providers/current-network.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { GnoWalletProvider } from '../../../providers';
import { GNO_ADDRESS_PREFIX } from '../../constants/chains.constant';
import { NetworkInfo } from '../../types';

describe('GnoWalletProvider.currentNetwork', () => {
let provider: GnoWalletProvider;
const mockNetworks: NetworkInfo[] = [
{
chainId: 'test-chain-1',
networkName: 'Test Network 1',
rpcUrl: 'http://test1.com',
addressPrefix: GNO_ADDRESS_PREFIX,
indexerUrl: null,
},
{
chainId: 'test-chain-2',
networkName: 'Test Network 2',
rpcUrl: 'http://test2.com',
addressPrefix: GNO_ADDRESS_PREFIX,
indexerUrl: null,
},
];

beforeEach(() => {
provider = new GnoWalletProvider(undefined, []);
});

afterEach(() => {
jest.clearAllMocks();
});

it('should return null when networks array is empty', () => {
const currentNetwork = (provider as unknown as { currentNetwork: NetworkInfo | null }).currentNetwork;
expect(currentNetwork).toBeNull();
});

it('should return first network when currentChainId is null', () => {
(provider as unknown as { networks: NetworkInfo[] }).networks = mockNetworks;
(provider as unknown as { currentChainId: string | null }).currentChainId = null;

const currentNetwork = (provider as unknown as { currentNetwork: NetworkInfo | null }).currentNetwork;
expect(currentNetwork).toEqual(mockNetworks[0]);
});

it('should return matching network when currentChainId is set', () => {
(provider as unknown as { networks: NetworkInfo[] }).networks = mockNetworks;
(provider as unknown as { currentChainId: string | null }).currentChainId = 'test-chain-2';

const currentNetwork = (provider as unknown as { currentNetwork: NetworkInfo | null }).currentNetwork;
expect(currentNetwork).toEqual(mockNetworks[1]);
});

it('should return null when currentChainId does not match any network', () => {
(provider as unknown as { networks: NetworkInfo[] }).networks = mockNetworks;
(provider as unknown as { currentChainId: string | null }).currentChainId = 'non-existent-chain';

const currentNetwork = (provider as unknown as { currentNetwork: NetworkInfo | null }).currentNetwork;
expect(currentNetwork).toBeNull();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { GnoWalletProvider } from '../../../providers';
import { GNO_ADDRESS_PREFIX } from '../../constants/chains.constant';
import { NetworkInfo } from '../../types';
import { GnoWallet } from '@gnolang/gno-js-client';

describe('GnoWalletProvider.disconnectProvider', () => {
let provider: GnoWalletProvider;

const mockNetwork: NetworkInfo = {
chainId: 'test-chain-1',
networkName: 'Test Network 1',
rpcUrl: 'http://test1.com',
addressPrefix: GNO_ADDRESS_PREFIX,
indexerUrl: null,
};

beforeEach(() => {
provider = new GnoWalletProvider(undefined, [mockNetwork]);
provider['networkCallback'] = jest.fn();
provider['wallet'] = new GnoWallet();
});

// Verify that all properties of the provider are initialized correctly
it('should reset all provider properties', () => {
provider['disconnectProvider']();

expect(provider['networkCallback']).toBeNull();
expect(provider['networks']).toEqual([]);
expect(provider['currentNetwork']).toBeNull();
expect(provider['wallet']).toBeNull();
});

it('should return true after disconnection', () => {
const result = provider['disconnectProvider']();
expect(result).toBe(true);
});
});
59 changes: 59 additions & 0 deletions packages/sdk/src/core/__tests__/providers/get-network.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { GnoWalletProvider } from '../../../providers';
import { GNO_ADDRESS_PREFIX } from '../../constants/chains.constant';
import { NetworkInfo, WalletResponseFailureType, WalletResponseSuccessType } from '../../types';
import { makeResponseMessage } from '../../utils';

describe('GnoWalletProvider.getNetwork', () => {
let provider: GnoWalletProvider;
const mockNetworks: NetworkInfo[] = [
{
chainId: 'test-chain-1',
networkName: 'Test Network 1',
rpcUrl: 'http://test1.com',
addressPrefix: GNO_ADDRESS_PREFIX,
indexerUrl: null,
},
{
chainId: 'test-chain-2',
networkName: 'Test Network 2',
rpcUrl: 'http://test2.com',
addressPrefix: GNO_ADDRESS_PREFIX,
indexerUrl: null,
},
];

beforeEach(() => {
provider = new GnoWalletProvider(undefined, mockNetworks);
});

afterEach(() => {
jest.clearAllMocks();
});

// Test for no wallet connection
it('should return NOT_CONNECTED when wallet is not connected', async () => {
const response = await provider.getNetwork();

expect(response).toEqual(makeResponseMessage(WalletResponseFailureType.NOT_CONNECTED));
});

// Test with wallet connected but network not initialized
it('should return NOT_INITIALIZED_NETWORK when network is not initialized', async () => {
(provider as unknown as { wallet: object }).wallet = {};
(provider as unknown as { networks: NetworkInfo[] }).networks = [];

const response = await provider.getNetwork();

expect(response).toEqual(makeResponseMessage(WalletResponseFailureType.NOT_INITIALIZED_NETWORK));
});

// Steady state test with both wallet connection and network initialization complete
it('should return current network information when wallet is connected and network is initialized', async () => {
(provider as unknown as { wallet: object }).wallet = {};
(provider as unknown as { currentChainId: string | null }).currentChainId = null;

const response = await provider.getNetwork();

expect(response).toEqual(makeResponseMessage(WalletResponseSuccessType.GET_NETWORK_SUCCESS, mockNetworks[0]));
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import { GnoWalletProvider } from '../../../providers';
import { GNO_ADDRESS_PREFIX } from '../../constants/chains.constant';
import { NetworkInfo } from '../../types';
import { OnChangeNetworkOptions } from '../../types/methods';

describe('GnoWalletProvider.onChange', () => {
let provider: GnoWalletProvider;
const mockNetworks: NetworkInfo[] = [
{
chainId: 'test-chain-1',
networkName: 'Test Network 1',
rpcUrl: 'http://test1.com',
addressPrefix: GNO_ADDRESS_PREFIX,
indexerUrl: null,
},
{
chainId: 'test-chain-2',
networkName: 'Test Network 2',
rpcUrl: 'http://test2.com',
addressPrefix: GNO_ADDRESS_PREFIX,
indexerUrl: null,
},
];

beforeEach(() => {
provider = new GnoWalletProvider(undefined, mockNetworks);
jest.spyOn(provider as unknown as { connectProvider(): boolean }, 'connectProvider').mockReturnValue(true);
jest.spyOn(provider as unknown as { setNetwork(network: NetworkInfo): void }, 'setNetwork');
});

afterEach(() => {
jest.clearAllMocks();
});

// Verify that the callbacks registered with onChangeNetwork are stored correctly inside the provider
it('should register network callback successfully', () => {
const mockCallback = jest.fn();
const options: OnChangeNetworkOptions = {
callback: mockCallback,
};

provider.onChangeNetwork(options);

const providerWithCallback = provider as unknown as { networkCallback: ((chainId: string) => void) | null };
expect(providerWithCallback.networkCallback).toBe(mockCallback);
});

// Ensure that only registered callbacks are executed and no other callbacks are executed
it('should fail when checking registered callback', () => {
const mockCallback = jest.fn();
const differentCallback = jest.fn();

provider.onChangeNetwork({ callback: mockCallback });

const providerWithCallback = provider as unknown as { networkCallback: ((chainId: string) => void) | null };
providerWithCallback.networkCallback?.('test-chain');

expect(mockCallback).toHaveBeenCalledTimes(1);
expect(differentCallback).not.toHaveBeenCalled();
});

// Ensure callbacks are fired with the correct chainId on network changes
it('should execute registered callback when network changes', () => {
const mockCallback = jest.fn();
const testChainId = 'test-chain-1';

provider.onChangeNetwork({ callback: mockCallback });

const providerWithCallback = provider as unknown as { networkCallback: ((chainId: string) => void) | null };
const registeredCallback = providerWithCallback.networkCallback;

registeredCallback?.(testChainId);

expect(mockCallback).toHaveBeenCalledWith(testChainId);
expect(mockCallback).toHaveBeenCalledTimes(1);
});

// To ensure that the callback system works in real-world network change situations.
it('should notify when network actually changes', () => {
const mockCallback = jest.fn();
provider.onChangeNetwork({ callback: mockCallback });

const newNetwork = mockNetworks[1];
(provider as unknown as { setNetwork(network: NetworkInfo): void }).setNetwork(newNetwork);

expect(mockCallback).toHaveBeenCalledWith(newNetwork.chainId);
expect(mockCallback).toHaveBeenCalledTimes(1);
});
});
Loading
Loading