From a972705ce6b2c13d89ea3b101624892794198099 Mon Sep 17 00:00:00 2001 From: Charles Bochet Date: Mon, 17 Jul 2023 17:14:53 -0700 Subject: [PATCH] Improve test coverage and refactor storybook arch (#723) * Improve test coverage and refactor storybook arch * Fix coverage * Fix tests * Fix lint * Fix lint --- front/.storybook/main.js | 1 + front/.storybook/preview.ts | 5 ++ front/package.json | 6 +- front/src/__stories__/App.stories.tsx | 9 -- .../{CommentThreadItem.tsx => Comment.tsx} | 2 +- .../comment/__stories__/Comment.stories.tsx | 25 ++++++ .../__stories__/CommentHeader.stories.tsx | 83 +++---------------- .../comment/__stories__/mock-comment.ts | 40 +++++++++ .../components/CommentThreadComments.tsx | 4 +- .../CommentThreadRelationPicker.tsx | 7 +- .../companies/__stories__/Board.stories.tsx | 2 +- .../__stories__/CompanyBoardCard.stories.tsx | 2 +- .../__stories__/PeopleChip.stories.tsx | 2 +- .../profile/components/NameFields.tsx | 8 +- .../BoardCardEditableFieldEditMode.tsx | 7 +- .../board/components/EditColumnTitleInput.tsx | 7 +- .../ui/button/components/IconButton.tsx | 13 +-- .../button/components/RoundedIconButton.tsx | 3 +- .../components/__stories__/Button.stories.tsx | 2 +- .../__stories__/IconButton.stories.tsx | 2 +- .../__stories__/MainButton.stories.tsx | 2 +- .../__stories__/RoundedIconButton.stories.tsx | 2 +- .../__stories__/DropdownMenu.stories.tsx | 2 +- .../hooks/useRegisterCloseFieldHandlers.ts | 13 +-- .../components/DropdownButton.tsx | 7 +- .../useListenClickOutsideArrayOfRef.test.tsx | 39 +++++++++ .../__tests__/useOutsideAlerter.test.tsx | 31 ------- .../hooks/useListenClickOutsideArrayOfRef.ts | 73 ++++++++++++---- .../src/modules/ui/hooks/useOutsideAlerter.ts | 52 ------------ .../components/SingleEntitySelect.tsx | 7 +- .../right-drawer/components/RightDrawer.tsx | 12 +-- .../ui/table/components/EntityTable.tsx | 7 +- .../hooks/useRegisterCloseCellHandlers.ts | 13 +-- .../pages/companies/__stories__/Companies.mdx | 11 --- .../companies/__stories__/Company.stories.tsx | 72 +++++++++++----- .../__stories__/Opportunities.stories.tsx | 2 +- front/src/pages/people/__stories__/People.mdx | 11 --- .../__stories__/SettingsProfile.stories.tsx | 17 ++++ .../SettingsWorkspaceMembers.stories.tsx | 4 + front/src/testing/graphqlMocks.ts | 22 +++++ .../src/testing/mock-data/comment-threads.ts | 4 + front/src/testing/mock-data/companies.ts | 1 + front/yarn.lock | 5 ++ 43 files changed, 365 insertions(+), 274 deletions(-) rename front/src/modules/activities/comment/{CommentThreadItem.tsx => Comment.tsx} (92%) create mode 100644 front/src/modules/activities/comment/__stories__/Comment.stories.tsx create mode 100644 front/src/modules/activities/comment/__stories__/mock-comment.ts create mode 100644 front/src/modules/ui/hooks/__tests__/useListenClickOutsideArrayOfRef.test.tsx delete mode 100644 front/src/modules/ui/hooks/__tests__/useOutsideAlerter.test.tsx delete mode 100644 front/src/modules/ui/hooks/useOutsideAlerter.ts delete mode 100644 front/src/pages/companies/__stories__/Companies.mdx delete mode 100644 front/src/pages/people/__stories__/People.mdx diff --git a/front/.storybook/main.js b/front/.storybook/main.js index fc1005781..f248fe2bf 100644 --- a/front/.storybook/main.js +++ b/front/.storybook/main.js @@ -55,6 +55,7 @@ module.exports = { "@storybook/addon-styling", "@storybook/addon-knobs", "storybook-addon-pseudo-states", + "storybook-addon-cookie", ], framework: { name: '@storybook/react-webpack5', diff --git a/front/.storybook/preview.ts b/front/.storybook/preview.ts index b40df9268..249492952 100644 --- a/front/.storybook/preview.ts +++ b/front/.storybook/preview.ts @@ -28,6 +28,11 @@ const preview: Preview = { date: /Date$/, }, }, + options: { + storySort: { + order: ['UI', 'Modules', 'Pages'], + }, + }, }, }; diff --git a/front/package.json b/front/package.json index a66332c62..33044dead 100644 --- a/front/package.json +++ b/front/package.json @@ -155,6 +155,7 @@ "prop-types": "^15.8.1", "react-scripts": "5.0.1", "storybook": "^7.0.22", + "storybook-addon-cookie": "^3.0.1", "storybook-addon-pseudo-states": "^2.1.0", "ts-jest": "^29.1.0", "typescript": "^4.9.3", @@ -164,8 +165,9 @@ "workerDirectory": "public" }, "nyc": { - "lines": 60, - "statements": 60, + "lines": 65, + "statements": 65, + "functions": 60, "exclude": [ "src/generated/**/*" ] diff --git a/front/src/__stories__/App.stories.tsx b/front/src/__stories__/App.stories.tsx index 0173b289b..226525fc8 100644 --- a/front/src/__stories__/App.stories.tsx +++ b/front/src/__stories__/App.stories.tsx @@ -2,7 +2,6 @@ import type { Meta, StoryObj } from '@storybook/react'; import { App } from '~/App'; import { graphqlMocks } from '~/testing/graphqlMocks'; -import { mockedUserJWT } from '~/testing/mock-data/jwt'; import { render } from './shared'; @@ -16,14 +15,6 @@ export type Story = StoryObj; export const Default: Story = { render, - loaders: [ - async () => ({ - accessTokenStored: window.localStorage.setItem( - 'accessToken', - mockedUserJWT, - ), - }), - ], parameters: { msw: graphqlMocks, }, diff --git a/front/src/modules/activities/comment/CommentThreadItem.tsx b/front/src/modules/activities/comment/Comment.tsx similarity index 92% rename from front/src/modules/activities/comment/CommentThreadItem.tsx rename to front/src/modules/activities/comment/Comment.tsx index a82e12329..db7d23022 100644 --- a/front/src/modules/activities/comment/CommentThreadItem.tsx +++ b/front/src/modules/activities/comment/Comment.tsx @@ -30,7 +30,7 @@ const StyledCommentBody = styled.div` text-align: left; `; -export function CommentThreadItem({ comment, actionBar }: OwnProps) { +export function Comment({ comment, actionBar }: OwnProps) { return ( diff --git a/front/src/modules/activities/comment/__stories__/Comment.stories.tsx b/front/src/modules/activities/comment/__stories__/Comment.stories.tsx new file mode 100644 index 000000000..c05f46119 --- /dev/null +++ b/front/src/modules/activities/comment/__stories__/Comment.stories.tsx @@ -0,0 +1,25 @@ +import type { Meta, StoryObj } from '@storybook/react'; + +import { getRenderWrapperForComponent } from '~/testing/renderWrappers'; + +import { Comment } from '../Comment'; + +import { mockComment, mockCommentWithLongValues } from './mock-comment'; + +const meta: Meta = { + title: 'Modules/Activity/Comment/Comment', + component: Comment, +}; + +export default meta; +type Story = StoryObj; + +export const Default: Story = { + render: getRenderWrapperForComponent(), +}; + +export const WithLongValues: Story = { + render: getRenderWrapperForComponent( + , + ), +}; diff --git a/front/src/modules/activities/comment/__stories__/CommentHeader.stories.tsx b/front/src/modules/activities/comment/__stories__/CommentHeader.stories.tsx index 81966110b..6ca2e702e 100644 --- a/front/src/modules/activities/comment/__stories__/CommentHeader.stories.tsx +++ b/front/src/modules/activities/comment/__stories__/CommentHeader.stories.tsx @@ -1,92 +1,34 @@ import type { Meta, StoryObj } from '@storybook/react'; -import { DateTime } from 'luxon'; import { CommentThreadActionBar } from '@/activities/right-drawer/components/CommentThreadActionBar'; -import { CommentForDrawer } from '@/activities/types/CommentForDrawer'; -import { mockedUsersData } from '~/testing/mock-data/users'; import { getRenderWrapperForComponent } from '~/testing/renderWrappers'; import { CommentHeader } from '../CommentHeader'; +import { mockComment, mockCommentWithLongValues } from './mock-comment'; + const meta: Meta = { - title: 'Modules/Comments/CommentHeader', + title: 'Modules/Activity/Comment/CommentHeader', component: CommentHeader, }; export default meta; type Story = StoryObj; -const mockUser = mockedUsersData[0]; - -const mockComment: Pick = { - id: 'fake_comment_1_uuid', - author: { - id: 'fake_comment_1_author_uuid', - displayName: mockUser.displayName ?? '', - firstName: mockUser.firstName ?? '', - lastName: mockUser.lastName ?? '', - avatarUrl: mockUser.avatarUrl, - }, - createdAt: DateTime.now().minus({ hours: 2 }).toISO() ?? '', -}; - -const mockCommentWithLongName: Pick< - CommentForDrawer, - 'id' | 'author' | 'createdAt' -> = { - id: 'fake_comment_2_uuid', - author: { - id: 'fake_comment_2_author_uuid', - displayName: mockUser.displayName + ' with a very long suffix' ?? '', - firstName: mockUser.firstName ?? '', - lastName: mockUser.lastName ?? '', - avatarUrl: mockUser.avatarUrl, - }, - createdAt: DateTime.now().minus({ hours: 2 }).toISO() ?? '', -}; - export const Default: Story = { - render: getRenderWrapperForComponent( - , - ), + render: getRenderWrapperForComponent(), }; export const FewDaysAgo: Story = { - render: getRenderWrapperForComponent( - , - ), + render: getRenderWrapperForComponent(), }; export const FewMonthsAgo: Story = { - render: getRenderWrapperForComponent( - , - ), + render: getRenderWrapperForComponent(), }; export const FewYearsAgo: Story = { - render: getRenderWrapperForComponent( - , - ), + render: getRenderWrapperForComponent(), }; export const WithoutAvatar: Story = { @@ -98,7 +40,6 @@ export const WithoutAvatar: Story = { ...mockComment.author, avatarUrl: '', }, - createdAt: DateTime.now().minus({ hours: 2 }).toISO() ?? '', }} />, ), @@ -108,12 +49,11 @@ export const WithLongUserName: Story = { render: getRenderWrapperForComponent( , ), @@ -122,10 +62,7 @@ export const WithLongUserName: Story = { export const WithActionBar: Story = { render: getRenderWrapperForComponent( } />, ), diff --git a/front/src/modules/activities/comment/__stories__/mock-comment.ts b/front/src/modules/activities/comment/__stories__/mock-comment.ts new file mode 100644 index 000000000..a5e070234 --- /dev/null +++ b/front/src/modules/activities/comment/__stories__/mock-comment.ts @@ -0,0 +1,40 @@ +import { DateTime } from 'luxon'; + +import { CommentForDrawer } from '@/activities/types/CommentForDrawer'; +import { mockedUsersData } from '~/testing/mock-data/users'; + +const mockUser = mockedUsersData[0]; + +export const mockComment: Pick< + CommentForDrawer, + 'id' | 'author' | 'createdAt' | 'body' | 'updatedAt' +> = { + id: 'fake_comment_1_uuid', + body: 'Hello, this is a comment.', + author: { + id: 'fake_comment_1_author_uuid', + displayName: mockUser.displayName ?? '', + firstName: mockUser.firstName ?? '', + lastName: mockUser.lastName ?? '', + avatarUrl: mockUser.avatarUrl, + }, + createdAt: DateTime.fromFormat('2021-03-12', 'yyyy-MM-dd').toISO() ?? '', + updatedAt: DateTime.fromFormat('2021-03-13', 'yyyy-MM-dd').toISO() ?? '', +}; + +export const mockCommentWithLongValues: Pick< + CommentForDrawer, + 'id' | 'author' | 'createdAt' | 'body' | 'updatedAt' +> = { + id: 'fake_comment_2_uuid', + body: 'Hello, this is a comment. Hello, this is a comment. Hello, this is a comment. Hello, this is a comment. Hello, this is a comment. Hello, this is a comment.', + author: { + id: 'fake_comment_2_author_uuid', + displayName: mockUser.displayName + ' with a very long suffix' ?? '', + firstName: mockUser.firstName ?? '', + lastName: mockUser.lastName ?? '', + avatarUrl: mockUser.avatarUrl, + }, + createdAt: DateTime.fromFormat('2021-03-12', 'yyyy-MM-dd').toISO() ?? '', + updatedAt: DateTime.fromFormat('2021-03-13', 'yyyy-MM-dd').toISO() ?? '', +}; diff --git a/front/src/modules/activities/components/CommentThreadComments.tsx b/front/src/modules/activities/components/CommentThreadComments.tsx index b6f2fba1f..245f46c92 100644 --- a/front/src/modules/activities/components/CommentThreadComments.tsx +++ b/front/src/modules/activities/components/CommentThreadComments.tsx @@ -8,7 +8,7 @@ import { AutosizeTextInput } from '@/ui/input/components/AutosizeTextInput'; import { CommentThread, useCreateCommentMutation } from '~/generated/graphql'; import { isNonEmptyString } from '~/utils/isNonEmptyString'; -import { CommentThreadItem } from '../comment/CommentThreadItem'; +import { Comment } from '../comment/Comment'; import { GET_COMMENT_THREAD } from '../queries'; import { CommentForDrawer } from '../types/CommentForDrawer'; @@ -80,7 +80,7 @@ export function CommentThreadComments({ commentThread }: OwnProps) { Comments {commentThread?.comments?.map((comment, index) => ( - + ))} diff --git a/front/src/modules/activities/components/CommentThreadRelationPicker.tsx b/front/src/modules/activities/components/CommentThreadRelationPicker.tsx index 59b5ce65b..92f2a8e97 100644 --- a/front/src/modules/activities/components/CommentThreadRelationPicker.tsx +++ b/front/src/modules/activities/components/CommentThreadRelationPicker.tsx @@ -152,8 +152,11 @@ export function CommentThreadRelationPicker({ commentThread }: OwnProps) { placement: 'bottom-start', }); - useListenClickOutsideArrayOfRef([refs.floating, refs.domReference], () => { - exitEditMode(); + useListenClickOutsideArrayOfRef({ + refs: [refs.floating, refs.domReference], + callback: () => { + exitEditMode(); + }, }); const selectedEntities = flatMapAndSortEntityForSelectArrayOfArrayByName([ diff --git a/front/src/modules/companies/__stories__/Board.stories.tsx b/front/src/modules/companies/__stories__/Board.stories.tsx index aef3e672a..59de69c84 100644 --- a/front/src/modules/companies/__stories__/Board.stories.tsx +++ b/front/src/modules/companies/__stories__/Board.stories.tsx @@ -7,7 +7,7 @@ import { graphqlMocks } from '~/testing/graphqlMocks'; import { getRenderWrapperForComponent } from '~/testing/renderWrappers'; const meta: Meta = { - title: 'UI/Board/Board', + title: 'Modules/Companies/Board', component: EntityBoard, decorators: [BoardDecorator], }; diff --git a/front/src/modules/companies/__stories__/CompanyBoardCard.stories.tsx b/front/src/modules/companies/__stories__/CompanyBoardCard.stories.tsx index 86345e3fc..928c7ed13 100644 --- a/front/src/modules/companies/__stories__/CompanyBoardCard.stories.tsx +++ b/front/src/modules/companies/__stories__/CompanyBoardCard.stories.tsx @@ -6,7 +6,7 @@ import { graphqlMocks } from '~/testing/graphqlMocks'; import { getRenderWrapperForComponent } from '~/testing/renderWrappers'; const meta: Meta = { - title: 'UI/Board/CompanyBoardCard', + title: 'Modules/Companies/CompanyBoardCard', component: CompanyBoardCard, decorators: [BoardCardDecorator], }; diff --git a/front/src/modules/people/components/__stories__/PeopleChip.stories.tsx b/front/src/modules/people/components/__stories__/PeopleChip.stories.tsx index b3ca5a646..d7e7ef3e2 100644 --- a/front/src/modules/people/components/__stories__/PeopleChip.stories.tsx +++ b/front/src/modules/people/components/__stories__/PeopleChip.stories.tsx @@ -7,7 +7,7 @@ import { getRenderWrapperForComponent } from '~/testing/renderWrappers'; import { PersonChip } from '../PersonChip'; const meta: Meta = { - title: 'Modules/Companies/PersonChip', + title: 'Modules/People/PersonChip', component: PersonChip, }; diff --git a/front/src/modules/settings/profile/components/NameFields.tsx b/front/src/modules/settings/profile/components/NameFields.tsx index c71830712..d72fd1fe9 100644 --- a/front/src/modules/settings/profile/components/NameFields.tsx +++ b/front/src/modules/settings/profile/components/NameFields.tsx @@ -72,9 +72,13 @@ export function NameFields({ }, 500); useEffect(() => { + if (!currentUser) { + return; + } + if ( - currentUser?.firstName !== firstName || - currentUser?.lastName !== lastName + currentUser.firstName !== firstName || + currentUser.lastName !== lastName ) { debouncedUpdate(); } diff --git a/front/src/modules/ui/board/card-field/components/BoardCardEditableFieldEditMode.tsx b/front/src/modules/ui/board/card-field/components/BoardCardEditableFieldEditMode.tsx index e97cbafab..8dffcb6a9 100644 --- a/front/src/modules/ui/board/card-field/components/BoardCardEditableFieldEditMode.tsx +++ b/front/src/modules/ui/board/card-field/components/BoardCardEditableFieldEditMode.tsx @@ -43,8 +43,11 @@ export function BoardCardEditableFieldEditMode({ }: OwnProps) { const wrapperRef = useRef(null); - useListenClickOutsideArrayOfRef([wrapperRef], () => { - onExit(); + useListenClickOutsideArrayOfRef({ + refs: [wrapperRef], + callback: () => { + onExit(); + }, }); useScopedHotkeys( diff --git a/front/src/modules/ui/board/components/EditColumnTitleInput.tsx b/front/src/modules/ui/board/components/EditColumnTitleInput.tsx index 0128cb32e..cf8d4555e 100644 --- a/front/src/modules/ui/board/components/EditColumnTitleInput.tsx +++ b/front/src/modules/ui/board/components/EditColumnTitleInput.tsx @@ -40,8 +40,11 @@ export function EditColumnTitleInput({ }) { const inputRef = React.useRef(null); - useListenClickOutsideArrayOfRef([inputRef], () => { - onFocusLeave(); + useListenClickOutsideArrayOfRef({ + refs: [inputRef], + callback: () => { + onFocusLeave(); + }, }); const setHotkeyScope = useSetHotkeyScope(); setHotkeyScope(ColumnHotkeyScope.EditColumnName, { goto: false }); diff --git a/front/src/modules/ui/button/components/IconButton.tsx b/front/src/modules/ui/button/components/IconButton.tsx index 51e2e3d89..e240d4fc0 100644 --- a/front/src/modules/ui/button/components/IconButton.tsx +++ b/front/src/modules/ui/button/components/IconButton.tsx @@ -83,14 +83,20 @@ const StyledIconButton = styled.button>` } }}; justify-content: center; + outline: none; padding: 0; transition: background 0.1s ease; - user-select: none; &:hover { background: ${({ theme, disabled }) => { return disabled ? 'auto' : theme.background.transparent.light; }}; } + user-select: none; + &:active { + background: ${({ theme, disabled }) => { + return disabled ? 'auto' : theme.background.transparent.medium; + }}; + } width: ${({ size }) => { switch (size) { case 'large': @@ -102,11 +108,6 @@ const StyledIconButton = styled.button>` return '20px'; } }}; - &:active { - background: ${({ theme, disabled }) => { - return disabled ? 'auto' : theme.background.transparent.medium; - }}; - } `; export function IconButton({ diff --git a/front/src/modules/ui/button/components/RoundedIconButton.tsx b/front/src/modules/ui/button/components/RoundedIconButton.tsx index ae49fffd7..6409daa1d 100644 --- a/front/src/modules/ui/button/components/RoundedIconButton.tsx +++ b/front/src/modules/ui/button/components/RoundedIconButton.tsx @@ -14,15 +14,16 @@ const StyledIconButton = styled.button` justify-content: center; + outline: none; padding: 0; transition: color 0.1s ease-in-out, background 0.1s ease-in-out; - width: 20px; &:disabled { background: ${({ theme }) => theme.background.quaternary}; color: ${({ theme }) => theme.font.color.tertiary}; cursor: default; } + width: 20px; `; export function RoundedIconButton({ diff --git a/front/src/modules/ui/button/components/__stories__/Button.stories.tsx b/front/src/modules/ui/button/components/__stories__/Button.stories.tsx index 8e96866fb..8501c2b59 100644 --- a/front/src/modules/ui/button/components/__stories__/Button.stories.tsx +++ b/front/src/modules/ui/button/components/__stories__/Button.stories.tsx @@ -56,7 +56,7 @@ const StyledButtonContainer = styled.div` `; const meta: Meta = { - title: 'UI/Buttons/Button', + title: 'UI/Button/Button', component: Button, decorators: [withKnobs], }; diff --git a/front/src/modules/ui/button/components/__stories__/IconButton.stories.tsx b/front/src/modules/ui/button/components/__stories__/IconButton.stories.tsx index c683bb19f..d860aae2e 100644 --- a/front/src/modules/ui/button/components/__stories__/IconButton.stories.tsx +++ b/front/src/modules/ui/button/components/__stories__/IconButton.stories.tsx @@ -53,7 +53,7 @@ const StyledIconButtonContainer = styled.div` `; const meta: Meta = { - title: 'UI/Buttons/IconButton', + title: 'UI/Button/IconButton', component: IconButton, decorators: [withKnobs], }; diff --git a/front/src/modules/ui/button/components/__stories__/MainButton.stories.tsx b/front/src/modules/ui/button/components/__stories__/MainButton.stories.tsx index 803d8dacc..78df7e03b 100644 --- a/front/src/modules/ui/button/components/__stories__/MainButton.stories.tsx +++ b/front/src/modules/ui/button/components/__stories__/MainButton.stories.tsx @@ -8,7 +8,7 @@ import { getRenderWrapperForComponent } from '~/testing/renderWrappers'; import { MainButton } from '../MainButton'; const meta: Meta = { - title: 'UI/Buttons/MainButton', + title: 'UI/Button/MainButton', component: MainButton, }; diff --git a/front/src/modules/ui/button/components/__stories__/RoundedIconButton.stories.tsx b/front/src/modules/ui/button/components/__stories__/RoundedIconButton.stories.tsx index befb3056c..f4b362512 100644 --- a/front/src/modules/ui/button/components/__stories__/RoundedIconButton.stories.tsx +++ b/front/src/modules/ui/button/components/__stories__/RoundedIconButton.stories.tsx @@ -8,7 +8,7 @@ import { getRenderWrapperForComponent } from '~/testing/renderWrappers'; import { RoundedIconButton } from '../RoundedIconButton'; const meta: Meta = { - title: 'UI/Buttons/RoundedIconButton', + title: 'UI/Button/RoundedIconButton', component: RoundedIconButton, }; diff --git a/front/src/modules/ui/dropdown/components/__stories__/DropdownMenu.stories.tsx b/front/src/modules/ui/dropdown/components/__stories__/DropdownMenu.stories.tsx index f81080b72..a418f5105 100644 --- a/front/src/modules/ui/dropdown/components/__stories__/DropdownMenu.stories.tsx +++ b/front/src/modules/ui/dropdown/components/__stories__/DropdownMenu.stories.tsx @@ -15,7 +15,7 @@ import { DropdownMenuSelectableItem } from '../DropdownMenuSelectableItem'; import { DropdownMenuSeparator } from '../DropdownMenuSeparator'; const meta: Meta = { - title: 'UI/Menu/DropdownMenu', + title: 'UI/Dropdown/DropdownMenu', component: DropdownMenu, }; diff --git a/front/src/modules/ui/editable-field/hooks/useRegisterCloseFieldHandlers.ts b/front/src/modules/ui/editable-field/hooks/useRegisterCloseFieldHandlers.ts index 2d60da4b1..767cb8383 100644 --- a/front/src/modules/ui/editable-field/hooks/useRegisterCloseFieldHandlers.ts +++ b/front/src/modules/ui/editable-field/hooks/useRegisterCloseFieldHandlers.ts @@ -12,11 +12,14 @@ export function useRegisterCloseFieldHandlers( ) { const { closeEditableField, isFieldInEditMode } = useEditableField(); - useListenClickOutsideArrayOfRef([wrapperRef], () => { - if (isFieldInEditMode) { - onSubmit?.(); - closeEditableField(); - } + useListenClickOutsideArrayOfRef({ + refs: [wrapperRef], + callback: () => { + if (isFieldInEditMode) { + onSubmit?.(); + closeEditableField(); + } + }, }); useScopedHotkeys( diff --git a/front/src/modules/ui/filter-n-sort/components/DropdownButton.tsx b/front/src/modules/ui/filter-n-sort/components/DropdownButton.tsx index 7a106a9b7..11249c18e 100644 --- a/front/src/modules/ui/filter-n-sort/components/DropdownButton.tsx +++ b/front/src/modules/ui/filter-n-sort/components/DropdownButton.tsx @@ -3,7 +3,7 @@ import styled from '@emotion/styled'; import { Key } from 'ts-key-enum'; import { DropdownMenu } from '@/ui/dropdown/components/DropdownMenu'; -import { useOutsideAlerter } from '@/ui/hooks/useOutsideAlerter'; +import { useListenClickOutsideArrayOfRef } from '@/ui/hooks/useListenClickOutsideArrayOfRef'; import { useScopedHotkeys } from '@/ui/hotkey/hooks/useScopedHotkeys'; import { IconChevronDown } from '@/ui/icon/index'; @@ -105,7 +105,10 @@ function DropdownButton({ }; const dropdownRef = useRef(null); - useOutsideAlerter({ ref: dropdownRef, callback: onOutsideClick }); + useListenClickOutsideArrayOfRef({ + refs: [dropdownRef], + callback: onOutsideClick, + }); return ( diff --git a/front/src/modules/ui/hooks/__tests__/useListenClickOutsideArrayOfRef.test.tsx b/front/src/modules/ui/hooks/__tests__/useListenClickOutsideArrayOfRef.test.tsx new file mode 100644 index 000000000..b289994c9 --- /dev/null +++ b/front/src/modules/ui/hooks/__tests__/useListenClickOutsideArrayOfRef.test.tsx @@ -0,0 +1,39 @@ +import { useRef } from 'react'; +import { fireEvent, render } from '@testing-library/react'; + +import { useListenClickOutsideArrayOfRef } from '../useListenClickOutsideArrayOfRef'; + +const onOutsideClick = jest.fn(); + +function TestComponentDomMode() { + const buttonRef = useRef(null); + const buttonRef2 = useRef(null); + useListenClickOutsideArrayOfRef({ + refs: [buttonRef, buttonRef2], + callback: onOutsideClick, + }); + + return ( +
+ Outside + + +
+ ); +} + +test('useListenClickOutsideArrayOfRef hook works in dom mode', async () => { + const { getByText } = render(); + const inside = getByText('Inside'); + const inside2 = getByText('Inside 2'); + const outside = getByText('Outside'); + + fireEvent.click(inside); + expect(onOutsideClick).toHaveBeenCalledTimes(0); + + fireEvent.click(inside2); + expect(onOutsideClick).toHaveBeenCalledTimes(0); + + fireEvent.click(outside); + expect(onOutsideClick).toHaveBeenCalledTimes(1); +}); diff --git a/front/src/modules/ui/hooks/__tests__/useOutsideAlerter.test.tsx b/front/src/modules/ui/hooks/__tests__/useOutsideAlerter.test.tsx deleted file mode 100644 index 65319c575..000000000 --- a/front/src/modules/ui/hooks/__tests__/useOutsideAlerter.test.tsx +++ /dev/null @@ -1,31 +0,0 @@ -import { useRef } from 'react'; -import { act } from 'react-dom/test-utils'; -import { fireEvent, render } from '@testing-library/react'; - -import { useOutsideAlerter } from '../useOutsideAlerter'; -const onOutsideClick = jest.fn(); - -function TestComponent() { - const buttonRef = useRef(null); - useOutsideAlerter({ ref: buttonRef, callback: onOutsideClick }); - - return ( -
- Outside - -
- ); -} - -test('useOutsideAlerter hook works properly', async () => { - const { getByText } = render(); - const inside = getByText('Inside'); - const outside = getByText('Outside'); - await act(() => Promise.resolve()); - - fireEvent.mouseDown(inside); - expect(onOutsideClick).toHaveBeenCalledTimes(0); - - fireEvent.mouseDown(outside); - expect(onOutsideClick).toHaveBeenCalledTimes(1); -}); diff --git a/front/src/modules/ui/hooks/useListenClickOutsideArrayOfRef.ts b/front/src/modules/ui/hooks/useListenClickOutsideArrayOfRef.ts index d5270f67a..eba805689 100644 --- a/front/src/modules/ui/hooks/useListenClickOutsideArrayOfRef.ts +++ b/front/src/modules/ui/hooks/useListenClickOutsideArrayOfRef.ts @@ -2,34 +2,75 @@ import React, { useEffect } from 'react'; import { isDefined } from '~/utils/isDefined'; -export function useListenClickOutsideArrayOfRef( - arrayOfRef: Array>, - outsideClickCallback: (event?: MouseEvent | TouchEvent) => void, -) { +export enum ClickOutsideMode { + absolute = 'absolute', + dom = 'dom', +} + +export function useListenClickOutsideArrayOfRef({ + refs, + callback, + mode = ClickOutsideMode.dom, +}: { + refs: Array>; + callback: (event?: MouseEvent | TouchEvent) => void; + mode?: ClickOutsideMode; +}) { useEffect(() => { function handleClickOutside(event: MouseEvent | TouchEvent) { - const clickedOnAtLeastOneRef = arrayOfRef - .filter((ref) => !!ref.current) - .some((ref) => ref.current?.contains(event.target as Node)); + if (mode === ClickOutsideMode.dom) { + const clickedOnAtLeastOneRef = refs + .filter((ref) => !!ref.current) + .some((ref) => ref.current?.contains(event.target as Node)); - if (!clickedOnAtLeastOneRef) { - outsideClickCallback(event); + if (!clickedOnAtLeastOneRef) { + callback(event); + } + } + + if (mode === ClickOutsideMode.absolute) { + const clickedOnAtLeastOneRef = refs + .filter((ref) => !!ref.current) + .some((ref) => { + if (!ref.current) { + return false; + } + + const { x, y, width, height } = ref.current.getBoundingClientRect(); + + const clientX = + 'clientX' in event ? event.clientX : event.touches[0].clientX; + const clientY = + 'clientY' in event ? event.clientY : event.touches[0].clientY; + + console.log(clientX, clientY, x, y, width, height); + + if ( + clientX < x || + clientX > x + width || + clientY < y || + clientY > y + height + ) { + return false; + } + return true; + }); + if (!clickedOnAtLeastOneRef) { + callback(event); + } } } - const hasAtLeastOneRefDefined = arrayOfRef.some((ref) => - isDefined(ref.current), - ); + const hasAtLeastOneRefDefined = refs.some((ref) => isDefined(ref.current)); if (hasAtLeastOneRefDefined) { - document.addEventListener('mouseup', handleClickOutside); + document.addEventListener('click', handleClickOutside); document.addEventListener('touchend', handleClickOutside); } return () => { - document.removeEventListener('mouseup', handleClickOutside); + document.removeEventListener('click', handleClickOutside); document.removeEventListener('touchend', handleClickOutside); }; - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [arrayOfRef, outsideClickCallback]); + }, [refs, callback, mode]); } diff --git a/front/src/modules/ui/hooks/useOutsideAlerter.ts b/front/src/modules/ui/hooks/useOutsideAlerter.ts deleted file mode 100644 index 5a77591c6..000000000 --- a/front/src/modules/ui/hooks/useOutsideAlerter.ts +++ /dev/null @@ -1,52 +0,0 @@ -import { useEffect } from 'react'; - -export enum OutsideClickAlerterMode { - absolute = 'absolute', - dom = 'dom', -} - -type OwnProps = { - ref: React.RefObject; - callback: () => void; - mode?: OutsideClickAlerterMode; -}; - -export function useOutsideAlerter({ - ref, - mode = OutsideClickAlerterMode.dom, - callback, -}: OwnProps) { - useEffect(() => { - function handleClickOutside(event: MouseEvent) { - const target = event.target as HTMLButtonElement; - - if (!ref.current) { - return; - } - - if ( - mode === OutsideClickAlerterMode.dom && - !ref.current.contains(target) - ) { - callback(); - } - - if (mode === OutsideClickAlerterMode.absolute) { - const { x, y, width, height } = ref.current.getBoundingClientRect(); - const { clientX, clientY } = event; - if ( - clientX < x || - clientX > x + width || - clientY < y || - clientY > y + height - ) { - callback(); - } - } - } - document.addEventListener('mousedown', handleClickOutside); - return () => { - document.removeEventListener('mousedown', handleClickOutside); - }; - }, [ref, callback, mode]); -} diff --git a/front/src/modules/ui/relation-picker/components/SingleEntitySelect.tsx b/front/src/modules/ui/relation-picker/components/SingleEntitySelect.tsx index 385a47697..33d41c9ec 100644 --- a/front/src/modules/ui/relation-picker/components/SingleEntitySelect.tsx +++ b/front/src/modules/ui/relation-picker/components/SingleEntitySelect.tsx @@ -46,8 +46,11 @@ export function SingleEntitySelect< const showCreateButton = isDefined(onCreate) && searchFilter !== ''; - useListenClickOutsideArrayOfRef([containerRef], () => { - onCancel?.(); + useListenClickOutsideArrayOfRef({ + refs: [containerRef], + callback: () => { + onCancel?.(); + }, }); return ( diff --git a/front/src/modules/ui/right-drawer/components/RightDrawer.tsx b/front/src/modules/ui/right-drawer/components/RightDrawer.tsx index 4dcae5291..97bde8650 100644 --- a/front/src/modules/ui/right-drawer/components/RightDrawer.tsx +++ b/front/src/modules/ui/right-drawer/components/RightDrawer.tsx @@ -5,9 +5,9 @@ import { motion } from 'framer-motion'; import { useRecoilState } from 'recoil'; import { - OutsideClickAlerterMode, - useOutsideAlerter, -} from '@/ui/hooks/useOutsideAlerter'; + ClickOutsideMode, + useListenClickOutsideArrayOfRef, +} from '@/ui/hooks/useListenClickOutsideArrayOfRef'; import { isDefined } from '~/utils/isDefined'; import { isRightDrawerOpenState } from '../states/isRightDrawerOpenState'; @@ -41,10 +41,10 @@ export function RightDrawer() { const [rightDrawerPage] = useRecoilState(rightDrawerPageState); const rightDrawerRef = useRef(null); - useOutsideAlerter({ - ref: rightDrawerRef, + useListenClickOutsideArrayOfRef({ + refs: [rightDrawerRef], callback: () => setIsRightDrawerOpen(false), - mode: OutsideClickAlerterMode.absolute, + mode: ClickOutsideMode.absolute, }); const theme = useTheme(); if (!isDefined(rightDrawerPage)) { diff --git a/front/src/modules/ui/table/components/EntityTable.tsx b/front/src/modules/ui/table/components/EntityTable.tsx index 9ad7f1eed..86ce6d907 100644 --- a/front/src/modules/ui/table/components/EntityTable.tsx +++ b/front/src/modules/ui/table/components/EntityTable.tsx @@ -90,8 +90,11 @@ export function EntityTable({ const leaveTableFocus = useLeaveTableFocus(); - useListenClickOutsideArrayOfRef([tableBodyRef], () => { - leaveTableFocus(); + useListenClickOutsideArrayOfRef({ + refs: [tableBodyRef], + callback: () => { + leaveTableFocus(); + }, }); return ( diff --git a/front/src/modules/ui/table/editable-cell/hooks/useRegisterCloseCellHandlers.ts b/front/src/modules/ui/table/editable-cell/hooks/useRegisterCloseCellHandlers.ts index 661410522..997a558bb 100644 --- a/front/src/modules/ui/table/editable-cell/hooks/useRegisterCloseCellHandlers.ts +++ b/front/src/modules/ui/table/editable-cell/hooks/useRegisterCloseCellHandlers.ts @@ -14,11 +14,14 @@ export function useRegisterCloseCellHandlers( ) { const { closeEditableCell } = useEditableCell(); const { isCurrentCellInEditMode } = useCurrentCellEditMode(); - useListenClickOutsideArrayOfRef([wrapperRef], () => { - if (isCurrentCellInEditMode) { - onSubmit?.(); - closeEditableCell(); - } + useListenClickOutsideArrayOfRef({ + refs: [wrapperRef], + callback: () => { + if (isCurrentCellInEditMode) { + onSubmit?.(); + closeEditableCell(); + } + }, }); const { moveRight, moveLeft, moveDown } = useMoveSoftFocus(); diff --git a/front/src/pages/companies/__stories__/Companies.mdx b/front/src/pages/companies/__stories__/Companies.mdx deleted file mode 100644 index 1c85ce2ea..000000000 --- a/front/src/pages/companies/__stories__/Companies.mdx +++ /dev/null @@ -1,11 +0,0 @@ -{ /* Companies.mdx */ } - -import { Canvas, Meta } from '@storybook/blocks'; - -import * as Companies from './Companies.stories'; - - - -# Companies View - - \ No newline at end of file diff --git a/front/src/pages/companies/__stories__/Company.stories.tsx b/front/src/pages/companies/__stories__/Company.stories.tsx index a3f90ef7b..6d05ca115 100644 --- a/front/src/pages/companies/__stories__/Company.stories.tsx +++ b/front/src/pages/companies/__stories__/Company.stories.tsx @@ -1,4 +1,5 @@ import { getOperationName } from '@apollo/client/utilities'; +import { expect } from '@storybook/jest'; import type { Meta, StoryObj } from '@storybook/react'; import { within } from '@storybook/testing-library'; import { graphql } from 'msw'; @@ -25,19 +26,66 @@ export default meta; export type Story = StoryObj; +const companyShowCommonGraphqlMocks = [ + graphql.query( + getOperationName(GET_COMMENT_THREADS_BY_TARGETS) ?? '', + (req, res, ctx) => { + return res( + ctx.data({ + findManyCommentThreads: mockedCommentThreads, + }), + ); + }, + ), + graphql.query(getOperationName(GET_COMPANY) ?? '', (req, res, ctx) => { + return res( + ctx.data({ + findUniqueCompany: mockedCompaniesData[0], + }), + ); + }), +]; + export const Default: Story = { + render: getRenderWrapperForPage( + , + '/companies/89bb825c-171e-4bcc-9cf7-43448d6fb278', + ), + parameters: { + msw: [...graphqlMocks, ...companyShowCommonGraphqlMocks], + }, +}; + +export const EditNote: Story = { render: getRenderWrapperForPage( , '/companies/89bb825c-171e-4bcc-9cf7-43448d6fb278', ), play: async ({ canvasElement }) => { const canvas = within(canvasElement); - const notesButton = await canvas.findByText('Note'); - await notesButton.click(); + const firstNoteTitle = await canvas.findByText('My very first note'); + await firstNoteTitle.click(); + + expect( + await canvas.findByDisplayValue('My very first note'), + ).toBeInTheDocument(); + + const workspaceName = await canvas.findByText('Twenty'); + await workspaceName.click(); + + expect(await canvas.queryByDisplayValue('My very first note')).toBeNull(); + + const noteButton = await canvas.findByText('Note'); + await noteButton.click(); + + expect( + await canvas.findByDisplayValue('My very first note'), + ).toBeInTheDocument(); }, parameters: { msw: [ ...graphqlMocks, + ...companyShowCommonGraphqlMocks, graphql.mutation( getOperationName(CREATE_COMMENT_THREAD_WITH_COMMENT) ?? '', (req, res, ctx) => { @@ -48,33 +96,17 @@ export const Default: Story = { ); }, ), - graphql.query( - getOperationName(GET_COMMENT_THREADS_BY_TARGETS) ?? '', - (req, res, ctx) => { - return res( - ctx.data({ - findManyCommentThreads: mockedCommentThreads, - }), - ); - }, - ), graphql.query( getOperationName(GET_COMMENT_THREAD) ?? '', (req, res, ctx) => { + console.log('coucou'); return res( ctx.data({ - findManyCommentThreads: mockedCommentThreads[0], + findManyCommentThreads: [mockedCommentThreads[0]], }), ); }, ), - graphql.query(getOperationName(GET_COMPANY) ?? '', (req, res, ctx) => { - return res( - ctx.data({ - findUniqueCompany: mockedCompaniesData[0], - }), - ); - }), ], }, }; diff --git a/front/src/pages/opportunities/__stories__/Opportunities.stories.tsx b/front/src/pages/opportunities/__stories__/Opportunities.stories.tsx index 1f2c81657..1fefb9cf7 100644 --- a/front/src/pages/opportunities/__stories__/Opportunities.stories.tsx +++ b/front/src/pages/opportunities/__stories__/Opportunities.stories.tsx @@ -6,7 +6,7 @@ import { getRenderWrapperForPage } from '~/testing/renderWrappers'; import { Opportunities } from '../Opportunities'; const meta: Meta = { - title: 'Pages/Opportunities', + title: 'Pages/Opportunities/Default', component: Opportunities, }; diff --git a/front/src/pages/people/__stories__/People.mdx b/front/src/pages/people/__stories__/People.mdx deleted file mode 100644 index acbca2095..000000000 --- a/front/src/pages/people/__stories__/People.mdx +++ /dev/null @@ -1,11 +0,0 @@ -{ /* People.mdx */ } - -import { Canvas, Meta } from '@storybook/blocks'; - -import * as People from './People.stories'; - - - -# People View - - \ No newline at end of file diff --git a/front/src/pages/settings/__stories__/SettingsProfile.stories.tsx b/front/src/pages/settings/__stories__/SettingsProfile.stories.tsx index c09725f2a..526d33a6d 100644 --- a/front/src/pages/settings/__stories__/SettingsProfile.stories.tsx +++ b/front/src/pages/settings/__stories__/SettingsProfile.stories.tsx @@ -1,6 +1,8 @@ import type { Meta, StoryObj } from '@storybook/react'; +import { within } from '@storybook/testing-library'; import { graphqlMocks } from '~/testing/graphqlMocks'; +import { mockedUserJWT } from '~/testing/mock-data/jwt'; import { getRenderWrapperForPage } from '~/testing/renderWrappers'; import { SettingsProfile } from '../SettingsProfile'; @@ -16,6 +18,21 @@ export type Story = StoryObj; export const Default: Story = { render: getRenderWrapperForPage(, '/settings/profile'), + parameters: { + msw: graphqlMocks, + cookie: { + tokenPair: `{%22accessToken%22:{%22token%22:%22${mockedUserJWT}%22%2C%22expiresAt%22:%222023-07-18T15:06:40.704Z%22%2C%22__typename%22:%22AuthToken%22}%2C%22refreshToken%22:{%22token%22:%22${mockedUserJWT}%22%2C%22expiresAt%22:%222023-10-15T15:06:41.558Z%22%2C%22__typename%22:%22AuthToken%22}%2C%22__typename%22:%22AuthTokenPair%22}`, + }, + }, +}; + +export const LogOut: Story = { + render: getRenderWrapperForPage(, '/settings/profile'), + play: async ({ canvasElement }) => { + const canvas = within(canvasElement); + const logoutButton = await canvas.findByText('Logout'); + await logoutButton.click(); + }, parameters: { msw: graphqlMocks, }, diff --git a/front/src/pages/settings/__stories__/SettingsWorkspaceMembers.stories.tsx b/front/src/pages/settings/__stories__/SettingsWorkspaceMembers.stories.tsx index 0f1a74cae..f358ffc05 100644 --- a/front/src/pages/settings/__stories__/SettingsWorkspaceMembers.stories.tsx +++ b/front/src/pages/settings/__stories__/SettingsWorkspaceMembers.stories.tsx @@ -1,6 +1,7 @@ import type { Meta, StoryObj } from '@storybook/react'; import { graphqlMocks } from '~/testing/graphqlMocks'; +import { mockedUserJWT } from '~/testing/mock-data/jwt'; import { getRenderWrapperForPage } from '~/testing/renderWrappers'; import { SettingsWorkspaceMembers } from '../SettingsWorkspaceMembers'; @@ -21,5 +22,8 @@ export const Default: Story = { ), parameters: { msw: graphqlMocks, + cookie: { + tokenPair: `{%22accessToken%22:{%22token%22:%22${mockedUserJWT}%22%2C%22expiresAt%22:%222023-07-18T15:06:40.704Z%22%2C%22__typename%22:%22AuthToken%22}%2C%22refreshToken%22:{%22token%22:%22${mockedUserJWT}%22%2C%22expiresAt%22:%222023-10-15T15:06:41.558Z%22%2C%22__typename%22:%22AuthToken%22}%2C%22__typename%22:%22AuthTokenPair%22}`, + }, }, }; diff --git a/front/src/testing/graphqlMocks.ts b/front/src/testing/graphqlMocks.ts index 14ed24c0a..ec98c71d2 100644 --- a/front/src/testing/graphqlMocks.ts +++ b/front/src/testing/graphqlMocks.ts @@ -8,6 +8,7 @@ import { GET_PEOPLE, UPDATE_PERSON } from '@/people/queries'; import { GET_PIPELINE_PROGRESS, GET_PIPELINES } from '@/pipeline/queries'; import { SEARCH_COMPANY_QUERY, + SEARCH_PEOPLE_QUERY, SEARCH_USER_QUERY, } from '@/search/queries/search'; import { GET_CURRENT_USER } from '@/users/queries'; @@ -15,6 +16,7 @@ import { GetCompaniesQuery, GetPeopleQuery, SearchCompanyQuery, + SearchPeopleQuery, SearchUserQuery, } from '~/generated/graphql'; @@ -61,6 +63,26 @@ export const graphqlMocks = [ ); }, ), + graphql.query( + getOperationName(SEARCH_PEOPLE_QUERY) ?? '', + (req, res, ctx) => { + const returnedMockedData = filterAndSortData< + SearchPeopleQuery['searchResults'][0] + >( + mockedPeopleData, + req.variables.where, + Array.isArray(req.variables.orderBy) + ? req.variables.orderBy + : [req.variables.orderBy], + req.variables.limit, + ); + return res( + ctx.data({ + searchResults: returnedMockedData, + }), + ); + }, + ), graphql.query(getOperationName(SEARCH_USER_QUERY) ?? '', (req, res, ctx) => { const returnedMockedData = filterAndSortData< SearchUserQuery['searchResults'][0] diff --git a/front/src/testing/mock-data/comment-threads.ts b/front/src/testing/mock-data/comment-threads.ts index f654d9910..9c44a2d5a 100644 --- a/front/src/testing/mock-data/comment-threads.ts +++ b/front/src/testing/mock-data/comment-threads.ts @@ -1,4 +1,5 @@ import { + ActivityType, Comment, CommentableType, CommentThread, @@ -11,6 +12,7 @@ type MockedCommentThread = Pick< | 'createdAt' | 'updatedAt' | '__typename' + | 'type' | 'body' | 'title' | 'authorId' @@ -42,6 +44,7 @@ export const mockedCommentThreads: Array = [ createdAt: '2023-04-26T10:12:42.33625+00:00', updatedAt: '2023-04-26T10:23:42.33625+00:00', title: 'My very first note', + type: ActivityType.Note, body: null, author: { id: '374fe3a5-df1e-4119-afe0-2a62a2ba481e', @@ -88,6 +91,7 @@ export const mockedCommentThreads: Array = [ updatedAt: new Date().toISOString(), title: 'Another note', body: null, + type: ActivityType.Note, author: { id: '374fe3a5-df1e-4119-afe0-2a62a2ba481e', firstName: 'Charles', diff --git a/front/src/testing/mock-data/companies.ts b/front/src/testing/mock-data/companies.ts index cf784baea..60664a38e 100644 --- a/front/src/testing/mock-data/companies.ts +++ b/front/src/testing/mock-data/companies.ts @@ -37,6 +37,7 @@ export const mockedCompaniesData: Array = [ displayName: 'Charles Test', firstName: 'Charles', lastName: 'Test', + avatarUrl: null, id: '7dfbc3f7-6e5e-4128-957e-8d86808cdf6b', __typename: 'User', }, diff --git a/front/yarn.lock b/front/yarn.lock index d4160fdf8..963b0b28a 100644 --- a/front/yarn.lock +++ b/front/yarn.lock @@ -17209,6 +17209,11 @@ store2@^2.14.2: resolved "https://registry.yarnpkg.com/store2/-/store2-2.14.2.tgz#56138d200f9fe5f582ad63bc2704dbc0e4a45068" integrity sha512-siT1RiqlfQnGqgT/YzXVUNsom9S0H1OX+dpdGN1xkyYATo4I6sep5NmsRD/40s3IIOvlCq6akxkqG82urIZW1w== +storybook-addon-cookie@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/storybook-addon-cookie/-/storybook-addon-cookie-3.0.1.tgz#e692fb72ce52063bf427eeac68dc46641da3178f" + integrity sha512-ne3jttK7rcJ2Lc5eZPi6h6/erKyIcfzuNPxLtvAssl+HRUtJ6OB4iEvYFFV9/nTxsuNlBkUILEkImqRFf/Bppg== + storybook-addon-pseudo-states@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/storybook-addon-pseudo-states/-/storybook-addon-pseudo-states-2.1.0.tgz#05faffb7e0d19fc012035ecee2a02d432f312d2d"