Improve tests (#871)
This commit is contained in:
@ -3,7 +3,7 @@ import { Preview } from '@storybook/react';
|
||||
import { ThemeProvider } from '@emotion/react';
|
||||
import { withThemeFromJSXProvider } from '@storybook/addon-styling';
|
||||
import { lightTheme, darkTheme } from '../src/modules/ui/themes/themes';
|
||||
import { RootDecorator } from '../src/testing/decorators';
|
||||
import { RootDecorator } from '../src/testing/decorators/RootDecorator';
|
||||
import 'react-loading-skeleton/dist/skeleton.css';
|
||||
import { mockedUserJWT } from '../src/testing/mock-data/jwt';
|
||||
initialize();
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import type { Meta, StoryObj } from '@storybook/react';
|
||||
|
||||
import { ComponentDecorator } from '~/testing/decorators';
|
||||
import { ComponentDecorator } from '~/testing/decorators/ComponentDecorator';
|
||||
|
||||
import { CommentThreadActionBar } from '../../right-drawer/components/CommentThreadActionBar';
|
||||
import { Comment } from '../Comment';
|
||||
|
||||
@ -2,7 +2,7 @@ import type { Meta, StoryObj } from '@storybook/react';
|
||||
import { DateTime } from 'luxon';
|
||||
|
||||
import { CommentThreadActionBar } from '@/activities/right-drawer/components/CommentThreadActionBar';
|
||||
import { ComponentDecorator } from '~/testing/decorators';
|
||||
import { ComponentDecorator } from '~/testing/decorators/ComponentDecorator';
|
||||
import { avatarUrl } from '~/testing/mock-data/users';
|
||||
|
||||
import { CommentHeader } from '../CommentHeader';
|
||||
|
||||
@ -26,7 +26,7 @@ export function CommentThreadCreateButton({
|
||||
<Button
|
||||
icon={<IconCheckbox size={theme.icon.size.sm} />}
|
||||
title="Task"
|
||||
soon={true}
|
||||
onClick={onTaskClick}
|
||||
/>
|
||||
<Button
|
||||
icon={<IconTimelineEvent size={theme.icon.size.sm} />}
|
||||
|
||||
@ -213,7 +213,7 @@ export function CommentThreadRelationPicker({ commentThread }: OwnProps) {
|
||||
key={entity.id}
|
||||
id={entity.id}
|
||||
name={entity.name}
|
||||
picture={entity.avatarUrl}
|
||||
pictureUrl={entity.avatarUrl}
|
||||
/>
|
||||
) : (
|
||||
<PersonChip key={entity.id} name={entity.name} id={entity.id} />
|
||||
|
||||
@ -2,7 +2,7 @@ import {
|
||||
DropdownButton,
|
||||
DropdownOptionType,
|
||||
} from '@/ui/button/components/DropdownButton';
|
||||
import { IconNotes } from '@/ui/icon/index';
|
||||
import { IconCheck, IconNotes } from '@/ui/icon';
|
||||
import {
|
||||
ActivityType,
|
||||
CommentThread,
|
||||
@ -17,6 +17,7 @@ export function CommentThreadTypeDropdown({ commentThread }: OwnProps) {
|
||||
const [updateCommentThreadMutation] = useUpdateCommentThreadMutation();
|
||||
const options: DropdownOptionType[] = [
|
||||
{ label: 'Note', key: 'note', icon: <IconNotes /> },
|
||||
{ label: 'Task', key: 'task', icon: <IconCheck /> },
|
||||
];
|
||||
|
||||
function getSelectedOptionKey() {
|
||||
|
||||
@ -2,7 +2,7 @@ import { MemoryRouter } from 'react-router-dom';
|
||||
import styled from '@emotion/styled';
|
||||
import type { Meta, StoryObj } from '@storybook/react';
|
||||
|
||||
import { ComponentDecorator } from '~/testing/decorators';
|
||||
import { ComponentDecorator } from '~/testing/decorators/ComponentDecorator';
|
||||
import { graphqlMocks } from '~/testing/graphqlMocks';
|
||||
import { mockedCommentThreads } from '~/testing/mock-data/comment-threads';
|
||||
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import styled from '@emotion/styled';
|
||||
import type { Meta, StoryObj } from '@storybook/react';
|
||||
|
||||
import { ComponentDecorator } from '~/testing/decorators';
|
||||
import { ComponentDecorator } from '~/testing/decorators/ComponentDecorator';
|
||||
|
||||
import { CommentChip } from '../CommentChip';
|
||||
|
||||
|
||||
@ -2,7 +2,7 @@ import { MemoryRouter } from 'react-router-dom';
|
||||
import type { Meta, StoryObj } from '@storybook/react';
|
||||
import { fireEvent, userEvent, within } from '@storybook/testing-library';
|
||||
|
||||
import { ComponentDecorator } from '~/testing/decorators';
|
||||
import { ComponentDecorator } from '~/testing/decorators/ComponentDecorator';
|
||||
import { sleep } from '~/testing/sleep';
|
||||
|
||||
import { CommandMenu } from '../CommandMenu';
|
||||
|
||||
@ -1,60 +0,0 @@
|
||||
import {
|
||||
DropdownButton,
|
||||
DropdownOptionType,
|
||||
} from '@/ui/button/components/DropdownButton';
|
||||
import { IconCheck, IconNotes } from '@/ui/icon';
|
||||
import {
|
||||
ActivityType,
|
||||
CommentThread,
|
||||
useUpdateCommentThreadMutation,
|
||||
} from '~/generated/graphql';
|
||||
|
||||
type OwnProps = {
|
||||
commentThread: Pick<CommentThread, 'id' | 'type'>;
|
||||
};
|
||||
|
||||
export function CommentThreadTypeDropdown({ commentThread }: OwnProps) {
|
||||
const [updateCommentThreadMutation] = useUpdateCommentThreadMutation();
|
||||
const options: DropdownOptionType[] = [
|
||||
{ label: 'Note', key: 'note', icon: <IconNotes /> },
|
||||
{ label: 'Task', key: 'task', icon: <IconCheck /> },
|
||||
];
|
||||
|
||||
function getSelectedOptionKey() {
|
||||
if (commentThread.type === ActivityType.Note) {
|
||||
return 'note';
|
||||
} else if (commentThread.type === ActivityType.Task) {
|
||||
return 'task';
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
const convertSelectionOptionKeyToActivityType = (key: string) => {
|
||||
switch (key) {
|
||||
case 'note':
|
||||
return ActivityType.Note;
|
||||
case 'task':
|
||||
return ActivityType.Task;
|
||||
default:
|
||||
return undefined;
|
||||
}
|
||||
};
|
||||
|
||||
const handleSelect = (selectedOption: DropdownOptionType) => {
|
||||
updateCommentThreadMutation({
|
||||
variables: {
|
||||
id: commentThread.id,
|
||||
type: convertSelectionOptionKeyToActivityType(selectedOption.key),
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<DropdownButton
|
||||
options={options}
|
||||
onSelection={handleSelect}
|
||||
selectedOptionKey={getSelectedOptionKey()}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@ -3,7 +3,7 @@ import { Meta, StoryObj } from '@storybook/react';
|
||||
|
||||
import { EntityBoard } from '@/pipeline/components/EntityBoard';
|
||||
import { opportunitiesBoardOptions } from '~/pages/opportunities/opportunitiesBoardOptions';
|
||||
import { ComponentDecorator } from '~/testing/decorators';
|
||||
import { ComponentDecorator } from '~/testing/decorators/ComponentDecorator';
|
||||
import { graphqlMocks } from '~/testing/graphqlMocks';
|
||||
|
||||
import { defaultPipelineProgressOrderBy } from '../../pipeline/queries';
|
||||
|
||||
@ -3,7 +3,7 @@ import { MemoryRouter } from 'react-router-dom';
|
||||
import { Meta, StoryObj } from '@storybook/react';
|
||||
|
||||
import { CompanyBoardCard } from '@/companies/components/CompanyBoardCard';
|
||||
import { ComponentDecorator } from '~/testing/decorators';
|
||||
import { ComponentDecorator } from '~/testing/decorators/ComponentDecorator';
|
||||
import { graphqlMocks } from '~/testing/graphqlMocks';
|
||||
import { mockedPipelineProgressData } from '~/testing/mock-data/pipeline-progress';
|
||||
|
||||
|
||||
@ -2,7 +2,7 @@ import { BrowserRouter } from 'react-router-dom';
|
||||
import styled from '@emotion/styled';
|
||||
import type { Meta, StoryObj } from '@storybook/react';
|
||||
|
||||
import { ComponentDecorator } from '~/testing/decorators';
|
||||
import { ComponentDecorator } from '~/testing/decorators/ComponentDecorator';
|
||||
|
||||
import { CompanyChip } from '../components/CompanyChip';
|
||||
|
||||
@ -37,14 +37,13 @@ const TestCellContainer = styled.div`
|
||||
min-width: 250px;
|
||||
|
||||
overflow: hidden;
|
||||
text-wrap: nowrap;
|
||||
`;
|
||||
|
||||
export const SmallName: Story = {
|
||||
args: {
|
||||
id: 'airbnb',
|
||||
name: 'Airbnb',
|
||||
picture: 'https://api.faviconkit.com/airbnb.com/144',
|
||||
pictureUrl: 'https://api.faviconkit.com/airbnb.com/144',
|
||||
},
|
||||
};
|
||||
|
||||
@ -56,6 +55,6 @@ export const BigName: Story = {
|
||||
args: {
|
||||
id: 'google',
|
||||
name: 'Google with a real big name to overflow the cell',
|
||||
picture: 'https://api.faviconkit.com/google.com/144',
|
||||
pictureUrl: 'https://api.faviconkit.com/google.com/144',
|
||||
},
|
||||
};
|
||||
|
||||
@ -41,7 +41,7 @@ export function CompanyAccountOwnerCell({ company }: OwnProps) {
|
||||
<PersonChip
|
||||
id={company.accountOwner.id}
|
||||
name={company.accountOwner?.displayName ?? ''}
|
||||
picture={company.accountOwner?.avatarUrl ?? ''}
|
||||
pictureUrl={company.accountOwner?.avatarUrl ?? ''}
|
||||
/>
|
||||
) : (
|
||||
<></>
|
||||
|
||||
@ -10,7 +10,6 @@ import { GET_PIPELINE_PROGRESS, GET_PIPELINES } from '@/pipeline/queries';
|
||||
import { BoardCardContext } from '@/pipeline/states/BoardCardContext';
|
||||
import { pipelineProgressIdScopedState } from '@/pipeline/states/pipelineProgressIdScopedState';
|
||||
import { selectedBoardCardsState } from '@/pipeline/states/selectedBoardCardsState';
|
||||
import { ChipVariant } from '@/ui/chip/components/EntityChip';
|
||||
import { DateEditableField } from '@/ui/editable-field/variants/components/DateEditableField';
|
||||
import { NumberEditableField } from '@/ui/editable-field/variants/components/NumberEditableField';
|
||||
import { IconCurrencyDollar, IconProgressCheck } from '@/ui/icon';
|
||||
@ -177,9 +176,8 @@ export function CompanyBoardCard() {
|
||||
<CompanyChip
|
||||
id={company.id}
|
||||
name={company.name}
|
||||
clickable
|
||||
picture={getLogoUrlFromDomainName(company.domainName)}
|
||||
variant={ChipVariant.transparent}
|
||||
pictureUrl={getLogoUrlFromDomainName(company.domainName)}
|
||||
clickable={false}
|
||||
/>
|
||||
<StyledCheckboxContainer className="checkbox-container">
|
||||
<Checkbox
|
||||
|
||||
@ -1,29 +1,25 @@
|
||||
import { ChipVariant, EntityChip } from '@/ui/chip/components/EntityChip';
|
||||
import { EntityChip } from '@/ui/chip/components/EntityChip';
|
||||
|
||||
type OwnProps = {
|
||||
id: string;
|
||||
name: string;
|
||||
picture?: string;
|
||||
pictureUrl?: string;
|
||||
clickable?: boolean;
|
||||
variant?: ChipVariant;
|
||||
};
|
||||
|
||||
export function CompanyChip({
|
||||
id,
|
||||
name,
|
||||
picture,
|
||||
clickable,
|
||||
variant = ChipVariant.opaque,
|
||||
pictureUrl,
|
||||
clickable = true,
|
||||
}: OwnProps) {
|
||||
return (
|
||||
<EntityChip
|
||||
entityId={id}
|
||||
linkToEntity={`/companies/${id}`}
|
||||
linkToEntity={clickable ? `/companies/${id}` : undefined}
|
||||
name={name}
|
||||
avatarType="squared"
|
||||
clickable={clickable}
|
||||
picture={picture}
|
||||
variant={variant}
|
||||
pictureUrl={pictureUrl}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@ -38,7 +38,7 @@ export function CompanyEditableNameChipCell({ company }: OwnProps) {
|
||||
id={company.id}
|
||||
name={company.name}
|
||||
clickable
|
||||
picture={getLogoUrlFromDomainName(company.domainName)}
|
||||
pictureUrl={getLogoUrlFromDomainName(company.domainName)}
|
||||
/>
|
||||
}
|
||||
onSubmit={() =>
|
||||
|
||||
@ -31,7 +31,7 @@ export function CompanyAccountOwnerEditableField({ company }: OwnProps) {
|
||||
<PersonChip
|
||||
id={company.accountOwner.id}
|
||||
name={company.accountOwner?.displayName ?? ''}
|
||||
picture={company.accountOwner?.avatarUrl ?? ''}
|
||||
pictureUrl={company.accountOwner?.avatarUrl ?? ''}
|
||||
/>
|
||||
) : (
|
||||
<></>
|
||||
|
||||
@ -58,7 +58,6 @@ export function EditablePeopleFullName({
|
||||
<PersonChip
|
||||
name={`${person?.firstName ?? ''} ${person?.lastName ?? ''}`}
|
||||
id={person?.id ?? ''}
|
||||
clickable
|
||||
/>
|
||||
</NoEditModeContainer>
|
||||
}
|
||||
|
||||
@ -38,7 +38,7 @@ export function PeopleCompanyCell({ people }: OwnProps) {
|
||||
<CompanyChip
|
||||
id={people.company?.id ?? ''}
|
||||
name={people.company?.name ?? ''}
|
||||
picture={getLogoUrlFromDomainName(people.company?.domainName)}
|
||||
pictureUrl={getLogoUrlFromDomainName(people.company?.domainName)}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
|
||||
@ -3,24 +3,23 @@ import { EntityChip } from '@/ui/chip/components/EntityChip';
|
||||
export type PersonChipPropsType = {
|
||||
id: string;
|
||||
name: string;
|
||||
picture?: string;
|
||||
pictureUrl?: string;
|
||||
clickable?: boolean;
|
||||
};
|
||||
|
||||
export function PersonChip({
|
||||
id,
|
||||
name,
|
||||
picture,
|
||||
clickable,
|
||||
pictureUrl,
|
||||
clickable = true,
|
||||
}: PersonChipPropsType) {
|
||||
return (
|
||||
<EntityChip
|
||||
entityId={id}
|
||||
linkToEntity={`/person/${id}`}
|
||||
linkToEntity={clickable ? `/person/${id}` : undefined}
|
||||
name={name}
|
||||
avatarType="rounded"
|
||||
clickable={clickable}
|
||||
picture={picture}
|
||||
pictureUrl={pictureUrl}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@ -2,7 +2,7 @@ import { BrowserRouter } from 'react-router-dom';
|
||||
import styled from '@emotion/styled';
|
||||
import type { Meta, StoryObj } from '@storybook/react';
|
||||
|
||||
import { ComponentDecorator } from '~/testing/decorators';
|
||||
import { ComponentDecorator } from '~/testing/decorators/ComponentDecorator';
|
||||
|
||||
import { PersonChip } from '../PersonChip';
|
||||
|
||||
@ -15,7 +15,6 @@ const TestCellContainer = styled.div`
|
||||
max-width: 250px;
|
||||
min-width: 250px;
|
||||
overflow: hidden;
|
||||
text-wrap: nowrap;
|
||||
`;
|
||||
|
||||
const meta: Meta<typeof PersonChip> = {
|
||||
|
||||
@ -33,7 +33,7 @@ export function PeopleCompanyEditableField({ people }: OwnProps) {
|
||||
<CompanyChip
|
||||
id={people.company.id}
|
||||
name={people.company.name}
|
||||
picture={getLogoUrlFromDomainName(people.company.domainName)}
|
||||
pictureUrl={getLogoUrlFromDomainName(people.company.domainName)}
|
||||
/>
|
||||
) : (
|
||||
<></>
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import type { Meta, StoryObj } from '@storybook/react';
|
||||
|
||||
import { ComponentDecorator } from '~/testing/decorators';
|
||||
import { ComponentDecorator } from '~/testing/decorators/ComponentDecorator';
|
||||
|
||||
import { ActionBar } from '../ActionBar';
|
||||
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import type { Meta, StoryObj } from '@storybook/react';
|
||||
|
||||
import { ComponentDecorator } from '~/testing/decorators';
|
||||
import { ComponentDecorator } from '~/testing/decorators/ComponentDecorator';
|
||||
|
||||
import {
|
||||
BoardColumnEditTitleMenu,
|
||||
|
||||
@ -3,7 +3,7 @@ import type { Meta, StoryObj } from '@storybook/react';
|
||||
import { userEvent, within } from '@storybook/testing-library';
|
||||
|
||||
import { IconBrandGoogle } from '@/ui/icon';
|
||||
import { ComponentDecorator } from '~/testing/decorators';
|
||||
import { ComponentDecorator } from '~/testing/decorators/ComponentDecorator';
|
||||
|
||||
import { MainButton } from '../MainButton';
|
||||
|
||||
|
||||
@ -3,7 +3,7 @@ import type { Meta, StoryObj } from '@storybook/react';
|
||||
import { userEvent, within } from '@storybook/testing-library';
|
||||
|
||||
import { IconArrowRight } from '@/ui/icon';
|
||||
import { ComponentDecorator } from '~/testing/decorators';
|
||||
import { ComponentDecorator } from '~/testing/decorators/ComponentDecorator';
|
||||
|
||||
import { RoundedIconButton } from '../RoundedIconButton';
|
||||
|
||||
|
||||
120
front/src/modules/ui/chip/components/Chip.tsx
Normal file
120
front/src/modules/ui/chip/components/Chip.tsx
Normal file
@ -0,0 +1,120 @@
|
||||
import * as React from 'react';
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
import { OverflowingTextWithTooltip } from '../../tooltip/OverflowingTextWithTooltip';
|
||||
|
||||
export enum ChipSize {
|
||||
Large = 'large',
|
||||
Small = 'small',
|
||||
}
|
||||
|
||||
export enum ChipVariant {
|
||||
Highlighted = 'highlighted',
|
||||
Regular = 'regular',
|
||||
Transparent = 'transparent',
|
||||
}
|
||||
|
||||
type OwnProps = {
|
||||
size?: ChipSize;
|
||||
disabled?: boolean;
|
||||
clickable?: boolean;
|
||||
label: string;
|
||||
maxWidth?: string;
|
||||
variant?: ChipVariant;
|
||||
leftComponent?: React.ReactNode;
|
||||
rightComponent?: React.ReactNode;
|
||||
className?: string;
|
||||
};
|
||||
|
||||
const StyledContainer = styled.div<Partial<OwnProps>>`
|
||||
align-items: center;
|
||||
|
||||
background-color: ${({ theme, variant }) =>
|
||||
variant === ChipVariant.Highlighted
|
||||
? theme.background.transparent.light
|
||||
: 'transparent'};
|
||||
border-radius: ${({ theme }) => theme.border.radius.sm};
|
||||
color: ${({ theme, disabled }) =>
|
||||
disabled ? theme.font.color.light : theme.font.color.primary};
|
||||
cursor: ${({ clickable, disabled, variant }) =>
|
||||
disabled || variant === ChipVariant.Transparent
|
||||
? 'auto'
|
||||
: clickable
|
||||
? 'pointer'
|
||||
: 'auto'};
|
||||
display: inline-flex;
|
||||
gap: ${({ theme }) => theme.spacing(1)};
|
||||
|
||||
height: ${({ size }) => (size === ChipSize.Large ? '16px' : '12px')};
|
||||
max-width: ${({ maxWidth }) => (maxWidth ? maxWidth : '200px')};
|
||||
|
||||
overflow: hidden;
|
||||
padding: ${({ theme }) => theme.spacing(1)};
|
||||
user-select: none;
|
||||
|
||||
:hover {
|
||||
${({ variant, theme, disabled }) => {
|
||||
if (!disabled) {
|
||||
return (
|
||||
'background-color: ' +
|
||||
(variant === ChipVariant.Highlighted
|
||||
? theme.background.transparent.medium
|
||||
: variant === ChipVariant.Regular
|
||||
? theme.background.transparent.light
|
||||
: 'transparent') +
|
||||
';'
|
||||
);
|
||||
}
|
||||
}}
|
||||
}
|
||||
:active {
|
||||
${({ variant, theme, disabled }) => {
|
||||
if (!disabled) {
|
||||
return (
|
||||
'background-color: ' +
|
||||
(variant === ChipVariant.Highlighted
|
||||
? theme.background.transparent.strong
|
||||
: variant === ChipVariant.Regular
|
||||
? theme.background.transparent.medium
|
||||
: 'transparent') +
|
||||
';'
|
||||
);
|
||||
}
|
||||
}}
|
||||
}
|
||||
`;
|
||||
|
||||
const StyledLabel = styled.span`
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
`;
|
||||
|
||||
export function Chip({
|
||||
size = ChipSize.Small,
|
||||
label,
|
||||
disabled = false,
|
||||
clickable = true,
|
||||
variant = ChipVariant.Regular,
|
||||
leftComponent,
|
||||
rightComponent,
|
||||
maxWidth,
|
||||
className,
|
||||
}: OwnProps) {
|
||||
return (
|
||||
<StyledContainer
|
||||
data-testid="chip"
|
||||
clickable={clickable}
|
||||
variant={variant}
|
||||
size={size}
|
||||
disabled={disabled}
|
||||
className={className}
|
||||
>
|
||||
{leftComponent}
|
||||
<StyledLabel>
|
||||
<OverflowingTextWithTooltip text={label} />
|
||||
</StyledLabel>
|
||||
{rightComponent}
|
||||
</StyledContainer>
|
||||
);
|
||||
}
|
||||
@ -1,122 +1,53 @@
|
||||
import * as React from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { Theme } from '@emotion/react';
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
import { Avatar, AvatarType } from '@/users/components/Avatar';
|
||||
import { isNonEmptyString } from '~/utils/isNonEmptyString';
|
||||
|
||||
import { OverflowingTextWithTooltip } from '../../tooltip/OverflowingTextWithTooltip';
|
||||
|
||||
export enum ChipVariant {
|
||||
opaque = 'opaque',
|
||||
transparent = 'transparent',
|
||||
}
|
||||
|
||||
const baseStyle = ({ theme }: { theme: Theme }) => `
|
||||
align-items: center;
|
||||
border-radius: ${theme.spacing(1)};
|
||||
color: ${theme.font.color.primary};
|
||||
display: inline-flex;
|
||||
gap: ${theme.spacing(1)};
|
||||
height: 12px;
|
||||
overflow: hidden;
|
||||
padding: ${theme.spacing(1)};
|
||||
text-decoration: none;
|
||||
|
||||
img {
|
||||
border-radius: ${theme.border.radius.rounded};
|
||||
height: 14px;
|
||||
object-fit: cover;
|
||||
width: 14px;
|
||||
}
|
||||
|
||||
white-space: nowrap;
|
||||
`;
|
||||
|
||||
const StyledContainerLink = styled.div<{ variant: string }>`
|
||||
${baseStyle}
|
||||
background-color: ${({ theme, variant }) =>
|
||||
variant === ChipVariant.opaque ? theme.background.tertiary : 'transparent'};
|
||||
:hover {
|
||||
background-color: ${({ variant, theme }) =>
|
||||
variant === ChipVariant.opaque
|
||||
? theme.background.quaternary
|
||||
: theme.background.transparent.light};
|
||||
}
|
||||
`;
|
||||
|
||||
const StyledContainerReadOnly = styled.div`
|
||||
${baseStyle}
|
||||
`;
|
||||
|
||||
const StyledName = styled.span`
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
`;
|
||||
import { Chip, ChipVariant } from './Chip';
|
||||
|
||||
type OwnProps = {
|
||||
linkToEntity: string;
|
||||
linkToEntity?: string;
|
||||
entityId: string;
|
||||
name: string;
|
||||
picture?: string;
|
||||
clickable?: boolean;
|
||||
pictureUrl?: string;
|
||||
avatarType?: AvatarType;
|
||||
variant?: ChipVariant;
|
||||
};
|
||||
|
||||
export function EntityChip({
|
||||
linkToEntity,
|
||||
entityId,
|
||||
name,
|
||||
picture,
|
||||
clickable,
|
||||
pictureUrl,
|
||||
avatarType = 'rounded',
|
||||
variant = ChipVariant.opaque,
|
||||
}: OwnProps) {
|
||||
const navigate = useNavigate();
|
||||
|
||||
function handleLinkClick(event: React.MouseEvent<HTMLDivElement>) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
navigate(linkToEntity);
|
||||
if (linkToEntity) {
|
||||
navigate(linkToEntity);
|
||||
}
|
||||
}
|
||||
|
||||
return clickable && linkToEntity ? (
|
||||
<StyledContainerLink
|
||||
data-testid="entity-chip"
|
||||
onClick={handleLinkClick}
|
||||
variant={variant}
|
||||
>
|
||||
{isNonEmptyString(name) && (
|
||||
<Avatar
|
||||
avatarUrl={picture}
|
||||
colorId={entityId}
|
||||
placeholder={name}
|
||||
size={14}
|
||||
type={avatarType}
|
||||
/>
|
||||
)}
|
||||
<StyledName>
|
||||
<OverflowingTextWithTooltip text={name} />
|
||||
</StyledName>
|
||||
</StyledContainerLink>
|
||||
return isNonEmptyString(name) ? (
|
||||
<div onClick={handleLinkClick}>
|
||||
<Chip
|
||||
label={name}
|
||||
variant={linkToEntity ? ChipVariant.Highlighted : ChipVariant.Regular}
|
||||
leftComponent={
|
||||
<Avatar
|
||||
avatarUrl={pictureUrl}
|
||||
colorId={entityId}
|
||||
placeholder={name}
|
||||
size={14}
|
||||
type={avatarType}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<StyledContainerReadOnly data-testid="entity-chip">
|
||||
{isNonEmptyString(name) && (
|
||||
<Avatar
|
||||
avatarUrl={picture}
|
||||
colorId={entityId}
|
||||
placeholder={name}
|
||||
size={14}
|
||||
type={avatarType}
|
||||
/>
|
||||
)}
|
||||
<StyledName>
|
||||
<OverflowingTextWithTooltip text={name} />
|
||||
</StyledName>
|
||||
</StyledContainerReadOnly>
|
||||
<></>
|
||||
);
|
||||
}
|
||||
|
||||
@ -0,0 +1,48 @@
|
||||
import type { Meta, StoryObj } from '@storybook/react';
|
||||
|
||||
import { ComponentDecorator } from '~/testing/decorators/ComponentDecorator';
|
||||
import { ExhaustiveComponentDecorator } from '~/testing/decorators/ExhaustiveComponentDecorator';
|
||||
|
||||
import { Chip, ChipSize, ChipVariant } from '../Chip';
|
||||
|
||||
const meta: Meta<typeof Chip> = {
|
||||
title: 'UI/Chip/Chip',
|
||||
component: Chip,
|
||||
};
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof Chip>;
|
||||
|
||||
export const Default: Story = {
|
||||
args: {
|
||||
label: 'Chip test',
|
||||
size: ChipSize.Small,
|
||||
variant: ChipVariant.Highlighted,
|
||||
clickable: true,
|
||||
maxWidth: '200px',
|
||||
},
|
||||
decorators: [ComponentDecorator],
|
||||
};
|
||||
|
||||
export const All: Story = {
|
||||
args: { size: ChipSize.Large, clickable: true, label: 'Hello' },
|
||||
argTypes: {
|
||||
size: { control: false },
|
||||
variant: { control: false },
|
||||
disabled: { control: false },
|
||||
className: { control: false },
|
||||
rightComponent: { control: false },
|
||||
leftComponent: { control: false },
|
||||
},
|
||||
parameters: {
|
||||
pseudo: { hover: ['.hover'], active: ['.active'] },
|
||||
variants: [
|
||||
ChipVariant.Highlighted,
|
||||
ChipVariant.Regular,
|
||||
ChipVariant.Transparent,
|
||||
],
|
||||
sizes: [ChipSize.Small, ChipSize.Large],
|
||||
states: ['default', 'hover', 'active', 'disabled'],
|
||||
},
|
||||
decorators: [ExhaustiveComponentDecorator],
|
||||
};
|
||||
@ -0,0 +1,21 @@
|
||||
import type { Meta, StoryObj } from '@storybook/react';
|
||||
|
||||
import { ComponentWithRouterDecorator } from '~/testing/decorators/ComponentWithRouterDecorator';
|
||||
|
||||
import { EntityChip } from '../EntityChip';
|
||||
|
||||
const meta: Meta<typeof EntityChip> = {
|
||||
title: 'UI/Chip/EntityChip',
|
||||
component: EntityChip,
|
||||
decorators: [ComponentWithRouterDecorator],
|
||||
args: {
|
||||
name: 'Entity name',
|
||||
linkToEntity: '/entity-link',
|
||||
avatarType: 'squared',
|
||||
},
|
||||
};
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof EntityChip>;
|
||||
|
||||
export const Default: Story = {};
|
||||
@ -1,7 +1,7 @@
|
||||
import styled from '@emotion/styled';
|
||||
import type { Meta, StoryObj } from '@storybook/react';
|
||||
|
||||
import { ComponentDecorator } from '~/testing/decorators';
|
||||
import { ComponentDecorator } from '~/testing/decorators/ComponentDecorator';
|
||||
|
||||
import { ColorSchemeCard } from '../ColorSchemeCard';
|
||||
|
||||
|
||||
@ -4,7 +4,7 @@ import type { Meta, StoryObj } from '@storybook/react';
|
||||
|
||||
import { IconPlus } from '@/ui/icon/index';
|
||||
import { Avatar } from '@/users/components/Avatar';
|
||||
import { ComponentDecorator } from '~/testing/decorators';
|
||||
import { ComponentDecorator } from '~/testing/decorators/ComponentDecorator';
|
||||
|
||||
import { DropdownMenuSkeletonItem } from '../../../relation-picker/components/skeletons/DropdownMenuSkeletonItem';
|
||||
import { DropdownMenu } from '../DropdownMenu';
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import type { Meta, StoryObj } from '@storybook/react';
|
||||
import { IconCalendar } from '@tabler/icons-react';
|
||||
|
||||
import { ComponentDecorator } from '~/testing/decorators';
|
||||
import { ComponentDecorator } from '~/testing/decorators/ComponentDecorator';
|
||||
|
||||
import { DateEditableField } from '../DateEditableField';
|
||||
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import type { Meta, StoryObj } from '@storybook/react';
|
||||
import { IconCurrencyDollar } from '@tabler/icons-react';
|
||||
|
||||
import { ComponentDecorator } from '~/testing/decorators';
|
||||
import { ComponentDecorator } from '~/testing/decorators/ComponentDecorator';
|
||||
|
||||
import { NumberEditableField } from '../NumberEditableField';
|
||||
|
||||
|
||||
@ -2,7 +2,7 @@ import { BrowserRouter } from 'react-router-dom';
|
||||
import type { Meta, StoryObj } from '@storybook/react';
|
||||
import { IconPhone } from '@tabler/icons-react';
|
||||
|
||||
import { ComponentDecorator } from '~/testing/decorators';
|
||||
import { ComponentDecorator } from '~/testing/decorators/ComponentDecorator';
|
||||
|
||||
import { PhoneEditableField } from '../PhoneEditableField';
|
||||
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import type { Meta, StoryObj } from '@storybook/react';
|
||||
import { IconUser } from '@tabler/icons-react';
|
||||
|
||||
import { ComponentDecorator } from '~/testing/decorators';
|
||||
import { ComponentDecorator } from '~/testing/decorators/ComponentDecorator';
|
||||
|
||||
import { TextEditableField } from '../TextEditableField';
|
||||
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import type { Meta, StoryObj } from '@storybook/react';
|
||||
|
||||
import { ComponentDecorator } from '~/testing/decorators';
|
||||
import { ComponentDecorator } from '~/testing/decorators/ComponentDecorator';
|
||||
|
||||
import { AutosizeTextInput } from '../AutosizeTextInput';
|
||||
|
||||
|
||||
@ -4,7 +4,7 @@ import { jest } from '@storybook/jest';
|
||||
import type { Meta, StoryObj } from '@storybook/react';
|
||||
import { userEvent, within } from '@storybook/testing-library';
|
||||
|
||||
import { ComponentDecorator } from '~/testing/decorators';
|
||||
import { ComponentDecorator } from '~/testing/decorators/ComponentDecorator';
|
||||
|
||||
import { TextInput } from '../TextInput';
|
||||
|
||||
|
||||
@ -3,7 +3,7 @@ import { expect, jest } from '@storybook/jest';
|
||||
import type { Meta, StoryObj } from '@storybook/react';
|
||||
import { userEvent, within } from '@storybook/testing-library';
|
||||
|
||||
import { ComponentDecorator } from '~/testing/decorators';
|
||||
import { ComponentDecorator } from '~/testing/decorators/ComponentDecorator';
|
||||
|
||||
import { PrimaryLink } from '../PrimaryLink';
|
||||
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import type { Meta, StoryObj } from '@storybook/react';
|
||||
|
||||
import { ComponentDecorator } from '~/testing/decorators';
|
||||
import { ComponentDecorator } from '~/testing/decorators/ComponentDecorator';
|
||||
|
||||
import { SoonPill } from '../SoonPill';
|
||||
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import type { Meta, StoryObj } from '@storybook/react';
|
||||
|
||||
import { ComponentDecorator } from '~/testing/decorators';
|
||||
import { ComponentDecorator } from '~/testing/decorators/ComponentDecorator';
|
||||
import { graphqlMocks } from '~/testing/graphqlMocks';
|
||||
|
||||
import { RightDrawerTopBar } from '../RightDrawerTopBar';
|
||||
|
||||
@ -2,10 +2,8 @@ import { expect } from '@storybook/jest';
|
||||
import type { Meta, StoryObj } from '@storybook/react';
|
||||
import { userEvent, within } from '@storybook/testing-library';
|
||||
|
||||
import {
|
||||
CellPositionDecorator,
|
||||
ComponentDecorator,
|
||||
} from '~/testing/decorators';
|
||||
import { CellPositionDecorator } from '~/testing/decorators/CellPositionDecorator';
|
||||
import { ComponentDecorator } from '~/testing/decorators/ComponentDecorator';
|
||||
|
||||
import { EditableCellText } from '../../types/EditableCellText';
|
||||
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import type { Meta, StoryObj } from '@storybook/react';
|
||||
|
||||
import { ComponentDecorator } from '~/testing/decorators';
|
||||
import { ComponentDecorator } from '~/testing/decorators/ComponentDecorator';
|
||||
|
||||
import { Tag } from '../Tag';
|
||||
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import type { Meta, StoryObj } from '@storybook/react';
|
||||
|
||||
import { ComponentDecorator } from '~/testing/decorators';
|
||||
import { ComponentDecorator } from '~/testing/decorators/ComponentDecorator';
|
||||
import { avatarUrl } from '~/testing/mock-data/users';
|
||||
|
||||
import { Avatar } from '../Avatar';
|
||||
|
||||
@ -194,7 +194,10 @@ export const EditRelation: Story = {
|
||||
mockedPeopleData[1].company.name,
|
||||
);
|
||||
|
||||
await userEvent.click(secondRowCompanyCell);
|
||||
await userEvent.click(
|
||||
secondRowCompanyCell.parentNode?.parentNode?.parentNode
|
||||
?.parentElement as HTMLElement,
|
||||
);
|
||||
});
|
||||
|
||||
await step('Type "Air" in relation picker', async () => {
|
||||
@ -244,7 +247,10 @@ export const SelectRelationWithKeys: Story = {
|
||||
);
|
||||
await sleep(25);
|
||||
|
||||
await userEvent.click(firstRowCompanyCell);
|
||||
await userEvent.click(
|
||||
firstRowCompanyCell.parentNode?.parentNode?.parentNode
|
||||
?.parentElement as HTMLElement,
|
||||
);
|
||||
firstRowCompanyCell = await canvas.findByText(
|
||||
mockedPeopleData[0].company.name,
|
||||
);
|
||||
|
||||
@ -9,6 +9,7 @@ const StyledLayout = styled.div`
|
||||
flex-direction: row;
|
||||
|
||||
height: fit-content;
|
||||
max-width: calc(100% - 40px);
|
||||
min-width: 300px;
|
||||
padding: 20px;
|
||||
width: fit-content;
|
||||
|
||||
@ -1,32 +0,0 @@
|
||||
import { ApolloProvider } from '@apollo/client';
|
||||
import { Decorator } from '@storybook/react';
|
||||
import { RecoilRoot } from 'recoil';
|
||||
|
||||
import { RecoilScope } from '@/ui/recoil-scope/components/RecoilScope';
|
||||
import { CellContext } from '@/ui/table/states/CellContext';
|
||||
import { RowContext } from '@/ui/table/states/RowContext';
|
||||
|
||||
import { ComponentStorybookLayout } from './ComponentStorybookLayout';
|
||||
import { mockedClient } from './mockedClient';
|
||||
|
||||
export const RootDecorator: Decorator = (Story) => (
|
||||
<RecoilRoot>
|
||||
<ApolloProvider client={mockedClient}>
|
||||
<Story />
|
||||
</ApolloProvider>
|
||||
</RecoilRoot>
|
||||
);
|
||||
|
||||
export const ComponentDecorator: Decorator = (Story) => (
|
||||
<ComponentStorybookLayout>
|
||||
<Story />
|
||||
</ComponentStorybookLayout>
|
||||
);
|
||||
|
||||
export const CellPositionDecorator: Decorator = (Story) => (
|
||||
<RecoilScope SpecificContext={RowContext}>
|
||||
<RecoilScope SpecificContext={CellContext}>
|
||||
<Story />
|
||||
</RecoilScope>
|
||||
</RecoilScope>
|
||||
);
|
||||
13
front/src/testing/decorators/CellPositionDecorator.tsx
Normal file
13
front/src/testing/decorators/CellPositionDecorator.tsx
Normal file
@ -0,0 +1,13 @@
|
||||
import { Decorator } from '@storybook/react';
|
||||
|
||||
import { RecoilScope } from '../../modules/ui/recoil-scope/components/RecoilScope';
|
||||
import { CellContext } from '../../modules/ui/table/states/CellContext';
|
||||
import { RowContext } from '../../modules/ui/table/states/RowContext';
|
||||
|
||||
export const CellPositionDecorator: Decorator = (Story) => (
|
||||
<RecoilScope SpecificContext={RowContext}>
|
||||
<RecoilScope SpecificContext={CellContext}>
|
||||
<Story />
|
||||
</RecoilScope>
|
||||
</RecoilScope>
|
||||
);
|
||||
9
front/src/testing/decorators/ComponentDecorator.tsx
Normal file
9
front/src/testing/decorators/ComponentDecorator.tsx
Normal file
@ -0,0 +1,9 @@
|
||||
import { Decorator } from '@storybook/react';
|
||||
|
||||
import { ComponentStorybookLayout } from '../ComponentStorybookLayout';
|
||||
|
||||
export const ComponentDecorator: Decorator = (Story) => (
|
||||
<ComponentStorybookLayout>
|
||||
<Story />
|
||||
</ComponentStorybookLayout>
|
||||
);
|
||||
@ -0,0 +1,12 @@
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
import { Decorator } from '@storybook/react';
|
||||
|
||||
import { ComponentStorybookLayout } from '../ComponentStorybookLayout';
|
||||
|
||||
export const ComponentWithRouterDecorator: Decorator = (Story) => (
|
||||
<ComponentStorybookLayout>
|
||||
<MemoryRouter>
|
||||
<Story />
|
||||
</MemoryRouter>
|
||||
</ComponentStorybookLayout>
|
||||
);
|
||||
110
front/src/testing/decorators/ExhaustiveComponentDecorator.tsx
Normal file
110
front/src/testing/decorators/ExhaustiveComponentDecorator.tsx
Normal file
@ -0,0 +1,110 @@
|
||||
import styled from '@emotion/styled';
|
||||
import { Decorator, StrictArgs } from '@storybook/react';
|
||||
|
||||
function stateProps(state: string) {
|
||||
switch (state) {
|
||||
case 'default':
|
||||
return {};
|
||||
case 'hover':
|
||||
return { className: 'hover' };
|
||||
case 'active':
|
||||
return { className: 'active' };
|
||||
case 'disabled':
|
||||
return { disabled: true };
|
||||
default:
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
const StyledSizeTitle = styled.h1`
|
||||
font-size: ${({ theme }) => theme.font.size.lg};
|
||||
font-weight: ${({ theme }) => theme.font.weight.semiBold};
|
||||
margin: ${({ theme }) => theme.spacing(2)};
|
||||
`;
|
||||
|
||||
const StyledVariantTitle = styled.h1`
|
||||
color: ${({ theme }) => theme.font.color.tertiary};
|
||||
font-size: ${({ theme }) => theme.font.size.md};
|
||||
font-weight: ${({ theme }) => theme.font.weight.semiBold};
|
||||
margin: ${({ theme }) => theme.spacing(2)};
|
||||
`;
|
||||
|
||||
const StyledStateTitle = styled.span`
|
||||
color: ${({ theme }) => theme.font.color.light};
|
||||
font-size: ${({ theme }) => theme.font.size.xs};
|
||||
font-weight: ${({ theme }) => theme.font.weight.semiBold};
|
||||
margin-bottom: ${({ theme }) => theme.spacing(1)};
|
||||
text-align: center;
|
||||
text-transform: uppercase;
|
||||
`;
|
||||
|
||||
const StyledContainer = styled.div`
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
`;
|
||||
|
||||
const StyledSizeContainer = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: ${({ theme }) => theme.spacing(2)};
|
||||
`;
|
||||
|
||||
const StyledLineContainer = styled.div`
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-direction: row;
|
||||
gap: ${({ theme }) => theme.spacing(2)};
|
||||
`;
|
||||
|
||||
const StyledComponentContainer = styled.div`
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: ${({ theme }) => theme.spacing(2)};
|
||||
`;
|
||||
|
||||
function renderSize(
|
||||
size: string,
|
||||
variants: string[],
|
||||
states: string[],
|
||||
args: StrictArgs,
|
||||
Story: React.FC<StrictArgs>,
|
||||
) {
|
||||
return (
|
||||
<StyledSizeContainer key={size}>
|
||||
<StyledSizeTitle>{size}</StyledSizeTitle>
|
||||
{variants.map((variant) => (
|
||||
<div key={variant}>
|
||||
<StyledVariantTitle>{variant}</StyledVariantTitle>
|
||||
<StyledLineContainer>
|
||||
{states.map((state) => (
|
||||
<StyledComponentContainer key={`${variant}-container-${state}`}>
|
||||
<StyledStateTitle>{state}</StyledStateTitle>
|
||||
<Story
|
||||
args={{ ...args, variant: variant, ...stateProps(state) }}
|
||||
/>
|
||||
</StyledComponentContainer>
|
||||
))}
|
||||
</StyledLineContainer>
|
||||
</div>
|
||||
))}
|
||||
</StyledSizeContainer>
|
||||
);
|
||||
}
|
||||
|
||||
export const ExhaustiveComponentDecorator: Decorator = (Story, context) => {
|
||||
const parameters = context.parameters;
|
||||
return (
|
||||
<StyledContainer>
|
||||
{parameters.sizes.map((size: string) =>
|
||||
renderSize(
|
||||
size,
|
||||
parameters.variants,
|
||||
parameters.states,
|
||||
context.args,
|
||||
Story,
|
||||
),
|
||||
)}
|
||||
</StyledContainer>
|
||||
);
|
||||
};
|
||||
13
front/src/testing/decorators/RootDecorator.tsx
Normal file
13
front/src/testing/decorators/RootDecorator.tsx
Normal file
@ -0,0 +1,13 @@
|
||||
import { ApolloProvider } from '@apollo/client';
|
||||
import { Decorator } from '@storybook/react';
|
||||
import { RecoilRoot } from 'recoil';
|
||||
|
||||
import { mockedClient } from '../mockedClient';
|
||||
|
||||
export const RootDecorator: Decorator = (Story) => (
|
||||
<RecoilRoot>
|
||||
<ApolloProvider client={mockedClient}>
|
||||
<Story />
|
||||
</ApolloProvider>
|
||||
</RecoilRoot>
|
||||
);
|
||||
Reference in New Issue
Block a user