diff --git a/docs/gatsby-node.js b/docs/gatsby-node.js index 41038318aa..c53ea10632 100644 --- a/docs/gatsby-node.js +++ b/docs/gatsby-node.js @@ -44,6 +44,10 @@ exports.modifyWebpackConfig = ({ config, stage }) => { __dirname, '../src/DayPickerInput.js' ), + 'react-day-picker/moment$': path.resolve( + __dirname, + '../src/addons/MomentLocaleUtils.js' + ), }, }, }); diff --git a/docs/src/code-samples/examples/input-custom-overlay.js b/docs/src/code-samples/examples/input-custom-overlay.js index 005f460442..798136c154 100644 --- a/docs/src/code-samples/examples/input-custom-overlay.js +++ b/docs/src/code-samples/examples/input-custom-overlay.js @@ -1,8 +1,5 @@ import React from 'react'; - import DayPickerInput from 'react-day-picker/DayPickerInput'; -import { LocaleUtils } from 'react-day-picker'; - import 'react-day-picker/lib/style.css'; function CustomOverlay({ classNames, selectedDay, children }) { @@ -23,7 +20,6 @@ function CustomOverlay({ classNames, selectedDay, children }) { export default function Example() { return ( { + this.setState({ from }, () => { if (!this.state.to) { this.focusTo(); } }); } - handleToChange(day) { - const to = day ? day.toDate() : undefined; + handleToChange(to) { this.setState({ to }, this.showFromMonth); } render() { - const format = 'YYYY-MM-DD'; const { from, to } = this.state; - const fromFormatted = from ? moment(from).format(format) : ''; - const toFormatted = to ? moment(to).format(format) : ''; const modifiers = { start: from, end: to }; return (
{' '} —{' '} (this.to = el)} - value={toFormatted} - onDayChange={this.handleToChange} + value={to} placeholder="To" - format={format} - hideOnDayClick={false} + format="LL" + formatDate={formatDate} + parseDate={parseDate} dayPickerProps={{ selectedDays: [from, { from, to }], disabledDays: { before: from }, @@ -82,6 +82,7 @@ export default class Example extends React.Component { fromMonth: from, numberOfMonths: 2, }} + onDayChange={this.handleToChange} /> @@ -105,7 +106,7 @@ export default class Example extends React.Component { width: 550px; } .InputFromTo-to .DayPickerInput-Overlay { - margin-left: -198px; + margin-left: -224.5px; } `} diff --git a/docs/src/code-samples/examples/input-moment.js b/docs/src/code-samples/examples/input-moment.js new file mode 100644 index 0000000000..f7a1d148c6 --- /dev/null +++ b/docs/src/code-samples/examples/input-moment.js @@ -0,0 +1,37 @@ +import React from 'react'; + +import DayPickerInput from 'react-day-picker/DayPickerInput'; +import 'react-day-picker/lib/style.css'; + +import MomentLocaleUtils, { + formatDate, + parseDate, +} from 'react-day-picker/moment'; + +import 'moment/locale/it'; + +export default function Example() { + return ( +
+

In English (default):

+ +

+ In Italian, using {'format="LL"'}: +

+ +
+ ); +} diff --git a/docs/src/code-samples/examples/input-state.js b/docs/src/code-samples/examples/input-state.js index 2a51e9ecc1..04cd79e292 100644 --- a/docs/src/code-samples/examples/input-state.js +++ b/docs/src/code-samples/examples/input-state.js @@ -1,5 +1,4 @@ import React from 'react'; -import moment from 'moment'; import DayPickerInput from 'react-day-picker/DayPickerInput'; import 'react-day-picker/lib/style.css'; @@ -15,29 +14,25 @@ export default class Example extends React.Component { handleDayChange(selectedDay, modifiers) { this.setState({ selectedDay, - isDisabled: modifiers.disabled, + isDisabled: modifiers.disabled === true, }); } render() { const { selectedDay, isDisabled } = this.state; - const formattedDay = selectedDay - ? moment(selectedDay).format('DD/MM/YYYY') - : ''; return (

{!selectedDay && '🤔 Type or pick a valid day'} {selectedDay && isDisabled && '😡 This day is disabled'} - {selectedDay && !isDisabled && `😄 You chose ${formattedDay}`} + {selectedDay && + !isDisabled && + `😄 You chose ${selectedDay.toLocaleDateString()}`}

Please type a day:

- console.log(day)} - placeholder="MM/DD/YYYY" - /> + console.log(day)} />
); } diff --git a/docs/src/code-samples/input-01.js b/docs/src/code-samples/input-01.js index de9e0c5d24..3969fe0203 100644 --- a/docs/src/code-samples/input-01.js +++ b/docs/src/code-samples/input-01.js @@ -17,13 +17,9 @@ export default class MyForm extends React.Component { const { selectedDay } = this.state; return (
- - {selectedDay &&

Selected: {

{selectedDay.format('LLL')}

}

} + {selectedDay &&

Day: {selectedDay.toLocaleDateString()}

} + {!selectedDay &&

Choose a day

} +
); } diff --git a/docs/src/code-samples/input-02.js b/docs/src/code-samples/input-02.js index 8a052caf3a..77d9457383 100644 --- a/docs/src/code-samples/input-02.js +++ b/docs/src/code-samples/input-02.js @@ -5,12 +5,10 @@ import 'react-day-picker/lib/style.css'; export default function MyDatePicker() { return ( ); diff --git a/docs/src/containers/DocPage.js b/docs/src/containers/DocPage.js index 88fa407e73..cdf9fbe17a 100644 --- a/docs/src/containers/DocPage.js +++ b/docs/src/containers/DocPage.js @@ -23,7 +23,7 @@ export default function DocPage({ title, children }) { Matching days Styling Localization - Using the input field + DayPickerInput DayPicker diff --git a/docs/src/containers/ExamplePage.js b/docs/src/containers/ExamplePage.js index 45339484ab..955f7bdd79 100644 --- a/docs/src/containers/ExamplePage.js +++ b/docs/src/containers/ExamplePage.js @@ -89,6 +89,9 @@ export default function ExamplePage({ children, title }) { Customize the overlay + + Using moment.js to parse and format dates + Range with two inputs diff --git a/docs/src/pages/api/DayPickerInput.js b/docs/src/pages/api/DayPickerInput.js index 50f480c25e..bd54a4680f 100644 --- a/docs/src/pages/api/DayPickerInput.js +++ b/docs/src/pages/api/DayPickerInput.js @@ -18,11 +18,12 @@ export default () => ( clickUnselectsDay,{' '} component,{' '} dayPickerProps, format,{' '} + formatDate,{' '} hideOnDayClick,{' '} inputProps,{' '} overlayComponent,{' '} - placeholder,{' '} - showOverlay + parseDate, placeholder,{' '} + showOverlay, value

Event handlers

@@ -108,15 +109,45 @@ function MyDayPickerInput(props) {

- format string | [string] + format string

- The format strings used for parsing the date entered in the input field. - It accepts all the{' '} - - format strings + The format strings used for formatting and parsing dates. It works with{' '} + + parseDate {' '} - used by moment.js. + and{' '} + + formatDate + +

+ +

+ + formatDate{' '} + (date: Date?, format: string?, locale: string?) ⇒ string +

+

+ Date formatter used for displaying the selected date as value of the + input field. As default, it returns dates formatted as{' '} + YYYY-M-D.
+ Arguments: format is the value coming from the{' '} + + format + {' '} + prop, while locale is from{' '} + + dayPickerProps + .
+ See also{' '} + + parseDate + . +

+

+ If you are using moment.js in your + app, we already provide this function as addon: see{' '} + this example.

@@ -159,6 +190,34 @@ function MyDayPickerInput(props) {

See also this example.

+ +

+ + parseDate{' '} + (str: string?, format: string?, locale: string?) ⇒ string +

+

+ Date parser used for parsing the string typed in the input field. As + default, it parses only dates formatted as YYYY-M-D. +
+ Arguments: format is the value coming from the{' '} + + format + {' '} + prop, while locale is from{' '} + + dayPickerProps + .
See also{' '} + + formatDate + . +

+

+ If you are using moment.js in your + app, we already provide this function as addon: see{' '} + this example. +

+

placeholder string @@ -174,6 +233,13 @@ function MyDayPickerInput(props) { Show the overlay during the initial rendering of the component. This is useful if you want to keep the overlay visibile while styling it.

+

+ + value string | Date +

+

+ The value of the input field. +


Event handlers

diff --git a/docs/src/pages/docs/getting-started.js b/docs/src/pages/docs/getting-started.js index e693fc2bef..2a4c3fb42a 100644 --- a/docs/src/pages/docs/getting-started.js +++ b/docs/src/pages/docs/getting-started.js @@ -37,14 +37,6 @@ export default () => ( $ yarn add react-day-picker`} -
-

- If you want to use the{' '} - DayPickerInput component, you must - install moment.js as well. -

-
-

Then import the component and its style into your component:

diff --git a/docs/src/pages/docs/input.js b/docs/src/pages/docs/input.js index 29342deee6..a13fa962c7 100644 --- a/docs/src/pages/docs/input.js +++ b/docs/src/pages/docs/input.js @@ -6,36 +6,28 @@ import CodeBlock from '../../ui/CodeBlock'; import CodeSample from '../../ui/CodeSample'; export default () => ( - +

- The package includes {``}, a component - rendering an input field and displaying react-day-picker in an overlay. + The package includes{' '} + + {``} + , a component rendering an input field and displaying + react-day-picker in an overlay.

-

Add moment.js dependency

-

- moment.js is required as it is used to - validate and format the date typed by the user. Make sure you have - installed it in your dependencies: -

- {`npm install moment --save -# or with yarn -yarn add moment`} -

Import the component

{`import DayPickerInput from 'react-day-picker/DayPickerInput';`}

- If you are using unpkg, the component is - available as DayPicker.Input global variable: + + If you are using unpkg, the component + is available as DayPicker.Input global variable. +

- {` - -`} -

Example

+

+ In its simple form, you use DayPickerInput to get the day + typed in the input field, or the day picked in the calendar: +

Customizing the DayPicker

@@ -46,13 +38,34 @@ yarn add moment`}

-

Localization

+

Changing the date format

+

- You can use the locale and format methods of moment.js to format the value - displayed in the input field. Remember to add the locale{' '} - value to the dayPickerProps. + As default, the date format is the ”ugly” YYYY-M-D. This is + because parsing JavaScript dates from strings is not an easy task, and we + don’t want to depend on an external library for doing this.

-

For example, this implementation display the input field in Italian:

- + +

+ To customize the way dates are parsed and formatted, for example using + your date library of choice, use the{' '} + + parseDate + {' '} + and the{' '} + + formatDate + {' '} + props. +

+ +

Use moment.js to parse and format dates

+

+ If you use moment.js, we provide an + utility for using its parser and formatter functions. See{' '} + this example. +

+ + ); diff --git a/docs/src/pages/docs/localization.js b/docs/src/pages/docs/localization.js index 12574b250f..d1a8ee42d9 100644 --- a/docs/src/pages/docs/localization.js +++ b/docs/src/pages/docs/localization.js @@ -2,6 +2,7 @@ import React from 'react'; import Link from 'gatsby-link'; import DocPage from '../../containers/DocPage'; +import Anchor from '../../ui/Anchor'; import CodeSample from '../../ui/CodeSample'; import NextButton from '../../ui/NextButton'; @@ -40,7 +41,9 @@ export default () => ( -

Localization with moment.js

+

+ Localization with moment.js +

If you already include moment.js in your dependencies, you may find convenient to use moment’s translation @@ -87,6 +90,6 @@ export default () => ( - + ); diff --git a/docs/src/pages/examples/input-moment.js b/docs/src/pages/examples/input-moment.js new file mode 100644 index 0000000000..f71b175aa4 --- /dev/null +++ b/docs/src/pages/examples/input-moment.js @@ -0,0 +1,26 @@ +import React from 'react'; +import Link from 'gatsby-link'; + +import ExamplePage from '../../containers/ExamplePage'; +import CodeSample from '../../ui/CodeSample'; + +export default () => ( + +

+ Use the{' '} + + parseDate + {' '} + prop to parse the input typed by the user, and the{' '} + + formatDate + {' '} + prop to format them. +

+

+ In the following example, we are importing the functions included in the + react-day-picker/moment package, that use moment.js. +

+ + +); diff --git a/docs/src/pages/examples/input.js b/docs/src/pages/examples/input.js index be5560beed..4e435f41f3 100644 --- a/docs/src/pages/examples/input.js +++ b/docs/src/pages/examples/input.js @@ -7,9 +7,13 @@ export default () => (

DayPickerInput binds the DayPicker - with an input field. See also how to{' '} - use it with state and how to{' '} - customize the overlay. + with an input field. +

+

+ See also how to use it with state, + how to{' '} + customize the overlay and + how to change the date format.

diff --git a/docs/src/pages/index.js b/docs/src/pages/index.js index ee1bf3ae0f..6f94f62d0b 100644 --- a/docs/src/pages/index.js +++ b/docs/src/pages/index.js @@ -13,6 +13,7 @@ import styles from './index.module.scss'; import FeatureModifiers from '../code-samples/examples/modifiers-styles'; import FeatureInput from '../code-samples/examples/input'; +import FeatureInput2 from '../code-samples/examples/input-from-to'; import FeatureLocalization from '../code-samples/examples/localization'; import FeatureRange from '../code-samples/examples/selected-range'; @@ -35,7 +36,8 @@ export default function HomePage() { React

- Flexible, highly customizable, localizable and with ARIA support. + Flexible, highly customizable, localizable, with ARIA support, no + external dependencies, ~9KB gzipped

Read the docs @@ -50,7 +52,7 @@ export default function HomePage() {

Style days with modifiers

Define the disabled or selected days and change the aspect of each - day cell with modifiers. + day cell with modifiers.

@@ -60,20 +62,19 @@ export default function HomePage() {

Works with input fields

Display the date picker in an overlay using the{' '} - DayPickerInput component.{' '} - - Requires moment.js. - + DayPickerInput component.

+

...or choose a range of days:

+

Localizable

- Use your own translation strings or import those from moment.js - (if you use it). + Use your own translation strings or import those from moment.js, + if you use it.

@@ -82,7 +83,9 @@ export default function HomePage() {

Select range of days

- Specify which days should be selected in your component’s state (source). + Specify which days should be selected in your component’s state ( + source + ).

diff --git a/src/DayPickerInput.js b/src/DayPickerInput.js index 00f4d98579..34233800ed 100644 --- a/src/DayPickerInput.js +++ b/src/DayPickerInput.js @@ -1,25 +1,62 @@ import React from 'react'; import PropTypes from 'prop-types'; -import moment from 'moment'; // eslint-disable-line import/no-extraneous-dependencies import DayPicker from './DayPicker'; import { getModifiersForDay } from './ModifiersUtils'; -import MomentLocaleUtils from './addons/MomentLocaleUtils'; import { ESC } from './keys'; // When clicking on a day cell, overlay will be hidden after this timeout export const HIDE_TIMEOUT = 100; +function isDate(date) { + return date instanceof Date && !isNaN(date.valueOf()); +} + +export function defaultFormat(d) { + if (isDate(d)) { + const year = d.getFullYear(); + const month = `${d.getMonth() + 1}`; + const day = `${d.getDate()}`; + return `${year}-${month}-${day}`; + } + return ''; +} + +export function defaultParse(str) { + if (typeof str !== 'string') { + return undefined; + } + const split = str.split('-'); + if (split.length !== 3) { + return undefined; + } + const year = parseInt(split[0], 10); + const month = parseInt(split[1], 10) - 1; + const day = parseInt(split[2], 10); + if ( + isNaN(year) || + isNaN(month) || + isNaN(day) || + day <= 0 || + day > 31 || + month <= 0 || + month >= 12 + ) { + return undefined; + } + + return new Date(year, month, day); +} + export default class DayPickerInput extends React.Component { static propTypes = { - value: PropTypes.string, + value: PropTypes.oneOfType([PropTypes.string, PropTypes.instanceOf(Date)]), inputProps: PropTypes.object, placeholder: PropTypes.string, - format: PropTypes.oneOfType([ - PropTypes.arrayOf(PropTypes.string), - PropTypes.string, - ]), + format: PropTypes.string, + formatDate: PropTypes.func, + parseDate: PropTypes.func, showOverlay: PropTypes.bool, dayPickerProps: PropTypes.object, @@ -45,7 +82,10 @@ export default class DayPickerInput extends React.Component { static defaultProps = { dayPickerProps: {}, value: '', + placeholder: 'YYYY-M-D', format: 'L', + formatDate: defaultFormat, + parseDate: defaultParse, showOverlay: false, hideOnDayClick: true, clickUnselectsDay: false, @@ -86,7 +126,7 @@ export default class DayPickerInput extends React.Component { const selectedDaysFromProps = this.props.dayPickerProps.selectedDays; const nextSelectedDaysFromProps = nextProps.dayPickerProps.selectedDays; - const nextValue = nextProps.value; + let nextValue = nextProps.value; const currentValue = this.props.value; const monthChanged = @@ -96,7 +136,16 @@ export default class DayPickerInput extends React.Component { nextMonthFromProps.getMonth() !== monthFromProps.getMonth())); if (nextValue !== currentValue) { - this.setState({ value: nextValue }); + if (isDate(nextValue)) { + nextValue = this.props.formatDate( + nextValue, + this.props.format, + this.props.dayPickerProps.locale + ); + } + this.setState({ + value: nextValue, + }); } if (monthChanged) { this.setState({ month: nextMonthFromProps }); @@ -113,13 +162,19 @@ export default class DayPickerInput extends React.Component { } getStateFromProps(props) { - const { dayPickerProps, format, value } = props; + const { dayPickerProps, formatDate, format } = props; + let { value } = props; let month; - if (value) { - // Display the month specified in the value prop - const m = moment(value, format, true); - if (m.isValid()) { - month = m.toDate(); + let day; + if (props.value) { + if (isDate(props.value)) { + day = props.value; + value = formatDate(props.value, format, dayPickerProps.locale); + } else { + day = props.parseDate(props.value, format, dayPickerProps.locale); + } + if (day) { + month = day; } } else { // Otherwise display the month coming from `dayPickerProps` or the current month @@ -175,7 +230,7 @@ export default class DayPickerInput extends React.Component { }, {} ); - onDayChange(moment(day), modifiers); + onDayChange(day, modifiers); }); } @@ -248,13 +303,18 @@ export default class DayPickerInput extends React.Component { } handleInputChange(e) { - const { inputProps, onDayChange, format } = this.props; + const { + dayPickerProps, + format, + inputProps, + onDayChange, + parseDate, + } = this.props; if (inputProps.onChange) { e.persist(); inputProps.onChange(e); } const { value } = e.target; - const m = moment(value, format, true); if (value.trim() === '') { this.setState({ value }); if (onDayChange) { @@ -262,11 +322,11 @@ export default class DayPickerInput extends React.Component { } return; } - if (!m.isValid()) { + const day = parseDate(value, format, dayPickerProps.locale); + if (!day) { this.setState({ value }); return; } - const day = m.toDate(); this.updateState(day, value); } @@ -284,6 +344,7 @@ export default class DayPickerInput extends React.Component { clickUnselectsDay, dayPickerProps, onDayChange, + formatDate, format, } = this.props; if (dayPickerProps.onDayClick) { @@ -303,7 +364,7 @@ export default class DayPickerInput extends React.Component { selectedDays = selectedDays.slice(0); const selectedDayIdx = selectedDays.indexOf(day); selectedDays.splice(selectedDayIdx, 1); - } else if (moment(selectedDays).isValid()) { + } else if (selectedDays) { selectedDays = null; } this.setState({ value: '', selectedDays }, this.hideAfterDayClick); @@ -313,24 +374,29 @@ export default class DayPickerInput extends React.Component { return; } - const m = moment(day).locale(dayPickerProps.locale || 'en'); - const value = m.format(typeof format === 'string' ? format : format[0]); + const value = formatDate(day, format, dayPickerProps.locale); this.setState({ value, month: day }, () => { if (onDayChange) { - onDayChange(m, modifiers); + onDayChange(day, modifiers); } this.hideAfterDayClick(); }); } renderOverlay() { - const { format, classNames, dayPickerProps } = this.props; + const { + classNames, + dayPickerProps, + parseDate, + formatDate, + format, + } = this.props; const { selectedDays, value } = this.state; let selectedDay; if (!selectedDays && value) { - const m = moment(value, format, true); - if (m.isValid()) { - selectedDay = m.toDate(); + const day = parseDate(value, format, dayPickerProps.locale); + if (day) { + selectedDay = day; } } else if (selectedDays) { selectedDay = selectedDays; @@ -341,9 +407,7 @@ export default class DayPickerInput extends React.Component { onTodayButtonClick = () => this.updateState( new Date(), - moment() - .locale(dayPickerProps.locale || 'en') - .format(this.props.format), + formatDate(new Date(), format, dayPickerProps.locale), this.hideAfterDayClick ); } @@ -357,7 +421,6 @@ export default class DayPickerInput extends React.Component { > (this.daypicker = el)} - localeUtils={MomentLocaleUtils} onTodayButtonClick={onTodayButtonClick} {...dayPickerProps} month={this.state.month} diff --git a/src/addons/MomentLocaleUtils.js b/src/addons/MomentLocaleUtils.js index 1fd1051afa..8f921386c8 100644 --- a/src/addons/MomentLocaleUtils.js +++ b/src/addons/MomentLocaleUtils.js @@ -46,6 +46,20 @@ export function getMonths(locale = 'en') { return months; } +export function formatDate(date, format = 'L', locale = 'en') { + return moment(date) + .locale(locale) + .format(format); +} + +export function parseDate(str, format = 'L', locale = 'en') { + const m = moment(str, format, locale); + if (m.isValid()) { + return m.toDate(); + } + return undefined; +} + export default { formatDay, formatMonthTitle, @@ -53,4 +67,6 @@ export default { formatWeekdayLong, getFirstDayOfWeek, getMonths, + formatDate, + parseDate, }; diff --git a/test/addons/MomentLocaleUtils.js b/test/addons/MomentLocaleUtils.js index b68d64b3c6..34fca8a892 100644 --- a/test/addons/MomentLocaleUtils.js +++ b/test/addons/MomentLocaleUtils.js @@ -54,4 +54,44 @@ describe('MomentLocaleUtils', () => { expect(MomentLocaleUtils.getMonths()).toHaveLength(12); }); }); + + describe('getMonths', () => { + it('return twelve months for it locale', () => { + const months = MomentLocaleUtils.getMonths('it'); + expect(months).toHaveLength(12); + expect(months[0]).toBe('gennaio'); + }); + it('return twelve months for default locale', () => { + expect(MomentLocaleUtils.getMonths()).toHaveLength(12); + }); + }); + + describe('formatDate', () => { + it('format a date', () => { + const formatted = MomentLocaleUtils.formatDate(new Date(2018, 1, 20)); + expect(formatted).toBe('02/20/2018'); + }); + it('format a date according to locale and format', () => { + const formatted = MomentLocaleUtils.formatDate( + new Date(2018, 1, 20), + 'LL', + 'it' + ); + expect(formatted).toBe('20 febbraio 2018'); + }); + }); + describe('parseDate', () => { + it('parse a date', () => { + const parsed = MomentLocaleUtils.parseDate(new Date(2018, 1, 20)); + expect(parsed).toEqual(new Date(2018, 1, 20)); + }); + it('parse a date according to locale and format', () => { + const parsed = MomentLocaleUtils.parseDate('02/20/2018'); + expect(parsed).toEqual(new Date(2018, 1, 20)); + }); + it('return undefined if date is not valid', () => { + const parsed = MomentLocaleUtils.parseDate('20 foo 2018', 'LL', 'it'); + expect(parsed).toBeUndefined(); + }); + }); }); diff --git a/test/daypickerinput/events.js b/test/daypickerinput/events.js index 8dc46dd348..8103a29ea4 100644 --- a/test/daypickerinput/events.js +++ b/test/daypickerinput/events.js @@ -2,6 +2,8 @@ import React from 'react'; import { mount } from 'enzyme'; +import moment from 'moment'; + import * as keys from '../../src/keys'; import DayPickerInput, { HIDE_TIMEOUT } from '../../src/DayPickerInput'; @@ -102,13 +104,13 @@ describe('DayPickerInput', () => { const input = wrapper.find('input'); wrapper.instance().showDayPicker(); wrapper.update(); - input.simulate('change', { target: { value: '12/16/2013' } }); + input.simulate('change', { target: { value: '2013-12-16' } }); expect(wrapper.find('.DayPicker-Caption').first()).toHaveText( 'December 2013' ); - input.simulate('change', { target: { value: '11/10/2015' } }); + input.simulate('change', { target: { value: '2015-10-11' } }); expect(wrapper.find('.DayPicker-Caption').first()).toHaveText( - 'November 2015' + 'October 2015' ); }); it('should call `onDayChange` with modifiers', () => { @@ -127,9 +129,9 @@ describe('DayPickerInput', () => { const input = wrapper.find('input'); wrapper.instance().showDayPicker(); wrapper.update(); - input.simulate('change', { target: { value: '12/20/2015' } }); + input.simulate('change', { target: { value: '2015-12-20' } }); expect(onDayChange).toHaveBeenCalledTimes(1); - expect(onDayChange.mock.calls[0][0].format('L')).toBe('12/20/2015'); + expect(onDayChange.mock.calls[0][0]).toEqual(new Date(2015, 11, 20)); expect(onDayChange.mock.calls[0][1]).toEqual({ foo: true, selected: true, @@ -176,7 +178,7 @@ describe('DayPickerInput', () => { .find('.DayPicker-Day') .at(10) .simulate('click'); - expect(wrapper.find('input')).toHaveProp('value', '02/08/2017'); + expect(wrapper.find('input')).toHaveProp('value', '2017-2-8'); expect(wrapper.find('.DayPicker-Caption')).toHaveText('February 2017'); expect(wrapper.find('.DayPicker-Day--selected')).toHaveText('8'); }); @@ -197,27 +199,10 @@ describe('DayPickerInput', () => { .find('.DayPicker-Day') .at(10) .simulate('click'); - expect(onDayChange.mock.calls[0][0].format('L')).toBe('02/08/2017'); - expect(onDayChange.mock.calls[0][1]).toEqual({ foo: true }); - }); - it('should work also when `format` is an array', () => { - const onDayChange = jest.fn(); - const wrapper = mount( - + expect(moment(onDayChange.mock.calls[0][0]).format('L')).toBe( + '02/08/2017' ); - wrapper.instance().showDayPicker(); - wrapper.update(); - wrapper - .find('.DayPicker-Day') - .at(10) - .simulate('click'); - expect(onDayChange.mock.calls[0][0].format('L')).toBe('02/08/2017'); + expect(onDayChange.mock.calls[0][1]).toEqual({ foo: true }); }); it('should hide the day picker when clicking on a day', done => { const wrapper = mount(); @@ -245,14 +230,14 @@ describe('DayPickerInput', () => { expect(wrapper.instance().hideTimeout).toBeNull(); expect(wrapper.find('.DayPicker')).toBeDefined(); }); - it('should unselect the clicked day if already selected', () => { + it('should unselect the clicked day if already selected (clickUnselectsDay)', () => { const wrapper = mount( ); @@ -265,14 +250,14 @@ describe('DayPickerInput', () => { expect(wrapper.find('input')).toHaveProp('value', ''); expect(wrapper.find('.DayPicker-Day--selected')).toHaveLength(0); }); - it('should unselect the clicked day if already selected', () => { + it('should unselect the clicked day if already selected (clickUnselectsDay)', () => { const wrapper = mount( ); @@ -289,7 +274,7 @@ describe('DayPickerInput', () => { const onDayChange = jest.fn(); const wrapper = mount( { const onDayChange = jest.fn(); const wrapper = mount( { + describe('methods', () => { + it('getInput should return the input field', () => { + const wrapper = mount(); + const instance = wrapper.instance(); + expect(instance.getInput().tagName.toLowerCase()).toBe('input'); + }); + it('getDayPicker should return the input field', () => { + const wrapper = mount(); + const instance = wrapper.instance(); + instance.showDayPicker(); + wrapper.update(); + expect(instance.getDayPicker()).toBeInstanceOf(DayPicker); + }); + }); + describe('defaultFormat', () => { + it('should return an empty string for invalid dates', () => { + const formatted = defaultFormat('foo'); + expect(formatted).toBe(''); + }); + it('should format a date', () => { + const formatted = defaultFormat(new Date(2018, 6, 12)); + expect(formatted).toBe('2018-7-12'); + }); + }); + describe('defaultParse', () => { + it('should return `undefined` for not valid dates', () => { + expect(defaultParse('foo')).toBeUndefined(); + expect(defaultParse({})).toBeUndefined(); + expect(defaultParse(null)).toBeUndefined(); + expect(defaultParse(new Date())).toBeUndefined(); + expect(defaultParse('foo-10-11')).toBeUndefined(); + expect(defaultParse('2017-foo-10')).toBeUndefined(); + expect(defaultParse('2017-10-foo')).toBeUndefined(); + expect(defaultParse('2012-2-0')).toBeUndefined(); + expect(defaultParse('2012-0-20')).toBeUndefined(); + expect(defaultParse('2012-13-20')).toBeUndefined(); + }); + it('should return a parsed date', () => { + const parsed = defaultParse('2018-7-12'); + expect(parsed).toEqual(new Date(2018, 6, 12)); + }); + }); +}); diff --git a/test/daypickerinput/rendering.js b/test/daypickerinput/rendering.js index 03d0a0fdee..a1926750d3 100644 --- a/test/daypickerinput/rendering.js +++ b/test/daypickerinput/rendering.js @@ -1,7 +1,6 @@ import React from 'react'; import ReactDOM from 'react-dom'; import { shallow, mount } from 'enzyme'; -import moment from 'moment'; import DayPickerInput from '../../src/DayPickerInput'; import DayPicker from '../../src/DayPicker'; @@ -12,7 +11,8 @@ describe('DayPickerInput', () => { const dayPicker = ; expect(dayPicker.props.dayPickerProps).toEqual({}); expect(dayPicker.props.value).toBe(''); - expect(dayPicker.props.format).toBe('L'); + expect(typeof dayPicker.props.formatDate).toBe('function'); + expect(typeof dayPicker.props.parseDate).toBe('function'); expect(dayPicker.props.hideOnDayClick).toBe(true); expect(dayPicker.props.component).toBe('input'); }); @@ -20,29 +20,6 @@ describe('DayPickerInput', () => { const wrapper = shallow(); expect(wrapper).toHaveClassName('DayPickerInput'); }); - it('should set the month based on the specified format', () => { - const wrapper = shallow( - - ); - expect(wrapper.instance().state.month.getMonth()).toBe(4); - expect(wrapper.instance().state.month.getFullYear()).toBe(2010); - }); - it('should not set the month if the value is not valid acording to `format`', () => { - const wrapper = shallow( - - ); - expect(wrapper.instance().state.month).toBeUndefined(); - }); - it('should accept multiple `format`s', () => { - const wrapper = shallow( - - ); - expect(wrapper.instance().state.month.getMonth()).toBe(4); - expect(wrapper.instance().state.month.getFullYear()).toBe(2010); - }); it('should render an input field with the passed attributes', () => { const wrapper = shallow( @@ -52,7 +29,7 @@ describe('DayPickerInput', () => { expect(input).toHaveProp('value', '12/14/2017'); expect(input).toHaveProp('placeholder', 'bar'); }); - it('should work with not value dates', () => { + it('should work with not valid date as value', () => { const wrapper = shallow( ); @@ -62,6 +39,13 @@ describe('DayPickerInput', () => { wrapper.update(); expect(wrapper.find(DayPicker)).toBeDefined(); }); + it('should work with a date object as value', () => { + const wrapper = shallow( + + ); + const input = wrapper.find('input'); + expect(input).toHaveProp('value', '2018-5-12'); + }); it('should show the DayPicker', () => { const wrapper = shallow(); wrapper.instance().showDayPicker(); @@ -111,7 +95,7 @@ describe('DayPickerInput', () => { expect(instance.daypicker.props.numberOfMonths).toBe(12); }); it('should open the daypicker to the month of the selected day', () => { - const wrapper = mount(); + const wrapper = mount(); wrapper.instance().showDayPicker(); wrapper.update(); expect(wrapper.find('.DayPicker-Caption div').first()).toHaveText( @@ -119,7 +103,7 @@ describe('DayPickerInput', () => { ); }); it('should display the current value as a selected day', () => { - const wrapper = mount(); + const wrapper = mount(); wrapper.instance().showDayPicker(); wrapper.update(); expect(wrapper.find('.DayPicker-Day--selected')).toHaveLength(1); @@ -140,25 +124,29 @@ describe('DayPickerInput', () => { wrapper.instance().showDayPicker(); wrapper.update(); wrapper.find('.DayPicker-TodayButton').simulate('click'); - expect(wrapper.find('input')).toHaveProp('value', moment().format('L')); + const today = new Date(); + expect(wrapper.find('input')).toHaveProp( + 'value', + `${today.getFullYear()}-${today.getMonth() + 1}-${today.getDate()}` + ); }); }); describe('updating props', () => { it('should update the current value when `value` prop is updated', () => { - const wrapper = mount(); + const wrapper = mount(); wrapper.setProps({ value: '01/10/2018' }); expect(wrapper.instance().state.value).toBe('01/10/2018'); }); it('should not update the current value when other props are updated', () => { - const wrapper = mount(); + const wrapper = mount(); wrapper.setProps({ dayPickerProps: {} }); - expect(wrapper.instance().state.value).toBe('12/15/2017'); + expect(wrapper.instance().state.value).toBe('2017-12-15'); }); it("should update the displayed month when `dayPickerProps.month`'s month is updated", () => { const wrapper = mount( ); wrapper.instance().showDayPicker(); @@ -167,7 +155,7 @@ describe('DayPickerInput', () => { 'December 2017' ); wrapper.setProps({ dayPickerProps: { month: new Date(2017, 10) } }); - expect(wrapper.instance().state.value).toBe('12/15/2017'); + expect(wrapper.instance().state.value).toBe('2017-12-15'); expect(wrapper.find('.DayPicker-Caption').first()).toHaveText( 'November 2017' ); @@ -176,7 +164,7 @@ describe('DayPickerInput', () => { const wrapper = mount( ); wrapper.instance().showDayPicker(); @@ -185,28 +173,47 @@ describe('DayPickerInput', () => { 'December 2017' ); wrapper.setProps({ dayPickerProps: { month: new Date(2016, 10) } }); - expect(wrapper.instance().state.value).toBe('12/15/2017'); + expect(wrapper.instance().state.value).toBe('2017-12-15'); expect(wrapper.find('.DayPicker-Caption').first()).toHaveText( 'November 2016' ); }); it('should not change state if month did not change', () => { - const wrapper = mount(); + const wrapper = mount(); wrapper.instance().showDayPicker(); wrapper.update(); expect(wrapper.find('.DayPicker-Caption').first()).toHaveText( 'December 2017' ); wrapper.setProps({ dayPickerProps: { month: new Date(2017, 11) } }); - expect(wrapper.instance().state.value).toBe('12/15/2017'); + expect(wrapper.instance().state.value).toBe('2017-12-15'); expect(wrapper.find('.DayPicker-Caption').first()).toHaveText( 'December 2017' ); }); it('should not update the current value when other props are updated', () => { - const wrapper = mount(); - wrapper.setProps({ dayPickerProps: {}, value: '12/15/2017' }); - expect(wrapper.instance().state.value).toBe('12/15/2017'); + const wrapper = mount(); + wrapper.setProps({ dayPickerProps: {}, value: '2017-12-15' }); + expect(wrapper.instance().state.value).toBe('2017-12-15'); + }); + it('should update the value when it is passed as Date', () => { + const wrapper = mount(); + wrapper.setProps({ dayPickerProps: {}, value: new Date(2017, 11, 16) }); + expect(wrapper.instance().state.value).toBe('2017-12-16'); + }); + it('should update the selected days from prop', () => { + const wrapper = mount( + + ); + wrapper.setProps({ + dayPickerProps: { selectedDays: new Date(2020, 2, 10) }, + }); + wrapper.update(); + expect(wrapper.instance().state.selectedDays).toEqual( + new Date(2020, 2, 10) + ); }); }); });