diff --git a/.circleci/config.yml b/.circleci/config.yml index 40d550024..83ccb67ee 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -444,6 +444,9 @@ jobs: - store_artifacts: path: coverage destination: coverage + - store_artifacts: + path: jest-coverage + destination: jest-coverage - store_artifacts: path: test-artifacts destination: test-artifacts @@ -501,11 +504,15 @@ jobs: - run: name: test:coverage command: yarn test:coverage + - run: + name: test:coverage:jest + command: yarn test:coverage:jest - persist_to_workspace: root: . paths: - .nyc_output - coverage + - jest-coverage test-unit-global: executor: node-browsers steps: diff --git a/.eslintrc.js b/.eslintrc.js index 4598b30b9..a86e6af5f 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -26,6 +26,7 @@ module.exports = { 'test-*/**', 'docs/**', 'coverage/', + 'jest-coverage/', 'development/chromereload.js', 'app/vendor/**', 'test/e2e/send-eth-with-private-key-test/**', @@ -107,11 +108,16 @@ module.exports = { }, { files: ['**/*.test.js'], + excludedFiles: ['ui/app/pages/swaps/**/*.test.js'], extends: ['@metamask/eslint-config-mocha'], rules: { 'mocha/no-setup-in-describe': 'off', }, }, + { + files: ['ui/app/pages/swaps/**/*.test.js'], + extends: ['@metamask/eslint-config-jest'], + }, { files: [ 'development/**/*.js', @@ -135,6 +141,7 @@ module.exports = { 'test/lib/wait-until-called.js', 'test/env.js', 'test/setup.js', + 'jest.config.js', ], parserOptions: { sourceType: 'script', diff --git a/.gitignore b/.gitignore index d2f051b18..6333a39a1 100644 --- a/.gitignore +++ b/.gitignore @@ -29,6 +29,7 @@ app/.DS_Store storybook-build/ coverage/ +jest-coverage/ dist builds/ builds.zip diff --git a/jest.config.js b/jest.config.js new file mode 100644 index 000000000..4dc020afb --- /dev/null +++ b/jest.config.js @@ -0,0 +1,6 @@ +module.exports = { + restoreMocks: true, + coverageDirectory: 'jest-coverage/', + setupFiles: ['./test/setup.js', './test/env.js'], + testMatch: ['**/ui/app/pages/swaps/**/?(*.)+(test).js'], +}; diff --git a/package.json b/package.json index 9daf6b427..9e863d111 100644 --- a/package.json +++ b/package.json @@ -14,15 +14,17 @@ "benchmark:firefox": "SELENIUM_BROWSER=firefox node test/e2e/benchmark.js", "build:test": "yarn build test", "build:test:metrics": "SEGMENT_HOST='http://localhost:9090' SEGMENT_WRITE_KEY='FAKE' SEGMENT_LEGACY_WRITE_KEY='FAKE' yarn build test", - "test": "yarn test:unit && yarn lint", + "test": "yarn lint && yarn test:unit && yarn test:unit:jest", "dapp": "node development/static-server.js node_modules/@metamask/test-dapp/dist --port 8080", "dapp-chain": "GANACHE_ARGS='-b 2' concurrently -k -n ganache,dapp -p '[{time}][{name}]' 'yarn ganache:start' 'sleep 5 && yarn dapp'", "forwarder": "node ./development/static-server.js ./node_modules/@metamask/forwarder/dist/ --port 9010", "dapp-forwarder": "concurrently -k -n forwarder,dapp -p '[{time}][{name}]' 'yarn forwarder' 'yarn dapp'", "sendwithprivatedapp": "node development/static-server.js test/e2e/send-eth-with-private-key-test --port 8080", - "test:unit": "mocha --exit --require test/env.js --require test/setup.js --recursive './{ui,app,shared}/**/*.test.js'", + "test:unit": "mocha --exit --require test/env.js --require test/setup.js --ignore './ui/app/pages/swaps/**/*.test.js' --recursive './{ui,app,shared}/**/*.test.js'", "test:unit:global": "mocha --exit --require test/env.js --require test/setup.js --recursive test/unit-global/*.test.js", - "test:unit:lax": "mocha --exit --require test/env.js --require test/setup.js --ignore './app/scripts/controllers/permissions/*.test.js' --recursive './{ui,app,shared}/**/*.test.js'", + "test:unit:jest": "jest", + "test:unit:jest:ci": "jest --maxWorkers=2", + "test:unit:lax": "mocha --exit --require test/env.js --require test/setup.js --ignore './app/scripts/controllers/permissions/*.test.js' --ignore './ui/app/pages/swaps/**/*.test.js' --recursive './{ui,app,shared}/**/*.test.js'", "test:unit:strict": "mocha --exit --require test/env.js --require test/setup.js --recursive './app/scripts/controllers/permissions/*.test.js'", "test:unit:path": "mocha --exit --require test/env.js --require test/setup.js --recursive", "test:e2e:chrome": "SELENIUM_BROWSER=chrome test/e2e/run-all.sh", @@ -30,6 +32,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/pages/swaps/**", "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", @@ -201,6 +204,7 @@ "@babel/register": "^7.5.5", "@lavamoat/allow-scripts": "^1.0.4", "@metamask/eslint-config": "^6.0.0", + "@metamask/eslint-config-jest": "^6.0.0", "@metamask/eslint-config-mocha": "^6.0.0", "@metamask/eslint-config-nodejs": "^6.0.0", "@metamask/forwarder": "^1.1.0", @@ -233,6 +237,7 @@ "eslint": "^7.23.0", "eslint-config-prettier": "^8.1.0", "eslint-plugin-import": "^2.22.1", + "eslint-plugin-jest": "^24.3.4", "eslint-plugin-mocha": "^8.1.0", "eslint-plugin-node": "^11.1.0", "eslint-plugin-prettier": "^3.3.1", diff --git a/ui/app/pages/swaps/swaps.util.test.js b/ui/app/pages/swaps/swaps.util.test.js index 8d61b4e1f..6e683b532 100644 --- a/ui/app/pages/swaps/swaps.util.test.js +++ b/ui/app/pages/swaps/swaps.util.test.js @@ -1,11 +1,6 @@ -import { strict as assert } from 'assert'; -import proxyquire from 'proxyquire'; +import nock from 'nock'; import { MAINNET_CHAIN_ID } from '../../../../shared/constants/network'; import { - TRADES_BASE_PROD_URL, - TOKENS_BASE_PROD_URL, - AGGREGATOR_METADATA_BASE_PROD_URL, - TOP_ASSET_BASE_PROD_URL, TOKENS, EXPECTED_TOKENS_RESULT, MOCK_TRADE_RESPONSE_2, @@ -13,42 +8,24 @@ import { TOP_ASSETS, } from './swaps-util-test-constants'; -const swapsUtils = proxyquire('./swaps.util.js', { - '../../helpers/utils/fetch-with-cache': { - default: (url, fetchObject) => { - assert.strictEqual(fetchObject.method, 'GET'); - if (url.match(TRADES_BASE_PROD_URL)) { - assert.strictEqual( - url, - 'https://api.metaswap.codefi.network/trades?destinationToken=0xE41d2489571d322189246DaFA5ebDe1F4699F498&sourceToken=0x617b3f8050a0BD94b6b1da02B4384eE5B4DF13F4&sourceAmount=2000000000000000000000000000000000000&slippage=3&timeout=10000&walletAddress=0xmockAddress', - ); - return Promise.resolve(MOCK_TRADE_RESPONSE_2); - } - if (url.match(TOKENS_BASE_PROD_URL)) { - assert.strictEqual(url, TOKENS_BASE_PROD_URL); - return Promise.resolve(TOKENS); - } - if (url.match(AGGREGATOR_METADATA_BASE_PROD_URL)) { - assert.strictEqual(url, AGGREGATOR_METADATA_BASE_PROD_URL); - return Promise.resolve(AGGREGATOR_METADATA); - } - if (url.match(TOP_ASSET_BASE_PROD_URL)) { - assert.strictEqual(url, TOP_ASSET_BASE_PROD_URL); - return Promise.resolve(TOP_ASSETS); - } - return Promise.resolve(); - }, - }, -}); -const { +import { fetchTradesInfo, fetchTokens, fetchAggregatorMetadata, fetchTopAssets, -} = swapsUtils; +} from './swaps.util'; -describe('Swaps Util', function () { - describe('fetchTradesInfo', function () { +jest.mock('../../../lib/storage-helpers.js', () => ({ + getStorageItem: jest.fn(), + setStorageItem: jest.fn(), +})); + +describe('Swaps Util', () => { + afterEach(() => { + nock.cleanAll(); + }); + + describe('fetchTradesInfo', () => { const expectedResult1 = { zeroEx: { trade: { @@ -90,7 +67,12 @@ describe('Swaps Util', function () { sourceAmount: '20000000000000000', }, }; - it('should fetch trade info on prod', async function () { + it('should fetch trade info on prod', async () => { + nock('https://api.metaswap.codefi.network') + .get('/trades') + .query(true) + .reply(200, MOCK_TRADE_RESPONSE_2); + const result = await fetchTradesInfo( { TOKENS, @@ -106,35 +88,53 @@ describe('Swaps Util', function () { }, { chainId: MAINNET_CHAIN_ID }, ); - assert.deepStrictEqual(result, expectedResult2); + expect(result).toStrictEqual(expectedResult2); }); }); - describe('fetchTokens', function () { - it('should fetch tokens', async function () { + describe('fetchTokens', () => { + beforeEach(() => { + nock('https://api.metaswap.codefi.network') + .get('/tokens') + .reply(200, TOKENS); + }); + + it('should fetch tokens', async () => { const result = await fetchTokens(MAINNET_CHAIN_ID); - assert.deepStrictEqual(result, EXPECTED_TOKENS_RESULT); + expect(result).toStrictEqual(EXPECTED_TOKENS_RESULT); }); - it('should fetch tokens on prod', async function () { + it('should fetch tokens on prod', async () => { const result = await fetchTokens(MAINNET_CHAIN_ID); - assert.deepStrictEqual(result, EXPECTED_TOKENS_RESULT); + expect(result).toStrictEqual(EXPECTED_TOKENS_RESULT); }); }); - describe('fetchAggregatorMetadata', function () { - it('should fetch aggregator metadata', async function () { - const result = await fetchAggregatorMetadata(MAINNET_CHAIN_ID); - assert.deepStrictEqual(result, AGGREGATOR_METADATA); + describe('fetchAggregatorMetadata', () => { + beforeEach(() => { + nock('https://api.metaswap.codefi.network') + .get('/aggregatorMetadata') + .reply(200, AGGREGATOR_METADATA); }); - it('should fetch aggregator metadata on prod', async function () { + it('should fetch aggregator metadata', async () => { const result = await fetchAggregatorMetadata(MAINNET_CHAIN_ID); - assert.deepStrictEqual(result, AGGREGATOR_METADATA); + expect(result).toStrictEqual(AGGREGATOR_METADATA); + }); + + it('should fetch aggregator metadata on prod', async () => { + const result = await fetchAggregatorMetadata(MAINNET_CHAIN_ID); + expect(result).toStrictEqual(AGGREGATOR_METADATA); }); }); - describe('fetchTopAssets', function () { + describe('fetchTopAssets', () => { + beforeEach(() => { + nock('https://api.metaswap.codefi.network') + .get('/topAssets') + .reply(200, TOP_ASSETS); + }); + const expectedResult = { '0x514910771af9ca656af840dff83e8264ecf986ca': { index: '0', @@ -152,14 +152,14 @@ describe('Swaps Util', function () { index: '4', }, }; - it('should fetch top assets', async function () { + it('should fetch top assets', async () => { const result = await fetchTopAssets(MAINNET_CHAIN_ID); - assert.deepStrictEqual(result, expectedResult); + expect(result).toStrictEqual(expectedResult); }); - it('should fetch top assets on prod', async function () { + it('should fetch top assets on prod', async () => { const result = await fetchTopAssets(MAINNET_CHAIN_ID); - assert.deepStrictEqual(result, expectedResult); + expect(result).toStrictEqual(expectedResult); }); }); }); diff --git a/ui/app/pages/swaps/view-quote/view-quote-price-difference.test.js b/ui/app/pages/swaps/view-quote/view-quote-price-difference.test.js index 019042bff..c00322959 100644 --- a/ui/app/pages/swaps/view-quote/view-quote-price-difference.test.js +++ b/ui/app/pages/swaps/view-quote/view-quote-price-difference.test.js @@ -1,4 +1,3 @@ -import assert from 'assert'; import React from 'react'; import { shallow } from 'enzyme'; import { Provider } from 'react-redux'; @@ -6,7 +5,7 @@ import configureMockStore from 'redux-mock-store'; import { NETWORK_TYPE_RPC } from '../../../../../shared/constants/network'; import ViewQuotePriceDifference from './view-quote-price-difference'; -describe('View Price Quote Difference', function () { +describe('View Price Quote Difference', () => { const t = (key) => `translate ${key}`; const state = { @@ -98,21 +97,21 @@ describe('View Price Quote Difference', function () { ); } - afterEach(function () { + afterEach(() => { component.unmount(); }); - it('does not render when there is no quote', function () { + it('does not render when there is no quote', () => { const props = { ...DEFAULT_PROPS, usedQuote: null }; renderComponent(props); const wrappingDiv = component.find( '.view-quote__price-difference-warning-wrapper', ); - assert.strictEqual(wrappingDiv.length, 0); + expect(wrappingDiv).toHaveLength(0); }); - it('does not render when the item is in the low bucket', function () { + it('does not render when the item is in the low bucket', () => { const props = { ...DEFAULT_PROPS }; props.usedQuote.priceSlippage.bucket = 'low'; @@ -120,31 +119,31 @@ describe('View Price Quote Difference', function () { const wrappingDiv = component.find( '.view-quote__price-difference-warning-wrapper', ); - assert.strictEqual(wrappingDiv.length, 0); + expect(wrappingDiv).toHaveLength(0); }); - it('displays an error when in medium bucket', function () { + it('displays an error when in medium bucket', () => { const props = { ...DEFAULT_PROPS }; props.usedQuote.priceSlippage.bucket = 'medium'; renderComponent(props); - assert.strictEqual(component.html().includes('medium'), true); + expect(component.html()).toContain('medium'); }); - it('displays an error when in high bucket', function () { + it('displays an error when in high bucket', () => { const props = { ...DEFAULT_PROPS }; props.usedQuote.priceSlippage.bucket = 'high'; renderComponent(props); - assert.strictEqual(component.html().includes('high'), true); + expect(component.html()).toContain('high'); }); - it('displays a fiat error when calculationError is present', function () { + it('displays a fiat error when calculationError is present', () => { const props = { ...DEFAULT_PROPS, priceSlippageUnknownFiatValue: true }; props.usedQuote.priceSlippage.calculationError = 'Could not determine price.'; renderComponent(props); - assert.strictEqual(component.html().includes('fiat-error'), true); + expect(component.html()).toContain('fiat-error'); }); }); diff --git a/yarn.lock b/yarn.lock index 95be3d2d0..c3c720956 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2689,6 +2689,11 @@ web3 "^0.20.7" web3-provider-engine "^16.0.1" +"@metamask/eslint-config-jest@^6.0.0": + version "6.0.0" + resolved "https://registry.yarnpkg.com/@metamask/eslint-config-jest/-/eslint-config-jest-6.0.0.tgz#9e10cfbca31236afd7be2058be70365084e540d6" + integrity sha512-C0sXmyp5Hnp5IHVYXaW2TJAo/E9UiS192CwyUcw2qU1Ck7lj4z/wHdgROaH5F6rInqBO3afkDaqnArqvoxvO5Q== + "@metamask/eslint-config-mocha@^6.0.0": version "6.0.0" resolved "https://registry.yarnpkg.com/@metamask/eslint-config-mocha/-/eslint-config-mocha-6.0.0.tgz#407fc07d4bdfbc79b64989fa9a56a5a671aa4721" @@ -3708,6 +3713,11 @@ dependencies: "@types/istanbul-lib-report" "*" +"@types/json-schema@^7.0.3": + version "7.0.7" + resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.7.tgz#98a993516c859eb0d5c4c8f098317a9ea68db9ad" + integrity sha512-cxWFQVseBm6O9Gbw1IWb8r6OS4OhSt3hPZLkFApLjM8TEXROBuQGLAH2i2gZpcXdLBIrpXuTDhH7Vbm1iXmNGA== + "@types/json-schema@^7.0.5", "@types/json-schema@^7.0.6": version "7.0.6" resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.6.tgz#f4c7ec43e81b319a9815115031709f26987891f0" @@ -3959,6 +3969,52 @@ dependencies: "@types/yargs-parser" "*" +"@typescript-eslint/experimental-utils@^4.0.1": + version "4.21.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-4.21.0.tgz#0b0bb7c15d379140a660c003bdbafa71ae9134b6" + integrity sha512-cEbgosW/tUFvKmkg3cU7LBoZhvUs+ZPVM9alb25XvR0dal4qHL3SiUqHNrzoWSxaXA9gsifrYrS1xdDV6w/gIA== + dependencies: + "@types/json-schema" "^7.0.3" + "@typescript-eslint/scope-manager" "4.21.0" + "@typescript-eslint/types" "4.21.0" + "@typescript-eslint/typescript-estree" "4.21.0" + eslint-scope "^5.0.0" + eslint-utils "^2.0.0" + +"@typescript-eslint/scope-manager@4.21.0": + version "4.21.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-4.21.0.tgz#c81b661c4b8af1ec0c010d847a8f9ab76ab95b4d" + integrity sha512-kfOjF0w1Ix7+a5T1knOw00f7uAP9Gx44+OEsNQi0PvvTPLYeXJlsCJ4tYnDj5PQEYfpcgOH5yBlw7K+UEI9Agw== + dependencies: + "@typescript-eslint/types" "4.21.0" + "@typescript-eslint/visitor-keys" "4.21.0" + +"@typescript-eslint/types@4.21.0": + version "4.21.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-4.21.0.tgz#abdc3463bda5d31156984fa5bc316789c960edef" + integrity sha512-+OQaupjGVVc8iXbt6M1oZMwyKQNehAfLYJJ3SdvnofK2qcjfor9pEM62rVjBknhowTkh+2HF+/KdRAc/wGBN2w== + +"@typescript-eslint/typescript-estree@4.21.0": + version "4.21.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-4.21.0.tgz#3817bd91857beeaeff90f69f1f112ea58d350b0a" + integrity sha512-ZD3M7yLaVGVYLw4nkkoGKumb7Rog7QID9YOWobFDMQKNl+vPxqVIW/uDk+MDeGc+OHcoG2nJ2HphwiPNajKw3w== + dependencies: + "@typescript-eslint/types" "4.21.0" + "@typescript-eslint/visitor-keys" "4.21.0" + debug "^4.1.1" + globby "^11.0.1" + is-glob "^4.0.1" + semver "^7.3.2" + tsutils "^3.17.1" + +"@typescript-eslint/visitor-keys@4.21.0": + version "4.21.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-4.21.0.tgz#990a9acdc124331f5863c2cf21c88ba65233cd8d" + integrity sha512-dH22dROWGi5Z6p+Igc8bLVLmwy7vEe8r+8c+raPQU0LxgogPUrRAtRGtvBWmlr9waTu3n+QLt/qrS/hWzk1x5w== + dependencies: + "@typescript-eslint/types" "4.21.0" + eslint-visitor-keys "^2.0.0" + "@webassemblyjs/ast@1.9.0": version "1.9.0" resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.9.0.tgz#bd850604b4042459a5a41cd7d338cbed695ed964" @@ -9957,6 +10013,13 @@ eslint-plugin-import@^2.22.1: resolve "^1.17.0" tsconfig-paths "^3.9.0" +eslint-plugin-jest@^24.3.4: + version "24.3.4" + resolved "https://registry.yarnpkg.com/eslint-plugin-jest/-/eslint-plugin-jest-24.3.4.tgz#6d90c3554de0302e879603dd6405474c98849f19" + integrity sha512-3n5oY1+fictanuFkTWPwSlehugBTAgwLnYLFsCllzE3Pl1BwywHl5fL0HFxmMjoQY8xhUDk8uAWc3S4JOHGh3A== + dependencies: + "@typescript-eslint/experimental-utils" "^4.0.1" + eslint-plugin-mocha@^8.1.0: version "8.1.0" resolved "https://registry.yarnpkg.com/eslint-plugin-mocha/-/eslint-plugin-mocha-8.1.0.tgz#b9aebbede46a808e46e622c8fd99d2a2f353e725" @@ -10027,7 +10090,7 @@ eslint-scope@^4.0.3: esrecurse "^4.1.0" estraverse "^4.1.1" -eslint-scope@^5.1.0, eslint-scope@^5.1.1: +eslint-scope@^5.0.0, eslint-scope@^5.1.0, eslint-scope@^5.1.1: version "5.1.1" resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.1.1.tgz#e786e59a66cb92b3f6c1fb0d508aab174848f48c" integrity sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw== @@ -25434,7 +25497,7 @@ tsconfig-paths@^3.9.0: minimist "^1.2.0" strip-bom "^3.0.0" -tslib@^1.10.0, tslib@^1.9.0, tslib@^1.9.3: +tslib@^1.10.0, tslib@^1.8.1, tslib@^1.9.0, tslib@^1.9.3: version "1.14.1" resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== @@ -25449,6 +25512,13 @@ tsscmp@1.0.6: resolved "https://registry.yarnpkg.com/tsscmp/-/tsscmp-1.0.6.tgz#85b99583ac3589ec4bfef825b5000aa911d605eb" integrity sha512-LxhtAkPDTkVCMQjt2h6eBVY28KCjikZqZfMcC15YBeNjkgUpdCfBu5HoiOTDu86v6smE8yOjyEktJ8hlbANHQA== +tsutils@^3.17.1: + version "3.21.0" + resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.21.0.tgz#b48717d394cea6c1e096983eed58e9d61715b623" + integrity sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA== + dependencies: + tslib "^1.8.1" + ttest@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/ttest/-/ttest-2.1.1.tgz#ee0c9118fff6580a5c8d6cfe69845219e075277f"