From 05356400baa8e05a9d7ea17e74fbb04bb3b23fa8 Mon Sep 17 00:00:00 2001 From: mmarkelov Date: Mon, 3 Feb 2020 16:47:47 +0300 Subject: [PATCH] [Fix] `SingleDatePicker`/`DateRangePicker`: Fix onChange validation --- src/components/DateRangePicker.jsx | 2 + .../DateRangePickerInputController.jsx | 8 ++- src/components/DayPickerKeyboardShortcuts.jsx | 2 +- src/components/SingleDatePicker.jsx | 2 + .../SingleDatePickerInputController.jsx | 5 +- .../DateRangePickerInputController_spec.jsx | 64 ++++++++++++++++++- test/components/DateRangePicker_spec.jsx | 10 +++ .../SingleDatePickerInputController_spec.jsx | 35 +++++++++- test/components/SingleDatePicker_spec.jsx | 15 +++++ 9 files changed, 137 insertions(+), 6 deletions(-) diff --git a/src/components/DateRangePicker.jsx b/src/components/DateRangePicker.jsx index 212e7f1196..7d6fc4988d 100644 --- a/src/components/DateRangePicker.jsx +++ b/src/components/DateRangePicker.jsx @@ -590,6 +590,7 @@ class DateRangePicker extends React.PureComponent { openDirection, phrases, isOutsideRange, + isDayBlocked, minimumNights, withPortal, withFullScreenPortal, @@ -640,6 +641,7 @@ class DateRangePicker extends React.PureComponent { reopenPickerOnClearDates={reopenPickerOnClearDates} keepOpenOnDateSelect={keepOpenOnDateSelect} isOutsideRange={isOutsideRange} + isDayBlocked={isDayBlocked} minimumNights={minimumNights} withFullScreenPortal={withFullScreenPortal} onDatesChange={onDatesChange} diff --git a/src/components/DateRangePickerInputController.jsx b/src/components/DateRangePickerInputController.jsx index 681beb2a48..8eda984b53 100644 --- a/src/components/DateRangePickerInputController.jsx +++ b/src/components/DateRangePickerInputController.jsx @@ -62,6 +62,7 @@ const propTypes = forbidExtraProps({ withFullScreenPortal: PropTypes.bool, minimumNights: nonNegativeInteger, isOutsideRange: PropTypes.func, + isDayBlocked: PropTypes.func, displayFormat: PropTypes.oneOfType([PropTypes.string, PropTypes.func]), onFocusChange: PropTypes.func, @@ -118,6 +119,7 @@ const defaultProps = { withFullScreenPortal: false, minimumNights: 1, isOutsideRange: (day) => !isInclusivelyAfterDay(day, moment()), + isDayBlocked: () => false, displayFormat: () => moment.localeData().longDateFormat('L'), onFocusChange() {}, @@ -167,6 +169,7 @@ export default class DateRangePickerInputController extends React.PureComponent const { startDate, isOutsideRange, + isDayBlocked, minimumNights, keepOpenOnDateSelect, onDatesChange, @@ -175,7 +178,7 @@ export default class DateRangePickerInputController extends React.PureComponent const endDate = toMomentObject(endDateString, this.getDisplayFormat()); const isEndDateValid = endDate - && !isOutsideRange(endDate) + && !isOutsideRange(endDate) && !isDayBlocked(endDate) && !(startDate && isBeforeDay(endDate, startDate.clone().add(minimumNights, 'days'))); if (isEndDateValid) { onDatesChange({ startDate, endDate }); @@ -210,6 +213,7 @@ export default class DateRangePickerInputController extends React.PureComponent let { endDate } = this.props; const { isOutsideRange, + isDayBlocked, minimumNights, onDatesChange, onFocusChange, @@ -220,7 +224,7 @@ export default class DateRangePickerInputController extends React.PureComponent const isEndDateBeforeStartDate = startDate && isBeforeDay(endDate, startDate.clone().add(minimumNights, 'days')); const isStartDateValid = startDate - && !isOutsideRange(startDate) + && !isOutsideRange(startDate) && !isDayBlocked(startDate) && !(disabled === END_DATE && isEndDateBeforeStartDate); if (isStartDateValid) { diff --git a/src/components/DayPickerKeyboardShortcuts.jsx b/src/components/DayPickerKeyboardShortcuts.jsx index 7e9b70014a..4c4f4e2174 100644 --- a/src/components/DayPickerKeyboardShortcuts.jsx +++ b/src/components/DayPickerKeyboardShortcuts.jsx @@ -214,7 +214,7 @@ class DayPickerKeyboardShortcuts extends React.PureComponent { topLeft && styles.DayPickerKeyboardShortcuts_showSpan__topLeft, )} > - ? + ? )} diff --git a/src/components/SingleDatePicker.jsx b/src/components/SingleDatePicker.jsx index 275736d1c9..656dc6566f 100644 --- a/src/components/SingleDatePicker.jsx +++ b/src/components/SingleDatePicker.jsx @@ -569,6 +569,7 @@ class SingleDatePicker extends React.PureComponent { keepOpenOnDateSelect, styles, isOutsideRange, + isDayBlocked, } = this.props; const { isInputFocused } = this.state; @@ -593,6 +594,7 @@ class SingleDatePicker extends React.PureComponent { showDefaultInputIcon={showDefaultInputIcon} inputIconPosition={inputIconPosition} isOutsideRange={isOutsideRange} + isDayBlocked={isDayBlocked} customCloseIcon={customCloseIcon} customInputIcon={customInputIcon} date={date} diff --git a/src/components/SingleDatePickerInputController.jsx b/src/components/SingleDatePickerInputController.jsx index 2da008ee16..ac65adcd54 100644 --- a/src/components/SingleDatePickerInputController.jsx +++ b/src/components/SingleDatePickerInputController.jsx @@ -54,6 +54,7 @@ const propTypes = forbidExtraProps({ keepOpenOnDateSelect: PropTypes.bool, reopenPickerOnClearDate: PropTypes.bool, isOutsideRange: PropTypes.func, + isDayBlocked: PropTypes.func, displayFormat: PropTypes.oneOfType([PropTypes.string, PropTypes.func]), onClose: PropTypes.func, @@ -98,6 +99,7 @@ const defaultProps = { keepOpenOnDateSelect: false, reopenPickerOnClearDate: false, isOutsideRange: (day) => !isInclusivelyAfterDay(day, moment()), + isDayBlocked: () => false, displayFormat: () => moment.localeData().longDateFormat('L'), onClose() {}, @@ -129,6 +131,7 @@ export default class SingleDatePickerInputController extends React.PureComponent onChange(dateString) { const { isOutsideRange, + isDayBlocked, keepOpenOnDateSelect, onDateChange, onFocusChange, @@ -136,7 +139,7 @@ export default class SingleDatePickerInputController extends React.PureComponent } = this.props; const newDate = toMomentObject(dateString, this.getDisplayFormat()); - const isValid = newDate && !isOutsideRange(newDate); + const isValid = newDate && !isOutsideRange(newDate) && !isDayBlocked(newDate); if (isValid) { onDateChange(newDate); if (!keepOpenOnDateSelect) { diff --git a/test/components/DateRangePickerInputController_spec.jsx b/test/components/DateRangePickerInputController_spec.jsx index 779450d8ec..a9b6566912 100644 --- a/test/components/DateRangePickerInputController_spec.jsx +++ b/test/components/DateRangePickerInputController_spec.jsx @@ -432,6 +432,37 @@ describe('DateRangePickerInputController', () => { }); }); + describe('is blocked', () => { + const futureDate = moment().add(7, 'days').format('DD/MM/YYYY'); + const isDayBlocked = sinon.stub().returns(true); + + it('calls props.onDatesChange', () => { + const onDatesChangeStub = sinon.stub(); + const wrapper = shallow(( + + )); + wrapper.instance().onEndDateChange(futureDate); + expect(onDatesChangeStub.callCount).to.equal(1); + }); + + it('calls props.onDatesChange with endDate === null', () => { + const onDatesChangeStub = sinon.stub(); + const wrapper = shallow(( + + )); + wrapper.instance().onEndDateChange(futureDate); + const args = onDatesChangeStub.getCall(0).args[0]; + expect(args.endDate).to.equal(null); + }); + }); + describe('is inclusively before state.startDate', () => { const startDate = moment(today).add(10, 'days'); const beforeStartDateString = today.toISOString(); @@ -720,7 +751,7 @@ describe('DateRangePickerInputController', () => { }); describe('is outside range', () => { - const futureDate = moment().add(7, 'days').toISOString(); + const futureDate = moment().add(7, 'days').format('DD/MM/YYYY'); const isOutsideRange = (day) => day > moment().add(5, 'days'); it('calls props.onDatesChange', () => { @@ -763,6 +794,37 @@ describe('DateRangePickerInputController', () => { expect(args.endDate).to.equal(today); }); }); + + describe('is blocked', () => { + const futureDate = moment().add(7, 'days').format('DD/MM/YYYY'); + const isDayBlocked = sinon.stub().returns(true); + + it('calls props.onDatesChange', () => { + const onDatesChangeStub = sinon.stub(); + const wrapper = shallow(( + + )); + wrapper.instance().onStartDateChange(futureDate); + expect(onDatesChangeStub.callCount).to.equal(1); + }); + + it('calls props.onDatesChange with startDate === null', () => { + const onDatesChangeStub = sinon.stub(); + const wrapper = shallow(( + + )); + wrapper.instance().onStartDateChange(futureDate); + const args = onDatesChangeStub.getCall(0).args[0]; + expect(args.startDate).to.equal(null); + }); + }); }); describe('#onStartDateFocus', () => { diff --git a/test/components/DateRangePicker_spec.jsx b/test/components/DateRangePicker_spec.jsx index deadee3453..e08f19c0f9 100644 --- a/test/components/DateRangePicker_spec.jsx +++ b/test/components/DateRangePicker_spec.jsx @@ -110,6 +110,16 @@ describe('DateRangePicker', () => { }); }); + describe('props.isDayBlocked is defined', () => { + it('should pass props.isDayBlocked to ', () => { + const isDayBlocked = sinon.stub(); + const wrapper = shallow(( + + )).dive(); + expect(wrapper.find(DateRangePickerInputController).prop('isDayBlocked')).to.equal(isDayBlocked); + }); + }); + describe('props.appendToBody', () => { it('renders inside ', () => { const wrapper = shallow(( diff --git a/test/components/SingleDatePickerInputController_spec.jsx b/test/components/SingleDatePickerInputController_spec.jsx index af304dc712..4e6407934c 100644 --- a/test/components/SingleDatePickerInputController_spec.jsx +++ b/test/components/SingleDatePickerInputController_spec.jsx @@ -203,7 +203,7 @@ describe('SingleDatePickerInputController', () => { describe('date string outside range', () => { const isOutsideRangeStub = sinon.stub().returns(true); - const todayDateString = today.toISOString(); + const todayDateString = today.format('DD/MM/YYYY'); it('calls props.onDateChange once', () => { const onDateChangeStub = sinon.stub(); @@ -247,6 +247,39 @@ describe('SingleDatePickerInputController', () => { expect(onFocusChangeStub.callCount).to.equal(0); }); }); + + describe('date string is blocked', () => { + const isDayBlocked = sinon.stub().returns(true); + const todayDateString = today.format('DD/MM/YYYY'); + + it('calls props.onDateChange once', () => { + const onDateChangeStub = sinon.stub(); + const wrapper = shallow(( + {}} + isDayBlocked={isDayBlocked} + /> + )); + wrapper.instance().onChange(todayDateString); + expect(onDateChangeStub.callCount).to.equal(1); + }); + + it('calls props.onDateChange with null as arg', () => { + const onDateChangeStub = sinon.stub(); + const wrapper = shallow(( + {}} + isDayBlocked={isDayBlocked} + /> + )); + wrapper.instance().onChange(todayDateString); + expect(onDateChangeStub.getCall(0).args[0]).to.equal(null); + }); + }); }); describe('#onFocus', () => { diff --git a/test/components/SingleDatePicker_spec.jsx b/test/components/SingleDatePicker_spec.jsx index 3b05e5e421..bd2ddff8e0 100644 --- a/test/components/SingleDatePicker_spec.jsx +++ b/test/components/SingleDatePicker_spec.jsx @@ -56,6 +56,21 @@ describe('SingleDatePicker', () => { expect(wrapper.find(SingleDatePickerInputController).prop('isOutsideRange')).to.equal(isOutsideRange); }); }); + + describe('props.isDayBlocked is defined', () => { + it('should pass props.isDayBlocked to ', () => { + const isDayBlocked = sinon.stub(); + const wrapper = shallow(( + {}} + onFocusChange={() => {}} + isDayBlocked={isDayBlocked} + /> + )).dive(); + expect(wrapper.find(SingleDatePickerInputController).prop('isDayBlocked')).to.equal(isDayBlocked); + }); + }); }); describe('DayPickerSingleDateController', () => {