Skip to content
Merged
Show file tree
Hide file tree
Changes from 9 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
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.

Updated the formatting for this SVG (and the ones below) to a consistent flat format since jest flattens it and the SVG transformer picks up the file content with format.

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;
},
};
15 changes: 11 additions & 4 deletions packages/snaps-simulation/src/interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -520,17 +520,24 @@ export async function typeInField(
);

assert(
result.element.type === 'Input',
`Expected an element of type "Input", but found "${result.element.type}".`,
result.element.type === 'Input' || result.element.type === 'AddressInput',
`Expected an element of type "Input" or "AddressInput", but found "${result.element.type}".`,
);

let newValue = value;

if (result.element.type === 'AddressInput') {
const { chainId } = result.element.props;
newValue = `${chainId}:${newValue}`;
}

const { state, context } = controllerMessenger.call(
'SnapInterfaceController:getInterface',
snapId,
id,
);

const newState = mergeValue(state, name, value, result.form);
const newState = mergeValue(state, name, newValue, result.form);

controllerMessenger.call(
'SnapInterfaceController:updateInterfaceState',
Expand All @@ -548,7 +555,7 @@ export async function typeInField(
event: {
type: UserInputEventType.InputChangeEvent,
name: result.element.props.name,
value,
value: newValue,
},
id,
context,
Expand Down
Loading