diff --git a/packages/components/CHANGELOG.md b/packages/components/CHANGELOG.md
index 427e5aa90759a..7d0368940c27b 100644
--- a/packages/components/CHANGELOG.md
+++ b/packages/components/CHANGELOG.md
@@ -13,6 +13,7 @@
- `Dropdown` now has a `focusOnMount` prop which is passed directly to the contained `Popover`.
- `DatePicker` has new prop `isInvalidDate` exposing react-dates' `isOutsideRange`.
+- `DatePicker` allows `null` as accepted value for `currentDate` prop to signify no date selection.
## 7.0.5 (2019-01-03)
diff --git a/packages/components/src/date-time/README.md b/packages/components/src/date-time/README.md
index 1366be8312635..f0fa63c69b6ce 100644
--- a/packages/components/src/date-time/README.md
+++ b/packages/components/src/date-time/README.md
@@ -41,10 +41,11 @@ The component accepts the following props:
### currentDate
-The current date and time at initialization.
+The current date and time at initialization. Optionally pass in a `null` value to specify no date is currently selected.
- Type: `string`
-- Required: Yes
+- Required: No
+- Default: today's date
### onChange
diff --git a/packages/components/src/date-time/date.js b/packages/components/src/date-time/date.js
index 92be3a3970a90..a8e4130e739c8 100644
--- a/packages/components/src/date-time/date.js
+++ b/packages/components/src/date-time/date.js
@@ -25,6 +25,7 @@ class DatePicker extends Component {
onChangeMoment( newDate ) {
const { currentDate, onChange } = this.props;
+ // If currentDate is null, use now as momentTime to designate hours, minutes, seconds.
const momentDate = currentDate ? moment( currentDate ) : moment();
const momentTime = {
hours: momentDate.hours(),
@@ -35,10 +36,24 @@ class DatePicker extends Component {
onChange( newDate.set( momentTime ).format( TIMEZONELESS_FORMAT ) );
}
+ /**
+ * Create a Moment object from a date string. With no currentDate supplied, default to a Moment
+ * object representing now. If a null value is passed, return a null value.
+ *
+ * @param {?string} currentDate Date representing the currently selected date or null to signify no selection.
+ * @return {?Moment} Moment object for selected date or null.
+ */
+ getMomentDate( currentDate ) {
+ if ( null === currentDate ) {
+ return null;
+ }
+ return currentDate ? moment( currentDate ) : moment();
+ }
+
render() {
const { currentDate, isInvalidDate } = this.props;
- const momentDate = currentDate ? moment( currentDate ) : moment();
+ const momentDate = this.getMomentDate( currentDate );
return (
@@ -49,7 +64,7 @@ class DatePicker extends Component {
hideKeyboardShortcutsPanel
// This is a hack to force the calendar to update on month or year change
// https://github.com/airbnb/react-dates/issues/240#issuecomment-361776665
- key={ `datepicker-controller-${ momentDate.format( 'MM-YYYY' ) }` }
+ key={ `datepicker-controller-${ momentDate ? momentDate.format( 'MM-YYYY' ) : 'null' }` }
noBorder
numberOfMonths={ 1 }
onDateChange={ this.onChangeMoment }
diff --git a/packages/components/src/date-time/test/date.js b/packages/components/src/date-time/test/date.js
new file mode 100644
index 0000000000000..a12999dbbc784
--- /dev/null
+++ b/packages/components/src/date-time/test/date.js
@@ -0,0 +1,110 @@
+/**
+ * External dependencies
+ */
+import { shallow } from 'enzyme';
+import moment from 'moment';
+
+/**
+ * Internal dependencies
+ */
+import DatePicker from '../date';
+
+const TIMEZONELESS_FORMAT = 'YYYY-MM-DDTHH:mm:ss';
+
+describe( 'DatePicker', () => {
+ it( 'should pass down a moment object for currentDate', () => {
+ const currentDate = '1986-10-18T23:00:00';
+ const wrapper = shallow( );
+ const date = wrapper.children().props().date;
+ expect( moment.isMoment( date ) ).toBe( true );
+ expect( date.isSame( moment( currentDate ) ) ).toBe( true );
+ } );
+
+ it( 'should pass down a null date when currentDate is set to null', () => {
+ const wrapper = shallow( );
+ expect( wrapper.children().props().date ).toBeNull();
+ } );
+
+ it( 'should pass down a moment object for now when currentDate is undefined', () => {
+ const wrapper = shallow( );
+ const date = wrapper.children().props().date;
+ expect( moment.isMoment( date ) ).toBe( true );
+ expect( date.isSame( moment(), 'second' ) ).toBe( true );
+ } );
+
+ describe( 'getMomentDate', () => {
+ it( 'should return a Moment object representing a given date string', () => {
+ const currentDate = '1986-10-18T23:00:00';
+ const wrapper = shallow( );
+ const momentDate = wrapper.instance().getMomentDate( currentDate );
+
+ expect( moment.isMoment( momentDate ) ).toBe( true );
+ expect( momentDate.isSame( moment( currentDate ) ) ).toBe( true );
+ } );
+
+ it( 'should return null when given a null agrument', () => {
+ const currentDate = null;
+ const wrapper = shallow( );
+ const momentDate = wrapper.instance().getMomentDate( currentDate );
+
+ expect( momentDate ).toBeNull();
+ } );
+
+ it( 'should return a Moment object representing now when given an undefined argument', () => {
+ const wrapper = shallow( );
+ const momentDate = wrapper.instance().getMomentDate();
+
+ expect( moment.isMoment( momentDate ) ).toBe( true );
+ expect( momentDate.isSame( moment(), 'second' ) ).toBe( true );
+ } );
+ } );
+
+ describe( 'onChangeMoment', () => {
+ it( 'should call onChange with a formated date of the input', () => {
+ const onChangeSpy = jest.fn();
+ const currentDate = '1986-10-18T11:00:00';
+ const wrapper = shallow( );
+ const newDate = moment();
+
+ wrapper.instance().onChangeMoment( newDate );
+
+ expect( onChangeSpy ).toHaveBeenCalledWith( newDate.format( TIMEZONELESS_FORMAT ) );
+ } );
+
+ it( 'should call onChange with hours, minutes, seconds of the current time when currentDate is undefined', () => {
+ let onChangeSpyArgument;
+ const onChangeSpy = ( arg ) => onChangeSpyArgument = arg;
+ const wrapper = shallow( );
+ const newDate = moment( '1986-10-18T11:00:00' );
+ const current = moment();
+ const newDateWithCurrentTime = newDate
+ .clone()
+ .set( {
+ hours: current.hours(),
+ minutes: current.minutes(),
+ seconds: current.seconds(),
+ } );
+ wrapper.instance().onChangeMoment( newDate );
+
+ expect( moment( onChangeSpyArgument ).isSame( newDateWithCurrentTime, 'minute' ) ).toBe( true );
+ } );
+
+ it( 'should call onChange with hours, minutes, seconds of the current time when currentDate is null', () => {
+ let onChangeSpyArgument;
+ const onChangeSpy = ( arg ) => onChangeSpyArgument = arg;
+ const wrapper = shallow( );
+ const newDate = moment( '1986-10-18T11:00:00' );
+ const current = moment();
+ const newDateWithCurrentTime = newDate
+ .clone()
+ .set( {
+ hours: current.hours(),
+ minutes: current.minutes(),
+ seconds: current.seconds(),
+ } );
+ wrapper.instance().onChangeMoment( newDate );
+
+ expect( moment( onChangeSpyArgument ).isSame( newDateWithCurrentTime, 'minute' ) ).toBe( true );
+ } );
+ } );
+} );