-
Notifications
You must be signed in to change notification settings - Fork 48
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
A series of hooks you can use to materialize features from wallets an…
…d accounts (#58) This PR implements React hooks for all of the `standard:` features except for `decrypt` and `encrypt`. The idea here is that you pass the hook an account (and chain, where relevant) and it either returns you a method implementation of that feature that you can call as many times as you need, or it throws in case the account can't be found, the account doesn't support that feature, or the account doesn't support that chain.
- Loading branch information
Showing
28 changed files
with
441 additions
and
547 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
--- | ||
'@wallet-standard/react-core': major | ||
--- | ||
|
||
Replaced the feature context providers with two hooks: `useConnect()` and `useDisconnect()` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,130 @@ | ||
import type { Wallet, WalletVersion } from '@wallet-standard/base'; | ||
import { StandardConnect } from '@wallet-standard/features'; | ||
import type { UiWallet } from '@wallet-standard/ui'; | ||
import { getWalletForHandle_DO_NOT_USE_OR_YOU_WILL_BE_FIRED } from '@wallet-standard/ui-registry'; | ||
|
||
import { useConnect } from '../features/useConnect.js'; | ||
import { renderHook } from '../test-renderer.js'; | ||
|
||
jest.mock('@wallet-standard/ui-registry'); | ||
|
||
describe('useConnect', () => { | ||
let mockConnect: jest.Mock; | ||
let mockUiWallet: UiWallet; | ||
beforeEach(() => { | ||
mockConnect = jest.fn().mockResolvedValue({ accounts: [] }); | ||
const mockWallet: Wallet = { | ||
accounts: [], | ||
chains: [], | ||
features: { | ||
[StandardConnect]: { | ||
connect: mockConnect, | ||
}, | ||
}, | ||
icon: 'data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEAAAAALAAAAAABAAEAAAIBAAA=', | ||
name: 'Mock Wallet', | ||
version: '1.0.0' as WalletVersion, | ||
}; | ||
jest.mocked(getWalletForHandle_DO_NOT_USE_OR_YOU_WILL_BE_FIRED).mockReturnValue(mockWallet); | ||
// Suppresses console output when an `ErrorBoundary` is hit. | ||
// See https://stackoverflow.com/a/72632884/802047 | ||
jest.spyOn(console, 'error').mockImplementation(); | ||
jest.spyOn(console, 'warn').mockImplementation(); | ||
}); | ||
describe('the `isConnecting` property', () => { | ||
it('is `false` before calling `connect()`', () => { | ||
const { result } = renderHook(() => useConnect(mockUiWallet)); | ||
if (result.__type === 'error' || !result.current) { | ||
throw result.current; | ||
} | ||
const [isConnecting, _] = result.current; | ||
expect(isConnecting).toBe(false); | ||
}); | ||
it('is `false` after the connection resolves', async () => { | ||
expect.assertions(1); | ||
const { result } = renderHook(() => useConnect(mockUiWallet)); | ||
if (result.__type === 'error' || !result.current) { | ||
throw result.current; | ||
} | ||
const [_A, connect] = result.current; | ||
await connect(); | ||
const [isConnecting, _B] = result.current; | ||
expect(isConnecting).toBe(false); | ||
}); | ||
it('is `true` after calling `connect()`', () => { | ||
const { result } = renderHook(() => useConnect(mockUiWallet)); | ||
if (result.__type === 'error' || !result.current) { | ||
throw result.current; | ||
} | ||
const [_, connect] = result.current; | ||
connect(); | ||
const [isConnecting] = result.current; | ||
expect(isConnecting).toBe(true); | ||
}); | ||
it('is `true` on hooks that did not trigger the connect', () => { | ||
const { result: resultA } = renderHook(() => useConnect(mockUiWallet)); | ||
const { result: resultB } = renderHook(() => useConnect(mockUiWallet)); | ||
if (resultA.__type === 'error' || !resultA.current) { | ||
throw resultA.current; | ||
} | ||
if (resultB.__type === 'error' || !resultB.current) { | ||
throw resultB.current; | ||
} | ||
const [_, connectA] = resultA.current; | ||
connectA(); | ||
const [isConnectingB] = resultB.current; | ||
expect(isConnectingB).toBe(true); | ||
}); | ||
}); | ||
describe('the `connect` property', () => { | ||
it("calls the wallet's connect implementation when called ", () => { | ||
const { result } = renderHook(() => useConnect(mockUiWallet)); | ||
if (result.__type === 'error' || !result.current) { | ||
throw result.current; | ||
} else { | ||
const [_, connect] = result.current; | ||
connect(); | ||
expect(mockConnect).toHaveBeenCalled(); | ||
} | ||
}); | ||
it("calls the wallet's connect implementation once despite multiple calls", () => { | ||
const { result } = renderHook(() => useConnect(mockUiWallet)); | ||
if (result.__type === 'error' || !result.current) { | ||
throw result.current; | ||
} else { | ||
const [_, connect] = result.current; | ||
connect(); | ||
connect(); | ||
expect(mockConnect).toHaveBeenCalledTimes(1); | ||
} | ||
}); | ||
it("calls the wallet's connect implementation once despite calls from different hooks", () => { | ||
const { result: resultA } = renderHook(() => useConnect(mockUiWallet)); | ||
const { result: resultB } = renderHook(() => useConnect(mockUiWallet)); | ||
if (resultA.__type === 'error' || !resultA.current) { | ||
throw resultA.current; | ||
} else if (resultB.__type === 'error' || !resultB.current) { | ||
throw resultB.current; | ||
} else { | ||
const [_A, connectA] = resultA.current; | ||
const [_B, connectB] = resultB.current; | ||
connectA(); | ||
connectB(); | ||
expect(mockConnect).toHaveBeenCalledTimes(1); | ||
} | ||
}); | ||
it("calls the wallet's connect implementation anew after the first connection resolves", async () => { | ||
expect.assertions(1); | ||
const { result } = renderHook(() => useConnect(mockUiWallet)); | ||
if (result.__type === 'error' || !result.current) { | ||
throw result.current; | ||
} else { | ||
const [_, connect] = result.current; | ||
connect(); | ||
await jest.runAllTimersAsync(); | ||
connect(); | ||
expect(mockConnect).toHaveBeenCalledTimes(2); | ||
} | ||
}); | ||
}); | ||
}); |
130 changes: 130 additions & 0 deletions
130
packages/react/core/src/__tests__/useDisconnect-test.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,130 @@ | ||
import type { Wallet, WalletVersion } from '@wallet-standard/base'; | ||
import { StandardDisconnect } from '@wallet-standard/features'; | ||
import type { UiWallet } from '@wallet-standard/ui'; | ||
import { getWalletForHandle_DO_NOT_USE_OR_YOU_WILL_BE_FIRED } from '@wallet-standard/ui-registry'; | ||
|
||
import { useDisconnect } from '../features/useDisconnect.js'; | ||
import { renderHook } from '../test-renderer.js'; | ||
|
||
jest.mock('@wallet-standard/ui-registry'); | ||
|
||
describe('useDisconnect', () => { | ||
let mockDisconnect: jest.Mock; | ||
let mockUiWallet: UiWallet; | ||
beforeEach(() => { | ||
mockDisconnect = jest.fn().mockResolvedValue({ accounts: [] }); | ||
const mockWallet: Wallet = { | ||
accounts: [], | ||
chains: [], | ||
features: { | ||
[StandardDisconnect]: { | ||
disconnect: mockDisconnect, | ||
}, | ||
}, | ||
icon: 'data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEAAAAALAAAAAABAAEAAAIBAAA=', | ||
name: 'Mock Wallet', | ||
version: '1.0.0' as WalletVersion, | ||
}; | ||
jest.mocked(getWalletForHandle_DO_NOT_USE_OR_YOU_WILL_BE_FIRED).mockReturnValue(mockWallet); | ||
// Suppresses console output when an `ErrorBoundary` is hit. | ||
// See https://stackoverflow.com/a/72632884/802047 | ||
jest.spyOn(console, 'error').mockImplementation(); | ||
jest.spyOn(console, 'warn').mockImplementation(); | ||
}); | ||
describe('the `isDisconnecting` property', () => { | ||
it('is `false` before calling `disconnect()`', () => { | ||
const { result } = renderHook(() => useDisconnect(mockUiWallet)); | ||
if (result.__type === 'error' || !result.current) { | ||
throw result.current; | ||
} | ||
const [isDisconnecting, _] = result.current; | ||
expect(isDisconnecting).toBe(false); | ||
}); | ||
it('is `false` after the disconnection resolves', async () => { | ||
expect.assertions(1); | ||
const { result } = renderHook(() => useDisconnect(mockUiWallet)); | ||
if (result.__type === 'error' || !result.current) { | ||
throw result.current; | ||
} | ||
const [_A, disconnectA] = result.current; | ||
await disconnectA(); | ||
const [isDisconnecting, _B] = result.current; | ||
expect(isDisconnecting).toBe(false); | ||
}); | ||
it('is `true` after calling `disconnect()`', () => { | ||
const { result } = renderHook(() => useDisconnect(mockUiWallet)); | ||
if (result.__type === 'error' || !result.current) { | ||
throw result.current; | ||
} | ||
const [_, disconnect] = result.current; | ||
disconnect(); | ||
const [isDisconnecting] = result.current; | ||
expect(isDisconnecting).toBe(true); | ||
}); | ||
it('is `true` on hooks that did not trigger the disconnect', () => { | ||
const { result: resultA } = renderHook(() => useDisconnect(mockUiWallet)); | ||
const { result: resultB } = renderHook(() => useDisconnect(mockUiWallet)); | ||
if (resultA.__type === 'error' || !resultA.current) { | ||
throw resultA.current; | ||
} | ||
if (resultB.__type === 'error' || !resultB.current) { | ||
throw resultB.current; | ||
} | ||
const [_, disconnectA] = resultA.current; | ||
disconnectA(); | ||
const [isDisconnectingB] = resultB.current; | ||
expect(isDisconnectingB).toBe(true); | ||
}); | ||
}); | ||
describe('the `disconnect` property', () => { | ||
it("calls the wallet's disconnect implementation when called ", () => { | ||
const { result } = renderHook(() => useDisconnect(mockUiWallet)); | ||
if (result.__type === 'error' || !result.current) { | ||
throw result.current; | ||
} else { | ||
const [_, disconnect] = result.current; | ||
disconnect(); | ||
expect(mockDisconnect).toHaveBeenCalled(); | ||
} | ||
}); | ||
it("calls the wallet's disconnect implementation once despite multiple calls", () => { | ||
const { result } = renderHook(() => useDisconnect(mockUiWallet)); | ||
if (result.__type === 'error' || !result.current) { | ||
throw result.current; | ||
} else { | ||
const [_, disconnect] = result.current; | ||
disconnect(); | ||
disconnect(); | ||
expect(mockDisconnect).toHaveBeenCalledTimes(1); | ||
} | ||
}); | ||
it("calls the wallet's disconnect implementation once despite calls from different hooks", () => { | ||
const { result: resultA } = renderHook(() => useDisconnect(mockUiWallet)); | ||
const { result: resultB } = renderHook(() => useDisconnect(mockUiWallet)); | ||
if (resultA.__type === 'error' || !resultA.current) { | ||
throw resultA.current; | ||
} else if (resultB.__type === 'error' || !resultB.current) { | ||
throw resultB.current; | ||
} else { | ||
const [_A, disconnectA] = resultA.current; | ||
const [_B, disconnectB] = resultB.current; | ||
disconnectA(); | ||
disconnectB(); | ||
expect(mockDisconnect).toHaveBeenCalledTimes(1); | ||
} | ||
}); | ||
it("calls the wallet's disconnect implementation anew after the first disconnection resolves", async () => { | ||
expect.assertions(1); | ||
const { result } = renderHook(() => useDisconnect(mockUiWallet)); | ||
if (result.__type === 'error' || !result.current) { | ||
throw result.current; | ||
} else { | ||
const [_, disconnect] = result.current; | ||
disconnect(); | ||
await jest.runAllTimersAsync(); | ||
disconnect(); | ||
expect(mockDisconnect).toHaveBeenCalledTimes(2); | ||
} | ||
}); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.