Merge pull request #113 from gnosis/110-sort-out-tests

#110 Sorting out tests: Safe creation and Safe loading, fix the validator for safe address
This commit is contained in:
Mikhail Mikheev 2019-05-31 17:22:53 +04:00 committed by GitHub
commit 031766fbce
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 426 additions and 391 deletions

View File

@ -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"]
}
}
}

49
babel.config.js Normal file
View File

@ -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'],
},
},
}

View File

@ -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'
},
};
}

View File

@ -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))};`
},
};
}

28
jest.config.js Normal file
View File

@ -0,0 +1,28 @@
// @flow
const esModules = ['immortal-db'].join('|')
module.exports = {
collectCoverageFrom: ['src/**/*.{js,jsx}'],
moduleNameMapper: {
'~(.*)$': '<rootDir>/src/$1',
'#(.*)$': '<rootDir>/safe-contracts/build/contracts/$1',
'^react-native$': 'react-native-web',
},
setupFiles: [
'<rootDir>/config/webpack.config.test.js',
'<rootDir>/config/polyfills.js',
'<rootDir>/config/jest/LocalStorageMock.js',
'<rootDir>/config/jest/Web3Mock.js',
],
setupFilesAfterEnv: ['<rootDir>/config/jest/jest.setup.js', 'react-testing-library/cleanup-after-each'],
testEnvironment: 'node',
testMatch: ['<rootDir>/src/**/__tests__/**/*.js?(x)', '<rootDir>/src/**/?(*.)(spec|test).js?(x)'],
testURL: 'http://localhost:8000',
transform: {
'^.+\\.(js|jsx)$': '<rootDir>/node_modules/babel-jest',
'^.+\\.(css|scss)$': '<rootDir>/config/jest/cssTransform.js',
'^(?!.*\\.(js|jsx|css|json)$)': '<rootDir>/config/jest/fileTransform.js',
},
transformIgnorePatterns: [`/node_modules/(?!${esModules})`],
verbose: true,
}

View File

@ -29,49 +29,14 @@
"pre-commit": [
"precommit"
],
"jest": {
"collectCoverageFrom": [
"src/**/*.{js,jsx}"
],
"moduleNameMapper": {
"~(.*)$": "<rootDir>/src/$1",
"#(.*)$": "<rootDir>/safe-contracts/build/contracts/$1",
"^react-native$": "react-native-web"
},
"setupFiles": [
"<rootDir>/config/webpack.config.test.js",
"<rootDir>/config/polyfills.js",
"<rootDir>/config/jest/LocalStorageMock.js",
"<rootDir>/config/jest/Web3Mock.js"
],
"setupFilesAfterEnv": [
"<rootDir>/config/jest/jest.setup.js"
],
"testEnvironment": "node",
"testMatch": [
"<rootDir>/src/**/__tests__/**/*.js?(x)",
"<rootDir>/src/**/?(*.)(spec|test).js?(x)"
],
"testURL": "http://localhost:8000",
"transform": {
"^.+\\.(js|jsx)$": "<rootDir>/node_modules/babel-jest",
"^.+\\.(css|scss)$": "<rootDir>/config/jest/cssTransform.js",
"^(?!.*\\.(js|jsx|css|json)$)": "<rootDir>/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",

View File

@ -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)

View File

@ -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<Props, State> {
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<Props, State> {
return (
<React.Fragment>
<GnoForm onSubmit={this.handleSubmit} initialValues={values} validation={this.validate}>
<GnoForm onSubmit={this.handleSubmit} initialValues={values} validation={this.validate} testId={testId}>
{(submitting: boolean, validating: boolean, ...rest: any) => {
const disabled = disabledWhenValidating ? submitting || validating : submitting
const controls = (

View File

@ -16,6 +16,7 @@ type Props = {
validation?: (values: Object) => Object | Promise<Object>,
initialValues?: Object,
formMutators?: Object,
testId?: string,
}
const stylesBasedOn = (padding: number): $Shape<CSSStyleDeclaration> => ({
@ -25,7 +26,7 @@ const stylesBasedOn = (padding: number): $Shape<CSSStyleDeclaration> => ({
})
const GnoForm = ({
onSubmit, validation, initialValues, children, padding = 0, formMutators,
onSubmit, validation, initialValues, children, padding = 0, formMutators, testId = '',
}: Props) => (
<Form
validate={validation}
@ -33,7 +34,7 @@ const GnoForm = ({
initialValues={initialValues}
mutators={formMutators}
render={({ handleSubmit, ...rest }) => (
<form onSubmit={handleSubmit} style={stylesBasedOn(padding)}>
<form onSubmit={handleSubmit} style={stylesBasedOn(padding)} data-testid={testId}>
{children(rest.submitting, rest.validating, rest, rest.form.mutators)}
</form>
)}

View File

@ -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)

View File

@ -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
}

View File

@ -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<void>,
@ -37,30 +35,24 @@ const Layout = ({
return (
<React.Fragment>
{ provider
? (
<Block>
<Row align="center">
<IconButton onClick={back} style={iconStyle} disableRipple>
<ChevronLeft />
</IconButton>
<Heading tag="h2">Load existing Safe</Heading>
</Row>
<Stepper
onSubmit={onLoadSafeSubmit}
steps={steps}
>
<Stepper.Page validate={safeFieldsValidation}>
{ DetailsForm }
</Stepper.Page>
<Stepper.Page network={network} userAddress={userAddress}>
{ ReviewInformation }
</Stepper.Page>
</Stepper>
</Block>
)
: <div>No metamask detected</div>
}
{provider ? (
<Block>
<Row align="center">
<IconButton onClick={back} style={iconStyle} disableRipple>
<ChevronLeft />
</IconButton>
<Heading tag="h2">Load existing Safe</Heading>
</Row>
<Stepper onSubmit={onLoadSafeSubmit} steps={steps} testId="load-safe-form">
<Stepper.Page validate={safeFieldsValidation}>{DetailsForm}</Stepper.Page>
<Stepper.Page network={network} userAddress={userAddress}>
{ReviewInformation}
</Stepper.Page>
</Stepper>
</Block>
) : (
<div>No metamask detected</div>
)}
</React.Fragment>
)
}

View File

@ -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 (
<React.Fragment>
{ provider
? (
<Block>
<Row align="center">
<IconButton onClick={back} style={iconStyle} disableRipple>
<ChevronLeft />
</IconButton>
<Heading tag="h2">Create New Safe</Heading>
</Row>
<Stepper
onSubmit={onCallSafeContractSubmit}
steps={steps}
initialValues={initialValues}
>
<Stepper.Page>
{ SafeNameField }
</Stepper.Page>
<Stepper.Page>
{ SafeOwnersFields }
</Stepper.Page>
<Stepper.Page validate={safeFieldsValidation}>
{ SafeThresholdField }
</Stepper.Page>
<Stepper.Page network={network}>
{ Review }
</Stepper.Page>
</Stepper>
</Block>
)
: <div>No metamask detected</div>
}
{provider ? (
<Block>
<Row align="center">
<IconButton onClick={back} style={iconStyle} disableRipple>
<ChevronLeft />
</IconButton>
<Heading tag="h2">Create New Safe</Heading>
</Row>
<Stepper onSubmit={onCallSafeContractSubmit} steps={steps} initialValues={initialValues} testId="create-safe-form">
<Stepper.Page>{SafeNameField}</Stepper.Page>
<Stepper.Page>{SafeOwnersFields}</Stepper.Page>
<Stepper.Page validate={safeFieldsValidation}>{SafeThresholdField}</Stepper.Page>
<Stepper.Page network={network}>{Review}</Stepper.Page>
</Stepper>
</Block>
) : (
<div>No metamask detected</div>
)}
</React.Fragment>
)
}

View File

@ -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<Props, State> {
const initialValues = calculateValuesAfterRemoving(index, numOwners, values)
updateInitialProps(initialValues)
this.setState(state => ({
numOwners: state.numOwners - 1,
}))
@ -135,11 +138,11 @@ class SafeOwners extends React.Component<Props, State> {
</Row>
<Hairline />
<Block margin="md" padding="md">
{ [...Array(Number(numOwners))].map((x, index) => {
{[...Array(Number(numOwners))].map((x, index) => {
const addressName = getOwnerAddressBy(index)
return (
<Row key={`owner${(index)}`} className={classes.owner}>
<Row key={`owner${index}`} className={classes.owner}>
<Col xs={4}>
<Field
className={classes.name}
@ -155,13 +158,15 @@ class SafeOwners extends React.Component<Props, State> {
<Field
name={addressName}
component={TextField}
inputAdornment={noErrorsOn(addressName, errors) && {
endAdornment: (
<InputAdornment position="end">
<CheckCircle className={classes.check} />
</InputAdornment>
),
}}
inputAdornment={
noErrorsOn(addressName, errors) && {
endAdornment: (
<InputAdornment position="end">
<CheckCircle className={classes.check} />
</InputAdornment>
),
}
}
type="text"
validate={getAddressValidators(otherAccounts, index)}
placeholder="Owner Address*"
@ -169,17 +174,17 @@ class SafeOwners extends React.Component<Props, State> {
/>
</Col>
<Col xs={1} center="xs" middle="xs" className={classes.remove}>
{ index > 0
&& <Img src={trash} height={20} alt="Delete" onClick={this.onRemoveRow(index)} />
}
{index > 0 && <Img src={trash} height={20} alt="Delete" onClick={this.onRemoveRow(index)} />}
</Col>
</Row>
)
}) }
})}
</Block>
<Row align="center" grow className={classes.add} margin="xl">
<Button color="secondary" onClick={this.onAddOwner}>
<Paragraph weight="bold" size="md" noMargin>{ADD_OWNER_BUTTON}</Paragraph>
<Button color="secondary" onClick={this.onAddOwner} data-testid="add-owner-btn">
<Paragraph weight="bold" size="md" noMargin>
{ADD_OWNER_BUTTON}
</Paragraph>
</Button>
</Row>
</React.Fragment>
@ -202,5 +207,4 @@ const SafeOwnersPage = ({ updateInitialProps }: Object) => (controls: React$Node
</React.Fragment>
)
export default SafeOwnersPage

View File

@ -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) => {
<Field
name={FIELD_CONFIRMATIONS}
component={SelectField}
validate={composeValidators(
required,
mustBeInteger,
minValue(1),
)}
validate={composeValidators(required, mustBeInteger, minValue(1))}
data-testid="threshold-select-input"
>
{
[...Array(Number(numOwners))].map((x, index) => (
<MenuItem key={`selectOwner${index}`} value={`${index + 1}`}>{index + 1}</MenuItem>
))
}
{[...Array(Number(numOwners))].map((x, index) => (
<MenuItem key={`selectOwner${index}`} value={`${index + 1}`}>
{index + 1}
</MenuItem>
))}
</Field>
</Col>
<Col xs={10}>
<Paragraph size="lg" color="primary" noMargin className={classes.owners}>
out of {numOwners} owner(s)
out of
{' '}
{numOwners}
{' '}
owner(s)
</Paragraph>
</Col>
</Row>
@ -85,5 +88,4 @@ const SafeOwnersPage = () => (controls: React$Node, { values }: Object) => (
</React.Fragment>
)
export default SafeOwnersPage

View File

@ -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<string, string>) => {
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<string, string>) => {
// 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<GlobalState>) => {
const transactions: Map<string, List<Transaction>> = await loadSafeTransactions(safeAddress)
// export default (safeAddress: string) => async (dispatch: ReduxDispatch<GlobalState>) => {
// const transactions: Map<string, List<Transaction>> = await loadSafeTransactions(safeAddress)
return dispatch(addTransactions(transactions))
}
// return dispatch(addTransactions(transactions))
// }

View File

@ -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<GlobalState>) => {
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<GlobalState>) => {
const provider = await getProviderInfo()
const walletRecord = makeProvider(provider)
localStore.dispatch(addProvider(walletRecord))
return TestUtils.renderIntoDocument(
return render(
<Provider store={localStore}>
<ConnectedRouter history={history}>
<Open />
@ -29,53 +45,54 @@ const fillOpenSafeForm = async (localStore: Store<GlobalState>) => {
)
}
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<GlobalState>, 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()

View File

@ -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<GlobalState>) => {
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<GlobalState>) => {
const provider = await getProviderInfo()
const walletRecord = makeProvider(provider)
localStore.dispatch(addProvider(walletRecord))
return TestUtils.renderIntoDocument(
return render(
<Provider store={localStore}>
<ConnectedRouter history={history}>
<Load />
@ -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)
})

View File

@ -2,17 +2,9 @@
import * as React from 'react'
type WrapperProps = {
children: React$Node
children: React$Node,
}
class Wrapper extends React.PureComponent<WrapperProps> {
render() {
return (
<React.Fragment>
{ this.props.children }
</React.Fragment>
)
}
}
const Wrapper = ({ children }: WrapperProps) => <React.Fragment>{children}</React.Fragment>
export default Wrapper

156
yarn.lock
View File

@ -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"