fix: portfolio balances after switching accounts (#6527)

* fix: handle portfolio staleness upon account change

* fix: only consider current account for tx updates

* test: add e2e test for account switching

* fix: remove unnused data-testid

* fix: added todo comment

* refactor: move test into existing folder

* fix: add account to dependency array

* todo: tx reducer

* test: update cypress config to pass in env variables

* fix: undo unintended change

* fix: use process.env

* fix: use regex instead of env
This commit is contained in:
cartcrom 2023-05-18 16:32:35 -04:00 committed by GitHub
parent 03095f4e48
commit cf2b6bf568
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 82 additions and 16 deletions

@ -0,0 +1,58 @@
import { getTestSelector } from '../../utils'
describe('Mini Portfolio account drawer', () => {
beforeEach(() => {
cy.intercept(/api.uniswap.org\/v1\/graphql/, cy.spy().as('gqlSpy'))
cy.visit('/swap', { ethereum: 'hardhat' })
})
it('fetches balances when account button is first hovered', () => {
// The balances should not be fetched before the account button is hovered
cy.get('@gqlSpy').should('not.have.been.called')
// Balances should have been fetched once after hover
cy.get(getTestSelector('web3-status-connected')).trigger('mouseover')
cy.get('@gqlSpy').should('have.been.calledOnce')
// Balances should not be refetched upon second hover
cy.get(getTestSelector('web3-status-connected')).trigger('mouseover')
cy.get('@gqlSpy').should('have.been.calledOnce')
// Balances should not be refetched upon opening drawer
cy.get(getTestSelector('web3-status-connected')).click()
cy.get('@gqlSpy').should('have.been.calledOnce')
// Balances should not be refetched upon closing & reopening drawer
cy.get(getTestSelector('close-account-drawer')).click()
cy.get(getTestSelector('web3-status-connected')).click()
cy.get('@gqlSpy').should('have.been.calledOnce')
})
it('refetches balances when account changes', () => {
cy.hardhat().then((hardhat) => {
const accountA = hardhat.wallets[0].address
const accountB = hardhat.wallets[1].address
// Opens the account drawer
cy.get(getTestSelector('web3-status-connected')).click()
// A shortened version of the first account's address should be shown
cy.contains(accountA.slice(0, 6)).should('exist')
// Stores the current portfolio balance to later compare to next account's balance
cy.get(getTestSelector('portfolio-total-balance'))
.invoke('text')
.then((originalBalance) => {
// TODO(INFRA-3) Replace window.ethereum access below with cypress-hardhat utility
// Simulates the wallet changing accounts via eip-1193 event
cy.window().then((win) => win.ethereum.emit('accountsChanged', [accountB]))
// The second account's address should now be shown
cy.contains(accountB.slice(0, 6)).should('exist')
// The second account's portfolio balance should differ from the original balance
cy.get(getTestSelector('portfolio-total-balance')).should('not.have.text', originalBalance)
})
})
})
})

@ -19,7 +19,7 @@ module.exports = {
chainId: 1,
forking: mainnetFork,
accounts: {
count: 1,
count: 2,
},
mining: {
auto: true, // automine to make tests easier to write.

@ -266,7 +266,7 @@ export default function AuthenticatedHeader({ account, openSettings }: { account
<PortfolioDrawerContainer>
{totalBalance !== undefined ? (
<FadeInColumn gap="xs">
<ThemedText.HeadlineLarge fontWeight={500}>
<ThemedText.HeadlineLarge fontWeight={500} data-testid="portfolio-total-balance">
{formatNumber(totalBalance, NumberType.PortfolioBalance)}
</ThemedText.HeadlineLarge>
<AutoRow marginBottom="20px">

@ -13,7 +13,7 @@ function wasPending(previousTxs: { [hash: string]: TransactionDetails | undefine
return previousTx && isTxPending(previousTx)
}
function useHasUpdatedTx() {
function useHasUpdatedTx(account: string | undefined) {
// TODO: consider monitoring tx's on chains other than the wallet's current chain
const currentChainTxs = useAllTransactions()
@ -27,12 +27,12 @@ function useHasUpdatedTx() {
const previousPendingTxs = usePrevious(pendingTxs)
return useMemo(() => {
if (!previousPendingTxs) return false
if (!previousPendingTxs || !account) return false
return Object.values(currentChainTxs).some(
(tx) => !isTxPending(tx) && wasPending(previousPendingTxs, tx),
(tx) => tx.from === account && !isTxPending(tx) && wasPending(previousPendingTxs, tx),
[currentChainTxs, previousPendingTxs]
)
}, [currentChainTxs, previousPendingTxs])
}, [account, currentChainTxs, previousPendingTxs])
}
/* Prefetches & caches portfolio balances when the wrapped component is hovered or the user completes a transaction */
@ -49,16 +49,22 @@ export default function PrefetchBalancesWrapper({ children }: PropsWithChildren)
}
}, [account, prefetchPortfolioBalances])
// TODO(cartcrom): add delay for refetching on optimism, as there is high latency in new balances being available
const hasUpdatedTx = useHasUpdatedTx()
// Listens for recently updated transactions to keep portfolio balances fresh in apollo cache
useEffect(() => {
if (!hasUpdatedTx) return
const prevAccount = usePrevious(account)
// If the drawer is open, fetch balances immediately, else set a flag to fetch on next hover
if (drawerOpen) fetchBalances()
else setHasUnfetchedBalances(true)
}, [drawerOpen, fetchBalances, hasUpdatedTx])
// TODO(cartcrom): add delay for refetching on optimism, as there is high latency in new balances being available
const hasUpdatedTx = useHasUpdatedTx(account)
// Listens for account changes & recently updated transactions to keep portfolio balances fresh in apollo cache
useEffect(() => {
const accountChanged = prevAccount !== undefined && prevAccount !== account
if (hasUpdatedTx || accountChanged) {
// If the drawer is open, fetch balances immediately, else set a flag to fetch on next hover
if (drawerOpen) {
fetchBalances()
} else {
setHasUnfetchedBalances(true)
}
}
}, [account, prevAccount, drawerOpen, fetchBalances, hasUpdatedTx])
const onHover = useCallback(() => {
if (hasUnfetchedBalances) fetchBalances()

@ -205,7 +205,7 @@ function AccountDrawer() {
name={InterfaceEventName.MINI_PORTFOLIO_TOGGLED}
properties={{ type: 'close' }}
>
<CloseDrawer onClick={toggleWalletDrawer}>
<CloseDrawer onClick={toggleWalletDrawer} data-testid="close-account-drawer">
<CloseIcon />
</CloseDrawer>
</TraceEvent>

@ -6,6 +6,8 @@ import { TransactionDetails, TransactionInfo } from './types'
const now = () => new Date().getTime()
// TODO(WEB-2053): update this to be a map of account -> chainId -> txHash -> TransactionDetails
// to simplify usage, once we're able to invalidate localstorage
export interface TransactionState {
[chainId: number]: {
[txHash: string]: TransactionDetails