From 02c99b678083c939438b23e23eb786d9da068670 Mon Sep 17 00:00:00 2001 From: Manuel Gellfart Date: Wed, 12 Jun 2024 09:08:56 +0200 Subject: [PATCH] feat: add feature toggle for zodiac roles integration (#3830) * feat: add feature toggle for zodiac roles integration * refactor: rewrite mock --- .../__test__/PermissionsCheck.test.tsx | 67 ++++++++++++++----- .../PermissionsCheck/hooks.ts | 7 +- .../__tests__/useRecoveryState.test.tsx | 1 - src/utils/chains.ts | 1 + 4 files changed, 55 insertions(+), 21 deletions(-) diff --git a/src/components/tx/SignOrExecuteForm/PermissionsCheck/__test__/PermissionsCheck.test.tsx b/src/components/tx/SignOrExecuteForm/PermissionsCheck/__test__/PermissionsCheck.test.tsx index 48063d0b93..814c6faca9 100644 --- a/src/components/tx/SignOrExecuteForm/PermissionsCheck/__test__/PermissionsCheck.test.tsx +++ b/src/components/tx/SignOrExecuteForm/PermissionsCheck/__test__/PermissionsCheck.test.tsx @@ -14,6 +14,9 @@ import { type OnboardAPI } from '@web3-onboard/core' import { AbiCoder, ZeroAddress, encodeBytes32String } from 'ethers' import PermissionsCheck from '..' import * as hooksModule from '../hooks' +import { FEATURES } from '@/utils/chains' +import { chainBuilder } from '@/tests/builders/chains' +import { useHasFeature } from '@/hooks/useChains' // We assume that CheckWallet always returns true jest.mock('@/components/common/CheckWallet', () => ({ @@ -23,16 +26,25 @@ jest.mock('@/components/common/CheckWallet', () => ({ }, })) -// mock useCurrentChain & useHasFeature +const mockChain = chainBuilder() + // @ts-expect-error - we are using a local FEATURES enum + .with({ features: [FEATURES.ZODIAC_ROLES, FEATURES.EIP1559] }) + .with({ chainId: '1' }) + .with({ shortName: 'eth' }) + .with({ chainName: 'Ethereum' }) + .with({ transactionService: 'https://tx.service.mock' }) + .build() + +// mock useCurrentChain jest.mock('@/hooks/useChains', () => ({ - useCurrentChain: jest.fn(() => ({ - shortName: 'eth', - chainId: '1', - chainName: 'Ethereum', - features: [], - transactionService: 'https://tx.service.mock', - })), - useHasFeature: jest.fn(() => true), // used to check for EIP1559 support + __esModule: true, + ...jest.requireActual('@/hooks/useChains'), + useCurrentChain: jest.fn(() => mockChain), + useHasFeature: jest.fn(), +})) + +jest.mock('@/hooks/useChainId', () => ({ + useChainId: jest.fn().mockReturnValue(() => '1'), })) // mock getModuleTransactionId @@ -72,15 +84,15 @@ describe('PermissionsCheck', () => { beforeEach(() => { jest.clearAllMocks() - - // Safe info - jest.spyOn(useSafeInfoHook, 'default').mockImplementation(() => ({ - safe: SAFE_INFO, - safeAddress: SAFE_INFO.address.value, - safeError: undefined, - safeLoading: false, - safeLoaded: true, - })) + ;(useHasFeature as jest.Mock).mockImplementation((feature) => mockChain.features.includes(feature)), + // Safe info + jest.spyOn(useSafeInfoHook, 'default').mockImplementation(() => ({ + safe: SAFE_INFO, + safeAddress: SAFE_INFO.address.value, + safeError: undefined, + safeLoading: false, + safeLoaded: true, + })) // Roles mod fetching @@ -100,6 +112,25 @@ describe('PermissionsCheck', () => { jest.spyOn(hooksModule, 'pollModuleTransactionId').mockReturnValue(Promise.resolve('i1234567890')) }) + it('only fetch roles and show the card if the feature is enabled', async () => { + ;(useHasFeature as jest.Mock).mockImplementation((feature) => feature !== FEATURES.ZODIAC_ROLES) + mockConnectedWalletAddress(SAFE_INFO.owners[0].value) // connect as safe owner (not a role member) + + const safeTx = createMockSafeTransaction({ + to: ZeroAddress, + data: '0xd0e30db0', // deposit() + value: AbiCoder.defaultAbiCoder().encode(['uint256'], [123]), + operation: OperationType.Call, + }) + + const { queryByText } = render() + + // the card is not shown + expect(queryByText('Execute without confirmations')).not.toBeInTheDocument() + + expect(fetchRolesModMock).not.toHaveBeenCalled() + }) + it('only shows the card when the user is a member of any role', async () => { mockConnectedWalletAddress(SAFE_INFO.owners[0].value) // connect as safe owner (not a role member) diff --git a/src/components/tx/SignOrExecuteForm/PermissionsCheck/hooks.ts b/src/components/tx/SignOrExecuteForm/PermissionsCheck/hooks.ts index 18e07f5af7..6922a96787 100644 --- a/src/components/tx/SignOrExecuteForm/PermissionsCheck/hooks.ts +++ b/src/components/tx/SignOrExecuteForm/PermissionsCheck/hooks.ts @@ -18,6 +18,8 @@ import { OperationType, type Transaction, type MetaTransactionData } from '@safe import { type JsonRpcProvider } from 'ethers' import { KnownContracts, getModuleInstance } from '@gnosis.pm/zodiac' import useWallet from '@/hooks/wallets/useWallet' +import { useHasFeature } from '@/hooks/useChains' +import { FEATURES } from '@/utils/chains' const ROLES_V2_SUPPORTED_CHAINS = Object.keys(chains) @@ -26,9 +28,10 @@ const ROLES_V2_SUPPORTED_CHAINS = Object.keys(chains) */ export const useRolesMods = () => { const { safe } = useSafeInfo() + const isFeatureEnabled = useHasFeature(FEATURES.ZODIAC_ROLES) const [data] = useAsync(async () => { - if (!ROLES_V2_SUPPORTED_CHAINS.includes(safe.chainId)) return [] + if (!ROLES_V2_SUPPORTED_CHAINS.includes(safe.chainId) || !isFeatureEnabled) return [] const safeModules = safe.modules || [] const rolesMods = await Promise.all( @@ -44,7 +47,7 @@ export const useRolesMods = () => { mod.avatar === safe.address.value.toLowerCase() && mod.roles.length > 0, ) - }, [safe]) + }, [safe, isFeatureEnabled]) return data } diff --git a/src/features/recovery/components/RecoveryContext/__tests__/useRecoveryState.test.tsx b/src/features/recovery/components/RecoveryContext/__tests__/useRecoveryState.test.tsx index 4296ddbd9a..55099f081d 100644 --- a/src/features/recovery/components/RecoveryContext/__tests__/useRecoveryState.test.tsx +++ b/src/features/recovery/components/RecoveryContext/__tests__/useRecoveryState.test.tsx @@ -25,7 +25,6 @@ jest.mock('@/hooks/useSafeInfo') jest.mock('@/hooks/wallets/web3') jest.mock('@/hooks/useChains') jest.mock('@/hooks/useTxHistory') -jest.mock('@/hooks/useChains') const mockUseSafeInfo = useSafeInfo as jest.MockedFunction const mockUseWeb3ReadOnly = useWeb3ReadOnly as jest.MockedFunction diff --git a/src/utils/chains.ts b/src/utils/chains.ts index 0485ac42b0..2cebd45e8b 100644 --- a/src/utils/chains.ts +++ b/src/utils/chains.ts @@ -23,6 +23,7 @@ export enum FEATURES { SAP_BANNER = 'SAP_BANNER', NATIVE_SWAPS = 'NATIVE_SWAPS', RELAY_NATIVE_SWAPS = 'RELAY_NATIVE_SWAPS', + ZODIAC_ROLES = 'ZODIAC_ROLES', } export const FeatureRoutes = {