diff --git a/build/config.js b/build/config.js index 9648f68e5..63e919618 100644 --- a/build/config.js +++ b/build/config.js @@ -1,5 +1,5 @@ module.exports = { dev: { - componentName: 'breadcrumb', // dev components + componentName: 'calendar', // dev components }, }; diff --git a/components/calendar/Header.jsx b/components/calendar/Header.jsx index 38b86e627..c7828b484 100644 --- a/components/calendar/Header.jsx +++ b/components/calendar/Header.jsx @@ -1,9 +1,21 @@ import Select from '../select'; import { Group, Button } from '../radio'; import PropTypes from '../_util/vue-types'; -import { initDefaultProps } from '../_util/props-util'; +import { initDefaultProps, getComponentFromProp } from '../_util/props-util'; import { ConfigConsumerProps } from '../config-provider'; -const Option = Select.Option; + +const { Option } = Select; + +function getMonthsLocale(value) { + const current = value.clone(); + const localeData = value.localeData(); + const months = []; + for (let i = 0; i < 12; i++) { + current.month(i); + months.push(localeData.monthsShort(current)); + } + return months; +} export const HeaderProps = { prefixCls: PropTypes.string, @@ -16,6 +28,7 @@ export const HeaderProps = { // onTypeChange: PropTypes.(type: string) => void, value: PropTypes.any, validRange: PropTypes.array, + headerRender: PropTypes.func, }; export default { @@ -29,7 +42,7 @@ export default { // private calenderHeaderNode: HTMLDivElement; methods: { getYearSelectElement(prefixCls, year) { - const { yearSelectOffset, yearSelectTotal, locale, fullscreen, validRange } = this; + const { yearSelectOffset, yearSelectTotal, locale = {}, fullscreen, validRange } = this; let start = year - yearSelectOffset; let end = start + yearSelectTotal; if (validRange) { @@ -56,17 +69,6 @@ export default { ); }, - getMonthsLocale(value) { - const current = value.clone(); - const localeData = value.localeData(); - const months = []; - for (let i = 0; i < 12; i++) { - current.month(i); - months.push(localeData.monthsShort(current)); - } - return months; - }, - getMonthSelectElement(prefixCls, month, months) { const { fullscreen, validRange, value } = this; const options = []; @@ -125,37 +127,68 @@ export default { this.$emit('valueChange', newValue); }, - onTypeChange(e) { - this.$emit('typeChange', e.target.value); + onInternalTypeChange(e) { + this.onTypeChange(e.target.value); + }, + + onTypeChange(val) { + this.$emit('typeChange', val); }, getCalenderHeaderNode() { return this.$refs.calenderHeaderNode; }, + getMonthYearSelections(getPrefixCls) { + const { prefixCls: customizePrefixCls, type, value } = this.$props; + + const prefixCls = getPrefixCls('fullcalendar', customizePrefixCls); + const yearReactNode = this.getYearSelectElement(prefixCls, value.year()); + const monthReactNode = + type === 'month' + ? this.getMonthSelectElement(prefixCls, value.month(), getMonthsLocale(value)) + : null; + return { + yearReactNode, + monthReactNode, + }; + }, + + getTypeSwitch() { + const { locale = {}, type, fullscreen } = this.$props; + const size = fullscreen ? 'default' : 'small'; + return ( + + + + + ); + }, + onValueChange() { + this.$emit('valueChange', ...arguments); + }, + headerRenderCustom(headerRender) { + const { type, value } = this.$props; + return headerRender({ + value, + type: type || 'month', + onChange: this.onValueChange, + onTypeChange: this.onTypeChange, + }); + }, }, render() { - const { prefixCls: customizePrefixCls, type, value, locale, fullscreen } = this; + const { prefixCls: customizePrefixCls, headerRender } = this; const getPrefixCls = this.configProvider.getPrefixCls; const prefixCls = getPrefixCls('fullcalendar', customizePrefixCls); - - const yearSelect = this.getYearSelectElement(prefixCls, value.year()); - const monthSelect = - type === 'date' - ? this.getMonthSelectElement(prefixCls, value.month(), this.getMonthsLocale(value)) - : null; - const size = fullscreen ? 'default' : 'small'; - const typeSwitch = ( - - - - - ); - - return ( + const typeSwitch = this.getTypeSwitch(); + const { yearReactNode, monthReactNode } = this.getMonthYearSelections(getPrefixCls); + return headerRender ? ( + this.headerRenderCustom(headerRender) + ) : (
- {yearSelect} - {monthSelect} + {yearReactNode} + {monthReactNode} {typeSwitch}
); diff --git a/components/calendar/__tests__/__snapshots__/demo.test.js.snap b/components/calendar/__tests__/__snapshots__/demo.test.js.snap index 7b20a6638..9403cc20d 100644 --- a/components/calendar/__tests__/__snapshots__/demo.test.js.snap +++ b/components/calendar/__tests__/__snapshots__/demo.test.js.snap @@ -17,7 +17,7 @@ exports[`renders ./components/calendar/demo/basic.md correctly 1`] = ` -
+
@@ -323,7 +323,7 @@ exports[`renders ./components/calendar/demo/card.md correctly 1`] = `
-
+
@@ -629,7 +629,7 @@ exports[`renders ./components/calendar/demo/notice-calendar.md correctly 1`] = `
-
+
@@ -1042,7 +1042,7 @@ exports[`renders ./components/calendar/demo/select.md correctly 1`] = `
-
+
@@ -1346,7 +1346,7 @@ exports[`renders ./components/calendar/demo/select.md correctly 1`] = `
-
+
diff --git a/components/calendar/__tests__/__snapshots__/index.test.js.snap b/components/calendar/__tests__/__snapshots__/index.test.js.snap index 74c928c71..a2f3f95aa 100644 --- a/components/calendar/__tests__/__snapshots__/index.test.js.snap +++ b/components/calendar/__tests__/__snapshots__/index.test.js.snap @@ -17,7 +17,7 @@ exports[`Calendar Calendar should support locale 1`] = `
-
+
diff --git a/components/calendar/__tests__/index.test.js b/components/calendar/__tests__/index.test.js index 54c04e1a5..50414e5ce 100644 --- a/components/calendar/__tests__/index.test.js +++ b/components/calendar/__tests__/index.test.js @@ -3,11 +3,21 @@ import { mount } from '@vue/test-utils'; import { asyncExpect } from '@/tests/utils'; import MockDate from 'mockdate'; import Calendar from '..'; +import Header from '../Header'; +import Select from '../../select'; +import Group from '../../radio/group'; +import Button from '../../radio/radioButton'; +import mountTest from '../../../tests/shared/mountTest'; +import { sleep } from '../../../tests/utils'; function $$(className) { return document.body.querySelectorAll(className); } describe('Calendar', () => { + mountTest(Calendar); + beforeAll(() => { + document.body.innerHTML = ''; + }); it('Calendar should be selectable', async () => { const onSelect = jest.fn(); const wrapper = mount( @@ -25,7 +35,7 @@ describe('Calendar', () => { .trigger('click'); }); await asyncExpect(() => { - expect(onSelect).toBeCalledWith(expect.anything()); + expect(onSelect).toHaveBeenCalledWith(expect.anything()); const value = onSelect.mock.calls[0][0]; expect(Moment.isMoment(value)).toBe(true); }); @@ -203,11 +213,11 @@ describe('Calendar', () => { }); await asyncExpect(() => { expect(wrapper.vm.sMode).toEqual(yearMode); - wrapper.vm.setType('date'); + wrapper.setProps({ mode: monthMode }); }); await asyncExpect(() => { expect(wrapper.vm.sMode).toEqual(monthMode); - expect(onPanelChangeStub).toHaveBeenCalledTimes(1); + expect(onPanelChangeStub).toHaveBeenCalledTimes(0); }); }); @@ -226,4 +236,164 @@ describe('Calendar', () => { MockDate.reset(); }); }); + + it('should trigger onPanelChange when click last month of date', () => { + const onPanelChange = jest.fn(); + const date = new Moment('1990-09-03'); + const wrapper = mount(Calendar, { + propsData: { + value: date, + }, + listeners: { + panelChange: onPanelChange, + }, + sync: false, + }); + wrapper + .findAll('.ant-fullcalendar-cell') + .at(0) + .trigger('click'); + + expect(onPanelChange).toHaveBeenCalled(); + expect(onPanelChange.mock.calls[0][0].month()).toEqual(date.month() - 1); + }); + + it('switch should work correctly without prop mode', async () => { + const onPanelChange = jest.fn(); + const date = new Moment(new Date(Date.UTC(2017, 7, 9, 8))); + const wrapper = mount(Calendar, { + propsData: { + value: date, + }, + listeners: { + panelChange: onPanelChange, + }, + sync: false, + }); + expect(wrapper.vm.sMode).toBe('month'); + expect(wrapper.findAll('.ant-fullcalendar-table').length).toBe(1); + expect(wrapper.findAll('.ant-fullcalendar-month-panel-table').length).toBe(0); + wrapper.findAll('.ant-radio-button-input[value="year"]').trigger('change'); + await sleep(50); + expect(wrapper.findAll('.ant-fullcalendar-table').length).toBe(0); + expect(wrapper.findAll('.ant-fullcalendar-month-panel-table').length).toBe(1); + expect(onPanelChange).toHaveBeenCalled(); + expect(onPanelChange.mock.calls[0][1]).toEqual('year'); + }); + + const createWrapper = async (start, end, value, onValueChange) => { + document.body.innerHTML = ''; + const wrapper = mount( + { + render() { + return ( +
+ ); + }, + }, + { + sync: false, + attachToDocument: true, + }, + ); + await sleep(50); + wrapper + .findAll('.ant-fullcalendar-year-select') + .at(0) + .trigger('click'); + await sleep(50); + $$('.ant-select-dropdown-menu-item')[0].click(); + await sleep(50); + }; + + it('if value.month > end.month, set value.month to end.month', async () => { + const value = new Moment('1990-01-03'); + const start = new Moment('2019-04-01'); + const end = new Moment('2019-11-01'); + const onValueChange = jest.fn(); + await createWrapper(start, end, value, onValueChange); + expect(onValueChange).toHaveBeenCalledWith(value.year('2019').month('3')); + }); + it('if value.month > end.month, set value.month to end.month1', async () => { + const value = new Moment('1990-01-03'); + const start = new Moment('2019-04-01'); + const end = new Moment('2019-11-01'); + const onValueChange = jest.fn(); + await createWrapper(start, end, value, onValueChange); + expect(onValueChange).toHaveBeenCalledWith(value.year('2019').month('3')); + }); + + it('if start.month > value.month, set value.month to start.month ', async () => { + const value = new Moment('1990-01-03'); + const start = new Moment('2019-11-01'); + const end = new Moment('2019-03-01'); + const onValueChange = jest.fn(); + await createWrapper(start, end, value, onValueChange); + expect(onValueChange).toHaveBeenCalledWith(value.year('2019').month('10')); + }); + + it('onMonthChange should work correctly', async () => { + const start = new Moment('2018-11-01'); + const end = new Moment('2019-03-01'); + const value = new Moment('2018-12-03'); + const onValueChange = jest.fn(); + const wrapper = mount( + { + render() { + return ( +
+ ); + }, + }, + { + sync: false, + attachToDocument: true, + }, + ); + await sleep(50); + wrapper + .findAll('.ant-fullcalendar-month-select') + .at(0) + .trigger('click'); + await sleep(50); + wrapper + .findAll('.ant-select-dropdown-menu-item') + .at(0) + .trigger('click'); + await sleep(50); + expect(onValueChange).toHaveBeenCalledWith(value.month(10)); + }); + + it('onTypeChange should work correctly', () => { + const onTypeChange = jest.fn(); + const value = new Moment('2018-12-03'); + const wrapper = mount({ + render() { + return ( +
+ ); + }, + }); + wrapper + .findAll('input') + .at(1) + .trigger('change'); + expect(onTypeChange).toHaveBeenCalledWith('year'); + }); }); diff --git a/components/calendar/demo/customize-header.vue b/components/calendar/demo/customize-header.vue new file mode 100644 index 000000000..f014363bd --- /dev/null +++ b/components/calendar/demo/customize-header.vue @@ -0,0 +1,99 @@ + +#### 自定义头部 +自定义日历头部内容。 + + + +#### Customize Header +Customize Calendar header content. + + +```tpl + + +``` diff --git a/components/calendar/demo/index.vue b/components/calendar/demo/index.vue index 265c681bf..999df62cd 100644 --- a/components/calendar/demo/index.vue +++ b/components/calendar/demo/index.vue @@ -3,6 +3,8 @@ import Basic from './basic'; import Card from './card'; import NoticeCalendar from './notice-calendar'; import Select from './select'; +import CustomizeHeader from './customize-header.vue'; +import CustomizeHeaderString from '!raw-loader!./customize-header.vue'; import CN from '../index.zh-CN.md'; import US from '../index.en-US.md'; const md = { @@ -40,6 +42,10 @@ export default {