1
0
mirror of https://github.com/kremalicious/metamask-extension.git synced 2024-11-22 09:57:02 +01:00

Continue converting tests from enzyme to @testing-library/react (#16373)

* Add transaction activity log component

* Remove duplicate tx activity log snapshot.

* Convert Identicon test to tlr.

* Convert Metafoxlogo test to tlr.

* Convert Reveal Seed Phrase test to tlr.

* Consolidate and convert Send Footer test to tlr.

* Convert Settings test to tlr.

* Consolidate and convert security tab test to tlr.

* Convert null selectedOption to empty string, and add test id to Dropdown component.

* Add Send state to mock-state

* Lint mock-state.json
This commit is contained in:
Thomas Huang 2022-11-04 13:56:24 -07:00 committed by GitHub
parent 83ec64ddd2
commit 646dbaaff2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 909 additions and 507 deletions

View File

@ -12,12 +12,15 @@
"previousModalState": {
"name": null
}
}
},
"warning": null
},
"history": {
"mostRecentOverviewPage": "/"
"mostRecentOverviewPage": "/mostRecentOverviewPage"
},
"metamask": {
"usePhishDetect": true,
"participateInMetaMetrics": false,
"gasEstimateType": "fee-market",
"gasFeeEstimates": {
"low": {
@ -46,6 +49,7 @@
"priorityFeeTrend": "down",
"networkCongestion": 0.90625
},
"snaps": [{}],
"preferences": {
"hideZeroBalanceTokens": false,
"showFiatInTestnets": false,
@ -241,20 +245,6 @@
"unapprovedEncryptionPublicKeyMsgCount": 0,
"unapprovedTypedMessages": {},
"unapprovedTypedMessagesCount": 0,
"send": {
"gasLimit": "0x5208",
"gasPrice": "0xee6b2800",
"gasTotal": "0x4c65c6294000",
"tokenBalance": null,
"from": "0xc42edfcc21ed14dda456aa0756c153f7985d8813",
"to": "",
"amount": "1bc16d674ec80000",
"memo": "",
"errors": {},
"maxModeOn": false,
"editingTransactionId": null,
"toNickname": ""
},
"useTokenDetection": true,
"advancedGasFee": {
"maxBaseFee": "75",
@ -1284,5 +1274,24 @@
"origin": "tmashuang.github.io"
}
]
},
"send": {
"amountMode": "INPUT",
"currentTransactionUUID": null,
"draftTransactions": {},
"eip1559support": false,
"gasEstimateIsLoading": true,
"gasEstimatePollToken": null,
"gasIsSetInModal": false,
"gasPriceEstimate": "0x0",
"gasLimitMinimum": "0x5208",
"gasTotalForLayer1": "0x0",
"recipientMode": "CONTACT_LIST",
"recipientInput": "",
"selectedAccount": {
"address": "0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc",
"balance": "0x0"
},
"stage": "INACTIVE"
}
}

View File

