diff --git a/package.json b/package.json
index 20067fa2c5..579b02ef5a 100644
--- a/package.json
+++ b/package.json
@@ -89,6 +89,7 @@
"semver": "^7.5.2"
},
"devDependencies": {
+ "@faker-js/faker": "^8.1.0",
"@next/bundle-analyzer": "^13.1.1",
"@openzeppelin/contracts": "^4.9.2",
"@safe-global/safe-core-sdk-types": "^1.9.1",
diff --git a/src/components/common/AddressInput/index.test.tsx b/src/components/common/AddressInput/index.test.tsx
index 4a87f46d5d..e5e0df9201 100644
--- a/src/components/common/AddressInput/index.test.tsx
+++ b/src/components/common/AddressInput/index.test.tsx
@@ -4,15 +4,16 @@ import { useForm, FormProvider } from 'react-hook-form'
import AddressInput, { type AddressInputProps } from '.'
import { useCurrentChain } from '@/hooks/useChains'
import useNameResolver from '@/components/common/AddressInput/useNameResolver'
+import { chainBuilder } from '@/tests/builders/chains'
+import { FEATURES } from '@safe-global/safe-gateway-typescript-sdk'
+
+const mockChain = chainBuilder()
+ .with({ features: [FEATURES.DOMAIN_LOOKUP] })
+ .build()
// mock useCurrentChain
jest.mock('@/hooks/useChains', () => ({
- useCurrentChain: jest.fn(() => ({
- shortName: 'gor',
- chainId: '5',
- chainName: 'Goerli',
- features: ['DOMAIN_LOOKUP'],
- })),
+ useCurrentChain: jest.fn(() => mockChain),
}))
// mock useNameResolver
@@ -92,7 +93,7 @@ describe('AddressInput tests', () => {
)
act(() => {
- fireEvent.change(input, { target: { value: 'gor:0x123' } })
+ fireEvent.change(input, { target: { value: `${mockChain.shortName}:0x123` } })
jest.advanceTimersByTime(1000)
})
@@ -103,14 +104,14 @@ describe('AddressInput tests', () => {
const { input, utils } = setup('', (val) => `${val} is wrong`)
act(() => {
- fireEvent.change(input, { target: { value: `gor:${TEST_ADDRESS_A}` } })
+ fireEvent.change(input, { target: { value: `${mockChain.shortName}:${TEST_ADDRESS_A}` } })
jest.advanceTimersByTime(1000)
})
await waitFor(() => expect(utils.getByLabelText(`${TEST_ADDRESS_A} is wrong`, { exact: false })).toBeDefined())
act(() => {
- fireEvent.change(input, { target: { value: `gor:${TEST_ADDRESS_B}` } })
+ fireEvent.change(input, { target: { value: `${mockChain.shortName}:${TEST_ADDRESS_B}` } })
jest.advanceTimersByTime(1000)
})
@@ -126,7 +127,7 @@ describe('AddressInput tests', () => {
})
act(() => {
- fireEvent.change(input, { target: { value: `gor:${TEST_ADDRESS_A}` } })
+ fireEvent.change(input, { target: { value: `${mockChain.shortName}:${TEST_ADDRESS_A}` } })
jest.advanceTimersByTime(1000)
})
@@ -195,17 +196,13 @@ describe('AddressInput tests', () => {
})
it('should not show the adornment prefix when the value contains correct prefix', async () => {
- ;(useCurrentChain as jest.Mock).mockImplementation(() => ({
- shortName: 'gor',
- chainId: '5',
- chainName: 'Goerli',
- features: [],
- }))
+ const mockChain = chainBuilder().with({ features: [] }).build()
+ ;(useCurrentChain as jest.Mock).mockImplementation(() => mockChain)
- const { input } = setup(`gor:${TEST_ADDRESS_A}`)
+ const { input } = setup(`${mockChain.shortName}:${TEST_ADDRESS_A}`)
act(() => {
- fireEvent.change(input, { target: { value: `gor:${TEST_ADDRESS_B}` } })
+ fireEvent.change(input, { target: { value: `${mockChain.shortName}:${TEST_ADDRESS_B}` } })
})
await waitFor(() => expect(input.previousElementSibling?.textContent).toBe(''))
diff --git a/src/components/common/CheckWallet/index.test.tsx b/src/components/common/CheckWallet/index.test.tsx
index bb7ae1c468..c10a0d9dd7 100644
--- a/src/components/common/CheckWallet/index.test.tsx
+++ b/src/components/common/CheckWallet/index.test.tsx
@@ -3,6 +3,7 @@ import CheckWallet from '.'
import useIsOnlySpendingLimitBeneficiary from '@/hooks/useIsOnlySpendingLimitBeneficiary'
import useIsSafeOwner from '@/hooks/useIsSafeOwner'
import useWallet from '@/hooks/wallets/useWallet'
+import { chainBuilder } from '@/tests/builders/chains'
// mock useWallet
jest.mock('@/hooks/wallets/useWallet', () => ({
@@ -27,9 +28,7 @@ jest.mock('@/hooks/useIsOnlySpendingLimitBeneficiary', () => ({
// mock useCurrentChain
jest.mock('@/hooks/useChains', () => ({
__esModule: true,
- useCurrentChain: jest.fn(() => ({
- chainName: 'Optimism',
- })),
+ useCurrentChain: jest.fn(() => chainBuilder().build()),
}))
const renderButton = () => render({(isOk) => })
diff --git a/src/components/new-safe/create/steps/StatusStep/__tests__/useSafeCreation.test.ts b/src/components/new-safe/create/steps/StatusStep/__tests__/useSafeCreation.test.ts
index 1ad617b11a..b1f29b0991 100644
--- a/src/components/new-safe/create/steps/StatusStep/__tests__/useSafeCreation.test.ts
+++ b/src/components/new-safe/create/steps/StatusStep/__tests__/useSafeCreation.test.ts
@@ -9,13 +9,13 @@ import * as txMonitor from '@/services/tx/txMonitor'
import * as usePendingSafe from '@/components/new-safe/create/steps/StatusStep/usePendingSafe'
import { JsonRpcProvider, Web3Provider } from '@ethersproject/providers'
import type { ConnectedWallet } from '@/hooks/wallets/useOnboard'
-import type { ChainInfo } from '@safe-global/safe-gateway-typescript-sdk'
+import { chainBuilder } from '@/tests/builders/chains'
import { BigNumber } from '@ethersproject/bignumber'
import { waitFor } from '@testing-library/react'
import type Safe from '@safe-global/safe-core-sdk'
import { hexZeroPad } from 'ethers/lib/utils'
import type CompatibilityFallbackHandlerEthersContract from '@safe-global/safe-ethers-lib/dist/src/contracts/CompatibilityFallbackHandler/CompatibilityFallbackHandlerEthersContract'
-import { FEATURES } from '@/utils/chains'
+import { FEATURES } from '@safe-global/safe-gateway-typescript-sdk'
import * as gasPrice from '@/hooks/useGasPrice'
const mockSafeInfo = {
@@ -44,10 +44,7 @@ describe('useSafeCreation', () => {
beforeEach(() => {
jest.resetAllMocks()
- const mockChain = {
- chainId: '4',
- features: [],
- } as unknown as ChainInfo
+ const mockChain = chainBuilder().with({ features: [] }).build()
jest.spyOn(web3, 'useWeb3').mockImplementation(() => mockProvider)
jest.spyOn(web3, 'getWeb3ReadOnly').mockImplementation(() => mockProvider)
@@ -89,12 +86,10 @@ describe('useSafeCreation', () => {
false,
])
- jest.spyOn(chain, 'useCurrentChain').mockImplementation(
- () =>
- ({
- chainId: '4',
- features: [FEATURES.EIP1559],
- } as unknown as ChainInfo),
+ jest.spyOn(chain, 'useCurrentChain').mockImplementation(() =>
+ chainBuilder()
+ .with({ features: [FEATURES.EIP1559] })
+ .build(),
)
jest.spyOn(usePendingSafe, 'usePendingSafe').mockReturnValue([mockPendingSafe, mockSetPendingSafe])
diff --git a/src/components/tx-flow/flows/SignMessage/SignMessage.test.tsx b/src/components/tx-flow/flows/SignMessage/SignMessage.test.tsx
index 655e32e8b6..f76a11f0a5 100644
--- a/src/components/tx-flow/flows/SignMessage/SignMessage.test.tsx
+++ b/src/components/tx-flow/flows/SignMessage/SignMessage.test.tsx
@@ -1,5 +1,5 @@
import { hexlify, hexZeroPad, toUtf8Bytes } from 'ethers/lib/utils'
-import type { ChainInfo, SafeInfo, SafeMessage, SafeMessageListPage } from '@safe-global/safe-gateway-typescript-sdk'
+import type { SafeInfo, SafeMessage, SafeMessageListPage } from '@safe-global/safe-gateway-typescript-sdk'
import { SafeMessageListItemType } from '@safe-global/safe-gateway-typescript-sdk'
import SignMessage from './SignMessage'
@@ -17,6 +17,7 @@ import type { EIP1193Provider, WalletState, AppState, OnboardAPI } from '@web3-o
import { generateSafeMessageHash } from '@/utils/safe-messages'
import { getSafeMessage } from '@safe-global/safe-gateway-typescript-sdk'
import useSafeMessages from '@/hooks/messages/useSafeMessages'
+import { chainBuilder } from '@/tests/builders/chains'
jest.mock('@safe-global/safe-gateway-typescript-sdk', () => ({
...jest.requireActual('@safe-global/safe-gateway-typescript-sdk'),
@@ -400,7 +401,7 @@ describe('SignMessage', () => {
jest.spyOn(onboard, 'default').mockReturnValue(mockOnboard)
jest.spyOn(useIsSafeOwnerHook, 'default').mockImplementation(() => true)
jest.spyOn(useIsWrongChainHook, 'default').mockImplementation(() => true)
- jest.spyOn(useChainsHook, 'useCurrentChain').mockReturnValue({ chainName: 'Goerli' } as ChainInfo)
+ jest.spyOn(useChainsHook, 'useCurrentChain').mockReturnValue(chainBuilder().build())
const { getByText } = render(
{
+ const mockChain = chainBuilder()
+ // @ts-expect-error - using local FEATURES enum
+ .with({ features: [FEATURES.RELAYING] })
+ .build()
beforeEach(() => {
- jest
- .spyOn(useChains, 'useCurrentChain')
- .mockReturnValue({ chainId: '5', features: FEATURES.RELAYING } as unknown as ChainInfo)
+ jest.spyOn(useChains, 'useCurrentChain').mockReturnValue(mockChain)
jest.spyOn(useSafeInfo, 'default').mockReturnValue({
safe: {
txHistoryTag: '0',
@@ -41,7 +43,7 @@ describe('fetch remaining relays hooks', () => {
global.fetch = jest.fn()
const mockFetch = jest.spyOn(global, 'fetch')
- const url = `${SAFE_RELAY_SERVICE_URL}/5/${SAFE_ADDRESS}`
+ const url = `${SAFE_RELAY_SERVICE_URL}/${mockChain.chainId}/${SAFE_ADDRESS}`
renderHook(() => useRelaysBySafe())
expect(mockFetch).toHaveBeenCalledTimes(1)
@@ -49,7 +51,7 @@ describe('fetch remaining relays hooks', () => {
})
it('should not do a network request if chain does not support relay', () => {
- jest.spyOn(useChains, 'useCurrentChain').mockReturnValue({ chainId: '5', features: [] } as unknown as ChainInfo)
+ jest.spyOn(useChains, 'useCurrentChain').mockReturnValue(chainBuilder().with({ features: [] }).build())
global.fetch = jest.fn()
const mockFetch = jest.spyOn(global, 'fetch')
@@ -151,7 +153,7 @@ describe('fetch remaining relays hooks', () => {
})
it('should not do a network request if chain does not support relay', () => {
- jest.spyOn(useChains, 'useCurrentChain').mockReturnValue({ chainId: '5', features: [] } as unknown as ChainInfo)
+ jest.spyOn(useChains, 'useCurrentChain').mockReturnValue(chainBuilder().with({ features: [] }).build())
global.fetch = jest
.fn()
.mockResolvedValue({
diff --git a/src/hooks/useMnemonicName/useMnemonicName.test.ts b/src/hooks/useMnemonicName/useMnemonicName.test.ts
index 9a126982e7..a47b206aff 100644
--- a/src/hooks/useMnemonicName/useMnemonicName.test.ts
+++ b/src/hooks/useMnemonicName/useMnemonicName.test.ts
@@ -1,11 +1,12 @@
import { getRandomName, useMnemonicName, useMnemonicSafeName } from '.'
import { renderHook } from '@/tests/test-utils'
+import { chainBuilder } from '@/tests/builders/chains'
+
+const mockChain = chainBuilder().build()
// Mock useCurrentChain hook
jest.mock('@/hooks/useChains', () => ({
- useCurrentChain: () => ({
- chainName: 'Rinkeby',
- }),
+ useCurrentChain: () => mockChain,
}))
describe('useMnemonicName tests', () => {
@@ -37,6 +38,7 @@ describe('useMnemonicName tests', () => {
it('should return a random safe name', () => {
const { result } = renderHook(() => useMnemonicSafeName())
- expect(result.current).toMatch(/^[a-z-]+-rinkeby-safe$/)
+ const regex = new RegExp(`^[a-z-]+-${mockChain.chainName.toLowerCase()}-safe$`)
+ expect(result.current).toMatch(regex)
})
})
diff --git a/src/tests/Builder.ts b/src/tests/Builder.ts
new file mode 100644
index 0000000000..139302194a
--- /dev/null
+++ b/src/tests/Builder.ts
@@ -0,0 +1,30 @@
+export interface IBuilder {
+ with(override: Partial): IBuilder
+
+ build(): T
+}
+
+export class Builder implements IBuilder {
+ private constructor(private target: Partial) {}
+
+ /**
+ * Returns a new {@link Builder} with the property {@link key} set to {@link value}.
+ *
+ * @param override - the override value to apply
+ */
+ with(override: Partial): IBuilder {
+ const target: Partial = { ...this.target, ...override }
+ return new Builder(target)
+ }
+
+ /**
+ * Returns an instance of T with the values that were set so far
+ */
+ build(): T {
+ return this.target as T
+ }
+
+ public static new(): IBuilder {
+ return new Builder({})
+ }
+}
diff --git a/src/tests/builders/chains.ts b/src/tests/builders/chains.ts
new file mode 100644
index 0000000000..e4327ba112
--- /dev/null
+++ b/src/tests/builders/chains.ts
@@ -0,0 +1,121 @@
+import { faker } from '@faker-js/faker'
+import { RPC_AUTHENTICATION, GAS_PRICE_TYPE } from '@safe-global/safe-gateway-typescript-sdk'
+import type {
+ BlockExplorerUriTemplate,
+ ChainInfo,
+ GasPriceFixed,
+ GasPriceFixedEIP1559,
+ GasPriceOracle,
+ GasPriceUnknown,
+ NativeCurrency,
+ RpcUri,
+ Theme,
+} from '@safe-global/safe-gateway-typescript-sdk'
+
+import { Builder } from '@/tests/Builder'
+import { FEATURES } from '@/utils/chains'
+import { generateRandomArray } from './utils'
+import type { IBuilder } from '@/tests/Builder'
+import type useChains from '@/hooks/useChains'
+
+const rpcUriBuilder = (): IBuilder => {
+ return Builder.new().with({
+ authentication: RPC_AUTHENTICATION.NO_AUTHENTICATION,
+ value: faker.internet.url({ appendSlash: false }),
+ })
+}
+
+const blockExplorerUriTemplateBuilder = (): IBuilder => {
+ return Builder.new().with({
+ address: faker.finance.ethereumAddress(),
+ txHash: faker.string.hexadecimal(),
+ api: faker.internet.url({ appendSlash: false }),
+ })
+}
+
+const nativeCurrencyBuilder = (): IBuilder => {
+ return Builder.new().with({
+ name: faker.finance.currencyName(),
+ symbol: faker.finance.currencySymbol(),
+ decimals: 18,
+ logoUri: faker.internet.url({ appendSlash: false }),
+ })
+}
+
+const themeBuilder = (): IBuilder => {
+ return Builder.new().with({
+ textColor: faker.color.rgb(),
+ backgroundColor: faker.color.rgb(),
+ })
+}
+
+const gasPriceFixedBuilder = (): IBuilder => {
+ return Builder.new().with({
+ type: GAS_PRICE_TYPE.FIXED,
+ weiValue: faker.string.numeric(),
+ })
+}
+
+const gasPriceFixedEIP1559Builder = (): IBuilder => {
+ return Builder.new().with({
+ type: GAS_PRICE_TYPE.FIXED_1559,
+ maxFeePerGas: faker.string.numeric(),
+ maxPriorityFeePerGas: faker.string.numeric(),
+ })
+}
+
+const gasPriceOracleBuilder = (): IBuilder => {
+ return Builder.new().with({
+ type: GAS_PRICE_TYPE.ORACLE,
+ uri: faker.internet.url({ appendSlash: false }),
+ gasParameter: faker.word.sample(),
+ gweiFactor: faker.string.numeric(),
+ })
+}
+
+const gasPriceOracleUnknownBuilder = (): IBuilder => {
+ return Builder.new().with({
+ type: GAS_PRICE_TYPE.UNKNOWN,
+ })
+}
+
+const getRandomGasPriceBuilder = () => {
+ const gasPriceBuilders = [
+ gasPriceFixedBuilder(),
+ gasPriceFixedEIP1559Builder(),
+ gasPriceOracleBuilder(),
+ gasPriceOracleUnknownBuilder(),
+ ]
+
+ const randomIndex = Math.floor(Math.random() * gasPriceBuilders.length)
+ return gasPriceBuilders[randomIndex]
+}
+
+export const chainBuilder = (): IBuilder => {
+ return Builder.new().with({
+ chainId: faker.string.numeric(),
+ chainName: faker.word.sample(),
+ description: faker.word.words(),
+ l2: faker.datatype.boolean(),
+ shortName: faker.word.sample(),
+ rpcUri: rpcUriBuilder().build(),
+ safeAppsRpcUri: rpcUriBuilder().build(),
+ publicRpcUri: rpcUriBuilder().build(),
+ blockExplorerUriTemplate: blockExplorerUriTemplateBuilder().build(),
+ nativeCurrency: nativeCurrencyBuilder().build(),
+ transactionService: faker.internet.url({ appendSlash: false }),
+ theme: themeBuilder().build(),
+ gasPrice: generateRandomArray(() => getRandomGasPriceBuilder().build(), { min: 1, max: 4 }),
+ ensRegistryAddress: faker.finance.ethereumAddress(),
+ disabledWallets: generateRandomArray(() => faker.word.sample(), { min: 1, max: 10 }),
+ // @ts-expect-error - we are using a local FEATURES enum
+ features: generateRandomArray(() => faker.helpers.enumValue(FEATURES), { min: 1, max: 10 }),
+ })
+}
+
+export const useChainsBuilder = (): IBuilder> => {
+ return Builder.new>().with({
+ configs: generateRandomArray(() => chainBuilder().build(), { min: 1, max: 25 }),
+ loading: false,
+ })
+}
diff --git a/src/tests/builders/utils.ts b/src/tests/builders/utils.ts
new file mode 100644
index 0000000000..5892653303
--- /dev/null
+++ b/src/tests/builders/utils.ts
@@ -0,0 +1,6 @@
+import { faker } from '@faker-js/faker'
+import type { NumberModule } from '@faker-js/faker'
+
+export const generateRandomArray = (generator: () => T, options?: Parameters[0]): Array => {
+ return Array.from({ length: faker.number.int(options) }, generator)
+}
diff --git a/yarn.lock b/yarn.lock
index 5d610da99d..6b66bcaa40 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2263,6 +2263,11 @@
"@ethersproject/properties" "^5.7.0"
"@ethersproject/strings" "^5.7.0"
+"@faker-js/faker@^8.1.0":
+ version "8.1.0"
+ resolved "https://registry.yarnpkg.com/@faker-js/faker/-/faker-8.1.0.tgz#e14896f1c57af2495e341dc4c7bf04125c8aeafd"
+ integrity sha512-38DT60rumHfBYynif3lmtxMqMqmsOQIxQgEuPZxCk2yUYN0eqWpTACgxi0VpidvsJB8CRxCpvP7B3anK85FjtQ==
+
"@firebase/analytics-compat@0.2.6":
version "0.2.6"
resolved "https://registry.yarnpkg.com/@firebase/analytics-compat/-/analytics-compat-0.2.6.tgz#50063978c42f13eb800e037e96ac4b17236841f4"