import React from 'react'; import { shallow, mount } from 'enzyme'; import { getMonthStart } from '@wojtekmaj/date-utils'; import Calendar from './Calendar'; const { format } = new Intl.DateTimeFormat('en-US', { day: 'numeric', month: 'long', year: 'numeric' }); const event = document.createEvent('MouseEvent'); event.initEvent('click', true, true); event.persist = () => {}; describe('Calendar', () => { it('applies className to its wrapper when given a string', () => { const className = 'testClassName'; const component = shallow( , ); const wrapperClassName = component.find('.react-calendar').prop('className'); expect(wrapperClassName.includes(className)).toBe(true); }); it('passes container element to inputRef properly', () => { const inputRef = jest.fn(); mount( , ); expect(inputRef).toHaveBeenCalled(); expect(inputRef.mock.calls[0][0]).toBeInstanceOf(HTMLElement); }); it('renders Navigation by default', () => { const component = shallow( , ); const navigation = component.find('Navigation'); expect(navigation).toHaveLength(1); }); it('does not render Navigation when showNavigation flag is set to false', () => { const component = shallow( , ); const navigation = component.find('Navigation'); expect(navigation).toHaveLength(0); }); it('uses given value when passed value using value prop', () => { const component = shallow( , ); expect(component.instance().value).toEqual(new Date(2019, 0, 1)); }); it('uses given value when passed value using defaultValue prop', () => { const component = shallow( , ); expect(component.instance().value).toEqual(new Date(2019, 0, 1)); }); it('renders given view when passed view using view prop', () => { const component = shallow( , ); expect(component.instance().view).toBe('century'); }); it('renders given view when passed view using defaultView prop', () => { const component = shallow( , ); expect(component.instance().view).toBe('century'); }); it('renders given active start date when passed active start date using activeStartDate prop', () => { const component = shallow( , ); expect(component.instance().activeStartDate).toEqual(new Date(2019, 0, 1)); }); it('renders given active start date when passed active start date using activeStartDate prop', () => { const component = shallow( , ); expect(component.instance().activeStartDate).toEqual(new Date(2019, 0, 1)); }); it('changes activeStartDate when updating value via props change', () => { const value = new Date(2018, 1, 15); const newValue = new Date(2018, 0, 15); const newActiveStartDate = new Date(2018, 0, 1); const component = shallow( , ); component.setProps({ value: newValue }); expect(component.instance().activeStartDate).toEqual(newActiveStartDate); }); it('changes activeStartDate when updating value via onChange', () => { const value = new Date(2018, 1, 15); const newValue = new Date(2018, 0, 15); const newActiveStartDate = new Date(2018, 0, 1); const component = shallow( , ); component.instance().onChange(newValue, event); expect(component.instance().activeStartDate).toEqual(newActiveStartDate); }); it('changes Calendar view given new activeStartDate value', () => { const activeStartDate = new Date(2017, 0, 1); const newActiveStartDate = new Date(2018, 0, 1); const component = shallow( , ); component.setProps({ activeStartDate: newActiveStartDate }); expect(component.instance().activeStartDate).toEqual(newActiveStartDate); }); describe('renders views properly', () => { it('renders MonthView by default', () => { const component = shallow( , ); const monthView = component.find('MonthView'); expect(monthView).toHaveLength(1); }); it('renders MonthView when given view = "month"', () => { const component = shallow( , ); const monthView = component.find('MonthView'); expect(monthView).toHaveLength(1); }); it('renders YearView when given view = "year"', () => { const component = shallow( , ); const yearView = component.find('YearView'); expect(yearView).toHaveLength(1); }); it('renders DecadeView when given view = "decade"', () => { const component = shallow( , ); const decadeView = component.find('DecadeView'); expect(decadeView).toHaveLength(1); }); it('renders CenturyView when given view = "century"', () => { const component = shallow( , ); const centuryView = component.find('CenturyView'); expect(centuryView).toHaveLength(1); }); it('renders maximum allowed view when given maxDetail', () => { const component = shallow( , ); const yearView = component.find('YearView'); expect(yearView).toHaveLength(1); }); it('renders maximum allowed view when given view that is not allowed', () => { const component = shallow( , ); const yearView = component.find('YearView'); expect(yearView).toHaveLength(1); }); it('renders maximum allowed view when attempting to externally switch to a view that is not allowed', () => { const component = shallow( , ); component.setProps({ view: 'month' }); const yearView = component.find('YearView'); expect(yearView).toHaveLength(1); }); it('renders maximum allowed view when given changed maxDetail', () => { const component = shallow( , ); component.setProps({ maxDetail: 'year' }); component.update(); const yearView = component.find('YearView'); expect(yearView).toHaveLength(1); }); }); it('does not pass showWeekNumbers flag to MonthView component by default', () => { const component = shallow( , ); const monthView = component.find('MonthView'); expect(monthView.prop('showWeekNumbers')).toBeFalsy(); }); it('passes showWeekNumbers flag to MonthView component given showWeekNumbers flag', () => { const component = shallow( , ); const monthView = component.find('MonthView'); expect(monthView.prop('showWeekNumbers')).toBeTruthy(); }); it('passes showNeighboringMonth flag to MonthView component given showNeighboringMonth flag', () => { const component = shallow( , ); const monthView = component.find('MonthView'); expect(monthView.prop('showNeighboringMonth')).toBeTruthy(); }); describe('displays initial view properly', () => { it('displays a view with a given value when defaultValue is given', () => { const defaultValue = new Date(2017, 0, 15); const component = shallow( , ); const monthView = component.find('MonthView'); expect(monthView.prop('activeStartDate')).toEqual(new Date(2017, 0, 1)); }); it('displays a view with a given value when value is given', () => { const value = new Date(2017, 0, 15); const component = shallow( , ); const monthView = component.find('MonthView'); expect(monthView.prop('activeStartDate')).toEqual(new Date(2017, 0, 1)); }); it('displays a view with defaultActiveStartDate when value is given and defaultActiveStartDate is given', () => { const defaultActiveStartDate = new Date(2017, 0, 1); const value = new Date(2018, 0, 15); const component = shallow( , ); const monthView = component.find('MonthView'); expect(monthView.prop('activeStartDate')).toEqual(defaultActiveStartDate); }); it('displays a view with defaultActiveStartDate when no value is given and defaultActiveStartDate is given', () => { const defaultActiveStartDate = new Date(2017, 0, 1); const component = shallow( , ); const monthView = component.find('MonthView'); expect(monthView.prop('activeStartDate')).toEqual(defaultActiveStartDate); }); it('displays a view with activeStartDate when no value is given and activeStartDate is given', () => { const activeStartDate = new Date(2017, 0, 1); const component = shallow( , ); const monthView = component.find('MonthView'); expect(monthView.prop('activeStartDate')).toEqual(activeStartDate); }); it('displays a view with today\'s date when no value and no activeStartDate is given', () => { const today = new Date(); const beginOfCurrentMonth = getMonthStart(today); const component = shallow( , ); const monthView = component.find('MonthView'); expect(monthView.prop('activeStartDate')).toEqual(beginOfCurrentMonth); }); it('displays days on the correct weekdays when given a defaultActiveStartDate', () => { const defaultActiveStartDate = new Date(2012, 5, 6); const component = mount( , ); const firstDayTile = component.find('.react-calendar__tile').first(); const firstDayTileTimeAbbr = firstDayTile.find('abbr').prop('aria-label'); // The date of the first Monday that this calendar should show is May 28, 2012. expect(firstDayTileTimeAbbr).toBe(format(new Date(2012, 4, 28))); }); it('displays days on the correct weekdays when given an activeStartDate', () => { const activeStartDate = new Date(2012, 5, 6); const component = mount( , ); const firstDayTile = component.find('.react-calendar__tile').first(); const firstDayTileTimeAbbr = firstDayTile.find('abbr').prop('aria-label'); // The date of the first Monday that this calendar should show is May 28, 2012. expect(firstDayTileTimeAbbr).toBe(format(new Date(2012, 4, 28))); }); }); describe('handles drill up properly', () => { it('drills up when allowed', () => { const component = shallow( , ); component.setState({ view: 'month' }); component.instance().drillUp(); expect(component.instance().view).toBe('year'); }); it('calls onDrillUp on drill up properly given view prop', () => { const onDrillUp = jest.fn(); const component = shallow( , ); component.instance().drillUp(); expect(onDrillUp).toHaveBeenCalledWith({ activeStartDate: new Date(2017, 0, 1), view: 'year', }); }); it('calls onDrillUp on drill up properly when not given view prop', () => { const onDrillUp = jest.fn(); const component = shallow( , ); component.setState({ view: 'month' }); component.instance().drillUp(); expect(onDrillUp).toHaveBeenCalledWith({ activeStartDate: new Date(2017, 0, 1), view: 'year', }); }); it('refuses to drill up when already on minimum allowed detail', () => { const onDrillUp = jest.fn(); const component = shallow( , ); component.instance().drillUp(); expect(onDrillUp).not.toHaveBeenCalled(); }); }); describe('handles drill down properly', () => { it('drills down when allowed', () => { const component = shallow( , ); component.setState({ view: 'century' }); component.instance().drillDown(new Date(2011, 0, 1)); expect(component.instance().view).toBe('decade'); }); it('calls onDrillDown on drill down given view prop', () => { const onDrillDown = jest.fn(); const component = shallow( , ); component.instance().drillDown(new Date(2011, 0, 1)); expect(onDrillDown).toHaveBeenCalledWith({ activeStartDate: new Date(2011, 0, 1), view: 'decade', }); }); it('calls onDrillDown on drill down when not given view prop', () => { const onDrillDown = jest.fn(); const component = shallow( , ); component.setState({ view: 'century' }); component.instance().drillDown(new Date(2011, 0, 1)); expect(onDrillDown).toHaveBeenCalledWith({ activeStartDate: new Date(2011, 0, 1), view: 'decade', }); }); it('refuses to drill down when already on minimum allowed detail', () => { const onDrillDown = jest.fn(); const component = shallow( , ); component.instance().drillUp(); expect(onDrillDown).not.toHaveBeenCalled(); }); }); describe('handles active start date change properly', () => { it('changes active start date when allowed', () => { const component = shallow( , ); component.instance().setActiveStartDate(new Date(2019, 0, 1)); expect(component.instance().activeStartDate).toEqual(new Date(2019, 0, 1)); }); it('calls onActiveStartDateChange on activeStartDate initial set', () => { const value = new Date(2019, 0, 15); const newActiveStartDate = new Date(2018, 0, 1); const onActiveStartDateChange = jest.fn(); const component = shallow( , ); component.instance().setActiveStartDate(newActiveStartDate); expect(onActiveStartDateChange).toHaveBeenCalledWith({ activeStartDate: newActiveStartDate, value, view: 'year', }); }); it('calls onActiveStartDateChange on activeStartDate change', () => { const value = new Date(2019, 0, 15); const activeStartDate = new Date(2017, 0, 1); const newActiveStartDate = new Date(2018, 0, 1); const onActiveStartDateChange = jest.fn(); const component = shallow( , ); component.instance().setActiveStartDate(newActiveStartDate); expect(onActiveStartDateChange).toHaveBeenCalledWith({ activeStartDate: newActiveStartDate, value, view: 'year', }); }); it('does not call onActiveStartDateChange on activeStartDate change if value is the same as before', () => { const activeStartDate = new Date(2017, 0, 1); const newActiveStartDate = new Date(2017, 0, 1); const onActiveStartDateChange = jest.fn(); const component = shallow( , ); component.instance().setActiveStartDate(newActiveStartDate); expect(onActiveStartDateChange).not.toHaveBeenCalled(); }); it('does not call onActiveStartDateChange on activeStartDate change if value is the same as previously inherited', () => { const value = new Date(2017, 0, 1); const newActiveStartDate = new Date(2017, 0, 1); const onActiveStartDateChange = jest.fn(); const component = shallow( , ); component.instance().setActiveStartDate(newActiveStartDate); expect(onActiveStartDateChange).not.toHaveBeenCalled(); }); }); describe('handles view change properly', () => { it('calls onViewChange on view initial set', () => { const value = new Date(2019, 0, 15); const activeStartDate = new Date(2017, 0, 1); const newView = 'year'; const onViewChange = jest.fn(); const component = shallow( , ); component.instance().setStateAndCallCallbacks({ view: newView }); expect(onViewChange).toHaveBeenCalledWith({ activeStartDate, value, view: newView, }); }); it('calls onViewChange on view change', () => { const value = new Date(2019, 0, 15); const activeStartDate = new Date(2017, 0, 1); const view = 'year'; const newView = 'month'; const onViewChange = jest.fn(); const component = shallow( , ); component.instance().setStateAndCallCallbacks({ view: newView }); expect(onViewChange).toHaveBeenCalledWith({ activeStartDate, value, view: newView, }); }); it('does not call onViewChange on view change if value is the same as before', () => { const view = 'year'; const newView = 'year'; const onViewChange = jest.fn(); const component = shallow( , ); component.instance().setStateAndCallCallbacks({ view: newView }); expect(onViewChange).not.toHaveBeenCalled(); }); }); describe('calls onChange properly', () => { it('calls onChange function returning the beginning of selected period by default', () => { const onChange = jest.fn(); const component = shallow( , ); component.instance().onChange(new Date(2017, 0, 1), event); expect(onChange).toHaveBeenCalledWith(new Date(2017, 0, 1), event); }); it('calls onChange function returning the beginning of the selected period when returnValue is set to "start"', () => { const onChange = jest.fn(); const component = shallow( , ); component.instance().onChange(new Date(2017, 0, 1), event); expect(onChange).toHaveBeenCalledWith(new Date(2017, 0, 1), event); }); it('calls onChange function returning the end of the selected period when returnValue is set to "end"', () => { const onChange = jest.fn(); const component = shallow( , ); component.instance().onChange(new Date(2017, 0, 1), event); expect(onChange).toHaveBeenCalledWith(new Date(2017, 0, 1, 23, 59, 59, 999), event); }); it('calls onChange function returning the beginning of selected period when returnValue is set to "range"', () => { const onChange = jest.fn(); const component = shallow( , ); component.instance().onChange(new Date(2017, 0, 1), event); expect(onChange).toHaveBeenCalledWith([ new Date(2017, 0, 1), new Date(2017, 0, 1, 23, 59, 59, 999), ], event); }); it('calls onChange function returning the beginning of selected period, but no earlier than minDate', () => { const onChange = jest.fn(); const component = shallow( , ); component.instance().onChange(new Date(2017, 0, 1), event); expect(onChange).toHaveBeenCalledWith(new Date(2017, 0, 1, 12), event); }); it('calls onChange function returning the beginning of selected period, but no later than maxDate', () => { const onChange = jest.fn(); const component = shallow( , ); component.instance().onChange(new Date(2017, 0, 2), event); expect(onChange).toHaveBeenCalledWith(new Date(2017, 0, 1, 12), event); }); it('calls onChange function returning the end of selected period, but no earlier than minDate', () => { const onChange = jest.fn(); const component = shallow( , ); component.instance().onChange(new Date(2017, 0, 1), event); expect(onChange).toHaveBeenCalledWith(new Date(2017, 0, 2, 12), event); }); it('calls onChange function returning the end of selected period, but no later than maxDate', () => { const onChange = jest.fn(); const component = shallow( , ); component.instance().onChange(new Date(2017, 0, 2), event); expect(onChange).toHaveBeenCalledWith(new Date(2017, 0, 1, 12), event); }); it('does not call onChange function returning a range when selected one piece of a range by default', () => { const onChange = jest.fn(); const component = shallow( , ); component.instance().onChange(new Date(2018, 0, 1), event); expect(onChange).not.toHaveBeenCalled(); }); it('does not call onChange function returning a range when selected one piece of a range given allowPartialRange = false', () => { const onChange = jest.fn(); const component = shallow( , ); component.instance().onChange(new Date(2018, 0, 1), event); expect(onChange).not.toHaveBeenCalled(); }); it('calls onChange function returning a partial range when selected one piece of a range given allowPartialRange = true', () => { const onChange = jest.fn(); const component = shallow( , ); component.instance().onChange(new Date(2018, 0, 1), event); expect(onChange).toHaveBeenCalledTimes(1); expect(onChange).toHaveBeenCalledWith([ new Date(2018, 0, 1), ], event); }); it('calls onChange function returning a range when selected two pieces of a range', () => { const onChange = jest.fn(); const component = shallow( , ); component.instance().onChange(new Date(2018, 0, 1), event); component.instance().onChange(new Date(2018, 6, 1), event); expect(onChange).toHaveBeenCalledTimes(1); expect(onChange).toHaveBeenCalledWith([ new Date(2018, 0, 1), new Date(2018, 6, 1, 23, 59, 59, 999), ], event); }); it('calls onChange function returning a range when selected reversed two pieces of a range', () => { const onChange = jest.fn(); const component = shallow( , ); component.instance().onChange(new Date(2018, 6, 1), event); component.instance().onChange(new Date(2018, 0, 1), event); expect(onChange).toHaveBeenCalledTimes(1); expect(onChange).toHaveBeenCalledWith([ new Date(2018, 0, 1), new Date(2018, 6, 1, 23, 59, 59, 999), ], event); }); }); it('passes formatMonthYear to Navigation component', () => { const formatMonthYear = () => 'Month year'; const component = shallow( , ); const navigation = component.find('Navigation'); expect(navigation.prop('formatMonthYear')).toBe(formatMonthYear); }); it('passes formatYear to Navigation component', () => { const formatYear = () => 'Year'; const component = shallow( , ); const navigation = component.find('Navigation'); expect(navigation.prop('formatYear')).toBe(formatYear); }); it('passes formatLongDate to MonthView component', () => { const formatLongDate = () => 'Long date'; const component = shallow( , ); const monthView = component.find('MonthView'); expect(monthView.prop('formatLongDate')).toBe(formatLongDate); }); it('passes formatShortWeekday to MonthView component', () => { const formatShortWeekday = () => 'Weekday'; const component = shallow( , ); const monthView = component.find('MonthView'); expect(monthView.prop('formatShortWeekday')).toBe(formatShortWeekday); }); it('passes formatMonth to YearView component', () => { const formatMonth = () => 'Month'; const component = shallow( , ); const yearView = component.find('YearView'); expect(yearView.prop('formatMonth')).toBe(formatMonth); }); it('passes formatYear to DecadeView component', () => { const formatYear = () => 'Year'; const component = shallow( , ); const decadeView = component.find('DecadeView'); expect(decadeView.prop('formatYear')).toBe(formatYear); }); it('passes formatYear to CenturyView component', () => { const formatYear = () => 'Year'; const component = shallow( , ); const centuryView = component.find('CenturyView'); expect(centuryView.prop('formatYear')).toBe(formatYear); }); });