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
6 changes: 6 additions & 0 deletions packages/examples/packages/send-flow/jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,4 +33,10 @@ module.exports = deepmerge(baseConfig, {
'<rootDir>/node_modules/@metamask/$1',
],
},

// Transform SVG files using our custom transformer
transform: {
...baseConfig.transform,
'\\.svg$': '<rootDir>/test/transformers/svgTransformer.js',
Copy link
Contributor Author

@hmalik88 hmalik88 Apr 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Needed to write a transformer for jest to be able to handle SVGs.

},
});
2 changes: 1 addition & 1 deletion packages/examples/packages/send-flow/snap.manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"url": "https://github.com/MetaMask/snaps.git"
},
"source": {
"shasum": "u6ivnu9fXa7saZ10sruh8I3ZKZjeAur4W+9+LVp9BoU=",
"shasum": "FOyn/lPGc/Fu6cW6EgX8OwklVJ/+HBydyqz+IDE8M+U=",
"location": {
"npm": {
"filePath": "dist/bundle.js",
Expand Down
13 changes: 1 addition & 12 deletions packages/examples/packages/send-flow/src/images/btc.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
12 changes: 1 addition & 11 deletions packages/examples/packages/send-flow/src/images/jazzicon1.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
12 changes: 1 addition & 11 deletions packages/examples/packages/send-flow/src/images/jazzicon2.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
221 changes: 220 additions & 1 deletion packages/examples/packages/send-flow/src/index.test.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import { expect } from '@jest/globals';
import { installSnap } from '@metamask/snaps-jest';

import { SendFlow } from './components';
import { accountsArray } from './data';

describe('onRpcRequest', () => {
it('throws an error if the requested method does not exist', async () => {
const { request } = await installSnap();
Expand All @@ -21,6 +24,222 @@ describe('onRpcRequest', () => {
});

describe('display', () => {
it.todo('shows a custom dialog with the SendFlow interface');
it('shows a custom dialog with the SendFlow interface', async () => {
const { request } = await installSnap();

const response = request({
method: 'display',
});

const sendFlowInterface = await response.getInterface();

expect(sendFlowInterface).toRender(
<SendFlow
accounts={accountsArray}
selectedAccount={accountsArray[0].address}
selectedCurrency="BTC"
total={{ amount: 0, fiat: 0 }}
fees={{ amount: 1.0001, fiat: 1.23 }}
/>,
);
});
});
});

describe('onHomePage', () => {
it('returns a custom UI', async () => {
const { onHomePage } = await installSnap();

const response = await onHomePage();

const sendFlowInterface = response.getInterface();

expect(sendFlowInterface).toRender(
<SendFlow
accounts={accountsArray}
selectedAccount={accountsArray[0].address}
selectedCurrency="BTC"
total={{ amount: 0, fiat: 0 }}
fees={{ amount: 1.0001, fiat: 1.23 }}
/>,
);
});
});

describe('onUserInput', () => {
it('handles account selection', async () => {
const { request } = await installSnap();

const response = request({
method: 'display',
});

const sendFlowInterface = await response.getInterface();

await sendFlowInterface.selectFromSelector(
'accountSelector',
accountsArray[1].address,
);

const updatedInterface = await response.getInterface();

expect(updatedInterface).toRender(
<SendFlow
accounts={accountsArray}
selectedAccount={accountsArray[1].address}
selectedCurrency="BTC"
total={{ amount: 1.0001, fiat: 251.23 }}
fees={{ amount: 1.0001, fiat: 1.23 }}
errors={{}}
displayAvatar={false}
/>,
);
});

it('handles amount input', async () => {
const { request } = await installSnap();

const response = request({
method: 'display',
});

const sendFlowInterface = await response.getInterface();

await sendFlowInterface.typeInField('amount', '0.5');

const updatedInterface = await response.getInterface();

expect(updatedInterface).toRender(
<SendFlow
accounts={accountsArray}
selectedAccount={accountsArray[0].address}
selectedCurrency="BTC"
total={{ amount: 1.5001, fiat: 251.23 }}
fees={{ amount: 1.0001, fiat: 1.23 }}
errors={{}}
displayAvatar={false}
/>,
);
});

it('handles to input', async () => {
const { request } = await installSnap();

const response = request({
method: 'display',
});

const sendFlowInterface = await response.getInterface();

await sendFlowInterface.typeInField(
'to',
'0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48',
);

const updatedInterface = await response.getInterface();

expect(updatedInterface).toRender(
<SendFlow
accounts={accountsArray}
selectedAccount={accountsArray[0].address}
selectedCurrency="BTC"
total={{ amount: 1.0001, fiat: 251.23 }}
fees={{ amount: 1.0001, fiat: 1.23 }}
errors={{}}
displayAvatar={true}
/>,
);
});

it('handles invalid input', async () => {
const { request } = await installSnap();

const response = request({
method: 'display',
});

const sendFlowInterface = await response.getInterface();

await sendFlowInterface.typeInField('amount', '2');

const updatedInterface = await response.getInterface();

expect(updatedInterface).toRender(
<SendFlow
accounts={accountsArray}
selectedAccount={accountsArray[0].address}
selectedCurrency="BTC"
total={{ amount: 3.0000999999999998, fiat: 251.23 }}
fees={{ amount: 1.0001, fiat: 1.23 }}
errors={{ amount: 'Insufficient funds' }}
displayAvatar={false}
/>,
);
});

it('maintains state across multiple interactions', async () => {
const { request } = await installSnap();

const response = request({
method: 'display',
});

const sendFlowInterface = await response.getInterface();

await sendFlowInterface.selectFromSelector(
'accountSelector',
accountsArray[1].address,
);

await sendFlowInterface.typeInField('amount', '0.5');

const updatedInterface = await response.getInterface();

expect(updatedInterface).toRender(
<SendFlow
accounts={accountsArray}
selectedAccount={accountsArray[1].address}
selectedCurrency="BTC"
total={{ amount: 1.5001, fiat: 251.23 }}
fees={{ amount: 1.0001, fiat: 1.23 }}
errors={{}}
displayAvatar={false}
/>,
);

await sendFlowInterface.typeInField(
'to',
'0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48',
);

const updatedInterface2 = await response.getInterface();

expect(updatedInterface2).toRender(
<SendFlow
accounts={accountsArray}
selectedAccount={accountsArray[1].address}
selectedCurrency="BTC"
total={{ amount: 1.5001, fiat: 251.23 }}
fees={{ amount: 1.0001, fiat: 1.23 }}
errors={{}}
displayAvatar={true}
/>,
);

await sendFlowInterface.typeInField('amount', '0');

const updatedInterface3 = await response.getInterface();

expect(updatedInterface3).toRender(
<SendFlow
accounts={accountsArray}
selectedAccount={accountsArray[1].address}
selectedCurrency="BTC"
total={{ amount: 1.0001, fiat: 251.23 }}
fees={{ amount: 1.0001, fiat: 1.23 }}
errors={{}}
displayAvatar={true}
/>,
);
});
});
5 changes: 4 additions & 1 deletion packages/examples/packages/send-flow/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,9 @@ export const onUserInput: OnUserInputHandler = async ({
errors={formErrors}
// For testing purposes, we display the avatar if the address is
// a valid hex checksum address.
displayAvatar={isCaipHexAddress(event.value)}
displayAvatar={isCaipHexAddress(
event.name === 'to' ? event.value : sendForm.to,
)}
/>
),
},
Expand All @@ -134,6 +136,7 @@ export const onUserInput: OnUserInputHandler = async ({
total={total}
fees={fees}
errors={formErrors}
displayAvatar={isCaipHexAddress(sendForm.to)}
/>
),
},
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/**
* A custom transformer for SVG files in Jest tests.
*/
module.exports = {
/**
* Process an SVG file for Jest tests.
*
* @param {string} sourceText - The content of the SVG file.
* @returns {object} The transformed code.
*/
process(sourceText) {
return {
code: `module.exports = ${JSON.stringify(sourceText)};`,
};
},

/**
* Generate a cache key for the transformation.
*
* @param {string} sourceText - The content of the SVG file.
* @returns {string} A cache key for Jest to use for caching purposes.
*/
getCacheKey(sourceText) {
return sourceText;
},
};
5 changes: 5 additions & 0 deletions packages/snaps-simulation/src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,8 @@ export const DEFAULT_CURRENCY = 'usd';
* The default JSON-RPC endpoint for Ethereum requests.
*/
export const DEFAULT_JSON_RPC_ENDPOINT = 'https://cloudflare-eth.com/';

/**
* The types of inputs that can be used in the `typeInField` interface action.
*/
export const TYPEABLE_INPUTS = ['Input', 'AddressInput'];
52 changes: 51 additions & 1 deletion packages/snaps-simulation/src/interface.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import {
RadioGroup,
Radio,
Box,
AddressInput,
Input,
FileInput,
Checkbox,
Expand Down Expand Up @@ -924,6 +925,55 @@ describe('typeInField', () => {
});
});

it('updates the interface state and sends an InputChangeEvent for an AddressInput', async () => {
jest.spyOn(rootControllerMessenger, 'call');
const content = (
<AddressInput
name="addressInput"
chainId="eip155:0"
placeholder="Enter an address"
/>
);

const interfaceId = await interfaceController.createInterface(
MOCK_SNAP_ID,
content,
);

await typeInField(
rootControllerMessenger,
interfaceId,
content,
MOCK_SNAP_ID,
'addressInput',
'0x1234567890123456789012345678901234567890',
);

expect(rootControllerMessenger.call).toHaveBeenCalledWith(
'SnapInterfaceController:updateInterfaceState',
interfaceId,
{ addressInput: 'eip155:0:0x1234567890123456789012345678901234567890' },
);

expect(handleRpcRequestMock).toHaveBeenCalledWith(MOCK_SNAP_ID, {
origin: 'metamask',
handler: HandlerType.OnUserInput,
request: {
jsonrpc: '2.0',
method: ' ',
params: {
event: {
type: UserInputEventType.InputChangeEvent,
name: 'addressInput',
value: 'eip155:0:0x1234567890123456789012345678901234567890',
},
id: interfaceId,
context: null,
},
},
});
});

it('throws if there is no inputs in the interface', async () => {
const content = text('bar');

Expand Down Expand Up @@ -964,7 +1014,7 @@ describe('typeInField', () => {
'baz',
),
).rejects.toThrow(
'Expected an element of type "Input", but found "Button".',
'Expected an element of type "Input" or "AddressInput", but found "Button".',
);
});
});
Expand Down
Loading
Loading