1867 timebox add storybook tests on meta typesinputcomponents (#1972)

* working on DateFieldInput story

* wip

* wip

* wip

* Fix story

* Fix other story

* finish stories for BooleanFieldInput and DateFieldInput

* reorganize stories in UI/Field in Input and Display folders

* unite FieldDisplayContextProvider and FieldInputContextProvider in one file FieldContextProvider

---------

Co-authored-by: Charles Bochet <charles@twenty.com>
This commit is contained in:
bosiraphael
2023-10-11 16:55:55 +02:00
committed by GitHub
parent c52b297612
commit b2352212fc
14 changed files with 290 additions and 49 deletions

View File

@ -3,17 +3,17 @@ import {
GenericFieldContextType,
} from '@/ui/field/contexts/FieldContext';
type FieldDisplayContextProviderProps = {
type FieldContextProviderProps = {
children: React.ReactNode;
fieldDefinition: GenericFieldContextType['fieldDefinition'];
entityId?: string;
};
export const FieldDisplayContextProvider = ({
export const FieldContextProvider = ({
children,
fieldDefinition,
entityId,
}: FieldDisplayContextProviderProps) => {
}: FieldContextProviderProps) => {
return (
<FieldContext.Provider
value={{

View File

@ -3,11 +3,10 @@ import { Meta, StoryObj } from '@storybook/react';
import { ComponentDecorator } from '~/testing/decorators/ComponentDecorator';
import { FieldContextProvider } from '../../../__stories__/FieldContextProvider';
import { useDateField } from '../../../hooks/useDateField';
import { DateFieldDisplay } from '../DateFieldDisplay';
import { FieldDisplayContextProvider } from './FieldDisplayContextProvider';
const formattedDate = new Date();
const DateFieldValueSetterEffect = ({ value }: { value: string }) => {
@ -30,7 +29,7 @@ const DateFieldDisplayWithContext = ({
entityId,
}: DateFieldDisplayWithContextProps) => {
return (
<FieldDisplayContextProvider
<FieldContextProvider
fieldDefinition={{
key: 'date',
name: 'Date',
@ -43,12 +42,12 @@ const DateFieldDisplayWithContext = ({
>
<DateFieldValueSetterEffect value={value} />
<DateFieldDisplay />
</FieldDisplayContextProvider>
</FieldContextProvider>
);
};
const meta: Meta = {
title: 'UI/Field/DateFieldDisplay',
title: 'UI/Field/Display/DateFieldDisplay',
component: DateFieldDisplayWithContext,
};

View File

@ -3,11 +3,10 @@ import { Meta, StoryObj } from '@storybook/react';
import { ComponentDecorator } from '~/testing/decorators/ComponentDecorator';
import { FieldContextProvider } from '../../../__stories__/FieldContextProvider';
import { useDoubleTextField } from '../../../hooks/useDoubleTextField';
import { DoubleTextFieldDisplay } from '../DoubleTextFieldDisplay'; // Import your component
import { FieldDisplayContextProvider } from './FieldDisplayContextProvider';
const DoubleTextFieldDisplayValueSetterEffect = ({
firstValue,
secondValue,
@ -37,7 +36,7 @@ const DoubleTextFieldDisplayWithContext = ({
entityId,
}: DoubleTextFieldDisplayWithContextProps) => {
return (
<FieldDisplayContextProvider
<FieldContextProvider
fieldDefinition={{
key: 'double-text',
name: 'Double-Text',
@ -56,12 +55,12 @@ const DoubleTextFieldDisplayWithContext = ({
secondValue={secondValue}
/>
<DoubleTextFieldDisplay />
</FieldDisplayContextProvider>
</FieldContextProvider>
);
};
const meta: Meta = {
title: 'UI/Field/DoubleTextFieldDisplay',
title: 'UI/Field/Display/DoubleTextFieldDisplay',
component: DoubleTextFieldDisplayWithContext,
};

View File

@ -4,11 +4,10 @@ import { Meta, StoryObj } from '@storybook/react';
import { ComponentDecorator } from '~/testing/decorators/ComponentDecorator';
import { FieldContextProvider } from '../../../__stories__/FieldContextProvider';
import { useEmailField } from '../../../hooks/useEmailField';
import { EmailFieldDisplay } from '../EmailFieldDisplay';
import { FieldDisplayContextProvider } from './FieldDisplayContextProvider';
const EmailFieldValueSetterEffect = ({ value }: { value: string }) => {
const { setFieldValue } = useEmailField();
@ -29,7 +28,7 @@ const EmailFieldDisplayWithContext = ({
entityId,
}: EmailFieldDisplayWithContextProps) => {
return (
<FieldDisplayContextProvider
<FieldContextProvider
fieldDefinition={{
key: 'email',
name: 'Email',
@ -45,12 +44,12 @@ const EmailFieldDisplayWithContext = ({
<EmailFieldValueSetterEffect value={value} />
<EmailFieldDisplay />
</MemoryRouter>
</FieldDisplayContextProvider>
</FieldContextProvider>
);
};
const meta: Meta = {
title: 'UI/Field/EmailFieldDisplay',
title: 'UI/Field/Display/EmailFieldDisplay',
component: EmailFieldDisplayWithContext,
};

View File

@ -6,11 +6,10 @@ import { CatalogDecorator } from '~/testing/decorators/CatalogDecorator';
import { ComponentDecorator } from '~/testing/decorators/ComponentDecorator';
import { CatalogStory } from '~/testing/types';
import { FieldContextProvider } from '../../../__stories__/FieldContextProvider';
import { useMoneyField } from '../../../hooks/useMoneyField';
import { MoneyFieldDisplay } from '../MoneyFieldDisplay';
import { FieldDisplayContextProvider } from './FieldDisplayContextProvider';
const MoneyFieldValueSetterEffect = ({ value }: { value: number }) => {
const { setFieldValue } = useMoneyField();
@ -31,7 +30,7 @@ const MoneyFieldDisplayWithContext = ({
entityId,
}: MoneyFieldDisplayWithContextProps) => {
return (
<FieldDisplayContextProvider
<FieldContextProvider
fieldDefinition={{
key: 'money',
name: 'Money',
@ -46,12 +45,12 @@ const MoneyFieldDisplayWithContext = ({
>
<MoneyFieldValueSetterEffect value={value} />
<MoneyFieldDisplay />
</FieldDisplayContextProvider>
</FieldContextProvider>
);
};
const meta: Meta = {
title: 'UI/Field/MoneyFieldDisplay',
title: 'UI/Field/Display/MoneyFieldDisplay',
component: MoneyFieldDisplayWithContext,
};

View File

@ -6,11 +6,10 @@ import { CatalogDecorator } from '~/testing/decorators/CatalogDecorator';
import { ComponentDecorator } from '~/testing/decorators/ComponentDecorator';
import { CatalogStory } from '~/testing/types';
import { FieldContextProvider } from '../../../__stories__/FieldContextProvider';
import { useNumberField } from '../../../hooks/useNumberField';
import { NumberFieldDisplay } from '../NumberFieldDisplay';
import { FieldDisplayContextProvider } from './FieldDisplayContextProvider';
const NumberFieldValueSetterEffect = ({ value }: { value: number }) => {
const { setFieldValue } = useNumberField();
@ -31,7 +30,7 @@ const NumberFieldDisplayWithContext = ({
entityId,
}: NumberFieldDisplayWithContextProps) => {
return (
<FieldDisplayContextProvider
<FieldContextProvider
fieldDefinition={{
key: 'number',
name: 'Number',
@ -46,12 +45,12 @@ const NumberFieldDisplayWithContext = ({
>
<NumberFieldValueSetterEffect value={value} />
<NumberFieldDisplay />
</FieldDisplayContextProvider>
</FieldContextProvider>
);
};
const meta: Meta = {
title: 'UI/Field/NumberFieldDisplay',
title: 'UI/Field/Display/NumberFieldDisplay',
component: NumberFieldDisplayWithContext,
};

View File

@ -4,11 +4,10 @@ import { Meta, StoryObj } from '@storybook/react';
import { ComponentDecorator } from '~/testing/decorators/ComponentDecorator';
import { FieldContextProvider } from '../../../__stories__/FieldContextProvider';
import { usePhoneField } from '../../../hooks/usePhoneField';
import { PhoneFieldDisplay } from '../PhoneFieldDisplay';
import { FieldDisplayContextProvider } from './FieldDisplayContextProvider';
const PhoneFieldValueSetterEffect = ({ value }: { value: string }) => {
const { setFieldValue } = usePhoneField();
@ -29,7 +28,7 @@ const PhoneFieldDisplayWithContext = ({
entityId,
}: PhoneFieldDisplayWithContextProps) => {
return (
<FieldDisplayContextProvider
<FieldContextProvider
fieldDefinition={{
key: 'phone',
name: 'Phone',
@ -45,12 +44,12 @@ const PhoneFieldDisplayWithContext = ({
<PhoneFieldValueSetterEffect value={value} />
<PhoneFieldDisplay />
</MemoryRouter>
</FieldDisplayContextProvider>
</FieldContextProvider>
);
};
const meta: Meta = {
title: 'UI/Field/PhoneFieldDisplay',
title: 'UI/Field/Display/PhoneFieldDisplay',
component: PhoneFieldDisplayWithContext,
};

View File

@ -6,11 +6,10 @@ import { CatalogDecorator } from '~/testing/decorators/CatalogDecorator';
import { ComponentDecorator } from '~/testing/decorators/ComponentDecorator';
import { CatalogStory } from '~/testing/types';
import { FieldContextProvider } from '../../../__stories__/FieldContextProvider';
import { useTextField } from '../../../hooks/useTextField';
import { TextFieldDisplay } from '../TextFieldDisplay';
import { FieldDisplayContextProvider } from './FieldDisplayContextProvider';
const TextFieldValueSetterEffect = ({ value }: { value: string }) => {
const { setFieldValue } = useTextField();
@ -31,7 +30,7 @@ const TextFieldDisplayWithContext = ({
entityId,
}: TextFieldDisplayWithContextProps) => {
return (
<FieldDisplayContextProvider
<FieldContextProvider
fieldDefinition={{
key: 'text',
name: 'Text',
@ -45,12 +44,12 @@ const TextFieldDisplayWithContext = ({
>
<TextFieldValueSetterEffect value={value} />
<TextFieldDisplay />
</FieldDisplayContextProvider>
</FieldContextProvider>
);
};
const meta: Meta = {
title: 'UI/Field/TextFieldDisplay',
title: 'UI/Field/Display/TextFieldDisplay',
component: TextFieldDisplayWithContext,
};

View File

@ -4,11 +4,10 @@ import { Meta, StoryObj } from '@storybook/react';
import { ComponentDecorator } from '~/testing/decorators/ComponentDecorator';
import { FieldContextProvider } from '../../../__stories__/FieldContextProvider';
import { useURLField } from '../../../hooks/useURLField';
import { URLFieldDisplay } from '../URLFieldDisplay';
import { FieldDisplayContextProvider } from './FieldDisplayContextProvider';
const URLFieldValueSetterEffect = ({ value }: { value: string }) => {
const { setFieldValue } = useURLField();
@ -29,7 +28,7 @@ const URLFieldDisplayWithContext = ({
entityId,
}: URLFieldDisplayWithContextProps) => {
return (
<FieldDisplayContextProvider
<FieldContextProvider
fieldDefinition={{
key: 'URL',
name: 'URL',
@ -45,12 +44,12 @@ const URLFieldDisplayWithContext = ({
<URLFieldValueSetterEffect value={value} />
<URLFieldDisplay />
</MemoryRouter>
</FieldDisplayContextProvider>
</FieldContextProvider>
);
};
const meta: Meta = {
title: 'UI/Field/URLFieldDisplay',
title: 'UI/Field/Display/URLFieldDisplay',
component: URLFieldDisplayWithContext,
};

View File

@ -5,11 +5,15 @@ import { useBooleanField } from '../../hooks/useBooleanField';
import { FieldInputEvent } from './DateFieldInput';
type BooleanFieldInputProps = {
export type BooleanFieldInputProps = {
onSubmit?: FieldInputEvent;
testId?: string;
};
export const BooleanFieldInput = ({ onSubmit }: BooleanFieldInputProps) => {
export const BooleanFieldInput = ({
onSubmit,
testId,
}: BooleanFieldInputProps) => {
const { fieldValue } = useBooleanField();
const persistField = usePersistField();
@ -18,5 +22,11 @@ export const BooleanFieldInput = ({ onSubmit }: BooleanFieldInputProps) => {
onSubmit?.(() => persistField(newValue));
};
return <BooleanInput value={fieldValue ?? ''} onToggle={handleToggle} />;
return (
<BooleanInput
value={fieldValue ?? ''}
onToggle={handleToggle}
testId={testId}
/>
);
};

View File

@ -6,7 +6,7 @@ import { useDateField } from '../../hooks/useDateField';
export type FieldInputEvent = (persist: () => void) => void;
type DateFieldInputProps = {
export type DateFieldInputProps = {
onClickOutside?: FieldInputEvent;
onEnter?: FieldInputEvent;
onEscape?: FieldInputEvent;

View File

@ -0,0 +1,100 @@
import { useEffect } from 'react';
import { jest } from '@storybook/jest';
import { expect } from '@storybook/jest';
import { Meta, StoryObj } from '@storybook/react';
import { userEvent, within } from '@storybook/testing-library';
import { FieldContextProvider } from '../../../__stories__/FieldContextProvider';
import { useBooleanField } from '../../../hooks/useBooleanField';
import {
BooleanFieldInput,
BooleanFieldInputProps,
} from '../BooleanFieldInput';
const BooleanFieldValueSetterEffect = ({ value }: { value: boolean }) => {
const { setFieldValue } = useBooleanField();
useEffect(() => {
setFieldValue(value);
}, [setFieldValue, value]);
return <></>;
};
type BooleanFieldInputWithContextProps = BooleanFieldInputProps & {
value: boolean;
entityId?: string;
};
const BooleanFieldInputWithContext = ({
value,
entityId,
onSubmit,
}: BooleanFieldInputWithContextProps) => {
return (
<FieldContextProvider
fieldDefinition={{
key: 'boolean',
name: 'Boolean',
type: 'boolean',
metadata: {
fieldName: 'Boolean',
},
}}
entityId={entityId}
>
<BooleanFieldValueSetterEffect value={value} />
<BooleanFieldInput onSubmit={onSubmit} testId="boolean-field-input" />
</FieldContextProvider>
);
};
const meta: Meta = {
title: 'UI/Field/Input/BooleanFieldInput',
component: BooleanFieldInputWithContext,
args: {
value: true,
},
};
export default meta;
type Story = StoryObj<typeof BooleanFieldInputWithContext>;
const submitJestFn = jest.fn();
export const Default: Story = {};
export const Toggle: Story = {
args: {
onSubmit: submitJestFn,
},
argTypes: {
onSubmit: {
control: false,
},
},
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
const input = canvas.getByTestId('boolean-field-input');
const trueText = await within(input).findByText('True');
await expect(trueText).toBeInTheDocument();
await expect(submitJestFn).toHaveBeenCalledTimes(0);
await userEvent.click(input);
await expect(input).toHaveTextContent('False');
await expect(submitJestFn).toHaveBeenCalledTimes(1);
await userEvent.click(input);
await expect(input).toHaveTextContent('True');
await expect(submitJestFn).toHaveBeenCalledTimes(2);
},
};

View File

@ -0,0 +1,131 @@
import { useEffect } from 'react';
import { expect } from '@storybook/jest';
import { jest } from '@storybook/jest';
import { Meta, StoryObj } from '@storybook/react';
import { userEvent, within } from '@storybook/testing-library';
import { useSetHotkeyScope } from '@/ui/utilities/hotkey/hooks/useSetHotkeyScope';
import { FieldContextProvider } from '../../../__stories__/FieldContextProvider';
import { useDateField } from '../../../hooks/useDateField';
import { DateFieldInput, DateFieldInputProps } from '../DateFieldInput';
const formattedDate = new Date();
const DateFieldValueSetterEffect = ({ value }: { value: Date }) => {
const { setFieldValue } = useDateField();
useEffect(() => {
setFieldValue(value.toISOString());
}, [setFieldValue, value]);
return <></>;
};
type DateFieldInputWithContextProps = DateFieldInputProps & {
value: Date;
entityId?: string;
};
const DateFieldInputWithContext = ({
value,
entityId,
onEscape,
onEnter,
onClickOutside,
}: DateFieldInputWithContextProps) => {
const setHotkeyScope = useSetHotkeyScope();
useEffect(() => {
setHotkeyScope('hotkey-scope');
}, [setHotkeyScope]);
return (
<div>
<FieldContextProvider
fieldDefinition={{
key: 'date',
name: 'Date',
type: 'date',
metadata: {
fieldName: 'Date',
},
}}
entityId={entityId}
>
<DateFieldValueSetterEffect value={value} />
<DateFieldInput
onEscape={onEscape}
onEnter={onEnter}
onClickOutside={onClickOutside}
/>
</FieldContextProvider>
<div data-testid="data-field-input-click-outside-div"></div>
</div>
);
};
const escapeJestFn = jest.fn();
const enterJestFn = jest.fn();
const clickOutsideJestFn = jest.fn();
const meta: Meta = {
title: 'UI/Field/Input/DateFieldInput',
component: DateFieldInputWithContext,
args: {
value: formattedDate,
onEscape: escapeJestFn,
onEnter: enterJestFn,
onClickOutside: clickOutsideJestFn,
},
argTypes: {
onEscape: {
control: false,
},
onEnter: {
control: false,
},
onClickOutside: {
control: false,
},
},
};
export default meta;
type Story = StoryObj<typeof DateFieldInputWithContext>;
export const Default: Story = {};
export const ClickOutside: Story = {
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
await expect(clickOutsideJestFn).toHaveBeenCalledTimes(0);
const emptyDiv = canvas.getByTestId('data-field-input-click-outside-div');
await userEvent.click(emptyDiv);
await expect(clickOutsideJestFn).toHaveBeenCalledTimes(1);
},
};
export const Escape: Story = {
play: async () => {
await expect(escapeJestFn).toHaveBeenCalledTimes(0);
await userEvent.keyboard('{esc}');
await expect(escapeJestFn).toHaveBeenCalledTimes(1);
},
};
export const Enter: Story = {
play: async () => {
await expect(enterJestFn).toHaveBeenCalledTimes(0);
await userEvent.keyboard('{enter}');
await expect(enterJestFn).toHaveBeenCalledTimes(1);
},
};

View File

@ -20,9 +20,14 @@ const StyledEditableBooleanFieldValue = styled.div`
type BooleanInputProps = {
value: boolean;
onToggle?: (newValue: boolean) => void;
testId?: string;
};
export const BooleanInput = ({ value, onToggle }: BooleanInputProps) => {
export const BooleanInput = ({
value,
onToggle,
testId,
}: BooleanInputProps) => {
const [internalValue, setInternalValue] = useState(value);
useEffect(() => {
@ -37,7 +42,10 @@ export const BooleanInput = ({ value, onToggle }: BooleanInputProps) => {
const theme = useTheme();
return (
<StyledEditableBooleanFieldContainer onClick={handleClick}>
<StyledEditableBooleanFieldContainer
onClick={handleClick}
data-testid={testId}
>
{internalValue ? (
<IconCheck size={theme.icon.size.sm} />
) : (