diff --git a/.eslintrc.json b/.eslintrc.json index 81eafda606..63839322a5 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -6,6 +6,9 @@ "plugin:storybook/recommended" ], "parser": "@typescript-eslint/parser", + "parserOptions": { + "project": ["./tsconfig.json"] + }, "rules": { "@next/next/no-img-element": "off", "@next/next/google-font-display": "off", @@ -13,7 +16,9 @@ "@next/next/no-page-custom-font": "off", "unused-imports/no-unused-imports-ts": "error", "@typescript-eslint/consistent-type-imports": "error", + "@typescript-eslint/await-thenable": "error", "no-constant-condition": "warn", + "no-unused-vars": ["error", { "varsIgnorePattern": "^_" }], "react-hooks/exhaustive-deps": [ "warn", { @@ -28,7 +33,9 @@ "ignorePatterns": [ "node_modules/", ".next/", - ".github/" + ".github/", + "cypress/", + "src/types/contracts/" ], "plugins": [ "unused-imports", diff --git a/.github/workflows/unit-tests.yml b/.github/workflows/unit-tests.yml index c05edb1168..0eba7e19ce 100644 --- a/.github/workflows/unit-tests.yml +++ b/.github/workflows/unit-tests.yml @@ -12,6 +12,11 @@ concurrency: jobs: test: + permissions: + contents: write + checks: write + pull-requests: write + runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 @@ -19,7 +24,7 @@ jobs: - uses: ./.github/workflows/yarn - name: Annotations and coverage report - uses: ArtiomTr/jest-coverage-report-action@v2 + uses: ArtiomTr/jest-coverage-report-action@v2.3.1 with: skip-step: install annotations: failed-tests diff --git a/src/components/address-book/EntryDialog/index.tsx b/src/components/address-book/EntryDialog/index.tsx index 41616de15d..e2d3202776 100644 --- a/src/components/address-book/EntryDialog/index.tsx +++ b/src/components/address-book/EntryDialog/index.tsx @@ -7,8 +7,8 @@ import ModalDialog from '@/components/common/ModalDialog' import NameInput from '@/components/common/NameInput' import useChainId from '@/hooks/useChainId' import { useAppDispatch } from '@/store' -import { upsertAddressBookEntry, upsertMultichainAddressBookEntry } from '@/store/addressBookSlice' import madProps from '@/utils/mad-props' +import { upsertAddressBookEntries } from '@/store/addressBookSlice' export type AddressEntry = { name: string @@ -41,11 +41,7 @@ function EntryDialog({ const { handleSubmit, formState } = methods const submitCallback = handleSubmit((data: AddressEntry) => { - if (chainIds) { - dispatch(upsertMultichainAddressBookEntry({ ...data, chainIds })) - } else { - dispatch(upsertAddressBookEntry({ ...data, chainId: currentChainId })) - } + dispatch(upsertAddressBookEntries({ ...data, chainIds: chainIds ?? [currentChainId] })) handleClose() }) @@ -55,7 +51,12 @@ function EntryDialog({ } return ( - + 1} + >
diff --git a/src/components/address-book/ImportDialog/index.tsx b/src/components/address-book/ImportDialog/index.tsx index 7b83a40d8b..13262285e7 100644 --- a/src/components/address-book/ImportDialog/index.tsx +++ b/src/components/address-book/ImportDialog/index.tsx @@ -7,7 +7,7 @@ import type { ParseResult } from 'papaparse' import { type ReactElement, useState, type MouseEvent, useMemo } from 'react' import ModalDialog from '@/components/common/ModalDialog' -import { upsertAddressBookEntry } from '@/store/addressBookSlice' +import { upsertAddressBookEntries } from '@/store/addressBookSlice' import { useAppDispatch } from '@/store' import css from './styles.module.css' @@ -60,7 +60,7 @@ const ImportDialog = ({ handleClose }: { handleClose: () => void }): ReactElemen for (const entry of entries) { const [address, name, chainId] = entry - dispatch(upsertAddressBookEntry({ address, name, chainId: chainId.trim() })) + dispatch(upsertAddressBookEntries({ address, name, chainIds: [chainId.trim()] })) } trackEvent({ ...ADDRESS_BOOK_EVENTS.IMPORT, label: entries.length }) @@ -114,7 +114,7 @@ const ImportDialog = ({ handleClose }: { handleClose: () => void }): ReactElemen }} > {/* https://github.com/Bunlong/react-papaparse/blob/master/src/useCSVReader.tsx */} - {({ getRootProps, acceptedFile, ProgressBar, getRemoveFileProps, Remove }: any) => { + {({ getRootProps, acceptedFile, getRemoveFileProps }: any) => { const { onClick } = getRemoveFileProps() const onRemove = (e: MouseEvent) => { diff --git a/src/components/common/AddressBookInput/index.test.tsx b/src/components/common/AddressBookInput/index.test.tsx index 449e0eead9..ee910e6dcc 100644 --- a/src/components/common/AddressBookInput/index.test.tsx +++ b/src/components/common/AddressBookInput/index.test.tsx @@ -1,4 +1,5 @@ -import { act, fireEvent, render, waitFor } from '@/tests/test-utils' +import { act } from 'react' +import { fireEvent, render, waitFor } from '@/tests/test-utils' import { FormProvider, useForm } from 'react-hook-form' import AddressBookInput from '.' import type { AddressInputProps } from '../AddressInput' @@ -147,12 +148,12 @@ describe('AddressBookInput', () => { expect(input).toHaveAttribute('aria-expanded', 'false') - await act(() => { + act(() => { fireEvent.mouseDown(input) fireEvent.mouseUp(input) }) - await act(() => { + act(() => { fireEvent.change(input, { target: { value: invalidAddress } }) jest.advanceTimersByTime(1000) }) @@ -160,7 +161,8 @@ describe('AddressBookInput', () => { await waitFor(() => expect(utils.getByLabelText(validationError, { exact: false })).toBeDefined()) const address = checksumAddress(faker.finance.ethereumAddress()) - await act(() => { + + act(() => { fireEvent.change(input, { target: { value: address } }) jest.advanceTimersByTime(1000) }) @@ -187,14 +189,14 @@ describe('AddressBookInput', () => { expect(input).toHaveAttribute('aria-expanded', 'false') - await act(() => { + act(() => { fireEvent.mouseDown(input) fireEvent.mouseUp(input) }) expect(input).toHaveAttribute('aria-expanded', 'true') - await act(() => { + act(() => { fireEvent.click(utils.getByText('InvalidAddress')) fireEvent.blur(input) jest.advanceTimersByTime(1000) @@ -206,7 +208,7 @@ describe('AddressBookInput', () => { }) // Clear the input by clicking on the readonly input - await act(() => { + act(() => { // first click clears input fireEvent.click(utils.getByLabelText(validationError, { exact: false })) }) @@ -215,13 +217,13 @@ describe('AddressBookInput', () => { const newInput = utils.getByLabelText(validationError, { exact: false }) expect(newInput).toBeVisible() - await act(() => { + act(() => { // mousedown opens autocompletion again fireEvent.mouseDown(newInput) fireEvent.mouseUp(newInput) }) - await act(() => { + act(() => { fireEvent.click(utils.getByText('ValidAddress')) fireEvent.blur(newInput) @@ -239,7 +241,7 @@ describe('AddressBookInput', () => { const { input, utils } = setup('', {}, undefined, true) const newAddress = checksumAddress(faker.finance.ethereumAddress()) - await act(() => { + act(() => { fireEvent.change(input, { target: { value: newAddress } }) jest.advanceTimersByTime(1000) }) @@ -253,7 +255,7 @@ describe('AddressBookInput', () => { }) const nameInput = utils.getByLabelText('Name', { exact: false }) - await act(() => { + act(() => { fireEvent.change(nameInput, { target: { value: 'Tim Testermann' } }) fireEvent.submit(nameInput) }) @@ -265,7 +267,7 @@ describe('AddressBookInput', () => { const { input, utils } = setup('', {}, undefined, false) const newAddress = checksumAddress(faker.finance.ethereumAddress()) - await act(() => { + act(() => { fireEvent.change(input, { target: { value: newAddress } }) jest.advanceTimersByTime(1000) }) diff --git a/src/components/common/AddressBookInput/index.tsx b/src/components/common/AddressBookInput/index.tsx index 3ae7b1f6b5..77be19a660 100644 --- a/src/components/common/AddressBookInput/index.tsx +++ b/src/components/common/AddressBookInput/index.tsx @@ -62,7 +62,8 @@ const AddressBookInput = ({ name, canAdd, ...props }: AddressInputProps & { canA ( + // eslint-disable-next-line + render={({ field: { ref, ...field } }) => ( { ;(useIsSafeOwner as jest.MockedFunction).mockReturnValueOnce(true) const renderButtonWithNetworkCheck = () => - render({(isOk) => }) + render({(isOk) => }) const { container } = renderButtonWithNetworkCheck() diff --git a/src/components/common/CookieAndTermBanner/index.tsx b/src/components/common/CookieAndTermBanner/index.tsx index e8f650276b..559684508e 100644 --- a/src/components/common/CookieAndTermBanner/index.tsx +++ b/src/components/common/CookieAndTermBanner/index.tsx @@ -149,7 +149,6 @@ export const CookieAndTermBanner = ({ const CookieBannerPopup = (): ReactElement | null => { const cookiePopup = useAppSelector(selectCookieBanner) - const cookies = useAppSelector(selectCookies) const dispatch = useAppDispatch() const hasAccepted = useAppSelector(hasAcceptedTerms) diff --git a/src/components/common/DatePickerInput/index.tsx b/src/components/common/DatePickerInput/index.tsx index ed760e712e..1d8ca61372 100644 --- a/src/components/common/DatePickerInput/index.tsx +++ b/src/components/common/DatePickerInput/index.tsx @@ -53,7 +53,7 @@ const DatePickerInput = ({ inputFormat="dd/MM/yyyy" {...field} disableFuture={disableFuture} - renderInput={({ label, error: _, ...params }) => ( + renderInput={({ label, ...params }) => ( )} PaperProps={{ diff --git a/src/components/common/DateTime/index.test.tsx b/src/components/common/DateTime/index.test.tsx index a749c21d71..d0e07ad038 100644 --- a/src/components/common/DateTime/index.test.tsx +++ b/src/components/common/DateTime/index.test.tsx @@ -70,13 +70,11 @@ describe('DateTime', () => { date.setDate(date.getDate() - days) - const { queryByText } = render(, { + const { getByText } = render(, { routerProps: { pathname: '/transactions/history' }, }) - const expected = formatDateTime(date.getTime()) - - expect(queryByText('3 days ago')).toBeInTheDocument() + expect(getByText('3 days ago')).toBeInTheDocument() }) it('should render the full date and time after threshold on the filter', () => { diff --git a/src/components/common/EthHashInfo/index.test.tsx b/src/components/common/EthHashInfo/index.test.tsx index 0facb02d3d..3ae9e9282d 100644 --- a/src/components/common/EthHashInfo/index.test.tsx +++ b/src/components/common/EthHashInfo/index.test.tsx @@ -1,7 +1,8 @@ import { blo } from 'blo' +import { act } from 'react' import type { ChainInfo } from '@safe-global/safe-gateway-typescript-sdk' -import { act, fireEvent, render, waitFor } from '@/tests/test-utils' +import { fireEvent, render, waitFor } from '@/tests/test-utils' import * as useAllAddressBooks from '@/hooks/useAllAddressBooks' import * as useChainId from '@/hooks/useChainId' import * as store from '@/store' @@ -258,7 +259,7 @@ describe('EthHashInfo', () => { const button = container.querySelector('button') - await act(() => { + act(() => { fireEvent.click(button!) }) @@ -288,7 +289,7 @@ describe('EthHashInfo', () => { const button = container.querySelector('button') - await act(() => { + act(() => { fireEvent.click(button!) }) @@ -320,7 +321,7 @@ describe('EthHashInfo', () => { const button = container.querySelector('button') - await act(() => { + act(() => { fireEvent.click(button!) }) @@ -349,7 +350,7 @@ describe('EthHashInfo', () => { const button = container.querySelector('button') - await act(() => { + act(() => { fireEvent.click(button!) }) @@ -375,7 +376,7 @@ describe('EthHashInfo', () => { const button = container.querySelector('button') - await act(() => { + act(() => { fireEvent.click(button!) }) diff --git a/src/components/common/NameInput/index.tsx b/src/components/common/NameInput/index.tsx index f2040e5f27..be31c2ba94 100644 --- a/src/components/common/NameInput/index.tsx +++ b/src/components/common/NameInput/index.tsx @@ -1,17 +1,15 @@ import type { TextFieldProps } from '@mui/material' import { TextField } from '@mui/material' import get from 'lodash/get' -import { type FieldError, type Validate, useFormContext } from 'react-hook-form' +import { type FieldError, useFormContext } from 'react-hook-form' import inputCss from '@/styles/inputs.module.css' const NameInput = ({ name, - validate, required = false, ...props }: Omit & { name: string - validate?: Validate required?: boolean }) => { const { register, formState } = useFormContext() || {} diff --git a/src/components/common/NetworkInput/index.tsx b/src/components/common/NetworkInput/index.tsx index 67b51bfc15..cd4385a4db 100644 --- a/src/components/common/NetworkInput/index.tsx +++ b/src/components/common/NetworkInput/index.tsx @@ -46,7 +46,8 @@ const NetworkInput = ({ name={name} rules={{ required }} control={control} - render={({ field: { ref, ...field }, fieldState: { error } }) => ( + // eslint-disable-next-line + render={({ field: { ref, ...field } }) => ( Network