Improve tests (#871)

This commit is contained in:
Charles Bochet
2023-07-24 00:57:56 -07:00
committed by GitHub
parent 2b885f2496
commit 07180af8c0
53 changed files with 432 additions and 251 deletions

View File

@ -3,7 +3,7 @@ import { Preview } from '@storybook/react';
import { ThemeProvider } from '@emotion/react'; import { ThemeProvider } from '@emotion/react';
import { withThemeFromJSXProvider } from '@storybook/addon-styling'; import { withThemeFromJSXProvider } from '@storybook/addon-styling';
import { lightTheme, darkTheme } from '../src/modules/ui/themes/themes'; 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 'react-loading-skeleton/dist/skeleton.css';
import { mockedUserJWT } from '../src/testing/mock-data/jwt'; import { mockedUserJWT } from '../src/testing/mock-data/jwt';
initialize(); initialize();

View File

@ -1,6 +1,6 @@
import type { Meta, StoryObj } from '@storybook/react'; 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 { CommentThreadActionBar } from '../../right-drawer/components/CommentThreadActionBar';
import { Comment } from '../Comment'; import { Comment } from '../Comment';

View File

@ -2,7 +2,7 @@ import type { Meta, StoryObj } from '@storybook/react';
import { DateTime } from 'luxon'; import { DateTime } from 'luxon';
import { CommentThreadActionBar } from '@/activities/right-drawer/components/CommentThreadActionBar'; 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 { avatarUrl } from '~/testing/mock-data/users';
import { CommentHeader } from '../CommentHeader'; import { CommentHeader } from '../CommentHeader';

View File

@ -26,7 +26,7 @@ export function CommentThreadCreateButton({
<Button <Button
icon={<IconCheckbox size={theme.icon.size.sm} />} icon={<IconCheckbox size={theme.icon.size.sm} />}
title="Task" title="Task"
soon={true} onClick={onTaskClick}
/> />
<Button <Button
icon={<IconTimelineEvent size={theme.icon.size.sm} />} icon={<IconTimelineEvent size={theme.icon.size.sm} />}

View File

@ -213,7 +213,7 @@ export function CommentThreadRelationPicker({ commentThread }: OwnProps) {
key={entity.id} key={entity.id}
id={entity.id} id={entity.id}
name={entity.name} name={entity.name}
picture={entity.avatarUrl} pictureUrl={entity.avatarUrl}
/> />
) : ( ) : (
<PersonChip key={entity.id} name={entity.name} id={entity.id} /> <PersonChip key={entity.id} name={entity.name} id={entity.id} />

View File

@ -2,7 +2,7 @@ import {
DropdownButton, DropdownButton,
DropdownOptionType, DropdownOptionType,
} from '@/ui/button/components/DropdownButton'; } from '@/ui/button/components/DropdownButton';
import { IconNotes } from '@/ui/icon/index'; import { IconCheck, IconNotes } from '@/ui/icon';
import { import {
ActivityType, ActivityType,
CommentThread, CommentThread,
@ -17,6 +17,7 @@ export function CommentThreadTypeDropdown({ commentThread }: OwnProps) {
const [updateCommentThreadMutation] = useUpdateCommentThreadMutation(); const [updateCommentThreadMutation] = useUpdateCommentThreadMutation();
const options: DropdownOptionType[] = [ const options: DropdownOptionType[] = [
{ label: 'Note', key: 'note', icon: <IconNotes /> }, { label: 'Note', key: 'note', icon: <IconNotes /> },
{ label: 'Task', key: 'task', icon: <IconCheck /> },
]; ];
function getSelectedOptionKey() { function getSelectedOptionKey() {

View File

@ -2,7 +2,7 @@ import { MemoryRouter } from 'react-router-dom';
import styled from '@emotion/styled'; import styled from '@emotion/styled';
import type { Meta, StoryObj } from '@storybook/react'; import type { Meta, StoryObj } from '@storybook/react';
import { ComponentDecorator } from '~/testing/decorators'; import { ComponentDecorator } from '~/testing/decorators/ComponentDecorator';
import { graphqlMocks } from '~/testing/graphqlMocks'; import { graphqlMocks } from '~/testing/graphqlMocks';
import { mockedCommentThreads } from '~/testing/mock-data/comment-threads'; import { mockedCommentThreads } from '~/testing/mock-data/comment-threads';

View File

@ -1,7 +1,7 @@
import styled from '@emotion/styled'; import styled from '@emotion/styled';
import type { Meta, StoryObj } from '@storybook/react'; import type { Meta, StoryObj } from '@storybook/react';
import { ComponentDecorator } from '~/testing/decorators'; import { ComponentDecorator } from '~/testing/decorators/ComponentDecorator';
import { CommentChip } from '../CommentChip'; import { CommentChip } from '../CommentChip';

View File

@ -2,7 +2,7 @@ import { MemoryRouter } from 'react-router-dom';
import type { Meta, StoryObj } from '@storybook/react'; import type { Meta, StoryObj } from '@storybook/react';
import { fireEvent, userEvent, within } from '@storybook/testing-library'; 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 { sleep } from '~/testing/sleep';
import { CommandMenu } from '../CommandMenu'; import { CommandMenu } from '../CommandMenu';

View File

@ -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()}
/>
);
}

View File

@ -3,7 +3,7 @@ import { Meta, StoryObj } from '@storybook/react';
import { EntityBoard } from '@/pipeline/components/EntityBoard'; import { EntityBoard } from '@/pipeline/components/EntityBoard';
import { opportunitiesBoardOptions } from '~/pages/opportunities/opportunitiesBoardOptions'; import { opportunitiesBoardOptions } from '~/pages/opportunities/opportunitiesBoardOptions';
import { ComponentDecorator } from '~/testing/decorators'; import { ComponentDecorator } from '~/testing/decorators/ComponentDecorator';
import { graphqlMocks } from '~/testing/graphqlMocks'; import { graphqlMocks } from '~/testing/graphqlMocks';
import { defaultPipelineProgressOrderBy } from '../../pipeline/queries'; import { defaultPipelineProgressOrderBy } from '../../pipeline/queries';

View File

@ -3,7 +3,7 @@ import { MemoryRouter } from 'react-router-dom';
import { Meta, StoryObj } from '@storybook/react'; import { Meta, StoryObj } from '@storybook/react';
import { CompanyBoardCard } from '@/companies/components/CompanyBoardCard'; import { CompanyBoardCard } from '@/companies/components/CompanyBoardCard';
import { ComponentDecorator } from '~/testing/decorators'; import { ComponentDecorator } from '~/testing/decorators/ComponentDecorator';
import { graphqlMocks } from '~/testing/graphqlMocks'; import { graphqlMocks } from '~/testing/graphqlMocks';
import { mockedPipelineProgressData } from '~/testing/mock-data/pipeline-progress'; import { mockedPipelineProgressData } from '~/testing/mock-data/pipeline-progress';

View File

@ -2,7 +2,7 @@ import { BrowserRouter } from 'react-router-dom';
import styled from '@emotion/styled'; import styled from '@emotion/styled';
import type { Meta, StoryObj } from '@storybook/react'; import type { Meta, StoryObj } from '@storybook/react';
import { ComponentDecorator } from '~/testing/decorators'; import { ComponentDecorator } from '~/testing/decorators/ComponentDecorator';
import { CompanyChip } from '../components/CompanyChip'; import { CompanyChip } from '../components/CompanyChip';
@ -37,14 +37,13 @@ const TestCellContainer = styled.div`
min-width: 250px; min-width: 250px;
overflow: hidden; overflow: hidden;
text-wrap: nowrap;
`; `;
export const SmallName: Story = { export const SmallName: Story = {
args: { args: {
id: 'airbnb', id: 'airbnb',
name: '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: { args: {
id: 'google', id: 'google',
name: 'Google with a real big name to overflow the cell', 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',
}, },
}; };

View File

@ -41,7 +41,7 @@ export function CompanyAccountOwnerCell({ company }: OwnProps) {
<PersonChip <PersonChip
id={company.accountOwner.id} id={company.accountOwner.id}
name={company.accountOwner?.displayName ?? ''} name={company.accountOwner?.displayName ?? ''}
picture={company.accountOwner?.avatarUrl ?? ''} pictureUrl={company.accountOwner?.avatarUrl ?? ''}
/> />
) : ( ) : (
<></> <></>

View File

@ -10,7 +10,6 @@ import { GET_PIPELINE_PROGRESS, GET_PIPELINES } from '@/pipeline/queries';
import { BoardCardContext } from '@/pipeline/states/BoardCardContext'; import { BoardCardContext } from '@/pipeline/states/BoardCardContext';
import { pipelineProgressIdScopedState } from '@/pipeline/states/pipelineProgressIdScopedState'; import { pipelineProgressIdScopedState } from '@/pipeline/states/pipelineProgressIdScopedState';
import { selectedBoardCardsState } from '@/pipeline/states/selectedBoardCardsState'; import { selectedBoardCardsState } from '@/pipeline/states/selectedBoardCardsState';
import { ChipVariant } from '@/ui/chip/components/EntityChip';
import { DateEditableField } from '@/ui/editable-field/variants/components/DateEditableField'; import { DateEditableField } from '@/ui/editable-field/variants/components/DateEditableField';
import { NumberEditableField } from '@/ui/editable-field/variants/components/NumberEditableField'; import { NumberEditableField } from '@/ui/editable-field/variants/components/NumberEditableField';
import { IconCurrencyDollar, IconProgressCheck } from '@/ui/icon'; import { IconCurrencyDollar, IconProgressCheck } from '@/ui/icon';
@ -177,9 +176,8 @@ export function CompanyBoardCard() {
<CompanyChip <CompanyChip
id={company.id} id={company.id}
name={company.name} name={company.name}
clickable pictureUrl={getLogoUrlFromDomainName(company.domainName)}
picture={getLogoUrlFromDomainName(company.domainName)} clickable={false}
variant={ChipVariant.transparent}
/> />
<StyledCheckboxContainer className="checkbox-container"> <StyledCheckboxContainer className="checkbox-container">
<Checkbox <Checkbox

View File

@ -1,29 +1,25 @@
import { ChipVariant, EntityChip } from '@/ui/chip/components/EntityChip'; import { EntityChip } from '@/ui/chip/components/EntityChip';
type OwnProps = { type OwnProps = {
id: string; id: string;
name: string; name: string;
picture?: string; pictureUrl?: string;
clickable?: boolean; clickable?: boolean;
variant?: ChipVariant;
}; };
export function CompanyChip({ export function CompanyChip({
id, id,
name, name,
picture, pictureUrl,
clickable, clickable = true,
variant = ChipVariant.opaque,
}: OwnProps) { }: OwnProps) {
return ( return (
<EntityChip <EntityChip
entityId={id} entityId={id}
linkToEntity={`/companies/${id}`} linkToEntity={clickable ? `/companies/${id}` : undefined}
name={name} name={name}
avatarType="squared" avatarType="squared"
clickable={clickable} pictureUrl={pictureUrl}
picture={picture}
variant={variant}
/> />
); );
} }

View File

@ -38,7 +38,7 @@ export function CompanyEditableNameChipCell({ company }: OwnProps) {
id={company.id} id={company.id}
name={company.name} name={company.name}
clickable clickable
picture={getLogoUrlFromDomainName(company.domainName)} pictureUrl={getLogoUrlFromDomainName(company.domainName)}
/> />
} }
onSubmit={() => onSubmit={() =>

View File

@ -31,7 +31,7 @@ export function CompanyAccountOwnerEditableField({ company }: OwnProps) {
<PersonChip <PersonChip
id={company.accountOwner.id} id={company.accountOwner.id}
name={company.accountOwner?.displayName ?? ''} name={company.accountOwner?.displayName ?? ''}
picture={company.accountOwner?.avatarUrl ?? ''} pictureUrl={company.accountOwner?.avatarUrl ?? ''}
/> />
) : ( ) : (
<></> <></>

View File

@ -58,7 +58,6 @@ export function EditablePeopleFullName({
<PersonChip <PersonChip
name={`${person?.firstName ?? ''} ${person?.lastName ?? ''}`} name={`${person?.firstName ?? ''} ${person?.lastName ?? ''}`}
id={person?.id ?? ''} id={person?.id ?? ''}
clickable
/> />
</NoEditModeContainer> </NoEditModeContainer>
} }

View File

@ -38,7 +38,7 @@ export function PeopleCompanyCell({ people }: OwnProps) {
<CompanyChip <CompanyChip
id={people.company?.id ?? ''} id={people.company?.id ?? ''}
name={people.company?.name ?? ''} name={people.company?.name ?? ''}
picture={getLogoUrlFromDomainName(people.company?.domainName)} pictureUrl={getLogoUrlFromDomainName(people.company?.domainName)}
/> />
} }
/> />

View File

@ -3,24 +3,23 @@ import { EntityChip } from '@/ui/chip/components/EntityChip';
export type PersonChipPropsType = { export type PersonChipPropsType = {
id: string; id: string;
name: string; name: string;
picture?: string; pictureUrl?: string;
clickable?: boolean; clickable?: boolean;
}; };
export function PersonChip({ export function PersonChip({
id, id,
name, name,
picture, pictureUrl,
clickable, clickable = true,
}: PersonChipPropsType) { }: PersonChipPropsType) {
return ( return (
<EntityChip <EntityChip
entityId={id} entityId={id}
linkToEntity={`/person/${id}`} linkToEntity={clickable ? `/person/${id}` : undefined}
name={name} name={name}
avatarType="rounded" avatarType="rounded"
clickable={clickable} pictureUrl={pictureUrl}
picture={picture}
/> />
); );
} }

View File

@ -2,7 +2,7 @@ import { BrowserRouter } from 'react-router-dom';
import styled from '@emotion/styled'; import styled from '@emotion/styled';
import type { Meta, StoryObj } from '@storybook/react'; import type { Meta, StoryObj } from '@storybook/react';
import { ComponentDecorator } from '~/testing/decorators'; import { ComponentDecorator } from '~/testing/decorators/ComponentDecorator';
import { PersonChip } from '../PersonChip'; import { PersonChip } from '../PersonChip';
@ -15,7 +15,6 @@ const TestCellContainer = styled.div`
max-width: 250px; max-width: 250px;
min-width: 250px; min-width: 250px;
overflow: hidden; overflow: hidden;
text-wrap: nowrap;
`; `;
const meta: Meta<typeof PersonChip> = { const meta: Meta<typeof PersonChip> = {

View File

@ -33,7 +33,7 @@ export function PeopleCompanyEditableField({ people }: OwnProps) {
<CompanyChip <CompanyChip
id={people.company.id} id={people.company.id}
name={people.company.name} name={people.company.name}
picture={getLogoUrlFromDomainName(people.company.domainName)} pictureUrl={getLogoUrlFromDomainName(people.company.domainName)}
/> />
) : ( ) : (
<></> <></>

View File

@ -1,6 +1,6 @@
import type { Meta, StoryObj } from '@storybook/react'; import type { Meta, StoryObj } from '@storybook/react';
import { ComponentDecorator } from '~/testing/decorators'; import { ComponentDecorator } from '~/testing/decorators/ComponentDecorator';
import { ActionBar } from '../ActionBar'; import { ActionBar } from '../ActionBar';

View File

@ -1,6 +1,6 @@
import type { Meta, StoryObj } from '@storybook/react'; import type { Meta, StoryObj } from '@storybook/react';
import { ComponentDecorator } from '~/testing/decorators'; import { ComponentDecorator } from '~/testing/decorators/ComponentDecorator';
import { import {
BoardColumnEditTitleMenu, BoardColumnEditTitleMenu,

View File

@ -3,7 +3,7 @@ import type { Meta, StoryObj } from '@storybook/react';
import { userEvent, within } from '@storybook/testing-library'; import { userEvent, within } from '@storybook/testing-library';
import { IconBrandGoogle } from '@/ui/icon'; import { IconBrandGoogle } from '@/ui/icon';
import { ComponentDecorator } from '~/testing/decorators'; import { ComponentDecorator } from '~/testing/decorators/ComponentDecorator';
import { MainButton } from '../MainButton'; import { MainButton } from '../MainButton';

View File

@ -3,7 +3,7 @@ import type { Meta, StoryObj } from '@storybook/react';
import { userEvent, within } from '@storybook/testing-library'; import { userEvent, within } from '@storybook/testing-library';
import { IconArrowRight } from '@/ui/icon'; import { IconArrowRight } from '@/ui/icon';
import { ComponentDecorator } from '~/testing/decorators'; import { ComponentDecorator } from '~/testing/decorators/ComponentDecorator';
import { RoundedIconButton } from '../RoundedIconButton'; import { RoundedIconButton } from '../RoundedIconButton';

View 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>
);
}

View File

@ -1,122 +1,53 @@
import * as React from 'react'; import * as React from 'react';
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
import { Theme } from '@emotion/react';
import styled from '@emotion/styled';
import { Avatar, AvatarType } from '@/users/components/Avatar'; import { Avatar, AvatarType } from '@/users/components/Avatar';
import { isNonEmptyString } from '~/utils/isNonEmptyString'; import { isNonEmptyString } from '~/utils/isNonEmptyString';
import { OverflowingTextWithTooltip } from '../../tooltip/OverflowingTextWithTooltip'; import { Chip, ChipVariant } from './Chip';
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;
`;
type OwnProps = { type OwnProps = {
linkToEntity: string; linkToEntity?: string;
entityId: string; entityId: string;
name: string; name: string;
picture?: string; pictureUrl?: string;
clickable?: boolean;
avatarType?: AvatarType; avatarType?: AvatarType;
variant?: ChipVariant;
}; };
export function EntityChip({ export function EntityChip({
linkToEntity, linkToEntity,
entityId, entityId,
name, name,
picture, pictureUrl,
clickable,
avatarType = 'rounded', avatarType = 'rounded',
variant = ChipVariant.opaque,
}: OwnProps) { }: OwnProps) {
const navigate = useNavigate(); const navigate = useNavigate();
function handleLinkClick(event: React.MouseEvent<HTMLDivElement>) { function handleLinkClick(event: React.MouseEvent<HTMLDivElement>) {
event.preventDefault(); event.preventDefault();
event.stopPropagation(); event.stopPropagation();
if (linkToEntity) {
navigate(linkToEntity); navigate(linkToEntity);
}
} }
return clickable && linkToEntity ? ( return isNonEmptyString(name) ? (
<StyledContainerLink <div onClick={handleLinkClick}>
data-testid="entity-chip" <Chip
onClick={handleLinkClick} label={name}
variant={variant} variant={linkToEntity ? ChipVariant.Highlighted : ChipVariant.Regular}
> leftComponent={
{isNonEmptyString(name) && ( <Avatar
<Avatar avatarUrl={pictureUrl}
avatarUrl={picture} colorId={entityId}
colorId={entityId} placeholder={name}
placeholder={name} size={14}
size={14} type={avatarType}
type={avatarType} />
/> }
)} />
<StyledName> </div>
<OverflowingTextWithTooltip text={name} />
</StyledName>
</StyledContainerLink>
) : ( ) : (
<StyledContainerReadOnly data-testid="entity-chip"> <></>
{isNonEmptyString(name) && (
<Avatar
avatarUrl={picture}
colorId={entityId}
placeholder={name}
size={14}
type={avatarType}
/>
)}
<StyledName>
<OverflowingTextWithTooltip text={name} />
</StyledName>
</StyledContainerReadOnly>
); );
} }

View File

@ -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],
};

View File

@ -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 = {};

View File

@ -1,7 +1,7 @@
import styled from '@emotion/styled'; import styled from '@emotion/styled';
import type { Meta, StoryObj } from '@storybook/react'; import type { Meta, StoryObj } from '@storybook/react';
import { ComponentDecorator } from '~/testing/decorators'; import { ComponentDecorator } from '~/testing/decorators/ComponentDecorator';
import { ColorSchemeCard } from '../ColorSchemeCard'; import { ColorSchemeCard } from '../ColorSchemeCard';

View File

@ -4,7 +4,7 @@ import type { Meta, StoryObj } from '@storybook/react';
import { IconPlus } from '@/ui/icon/index'; import { IconPlus } from '@/ui/icon/index';
import { Avatar } from '@/users/components/Avatar'; 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 { DropdownMenuSkeletonItem } from '../../../relation-picker/components/skeletons/DropdownMenuSkeletonItem';
import { DropdownMenu } from '../DropdownMenu'; import { DropdownMenu } from '../DropdownMenu';

View File

@ -1,7 +1,7 @@
import type { Meta, StoryObj } from '@storybook/react'; import type { Meta, StoryObj } from '@storybook/react';
import { IconCalendar } from '@tabler/icons-react'; import { IconCalendar } from '@tabler/icons-react';
import { ComponentDecorator } from '~/testing/decorators'; import { ComponentDecorator } from '~/testing/decorators/ComponentDecorator';
import { DateEditableField } from '../DateEditableField'; import { DateEditableField } from '../DateEditableField';

View File

@ -1,7 +1,7 @@
import type { Meta, StoryObj } from '@storybook/react'; import type { Meta, StoryObj } from '@storybook/react';
import { IconCurrencyDollar } from '@tabler/icons-react'; import { IconCurrencyDollar } from '@tabler/icons-react';
import { ComponentDecorator } from '~/testing/decorators'; import { ComponentDecorator } from '~/testing/decorators/ComponentDecorator';
import { NumberEditableField } from '../NumberEditableField'; import { NumberEditableField } from '../NumberEditableField';

View File

@ -2,7 +2,7 @@ import { BrowserRouter } from 'react-router-dom';
import type { Meta, StoryObj } from '@storybook/react'; import type { Meta, StoryObj } from '@storybook/react';
import { IconPhone } from '@tabler/icons-react'; import { IconPhone } from '@tabler/icons-react';
import { ComponentDecorator } from '~/testing/decorators'; import { ComponentDecorator } from '~/testing/decorators/ComponentDecorator';
import { PhoneEditableField } from '../PhoneEditableField'; import { PhoneEditableField } from '../PhoneEditableField';

View File

@ -1,7 +1,7 @@
import type { Meta, StoryObj } from '@storybook/react'; import type { Meta, StoryObj } from '@storybook/react';
import { IconUser } from '@tabler/icons-react'; import { IconUser } from '@tabler/icons-react';
import { ComponentDecorator } from '~/testing/decorators'; import { ComponentDecorator } from '~/testing/decorators/ComponentDecorator';
import { TextEditableField } from '../TextEditableField'; import { TextEditableField } from '../TextEditableField';

View File

@ -1,6 +1,6 @@
import type { Meta, StoryObj } from '@storybook/react'; import type { Meta, StoryObj } from '@storybook/react';
import { ComponentDecorator } from '~/testing/decorators'; import { ComponentDecorator } from '~/testing/decorators/ComponentDecorator';
import { AutosizeTextInput } from '../AutosizeTextInput'; import { AutosizeTextInput } from '../AutosizeTextInput';

View File

@ -4,7 +4,7 @@ import { jest } from '@storybook/jest';
import type { Meta, StoryObj } from '@storybook/react'; import type { Meta, StoryObj } from '@storybook/react';
import { userEvent, within } from '@storybook/testing-library'; import { userEvent, within } from '@storybook/testing-library';
import { ComponentDecorator } from '~/testing/decorators'; import { ComponentDecorator } from '~/testing/decorators/ComponentDecorator';
import { TextInput } from '../TextInput'; import { TextInput } from '../TextInput';

View File

@ -3,7 +3,7 @@ import { expect, jest } from '@storybook/jest';
import type { Meta, StoryObj } from '@storybook/react'; import type { Meta, StoryObj } from '@storybook/react';
import { userEvent, within } from '@storybook/testing-library'; import { userEvent, within } from '@storybook/testing-library';
import { ComponentDecorator } from '~/testing/decorators'; import { ComponentDecorator } from '~/testing/decorators/ComponentDecorator';
import { PrimaryLink } from '../PrimaryLink'; import { PrimaryLink } from '../PrimaryLink';

View File

@ -1,6 +1,6 @@
import type { Meta, StoryObj } from '@storybook/react'; import type { Meta, StoryObj } from '@storybook/react';
import { ComponentDecorator } from '~/testing/decorators'; import { ComponentDecorator } from '~/testing/decorators/ComponentDecorator';
import { SoonPill } from '../SoonPill'; import { SoonPill } from '../SoonPill';

View File

@ -1,6 +1,6 @@
import type { Meta, StoryObj } from '@storybook/react'; import type { Meta, StoryObj } from '@storybook/react';
import { ComponentDecorator } from '~/testing/decorators'; import { ComponentDecorator } from '~/testing/decorators/ComponentDecorator';
import { graphqlMocks } from '~/testing/graphqlMocks'; import { graphqlMocks } from '~/testing/graphqlMocks';
import { RightDrawerTopBar } from '../RightDrawerTopBar'; import { RightDrawerTopBar } from '../RightDrawerTopBar';

View File

@ -2,10 +2,8 @@ import { expect } from '@storybook/jest';
import type { Meta, StoryObj } from '@storybook/react'; import type { Meta, StoryObj } from '@storybook/react';
import { userEvent, within } from '@storybook/testing-library'; import { userEvent, within } from '@storybook/testing-library';
import { import { CellPositionDecorator } from '~/testing/decorators/CellPositionDecorator';
CellPositionDecorator, import { ComponentDecorator } from '~/testing/decorators/ComponentDecorator';
ComponentDecorator,
} from '~/testing/decorators';
import { EditableCellText } from '../../types/EditableCellText'; import { EditableCellText } from '../../types/EditableCellText';

View File

@ -1,6 +1,6 @@
import type { Meta, StoryObj } from '@storybook/react'; import type { Meta, StoryObj } from '@storybook/react';
import { ComponentDecorator } from '~/testing/decorators'; import { ComponentDecorator } from '~/testing/decorators/ComponentDecorator';
import { Tag } from '../Tag'; import { Tag } from '../Tag';

View File

@ -1,6 +1,6 @@
import type { Meta, StoryObj } from '@storybook/react'; 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 { avatarUrl } from '~/testing/mock-data/users';
import { Avatar } from '../Avatar'; import { Avatar } from '../Avatar';

View File

@ -194,7 +194,10 @@ export const EditRelation: Story = {
mockedPeopleData[1].company.name, 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 () => { await step('Type "Air" in relation picker', async () => {
@ -244,7 +247,10 @@ export const SelectRelationWithKeys: Story = {
); );
await sleep(25); await sleep(25);
await userEvent.click(firstRowCompanyCell); await userEvent.click(
firstRowCompanyCell.parentNode?.parentNode?.parentNode
?.parentElement as HTMLElement,
);
firstRowCompanyCell = await canvas.findByText( firstRowCompanyCell = await canvas.findByText(
mockedPeopleData[0].company.name, mockedPeopleData[0].company.name,
); );

View File

@ -9,6 +9,7 @@ const StyledLayout = styled.div`
flex-direction: row; flex-direction: row;
height: fit-content; height: fit-content;
max-width: calc(100% - 40px);
min-width: 300px; min-width: 300px;
padding: 20px; padding: 20px;
width: fit-content; width: fit-content;

View File

@ -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>
);

View 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>
);

View File

@ -0,0 +1,9 @@
import { Decorator } from '@storybook/react';
import { ComponentStorybookLayout } from '../ComponentStorybookLayout';
export const ComponentDecorator: Decorator = (Story) => (
<ComponentStorybookLayout>
<Story />
</ComponentStorybookLayout>
);

View File

@ -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>
);

View 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>
);
};

View 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>
);