diff --git a/.babelrc b/.babelrc deleted file mode 100644 index 0c21a441..00000000 --- a/.babelrc +++ /dev/null @@ -1,48 +0,0 @@ -{ - "presets": [ - "@babel/react", - "@babel/preset-flow", - [ - "@babel/env", - { - "forceAllTransforms": true - } - ] - ], - "plugins": [ - "react-hot-loader/babel", - "@babel/plugin-syntax-dynamic-import", - "@babel/plugin-transform-member-expression-literals", - "@babel/plugin-transform-property-literals", - "@babel/plugin-syntax-import-meta", - "@babel/plugin-proposal-class-properties", - "@babel/plugin-proposal-json-strings", - [ - "@babel/plugin-proposal-decorators", - { - "legacy": true - } - ], - "@babel/plugin-proposal-function-sent", - "@babel/plugin-proposal-export-namespace-from", - "@babel/plugin-proposal-numeric-separator", - "@babel/plugin-proposal-throw-expressions", - "@babel/plugin-proposal-export-default-from", - "@babel/plugin-proposal-logical-assignment-operators", - "@babel/plugin-proposal-optional-chaining", - [ - "@babel/plugin-proposal-pipeline-operator", - { - "proposal": "minimal" - } - ], - "@babel/plugin-proposal-nullish-coalescing-operator", - "@babel/plugin-proposal-do-expressions", - "@babel/plugin-proposal-function-bind" - ], - "env": { - "test": { - "plugins": ["dynamic-import-node"] - } - } -} diff --git a/babel.config.js b/babel.config.js new file mode 100644 index 00000000..a5da135e --- /dev/null +++ b/babel.config.js @@ -0,0 +1,49 @@ +// @flow +module.exports = { + presets: [ + '@babel/react', + '@babel/preset-flow', + [ + '@babel/env', + { + forceAllTransforms: true, + }, + ], + ], + plugins: [ + 'react-hot-loader/babel', + '@babel/plugin-syntax-dynamic-import', + '@babel/plugin-transform-member-expression-literals', + '@babel/plugin-transform-property-literals', + '@babel/plugin-syntax-import-meta', + '@babel/plugin-proposal-class-properties', + '@babel/plugin-proposal-json-strings', + [ + '@babel/plugin-proposal-decorators', + { + legacy: true, + }, + ], + '@babel/plugin-proposal-function-sent', + '@babel/plugin-proposal-export-namespace-from', + '@babel/plugin-proposal-numeric-separator', + '@babel/plugin-proposal-throw-expressions', + '@babel/plugin-proposal-export-default-from', + '@babel/plugin-proposal-logical-assignment-operators', + '@babel/plugin-proposal-optional-chaining', + [ + '@babel/plugin-proposal-pipeline-operator', + { + proposal: 'minimal', + }, + ], + '@babel/plugin-proposal-nullish-coalescing-operator', + '@babel/plugin-proposal-do-expressions', + '@babel/plugin-proposal-function-bind', + ], + env: { + test: { + plugins: ['dynamic-import-node'], + }, + }, +} diff --git a/config/jest/cssTransform.js b/config/jest/cssTransform.js index aa17d127..05ec203f 100644 --- a/config/jest/cssTransform.js +++ b/config/jest/cssTransform.js @@ -1,12 +1,13 @@ +// @flow // This is a custom Jest transformer turning style imports into empty objects. // http://facebook.github.io/jest/docs/tutorial-webpack.html module.exports = { process() { - return 'module.exports = {};'; + return 'module.exports = {};' }, getCacheKey(fileData, filename) { // The output is always the same. - return 'cssTransform'; + return 'cssTransform' }, -}; +} diff --git a/config/jest/fileTransform.js b/config/jest/fileTransform.js index 927eb305..06760aa5 100644 --- a/config/jest/fileTransform.js +++ b/config/jest/fileTransform.js @@ -1,10 +1,11 @@ -const path = require('path'); +// @flow +const path = require('path') // This is a custom Jest transformer turning file imports into filenames. // http://facebook.github.io/jest/docs/tutorial-webpack.html module.exports = { process(src, filename) { - return 'module.exports = ' + JSON.stringify(path.basename(filename)) + ';'; + return `module.exports = ${JSON.stringify(path.basename(filename))};` }, -}; +} diff --git a/jest.config.js b/jest.config.js new file mode 100644 index 00000000..3b30f44f --- /dev/null +++ b/jest.config.js @@ -0,0 +1,28 @@ +// @flow +const esModules = ['immortal-db'].join('|') + +module.exports = { + collectCoverageFrom: ['src/**/*.{js,jsx}'], + moduleNameMapper: { + '~(.*)$': '/src/$1', + '#(.*)$': '/safe-contracts/build/contracts/$1', + '^react-native$': 'react-native-web', + }, + setupFiles: [ + '/config/webpack.config.test.js', + '/config/polyfills.js', + '/config/jest/LocalStorageMock.js', + '/config/jest/Web3Mock.js', + ], + setupFilesAfterEnv: ['/config/jest/jest.setup.js', 'react-testing-library/cleanup-after-each'], + testEnvironment: 'node', + testMatch: ['/src/**/__tests__/**/*.js?(x)', '/src/**/?(*.)(spec|test).js?(x)'], + testURL: 'http://localhost:8000', + transform: { + '^.+\\.(js|jsx)$': '/node_modules/babel-jest', + '^.+\\.(css|scss)$': '/config/jest/cssTransform.js', + '^(?!.*\\.(js|jsx|css|json)$)': '/config/jest/fileTransform.js', + }, + transformIgnorePatterns: [`/node_modules/(?!${esModules})`], + verbose: true, +} diff --git a/package.json b/package.json index 7759d189..a65e665d 100644 --- a/package.json +++ b/package.json @@ -29,49 +29,14 @@ "pre-commit": [ "precommit" ], - "jest": { - "collectCoverageFrom": [ - "src/**/*.{js,jsx}" - ], - "moduleNameMapper": { - "~(.*)$": "/src/$1", - "#(.*)$": "/safe-contracts/build/contracts/$1", - "^react-native$": "react-native-web" - }, - "setupFiles": [ - "/config/webpack.config.test.js", - "/config/polyfills.js", - "/config/jest/LocalStorageMock.js", - "/config/jest/Web3Mock.js" - ], - "setupFilesAfterEnv": [ - "/config/jest/jest.setup.js" - ], - "testEnvironment": "node", - "testMatch": [ - "/src/**/__tests__/**/*.js?(x)", - "/src/**/?(*.)(spec|test).js?(x)" - ], - "testURL": "http://localhost:8000", - "transform": { - "^.+\\.(js|jsx)$": "/node_modules/babel-jest", - "^.+\\.(css|scss)$": "/config/jest/cssTransform.js", - "^(?!.*\\.(js|jsx|css|json)$)": "/config/jest/fileTransform.js" - }, - "transformIgnorePatterns": [ - "[/\\\\]node_modules[/\\\\].+\\.(js|jsx)$", - "[/\\\\]flow-typed[/\\\\].+\\.(js|jsx)$" - ], - "verbose": true - }, "dependencies": { - "@gnosis.pm/util-contracts": "2.0.1", "@gnosis.pm/safe-contracts": "^1.0.0", - "@material-ui/core": "4.0.0", - "@material-ui/icons": "4.0.0", + "@gnosis.pm/util-contracts": "2.0.1", + "@material-ui/core": "4.0.1", + "@material-ui/icons": "4.0.1", "@welldone-software/why-did-you-render": "^3.0.9", "axios": "^0.18.0", - "bignumber.js": "^8.1.1", + "bignumber.js": "9.0.0", "connected-react-router": "^6.3.1", "final-form": "4.13.0", "history": "^4.7.2", @@ -146,18 +111,19 @@ "ethereumjs-abi": "^0.6.7", "extract-text-webpack-plugin": "^4.0.0-beta.0", "file-loader": "^3.0.1", - "flow-bin": "0.98.1", + "flow-bin": "0.99.0", "fs-extra": "8.0.1", "html-loader": "^0.5.5", "html-webpack-plugin": "^3.0.4", "jest": "24.8.0", "json-loader": "^0.5.7", - "mini-css-extract-plugin": "0.6.0", + "mini-css-extract-plugin": "0.7.0", "postcss-loader": "^3.0.0", "postcss-mixins": "^6.2.0", "postcss-simple-vars": "^5.0.2", "pre-commit": "^1.2.2", "prettier-eslint-cli": "^4.7.1", + "react-testing-library": "^7.0.1", "run-with-testrpc": "0.3.1", "storybook-host": "^5.0.3", "storybook-router": "^0.3.3", diff --git a/scripts/test.js b/scripts/test.js index 5ac4f6e4..ff7207c1 100644 --- a/scripts/test.js +++ b/scripts/test.js @@ -1,18 +1,20 @@ -process.env.NODE_ENV = 'test'; -process.env.PUBLIC_URL = ''; +// @flow +process.env.NODE_ENV = 'test' +process.env.PUBLIC_URL = '' // Load environment variables from .env file. Suppress warnings using silent // if this file is missing. dotenv will never modify any environment variables // that have already been set. // https://github.com/motdotla/dotenv -require('dotenv').config({silent: true}); +require('dotenv').config({ silent: true }) -const jest = require('jest'); -const argv = process.argv.slice(2); +const jest = require('jest') + +const argv = process.argv.slice(2) // Watch unless on CI or in coverage mode if (!process.env.CI && argv.indexOf('--coverage') < 0) { - argv.push('--watch'); + argv.push('--watch') } -jest.run(argv); +jest.run(argv) diff --git a/src/components/Stepper/index.jsx b/src/components/Stepper/index.jsx index 0481ce6c..69e3c888 100644 --- a/src/components/Stepper/index.jsx +++ b/src/components/Stepper/index.jsx @@ -21,6 +21,7 @@ type Props = { onReset?: () => void, initialValues?: Object, disabledWhenValidating?: boolean, + testId?: string, } type State = { @@ -145,7 +146,7 @@ class GnoStepper extends React.PureComponent { render() { const { - steps, children, classes, disabledWhenValidating = false, + steps, children, classes, disabledWhenValidating = false, testId, } = this.props const { page, values } = this.state const activePage = this.getActivePageFrom(children) @@ -154,7 +155,7 @@ class GnoStepper extends React.PureComponent { return ( - + {(submitting: boolean, validating: boolean, ...rest: any) => { const disabled = disabledWhenValidating ? submitting || validating : submitting const controls = ( diff --git a/src/components/forms/GnoForm/index.jsx b/src/components/forms/GnoForm/index.jsx index 9ebf3210..d3d690db 100644 --- a/src/components/forms/GnoForm/index.jsx +++ b/src/components/forms/GnoForm/index.jsx @@ -16,6 +16,7 @@ type Props = { validation?: (values: Object) => Object | Promise, initialValues?: Object, formMutators?: Object, + testId?: string, } const stylesBasedOn = (padding: number): $Shape => ({ @@ -25,7 +26,7 @@ const stylesBasedOn = (padding: number): $Shape => ({ }) const GnoForm = ({ - onSubmit, validation, initialValues, children, padding = 0, formMutators, + onSubmit, validation, initialValues, children, padding = 0, formMutators, testId = '', }: Props) => (
( - + {children(rest.submitting, rest.validating, rest, rest.form.mutators)}
)} diff --git a/src/logic/contracts/safeContracts.js b/src/logic/contracts/safeContracts.js index f7997e5c..e2108346 100644 --- a/src/logic/contracts/safeContracts.js +++ b/src/logic/contracts/safeContracts.js @@ -49,7 +49,7 @@ const createMasterCopies = async () => { proxyFactoryMaster = await ProxyFactory.new({ from: userAccount, gas: '5000000' }) const GnosisSafe = getGnosisSafeContract(web3) - safeMaster = await GnosisSafe.new({ from: userAccount, gas: '6000000' }) + safeMaster = await GnosisSafe.new({ from: userAccount, gas: '7000000' }) } export const initContracts = ensureOnce(process.env.NODE_ENV === 'test' ? createMasterCopies : instanciateMasterCopies) diff --git a/src/routes/load/components/DetailsForm/index.jsx b/src/routes/load/components/DetailsForm/index.jsx index e14d9585..9879dd4e 100644 --- a/src/routes/load/components/DetailsForm/index.jsx +++ b/src/routes/load/components/DetailsForm/index.jsx @@ -37,9 +37,11 @@ const styles = () => ({ export const SAFE_INSTANCE_ERROR = 'Address given is not a safe instance' export const SAFE_MASTERCOPY_ERROR = 'Mastercopy used by this safe is not the same' +// In case of an error here, it will be swallowed by final-form +// So if you're experiencing any strang behaviours like freeze or hanging +// Don't mind to check if everything is OK inside this function :) export const safeFieldsValidation = async (values: Object) => { const errors = {} - const web3 = getWeb3() const safeAddress = values[FIELD_LOAD_ADDRESS] if (!safeAddress || mustBeEthereumAddress(safeAddress) !== undefined) { @@ -51,27 +53,21 @@ export const safeFieldsValidation = async (values: Object) => { const code = await web3.eth.getCode(safeAddress) const codeWithoutMetadata = code.substring(0, code.lastIndexOf(metaData)) - const proxyCode = SafeProxy.deployedBytecode const proxyCodeWithoutMetadata = proxyCode.substring(0, proxyCode.lastIndexOf(metaData)) - const safeInstance = codeWithoutMetadata === proxyCodeWithoutMetadata if (!safeInstance) { errors[FIELD_LOAD_ADDRESS] = SAFE_INSTANCE_ERROR - return errors } // check mastercopy - const proxy = contract(SafeProxy) - proxy.setProvider(web3.currentProvider) - const proxyInstance = await proxy.at(safeAddress) - const proxyImplementation = await proxyInstance.implementation() - + const proxyAddressFromStorage = await web3.eth.getStorageAt(safeAddress, 0) + const checksummedProxyAddress = web3.utils.toChecksumAddress(proxyAddressFromStorage) const safeMaster = await getSafeMasterContract() const masterCopy = safeMaster.address - const sameMasterCopy = proxyImplementation === masterCopy + const sameMasterCopy = checksummedProxyAddress === masterCopy if (!sameMasterCopy) { errors[FIELD_LOAD_ADDRESS] = SAFE_MASTERCOPY_ERROR } diff --git a/src/routes/load/components/Layout.jsx b/src/routes/load/components/Layout.jsx index d5741028..167ce9c8 100644 --- a/src/routes/load/components/Layout.jsx +++ b/src/routes/load/components/Layout.jsx @@ -12,9 +12,7 @@ import { history } from '~/store' import { secondary } from '~/theme/variables' import { type SelectorProps } from '~/routes/load/container/selector' -const getSteps = () => [ - 'Details', 'Review', -] +const getSteps = () => ['Details', 'Review'] type Props = SelectorProps & { onLoadSafeSubmit: (values: Object) => Promise, @@ -37,30 +35,24 @@ const Layout = ({ return ( - { provider - ? ( - - - - - - Load existing Safe - - - - { DetailsForm } - - - { ReviewInformation } - - - - ) - :
No metamask detected
- } + {provider ? ( + + + + + + Load existing Safe + + + {DetailsForm} + + {ReviewInformation} + + + + ) : ( +
No metamask detected
+ )}
) } diff --git a/src/routes/open/components/Layout.jsx b/src/routes/open/components/Layout.jsx index 342e392d..e2a545b4 100644 --- a/src/routes/open/components/Layout.jsx +++ b/src/routes/open/components/Layout.jsx @@ -14,9 +14,7 @@ import { getOwnerNameBy, getOwnerAddressBy, FIELD_CONFIRMATIONS } from '~/routes import { history } from '~/store' import { secondary } from '~/theme/variables' -const getSteps = () => [ - 'Start', 'Owners', 'Confirmations', 'Review', -] +const getSteps = () => ['Start', 'Owners', 'Confirmations', 'Review'] const initialValuesFrom = (userAccount: string) => ({ [getOwnerNameBy(0)]: 'My Metamask (me)', @@ -49,37 +47,24 @@ const Layout = ({ return ( - { provider - ? ( - - - - - - Create New Safe - - - - { SafeNameField } - - - { SafeOwnersFields } - - - { SafeThresholdField } - - - { Review } - - - - ) - :
No metamask detected
- } + {provider ? ( + + + + + + Create New Safe + + + {SafeNameField} + {SafeOwnersFields} + {SafeThresholdField} + {Review} + + + ) : ( +
No metamask detected
+ )}
) } diff --git a/src/routes/open/components/SafeOwnersForm/index.jsx b/src/routes/open/components/SafeOwnersForm/index.jsx index 719a864a..eaaea059 100644 --- a/src/routes/open/components/SafeOwnersForm/index.jsx +++ b/src/routes/open/components/SafeOwnersForm/index.jsx @@ -4,7 +4,11 @@ import { withStyles } from '@material-ui/core/styles' import Field from '~/components/forms/Field' import TextField from '~/components/forms/TextField' import { - required, composeValidators, uniqueAddress, mustBeEthereumAddress, noErrorsOn, + required, + composeValidators, + uniqueAddress, + mustBeEthereumAddress, + noErrorsOn, } from '~/components/forms/validator' import Block from '~/components/layout/Block' import Button from '~/components/layout/Button' @@ -105,7 +109,6 @@ class SafeOwners extends React.Component { const initialValues = calculateValuesAfterRemoving(index, numOwners, values) updateInitialProps(initialValues) - this.setState(state => ({ numOwners: state.numOwners - 1, })) @@ -135,11 +138,11 @@ class SafeOwners extends React.Component { - { [...Array(Number(numOwners))].map((x, index) => { + {[...Array(Number(numOwners))].map((x, index) => { const addressName = getOwnerAddressBy(index) return ( - + { - - - ), - }} + inputAdornment={ + noErrorsOn(addressName, errors) && { + endAdornment: ( + + + + ), + } + } type="text" validate={getAddressValidators(otherAccounts, index)} placeholder="Owner Address*" @@ -169,17 +174,17 @@ class SafeOwners extends React.Component { /> - { index > 0 - && Delete - } + {index > 0 && Delete} ) - }) } + })} - @@ -202,5 +207,4 @@ const SafeOwnersPage = ({ updateInitialProps }: Object) => (controls: React$Node ) - export default SafeOwnersPage diff --git a/src/routes/open/components/SafeThresholdForm/index.jsx b/src/routes/open/components/SafeThresholdForm/index.jsx index e2f3cf1e..ef415732 100644 --- a/src/routes/open/components/SafeThresholdForm/index.jsx +++ b/src/routes/open/components/SafeThresholdForm/index.jsx @@ -4,7 +4,9 @@ import { withStyles } from '@material-ui/core/styles' import MenuItem from '@material-ui/core/MenuItem' import Field from '~/components/forms/Field' import SelectField from '~/components/forms/SelectField' -import { composeValidators, minValue, mustBeInteger, required } from '~/components/forms/validator' +import { + composeValidators, minValue, mustBeInteger, required, +} from '~/components/forms/validator' import Block from '~/components/layout/Block' import Row from '~/components/layout/Row' import Col from '~/components/layout/Col' @@ -52,22 +54,23 @@ const SafeThreshold = ({ classes, values }: Props) => { - { - [...Array(Number(numOwners))].map((x, index) => ( - {index + 1} - )) - } + {[...Array(Number(numOwners))].map((x, index) => ( + + {index + 1} + + ))} - out of {numOwners} owner(s) + out of + {' '} + {numOwners} + {' '} + owner(s) @@ -85,5 +88,4 @@ const SafeOwnersPage = () => (controls: React$Node, { values }: Object) => ( ) - export default SafeOwnersPage diff --git a/src/routes/safe/store/actions/fetchTransactions.js b/src/routes/safe/store/actions/fetchTransactions.js index b1aebb3d..fce22749 100644 --- a/src/routes/safe/store/actions/fetchTransactions.js +++ b/src/routes/safe/store/actions/fetchTransactions.js @@ -1,74 +1,74 @@ -// @flow -import { List, Map } from 'immutable' -import axios from 'axios' -import type { Dispatch as ReduxDispatch } from 'redux' -import { type GlobalState } from '~/store/index' -import { makeOwner } from '~/routes/safe/store/models/owner' -import { makeTransaction, type Transaction } from '~/routes/safe/store/models/transaction' -import { makeConfirmation } from '~/routes/safe/store/models/confirmation' -import { loadSafeSubjects } from '~/utils/storage/transactions' -import { buildTxServiceUrlFrom, type TxServiceType } from '~/logic/safe/safeTxHistory' -import { getOwners } from '~/logic/safe/utils' -import { EMPTY_DATA } from '~/logic/wallets/ethTransactions' -import addTransactions from './addTransactions' +// // @flow +// import { List, Map } from 'immutable' +// import axios from 'axios' +// import type { Dispatch as ReduxDispatch } from 'redux' +// import { type GlobalState } from '~/store/index' +// import { makeOwner } from '~/routes/safe/store/models/owner' +// import { makeTransaction, type Transaction } from '~/routes/safe/store/models/transaction' +// import { makeConfirmation } from '~/routes/safe/store/models/confirmation' +// import { loadSafeSubjects } from '~/utils/storage/transactions' +// import { buildTxServiceUrlFrom, type TxServiceType } from '~/logic/safe/safeTxHistory' +// import { getOwners } from '~/logic/safe/utils' +// import { EMPTY_DATA } from '~/logic/wallets/ethTransactions' +// import addTransactions from './addTransactions' -type ConfirmationServiceModel = { - owner: string, - submissionDate: Date, - type: string, - transactionHash: string, -} +// type ConfirmationServiceModel = { +// owner: string, +// submissionDate: Date, +// type: string, +// transactionHash: string, +// } -type TxServiceModel = { - to: string, - value: number, - data: string, - operation: number, - nonce: number, - submissionDate: Date, - executionDate: Date, - confirmations: ConfirmationServiceModel[], - isExecuted: boolean, -} +// type TxServiceModel = { +// to: string, +// value: number, +// data: string, +// operation: number, +// nonce: number, +// submissionDate: Date, +// executionDate: Date, +// confirmations: ConfirmationServiceModel[], +// isExecuted: boolean, +// } -const buildTransactionFrom = (safeAddress: string, tx: TxServiceModel, safeSubjects: Map) => { - const name = safeSubjects.get(String(tx.nonce)) || 'Unknown' - const storedOwners = getOwners(safeAddress) - const confirmations = List( - tx.confirmations.map((conf: ConfirmationServiceModel) => { - const ownerName = storedOwners.get(conf.owner.toLowerCase()) || 'UNKNOWN' +// const buildTransactionFrom = (safeAddress: string, tx: TxServiceModel, safeSubjects: Map) => { +// const name = safeSubjects.get(String(tx.nonce)) || 'Unknown' +// const storedOwners = getOwners(safeAddress) +// const confirmations = List( +// tx.confirmations.map((conf: ConfirmationServiceModel) => { +// const ownerName = storedOwners.get(conf.owner.toLowerCase()) || 'UNKNOWN' - return makeConfirmation({ - owner: makeOwner({ address: conf.owner, name: ownerName }), - type: ((conf.type.toLowerCase(): any): TxServiceType), - hash: conf.transactionHash, - }) - }), - ) +// return makeConfirmation({ +// owner: makeOwner({ address: conf.owner, name: ownerName }), +// type: ((conf.type.toLowerCase(): any): TxServiceType), +// hash: conf.transactionHash, +// }) +// }), +// ) - return makeTransaction({ - name, - nonce: tx.nonce, - value: Number(tx.value), - confirmations, - destination: tx.to, - data: tx.data ? tx.data : EMPTY_DATA, - isExecuted: tx.isExecuted, - }) -} +// return makeTransaction({ +// name, +// nonce: tx.nonce, +// value: Number(tx.value), +// confirmations, +// destination: tx.to, +// data: tx.data ? tx.data : EMPTY_DATA, +// isExecuted: tx.isExecuted, +// }) +// } -export const loadSafeTransactions = async (safeAddress: string) => { - const url = buildTxServiceUrlFrom(safeAddress) - const response = await axios.get(url) - const transactions: TxServiceModel[] = response.data.results - const safeSubjects = loadSafeSubjects(safeAddress) - const txsRecord = transactions.map((tx: TxServiceModel) => buildTransactionFrom(safeAddress, tx, safeSubjects)) +// export const loadSafeTransactions = async (safeAddress: string) => { +// const url = buildTxServiceUrlFrom(safeAddress) +// const response = await axios.get(url) +// const transactions: TxServiceModel[] = response.data.results +// const safeSubjects = loadSafeSubjects(safeAddress) +// const txsRecord = transactions.map((tx: TxServiceModel) => buildTransactionFrom(safeAddress, tx, safeSubjects)) - return Map().set(safeAddress, List(txsRecord)) -} +// return Map().set(safeAddress, List(txsRecord)) +// } -export default (safeAddress: string) => async (dispatch: ReduxDispatch) => { - const transactions: Map> = await loadSafeTransactions(safeAddress) +// export default (safeAddress: string) => async (dispatch: ReduxDispatch) => { +// const transactions: Map> = await loadSafeTransactions(safeAddress) - return dispatch(addTransactions(transactions)) -} +// return dispatch(addTransactions(transactions)) +// } diff --git a/src/test/safe.dom.create.test.js b/src/test/safe.dom.create.test.js index 20699fa7..adf7181c 100644 --- a/src/test/safe.dom.create.test.js +++ b/src/test/safe.dom.create.test.js @@ -1,8 +1,7 @@ // @flow import * as React from 'react' import { type Store } from 'redux' -import TestUtils from 'react-dom/test-utils' -import Select from '@material-ui/core/Select' +import { render, fireEvent, cleanup } from 'react-testing-library' import { Provider } from 'react-redux' import { ConnectedRouter } from 'connected-react-router' import { ADD_OWNER_BUTTON } from '~/routes/open/components/SafeOwnersForm' @@ -15,12 +14,29 @@ import { makeProvider } from '~/logic/wallets/store/model/provider' import { getGnosisSafeInstanceAt } from '~/logic/contracts/safeContracts' import { whenSafeDeployed } from './builder/safe.dom.utils' -const fillOpenSafeForm = async (localStore: Store) => { +afterEach(cleanup) + +// https://github.com/testing-library/react-testing-library/issues/281 +const originalError = console.error +beforeAll(() => { + console.error = (...args) => { + if (/Warning.*not wrapped in act/.test(args[0])) { + return + } + originalError.call(console, ...args) + } +}) + +afterAll(() => { + console.error = originalError +}) + +const renderOpenSafeForm = async (localStore: Store) => { const provider = await getProviderInfo() const walletRecord = makeProvider(provider) localStore.dispatch(addProvider(walletRecord)) - return TestUtils.renderIntoDocument( + return render( @@ -29,53 +45,54 @@ const fillOpenSafeForm = async (localStore: Store) => { ) } -const deploySafe = async (safe: React$Component<{}>, threshold: number, numOwners: number) => { +const deploySafe = async (createSafeForm: any, threshold: number, numOwners: number) => { const web3 = getWeb3() const accounts = await web3.eth.getAccounts() expect(threshold).toBeLessThanOrEqual(numOwners) - const form = TestUtils.findRenderedDOMComponentWithTag(safe, 'form') + const form = createSafeForm.getByTestId('create-safe-form') // Fill Safe's name - const inputs = TestUtils.scryRenderedDOMComponentsWithTag(safe, 'input') - expect(inputs.length).toBe(1) - const fieldName = inputs[0] - TestUtils.Simulate.change(fieldName, { target: { value: 'Adolfo Safe' } }) - TestUtils.Simulate.submit(form) + const nameInput: HTMLInputElement = createSafeForm.getByPlaceholderText('Name of the new Safe') + + fireEvent.change(nameInput, { target: { value: 'Adolfo Safe' } }) + fireEvent.submit(form) await sleep(400) // Fill owners const addedUpfront = 1 - const buttons = TestUtils.scryRenderedDOMComponentsWithTag(safe, 'button') - const addOwnerButton = buttons[1] + const addOwnerButton = createSafeForm.getByTestId('add-owner-btn') + expect(addOwnerButton.getElementsByTagName('span')[0].textContent).toEqual(ADD_OWNER_BUTTON) for (let i = addedUpfront; i < numOwners; i += 1) { - TestUtils.Simulate.click(addOwnerButton) + fireEvent.click(addOwnerButton) } - const ownerInputs = TestUtils.scryRenderedDOMComponentsWithTag(safe, 'input') - expect(ownerInputs.length).toBe(numOwners * 2) + const ownerNameInputs = createSafeForm.getAllByPlaceholderText('Owner Name*') + const ownerAddressInputs = createSafeForm.getAllByPlaceholderText('Owner Address*') + expect(ownerNameInputs.length).toBe(numOwners) + expect(ownerAddressInputs.length).toBe(numOwners) + for (let i = addedUpfront; i < numOwners; i += 1) { - const nameIndex = i * 2 - const addressIndex = i * 2 + 1 - const ownerName = ownerInputs[nameIndex] - const account = ownerInputs[addressIndex] + const ownerNameInput = ownerNameInputs[i] + const ownerAddressInput = ownerAddressInputs[i] - TestUtils.Simulate.change(ownerName, { target: { value: `Adolfo ${i + 1} Eth Account` } }) - TestUtils.Simulate.change(account, { target: { value: accounts[i] } }) + fireEvent.change(ownerNameInput, { target: { value: `Owner ${i + 1}` } }) + fireEvent.change(ownerAddressInput, { target: { value: accounts[i] } }) } - TestUtils.Simulate.submit(form) + fireEvent.submit(form) await sleep(400) // Fill Threshold - const muiSelectFields = TestUtils.scryRenderedComponentsWithType(safe, Select) - expect(muiSelectFields.length).toEqual(1) - muiSelectFields[0].props.onChange(`${threshold}`) - TestUtils.Simulate.submit(form) + const thresholdSelect = createSafeForm.getByRole('button') + fireEvent.click(thresholdSelect) + const thresholdOptions = createSafeForm.getAllByRole('option') + fireEvent.click(thresholdOptions[numOwners - 1]) + fireEvent.submit(form) await sleep(400) // Submit - TestUtils.Simulate.submit(form) + fireEvent.submit(form) await sleep(400) // giving some time to the component for updating its state with safe @@ -84,14 +101,14 @@ const deploySafe = async (safe: React$Component<{}>, threshold: number, numOwner } const aDeployedSafe = async (specificStore: Store, threshold?: number = 1, numOwners?: number = 1) => { - const safe: React$Component<{}> = await fillOpenSafeForm(specificStore) + const safe: React$Component<{}> = await renderOpenSafeForm(specificStore) const safeAddress = await deploySafe(safe, threshold, numOwners) return safeAddress } describe('DOM > Feature > CREATE a safe', () => { - it('fills correctly the safe form with 4 owners and 4 threshold', async () => { + it('fills correctly the safe form with 4 owners and 4 threshold and creates a safe', async () => { const owners = 4 const threshold = 4 const store = aNewStore() diff --git a/src/test/safe.dom.load.test.js b/src/test/safe.dom.load.test.js index f0f0fef6..d909127b 100644 --- a/src/test/safe.dom.load.test.js +++ b/src/test/safe.dom.load.test.js @@ -1,8 +1,8 @@ // @flow import * as React from 'react' import { type Store } from 'redux' -import TestUtils from 'react-dom/test-utils' import { Provider } from 'react-redux' +import { render, fireEvent, cleanup } from 'react-testing-library' import { ConnectedRouter } from 'connected-react-router' import Load from '~/routes/load/container/Load' import { aNewStore, history, type GlobalState } from '~/store' @@ -13,12 +13,30 @@ import { makeProvider } from '~/logic/wallets/store/model/provider' import { aMinedSafe } from './builder/safe.redux.builder' import { whenSafeDeployed } from './builder/safe.dom.utils' -const travelToLoadRoute = async (localStore: Store) => { +afterEach(cleanup) + +// https://github.com/testing-library/react-testing-library/issues/281 +const originalError = console.error +beforeAll(() => { + console.error = (...args) => { + if (/Warning.*not wrapped in act/.test(args[0])) { + return + } + originalError.call(console, ...args) + } +}) + +afterAll(() => { + console.error = originalError +}) + + +const renderLoadSafe = async (localStore: Store) => { const provider = await getProviderInfo() const walletRecord = makeProvider(provider) localStore.dispatch(addProvider(walletRecord)) - return TestUtils.renderIntoDocument( + return render( @@ -31,26 +49,22 @@ describe('DOM > Feature > LOAD a safe', () => { it('load correctly a created safe', async () => { const store = aNewStore() const address = await aMinedSafe(store) - const LoadDom = await travelToLoadRoute(store) - - const form = TestUtils.findRenderedDOMComponentWithTag(LoadDom, 'form') - const inputs = TestUtils.scryRenderedDOMComponentsWithTag(LoadDom, 'input') + const LoadSafePage = await renderLoadSafe(store) + const form = LoadSafePage.getByTestId('load-safe-form') + const safeNameInput = LoadSafePage.getByPlaceholderText('Name of the Safe') + const safeAddressInput = LoadSafePage.getByPlaceholderText('Safe Address*') // Fill Safe's name - const fieldName = inputs[0] - TestUtils.Simulate.change(fieldName, { target: { value: 'Adolfo Safe' } }) - const fieldAddress = inputs[1] - TestUtils.Simulate.change(fieldAddress, { target: { value: address } }) + fireEvent.change(safeNameInput, { target: { value: 'A Safe To Load' } }) + fireEvent.change(safeAddressInput, { target: { value: address } }) await sleep(400) // Click next - TestUtils.Simulate.submit(form) + fireEvent.submit(form) await sleep(400) // Submit - TestUtils.Simulate.submit(form) - await sleep(400) - + fireEvent.submit(form) const deployedAddress = await whenSafeDeployed() expect(deployedAddress).toBe(address) }) diff --git a/src/test/utils/Wrapper.jsx b/src/test/utils/Wrapper.jsx index 3351c228..b76cc665 100644 --- a/src/test/utils/Wrapper.jsx +++ b/src/test/utils/Wrapper.jsx @@ -2,17 +2,9 @@ import * as React from 'react' type WrapperProps = { - children: React$Node + children: React$Node, } -class Wrapper extends React.PureComponent { - render() { - return ( - - { this.props.children } - - ) - } -} +const Wrapper = ({ children }: WrapperProps) => {children} export default Wrapper diff --git a/yarn.lock b/yarn.lock index 8f8cdc70..51a1e126 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1837,16 +1837,16 @@ "@types/istanbul-reports" "^1.1.1" "@types/yargs" "^12.0.9" -"@material-ui/core@4.0.0": - version "4.0.0" - resolved "https://registry.yarnpkg.com/@material-ui/core/-/core-4.0.0.tgz#f8bead508cc727188e5d819ae937aa928d83857b" - integrity sha512-mLEGTuzgUALRKFI3hkRcS0gi/cB3XV0JA4F5PT3rGUt7Dc4liu8/IGiHF7iQh+p337FMk8vkEMxMVdYd9JXKMQ== +"@material-ui/core@4.0.1": + version "4.0.1" + resolved "https://registry.yarnpkg.com/@material-ui/core/-/core-4.0.1.tgz#7fe3bcb1a89b01376b181358a5ded58291da0016" + integrity sha512-cw2Qs3BLem8FOrp/knfjJkwJXG4dZO/HGyWwZV71UWiqDIOF3plHZ7duCbOMIWwKgSUJ85k9omlSHUTT63E/pw== dependencies: "@babel/runtime" "^7.2.0" - "@material-ui/styles" "^4.0.0" - "@material-ui/system" "^4.0.0" - "@material-ui/types" "^4.0.0" - "@material-ui/utils" "^4.0.0" + "@material-ui/styles" "^4.0.1" + "@material-ui/system" "^4.0.1" + "@material-ui/types" "^4.0.1" + "@material-ui/utils" "^4.0.1" "@types/react-transition-group" "^2.0.16" clsx "^1.0.2" convert-css-length "^1.0.2" @@ -1862,22 +1862,22 @@ react-transition-group "^4.0.0" warning "^4.0.1" -"@material-ui/icons@4.0.0": - version "4.0.0" - resolved "https://registry.yarnpkg.com/@material-ui/icons/-/icons-4.0.0.tgz#1dec886098663e08dc80f38c5c06d70b21dfc4e7" - integrity sha512-hXoKnVLmVer+kic84ypoyG3Amym3a8q3pvDg4KYjeKW9fxGru7x/IkelBJODQL0jO+nAPz1+9RNpFWC75v35dg== +"@material-ui/icons@4.0.1": + version "4.0.1" + resolved "https://registry.yarnpkg.com/@material-ui/icons/-/icons-4.0.1.tgz#f63990f731c6206c82023c96d510bd7bdda44250" + integrity sha512-03zUfksGXXbaWX2piB1LCmC28eydlT8ah8MbYT4n4mgiX9BTL4HH50lkFn9JIJJSk2oO5kRy4FvpXRGRBI+oxw== dependencies: "@babel/runtime" "^7.2.0" -"@material-ui/styles@^4.0.0": - version "4.0.0" - resolved "https://registry.yarnpkg.com/@material-ui/styles/-/styles-4.0.0.tgz#789461e3e2b484a26206db7656c78b3248904f3b" - integrity sha512-TUpmXlyZDVOl6E2//+UzsZxgi2E+2L753QY02nNkbAC6PPx8FUBqvnjYSGqX0V/BjTJ/fD4CkoS6ZpY3lHf+Gg== +"@material-ui/styles@^4.0.1": + version "4.0.1" + resolved "https://registry.yarnpkg.com/@material-ui/styles/-/styles-4.0.1.tgz#67e880d490f010c9f2956c572b07d218bfa255d7" + integrity sha512-SywkWzBzXvm9dUY2rtmzTc/FTlKGctVYGb8hzPZyHU3OI4X9jQH4YnR/OiqTwg4jNpFnASJX5rW1rEUJM+ZnhA== dependencies: "@babel/runtime" "^7.2.0" "@emotion/hash" "^0.7.1" - "@material-ui/types" "^4.0.0" - "@material-ui/utils" "^4.0.0" + "@material-ui/types" "^4.0.1" + "@material-ui/utils" "^4.0.1" clsx "^1.0.2" deepmerge "^3.0.0" hoist-non-react-statics "^3.2.1" @@ -1892,25 +1892,25 @@ prop-types "^15.7.2" warning "^4.0.1" -"@material-ui/system@^4.0.0": - version "4.0.0" - resolved "https://registry.yarnpkg.com/@material-ui/system/-/system-4.0.0.tgz#bb9a03aa3cf0405c2159c2408b7b8b20b959b119" - integrity sha512-SIsqIwjix98Mqw9LVAmRqTs10E4S/SP5n5mlBlhHVHI+2XG2c+MaCPzOF2Zxq0KdqOMgTb7/aevR3mG9UmODxg== +"@material-ui/system@^4.0.1": + version "4.0.1" + resolved "https://registry.yarnpkg.com/@material-ui/system/-/system-4.0.1.tgz#d78969f3dd9eb6baf82ded0ef183368d8befb00b" + integrity sha512-NlMF4jZk1xx7taUOT+QhrJw7v7uUi9Ae+G8C8fowGgP5x04whxOuSuSmN9a8u2j7dc8XqahR0OJeA6Xch8ymog== dependencies: "@babel/runtime" "^7.2.0" deepmerge "^3.0.0" prop-types "^15.7.2" warning "^4.0.1" -"@material-ui/types@^4.0.0": - version "4.0.0" - resolved "https://registry.yarnpkg.com/@material-ui/types/-/types-4.0.0.tgz#6804123b1c5d56db232fc54fe745c5b1c3ef7306" - integrity sha512-wuiQMo8nSljZR1oWh57UQYssdtFqaU+Cbhr16uLohzzTllpCAK4LkH0slnH3n+5vCa2dgOdNlZTrmsIDDwvRJQ== +"@material-ui/types@^4.0.1": + version "4.0.1" + resolved "https://registry.yarnpkg.com/@material-ui/types/-/types-4.0.1.tgz#a05fe801a10604d99e593303df9e843868008d4f" + integrity sha512-FGhogU9l4s+ycMcC3hhOAvu5hcWa5TVSCCGUf4NOUF904ythroWSAvcCHn92NjftXZ8WZqmtPjL1K/d90Pq/3Q== -"@material-ui/utils@^4.0.0": - version "4.0.0" - resolved "https://registry.yarnpkg.com/@material-ui/utils/-/utils-4.0.0.tgz#cfe8da2328afea8fbc5599d8e7abd64e25828bc3" - integrity sha512-gjz52hO1hkIbKPMng1diQybVgtfgCptOCrulUs4emSCHHKUoR1zfT+IUrjgOaKIpYZNOgS/CI7KDMp689+FzeQ== +"@material-ui/utils@^4.0.1": + version "4.0.1" + resolved "https://registry.yarnpkg.com/@material-ui/utils/-/utils-4.0.1.tgz#ea3ab6fe4eea9588713813cff6eca33bda3519f6" + integrity sha512-mWRcMQIrqsXGze73tx3hNfB1NUu+BL/oIQI7TImyuhsia1EQXw3bPVBjgwTzqM6MqfXw6eh1fR45Di+WN5hASA== dependencies: "@babel/runtime" "^7.2.0" prop-types "^15.7.2" @@ -1991,6 +1991,11 @@ dependencies: uuid "^3.1.0" +"@sheerun/mutationobserver-shim@^0.3.2": + version "0.3.2" + resolved "https://registry.yarnpkg.com/@sheerun/mutationobserver-shim/-/mutationobserver-shim-0.3.2.tgz#8013f2af54a2b7d735f71560ff360d3a8176a87b" + integrity sha512-vTCdPp/T/Q3oSqwHmZ5Kpa9oI7iLtGl3RQaA/NyLHikvcrPxACkkKVr/XzkSPJWXHRhKGzVvb0urJsbMlRxi1Q== + "@storybook/addon-actions@5.0.11": version "5.0.11" resolved "https://registry.yarnpkg.com/@storybook/addon-actions/-/addon-actions-5.0.11.tgz#7ca6d6ce9400b9b97f2699935edade88905767c3" @@ -4402,16 +4407,16 @@ big.js@^5.2.2: resolved "https://registry.yarnpkg.com/big.js/-/big.js-5.2.2.tgz#65f0af382f578bcdc742bd9c281e9cb2d7768328" integrity sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ== +bignumber.js@9.0.0: + version "9.0.0" + resolved "https://registry.yarnpkg.com/bignumber.js/-/bignumber.js-9.0.0.tgz#805880f84a329b5eac6e7cb6f8274b6d82bdf075" + integrity sha512-t/OYhhJ2SD+YGBQcjY8GzzDHEk9f3nerxjtfa6tlMXfe7frs/WozhvCNoGvpM0P3bNf3Gq5ZRMlGr5f3r4/N8A== + bignumber.js@^7.2.1: version "7.2.1" resolved "https://registry.yarnpkg.com/bignumber.js/-/bignumber.js-7.2.1.tgz#80c048759d826800807c4bfd521e50edbba57a5f" integrity sha512-S4XzBk5sMB+Rcb/LNcpzXr57VRTxgAvaAEDAl1AwRx27j00hT84O6OkteE7u8UB3NuaaygCRrEpqox4uDOrbdQ== -bignumber.js@^8.1.1: - version "8.1.1" - resolved "https://registry.yarnpkg.com/bignumber.js/-/bignumber.js-8.1.1.tgz#4b072ae5aea9c20f6730e4e5d529df1271c4d885" - integrity sha512-QD46ppGintwPGuL1KqmwhR0O+N2cZUg8JG/VzwI2e28sM9TqHjQB10lI4QAaMHVbLzwVLLAwEglpKPViWX+5NQ== - "bignumber.js@git+https://github.com/debris/bignumber.js#master": version "2.0.7" resolved "git+https://github.com/debris/bignumber.js#c7a38de919ed75e6fb6ba38051986e294b328df9" @@ -6554,6 +6559,16 @@ dom-serializer@0, dom-serializer@~0.1.0: domelementtype "^1.3.0" entities "^1.1.1" +dom-testing-library@^4.1.0: + version "4.1.1" + resolved "https://registry.yarnpkg.com/dom-testing-library/-/dom-testing-library-4.1.1.tgz#615af61bee06db51bd8ecea60c113eba7cb49dda" + integrity sha512-PUsG7aY5BJxzulDrOtkksqudRRypcVQF6d4RGAyj9xNwallOFqrNLOyg2QW2mCpFaNVPELX8hBX/wbHQtOto/A== + dependencies: + "@babel/runtime" "^7.4.3" + "@sheerun/mutationobserver-shim" "^0.3.2" + pretty-format "^24.7.0" + wait-for-expect "^1.1.1" + dom-walk@^0.1.0: version "0.1.1" resolved "https://registry.yarnpkg.com/dom-walk/-/dom-walk-0.1.1.tgz#672226dc74c8f799ad35307df936aba11acd6018" @@ -8155,10 +8170,10 @@ flatted@^2.0.0: resolved "https://registry.yarnpkg.com/flatted/-/flatted-2.0.0.tgz#55122b6536ea496b4b44893ee2608141d10d9916" integrity sha512-R+H8IZclI8AAkSBRQJLVOsxwAoHd6WC40b4QTNWIjzAa6BXOBfQcM587MXDTVPeYaopFNWHUFLx7eNmHDSxMWg== -flow-bin@0.98.1: - version "0.98.1" - resolved "https://registry.yarnpkg.com/flow-bin/-/flow-bin-0.98.1.tgz#a8d781621c91703df69928acc83c9777e2fcbb49" - integrity sha512-y1YzQgbFUX4EG6h2EO8PhyJeS0VxNgER8XsTwU8IXw4KozfneSmGVgw8y3TwAOza7rVhTlHEoli1xNuNW1rhPw== +flow-bin@0.99.0: + version "0.99.0" + resolved "https://registry.yarnpkg.com/flow-bin/-/flow-bin-0.99.0.tgz#df035be2493825ddce0b2f26d1273747465b538c" + integrity sha512-PjTzcOwte2mq+aP+HFCQZw/AojltOnOdtZC9iPzkWJJdPH7nYSKkZhC4dFgP24BuXsJH6yZBZ48gEKsX04UegQ== flush-write-stream@^1.0.0: version "1.1.1" @@ -11628,13 +11643,13 @@ min-document@^2.19.0: dependencies: dom-walk "^0.1.0" -mini-css-extract-plugin@0.6.0: - version "0.6.0" - resolved "https://registry.yarnpkg.com/mini-css-extract-plugin/-/mini-css-extract-plugin-0.6.0.tgz#a3f13372d6fcde912f3ee4cd039665704801e3b9" - integrity sha512-79q5P7YGI6rdnVyIAV4NXpBQJFWdkzJxCim3Kog4078fM0piAaFlwocqbejdWtLW1cEzCexPrh6EdyFsPgVdAw== +mini-css-extract-plugin@0.7.0: + version "0.7.0" + resolved "https://registry.yarnpkg.com/mini-css-extract-plugin/-/mini-css-extract-plugin-0.7.0.tgz#5ba8290fbb4179a43dd27cca444ba150bee743a0" + integrity sha512-RQIw6+7utTYn8DBGsf/LpRgZCJMpZt+kuawJ/fju0KiOL6nAaTBNmCJwS7HtwSCXfS47gCkmtBFS7HdsquhdxQ== dependencies: loader-utils "^1.1.0" - normalize-url "^2.0.1" + normalize-url "1.9.1" schema-utils "^1.0.0" webpack-sources "^1.1.0" @@ -12095,14 +12110,15 @@ normalize-scroll-left@^0.1.2: resolved "https://registry.yarnpkg.com/normalize-scroll-left/-/normalize-scroll-left-0.1.2.tgz#6b79691ba79eb5fb107fa5edfbdc06b55caee2aa" integrity sha512-F9YMRls0zCF6BFIE2YnXDRpHPpfd91nOIaNdDgrx5YMoPLo8Wqj+6jNXHQsYBavJeXP4ww8HCt0xQAKc5qk2Fg== -normalize-url@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-2.0.1.tgz#835a9da1551fa26f70e92329069a23aa6574d7e6" - integrity sha512-D6MUW4K/VzoJ4rJ01JFKxDrtY1v9wrgzCX5f2qj/lzH1m/lW6MhUZFKerVsnyjOhOsYzI9Kqqak+10l4LvLpMw== +normalize-url@1.9.1: + version "1.9.1" + resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-1.9.1.tgz#2cc0d66b31ea23036458436e3620d85954c66c3c" + integrity sha1-LMDWazHqIwNkWENuNiDYWVTGbDw= dependencies: - prepend-http "^2.0.0" - query-string "^5.0.1" - sort-keys "^2.0.0" + object-assign "^4.0.1" + prepend-http "^1.0.0" + query-string "^4.1.0" + sort-keys "^1.0.0" normalize-url@^3.0.0: version "3.3.0" @@ -13270,16 +13286,11 @@ prelude-ls@~1.1.2: resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54" integrity sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ= -prepend-http@^1.0.1: +prepend-http@^1.0.0, prepend-http@^1.0.1: version "1.0.4" resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-1.0.4.tgz#d4f4562b0ce3696e41ac52d0e002e57a635dc6dc" integrity sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw= -prepend-http@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-2.0.0.tgz#e92434bfa5ea8c19f41cdfd401d741a3c819d897" - integrity sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc= - preserve@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/preserve/-/preserve-0.2.0.tgz#815ed1f6ebc65926f865b310c0713bcb3315ce4b" @@ -13349,7 +13360,7 @@ pretty-format@^23.0.1: ansi-regex "^3.0.0" ansi-styles "^3.2.0" -pretty-format@^24.8.0: +pretty-format@^24.7.0, pretty-format@^24.8.0: version "24.8.0" resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-24.8.0.tgz#8dae7044f58db7cb8be245383b565a963e3c27f2" integrity sha512-P952T7dkrDEplsR+TuY7q3VXDae5Sr7zmQb12JU/NDQa/3CH7/QW0yvqLcGN6jL+zQFKaoJcPc+yJxMTGmosqw== @@ -13623,6 +13634,14 @@ qs@6.7.0, qs@^6.5.2: resolved "https://registry.yarnpkg.com/qs/-/qs-6.7.0.tgz#41dc1a015e3d581f1621776be31afb2876a9b1bc" integrity sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ== +query-string@^4.1.0: + version "4.3.4" + resolved "https://registry.yarnpkg.com/query-string/-/query-string-4.3.4.tgz#bbb693b9ca915c232515b228b1a02b609043dbeb" + integrity sha1-u7aTucqRXCMlFbIosaArYJBD2+s= + dependencies: + object-assign "^4.1.0" + strict-uri-encode "^1.0.0" + query-string@^5.0.1: version "5.1.1" resolved "https://registry.yarnpkg.com/query-string/-/query-string-5.1.1.tgz#a78c012b71c17e05f2e3fa2319dd330682efb3cb" @@ -14042,6 +14061,14 @@ react-syntax-highlighter@^8.0.1: prismjs "^1.8.4" refractor "^2.4.1" +react-testing-library@^7.0.1: + version "7.0.1" + resolved "https://registry.yarnpkg.com/react-testing-library/-/react-testing-library-7.0.1.tgz#0cf113bb53a78599f018378f6854e91a52dbf205" + integrity sha512-doQkM3/xPcIm22x9jgTkGxU8xqXg4iWvM1WwbbQ7CI5/EMk3DhloYBwMyk+Ywtta3dIAIh9sC7llXoKovf3L+w== + dependencies: + "@babel/runtime" "^7.4.3" + dom-testing-library "^4.1.0" + react-textarea-autosize@^7.0.4: version "7.1.0" resolved "https://registry.yarnpkg.com/react-textarea-autosize/-/react-textarea-autosize-7.1.0.tgz#3132cb77e65d94417558d37c0bfe415a5afd3445" @@ -15459,10 +15486,10 @@ solidity-sha3@^0.4.1: left-pad "^1.1.1" web3 "^0.16.0" -sort-keys@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/sort-keys/-/sort-keys-2.0.0.tgz#658535584861ec97d730d6cf41822e1f56684128" - integrity sha1-ZYU1WEhh7JfXMNbPQYIuH1ZoQSg= +sort-keys@^1.0.0: + version "1.1.2" + resolved "https://registry.yarnpkg.com/sort-keys/-/sort-keys-1.1.2.tgz#441b6d4d346798f1b4e49e8920adfba0e543f9ad" + integrity sha1-RBttTTRnmPG05J6JIK37oOVD+a0= dependencies: is-plain-obj "^1.0.0" @@ -17515,6 +17542,11 @@ w3c-hr-time@^1.0.1: dependencies: browser-process-hrtime "^0.1.2" +wait-for-expect@^1.1.1: + version "1.2.0" + resolved "https://registry.yarnpkg.com/wait-for-expect/-/wait-for-expect-1.2.0.tgz#fdab6a26e87d2039101db88bff3d8158e5c3e13f" + integrity sha512-EJhKpA+5UHixduMBEGhTFuLuVgQBKWxkFbefOdj2bbk2/OpA5Opsc4aUTGmF+qJ+v3kTGxDRNYwKaT4j6g5n8Q== + walker@^1.0.7, walker@~1.0.5: version "1.0.7" resolved "https://registry.yarnpkg.com/walker/-/walker-1.0.7.tgz#2f7f9b8fd10d677262b18a884e28d19618e028fb"