diff --git a/src/routes/safe/store/test/builder/deployedSafe.builder.js b/src/routes/safe/store/test/builder/deployedSafe.builder.js index 0e86b59330..c5e58f96b6 100644 --- a/src/routes/safe/store/test/builder/deployedSafe.builder.js +++ b/src/routes/safe/store/test/builder/deployedSafe.builder.js @@ -8,10 +8,11 @@ import { DEPLOYED_COMPONENT_ID } from '~/routes/open/components/FormConfirmation import Open from '~/routes/open/container/Open' import { history, type GlobalState } from '~/store' import { sleep } from '~/utils/timer' -import { getProviderInfo } from '~/wallets/getWeb3' +import { getProviderInfo, getWeb3 } from '~/wallets/getWeb3' import addProvider from '~/wallets/store/actions/addProvider' import { makeProvider } from '~/wallets/store/model/provider' import withdrawn, { DESTINATION_PARAM, VALUE_PARAM } from '~/routes/safe/component/Withdrawn/withdrawn' +import { promisify } from '~/utils/promisify' export const renderSafe = async (localStore: Store) => { const provider = await getProviderInfo() @@ -29,20 +30,34 @@ export const renderSafe = async (localStore: Store) => { ) } -const deploySafe = async (safe: React$Component<{}>, dailyLimit: string) => { +const deploySafe = async (safe: React$Component<{}>, dailyLimit: string, threshold: number, numOwners: number) => { + expect(threshold).toBeLessThanOrEqual(numOwners) const inputs = TestUtils.scryRenderedDOMComponentsWithTag(safe, 'input') const fieldName = inputs[0] const fieldOwners = inputs[1] const fieldConfirmations = inputs[2] const fieldDailyLimit = inputs[3] - TestUtils.Simulate.change(fieldOwners, { target: { value: '1' } }) + const web3 = getWeb3() + const accounts = await promisify(cb => web3.eth.getAccounts(cb)) + TestUtils.Simulate.change(fieldOwners, { target: { value: `${numOwners}` } }) + await sleep(800) const inputsExpanded = TestUtils.scryRenderedDOMComponentsWithTag(safe, 'input') - const ownerName = inputsExpanded[2] + expect(inputsExpanded.length).toBe((numOwners * 2) + 4) // 2 per owner + name, dailyLimit, confirmations, numOwners + + for (let i = 0; i < numOwners; i += 1) { + const nameIndex = (i * 2) + 2 + const addressIndex = (i * 2) + 3 + const ownerName = inputsExpanded[nameIndex] + const account = inputsExpanded[addressIndex] + + TestUtils.Simulate.change(ownerName, { target: { value: `Adolfo ${i + 1} Eth Account` } }) + TestUtils.Simulate.change(account, { target: { value: accounts[i] } }) + } TestUtils.Simulate.change(fieldName, { target: { value: 'Adolfo Safe' } }) - TestUtils.Simulate.change(fieldConfirmations, { target: { value: '1' } }) - TestUtils.Simulate.change(ownerName, { target: { value: 'Adolfo Eth Account' } }) + TestUtils.Simulate.change(fieldConfirmations, { target: { value: `${threshold}` } }) + TestUtils.Simulate.change(fieldDailyLimit, { target: { value: dailyLimit } }) const form = TestUtils.findRenderedDOMComponentWithTag(safe, 'form') @@ -67,9 +82,14 @@ const deploySafe = async (safe: React$Component<{}>, dailyLimit: string) => { return transactionHash } -export const aDeployedSafe = async (specificStore: Store, dailyLimit?: number = 0.5) => { +export const aDeployedSafe = async ( + specificStore: Store, + dailyLimit?: number = 0.5, + threshold?: number = 1, + numOwners?: number = 1, +) => { const safe: React$Component<{}> = await renderSafe(specificStore) - const deployedSafe = await deploySafe(safe, `${dailyLimit}`) + const deployedSafe = await deploySafe(safe, `${dailyLimit}`, threshold, numOwners) return deployedSafe.logs[1].args.proxy } diff --git a/src/routes/safe/component/Safe.txs.test.js b/src/routes/safe/test/Safe.multisig.1owners1threshold.test.js similarity index 100% rename from src/routes/safe/component/Safe.txs.test.js rename to src/routes/safe/test/Safe.multisig.1owners1threshold.test.js diff --git a/src/routes/safe/test/Safe.multisig.3owners1threshold.test.js b/src/routes/safe/test/Safe.multisig.3owners1threshold.test.js new file mode 100644 index 0000000000..4b1c7a47fb --- /dev/null +++ b/src/routes/safe/test/Safe.multisig.3owners1threshold.test.js @@ -0,0 +1,59 @@ +// @flow +import * as React from 'react' +import TestUtils from 'react-dom/test-utils' +import { Provider } from 'react-redux' +import { ConnectedRouter } from 'react-router-redux' +import { aNewStore, history } from '~/store' +import { aDeployedSafe } from '~/routes/safe/store/test/builder/deployedSafe.builder' +import { SAFELIST_ADDRESS } from '~/routes/routes' +import AppRoutes from '~/routes' +import AddTransactionComponent from '~/routes/safe/component/AddTransaction' +import { createMultisigTxFilling, addFundsTo, checkBalanceOf, listTxsOf, getTagFromTransaction, expandTransactionOf, getTransactionFromReduxStore, confirmOwners } from '~/routes/safe/test/testMultisig' + +const renderSafe = localStore => ( + TestUtils.renderIntoDocument(( + + + + + + )) +) + +describe('React DOM TESTS > Multisig transactions from safe [3 owners & 1 threshold] ', () => { + let SafeDom + let store + let address + beforeEach(async () => { + // create store + store = aNewStore() + // deploy safe updating store + address = await aDeployedSafe(store, 10, 1, 3) + // navigate to SAFE route + history.push(`${SAFELIST_ADDRESS}/${address}`) + SafeDom = renderSafe(store) + }) + + it('should execute transaction after 2 owners have confirmed and the last one executed correctly', async () => { + await addFundsTo(SafeDom, address) + await checkBalanceOf(address, '0.1') + await createMultisigTxFilling(SafeDom, AddTransactionComponent, store) + await checkBalanceOf(address, '0.09') + await listTxsOf(SafeDom) + + await expandTransactionOf(SafeDom, 3, 1) + await confirmOwners(SafeDom, 'Adolfo 1 Eth Account [Confirmed]', 'Adolfo 2 Eth Account [Not confirmed]', 'Adolfo 3 Eth Account [Not confirmed]') + + const paragraphs = getTagFromTransaction(SafeDom, 'p') + + const status = paragraphs[2].innerHTML + expect(status).toBe('Already executed') + + const confirmed = paragraphs[3].innerHTML + const tx = getTransactionFromReduxStore(store, address) + expect(confirmed).toBe(tx.get('tx')) + + const ownerTx = paragraphs[6].innerHTML + expect(ownerTx).toBe('Confirmation hash: EXECUTED') + }) +}) diff --git a/src/routes/safe/test/Safe.multisig.3owners3threshold.test.js b/src/routes/safe/test/Safe.multisig.3owners3threshold.test.js new file mode 100644 index 0000000000..8a96b50bd0 --- /dev/null +++ b/src/routes/safe/test/Safe.multisig.3owners3threshold.test.js @@ -0,0 +1,101 @@ +// @flow +import * as React from 'react' +import TestUtils from 'react-dom/test-utils' +import { Provider } from 'react-redux' +import { ConnectedRouter } from 'react-router-redux' +import { aNewStore, history } from '~/store' +import { aDeployedSafe } from '~/routes/safe/store/test/builder/deployedSafe.builder' +import { SAFELIST_ADDRESS } from '~/routes/routes' +import AppRoutes from '~/routes' +import { getWeb3 } from '~/wallets/getWeb3' +import { sleep } from '~/utils/timer' +import { promisify } from '~/utils/promisify' +import AddTransactionComponent from '~/routes/safe/component/AddTransaction' +import { processTransaction } from '~/routes/safe/component/Transactions/processTransactions' +import { confirmationsTransactionSelector } from '~/routes/safe/store/selectors/index' +import fetchTransactions from '~/routes/safe/store/actions/fetchTransactions' +import { createMultisigTxFilling, addFundsTo, checkBalanceOf, listTxsOf, getTagFromTransaction, expandTransactionOf, getTransactionFromReduxStore, confirmOwners } from '~/routes/safe/test/testMultisig' + +const renderSafe = localStore => ( + TestUtils.renderIntoDocument(( + + + + + + )) +) + +describe('React DOM TESTS > Multisig transactions from safe [3 owners & 3 threshold] ', () => { + let SafeDom + let store + let address + let accounts + beforeEach(async () => { + // create store + store = aNewStore() + // deploy safe updating store + address = await aDeployedSafe(store, 10, 3, 3) + // navigate to SAFE route + history.push(`${SAFELIST_ADDRESS}/${address}`) + SafeDom = renderSafe(store) + accounts = await promisify(cb => getWeb3().eth.getAccounts(cb)) + }) + + + const getAlreadyConfirmed = () => { + const tx = getTransactionFromReduxStore(store, address) + const confirmed = confirmationsTransactionSelector(store.getState(), { transaction: tx }) + + return confirmed + } + + const makeConfirmation = async (executor) => { + const alreadyConfirmed = getAlreadyConfirmed() + const tx = getTransactionFromReduxStore(store, address) + await processTransaction(address, tx, alreadyConfirmed, executor) + await sleep(800) + store.dispatch(fetchTransactions()) + sleep(1800) + SafeDom = renderSafe(store) + sleep(1800) + await listTxsOf(SafeDom) + sleep(800) + await expandTransactionOf(SafeDom, 3, 3) + sleep(800) + } + + it('should execute transaction after 2 owners have confirmed and the last one executed correctly', async () => { + await addFundsTo(SafeDom, address) + await createMultisigTxFilling(SafeDom, AddTransactionComponent, store) + + await checkBalanceOf(address, '0.1') + await listTxsOf(SafeDom) + sleep(800) + const paragraphs = getTagFromTransaction(SafeDom, 'p') + + const status = paragraphs[2].innerHTML + expect(status).toBe('1 of the 3 confirmations needed') + + const confirmed = paragraphs[3].innerHTML + expect(confirmed).toBe('Waiting for the rest of confirmations') + + await expandTransactionOf(SafeDom, 3, 3) + await confirmOwners(SafeDom, 'Adolfo 1 Eth Account [Confirmed]', 'Adolfo 2 Eth Account [Not confirmed]', 'Adolfo 3 Eth Account [Not confirmed]') + + await makeConfirmation(accounts[1]) + await confirmOwners(SafeDom, 'Adolfo 1 Eth Account [Confirmed]', 'Adolfo 2 Eth Account [Confirmed]', 'Adolfo 3 Eth Account [Not confirmed]') + + await makeConfirmation(accounts[2]) + await confirmOwners(SafeDom, 'Adolfo 1 Eth Account [Confirmed]', 'Adolfo 2 Eth Account [Confirmed]', 'Adolfo 3 Eth Account [Confirmed]') + + const paragraphsExecuted = getTagFromTransaction(SafeDom, 'p') + + const statusExecuted = paragraphsExecuted[2].innerHTML + expect(statusExecuted).toBe('Already executed') + + const confirmedExecuted = paragraphsExecuted[3].innerHTML + const tx = getTransactionFromReduxStore(store, address) + expect(confirmedExecuted).toBe(tx.get('tx')) + }) +}) diff --git a/src/routes/safe/component/Safe.withdrawn.test.js b/src/routes/safe/test/Safe.withdrawn.test.js similarity index 100% rename from src/routes/safe/component/Safe.withdrawn.test.js rename to src/routes/safe/test/Safe.withdrawn.test.js diff --git a/src/routes/safe/test/testMultisig.js b/src/routes/safe/test/testMultisig.js new file mode 100644 index 0000000000..c4264cd523 --- /dev/null +++ b/src/routes/safe/test/testMultisig.js @@ -0,0 +1,96 @@ +// @flow +import TestUtils from 'react-dom/test-utils' +import { sleep } from '~/utils/timer' +import { getBalanceInEtherOf } from '~/wallets/getWeb3' +import Button from '~/components/layout/Button' +import { ADD_MULTISIG_BUTTON_TEXT, SEE_MULTISIG_BUTTON_TEXT } from '~/routes/safe/component/Safe/MultisigTx' +import { addEtherTo } from '~/test/addEtherTo' +import SafeView from '~/routes/safe/component/Safe' +import TransactionsComponent from '~/routes/safe/component/Transactions' +import TransactionComponent from '~/routes/safe/component/Transactions/Transaction' +import { safeTransactionsSelector } from '~/routes/safe/store/selectors/index' + +export const createMultisigTxFilling = async (SafeDom, AddTransactionComponent, store) => { + // Get AddTransaction form component + const AddTransaction = TestUtils.findRenderedComponentWithType(SafeDom, AddTransactionComponent) + + // $FlowFixMe + const inputs = TestUtils.scryRenderedDOMComponentsWithTag(AddTransaction, 'input') + const name = inputs[0] + const destination = inputs[1] + const amountInEth = inputs[2] + TestUtils.Simulate.change(name, { target: { value: 'Buying betteries' } }) + TestUtils.Simulate.change(amountInEth, { target: { value: '0.01' } }) + TestUtils.Simulate.change(destination, { target: { value: store.getState().providers.account } }) + + // $FlowFixMe + const form = TestUtils.findRenderedDOMComponentWithTag(AddTransaction, 'form') + + TestUtils.Simulate.submit(form) // fill the form + TestUtils.Simulate.submit(form) // confirming data + return sleep(4000) +} + +export const checkBalanceOf = async (addressToTest: string, value: string) => { + const safeBalance = await getBalanceInEtherOf(addressToTest) + expect(safeBalance).toBe(value) +} + +export const addFundsTo = async (SafeDom, destination: string) => { + // add funds to safe + await addEtherTo(destination, '0.1') + const Safe = TestUtils.findRenderedComponentWithType(SafeDom, SafeView) + + // $FlowFixMe + const buttons = TestUtils.scryRenderedComponentsWithType(Safe, Button) + const addTxButton = buttons[1] + expect(addTxButton.props.children).toEqual(ADD_MULTISIG_BUTTON_TEXT) + await sleep(1800) // Give time to enable Add button + TestUtils.Simulate.click(TestUtils.scryRenderedDOMComponentsWithTag(addTxButton, 'button')[0]) +} + +export const listTxsOf = (SafeDom) => { + const Safe = TestUtils.findRenderedComponentWithType(SafeDom, SafeView) + + // $FlowFixMe + const buttons = TestUtils.scryRenderedComponentsWithType(Safe, Button) + const seeTx = buttons[2] + expect(seeTx.props.children).toEqual(SEE_MULTISIG_BUTTON_TEXT) + TestUtils.Simulate.click(TestUtils.scryRenderedDOMComponentsWithTag(seeTx, 'button')[0]) +} + +export const getTagFromTransaction = (SafeDom, tag: string) => { + const Transactions = TestUtils.findRenderedComponentWithType(SafeDom, TransactionsComponent) + if (!Transactions) throw new Error() + const Transaction = TestUtils.findRenderedComponentWithType(Transactions, TransactionComponent) + if (!Transaction) throw new Error() + + return TestUtils.scryRenderedDOMComponentsWithTag(Transaction, tag) +} + +export const expandTransactionOf = async (SafeDom, numOwners, safeThreshold) => { + const paragraphs = getTagFromTransaction(SafeDom, 'p') + TestUtils.Simulate.click(paragraphs[2]) // expanded + await sleep(1000) // Time to expand + const paragraphsExpanded = getTagFromTransaction(SafeDom, 'p') + const threshold = paragraphsExpanded[5] + expect(threshold.innerHTML).toContain(`confirmation${safeThreshold === 1 ? '' : 's'} needed`) + TestUtils.Simulate.click(threshold) // expanded + await sleep(1000) // Time to expand + expect(paragraphsExpanded.length).toBe(paragraphs.length + numOwners) +} + +export const getTransactionFromReduxStore = (store, address) => { + const transactions = safeTransactionsSelector(store.getState(), { safeAddress: address }) + + return transactions.get(0) +} + +export const confirmOwners = async (SafeDom, ...statusses: string[]) => { + const paragraphsWithOwners = getTagFromTransaction(SafeDom, 'h3') + for (let i = 0; i < statusses.length; i += 1) { + const ownerIndex = i + 6 + const ownerParagraph = paragraphsWithOwners[ownerIndex].innerHTML + expect(statusses[i]).toEqual(ownerParagraph) + } +}