Merge pull request #17 from gnosis/feature/WA-230-add-balance-selector
WA-230 Adding get balance selector
This commit is contained in:
commit
f376bfcba4
|
@ -6,10 +6,10 @@ import GnoSafe from './Safe'
|
||||||
|
|
||||||
type Props = SelectorProps
|
type Props = SelectorProps
|
||||||
|
|
||||||
const Layout = ({ safe, provider }: Props) => (
|
const Layout = ({ safe, balance, provider }: Props) => (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
{ safe
|
{ safe
|
||||||
? <GnoSafe safe={safe} />
|
? <GnoSafe safe={safe} balance={balance} />
|
||||||
: <NoSafe provider={provider} text="Not found safe" />
|
: <NoSafe provider={provider} text="Not found safe" />
|
||||||
}
|
}
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
|
|
|
@ -14,12 +14,31 @@ const FrameDecorator = story => (
|
||||||
|
|
||||||
storiesOf('Routes /safe:address', module)
|
storiesOf('Routes /safe:address', module)
|
||||||
.addDecorator(FrameDecorator)
|
.addDecorator(FrameDecorator)
|
||||||
.add('Safe undefined being connected', () => <Component safe={undefined} provider="METAMASK" />)
|
.add('Safe undefined being connected', () => (
|
||||||
.add('Safe undefined NOT connected', () => <Component safe={undefined} provider="" />)
|
<Component
|
||||||
|
safe={undefined}
|
||||||
|
provider="METAMASK"
|
||||||
|
balance="0"
|
||||||
|
fetchBalance={() => {}}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
.add('Safe undefined NOT connected', () => (
|
||||||
|
<Component
|
||||||
|
safe={undefined}
|
||||||
|
provider=""
|
||||||
|
balance="0"
|
||||||
|
fetchBalance={() => {}}
|
||||||
|
/>
|
||||||
|
))
|
||||||
.add('Safe with 2 owners', () => {
|
.add('Safe with 2 owners', () => {
|
||||||
const safe = SafeFactory.twoOwnersSafe
|
const safe = SafeFactory.twoOwnersSafe
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Component safe={safe} provider="METAMASK" />
|
<Component
|
||||||
|
safe={safe}
|
||||||
|
provider="METAMASK"
|
||||||
|
balance="2"
|
||||||
|
fetchBalance={() => {}}
|
||||||
|
/>
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
|
@ -10,9 +10,10 @@ import { type Safe } from '~/routes/safe/store/model/safe'
|
||||||
|
|
||||||
type SafeProps = {
|
type SafeProps = {
|
||||||
safe: Safe,
|
safe: Safe,
|
||||||
|
balance: string,
|
||||||
}
|
}
|
||||||
|
|
||||||
const GnoSafe = ({ safe }: SafeProps) => (
|
const GnoSafe = ({ safe, balance }: SafeProps) => (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
<Row>
|
<Row>
|
||||||
<Col xs={12}>
|
<Col xs={12}>
|
||||||
|
@ -21,6 +22,18 @@ const GnoSafe = ({ safe }: SafeProps) => (
|
||||||
</Paragraph>
|
</Paragraph>
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
<Row>
|
||||||
|
<Paragraph size="lg">
|
||||||
|
<Bold>Balance</Bold>
|
||||||
|
</Paragraph>
|
||||||
|
</Row>
|
||||||
|
<Row>
|
||||||
|
<Block>
|
||||||
|
<Paragraph>
|
||||||
|
{balance} - ETH
|
||||||
|
</Paragraph>
|
||||||
|
</Block>
|
||||||
|
</Row>
|
||||||
<Row>
|
<Row>
|
||||||
<Paragraph size="lg">
|
<Paragraph size="lg">
|
||||||
<Bold>Address</Bold>
|
<Bold>Address</Bold>
|
||||||
|
|
|
@ -0,0 +1,10 @@
|
||||||
|
// @flow
|
||||||
|
import fetchBalance from '~/routes/safe/store/actions/fetchBalance'
|
||||||
|
|
||||||
|
export type Actions = {
|
||||||
|
fetchBalance: typeof fetchBalance,
|
||||||
|
}
|
||||||
|
|
||||||
|
export default {
|
||||||
|
fetchBalance,
|
||||||
|
}
|
|
@ -4,19 +4,30 @@ import { connect } from 'react-redux'
|
||||||
import Page from '~/components/layout/Page'
|
import Page from '~/components/layout/Page'
|
||||||
import Layout from '~/routes/safe/component/Layout'
|
import Layout from '~/routes/safe/component/Layout'
|
||||||
import selector, { type SelectorProps } from './selector'
|
import selector, { type SelectorProps } from './selector'
|
||||||
|
import actions, { type Actions } from './actions'
|
||||||
|
|
||||||
type Props = SelectorProps
|
class SafeView extends React.PureComponent<Actions & SelectorProps> {
|
||||||
|
componentDidMount() {
|
||||||
|
const { safe, fetchBalance } = this.props
|
||||||
|
if (!safe) { return }
|
||||||
|
|
||||||
|
const safeAddress: string = safe.get('address')
|
||||||
|
fetchBalance(safeAddress)
|
||||||
|
}
|
||||||
|
|
||||||
class SafeView extends React.PureComponent<Props> {
|
|
||||||
render() {
|
render() {
|
||||||
const { safe, provider } = this.props
|
const { safe, provider, balance } = this.props
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Page>
|
<Page>
|
||||||
<Layout provider={provider} safe={safe} />
|
<Layout
|
||||||
|
balance={balance}
|
||||||
|
provider={provider}
|
||||||
|
safe={safe}
|
||||||
|
/>
|
||||||
</Page>
|
</Page>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default connect(selector)(SafeView)
|
export default connect(selector, actions)(SafeView)
|
||||||
|
|
|
@ -1,14 +1,16 @@
|
||||||
// @flow
|
// @flow
|
||||||
import { createStructuredSelector } from 'reselect'
|
import { createStructuredSelector } from 'reselect'
|
||||||
import { safeSelector, type SafeSelectorProps } from '~/routes/safe/store/selectors'
|
import { balanceSelector, safeSelector, type SafeSelectorProps } from '~/routes/safe/store/selectors'
|
||||||
import { providerNameSelector } from '~/wallets/store/selectors/index'
|
import { providerNameSelector } from '~/wallets/store/selectors/index'
|
||||||
|
|
||||||
export type SelectorProps = {
|
export type SelectorProps = {
|
||||||
safe: SafeSelectorProps,
|
safe: SafeSelectorProps,
|
||||||
provider: string,
|
provider: string,
|
||||||
|
balance: string,
|
||||||
}
|
}
|
||||||
|
|
||||||
export default createStructuredSelector({
|
export default createStructuredSelector({
|
||||||
safe: safeSelector,
|
safe: safeSelector,
|
||||||
provider: providerNameSelector,
|
provider: providerNameSelector,
|
||||||
|
balance: balanceSelector,
|
||||||
})
|
})
|
||||||
|
|
|
@ -6,6 +6,7 @@ import { type GlobalState } from '~/store/index'
|
||||||
import { SAFE_PARAM_ADDRESS } from '~/routes/routes'
|
import { SAFE_PARAM_ADDRESS } from '~/routes/routes'
|
||||||
import { type Safe } from '~/routes/safe/store/model/safe'
|
import { type Safe } from '~/routes/safe/store/model/safe'
|
||||||
import { safesMapSelector } from '~/routes/safeList/store/selectors'
|
import { safesMapSelector } from '~/routes/safeList/store/selectors'
|
||||||
|
import { BALANCE_REDUCER_ID } from '~/routes/safe/store/reducer/balances'
|
||||||
|
|
||||||
type RouterProps = {
|
type RouterProps = {
|
||||||
match: Match,
|
match: Match,
|
||||||
|
@ -13,6 +14,8 @@ type RouterProps = {
|
||||||
|
|
||||||
const safeAddessSelector = (state: GlobalState, props: RouterProps) => props.match.params[SAFE_PARAM_ADDRESS] || ''
|
const safeAddessSelector = (state: GlobalState, props: RouterProps) => props.match.params[SAFE_PARAM_ADDRESS] || ''
|
||||||
|
|
||||||
|
const balancesSelector = (state: GlobalState) => state[BALANCE_REDUCER_ID]
|
||||||
|
|
||||||
export type SafeSelectorProps = Safe | typeof undefined
|
export type SafeSelectorProps = Safe | typeof undefined
|
||||||
|
|
||||||
export const safeSelector: Selector<GlobalState, RouterProps, SafeSelectorProps> = createSelector(
|
export const safeSelector: Selector<GlobalState, RouterProps, SafeSelectorProps> = createSelector(
|
||||||
|
@ -27,6 +30,18 @@ export const safeSelector: Selector<GlobalState, RouterProps, SafeSelectorProps>
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
export const balanceSelector: Selector<GlobalState, RouterProps, string> = createSelector(
|
||||||
|
balancesSelector,
|
||||||
|
safeAddessSelector,
|
||||||
|
(balances: Map<string, string>, address: string) => {
|
||||||
|
if (!address) {
|
||||||
|
return '0'
|
||||||
|
}
|
||||||
|
|
||||||
|
return balances.get(address) || '0'
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
export default createStructuredSelector({
|
export default createStructuredSelector({
|
||||||
safe: safeSelector,
|
safe: safeSelector,
|
||||||
})
|
})
|
||||||
|
|
|
@ -6,10 +6,10 @@ import { getWeb3 } from '~/wallets/getWeb3'
|
||||||
import { promisify } from '~/utils/promisify'
|
import { promisify } from '~/utils/promisify'
|
||||||
import { aDeployedSafe } from './builder/deployedSafe.builder'
|
import { aDeployedSafe } from './builder/deployedSafe.builder'
|
||||||
|
|
||||||
const addOneEtherTo = async (address: string) => {
|
const addEtherTo = async (address: string, eth: string) => {
|
||||||
const web3 = getWeb3()
|
const web3 = getWeb3()
|
||||||
const accounts = await promisify(cb => web3.eth.getAccounts(cb))
|
const accounts = await promisify(cb => web3.eth.getAccounts(cb))
|
||||||
const txData = { from: accounts[0], to: address, value: web3.toWei('1', 'ether') }
|
const txData = { from: accounts[0], to: address, value: web3.toWei(eth, 'ether') }
|
||||||
return promisify(cb => web3.eth.sendTransaction(txData, cb))
|
return promisify(cb => web3.eth.sendTransaction(txData, cb))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -34,19 +34,19 @@ const balanceReducerTests = () => {
|
||||||
expect(balances.get(address)).toBe('0')
|
expect(balances.get(address)).toBe('0')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('reducer should return 1 ETH as funds to safe with 1 ETH', async () => {
|
it('reducer should return 1.3456 ETH as funds to safe with 1 ETH', async () => {
|
||||||
// GIVEN
|
// GIVEN
|
||||||
const safeTx = await aDeployedSafe(store)
|
const safeTx = await aDeployedSafe(store)
|
||||||
const address = safeTx.contractAddress
|
const address = safeTx.contractAddress
|
||||||
|
|
||||||
// WHEN
|
// WHEN
|
||||||
await addOneEtherTo(address)
|
await addEtherTo(address, '1.3456')
|
||||||
await store.dispatch(fetchBalance(address))
|
await store.dispatch(fetchBalance(address))
|
||||||
|
|
||||||
// THEN
|
// THEN
|
||||||
const balances = store.getState()[BALANCE_REDUCER_ID]
|
const balances = store.getState()[BALANCE_REDUCER_ID]
|
||||||
expect(balances).not.toBe(undefined)
|
expect(balances).not.toBe(undefined)
|
||||||
expect(balances.get(address)).toBe('1')
|
expect(balances.get(address)).toBe('1.3456')
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,61 @@
|
||||||
|
// @flow
|
||||||
|
import { type Match } from 'react-router-dom'
|
||||||
|
import addBalance from '~/routes/safe/store/actions/addBalance'
|
||||||
|
import { aNewStore } from '~/store'
|
||||||
|
import { balanceSelector } from '../selectors'
|
||||||
|
|
||||||
|
const buildMathPropsFrom = (address): Match => ({
|
||||||
|
params: {
|
||||||
|
address,
|
||||||
|
},
|
||||||
|
isExact: true,
|
||||||
|
path: '',
|
||||||
|
url: '',
|
||||||
|
})
|
||||||
|
|
||||||
|
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(addBalance('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(addBalance(safeAddress, '1.3456'))
|
||||||
|
const balance = balanceSelector(store.getState(), { match })
|
||||||
|
|
||||||
|
// THEN
|
||||||
|
expect(balance).toBe('1.3456')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export default balanceSelectorTests
|
|
@ -1,6 +1,7 @@
|
||||||
// @flow
|
// @flow
|
||||||
import balanceReducerTests from './balance.reducer'
|
import balanceReducerTests from './balance.reducer'
|
||||||
import safeReducerTests from './safe.reducer'
|
import safeReducerTests from './safe.reducer'
|
||||||
|
import balanceSelectorTests from './balance.selector'
|
||||||
import safeSelectorTests from './safe.selector'
|
import safeSelectorTests from './safe.selector'
|
||||||
|
|
||||||
describe('Safe Test suite', () => {
|
describe('Safe Test suite', () => {
|
||||||
|
@ -10,4 +11,7 @@ describe('Safe Test suite', () => {
|
||||||
|
|
||||||
// SAFE SELECTOR
|
// SAFE SELECTOR
|
||||||
safeSelectorTests()
|
safeSelectorTests()
|
||||||
|
|
||||||
|
// BALANCE SELECTOR
|
||||||
|
balanceSelectorTests()
|
||||||
})
|
})
|
||||||
|
|
|
@ -33,4 +33,5 @@ const initialState = { [SAFE_REDUCER_ID]: calculateInitialState() }
|
||||||
|
|
||||||
export const store: Store<GlobalState> = createStore(reducers, initialState, finalCreateStore)
|
export const store: Store<GlobalState> = createStore(reducers, initialState, finalCreateStore)
|
||||||
|
|
||||||
export const aNewStore = (): Store<GlobalState> => createStore(reducers, initialState, finalCreateStore)
|
export const aNewStore = (localState?: Object): Store<GlobalState> =>
|
||||||
|
createStore(reducers, localState, finalCreateStore)
|
||||||
|
|
Loading…
Reference in New Issue