@ -8,9 +8,10 @@ const Dropdown = ({
disabled = false,
onChange,
options,
selectedOption = null,
selectedOption = '',
style,
title,
'data-testid': dataTestId,
}) => {
const _onChange = useCallback(
(event) => {
@ -25,6 +26,7 @@ const Dropdown = ({
<div className={classnames('dropdown', className)}>
<select
className="dropdown__select"
data-testid={dataTestId}
disabled={disabled}
title={title}
onChange={_onChange}
@ -78,6 +80,10 @@ Dropdown.propTypes = {
* Add inline style for the component
*/
style: PropTypes.object,
/**
* Unit testing test id
*/
'data-testid': PropTypes.string,
};
export default Dropdown;

View File

@ -0,0 +1,70 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Identicon should match snapshot with address prop div 1`] = `
<div>
<div
class=""
>
<div
class="identicon test-address"
style="height: 46px; width: 46px; border-radius: 23px;"
>
<div
style="border-radius: 50px; overflow: hidden; padding: 0px; margin: 0px; width: 46px; height: 46px; display: inline-block; background: rgb(24, 151, 242);"
>
<svg
height="46"
width="46"
x="0"
y="0"
>
<rect
fill="#2362E1"
height="46"
transform="translate(5.159662140639244 -7.668071520898189) rotate(458.4 23 23)"
width="46"
x="0"
y="0"
/>
<rect
fill="#F94301"
height="46"
transform="translate(-22.084972728872177 11.489005476012034) rotate(268.8 23 23)"
width="46"
x="0"
y="0"
/>
<rect
fill="#FA7900"
height="46"
transform="translate(-13.047115238995795 42.377522828482356) rotate(117.3 23 23)"
width="46"
x="0"
y="0"
/>
</svg>
</div>
</div>
</div>
</div>
`;
exports[`Identicon should match snapshot with custom image and className props 1`] = `
<div>
<img
alt=""
class="identicon test-image"
src="test-image"
style="height: 46px; width: 46px; border-radius: 23px;"
/>
</div>
`;
exports[`Identicon should match snapshot with default props 1`] = `
<div>
<div
class="identicon__image-border"
style="height: 46px; width: 46px; border-radius: 23px;"
/>
</div>
`;

View File

@ -1,52 +1,51 @@
import React from 'react';
import thunk from 'redux-thunk';
import configureMockStore from 'redux-mock-store';
import { mount } from 'enzyme';
import Identicon from './identicon.component';
import { renderWithProvider } from '../../../../test/lib/render-helpers';
import Identicon from '.';
describe('Identicon', () => {
const state = {
const mockState = {
metamask: {
provider: {
chainId: '0x99',
},
useBlockie: false,
},
};
const middlewares = [thunk];
const mockStore = configureMockStore(middlewares);
const store = mockStore(state);
const mockStore = configureMockStore()(mockState);
it('renders empty identicon with no props', () => {
const wrapper = mount(<Identicon store={store} />);
it('should match snapshot with default props', () => {
const { container } = renderWithProvider(<Identicon />, mockStore);
expect(wrapper.find('div').prop('className')).toStrictEqual(
'identicon__image-border',
);
expect(container).toMatchSnapshot();
});
it('renders custom image and add className props', () => {
const wrapper = mount(
<Identicon store={store} className="test-image" image="test-image" />,
it('should match snapshot with custom image and className props', () => {
const props = {
className: 'test-image',
image: 'test-image',
};
const { container } = renderWithProvider(
<Identicon {...props} />,
mockStore,
);
expect(wrapper.find('img.test-image').prop('className')).toStrictEqual(
'identicon test-image',
);
expect(wrapper.find('img.test-image').prop('src')).toStrictEqual(
'test-image',
);
expect(container).toMatchSnapshot();
});
it('renders div with address prop', () => {
const wrapper = mount(
<Identicon
store={store}
className="test-address"
address="0x0000000000000000000000000000000000000000"
/>,
it('should match snapshot with address prop div', () => {
const props = {
className: 'test-address',
address: '0x0000000000000000000000000000000000000000',
};
const { container } = renderWithProvider(
<Identicon {...props} />,
mockStore,
);
expect(wrapper.find('div.test-address').prop('className')).toStrictEqual(
'identicon test-address',
);
expect(container).toMatchSnapshot();
});
});

View File

@ -0,0 +1,35 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`MetaFoxLogo does not set icon height and width when unsetIconHeight is true 1`] = `
<div>
<div
class="app-header__logo-container"
data-testid="app-header-logo"
>
<div />
<img
alt=""
class="app-header__metafox-logo--icon"
src="./images/logo/metamask-fox.svg"
/>
</div>
</div>
`;
exports[`MetaFoxLogo should match snapshot with img width and height default set to 42 1`] = `
<div>
<div
class="app-header__logo-container"
data-testid="app-header-logo"
>
<div />
<img
alt=""
class="app-header__metafox-logo--icon"
height="42"
src="./images/logo/metamask-fox.svg"
width="42"
/>
</div>
</div>
`;

View File

@ -1,27 +1,22 @@
import React from 'react';
import { mount } from 'enzyme';
import { renderWithProvider } from '../../../../test/lib/render-helpers';
import MetaFoxLogo from '.';
describe('MetaFoxLogo', () => {
it('sets icon height and width to 42 by default', () => {
const wrapper = mount(<MetaFoxLogo />);
// eslint-disable-next-line react/display-name
jest.mock('./horizontal-logo.js', () => () => {
return <div></div>;
});
expect(
wrapper.find('img.app-header__metafox-logo--icon').prop('width'),
).toStrictEqual(42);
expect(
wrapper.find('img.app-header__metafox-logo--icon').prop('height'),
).toStrictEqual(42);
describe('MetaFoxLogo', () => {
it('should match snapshot with img width and height default set to 42', () => {
const { container } = renderWithProvider(<MetaFoxLogo />);
expect(container).toMatchSnapshot();
});
it('does not set icon height and width when unsetIconHeight is true', () => {
const wrapper = mount(<MetaFoxLogo unsetIconHeight />);
const { container } = renderWithProvider(<MetaFoxLogo unsetIconHeight />);
expect(
wrapper.find('img.app-header__metafox-logo--icon').prop('width'),
).toBeUndefined();
expect(
wrapper.find('img.app-header__metafox-logo--icon').prop('height'),
).toBeUndefined();
expect(container).toMatchSnapshot();
});
});

View File

@ -0,0 +1,135 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Reveal Secret Recovery Phrase should match snapshot 1`] = `
<div>
<div
class="reveal-seed-phrase"
data-testid="reveal-seed-phrase"
>
<div
class="seed-phrase__sections"
>
<div
class="seed-phrase__main"
>
<div
class="box box--margin-bottom-4 box--flex-direction-row"
>
<a
href="#"
>
&lt; Back
</a>
</div>
<div
class="first-time-flow__header"
>
Secret Recovery Phrase
</div>
<div
class="first-time-flow__text-block"
>
Your Secret Recovery Phrase makes it easy to back up and restore your account.
</div>
<div
class="first-time-flow__text-block"
>
WARNING: Never disclose your Secret Recovery Phrase. Anyone with this phrase can take your Ether forever.
</div>
<div
class="reveal-seed-phrase__secret"
>
<div
class="reveal-seed-phrase__secret-words notranslate reveal-seed-phrase__secret-words--hidden"
data-testid="hidden-seed-phrase"
>
debris dizzy just program just float decrease vacant alarm reduce speak stadium
</div>
<div
class="reveal-seed-phrase__secret-blocker"
data-testid="reveal-seed-blocker"
>
<svg
fill="var(--color-overlay-inverse)"
height="35px"
id="Capa_1"
style="enable-background: new 0 0 401.998 401.998;"
version="1.1"
viewBox="0 0 401.998 401.998"
width="28px"
x="0px"
xml:space="preserve"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
y="0px"
>
<g>
<path
d="M357.45,190.721c-5.331-5.33-11.8-7.993-19.417-7.993h-9.131v-54.821c0-35.022-12.559-65.093-37.685-90.218 C266.093,12.563,236.025,0,200.998,0c-35.026,0-65.1,12.563-90.222,37.688C85.65,62.814,73.091,92.884,73.091,127.907v54.821 h-9.135c-7.611,0-14.084,2.663-19.414,7.993c-5.33,5.326-7.994,11.799-7.994,19.417V374.59c0,7.611,2.665,14.086,7.994,19.417 c5.33,5.325,11.803,7.991,19.414,7.991H338.04c7.617,0,14.085-2.663,19.417-7.991c5.325-5.331,7.994-11.806,7.994-19.417V210.135 C365.455,202.523,362.782,196.051,357.45,190.721z M274.087,182.728H127.909v-54.821c0-20.175,7.139-37.402,21.414-51.675 c14.277-14.275,31.501-21.411,51.678-21.411c20.179,0,37.399,7.135,51.677,21.411c14.271,14.272,21.409,31.5,21.409,51.675V182.728 z"
/>
</g>
</svg>
<div
class="reveal-seed-phrase__reveal-button"
>
Click here to reveal secret words
</div>
</div>
</div>
</div>
<div
class="seed-phrase__side"
>
<div
class="first-time-flow__text-block"
>
Tips:
</div>
<div
class="first-time-flow__text-block"
>
Store this phrase in a password manager like 1Password.
</div>
<div
class="first-time-flow__text-block"
>
Write this phrase on a piece of paper and store in a secure location. If you want even more security, write it down on multiple pieces of paper and store each in 2 - 3 different locations.
</div>
<div
class="first-time-flow__text-block"
>
Memorize this phrase.
</div>
<div
class="first-time-flow__text-block"
>
<a
class="reveal-seed-phrase__export-text"
>
Download this Secret Recovery Phrase and keep it stored safely on an external encrypted hard drive or storage medium.
</a>
</div>
</div>
</div>
<div
class="reveal-seed-phrase__buttons"
>
<button
class="button btn--rounded btn-secondary first-time-flow__button"
role="button"
tabindex="0"
>
Remind me later
</button>
<button
class="button btn--rounded btn-primary first-time-flow__button"
disabled=""
role="button"
tabindex="0"
>
Next
</button>
</div>
</div>
</div>
`;

View File

@ -112,12 +112,16 @@ export default class RevealSeedPhrase extends PureComponent {
'reveal-seed-phrase__secret-words--hidden': !isShowingSeedPhrase,
},
)}
data-testid={
isShowingSeedPhrase ? 'showing-seed-phrase' : 'hidden-seed-phrase'
}
>
{seedPhrase}
</div>
{!isShowingSeedPhrase && (
<div
className="reveal-seed-phrase__secret-blocker"
data-testid="reveal-seed-blocker"
onClick={() => {
this.context.trackEvent({
category: EVENT.CATEGORIES.ONBOARDING,

View File

@ -1,11 +1,11 @@
import React from 'react';
import sinon from 'sinon';
import { mount } from 'enzyme';
import RevealSeedPhrase from './reveal-seed-phrase.container';
import { fireEvent } from '@testing-library/react';
import configureMockStore from 'redux-mock-store';
import { renderWithProvider } from '../../../../../test/lib/render-helpers';
import RevealSeedPhrase from '.';
describe('Reveal Secret Recovery Phrase', () => {
let wrapper;
const TEST_SEED =
'debris dizzy just program just float decrease vacant alarm reduce speak stadium';
@ -18,31 +18,29 @@ describe('Reveal Secret Recovery Phrase', () => {
setCompletedOnboarding: sinon.spy(),
};
beforeEach(() => {
wrapper = mount(<RevealSeedPhrase.WrappedComponent {...props} />, {
context: {
t: (str) => str,
trackEvent: () => undefined,
},
});
});
const mockState = {
metamask: {},
};
it('secret recovery phrase', () => {
const seedPhrase = wrapper.find(
'.reveal-seed-phrase__secret-words--hidden',
const mockStore = configureMockStore()(mockState);
it('should match snapshot', () => {
const { container } = renderWithProvider(
<RevealSeedPhrase {...props} />,
mockStore,
);
expect(seedPhrase).toHaveLength(1);
expect(seedPhrase.text()).toStrictEqual(TEST_SEED);
expect(container).toMatchSnapshot();
});
it('clicks to reveal', () => {
const reveal = wrapper.find('.reveal-seed-phrase__secret-blocker');
it('clicks to reveal shows seed phrase', () => {
const { queryByTestId } = renderWithProvider(
<RevealSeedPhrase {...props} />,
mockStore,
);
expect(wrapper.state().isShowingSeedPhrase).toStrictEqual(false);
reveal.simulate('click');
expect(wrapper.state().isShowingSeedPhrase).toStrictEqual(true);
fireEvent.click(queryByTestId('reveal-seed-blocker'));
const showSeed = wrapper.find('.reveal-seed-phrase__secret-words');
expect(showSeed).toHaveLength(1);
expect(queryByTestId('showing-seed-phrase')).toBeInTheDocument();
});
});

View File

@ -0,0 +1,55 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`SendFooter Component Component Update should match snapshot when component updated with errors 1`] = `
<div>
<div
class="page-container__footer"
>
<footer>
<button
class="button btn--rounded btn-secondary page-container__footer-button"
data-testid="page-container-footer-cancel"
role="button"
tabindex="0"
>
[cancel]
</button>
<button
class="button btn--rounded btn-primary page-container__footer-button"
data-testid="page-container-footer-next"
role="button"
tabindex="0"
>
[next]
</button>
</footer>
</div>
</div>
`;
exports[`SendFooter Component should match snapshot 1`] = `
<div>
<div
class="page-container__footer"
>
<footer>
<button
class="button btn--rounded btn-secondary page-container__footer-button"
data-testid="page-container-footer-cancel"
role="button"
tabindex="0"
>
Cancel
</button>
<button
class="button btn--rounded btn-primary page-container__footer-button"
data-testid="page-container-footer-next"
role="button"
tabindex="0"
>
Next
</button>
</footer>
</div>
</div>
`;

View File

@ -1,207 +0,0 @@
import React from 'react';
import { shallow } from 'enzyme';
import sinon from 'sinon';
import {
CONFIRM_TRANSACTION_ROUTE,
DEFAULT_ROUTE,
} from '../../../helpers/constants/routes';
import PageContainerFooter from '../../../components/ui/page-container/page-container-footer';
import { renderWithProvider } from '../../../../test/jest';
import SendFooter from './send-footer.component';
describe('SendFooter Component', () => {
let wrapper;
const propsMethodSpies = {
addToAddressBookIfNew: sinon.spy(),
cancelTx: sinon.spy(),
resetSendState: sinon.spy(),
sign: sinon.spy(),
update: sinon.spy(),
mostRecentOverviewPage: '/',
};
const historySpies = {
push: sinon.spy(),
};
const MOCK_EVENT = { preventDefault: () => undefined };
const renderShallow = (props) => {
return shallow(
<SendFooter
addToAddressBookIfNew={propsMethodSpies.addToAddressBookIfNew}
resetSendState={propsMethodSpies.resetSendState}
cancelTx={propsMethodSpies.cancelTx}
disabled
draftTransactionID="ID"
history={historySpies}
sign={propsMethodSpies.sign}
to="mockTo"
toAccounts={['mockAccount']}
sendErrors={{}}
sendStage="DRAFT"
gasEstimateType="BASIC"
mostRecentOverviewPage="mostRecentOverviewPage"
{...props}
/>,
{ context: { t: (str) => str, trackEvent: () => ({}) } },
);
};
beforeAll(() => {
sinon.spy(SendFooter.prototype, 'onCancel');
sinon.spy(SendFooter.prototype, 'onSubmit');
});
beforeEach(() => {
wrapper = renderShallow();
});
afterEach(() => {
propsMethodSpies.resetSendState.resetHistory();
propsMethodSpies.cancelTx.resetHistory();
propsMethodSpies.addToAddressBookIfNew.resetHistory();
propsMethodSpies.resetSendState.resetHistory();
propsMethodSpies.sign.resetHistory();
propsMethodSpies.update.resetHistory();
historySpies.push.resetHistory();
SendFooter.prototype.onCancel.resetHistory();
SendFooter.prototype.onSubmit.resetHistory();
});
afterAll(() => {
sinon.restore();
});
describe('onCancel', () => {
it('should call resetSendState', () => {
expect(propsMethodSpies.resetSendState.callCount).toStrictEqual(0);
wrapper.instance().onCancel();
expect(propsMethodSpies.resetSendState.callCount).toStrictEqual(1);
});
it('should call cancelTx', () => {
expect(propsMethodSpies.cancelTx.callCount).toStrictEqual(0);
wrapper.instance().onCancel();
expect(propsMethodSpies.cancelTx.callCount).toStrictEqual(1);
expect(propsMethodSpies.cancelTx.getCall(0).args[0]?.id).toStrictEqual(
'ID',
);
});
it('should call history.push', () => {
expect(historySpies.push.callCount).toStrictEqual(0);
wrapper.instance().onCancel();
expect(historySpies.push.callCount).toStrictEqual(1);
expect(historySpies.push.getCall(0).args[0]).toStrictEqual(
'mostRecentOverviewPage',
);
});
it('should call history.push with DEFAULT_ROUTE in edit stage', () => {
wrapper = renderShallow({ sendStage: 'EDIT' });
expect(historySpies.push.callCount).toStrictEqual(0);
wrapper.instance().onCancel();
expect(historySpies.push.callCount).toStrictEqual(1);
expect(historySpies.push.getCall(0).args[0]).toStrictEqual(DEFAULT_ROUTE);
});
});
describe('onSubmit', () => {
it('should call addToAddressBookIfNew with the correct params', () => {
wrapper.instance().onSubmit(MOCK_EVENT);
expect(propsMethodSpies.addToAddressBookIfNew.calledOnce).toStrictEqual(
true,
);
expect(
propsMethodSpies.addToAddressBookIfNew.getCall(0).args,
).toStrictEqual(['mockTo', ['mockAccount']]);
});
it('should call props.sign whe submitting', async () => {
await wrapper.instance().onSubmit(MOCK_EVENT);
expect(propsMethodSpies.sign.calledOnce).toStrictEqual(true);
});
it('should call history.push', async () => {
await wrapper.instance().onSubmit(MOCK_EVENT);
expect(historySpies.push.callCount).toStrictEqual(1);
expect(historySpies.push.getCall(0).args[0]).toStrictEqual(
CONFIRM_TRANSACTION_ROUTE,
);
});
});
describe('render', () => {
beforeEach(() => {
wrapper = shallow(
<SendFooter
addToAddressBookIfNew={propsMethodSpies.addToAddressBookIfNew}
amount="mockAmount"
resetSendState={propsMethodSpies.resetSendState}
cancelTx={propsMethodSpies.cancelTx}
disabled
draftTransactionID="ID"
editingTransactionId="mockEditingTransactionId"
errors={{}}
from={{ address: 'mockAddress', balance: 'mockBalance' }}
gasLimit="mockGasLimit"
gasPrice="mockGasPrice"
gasTotal="mockGasTotal"
history={historySpies}
sendToken={{ mockProp: 'mockSendTokenProp' }}
sign={propsMethodSpies.sign}
to="mockTo"
toAccounts={['mockAccount']}
tokenBalance="mockTokenBalance"
unapprovedTxs={{}}
update={propsMethodSpies.update}
mostRecentOverviewPage="mostRecentOverviewPage"
/>,
{ context: { t: (str) => str, trackEvent: () => ({}) } },
);
});
it('should render a PageContainerFooter component', () => {
expect(wrapper.find(PageContainerFooter)).toHaveLength(1);
});
it('should pass the correct props to PageContainerFooter', () => {
const { onCancel, onSubmit, disabled } = wrapper
.find(PageContainerFooter)
.props();
expect(disabled).toStrictEqual(true);
expect(SendFooter.prototype.onSubmit.callCount).toStrictEqual(0);
onSubmit(MOCK_EVENT);
expect(SendFooter.prototype.onSubmit.callCount).toStrictEqual(1);
expect(SendFooter.prototype.onCancel.callCount).toStrictEqual(0);
onCancel();
expect(SendFooter.prototype.onCancel.callCount).toStrictEqual(1);
});
});
describe('Cancel Button', () => {
const renderFooter = (props) =>
renderWithProvider(
<SendFooter
disabled
mostRecentOverviewPage="mostRecentOverviewPage"
draftTransactionID="ID"
sendErrors={{}}
sendStage="DRAFT"
{...props}
/>,
);
it('has a cancel button in footer', () => {
const { getByText } = renderFooter();
expect(getByText('Cancel')).toBeTruthy();
});
it('has label changed to Reject in editing stage', () => {
const { getByText } = renderFooter({ sendStage: 'EDIT' });
expect(getByText('Reject')).toBeTruthy();
});
});
});

View File

@ -1,87 +0,0 @@
import sinon from 'sinon';
import { addToAddressBook, cancelTx } from '../../../store/actions';
import { resetSendState, signTransaction } from '../../../ducks/send';
let mapDispatchToProps;
jest.mock('react-redux', () => ({
connect: (_, md) => {
mapDispatchToProps = md;
return () => ({});
},
}));
jest.mock('../../../store/actions', () => ({
addToAddressBook: jest.fn(),
cancelTx: jest.fn(),
}));
jest.mock('../../../ducks/metamask/metamask', () => ({
getSendToAccounts: (s) => [`mockToAccounts:${s}`],
}));
jest.mock('../../../ducks/send', () => ({
getGasPrice: (s) => `mockGasPrice:${s}`,
getSendTo: (s) => `mockTo:${s}`,
getSendErrors: (s) => `mockSendErrors:${s}`,
getSendStage: (s) => `mockStage:${s}`,
getDraftTransaction: (s) => ({ id: `draftTransaction:${s}` }),
resetSendState: jest.fn(),
signTransaction: jest.fn(),
}));
require('./send-footer.container');
describe('send-footer container', () => {
describe('mapDispatchToProps()', () => {
let dispatchSpy;
let mapDispatchToPropsObject;
beforeEach(() => {
dispatchSpy = sinon.spy();
mapDispatchToPropsObject = mapDispatchToProps(dispatchSpy);
});
describe('resetSendState()', () => {
it('should dispatch an action', () => {
mapDispatchToPropsObject.resetSendState();
expect(dispatchSpy.calledOnce).toStrictEqual(true);
expect(resetSendState).toHaveBeenCalled();
});
});
describe('cancelTx()', () => {
it('should dispatch an action', () => {
const draftTansaction = { id: 'ID' };
mapDispatchToPropsObject.cancelTx(draftTansaction);
expect(dispatchSpy.calledOnce).toStrictEqual(true);
expect(cancelTx).toHaveBeenCalledTimes(1);
expect(cancelTx).toHaveBeenCalledWith(draftTansaction);
});
});
describe('sign()', () => {
it('should dispatch a signTransaction action', () => {
mapDispatchToPropsObject.sign();
expect(dispatchSpy.calledOnce).toStrictEqual(true);
expect(signTransaction).toHaveBeenCalledTimes(1);
});
});
describe('addToAddressBookIfNew()', () => {
it('should dispatch an action', () => {
mapDispatchToPropsObject.addToAddressBookIfNew(
'mockNewAddress',
[{ address: 'mockToAccounts' }],
'mockNickname',
);
expect(dispatchSpy.calledOnce).toStrictEqual(true);
expect(addToAddressBook).toHaveBeenCalledWith(
'0xmockNewAddress',
'mockNickname',
);
});
});
});
});

View File

@ -0,0 +1,136 @@
import React from 'react';
import configureMockStore from 'redux-mock-store';
import { fireEvent, waitFor } from '@testing-library/react';
import thunk from 'redux-thunk';
import { renderWithProvider } from '../../../../test/lib/render-helpers';
import mockState from '../../../../test/data/mock-state.json';
import {
CONFIRM_TRANSACTION_ROUTE,
DEFAULT_ROUTE,
} from '../../../helpers/constants/routes';
import { SEND_STAGES } from '../../../ducks/send';
import SendFooter from '.';
const mockResetSendState = jest.fn();
const mockSendTransaction = jest.fn();
const mockAddtoAddressBook = jest.fn();
const mockCancelTx = jest.fn();
jest.mock('../../../ducks/send/index.js', () => ({
...jest.requireActual('../../../ducks/send/index.js'),
signTransaction: () => mockSendTransaction,
resetSendState: () => mockResetSendState,
}));
jest.mock('../../../store/actions.js', () => ({
addToAddressBook: () => mockAddtoAddressBook,
cancelTx: () => mockCancelTx,
}));
describe('SendFooter Component', () => {
const props = {
history: {
push: jest.fn(),
},
};
afterEach(() => {
props.history.push.mockReset();
});
const mockStore = configureMockStore([thunk])(mockState);
it('should match snapshot', () => {
const { container } = renderWithProvider(
<SendFooter {...props} />,
mockStore,
);
expect(container).toMatchSnapshot();
});
describe('onCancel', () => {
it('should call reset send state and route to recent page without cancelling tx', () => {
const { queryByText } = renderWithProvider(
<SendFooter {...props} />,
mockStore,
);
const cancelText = queryByText('Cancel');
fireEvent.click(cancelText);
expect(mockResetSendState).toHaveBeenCalled();
expect(mockCancelTx).not.toHaveBeenCalled();
expect(props.history.push).toHaveBeenCalledWith(
'/mostRecentOverviewPage',
);
});
it('should reject/cancel tx when coming from tx editing and route to index', () => {
const sendDataState = {
...mockState,
send: {
currentTransactionUUID: '01',
draftTransactions: {
'01': {
id: '99',
},
},
stage: SEND_STAGES.EDIT,
},
};
const sendStateStore = configureMockStore([thunk])(sendDataState);
const { queryByText } = renderWithProvider(
<SendFooter {...props} />,
sendStateStore,
);
const rejectText = queryByText('Reject');
fireEvent.click(rejectText);
expect(mockResetSendState).toHaveBeenCalled();
expect(mockCancelTx).toHaveBeenCalled();
expect(props.history.push).toHaveBeenCalledWith(DEFAULT_ROUTE);
});
});
describe('onSubmit', () => {
it('should', async () => {
const { queryByText } = renderWithProvider(
<SendFooter {...props} />,
mockStore,
);
const nextText = queryByText('Next');
fireEvent.click(nextText);
await waitFor(() => {
expect(mockAddtoAddressBook).toHaveBeenCalled();
expect(mockSendTransaction).toHaveBeenCalled();
expect(props.history.push).toHaveBeenCalledWith(
CONFIRM_TRANSACTION_ROUTE,
);
});
});
});
describe('Component Update', () => {
it('should match snapshot when component updated with errors', () => {
const { container, rerender } = renderWithProvider(
<SendFooter.WrappedComponent />,
);
const sendErrorProps = {
sendErrors: {
gasFee: 'gas fee error',
amount: 'amount error',
},
};
rerender(<SendFooter.WrappedComponent {...sendErrorProps} />);
expect(container).toMatchSnapshot();
});
});
});

View File

@ -0,0 +1,246 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Security Tab should match snapshot 1`] = `
<div>
<div
class="settings-page__body"
>
<div
class="settings-page__content-row"
>
<div
class="settings-page__content-item"
>
<span>
Reveal Secret Recovery Phrase
</span>
</div>
<div
class="settings-page__content-item"
>
<div
class="settings-page__content-item-col"
>
<button
class="button btn--rounded btn-danger btn--large"
data-testid="reveal-seed-words"
role="button"
tabindex="0"
>
Reveal Secret Recovery Phrase
</button>
</div>
</div>
</div>
<div
class="settings-page__content-row"
>
<div
class="settings-page__content-item"
>
<span>
Show incoming transactions
</span>
<div
class="settings-page__content-description"
>
Select this to use Etherscan to show incoming transactions in the transactions list
</div>
</div>
<div
class="settings-page__content-item"
>
<div
class="settings-page__content-item-col"
>
<label
class="toggle-button toggle-button--on"
tabindex="0"
>
<div
style="display: flex; width: 52px; align-items: center; justify-content: flex-start; position: relative; cursor: pointer; background-color: transparent; border: 0px; padding: 0px; user-select: none;"
>
<div
style="width: 40px; height: 24px; padding: 0px; border-radius: 26px; display: flex; align-items: center; justify-content: center; background-color: rgb(242, 244, 246);"
>
<div
style="font-size: 11px; display: flex; align-items: center; justify-content: center; font-family: 'Helvetica Neue', Helvetica, sans-serif; position: relative; color: rgb(250, 250, 250); margin-top: auto; margin-bottom: auto; line-height: 0; opacity: 1; width: 26px; height: 20px; left: 4px;"
/>
<div
style="font-size: 11px; display: flex; align-items: center; justify-content: center; font-family: 'Helvetica Neue', Helvetica, sans-serif; position: relative; color: rgba(255, 255, 255, 0.6); bottom: 0px; margin-top: auto; margin-bottom: auto; padding-right: 5px; line-height: 0; width: 26px; height: 20px; opacity: 0;"
/>
</div>
<div
style="position: absolute; height: 100%; top: 0px; left: 0px; display: flex; flex: 1; align-self: stretch; align-items: center; justify-content: flex-start;"
>
<div
style="width: 18px; height: 18px; display: flex; align-self: center; box-shadow: none; border-radius: 50%; box-sizing: border-box; position: relative; background-color: rgb(3, 125, 214); left: 18px;"
/>
</div>
<input
style="border: 0px; height: 1px; margin: -1px; overflow: hidden; padding: 0px; position: absolute; width: 1px;"
type="checkbox"
value="true"
/>
</div>
<div
class="toggle-button__status"
>
<span
class="toggle-button__label-off"
>
Off
</span>
<span
class="toggle-button__label-on"
>
On
</span>
</div>
</label>
</div>
</div>
</div>
<div
class="settings-page__content-row"
>
<div
class="settings-page__content-item"
>
<span>
Use phishing detection
</span>
<div
class="settings-page__content-description"
>
Display a warning for phishing domains targeting Ethereum users
</div>
</div>
<div
class="settings-page__content-item"
>
<div
class="settings-page__content-item-col"
>
<label
class="toggle-button toggle-button--on"
tabindex="0"
>
<div
style="display: flex; width: 52px; align-items: center; justify-content: flex-start; position: relative; cursor: pointer; background-color: transparent; border: 0px; padding: 0px; user-select: none;"
>
<div
style="width: 40px; height: 24px; padding: 0px; border-radius: 26px; display: flex; align-items: center; justify-content: center; background-color: rgb(242, 244, 246);"
>
<div
style="font-size: 11px; display: flex; align-items: center; justify-content: center; font-family: 'Helvetica Neue', Helvetica, sans-serif; position: relative; color: rgb(250, 250, 250); margin-top: auto; margin-bottom: auto; line-height: 0; opacity: 1; width: 26px; height: 20px; left: 4px;"
/>
<div
style="font-size: 11px; display: flex; align-items: center; justify-content: center; font-family: 'Helvetica Neue', Helvetica, sans-serif; position: relative; color: rgba(255, 255, 255, 0.6); bottom: 0px; margin-top: auto; margin-bottom: auto; padding-right: 5px; line-height: 0; width: 26px; height: 20px; opacity: 0;"
/>
</div>
<div
style="position: absolute; height: 100%; top: 0px; left: 0px; display: flex; flex: 1; align-self: stretch; align-items: center; justify-content: flex-start;"
>
<div
style="width: 18px; height: 18px; display: flex; align-self: center; box-shadow: none; border-radius: 50%; box-sizing: border-box; position: relative; background-color: rgb(3, 125, 214); left: 18px;"
/>
</div>
<input
style="border: 0px; height: 1px; margin: -1px; overflow: hidden; padding: 0px; position: absolute; width: 1px;"
type="checkbox"
value="true"
/>
</div>
<div
class="toggle-button__status"
>
<span
class="toggle-button__label-off"
>
Off
</span>
<span
class="toggle-button__label-on"
>
On
</span>
</div>
</label>
</div>
</div>
</div>
<div
class="settings-page__content-row"
>
<div
class="settings-page__content-item"
>
<span>
Participate in MetaMetrics
</span>
<div
class="settings-page__content-description"
>
<span>
Participate in MetaMetrics to help us make MetaMask better
</span>
</div>
</div>
<div
class="settings-page__content-item"
>
<div
class="settings-page__content-item-col"
>
<label
class="toggle-button toggle-button--off"
tabindex="0"
>
<div
style="display: flex; width: 52px; align-items: center; justify-content: flex-start; position: relative; cursor: pointer; background-color: transparent; border: 0px; padding: 0px; user-select: none;"
>
<div
style="width: 40px; height: 24px; padding: 0px; border-radius: 26px; display: flex; align-items: center; justify-content: center; background-color: rgb(242, 244, 246);"
>
<div
style="font-size: 11px; display: flex; align-items: center; justify-content: center; font-family: 'Helvetica Neue', Helvetica, sans-serif; position: relative; color: rgb(250, 250, 250); margin-top: auto; margin-bottom: auto; line-height: 0; opacity: 0; width: 26px; height: 20px; left: 4px;"
/>
<div
style="font-size: 11px; display: flex; align-items: center; justify-content: center; font-family: 'Helvetica Neue', Helvetica, sans-serif; position: relative; color: rgba(255, 255, 255, 0.6); bottom: 0px; margin-top: auto; margin-bottom: auto; padding-right: 5px; line-height: 0; width: 26px; height: 20px; opacity: 1;"
/>
</div>
<div
style="position: absolute; height: 100%; top: 0px; left: 0px; display: flex; flex: 1; align-self: stretch; align-items: center; justify-content: flex-start;"
>
<div
style="width: 18px; height: 18px; display: flex; align-self: center; box-shadow: none; border-radius: 50%; box-sizing: border-box; position: relative; background-color: rgb(106, 115, 125); left: 3px;"
/>
</div>
<input
style="border: 0px; height: 1px; margin: -1px; overflow: hidden; padding: 0px; position: absolute; width: 1px;"
type="checkbox"
value="false"
/>
</div>
<div
class="toggle-button__status"
>
<span
class="toggle-button__label-off"
>
Off
</span>
<span
class="toggle-button__label-on"
>
On
</span>
</div>
</label>
</div>
</div>
</div>
</div>
</div>
`;

View File

@ -59,6 +59,7 @@ export default class SecurityTab extends PureComponent {
<div className="settings-page__content-item">
<div className="settings-page__content-item-col">
<Button
data-testid="reveal-seed-words"
type="danger"
large
onClick={(event) => {

View File

@ -1,63 +0,0 @@
import React from 'react';
import sinon from 'sinon';
import { mount } from 'enzyme';
import SecurityTab from './security-tab.container';
describe('Security Tab', () => {
let wrapper;
const props = {
revealSeedConfirmation: sinon.spy(),
showClearApprovalModal: sinon.spy(),
setParticipateInMetaMetrics: sinon.spy(),
displayWarning: sinon.spy(),
showIncomingTransactions: false,
setShowIncomingTransactionsFeatureFlag: sinon.spy(),
history: {
push: sinon.spy(),
},
privacyMode: true,
warning: '',
participateInMetaMetrics: false,
setUsePhishDetect: sinon.spy(),
usePhishDetect: true,
};
beforeEach(() => {
wrapper = mount(<SecurityTab.WrappedComponent {...props} />, {
context: {
t: (str) => str,
trackEvent: () => undefined,
},
});
});
it('navigates to reveal seed words page', () => {
const seedWords = wrapper.find('.button.btn-danger.btn--large');
seedWords.simulate('click');
expect(props.history.push.calledOnce).toStrictEqual(true);
expect(props.history.push.getCall(0).args[0]).toStrictEqual('/seed');
});
it('toggles incoming txs', () => {
const incomingTxs = wrapper.find({ type: 'checkbox' }).at(0);
incomingTxs.simulate('click');
expect(
props.setShowIncomingTransactionsFeatureFlag.calledOnce,
).toStrictEqual(true);
});
it('toggles phishing detection', () => {
const phishDetect = wrapper.find({ type: 'checkbox' }).at(1);
phishDetect.simulate('click');
expect(props.setUsePhishDetect.calledOnce).toStrictEqual(true);
});
it('toggles metaMetrics', () => {
const metaMetrics = wrapper.find({ type: 'checkbox' }).at(2);
metaMetrics.simulate('click');
expect(props.setParticipateInMetaMetrics.calledOnce).toStrictEqual(true);
});
});

View File

@ -0,0 +1,86 @@
import React from 'react';
import { fireEvent } from '@testing-library/react';
import configureMockStore from 'redux-mock-store';
import mockState from '../../../../test/data/mock-state.json';
import { renderWithProvider } from '../../../../test/lib/render-helpers';
import SecurityTab from './security-tab.container';
const mockSetFeatureFlag = jest.fn();
const mockSetParticipateInMetaMetrics = jest.fn();
const mockSetUsePhishDetect = jest.fn();
jest.mock('../../../store/actions.js', () => {
return {
setFeatureFlag: () => mockSetFeatureFlag,
setParticipateInMetaMetrics: () => mockSetParticipateInMetaMetrics,
setUsePhishDetect: () => mockSetUsePhishDetect,
};
});
describe('Security Tab', () => {
const mockStore = configureMockStore()(mockState);
it('should match snapshot', () => {
const { container } = renderWithProvider(<SecurityTab />, mockStore);
expect(container).toMatchSnapshot();
});
it('navigates to reveal seed words page', () => {
const { queryByTestId, history } = renderWithProvider(
<SecurityTab />,
mockStore,
);
expect(history.location.pathname).toStrictEqual('/');
fireEvent.click(queryByTestId('reveal-seed-words'));
expect(history.location.pathname).toStrictEqual('/seed');
});
it('toggles incoming txs', () => {
const { queryAllByRole } = renderWithProvider(<SecurityTab />, mockStore);
const checkboxes = queryAllByRole('checkbox');
const showIncomingCheckbox = checkboxes[0];
expect(showIncomingCheckbox).toHaveAttribute('value', 'true');
fireEvent.change(showIncomingCheckbox, {
target: { value: false },
});
expect(showIncomingCheckbox).toHaveAttribute('value', 'false');
});
it('toggles phishing detection', () => {
const { queryAllByRole } = renderWithProvider(<SecurityTab />, mockStore);
const checkboxes = queryAllByRole('checkbox');
const showIncomingCheckbox = checkboxes[1];
expect(showIncomingCheckbox).toHaveAttribute('value', 'true');
fireEvent.change(showIncomingCheckbox, {
target: { value: false },
});
expect(showIncomingCheckbox).toHaveAttribute('value', 'false');
});
it('toggles metaMetrics', () => {
const { queryAllByRole } = renderWithProvider(<SecurityTab />, mockStore);
const checkboxes = queryAllByRole('checkbox');
const showIncomingCheckbox = checkboxes[2];
expect(showIncomingCheckbox).toHaveAttribute('value', 'false');
fireEvent.change(showIncomingCheckbox, {
target: { value: true },
});
expect(showIncomingCheckbox).toHaveAttribute('value', 'true');
});
});

View File

@ -1,61 +0,0 @@
import React from 'react';
import { mount, shallow } from 'enzyme';
import { Provider } from 'react-redux';
import TextField from '../../components/ui/text-field';
import configureStore from '../../store/store';
import Settings from './settings.container';
import SettingsSearch from './settings-search';
describe('SettingsPage', () => {
let wrapper;
const props = {
addNewNetwork: false,
addressName: '',
backRoute: '/',
conversionDate: Date.now(),
currentPath: '/settings',
initialBreadCrumbKey: undefined,
initialBreadCrumbRoute: undefined,
isAddressEntryPage: false,
isPopup: false,
location: '/settings',
mostRecentOverviewPage: '',
pathnameI18nKey: undefined,
};
beforeEach(() => {
wrapper = shallow(<Settings.WrappedComponent {...props} />, {
context: {
t: (str) => str,
},
});
});
it('should render title correctly', () => {
expect(
wrapper.find('.settings-page__header__title-container__title').text(),
).toStrictEqual('settings');
});
it('should render search correctly', () => {
const store = configureStore({
metamask: {
snaps: {},
},
});
wrapper = mount(
<Provider store={store}>
<SettingsSearch onSearch={() => undefined} settingsRoutesList={[]} />
</Provider>,
{
context: {
t: (s) => `${s}`,
},
},
);
expect(wrapper.find(TextField).props().id).toStrictEqual('search-settings');
expect(wrapper.find(TextField).props().value).toStrictEqual('');
});
});

View File

@ -0,0 +1,45 @@
import React from 'react';
import configureMockStore from 'redux-mock-store';
import { renderWithProvider } from '../../../test/lib/render-helpers';
import mockState from '../../../test/data/mock-state.json';
import Settings from '.';
import 'jest-canvas-mock';
describe('SettingsPage', () => {
const props = {
addNewNetwork: false,
addressName: '',
backRoute: '/',
breadCrumbTextKey: '',
conversionDate: Date.now(),
initialBreadCrumbKey: '',
initialBreadCrumbRoute: '',
isAddressEntryPage: false,
isPopup: false,
isSnapViewPage: false,
mostRecentOverviewPage: '/',
pathnameI18nKey: '',
};
const mockStore = configureMockStore()(mockState);
it('should render correctly', () => {
const { queryByText } = renderWithProvider(
<Settings {...props} />,
mockStore,
'/settings',
);
expect(queryByText('Settings')).toBeInTheDocument();
});
it('should render search correctly', () => {
const { queryByPlaceholderText } = renderWithProvider(
<Settings {...props} />,
mockStore,
'/settings',
);
expect(queryByPlaceholderText('Search in Settings')).toBeInTheDocument();
});
});