pull from dev

This commit is contained in:
mmv 2019-07-25 14:44:43 +04:00
commit c442ed76ae
45 changed files with 537 additions and 341 deletions

View File

@ -23,7 +23,6 @@
"start": "node scripts/start.js",
"storybook": "start-storybook -p 6006",
"test": "NODE_ENV=test && node scripts/test.js --env=jsdom",
"test-local": "NODE_ENV=test && node scripts/test.js --env=jsdom",
"format": "prettier-eslint \"src/**/*.js\" --write"
},
"pre-commit": [
@ -55,7 +54,7 @@
"react-infinite-scroll-component": "^4.5.2",
"react-qr-reader": "^2.2.1",
"react-redux": "7.1.0",
"react-router-dom": "^4.3.1",
"react-router-dom": "^5.0.1",
"recompose": "^0.30.0",
"redux": "4.0.4",
"redux-actions": "^2.3.0",

View File

@ -35,22 +35,29 @@ yarn start
## Running the tests
To run the test, you'll need to migrate contracts `safe-contracts` to the local ganache-cli
1. Run `transaction-history-service`
```
git clone https://github.com/gnosis/safe-transaction-service.git
cd safe-transaction-history
git checkout develop
docker-compose build
docker-compose up -d
```
Check that the service is running at https://localhost:8000
1. Migrating Safe Contracts:
2. Migrate Safe Contracts:
```
git clone https://github.com/gnosis/safe-contracts.git
cd safe-contracts
yarn
ganache-cli -l 7000000
npx truffle migrate
```
2. Migrate Token Contracts for the tests:
3. Migrate Token Contracts for the tests:
Inside `safe-react` directory
```
npx truffle migrate
```
3. Run the tests:
4. Run the tests:
```
yarn test
```

View File

@ -1,14 +1,12 @@
// @flow
import {
TX_SERVICE_HOST,
ENABLED_TX_SERVICE_REMOVAL_SENDER,
SIGNATURES_VIA_METAMASK,
RELAY_API_URL,
} from '~/config/names'
const devConfig = {
[TX_SERVICE_HOST]: 'https://safe-transaction-service.dev.gnosisdev.com/api/v1/',
[ENABLED_TX_SERVICE_REMOVAL_SENDER]: false,
[SIGNATURES_VIA_METAMASK]: false,
[RELAY_API_URL]: 'https://safe-relay.staging.gnosisdev.com/api/v1/',
}

View File

@ -2,7 +2,6 @@
import { ensureOnce } from '~/utils/singleton'
import {
TX_SERVICE_HOST,
ENABLED_TX_SERVICE_REMOVAL_SENDER,
SIGNATURES_VIA_METAMASK,
RELAY_API_URL,
} from '~/config/names'
@ -34,12 +33,6 @@ export const getTxServiceUriFrom = (safeAddress: string) => `safes/${safeAddress
export const getRelayUrl = () => getConfig()[RELAY_API_URL]
export const allowedRemoveSenderInTxHistoryService = () => {
const config = getConfig()
return config[ENABLED_TX_SERVICE_REMOVAL_SENDER]
}
export const signaturesViaMetamask = () => {
const config = getConfig()

View File

@ -1,6 +1,5 @@
// @flow
export const TX_SERVICE_HOST = 'tsh'
export const ENABLED_TX_SERVICE_REMOVAL_SENDER = 'trs'
export const SIGNATURES_VIA_METAMASK = 'svm'
export const RELAY_API_URL = 'rau'

View File

@ -1,14 +1,12 @@
// @flow
import {
TX_SERVICE_HOST,
ENABLED_TX_SERVICE_REMOVAL_SENDER,
SIGNATURES_VIA_METAMASK,
RELAY_API_URL,
} from '~/config/names'
const prodConfig = {
[TX_SERVICE_HOST]: 'https://safe-transaction-service.dev.gnosisdev.com/api/v1/',
[ENABLED_TX_SERVICE_REMOVAL_SENDER]: false,
[SIGNATURES_VIA_METAMASK]: false,
[RELAY_API_URL]: 'https://safe-relay.staging.gnosisdev.com/api/v1/',
}

View File

@ -1,14 +1,12 @@
// @flow
import {
TX_SERVICE_HOST,
ENABLED_TX_SERVICE_REMOVAL_SENDER,
SIGNATURES_VIA_METAMASK,
RELAY_API_URL,
} from '~/config/names'
const testConfig = {
[TX_SERVICE_HOST]: 'https://safe-transaction-service.dev.gnosisdev.com/api/v1/',
[ENABLED_TX_SERVICE_REMOVAL_SENDER]: false,
[TX_SERVICE_HOST]: 'http://localhost:8000/api/v1/',
[SIGNATURES_VIA_METAMASK]: false,
[RELAY_API_URL]: 'https://safe-relay.staging.gnosisdev.com/api/v1',
}

View File

@ -39,19 +39,17 @@ export const approveTransaction = async (
)
const receipt = await safeInstance.approveHash(contractTxHash, { from: sender })
if (process.env.NODE_ENV !== 'test') {
await saveTxToHistory(
safeInstance,
to,
valueInWei,
data,
operation,
nonce,
receipt.tx, // tx hash,
sender,
TX_TYPE_CONFIRMATION,
)
}
await saveTxToHistory(
safeInstance,
to,
valueInWei,
data,
operation,
nonce,
receipt.tx, // tx hash,
sender,
TX_TYPE_CONFIRMATION,
)
return receipt
}
@ -91,19 +89,17 @@ export const executeTransaction = async (
{ from: sender },
)
if (process.env.NODE_ENV !== 'test') {
await saveTxToHistory(
safeInstance,
to,
valueInWei,
data,
operation,
nonce,
receipt.tx, // tx hash,
sender,
TX_TYPE_EXECUTION,
)
}
await saveTxToHistory(
safeInstance,
to,
valueInWei,
data,
operation,
nonce,
receipt.tx, // tx hash,
sender,
TX_TYPE_EXECUTION,
)
return receipt
} catch (error) {

View File

@ -57,6 +57,10 @@ const getProviderName: Function = (web3Provider): boolean => {
const getAccountFrom: Function = async (web3Provider): Promise<string | null> => {
const accounts = await web3Provider.eth.getAccounts()
if (process.env.NODE_ENV === 'test' && window.testAccountIndex) {
return accounts[window.testAccountIndex]
}
return accounts && accounts.length > 0 ? accounts[0] : null
}

View File

@ -22,8 +22,10 @@ import Balances from './Balances'
import Transactions from './TransactionsNew'
import Settings from './Settings'
export const SETTINGS_TAB_BTN_TESTID = 'settings-tab-btn'
export const SAFE_VIEW_NAME_HEADING_TESTID = 'safe-name-heading'
export const BALANCES_TAB_BTN_TEST_ID = 'balances-tab-btn'
export const SETTINGS_TAB_BTN_TEST_ID = 'settings-tab-btn'
export const TRANSACTIONS_TAB_BTN_TEST_ID = 'transactions-tab-btn'
export const SAFE_VIEW_NAME_HEADING_TEST_ID = 'safe-name-heading'
type State = {
tabIndex: number,
@ -126,7 +128,7 @@ class Layout extends React.Component<Props, State> {
<Identicon address={address} diameter={50} />
<Block className={classes.name}>
<Row>
<Heading tag="h2" color="secondary" testId={SAFE_VIEW_NAME_HEADING_TESTID}>
<Heading tag="h2" color="secondary" testId={SAFE_VIEW_NAME_HEADING_TEST_ID}>
{name}
</Heading>
{!granted && <Block className={classes.readonly}>Read Only</Block>}
@ -143,9 +145,9 @@ class Layout extends React.Component<Props, State> {
</Block>
<Row>
<Tabs value={tabIndex} onChange={this.handleChange} indicatorColor="secondary" textColor="secondary">
<Tab label="Balances" />
<Tab label="Transactions" />
<Tab label="Settings" data-testid={SETTINGS_TAB_BTN_TESTID} />
<Tab label="Balances" data-testid={BALANCES_TAB_BTN_TEST_ID} />
<Tab label="Transactions" data-testid={TRANSACTIONS_TAB_BTN_TEST_ID} />
<Tab label="Settings" data-testid={SETTINGS_TAB_BTN_TEST_ID} />
</Tabs>
</Row>
<Hairline color="#c8ced4" />

View File

@ -20,8 +20,8 @@ const controlsStyle = {
padding: sm,
}
export const SAFE_NAME_INPUT_TESTID = 'safe-name-input'
export const SAFE_NAME_SUBMIT_BTN_TESTID = 'change-safe-name-btn'
export const SAFE_NAME_INPUT_TEST_ID = 'safe-name-input'
export const SAFE_NAME_SUBMIT_BTN_TEST_ID = 'change-safe-name-btn'
type Props = {
classes: Object,
@ -59,7 +59,7 @@ const ChangeSafeName = (props: Props) => {
placeholder="Safe name*"
text="Safe name*"
defaultValue={safeName}
testId={SAFE_NAME_INPUT_TESTID}
testId={SAFE_NAME_INPUT_TEST_ID}
/>
</Block>
</Block>
@ -72,7 +72,7 @@ const ChangeSafeName = (props: Props) => {
size="small"
variant="contained"
color="primary"
testId={SAFE_NAME_SUBMIT_BTN_TESTID}
testId={SAFE_NAME_SUBMIT_BTN_TEST_ID}
>
SAVE
</Button>

View File

@ -23,9 +23,9 @@ import {
} from '~/components/forms/validator'
import { styles } from './style'
export const ADD_OWNER_NAME_INPUT_TESTID = 'add-owner-name-input'
export const ADD_OWNER_ADDRESS_INPUT_TESTID = 'add-owner-address-testid'
export const ADD_OWNER_NEXT_BTN_TESTID = 'add-owner-next-btn'
export const ADD_OWNER_NAME_INPUT_TEST_ID = 'add-owner-name-input'
export const ADD_OWNER_ADDRESS_INPUT_TEST_ID = 'add-owner-address-testid'
export const ADD_OWNER_NEXT_BTN_TEST_ID = 'add-owner-next-btn'
type Props = {
onClose: () => void,
@ -71,7 +71,7 @@ const OwnerForm = ({
placeholder="Owner name*"
text="Owner name*"
className={classes.addressInput}
testId={ADD_OWNER_NAME_INPUT_TESTID}
testId={ADD_OWNER_NAME_INPUT_TEST_ID}
/>
</Col>
</Row>
@ -85,7 +85,7 @@ const OwnerForm = ({
placeholder="Owner address*"
text="Owner address*"
className={classes.addressInput}
testId={ADD_OWNER_ADDRESS_INPUT_TESTID}
testId={ADD_OWNER_ADDRESS_INPUT_TEST_ID}
/>
</Col>
</Row>
@ -101,7 +101,7 @@ const OwnerForm = ({
variant="contained"
minWidth={140}
color="primary"
testId={ADD_OWNER_NEXT_BTN_TESTID}
testId={ADD_OWNER_NEXT_BTN_TEST_ID}
>
Next
</Button>

View File

@ -19,7 +19,7 @@ import { getEtherScanLink } from '~/logic/wallets/getWeb3'
import { secondary } from '~/theme/variables'
import { styles } from './style'
export const ADD_OWNER_SUBMIT_BTN_TESTID = 'add-owner-submit-btn'
export const ADD_OWNER_SUBMIT_BTN_TEST_ID = 'add-owner-submit-btn'
const openIconStyle = {
height: '16px',
@ -164,7 +164,7 @@ const ReviewAddOwner = ({
variant="contained"
minWidth={140}
color="primary"
testId={ADD_OWNER_SUBMIT_BTN_TESTID}
testId={ADD_OWNER_SUBMIT_BTN_TEST_ID}
>
Submit
</Button>

View File

@ -20,7 +20,7 @@ import {
} from '~/components/forms/validator'
import { styles } from './style'
export const ADD_OWNER_THRESHOLD_NEXT_BTN_TESTID = 'add-owner-threshold-next-btn'
export const ADD_OWNER_THRESHOLD_NEXT_BTN_TEST_ID = 'add-owner-threshold-next-btn'
type Props = {
onClose: () => void,
@ -110,7 +110,7 @@ owner(s)
variant="contained"
minWidth={140}
color="primary"
testId={ADD_OWNER_THRESHOLD_NEXT_BTN_TESTID}
testId={ADD_OWNER_THRESHOLD_NEXT_BTN_TEST_ID}
>
Review
</Button>

View File

@ -20,8 +20,8 @@ import Modal from '~/components/Modal'
import { styles } from './style'
import { secondary } from '~/theme/variables'
export const RENAME_OWNER_INPUT_TESTID = 'rename-owner-input'
export const SAVE_OWNER_CHANGES_BTN_TESTID = 'save-owner-changes-btn'
export const RENAME_OWNER_INPUT_TEST_ID = 'rename-owner-input'
export const SAVE_OWNER_CHANGES_BTN_TEST_ID = 'save-owner-changes-btn'
const openIconStyle = {
height: '16px',
@ -85,7 +85,7 @@ const EditOwnerComponent = ({
text="Owner name*"
initialValue={selectedOwnerName}
className={classes.addressInput}
testId={RENAME_OWNER_INPUT_TESTID}
testId={RENAME_OWNER_INPUT_TEST_ID}
/>
</Row>
<Row>
@ -105,7 +105,7 @@ const EditOwnerComponent = ({
<Button className={classes.button} minWidth={140} onClick={onClose}>
Cancel
</Button>
<Button type="submit" className={classes.button} variant="contained" minWidth={140} color="primary" testId={SAVE_OWNER_CHANGES_BTN_TESTID}>
<Button type="submit" className={classes.button} variant="contained" minWidth={140} color="primary" testId={SAVE_OWNER_CHANGES_BTN_TEST_ID}>
Save
</Button>
</Row>

View File

@ -17,7 +17,7 @@ import { getEtherScanLink } from '~/logic/wallets/getWeb3'
import { styles } from './style'
import { secondary } from '~/theme/variables'
export const REMOVE_OWNER_MODAL_NEXT_BTN_TESTID = 'remove-owner-next-btn'
export const REMOVE_OWNER_MODAL_NEXT_BTN_TEST_ID = 'remove-owner-next-btn'
const openIconStyle = {
height: '16px',
@ -95,7 +95,7 @@ const CheckOwner = ({
minWidth={140}
color="primary"
onClick={handleSubmit}
testId={REMOVE_OWNER_MODAL_NEXT_BTN_TESTID}
testId={REMOVE_OWNER_MODAL_NEXT_BTN_TEST_ID}
>
Next
</Button>

View File

@ -19,7 +19,7 @@ import { getEtherScanLink } from '~/logic/wallets/getWeb3'
import { secondary } from '~/theme/variables'
import { styles } from './style'
export const REMOVE_OWNER_REVIEW_BTN_TESTID = 'remove-owner-review-btn'
export const REMOVE_OWNER_REVIEW_BTN_TEST_ID = 'remove-owner-review-btn'
const openIconStyle = {
height: '16px',
@ -182,7 +182,7 @@ const ReviewRemoveOwner = ({
variant="contained"
minWidth={140}
color="primary"
testId={REMOVE_OWNER_REVIEW_BTN_TESTID}
testId={REMOVE_OWNER_REVIEW_BTN_TEST_ID}
>
Submit
</Button>

View File

@ -20,7 +20,7 @@ import {
} from '~/components/forms/validator'
import { styles } from './style'
export const REMOVE_OWNER_THRESHOLD_NEXT_BTN_TESTID = 'remove-owner-threshold-next-btn'
export const REMOVE_OWNER_THRESHOLD_NEXT_BTN_TEST_ID = 'remove-owner-threshold-next-btn'
type Props = {
onClose: () => void,
@ -114,7 +114,7 @@ owner(s)
variant="contained"
minWidth={140}
color="primary"
data-testid={REMOVE_OWNER_THRESHOLD_NEXT_BTN_TESTID}
data-testid={REMOVE_OWNER_THRESHOLD_NEXT_BTN_TEST_ID}
>
Review
</Button>

View File

@ -29,9 +29,9 @@ import {
import { styles } from './style'
import { secondary } from '~/theme/variables'
export const REPLACE_OWNER_NAME_INPUT_TESTID = 'replace-owner-name-input'
export const REPLACE_OWNER_ADDRESS_INPUT_TESTID = 'replace-owner-address-testid'
export const REPLACE_OWNER_NEXT_BTN_TESTID = 'replace-owner-next-btn'
export const REPLACE_OWNER_NAME_INPUT_TEST_ID = 'replace-owner-name-input'
export const REPLACE_OWNER_ADDRESS_INPUT_TEST_ID = 'replace-owner-address-testid'
export const REPLACE_OWNER_NEXT_BTN_TEST_ID = 'replace-owner-next-btn'
const openIconStyle = {
height: '16px',
@ -114,7 +114,7 @@ const OwnerForm = ({
placeholder="Owner name*"
text="Owner name*"
className={classes.addressInput}
testId={REPLACE_OWNER_NAME_INPUT_TESTID}
testId={REPLACE_OWNER_NAME_INPUT_TEST_ID}
/>
</Col>
</Row>
@ -128,7 +128,7 @@ const OwnerForm = ({
placeholder="Owner address*"
text="Owner address*"
className={classes.addressInput}
testId={REPLACE_OWNER_ADDRESS_INPUT_TESTID}
testId={REPLACE_OWNER_ADDRESS_INPUT_TEST_ID}
/>
</Col>
</Row>
@ -144,7 +144,7 @@ const OwnerForm = ({
variant="contained"
minWidth={140}
color="primary"
testId={REPLACE_OWNER_NEXT_BTN_TESTID}
testId={REPLACE_OWNER_NEXT_BTN_TEST_ID}
>
Next
</Button>

View File

@ -19,7 +19,7 @@ import { getEtherScanLink } from '~/logic/wallets/getWeb3'
import { secondary } from '~/theme/variables'
import { styles } from './style'
export const REPLACE_OWNER_SUBMIT_BTN_TESTID = 'replace-owner-submit-btn'
export const REPLACE_OWNER_SUBMIT_BTN_TEST_ID = 'replace-owner-submit-btn'
const openIconStyle = {
height: '16px',
@ -210,7 +210,7 @@ const ReviewRemoveOwner = ({
variant="contained"
minWidth={140}
color="primary"
testId={REPLACE_OWNER_SUBMIT_BTN_TESTID}
testId={REPLACE_OWNER_SUBMIT_BTN_TEST_ID}
>
Submit
</Button>

View File

@ -28,11 +28,11 @@ import ReplaceOwnerIcon from './assets/icons/replace-owner.svg'
import RenameOwnerIcon from './assets/icons/rename-owner.svg'
import RemoveOwnerIcon from '../assets/icons/bin.svg'
export const RENAME_OWNER_BTN_TESTID = 'rename-owner-btn'
export const REMOVE_OWNER_BTN_TESTID = 'remove-owner-btn'
export const ADD_OWNER_BTN_TESTID = 'add-owner-btn'
export const REPLACE_OWNER_BTN_TESTID = 'replace-owner-btn'
export const OWNERS_ROW_TESTID = 'owners-row'
export const RENAME_OWNER_BTN_TEST_ID = 'rename-owner-btn'
export const REMOVE_OWNER_BTN_TEST_ID = 'remove-owner-btn'
export const ADD_OWNER_BTN_TEST_ID = 'add-owner-btn'
export const REPLACE_OWNER_BTN_TEST_ID = 'replace-owner-btn'
export const OWNERS_ROW_TEST_ID = 'owners-row'
const controlsStyle = {
backgroundColor: 'white',
@ -144,7 +144,7 @@ class ManageOwners extends React.Component<Props, State> {
noBorder
>
{(sortedData: Array<OwnerRow>) => sortedData.map((row: any, index: number) => (
<TableRow tabIndex={-1} key={index} className={classes.hide} data-testid={OWNERS_ROW_TESTID}>
<TableRow tabIndex={-1} key={index} className={classes.hide} data-testid={OWNERS_ROW_TEST_ID}>
{autoColumns.map((column: Column) => (
<TableCell key={column.id} style={cellWidth(column.width)} align={column.align} component="td">
{column.id === OWNERS_TABLE_ADDRESS_ID ? (
@ -162,14 +162,14 @@ class ManageOwners extends React.Component<Props, State> {
className={classes.editOwnerIcon}
src={RenameOwnerIcon}
onClick={this.onShow('EditOwner', row)}
testId={RENAME_OWNER_BTN_TESTID}
testId={RENAME_OWNER_BTN_TEST_ID}
/>
<Img
alt="Replace owner"
className={classes.replaceOwnerIcon}
src={ReplaceOwnerIcon}
onClick={this.onShow('ReplaceOwner', row)}
testId={REPLACE_OWNER_BTN_TESTID}
testId={REPLACE_OWNER_BTN_TEST_ID}
/>
{ownerData.size > 1 && (
<Img
@ -177,7 +177,7 @@ class ManageOwners extends React.Component<Props, State> {
className={classes.removeOwnerIcon}
src={RemoveOwnerIcon}
onClick={this.onShow('RemoveOwner', row)}
testId={REMOVE_OWNER_BTN_TESTID}
testId={REMOVE_OWNER_BTN_TEST_ID}
/>
)}
</Row>
@ -199,7 +199,7 @@ class ManageOwners extends React.Component<Props, State> {
variant="contained"
color="primary"
onClick={this.onShow('AddOwner')}
testId={ADD_OWNER_BTN_TESTID}
testId={ADD_OWNER_BTN_TEST_ID}
>
Add new owner
</Button>

View File

@ -20,7 +20,7 @@ import actions, { type Actions } from './actions'
import { styles } from './style'
import RemoveSafeIcon from './assets/icons/bin.svg'
export const OWNERS_SETTINGS_TAB_TESTID = 'owner-settings-tab'
export const OWNERS_SETTINGS_TAB_TEST_ID = 'owner-settings-tab'
type State = {
showRemoveSafe: boolean,
@ -121,7 +121,7 @@ class Settings extends React.Component<Props, State> {
<Row
className={cn(classes.menuOption, menuOptionIndex === 2 && classes.active)}
onClick={this.handleChange(2)}
testId={OWNERS_SETTINGS_TAB_TESTID}
testId={OWNERS_SETTINGS_TAB_TEST_ID}
>
Owners (
{owners.size}

View File

@ -5,8 +5,10 @@ import Col from '~/components/layout/Col'
import Row from '~/components/layout/Row'
import Paragraph from '~/components/layout/Paragraph/index'
export const NO_TRANSACTION_ROW_TEST_ID = 'no-transaction-row'
const NoTransactions = () => (
<Row>
<Row data-testid={NO_TRANSACTION_ROW_TEST_ID}>
<Col xs={12} center="xs" sm={10} smOffset={2} start="sm" margin="md">
<Paragraph size="lg">
<Bold>No transactions found for this safe</Bold>

View File

@ -16,6 +16,8 @@ import Paragraph from '~/components/layout/Paragraph'
import { type Transaction } from '~/routes/safe/store/models/transaction'
import { styles } from './style'
export const APPROVE_TX_MODAL_SUBMIT_BTN_TEST_ID = 'approve-tx-modal-submit-btn'
type Props = {
onClose: () => void,
classes: Object,
@ -113,6 +115,7 @@ const ApproveTxModal = ({
minHeight={42}
color="primary"
onClick={approveTx}
testId={APPROVE_TX_MODAL_SUBMIT_BTN_TEST_ID}
>
{title}
</Button>

View File

@ -7,6 +7,9 @@ import Row from '~/components/layout/Row'
import Button from '~/components/layout/Button'
import { sm, lg } from '~/theme/variables'
export const CONFIRM_TX_BTN_TEST_ID = 'confirm-btn'
export const EXECUTE_TX_BTN_TEST_ID = 'execute-btn'
type Props = {
onTxConfirm: Function,
onTxCancel: Function,
@ -54,14 +57,28 @@ const ButtonRow = ({
</Button>
)}
{showConfirmBtn && (
<Button className={classes.button} variant="contained" minWidth={140} color="primary" onClick={onTxConfirm}>
<Button
className={classes.button}
variant="contained"
minWidth={140}
color="primary"
onClick={onTxConfirm}
testId={CONFIRM_TX_BTN_TEST_ID}
>
<EditIcon className={classes.icon} />
{' '}
Confirm TX
</Button>
)}
{showExecuteBtn && (
<Button className={classes.button} variant="contained" minWidth={140} color="primary" onClick={onTxExecute}>
<Button
className={classes.button}
variant="contained"
minWidth={140}
color="primary"
onClick={onTxExecute}
testId={EXECUTE_TX_BTN_TEST_ID}
>
<EditIcon className={classes.icon} />
{' '}
Execute TX

View File

@ -6,9 +6,14 @@ import Bold from '~/components/layout/Bold'
import EtherscanLink from '~/components/EtherscanLink'
import Paragraph from '~/components/layout/Paragraph'
import Block from '~/components/layout/Block'
import { md, lg, secondary } from '~/theme/variables'
import { md, lg } from '~/theme/variables'
import { getTxData } from './utils'
export const TRANSACTIONS_DESC_ADD_OWNER_TEST_ID = 'tx-description-add-owner'
export const TRANSACTIONS_DESC_REMOVE_OWNER_TEST_ID = 'tx-description-remove-owner'
export const TRANSACTIONS_DESC_CHANGE_THRESHOLD_TEST_ID = 'tx-description-change-threshold'
export const TRANSACTIONS_DESC_SEND_TEST_ID = 'tx-description-send'
export const styles = () => ({
txDataContainer: {
padding: `${lg} ${md}`,
@ -33,7 +38,7 @@ type DescriptionDescProps = {
}
const TransferDescription = ({ value = '', symbol, recipient }: TransferDescProps) => (
<Paragraph noMargin>
<Paragraph noMargin data-testid={TRANSACTIONS_DESC_SEND_TEST_ID}>
<Bold>
Send
{' '}
@ -51,21 +56,21 @@ to:
const SettingsDescription = ({ removedOwner, addedOwner, newThreshold }: DescriptionDescProps) => (
<>
{removedOwner && (
<Paragraph>
<Paragraph data-testid={TRANSACTIONS_DESC_REMOVE_OWNER_TEST_ID}>
<Bold>Remove owner:</Bold>
<br />
<EtherscanLink type="address" value={removedOwner} />
</Paragraph>
)}
{addedOwner && (
<Paragraph>
<Paragraph data-testid={TRANSACTIONS_DESC_ADD_OWNER_TEST_ID}>
<Bold>Add owner:</Bold>
<br />
<EtherscanLink type="address" value={addedOwner} />
</Paragraph>
)}
{newThreshold && (
<Paragraph>
<Paragraph data-testid={TRANSACTIONS_DESC_CHANGE_THRESHOLD_TEST_ID}>
<Bold>Change required confirmations:</Bold>
<br />
{newThreshold}

View File

@ -39,9 +39,9 @@ export const getTxData = (tx: Transaction): DecodedTxData => {
txData.addedOwner = tx.decodedParams.args[0]
txData.newThreshold = tx.decodedParams.args[1]
} else if (tx.decodedParams.methodName === 'swapOwner') {
txData.addedOwner = tx.decodedParams.args[0]
txData.newThreshold = tx.decodedParams.args[0]
txData.removedOwner = tx.decodedParams.args[1]
txData.newThreshold = tx.decodedParams.args[2]
txData.addedOwner = tx.decodedParams.args[2]
}
/* eslint-enable */
}

View File

@ -22,6 +22,8 @@ import {
import { styles } from './style'
import Status from './Status'
export const TRANSACTION_ROW_TEST_ID = 'transaction-row'
const expandCellStyle = {
paddingLeft: 0,
paddingRight: 0,
@ -52,8 +54,8 @@ const TxsTable = ({
}: Props) => {
const [expandedTx, setExpandedTx] = useState<string | null>(null)
const handleTxExpand = (creationTxHash) => {
setExpandedTx(prevTx => (prevTx === creationTxHash ? null : creationTxHash))
const handleTxExpand = (safeTxHash) => {
setExpandedTx(prevTx => (prevTx === safeTxHash ? null : safeTxHash))
}
const columns = generateColumns()
@ -75,8 +77,9 @@ const TxsTable = ({
<React.Fragment key={index}>
<TableRow
tabIndex={-1}
className={cn(classes.row, expandedTx === row.tx.creationTxHash && classes.expandedRow)}
onClick={() => handleTxExpand(row.tx.creationTxHash)}
className={cn(classes.row, expandedTx === row.tx.safeTxHash && classes.expandedRow)}
onClick={() => handleTxExpand(row.tx.safeTxHash)}
data-testid={TRANSACTION_ROW_TEST_ID}
>
{autoColumns.map((column: Column) => (
<TableCell
@ -95,7 +98,7 @@ const TxsTable = ({
</Row>
</TableCell>
<TableCell style={expandCellStyle}>
<IconButton disableRipple>{expandedTx === row.nonce ? <ExpandLess /> : <ExpandMore />}</IconButton>
<IconButton disableRipple>{expandedTx === row.safeTxHash ? <ExpandLess /> : <ExpandMore />}</IconButton>
</TableCell>
</TableRow>
<TableRow>
@ -105,7 +108,7 @@ const TxsTable = ({
className={classes.extendedTxContainer}
>
<Collapse
in={expandedTx === row.tx.creationTxHash}
in={expandedTx === row.tx.safeTxHash}
timeout="auto"
component={ExpandedTxComponent}
unmountOnExit

View File

@ -14,6 +14,8 @@ export type Props = Actions &
const TIMEOUT = process.env.NODE_ENV === 'test' ? 1500 : 5000
class SafeView extends React.Component<Props> {
intervalId: IntervalID
componentDidMount() {
const {
fetchSafe, activeTokens, safeUrl, fetchTokenBalances, fetchTokens,
@ -44,15 +46,12 @@ class SafeView extends React.Component<Props> {
checkForUpdates() {
const {
safeUrl, activeTokens, fetchSafe, fetchTokenBalances,
safeUrl, activeTokens, fetchTokenBalances,
} = this.props
fetchSafe(safeUrl, true)
fetchTokenBalances(safeUrl, activeTokens)
}
intervalId: IntervalID
render() {
const {
safe,

View File

@ -34,9 +34,7 @@ const createTransaction = (
openSnackbar('Approval transaction has been confirmed', 'success')
}
if (!process.env.NODE_ENV === 'test') {
dispatch(fetchTransactions(safeAddress))
}
dispatch(fetchTransactions(safeAddress))
return txHash
}

View File

@ -38,7 +38,11 @@ type TxServiceModel = {
isExecuted: boolean,
}
const buildTransactionFrom = async (safeAddress: string, tx: TxServiceModel, safeSubjects: Map<string, string>) => {
export const buildTransactionFrom = async (
safeAddress: string,
tx: TxServiceModel,
safeSubjects: Map<string, string>,
) => {
const name = safeSubjects.get(String(tx.nonce)) || 'Unknown'
const storedOwners = await getOwners(safeAddress)
const confirmations = List(
@ -55,7 +59,6 @@ const buildTransactionFrom = async (safeAddress: string, tx: TxServiceModel, saf
const modifySettingsTx = tx.to === safeAddress && Number(tx.value) === 0 && !!tx.data
const cancellationTx = tx.to === safeAddress && Number(tx.value) === 0 && !tx.data
const isTokenTransfer = await isAddressAToken(tx.to)
const creationTxHash = confirmations.last().hash
let executionTxHash
const executionTx = confirmations.find(conf => conf.type === TX_TYPE_EXECUTION)
@ -76,9 +79,7 @@ const buildTransactionFrom = async (safeAddress: string, tx: TxServiceModel, saf
recipient: params[0],
value: params[1],
}
} else if (
modifySettingsTx && tx.data
) {
} else if (modifySettingsTx && tx.data) {
decodedParams = await decodeParamsFromSafeMethod(tx.data)
}
@ -94,7 +95,7 @@ const buildTransactionFrom = async (safeAddress: string, tx: TxServiceModel, saf
submissionDate: tx.submissionDate,
executionDate: tx.executionDate,
executionTxHash,
creationTxHash,
safeTxHash: tx.safeTxHash,
isTokenTransfer,
decodedParams,
modifySettingsTx,

View File

@ -44,7 +44,6 @@ const processTransaction = (
const shouldExecute = threshold === tx.confirmations.size || approveAndExecute
const sigs = generateSignaturesFromTxConfirmations(tx, approveAndExecute && userAddress)
let txHash
if (shouldExecute) {
openSnackbar('Transaction has been submitted', 'success')
@ -56,9 +55,7 @@ const processTransaction = (
openSnackbar('Approval transaction has been confirmed', 'success')
}
if (!process.env.NODE_ENV === 'test') {
dispatch(fetchTransactions(safeAddress))
}
dispatch(fetchTransactions(safeAddress))
return txHash
}

View File

@ -18,7 +18,7 @@ export type TransactionProps = {
symbol: string,
modifySettingsTx: boolean,
cancellationTx: boolean,
creationTxHash: string,
safeTxHash: string,
executionTxHash?: string,
cancelled?: boolean,
status?: TransactionStatus,
@ -38,7 +38,7 @@ export const makeTransaction: RecordFactory<TransactionProps> = Record({
executionDate: '',
symbol: '',
executionTxHash: undefined,
creationTxHash: '',
safeTxHash: '',
cancelled: false,
modifySettingsTx: false,
cancellationTx: false,

View File

@ -13,11 +13,12 @@ import { calculateBalanceOf } from '~/routes/safe/store/actions/fetchTokenBalanc
import updateActiveTokens from '~/routes/safe/store/actions/updateActiveTokens'
import '@testing-library/jest-dom/extend-expect'
import updateSafe from '~/routes/safe/store/actions/updateSafe'
import { checkRegisteredTxSend, fillAndSubmitSendFundsForm } from './utils/transactions'
import { BALANCE_ROW_TEST_ID } from '~/routes/safe/components/Balances'
afterEach(cleanup)
describe('DOM > Feature > Funds', () => {
describe('DOM > Feature > Sending Funds', () => {
let store
let safeAddress: string
let accounts
@ -31,8 +32,11 @@ describe('DOM > Feature > Funds', () => {
it('Sends ETH with threshold = 1', async () => {
// GIVEN
const ethAmount = '5'
await sendEtherTo(safeAddress, ethAmount)
const balanceAfterSendingEthToSafe = await getBalanceInEtherOf(accounts[0])
// the tests are run in parallel, lets use account 9 because it's not used anywhere else
// (in other tests we trigger transactions and pay gas for it, so we can't really make reliable
// assumptions on account's ETH balance)
await sendEtherTo(safeAddress, ethAmount, 9)
// WHEN
const SafeDom = renderSafeView(store, safeAddress)
@ -44,29 +48,21 @@ describe('DOM > Feature > Funds', () => {
const sendButton = SafeDom.getByTestId('balance-send-btn')
fireEvent.click(sendButton)
// Fill first send funds screen
const recipientInput = SafeDom.getByPlaceholderText('Recipient*')
const amountInput = SafeDom.getByPlaceholderText('Amount*')
const reviewBtn = SafeDom.getByTestId('review-tx-btn')
fireEvent.change(recipientInput, { target: { value: accounts[0] } })
fireEvent.change(amountInput, { target: { value: ethAmount } })
await sleep(200)
fireEvent.click(reviewBtn)
// Submit the tx (Review Tx screen)
const submitBtn = SafeDom.getByTestId('submit-tx-btn')
fireEvent.click(submitBtn)
await sleep(1000)
const receiverBalanceBeforeTx = await getBalanceInEtherOf(accounts[9])
await fillAndSubmitSendFundsForm(SafeDom, sendButton, ethAmount, accounts[9])
// THEN
const safeFunds = await getBalanceInEtherOf(safeAddress)
expect(Number(safeFunds)).toBe(0)
const receiverBalanceAfterTx = await getBalanceInEtherOf(accounts[9])
const receiverFunds = await getBalanceInEtherOf(accounts[0])
const ESTIMATED_GASCOSTS = 0.3
expect(Number(parseInt(receiverFunds, 10) - parseInt(balanceAfterSendingEthToSafe, 10))).toBeGreaterThan(
expect(Number(parseInt(receiverBalanceAfterTx, 10) - parseInt(receiverBalanceBeforeTx, 10))).toBeGreaterThan(
parseInt(ethAmount, 10) - ESTIMATED_GASCOSTS,
)
// Check that the transaction was registered
await checkRegisteredTxSend(SafeDom, ethAmount, 'ETH', accounts[9])
})
it('Sends Tokens with threshold = 1', async () => {
@ -97,26 +93,16 @@ describe('DOM > Feature > Funds', () => {
expect(balanceRows.length).toBe(2)
const sendButtons = SafeDom.getAllByTestId('balance-send-btn')
expect(sendButtons.length).toBe(2)
fireEvent.click(sendButtons[1])
// Fill first send funds screen
const recipientInput = SafeDom.getByPlaceholderText('Recipient*')
const amountInput = SafeDom.getByPlaceholderText('Amount*')
const reviewBtn = SafeDom.getByTestId('review-tx-btn')
fireEvent.change(recipientInput, { target: { value: tokenReceiver } })
fireEvent.change(amountInput, { target: { value: tokensAmount } })
await sleep(200)
fireEvent.click(reviewBtn)
// Submit the tx (Review Tx screen)
const submitBtn = SafeDom.getByTestId('submit-tx-btn')
fireEvent.click(submitBtn)
await sleep(1000)
await fillAndSubmitSendFundsForm(SafeDom, sendButtons[1], tokensAmount, tokenReceiver)
// THEN
const safeFunds = await calculateBalanceOf(tokenAddress, safeAddress, 18)
expect(Number(safeFunds)).toBe(0)
const receiverFunds = await calculateBalanceOf(tokenAddress, tokenReceiver, 18)
expect(receiverFunds).toBe(tokensAmount)
// Check that the transaction was registered
await checkRegisteredTxSend(SafeDom, tokensAmount, 'OMG', tokenReceiver)
})
})

View File

@ -0,0 +1,84 @@
// @flow
import { fireEvent, cleanup } from '@testing-library/react'
import { aNewStore } from '~/store'
import { aMinedSafe } from '~/test/builder/safe.redux.builder'
import { sendEtherTo } from '~/test/utils/tokenMovements'
import { renderSafeView } from '~/test/builder/safe.dom.utils'
import { getWeb3, getBalanceInEtherOf } from '~/logic/wallets/getWeb3'
import { sleep } from '~/utils/timer'
import '@testing-library/jest-dom/extend-expect'
import { BALANCE_ROW_TEST_ID } from '~/routes/safe/components/Balances'
import { fillAndSubmitSendFundsForm } from './utils/transactions'
import { TRANSACTIONS_TAB_BTN_TEST_ID } from '~/routes/safe/components/Layout'
import { TRANSACTION_ROW_TEST_ID } from '~/routes/safe/components/TransactionsNew/TxsTable'
import { useTestAccountAt, resetTestAccount } from './utils/accounts'
import { CONFIRM_TX_BTN_TEST_ID, EXECUTE_TX_BTN_TEST_ID } from '~/routes/safe/components/TransactionsNew/TxsTable/ExpandedTx/OwnersColumn/ButtonRow'
import { APPROVE_TX_MODAL_SUBMIT_BTN_TEST_ID } from '~/routes/safe/components/TransactionsNew/TxsTable/ExpandedTx/ApproveTxModal'
afterEach(cleanup)
afterEach(resetTestAccount)
describe('DOM > Feature > Sending Funds', () => {
let store
let safeAddress: string
let accounts
beforeEach(async () => {
store = aNewStore()
// using 4th account because other accounts were used in other tests and paid gas
safeAddress = await aMinedSafe(store, 2, 2)
accounts = await getWeb3().eth.getAccounts()
})
it('Sends ETH with threshold = 2', async () => {
// GIVEN
const ethAmount = '5'
await sendEtherTo(safeAddress, ethAmount)
const balanceAfterSendingEthToSafe = await getBalanceInEtherOf(accounts[0])
// WHEN
const SafeDom = renderSafeView(store, safeAddress)
await sleep(1300)
// Open send funds modal
const balanceRows = SafeDom.getAllByTestId(BALANCE_ROW_TEST_ID)
expect(balanceRows[0]).toHaveTextContent(`${ethAmount} ETH`)
const sendButton = SafeDom.getByTestId('balance-send-btn')
fireEvent.click(sendButton)
await fillAndSubmitSendFundsForm(SafeDom, sendButton, ethAmount, accounts[0])
// CONFIRM TX
fireEvent.click(SafeDom.getByTestId(TRANSACTIONS_TAB_BTN_TEST_ID))
await sleep(200)
useTestAccountAt(1)
await sleep(2200)
const txRows = SafeDom.getAllByTestId(TRANSACTION_ROW_TEST_ID)
expect(txRows.length).toBe(1)
fireEvent.click(txRows[0])
await sleep(100)
fireEvent.click(SafeDom.getByTestId(CONFIRM_TX_BTN_TEST_ID))
await sleep(100)
// Travel confirm modal
fireEvent.click(SafeDom.getByTestId(APPROVE_TX_MODAL_SUBMIT_BTN_TEST_ID))
await sleep(2000)
// EXECUTE TX
fireEvent.click(SafeDom.getByTestId(EXECUTE_TX_BTN_TEST_ID))
await sleep(100)
fireEvent.click(SafeDom.getByTestId(APPROVE_TX_MODAL_SUBMIT_BTN_TEST_ID))
await sleep(500)
// THEN
const safeFunds = await getBalanceInEtherOf(safeAddress)
expect(Number(safeFunds)).toBe(0)
const receiverFunds = await getBalanceInEtherOf(accounts[0])
const ESTIMATED_GASCOSTS = 0.3
expect(Number(parseInt(receiverFunds, 10) - parseInt(balanceAfterSendingEthToSafe, 10))).toBeGreaterThan(
parseInt(ethAmount, 10) - ESTIMATED_GASCOSTS,
)
})
})

View File

@ -5,8 +5,8 @@ import { aMinedSafe } from '~/test/builder/safe.redux.builder'
import { renderSafeView } from '~/test/builder/safe.dom.utils'
import { sleep } from '~/utils/timer'
import '@testing-library/jest-dom/extend-expect'
import { SETTINGS_TAB_BTN_TESTID, SAFE_VIEW_NAME_HEADING_TESTID } from '~/routes/safe/components/Layout'
import { SAFE_NAME_INPUT_TESTID, SAFE_NAME_SUBMIT_BTN_TESTID } from '~/routes/safe/components/Settings/ChangeSafeName'
import { SETTINGS_TAB_BTN_TEST_ID, SAFE_VIEW_NAME_HEADING_TEST_ID } from '~/routes/safe/components/Layout'
import { SAFE_NAME_INPUT_TEST_ID, SAFE_NAME_SUBMIT_BTN_TEST_ID } from '~/routes/safe/components/Settings/ChangeSafeName'
afterEach(cleanup)
@ -26,17 +26,17 @@ describe('DOM > Feature > Settings - Name', () => {
const SafeDom = renderSafeView(store, safeAddress)
await sleep(1300)
const safeNameHeading = SafeDom.getByTestId(SAFE_VIEW_NAME_HEADING_TESTID)
const safeNameHeading = SafeDom.getByTestId(SAFE_VIEW_NAME_HEADING_TEST_ID)
expect(safeNameHeading).toHaveTextContent(INITIAL_NAME)
// Open settings tab
// Safe name setting screen should be pre-selected
const settingsBtn = SafeDom.getByTestId(SETTINGS_TAB_BTN_TESTID)
const settingsBtn = SafeDom.getByTestId(SETTINGS_TAB_BTN_TEST_ID)
fireEvent.click(settingsBtn)
// Change the name
const safeNameInput = SafeDom.getByTestId(SAFE_NAME_INPUT_TESTID)
const submitBtn = SafeDom.getByTestId(SAFE_NAME_SUBMIT_BTN_TESTID)
const safeNameInput = SafeDom.getByTestId(SAFE_NAME_INPUT_TEST_ID)
const submitBtn = SafeDom.getByTestId(SAFE_NAME_SUBMIT_BTN_TEST_ID)
fireEvent.change(safeNameInput, { target: { value: NEW_NAME } })
fireEvent.click(submitBtn)

View File

@ -5,35 +5,40 @@ import { aMinedSafe } from '~/test/builder/safe.redux.builder'
import { renderSafeView } from '~/test/builder/safe.dom.utils'
import { sleep } from '~/utils/timer'
import '@testing-library/jest-dom/extend-expect'
import { SETTINGS_TAB_BTN_TESTID } from '~/routes/safe/components/Layout'
import { OWNERS_SETTINGS_TAB_TESTID } from '~/routes/safe/components/Settings'
import {
RENAME_OWNER_BTN_TESTID,
OWNERS_ROW_TESTID,
REMOVE_OWNER_BTN_TESTID,
ADD_OWNER_BTN_TESTID,
REPLACE_OWNER_BTN_TESTID,
checkRegisteredTxAddOwner,
checkRegisteredTxRemoveOwner,
checkRegisteredTxReplaceOwner,
} from './utils/transactions'
import { SETTINGS_TAB_BTN_TEST_ID } from '~/routes/safe/components/Layout'
import { OWNERS_SETTINGS_TAB_TEST_ID } from '~/routes/safe/components/Settings'
import {
RENAME_OWNER_BTN_TEST_ID,
OWNERS_ROW_TEST_ID,
REMOVE_OWNER_BTN_TEST_ID,
ADD_OWNER_BTN_TEST_ID,
REPLACE_OWNER_BTN_TEST_ID,
} from '~/routes/safe/components/Settings/ManageOwners'
import {
RENAME_OWNER_INPUT_TESTID,
SAVE_OWNER_CHANGES_BTN_TESTID,
RENAME_OWNER_INPUT_TEST_ID,
SAVE_OWNER_CHANGES_BTN_TEST_ID,
} from '~/routes/safe/components/Settings/ManageOwners/EditOwnerModal'
import { REMOVE_OWNER_MODAL_NEXT_BTN_TESTID } from '~/routes/safe/components/Settings/ManageOwners/RemoveOwnerModal/screens/CheckOwner'
import { REMOVE_OWNER_THRESHOLD_NEXT_BTN_TESTID } from '~/routes/safe/components/Settings/ManageOwners/RemoveOwnerModal/screens/ThresholdForm'
import { REMOVE_OWNER_REVIEW_BTN_TESTID } from '~/routes/safe/components/Settings/ManageOwners/RemoveOwnerModal/screens/Review'
import { ADD_OWNER_THRESHOLD_NEXT_BTN_TESTID } from '~/routes/safe/components/Settings/ManageOwners/AddOwnerModal/screens/ThresholdForm'
import { REMOVE_OWNER_MODAL_NEXT_BTN_TEST_ID } from '~/routes/safe/components/Settings/ManageOwners/RemoveOwnerModal/screens/CheckOwner'
import { REMOVE_OWNER_THRESHOLD_NEXT_BTN_TEST_ID } from '~/routes/safe/components/Settings/ManageOwners/RemoveOwnerModal/screens/ThresholdForm'
import { REMOVE_OWNER_REVIEW_BTN_TEST_ID } from '~/routes/safe/components/Settings/ManageOwners/RemoveOwnerModal/screens/Review'
import { ADD_OWNER_THRESHOLD_NEXT_BTN_TEST_ID } from '~/routes/safe/components/Settings/ManageOwners/AddOwnerModal/screens/ThresholdForm'
import {
ADD_OWNER_NAME_INPUT_TESTID,
ADD_OWNER_ADDRESS_INPUT_TESTID,
ADD_OWNER_NEXT_BTN_TESTID,
ADD_OWNER_NAME_INPUT_TEST_ID,
ADD_OWNER_ADDRESS_INPUT_TEST_ID,
ADD_OWNER_NEXT_BTN_TEST_ID,
} from '~/routes/safe/components/Settings/ManageOwners/AddOwnerModal/screens/OwnerForm'
import { ADD_OWNER_SUBMIT_BTN_TESTID } from '~/routes/safe/components/Settings/ManageOwners/AddOwnerModal/screens/Review'
import { ADD_OWNER_SUBMIT_BTN_TEST_ID } from '~/routes/safe/components/Settings/ManageOwners/AddOwnerModal/screens/Review'
import {
REPLACE_OWNER_NEXT_BTN_TESTID,
REPLACE_OWNER_NAME_INPUT_TESTID,
REPLACE_OWNER_ADDRESS_INPUT_TESTID,
REPLACE_OWNER_NEXT_BTN_TEST_ID,
REPLACE_OWNER_NAME_INPUT_TEST_ID,
REPLACE_OWNER_ADDRESS_INPUT_TEST_ID,
} from '~/routes/safe/components/Settings/ManageOwners/ReplaceOwnerModal/screens/OwnerForm'
import { REPLACE_OWNER_SUBMIT_BTN_TESTID } from '~/routes/safe/components/Settings/ManageOwners/ReplaceOwnerModal/screens/Review'
import { REPLACE_OWNER_SUBMIT_BTN_TEST_ID } from '~/routes/safe/components/Settings/ManageOwners/ReplaceOwnerModal/screens/Review'
afterEach(cleanup)
@ -52,28 +57,28 @@ describe('DOM > Feature > Settings - Manage owners', () => {
await sleep(1300)
// Travel to settings
const settingsBtn = SafeDom.getByTestId(SETTINGS_TAB_BTN_TESTID)
const settingsBtn = SafeDom.getByTestId(SETTINGS_TAB_BTN_TEST_ID)
fireEvent.click(settingsBtn)
await sleep(200)
// click on owners settings
const ownersSettingsBtn = SafeDom.getByTestId(OWNERS_SETTINGS_TAB_TESTID)
const ownersSettingsBtn = SafeDom.getByTestId(OWNERS_SETTINGS_TAB_TEST_ID)
fireEvent.click(ownersSettingsBtn)
await sleep(200)
// open rename owner modal
const renameOwnerBtn = SafeDom.getByTestId(RENAME_OWNER_BTN_TESTID)
const renameOwnerBtn = SafeDom.getByTestId(RENAME_OWNER_BTN_TEST_ID)
fireEvent.click(renameOwnerBtn)
// rename owner
const ownerNameInput = SafeDom.getByTestId(RENAME_OWNER_INPUT_TESTID)
const saveOwnerChangesBtn = SafeDom.getByTestId(SAVE_OWNER_CHANGES_BTN_TESTID)
const ownerNameInput = SafeDom.getByTestId(RENAME_OWNER_INPUT_TEST_ID)
const saveOwnerChangesBtn = SafeDom.getByTestId(SAVE_OWNER_CHANGES_BTN_TEST_ID)
fireEvent.change(ownerNameInput, { target: { value: NEW_OWNER_NAME } })
fireEvent.click(saveOwnerChangesBtn)
await sleep(200)
// check if the name updated
const ownerRow = SafeDom.getByTestId(OWNERS_ROW_TESTID)
const ownerRow = SafeDom.getByTestId(OWNERS_ROW_TEST_ID)
expect(ownerRow).toHaveTextContent(NEW_OWNER_NAME)
})
@ -84,17 +89,17 @@ describe('DOM > Feature > Settings - Manage owners', () => {
await sleep(1300)
// Travel to settings
const settingsBtn = SafeDom.getByTestId(SETTINGS_TAB_BTN_TESTID)
const settingsBtn = SafeDom.getByTestId(SETTINGS_TAB_BTN_TEST_ID)
fireEvent.click(settingsBtn)
await sleep(200)
// click on owners settings
const ownersSettingsBtn = SafeDom.getByTestId(OWNERS_SETTINGS_TAB_TESTID)
const ownersSettingsBtn = SafeDom.getByTestId(OWNERS_SETTINGS_TAB_TEST_ID)
fireEvent.click(ownersSettingsBtn)
await sleep(200)
// check if there are 2 owners
let ownerRows = SafeDom.getAllByTestId(OWNERS_ROW_TESTID)
let ownerRows = SafeDom.getAllByTestId(OWNERS_ROW_TEST_ID)
expect(ownerRows.length).toBe(2)
expect(ownerRows[0]).toHaveTextContent('Adol 1 Eth Account')
expect(ownerRows[0]).toHaveTextContent('0x90F8bf6A479f320ead074411a4B0e7944Ea8c9C1')
@ -102,25 +107,28 @@ describe('DOM > Feature > Settings - Manage owners', () => {
expect(ownerRows[1]).toHaveTextContent('0xFFcf8FDEE72ac11b5c542428B35EEF5769C409f0')
// click remove owner btn which opens the modal
const removeOwnerBtn = SafeDom.getAllByTestId(REMOVE_OWNER_BTN_TESTID)[1]
const removeOwnerBtn = SafeDom.getAllByTestId(REMOVE_OWNER_BTN_TEST_ID)[1]
fireEvent.click(removeOwnerBtn)
// modal navigation
const nextBtnStep1 = SafeDom.getByTestId(REMOVE_OWNER_MODAL_NEXT_BTN_TESTID)
const nextBtnStep1 = SafeDom.getByTestId(REMOVE_OWNER_MODAL_NEXT_BTN_TEST_ID)
fireEvent.click(nextBtnStep1)
const nextBtnStep2 = SafeDom.getByTestId(REMOVE_OWNER_THRESHOLD_NEXT_BTN_TESTID)
const nextBtnStep2 = SafeDom.getByTestId(REMOVE_OWNER_THRESHOLD_NEXT_BTN_TEST_ID)
fireEvent.click(nextBtnStep2)
const nextBtnStep3 = SafeDom.getByTestId(REMOVE_OWNER_REVIEW_BTN_TESTID)
const nextBtnStep3 = SafeDom.getByTestId(REMOVE_OWNER_REVIEW_BTN_TEST_ID)
fireEvent.click(nextBtnStep3)
await sleep(1300)
// check if owner was removed
ownerRows = SafeDom.getAllByTestId(OWNERS_ROW_TESTID)
ownerRows = SafeDom.getAllByTestId(OWNERS_ROW_TEST_ID)
expect(ownerRows.length).toBe(1)
expect(ownerRows[0]).toHaveTextContent('Adol 1 Eth Account')
expect(ownerRows[0]).toHaveTextContent('0x90F8bf6A479f320ead074411a4B0e7944Ea8c9C1')
// Check that the transaction was registered
await checkRegisteredTxRemoveOwner(SafeDom, '0xFFcf8FDEE72ac11b5c542428B35EEF5769C409f0')
})
it('Adds a new owner', async () => {
@ -131,46 +139,49 @@ describe('DOM > Feature > Settings - Manage owners', () => {
await sleep(1300)
// Travel to settings
const settingsBtn = SafeDom.getByTestId(SETTINGS_TAB_BTN_TESTID)
const settingsBtn = SafeDom.getByTestId(SETTINGS_TAB_BTN_TEST_ID)
fireEvent.click(settingsBtn)
await sleep(200)
// click on owners settings
const ownersSettingsBtn = SafeDom.getByTestId(OWNERS_SETTINGS_TAB_TESTID)
const ownersSettingsBtn = SafeDom.getByTestId(OWNERS_SETTINGS_TAB_TEST_ID)
fireEvent.click(ownersSettingsBtn)
await sleep(200)
// check if there is 1 owner
let ownerRows = SafeDom.getAllByTestId(OWNERS_ROW_TESTID)
let ownerRows = SafeDom.getAllByTestId(OWNERS_ROW_TEST_ID)
expect(ownerRows.length).toBe(1)
expect(ownerRows[0]).toHaveTextContent('Adol 1 Eth Account')
expect(ownerRows[0]).toHaveTextContent('0x90F8bf6A479f320ead074411a4B0e7944Ea8c9C1')
// click add owner btn
fireEvent.click(SafeDom.getByTestId(ADD_OWNER_BTN_TESTID))
fireEvent.click(SafeDom.getByTestId(ADD_OWNER_BTN_TEST_ID))
await sleep(200)
// fill and travel add owner modal
const ownerNameInput = SafeDom.getByTestId(ADD_OWNER_NAME_INPUT_TESTID)
const ownerAddressInput = SafeDom.getByTestId(ADD_OWNER_ADDRESS_INPUT_TESTID)
const nextBtn = SafeDom.getByTestId(ADD_OWNER_NEXT_BTN_TESTID)
const ownerNameInput = SafeDom.getByTestId(ADD_OWNER_NAME_INPUT_TEST_ID)
const ownerAddressInput = SafeDom.getByTestId(ADD_OWNER_ADDRESS_INPUT_TEST_ID)
const nextBtn = SafeDom.getByTestId(ADD_OWNER_NEXT_BTN_TEST_ID)
fireEvent.change(ownerNameInput, { target: { value: NEW_OWNER_NAME } })
fireEvent.change(ownerAddressInput, { target: { value: NEW_OWNER_ADDRESS } })
fireEvent.click(nextBtn)
await sleep(200)
fireEvent.click(SafeDom.getByTestId(ADD_OWNER_THRESHOLD_NEXT_BTN_TESTID))
fireEvent.click(SafeDom.getByTestId(ADD_OWNER_THRESHOLD_NEXT_BTN_TEST_ID))
await sleep(200)
fireEvent.click(SafeDom.getByTestId(ADD_OWNER_SUBMIT_BTN_TESTID))
fireEvent.click(SafeDom.getByTestId(ADD_OWNER_SUBMIT_BTN_TEST_ID))
await sleep(1500)
// check if owner was added
ownerRows = SafeDom.getAllByTestId(OWNERS_ROW_TESTID)
ownerRows = SafeDom.getAllByTestId(OWNERS_ROW_TEST_ID)
expect(ownerRows.length).toBe(2)
expect(ownerRows[0]).toHaveTextContent('Adol 1 Eth Account')
expect(ownerRows[0]).toHaveTextContent('0x90F8bf6A479f320ead074411a4B0e7944Ea8c9C1')
expect(ownerRows[1]).toHaveTextContent(NEW_OWNER_NAME)
expect(ownerRows[1]).toHaveTextContent(NEW_OWNER_ADDRESS)
// Check that the transaction was registered
await checkRegisteredTxAddOwner(SafeDom, NEW_OWNER_ADDRESS)
})
it('Replaces an owner', async () => {
@ -183,17 +194,17 @@ describe('DOM > Feature > Settings - Manage owners', () => {
await sleep(1300)
// Travel to settings
const settingsBtn = SafeDom.getByTestId(SETTINGS_TAB_BTN_TESTID)
const settingsBtn = SafeDom.getByTestId(SETTINGS_TAB_BTN_TEST_ID)
fireEvent.click(settingsBtn)
await sleep(200)
// click on owners settings
const ownersSettingsBtn = SafeDom.getByTestId(OWNERS_SETTINGS_TAB_TESTID)
const ownersSettingsBtn = SafeDom.getByTestId(OWNERS_SETTINGS_TAB_TEST_ID)
fireEvent.click(ownersSettingsBtn)
await sleep(200)
// check if there are 2 owners
let ownerRows = SafeDom.getAllByTestId(OWNERS_ROW_TESTID)
let ownerRows = SafeDom.getAllByTestId(OWNERS_ROW_TEST_ID)
expect(ownerRows.length).toBe(2)
expect(ownerRows[0]).toHaveTextContent('Adol 1 Eth Account')
expect(ownerRows[0]).toHaveTextContent('0x90F8bf6A479f320ead074411a4B0e7944Ea8c9C1')
@ -201,28 +212,31 @@ describe('DOM > Feature > Settings - Manage owners', () => {
expect(ownerRows[1]).toHaveTextContent('0xFFcf8FDEE72ac11b5c542428B35EEF5769C409f0')
// click replace owner btn which opens the modal
const replaceOwnerBtn = SafeDom.getAllByTestId(REPLACE_OWNER_BTN_TESTID)[1]
const replaceOwnerBtn = SafeDom.getAllByTestId(REPLACE_OWNER_BTN_TEST_ID)[1]
fireEvent.click(replaceOwnerBtn)
// fill and travel add owner modal
const ownerNameInput = SafeDom.getByTestId(REPLACE_OWNER_NAME_INPUT_TESTID)
const ownerAddressInput = SafeDom.getByTestId(REPLACE_OWNER_ADDRESS_INPUT_TESTID)
const nextBtn = SafeDom.getByTestId(REPLACE_OWNER_NEXT_BTN_TESTID)
const ownerNameInput = SafeDom.getByTestId(REPLACE_OWNER_NAME_INPUT_TEST_ID)
const ownerAddressInput = SafeDom.getByTestId(REPLACE_OWNER_ADDRESS_INPUT_TEST_ID)
const nextBtn = SafeDom.getByTestId(REPLACE_OWNER_NEXT_BTN_TEST_ID)
fireEvent.change(ownerNameInput, { target: { value: NEW_OWNER_NAME } })
fireEvent.change(ownerAddressInput, { target: { value: NEW_OWNER_ADDRESS } })
fireEvent.click(nextBtn)
await sleep(200)
const replaceSubmitBtn = SafeDom.getByTestId(REPLACE_OWNER_SUBMIT_BTN_TESTID)
const replaceSubmitBtn = SafeDom.getByTestId(REPLACE_OWNER_SUBMIT_BTN_TEST_ID)
fireEvent.click(replaceSubmitBtn)
await sleep(1000)
// check if the owner was replaced
ownerRows = SafeDom.getAllByTestId(OWNERS_ROW_TESTID)
ownerRows = SafeDom.getAllByTestId(OWNERS_ROW_TEST_ID)
expect(ownerRows.length).toBe(2)
expect(ownerRows[0]).toHaveTextContent('Adol 1 Eth Account')
expect(ownerRows[0]).toHaveTextContent('0x90F8bf6A479f320ead074411a4B0e7944Ea8c9C1')
expect(ownerRows[1]).toHaveTextContent(NEW_OWNER_NAME)
expect(ownerRows[1]).toHaveTextContent(NEW_OWNER_ADDRESS)
// Check that the transaction was registered
await checkRegisteredTxReplaceOwner(SafeDom, '0xFFcf8FDEE72ac11b5c542428B35EEF5769C409f0', NEW_OWNER_ADDRESS)
})
})

View File

@ -1,11 +0,0 @@
// @flow
// TBD
describe('DOM > Feature > SAFE MULTISIG Transactions', () => {
it.only('mines correctly all multisig txs in a 1 owner & 1 threshold safe', async () => {})
it.only('mines withdraw process correctly all multisig txs in a 2 owner & 2 threshold safe', async () => {})
it.only('approves and executes pending transactions', async () => {})
})

View File

@ -0,0 +1,13 @@
// @flow
function useTestAccountAt(index: number = 0) {
window.testAccountIndex = index
}
function resetTestAccount() {
delete window.testAccountIndex
}
export {
useTestAccountAt,
resetTestAccount,
}

View File

@ -6,11 +6,11 @@ import { toNative } from '~/logic/wallets/tokens'
import TokenOMG from '../../../build/contracts/TokenOMG'
import TokenRDN from '../../../build/contracts/TokenRDN'
export const sendEtherTo = async (address: string, eth: string) => {
export const sendEtherTo = async (address: string, eth: string, fromAccountIndex: number = 0) => {
const web3 = getWeb3()
const accounts = await web3.eth.getAccounts()
const { toBN, toWei } = web3.utils
const txData = { from: accounts[0], to: address, value: toBN(toWei(eth, 'ether')) }
const txData = { from: accounts[fromAccountIndex], to: address, value: toBN(toWei(eth, 'ether')) }
return web3.eth.sendTransaction(txData)
}

View File

@ -0,0 +1,5 @@
// @flow
export * from './moveFunds.helper'
export * from './moveTokens.helper'
export * from './threshold.helper'
export * from './transactionList.helper'

View File

@ -1,52 +1,30 @@
// @flow
import TestUtils from 'react-dom/test-utils'
import * as React from 'react'
import { fireEvent } from '@testing-library/react'
import { sleep } from '~/utils/timer'
import { checkMinedTx, checkPendingTx } from '~/test/builder/safe.dom.utils'
import SendToken from '~/routes/safe/components/SendToken'
import { whenExecuted } from '~/test/utils/logTransactions'
export const sendMoveFundsForm = async (
SafeDom: React.Component<any, any>,
expandBalance: React.Component<any, any>,
export const fillAndSubmitSendFundsForm = async (
SafeDom: any,
sendButton: React.Component<any, any>,
value: string,
destination: string,
recipient: string,
) => {
// load add multisig form component
TestUtils.Simulate.click(expandBalance)
fireEvent.click(sendButton)
// give time to re-render it
await sleep(400)
const ethList = TestUtils.findRenderedDOMComponentWithClass(SafeDom, 'ETH')
if (!ethList) throw new Error()
const ethButton = ethList.getElementsByTagName('button')
TestUtils.Simulate.click(ethButton[0])
await sleep(450)
// Fill first send funds screen
const recipientInput = SafeDom.getByPlaceholderText('Recipient*')
const amountInput = SafeDom.getByPlaceholderText('Amount*')
const reviewBtn = SafeDom.getByTestId('review-tx-btn')
fireEvent.change(recipientInput, { target: { value: recipient } })
fireEvent.change(amountInput, { target: { value } })
await sleep(200)
fireEvent.click(reviewBtn)
// fill the form
const inputs = TestUtils.scryRenderedDOMComponentsWithTag(SafeDom, 'input')
const destinationInput = inputs[0]
const amountInEthInput = inputs[1]
TestUtils.Simulate.change(amountInEthInput, { target: { value } })
TestUtils.Simulate.change(destinationInput, { target: { value: destination } })
// $FlowFixMe
const form = TestUtils.findRenderedDOMComponentWithTag(SafeDom, 'form')
// submit it
TestUtils.Simulate.submit(form)
TestUtils.Simulate.submit(form)
return whenExecuted(SafeDom, SendToken)
}
export const checkMinedMoveFundsTx = (Transaction: React.Component<any, any>, name: string) => {
checkMinedTx(Transaction, name)
}
export const checkPendingMoveFundsTx = async (
Transaction: React.Component<any, any>,
safeThreshold: number,
name: string,
statusses: string[],
) => {
await checkPendingTx(Transaction, safeThreshold, name, statusses)
// Submit the tx (Review Tx screen)
const submitBtn = SafeDom.getByTestId('submit-tx-btn')
fireEvent.click(submitBtn)
await sleep(1000)
}

View File

@ -0,0 +1,68 @@
// @flow
import { fireEvent } from '@testing-library/react'
import { sleep } from '~/utils/timer'
import { shortVersionOf } from '~/logic/wallets/ethAddresses'
import { TRANSACTIONS_TAB_BTN_TEST_ID } from '~/routes/safe/components/Layout'
import { TRANSACTION_ROW_TEST_ID } from '~/routes/safe/components/TransactionsNew/TxsTable'
import {
TRANSACTIONS_DESC_ADD_OWNER_TEST_ID,
TRANSACTIONS_DESC_REMOVE_OWNER_TEST_ID,
TRANSACTIONS_DESC_SEND_TEST_ID,
} from '~/routes/safe/components/TransactionsNew/TxsTable/ExpandedTx/TxDescription'
export const getLastTransaction = async (SafeDom: React.Component<any, any>) => {
// Travel to transactions
const transactionsBtn = SafeDom.getByTestId(TRANSACTIONS_TAB_BTN_TEST_ID)
fireEvent.click(transactionsBtn)
await sleep(200)
// Check the last transaction was registered
const transactionRows = SafeDom.getAllByTestId(TRANSACTION_ROW_TEST_ID)
expect(transactionRows.length).toBe(1)
fireEvent.click(transactionRows[0])
}
export const checkRegisteredTxSend = async (
SafeDom: React.Component<any, any>,
ethAmount: number,
symbol: string,
ethAddress: string,
) => {
await getLastTransaction(SafeDom)
const txDescription = SafeDom.getAllByTestId(TRANSACTIONS_DESC_SEND_TEST_ID)[0]
expect(txDescription).toHaveTextContent(`Send ${ethAmount} ${symbol} to:${shortVersionOf(ethAddress, 4)}`)
}
export const checkRegisteredTxAddOwner = async (
SafeDom: React.Component<any, any>,
ownerAddress: string,
) => {
await getLastTransaction(SafeDom)
const txDescription = SafeDom.getAllByTestId(TRANSACTIONS_DESC_ADD_OWNER_TEST_ID)[0]
expect(txDescription).toHaveTextContent(`Add owner:${shortVersionOf(ownerAddress, 4)}`)
}
export const checkRegisteredTxRemoveOwner = async (
SafeDom: React.Component<any, any>,
ownerAddress: string,
) => {
await getLastTransaction(SafeDom)
const txDescription = SafeDom.getAllByTestId(TRANSACTIONS_DESC_REMOVE_OWNER_TEST_ID)[0]
expect(txDescription).toHaveTextContent(`Remove owner:${shortVersionOf(ownerAddress, 4)}`)
}
export const checkRegisteredTxReplaceOwner = async (
SafeDom: React.Component<any, any>,
oldOwnerAddress: string,
newOwnerAddress: string,
) => {
await getLastTransaction(SafeDom)
const txDescriptionRemove = SafeDom.getAllByTestId(TRANSACTIONS_DESC_REMOVE_OWNER_TEST_ID)[0]
expect(txDescriptionRemove).toHaveTextContent(`Remove owner:${shortVersionOf(oldOwnerAddress, 4)}`)
const txDescriptionAdd = SafeDom.getAllByTestId(TRANSACTIONS_DESC_ADD_OWNER_TEST_ID)[0]
expect(txDescriptionAdd).toHaveTextContent(`Add owner:${shortVersionOf(newOwnerAddress, 4)}`)
}

View File

@ -7,17 +7,17 @@ export type Size = 'xs' | 'sm' | 'md' | 'lg' | 'xl'
export const getSize = (size: Size | typeof undefined) => {
switch (size) {
case 'xs':
return xs
case 'sm':
return sm
case 'md':
return md
case 'lg':
return lg
case 'xl':
return xl
default:
return '0px'
case 'xs':
return xs
case 'sm':
return sm
case 'md':
return md
case 'lg':
return lg
case 'xl':
return xl
default:
return '0px'
}
}

140
yarn.lock
View File

@ -1127,7 +1127,7 @@
dependencies:
regenerator-runtime "^0.13.2"
"@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.1.5", "@babel/runtime@^7.2.0", "@babel/runtime@^7.3.1", "@babel/runtime@^7.4.2", "@babel/runtime@^7.4.3", "@babel/runtime@^7.4.5", "@babel/runtime@^7.5.1", "@babel/runtime@^7.5.4":
"@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.1.5", "@babel/runtime@^7.2.0", "@babel/runtime@^7.3.1", "@babel/runtime@^7.4.0", "@babel/runtime@^7.4.2", "@babel/runtime@^7.4.3", "@babel/runtime@^7.4.5", "@babel/runtime@^7.5.1", "@babel/runtime@^7.5.4":
version "7.5.5"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.5.5.tgz#74fba56d35efbeca444091c7850ccd494fd2f132"
integrity sha512-28QvEGyQyNkB0/m2B4FU7IEZGK2NUrcMtT6BZEFALTguLk+AUT6ofsHtPk5QyjAdUkpMJ+/Em+quwz4HOt30AQ==
@ -2232,6 +2232,11 @@
"@types/istanbul-lib-coverage" "*"
"@types/istanbul-lib-report" "*"
"@types/json-schema@^7.0.3":
version "7.0.3"
resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.3.tgz#bdfd69d61e464dcc81b25159c270d75a73c1a636"
integrity sha512-Il2DtDVRGDcqjDtE+rF8iqg1CArehSK84HZJCT7AMITlyXRBpuPhqGLDQMowraqqu1coEaimg4ZOqggt6L6L+A==
"@types/minimatch@*":
version "3.0.3"
resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.3.tgz#3dca0e3f33b200fc7d1139c0cd96c1268cadfd9d"
@ -2287,28 +2292,29 @@
resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-12.0.12.tgz#45dd1d0638e8c8f153e87d296907659296873916"
integrity sha512-SOhuU4wNBxhhTHxYaiG5NY4HBhDIDnJF60GU+2LqHAdKKer86//e4yg69aENCtQ04n0ovz+tq2YPME5t5yp4pw==
"@typescript-eslint/experimental-utils@1.12.0":
version "1.12.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-1.12.0.tgz#98417ee2e0c6fe8d1e50d934a6535d9c0f4277b6"
integrity sha512-s0soOTMJloytr9GbPteMLNiO2HvJ+qgQkRNplABXiVw6vq7uQRvidkby64Gqt/nA7pys74HksHwRULaB/QRVyw==
"@typescript-eslint/experimental-utils@1.13.0":
version "1.13.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-1.13.0.tgz#b08c60d780c0067de2fb44b04b432f540138301e"
integrity sha512-zmpS6SyqG4ZF64ffaJ6uah6tWWWgZ8m+c54XXgwFtUv0jNz8aJAVx8chMCvnk7yl6xwn8d+d96+tWp7fXzTuDg==
dependencies:
"@typescript-eslint/typescript-estree" "1.12.0"
"@types/json-schema" "^7.0.3"
"@typescript-eslint/typescript-estree" "1.13.0"
eslint-scope "^4.0.0"
"@typescript-eslint/parser@^1.10.2":
version "1.12.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-1.12.0.tgz#9965895ec4745578185965d63f21510f93a3f35a"
integrity sha512-0uzbaa9ZLCA5yMWJywnJJ7YVENKGWVUhJDV5UrMoldC5HoI54W5kkdPhTfmtFKpPFp93MIwmJj0/61ztvmz5Dw==
version "1.13.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-1.13.0.tgz#61ac7811ea52791c47dc9fd4dd4a184fae9ac355"
integrity sha512-ITMBs52PCPgLb2nGPoeT4iU3HdQZHcPaZVw+7CsFagRJHUhyeTgorEwHXhFf3e7Evzi8oujKNpHc8TONth8AdQ==
dependencies:
"@types/eslint-visitor-keys" "^1.0.0"
"@typescript-eslint/experimental-utils" "1.12.0"
"@typescript-eslint/typescript-estree" "1.12.0"
"@typescript-eslint/experimental-utils" "1.13.0"
"@typescript-eslint/typescript-estree" "1.13.0"
eslint-visitor-keys "^1.0.0"
"@typescript-eslint/typescript-estree@1.12.0":
version "1.12.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-1.12.0.tgz#d8dd0a7cffb5e3c0c3e98714042d83e316dfc9a9"
integrity sha512-nwN6yy//XcVhFs0ZyU+teJHB8tbCm7AIA8mu6E2r5hu6MajwYBY3Uwop7+rPZWUN/IUOHpL8C+iUPMDVYUU3og==
"@typescript-eslint/typescript-estree@1.13.0":
version "1.13.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-1.13.0.tgz#8140f17d0f60c03619798f1d628b8434913dc32e"
integrity sha512-b5rCmd2e6DCC6tCTN9GSUAuxdYwCM/k/2wdjHGrIRGPSJotWMCe/dGpi66u42bhuh8q3QBzqM4TMA1GUUCJvdw==
dependencies:
lodash.unescape "4.0.1"
semver "5.5.0"
@ -2582,9 +2588,9 @@ acorn@^5.5.0, acorn@^5.5.3:
integrity sha512-T/zvzYRfbVojPWahDsE5evJdHb3oJoQfFbsrKM7w5Zcs++Tr257tia3BmMP8XYVjp1S9RZXQMh7gao96BlqZOw==
acorn@^6.0.1, acorn@^6.0.7, acorn@^6.2.0:
version "6.2.0"
resolved "https://registry.yarnpkg.com/acorn/-/acorn-6.2.0.tgz#67f0da2fc339d6cfb5d6fb244fd449f33cd8bbe3"
integrity sha512-8oe72N3WPMjA+2zVG71Ia0nXZ8DpQH+QyyHO+p06jT8eg8FGG3FbcUIi8KziHlAfheJQZeoqbvq1mQSQHXKYLw==
version "6.2.1"
resolved "https://registry.yarnpkg.com/acorn/-/acorn-6.2.1.tgz#3ed8422d6dec09e6121cc7a843ca86a330a86b51"
integrity sha512-JD0xT5FCRDNyjDda3Lrg/IxFscp9q4tiYtxE1/nOzlKCk7hIRuYjhq1kCNkbPjMRMZuFq20HNQn1I9k8Oj0E+Q==
address@1.0.3:
version "1.0.3"
@ -3467,10 +3473,11 @@ babel-plugin-emotion@^9.2.11:
touch "^2.0.1"
babel-plugin-istanbul@^5.1.0:
version "5.1.4"
resolved "https://registry.yarnpkg.com/babel-plugin-istanbul/-/babel-plugin-istanbul-5.1.4.tgz#841d16b9a58eeb407a0ddce622ba02fe87a752ba"
integrity sha512-dySz4VJMH+dpndj0wjJ8JPs/7i1TdSPb1nRrn56/92pKOF9VKC1FMFJmMXjzlGGusnCAqujP6PBCiKq0sVA+YQ==
version "5.2.0"
resolved "https://registry.yarnpkg.com/babel-plugin-istanbul/-/babel-plugin-istanbul-5.2.0.tgz#df4ade83d897a92df069c4d9a25cf2671293c854"
integrity sha512-5LphC0USA8t4i1zCtjbbNb6jJj/9+X6P37Qfirc/70EQ34xKlMW+a1RHGwxGI+SwWpNwZ27HqvzAobeqaXwiZw==
dependencies:
"@babel/helper-plugin-utils" "^7.0.0"
find-up "^3.0.0"
istanbul-lib-instrument "^3.3.0"
test-exclude "^5.2.3"
@ -4775,9 +4782,9 @@ caniuse-api@^3.0.0:
lodash.uniq "^4.5.0"
caniuse-lite@^1.0.0, caniuse-lite@^1.0.30000844, caniuse-lite@^1.0.30000955, caniuse-lite@^1.0.30000980, caniuse-lite@^1.0.30000984:
version "1.0.30000984"
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30000984.tgz#dc96c3c469e9bcfc6ad5bdd24c77ec918ea76fe0"
integrity sha512-n5tKOjMaZ1fksIpQbjERuqCyfgec/m9pferkFQbLmWtqLUdmt12hNhjSwsmPdqeiG2NkITOQhr1VYIwWSAceiA==
version "1.0.30000985"
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30000985.tgz#0eb40f6c8a8c219155cbe43c4975c0efb4a0f77f"
integrity sha512-1ngiwkgqAYPG0JSSUp3PUDGPKKY59EK7NrGGX+VOxaKCNzRbNc7uXMny+c3VJfZxtoK3wSImTvG9T9sXiTw2+w==
capture-exit@^2.0.0:
version "2.0.0"
@ -6462,9 +6469,9 @@ ejs@^2.6.1:
integrity sha512-PcW2a0tyTuPHz3tWyYqtK6r1fZ3gp+3Sop8Ph+ZYN81Ob5rwmbHEzaqs10N3BEsaGTkh/ooniXK+WwszGlc2+Q==
electron-to-chromium@^1.3.122, electron-to-chromium@^1.3.191, electron-to-chromium@^1.3.47:
version "1.3.194"
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.194.tgz#a96452a96d4539131957aade9f634a45721f2819"
integrity sha512-w0LHR2YD9Ex1o+Sz4IN2hYzCB8vaFtMNW+yJcBf6SZlVqgFahkne/4rGVJdk4fPF98Gch9snY7PiabOh+vqHNg==
version "1.3.199"
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.199.tgz#f9a62a74cda77854310a2abffde8b75591ea09a1"
integrity sha512-gachlDdHSK47s0N2e58GH9HMC6Z4ip0SfmYUa5iEbE50AKaOUXysaJnXMfKj0xB245jWbYcyFSH+th3rqsF8hA==
elliptic@6.3.3:
version "6.3.3"
@ -6725,9 +6732,9 @@ eslint-import-resolver-node@^0.3.2:
resolve "^1.5.0"
eslint-module-utils@^2.4.0:
version "2.4.0"
resolved "https://registry.yarnpkg.com/eslint-module-utils/-/eslint-module-utils-2.4.0.tgz#8b93499e9b00eab80ccb6614e69f03678e84e09a"
integrity sha512-14tltLm38Eu3zS+mt0KvILC3q8jyIAH518MlG+HO0p+yK885Lb1UHTY/UgR91eOyGdmxAPb+OLoW4znqIT6Ndw==
version "2.4.1"
resolved "https://registry.yarnpkg.com/eslint-module-utils/-/eslint-module-utils-2.4.1.tgz#7b4675875bf96b0dbf1b21977456e5bb1f5e018c"
integrity sha512-H6DOj+ejw7Tesdgbfs4jeS4YMFrT8uI8xwd1gtQqXssaR0EQ26L+2O/w6wkYFy2MymON0fTwHmXBvvfLNZVZEw==
dependencies:
debug "^2.6.8"
pkg-dir "^2.0.0"
@ -8812,7 +8819,7 @@ highlight.js@~9.12.0:
resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-9.12.0.tgz#e6d9dbe57cbefe60751f02af336195870c90c01e"
integrity sha1-5tnb5Xy+/mB1HwKvM2GVhwyQwB4=
history@^4.7.2:
history@^4.7.2, history@^4.9.0:
version "4.9.0"
resolved "https://registry.yarnpkg.com/history/-/history-4.9.0.tgz#84587c2068039ead8af769e9d6a6860a14fa1bca"
integrity sha512-H2DkjCjXf0Op9OAr6nJ56fcRkTSNrUiv41vNJ6IswJjif6wlpZK0BTfFbi7qK9dXLSYZxkq5lBsj3vUjlYBYZA==
@ -8838,7 +8845,7 @@ hoist-non-react-statics@^2.3.1, hoist-non-react-statics@^2.5.0:
resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-2.5.5.tgz#c5903cf409c0dfd908f388e619d86b9c1174cb47"
integrity sha512-rqcy4pJo55FTTLWt+bU8ukscqHeE/e9KWvsOW2b/a3afxQZhwkQdT1rPPCJ0rYXdj4vNcasY8zHTH+jF/qStxw==
hoist-non-react-statics@^3.2.1, hoist-non-react-statics@^3.3.0:
hoist-non-react-statics@^3.1.0, hoist-non-react-statics@^3.2.1, hoist-non-react-statics@^3.3.0:
version "3.3.0"
resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.0.tgz#b09178f0122184fb95acf525daaecb4d8f45958b"
integrity sha512-0XsbTXxgiaCDYDIWFcwkmerZPSwywfUqYmwT4jzewKTQSWoE6FCMoUVOeBJWK3E/CrWbxRG3m5GzY4lnIwGRBA==
@ -11545,6 +11552,15 @@ min-indent@^1.0.0:
resolved "https://registry.yarnpkg.com/min-indent/-/min-indent-1.0.0.tgz#cfc45c37e9ec0d8f0a0ec3dd4ef7f7c3abe39256"
integrity sha1-z8RcN+nsDY8KDsPdTvf3w6vjklY=
mini-create-react-context@^0.3.0:
version "0.3.2"
resolved "https://registry.yarnpkg.com/mini-create-react-context/-/mini-create-react-context-0.3.2.tgz#79fc598f283dd623da8e088b05db8cddab250189"
integrity sha512-2v+OeetEyliMt5VHMXsBhABoJ0/M4RCe7fatd/fBy6SMiKazUSEt3gxxypfnk2SHMkdBYvorHRoQxuGoiwbzAw==
dependencies:
"@babel/runtime" "^7.4.0"
gud "^1.0.0"
tiny-warning "^1.0.2"
mini-css-extract-plugin@0.8.0:
version "0.8.0"
resolved "https://registry.yarnpkg.com/mini-css-extract-plugin/-/mini-css-extract-plugin-0.8.0.tgz#81d41ec4fe58c713a96ad7c723cdb2d0bd4d70e1"
@ -13360,7 +13376,14 @@ pretty-hrtime@^1.0.0, pretty-hrtime@^1.0.3:
resolved "https://registry.yarnpkg.com/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz#b7e3ea42435a4c9b2759d99e0f201eb195802ee1"
integrity sha1-t+PqQkNaTJsnWdmeDyAesZWALuE=
prismjs@^1.8.4, prismjs@~1.16.0:
prismjs@^1.8.4:
version "1.17.1"
resolved "https://registry.yarnpkg.com/prismjs/-/prismjs-1.17.1.tgz#e669fcbd4cdd873c35102881c33b14d0d68519be"
integrity sha512-PrEDJAFdUGbOP6xK/UsfkC5ghJsPJviKgnQOoxaDbBjwc8op68Quupwt1DeAFoG8GImPhiKXAvvsH7wDSLsu1Q==
optionalDependencies:
clipboard "^2.0.0"
prismjs@~1.16.0:
version "1.16.0"
resolved "https://registry.yarnpkg.com/prismjs/-/prismjs-1.16.0.tgz#406eb2c8aacb0f5f0f1167930cb83835d10a4308"
integrity sha512-OA4MKxjFZHSvZcisLGe14THYsug/nF6O1f0pAJc0KN0wTyAcLqmsbE+lTGKSpyh+9pEW57+k6pg2AfYR+coyHA==
@ -13916,7 +13939,7 @@ react-inspector@^3.0.2:
is-dom "^1.0.9"
prop-types "^15.6.1"
react-is@^16.7.0, react-is@^16.8.0, react-is@^16.8.1, react-is@^16.8.4, react-is@^16.8.6:
react-is@^16.6.0, react-is@^16.7.0, react-is@^16.8.0, react-is@^16.8.1, react-is@^16.8.4, react-is@^16.8.6:
version "16.8.6"
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.8.6.tgz#5bbc1e2d29141c9fbdfed456343fe2bc430a6a16"
integrity sha512-aUk3bHfZ2bRSVFFbbeVS4i+lNPZr3/WM5jT2J5omUVV1zzcs1nAaf3l51ctA5FFvCRbhrH0bdAsRRQddFJZPtA==
@ -13978,19 +14001,36 @@ react-resize-detector@^4.0.5:
raf-schd "^4.0.0"
resize-observer-polyfill "^1.5.1"
react-router-dom@^4.3.1:
version "4.3.1"
resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-4.3.1.tgz#4c2619fc24c4fa87c9fd18f4fb4a43fe63fbd5c6"
integrity sha512-c/MlywfxDdCp7EnB7YfPMOfMD3tOtIjrQlj/CKfNMBxdmpJP8xcz5P/UAFn3JbnQCNUxsHyVVqllF9LhgVyFCA==
react-router-dom@^5.0.1:
version "5.0.1"
resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-5.0.1.tgz#ee66f4a5d18b6089c361958e443489d6bab714be"
integrity sha512-zaVHSy7NN0G91/Bz9GD4owex5+eop+KvgbxXsP/O+iW1/Ln+BrJ8QiIR5a6xNPtrdTvLkxqlDClx13QO1uB8CA==
dependencies:
history "^4.7.2"
invariant "^2.2.4"
"@babel/runtime" "^7.1.2"
history "^4.9.0"
loose-envify "^1.3.1"
prop-types "^15.6.1"
react-router "^4.3.1"
warning "^4.0.1"
prop-types "^15.6.2"
react-router "5.0.1"
tiny-invariant "^1.0.2"
tiny-warning "^1.0.0"
"react-router@^3.0.0 || ^4.0.0", react-router@^4.3.1:
react-router@5.0.1:
version "5.0.1"
resolved "https://registry.yarnpkg.com/react-router/-/react-router-5.0.1.tgz#04ee77df1d1ab6cb8939f9f01ad5702dbadb8b0f"
integrity sha512-EM7suCPNKb1NxcTZ2LEOWFtQBQRQXecLxVpdsP4DW4PbbqYWeRiLyV/Tt1SdCrvT2jcyXAXmVTmzvSzrPR63Bg==
dependencies:
"@babel/runtime" "^7.1.2"
history "^4.9.0"
hoist-non-react-statics "^3.1.0"
loose-envify "^1.3.1"
mini-create-react-context "^0.3.0"
path-to-regexp "^1.7.0"
prop-types "^15.6.2"
react-is "^16.6.0"
tiny-invariant "^1.0.2"
tiny-warning "^1.0.0"
"react-router@^3.0.0 || ^4.0.0":
version "4.3.1"
resolved "https://registry.yarnpkg.com/react-router/-/react-router-4.3.1.tgz#aada4aef14c809cb2e686b05cee4742234506c4e"
integrity sha512-yrvL8AogDh2X42Dt9iknk4wF4V8bWREPirFfS9gLU1huk6qK41sg7Z/1S81jjTrGHxa3B8R3J6xIkDAA6CVarg==
@ -14375,9 +14415,9 @@ regenerator-runtime@^0.12.0, regenerator-runtime@^0.12.1:
integrity sha512-odxIc1/vDlo4iZcfXqRYFj0vpXFNoGdKMAUieAlFYO6m/nl5e9KR/beGf41z4a1FI+aQgtjhuaSlDxQ0hmkrHg==
regenerator-runtime@^0.13.2:
version "0.13.2"
resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.2.tgz#32e59c9a6fb9b1a4aff09b4930ca2d4477343447"
integrity sha512-S/TQAZJO+D3m9xeN1WTI8dLKBBiRgXBlTJvbWjCThHWZj9EvHK70Ff50/tYj2J/fvBY6JtFVwRuazHN2E7M9BA==
version "0.13.3"
resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.3.tgz#7cf6a77d8f5c6f60eb73c5fc1955b2ceb01e6bf5"
integrity sha512-naKIZz2GQ8JWh///G7L3X6LaQUAMp2lvb1rvwwsURe/VXwD6VMfr+/1NuNw3ag8v2kY1aQ/go5SNn79O9JU7yw==
regenerator-transform@^0.10.0:
version "0.10.1"
@ -14389,9 +14429,9 @@ regenerator-transform@^0.10.0:
private "^0.1.6"
regenerator-transform@^0.14.0:
version "0.14.0"
resolved "https://registry.yarnpkg.com/regenerator-transform/-/regenerator-transform-0.14.0.tgz#2ca9aaf7a2c239dd32e4761218425b8c7a86ecaf"
integrity sha512-rtOelq4Cawlbmq9xuMR5gdFmv7ku/sFoB7sRiywx7aq53bc52b4j6zvH7Te1Vt/X2YveDKnCGUbioieU7FEL3w==
version "0.14.1"
resolved "https://registry.yarnpkg.com/regenerator-transform/-/regenerator-transform-0.14.1.tgz#3b2fce4e1ab7732c08f665dfdb314749c7ddd2fb"
integrity sha512-flVuee02C3FKRISbxhXl9mGzdbWUVHubl1SMaknjxkFB1/iqpJhArQUvRxOOPEc/9tAiX0BaQ28FJH10E4isSQ==
dependencies:
private "^0.1.6"