Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {
stubMatchMedia,
} from 'test/utils/pickers';
import { pickerPopperClasses } from '@mui/x-date-pickers/internals';
import { MultiInputDateRangeField } from '../MultiInputDateRangeField';
import { MultiInputDateRangeField } from '../../MultiInputDateRangeField';

describe('<DateRangePicker />', () => {
const { render } = createPickerRenderer();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import * as React from 'react';
import { spy } from 'sinon';
import { DateRangePicker } from '@mui/x-date-pickers-pro/DateRangePicker';
import { screen } from '@mui/internal-test-utils';
import { createPickerRenderer, getFieldInputRoot } from 'test/utils/pickers';

describe('<DateRangePicker /> - partiallyFilledDate validation', () => {
const { render } = createPickerRenderer();

it('should call onError with partiallyFilledDate for start when start is partially filled', async () => {
const onError = spy();
const { user } = render(<DateRangePicker onError={onError} />);

const month = screen.getAllByRole('spinbutton', { name: 'Month' })[0]; // start month
await user.click(month);
await user.keyboard('01');

expect(onError.callCount).to.equal(1);
expect(onError.lastCall.args[0]).to.deep.equal(['partiallyFilledDate', null]);
});

it('should call onError with partiallyFilledDate for end when end is partially filled', async () => {
const onError = spy();
const { user } = render(<DateRangePicker onError={onError} />);

const months = screen.getAllByRole('spinbutton', { name: 'Month' });
const endMonth = months[1];
await user.click(endMonth);
await user.keyboard('01');

expect(onError.callCount).to.equal(1);
expect(onError.lastCall.args[0]).to.deep.equal([null, 'partiallyFilledDate']);
});

it('should call onError with null when partially filled start is fully cleared', async () => {
const onError = spy();
const { user } = render(<DateRangePicker onError={onError} />);

const month = screen.getAllByRole('spinbutton', { name: 'Month' })[0];
await user.click(month);
await user.keyboard('01');
expect(onError.lastCall.args[0]).to.deep.equal(['partiallyFilledDate', null]);

await user.click(month);
await user.keyboard('{Control>}a{/Control}');
await user.keyboard('{Delete}');

expect(onError.lastCall.args[0]).to.deep.equal([null, null]);
});

it('should show field as invalid when start is partially filled', async () => {
const { user } = render(<DateRangePicker />);

const month = screen.getAllByRole('spinbutton', { name: 'Month' })[0];
await user.click(month);
await user.keyboard('01');

expect(getFieldInputRoot()).to.have.attribute('aria-invalid', 'true');
});

it('should return invalidDate error when start is fully filled but is invalid', async () => {
const onError = spy();
const { user } = render(<DateRangePicker onError={onError} />);

const months = screen.getAllByRole('spinbutton', { name: 'Month' });
const days = screen.getAllByRole('spinbutton', { name: 'Day' });
const years = screen.getAllByRole('spinbutton', { name: 'Year' });

await user.click(months[0]);
await user.keyboard('02');
await user.click(days[0]);
await user.keyboard('30');

expect(onError.lastCall.args[0]).to.deep.equal(['partiallyFilledDate', null]);

await user.click(years[0]);
await user.keyboard('2024');

expect(onError.lastCall.args[0][0]).not.to.equal('partiallyFilledDate');
expect(onError.lastCall.args[0][0]).to.equal('invalidDate');
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import * as React from 'react';
import { spy } from 'sinon';
import { MultiInputDateRangeField } from '@mui/x-date-pickers-pro/MultiInputDateRangeField';
import { screen } from '@mui/internal-test-utils';
import { createPickerRenderer } from 'test/utils/pickers';

describe('<MultiInputDateRangeField /> - partiallyFilledDate validation', () => {
const { render } = createPickerRenderer();

it('should call onError with partiallyFilledDate for start when start is partially filled', async () => {
const onError = spy();
const { user } = render(<MultiInputDateRangeField onError={onError} />);

const months = screen.getAllByRole('spinbutton', { name: 'Month' });
await user.click(months[0]);
await user.keyboard('01');

expect(onError.callCount).to.equal(1);
expect(onError.lastCall.args[0]).to.deep.equal(['partiallyFilledDate', null]);
});

it('should call onError with partiallyFilledDate for end when end is partially filled', async () => {
const onError = spy();
const { user } = render(<MultiInputDateRangeField onError={onError} />);

const months = screen.getAllByRole('spinbutton', { name: 'Month' });
await user.click(months[1]);
await user.keyboard('01');

expect(onError.callCount).to.equal(1);
expect(onError.lastCall.args[0]).to.deep.equal([null, 'partiallyFilledDate']);
});

it('should call onError with null when partially filled start is fully cleared', async () => {
const onError = spy();
const { user } = render(<MultiInputDateRangeField onError={onError} />);

const months = screen.getAllByRole('spinbutton', { name: 'Month' });
await user.click(months[0]);
await user.keyboard('01');
expect(onError.lastCall.args[0]).to.deep.equal(['partiallyFilledDate', null]);

await user.click(months[0]);
await user.keyboard('{Control>}a{/Control}');
await user.keyboard('{Delete}');

expect(onError.lastCall.args[0]).to.deep.equal([null, null]);
});

it('should show start field as invalid when start is partially filled', async () => {
const { user } = render(<MultiInputDateRangeField />);

const months = screen.getAllByRole('spinbutton', { name: 'Month' });
await user.click(months[0]);
await user.keyboard('01');

const roots = screen.getAllByRole('group');
expect(roots[0]).to.have.attribute('aria-invalid', 'true');
expect(roots[1]).to.have.attribute('aria-invalid', 'false');
});

it('should not show fields as invalid for empty fields', async () => {
render(<MultiInputDateRangeField />);

const roots = screen.getAllByRole('group');
expect(roots[0]).to.have.attribute('aria-invalid', 'false');
expect(roots[1]).to.have.attribute('aria-invalid', 'false');
});

it('should return invalidDate error when start is fully filled but date is invalid', async () => {
const onError = spy();
const { user } = render(<MultiInputDateRangeField onError={onError} />);

const months = screen.getAllByRole('spinbutton', { name: 'Month' });
const days = screen.getAllByRole('spinbutton', { name: 'Day' });
const years = screen.getAllByRole('spinbutton', { name: 'Year' });

await user.click(months[0]);
await user.keyboard('02');
await user.click(days[0]);
await user.keyboard('30');

expect(onError.lastCall.args[0]).to.deep.equal(['partiallyFilledDate', null]);

await user.click(years[0]);
await user.keyboard('2024');

expect(onError.lastCall.args[0][0]).not.to.equal('partiallyFilledDate');
expect(onError.lastCall.args[0][0]).to.equal('invalidDate');
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import * as React from 'react';
import { spy } from 'sinon';
import { SingleInputDateRangeField } from '@mui/x-date-pickers-pro/SingleInputDateRangeField';
import { screen } from '@mui/internal-test-utils';
import { createPickerRenderer, getFieldInputRoot } from 'test/utils/pickers';

describe('<SingleInputDateRangeField /> - partiallyFilledDate validation', () => {
const { render } = createPickerRenderer();

it('should call onError with partiallyFilledDate for start when start is partially filled', async () => {
const onError = spy();
const { user } = render(<SingleInputDateRangeField onError={onError} />);

const month = screen.getAllByRole('spinbutton', { name: 'Month' })[0];
await user.click(month);
await user.keyboard('01');

expect(onError.callCount).to.equal(1);
expect(onError.lastCall.args[0]).to.deep.equal(['partiallyFilledDate', null]);
});

it('should call onError with partiallyFilledDate for end when end is partially filled', async () => {
const onError = spy();
const { user } = render(<SingleInputDateRangeField onError={onError} />);

const months = screen.getAllByRole('spinbutton', { name: 'Month' });
const endMonth = months[1];
await user.click(endMonth);
await user.keyboard('01');

expect(onError.callCount).to.equal(1);
expect(onError.lastCall.args[0]).to.deep.equal([null, 'partiallyFilledDate']);
});

it('should call onError with null when partially filled start is fully cleared', async () => {
const onError = spy();
const { user } = render(<SingleInputDateRangeField onError={onError} />);

const month = screen.getAllByRole('spinbutton', { name: 'Month' })[0];
await user.click(month);
await user.keyboard('01');
expect(onError.lastCall.args[0]).to.deep.equal(['partiallyFilledDate', null]);

await user.click(month);
await user.keyboard('{Control>}a{/Control}');
await user.keyboard('{Delete}');

expect(onError.lastCall.args[0]).to.deep.equal([null, null]);
});

it('should show field as invalid when start is partially filled', async () => {
const { user } = render(<SingleInputDateRangeField />);

const month = screen.getAllByRole('spinbutton', { name: 'Month' })[0];
await user.click(month);
await user.keyboard('01');

expect(getFieldInputRoot()).to.have.attribute('aria-invalid', 'true');
});

it('should return invalidDate error when start is fully filled but date is invalid', async () => {
const onError = spy();
const { user } = render(<SingleInputDateRangeField onError={onError} />);

const months = screen.getAllByRole('spinbutton', { name: 'Month' });
const days = screen.getAllByRole('spinbutton', { name: 'Day' });
const years = screen.getAllByRole('spinbutton', { name: 'Year' });

await user.click(months[0]);
await user.keyboard('02');
await user.click(days[0]);
await user.keyboard('30');

expect(onError.lastCall.args[0]).to.deep.equal(['partiallyFilledDate', null]);

await user.click(years[0]);
await user.keyboard('2024');

expect(onError.lastCall.args[0][0]).not.to.equal('partiallyFilledDate');
expect(onError.lastCall.args[0][0]).to.equal('invalidDate');
});
});
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
'use client';
import * as React from 'react';
import {
PickerManagerError,
PickerManagerFieldInternalProps,
useControlledValue,
useFieldInternalPropsWithDefaults,
UseFieldReturnValue,
usePickerPrivateContext,
} from '@mui/x-date-pickers/internals';
import { useValidation } from '@mui/x-date-pickers/validation';
import { UseTextFieldBaseForwardedProps, useTextFieldProps } from './useTextFieldProps';
Expand Down Expand Up @@ -58,6 +61,7 @@ export function useMultiInputRangeField<
>(
parameters: UseMultiInputRangeFieldParameters<TManager, TTextFieldProps, TRootProps>,
): UseMultiInputRangeFieldReturnValue<TTextFieldProps, TRootProps> {
type TError = PickerManagerError<TManager>;
const { manager, internalProps, rootProps, startTextFieldProps, endTextFieldProps } = parameters;

const internalPropsWithDefaults = useFieldInternalPropsWithDefaults({
Expand Down Expand Up @@ -93,11 +97,14 @@ export function useMultiInputRangeField<
valueManager: manager.internal_valueManager,
});

const { isPartiallyFilled } = usePickerPrivateContext();

const validation = useValidation({
props: internalPropsWithDefaults,
value,
timezone,
validator: manager.validator,
isPartiallyFilled,
onError: internalPropsWithDefaults.onError,
});

Expand All @@ -119,7 +126,27 @@ export function useMultiInputRangeField<

const rootResponse = useMultiInputRangeFieldRootProps(rootProps);

const startTextFieldResponse = useTextFieldProps<TManager, TTextFieldProps>({
const startOnError = React.useCallback(
(error: any, val: any) => {
internalPropsWithDefaults.onError?.(
[error, validation.validationError[1]],
[val, internalPropsWithDefaults.value?.[1] ?? null],
);
},
[internalPropsWithDefaults, validation.validationError],
);

const endOnError = React.useCallback(
(error: any, val: any) => {
internalPropsWithDefaults.onError?.(
[validation.validationError[0], error],
[internalPropsWithDefaults.value?.[0] ?? null, val],
);
},
[internalPropsWithDefaults, validation.validationError],
);

const startTextFieldResponse = useTextFieldProps<TManager, TTextFieldProps, TError>({
valueType: manager.valueType,
position: 'start',
value,
Expand All @@ -129,9 +156,11 @@ export function useMultiInputRangeField<
forwardedProps: startTextFieldProps,
selectedSectionProps: selectedSectionsResponse.start,
sharedInternalProps,
isPartiallyFilled,
onError: startOnError,
});

const endTextFieldResponse = useTextFieldProps<TManager, TTextFieldProps>({
const endTextFieldResponse = useTextFieldProps<TManager, TTextFieldProps, TError>({
valueType: manager.valueType,
position: 'end',
value,
Expand All @@ -141,6 +170,8 @@ export function useMultiInputRangeField<
forwardedProps: endTextFieldProps,
selectedSectionProps: selectedSectionsResponse.end,
sharedInternalProps,
isPartiallyFilled,
onError: endOnError,
});

return {
Expand Down
Loading
Loading