Skip to content
Merged
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
1 change: 1 addition & 0 deletions packages/typink/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
"react-use": "^17.6.0"
},
"peerDependencies": {
"@dedot/chaintypes": ">=0.132.0",
"dedot": ">=0.15.0",
"react": ">=18.3.1"
},
Expand Down
129 changes: 129 additions & 0 deletions packages/typink/src/hooks/__tests__/useClient.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { useClient, ClientContextProps } from '../../providers/ClientProvider.js';
import { PolkadotApi } from '@dedot/chaintypes';
import * as React from 'react';

// Mock useContext to return our mock values
vi.mock('react', async () => {
const actual = await vi.importActual('react');
return {
...actual,
useContext: vi.fn(),
};
});

// Mock client for testing
const mockClient = {
query: {},
tx: {},
call: {},
rpc: {},
disconnect: vi.fn(),
} as any;

const mockContextValue: ClientContextProps = {
client: mockClient,
ready: true,
supportedNetworks: [],
network: { id: 'test', name: 'Test Network', providers: [], decimals: 10, logo: '', symbol: 'UNIT' },
networkId: 'test' as any,
setNetworkId: vi.fn(),
cacheMetadata: false,
};

describe('useClient', () => {
beforeEach(() => {
vi.clearAllMocks();
// Setup the mock to return our context value
vi.mocked(React.useContext).mockReturnValue(mockContextValue);
});

it('should return client context with default SubstrateApi type', () => {
const result = useClient();

expect(result).toEqual(mockContextValue);
expect(result.client).toBe(mockClient);
expect(result.ready).toBe(true);
});

it('should return client context with PolkadotApi type', () => {
const result = useClient<PolkadotApi>();

expect(result).toEqual(mockContextValue);
expect(result.client).toBe(mockClient);
expect(result.ready).toBe(true);
});

it('should handle undefined client', () => {
const contextWithoutClient = {
...mockContextValue,
client: undefined,
ready: false,
};

vi.mocked(React.useContext).mockReturnValue(contextWithoutClient);

const result = useClient();

expect(result.client).toBeUndefined();
expect(result.ready).toBe(false);
});

// Type-level tests using TypeScript's type checking
it('should have correct return types', () => {
const result = useClient();

// These type assertions will fail at compile time if types are wrong
type ClientType = typeof result.client;
type ReadyType = typeof result.ready;
type NetworkIdType = typeof result.networkId;

// Verify the properties exist and have expected types
expect(typeof result.ready).toBe('boolean');
expect(typeof result.networkId).toBe('string');
expect(typeof result.setNetworkId).toBe('function');
expect(Array.isArray(result.supportedNetworks)).toBe(true);
});

it('should have correct return types with generic PolkadotApi', () => {
const result = useClient<PolkadotApi>();

// Type assertion - this will fail at compile time if generic typing is wrong
const clientContext: ClientContextProps<PolkadotApi> = result;

expect(clientContext).toEqual(mockContextValue);
expect(typeof clientContext.ready).toBe('boolean');
expect(typeof clientContext.networkId).toBe('string');
});

it('should maintain type safety across different ChainApi types', () => {
// Test with default SubstrateApi
const defaultResult = useClient();

// Test with PolkadotApi
const polkadotResult = useClient<PolkadotApi>();

// Both should have the same runtime properties
expect(defaultResult.ready).toBe(polkadotResult.ready);
expect(defaultResult.networkId).toBe(polkadotResult.networkId);
expect(defaultResult.client).toBe(polkadotResult.client);

// Type-level verification that both are valid ClientContextProps
const defaultContext: ClientContextProps = defaultResult;
const polkadotContext: ClientContextProps<PolkadotApi> = polkadotResult;

expect(defaultContext).toBeDefined();
expect(polkadotContext).toBeDefined();
});

it('should properly cast context value with generic type', () => {
// Test that the hook properly casts the context value
const result = useClient<PolkadotApi>();

// Verify that useContext was called
expect(React.useContext).toHaveBeenCalled();

// The result should be the same as our mock context value
expect(result).toBe(mockContextValue);
});
});
229 changes: 229 additions & 0 deletions packages/typink/src/hooks/__tests__/useTypink.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,229 @@
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { useTypink } from '../useTypink.js';
import { TypinkContextProps } from '../../providers/TypinkProvider.js';
import { PolkadotApi } from '@dedot/chaintypes';
import * as React from 'react';

