Separate date and datetime components (#9161)

Add a 2 components solution for FormDate and FormDateTime
This commit is contained in:
martmull
2024-12-20 11:50:30 +01:00
committed by GitHub
parent 779c503381
commit 0d2bfad3c9
6 changed files with 810 additions and 782 deletions

View File

@ -1,6 +1,6 @@
import { FormAddressFieldInput } from '@/object-record/record-field/form-types/components/FormAddressFieldInput';
import { FormBooleanFieldInput } from '@/object-record/record-field/form-types/components/FormBooleanFieldInput';
import { FormDateTimeFieldInputBase } from '@/object-record/record-field/form-types/components/FormDateTimeFieldInputBase';
import { FormDateFieldInput } from '@/object-record/record-field/form-types/components/FormDateFieldInput';
import { FormEmailsFieldInput } from '@/object-record/record-field/form-types/components/FormEmailsFieldInput';
import { FormFullNameFieldInput } from '@/object-record/record-field/form-types/components/FormFullNameFieldInput';
import { FormLinksFieldInput } from '@/object-record/record-field/form-types/components/FormLinksFieldInput';
@ -34,6 +34,7 @@ import { isFieldSelect } from '@/object-record/record-field/types/guards/isField
import { isFieldText } from '@/object-record/record-field/types/guards/isFieldText';
import { isFieldUuid } from '@/object-record/record-field/types/guards/isFieldUuid';
import { JsonValue } from 'type-fest';
import { FormDateTimeFieldInput } from '@/object-record/record-field/form-types/components/FormDateTimeFieldInput';
type FormFieldInputProps = {
field: FieldDefinition<FieldMetadata>;
@ -109,16 +110,14 @@ export const FormFieldInput = ({
VariablePicker={VariablePicker}
/>
) : isFieldDate(field) ? (
<FormDateTimeFieldInputBase
mode="date"
<FormDateFieldInput
label={field.label}
defaultValue={defaultValue as string | undefined}
onPersist={onPersist}
VariablePicker={VariablePicker}
/>
) : isFieldDateTime(field) ? (
<FormDateTimeFieldInputBase
mode="datetime"
<FormDateTimeFieldInput
label={field.label}
defaultValue={defaultValue as string | undefined}
onPersist={onPersist}

View File

@ -0,0 +1,26 @@
import { FormDateTimeFieldInput } from '@/object-record/record-field/form-types/components/FormDateTimeFieldInput';
import { VariablePickerComponent } from '@/object-record/record-field/form-types/types/VariablePickerComponent';
type FormDateFieldInputProps = {
label?: string;
defaultValue: string | undefined;
onPersist: (value: string | null) => void;
VariablePicker?: VariablePickerComponent;
};
export const FormDateFieldInput = ({
label,
defaultValue,
onPersist,
VariablePicker,
}: FormDateFieldInputProps) => {
return (
<FormDateTimeFieldInput
dateOnly
label={label}
defaultValue={defaultValue}
onPersist={onPersist}
VariablePicker={VariablePicker}
/>
);
};

View File

@ -69,27 +69,26 @@ type DraftValue =
value: string;
};
type FormDateTimeFieldInputBaseProps = {
mode: 'date' | 'datetime';
type FormDateTimeFieldInputProps = {
dateOnly?: boolean;
label?: string;
placeholder?: string;
defaultValue: string | undefined;
onPersist: (value: string | null) => void;
VariablePicker?: VariablePickerComponent;
};
export const FormDateTimeFieldInputBase = ({
mode,
export const FormDateTimeFieldInput = ({
dateOnly,
label,
defaultValue,
onPersist,
VariablePicker,
}: FormDateTimeFieldInputBaseProps) => {
}: FormDateTimeFieldInputProps) => {
const { timeZone } = useContext(UserContext);
const inputId = useId();
const placeholder = mode === 'date' ? 'mm/dd/yyyy' : 'mm/dd/yyyy hh:mm';
const [draftValue, setDraftValue] = useState<DraftValue>(
isStandaloneVariableString(defaultValue)
? {
@ -116,7 +115,7 @@ export const FormDateTimeFieldInputBase = ({
isDefined(draftValueAsDate) && !isStandaloneVariableString(defaultValue)
? parseDateToString({
date: draftValueAsDate,
isDateTimeInput: mode === 'datetime',
isDateTimeInput: !dateOnly,
userTimezone: timeZone,
})
: '',
@ -143,6 +142,8 @@ export const FormDateTimeFieldInputBase = ({
const displayDatePicker =
draftValue.type === 'static' && draftValue.mode === 'edit';
const placeholder = dateOnly ? 'mm/dd/yyyy' : 'mm/dd/yyyy hh:mm';
useListenClickOutside({
refs: [datePickerWrapperRef],
listenerId: 'FormDateTimeFieldInputBase',
@ -168,7 +169,7 @@ export const FormDateTimeFieldInputBase = ({
isDefined(newDate)
? parseDateToString({
date: newDate,
isDateTimeInput: mode === 'datetime',
isDateTimeInput: !dateOnly,
userTimezone: timeZone,
})
: '',
@ -226,7 +227,7 @@ export const FormDateTimeFieldInputBase = ({
isDefined(newDate)
? parseDateToString({
date: newDate,
isDateTimeInput: mode === 'datetime',
isDateTimeInput: !dateOnly,
userTimezone: timeZone,
})
: '',
@ -262,7 +263,7 @@ export const FormDateTimeFieldInputBase = ({
const parsedInputDateTime = parseStringToDate({
dateAsString: inputDateTimeTrimmed,
isDateTimeInput: mode === 'datetime',
isDateTimeInput: !dateOnly,
userTimezone: timeZone,
});
@ -288,7 +289,7 @@ export const FormDateTimeFieldInputBase = ({
setInputDateTime(
parseDateToString({
date: validatedDate,
isDateTimeInput: mode === 'datetime',
isDateTimeInput: !dateOnly,
userTimezone: timeZone,
}),
);

View File

@ -0,0 +1,370 @@
import { MAX_DATE } from '@/ui/input/components/internal/date/constants/MaxDate';
import { MIN_DATE } from '@/ui/input/components/internal/date/constants/MinDate';
import { parseDateToString } from '@/ui/input/components/internal/date/utils/parseDateToString';
import { expect } from '@storybook/jest';
import { Meta, StoryObj } from '@storybook/react';
import {
fn,
userEvent,
waitFor,
waitForElementToBeRemoved,
within,
} from '@storybook/test';
import { DateTime } from 'luxon';
import { FormDateFieldInput } from '../FormDateFieldInput';
const meta: Meta<typeof FormDateFieldInput> = {
title: 'UI/Data/Field/Form/Input/FormDateFieldInput',
component: FormDateFieldInput,
args: {},
argTypes: {},
};
export default meta;
type Story = StoryObj<typeof FormDateFieldInput>;
export const Default: Story = {
args: {
label: 'Created At',
defaultValue: '2024-12-09T13:20:19.631Z',
},
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
await canvas.findByText('Created At');
await canvas.findByDisplayValue('12/09/2024');
},
};
export const WithDefaultEmptyValue: Story = {
args: {
label: 'Created At',
defaultValue: undefined,
},
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
await canvas.findByText('Created At');
await canvas.findByDisplayValue('');
await canvas.findByPlaceholderText('mm/dd/yyyy');
},
};
export const SetsDateWithInput: Story = {
args: {
label: 'Created At',
defaultValue: undefined,
onPersist: fn(),
},
play: async ({ canvasElement, args }) => {
const canvas = within(canvasElement);
const input = await canvas.findByPlaceholderText('mm/dd/yyyy');
await userEvent.click(input);
const dialog = await canvas.findByRole('dialog');
expect(dialog).toBeVisible();
await userEvent.type(input, '12/08/2024{enter}');
await waitFor(() => {
expect(args.onPersist).toHaveBeenCalledWith('2024-12-08T00:00:00.000Z');
});
expect(dialog).toBeVisible();
},
};
export const SetsDateWithDatePicker: Story = {
args: {
label: 'Created At',
defaultValue: undefined,
onPersist: fn(),
},
play: async ({ canvasElement, args }) => {
const canvas = within(canvasElement);
const input = await canvas.findByPlaceholderText('mm/dd/yyyy');
expect(input).toBeVisible();
await userEvent.click(input);
const datePicker = await canvas.findByRole('dialog');
expect(datePicker).toBeVisible();
const dayToChoose = await within(datePicker).findByRole('option', {
name: 'Choose Saturday, December 7th, 2024',
});
await Promise.all([
userEvent.click(dayToChoose),
waitForElementToBeRemoved(datePicker),
waitFor(() => {
expect(args.onPersist).toHaveBeenCalledWith(
expect.stringMatching(/^2024-12-07/),
);
}),
waitFor(() => {
expect(canvas.getByDisplayValue('12/07/2024')).toBeVisible();
}),
]);
},
};
export const ResetsDateByClickingButton: Story = {
args: {
label: 'Created At',
defaultValue: '2024-12-09T13:20:19.631Z',
onPersist: fn(),
},
play: async ({ canvasElement, args }) => {
const canvas = within(canvasElement);
const input = await canvas.findByPlaceholderText('mm/dd/yyyy');
expect(input).toBeVisible();
await userEvent.click(input);
const datePicker = await canvas.findByRole('dialog');
expect(datePicker).toBeVisible();
const clearButton = await canvas.findByText('Clear');
await Promise.all([
userEvent.click(clearButton),
waitForElementToBeRemoved(datePicker),
waitFor(() => {
expect(args.onPersist).toHaveBeenCalledWith(null);
}),
waitFor(() => {
expect(input).toHaveDisplayValue('');
}),
]);
},
};
export const ResetsDateByErasingInputContent: Story = {
args: {
label: 'Created At',
defaultValue: '2024-12-09T13:20:19.631Z',
onPersist: fn(),
},
play: async ({ canvasElement, args }) => {
const canvas = within(canvasElement);
const input = await canvas.findByPlaceholderText('mm/dd/yyyy');
expect(input).toBeVisible();
expect(input).toHaveDisplayValue('12/09/2024');
await userEvent.clear(input);
await Promise.all([
userEvent.type(input, '{Enter}'),
waitForElementToBeRemoved(() => canvas.queryByRole('dialog')),
waitFor(() => {
expect(args.onPersist).toHaveBeenCalledWith(null);
}),
waitFor(() => {
expect(input).toHaveDisplayValue('');
}),
]);
},
};
export const DefaultsToMinValueWhenTypingReallyOldDate: Story = {
args: {
label: 'Created At',
defaultValue: undefined,
onPersist: fn(),
},
play: async ({ canvasElement, args }) => {
const canvas = within(canvasElement);
const input = await canvas.findByPlaceholderText('mm/dd/yyyy');
expect(input).toBeVisible();
await userEvent.click(input);
const datePicker = await canvas.findByRole('dialog');
expect(datePicker).toBeVisible();
await Promise.all([
userEvent.type(input, '02/02/1500{Enter}'),
waitFor(() => {
expect(args.onPersist).toHaveBeenCalledWith(MIN_DATE.toISOString());
}),
waitFor(() => {
expect(input).toHaveDisplayValue(
parseDateToString({
date: MIN_DATE,
isDateTimeInput: false,
userTimezone: undefined,
}),
);
}),
waitFor(() => {
const expectedDate = DateTime.fromJSDate(MIN_DATE)
.toLocal()
.set({
day: MIN_DATE.getUTCDate(),
month: MIN_DATE.getUTCMonth() + 1,
year: MIN_DATE.getUTCFullYear(),
hour: 0,
minute: 0,
second: 0,
millisecond: 0,
});
const selectedDay = within(datePicker).getByRole('option', {
selected: true,
name: (accessibleName) => {
// The name looks like "Choose Sunday, December 31st, 1899"
return accessibleName.includes(expectedDate.toFormat('yyyy'));
},
});
expect(selectedDay).toBeVisible();
}),
]);
},
};
export const DefaultsToMaxValueWhenTypingReallyFarDate: Story = {
args: {
label: 'Created At',
defaultValue: undefined,
onPersist: fn(),
},
play: async ({ canvasElement, args }) => {
const canvas = within(canvasElement);
const input = await canvas.findByPlaceholderText('mm/dd/yyyy');
expect(input).toBeVisible();
await userEvent.click(input);
const datePicker = await canvas.findByRole('dialog');
expect(datePicker).toBeVisible();
await Promise.all([
userEvent.type(input, '02/02/2500{Enter}'),
waitFor(() => {
expect(args.onPersist).toHaveBeenCalledWith(MAX_DATE.toISOString());
}),
waitFor(() => {
expect(input).toHaveDisplayValue(
parseDateToString({
date: MAX_DATE,
isDateTimeInput: false,
userTimezone: undefined,
}),
);
}),
waitFor(() => {
const expectedDate = DateTime.fromJSDate(MAX_DATE)
.toLocal()
.set({
day: MAX_DATE.getUTCDate(),
month: MAX_DATE.getUTCMonth() + 1,
year: MAX_DATE.getUTCFullYear(),
hour: 0,
minute: 0,
second: 0,
millisecond: 0,
});
const selectedDay = within(datePicker).getByRole('option', {
selected: true,
name: (accessibleName) => {
// The name looks like "Choose Thursday, December 30th, 2100"
return accessibleName.includes(expectedDate.toFormat('yyyy'));
},
});
expect(selectedDay).toBeVisible();
}),
]);
},
};
export const SwitchesToStandaloneVariable: Story = {
args: {
label: 'Created At',
defaultValue: undefined,
onPersist: fn(),
VariablePicker: ({ onVariableSelect }) => {
return (
<button
onClick={() => {
onVariableSelect('{{test}}');
}}
>
Add variable
</button>
);
},
},
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
const addVariableButton = await canvas.findByText('Add variable');
await userEvent.click(addVariableButton);
const variableTag = await canvas.findByText('test');
expect(variableTag).toBeVisible();
const removeVariableButton = canvas.getByTestId(/^remove-icon/);
await Promise.all([
userEvent.click(removeVariableButton),
waitForElementToBeRemoved(variableTag),
waitFor(() => {
const input = canvas.getByPlaceholderText('mm/dd/yyyy');
expect(input).toBeVisible();
}),
]);
},
};
export const ClickingOutsideDoesNotResetInputState: Story = {
args: {
label: 'Created At',
defaultValue: '2024-12-09T13:20:19.631Z',
onPersist: fn(),
},
play: async ({ canvasElement, args }) => {
const defaultValueAsDisplayString = parseDateToString({
date: new Date(args.defaultValue!),
isDateTimeInput: false,
userTimezone: undefined,
});
const canvas = within(canvasElement);
const input = await canvas.findByPlaceholderText('mm/dd/yyyy');
expect(input).toBeVisible();
expect(input).toHaveDisplayValue(defaultValueAsDisplayString);
await userEvent.type(input, '{Backspace}{Backspace}');
const datePicker = await canvas.findByRole('dialog');
expect(datePicker).toBeVisible();
await Promise.all([
userEvent.click(canvasElement),
waitForElementToBeRemoved(datePicker),
]);
expect(args.onPersist).not.toHaveBeenCalled();
expect(input).toHaveDisplayValue(defaultValueAsDisplayString.slice(0, -2));
},
};

View File

@ -0,0 +1,397 @@
import { MAX_DATE } from '@/ui/input/components/internal/date/constants/MaxDate';
import { MIN_DATE } from '@/ui/input/components/internal/date/constants/MinDate';
import { parseDateToString } from '@/ui/input/components/internal/date/utils/parseDateToString';
import { expect } from '@storybook/jest';
import { Meta, StoryObj } from '@storybook/react';
import {
fn,
userEvent,
waitFor,
waitForElementToBeRemoved,
within,
} from '@storybook/test';
import { DateTime } from 'luxon';
import { FormDateTimeFieldInput } from '@/object-record/record-field/form-types/components/FormDateTimeFieldInput';
const meta: Meta<typeof FormDateTimeFieldInput> = {
title: 'UI/Data/Field/Form/Input/FormDateTimeFieldInput',
component: FormDateTimeFieldInput,
args: {},
argTypes: {},
};
export default meta;
type Story = StoryObj<typeof FormDateTimeFieldInput>;
export const Default: Story = {
args: {
label: 'Created At',
defaultValue: '2024-12-09T13:20:19.631Z',
},
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
await canvas.findByText('Created At');
await canvas.findByDisplayValue(/12\/09\/2024 \d{2}:20/);
},
};
export const WithDefaultEmptyValue: Story = {
args: {
label: 'Created At',
defaultValue: undefined,
},
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
await canvas.findByText('Created At');
await canvas.findByDisplayValue('');
await canvas.findByPlaceholderText('mm/dd/yyyy hh:mm');
},
};
export const SetsDateTimeWithInput: Story = {
args: {
label: 'Created At',
defaultValue: undefined,
onPersist: fn(),
},
play: async ({ canvasElement, args }) => {
const canvas = within(canvasElement);
const input = await canvas.findByPlaceholderText('mm/dd/yyyy hh:mm');
await userEvent.click(input);
const dialog = await canvas.findByRole('dialog');
expect(dialog).toBeVisible();
await userEvent.type(input, '12/08/2024 12:10{enter}');
await waitFor(() => {
expect(args.onPersist).toHaveBeenCalledWith(
expect.stringMatching(/2024-12-08T\d{2}:10:00.000Z/),
);
});
expect(dialog).toBeVisible();
},
};
export const DoesNotSetDateWithoutTime: Story = {
args: {
label: 'Created At',
defaultValue: undefined,
onPersist: fn(),
},
play: async ({ canvasElement, args }) => {
const canvas = within(canvasElement);
const input = await canvas.findByPlaceholderText('mm/dd/yyyy hh:mm');
await userEvent.click(input);
const dialog = await canvas.findByRole('dialog');
expect(dialog).toBeVisible();
await userEvent.type(input, '12/08/2024{enter}');
expect(args.onPersist).not.toHaveBeenCalled();
expect(dialog).toBeVisible();
},
};
export const SetsDateTimeWithDatePicker: Story = {
args: {
label: 'Created At',
defaultValue: undefined,
onPersist: fn(),
},
play: async ({ canvasElement, args }) => {
const canvas = within(canvasElement);
const input = await canvas.findByPlaceholderText('mm/dd/yyyy hh:mm');
expect(input).toBeVisible();
await userEvent.click(input);
const datePicker = await canvas.findByRole('dialog');
expect(datePicker).toBeVisible();
const dayToChoose = await within(datePicker).findByRole('option', {
name: 'Choose Saturday, December 7th, 2024',
});
await Promise.all([
userEvent.click(dayToChoose),
waitForElementToBeRemoved(datePicker),
waitFor(() => {
expect(args.onPersist).toHaveBeenCalledWith(
expect.stringMatching(/^2024-12-07/),
);
}),
waitFor(() => {
expect(
canvas.getByDisplayValue(/12\/07\/2024 \d{2}:\d{2}/),
).toBeVisible();
}),
]);
},
};
export const ResetsDateByClickingButton: Story = {
args: {
label: 'Created At',
defaultValue: '2024-12-09T13:20:19.631Z',
onPersist: fn(),
},
play: async ({ canvasElement, args }) => {
const canvas = within(canvasElement);
const input = await canvas.findByPlaceholderText('mm/dd/yyyy hh:mm');
expect(input).toBeVisible();
await userEvent.click(input);
const datePicker = await canvas.findByRole('dialog');
expect(datePicker).toBeVisible();
const clearButton = await canvas.findByText('Clear');
await Promise.all([
userEvent.click(clearButton),
waitForElementToBeRemoved(datePicker),
waitFor(() => {
expect(args.onPersist).toHaveBeenCalledWith(null);
}),
waitFor(() => {
expect(input).toHaveDisplayValue('');
}),
]);
},
};
export const ResetsDateByErasingInputContent: Story = {
args: {
label: 'Created At',
defaultValue: '2024-12-09T13:20:19.631Z',
onPersist: fn(),
},
play: async ({ canvasElement, args }) => {
const canvas = within(canvasElement);
const input = await canvas.findByPlaceholderText('mm/dd/yyyy hh:mm');
expect(input).toBeVisible();
expect(input).toHaveDisplayValue(/12\/09\/2024 \d{2}:\d{2}/);
await userEvent.clear(input);
await Promise.all([
userEvent.type(input, '{Enter}'),
waitForElementToBeRemoved(() => canvas.queryByRole('dialog')),
waitFor(() => {
expect(args.onPersist).toHaveBeenCalledWith(null);
}),
waitFor(() => {
expect(input).toHaveDisplayValue('');
}),
]);
},
};
export const DefaultsToMinValueWhenTypingReallyOldDate: Story = {
args: {
label: 'Created At',
defaultValue: undefined,
onPersist: fn(),
},
play: async ({ canvasElement, args }) => {
const canvas = within(canvasElement);
const input = await canvas.findByPlaceholderText('mm/dd/yyyy hh:mm');
expect(input).toBeVisible();
await userEvent.click(input);
const datePicker = await canvas.findByRole('dialog');
expect(datePicker).toBeVisible();
await Promise.all([
userEvent.type(input, '02/02/1500 10:10{Enter}'),
waitFor(() => {
expect(args.onPersist).toHaveBeenCalledWith(MIN_DATE.toISOString());
}),
waitFor(() => {
expect(input).toHaveDisplayValue(
parseDateToString({
date: MIN_DATE,
isDateTimeInput: true,
userTimezone: undefined,
}),
);
}),
waitFor(() => {
const expectedDate = DateTime.fromJSDate(MIN_DATE)
.toLocal()
.set({
day: MIN_DATE.getUTCDate(),
month: MIN_DATE.getUTCMonth() + 1,
year: MIN_DATE.getUTCFullYear(),
hour: 0,
minute: 0,
second: 0,
millisecond: 0,
});
const selectedDay = within(datePicker).getByRole('option', {
selected: true,
name: (accessibleName) => {
// The name looks like "Choose Sunday, December 31st, 1899"
return accessibleName.includes(expectedDate.toFormat('yyyy'));
},
});
expect(selectedDay).toBeVisible();
}),
]);
},
};
export const DefaultsToMaxValueWhenTypingReallyFarDate: Story = {
args: {
label: 'Created At',
defaultValue: undefined,
onPersist: fn(),
},
play: async ({ canvasElement, args }) => {
const canvas = within(canvasElement);
const input = await canvas.findByPlaceholderText('mm/dd/yyyy hh:mm');
expect(input).toBeVisible();
await userEvent.click(input);
const datePicker = await canvas.findByRole('dialog');
expect(datePicker).toBeVisible();
await Promise.all([
userEvent.type(input, '02/02/2500 10:10{Enter}'),
waitFor(() => {
expect(args.onPersist).toHaveBeenCalledWith(MAX_DATE.toISOString());
}),
waitFor(() => {
expect(input).toHaveDisplayValue(
parseDateToString({
date: MAX_DATE,
isDateTimeInput: true,
userTimezone: undefined,
}),
);
}),
waitFor(() => {
const expectedDate = DateTime.fromJSDate(MAX_DATE)
.toLocal()
.set({
day: MAX_DATE.getUTCDate(),
month: MAX_DATE.getUTCMonth() + 1,
year: MAX_DATE.getUTCFullYear(),
hour: 0,
minute: 0,
second: 0,
millisecond: 0,
});
const selectedDay = within(datePicker).getByRole('option', {
selected: true,
name: (accessibleName) => {
// The name looks like "Choose Thursday, December 30th, 2100"
return accessibleName.includes(expectedDate.toFormat('yyyy'));
},
});
expect(selectedDay).toBeVisible();
}),
]);
},
};
export const SwitchesToStandaloneVariable: Story = {
args: {
label: 'Created At',
defaultValue: undefined,
onPersist: fn(),
VariablePicker: ({ onVariableSelect }) => {
return (
<button
onClick={() => {
onVariableSelect('{{test}}');
}}
>
Add variable
</button>
);
},
},
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
const addVariableButton = await canvas.findByText('Add variable');
await userEvent.click(addVariableButton);
const variableTag = await canvas.findByText('test');
expect(variableTag).toBeVisible();
const removeVariableButton = canvas.getByTestId(/^remove-icon/);
await Promise.all([
userEvent.click(removeVariableButton),
waitForElementToBeRemoved(variableTag),
waitFor(() => {
const input = canvas.getByPlaceholderText('mm/dd/yyyy hh:mm');
expect(input).toBeVisible();
}),
]);
},
};
export const ClickingOutsideDoesNotResetInputState: Story = {
args: {
label: 'Created At',
defaultValue: '2024-12-09T13:20:19.631Z',
onPersist: fn(),
},
play: async ({ canvasElement, args }) => {
const defaultValueAsDisplayString = parseDateToString({
date: new Date(args.defaultValue!),
isDateTimeInput: true,
userTimezone: undefined,
});
const canvas = within(canvasElement);
const input = await canvas.findByPlaceholderText('mm/dd/yyyy hh:mm');
expect(input).toBeVisible();
expect(input).toHaveDisplayValue(defaultValueAsDisplayString);
await userEvent.type(input, '{Backspace}{Backspace}');
const datePicker = await canvas.findByRole('dialog');
expect(datePicker).toBeVisible();
await Promise.all([
userEvent.click(canvasElement),
waitForElementToBeRemoved(datePicker),
]);
expect(args.onPersist).not.toHaveBeenCalled();
expect(input).toHaveDisplayValue(defaultValueAsDisplayString.slice(0, -2));
},
};

View File

@ -1,765 +0,0 @@
import { MAX_DATE } from '@/ui/input/components/internal/date/constants/MaxDate';
import { MIN_DATE } from '@/ui/input/components/internal/date/constants/MinDate';
import { parseDateToString } from '@/ui/input/components/internal/date/utils/parseDateToString';
import { expect } from '@storybook/jest';
import { Meta, StoryObj } from '@storybook/react';
import {
fn,
userEvent,
waitFor,
waitForElementToBeRemoved,
within,
} from '@storybook/test';
import { DateTime } from 'luxon';
import { FormDateTimeFieldInputBase } from '../FormDateTimeFieldInputBase';
const meta: Meta<typeof FormDateTimeFieldInputBase> = {
title: 'UI/Data/Field/Form/Input/FormDateTimeFieldInputBase',
component: FormDateTimeFieldInputBase,
args: {},
argTypes: {},
};
export default meta;
type Story = StoryObj<typeof FormDateTimeFieldInputBase>;
export const DateDefault: Story = {
args: {
mode: 'date',
label: 'Created At',
defaultValue: '2024-12-09T13:20:19.631Z',
},
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
await canvas.findByText('Created At');
await canvas.findByDisplayValue('12/09/2024');
},
};
export const DateWithDefaultEmptyValue: Story = {
args: {
mode: 'date',
label: 'Created At',
defaultValue: undefined,
},
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
await canvas.findByText('Created At');
await canvas.findByDisplayValue('');
await canvas.findByPlaceholderText('mm/dd/yyyy');
},
};
export const DateSetsDateWithInput: Story = {
args: {
mode: 'date',
label: 'Created At',
defaultValue: undefined,
onPersist: fn(),
},
play: async ({ canvasElement, args }) => {
const canvas = within(canvasElement);
const input = await canvas.findByPlaceholderText('mm/dd/yyyy');
await userEvent.click(input);
const dialog = await canvas.findByRole('dialog');
expect(dialog).toBeVisible();
await userEvent.type(input, '12/08/2024{enter}');
await waitFor(() => {
expect(args.onPersist).toHaveBeenCalledWith('2024-12-08T00:00:00.000Z');
});
expect(dialog).toBeVisible();
},
};
export const DateSetsDateWithDatePicker: Story = {
args: {
mode: 'date',
label: 'Created At',
defaultValue: undefined,
onPersist: fn(),
},
play: async ({ canvasElement, args }) => {
const canvas = within(canvasElement);
const input = await canvas.findByPlaceholderText('mm/dd/yyyy');
expect(input).toBeVisible();
await userEvent.click(input);
const datePicker = await canvas.findByRole('dialog');
expect(datePicker).toBeVisible();
const dayToChoose = await within(datePicker).findByRole('option', {
name: 'Choose Saturday, December 7th, 2024',
});
await Promise.all([
userEvent.click(dayToChoose),
waitForElementToBeRemoved(datePicker),
waitFor(() => {
expect(args.onPersist).toHaveBeenCalledWith(
expect.stringMatching(/^2024-12-07/),
);
}),
waitFor(() => {
expect(canvas.getByDisplayValue('12/07/2024')).toBeVisible();
}),
]);
},
};
export const DateResetsDateByClickingButton: Story = {
args: {
mode: 'date',
label: 'Created At',
defaultValue: '2024-12-09T13:20:19.631Z',
onPersist: fn(),
},
play: async ({ canvasElement, args }) => {
const canvas = within(canvasElement);
const input = await canvas.findByPlaceholderText('mm/dd/yyyy');
expect(input).toBeVisible();
await userEvent.click(input);
const datePicker = await canvas.findByRole('dialog');
expect(datePicker).toBeVisible();
const clearButton = await canvas.findByText('Clear');
await Promise.all([
userEvent.click(clearButton),
waitForElementToBeRemoved(datePicker),
waitFor(() => {
expect(args.onPersist).toHaveBeenCalledWith(null);
}),
waitFor(() => {
expect(input).toHaveDisplayValue('');
}),
]);
},
};
export const DateResetsDateByErasingInputContent: Story = {
args: {
mode: 'date',
label: 'Created At',
defaultValue: '2024-12-09T13:20:19.631Z',
onPersist: fn(),
},
play: async ({ canvasElement, args }) => {
const canvas = within(canvasElement);
const input = await canvas.findByPlaceholderText('mm/dd/yyyy');
expect(input).toBeVisible();
expect(input).toHaveDisplayValue('12/09/2024');
await userEvent.clear(input);
await Promise.all([
userEvent.type(input, '{Enter}'),
waitForElementToBeRemoved(() => canvas.queryByRole('dialog')),
waitFor(() => {
expect(args.onPersist).toHaveBeenCalledWith(null);
}),
waitFor(() => {
expect(input).toHaveDisplayValue('');
}),
]);
},
};
export const DateDefaultsToMinValueWhenTypingReallyOldDate: Story = {
args: {
mode: 'date',
label: 'Created At',
defaultValue: undefined,
onPersist: fn(),
},
play: async ({ canvasElement, args }) => {
const canvas = within(canvasElement);
const input = await canvas.findByPlaceholderText('mm/dd/yyyy');
expect(input).toBeVisible();
await userEvent.click(input);
const datePicker = await canvas.findByRole('dialog');
expect(datePicker).toBeVisible();
await Promise.all([
userEvent.type(input, '02/02/1500{Enter}'),
waitFor(() => {
expect(args.onPersist).toHaveBeenCalledWith(MIN_DATE.toISOString());
}),
waitFor(() => {
expect(input).toHaveDisplayValue(
parseDateToString({
date: MIN_DATE,
isDateTimeInput: false,
userTimezone: undefined,
}),
);
}),
waitFor(() => {
const expectedDate = DateTime.fromJSDate(MIN_DATE)
.toLocal()
.set({
day: MIN_DATE.getUTCDate(),
month: MIN_DATE.getUTCMonth() + 1,
year: MIN_DATE.getUTCFullYear(),
hour: 0,
minute: 0,
second: 0,
millisecond: 0,
});
const selectedDay = within(datePicker).getByRole('option', {
selected: true,
name: (accessibleName) => {
// The name looks like "Choose Sunday, December 31st, 1899"
return accessibleName.includes(expectedDate.toFormat('yyyy'));
},
});
expect(selectedDay).toBeVisible();
}),
]);
},
};
export const DateDefaultsToMaxValueWhenTypingReallyFarDate: Story = {
args: {
mode: 'date',
label: 'Created At',
defaultValue: undefined,
onPersist: fn(),
},
play: async ({ canvasElement, args }) => {
const canvas = within(canvasElement);
const input = await canvas.findByPlaceholderText('mm/dd/yyyy');
expect(input).toBeVisible();
await userEvent.click(input);
const datePicker = await canvas.findByRole('dialog');
expect(datePicker).toBeVisible();
await Promise.all([
userEvent.type(input, '02/02/2500{Enter}'),
waitFor(() => {
expect(args.onPersist).toHaveBeenCalledWith(MAX_DATE.toISOString());
}),
waitFor(() => {
expect(input).toHaveDisplayValue(
parseDateToString({
date: MAX_DATE,
isDateTimeInput: false,
userTimezone: undefined,
}),
);
}),
waitFor(() => {
const expectedDate = DateTime.fromJSDate(MAX_DATE)
.toLocal()
.set({
day: MAX_DATE.getUTCDate(),
month: MAX_DATE.getUTCMonth() + 1,
year: MAX_DATE.getUTCFullYear(),
hour: 0,
minute: 0,
second: 0,
millisecond: 0,
});
const selectedDay = within(datePicker).getByRole('option', {
selected: true,
name: (accessibleName) => {
// The name looks like "Choose Thursday, December 30th, 2100"
return accessibleName.includes(expectedDate.toFormat('yyyy'));
},
});
expect(selectedDay).toBeVisible();
}),
]);
},
};
export const DateSwitchesToStandaloneVariable: Story = {
args: {
mode: 'date',
label: 'Created At',
defaultValue: undefined,
onPersist: fn(),
VariablePicker: ({ onVariableSelect }) => {
return (
<button
onClick={() => {
onVariableSelect('{{test}}');
}}
>
Add variable
</button>
);
},
},
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
const addVariableButton = await canvas.findByText('Add variable');
await userEvent.click(addVariableButton);
const variableTag = await canvas.findByText('test');
expect(variableTag).toBeVisible();
const removeVariableButton = canvas.getByTestId(/^remove-icon/);
await Promise.all([
userEvent.click(removeVariableButton),
waitForElementToBeRemoved(variableTag),
waitFor(() => {
const input = canvas.getByPlaceholderText('mm/dd/yyyy');
expect(input).toBeVisible();
}),
]);
},
};
export const DateClickingOutsideDoesNotResetInputState: Story = {
args: {
mode: 'date',
label: 'Created At',
defaultValue: '2024-12-09T13:20:19.631Z',
onPersist: fn(),
},
play: async ({ canvasElement, args }) => {
const defaultValueAsDisplayString = parseDateToString({
date: new Date(args.defaultValue!),
isDateTimeInput: false,
userTimezone: undefined,
});
const canvas = within(canvasElement);
const input = await canvas.findByPlaceholderText('mm/dd/yyyy');
expect(input).toBeVisible();
expect(input).toHaveDisplayValue(defaultValueAsDisplayString);
await userEvent.type(input, '{Backspace}{Backspace}');
const datePicker = await canvas.findByRole('dialog');
expect(datePicker).toBeVisible();
await Promise.all([
userEvent.click(canvasElement),
waitForElementToBeRemoved(datePicker),
]);
expect(args.onPersist).not.toHaveBeenCalled();
expect(input).toHaveDisplayValue(defaultValueAsDisplayString.slice(0, -2));
},
};
// ----
export const DateTimeDefault: Story = {
args: {
mode: 'datetime',
label: 'Created At',
defaultValue: '2024-12-09T13:20:19.631Z',
},
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
await canvas.findByText('Created At');
await canvas.findByDisplayValue(/12\/09\/2024 \d{2}:20/);
},
};
export const DateTimeWithDefaultEmptyValue: Story = {
args: {
mode: 'datetime',
label: 'Created At',
defaultValue: undefined,
},
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
await canvas.findByText('Created At');
await canvas.findByDisplayValue('');
await canvas.findByPlaceholderText('mm/dd/yyyy hh:mm');
},
};
export const DateTimeSetsDateTimeWithInput: Story = {
args: {
mode: 'datetime',
label: 'Created At',
defaultValue: undefined,
onPersist: fn(),
},
play: async ({ canvasElement, args }) => {
const canvas = within(canvasElement);
const input = await canvas.findByPlaceholderText('mm/dd/yyyy hh:mm');
await userEvent.click(input);
const dialog = await canvas.findByRole('dialog');
expect(dialog).toBeVisible();
await userEvent.type(input, '12/08/2024 12:10{enter}');
await waitFor(() => {
expect(args.onPersist).toHaveBeenCalledWith(
expect.stringMatching(/2024-12-08T\d{2}:10:00.000Z/),
);
});
expect(dialog).toBeVisible();
},
};
export const DateTimeDoesNotSetDateWithoutTime: Story = {
args: {
mode: 'datetime',
label: 'Created At',
defaultValue: undefined,
onPersist: fn(),
},
play: async ({ canvasElement, args }) => {
const canvas = within(canvasElement);
const input = await canvas.findByPlaceholderText('mm/dd/yyyy hh:mm');
await userEvent.click(input);
const dialog = await canvas.findByRole('dialog');
expect(dialog).toBeVisible();
await userEvent.type(input, '12/08/2024{enter}');
expect(args.onPersist).not.toHaveBeenCalled();
expect(dialog).toBeVisible();
},
};
export const DateTimeSetsDateTimeWithDatePicker: Story = {
args: {
mode: 'datetime',
label: 'Created At',
defaultValue: undefined,
onPersist: fn(),
},
play: async ({ canvasElement, args }) => {
const canvas = within(canvasElement);
const input = await canvas.findByPlaceholderText('mm/dd/yyyy hh:mm');
expect(input).toBeVisible();
await userEvent.click(input);
const datePicker = await canvas.findByRole('dialog');
expect(datePicker).toBeVisible();
const dayToChoose = await within(datePicker).findByRole('option', {
name: 'Choose Saturday, December 7th, 2024',
});
await Promise.all([
userEvent.click(dayToChoose),
waitForElementToBeRemoved(datePicker),
waitFor(() => {
expect(args.onPersist).toHaveBeenCalledWith(
expect.stringMatching(/^2024-12-07/),
);
}),
waitFor(() => {
expect(
canvas.getByDisplayValue(/12\/07\/2024 \d{2}:\d{2}/),
).toBeVisible();
}),
]);
},
};
export const DateTimeResetsDateByClickingButton: Story = {
args: {
mode: 'datetime',
label: 'Created At',
defaultValue: '2024-12-09T13:20:19.631Z',
onPersist: fn(),
},
play: async ({ canvasElement, args }) => {
const canvas = within(canvasElement);
const input = await canvas.findByPlaceholderText('mm/dd/yyyy hh:mm');
expect(input).toBeVisible();
await userEvent.click(input);
const datePicker = await canvas.findByRole('dialog');
expect(datePicker).toBeVisible();
const clearButton = await canvas.findByText('Clear');
await Promise.all([
userEvent.click(clearButton),
waitForElementToBeRemoved(datePicker),
waitFor(() => {
expect(args.onPersist).toHaveBeenCalledWith(null);
}),
waitFor(() => {
expect(input).toHaveDisplayValue('');
}),
]);
},
};
export const DateTimeResetsDateByErasingInputContent: Story = {
args: {
mode: 'datetime',
label: 'Created At',
defaultValue: '2024-12-09T13:20:19.631Z',
onPersist: fn(),
},
play: async ({ canvasElement, args }) => {
const canvas = within(canvasElement);
const input = await canvas.findByPlaceholderText('mm/dd/yyyy hh:mm');
expect(input).toBeVisible();
expect(input).toHaveDisplayValue(/12\/09\/2024 \d{2}:\d{2}/);
await userEvent.clear(input);
await Promise.all([
userEvent.type(input, '{Enter}'),
waitForElementToBeRemoved(() => canvas.queryByRole('dialog')),
waitFor(() => {
expect(args.onPersist).toHaveBeenCalledWith(null);
}),
waitFor(() => {
expect(input).toHaveDisplayValue('');
}),
]);
},
};
export const DateTimeDefaultsToMinValueWhenTypingReallyOldDate: Story = {
args: {
mode: 'datetime',
label: 'Created At',
defaultValue: undefined,
onPersist: fn(),
},
play: async ({ canvasElement, args }) => {
const canvas = within(canvasElement);
const input = await canvas.findByPlaceholderText('mm/dd/yyyy hh:mm');
expect(input).toBeVisible();
await userEvent.click(input);
const datePicker = await canvas.findByRole('dialog');
expect(datePicker).toBeVisible();
await Promise.all([
userEvent.type(input, '02/02/1500 10:10{Enter}'),
waitFor(() => {
expect(args.onPersist).toHaveBeenCalledWith(MIN_DATE.toISOString());
}),
waitFor(() => {
expect(input).toHaveDisplayValue(
parseDateToString({
date: MIN_DATE,
isDateTimeInput: true,
userTimezone: undefined,
}),
);
}),
waitFor(() => {
const expectedDate = DateTime.fromJSDate(MIN_DATE)
.toLocal()
.set({
day: MIN_DATE.getUTCDate(),
month: MIN_DATE.getUTCMonth() + 1,
year: MIN_DATE.getUTCFullYear(),
hour: 0,
minute: 0,
second: 0,
millisecond: 0,
});
const selectedDay = within(datePicker).getByRole('option', {
selected: true,
name: (accessibleName) => {
// The name looks like "Choose Sunday, December 31st, 1899"
return accessibleName.includes(expectedDate.toFormat('yyyy'));
},
});
expect(selectedDay).toBeVisible();
}),
]);
},
};
export const DateTimeDefaultsToMaxValueWhenTypingReallyFarDate: Story = {
args: {
mode: 'datetime',
label: 'Created At',
defaultValue: undefined,
onPersist: fn(),
},
play: async ({ canvasElement, args }) => {
const canvas = within(canvasElement);
const input = await canvas.findByPlaceholderText('mm/dd/yyyy hh:mm');
expect(input).toBeVisible();
await userEvent.click(input);
const datePicker = await canvas.findByRole('dialog');
expect(datePicker).toBeVisible();
await Promise.all([
userEvent.type(input, '02/02/2500 10:10{Enter}'),
waitFor(() => {
expect(args.onPersist).toHaveBeenCalledWith(MAX_DATE.toISOString());
}),
waitFor(() => {
expect(input).toHaveDisplayValue(
parseDateToString({
date: MAX_DATE,
isDateTimeInput: true,
userTimezone: undefined,
}),
);
}),
waitFor(() => {
const expectedDate = DateTime.fromJSDate(MAX_DATE)
.toLocal()
.set({
day: MAX_DATE.getUTCDate(),
month: MAX_DATE.getUTCMonth() + 1,
year: MAX_DATE.getUTCFullYear(),
hour: 0,
minute: 0,
second: 0,
millisecond: 0,
});
const selectedDay = within(datePicker).getByRole('option', {
selected: true,
name: (accessibleName) => {
// The name looks like "Choose Thursday, December 30th, 2100"
return accessibleName.includes(expectedDate.toFormat('yyyy'));
},
});
expect(selectedDay).toBeVisible();
}),
]);
},
};
export const DateTimeSwitchesToStandaloneVariable: Story = {
args: {
mode: 'datetime',
label: 'Created At',
defaultValue: undefined,
onPersist: fn(),
VariablePicker: ({ onVariableSelect }) => {
return (
<button
onClick={() => {
onVariableSelect('{{test}}');
}}
>
Add variable
</button>
);
},
},
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
const addVariableButton = await canvas.findByText('Add variable');
await userEvent.click(addVariableButton);
const variableTag = await canvas.findByText('test');
expect(variableTag).toBeVisible();
const removeVariableButton = canvas.getByTestId(/^remove-icon/);
await Promise.all([
userEvent.click(removeVariableButton),
waitForElementToBeRemoved(variableTag),
waitFor(() => {
const input = canvas.getByPlaceholderText('mm/dd/yyyy hh:mm');
expect(input).toBeVisible();
}),
]);
},
};
export const DateTimeClickingOutsideDoesNotResetInputState: Story = {
args: {
mode: 'datetime',
label: 'Created At',
defaultValue: '2024-12-09T13:20:19.631Z',
onPersist: fn(),
},
play: async ({ canvasElement, args }) => {
const defaultValueAsDisplayString = parseDateToString({
date: new Date(args.defaultValue!),
isDateTimeInput: true,
userTimezone: undefined,
});
const canvas = within(canvasElement);
const input = await canvas.findByPlaceholderText('mm/dd/yyyy hh:mm');
expect(input).toBeVisible();
expect(input).toHaveDisplayValue(defaultValueAsDisplayString);
await userEvent.type(input, '{Backspace}{Backspace}');
const datePicker = await canvas.findByRole('dialog');
expect(datePicker).toBeVisible();
await Promise.all([
userEvent.click(canvasElement),
waitForElementToBeRemoved(datePicker),
]);
expect(args.onPersist).not.toHaveBeenCalled();
expect(input).toHaveDisplayValue(defaultValueAsDisplayString.slice(0, -2));
},
};