From dc6e27e38873d748d85bab4ef77c221393be66fc Mon Sep 17 00:00:00 2001 From: Charles Bochet Date: Sat, 13 Jul 2024 17:57:13 +0200 Subject: [PATCH] Improve test coverage (#6244) --- .../perf/AddressFieldDisplay.perf.stories.tsx | 4 +- .../ui/input/button/components/Button.tsx | 7 +- .../modal/components/ConfirmationModal.tsx | 3 +- .../ui/layout/modal/components/Modal.tsx | 11 +-- .../pages/auth/__stories__/Invite.stories.tsx | 84 +++++++++++++++++++ ...ettingsDevelopersApiKeysDetail.stories.tsx | 57 ++++++++++++- .../SettingsDevelopersApiKeyDetail.tsx | 4 +- 7 files changed, 153 insertions(+), 17 deletions(-) create mode 100644 packages/twenty-front/src/pages/auth/__stories__/Invite.stories.tsx diff --git a/packages/twenty-front/src/modules/object-record/record-field/meta-types/display/components/__stories__/perf/AddressFieldDisplay.perf.stories.tsx b/packages/twenty-front/src/modules/object-record/record-field/meta-types/display/components/__stories__/perf/AddressFieldDisplay.perf.stories.tsx index 6c138c795..50e86a4c6 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/meta-types/display/components/__stories__/perf/AddressFieldDisplay.perf.stories.tsx +++ b/packages/twenty-front/src/modules/object-record/record-field/meta-types/display/components/__stories__/perf/AddressFieldDisplay.perf.stories.tsx @@ -49,6 +49,6 @@ export const Elipsis: Story = { export const Performance = getProfilingStory({ componentName: 'AddressFieldDisplay', averageThresholdInMs: 0.15, - numberOfRuns: 50, - numberOfTestsPerRun: 100, + numberOfRuns: 30, + numberOfTestsPerRun: 50, }); diff --git a/packages/twenty-front/src/modules/ui/input/button/components/Button.tsx b/packages/twenty-front/src/modules/ui/input/button/components/Button.tsx index 375b55fdf..a90daab5b 100644 --- a/packages/twenty-front/src/modules/ui/input/button/components/Button.tsx +++ b/packages/twenty-front/src/modules/ui/input/button/components/Button.tsx @@ -1,8 +1,8 @@ -import React from 'react'; -import { Link } from 'react-router-dom'; import isPropValid from '@emotion/is-prop-valid'; import { css, useTheme } from '@emotion/react'; import styled from '@emotion/styled'; +import React from 'react'; +import { Link } from 'react-router-dom'; import { IconComponent, Pill } from 'twenty-ui'; export type ButtonSize = 'medium' | 'small'; @@ -27,6 +27,7 @@ export type ButtonProps = { onClick?: (event: React.MouseEvent) => void; to?: string; target?: string; + dataTestId?: string; } & React.ComponentProps<'button'>; const StyledButton = styled('button', { @@ -374,6 +375,7 @@ export const Button = ({ onClick, to, target, + dataTestId, }: ButtonProps) => { const theme = useTheme(); @@ -393,6 +395,7 @@ export const Button = ({ to={to} as={to ? Link : 'button'} target={target} + data-testid={dataTestId} > {Icon && } {title} diff --git a/packages/twenty-front/src/modules/ui/layout/modal/components/ConfirmationModal.tsx b/packages/twenty-front/src/modules/ui/layout/modal/components/ConfirmationModal.tsx index c6775ba1e..402b94c51 100644 --- a/packages/twenty-front/src/modules/ui/layout/modal/components/ConfirmationModal.tsx +++ b/packages/twenty-front/src/modules/ui/layout/modal/components/ConfirmationModal.tsx @@ -1,6 +1,6 @@ -import { ReactNode, useState } from 'react'; import styled from '@emotion/styled'; import { AnimatePresence, LayoutGroup } from 'framer-motion'; +import { ReactNode, useState } from 'react'; import { H1Title, H1TitleFontColor } from 'twenty-ui'; import { useDebouncedCallback } from 'use-debounce'; @@ -130,6 +130,7 @@ export const ConfirmationModal = ({ title={deleteButtonText} disabled={!isValidValue} fullWidth + dataTestId="confirmation-modal-confirm-button" /> diff --git a/packages/twenty-front/src/modules/ui/layout/modal/components/Modal.tsx b/packages/twenty-front/src/modules/ui/layout/modal/components/Modal.tsx index 445086b52..d9fd4f181 100644 --- a/packages/twenty-front/src/modules/ui/layout/modal/components/Modal.tsx +++ b/packages/twenty-front/src/modules/ui/layout/modal/components/Modal.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useRef } from 'react'; +import { useEffect, useRef } from 'react'; import { Key } from 'ts-key-enum'; import { @@ -7,11 +7,8 @@ import { } from '@/ui/layout/modal/components/ModalLayout'; import { usePreviousHotkeyScope } from '@/ui/utilities/hotkey/hooks/usePreviousHotkeyScope'; import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys'; -import { - ClickOutsideMode, - useListenClickOutside, -} from '@/ui/utilities/pointer-event/hooks/useListenClickOutside'; +import { useListenClickOutsideV2 } from '@/ui/utilities/pointer-event/hooks/useListenClickOutsideV2'; import { ModalHotkeyScope } from './types/ModalHotkeyScope'; type ModalProps = ModalLayoutProps & { @@ -68,10 +65,10 @@ export const Modal = ({ const modalRef = useRef(null); - useListenClickOutside({ + useListenClickOutsideV2({ refs: [modalRef], + listenerId: 'MODAL_CLICK_OUTSIDE_LISTENER_ID', callback: () => onClose?.(), - mode: ClickOutsideMode.comparePixels, }); return isOpen ? ( diff --git a/packages/twenty-front/src/pages/auth/__stories__/Invite.stories.tsx b/packages/twenty-front/src/pages/auth/__stories__/Invite.stories.tsx new file mode 100644 index 000000000..08369b84f --- /dev/null +++ b/packages/twenty-front/src/pages/auth/__stories__/Invite.stories.tsx @@ -0,0 +1,84 @@ +import { getOperationName } from '@apollo/client/utilities'; +import { Meta, StoryObj } from '@storybook/react'; +import { fireEvent, within } from '@storybook/test'; +import { HttpResponse, graphql } from 'msw'; + +import { AppPath } from '@/types/AppPath'; +import { GET_CURRENT_USER } from '@/users/graphql/queries/getCurrentUser'; +import { GET_WORKSPACE_FROM_INVITE_HASH } from '@/workspace/graphql/queries/getWorkspaceFromInviteHash'; +import { + PageDecorator, + PageDecoratorArgs, +} from '~/testing/decorators/PageDecorator'; +import { graphqlMocks } from '~/testing/graphqlMocks'; + +import { Invite } from '../Invite'; + +const meta: Meta = { + title: 'Pages/Auth/Invite', + component: Invite, + decorators: [PageDecorator], + args: { + routePath: AppPath.Invite, + routeParams: { ':workspaceInviteHash': 'my-hash' }, + }, + parameters: { + msw: { + handlers: [ + graphql.query( + getOperationName(GET_WORKSPACE_FROM_INVITE_HASH) ?? '', + () => { + return HttpResponse.json({ + data: { + findWorkspaceFromInviteHash: { + __typename: 'Workspace', + id: '20202020-91f0-46d0-acab-cb5afef3cc3b', + displayName: 'Twenty dev', + logo: null, + allowImpersonation: false, + }, + }, + }); + }, + ), + graphql.query(getOperationName(GET_CURRENT_USER) ?? '', () => { + return HttpResponse.json({ + data: null, + errors: [ + { + message: 'Unauthorized', + extensions: { + code: 'UNAUTHENTICATED', + response: { + statusCode: 401, + message: 'Unauthorized', + }, + }, + }, + ], + }); + }), + graphqlMocks.handlers, + ], + }, + cookie: '', + }, +}; + +export default meta; + +export type Story = StoryObj; + +export const Default: Story = { + play: async ({ canvasElement }) => { + const canvas = within(canvasElement); + + await canvas.findByText('Join Twenty dev team'); + + const continueWithEmailButton = await canvas.findByText( + 'Continue With Email', + ); + + await fireEvent.click(continueWithEmailButton); + }, +}; diff --git a/packages/twenty-front/src/pages/settings/developers/__stories__/api-keys/SettingsDevelopersApiKeysDetail.stories.tsx b/packages/twenty-front/src/pages/settings/developers/__stories__/api-keys/SettingsDevelopersApiKeysDetail.stories.tsx index cb5902051..34d0f6ccf 100644 --- a/packages/twenty-front/src/pages/settings/developers/__stories__/api-keys/SettingsDevelopersApiKeysDetail.stories.tsx +++ b/packages/twenty-front/src/pages/settings/developers/__stories__/api-keys/SettingsDevelopersApiKeysDetail.stories.tsx @@ -1,6 +1,6 @@ import { Meta, StoryObj } from '@storybook/react'; -import { within } from '@storybook/test'; -import { graphql, HttpResponse } from 'msw'; +import { userEvent, within } from '@storybook/test'; +import { HttpResponse, graphql } from 'msw'; import { SettingsDevelopersApiKeyDetail } from '~/pages/settings/developers/api-keys/SettingsDevelopersApiKeyDetail'; import { @@ -8,6 +8,7 @@ import { PageDecoratorArgs, } from '~/testing/decorators/PageDecorator'; import { graphqlMocks } from '~/testing/graphqlMocks'; +import { sleep } from '~/utils/sleep'; const meta: Meta = { title: 'Pages/Settings/Developers/ApiKeys/SettingsDevelopersApiKeyDetail', @@ -50,6 +51,56 @@ export const Default: Story = { play: async ({ canvasElement }) => { const canvas = within(canvasElement); await canvas.findByText('Settings'); - await canvas.findByText('Regenerate an Api key'); + await canvas.findByText('sfsfdsf API Key'); + }, +}; + +export const RegenerateApiKey: Story = { + play: async ({ canvasElement, step }) => { + const canvas = within(canvasElement); + await canvas.findByText('Settings'); + + await userEvent.click(canvas.getByText('Regenerate Key')); + + await canvas.findByText('Cancel'); + const confirmationInput = await canvas.findByPlaceholderText('yes'); + await userEvent.click(confirmationInput); + await userEvent.keyboard('y'); + await userEvent.keyboard('e'); + await userEvent.keyboard('s'); + + const confirmButton = await canvas.findByTestId( + 'confirmation-modal-confirm-button', + ); + + await step('Click on confirm button', async () => { + await sleep(1000); + await userEvent.click(confirmButton); + }); + }, +}; + +export const DeleteApiKey: Story = { + play: async ({ canvasElement, step }) => { + const canvas = within(canvasElement); + await canvas.findByText('Settings'); + + await userEvent.click(canvas.getByText('Delete')); + + await canvas.findByText('Cancel'); + const confirmationInput = await canvas.findByPlaceholderText('yes'); + await userEvent.click(confirmationInput); + await userEvent.keyboard('y'); + await userEvent.keyboard('e'); + await userEvent.keyboard('s'); + + const confirmButton = await canvas.findByTestId( + 'confirmation-modal-confirm-button', + ); + + await step('Click on confirm button', async () => { + await sleep(1000); + await userEvent.click(confirmButton); + }); }, }; diff --git a/packages/twenty-front/src/pages/settings/developers/api-keys/SettingsDevelopersApiKeyDetail.tsx b/packages/twenty-front/src/pages/settings/developers/api-keys/SettingsDevelopersApiKeyDetail.tsx index 0a718cf76..2f3987e81 100644 --- a/packages/twenty-front/src/pages/settings/developers/api-keys/SettingsDevelopersApiKeyDetail.tsx +++ b/packages/twenty-front/src/pages/settings/developers/api-keys/SettingsDevelopersApiKeyDetail.tsx @@ -1,8 +1,8 @@ -import { useEffect, useState } from 'react'; -import { useNavigate, useParams } from 'react-router-dom'; import styled from '@emotion/styled'; import { isNonEmptyString } from '@sniptt/guards'; import { DateTime } from 'luxon'; +import { useEffect, useState } from 'react'; +import { useNavigate, useParams } from 'react-router-dom'; import { useRecoilState } from 'recoil'; import { H2Title, IconRepeat, IconSettings, IconTrash } from 'twenty-ui';