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 (#15956)

* Update mock state data

* Convert App Header test to tlr.

* Convert Gas Timing test to tlr.

* Convert Account Details Modal to tlr.

* Update Sig Req test to match mock state changes.

* Add test-ids to Editable Label for Account Details Modal

* Adjust selectors test for the mock state update.

* Add back gasIsLoading for selectors test.
This commit is contained in:
Thomas Huang 2022-09-27 08:03:26 -07:00 committed by GitHub
parent 362ef792bd
commit 0bbcbe6e90
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 631 additions and 310 deletions

View File

@ -1,43 +1,50 @@
{
"appState": {
"isLoading": false,
"networkDropdownOpen": false,
"gasIsLoading": false,
"currentView": {
"name": "accountDetail",
"detailView": null,
"context": "0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc"
"isLoading": false,
"modal": {
"open": false,
"modalState": {
"name": null,
"props": {}
},
"previousModalState": {
"name": null
}
}
},
"history": {
"mostRecentOverviewPage": "/"
},
"metamask": {
"gasEstimateType": "fee-market",
"gasFeeEstimates": {
"low": {
"suggestedMaxPriorityFeePerGas": "1.233864249",
"suggestedMaxFeePerGas": "19.308202033",
"minWaitTimeEstimate": 15000,
"maxWaitTimeEstimate": 30000
"minWaitTimeEstimate": 180000,
"maxWaitTimeEstimate": 300000,
"suggestedMaxPriorityFeePerGas": "3",
"suggestedMaxFeePerGas": "53"
},
"medium": {
"suggestedMaxPriorityFeePerGas": "1.5",
"suggestedMaxFeePerGas": "25.900356008",
"minWaitTimeEstimate": 15000,
"maxWaitTimeEstimate": 45000
"maxWaitTimeEstimate": 60000,
"suggestedMaxPriorityFeePerGas": "7",
"suggestedMaxFeePerGas": "70"
},
"high": {
"suggestedMaxPriorityFeePerGas": "2",
"suggestedMaxFeePerGas": "32.726374232",
"minWaitTimeEstimate": 15000,
"maxWaitTimeEstimate": 60000
"minWaitTimeEstimate": 0,
"maxWaitTimeEstimate": 15000,
"suggestedMaxPriorityFeePerGas": "10",
"suggestedMaxFeePerGas": "100"
},
"estimatedBaseFee": "18.074337783",
"historicalBaseFeeRange": ["10.621659703", "19.673392581"],
"estimatedBaseFee": "50",
"historicalBaseFeeRange": ["28.533098435", "70.351148354"],
"baseFeeTrend": "up",
"latestPriorityFeeRange": ["1.5", "17.536644176"],
"historicalPriorityFeeRange": ["0.110624865", "718.303816711"],
"priorityFeeTrend": "up",
"networkCongestion": 0.87115
"latestPriorityFeeRange": ["1", "40"],
"historicalPriorityFeeRange": ["0.1458417", "700.156384646"],
"priorityFeeTrend": "down",
"networkCongestion": 0.90625
},
"preferences": {
"hideZeroBalanceTokens": false,
@ -46,17 +53,34 @@
"useNativeCurrencyAsPrimaryCurrency": true
},
"ensResolutionsByAddress": {},
"isAccountMenuOpen": false,
"isUnlocked": true,
"alertEnabledness": {
"unconnectedAccount": true
},
"featureFlags": {
"showIncomingTransactions": true
},
"network": "4",
"provider": {
"type": "rpc",
"chainId": "0x4"
},
"keyrings": [
{
"type": "HD Key Tree",
"accounts": [
"0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc",
"0xec1adf982415d2ef5ec55899b9bfb8bc0f29251b"
]
},
{
"type": "Ledger Hardware",
"accounts": ["0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc"]
"accounts": ["0xc42edfcc21ed14dda456aa0756c153f7985d8813"]
},
{
"type": "Simple Key Pair",
"accounts": ["0xeb9e64b93097bc15f01f13eae97015c57ab64823"]
}
],
"identities": {
@ -64,9 +88,17 @@
"address": "0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc",
"name": "Test Account"
},
"0xec1adf982415d2ef5ec55899b9bfb8bc0f29251b": {
"address": "0xec1adf982415d2ef5ec55899b9bfb8bc0f29251b",
"name": "Test Account 2"
},
"0xc42edfcc21ed14dda456aa0756c153f7985d8813": {
"address": "0xc42edfcc21ed14dda456aa0756c153f7985d8813",
"name": "Test Account 2"
"name": "Test Ledger 1"
},
"0xeb9e64b93097bc15f01f13eae97015c57ab64823": {
"name": "Test Account 3",
"address": "0xeb9e64b93097bc15f01f13eae97015c57ab64823"
}
},
"networkDetails": {
@ -150,9 +182,17 @@
"balance": "0x0",
"address": "0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc"
},
"0xec1adf982415d2ef5ec55899b9bfb8bc0f29251b": {
"address": "0xec1adf982415d2ef5ec55899b9bfb8bc0f29251b",
"balance": "0x0"
},
"0xc42edfcc21ed14dda456aa0756c153f7985d8813": {
"address": "0xc42edfcc21ed14dda456aa0756c153f7985d8813",
"balance": "0x0"
},
"0xeb9e64b93097bc15f01f13eae97015c57ab64823": {
"address": "0xeb9e64b93097bc15f01f13eae97015c57ab64823",
"balance": "0x0"
}
},
"tokens": [

View File

@ -0,0 +1,309 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`App Header should match snapshot 1`] = `
<div>
<div
class="app-header"
>
<div
class="app-header__contents"
>
<div
class="app-header__logo-container app-header__logo-container--clickable"
data-testid="app-header-logo"
>
<svg
class="app-header__metafox-logo--horizontal"
height="30"
viewBox="0 0 1311 242"
width="162"
xmlns="http://www.w3.org/2000/svg"
>
<g
fill="none"
>
<g
fill="var(--color-text-default)"
transform="translate(361 61)"
>
<path
d="m796.7 60.9c-6.8-4.5-14.3-7.7-21.4-11.7-4.6-2.6-9.5-4.9-13.5-8.2-6.8-5.6-5.4-16.6 1.7-21.4 10.2-6.8 27.1-3 28.9 10.9 0 .3.3.5.6.5h15.4c.4 0 .7-.3.6-.7-.8-9.6-4.5-17.6-11.3-22.7-6.5-4.9-13.9-7.5-21.8-7.5-40.7 0-44.4 43.1-22.5 56.7 2.5 1.6 24 12.4 31.6 17.1s10 13.3 6.7 20.1c-3 6.2-10.8 10.5-18.6 10-8.5-.5-15.1-5.1-17.4-12.3-.4-1.3-.6-3.8-.6-4.9 0-.3-.3-.6-.6-.6h-16.7c-.3 0-.6.3-.6.6 0 12.1 3 18.8 11.2 24.9 7.7 5.8 16.1 8.2 24.8 8.2 22.8 0 34.6-12.9 37-26.3 2.1-13.1-1.8-24.9-13.5-32.7z"
/>
<path
d="m71.6 2.3h-7.4-8.1c-.3 0-.5.2-.6.4l-13.7 45.2c-.2.6-1 .6-1.2 0l-13.7-45.2c-.1-.3-.3-.4-.6-.4h-8.1-7.4-10c-.3 0-.6.3-.6.6v115.4c0 .3.3.6.6.6h16.7c.3 0 .6-.3.6-.6v-87.7c0-.7 1-.8 1.2-.2l13.8 45.5 1 3.2c.1.3.3.4.6.4h12.8c.3 0 .5-.2.6-.4l1-3.2 13.8-45.5c.2-.7 1.2-.5 1.2.2v87.7c0 .3.3.6.6.6h16.7c.3 0 .6-.3.6-.6v-115.4c0-.3-.3-.6-.6-.6z"
/>
<path
d="m541 2.3c-.3 0-.5.2-.6.4l-13.7 45.2c-.2.6-1 .6-1.2 0l-13.7-45.2c-.1-.3-.3-.4-.6-.4h-25.4c-.3 0-.6.3-.6.6v115.4c0 .3.3.6.6.6h16.7c.3 0 .6-.3.6-.6v-87.7c0-.7 1-.8 1.2-.2l13.8 45.5 1 3.2c.1.3.3.4.6.4h12.8c.3 0 .5-.2.6-.4l1-3.2 13.8-45.5c.2-.7 1.2-.5 1.2.2v87.7c0 .3.3.6.6.6h16.7c.3 0 .6-.3.6-.6v-115.4c0-.3-.3-.6-.6-.6z"
/>
<path
d="m325.6 2.3h-31.1-16.7-31.1c-.3 0-.6.3-.6.6v14.4c0 .3.3.6.6.6h30.5v100.4c0 .3.3.6.6.6h16.7c.3 0 .6-.3.6-.6v-100.4h30.5c.3 0 .6-.3.6-.6v-14.4c0-.3-.2-.6-.6-.6z"
/>
<path
d="m424.1 118.9h15.2c.4 0 .7-.4.6-.8l-31.4-115.8c-.1-.3-.3-.4-.6-.4h-5.8-10.2-5.8c-.3 0-.5.2-.6.4l-31.4 115.8c-.1.4.2.8.6.8h15.2c.3 0 .5-.2.6-.4l9.1-33.7c.1-.3.3-.4.6-.4h33.6c.3 0 .5.2.6.4l9.1 33.7c.1.2.4.4.6.4zm-39.9-51 12.2-45.1c.2-.6 1-.6 1.2 0l12.2 45.1c.1.4-.2.8-.6.8h-24.4c-.4 0-.7-.4-.6-.8z"
/>
<path
d="m683.3 118.9h15.2c.4 0 .7-.4.6-.8l-31.4-115.8c-.1-.3-.3-.4-.6-.4h-5.8-10.2-5.8c-.3 0-.5.2-.6.4l-31.4 115.8c-.1.4.2.8.6.8h15.2c.3 0 .5-.2.6-.4l9.1-33.7c.1-.3.3-.4.6-.4h33.6c.3 0 .5.2.6.4l9.1 33.7c.1.2.3.4.6.4zm-39.9-51 12.2-45.1c.2-.6 1-.6 1.2 0l12.2 45.1c.1.4-.2.8-.6.8h-24.4c-.4 0-.7-.4-.6-.8z"
/>
<path
d="m149.8 101.8v-35.8c0-.3.3-.6.6-.6h44.5c.3 0 .6-.3.6-.6v-14.4c0-.3-.3-.6-.6-.6h-44.5c-.3 0-.6-.3-.6-.6v-30.6c0-.3.3-.6.6-.6h50.6c.3 0 .6-.3.6-.6v-14.4c0-.3-.3-.6-.6-.6h-51.2-17.3c-.3 0-.6.3-.6.6v15 31.9 15.6 37 15.8c0 .3.3.6.6.6h17.3 53.3c.3 0 .6-.3.6-.6v-15.2c0-.3-.3-.6-.6-.6h-52.8c-.3-.1-.5-.3-.5-.7z"
/>
<path
d="m949.3 117.9-57.8-59.7c-.2-.2-.2-.6 0-.8l52-54c.4-.4.1-1-.4-1h-21.3c-.2 0-.3.1-.4.2l-44.1 45.8c-.4.4-1 .1-1-.4v-45c0-.3-.3-.6-.6-.6h-16.7c-.3 0-.6.3-.6.6v115.4c0 .3.3.6.6.6h16.7c.3 0 .6-.3.6-.6v-50.8c0-.5.7-.8 1-.4l50 51.6c.1.1.3.2.4.2h21.3c.4-.1.7-.8.3-1.1z"
/>
</g>
<g
stroke-linecap="round"
stroke-linejoin="round"
transform="translate(1 1)"
>
<path
d="m246.1.2-101.1 75 18.8-44.2z"
fill="#e17726"
stroke="#e17726"
/>
<g
fill="#e27625"
stroke="#e27625"
transform="translate(2)"
>
<path
d="m10.9.2 100.2 75.7-17.9-44.9z"
/>
<path
d="m207.7 174.1-26.9 41.2 57.6 15.9 16.5-56.2z"
/>
<path
d="m.2 175 16.4 56.2 57.5-15.9-26.8-41.2z"
/>
<path
d="m71 104.5-16 24.2 57 2.6-1.9-61.5z"
/>
<path
d="m184 104.5-39.7-35.4-1.3 62.2 57-2.6z"
/>
<path
d="m74.1 215.3 34.5-16.7-29.7-23.2z"
/>
<path
d="m146.4 198.6 34.4 16.7-4.7-39.9z"
/>
</g>
<g
fill="#d5bfb2"
stroke="#d5bfb2"
transform="translate(76 198)"
>
<path
d="m106.8 17.3-34.4-16.7 2.8 22.4-.3 9.5z"
/>
<path
d="m.1 17.3 32 15.2-.2-9.5 2.7-22.4z"
/>
</g>
<path
d="m108.7 160.6-28.6-8.4 20.2-9.3z"
fill="#233447"
stroke="#233447"
/>
<path
d="m150.3 160.6 8.4-17.7 20.3 9.3z"
fill="#233447"
stroke="#233447"
/>
<g
fill="#cc6228"
stroke="#cc6228"
transform="translate(49 128)"
>
<path
d="m27.1 87.3 5-41.2-31.8.9z"
/>
<path
d="m128.9 46.1 4.9 41.2 26.9-40.3z"
/>
<path
d="m153 .7-57 2.6 5.3 29.3 8.4-17.7 20.3 9.3z"
/>
<path
d="m31.1 24.2 20.2-9.3 8.4 17.7 5.3-29.3-57-2.6z"
/>
</g>
<g
fill="#e27525"
stroke="#e27525"
transform="translate(57 128)"
>
<path
d="m0 .7 23.9 46.7-.8-23.2z"
/>
<path
d="m122 24.2-.9 23.2 23.9-46.7z"
/>
<path
d="m57 3.3-5.3 29.3 6.7 34.6 1.5-45.6z"
/>
<path
d="m88 3.3-2.8 18.2 1.4 45.7 6.7-34.6z"
/>
</g>
<path
d="m150.3 160.6-6.7 34.6 4.8 3.4 29.7-23.2.9-23.2z"
fill="#f5841f"
stroke="#f5841f"
/>
<path
d="m80.1 152.2.8 23.2 29.7 23.2 4.8-3.4-6.7-34.6z"
fill="#f5841f"
stroke="#f5841f"
/>
<path
d="m150.9 230.5.3-9.5-2.6-2.2h-38.2l-2.5 2.2.2 9.5-32-15.2 11.2 9.2 22.7 15.7h38.9l22.8-15.7 11.1-9.2z"
fill="#c0ac9d"
stroke="#c0ac9d"
/>
<path
d="m148.4 198.6-4.8-3.4h-28.2l-4.8 3.4-2.7 22.4 2.5-2.2h38.2l2.6 2.2z"
fill="#161616"
stroke="#161616"
/>
<g
fill="#763e1a"
stroke="#763e1a"
>
<path
d="m250.4 80.1 8.5-41.4-12.8-38.5-97.7 72.5 37.6 31.8 53.1 15.5 11.7-13.7-5.1-3.7 8.1-7.4-6.2-4.8 8.1-6.2z"
/>
<path
d="m.1 38.7 8.6 41.4-5.5 4.1 8.2 6.2-6.2 4.8 8.1 7.4-5.1 3.7 11.7 13.7 53.1-15.5 37.6-31.8-97.7-72.5z"
/>
</g>
<g
fill="#f5841f"
stroke="#f5841f"
>
<path
d="m239.1 120-53.1-15.5 16 24.2-23.9 46.7 31.6-.4h47.2z"
/>
<path
d="m73 104.5-53.1 15.5-17.7 55h47.1l31.6.4-23.9-46.7z"
/>
<path
d="m145 131.3 3.4-58.6 15.4-41.7h-68.6l15.4 41.7 3.4 58.6 1.3 18.4.1 45.5h28.2l.1-45.5z"
/>
</g>
</g>
</g>
</svg>
<img
alt=""
class="app-header__metafox-logo--icon"
src="./images/logo/metamask-fox.svg"
/>
</div>
<div
class="app-header__account-menu-container"
>
<div
class="app-header__network-component-wrapper"
>
<div
class="network-display network-display--clickable chip chip--with-left-icon chip--with-right-icon chip--border-color-border-default chip--background-color-undefined chip--max-content"
data-testid="network-display"
role="button"
tabindex="0"
>
<div
class="chip__left-icon"
>
<div
class="color-indicator color-indicator--filled color-indicator--color-icon-muted color-indicator--size-lg"
data-testid="color-icon-icon-muted"
>
<i
class="color-indicator__icon fa fa-question"
/>
</div>
</div>
<span
class="box box--margin-top-1 box--margin-bottom-1 box--flex-direction-row typography chip__label typography--h7 typography--weight-normal typography--style-normal typography--color-text-alternative"
>
Private network
</span>
<div
class="chip__right-icon"
>
<svg
class="network-display__icon"
fill="currentColor"
height="16"
viewBox="0 0 512 512"
width="16"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="m399 177c-8-8-22-8-30 0l-113 113-113-113c-8-8-22-8-30 0-8 8-8 22 0 30l128 128c8 8 22 8 30 0l128-128c8-8 8-22 0-30z"
/>
</svg>
</div>
</div>
</div>
<button
class="account-menu__icon"
data-testid="account-menu-icon"
>
<div
class="identicon__address-wrapper"
style="height: 40px; width: 40px; border-radius: 20px;"
>
<div
class="identicon"
style="height: 32px; width: 32px; border-radius: 16px;"
>
<div
style="border-radius: 50px; overflow: hidden; padding: 0px; margin: 0px; width: 32px; height: 32px; display: inline-block; background: rgb(250, 58, 0);"
>
<svg
height="32"
width="32"
x="0"
y="0"
>
<rect
fill="#18CDF2"
height="32"
transform="translate(-1.04839350379394 -3.3042840694604987) rotate(328.9 16 16)"
width="32"
x="0"
y="0"
/>
<rect
fill="#035E56"
height="32"
transform="translate(-18.298461708832043 10.5924618717486) rotate(176.2 16 16)"
width="32"
x="0"
y="0"
/>
<rect
fill="#F26602"
height="32"
transform="translate(16.667842018223922 -14.205139722997082) rotate(468.9 16 16)"
width="32"
x="0"
y="0"
/>
</svg>
</div>
</div>
</div>
<div
class="account-menu__icon__notification-count"
>
1
</div>
</button>
</div>
</div>
</div>
</div>
`;

View File

@ -74,6 +74,7 @@ export default class AppHeader extends PureComponent {
return (
isUnlocked && (
<button
data-testid="account-menu-icon"
className={classnames('account-menu__icon', {
'account-menu__icon--disabled': disabled,
})}

View File

@ -1,94 +1,148 @@
import React from 'react';
import sinon from 'sinon';
import { shallow } from 'enzyme';
import MetaFoxLogo from '../../ui/metafox-logo';
import NetworkDisplay from '../network-display';
import AppHeader from './app-header.container';
import { fireEvent } from '@testing-library/react';
import configureMockStore from 'redux-mock-store';
import thunk from 'redux-thunk';
import mockState from '../../../../test/data/mock-state.json';
import { renderWithProvider } from '../../../../test/lib/render-helpers';
import AppHeader from '.';
const mockShowNetworkDropdown = jest.fn();
const mockHideNetworkDropdown = jest.fn();
const mockToggleAccountMenu = jest.fn();
jest.mock('../../../store/actions', () => {
return {
showNetworkDropdown: () => mockShowNetworkDropdown,
hideNetworkDropdown: () => mockHideNetworkDropdown,
toggleAccountMenu: () => mockToggleAccountMenu,
};
});
describe('App Header', () => {
let wrapper;
const props = {
hideNetworkDropdown: sinon.spy(),
showNetworkDropdown: sinon.spy(),
toggleAccountMenu: sinon.spy(),
history: {
push: sinon.spy(),
},
network: 'test',
provider: {},
selectedAddress: '0xAddress',
disabled: false,
hideNetworkIndicator: false,
networkDropdownOpen: false,
isAccountMenuOpen: false,
isUnlocked: true,
};
beforeEach(() => {
wrapper = shallow(<AppHeader.WrappedComponent {...props} />, {
context: {
t: (str) => str,
trackEvent: () => undefined,
},
});
});
afterEach(() => {
props.toggleAccountMenu.resetHistory();
mockShowNetworkDropdown.mockClear();
mockHideNetworkDropdown.mockClear();
mockToggleAccountMenu.mockClear();
});
const store = configureMockStore([thunk])(mockState);
it('should match snapshot', () => {
const { container } = renderWithProvider(<AppHeader />, store);
expect(container).toMatchSnapshot();
});
describe('App Header Logo', () => {
it('routes to default route when logo is clicked', () => {
const appLogo = wrapper.find(MetaFoxLogo);
appLogo.simulate('click');
expect(props.history.push.calledOnce).toStrictEqual(true);
expect(props.history.push.getCall(0).args[0]).toStrictEqual('/');
const { history, queryByTestId } = renderWithProvider(
<AppHeader />,
store,
'/different-route',
);
expect(history.location.pathname).toStrictEqual('/different-route');
const appLogo = queryByTestId('app-header-logo');
fireEvent.click(appLogo);
expect(history.location.pathname).toStrictEqual('/');
});
});
describe('Network', () => {
it('shows network dropdown when networkDropdownOpen is false', () => {
const network = wrapper.find(NetworkDisplay);
network.simulate('click', {
preventDefault: () => undefined,
stopPropagation: () => undefined,
});
const { queryByTestId } = renderWithProvider(<AppHeader />, store);
expect(props.showNetworkDropdown.calledOnce).toStrictEqual(true);
const networkDisplay = queryByTestId('network-display');
fireEvent.click(networkDisplay);
expect(mockShowNetworkDropdown).toHaveBeenCalled();
expect(mockHideNetworkDropdown).not.toHaveBeenCalled();
});
it('hides network dropdown when networkDropdownOpen is true', () => {
wrapper.setProps({ networkDropdownOpen: true });
const network = wrapper.find(NetworkDisplay);
const openNetworkDropdownState = {
...mockState,
appState: {
networkDropdownOpen: true,
},
};
network.simulate('click', {
preventDefault: () => undefined,
stopPropagation: () => undefined,
});
const openNetworkDropdownStore = configureMockStore([thunk])(
openNetworkDropdownState,
);
expect(props.hideNetworkDropdown.calledOnce).toStrictEqual(true);
const { queryByTestId } = renderWithProvider(
<AppHeader />,
openNetworkDropdownStore,
);
const networkDisplay = queryByTestId('network-display');
fireEvent.click(networkDisplay);
expect(mockShowNetworkDropdown).not.toHaveBeenCalled();
expect(mockHideNetworkDropdown).toHaveBeenCalled();
});
it('hides network indicator', () => {
wrapper.setProps({ hideNetworkIndicator: true });
const network = wrapper.find(NetworkDisplay);
expect(network).toHaveLength(0);
const props = {
hideNetworkIndicator: true,
};
const { queryByTestId } = renderWithProvider(
<AppHeader {...props} />,
store,
);
const networkDisplay = queryByTestId('network-display');
expect(networkDisplay).not.toBeInTheDocument();
});
});
describe('Account Menu', () => {
it('toggles account menu', () => {
const accountMenu = wrapper.find('.account-menu__icon');
accountMenu.simulate('click');
expect(props.toggleAccountMenu.calledOnce).toStrictEqual(true);
const { queryByTestId } = renderWithProvider(<AppHeader />, store);
const accountMenuIcon = queryByTestId('account-menu-icon');
fireEvent.click(accountMenuIcon);
expect(mockToggleAccountMenu).toHaveBeenCalled();
});
it('should not render account menu icon if isUnlocked is false', () => {
const lockedState = {
...mockState,
metamask: {
...mockState.metamask,
isUnlocked: false,
},
};
const lockedStore = configureMockStore([thunk])(lockedState);
const { queryByTestId } = renderWithProvider(<AppHeader />, lockedStore);
const accountMenuIcon = queryByTestId('account-menu-icon');
expect(accountMenuIcon).not.toBeInTheDocument();
});
it('does not toggle account menu when disabled', () => {
wrapper.setProps({ disabled: true });
const accountMenu = wrapper.find('.account-menu__icon');
accountMenu.simulate('click');
expect(props.toggleAccountMenu.notCalled).toStrictEqual(true);
const props = {
disabled: true,
};
const { queryByTestId } = renderWithProvider(
<AppHeader {...props} />,
store,
);
const accountMenuIcon = queryByTestId('account-menu-icon');
fireEvent.click(accountMenuIcon);
expect(mockToggleAccountMenu).not.toHaveBeenCalled();
});
});
});

View File

@ -0,0 +1,3 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Gas timing renders nothing when gas is loading 1`] = `<div />`;

View File

@ -1,146 +1,81 @@
import React from 'react';
import { useSelector } from 'react-redux';
import { shallow } from 'enzyme';
import sinon from 'sinon';
import configureMockStore from 'redux-mock-store';
import { waitFor } from '@testing-library/react';
import { renderWithProvider } from '../../../../test/lib/render-helpers';
import { GAS_ESTIMATE_TYPES } from '../../../../shared/constants/gas';
import messages from '../../../../app/_locales/en/messages.json';
import { getMessage } from '../../../helpers/utils/i18n-helper';
import * as i18nhooks from '../../../hooks/useI18nContext';
import { checkNetworkAndAccountSupports1559 } from '../../../selectors';
import {
getGasEstimateType,
getGasFeeEstimates,
getIsGasEstimatesLoading,
} from '../../../ducks/metamask/metamask';
import mockState from '../../../../test/data/mock-state.json';
import GasTiming from '.';
const MOCK_FEE_ESTIMATE = {
low: {
minWaitTimeEstimate: 180000,
maxWaitTimeEstimate: 300000,
suggestedMaxPriorityFeePerGas: '3',
suggestedMaxFeePerGas: '53',
},
medium: {
minWaitTimeEstimate: 15000,
maxWaitTimeEstimate: 60000,
suggestedMaxPriorityFeePerGas: '7',
suggestedMaxFeePerGas: '70',
},
high: {
minWaitTimeEstimate: 0,
maxWaitTimeEstimate: 15000,
suggestedMaxPriorityFeePerGas: '10',
suggestedMaxFeePerGas: '100',
},
estimatedBaseFee: '50',
};
jest.mock('react-redux', () => {
const actual = jest.requireActual('react-redux');
return {
...actual,
useSelector: jest.fn(),
};
});
const DEFAULT_OPTS = {
gasEstimateType: GAS_ESTIMATE_TYPES.FEE_MARKET,
gasFeeEstimates: {
low: '10',
medium: '20',
high: '30',
},
isGasEstimatesLoading: true,
};
const generateUseSelectorRouter =
(opts = DEFAULT_OPTS) =>
(selector) => {
if (selector === checkNetworkAndAccountSupports1559) {
return true;
}
if (selector === getGasEstimateType) {
return opts.gasEstimateType ?? DEFAULT_OPTS.gasEstimateType;
}
if (selector === getGasFeeEstimates) {
return opts.gasFeeEstimates ?? DEFAULT_OPTS.gasFeeEstimates;
}
if (selector === getIsGasEstimatesLoading) {
return opts.isGasEstimatesLoading ?? DEFAULT_OPTS.isGasEstimatesLoading;
}
return undefined;
};
jest.mock('../../../store/actions.js', () => ({
getGasFeeTimeEstimate: jest.fn().mockImplementation(() => Promise.resolve()),
}));
describe('Gas timing', () => {
beforeEach(() => {
const useI18nContext = sinon.stub(i18nhooks, 'useI18nContext');
useI18nContext.returns((key, variables) =>
getMessage('en', messages, key, variables),
);
jest.clearAllMocks();
useSelector.mockImplementation(generateUseSelectorRouter());
});
afterEach(() => {
sinon.restore();
});
it('renders nothing when gas is loading', () => {
useSelector.mockImplementation(
generateUseSelectorRouter({
isGasEstimatesLoading: true,
// Fails the networkAndAccountSupports1559 check
const nullGasState = {
metamask: {
gasFeeEstimates: null,
gasEstimateType: GAS_ESTIMATE_TYPES.FEE_MARKET,
}),
},
};
const mockStore = configureMockStore()(nullGasState);
const { container } = renderWithProvider(<GasTiming />, mockStore);
expect(container).toMatchSnapshot();
});
it('renders "very likely" when high estimate is chosen', async () => {
const mockStore = configureMockStore()(mockState);
const props = {
maxPriorityFeePerGas: '10',
};
const { queryByText } = renderWithProvider(
<GasTiming {...props} />,
mockStore,
);
const wrapper = shallow(<GasTiming />);
expect(wrapper.html()).toBeNull();
await waitFor(() => {
expect(queryByText(/Very likely in/u)).toBeInTheDocument();
});
});
it('renders "very likely" when high estimate is chosen', () => {
useSelector.mockImplementation(
generateUseSelectorRouter({
isGasEstimatesLoading: false,
gasFeeEstimates: MOCK_FEE_ESTIMATE,
gasEstimateType: GAS_ESTIMATE_TYPES.FEE_MARKET,
}),
it('renders "likely" when medium estimate is chosen', async () => {
const mockStore = configureMockStore()(mockState);
const props = {
maxPriorityFeePerGas: '8',
};
const { queryByText } = renderWithProvider(
<GasTiming {...props} />,
mockStore,
);
const wrapper = shallow(<GasTiming maxPriorityFeePerGas="10" />);
expect(wrapper.html()).toContain('gasTimingVeryPositive');
await waitFor(() => {
expect(queryByText(/Likely in/u)).toBeInTheDocument();
});
});
it('renders "likely" when medium estimate is chosen', () => {
useSelector.mockImplementation(
generateUseSelectorRouter({
isGasEstimatesLoading: false,
gasFeeEstimates: MOCK_FEE_ESTIMATE,
gasEstimateType: GAS_ESTIMATE_TYPES.FEE_MARKET,
}),
it('renders "maybe" when low estimate is chosen', async () => {
const mockStore = configureMockStore()(mockState);
const props = {
maxPriorityFeePerGas: '3',
};
const { queryByText } = renderWithProvider(
<GasTiming {...props} />,
mockStore,
);
const wrapper = shallow(<GasTiming maxPriorityFeePerGas="8" />);
expect(wrapper.html()).toContain('gasTimingPositive');
});
it('renders "maybe" when low estimate is chosen', () => {
useSelector.mockImplementation(
generateUseSelectorRouter({
isGasEstimatesLoading: false,
gasFeeEstimates: MOCK_FEE_ESTIMATE,
gasEstimateType: GAS_ESTIMATE_TYPES.FEE_MARKET,
}),
);
const wrapper = shallow(<GasTiming maxPriorityFeePerGas="3" />);
expect(wrapper.html()).toContain('gasTimingNegative');
await waitFor(() => {
expect(queryByText(/Maybe in/u)).toBeInTheDocument();
});
});
});

View File

@ -1,96 +1,106 @@
import React from 'react';
import sinon from 'sinon';
import { shallow } from 'enzyme';
import AccountDetailsModal from './account-details-modal.container';
import configureMockState from 'redux-mock-store';
import { fireEvent } 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 {
etherscanViewOn,
exportPrivateKey,
} from '../../../../../app/_locales/en/messages.json';
import AccountDetailsModal from '.';
const mockShowModal = jest.fn();
jest.mock('../../../../store/actions.js', () => {
return {
showModal: () => mockShowModal,
};
});
describe('Account Details Modal', () => {
let wrapper;
const mockStore = configureMockState([thunk])(mockState);
global.platform = { openTab: sinon.spy() };
global.platform = { openTab: jest.fn() };
const props = {
hideModal: sinon.spy(),
setAccountLabel: sinon.spy(),
showExportPrivateKeyModal: sinon.spy(),
network: 'test',
rpcPrefs: {},
selectedIdentity: {
address: '0xAddress',
name: 'Account 1',
},
keyrings: [
{
type: 'HD Key Tree',
accounts: ['0xAddress'],
},
],
identities: {
'0xAddress': {
address: '0xAddress',
name: 'Account 1',
},
},
accounts: {
address: '0xAddress',
lastSelected: 1637764711510,
name: 'Account 1',
balance: '0x543a5fb6caccf599',
},
blockExplorerLinkText: {
firstPart: 'addBlockExplorer',
secondPart: '',
},
};
it('should set account label when changing default account label', () => {
const { queryByTestId } = renderWithProvider(
<AccountDetailsModal />,
mockStore,
);
beforeEach(() => {
wrapper = shallow(<AccountDetailsModal.WrappedComponent {...props} />, {
context: {
t: (str) => str,
trackEvent: (e) => e,
},
});
const editButton = queryByTestId('editable-label-button');
expect(queryByTestId('editable-input')).not.toBeInTheDocument();
fireEvent.click(editButton);
expect(queryByTestId('editable-input')).toBeInTheDocument();
const editableInput = queryByTestId('editable-input');
const newAccountLabel = 'New Label';
fireEvent.change(editableInput, {
target: { value: newAccountLabel },
});
it('sets account label when changing default account label', () => {
const accountLabel = wrapper.find('.account-details-modal__name').first();
accountLabel.simulate('submit', 'New Label');
expect(props.setAccountLabel.calledOnce).toStrictEqual(true);
expect(props.setAccountLabel.getCall(0).args[1]).toStrictEqual('New Label');
expect(editableInput).toHaveAttribute('value', newAccountLabel);
});
it('opens new tab when view block explorer is clicked', () => {
wrapper.setProps({
blockExplorerLinkText: {
firstPart: 'viewOnEtherscan',
secondPart: 'blockExplorerAccountAction',
},
});
const modalButton = wrapper.find('.account-details-modal__button');
const etherscanLink = modalButton.first();
const { queryByText } = renderWithProvider(
<AccountDetailsModal />,
mockStore,
);
etherscanLink.simulate('click');
expect(global.platform.openTab.calledOnce).toStrictEqual(true);
const viewOnEtherscan = queryByText(etherscanViewOn.message);
fireEvent.click(viewOnEtherscan);
expect(global.platform.openTab).toHaveBeenCalled();
});
it('shows export private key modal when clicked', () => {
const modalButton = wrapper.find('.account-details-modal__button');
const etherscanLink = modalButton.last();
const { queryByText } = renderWithProvider(
<AccountDetailsModal />,
mockStore,
);
etherscanLink.simulate('click');
expect(props.showExportPrivateKeyModal.calledOnce).toStrictEqual(true);
const exportPrivButton = queryByText(exportPrivateKey.message);
fireEvent.click(exportPrivButton);
expect(mockShowModal).toHaveBeenCalled();
});
it('sets blockexplorerview text when block explorer url in rpcPrefs exists', () => {
const blockExplorerUrl = 'https://block.explorer';
wrapper.setProps({
rpcPrefs: { blockExplorerUrl },
blockExplorerLinkText: { firstPart: 'blockExplorerView' },
});
const modalButton = wrapper.find('.account-details-modal__button');
const blockExplorerLink = modalButton.first().shallow();
const customProviderMockState = {
...mockState,
metamask: {
...mockState.metamask,
frequentRpcListDetail: [
{
chainId: '0x99',
rpcPrefs: {
blockExplorerUrl,
},
},
],
provider: {
chainId: '0x99',
},
},
};
expect(blockExplorerLink.text()).toStrictEqual('blockExplorerView');
const customProviderMockStore = configureMockState([thunk])(
customProviderMockState,
);
const { queryByText } = renderWithProvider(
<AccountDetailsModal />,
customProviderMockStore,
);
expect(queryByText(/block.explorer/u)).toBeInTheDocument();
});
});

View File

@ -169,39 +169,6 @@ exports[`SignatureRequestOriginal should match snapshot 1`] = `
</div>
</div>
</div>
<div
class="confirm-approve-content__ledger-instruction-wrapper"
>
<div>
<div
class="confirm-detail-row"
>
<div
class="dialog dialog--message"
>
<div
class="ledger-live-dialog"
>
<h6
class="box box--margin-top-1 box--margin-bottom-1 box--flex-direction-row typography typography--h7 typography--weight-bold typography--style-normal typography--color-text-default"
>
Prior to clicking confirm:
</h6>
<h6
class="box box--margin-top-1 box--margin-bottom-1 box--flex-direction-row typography typography--h7 typography--weight-bold typography--style-normal typography--color-text-default"
>
- Plug in your Ledger device and select the Ethereum app
</h6>
<h6
class="box box--margin-top-1 box--margin-bottom-1 box--flex-direction-row typography typography--h7 typography--weight-bold typography--style-normal typography--color-text-default"
>
- Enable "smart contract data" or "blind signing" on your Ledger device
</h6>
</div>
</div>
</div>
</div>
</div>
<div
class="request-signature__footer"
>

View File

@ -49,6 +49,7 @@ class EditableLabel extends Component {
}
}}
onChange={(event) => this.setState({ value: event.target.value })}
data-testid="editable-input"
className={classnames('large-input', 'editable-label__input', {
'editable-label__input--error':
value === '' || accountsNames.includes(value),
@ -73,6 +74,7 @@ class EditableLabel extends Component {
<button
key={2}
className="editable-label__icon-button"
data-testid="editable-label-button"
onClick={() => this.setState({ isEditing: true })}
>
<i className="fas fa-pencil-alt editable-label__icon" />

View File

@ -137,7 +137,7 @@ describe('Selectors', () => {
it('returns accounts with balance, address, and name from identity and accounts in state', () => {
const accountsWithSendEther =
selectors.accountsWithSendEtherInfoSelector(mockState);
expect(accountsWithSendEther).toHaveLength(2);
expect(accountsWithSendEther).toHaveLength(4);
expect(accountsWithSendEther[0].balance).toStrictEqual('0x0');
expect(accountsWithSendEther[0].address).toStrictEqual(
'0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc',