Improve test coverage and refactor storybook arch (#723)
* Improve test coverage and refactor storybook arch * Fix coverage * Fix tests * Fix lint * Fix lint
This commit is contained in:
@ -55,6 +55,7 @@ module.exports = {
|
|||||||
"@storybook/addon-styling",
|
"@storybook/addon-styling",
|
||||||
"@storybook/addon-knobs",
|
"@storybook/addon-knobs",
|
||||||
"storybook-addon-pseudo-states",
|
"storybook-addon-pseudo-states",
|
||||||
|
"storybook-addon-cookie",
|
||||||
],
|
],
|
||||||
framework: {
|
framework: {
|
||||||
name: '@storybook/react-webpack5',
|
name: '@storybook/react-webpack5',
|
||||||
|
|||||||
@ -28,6 +28,11 @@ const preview: Preview = {
|
|||||||
date: /Date$/,
|
date: /Date$/,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
options: {
|
||||||
|
storySort: {
|
||||||
|
order: ['UI', 'Modules', 'Pages'],
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -155,6 +155,7 @@
|
|||||||
"prop-types": "^15.8.1",
|
"prop-types": "^15.8.1",
|
||||||
"react-scripts": "5.0.1",
|
"react-scripts": "5.0.1",
|
||||||
"storybook": "^7.0.22",
|
"storybook": "^7.0.22",
|
||||||
|
"storybook-addon-cookie": "^3.0.1",
|
||||||
"storybook-addon-pseudo-states": "^2.1.0",
|
"storybook-addon-pseudo-states": "^2.1.0",
|
||||||
"ts-jest": "^29.1.0",
|
"ts-jest": "^29.1.0",
|
||||||
"typescript": "^4.9.3",
|
"typescript": "^4.9.3",
|
||||||
@ -164,8 +165,9 @@
|
|||||||
"workerDirectory": "public"
|
"workerDirectory": "public"
|
||||||
},
|
},
|
||||||
"nyc": {
|
"nyc": {
|
||||||
"lines": 60,
|
"lines": 65,
|
||||||
"statements": 60,
|
"statements": 65,
|
||||||
|
"functions": 60,
|
||||||
"exclude": [
|
"exclude": [
|
||||||
"src/generated/**/*"
|
"src/generated/**/*"
|
||||||
]
|
]
|
||||||
|
|||||||
@ -2,7 +2,6 @@ import type { Meta, StoryObj } from '@storybook/react';
|
|||||||
|
|
||||||
import { App } from '~/App';
|
import { App } from '~/App';
|
||||||
import { graphqlMocks } from '~/testing/graphqlMocks';
|
import { graphqlMocks } from '~/testing/graphqlMocks';
|
||||||
import { mockedUserJWT } from '~/testing/mock-data/jwt';
|
|
||||||
|
|
||||||
import { render } from './shared';
|
import { render } from './shared';
|
||||||
|
|
||||||
@ -16,14 +15,6 @@ export type Story = StoryObj<typeof App>;
|
|||||||
|
|
||||||
export const Default: Story = {
|
export const Default: Story = {
|
||||||
render,
|
render,
|
||||||
loaders: [
|
|
||||||
async () => ({
|
|
||||||
accessTokenStored: window.localStorage.setItem(
|
|
||||||
'accessToken',
|
|
||||||
mockedUserJWT,
|
|
||||||
),
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
parameters: {
|
parameters: {
|
||||||
msw: graphqlMocks,
|
msw: graphqlMocks,
|
||||||
},
|
},
|
||||||
|
|||||||
@ -30,7 +30,7 @@ const StyledCommentBody = styled.div`
|
|||||||
text-align: left;
|
text-align: left;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export function CommentThreadItem({ comment, actionBar }: OwnProps) {
|
export function Comment({ comment, actionBar }: OwnProps) {
|
||||||
return (
|
return (
|
||||||
<StyledContainer>
|
<StyledContainer>
|
||||||
<CommentHeader comment={comment} actionBar={actionBar} />
|
<CommentHeader comment={comment} actionBar={actionBar} />
|
||||||
@ -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<typeof Comment> = {
|
||||||
|
title: 'Modules/Activity/Comment/Comment',
|
||||||
|
component: Comment,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default meta;
|
||||||
|
type Story = StoryObj<typeof Comment>;
|
||||||
|
|
||||||
|
export const Default: Story = {
|
||||||
|
render: getRenderWrapperForComponent(<Comment comment={mockComment} />),
|
||||||
|
};
|
||||||
|
|
||||||
|
export const WithLongValues: Story = {
|
||||||
|
render: getRenderWrapperForComponent(
|
||||||
|
<Comment comment={mockCommentWithLongValues} />,
|
||||||
|
),
|
||||||
|
};
|
||||||
@ -1,92 +1,34 @@
|
|||||||
import type { Meta, StoryObj } from '@storybook/react';
|
import type { Meta, StoryObj } from '@storybook/react';
|
||||||
import { DateTime } from 'luxon';
|
|
||||||
|
|
||||||
import { CommentThreadActionBar } from '@/activities/right-drawer/components/CommentThreadActionBar';
|
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 { getRenderWrapperForComponent } from '~/testing/renderWrappers';
|
||||||
|
|
||||||
import { CommentHeader } from '../CommentHeader';
|
import { CommentHeader } from '../CommentHeader';
|
||||||
|
|
||||||
|
import { mockComment, mockCommentWithLongValues } from './mock-comment';
|
||||||
|
|
||||||
const meta: Meta<typeof CommentHeader> = {
|
const meta: Meta<typeof CommentHeader> = {
|
||||||
title: 'Modules/Comments/CommentHeader',
|
title: 'Modules/Activity/Comment/CommentHeader',
|
||||||
component: CommentHeader,
|
component: CommentHeader,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default meta;
|
export default meta;
|
||||||
type Story = StoryObj<typeof CommentHeader>;
|
type Story = StoryObj<typeof CommentHeader>;
|
||||||
|
|
||||||
const mockUser = mockedUsersData[0];
|
|
||||||
|
|
||||||
const mockComment: Pick<CommentForDrawer, 'id' | 'author' | 'createdAt'> = {
|
|
||||||
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 = {
|
export const Default: Story = {
|
||||||
render: getRenderWrapperForComponent(
|
render: getRenderWrapperForComponent(<CommentHeader comment={mockComment} />),
|
||||||
<CommentHeader
|
|
||||||
comment={{
|
|
||||||
...mockComment,
|
|
||||||
createdAt: DateTime.now().minus({ hours: 2 }).toISO() ?? '',
|
|
||||||
}}
|
|
||||||
/>,
|
|
||||||
),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const FewDaysAgo: Story = {
|
export const FewDaysAgo: Story = {
|
||||||
render: getRenderWrapperForComponent(
|
render: getRenderWrapperForComponent(<CommentHeader comment={mockComment} />),
|
||||||
<CommentHeader
|
|
||||||
comment={{
|
|
||||||
...mockComment,
|
|
||||||
createdAt: DateTime.now().minus({ days: 2 }).toISO() ?? '',
|
|
||||||
}}
|
|
||||||
/>,
|
|
||||||
),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const FewMonthsAgo: Story = {
|
export const FewMonthsAgo: Story = {
|
||||||
render: getRenderWrapperForComponent(
|
render: getRenderWrapperForComponent(<CommentHeader comment={mockComment} />),
|
||||||
<CommentHeader
|
|
||||||
comment={{
|
|
||||||
...mockComment,
|
|
||||||
createdAt: DateTime.now().minus({ months: 2 }).toISO() ?? '',
|
|
||||||
}}
|
|
||||||
/>,
|
|
||||||
),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const FewYearsAgo: Story = {
|
export const FewYearsAgo: Story = {
|
||||||
render: getRenderWrapperForComponent(
|
render: getRenderWrapperForComponent(<CommentHeader comment={mockComment} />),
|
||||||
<CommentHeader
|
|
||||||
comment={{
|
|
||||||
...mockComment,
|
|
||||||
createdAt: DateTime.now().minus({ years: 2 }).toISO() ?? '',
|
|
||||||
}}
|
|
||||||
/>,
|
|
||||||
),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const WithoutAvatar: Story = {
|
export const WithoutAvatar: Story = {
|
||||||
@ -98,7 +40,6 @@ export const WithoutAvatar: Story = {
|
|||||||
...mockComment.author,
|
...mockComment.author,
|
||||||
avatarUrl: '',
|
avatarUrl: '',
|
||||||
},
|
},
|
||||||
createdAt: DateTime.now().minus({ hours: 2 }).toISO() ?? '',
|
|
||||||
}}
|
}}
|
||||||
/>,
|
/>,
|
||||||
),
|
),
|
||||||
@ -108,12 +49,11 @@ export const WithLongUserName: Story = {
|
|||||||
render: getRenderWrapperForComponent(
|
render: getRenderWrapperForComponent(
|
||||||
<CommentHeader
|
<CommentHeader
|
||||||
comment={{
|
comment={{
|
||||||
...mockCommentWithLongName,
|
...mockCommentWithLongValues,
|
||||||
author: {
|
author: {
|
||||||
...mockCommentWithLongName.author,
|
...mockCommentWithLongValues.author,
|
||||||
avatarUrl: '',
|
avatarUrl: '',
|
||||||
},
|
},
|
||||||
createdAt: DateTime.now().minus({ hours: 2 }).toISO() ?? '',
|
|
||||||
}}
|
}}
|
||||||
/>,
|
/>,
|
||||||
),
|
),
|
||||||
@ -122,10 +62,7 @@ export const WithLongUserName: Story = {
|
|||||||
export const WithActionBar: Story = {
|
export const WithActionBar: Story = {
|
||||||
render: getRenderWrapperForComponent(
|
render: getRenderWrapperForComponent(
|
||||||
<CommentHeader
|
<CommentHeader
|
||||||
comment={{
|
comment={mockComment}
|
||||||
...mockComment,
|
|
||||||
createdAt: DateTime.now().minus({ days: 2 }).toISO() ?? '',
|
|
||||||
}}
|
|
||||||
actionBar={<CommentThreadActionBar commentThreadId="test-id" />}
|
actionBar={<CommentThreadActionBar commentThreadId="test-id" />}
|
||||||
/>,
|
/>,
|
||||||
),
|
),
|
||||||
|
|||||||
@ -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() ?? '',
|
||||||
|
};
|
||||||
@ -8,7 +8,7 @@ import { AutosizeTextInput } from '@/ui/input/components/AutosizeTextInput';
|
|||||||
import { CommentThread, useCreateCommentMutation } from '~/generated/graphql';
|
import { CommentThread, useCreateCommentMutation } from '~/generated/graphql';
|
||||||
import { isNonEmptyString } from '~/utils/isNonEmptyString';
|
import { isNonEmptyString } from '~/utils/isNonEmptyString';
|
||||||
|
|
||||||
import { CommentThreadItem } from '../comment/CommentThreadItem';
|
import { Comment } from '../comment/Comment';
|
||||||
import { GET_COMMENT_THREAD } from '../queries';
|
import { GET_COMMENT_THREAD } from '../queries';
|
||||||
import { CommentForDrawer } from '../types/CommentForDrawer';
|
import { CommentForDrawer } from '../types/CommentForDrawer';
|
||||||
|
|
||||||
@ -80,7 +80,7 @@ export function CommentThreadComments({ commentThread }: OwnProps) {
|
|||||||
<StyledThreadItemListContainer>
|
<StyledThreadItemListContainer>
|
||||||
<StyledThreadCommentTitle>Comments</StyledThreadCommentTitle>
|
<StyledThreadCommentTitle>Comments</StyledThreadCommentTitle>
|
||||||
{commentThread?.comments?.map((comment, index) => (
|
{commentThread?.comments?.map((comment, index) => (
|
||||||
<CommentThreadItem key={comment.id} comment={comment} />
|
<Comment key={comment.id} comment={comment} />
|
||||||
))}
|
))}
|
||||||
</StyledThreadItemListContainer>
|
</StyledThreadItemListContainer>
|
||||||
</>
|
</>
|
||||||
|
|||||||
@ -152,8 +152,11 @@ export function CommentThreadRelationPicker({ commentThread }: OwnProps) {
|
|||||||
placement: 'bottom-start',
|
placement: 'bottom-start',
|
||||||
});
|
});
|
||||||
|
|
||||||
useListenClickOutsideArrayOfRef([refs.floating, refs.domReference], () => {
|
useListenClickOutsideArrayOfRef({
|
||||||
exitEditMode();
|
refs: [refs.floating, refs.domReference],
|
||||||
|
callback: () => {
|
||||||
|
exitEditMode();
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const selectedEntities = flatMapAndSortEntityForSelectArrayOfArrayByName([
|
const selectedEntities = flatMapAndSortEntityForSelectArrayOfArrayByName([
|
||||||
|
|||||||
@ -7,7 +7,7 @@ import { graphqlMocks } from '~/testing/graphqlMocks';
|
|||||||
import { getRenderWrapperForComponent } from '~/testing/renderWrappers';
|
import { getRenderWrapperForComponent } from '~/testing/renderWrappers';
|
||||||
|
|
||||||
const meta: Meta<typeof EntityBoard> = {
|
const meta: Meta<typeof EntityBoard> = {
|
||||||
title: 'UI/Board/Board',
|
title: 'Modules/Companies/Board',
|
||||||
component: EntityBoard,
|
component: EntityBoard,
|
||||||
decorators: [BoardDecorator],
|
decorators: [BoardDecorator],
|
||||||
};
|
};
|
||||||
|
|||||||
@ -6,7 +6,7 @@ import { graphqlMocks } from '~/testing/graphqlMocks';
|
|||||||
import { getRenderWrapperForComponent } from '~/testing/renderWrappers';
|
import { getRenderWrapperForComponent } from '~/testing/renderWrappers';
|
||||||
|
|
||||||
const meta: Meta<typeof CompanyBoardCard> = {
|
const meta: Meta<typeof CompanyBoardCard> = {
|
||||||
title: 'UI/Board/CompanyBoardCard',
|
title: 'Modules/Companies/CompanyBoardCard',
|
||||||
component: CompanyBoardCard,
|
component: CompanyBoardCard,
|
||||||
decorators: [BoardCardDecorator],
|
decorators: [BoardCardDecorator],
|
||||||
};
|
};
|
||||||
|
|||||||
@ -7,7 +7,7 @@ import { getRenderWrapperForComponent } from '~/testing/renderWrappers';
|
|||||||
import { PersonChip } from '../PersonChip';
|
import { PersonChip } from '../PersonChip';
|
||||||
|
|
||||||
const meta: Meta<typeof PersonChip> = {
|
const meta: Meta<typeof PersonChip> = {
|
||||||
title: 'Modules/Companies/PersonChip',
|
title: 'Modules/People/PersonChip',
|
||||||
component: PersonChip,
|
component: PersonChip,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -72,9 +72,13 @@ export function NameFields({
|
|||||||
}, 500);
|
}, 500);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
if (!currentUser) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
currentUser?.firstName !== firstName ||
|
currentUser.firstName !== firstName ||
|
||||||
currentUser?.lastName !== lastName
|
currentUser.lastName !== lastName
|
||||||
) {
|
) {
|
||||||
debouncedUpdate();
|
debouncedUpdate();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -43,8 +43,11 @@ export function BoardCardEditableFieldEditMode({
|
|||||||
}: OwnProps) {
|
}: OwnProps) {
|
||||||
const wrapperRef = useRef(null);
|
const wrapperRef = useRef(null);
|
||||||
|
|
||||||
useListenClickOutsideArrayOfRef([wrapperRef], () => {
|
useListenClickOutsideArrayOfRef({
|
||||||
onExit();
|
refs: [wrapperRef],
|
||||||
|
callback: () => {
|
||||||
|
onExit();
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
useScopedHotkeys(
|
useScopedHotkeys(
|
||||||
|
|||||||
@ -40,8 +40,11 @@ export function EditColumnTitleInput({
|
|||||||
}) {
|
}) {
|
||||||
const inputRef = React.useRef<HTMLInputElement>(null);
|
const inputRef = React.useRef<HTMLInputElement>(null);
|
||||||
|
|
||||||
useListenClickOutsideArrayOfRef([inputRef], () => {
|
useListenClickOutsideArrayOfRef({
|
||||||
onFocusLeave();
|
refs: [inputRef],
|
||||||
|
callback: () => {
|
||||||
|
onFocusLeave();
|
||||||
|
},
|
||||||
});
|
});
|
||||||
const setHotkeyScope = useSetHotkeyScope();
|
const setHotkeyScope = useSetHotkeyScope();
|
||||||
setHotkeyScope(ColumnHotkeyScope.EditColumnName, { goto: false });
|
setHotkeyScope(ColumnHotkeyScope.EditColumnName, { goto: false });
|
||||||
|
|||||||
@ -83,14 +83,20 @@ const StyledIconButton = styled.button<Pick<ButtonProps, 'variant' | 'size'>>`
|
|||||||
}
|
}
|
||||||
}};
|
}};
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
outline: none;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
transition: background 0.1s ease;
|
transition: background 0.1s ease;
|
||||||
user-select: none;
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background: ${({ theme, disabled }) => {
|
background: ${({ theme, disabled }) => {
|
||||||
return disabled ? 'auto' : theme.background.transparent.light;
|
return disabled ? 'auto' : theme.background.transparent.light;
|
||||||
}};
|
}};
|
||||||
}
|
}
|
||||||
|
user-select: none;
|
||||||
|
&:active {
|
||||||
|
background: ${({ theme, disabled }) => {
|
||||||
|
return disabled ? 'auto' : theme.background.transparent.medium;
|
||||||
|
}};
|
||||||
|
}
|
||||||
width: ${({ size }) => {
|
width: ${({ size }) => {
|
||||||
switch (size) {
|
switch (size) {
|
||||||
case 'large':
|
case 'large':
|
||||||
@ -102,11 +108,6 @@ const StyledIconButton = styled.button<Pick<ButtonProps, 'variant' | 'size'>>`
|
|||||||
return '20px';
|
return '20px';
|
||||||
}
|
}
|
||||||
}};
|
}};
|
||||||
&:active {
|
|
||||||
background: ${({ theme, disabled }) => {
|
|
||||||
return disabled ? 'auto' : theme.background.transparent.medium;
|
|
||||||
}};
|
|
||||||
}
|
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export function IconButton({
|
export function IconButton({
|
||||||
|
|||||||
@ -14,15 +14,16 @@ const StyledIconButton = styled.button`
|
|||||||
|
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
|
||||||
|
outline: none;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
transition: color 0.1s ease-in-out, background 0.1s ease-in-out;
|
transition: color 0.1s ease-in-out, background 0.1s ease-in-out;
|
||||||
width: 20px;
|
|
||||||
|
|
||||||
&:disabled {
|
&:disabled {
|
||||||
background: ${({ theme }) => theme.background.quaternary};
|
background: ${({ theme }) => theme.background.quaternary};
|
||||||
color: ${({ theme }) => theme.font.color.tertiary};
|
color: ${({ theme }) => theme.font.color.tertiary};
|
||||||
cursor: default;
|
cursor: default;
|
||||||
}
|
}
|
||||||
|
width: 20px;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export function RoundedIconButton({
|
export function RoundedIconButton({
|
||||||
|
|||||||
@ -56,7 +56,7 @@ const StyledButtonContainer = styled.div`
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
const meta: Meta<typeof Button> = {
|
const meta: Meta<typeof Button> = {
|
||||||
title: 'UI/Buttons/Button',
|
title: 'UI/Button/Button',
|
||||||
component: Button,
|
component: Button,
|
||||||
decorators: [withKnobs],
|
decorators: [withKnobs],
|
||||||
};
|
};
|
||||||
|
|||||||
@ -53,7 +53,7 @@ const StyledIconButtonContainer = styled.div`
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
const meta: Meta<typeof IconButton> = {
|
const meta: Meta<typeof IconButton> = {
|
||||||
title: 'UI/Buttons/IconButton',
|
title: 'UI/Button/IconButton',
|
||||||
component: IconButton,
|
component: IconButton,
|
||||||
decorators: [withKnobs],
|
decorators: [withKnobs],
|
||||||
};
|
};
|
||||||
|
|||||||
@ -8,7 +8,7 @@ import { getRenderWrapperForComponent } from '~/testing/renderWrappers';
|
|||||||
import { MainButton } from '../MainButton';
|
import { MainButton } from '../MainButton';
|
||||||
|
|
||||||
const meta: Meta<typeof MainButton> = {
|
const meta: Meta<typeof MainButton> = {
|
||||||
title: 'UI/Buttons/MainButton',
|
title: 'UI/Button/MainButton',
|
||||||
component: MainButton,
|
component: MainButton,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -8,7 +8,7 @@ import { getRenderWrapperForComponent } from '~/testing/renderWrappers';
|
|||||||
import { RoundedIconButton } from '../RoundedIconButton';
|
import { RoundedIconButton } from '../RoundedIconButton';
|
||||||
|
|
||||||
const meta: Meta<typeof RoundedIconButton> = {
|
const meta: Meta<typeof RoundedIconButton> = {
|
||||||
title: 'UI/Buttons/RoundedIconButton',
|
title: 'UI/Button/RoundedIconButton',
|
||||||
component: RoundedIconButton,
|
component: RoundedIconButton,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -15,7 +15,7 @@ import { DropdownMenuSelectableItem } from '../DropdownMenuSelectableItem';
|
|||||||
import { DropdownMenuSeparator } from '../DropdownMenuSeparator';
|
import { DropdownMenuSeparator } from '../DropdownMenuSeparator';
|
||||||
|
|
||||||
const meta: Meta<typeof DropdownMenu> = {
|
const meta: Meta<typeof DropdownMenu> = {
|
||||||
title: 'UI/Menu/DropdownMenu',
|
title: 'UI/Dropdown/DropdownMenu',
|
||||||
component: DropdownMenu,
|
component: DropdownMenu,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -12,11 +12,14 @@ export function useRegisterCloseFieldHandlers(
|
|||||||
) {
|
) {
|
||||||
const { closeEditableField, isFieldInEditMode } = useEditableField();
|
const { closeEditableField, isFieldInEditMode } = useEditableField();
|
||||||
|
|
||||||
useListenClickOutsideArrayOfRef([wrapperRef], () => {
|
useListenClickOutsideArrayOfRef({
|
||||||
if (isFieldInEditMode) {
|
refs: [wrapperRef],
|
||||||
onSubmit?.();
|
callback: () => {
|
||||||
closeEditableField();
|
if (isFieldInEditMode) {
|
||||||
}
|
onSubmit?.();
|
||||||
|
closeEditableField();
|
||||||
|
}
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
useScopedHotkeys(
|
useScopedHotkeys(
|
||||||
|
|||||||
@ -3,7 +3,7 @@ import styled from '@emotion/styled';
|
|||||||
import { Key } from 'ts-key-enum';
|
import { Key } from 'ts-key-enum';
|
||||||
|
|
||||||
import { DropdownMenu } from '@/ui/dropdown/components/DropdownMenu';
|
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 { useScopedHotkeys } from '@/ui/hotkey/hooks/useScopedHotkeys';
|
||||||
import { IconChevronDown } from '@/ui/icon/index';
|
import { IconChevronDown } from '@/ui/icon/index';
|
||||||
|
|
||||||
@ -105,7 +105,10 @@ function DropdownButton({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const dropdownRef = useRef(null);
|
const dropdownRef = useRef(null);
|
||||||
useOutsideAlerter({ ref: dropdownRef, callback: onOutsideClick });
|
useListenClickOutsideArrayOfRef({
|
||||||
|
refs: [dropdownRef],
|
||||||
|
callback: onOutsideClick,
|
||||||
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledDropdownButtonContainer>
|
<StyledDropdownButtonContainer>
|
||||||
|
|||||||
@ -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 (
|
||||||
|
<div>
|
||||||
|
<span>Outside</span>
|
||||||
|
<button ref={buttonRef}>Inside</button>
|
||||||
|
<button ref={buttonRef2}>Inside 2</button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
test('useListenClickOutsideArrayOfRef hook works in dom mode', async () => {
|
||||||
|
const { getByText } = render(<TestComponentDomMode />);
|
||||||
|
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);
|
||||||
|
});
|
||||||
@ -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 (
|
|
||||||
<div>
|
|
||||||
<span>Outside</span>
|
|
||||||
<button ref={buttonRef}>Inside</button>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
test('useOutsideAlerter hook works properly', async () => {
|
|
||||||
const { getByText } = render(<TestComponent />);
|
|
||||||
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);
|
|
||||||
});
|
|
||||||
@ -2,34 +2,75 @@ import React, { useEffect } from 'react';
|
|||||||
|
|
||||||
import { isDefined } from '~/utils/isDefined';
|
import { isDefined } from '~/utils/isDefined';
|
||||||
|
|
||||||
export function useListenClickOutsideArrayOfRef<T extends Element>(
|
export enum ClickOutsideMode {
|
||||||
arrayOfRef: Array<React.RefObject<T>>,
|
absolute = 'absolute',
|
||||||
outsideClickCallback: (event?: MouseEvent | TouchEvent) => void,
|
dom = 'dom',
|
||||||
) {
|
}
|
||||||
|
|
||||||
|
export function useListenClickOutsideArrayOfRef<T extends Element>({
|
||||||
|
refs,
|
||||||
|
callback,
|
||||||
|
mode = ClickOutsideMode.dom,
|
||||||
|
}: {
|
||||||
|
refs: Array<React.RefObject<T>>;
|
||||||
|
callback: (event?: MouseEvent | TouchEvent) => void;
|
||||||
|
mode?: ClickOutsideMode;
|
||||||
|
}) {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
function handleClickOutside(event: MouseEvent | TouchEvent) {
|
function handleClickOutside(event: MouseEvent | TouchEvent) {
|
||||||
const clickedOnAtLeastOneRef = arrayOfRef
|
if (mode === ClickOutsideMode.dom) {
|
||||||
.filter((ref) => !!ref.current)
|
const clickedOnAtLeastOneRef = refs
|
||||||
.some((ref) => ref.current?.contains(event.target as Node));
|
.filter((ref) => !!ref.current)
|
||||||
|
.some((ref) => ref.current?.contains(event.target as Node));
|
||||||
|
|
||||||
if (!clickedOnAtLeastOneRef) {
|
if (!clickedOnAtLeastOneRef) {
|
||||||
outsideClickCallback(event);
|
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) =>
|
const hasAtLeastOneRefDefined = refs.some((ref) => isDefined(ref.current));
|
||||||
isDefined(ref.current),
|
|
||||||
);
|
|
||||||
|
|
||||||
if (hasAtLeastOneRefDefined) {
|
if (hasAtLeastOneRefDefined) {
|
||||||
document.addEventListener('mouseup', handleClickOutside);
|
document.addEventListener('click', handleClickOutside);
|
||||||
document.addEventListener('touchend', handleClickOutside);
|
document.addEventListener('touchend', handleClickOutside);
|
||||||
}
|
}
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
document.removeEventListener('mouseup', handleClickOutside);
|
document.removeEventListener('click', handleClickOutside);
|
||||||
document.removeEventListener('touchend', handleClickOutside);
|
document.removeEventListener('touchend', handleClickOutside);
|
||||||
};
|
};
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
}, [refs, callback, mode]);
|
||||||
}, [arrayOfRef, outsideClickCallback]);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,52 +0,0 @@
|
|||||||
import { useEffect } from 'react';
|
|
||||||
|
|
||||||
export enum OutsideClickAlerterMode {
|
|
||||||
absolute = 'absolute',
|
|
||||||
dom = 'dom',
|
|
||||||
}
|
|
||||||
|
|
||||||
type OwnProps = {
|
|
||||||
ref: React.RefObject<HTMLInputElement>;
|
|
||||||
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]);
|
|
||||||
}
|
|
||||||
@ -46,8 +46,11 @@ export function SingleEntitySelect<
|
|||||||
|
|
||||||
const showCreateButton = isDefined(onCreate) && searchFilter !== '';
|
const showCreateButton = isDefined(onCreate) && searchFilter !== '';
|
||||||
|
|
||||||
useListenClickOutsideArrayOfRef([containerRef], () => {
|
useListenClickOutsideArrayOfRef({
|
||||||
onCancel?.();
|
refs: [containerRef],
|
||||||
|
callback: () => {
|
||||||
|
onCancel?.();
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@ -5,9 +5,9 @@ import { motion } from 'framer-motion';
|
|||||||
import { useRecoilState } from 'recoil';
|
import { useRecoilState } from 'recoil';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
OutsideClickAlerterMode,
|
ClickOutsideMode,
|
||||||
useOutsideAlerter,
|
useListenClickOutsideArrayOfRef,
|
||||||
} from '@/ui/hooks/useOutsideAlerter';
|
} from '@/ui/hooks/useListenClickOutsideArrayOfRef';
|
||||||
import { isDefined } from '~/utils/isDefined';
|
import { isDefined } from '~/utils/isDefined';
|
||||||
|
|
||||||
import { isRightDrawerOpenState } from '../states/isRightDrawerOpenState';
|
import { isRightDrawerOpenState } from '../states/isRightDrawerOpenState';
|
||||||
@ -41,10 +41,10 @@ export function RightDrawer() {
|
|||||||
const [rightDrawerPage] = useRecoilState(rightDrawerPageState);
|
const [rightDrawerPage] = useRecoilState(rightDrawerPageState);
|
||||||
|
|
||||||
const rightDrawerRef = useRef(null);
|
const rightDrawerRef = useRef(null);
|
||||||
useOutsideAlerter({
|
useListenClickOutsideArrayOfRef({
|
||||||
ref: rightDrawerRef,
|
refs: [rightDrawerRef],
|
||||||
callback: () => setIsRightDrawerOpen(false),
|
callback: () => setIsRightDrawerOpen(false),
|
||||||
mode: OutsideClickAlerterMode.absolute,
|
mode: ClickOutsideMode.absolute,
|
||||||
});
|
});
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
if (!isDefined(rightDrawerPage)) {
|
if (!isDefined(rightDrawerPage)) {
|
||||||
|
|||||||
@ -90,8 +90,11 @@ export function EntityTable<SortField>({
|
|||||||
|
|
||||||
const leaveTableFocus = useLeaveTableFocus();
|
const leaveTableFocus = useLeaveTableFocus();
|
||||||
|
|
||||||
useListenClickOutsideArrayOfRef([tableBodyRef], () => {
|
useListenClickOutsideArrayOfRef({
|
||||||
leaveTableFocus();
|
refs: [tableBodyRef],
|
||||||
|
callback: () => {
|
||||||
|
leaveTableFocus();
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@ -14,11 +14,14 @@ export function useRegisterCloseCellHandlers(
|
|||||||
) {
|
) {
|
||||||
const { closeEditableCell } = useEditableCell();
|
const { closeEditableCell } = useEditableCell();
|
||||||
const { isCurrentCellInEditMode } = useCurrentCellEditMode();
|
const { isCurrentCellInEditMode } = useCurrentCellEditMode();
|
||||||
useListenClickOutsideArrayOfRef([wrapperRef], () => {
|
useListenClickOutsideArrayOfRef({
|
||||||
if (isCurrentCellInEditMode) {
|
refs: [wrapperRef],
|
||||||
onSubmit?.();
|
callback: () => {
|
||||||
closeEditableCell();
|
if (isCurrentCellInEditMode) {
|
||||||
}
|
onSubmit?.();
|
||||||
|
closeEditableCell();
|
||||||
|
}
|
||||||
|
},
|
||||||
});
|
});
|
||||||
const { moveRight, moveLeft, moveDown } = useMoveSoftFocus();
|
const { moveRight, moveLeft, moveDown } = useMoveSoftFocus();
|
||||||
|
|
||||||
|
|||||||
@ -1,11 +0,0 @@
|
|||||||
{ /* Companies.mdx */ }
|
|
||||||
|
|
||||||
import { Canvas, Meta } from '@storybook/blocks';
|
|
||||||
|
|
||||||
import * as Companies from './Companies.stories';
|
|
||||||
|
|
||||||
<Meta of={Companies} />
|
|
||||||
|
|
||||||
# Companies View
|
|
||||||
|
|
||||||
<Canvas of={Companies.Default} />
|
|
||||||
@ -1,4 +1,5 @@
|
|||||||
import { getOperationName } from '@apollo/client/utilities';
|
import { getOperationName } from '@apollo/client/utilities';
|
||||||
|
import { expect } from '@storybook/jest';
|
||||||
import type { Meta, StoryObj } from '@storybook/react';
|
import type { Meta, StoryObj } from '@storybook/react';
|
||||||
import { within } from '@storybook/testing-library';
|
import { within } from '@storybook/testing-library';
|
||||||
import { graphql } from 'msw';
|
import { graphql } from 'msw';
|
||||||
@ -25,19 +26,66 @@ export default meta;
|
|||||||
|
|
||||||
export type Story = StoryObj<typeof CompanyShow>;
|
export type Story = StoryObj<typeof CompanyShow>;
|
||||||
|
|
||||||
|
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 = {
|
export const Default: Story = {
|
||||||
|
render: getRenderWrapperForPage(
|
||||||
|
<CompanyShow />,
|
||||||
|
'/companies/89bb825c-171e-4bcc-9cf7-43448d6fb278',
|
||||||
|
),
|
||||||
|
parameters: {
|
||||||
|
msw: [...graphqlMocks, ...companyShowCommonGraphqlMocks],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const EditNote: Story = {
|
||||||
render: getRenderWrapperForPage(
|
render: getRenderWrapperForPage(
|
||||||
<CompanyShow />,
|
<CompanyShow />,
|
||||||
'/companies/89bb825c-171e-4bcc-9cf7-43448d6fb278',
|
'/companies/89bb825c-171e-4bcc-9cf7-43448d6fb278',
|
||||||
),
|
),
|
||||||
play: async ({ canvasElement }) => {
|
play: async ({ canvasElement }) => {
|
||||||
const canvas = within(canvasElement);
|
const canvas = within(canvasElement);
|
||||||
const notesButton = await canvas.findByText('Note');
|
const firstNoteTitle = await canvas.findByText('My very first note');
|
||||||
await notesButton.click();
|
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: {
|
parameters: {
|
||||||
msw: [
|
msw: [
|
||||||
...graphqlMocks,
|
...graphqlMocks,
|
||||||
|
...companyShowCommonGraphqlMocks,
|
||||||
graphql.mutation(
|
graphql.mutation(
|
||||||
getOperationName(CREATE_COMMENT_THREAD_WITH_COMMENT) ?? '',
|
getOperationName(CREATE_COMMENT_THREAD_WITH_COMMENT) ?? '',
|
||||||
(req, res, ctx) => {
|
(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(
|
graphql.query(
|
||||||
getOperationName(GET_COMMENT_THREAD) ?? '',
|
getOperationName(GET_COMMENT_THREAD) ?? '',
|
||||||
(req, res, ctx) => {
|
(req, res, ctx) => {
|
||||||
|
console.log('coucou');
|
||||||
return res(
|
return res(
|
||||||
ctx.data({
|
ctx.data({
|
||||||
findManyCommentThreads: mockedCommentThreads[0],
|
findManyCommentThreads: [mockedCommentThreads[0]],
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
graphql.query(getOperationName(GET_COMPANY) ?? '', (req, res, ctx) => {
|
|
||||||
return res(
|
|
||||||
ctx.data({
|
|
||||||
findUniqueCompany: mockedCompaniesData[0],
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
}),
|
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@ -6,7 +6,7 @@ import { getRenderWrapperForPage } from '~/testing/renderWrappers';
|
|||||||
import { Opportunities } from '../Opportunities';
|
import { Opportunities } from '../Opportunities';
|
||||||
|
|
||||||
const meta: Meta<typeof Opportunities> = {
|
const meta: Meta<typeof Opportunities> = {
|
||||||
title: 'Pages/Opportunities',
|
title: 'Pages/Opportunities/Default',
|
||||||
component: Opportunities,
|
component: Opportunities,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -1,11 +0,0 @@
|
|||||||
{ /* People.mdx */ }
|
|
||||||
|
|
||||||
import { Canvas, Meta } from '@storybook/blocks';
|
|
||||||
|
|
||||||
import * as People from './People.stories';
|
|
||||||
|
|
||||||
<Meta of={People} />
|
|
||||||
|
|
||||||
# People View
|
|
||||||
|
|
||||||
<Canvas of={People.Default} />
|
|
||||||
@ -1,6 +1,8 @@
|
|||||||
import type { Meta, StoryObj } from '@storybook/react';
|
import type { Meta, StoryObj } from '@storybook/react';
|
||||||
|
import { within } from '@storybook/testing-library';
|
||||||
|
|
||||||
import { graphqlMocks } from '~/testing/graphqlMocks';
|
import { graphqlMocks } from '~/testing/graphqlMocks';
|
||||||
|
import { mockedUserJWT } from '~/testing/mock-data/jwt';
|
||||||
import { getRenderWrapperForPage } from '~/testing/renderWrappers';
|
import { getRenderWrapperForPage } from '~/testing/renderWrappers';
|
||||||
|
|
||||||
import { SettingsProfile } from '../SettingsProfile';
|
import { SettingsProfile } from '../SettingsProfile';
|
||||||
@ -16,6 +18,21 @@ export type Story = StoryObj<typeof SettingsProfile>;
|
|||||||
|
|
||||||
export const Default: Story = {
|
export const Default: Story = {
|
||||||
render: getRenderWrapperForPage(<SettingsProfile />, '/settings/profile'),
|
render: getRenderWrapperForPage(<SettingsProfile />, '/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(<SettingsProfile />, '/settings/profile'),
|
||||||
|
play: async ({ canvasElement }) => {
|
||||||
|
const canvas = within(canvasElement);
|
||||||
|
const logoutButton = await canvas.findByText('Logout');
|
||||||
|
await logoutButton.click();
|
||||||
|
},
|
||||||
parameters: {
|
parameters: {
|
||||||
msw: graphqlMocks,
|
msw: graphqlMocks,
|
||||||
},
|
},
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import type { Meta, StoryObj } from '@storybook/react';
|
import type { Meta, StoryObj } from '@storybook/react';
|
||||||
|
|
||||||
import { graphqlMocks } from '~/testing/graphqlMocks';
|
import { graphqlMocks } from '~/testing/graphqlMocks';
|
||||||
|
import { mockedUserJWT } from '~/testing/mock-data/jwt';
|
||||||
import { getRenderWrapperForPage } from '~/testing/renderWrappers';
|
import { getRenderWrapperForPage } from '~/testing/renderWrappers';
|
||||||
|
|
||||||
import { SettingsWorkspaceMembers } from '../SettingsWorkspaceMembers';
|
import { SettingsWorkspaceMembers } from '../SettingsWorkspaceMembers';
|
||||||
@ -21,5 +22,8 @@ export const Default: Story = {
|
|||||||
),
|
),
|
||||||
parameters: {
|
parameters: {
|
||||||
msw: graphqlMocks,
|
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}`,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@ -8,6 +8,7 @@ import { GET_PEOPLE, UPDATE_PERSON } from '@/people/queries';
|
|||||||
import { GET_PIPELINE_PROGRESS, GET_PIPELINES } from '@/pipeline/queries';
|
import { GET_PIPELINE_PROGRESS, GET_PIPELINES } from '@/pipeline/queries';
|
||||||
import {
|
import {
|
||||||
SEARCH_COMPANY_QUERY,
|
SEARCH_COMPANY_QUERY,
|
||||||
|
SEARCH_PEOPLE_QUERY,
|
||||||
SEARCH_USER_QUERY,
|
SEARCH_USER_QUERY,
|
||||||
} from '@/search/queries/search';
|
} from '@/search/queries/search';
|
||||||
import { GET_CURRENT_USER } from '@/users/queries';
|
import { GET_CURRENT_USER } from '@/users/queries';
|
||||||
@ -15,6 +16,7 @@ import {
|
|||||||
GetCompaniesQuery,
|
GetCompaniesQuery,
|
||||||
GetPeopleQuery,
|
GetPeopleQuery,
|
||||||
SearchCompanyQuery,
|
SearchCompanyQuery,
|
||||||
|
SearchPeopleQuery,
|
||||||
SearchUserQuery,
|
SearchUserQuery,
|
||||||
} from '~/generated/graphql';
|
} 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) => {
|
graphql.query(getOperationName(SEARCH_USER_QUERY) ?? '', (req, res, ctx) => {
|
||||||
const returnedMockedData = filterAndSortData<
|
const returnedMockedData = filterAndSortData<
|
||||||
SearchUserQuery['searchResults'][0]
|
SearchUserQuery['searchResults'][0]
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import {
|
import {
|
||||||
|
ActivityType,
|
||||||
Comment,
|
Comment,
|
||||||
CommentableType,
|
CommentableType,
|
||||||
CommentThread,
|
CommentThread,
|
||||||
@ -11,6 +12,7 @@ type MockedCommentThread = Pick<
|
|||||||
| 'createdAt'
|
| 'createdAt'
|
||||||
| 'updatedAt'
|
| 'updatedAt'
|
||||||
| '__typename'
|
| '__typename'
|
||||||
|
| 'type'
|
||||||
| 'body'
|
| 'body'
|
||||||
| 'title'
|
| 'title'
|
||||||
| 'authorId'
|
| 'authorId'
|
||||||
@ -42,6 +44,7 @@ export const mockedCommentThreads: Array<MockedCommentThread> = [
|
|||||||
createdAt: '2023-04-26T10:12:42.33625+00:00',
|
createdAt: '2023-04-26T10:12:42.33625+00:00',
|
||||||
updatedAt: '2023-04-26T10:23:42.33625+00:00',
|
updatedAt: '2023-04-26T10:23:42.33625+00:00',
|
||||||
title: 'My very first note',
|
title: 'My very first note',
|
||||||
|
type: ActivityType.Note,
|
||||||
body: null,
|
body: null,
|
||||||
author: {
|
author: {
|
||||||
id: '374fe3a5-df1e-4119-afe0-2a62a2ba481e',
|
id: '374fe3a5-df1e-4119-afe0-2a62a2ba481e',
|
||||||
@ -88,6 +91,7 @@ export const mockedCommentThreads: Array<MockedCommentThread> = [
|
|||||||
updatedAt: new Date().toISOString(),
|
updatedAt: new Date().toISOString(),
|
||||||
title: 'Another note',
|
title: 'Another note',
|
||||||
body: null,
|
body: null,
|
||||||
|
type: ActivityType.Note,
|
||||||
author: {
|
author: {
|
||||||
id: '374fe3a5-df1e-4119-afe0-2a62a2ba481e',
|
id: '374fe3a5-df1e-4119-afe0-2a62a2ba481e',
|
||||||
firstName: 'Charles',
|
firstName: 'Charles',
|
||||||
|
|||||||
@ -37,6 +37,7 @@ export const mockedCompaniesData: Array<MockedCompany> = [
|
|||||||
displayName: 'Charles Test',
|
displayName: 'Charles Test',
|
||||||
firstName: 'Charles',
|
firstName: 'Charles',
|
||||||
lastName: 'Test',
|
lastName: 'Test',
|
||||||
|
avatarUrl: null,
|
||||||
id: '7dfbc3f7-6e5e-4128-957e-8d86808cdf6b',
|
id: '7dfbc3f7-6e5e-4128-957e-8d86808cdf6b',
|
||||||
__typename: 'User',
|
__typename: 'User',
|
||||||
},
|
},
|
||||||
|
|||||||
@ -17209,6 +17209,11 @@ store2@^2.14.2:
|
|||||||
resolved "https://registry.yarnpkg.com/store2/-/store2-2.14.2.tgz#56138d200f9fe5f582ad63bc2704dbc0e4a45068"
|
resolved "https://registry.yarnpkg.com/store2/-/store2-2.14.2.tgz#56138d200f9fe5f582ad63bc2704dbc0e4a45068"
|
||||||
integrity sha512-siT1RiqlfQnGqgT/YzXVUNsom9S0H1OX+dpdGN1xkyYATo4I6sep5NmsRD/40s3IIOvlCq6akxkqG82urIZW1w==
|
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:
|
storybook-addon-pseudo-states@^2.1.0:
|
||||||
version "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"
|
resolved "https://registry.yarnpkg.com/storybook-addon-pseudo-states/-/storybook-addon-pseudo-states-2.1.0.tgz#05faffb7e0d19fc012035ecee2a02d432f312d2d"
|
||||||
|
|||||||
Reference in New Issue
Block a user