Write Storybook tests for front/src/modules/ui/field/meta-types/display components (#1932)

* Write Storybook tests for front/src/modules/ui/field/meta-types/display components

Co-authored-by: v1b3m <vibenjamin6@gmail.com>
Co-authored-by: FellipeMTX <fellipefacdir@gmail.com>

* Write Storybook tests for front/src/modules/ui/field/meta-types/display components

Co-authored-by: FellipeMTX <fellipefacdir@gmail.com>
Co-authored-by: v1b3m <vibenjamin6@gmail.com>

* Write Storybook tests for front/src/modules/ui/field/meta-types/display components

Co-authored-by: v1b3m <vibenjamin6@gmail.com>
Co-authored-by: FellipeMTX <fellipefacdir@gmail.com>

* add EllipsisDisplay component

* add EllipsisDisplay component

* modified ComponentDecorator to pass a minWidth parameter to test ellipsis

* add ellipsis test to all components

* add ellipsis to links

* removed minWidth and set it to 'unset' if the width is not defined

---------

Co-authored-by: v1b3m <vibenjamin6@gmail.com>
Co-authored-by: FellipeMTX <fellipefacdir@gmail.com>
Co-authored-by: bosiraphael <raphael.bosi@gmail.com>
This commit is contained in:
gitstart-twenty
2023-10-10 13:39:19 +03:00
committed by GitHub
parent ae32a2da3b
commit 612bd57d5b
20 changed files with 609 additions and 65 deletions

View File

@ -0,0 +1,78 @@
import { useEffect } from 'react';
import { Meta, StoryObj } from '@storybook/react';
import { ComponentDecorator } from '~/testing/decorators/ComponentDecorator';
import { useDateField } from '../../../hooks/useDateField';
import { DateFieldDisplay } from '../DateFieldDisplay';
import { FieldDisplayContextProvider } from './FieldDisplayContextProvider';
const formattedDate = new Date();
const DateFieldValueSetterEffect = ({ value }: { value: string }) => {
const { setFieldValue } = useDateField();
useEffect(() => {
setFieldValue(value);
}, [setFieldValue, value]);
return <></>;
};
type DateFieldDisplayWithContextProps = {
value: string;
entityId?: string;
};
const DateFieldDisplayWithContext = ({
value,
entityId,
}: DateFieldDisplayWithContextProps) => {
return (
<FieldDisplayContextProvider
fieldDefinition={{
key: 'date',
name: 'Date',
type: 'date',
metadata: {
fieldName: 'Date',
},
}}
entityId={entityId}
>
<DateFieldValueSetterEffect value={value} />
<DateFieldDisplay />
</FieldDisplayContextProvider>
);
};
const meta: Meta = {
title: 'UI/Field/DateFieldDisplay',
component: DateFieldDisplayWithContext,
};
export default meta;
type Story = StoryObj<typeof DateFieldDisplayWithContext>;
export const Default: Story = {
args: {
value: formattedDate.toISOString(),
},
};
export const Elipsis: Story = {
args: {
value: formattedDate.toISOString(),
},
argTypes: {
value: { control: false },
},
parameters: {
container: {
width: 50,
},
},
decorators: [ComponentDecorator],
};

View File

@ -0,0 +1,102 @@
import React, { useEffect } from 'react';
import { Meta, StoryObj } from '@storybook/react';
import { ComponentDecorator } from '~/testing/decorators/ComponentDecorator';
import { useDoubleTextField } from '../../../hooks/useDoubleTextField';
import { DoubleTextFieldDisplay } from '../DoubleTextFieldDisplay'; // Import your component
import { FieldDisplayContextProvider } from './FieldDisplayContextProvider';
const DoubleTextFieldDisplayValueSetterEffect = ({
firstValue,
secondValue,
}: {
firstValue: string;
secondValue: string;
}) => {
const { setFirstValue, setSecondValue } = useDoubleTextField();
useEffect(() => {
setFirstValue(firstValue);
setSecondValue(secondValue);
}, [setFirstValue, setSecondValue, firstValue, secondValue]);
return <></>;
};
type DoubleTextFieldDisplayWithContextProps = {
firstValue: string;
secondValue: string;
entityId?: string;
};
const DoubleTextFieldDisplayWithContext = ({
firstValue,
secondValue,
entityId,
}: DoubleTextFieldDisplayWithContextProps) => {
return (
<FieldDisplayContextProvider
fieldDefinition={{
key: 'double-text',
name: 'Double-Text',
type: 'double-text',
metadata: {
firstValueFieldName: 'First-text',
firstValuePlaceholder: 'First-text',
secondValueFieldName: 'Second-text',
secondValuePlaceholder: 'Second-text',
},
}}
entityId={entityId}
>
<DoubleTextFieldDisplayValueSetterEffect
firstValue={firstValue}
secondValue={secondValue}
/>
<DoubleTextFieldDisplay />
</FieldDisplayContextProvider>
);
};
const meta: Meta = {
title: 'UI/Field/DoubleTextFieldDisplay',
component: DoubleTextFieldDisplayWithContext,
};
export default meta;
type Story = StoryObj<typeof DoubleTextFieldDisplayWithContext>;
export const Default: Story = {
args: {
firstValue: 'Lorem',
secondValue: 'ipsum',
},
};
export const CustomValues: Story = {
args: {
firstValue: 'Lorem',
secondValue: 'ipsum',
},
};
export const Elipsis: Story = {
args: {
firstValue:
'Lorem Ipsum is simply dummy text of the printing and typesetting industry.',
secondValue: 'ipsum dolor sit amet, consectetur adipiscing elit.',
},
argTypes: {
firstValue: { control: true },
secondValue: { control: true },
},
parameters: {
container: {
width: 100,
},
},
decorators: [ComponentDecorator],
};

View File

@ -0,0 +1,80 @@
import { useEffect } from 'react';
import { MemoryRouter } from 'react-router-dom';
import { Meta, StoryObj } from '@storybook/react';
import { ComponentDecorator } from '~/testing/decorators/ComponentDecorator';
import { useEmailField } from '../../../hooks/useEmailField';
import { EmailFieldDisplay } from '../EmailFieldDisplay';
import { FieldDisplayContextProvider } from './FieldDisplayContextProvider';
const EmailFieldValueSetterEffect = ({ value }: { value: string }) => {
const { setFieldValue } = useEmailField();
useEffect(() => {
setFieldValue(value);
}, [setFieldValue, value]);
return <></>;
};
type EmailFieldDisplayWithContextProps = {
value: string;
entityId?: string;
};
const EmailFieldDisplayWithContext = ({
value,
entityId,
}: EmailFieldDisplayWithContextProps) => {
return (
<FieldDisplayContextProvider
fieldDefinition={{
key: 'email',
name: 'Email',
type: 'email',
metadata: {
fieldName: 'Email',
placeHolder: 'Email',
},
}}
entityId={entityId}
>
<MemoryRouter>
<EmailFieldValueSetterEffect value={value} />
<EmailFieldDisplay />
</MemoryRouter>
</FieldDisplayContextProvider>
);
};
const meta: Meta = {
title: 'UI/Field/EmailFieldDisplay',
component: EmailFieldDisplayWithContext,
};
export default meta;
type Story = StoryObj<typeof EmailFieldDisplayWithContext>;
export const Default: Story = {
args: {
value: 'Test@Test.test',
},
};
export const Elipsis: Story = {
args: {
value: 'Test@Test.test',
},
argTypes: {
value: { control: false },
},
parameters: {
container: {
width: 50,
},
},
decorators: [ComponentDecorator],
};

View File

@ -0,0 +1,109 @@
import { useEffect } from 'react';
import { Meta, StoryObj } from '@storybook/react';
import { v4 } from 'uuid';
import { CatalogDecorator } from '~/testing/decorators/CatalogDecorator';
import { ComponentDecorator } from '~/testing/decorators/ComponentDecorator';
import { CatalogStory } from '~/testing/types';
import { useNumberField } from '../../../hooks/useNumberField';
import { NumberFieldDisplay } from '../NumberFieldDisplay';
import { FieldDisplayContextProvider } from './FieldDisplayContextProvider';
const NumberFieldValueSetterEffect = ({ value }: { value: number }) => {
const { setFieldValue } = useNumberField();
useEffect(() => {
setFieldValue(value);
}, [setFieldValue, value]);
return <></>;
};
type NumberFieldDisplayWithContextProps = {
value: number;
entityId?: string;
};
const NumberFieldDisplayWithContext = ({
value,
entityId,
}: NumberFieldDisplayWithContextProps) => {
return (
<FieldDisplayContextProvider
fieldDefinition={{
key: 'number',
name: 'Number',
type: 'number',
metadata: {
fieldName: 'Number',
placeHolder: 'Number',
isPositive: true,
},
}}
entityId={entityId}
>
<NumberFieldValueSetterEffect value={value} />
<NumberFieldDisplay />
</FieldDisplayContextProvider>
);
};
const meta: Meta = {
title: 'UI/Field/NumberFieldDisplay',
component: NumberFieldDisplayWithContext,
};
export default meta;
type Story = StoryObj<typeof NumberFieldDisplayWithContext>;
export const Default: Story = {
args: {
value: 100,
},
};
export const Elipsis: Story = {
args: {
value: 1e100,
},
argTypes: {
value: { control: false },
},
parameters: {
container: {
width: 100,
},
},
decorators: [ComponentDecorator],
};
export const Catalog: CatalogStory<
Story,
typeof NumberFieldDisplayWithContext
> = {
argTypes: {
value: { control: false },
},
parameters: {
catalog: {
dimensions: [
{
name: 'value',
values: [
100, 1000, -1000, 1e10, 1.357802, -1.283, 0,
] satisfies number[],
props: (value: number) => ({ value, entityId: v4() }),
},
],
options: {
elementContainer: {
width: 100,
},
},
},
},
decorators: [CatalogDecorator],
};

View File

@ -0,0 +1,80 @@
import { useEffect } from 'react';
import { MemoryRouter } from 'react-router-dom';
import { Meta, StoryObj } from '@storybook/react';
import { ComponentDecorator } from '~/testing/decorators/ComponentDecorator';
import { usePhoneField } from '../../../hooks/usePhoneField';
import { PhoneFieldDisplay } from '../PhoneFieldDisplay';
import { FieldDisplayContextProvider } from './FieldDisplayContextProvider';
const PhoneFieldValueSetterEffect = ({ value }: { value: string }) => {
const { setFieldValue } = usePhoneField();
useEffect(() => {
setFieldValue(value);
}, [setFieldValue, value]);
return <></>;
};
type PhoneFieldDisplayWithContextProps = {
value: string;
entityId?: string;
};
const PhoneFieldDisplayWithContext = ({
value,
entityId,
}: PhoneFieldDisplayWithContextProps) => {
return (
<FieldDisplayContextProvider
fieldDefinition={{
key: 'phone',
name: 'Phone',
type: 'phone',
metadata: {
fieldName: 'Phone',
placeHolder: 'Phone',
},
}}
entityId={entityId}
>
<MemoryRouter>
<PhoneFieldValueSetterEffect value={value} />
<PhoneFieldDisplay />
</MemoryRouter>
</FieldDisplayContextProvider>
);
};
const meta: Meta = {
title: 'UI/Field/PhoneFieldDisplay',
component: PhoneFieldDisplayWithContext,
};
export default meta;
type Story = StoryObj<typeof PhoneFieldDisplayWithContext>;
export const Default: Story = {
args: {
value: '362763872687362',
},
};
export const Elipsis: Story = {
args: {
value: '362763872687362',
},
argTypes: {
value: { control: false },
},
parameters: {
container: {
width: 50,
},
},
decorators: [ComponentDecorator],
};

View File

@ -0,0 +1,80 @@
import { useEffect } from 'react';
import { MemoryRouter } from 'react-router';
import { Meta, StoryObj } from '@storybook/react';
import { ComponentDecorator } from '~/testing/decorators/ComponentDecorator';
import { useURLField } from '../../../hooks/useURLField';
import { URLFieldDisplay } from '../URLFieldDisplay';
import { FieldDisplayContextProvider } from './FieldDisplayContextProvider';
const URLFieldValueSetterEffect = ({ value }: { value: string }) => {
const { setFieldValue } = useURLField();
useEffect(() => {
setFieldValue(value);
}, [setFieldValue, value]);
return <></>;
};
type URLFieldDisplayWithContextProps = {
value: string;
entityId?: string;
};
const URLFieldDisplayWithContext = ({
value,
entityId,
}: URLFieldDisplayWithContextProps) => {
return (
<FieldDisplayContextProvider
fieldDefinition={{
key: 'URL',
name: 'URL',
type: 'url',
metadata: {
fieldName: 'URL',
placeHolder: 'URL',
},
}}
entityId={entityId}
>
<MemoryRouter>
<URLFieldValueSetterEffect value={value} />
<URLFieldDisplay />
</MemoryRouter>
</FieldDisplayContextProvider>
);
};
const meta: Meta = {
title: 'UI/Field/URLFieldDisplay',
component: URLFieldDisplayWithContext,
};
export default meta;
type Story = StoryObj<typeof URLFieldDisplayWithContext>;
export const Default: Story = {
args: {
value: 'https://github.com/GitStartHQ',
},
};
export const Elipsis: Story = {
args: {
value: 'https://www.instagram.com/gitstart/',
},
argTypes: {
value: { control: true },
},
parameters: {
container: {
width: 200,
},
},
decorators: [ComponentDecorator],
};

View File

@ -1,9 +1,11 @@
import { formatToHumanReadableDate } from '~/utils';
import { EllipsisDisplay } from './EllipsisDisplay';
type DateDisplayProps = {
value: Date | string | null | undefined;
};
export const DateDisplay = ({ value }: DateDisplayProps) => (
<div>{value && formatToHumanReadableDate(value)}</div>
<EllipsisDisplay>{value && formatToHumanReadableDate(value)}</EllipsisDisplay>
);

View File

@ -0,0 +1,10 @@
import styled from '@emotion/styled';
const StyledEllipsisDisplay = styled.div`
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
width: 100%;
`;
export { StyledEllipsisDisplay as EllipsisDisplay };

View File

@ -2,6 +2,8 @@ import { MouseEvent } from 'react';
import { ContactLink } from '@/ui/link/components/ContactLink';
import { EllipsisDisplay } from './EllipsisDisplay';
const validateEmail = (email: string) => {
const emailPattern = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return emailPattern.test(email.trim());
@ -11,16 +13,19 @@ type EmailDisplayProps = {
value: string | null;
};
export const EmailDisplay = ({ value }: EmailDisplayProps) =>
value && validateEmail(value) ? (
<ContactLink
href={`mailto:${value}`}
onClick={(event: MouseEvent<HTMLElement>) => {
event.stopPropagation();
}}
>
{value}
</ContactLink>
) : (
<ContactLink href="#">{value}</ContactLink>
);
export const EmailDisplay = ({ value }: EmailDisplayProps) => (
<EllipsisDisplay>
{value && validateEmail(value) ? (
<ContactLink
href={`mailto:${value}`}
onClick={(event: MouseEvent<HTMLElement>) => {
event.stopPropagation();
}}
>
{value}
</ContactLink>
) : (
<ContactLink href="#">{value}</ContactLink>
)}
</EllipsisDisplay>
);

View File

@ -1,20 +1,11 @@
import styled from '@emotion/styled';
import { formatNumber } from '~/utils/format/number';
const StyledTextInputDisplay = styled.div`
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
width: 100%;
`;
import { EllipsisDisplay } from './EllipsisDisplay';
type MoneyDisplayProps = {
value: number | null;
};
export const MoneyDisplay = ({ value }: MoneyDisplayProps) => (
<StyledTextInputDisplay>
{value ? `$${formatNumber(value)}` : ''}
</StyledTextInputDisplay>
<EllipsisDisplay>{value ? `$${formatNumber(value)}` : ''}</EllipsisDisplay>
);

View File

@ -1,20 +1,11 @@
import styled from '@emotion/styled';
import { formatNumber } from '~/utils/format/number';
const StyledNumberDisplay = styled.div`
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
width: 100%;
`;
import { EllipsisDisplay } from './EllipsisDisplay';
type NumberDisplayProps = {
value: string | number | null;
};
export const NumberDisplay = ({ value }: NumberDisplayProps) => (
<StyledNumberDisplay>
{value && formatNumber(Number(value))}
</StyledNumberDisplay>
<EllipsisDisplay>{value && formatNumber(Number(value))}</EllipsisDisplay>
);

View File

@ -3,20 +3,25 @@ import { isValidPhoneNumber, parsePhoneNumber } from 'libphonenumber-js';
import { ContactLink } from '@/ui/link/components/ContactLink';
import { EllipsisDisplay } from './EllipsisDisplay';
type PhoneDisplayProps = {
value: string | null;
};
export const PhoneDisplay = ({ value }: PhoneDisplayProps) =>
value && isValidPhoneNumber(value) ? (
<ContactLink
href={parsePhoneNumber(value, 'FR')?.getURI()}
onClick={(event: MouseEvent<HTMLElement>) => {
event.stopPropagation();
}}
>
{parsePhoneNumber(value, 'FR')?.formatInternational() || value}
</ContactLink>
) : (
<ContactLink href="#">{value}</ContactLink>
);
export const PhoneDisplay = ({ value }: PhoneDisplayProps) => (
<EllipsisDisplay>
{value && isValidPhoneNumber(value) ? (
<ContactLink
href={parsePhoneNumber(value, 'FR')?.getURI()}
onClick={(event: MouseEvent<HTMLElement>) => {
event.stopPropagation();
}}
>
{parsePhoneNumber(value, 'FR')?.formatInternational() || value}
</ContactLink>
) : (
<ContactLink href="#">{value}</ContactLink>
)}
</EllipsisDisplay>
);

View File

@ -1,16 +1,9 @@
import styled from '@emotion/styled';
const StyledTextInputDisplay = styled.div`
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
width: 100%;
`;
import { EllipsisDisplay } from './EllipsisDisplay';
type TextDisplayProps = {
text: string;
};
export const TextDisplay = ({ text }: TextDisplayProps) => (
<StyledTextInputDisplay>{text}</StyledTextInputDisplay>
<EllipsisDisplay>{text}</EllipsisDisplay>
);

View File

@ -4,6 +4,8 @@ import styled from '@emotion/styled';
import { RoundedLink } from '@/ui/link/components/RoundedLink';
import { LinkType, SocialLink } from '@/ui/link/components/SocialLink';
import { EllipsisDisplay } from './EllipsisDisplay';
const StyledRawLink = styled(RoundedLink)`
overflow: hidden;
@ -50,14 +52,18 @@ export const URLDisplay = ({ value }: URLDisplayProps) => {
if (type === LinkType.LinkedIn || type === LinkType.Twitter) {
return (
<SocialLink href={absoluteUrl} onClick={handleClick} type={type}>
{displayedValue}
</SocialLink>
<EllipsisDisplay>
<SocialLink href={absoluteUrl} onClick={handleClick} type={type}>
{displayedValue}
</SocialLink>
</EllipsisDisplay>
);
}
return (
<StyledRawLink href={absoluteUrl} onClick={handleClick}>
{displayedValue}
</StyledRawLink>
<EllipsisDisplay>
<StyledRawLink href={absoluteUrl} onClick={handleClick}>
{displayedValue}
</StyledRawLink>
</EllipsisDisplay>
);
};

View File

@ -16,8 +16,10 @@ const StyledClickable = styled.div`
a {
color: inherit;
overflow: hidden;
text-decoration: underline;
text-decoration-color: ${({ theme }) => theme.border.color.strong};
text-overflow: ellipsis;
&:hover {
text-decoration-color: ${({ theme }) => theme.font.color.primary};

View File

@ -14,7 +14,9 @@ const StyledClickable = styled.div`
a {
color: ${({ theme }) => theme.font.color.tertiary};
font-size: ${({ theme }) => theme.font.size.sm};
overflow: hidden;
text-decoration: underline;
text-overflow: ellipsis;
}
`;

View File

@ -16,6 +16,8 @@ const StyledClickable = styled.div`
a {
color: inherit;
overflow: hidden;
text-overflow: ellipsis;
}
`;

View File

@ -17,7 +17,9 @@ const StyledClickable = styled.div`
a {
color: inherit;
overflow: hidden;
text-decoration: none;
text-overflow: ellipsis;
}
`;

View File

@ -10,7 +10,7 @@ const StyledLayout = styled.div<{ width?: number }>`
height: fit-content;
max-width: calc(100% - 40px);
min-width: 300px;
min-width: ${({ width }) => (width ? 'unset' : '300px')};
padding: 20px;
width: ${({ width }) => (width ? width + 'px' : 'fit-content')};
`;

4
yarn.lock Normal file
View File

@ -0,0 +1,4 @@
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1