// Mock useContext to return our mock values
vi.mock('react', async () => {
const actual = await vi.importActual('react');
return {
...actual,
useContext: vi.fn(),
};
});

// Mock client for testing
const mockClient = {
query: {},
tx: {},
call: {},
rpc: {},
disconnect: vi.fn(),
} as any;

const mockTypinkContextValue: TypinkContextProps = {
// ClientContextProps
client: mockClient,
ready: true,
supportedNetworks: [],
network: { id: 'test', name: 'Test Network', providers: [], decimals: 10, logo: '', symbol: 'UNIT' },
networkId: 'test' as any,
setNetworkId: vi.fn(),
cacheMetadata: false,

// WalletSetupContextProps
signer: undefined,
connectedAccount: undefined,
wallets: [],

// WalletContextProps
accounts: [],
disconnect: vi.fn(),
appName: '',
connectWallet: vi.fn(),
setConnectedAccount: vi.fn(),

// TypinkEventsContextProps
subscribeToEvent: vi.fn(),

// TypinkContextProps specific
deployments: [
{
address: 'mock-address',
metadata: {} as any,
network: 'test',
id: 'test',
},
],
defaultCaller: 'default-caller-address' as any,
};

describe('useTypink', () => {
beforeEach(() => {
vi.clearAllMocks();
// Setup the mock to return our context value
vi.mocked(React.useContext).mockReturnValue(mockTypinkContextValue);
});

it('should return full Typink context with default SubstrateApi type', () => {
const result = useTypink();

expect(result).toEqual(mockTypinkContextValue);
expect(result.client).toBe(mockClient);
expect(result.ready).toBe(true);
expect(result.deployments).toHaveLength(1);
expect(result.defaultCaller).toBe('default-caller-address');
});

it('should return full Typink context with PolkadotApi type', () => {
const result = useTypink<PolkadotApi>();

expect(result).toEqual(mockTypinkContextValue);
expect(result.client).toBe(mockClient);
expect(result.ready).toBe(true);
expect(result.deployments).toHaveLength(1);
expect(result.defaultCaller).toBe('default-caller-address');
});

it('should handle context without client', () => {
const contextWithoutClient = {
...mockTypinkContextValue,
client: undefined,
ready: false,
};

vi.mocked(React.useContext).mockReturnValue(contextWithoutClient);

const result = useTypink();

expect(result.client).toBeUndefined();
expect(result.ready).toBe(false);
expect(result.deployments).toHaveLength(1);
expect(result.defaultCaller).toBe('default-caller-address');
});

it('should have all required context properties', () => {
const result = useTypink();

// ClientContextProps properties
expect(result).toHaveProperty('client');
expect(result).toHaveProperty('ready');
expect(result).toHaveProperty('supportedNetworks');
expect(result).toHaveProperty('network');
expect(result).toHaveProperty('networkId');
expect(result).toHaveProperty('setNetworkId');
expect(result).toHaveProperty('cacheMetadata');

// WalletSetupContextProps properties
expect(result).toHaveProperty('signer');
expect(result).toHaveProperty('connectedAccount');
expect(result).toHaveProperty('wallets');

// WalletContextProps properties
expect(result).toHaveProperty('accounts');
expect(result).toHaveProperty('disconnect');

// TypinkEventsContextProps properties
expect(result).toHaveProperty('subscribeToEvent');

// TypinkContextProps specific properties
expect(result).toHaveProperty('deployments');
expect(result).toHaveProperty('defaultCaller');
});

it('should have correct property types', () => {
const result = useTypink();

// Type assertions for primitive types
expect(typeof result.ready).toBe('boolean');
expect(typeof result.networkId).toBe('string');
expect(typeof result.defaultCaller).toBe('string');

// Type assertions for function types
expect(typeof result.setNetworkId).toBe('function');
expect(typeof result.disconnect).toBe('function');
expect(typeof result.subscribeToEvent).toBe('function');

// Type assertions for array types
expect(Array.isArray(result.supportedNetworks)).toBe(true);
expect(Array.isArray(result.accounts)).toBe(true);
expect(Array.isArray(result.deployments)).toBe(true);
expect(Array.isArray(result.wallets)).toBe(true);
});

it('should have correct return types with generic PolkadotApi', () => {
const result = useTypink<PolkadotApi>();

// Type assertion - this will fail at compile time if generic typing is wrong
const typinkContext: TypinkContextProps<PolkadotApi> = result;

expect(typinkContext).toEqual(mockTypinkContextValue);
expect(typeof typinkContext.ready).toBe('boolean');
expect(typeof typinkContext.networkId).toBe('string');
expect(typeof typinkContext.defaultCaller).toBe('string');
expect(Array.isArray(typinkContext.deployments)).toBe(true);
});

it('should maintain type safety across different ChainApi types', () => {
// Test with default SubstrateApi
const defaultResult = useTypink();

// Test with PolkadotApi
const polkadotResult = useTypink<PolkadotApi>();

// Both should have the same runtime properties
expect(defaultResult.ready).toBe(polkadotResult.ready);
expect(defaultResult.networkId).toBe(polkadotResult.networkId);
expect(defaultResult.client).toBe(polkadotResult.client);
expect(defaultResult.deployments).toEqual(polkadotResult.deployments);
expect(defaultResult.defaultCaller).toBe(polkadotResult.defaultCaller);

// Type-level verification that both are valid TypinkContextProps
const defaultContext: TypinkContextProps = defaultResult;
const polkadotContext: TypinkContextProps<PolkadotApi> = polkadotResult;

expect(defaultContext).toBeDefined();
expect(polkadotContext).toBeDefined();
});

it('should handle empty deployments array', () => {
const contextWithEmptyDeployments = {
...mockTypinkContextValue,
deployments: [],
};

vi.mocked(React.useContext).mockReturnValue(contextWithEmptyDeployments);

const result = useTypink();

expect(result.deployments).toHaveLength(0);
expect(Array.isArray(result.deployments)).toBe(true);
});

it('should properly type client as CompatibleSubstrateApi when using generics', () => {
const result = useTypink<PolkadotApi>();

// The client should be defined and have the expected structure
expect(result.client).toBeDefined();

if (result.client) {
// These properties should exist on any substrate client
expect(result.client).toHaveProperty('query');
expect(result.client).toHaveProperty('tx');
expect(result.client).toHaveProperty('call');
expect(result.client).toHaveProperty('rpc');
}
});

it('should properly cast context value with generic type', () => {
// Test that the hook properly casts the context value
const result = useTypink<PolkadotApi>();

// Verify that useContext was called
expect(React.useContext).toHaveBeenCalled();

// The result should be the same as our mock context value
expect(result).toBe(mockTypinkContextValue);
});
});
10 changes: 6 additions & 4 deletions packages/typink/src/hooks/useTypink.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { useContext } from 'react';
import { TypinkContext } from '../providers/index.js';
import { TypinkContext, TypinkContextProps } from '../providers/index.js';
import { VersionedGenericSubstrateApi } from 'dedot/types';
import { SubstrateApi } from 'dedot/chaintypes';

export const useTypink = () => {
return useContext(TypinkContext);
};
export function useTypink<ChainApi extends VersionedGenericSubstrateApi = SubstrateApi>(): TypinkContextProps<ChainApi> {
return useContext(TypinkContext) as TypinkContextProps<ChainApi>;
}
Loading