From c06659146085d44ad9209126bbf17d10bcc46678 Mon Sep 17 00:00:00 2001 From: AlexeyPonomarenko Date: Fri, 29 Apr 2022 22:33:49 +0300 Subject: [PATCH] test(tests): another portion of tests --- .../SearchInput/tests/SearchInput.spec.tsx | 114 ++++++++++++++++++ .../SubjectRule/tests/SubjectRule.spec.tsx | 76 ++++++++++++ .../helpers/tests/newProposalObject.spec.ts | 60 +++++++++ .../tests/proposalObjectHelpers.spec.ts | 114 ++++++++++++++++++ .../Notification/Notification.tsx | 20 +-- .../Notification/tests/Notification.spec.tsx | 37 ++++++ .../tests/NotificationContainer.spec.tsx | 87 +++++++++++++ features/proposal/helpers.ts | 33 ----- 8 files changed, 493 insertions(+), 48 deletions(-) create mode 100644 astro_2.0/components/SearchInput/tests/SearchInput.spec.tsx create mode 100644 astro_2.0/features/CreateDao/components/SubjectRule/tests/SubjectRule.spec.tsx create mode 100644 astro_2.0/features/CreateProposal/helpers/tests/newProposalObject.spec.ts create mode 100644 features/notifications/Notification/tests/Notification.spec.tsx create mode 100644 features/notifications/NotificationContainer/tests/NotificationContainer.spec.tsx delete mode 100644 features/proposal/helpers.ts diff --git a/astro_2.0/components/SearchInput/tests/SearchInput.spec.tsx b/astro_2.0/components/SearchInput/tests/SearchInput.spec.tsx new file mode 100644 index 000000000..379b244e5 --- /dev/null +++ b/astro_2.0/components/SearchInput/tests/SearchInput.spec.tsx @@ -0,0 +1,114 @@ +import { render } from 'jest/testUtils'; + +import { SearchInput } from 'astro_2.0/components/SearchInput'; +import { fireEvent } from '@testing-library/dom'; + +jest.mock('astro_2.0/components/LoadingIndicator', () => { + return { + LoadingIndicator: () =>
LoadingIndicator
, + }; +}); + +jest.mock('components/Icon', () => { + return { + Icon: ({ name }: { name: string }) =>
{name}
, + }; +}); + +jest.mock('next-i18next', () => ({ + // this mock makes sure any components using the translate hook can use it without a warning being shown + useTranslation: () => { + return { + t: (str: string): string => str, + }; + }, +})); + +describe('SearchInput', () => { + it('Should render loading state', () => { + const { getByText } = render( + Promise.resolve(null)} loading /> + ); + + expect(getByText('LoadingIndicator')).toBeInTheDocument(); + }); + + it('Should render search icon', () => { + const { getByText } = render( + Promise.resolve(null)} + /> + ); + + expect(getByText('buttonSearch')).toBeInTheDocument(); + }); + + it('Should render "close" button', () => { + const inputPlaceholder = 'Hello World'; + + const { getByText, getByPlaceholderText } = render( + Promise.resolve(null)} + /> + ); + + fireEvent.change(getByPlaceholderText(inputPlaceholder), { + target: { value: 'Some Value' }, + }); + + expect(getByText('closeCircle')).toBeInTheDocument(); + }); + + it('Should trigger "onClose"', () => { + const onClose = jest.fn(); + + const inputPlaceholder = 'Hello World'; + + const { getByText, getByPlaceholderText } = render( + Promise.resolve(null)} + /> + ); + + fireEvent.change(getByPlaceholderText(inputPlaceholder), { + target: { value: 'Some Value' }, + }); + + fireEvent.click(getByText('closeCircle')); + + expect(onClose).toBeCalled(); + }); + + it('Should trigger "onSubmit"', () => { + const onClose = jest.fn(); + + const inputPlaceholder = 'Hello World'; + + const { getByText, getByPlaceholderText } = render( + Promise.resolve(null)} + /> + ); + + fireEvent.change(getByPlaceholderText(inputPlaceholder), { + target: { value: 'Some Value' }, + }); + + fireEvent.click(getByText('closeCircle')); + + expect(onClose).toBeCalled(); + }); +}); diff --git a/astro_2.0/features/CreateDao/components/SubjectRule/tests/SubjectRule.spec.tsx b/astro_2.0/features/CreateDao/components/SubjectRule/tests/SubjectRule.spec.tsx new file mode 100644 index 000000000..3ce2c8da1 --- /dev/null +++ b/astro_2.0/features/CreateDao/components/SubjectRule/tests/SubjectRule.spec.tsx @@ -0,0 +1,76 @@ +/* eslint-disable @typescript-eslint/ban-ts-comment */ + +import { ReactNode } from 'react'; +import { render } from 'jest/testUtils'; + +import { useFormContext } from 'react-hook-form'; + +import { SubjectRule } from 'astro_2.0/features/CreateDao/components/SubjectRule'; +import { fireEvent } from '@testing-library/dom'; + +const formContextMock = { + trigger: () => 0, + formState: { + errors: {}, + touchedFields: {}, + }, +}; + +jest.mock('react-hook-form', () => { + return { + ...jest.requireActual('react-hook-form'), + // eslint-disable-next-line @typescript-eslint/no-shadow + Controller: ({ render }: { render: (data: unknown) => ReactNode }) => { + const renderProps = { + field: { + value: '123', + onChange: () => 0, + }, + }; + + return
{render(renderProps)}
; + }, + useFormContext: jest.fn(() => formContextMock), + }; +}); + +jest.mock('next-i18next', () => ({ + // this mock makes sure any components using the translate hook can use it without a warning being shown + useTranslation: () => { + return { + t: (str: string): string => str, + }; + }, +})); + +describe('SubjectRule', () => { + it('Should render component', () => { + const title = 'Hello World'; + + const { getByText } = render( + + ); + + expect(getByText(title)).toBeTruthy(); + }); + + it('Should handle dao option change', () => { + const subject = 'proposals'; + + const trigger = jest.fn(); + + // @ts-ignore + useFormContext.mockImplementation(() => ({ + ...formContextMock, + trigger, + })); + + const { getAllByRole } = render( + + ); + + fireEvent.click(getAllByRole('button')[0]); + + expect(trigger).toBeCalledWith(subject); + }); +}); diff --git a/astro_2.0/features/CreateProposal/helpers/tests/newProposalObject.spec.ts b/astro_2.0/features/CreateProposal/helpers/tests/newProposalObject.spec.ts new file mode 100644 index 000000000..095d27938 --- /dev/null +++ b/astro_2.0/features/CreateProposal/helpers/tests/newProposalObject.spec.ts @@ -0,0 +1,60 @@ +import { ProposalVariant } from 'types/proposal'; + +import { getNewProposalObject } from 'astro_2.0/features/CreateProposal/helpers/newProposalObject'; + +import { dao, tokens } from './mock'; + +jest.mock( + 'astro_2.0/features/CreateProposal/helpers/proposalObjectHelpers', + () => { + return { + ...jest.requireActual( + 'astro_2.0/features/CreateProposal/helpers/proposalObjectHelpers' + ), + getAddBountyProposal: () => 'getAddBountyProposal', + getUpgradeCodeProposal: () => 'getUpgradeCodeProposal', + getUpgradeSelfProposal: () => 'getUpgradeSelfProposal', + getRemoveUpgradeCodeProposal: () => 'getRemoveUpgradeCodeProposal', + }; + } +); + +jest.mock('astro_2.0/features/CreateProposal/helpers/bountiesHelpers', () => { + return { + ...jest.requireActual( + 'astro_2.0/features/CreateProposal/helpers/bountiesHelpers' + ), + getAddBountyProposal: () => 'getAddBountyProposal', + }; +}); + +describe('newProposalObject', () => { + describe('getChangeConfigProposal', () => { + it.each` + type | expectedResult + ${ProposalVariant.ProposeGetUpgradeCode} | ${'getUpgradeCodeProposal'} + ${ProposalVariant.ProposeRemoveUpgradeCode} | ${'getRemoveUpgradeCodeProposal'} + ${ProposalVariant.ProposeUpgradeSelf} | ${'getUpgradeSelfProposal'} + ${ProposalVariant.ProposeCreateBounty} | ${'getAddBountyProposal'} + `( + 'Should return proposal for $type proposal', + async ({ type, expectedResult }) => { + const data = { + details: 'details', + externalUrl: 'externalUrl', + versionHash: 'versionHash', + }; + + const result = await getNewProposalObject( + dao, + type, + data, + tokens, + 'MyAccount' + ); + + expect(result).toEqual(expectedResult); + } + ); + }); +}); diff --git a/astro_2.0/features/CreateProposal/helpers/tests/proposalObjectHelpers.spec.ts b/astro_2.0/features/CreateProposal/helpers/tests/proposalObjectHelpers.spec.ts index 223a31a5d..e2db5a11a 100644 --- a/astro_2.0/features/CreateProposal/helpers/tests/proposalObjectHelpers.spec.ts +++ b/astro_2.0/features/CreateProposal/helpers/tests/proposalObjectHelpers.spec.ts @@ -1,11 +1,16 @@ import { DAO } from 'types/dao'; +import { CreateTokenInput } from 'astro_2.0/features/CreateProposal/types'; import { BuyNftFromParasInput, BuyNftFromMintbaseInput, CustomFunctionCallInput, getSwapsOnRefProposal, + getUpgradeSelfProposal, + getUpgradeCodeProposal, + getCreateTokenProposal, getBuyNftFromParasProposal, + getRemoveUpgradeCodeProposal, getBuyNftFromMintbaseProposal, getCustomFunctionCallProposal, getTransferMintbaseNFTProposal, @@ -189,4 +194,113 @@ describe('proposalObjectHelpers', () => { }); }); }); + + describe('getUpgradeCodeProposal', () => { + it('Should return proposal', () => { + const data = { + versionHash: 'versionHash', + details: 'details', + externalUrl: 'externalUrl', + }; + + const result = getUpgradeCodeProposal(dao, data); + + expect(result).toEqual({ + daoId: 'legaldao.sputnikv2.testnet', + description: 'details$$$$externalUrl', + kind: 'FunctionCall', + data: { + receiver_id: 'sputnikv2.testnet', + actions: [ + { + method_name: 'store_contract_self', + args: 'eyJjb2RlX2hhc2giOiJ2ZXJzaW9uSGFzaCJ9', + deposit: '6000000000000000000000000', + gas: '220000000000000', + }, + ], + }, + bond: '100000000000000000000000', + }); + }); + }); + + describe('getRemoveUpgradeCodeProposal', () => { + it('Should return proposal', () => { + const data = { + versionHash: 'versionHash', + details: 'details', + externalUrl: 'externalUrl', + }; + + const result = getRemoveUpgradeCodeProposal(dao, data); + + expect(result).toEqual({ + daoId: 'legaldao.sputnikv2.testnet', + description: 'details$$$$externalUrl', + kind: 'FunctionCall', + data: { + receiver_id: 'sputnikv2.testnet', + actions: [ + { + method_name: 'remove_contract_self', + args: 'eyJjb2RlX2hhc2giOiJ2ZXJzaW9uSGFzaCJ9', + deposit: '0', + gas: '220000000000000', + }, + ], + }, + bond: '100000000000000000000000', + }); + }); + }); + + describe('getUpgradeSelfProposal', () => { + it('Should return proposal', () => { + const data = { + versionHash: 'versionHash', + details: 'details', + externalUrl: 'externalUrl', + }; + + const result = getUpgradeSelfProposal(dao, data); + + expect(result).toEqual({ + daoId: 'legaldao.sputnikv2.testnet', + description: 'details$$$$externalUrl', + kind: 'UpgradeSelf', + data: { hash: 'versionHash' }, + bond: '100000000000000000000000', + }); + }); + }); + + describe('getCreateTokenProposal', () => { + it('Should return proposal', async () => { + const data = { + details: 'details', + externalUrl: 'externalUrl', + } as CreateTokenInput; + + const result = await getCreateTokenProposal(dao, data); + + expect(result).toEqual({ + daoId: 'legaldao.sputnikv2.testnet', + description: 'details$$$$externalUrl', + kind: 'FunctionCall', + data: { + receiver_id: 'legaldao.sputnikv2.testnet', + actions: [ + { + args: 'e30=', + deposit: '6000000000000000000000000', + gas: '220000000000000', + method_name: 'store_contract_self', + }, + ], + }, + bond: '100000000000000000000000', + }); + }); + }); }); diff --git a/features/notifications/Notification/Notification.tsx b/features/notifications/Notification/Notification.tsx index 84f379903..0879adf05 100644 --- a/features/notifications/Notification/Notification.tsx +++ b/features/notifications/Notification/Notification.tsx @@ -1,7 +1,9 @@ import React from 'react'; import cn from 'classnames'; + import { dispatchCustomEvent } from 'utils/dispatchCustomEvent'; -import { Icon, IconName } from 'components/Icon'; + +import { Icon } from 'components/Icon'; import { HIDE_NOTIFICATION_EVENT } from 'constants/common'; @@ -22,19 +24,6 @@ export const Notification: React.FC = props => { [s.flat]: flat, }); - function getIconName(): IconName { - switch (type) { - case NOTIFICATION_TYPES.SUCCESS: - return 'info'; - case NOTIFICATION_TYPES.ERROR: - return 'info'; - case NOTIFICATION_TYPES.INFO: - return 'info'; - default: - return 'info'; - } - } - function onCloseClick() { dispatchCustomEvent(HIDE_NOTIFICATION_EVENT, { id }); } @@ -52,7 +41,7 @@ export const Notification: React.FC = props => { [s.info]: type === NOTIFICATION_TYPES.INFO, })} > - +
{renderTag()} @@ -63,6 +52,7 @@ export const Notification: React.FC = props => { className={s.closeIcon} onClick={onCloseClick} width={18} + data-testid="agn-close-button" />
); diff --git a/features/notifications/Notification/tests/Notification.spec.tsx b/features/notifications/Notification/tests/Notification.spec.tsx new file mode 100644 index 000000000..75c23630a --- /dev/null +++ b/features/notifications/Notification/tests/Notification.spec.tsx @@ -0,0 +1,37 @@ +/* eslint-disable @typescript-eslint/ban-ts-comment */ +import { render } from 'jest/testUtils'; +import { fireEvent } from '@testing-library/dom'; + +import { NOTIFICATION_TYPES } from 'features/notifications'; + +import { dispatchCustomEvent } from 'utils/dispatchCustomEvent'; + +import { Notification } from 'features/notifications/Notification'; + +jest.mock('utils/dispatchCustomEvent', () => { + return { + dispatchCustomEvent: jest.fn(), + }; +}); + +describe('Notification', () => { + it('Should close notification', () => { + const mock = jest.fn(); + + // @ts-ignore + dispatchCustomEvent.mockImplementation(mock); + + const { getByTestId } = render( + + ); + + fireEvent.click(getByTestId('agn-close-button')); + + expect(mock).toBeCalled(); + }); +}); diff --git a/features/notifications/NotificationContainer/tests/NotificationContainer.spec.tsx b/features/notifications/NotificationContainer/tests/NotificationContainer.spec.tsx new file mode 100644 index 000000000..12df31a35 --- /dev/null +++ b/features/notifications/NotificationContainer/tests/NotificationContainer.spec.tsx @@ -0,0 +1,87 @@ +import { render } from 'jest/testUtils'; +import { act, screen } from '@testing-library/react'; + +import { + NOTIFICATION_TYPES, + NotificationRawData, + showNotification, + hideNotificationId, + NotificationContainer, + hideNotificationByTag, +} from 'features/notifications'; + +describe('NotificationContainer', () => { + const description = 'Hello World'; + + function showNoty(msToRun = 200, notyProps?: Partial) { + render(); + + act(() => { + showNotification({ + type: NOTIFICATION_TYPES.SUCCESS, + description, + ...notyProps, + }); + + jest.advanceTimersByTime(msToRun); + }); + } + + it('Should render notification', () => { + jest.useFakeTimers(); + + showNoty(); + + expect(screen.getByText(description)).toBeTruthy(); + }); + + it('Should hide notification by lifetime', () => { + jest.useFakeTimers(); + + showNoty(200, { lifetime: 400, type: NOTIFICATION_TYPES.ERROR }); + + expect(screen.getByText(description)).toBeTruthy(); + + act(() => { + jest.advanceTimersByTime(600); + }); + + expect(screen.queryByText(description)).toBeFalsy(); + }); + + it('Should remove notification by tag', () => { + const tag = '234'; + + jest.useFakeTimers(); + + showNoty(300, { tag, type: NOTIFICATION_TYPES.INFO }); + + expect(screen.getByText(description)).toBeTruthy(); + + act(() => { + hideNotificationByTag(tag); + + jest.advanceTimersByTime(1500); + }); + + expect(screen.queryByText(description)).toBeFalsy(); + }); + + it('Should remove notification by id', () => { + const tag = '234'; + + jest.useFakeTimers(); + + showNoty(300, { tag }); + + expect(screen.getByText(description)).toBeTruthy(); + + act(() => { + hideNotificationId('0'); + + jest.advanceTimersByTime(1500); + }); + + expect(screen.queryByText(description)).toBeFalsy(); + }); +}); diff --git a/features/proposal/helpers.ts b/features/proposal/helpers.ts deleted file mode 100644 index b9df1821e..000000000 --- a/features/proposal/helpers.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { ProposalType } from 'types/proposal'; - -export function getProposalNameByType(type: ProposalType): string { - switch (type) { - case ProposalType.AddBounty: { - return 'Add Bounty'; - } - case ProposalType.AddMemberToRole: { - return 'Add Member to Role'; - } - case ProposalType.BountyDone: { - return 'Bounty done'; - } - case ProposalType.ChangeConfig: { - return 'Change DAO config'; - } - case ProposalType.ChangePolicy: { - return 'Change DAO policy'; - } - case ProposalType.RemoveMemberFromRole: { - return 'Remove Member from Role'; - } - case ProposalType.Transfer: { - return 'Transfer'; - } - case ProposalType.Vote: { - return 'Vote'; - } - default: { - return type; - } - } -}