Skip to content

DateTimePicker - invoke value change only on real change #3227

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
51 changes: 51 additions & 0 deletions src/components/dateTimePicker/__tests__/index.spec.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import React from 'react';
import {fireEvent, render} from '@testing-library/react-native';
import DateTimePicker, {DateTimePickerProps} from '../index';
import {act} from 'react-test-renderer';

const testID = 'dateTimePicker';
const TestCase = (props: Partial<Omit<DateTimePickerProps, 'dialogProps'>>) => {
const defaultProps: DateTimePickerProps = {
value: new Date('2021-04-04T00:00:00Z'),
migrateDialog: true,
testID
};
return <DateTimePicker {...defaultProps} {...props}/>;
};

const someDate = new Date('2021-04-04T00:00:00Z');
const someDateNextDay = new Date(someDate.getTime() + 24 * 60 * 60 * 1000);
const someDateNextHour = new Date(someDate.getTime() + 60 * 60 * 1000);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe:
sameTime \ sameDate
nextDay
nextHour

Copy link
Collaborator Author

@nitzanyiz nitzanyiz Sep 1, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wdym here?
Do you think date, nextHour, nextDay is better?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, sorry I was not clear - this is about naming.
Personally I think sameTime, nextDay and nextHour.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why would you call it sameDate? What is it similar to?
I feel like its pretty clear, it also states that the date its self is not important.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because it's the same date as the date of the value, it's not a random "some date" it's the same date and the next day after the value and so on.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I moved the date objects to the top of the file and used them in the default props.

jest.mock('../../../optionalDependencies', () => {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't this be in jest-setup.js?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure. jest-setup will mock this for every test file we have. I only see external modules in the jest-mock file i think its more organized this way.
Adding it to the jest-setup file also works. I guess we could do that. Do you think it will be clearer to put it there?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I think that's the proper place for it because if we'll have an issue in private we'll look there first for a mock.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What kind of issue will possibly have? When I you the jest mock here I don't think it has any effect on other test files. If I put it in the jest-setup one it does.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we'll need to mock the DateTimePickerPackage we'll probably look there and not here

const actualOptional = jest.requireActual('../../../optionalDependencies');
const view = jest.requireActual('../../view').default;
return {
...actualOptional,
DateTimePickerPackage: view
};
});

describe('DateTimePicker', () => {
test.each(['time', 'date'] as const)('should not invoke onChange when value is not changed - mode %s', mode => {
const onChange = jest.fn();
const renderTree = render(<TestCase onChange={onChange} mode={mode}/>);
expect(onChange).not.toHaveBeenCalled();
fireEvent.press(renderTree.getByTestId(`${testID}.overlay`));
fireEvent.press(renderTree.getByTestId(`${testID}.done`));
expect(onChange).not.toHaveBeenCalled();
});
test.each(['time', 'date'] as const)('should invoke onChange when value is changed - mode %s', async mode => {
const onChange = jest.fn();
const renderTree = render(<TestCase onChange={onChange} mode={mode} value={someDate}/>);
expect(onChange).not.toHaveBeenCalled();
act(() => {
fireEvent.press(renderTree.getByTestId(`${testID}.overlay`));
});
fireEvent(renderTree.getByTestId(`${testID}.picker`),
'onChange',
{type: 'set'},
mode === 'time' ? someDateNextHour : someDateNextDay);
fireEvent.press(renderTree.getByTestId(`${testID}.done`));
expect(onChange).toHaveBeenCalled();
});
});
20 changes: 17 additions & 3 deletions src/components/dateTimePicker/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -203,16 +203,30 @@ const DateTimePicker = forwardRef((props: DateTimePickerPropsInternal, ref: Forw
expandable.current?.toggleExpandable?.();
}, []);

const isValueChanged = useCallback(() => {
if (mode === 'time') {
return (
chosenDate.current?.getHours() !== value?.getHours() || chosenDate.current?.getMinutes() !== value?.getMinutes()
);
}
return (
chosenDate.current?.getFullYear() !== value?.getFullYear() ||
chosenDate.current?.getMonth() !== value?.getMonth() ||
chosenDate.current?.getDate() !== value?.getDate()
);
}, [mode, value]);

const onDonePressed = useCallback(() => {
toggleExpandableOverlay();
if (Constants.isIOS && !chosenDate.current) {
// since handleChange() is not called on iOS when there is no actual change
chosenDate.current = new Date();
}

onChange?.(chosenDate.current!);
if (isValueChanged() && chosenDate.current) {
onChange?.(chosenDate?.current);
}
setValue(chosenDate.current);
}, [toggleExpandableOverlay, onChange]);
}, [toggleExpandableOverlay, onChange, isValueChanged]);

const handleChange = useCallback((event: any = {}, date: Date) => {
// NOTE: will be called on Android even when there was no actual change
Expand Down