Skip to content

Commit 19fc54d

Browse files
grabbouzzmp
andauthored
feat: export and share MockEip1193 provider and use it throughout the test suite (#793)
* feat: export mock and update tests * chore: add todo * chore: bring back comment * chore * chore * chore * chore * chore: update packages/core/src/mocks.ts Co-authored-by: Zach Pomerantz <[email protected]> --------- Co-authored-by: Zach Pomerantz <[email protected]>
1 parent 615494c commit 19fc54d

File tree

9 files changed

+72
-68
lines changed

9 files changed

+72
-68
lines changed

packages/coinbase-wallet/src/index.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { createWeb3ReactStoreAndActions } from '@web3-react/store'
22
import type { Actions, Web3ReactStore } from '@web3-react/types'
33
import { CoinbaseWallet } from '.'
4-
import { MockEIP1193Provider } from '../../eip1193/src/mock'
4+
import { MockEIP1193Provider } from '@web3-react/core'
55

66
jest.mock(
77
'@coinbase/wallet-sdk',

packages/core/src/index.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
export * from './hooks'
2+
export * from './mocks'
23
export * from './provider'

packages/eip1193/src/mock.ts renamed to packages/core/src/mocks.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
import type { ProviderRpcError, RequestArguments } from '@web3-react/types'
22
import { EventEmitter } from 'eventemitter3'
33

4-
export class MockEIP1193Provider extends EventEmitter {
5-
public chainId?: string
4+
export class MockEIP1193Provider<T = string> extends EventEmitter {
5+
public chainId?: T
66
public accounts?: string[]
77

8-
public eth_chainId = jest.fn((chainId?: string) => chainId)
8+
public eth_chainId = jest.fn((chainId?: T) => chainId)
99
public eth_accounts = jest.fn((accounts?: string[]) => accounts)
1010
public eth_requestAccounts = jest.fn((accounts?: string[]) => accounts)
1111

@@ -21,7 +21,7 @@ export class MockEIP1193Provider extends EventEmitter {
2121
case 'eth_requestAccounts':
2222
return Promise.resolve(this.eth_requestAccounts(this.accounts))
2323
default:
24-
throw new Error()
24+
throw new Error(`Method not supported on mock: ${JSON.stringify(x)}`)
2525
}
2626
}
2727

packages/core/tsconfig.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
"extends": "../../tsconfig.json",
33
"include": ["./src"],
44
"compilerOptions": {
5-
"jsx": "react",
65
"outDir": "./dist"
76
}
87
}

packages/eip1193/src/index.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
import { Eip1193Bridge } from '@ethersproject/experimental'
22
import { Web3Provider } from '@ethersproject/providers'
33
import { createWeb3ReactStoreAndActions } from '@web3-react/store'
4+
import { MockEIP1193Provider } from '@web3-react/core'
45
import type { Actions, Web3ReactStore } from '@web3-react/types'
56
import { EIP1193 } from '.'
6-
import { MockEIP1193Provider } from './mock'
77

88
class MockProviderRpcError extends Error {
99
public code: number

packages/metamask/src/index.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { createWeb3ReactStoreAndActions } from '@web3-react/store'
22
import type { Actions, Web3ReactStore } from '@web3-react/types'
33
import { MetaMask } from '.'
4-
import { MockEIP1193Provider } from '../../eip1193/src/mock'
4+
import { MockEIP1193Provider } from '@web3-react/core'
55

66
const chainId = '0x1'
77
const accounts: string[] = ['0x0000000000000000000000000000000000000000']

packages/walletconnect-v2/src/index.spec.ts

Lines changed: 53 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,9 @@
22
global.TextEncoder = jest.fn()
33
global.TextDecoder = jest.fn()
44

5-
// We are not using Web3Modal and it is not available in the `node` environment either
6-
jest.mock('@web3modal/standalone', () => ({ Web3Modal: jest.fn().mockImplementation() }))
7-
85
import { createWeb3ReactStoreAndActions } from '@web3-react/store'
6+
import { MockEIP1193Provider } from '@web3-react/core'
7+
import { RequestArguments } from '@web3-react/types'
98
import { EthereumProvider } from '@walletconnect/ethereum-provider'
109

1110
import { WalletConnect, WalletConnectOptions } from '.'
@@ -19,38 +18,56 @@ const createTestEnvironment = (opts: Omit<WalletConnectOptions, 'projectId'>) =>
1918
const accounts = ['0x0000000000000000000000000000000000000000']
2019
const chains = [1, 2, 3]
2120

21+
/*
22+
* TODO: Move this to `@web3-react/types` and further narrow down types for `RequestArguments`
23+
*/
24+
type SwitchEthereumChainRequestArguments = {
25+
method: 'wallet_switchEthereumChain',
26+
params: [{ chainId: string }]
27+
}
28+
29+
const isSwitchEthereumChainRequest = (x: RequestArguments): x is SwitchEthereumChainRequestArguments => {
30+
return x.method === 'wallet_switchEthereumChain'
31+
}
32+
33+
class MockWalletConnectProvider extends MockEIP1193Provider<number> {
34+
/** per {@link https://eips.ethereum.org/EIPS/eip-3326#specification EIP-3326} */
35+
public eth_switchEthereumChain = jest.fn((args: string) => null)
36+
37+
public request(x: RequestArguments | SwitchEthereumChainRequestArguments): Promise<unknown> {
38+
if (isSwitchEthereumChainRequest(x)) {
39+
this.chainId = parseInt(x.params[0].chainId, 16)
40+
return Promise.resolve(this.eth_switchEthereumChain(JSON.stringify(x)))
41+
} else {
42+
return super.request(x)
43+
}
44+
}
45+
46+
public enable() {
47+
return super.request({ method: 'eth_requestAccounts' })
48+
}
49+
50+
// session is an object when connected, undefined otherwise
51+
get session() {
52+
return this.eth_requestAccounts.mock.calls.length > 0 ? {} : undefined
53+
}
54+
}
55+
2256
describe('WalletConnect', () => {
23-
const wc2RequestMock = jest.fn()
2457
let wc2InitMock: jest.Mock
2558

2659
beforeEach(() => {
27-
const wc2EnableMock = jest.fn().mockResolvedValue(accounts)
60+
/*
61+
* TypeScript error is expected here. We're mocking a factory `init` method
62+
* to only define a subset of `EthereumProvider` that we use internally
63+
*/
2864
// @ts-ignore
29-
// TypeScript error is expected here. We're mocking a factory `init` method
30-
// to only define a subset of `EthereumProvider` that we use internally
31-
wc2InitMock = jest.spyOn(EthereumProvider, 'init').mockImplementation(async (opts) => ({
32-
// we read this in `enable` to get current chain
33-
accounts,
34-
chainId: opts.chains[0],
35-
// session is an object when connected, undefined otherwise
36-
get session() {
37-
return wc2EnableMock.mock.calls.length > 0 ? {} : undefined
38-
},
39-
// methods used in `activate` and `isomorphicInitialize`
40-
enable: wc2EnableMock,
41-
// mock EIP-1193
42-
request: wc2RequestMock,
43-
on() {
44-
return this
45-
},
46-
removeListener() {
47-
return this
48-
},
49-
}))
50-
})
51-
52-
afterEach(() => {
53-
wc2RequestMock.mockReset()
65+
wc2InitMock = jest.spyOn(EthereumProvider, 'init').mockImplementation(async (opts) => {
66+
const provider = new MockWalletConnectProvider()
67+
provider.chainId = opts.chains[0]
68+
provider.accounts = accounts
69+
return provider
70+
})
5471
})
5572

5673
describe('#connectEagerly', () => {
@@ -62,7 +79,7 @@ describe('WalletConnect', () => {
6279

6380
describe(`#isomorphicInitialize`, () => {
6481
test('should initialize exactly one provider and return a Promise if pending initialization', async () => {
65-
const {connector, store} = createTestEnvironment({ chains })
82+
const {connector} = createTestEnvironment({ chains })
6683
connector.activate()
6784
connector.activate()
6885
expect(wc2InitMock).toHaveBeenCalledTimes(1)
@@ -82,9 +99,9 @@ describe('WalletConnect', () => {
8299
})
83100

84101
test('should activate passed chain', async () => {
85-
const {connector, store} = createTestEnvironment({ chains })
102+
const {connector} = createTestEnvironment({ chains })
86103
await connector.activate(2)
87-
expect(store.getState().chainId).toEqual(2)
104+
expect(connector.provider?.chainId).toEqual(2)
88105
})
89106

90107
test('should throw an error for invalid chain', async () => {
@@ -95,15 +112,16 @@ describe('WalletConnect', () => {
95112
test('should switch chain if already connected', async () => {
96113
const {connector} = createTestEnvironment({ chains })
97114
await connector.activate()
115+
expect(connector.provider?.chainId).toEqual(1)
98116
await connector.activate(2)
99-
expect(wc2RequestMock).toHaveBeenCalledWith({ method: 'wallet_switchEthereumChain', params: [{ chainId: '0x2' }] })
117+
expect(connector.provider?.chainId).toEqual(2)
100118
})
101119

102120
test('should not switch chain if already connected', async () => {
103121
const {connector} = createTestEnvironment({ chains })
104122
await connector.activate(2)
105123
await connector.activate(2)
106-
expect(wc2RequestMock).toBeCalledTimes(0)
124+
expect((connector.provider as unknown as MockWalletConnectProvider).eth_switchEthereumChain).toBeCalledTimes(0)
107125
})
108126
})
109127
})

packages/walletconnect/src/index.spec.ts

Lines changed: 9 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -2,40 +2,25 @@ import { createWeb3ReactStoreAndActions } from '@web3-react/store'
22
import type { Actions, RequestArguments, Web3ReactStore } from '@web3-react/types'
33
import EventEmitter from 'eventemitter3'
44
import { WalletConnect } from '.'
5-
import { MockEIP1193Provider } from '../../eip1193/src/mock'
6-
7-
// necessary because walletconnect returns chainId as a number
8-
class MockMockWalletConnectProvider extends MockEIP1193Provider {
9-
public connector = new EventEmitter()
10-
11-
public eth_chainId_number = jest.fn((chainId?: string) =>
12-
chainId === undefined ? chainId : Number.parseInt(chainId, 16)
13-
)
14-
15-
public request(x: RequestArguments): Promise<unknown> {
16-
if (x.method === 'eth_chainId') {
17-
return Promise.resolve(this.eth_chainId_number(this.chainId))
18-
} else {
19-
return super.request(x)
20-
}
21-
}
5+
import { MockEIP1193Provider } from '@web3-react/core'
226

7+
class MockWalletConnectProvider extends MockEIP1193Provider<number> {
238
/**
249
* TODO(INFRA-140): We're using the following private API to fix an underlying WalletConnect issue.
2510
* See {@link WalletConnect.activate} for details.
2611
*/
2712
private setHttpProvider() {}
2813
}
2914

30-
jest.mock('@walletconnect/ethereum-provider', () => MockMockWalletConnectProvider)
15+
jest.mock('@walletconnect/ethereum-provider', () => MockWalletConnectProvider)
3116

32-
const chainId = '0x1'
17+
const chainId = 1
3318
const accounts: string[] = []
3419

3520
describe('WalletConnect', () => {
3621
let store: Web3ReactStore
3722
let connector: WalletConnect
38-
let mockProvider: MockMockWalletConnectProvider
23+
let mockProvider: MockWalletConnectProvider
3924

4025
describe('works', () => {
4126
beforeEach(async () => {
@@ -47,20 +32,20 @@ describe('WalletConnect', () => {
4732
test('#activate', async () => {
4833
await connector.connectEagerly().catch(() => {})
4934

50-
mockProvider = connector.provider as unknown as MockMockWalletConnectProvider
35+
mockProvider = connector.provider as unknown as MockWalletConnectProvider
5136
mockProvider.chainId = chainId
5237
mockProvider.accounts = accounts
5338

5439
await connector.activate()
5540

5641
expect(mockProvider.eth_requestAccounts).toHaveBeenCalled()
5742
expect(mockProvider.eth_accounts).not.toHaveBeenCalled()
58-
expect(mockProvider.eth_chainId_number).toHaveBeenCalled()
59-
expect(mockProvider.eth_chainId_number.mock.invocationCallOrder[0])
43+
expect(mockProvider.eth_chainId).toHaveBeenCalled()
44+
expect(mockProvider.eth_chainId.mock.invocationCallOrder[0])
6045
.toBeGreaterThan(mockProvider.eth_requestAccounts.mock.invocationCallOrder[0])
6146

6247
expect(store.getState()).toEqual({
63-
chainId: Number.parseInt(chainId, 16),
48+
chainId,
6449
accounts,
6550
activating: false,
6651
error: undefined,

tsconfig.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@
33
"compilerOptions": {
44
"module": "CommonJS",
55
"declaration": true,
6-
"moduleResolution": "Node"
6+
"moduleResolution": "Node",
7+
"jsx": "react"
78
},
89
"exclude": ["**/*.spec.ts"]
910
}

0 commit comments

Comments
 (0)