Skip to content

Commit

Permalink
refactor: remove slice + move state to context
Browse files Browse the repository at this point in the history
  • Loading branch information
iamacook committed Nov 23, 2023
1 parent 98fdc8d commit f982e68
Show file tree
Hide file tree
Showing 46 changed files with 685 additions and 619 deletions.
2 changes: 1 addition & 1 deletion src/components/dashboard/RecoveryHeader/index.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { BigNumber } from 'ethers'

import { _RecoveryHeader } from '.'
import { render } from '@/tests/test-utils'
import type { RecoveryQueueItem } from '@/store/recoverySlice'
import type { RecoveryQueueItem } from '@/components/recovery/RecoveryLoaderContext'

describe('RecoveryHeader', () => {
it('should not render a widget if the chain does not support recovery', () => {
Expand Down
2 changes: 1 addition & 1 deletion src/components/dashboard/RecoveryHeader/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { RecoveryProposalCard } from '@/components/recovery/RecoveryCards/Recove
import { RecoveryInProgressCard } from '@/components/recovery/RecoveryCards/RecoveryInProgressCard'
import { WidgetContainer, WidgetBody } from '../styled'
import useIsSafeOwner from '@/hooks/useIsSafeOwner'
import type { RecoveryQueueItem } from '@/store/recoverySlice'
import type { RecoveryQueueItem } from '@/components/recovery/RecoveryLoaderContext'

export function _RecoveryHeader({
isGuardian,
Expand Down
2 changes: 1 addition & 1 deletion src/components/recovery/ExecuteRecoveryButton/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ import useOnboard from '@/hooks/wallets/useOnboard'
import useSafeInfo from '@/hooks/useSafeInfo'
import { useRecoveryTxState } from '@/hooks/useRecoveryTxState'
import { Errors, logError } from '@/services/exceptions'
import type { RecoveryQueueItem } from '@/store/recoverySlice'
import { RecoveryLoaderContext } from '../RecoveryLoaderContext'
import type { RecoveryQueueItem } from '@/components/recovery/RecoveryLoaderContext'

export function ExecuteRecoveryButton({
recovery,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { Countdown } from '@/components/common/Countdown'
import RecoveryPending from '@/public/images/common/recovery-pending.svg'
import ExternalLink from '@/components/common/ExternalLink'
import { AppRoutes } from '@/config/routes'
import type { RecoveryQueueItem } from '@/store/recoverySlice'
import type { RecoveryQueueItem } from '@/components/recovery/RecoveryLoaderContext'

import css from './styles.module.css'

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { fireEvent, waitFor } from '@testing-library/react'
import { render } from '@/tests/test-utils'
import { RecoveryInProgressCard } from '../RecoveryInProgressCard'
import { useRecoveryTxState } from '@/hooks/useRecoveryTxState'
import type { RecoveryQueueItem } from '@/store/recoverySlice'
import type { RecoveryQueueItem } from '@/components/recovery/RecoveryLoaderContext'

jest.mock('@/hooks/useRecoveryTxState')

Expand Down
2 changes: 1 addition & 1 deletion src/components/recovery/RecoveryDetails/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import useSafeInfo from '@/hooks/useSafeInfo'
import ErrorMessage from '@/components/tx/ErrorMessage'
import { RecoverySigners } from '../RecoverySigners'
import { Errors, logError } from '@/services/exceptions'
import type { RecoveryQueueItem } from '@/store/recoverySlice'
import type { RecoveryQueueItem } from '@/components/recovery/RecoveryLoaderContext'

import txDetailsCss from '@/components/transactions/TxDetails/styles.module.css'
import summaryCss from '@/components/transactions/TxDetails/Summary/styles.module.css'
Expand Down
5 changes: 2 additions & 3 deletions src/components/recovery/RecoveryList/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,12 @@ import type { ReactElement } from 'react'

import { TxListGrid } from '@/components/transactions/TxList'
import { RecoveryListItem } from '@/components/recovery/RecoveryListItem'
import { selectRecoveryQueues } from '@/store/recoverySlice'
import { useAppSelector } from '@/store'
import { useRecoveryQueue } from '@/hooks/useRecoveryQueue'

import labelCss from '@/components/transactions/GroupLabel/styles.module.css'

export function RecoveryList(): ReactElement | null {
const queue = useAppSelector(selectRecoveryQueues)
const queue = useRecoveryQueue()

if (queue.length === 0) {
return null
Expand Down
2 changes: 1 addition & 1 deletion src/components/recovery/RecoveryListItem/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import type { ReactElement } from 'react'
import txListItemCss from '@/components/transactions/TxListItem/styles.module.css'
import { RecoverySummary } from '../RecoverySummary'
import { RecoveryDetails } from '../RecoveryDetails'
import type { RecoveryQueueItem } from '@/store/recoverySlice'
import type { RecoveryQueueItem } from '@/components/recovery/RecoveryLoaderContext'

export function RecoveryListItem({ item }: { item: RecoveryQueueItem }): ReactElement {
return (
Expand Down
127 changes: 127 additions & 0 deletions src/components/recovery/RecoveryLoaderContext/__tests__/index.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
import { faker } from '@faker-js/faker'
import { useContext } from 'react'

import { useCurrentChain, useHasFeature } from '@/hooks/useChains'
import useSafeInfo from '@/hooks/useSafeInfo'
import { useWeb3ReadOnly } from '@/hooks/wallets/web3'
import { getDelayModifiers } from '@/services/recovery/delay-modifier'
import { getRecoveryState } from '@/services/recovery/recovery-state'
import { txDispatch, TxEvent } from '@/services/tx/txEvents'
import { chainBuilder } from '@/tests/builders/chains'
import { addressExBuilder, safeInfoBuilder } from '@/tests/builders/safe'
import { act, fireEvent, render, waitFor } from '@/tests/test-utils'
import { RecoveryLoaderContext, RecoveryLoaderProvider } from '..'
import { getTxDetails } from '@/services/tx/txDetails'

jest.mock('@/services/recovery/delay-modifier')
jest.mock('@/services/recovery/recovery-state')

const mockGetDelayModifiers = getDelayModifiers as jest.MockedFunction<typeof getDelayModifiers>
const mockGetRecoveryState = getRecoveryState as jest.MockedFunction<typeof getRecoveryState>

jest.mock('@/hooks/useSafeInfo')
jest.mock('@/hooks/wallets/web3')
jest.mock('@/hooks/useChains')
jest.mock('@/services/tx/txDetails')

const mockUseSafeInfo = useSafeInfo as jest.MockedFunction<typeof useSafeInfo>
const mockUseWeb3ReadOnly = useWeb3ReadOnly as jest.MockedFunction<typeof useWeb3ReadOnly>
const mockUseCurrentChain = useCurrentChain as jest.MockedFunction<typeof useCurrentChain>
const mockUseHasFeature = useHasFeature as jest.MockedFunction<typeof useHasFeature>
const mockGetTxDetails = getTxDetails as jest.MockedFunction<typeof getTxDetails>

describe('RecoveryLoaderContext', () => {
beforeEach(() => {
jest.clearAllMocks()

// Clear memoization cache
getTxDetails.cache.clear?.()
})

it('should refetch manually calling it', async () => {
mockUseHasFeature.mockReturnValue(true)
const provider = {}
mockUseWeb3ReadOnly.mockReturnValue(provider as any)
const chainId = '5'
const safe = safeInfoBuilder()
.with({ chainId, modules: [addressExBuilder().build()] })
.build()
const safeInfo = { safe, safeAddress: safe.address.value }
mockUseSafeInfo.mockReturnValue(safeInfo as any)
const chain = chainBuilder().build()
mockUseCurrentChain.mockReturnValue(chain)
const delayModifiers = [{}]
mockGetDelayModifiers.mockResolvedValue(delayModifiers as any)

function Test() {
const { refetch } = useContext(RecoveryLoaderContext)

return <button onClick={refetch}>Refetch</button>
}

const { queryByText } = render(
<RecoveryLoaderProvider>
<Test />
</RecoveryLoaderProvider>,
)

await waitFor(() => {
expect(mockGetDelayModifiers).toHaveBeenCalledTimes(1)
expect(mockGetRecoveryState).toHaveBeenCalledTimes(1)
})

act(() => {
fireEvent.click(queryByText('Refetch')!)
})

await waitFor(() => {
expect(mockGetRecoveryState).toHaveBeenCalledTimes(2)
})

expect(mockGetDelayModifiers).toHaveBeenCalledTimes(1)
})

it('should refetch when interacting with a Delay Modifier', async () => {
mockUseHasFeature.mockReturnValue(true)
const provider = {}
mockUseWeb3ReadOnly.mockReturnValue(provider as any)
const chainId = '5'
const safe = safeInfoBuilder()
.with({ chainId, modules: [addressExBuilder().build()] })
.build()
const safeInfo = { safe, safeAddress: safe.address.value }
mockUseSafeInfo.mockReturnValue(safeInfo as any)
const chain = chainBuilder().build()
mockUseCurrentChain.mockReturnValue(chain)
const delayModifierAddress = faker.finance.ethereumAddress()
mockGetDelayModifiers.mockResolvedValue([{ address: delayModifierAddress } as any])
mockGetTxDetails.mockResolvedValue({ txData: { to: { value: delayModifierAddress } } } as any)

render(
<RecoveryLoaderProvider>
<></>
</RecoveryLoaderProvider>,
)

await waitFor(() => {
expect(mockGetDelayModifiers).toHaveBeenCalledTimes(1)
expect(mockGetRecoveryState).toHaveBeenCalledTimes(1)
})

const txId = faker.string.alphanumeric()

act(() => {
txDispatch(TxEvent.PROCESSED, {
txId,
safeAddress: faker.finance.ethereumAddress(),
})
})

await waitFor(() => {
expect(mockGetTxDetails).toHaveBeenCalledTimes(1)
expect(mockGetTxDetails).toHaveBeenNthCalledWith(1, txId, safe.chainId)

expect(mockGetRecoveryState).toHaveBeenCalledTimes(2)
})
})
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
import { useHasFeature } from '@/hooks/useChains'
import useSafeInfo from '@/hooks/useSafeInfo'
import { useWeb3ReadOnly } from '@/hooks/wallets/web3'
import { getSpendingLimitModuleAddress } from '@/services/contracts/spendingLimitContracts'
import { getDelayModifiers } from '@/services/recovery/delay-modifier'
import { addressExBuilder, safeInfoBuilder } from '@/tests/builders/safe'
import { act, renderHook } from '@/tests/test-utils'
import { useDelayModifiers } from '../useDelayModifiers'

jest.mock('@/services/recovery/delay-modifier')

const mockGetDelayModifiers = getDelayModifiers as jest.MockedFunction<typeof getDelayModifiers>

jest.mock('@/hooks/useSafeInfo')
jest.mock('@/hooks/wallets/web3')
jest.mock('@/hooks/useChains')

const mockUseSafeInfo = useSafeInfo as jest.MockedFunction<typeof useSafeInfo>
const mockUseWeb3ReadOnly = useWeb3ReadOnly as jest.MockedFunction<typeof useWeb3ReadOnly>
const mockUseHasFeature = useHasFeature as jest.MockedFunction<typeof useHasFeature>

describe('useDelayModifiers', () => {
beforeEach(() => {
jest.clearAllMocks()
})

it('should not fetch if the current chain does not support Delay Modifiers', async () => {
jest.useFakeTimers()

mockUseHasFeature.mockReturnValue(false)
const provider = {}
mockUseWeb3ReadOnly.mockReturnValue(provider as any)
const safe = safeInfoBuilder().build()
const safeInfo = { safe, safeAddress: safe.address.value }
mockUseSafeInfo.mockReturnValue(safeInfo as any)

const { result } = renderHook(() => useDelayModifiers())

// Give enough time for loading to occur, if it will
await act(async () => {
jest.advanceTimersByTime(10)
})

expect(result.current).toEqual([undefined, undefined, false])
expect(mockGetDelayModifiers).not.toHaveBeenCalledTimes(1)

jest.useRealTimers()
})

it('should not fetch is there is no provider', async () => {
jest.useFakeTimers()

mockUseHasFeature.mockReturnValue(true)
mockUseWeb3ReadOnly.mockReturnValue(undefined)
const safe = safeInfoBuilder().build()
const safeInfo = { safe, safeAddress: safe.address.value }
mockUseSafeInfo.mockReturnValue(safeInfo as any)

const { result } = renderHook(() => useDelayModifiers())

// Give enough time for loading to occur, if it will
await act(async () => {
jest.advanceTimersByTime(10)
})

expect(result.current).toEqual([undefined, undefined, false])
expect(mockGetDelayModifiers).not.toHaveBeenCalledTimes(1)

jest.useRealTimers()
})

it('should not fetch if there is no Safe modules enabled', async () => {
jest.useFakeTimers()

mockUseHasFeature.mockReturnValue(true)
const provider = {}
mockUseWeb3ReadOnly.mockReturnValue(provider as any)
const safe = safeInfoBuilder().with({ modules: [] }).build()
const safeInfo = { safe, safeAddress: safe.address.value }
mockUseSafeInfo.mockReturnValue(safeInfo as any)

const { result } = renderHook(() => useDelayModifiers())

// Give enough time for loading to occur, if it will
await act(async () => {
jest.advanceTimersByTime(10)
})

expect(result.current).toEqual([undefined, undefined, false])
expect(mockGetDelayModifiers).not.toHaveBeenCalledTimes(1)

jest.useRealTimers()
})

it('should not fetch if only the spending limit is enabled', async () => {
jest.useFakeTimers()

mockUseHasFeature.mockReturnValue(true)
const provider = {}
mockUseWeb3ReadOnly.mockReturnValue(provider as any)
const chainId = '5'
const safe = safeInfoBuilder()
.with({ chainId, modules: [{ value: getSpendingLimitModuleAddress(chainId)! }] })
.build()
const safeInfo = { safe, safeAddress: safe.address.value }
mockUseSafeInfo.mockReturnValue(safeInfo as any)

const { result } = renderHook(() => useDelayModifiers())

// Give enough time for loading to occur, if it will
await act(async () => {
jest.advanceTimersByTime(10)
})

expect(result.current).toEqual([undefined, undefined, false])
expect(mockGetDelayModifiers).not.toHaveBeenCalledTimes(1)

jest.useRealTimers()
})

it('should otherwise fetch', async () => {
mockUseHasFeature.mockReturnValue(true)
const provider = {}
mockUseWeb3ReadOnly.mockReturnValue(provider as any)
const chainId = '5'
const safe = safeInfoBuilder()
.with({ chainId, modules: [addressExBuilder().build()] })
.build()
const safeInfo = { safe, safeAddress: safe.address.value }
mockUseSafeInfo.mockReturnValue(safeInfo as any)

renderHook(() => useDelayModifiers())

expect(mockGetDelayModifiers).toHaveBeenCalledTimes(1)
})
})
Loading

0 comments on commit f982e68

Please sign in to comment.