Commit 68e364211969e574fc824e8c0a543bec5fc1d948
Committed by
GitHub

Merge pull request #1712 from SergioCrisostomo/add-more-datepicker-tests
Refactor cell click handler and add more date picker and date range picker tests
Showing
3 changed files
with
157 additions
and
82 deletions
Show diff stats
src/components/date-picker/base/date-table.vue
1 | 1 | <template> |
2 | 2 | <div |
3 | 3 | :class="classes" |
4 | - @click="handleClick" | |
5 | 4 | @mousemove="handleMouseMove"> |
6 | 5 | <div :class="[prefixCls + '-header']"> |
7 | 6 | <span>{{ t('i.datepicker.weeks.sun') }}</span><span>{{ t('i.datepicker.weeks.mon') }}</span><span>{{ t('i.datepicker.weeks.tue') }}</span><span>{{ t('i.datepicker.weeks.wed') }}</span><span>{{ t('i.datepicker.weeks.thu') }}</span><span>{{ t('i.datepicker.weeks.fri') }}</span><span>{{ t('i.datepicker.weeks.sat') }}</span> |
8 | 7 | </div> |
9 | - <span :class="getCellCls(cell)" v-for="(cell, index) in readCells"><em :index="index">{{ cell.text }}</em></span> | |
8 | + <span :class="getCellCls(cell)" v-for="(cell, index) in readCells"><em :index="index" @click="handleClick(cell)">{{ cell.text }}</em></span> | |
10 | 9 | </div> |
11 | 10 | </template> |
12 | 11 | <script> |
... | ... | @@ -106,6 +105,7 @@ |
106 | 105 | const cell_tmpl = { |
107 | 106 | text: '', |
108 | 107 | type: '', |
108 | + date: null, | |
109 | 109 | selected: false, |
110 | 110 | disabled: false, |
111 | 111 | range: false, |
... | ... | @@ -117,14 +117,8 @@ |
117 | 117 | const cell = deepCopy(cell_tmpl); |
118 | 118 | cell.type = 'prev-month'; |
119 | 119 | cell.text = dateCountOfLastMonth - (day - 1) + i; |
120 | - | |
121 | - let prevMonth = this.month - 1; | |
122 | - let prevYear = this.year; | |
123 | - if (this.month === 0) { | |
124 | - prevMonth = 11; | |
125 | - prevYear -= 1; | |
126 | - } | |
127 | - const time = clearHours(new Date(prevYear, prevMonth, cell.text)); | |
120 | + cell.date = new Date(this.year, this.month - 1, cell.text); | |
121 | + const time = clearHours(cell.date); | |
128 | 122 | cell.disabled = typeof disabledDate === 'function' && disabledDate(new Date(time)); |
129 | 123 | cells.push(cell); |
130 | 124 | } |
... | ... | @@ -132,9 +126,10 @@ |
132 | 126 | |
133 | 127 | for (let i = 1; i <= dateCountOfMonth; i++) { |
134 | 128 | const cell = deepCopy(cell_tmpl); |
135 | - const time = clearHours(new Date(this.year, this.month, i)); | |
136 | - cell.type = time === today ? 'today' : 'normal'; | |
137 | 129 | cell.text = i; |
130 | + cell.date = new Date(this.year, this.month, cell.text); | |
131 | + const time = clearHours(cell.date); | |
132 | + cell.type = time === today ? 'today' : 'normal'; | |
138 | 133 | cell.selected = time === selectDay; |
139 | 134 | cell.disabled = typeof disabledDate === 'function' && disabledDate(new Date(time)); |
140 | 135 | cell.range = time >= minDay && time <= maxDay; |
... | ... | @@ -149,14 +144,8 @@ |
149 | 144 | const cell = deepCopy(cell_tmpl); |
150 | 145 | cell.type = 'next-month'; |
151 | 146 | cell.text = i; |
152 | - | |
153 | - let nextMonth = this.month + 1; | |
154 | - let nextYear = this.year; | |
155 | - if (this.month === 11) { | |
156 | - nextMonth = 0; | |
157 | - nextYear += 1; | |
158 | - } | |
159 | - const time = clearHours(new Date(nextYear, nextMonth, cell.text)); | |
147 | + cell.date = new Date(this.year, this.month + 1, cell.text); | |
148 | + const time = clearHours(cell.date); | |
160 | 149 | cell.disabled = typeof disabledDate === 'function' && disabledDate(new Date(time)); |
161 | 150 | cells.push(cell); |
162 | 151 | } |
... | ... | @@ -165,71 +154,39 @@ |
165 | 154 | } |
166 | 155 | }, |
167 | 156 | methods: { |
168 | - getDateOfCell (cell) { | |
169 | - let year = this.year; | |
170 | - let month = this.month; | |
171 | - let day = cell.text; | |
172 | - | |
173 | - const date = this.date; | |
174 | - const hours = date.getHours(); | |
175 | - const minutes = date.getMinutes(); | |
176 | - const seconds = date.getSeconds(); | |
177 | - | |
178 | - if (cell.type === 'prev-month') { | |
179 | - if (month === 0) { | |
180 | - month = 11; | |
181 | - year--; | |
182 | - } else { | |
183 | - month--; | |
184 | - } | |
185 | - } else if (cell.type === 'next-month') { | |
186 | - if (month === 11) { | |
187 | - month = 0; | |
188 | - year++; | |
189 | - } else { | |
190 | - month++; | |
191 | - } | |
192 | - } | |
193 | - | |
194 | - return new Date(year, month, day, hours, minutes, seconds); | |
195 | - }, | |
196 | - handleClick (event) { | |
197 | - const target = event.target; | |
198 | - if (target.tagName === 'EM') { | |
199 | - const cell = this.cells[parseInt(event.target.getAttribute('index'))]; | |
200 | - if (cell.disabled) return; | |
201 | - | |
202 | - const newDate = this.getDateOfCell(cell); | |
203 | - | |
204 | - if (this.selectionMode === 'range') { | |
205 | - if (this.minDate && this.maxDate) { | |
157 | + handleClick (cell) { | |
158 | + | |
159 | + if (cell.disabled) return; | |
160 | + const newDate = cell.date; | |
161 | + | |
162 | + if (this.selectionMode === 'range') { | |
163 | + if (this.minDate && this.maxDate) { | |
164 | + const minDate = new Date(newDate.getTime()); | |
165 | + const maxDate = null; | |
166 | + this.rangeState.selecting = true; | |
167 | + this.markRange(this.minDate); | |
168 | + | |
169 | + this.$emit('on-pick', {minDate, maxDate}, false); | |
170 | + } else if (this.minDate && !this.maxDate) { | |
171 | + if (newDate >= this.minDate) { | |
172 | + const maxDate = new Date(newDate.getTime()); | |
173 | + this.rangeState.selecting = false; | |
174 | + | |
175 | + this.$emit('on-pick', {minDate: this.minDate, maxDate}); | |
176 | + } else { | |
206 | 177 | const minDate = new Date(newDate.getTime()); |
207 | - const maxDate = null; | |
208 | - this.rangeState.selecting = true; | |
209 | - this.markRange(this.minDate); | |
210 | - | |
211 | - this.$emit('on-pick', {minDate, maxDate}, false); | |
212 | - } else if (this.minDate && !this.maxDate) { | |
213 | - if (newDate >= this.minDate) { | |
214 | - const maxDate = new Date(newDate.getTime()); | |
215 | - this.rangeState.selecting = false; | |
216 | - | |
217 | - this.$emit('on-pick', {minDate: this.minDate, maxDate}); | |
218 | - } else { | |
219 | - const minDate = new Date(newDate.getTime()); | |
220 | - | |
221 | - this.$emit('on-pick', {minDate, maxDate: this.maxDate}, false); | |
222 | - } | |
223 | - } else if (!this.minDate) { | |
224 | - const minDate = new Date(newDate.getTime()); | |
225 | - this.rangeState.selecting = true; | |
226 | - this.markRange(this.minDate); | |
227 | 178 | |
228 | 179 | this.$emit('on-pick', {minDate, maxDate: this.maxDate}, false); |
229 | 180 | } |
230 | - } else { | |
231 | - this.$emit('on-pick', newDate); | |
181 | + } else if (!this.minDate) { | |
182 | + const minDate = new Date(newDate.getTime()); | |
183 | + this.rangeState.selecting = true; | |
184 | + this.markRange(this.minDate); | |
185 | + | |
186 | + this.$emit('on-pick', {minDate, maxDate: this.maxDate}, false); | |
232 | 187 | } |
188 | + } else { | |
189 | + this.$emit('on-pick', newDate); | |
233 | 190 | } |
234 | 191 | this.$emit('on-pick-click'); |
235 | 192 | }, |
... | ... | @@ -246,7 +203,7 @@ |
246 | 203 | if (target.tagName === 'EM') { |
247 | 204 | const cell = this.cells[parseInt(event.target.getAttribute('index'))]; |
248 | 205 | // if (cell.disabled) return; // todo 待确定 |
249 | - this.rangeState.endDate = this.getDateOfCell(cell); | |
206 | + this.rangeState.endDate = cell.date; | |
250 | 207 | } |
251 | 208 | }, |
252 | 209 | markRange (maxDate) { | ... | ... |
test/unit/specs/date-picker.spec.js
1 | -import { createVue, destroyVM } from '../util'; | |
1 | +import { createVue, destroyVM, stringToDate } from '../util'; | |
2 | 2 | |
3 | 3 | describe('DatePicker.vue', () => { |
4 | 4 | let vm; |
... | ... | @@ -25,4 +25,112 @@ describe('DatePicker.vue', () => { |
25 | 25 | done(); |
26 | 26 | }); |
27 | 27 | }); |
28 | + | |
29 | + it('should create a DatePicker component of type="datetimerange"', done => { | |
30 | + vm = createVue(` | |
31 | + <Date-Picker type="datetimerange"></Date-Picker> | |
32 | + `); | |
33 | + const picker = vm.$children[0]; | |
34 | + expect(picker.$children.length).to.equal(2); | |
35 | + expect(Array.isArray(picker.currentValue)).to.equal(true); | |
36 | + done(); | |
37 | + }); | |
38 | + | |
39 | + it('should create a datetimerange component and pick 2 dates in the current month', done => { | |
40 | + vm = createVue(` | |
41 | + <Date-picker type="datetimerange"></Date-picker> | |
42 | + `); | |
43 | + | |
44 | + const picker = vm.$children[0]; | |
45 | + picker.handleIconClick(); | |
46 | + vm.$nextTick(() => { | |
47 | + const displayField = vm.$el.querySelector('.ivu-input'); | |
48 | + const clickableCells = vm.$el.querySelectorAll('.ivu-date-picker-cells-cell'); | |
49 | + const lastMonthClass = 'ivu-date-picker-cells-cell-prev-month'; | |
50 | + const firstDayInMonthIndex = [...clickableCells].findIndex(cell => !cell.classList.contains(lastMonthClass)); | |
51 | + | |
52 | + clickableCells[firstDayInMonthIndex].firstElementChild.click(); | |
53 | + vm.$nextTick(() => { | |
54 | + clickableCells[firstDayInMonthIndex + 4].firstElementChild.click(); | |
55 | + vm.$nextTick(() => { | |
56 | + const dayOne = new Date(); | |
57 | + dayOne.setDate(1); | |
58 | + dayOne.setHours(0, 0, 0, 0); | |
59 | + const dayFive = new Date(dayOne.getTime()); | |
60 | + dayFive.setDate(5); | |
61 | + dayFive.setHours(0, 0, 0, 0); | |
62 | + | |
63 | + // check pickers internal value | |
64 | + const [startInternalValue, endInternalValue] = picker.currentValue; // Date Objects | |
65 | + expect(Math.abs(dayOne - startInternalValue)).to.equal(0); | |
66 | + expect(Math.abs(dayFive - endInternalValue)).to.equal(0); | |
67 | + | |
68 | + // check pickers display value | |
69 | + const [startDisplayValue, endDisplayValue] = displayField.value.split(' - ').map(stringToDate); // Date Objects | |
70 | + expect(Math.abs(dayOne - startDisplayValue)).to.equal(0); | |
71 | + expect(Math.abs(dayFive - endDisplayValue)).to.equal(0); | |
72 | + | |
73 | + done(); | |
74 | + }); | |
75 | + }); | |
76 | + }); | |
77 | + }); | |
78 | + | |
79 | + it('should have same behavior after a reset as before the reset', done => { | |
80 | + vm = createVue(` | |
81 | + <Date-picker type="datetimerange"></Date-picker> | |
82 | + `); | |
83 | + | |
84 | + const picker = vm.$children[0]; | |
85 | + picker.handleIconClick(); | |
86 | + vm.$nextTick(() => { | |
87 | + const displayField = vm.$el.querySelector('.ivu-input'); | |
88 | + const clickableCells = vm.$el.querySelectorAll('.ivu-date-picker-cells-cell'); | |
89 | + const lastMonthClass = 'ivu-date-picker-cells-cell-prev-month'; | |
90 | + const firstDayInMonthIndex = [...clickableCells].findIndex(cell => !cell.classList.contains(lastMonthClass)); | |
91 | + | |
92 | + // choose first date | |
93 | + clickableCells[firstDayInMonthIndex].firstElementChild.click(); | |
94 | + vm.$nextTick(() => { | |
95 | + // choose second date | |
96 | + clickableCells[firstDayInMonthIndex + 4].firstElementChild.click(); | |
97 | + vm.$nextTick(() => { | |
98 | + // cache first values | |
99 | + const [startInternalValue, endInternalValue] = picker.currentValue; // Date Objects | |
100 | + const [startDisplayValue, endDisplayValue] = displayField.value.split(' - ').map(stringToDate); // Date Objects | |
101 | + | |
102 | + // clear picker | |
103 | + picker.handleClear(); | |
104 | + vm.$nextTick(() => { | |
105 | + // it should be closed by now | |
106 | + expect(picker.visible).to.equal(false); | |
107 | + // open picker again | |
108 | + picker.handleIconClick(); | |
109 | + | |
110 | + vm.$nextTick(() => { | |
111 | + expect(picker.visible).to.equal(true); | |
112 | + expect(JSON.stringify(picker.currentValue)).to.equal('[null,null]'); | |
113 | + expect(displayField.value).to.equal(''); | |
114 | + | |
115 | + clickableCells[firstDayInMonthIndex].firstElementChild.click(); | |
116 | + vm.$nextTick(() => { | |
117 | + clickableCells[firstDayInMonthIndex + 4].firstElementChild.click(); | |
118 | + vm.$nextTick(() => { | |
119 | + // recheck internal values | |
120 | + expect(Math.abs(picker.currentValue[0] - startInternalValue)).to.equal(0); | |
121 | + expect(Math.abs(picker.currentValue[1] - endInternalValue)).to.equal(0); | |
122 | + // recheck display value | |
123 | + const [_startDisplayValue, _endDisplayValue] = displayField.value.split(' - ').map(stringToDate); // Date Objects | |
124 | + expect(Math.abs(_startDisplayValue - startDisplayValue)).to.equal(0); | |
125 | + expect(Math.abs(_endDisplayValue - endDisplayValue)).to.equal(0); | |
126 | + | |
127 | + done(); | |
128 | + }); | |
129 | + }); | |
130 | + }); | |
131 | + }); | |
132 | + }); | |
133 | + }); | |
134 | + }); | |
135 | + }); | |
28 | 136 | }); | ... | ... |
test/unit/util.js
... | ... | @@ -58,6 +58,16 @@ exports.createTest = function(Compo, propsData = {}, mounted = false) { |
58 | 58 | }; |
59 | 59 | |
60 | 60 | /** |
61 | + * Transform Date string (yyyy-mm-dd hh:mm:ss) to Date object | |
62 | + * @param {String} | |
63 | + */ | |
64 | +exports.stringToDate = function(str) { | |
65 | + const parts = str.split(/[^\d]/).filter(Boolean); | |
66 | + parts[1] = parts[1] - 1; | |
67 | + return new Date(...parts); | |
68 | +}; | |
69 | + | |
70 | +/** | |
61 | 71 | * 触发一个事件 |
62 | 72 | * mouseenter, mouseleave, mouseover, keyup, change, click 等 |
63 | 73 | * @param {Element} elm | ... | ... |