Merge pull request #117 from gnosis/110-sort-out-tests
110: Sort out tests
This commit is contained in:
commit
7bb49a353f
|
@ -1,4 +1,5 @@
|
|||
node_modules/
|
||||
build_webpack/
|
||||
build_storybook/
|
||||
.DS_Store
|
||||
.DS_Store
|
||||
build/
|
|
@ -21,7 +21,7 @@ const cssvariables = require(paths.appSrc + '/theme/variables')
|
|||
|
||||
const postcssPlugins = [
|
||||
autoprefixer({
|
||||
browsers: [
|
||||
overrideBrowserslist: [
|
||||
'>1%',
|
||||
'last 4 versions',
|
||||
'Firefox ESR',
|
||||
|
@ -102,8 +102,9 @@ module.exports = {
|
|||
loader: 'css-loader',
|
||||
options: {
|
||||
importLoaders: 1,
|
||||
modules: true,
|
||||
localIdentName: '[name]__[local]___[hash:base64:5]',
|
||||
modules: {
|
||||
localIdentName: '[name]__[local]___[hash:base64:5]',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
|
|
|
@ -20,7 +20,7 @@ const cssvariables = require(`${paths.appSrc}/theme/variables`)
|
|||
|
||||
const postcssPlugins = [
|
||||
autoprefixer({
|
||||
browsers: [
|
||||
overrideBrowserslist: [
|
||||
'>1%',
|
||||
'last 4 versions',
|
||||
'Firefox ESR',
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
pragma solidity ^0.5.2;
|
||||
|
||||
import "../src/test/contracts/TokenOMG.sol";
|
||||
import "../src/test/contracts/TokenRDN.sol";
|
||||
|
||||
contract DevDependenciesGetter {}
|
|
@ -0,0 +1,23 @@
|
|||
pragma solidity ^0.5.2;
|
||||
|
||||
contract Migrations {
|
||||
address public owner;
|
||||
uint public last_completed_migration;
|
||||
|
||||
modifier restricted() {
|
||||
if (msg.sender == owner) _;
|
||||
}
|
||||
|
||||
constructor () public {
|
||||
owner = msg.sender;
|
||||
}
|
||||
|
||||
function setCompleted(uint completed) public restricted {
|
||||
last_completed_migration = completed;
|
||||
}
|
||||
|
||||
function upgrade(address new_address) public restricted {
|
||||
Migrations upgraded = Migrations(new_address);
|
||||
upgraded.setCompleted(last_completed_migration);
|
||||
}
|
||||
}
|
|
@ -14,7 +14,7 @@ module.exports = {
|
|||
'<rootDir>/config/jest/LocalStorageMock.js',
|
||||
'<rootDir>/config/jest/Web3Mock.js',
|
||||
],
|
||||
setupFilesAfterEnv: ['<rootDir>/config/jest/jest.setup.js', 'react-testing-library/cleanup-after-each'],
|
||||
setupFilesAfterEnv: ['<rootDir>/config/jest/jest.setup.js', '@testing-library/react/cleanup-after-each'],
|
||||
testEnvironment: 'node',
|
||||
testMatch: ['<rootDir>/src/**/__tests__/**/*.js?(x)', '<rootDir>/src/**/?(*.)(spec|test).js?(x)'],
|
||||
testURL: 'http://localhost:8000',
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
// @flow
|
||||
const Migrations = artifacts.require('./Migrations.sol')
|
||||
|
||||
module.exports = deployer => deployer.deploy(Migrations)
|
|
@ -0,0 +1,21 @@
|
|||
// @flow
|
||||
/* eslint-disable no-console */
|
||||
const TokenOMG = artifacts.require('TokenOMG')
|
||||
const TokenRDN = artifacts.require('TokenRDN')
|
||||
|
||||
module.exports = (deployer, network) => {
|
||||
let toBN
|
||||
if (typeof web3.version === 'string') {
|
||||
// 1.X.xx Web3
|
||||
({ toBN } = web3.utils)
|
||||
} else {
|
||||
toBN = web3.toBigNumber
|
||||
}
|
||||
if (network === 'development') {
|
||||
return deployer
|
||||
.deploy(TokenOMG, toBN(50000).mul(toBN(10).pow(toBN(18))))
|
||||
.then(() => deployer.deploy(TokenRDN, toBN(50000).mul(toBN(10).pow(toBN(18)))))
|
||||
}
|
||||
|
||||
return console.log('Not running on development, skipping.')
|
||||
}
|
47
package.json
47
package.json
|
@ -32,13 +32,13 @@
|
|||
"dependencies": {
|
||||
"@gnosis.pm/safe-contracts": "^1.0.0",
|
||||
"@gnosis.pm/util-contracts": "2.0.1",
|
||||
"@material-ui/core": "4.0.1",
|
||||
"@material-ui/icons": "4.0.1",
|
||||
"@material-ui/core": "4.1.0",
|
||||
"@material-ui/icons": "4.1.0",
|
||||
"@welldone-software/why-did-you-render": "^3.0.9",
|
||||
"axios": "^0.18.0",
|
||||
"axios": "0.19.0",
|
||||
"bignumber.js": "9.0.0",
|
||||
"connected-react-router": "^6.3.1",
|
||||
"final-form": "4.13.0",
|
||||
"final-form": "4.14.1",
|
||||
"history": "^4.7.2",
|
||||
"immortal-db": "^1.0.2",
|
||||
"immutable": "^4.0.0-rc.9",
|
||||
|
@ -47,11 +47,11 @@
|
|||
"qrcode.react": "^0.9.3",
|
||||
"react": "^16.8.6",
|
||||
"react-dom": "^16.8.6",
|
||||
"react-final-form": "6.0.0",
|
||||
"react-final-form": "6.1.0",
|
||||
"react-final-form-listeners": "^1.0.2",
|
||||
"react-hot-loader": "4.8.8",
|
||||
"react-hot-loader": "4.11.0",
|
||||
"react-infinite-scroll-component": "^4.5.2",
|
||||
"react-redux": "7.0.3",
|
||||
"react-redux": "7.1.0",
|
||||
"react-router-dom": "^4.3.1",
|
||||
"recompose": "^0.30.0",
|
||||
"redux": "^4.0.1",
|
||||
|
@ -86,11 +86,12 @@
|
|||
"@babel/preset-flow": "^7.0.0-beta.40",
|
||||
"@babel/preset-react": "^7.0.0-beta.40",
|
||||
"@sambego/storybook-state": "^1.0.7",
|
||||
"@storybook/addon-actions": "5.0.11",
|
||||
"@storybook/addon-knobs": "5.0.11",
|
||||
"@storybook/addon-links": "5.0.11",
|
||||
"@storybook/react": "5.0.11",
|
||||
"autoprefixer": "9.5.1",
|
||||
"@storybook/addon-actions": "5.1.4",
|
||||
"@storybook/addon-knobs": "5.1.4",
|
||||
"@storybook/addon-links": "5.1.4",
|
||||
"@storybook/react": "5.1.4",
|
||||
"@testing-library/react": "^8.0.1",
|
||||
"autoprefixer": "9.6.0",
|
||||
"babel-core": "^7.0.0-bridge.0",
|
||||
"babel-eslint": "^10.0.1",
|
||||
"babel-jest": "24.8.0",
|
||||
|
@ -99,23 +100,24 @@
|
|||
"babel-plugin-transform-es3-member-expression-literals": "^6.22.0",
|
||||
"babel-plugin-transform-es3-property-literals": "^6.22.0",
|
||||
"classnames": "^2.2.5",
|
||||
"css-loader": "^2.1.0",
|
||||
"css-loader": "3.0.0",
|
||||
"detect-port": "^1.2.2",
|
||||
"eslint": "^5.16.0",
|
||||
"eslint-config-airbnb": "^17.1.0",
|
||||
"eslint-plugin-flowtype": "3.9.1",
|
||||
"eslint-plugin-flowtype": "3.10.2",
|
||||
"eslint-plugin-import": "2.17.3",
|
||||
"eslint-plugin-jest": "22.6.4",
|
||||
"eslint-plugin-jsx-a11y": "^6.0.3",
|
||||
"eslint-plugin-react": "7.13.0",
|
||||
"ethereumjs-abi": "^0.6.7",
|
||||
"extract-text-webpack-plugin": "^4.0.0-beta.0",
|
||||
"file-loader": "^3.0.1",
|
||||
"flow-bin": "0.99.0",
|
||||
"file-loader": "4.0.0",
|
||||
"flow-bin": "0.101.0",
|
||||
"fs-extra": "8.0.1",
|
||||
"html-loader": "^0.5.5",
|
||||
"html-webpack-plugin": "^3.0.4",
|
||||
"jest": "24.8.0",
|
||||
"jest-dom": "^3.4.0",
|
||||
"json-loader": "^0.5.7",
|
||||
"mini-css-extract-plugin": "0.7.0",
|
||||
"postcss-loader": "^3.0.0",
|
||||
|
@ -123,19 +125,18 @@
|
|||
"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",
|
||||
"style-loader": "^0.23.1",
|
||||
"truffle": "5.0.19",
|
||||
"truffle-contract": "4.0.17",
|
||||
"truffle-solidity-loader": "0.1.18",
|
||||
"truffle": "5.0.22",
|
||||
"truffle-contract": "4.0.20",
|
||||
"truffle-solidity-loader": "0.1.21",
|
||||
"uglifyjs-webpack-plugin": "2.1.3",
|
||||
"webpack": "4.32.2",
|
||||
"webpack": "4.34.0",
|
||||
"webpack-bundle-analyzer": "3.3.2",
|
||||
"webpack-cli": "3.3.2",
|
||||
"webpack-dev-server": "3.4.1",
|
||||
"webpack-cli": "3.3.4",
|
||||
"webpack-dev-server": "3.7.1",
|
||||
"webpack-manifest-plugin": "^2.0.0-rc.2"
|
||||
}
|
||||
}
|
||||
|
|
17
readme.md
17
readme.md
|
@ -35,6 +35,23 @@ yarn start
|
|||
|
||||
## Running the tests
|
||||
|
||||
To run the test, you'll need to migrate contracts `safe-contracts` to the local ganache-cli
|
||||
|
||||
1. Migrating Safe Contracts:
|
||||
```
|
||||
git clone https://github.com/gnosis/safe-contracts.git
|
||||
cd safe-contracts
|
||||
yarn
|
||||
ganache-cli -l 7000000
|
||||
npx truffle compile
|
||||
npx truffle migrate
|
||||
```
|
||||
2. Compiling Token Contracts for the tests:
|
||||
Inside `safe-react` directory
|
||||
```
|
||||
npx truffle compile
|
||||
```
|
||||
3. Run the tests:
|
||||
```
|
||||
yarn test
|
||||
```
|
||||
|
|
|
@ -19,8 +19,8 @@ const logo = require('../assets/gnosis-safe-logo.svg')
|
|||
|
||||
type Props = Open & {
|
||||
classes: Object,
|
||||
providerDetails: React$Node,
|
||||
providerInfo: React$Node,
|
||||
providerDetails: React.Node,
|
||||
providerInfo: React.Node,
|
||||
}
|
||||
|
||||
const styles = () => ({
|
||||
|
|
|
@ -10,8 +10,8 @@ import { sm, md } from '~/theme/variables'
|
|||
|
||||
type Props = Open & {
|
||||
classes: Object,
|
||||
popupDetails: React$Node,
|
||||
info: React$Node,
|
||||
popupDetails: React.Node,
|
||||
info: React.Node,
|
||||
children: Function,
|
||||
}
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
import * as React from 'react'
|
||||
import { connect } from 'react-redux'
|
||||
import { logComponentStack, type Info } from '~/utils/logBoundaries'
|
||||
import { SharedSnackbarConsumer, type Variant } from '~/components/SharedSnackBar/Context'
|
||||
import { SharedSnackbarConsumer, type Variant } from '~/components/SharedSnackBar'
|
||||
import { WALLET_ERROR_MSG } from '~/logic/wallets/store/actions'
|
||||
import { getProviderInfo } from '~/logic/wallets/getWeb3'
|
||||
import ProviderAccesible from './component/ProviderInfo/ProviderAccesible'
|
||||
|
|
|
@ -9,7 +9,7 @@ type Props = {
|
|||
description: string,
|
||||
open: boolean,
|
||||
handleClose: Function,
|
||||
children: React$Node,
|
||||
children: React.Node,
|
||||
classes: Object,
|
||||
modalClassName: ?string,
|
||||
paperClassName: ?string,
|
||||
|
|
|
@ -1,67 +0,0 @@
|
|||
// @flow
|
||||
import * as React from 'react'
|
||||
import SharedSnackBar from './index'
|
||||
|
||||
const SharedSnackbarContext = React.createContext({
|
||||
openSnackbar: undefined,
|
||||
closeSnackbar: undefined,
|
||||
snackbarIsOpen: false,
|
||||
message: '',
|
||||
variant: 'info',
|
||||
})
|
||||
|
||||
type Props = {
|
||||
children: React$Node,
|
||||
}
|
||||
|
||||
export type Variant = 'success' | 'error' | 'warning' | 'info'
|
||||
|
||||
type State = {
|
||||
isOpen: boolean,
|
||||
message: string,
|
||||
variant: Variant,
|
||||
}
|
||||
|
||||
export class SharedSnackbarProvider extends React.Component<Props, State> {
|
||||
state = {
|
||||
isOpen: false,
|
||||
message: '',
|
||||
variant: 'info',
|
||||
}
|
||||
|
||||
openSnackbar = (message: string, variant: Variant) => {
|
||||
this.setState({
|
||||
message,
|
||||
variant,
|
||||
isOpen: true,
|
||||
})
|
||||
}
|
||||
|
||||
closeSnackbar = () => {
|
||||
this.setState({
|
||||
message: '',
|
||||
isOpen: false,
|
||||
})
|
||||
}
|
||||
|
||||
render() {
|
||||
const { children } = this.props
|
||||
|
||||
return (
|
||||
<SharedSnackbarContext.Provider
|
||||
value={{
|
||||
openSnackbar: this.openSnackbar,
|
||||
closeSnackbar: this.closeSnackbar,
|
||||
snackbarIsOpen: this.state.isOpen,
|
||||
message: this.state.message,
|
||||
variant: this.state.variant,
|
||||
}}
|
||||
>
|
||||
<SharedSnackBar />
|
||||
{children}
|
||||
</SharedSnackbarContext.Provider>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export const SharedSnackbarConsumer = SharedSnackbarContext.Consumer
|
|
@ -2,9 +2,8 @@
|
|||
import * as React from 'react'
|
||||
import { Snackbar } from '@material-ui/core'
|
||||
import SnackbarContent from '~/components/SnackbarContent'
|
||||
import { SharedSnackbarConsumer } from './Context'
|
||||
|
||||
const SharedSnackbar = () => (
|
||||
export const SharedSnackbar = () => (
|
||||
<SharedSnackbarConsumer>
|
||||
{(value) => {
|
||||
const {
|
||||
|
@ -32,4 +31,75 @@ const SharedSnackbar = () => (
|
|||
</SharedSnackbarConsumer>
|
||||
)
|
||||
|
||||
export default SharedSnackbar
|
||||
type SnackbarContext = {
|
||||
openSnackbar: Function,
|
||||
closeSnackbar: Function,
|
||||
snackbarIsOpen: boolean,
|
||||
message: string,
|
||||
variant: string,
|
||||
}
|
||||
|
||||
const SharedSnackbarContext = React.createContext<SnackbarContext>({
|
||||
openSnackbar: undefined,
|
||||
closeSnackbar: undefined,
|
||||
snackbarIsOpen: false,
|
||||
message: '',
|
||||
variant: 'info',
|
||||
})
|
||||
|
||||
type Props = {
|
||||
children: React.Node,
|
||||
}
|
||||
|
||||
export type Variant = 'success' | 'error' | 'warning' | 'info'
|
||||
|
||||
type State = {
|
||||
isOpen: boolean,
|
||||
message: string,
|
||||
variant: Variant,
|
||||
}
|
||||
|
||||
export class SharedSnackbarProvider extends React.Component<Props, State> {
|
||||
state = {
|
||||
isOpen: false,
|
||||
message: '',
|
||||
variant: 'info',
|
||||
}
|
||||
|
||||
openSnackbar = (message: string, variant: Variant) => {
|
||||
this.setState({
|
||||
message,
|
||||
variant,
|
||||
isOpen: true,
|
||||
})
|
||||
}
|
||||
|
||||
closeSnackbar = () => {
|
||||
this.setState({
|
||||
message: '',
|
||||
isOpen: false,
|
||||
})
|
||||
}
|
||||
|
||||
render() {
|
||||
const { children } = this.props
|
||||
const { message, variant, isOpen } = this.state
|
||||
|
||||
return (
|
||||
<SharedSnackbarContext.Provider
|
||||
value={{
|
||||
openSnackbar: this.openSnackbar,
|
||||
closeSnackbar: this.closeSnackbar,
|
||||
snackbarIsOpen: isOpen,
|
||||
message,
|
||||
variant,
|
||||
}}
|
||||
>
|
||||
<SharedSnackbar />
|
||||
{children}
|
||||
</SharedSnackbarContext.Provider>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export const SharedSnackbarConsumer = SharedSnackbarContext.Consumer
|
||||
|
|
|
@ -21,8 +21,8 @@ const styles = () => ({
|
|||
|
||||
type Props = {
|
||||
classes: Object,
|
||||
children: React$Node,
|
||||
controls: React$Node,
|
||||
children: React.Node,
|
||||
controls: React.Node,
|
||||
container?: number,
|
||||
padding?: boolean,
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
import * as React from 'react'
|
||||
|
||||
type Props = {
|
||||
children: React$Node,
|
||||
children: React.Node,
|
||||
}
|
||||
|
||||
const Step = ({ children }: Props) => (
|
||||
|
|
|
@ -16,7 +16,7 @@ export { default as Step } from './Step'
|
|||
type Props = {
|
||||
steps: string[],
|
||||
onSubmit: (values: Object) => Promise<void>,
|
||||
children: React$Node,
|
||||
children: React.Node,
|
||||
classes: Object,
|
||||
onReset?: () => void,
|
||||
initialValues?: Object,
|
||||
|
@ -73,13 +73,13 @@ class GnoStepper extends React.PureComponent<Props, State> {
|
|||
}))
|
||||
}
|
||||
|
||||
getPageProps = (pages: React$Node): PageProps => {
|
||||
getPageProps = (pages: React.Node): PageProps => {
|
||||
const { page } = this.state
|
||||
|
||||
return React.Children.toArray(pages)[page].props
|
||||
}
|
||||
|
||||
getActivePageFrom = (pages: React$Node) => {
|
||||
getActivePageFrom = (pages: React.Node) => {
|
||||
const activePageProps = this.getPageProps(pages)
|
||||
const { children, ...props } = activePageProps
|
||||
|
||||
|
|
|
@ -15,6 +15,7 @@ export type Column = {
|
|||
label: string,
|
||||
custom: boolean, // If content will be rendered by user manually
|
||||
width?: number,
|
||||
static?: boolean, // If content can't be sorted by values in the column
|
||||
}
|
||||
|
||||
export const cellWidth = (width: number | typeof undefined) => {
|
||||
|
@ -54,13 +55,17 @@ class GnoTableHead extends React.PureComponent<Props> {
|
|||
padding={column.disablePadding ? 'none' : 'default'}
|
||||
sortDirection={orderBy === column.id ? order : false}
|
||||
>
|
||||
<TableSortLabel
|
||||
active={orderBy === column.id}
|
||||
direction={order}
|
||||
onClick={this.changeSort(column.id, column.order)}
|
||||
>
|
||||
{column.label}
|
||||
</TableSortLabel>
|
||||
{column.static ? (
|
||||
column.label
|
||||
) : (
|
||||
<TableSortLabel
|
||||
active={orderBy === column.id}
|
||||
direction={order}
|
||||
onClick={this.changeSort(column.id, column.order)}
|
||||
>
|
||||
{column.label}
|
||||
</TableSortLabel>
|
||||
)}
|
||||
</TableCell>
|
||||
))}
|
||||
</TableRow>
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
// @flow
|
||||
import { List } from 'immutable'
|
||||
|
||||
export const FIXED = 'fixed'
|
||||
type Fixed = {
|
||||
|
@ -23,13 +24,14 @@ const desc = (a: Object, b: Object, orderBy: string, orderProp: boolean) => {
|
|||
}
|
||||
|
||||
// eslint-disable-next-line
|
||||
export const stableSort = <SortRow>(array: Array<SortRow>, cmp: any, fixed: boolean): Array<SortRow> => {
|
||||
const fixedElems: Array<SortRow> = fixed ? array.filter((elem: any) => elem.fixed) : []
|
||||
const data: Array<SortRow> = fixed ? array.filter((elem: any) => !elem[FIXED]) : array
|
||||
const stabilizedThis = data.map((el, index) => [el, index])
|
||||
export const stableSort = (dataArray: List<any>, cmp: any, fixed: boolean): List<any> => {
|
||||
const fixedElems: List<any> = fixed ? dataArray.filter((elem: any) => elem.fixed) : List([])
|
||||
const data: List<any> = fixed ? dataArray.filter((elem: any) => !elem[FIXED]) : dataArray
|
||||
let stabilizedThis = data.map((el, index) => [el, index])
|
||||
|
||||
stabilizedThis.sort((a, b) => {
|
||||
stabilizedThis = stabilizedThis.sort((a, b) => {
|
||||
const order = cmp(a[0], b[0])
|
||||
|
||||
if (order !== 0) {
|
||||
return order
|
||||
}
|
||||
|
@ -37,7 +39,7 @@ export const stableSort = <SortRow>(array: Array<SortRow>, cmp: any, fixed: bool
|
|||
return a[1] - b[1]
|
||||
})
|
||||
|
||||
const sortedElems: Array<SortRow> = stabilizedThis.map(el => el[0])
|
||||
const sortedElems: List<any> = stabilizedThis.map(el => el[0])
|
||||
|
||||
return fixedElems.concat(sortedElems)
|
||||
}
|
||||
|
|
|
@ -29,6 +29,7 @@ class TextField extends React.PureComponent<TextFieldProps> {
|
|||
text,
|
||||
inputAdornment,
|
||||
classes,
|
||||
testId,
|
||||
...rest
|
||||
} = this.props
|
||||
const helperText = value ? text : undefined
|
||||
|
@ -36,7 +37,7 @@ class TextField extends React.PureComponent<TextFieldProps> {
|
|||
const underline = meta.active || (meta.visited && !meta.valid)
|
||||
|
||||
const inputRoot = helperText ? classes.root : undefined
|
||||
const inputProps = { ...restInput, autoComplete: 'off' }
|
||||
const inputProps = { ...restInput, autoComplete: 'off', 'data-testid': testId }
|
||||
const inputRootProps = { ...inputAdornment, disableUnderline: !underline, className: inputRoot }
|
||||
|
||||
return (
|
||||
|
@ -51,6 +52,7 @@ class TextField extends React.PureComponent<TextFieldProps> {
|
|||
inputProps={inputProps}
|
||||
onChange={onChange}
|
||||
value={value}
|
||||
// data-testid={testId}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -11,7 +11,7 @@ type Props = {
|
|||
margin?: Size,
|
||||
padding?: Size,
|
||||
align?: 'center' | 'right' | 'left',
|
||||
children: React$Node,
|
||||
children: React.Node,
|
||||
className?: string,
|
||||
}
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
import * as React from 'react'
|
||||
|
||||
type Props = {
|
||||
children: React$Node,
|
||||
children: React.Node,
|
||||
}
|
||||
|
||||
class Bold extends React.PureComponent<Props> {
|
||||
|
|
|
@ -12,6 +12,7 @@ const styles = {
|
|||
type Props = {
|
||||
minWidth?: number,
|
||||
minHeight?: number,
|
||||
testId: string,
|
||||
}
|
||||
|
||||
const calculateStyleBased = (minWidth, minHeight) => ({
|
||||
|
@ -19,10 +20,12 @@ const calculateStyleBased = (minWidth, minHeight) => ({
|
|||
minHeight: minHeight && `${minHeight}px`,
|
||||
})
|
||||
|
||||
const GnoButton = ({ minWidth, minHeight, ...props }: Props) => {
|
||||
const GnoButton = ({
|
||||
minWidth, minHeight, testId = '', ...props
|
||||
}: Props) => {
|
||||
const style = calculateStyleBased(minWidth, minHeight)
|
||||
|
||||
return <Button style={style} {...props} />
|
||||
return <Button style={style} data-testid={testId} {...props} />
|
||||
}
|
||||
|
||||
export default withStyles(styles)(GnoButton)
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
// @flow
|
||||
/* eslint-disable react/button-has-type */
|
||||
/* eslint-disable react/default-props-match-prop-types */
|
||||
import * as React from 'react'
|
||||
import cn from 'classnames/bind'
|
||||
import styles from './index.scss'
|
||||
|
@ -12,20 +11,16 @@ type Props = {
|
|||
size?: 'sm' | 'md' | 'lg' | 'xl' | 'xxl',
|
||||
weight?: 'light' | 'regular' | 'bolder' | 'bold',
|
||||
color?: 'soft' | 'medium' | 'dark' | 'white' | 'fancy' | 'primary' | 'secondary' | 'warning' | 'disabled',
|
||||
testId?: string,
|
||||
}
|
||||
|
||||
const GnoButtonLink = ({
|
||||
type, size, weight, color, ...props
|
||||
}: Props) => (
|
||||
<button type={type} className={cx(styles.btnLink, size, color, weight)} {...props} />
|
||||
)
|
||||
|
||||
|
||||
GnoButtonLink.defaultProps = {
|
||||
type: 'button',
|
||||
size: 'md',
|
||||
weight: 'regular',
|
||||
color: 'secondary',
|
||||
}
|
||||
type = 'button',
|
||||
size = 'md',
|
||||
weight = 'regular',
|
||||
color = 'secondary',
|
||||
testId = '',
|
||||
...props
|
||||
}: Props) => <button type={type} className={cx(styles.btnLink, size, color, weight)} data-testid={testId} {...props} />
|
||||
|
||||
export default GnoButtonLink
|
||||
|
|
|
@ -27,7 +27,7 @@ type Props = {
|
|||
mdOffset?: number,
|
||||
lgOffset?: number,
|
||||
className?: string,
|
||||
children: React$Node,
|
||||
children: React.Node,
|
||||
}
|
||||
|
||||
const Col = ({
|
||||
|
|
|
@ -14,7 +14,7 @@ type Props = {
|
|||
color?: 'soft' | 'medium' | 'dark' | 'white' | 'fancy' | 'primary' | 'secondary' | 'warning' | 'disabled',
|
||||
tag: HeadingTag,
|
||||
truncate?: boolean,
|
||||
children: React$Node,
|
||||
children: React.Node,
|
||||
}
|
||||
|
||||
class Heading extends React.PureComponent<Props> {
|
||||
|
|
|
@ -10,7 +10,7 @@ type Props = {
|
|||
fullwidth?: boolean,
|
||||
bordered?: boolean,
|
||||
className?: string,
|
||||
style?: React$Node,
|
||||
style?: React.Node,
|
||||
}
|
||||
|
||||
const Img = ({
|
||||
|
|
|
@ -10,7 +10,7 @@ const cx = classNames.bind(styles)
|
|||
type Props = {
|
||||
padding?: 'xs' | 'sm' | 'md',
|
||||
to: string,
|
||||
children: React$Node,
|
||||
children: React.Node,
|
||||
color?: 'regular' | 'white',
|
||||
className?: string,
|
||||
innerRef: React.ElementRef<any>,
|
||||
|
|
|
@ -6,7 +6,7 @@ import styles from './index.scss'
|
|||
const cx = classNames.bind(styles)
|
||||
|
||||
type Props = {
|
||||
children: React$Node,
|
||||
children: React.Node,
|
||||
align?: 'center',
|
||||
overflow?: boolean
|
||||
}
|
||||
|
|
|
@ -2,11 +2,11 @@
|
|||
import React from 'react'
|
||||
import Footer from '~/components/Footer'
|
||||
import Header from '~/components/Header'
|
||||
import { SharedSnackbarProvider } from '~/components/SharedSnackBar/Context'
|
||||
import { SharedSnackbarProvider } from '~/components/SharedSnackBar'
|
||||
import styles from './index.scss'
|
||||
|
||||
type Props = {
|
||||
children: React$Node,
|
||||
children: React.Node,
|
||||
}
|
||||
|
||||
const PageFrame = ({ children }: Props) => (
|
||||
|
|
|
@ -13,7 +13,7 @@ type Props = {
|
|||
size?: 'sm' | 'md' | 'lg' | 'xl' | 'xxl',
|
||||
color?: 'soft' | 'medium' | 'dark' | 'white' | 'fancy' | 'primary' | 'secondary' | 'warning' | 'disabled',
|
||||
transform?: 'capitalize' | 'lowercase' | 'uppercase',
|
||||
children: React$Node,
|
||||
children: React.Node,
|
||||
dot?: boolean,
|
||||
className?: string,
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@ import styles from './index.scss'
|
|||
const cx = classNames.bind(styles)
|
||||
|
||||
type Props = {
|
||||
children: React$Node,
|
||||
children: React.Node,
|
||||
}
|
||||
|
||||
const Pre = ({ children, ...props }: Props) => (
|
||||
|
|
|
@ -8,7 +8,7 @@ const cx = classNames.bind(styles)
|
|||
|
||||
type Props = {
|
||||
className?: string,
|
||||
children: React$Node,
|
||||
children: React.Node,
|
||||
margin?: 'xs' | 'sm' | 'md' | 'lg' | 'xl',
|
||||
align?: 'center' | 'end' | 'start',
|
||||
grow?: boolean,
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
import React, { PureComponent } from 'react'
|
||||
|
||||
type Props = {
|
||||
children: React$Node
|
||||
children: React.Node
|
||||
}
|
||||
|
||||
class Span extends PureComponent<Props> {
|
||||
|
|
|
@ -6,10 +6,12 @@ import TableCell from '@material-ui/core/TableCell'
|
|||
import TableHead from '@material-ui/core/TableHead'
|
||||
import TableRow from '@material-ui/core/TableRow'
|
||||
|
||||
export { TableBody, TableCell, TableHead, TableRow }
|
||||
export {
|
||||
TableBody, TableCell, TableHead, TableRow,
|
||||
}
|
||||
|
||||
type Props = {
|
||||
children: React$Node,
|
||||
children: React.Node,
|
||||
size?: number
|
||||
}
|
||||
|
||||
|
@ -35,4 +37,3 @@ const GnoTable = ({ size, children }: Props) => {
|
|||
}
|
||||
|
||||
export default GnoTable
|
||||
|
||||
|
|
|
@ -7,7 +7,6 @@ import {
|
|||
OPEN_ADDRESS,
|
||||
SAFE_PARAM_ADDRESS,
|
||||
WELCOME_ADDRESS,
|
||||
SETTINS_ADDRESS,
|
||||
OPENING_ADDRESS,
|
||||
LOAD_ADDRESS,
|
||||
} from './routes'
|
||||
|
@ -23,9 +22,6 @@ const Opening = React.lazy(() => import('./opening/container'))
|
|||
const Load = React.lazy(() => import('./load/container/Load'))
|
||||
|
||||
const SAFE_ADDRESS = `${SAFELIST_ADDRESS}/:${SAFE_PARAM_ADDRESS}`
|
||||
const SAFE_SETTINGS = `${SAFE_ADDRESS}${SETTINS_ADDRESS}`
|
||||
|
||||
export const settingsUrlFrom = (safeAddress: string) => `${SAFELIST_ADDRESS}/${safeAddress}${SETTINS_ADDRESS}`
|
||||
|
||||
const Routes = () => (
|
||||
<Switch>
|
||||
|
|
|
@ -122,7 +122,7 @@ const Details = ({ classes, errors }: Props) => (
|
|||
|
||||
const DetailsForm = withStyles(styles)(Details)
|
||||
|
||||
const DetailsPage = () => (controls: React$Node, { errors }: Object) => (
|
||||
const DetailsPage = () => (controls: React.Node, { errors }: Object) => (
|
||||
<React.Fragment>
|
||||
<OpenPaper controls={controls} container={605}>
|
||||
<DetailsForm errors={errors} />
|
||||
|
|
|
@ -138,7 +138,7 @@ class ReviewComponent extends React.PureComponent<Props, State> {
|
|||
|
||||
const ReviewPage = withStyles(styles)(ReviewComponent)
|
||||
|
||||
const Review = ({ network, userAddress }: LayoutProps) => (controls: React$Node, { values }: Object) => (
|
||||
const Review = ({ network, userAddress }: LayoutProps) => (controls: React.Node, { values }: Object) => (
|
||||
<React.Fragment>
|
||||
<OpenPaper controls={controls} padding={false}>
|
||||
<ReviewPage network={network} values={values} userAddress={userAddress} />
|
||||
|
|
|
@ -155,7 +155,7 @@ const ReviewComponent = ({ values, classes, network }: Props) => {
|
|||
|
||||
const ReviewPage = withStyles(styles)(ReviewComponent)
|
||||
|
||||
const Review = ({ network }: LayoutProps) => (controls: React$Node, { values }: Object) => (
|
||||
const Review = ({ network }: LayoutProps) => (controls: React.Node, { values }: Object) => (
|
||||
<React.Fragment>
|
||||
<OpenPaper controls={controls} padding={false}>
|
||||
<ReviewPage network={network} values={values} />
|
||||
|
|
|
@ -83,7 +83,7 @@ const SafeName = ({ classes }: Props) => (
|
|||
|
||||
const SafeNameForm = withStyles(styles)(SafeName)
|
||||
|
||||
const SafeNamePage = () => (controls: React$Node) => (
|
||||
const SafeNamePage = () => (controls: React.Node) => (
|
||||
<OpenPaper controls={controls} container={600}>
|
||||
<SafeNameForm />
|
||||
</OpenPaper>
|
||||
|
|
|
@ -194,7 +194,7 @@ class SafeOwners extends React.Component<Props, State> {
|
|||
|
||||
const SafeOwnersForm = withStyles(styles)(SafeOwners)
|
||||
|
||||
const SafeOwnersPage = ({ updateInitialProps }: Object) => (controls: React$Node, { values, errors }: Object) => (
|
||||
const SafeOwnersPage = ({ updateInitialProps }: Object) => (controls: React.Node, { values, errors }: Object) => (
|
||||
<React.Fragment>
|
||||
<OpenPaper controls={controls} padding={false}>
|
||||
<SafeOwnersForm
|
||||
|
|
|
@ -80,7 +80,7 @@ const SafeThreshold = ({ classes, values }: Props) => {
|
|||
|
||||
const SafeThresholdForm = withStyles(styles)(SafeThreshold)
|
||||
|
||||
const SafeOwnersPage = () => (controls: React$Node, { values }: Object) => (
|
||||
const SafeOwnersPage = () => (controls: React.Node, { values }: Object) => (
|
||||
<React.Fragment>
|
||||
<OpenPaper controls={controls} container={450}>
|
||||
<SafeThresholdForm values={values} />
|
||||
|
|
|
@ -6,7 +6,6 @@ export const SAFELIST_ADDRESS = '/safes'
|
|||
export const OPEN_ADDRESS = '/open'
|
||||
export const LOAD_ADDRESS = '/load'
|
||||
export const WELCOME_ADDRESS = '/welcome'
|
||||
export const SETTINS_ADDRESS = '/settings'
|
||||
export const OPENING_ADDRESS = '/opening'
|
||||
|
||||
export const stillInOpeningView = () => {
|
||||
|
|
|
@ -32,7 +32,7 @@ type Props = {
|
|||
addresses: string[],
|
||||
}
|
||||
|
||||
const AddOwnerForm = ({ addresses, numOwners, threshold }: Props) => (controls: React$Node) => (
|
||||
const AddOwnerForm = ({ addresses, numOwners, threshold }: Props) => (controls: React.Node) => (
|
||||
<OpenPaper controls={controls}>
|
||||
<Heading tag="h2" margin="lg">
|
||||
Add Owner
|
||||
|
|
|
@ -17,7 +17,7 @@ const spinnerStyle = {
|
|||
minHeight: '50px',
|
||||
}
|
||||
|
||||
const Review = () => (controls: React$Node, { values, submitting }: FormProps) => {
|
||||
const Review = () => (controls: React.Node, { values, submitting }: FormProps) => {
|
||||
const text = values[INCREASE_PARAM]
|
||||
? 'This operation will increase the threshold of the safe'
|
||||
: 'This operation will not modify the threshold of the safe'
|
||||
|
@ -26,10 +26,14 @@ const Review = () => (controls: React$Node, { values, submitting }: FormProps) =
|
|||
<OpenPaper controls={controls}>
|
||||
<Heading tag="h2">Review the Add Owner operation</Heading>
|
||||
<Paragraph align="left">
|
||||
<Bold>Owner Name: </Bold> {values[NAME_PARAM]}
|
||||
<Bold>Owner Name: </Bold>
|
||||
{' '}
|
||||
{values[NAME_PARAM]}
|
||||
</Paragraph>
|
||||
<Paragraph align="left">
|
||||
<Bold>Owner Address: </Bold> {values[OWNER_ADDRESS_PARAM]}
|
||||
<Bold>Owner Address: </Bold>
|
||||
{' '}
|
||||
{values[OWNER_ADDRESS_PARAM]}
|
||||
</Paragraph>
|
||||
<Paragraph align="left">
|
||||
<Bold>{text}</Bold>
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
import React from 'react'
|
||||
import OpenInNew from '@material-ui/icons/OpenInNew'
|
||||
import { withStyles } from '@material-ui/core/styles'
|
||||
import { SharedSnackbarConsumer } from '~/components/SharedSnackBar/Context'
|
||||
import { SharedSnackbarConsumer } from '~/components/SharedSnackBar'
|
||||
import Close from '@material-ui/icons/Close'
|
||||
import IconButton from '@material-ui/core/IconButton'
|
||||
import Paragraph from '~/components/layout/Paragraph'
|
||||
|
@ -124,6 +124,7 @@ const ReviewTx = ({
|
|||
variant="contained"
|
||||
minWidth={140}
|
||||
color="primary"
|
||||
data-testid="submit-tx-btn"
|
||||
>
|
||||
SUBMIT
|
||||
</Button>
|
||||
|
|
|
@ -161,7 +161,14 @@ const SendFunds = ({
|
|||
<Button className={classes.button} minWidth={140} onClick={onClose}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button type="submit" className={classes.button} variant="contained" minWidth={140} color="primary">
|
||||
<Button
|
||||
type="submit"
|
||||
className={classes.button}
|
||||
variant="contained"
|
||||
minWidth={140}
|
||||
color="primary"
|
||||
data-testid="review-tx-btn"
|
||||
>
|
||||
Review
|
||||
</Button>
|
||||
</Row>
|
||||
|
|
|
@ -14,6 +14,8 @@ import { type Token } from '~/logic/tokens/store/model/token'
|
|||
import actions, { type Actions } from './actions'
|
||||
import { styles } from './style'
|
||||
|
||||
export const MANAGE_TOKENS_MODAL_CLOSE_BUTTON_TEST_ID = 'manage-tokens-close-modal-btn'
|
||||
|
||||
type Props = Actions & {
|
||||
onClose: () => void,
|
||||
classes: Object,
|
||||
|
@ -43,7 +45,7 @@ const Tokens = (props: Props) => {
|
|||
<Paragraph className={classes.manage} noMargin>
|
||||
Manage Tokens
|
||||
</Paragraph>
|
||||
<IconButton onClick={onClose} disableRipple>
|
||||
<IconButton onClick={onClose} disableRipple data-testid={MANAGE_TOKENS_MODAL_CLOSE_BUTTON_TEST_ID}>
|
||||
<Close className={classes.close} />
|
||||
</IconButton>
|
||||
</Row>
|
||||
|
|
|
@ -23,6 +23,11 @@ import { addressIsTokenContract, doesntExistInTokenList } from './validators'
|
|||
import { styles } from './style'
|
||||
import { getSymbolAndDecimalsFromContract } from './utils'
|
||||
|
||||
export const ADD_CUSTOM_TOKEN_ADDRESS_INPUT_TEST_ID = 'add-custom-token-address-input'
|
||||
export const ADD_CUSTOM_TOKEN_SYMBOLS_INPUT_TEST_ID = 'add-custom-token-symbols-input'
|
||||
export const ADD_CUSTOM_TOKEN_DECIMALS_INPUT_TEST_ID = 'add-custom-token-decimals-input'
|
||||
export const ADD_CUSTOM_TOKEN_FORM = 'add-custom-token-form'
|
||||
|
||||
type Props = {
|
||||
classes: Object,
|
||||
addToken: Function,
|
||||
|
@ -115,7 +120,7 @@ const AddCustomToken = (props: Props) => {
|
|||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<GnoForm onSubmit={handleSubmit} initialValues={formValues}>
|
||||
<GnoForm onSubmit={handleSubmit} initialValues={formValues} testId={ADD_CUSTOM_TOKEN_FORM}>
|
||||
{() => (
|
||||
<React.Fragment>
|
||||
<Block className={classes.formContainer}>
|
||||
|
@ -135,6 +140,7 @@ const AddCustomToken = (props: Props) => {
|
|||
placeholder="Token contract address*"
|
||||
text="Token contract address*"
|
||||
className={classes.addressInput}
|
||||
testId={ADD_CUSTOM_TOKEN_ADDRESS_INPUT_TEST_ID}
|
||||
/>
|
||||
<FormSpy
|
||||
subscription={{
|
||||
|
@ -156,6 +162,7 @@ const AddCustomToken = (props: Props) => {
|
|||
placeholder="Token symbol*"
|
||||
text="Token symbol"
|
||||
className={classes.addressInput}
|
||||
testId={ADD_CUSTOM_TOKEN_SYMBOLS_INPUT_TEST_ID}
|
||||
/>
|
||||
<Field
|
||||
name="decimals"
|
||||
|
@ -165,6 +172,7 @@ const AddCustomToken = (props: Props) => {
|
|||
placeholder="Token decimals*"
|
||||
text="Token decimals*"
|
||||
className={classes.addressInput}
|
||||
testId={ADD_CUSTOM_TOKEN_DECIMALS_INPUT_TEST_ID}
|
||||
/>
|
||||
<Block align="left">
|
||||
<Field name="showForAllSafes" component={Checkbox} type="checkbox" className={classes.checkbox} />
|
||||
|
|
|
@ -24,6 +24,9 @@ import { type Token } from '~/logic/tokens/store/model/token'
|
|||
import { setImageToPlaceholder } from '~/routes/safe/components/Balances/utils'
|
||||
import { styles } from './style'
|
||||
|
||||
export const ADD_CUSTOM_TOKEN_BUTTON_TEST_ID = 'add-custom-token-btn'
|
||||
export const TOGGLE_TOKEN_TEST_ID = 'toggle-token-btn'
|
||||
|
||||
type Props = {
|
||||
classes: Object,
|
||||
tokens: List<Token>,
|
||||
|
@ -141,6 +144,7 @@ class Tokens extends React.Component<Props, State> {
|
|||
color="secondary"
|
||||
className={classes.add}
|
||||
onClick={switchToAddCustomTokenScreen}
|
||||
testId={ADD_CUSTOM_TOKEN_BUTTON_TEST_ID}
|
||||
>
|
||||
+ ADD CUSTOM TOKEN
|
||||
</Button>
|
||||
|
@ -164,7 +168,11 @@ class Tokens extends React.Component<Props, State> {
|
|||
<ListItemText primary={token.symbol} secondary={token.name} />
|
||||
{token.address !== ETH_ADDRESS && (
|
||||
<ListItemSecondaryAction>
|
||||
<Switch onChange={this.onSwitch(token)} checked={isActive} />
|
||||
<Switch
|
||||
onChange={this.onSwitch(token)}
|
||||
checked={isActive}
|
||||
inputProps={{ 'data-testid': `${token.symbol}_${TOGGLE_TOKEN_TEST_ID}` }}
|
||||
/>
|
||||
</ListItemSecondaryAction>
|
||||
)}
|
||||
</ListItem>
|
||||
|
|
|
@ -18,6 +18,7 @@ export type BalanceRow = SortRow<BalanceData>
|
|||
export const getBalanceData = (activeTokens: List<Token>): List<BalanceRow> => {
|
||||
const rows = activeTokens.map((token: Token) => ({
|
||||
[BALANCE_TABLE_ASSET_ID]: { name: token.name, logoUri: token.logoUri },
|
||||
[buildOrderFieldFrom(BALANCE_TABLE_ASSET_ID)]: token.name,
|
||||
[BALANCE_TABLE_BALANCE_ID]: `${token.balance} ${token.symbol}`,
|
||||
[buildOrderFieldFrom(BALANCE_TABLE_BALANCE_ID)]: Number(token.balance),
|
||||
[FIXED]: token.get('symbol') === 'ETH',
|
||||
|
@ -27,16 +28,16 @@ export const getBalanceData = (activeTokens: List<Token>): List<BalanceRow> => {
|
|||
}
|
||||
|
||||
export const generateColumns = () => {
|
||||
const assetRow: Column = {
|
||||
const assetColumn: Column = {
|
||||
id: BALANCE_TABLE_ASSET_ID,
|
||||
order: false,
|
||||
order: true,
|
||||
disablePadding: false,
|
||||
label: 'Asset',
|
||||
custom: false,
|
||||
width: 250,
|
||||
}
|
||||
|
||||
const balanceRow: Column = {
|
||||
const balanceColumn: Column = {
|
||||
id: BALANCE_TABLE_BALANCE_ID,
|
||||
align: 'right',
|
||||
order: true,
|
||||
|
@ -53,7 +54,7 @@ export const generateColumns = () => {
|
|||
custom: true,
|
||||
}
|
||||
|
||||
return List([assetRow, balanceRow, actions])
|
||||
return List([assetColumn, balanceColumn, actions])
|
||||
}
|
||||
|
||||
export const filterByZero = (data: List<BalanceRow>, hideZero: boolean): List<BalanceRow> => data.filter((row: BalanceRow) => (hideZero ? row[buildOrderFieldFrom(BALANCE_TABLE_BALANCE_ID)] !== 0 : true))
|
||||
|
|
|
@ -12,6 +12,7 @@ import TableCell from '@material-ui/core/TableCell'
|
|||
import { withStyles } from '@material-ui/core/styles'
|
||||
import Col from '~/components/layout/Col'
|
||||
import Row from '~/components/layout/Row'
|
||||
import ButtonLink from '~/components/layout/ButtonLink'
|
||||
import Paragraph from '~/components/layout/Paragraph'
|
||||
import Modal from '~/components/Modal'
|
||||
import { type Column, cellWidth } from '~/components/Table/TableHead'
|
||||
|
@ -25,6 +26,9 @@ import SendModal from './SendModal'
|
|||
import Receive from './Receive'
|
||||
import { styles } from './style'
|
||||
|
||||
export const MANAGE_TOKENS_BUTTON_TEST_ID = 'manage-tokens-btn'
|
||||
export const BALANCE_ROW_TEST_ID = 'balance-row'
|
||||
|
||||
type State = {
|
||||
hideZero: boolean,
|
||||
showToken: boolean,
|
||||
|
@ -127,9 +131,7 @@ class Balances extends React.Component<Props, State> {
|
|||
<Paragraph className={classes.zero}>Hide zero balances</Paragraph>
|
||||
</Col>
|
||||
<Col xs={6} end="sm">
|
||||
<Paragraph noMargin size="md" color="secondary" className={classes.links} onClick={this.onShow('Token')}>
|
||||
Manage Tokens
|
||||
</Paragraph>
|
||||
<ButtonLink onClick={this.onShow('Token')} testId="manage-tokens-btn">Manage Tokens</ButtonLink>
|
||||
<Modal
|
||||
title="Manage Tokens"
|
||||
description="Enable and disable tokens to be listed"
|
||||
|
@ -154,7 +156,7 @@ class Balances extends React.Component<Props, State> {
|
|||
defaultFixed
|
||||
>
|
||||
{(sortedData: Array<BalanceRow>) => sortedData.map((row: any, index: number) => (
|
||||
<TableRow tabIndex={-1} key={index} className={classes.hide}>
|
||||
<TableRow tabIndex={-1} key={index} className={classes.hide} data-testid={BALANCE_ROW_TEST_ID}>
|
||||
{autoColumns.map((column: Column) => (
|
||||
<TableCell key={column.id} style={cellWidth(column.width)} align={column.align} component="td">
|
||||
{column.id === BALANCE_TABLE_ASSET_ID ? <AssetTableCell asset={row[column.id]} /> : row[column.id]}
|
||||
|
@ -169,6 +171,7 @@ class Balances extends React.Component<Props, State> {
|
|||
color="secondary"
|
||||
className={classes.send}
|
||||
onClick={() => this.showSendFunds(row.asset.name)}
|
||||
data-testid="balance-send-btn"
|
||||
>
|
||||
<CallMade className={classNames(classes.leftIcon, classes.iconSmall)} />
|
||||
Send
|
||||
|
|
|
@ -25,6 +25,9 @@ export const styles = (theme: Object) => ({
|
|||
'&:hover $actions': {
|
||||
visibility: 'initial',
|
||||
},
|
||||
'&:focus $actions': {
|
||||
visibility: 'initial',
|
||||
},
|
||||
},
|
||||
actions: {
|
||||
justifyContent: 'flex-end',
|
||||
|
|
|
@ -20,7 +20,7 @@ type Props = {
|
|||
const RemoveOwnerForm = ({
|
||||
numOwners, threshold, name, disabled, pendingTransactions,
|
||||
}: Props) => (
|
||||
controls: React$Node,
|
||||
controls: React.Node,
|
||||
) => (
|
||||
<OpenPaper controls={controls}>
|
||||
<Heading tag="h2" margin="lg">
|
||||
|
|
|
@ -21,7 +21,7 @@ const spinnerStyle = {
|
|||
minHeight: '50px',
|
||||
}
|
||||
|
||||
const Review = ({ name }: Props) => (controls: React$Node, { values, submitting }: FormProps) => {
|
||||
const Review = ({ name }: Props) => (controls: React.Node, { values, submitting }: FormProps) => {
|
||||
const text = values[DECREASE_PARAM]
|
||||
? 'This operation will decrease the threshold of the safe'
|
||||
: 'This operation will not modify the threshold of the safe'
|
||||
|
@ -30,7 +30,9 @@ const Review = ({ name }: Props) => (controls: React$Node, { values, submitting
|
|||
<OpenPaper controls={controls}>
|
||||
<Heading tag="h2">Review the Remove Owner operation</Heading>
|
||||
<Paragraph align="left">
|
||||
<Bold>Owner Name: </Bold> {name}
|
||||
<Bold>Owner Name: </Bold>
|
||||
{' '}
|
||||
{name}
|
||||
</Paragraph>
|
||||
<Paragraph align="left">
|
||||
<Bold>{text}</Bold>
|
||||
|
|
|
@ -1,9 +1,7 @@
|
|||
// @flow
|
||||
import * as React from 'react'
|
||||
import classNames from 'classnames'
|
||||
import Link from '~/components/layout/Link'
|
||||
import AccountBalance from '@material-ui/icons/AccountBalance'
|
||||
import Settings from '@material-ui/icons/Settings'
|
||||
import Avatar from '@material-ui/core/Avatar'
|
||||
import Collapse from '@material-ui/core/Collapse'
|
||||
import IconButton from '@material-ui/core/IconButton'
|
||||
|
@ -20,10 +18,8 @@ import Button from '~/components/layout/Button'
|
|||
import openHoc, { type Open } from '~/components/hoc/OpenHoc'
|
||||
import { type WithStyles } from '~/theme/mui'
|
||||
import { type Token } from '~/logic/tokens/store/model/token'
|
||||
import { settingsUrlFrom } from '~/routes'
|
||||
|
||||
type Props = Open & WithStyles & {
|
||||
safeAddress: string,
|
||||
tokens: Map<string, Token>,
|
||||
onMoveFunds: (token: Token) => void,
|
||||
}
|
||||
|
@ -37,10 +33,9 @@ const styles = {
|
|||
export const MOVE_FUNDS_BUTTON_TEXT = 'Move'
|
||||
|
||||
const BalanceComponent = openHoc(({
|
||||
open, toggle, tokens, classes, onMoveFunds, safeAddress,
|
||||
open, toggle, tokens, classes, onMoveFunds,
|
||||
}: Props) => {
|
||||
const hasBalances = tokens.count() > 0
|
||||
const settingsUrl = settingsUrlFrom(safeAddress)
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
|
@ -49,11 +44,6 @@ const BalanceComponent = openHoc(({
|
|||
<AccountBalance />
|
||||
</Avatar>
|
||||
<ListItemText primary="Balance" secondary="List of different token balances" />
|
||||
<ListItemIcon>
|
||||
<IconButton to={settingsUrl} disabled={!hasBalances} component={Link} className={classes.button}>
|
||||
<Settings />
|
||||
</IconButton>
|
||||
</ListItemIcon>
|
||||
<ListItemIcon>
|
||||
{open
|
||||
? <IconButton disableRipple><ExpandLess /></IconButton>
|
||||
|
|
|
@ -32,7 +32,7 @@ type SafeProps = {
|
|||
}
|
||||
|
||||
type State = {
|
||||
component?: React$Node,
|
||||
component?: React.Node,
|
||||
}
|
||||
|
||||
const listStyle = {
|
||||
|
|
|
@ -21,7 +21,7 @@ const spinnerStyle = {
|
|||
minHeight: '50px',
|
||||
}
|
||||
|
||||
const ReviewTx = ({ symbol }: Props) => (controls: React$Node, { values, submitting }: FormProps) => (
|
||||
const ReviewTx = ({ symbol }: Props) => (controls: React.Node, { values, submitting }: FormProps) => (
|
||||
<OpenPaper controls={controls}>
|
||||
<Heading tag="h2">Review the move token funds</Heading>
|
||||
<Paragraph align="left">
|
||||
|
|
|
@ -2,7 +2,9 @@
|
|||
import * as React from 'react'
|
||||
import Field from '~/components/forms/Field'
|
||||
import TextField from '~/components/forms/TextField'
|
||||
import { composeValidators, inLimit, mustBeFloat, required, greaterThan, mustBeEthereumAddress } from '~/components/forms/validator'
|
||||
import {
|
||||
composeValidators, inLimit, mustBeFloat, required, greaterThan, mustBeEthereumAddress,
|
||||
} from '~/components/forms/validator'
|
||||
import Block from '~/components/layout/Block'
|
||||
import OpenPaper from '~/components/Stepper/OpenPaper'
|
||||
import Heading from '~/components/layout/Heading'
|
||||
|
@ -17,7 +19,7 @@ type Props = {
|
|||
symbol: string,
|
||||
}
|
||||
|
||||
const SendTokenForm = ({ funds, symbol }: Props) => (controls: React$Node) => (
|
||||
const SendTokenForm = ({ funds, symbol }: Props) => (controls: React.Node) => (
|
||||
<OpenPaper controls={controls}>
|
||||
<Heading tag="h2" margin="lg">
|
||||
Send tokens Transaction
|
||||
|
|
|
@ -17,11 +17,13 @@ const spinnerStyle = {
|
|||
minHeight: '50px',
|
||||
}
|
||||
|
||||
const Review = () => (controls: React$Node, { values, submitting }: FormProps) => (
|
||||
const Review = () => (controls: React.Node, { values, submitting }: FormProps) => (
|
||||
<OpenPaper controls={controls}>
|
||||
<Heading tag="h2">Review the Threshold operation</Heading>
|
||||
<Paragraph align="left">
|
||||
<Bold>The new threshold will be: </Bold> {values[THRESHOLD_PARAM]}
|
||||
<Bold>The new threshold will be: </Bold>
|
||||
{' '}
|
||||
{values[THRESHOLD_PARAM]}
|
||||
</Paragraph>
|
||||
<Block style={spinnerStyle}>
|
||||
{ submitting && <CircularProgress size={50} /> }
|
||||
|
|
|
@ -5,7 +5,9 @@ import Heading from '~/components/layout/Heading'
|
|||
import OpenPaper from '~/components/Stepper/OpenPaper'
|
||||
import Field from '~/components/forms/Field'
|
||||
import TextField from '~/components/forms/TextField'
|
||||
import { composeValidators, minValue, maxValue, mustBeInteger, required } from '~/components/forms/validator'
|
||||
import {
|
||||
composeValidators, minValue, maxValue, mustBeInteger, required,
|
||||
} from '~/components/forms/validator'
|
||||
import { type Safe } from '~/routes/safe/store/models/safe'
|
||||
|
||||
export const THRESHOLD_PARAM = 'threshold'
|
||||
|
@ -15,7 +17,7 @@ type ThresholdProps = {
|
|||
safe: Safe,
|
||||
}
|
||||
|
||||
const ThresholdForm = ({ numOwners, safe }: ThresholdProps) => (controls: React$Node) => (
|
||||
const ThresholdForm = ({ numOwners, safe }: ThresholdProps) => (controls: React.Node) => (
|
||||
<OpenPaper controls={controls}>
|
||||
<Heading tag="h2" margin="lg">
|
||||
{'Change safe\'s threshold'}
|
||||
|
|
|
@ -50,11 +50,6 @@ export const grantedSelector: Selector<GlobalState, RouterProps, boolean> = crea
|
|||
},
|
||||
)
|
||||
|
||||
type UserToken = {
|
||||
address: string,
|
||||
balance: string,
|
||||
}
|
||||
|
||||
const safeEthAsTokenSelector: Selector<GlobalState, RouterProps, ?Token> = createSelector(
|
||||
safeSelector,
|
||||
(safe: Safe) => {
|
||||
|
|
|
@ -1,54 +0,0 @@
|
|||
// @flow
|
||||
/*
|
||||
import addBalances from '~/routes/safe/store/actions/addBalances'
|
||||
import { aNewStore } from '~/store'
|
||||
import { buildMathPropsFrom } from '~/test/utils/buildReactRouterProps'
|
||||
import { balanceSelector } from '../selectors'
|
||||
|
||||
const balanceSelectorTests = () => {
|
||||
describe('Safe Selector[balanceSelector]', () => {
|
||||
it('should return 0 when safe address is not found', () => {
|
||||
// GIVEN
|
||||
const safeAddress = 'foo'
|
||||
const match = buildMathPropsFrom(safeAddress)
|
||||
const store = aNewStore()
|
||||
|
||||
// WHEN
|
||||
const balance = balanceSelector(store.getState(), { match })
|
||||
|
||||
// THEN
|
||||
expect(balance).toBe('0')
|
||||
})
|
||||
|
||||
it('should return 0 when safe has no funds', async () => {
|
||||
// GIVEN
|
||||
const safeAddress = 'foo'
|
||||
const match = buildMathPropsFrom(safeAddress)
|
||||
const store = aNewStore()
|
||||
|
||||
// WHEN
|
||||
await store.dispatch(addBalances('bar', '1'))
|
||||
const balance = balanceSelector(store.getState(), { match })
|
||||
|
||||
// THEN
|
||||
expect(balance).toBe('0')
|
||||
})
|
||||
|
||||
it('should return safe funds', async () => {
|
||||
// GIVEN
|
||||
const safeAddress = 'foo'
|
||||
const match = buildMathPropsFrom(safeAddress)
|
||||
const store = aNewStore()
|
||||
|
||||
// WHEN
|
||||
await store.dispatch(addBalances(safeAddress, '1.3456'))
|
||||
const balance = balanceSelector(store.getState(), { match })
|
||||
|
||||
// THEN
|
||||
expect(balance).toBe('1.3456')
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
export default balanceSelectorTests
|
||||
*/
|
|
@ -1,59 +0,0 @@
|
|||
// @flow
|
||||
import SafeRecord, { type Safe } from '~/routes/safe/store/models/safe'
|
||||
import { buildOwnersFrom } from '~/routes/safe/store/actions/addSafe'
|
||||
|
||||
class SafeBuilder {
|
||||
safe: Safe
|
||||
|
||||
constructor() {
|
||||
this.safe = SafeRecord()
|
||||
}
|
||||
|
||||
withAddress(address: string) {
|
||||
this.safe = this.safe.set('address', address)
|
||||
return this
|
||||
}
|
||||
|
||||
withName(name: string) {
|
||||
this.safe = this.safe.set('name', name)
|
||||
return this
|
||||
}
|
||||
|
||||
withConfirmations(confirmations: number) {
|
||||
this.safe = this.safe.set('threshold', confirmations)
|
||||
return this
|
||||
}
|
||||
|
||||
withOwner(names: string[], adresses: string[]) {
|
||||
const owners = buildOwnersFrom(names, adresses)
|
||||
this.safe = this.safe.set('owners', owners)
|
||||
return this
|
||||
}
|
||||
|
||||
get() {
|
||||
return this.safe
|
||||
}
|
||||
}
|
||||
|
||||
const aSafe = () => new SafeBuilder()
|
||||
|
||||
export class SafeFactory {
|
||||
static oneOwnerSafe = (ownerAddress: string = '0x03db1a8b26d08df23337e9276a36b474510f0023') => aSafe()
|
||||
.withAddress('0x03db1a8b26d08df23337e9276a36b474510f0025')
|
||||
.withName('Adol ICO Safe')
|
||||
.withConfirmations(1)
|
||||
.withOwner(['Adol Metamask'], [ownerAddress])
|
||||
.get()
|
||||
|
||||
static twoOwnersSafe = (
|
||||
firstOwner: string = '0x03db1a8b26d08df23337e9276a36b474510f0023',
|
||||
secondOwner: string = '0x03db1a8b26d08df23337e9276a36b474510f0024',
|
||||
) => aSafe()
|
||||
.withAddress('0x03db1a8b26d08df23337e9276a36b474510f0026')
|
||||
.withName('Adol & Tobias Safe')
|
||||
.withConfirmations(2)
|
||||
.withOwner(['Adol Metamask', 'Tobias Metamask'], [firstOwner, secondOwner])
|
||||
.get()
|
||||
}
|
||||
|
||||
export default aSafe
|
|
@ -1,102 +0,0 @@
|
|||
// @flow
|
||||
/*
|
||||
import { List, Map } from 'immutable'
|
||||
import { makeTransaction, type Transaction } from '~/routes/safe/store/model/transaction'
|
||||
import { type Confirmation, makeConfirmation } from '~/routes/safe/store/model/confirmation'
|
||||
import { makeOwner } from '~/routes/safe/store/model/owner'
|
||||
import { confirmationsTransactionSelector } from '~/routes/safe/store/selectors/index'
|
||||
import { makeProvider } from '~/wallets/store/model/provider'
|
||||
|
||||
|
||||
const grantedSelectorTests = () => {
|
||||
describe('Safe Selector[confirmationsTransactionSelector]', () => {
|
||||
it('returns 1 confirmation if safe has only one owner when tx is created', () => {
|
||||
// GIVEN
|
||||
const firstConfirmation: Confirmation = makeConfirmation({
|
||||
owner: makeOwner(),
|
||||
status: true,
|
||||
hash: 'asdf',
|
||||
})
|
||||
|
||||
const transaction: Transaction = makeTransaction({
|
||||
name: 'Buy batteries',
|
||||
nonce: 1,
|
||||
value: 2,
|
||||
confirmations: List([firstConfirmation]),
|
||||
destination: 'destAddress',
|
||||
threshold: 2,
|
||||
tx: '',
|
||||
})
|
||||
|
||||
const reduxStore = {
|
||||
safes: Map(),
|
||||
providers: makeProvider(),
|
||||
tokens: Map(),
|
||||
transactions: Map(),
|
||||
}
|
||||
|
||||
// WHEN
|
||||
const threshold = confirmationsTransactionSelector(reduxStore, { transaction })
|
||||
|
||||
// THEN
|
||||
expect(threshold).toBe(1)
|
||||
})
|
||||
|
||||
it('returns 1 confirmation if safe has two or more owners when multisig tx is created', () => {
|
||||
// GIVEN
|
||||
const firstConfirmation: Confirmation = makeConfirmation({
|
||||
owner: makeOwner(),
|
||||
status: true,
|
||||
hash: 'asdf',
|
||||
})
|
||||
|
||||
const secondConfirmation: Confirmation = makeConfirmation({
|
||||
owner: makeOwner(),
|
||||
status: false,
|
||||
hash: '',
|
||||
})
|
||||
|
||||
const transaction: Transaction = makeTransaction({
|
||||
name: 'Buy batteries',
|
||||
nonce: 1,
|
||||
value: 2,
|
||||
confirmations: List([firstConfirmation, secondConfirmation]),
|
||||
destination: 'destAddress',
|
||||
threshold: 2,
|
||||
tx: '',
|
||||
})
|
||||
|
||||
const reduxStore = {
|
||||
safes: Map(),
|
||||
providers: makeProvider(),
|
||||
tokens: Map(),
|
||||
transactions: Map(),
|
||||
}
|
||||
|
||||
// WHEN
|
||||
const threshold = confirmationsTransactionSelector(reduxStore, { transaction })
|
||||
|
||||
// THEN
|
||||
expect(threshold).toBe(1)
|
||||
})
|
||||
|
||||
it('should return 0 confirmations if not transaction is sent as prop to component', () => {
|
||||
const reduxStore = {
|
||||
safes: Map(),
|
||||
providers: makeProvider(),
|
||||
tokens: Map(),
|
||||
transactions: Map(),
|
||||
}
|
||||
|
||||
// WHEN
|
||||
// $FlowFixMe
|
||||
const threshold = confirmationsTransactionSelector(reduxStore, { transaction: undefined })
|
||||
|
||||
// THEN
|
||||
expect(threshold).toBe(0)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
export default grantedSelectorTests
|
||||
*/
|
|
@ -1,84 +0,0 @@
|
|||
// @flow
|
||||
import { Map } from 'immutable'
|
||||
import { type Match } from 'react-router-dom'
|
||||
import { SAFE_REDUCER_ID } from '~/routes/safe/store/reducer/safe'
|
||||
import { type Safe } from '~/routes/safe/store/models/safe'
|
||||
import { SafeFactory } from '~/routes/safe/store/test/builder/safe.builder'
|
||||
import { buildMathPropsFrom } from '~/test/utils/buildReactRouterProps'
|
||||
import { getProviderInfo } from '~/logic/wallets/getWeb3'
|
||||
import { grantedSelector } from '~/routes/safe/container/selector'
|
||||
import { makeProvider } from '~/logic/wallets/store/model/provider'
|
||||
|
||||
const grantedSelectorTests = () => {
|
||||
let provider
|
||||
beforeEach(async () => {
|
||||
provider = await getProviderInfo()
|
||||
})
|
||||
|
||||
describe('Safe Selector[grantedSelector]', () => {
|
||||
it('should be granted to operate a safe when the user is owner', () => {
|
||||
// GIVEN
|
||||
let map: Map<string, Safe> = Map()
|
||||
map = map.set('fooAddress', SafeFactory.oneOwnerSafe(provider.account))
|
||||
|
||||
const match: Match = buildMathPropsFrom('fooAddress')
|
||||
|
||||
const reduxStore = {
|
||||
[SAFE_REDUCER_ID]: map,
|
||||
providers: makeProvider(provider),
|
||||
tokens: undefined,
|
||||
transactions: undefined,
|
||||
}
|
||||
|
||||
// WHEN
|
||||
const granted = grantedSelector(reduxStore, { match })
|
||||
|
||||
// THEN
|
||||
expect(granted).toBe(true)
|
||||
})
|
||||
|
||||
it('should be granted to operate a safe when the user is owner in case-insensitive', () => {
|
||||
// GIVEN
|
||||
let map: Map<string, Safe> = Map()
|
||||
map = map.set('fooAddress', SafeFactory.oneOwnerSafe(provider.account.toUpperCase()))
|
||||
|
||||
const match: Match = buildMathPropsFrom('fooAddress')
|
||||
|
||||
const reduxStore = {
|
||||
[SAFE_REDUCER_ID]: map,
|
||||
providers: makeProvider(provider),
|
||||
tokens: undefined,
|
||||
transactions: undefined,
|
||||
}
|
||||
|
||||
// WHEN
|
||||
const granted = grantedSelector(reduxStore, { match })
|
||||
|
||||
// THEN
|
||||
expect(granted).toBe(true)
|
||||
})
|
||||
|
||||
it('should NOT be granted to operate with a Safe when the user is NOT owner', () => {
|
||||
// GIVEN
|
||||
let map: Map<string, Safe> = Map()
|
||||
map = map.set('fooAddress', SafeFactory.oneOwnerSafe('inventedOwner'))
|
||||
|
||||
const match: Match = buildMathPropsFrom('fooAddress')
|
||||
|
||||
const reduxStore = {
|
||||
[SAFE_REDUCER_ID]: map,
|
||||
providers: makeProvider(provider),
|
||||
tokens: undefined,
|
||||
transactions: undefined,
|
||||
}
|
||||
|
||||
// WHEN
|
||||
const granted = grantedSelector(reduxStore, { match })
|
||||
|
||||
// THEN
|
||||
expect(granted).toBe(false)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
export default grantedSelectorTests
|
|
@ -1,60 +0,0 @@
|
|||
// @flow
|
||||
import {
|
||||
combineReducers, createStore, applyMiddleware, compose,
|
||||
} from 'redux'
|
||||
import thunk from 'redux-thunk'
|
||||
import safeReducer, { SAFE_REDUCER_ID } from '~/routes/safe/store/reducer/safe'
|
||||
import addSafe from '~/routes/safe/store/actions/addSafe'
|
||||
import * as SafeFields from '~/routes/open/components/fields'
|
||||
import { getAccountsFrom, getNamesFrom } from '~/routes/open/utils/safeDataExtractor'
|
||||
import { SafeFactory } from './builder/safe.builder'
|
||||
|
||||
const aStore = (initState) => {
|
||||
const reducers = combineReducers({
|
||||
[SAFE_REDUCER_ID]: safeReducer,
|
||||
})
|
||||
const middlewares = [thunk]
|
||||
const enhancers = [applyMiddleware(...middlewares)]
|
||||
return createStore(reducers, initState, compose(...enhancers))
|
||||
}
|
||||
|
||||
const providerReducerTests = () => {
|
||||
describe('Safe Actions[addSafe]', () => {
|
||||
let store
|
||||
let address
|
||||
let formValues
|
||||
beforeEach(() => {
|
||||
store = aStore()
|
||||
address = '0x03db1a8b26d08df23337e9276a36b474510f0025'
|
||||
formValues = {
|
||||
[SafeFields.FIELD_NAME]: 'Adol ICO Safe',
|
||||
[SafeFields.FIELD_CONFIRMATIONS]: 1,
|
||||
[SafeFields.FIELD_OWNERS]: 1,
|
||||
[SafeFields.getOwnerAddressBy(0)]: '0x03db1a8b26d08df23337e9276a36b474510f0023',
|
||||
[SafeFields.getOwnerNameBy(0)]: 'Adol Metamask',
|
||||
address,
|
||||
}
|
||||
})
|
||||
|
||||
it('reducer should return SafeRecord from form values', () => {
|
||||
// GIVEN in beforeEach method
|
||||
|
||||
// WHEN
|
||||
store.dispatch(
|
||||
addSafe(
|
||||
formValues[SafeFields.FIELD_NAME],
|
||||
formValues.address,
|
||||
formValues[SafeFields.FIELD_CONFIRMATIONS],
|
||||
getNamesFrom(formValues),
|
||||
getAccountsFrom(formValues),
|
||||
),
|
||||
)
|
||||
const safes = store.getState()[SAFE_REDUCER_ID]
|
||||
|
||||
// THEN
|
||||
expect(safes.get(address)).toEqual(SafeFactory.oneOwnerSafe())
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
export default providerReducerTests
|
|
@ -1,56 +0,0 @@
|
|||
// @flow
|
||||
import { Map } from 'immutable'
|
||||
import { type Match } from 'react-router-dom'
|
||||
import { SAFE_REDUCER_ID } from '~/routes/safe/store/reducer/safe'
|
||||
import { type Safe } from '~/routes/safe/store/models/safe'
|
||||
import { SafeFactory } from '~/routes/safe/store/test/builder/safe.builder'
|
||||
import { buildMathPropsFrom } from '~/test/utils/buildReactRouterProps'
|
||||
import { safeSelector } from '../selectors'
|
||||
|
||||
const safeSelectorTests = () => {
|
||||
describe('Safe Selector[safeSelector]', () => {
|
||||
it('should return empty list when no safes', () => {
|
||||
// GIVEN
|
||||
const reduxStore = {
|
||||
[SAFE_REDUCER_ID]: Map(),
|
||||
providers: undefined,
|
||||
tokens: undefined,
|
||||
transactions: undefined,
|
||||
}
|
||||
const match: Match = buildMathPropsFrom('fooAddress')
|
||||
|
||||
// WHEN
|
||||
const safes = safeSelector(reduxStore, { match })
|
||||
|
||||
// THEN
|
||||
expect(safes).toBe(undefined)
|
||||
})
|
||||
|
||||
it('should return a list of size 2 when 2 safes are created', () => {
|
||||
// GIVEN
|
||||
let map: Map<string, Safe> = Map()
|
||||
map = map.set('fooAddress', SafeFactory.oneOwnerSafe())
|
||||
map = map.set('barAddress', SafeFactory.twoOwnersSafe())
|
||||
|
||||
const match: Match = buildMathPropsFrom('fooAddress')
|
||||
const undefMatch: Match = buildMathPropsFrom('inventedAddress')
|
||||
|
||||
const reduxStore = {
|
||||
[SAFE_REDUCER_ID]: map,
|
||||
providers: undefined,
|
||||
tokens: undefined,
|
||||
transactions: undefined,
|
||||
}
|
||||
|
||||
// WHEN
|
||||
const oneOwnerSafe = safeSelector(reduxStore, { match })
|
||||
const undefinedSafe = safeSelector(reduxStore, { match: undefMatch })
|
||||
|
||||
// THEN
|
||||
expect(oneOwnerSafe).toEqual(SafeFactory.oneOwnerSafe())
|
||||
expect(undefinedSafe).toBe(undefined)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
export default safeSelectorTests
|
|
@ -1,27 +0,0 @@
|
|||
// @flow
|
||||
import safeReducerTests from './safe.reducer'
|
||||
// import balanceSelectorTests from './balance.selector'
|
||||
import safeSelectorTests from './safe.selector'
|
||||
import grantedSelectorTests from './granted.selector'
|
||||
// import confirmationsSelectorTests from './confirmations.selector'
|
||||
// import transactionsSelectorTests from './transactions.selector'
|
||||
|
||||
describe('Safe Test suite', () => {
|
||||
// ACTIONS AND REDUCERS
|
||||
safeReducerTests()
|
||||
|
||||
// SAFE SELECTOR
|
||||
safeSelectorTests()
|
||||
|
||||
// BALANCE SELECTOR
|
||||
// balanceSelectorTests()
|
||||
|
||||
// GRANTED SELECTOR
|
||||
grantedSelectorTests()
|
||||
|
||||
// CONFIRMATIONS SELECTOR
|
||||
// confirmationsSelectorTests()
|
||||
|
||||
// TRANSACTIONS SELECTOR
|
||||
// transactionsSelectorTests()
|
||||
})
|
|
@ -1,131 +0,0 @@
|
|||
// @flow
|
||||
/*
|
||||
import { List, Map } from 'immutable'
|
||||
import { SAFE_REDUCER_ID } from '~/routes/safe/store/reducer/safe'
|
||||
import { safeTransactionsSelector } from '~/routes/safe/store/selectors/index'
|
||||
import { makeProvider } from '~/wallets/store/model/provider'
|
||||
import { makeConfirmation, type Confirmation } from '~/routes/safe/store/model/confirmation'
|
||||
import { makeOwner } from '~/routes/safe/store/model/owner'
|
||||
import { makeTransaction, type Transaction } from '~/routes/safe/store/model/transaction'
|
||||
|
||||
const grantedSelectorTests = () => {
|
||||
describe('Safe Selector[safeTransactionsSelector]', () => {
|
||||
it('should return empty list if no transactions in store', () => {
|
||||
// GIVEN
|
||||
const reduxStore = {
|
||||
[SAFE_REDUCER_ID]: Map(),
|
||||
providers: makeProvider(),
|
||||
tokens: undefined,
|
||||
transactions: Map(),
|
||||
}
|
||||
|
||||
// WHEN
|
||||
const transactions = safeTransactionsSelector(reduxStore, { safeAddress: 'fooAddress' })
|
||||
|
||||
// THEN
|
||||
expect(transactions).toEqual(List([]))
|
||||
})
|
||||
|
||||
it('should return empty list if transactions in store but not safe address in props', () => {
|
||||
// GIVEN
|
||||
const firstConfirmation: Confirmation = makeConfirmation({
|
||||
owner: makeOwner(),
|
||||
status: true,
|
||||
hash: 'asdf',
|
||||
})
|
||||
|
||||
const transaction: Transaction = makeTransaction({
|
||||
name: 'Buy batteries',
|
||||
nonce: 1,
|
||||
value: 2,
|
||||
confirmations: List([firstConfirmation]),
|
||||
destination: 'destAddress',
|
||||
threshold: 2,
|
||||
tx: '',
|
||||
})
|
||||
|
||||
const reduxStore = {
|
||||
[SAFE_REDUCER_ID]: Map(),
|
||||
providers: makeProvider(),
|
||||
tokens: undefined,
|
||||
transactions: Map({ fooAddress: List([transaction]) }),
|
||||
}
|
||||
|
||||
// WHEN
|
||||
const transactionsEmpty = safeTransactionsSelector(reduxStore, { safeAddress: '' })
|
||||
// $FlowFixMe
|
||||
const transactionsUndefined = safeTransactionsSelector(reduxStore, { safeAddress: undefined })
|
||||
|
||||
// THEN
|
||||
expect(transactionsEmpty).toEqual(List([]))
|
||||
expect(transactionsUndefined).toEqual(List([]))
|
||||
})
|
||||
|
||||
it('should return empty list if there are transactions belonging to different address', () => {
|
||||
// GIVEN
|
||||
const firstConfirmation: Confirmation = makeConfirmation({
|
||||
owner: makeOwner(),
|
||||
status: true,
|
||||
hash: 'asdf',
|
||||
})
|
||||
|
||||
const transaction: Transaction = makeTransaction({
|
||||
name: 'Buy batteries',
|
||||
nonce: 1,
|
||||
value: 2,
|
||||
confirmations: List([firstConfirmation]),
|
||||
destination: 'destAddress',
|
||||
threshold: 2,
|
||||
tx: '',
|
||||
})
|
||||
|
||||
const reduxStore = {
|
||||
[SAFE_REDUCER_ID]: Map(),
|
||||
providers: makeProvider(),
|
||||
tokens: undefined,
|
||||
transactions: Map({ fooAddress: List([transaction]) }),
|
||||
}
|
||||
|
||||
// WHEN
|
||||
const transactions = safeTransactionsSelector(reduxStore, { safeAddress: 'invented' })
|
||||
|
||||
// THEN
|
||||
expect(transactions).toEqual(List([]))
|
||||
})
|
||||
|
||||
it('should return transactions of safe', () => {
|
||||
// GIVEN
|
||||
const firstConfirmation: Confirmation = makeConfirmation({
|
||||
owner: makeOwner(),
|
||||
status: true,
|
||||
hash: 'asdf',
|
||||
})
|
||||
|
||||
const transaction: Transaction = makeTransaction({
|
||||
name: 'Buy batteries',
|
||||
nonce: 1,
|
||||
value: 2,
|
||||
confirmations: List([firstConfirmation]),
|
||||
destination: 'destAddress',
|
||||
threshold: 2,
|
||||
tx: '',
|
||||
})
|
||||
|
||||
const reduxStore = {
|
||||
[SAFE_REDUCER_ID]: Map(),
|
||||
providers: makeProvider(),
|
||||
tokens: undefined,
|
||||
transactions: Map({ fooAddress: List([transaction]) }),
|
||||
}
|
||||
|
||||
// WHEN
|
||||
const transactions = safeTransactionsSelector(reduxStore, { safeAddress: 'fooAddress' })
|
||||
|
||||
// THEN
|
||||
expect(transactions).toEqual(List([transaction]))
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
export default grantedSelectorTests
|
||||
*/
|
|
@ -3,7 +3,6 @@ import { storiesOf } from '@storybook/react'
|
|||
import { List } from 'immutable'
|
||||
import * as React from 'react'
|
||||
import styles from '~/components/layout/PageFrame/index.scss'
|
||||
import { SafeFactory } from '~/routes/safe/store/test/builder/safe.builder'
|
||||
import Component from './Layout'
|
||||
|
||||
const FrameDecorator = story => <div className={styles.frame}>{story()}</div>
|
||||
|
@ -12,7 +11,3 @@ storiesOf('Routes /safes', module)
|
|||
.addDecorator(FrameDecorator)
|
||||
.add('Safe List whithout safes and connected', () => <Component provider="METAMASK" safes={List([])} />)
|
||||
.add('Safe List whithout safes and NOT connected', () => <Component provider="" safes={List([])} />)
|
||||
.add('Safe List whith 2 safes', () => {
|
||||
const safes = List([SafeFactory.oneOwnerSafe(), SafeFactory.twoOwnersSafe()])
|
||||
return <Component provider="METAMASK" safes={safes} />
|
||||
})
|
||||
|
|
|
@ -1,7 +0,0 @@
|
|||
// @flow
|
||||
import safesSelectorTests from './safes.selector'
|
||||
|
||||
describe('SafeList Test suite', () => {
|
||||
// safesSelector SELECTOR
|
||||
safesSelectorTests()
|
||||
})
|
|
@ -1,122 +0,0 @@
|
|||
// @flow
|
||||
import { List, Map } from 'immutable'
|
||||
import { SAFE_REDUCER_ID } from '~/routes/safe/store/reducer/safe'
|
||||
import { type Safe } from '~/routes/safe/store/models/safe'
|
||||
import { getProviderInfo } from '~/logic/wallets/getWeb3'
|
||||
import { SafeFactory } from '~/routes/safe/store/test/builder/safe.builder'
|
||||
import { PROVIDER_REDUCER_ID } from '~/logic/wallets/store/reducer/provider'
|
||||
import { makeProvider, type Provider } from '~/logic/wallets/store/model/provider'
|
||||
import { safesByOwnerSelector } from '../selectors'
|
||||
|
||||
const safesListSelectorTests = () => {
|
||||
let walletRecord: Provider
|
||||
beforeEach(async () => {
|
||||
const provider = await getProviderInfo()
|
||||
walletRecord = makeProvider(provider)
|
||||
})
|
||||
|
||||
describe('Safes Selector[safesByOwnerSelector]', () => {
|
||||
it('should return empty list when no safes', () => {
|
||||
// GIVEN
|
||||
const reduxStore = {
|
||||
[PROVIDER_REDUCER_ID]: walletRecord,
|
||||
[SAFE_REDUCER_ID]: Map(),
|
||||
tokens: undefined,
|
||||
transactions: undefined,
|
||||
}
|
||||
const emptyList = List([])
|
||||
|
||||
// WHEN
|
||||
const safes = safesByOwnerSelector(reduxStore, {})
|
||||
|
||||
// THEN
|
||||
expect(safes).toEqual(emptyList)
|
||||
})
|
||||
|
||||
it('should return a list of size 0 when 0 of 2 safes contains the user as owner', () => {
|
||||
// GIVEN
|
||||
let map: Map<string, Safe> = Map()
|
||||
map = map.set('fooAddress', SafeFactory.oneOwnerSafe('foo'))
|
||||
map = map.set('barAddress', SafeFactory.twoOwnersSafe('foo', 'bar'))
|
||||
|
||||
const reduxStore = {
|
||||
[PROVIDER_REDUCER_ID]: walletRecord,
|
||||
[SAFE_REDUCER_ID]: map,
|
||||
tokens: undefined,
|
||||
transactions: undefined,
|
||||
}
|
||||
|
||||
// WHEN
|
||||
const safes = safesByOwnerSelector(reduxStore, {})
|
||||
|
||||
// THEN
|
||||
expect(safes.count()).toEqual(0)
|
||||
})
|
||||
|
||||
it('should return a list of size 1 when 1 of 2 safes contains the user as owner', () => {
|
||||
// GIVEN
|
||||
let map: Map<string, Safe> = Map()
|
||||
map = map.set('fooAddress', SafeFactory.oneOwnerSafe(walletRecord.account))
|
||||
map = map.set('barAddress', SafeFactory.twoOwnersSafe('foo', 'bar'))
|
||||
|
||||
const reduxStore = {
|
||||
[PROVIDER_REDUCER_ID]: walletRecord,
|
||||
[SAFE_REDUCER_ID]: map,
|
||||
tokens: undefined,
|
||||
transactions: undefined,
|
||||
}
|
||||
|
||||
// WHEN
|
||||
const safes = safesByOwnerSelector(reduxStore, {})
|
||||
|
||||
// THEN
|
||||
expect(safes.count()).toEqual(1)
|
||||
})
|
||||
|
||||
it('should return a list of size 2 when 2 of 2 safes contains the user as owner', () => {
|
||||
// GIVEN
|
||||
let map: Map<string, Safe> = Map()
|
||||
const userAccount = walletRecord.account
|
||||
map = map.set('fooAddress', SafeFactory.oneOwnerSafe(userAccount))
|
||||
map = map.set('barAddress', SafeFactory.twoOwnersSafe('foo', userAccount))
|
||||
|
||||
const reduxStore = {
|
||||
[SAFE_REDUCER_ID]: map,
|
||||
[PROVIDER_REDUCER_ID]: walletRecord,
|
||||
tokens: undefined,
|
||||
transactions: undefined,
|
||||
}
|
||||
|
||||
// WHEN
|
||||
const safes = safesByOwnerSelector(reduxStore, {})
|
||||
|
||||
// THEN
|
||||
expect(safes.count()).toEqual(2)
|
||||
expect(safes.get(0)).not.toEqual(safes.get(1))
|
||||
})
|
||||
|
||||
it('should return safes under owners case-insensitive', () => {
|
||||
// GIVEN
|
||||
let map: Map<string, Safe> = Map()
|
||||
const userAccountUpper = walletRecord.account.toUpperCase()
|
||||
map = map.set('fooAddress', SafeFactory.oneOwnerSafe(userAccountUpper))
|
||||
map = map.set('barAddress', SafeFactory.twoOwnersSafe('foo', userAccountUpper))
|
||||
|
||||
const reduxStore = {
|
||||
[SAFE_REDUCER_ID]: map,
|
||||
[PROVIDER_REDUCER_ID]: walletRecord,
|
||||
tokens: undefined,
|
||||
transactions: undefined,
|
||||
}
|
||||
|
||||
// WHEN
|
||||
const safes = safesByOwnerSelector(reduxStore, {})
|
||||
|
||||
// THEN
|
||||
expect(safes.count()).toEqual(2)
|
||||
expect(safes.get(0)).not.toEqual(safes.get(1))
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
export default safesListSelectorTests
|
|
@ -1,27 +1,19 @@
|
|||
// @flow
|
||||
import { type Store } from 'redux'
|
||||
import TestUtils from 'react-dom/test-utils'
|
||||
import SafeView from '~/routes/safe/components/Safe'
|
||||
import { aNewStore, type GlobalState } from '~/store'
|
||||
import { sleep } from '~/utils/timer'
|
||||
import { getWeb3 } from '~/logic/wallets/getWeb3'
|
||||
import { addEtherTo } from '~/test/utils/tokenMovements'
|
||||
import { sendEtherTo } from '~/test/utils/tokenMovements'
|
||||
import { aMinedSafe } from '~/test/builder/safe.redux.builder'
|
||||
import { travelToSafe } from '~/test/builder/safe.dom.utils'
|
||||
import { MOVE_FUNDS_BUTTON_TEXT } from '~/routes/safe/components/Safe/BalanceInfo'
|
||||
import { renderSafeView } from '~/test/builder/safe.dom.utils'
|
||||
|
||||
export type DomSafe = {
|
||||
address: string,
|
||||
safeButtons: Element[],
|
||||
safe: React$Component<any, any>,
|
||||
accounts: string[],
|
||||
store: Store<GlobalState>,
|
||||
SafeDom: any,
|
||||
}
|
||||
|
||||
export const filterMoveButtonsFrom = (buttons: Element[]) => buttons.filter(
|
||||
(button: Element): boolean => button.getElementsByTagName('span')[0].textContent !== MOVE_FUNDS_BUTTON_TEXT,
|
||||
)
|
||||
|
||||
export const renderSafeInDom = async (owners: number = 1, threshold: number = 1): Promise<DomSafe> => {
|
||||
// create store
|
||||
const store = aNewStore()
|
||||
|
@ -30,23 +22,16 @@ export const renderSafeInDom = async (owners: number = 1, threshold: number = 1)
|
|||
// have available accounts
|
||||
const accounts = await getWeb3().eth.getAccounts()
|
||||
// navigate to SAFE route
|
||||
const SafeDom = travelToSafe(store, address)
|
||||
const SafeDom = renderSafeView(store, address)
|
||||
|
||||
// add funds to safe
|
||||
await addEtherTo(address, '0.1')
|
||||
await sendEtherTo(address, '0.1')
|
||||
// wait until funds are displayed and buttons are enabled
|
||||
await sleep(3000)
|
||||
|
||||
// $FlowFixMe
|
||||
const Safe = TestUtils.findRenderedComponentWithType(SafeDom, SafeView)
|
||||
// $FlowFixMe
|
||||
const buttons = TestUtils.scryRenderedDOMComponentsWithTag(Safe, 'button')
|
||||
const filteredButtons = filterMoveButtonsFrom(buttons)
|
||||
|
||||
return {
|
||||
address,
|
||||
safeButtons: filteredButtons,
|
||||
safe: SafeDom,
|
||||
SafeDom,
|
||||
accounts,
|
||||
store,
|
||||
}
|
||||
|
|
|
@ -1,15 +1,18 @@
|
|||
// @flow
|
||||
import * as React from 'react'
|
||||
import TestUtils from 'react-dom/test-utils'
|
||||
import { type Store } from 'redux'
|
||||
import { Provider } from 'react-redux'
|
||||
import { ConnectedRouter } from 'connected-react-router'
|
||||
import PageFrame from '~/components/layout/PageFrame'
|
||||
import { render } from '@testing-library/react'
|
||||
import ListItemText from '~/components/List/ListItemText/index'
|
||||
import { SEE_MULTISIG_BUTTON_TEXT } from '~/routes/safe/components/Safe/MultisigTx'
|
||||
import fetchTransactions from '~/routes/safe/store/actions/fetchTransactions'
|
||||
import { sleep } from '~/utils/timer'
|
||||
import { Provider } from 'react-redux'
|
||||
import { ConnectedRouter } from 'connected-react-router'
|
||||
import { history } from '~/store'
|
||||
import AppRoutes from '~/routes'
|
||||
import { SAFELIST_ADDRESS, SETTINS_ADDRESS } from '~/routes/routes'
|
||||
import { history, type GlobalState } from '~/store'
|
||||
import { EMPTY_DATA } from '~/logic/wallets/ethTransactions'
|
||||
|
||||
export const EXPAND_BALANCE_INDEX = 0
|
||||
|
@ -30,7 +33,7 @@ export const listTxsClickingOn = async (store: Store, seeTxsButton: Element, saf
|
|||
await sleep(800)
|
||||
}
|
||||
|
||||
export const checkMinedTx = (Transaction: React$Component<any, any>, name: string) => {
|
||||
export const checkMinedTx = (Transaction: React.Component<any, any>, name: string) => {
|
||||
const paragraphs = TestUtils.scryRenderedDOMComponentsWithTag(Transaction, 'p')
|
||||
|
||||
const status = 'Already executed'
|
||||
|
@ -46,9 +49,9 @@ export const checkMinedTx = (Transaction: React$Component<any, any>, name: strin
|
|||
expect(hashParagraph).toContain(EMPTY_DATA)
|
||||
}
|
||||
|
||||
export const getListItemsFrom = (Transaction: React$Component<any, any>) => TestUtils.scryRenderedComponentsWithType(Transaction, ListItemText)
|
||||
export const getListItemsFrom = (Transaction: React.Component<any, any>) => TestUtils.scryRenderedComponentsWithType(Transaction, ListItemText)
|
||||
|
||||
export const expand = async (Transaction: React$Component<any, any>) => {
|
||||
export const expand = async (Transaction: React.Component<any, any>) => {
|
||||
const listItems = getListItemsFrom(Transaction)
|
||||
if (listItems.length > 4) {
|
||||
return
|
||||
|
@ -65,7 +68,7 @@ export const expand = async (Transaction: React$Component<any, any>) => {
|
|||
}
|
||||
|
||||
export const checkPendingTx = async (
|
||||
Transaction: React$Component<any, any>,
|
||||
Transaction: React.Component<any, any>,
|
||||
safeThreshold: number,
|
||||
name: string,
|
||||
statusses: string[],
|
||||
|
@ -92,25 +95,28 @@ export const refreshTransactions = async (store: Store<GlobalState>, safeAddress
|
|||
await sleep(1500)
|
||||
}
|
||||
|
||||
const createDom = (store: Store): React$Component<{}> => TestUtils.renderIntoDocument(
|
||||
<Provider store={store}>
|
||||
<ConnectedRouter history={history}>
|
||||
<AppRoutes />
|
||||
</ConnectedRouter>
|
||||
</Provider>,
|
||||
)
|
||||
const renderApp = (store: Store) => ({
|
||||
...render(
|
||||
<Provider store={store}>
|
||||
<ConnectedRouter history={history}>
|
||||
<PageFrame>
|
||||
<React.Suspense fallback={<div />}>
|
||||
<AppRoutes />
|
||||
</React.Suspense>
|
||||
</PageFrame>
|
||||
</ConnectedRouter>
|
||||
</Provider>,
|
||||
),
|
||||
history,
|
||||
})
|
||||
|
||||
export const travelToSafe = (store: Store, address: string): React$Component<{}> => {
|
||||
history.push(`${SAFELIST_ADDRESS}/${address}`)
|
||||
export const renderSafeView = (store: Store<GlobalState>, address: string) => {
|
||||
const app = renderApp(store)
|
||||
|
||||
return createDom(store)
|
||||
}
|
||||
|
||||
export const travelToTokens = (store: Store, address: string): React$Component<{}> => {
|
||||
const url = `${SAFELIST_ADDRESS}/${address}${SETTINS_ADDRESS}`
|
||||
const url = `${SAFELIST_ADDRESS}/${address}`
|
||||
history.push(url)
|
||||
|
||||
return createDom(store)
|
||||
return app
|
||||
}
|
||||
|
||||
const INTERVAL = 500
|
||||
|
|
|
@ -1,28 +1,7 @@
|
|||
// @flow
|
||||
import * as TestUtils from 'react-dom/test-utils'
|
||||
import { travelToTokens } from '~/test/builder/safe.dom.utils'
|
||||
import { sleep } from '~/utils/timer'
|
||||
import { type Token } from '~/logic/tokens/store/model/token'
|
||||
|
||||
export const enableFirstToken = async (store: Store, safeAddress: string) => {
|
||||
const TokensDom = await travelToTokens(store, safeAddress)
|
||||
await sleep(400)
|
||||
|
||||
// WHEN
|
||||
const inputs = TestUtils.scryRenderedDOMComponentsWithTag(TokensDom, 'input')
|
||||
|
||||
const ethTokenInput = inputs[2]
|
||||
expect(ethTokenInput.hasAttribute('disabled')).toBe(true)
|
||||
const firstTokenInput = inputs[0]
|
||||
expect(firstTokenInput.hasAttribute('disabled')).toBe(false)
|
||||
TestUtils.Simulate.change(firstTokenInput, { target: { checked: 'true' } })
|
||||
}
|
||||
|
||||
export const testToken = (token: Token | typeof undefined, symbol: string, status: boolean, funds?: string) => {
|
||||
export const testToken = (token: Token | typeof undefined, symbol: string) => {
|
||||
if (!token) throw new Error()
|
||||
expect(token.get('symbol')).toBe(symbol)
|
||||
expect(token.get('status')).toBe(status)
|
||||
if (funds) {
|
||||
expect(token.get('funds')).toBe(funds)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
pragma solidity ^0.5.2;
|
||||
|
||||
import "@gnosis.pm/util-contracts/contracts/GnosisStandardToken.sol";
|
||||
|
||||
contract TokenOMG is GnosisStandardToken {
|
||||
string public constant symbol = "OMG";
|
||||
string public constant name = "Omisego Token";
|
||||
uint8 public constant decimals = 18;
|
||||
|
||||
constructor(
|
||||
uint amount
|
||||
)
|
||||
public
|
||||
{
|
||||
balances[msg.sender] = amount;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
pragma solidity ^0.5.2;
|
||||
|
||||
import "@gnosis.pm/util-contracts/contracts/GnosisStandardToken.sol";
|
||||
|
||||
contract TokenRDN is GnosisStandardToken {
|
||||
string public constant symbol = "RDN";
|
||||
string public constant name = "Raiden Token";
|
||||
uint8 public constant decimals = 18;
|
||||
|
||||
constructor(
|
||||
uint amount
|
||||
)
|
||||
public
|
||||
{
|
||||
balances[msg.sender] = amount;
|
||||
}
|
||||
}
|
|
@ -1,7 +1,7 @@
|
|||
// @flow
|
||||
import * as React from 'react'
|
||||
import { type Store } from 'redux'
|
||||
import { render, fireEvent, cleanup } from 'react-testing-library'
|
||||
import { render, fireEvent, cleanup } from '@testing-library/react'
|
||||
import { Provider } from 'react-redux'
|
||||
import { ConnectedRouter } from 'connected-react-router'
|
||||
import { ADD_OWNER_BUTTON } from '~/routes/open/components/SafeOwnersForm'
|
||||
|
@ -16,7 +16,7 @@ import { whenSafeDeployed } from './builder/safe.dom.utils'
|
|||
|
||||
afterEach(cleanup)
|
||||
|
||||
// https://github.com/testing-library/react-testing-library/issues/281
|
||||
// https://github.com/testing-library/@testing-library/react/issues/281
|
||||
const originalError = console.error
|
||||
beforeAll(() => {
|
||||
console.error = (...args) => {
|
||||
|
@ -81,10 +81,12 @@ const deploySafe = async (createSafeForm: any, threshold: number, numOwners: num
|
|||
fireEvent.change(ownerAddressInput, { target: { value: accounts[i] } })
|
||||
}
|
||||
fireEvent.submit(form)
|
||||
await sleep(400)
|
||||
await sleep(600)
|
||||
|
||||
// Fill Threshold
|
||||
const thresholdSelect = createSafeForm.getByRole('button')
|
||||
// The test is fragile here, MUI select btn is hard to find
|
||||
const thresholdSelect = createSafeForm.getAllByRole('button')[1]
|
||||
|
||||
fireEvent.click(thresholdSelect)
|
||||
const thresholdOptions = createSafeForm.getAllByRole('option')
|
||||
fireEvent.click(thresholdOptions[numOwners - 1])
|
||||
|
@ -101,7 +103,7 @@ const deploySafe = async (createSafeForm: any, threshold: number, numOwners: num
|
|||
}
|
||||
|
||||
const aDeployedSafe = async (specificStore: Store<GlobalState>, threshold?: number = 1, numOwners?: number = 1) => {
|
||||
const safe: React$Component<{}> = await renderOpenSafeForm(specificStore)
|
||||
const safe: React.Component<{}> = await renderOpenSafeForm(specificStore)
|
||||
const safeAddress = await deploySafe(safe, threshold, numOwners)
|
||||
|
||||
return safeAddress
|
||||
|
|
|
@ -0,0 +1,122 @@
|
|||
// @flow
|
||||
import { fireEvent, cleanup } from '@testing-library/react'
|
||||
import { List } from 'immutable'
|
||||
import { aNewStore } from '~/store'
|
||||
import { aMinedSafe } from '~/test/builder/safe.redux.builder'
|
||||
import { sendTokenTo, sendEtherTo } from '~/test/utils/tokenMovements'
|
||||
import { renderSafeView } from '~/test/builder/safe.dom.utils'
|
||||
import { getWeb3, getBalanceInEtherOf } from '~/logic/wallets/getWeb3'
|
||||
import { dispatchAddTokenToList } from '~/test/utils/transactions/moveTokens.helper'
|
||||
import { sleep } from '~/utils/timer'
|
||||
import TokenBalanceRecord from '~/routes/safe/store/models/tokenBalance'
|
||||
import { calculateBalanceOf } from '~/routes/safe/store/actions/fetchTokenBalances'
|
||||
import updateActiveTokens from '~/routes/safe/store/actions/updateActiveTokens'
|
||||
import 'jest-dom/extend-expect'
|
||||
import updateSafe from '~/routes/safe/store/actions/updateSafe'
|
||||
import { BALANCE_ROW_TEST_ID } from '~/routes/safe/components/Balances'
|
||||
|
||||
afterEach(cleanup)
|
||||
|
||||
describe('DOM > Feature > 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)
|
||||
accounts = await getWeb3().eth.getAccounts()
|
||||
})
|
||||
|
||||
it('Sends ETH with threshold = 1', 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)
|
||||
|
||||
// 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)
|
||||
|
||||
// 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,
|
||||
)
|
||||
})
|
||||
|
||||
it('Sends Tokens with threshold = 1', async () => {
|
||||
// GIVEN
|
||||
const tokensAmount = '100'
|
||||
const tokenReceiver = accounts[1]
|
||||
const tokenAddress = await sendTokenTo(safeAddress, tokensAmount)
|
||||
await dispatchAddTokenToList(store, tokenAddress)
|
||||
|
||||
// WHEN
|
||||
const SafeDom = await renderSafeView(store, safeAddress)
|
||||
await sleep(1300)
|
||||
|
||||
// Activate token
|
||||
const safeTokenBalance = await calculateBalanceOf(tokenAddress, safeAddress, 18)
|
||||
expect(safeTokenBalance).toBe(tokensAmount)
|
||||
|
||||
const balanceAsRecord = TokenBalanceRecord({
|
||||
address: tokenAddress,
|
||||
balance: safeTokenBalance,
|
||||
})
|
||||
store.dispatch(updateActiveTokens(safeAddress, List([tokenAddress])))
|
||||
store.dispatch(updateSafe({ address: safeAddress, balances: List([balanceAsRecord]) }))
|
||||
await sleep(1000)
|
||||
|
||||
// Open send funds modal
|
||||
const balanceRows = SafeDom.getAllByTestId(BALANCE_ROW_TEST_ID)
|
||||
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)
|
||||
|
||||
// THEN
|
||||
const safeFunds = await calculateBalanceOf(tokenAddress, safeAddress, 18)
|
||||
expect(Number(safeFunds)).toBe(0)
|
||||
const receiverFunds = await calculateBalanceOf(tokenAddress, tokenReceiver, 18)
|
||||
expect(receiverFunds).toBe(tokensAmount)
|
||||
})
|
||||
})
|
|
@ -2,7 +2,7 @@
|
|||
import * as React from 'react'
|
||||
import { type Store } from 'redux'
|
||||
import { Provider } from 'react-redux'
|
||||
import { render, fireEvent, cleanup } from 'react-testing-library'
|
||||
import { render, fireEvent, cleanup } from '@testing-library/react'
|
||||
import { ConnectedRouter } from 'connected-react-router'
|
||||
import Load from '~/routes/load/container/Load'
|
||||
import { aNewStore, history, type GlobalState } from '~/store'
|
||||
|
@ -15,7 +15,7 @@ import { whenSafeDeployed } from './builder/safe.dom.utils'
|
|||
|
||||
afterEach(cleanup)
|
||||
|
||||
// https://github.com/testing-library/react-testing-library/issues/281
|
||||
// https://github.com/testing-library/@testing-library/react/issues/281
|
||||
const originalError = console.error
|
||||
beforeAll(() => {
|
||||
console.error = (...args) => {
|
||||
|
@ -57,7 +57,6 @@ describe('DOM > Feature > LOAD a safe', () => {
|
|||
fireEvent.change(safeNameInput, { target: { value: 'A Safe To Load' } })
|
||||
fireEvent.change(safeAddressInput, { target: { value: address } })
|
||||
await sleep(400)
|
||||
|
||||
// Click next
|
||||
fireEvent.submit(form)
|
||||
await sleep(400)
|
||||
|
|
|
@ -1,74 +0,0 @@
|
|||
// @flow
|
||||
|
||||
import TestUtils from 'react-dom/test-utils'
|
||||
import * as fetchBalancesAction from '~/logic/tokens/store/actions/fetchTokens'
|
||||
import { aNewStore } from '~/store'
|
||||
import { aMinedSafe } from '~/test/builder/safe.redux.builder'
|
||||
import { addTknTo, getFirstTokenContract } from '~/test/utils/tokenMovements'
|
||||
import { EXPAND_BALANCE_INDEX, travelToSafe } from '~/test/builder/safe.dom.utils'
|
||||
import { getWeb3 } from '~/logic/wallets/getWeb3'
|
||||
import { sendMoveTokensForm, dispatchTknBalance } from '~/test/utils/transactions/moveTokens.helper'
|
||||
import { sleep } from '~/utils/timer'
|
||||
|
||||
describe('DOM > Feature > SAFE ERC20 TOKENS', () => {
|
||||
let store
|
||||
let safeAddress: string
|
||||
let accounts
|
||||
beforeEach(async () => {
|
||||
store = aNewStore()
|
||||
safeAddress = await aMinedSafe(store)
|
||||
accounts = await getWeb3().eth.getAccounts()
|
||||
})
|
||||
|
||||
it('sends ERC20 tokens', async () => {
|
||||
// GIVEN
|
||||
const numTokens = '100'
|
||||
const tokenAddress = await addTknTo(safeAddress, numTokens)
|
||||
|
||||
await dispatchTknBalance(store, tokenAddress, safeAddress)
|
||||
// const StandardToken = await fetchBalancesAction.getStandardTokenContract()
|
||||
// const myToken = await StandardToken.at(tokenAddress)
|
||||
// console.log(await myToken.allowance(safeAddress, accounts[2]))
|
||||
// console.log(await myToken.balanceOf(safeAddress))
|
||||
|
||||
// WHEN
|
||||
const SafeDom = await travelToSafe(store, safeAddress)
|
||||
await sleep(800)
|
||||
// $FlowFixMe
|
||||
const buttons = TestUtils.scryRenderedDOMComponentsWithTag(SafeDom, 'button')
|
||||
const expandBalance = buttons[EXPAND_BALANCE_INDEX]
|
||||
const receiver = accounts[2]
|
||||
await sendMoveTokensForm(SafeDom, expandBalance, 20, accounts[2])
|
||||
|
||||
// THEN
|
||||
const safeFunds = await fetchBalancesAction.calculateBalanceOf(tokenAddress, safeAddress, 18)
|
||||
expect(Number(safeFunds)).toBe(80)
|
||||
const receiverFunds = await fetchBalancesAction.calculateBalanceOf(tokenAddress, receiver, 18)
|
||||
expect(Number(receiverFunds)).toBe(20)
|
||||
|
||||
const token = await getFirstTokenContract(getWeb3(), accounts[0])
|
||||
const nativeSafeFunds = await token.balanceOf(safeAddress)
|
||||
expect(Number(nativeSafeFunds.valueOf())).toEqual(80 * 10 ** 18)
|
||||
})
|
||||
|
||||
it('disables send token button when balance is 0', async () => {
|
||||
// GIVEN
|
||||
const token = await getFirstTokenContract(getWeb3(), accounts[0])
|
||||
await dispatchTknBalance(store, token.address, safeAddress)
|
||||
|
||||
// WHEN
|
||||
const SafeDom = travelToSafe(store, safeAddress)
|
||||
|
||||
// $FlowFixMe
|
||||
const buttons = TestUtils.scryRenderedDOMComponentsWithTag(SafeDom, 'button')
|
||||
const expandBalance = buttons[EXPAND_BALANCE_INDEX]
|
||||
|
||||
TestUtils.Simulate.click(expandBalance)
|
||||
await sleep(800)
|
||||
|
||||
// $FlowFixMe
|
||||
const balanceButtons = TestUtils.scryRenderedDOMComponentsWithTag(SafeDom, 'button')
|
||||
const tokenButton = balanceButtons[EXPAND_BALANCE_INDEX + 1] // expand button, and the next one is for sending
|
||||
expect(tokenButton.hasAttribute('disabled')).toBe(true)
|
||||
})
|
||||
})
|
|
@ -1,147 +1,17 @@
|
|||
// @flow
|
||||
import TestUtils from 'react-dom/test-utils'
|
||||
import { List } from 'immutable'
|
||||
import Transaction from '~/routes/safe/components/Transactions/Transaction'
|
||||
import {
|
||||
listTxsClickingOn,
|
||||
LIST_TXS_INDEX,
|
||||
ADD_OWNERS_INDEX,
|
||||
EXPAND_OWNERS_INDEX,
|
||||
EDIT_THRESHOLD_INDEX,
|
||||
refreshTransactions,
|
||||
EXPAND_BALANCE_INDEX,
|
||||
} from '~/test/builder/safe.dom.utils'
|
||||
import { renderSafeInDom, type DomSafe } from '~/test/builder/safe.dom.builder'
|
||||
import {
|
||||
sendMoveFundsForm,
|
||||
checkMinedMoveFundsTx,
|
||||
checkPendingMoveFundsTx,
|
||||
} from '~/test/utils/transactions/moveFunds.helper'
|
||||
import {
|
||||
sendAddOwnerForm,
|
||||
checkMinedAddOwnerTx,
|
||||
checkPendingAddOwnerTx,
|
||||
} from '~/test/utils/transactions/addOwner.helper'
|
||||
import {
|
||||
sendRemoveOwnerForm,
|
||||
checkMinedRemoveOwnerTx,
|
||||
checkPendingRemoveOwnerTx,
|
||||
} from '~/test/utils/transactions/removeOwner.helper'
|
||||
import {
|
||||
checkMinedThresholdTx,
|
||||
sendChangeThresholdForm,
|
||||
checkThresholdOf,
|
||||
} from '~/test/utils/transactions/threshold.helper'
|
||||
import { checkBalanceOf } from '~/test/utils/tokenMovements'
|
||||
import { sleep } from '~/utils/timer'
|
||||
import { processTransaction } from '~/logic/safe/safeFrontendOperations'
|
||||
|
||||
// TBD
|
||||
|
||||
describe('DOM > Feature > SAFE MULTISIG Transactions', () => {
|
||||
let domSafe: DomSafe
|
||||
it.only('mines correctly all multisig txs in a 1 owner & 1 threshold safe', async () => {
|
||||
// GIVEN one safe with 1 owner and 1 threshold
|
||||
const owners = 1
|
||||
const threshold = 1
|
||||
domSafe = await renderSafeInDom(owners, threshold)
|
||||
const {
|
||||
address, safe: SafeDom, safeButtons, accounts, store,
|
||||
} = domSafe
|
||||
|
||||
// WHEN
|
||||
await sendMoveFundsForm(SafeDom, safeButtons[EXPAND_BALANCE_INDEX], '0.01', accounts[1])
|
||||
await sendAddOwnerForm(SafeDom, safeButtons[ADD_OWNERS_INDEX], 'Adol Metamask 2', accounts[1])
|
||||
await sleep(1200)
|
||||
await sendChangeThresholdForm(SafeDom, safeButtons[EDIT_THRESHOLD_INDEX], '2')
|
||||
|
||||
// THEN
|
||||
await listTxsClickingOn(store, safeButtons[LIST_TXS_INDEX], address)
|
||||
const transactions = TestUtils.scryRenderedComponentsWithType(SafeDom, Transaction)
|
||||
|
||||
checkMinedMoveFundsTx(transactions[0], 'Send 0.01 ETH to')
|
||||
checkMinedAddOwnerTx(transactions[1], 'Add Owner Adol Metamask 2')
|
||||
checkMinedThresholdTx(transactions[2], "Change Safe's threshold")
|
||||
})
|
||||
|
||||
it.only('mines withdraw process correctly all multisig txs in a 2 owner & 2 threshold safe', async () => {
|
||||
// GIVEN reusing the state from previous test
|
||||
const {
|
||||
address, safe: SafeDom, safeButtons, accounts, store,
|
||||
} = domSafe
|
||||
|
||||
// WHEN
|
||||
await sendMoveFundsForm(SafeDom, safeButtons[EXPAND_BALANCE_INDEX], '0.01', accounts[1])
|
||||
const increaseThreshold = true
|
||||
await sendAddOwnerForm(SafeDom, safeButtons[ADD_OWNERS_INDEX], 'Adol Metamask 3', accounts[2], increaseThreshold)
|
||||
|
||||
// THEN
|
||||
await listTxsClickingOn(store, safeButtons[LIST_TXS_INDEX], address)
|
||||
const transactions = TestUtils.scryRenderedComponentsWithType(SafeDom, Transaction)
|
||||
|
||||
const statusses = ['Adol 1 Eth Account [Confirmed]']
|
||||
await checkPendingMoveFundsTx(transactions[3], 2, 'Send 0.01 ETH to', statusses)
|
||||
await checkPendingAddOwnerTx(transactions[4], 2, 'Add Owner Adol Metamask 3', statusses)
|
||||
await checkBalanceOf(address, '0.09')
|
||||
})
|
||||
|
||||
it.only('approves and executes pending transactions', async () => {
|
||||
// GIVEN reusing the state from previous test
|
||||
const {
|
||||
address, safe: SafeDom, safeButtons, accounts, store,
|
||||
} = domSafe
|
||||
|
||||
let transactions = TestUtils.scryRenderedComponentsWithType(SafeDom, Transaction)
|
||||
expect(transactions.length).toBe(5)
|
||||
await checkThresholdOf(address, 2)
|
||||
|
||||
// WHEN... processing pending TXs
|
||||
await processTransaction(address, transactions[3].props.transaction, 1, accounts[1], 2, List([accounts[0]]))
|
||||
await processTransaction(address, transactions[4].props.transaction, 1, accounts[1], 2, List([accounts[0]]))
|
||||
await refreshTransactions(store, address)
|
||||
|
||||
// THEN
|
||||
checkMinedMoveFundsTx(transactions[3], 'Send 0.01 ETH to')
|
||||
await checkBalanceOf(address, '0.08')
|
||||
checkMinedAddOwnerTx(transactions[4], 'Add Owner Adol Metamask 3')
|
||||
await checkThresholdOf(address, 3)
|
||||
|
||||
// WHEN... reducing threshold
|
||||
await sendRemoveOwnerForm(SafeDom, safeButtons[EXPAND_OWNERS_INDEX])
|
||||
|
||||
// THEN
|
||||
await listTxsClickingOn(store, safeButtons[LIST_TXS_INDEX], address)
|
||||
transactions = TestUtils.scryRenderedComponentsWithType(SafeDom, Transaction)
|
||||
expect(transactions.length).toBe(6)
|
||||
let statusses = ['Adol 1 Eth Account [Confirmed]']
|
||||
await checkPendingRemoveOwnerTx(transactions[5], 3, 'Remove Owner Adol Metamask 3', statusses)
|
||||
|
||||
await processTransaction(address, transactions[5].props.transaction, 1, accounts[2], 3, List([accounts[0]]))
|
||||
await refreshTransactions(store, address)
|
||||
|
||||
transactions = TestUtils.scryRenderedComponentsWithType(SafeDom, Transaction)
|
||||
statusses = ['Adol Metamask 3 [Confirmed]', 'Adol 1 Eth Account [Confirmed]']
|
||||
await checkPendingRemoveOwnerTx(transactions[5], 3, 'Remove Owner Adol Metamask 3', statusses)
|
||||
await checkThresholdOf(address, 3)
|
||||
await processTransaction(
|
||||
address,
|
||||
transactions[5].props.transaction,
|
||||
2,
|
||||
accounts[1],
|
||||
3,
|
||||
List([accounts[0], accounts[2]]),
|
||||
)
|
||||
|
||||
await refreshTransactions(store, address)
|
||||
await checkThresholdOf(address, 2)
|
||||
transactions = TestUtils.scryRenderedComponentsWithType(SafeDom, Transaction)
|
||||
await checkMinedRemoveOwnerTx(transactions[5], 'Remove Owner')
|
||||
|
||||
// WHEN... changing threshold
|
||||
await sendChangeThresholdForm(SafeDom, safeButtons[EDIT_THRESHOLD_INDEX], '1')
|
||||
await listTxsClickingOn(store, safeButtons[LIST_TXS_INDEX], address)
|
||||
|
||||
// THEN
|
||||
transactions = TestUtils.scryRenderedComponentsWithType(SafeDom, Transaction)
|
||||
await processTransaction(address, transactions[6].props.transaction, 1, accounts[1], 2, List([accounts[0]]))
|
||||
await checkThresholdOf(address, 1)
|
||||
})
|
||||
})
|
||||
|
|
|
@ -1,72 +0,0 @@
|
|||
// @flow
|
||||
import { Map } from 'immutable'
|
||||
import * as fetchTokensAction from '~/logic/tokens/store/actions/fetchTokens'
|
||||
import { aNewStore } from '~/store'
|
||||
import { aMinedSafe } from '~/test/builder/safe.redux.builder'
|
||||
import { type Token } from '~/logic/tokens/store/model/token'
|
||||
import { TOKEN_REDUCER_ID } from '~/logic/tokens/store/reducer/tokens'
|
||||
import { addEtherTo, addTknTo } from '~/test/utils/tokenMovements'
|
||||
import { dispatchTknBalance } from '~/test/utils/transactions/moveTokens.helper'
|
||||
import { ETH_ADDRESS } from '~/logic/tokens/utils/tokenHelpers'
|
||||
|
||||
describe('Safe - redux balance property', () => {
|
||||
let store
|
||||
let address: string
|
||||
beforeEach(async () => {
|
||||
store = aNewStore()
|
||||
address = await aMinedSafe(store)
|
||||
})
|
||||
|
||||
it('reducer should return 0 to just deployed safe', async () => {
|
||||
// WHEN
|
||||
await store.dispatch(fetchTokensAction.fetchTokens(address))
|
||||
|
||||
// THEN
|
||||
const tokens: Map<string, Map<string, Token>> | typeof undefined = store.getState()[TOKEN_REDUCER_ID]
|
||||
if (!tokens) throw new Error()
|
||||
|
||||
const safeBalances: Map<string, Token> | typeof undefined = tokens.get(address)
|
||||
if (!safeBalances) throw new Error('No tokens available, probably failed to fetch')
|
||||
expect(safeBalances.size).toBe(11)
|
||||
|
||||
// safeBalances.forEach((token: string) => {
|
||||
// const record = safeBalances.get(token)
|
||||
// if (!record) throw new Error()
|
||||
// expect(record.get('funds')).toBe('0')
|
||||
// })
|
||||
})
|
||||
|
||||
it('reducer should return 0.03456 ETH as funds to safe with 0.03456 ETH', async () => {
|
||||
// WHEN
|
||||
await addEtherTo(address, '0.03456')
|
||||
await store.dispatch(fetchTokensAction.fetchTokens(address))
|
||||
|
||||
// THEN
|
||||
const tokens: Map<string, Map<string, Token>> | typeof undefined = store.getState()[TOKEN_REDUCER_ID]
|
||||
if (!tokens) throw new Error()
|
||||
|
||||
const safeBalances: Map<string, Token> | typeof undefined = tokens.get(address)
|
||||
if (!safeBalances) throw new Error()
|
||||
expect(safeBalances.size).toBe(11)
|
||||
|
||||
const ethBalance = safeBalances.get(ETH_ADDRESS)
|
||||
if (!ethBalance) throw new Error()
|
||||
expect(ethBalance.get('funds')).toBe('0.03456')
|
||||
})
|
||||
|
||||
it('reducer should return 100 TKN when safe has 100 TKN', async () => {
|
||||
// GIVEN
|
||||
const numTokens = '100'
|
||||
const tokenAddress = await addTknTo(address, numTokens)
|
||||
|
||||
// WHEN
|
||||
await dispatchTknBalance(store, tokenAddress, address)
|
||||
|
||||
// THEN
|
||||
const safeBalances = store.getState()[TOKEN_REDUCER_ID].get(address)
|
||||
expect(safeBalances.size).toBe(1)
|
||||
|
||||
const tknBalance = safeBalances.get('TKN')
|
||||
expect(tknBalance.get('funds')).toBe(String(numTokens))
|
||||
})
|
||||
})
|
|
@ -1,112 +0,0 @@
|
|||
// @flow
|
||||
import { Map, List } from 'immutable'
|
||||
import { type Safe } from '~/routes/safe/store/models/safe'
|
||||
import { aNewStore } from '~/store'
|
||||
import { aMinedSafe } from '~/test/builder/safe.redux.builder'
|
||||
import updateSafe from '~/routes/safe/store/actions/updateSafe'
|
||||
import { loadSafe } from '~/routes/load/container/Load'
|
||||
import { safesMapSelector } from '~/routes/safeList/store/selectors'
|
||||
import { makeOwner, type Owner } from '~/routes/safe/store/models/owner'
|
||||
import { getWeb3 } from '~/logic/wallets/getWeb3'
|
||||
import { safesInitialState } from '~/routes/safe/store/reducer/safe'
|
||||
import { setOwners, OWNERS_KEY } from '~/utils/storage'
|
||||
|
||||
describe('Safe - redux load safe', () => {
|
||||
let store
|
||||
let address: string
|
||||
let accounts
|
||||
beforeEach(async () => {
|
||||
store = aNewStore()
|
||||
address = await aMinedSafe(store)
|
||||
localStorage.clear()
|
||||
accounts = await getWeb3().eth.getAccounts()
|
||||
})
|
||||
|
||||
it('if safe is not present, store and persist it with default names', async () => {
|
||||
const safeName = 'Loaded Safe'
|
||||
const safeAddress = address
|
||||
const updateSafeFn: any = (...args) => store.dispatch(updateSafe(...args))
|
||||
|
||||
await loadSafe(safeName, safeAddress, updateSafeFn)
|
||||
|
||||
const safes: Map<string, Safe> = safesMapSelector(store.getState())
|
||||
expect(safes.size).toBe(1)
|
||||
if (!safes) throw new Error()
|
||||
const safe = safes.get(safeAddress)
|
||||
if (!safe) throw new Error()
|
||||
|
||||
expect(safe.get('name')).toBe(safeName)
|
||||
expect(safe.get('threshold')).toBe(1)
|
||||
expect(safe.get('address')).toBe(safeAddress)
|
||||
expect(safe.get('owners')).toEqual(List([makeOwner({ name: 'UNKNOWN', address: accounts[0] })]))
|
||||
|
||||
expect(await safesInitialState()).toEqual(safes)
|
||||
})
|
||||
|
||||
it('if safe is not present but owners, store and persist it with stored names', async () => {
|
||||
const safeName = 'Loaded Safe'
|
||||
const safeAddress = address
|
||||
const ownerName = 'Foo Bar Restores'
|
||||
const updateSafeFn: any = (...args) => store.dispatch(updateSafe(...args))
|
||||
const owner: Owner = makeOwner({ name: ownerName, address: accounts[0] })
|
||||
setOwners(safeAddress, List([owner]))
|
||||
|
||||
await loadSafe(safeName, safeAddress, updateSafeFn)
|
||||
|
||||
const safes: Map<string, Safe> = safesMapSelector(store.getState())
|
||||
expect(safes.size).toBe(1)
|
||||
if (!safes) throw new Error()
|
||||
const safe = safes.get(safeAddress)
|
||||
if (!safe) throw new Error()
|
||||
|
||||
expect(safe.get('name')).toBe(safeName)
|
||||
expect(safe.get('threshold')).toBe(1)
|
||||
expect(safe.get('address')).toBe(safeAddress)
|
||||
expect(safe.get('owners')).toEqual(List([makeOwner({ name: ownerName, address: accounts[0] })]))
|
||||
|
||||
expect(await safesInitialState()).toEqual(safes)
|
||||
})
|
||||
|
||||
it('if safe is present but no owners, store and persist it with default names', async () => {
|
||||
const safeAddress = await aMinedSafe(store)
|
||||
localStorage.removeItem(`${OWNERS_KEY}-${safeAddress}`)
|
||||
|
||||
const safeName = 'Loaded Safe'
|
||||
const updateSafeFn: any = (...args) => store.dispatch(updateSafe(...args))
|
||||
await loadSafe(safeName, safeAddress, updateSafeFn)
|
||||
|
||||
const safes: Map<string, Safe> = safesMapSelector(store.getState())
|
||||
expect(safes.size).toBe(2)
|
||||
if (!safes) throw new Error()
|
||||
const safe = safes.get(safeAddress)
|
||||
if (!safe) throw new Error()
|
||||
|
||||
expect(safe.get('name')).toBe(safeName)
|
||||
expect(safe.get('threshold')).toBe(1)
|
||||
expect(safe.get('address')).toBe(safeAddress)
|
||||
expect(safe.get('owners')).toEqual(List([makeOwner({ name: 'UNKNOWN', address: accounts[0] })]))
|
||||
|
||||
expect(await safesInitialState()).toEqual(safes)
|
||||
})
|
||||
|
||||
it('if safe is present but owners, store and persist it with stored names', async () => {
|
||||
const safeAddress = await aMinedSafe(store)
|
||||
|
||||
const safeName = 'Loaded Safe'
|
||||
const updateSafeFn: any = (...args) => store.dispatch(updateSafe(...args))
|
||||
await loadSafe(safeName, safeAddress, updateSafeFn)
|
||||
|
||||
const safes: Map<string, Safe> = safesMapSelector(store.getState())
|
||||
expect(safes.size).toBe(2)
|
||||
if (!safes) throw new Error()
|
||||
const safe = safes.get(safeAddress)
|
||||
if (!safe) throw new Error()
|
||||
|
||||
expect(safe.get('name')).toBe(safeName)
|
||||
expect(safe.get('threshold')).toBe(1)
|
||||
expect(safe.get('address')).toBe(safeAddress)
|
||||
expect(safe.get('owners')).toEqual(List([makeOwner({ name: 'Adol 1 Eth Account', address: accounts[0] })]))
|
||||
|
||||
expect(await safesInitialState()).toEqual(safes)
|
||||
})
|
||||
})
|
|
@ -1,237 +0,0 @@
|
|||
// @flow
|
||||
import { List } from 'immutable'
|
||||
import { aNewStore } from '~/store'
|
||||
import { getWeb3 } from '~/logic/wallets/getWeb3'
|
||||
import { confirmationsTransactionSelector, safeTransactionsSelector } from '~/routes/safe/store/selectors'
|
||||
import fetchTransactions from '~/routes/safe/store/actions/fetchTransactions'
|
||||
import { type Safe } from '~/routes/safe/store/models/safe'
|
||||
import { getGnosisSafeInstanceAt } from '~/logic/contracts/safeContracts'
|
||||
import { aMinedSafe } from '~/test/builder/safe.redux.builder'
|
||||
import { NAME_PARAM, OWNER_ADDRESS_PARAM, INCREASE_PARAM } from '~/routes/safe/components/AddOwner/AddOwnerForm'
|
||||
import { addOwner } from '~/routes/safe/components/AddOwner/index'
|
||||
import fetchSafe from '~/routes/safe/store/actions/fetchSafe'
|
||||
import { removeOwner, shouldDecrease, initialValuesFrom } from '~/routes/safe/components/RemoveOwner'
|
||||
import { DECREASE_PARAM } from '~/routes/safe/components/RemoveOwner/RemoveOwnerForm'
|
||||
import { getSafeFrom } from '~/test/utils/safeHelper'
|
||||
import { getOwnerNameBy, getOwnerAddressBy } from '~/routes/open/components/fields'
|
||||
import { processTransaction } from '~/logic/safe/safeFrontendOperations'
|
||||
import { allowedRemoveSenderInTxHistoryService } from '~/config'
|
||||
import { calculateValuesAfterRemoving } from '~/routes/open/components/SafeOwnersForm'
|
||||
|
||||
describe('React DOM TESTS > Add and remove owners', () => {
|
||||
const processOwnerModification = async (store, safeAddress, executor, threshold, alreadyConfirmed) => {
|
||||
const reduxTransactions = safeTransactionsSelector(store.getState(), { safeAddress })
|
||||
const tx = reduxTransactions.get(0)
|
||||
if (!tx) throw new Error()
|
||||
|
||||
const confirmed = confirmationsTransactionSelector(store.getState(), { transaction: tx })
|
||||
const data = tx.get('data')
|
||||
expect(data).not.toBe(null)
|
||||
expect(data).not.toBe(undefined)
|
||||
expect(data).not.toBe('')
|
||||
|
||||
return processTransaction(safeAddress, tx, confirmed, executor, threshold, alreadyConfirmed)
|
||||
}
|
||||
|
||||
const assureThresholdIs = async (gnosisSafe, threshold: number) => {
|
||||
const safeThreshold = await gnosisSafe.getThreshold()
|
||||
expect(Number(safeThreshold)).toEqual(threshold)
|
||||
}
|
||||
|
||||
const assureOwnersAre = async (gnosisSafe, ...owners) => {
|
||||
const safeOwners = await gnosisSafe.getOwners()
|
||||
expect(safeOwners.length).toEqual(owners.length)
|
||||
for (let i = 0; i < owners.length; i += 1) {
|
||||
expect(safeOwners[i]).toBe(owners[i])
|
||||
}
|
||||
}
|
||||
|
||||
const getAddressesFrom = (safe: Safe) => safe.get('owners').map(owner => owner.get('address'))
|
||||
|
||||
it.only('creates initialValues removing last owner', () => {
|
||||
const numOwners = 3
|
||||
const values = {
|
||||
moe: 'Bart',
|
||||
[getOwnerNameBy(0)]: 'Foo',
|
||||
[getOwnerAddressBy(0)]: '0x1',
|
||||
[getOwnerNameBy(1)]: 'Bar',
|
||||
[getOwnerAddressBy(1)]: '0x2',
|
||||
[getOwnerNameBy(2)]: 'Baz',
|
||||
[getOwnerAddressBy(2)]: '0x3',
|
||||
}
|
||||
|
||||
const indexToRemove = 2
|
||||
const initialValues = calculateValuesAfterRemoving(indexToRemove, numOwners, values)
|
||||
|
||||
expect(initialValues).toEqual({
|
||||
moe: 'Bart',
|
||||
[getOwnerNameBy(0)]: 'Foo',
|
||||
[getOwnerAddressBy(0)]: '0x1',
|
||||
[getOwnerNameBy(1)]: 'Bar',
|
||||
[getOwnerAddressBy(1)]: '0x2',
|
||||
})
|
||||
})
|
||||
|
||||
it.only('creates initialValues removing middle owner', () => {
|
||||
const numOwners = 3
|
||||
const values = {
|
||||
moe: 'Bart',
|
||||
[getOwnerNameBy(0)]: 'Foo',
|
||||
[getOwnerAddressBy(0)]: '0x1',
|
||||
[getOwnerNameBy(1)]: 'Bar',
|
||||
[getOwnerAddressBy(1)]: '0x2',
|
||||
[getOwnerNameBy(2)]: 'Baz',
|
||||
[getOwnerAddressBy(2)]: '0x3',
|
||||
}
|
||||
|
||||
const indexToRemove = 1
|
||||
const initialValues = calculateValuesAfterRemoving(indexToRemove, numOwners, values)
|
||||
|
||||
expect(initialValues).toEqual({
|
||||
moe: 'Bart',
|
||||
[getOwnerNameBy(0)]: 'Foo',
|
||||
[getOwnerAddressBy(0)]: '0x1',
|
||||
[getOwnerNameBy(1)]: 'Baz',
|
||||
[getOwnerAddressBy(1)]: '0x3',
|
||||
})
|
||||
})
|
||||
|
||||
it('adds owner without increasing the threshold', async () => {
|
||||
// GIVEN
|
||||
const numOwners = 2
|
||||
const threshold = 1
|
||||
const store = aNewStore()
|
||||
const address = await aMinedSafe(store, numOwners, threshold)
|
||||
const accounts = await getWeb3().eth.getAccounts()
|
||||
const gnosisSafe = await getGnosisSafeInstanceAt(address)
|
||||
|
||||
const values = {
|
||||
[NAME_PARAM]: 'Adol 3 Metamask',
|
||||
[OWNER_ADDRESS_PARAM]: accounts[2],
|
||||
[INCREASE_PARAM]: false,
|
||||
}
|
||||
|
||||
// WHEN
|
||||
let safe = getSafeFrom(store.getState(), address)
|
||||
await addOwner(values, safe, threshold, accounts[0])
|
||||
|
||||
// THEN
|
||||
await assureThresholdIs(gnosisSafe, 1)
|
||||
await assureOwnersAre(gnosisSafe, accounts[2], accounts[0], accounts[1])
|
||||
|
||||
await store.dispatch(fetchSafe(safe.get('address')))
|
||||
safe = getSafeFrom(store.getState(), address)
|
||||
expect(safe.get('owners').count()).toBe(3)
|
||||
await assureOwnersAre(gnosisSafe, ...getAddressesFrom(safe))
|
||||
})
|
||||
|
||||
it('adds owner increasing the threshold', async () => {
|
||||
// GIVEN
|
||||
const numOwners = 2
|
||||
const threshold = 1
|
||||
const store = aNewStore()
|
||||
const address = await aMinedSafe(store, numOwners, threshold)
|
||||
const accounts = await getWeb3().eth.getAccounts()
|
||||
const gnosisSafe = await getGnosisSafeInstanceAt(address)
|
||||
|
||||
const values = {
|
||||
[NAME_PARAM]: 'Adol 3 Metamask',
|
||||
[OWNER_ADDRESS_PARAM]: accounts[2],
|
||||
[INCREASE_PARAM]: true,
|
||||
}
|
||||
|
||||
// WHEN
|
||||
let safe = getSafeFrom(store.getState(), address)
|
||||
await addOwner(values, safe, threshold, accounts[0])
|
||||
|
||||
// THEN
|
||||
await assureThresholdIs(gnosisSafe, 2)
|
||||
await assureOwnersAre(gnosisSafe, accounts[2], accounts[0], accounts[1])
|
||||
|
||||
await store.dispatch(fetchSafe(safe.get('address')))
|
||||
safe = getSafeFrom(store.getState(), address)
|
||||
expect(safe.get('owners').count()).toBe(3)
|
||||
await assureOwnersAre(gnosisSafe, ...getAddressesFrom(safe))
|
||||
})
|
||||
|
||||
it('remove owner decreasing owner automatically', async () => {
|
||||
if (!allowedRemoveSenderInTxHistoryService()) {
|
||||
return
|
||||
}
|
||||
|
||||
const numOwners = 2
|
||||
const threshold = 2
|
||||
const store = aNewStore()
|
||||
const address = await aMinedSafe(store, numOwners, threshold)
|
||||
const accounts = await getWeb3().eth.getAccounts()
|
||||
const gnosisSafe = await getGnosisSafeInstanceAt(address)
|
||||
|
||||
const decrease = shouldDecrease(numOwners, threshold)
|
||||
const values = initialValuesFrom(decrease)
|
||||
expect(values[DECREASE_PARAM]).toBe(true)
|
||||
|
||||
let safe = getSafeFrom(store.getState(), address)
|
||||
await removeOwner(values, safe, threshold, accounts[1], 'Adol Metamask 2', accounts[0])
|
||||
await store.dispatch(fetchTransactions(address))
|
||||
await processOwnerModification(store, address, accounts[1], 2, List([accounts[0]]))
|
||||
|
||||
await assureThresholdIs(gnosisSafe, 1)
|
||||
await assureOwnersAre(gnosisSafe, accounts[0])
|
||||
|
||||
await store.dispatch(fetchSafe(safe.get('address')))
|
||||
safe = getSafeFrom(store.getState(), address)
|
||||
expect(safe.get('owners').count()).toBe(1)
|
||||
await assureOwnersAre(gnosisSafe, ...getAddressesFrom(safe))
|
||||
})
|
||||
|
||||
it('remove owner decreasing threshold', async () => {
|
||||
const numOwners = 3
|
||||
const threshold = 2
|
||||
const store = aNewStore()
|
||||
const address = await aMinedSafe(store, numOwners, threshold)
|
||||
const accounts = await getWeb3().eth.getAccounts()
|
||||
const gnosisSafe = await getGnosisSafeInstanceAt(address)
|
||||
|
||||
const decrease = true
|
||||
const values = initialValuesFrom(decrease)
|
||||
|
||||
let safe = getSafeFrom(store.getState(), address)
|
||||
await removeOwner(values, safe, threshold, accounts[2], 'Adol Metamask 3', accounts[0])
|
||||
await store.dispatch(fetchTransactions(address))
|
||||
await processOwnerModification(store, address, accounts[1], 2, List([accounts[0]]))
|
||||
|
||||
await assureThresholdIs(gnosisSafe, 1)
|
||||
await assureOwnersAre(gnosisSafe, accounts[0], accounts[1])
|
||||
|
||||
await store.dispatch(fetchSafe(safe.get('address')))
|
||||
safe = getSafeFrom(store.getState(), address)
|
||||
expect(safe.get('owners').count()).toBe(2)
|
||||
await assureOwnersAre(gnosisSafe, ...getAddressesFrom(safe))
|
||||
})
|
||||
|
||||
it('remove owner without decreasing threshold', async () => {
|
||||
const numOwners = 3
|
||||
const threshold = 2
|
||||
const store = aNewStore()
|
||||
const address = await aMinedSafe(store, numOwners, threshold)
|
||||
const accounts = await getWeb3().eth.getAccounts()
|
||||
const gnosisSafe = await getGnosisSafeInstanceAt(address)
|
||||
|
||||
const decrease = shouldDecrease(numOwners, threshold)
|
||||
const values = initialValuesFrom(decrease)
|
||||
expect(values[DECREASE_PARAM]).toBe(false)
|
||||
|
||||
let safe = getSafeFrom(store.getState(), address)
|
||||
await removeOwner(values, safe, threshold, accounts[2], 'Adol Metamask 3', accounts[0])
|
||||
await store.dispatch(fetchTransactions(address))
|
||||
await processOwnerModification(store, address, accounts[1], 2, List([accounts[0]]))
|
||||
|
||||
await assureThresholdIs(gnosisSafe, 2)
|
||||
await assureOwnersAre(gnosisSafe, accounts[0], accounts[1])
|
||||
|
||||
await store.dispatch(fetchSafe(safe.get('address')))
|
||||
safe = getSafeFrom(store.getState(), address)
|
||||
expect(safe.get('owners').count()).toBe(2)
|
||||
await assureOwnersAre(gnosisSafe, ...getAddressesFrom(safe))
|
||||
})
|
||||
})
|
|
@ -1,178 +0,0 @@
|
|||
// @flow
|
||||
import { List } from 'immutable'
|
||||
import { createTransaction } from '~/logic/safe/safeFrontendOperations'
|
||||
import { getGnosisSafeInstanceAt } from '~/logic/contracts/safeContracts'
|
||||
import { type Safe } from '~/routes/safe/store/models/safe'
|
||||
import { makeOwner } from '~/routes/safe/store/models/owner'
|
||||
import fetchTransactions from '~/routes/safe/store/actions/fetchTransactions'
|
||||
import { makeConfirmation } from '~/routes/safe/store/models/confirmation'
|
||||
import { aNewStore } from '~/store'
|
||||
import { aMinedSafe } from '~/test/builder/safe.redux.builder'
|
||||
import { getSafeFrom } from '~/test/utils/safeHelper'
|
||||
import { getWeb3 } from '~/logic/wallets/getWeb3'
|
||||
import { safeTransactionsSelector } from '~/routes/safe/store/selectors'
|
||||
import fetchSafe from '~/routes/safe/store/actions/fetchSafe'
|
||||
import { testTransactionFrom, testSizeOfTransactions } from './utils/historyServiceHelper'
|
||||
|
||||
describe('Transactions Suite', () => {
|
||||
let store: Store
|
||||
let safeAddress: string
|
||||
let accounts: string[]
|
||||
beforeAll(async () => {
|
||||
accounts = await getWeb3().eth.getAccounts()
|
||||
})
|
||||
beforeEach(async () => {
|
||||
localStorage.clear()
|
||||
|
||||
store = aNewStore()
|
||||
safeAddress = await aMinedSafe(store)
|
||||
})
|
||||
|
||||
it('retrieves tx info from service having subject available', async () => {
|
||||
let safe: Safe = getSafeFrom(store.getState(), safeAddress)
|
||||
const gnosisSafe = await getGnosisSafeInstanceAt(safeAddress)
|
||||
const firstTxData = gnosisSafe.contract.methods.addOwnerWithThreshold(accounts[1], 2).encodeABI()
|
||||
const executor = accounts[0]
|
||||
const nonce = await gnosisSafe.nonce()
|
||||
const firstTxHash = await createTransaction(
|
||||
safe,
|
||||
'Add Owner Second account',
|
||||
safeAddress,
|
||||
'0',
|
||||
nonce,
|
||||
executor,
|
||||
firstTxData,
|
||||
)
|
||||
await store.dispatch(fetchSafe(safe.get('address')))
|
||||
safe = getSafeFrom(store.getState(), safeAddress)
|
||||
|
||||
const secondTxData = gnosisSafe.contract.methods.addOwnerWithThreshold(accounts[2], 2).encodeABI()
|
||||
const secondTxHash = await createTransaction(
|
||||
safe,
|
||||
'Add Owner Third account',
|
||||
safeAddress,
|
||||
'0',
|
||||
nonce + 100,
|
||||
executor,
|
||||
secondTxData,
|
||||
)
|
||||
await store.dispatch(fetchSafe(safe.get('address')))
|
||||
safe = getSafeFrom(store.getState(), safeAddress)
|
||||
|
||||
// WHEN
|
||||
await store.dispatch(fetchTransactions(safeAddress))
|
||||
let transactions = safeTransactionsSelector(store.getState(), { safeAddress })
|
||||
testSizeOfTransactions(transactions, 2)
|
||||
|
||||
// THEN
|
||||
const firstTxConfirmations = List([
|
||||
makeConfirmation({
|
||||
owner: makeOwner({ address: getWeb3().toChecksumAddress(executor), name: 'Adol 1 Eth Account' }),
|
||||
type: 'execution',
|
||||
hash: firstTxHash,
|
||||
}),
|
||||
])
|
||||
testTransactionFrom(
|
||||
transactions,
|
||||
0,
|
||||
'Add Owner Second account',
|
||||
nonce,
|
||||
0,
|
||||
safeAddress,
|
||||
firstTxData,
|
||||
true,
|
||||
firstTxConfirmations,
|
||||
)
|
||||
|
||||
const secondTxConfirmations = List([
|
||||
makeConfirmation({
|
||||
owner: makeOwner({ address: getWeb3().toChecksumAddress(accounts[0]), name: 'Adol 1 Eth Account' }),
|
||||
type: 'confirmation',
|
||||
hash: secondTxHash,
|
||||
}),
|
||||
])
|
||||
testTransactionFrom(
|
||||
transactions,
|
||||
1,
|
||||
'Add Owner Third account',
|
||||
nonce + 100,
|
||||
0,
|
||||
safeAddress,
|
||||
secondTxData,
|
||||
false,
|
||||
secondTxConfirmations,
|
||||
)
|
||||
|
||||
localStorage.clear()
|
||||
|
||||
await store.dispatch(fetchTransactions(safeAddress))
|
||||
transactions = safeTransactionsSelector(store.getState(), { safeAddress })
|
||||
|
||||
testSizeOfTransactions(transactions, 2)
|
||||
const firstTxConfWithoutStorage = List([
|
||||
makeConfirmation({
|
||||
owner: makeOwner({ address: getWeb3().toChecksumAddress(executor), name: 'UNKNOWN' }),
|
||||
type: 'execution',
|
||||
hash: firstTxHash,
|
||||
}),
|
||||
])
|
||||
testTransactionFrom(transactions, 0, 'Unknown', nonce, 0, safeAddress, firstTxData, true, firstTxConfWithoutStorage)
|
||||
const secondTxConfWithoutStorage = List([
|
||||
makeConfirmation({
|
||||
owner: makeOwner({ address: getWeb3().toChecksumAddress(executor), name: 'UNKNOWN' }),
|
||||
type: 'confirmation',
|
||||
hash: secondTxHash,
|
||||
}),
|
||||
])
|
||||
testTransactionFrom(
|
||||
transactions,
|
||||
1,
|
||||
'Unknown',
|
||||
nonce + 100,
|
||||
0,
|
||||
safeAddress,
|
||||
secondTxData,
|
||||
false,
|
||||
secondTxConfWithoutStorage,
|
||||
)
|
||||
})
|
||||
|
||||
it('returns empty list of trnsactions when safe is not configured', async () => {
|
||||
// routes/safe/transactions.selector.js the 4 cases
|
||||
// confirmations.selector.js the last one
|
||||
})
|
||||
|
||||
it('pending transactions are treated correctly', async () => {
|
||||
// create a safe 3 owners 3 threshold
|
||||
// create a tx adding 4th owner
|
||||
// confirm tx and check on every step
|
||||
})
|
||||
|
||||
it('returns count of confirmed but not executed txs', async () => {
|
||||
// pendingTransactionSelector
|
||||
})
|
||||
|
||||
it('returns count of executed txs', async () => {
|
||||
// confirmationsTransactionSelector
|
||||
})
|
||||
|
||||
it('returns correctly transaction list when safe is not available', async () => {
|
||||
// routes/safe/test/transactions.selector.js
|
||||
})
|
||||
|
||||
it('process only updated txs', async () => {
|
||||
// Basically I would like when I call the GET TXs endpoint to retrieve those transactions ORDERED based on
|
||||
// when they have been updated (just created, or just added another extra confirmation).
|
||||
// In that way I do not need to parse and threat all txs in client side and also we mitigate the risk of
|
||||
// do not get old txs updates. For doing that I would need to keep stored a number indicating
|
||||
// if the tx has been updated in DB.
|
||||
// For instance:
|
||||
/*
|
||||
create tx1 ---> [{ tx:1, updated: 1 }]
|
||||
create tx2 ---> [{ tx:2, updated: 1 }, { tx:1, updated: 1 }]
|
||||
user 2 confirms tx1 ---> [{ tx:1, updated: 2 }, { tx:2, updated: 1 }]
|
||||
|
||||
In that way I keep stored tx1 -> 1 and if I see tx2 -> 2 I do not skip it
|
||||
*/
|
||||
})
|
||||
})
|
|
@ -1,67 +1,83 @@
|
|||
// @flow
|
||||
import * as TestUtils from 'react-dom/test-utils'
|
||||
import { getWeb3 } from '~/logic/wallets/getWeb3'
|
||||
import { type Match } from 'react-router-dom'
|
||||
import { getFirstTokenContract, getSecondTokenContract } from '~/test/utils/tokenMovements'
|
||||
import { fireEvent } from '@testing-library/react'
|
||||
import { getFirstTokenContract } from '~/test/utils/tokenMovements'
|
||||
import { aNewStore } from '~/store'
|
||||
import { aMinedSafe } from '~/test/builder/safe.redux.builder'
|
||||
import { travelToTokens } from '~/test/builder/safe.dom.utils'
|
||||
import { renderSafeView } from '~/test/builder/safe.dom.utils'
|
||||
import { sleep } from '~/utils/timer'
|
||||
import { buildMathPropsFrom } from '~/test/utils/buildReactRouterProps'
|
||||
import { tokenListSelector } from '~/logic/tokens/store/selectors'
|
||||
import { testToken } from '~/test/builder/tokens.dom.utils'
|
||||
import { clickOnManageTokens, clickOnAddCustomToken } from '~/test/utils/DOMNavigation'
|
||||
import * as fetchTokensModule from '~/logic/tokens/store/actions/fetchTokens'
|
||||
import * as enhancedFetchModule from '~/utils/fetch'
|
||||
import { clickOnAddToken, fillAddress, fillHumanReadableInfo } from '~/test/utils/tokens/addToken.helper'
|
||||
import {
|
||||
ADD_CUSTOM_TOKEN_ADDRESS_INPUT_TEST_ID,
|
||||
ADD_CUSTOM_TOKEN_SYMBOLS_INPUT_TEST_ID,
|
||||
ADD_CUSTOM_TOKEN_DECIMALS_INPUT_TEST_ID,
|
||||
ADD_CUSTOM_TOKEN_FORM,
|
||||
} from '~/routes/safe/components/Balances/Tokens/screens/AddCustomToken'
|
||||
import { BALANCE_ROW_TEST_ID } from '~/routes/safe/components/Balances/'
|
||||
import 'jest-dom/extend-expect'
|
||||
|
||||
describe('DOM > Feature > Add new ERC 20 Tokens', () => {
|
||||
// let web3
|
||||
// let accounts
|
||||
// let firstErc20Token
|
||||
// let secondErc20Token
|
||||
// https://github.com/testing-library/@testing-library/react/issues/281
|
||||
const originalError = console.error
|
||||
beforeAll(() => {
|
||||
console.error = (...args) => {
|
||||
if (/Warning.*not wrapped in act/.test(args[0])) {
|
||||
return
|
||||
}
|
||||
originalError.call(console, ...args)
|
||||
}
|
||||
})
|
||||
|
||||
// beforeAll(async () => {
|
||||
// web3 = getWeb3()
|
||||
// accounts = await web3.eth.getAccounts()
|
||||
// firstErc20Token = await getFirstTokenContract(web3, accounts[0])
|
||||
// secondErc20Token = await getSecondTokenContract(web3, accounts[0])
|
||||
afterAll(() => {
|
||||
console.error = originalError
|
||||
})
|
||||
|
||||
// // $FlowFixMe
|
||||
// enhancedFetchModule.enhancedFetch = jest.fn()
|
||||
// enhancedFetchModule.enhancedFetch.mockImplementation(() => Promise.resolve({
|
||||
// results: [
|
||||
// {
|
||||
// address: firstErc20Token.address,
|
||||
// name: 'First Token Example',
|
||||
// symbol: 'FTE',
|
||||
// decimals: 18,
|
||||
// logoUri: 'https://upload.wikimedia.org/wikipedia/commons/c/c0/Earth_simple_icon.png',
|
||||
// },
|
||||
// ],
|
||||
// }))
|
||||
// })
|
||||
describe('DOM > Feature > Add custom ERC 20 Tokens', () => {
|
||||
let web3
|
||||
let accounts
|
||||
let erc20Token
|
||||
|
||||
it('adds a second erc 20 token filling the form', async () => {
|
||||
// // GIVEN
|
||||
// const store = aNewStore()
|
||||
// const safeAddress = await aMinedSafe(store)
|
||||
// await store.dispatch(fetchTokensModule.fetchTokens(safeAddress))
|
||||
// const TokensDom = await travelToTokens(store, safeAddress)
|
||||
// await sleep(400)
|
||||
// const tokens = TestUtils.scryRenderedComponentsWithType(TokensDom, TokenComponent)
|
||||
// expect(tokens.length).toBe(2)
|
||||
// testToken(tokens[0].props.token, 'FTE', false)
|
||||
// testToken(tokens[1].props.token, 'ETH', true)
|
||||
// // WHEN
|
||||
// await clickOnAddToken(TokensDom)
|
||||
// await fillAddress(TokensDom, secondErc20Token)
|
||||
// await fillHumanReadableInfo(TokensDom)
|
||||
// // THEN
|
||||
// const match: Match = buildMathPropsFrom(safeAddress)
|
||||
// const tokenList = tokenListSelector(store.getState(), { match })
|
||||
// expect(tokenList.count()).toBe(3)
|
||||
// testToken(tokenList.get(0), 'FTE', false)
|
||||
// testToken(tokenList.get(1), 'ETH', true)
|
||||
// testToken(tokenList.get(2), 'TKN', true)
|
||||
beforeAll(async () => {
|
||||
web3 = getWeb3()
|
||||
accounts = await web3.eth.getAccounts()
|
||||
erc20Token = await getFirstTokenContract(web3, accounts[0])
|
||||
})
|
||||
|
||||
it('adds and displays an erc 20 token after filling the form', async () => {
|
||||
// GIVEN
|
||||
const store = aNewStore()
|
||||
const safeAddress = await aMinedSafe(store)
|
||||
await store.dispatch(fetchTokensModule.fetchTokens())
|
||||
const TokensDom = renderSafeView(store, safeAddress)
|
||||
await sleep(400)
|
||||
|
||||
// WHEN
|
||||
clickOnManageTokens(TokensDom)
|
||||
clickOnAddCustomToken(TokensDom)
|
||||
await sleep(200)
|
||||
|
||||
// Fill address
|
||||
const addTokenForm = TokensDom.getByTestId(ADD_CUSTOM_TOKEN_FORM)
|
||||
const addressInput = TokensDom.getByTestId(ADD_CUSTOM_TOKEN_ADDRESS_INPUT_TEST_ID)
|
||||
fireEvent.change(addressInput, { target: { value: erc20Token.address } })
|
||||
await sleep(500)
|
||||
|
||||
// Check if it loaded symbol/decimals correctly
|
||||
const symbolInput = TokensDom.getByTestId(ADD_CUSTOM_TOKEN_SYMBOLS_INPUT_TEST_ID)
|
||||
const decimalsInput = TokensDom.getByTestId(ADD_CUSTOM_TOKEN_DECIMALS_INPUT_TEST_ID)
|
||||
|
||||
const tokenSymbol = await erc20Token.symbol()
|
||||
const tokenDecimals = await erc20Token.decimals()
|
||||
expect(symbolInput.value).toBe(tokenSymbol)
|
||||
expect(decimalsInput.value).toBe(tokenDecimals.toString())
|
||||
|
||||
// Submit form
|
||||
fireEvent.submit(addTokenForm)
|
||||
await sleep(300)
|
||||
|
||||
// check if token is displayed
|
||||
const balanceRows = TokensDom.getAllByTestId(BALANCE_ROW_TEST_ID)
|
||||
expect(balanceRows.length).toBe(2)
|
||||
expect(balanceRows[1]).toHaveTextContent(tokenSymbol)
|
||||
})
|
||||
})
|
||||
|
|
|
@ -1,130 +1,83 @@
|
|||
// @flow
|
||||
import * as TestUtils from 'react-dom/test-utils'
|
||||
import { List } from 'immutable'
|
||||
import { getWeb3 } from '~/logic/wallets/getWeb3'
|
||||
import { type Match } from 'react-router-dom'
|
||||
import Checkbox from '@material-ui/core/Checkbox'
|
||||
import { getFirstTokenContract, getSecondTokenContract, addTknTo } from '~/test/utils/tokenMovements'
|
||||
import { getFirstTokenContract, getSecondTokenContract } from '~/test/utils/tokenMovements'
|
||||
import { aNewStore } from '~/store'
|
||||
import { aMinedSafe } from '~/test/builder/safe.redux.builder'
|
||||
import { travelToTokens } from '~/test/builder/safe.dom.utils'
|
||||
import { renderSafeView } from '~/test/builder/safe.dom.utils'
|
||||
import { sleep } from '~/utils/timer'
|
||||
import { buildMathPropsFrom } from '~/test/utils/buildReactRouterProps'
|
||||
import { tokenListSelector } from '~/logic/tokens/store/selectors'
|
||||
import { getActiveTokenAddresses } from '~/logic/tokens/utils/tokensStorage'
|
||||
import { enableFirstToken, testToken } from '~/test/builder/tokens.dom.utils'
|
||||
import * as fetchTokensModule from '~/logic/tokens/store/actions/fetchTokens'
|
||||
import * as enhancedFetchModule from '~/utils/fetch'
|
||||
import saveTokens from '~/logic/tokens/store/actions/saveTokens'
|
||||
import { clickOnManageTokens, toggleToken, closeManageTokensModal } from './utils/DOMNavigation'
|
||||
import { BALANCE_ROW_TEST_ID } from '~/routes/safe/components/Balances'
|
||||
import { makeToken } from '~/logic/tokens/store/model/token'
|
||||
import 'jest-dom/extend-expect'
|
||||
|
||||
describe('DOM > Feature > Enable and disable default tokens', () => {
|
||||
let web3
|
||||
let accounts
|
||||
let firstErc20Token
|
||||
let secondErc20Token
|
||||
let testTokens
|
||||
|
||||
beforeAll(async () => {
|
||||
web3 = getWeb3()
|
||||
accounts = await web3.eth.getAccounts()
|
||||
|
||||
firstErc20Token = await getFirstTokenContract(web3, accounts[0])
|
||||
secondErc20Token = await getSecondTokenContract(web3, accounts[0])
|
||||
// $FlowFixMe
|
||||
enhancedFetchModule.enhancedFetch = jest.fn()
|
||||
enhancedFetchModule.enhancedFetch.mockImplementation(() => Promise.resolve({
|
||||
results: [
|
||||
{
|
||||
address: firstErc20Token.address,
|
||||
name: 'First Token Example',
|
||||
symbol: 'FTE',
|
||||
decimals: 18,
|
||||
logoUri: 'https://upload.wikimedia.org/wikipedia/commons/c/c0/Earth_simple_icon.png',
|
||||
},
|
||||
{
|
||||
address: secondErc20Token.address,
|
||||
name: 'Second Token Example',
|
||||
symbol: 'STE',
|
||||
decimals: 18,
|
||||
logoUri: 'https://upload.wikimedia.org/wikipedia/commons/c/c0/Earth_simple_icon.png',
|
||||
},
|
||||
],
|
||||
}))
|
||||
testTokens = List([
|
||||
makeToken({
|
||||
address: firstErc20Token.address,
|
||||
name: 'First Token Example',
|
||||
symbol: 'FTE',
|
||||
decimals: 18,
|
||||
logoUri: 'https://upload.wikimedia.org/wikipedia/commons/c/c0/Earth_simple_icon.png',
|
||||
}),
|
||||
makeToken({
|
||||
address: secondErc20Token.address,
|
||||
name: 'Second Token Example',
|
||||
symbol: 'STE',
|
||||
decimals: 18,
|
||||
logoUri: 'https://upload.wikimedia.org/wikipedia/commons/c/c0/Earth_simple_icon.png',
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
it('retrieves only ether as active token in first moment', async () => {
|
||||
it('allows to enable and disable tokens', async () => {
|
||||
// GIVEN
|
||||
const store = aNewStore()
|
||||
const safeAddress = await aMinedSafe(store)
|
||||
await store.dispatch(fetchTokensModule.fetchTokens(safeAddress))
|
||||
await store.dispatch(saveTokens(testTokens))
|
||||
|
||||
// WHEN
|
||||
const TokensDom = await travelToTokens(store, safeAddress)
|
||||
const TokensDom = await renderSafeView(store, safeAddress)
|
||||
await sleep(400)
|
||||
|
||||
// THEN
|
||||
const tokens = TestUtils.scryRenderedComponentsWithType(TokensDom, TokenComponent)
|
||||
expect(tokens.length).toBe(3)
|
||||
|
||||
testToken(tokens[0].props.token, 'FTE', false)
|
||||
testToken(tokens[1].props.token, 'STE', false)
|
||||
testToken(tokens[2].props.token, 'ETH', true)
|
||||
|
||||
const ethCheckbox = TestUtils.findRenderedComponentWithType(tokens[2], Checkbox)
|
||||
if (!ethCheckbox) throw new Error()
|
||||
expect(ethCheckbox.props.disabled).toBe(true)
|
||||
})
|
||||
|
||||
it('fetch balances of only enabled tokens', async () => {
|
||||
// GIVEN
|
||||
const store = aNewStore()
|
||||
const safeAddress = await aMinedSafe(store)
|
||||
await addTknTo(safeAddress, '50', firstErc20Token)
|
||||
await addTknTo(safeAddress, '50', secondErc20Token)
|
||||
await store.dispatch(fetchTokensModule.fetchTokens(safeAddress))
|
||||
|
||||
const match: Match = buildMathPropsFrom(safeAddress)
|
||||
let tokenList = tokenListSelector(store.getState(), { match })
|
||||
expect(tokenList.count()).toBe(3)
|
||||
|
||||
await enableFirstToken(store, safeAddress)
|
||||
tokenList = tokenListSelector(store.getState(), { match })
|
||||
expect(tokenList.count()).toBe(3) // assuring the enableToken do not add extra info
|
||||
// Check if only ETH is enabled
|
||||
let balanceRows = TokensDom.getAllByTestId(BALANCE_ROW_TEST_ID)
|
||||
expect(balanceRows.length).toBe(1)
|
||||
|
||||
// THEN
|
||||
testToken(tokenList.get(0), 'FTE', true)
|
||||
testToken(tokenList.get(1), 'STE', false)
|
||||
testToken(tokenList.get(2), 'ETH', true)
|
||||
clickOnManageTokens(TokensDom)
|
||||
toggleToken(TokensDom, 'FTE')
|
||||
toggleToken(TokensDom, 'STE')
|
||||
closeManageTokensModal(TokensDom)
|
||||
|
||||
const activeTokenList = activeTokensSelector(store.getState(), { match })
|
||||
expect(activeTokenList.count()).toBe(2)
|
||||
// Check if tokens were enabled
|
||||
balanceRows = TokensDom.getAllByTestId(BALANCE_ROW_TEST_ID)
|
||||
expect(balanceRows.length).toBe(3)
|
||||
expect(balanceRows[1]).toHaveTextContent('FTE')
|
||||
expect(balanceRows[2]).toHaveTextContent('STE')
|
||||
|
||||
testToken(activeTokenList.get(0), 'FTE', true)
|
||||
testToken(activeTokenList.get(1), 'ETH', true)
|
||||
// disable tokens
|
||||
clickOnManageTokens(TokensDom)
|
||||
toggleToken(TokensDom, 'FTE')
|
||||
toggleToken(TokensDom, 'STE')
|
||||
closeManageTokensModal(TokensDom)
|
||||
|
||||
await store.dispatch(fetchTokensModule.fetchTokens(safeAddress))
|
||||
|
||||
const fundedTokenList = tokenListSelector(store.getState(), { match })
|
||||
expect(fundedTokenList.count()).toBe(3)
|
||||
|
||||
testToken(fundedTokenList.get(0), 'FTE', true, '50')
|
||||
testToken(fundedTokenList.get(1), 'STE', false, '0')
|
||||
testToken(fundedTokenList.get(2), 'ETH', true, '0')
|
||||
})
|
||||
|
||||
it('localStorage always returns a list', async () => {
|
||||
const store = aNewStore()
|
||||
const safeAddress = await aMinedSafe(store)
|
||||
let tokens: List<string> = getActiveTokenAddresses(safeAddress)
|
||||
expect(tokens).toEqual(List([]))
|
||||
|
||||
await store.dispatch(fetchTokensModule.fetchTokens(safeAddress))
|
||||
tokens = getActiveTokenAddresses(safeAddress)
|
||||
expect(tokens.count()).toBe(0)
|
||||
|
||||
await enableFirstToken(store, safeAddress)
|
||||
tokens = getActiveTokenAddresses(safeAddress)
|
||||
expect(tokens.count()).toBe(1)
|
||||
|
||||
await store.dispatch(fetchTokensModule.fetchTokens(safeAddress))
|
||||
tokens = getActiveTokenAddresses(safeAddress)
|
||||
expect(tokens.count()).toBe(1)
|
||||
// check if tokens were disabled
|
||||
balanceRows = TokensDom.getAllByTestId(BALANCE_ROW_TEST_ID)
|
||||
expect(balanceRows.length).toBe(1)
|
||||
expect(balanceRows[0]).toHaveTextContent('ETH')
|
||||
})
|
||||
})
|
||||
|
|
|
@ -1,71 +0,0 @@
|
|||
// @flow
|
||||
import * as TestUtils from 'react-dom/test-utils'
|
||||
import { getWeb3 } from '~/logic/wallets/getWeb3'
|
||||
import { getFirstTokenContract, getSecondTokenContract } from '~/test/utils/tokenMovements'
|
||||
import { aNewStore } from '~/store'
|
||||
import { aMinedSafe } from '~/test/builder/safe.redux.builder'
|
||||
import { travelToTokens } from '~/test/builder/safe.dom.utils'
|
||||
import * as fetchTokensModule from '~/logic/tokens/store/actions/fetchTokens'
|
||||
import * as enhancedFetchModule from '~/utils/fetch'
|
||||
import addToken from '~/logic/tokens/store/actions/addToken'
|
||||
import { sleep } from '~/utils/timer'
|
||||
import { testToken } from '~/test/builder/tokens.dom.utils'
|
||||
|
||||
describe('DOM > Feature > Add new ERC 20 Tokens', () => {
|
||||
// let web3
|
||||
// let accounts
|
||||
// let firstErc20Token
|
||||
// let secondErc20Token
|
||||
// beforeAll(async () => {
|
||||
// web3 = getWeb3()
|
||||
// accounts = await web3.eth.getAccounts()
|
||||
// firstErc20Token = await getFirstTokenContract(web3, accounts[0])
|
||||
// secondErc20Token = await getSecondTokenContract(web3, accounts[0])
|
||||
// // $FlowFixMe
|
||||
// enhancedFetchModule.enhancedFetch = jest.fn()
|
||||
// enhancedFetchModule.enhancedFetch.mockImplementation(() => Promise.resolve({
|
||||
// results: [
|
||||
// {
|
||||
// address: firstErc20Token.address,
|
||||
// name: 'First Token Example',
|
||||
// symbol: 'FTE',
|
||||
// decimals: 18,
|
||||
// logoUri: 'https://upload.wikimedia.org/wikipedia/commons/c/c0/Earth_simple_icon.png',
|
||||
// },
|
||||
// ],
|
||||
// }))
|
||||
// })
|
||||
// it('remove custom ERC 20 tokens', async () => {
|
||||
// // GIVEN
|
||||
// const store = aNewStore()
|
||||
// const safeAddress = await aMinedSafe(store)
|
||||
// await store.dispatch(fetchTokensModule.fetchTokens(safeAddress))
|
||||
// const values = {
|
||||
// [TOKEN_ADRESS_PARAM]: secondErc20Token.address,
|
||||
// [TOKEN_NAME_PARAM]: 'Custom ERC20 Token',
|
||||
// [TOKEN_SYMBOL_PARAM]: 'CTS',
|
||||
// [TOKEN_DECIMALS_PARAM]: '10',
|
||||
// [TOKEN_LOGO_URL_PARAM]: 'https://example.com',
|
||||
// }
|
||||
// const customAddTokensFn: any = (...args) => store.dispatch(addToken(...args))
|
||||
// await addTokenFnc(values, customAddTokensFn, safeAddress)
|
||||
// const TokensDom = travelToTokens(store, safeAddress)
|
||||
// await sleep(400)
|
||||
// // WHEN
|
||||
// const buttons = TestUtils.scryRenderedDOMComponentsWithTag(TokensDom, 'button')
|
||||
// expect(buttons.length).toBe(2)
|
||||
// const removeUserButton = buttons[0]
|
||||
// expect(removeUserButton.getAttribute('aria-label')).toBe('Delete')
|
||||
// TestUtils.Simulate.click(removeUserButton)
|
||||
// await sleep(400)
|
||||
// const form = TestUtils.findRenderedDOMComponentWithTag(TokensDom, 'form')
|
||||
// // submit it
|
||||
// TestUtils.Simulate.submit(form)
|
||||
// TestUtils.Simulate.submit(form)
|
||||
// await sleep(400)
|
||||
// const tokens = TestUtils.scryRenderedComponentsWithType(TokensDom, TokenComponent)
|
||||
// expect(tokens.length).toBe(2)
|
||||
// testToken(tokens[0].props.token, 'FTE', false)
|
||||
// testToken(tokens[1].props.token, 'ETH', true)
|
||||
// })
|
||||
})
|
|
@ -1,64 +0,0 @@
|
|||
// @flow
|
||||
import { getWeb3 } from '~/logic/wallets/getWeb3'
|
||||
import { type Match } from 'react-router-dom'
|
||||
import { getFirstTokenContract, getSecondTokenContract } from '~/test/utils/tokenMovements'
|
||||
import { aNewStore } from '~/store'
|
||||
import { aMinedSafe } from '~/test/builder/safe.redux.builder'
|
||||
import { travelToSafe } from '~/test/builder/safe.dom.utils'
|
||||
import { buildMathPropsFrom } from '~/test/utils/buildReactRouterProps'
|
||||
import { testToken } from '~/test/builder/tokens.dom.utils'
|
||||
import * as fetchTokensModule from '~/logic/tokens/store/actions/fetchTokens'
|
||||
import * as enhancedFetchModule from '~/utils/fetch'
|
||||
import addToken from '~/logic/tokens/store/actions/addToken'
|
||||
|
||||
describe('DOM > Feature > Add new ERC 20 Tokens', () => {
|
||||
// let web3
|
||||
// let accounts
|
||||
// let firstErc20Token
|
||||
// let secondErc20Token
|
||||
// beforeAll(async () => {
|
||||
// web3 = getWeb3()
|
||||
// accounts = await web3.eth.getAccounts()
|
||||
// firstErc20Token = await getFirstTokenContract(web3, accounts[0])
|
||||
// secondErc20Token = await getSecondTokenContract(web3, accounts[0])
|
||||
// // $FlowFixMe
|
||||
// enhancedFetchModule.enhancedFetch = jest.fn()
|
||||
// enhancedFetchModule.enhancedFetch.mockImplementation(() => Promise.resolve({
|
||||
// results: [
|
||||
// {
|
||||
// address: firstErc20Token.address,
|
||||
// name: 'First Token Example',
|
||||
// symbol: 'FTE',
|
||||
// decimals: 18,
|
||||
// logoUri: 'https://upload.wikimedia.org/wikipedia/commons/c/c0/Earth_simple_icon.png',
|
||||
// },
|
||||
// ],
|
||||
// }))
|
||||
// })
|
||||
// it('persist added custom ERC 20 tokens as active when reloading the page', async () => {
|
||||
// // GIVEN
|
||||
// const store = aNewStore()
|
||||
// const safeAddress = await aMinedSafe(store)
|
||||
// await store.dispatch(fetchTokensModule.fetchTokens(safeAddress))
|
||||
// const values = {
|
||||
// [TOKEN_ADRESS_PARAM]: secondErc20Token.address,
|
||||
// [TOKEN_NAME_PARAM]: 'Custom ERC20 Token',
|
||||
// [TOKEN_SYMBOL_PARAM]: 'CTS',
|
||||
// [TOKEN_DECIMALS_PARAM]: '10',
|
||||
// [TOKEN_LOGO_URL_PARAM]: 'https://example.com',
|
||||
// }
|
||||
// const customAddTokensFn: any = (...args) => store.dispatch(addToken(...args))
|
||||
// await addTokenFnc(values, customAddTokensFn, safeAddress)
|
||||
// travelToSafe(store, safeAddress)
|
||||
// // WHEN
|
||||
// const reloadedStore = aNewStore()
|
||||
// await reloadedStore.dispatch(fetchTokensModule.fetchTokens(safeAddress))
|
||||
// travelToSafe(reloadedStore, safeAddress) // reload
|
||||
// // THEN
|
||||
// const match: Match = buildMathPropsFrom(safeAddress)
|
||||
// const activeTokenList = activeTokensSelector(reloadedStore.getState(), { match })
|
||||
// expect(activeTokenList.count()).toBe(2)
|
||||
// testToken(activeTokenList.get(0), 'CTS', true)
|
||||
// testToken(activeTokenList.get(1), 'ETH', true)
|
||||
// })
|
||||
})
|
|
@ -1,83 +0,0 @@
|
|||
// @flow
|
||||
import { getWeb3 } from '~/logic/wallets/getWeb3'
|
||||
import { type Match } from 'react-router-dom'
|
||||
import { getFirstTokenContract, getSecondTokenContract } from '~/test/utils/tokenMovements'
|
||||
import { aNewStore } from '~/store'
|
||||
import { aMinedSafe } from '~/test/builder/safe.redux.builder'
|
||||
import { travelToSafe } from '~/test/builder/safe.dom.utils'
|
||||
import { buildMathPropsFrom } from '~/test/utils/buildReactRouterProps'
|
||||
import { testToken } from '~/test/builder/tokens.dom.utils'
|
||||
import * as fetchTokensModule from '~/logic/tokens/store/actions/fetchTokens'
|
||||
import * as enhancedFetchModule from '~/utils/fetch'
|
||||
import addToken from '~/logic/tokens/store/actions/addToken'
|
||||
import removeTokenAction from '~/logic/tokens/store/actions/removeToken'
|
||||
import { makeToken } from '~/logic/tokens/store/model/token'
|
||||
|
||||
describe('DOM > Feature > Add new ERC 20 Tokens', () => {
|
||||
// let web3
|
||||
// let accounts
|
||||
// let firstErc20Token
|
||||
// let secondErc20Token
|
||||
// beforeAll(async () => {
|
||||
// web3 = getWeb3()
|
||||
// accounts = await web3.eth.getAccounts()
|
||||
// firstErc20Token = await getFirstTokenContract(web3, accounts[0])
|
||||
// secondErc20Token = await getSecondTokenContract(web3, accounts[0])
|
||||
// // $FlowFixMe
|
||||
// enhancedFetchModule.enhancedFetch = jest.fn()
|
||||
// enhancedFetchModule.enhancedFetch.mockImplementation(() => Promise.resolve({
|
||||
// results: [
|
||||
// {
|
||||
// address: firstErc20Token.address,
|
||||
// name: 'First Token Example',
|
||||
// symbol: 'FTE',
|
||||
// decimals: 18,
|
||||
// logoUri: 'https://upload.wikimedia.org/wikipedia/commons/c/c0/Earth_simple_icon.png',
|
||||
// },
|
||||
// ],
|
||||
// }))
|
||||
// })
|
||||
// const checkTokensOf = (store: Store, safeAddress: string) => {
|
||||
// const match: Match = buildMathPropsFrom(safeAddress)
|
||||
// const activeTokenList = activeTokensSelector(store.getState(), { match })
|
||||
// expect(activeTokenList.count()).toBe(1)
|
||||
// testToken(activeTokenList.get(0), 'ETH', true)
|
||||
// const tokenList = tokenListSelector(store.getState(), { match })
|
||||
// expect(tokenList.count()).toBe(2)
|
||||
// testToken(tokenList.get(0), 'FTE', false)
|
||||
// testToken(tokenList.get(1), 'ETH', true)
|
||||
// }
|
||||
// it('removes custom ERC 20 including page reload', async () => {
|
||||
// // GIVEN
|
||||
// const store = aNewStore()
|
||||
// const safeAddress = await aMinedSafe(store)
|
||||
// await store.dispatch(fetchTokensModule.fetchTokens(safeAddress))
|
||||
// const values = {
|
||||
// [TOKEN_ADRESS_PARAM]: secondErc20Token.address,
|
||||
// [TOKEN_NAME_PARAM]: 'Custom ERC20 Token',
|
||||
// [TOKEN_SYMBOL_PARAM]: 'CTS',
|
||||
// [TOKEN_DECIMALS_PARAM]: '10',
|
||||
// [TOKEN_LOGO_URL_PARAM]: 'https://example.com',
|
||||
// }
|
||||
// const customAddTokensFn: any = (...args) => store.dispatch(addToken(...args))
|
||||
// await addTokenFnc(values, customAddTokensFn, safeAddress)
|
||||
// const token = makeToken({
|
||||
// address: secondErc20Token.address,
|
||||
// name: 'Custom ERC20 Token',
|
||||
// symbol: 'CTS',
|
||||
// decimals: 10,
|
||||
// logoUri: 'https://example.com',
|
||||
// status: true,
|
||||
// removable: true,
|
||||
// })
|
||||
// const customRemoveTokensFnc: any = (...args) => store.dispatch(removeTokenAction(...args))
|
||||
// await removeToken(safeAddress, token, customRemoveTokensFnc)
|
||||
// checkTokensOf(store, safeAddress)
|
||||
// // WHEN
|
||||
// const reloadedStore = aNewStore()
|
||||
// await reloadedStore.dispatch(fetchTokensModule.fetchTokens(safeAddress))
|
||||
// travelToSafe(reloadedStore, safeAddress) // reload
|
||||
// // THEN
|
||||
// checkTokensOf(reloadedStore, safeAddress)
|
||||
// })
|
||||
})
|
|
@ -0,0 +1,3 @@
|
|||
// @flow
|
||||
|
||||
export * from './tokens'
|
|
@ -0,0 +1,29 @@
|
|||
// @flow
|
||||
import { fireEvent } from '@testing-library/react'
|
||||
import { MANAGE_TOKENS_BUTTON_TEST_ID } from '~/routes/safe/components/Balances'
|
||||
import { ADD_CUSTOM_TOKEN_BUTTON_TEST_ID, TOGGLE_TOKEN_TEST_ID } from '~/routes/safe/components/Balances/Tokens/screens/TokenList'
|
||||
import { MANAGE_TOKENS_MODAL_CLOSE_BUTTON_TEST_ID } from '~/routes/safe/components/Balances/Tokens'
|
||||
|
||||
export const clickOnManageTokens = (dom: any): void => {
|
||||
const btn = dom.getByTestId(MANAGE_TOKENS_BUTTON_TEST_ID)
|
||||
|
||||
fireEvent.click(btn)
|
||||
}
|
||||
|
||||
export const clickOnAddCustomToken = (dom: any): void => {
|
||||
const btn = dom.getByTestId(ADD_CUSTOM_TOKEN_BUTTON_TEST_ID)
|
||||
|
||||
fireEvent.click(btn)
|
||||
}
|
||||
|
||||
export const toggleToken = (dom: any, symbol: string): void => {
|
||||
const btn = dom.getByTestId(`${symbol}_${TOGGLE_TOKEN_TEST_ID}`)
|
||||
|
||||
fireEvent.click(btn)
|
||||
}
|
||||
|
||||
export const closeManageTokensModal = (dom: any) => {
|
||||
const btn = dom.getByTestId(MANAGE_TOKENS_MODAL_CLOSE_BUTTON_TEST_ID)
|
||||
|
||||
fireEvent.click(btn)
|
||||
}
|
|
@ -1,10 +0,0 @@
|
|||
// @flow
|
||||
import * as React from 'react'
|
||||
|
||||
type WrapperProps = {
|
||||
children: React$Node,
|
||||
}
|
||||
|
||||
const Wrapper = ({ children }: WrapperProps) => <React.Fragment>{children}</React.Fragment>
|
||||
|
||||
export default Wrapper
|
|
@ -1,7 +1,7 @@
|
|||
// @flow
|
||||
import { type Match } from 'react-router-dom'
|
||||
|
||||
export const buildMathPropsFrom = (address: string): Match => ({
|
||||
export const buildMatchPropsFrom = (address: string): Match => ({
|
||||
params: {
|
||||
address,
|
||||
},
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
// @flow
|
||||
import React from 'react'
|
||||
import { getGnosisSafeInstanceAt } from '~/logic/contracts/safeContracts'
|
||||
import GnoStepper from '~/components/Stepper'
|
||||
import Stepper from '@material-ui/core/Stepper'
|
||||
|
@ -37,7 +38,7 @@ type FinsihedTx = {
|
|||
finishedTransaction: boolean,
|
||||
}
|
||||
|
||||
export const whenExecuted = (SafeDom: React$Component<any, any>, ParentComponent: React$ElementType): Promise<void> => new Promise((resolve, reject) => {
|
||||
export const whenExecuted = (SafeDom: React.Component<any, any>, ParentComponent: React.ElementType): Promise<void> => new Promise((resolve, reject) => {
|
||||
let times = 0
|
||||
const interval = setInterval(() => {
|
||||
if (times >= MAX_TIMES_EXECUTED) {
|
||||
|
@ -47,7 +48,7 @@ export const whenExecuted = (SafeDom: React$Component<any, any>, ParentComponent
|
|||
|
||||
// $FlowFixMe
|
||||
const SafeComponent = TestUtils.findRenderedComponentWithType(SafeDom, ParentComponent)
|
||||
type GnoStepperType = React$Component<FinsihedTx, any>
|
||||
type GnoStepperType = React.Component<FinsihedTx, any>
|
||||
// $FlowFixMe
|
||||
const StepperComponent: GnoStepperType = TestUtils.findRenderedComponentWithType(SafeComponent, GnoStepper)
|
||||
|
||||
|
@ -64,8 +65,8 @@ type MiddleStep = {
|
|||
}
|
||||
|
||||
export const whenOnNext = (
|
||||
SafeDom: React$Component<any, any>,
|
||||
ParentComponent: React$ElementType,
|
||||
SafeDom: React.Component<any, any>,
|
||||
ParentComponent: React.ElementType,
|
||||
position: number,
|
||||
): Promise<void> => new Promise((resolve, reject) => {
|
||||
let times = 0
|
||||
|
@ -77,7 +78,7 @@ export const whenOnNext = (
|
|||
|
||||
// $FlowFixMe
|
||||
const SafeComponent = TestUtils.findRenderedComponentWithType(SafeDom, ParentComponent)
|
||||
type StepperType = React$Component<MiddleStep, any>
|
||||
type StepperType = React.Component<MiddleStep, any>
|
||||
// $FlowFixMe
|
||||
const StepperComponent: StepperType = TestUtils.findRenderedComponentWithType(SafeComponent, Stepper)
|
||||
if (StepperComponent.props.activeStep === position) {
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
// @flow
|
||||
import { buildMathPropsFrom } from '~/test/utils/buildReactRouterProps'
|
||||
import { buildMatchPropsFrom } from '~/test/utils/buildReactRouterProps'
|
||||
import { safeSelector } from '~/routes/safe/store/selectors/index'
|
||||
import { type Match } from 'react-router-dom'
|
||||
import { type GlobalState } from '~/store'
|
||||
import { type Safe } from '~/routes/safe/store/models/safe'
|
||||
|
||||
export const getSafeFrom = (state: GlobalState, safeAddress: string): Safe => {
|
||||
const match: Match = buildMathPropsFrom(safeAddress)
|
||||
const match: Match = buildMatchPropsFrom(safeAddress)
|
||||
const safe = safeSelector(state, { match })
|
||||
if (!safe) throw new Error()
|
||||
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue