Skip to content

Commit e823598

Browse files
committed
tests
1 parent 01471ff commit e823598

15 files changed

Lines changed: 361 additions & 113 deletions

packages/client/jest.config.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,6 @@ export default {
3737
moduleNameMapper: {
3838
'^src/(.*)$': '<rootDir>/src/$1',
3939
'^:core/(.*)$': '<rootDir>/src/core/$1',
40-
'^:util/(.*)$': '<rootDir>/src/util/$1',
4140
},
4241

4342
// A list of paths to directories that Jest should use to search for files in
Lines changed: 60 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -1,76 +1,79 @@
1-
import { standardErrorCodes, standardErrors } from './core/error';
1+
import { standardErrors } from './core/error';
22
import { EIP1193Provider } from './EIP1193Provider';
33
import { MWPClient } from './MWPClient';
4-
import { RequestArguments } from ':core/provider/interface';
5-
import { AddressString } from ':core/type';
6-
import { Wallets } from ':core/wallet';
4+
import { serializeError } from ':core/error/serialize';
5+
import { Wallet, Wallets } from ':core/wallet';
76

8-
function createProvider() {
9-
return new EIP1193Provider({
10-
metadata: {
11-
appName: 'Test App',
12-
appLogoUrl: undefined,
13-
appChainIds: [1],
14-
appDeeplinkUrl: 'https://example.com',
15-
},
16-
wallet: Wallets.CoinbaseSmartWallet,
17-
});
18-
}
7+
jest.mock('expo-web-browser', () => ({
8+
openBrowserAsync: jest.fn(),
9+
WebBrowserPresentationStyle: {
10+
FORM_SHEET: 'FORM_SHEET',
11+
},
12+
dismissBrowser: jest.fn(),
13+
}));
1914

20-
const mockHandshake = jest.fn();
21-
const mockRequest = jest.fn();
22-
const mockReset = jest.fn();
15+
jest.mock('./MWPClient');
16+
jest.mock(':core/wallet');
2317

24-
let provider: EIP1193Provider;
18+
describe('EIP1193Provider', () => {
19+
let provider: EIP1193Provider;
20+
let mockWallet: jest.Mocked<Wallet>;
21+
let mockClient: jest.Mocked<MWPClient>;
2522

26-
beforeEach(() => {
27-
jest.resetAllMocks();
28-
jest.spyOn(MWPClient, 'createInstance').mockImplementation(async () => {
29-
return {
30-
accounts: [AddressString('0x123')],
31-
chainId: 1,
32-
handshake: mockHandshake,
33-
request: mockRequest,
34-
reset: mockReset,
35-
} as unknown as MWPClient;
36-
});
37-
provider = createProvider();
38-
});
23+
beforeEach(() => {
24+
mockWallet = Wallets.CoinbaseSmartWallet;
25+
mockClient = {
26+
request: jest.fn(),
27+
reset: jest.fn(),
28+
} as unknown as jest.Mocked<MWPClient>;
29+
(MWPClient.createInstance as jest.Mock).mockResolvedValue(mockClient);
3930

40-
describe('Event handling', () => {
41-
it('emits disconnect event on user initiated disconnection', async () => {
42-
const disconnectListener = jest.fn();
43-
provider.on('disconnect', disconnectListener);
31+
provider = new EIP1193Provider({
32+
metadata: { appName: 'Test App', appDeeplinkUrl: 'test://deeplink' },
33+
wallet: mockWallet,
34+
});
35+
console.warn = jest.fn();
36+
});
4437

45-
await provider.disconnect();
38+
afterEach(() => {
39+
jest.clearAllMocks();
40+
});
4641

47-
expect(disconnectListener).toHaveBeenCalledWith(
48-
standardErrors.provider.disconnected('User initiated disconnection')
49-
);
42+
test('constructor initializes correctly', () => {
43+
expect(provider).toBeDefined();
44+
expect(provider.isCoinbaseWallet).toBe(true);
5045
});
51-
});
5246

53-
describe('Request Handling', () => {
54-
it('returns default chain id even without signer set up', async () => {
55-
expect(provider.request({ method: 'eth_chainId' })).resolves.toBe('0x1');
56-
expect(provider.request({ method: 'net_version' })).resolves.toBe(1);
47+
test('request method calls client.request', async () => {
48+
const args = { method: 'eth_getBalance', params: ['0x123'] };
49+
await provider.request(args);
50+
expect(mockClient.request).toHaveBeenCalledWith(args);
5751
});
5852

59-
it('throws error when handling invalid request', async () => {
60-
await expect(provider.request({} as RequestArguments)).rejects.toThrowEIPError(
61-
standardErrorCodes.rpc.invalidParams,
62-
"'args.method' must be a non-empty string."
53+
test('request method handles errors', async () => {
54+
const mockError = standardErrors.provider.unauthorized();
55+
mockClient.request.mockRejectedValue(mockError);
56+
await expect(provider.request({ method: 'eth_getBalance' })).rejects.toEqual(
57+
serializeError(mockError)
6358
);
59+
expect(mockClient.reset).toHaveBeenCalled();
60+
});
61+
62+
test('enable method calls request with eth_requestAccounts', async () => {
63+
const spy = jest.spyOn(provider, 'request');
64+
await provider.enable();
65+
expect(spy).toHaveBeenCalledWith({ method: 'eth_requestAccounts' });
6466
});
6567

66-
it('throws error for requests with unsupported or deprecated method', async () => {
67-
const deprecated = ['eth_sign', 'eth_signTypedData_v2'];
68-
const unsupported = ['eth_subscribe', 'eth_unsubscribe'];
68+
test('disconnect method calls client.reset and emits disconnect event', async () => {
69+
const spy = jest.spyOn(provider, 'emit');
70+
await provider.disconnect();
71+
expect(mockClient.reset).toHaveBeenCalled();
72+
expect(spy).toHaveBeenCalledWith('disconnect', expect.any(Error));
73+
});
6974

70-
for (const method of [...deprecated, ...unsupported]) {
71-
await expect(provider.request({ method })).rejects.toThrowEIPError(
72-
standardErrorCodes.provider.unsupportedMethod
73-
);
74-
}
75+
test('ensureInitialized waits for initialization', async () => {
76+
const privateEnsureInitialized = (provider as any).ensureInitialized.bind(provider);
77+
await expect(privateEnsureInitialized()).resolves.not.toThrow();
7578
});
7679
});

packages/client/src/EIP1193Provider.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ export class EIP1193Provider extends ProviderEventEmitter implements ProviderInt
3232
try {
3333
if (!this.client) throw standardErrors.rpc.internal('MWPClient not initialized');
3434

35-
return this.client.request(args);
35+
return await this.client.request(args);
3636
} catch (error) {
3737
const { code } = error as { code?: number };
3838
if (code === standardErrorCodes.provider.unauthorized) this.disconnect();

packages/client/src/MWPClient.test.ts

Lines changed: 17 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,34 @@
11
import { WebBasedWalletCommunicator } from 'src/components/communicator/webBased/Communicator';
22

33
import { Communicator, CommunicatorInterface } from './components/communicator';
4-
import { KeyManager } from './components/keyManager/KeyManager';
4+
import { KeyManager } from './components/key/KeyManager';
55
import { MWPClient } from './MWPClient';
66
import {
77
decryptContent,
88
encryptContent,
99
exportKeyToHexString,
1010
importKeyFromHexString,
1111
} from ':core/cipher/cipher';
12+
import { CryptoKey } from ':core/cipher/types';
1213
import { standardErrors } from ':core/error';
1314
import { EncryptedData, RPCResponseMessage } from ':core/message';
14-
import { AppMetadata, ProviderEventCallback, RequestArguments } from ':core/provider/interface';
15+
import { AppMetadata, RequestArguments } from ':core/provider/interface';
1516
import { ScopedAsyncStorage } from ':core/storage/ScopedAsyncStorage';
1617
import { Wallets } from ':core/wallet';
1718

18-
jest.mock('./KeyManager');
19+
jest.mock('expo-web-browser', () => ({
20+
openBrowserAsync: jest.fn(),
21+
WebBrowserPresentationStyle: {
22+
FORM_SHEET: 'FORM_SHEET',
23+
},
24+
dismissBrowser: jest.fn(),
25+
}));
26+
27+
jest.mock('./components/key/KeyManager');
1928
const storageStoreSpy = jest.spyOn(ScopedAsyncStorage.prototype, 'storeObject');
2029
const storageClearSpy = jest.spyOn(ScopedAsyncStorage.prototype, 'clear');
2130

22-
jest.mock(':util/cipher', () => ({
31+
jest.mock(':core/cipher/cipher', () => ({
2332
decryptContent: jest.fn(),
2433
encryptContent: jest.fn(),
2534
exportKeyToHexString: jest.fn(),
@@ -49,15 +58,13 @@ describe('MWPClient', () => {
4958
let signer: MWPClient;
5059
let mockMetadata: AppMetadata;
5160
let mockCommunicator: CommunicatorInterface;
52-
let mockCallback: ProviderEventCallback;
5361
let mockKeyManager: jest.Mocked<KeyManager>;
5462

5563
beforeEach(async () => {
5664
mockMetadata = {
5765
appName: 'test',
58-
appLogoUrl: null,
5966
appChainIds: [1],
60-
appDeeplinkUrl: null,
67+
appDeeplinkUrl: 'https://example.com',
6168
};
6269

6370
WebBasedWalletCommunicator.communicators.clear();
@@ -66,7 +73,6 @@ describe('MWPClient', () => {
6673
.spyOn(mockCommunicator, 'postRequestAndWaitForResponse')
6774
.mockResolvedValue(mockSuccessResponse);
6875

69-
mockCallback = jest.fn();
7076
mockKeyManager = new KeyManager({
7177
wallet: mockWallet,
7278
}) as jest.Mocked<KeyManager>;
@@ -110,8 +116,6 @@ describe('MWPClient', () => {
110116
expect(storageStoreSpy).toHaveBeenCalledWith('accounts', ['0xAddress']);
111117

112118
expect(signer.request({ method: 'eth_requestAccounts' })).resolves.toEqual(['0xAddress']);
113-
expect(mockCallback).toHaveBeenCalledWith('accountsChanged', ['0xAddress']);
114-
expect(mockCallback).toHaveBeenCalledWith('connect', { chainId: '0x1' });
115119
});
116120

117121
it('should throw an error if failure in response.content', async () => {
@@ -208,13 +212,12 @@ describe('MWPClient', () => {
208212
{ id: 2, rpcUrl: 'https://eth-rpc.example.com/2' },
209213
]);
210214
expect(storageStoreSpy).toHaveBeenCalledWith('walletCapabilities', mockCapabilities);
211-
expect(mockCallback).toHaveBeenCalledWith('chainChanged', '0x1');
212215
});
213216
});
214217

215-
describe('disconnect', () => {
216-
it('should disconnect successfully', async () => {
217-
await signer.cleanup();
218+
describe('reset', () => {
219+
it('should reset successfully', async () => {
220+
await signer.reset();
218221

219222
expect(storageClearSpy).toHaveBeenCalled();
220223
expect(mockKeyManager.clear).toHaveBeenCalled();

packages/client/src/MWPClient.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { KeyManager } from './components/keyManager/KeyManager';
1+
import { KeyManager } from './components/key/KeyManager';
22
import {
33
decryptContent,
44
encryptContent,
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import { Communicator } from './Communicator';
2+
import { WebBasedWalletCommunicator } from './webBased/Communicator';
3+
import { Wallet } from ':core/wallet';
4+
5+
jest.mock('expo-web-browser', () => ({
6+
openBrowserAsync: jest.fn(),
7+
WebBrowserPresentationStyle: {
8+
FORM_SHEET: 'FORM_SHEET',
9+
},
10+
dismissBrowser: jest.fn(),
11+
}));
12+
13+
jest.mock('./webBased/Communicator');
14+
15+
describe('Communicator', () => {
16+
beforeEach(() => {
17+
jest.clearAllMocks();
18+
});
19+
20+
test('getInstance returns WebBasedWalletCommunicator for web-based wallet', () => {
21+
const mockWebBasedWallet: Wallet = { type: 'webBased', scheme: 'test-scheme://' } as Wallet;
22+
const mockCommunicator = {
23+
postRequestAndWaitForResponse: jest.fn(),
24+
handleResponse: jest.fn(),
25+
};
26+
(WebBasedWalletCommunicator.getInstance as jest.Mock).mockReturnValue(mockCommunicator);
27+
28+
const result = Communicator.getInstance(mockWebBasedWallet);
29+
30+
expect(WebBasedWalletCommunicator.getInstance).toHaveBeenCalledWith('test-scheme://');
31+
expect(result).toBe(mockCommunicator);
32+
});
33+
34+
test('getInstance throws error for native wallet', () => {
35+
const mockNativeWallet: Wallet = { type: 'native', scheme: 'native-scheme://' } as Wallet;
36+
37+
expect(() => Communicator.getInstance(mockNativeWallet)).toThrow(
38+
'Native wallet not supported yet'
39+
);
40+
});
41+
42+
test('getInstance throws error for unsupported wallet type', () => {
43+
const mockUnsupportedWallet: Wallet = {
44+
type: 'unsupported' as any,
45+
scheme: 'unsupported-scheme://',
46+
} as Wallet;
47+
48+
expect(() => Communicator.getInstance(mockUnsupportedWallet)).toThrow(
49+
'Unsupported wallet type'
50+
);
51+
});
52+
});
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import { WebBasedWalletCommunicator } from './webBased/Communicator';
2+
import { RPCRequestMessage, RPCResponseMessage } from ':core/message';
3+
import { Wallet } from ':core/wallet';
4+
5+
export interface CommunicatorInterface {
6+
postRequestAndWaitForResponse: (message: RPCRequestMessage) => Promise<RPCResponseMessage>;
7+
handleResponse: (responseUrl: string) => boolean;
8+
}
9+
10+
export class Communicator {
11+
static getInstance(wallet: Wallet): CommunicatorInterface {
12+
const { type, scheme } = wallet;
13+
14+
if (type === 'webBased') {
15+
return WebBasedWalletCommunicator.getInstance(scheme);
16+
}
17+
18+
if (type === 'native') {
19+
throw new Error('Native wallet not supported yet');
20+
}
21+
22+
throw new Error('Unsupported wallet type');
23+
}
24+
}
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
import { handleResponse } from './handleResponse';
2+
import { WebBasedWalletCommunicator } from './webBased/Communicator';
3+
import { MWP_RESPONSE_PATH } from ':core/constants';
4+
import { Wallet } from ':core/wallet';
5+
6+
jest.mock('./webBased/Communicator');
7+
jest.mock('expo-web-browser', () => ({
8+
openBrowserAsync: jest.fn(),
9+
WebBrowserPresentationStyle: {
10+
FORM_SHEET: 'FORM_SHEET',
11+
},
12+
dismissBrowser: jest.fn(),
13+
}));
14+
15+
describe('handleResponse', () => {
16+
const mockWebBasedWallet = { type: 'webBased', scheme: 'https://example.com' } as Wallet;
17+
const mockNativeWallet = { type: 'native' } as Wallet;
18+
const mockOtherWallet = { type: 'other' as any } as Wallet;
19+
20+
beforeEach(() => {
21+
jest.clearAllMocks();
22+
});
23+
24+
test('returns false for non-MWP response URLs', () => {
25+
const url = 'https://example.com/some-path';
26+
expect(handleResponse(url, mockWebBasedWallet)).toBe(false);
27+
});
28+
29+
test('handles web-based wallet response correctly', () => {
30+
const url = `https://example.com/${MWP_RESPONSE_PATH}/some-params`;
31+
const mockHandleResponse = jest.fn().mockReturnValue(true);
32+
(WebBasedWalletCommunicator.getInstance as jest.Mock).mockReturnValue({
33+
handleResponse: mockHandleResponse,
34+
});
35+
36+
const result = handleResponse(url, mockWebBasedWallet);
37+
38+
expect(WebBasedWalletCommunicator.getInstance).toHaveBeenCalledWith(mockWebBasedWallet.scheme);
39+
expect(mockHandleResponse).toHaveBeenCalledWith(url);
40+
expect(result).toBe(true);
41+
});
42+
43+
test('throws error for native wallet', () => {
44+
const url = `https://example.com/${MWP_RESPONSE_PATH}/some-params`;
45+
expect(() => handleResponse(url, mockNativeWallet)).toThrow('Native wallet not supported yet');
46+
});
47+
48+
test('returns false for unsupported wallet types', () => {
49+
const url = `https://example.com/${MWP_RESPONSE_PATH}/some-params`;
50+
expect(handleResponse(url, mockOtherWallet)).toBe(false);
51+
});
52+
53+
test('WebBasedWalletCommunicator.handleResponse returns false', () => {
54+
const url = `https://example.com/${MWP_RESPONSE_PATH}/some-params`;
55+
const mockHandleResponse = jest.fn().mockReturnValue(false);
56+
(WebBasedWalletCommunicator.getInstance as jest.Mock).mockReturnValue({
57+
handleResponse: mockHandleResponse,
58+
});
59+
60+
const result = handleResponse(url, mockWebBasedWallet);
61+
62+
expect(mockHandleResponse).toHaveBeenCalledWith(url);
63+
expect(result).toBe(false);
64+
});
65+
});

0 commit comments

Comments
 (0)