Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Increase Jest unit test coverage for the Swaps feature to ~43% #10934

Merged
merged 12 commits into from Apr 27, 2021
1 change: 1 addition & 0 deletions .eslintrc.js
Expand Up @@ -130,6 +130,7 @@ module.exports = {
rules: {
'jest/no-restricted-matchers': 'off',
'import/unambiguous': 'off',
'import/named': 'off',
Copy link
Contributor

Choose a reason for hiding this comment

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

Why did this get turned off?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Great question! That's because I used module.exports in the link below: https://github.com/MetaMask/metamask-extension/pull/10934/files#diff-8fd96322fc664541ebc0326e3c5963e55db825809abb977c6ffad8be7555cbd1R7
After which I started getting these errors:
image

I will give it little more time to use export instead of module.exports when re-exporting imported things. Then we hopefully wouldn't need to turn import/named off for tests.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

So, I've changed it to export only:

export { createSwapsMockStore } from './mock-store';
export { renderWithProvider } from './rendering';
export { setBackgroundConnection } from './background';
export * as MOCKS from './mocks';
export * as CONSTANTS from './constants';

But even while tests are green, I'm getting the same ESLint issue when re-exporting:
image

That's why I would prefer to keep 'import/named': 'off',, just for tests.

Copy link
Contributor

Choose a reason for hiding this comment

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

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Nice find!

},
},
{
Expand Down
10 changes: 6 additions & 4 deletions jest.config.js
@@ -1,12 +1,14 @@
module.exports = {
restoreMocks: true,
coverageDirectory: 'jest-coverage/',
collectCoverageFrom: ['<rootDir>/ui/app/**/swaps/**'],
coveragePathIgnorePatterns: ['.stories.js', '.snap'],
coverageThreshold: {
global: {
branches: 21.24,
functions: 23.01,
lines: 27.19,
statements: 27.07,
branches: 32.75,
functions: 43.31,
lines: 43.12,
statements: 43.67,
},
},
setupFiles: ['./test/setup.js', './test/env.js'],
Expand Down
2 changes: 1 addition & 1 deletion package.json
Expand Up @@ -33,7 +33,7 @@
"test:e2e:firefox": "SELENIUM_BROWSER=firefox test/e2e/run-all.sh",
"test:e2e:firefox:metrics": "SELENIUM_BROWSER=firefox mocha test/e2e/metrics.spec.js",
"test:coverage": "nyc --silent --check-coverage yarn test:unit:strict && nyc --silent --no-clean yarn test:unit:lax && nyc report --reporter=text --reporter=html",
"test:coverage:jest": "jest --coverage --maxWorkers=2 --collectCoverageFrom=**/ui/app/**/swaps/**",
"test:coverage:jest": "jest --coverage --maxWorkers=2",
Copy link
Contributor

Choose a reason for hiding this comment

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

why the removal of the collectCoverageFrom flag

Copy link
Contributor Author

Choose a reason for hiding this comment

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

"test:coverage:strict": "nyc --check-coverage yarn test:unit:strict",
"test:coverage:path": "nyc --check-coverage yarn test:unit:path",
"ganache:start": "./development/run-ganache.sh",
Expand Down
5 changes: 5 additions & 0 deletions test/jest/background.js
@@ -0,0 +1,5 @@
import * as actions from '../../ui/app/store/actions';

export const setBackgroundConnection = (backgroundConnection = {}) => {
actions._setBackgroundConnection(backgroundConnection);
};
1 change: 1 addition & 0 deletions test/jest/constants.js
@@ -0,0 +1 @@
export const METASWAP_BASE_URL = 'https://api.metaswap.codefi.network';
9 changes: 5 additions & 4 deletions test/jest/index.js
@@ -1,4 +1,5 @@
import { createSwapsMockStore } from './mock-store';
import { renderWithProvider } from './rendering';

export { createSwapsMockStore, renderWithProvider };
export { createSwapsMockStore } from './mock-store';
export { renderWithProvider } from './rendering';
export { setBackgroundConnection } from './background';
export * as MOCKS from './mocks';
export * as CONSTANTS from './constants';
38 changes: 24 additions & 14 deletions test/jest/mock-store.js
Expand Up @@ -6,6 +6,7 @@ export const createSwapsMockStore = () => {
customGas: {
fallBackPrice: 5,
},
fromToken: 'ETH',
},
metamask: {
provider: {
Expand All @@ -14,6 +15,15 @@ export const createSwapsMockStore = () => {
cachedBalances: {
[MAINNET_CHAIN_ID]: 5,
},
preferences: {
showFiatInTestnets: true,
},
currentCurrency: 'ETH',
conversionRate: 1,
contractExchangeRates: {
'0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48': 2,
'0x1111111111111111111111111111111111111111': 0.1,
},
accounts: {
'0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc': {
address: '0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc',
Expand All @@ -26,6 +36,20 @@ export const createSwapsMockStore = () => {
},
selectedAddress: '0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc',
frequentRpcListDetail: [],
tokens: [
{
erc20: true,
symbol: 'BAT',
decimals: 18,
address: '0x0D8775F648430679A709E98d2b0Cb6250d2887EF',
},
{
erc20: true,
symbol: 'USDT',
decimals: 6,
address: '0xdAC17F958D2ee523a2206206994597C13D831ec7',
},
],
swapsState: {
quotes: {},
fetchParams: {
Expand All @@ -38,20 +62,6 @@ export const createSwapsMockStore = () => {
},
},
},
tokens: [
{
erc20: true,
symbol: 'BAT',
decimals: 18,
address: '0x0D8775F648430679A709E98d2b0Cb6250d2887EF',
},
{
erc20: true,
symbol: 'USDT',
decimals: 6,
address: '0xdAC17F958D2ee523a2206206994597C13D831ec7',
},
],
tradeTxId: null,
approveTxId: null,
quotesLastFetched: null,
Expand Down
61 changes: 61 additions & 0 deletions test/jest/mocks.js
@@ -0,0 +1,61 @@
export const TOP_ASSETS_GET_RESPONSE = [
{
symbol: 'LINK',
address: '0x514910771af9ca656af840dff83e8264ecf986ca',
},
{
symbol: 'UMA',
address: '0x04fa0d235c4abf4bcf4787af4cf447de572ef828',
},
{
symbol: 'YFI',
address: '0x0bc529c00c6401aef6d220be8c6ea1667f6ad93e',
},
{
symbol: 'LEND',
address: '0x80fb784b7ed66730e8b1dbd9820afd29931aab03',
},
{
symbol: 'SNX',
address: '0xc011a73ee8576fb46f5e1c5751ca3b9fe0af2a6f',
},
];

export const REFRESH_TIME_GET_RESPONSE = {
seconds: 3600,
};

export const AGGREGATOR_METADATA_GET_RESPONSE = {};

export const GAS_PRICES_GET_RESPONSE = {
SafeGasPrice: '10',
ProposeGasPrice: '20',
FastGasPrice: '30',
};

export const TOKENS_GET_RESPONSE = [
{
erc20: true,
symbol: 'META',
decimals: 18,
address: '0x617b3f8050a0BD94b6b1da02B4384eE5B4DF13F4',
},
{
erc20: true,
symbol: 'ZRX',
decimals: 18,
address: '0xE41d2489571d322189246DaFA5ebDe1F4699F498',
},
{
erc20: true,
symbol: 'AST',
decimals: 4,
address: '0x27054b13b1B798B345b591a4d22e6562d47eA75a',
},
{
erc20: true,
symbol: 'BAT',
decimals: 18,
address: '0x0D8775F648430679A709E98d2b0Cb6250d2887EF',
},
];
5 changes: 5 additions & 0 deletions ui/app/__mocks__/react-router-dom.js
Expand Up @@ -3,4 +3,9 @@ const originalModule = jest.requireActual('react-router-dom');
module.exports = {
...originalModule,
useHistory: jest.fn(),
useLocation: jest.fn(() => {
Copy link
Contributor

Choose a reason for hiding this comment

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

presumably when we expand test coverage in our other areas of the repo we'll need to mock this out in those files to override the pathname?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

That's right. My assumption is that most of the tests will be fine with this path, but if they need to use a different one, they can just do this:

jest.mock('react-router-dom', () => {
  const originalModule = jest.requireActual('react-router-dom');
  return {
    ...originalModule,
    useHistory: jest.fn(),
    useLocation: jest.fn(() => {
      return {
        pathname: '/feature-z/page-1',
      };
    }),
  }
});

return {
pathname: '/swaps/build-quote',
};
}),
};
31 changes: 31 additions & 0 deletions ui/app/pages/swaps/__snapshots__/index.test.js.snap
@@ -0,0 +1,31 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`Swap renders the component with initial props 1`] = `
<div>
<div
class="swaps"
>
<div
class="swaps__container"
>
<div
class="swaps__header"
>
<div
class="swaps__title"
>
Swap
</div>
<div
class="swaps__header-cancel"
>
Cancel
</div>
</div>
<div
class="swaps__content"
/>
</div>
</div>
</div>
`;
@@ -0,0 +1,18 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`QuotesTimeoutIcon renders the component 1`] = `
<div>
<svg
fill="none"
height="44"
viewBox="0 0 44 44"
width="44"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M22 0C9.96768 0 0.178406 9.78928 0.178406 21.8216C0.178406 33.8539 9.96768 43.6432 22 43.6432C34.0323 43.6432 43.8216 33.8539 43.8216 21.8216C43.8216 9.78929 34.0323 0 22 0ZM22 3.27324C32.2633 3.27324 40.5484 11.5583 40.5484 21.8216C40.5484 32.0849 32.2633 40.3699 22 40.3699C11.7367 40.3699 3.45164 32.0849 3.45164 21.8216C3.45164 11.5583 11.7367 3.27324 22 3.27324ZM22 6.00094C21.0961 6.00094 20.3634 6.73371 20.3634 7.63756V21.8216C20.3634 22.4269 20.6932 22.9534 21.1817 23.2366L32.5187 29.783C33.3014 30.235 34.3001 29.9692 34.752 29.1864C35.2039 28.4036 34.938 27.405 34.1553 26.953L23.6366 20.8839V7.63756C23.6366 6.73371 22.9039 6.00094 22 6.00094Z"
fill="#037DD6"
/>
</svg>
</div>
`;
@@ -0,0 +1,18 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`SwapFailureIcon renders the component 1`] = `
<div>
<svg
fill="none"
height="39"
viewBox="0 0 45 39"
width="45"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M22.203 0.424438L0.285706 38.2816H44.1203L22.203 0.424438ZM22.203 8.39436L37.2064 34.2966H7.19961L22.203 8.39436ZM20.2105 16.3643V24.3342H24.1955V16.3643H20.2105ZM20.2105 28.3192V32.3041H24.1955V28.3192"
fill="#D73A49"
/>
</svg>
</div>
`;
@@ -0,0 +1,18 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`SwapSuccessIcon renders the component 1`] = `
<div>
<svg
fill="none"
height="38"
viewBox="0 0 38 38"
width="38"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M34.1429 19C34.1429 23.0161 32.5474 26.8678 29.7076 29.7076C26.8678 32.5474 23.0161 34.1428 19 34.1428C14.9839 34.1428 11.1322 32.5474 8.29238 29.7076C5.45254 26.8678 3.85714 23.0161 3.85714 19C3.85714 14.9838 5.45254 11.1322 8.29238 8.29237C11.1322 5.45253 14.9839 3.85713 19 3.85713C20.4386 3.85713 21.8393 4.06534 23.1643 4.44391L26.1361 1.47213C23.9404 0.563554 21.5364 0.0714111 19 0.0714111C16.5143 0.0714111 14.0529 0.561013 11.7563 1.51226C9.45983 2.46351 7.37316 3.85778 5.61548 5.61546C2.06568 9.16526 0.0714264 13.9798 0.0714264 19C0.0714264 24.0201 2.06568 28.8347 5.61548 32.3845C7.37316 34.1422 9.45983 35.5364 11.7563 36.4877C14.0529 37.4389 16.5143 37.9286 19 37.9286C24.0202 37.9286 28.8347 35.9343 32.3845 32.3845C35.9343 28.8347 37.9286 24.0201 37.9286 19H34.1429ZM11.2582 15.3657L8.58928 18.0536L17.1071 26.5714L36.0357 7.64284L33.3668 4.95498L17.1071 21.2146L11.2582 15.3657Z"
fill="#28A745"
/>
</svg>
</div>
`;
11 changes: 11 additions & 0 deletions ui/app/pages/swaps/awaiting-swap/quotes-timeout-icon.test.js
@@ -0,0 +1,11 @@
import React from 'react';

import { renderWithProvider } from '../../../../../test/jest';
import QuotesTimeoutIcon from './quotes-timeout-icon';

describe('QuotesTimeoutIcon', () => {
it('renders the component', () => {
const { container } = renderWithProvider(<QuotesTimeoutIcon />);
expect(container).toMatchSnapshot();
});
});
11 changes: 11 additions & 0 deletions ui/app/pages/swaps/awaiting-swap/swap-failure-icon.test.js
@@ -0,0 +1,11 @@
import React from 'react';

import { renderWithProvider } from '../../../../../test/jest';
import SwapFailureIcon from './swap-failure-icon';

describe('SwapFailureIcon', () => {
it('renders the component', () => {
const { container } = renderWithProvider(<SwapFailureIcon />);
expect(container).toMatchSnapshot();
});
});
11 changes: 11 additions & 0 deletions ui/app/pages/swaps/awaiting-swap/swap-success-icon.test.js
@@ -0,0 +1,11 @@
import React from 'react';

import { renderWithProvider } from '../../../../../test/jest';
import SwapSuccessIcon from './swap-success-icon';

describe('SwapSuccessIcon', () => {
it('renders the component', () => {
const { container } = renderWithProvider(<SwapSuccessIcon />);
expect(container).toMatchSnapshot();
});
});
@@ -0,0 +1,21 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`ViewOnEtherScanLink renders the component with a custom block explorer link 1`] = `
<div>
<div
class="awaiting-swap__view-on-etherscan awaiting-swap__view-on-etherscan--visible"
>
View at custom-blockchain.explorer
</div>
</div>
`;

exports[`ViewOnEtherScanLink renders the component with initial props 1`] = `
<div>
<div
class="awaiting-swap__view-on-etherscan awaiting-swap__view-on-etherscan--visible"
>
View on Etherscan
</div>
</div>
`;
@@ -0,0 +1,37 @@
import React from 'react';

import { renderWithProvider } from '../../../../../../test/jest';
import ViewOnEtherScanLink from '.';

const createProps = (customProps = {}) => {
return {
txHash:
'0x58e5a0fc7fbc849eddc100d44e86276168a8c7baaa5604e44ba6f5eb8ba1b7eb',
blockExplorerUrl: 'https://block.explorer',
isCustomBlockExplorerUrl: false,
...customProps,
};
};

describe('ViewOnEtherScanLink', () => {
it('renders the component with initial props', () => {
const { container, getByText } = renderWithProvider(
<ViewOnEtherScanLink {...createProps()} />,
);
expect(getByText('View on Etherscan')).toBeInTheDocument();
expect(container).toMatchSnapshot();
});

it('renders the component with a custom block explorer link', () => {
const { container, getByText } = renderWithProvider(
<ViewOnEtherScanLink
{...createProps({
blockExplorerUrl: 'https://custom-blockchain.explorer',
isCustomBlockExplorerUrl: true,
})}
/>,
);
expect(getByText('View at custom-blockchain.explorer')).toBeInTheDocument();
Copy link
Contributor

Choose a reason for hiding this comment

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

Is it possible for us to test what the URL will go to here instead of just checking the link text? It'd help to ensure that changes made to the URL but not the text don't pass through our test cases.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Great point, will add an assertion for a URL.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I've checked the code and we don't render a link, but open a new tab via an onClick event on a div element:
onClick={() => global.platform.openTab({ url: blockExplorerUrl })}

I will be testing events in following PRs, so I would prefer to do it there.

expect(container).toMatchSnapshot();
});
});